[
  {
    "path": ".dockerignore",
    "content": "# binaries\n*.a\n*.so\n*.so.*\n/demo/demo\n\n# NetBeans Project\nnbproject\n\n# IDEA-based IDE project\n.idea/\n\n# VSCode\n.vscode/\n\n# build configuration autogenerated files\n/build\n/output\n*.swp\nCMakeFiles/\nCMakeTmp/\nCMakeCache.txt\nCMakeDoxyfile.in\nCMakeDoxygenDefaults.cmake\ncmake_install.cmake\ninstall_manifest.txt\n_CPack_Packages/\nCPackConfig.cmake\nCPackSourceConfig.cmake\nCTestTestfile.cmake\nTesting/\n/include_public/anjay/anjay_config.h\nposix-config.h\nanjay-*.cmake\nDoxyfile\n/doc/sphinx/source/conf.py\ntools/test-framework-tools/pymbedtls/build/\n\n# other\nMakefile\n*.log\n*.gcda\n*.gcno\n*.gcov\n*~\ntags\n.ycm_extra_conf.py\n.ycm_extra_conf.pyc\n/tests/codegen/*.c\n/tests/codegen/*.cpp\n/tests/integration/build\n__pycache__/\n.gdb_history\n*.orig\n*.egg-info\n*.pyc\n/tools/test-framework-tools/pymbedtls*\ncompile_commands.json\n\n/avs_commons/install\n/doc/sphinx/build\n/doc/sphinx/html\n/doc/doxygen\n/doc/sphinx/source/.doctrees\n!/doc/sphinx/Makefile\n\n# built tutorials\nexamples/build\n"
  },
  {
    "path": ".github/workflows/anjay-tests.yml",
    "content": "# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nname: Anjay tests\non: [push]\njobs:\n  ubuntu2204-compilers-test:\n    runs-on: ubuntu-latest\n    container: avsystemembedded/anjay-travis:ubuntu-22.04-3.0\n    env:\n      CC: ${{ matrix.CC }}\n      CXX: ${{ matrix.CXX }}\n      MEM_CHECK_TOOL: ${{ matrix.MEM_CHECK_TOOL }}\n    steps:\n      # NOTE: workaround for https://github.com/actions/checkout/issues/760\n      - run: git config --global safe.directory '*'\n      # NOTE: v2 requires Git 2.18 for submodules, it's not present in the image\n      - uses: actions/checkout@v1\n        with:\n          submodules: recursive\n      - run: apt-get update\n      - run: apt-get -y install $CC $CXX\n      - run: ./devconfig $MEM_CHECK_TOOL --without-analysis -DWITH_VALGRIND_TRACK_ORIGINS=OFF -DWITH_IPV6=OFF\n      - run: |\n          . /venv/bin/activate\n          env CC=gcc LC_ALL=C.UTF-8 make -j\n      - run: |\n          . /venv/bin/activate\n          env CC=gcc LC_ALL=C.UTF-8 make check\n    strategy:\n      fail-fast: false\n      matrix:\n        include:\n          - CC: gcc-11\n            CXX: g++-11\n            MEM_CHECK_TOOL: --with-valgrind\n          - CC: gcc-12\n            CXX: g++-12\n            MEM_CHECK_TOOL: --with-valgrind\n          - CC: clang-11\n            CXX: clang++-11\n            MEM_CHECK_TOOL: --with-valgrind\n          - CC: clang-12\n            CXX: clang++-12\n            MEM_CHECK_TOOL: --with-valgrind\n          - CC: clang-13\n            CXX: clang++-13\n            MEM_CHECK_TOOL: --with-valgrind\n          - CC: clang-14\n            CXX: clang++-14\n            MEM_CHECK_TOOL: --without-memcheck # NOTE: workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=1758782\n\n  ubuntu2404-compilers-test:\n    runs-on: ubuntu-latest\n    container: avsystemembedded/anjay-travis:ubuntu-24.04-3.0\n    env:\n      CC: ${{ matrix.CC }}\n      CXX: ${{ matrix.CXX }}\n      MEM_CHECK_TOOL: ${{ matrix.MEM_CHECK_TOOL }}\n    steps:\n      # NOTE: workaround for https://github.com/actions/checkout/issues/760\n      - run: git config --global safe.directory '*'\n      # NOTE: v2 requires Git 2.18 for submodules, it's not present in the image\n      - uses: actions/checkout@v1\n        with:\n          submodules: recursive\n      - run: apt-get update\n      - run: apt-get -y install $CC $CXX\n      - run: ./devconfig $MEM_CHECK_TOOL --without-analysis -DWITH_VALGRIND_TRACK_ORIGINS=OFF -DWITH_IPV6=OFF\n      - run: |\n          . /venv/bin/activate\n          env CC=gcc LC_ALL=C.UTF-8 make -j\n      - run: |\n          . /venv/bin/activate\n          env CC=gcc LC_ALL=C.UTF-8 make check\n    strategy:\n      fail-fast: false\n      matrix:\n        include:\n          - CC: gcc-12\n            CXX: g++-12\n            MEM_CHECK_TOOL: --with-valgrind\n          - CC: gcc-13\n            CXX: g++-13\n            MEM_CHECK_TOOL: --with-valgrind\n          - CC: gcc-14\n            CXX: g++-14\n            MEM_CHECK_TOOL: --with-valgrind\n          - CC: clang-17\n            CXX: clang++-17\n            MEM_CHECK_TOOL: --with-valgrind\n          - CC: clang-18\n            CXX: clang++-18\n            MEM_CHECK_TOOL: --with-valgrind\n          - CC: clang-19\n            CXX: clang++-19\n            MEM_CHECK_TOOL: --with-valgrind\n\n  rockylinux9-compilers-test:\n    runs-on: ubuntu-latest\n    container: avsystemembedded/anjay-travis:rockylinux-9-3.0\n    env:\n      CC: ${{ matrix.CC }}\n      CXX: ${{ matrix.CXX }}\n    steps:\n      # NOTE: workaround for https://github.com/actions/checkout/issues/760\n      - run: git config --global safe.directory '*'\n      # NOTE: v2 requires Git 2.18 for submodules, it's not present in the image\n      - uses: actions/checkout@v1\n        with:\n          submodules: recursive\n      - run: dnf update -y --nobest\n      - run: dnf install -y $CC\n      # Solve issues with EPERM when running dumpcap\n      - run: setcap '' $(which dumpcap)\n      - run: ./devconfig --with-valgrind --without-analysis -DWITH_VALGRIND_TRACK_ORIGINS=OFF -DWITH_IPV6=OFF\n      - run: |\n          . /venv/bin/activate\n          env CC=gcc LC_ALL=C.UTF-8 make -j\n      - run: |\n          . /venv/bin/activate\n          env CC=gcc LC_ALL=C.UTF-8 make check\n    strategy:\n      fail-fast: false\n      matrix:\n        include:\n          - CC: gcc\n            CXX: g++\n          - CC: clang\n            CXX: clang++\n\n  macOS-test:\n    runs-on: \"${{ matrix.RUNNER }}\"\n    steps:\n      # NOTE: v2 requires Git 2.18 for submodules, it's not present in the image\n      - uses: actions/checkout@v1\n        with:\n          submodules: recursive\n      - run: brew update\n      # NOTE: try the brew install command twice to work around \"brew link\" errors\n      - run: INSTALL_CMD=\"brew install python3 openssl\"; $INSTALL_CMD || $INSTALL_CMD\n      # NOTE: Some tests don't pass on mbedTLS 3.6.2 now, so we need to install an older version\n      #       Homebrew only specifiers major version of mbedTLS, so let's pin the version to 3.6.0 manually\n      - run: curl -f https://raw.githubusercontent.com/Homebrew/homebrew-core/219dabf6cab172fb8b62b4d8598e016e190c3c20/Formula/m/mbedtls.rb > /tmp/mbedtls.rb\n      # HACK: New version of homebrew requires us to install a formulae from a tap, create a tap for our mbedTLS.\n      # The change in homebrew is intentional and won't be fixed, for more details see: https://github.com/orgs/Homebrew/discussions/6351\n      - run: brew tap-new embedded/mbedtls\n      - run: |\n          TAP_DIR=\"$(brew --repo embedded/mbedtls)\"\n          mkdir -p \"${TAP_DIR}/Formula\"\n          cp /tmp/mbedtls.rb \"${TAP_DIR}/Formula/mbedtls.rb\"\n          git -C \"${TAP_DIR}\" add Formula/mbedtls.rb\n          git -C \"${TAP_DIR}\" -c user.name=\"CI\" -c user.email=\"ci@example.com\" commit -m \"Add custom mbedtls formula\"\n      - run: brew install embedded/mbedtls/mbedtls\n      - run: brew pin mbedtls\n      # NOTE: The above command may have installed a new version of Python, that's why we launch it weirdly\n      # NOTE: We manualy create env since devconfig does not use /usr/bin/env python3\n      - run: /usr/bin/env python3 -m venv venv\n      - run: env JAVA_HOME=\"$JAVA_HOME_17_X64\" ./devconfig --with-asan --without-analysis --no-examples -DWITH_VALGRIND_TRACK_ORIGINS=OFF -DWITH_IPV6=OFF -DMBEDTLS_ROOT_DIR=/opt/homebrew/opt/mbedtls\n      - run: |\n          . venv/bin/activate\n          LC_ALL=en_US.UTF-8 make -j\n      - run: |\n          . venv/bin/activate\n          LC_ALL=en_US.UTF-8 make check\n    strategy:\n      fail-fast: false\n      matrix:\n        include:\n          - RUNNER: macos-14\n          - RUNNER: macos-15\n          - RUNNER: macos-26\n"
  },
  {
    "path": ".github/workflows/coverity.yml",
    "content": "# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nname: Coverity\non:\n  push:\n    branches: [master]\njobs:\n  coverity:\n    runs-on: ubuntu-latest\n    container: avsystemembedded/anjay-travis:ubuntu-22.04-3.0\n    env:\n      # NOTE: These need to be configured in GitHub Actions GUI\n      COVERITY_EMAIL: ${{ secrets.COVERITY_EMAIL }}\n      COVERITY_SCAN_TOKEN: ${{ secrets.COVERITY_SCAN_TOKEN }}\n    steps:\n      # NOTE: workaround for https://github.com/actions/checkout/issues/760\n      - run: git config --global safe.directory '*'\n      # NOTE: v2 requires Git 2.18 for submodules, it's not present in the image\n      - uses: actions/checkout@v1\n        with:\n          submodules: recursive\n      - run: |\n          mkdir -p coverity_tool\n          bash -c 'cd coverity_tool && \\\n              wget https://scan.coverity.com/download/linux64 --post-data \"token=$COVERITY_SCAN_TOKEN&project=AVSystem%2FAnjay\" -O coverity_tool.tgz && \\\n              tar xf coverity_tool.tgz'\n      - run: ./devconfig --without-analysis -DWITH_NESTED_FUNCTION_MUTEX_LOCKS=OFF -DWITH_IPV6=OFF\n      - run: env LC_ALL=C.UTF-8 ./coverity_tool/cov-analysis*/bin/cov-build --dir cov-int make\n      - run: tar zcf cov-int.tgz cov-int\n      - run: |\n          curl --form \"token=$COVERITY_SCAN_TOKEN\" \\\n               --form \"email=$COVERITY_EMAIL\" \\\n               --form file=@cov-int.tgz \\\n               --form version=$(git rev-parse HEAD) \\\n               --form description=\"\" \\\n               https://scan.coverity.com/builds?project=AVSystem%2FAnjay\n"
  },
  {
    "path": ".gitignore",
    "content": "# binaries\n*.a\n*.so\n*.so.*\n/demo/demo\n\n# NetBeans Project\nnbproject\n\n# IDEA-based IDE project\n.idea/\n\n# VSCode\n.vscode/\n\n# build configuration autogenerated files\n/build\n/coverage\n/output\n*.swp\nCMakeFiles/\nCMakeTmp/\nCMakeCache.txt\nCMakeDoxyfile.in\nCMakeDoxygenDefaults.cmake\ncmake_install.cmake\ninstall_manifest.txt\n_CPack_Packages/\nCPackConfig.cmake\nCPackSourceConfig.cmake\nCTestTestfile.cmake\nTesting/\n/include_public/anjay/anjay_config.h\nposix-config.h\nanjay-*.cmake\nDoxyfile\n/doc/sphinx/source/conf.py\n/doc/sphinx/source_api/api_generated\ntests/integration/run_tests.sh\n\n# other\nMakefile\n*.log\n*.gcda\n*.gcno\n*.gcov\n*~\ntags\n.ycm_extra_conf.py\n.ycm_extra_conf.pyc\n/tests/codegen/*.c\n/tests/codegen/*.cpp\n/tests/integration/build\n__pycache__/\n.gdb_history\n*.orig\n*.egg-info\n*.pyc\n*.cache\ncompile_commands.json\n\n/avs_commons/install\n/doc/sphinx/build\n/doc/sphinx/html\n/doc/doxygen/Doxyfile*\n/doc/sphinx/source/.doctrees\n!/doc/sphinx/Makefile\n\n# built tutorials\nexamples/*-build\n\n# Anjay persistence files\n*-persistence.dat\n\n# Python venv\n/venv/\nbuild/\ndist/\n*.egg-info/\n\n# py-build-cmake cache\n.py-build-cmake_cache\n"
  },
  {
    "path": ".gitmodules",
    "content": "[submodule \"deps/avs_commons\"]\n\tpath = deps/avs_commons\n\turl = https://github.com/AVSystem/avs_commons.git\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\n\n## 3.13.1 (April 2nd, 2026)\n\n### Bugfixes\n\n- Tests and documentation corrections\n\n\n## 3.13.0 (March 31st, 2026)\n\n### Features\n- Introduced experimental SSL Error API under `WITH_SSL_ERROR_API` for MbedTLS\n  and custom TLS backends\n- Implemented FW Update Object v1.1 Resources.\n- Introduced support for LwM2M 1.2 features:\n  - LwM2M CBOR format,\n  - Bootstrap Pack operation,\n  - Observation Attributes (carried in Observe, not in Write Attributes)\n  - `hqmax` and `edge` attributes (`con` attribute is supported separatley from\n    LwM2M 1.2, enabled with `ANJAY_WITH_CON_ATTR`),\n  - Discover `depth` parameter,\n  - SenML-ETCH CBOR & JSON formats for composite operations,\n  - deleting Resource Instances.\n\n\n## 3.12.0 (January 30th, 2026)\n\n### BREAKING CHANGES\n\n- PEP 668 was adopted. Using Python based tools requires a virtual environment\n  and `devconfig` script ensures it's activated.\n- Most tools from `tests/integration/framework` were extracted\n  into `tools/test-framework-tools` directory which is being installed\n  in venv by `devconfig`\n\n### Bugfixes\n- Fixed crash in avs_coap when block transfers are disabled and outgoing\n  message does not fit into the external output buffer.\n- (commercial version only) Fixed a bug that made it impossible to set a Master\n  Secret which length was anything other than 16 bytes (OSCORE).\n- Fixed avs_coap tests for GCC 15\n\n### Features\n- Added traffic interceptor usage to demo application.\n- (commercial version only) Made it possible to set the maximum length of the\n  Master Secret and Master Salt using the AVS_COAP_OSCORE_MASTER_SECRET_SIZE\n  and AVS_COAP_OSCORE_MASTER_SALT_SIZE options, respectively.\n- Removed experimental tags from server connection status API, IPSO objects v2\n  API, Software Management object API, CoAP Download retry API and\n  Confirmable Notification status callback API.\n\n### Improvements\n- Reworked the help menu in Anjay Demo.\n- (commercial version only) Added support for running PKCS11 integration tests\n  with OpenSSL as (D)TLS backend.\n- Migrated pymbedtls build system to pyproject.toml.\n\n## 3.11.0 (September 26th, 2025)\n\n### BREAKING CHANGES\n\n- Default value of maximal holdoff time during Bootstrap limited from 120 sec.\n  to 20 sec. The limit can still be changed using ANJAY_MAX_HOLDOFF_TIME define.\n- If the Server certificate is missing from the Data Model, Anjay falls back to\n  PKIX verification, provided that a Trust Store is available, even when the\n  certificate usage is set to DANE-TA or DANE-EE.\n- avs_commons 5.5.0 that is used by Anjay 3.11.0 stoped passing the trust store\n  to Mbed TLS backend for Certificate Usage 2 or 3. For details on how Anjay\n  handles Certificate Usage resource refer to\n  Anjay Specification -> Advanced Topics -> Certificate Usage.\n\n### Bugfixes\n\n- Updated avs_commons to a version without a PSK-mode vulnerability in the Mbed\n  TLS backend where a client configured for PSK could connect to a server that\n  did not know the PSK, due to advertising non-PSK key exchange and skipping\n  certificate verification. This issue affected only TLS 1.3 connections with\n  Mbed TLS versions ≥ 3.6.1.\n  For details, see: https://github.com/AVSystem/avs_commons/releases/tag/5.5.0\n\n### Features\n- (commercial version only) Added support for configuring OSCORE Object for each\n  Security Object in Anjay Demo.\n\n## 3.10.0 (May 28th, 2025)\n\n### Features\n- Instruction how to generate packages for Anjay Demo was added to documentation.\n- Dropped support for Ubuntu 18.04 and removed tests for it.\n\n### Bugfixes\n\n- Fixed `devconfig` script which was incorrectly setting Anjay version in logs\n  to \"unknown\".\n- Added canceling the Register or Update message exchange if it's in progress\n  when Server Disable (with resource execution or by API) is called that could\n  have led to disabling the server infinitely.\n\n### Improvements\n\n- Unified script for generating packages in integration tests, now it can also\n  be used to generate packages for the Software Management object.\n- The format of the metadata for packages used in integration tests and the\n  demo has been revised to be more unambiguous and unified.\n- Added `confirmable_notification_status_cb` handler that is called if\n  acknowledgement for confirmable notification is received from the Server or\n  some error has occurred.\n\n## 3.9.0 (February 28th, 2025)\n\n### Features\n\n- LwM2M Gateway functionality was added. To see the feature description, API\n  documentation and tutorial on its usage, see Anjay docs\n- Added a ``coap_downloader_retry_count`` and ``coap_downloader_retry_delay``\n  configuration options that allow to resume the CoAP download process in case\n  of network errors.\n\n### Improvements\n\n- Extended the functionality of the flag enabling Connection ID to also include\n  connections to the file download CoAP server, in addition to the LwM2M server\n- Migrated GitHub Actions tests on macOS to the macos-14 runner.\n- Refactored test_ghactions.py.\n- Added Github Actions test on macOS for the AppleClang.\n\n### Bugfixes\n\n- Ensured that Github Actions tests on macOS actually runs on LLVM installed\n  from Homebrew instead of AppleClang.\n- Minor fix in the devconfig.\n\n## 3.8.1 (November 13th, 2024)\n\n### Improvements\n\n- Improved the coverage script and switched to lcov.\n- In case when the LwM2M server answers with an RST message to a notification\n  that is yielding an error value (e.g. failure to read), which effectively\n  cancels the notification, Anjay is not infinitely trying to transmit that\n  message with error value once again. New behavior is enabled by default, and\n  controlled with `WITH_AVS_COAP_OBSERVE_FORCE_CANCEL_ON_UNACKED_ERROR` option\n  of `avs_coap`. Existing projects have to opt-in explicitly.\n- Added `--nobest` flag to `dnf update` in Rockylinux image preparation for\n  tests to solve installation candidates conflicts.\n\n### Bugfixes\n\n- Actually fixed compatibility with Mbed TLS 3.6.\n- Fixed compatibility of integration test framework with Mbed TLS versions that\n  enabled TLS 1.3, but didn't use `MBEDTLS_USE_PSA_CRYPTO`.\n- The -Wformat warning appearing in some compilers has been fixed.\n- Fixed LwM2M CBOR parser incorrectly accepting inputs containing empty arrays\n  as keys\n- Prevent from generating non unique session tokens when the monotonic system\n  clock granulation is not fine enough.\n- Refactored how timeouts are handled in pymbedtls to be in line with use of\n  mbedTLS in avs_commons.\n\n## 3.8.0 (May 28th, 2024)\n\n### BREAKING CHANGES\n\n- Timeout in case of sending a Confirmable Notification does not cancel the\n  observation anymore by default.\n\n### Features\n\n- Added a ``connection_error_is_registration_failure`` configuration option that\n  allows handling connection errors as Register failures, including the\n  automatic retry mechanism\n- Added experimental server connection status API.\n\n### Improvements\n\n- (commercial version only) Changed MSISDN matching method in SMS binding\n  feature to allow handling messages with Short Codes as originating number\n\n### Bugfixes\n\n- Fixed a corner case in which a connection error during a non-first Register\n  attempt could cause uncontrolled infinite retries\n- Fixed a bug in demo of Advanced Firmware Update module that prevented\n  proper handling of security config for targets other than APP\n- Fixed a bug that caused anjay_next_planned_notify_trigger family APIs to\n  return an invalid value after canceling observations\n\n## 3.7.0 (February 16th, 2024)\n\n### Features\n\n- Added support for LwM2M 1.2 server object requirement for infinite lifetime\n  (lifetime == 0).\n- Introduced @experimental and @deprecated Doxygen tags.\n- Added experimental IPSO objects v2 API.\n- Added experimental Software Management object API.\n\n### Improvements\n\n- Improved integration tests compability and framework stability\n- Added support for wget2 for validating links in documentation (for HTTP/2\n  support)\n\n### Bugfixes\n\n- Refactored generation of blockwise Confirmable notifications to avoid a\n  possible assertion failure; requests for subsequent blocks of such\n  notifications are now sent as Piggybacked responses\n- Fixed a bug in the documentation of an object definition struct\n- (commercial version only) Fixed problems with running some tests on systems\n  with Mbed TLS 3.x\n- Fixed a few assertion and pointer punning issues regarding calls to IPSO\n  objects APIs in erroneous cases\n- Prevent from trying to store empty cert/keys on HSM\n\n## 3.6.1 (November 21st, 2023)\n\n### Improvements\n\n- Optimized heap memory usage: SenML CBOR payloads for Send and Notify\n  operations are no longer serialized in memory in their entirety unless their\n  contents depend on the Access Control object state\n- Added a public define for MSISDN string size\n- Optimized \"Out of memory\" logs in favor of a smaller flash memory footprint\n- (commercial feature only) Added API for querying Anjay for SSID associated\n  with given MSISDN and SMS Trigger resource value\n\n### Bugfixes\n\n- (commercial feature only) Fixes for various bugs that could cause invalid\n  memory accesses when restoring data from corrupted core persistence data\n\n## 3.6.0 (October 9th, 2023)\n\n### Features\n\n- Added APIs for setting custom timeouts for downloads performed over CoAP+TCP\n  and HTTP, including firmware update downloads\n- Added `requirements.txt` file to manage Python dependencies more efficiently\n\n### Improvements\n\n- Clarified documentation on behavior of Firmware Update and Advanced Firmware\n  Update modules when ``anjay_fw_update_get_security_config_t`` and\n  ``anjay_advanced_fw_update_perform_upgrade_t`` callbacks, respectively, are\n  not defined.\n- Added compilation flag that disables all composite operations\n\n### Bugfixes\n\n- Updated integration tests so that they pass on macOS\n- Fix abort scenario in Advanced Firmware Update module\n\n## 3.5.0 (September 7th, 2023)\n\n### BREAKING CHANGES\n\n- Reversed the order of calling the ``delivery_handler`` callback vs. canceling\n  the observation when sending notifications with 4.xx or 5.xx code; this change\n  is breaking only for direct users of ``avs_coap`` API\n\n### Features\n\n- Added APIs for suspending and resuming standalone downloads as well as\n  Firmware Update and Advanced Firmware Update PULL-mode downloads\n- Added standalone versions of the Security and Server object implementations,\n  that can be customized by the end users\n- Added definitions for common Core Object IDs in the public API\n- Removed potentially faulty assertion in code generated by anjay_codegen.py\n- (commercial feature only) added ``anjay_sim_bootstrap_calculate_md5()``\n  function that allows verification whether SIM Bootstrap data has been changed\n  (e.g. as a result of SIM OTA)\n\n### Improvements\n\n- Rewritten Send-based reporting in Advanced Firmware Update in such a way that\n  it will now work with custom implementations of the Server object\n- Simplified the CoAP downloader implementation so that the\n  ``get_remote_hostname`` socket operation is no longer necessary for download\n  resumption\n- Made handling of initial peer CSM messages in CoAP+TCP asynchronous\n- Updated the documentation with more descriptive warnings about functions that\n  require extra care to maintain thread safety\n- Removed ``const`` qualifier from ``MAKE_URI_PATH()`` compound literal which\n  triggers a plausible compiler bug on IAR EWARM v9.30\n- Reading SNI from the Security Object for the FOTA download connection\n\n### Bugfixes\n\n- Fixed a critical bug that caused Anjay to crash when sending notifications\n  with 4.xx or 5.xx code over TCP\n- Fixed a regression introduced in 2.13.0 that prevented the Firmware Update\n  and Advanced Firmware Update from compiling without the\n  ``ANJAY_WITH_DOWNLOADER`` configuration option enabled\n- Fixed a condition where the Register or Update messages could be erroneously\n  regenerated when refreshing server connections while already performing a\n  Register or Update request\n- Fixed a condition where the connection could be erroneously retried\n  automatically when a fatal failure was expected\n- Decoupled the ``WITH_AVS_COAP_TCP`` and ``ANJAY_WITH_LWM2M11`` configuration\n  options so that they can be set independently as intended\n- Fixed the ``devconfig`` script and Github Actions configuration for better\n  compatibility with building on macOS\n- Refactored TCP binding handling in integration tests for more reliability\n- Fixed the case where CoAP+TCP Abort message could erroneously be sent multiple\n  times\n- Loosened some time constraints in Advanced Firmware Update tests\n- Fixed supplemental iid sort in Advanced Firmware Update\n- Fixed too early restart while performing an upgrade using Advanced Firmware\n  Update module in Anjay demo app\n- Fixed too early persistence write while performing an upgrade using Advanced\n  Firmware Update module in Anjay demo app\n\n## 3.4.1 (June 23rd, 2023)\n\n### Features\n\n- (commercial feature only) New ``sim_bootstrap`` module that implements the\n  logic necessary to extract the EF(DODF-bootstrap) file contents from a smart\n  card\n\n### Bugfixes\n\n- Fixed a potential crash in case of a specific out-of-memory condition in\n  Advanced Firmware Update\n- Fixed `anjay_config_log.h` so that all non-binary configuration options are\n  properly logged\n- Fixed a regression from 3.4.0 that prevented ``nsh_lwm2m.py`` from launching\n\n## 3.4.0 (June 14th, 2023)\n\n### Features\n\n- New APIs for server connection lifecycle management:\n  ``anjay_server_schedule_reconnect()`` and ``anjay_schedule_register()``\n- New options in ``anjay_configuration_t`` that allow for optional more strict\n  LwM2M TS compliance: ``update_immediately_on_dm_change`` and\n  ``enable_self_notify``\n- Added option to disable auto-closing of the socket when in queue mode.\n- `avs_coap_observe_cancel()` is now public API (for direct users of avs_coap)\n- Added Advanced Firmware Update as an Anjay module\n- Added simplified demo of Advanced Firmware Update with two firmware images\n- Added tutorial for Advanced Firmware Update\n\n### Improvements\n\n- Observations are now automatically cancelled if the client needs to send a new\n  Register messsage\n- Added explicit casts in macros that involve negating an unsigned value, to\n  silence warnings generated by some compilers\n- Various improvements and refactors of integration tests, to make them run\n  faster and more stable\n- (commercial feature only) Disabled servers are saved now in Core Persistence,\n  which prevents them from registering until the timeout passes with respect to\n  the real clock.\n\n### Bugfixes\n\n- Fixed handling of SenML payload that could cause erroneous behavior and memory\n  leaks when parsing payloads only containing the Base Name field, without Name\n- Fixed error handling in bootstrapper (commercial only) and factory\n  provisioning modules so that the changes are properly rolled back in case of\n  error\n- Fixed assertion error (or 4.05 Method Not Allowed when compiled without\n  assertions) when attempting to set the Disable Timeout resource in the Server\n  object when the `ANJAY_WITHOUT_DEREGISTER` configuration option is set\n- Fixed inequality comparisons on some time values that could cause erroneous\n  behavior on platforms with low-resolution system clocks, and updated unit\n  tests to not assume a high-resolution clock\n- Fixed a problem where `anjay_ongoing_registration_exists()` inconditionally\n  returned `true` if any server connection in a \"disabled\" state existed\n- Timeout when sending a Confirmable Notification now cancels the observation,\n  as required by RFC 7641\n- Removed sending of Release messages when using CoAP+TCP, which fixes the issue\n  of erroneously sending them at the beginning after reconnecting a LwM2M\n  connection socket\n- Fixed the serial port handling code in the sample NIDD driver, to properly\n  handle cases where more than one line is received in a single read() call\n- Fixed compatibility of integration tests with the current versions of the\n  Python cryptography module\n- Fixed problems with compiling the library without `WITH_AVS_COAP_BLOCK`\n  enabled (contributed by Flonidan A/S)\n- Fixed a bug in the pymbedtls library used by tests, that prevented it from\n  working in DTLS client mode\n- Fixed bug in the \"Custom (D)TLS layers\" code examples\n\n## 3.3.1 (March 10th, 2023)\n\n### Improvements\n\n- `anjay_disable_server()` and `anjay_disable_server_with_timeout()` can now be\n  called on servers that are not enabled as well\n\n### Bugfixes\n\n- Fixed resetting of counter for the Communication Sequence Retry Count resource\n- Fixed a regression in 3.2.0 that prevented the bootstrap connection to be\n  properly closed if the Bootstrap Server is reconfigured in the new bootstrap\n  information and legacy Server-Initiated Bootstrap is disabled\n\n## 3.3.0 (February 21st, 2023)\n\n### Features\n\n- New configuration option, `WITHOUT_MODULE_fw_update_PUSH_MODE` (CMake) / `ANJAY_WITHOUT_MODULE_FW_UPDATE_PUSH_MODE` (header), that allows disabling support for the PUSH mode in the Firmware Update module\n\n### Improvements\n\n- Refactored tests to use `avs_stream_inbuf` instead of `avs_unit_memstream`\n- Refactored `anjay_input_ctx_constructor_t` to use only a single pointer for input stream\n- Revised support for DTLS Connection ID extension, so that a new handshake is\n  not performed if Connection ID is used, unless an error occurs\n- Revised example Anjay configurations for embedded builds without CMake to\n  optimize compile time and code size\n\n#### Bugfixes\n\n- Fixed a critical regression in 3.2.0 that could cause an assertion failure and use-after-free during Bootstrap Finish if the Bootstrap Server is reconfigured in the new bootstrap information and legacy Server-Initiated Bootstrap is disabled\n- Fixed a bug that could cause undefined behavior when reading the Update Delivery Method resource in the Firmware Update object with thread safety enabled but Downloader disabled\n- Fixed a bug that prevented notifications from being sent in a timely manner after receiving Reset message cancelling an Observation in response to another confirmable notification\n- Fixed a bug that could cause an assertion failure when using `anjay_delete_with_core_persistence()` if a primary server connection failed, but a trigger (SMS) connection is operational\n- Fixed the response code of unsuccessful Resource /1/x/9 Bootstrap-Request Trigger execution (e.g. when there is no Bootstrap-Server Account)\n\n## 3.2.1 (December 13th, 2022)\n\n### Improvements\n\n- Added some missing log messages for potential scheduler errors\n- Updated the version of pybind11 used by integration tests to 2.10.1\n\n### Bugfixes\n\n- Fixed a regression in 3.2.0 that caused some invalid Writes to be silently ignored without responding with proper error codes\n- Fixed compatibility of integration tests with Python 3.11 and the current Github macOS environment\n\n## 3.2.0 (December 7th, 2022)\n\n### BREAKING CHANGES\n\n- Observations are now implicitly canceled when the client's endpoint identity changes (i.e., when the socket is reconnected without a successful DTLS session resumption). This is in line with [RFC 7641](https://datatracker.ietf.org/doc/html/rfc7641#page-22) and LwM2M TS requirements (see [Core 6.4.1](https://www.openmobilealliance.org/release/LightweightM2M/V1_1_1-20190617-A/HTML-Version/OMA-TS-LightweightM2M_Core-V1_1_1-20190617-A.html#6-4-1-0-641-Observe-Operation) and [Transport 6.4.3](https://www.openmobilealliance.org/release/LightweightM2M/V1_1_1-20190617-A/HTML-Version/OMA-TS-LightweightM2M_Transport-V1_1_1-20190617-A.html#6-4-3-0-643-Registration-Interface)), but **may break compatibility with some non-well-behaved servers.**\n\n### Features\n\n- New APIs to access information about the last registration time, next registration update time and last communication with a server time\n- Expanded `anjay_resource_observation_status_t` structure so that now `anjay_resource_observation_status()` returns also the number of servers that observe the given Resource (capped at newly introduced `ANJAY_MAX_OBSERVATION_SERVERS_REPORTED_NUMBER`) and their SSIDs\n\n### Improvements\n\n- Migrated GitHub Actions tests from Fedora-36 to RockyLinux-9\n- Added compilation flag to enforce Content-Format in Send messages.\n- Refactored Firmware Update notification handling and simplified internal module support\n- Removed the usage of symbolic links between Python packages to make them usable on Windows\n- Key generation in the factory provisioning script has been rewritten to use the cryptography Python module instead of pyOpenSSL\n- Factory provisioning script now uses elliptic curve cryptography by default in certificate mode\n- `anjay_next_planned_lifecycle_operation()` and `anjay_transport_next_planned_lifecycle_operation()` now properly respect jobs that have been scheduled manually (e.g. `anjay_schedule_registration_update()`)\n\n### Bugfixes\n\n- Fixed a bug that could cause some resources in a Write message to be ignored when they follow a Multiple-Instance Resource entry\n- Fixed semantics of Resources 19 and 20 in the Server object, which were mistakenly swapped\n  - **NOTE:** The persistence format for the Server object has been reinterpreted so that Resources 19 and 20 remain where they were, without taking semantics into account. This will fix configurations provisioned by Servers but may break configuration persisted just after initially configuring it from code.\n- Made sure that `anjay_schedule_registration_update()` forces a single Update request even when followed by `anjay_transport_schedule_reconnect()` or a change of offline mode\n- Made sure that notifications are not sent before the Update operation if one has been scheduled\n- Made sure that `anjay_transport_schedule_reconnect()` properly reconnects the Bootstrap server connection in all cases\n- Made sure that the socket is properly closed when queue mode is enabled, including previously missing cases related to the Send operation and when no CoAP message needs to be sent at all\n- Refactored asynchronous server connection management to avoid race conditions that could lead to required actions (e.g. EST requests) not being performed when the calculated delays were not big enough\n\n## 3.1.2 (August 24th, 2022)\n\n### Improvements\n\n- Reduced code size of the Security object implementation\n- Updated documentation, readme and examples to mention the new EU IoT Cloud platform\n- Migrated GitHub Actions tests to ubuntu-18.04, ubuntu-20.04, ubuntu-22.04, fedora-36 and macos-11\n\n### Bugfixes\n\n- Fixed various compilation warnings\n- Fixed dangerous usage of `avs_realloc()` in the event loop implementation\n\n## 3.1.1 (July 22nd, 2022)\n\n### Improvements\n\n- Added `CHANGELOG.md`\n\n### Bugfixes\n\n- Added the missing return in anjay_dm_handlers.c that could cause undefined behavior when `ANJAY_WITH_THREAD_SAFETY` was disabled\n- Removed the unused option in the factory provisioning script\n- Removed usage of Python 3.6 syntax in tests that caused Github Actions tests to fail\n- Added missing notes about the change to (D)TLS version in all migration guides in the documentation\n- (commercial feature only) Fixed proper handling of changing the disable_legacy_server_initiated_bootstrap across core persistence cycles\n\n## 3.1.0 (July 6th, 2022)\n\n### BREAKING CHANGES\n\n**Note:** the following changes, while technically breaking, are minor, and should not cause problems in most pratical usages. See also: https://docs.avsystem.com/hubfs/Anjay_Docs/Migrating/MigratingFromAnjay30.html\n\n- Changed error handling semantics of anjay_attr_storage_restore() to match other persistence restore functions\n- TLS 1.2 is no longer implicitly set as the default (D)TLS version; the underlying crypto library's default is now used\n\n### Features\n\n- Factory provisioning feature that allows to perform \"Factory bootstrap\" based on SenML CBOR data stream\n- New API: anjay_access_control_set_owner(), allowing to set Owner resource in the Access Control object during the \"Factory bootstrap\" phase\n- New APIs for changing the CoAP transmission parameters, CoAP exchange timeout and DTLS handshake timeouts while the library is running\n\n### Improvements\n\n- Migrated the Observe/Notify subsystem to use the new AVS_SORTED_SET API from avs_commons; this means that avs_rbtree can be disabled, in which case a more lightweight list-based implementation will be used\n- Minor code size optimizations in the Server object implementation\n- Added documentation for the OSCORE commercial feature\n- (D)TLS version can now be set from command line in the demo application\n\n### Bugfixes\n\n- Fixed a bug in anjay_ongoing_registration_exists() that could cause it to always return true if disable_legacy_server_initiated_bootstrap is set to true\n- Fixed improper formatting of the payload describing the data model in the Register message during initial negotiation of the LwM2M version\n- Fixed handling of persistence format versioning for the Security object, that could cause crashes if Anjay was compiled without LwM2M 1.1 support\n- Changed the \"Bootstrap on Registration Failure\" resource in the Server object to be readable, as specified in LwM2M TS 1.2\n- (commercial feature only) Added persistence of runtime LwM2M version in the core persistence feature; previously the client could erroneously use a different LwM2M version than it registered with after core persistence restore\n\n## 3.0.0 (May 18th, 2022)\n\n### BREAKING CHANGES\n\n- Changed license of the free version to AVSystem-5-clause\n- Refactored the attr_storage module as a core feature\n  - Names of the relevant CMake options and configuration macros have changed\n  - anjay_attr_storage_install() has been removed; Attribute Storage is  now always installed if enabled at compilation time\n  - Behavior of anjay_attr_storage_restore() has been changed - this function now fails if supplied source stream is empty\n- The \"con\" attribute is now included in anjay_dm_oi_attributes_t, as it has been standardized for LwM2M TS 1.2\n- Refactored public headers to consistently use conditional compilation; APIs for disabled features are no longer accessible\n- Removed previously deprecated APIs\n- avs_commons 5.0 refactor the API for providing PSK credentials. Please refer to the change log there, or the document below for details: https://docs.avsystem.com/hubfs/Anjay_Docs/Migrating/MigratingFromAnjay215.html\n\n### Features\n\n- LwM2M TS 1.1 support and related features are now available in the open source version; the features include:\n  - Support for TCP binding\n  - Support for SenML JSON, SenML CBOR and raw CBOR content formats\n  - Support for the Send operation\n  - Possibility for automatically moving security credentials provisioned by the Bootstrap Server or the bootstrapper module onto hardware security engines (note: no hardware security engine implementation is provided in the open source version)\n- Security credentials provisioned by the Bootstrap server or bootstrapper module and automatically moved onto hardware security engine can now be marked as \"permanent\" to prevent them from being removed\n- (commercial feature only) Experimental support for some LwM2M TS 1.2 features\n\n### Improvements\n\n- Refactored incoming message handling to make use of the `AVS_NET_SOCKET_HAS_BUFFERED_DATA` feature added in avs_commons 5.0\n- Refactored and simplified internal flow of calling data model handlers\n- Refactored internal handling of communication state\n- Commercial features are now available for separate inclusion, described in the documentation more clearly and feature code examples\n- Various improvements in the documentation\n\n## Anjay 2.15.0 (April 8th, 2022)\n\n### Bugfixes\n\n- Fixed some uninitialized variables in IPSO object implementations\n- Fixed some compilation warnings in unit tests\n- Fixed compatibility of integration tests with OpenSSL 3\n- Fixed socket flag handling in tests that were breaking with some versions of Python\n- Fixed some obsolete information in Doxygen documentation\n- (commercial feature only) Added a validity check for the certificate provisioned via EST; previous code could lead to an assertion failure if the server misbehaved or the system clock was not set correctly\n\n### BREAKING CHANGES\n\n- avs_commons 4.10 contains a refator of PSK security credential handling. Please refer to the change log there, or the document below for details: https://docs.avsystem.com/hubfs/Anjay_Docs/Migrating/MigratingFromAnjay214.html\n\n### Features\n\n- Added a new anjay_event_loop_run_with_error_handling() API that automatically restarts the communication in case of a fatal error\n- (commercial version only) Added support for using PSK security credentials located in hardware security engines\n- (commercial version only) Added the possibility for automatically moving security credentials provisioned by the Bootstrap Server or the bootstrapper module onto hardware security engines\n\n### Improvements\n\n- Added proper support for object versioning in the object stub generator\n- Refactored LOG_VALIDATION_FAILED macros in security and server modules to prevent some compilers to generate code with excessive stack usage\n- Stopped using LOG macro in expression context for better compatibility with external logger implementations\n- Added support for Mbed TLS 3.1 in the pymbedtls module used in tests\n\n### Bugfixes\n\n- Fixed firmware update protocol support being erroneously reported when a custom TLS layer is used\n- Changed default \"Minimum Period\" attribute value to 0, as mandated by the specification\n- Fixed a potential memory leak in fw_update module's cleanup routine\n- Fixed the downloader and fw_update modules erroneously passing empty CoAP ETags to the user\n- Fixed ETag handling in the firmware update tutorial examples\n- Fixed handling of objects with names that contain characters that are invalid for C identifiers in the object stub generator\n- Fixed building documentation on newer versions of Sphinx\n- Prevented the custom TLS layer from building during testing if the dependencies are not met\n- (commercial version only) Fixed the wrong resource being written when updating the TLS/DTLS Alert Code resource\n- (commercial version only) Fixed a bug which could cause communication over coaps+tcp to be stalled due to buffering on the TLS layer\n\nAlso updates avs_commons to version 4.10.0. For details, see https://github.com/AVSystem/avs_commons/releases/tag/4.10.0\n\n## Anjay 2.14.1 (November 29th, 2021)\n\n### Features\n\n- added custom TLS layer tutorial,\n- added possibility to generate code for objects with constant number of instances.\n\n### Improvements\n\n- updated avs_commons to version 4.9.1,\n- made avs_coap work with external logger feature,\n- expanded code generation docs.\n\n### Bugfixes\n\n- fixed erronous links in documentation,\n- removed issues from IPSO objects documentation.\n\n## Anjay 2.14.0 (October 1st, 2021)\n\n### Features\n\n- Added anjay_event_loop_run() and anjay_serve_any() APIs that remove the need to implement the event loop manually in client applications\n- Added predefined implementation of some common IPSO object types\n- (commercial version only) Support for PSA API for hardware-based security\n\n### Improvements\n\n- Moved mutex locking from anjay_sched_run() to scheduler jobs themselves, so that custom scheduler jobs are properly supported when thread safety is enabled; this is technically a breaking change against 2.13.0, but the 2.13.0 behaviour has been classified as a defect\n- Refactored the demo client to use the new event loop API\n- Made data types used by anjay_codegen.py more consistent\n- Various improvements and updates to the documentation, including new tutorials about thread safety and the new APIs\n- Improved integration tests so that they are more deterministic\n- Migrated public CI for the open-source version to GitHub Actions\n\n### Bugfixes\n\n- Fixed incorrect behavior of anjay_ongoing_registration_exists() when Server-Initiated Bootstrap was in use (issue #56)\n- Fixed some potential race conditions that could cause anjay_get_socket_entries() to return invalid sockets when downloads were in progress\n- Fixed obsolete URLs in documentation\n\nAlso updates avs_commons to version 4.9.0. For details, see: https://github.com/AVSystem/avs_commons/releases/tag/4.9.0\n\n## Anjay 2.13.0 (July 19th, 2021)\n\n### Features\n\n- Added optional support for thread safety When enabled, all calls to Anjay library functions are protected using built-in mutex, which allows safe integration into multi-threaded applications.\n\n### Improvements\n\n- General improvements when using fw_update module without ANJAY_WITH_DOWNLOADER\n- Add option in demo to provide identity and psk as ASCII string\n\n### Bugfixes\n- Fixed URL in lwm2m_object_registry.py\n- Fix bug in dockerfile with apt-get install change not refreshing apt index\n\nAlso updates avs_commons to version 4.8.1. For details, see: https://github.com/AVSystem/avs_commons/releases/tag/4.8.1\n\n## Anjay 2.12.0 (June 30th, 2021)\n\n### Features\n\n- Added extended log handler implementation, it can be enabled in demo by `--alternative-logger` argument.\n\n### Improvements\n\n- Endpoint name and local MSISDN are copied during anjay_new() along with the rest of the parameters.\n- Demo now checks if binding mode is compatible with the provided URI.\n\n### Bugfixes\n\n- Fixed the case where the LwM2M server requests a block from the middle.\n- Handle fatal CoAP errors during registration, in case of aborted context device will abort registration. (commercial version only)\n\nAlso updates avs_commons to version 4.8.0. For details, see: https://github.com/AVSystem/avs_commons/releases/tag/4.8.0\n\n## Anjay 2.11.1 (June 2nd, 2021)\n\n### Features\n\n- Added anjay_send_batch_data_add_current_multiple_ignore_not_found(), a variant of anjay_send_batch_data_add_current_multiple() that does not treat non-existing resources as an error\n\n### Improvements\n\n- Simplifications in JSON serialization code\n- Relaxed timeout values in some integration tests\n- Added documentation for internal CoAP packet parsing APIs\n- (commercial version only) Read-Composite and Observe-Composite responses now make use of the \"base name\" and \"base time\" SenML labels\n- (commercial version only) Observe response sequence number is now omitted for the TCP transport, as permitted by RFC 8323\n\n### Bugfixes\n\n- Fixed default content-format selection for simple resources if it is not selected by the server and text/plain is disabled at compile time\n- (commercial version only) Fixed a critical bug in anjay_send_batch_data_add_current_multiple() that could cause the batch to be in an inconsistent state if the underlying read operation failed\n- (commercial version only) Fixed a potential memory leak when restoring observation state in anjay_new_from_core_persistence()\n\nAlso updates avs_commons to version 4.7.2. For details, see: https://github.com/AVSystem/avs_commons/releases/tag/4.7.2\n\n## Anjay 2.11.0 (April 29th, 2021)\n\n### Features\n\n- (commercial version only) Added anjay_send_batch_data_add_current_multiple() API for sending multiple resources with the same timestamp\n- (commercial version only) \"send\" command in demo application now supports sending multiple resources at once\n\n### Improvements\n\n- Added documentation for the avs_coap module\n- Added tutorial for using the LwM2M Send method (available in commercial version only)\n- (commercial version only) Resource Instances can now be created through the Write-Composite operation\n- (commercial version only) Made use of Base Name and Base Time labels when generating SenML documents to reduce message size\n\n### Bugfixes\n\n- Enforced decimal base when handling text/plain content format (previously C-style octal and hexadecimal literals were erroneously supported)\n- Made anjay_ongoing_registration_exists() work even if non-default implementation of the Server object is in use\n- Fixed various compilation warnings\n- (commercial version only) Removed erroneous quotes when reporting LwM2M Enabler version in Register requests and Discover responses for LwM2M 1.1\n- (commercial version only) Proper checks for attempts to send data from forbidden objects (Security, OSCORE) via anjay_send_batch_data_add_current()\n- (commercial version only) Added graceful handling of the case when security information provisioned through EST onto an HSM is not accessible, but necessary to attempt bootstrap\n\nAlso updates avs_commons to version 4.7.1. For details, see: https://github.com/AVSystem/avs_commons/releases/tag/4.7.1\n\n## Anjay 2.10.0 (March 19th, 2021)\n\n### Features\n\n- Added additional_tls_config_clb field to anjay_configuration_t that allows for advanced configuration of the TLS backend\n- Added possibility to mix NoSec and secured server connections in demo client\n- Added pretty-printing of JSON payloads in the NSH testing shell\n- (commercial version only) Added APIs to query times of upcoming Register, Update and Notify events\n- (commercial version only) Implemented reporting of Short Server ID for the OSCORE object on Bootstrap-Discover, as required by LwM2M 1.1\n- (commercial version only) Added support for the Trigger resource in the Server object\n- (commercial version only) Added support for the ID Context resource in the OSCORE object\n\n### Improvements\n\n- Refactored _anjay_sync_access_control() to avoid recursion\n- Refactored debug logging of CoAP BLOCK options\n\n### Bugfixes\n\n- Fixed the issue where pmax-based notifications were not being scheduled when Notification Storing was disabled\n- Fixed semantics of the LwM2M Write operation when writing optional resources that are not supported in the implementation; this behaviour has been clarified between LwM2M TS releases 1.1 and 1.1.1\n- Fixed queue-mode server connections superfluously resuming when exiting offline mode if there is no data to be sent\n- Fixed potential NULL dereferences in Security, Server, Access Control and OSCORE object implementations\n- Fixed potential memory leak in data batch storage module (used by Notify and Send operations)\n- More robust state checking in the TLV output module\n- Fixed some linting checks (visibility, header and code duplication verification) that were not executing properly as part of \"make check\" and deduplicated them between subprojects\n- Fixed a bug in pymbedtls module that caused integration tests to sometimes freeze indefinitely\n- Fixed a bug that prevented lwm2m_decode command in the NSH testing shell from working\n- Fixed a problem with building the demo application on Windows\n- (commercial version only) Made LwM2M Send operation work properly when the server connection is suspended due to queue mode operation\n- (commercial version only) Fixed handling of buffer overflow corner cases in the BG96 driver in the demo application\n\nAlso updates avs_commons to version 4.7. For details, see: https://github.com/AVSystem/avs_commons/releases/tag/4.7\n\n## Anjay 2.9.0 (January 18th, 2021)\n\n### BREAKING CHANGES\n\n- Minimum required CMake version is raised to 3.6\n- avs_commons 4.6 contains a refactor of avs_net_local_address_for_target_host() that may be breaking for users who maintain their own socket integration code\n\nFor more detailed information about breaking changes and how your code needs to be updated, see: https://docs.avsystem.com/hubfs/Anjay_Docs/Migrating/MigratingFromAnjay28.html\n\n### Features\n\n- LwM2M Testing Shell is now included in the open-source version, see https://docs.avsystem.com/hubfs/Anjay_Docs/Tools/CliLwM2MServer.html\n- Demo application can now be built even when some of the optional library features (e.g. bootstrap, Access Control, observation support, persistence) are disabled\n- A new guide for writing custom socket integration code, and example lightweight implementation: https://docs.avsystem.com/hubfs/Anjay_Docs/PortingGuideForNonPOSIXPlatforms/NetworkingAPI.html\n\n### Improvements\n\n- Improved wording in migration documentation to make usage clearer\n- Various improvements to integration tests:\n  - Tests that involve restarting the demo process now retain execution logs for each launch\n  - Fixed a race condition when handling demo process shutdown\n  - (commercial version only) Additional tests for rebuilding client certificate chain\n- (commercial version only) CoAP message cache, previously only enabled for UDP, is now also used for SMS and NIDD transports\n- (commercial version only) Made closing NIDD connection in the BG96 driver more resilient to errors\n\n### Bugfixes\n\n- Removed some misleading log messages\n- (commercial version only) Fixed some dependencies between CMake configuration options so that cmake -DDTLS_BACKEND= . works with default settings\n\nAlso updates avs_commons to version 4.6 which, in addition to the breaking change mentioned above, introduces the following changes:\n\n### Improvements\n\n- Additional tests for the avs_stream module\n\nBugfixes\n- Fixed erroneous bounds check in _avs_crypto_get_data_source_definition()\n- Made removal of PKCS#11 objects more resilient to errors (relevant mostly for commercial Anjay users)\n- Fixed CMake code for importing the libp11 library (relevant mostly for commercial Anjay users)\n\n## Anjay 2.8.0 (November 23rd, 2020)\n\n### BREAKING CHANGES\n\nSee below for breaking changes in avs_commons. Note that these are unlikely to affect users that use CMake for building the library, but may require updating configuration headers when using alternative build systems.\n\nFor more detailed information about breaking changes and how your code needs to be updated, see: https://docs.avsystem.com/hubfs/Anjay_Docs/Migrating/MigratingFromAnjay27.html\n\n### Features\n\n- Made use of avs_commons' new floating point formatting functions, making it possible to link against libc implementations that don't support printf(\"%g\")\n- (commercial version only) Support for Enrollment over Secure Transports (EST-coaps) with key and certificate storage on Hardware Security Modules via avs_commons' OpenSSL engine support\n- (commercial version only) Support for certificate chain reconstruction based on trust store when performing (D)TLS handshake - this is especially useful for EST-based security, as certificates provisioned via /est/crts can be used as client certificate chain during handshake\n\n### Improvements\n\n- Better CMake-level dependencies and error handling for compile-time configuration options\n- Fixed various compile-time warnings\n- Included avs_commons and avs_coap configuration in the TRACE-level configuration report log at initialization time\n- Relaxed timeout for Deregister message in integration tests\n- Integration test target logs path is now configurable in runtest.py\n- Various improvements to documentation and examples:\n  - Attribute storage module is now installed in most tutorials, making them more complete\n  - https://docs.avsystem.com/hubfs/Anjay_Docs/AdvancedTopics/AT-NetworkErrorHandling.html now mentions retry behavior of the commercial version in LwM2M 1.1\n  - API documentation generated by Doxygen now properly includes all commercial-only APIs when run in commercial codebase\n  - Updated installation instructions for CentOS that referred to non-existent URLs\n  - Updated visual style to match corporate identity\n\n### Bugfixes\n\n- Data model persistence routines can no longer be successfully called during the bootstrap procedure, preventing from persisting potentially invalid data\n- Fixed a regression in 2.7.0 that prevented ciphersuite setting from being properly respected for HTTP downloads\n- Fixed a bug that could result in an assertion failure when showing demo client's help message\n- Fixed a bug in \"get_transport\" command implementation in demo client\n- Fixed erroneous setting of AVS_COMMONS_WITH_AVS_CRYPTO_ADVANCED_FEATURES in example configuration headers\n- Removed duplicate file names that could prevent building with some embedded IDEs\n\nAlso updates avs_commons to version 4.5 which introduces the following changes:\n\n### BREAKING CHANGES\n\n- Moved URL handling routines to a separate avs_url component\n- Implementation of avs_net_validate_ip_address() is no longer required when writing custom socket integration layer\n- Hardware Security Module support has been reorganized to allow easier implementation of third-party engines\n\n### Features\n\n- Support for private key generation and removal on Hardware Security Modules via PKCS#11 engine\n- Support for storing and removing certificates stored on Hardware Security Modules via PKCS#11 engine\n- Support for certificate chain reconstruction based on trust store when performing (D)TLS handshake\n- New AVS_DOUBLE_AS_STRING() API and AVS_COMMONS_WITHOUT_FLOAT_FORMAT_SPECIFIERS configuration options, making it possible to stringify floating point numbers on libc implementations that don't support printf(\"%g\")\n\n### Improvements\n\n- Simplified URL hostname validation - it is now somewhat more lenient, but no longer depends on avs_net_validate_ip_address()\n- Removed internal usage of avs_net_validate_ip_address() and reimplemented it as an inline function that wraps avs_net_addrinfo_resolve_ex()\n- Better CMake-level dependencies and compile-time error handling for compile-time configuration options\n- PEM-formatted security objects can now be loaded from buffer in the Mbed TLS backend\n\n### Bugfixes\n\n- Fixed conditional compilation clauses for avs_crypto global initialization\n- Additional NULL checks when loading security information\n- Removed duplicate file names that could prevent building with some embedded IDEs\n\n## Anjay 2.7.0 (October 15th, 2020)\n\n### BREAKING CHANGES\n\n- Changed signature of anjay_security_config_from_dm() and expected lifetime of anjay_security_config_t; removed anjay_fw_update_load_security_from_dm() compatibility alias\n\nNote: For a more detailed information about breaking changes and how your code needs to be updated, see: https://docs.avsystem.com/hubfs/Anjay_Docs/Migrating/MigratingFromAnjay26.html\n\n### Features\n\n- New anjay_download_set_next_block_offset() API that allows skipping parts of downloads\n- (commercial version only) Support for configuring certificate security from external sources during the Factory Bootstrap phase, including initial support for PKCS11-based hardware security\n- (commercial version only) Support for TCP in the NSH testing shell\n\n### Bugfixes\n\n- Fixed support for older versions of Mbed TLS\n- More graceful error handling in anjay_security_object_add_instance()\n- Made the option to disable bootstrap support work again\n- Other bug fixes, partially found using fuzz testing\n\nAlso updates avs_commons to version 4.4 which introduces the following changes:\n\n### BREAKING CHANGES\n\n- Significant refactor of avs_crypto_security_info_union_t family of types (compatibility aliases are available)\n\n### Features\n\n- Initial support for PKCS11-based hardware security\n- New APIs:\n  - avs_crypto_certificate_chain_info_array_persistence()\n  - avs_crypto_certificate_chain_info_from_engine()\n  - avs_crypto_certificate_chain_info_list_persistence()\n  - avs_crypto_certificate_chain_info_persist()\n  - avs_crypto_cert_revocation_list_info_array_persistence()\n  - avs_crypto_cert_revocation_list_info_list_persistence()\n  - avs_crypto_cert_revocation_list_info_persist()\n  - avs_crypto_private_key_info_copy()\n  - avs_crypto_private_key_info_from_engine()\n  - avs_crypto_private_key_info_persistence()\n  - avs_net_socket_dane_tlsa_array_copy()\n  - avs_stream_copy()\n  - avs_stream_offset()\n- Added scripts simplifying unit test code coverage calculation\n\n## Anjay 2.6.1 (August 31st, 2020)\n\n### Features\n\n- Added documentation for the LwM2M testing shell (NSH) - note that the shell itself is only available in the commercial version\n\n### Improvements\n\n- Refactored security key loading flow\n\n### Bugfixes\n\n- Fixed testing scripts to make them work on macOS and Raspberry Pi OS again\n- Added __odr_asan to the list of permitted symbols so that \"make check\" succeeds when the library is built with AddressSanitizer enabled\n- (applicable to commercial version only) Fixed a bug in anjay_server_object_set_lifetime() that could lead to sending the Update message twice afterwards\n\nAlso updates avs_commons to version 4.3.1 which introduces the following changes:\n\n### Improvements\n\n- Replaced the test PKCS#7 file in unit tests with a more modern one, that can be loaded properly with newest releases of Mbed TLS\n\n### Bugfixes\n\n- Made the library compile again with Mbed TLS configured without CRL support or without file system support\n- Fixed some testing code to make it work on macOS and Raspberry Pi OS again\n- Added __odr_asan to the list of permitted symbols so that \"make check\" succeeds when the library is built with AddressSanitizer enabled\n\n## Anjay 2.6.0 (August 25th, 2020)\n\n### Features\n\n- Added compile-time option to disable plaintext and TLV format support\n- Added compile-time option to disable usage of the Deregister message\n- Added support for DANE TLSA entries for downloads\n- Added support for Security and Server (and OSCORE in commercial version) persistence to the demo client\n- (commercial version only) More complete support for Enrollment over Secure Transports (EST-coaps), including:\n  - Support for /est/sren and /est/crts operations\n  - Persistence of EST data\n  - Support for application/pkcs7-mime;smime-type=certs-only content format\n- (commercial version only) Implemented OSCORE object persistence\n- (commercial version only) Support for Matching Type and Certificate Usage Resources in the LwM2M Security object\n- (commercial version only) LwM2M Security object Resources that has previously been only supported through Bootstrap Write, are now also exposed through anjay_security_instance_t\n\n### Improvements\n\n- Anjay can now be used on platforms that do not support handling 64-bit integers through printf() and scanf()\n- Stricter command line option parsing in the demo client\n- Improved help message formatting in the demo client\n- (commercial version only) Various improvements to TLV and CBOR handling in the nsh tool\n\n### Bugfixes\n\n- Fixed a bug that could lead to a blocking receive with infinite timeout when DTLS is in use and handshake messages had to be retransmitted\n  - NOTE: This, strictly speaking, introduces a BREAKING CHANGE in semantics of `anjay_serve()` and `avs_coap_*_handle_incoming_packet()` - as they no longer wait for the first message to arrive, but handle it in a non-blocking manner. However, this should not matter in practice if recommended patterns of these functions' usage are followed, and in the worst case scenario it may cause poorly written event loop code to behave as a busy loop, but it should not prevent the code from working.\n- Stricter parsing of TLV payloads\n- Fixed calculation of block size when resuming CoAP downloads which did not use the ETag option\n- Made tests related to X.509 certificate mode pass with the OpenSSL backend\n- More graceful error handling in downloader when required callbacks are not passed\n- Fixed various compilation warnings\n- (commercial version only) Fixed a bug that could lead to crash if offline mode was toggled during same-socket CoAP download and another LwM2M exchange was also scheduled\n- (commercial version only) Fixed CMake option dependencies for the EST feature\n\nAlso updates avs_commons to version 4.3 which introduces the following changes:\n\n### Features\n\n- Improved trust store handling, including:\n  - Support for configuring usage of system-wide trust store\n  - Support for trusted certificate arrays and lists in addition to single entries\n  - Support for CRLs\n- Support for DANE TLSA entries\n- Support for loading certs-only PKCS#7 files\n- New avs_crypto_client_cert_expiration_date() API\n- Removed dtls_echo_server tool that has been unused since version 4.1\n\n### Bugfixes\n\n- Fixed a bug that prevented compiling avs_commons without TLS support\n- Fixed missing error handling in avs_persistence_sized_buffer()\n- Fixed a bug in safe_add_int64_t() that could cause a crash if the result of addition was INT64_MIN\n- Fixed various compilation warnings\n\n## Anjay 2.5.0 (July 14th, 2020)\n\n### Features\n\n- Updated AvsCommons to 4.2.1\n- Added new API for etag allocation\n- (commercial version only) Added initial support for Enrollment Over Secure Transport (EST)\n\n### Bugfixes\n\n- Fixed segfault in CoAP downloads caused by cancellation in the middle of the transfer\n- Fixed building tests on CentOS\n- Fixed compilation when WITH_ANJAY_LOGS=OFF is used\n- Fixed handling of transactional LwM2M Write\n- (commercial version only) Fixed download suspension for downloads over shared socket\n\n## Anjay 2.4.4 (July 1st, 2020)\n\n### Features\n\n- Updated avs_commons to version 4.2.0\n- (commercial version) Added API for run-time Lifetime management\n\n### Bugfixes\n\n- (commercial version) Fixed corner case handling of Last Bootstrapped Resource\n\n## Anjay 2.4.3 (June 25th, 2020)\n\n### Improvements\n\n- Added workarounds for non-deterministic operation of time-sensitive integration tests\n\n### Bugfixes\n\n- Fixed a critical bug in error handling of notification sending\n- Fixed some bugs in Docker and Travis integration\n- (commercial version only) Fixed a bug in `anjay_transport_*()` functions that prevented them from working correctly with NIDD transport\n\n## Anjay 2.4.2 (June 17th, 2020)\n\n### Features\n\n- (commercial version only) Implemented NIDD MTU management, allowing to configure maximum message size to be sent, and the maximum message to be received\n- (commercial version only) Added binding mode deduction from URI scheme to demo client\n\n### Improvements\n\n- Added Dockerfile to simplify compiling & launching Anjay demo client on various systems\n- Improved error reporting when executing Registration Update Trigger\n\n### Bugfixes\n\n- Fixed incorrect condition in time_object_notify in tutorials' code\n- Fixed compilation issues of pymbedtls on newer GCC versions\n- Fixed compilation of the demo client on Windows\n\n## Anjay 2.4.1 (May 29th, 2020)\n\n- NOTE: The endpoint name and server URI arguments to the demo client are now mandatory\n\n### Improvements\n\n- Fixed various compilation warnings in certain configurations\n- Updated documentation, readme and examples to mention the new Try Anjay platform\n- Added some missing information in \"Porting guide for non-POSIX platforms\" documentation article\n- Additional script for testing Docker configurations used by Travis locally\n\n### Bugfixes\n\n- Updated avs_commons to version 4.1.3, with more fixes in CMake scripts for corner cases when searching for mbed TLSa, and fix for allowing compilation on platforms that define macros that conflict with avs_log verbosity levels (DEBUG, ERROR etc.)\n- Removed the .clang-format file that relied on features specific to an unpublished custom fork of clang-format\n\n## Anjay 2.4a (May 22nd, 2020)\n\nUpdated avs_commons to version 4.1.2, which fixes interoperability problem with CMake versions older than 3.11.\n\n## Anjay 2.4 (May 21st, 2020)\n\n### Features\n\n- Added anjay_ongoing_registration_exists() API\n- Added anjay_server_get_ssids() API for the default implementation of the Server object\n- Made offline mode configurable independently per transport (UDP, TCP; in commercial version also SMS and NIDD) and respected by downloads (including firmware update)\n- Network integration layer (in commercial version, also SMS and NIDD drivers) may now use avs_errno(AVS_ENODEV) as a special error condition that will NOT trigger connection reset\n- (commercial version only) Added avs_send_deferrable() API\n- (commercial version only) Support for reporting State and Result changes using LwM2M Send messages in the fw_update module\n- (commercial version only) Ability to perform CoAP(S) downloads (including firmware update) over the same socket that is already used for LwM2M communication. In particular, this allows downloads over SMS and NIDD.\n\n### Improvements\n\n- fw_update module now allows reset of the state machine during download\n- Ability to report successful firmware update without reboot through the fw_update module is now officially supported\n- (commercial verison only) OSCORE implementation now properly supports kid_ctx negotiation as specified by RFC 8613 Appendix B.2\n- (commercial version only) Improvements to NIDD handling, to make sure that different packet size limits may be used for incoming and outgoing messages (NOTE: For the commercial version, this includes BREAKING CHANGES)\n\n### Bugfixes\n\n- Made some payload processing errors (including text/plain base64 decoding errors) return 4.00 Bad Request properly instead of 5.00 Internal Server Error\n- The default Disable timeout in the default implementation of the Server object is now 86400 as mandated by the spec instead of infinity\n- (commercial version only) Fixed a bug in timeout handling that sometimes caused the bg96_nidd driver to report spurious errors\n\nAlso updates avs_commons to version 4.1.1, which includes the following changes:\n\n### Bugfixes\n\n- Fixed a bug in CMake scripts that caused link errors when using statically linked versions of mbed TLS\n ## Anjay 2.3a (May 15th, 2020)\n BREAKING CHANGES:\n- Removed usages of the ssize_t type. APIs in both Anjay and avs_commons that had it in public signatures have been redesigned\n- (commercial version only) Retry mechanisms described in LwM2M TS 1.1.1, section 6.2.1.2 are now used by default, which changes the default registration retry policy\n- Updated avs_commons to version 4.1, with the following breaking changes:\n  - Renamed public header files for better uniqueness\n  - Redesigned socket creation and in-place decoration APIs, including the addition of a requirement to provide PRNG context\n  - Renamed some public configuration macros, to unify with the updated compile-time configuration pattern\n  - Removed the legacy avs_coap component (the version used by Anjay 1.x)\n  - Removed the mbed TLS custom entropy initializer pattern in favor of the new PRNG framework\n\nNote: For a more detailed information about breaking changes and how your code needs to be fixed, see https://docs.avsystem.com/hubfs/Anjay_Docs/Migrating/MigratingFromAnjay225.html\n\n### Features\n\n- Changed project structure, configuration headers and updated build system, so that building the library without using CMake is now officially supported\n- Allowed public access to Anjay's scheduler using anjay_get_scheduler()\n- Code generator now allows omitting some of the resources during generation\n- (commercial version only) Added support for some of the retry mechanisms described in LwM2M TS 1.1.1, including the following resources:\n  - Bootstrap on Registration Failure\n  - Communication Retry Count\n  - Communication Retry Timer\n  - Communication Sequence Delay Timer\n  - Communication Sequence Retry Count\n- (commercial version only) SMS driver API is now public, allowing for custom driver implementation\n\n### Improvements\n\n- Major overhaul of documentation, including new tutorials for data model and firmware update implementation, as well as guides for migration from both Anjay 1.16 and 2.2\n- Cryptographically secure PRNG is now used whenever possible for generation of CoAP tokens and initial message IDs\n- Improvements to code generator:\n  - Refactored the generated code so that programmatic instantiation of Object Instances is now easier\n  - Code generated in C++ mode is now more object-oriented and idiomatic\n- Thanks to all compile-time configuration now being accessible via a public header, the demo client can now be compiled even when some optional features are disabled\n- Removed some unused code from the open source version\n\n### Bugfixes\n\n- Fixed a problem that could occur while reconnecting to a server, if the host's local address was switching between IPv4 and IPv6\n- Fixed behavior of anjay_exit_offline() when called immediately after anjay_enter_offline()\n- disable_legacy_server_initiated_bootstrap is now properly respected when reconnecting after a connectivity failure\n- anjay_get_string() and anjay_execute_get_arg_value() now report buffer underruns more reliably\n- Added missing HTTP download timeout logic\n- Errors from the socket's send() method are now properly propagated through the CoAP layer\n- Fixed fatal errors that could occur during some specific CoAP error conditions, including during attempts to silently ignore incoming packets\n- Fixes to minor issues found by Coverity\n- Fixed working on platforms where malloc/calloc returns NULL when 0 bytes is requested\n- Fixed compatibility with some embedded compilers\n- Fixed interoperability with servers that use Uri-Path: '' to represent empty query path, as it seems to be permitted by the CoAP RFC\n- Fixed error codes used by the Portfolio object in the demo client\n- Minor fixes in integration testing framework\n- (commercial version only) Added missing support of LwM2M 1.1-specific resources of the Server object to its persistence functions\n- (commercial version only) Fixed various bugs in the bootstrapper module\n- (commercial version only) Additional verification of NIDD URLs\n- (commercial version only) More robust error handling in demo client's NIDD driver\n\nAlso updates avs_commons to version 4.1, which includes the following changes, in addition to those mentioned above as \"breaking changes\"\n\n\n### Features\n\n- Building without CMake is now officially supported\n- Added idiomatic C++ wrapper for AVS_LIST\n- New API for cryptographically safe PRNGs in avs_crypto\n- File-based streams and default log handler can now be disabled at compile time\n\n### Bugfixes\n\n- Fixed a bug in the default socket implementation that prevented compiling on platforms without the IP_TOS socket option support\n- Fixed improper parsing of empty host in URLs\n- Some previously missed log messages now properly respect WITH_AVS_MICRO_LOGS\n- Fixed a bug in netbuf stream's error handling\n\n## Anjay 2.2.5 (February 7th, 2020)\n\n### Bugfixes\n\n- Updated avs_commons to version 4.0.3, which includes:\n  - Fix for scope of avs_net_mbedtls_entropy_init() declaration in deps.h\n  - Fix that prevented net_impl.c from compiling when IP_TOS is not available\n\n## Anjay 2.2.4 (January 30th, 2020)\n\n### Bugfixes\n\n- Fixed bugs that caused problems with compilation on macOS and Travis\n- avs_commons 4.0.2 include a fix to TLS backend data loader unit tests\n\n### Features\n\n- Added support for \"micro logs\", removing most of log strings to save space, while retaining all the information useful for debugging\n- avs_commons 4.0.2 include support for proper RFC 6125-compliant validation of certificates against hostnames in the OpenSSL backend\n\n\n## Anjay 2.2.3 (January 17th, 2020)\n\n### Bugfixes\n\n- Fixed error in CoAP message ID assignment when a CoAP request was being sent from a response handler\n\n### Features\n\n- Added anjay_resource_observation_status() API to the open source version\n- Added --server-public-key-file to the demo application\n\n## Anjay 2.2.2 (December 20th, 2019)\n\n### Bugfixes\n\n- Fixed an assertion failure on Cancel Observe arriving while sending a confirmable notification\n\n### Improvements\n\n- Minor workarounds for various compiler warnings\n- Fixed unnecessary building of CoAP library test targets\n\nAlso updates avs_commons to version 4.0.1, which includes the following changes:\n\n### Bugfixes\n\n- Prevented certificate-based ciphersuites from being sent in Client Hello when PSK is used over the OpenSSL backend\n\n### Features\n\n- Introduced \"micro log\" feature and AVS_DISPOSABLE_LOG() macro\n\n## Anjay 2.2.1 (December 6th, 2019)\n\nThis release synchronizes the open-source version of Anjay with the commercial branch, that has been in development since September 2018. Versions 2.0.0 (June 14th, 2019) through 2.2.0 have only been released to commercial customers.\n\nNote that the commercial version includes extensive support for LwM2M TS 1.1 features. These are not available in the open-source version and not described in this changelog.\n\n### BREAKING CHANGES\n\n- Redesigned data model APIs\n  - Replaced instance_it and instance_present handlers with list_instances\n  - Simplified the instance_create API\n    - IIDs for Create are always assigned by Anjay - user code no longer needs to allocate IDs\n    - Removed the SSID argument\n  - Replaced supported_rids field, resource_present, resource_it and resource_operations handlers with list_resources\n  - Redesigned handling of Multiple-Instance Resources:\n    - Old APIs for resource arrays are no longer available\n    - Read and write handlers now take additional Resource Instance ID argument\n    - Removed resource_dim handler\n    - Introduced new resource_reset and list_resource_instances handlers\n- Renamed various types, in particular those related to LwM2M Attributes\n- Disallowed 65535 for all levels of IDs, as mandated in LwM2M TS 1.1\n- Changed custom objects in demo client to use Object IDs from the range of Bulk Objects Reserved by AVSystem\n  - Additional minor changes to the Test object semantics\n- It is now not possible to build both static and shared versions of Anjay as part of the same build\n- Heavily refactored error handling; some public APIs may require use of avs_error_t instead of plain integer error codes\n- Removed stubs of commercial-only APIs from the open source version\n\n### Features\n\n- Entirely rewritten CoAP implementation\n- Register, Update, Request Bootstrap and confirmable Notify messages are now sent asynchronously and do not block other functionality\n- Notifications in JSON format now include timestamps\n- Added setting to prefer hierarchical Content-Format even when reading simple resources, to improve interoperability with certain server implementations\n- Added support for DTLS Connection ID extension if using a development version of mbed TLS that supports it\n- Added ability to configure (D)TLS ciphersuites\n- Added support for epmin and epmax attributes, specified in LwM2M TS 1.1\n- Moved most of the Access Control mechanism logic from the access_control module (i.e., object implementation) to Anjay core\n- Changed `anjay_execute_get_*()` error codes to ANJAY_ERR constants so that they can be safely propagated by DM handler callbacks\n- Added generating notifications when Access Control object changes\n- New revision of persistent format of the Server object implementation, with redesigned handling of Binding resource\n- Improvements to demo client:\n  - Notifications in Device object now work properly in the demo client\n  - Demo client now supports different binding modes for different servers\n  - Support for more resources in the Cellular Connectivity object\n  - Support for Event Log object\n  - Support for BinaryAppDataContainer object\n- Added script and build target for finding unused code\n\n### Improvements\n\n- Improvements to logging:\n  - Made it easier to compile Anjay without any logs\n  - Made some log messages more descriptive\n  - Tweaked log levels for better manageability\n- More robust downloader module, including improvements to HTTP ETag handling\n- Allowed Accept option in all incoming requests, for better interoperability with certain server implementations\n- Removed internal scheduler implementation, migrated to avs_sched from avs_commons\n- Made use of stack-allocated avs_persistence contexts\n- Refactored handling of server connections and data in Security and Server objects to be more in line with OMA guidelines\n- Refactored CMake scripts to make use of CMake 3 features\n- Extracted parts of dm_core.c to separate files\n- Improvements to internal symbol naming scheme\n- Simplified a lot of internal APIs, including I/O contexts and DM path handling\n- Various cleanups and improvements of Python-based integration test code\n\n### Bugfixes\n\n- Added missing protocol version in responses to Bootstrap Discover on an object path\n- Various minor bugfixes\n\nAlso updates avs_commons to version 4.0.0, which includes the following changes:\n\n### Breaking changes\n\n- Removed ignoring context feature from avs_persistence\n- Refactored error handling, introducing the new avs_error_t concept\n- Renamed avs_stream_abstract_t to avs_stream_t\n- Renamed avs_net_abstract_socket_t to avs_net_socket_t\n\n### Features\n\n- avs_net\n  - Added support for Server Name Identification (D)TLS extension when using OpenSSL, and ability to enable or disable it explicitly\n  - Added support for DTLS Connection ID extension if using a development version of mbed TLS that supports it\n  - Added possibility to use custom mbed TLS entropy pool configuration\n  - Added ability to configure (D)TLS ciphersuites\n  - Added propagation of (D)TLS handshake alert codes to user code\n  - Implemented accept() call for UDP sockets\n  - Added avs_url_parse_lenient function and separate validation functions\n- avs_stream\n  - Added avs_stream_membuf_take_ownership function\n  - Added avs_stream_membuf_reserve function\n- avs_utils\n  - Added avs_unhexlify function\n- avs_algorithm\n  - Refactored base64 to support alternate alphabets and padding settings\n- avs_unit\n  - Added support for and_then callbacks in mock sockets\n\n### Improvements\n\n- Made logs render \"...\" at the end if truncated\n- Improved compatibility with various platforms, including Zephyr\n- Improved structure of CMake stage configuration, removed unused definitions\n- Reformatted entire codebase\n\n### Bugfixes\n\n- Added extern \"C\" clauses missing in some files, added regression testing for that, fixed some other C++ incompatibilities\n- Fixed some improperly propagated error cases in HTTP client\n- Fixed problems with avs_net sockets not working for localhost if no non-loopback network interfaces are available\n- Fixed some potential NULL dereferences, assertion errors and various other fixes\n\n## Anjay 1.16 (September 12th, 2019)\n\n### Features\n\n- Added anjay_fw_update_set_result API for changing Firmware Update Result at runtime\n\n### Improvements\n\n- Make Travis tests a bit faster by no longer using --track-origins=yes Valgrind argument, and also using pre-built Docker images\n\n### Bugfixes\n\n- Disabled stdin buffering in demo application. Fixes occasional hangs in Python tests\n- Updated usages of deprecated `avs_persistence_*` functions\n- Added mbedx509 to pymbedtls dependencies\n- Fixed issues found by Coverity scan\n\n## Anjay 1.15.5 (April 24th, 2019)\n\n### Bug fixes\n\n- Updated avs_commons to 3.10.0, which includes a fix that drastically (one order of magnitude in some cases) changes the result of `avs_coap_exchange_lifetime()`. Previously the results were not in line with RFC7252 requirements.\n\n### Improvements\n\n- The client will no longer wait indefinitely for Bootstrap Finish, but rather for an interval of at most EXCHANGE_LIFETIME seconds since last Bootstrap Interface operation.\n\n## Anjay 1.15.4 (April 19th, 2019)\n\n### Bugfixes\n\n- Fixed bug that caused Anjay fw_update module `perform_upgrade` handler to be called more than once in some cases\n\n### Improvements\n\n- Anjay CMakeLists.txt does not use `${PROJECT_NAME}` anymore, improving compatibility with projects that include it as sources\n\n## Anjay 1.15.3a (April 5th, 2019)\n\n### Improvements\n\n- Documented retransmission parameters configuration in chapter \"Retransmissions, timeouts & response caching\"\n\n## Anjay 1.15.3 (April 3rd, 2019)\n\n### Bugfixes\n\n- Fixed some issues found by Coverity scan\n- Upgraded avs_commons to version 3.9.1, which includes:\n  - Fix of usage of select() on platforms that do not support poll()\n  - Added new `AVS_RESCHED_*` APIs\n\n## Anjay 1.15.2 (March 25th, 2019)\n\n### Improvements\n\n- Use https:// URI instead of git:// for avs_commons submodule. This allows fetching the submodule without setting up SSH keys. Fixes [#23](https://github.com/AVSystem/Anjay/issues/23)\n\n## Anjay 1.15.1 (February 19th, 2019)\n\n### Improvements\n\n- Anjay will never attempt to send Register/Update from anjay_delete any more. It used to happen when that message was scheduled to be sent at a time that happened to pass between last anjay_sched_run and anjay_delete calls.\n\n## Anjay 1.15 (February 14th, 2019)\n\n### BREAKING CHANGES\n\n- Updated avs_commons library to 3.9, which extracts an avs_stream_net library to break a dependency cycle between components. Applications that do not use CMake need to manually add libavs_stream_net.a to the linker command line.\n\n### Bugfixes\n\n- PUT/POST requests with an Accept: CoAP option are no longer rejected as invalid.\n- Fixed a bug in HTTPS downloader that caused the download to hang indefinitely if the internal buffer of TLS socket is larger than 4KB and downloaded data length module TLS socket buffer size is larger than 4KB.\n- Aborting HTTP(S) downloads no longer waits for the whole transfer to complete.\n\n## Anjay 1.14.2 (January 29th, 2019)\n\n### Bugfixes\n\n- Fixed NULL pointer dereference in log messages displayed when an unknown attribute with no value is passed to Write-Attributes\n\n## Anjay 1.14.1 (January 22nd, 2019)\n\n### Features\n\n- Added command line flag to the demo client that disables use of stdin\n\n### Improvements\n\n- Removed some code duplicated with avs_commons\n- Fixed some minor issues found by scan-build 7\n- Simplified flow of code for Register, Update and Request Bootstrap operations\n- Reformatted example code\n\n### Bugfixes\n\n- Fixed a bug that prevented attempt to retry DTLS handshake after failed Request Bootstrap operation in some scenarios when Server-Initiated Bootstrap is enabled\n- Fixed misleading, erroneous log message for when receiving CoAP messages time out\n- Fixed the documentation URL test randomly failing on some machines\n\n## Anjay 1.14.0 (December 4th, 2018)\n\n### Improvements\n\n- Added anjay_configuration_t::stored_notification_limit configuration option for limiting the maximum number of notifications stored when the client is offline.\n\n## Anjay 1.13.1 (October 8th, 2018)\n\n### Improvements\n\n- Fixed compilation warnings caused by unused variables / mismatching printf format specifiers\n\n## Anjay 1.13.0 (October 4th, 2018)\n\n### Breaking changes\n\n- anjay_configuration_t::max_icmp_failures field has been removed.\n- Changed the way connection errors are handled. Connections are now NOT automatically retried in most of the cases. Please refer to the documentation (Advanced tutorial -> Network error handling) for a summary of the new semantics.\n\n### Improvements\n\n- Extensive refactor of the server connection handling subsystem.\n- Added timeout for the documentation URL check.\n- Prevented integration tests from running concurrently on Travis.\n\n### Bugfixes\n\n- Fixed behaviour when the attributes are set so that pmax < pmin.\n- Fixed a bug that caused Discover operation on the Security object to erroneously work instead of causing 4.01 Unauthorized error as mandated by the spec.\n- Fixed compilation warnings on various compilers.\n- Upgraded avs_commons to version 3.8.2, which includes:\n  - Fixes for proper propagation of avs_stream_close() errors.\n  - Fixes for external library dependency checking.\n  - Fixes for various compilation warnings.\n  - Improved logs from the IP address stringification code.\n\n## Anjay 1.12.1 (September 21st, 2018)\n\nUpdate avs_commons to 3.8.1\n\n## Anjay 1.12.0 (September 21st, 2018)\n\n### Breaking changes\n\n- Updated AvsCommons to 3.8.0, which requires CMake 3.4.0 or higher. This means that Anjay requires CMake 3.4.0 or higher as well.\n- Running tests requires grequests (https://github.com/kennethreitz/grequests) now\n\n### Features\n\n- Allowed configuration of UDP DTLS Handshake transmission parameters by anjay_configuration_t::udp_dtls_hs_tx_params field\n- Allowed configuration of firmware download CoAP transmission parameters by anjay_fw_update_handlers_t::get_coap_tx_params handler implemented by the user\n- Added sequence diagrams for library operations in documentation chapter \"4.4. A few notes on general usage\"\n\n### Improvements\n\n- Reformatted the entire codebase with clang-format\n- Added more tests verifying demo client's behavior in situations with network connectivity issues\n- Explained in the demo application why file descriptors other than 0, 1, 2, are being closed\n\n### Bugfixes\n\n- Fixed the cause of \"could not stringify socket address\" error\n\n## Anjay 1.11.0 (September 4th, 2018)\n\n### Breaking changes\n\n- `Removed ANJAY_BINDING_*` constants. Whenever used, they should now be replaced with plain c-strings, as follows:\n  * `ANJAY_BINDING_U` -> `\"U\"`,\n  * `ANJAY_BINDING_S` -> `\"S\"`,\n  * `ANJAY_BINDING_US` -> `\"US\"`,\n  * `ANJAY_BINDING_UQ` -> `\"UQ\"`,\n  * `ANJAY_BINDING_SQ` -> `\"SQ\"`,\n  * `ANJAY_BINDING_UQS` -> `\"UQS\"`,\n  * `ANJAY_BINDING_NONE` -> `\"\"`\n\n### Features\n\n- Implemented anjay_attr_storage_purge(), to allow cleaning up Attribute Storage data without recreating a whole client instance\n- Implemented anjay_access_control_purge(), anjay_access_control_is_modified(), to allow better control over persistence\n- Updated avs_commons to version 3.7.1\n\n### Bugfixes\n\n- Fixed implementation of bytes resources in demo test object code\n- Added missing header in attr_storage.h\n\n### Improvements\n\n- Added support for multiple object versions in lwm2m_object_registry.py script\n- Added some previously missing optional packages to README.md, required to run integration tests\n- Improved performance of integration tests\n- Improved documentation of internal server-related APIs\n- Improved unit tests API, specifically added macros that help building CoAP messages without the knowledge of exact packet encoding\n\n## Anjay 1.10.4 (July 30th, 2018)\n\n### Features\n\n- Added a configuration option that allows disabling Server-Initiated Bootstrap\n\n### Bugfixes\n\n- Very short HTTP downloads now do not hang forever when the server does not close the TCP connection\n\n### Improvements\n\n- Refactored management of bootstrap backoff state\n- Add tests for client behavior after receiving 4.03 Forbidden in response to Register request\n\n## Anjay 1.10.3a (July 19th, 2018)\n\n### Fixes\n\n- Fixed Travis build\n\n## Anjay 1.10.3 (July 19th, 2018)\n\n### Fixes\n\n- Fixed warning about uninitialized retval when compiling in SW4STM32\n\n### Improvements\n\n- Added validation of URLs in documentation\n- Added multiple test cases\n\n## Anjay 1.10.2 (July 10th, 2018)\n\n### Features\n\n- Updated AvsComons to version 3.6.2 which includes:\n  * a more restrictive approach to symbols from POSIX or C standard library that should not be used in embedded environments\n  * a fix of compilation on ARMCC\n  * a fix of compile time warning on IAR\n\n### Fixes\n\n- Fixed client behavior when received a 4.03 Forbidden on LwM2M Register\n- Fixed outdated reference to the LwM2M Specification in the documentation\n\n### Improvements\n\n- Added more tests verifying client behavior in different scenarios\n\n## Anjay 1.10.1 (June 29th, 2018)\n\n### Fixes\n\n- Updated avs_commons to 3.6.1 - fixes compatibility issues in tests.\n\n## Anjay 1.10.0 (June 28th, 2018)\n\n### Features\n\n- Updated avs_commons to 3.6.0, which includes:\n  * an abstraction layer over allocator routines, making it possible for the user to provide custom allocation/deallocation functions to be used by AvsCommons,\n  * removal of ``AVS_LIST_CONFIG_ALLOC/FREE`` (they are now replaced with calls to ``avs_calloc()`` and ``avs_free()`` respectively),\n  * removal of use of all ``time()`` calls,\n  * removal of use of variable length array language feature,\n  * default socket implementation refactor to use a nonblocking socket API,\n  * ``avs_compat_threading`` module, implementing necessary synchronization primitives used across AvsCommons such as mutexes,\n  * ``avs_cleanup_global_state()`` method, allowing to (optionally) free any global state implicitly instantiated in AvsCommons,\n  * various compatibility fixes for FreeBSD.\n\n- Prevented ``anjay_schedule_reconnect()`` from sending Updates when they are not necessary,\n\n- Introduced an API (``anjay_get_socket_entries()``) allowing to obtain different kinds of sockets used by Anjay.\n\n### Improvements\n\n- Removed all uses of ``malloc()/calloc()/realloc()/free()`` in favor of AvsCommons' memory layer abstraction,\n- Removed all occurrences of ``time()``,\n- Removed all uses of variable length arrays,\n- Improved non-GNU compilers compatibility,\n- Fixed multiple typos in documentation,\n- Fixed LwM2M-level error reporting for some LwM2M requests with unexpected payloads/content-formats (sane error codes are now returned instead of Internal Server Error),\n- Various compatibility fixes for FreeBSD.\n\n### Fixes\n\n- Made bind address family depend on resolved numeric address rather than domain name,\n- Fixed project compilation when ``WITH_AVS_PERSISTENCE`` is disabled.\n\n## Anjay 1.9.3 (May 29th, 2018)\n\n### Improvements\n\n- Updated avs_commons to 3.4.3\n- AVS_ASSERT and AVS_UNREACHABLE macros are now used for assertions that contain string literals. This prevents some compilers from emitting warnings about constant expressions being used in asserts.\n\n## Anjay 1.9.2 (May 28th, 2018)\n\n### Features\n\n- Updated avs_commons to 3.4.2 including various compatibility improvements\n- Added preliminary Windows support\n\n### Bugfixes\n\n- Fixed many different casts between incompatible function types found by gcc 8.1\n- Fixed compile errors caused by some (perfectly valid) CMake option configurations\n\n### Improvements\n\n- Improved documentation of anjay_fw_update_perform_upgrade_t\n- Improved compatibility with compilers without typeof() support\n\n## Anjay 1.9.1 (May 17th, 2018)\n\n### Bugfixes\n\n- Fixed searching for scan-build (\"make analyze\" target) on Ubuntu 18.04\n- Prevented sending superfluous notifications before cleaning up the library\n\nAlso updates avs_commons to version 3.4.1, which includes the following changes:\n\n### Bugfixes\n\n- Fixed bug in avs_http that prevented digest authentication from working\n- Fixed conditional compilation bugs in avs_net that made it impossible to disable certain features\n- Fixed bugs in avs_net unit tests that prevented them from passing on systems without JDK installed and when ran as root\n\n### Improvements\n\n- Simplified TLS session persistence logic (removed dependency on mbed TLS session cache)\n- Fixed compilation warnings on mbed TLS >= 2.7\n- Worked around false positive warnings from scan-build 6.0\n\n## Anjay 1.9.0 (May 11th, 2018)\n\n### Features\n\n* anjay_notify_instances_changed() are now automatically called when manipulating pre-implemented Security and Server objects via module API\n* (commercial version only) Added support for persistence of server registration and notification state, designed for devices with aggressive power saving\n* (commercial version only) Added minimal CoAP file server to the command-line test server application\n\n### Improvements\n\n* BREAKING API CHANGE: Security and Server object implementation modules no longer expose the anjay_dm_object_def_t pointer directly.\n* anjay_schedule_reconnect() now also reconnects downloads started using anjay_download()\n* Notifications with non-success message codes are now always sent as Confirmable messages to ensure consistency with server-side state\n* Integration tests now can be easily launched under rr for easier debugging\n* Added various informative log messages\n* Moved persistence subsystem to avs_commons and migrated to it\n* Fixed various compilation warnings and compatibility with different compilers\n* Major internal codebase refactoring, including:\n  * Saner scheduler function signatures\n  * Changed registration expiration time to use realtime clock instead of the monotonic one, which improves compatibility with sleep mode scenarios\n  * Better hermetization of Observe handling implementation\n  * Simplification and better hermetization of server connection handling\n  * Reorganization of Registration Interface implementation\n* (commercial version only) More flexible management of commercial features during packaging\n\nAlso updates avs_commons to version 3.4.0, which includes the following changes:\n\n### Features\n\n* Moved persistence subsystem from Anjay and improved upon it:\n  * Added support for persisting additional integer types\n  * Added support for persisting containers with variable size elements\n  * Added ability to check the type of persistence context\n\n### Improvements\n\n* BREAKING API CHANGE: Changed TLS session resumption API so that it is now serialized to and deserialized from user-provided buffer\n* BREAKING API CHANGE: Simplified certificate and key configuration API\n  * Note that this change dropped support for some libraries that implement \"fake\" OpenSSL API\n* Refactored avs_log() so that compiler will always parse TRACE-level logs, even if code generation for them is disabled\n* Fixed various compilation warnings and compatibility with different compilers\n* Fixed warnings when compiling with mbed TLS 2.3 and newer\n\n### Bugfixes\n\n* Fixed critical bugs in CoAP option handling:\n  * Potential integer overflow\n  * Erroneous operation on big-endian machines\n* Added various missing NULL checks\n\n## Anjay 1.8.2 (March 14th, 2018)\n\n### Improvements\n\n- Added X.509 certificate support to pymbedtls Python module.\n- Made `BINDING_MODE_AS_STR` `const`.\n- Changed type of the buffer size argument of `anjay_execute_get_arg_value` to unsigned size_t.\n- Added a note on using LwIP socket integration layer to \"Porting guide for non-POSIX platforms\".\n- Added debug logs in instance validators for Security and Server LwM2M objects.\n- Added proper notifications for \"Last Execute Arguments\" resource of the Test Object in demo client.\n- Disabled Coverity scan on Travis. This avoids marking the build as failing despite all tests passing - Coverity service is \"down for maintenance\" since 2018-02-20, and there seems to be no information on when will it be up again.\n\n### Bugfixes\n\n- Fixed `anjay_schedule_reconnect` behavior when called after the client gives up on reaching a LwM2M server. Previously, only a single reconnection attempt was attempted in such case, regardless of the `max_icmp_failures` configuration option.\n- Fixed compilation errors on compilers that do not support typeof.\n\n## Anjay 1.8.1 (February 28th, 2018)\n\n### Bugfixes\n\n- Fixed infinite loop of Register retransmissions when LwM2M Server ignores Updates and Client attempts to re-register\n- Fixed nested links in README.md\n\n## Anjay 1.8.0 (February 21st, 2018)\n\n### Features\n\n- Added get_security_info() handler to fw_update module, enabling configuration of security information for PULL-mode downloads over encrypted channels\n- Added anjay_fw_update_load_security_from_dm() which allows to match security information from the Security object based on URI\n\n### Bugfixes\n\n- fw_update module will no longer connect to any HTTPS or CoAPS URI without authentication\n\n## Anjay 1.7.3 (February 16th, 2018)\n\n### Features\n\n- anjay_codegen is now able to generate C++ code\n\n### Improvements\n\n- Updated timeouts in integration tests which should improve test result stability\n- Added log when security mode does not match server URI\n\n## Anjay 1.7.2 (February 12th, 2018)\n\n### Bugfixes\n\n- Fix anjay_all_connections_failed(). It is no longer returning true if no LwM2M Servers are configured.\n\n## Anjay 1.7.1 (February 12th, 2018)\n\n### Bugfixes\n\n- Skip retransmission loop in case of DTLS handshake timeout. DTLS packet retransmissions are handled within avs_net_socket_connect anyway, so there is no point in applying yet another exponential backoff loop.\n\n## Anjay 1.7.0 (February 12th, 2018)\n\n### Breaking changes\n\n- Reverted anjay_server_unreachable_handler_t as it has been found that using the handler correctly is close to being impossible.\n\n- Anjay no longer attempts to reach LwM2M Servers indefinitely. Maximum number of retries is now configured via anjay_configuration_t::max_icmp_failures, and by default is set to 7.\n\n### Features\n\n- Introduced anjay_all_connections_failed() method, allowing the user to check if Anjay already gave up on trying to reach LwM2M Servers.\n\n### Improvements\n\n- Downgrade log level in _anjay_dm_foreach_instance() to TRACE.\n\n## Anjay 1.6.1 (January 29th, 2018)\n\n### Features\n\n- Added anjay_server_unreachable_handler_t, that may be implemented by the user to control behavior of Anjay when it failed to connect to the LwM2M Server\n- Added new demo command line option \"--server-unreachable-action\" to be able to present the aforementioned handler in action\n- Added new demo command \"enable-server\" to enable Server of specified SSID\n- Added anjay_disable_server_with_timeout()\n\n### Improvements\n\n- Improved compatibility with CMake 2.8.12\n\n### Bugfixes\n\n- Fixed Update interval: when lifetime is larger than `2*MAX_TRANSMIT_WAIT`, Update is now sent at `lifetime-MAX_TRANSMIT_WAIT` instead of `lifetime/2`\n- Fixed demo command line parsing functions\n- Fixed Travis problems and configuration on CentOS\n\n## Anjay 1.6.0 (January 8th, 2018)\n\n### Breaking changes\n\n- Replaced time_t with int32_t for period Attributes; fixes compatibility with platforms that have unsigned time_t\n\n### Improvements\n\n- Removed useless symlinks that caused problems on Windows\n- Fixed usage of errno constants that are defined by avs_commons compatibility layer; fixes compatibility with platforms that don't declare sane errno constants\n- Improved compatibility with CMake 2.8 and CentOS\n\n### Other\n\n- anjay_persistence_time() is now deprecated\n\n## Anjay 1.5.2 (December 11th, 2017)\n\n### Bugfixes\n\n- Fixed flow of flushing unsent notifications\n- Bug fixes in avs_commons, including:\n  - Fixed undefined behavior in CoAP message cache\n  - Fixed compatibility with compilers that don't support either stdatomic.h or GCC-style `__sync_*` builtins\n  - Prevented CoAP back-off timer randomization from occasionally using negative numbers\n  - Fixed minor error handling problems\n  - Fixed link commands for TinyDTLS interoperability\n\n### Improvements\n\n- Added WITH_TEST CMake flag\n- Improved compatibility with BSD operating systems\n- Improvements in avs_commons, including:\n  - Fixed interoperability with HTTP servers that unexpectedly close connection\n\n## Anjay 1.5.1 (November 27th, 2017)\n\n### Features\n\n- Support HTTP download resumption\n\n### Bugfixes\n\n- Fix some race conditions in integration tests that revealed themselves on slow machines\n- Fix anjay_download() retrying download forever even in case of terminal failures\n- Fix Firmware Update resetting by a null-byte Write\n- Stop scheduling useless LwM2M Updates to the Bootstrap Server\n\n### Improvements\n\n- Test Firmware Update \"not enough storage\" scenario over CoAP\n- Test Firmware Update \"connection lost during download\" scenario over CoAP\n\n## Anjay 1.5.0 (November 20th, 2017)\n\n### Features\n\n- Extracted Firmware Update logic to a separate module so that the end user have to implement device-specific firmware updating logic only\n- Implemented API for firmware update resumption\n- Implemented stub of the portfolio object (in demo client) required for the OMA TestFest 2017\n- Added support for DTLS session resumption as well as register-after-reconnect semantics\n- Added object versioning support\n- Added support for LwM2M Server URIs with Uri-Path and Uri-Query\n\n### Bugfixes\n\n- Fixed travis builds on macOS\n- Fixed a few misleading statements in the documentation\n- Fixed anjay_codegen.py handling of Multiple Instance Resources\n- Fixed Content-Format for responses on Bootstrap Discover request\n- Fixed Write (replace) on Device object instance in demo client\n\n### Improvements\n\n- Added more tests covering OMA TestFest 2017 test cases\n- Allowed configuring Security/Server IIDs from command line in demo\n- Allowed Bootstrap Delete on \"/\"\n- Added support for re-bootstrapping after failed registrations\n- Added anjay_server/security_object_is_modified simiar to anjay_attr_storage_is_modified\n- Updated porting guide\n- Replaced Internal Server Error responses with more specific error codes in a few places\n\n### Other\n\n- Relaxed validator of Update location path, due to specification being unclear (see: https://github.com/OpenMobileAlliance/OMA_LwM2M_for_Developers/issues/230)\n\n## Anjay 1.4.1 (October 16th, 2017)\n\n### Features\n\n- Added CMake option `WITH_STATIC_DEPS_LINKED` that forces direct linkage of the library dependencies into the final target library\n- Migrated to a new time API implemented in avs_commons\n- Removed dependency on wget completely and used built-in downloader instead\n\n### Bugfixes\n\n- Fixed symbol visibility checks\n\n### Improvements\n\n- Renamed a few files to improve compatibility with various IDEs that do not handle files with non-unique naming across the entire project\n- Lowered severity of some log messages that were actually not that critical\n- Published example output from anjay_codegen.py script in the documentation\n\n## Anjay 1.4.0 (September 8th, 2017)\n\n### Features\n\n- New tools: lwm2m_object_registry.py and anjay_codegen.py, that allow automatic generation of object implementation stubs from LwM2M object definition XMLs\n- anjay_download() now supports HTTP(S), using the client from avs_commons\n- New APIs for querying Anjay's network traffic statistics\n- New APIs in attr_storage for direct attribute manipulation:\n  - anjay_attr_storage_set_object_attrs()\n  - anjay_attr_storage_set_instance_attrs()\n  - anjay_attr_storage_set_resource_attrs()\n- CoAP implementation base has been refactored and moved to avs_commons, so that it can now be used standalone; Anjay code has been refactored accordingly\n\n### Bugfixes\n\n- Fixed a bug that prevented anjay_get_string() from working as documented when the buffer was too short\n- Fixed conformance with RFC 7252 when sending error responses on observed resources (previously the Observe header was erroneously included)\n- Fixed various minor bugs found through static code analysis and compilation on various platforms\n\n### Improvements\n\n- POSIX dependencies are now better isolated to ease porting onto non-POSIX platforms\n- Added more documentation, including:\n  - New tutorial page (BT4) with general notes on library usage\n  - Porting guide for non-POSIX platforms\n- Removed some superfluous log messages\n\n## Anjay 1.3.3 (July 27th, 2017)\n\n### Features\n\n- Implemented anjay_download() API for asynchronous CoAP(S) downloads\n- Added anjay_download example code\n- Added support for CoAP firmware download in demo application\n\n### Bugfixes\n\n- Fixed Register/Update transport when changing Binding\n- Fixed lt/gt/st semantics according to https://github.com/OpenMobileAlliance/OMA_LwM2M_for_Developers/issues/191\n- Fixed handling of unrelated BLOCK2 requests during a block-wise Read\n- Disallowed Write-Attributes requests if the server does not have Read access rights\n- Fixed build instructions for OS X in README\n\n### Improvements\n\n- Added packet capture in Python tests\n- Added compilation instructions for Android\n- Made missing scan-build a fatal error if static analysis was enabled with a CMake flag\n- Integrated Coverity scan with Travis build\n- Allowed configuration of CoAP transmission parameters in anjay_new()\n\n## Anjay 1.3.2 (July 13th, 2017)\n\n### Features\n\n- Added custom Attribute \"con\" that may be used to enforce sending Confirmable Notifications for observed entities.\n\n- Added new chapter of the Tutorial about Executable Resources.\n\n- Added documentation subsection about relation LwM2M Discover and Attribute Storage.\n\n### Improvements\n\n- Removed dependency to Boost.Python in tests, and migrated to pybind11 instead (included as a submodule).\n\n- Implemented sending Update after Lifetime Resource has changed (https://github.com/OpenMobileAlliance/OMA_LwM2M_for_Developers/issues/185)\n\n- Implemented proper Bootstrap Server Account purge logic (https://github.com/OpenMobileAlliance/OMA_LwM2M_for_Developers/issues/195)\n\n- Improved handling of CoAP Ping messages - they no longer produce error messages in the logs.\n\n- Documented `ANJAY_ERR_*` constants.\n\n- Added more NULL-assertions in the example demo client.\n\n- Enabled scan-build in make check by default.\n\n- Bumped all copyright years.\n\n### Bugfixes\n\n- Fixed dead URLs in the documentation.\n\n- Fixed segfault when Bootstrap Discover was performed on a non-existing Object.\n\n- Fixed various minor issues found by the static analysis tools.\n\n- Fixed various compilation issues when compiled on older Android platforms.\n\n## Anjay 1.3.1 (June 22nd, 2017)\n\n### Features\n\n- Added `confirmable_notifications` field to `anjay_configuration_t`, enabling the client to only send LwM2M Notify as CoAP Confirmable messages.\n- Added retransmission detection using message cache with fixed size, configurable at library initialization.\n- Added Custom Object/Notifications tutorial with example client.\n- Added documentation page explaining message cache purpose and usage.\n\n### Improvements\n\n- Added support for Write on Instance with superfluous TLV instance header. Anjay used to reject such requests as malformed.\n- Implemented ETS test 204 (Read with Accept: JSON).\n- Made attribute parsing stricter. Unknown or duplicate attributes now cause Bad Request responses\n- Splitted `anjay.h` header into smaller ones. Note: `anjay.h` now includes all other headers, so no changes to user code are required.\n\n### Bugfixes\n\n- Fixed problem with duplicate request aborting block-wise Read responses.\n- Prevented tests from failing if Sphinx is not installed.\n- Fixed ConnectivityMonitoring.APN type to Multiple Resource. Fixes issue #10.\n- Fixed semantics of `lt` and `gt` attributes to match draft-ietf-core-dynlink document.\n- Fixed build issue when configuring build with -DWITH_BOOTSTRAP=OFF CMake option.\n- Fixed compilation warnings in relase builds.\n\n## Anjay 1.3.0 (May 26th, 2017)\n\n### Features\n\n* Added initial output-only support for JSON Content-Format\n* Added support for SMS-related Resources in security module\n* Refactored code to facilitate support for SMS Binding\n  * Actual SMS Binding support now implemented in the commercial version\n\n### Improvements\n\n* BREAKING API CHANGE: Replaced rid_bound and resource_supported handler with statically declared list of supported resources\n* Improved handling of DTLS backends in build system\n* 5.03 Service Unavailable is now sent instead of Reset when an unexpected request arrives while waiting for a response to Register or Update\n* Improvements in demo client:\n  * Fixed Firmware Update object state machine\n  * Added default Access Control entries\n\n### Bugfixes\n\n* Fixed sending errnoeus 2.31 Continue if the last block payload chunk trigerred an error\n* Relaxed invariants for Client-Initiated Bootstrap as per https://github.com/OpenMobileAlliance/OMA_LwM2M_for_Developers/issues/164\n* Prevented sending Object Instance list in Update messages if only changes are in the Security object\n* Fixed various bugs in access_control module\n\n### Other\n\n* Fixed in-code version numbers\n\n## Anjay 1.2.1 (April 10th, 2017)\n\n### Features\n\n* Added automatic Update after object (de)register\n\n### Bugfix\n\n* Fixed potential access violation in anjay_attr_storage_restore()\n\n## Anjay 1.2.0 (April 3rd, 2017)\n\n### Features\n\n* Added new API: anjay_unregister_object()\n\n### Improvements\n\n* Added new constant: ANJAY_ERR_SERVICE_UNAVAILABLE\n* Made documentation linter work better when the MD5 file is broken\n* Refactored anjay_dm_object_def_t, introduced anjay_dm_handlers_t\n* Refactored attr_storage so that object wrapping is no longer necessary\n* Fixed Travis configuration and added macOS build\n* Refactored access_control implementation, simplified API\n* Removed the on_register object handler\n\n## Anjay 1.1.1 (March 14th, 2017)\n\n### Features\n\n* Added API to specify configuration for CoAP sockets, including overriding path MTU\n* Added support for new DTLS backend: tinydtls\n\n### Improvements\n\n* When CoAP logic is blocked on BLOCK processing, 5.03 is now sent as response to unrelated requests\n* Anjay can now be compiled and run on macOS\n* Updated compliance to the released 1.0 specification\n* Added actual reconnection of sockets when using queue mode\n\n### Bugfixes\n\n* Fixes to various issues found using static analysis\n* Fixed compliance issues with handling Bootstrap-Server Account Timeout\n* Removed automatic instantiation of Access Control object instances, as per https://github.com/OpenMobileAlliance/OMA_LwM2M_for_Developers/issues/192\n* Fixed handling of integration test dependencies in CMake files\n* Workaroud for bug in mbed TLS, see https://github.com/Mbed-TLS/mbedtls/issues/843\n\n## Anjay 1.1.0 (February 22nd, 2017)\n\n### Features\n\n* Demo client loads certificates from files\n* Added Bootstrap Awareness tutorial\n* Added Access Control tutorial\n* Added DTLS tutorial\n* Refactored Attribute handling to meet current LwM2M standard requirements\n* Improved out of source build support\n* Improved test coverage\n\n### Bugfixes\n\n* Fixed coverage script\n* Fixed fuzz tests compilation & running on some configurations\n* Fixed mbedTLS detection in integration tests\n* Fixed a case where mismatched Resource ID was accepted in the TLV payload\n* Fixed compilation of tests under gcc-4.6\n* Fixed detection of the required python version\n\n### Other\n\n* Added missing license headers\n* Lowered loglevel of some less important messages\n* Refactored some of the macros and replaced them with real C code\n* Replaced all \"LWM2M\" with \"LwM2M\"\n* Other minor fixes\n\n## Anjay 1.0.0 (February 8th, 2017)\n\nInitial release.\n"
  },
  {
    "path": "CMakeLists.txt",
    "content": "# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\ncmake_minimum_required(VERSION 3.16)\n\nproject(anjay C)\nset(ANJAY_VERSION \"23f4cd115\" CACHE STRING \"Anjay library version\")\nset(ANJAY_BINARY_VERSION 1.0.0)\n\nset(ANJAY_SOURCE_DIR \"${CMAKE_CURRENT_SOURCE_DIR}\")\nset(ANJAY_BUILD_OUTPUT_DIR \"${CMAKE_CURRENT_BINARY_DIR}/output\")\nset(CMAKE_RUNTIME_OUTPUT_DIRECTORY \"${ANJAY_BUILD_OUTPUT_DIR}/bin\")\nset(CMAKE_LIBRARY_OUTPUT_DIRECTORY \"${ANJAY_BUILD_OUTPUT_DIR}/lib\")\nset(CMAKE_ARCHIVE_OUTPUT_DIRECTORY \"${ANJAY_BUILD_OUTPUT_DIR}/lib\")\n\nset(CMAKE_USE_RELATIVE_PATHS TRUE)\nset(CMAKE_EXPORT_COMPILE_COMMANDS ON)\n\ninclude(CheckFunctionExists)\n\nif(WITH_DEMO OR WITH_INTEGRATION_TESTS)\n    # ensure Python venv is active\n    include(cmake/requirePython3venv.cmake)\nendif()\n\n# On Linux, one needs to link libdl to use dlsym(). On BSD, it is not necessary,\n# and even harmful, since libdl does not exist.\nset(CMAKE_REQUIRED_INCLUDES \"dlfcn.h\")\nforeach(lib \"\" dl)\n    message(STATUS \"Looking for dlsym() in library: ${lib}\")\n    set(CMAKE_REQUIRED_LIBRARIES ${lib})\n\n    # check_function_exists caches its result; make sure the check is\n    # actually repeated for each lib\n    unset(HAVE_DLSYM CACHE)\n    check_function_exists(dlsym HAVE_DLSYM)\n    set(CMAKE_REQUIRED_LIBRARIES)\n\n    if(HAVE_DLSYM)\n        set(DETECTED_DLSYM_LIBRARY \"${lib}\" CACHE STRING \"\" FORCE)\n        break()\n    endif()\nendforeach()\nset(CMAKE_REQUIRED_INCLUDES)\nset(DLSYM_LIBRARY \"${DETECTED_DLSYM_LIBRARY}\" CACHE STRING \"Name of the library containing dlsym() symbol\")\n\ninclude(CMakeDependentOption)\n\n# compilation flags\nset(CMAKE_C_STANDARD 99)\nset(CMAKE_C_EXTENSIONS OFF)\n\noption(WITH_EXTRA_WARNINGS \"Enable extra compilation warnings\" OFF)\nif(WITH_EXTRA_WARNINGS)\n    set(CMAKE_C_FLAGS \"${CMAKE_C_FLAGS} -pedantic -Wall -Wextra -Winit-self -Wmissing-declarations -Wc++-compat -Wsign-conversion -Wconversion -Wcast-qual -Wvla -Wno-variadic-macros -Wno-long-long -Wshadow\")\n    if(CMAKE_C_COMPILER_ID MATCHES \"GNU\")\n        set(CMAKE_C_FLAGS \"${CMAKE_C_FLAGS} -Wjump-misses-init\")\n    endif()\nendif()\n\ninclude(${CMAKE_CURRENT_LIST_DIR}/deps/avs_commons/cmake/PosixFeatures.cmake)\n\noption(WITH_LIBRARY_SHARED \"Compile Anjay as shared library\" \"${UNIX}\")\nif(WITH_LIBRARY_SHARED)\n    # It is not possible to create a shared library if -fPIC is not enabled for\n    # either Anjay or any of its dependencies.\n    set(CMAKE_POSITION_INDEPENDENT_CODE ON)\nendif()\n\n################# TUNABLES #####################################################\n\nset(MAX_PK_OR_IDENTITY_SIZE 2048 CACHE STRING\n    \"Maximum supported size (in bytes) of 'PK or Identity' Resource in Security object.\")\nset(MAX_SECRET_KEY_SIZE 256 CACHE STRING\n    \"Maximum supported size (in bytes) of 'Secret Key' Resource in Security object.\")\n\n# Following options refer to the payload of plaintext-encoded CoAP packets.\nset(MAX_DOUBLE_STRING_SIZE 512 CACHE STRING\n    \"Maximum supported length (in characters) of a string that can be parsed as a double-precision float value, including trailing nullbyte.\")\n\n# CoAP guarantees that Uri-Path/Uri-Query/Location-Path/Location-Query option\n# values are at most 255 characters long, so default values will work for all\n# CoAP requests. Reducing these values is discouraged.\nset(MAX_URI_SEGMENT_SIZE 256 CACHE STRING\n    \"Maximum supported length (in characters) of a single Uri-Path/Location-Path CoAP option value, including trailing nullbyte.\")\nset(MAX_URI_QUERY_SEGMENT_SIZE 256 CACHE STRING\n    \"Maximum supported length (in characters) of a single Uri-Query CoAP option value, including trailing nullbyte.\")\n\nset(DTLS_SESSION_BUFFER_SIZE 1024 CACHE STRING\n    \"Size of the buffer that caches DTLS session information for resumption support.\")\n\nset(MAX_OBSERVATION_SERVERS_REPORTED_NUMBER 0 CACHE STRING\n    \"Maximum number of servers observing a given Resource listed by anjay_resource_observation_status() function.\")\n\nset(ANJAY_DEFAULT_SEND_FORMAT AVS_COAP_FORMAT_NONE CACHE STRING\n    \"Default value of Content-Format used in Send messages. Value AVS_COAP_FORMAT_NONE(65535) means no default value.\")\n\nset(MAX_HOLDOFF_TIME 20 CACHE STRING\n    \"Upper limit for the Hold Off Time for the Bootstrap connection.\")\n\n################# FEATURES THAT REQUIRE LIBRARY CONFIGURATION ##################\n\noption(WITH_AVS_PERSISTENCE \"Enable support for persisting objects data\" ON)\n\noption(WITH_BOOTSTRAP \"Enable LwM2M Bootstrap Interface support\" ON)\noption(WITHOUT_TLV \"Disable support for TLV content format\" OFF)\noption(WITH_DOWNLOADER \"Enable support for downloader API\" ON)\ncmake_dependent_option(WITH_HTTP_DOWNLOAD \"Enable support for HTTP(S) downloads\" OFF \"WITH_DOWNLOADER\" OFF)\noption(WITH_LWM2M11 \"Enable support for LwM2M 1.1\" ON)\ncmake_dependent_option(WITH_LWM2M12 \"Enable support for LwM2M 1.2 features\" ON \"WITH_LWM2M11\" OFF)\n# NOTE: WITH_EST is moved below due to dependencies\n\nset(THREAD_SAFETY_DEFAULT OFF)\nif(WIN32 OR UNIX)\n    set(THREAD_SAFETY_DEFAULT ON)\nendif()\n\noption(WITH_THREAD_SAFETY \"Enable guarding of all accesses to anjay_t with a mutex\" \"${THREAD_SAFETY_DEFAULT}\")\n\n################# LIBRARIES ####################################################\n\n# avs_commons required components.\nset(AVS_COMMONS_COMPONENTS algorithm crypto list buffer net sched stream stream_net url utils compat_threading)\nif(WITH_HTTP_DOWNLOAD)\n    list(APPEND AVS_COMMONS_COMPONENTS http)\nendif()\nif(WITH_AVS_PERSISTENCE)\n    list(APPEND AVS_COMMONS_COMPONENTS persistence)\nendif()\nif(WITH_MODULE_sim_bootstrap)\n    list(APPEND AVS_COMMONS_COMPONENTS stream_md5)\nendif()\nif(NOT DEFINED WITH_AVS_RBTREE OR WITH_AVS_RBTREE)\n    set(WITH_AVS_RBTREE ON CACHE INTERNAL \"\")\nendif()\n\nset(AVS_COMMONS_LIBRARIES)\nforeach(_component ${AVS_COMMONS_COMPONENTS})\n    string(TOUPPER ${_component} _component_uppercase)\n\n    # Enable compilation of each component.\n    set(WITH_AVS_${_component_uppercase} ON CACHE INTERNAL \"\")\n\n    # And mark it as a weak dependency.\n    list(APPEND AVS_COMMONS_LIBRARIES avs_${_component})\nendforeach()\n\noption(WITH_LOCAL_AVS_COMMONS \"Use locally installed avs_commons libraries\" OFF)\n\nif(WITH_LOCAL_AVS_COMMONS)\n    # WITH_TEST is also defined in Commons\n    # it defaults to ON there, but if we have external avs_commons, let's better not depend on avs_unit by default\n    cmake_dependent_option(WITH_TEST \"Enable Anjay tests\" OFF WITH_AVS_UNIT OFF)\n    if(WITH_TEST)\n        set(AVS_COMMONS_COMPONENTS ${AVS_COMMONS_COMPONENTS} unit)\n\n        find_program(VALGRIND_EXECUTABLE valgrind)\n        cmake_dependent_option(WITH_VALGRIND \"Enable usage of valgrind during unit tests\" OFF \"VALGRIND_EXECUTABLE\" OFF)\n    endif()\n    find_package(avs_commons COMPONENTS ${AVS_COMMONS_COMPONENTS} REQUIRED)\n\n    function(read_avs_commons_compile_time_option OPTION_NAME)\n        file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/CMakeTmp/avs_commons_check.c\n             \"#include <avsystem/commons/avs_commons_config.h>\\nint main() {\\n#ifndef ${OPTION_NAME}\\nint error[-1];\\n#endif\\nreturn 0; }\\n\")\n        get_target_property(INCLUDE_DIRS avs_utils INTERFACE_INCLUDE_DIRECTORIES)\n        try_compile(OPTION_VALUE\n                    ${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/CMakeTmp\n                    ${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/CMakeTmp/avs_commons_check.c\n                    CMAKE_FLAGS \"-DINCLUDE_DIRECTORIES=${INCLUDE_DIRS}\")\n        set(${OPTION_NAME} ${OPTION_VALUE} CACHE INTERNAL \"\")\n    endfunction()\n\n    read_avs_commons_compile_time_option(AVS_COMMONS_HAVE_VISIBILITY)\n    read_avs_commons_compile_time_option(AVS_COMMONS_STREAM_WITH_FILE)\n    set(WITH_AVS_STREAM_FILE \"${AVS_COMMONS_STREAM_WITH_FILE}\")\n    read_avs_commons_compile_time_option(AVS_COMMONS_SCHED_THREAD_SAFE)\n    set(WITH_SCHEDULER_THREAD_SAFE \"${AVS_COMMONS_SCHED_THREAD_SAFE}\")\n    read_avs_commons_compile_time_option(AVS_COMMONS_NET_WITH_POSIX_AVS_SOCKET)\n    set(WITH_POSIX_AVS_SOCKET \"${AVS_COMMONS_NET_WITH_POSIX_AVS_SOCKET}\")\nelse()\n    set(_DTLS_BACKENDS \"mbedtls\" \"openssl\" \"tinydtls\" \"custom\")\n    set(DTLS_BACKEND \"mbedtls\" CACHE STRING \"DTLS backend to use; possible values: <empty> ${_DTLS_BACKENDS}\")\n\n    # Reset enabled backends first, to avoid issues with CMakeCache\n    set(WITH_CUSTOM_TLS OFF CACHE INTERNAL \"\")\n    set(WITH_MBEDTLS OFF CACHE INTERNAL \"\")\n    set(WITH_OPENSSL OFF CACHE INTERNAL \"\")\n    set(WITH_TINYDTLS OFF CACHE INTERNAL \"\")\n\n    string(TOLOWER \"${DTLS_BACKEND}\" _DTLS_BACKEND_LOWERCASE)\n    if(_DTLS_BACKEND_LOWERCASE STREQUAL \"mbedtls\")\n        set(WITH_MBEDTLS ON CACHE INTERNAL \"\")\n    elseif(_DTLS_BACKEND_LOWERCASE STREQUAL \"openssl\")\n        set(OPENSSL_CUSTOM_CIPHERS_ENABLED ON CACHE INTERNAL \"\")\n        set(WITH_OPENSSL ON CACHE INTERNAL \"\")\n        set(WITH_OPENSSL_CUSTOM_CIPHERS \"ECDHE-ECDSA-AES128-CCM8:PSK-AES128-CCM8\" CACHE INTERNAL \"\")\n    elseif(_DTLS_BACKEND_LOWERCASE STREQUAL \"tinydtls\")\n        set(WITH_TINYDTLS ON CACHE INTERNAL \"\")\n    elseif(_DTLS_BACKEND_LOWERCASE STREQUAL \"custom\")\n        set(WITH_CUSTOM_TLS ON CACHE INTERNAL \"\")\n    elseif(NOT _DTLS_BACKEND_LOWERCASE STREQUAL \"\")\n        message(FATAL_ERROR \"Unsupported DTLS backend: ${_DTLS_BACKEND_LOWERCASE}; possible values: ${_DTLS_BACKENDS}\")\n    endif()\n\n    message(STATUS \"DTLS backend: ${_DTLS_BACKEND_LOWERCASE}\")\n\n    if(WITH_MBEDTLS OR WITH_CUSTOM_TLS)\n        set(SSL_ERROR_API_DEFAULT ON)\n    else()\n        set(SSL_ERROR_API_DEFAULT OFF)\n    endif()\nendif()\n\noption(WITH_AVS_COAP_TCP \"Enable CoAP over TCP support\" \"${WITH_LWM2M11}\")\noption(WITH_CONN_STATUS_API \"Enable support for the experimental anjay_get_server_connection_status() API and related callback.\" ON)\noption(WITH_SSL_ERROR_API \"Enable support for the experimental server SSL error callback.\" \"${SSL_ERROR_API_DEFAULT}\")\n\n\n\nset(ENABLE_ADVANCED_CRYPTO OFF)\n\noption(WITH_AVS_LOG \"Enable logging support\" ON)\n\nif(NOT WITH_LOCAL_AVS_COMMONS)\n    set(WITH_SCHEDULER_THREAD_SAFE \"${WITH_THREAD_SAFETY}\" CACHE INTERNAL \"\")\n    set(WITH_AVS_CRYPTO_ADVANCED_FEATURES ${ENABLE_ADVANCED_CRYPTO} CACHE INTERNAL \"\")\n\n    add_subdirectory(deps/avs_commons)\nendif()\n\noption(WITH_LOCAL_AVS_COAP \"Use locally installed avs_coap library\" OFF)\nif(WITH_LOCAL_AVS_COAP)\n    find_package(avs_coap REQUIRED)\n\n    function(read_avs_coap_compile_time_option OPTION_NAME)\n        file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/CMakeTmp/avs_coap_check.c\n             \"#include <avsystem/coap/avs_coap_config.h>\\nint main() {\\n#ifndef ${OPTION_NAME}\\nint error[-1];\\n#endif\\nreturn 0; }\\n\")\n        get_target_property(INCLUDE_DIRS avs_coap INTERFACE_INCLUDE_DIRECTORIES)\n        try_compile(OPTION_VALUE\n                    ${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/CMakeTmp\n                    ${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/CMakeTmp/avs_coap_check.c\n                    CMAKE_FLAGS \"-DINCLUDE_DIRECTORIES=${INCLUDE_DIRS}\")\n        set(${OPTION_NAME} ${OPTION_VALUE} CACHE INTERNAL \"\")\n    endfunction()\n\n    read_avs_coap_compile_time_option(WITH_AVS_COAP_UDP)\n    read_avs_coap_compile_time_option(WITH_AVS_COAP_TCP)\n    read_avs_coap_compile_time_option(WITH_AVS_COAP_OBSERVE)\n    read_avs_coap_compile_time_option(WITH_AVS_COAP_BLOCK)\n    read_avs_coap_compile_time_option(WITH_AVS_COAP_STREAMING_API)\nelse()\n    add_subdirectory(deps/avs_coap)\nendif()\n\nif(NOT WITH_AVS_COAP_STREAMING_API)\n    message(FATAL_ERROR \"avs_coap streaming API is required, but disabled\")\nendif()\n\nif(WITH_AVS_LOG)\n    list(APPEND AVS_COMMONS_LIBRARIES avs_log)\nendif()\n\n################# FEATURES #####################################################\n\noption(WITH_ACCESS_CONTROL \"Enable core support for Access Control mechanism\" ON)\noption(WITH_DISCOVER \"Enable support for LwM2M Discover operation\" ON)\ncmake_dependent_option(WITH_OBSERVE \"Enable support for Information Reporting interface (Observe)\" ON \"WITH_AVS_COAP_OBSERVE\" OFF)\ncmake_dependent_option(WITH_CON_ATTR \"Enable support for the Confirmable Notification attribute\" \"${WITH_LWM2M12}\" WITH_OBSERVE OFF)\noption(WITH_LEGACY_CONTENT_FORMAT_SUPPORT\n       \"Enable support for pre-LwM2M 1.0 CoAP Content-Format values (1541-1543)\" OFF)\noption(WITH_LWM2M_JSON \"Enable support for LwM2M 1.0 JSON (output only)\" ON)\noption(WITHOUT_PLAINTEXT \"Disable support for Plain Text content format\" OFF)\noption(WITHOUT_DEREGISTER \"Disable use of the Deregister message\" OFF)\noption(WITHOUT_IP_STICKINESS \"Disable support for IP stickiness\" OFF)\noption(WITHOUT_COMPOSITE_OPERATIONS \"Disable composite operations\" OFF)\ncmake_dependent_option(WITH_SENML_JSON \"Enable support for SenML JSON content format\" ON WITH_LWM2M11 OFF)\ncmake_dependent_option(WITH_CBOR \"Enable support for CBOR and SenML CBOR content formats\" ON WITH_LWM2M11 OFF)\ncmake_dependent_option(WITH_LWM2M_GATEWAY \"Enable support /25 LwM2M Gateway Object\" OFF \"WITH_LWM2M11;NOT WITH_CORE_PERSISTENCE\" OFF)\ncmake_dependent_option(WITH_BOOTSTRAP_PACK \"Enable LwM2M Bootstrap-Pack support\" ON \"WITH_LWM2M12;WITH_BOOTSTRAP;WITH_CBOR OR WITH_SENML_JSON\" OFF)\ncmake_dependent_option(WITH_SEND \"Enable support for LwM2M 1.1 Send operation\" ON \"WITH_CBOR OR WITH_SENML_JSON\" OFF)\ncmake_dependent_option(WITH_OBSERVATION_ATTRIBUTES \"Enable support for Observation Attributes\" ON \"WITH_OBSERVE;WITH_LWM2M12\" OFF)\noption(WITHOUT_QUEUE_MODE_AUTOCLOSE \"Disable automatic closing of server connection sockets after MAX_TRANSMIT_WAIT of inactivity\" OFF)\n\ncmake_dependent_option(WITH_OBSERVATION_STATUS \"Enable support for anjay_resource_observation_status() API\" ON \"WITH_OBSERVE\" OFF)\ncmake_dependent_option(WITH_COAP_DOWNLOAD \"Enable support for CoAP(S) downloads\" ON WITH_DOWNLOADER OFF)\n\ncmake_dependent_option(WITH_ANJAY_LOGS \"Enable logging support\" ON WITH_AVS_LOG OFF)\ncmake_dependent_option(WITH_ANJAY_TRACE_LOGS \"Enable logging support\" ON \"WITH_ANJAY_LOGS;NOT EXTERNAL_LOG_LEVELS_HEADER\" OFF)\n\ncmake_dependent_option(AVS_LOG_WITH_TRACE \"Enable TRACE level logging\" OFF WITH_AVS_LOG OFF)\ncmake_dependent_option(WITH_INTERNAL_LOGS \"Enable logging from inside AVSystem Commons libraries\" ON WITH_AVS_LOG OFF)\ncmake_dependent_option(WITH_INTERNAL_TRACE \"Enable TRACE-level logs inside AVSystem Commons libraries\" ON AVS_LOG_WITH_TRACE OFF)\n\noption(WITH_NET_STATS \"Enable measuring amount of LwM2M traffic\" ON)\n\noption(WITH_COMMUNICATION_TIMESTAMP_API \"Enable communication timestamps\" ON)\n\noption(WITH_EVENT_LOOP \"Enable default implementation of the event loop\" \"${WITH_POSIX_AVS_SOCKET}\")\n\nif(DEFINED WITH_MODULE_attr_storage)\n    message(FATAL_ERROR \"WITH_MODULE_attr_storage has been removed since Anjay 3.0. Please use WITH_ATTR_STORAGE instead.\")\nendif()\n\ncmake_dependent_option(WITH_ATTR_STORAGE \"Enable automatic attribute storage\" ON WITH_AVS_PERSISTENCE OFF)\n\noption(WITH_SECURITY_STRUCTURED \"Enable support for avs_crypto types in the data model\" ON)\n\n# ANJAY_MUTEX_LOCK() variant based on nested functions\nfile(WRITE \"${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/CMakeTmp/nested_funcs.c\"\n     \"int main() { int nested(void) { return 0; } return nested(); }\")\ntry_compile(HAVE_NESTED_FUNCTIONS\n            \"${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/CMakeTmp\"\n            \"${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/CMakeTmp/nested_funcs.c\")\n\nset(WITH_NESTED_FUNCTION_MUTEX_LOCKS_DEFAULT OFF)\nif(WITH_EXTRA_WARNINGS AND NOT CMAKE_BUILD_TYPE MATCHES \"Rel\")\n    set(WITH_NESTED_FUNCTION_MUTEX_LOCKS_DEFAULT ON)\nendif()\n\ncmake_dependent_option(WITH_NESTED_FUNCTION_MUTEX_LOCKS\n                       \"Enable mutex locking variant based on nested functions (provides more compiler warnings)\"\n                       \"${WITH_NESTED_FUNCTION_MUTEX_LOCKS_DEFAULT}\" \"WITH_THREAD_SAFETY;HAVE_NESTED_FUNCTIONS;AVS_COMMONS_HAVE_PRAGMA_DIAGNOSTIC\" OFF)\n\n# This option is not exposed in anjay_config.h,\n# as it is basically useless outside of development of the Anjay library itself\nif(WITH_NESTED_FUNCTION_MUTEX_LOCKS)\n    add_definitions(-DANJAY_WITH_NESTED_FUNCTION_MUTEX_LOCKS)\nendif()\n\n################# MODULES ######################################################\n\n\ncmake_dependent_option(WITH_MODULE_access_control \"Access control object implementation module\" ON WITH_ACCESS_CONTROL OFF)\noption(WITH_MODULE_ipso_objects \"Generic implementation of certain kinds of IPSO objects\" ON)\noption(WITH_MODULE_ipso_objects_v2 \"Experimental generic implementation of certain kinds of IPSO objects\" ON)\noption(WITH_MODULE_security \"Security object module\" ON)\noption(WITH_MODULE_server \"Server object module\" ON)\noption(WITH_MODULE_fw_update \"Firmware Update object module\" ON)\ncmake_dependent_option(WITHOUT_MODULE_fw_update_PUSH_MODE\n                       \"Disable support for PUSH mode Firmware Update\"\n                       OFF \"WITH_MODULE_fw_update;WITH_DOWNLOADER\" OFF)\ncmake_dependent_option(WITH_MODULE_fw_update_v11_resources\n                       \"Enable support for optional resources in Firmware Update object that were added in LwM2M 1.1\"\n                       OFF \"WITH_LWM2M11\" OFF)\ncmake_dependent_option(WITH_MODULE_factory_provisioning \"Factory provisioning module\" ON \"WITH_BOOTSTRAP;WITH_CBOR\" OFF)\noption(WITH_MODULE_advanced_fw_update \"Advanced Firmware Update object module\" OFF)\noption(WITH_MODULE_sw_mgmt \"Software Management object module\" OFF)\n\n################# CODE #########################################################\n\nadd_library(anjay\n            include_public/anjay/access_control.h\n            include_public/anjay/advanced_fw_update.h\n            include_public/anjay/anjay.h\n            include_public/anjay/attr_storage.h\n            include_public/anjay/core.h\n            include_public/anjay/dm.h\n            include_public/anjay/download.h\n            include_public/anjay/factory_provisioning.h\n            include_public/anjay/fw_update.h\n            include_public/anjay/io.h\n            include_public/anjay/ipso_objects.h\n            include_public/anjay/ipso_objects_v2.h\n            include_public/anjay/lwm2m_gateway.h\n            include_public/anjay/lwm2m_send.h\n            include_public/anjay/security.h\n            include_public/anjay/server.h\n            include_public/anjay/stats.h\n            include_public/anjay/sw_mgmt.h\n            src/anjay_config_log.h\n            src/anjay_init.h\n            src/anjay_modules/anjay_access_utils.h\n            src/anjay_modules/anjay_bootstrap.h\n            src/anjay_modules/anjay_dm_utils.h\n            src/anjay_modules/anjay_io_utils.h\n            src/anjay_modules/anjay_notify.h\n            src/anjay_modules/anjay_raw_buffer.h\n            src/anjay_modules/anjay_sched.h\n            src/anjay_modules/anjay_servers.h\n            src/anjay_modules/anjay_time_defs.h\n            src/anjay_modules/anjay_utils_core.h\n            src/anjay_modules/dm/anjay_execute.h\n            src/anjay_modules/dm/anjay_modules.h\n            src/core/anjay_access_utils.c\n            src/core/anjay_access_utils_private.h\n            src/core/anjay_bootstrap_core.c\n            src/core/anjay_bootstrap_core.h\n            src/core/anjay_core.c\n            src/core/anjay_core.h\n            src/core/anjay_dm_core.c\n            src/core/anjay_dm_core.h\n            src/core/anjay_downloader.h\n            src/core/anjay_event_loop.c\n            src/core/anjay_io_core.c\n            src/core/anjay_io_core.h\n            src/core/anjay_io_utils.c\n            src/core/anjay_lwm2m_send.c\n            src/core/anjay_lwm2m_send.h\n            src/core/anjay_notify.c\n            src/core/anjay_raw_buffer.c\n            src/core/anjay_servers_inactive.h\n            src/core/anjay_servers_private.h\n            src/core/anjay_servers_reload.h\n            src/core/anjay_servers_utils.c\n            src/core/anjay_servers_utils.h\n            src/core/anjay_stats.c\n            src/core/anjay_stats.h\n            src/core/anjay_utils_core.c\n            src/core/anjay_utils_private.h\n            src/core/attr_storage/anjay_attr_storage.h\n            src/core/attr_storage/anjay_attr_storage_persistence.c\n            src/core/attr_storage/anjay_attr_storage_private.h\n            src/core/attr_storage/anjay_attr_storage.c\n            src/core/coap/anjay_content_format.h\n            src/core/coap/anjay_msg_details.h\n            src/core/dm/anjay_discover.c\n            src/core/dm/anjay_discover.h\n            src/core/dm/anjay_dm_attributes.c\n            src/core/dm/anjay_dm_attributes.h\n            src/core/dm/anjay_dm_create.c\n            src/core/dm/anjay_dm_create.h\n            src/core/dm/anjay_dm_execute.c\n            src/core/dm/anjay_dm_execute.h\n            src/core/dm/anjay_dm_handlers.c\n            src/core/dm/anjay_dm_read.c\n            src/core/dm/anjay_dm_read.h\n            src/core/dm/anjay_dm_write_attrs.c\n            src/core/dm/anjay_dm_write_attrs.h\n            src/core/dm/anjay_dm_write.c\n            src/core/dm/anjay_dm_write.h\n            src/core/dm/anjay_modules.c\n            src/core/dm/anjay_query.c\n            src/core/dm/anjay_query.h\n            src/core/downloader/anjay_coap.c\n            src/core/downloader/anjay_downloader.c\n            src/core/downloader/anjay_http.c\n            src/core/downloader/anjay_private.h\n            src/core/io/anjay_base64_out.c\n            src/core/io/anjay_base64_out.h\n            src/core/io/anjay_batch_builder.c\n            src/core/io/anjay_batch_builder.h\n            src/core/io/anjay_cbor_in.c\n            src/core/io/anjay_cbor_out.c\n            src/core/io/anjay_common.c\n            src/core/io/anjay_common.h\n            src/core/io/anjay_corelnk.c\n            src/core/io/anjay_corelnk.h\n            src/core/io/anjay_dynamic.c\n            src/core/io/anjay_input_buf.c\n            src/core/io/anjay_json_encoder.c\n            src/core/io/anjay_json_like_decoder.c\n            src/core/io/anjay_json_like_decoder.h\n            src/core/io/anjay_json_like_decoder_vtable.h\n            src/core/io/anjay_lwm2m_cbor_in.c\n            src/core/io/anjay_lwm2m_cbor_out.c\n            src/core/io/anjay_opaque.c\n            src/core/io/anjay_output_buf.c\n            src/core/io/anjay_senml_in.c\n            src/core/io/anjay_senml_like_encoder.c\n            src/core/io/anjay_senml_like_encoder.h\n            src/core/io/anjay_senml_like_encoder_vtable.h\n            src/core/io/anjay_senml_like_out.c\n            src/core/io/anjay_text.c\n            src/core/io/anjay_tlv.h\n            src/core/io/anjay_tlv_in.c\n            src/core/io/anjay_tlv_out.c\n            src/core/io/anjay_vtable.h\n            src/core/io/cbor/anjay_cbor_encoder_ll.c\n            src/core/io/cbor/anjay_cbor_encoder_ll.h\n            src/core/io/cbor/anjay_cbor_types.h\n            src/core/io/cbor/anjay_json_like_cbor_decoder.c\n            src/core/io/cbor/anjay_json_like_cbor_decoder.h\n            src/core/io/cbor/anjay_senml_cbor_encoder.c\n            src/core/io/json/anjay_json_decoder.c\n            src/core/io/json/anjay_json_decoder.h\n            src/core/observe/anjay_observe_core.c\n            src/core/observe/anjay_observe_core.h\n            src/core/observe/anjay_observe_internal.h\n            src/core/observe/anjay_observe_planning.c\n            src/core/servers/anjay_activate.c\n            src/core/servers/anjay_activate.h\n            src/core/servers/anjay_connection_ip.c\n            src/core/servers/anjay_connections.c\n            src/core/servers/anjay_connections.h\n            src/core/servers/anjay_connections_internal.h\n            src/core/servers/anjay_register.c\n            src/core/servers/anjay_register.h\n            src/core/servers/anjay_reload.c\n            src/core/servers/anjay_security_generic.c\n            src/core/servers/anjay_security.h\n            src/core/servers/anjay_server_connections.c\n            src/core/servers/anjay_server_connections.h\n            src/core/servers/anjay_servers_internal.c\n            src/core/servers/anjay_servers_internal.h\n            src/modules/access_control/anjay_access_control_handlers.c\n            src/modules/access_control/anjay_access_control_persistence.c\n            src/modules/access_control/anjay_mod_access_control.c\n            src/modules/access_control/anjay_mod_access_control.h\n            src/modules/advanced_fw_update/anjay_advanced_fw_update.c\n            src/modules/factory_provisioning/anjay_provisioning.c\n            src/modules/fw_update/anjay_fw_update.c\n            src/modules/ipso/anjay_ipso_3d_sensor.c\n            src/modules/ipso/anjay_ipso_basic_sensor.c\n            src/modules/ipso/anjay_ipso_button.c\n            src/modules/ipso_v2/anjay_ipso_v2_3d_sensor.c\n            src/modules/ipso_v2/anjay_ipso_v2_basic_sensor.c\n            src/modules/lwm2m_gateway/anjay_lwm2m_gateway.c\n            src/modules/security/anjay_mod_security.c\n            src/modules/security/anjay_mod_security.h\n            src/modules/security/anjay_security_persistence.c\n            src/modules/security/anjay_security_transaction.c\n            src/modules/security/anjay_security_transaction.h\n            src/modules/security/anjay_security_utils.c\n            src/modules/security/anjay_security_utils.h\n            src/modules/server/anjay_mod_server.c\n            src/modules/server/anjay_mod_server.h\n            src/modules/server/anjay_server_persistence.c\n            src/modules/server/anjay_server_transaction.c\n            src/modules/server/anjay_server_transaction.h\n            src/modules/server/anjay_server_utils.c\n            src/modules/server/anjay_server_utils.h\n            src/modules/sw_mgmt/anjay_sw_mgmt.c\n            )\n\ntarget_include_directories(anjay PUBLIC\n                           $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/include_public>\n                           $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include_public>\n                           $<INSTALL_INTERFACE:include>)\ntarget_include_directories(anjay PRIVATE\n                           $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src>)\ntarget_link_libraries(anjay PUBLIC avs_coap ${AVS_COMMONS_LIBRARIES})\nset_property(TARGET anjay APPEND PROPERTY COMPILE_DEFINITIONS \"ANJAY_VERSION=\\\"${ANJAY_VERSION}\\\"\")\n\n################# LINK #########################################################\n\nset(ANJAY_WITH_ACCESS_CONTROL \"${WITH_ACCESS_CONTROL}\")\nset(ANJAY_WITH_ATTR_STORAGE \"${WITH_ATTR_STORAGE}\")\nset(ANJAY_WITH_BOOTSTRAP \"${WITH_BOOTSTRAP}\")\nset(ANJAY_WITH_BOOTSTRAP_PACK \"${WITH_BOOTSTRAP_PACK}\")\nset(ANJAY_WITH_COAP_DOWNLOAD \"${WITH_COAP_DOWNLOAD}\")\nset(ANJAY_WITH_CON_ATTR \"${WITH_CON_ATTR}\")\nset(ANJAY_WITH_DISCOVER \"${WITH_DISCOVER}\")\nset(ANJAY_WITH_DOWNLOADER \"${WITH_DOWNLOADER}\")\nset(ANJAY_WITH_HTTP_DOWNLOAD \"${WITH_HTTP_DOWNLOAD}\")\nset(ANJAY_WITH_LEGACY_CONTENT_FORMAT_SUPPORT \"${WITH_LEGACY_CONTENT_FORMAT_SUPPORT}\")\nset(ANJAY_WITH_LOGS \"${WITH_ANJAY_LOGS}\")\nset(ANJAY_WITH_LWM2M_JSON \"${WITH_LWM2M_JSON}\")\nset(ANJAY_WITHOUT_TLV \"${WITHOUT_TLV}\")\nset(ANJAY_WITHOUT_PLAINTEXT \"${WITHOUT_PLAINTEXT}\")\nset(ANJAY_WITHOUT_DEREGISTER \"${WITHOUT_DEREGISTER}\")\nset(ANJAY_WITHOUT_IP_STICKINESS \"${WITHOUT_IP_STICKINESS}\")\nset(ANJAY_WITHOUT_COMPOSITE_OPERATIONS \"${WITHOUT_COMPOSITE_OPERATIONS}\")\nset(ANJAY_WITH_MODULE_ACCESS_CONTROL \"${WITH_MODULE_access_control}\")\nset(ANJAY_WITH_MODULE_IPSO_OBJECTS \"${WITH_MODULE_ipso_objects}\")\nset(ANJAY_WITH_MODULE_IPSO_OBJECTS_V2 \"${WITH_MODULE_ipso_objects_v2}\")\nset(ANJAY_WITH_MODULE_FW_UPDATE \"${WITH_MODULE_fw_update}\")\nset(ANJAY_WITH_MODULE_FW_UPDATE_V11_RESOURCES \"${WITH_MODULE_fw_update_v11_resources}\")\nset(ANJAY_WITH_MODULE_ADVANCED_FW_UPDATE \"${WITH_MODULE_advanced_fw_update}\")\nset(ANJAY_WITHOUT_MODULE_FW_UPDATE_PUSH_MODE \"${WITHOUT_MODULE_fw_update_PUSH_MODE}\")\nset(ANJAY_WITH_MODULE_SECURITY \"${WITH_MODULE_security}\")\nset(ANJAY_WITH_MODULE_SERVER \"${WITH_MODULE_server}\")\nset(ANJAY_WITH_MODULE_SW_MGMT \"${WITH_MODULE_sw_mgmt}\")\nset(ANJAY_WITH_NET_STATS \"${WITH_NET_STATS}\")\nset(ANJAY_WITH_COMMUNICATION_TIMESTAMP_API \"${WITH_COMMUNICATION_TIMESTAMP_API}\")\nset(ANJAY_WITH_EVENT_LOOP \"${WITH_EVENT_LOOP}\")\nset(ANJAY_WITH_OBSERVATION_STATUS \"${WITH_OBSERVATION_STATUS}\")\nset(ANJAY_WITH_OBSERVE \"${WITH_OBSERVE}\")\nset(ANJAY_WITH_THREAD_SAFETY \"${WITH_THREAD_SAFETY}\")\nset(ANJAY_WITH_TRACE_LOGS \"${WITH_ANJAY_TRACE_LOGS}\")\nset(ANJAY_WITH_MODULE_FACTORY_PROVISIONING \"${WITH_MODULE_factory_provisioning}\")\n\nset(ANJAY_WITH_CBOR \"${WITH_CBOR}\")\nset(ANJAY_WITH_LWM2M11 \"${WITH_LWM2M11}\")\nset(ANJAY_WITH_LWM2M12 \"${WITH_LWM2M12}\")\nset(ANJAY_WITH_OBSERVATION_ATTRIBUTES \"${WITH_OBSERVATION_ATTRIBUTES}\")\nset(ANJAY_WITH_SECURITY_STRUCTURED \"${WITH_SECURITY_STRUCTURED}\")\nset(ANJAY_WITH_SEND \"${WITH_SEND}\")\nset(ANJAY_WITH_SENML_JSON \"${WITH_SENML_JSON}\")\nset(ANJAY_WITHOUT_QUEUE_MODE_AUTOCLOSE \"${WITHOUT_QUEUE_MODE_AUTOCLOSE}\")\nset(ANJAY_WITH_CONN_STATUS_API \"${WITH_CONN_STATUS_API}\")\nset(ANJAY_WITH_SSL_ERROR_API \"${WITH_SSL_ERROR_API}\")\nset(ANJAY_WITH_LWM2M_GATEWAY \"${WITH_LWM2M_GATEWAY}\")\n\nconfigure_file(include_public/anjay/anjay_config.h.in\n               include_public/anjay/anjay_config.h)\n\nif(BUILD_SHARED_LIBS)\n    set_target_properties(anjay PROPERTIES\n                          VERSION ${ANJAY_BINARY_VERSION})\n    if(AVS_COMMONS_HAVE_VISIBILITY)\n        get_property(LINK_FLAGS TARGET anjay PROPERTY LINK_FLAGS)\n        set_property(TARGET anjay PROPERTY LINK_FLAGS \"${LINK_FLAGS} -Wl,--exclude-libs,ALL\")\n    endif()\n    if(APPLE)\n        get_property(LINK_FLAGS TARGET anjay PROPERTY LINK_FLAGS)\n        set_property(TARGET anjay PROPERTY LINK_FLAGS \"${LINK_FLAGS} -Wl,-undefined,dynamic_lookup\")\n    endif()\nendif()\n\n################# DEMO #########################################################\n\nset(DEMO_DEPENDENCIES\n    WITH_AVS_LOG\n    WITH_EVENT_LOOP\n    # WITH_SCHEDULER_THREAD_SAFE\n    WITH_MODULE_security\n    WITH_MODULE_server)\n\ncmake_dependent_option(WITH_DEMO \"Compile DEMO applications\" ON \"${DEMO_DEPENDENCIES}\" OFF)\n\nif(WITH_DEMO)\n    find_program(OPENSSL_EXECUTABLE openssl)\n    if(OPENSSL_EXECUTABLE)\n        if(NOT EXISTS \"${ANJAY_BUILD_OUTPUT_DIR}/certs/client.crt.der\")\n            execute_process(COMMAND\n                            env bash\n                            \"${CMAKE_CURRENT_SOURCE_DIR}/tools/generate-certs.sh\"\n                            \"${ANJAY_BUILD_OUTPUT_DIR}/certs\"\n                            RESULT_VARIABLE RES)\n            if(NOT ${RES} EQUAL 0)\n                message(FATAL_ERROR \"could not generate SSL certificates\")\n            endif()\n        endif()\n    elseif(WITH_AVS_UNIT)\n        message(FATAL_ERROR \"OpenSSL command line utility is required for unit tests\")\n    endif()\n\n    add_subdirectory(demo)\nendif()\n\n################# TEST ########################################################\n\ncmake_dependent_option(WITH_INTEGRATION_TESTS \"Enable integration tests\" OFF \"WITH_TEST;WITH_DEMO\" OFF)\n\nif(WITH_TEST)\n    enable_testing()\n\n    add_custom_target(check)\n    add_custom_target(anjay_unit_check)\n    add_dependencies(check anjay_unit_check)\n\n    # anjay_test\n    add_executable(anjay_test EXCLUDE_FROM_ALL\n                   $<TARGET_PROPERTY:anjay,SOURCES>\n                   tests/core/coap/utils.c\n                   tests/core/coap/utils.h\n                   tests/core/bootstrap_mock.h\n                   tests/core/io/bigdata.h\n                   tests/core/observe/observe_mock.h\n                   tests/core/socket_mock.c\n                   tests/core/socket_mock.h\n                   tests/utils/dm.c\n                   tests/utils/dm.h\n                   tests/utils/mock_clock.c\n                   tests/utils/mock_clock.h\n                   tests/utils/mock_dm.c\n                   tests/utils/mock_dm.h\n                   tests/utils/utils.h\n                   tests/utils/coap/socket.h\n                   tests/utils/coap/socket.c)\n    if(WITH_SENML_JSON)\n        target_sources(anjay_test PRIVATE tests/core/io/senml_json_encoder.c)\n    endif()\n    if(WITH_CBOR)\n        target_sources(anjay_test PRIVATE tests/core/io/senml_cbor_encoder.c)\n    endif()\n    if(WITH_SEND)\n        target_sources(anjay_test PRIVATE tests/core/lwm2m_send.c)\n    endif()\n    if(WITH_MODULE_factory_provisioning)\n        target_sources(anjay_test PRIVATE tests/modules/factory_provisioning/provisioning.c)\n    endif()\n    target_include_directories(anjay_test PRIVATE\n                               \"${CMAKE_CURRENT_SOURCE_DIR}\"\n                               $<TARGET_PROPERTY:anjay,INCLUDE_DIRECTORIES>)\n    target_link_libraries(anjay_test PRIVATE avs_unit avs_coap_for_tests ${AVS_COMMONS_LIBRARIES})\n\n    if(NOT HAVE_DLSYM AND NOT DLSYM_LIBRARY)\n        message(FATAL_ERROR \"dlsym() is required for tests, but its definition \"\n                \"could not be found; either use -DDLSYM_LIBRARY to select the \"\n                \"library or disable tests\")\n    elseif(DLSYM_LIBRARY)\n        target_link_libraries(anjay_test PRIVATE ${DLSYM_LIBRARY})\n    endif()\n    set_property(TARGET anjay_test APPEND PROPERTY COMPILE_DEFINITIONS\n                 ANJAY_TEST\n                 \"ANJAY_BIN_DIR=\\\"${CMAKE_RUNTIME_OUTPUT_DIRECTORY}\\\"\")\n\n    target_compile_options(anjay_test PRIVATE -Wno-overlength-strings -Wno-vla -Wno-c++-compat)\n    if(CMAKE_C_COMPILER_ID MATCHES \"GNU\")\n        target_compile_options(anjay_test PRIVATE -Wno-jump-misses-init)\n        if(CMAKE_C_COMPILER_VERSION VERSION_LESS 4.8)\n            target_compile_options(anjay_test PRIVATE -Wno-missing-field-initializers -Wno-uninitialized -Wno-unused-value)\n        else()\n            # before GCC 4.8 there was no -Wpedantic, only -pedantic; and no way to disable it\n            target_compile_options(anjay_test PRIVATE -Wno-pedantic)\n        endif()\n    endif()\n\n    if(WITH_VALGRIND)\n        set(VALGRIND ${VALGRIND_EXECUTABLE} --leak-check=full --track-origins=yes -q --error-exitcode=63 --suppressions=${CMAKE_CURRENT_SOURCE_DIR}/valgrind_test.supp)\n    endif()\n\n    if(VALGRIND)\n        set(VALGRIND_LOG ${VALGRIND} --log-file=${ANJAY_BUILD_OUTPUT_DIR}/VALGRIND.anjay_test.log)\n    else()\n        set(VALGRIND_LOG)\n    endif()\n\n    add_test(NAME anjay_test\n             WORKING_DIRECTORY \"${CMAKE_CURRENT_BINARY_DIR}\"\n             COMMAND ${VALGRIND_LOG} $<TARGET_FILE:anjay_test>)\n\n    add_custom_target(anjay_check COMMAND ${CMAKE_CTEST_COMMAND} -R \"^anjay_test$$\" -V DEPENDS anjay_test)\n    add_dependencies(anjay_unit_check anjay_check)\n\n    if(TARGET avs_commons_check)\n        add_dependencies(check avs_commons_check)\n    endif()\n    # TODO T2192: rename to avs_coap after migration\n    if(TARGET avs_coap_check)\n        add_dependencies(check avs_coap_check)\n    endif()\n\n    # Source validation\n    get_target_property(ANJAY_SOURCES anjay SOURCES)\n    set(ABSOLUTE_HEADERS)\n    foreach(F ${ANJAY_SOURCES})\n        add_test(NAME test_anjay_${F}_visibility\n                 COMMAND deps/avs_commons/test_visibility.py ${F}\n                 WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})\n        add_test(NAME test_anjay_${F}_headers\n                 COMMAND deps/avs_commons/test_headers.py ${F} tools/conditional_headers_whitelist.json\n                 WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})\n        if(F MATCHES [.]h$)\n            list(APPEND ABSOLUTE_HEADERS \"${CMAKE_CURRENT_SOURCE_DIR}/${F}\")\n        endif()\n    endforeach()\n\n    add_custom_target(visibility_check COMMAND ${CMAKE_CTEST_COMMAND} -R \"'^test_anjay_.*_visibility$$'\")\n    add_dependencies(anjay_unit_check visibility_check)\n\n    add_custom_target(headers_check COMMAND ${CMAKE_CTEST_COMMAND} -R \"'^test_anjay_.*_headers$$'\")\n    add_dependencies(anjay_unit_check headers_check)\n\n    add_test(NAME test_function_duplicates COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/tools/test_duplicates.py ${ABSOLUTE_HEADERS})\n    add_test(NAME test_markdown_toc COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/tools/markdown-toc.py --check \"${CMAKE_CURRENT_SOURCE_DIR}/README.md\")\n    add_test(NAME test_config_log COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/tools/anjay_config_log_tool.py validate)\n\n    add_custom_target(function_duplicates_check COMMAND ${CMAKE_CTEST_COMMAND} -V -R \"'^test_function_duplicates$$'\")\n\n    add_custom_target(toc_check COMMAND ${CMAKE_CTEST_COMMAND} -V -R \"'^test_markdown_toc$$'\")\n\n    add_custom_target(config_log_check COMMAND ${CMAKE_CTEST_COMMAND} -V -R \"'^test_config_log$$'\")\n\n    add_dependencies(anjay_unit_check\n                     function_duplicates_check\n                     toc_check\n                     config_log_check)\n\n\n    # Symbol validation\n    add_custom_target(symbols_check COMMAND ${CMAKE_CTEST_COMMAND} -R \"'^test_.*_symbols$$'\" --output-on-failure)\n\n    if(BUILD_SHARED_LIBS)\n        add_test(NAME test_so_symbols COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/deps/avs_commons/test_symbols.sh $<TARGET_FILE:anjay> anjay_ ANJAY_ \"__odr_asan[.]\")\n    else()\n        add_test(NAME test_a_symbols COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/deps/avs_commons/test_symbols.sh $<TARGET_FILE:anjay> anjay_ ANJAY_ _anjay_ _ANJAY_ \"__odr_asan[.]\")\n    endif()\n    add_dependencies(symbols_check anjay)\n    add_dependencies(anjay_unit_check symbols_check)\n\n    add_custom_target(anjay_extern_c_check COMMAND\n                      \"${CMAKE_CURRENT_SOURCE_DIR}/deps/avs_commons/tools/check_extern_c.py\" --path \"${CMAKE_CURRENT_SOURCE_DIR}\")\n    add_dependencies(anjay_unit_check anjay_extern_c_check)\n\n    add_custom_target(anjay_filename_check\n                      COMMAND ! find src -name \"'*.[ch]'\" | sed -e \"'s|^.*/||'\" | grep -v \"'^anjay_'\"\n                      COMMAND ! find src -name \"'*.[ch]'\" | sed -e \"'s|^.*/||'\" | sort | uniq -c | grep -v \"'^ *1 '\"\n                      WORKING_DIRECTORY \"${CMAKE_CURRENT_SOURCE_DIR}\")\n    add_dependencies(anjay_check anjay_filename_check)\n\n    add_custom_target(symlink_check COMMAND\n                      \"${CMAKE_CURRENT_SOURCE_DIR}/tools/symlink-check.sh\")\n    add_dependencies(anjay_check symlink_check)\n\n    # Unit tests\n    include(ProcessorCount)\n    ProcessorCount(ANJAY_DEFAULT_NPROC)\n\n    set(NPROC \"${ANJAY_DEFAULT_NPROC}\" CACHE STRING \"Number of threads for multi-threaded build/test operations\")\n\n    add_subdirectory(tests/codegen)\n\n    if(WITH_INTEGRATION_TESTS)\n        set(TEST_RERUNS 3 CACHE STRING \"Maximal number of times we try to rerun each testsuite\")\n        option(TEST_KEEP_SUCCESS_LOGS \"Store all logs from test cases, even if no error happened\" ON)\n        add_subdirectory(tests/integration)\n    endif()\nelse(WITH_TEST)\n    macro(add_anjay_test NAME)\n    endmacro()\nendif(WITH_TEST)\n\n################# FUZZ TESTING #################################################\n\nadd_subdirectory(tests/fuzz)\n\nif(WITH_DOCS)\n    add_subdirectory(doc)\nendif()\n\n################# STATIC ANALYSIS ##############################################\n\ncmake_dependent_option(WITH_STATIC_ANALYSIS \"Perform static analysis of the codebase on `make check`\" OFF WITH_TEST OFF)\nif(WITH_STATIC_ANALYSIS)\n    find_program(SCAN_BUILD_BINARY scan-build)\n    if(NOT SCAN_BUILD_BINARY)\n        # some systems only have scan-build-x.y, where x.y is the version of LLVM\n        # let's try that\n        find_program(CLANG_BINARY clang)\n        if(CLANG_BINARY)\n            avs_temp_name(_fname)\n            file(WRITE ${_fname} \"__clang_major__ __clang_minor__\")\n            execute_process(COMMAND \"${CLANG_BINARY}\" -E -P -x c ${_fname}\n                            OUTPUT_VARIABLE CLANG_VERSION_OUTPUT\n                            OUTPUT_STRIP_TRAILING_WHITESPACE)\n            file(REMOVE ${_fname})\n            string(REPLACE \" \" \".\" CLANG_VERSION_OUTPUT \"${CLANG_VERSION_OUTPUT}\")\n            find_program(SCAN_BUILD_BINARY \"scan-build-${CLANG_VERSION_OUTPUT}\")\n        endif()\n    endif()\n    if(NOT SCAN_BUILD_BINARY)\n        message(FATAL_ERROR \"scan-build not found; specify path with -DSCAN_BUILD_BINARY or disable static analysis with -DWITH_STATIC_ANALYSIS=OFF\")\n    else()\n        add_custom_target(analyze\n                          COMMAND \"${CMAKE_CURRENT_SOURCE_DIR}/tools/analyze\"\n                                  --output-dir \"${CMAKE_CURRENT_BINARY_DIR}/static-analysis\"\n                                  --scan-build \"${SCAN_BUILD_BINARY}\")\n        add_dependencies(check analyze)\n    endif()\nendif()\n\ncmake_dependent_option(WITH_FIND_UNUSED_CODE \"Check for unused symbols on `make check`\" ON \"WITH_TEST;UNIX;NOT APPLE\" OFF)\nif(WITH_FIND_UNUSED_CODE)\n    add_custom_target(find_unused_code\n                      COMMAND \"${CMAKE_CURRENT_SOURCE_DIR}/tools/find_unused_code.py\"\n                              # avs_commons global cleanup functions\n                              --ignore-symbol \"^_avs_.*_cleanup_global.*_state$$\"\n                              # hardware crypto PSK support\n                              --ignore-symbol \"^_avs_crypto_mbedtls_engine_load_psk_\\\\(identity\\\\|key\\\\)$$\"\n                              # gcc internals\n                              --ignore-object \".*/crtbegin.o\"\n                              # external libraries\n                              --ignore-file \"^/usr/lib/.*$$\")\n    add_dependencies(check find_unused_code)\nendif()\n\noption(WITH_EXAMPLES \"Compile and test examples\" ON)\nif(WITH_EXAMPLES)\n    add_subdirectory(examples)\nendif()\n\n################# INSTALL ######################################################\n\n# libraries\ninstall(TARGETS anjay EXPORT anjay-targets DESTINATION lib)\n\ninstall(FILES\n        \"${CMAKE_CURRENT_BINARY_DIR}/include_public/anjay/anjay_config.h\"\n        \"${CMAKE_CURRENT_SOURCE_DIR}/include_public/anjay/anjay.h\"\n        \"${CMAKE_CURRENT_SOURCE_DIR}/include_public/anjay/core.h\"\n        \"${CMAKE_CURRENT_SOURCE_DIR}/include_public/anjay/dm.h\"\n        \"${CMAKE_CURRENT_SOURCE_DIR}/include_public/anjay/download.h\"\n        \"${CMAKE_CURRENT_SOURCE_DIR}/include_public/anjay/io.h\"\n        \"${CMAKE_CURRENT_SOURCE_DIR}/include_public/anjay/stats.h\"\n        DESTINATION include/anjay)\nif(WITH_ATTR_STORAGE)\n    install(FILES \"${CMAKE_CURRENT_SOURCE_DIR}/include_public/anjay/attr_storage.h\"\n            DESTINATION include/anjay)\nendif()\nif(WITH_SEND)\n    install(FILES \"${CMAKE_CURRENT_SOURCE_DIR}/include_public/anjay/lwm2m_send.h\"\n            DESTINATION include/anjay)\nendif()\nif(WITH_LWM2M_GATEWAY)\n    install(FILES \"${CMAKE_CURRENT_SOURCE_DIR}/include_public/anjay/lwm2m_gateway.h\"\n            DESTINATION include/anjay)\nendif()\nif(WITH_MODULE_access_control)\n    install(FILES \"${CMAKE_CURRENT_SOURCE_DIR}/include_public/anjay/access_control.h\"\n            DESTINATION include/anjay)\nendif()\nif(WITH_MODULE_ipso_objects)\n    install(FILES \"${CMAKE_CURRENT_SOURCE_DIR}/include_public/anjay/ipso_objects.h\"\n            DESTINATION include/anjay)\nendif()\nif(WITH_MODULE_ipso_objects_v2)\n    install(FILES \"${CMAKE_CURRENT_SOURCE_DIR}/include_public/anjay/ipso_objects_v2.h\"\n            DESTINATION include/anjay)\nendif()\nif(WITH_MODULE_fw_update)\n    install(FILES \"${CMAKE_CURRENT_SOURCE_DIR}/include_public/anjay/fw_update.h\"\n            DESTINATION include/anjay)\nendif()\nif(WITH_MODULE_advanced_fw_update)\n    install(FILES \"${CMAKE_CURRENT_SOURCE_DIR}/include_public/anjay/advanced_fw_update.h\"\n            DESTINATION include/anjay)\nendif()\nif(WITH_MODULE_security)\n    install(FILES \"${CMAKE_CURRENT_SOURCE_DIR}/include_public/anjay/security.h\"\n            DESTINATION include/anjay)\nendif()\nif(WITH_MODULE_server)\n    install(FILES \"${CMAKE_CURRENT_SOURCE_DIR}/include_public/anjay/server.h\"\n            DESTINATION include/anjay)\nendif()\nif(WITH_MODULE_factory_provisioning)\n    install(FILES \"${CMAKE_CURRENT_SOURCE_DIR}/include_public/anjay/factory_provisioning.h\"\n            DESTINATION include/anjay)\nendif()\nif(WITH_MODULE_sw_mgmt)\n    install(FILES \"${CMAKE_CURRENT_SOURCE_DIR}/include_public/anjay/sw_mgmt.h\"\n            DESTINATION include/anjay)\nendif()\n\n# install CMake package\nconfigure_file(${CMAKE_CURRENT_SOURCE_DIR}/cmake/anjay-config.cmake.in\n               ${ANJAY_BUILD_OUTPUT_DIR}/cmake/anjay-config.cmake\n               @ONLY)\nconfigure_file(${CMAKE_CURRENT_SOURCE_DIR}/cmake/anjay-version.cmake.in\n               ${ANJAY_BUILD_OUTPUT_DIR}/cmake/anjay-version.cmake\n               @ONLY)\n\ninstall(EXPORT anjay-targets DESTINATION lib/anjay)\ninstall(FILES\n        ${ANJAY_BUILD_OUTPUT_DIR}/cmake/anjay-config.cmake\n        ${ANJAY_BUILD_OUTPUT_DIR}/cmake/anjay-version.cmake\n        DESTINATION lib/anjay)\n"
  },
  {
    "path": "CONTRIBUTING.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nContributing to Anjay\n=====================\n\nThank you for considering contributing to Anjay!\n\nPlease take a moment to review this document in order to make the contribution process easy and effective for everyone involved.\n\nFollowing these guidelines helps to communicate that you respect the time of the developers managing and developing this open source project. In return, they should reciprocate that respect in addressing your issue, assessing changes, and helping you finalize your pull requests.\n\n\nReporting issues\n----------------\n\n**If you find a security vulnerability, do NOT open an issue!** Please email `opensource@avsystem.com <mailto:opensource@avsystem.com>`_ instead. We will address the issue as soon as possible and will give you an estimate for when we have a fix and release available for an eventual public disclosure.\n\nUse GitHub issue tracker for reporting other bugs. A great bug report should include:\n\n- Affected library version.\n- Platform information:\n\n  - processor architecture,\n  - operating system,\n  - compiler version.\n- Minimal code example or a list of steps required to reproduce the bug.\n- In case of run-time errors, logs from ``strace``, ``valgrind`` and PCAP network traffic dumps are greatly appreciated.\n\n\nFeature requests\n----------------\n\nIf you think some feature is missing, or that something can be improved, do not hesitate to suggest how we can make it better! When doing that, use GitHub issue tracker to post a description of the feature and some explanation why you need it.\n\n\nCode contributions\n------------------\n\nIf you never submitted a pull request before, take a look at `this fantastic tutorial <https://egghead.io/courses/how-to-contribute-to-an-open-source-project-on-github>`_.\n\n#. Fork the project.\n#. Work on your contribution - follow guidelines described below.\n#. Push changes to your fork.\n#. Make sure all ``make check`` tests still pass.\n#. `Create a pull request <https://help.github.com/articles/creating-a-pull-request-from-a-fork/>`_.\n#. Wait for someone from Anjay team to review your contribution and give feedback.\n\n\nCode style\n^^^^^^^^^^\n\nAll code should be fully C99-compliant and compile without warnings under ``gcc`` and ``clang``. Enable extra warnings by using ``cmake -DWITH_EXTRA_WARNINGS=ON`` or ``devconfig`` script. Compiling and testing the code on multiple architectures (e.g. 32/64-bit x86, ARM, MIPS) is not required, but welcome.\n\n\nGeneral guidelines\n``````````````````\n- Do not use GNU extensions.\n- Use ``UPPER_CASE`` for constants and ``snake_case`` in all other cases.\n- Prefer ``static`` functions. Use ``_anjay_`` prefix for private functions shared between translation units and ``anjay_`` prefix for types and public functions.\n- Do not use global variables. Only constants are allowed in global scope.\n- When using bitfields, make sure they are not saved to persistent storage nor sent over the network - their memory layout is implementation-defined, making them non-portable.\n- Avoid recursion - when writing code for an embedded platform, it is important to determine a hard limit on the stack space used by a program.\n- Include license information at the top of each file you add.\n- Use visibility macros defined in ``anjay_init.h`` to prevent internal symbols from being exported when using a GCC-compatible compiler. See `Visibility macros`_ section for examples.\n\n\nVisibility macros\n`````````````````\n- Public header files (``.h`` files inside ``include_public/`` directories): no visibility macros.\n- Private header files (``.h`` files outside ``include_public/`` directories)::\n\n    // ... includes\n\n    VISIBILITY_PRIVATE_HEADER_BEGIN\n\n    // ... code\n\n    VISIBILITY_PRIVATE_HEADER_END\n\n\n- Source files (``.c``)::\n\n    #include <anjay_init.h>\n\n    // ... includes\n\n    VISIBILITY_SOURCE_BEGIN\n\n    // ... code\n\n\nTests\n^^^^^\n\nMake use of the `coverage script <tools/coverage>`_ to generate a code coverage report. New code should be covered by tests.\n\nBefore submitting your code, run the whole test suite (``make check``) to ensure that it does not introduce regressions. Use ``valgrind`` and Address Sanitizer to check for memory corruption errors.\n\nRunning tests on Ubuntu 20.04 or later: ::\n\n    # Install these for tests:\n    sudo apt-get install python3-pip git libmbedtls-dev libssl-dev zlib1g-dev python3 libpython3-dev wget valgrind curl cmake build-essential tshark\n    pip3 install -U -r requirements.txt\n    # Configure and run check target\n    ./devconfig && make check\n\nRunning tests on CentOS 7 or later: ::\n\n    # Install these for tests:\n    # (IUS is required for Python 3.5)\n    sudo yum install -y https://repo.ius.io/ius-release-el7.rpm\n    sudo yum install -y valgrind valgrind-devel openssl openssl-devel python35u python35u-devel python35u-pip clang-analyzer\n    # Some test scripts expect Python >=3.5 to be available via `python3` command\n    # Use update-alternatives to create a /usr/bin/python3 symlink with priority 0\n    # (lowest possible)\n    sudo update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.5 0\n    sudo python3 -m pip install -r requirements.txt\n\n    # Configure and run check target\n    # NOTE: clang-3.4 static analyzer (default version for CentOS) gives false\n    # positives. --without-analysis flag disables static analysis.\n    ./devconfig --without-analysis -DPython_ADDITIONAL_VERSIONS=3.5 && make check\n\nRunning tests on macOS Sierra or later: ::\n\n    # Install these for tests:\n    brew install python3 openssl llvm\n    pip3 install -r requirements.txt\n\n    # Configure and run check target:\n    # if the scan-build script is located somewhere else, then you need to\n    # specify a different SCAN_BUILD_BINARY. Below, we are assumming scan-build\n    # comes from an llvm package, installed via homebrew.\n    ./devconfig -DSCAN_BUILD_BINARY=/usr/local/Cellar/llvm/*/bin/scan-build && make check\n"
  },
  {
    "path": "Dockerfile",
    "content": "# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\n\nFROM ubuntu:24.04\n\nWORKDIR /Anjay\n\nRUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -yq git build-essential cmake libmbedtls-dev zlib1g-dev\n\nCOPY . .\n\nRUN cmake .\nRUN make -j\n\nENV HOME /Anjay\n\n"
  },
  {
    "path": "Doxyfile.in",
    "content": "# Doxyfile 1.9.8\n\n# This file describes the settings to be used by the documentation system\n# doxygen (www.doxygen.org) for a project.\n#\n# All text after a double hash (##) is considered a comment and is placed in\n# front of the TAG it is preceding.\n#\n# All text after a single hash (#) is considered a comment and will be ignored.\n# The format is:\n# TAG = value [value, ...]\n# For lists, items can also be appended using:\n# TAG += value [value, ...]\n# Values that contain spaces should be placed between quotes (\\\" \\\").\n#\n# Note:\n#\n# Use doxygen to compare the used configuration file with the template\n# configuration file:\n# doxygen -x [configFile]\n# Use doxygen to compare the used configuration file with the template\n# configuration file without replacing the environment variables or CMake type\n# replacement variables:\n# doxygen -x_noenv [configFile]\n\n#---------------------------------------------------------------------------\n# Project related configuration options\n#---------------------------------------------------------------------------\n\n# This tag specifies the encoding used for all characters in the configuration\n# file that follow. The default is UTF-8 which is also the encoding used for all\n# text before the first occurrence of this tag. Doxygen uses libiconv (or the\n# iconv built into libc) for the transcoding. See\n# https://www.gnu.org/software/libiconv/ for the list of possible encodings.\n# The default value is: UTF-8.\n\nDOXYFILE_ENCODING      = UTF-8\n\n# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by\n# double-quotes, unless you are using Doxywizard) that should identify the\n# project for which the documentation is generated. This name is used in the\n# title of most generated pages and in a few other places.\n# The default value is: My Project.\n\nPROJECT_NAME           = anjay\n\n# The PROJECT_NUMBER tag can be used to enter a project or revision number. This\n# could be handy for archiving the generated documentation or if some version\n# control system is used.\n\nPROJECT_NUMBER         =\n\n# Using the PROJECT_BRIEF tag one can provide an optional one line description\n# for a project that appears at the top of each page and should give viewer a\n# quick idea about the purpose of the project. Keep the description short.\n\nPROJECT_BRIEF          =\n\n# With the PROJECT_LOGO tag one can specify a logo or an icon that is included\n# in the documentation. The maximum height of the logo should not exceed 55\n# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy\n# the logo to the output directory.\n\nPROJECT_LOGO           =\n\n# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path\n# into which the generated documentation will be written. If a relative path is\n# entered, it will be relative to the location where doxygen was started. If\n# left blank the current directory will be used.\n\nOUTPUT_DIRECTORY       = @DOXYGEN_OUTPUT_DIR@\n\n# If the CREATE_SUBDIRS tag is set to YES then doxygen will create up to 4096\n# sub-directories (in 2 levels) under the output directory of each output format\n# and will distribute the generated files over these directories. Enabling this\n# option can be useful when feeding doxygen a huge amount of source files, where\n# putting all generated files in the same directory would otherwise causes\n# performance problems for the file system. Adapt CREATE_SUBDIRS_LEVEL to\n# control the number of sub-directories.\n# The default value is: NO.\n\nCREATE_SUBDIRS         = NO\n\n# Controls the number of sub-directories that will be created when\n# CREATE_SUBDIRS tag is set to YES. Level 0 represents 16 directories, and every\n# level increment doubles the number of directories, resulting in 4096\n# directories at level 8 which is the default and also the maximum value. The\n# sub-directories are organized in 2 levels, the first level always has a fixed\n# number of 16 directories.\n# Minimum value: 0, maximum value: 8, default value: 8.\n# This tag requires that the tag CREATE_SUBDIRS is set to YES.\n\nCREATE_SUBDIRS_LEVEL   = 8\n\n# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII\n# characters to appear in the names of generated files. If set to NO, non-ASCII\n# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode\n# U+3044.\n# The default value is: NO.\n\nALLOW_UNICODE_NAMES    = NO\n\n# The OUTPUT_LANGUAGE tag is used to specify the language in which all\n# documentation generated by doxygen is written. Doxygen will use this\n# information to generate all constant output in the proper language.\n# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Bulgarian,\n# Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, Dutch, English\n# (United States), Esperanto, Farsi (Persian), Finnish, French, German, Greek,\n# Hindi, Hungarian, Indonesian, Italian, Japanese, Japanese-en (Japanese with\n# English messages), Korean, Korean-en (Korean with English messages), Latvian,\n# Lithuanian, Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese,\n# Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish,\n# Swedish, Turkish, Ukrainian and Vietnamese.\n# The default value is: English.\n\nOUTPUT_LANGUAGE        = English\n\n# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member\n# descriptions after the members that are listed in the file and class\n# documentation (similar to Javadoc). Set to NO to disable this.\n# The default value is: YES.\n\nBRIEF_MEMBER_DESC      = YES\n\n# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief\n# description of a member or function before the detailed description\n#\n# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the\n# brief descriptions will be completely suppressed.\n# The default value is: YES.\n\nREPEAT_BRIEF           = YES\n\n# This tag implements a quasi-intelligent brief description abbreviator that is\n# used to form the text in various listings. Each string in this list, if found\n# as the leading text of the brief description, will be stripped from the text\n# and the result, after processing the whole list, is used as the annotated\n# text. Otherwise, the brief description is used as-is. If left blank, the\n# following values are used ($name is automatically replaced with the name of\n# the entity):The $name class, The $name widget, The $name file, is, provides,\n# specifies, contains, represents, a, an and the.\n\nABBREVIATE_BRIEF       = \"The $name class\" \\\n                         \"The $name widget\" \\\n                         \"The $name file\" \\\n                         is \\\n                         provides \\\n                         specifies \\\n                         contains \\\n                         represents \\\n                         a \\\n                         an \\\n                         the\n\n# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then\n# doxygen will generate a detailed section even if there is only a brief\n# description.\n# The default value is: NO.\n\nALWAYS_DETAILED_SEC    = NO\n\n# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all\n# inherited members of a class in the documentation of that class as if those\n# members were ordinary class members. Constructors, destructors and assignment\n# operators of the base classes will not be shown.\n# The default value is: NO.\n\nINLINE_INHERITED_MEMB  = NO\n\n# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path\n# before files name in the file list and in the header files. If set to NO the\n# shortest path that makes the file name unique will be used\n# The default value is: YES.\n\nFULL_PATH_NAMES        = YES\n\n# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path.\n# Stripping is only done if one of the specified strings matches the left-hand\n# part of the path. The tag can be used to show relative paths in the file list.\n# If left blank the directory from which doxygen is run is used as the path to\n# strip.\n#\n# Note that you can specify absolute paths here, but also relative paths, which\n# will be relative from the directory where doxygen is started.\n# This tag requires that the tag FULL_PATH_NAMES is set to YES.\n\nSTRIP_FROM_PATH        = @DOXYGEN_INPUT_PATHS@\n\n# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the\n# path mentioned in the documentation of a class, which tells the reader which\n# header file to include in order to use a class. If left blank only the name of\n# the header file containing the class definition is used. Otherwise one should\n# specify the list of include paths that are normally passed to the compiler\n# using the -I flag.\n\nSTRIP_FROM_INC_PATH    =\n\n# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but\n# less readable) file names. This can be useful is your file systems doesn't\n# support long names like on DOS, Mac, or CD-ROM.\n# The default value is: NO.\n\nSHORT_NAMES            = NO\n\n# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the\n# first line (until the first dot) of a Javadoc-style comment as the brief\n# description. If set to NO, the Javadoc-style will behave just like regular Qt-\n# style comments (thus requiring an explicit @brief command for a brief\n# description.)\n# The default value is: NO.\n\nJAVADOC_AUTOBRIEF      = NO\n\n# If the JAVADOC_BANNER tag is set to YES then doxygen will interpret a line\n# such as\n# /***************\n# as being the beginning of a Javadoc-style comment \"banner\". If set to NO, the\n# Javadoc-style will behave just like regular comments and it will not be\n# interpreted by doxygen.\n# The default value is: NO.\n\nJAVADOC_BANNER         = NO\n\n# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first\n# line (until the first dot) of a Qt-style comment as the brief description. If\n# set to NO, the Qt-style will behave just like regular Qt-style comments (thus\n# requiring an explicit \\brief command for a brief description.)\n# The default value is: NO.\n\nQT_AUTOBRIEF           = NO\n\n# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a\n# multi-line C++ special comment block (i.e. a block of //! or /// comments) as\n# a brief description. This used to be the default behavior. The new default is\n# to treat a multi-line C++ comment block as a detailed description. Set this\n# tag to YES if you prefer the old behavior instead.\n#\n# Note that setting this tag to YES also means that rational rose comments are\n# not recognized any more.\n# The default value is: NO.\n\nMULTILINE_CPP_IS_BRIEF = NO\n\n# By default Python docstrings are displayed as preformatted text and doxygen's\n# special commands cannot be used. By setting PYTHON_DOCSTRING to NO the\n# doxygen's special commands can be used and the contents of the docstring\n# documentation blocks is shown as doxygen documentation.\n# The default value is: YES.\n\nPYTHON_DOCSTRING       = YES\n\n# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the\n# documentation from any documented member that it re-implements.\n# The default value is: YES.\n\nINHERIT_DOCS           = YES\n\n# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new\n# page for each member. If set to NO, the documentation of a member will be part\n# of the file/class/namespace that contains it.\n# The default value is: NO.\n\nSEPARATE_MEMBER_PAGES  = NO\n\n# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen\n# uses this value to replace tabs by spaces in code fragments.\n# Minimum value: 1, maximum value: 16, default value: 4.\n\nTAB_SIZE               = 8\n\n# This tag can be used to specify a number of aliases that act as commands in\n# the documentation. An alias has the form:\n# name=value\n# For example adding\n# \"sideeffect=@par Side Effects:^^\"\n# will allow you to put the command \\sideeffect (or @sideeffect) in the\n# documentation, which will result in a user-defined paragraph with heading\n# \"Side Effects:\". Note that you cannot put \\n's in the value part of an alias\n# to insert newlines (in the resulting output). You can put ^^ in the value part\n# of an alias to insert a newline as if a physical newline was in the original\n# file. When you need a literal { or } or , in the value part of an alias you\n# have to escape them by means of a backslash (\\), this can lead to conflicts\n# with the commands \\{ and \\} for these it is advised to use the version @{ and\n# @} or use a double escape (\\\\{ and \\\\})\n\nALIASES += \"experimental=\\xrefitem experimentals \\\"Experimental\\\" \\\"Experimental List\\\"\" \n\n# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources\n# only. Doxygen will then generate output that is more tailored for C. For\n# instance, some of the names that are used will be different. The list of all\n# members will be omitted, etc.\n# The default value is: NO.\n\nOPTIMIZE_OUTPUT_FOR_C  = YES\n\n# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or\n# Python sources only. Doxygen will then generate output that is more tailored\n# for that language. For instance, namespaces will be presented as packages,\n# qualified scopes will look different, etc.\n# The default value is: NO.\n\nOPTIMIZE_OUTPUT_JAVA   = NO\n\n# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran\n# sources. Doxygen will then generate output that is tailored for Fortran.\n# The default value is: NO.\n\nOPTIMIZE_FOR_FORTRAN   = NO\n\n# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL\n# sources. Doxygen will then generate output that is tailored for VHDL.\n# The default value is: NO.\n\nOPTIMIZE_OUTPUT_VHDL   = NO\n\n# Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice\n# sources only. Doxygen will then generate output that is more tailored for that\n# language. For instance, namespaces will be presented as modules, types will be\n# separated into more groups, etc.\n# The default value is: NO.\n\nOPTIMIZE_OUTPUT_SLICE  = NO\n\n# Doxygen selects the parser to use depending on the extension of the files it\n# parses. With this tag you can assign which parser to use for a given\n# extension. Doxygen has a built-in mapping, but you can override or extend it\n# using this tag. The format is ext=language, where ext is a file extension, and\n# language is one of the parsers supported by doxygen: IDL, Java, JavaScript,\n# Csharp (C#), C, C++, Lex, D, PHP, md (Markdown), Objective-C, Python, Slice,\n# VHDL, Fortran (fixed format Fortran: FortranFixed, free formatted Fortran:\n# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser\n# tries to guess whether the code is fixed or free formatted code, this is the\n# default for Fortran type files). For instance to make doxygen treat .inc files\n# as Fortran files (default is PHP), and .f files as C (default is Fortran),\n# use: inc=Fortran f=C.\n#\n# Note: For files without extension you can use no_extension as a placeholder.\n#\n# Note that for custom extensions you also need to set FILE_PATTERNS otherwise\n# the files are not read by doxygen. When specifying no_extension you should add\n# * to the FILE_PATTERNS.\n#\n# Note see also the list of default file extension mappings.\n\nEXTENSION_MAPPING      =\n\n# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments\n# according to the Markdown format, which allows for more readable\n# documentation. See https://daringfireball.net/projects/markdown/ for details.\n# The output of markdown processing is further processed by doxygen, so you can\n# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in\n# case of backward compatibilities issues.\n# The default value is: YES.\n\nMARKDOWN_SUPPORT       = YES\n\n# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up\n# to that level are automatically included in the table of contents, even if\n# they do not have an id attribute.\n# Note: This feature currently applies only to Markdown headings.\n# Minimum value: 0, maximum value: 99, default value: 5.\n# This tag requires that the tag MARKDOWN_SUPPORT is set to YES.\n\nTOC_INCLUDE_HEADINGS   = 5\n\n# The MARKDOWN_ID_STYLE tag can be used to specify the algorithm used to\n# generate identifiers for the Markdown headings. Note: Every identifier is\n# unique.\n# Possible values are: DOXYGEN use a fixed 'autotoc_md' string followed by a\n# sequence number starting at 0 and GITHUB use the lower case version of title\n# with any whitespace replaced by '-' and punctuation characters removed.\n# The default value is: DOXYGEN.\n# This tag requires that the tag MARKDOWN_SUPPORT is set to YES.\n\nMARKDOWN_ID_STYLE      = DOXYGEN\n\n# When enabled doxygen tries to link words that correspond to documented\n# classes, or namespaces to their corresponding documentation. Such a link can\n# be prevented in individual cases by putting a % sign in front of the word or\n# globally by setting AUTOLINK_SUPPORT to NO.\n# The default value is: YES.\n\nAUTOLINK_SUPPORT       = YES\n\n# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want\n# to include (a tag file for) the STL sources as input, then you should set this\n# tag to YES in order to let doxygen match functions declarations and\n# definitions whose arguments contain STL classes (e.g. func(std::string);\n# versus func(std::string) {}). This also make the inheritance and collaboration\n# diagrams that involve STL classes more complete and accurate.\n# The default value is: NO.\n\nBUILTIN_STL_SUPPORT    = NO\n\n# If you use Microsoft's C++/CLI language, you should set this option to YES to\n# enable parsing support.\n# The default value is: NO.\n\nCPP_CLI_SUPPORT        = NO\n\n# Set the SIP_SUPPORT tag to YES if your project consists of sip (see:\n# https://www.riverbankcomputing.com/software/sip/intro) sources only. Doxygen\n# will parse them like normal C++ but will assume all classes use public instead\n# of private inheritance when no explicit protection keyword is present.\n# The default value is: NO.\n\nSIP_SUPPORT            = NO\n\n# For Microsoft's IDL there are propget and propput attributes to indicate\n# getter and setter methods for a property. Setting this option to YES will make\n# doxygen to replace the get and set methods by a property in the documentation.\n# This will only work if the methods are indeed getting or setting a simple\n# type. If this is not the case, or you want to show the methods anyway, you\n# should set this option to NO.\n# The default value is: YES.\n\nIDL_PROPERTY_SUPPORT   = YES\n\n# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC\n# tag is set to YES then doxygen will reuse the documentation of the first\n# member in the group (if any) for the other members of the group. By default\n# all members of a group must be documented explicitly.\n# The default value is: NO.\n\nDISTRIBUTE_GROUP_DOC   = NO\n\n# If one adds a struct or class to a group and this option is enabled, then also\n# any nested class or struct is added to the same group. By default this option\n# is disabled and one has to add nested compounds explicitly via \\ingroup.\n# The default value is: NO.\n\nGROUP_NESTED_COMPOUNDS = NO\n\n# Set the SUBGROUPING tag to YES to allow class member groups of the same type\n# (for instance a group of public functions) to be put as a subgroup of that\n# type (e.g. under the Public Functions section). Set it to NO to prevent\n# subgrouping. Alternatively, this can be done per class using the\n# \\nosubgrouping command.\n# The default value is: YES.\n\nSUBGROUPING            = YES\n\n# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions\n# are shown inside the group in which they are included (e.g. using \\ingroup)\n# instead of on a separate page (for HTML and Man pages) or section (for LaTeX\n# and RTF).\n#\n# Note that this feature does not work in combination with\n# SEPARATE_MEMBER_PAGES.\n# The default value is: NO.\n\nINLINE_GROUPED_CLASSES = NO\n\n# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions\n# with only public data fields or simple typedef fields will be shown inline in\n# the documentation of the scope in which they are defined (i.e. file,\n# namespace, or group documentation), provided this scope is documented. If set\n# to NO, structs, classes, and unions are shown on a separate page (for HTML and\n# Man pages) or section (for LaTeX and RTF).\n# The default value is: NO.\n\nINLINE_SIMPLE_STRUCTS  = NO\n\n# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or\n# enum is documented as struct, union, or enum with the name of the typedef. So\n# typedef struct TypeS {} TypeT, will appear in the documentation as a struct\n# with name TypeT. When disabled the typedef will appear as a member of a file,\n# namespace, or class. And the struct will be named TypeS. This can typically be\n# useful for C code in case the coding convention dictates that all compound\n# types are typedef'ed and only the typedef is referenced, never the tag name.\n# The default value is: NO.\n\nTYPEDEF_HIDES_STRUCT   = NO\n\n# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This\n# cache is used to resolve symbols given their name and scope. Since this can be\n# an expensive process and often the same symbol appears multiple times in the\n# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small\n# doxygen will become slower. If the cache is too large, memory is wasted. The\n# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range\n# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536\n# symbols. At the end of a run doxygen will report the cache usage and suggest\n# the optimal cache size from a speed point of view.\n# Minimum value: 0, maximum value: 9, default value: 0.\n\nLOOKUP_CACHE_SIZE      = 0\n\n# The NUM_PROC_THREADS specifies the number of threads doxygen is allowed to use\n# during processing. When set to 0 doxygen will based this on the number of\n# cores available in the system. You can set it explicitly to a value larger\n# than 0 to get more control over the balance between CPU load and processing\n# speed. At this moment only the input processing can be done using multiple\n# threads. Since this is still an experimental feature the default is set to 1,\n# which effectively disables parallel processing. Please report any issues you\n# encounter. Generating dot graphs in parallel is controlled by the\n# DOT_NUM_THREADS setting.\n# Minimum value: 0, maximum value: 32, default value: 1.\n\nNUM_PROC_THREADS       = 1\n\n# If the TIMESTAMP tag is set different from NO then each generated page will\n# contain the date or date and time when the page was generated. Setting this to\n# NO can help when comparing the output of multiple runs.\n# Possible values are: YES, NO, DATETIME and DATE.\n# The default value is: NO.\n\nTIMESTAMP              = NO\n\n#---------------------------------------------------------------------------\n# Build related configuration options\n#---------------------------------------------------------------------------\n\n# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in\n# documentation are documented, even if no documentation was available. Private\n# class members and static file members will be hidden unless the\n# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES.\n# Note: This will also disable the warnings about undocumented members that are\n# normally produced when WARNINGS is set to YES.\n# The default value is: NO.\n\nEXTRACT_ALL            = YES\n\n# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will\n# be included in the documentation.\n# The default value is: NO.\n\nEXTRACT_PRIVATE        = NO\n\n# If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual\n# methods of a class will be included in the documentation.\n# The default value is: NO.\n\nEXTRACT_PRIV_VIRTUAL   = NO\n\n# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal\n# scope will be included in the documentation.\n# The default value is: NO.\n\nEXTRACT_PACKAGE        = NO\n\n# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be\n# included in the documentation.\n# The default value is: NO.\n\nEXTRACT_STATIC         = YES\n\n# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined\n# locally in source files will be included in the documentation. If set to NO,\n# only classes defined in header files are included. Does not have any effect\n# for Java sources.\n# The default value is: YES.\n\nEXTRACT_LOCAL_CLASSES  = YES\n\n# This flag is only useful for Objective-C code. If set to YES, local methods,\n# which are defined in the implementation section but not in the interface are\n# included in the documentation. If set to NO, only methods in the interface are\n# included.\n# The default value is: NO.\n\nEXTRACT_LOCAL_METHODS  = NO\n\n# If this flag is set to YES, the members of anonymous namespaces will be\n# extracted and appear in the documentation as a namespace called\n# 'anonymous_namespace{file}', where file will be replaced with the base name of\n# the file that contains the anonymous namespace. By default anonymous namespace\n# are hidden.\n# The default value is: NO.\n\nEXTRACT_ANON_NSPACES   = NO\n\n# If this flag is set to YES, the name of an unnamed parameter in a declaration\n# will be determined by the corresponding definition. By default unnamed\n# parameters remain unnamed in the output.\n# The default value is: YES.\n\nRESOLVE_UNNAMED_PARAMS = YES\n\n# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all\n# undocumented members inside documented classes or files. If set to NO these\n# members will be included in the various overviews, but no documentation\n# section is generated. This option has no effect if EXTRACT_ALL is enabled.\n# The default value is: NO.\n\nHIDE_UNDOC_MEMBERS     = NO\n\n# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all\n# undocumented classes that are normally visible in the class hierarchy. If set\n# to NO, these classes will be included in the various overviews. This option\n# will also hide undocumented C++ concepts if enabled. This option has no effect\n# if EXTRACT_ALL is enabled.\n# The default value is: NO.\n\nHIDE_UNDOC_CLASSES     = NO\n\n# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend\n# declarations. If set to NO, these declarations will be included in the\n# documentation.\n# The default value is: NO.\n\nHIDE_FRIEND_COMPOUNDS  = NO\n\n# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any\n# documentation blocks found inside the body of a function. If set to NO, these\n# blocks will be appended to the function's detailed documentation block.\n# The default value is: NO.\n\nHIDE_IN_BODY_DOCS      = NO\n\n# The INTERNAL_DOCS tag determines if documentation that is typed after a\n# \\internal command is included. If the tag is set to NO then the documentation\n# will be excluded. Set it to YES to include the internal documentation.\n# The default value is: NO.\n\nINTERNAL_DOCS          = NO\n\n# With the correct setting of option CASE_SENSE_NAMES doxygen will better be\n# able to match the capabilities of the underlying filesystem. In case the\n# filesystem is case sensitive (i.e. it supports files in the same directory\n# whose names only differ in casing), the option must be set to YES to properly\n# deal with such files in case they appear in the input. For filesystems that\n# are not case sensitive the option should be set to NO to properly deal with\n# output files written for symbols that only differ in casing, such as for two\n# classes, one named CLASS and the other named Class, and to also support\n# references to files without having to specify the exact matching casing. On\n# Windows (including Cygwin) and MacOS, users should typically set this option\n# to NO, whereas on Linux or other Unix flavors it should typically be set to\n# YES.\n# Possible values are: SYSTEM, NO and YES.\n# The default value is: SYSTEM.\n\nCASE_SENSE_NAMES       = SYSTEM\n\n# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with\n# their full class and namespace scopes in the documentation. If set to YES, the\n# scope will be hidden.\n# The default value is: NO.\n\nHIDE_SCOPE_NAMES       = YES\n\n# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will\n# append additional text to a page's title, such as Class Reference. If set to\n# YES the compound reference will be hidden.\n# The default value is: NO.\n\nHIDE_COMPOUND_REFERENCE= NO\n\n# If the SHOW_HEADERFILE tag is set to YES then the documentation for a class\n# will show which file needs to be included to use the class.\n# The default value is: YES.\n\nSHOW_HEADERFILE        = YES\n\n# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of\n# the files that are included by a file in the documentation of that file.\n# The default value is: YES.\n\nSHOW_INCLUDE_FILES     = YES\n\n# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each\n# grouped member an include statement to the documentation, telling the reader\n# which file to include in order to use the member.\n# The default value is: NO.\n\nSHOW_GROUPED_MEMB_INC  = NO\n\n# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include\n# files with double quotes in the documentation rather than with sharp brackets.\n# The default value is: NO.\n\nFORCE_LOCAL_INCLUDES   = NO\n\n# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the\n# documentation for inline members.\n# The default value is: YES.\n\nINLINE_INFO            = YES\n\n# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the\n# (detailed) documentation of file and class members alphabetically by member\n# name. If set to NO, the members will appear in declaration order.\n# The default value is: YES.\n\nSORT_MEMBER_DOCS       = YES\n\n# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief\n# descriptions of file, namespace and class members alphabetically by member\n# name. If set to NO, the members will appear in declaration order. Note that\n# this will also influence the order of the classes in the class list.\n# The default value is: NO.\n\nSORT_BRIEF_DOCS        = NO\n\n# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the\n# (brief and detailed) documentation of class members so that constructors and\n# destructors are listed first. If set to NO the constructors will appear in the\n# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS.\n# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief\n# member documentation.\n# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting\n# detailed member documentation.\n# The default value is: NO.\n\nSORT_MEMBERS_CTORS_1ST = NO\n\n# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy\n# of group names into alphabetical order. If set to NO the group names will\n# appear in their defined order.\n# The default value is: NO.\n\nSORT_GROUP_NAMES       = NO\n\n# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by\n# fully-qualified names, including namespaces. If set to NO, the class list will\n# be sorted only by class name, not including the namespace part.\n# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.\n# Note: This option applies only to the class list, not to the alphabetical\n# list.\n# The default value is: NO.\n\nSORT_BY_SCOPE_NAME     = NO\n\n# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper\n# type resolution of all parameters of a function it will reject a match between\n# the prototype and the implementation of a member function even if there is\n# only one candidate or it is obvious which candidate to choose by doing a\n# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still\n# accept a match between prototype and implementation in such cases.\n# The default value is: NO.\n\nSTRICT_PROTO_MATCHING  = NO\n\n# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo\n# list. This list is created by putting \\todo commands in the documentation.\n# The default value is: YES.\n\nGENERATE_TODOLIST      = YES\n\n# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test\n# list. This list is created by putting \\test commands in the documentation.\n# The default value is: YES.\n\nGENERATE_TESTLIST      = YES\n\n# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug\n# list. This list is created by putting \\bug commands in the documentation.\n# The default value is: YES.\n\nGENERATE_BUGLIST       = YES\n\n# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO)\n# the deprecated list. This list is created by putting \\deprecated commands in\n# the documentation.\n# The default value is: YES.\n\nGENERATE_DEPRECATEDLIST= YES\n\n# The ENABLED_SECTIONS tag can be used to enable conditional documentation\n# sections, marked by \\if <section_label> ... \\endif and \\cond <section_label>\n# ... \\endcond blocks.\n\nENABLED_SECTIONS       =\n\n# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the\n# initial value of a variable or macro / define can have for it to appear in the\n# documentation. If the initializer consists of more lines than specified here\n# it will be hidden. Use a value of 0 to hide initializers completely. The\n# appearance of the value of individual variables and macros / defines can be\n# controlled using \\showinitializer or \\hideinitializer command in the\n# documentation regardless of this setting.\n# Minimum value: 0, maximum value: 10000, default value: 30.\n\nMAX_INITIALIZER_LINES  = 30\n\n# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at\n# the bottom of the documentation of classes and structs. If set to YES, the\n# list will mention the files that were used to generate the documentation.\n# The default value is: YES.\n\nSHOW_USED_FILES        = YES\n\n# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This\n# will remove the Files entry from the Quick Index and from the Folder Tree View\n# (if specified).\n# The default value is: YES.\n\nSHOW_FILES             = YES\n\n# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces\n# page. This will remove the Namespaces entry from the Quick Index and from the\n# Folder Tree View (if specified).\n# The default value is: YES.\n\nSHOW_NAMESPACES        = YES\n\n# The FILE_VERSION_FILTER tag can be used to specify a program or script that\n# doxygen should invoke to get the current version for each file (typically from\n# the version control system). Doxygen will invoke the program by executing (via\n# popen()) the command command input-file, where command is the value of the\n# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided\n# by doxygen. Whatever the program writes to standard output is used as the file\n# version. For an example see the documentation.\n\nFILE_VERSION_FILTER    =\n\n# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed\n# by doxygen. The layout file controls the global structure of the generated\n# output files in an output format independent way. To create the layout file\n# that represents doxygen's defaults, run doxygen with the -l option. You can\n# optionally specify a file name after the option, if omitted DoxygenLayout.xml\n# will be used as the name of the layout file. See also section \"Changing the\n# layout of pages\" for information.\n#\n# Note that if you run doxygen from a directory containing a file called\n# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE\n# tag is left empty.\n\nLAYOUT_FILE            =\n\n# The CITE_BIB_FILES tag can be used to specify one or more bib files containing\n# the reference definitions. This must be a list of .bib files. The .bib\n# extension is automatically appended if omitted. This requires the bibtex tool\n# to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info.\n# For LaTeX the style of the bibliography can be controlled using\n# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the\n# search path. See also \\cite for info how to create references.\n\nCITE_BIB_FILES         =\n\n#---------------------------------------------------------------------------\n# Configuration options related to warning and progress messages\n#---------------------------------------------------------------------------\n\n# The QUIET tag can be used to turn on/off the messages that are generated to\n# standard output by doxygen. If QUIET is set to YES this implies that the\n# messages are off.\n# The default value is: NO.\n\nQUIET                  = NO\n\n# The WARNINGS tag can be used to turn on/off the warning messages that are\n# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES\n# this implies that the warnings are on.\n#\n# Tip: Turn warnings on while writing the documentation.\n# The default value is: YES.\n\nWARNINGS               = YES\n\n# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate\n# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag\n# will automatically be disabled.\n# The default value is: YES.\n\nWARN_IF_UNDOCUMENTED   = YES\n\n# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for\n# potential errors in the documentation, such as documenting some parameters in\n# a documented function twice, or documenting parameters that don't exist or\n# using markup commands wrongly.\n# The default value is: YES.\n\nWARN_IF_DOC_ERROR      = YES\n\n# If WARN_IF_INCOMPLETE_DOC is set to YES, doxygen will warn about incomplete\n# function parameter documentation. If set to NO, doxygen will accept that some\n# parameters have no documentation without warning.\n# The default value is: YES.\n\nWARN_IF_INCOMPLETE_DOC = YES\n\n# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that\n# are documented, but have no documentation for their parameters or return\n# value. If set to NO, doxygen will only warn about wrong parameter\n# documentation, but not about the absence of documentation. If EXTRACT_ALL is\n# set to YES then this flag will automatically be disabled. See also\n# WARN_IF_INCOMPLETE_DOC\n# The default value is: NO.\n\nWARN_NO_PARAMDOC       = NO\n\n# If WARN_IF_UNDOC_ENUM_VAL option is set to YES, doxygen will warn about\n# undocumented enumeration values. If set to NO, doxygen will accept\n# undocumented enumeration values. If EXTRACT_ALL is set to YES then this flag\n# will automatically be disabled.\n# The default value is: NO.\n\nWARN_IF_UNDOC_ENUM_VAL = NO\n\n# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when\n# a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS\n# then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but\n# at the end of the doxygen process doxygen will return with a non-zero status.\n# If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS_PRINT then doxygen behaves\n# like FAIL_ON_WARNINGS but in case no WARN_LOGFILE is defined doxygen will not\n# write the warning messages in between other messages but write them at the end\n# of a run, in case a WARN_LOGFILE is defined the warning messages will be\n# besides being in the defined file also be shown at the end of a run, unless\n# the WARN_LOGFILE is defined as - i.e. standard output (stdout) in that case\n# the behavior will remain as with the setting FAIL_ON_WARNINGS.\n# Possible values are: NO, YES, FAIL_ON_WARNINGS and FAIL_ON_WARNINGS_PRINT.\n# The default value is: NO.\n\nWARN_AS_ERROR          = NO\n\n# The WARN_FORMAT tag determines the format of the warning messages that doxygen\n# can produce. The string should contain the $file, $line, and $text tags, which\n# will be replaced by the file and line number from which the warning originated\n# and the warning text. Optionally the format may contain $version, which will\n# be replaced by the version of the file (if it could be obtained via\n# FILE_VERSION_FILTER)\n# See also: WARN_LINE_FORMAT\n# The default value is: $file:$line: $text.\n\nWARN_FORMAT            = \"$file:$line: $text\"\n\n# In the $text part of the WARN_FORMAT command it is possible that a reference\n# to a more specific place is given. To make it easier to jump to this place\n# (outside of doxygen) the user can define a custom \"cut\" / \"paste\" string.\n# Example:\n# WARN_LINE_FORMAT = \"'vi $file +$line'\"\n# See also: WARN_FORMAT\n# The default value is: at line $line of file $file.\n\nWARN_LINE_FORMAT       = \"at line $line of file $file\"\n\n# The WARN_LOGFILE tag can be used to specify a file to which warning and error\n# messages should be written. If left blank the output is written to standard\n# error (stderr). In case the file specified cannot be opened for writing the\n# warning and error messages are written to standard error. When as file - is\n# specified the warning and error messages are written to standard output\n# (stdout).\n\nWARN_LOGFILE           =\n\n#---------------------------------------------------------------------------\n# Configuration options related to the input files\n#---------------------------------------------------------------------------\n\n# The INPUT tag is used to specify the files and/or directories that contain\n# documented source files. You may enter file names like myfile.cpp or\n# directories like /usr/src/myproject. Separate the files or directories with\n# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING\n# Note: If this tag is empty the current directory is searched.\n\nINPUT                  = @DOXYGEN_INPUT_PATHS@\n\n# This tag can be used to specify the character encoding of the source files\n# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses\n# libiconv (or the iconv built into libc) for the transcoding. See the libiconv\n# documentation (see:\n# https://www.gnu.org/software/libiconv/) for the list of possible encodings.\n# See also: INPUT_FILE_ENCODING\n# The default value is: UTF-8.\n\nINPUT_ENCODING         = UTF-8\n\n# This tag can be used to specify the character encoding of the source files\n# that doxygen parses The INPUT_FILE_ENCODING tag can be used to specify\n# character encoding on a per file pattern basis. Doxygen will compare the file\n# name with each pattern and apply the encoding instead of the default\n# INPUT_ENCODING) if there is a match. The character encodings are a list of the\n# form: pattern=encoding (like *.php=ISO-8859-1). See cfg_input_encoding\n# \"INPUT_ENCODING\" for further information on supported encodings.\n\nINPUT_FILE_ENCODING    =\n\n# If the value of the INPUT tag contains directories, you can use the\n# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and\n# *.h) to filter out the source-files in the directories.\n#\n# Note that for custom extensions or not directly supported extensions you also\n# need to set EXTENSION_MAPPING for the extension otherwise the files are not\n# read by doxygen.\n#\n# Note the list of default checked file patterns might differ from the list of\n# default file extension mappings.\n#\n# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cxxm,\n# *.cpp, *.cppm, *.c++, *.c++m, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl,\n# *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp, *.h++, *.ixx, *.l, *.cs, *.d, *.php,\n# *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to be\n# provided as doxygen C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08,\n# *.f18, *.f, *.for, *.vhd, *.vhdl, *.ucf, *.qsf and *.ice.\n\nFILE_PATTERNS          = *.c \\\n                         *.cxx \\\n                         *.cpp \\\n                         *.inl \\\n                         *.h \\\n                         *.hpp \\\n                         *.h++\n\n# The RECURSIVE tag can be used to specify whether or not subdirectories should\n# be searched for input files as well.\n# The default value is: NO.\n\nRECURSIVE              = YES\n\n# The EXCLUDE tag can be used to specify files and/or directories that should be\n# excluded from the INPUT source files. This way you can easily exclude a\n# subdirectory from a directory tree whose root is specified with the INPUT tag.\n#\n# Note that relative paths are relative to the directory from which doxygen is\n# run.\n\nEXCLUDE                =\n\n# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or\n# directories that are symbolic links (a Unix file system feature) are excluded\n# from the input.\n# The default value is: NO.\n\nEXCLUDE_SYMLINKS       = NO\n\n# If the value of the INPUT tag contains directories, you can use the\n# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude\n# certain files from those directories.\n#\n# Note that the wildcards are matched against the file with absolute path, so to\n# exclude all test directories for example use the pattern */test/*\n\nEXCLUDE_PATTERNS       =\n\n# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names\n# (namespaces, classes, functions, etc.) that should be excluded from the\n# output. The symbol name can be a fully qualified name, a word, or if the\n# wildcard * is used, a substring. Examples: ANamespace, AClass,\n# ANamespace::AClass, ANamespace::*Test\n\nEXCLUDE_SYMBOLS        =\n\n# The EXAMPLE_PATH tag can be used to specify one or more files or directories\n# that contain example code fragments that are included (see the \\include\n# command).\n\nEXAMPLE_PATH           =\n\n# If the value of the EXAMPLE_PATH tag contains directories, you can use the\n# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and\n# *.h) to filter out the source-files in the directories. If left blank all\n# files are included.\n\nEXAMPLE_PATTERNS       = *\n\n# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be\n# searched for input files to be used with the \\include or \\dontinclude commands\n# irrespective of the value of the RECURSIVE tag.\n# The default value is: NO.\n\nEXAMPLE_RECURSIVE      = NO\n\n# The IMAGE_PATH tag can be used to specify one or more files or directories\n# that contain images that are to be included in the documentation (see the\n# \\image command).\n\nIMAGE_PATH             =\n\n# The INPUT_FILTER tag can be used to specify a program that doxygen should\n# invoke to filter for each input file. Doxygen will invoke the filter program\n# by executing (via popen()) the command:\n#\n# <filter> <input-file>\n#\n# where <filter> is the value of the INPUT_FILTER tag, and <input-file> is the\n# name of an input file. Doxygen will then use the output that the filter\n# program writes to standard output. If FILTER_PATTERNS is specified, this tag\n# will be ignored.\n#\n# Note that the filter must not add or remove lines; it is applied before the\n# code is scanned, but not when the output code is generated. If lines are added\n# or removed, the anchors will not be placed correctly.\n#\n# Note that doxygen will use the data processed and written to standard output\n# for further processing, therefore nothing else, like debug statements or used\n# commands (so in case of a Windows batch file always use @echo OFF), should be\n# written to standard output.\n#\n# Note that for custom extensions or not directly supported extensions you also\n# need to set EXTENSION_MAPPING for the extension otherwise the files are not\n# properly processed by doxygen.\n\nINPUT_FILTER           =\n\n# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern\n# basis. Doxygen will compare the file name with each pattern and apply the\n# filter if there is a match. The filters are a list of the form: pattern=filter\n# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how\n# filters are used. If the FILTER_PATTERNS tag is empty or if none of the\n# patterns match the file name, INPUT_FILTER is applied.\n#\n# Note that for custom extensions or not directly supported extensions you also\n# need to set EXTENSION_MAPPING for the extension otherwise the files are not\n# properly processed by doxygen.\n\nFILTER_PATTERNS        =\n\n# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using\n# INPUT_FILTER) will also be used to filter the input files that are used for\n# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES).\n# The default value is: NO.\n\nFILTER_SOURCE_FILES    = NO\n\n# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file\n# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and\n# it is also possible to disable source filtering for a specific pattern using\n# *.ext= (so without naming a filter).\n# This tag requires that the tag FILTER_SOURCE_FILES is set to YES.\n\nFILTER_SOURCE_PATTERNS =\n\n# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that\n# is part of the input, its contents will be placed on the main page\n# (index.html). This can be useful if you have a project on for instance GitHub\n# and want to reuse the introduction page also for the doxygen output.\n\nUSE_MDFILE_AS_MAINPAGE =\n\n# The Fortran standard specifies that for fixed formatted Fortran code all\n# characters from position 72 are to be considered as comment. A common\n# extension is to allow longer lines before the automatic comment starts. The\n# setting FORTRAN_COMMENT_AFTER will also make it possible that longer lines can\n# be processed before the automatic comment starts.\n# Minimum value: 7, maximum value: 10000, default value: 72.\n\nFORTRAN_COMMENT_AFTER  = 72\n\n#---------------------------------------------------------------------------\n# Configuration options related to source browsing\n#---------------------------------------------------------------------------\n\n# If the SOURCE_BROWSER tag is set to YES then a list of source files will be\n# generated. Documented entities will be cross-referenced with these sources.\n#\n# Note: To get rid of all source code in the generated output, make sure that\n# also VERBATIM_HEADERS is set to NO.\n# The default value is: NO.\n\nSOURCE_BROWSER         = NO\n\n# Setting the INLINE_SOURCES tag to YES will include the body of functions,\n# classes and enums directly into the documentation.\n# The default value is: NO.\n\nINLINE_SOURCES         = NO\n\n# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any\n# special comment blocks from generated source code fragments. Normal C, C++ and\n# Fortran comments will always remain visible.\n# The default value is: YES.\n\nSTRIP_CODE_COMMENTS    = YES\n\n# If the REFERENCED_BY_RELATION tag is set to YES then for each documented\n# entity all documented functions referencing it will be listed.\n# The default value is: NO.\n\nREFERENCED_BY_RELATION = NO\n\n# If the REFERENCES_RELATION tag is set to YES then for each documented function\n# all documented entities called/used by that function will be listed.\n# The default value is: NO.\n\nREFERENCES_RELATION    = NO\n\n# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set\n# to YES then the hyperlinks from functions in REFERENCES_RELATION and\n# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will\n# link to the documentation.\n# The default value is: YES.\n\nREFERENCES_LINK_SOURCE = YES\n\n# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the\n# source code will show a tooltip with additional information such as prototype,\n# brief description and links to the definition and documentation. Since this\n# will make the HTML file larger and loading of large files a bit slower, you\n# can opt to disable this feature.\n# The default value is: YES.\n# This tag requires that the tag SOURCE_BROWSER is set to YES.\n\nSOURCE_TOOLTIPS        = YES\n\n# If the USE_HTAGS tag is set to YES then the references to source code will\n# point to the HTML generated by the htags(1) tool instead of doxygen built-in\n# source browser. The htags tool is part of GNU's global source tagging system\n# (see https://www.gnu.org/software/global/global.html). You will need version\n# 4.8.6 or higher.\n#\n# To use it do the following:\n# - Install the latest version of global\n# - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file\n# - Make sure the INPUT points to the root of the source tree\n# - Run doxygen as normal\n#\n# Doxygen will invoke htags (and that will in turn invoke gtags), so these\n# tools must be available from the command line (i.e. in the search path).\n#\n# The result: instead of the source browser generated by doxygen, the links to\n# source code will now point to the output of htags.\n# The default value is: NO.\n# This tag requires that the tag SOURCE_BROWSER is set to YES.\n\nUSE_HTAGS              = NO\n\n# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a\n# verbatim copy of the header file for each class for which an include is\n# specified. Set to NO to disable this.\n# See also: Section \\class.\n# The default value is: YES.\n\nVERBATIM_HEADERS       = YES\n\n# If the CLANG_ASSISTED_PARSING tag is set to YES then doxygen will use the\n# clang parser (see:\n# http://clang.llvm.org/) for more accurate parsing at the cost of reduced\n# performance. This can be particularly helpful with template rich C++ code for\n# which doxygen's built-in parser lacks the necessary type information.\n# Note: The availability of this option depends on whether or not doxygen was\n# generated with the -Duse_libclang=ON option for CMake.\n# The default value is: NO.\n\nCLANG_ASSISTED_PARSING = NO\n\n# If the CLANG_ASSISTED_PARSING tag is set to YES and the CLANG_ADD_INC_PATHS\n# tag is set to YES then doxygen will add the directory of each input to the\n# include path.\n# The default value is: YES.\n# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES.\n\nCLANG_ADD_INC_PATHS    = YES\n\n# If clang assisted parsing is enabled you can provide the compiler with command\n# line options that you would normally use when invoking the compiler. Note that\n# the include paths will already be set by doxygen for the files and directories\n# specified with INPUT and INCLUDE_PATH.\n# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES.\n\nCLANG_OPTIONS          =\n\n# If clang assisted parsing is enabled you can provide the clang parser with the\n# path to the directory containing a file called compile_commands.json. This\n# file is the compilation database (see:\n# http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html) containing the\n# options used when the source files were built. This is equivalent to\n# specifying the -p option to a clang tool, such as clang-check. These options\n# will then be passed to the parser. Any options specified with CLANG_OPTIONS\n# will be added as well.\n# Note: The availability of this option depends on whether or not doxygen was\n# generated with the -Duse_libclang=ON option for CMake.\n\nCLANG_DATABASE_PATH    =\n\n#---------------------------------------------------------------------------\n# Configuration options related to the alphabetical class index\n#---------------------------------------------------------------------------\n\n# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all\n# compounds will be generated. Enable this if the project contains a lot of\n# classes, structs, unions or interfaces.\n# The default value is: YES.\n\nALPHABETICAL_INDEX     = YES\n\n# The IGNORE_PREFIX tag can be used to specify a prefix (or a list of prefixes)\n# that should be ignored while generating the index headers. The IGNORE_PREFIX\n# tag works for classes, function and member names. The entity will be placed in\n# the alphabetical list under the first letter of the entity name that remains\n# after removing the prefix.\n# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.\n\nIGNORE_PREFIX          =\n\n#---------------------------------------------------------------------------\n# Configuration options related to the HTML output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output\n# The default value is: YES.\n\nGENERATE_HTML          = NO\n\n# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a\n# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of\n# it.\n# The default directory is: html.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_OUTPUT            = html\n\n# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each\n# generated HTML page (for example: .htm, .php, .asp).\n# The default value is: .html.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_FILE_EXTENSION    = .html\n\n# The HTML_HEADER tag can be used to specify a user-defined HTML header file for\n# each generated HTML page. If the tag is left blank doxygen will generate a\n# standard header.\n#\n# To get valid HTML the header file that includes any scripts and style sheets\n# that doxygen needs, which is dependent on the configuration options used (e.g.\n# the setting GENERATE_TREEVIEW). It is highly recommended to start with a\n# default header using\n# doxygen -w html new_header.html new_footer.html new_stylesheet.css\n# YourConfigFile\n# and then modify the file new_header.html. See also section \"Doxygen usage\"\n# for information on how to generate the default header that doxygen normally\n# uses.\n# Note: The header is subject to change so you typically have to regenerate the\n# default header when upgrading to a newer version of doxygen. For a description\n# of the possible markers and block names see the documentation.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_HEADER            =\n\n# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each\n# generated HTML page. If the tag is left blank doxygen will generate a standard\n# footer. See HTML_HEADER for more information on how to generate a default\n# footer and what special commands can be used inside the footer. See also\n# section \"Doxygen usage\" for information on how to generate the default footer\n# that doxygen normally uses.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_FOOTER            =\n\n# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style\n# sheet that is used by each HTML page. It can be used to fine-tune the look of\n# the HTML output. If left blank doxygen will generate a default style sheet.\n# See also section \"Doxygen usage\" for information on how to generate the style\n# sheet that doxygen normally uses.\n# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as\n# it is more robust and this tag (HTML_STYLESHEET) will in the future become\n# obsolete.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_STYLESHEET        =\n\n# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined\n# cascading style sheets that are included after the standard style sheets\n# created by doxygen. Using this option one can overrule certain style aspects.\n# This is preferred over using HTML_STYLESHEET since it does not replace the\n# standard style sheet and is therefore more robust against future updates.\n# Doxygen will copy the style sheet files to the output directory.\n# Note: The order of the extra style sheet files is of importance (e.g. the last\n# style sheet in the list overrules the setting of the previous ones in the\n# list).\n# Note: Since the styling of scrollbars can currently not be overruled in\n# Webkit/Chromium, the styling will be left out of the default doxygen.css if\n# one or more extra stylesheets have been specified. So if scrollbar\n# customization is desired it has to be added explicitly. For an example see the\n# documentation.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_EXTRA_STYLESHEET  =\n\n# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or\n# other source files which should be copied to the HTML output directory. Note\n# that these files will be copied to the base HTML output directory. Use the\n# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these\n# files. In the HTML_STYLESHEET file, use the file name only. Also note that the\n# files will be copied as-is; there are no commands or markers available.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_EXTRA_FILES       =\n\n# The HTML_COLORSTYLE tag can be used to specify if the generated HTML output\n# should be rendered with a dark or light theme.\n# Possible values are: LIGHT always generate light mode output, DARK always\n# generate dark mode output, AUTO_LIGHT automatically set the mode according to\n# the user preference, use light mode if no preference is set (the default),\n# AUTO_DARK automatically set the mode according to the user preference, use\n# dark mode if no preference is set and TOGGLE allow to user to switch between\n# light and dark mode via a button.\n# The default value is: AUTO_LIGHT.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_COLORSTYLE        = AUTO_LIGHT\n\n# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen\n# will adjust the colors in the style sheet and background images according to\n# this color. Hue is specified as an angle on a color-wheel, see\n# https://en.wikipedia.org/wiki/Hue for more information. For instance the value\n# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300\n# purple, and 360 is red again.\n# Minimum value: 0, maximum value: 359, default value: 220.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_COLORSTYLE_HUE    = 220\n\n# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors\n# in the HTML output. For a value of 0 the output will use gray-scales only. A\n# value of 255 will produce the most vivid colors.\n# Minimum value: 0, maximum value: 255, default value: 100.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_COLORSTYLE_SAT    = 100\n\n# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the\n# luminance component of the colors in the HTML output. Values below 100\n# gradually make the output lighter, whereas values above 100 make the output\n# darker. The value divided by 100 is the actual gamma applied, so 80 represents\n# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not\n# change the gamma.\n# Minimum value: 40, maximum value: 240, default value: 80.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_COLORSTYLE_GAMMA  = 80\n\n# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML\n# documentation will contain a main index with vertical navigation menus that\n# are dynamically created via JavaScript. If disabled, the navigation index will\n# consists of multiple levels of tabs that are statically embedded in every HTML\n# page. Disable this option to support browsers that do not have JavaScript,\n# like the Qt help browser.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_DYNAMIC_MENUS     = YES\n\n# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML\n# documentation will contain sections that can be hidden and shown after the\n# page has loaded.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_DYNAMIC_SECTIONS  = NO\n\n# If the HTML_CODE_FOLDING tag is set to YES then classes and functions can be\n# dynamically folded and expanded in the generated HTML source code.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_CODE_FOLDING      = YES\n\n# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries\n# shown in the various tree structured indices initially; the user can expand\n# and collapse entries dynamically later on. Doxygen will expand the tree to\n# such a level that at most the specified number of entries are visible (unless\n# a fully collapsed tree already exceeds this amount). So setting the number of\n# entries 1 will produce a full collapsed tree by default. 0 is a special value\n# representing an infinite number of entries and will result in a full expanded\n# tree by default.\n# Minimum value: 0, maximum value: 9999, default value: 100.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_INDEX_NUM_ENTRIES = 100\n\n# If the GENERATE_DOCSET tag is set to YES, additional index files will be\n# generated that can be used as input for Apple's Xcode 3 integrated development\n# environment (see:\n# https://developer.apple.com/xcode/), introduced with OSX 10.5 (Leopard). To\n# create a documentation set, doxygen will generate a Makefile in the HTML\n# output directory. Running make will produce the docset in that directory and\n# running make install will install the docset in\n# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at\n# startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy\n# genXcode/_index.html for more information.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nGENERATE_DOCSET        = NO\n\n# This tag determines the name of the docset feed. A documentation feed provides\n# an umbrella under which multiple documentation sets from a single provider\n# (such as a company or product suite) can be grouped.\n# The default value is: Doxygen generated docs.\n# This tag requires that the tag GENERATE_DOCSET is set to YES.\n\nDOCSET_FEEDNAME        = \"Doxygen generated docs\"\n\n# This tag determines the URL of the docset feed. A documentation feed provides\n# an umbrella under which multiple documentation sets from a single provider\n# (such as a company or product suite) can be grouped.\n# This tag requires that the tag GENERATE_DOCSET is set to YES.\n\nDOCSET_FEEDURL         =\n\n# This tag specifies a string that should uniquely identify the documentation\n# set bundle. This should be a reverse domain-name style string, e.g.\n# com.mycompany.MyDocSet. Doxygen will append .docset to the name.\n# The default value is: org.doxygen.Project.\n# This tag requires that the tag GENERATE_DOCSET is set to YES.\n\nDOCSET_BUNDLE_ID       = org.doxygen.Project\n\n# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify\n# the documentation publisher. This should be a reverse domain-name style\n# string, e.g. com.mycompany.MyDocSet.documentation.\n# The default value is: org.doxygen.Publisher.\n# This tag requires that the tag GENERATE_DOCSET is set to YES.\n\nDOCSET_PUBLISHER_ID    = org.doxygen.Publisher\n\n# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher.\n# The default value is: Publisher.\n# This tag requires that the tag GENERATE_DOCSET is set to YES.\n\nDOCSET_PUBLISHER_NAME  = Publisher\n\n# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three\n# additional HTML index files: index.hhp, index.hhc, and index.hhk. The\n# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop\n# on Windows. In the beginning of 2021 Microsoft took the original page, with\n# a.o. the download links, offline the HTML help workshop was already many years\n# in maintenance mode). You can download the HTML help workshop from the web\n# archives at Installation executable (see:\n# http://web.archive.org/web/20160201063255/http://download.microsoft.com/downlo\n# ad/0/A/9/0A939EF6-E31C-430F-A3DF-DFAE7960D564/htmlhelp.exe).\n#\n# The HTML Help Workshop contains a compiler that can convert all HTML output\n# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML\n# files are now used as the Windows 98 help format, and will replace the old\n# Windows help format (.hlp) on all Windows platforms in the future. Compressed\n# HTML files also contain an index, a table of contents, and you can search for\n# words in the documentation. The HTML workshop also contains a viewer for\n# compressed HTML files.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nGENERATE_HTMLHELP      = NO\n\n# The CHM_FILE tag can be used to specify the file name of the resulting .chm\n# file. You can add a path in front of the file if the result should not be\n# written to the html output directory.\n# This tag requires that the tag GENERATE_HTMLHELP is set to YES.\n\nCHM_FILE               =\n\n# The HHC_LOCATION tag can be used to specify the location (absolute path\n# including file name) of the HTML help compiler (hhc.exe). If non-empty,\n# doxygen will try to run the HTML help compiler on the generated index.hhp.\n# The file has to be specified with full path.\n# This tag requires that the tag GENERATE_HTMLHELP is set to YES.\n\nHHC_LOCATION           =\n\n# The GENERATE_CHI flag controls if a separate .chi index file is generated\n# (YES) or that it should be included in the main .chm file (NO).\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTMLHELP is set to YES.\n\nGENERATE_CHI           = NO\n\n# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc)\n# and project file content.\n# This tag requires that the tag GENERATE_HTMLHELP is set to YES.\n\nCHM_INDEX_ENCODING     =\n\n# The BINARY_TOC flag controls whether a binary table of contents is generated\n# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it\n# enables the Previous and Next buttons.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTMLHELP is set to YES.\n\nBINARY_TOC             = NO\n\n# The TOC_EXPAND flag can be set to YES to add extra items for group members to\n# the table of contents of the HTML help documentation and to the tree view.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTMLHELP is set to YES.\n\nTOC_EXPAND             = NO\n\n# The SITEMAP_URL tag is used to specify the full URL of the place where the\n# generated documentation will be placed on the server by the user during the\n# deployment of the documentation. The generated sitemap is called sitemap.xml\n# and placed on the directory specified by HTML_OUTPUT. In case no SITEMAP_URL\n# is specified no sitemap is generated. For information about the sitemap\n# protocol see https://www.sitemaps.org\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nSITEMAP_URL            =\n\n# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and\n# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that\n# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help\n# (.qch) of the generated HTML documentation.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nGENERATE_QHP           = NO\n\n# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify\n# the file name of the resulting .qch file. The path specified is relative to\n# the HTML output folder.\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQCH_FILE               =\n\n# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help\n# Project output. For more information please see Qt Help Project / Namespace\n# (see:\n# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace).\n# The default value is: org.doxygen.Project.\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQHP_NAMESPACE          = org.doxygen.Project\n\n# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt\n# Help Project output. For more information please see Qt Help Project / Virtual\n# Folders (see:\n# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-folders).\n# The default value is: doc.\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQHP_VIRTUAL_FOLDER     = doc\n\n# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom\n# filter to add. For more information please see Qt Help Project / Custom\n# Filters (see:\n# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters).\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQHP_CUST_FILTER_NAME   =\n\n# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the\n# custom filter to add. For more information please see Qt Help Project / Custom\n# Filters (see:\n# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters).\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQHP_CUST_FILTER_ATTRS  =\n\n# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this\n# project's filter section matches. Qt Help Project / Filter Attributes (see:\n# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes).\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQHP_SECT_FILTER_ATTRS  =\n\n# The QHG_LOCATION tag can be used to specify the location (absolute path\n# including file name) of Qt's qhelpgenerator. If non-empty doxygen will try to\n# run qhelpgenerator on the generated .qhp file.\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQHG_LOCATION           =\n\n# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be\n# generated, together with the HTML files, they form an Eclipse help plugin. To\n# install this plugin and make it available under the help contents menu in\n# Eclipse, the contents of the directory containing the HTML and XML files needs\n# to be copied into the plugins directory of eclipse. The name of the directory\n# within the plugins directory should be the same as the ECLIPSE_DOC_ID value.\n# After copying Eclipse needs to be restarted before the help appears.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nGENERATE_ECLIPSEHELP   = NO\n\n# A unique identifier for the Eclipse help plugin. When installing the plugin\n# the directory name containing the HTML and XML files should also have this\n# name. Each documentation set should have its own identifier.\n# The default value is: org.doxygen.Project.\n# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES.\n\nECLIPSE_DOC_ID         = org.doxygen.Project\n\n# If you want full control over the layout of the generated HTML pages it might\n# be necessary to disable the index and replace it with your own. The\n# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top\n# of each HTML page. A value of NO enables the index and the value YES disables\n# it. Since the tabs in the index contain the same information as the navigation\n# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nDISABLE_INDEX          = NO\n\n# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index\n# structure should be generated to display hierarchical information. If the tag\n# value is set to YES, a side panel will be generated containing a tree-like\n# index structure (just like the one that is generated for HTML Help). For this\n# to work a browser that supports JavaScript, DHTML, CSS and frames is required\n# (i.e. any modern browser). Windows users are probably better off using the\n# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can\n# further fine tune the look of the index (see \"Fine-tuning the output\"). As an\n# example, the default style sheet generated by doxygen has an example that\n# shows how to put an image at the root of the tree instead of the PROJECT_NAME.\n# Since the tree basically has the same information as the tab index, you could\n# consider setting DISABLE_INDEX to YES when enabling this option.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nGENERATE_TREEVIEW      = YES\n\n# When both GENERATE_TREEVIEW and DISABLE_INDEX are set to YES, then the\n# FULL_SIDEBAR option determines if the side bar is limited to only the treeview\n# area (value NO) or if it should extend to the full height of the window (value\n# YES). Setting this to YES gives a layout similar to\n# https://docs.readthedocs.io with more room for contents, but less room for the\n# project logo, title, and description. If either GENERATE_TREEVIEW or\n# DISABLE_INDEX is set to NO, this option has no effect.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nFULL_SIDEBAR           = NO\n\n# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that\n# doxygen will group on one line in the generated HTML documentation.\n#\n# Note that a value of 0 will completely suppress the enum values from appearing\n# in the overview section.\n# Minimum value: 0, maximum value: 20, default value: 4.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nENUM_VALUES_PER_LINE   = 4\n\n# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used\n# to set the initial width (in pixels) of the frame in which the tree is shown.\n# Minimum value: 0, maximum value: 1500, default value: 250.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nTREEVIEW_WIDTH         = 250\n\n# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to\n# external symbols imported via tag files in a separate window.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nEXT_LINKS_IN_WINDOW    = NO\n\n# If the OBFUSCATE_EMAILS tag is set to YES, doxygen will obfuscate email\n# addresses.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nOBFUSCATE_EMAILS       = YES\n\n# If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg\n# tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see\n# https://inkscape.org) to generate formulas as SVG images instead of PNGs for\n# the HTML output. These images will generally look nicer at scaled resolutions.\n# Possible values are: png (the default) and svg (looks nicer but requires the\n# pdf2svg or inkscape tool).\n# The default value is: png.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_FORMULA_FORMAT    = png\n\n# Use this tag to change the font size of LaTeX formulas included as images in\n# the HTML documentation. When you change the font size after a successful\n# doxygen run you need to manually remove any form_*.png images from the HTML\n# output directory to force them to be regenerated.\n# Minimum value: 8, maximum value: 50, default value: 10.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nFORMULA_FONTSIZE       = 10\n\n# The FORMULA_MACROFILE can contain LaTeX \\newcommand and \\renewcommand commands\n# to create new LaTeX commands to be used in formulas as building blocks. See\n# the section \"Including formulas\" for details.\n\nFORMULA_MACROFILE      =\n\n# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see\n# https://www.mathjax.org) which uses client side JavaScript for the rendering\n# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX\n# installed or if you want to formulas look prettier in the HTML output. When\n# enabled you may also need to install MathJax separately and configure the path\n# to it using the MATHJAX_RELPATH option.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nUSE_MATHJAX            = NO\n\n# With MATHJAX_VERSION it is possible to specify the MathJax version to be used.\n# Note that the different versions of MathJax have different requirements with\n# regards to the different settings, so it is possible that also other MathJax\n# settings have to be changed when switching between the different MathJax\n# versions.\n# Possible values are: MathJax_2 and MathJax_3.\n# The default value is: MathJax_2.\n# This tag requires that the tag USE_MATHJAX is set to YES.\n\nMATHJAX_VERSION        = MathJax_2\n\n# When MathJax is enabled you can set the default output format to be used for\n# the MathJax output. For more details about the output format see MathJax\n# version 2 (see:\n# http://docs.mathjax.org/en/v2.7-latest/output.html) and MathJax version 3\n# (see:\n# http://docs.mathjax.org/en/latest/web/components/output.html).\n# Possible values are: HTML-CSS (which is slower, but has the best\n# compatibility. This is the name for Mathjax version 2, for MathJax version 3\n# this will be translated into chtml), NativeMML (i.e. MathML. Only supported\n# for NathJax 2. For MathJax version 3 chtml will be used instead.), chtml (This\n# is the name for Mathjax version 3, for MathJax version 2 this will be\n# translated into HTML-CSS) and SVG.\n# The default value is: HTML-CSS.\n# This tag requires that the tag USE_MATHJAX is set to YES.\n\nMATHJAX_FORMAT         = HTML-CSS\n\n# When MathJax is enabled you need to specify the location relative to the HTML\n# output directory using the MATHJAX_RELPATH option. The destination directory\n# should contain the MathJax.js script. For instance, if the mathjax directory\n# is located at the same level as the HTML output directory, then\n# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax\n# Content Delivery Network so you can quickly see the result without installing\n# MathJax. However, it is strongly recommended to install a local copy of\n# MathJax from https://www.mathjax.org before deployment. The default value is:\n# - in case of MathJax version 2: https://cdn.jsdelivr.net/npm/mathjax@2\n# - in case of MathJax version 3: https://cdn.jsdelivr.net/npm/mathjax@3\n# This tag requires that the tag USE_MATHJAX is set to YES.\n\nMATHJAX_RELPATH        =\n\n# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax\n# extension names that should be enabled during MathJax rendering. For example\n# for MathJax version 2 (see\n# https://docs.mathjax.org/en/v2.7-latest/tex.html#tex-and-latex-extensions):\n# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols\n# For example for MathJax version 3 (see\n# http://docs.mathjax.org/en/latest/input/tex/extensions/index.html):\n# MATHJAX_EXTENSIONS = ams\n# This tag requires that the tag USE_MATHJAX is set to YES.\n\nMATHJAX_EXTENSIONS     =\n\n# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces\n# of code that will be used on startup of the MathJax code. See the MathJax site\n# (see:\n# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. For an\n# example see the documentation.\n# This tag requires that the tag USE_MATHJAX is set to YES.\n\nMATHJAX_CODEFILE       =\n\n# When the SEARCHENGINE tag is enabled doxygen will generate a search box for\n# the HTML output. The underlying search engine uses javascript and DHTML and\n# should work on any modern browser. Note that when using HTML help\n# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET)\n# there is already a search function so this one should typically be disabled.\n# For large projects the javascript based search engine can be slow, then\n# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to\n# search using the keyboard; to jump to the search box use <access key> + S\n# (what the <access key> is depends on the OS and browser, but it is typically\n# <CTRL>, <ALT>/<option>, or both). Inside the search box use the <cursor down\n# key> to jump into the search results window, the results can be navigated\n# using the <cursor keys>. Press <Enter> to select an item or <escape> to cancel\n# the search. The filter options can be selected when the cursor is inside the\n# search box by pressing <Shift>+<cursor down>. Also here use the <cursor keys>\n# to select a filter and <Enter> or <escape> to activate or cancel the filter\n# option.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nSEARCHENGINE           = YES\n\n# When the SERVER_BASED_SEARCH tag is enabled the search engine will be\n# implemented using a web server instead of a web client using JavaScript. There\n# are two flavors of web server based searching depending on the EXTERNAL_SEARCH\n# setting. When disabled, doxygen will generate a PHP script for searching and\n# an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing\n# and searching needs to be provided by external tools. See the section\n# \"External Indexing and Searching\" for details.\n# The default value is: NO.\n# This tag requires that the tag SEARCHENGINE is set to YES.\n\nSERVER_BASED_SEARCH    = NO\n\n# When EXTERNAL_SEARCH tag is enabled doxygen will no longer generate the PHP\n# script for searching. Instead the search results are written to an XML file\n# which needs to be processed by an external indexer. Doxygen will invoke an\n# external search engine pointed to by the SEARCHENGINE_URL option to obtain the\n# search results.\n#\n# Doxygen ships with an example indexer (doxyindexer) and search engine\n# (doxysearch.cgi) which are based on the open source search engine library\n# Xapian (see:\n# https://xapian.org/).\n#\n# See the section \"External Indexing and Searching\" for details.\n# The default value is: NO.\n# This tag requires that the tag SEARCHENGINE is set to YES.\n\nEXTERNAL_SEARCH        = NO\n\n# The SEARCHENGINE_URL should point to a search engine hosted by a web server\n# which will return the search results when EXTERNAL_SEARCH is enabled.\n#\n# Doxygen ships with an example indexer (doxyindexer) and search engine\n# (doxysearch.cgi) which are based on the open source search engine library\n# Xapian (see:\n# https://xapian.org/). See the section \"External Indexing and Searching\" for\n# details.\n# This tag requires that the tag SEARCHENGINE is set to YES.\n\nSEARCHENGINE_URL       =\n\n# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed\n# search data is written to a file for indexing by an external tool. With the\n# SEARCHDATA_FILE tag the name of this file can be specified.\n# The default file is: searchdata.xml.\n# This tag requires that the tag SEARCHENGINE is set to YES.\n\nSEARCHDATA_FILE        = searchdata.xml\n\n# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the\n# EXTERNAL_SEARCH_ID tag can be used as an identifier for the project. This is\n# useful in combination with EXTRA_SEARCH_MAPPINGS to search through multiple\n# projects and redirect the results back to the right project.\n# This tag requires that the tag SEARCHENGINE is set to YES.\n\nEXTERNAL_SEARCH_ID     =\n\n# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen\n# projects other than the one defined by this configuration file, but that are\n# all added to the same external search index. Each project needs to have a\n# unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id of\n# to a relative location where the documentation can be found. The format is:\n# EXTRA_SEARCH_MAPPINGS = tagname1=loc1 tagname2=loc2 ...\n# This tag requires that the tag SEARCHENGINE is set to YES.\n\nEXTRA_SEARCH_MAPPINGS  =\n\n#---------------------------------------------------------------------------\n# Configuration options related to the LaTeX output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output.\n# The default value is: YES.\n\nGENERATE_LATEX         = NO\n\n# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If a\n# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of\n# it.\n# The default directory is: latex.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_OUTPUT           = latex\n\n# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be\n# invoked.\n#\n# Note that when not enabling USE_PDFLATEX the default is latex when enabling\n# USE_PDFLATEX the default is pdflatex and when in the later case latex is\n# chosen this is overwritten by pdflatex. For specific output languages the\n# default can have been set differently, this depends on the implementation of\n# the output language.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_CMD_NAME         =\n\n# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate\n# index for LaTeX.\n# Note: This tag is used in the Makefile / make.bat.\n# See also: LATEX_MAKEINDEX_CMD for the part in the generated output file\n# (.tex).\n# The default file is: makeindex.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nMAKEINDEX_CMD_NAME     = makeindex\n\n# The LATEX_MAKEINDEX_CMD tag can be used to specify the command name to\n# generate index for LaTeX. In case there is no backslash (\\) as first character\n# it will be automatically added in the LaTeX code.\n# Note: This tag is used in the generated output file (.tex).\n# See also: MAKEINDEX_CMD_NAME for the part in the Makefile / make.bat.\n# The default value is: makeindex.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_MAKEINDEX_CMD    = makeindex\n\n# If the COMPACT_LATEX tag is set to YES, doxygen generates more compact LaTeX\n# documents. This may be useful for small projects and may help to save some\n# trees in general.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nCOMPACT_LATEX          = NO\n\n# The PAPER_TYPE tag can be used to set the paper type that is used by the\n# printer.\n# Possible values are: a4 (210 x 297 mm), letter (8.5 x 11 inches), legal (8.5 x\n# 14 inches) and executive (7.25 x 10.5 inches).\n# The default value is: a4.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nPAPER_TYPE             = a4\n\n# The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names\n# that should be included in the LaTeX output. The package can be specified just\n# by its name or with the correct syntax as to be used with the LaTeX\n# \\usepackage command. To get the times font for instance you can specify :\n# EXTRA_PACKAGES=times or EXTRA_PACKAGES={times}\n# To use the option intlimits with the amsmath package you can specify:\n# EXTRA_PACKAGES=[intlimits]{amsmath}\n# If left blank no extra packages will be included.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nEXTRA_PACKAGES         =\n\n# The LATEX_HEADER tag can be used to specify a user-defined LaTeX header for\n# the generated LaTeX document. The header should contain everything until the\n# first chapter. If it is left blank doxygen will generate a standard header. It\n# is highly recommended to start with a default header using\n# doxygen -w latex new_header.tex new_footer.tex new_stylesheet.sty\n# and then modify the file new_header.tex. See also section \"Doxygen usage\" for\n# information on how to generate the default header that doxygen normally uses.\n#\n# Note: Only use a user-defined header if you know what you are doing!\n# Note: The header is subject to change so you typically have to regenerate the\n# default header when upgrading to a newer version of doxygen. The following\n# commands have a special meaning inside the header (and footer): For a\n# description of the possible markers and block names see the documentation.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_HEADER           =\n\n# The LATEX_FOOTER tag can be used to specify a user-defined LaTeX footer for\n# the generated LaTeX document. The footer should contain everything after the\n# last chapter. If it is left blank doxygen will generate a standard footer. See\n# LATEX_HEADER for more information on how to generate a default footer and what\n# special commands can be used inside the footer. See also section \"Doxygen\n# usage\" for information on how to generate the default footer that doxygen\n# normally uses. Note: Only use a user-defined footer if you know what you are\n# doing!\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_FOOTER           =\n\n# The LATEX_EXTRA_STYLESHEET tag can be used to specify additional user-defined\n# LaTeX style sheets that are included after the standard style sheets created\n# by doxygen. Using this option one can overrule certain style aspects. Doxygen\n# will copy the style sheet files to the output directory.\n# Note: The order of the extra style sheet files is of importance (e.g. the last\n# style sheet in the list overrules the setting of the previous ones in the\n# list).\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_EXTRA_STYLESHEET =\n\n# The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or\n# other source files which should be copied to the LATEX_OUTPUT output\n# directory. Note that the files will be copied as-is; there are no commands or\n# markers available.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_EXTRA_FILES      =\n\n# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated is\n# prepared for conversion to PDF (using ps2pdf or pdflatex). The PDF file will\n# contain links (just like the HTML output) instead of page references. This\n# makes the output suitable for online browsing using a PDF viewer.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nPDF_HYPERLINKS         = YES\n\n# If the USE_PDFLATEX tag is set to YES, doxygen will use the engine as\n# specified with LATEX_CMD_NAME to generate the PDF file directly from the LaTeX\n# files. Set this option to YES, to get a higher quality PDF documentation.\n#\n# See also section LATEX_CMD_NAME for selecting the engine.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nUSE_PDFLATEX           = YES\n\n# The LATEX_BATCHMODE tag signals the behavior of LaTeX in case of an error.\n# Possible values are: NO same as ERROR_STOP, YES same as BATCH, BATCH In batch\n# mode nothing is printed on the terminal, errors are scrolled as if <return> is\n# hit at every error; missing files that TeX tries to input or request from\n# keyboard input (\\read on a not open input stream) cause the job to abort,\n# NON_STOP In nonstop mode the diagnostic message will appear on the terminal,\n# but there is no possibility of user interaction just like in batch mode,\n# SCROLL In scroll mode, TeX will stop only for missing files to input or if\n# keyboard input is necessary and ERROR_STOP In errorstop mode, TeX will stop at\n# each error, asking for user intervention.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_BATCHMODE        = NO\n\n# If the LATEX_HIDE_INDICES tag is set to YES then doxygen will not include the\n# index chapters (such as File Index, Compound Index, etc.) in the output.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_HIDE_INDICES     = NO\n\n# The LATEX_BIB_STYLE tag can be used to specify the style to use for the\n# bibliography, e.g. plainnat, or ieeetr. See\n# https://en.wikipedia.org/wiki/BibTeX and \\cite for more info.\n# The default value is: plain.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_BIB_STYLE        = plain\n\n# The LATEX_EMOJI_DIRECTORY tag is used to specify the (relative or absolute)\n# path from which the emoji images will be read. If a relative path is entered,\n# it will be relative to the LATEX_OUTPUT directory. If left blank the\n# LATEX_OUTPUT directory will be used.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_EMOJI_DIRECTORY  =\n\n#---------------------------------------------------------------------------\n# Configuration options related to the RTF output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_RTF tag is set to YES, doxygen will generate RTF output. The\n# RTF output is optimized for Word 97 and may not look too pretty with other RTF\n# readers/editors.\n# The default value is: NO.\n\nGENERATE_RTF           = NO\n\n# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. If a\n# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of\n# it.\n# The default directory is: rtf.\n# This tag requires that the tag GENERATE_RTF is set to YES.\n\nRTF_OUTPUT             = rtf\n\n# If the COMPACT_RTF tag is set to YES, doxygen generates more compact RTF\n# documents. This may be useful for small projects and may help to save some\n# trees in general.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_RTF is set to YES.\n\nCOMPACT_RTF            = NO\n\n# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated will\n# contain hyperlink fields. The RTF file will contain links (just like the HTML\n# output) instead of page references. This makes the output suitable for online\n# browsing using Word or some other Word compatible readers that support those\n# fields.\n#\n# Note: WordPad (write) and others do not support links.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_RTF is set to YES.\n\nRTF_HYPERLINKS         = NO\n\n# Load stylesheet definitions from file. Syntax is similar to doxygen's\n# configuration file, i.e. a series of assignments. You only have to provide\n# replacements, missing definitions are set to their default value.\n#\n# See also section \"Doxygen usage\" for information on how to generate the\n# default style sheet that doxygen normally uses.\n# This tag requires that the tag GENERATE_RTF is set to YES.\n\nRTF_STYLESHEET_FILE    =\n\n# Set optional variables used in the generation of an RTF document. Syntax is\n# similar to doxygen's configuration file. A template extensions file can be\n# generated using doxygen -e rtf extensionFile.\n# This tag requires that the tag GENERATE_RTF is set to YES.\n\nRTF_EXTENSIONS_FILE    =\n\n#---------------------------------------------------------------------------\n# Configuration options related to the man page output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_MAN tag is set to YES, doxygen will generate man pages for\n# classes and files.\n# The default value is: NO.\n\nGENERATE_MAN           = NO\n\n# The MAN_OUTPUT tag is used to specify where the man pages will be put. If a\n# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of\n# it. A directory man3 will be created inside the directory specified by\n# MAN_OUTPUT.\n# The default directory is: man.\n# This tag requires that the tag GENERATE_MAN is set to YES.\n\nMAN_OUTPUT             = man\n\n# The MAN_EXTENSION tag determines the extension that is added to the generated\n# man pages. In case the manual section does not start with a number, the number\n# 3 is prepended. The dot (.) at the beginning of the MAN_EXTENSION tag is\n# optional.\n# The default value is: .3.\n# This tag requires that the tag GENERATE_MAN is set to YES.\n\nMAN_EXTENSION          = .3\n\n# The MAN_SUBDIR tag determines the name of the directory created within\n# MAN_OUTPUT in which the man pages are placed. If defaults to man followed by\n# MAN_EXTENSION with the initial . removed.\n# This tag requires that the tag GENERATE_MAN is set to YES.\n\nMAN_SUBDIR             =\n\n# If the MAN_LINKS tag is set to YES and doxygen generates man output, then it\n# will generate one additional man file for each entity documented in the real\n# man page(s). These additional files only source the real man page, but without\n# them the man command would be unable to find the correct page.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_MAN is set to YES.\n\nMAN_LINKS              = NO\n\n#---------------------------------------------------------------------------\n# Configuration options related to the XML output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_XML tag is set to YES, doxygen will generate an XML file that\n# captures the structure of the code including all documentation.\n# The default value is: NO.\n\nGENERATE_XML           = YES\n\n# The XML_OUTPUT tag is used to specify where the XML pages will be put. If a\n# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of\n# it.\n# The default directory is: xml.\n# This tag requires that the tag GENERATE_XML is set to YES.\n\nXML_OUTPUT             = xml\n\n# If the XML_PROGRAMLISTING tag is set to YES, doxygen will dump the program\n# listings (including syntax highlighting and cross-referencing information) to\n# the XML output. Note that enabling this will significantly increase the size\n# of the XML output.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_XML is set to YES.\n\nXML_PROGRAMLISTING     = YES\n\n# If the XML_NS_MEMB_FILE_SCOPE tag is set to YES, doxygen will include\n# namespace members in file scope as well, matching the HTML output.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_XML is set to YES.\n\nXML_NS_MEMB_FILE_SCOPE = NO\n\n#---------------------------------------------------------------------------\n# Configuration options related to the DOCBOOK output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_DOCBOOK tag is set to YES, doxygen will generate Docbook files\n# that can be used to generate PDF.\n# The default value is: NO.\n\nGENERATE_DOCBOOK       = NO\n\n# The DOCBOOK_OUTPUT tag is used to specify where the Docbook pages will be put.\n# If a relative path is entered the value of OUTPUT_DIRECTORY will be put in\n# front of it.\n# The default directory is: docbook.\n# This tag requires that the tag GENERATE_DOCBOOK is set to YES.\n\nDOCBOOK_OUTPUT         = docbook\n\n#---------------------------------------------------------------------------\n# Configuration options for the AutoGen Definitions output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an\n# AutoGen Definitions (see https://autogen.sourceforge.net/) file that captures\n# the structure of the code including all documentation. Note that this feature\n# is still experimental and incomplete at the moment.\n# The default value is: NO.\n\nGENERATE_AUTOGEN_DEF   = NO\n\n#---------------------------------------------------------------------------\n# Configuration options related to Sqlite3 output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_SQLITE3 tag is set to YES doxygen will generate a Sqlite3\n# database with symbols found by doxygen stored in tables.\n# The default value is: NO.\n\nGENERATE_SQLITE3       = NO\n\n# The SQLITE3_OUTPUT tag is used to specify where the Sqlite3 database will be\n# put. If a relative path is entered the value of OUTPUT_DIRECTORY will be put\n# in front of it.\n# The default directory is: sqlite3.\n# This tag requires that the tag GENERATE_SQLITE3 is set to YES.\n\nSQLITE3_OUTPUT         = sqlite3\n\n# The SQLITE3_OVERWRITE_DB tag is set to YES, the existing doxygen_sqlite3.db\n# database file will be recreated with each doxygen run. If set to NO, doxygen\n# will warn if an a database file is already found and not modify it.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_SQLITE3 is set to YES.\n\nSQLITE3_RECREATE_DB    = YES\n\n#---------------------------------------------------------------------------\n# Configuration options related to the Perl module output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_PERLMOD tag is set to YES, doxygen will generate a Perl module\n# file that captures the structure of the code including all documentation.\n#\n# Note that this feature is still experimental and incomplete at the moment.\n# The default value is: NO.\n\nGENERATE_PERLMOD       = NO\n\n# If the PERLMOD_LATEX tag is set to YES, doxygen will generate the necessary\n# Makefile rules, Perl scripts and LaTeX code to be able to generate PDF and DVI\n# output from the Perl module output.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_PERLMOD is set to YES.\n\nPERLMOD_LATEX          = NO\n\n# If the PERLMOD_PRETTY tag is set to YES, the Perl module output will be nicely\n# formatted so it can be parsed by a human reader. This is useful if you want to\n# understand what is going on. On the other hand, if this tag is set to NO, the\n# size of the Perl module output will be much smaller and Perl will parse it\n# just the same.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_PERLMOD is set to YES.\n\nPERLMOD_PRETTY         = YES\n\n# The names of the make variables in the generated doxyrules.make file are\n# prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. This is useful\n# so different doxyrules.make files included by the same Makefile don't\n# overwrite each other's variables.\n# This tag requires that the tag GENERATE_PERLMOD is set to YES.\n\nPERLMOD_MAKEVAR_PREFIX =\n\n#---------------------------------------------------------------------------\n# Configuration options related to the preprocessor\n#---------------------------------------------------------------------------\n\n# If the ENABLE_PREPROCESSING tag is set to YES, doxygen will evaluate all\n# C-preprocessor directives found in the sources and include files.\n# The default value is: YES.\n\nENABLE_PREPROCESSING   = YES\n\n# If the MACRO_EXPANSION tag is set to YES, doxygen will expand all macro names\n# in the source code. If set to NO, only conditional compilation will be\n# performed. Macro expansion can be done in a controlled way by setting\n# EXPAND_ONLY_PREDEF to YES.\n# The default value is: NO.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nMACRO_EXPANSION        = YES\n\n# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then\n# the macro expansion is limited to the macros specified with the PREDEFINED and\n# EXPAND_AS_DEFINED tags.\n# The default value is: NO.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nEXPAND_ONLY_PREDEF     = YES\n\n# If the SEARCH_INCLUDES tag is set to YES, the include files in the\n# INCLUDE_PATH will be searched if a #include is found.\n# The default value is: YES.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nSEARCH_INCLUDES        = YES\n\n# The INCLUDE_PATH tag can be used to specify one or more directories that\n# contain include files that are not input files but should be processed by the\n# preprocessor. Note that the INCLUDE_PATH is not recursive, so the setting of\n# RECURSIVE has no effect here.\n# This tag requires that the tag SEARCH_INCLUDES is set to YES.\n\nINCLUDE_PATH           = deps/avs_coap/include_public \\\n                         deps/avs_commons/include_public\n\n# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard\n# patterns (like *.h and *.hpp) to filter out the header-files in the\n# directories. If left blank, the patterns specified with FILE_PATTERNS will be\n# used.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nINCLUDE_FILE_PATTERNS  =\n\n# The PREDEFINED tag can be used to specify one or more macro names that are\n# defined before the preprocessor is started (similar to the -D option of e.g.\n# gcc). The argument of the tag is a list of macros of the form: name or\n# name=definition (no spaces). If the definition and the \"=\" are omitted, \"=1\"\n# is assumed. To prevent a macro definition from being undefined via #undef or\n# recursively expanded use the := operator instead of the = operator.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nPREDEFINED             = @DOXYFILE_PREDEFINED_MACROS@ AVS_LIST(x)=AVS_LIST<x>\n\n# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this\n# tag can be used to specify a list of macro names that should be expanded. The\n# macro definition that is found in the sources will be used. Use the PREDEFINED\n# tag if you want to use a different macro definition that overrules the\n# definition found in the source code.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nEXPAND_AS_DEFINED      =\n\n# If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will\n# remove all references to function-like macros that are alone on a line, have\n# an all uppercase name, and do not end with a semicolon. Such function macros\n# are typically used for boiler-plate code, and will confuse the parser if not\n# removed.\n# The default value is: YES.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nSKIP_FUNCTION_MACROS   = YES\n\n#---------------------------------------------------------------------------\n# Configuration options related to external references\n#---------------------------------------------------------------------------\n\n# The TAGFILES tag can be used to specify one or more tag files. For each tag\n# file the location of the external documentation should be added. The format of\n# a tag file without this location is as follows:\n# TAGFILES = file1 file2 ...\n# Adding location for the tag files is done as follows:\n# TAGFILES = file1=loc1 \"file2 = loc2\" ...\n# where loc1 and loc2 can be relative or absolute paths or URLs. See the\n# section \"Linking to external documentation\" for more information about the use\n# of tag files.\n# Note: Each tag file must have a unique name (where the name does NOT include\n# the path). If a tag file is not located in the directory in which doxygen is\n# run, you must also specify the path to the tagfile here.\n\nTAGFILES               =\n\n# When a file name is specified after GENERATE_TAGFILE, doxygen will create a\n# tag file that is based on the input files it reads. See section \"Linking to\n# external documentation\" for more information about the usage of tag files.\n\nGENERATE_TAGFILE       =\n\n# If the ALLEXTERNALS tag is set to YES, all external classes and namespaces\n# will be listed in the class and namespace index. If set to NO, only the\n# inherited external classes will be listed.\n# The default value is: NO.\n\nALLEXTERNALS           = NO\n\n# If the EXTERNAL_GROUPS tag is set to YES, all external groups will be listed\n# in the topic index. If set to NO, only the current project's groups will be\n# listed.\n# The default value is: YES.\n\nEXTERNAL_GROUPS        = YES\n\n# If the EXTERNAL_PAGES tag is set to YES, all external pages will be listed in\n# the related pages index. If set to NO, only the current project's pages will\n# be listed.\n# The default value is: YES.\n\nEXTERNAL_PAGES         = YES\n\n#---------------------------------------------------------------------------\n# Configuration options related to diagram generator tools\n#---------------------------------------------------------------------------\n\n# If set to YES the inheritance and collaboration graphs will hide inheritance\n# and usage relations if the target is undocumented or is not a class.\n# The default value is: YES.\n\nHIDE_UNDOC_RELATIONS   = YES\n\n# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is\n# available from the path. This tool is part of Graphviz (see:\n# https://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent\n# Bell Labs. The other options in this section have no effect if this option is\n# set to NO\n# The default value is: YES.\n\nHAVE_DOT               = NO\n\n# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed\n# to run in parallel. When set to 0 doxygen will base this on the number of\n# processors available in the system. You can set it explicitly to a value\n# larger than 0 to get control over the balance between CPU load and processing\n# speed.\n# Minimum value: 0, maximum value: 32, default value: 0.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_NUM_THREADS        = 0\n\n# DOT_COMMON_ATTR is common attributes for nodes, edges and labels of\n# subgraphs. When you want a differently looking font in the dot files that\n# doxygen generates you can specify fontname, fontcolor and fontsize attributes.\n# For details please see <a href=https://graphviz.org/doc/info/attrs.html>Node,\n# Edge and Graph Attributes specification</a> You need to make sure dot is able\n# to find the font, which can be done by putting it in a standard location or by\n# setting the DOTFONTPATH environment variable or by setting DOT_FONTPATH to the\n# directory containing the font. Default graphviz fontsize is 14.\n# The default value is: fontname=Helvetica,fontsize=10.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_COMMON_ATTR        = \"fontname=Helvetica,fontsize=10\"\n\n# DOT_EDGE_ATTR is concatenated with DOT_COMMON_ATTR. For elegant style you can\n# add 'arrowhead=open, arrowtail=open, arrowsize=0.5'. <a\n# href=https://graphviz.org/doc/info/arrows.html>Complete documentation about\n# arrows shapes.</a>\n# The default value is: labelfontname=Helvetica,labelfontsize=10.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_EDGE_ATTR          = \"labelfontname=Helvetica,labelfontsize=10\"\n\n# DOT_NODE_ATTR is concatenated with DOT_COMMON_ATTR. For view without boxes\n# around nodes set 'shape=plain' or 'shape=plaintext' <a\n# href=https://www.graphviz.org/doc/info/shapes.html>Shapes specification</a>\n# The default value is: shape=box,height=0.2,width=0.4.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_NODE_ATTR          = \"shape=box,height=0.2,width=0.4\"\n\n# You can set the path where dot can find font specified with fontname in\n# DOT_COMMON_ATTR and others dot attributes.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_FONTPATH           =\n\n# If the CLASS_GRAPH tag is set to YES or GRAPH or BUILTIN then doxygen will\n# generate a graph for each documented class showing the direct and indirect\n# inheritance relations. In case the CLASS_GRAPH tag is set to YES or GRAPH and\n# HAVE_DOT is enabled as well, then dot will be used to draw the graph. In case\n# the CLASS_GRAPH tag is set to YES and HAVE_DOT is disabled or if the\n# CLASS_GRAPH tag is set to BUILTIN, then the built-in generator will be used.\n# If the CLASS_GRAPH tag is set to TEXT the direct and indirect inheritance\n# relations will be shown as texts / links.\n# Possible values are: NO, YES, TEXT, GRAPH and BUILTIN.\n# The default value is: YES.\n\nCLASS_GRAPH            = YES\n\n# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a\n# graph for each documented class showing the direct and indirect implementation\n# dependencies (inheritance, containment, and class references variables) of the\n# class with other documented classes. Explicit enabling a collaboration graph,\n# when COLLABORATION_GRAPH is set to NO, can be accomplished by means of the\n# command \\collaborationgraph. Disabling a collaboration graph can be\n# accomplished by means of the command \\hidecollaborationgraph.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nCOLLABORATION_GRAPH    = YES\n\n# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for\n# groups, showing the direct groups dependencies. Explicit enabling a group\n# dependency graph, when GROUP_GRAPHS is set to NO, can be accomplished by means\n# of the command \\groupgraph. Disabling a directory graph can be accomplished by\n# means of the command \\hidegroupgraph. See also the chapter Grouping in the\n# manual.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nGROUP_GRAPHS           = YES\n\n# If the UML_LOOK tag is set to YES, doxygen will generate inheritance and\n# collaboration diagrams in a style similar to the OMG's Unified Modeling\n# Language.\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nUML_LOOK               = NO\n\n# If the UML_LOOK tag is enabled, the fields and methods are shown inside the\n# class node. If there are many fields or methods and many nodes the graph may\n# become too big to be useful. The UML_LIMIT_NUM_FIELDS threshold limits the\n# number of items for each type to make the size more manageable. Set this to 0\n# for no limit. Note that the threshold may be exceeded by 50% before the limit\n# is enforced. So when you set the threshold to 10, up to 15 fields may appear,\n# but if the number exceeds 15, the total amount of fields shown is limited to\n# 10.\n# Minimum value: 0, maximum value: 100, default value: 10.\n# This tag requires that the tag UML_LOOK is set to YES.\n\nUML_LIMIT_NUM_FIELDS   = 10\n\n# If the DOT_UML_DETAILS tag is set to NO, doxygen will show attributes and\n# methods without types and arguments in the UML graphs. If the DOT_UML_DETAILS\n# tag is set to YES, doxygen will add type and arguments for attributes and\n# methods in the UML graphs. If the DOT_UML_DETAILS tag is set to NONE, doxygen\n# will not generate fields with class member information in the UML graphs. The\n# class diagrams will look similar to the default class diagrams but using UML\n# notation for the relationships.\n# Possible values are: NO, YES and NONE.\n# The default value is: NO.\n# This tag requires that the tag UML_LOOK is set to YES.\n\nDOT_UML_DETAILS        = NO\n\n# The DOT_WRAP_THRESHOLD tag can be used to set the maximum number of characters\n# to display on a single line. If the actual line length exceeds this threshold\n# significantly it will wrapped across multiple lines. Some heuristics are apply\n# to avoid ugly line breaks.\n# Minimum value: 0, maximum value: 1000, default value: 17.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_WRAP_THRESHOLD     = 17\n\n# If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and\n# collaboration graphs will show the relations between templates and their\n# instances.\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nTEMPLATE_RELATIONS     = NO\n\n# If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to\n# YES then doxygen will generate a graph for each documented file showing the\n# direct and indirect include dependencies of the file with other documented\n# files. Explicit enabling an include graph, when INCLUDE_GRAPH is is set to NO,\n# can be accomplished by means of the command \\includegraph. Disabling an\n# include graph can be accomplished by means of the command \\hideincludegraph.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nINCLUDE_GRAPH          = YES\n\n# If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are\n# set to YES then doxygen will generate a graph for each documented file showing\n# the direct and indirect include dependencies of the file with other documented\n# files. Explicit enabling an included by graph, when INCLUDED_BY_GRAPH is set\n# to NO, can be accomplished by means of the command \\includedbygraph. Disabling\n# an included by graph can be accomplished by means of the command\n# \\hideincludedbygraph.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nINCLUDED_BY_GRAPH      = YES\n\n# If the CALL_GRAPH tag is set to YES then doxygen will generate a call\n# dependency graph for every global function or class method.\n#\n# Note that enabling this option will significantly increase the time of a run.\n# So in most cases it will be better to enable call graphs for selected\n# functions only using the \\callgraph command. Disabling a call graph can be\n# accomplished by means of the command \\hidecallgraph.\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nCALL_GRAPH             = NO\n\n# If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller\n# dependency graph for every global function or class method.\n#\n# Note that enabling this option will significantly increase the time of a run.\n# So in most cases it will be better to enable caller graphs for selected\n# functions only using the \\callergraph command. Disabling a caller graph can be\n# accomplished by means of the command \\hidecallergraph.\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nCALLER_GRAPH           = NO\n\n# If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical\n# hierarchy of all classes instead of a textual one.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nGRAPHICAL_HIERARCHY    = YES\n\n# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the\n# dependencies a directory has on other directories in a graphical way. The\n# dependency relations are determined by the #include relations between the\n# files in the directories. Explicit enabling a directory graph, when\n# DIRECTORY_GRAPH is set to NO, can be accomplished by means of the command\n# \\directorygraph. Disabling a directory graph can be accomplished by means of\n# the command \\hidedirectorygraph.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDIRECTORY_GRAPH        = YES\n\n# The DIR_GRAPH_MAX_DEPTH tag can be used to limit the maximum number of levels\n# of child directories generated in directory dependency graphs by dot.\n# Minimum value: 1, maximum value: 25, default value: 1.\n# This tag requires that the tag DIRECTORY_GRAPH is set to YES.\n\nDIR_GRAPH_MAX_DEPTH    = 1\n\n# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images\n# generated by dot. For an explanation of the image formats see the section\n# output formats in the documentation of the dot tool (Graphviz (see:\n# https://www.graphviz.org/)).\n# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order\n# to make the SVG files visible in IE 9+ (other browsers do not have this\n# requirement).\n# Possible values are: png, jpg, jpg:cairo, jpg:cairo:gd, jpg:gd, jpg:gd:gd,\n# gif, gif:cairo, gif:cairo:gd, gif:gd, gif:gd:gd, svg, png:gd, png:gd:gd,\n# png:cairo, png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and\n# png:gdiplus:gdiplus.\n# The default value is: png.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_IMAGE_FORMAT       = png\n\n# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to\n# enable generation of interactive SVG images that allow zooming and panning.\n#\n# Note that this requires a modern browser other than Internet Explorer. Tested\n# and working are Firefox, Chrome, Safari, and Opera.\n# Note: For IE 9+ you need to set HTML_FILE_EXTENSION to xhtml in order to make\n# the SVG files visible. Older versions of IE do not have SVG support.\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nINTERACTIVE_SVG        = NO\n\n# The DOT_PATH tag can be used to specify the path where the dot tool can be\n# found. If left blank, it is assumed the dot tool can be found in the path.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_PATH               =\n\n# The DOTFILE_DIRS tag can be used to specify one or more directories that\n# contain dot files that are included in the documentation (see the \\dotfile\n# command).\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOTFILE_DIRS           =\n\n# You can include diagrams made with dia in doxygen documentation. Doxygen will\n# then run dia to produce the diagram and insert it in the documentation. The\n# DIA_PATH tag allows you to specify the directory where the dia binary resides.\n# If left empty dia is assumed to be found in the default search path.\n\nDIA_PATH               =\n\n# The DIAFILE_DIRS tag can be used to specify one or more directories that\n# contain dia files that are included in the documentation (see the \\diafile\n# command).\n\nDIAFILE_DIRS           =\n\n# When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the\n# path where java can find the plantuml.jar file or to the filename of jar file\n# to be used. If left blank, it is assumed PlantUML is not used or called during\n# a preprocessing step. Doxygen will generate a warning when it encounters a\n# \\startuml command in this case and will not generate output for the diagram.\n\nPLANTUML_JAR_PATH      =\n\n# When using plantuml, the PLANTUML_CFG_FILE tag can be used to specify a\n# configuration file for plantuml.\n\nPLANTUML_CFG_FILE      =\n\n# When using plantuml, the specified paths are searched for files specified by\n# the !include statement in a plantuml block.\n\nPLANTUML_INCLUDE_PATH  =\n\n# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes\n# that will be shown in the graph. If the number of nodes in a graph becomes\n# larger than this value, doxygen will truncate the graph, which is visualized\n# by representing a node as a red box. Note that doxygen if the number of direct\n# children of the root node in a graph is already larger than\n# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note that\n# the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.\n# Minimum value: 0, maximum value: 10000, default value: 50.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_GRAPH_MAX_NODES    = 50\n\n# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the graphs\n# generated by dot. A depth value of 3 means that only nodes reachable from the\n# root by following a path via at most 3 edges will be shown. Nodes that lay\n# further from the root node will be omitted. Note that setting this option to 1\n# or 2 may greatly reduce the computation time needed for large code bases. Also\n# note that the size of a graph can be further restricted by\n# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.\n# Minimum value: 0, maximum value: 1000, default value: 0.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nMAX_DOT_GRAPH_DEPTH    = 0\n\n# Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output\n# files in one run (i.e. multiple -o and -T options on the command line). This\n# makes dot run faster, but since only newer versions of dot (>1.8.10) support\n# this, this feature is disabled by default.\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_MULTI_TARGETS      = NO\n\n# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page\n# explaining the meaning of the various boxes and arrows in the dot generated\n# graphs.\n# Note: This tag requires that UML_LOOK isn't set, i.e. the doxygen internal\n# graphical representation for inheritance and collaboration diagrams is used.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nGENERATE_LEGEND        = YES\n\n# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate\n# files that are used to generate the various graphs.\n#\n# Note: This setting is not only used for dot files but also for msc temporary\n# files.\n# The default value is: YES.\n\nDOT_CLEANUP            = YES\n\n# You can define message sequence charts within doxygen comments using the \\msc\n# command. If the MSCGEN_TOOL tag is left empty (the default), then doxygen will\n# use a built-in version of mscgen tool to produce the charts. Alternatively,\n# the MSCGEN_TOOL tag can also specify the name an external tool. For instance,\n# specifying prog as the value, doxygen will call the tool as prog -T\n# <outfile_format> -o <outputfile> <inputfile>. The external tool should support\n# output file formats \"png\", \"eps\", \"svg\", and \"ismap\".\n\nMSCGEN_TOOL            =\n\n# The MSCFILE_DIRS tag can be used to specify one or more directories that\n# contain msc files that are included in the documentation (see the \\mscfile\n# command).\n\nMSCFILE_DIRS           =\n"
  },
  {
    "path": "LICENSE",
    "content": "AVSystem Anjay LwM2M Client SDK - Non-Commercial License\n========================================================\n\nVersion 1.0\nEffective Date: May 2025\nCopyright 2017-2026 AVSystem Sp. z o.o.\n\nSubject to the terms and conditions set forth herein, AVSystem Sp. z o.o.\n(\"Licensor\") hereby grants any person or legal entity obtaining a copy of this\nsoftware and associated documentation files (collectively, the \"Software\") a\nlimited, non-exclusive, non-transferable, royalty-free license to use,\nreproduce, modify, and distribute the Software solely for Non-Commercial\nPurposes, as defined below.\n\n1. DEFINITIONS\n\n   \"Non-Commercial Purposes\" means use of the Software:\n   - for internal evaluation or prototyping by individuals or legal entities;\n   - in academic or research institutions for educational or scientific\n     purposes;\n   - in personal, non-revenue-generating projects;\n   - in open-source, community, or not-for-profit settings, provided such use is\n     not part of any direct or indirect commercial activity.\n\n   \"Commercial Use\" includes, but is not limited to:\n   - incorporating the Software into any commercial product or service;\n   - using the Software in production systems operated by for-profit entities;\n   - using the Software in any revenue-generating or business-critical\n     operations;\n   - deploying the Software in any manner that supports commercial offerings,\n     including SaaS, OEM integrations, or professional services.\n\n2. LICENSE CONDITIONS\n\n   Subject to compliance with this License, the Licensee may:\n   1. use the Software for Non-Commercial Purposes;\n   2. copy and modify the Software;\n   3. distribute unmodified or modified versions of the Software, provided that:\n      - Redistribution of source code must retain AVSystem’s copyright notice\n        and include the list of usage conditions and AVSystem’s disclaimer;\n      - Redistribution in binary form, except as embedded in a physical device,\n        must include the following acknowledgment in accompanying documentation\n        or user interface: \"This product includes software developed by AVSystem\n        Sp. z o.o.\", this list of conditions of use and the following disclaimer\n        in the documentation and/or other materials provided with the software.\n      - Neither the name of AVSystem nor its collaborators (if any) may be used\n        to offer or promote products derived from this software without prior\n        written consent of the owner.\n\n3. DISCLAIMER OF WARRANTY\n\n   THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n   IMPLIED, INCLUDING, WITHOUT LIMITATION, WARRANTIES OF MERCHANTABILITY,\n   FITNESS FOR A PARTICULAR PURPOSE, TITLE, OR NON-INFRINGEMENT.\n   IN NO EVENT SHALL THE LICENSOR, ITS AFFILIATES, OR CONTRIBUTORS BE LIABLE FOR\n   ANY DAMAGES, INCLUDING DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n   CONSEQUENTIAL DAMAGES (INCLUDING BUT NOT LIMITED TO PROCUREMENT OF SUBSTITUTE\n   GOODS OR SERVICES, LOSS OF USE, DATA, OR PROFITS, OR BUSINESS INTERRUPTION)\n   HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n   LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE), ARISING IN ANY WAY\n   OUT OF THE USE OF THE SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH\n   DAMAGE.\n\n4. COMMERCIAL LICENSING\n\n   Any use of the Software falling outside the scope of Non-Commercial Purposes\n   constitutes Commercial Use and is expressly prohibited under this License.\n   To obtain rights for Commercial Use, including deployment, integration into\n   commercial products, or support for business operations, the Licensee must\n   acquire a Commercial License from AVSystem. Commercial Licenses are issued on\n   a per-SKU (Vendor Device Model) basis and may include:\n   - Free-of-charge usage for up to 10,000 devices per SKU;\n   - Access to premium features (subject to additional fees);\n   - Technical support packages (subject to additional fees);\n   - Additional licensing packages (subject to additional fees);\n\nTo register for a Commercial License, visit:\nhttps://go.avsystem.com/anjay-registration\n\nFor commercial inquiries, contact:\nsales@avsystem.com\n\n5. GOVERNING LAW\n\n   This License shall be governed by and construed in accordance with the laws\n   of the Republic of Poland, without regard to its conflict of law provisions.\n   All disputes arising from or relating to this License shall be subject to the\n   exclusive jurisdiction of the competent courts located in Kraków, Poland.\n\n============================================\nEND OF AVSYSTEM ANJAY NON-COMMERCIAL LICENSE\n============================================\n"
  },
  {
    "path": "NOTICE",
    "content": "Anjay\nCopyright 2017-2026 AVSystem\n\nThis product includes software developed at AVSystem (www.avsystem.com).\n\nThis product bundles the AVSystem Commons Library\n(https://github.com/AVSystem/avs_commons), licensed under the terms of the\nApache License, version 2.0. See deps/avs_commons/NOTICE file for details.\n\nThis product's integration testing framework uses pybind11\n(https://github.com/pybind/pybind11), licensed under the terms of\na 3-clause BSD-style license. For details, see the\ntools/test-framework-tools/pymbedtls/src/pybind11/LICENSE file.\n"
  },
  {
    "path": "README.Windows.md",
    "content": "# Anjay on Windows\n\n## Limitations\n\nAnjay is currently not supported or regularly tested (which means that builds may break between releases) on Windows. However, there is preliminary support for building and running on Windows - with some limitations:\n\n- Any degree of testing was only performed on Windows 10; the code is theoretically compatible with Windows Vista and up\n- Building using MinGW from MSYS shell is currently the only supported toolchain\n- Building as a shared library (DLL) is not currently supported\n- IP Ping object is not available in the demo application\n- Testing frameworks are not supported\n- SMS binding (available as a commercial feature) is not supported\n\n## Prerequisites\n\n### Installing dependencies\n\n1. Install [MSYS2](http://www.msys2.org/)\n2. Install [Git for Windows](https://gitforwindows.org/)\n\n   **NOTE:** You can also install these using [Chocolatey](https://chocolatey.org/): `choco install git msys2` but please make sure to still follow the instructions to update MSYS2 after installing it.\n\n3. Open the appropriate MINGW shell (e.g., `C:\\msys64\\mingw32.exe` or `C:\\msys64\\mingw64.exe`, depending on whether you want to build 32- or 64-bit binaries) and install the compile-time dependencies:\n\n   ``` sh\n   pacman -Sy make ${MINGW_PACKAGE_PREFIX}-gcc ${MINGW_PACKAGE_PREFIX}-cmake ${MINGW_PACKAGE_PREFIX}-mbedtls\n   ```\n\n## Cloning the repository\n\nRun the following command in a directory of choice **in the Git for Windows bash environment**:\n\n``` sh\ngit clone --recurse-submodules https://github.com/AVSystem/Anjay.git\n```\n\n## Compiling the project\n\nRun the following commands **in the MINGW shell**, after navigating to the directory created using Git above:\n\n``` sh\ncmake -G\"MSYS Makefiles\" -DDTLS_BACKEND=\"mbedtls\" .\nmake\n```\n\n## Running the demo application\n\nThe demo application can be run from the MINGW shell just like on any other Unix system, e.g.:\n\n```\n./output/bin/demo --endpoint-name $(hostname) --server-uri coap://eu.iot.avsystem.cloud:5683\n```\n\nIf you want to run the resulting application outside of the MINGW shell, you will likely need to copy the DLL dependencies, such as:\n\n* `libgcc_s_dw2-1.dll`\n* `libmbedcrypto.dll`\n* `libmbedtls.dll`\n* `libmbedx509.dll`\n* `libwinpthread-1.dll`\n"
  },
  {
    "path": "README.md",
    "content": "# Anjay LwM2M library [<img align=\"right\" height=\"50px\" src=\"https://docs.avsystem.com/hubfs/Anjay_Docs/_images/avsystem_logo.png\">](http://www.avsystem.com/)\n\n[![Build Status](https://github.com/AVSystem/Anjay/actions/workflows/anjay-tests.yml/badge.svg?branch=master)](https://github.com/AVSystem/Anjay/actions)\n[![Coverity Status](https://scan.coverity.com/projects/13206/badge.svg)](https://scan.coverity.com/projects/avsystem-anjay)\n\n\n## Licensing Notice (from v3.10.0)\n\nStarting from **version 3.10.0**, the **Anjay LwM2M Client SDK** is distributed\nunder a new **License**.\n\n### Mandatory Registration for Commercial Use\n\nIf you intend to use Anjay in any **commercial context**, **you must fill in a registration form**\nto obtain a **free commercial license** for your product.\n\n**Register here**: [https://go.avsystem.com/anjay-registration](https://go.avsystem.com/anjay-registration)\n\n### Why is registration required?\n\nWe introduced registration to:\n\n- **Gain insight into usage patterns** – so we can prioritize support, features,\n  and enhancements relevant to real-world use cases.\n- **Engage with users** – allow us to notify you about important updates,\n  security advisories, or licensing changes.\n- **Offer tailored commercial plugins, professional services, and technical support**\n  to accelerate your product development.\n\nFor inquiries, please contact: [sales@avsystem.com](mailto:sales@avsystem.com)\n\n## What is Anjay?\n\nAnjay 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.\n\nThe project has been created and is actively maintained by [AVSystem](https://www.avsystem.com).\n\nQuick links:\n- [Full documentation](https://docs.avsystem.com/hubfs/Anjay_Docs/index.html)\n- [Tutorials](https://docs.avsystem.com/hubfs/Anjay_Docs/BasicClient.html)\n- [API docs](https://docs.avsystem.com/hubfs/Anjay_Docs/api/index.html)\n- [Changelog](CHANGELOG.md)\n\nTable of contents:\n<!-- toc -->\n\n* [Supported features](#supported-features)\n* [About OMA LwM2M](#about-oma-lwm2m)\n* [Embedded operating systems ports](#embedded-operating-systems-ports)\n* [Quickstart guide](#quickstart-guide)\n  * [Dependencies](#dependencies)\n    * [Ubuntu 20.04 LTS / Raspbian Buster or later](#ubuntu-2004-lts--raspbian-buster-or-later)\n    * [CentOS 7 or later](#centos-7-or-later)\n    * [macOS Sierra or later, with Homebrew](#macos-sierra-or-later-with-homebrew)\n  * [Running the demo client](#running-the-demo-client)\n  * [Detailed compilation guide](#detailed-compilation-guide)\n    * [Building using CMake](#building-using-cmake)\n  * [Raspberry Pi client](#raspberry-pi-client)\n  * [Use a Dockerfile](#use-a-dockerfile)\n* [License](#license)\n  * [Commercial support](#commercial-support)\n* [Feedback](#feedback)\n* [Contributing](#contributing)\n\n<!-- /toc -->\n\n## Supported features\n\nThis version includes full support for OMA LwM2M TS 1.1 and most of LwM2M 1.2 features.\nSome features, such as support for EST, SMS binding or HSM's are [available commercially](#commercial-support).\n\n- LwM2M Bootstrap Interface:\n    - Request\n    - Discover\n    - Read\n    - Write\n    - Delete\n    - Finish\n\n- LwM2M Client Registration Interface:\n    - Register\n    - Update\n    - De-register\n\n- LwM2M Device Management and Service Enablement Interface:\n    - Discover\n    - Read\n    - Read-Composite\n    - Write\n    - Write-Composite\n    - Execute\n    - Write-Attributes\n    - Create\n    - Delete\n    - Send\n\n- LwM2M Information Reporting Interface:\n    - Observe\n    - Observe-Composite\n    - Cancel Observation\n    - Cancel Observation-Composite\n    - Notify\n\n- LwM2M Security modes:\n    - DTLS with Certificates (if supported by backend TLS library)\n    - DTLS with PSK (if supported by backend TLS library)\n    - NoSec mode\n\n- Supported TLS backends:\n    - mbed TLS\n    - OpenSSL\n    - tinydtls\n\n- Supported platforms:\n    - any Unix-like operating system, such as Linux (including Android), macOS and BSD family\n    - Microsoft Windows (preliminary support, see [README.Windows.md](README.Windows.md) for details)\n    - any embedded platform (e.g. FreeRTOS, Zephyr) - check the list of [ready to use integrations](#embedded-operating-systems-ports)\n    - porting is possible for any other platform that has ISO C99 compiler available, see [Porting guide for non-POSIX platforms](https://docs.avsystem.com/hubfs/Anjay_Docs/PortingGuideForNonPOSIXPlatforms.html) for details\n\n- CoAP data formats:\n    - Plain Text\n    - Opaque\n    - CBOR\n    - TLV\n    - SenML JSON\n    - SenML CBOR\n    - LwM2M JSON (output only)\n\n- CoAP BLOCK transfers (for transferring data that does not fit in a single UDP packet):\n    - Block1 (sending / receiving requests)\n    - Block2 (sending responses)\n\n- Pre-implemented LwM2M Objects:\n    - Access Control\n    - Security\n    - Server\n    - Firmware Update\n    - IPSO single and three-axis sensor objects\n    - LwM2M Gateway\n\n- Stream-oriented persistence API\n\n- [LwM2M Gateway functionality](https://www.openmobilealliance.org/release/LwM2M_Gateway/V1_1_1-20240312-A/OMA-TS-LWM2M_Gateway-V1_1_1-20240312-A.pdf)\n\n## About OMA LwM2M\n\nOMA LwM2M is a remote device management and telemetry protocol designed to conserve network resources. It is especially suitable for constrained wireless devices, where network communication is a major factor affecting battery life. LwM2M features secure (DTLS-encrypted) methods of remote bootstrapping, configuration and notifications over UDP or SMS.\n\nFor quick and simple protocol learning, visit [LwM2M Crash Course](https://avsystem.com/crashcourse/lwm2m/)\n\nMore details about OMA LwM2M: [Brief introduction to LwM2M](https://docs.avsystem.com/hubfs/Anjay_Docs/LwM2M.html)\n\n## Embedded operating systems ports\n\nIf you want to use Anjay on Zephyr OS, FreeRTOS, Azure RTOS, or RPI Pico W check our demo\napplications available in other repositories:\n- [Anjay-zephyr-client](https://github.com/AVSystem/Anjay-zephyr-client) (uses [Anjay-zephyr](https://github.com/AVSystem/Anjay-zephyr) integration layer)\n- [Anjay-freertos-client](https://github.com/AVSystem/Anjay-freertos-client)\n- [Anjay-pico-client](https://github.com/AVSystem/Anjay-pico-client) (uses FreeRTOS Kernel)\n\nThere are also archived, not actively maintained Anjay demos and integrations:\n- [Anjay-esp32-client](https://github.com/AVSystem/Anjay-esp32-client) (uses [Anjay-esp-idf](https://github.com/AVSystem/Anjay-esp-idf) integration layer)\n- [Anjay-mbedos-client](https://github.com/AVSystem/Anjay-mbedos-client) (uses [Anjay-mbedos](https://github.com/AVSystem/Anjay-mbedos) integration layer)\n- [Anjay-stm32-azurertos-client](https://github.com/AVSystem/Anjay-stm32-azurertos-client)\n\n## Quickstart guide\n\n### Dependencies\n\n-   C compiler with C99 support,\n-   [avs\\_commons](https://github.com/AVSystem/avs_commons/) - included in the repository as a subproject,\n-   If DTLS support is enabled, at least one of:\n    -   [OpenSSL 1.1+](https://www.openssl.org/),\n    -   [mbed TLS 2.0+](https://www.trustedfirmware.org/projects/mbed-tls/),\n    -   [tinydtls 0.9+](https://projects.eclipse.org/projects/iot.tinydtls),\n-   Optional dependencies (required for tests):\n    -   [CMake 3.22+](https://cmake.org/) - non-mandatory, but preferred build system,\n    -   C++ compiler with C++11 support,\n    -   [Python 3.5+](https://www.python.org/),\n    -   [pybind11](https://github.com/pybind/pybind11) - included in the repository as a subproject,\n    -   [scan-build](https://clang-analyzer.llvm.org/scan-build.html) - for static analysis,\n-   Optional dependencies (required for building documentation - more information in \"Contributing\" section):\n    -   [Doxygen](http://www.doxygen.nl/),\n    -   [Sphinx](https://www.sphinx-doc.org/en/master/).\n\n#### Ubuntu 20.04 LTS / Raspbian Buster or later\n\n<!-- deps_install_begin -->\n``` sh\nsudo apt-get install git build-essential cmake libmbedtls-dev zlib1g-dev\n```\n<!-- deps_install_end -->\n\n#### CentOS 7 or later\n\n``` sh\n# EPEL is required for mbedtls-devel and cmake3\nsudo yum install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm\nsudo yum install -y which git make cmake3 mbedtls-devel gcc gcc-c++ zlib-devel\n```\n\n#### macOS Sierra or later, with [Homebrew](https://brew.sh/)\n\n``` sh\nbrew install cmake mbedtls openssl\n```\n\n### Running the demo client\n\nFor initial development and testing of LwM2M clients, we recommend using the [Coiote IoT Device Management](https://avsystem.com/coiote-iot-device-management-platform/) where you can use the basic LwM2M server functionality for free.\nAfter registering and logging in Coiote, you can [onboard your first device](https://eu.iot.avsystem.cloud/doc/user/getting-started/onboard-your-first-device/).\n\nWith a device added to the LwM2M Server, you can compile Anjay demo client and connect it to the platform by running:\n\n<!-- compile_instruction_begin -->\n``` sh\ngit clone https://github.com/AVSystem/Anjay.git \\\n    && cd Anjay \\\n    && git submodule update --init \\\n    && cmake . \\\n    && make -j \\\n    && ./output/bin/demo --endpoint-name $(hostname) --server-uri coap://eu.iot.avsystem.cloud:5683\n```\n<!-- compile_instruction_end -->\n\n**NOTE**: On some older systems like CentOS 7, you may need to use `cmake3` instead of `cmake`.\n\n**NOTE**: We strongly recommend replacing `$(hostname)` with some actual unique hostname. Please see the [documentation](https://docs.avsystem.com/hubfs/Anjay_Docs/LwM2M.html#clients-and-servers) for information on preferred endpoint name formats. Note that with the Coiote IoT Device Management platform, you will need to enter the endpoint name into the server UI first.\n\n### Detailed compilation guide\n\nFor a detailed guide on configuring and compiling the project (including cross-compiling), see [Compiling client applications](https://docs.avsystem.com/hubfs/Anjay_Docs/Compiling_client_applications.html).\n\nFirst, make sure all necessary submodules are downloaded and up-to-date:\n\n``` sh\ngit submodule update --init\n```\n\nAfter that, you have several options to compile the library.\n\n#### Building using CMake\n\nThe preferred way of building Anjay is to use CMake.\n\nBy default demo client compiles with DTLS enabled and uses `mbedtls` as a DTLS provider,\nbut you may choose other DTLS backends currently supported by setting `DTLS_BACKEND` in\na CMake invocation to one of the following DTLS backends: `openssl`, `mbedtls` or `tinydtls`:\n\n``` sh\ncmake . -DDTLS_BACKEND=\"mbedtls\" && make -j\n```\n\nOr, if a lack of security (not recommended) is what you need for some reason:\n\n```sh\ncmake . -DDTLS_BACKEND=\"\" && make -j\n```\n\nCompiled executables, including demo client, can be found in output/bin subdirectory.\n\nTo start the demo client:\n\n``` sh\n# uses plain CoAP\n./output/bin/demo --endpoint-name $(hostname) --server-uri coap://eu.iot.avsystem.cloud:5683\n\n# uses DTLS in PSK mode\n./output/bin/demo --endpoint-name $(hostname) --server-uri coaps://eu.iot.avsystem.cloud:5684 --security-mode psk --identity-as-string your_psk_identity --key-as-string your_psk_key\n```\n\n**NOTE**: When establishing a DTLS connection, the URI MUST use \"coaps://\". In NoSec mode (default), the URI MUST use \"<coap://>\".\n\n### Raspberry Pi client\n\nLwM2M Client for Raspberry Pi, with a feature allowing for implementing LwM2M Objects in Python, is available in [Svetovid-raspberry-client repository](https://github.com/AVSystem/Svetovid-raspberry-client).\n\n### Use a Dockerfile\n\nFor some cases you may find it comfortable to use Docker image. In this case, the only dependency is Docker, which you can install with your favorite package manager.\nIf Docker is already installed, you can clone the repo and build the Docker image:\n\n```\ngit clone --recurse-submodules https://github.com/AVSystem/Anjay.git\ncd Anjay\ndocker build --no-cache --tag anjay .\n```\n\nThen, you can launch the built image and run the demo client:\n\n```\ndocker run -it anjay\n./output/bin/demo -e $(hostname) -u coap://eu.iot.avsystem.cloud:5683\n```\n\n## License\n\nSee [LICENSE](LICENSE) file.\n\n### Commercial support\n\nAnjay LwM2M library comes with the option of [full commercial support, provided by AVSystem](https://avsystem.com/anjay-iot-sdk/).\n\nThe list of features available commercially is [available here](https://docs.avsystem.com/hubfs/Anjay_Docs/CommercialFeatures.html).\n\nIf you're interested in LwM2M Server, be sure to check out the [Coiote IoT Device Management](https://www.avsystem.com/products/coiote-iot-dm/) platform by AVSystem. It also includes the [interoperability test module](https://avsystem.com/coiote-iot-device-management-platform/lwm2m-interoperability-test/) that you can use to test your LwM2M client implementation. Our automated tests and testing scenarios enable you to quickly check how interoperable your device is with LwM2M.\n\n## Feedback\n\nGot a minute? Help improve **Anjay IoT SDK - [fill out this short form](https://docs.google.com/forms/d/e/1FAIpQLSegs_HTDEM-J3w0VeEvVdVTsjiB41YKxj_4w9dud0GQsUIQiA/viewform)**.\n\n## Contributing\n\nContributions are welcome! See our [contributing guide](CONTRIBUTING.rst).\n\n# Building documentation\n\nMake sure, both Doxygen and Sphinx are installed on your system, then type:\n\n``` sh\ncmake . && make doc\n```\n\nthe documentation will be available under `output/doc/doxygen` and `output/doc/sphinx`.\n"
  },
  {
    "path": "cmake/anjay-config.cmake.in",
    "content": "# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nset(ANJAY_VERSION \"@ANJAY_VERSION@\")\n\nget_filename_component(CURR_DIR \"${CMAKE_CURRENT_LIST_FILE}\" PATH)\n\nif(NOT DEFINED avs_commons_DIR)\n    set(avs_commons_DIR \"${CMAKE_CURRENT_LIST_DIR}/../avs_commons\")\nendif()\nfind_package(avs_commons REQUIRED COMPONENTS @AVS_COMMONS_COMPONENTS@)\n\nif(NOT DEFINED avs_coap_DIR)\n    set(avs_coap_DIR \"${CMAKE_CURRENT_LIST_DIR}/../avs_coap\")\nendif()\nfind_package(avs_coap REQUIRED)\n\ninclude(${CURR_DIR}/@PROJECT_NAME@-targets.cmake)\n\nget_filename_component(ANJAY_INCLUDE_DIRS \"${CURR_DIR}/../../@INCLUDE_INSTALL_DIR@\" ABSOLUTE)\nset(ANJAY_INCLUDE_DIRS \"${ANJAY_INCLUDE_DIRS}\" \"${AVS_COMMONS_INCLUDE_DIRS}\")\nset(ANJAY_LIBRARIES @PROJECT_NAME@)\nset(ANJAY_LIBRARIES_STATIC @PROJECT_NAME@)\n\nunset(CURR_DIR)\n"
  },
  {
    "path": "cmake/anjay-version.cmake.in",
    "content": "# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nset(PACKAGE_VERSION \"@ANJAY_VERSION@\")\n\nif (${PACKAGE_VERSION} VERSION_LESS ${PACKAGE_FIND_VERSION})\n    set(PACKAGE_VERSION_COMPATIBLE FALSE)\nelse()\n    set(PACKAGE_VERSION_COMPATIBLE TRUE)\n    if (${PACKAGE_FIND_VERSION} STREQUAL ${PACKAGE_VERSION})\n        set(PACKAGE_VERSION_EXACT TRUE)\n    endif()\nendif()\n"
  },
  {
    "path": "cmake/requirePython3venv.cmake",
    "content": "# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\ninclude_guard(GLOBAL)\nset(Python_FIND_FRAMEWORK LAST)\nset(Python3_FIND_VIRTUALENV ONLY)\nfind_package(Python3 3.8 REQUIRED COMPONENTS Interpreter)\n\nexecute_process(\n    COMMAND \"${Python3_EXECUTABLE}\" -c \"import sys; print(sys.base_prefix != sys.prefix)\"\n    OUTPUT_VARIABLE _is_venv\n    OUTPUT_STRIP_TRAILING_WHITESPACE)\n\nif(NOT _is_venv STREQUAL \"True\")\n    message(FATAL_ERROR\n    \"Python3_EXECUTABLE=${Python3_EXECUTABLE} is not from a virtualenv.\\n\"\n    \"Activate your .venv or pass -DPython3_EXECUTABLE=/path/to/.venv/bin/python\")\nendif()\n\nfunction(get_venv_check_command OUT_CMD)\n    set(PYTHON_SCRIPT \n        \"import sys; \\\n        in_venv = (sys.prefix != sys.base_prefix); \\\n        print(f'Venv check: {sys.prefix}') if in_venv else print('\\\\nFATAL: Run make command inside python venv!\\\\n'); \\\n        sys.exit(not in_venv)\"\n    )\n\n    # python3 is called instead of Python3_EXECUTABLE to allow entering venv after calling cmake\n    set(${OUT_CMD} python3 -c \"${PYTHON_SCRIPT}\" PARENT_SCOPE)\nendfunction()\n"
  },
  {
    "path": "cmake/toolchain/afl-gcc.cmake",
    "content": "# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nset(AFL_FUZZER_DIR \"\" CACHE STRING \"AFL fuzzer binary directory\")\nif(AFL_FUZZER_DIR)\n    set(CMAKE_C_COMPILER \"${AFL_FUZZER_DIR}/afl-gcc\")\nelse()\n    set(CMAKE_C_COMPILER afl-gcc)\nendif()\n"
  },
  {
    "path": "demo/CMakeLists.txt",
    "content": "# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\ncmake_minimum_required(VERSION 3.16)\nproject(lwm2m_demo C)\n\ninclude(CMakeDependentOption)\n\noption(WITH_DEMO_USE_STANDALONE_OBJECTS \"Use standalone versions of built-in objects in demo\" OFF)\ncmake_dependent_option(WITH_DEMO_TRAFFIC_INTERCEPTOR \"Turn on traffic interceptor usage in demo\" ON WITH_TRAFFIC_INTERCEPTOR OFF)\n\nset(SOURCES\n    demo.c\n    demo_args.c\n    demo_cmds.c\n    demo_utils.c\n    demo_time.c\n    objects/apn_conn_profile.c\n    objects/binary_app_data_container.c\n    objects/cell_connectivity.c\n    objects/conn_monitoring.c\n    objects/conn_statistics.c\n    objects/device.c\n    objects/download_diagnostics.c\n    objects/event_log.c\n    objects/ext_dev_info.c\n    objects/geopoints.c\n    objects/ipso_objects.c\n    objects/location.c\n    objects/portfolio.c\n    objects/test.c)\n\nif(WITH_DEMO_USE_STANDALONE_OBJECTS)\n    file(GLOB STANDALONE_SOURCES ../standalone/*/*.c)\n    set(SOURCES ${SOURCES} ${STANDALONE_SOURCES})\nendif()\n\nif (${ANJAY_WITH_MODULE_FW_UPDATE})\n    set(SOURCES ${SOURCES} firmware_update.c)\nendif()\n\n\nif (${ANJAY_WITH_MODULE_ADVANCED_FW_UPDATE})\n    set(SOURCES ${SOURCES}\n                advanced_firmware_update.c\n                advanced_firmware_update_app.c\n                advanced_firmware_update_addimg.c)\n\nendif()\n\nif (${ANJAY_WITH_MODULE_SW_MGMT})\n    set(SOURCES ${SOURCES} software_mgmt.c)\nendif()\n\nif (${ANJAY_WITH_LWM2M_GATEWAY})\n    set(SOURCES ${SOURCES} lwm2m_gateway.c\n                           objects/gateway_end_devices/temperature_object.c\n                           objects/gateway_end_devices/push_button_object.c\n                           objects/gateway_end_devices/binary_app_data_container.c\n                           )\nendif()\n\nif(${WITH_DEMO_TRAFFIC_INTERCEPTOR} AND NOT WIN32)\n    set(SOURCES ${SOURCES} net_traffic_interceptor.c)\nendif()\n\nif(NOT WIN32)\n    set(SOURCES ${SOURCES} objects/ip_ping.c)\nendif()\n\nset(HEADERS\n    demo.h\n    demo_args.h\n    demo_cmds.h\n    demo_utils.h\n    objects.h)\n\nif(WITH_DEMO_USE_STANDALONE_OBJECTS)\n    file(GLOB STANDALONE_HEADERS ../standalone/*/*.h)\n    set(HEADERS ${HEADERS} ${STANDALONE_HEADERS})\nendif()\n\nif (${ANJAY_WITH_MODULE_FW_UPDATE})\n    set(HEADERS ${HEADERS} firmware_update.h)\nendif()\n\nif (${ANJAY_WITH_MODULE_ADVANCED_FW_UPDATE})\n    set(HEADERS ${HEADERS} advanced_firmware_update.h)\nendif()\n\nif (${ANJAY_WITH_MODULE_SW_MGMT})\n    set(HEADERS ${HEADERS} software_mgmt.h)\nendif()\n\nset(ALL_SOURCES ${SOURCES} ${HEADERS})\n\nif(NOT TARGET anjay)\n    find_package(anjay REQUIRED HINTS \"${CMAKE_CURRENT_SOURCE_DIR}/..\")\nendif()\n\nfind_package(Threads REQUIRED)\n\nadd_executable(demo ${ALL_SOURCES})\ntarget_link_libraries(demo PRIVATE anjay m ${CMAKE_THREAD_LIBS_INIT})\n\nif(WITH_DEMO_USE_STANDALONE_OBJECTS)\n    target_compile_definitions(demo PRIVATE WITH_DEMO_USE_STANDALONE_OBJECTS)\nendif()\nif(WITH_DEMO_TRAFFIC_INTERCEPTOR)\n    target_compile_definitions(demo PRIVATE WITH_DEMO_TRAFFIC_INTERCEPTOR)\nendif()\n\n\n### FindPython3\nif(NOT TARGET Python3::Interpreter)\n    include(../cmake/requirePython3venv.cmake)\nendif()\n\nadd_custom_target(demo_firmware\n                  COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/../tests/integration/framework/framework/create_package.py\n                          -i ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/demo\n                          -o ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/demo.fw-pkg\n                  DEPENDS demo)\n"
  },
  {
    "path": "demo/advanced_firmware_update.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include \"advanced_firmware_update.h\"\n#include <avsystem/commons/avs_stream_file.h>\n#include <errno.h>\n#include <stdio.h>\n\n#include <sys/stat.h>\n#include <unistd.h>\n\n#define HEADER_VER_AFU_SINGLE 3\n#define HEADER_VER_AFU_MULTI 4\n\ntypedef struct {\n    states_results_paths_t states_results_paths;\n    char *download_file;\n    anjay_advanced_fw_update_severity_t severity;\n    avs_time_real_t last_state_change_time;\n    avs_time_real_t update_deadline;\n    char current_ver[IMG_VER_STR_MAX_LEN + 1];\n} advanced_firmware_update_persistence_file_data_t;\n\ntypedef struct {\n    anjay_t *anjay;\n    anjay_iid_t iid;\n    anjay_advanced_fw_update_state_t delayed_state;\n    anjay_advanced_fw_update_result_t delayed_result;\n} set_delayed_advanced_fw_update_result_args_t;\n\nstatic void set_delayed_fw_update_result(avs_sched_t *sched, const void *arg) {\n    (void) sched;\n    const set_delayed_advanced_fw_update_result_args_t *args =\n            (const set_delayed_advanced_fw_update_result_args_t *) arg;\n\n    anjay_advanced_fw_update_set_state_and_result(\n            args->anjay, args->iid, args->delayed_state, args->delayed_result);\n}\n\nstatic void fix_fw_meta_endianness(advanced_fw_metadata_t *meta) {\n    meta->header_ver = avs_convert_be16(meta->header_ver);\n    meta->force_error_case = avs_convert_be16(meta->force_error_case);\n    meta->crc = avs_convert_be32(meta->crc);\n}\n\nstatic int read_fw_meta_from_file(FILE *f,\n                                  advanced_fw_metadata_t *out_metadata,\n                                  uint32_t *out_metadata_len) {\n    advanced_fw_metadata_t m;\n    memset(&m, 0, sizeof(m));\n\n    if (fread(m.magic, sizeof(m.magic), 1, f) != 1\n            || fread(&m.header_ver, sizeof(m.header_ver), 1, f) != 1\n            || fread(&m.force_error_case, sizeof(m.force_error_case), 1, f) != 1\n            || fread(&m.crc, sizeof(m.crc), 1, f) != 1\n            || fread(m.linked, sizeof(m.linked), 1, f) != 1\n            || fread(&m.pkg_ver_len, sizeof(m.pkg_ver_len), 1, f) != 1) {\n        demo_log(ERROR, \"could not read firmware metadata\");\n        return -1;\n    }\n\n    if (m.pkg_ver_len > IMG_VER_STR_MAX_LEN || m.pkg_ver_len == 0) {\n        demo_log(ERROR, \"Wrong pkg version len\");\n        return ANJAY_ADVANCED_FW_UPDATE_ERR_UNSUPPORTED_PACKAGE_TYPE;\n    }\n\n    if (fread(m.pkg_ver, m.pkg_ver_len, 1, f) != 1) {\n        demo_log(ERROR, \"could not read firmware metadata\");\n        return -1;\n    }\n\n    fix_fw_meta_endianness(&m);\n    *out_metadata = m;\n    *out_metadata_len = sizeof(m.magic) + sizeof(m.header_ver)\n                        + sizeof(m.force_error_case) + sizeof(m.crc)\n                        + sizeof(m.linked) + sizeof(m.pkg_ver_len)\n                        + m.pkg_ver_len;\n    return 0;\n}\n\nstatic int\nhandle_multipackage(FILE *f,\n                    advanced_fw_multipkg_metadata_t *out_multiple_metadata) {\n    advanced_fw_multipkg_metadata_t mm;\n    memset(&mm, 0, sizeof(mm));\n\n    if (fread(mm.magic, sizeof(mm.magic), 1, f) != 1\n            || fread(&mm.header_ver, sizeof(mm.header_ver), 1, f) != 1) {\n        demo_log(ERROR, \"could not read firmware metadata\");\n        return -1;\n    }\n\n    mm.header_ver = avs_convert_be16(mm.header_ver);\n    if (mm.header_ver == HEADER_VER_AFU_MULTI\n            && !memcmp(mm.magic, \"MULTIPKG\", sizeof(mm.magic))) {\n        demo_log(INFO, \"Received multi package firmware\");\n        if (fread(&mm.packages_count, sizeof(mm.packages_count), 1, f) != 1) {\n            demo_log(ERROR, \"could not read firmware metadata\");\n            return -1;\n        }\n        mm.packages_count = avs_convert_be16(mm.packages_count);\n        if (mm.packages_count > FW_UPDATE_IID_IMAGE_SLOTS) {\n            demo_log(ERROR,\n                     \"Received packages_count %u is more than\"\n                     \" available slots\",\n                     mm.packages_count);\n            return -1;\n        }\n        for (uint16_t i = 0; i < mm.packages_count; ++i) {\n            if (fread(&mm.package_len[i], sizeof(mm.package_len[i]), 1, f)\n                    != 1) {\n                demo_log(ERROR, \"could not read firmware metadata\");\n                return -1;\n            }\n            mm.package_len[i] = avs_convert_be32(mm.package_len[i]);\n            if (mm.package_len[i] == 0) {\n                demo_log(\n                        ERROR,\n                        \"Zero-length packages within multipackage not allowed\");\n                return -1;\n            }\n        }\n        demo_log(INFO, \"Multi meta: {header version: %u, packages_count: %u}\",\n                 mm.header_ver, mm.packages_count);\n        *out_multiple_metadata = mm;\n    } else {\n        /* It is not multipackage, move stream to the beginning to easily handle\n         * like standard package */\n        if (fseek(f, 0L, SEEK_SET)) {\n            demo_log(ERROR, \"Could not seek in the multipackage file\");\n            return -1;\n        }\n    }\n    return 0;\n}\n\nstatic int copy_file_contents_by_bytes(FILE *dst, FILE *src, uint32_t len) {\n    char buf[4096];\n    uint32_t to_read = 0;\n    while (len) {\n        to_read = len > sizeof(buf) ? sizeof(buf) : len;\n        size_t bytes_read = fread(buf, 1, to_read, src);\n        if (bytes_read != to_read) {\n            return -1;\n        }\n\n        if (fwrite(buf, 1, bytes_read, dst) != bytes_read) {\n            return -1;\n        }\n        len -= (uint32_t) bytes_read;\n    }\n    return 0;\n}\n\nstatic int unpack_fw_to_file(FILE *fw,\n                             uint32_t fw_len,\n                             const char *target_path,\n                             advanced_fw_metadata_t *out_metadata) {\n    int result = -1;\n    FILE *tmp = NULL;\n    uint32_t metadata_len = 0;\n\n    tmp = fopen(target_path, \"wb\");\n    if (!tmp) {\n        demo_log(ERROR, \"could not open file: %s\", target_path);\n        goto cleanup;\n    }\n\n    result = read_fw_meta_from_file(fw, out_metadata, &metadata_len);\n    if (result) {\n        demo_log(ERROR, \"could not read metadata\");\n        goto cleanup;\n    }\n    if (fw_len) {\n        result = copy_file_contents_by_bytes(tmp, fw, fw_len - metadata_len);\n    } else {\n        result = copy_file_contents(tmp, fw);\n    }\n    if (result) {\n        demo_log(ERROR, \"could not copy firmware\");\n        goto cleanup;\n    }\n\n    result = 0;\n\ncleanup:\n    if (tmp) {\n        fclose(tmp);\n    }\n    return result;\n}\n\nstatic void maybe_delete_firmware_file(advanced_fw_update_logic_t *fw) {\n    if (fw->next_target_path) {\n        unlink(fw->next_target_path);\n        demo_log(INFO, \"Deleted %s\", fw->next_target_path);\n        avs_free(fw->next_target_path);\n        fw->next_target_path = NULL;\n    }\n}\n\nconst char *const MAGICS[] = {\n    [FW_UPDATE_IID_APP] = \"AJAY_APP\",\n    [FW_UPDATE_IID_TEE] = \"AJAY_TEE\",\n    [FW_UPDATE_IID_BOOT] = \"AJAYBOOT\",\n    [FW_UPDATE_IID_MODEM] = \"AJAYMODE\"\n};\n\nstatic int find_instance_magic_based(advanced_fw_metadata_t *meta,\n                                     anjay_iid_t *iid) {\n    for (size_t i = 0; i < AVS_ARRAY_SIZE(MAGICS); ++i) {\n        if (!memcmp(meta->magic, MAGICS[i], sizeof(meta->magic))) {\n            *iid = (anjay_iid_t) i;\n            return 0;\n        }\n    }\n    return -1;\n}\n\nstatic int unpack_firmware(FILE *firmware,\n                           uint32_t len,\n                           unpacked_imgs_info_t *unpacked_info) {\n    char *tmp_path = generate_random_target_filepath();\n    if (!tmp_path) {\n        return -1;\n    }\n\n    advanced_fw_metadata_t metadata;\n    memset(&metadata, 0x00, sizeof(metadata));\n    int result = unpack_fw_to_file(firmware, len, tmp_path, &metadata);\n    if (result) {\n        goto cleanup;\n    }\n    anjay_iid_t iid;\n    result = find_instance_magic_based(&metadata, &iid);\n    if (!result) {\n        unpacked_info[iid].path = tmp_path;\n        unpacked_info[iid].meta = metadata;\n    }\n\ncleanup:\n    if (result) {\n        unlink(tmp_path);\n        avs_free(tmp_path);\n    }\n    return result;\n}\n\nstatic bool is_state_downloaded(advanced_fw_update_logic_t *fw) {\n    assert(fw->anjay);\n    anjay_advanced_fw_update_state_t state =\n            ANJAY_ADVANCED_FW_UPDATE_STATE_IDLE;\n    anjay_advanced_fw_update_get_state(fw->anjay, fw->iid, &state);\n    return state == ANJAY_ADVANCED_FW_UPDATE_STATE_DOWNLOADED;\n}\n\nstatic int unpack_firmware_in_place(anjay_iid_t iid,\n                                    advanced_fw_update_logic_t *fw_table,\n                                    anjay_iid_t *downloaded_iids,\n                                    int *downloaded_iids_count) {\n    advanced_fw_update_logic_t *fw = &fw_table[iid];\n\n    *downloaded_iids_count = 0;\n    advanced_fw_multipkg_metadata_t multi_metadata;\n    memset(&multi_metadata, 0x00, sizeof(multi_metadata));\n    FILE *firmware = NULL;\n    unpacked_imgs_info_t unpacked_info[FW_UPDATE_IID_IMAGE_SLOTS];\n    memset(&unpacked_info, 0x00, sizeof(unpacked_info));\n    uint16_t to_unpack = 0;\n    firmware = fopen(fw->next_target_path, \"rb\");\n    if (!firmware) {\n        demo_log(ERROR, \"could not open file: %s\", fw->next_target_path);\n        return -1;\n    }\n    int result = handle_multipackage(firmware, &multi_metadata);\n    if (result) {\n        goto cleanup;\n    }\n\n    /* packages_count == 0 means that it is not multipackage, but there is\n     * still one 'normal' package to unpack */\n    to_unpack = AVS_MAX(1, multi_metadata.packages_count);\n\n    for (int i = 0; i < to_unpack; ++i) {\n        if ((result = unpack_firmware(firmware, multi_metadata.package_len[i],\n                                      unpacked_info))) {\n            goto cleanup;\n        }\n    }\n\n    for (anjay_iid_t i = 0; i < FW_UPDATE_IID_IMAGE_SLOTS; ++i) {\n        if (unpacked_info[i].path && is_state_downloaded(&fw_table[i])) {\n            demo_log(ERROR,\n                     \"Failure. Multipackage contains package for \"\n                     \"instance /\" AVS_QUOTE_MACRO(\n                             ANJAY_ADVANCED_FW_UPDATE_OID) \"/%d which is \"\n                                                           \"already in \"\n                                                           \"DOWNLOADED state.\",\n                     i);\n            anjay_advanced_fw_update_set_conflicting_instances(fw->anjay,\n                                                               fw->iid, &i, 1);\n            result = ANJAY_ADVANCED_FW_UPDATE_ERR_CONFLICTING_STATE;\n            goto cleanup;\n        }\n    }\n\n    for (anjay_iid_t i = 0; i < FW_UPDATE_IID_IMAGE_SLOTS; ++i) {\n        if (unpacked_info[i].path) {\n            fw_update_common_maybe_create_firmware_file(&fw_table[i]);\n            if ((result = rename(unpacked_info[i].path,\n                                 fw_table[i].next_target_path))\n                    == -1) {\n                demo_log(ERROR, \"could not rename %s to %s: %s\",\n                         unpacked_info[i].path, fw_table[i].next_target_path,\n                         strerror(errno));\n                goto cleanup;\n            }\n            if ((result = chmod(fw_table[i].next_target_path, 0700)) == -1) {\n                demo_log(ERROR, \"could not set permissions for %s: %s\",\n                         fw_table[i].next_target_path, strerror(errno));\n                goto cleanup;\n            }\n            fw_table[i].metadata = unpacked_info[i].meta;\n            downloaded_iids[*downloaded_iids_count] = i;\n            (*downloaded_iids_count)++;\n        }\n    }\n\ncleanup:\n    for (int i = 0; i < FW_UPDATE_IID_IMAGE_SLOTS; ++i) {\n        if (unpacked_info[i].path) {\n            unlink(unpacked_info[i].path);\n            avs_free(unpacked_info[i].path);\n        }\n    }\n\n    if (firmware) {\n        fclose(firmware);\n    }\n    if (result) {\n        maybe_delete_firmware_file(fw);\n    }\n    return result;\n}\n\nstatic bool fw_magic_valid(const advanced_fw_metadata_t *meta,\n                           anjay_iid_t iid) {\n    switch (iid) {\n    case FW_UPDATE_IID_APP:\n    case FW_UPDATE_IID_TEE:\n    case FW_UPDATE_IID_BOOT:\n    case FW_UPDATE_IID_MODEM:\n        if (!memcmp(meta->magic, MAGICS[iid], sizeof(meta->magic))) {\n            return true;\n        }\n        break;\n    default:\n        break;\n    }\n    demo_log(ERROR, \"invalid firmware magic\");\n    return false;\n}\n\nstatic bool fw_header_version_valid(const advanced_fw_metadata_t *meta) {\n    if (meta->header_ver != HEADER_VER_AFU_SINGLE) {\n        demo_log(ERROR, \"wrong header version\");\n        return false;\n    }\n\n    return true;\n}\n\nstatic int validate_firmware(advanced_fw_update_logic_t *fw) {\n    if (!fw_magic_valid(&fw->metadata, fw->iid)\n            || !fw_header_version_valid(&fw->metadata)) {\n        return ANJAY_ADVANCED_FW_UPDATE_ERR_UNSUPPORTED_PACKAGE_TYPE;\n    }\n\n    uint32_t actual_crc;\n    int result = calc_file_crc32(fw->next_target_path, &actual_crc);\n\n    if (result) {\n        demo_log(WARNING, \"unable to check firmware CRC\");\n        return ANJAY_ADVANCED_FW_UPDATE_ERR_INTEGRITY_FAILURE;\n    }\n\n    if (fw->metadata.crc != actual_crc) {\n        demo_log(WARNING, \"CRC mismatch: expected %08x != %08x actual\",\n                 fw->metadata.crc, actual_crc);\n        return ANJAY_ADVANCED_FW_UPDATE_ERR_INTEGRITY_FAILURE;\n    }\n\n    switch (fw->metadata.force_error_case) {\n    case FORCE_ERROR_OUT_OF_MEMORY:\n        return ANJAY_ADVANCED_FW_UPDATE_ERR_OUT_OF_MEMORY;\n    default:\n        break;\n    }\n\n    return 0;\n}\n\nstatic int process_linked(advanced_fw_update_logic_t *fw) {\n    anjay_iid_t linked[METADATA_LINKED_SLOTS];\n    size_t linked_count = 0;\n    memset(linked, 0xFF, sizeof(linked));\n    for (int i = 0; i < METADATA_LINKED_SLOTS; ++i) {\n        // Below condition compares metadata.linked[] with\n        // METADATA_LINKED_SLOTS, because max handled iid is derived from\n        // available slots.\n        if (fw->metadata.linked[i] < METADATA_LINKED_SLOTS) {\n            linked[linked_count++] = fw->metadata.linked[i];\n        } else if (fw->metadata.linked[i] != 0xFF) {\n            demo_log(WARNING, \"Unexpected linked instance iid\");\n        }\n    }\n    return anjay_advanced_fw_update_set_linked_instances(fw->anjay, fw->iid,\n                                                         linked, linked_count);\n}\n\nstatic int preprocess_firmware(anjay_iid_t iid,\n                               advanced_fw_update_logic_t *fw_table) {\n    anjay_iid_t downloaded_iids[FW_UPDATE_IID_IMAGE_SLOTS];\n    int downloaded_iids_count = 0;\n    int result = unpack_firmware_in_place(iid, fw_table, downloaded_iids,\n                                          &downloaded_iids_count);\n    if (result) {\n        return result;\n    }\n\n    result = -1;\n    for (int i = 0; i < downloaded_iids_count; ++i) {\n        advanced_fw_update_logic_t *fw = &fw_table[downloaded_iids[i]];\n        if ((result = validate_firmware(fw))) {\n            break;\n        }\n        if ((result = process_linked(fw))) {\n            break;\n        }\n        demo_log(INFO,\n                 \"firmware for instance /\" AVS_QUOTE_MACRO(\n                         ANJAY_ADVANCED_FW_UPDATE_OID) \"/%u downloaded \"\n                                                       \"successfully\",\n                 downloaded_iids[i]);\n        if ((result = anjay_advanced_fw_update_set_state_and_result(\n                     fw->anjay, fw->iid,\n                     ANJAY_ADVANCED_FW_UPDATE_STATE_DOWNLOADED,\n                     ANJAY_ADVANCED_FW_UPDATE_RESULT_INITIAL))) {\n            break;\n        }\n    }\n    return result;\n}\n\n#if defined(AVS_COMMONS_WITH_AVS_PERSISTENCE) \\\n        && defined(AVS_COMMONS_STREAM_WITH_FILE)\nstatic advanced_firmware_update_persistence_file_data_t\nadvanced_firmware_update_read_persistence_file(const char *path) {\n    advanced_firmware_update_persistence_file_data_t data;\n    memset(&data, 0, sizeof(data));\n    avs_stream_t *stream = NULL;\n    int8_t results8[FW_UPDATE_IID_IMAGE_SLOTS];\n    memset(results8, 0x00, sizeof(results8));\n    int8_t states8[FW_UPDATE_IID_IMAGE_SLOTS];\n    memset(states8, 0x00, sizeof(states8));\n    for (size_t i = 0; i < AVS_ARRAY_SIZE(results8); ++i) {\n        results8[i] = (int8_t) ANJAY_ADVANCED_FW_UPDATE_RESULT_INITIAL;\n        states8[i] = (int8_t) ANJAY_ADVANCED_FW_UPDATE_STATE_IDLE;\n    }\n    if ((stream = avs_stream_file_create(path, AVS_STREAM_FILE_READ))) {\n        // invalid or empty but existing file still signifies success but only\n        // for APP instance\n        results8[FW_UPDATE_IID_APP] =\n                (int8_t) ANJAY_ADVANCED_FW_UPDATE_RESULT_SUCCESS;\n    }\n    avs_persistence_context_t ctx =\n            avs_persistence_restore_context_create(stream);\n    uint8_t severity8 = (uint8_t) ANJAY_ADVANCED_FW_UPDATE_SEVERITY_MANDATORY;\n    int64_t last_state_change_timestamp = 0;\n    int64_t update_timestamp = 0;\n    char *current_ver = NULL;\n    if (!stream\n            || avs_is_err(avs_persistence_bytes(&ctx, (uint8_t *) &results8,\n                                                sizeof(results8)))\n            || avs_is_err(avs_persistence_bytes(&ctx, (uint8_t *) &states8,\n                                                sizeof(states8)))\n            || avs_is_err(avs_persistence_bytes(&ctx, &severity8, 1))\n            || avs_is_err(\n                       avs_persistence_i64(&ctx, &last_state_change_timestamp))\n            || avs_is_err(avs_persistence_i64(&ctx, &update_timestamp))\n            || avs_is_err(avs_persistence_string(&ctx, &current_ver))) {\n        demo_log(WARNING,\n                 \"Invalid data in the firmware state persistence file\");\n        memset(&data, 0, sizeof(data));\n    } else {\n        for (anjay_iid_t iid = FW_UPDATE_IID_APP;\n             iid < FW_UPDATE_IID_IMAGE_SLOTS;\n             ++iid) {\n            if (avs_is_err(avs_persistence_string(\n                        &ctx,\n                        &data.states_results_paths.next_target_paths[iid]))) {\n                for (anjay_iid_t i = FW_UPDATE_IID_APP;\n                     i < FW_UPDATE_IID_IMAGE_SLOTS;\n                     ++i) {\n                    avs_free(data.states_results_paths.next_target_paths[i]);\n                }\n                demo_log(WARNING,\n                         \"Invalid data in the firmware state persistence file\");\n                memset(&data, 0, sizeof(data));\n                break;\n            }\n        }\n    }\n    if (current_ver && strlen(current_ver) <= IMG_VER_STR_MAX_LEN) {\n        strcpy(data.current_ver, current_ver);\n    } else {\n        demo_log(WARNING, \"Invalid version string\");\n    }\n    avs_free(current_ver);\n    for (size_t i = 0; i < AVS_ARRAY_SIZE(results8); ++i) {\n        data.states_results_paths.inst_results[i] =\n                (anjay_advanced_fw_update_result_t) results8[i];\n        data.states_results_paths.inst_states[i] =\n                (anjay_advanced_fw_update_state_t) states8[i];\n    }\n    data.severity = (anjay_advanced_fw_update_severity_t) severity8;\n    data.last_state_change_time =\n            avs_time_real_from_scalar(last_state_change_timestamp, AVS_TIME_S);\n    data.update_deadline =\n            avs_time_real_from_scalar(update_timestamp, AVS_TIME_S);\n    if (stream) {\n        avs_stream_cleanup(&stream);\n    }\n    return data;\n}\n\nint advanced_firmware_update_write_persistence_file(\n        const char *path,\n        states_results_paths_t *states_results_paths,\n        anjay_advanced_fw_update_severity_t severity,\n        avs_time_real_t last_state_change_time,\n        avs_time_real_t update_deadline,\n        const char *current_ver) {\n    avs_stream_t *stream = avs_stream_file_create(path, AVS_STREAM_FILE_WRITE);\n    avs_persistence_context_t ctx =\n            avs_persistence_store_context_create(stream);\n    int8_t results8[FW_UPDATE_IID_IMAGE_SLOTS];\n    memset(results8, 0x00, sizeof(results8));\n    int8_t states8[FW_UPDATE_IID_IMAGE_SLOTS];\n    memset(states8, 0x00, sizeof(states8));\n    for (size_t i = 0; i < AVS_ARRAY_SIZE(results8); ++i) {\n        results8[i] = (int8_t) states_results_paths->inst_results[i];\n        states8[i] = (int8_t) states_results_paths->inst_states[i];\n    }\n    uint8_t severity8 = (uint8_t) severity;\n    int64_t last_state_change_timestamp = 0;\n    avs_time_real_to_scalar(&last_state_change_timestamp, AVS_TIME_S,\n                            last_state_change_time);\n    int64_t update_timestamp = 0;\n    avs_time_real_to_scalar(&update_timestamp, AVS_TIME_S, update_deadline);\n\n    int retval = 0;\n    if (!stream\n            || avs_is_err(avs_persistence_bytes(&ctx, (uint8_t *) &results8,\n                                                sizeof(results8)))\n            || avs_is_err(avs_persistence_bytes(&ctx, (uint8_t *) &states8,\n                                                sizeof(states8)))\n            || avs_is_err(avs_persistence_bytes(&ctx, &severity8, 1))\n            || avs_is_err(\n                       avs_persistence_i64(&ctx, &last_state_change_timestamp))\n            || avs_is_err(avs_persistence_i64(&ctx, &update_timestamp))\n            || avs_is_err(avs_persistence_string(\n                       &ctx, (char **) (intptr_t) &current_ver))) {\n        demo_log(ERROR, \"Could not write firmware state persistence file\");\n        retval = -1;\n    } else {\n        for (anjay_iid_t iid = FW_UPDATE_IID_APP;\n             iid < FW_UPDATE_IID_IMAGE_SLOTS;\n             ++iid) {\n            if (avs_is_err(avs_persistence_string(\n                        &ctx, &states_results_paths->next_target_paths[iid]))) {\n                demo_log(ERROR,\n                         \"Could not write firmware state persistence file\");\n                retval = -1;\n                break;\n            }\n        }\n    }\n    if (stream) {\n        avs_stream_cleanup(&stream);\n    }\n    if (retval) {\n        unlink(path);\n    }\n    return retval;\n}\n\nvoid advanced_firmware_update_delete_persistence_file(\n        const advanced_fw_update_logic_t *fw) {\n    if (fw->persistence_file) {\n        unlink(fw->persistence_file);\n    }\n}\n\n#else  // defined(AVS_COMMONS_WITH_AVS_PERSISTENCE) &&\n       // defined(AVS_COMMONS_STREAM_WITH_FILE)\nadvanced_firmware_update_persistence_file_data_t\nadvanced_firmware_update_read_persistence_file(const char *path) {\n    (void) path;\n    demo_log(WARNING, \"Persistence not compiled in\");\n    persistence_file_data_t retval;\n    memset(&retval, 0, sizeof(retval));\n    return retval;\n}\n\nint advanced_firmware_update_write_persistence_file(\n        const char *path,\n        anjay_advanced_fw_update_state_t state,\n        anjay_advanced_fw_update_state_t result) {\n    (void) path;\n    (void) state;\n    (void) result;\n    demo_log(WARNING, \"Persistence not compiled in\");\n    return 0;\n}\n\nvoid advanced_firmware_update_delete_persistence_file(\n        const advanced_fw_update_logic_t *fw) {\n    (void) fw;\n    demo_log(WARNING, \"Persistence not compiled in\");\n}\n#endif // defined(AVS_COMMONS_WITH_AVS_PERSISTENCE) &&\n       // defined(AVS_COMMONS_STREAM_WITH_FILE)\n\nstatic void fw_reset(advanced_fw_update_logic_t *fw) {\n    if (fw->stream) {\n        fclose(fw->stream);\n        fw->stream = NULL;\n    }\n    maybe_delete_firmware_file(fw);\n    advanced_firmware_update_delete_persistence_file(fw);\n}\n\nint advanced_firmware_update_read_states_results_paths(\n        advanced_fw_update_logic_t *fw_table,\n        states_results_paths_t *out_states_results_paths) {\n    for (int i = 0; i < FW_UPDATE_IID_IMAGE_SLOTS; ++i) {\n        if (anjay_advanced_fw_update_get_state(\n                    fw_table[i].anjay,\n                    fw_table[i].iid,\n                    &out_states_results_paths->inst_states[i])) {\n            return -1;\n        }\n        if (anjay_advanced_fw_update_get_result(\n                    fw_table[i].anjay,\n                    fw_table[i].iid,\n                    &out_states_results_paths->inst_results[i])) {\n            return -1;\n        }\n        out_states_results_paths->next_target_paths[i] =\n                fw_table[i].next_target_path;\n    }\n    return 0;\n}\n\nint fw_update_common_open(anjay_iid_t iid, void *fw_) {\n    advanced_fw_update_logic_t *fw_table = (advanced_fw_update_logic_t *) fw_;\n    advanced_fw_update_logic_t *fw = &fw_table[iid];\n    assert(!fw->stream);\n\n    if (fw_update_common_maybe_create_firmware_file(fw)) {\n        return -1;\n    }\n    if (!(fw->stream = fopen(fw->next_target_path, \"wb\"))) {\n        return -1;\n    }\n\n    states_results_paths_t states_results_paths;\n    if (advanced_firmware_update_read_states_results_paths(\n                fw_table, &states_results_paths)) {\n        return -1;\n    }\n    states_results_paths.inst_states[FW_UPDATE_IID_APP] =\n            ANJAY_ADVANCED_FW_UPDATE_STATE_DOWNLOADING;\n    states_results_paths.inst_results[FW_UPDATE_IID_APP] =\n            ANJAY_ADVANCED_FW_UPDATE_RESULT_INITIAL;\n    if (fw->persistence_file\n            && advanced_firmware_update_write_persistence_file(\n                       fw->persistence_file, &states_results_paths,\n                       anjay_advanced_fw_update_get_severity(fw->anjay,\n                                                             fw->iid),\n                       anjay_advanced_fw_update_get_last_state_change_time(\n                               fw->anjay, fw->iid),\n                       anjay_advanced_fw_update_get_deadline(fw->anjay,\n                                                             fw->iid),\n                       fw->current_ver)) {\n        fw_reset(fw);\n        return -1;\n    }\n    return 0;\n}\n\nint fw_update_common_write(anjay_iid_t iid,\n                           void *fw_,\n                           const void *data,\n                           size_t length) {\n    advanced_fw_update_logic_t *fw_table = (advanced_fw_update_logic_t *) fw_;\n    advanced_fw_update_logic_t *fw = &fw_table[iid];\n    if (!fw->stream) {\n        demo_log(ERROR, \"stream not open\");\n        return -1;\n    }\n    if (length\n            && (fwrite(data, length, 1, fw->stream) != 1\n                // Firmware update integration tests measure download\n                // progress by checking file size, so avoiding buffering\n                // is required.\n                || fflush(fw->stream) != 0)) {\n        demo_log(ERROR, \"fwrite or fflush failed: %s\", strerror(errno));\n        return ANJAY_ADVANCED_FW_UPDATE_ERR_NOT_ENOUGH_SPACE;\n    }\n    return 0;\n}\n\nstatic int stream_finish(anjay_iid_t iid, void *fw_) {\n    advanced_fw_update_logic_t *fw_table = (advanced_fw_update_logic_t *) fw_;\n    advanced_fw_update_logic_t *fw = &fw_table[iid];\n    if (fw->auto_suspend) {\n        anjay_advanced_fw_update_pull_suspend(fw->anjay);\n    }\n    if (!fw->stream) {\n        demo_log(ERROR, \"stream not open\");\n        return -1;\n    }\n    fclose(fw->stream);\n    fw->stream = NULL;\n\n    anjay_advanced_fw_update_state_t tee_state;\n    anjay_advanced_fw_update_result_t tee_result;\n    anjay_advanced_fw_update_get_state(fw_table[FW_UPDATE_IID_TEE].anjay,\n                                       FW_UPDATE_IID_TEE, &tee_state);\n    anjay_advanced_fw_update_get_result(fw_table[FW_UPDATE_IID_TEE].anjay,\n                                        FW_UPDATE_IID_TEE, &tee_result);\n\n    states_results_paths_t states_results_paths;\n    int result = advanced_firmware_update_read_states_results_paths(\n            fw_table, &states_results_paths);\n    if (result) {\n        return result;\n    }\n    states_results_paths.inst_states[FW_UPDATE_IID_APP] =\n            ANJAY_ADVANCED_FW_UPDATE_STATE_DOWNLOADED;\n    states_results_paths.inst_results[FW_UPDATE_IID_APP] =\n            ANJAY_ADVANCED_FW_UPDATE_RESULT_INITIAL;\n    if ((result = preprocess_firmware(iid, fw_table))\n            || (fw->persistence_file\n                && (result = advanced_firmware_update_write_persistence_file(\n                            fw->persistence_file, &states_results_paths,\n                            anjay_advanced_fw_update_get_severity(fw->anjay,\n                                                                  fw->iid),\n                            anjay_advanced_fw_update_get_last_state_change_time(\n                                    fw->anjay, fw->iid),\n                            anjay_advanced_fw_update_get_deadline(fw->anjay,\n                                                                  fw->iid),\n                            fw->current_ver)))) {\n        fw_reset(fw);\n    }\n    return result;\n}\n\nconst char *fw_update_common_get_current_version(anjay_iid_t iid, void *fw_) {\n    advanced_fw_update_logic_t *fw_table = (advanced_fw_update_logic_t *) fw_;\n    advanced_fw_update_logic_t *fw = &fw_table[iid];\n    return (const char *) fw->current_ver;\n}\n\nconst char *fw_update_common_get_pkg_version(anjay_iid_t iid, void *fw_) {\n    advanced_fw_update_logic_t *fw_table = (advanced_fw_update_logic_t *) fw_;\n    advanced_fw_update_logic_t *fw = &fw_table[iid];\n    return (const char *) fw->metadata.pkg_ver;\n}\n\nstatic int\nadd_conflicting(anjay_iid_t (*inout_conflicting)[FW_UPDATE_IID_IMAGE_SLOTS],\n                size_t *inout_conflicting_len,\n                anjay_iid_t add_conf) {\n    if (*inout_conflicting_len + 1 > FW_UPDATE_IID_IMAGE_SLOTS\n            || ((*inout_conflicting) == NULL && *inout_conflicting_len != 0)) {\n        return -1;\n    }\n\n    anjay_iid_t new_conflicting[FW_UPDATE_IID_IMAGE_SLOTS];\n    memset(new_conflicting, 0x00, sizeof(new_conflicting));\n    size_t position = 0;\n\n    for (position = 0; position < *inout_conflicting_len; ++position) {\n        if ((*inout_conflicting)[position] == add_conf) {\n            return 0;\n        } else if ((*inout_conflicting)[position] > add_conf) {\n            break;\n        }\n    }\n\n    for (size_t i = 0, j = 0; j < *inout_conflicting_len; ++i, ++j) {\n        if (i == position) {\n            i++;\n        }\n        new_conflicting[i] = (*inout_conflicting)[j];\n    }\n    new_conflicting[position] = add_conf;\n\n    for (size_t j = 0; j < *inout_conflicting_len + 1; ++j) {\n        (*inout_conflicting)[j] = new_conflicting[j];\n    }\n    *inout_conflicting_len = *inout_conflicting_len + 1;\n    return 0;\n}\n\nstatic void check_version_logic(\n        anjay_iid_t iid_in_check,\n        advanced_fw_update_logic_t *fw_table,\n        anjay_iid_t (*inout_conflicting_instances)[FW_UPDATE_IID_IMAGE_SLOTS],\n        size_t *inout_conflicting_instances_count) {\n    if (iid_in_check == FW_UPDATE_IID_APP) {\n        const char *app_pkg_ver = fw_update_common_get_pkg_version(\n                fw_table[FW_UPDATE_IID_APP].iid, fw_table);\n        const char *tee_cur_ver = fw_update_common_get_current_version(\n                fw_table[FW_UPDATE_IID_TEE].iid, fw_table);\n        /* Check major version, assuming that it is one digit len, first in ver\n         * string */\n        if (app_pkg_ver[0] > tee_cur_ver[0]) {\n            add_conflicting(inout_conflicting_instances,\n                            inout_conflicting_instances_count,\n                            fw_table[FW_UPDATE_IID_TEE].iid);\n        }\n    }\n}\n\nint fw_update_common_finish(anjay_iid_t iid, void *fw_) {\n    advanced_fw_update_logic_t *fw_table = (advanced_fw_update_logic_t *) fw_;\n    int result = -1;\n    result = stream_finish(iid, fw_);\n    if (!result) {\n        /* Below code checks two things:\n         * 1. Relationship between instances in DOWNLOADED state\n         *    and their linked instances with context of common_finish\n         * 2. Version logic which is logic specific for targeted platform\n         * Then it sets conflicting instances accordingly\n         * */\n        for (int i = 0; i < FW_UPDATE_IID_IMAGE_SLOTS; ++i) {\n            if (is_state_downloaded(&fw_table[i])) {\n                const anjay_iid_t *linked_instances;\n                size_t linked_instances_count = 0;\n                anjay_iid_t conflicting_instances[FW_UPDATE_IID_IMAGE_SLOTS];\n                size_t conflicting_instances_count = 0;\n                anjay_advanced_fw_update_get_linked_instances(\n                        fw_table[i].anjay, fw_table[i].iid, &linked_instances,\n                        &linked_instances_count);\n                for (size_t j = 0; j < linked_instances_count; ++j) {\n                    if (!is_state_downloaded(&fw_table[linked_instances[j]])) {\n                        conflicting_instances[conflicting_instances_count++] =\n                                linked_instances[j];\n                    }\n                }\n\n                check_version_logic(fw_table[i].iid,\n                                    fw_table,\n                                    &conflicting_instances,\n                                    &conflicting_instances_count);\n\n                anjay_advanced_fw_update_set_conflicting_instances(\n                        fw_table[i].anjay, fw_table[i].iid,\n                        conflicting_instances, conflicting_instances_count);\n            }\n        }\n    }\n    return result;\n}\n\nvoid fw_update_common_reset(anjay_iid_t iid, void *fw_) {\n    advanced_fw_update_logic_t *fw_table = (advanced_fw_update_logic_t *) fw_;\n    advanced_fw_update_logic_t *fw = &fw_table[iid];\n    if (fw->stream) {\n        fclose(fw->stream);\n        fw->stream = NULL;\n    }\n    maybe_delete_firmware_file(fw);\n    advanced_firmware_update_delete_persistence_file(fw);\n    anjay_advanced_fw_update_set_conflicting_instances(fw->anjay, fw->iid, NULL,\n                                                       0);\n    anjay_advanced_fw_update_set_linked_instances(fw->anjay, fw->iid, NULL, 0);\n    if (fw->update_job) {\n        avs_sched_del(&fw->update_job);\n    }\n    demo_log(INFO,\n             \"Reset done for instance: /\" AVS_QUOTE_MACRO(\n                     ANJAY_ADVANCED_FW_UPDATE_OID) \"/%u\",\n             iid);\n\n    /* Below code checks two things:\n     * 1. Relationship between instances in DOWNLOADED state\n     *    and their linked instances with context of common_reset\n     * 2. Version logic which is logic specific for targeted platform\n     * Then it sets conflicting instances accordingly\n     * */\n    for (int i = 0; i < FW_UPDATE_IID_IMAGE_SLOTS; ++i) {\n        /* Reset could be called by anjay befor all instances are initialized.\n         * Check of fw_table[i].anjay below makes sure that inst already\n         * initialized */\n        if (fw_table[i].anjay && is_state_downloaded(&fw_table[i])) {\n            const anjay_iid_t *linked_instances;\n            size_t linked_instances_count = 0;\n            anjay_iid_t conflicting_instances[FW_UPDATE_IID_IMAGE_SLOTS];\n            size_t conflicting_instances_count = 0;\n            anjay_advanced_fw_update_get_linked_instances(\n                    fw_table[i].anjay, fw_table[i].iid, &linked_instances,\n                    &linked_instances_count);\n            for (size_t j = 0; j < linked_instances_count; ++j) {\n                if ((!is_state_downloaded(&fw_table[linked_instances[j]]))\n                        || linked_instances[j] == fw->iid) {\n                    conflicting_instances[conflicting_instances_count++] =\n                            linked_instances[j];\n                }\n            }\n\n            check_version_logic(fw_table[i].iid,\n                                fw_table,\n                                &conflicting_instances,\n                                &conflicting_instances_count);\n\n            anjay_advanced_fw_update_set_conflicting_instances(\n                    fw_table[i].anjay, fw_table[i].iid, conflicting_instances,\n                    conflicting_instances_count);\n        }\n    }\n    if (fw->auto_suspend) {\n        anjay_advanced_fw_update_pull_suspend(fw->anjay);\n    }\n}\n\nint fw_update_common_perform_upgrade(\n        anjay_iid_t iid,\n        void *fw_,\n        const anjay_iid_t *requested_supplemental_iids,\n        size_t requested_supplemental_iids_count) {\n    advanced_fw_update_logic_t *fw_table = (advanced_fw_update_logic_t *) fw_;\n    advanced_fw_update_logic_t *fw = &fw_table[iid];\n    const anjay_iid_t *conflicting_instances = NULL;\n    size_t conflicting_instances_count;\n    anjay_advanced_fw_update_get_conflicting_instances(\n            fw->anjay, iid, &conflicting_instances,\n            &conflicting_instances_count);\n    if (conflicting_instances) {\n        demo_log(ERROR,\n                 \"Trying to update /\" AVS_QUOTE_MACRO(\n                         ANJAY_ADVANCED_FW_UPDATE_OID) \"/%u, but there are \"\n                                                       \"conflicting \"\n                                                       \"images\",\n                 fw->iid);\n        return ANJAY_ADVANCED_FW_UPDATE_ERR_DEPENDENCY_ERROR;\n    }\n\n    const anjay_iid_t *update_with_iid = NULL;\n    size_t update_with_iid_count = 0;\n    if (requested_supplemental_iids) {\n        demo_log(INFO, \"Received supplemental iids\");\n        update_with_iid = requested_supplemental_iids;\n        update_with_iid_count = requested_supplemental_iids_count;\n    } else {\n        const anjay_iid_t *linked_instances;\n        size_t linked_instances_count;\n        anjay_advanced_fw_update_get_linked_instances(\n                fw->anjay, iid, &linked_instances, &linked_instances_count);\n        if (linked_instances) {\n            update_with_iid = linked_instances;\n            update_with_iid_count = linked_instances_count;\n        }\n    }\n    int result = 0;\n    if (update_with_iid) {\n        for (size_t i = 0; i < update_with_iid_count; ++i) {\n            anjay_advanced_fw_update_set_state_and_result(\n                    fw_table[update_with_iid[i]].anjay,\n                    fw_table[update_with_iid[i]].iid,\n                    ANJAY_ADVANCED_FW_UPDATE_STATE_UPDATING,\n                    ANJAY_ADVANCED_FW_UPDATE_RESULT_INITIAL);\n            assert(fw_table[update_with_iid[i]].check_yourself);\n            if (fw_table[update_with_iid[i]].check_yourself(\n                        &fw_table[update_with_iid[i]])) {\n                result = -1;\n            }\n        }\n    }\n    assert(fw->check_yourself);\n    if (fw->check_yourself(fw)) {\n        result = -1;\n    }\n    if (result) {\n        return result;\n    }\n\n    if (update_with_iid) {\n        for (size_t i = 0; i < update_with_iid_count; ++i) {\n            assert(fw_table[update_with_iid[i]].update_yourself);\n            if ((result = fw_table[update_with_iid[i]].update_yourself(\n                         &fw_table[update_with_iid[i]]))) {\n                return result;\n            }\n        }\n    }\n    assert(fw->update_yourself);\n    return fw->update_yourself(fw);\n}\n\nint fw_update_common_maybe_create_firmware_file(\n        advanced_fw_update_logic_t *fw) {\n    if (fw->next_target_path) {\n        return 0;\n    }\n    if (fw->administratively_set_target_path) {\n        fw->next_target_path = avs_strdup(fw->administratively_set_target_path);\n    } else {\n        fw->next_target_path = generate_random_target_filepath();\n    }\n    if (!fw->next_target_path) {\n        return -1;\n    }\n    demo_log(INFO, \"Created %s\", fw->next_target_path);\n    return 0;\n}\n\nstatic void afu_logic_destroy(advanced_fw_update_logic_t *fw) {\n    assert(fw);\n    if (fw->stream) {\n        fclose(fw->stream);\n    }\n    if (fw->update_job) {\n        avs_sched_del(&fw->update_job);\n    }\n    avs_free(fw->administratively_set_target_path);\n    avs_free(fw->next_target_path);\n}\n\nconst char *const ADD_IMG_NAMES[] = {\n    [FW_UPDATE_IID_TEE] = \"TEE\",\n    [FW_UPDATE_IID_BOOT] = \"Bootloader\",\n    [FW_UPDATE_IID_MODEM] = \"Modem\"\n};\n\nint advanced_firmware_update_install(\n        anjay_t *anjay,\n        advanced_fw_update_logic_t *fw_table,\n        const char *persistence_file,\n        const avs_net_security_info_t *security_info,\n        const avs_coap_udp_tx_params_t *tx_params,\n        avs_time_duration_t tcp_request_timeout,\n        anjay_advanced_fw_update_result_t delayed_result,\n        bool prefer_same_socket_downloads,\n        const char *original_img_file_path,\n#ifdef ANJAY_WITH_SEND\n        bool use_lwm2m_send,\n#endif // ANJAY_WITH_SEND\n        bool auto_suspend) {\n    advanced_fw_update_logic_t *fw_logic_app = NULL;\n    int result = -1;\n\n    anjay_advanced_fw_update_global_config_t config = {\n#ifdef ANJAY_WITH_SEND\n        .use_lwm2m_send = use_lwm2m_send,\n#endif // ANJAY_WITH_SEND\n        .prefer_same_socket_downloads = prefer_same_socket_downloads\n    };\n    result = anjay_advanced_fw_update_install(anjay, &config);\n    if (!result && !original_img_file_path) {\n        demo_log(\n                INFO,\n                \"Advanced Firmware Update init not finished. Lack of original \"\n                \"image path, which is a path to file used to compare with file \"\n                \"obtained from server during update.\");\n        /* Already installed object (by anjay_advanced_fw_update_install())\n         * stays in demo and is not destroyed because some integration tests\n         * (other than AFU) needs accordance between objects in demo and objects\n         * defined in test_utils.py */\n        return 0;\n    }\n\n    advanced_firmware_update_persistence_file_data_t data =\n            advanced_firmware_update_read_persistence_file(persistence_file);\n\n    if (!result) {\n        fw_logic_app = &fw_table[FW_UPDATE_IID_APP];\n        fw_logic_app->iid = FW_UPDATE_IID_APP;\n\n        fw_logic_app->anjay = anjay;\n        fw_logic_app->persistence_file = persistence_file;\n        memcpy(fw_logic_app->current_ver, VER_DEFAULT, sizeof(VER_DEFAULT));\n\n        advanced_firmware_update_delete_persistence_file(fw_logic_app);\n        demo_log(\n                INFO,\n                \"Initial state of firmware upgrade of instance \"\n                \"/\" AVS_QUOTE_MACRO(\n                        ANJAY_ADVANCED_FW_UPDATE_OID) \"/%u - \"\n                                                      \"state: %d, result: %d, \",\n                (int) fw_logic_app->iid,\n                (int) data.states_results_paths.inst_states[FW_UPDATE_IID_APP],\n                (int) data.states_results_paths\n                        .inst_results[FW_UPDATE_IID_APP]);\n        fw_logic_app->next_target_path =\n                data.states_results_paths.next_target_paths[FW_UPDATE_IID_APP];\n        data.states_results_paths.next_target_paths[FW_UPDATE_IID_APP] = NULL;\n        anjay_advanced_fw_update_initial_state_t state = {\n            .state = data.states_results_paths.inst_states[FW_UPDATE_IID_APP],\n            .result = data.states_results_paths.inst_results[FW_UPDATE_IID_APP],\n            .persisted_severity = data.severity,\n            .persisted_last_state_change_time = data.last_state_change_time,\n            .persisted_update_deadline = data.update_deadline\n        };\n\n        if (delayed_result != ANJAY_ADVANCED_FW_UPDATE_RESULT_INITIAL) {\n            demo_log(INFO,\n                     \"delayed_result == %d; initializing Advanced Firmware \"\n                     \"Update in UPDATING state\",\n                     (int) delayed_result);\n            state.state = ANJAY_ADVANCED_FW_UPDATE_STATE_UPDATING;\n            state.result = ANJAY_ADVANCED_FW_UPDATE_RESULT_INITIAL;\n\n            // Simulate FOTA process that finishes after the LwM2M client starts\n            // by changing the Update Result later at runtime\n            set_delayed_advanced_fw_update_result_args_t args = {\n                .anjay = anjay,\n                .iid = FW_UPDATE_IID_APP,\n                .delayed_result = delayed_result,\n            };\n            if (args.delayed_result\n                    == ANJAY_ADVANCED_FW_UPDATE_RESULT_SUCCESS) {\n                args.delayed_state = ANJAY_ADVANCED_FW_UPDATE_STATE_IDLE;\n            } else if (args.delayed_result\n                       == ANJAY_ADVANCED_FW_UPDATE_RESULT_FAILED) {\n                args.delayed_state = ANJAY_ADVANCED_FW_UPDATE_STATE_IDLE;\n            } else {\n                demo_log(WARNING, \"Other configurations should not occur.\");\n            }\n            if (AVS_SCHED_NOW(anjay_get_scheduler(anjay), NULL,\n                              set_delayed_fw_update_result, &args,\n                              sizeof(args))) {\n                result = -1;\n                goto exit;\n            }\n        }\n\n        if (state.state == ANJAY_ADVANCED_FW_UPDATE_STATE_DOWNLOADING) {\n            if (!fw_logic_app->next_target_path\n                    || !(fw_logic_app->stream =\n                                 fopen(fw_logic_app->next_target_path, \"ab\"))) {\n                if (fw_logic_app->stream) {\n                    fclose(fw_logic_app->stream);\n                    fw_logic_app->stream = NULL;\n                }\n                state.state = ANJAY_ADVANCED_FW_UPDATE_STATE_IDLE;\n            }\n        } else if (state.state == ANJAY_ADVANCED_FW_UPDATE_STATE_IDLE) {\n            // we're initializing in the \"Idle\" state, so the firmware file is\n            // not supposed to exist; delete it if we have it for any weird\n            // reason\n            maybe_delete_firmware_file(fw_logic_app);\n        }\n        result = advanced_firmware_update_application_install(\n                anjay, fw_table, &state, security_info, tx_params,\n                tcp_request_timeout, auto_suspend);\n        if (result) {\n            demo_log(ERROR, \"AFU instance %u install failed\",\n                     FW_UPDATE_IID_APP);\n            result = -1;\n        }\n    }\n\n    for (anjay_iid_t i = FW_UPDATE_IID_TEE; i < FW_UPDATE_IID_IMAGE_SLOTS;\n         ++i) {\n        if (!result) {\n            advanced_fw_update_logic_t *fw_logic_add_inst = &fw_table[i];\n            fw_logic_add_inst->iid = i;\n            fw_logic_add_inst->anjay = anjay;\n            fw_logic_add_inst->original_img_file_path = original_img_file_path;\n            anjay_advanced_fw_update_initial_state_t state = {\n                .state = data.states_results_paths.inst_states[i],\n                .result = data.states_results_paths.inst_results[i]\n            };\n            fw_logic_add_inst->next_target_path =\n                    data.states_results_paths.next_target_paths[i];\n            data.states_results_paths.next_target_paths[i] = NULL;\n            demo_log(INFO,\n                     \"Initial state of firmware upgrade of instance \"\n                     \"/\" AVS_QUOTE_MACRO(\n                             ANJAY_ADVANCED_FW_UPDATE_OID) \"/%u - \"\n                                                           \"state: %d, result: \"\n                                                           \"%d, \",\n                     (int) fw_logic_add_inst->iid, (int) state.state,\n                     (int) state.result);\n            result = advanced_firmware_update_additional_image_install(\n                    anjay, fw_logic_add_inst->iid, fw_table, &state,\n                    security_info, ADD_IMG_NAMES[i]);\n\n            if (result) {\n                demo_log(ERROR, \"AFU instance %u install failed\",\n                         FW_UPDATE_IID_TEE);\n                result = -1;\n                break;\n            }\n        }\n    }\n\n    if (!result) {\n        if (auto_suspend) {\n            anjay_advanced_fw_update_pull_suspend(anjay);\n        }\n        demo_log(INFO, \"AFU object install success\");\n    }\n\nexit:\n    if (result) {\n        for (int i = 0; i < FW_UPDATE_IID_IMAGE_SLOTS; ++i) {\n            afu_logic_destroy(&fw_table[i]);\n            // If next_target_paths were read properly but some of\n            // image_install() failed, there still could be need to free\n            // allocated memory\n            avs_free(data.states_results_paths.next_target_paths[i]);\n        }\n    }\n    return result;\n}\n\nvoid advanced_firmware_update_set_package_path(\n        advanced_fw_update_logic_t *fw_logic, const char *path) {\n    if (fw_logic->stream) {\n        demo_log(ERROR,\n                 \"cannot set package path while a download is in progress\");\n        return;\n    }\n    char *new_target_path = avs_strdup(path);\n    if (!new_target_path) {\n        demo_log(ERROR, \"out of memory\");\n        return;\n    }\n\n    avs_free(fw_logic->administratively_set_target_path);\n    fw_logic->administratively_set_target_path = new_target_path;\n    demo_log(INFO, \"firmware package path set to %s\",\n             fw_logic->administratively_set_target_path);\n}\n\nvoid advanced_firmware_update_uninstall(advanced_fw_update_logic_t *fw_table) {\n    for (int i = 0; i < FW_UPDATE_IID_IMAGE_SLOTS; ++i) {\n        afu_logic_destroy(&fw_table[i]);\n    }\n}\n\nint advanced_firmware_update_get_security_config(\n        anjay_iid_t iid,\n        void *fw_,\n        anjay_security_config_t *out_security_config,\n        const char *download_uri) {\n    (void) iid;\n    advanced_fw_update_logic_t *fw_table = (advanced_fw_update_logic_t *) fw_;\n    advanced_fw_update_logic_t *fw = &fw_table[FW_UPDATE_IID_APP];\n    (void) download_uri;\n    memset(out_security_config, 0, sizeof(*out_security_config));\n    out_security_config->security_info = fw->security_info;\n    return 0;\n}\n"
  },
  {
    "path": "demo/advanced_firmware_update.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ADVANCED_FIRMWARE_UPDATE_H\n#define ADVANCED_FIRMWARE_UPDATE_H\n\n#include <stddef.h>\n#include <stdio.h>\n\n#include \"demo_utils.h\"\n#include \"objects.h\"\n#include <anjay/advanced_fw_update.h>\n#include <anjay/anjay_config.h>\n\n#define FW_UPDATE_IID_APP 0\n#define FW_UPDATE_IID_TEE 1\n#define FW_UPDATE_IID_BOOT 2\n#define FW_UPDATE_IID_MODEM 3\n#define FW_UPDATE_IID_IMAGE_SLOTS 4\n#define METADATA_LINKED_SLOTS 8\n\n#define FORCE_ERROR_OUT_OF_MEMORY 1\n#define FORCE_ERROR_FAILED_UPDATE 2\n#define FORCE_DELAYED_SUCCESS 3\n#define FORCE_DELAYED_ERROR_FAILED_UPDATE 4\n#define FORCE_SET_SUCCESS_FROM_PERFORM_UPGRADE 5\n#define FORCE_SET_FAILURE_FROM_PERFORM_UPGRADE 6\n#define FORCE_DO_NOTHING 7\n#define FORCE_DEFER 8\n\nenum target_image_t {\n    TARGET_IMAGE_TYPE_APPLICATION = 0,\n    TARGET_IMAGE_TYPE_ADDITIONAL_IMAGE,\n};\n\ntypedef struct advanced_firmware_metadata {\n    uint8_t magic[8];\n    uint16_t header_ver;\n    uint16_t force_error_case;\n    uint32_t crc;\n    uint8_t linked[METADATA_LINKED_SLOTS];\n    uint8_t pkg_ver_len;\n    uint8_t pkg_ver[IMG_VER_STR_MAX_LEN + 1];\n} advanced_fw_metadata_t;\n\ntypedef struct unpacked_imgs_info {\n    char *path;\n    advanced_fw_metadata_t meta;\n} unpacked_imgs_info_t;\n\ntypedef struct advanced_firmware_multipkg_metadata {\n    uint8_t magic[8];\n    uint16_t header_ver;\n    uint16_t packages_count;\n    uint32_t package_len[FW_UPDATE_IID_IMAGE_SLOTS];\n} advanced_fw_multipkg_metadata_t;\n\nstruct advanced_fw_update_logic {\n    anjay_iid_t iid;\n    const char *original_img_file_path;\n    char current_ver[IMG_VER_STR_MAX_LEN + 1];\n    anjay_t *anjay;\n    advanced_fw_metadata_t metadata;\n    char *administratively_set_target_path;\n    char *next_target_path;\n    const char *persistence_file;\n    FILE *stream;\n    avs_net_security_info_t security_info;\n    avs_coap_udp_tx_params_t coap_tx_params;\n    avs_time_duration_t tcp_request_timeout;\n    bool auto_suspend;\n    int (*check_yourself)(struct advanced_fw_update_logic *);\n    int (*update_yourself)(struct advanced_fw_update_logic *);\n    avs_sched_handle_t update_job;\n};\ntypedef struct advanced_fw_update_logic advanced_fw_update_logic_t;\n\nint advanced_firmware_update_application_install(\n        anjay_t *anjay,\n        advanced_fw_update_logic_t *fw_logic,\n        anjay_advanced_fw_update_initial_state_t *init_state,\n        const avs_net_security_info_t *security_info,\n        const avs_coap_udp_tx_params_t *tx_params,\n        avs_time_duration_t tcp_request_timeout,\n        bool auto_suspend);\nint advanced_firmware_update_app_perform(advanced_fw_update_logic_t *fw);\nconst char *advanced_firmware_update_app_get_pkg_version(anjay_iid_t iid,\n                                                         void *fw_);\nconst char *advanced_firmware_update_app_get_current_version(anjay_iid_t iid,\n                                                             void *fw_);\n\nint advanced_firmware_update_additional_image_install(\n        anjay_t *anjay,\n        anjay_iid_t iid,\n        advanced_fw_update_logic_t *fw_table,\n        anjay_advanced_fw_update_initial_state_t *init_state,\n        const avs_net_security_info_t *security_info,\n        const char *component_name);\n\nconst char *\nadvanced_firmware_update_additional_image_get_pkg_version(anjay_iid_t iid,\n                                                          void *fw_);\nconst char *\nadvanced_firmware_update_additional_image_get_current_version(anjay_iid_t iid,\n                                                              void *fw_);\n\nint advanced_firmware_update_install(\n        anjay_t *anjay,\n        advanced_fw_update_logic_t *fw_table,\n        const char *persistence_file,\n        const avs_net_security_info_t *security_info,\n        const avs_coap_udp_tx_params_t *tx_params,\n        avs_time_duration_t tcp_request_timeout,\n        anjay_advanced_fw_update_result_t delayed_result,\n        bool prefer_same_socket_downloads,\n        const char *original_img_file_path,\n#ifdef ANJAY_WITH_SEND\n        bool use_lwm2m_send,\n#endif // ANJAY_WITH_SEND\n        bool auto_suspend);\n\nvoid advanced_firmware_update_uninstall(advanced_fw_update_logic_t *fw_table);\nint fw_update_common_open(anjay_iid_t iid, void *fw_);\nint fw_update_common_write(anjay_iid_t iid,\n                           void *user_ptr,\n                           const void *data,\n                           size_t length);\nconst char *fw_update_common_get_current_version(anjay_iid_t iid, void *fw_);\nconst char *fw_update_common_get_pkg_version(anjay_iid_t iid, void *fw_);\nint fw_update_common_finish(anjay_iid_t iid, void *fw_);\nvoid fw_update_common_reset(anjay_iid_t iid, void *fw_);\nint fw_update_common_perform_upgrade(\n        anjay_iid_t iid,\n        void *fw_,\n        const anjay_iid_t *requested_supplemental_iids,\n        size_t requested_supplemental_iids_count);\nint fw_update_common_maybe_create_firmware_file(advanced_fw_update_logic_t *fw);\n\nint advanced_firmware_update_get_security_config(\n        anjay_iid_t iid,\n        void *fw_,\n        anjay_security_config_t *out_security_config,\n        const char *download_uri);\n\ntypedef struct {\n    anjay_advanced_fw_update_state_t inst_states[FW_UPDATE_IID_IMAGE_SLOTS];\n    anjay_advanced_fw_update_result_t inst_results[FW_UPDATE_IID_IMAGE_SLOTS];\n    char *next_target_paths[FW_UPDATE_IID_IMAGE_SLOTS];\n} states_results_paths_t;\n\nint advanced_firmware_update_read_states_results_paths(\n        advanced_fw_update_logic_t *fw_table,\n        states_results_paths_t *out_states_results_paths);\n\nint advanced_firmware_update_write_persistence_file(\n        const char *path,\n        states_results_paths_t *states_results_paths,\n        anjay_advanced_fw_update_severity_t severity,\n        avs_time_real_t last_state_change_time,\n        avs_time_real_t update_deadline,\n        const char *current_ver);\n\nvoid advanced_firmware_update_delete_persistence_file(\n        const advanced_fw_update_logic_t *fw);\n\nvoid advanced_firmware_update_set_package_path(\n        advanced_fw_update_logic_t *fw_logic, const char *path);\n\n#endif // ADVANCED_FIRMWARE_UPDATE_H\n"
  },
  {
    "path": "demo/advanced_firmware_update_addimg.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include \"advanced_firmware_update.h\"\n#include \"demo_utils.h\"\n\n#include <unistd.h>\n\nstatic advanced_fw_update_logic_t *fw_global;\n\nstatic int fw_stream_open(anjay_iid_t iid, void *fw_) {\n    (void) iid;\n\n    return fw_update_common_open(iid, fw_);\n}\n\nstatic int compare_files(FILE *s1, FILE *s2) {\n    while (!feof(s1)) {\n        char buf_1[1024];\n        char buf_2[1024];\n        size_t bytes_read_1 = fread(buf_1, 1, sizeof(buf_1), s1);\n        size_t bytes_read_2 = fread(buf_2, 1, sizeof(buf_2), s2);\n        if (bytes_read_1 != bytes_read_2) {\n            return -1;\n        }\n        if (memcmp(buf_1, buf_2, bytes_read_1)) {\n            return -1;\n        }\n    }\n    return 0;\n}\n\nstatic int compare_images(const char *file_path_1, const char *file_path_2) {\n    int result = -1;\n    FILE *stream1 = fopen(file_path_1, \"r\");\n    FILE *stream2 = NULL;\n\n    if (!stream1) {\n        demo_log(ERROR, \"could not open file: %s\", file_path_1);\n        goto cleanup;\n    }\n\n    stream2 = fopen(file_path_2, \"r\");\n    if (!stream2) {\n        demo_log(ERROR, \"could not open file: %s\", file_path_2);\n        goto cleanup;\n    }\n\n    result = compare_files(stream1, stream2);\ncleanup:\n    if (stream1) {\n        fclose(stream1);\n    }\n    if (stream2) {\n        fclose(stream2);\n    }\n    return result;\n}\n\nstatic int prepare_and_validate_update(advanced_fw_update_logic_t *fw) {\n    demo_log(INFO,\n             \"Checking image of \" AVS_QUOTE_MACRO(\n                     ANJAY_ADVANCED_FW_UPDATE_OID) \"/%u instance\",\n             fw->iid);\n    if (!compare_images(fw->original_img_file_path, fw->next_target_path)) {\n        demo_log(INFO, \"Image check success\");\n        return 0;\n    }\n    demo_log(ERROR, \"Image check failure\");\n    anjay_advanced_fw_update_set_state_and_result(\n            fw->anjay, fw->iid, ANJAY_ADVANCED_FW_UPDATE_STATE_DOWNLOADED,\n            ANJAY_ADVANCED_FW_UPDATE_RESULT_FAILED);\n    return -1;\n}\n\nstatic int update(advanced_fw_update_logic_t *fw) {\n    demo_log(INFO, \"*** FIRMWARE UPDATE: %s ***\", fw->next_target_path);\n    demo_log(INFO,\n             \"Update success for \" AVS_QUOTE_MACRO(\n                     ANJAY_ADVANCED_FW_UPDATE_OID) \"/%u instance\",\n             fw->iid);\n    memcpy(fw->current_ver, fw->metadata.pkg_ver, fw->metadata.pkg_ver_len);\n    anjay_advanced_fw_update_set_state_and_result(\n            fw->anjay, fw->iid, ANJAY_ADVANCED_FW_UPDATE_STATE_IDLE,\n            ANJAY_ADVANCED_FW_UPDATE_RESULT_SUCCESS);\n    return 0;\n}\n\nstatic anjay_advanced_fw_update_handlers_t handlers = {\n    .stream_open = fw_stream_open,\n    .stream_write = fw_update_common_write,\n    .stream_finish = fw_update_common_finish,\n    .reset = fw_update_common_reset,\n    .get_pkg_version = fw_update_common_get_pkg_version,\n    .get_current_version = fw_update_common_get_current_version,\n    .perform_upgrade = fw_update_common_perform_upgrade\n};\n\nint advanced_firmware_update_additional_image_install(\n        anjay_t *anjay,\n        anjay_iid_t iid,\n        advanced_fw_update_logic_t *fw_table,\n        anjay_advanced_fw_update_initial_state_t *init_state,\n        const avs_net_security_info_t *security_info,\n        const char *component_name) {\n    advanced_fw_update_logic_t *fw_logic = &fw_table[iid];\n    memcpy(fw_logic->current_ver, VER_DEFAULT, sizeof(VER_DEFAULT));\n    fw_global = fw_logic;\n    if (security_info) {\n        memcpy(&fw_logic->security_info, security_info,\n               sizeof(fw_logic->security_info));\n        handlers.get_security_config =\n                advanced_firmware_update_get_security_config;\n    } else {\n        handlers.get_security_config = NULL;\n    }\n    int result =\n            anjay_advanced_fw_update_instance_add(anjay, fw_logic->iid,\n                                                  component_name, &handlers,\n                                                  fw_table, init_state);\n    if (!result) {\n        fw_logic->check_yourself = prepare_and_validate_update;\n        fw_logic->update_yourself = update;\n    }\n    if (result) {\n        memset(fw_global, 0x00, sizeof(advanced_fw_update_logic_t));\n    }\n    return result;\n}\n"
  },
  {
    "path": "demo/advanced_firmware_update_app.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include \"advanced_firmware_update.h\"\n#include \"demo_utils.h\"\n\n#include <errno.h>\n#include <unistd.h>\n\n#define RESTART_DELAY_SEC 3\n\nstatic advanced_fw_update_logic_t *fw_global;\n\nstatic int fw_stream_open(anjay_iid_t iid, void *fw_) {\n    (void) iid;\n    return fw_update_common_open(iid, fw_);\n}\n\nstatic int prepare_and_validate_update(advanced_fw_update_logic_t *fw) {\n    demo_log(INFO,\n             \"Checking image of \" AVS_QUOTE_MACRO(\n                     ANJAY_ADVANCED_FW_UPDATE_OID) \"/%u instance\",\n             fw->iid);\n    if (fw->metadata.force_error_case) {\n        demo_log(INFO, \"force_error_case present and set to: %d\",\n                 (int) fw->metadata.force_error_case);\n    }\n    if (fw->metadata.force_error_case == FORCE_ERROR_FAILED_UPDATE) {\n        demo_log(ERROR, \"Image check failure\");\n        advanced_firmware_update_delete_persistence_file(fw);\n        anjay_advanced_fw_update_set_state_and_result(\n                fw->anjay, fw->iid, ANJAY_ADVANCED_FW_UPDATE_STATE_DOWNLOADED,\n                ANJAY_ADVANCED_FW_UPDATE_RESULT_FAILED);\n        return -1;\n    }\n    demo_log(ERROR, \"Image check success\");\n    return 0;\n}\n\nstatic int write_persistence(advanced_fw_update_logic_t *fw_table) {\n    advanced_fw_update_logic_t *fw =\n            (advanced_fw_update_logic_t *) &fw_table[FW_UPDATE_IID_APP];\n    states_results_paths_t states_results_paths;\n    if (advanced_firmware_update_read_states_results_paths(\n                fw_table, &states_results_paths)) {\n        demo_log(ERROR, \"Can't read states/results/paths.\");\n        return -1;\n    }\n    states_results_paths.inst_states[FW_UPDATE_IID_APP] =\n            ANJAY_ADVANCED_FW_UPDATE_STATE_IDLE;\n    states_results_paths.inst_results[FW_UPDATE_IID_APP] =\n            ANJAY_ADVANCED_FW_UPDATE_RESULT_SUCCESS;\n    if (fw->persistence_file\n            && advanced_firmware_update_write_persistence_file(\n                       fw->persistence_file, &states_results_paths,\n                       anjay_advanced_fw_update_get_severity(fw->anjay,\n                                                             fw->iid),\n                       anjay_advanced_fw_update_get_last_state_change_time(\n                               fw->anjay, fw->iid),\n                       anjay_advanced_fw_update_get_deadline(fw->anjay,\n                                                             fw->iid),\n                       fw->current_ver)) {\n        advanced_firmware_update_delete_persistence_file(fw);\n        demo_log(ERROR, \"Can't write persistence file.\");\n        return -1;\n    }\n    return 0;\n}\n\nstruct execute_new_app_args {\n    advanced_fw_update_logic_t *fw_table;\n};\n\nstatic void execute_new_app(avs_sched_t *sched, const void *args_) {\n    (void) sched;\n    const struct execute_new_app_args *args =\n            (const struct execute_new_app_args *) args_;\n    advanced_fw_update_logic_t *fw =\n            (advanced_fw_update_logic_t *) &args->fw_table[FW_UPDATE_IID_APP];\n    if (write_persistence(args->fw_table)) {\n        demo_log(ERROR, \"Can't persist state. Execute new app failed.\");\n        return;\n    }\n    demo_log(INFO, \"App image going to execv from %s\", fw->next_target_path);\n    execv(fw->next_target_path, argv_get());\n    demo_log(ERROR, \"execv failed (%s)\", strerror(errno));\n}\n\nstatic int update(advanced_fw_update_logic_t *fw) {\n    /* This function only works with APP so fw always points to fw_table: */\n    advanced_fw_update_logic_t *fw_table = (advanced_fw_update_logic_t *) fw;\n    demo_log(INFO, \"*** FIRMWARE UPDATE: %s ***\", fw->next_target_path);\n    if (fw->metadata.force_error_case) {\n        demo_log(INFO, \"force_error_case present and set to: %d\",\n                 (int) fw->metadata.force_error_case);\n        if (write_persistence(fw_table)) {\n            demo_log(ERROR, \"Can't persist state. Update failed.\");\n            return -1;\n        }\n    }\n    switch (fw->metadata.force_error_case) {\n    case FORCE_ERROR_FAILED_UPDATE:\n        AVS_UNREACHABLE(\"Update process should fail earlier\");\n    case FORCE_DELAYED_SUCCESS:\n        if (argv_append(\"--delayed-afu-result\") || argv_append(\"1\")) {\n            demo_log(ERROR, \"could not append delayed result to argv\");\n            return -1;\n        }\n        break;\n    case FORCE_DELAYED_ERROR_FAILED_UPDATE:\n        if (argv_append(\"--delayed-afu-result\") || argv_append(\"8\")) {\n            demo_log(ERROR, \"could not append delayed result to argv\");\n            return -1;\n        }\n        break;\n    case FORCE_SET_SUCCESS_FROM_PERFORM_UPGRADE:\n        if (anjay_advanced_fw_update_set_state_and_result(\n                    fw->anjay, fw->iid, ANJAY_ADVANCED_FW_UPDATE_STATE_IDLE,\n                    ANJAY_ADVANCED_FW_UPDATE_RESULT_SUCCESS)) {\n            demo_log(ERROR,\n                     \"anjay_advanced_fw_update_set_state_and_result failed\");\n            return -1;\n        }\n        return 0;\n    case FORCE_SET_FAILURE_FROM_PERFORM_UPGRADE:\n        if (anjay_advanced_fw_update_set_state_and_result(\n                    fw->anjay, fw->iid, ANJAY_ADVANCED_FW_UPDATE_STATE_IDLE,\n                    ANJAY_ADVANCED_FW_UPDATE_RESULT_FAILED)) {\n            demo_log(ERROR, \"anjay_fw_update_set_result failed\");\n            return -1;\n        }\n        return 0;\n    case FORCE_DO_NOTHING:\n        return 0;\n    case FORCE_DEFER:\n        return ANJAY_ADVANCED_FW_UPDATE_ERR_DEFERRED;\n    default:\n        break;\n    }\n    struct execute_new_app_args args = {\n        .fw_table = fw_table,\n    };\n    if (AVS_SCHED_DELAYED(anjay_get_scheduler(fw->anjay), &fw->update_job,\n                          avs_time_duration_from_scalar(RESTART_DELAY_SEC,\n                                                        AVS_TIME_S),\n                          execute_new_app, &args, sizeof(args))) {\n        demo_log(WARNING, \"Could not schedule the upgrade job\");\n        return ANJAY_ERR_INTERNAL;\n    }\n    return 0;\n}\n\nstatic avs_coap_udp_tx_params_t fw_get_coap_tx_params(\n        anjay_iid_t iid, void *user_ptr, const char *download_uri) {\n    (void) iid;\n    (void) download_uri;\n    advanced_fw_update_logic_t *fw_table =\n            (advanced_fw_update_logic_t *) user_ptr;\n    advanced_fw_update_logic_t *fw = &fw_table[FW_UPDATE_IID_APP];\n    if (fw->auto_suspend) {\n        anjay_advanced_fw_update_pull_reconnect(fw->anjay);\n    }\n    return fw->coap_tx_params;\n}\n\nstatic avs_time_duration_t fw_get_tcp_request_timeout(\n        anjay_iid_t iid, void *user_ptr, const char *download_uri) {\n    (void) iid;\n    (void) download_uri;\n    advanced_fw_update_logic_t *fw_table =\n            (advanced_fw_update_logic_t *) user_ptr;\n    advanced_fw_update_logic_t *fw = &fw_table[FW_UPDATE_IID_APP];\n    return fw->tcp_request_timeout;\n}\n\nstatic anjay_advanced_fw_update_handlers_t handlers = {\n    .stream_open = fw_stream_open,\n    .stream_write = fw_update_common_write,\n    .stream_finish = fw_update_common_finish,\n    .reset = fw_update_common_reset,\n    .get_pkg_version = fw_update_common_get_pkg_version,\n    .get_current_version = fw_update_common_get_current_version,\n    .perform_upgrade = fw_update_common_perform_upgrade\n};\n\nint advanced_firmware_update_application_install(\n        anjay_t *anjay,\n        advanced_fw_update_logic_t *fw_table,\n        anjay_advanced_fw_update_initial_state_t *init_state,\n        const avs_net_security_info_t *security_info,\n        const avs_coap_udp_tx_params_t *tx_params,\n        avs_time_duration_t tcp_request_timeout,\n        bool auto_suspend) {\n    advanced_fw_update_logic_t *fw_logic = &fw_table[FW_UPDATE_IID_APP];\n\n    if (security_info) {\n        memcpy(&fw_logic->security_info, security_info,\n               sizeof(fw_logic->security_info));\n        handlers.get_security_config =\n                advanced_firmware_update_get_security_config;\n    } else {\n        handlers.get_security_config = NULL;\n    }\n\n    if (tx_params || auto_suspend) {\n        if (tx_params) {\n            fw_logic->coap_tx_params = *tx_params;\n        } else if (auto_suspend) {\n            fw_logic->coap_tx_params = AVS_COAP_DEFAULT_UDP_TX_PARAMS;\n        }\n        fw_logic->auto_suspend = auto_suspend;\n        handlers.get_coap_tx_params = fw_get_coap_tx_params;\n    } else {\n        handlers.get_coap_tx_params = NULL;\n    }\n\n    if (avs_time_duration_valid(tcp_request_timeout)) {\n        fw_logic->tcp_request_timeout = tcp_request_timeout;\n        handlers.get_tcp_request_timeout = fw_get_tcp_request_timeout;\n    } else {\n        handlers.get_tcp_request_timeout = NULL;\n    }\n\n    fw_global = fw_logic;\n    int result = anjay_advanced_fw_update_instance_add(anjay,\n                                                       fw_logic->iid,\n                                                       \"application\",\n                                                       &handlers,\n                                                       fw_table,\n                                                       init_state);\n    if (!result) {\n        fw_logic->check_yourself = prepare_and_validate_update;\n        fw_logic->update_yourself = update;\n    }\n    if (result) {\n        memset(fw_global, 0x00, sizeof(advanced_fw_update_logic_t));\n    }\n    return result;\n}\n"
  },
  {
    "path": "demo/demo.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifdef _WIN32\n#    define WIN32_LEAN_AND_MEAN\n#    define _WIN32_WINNT \\\n        0x600 // minimum requirement: Windows NT 6.0 a.k.a. Vista\n#    include <ws2tcpip.h>\n#    undef ERROR\n#else // _WIN32\n#    include <netinet/in.h>\n#endif // _WIN32\n\n#include \"demo.h\"\n#include \"demo_args.h\"\n#include \"demo_cmds.h\"\n#include \"demo_utils.h\"\n#ifdef ANJAY_WITH_MODULE_FW_UPDATE\n#    include \"firmware_update.h\"\n#endif // ANJAY_WITH_MODULE_FW_UPDATE\n\n#ifdef ANJAY_WITH_MODULE_ADVANCED_FW_UPDATE\n#    include \"advanced_firmware_update.h\"\n#endif // ANJAY_WITH_MODULE_ADVANCED_FW_UPDATE\n\n#include \"objects.h\"\n#include <avsystem/commons/avs_url.h>\n\n#include <assert.h>\n#include <inttypes.h>\n#include <signal.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n#include <pthread.h>\n#include <sys/stat.h>\n#include <sys/time.h>\n#include <sys/types.h>\n#include <unistd.h>\n\n#include <avsystem/commons/avs_base64.h>\n#include <avsystem/commons/avs_stream_file.h>\n\n#include <anjay/access_control.h>\n#include <anjay/attr_storage.h>\n#include <anjay/fw_update.h>\n\n#ifdef WITH_DEMO_USE_STANDALONE_OBJECTS\n#    include \"../standalone/security/standalone_security.h\"\n#    include \"../standalone/server/standalone_server.h\"\n#else // WITH_DEMO_USE_STANDALONE_OBJECTS\n#    include <anjay/security.h>\n#    include <anjay/server.h>\n#endif // WITH_DEMO_USE_STANDALONE_OBJECTS\n\n#ifdef ANJAY_WITH_MODULE_FACTORY_PROVISIONING\n#    include <anjay/factory_provisioning.h>\n#endif // ANJAY_WITH_MODULE_FACTORY_PROVISIONING\n\n#ifdef ANJAY_WITH_LWM2M_GATEWAY\n#    include \"lwm2m_gateway.h\"\n#endif // ANJAY_WITH_LWM2M_GATEWAY\n\n#include \"net_traffic_interceptor.h\"\n\n#define MAX_PATH_STRING_SIZE_WO_PREFIX sizeof(\"/65535/65535/65535/65535\")\n#ifdef ANJAY_WITH_LWM2M_GATEWAY\n// ANJAY_GATEWAY_MAX_PREFIX_LEN contains space for null-terminator, but since\n// sizeof(...) already includes null-terminator, we use that one additional byte\n// for the new leading '/' in path\n#    define MAX_PATH_STRING_SIZE \\\n        MAX_PATH_STRING_SIZE_WO_PREFIX + ANJAY_GATEWAY_MAX_PREFIX_LEN\n#else // ANJAY_WITH_LWM2M_GATEWAY\n#    define MAX_PATH_STRING_SIZE MAX_PATH_STRING_SIZE_WO_PREFIX\n#endif // ANJAY_WITH_LWM2M_GATEWAY\n\n#ifdef AVS_COMMONS_NET_WITH_MBEDTLS_SSLKEYLOG\n#    define SSLKEYLOGFILE \"/tmp/sslkey.log\"\n#endif // AVS_COMMONS_NET_WITH_MBEDTLS_SSLKEYLOG\n\nstatic int security_object_reload(anjay_demo_t *demo) {\n#ifdef WITH_DEMO_USE_STANDALONE_OBJECTS\n    standalone_security_object_purge(demo->security_obj_ptr);\n#else  // WITH_DEMO_USE_STANDALONE_OBJECTS\n    anjay_security_object_purge(demo->anjay);\n#endif // WITH_DEMO_USE_STANDALONE_OBJECTS\n    const server_connection_args_t *args = demo->connection_args;\n    const server_entry_t *server;\n    DEMO_FOREACH_SERVER_ENTRY(server, args) {\n#ifdef WITH_DEMO_USE_STANDALONE_OBJECTS\n        standalone_security_instance_t instance;\n#else  // WITH_DEMO_USE_STANDALONE_OBJECTS\n        anjay_security_instance_t instance;\n#endif // WITH_DEMO_USE_STANDALONE_OBJECTS\n        memset(&instance, 0, sizeof(instance));\n        instance.ssid = ANJAY_SSID_ANY;\n        if ((instance.bootstrap_server = server->is_bootstrap)) {\n#ifdef ANJAY_WITH_BOOTSTRAP\n            instance.client_holdoff_s = args->bootstrap_holdoff_s;\n            instance.bootstrap_timeout_s = args->bootstrap_timeout_s;\n#endif // ANJAY_WITH_BOOTSTRAP\n        } else {\n            instance.client_holdoff_s = -1;\n            instance.bootstrap_timeout_s = -1;\n            instance.ssid = server->id;\n        }\n        static const char SECURE_PREFIX[] = \"coaps\";\n        if (server->uri\n                && strncmp(server->uri, SECURE_PREFIX, strlen(SECURE_PREFIX))\n                               == 0) {\n            instance.security_mode = args->security_mode;\n        } else {\n            instance.security_mode = ANJAY_SECURITY_NOSEC;\n        }\n\n        /**\n         * Note: we can assign pointers by value, as @ref\n         * anjay_security_object_add_instance will make a deep copy by itself.\n         */\n        instance.server_uri = server->uri;\n        if (instance.security_mode != ANJAY_SECURITY_EST\n                || server->is_bootstrap) {\n#ifdef ANJAY_WITH_SECURITY_STRUCTURED\n            if (args->public_cert.desc.source != AVS_CRYPTO_DATA_SOURCE_EMPTY) {\n                instance.public_cert = args->public_cert;\n            } else if (args->psk_identity.desc.source\n                       != AVS_CRYPTO_DATA_SOURCE_EMPTY) {\n                instance.psk_identity = args->psk_identity;\n            } else\n#endif // ANJAY_WITH_SECURITY_STRUCTURED\n            {\n                instance.public_cert_or_psk_identity =\n                        args->public_cert_or_psk_identity;\n                instance.public_cert_or_psk_identity_size =\n                        args->public_cert_or_psk_identity_size;\n            }\n#ifdef ANJAY_WITH_SECURITY_STRUCTURED\n            if (args->private_key.desc.source != AVS_CRYPTO_DATA_SOURCE_EMPTY) {\n                instance.private_key = args->private_key;\n            } else if (args->psk_key.desc.source\n                       != AVS_CRYPTO_DATA_SOURCE_EMPTY) {\n                instance.psk_key = args->psk_key;\n            } else\n#endif // ANJAY_WITH_SECURITY_STRUCTURED\n            {\n                instance.private_cert_or_psk_key =\n                        args->private_cert_or_psk_key;\n                instance.private_cert_or_psk_key_size =\n                        args->private_cert_or_psk_key_size;\n            }\n        }\n        instance.server_public_key = args->server_public_key;\n        instance.server_public_key_size = args->server_public_key_size;\n#ifdef ANJAY_WITH_LWM2M11\n        instance.server_name_indication = server->sni;\n        instance.certificate_usage =\n                (const uint8_t *) &server->certificate_usage;\n#endif // ANJAY_WITH_LWM2M11\n\n        anjay_iid_t iid = server->security_iid;\n        if (\n#ifdef WITH_DEMO_USE_STANDALONE_OBJECTS\n                    standalone_security_object_add_instance(\n                            demo->security_obj_ptr, &instance, &iid)\n#else  // WITH_DEMO_USE_STANDALONE_OBJECTS\n                    anjay_security_object_add_instance(demo->anjay, &instance,\n                                                       &iid)\n#endif // WITH_DEMO_USE_STANDALONE_OBJECTS\n        ) {\n            demo_log(ERROR, \"Cannot add Security Instance\");\n            return -1;\n        }\n    }\n    return 0;\n}\n\nstatic int server_object_reload(anjay_demo_t *demo) {\n#ifdef WITH_DEMO_USE_STANDALONE_OBJECTS\n    standalone_server_object_purge(demo->server_obj_ptr);\n#else  // WITH_DEMO_USE_STANDALONE_OBJECTS\n    anjay_server_object_purge(demo->anjay);\n#endif // WITH_DEMO_USE_STANDALONE_OBJECTS\n    const server_entry_t *server;\n    DEMO_FOREACH_SERVER_ENTRY(server, demo->connection_args) {\n        if (server->is_bootstrap) {\n            continue;\n        }\n\n#ifdef WITH_DEMO_USE_STANDALONE_OBJECTS\n        const standalone_server_instance_t instance =\n#else  // WITH_DEMO_USE_STANDALONE_OBJECTS\n        const anjay_server_instance_t instance =\n#endif // WITH_DEMO_USE_STANDALONE_OBJECTS\n                {\n                    .ssid = server->id,\n                    .lifetime = demo->connection_args->lifetime,\n                    .default_min_period = -1,\n                    .default_max_period = -1,\n                    .disable_timeout = -1,\n                    .binding = server->binding_mode,\n                    .notification_storing = true,\n#ifdef ANJAY_WITH_LWM2M11\n                    .communication_retry_count = &server->retry_count,\n                    .communication_retry_timer = &server->retry_timer,\n                    .communication_sequence_retry_count =\n                            &server->sequence_retry_count,\n                    .communication_sequence_delay_timer =\n                            &server->sequence_delay_timer,\n                    .preferred_transport = '\\0',\n#endif // ANJAY_WITH_LWM2M11\n                };\n        anjay_iid_t iid = server->server_iid;\n        if (\n#ifdef WITH_DEMO_USE_STANDALONE_OBJECTS\n                    standalone_server_object_add_instance(demo->server_obj_ptr,\n                                                          &instance, &iid)\n#else  // WITH_DEMO_USE_STANDALONE_OBJECTS\n                    anjay_server_object_add_instance(demo->anjay, &instance,\n                                                     &iid)\n#endif // WITH_DEMO_USE_STANDALONE_OBJECTS\n        ) {\n            demo_log(ERROR, \"Cannot add Server Instance\");\n            return -1;\n        }\n    }\n    return 0;\n}\n\nconst anjay_dm_object_def_t **demo_find_object(anjay_demo_t *demo,\n                                               anjay_oid_t oid) {\n    AVS_LIST(anjay_demo_object_t) object;\n    AVS_LIST_FOREACH(object, demo->objects) {\n        if ((*object->obj_ptr)->oid == oid) {\n            return object->obj_ptr;\n        }\n    }\n    return NULL;\n}\n\nvoid demo_reload_servers(anjay_demo_t *demo) {\n    if (security_object_reload(demo) || server_object_reload(demo)) {\n        demo_log(ERROR, \"Error while adding new server objects\");\n        exit(-1);\n    }\n}\n\nstatic void demo_delete(anjay_demo_t *demo) {\n    avs_sched_del(&demo->notify_time_dependent_job);\n\n#if defined(AVS_COMMONS_WITH_AVS_PERSISTENCE) \\\n        && defined(AVS_COMMONS_STREAM_WITH_FILE)\n#    ifdef ANJAY_WITH_ATTR_STORAGE\n    if (demo->anjay && demo->attr_storage_file) {\n        avs_stream_t *data = avs_stream_file_create(demo->attr_storage_file,\n                                                    AVS_STREAM_FILE_WRITE);\n        if (!data\n                || avs_is_err(anjay_attr_storage_persist(demo->anjay, data))) {\n            demo_log(ERROR, \"Cannot persist attribute storage to file %s\",\n                     demo->attr_storage_file);\n        }\n        avs_stream_cleanup(&data);\n    }\n#    endif // ANJAY_WITH_ATTR_STORAGE\n\n    if (demo->anjay && demo->dm_persistence_file) {\n        avs_stream_t *data = avs_stream_file_create(demo->dm_persistence_file,\n                                                    AVS_STREAM_FILE_WRITE);\n        if (!data\n#    ifdef WITH_DEMO_USE_STANDALONE_OBJECTS\n                || avs_is_err(standalone_security_object_persist(\n                           demo->security_obj_ptr, data))\n                || avs_is_err(standalone_server_object_persist(\n                           demo->server_obj_ptr, data))\n#    else  // WITH_DEMO_USE_STANDALONE_OBJECTS\n                || avs_is_err(anjay_security_object_persist(demo->anjay, data))\n                || avs_is_err(anjay_server_object_persist(demo->anjay, data))\n#    endif // WITH_DEMO_USE_STANDALONE_OBJECTS\n#    ifdef ANJAY_WITH_MODULE_ACCESS_CONTROL\n                || avs_is_err(anjay_access_control_persist(demo->anjay, data))\n#    endif // ANJAY_WITH_MODULE_ACCESS_CONTROL\n        ) {\n            demo_log(ERROR, \"Cannot persist data model to file %s\",\n                     demo->dm_persistence_file);\n        }\n        avs_stream_cleanup(&data);\n    }\n#endif // defined(AVS_COMMONS_WITH_AVS_PERSISTENCE) &&\n       // defined(AVS_COMMONS_STREAM_WITH_FILE)\n\n    if (demo->schedule_update_on_exit) {\n        demo_log(INFO, \"forced registration update on exit\");\n        if (demo->anjay) {\n            anjay_schedule_registration_update(demo->anjay, ANJAY_SSID_ANY);\n        } else {\n            demo_log(INFO, \"Anjay object not created, skipping\");\n        }\n    }\n\n#ifdef ANJAY_WITH_LWM2M_GATEWAY\n    lwm2m_gateway_cleanup(demo->anjay);\n#endif // ANJAY_WITH_LWM2M_GATEWAY\n    if (demo->anjay) {\n        anjay_delete(demo->anjay);\n    }\n    AVS_LIST_CLEAR(&demo->objects) {\n        demo->objects->release_func(demo->objects->obj_ptr);\n    }\n    AVS_LIST_CLEAR(&demo->installed_objects_update_handlers);\n#ifdef ANJAY_WITH_MODULE_FW_UPDATE\n    firmware_update_destroy(&demo->fw_update);\n#endif // ANJAY_WITH_MODULE_FW_UPDATE\n#ifdef ANJAY_WITH_MODULE_ADVANCED_FW_UPDATE\n    advanced_firmware_update_uninstall(demo->advanced_fw_update_logic_table);\n#endif // ANJAY_WITH_MODULE_ADVANCED_FW_UPDATE\n#ifdef ANJAY_WITH_MODULE_SW_MGMT\n    sw_mgmt_update_destroy(demo->sw_mgmt_table);\n#endif // ANJAY_WITH_MODULE_SW_MGMT\n#ifdef WITH_DEMO_USE_STANDALONE_OBJECTS\n    standalone_server_object_cleanup(demo->server_obj_ptr);\n    standalone_security_object_cleanup(demo->security_obj_ptr);\n    demo->server_obj_ptr = NULL;\n    demo->security_obj_ptr = NULL;\n#endif // WITH_DEMO_USE_STANDALONE_OBJECTS\n\n    AVS_LIST_CLEAR(&demo->allocated_buffers);\n\n#ifdef WITH_DEMO_TRAFFIC_INTERCEPTOR\n    (void) interceptor_deinit();\n#endif // WITH_DEMO_TRAFFIC_INTERCEPTOR\n    avs_free(demo);\n}\n\nstatic bool has_bootstrap_server(anjay_demo_t *demo) {\n    const server_entry_t *server;\n    DEMO_FOREACH_SERVER_ENTRY(server, demo->connection_args) {\n        if (server->is_bootstrap) {\n            return true;\n        }\n    }\n    return false;\n}\n\nstatic size_t count_non_bootstrap_servers(anjay_demo_t *demo) {\n    size_t result = 0;\n    const server_entry_t *server;\n    DEMO_FOREACH_SERVER_ENTRY(server, demo->connection_args) {\n        if (!server->is_bootstrap) {\n            ++result;\n        }\n    }\n    return result;\n}\n\n#ifdef ANJAY_WITH_MODULE_ACCESS_CONTROL\nstatic int add_default_access_entries(anjay_demo_t *demo) {\n    if (has_bootstrap_server(demo) || count_non_bootstrap_servers(demo) <= 1) {\n        // ACLs are not necessary\n        return 0;\n    }\n\n    const server_entry_t *server;\n    DEMO_FOREACH_SERVER_ENTRY(server, demo->connection_args) {\n        if (anjay_access_control_set_acl(demo->anjay, DEMO_OID_SERVER,\n                                         server->server_iid, server->id,\n                                         ANJAY_ACCESS_MASK_READ\n                                                 | ANJAY_ACCESS_MASK_WRITE\n                                                 | ANJAY_ACCESS_MASK_EXECUTE)) {\n            return -1;\n        }\n    }\n\n    int result = 0;\n    AVS_LIST(anjay_demo_object_t) object;\n    AVS_LIST_FOREACH(object, demo->objects) {\n        if ((*object->obj_ptr)->oid == DEMO_OID_SECURITY\n                || (*object->obj_ptr)->oid == DEMO_OID_SERVER) {\n            continue;\n        }\n        AVS_LIST(anjay_iid_t) iids = NULL;\n        result = object->get_instances_func(object->obj_ptr, &iids);\n        AVS_LIST_CLEAR(&iids) {\n            if (!result) {\n                result = anjay_access_control_set_acl(\n                        demo->anjay,\n                        (*object->obj_ptr)->oid,\n                        *iids,\n                        ANJAY_SSID_ANY,\n                        ANJAY_ACCESS_MASK_READ | ANJAY_ACCESS_MASK_WRITE\n                                | ANJAY_ACCESS_MASK_EXECUTE);\n            }\n        }\n    }\n\n    return result;\n}\n\nstatic int add_access_entries(anjay_demo_t *demo,\n                              const cmdline_args_t *cmdline_args) {\n    const AVS_LIST(access_entry_t) it;\n    AVS_LIST_FOREACH(it, cmdline_args->access_entries) {\n        if (anjay_access_control_set_acl(demo->anjay, it->oid, it->iid,\n                                         it->ssid, it->mask)) {\n            return -1;\n        }\n    }\n    return 0;\n}\n#endif // ANJAY_WITH_MODULE_ACCESS_CONTROL\n\nstatic const char *derive_binding_mode_from_uri(const char *uri) {\n    static const struct {\n        const char *prefix;\n        const char *mode;\n    } mode_dict[] = {\n        { \"coap+tcp://\", \"T\" },\n        { \"coaps+tcp://\", \"T\" },\n        { \"coap://\", \"U\" },\n        { \"coaps://\", \"U\" },\n    };\n\n    for (size_t i = 0; i < AVS_ARRAY_SIZE(mode_dict); i++) {\n        if (strncmp(mode_dict[i].prefix, uri, strlen(mode_dict[i].prefix))\n                == 0) {\n            return mode_dict[i].mode;\n        }\n    }\n\n    return \"U\";\n}\n\nstatic int get_single_instance(const anjay_dm_object_def_t **obj_ptr,\n                               AVS_LIST(anjay_iid_t) *out) {\n    (void) obj_ptr;\n    assert(!*out);\n    if (!(*out = AVS_LIST_NEW_ELEMENT(anjay_iid_t))) {\n        demo_log(ERROR, \"out of memory\");\n        return -1;\n    }\n    **out = 0;\n    return 0;\n}\n\nstatic int\ninstall_object(anjay_demo_t *demo,\n               const anjay_dm_object_def_t **obj_ptr,\n               anjay_demo_object_get_instances_t *get_instances_func,\n               anjay_demo_object_notify_t *time_dependent_notify_func,\n               anjay_demo_object_deleter_t *release_func) {\n    if (!obj_ptr) {\n        return -1;\n    }\n\n    AVS_LIST(anjay_demo_object_t) *object_entry =\n            AVS_LIST_APPEND_PTR(&demo->objects);\n    assert(object_entry && !*object_entry);\n    *object_entry = AVS_LIST_NEW_ELEMENT(anjay_demo_object_t);\n    if (!*object_entry) {\n        release_func(obj_ptr);\n        return -1;\n    }\n\n    if (anjay_register_object(demo->anjay, obj_ptr)) {\n        release_func(obj_ptr);\n        AVS_LIST_DELETE(object_entry);\n        return -1;\n    }\n\n    (*object_entry)->obj_ptr = obj_ptr;\n    (*object_entry)->get_instances_func =\n            (get_instances_func ? get_instances_func : get_single_instance);\n    (*object_entry)->time_dependent_notify_func = time_dependent_notify_func;\n    (*object_entry)->release_func = release_func;\n    return 0;\n}\n\nstatic int\nadd_installed_object_update_handler(anjay_demo_t *demo,\n                                    anjay_update_handler_t *handler) {\n    assert(demo);\n\n    AVS_LIST(anjay_update_handler_t *) *handler_entry =\n            AVS_LIST_APPEND_PTR(&demo->installed_objects_update_handlers);\n    assert(handler_entry && !*handler_entry);\n    *handler_entry = AVS_LIST_NEW_ELEMENT(anjay_update_handler_t *);\n    **handler_entry = handler;\n\n    return 0;\n}\n\nstatic void reschedule_notify_time_dependent(anjay_demo_t *demo);\n\nstatic void notify_time_dependent_job(avs_sched_t *sched,\n                                      const void *demo_ptr) {\n    (void) sched;\n    anjay_demo_t *demo = *(anjay_demo_t *const *) demo_ptr;\n    anjay_demo_object_t *object;\n    AVS_LIST_FOREACH(object, demo->objects) {\n        if (object->time_dependent_notify_func) {\n            object->time_dependent_notify_func(demo->anjay, object->obj_ptr);\n        }\n    }\n    anjay_update_handler_t **update_handler;\n    AVS_LIST_FOREACH(update_handler, demo->installed_objects_update_handlers) {\n        (*update_handler)(demo->anjay);\n    }\n    reschedule_notify_time_dependent(demo);\n}\n\nstatic void reschedule_notify_time_dependent(anjay_demo_t *demo) {\n    avs_time_real_t now = avs_time_real_now();\n    avs_time_real_t next_full_second = {\n        .since_real_epoch = {\n            .seconds = now.since_real_epoch.seconds + 1,\n            .nanoseconds = 0\n        }\n    };\n    if (AVS_SCHED_DELAYED(anjay_get_scheduler(demo->anjay),\n                          &demo->notify_time_dependent_job,\n                          avs_time_real_diff(next_full_second, now),\n                          notify_time_dependent_job, &demo, sizeof(demo))) {\n        demo_log(ERROR, \"Could not reschedule notify_time_dependent_job\");\n    }\n}\n\n// !defined(ANJAY_WITH_CONN_STATUS_API)\n\n#ifdef ANJAY_WITH_CONN_STATUS_API\nstatic void\nserver_connection_status_change_callback(void *demo_,\n                                         anjay_t *anjay,\n                                         anjay_ssid_t ssid,\n                                         anjay_server_conn_status_t status) {\n    (void) demo_;\n    (void) anjay;\n    demo_log(INFO, \"Current status of the server with SSID %d is: %s\", ssid,\n             translate_server_connection_status_enum_to_str(status));\n}\n#endif // ANJAY_WITH_CONN_STATUS_API\n\n#ifdef ANJAY_WITH_SSL_ERROR_API\nstatic void ssl_error_callback(void *demo_,\n                               anjay_t *anjay,\n                               anjay_ssid_t ssid,\n                               avs_error_t err) {\n    (void) demo_;\n    (void) anjay;\n    demo_log(ERROR,\n             \"SSL error from server with SSID=%\" PRIu16 \": category=%\" PRIu16\n             \", code=%\" PRIu16,\n             ssid, err.category, err.code);\n}\n#endif // ANJAY_WITH_SSL_ERROR_API\n\nstatic void confirmable_notification_status_callback(\n        anjay_t *anjay,\n        anjay_ssid_t ssid,\n        const anjay_uri_path_t *observation_paths,\n        const size_t paths_count,\n        avs_error_t err) {\n    (void) anjay;\n\n    if (avs_is_err(err)) {\n        demo_log(WARNING,\n                 \"confirmable_notification_status_callback: There was some \"\n                 \"error during receiving acknowledgement/sending notification \"\n                 \"for server SSID %\" PRIu16 \", paths count %zu:\",\n                 ssid, paths_count);\n    } else {\n        demo_log(INFO,\n                 \"confirmable_notification_status_callback: Acknowledgement \"\n                 \"for notification was received for server SSID %\" PRIu16\n                 \", paths count %zu:\",\n                 ssid, paths_count);\n    }\n\n    char path[MAX_PATH_STRING_SIZE];\n    char *path_ptr = path;\n    int result;\n    for (size_t i = 0; i < paths_count; i++) {\n#ifdef ANJAY_WITH_LWM2M_GATEWAY\n        if (observation_paths[i].prefix[0] != '\\0') {\n            result = avs_simple_snprintf(path, ANJAY_GATEWAY_MAX_PREFIX_LEN + 1,\n                                         \"/%s\", observation_paths[i].prefix);\n            assert(result >= 0);\n            path_ptr += result;\n        }\n#endif // ANJAY_WITH_LWM2M_GATEWAY\n        if (observation_paths[i].ids[0] == ANJAY_ID_INVALID) {\n            result = avs_simple_snprintf(path_ptr,\n                                         MAX_PATH_STRING_SIZE_WO_PREFIX, \"/\");\n        } else if (observation_paths[i].ids[1] == ANJAY_ID_INVALID) {\n            result = avs_simple_snprintf(path_ptr,\n                                         MAX_PATH_STRING_SIZE_WO_PREFIX, \"/%d\",\n                                         observation_paths[i].ids[0]);\n        } else if (observation_paths[i].ids[2] == ANJAY_ID_INVALID) {\n            result = avs_simple_snprintf(path_ptr,\n                                         MAX_PATH_STRING_SIZE_WO_PREFIX,\n                                         \"/%d/%d\", observation_paths[i].ids[0],\n                                         observation_paths[i].ids[1]);\n        } else if (observation_paths[i].ids[3] == ANJAY_ID_INVALID) {\n            result = avs_simple_snprintf(\n                    path_ptr, MAX_PATH_STRING_SIZE_WO_PREFIX, \"/%d/%d/%d\",\n                    observation_paths[i].ids[0], observation_paths[i].ids[1],\n                    observation_paths[i].ids[2]);\n        } else {\n            result = avs_simple_snprintf(\n                    path_ptr, MAX_PATH_STRING_SIZE, \"/%d/%d/%d/%d\",\n                    observation_paths[i].ids[0], observation_paths[i].ids[1],\n                    observation_paths[i].ids[2], observation_paths[i].ids[3]);\n        }\n        assert(result >= 0);\n        demo_log(INFO, \"confirmable_notification_status_callback: %s\", path);\n    }\n}\n\nstatic int demo_init(anjay_demo_t *demo, cmdline_args_t *cmdline_args) {\n    demo->allocated_buffers = cmdline_args->allocated_buffers;\n    cmdline_args->allocated_buffers = NULL;\n\n    for (size_t i = 0; i < MAX_SERVERS; ++i) {\n        server_entry_t *entry = &cmdline_args->connection_args.servers[i];\n        if (entry->uri == NULL) {\n            break;\n        }\n\n        const char *derived_binding_mode =\n                derive_binding_mode_from_uri(entry->uri);\n        if (entry->binding_mode == NULL) {\n            entry->binding_mode = derived_binding_mode;\n        } else if (entry->binding_mode[0] != derived_binding_mode[0]\n                   && (strcmp(derived_binding_mode, \"U\"))) {\n            demo_log(ERROR,\n                     \"Provided binding mode is incompatible with the URI\");\n            return -1;\n        }\n    }\n\n    anjay_configuration_t config = {\n        .endpoint_name = cmdline_args->endpoint_name,\n        .udp_listen_port = cmdline_args->udp_listen_port,\n        .dtls_version = cmdline_args->dtls_version,\n        .in_buffer_size = (size_t) cmdline_args->inbuf_size,\n        .out_buffer_size = (size_t) cmdline_args->outbuf_size,\n        .msg_cache_size = (size_t) cmdline_args->msg_cache_size,\n#ifndef IP_MTU\n        .socket_config = {\n            .forced_mtu = 1492\n        },\n#endif\n        .confirmable_notifications = cmdline_args->confirmable_notifications,\n        .disable_legacy_server_initiated_bootstrap =\n                cmdline_args->disable_legacy_server_initiated_bootstrap,\n        .udp_tx_params = &cmdline_args->tx_params,\n        .udp_dtls_hs_tx_params = &cmdline_args->dtls_hs_tx_params,\n        .stored_notification_limit = cmdline_args->stored_notification_limit,\n        .prefer_hierarchical_formats =\n                cmdline_args->prefer_hierarchical_formats,\n        .use_connection_id = cmdline_args->use_connection_id,\n        .update_immediately_on_dm_change =\n                cmdline_args->update_immediately_on_dm_change,\n        .enable_self_notify = cmdline_args->enable_self_notify,\n        .connection_error_is_registration_failure =\n                cmdline_args->connection_error_is_registration_failure,\n        .default_tls_ciphersuites = {\n            .ids = cmdline_args->default_ciphersuites,\n            .num_ids = cmdline_args->default_ciphersuites_count\n        },\n#ifdef ANJAY_WITH_LWM2M11\n        .lwm2m_version_config = &cmdline_args->lwm2m_version_config,\n        .rebuild_client_cert_chain = cmdline_args->rebuild_client_cert_chain,\n#endif // ANJAY_WITH_LWM2M11\n        .confirmable_notification_status_cb =\n                confirmable_notification_status_callback,\n#if defined(ANJAY_WITH_LWM2M11) && defined(WITH_AVS_COAP_TCP)\n        .coap_tcp_request_timeout = cmdline_args->tcp_request_timeout,\n#endif // defined(ANJAY_WITH_LWM2M11) && defined(WITH_AVS_COAP_TCP)\n#ifdef ANJAY_WITH_CONN_STATUS_API\n        .server_connection_status_cb = server_connection_status_change_callback,\n        .server_connection_status_cb_arg = demo,\n#endif // ANJAY_WITH_CONN_STATUS_API\n#ifdef ANJAY_WITH_SSL_ERROR_API\n        .ssl_error_cb = ssl_error_callback,\n        .ssl_error_cb_arg = demo,\n#endif // ANJAY_WITH_SSL_ERROR_API\n#ifdef ANJAY_WITH_DOWNLOADER\n        .coap_downloader_retry_count =\n                cmdline_args->coap_downloader_retry_count,\n        .coap_downloader_retry_delay = cmdline_args->coap_downloader_retry_delay\n#endif // ANJAY_WITH_DOWNLOADER\n    };\n\n#ifdef ANJAY_WITH_LWM2M11\n    if (cmdline_args->pkix_trust_store) {\n        struct stat st;\n        if (!stat(cmdline_args->pkix_trust_store, &st) && S_ISDIR(st.st_mode)) {\n            config.trust_store_certs =\n                    avs_crypto_certificate_chain_info_from_path(\n                            cmdline_args->pkix_trust_store);\n        } else {\n            config.trust_store_certs =\n                    avs_crypto_certificate_chain_info_from_file(\n                            cmdline_args->pkix_trust_store);\n        }\n    }\n#endif // ANJAY_WITH_LWM2M11\n\n#ifdef ANJAY_WITH_MODULE_FW_UPDATE\n    const avs_net_security_info_t *fw_security_info_ptr = NULL;\n    if (cmdline_args->fw_security_info.mode != (avs_net_security_mode_t) -1) {\n        fw_security_info_ptr = &cmdline_args->fw_security_info;\n    }\n#endif // ANJAY_WITH_MODULE_FW_UPDATE\n\n#ifdef ANJAY_WITH_MODULE_ADVANCED_FW_UPDATE\n    const avs_net_security_info_t *advanced_fw_security_info_ptr = NULL;\n    if (cmdline_args->advanced_fw_security_info.mode\n            != (avs_net_security_mode_t) -1) {\n        advanced_fw_security_info_ptr =\n                &cmdline_args->advanced_fw_security_info;\n    }\n#endif // ANJAY_WITH_MODULE_ADVANCED_FW_UPDATE\n\n#if defined(ANJAY_WITH_MODULE_SW_MGMT) && defined(ANJAY_WITH_DOWNLOADER)\n    avs_net_security_info_t *sw_mgmt_security_info_ptr = NULL;\n    if (cmdline_args->sw_mgmt_security_info.mode\n            != (avs_net_security_mode_t) -1) {\n        sw_mgmt_security_info_ptr = &cmdline_args->sw_mgmt_security_info;\n    }\n#endif // defined(ANJAY_WITH_MODULE_SW_MGMT) && defined(ANJAY_WITH_DOWNLOADER)\n\n    demo->connection_args = &cmdline_args->connection_args;\n#ifdef AVS_COMMONS_STREAM_WITH_FILE\n#    ifdef ANJAY_WITH_ATTR_STORAGE\n    demo->attr_storage_file = cmdline_args->attr_storage_file;\n#    endif // ANJAY_WITH_ATTR_STORAGE\n#    ifdef AVS_COMMONS_WITH_AVS_PERSISTENCE\n    demo->dm_persistence_file = cmdline_args->dm_persistence_file;\n#    endif // AVS_COMMONS_WITH_AVS_PERSISTENCE\n           // defined(ANJAY_WITH_LWM2M11) && defined(WITH_AVS_COAP_TCP)\n#endif     // AVS_COMMONS_STREAM_WITH_FILE\n    { demo->anjay = anjay_new(&config); }\n    if (!demo->anjay\n#ifdef ANJAY_WITH_MODULE_ACCESS_CONTROL\n            || anjay_access_control_install(demo->anjay)\n#endif // ANJAY_WITH_MODULE_ACCESS_CONTROL\n    ) {\n        return -1;\n    }\n\n    if (\n#ifdef WITH_DEMO_USE_STANDALONE_OBJECTS\n                !(demo->security_obj_ptr =\n                          standalone_security_object_install(demo->anjay))\n                    || !(demo->server_obj_ptr =\n                                 standalone_server_object_install(demo->anjay))\n#else  // WITH_DEMO_USE_STANDALONE_OBJECTS\n                anjay_security_object_install(demo->anjay)\n                    || anjay_server_object_install(demo->anjay)\n#endif // WITH_DEMO_USE_STANDALONE_OBJECTS\n#ifdef ANJAY_WITH_MODULE_IPSO_OBJECTS\n                    || install_accelerometer_object(demo->anjay)\n                    || add_installed_object_update_handler(\n                               demo, accelerometer_update_handler)\n                    || install_push_button_object(demo->anjay)\n                    || install_temperature_object(demo->anjay)\n                    || add_installed_object_update_handler(\n                               demo, temperature_update_handler)\n#endif // ANJAY_WITH_MODULE_IPSO_OBJECTS\n                    || install_object(demo, location_object_create(), NULL,\n                                      location_notify_time_dependent,\n                                      location_object_release)\n                    || install_object(demo, apn_conn_profile_object_create(),\n                                      apn_conn_profile_get_instances, NULL,\n                                      apn_conn_profile_object_release)\n                    || install_object(\n                               demo, binary_app_data_container_object_create(),\n                               binary_app_data_container_get_instances, NULL,\n                               binary_app_data_container_object_release)\n                    || install_object(\n                               demo, cell_connectivity_object_create(demo),\n                               NULL, NULL, cell_connectivity_object_release)\n                    || install_object(demo, cm_object_create(), NULL,\n                                      cm_notify_time_dependent,\n                                      cm_object_release)\n                    || install_object(demo, cs_object_create(), NULL, NULL,\n                                      cs_object_release)\n                    || install_object(\n                               demo, download_diagnostics_object_create(), NULL,\n                               NULL, download_diagnostics_object_release)\n                    || install_object(demo,\n                                      device_object_create(\n                                              cmdline_args->endpoint_name),\n                                      NULL, device_notify_time_dependent,\n                                      device_object_release)\n                    || install_object(demo, ext_dev_info_object_create(), NULL,\n                                      ext_dev_info_notify_time_dependent,\n                                      ext_dev_info_object_release)\n                    || install_object(demo, geopoints_object_create(demo),\n                                      geopoints_get_instances,\n                                      geopoints_notify_time_dependent,\n                                      geopoints_object_release)\n#ifndef _WIN32\n                    || install_object(demo, ip_ping_object_create(), NULL, NULL,\n                                      ip_ping_object_release)\n#endif // _WIN32\n                    || install_object(\n                               demo, test_object_create(), test_get_instances,\n                               test_notify_time_dependent, test_object_release)\n                    || install_object(demo, portfolio_object_create(),\n                                      portfolio_get_instances, NULL,\n                                      portfolio_object_release)\n                    || install_object(demo, event_log_object_create(), NULL,\n                                      NULL, event_log_object_release)) {\n        return -1;\n    }\n#ifdef ANJAY_WITH_LWM2M_GATEWAY\n    if (cmdline_args->lwm2m_gateway_enabled) {\n        if (lwm2m_gateway_setup(demo->anjay)) {\n            return -1;\n        }\n    }\n#endif // ANJAY_WITH_LWM2M_GATEWAY\n\n    if (cmdline_args->location_csv\n            && location_open_csv(demo_find_object(demo, DEMO_OID_LOCATION),\n                                 cmdline_args->location_csv,\n                                 cmdline_args->location_update_frequency_s)) {\n        return -1;\n    }\n\n    bool dm_persistence_restored = false;\n#if defined(AVS_COMMONS_WITH_AVS_PERSISTENCE) \\\n        && defined(AVS_COMMONS_STREAM_WITH_FILE)\n    if (cmdline_args->dm_persistence_file) {\n        avs_stream_t *data =\n                avs_stream_file_create(cmdline_args->dm_persistence_file,\n                                       AVS_STREAM_FILE_READ);\n        if (!data\n#    ifdef WITH_DEMO_USE_STANDALONE_OBJECTS\n                || avs_is_err(standalone_security_object_restore(\n                           demo->security_obj_ptr, data))\n                || avs_is_err(standalone_server_object_restore(\n                           demo->server_obj_ptr, data))\n#    else  // WITH_DEMO_USE_STANDALONE_OBJECTS\n                || avs_is_err(anjay_security_object_restore(demo->anjay, data))\n                || avs_is_err(anjay_server_object_restore(demo->anjay, data))\n#    endif // WITH_DEMO_USE_STANDALONE_OBJECTS\n#    ifdef ANJAY_WITH_MODULE_ACCESS_CONTROL\n                || avs_is_err(anjay_access_control_restore(demo->anjay, data))\n#    endif // ANJAY_WITH_MODULE_ACCESS_CONTROL\n        ) {\n            demo_log(ERROR, \"Cannot restore data model from file %s\",\n                     cmdline_args->dm_persistence_file);\n        } else {\n            dm_persistence_restored = true;\n        }\n        avs_stream_cleanup(&data);\n    }\n#endif // defined(AVS_COMMONS_WITH_AVS_PERSISTENCE) &&\n       // defined(AVS_COMMONS_STREAM_WITH_FILE)\n\n    if (!dm_persistence_restored) {\n        demo_reload_servers(demo);\n\n#ifdef ANJAY_WITH_MODULE_FACTORY_PROVISIONING\n        if (cmdline_args->provisioning_file) {\n            avs_stream_t *data =\n                    avs_stream_file_create(cmdline_args->provisioning_file,\n                                           AVS_STREAM_FILE_READ);\n            avs_error_t err = AVS_OK;\n            if (!data) {\n                err = avs_errno(AVS_EIO);\n            } else {\n                err = anjay_factory_provision(demo->anjay, data);\n                avs_stream_cleanup(&data);\n            }\n            if (avs_is_err(err)) {\n                demo_log(ERROR, \"Cannot provision client from file %s\",\n                         cmdline_args->provisioning_file);\n                return -1;\n            }\n            demo_log(DEBUG, \"Factory provisioning complete\");\n        }\n#endif // ANJAY_WITH_MODULE_FACTORY_PROVISIONING\n    }\n\n#ifdef ANJAY_WITH_MODULE_FW_UPDATE\n    // Install Firmware Update Object at the end, because installed Device\n    // Object and Server Object's instances may be needed.\n    if (firmware_update_install(demo->anjay, &demo->fw_update,\n                                cmdline_args->fw_updated_marker_path,\n                                fw_security_info_ptr,\n                                cmdline_args->fwu_tx_params_modified\n                                        ? &cmdline_args->fwu_tx_params\n                                        : NULL,\n                                cmdline_args->fwu_tcp_request_timeout,\n                                cmdline_args->fw_update_delayed_result,\n                                cmdline_args->prefer_same_socket_downloads,\n#    ifdef ANJAY_WITH_SEND\n                                cmdline_args->fw_update_use_send,\n#    endif // ANJAY_WITH_SEND\n                                cmdline_args->fw_update_auto_suspend)) {\n        return -1;\n    }\n#endif // ANJAY_WITH_MODULE_FW_UPDATE\n\n#ifdef ANJAY_WITH_MODULE_ADVANCED_FW_UPDATE\n    // Install Advanced Firmware Update Object at the end, because installed\n    // Device Object and Server Object's instances may be needed.\n    if (advanced_firmware_update_install(\n                demo->anjay,\n                demo->advanced_fw_update_logic_table,\n                cmdline_args->advanced_fw_updated_marker_path,\n                advanced_fw_security_info_ptr,\n                cmdline_args->advanced_fwu_tx_params_modified\n                        ? &cmdline_args->advanced_fwu_tx_params\n                        : NULL,\n                cmdline_args->advanced_fwu_tcp_request_timeout,\n                cmdline_args->advanced_fw_update_delayed_result,\n                cmdline_args->prefer_same_socket_downloads,\n                cmdline_args->original_img_file_path,\n#    ifdef ANJAY_WITH_SEND\n                cmdline_args->advanced_fw_update_use_send,\n#    endif // ANJAY_WITH_SEND\n                cmdline_args->advanced_fw_update_auto_suspend)) {\n        return -1;\n    }\n#endif // ANJAY_WITH_MODULE_ADVANCED_FW_UPDATE\n\n#ifdef ANJAY_WITH_MODULE_SW_MGMT\n    if (sw_mgmt_install(\n                demo->anjay, &demo->sw_mgmt_common, demo->sw_mgmt_table,\n                cmdline_args->sw_mgmt_persistence_file,\n                cmdline_args->prefer_same_socket_downloads,\n                cmdline_args->sw_mgmt_delayed_first_instance_install_result,\n                cmdline_args->sw_mgmt_terminate_after_downloading,\n                cmdline_args->sw_mgmt_disable_repeated_activation_deactivation\n#    ifdef ANJAY_WITH_DOWNLOADER\n                ,\n                sw_mgmt_security_info_ptr,\n                cmdline_args->sw_mgmt_tx_params_modified\n                        ? &cmdline_args->sw_mgmt_tx_params\n                        : NULL,\n                &cmdline_args->sw_mgmt_tcp_request_timeout,\n                cmdline_args->sw_mgmt_auto_suspend\n#    endif // ANJAY_WITH_DOWNLOADER\n                )) {\n        return -1;\n    }\n#endif // ANJAY_WITH_MODULE_SW_MGMT\n\n#ifdef ANJAY_WITH_MODULE_ACCESS_CONTROL\n    if (!dm_persistence_restored\n            && (add_default_access_entries(demo)\n                || add_access_entries(demo, cmdline_args))) {\n        return -1;\n    }\n#endif // ANJAY_WITH_MODULE_ACCESS_CONTROL\n\n#if defined(ANJAY_WITH_ATTR_STORAGE) && defined(AVS_COMMONS_STREAM_WITH_FILE)\n    if (cmdline_args->attr_storage_file) {\n        avs_stream_t *data =\n                avs_stream_file_create(cmdline_args->attr_storage_file,\n                                       AVS_STREAM_FILE_READ);\n        if (!data\n                || avs_is_err(anjay_attr_storage_restore(demo->anjay, data))) {\n            demo_log(\n                    ERROR,\n                    \"Cannot restore attribute storage persistence from file %s\",\n                    cmdline_args->attr_storage_file);\n        }\n        // no success log there, as Attribute Storage module logs it by itself\n        avs_stream_cleanup(&data);\n    }\n#endif // defined(ANJAY_WITH_ATTR_STORAGE) &&\n       // defined(AVS_COMMONS_STREAM_WITH_FILE)\n\n    reschedule_notify_time_dependent(demo);\n\n    if (cmdline_args->start_offline\n            && anjay_transport_enter_offline(demo->anjay,\n                                             ANJAY_TRANSPORT_SET_ALL)) {\n        return -1;\n    }\n\n#ifdef WITH_DEMO_TRAFFIC_INTERCEPTOR\n    if (cmdline_args->traffic_intercept_path\n            && interceptor_init(cmdline_args->traffic_intercept_path,\n                                cmdline_args->endpoint_name)) {\n        return -1;\n    }\n#endif // WITH_DEMO_TRAFFIC_INTERCEPTOR\n\n    return 0;\n}\n\nstatic anjay_demo_t *demo_new(cmdline_args_t *cmdline_args) {\n    anjay_demo_t *demo = (anjay_demo_t *) avs_calloc(1, sizeof(anjay_demo_t));\n    if (!demo) {\n        return NULL;\n    }\n\n    if (demo_init(demo, cmdline_args)) {\n        demo_delete(demo);\n        return NULL;\n    }\n\n    return demo;\n}\n\nstatic void *event_loop_func(void *demo) {\n    // NOTE: This log is expected by our test suite (see Lwm2mTest.start_demo())\n    // Please don't remove.\n    demo_log(INFO, \"*** ANJAY DEMO STARTUP FINISHED ***\");\n    int result = anjay_event_loop_run(\n            ((anjay_demo_t *) demo)->anjay,\n            avs_time_duration_from_scalar(100, AVS_TIME_MS));\n    // force the stdin reading loop to finish\n    close(STDIN_FILENO);\n    return (void *) (intptr_t) result;\n}\n\nstatic void interrupt_event_loop_job(avs_sched_t *sched, const void *demo_ptr) {\n    (void) sched;\n    anjay_demo_t *demo = *(anjay_demo_t *const *) demo_ptr;\n    anjay_event_loop_interrupt(demo->anjay);\n}\n\nstatic void log_extended_handler(avs_log_level_t level,\n                                 const char *module,\n                                 const char *file,\n                                 unsigned line,\n                                 const char *message) {\n    static const char *log_levels[] = { \"TRC\", \"DBG\", \"INF\", \"WRN\", \"ERR\", \"\" };\n    char *name = strrchr(file, '/');\n\n    if (name) {\n        char file_name[30];\n        snprintf(file_name, sizeof(file_name), \"%s:%d\", name + 1, line);\n        fprintf(stderr, \"%s: |%-15s| %-30s| %s\\n\", log_levels[level], module,\n                file_name, message);\n    } else {\n        fprintf(stderr, \"%s: |%-15s| %s:%d| %s\\n\", log_levels[level], module,\n                file, line, message);\n    }\n}\n\nstatic void\nlog_handler(avs_log_level_t level, const char *module, const char *message) {\n    (void) level;\n    (void) module;\n\n    char timebuf[128];\n    avs_time_real_t now = avs_time_real_now();\n    time_t seconds = now.since_real_epoch.seconds;\n\n    struct tm *now_tm = localtime(&seconds);\n    assert(now_tm);\n    strftime(timebuf, sizeof(timebuf), \"%Y-%m-%d %H:%M:%S\", now_tm);\n\n    fprintf(stderr, \"%s.%06d %s\\n\", timebuf,\n            (int) now.since_real_epoch.nanoseconds / 1000, message);\n}\n\nstatic void cmdline_args_cleanup(cmdline_args_t *cmdline_args) {\n#ifdef ANJAY_WITH_MODULE_ACCESS_CONTROL\n    AVS_LIST_CLEAR(&cmdline_args->access_entries);\n#endif // ANJAY_WITH_MODULE_ACCESS_CONTROL\n    avs_free(cmdline_args->default_ciphersuites);\n    AVS_LIST_CLEAR(&cmdline_args->allocated_buffers);\n}\n\nint main(int argc, char *argv[]) {\n#ifndef _WIN32\n    /*\n     * The demo application implements mock firmware update with execv() call\n     * on the new LwM2M client application. As a direct consequence, all file\n     * descriptors from the original process are inherited, even though we will\n     * never use most of them. To free resources associated with these\n     * descriptors and avoid weird behavior caused by multiple sockets bound to\n     * the same local port (*), we close all unknown descriptors before\n     * continuing. Only 0 (stdin), 1 (stdout) and 2 (stderr) are left open.\n     *\n     * (*) For example, Linux does load-balancing between UDP sockets that\n     * reuse the same local address and port. See `man 7 socket` or\n     * http://man7.org/linux/man-pages/man7/socket.7.html .\n     * https://stackoverflow.com/a/14388707/2339636 contains more detailed\n     * info on SO_REUSEADDR/SO_REUSEPORT behavior on various systems.\n     */\n    for (int fd = 3, maxfd = (int) sysconf(_SC_OPEN_MAX); fd < maxfd; ++fd) {\n        close(fd);\n    }\n#endif // WIN32\n#ifdef AVS_COMMONS_NET_WITH_MBEDTLS_SSLKEYLOG\n    avs_stream_t *ssl_key_log_file =\n            avs_stream_file_create(SSLKEYLOGFILE, AVS_STREAM_FILE_WRITE);\n    avs_mbedtls_set_sslkeylog_stream(ssl_key_log_file);\n#endif // AVS_COMMONS_NET_WITH_MBEDTLS_SSLKEYLOG\n    /*\n     * If, as a result of a single poll() more than a single line is read into\n     * stdin buffer, we will end up handling just a single command and then\n     * wait for another poll() trigger which may never happen - because all the\n     * data from fd 0 was already read, and it's just waiting to be read from\n     * the buffer.\n     *\n     * This problematic behavior can be reproduced by sending a \"\\ncommand\\n\"\n     * string to the demo application with a single write() syscall.\n     *\n     * Disabling stdin buffering prevents Python tests from hanging randomly.\n     * While generally that is not a good idea performance-wise, demo commands\n     * do not require passing large amounts of data, so it is fine in our use\n     * case.\n     */\n    setbuf(stdin, NULL);\n    setbuf(stdout, NULL);\n    setbuf(stderr, NULL);\n\n    avs_log_set_handler(log_handler);\n    avs_log_set_default_level(AVS_LOG_TRACE);\n    avs_log_set_level(demo, AVS_LOG_DEBUG);\n    avs_log_set_level(avs_sched, AVS_LOG_DEBUG);\n    avs_log_set_level(anjay_dm, AVS_LOG_DEBUG);\n\n    if (argv_store(argc, argv)) {\n        return -1;\n    }\n\n    cmdline_args_t cmdline_args;\n    if (demo_parse_argv(&cmdline_args, argc, argv)) {\n        return -1;\n    }\n\n    if (cmdline_args.alternative_logger) {\n        avs_log_set_extended_handler(log_extended_handler);\n    }\n\n#ifdef SIGXFSZ\n    // do not terminate after exceeding file size\n    signal(SIGXFSZ, SIG_IGN);\n#endif // SIGXFSZ\n\n    anjay_demo_t *demo = demo_new(&cmdline_args);\n    if (!demo) {\n        cmdline_args_cleanup(&cmdline_args);\n        return -1;\n    }\n\n    pthread_t event_loop_thread;\n    if (!pthread_create(&event_loop_thread, NULL, event_loop_func, demo)) {\n        if (!cmdline_args.disable_stdin) {\n            union {\n                demo_command_invocation_t invocation;\n                char buf[offsetof(demo_command_invocation_t, cmd) + 500];\n            } invocation = {\n                .invocation.demo = demo\n            };\n            while (!feof(stdin) && !ferror(stdin)) {\n                if (fgets(invocation.invocation.cmd,\n                          sizeof(invocation)\n                                  - offsetof(demo_command_invocation_t, cmd),\n                          stdin)) {\n                    while (true) {\n                        size_t buf_len = strlen(invocation.invocation.cmd);\n                        if (!buf_len) {\n                            break;\n                        }\n                        char *last_char =\n                                &invocation.invocation.cmd[buf_len - 1];\n                        if (*last_char == '\\r' || *last_char == '\\n') {\n                            *last_char = '\\0';\n                        } else {\n                            break;\n                        }\n                    }\n                    demo_command_dispatch(&invocation.invocation);\n                }\n            }\n            // NOTE: anjay_event_loop_interrupt() intentionally does not work if\n            // called before the event loop actually starts; it means that we\n            // can't call it directly here, as it would lead to a race condition\n            // if stdin is closed immediately (e.g. is /dev/null).\n            if (AVS_SCHED_NOW(anjay_get_scheduler(demo->anjay), NULL,\n                              interrupt_event_loop_job, &demo, sizeof(demo))) {\n                demo_log(ERROR, \"Could not schedule interrupt_event_loop_job\");\n            }\n        }\n\n        pthread_join(event_loop_thread, NULL);\n    }\n\n    demo_delete(demo);\n    cmdline_args_cleanup(&cmdline_args);\n    avs_log_reset();\n#ifdef AVS_COMMONS_NET_WITH_MBEDTLS_SSLKEYLOG\n    avs_stream_cleanup(&ssl_key_log_file);\n#endif // AVS_COMMONS_NET_WITH_MBEDTLS_SSLKEYLOG\n    return 0;\n}\n"
  },
  {
    "path": "demo/demo.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef DEMO_H\n#define DEMO_H\n\n#include <stdatomic.h>\n\n#include <anjay/access_control.h>\n#include <anjay/anjay.h>\n#include <anjay/anjay_config.h>\n\n#include <avsystem/commons/avs_list.h>\n#include <avsystem/commons/avs_stream_file.h>\n#include <avsystem/commons/avs_time.h>\n\n#ifdef ANJAY_WITH_MODULE_FW_UPDATE\n#    include \"firmware_update.h\"\n#endif // ANJAY_WITH_MODULE_FW_UPDATE\n\n#ifdef ANJAY_WITH_MODULE_ADVANCED_FW_UPDATE\n#    include \"advanced_firmware_update.h\"\n#endif // ANJAY_WITH_MODULE_ADVANCED_FW_UPDATE\n\n#ifdef ANJAY_WITH_MODULE_SW_MGMT\n#    include \"software_mgmt.h\"\n#endif // ANJAY_WITH_MODULE_SW_MGMT\n#include \"objects.h\"\n\ntypedef int anjay_demo_object_get_instances_t(const anjay_dm_object_def_t **,\n                                              AVS_LIST(anjay_iid_t) *);\ntypedef void anjay_demo_object_deleter_t(const anjay_dm_object_def_t **);\ntypedef void anjay_demo_object_notify_t(anjay_t *,\n                                        const anjay_dm_object_def_t **);\n\ntypedef struct {\n    const anjay_dm_object_def_t **obj_ptr;\n    anjay_demo_object_get_instances_t *get_instances_func;\n    anjay_demo_object_notify_t *time_dependent_notify_func;\n    anjay_demo_object_deleter_t *release_func;\n} anjay_demo_object_t;\n\ntypedef void anjay_update_handler_t(anjay_t *anjay);\n\nstruct anjay_demo_struct {\n    anjay_t *anjay;\n\n    AVS_LIST(anjay_demo_allocated_buffer_t) allocated_buffers;\n    server_connection_args_t *connection_args;\n#ifdef AVS_COMMONS_STREAM_WITH_FILE\n#    ifdef ANJAY_WITH_ATTR_STORAGE\n    const char *attr_storage_file;\n#    endif // ANJAY_WITH_ATTR_STORAGE\n#    ifdef AVS_COMMONS_WITH_AVS_PERSISTENCE\n    const char *dm_persistence_file;\n#    endif // AVS_COMMONS_WITH_AVS_PERSISTENCE\n#endif     // AVS_COMMONS_STREAM_WITH_FILE\n\n    avs_sched_handle_t notify_time_dependent_job;\n#ifdef ANJAY_WITH_MODULE_FW_UPDATE\n    fw_update_logic_t fw_update;\n#endif // ANJAY_WITH_MODULE_FW_UPDATE\n#ifdef ANJAY_WITH_MODULE_ADVANCED_FW_UPDATE\n    advanced_fw_update_logic_t\n            advanced_fw_update_logic_table[FW_UPDATE_IID_IMAGE_SLOTS];\n#endif // ANJAY_WITH_MODULE_ADVANCED_FW_UPDATE\n#ifdef ANJAY_WITH_MODULE_SW_MGMT\n    sw_mgmt_common_logic_t sw_mgmt_common;\n    sw_mgmt_logic_t sw_mgmt_table[SW_MGMT_PACKAGE_COUNT];\n#endif // ANJAY_WITH_MODULE_SW_MGMT\n\n#ifdef WITH_DEMO_USE_STANDALONE_OBJECTS\n    const anjay_dm_object_def_t **security_obj_ptr;\n    const anjay_dm_object_def_t **server_obj_ptr;\n#endif // WITH_DEMO_USE_STANDALONE_OBJECTS\n\n    AVS_LIST(anjay_demo_object_t) objects;\n\n    AVS_LIST(anjay_update_handler_t *) installed_objects_update_handlers;\n\n    // for testing purposes only: causes a Registration Update to be scheduled\n    // immediately before calling anjay_delete\n    bool schedule_update_on_exit;\n};\n\nconst anjay_dm_object_def_t **demo_find_object(anjay_demo_t *demo,\n                                               anjay_oid_t oid);\n\nvoid demo_reload_servers(anjay_demo_t *demo);\nvoid demo_advance_time(avs_time_duration_t duration);\n\n#endif\n"
  },
  {
    "path": "demo/demo_args.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include \"demo_args.h\"\n#include \"demo.h\"\n\n#include <assert.h>\n#include <ctype.h>\n#include <errno.h>\n#include <getopt.h>\n#include <inttypes.h>\n#include <string.h>\n\n#ifndef _WIN32\n#    include <sys/ioctl.h>\n#    include <unistd.h>\n#endif // _WIN32\n\n#include <avsystem/commons/avs_memory.h>\n\n#include \"net_traffic_interceptor.h\"\n\n#define DEFAULT_PSK_IDENTITY \"sesame\"\n#define DEFAULT_PSK_KEY \"password\"\n\n#ifdef ANJAY_WITH_LWM2M11\n#    ifdef ANJAY_WITH_LWM2M12\n#        define DEFAULT_MAX_LWM2M_VER \"1.2\"\n#    else // ANJAY_WITH_LWM2M12\n#        define DEFAULT_MAX_LWM2M_VER \"1.1\"\n#    endif // ANJAY_WITH_LWM2M12\n#endif     // ANJAY_WITH_LWM2M11\n\nstatic const cmdline_args_t DEFAULT_CMDLINE_ARGS = {\n    .connection_args = {\n        .servers[0] = {\n            .security_iid = ANJAY_ID_INVALID,\n            .server_iid = ANJAY_ID_INVALID,\n            .id = 1,\n            .binding_mode = NULL,\n#ifdef ANJAY_WITH_LWM2M11\n            .retry_count = 1,\n            .retry_timer = 0,\n            .sequence_retry_count = 1,\n            .sequence_delay_timer = 0,\n            .certificate_usage = AVS_NET_SOCKET_DANE_DOMAIN_ISSUED_CERTIFICATE,\n#endif // ANJAY_WITH_LWM2M11\n        },\n#ifdef ANJAY_WITH_BOOTSTRAP\n        .bootstrap_holdoff_s = 0,\n        .bootstrap_timeout_s = 0,\n#endif // ANJAY_WITH_BOOTSTRAP\n        .lifetime = 86400,\n        .security_mode = ANJAY_SECURITY_NOSEC\n    },\n    .location_csv = NULL,\n    .location_update_frequency_s = 1,\n    .inbuf_size = 4000,\n    .outbuf_size = 4000,\n    .msg_cache_size = 0,\n#ifdef ANJAY_WITH_MODULE_FW_UPDATE\n#    if defined(AVS_COMMONS_WITH_AVS_PERSISTENCE) \\\n            && defined(AVS_COMMONS_STREAM_WITH_FILE)\n    .fw_updated_marker_path = \"/tmp/anjay-fw-updated\",\n#    endif // defined(AVS_COMMONS_WITH_AVS_PERSISTENCE) &&\n           // defined(AVS_COMMONS_STREAM_WITH_FILE)\n    .fw_security_info = {\n        .mode = (avs_net_security_mode_t) -1\n    },\n#endif // ANJAY_WITH_MODULE_FW_UPDATE\n\n#ifdef ANJAY_WITH_MODULE_ADVANCED_FW_UPDATE\n    .advanced_fw_security_info = {\n        .mode = (avs_net_security_mode_t) -1\n    },\n#endif // ANJAY_WITH_MODULE_ADVANCED_FW_UPDATE\n\n#ifdef ANJAY_WITH_MODULE_SW_MGMT\n    .sw_mgmt_delayed_first_instance_install_result = UINT8_MAX,\n#    if defined(AVS_COMMONS_WITH_AVS_PERSISTENCE) \\\n            && defined(AVS_COMMONS_STREAM_WITH_FILE)\n    .sw_mgmt_persistence_file = \"/tmp/anjay-sw-mgmt\",\n    .sw_mgmt_terminate_after_downloading = false,\n#    endif // defined(AVS_COMMONS_WITH_AVS_PERSISTENCE) &&\n           // defined(AVS_COMMONS_STREAM_WITH_FILE)\n#    ifdef ANJAY_WITH_DOWNLOADER\n    .sw_mgmt_security_info = {\n        .mode = (avs_net_security_mode_t) -1\n    },\n#    endif // ANJAY_WITH_DOWNLOADER\n#endif     // ANJAY_WITH_MODULE_SW_MGMT\n\n#ifdef AVS_COMMONS_STREAM_WITH_FILE\n#    ifdef ANJAY_WITH_ATTR_STORAGE\n    .attr_storage_file = NULL,\n#    endif // ANJAY_WITH_ATTR_STORAGE\n#    ifdef AVS_COMMONS_WITH_AVS_PERSISTENCE\n    .dm_persistence_file = NULL,\n#    endif // AVS_COMMONS_WITH_AVS_PERSISTENCE\n#endif     // AVS_COMMONS_STREAM_WITH_FILE\n    .disable_legacy_server_initiated_bootstrap = false,\n#ifdef AVS_COMMONS_STREAM_WITH_FILE\n#endif // AVS_COMMONS_STREAM_WITH_FILE\n#ifdef ANJAY_WITH_MODULE_FACTORY_PROVISIONING\n    .provisioning_file = NULL,\n#endif // ANJAY_WITH_MODULE_FACTORY_PROVISIONING\n    .tx_params = ANJAY_COAP_DEFAULT_UDP_TX_PARAMS,\n    .dtls_hs_tx_params = ANJAY_DTLS_DEFAULT_UDP_HS_TX_PARAMS,\n#ifdef ANJAY_WITH_MODULE_FW_UPDATE\n    .fwu_tx_params_modified = false,\n    .fwu_tx_params = ANJAY_COAP_DEFAULT_UDP_TX_PARAMS,\n    .fwu_tcp_request_timeout = { 0, -1 },\n// AVS_TIME_DURATION_INVALID\n#endif // ANJAY_WITH_MODULE_FW_UPDATE\n#ifdef ANJAY_WITH_MODULE_ADVANCED_FW_UPDATE\n    .advanced_fwu_tx_params_modified = false,\n    .advanced_fwu_tx_params = ANJAY_COAP_DEFAULT_UDP_TX_PARAMS,\n    .advanced_fwu_tcp_request_timeout = { 0, -1 },\n#endif // ANJAY_WITH_MODULE_ADVANCED_FW_UPDATE\n#ifdef ANJAY_WITH_MODULE_SW_MGMT\n    .sw_mgmt_tx_params_modified = false,\n    .sw_mgmt_tx_params = ANJAY_COAP_DEFAULT_UDP_TX_PARAMS,\n    .sw_mgmt_tcp_request_timeout = { 0, -1 },\n// AVS_TIME_DURATION_INVALID\n#endif // ANJAY_WITH_MODULE_SW_MGMT\n#ifdef ANJAY_WITH_LWM2M11\n    .lwm2m_version_config = {\n        .minimum_version = ANJAY_LWM2M_VERSION_1_0,\n        .maximum_version =\n#    ifdef ANJAY_WITH_LWM2M12\n                ANJAY_LWM2M_VERSION_1_2\n#    else  // ANJAY_WITH_LWM2M12\n                ANJAY_LWM2M_VERSION_1_1\n#    endif // ANJAY_WITH_LWM2M12\n    },\n#endif // ANJAY_WITH_LWM2M11\n    .prefer_hierarchical_formats = false,\n    .update_immediately_on_dm_change = false,\n    .enable_self_notify = false,\n    .connection_error_is_registration_failure = false,\n    .prefer_same_socket_downloads = false,\n};\n\nstatic int parse_security_mode(const char *mode_string,\n                               anjay_security_mode_t *out_mode) {\n    if (!mode_string) {\n        return -1;\n    }\n\n    static const struct {\n        const char *name;\n        anjay_security_mode_t value;\n    } MODES[] = {\n        // clang-format off\n        { \"psk\",   ANJAY_SECURITY_PSK         },\n        { \"rpk\",   ANJAY_SECURITY_RPK         },\n        { \"cert\",  ANJAY_SECURITY_CERTIFICATE },\n        { \"nosec\", ANJAY_SECURITY_NOSEC       },\n        { \"est\",   ANJAY_SECURITY_EST         },\n        // clang-format on\n    };\n\n    for (size_t i = 0; i < AVS_ARRAY_SIZE(MODES); ++i) {\n        if (!strcmp(mode_string, MODES[i].name)) {\n            *out_mode = MODES[i].value;\n            return 0;\n        }\n    }\n\n    char allowed_modes[64];\n    size_t offset = 0;\n    for (size_t i = 0; i < AVS_ARRAY_SIZE(MODES); ++i) {\n        int written =\n                snprintf(allowed_modes + offset, sizeof(allowed_modes) - offset,\n                         \" %s\", MODES[i].name);\n        if (written < 0 || (size_t) written >= sizeof(allowed_modes) - offset) {\n            demo_log(ERROR, \"could not enumerate available security modes\");\n            allowed_modes[0] = '\\0';\n            break;\n        }\n\n        offset += (size_t) written;\n    }\n\n    demo_log(ERROR, \"unrecognized security mode %s (expected one of:%s)\",\n             mode_string, allowed_modes);\n    return -1;\n}\n\nstatic int parse_tls_version(const char *str,\n                             avs_net_ssl_version_t *out_version) {\n    assert(str);\n    if (strcmp(str, \"default\") == 0) {\n        *out_version = AVS_NET_SSL_VERSION_DEFAULT;\n        return 0;\n    } else if (strcmp(str, \"SSLv23\") == 0) {\n        *out_version = AVS_NET_SSL_VERSION_SSLv2_OR_3;\n        return 0;\n    } else if (strcmp(str, \"SSLv2\") == 0 || strcmp(str, \"SSLv2.0\") == 0\n               || strcmp(str, \"2.0\") == 0) {\n        *out_version = AVS_NET_SSL_VERSION_SSLv2;\n        return 0;\n    } else if (strcmp(str, \"SSLv3\") == 0 || strcmp(str, \"SSLv3.0\") == 0\n               || strcmp(str, \"3.0\") == 0) {\n        *out_version = AVS_NET_SSL_VERSION_SSLv3;\n        return 0;\n    } else if (strcmp(str, \"TLSv1\") == 0 || strcmp(str, \"TLSv1.0\") == 0\n               || strcmp(str, \"1.0\") == 0) {\n        *out_version = AVS_NET_SSL_VERSION_TLSv1;\n        return 0;\n    } else if (strcmp(str, \"TLSv1.1\") == 0 || strcmp(str, \"1.1\") == 0) {\n        *out_version = AVS_NET_SSL_VERSION_TLSv1_1;\n        return 0;\n    } else if (strcmp(str, \"TLSv1.2\") == 0 || strcmp(str, \"1.2\") == 0) {\n        *out_version = AVS_NET_SSL_VERSION_TLSv1_2;\n        return 0;\n    } else if (strcmp(str, \"TLSv1.3\") == 0 || strcmp(str, \"1.3\") == 0) {\n        *out_version = AVS_NET_SSL_VERSION_TLSv1_3;\n        return 0;\n    } else {\n        demo_log(ERROR, \"Invalid TLS version: %s\", str);\n        return -1;\n    }\n}\n\nstatic size_t get_screen_width(void) {\n#ifndef _WIN32\n    struct winsize ws;\n    if (!ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws)) {\n        return ws.ws_col;\n    }\n#endif // _WIN32\n    // fallback to 80 columns as default\n    return 80;\n}\n\nstatic void\nprint_wrapped(const char *str, size_t padding, size_t screen_width) {\n    const char *str_end = str + strlen(str);\n    do {\n        for (size_t i = 0; i < padding; ++i) {\n            putchar(' ');\n        }\n\n        const char *line_start = str;\n        const char *line_end = NULL;\n        bool first_word = true;\n        while (str < str_end) {\n            str += strspn(str, AVS_SPACES);\n            str += strcspn(str, AVS_SPACES);\n            if (first_word\n                    || (size_t) (str - line_start) + padding < screen_width) {\n                line_end = str;\n                first_word = false;\n            } else {\n                break;\n            }\n        }\n        str = line_end + strspn(line_end, AVS_SPACES);\n        fwrite(line_start, 1, (size_t) (line_end - line_start), stdout);\n        putchar('\\n');\n    } while (str < str_end);\n}\n\nstatic void\nformat_wrapped(size_t padding, size_t screen, const char *fmt, ...) {\n    char buf[1024];\n    va_list ap;\n    va_start(ap, fmt);\n    (void) vsnprintf(buf, sizeof(buf), fmt, ap);\n    va_end(ap);\n    print_wrapped(buf, padding, screen);\n}\n\nstatic bool stdout_supports_color(void) {\n#ifdef _WIN32\n    return false;\n#else  // _WIN32\n    if (!isatty(STDOUT_FILENO)) {\n        return false;\n    }\n    const char *no_color = getenv(\"NO_COLOR\");\n    if (no_color && *no_color) {\n        return false;\n    }\n    const char *term = getenv(\"TERM\");\n    if (!term || !*term || !strcmp(term, \"dumb\")) {\n        return false;\n    }\n    return true;\n#endif // _WIN32\n}\n\nstatic void print_help_short(const char *progname) {\n    size_t screen = get_screen_width();\n    bool ansi_on = stdout_supports_color();\n    const char *s_dim = ansi_on ? \"\\x1b[2m\" : \"\";\n    const char *s_cyan = ansi_on ? \"\\x1b[36m\" : \"\";\n    const char *s_reset = ansi_on ? \"\\x1b[0m\" : \"\";\n    const char *s_bold_cyan = ansi_on ? \"\\x1b[1;36m\" : \"\";\n\n    puts(\"Anjay Demo - quick help\\n\");\n\n    printf(\"%sSynopsis:%s\\n\", s_cyan, s_reset);\n    format_wrapped(2, screen,\n                   \"  %s [OPTIONS] --endpoint-name <name> --server-uri \"\n                   \"<coap(s)://host:port> \"\n                   \"[--security-mode nosec|psk|cert|est] [...]\",\n                   progname);\n    puts(\"\");\n\n    printf(\"%sQuick examples:%s\\n\", s_cyan, s_reset);\n\n    printf(\"%s\", s_dim);\n    format_wrapped(2, screen, \"  # No security (management)\");\n    printf(\"%s\", s_reset);\n    format_wrapped(4, screen,\n                   \"  %s --endpoint-name my-device \"\n                   \"--security-mode nosec \"\n                   \"--server-uri coap://eu.iot.avsystem.cloud:5683 \"\n                   \"--lifetime 60\",\n                   progname);\n#ifdef ANJAY_WITH_BOOTSTRAP\n    printf(\"%s\", s_dim);\n    format_wrapped(2, screen, \"  # No security (bootstrap)\");\n    printf(\"%s\", s_reset);\n    format_wrapped(4, screen,\n                   \"  %s --endpoint-name my-device \"\n                   \"--security-mode nosec \"\n                   \"--server-uri coap://eu.iot.avsystem.cloud:5693 \"\n                   \"--bootstrap\",\n                   progname);\n#endif // ANJAY_WITH_BOOTSTRAP\n    puts(\"\");\n\n    printf(\"%s\", s_dim);\n    format_wrapped(2, screen,\n                   \"  # PSK (identity/key as ASCII strings, management)\");\n    printf(\"%s\", s_reset);\n    format_wrapped(4, screen,\n                   \"  %s --endpoint-name my-device \"\n                   \"--security-mode psk \"\n                   \"--identity-as-string my-device \"\n                   \"--key-as-string 1234 \"\n                   \"--server-uri coaps://eu.iot.avsystem.cloud:5684 \"\n                   \"--lifetime 60\",\n                   progname);\n\n    printf(\"%s\", s_dim);\n    format_wrapped(2, screen, \"  # PSK (identity/key as hex, management)\");\n    printf(\"%s\", s_reset);\n    format_wrapped(4, screen,\n                   \"  %s --endpoint-name my-device \"\n                   \"--security-mode psk \"\n                   \"--identity 6d792d646576696365 \"\n                   \"--key 31323334 \"\n                   \"--server-uri coaps://eu.iot.avsystem.cloud:5684 \"\n                   \"--lifetime 60\",\n                   progname);\n#ifdef ANJAY_WITH_BOOTSTRAP\n    printf(\"%s\", s_dim);\n    format_wrapped(2, screen, \"  # PSK (bootstrap)\");\n    printf(\"%s\", s_reset);\n    format_wrapped(4, screen,\n                   \"  %s --endpoint-name my-device \"\n                   \"--security-mode psk \"\n                   \"--identity-as-string my-device \"\n                   \"--key-as-string 1234 \"\n                   \"--server-uri coaps://eu.iot.avsystem.cloud:5694 \"\n                   \"--bootstrap\",\n                   progname);\n#endif // ANJAY_WITH_BOOTSTRAP\n    puts(\"\");\n\n    printf(\"%s\", s_dim);\n    format_wrapped(2, screen, \"  # Certificates (management)\");\n    printf(\"%s\", s_reset);\n    format_wrapped(4, screen,\n                   \"  %s --endpoint-name my-device \"\n                   \"--security-mode cert \"\n                   \"--client-cert-file ./client.crt.der \"\n                   \"--key-file ./client.key.der \"\n                   \"--server-uri coaps://eu.iot.avsystem.cloud:5684 \"\n                   \"--lifetime 60\",\n                   progname);\n#ifdef ANJAY_WITH_BOOTSTRAP\n    printf(\"%s\", s_dim);\n    format_wrapped(2, screen, \"  # Certificates (bootstrap)\");\n    printf(\"%s\", s_reset);\n    format_wrapped(4, screen,\n                   \"  %s --endpoint-name my-device \"\n                   \"--security-mode cert \"\n                   \"--client-cert-file ./client.crt.der \"\n                   \"--key-file ./client.key.der \"\n                   \"--server-uri coaps://eu.iot.avsystem.cloud:5694 \"\n                   \"--bootstrap\",\n                   progname);\n#endif // ANJAY_WITH_BOOTSTRAP\n    puts(\"\");\n\n    printf(\"%sCommonly used options:%s\\n\", s_cyan, s_reset);\n    format_wrapped(2, screen,\n                   \"  -e, --endpoint-name <name>              Endpoint name.\");\n    format_wrapped(2, screen,\n                   \"  -u, --server-uri <URI>                  LwM2M server URI \"\n                   \"(coap:// | coaps:// | coaps+tcp://).\");\n    format_wrapped(\n            2, screen,\n            \"  -s, --security-mode <mode>              nosec | psk | cert\"\n            \".\");\n    format_wrapped(2, screen,\n                   \"      --identity-as-string <s>            PSK identity as \"\n                   \"ASCII (psk).\");\n    format_wrapped(2, screen,\n                   \"      --key-as-string <s>                 PSK key as ASCII \"\n                   \"(psk).\");\n    format_wrapped(2, screen,\n                   \"  -i, --identity <hex>                    PSK identity in \"\n                   \"hex (psk).\");\n    format_wrapped(\n            2, screen,\n            \"  -k, --key <hex>                         PSK key in hex (psk).\");\n    format_wrapped(2, screen,\n                   \"  -C, --client-cert-file <file>           Client \"\n                   \"certificate (DER) (cert\"\n                   \").\");\n    format_wrapped(2, screen,\n                   \"  -K, --key-file <file>                   Private key \"\n                   \"(DER/PKCS#8) (cert\"\n                   \").\");\n#ifdef ANJAY_WITH_BOOTSTRAP\n    format_wrapped(\n            2, screen,\n            \"  -b, --bootstrap[=client-initiated-only] Use Bootstrap Server.\");\n#endif // ANJAY_WITH_BOOTSTRAP\n    format_wrapped(2, screen,\n                   \"  -l, --lifetime <s>                      Registration \"\n                   \"lifetime (default: 86400).\");\n    format_wrapped(2, screen,\n                   \"      --use-connection-id                 Enable DTLS \"\n                   \"connection_id.\");\n    format_wrapped(2, screen,\n                   \"      --ciphersuites <list>               Limit (D)TLS \"\n                   \"ciphersuites, e.g. 0xC02C[,0x..].\");\n    format_wrapped(2, screen,\n                   \"      --alternative-logger                Enable \"\n                   \"alternative logger output.\");\n\n    puts(\"\");\n\n    printf(\"%s\", s_bold_cyan);\n    format_wrapped(\n            0, screen,\n            \"For the complete list of options and detailed explanations, run:\\n\"\n            \"  %s --help=full\",\n            progname);\n    printf(\"%s\\n\", s_reset);\n}\n\nstatic void print_help_full(const struct option *options) {\n    const struct {\n        int opt_val;\n        const char *args;\n        const char *default_value;\n        const char *help;\n    } HELP_INFO[] = {\n#ifdef ANJAY_WITH_MODULE_ACCESS_CONTROL\n        { 'a', \"/OID/IID,SSID,ACCESS_MASK\", NULL,\n          \"create ACL entry for specified /OID/IID and SSID\" },\n#endif // ANJAY_WITH_MODULE_ACCESS_CONTROL\n#ifdef ANJAY_WITH_BOOTSTRAP\n        { 'b', \"client-initiated-only\", NULL,\n          \"treat first URI as Bootstrap Server. If the optional \"\n          \"\\\"client-initiated-only\\\" option is specified, the legacy LwM2M \"\n          \"1.0-style Server-Initiated bootstrap mode is not available.\" },\n        { 'H', \"SECONDS\", \"0\",\n          \"number of seconds to wait before attempting Client Initiated \"\n          \"Bootstrap.\" },\n        { 'T', \"SECONDS\", \"0\",\n          \"number of seconds to keep the Bootstrap Server Account for after \"\n          \"successful bootstrapping, or 0 for infinity.\" },\n#endif // ANJAY_WITH_BOOTSTRAP\n        { 'e', \"URN\", DEFAULT_CMDLINE_ARGS.endpoint_name,\n          \"endpoint name to use.\" },\n        { 'h', NULL, NULL, \"show this message and exit.\" },\n#ifndef _WIN32\n        { 't', NULL, NULL,\n          \"disables standard input. Useful for running the client as a \"\n          \"daemon.\" },\n#endif // _WIN32\n        { 'l', \"SECONDS\", \"86400\",\n          \"set registration lifetime. If SECONDS == 0, the lifetime is \"\n          \"infinite and register updates are not being sent automatically. \"\n          \"Negative values are not allowed.\" },\n        { 'L', \"MAX_NOTIFICATIONS\", \"0\",\n          \"set limit of queued notifications in queue/offline mode. 0: \"\n          \"unlimited; >0: keep that much newest ones\" },\n        { 'c', \"CSV_FILE\", NULL, \"file to load location CSV from\" },\n        { 'f', \"SECONDS\", \"1\", \"location update frequency in seconds\" },\n        { 'p', \"PORT\", NULL, \"bind all sockets to the specified UDP port.\" },\n        { 'i', \"PSK identity (psk mode) or Public Certificate (cert mode)\",\n          NULL, \"Both are specified as hexlified strings\" },\n        { 'C', \"CLIENT_CERT_FILE\", \"$(dirname $0)/../certs/client.crt.der\",\n          \"DER-formatted client certificate file to load. Mutually exclusive \"\n          \"with -i\" },\n        { 'k', \"PSK key (psk mode) or Private Certificate (cert mode)\", NULL,\n          \"Both are specified as hexlified strings\" },\n        { 'K', \"PRIVATE_KEY_FILE\", \"$(dirname $0)/../certs/client.key.der\",\n          \"DER-formatted PKCS#8 private key complementary to the certificate \"\n          \"specified with -C. Mutually exclusive with -k\" },\n        { 'P', \"SERVER_PUBLIC_KEY_FILE\",\n          \"$(dirname $0)/../certs/server.crt.der\",\n          \"DER-formatted server public key file to load.\" },\n        { 'q', \"BINDING_MODE=UQ\", NULL,\n          \"set the Binding Mode to use for the currently configured server. \"\n          \"If Binding Mode is not set by this flag, client tries to derive it \"\n          \"from URI and if it cannot, it uses the default value 'U'\" },\n        { 's', \"MODE\", NULL,\n          \"set security mode, one of: psk rpk cert nosec. \"\n          \"Note: only affects coaps:// and coaps+*:// URLs\" },\n        { 'u', \"URI\", NULL,\n          \"server URI to use. N consecutive URIs will create N servers \"\n          \"enumerated from 1 to N.\" },\n        { 'D', \"IID\", NULL,\n          \"enforce particular Security Instance IID for last configured \"\n          \"server.\" },\n        { 'd', \"IID\", NULL,\n          \"enforce particular Server Instance IID for last configured server. \"\n          \"Ignored if last configured server is an LwM2M Bootstrap Server.\" },\n        { 'I', \"SIZE\", \"4000\",\n          \"Nonnegative integer representing maximum size of an incoming CoAP \"\n          \"packet the client should be able to handle.\" },\n        { 'O', \"SIZE\", \"4000\",\n          \"Nonnegative integer representing maximum size of a non-BLOCK CoAP \"\n          \"packet the client should be able to send.\" },\n        { '$', \"SIZE\", \"0\",\n          \"Size, in bytes, of a buffer reserved for caching sent responses to \"\n          \"detect retransmissions. Setting it to 0 disables caching \"\n          \"mechanism.\" },\n        { 'N', NULL, NULL,\n          \"Send notifications as Confirmable messages by default\" },\n#ifdef ANJAY_WITH_LWM2M11\n        { 'v', \"VERSION\", \"1.0\", \"Lowest version of LwM2M Enabler to allow\" },\n        { 'V', \"VERSION\", DEFAULT_MAX_LWM2M_VER,\n          \"Highest version of LwM2M Enabler to allow\" },\n#endif // ANJAY_WITH_LWM2M11\n#ifdef ANJAY_WITH_MODULE_FW_UPDATE\n        { 'r', \"RESULT\", NULL,\n          \"If specified and nonzero, initializes the Firmware Update object in \"\n          \"UPDATING state, and sets the result to given value after a short \"\n          \"while\" },\n#    if defined(AVS_COMMONS_WITH_AVS_PERSISTENCE) \\\n            && defined(AVS_COMMONS_STREAM_WITH_FILE)\n        { 256, \"PATH\", DEFAULT_CMDLINE_ARGS.fw_updated_marker_path,\n          \"File path to use as a marker for persisting firmware update state\" },\n#    endif // defined(AVS_COMMONS_WITH_AVS_PERSISTENCE) &&\n           // defined(AVS_COMMONS_STREAM_WITH_FILE)\n        { 257, \"CERT_FILE\", NULL,\n          \"Require certificate validation against specified file when \"\n          \"downloading firmware over encrypted channels\" },\n        { 258, \"CERT_DIR\", NULL,\n          \"Require certificate validation against files in specified path when \"\n          \"downloading firmware over encrypted channels; note that the TLS \"\n          \"backend may impose specific requirements for file names and \"\n          \"formats\" },\n        { 259, \"PSK identity\", NULL,\n          \"Download firmware over encrypted channels using PSK-mode encryption \"\n          \"with the specified identity (provided as hexlified string); must be \"\n          \"used together with --fw-psk-key\" },\n        { 260, \"PSK key\", NULL,\n          \"Download firmware over encrypted channels using PSK-mode encryption \"\n          \"with the specified key (provided as hexlified string); must be used \"\n          \"together with --fw-psk-identity\" },\n#endif // ANJAY_WITH_MODULE_FW_UPDATE\n#if defined(ANJAY_WITH_ATTR_STORAGE) && defined(AVS_COMMONS_STREAM_WITH_FILE)\n        { 261, \"PERSISTENCE_FILE\", NULL,\n          \"File to load attribute storage data from at startup, and \"\n          \"store it at shutdown\" },\n#endif // defined(ANJAY_WITH_ATTR_STORAGE) &&\n       // defined(AVS_COMMONS_STREAM_WITH_FILE)\n#ifdef ANJAY_WITH_MODULE_FACTORY_PROVISIONING\n        { 'F', \"PROVISIONING_FILE\", NULL,\n          \"File where factory provisioning data is contained.\" },\n#endif // ANJAY_WITH_MODULE_FACTORY_PROVISIONING\n        { 267, \"ACK_RANDOM_FACTOR\", \"1.5\",\n          \"Configures ACK_RANDOM_FACTOR (defined in RFC7252)\" },\n        { 268, \"ACK_TIMEOUT\", \"2.0\",\n          \"Configures ACK_TIMEOUT (defined in RFC7252) in seconds\" },\n        { 269, \"MAX_RETRANSMIT\", \"4\",\n          \"Configures MAX_RETRANSMIT (defined in RFC7252)\" },\n        { 270, \"DTLS_HS_RETRY_WAIT_MIN\", \"1\",\n          \"Configures minimum period of time to wait before sending first \"\n          \"DTLS HS retransmission\" },\n        { 271, \"DTLS_HS_RETRY_WAIT_MAX\", \"60\",\n          \"Configures maximum period of time to wait (after last \"\n          \"retransmission) before giving up on handshake completely\" },\n#ifdef ANJAY_WITH_MODULE_FW_UPDATE\n        { 272, \"ACK_RANDOM_FACTOR\", \"1.5\",\n          \"Configures ACK_RANDOM_FACTOR (defined in RFC7252) for firmware \"\n          \"update\" },\n        { 273, \"ACK_TIMEOUT\", \"2.0\",\n          \"Configures ACK_TIMEOUT (defined in RFC7252) in seconds for firmware \"\n          \"update\" },\n        { 274, \"MAX_RETRANSMIT\", \"4\",\n          \"Configures MAX_RETRANSMIT (defined in RFC7252) for firmware \"\n          \"update\" },\n#endif // ANJAY_WITH_MODULE_FW_UPDATE\n        { 275, NULL, NULL,\n          \"Sets the library to use hierarchical content formats by default for \"\n          \"all responses.\" },\n#ifdef ANJAY_WITH_LWM2M11\n        { 276, \"SNI\", \"server hostname\",\n          \"Sets the Server Name Indication value for currently configured \"\n          \"server.\" },\n#endif // ANJAY_WITH_LWM2M11\n        { 277, NULL, NULL, \"Enables DTLS connection_id extension.\" },\n        { 278, \"CIPHERSUITE[,CIPHERSUITE...]\", \"TLS library defaults\",\n          \"Sets the ciphersuites to be used by default for (D)TLS \"\n          \"connections.\" },\n#ifdef ANJAY_WITH_LWM2M11\n        { 279, \"RETRY_COUNT\", \"1\",\n          \"Configures the number of registration retry sequences for a last \"\n          \"server\" },\n        { 280, \"RETRY_TIMER\", \"0\",\n          \"Configures the exponential delay between registration retries\" },\n        { 281, \"SEQUENCE_RETRY_COUNT\", \"1\",\n          \"Configures the number of registration sequences\" },\n        { 282, \"SEQUENCE_DELAY_TIMER\", \"86400\",\n          \"Configures the delay between consecutive communication sequences\" },\n#endif // ANJAY_WITH_LWM2M11\n        { 283, NULL, NULL,\n          \"Configures preference of re-using existing LwM2M CoAP contexts for \"\n          \"firmware and software download\" },\n        { 284, \"NSTART\", \"1\", \"Configures NSTART (defined in RFC7252)\" },\n#if defined(ANJAY_WITH_SEND) && defined(ANJAY_WITH_MODULE_FW_UPDATE)\n        { 287, NULL, NULL,\n          \"Enables using LwM2M Send to report state and result of firmware \"\n          \"update\" },\n#endif // defined(ANJAY_WITH_SEND) && defined(ANJAY_WITH_MODULE_FW_UPDATE)\n#ifdef ANJAY_WITH_LWM2M11\n        { 288, \"TRUST_STORE_PATH\", NULL,\n          \"Path (file or directory) to use as the trust store for \"\n          \"PKIX verification\" },\n#endif // ANJAY_WITH_LWM2M11\n#if defined(AVS_COMMONS_WITH_AVS_PERSISTENCE) \\\n        && defined(AVS_COMMONS_STREAM_WITH_FILE)\n        { 289, \"PERSISTENCE_FILE\", NULL,\n          \"File to load Server, Security and Access Control object contents at \"\n          \"startup, and store it at shutdown\" },\n#endif // defined(AVS_COMMONS_WITH_AVS_PERSISTENCE) &&\n       // defined(AVS_COMMONS_STREAM_WITH_FILE)\n#ifdef ANJAY_WITH_SECURITY_STRUCTURED\n        { 298, NULL, NULL,\n          \"Causes security credentials to be loaded as external security info \"\n          \"objects instead of loading them into internal buffers\" },\n#endif // ANJAY_WITH_SECURITY_STRUCTURED\n#ifdef ANJAY_WITH_LWM2M11\n        { 299, NULL, NULL,\n          \"Enables rebuilding of client certificate chain based on the trust \"\n          \"store\" },\n#endif // ANJAY_WITH_LWM2M11\n        { 306, NULL, NULL,\n          \"Enable alternative logger as a showcase of extended logger \"\n          \"feature.\" },\n        { 307, NULL, NULL,\n          \"Provide identity from ASCII string (see -i parameter for more \"\n          \"details)\" },\n        { 308, NULL, NULL,\n          \"Provide key from ASCII string (see -k parameter for more details)\" },\n        { 317, \"VERSION\", \"TLS library default\",\n          \"Minimum (D)TLS version to use.\" },\n#if defined(ANJAY_WITH_LWM2M11) && defined(WITH_AVS_COAP_TCP)\n        { 319, \"TIMEOUT\", \"30.0\",\n          \"Time in seconds to wait for incoming response after sending a TCP \"\n          \"request\" },\n        { 320, NULL, NULL,\n          \"Send the Update message immediately when Object Instances are \"\n          \"created or deleted.\" },\n        { 321, NULL, NULL,\n          \"Send the Notify messages as a result of a server action (e.g. \"\n          \"Write) even to the initiating server.\" },\n#endif // defined(ANJAY_WITH_LWM2M11) && defined(WITH_AVS_COAP_TCP)\n#ifdef ANJAY_WITH_MODULE_ADVANCED_FW_UPDATE\n        { 322, \"ADDITIONAL_IMG_FILE_PATH\", NULL,\n          \"Path to additional img binary file. Used to compare with obtained \"\n          \"through advanced firmware update procedure\" },\n#    if defined(AVS_COMMONS_WITH_AVS_PERSISTENCE) \\\n            && defined(AVS_COMMONS_STREAM_WITH_FILE)\n        { 323, \"AFU_PERSISTENCE_FILE\", NULL,\n          \"Path to file used to persist advanced firmware update data, \"\n          \"if file not exists, it will be created\" },\n#    endif // defined(AVS_COMMONS_WITH_AVS_PERSISTENCE) &&\n           // defined(AVS_COMMONS_STREAM_WITH_FILE)\n        { 324, \"CERT_FILE\", NULL,\n          \"Require certificate validation against specified file when \"\n          \"downloading firmware over encrypted channels. This argument is \"\n          \"used by Advanced Firmware Update.\" },\n        { 325, \"RESULT\", NULL,\n          \"If specified and nonzero, initializes the Advanced Firmware Update \"\n          \"object in \"\n          \"UPDATING state, and sets the result to given value after a short \"\n          \"while\" },\n#    ifdef ANJAY_WITH_SEND\n        { 326, NULL, NULL,\n          \"Enables using LwM2M Send to report state and result of advanced \"\n          \"firmware update\" },\n#    endif // ANJAY_WITH_SEND\n        { 327, \"ACK_TIMEOUT\", \"2.0\",\n          \"Configures ACK_TIMEOUT (defined in RFC7252) in seconds for advanced \"\n          \"firmware update\" },\n#endif // ANJAY_WITH_MODULE_ADVANCED_FW_UPDATE\n        { 328, NULL, NULL,\n          \"Enter offline mode before starting the event loop.\" },\n#ifdef ANJAY_WITH_MODULE_FW_UPDATE\n        { 329, NULL, NULL,\n          \"Start the Firmware Update downloads in suspended mode and resume \"\n          \"them just when they are requested. Useful for testing purposes \"\n          \"only.\" },\n#endif // ANJAY_WITH_MODULE_FW_UPDATE\n#ifdef ANJAY_WITH_MODULE_ADVANCED_FW_UPDATE\n        { 330, NULL, NULL,\n          \"Start the Advanced Firmware Update downloads in suspended mode and \"\n          \"resume them just in time. Useful for testing purposes only.\" },\n        { 331, \"PSK identity\", NULL,\n          \"Download firmware over encrypted channels using PSK-mode encryption \"\n          \"with the specified identity (provided as hexlified string); this \"\n          \"argument is used by Advanced Firmware Update and must be used \"\n          \"together with --afu-psk-key\" },\n        { 332, \"PSK key\", NULL,\n          \"Download firmware over encrypted channels using PSK-mode encryption \"\n          \"with the specified key (provided as hexlified string); this \"\n          \"argument is used by Advanced Firmware Update and must be used \"\n          \"together with --afu-psk-identity\" },\n#endif // ANJAY_WITH_MODULE_ADVANCED_FW_UPDATE\n#ifdef ANJAY_WITH_MODULE_FW_UPDATE\n        { 333, \"TIMEOUT\", NULL,\n          \"Request timeout (in seconds) to use for firmware updates performed \"\n          \"over CoAP+TCP and HTTP\" },\n#endif // ANJAY_WITH_MODULE_FW_UPDATE\n#ifdef ANJAY_WITH_MODULE_ADVANCED_FW_UPDATE\n        { 334, \"TIMEOUT\", NULL,\n          \"Request timeout (in seconds) to use for Advanced Firmware Update \"\n          \"downloads performed over CoAP+TCP and HTTP\" },\n#endif // ANJAY_WITH_MODULE_ADVANCED_FW_UPDATE\n#ifdef ANJAY_WITH_MODULE_SW_MGMT\n        { 335, \"RESULT\", NULL,\n          \"If specified, initializes first Software Management \"\n          \"object in DELIVERED state, and sets the result to given value after \"\n          \"a short \"\n          \"while\" },\n        { 336, NULL, NULL,\n          \"Terminate the program after downloading package for instance 0. \"\n          \"Useful for testing purposes only.\" },\n        { 337, NULL, NULL,\n          \"Do not execute code related to activation/deactivation and return \"\n          \"an error from the activation/deactivation handler when the package \"\n          \"is already activated/deactivated.\" },\n#    if defined(AVS_COMMONS_WITH_AVS_PERSISTENCE) \\\n            && defined(AVS_COMMONS_STREAM_WITH_FILE)\n        { 338, \"PATH\", DEFAULT_CMDLINE_ARGS.sw_mgmt_persistence_file,\n          \"Persistence file path\" },\n#    endif // defined(AVS_COMMONS_WITH_AVS_PERSISTENCE) &&\n           // defined(AVS_COMMONS_STREAM_WITH_FILE)\n#    ifdef ANJAY_WITH_DOWNLOADER\n        { 339, \"CERT_FILE\", NULL,\n          \"Require certificate validation against specified file when \"\n          \"downloading software over encrypted channels\" },\n        { 340, \"CERT_DIR\", NULL,\n          \"Require certificate validation against files in specified path when \"\n          \"downloading software over encrypted channels; note that the TLS \"\n          \"backend may impose specific requirements for file names and \"\n          \"formats\" },\n        { 341, \"PSK identity\", NULL,\n          \"Download software over encrypted channels using PSK-mode encryption \"\n          \"with the specified identity (provided as hexlified string); must be \"\n          \"used together with --sw-mgmt-psk-key\" },\n        { 342, \"PSK key\", NULL,\n          \"Download software over encrypted channels using PSK-mode encryption \"\n          \"with the specified key (provided as hexlified string); must be used \"\n          \"together with --sw-mgmt-psk-identity\" },\n        { 343, \"ACK_RANDOM_FACTOR\", \"1.5\",\n          \"Configures ACK_RANDOM_FACTOR (defined in RFC7252) for software \"\n          \"update\" },\n        { 344, \"ACK_TIMEOUT\", \"2.0\",\n          \"Configures ACK_TIMEOUT (defined in RFC7252) in seconds for software \"\n          \"update\" },\n        { 345, \"MAX_RETRANSMIT\", \"4\",\n          \"Configures MAX_RETRANSMIT (defined in RFC7252) for software \"\n          \"update\" },\n        { 346, NULL, NULL,\n          \"Start the software downloads in suspended mode and resume \"\n          \"them just when they are requested. Useful for testing purposes \"\n          \"only.\" },\n        { 347, \"TIMEOUT\", NULL,\n          \"Request timeout (in seconds) to use for software download performed \"\n          \"over CoAP+TCP and HTTP\" },\n#    endif // ANJAY_WITH_DOWNLOADER\n#endif     // ANJAY_WITH_MODULE_SW_MGMT\n        { 348, NULL, NULL,\n          \"Treat failures of the \\\"connect\\\" socket operation (e.g. (D)TLS \"\n          \"handshake failures) as a failed LwM2M Register operation.\" },\n#ifdef ANJAY_WITH_LWM2M_GATEWAY\n        { 'g', NULL, NULL,\n          \"Enable LwM2M Gateway funcitonality and register example End Devices \"\n          \"with their objects\" },\n#endif // ANJAY_WITH_LWM2M_GATEWAY\n#ifdef ANJAY_WITH_DOWNLOADER\n        { 349, \"RETRY COUNT\", NULL, \"Number of CoAP downloader retry\" },\n        { 350, \"RETRY DELAY\", NULL,\n          \"Delay (in seconds) between CoAP downloader retry\" },\n#endif // ANJAY_WITH_DOWNLOADER\n#ifdef ANJAY_WITH_LWM2M11\n        { 351, \"CERTIFICATE USAGE\", \"3\",\n          \"Certificate usage to set for the last configured server\" },\n#endif // ANJAY_WITH_LWM2M11\n#ifdef WITH_DEMO_TRAFFIC_INTERCEPTOR\n        { 357, \"INTERCEPTOR UNIX SOCKET PATH\", NULL,\n          \"Required by traffic interceptor. Path to a unix socket where \"\n          \"traffic data \"\n          \"is written to. Each packet contains json with intercepted \"\n          \"datagram.\" },\n#endif // WITH_DEMO_TRAFFIC_INTERCEPTOR\n    };\n\n    const size_t screen_width = get_screen_width();\n\n    puts(\"Anjay Demo - full list of options:\\n\");\n    for (size_t i = 0; options[i].name || options[i].val; ++i) {\n        assert(i < AVS_ARRAY_SIZE(HELP_INFO));\n        assert(HELP_INFO[i].opt_val == options[i].val);\n\n        printf(\"  \");\n        if (isprint(options[i].val)) {\n            printf(\"-%c, \", options[i].val);\n        }\n\n        int chars_written = 0;\n        printf(\"--%s%n\", options[i].name, &chars_written);\n\n        const char *args = HELP_INFO[i].args ? HELP_INFO[i].args : \"\";\n        const char *arg_prefix = \"\";\n        const char *arg_suffix = \"\";\n        if (options[i].has_arg == required_argument) {\n            arg_prefix = \" \";\n        } else if (options[i].has_arg == optional_argument) {\n            arg_prefix = \"[=\";\n            arg_suffix = \"]\";\n        }\n        printf(\"%s%s%s\\n\", arg_prefix, args, arg_suffix);\n\n        print_wrapped(HELP_INFO[i].help, 6, screen_width);\n        if (HELP_INFO[i].default_value) {\n            printf(\"      (default: %s)\\n\", HELP_INFO[i].default_value);\n        }\n        printf(\"\\n\");\n    }\n}\n\nstatic int parse_i32(const char *str, int32_t *out_value) {\n    long long_value;\n    if (demo_parse_long(str, &long_value) || long_value < INT32_MIN\n            || long_value > INT32_MAX) {\n        demo_log(ERROR,\n                 \"value out of range: expected 32-bit signed value, got %s\",\n                 str);\n        return -1;\n    }\n\n    *out_value = (int32_t) long_value;\n    return 0;\n}\n\nstatic int parse_u32(const char *str, uint32_t *out_value) {\n    long long_value;\n    if (demo_parse_long(str, &long_value) || long_value < 0\n            || long_value > UINT32_MAX) {\n        demo_log(ERROR,\n                 \"value out of range: expected 32-bit unsigned value, got %s\",\n                 str);\n        return -1;\n    }\n\n    *out_value = (uint32_t) long_value;\n    return 0;\n}\n\nstatic int parse_u16(const char *str, uint16_t *out_value) {\n    long long_value;\n    if (demo_parse_long(str, &long_value) || long_value < 0\n            || long_value > UINT16_MAX) {\n        demo_log(ERROR,\n                 \"value out of range: expected 16-bit unsigned value, got %s\",\n                 str);\n        return -1;\n    }\n\n    *out_value = (uint16_t) long_value;\n    return 0;\n}\n\nstatic int parse_size(const char *str, size_t *out_value) {\n    long long_value;\n    if (demo_parse_long(str, &long_value) || long_value < 0\n#if SIZE_MAX < LONG_MAX\n            || long_value > SIZE_MAX\n#endif\n    ) {\n        demo_log(ERROR,\n                 \"value out of range: expected %d-bit unsigned value, got %s\",\n                 (int) (CHAR_BIT * sizeof(size_t)), str);\n        return -1;\n    }\n\n    *out_value = (size_t) long_value;\n    return 0;\n}\n\nstatic int parse_double(const char *str, double *out_value) {\n    assert(str);\n    errno = 0;\n    char *endptr = NULL;\n    *out_value = strtod(str, &endptr);\n    if (!*str || isspace((unsigned char) *str) || errno || !endptr || *endptr) {\n        return -1;\n    }\n    return 0;\n}\n\nstatic int parse_hexstring(cmdline_args_t *cmdline_args,\n                           const char *str,\n                           uint8_t **out,\n                           size_t *out_size) {\n    if (!str) {\n        return -1;\n    }\n\n    size_t length = strlen(str);\n    if (length % 2 || !length) {\n        return -1;\n    }\n    if (*out) {\n        return -1;\n    }\n    AVS_LIST(anjay_demo_allocated_buffer_t) buffer = (AVS_LIST(\n            anjay_demo_allocated_buffer_t)) AVS_LIST_NEW_BUFFER(length / 2);\n    if (!buffer) {\n        return -1;\n    }\n    *out = (uint8_t *) buffer;\n    *out_size = 0;\n    const char *curr = str;\n    uint8_t *data = *out;\n    while (*curr) {\n        unsigned value;\n        if (sscanf(curr, \"%2x\", &value) != 1 || (uint8_t) value != value) {\n            AVS_LIST_DELETE(&buffer);\n            return -1;\n        }\n        *data++ = (uint8_t) value;\n        curr += 2;\n    }\n    *out_size = length / 2;\n    AVS_LIST_INSERT(&cmdline_args->allocated_buffers, buffer);\n    return 0;\n}\n\nstatic void build_getopt_string(const struct option *options,\n                                char *buffer,\n                                size_t buffer_size) {\n    const struct option *curr_opt = options;\n    char *getopt_string_ptr = buffer;\n\n    memset(buffer, 0, buffer_size);\n\n    while (curr_opt->val != 0) {\n        if (curr_opt->val > UCHAR_MAX) {\n            curr_opt++;\n            continue;\n        }\n\n        assert(getopt_string_ptr - buffer < (ptrdiff_t) buffer_size - 1);\n        *getopt_string_ptr++ = (char) curr_opt->val;\n\n        int colons = curr_opt->has_arg;\n        assert(colons >= 0 && colons <= 2); // 2 colons signify optional arg\n        while (colons-- > 0) {\n            assert(getopt_string_ptr - buffer < (ptrdiff_t) buffer_size - 1);\n            *getopt_string_ptr++ = ':';\n        }\n\n        ++curr_opt;\n    }\n}\n\nstatic int clone_buffer(cmdline_args_t *cmdline_args,\n                        uint8_t **out,\n                        size_t *out_size,\n                        const void *src,\n                        size_t src_size) {\n    AVS_LIST(anjay_demo_allocated_buffer_t) buffer = (AVS_LIST(\n            anjay_demo_allocated_buffer_t)) AVS_LIST_NEW_BUFFER(src_size);\n    if (!buffer) {\n        return -1;\n    }\n    *out = (uint8_t *) buffer;\n    *out_size = src_size;\n    memcpy((void *) buffer, src, src_size);\n    AVS_LIST_INSERT(&cmdline_args->allocated_buffers, buffer);\n    return 0;\n}\n\nstatic int load_buffer_from_file(cmdline_args_t *cmdline_args,\n                                 uint8_t **out,\n                                 size_t *out_size,\n                                 const char *filename) {\n    AVS_LIST(anjay_demo_allocated_buffer_t) buffer = NULL;\n    FILE *f = fopen(filename, \"rb\");\n    if (!f) {\n        return -1;\n    }\n    int result = -1;\n    long size;\n    if (fseek(f, 0, SEEK_END)) {\n        goto finish;\n    }\n    size = ftell(f);\n    if (size < 0 || (unsigned long) size > SIZE_MAX || fseek(f, 0, SEEK_SET)) {\n        goto finish;\n    }\n    if (!(*out_size = (size_t) size)) {\n        *out = NULL;\n    } else {\n        if (!(buffer = (AVS_LIST(anjay_demo_allocated_buffer_t))\n                      AVS_LIST_NEW_BUFFER(*out_size))) {\n            goto finish;\n        }\n        *out = (uint8_t *) buffer;\n        if (fread(*out, *out_size, 1, f) != 1) {\n            AVS_LIST_DELETE(&buffer);\n            *out = NULL;\n            goto finish;\n        }\n    }\n    result = 0;\n    if (buffer) {\n        AVS_LIST_INSERT(&cmdline_args->allocated_buffers, buffer);\n    }\nfinish:\n    fclose(f);\n    return result;\n}\n\n#ifdef ANJAY_WITH_LWM2M11\nstatic int parse_lwm2m_version(const char *str,\n                               anjay_lwm2m_version_t *out_version) {\n    assert(str);\n    if (strcmp(str, \"1.0\") == 0) {\n        *out_version = ANJAY_LWM2M_VERSION_1_0;\n        return 0;\n    } else if (strcmp(str, \"1.1\") == 0) {\n        *out_version = ANJAY_LWM2M_VERSION_1_1;\n        return 0;\n    }\n#    ifdef ANJAY_WITH_LWM2M12\n    else if (strcmp(str, \"1.2\") == 0) {\n        *out_version = ANJAY_LWM2M_VERSION_1_2;\n        return 0;\n    }\n#    endif // ANJAY_WITH_LWM2M12\n    else {\n        demo_log(ERROR, \"Invalid LwM2M version: %s\", str);\n        return -1;\n    }\n}\n#endif // ANJAY_WITH_LWM2M11\n\nint demo_parse_argv(cmdline_args_t *parsed_args, int argc, char *argv[]) {\n    static const char DEFAULT_CERT_FILE[] = \"../certs/client.crt.der\";\n    static const char DEFAULT_KEY_FILE[] = \"../certs/client.key.der\";\n    const char *last_arg0_slash = strrchr(argv[0], '/');\n    size_t arg0_prefix_length =\n            (size_t) (last_arg0_slash ? (last_arg0_slash - argv[0] + 1) : 0);\n    bool identity_set, key_set;\n    int num_servers = 0;\n\n    const struct option options[] = {\n    // clang-format off\n#ifdef ANJAY_WITH_MODULE_ACCESS_CONTROL\n        { \"access-entry\",                  required_argument, 0, 'a' },\n#endif // ANJAY_WITH_MODULE_ACCESS_CONTROL\n#ifdef ANJAY_WITH_BOOTSTRAP\n        { \"bootstrap\",                     optional_argument, 0, 'b' },\n        { \"bootstrap-holdoff\",             required_argument, 0, 'H' },\n        { \"bootstrap-timeout\",             required_argument, 0, 'T' },\n#endif // ANJAY_WITH_BOOTSTRAP\n        { \"endpoint-name\",                 required_argument, 0, 'e' },\n        { \"help\",                          optional_argument, 0, 'h' },\n#ifndef _WIN32\n        { \"disable-stdin\",                 no_argument,       0, 't' },\n#endif // _WIN32\n        { \"lifetime\",                      required_argument, 0, 'l' },\n        { \"stored-notification-limit\",     required_argument, 0, 'L' },\n        { \"location-csv\",                  required_argument, 0, 'c' },\n        { \"location-update-freq-s\",        required_argument, 0, 'f' },\n        { \"port\",                          required_argument, 0, 'p' },\n        { \"identity\",                      required_argument, 0, 'i' },\n        { \"client-cert-file\",              required_argument, 0, 'C' },\n        { \"key\",                           required_argument, 0, 'k' },\n        { \"key-file\",                      required_argument, 0, 'K' },\n        { \"server-public-key-file\",        required_argument, 0, 'P' },\n        { \"binding\",                       required_argument, 0, 'q' },\n        { \"security-mode\",                 required_argument, 0, 's' },\n        { \"server-uri\",                    required_argument, 0, 'u' },\n        { \"security-iid\",                  required_argument, 0, 'D' },\n        { \"server-iid\",                    required_argument, 0, 'd' },\n        { \"inbuf-size\",                    required_argument, 0, 'I' },\n        { \"outbuf-size\",                   required_argument, 0, 'O' },\n        { \"cache-size\",                    required_argument, 0, '$' },\n        { \"confirmable-notifications\",     no_argument,       0, 'N' },\n#ifdef ANJAY_WITH_LWM2M11\n        { \"minimum-version\",               required_argument, 0, 'v' },\n        { \"maximum-version\",               required_argument, 0, 'V' },\n#endif // ANJAY_WITH_LWM2M11\n#ifdef ANJAY_WITH_MODULE_FW_UPDATE\n        { \"delayed-upgrade-result\",        required_argument, 0, 'r' },\n#   if defined(AVS_COMMONS_WITH_AVS_PERSISTENCE) && defined(AVS_COMMONS_STREAM_WITH_FILE)\n        { \"fw-updated-marker-path\",        required_argument, 0, 256 },\n#   endif // defined(AVS_COMMONS_WITH_AVS_PERSISTENCE) && defined(AVS_COMMONS_STREAM_WITH_FILE)\n        { \"fw-cert-file\",                  required_argument, 0, 257 },\n        { \"fw-cert-path\",                  required_argument, 0, 258 },\n        { \"fw-psk-identity\",               required_argument, 0, 259 },\n        { \"fw-psk-key\",                    required_argument, 0, 260 },\n#endif // ANJAY_WITH_MODULE_FW_UPDATE\n#if defined(ANJAY_WITH_ATTR_STORAGE) && defined(AVS_COMMONS_STREAM_WITH_FILE)\n        { \"attribute-storage-persistence-file\", required_argument, 0, 261 },\n#endif // defined(ANJAY_WITH_ATTR_STORAGE) && defined(AVS_COMMONS_STREAM_WITH_FILE)\n#ifdef ANJAY_WITH_MODULE_FACTORY_PROVISIONING\n        { \"factory-provisioning-file\",     required_argument, 0, 'F' },\n#endif // ANJAY_WITH_MODULE_FACTORY_PROVISIONING\n        { \"ack-random-factor\",             required_argument, 0, 267 },\n        { \"ack-timeout\",                   required_argument, 0, 268 },\n        { \"max-retransmit\",                required_argument, 0, 269 },\n        { \"dtls-hs-retry-wait-min\",        required_argument, 0, 270 },\n        { \"dtls-hs-retry-wait-max\",        required_argument, 0, 271 },\n#ifdef ANJAY_WITH_MODULE_FW_UPDATE\n        { \"fwu-ack-random-factor\",         required_argument, 0, 272 },\n        { \"fwu-ack-timeout\",               required_argument, 0, 273 },\n        { \"fwu-max-retransmit\",            required_argument, 0, 274 },\n#endif // ANJAY_WITH_MODULE_FW_UPDATE\n        { \"prefer-hierarchical-formats\",   no_argument,       0, 275 },\n#ifdef ANJAY_WITH_LWM2M11\n        { \"sni\",                           required_argument, 0, 276 },\n#endif // ANJAY_WITH_LWM2M11\n        { \"use-connection-id\",             no_argument,       0, 277 },\n        { \"ciphersuites\",                  required_argument, 0, 278 },\n#ifdef ANJAY_WITH_LWM2M11\n        { \"retry-count\",                   required_argument, 0, 279 },\n        { \"retry-timer\",                   required_argument, 0, 280 },\n        { \"sequence-retry-count\",          required_argument, 0, 281 },\n        { \"sequence-delay-timer\",          required_argument, 0, 282 },\n#endif // ANJAY_WITH_LWM2M11\n        { \"prefer-same-socket-downloads\",  no_argument,       0, 283 },\n        { \"nstart\",                        required_argument, 0, 284 },\n#if defined(ANJAY_WITH_SEND) && defined(ANJAY_WITH_MODULE_FW_UPDATE)\n        { \"fw-update-use-send\",            no_argument,       0, 287 },\n#endif // defined(ANJAY_WITH_SEND) && defined(ANJAY_WITH_MODULE_FW_UPDATE)\n#ifdef ANJAY_WITH_LWM2M11\n        { \"pkix-trust-store\",              required_argument, 0, 288 },\n#endif // ANJAY_WITH_LWM2M11\n#if defined(AVS_COMMONS_WITH_AVS_PERSISTENCE) && defined(AVS_COMMONS_STREAM_WITH_FILE)\n        { \"dm-persistence-file\",           required_argument, 0, 289 },\n#endif // defined(AVS_COMMONS_WITH_AVS_PERSISTENCE) && defined(AVS_COMMONS_STREAM_WITH_FILE)\n#ifdef ANJAY_WITH_SECURITY_STRUCTURED\n        { \"use-external-security-info\",    no_argument,       0, 298 },\n#endif // ANJAY_WITH_SECURITY_STRUCTURED\n#ifdef ANJAY_WITH_LWM2M11\n        { \"rebuild-client-cert-chain\",     no_argument,       0, 299 },\n#endif // ANJAY_WITH_LWM2M11\n        { \"alternative-logger\",            no_argument,       0, 306 },\n        { \"identity-as-string\",            required_argument, 0, 307 },\n        { \"key-as-string\",                 required_argument, 0, 308 },\n        { \"tls-version\",                   required_argument, 0, 317 },\n#if defined(ANJAY_WITH_LWM2M11) && defined(WITH_AVS_COAP_TCP)\n        { \"tcp-request-timeout\",           required_argument, 0, 319 },\n#endif // defined(ANJAY_WITH_LWM2M11) && defined(WITH_AVS_COAP_TCP)\n        { \"update-immediately-on-dm-change\", no_argument,     0, 320 },\n        { \"enable-self-notify\",              no_argument,     0, 321 },\n#ifdef ANJAY_WITH_MODULE_ADVANCED_FW_UPDATE\n        { \"afu-original-img-file-path\",    required_argument, 0, 322 },\n#   if defined(AVS_COMMONS_WITH_AVS_PERSISTENCE) && defined(AVS_COMMONS_STREAM_WITH_FILE)\n        { \"afu-marker-path\",               required_argument, 0, 323 },\n#   endif // defined(AVS_COMMONS_WITH_AVS_PERSISTENCE) && defined(AVS_COMMONS_STREAM_WITH_FILE)\n        { \"afu-cert-file\",                 required_argument, 0, 324 },\n        { \"delayed-afu-result\",            required_argument, 0, 325 },\n#   if defined(ANJAY_WITH_SEND)\n        { \"afu-use-send\",                  no_argument,       0, 326 },\n#   endif // defined(ANJAY_WITH_SEND)\n        { \"afu-ack-timeout\",               required_argument, 0, 327 },\n#endif // ANJAY_WITH_MODULE_ADVANCED_FW_UPDATE\n        { \"start-offline\",                 no_argument,       0, 328 },\n#ifdef ANJAY_WITH_MODULE_FW_UPDATE\n        { \"fw-auto-suspend\",               no_argument,       0, 329 },\n#endif // ANJAY_WITH_MODULE_FW_UPDATE\n#ifdef ANJAY_WITH_MODULE_ADVANCED_FW_UPDATE\n        { \"afu-auto-suspend\",              no_argument,       0, 330 },\n        { \"afu-psk-identity\",              required_argument, 0, 331 },\n        { \"afu-psk-key\",                   required_argument, 0, 332 },\n#endif // ANJAY_WITH_MODULE_ADVANCED_FW_UPDATE\n#ifdef ANJAY_WITH_MODULE_FW_UPDATE\n        { \"fwu-tcp-request-timeout\",       required_argument, 0, 333 },\n#endif // ANJAY_WITH_MODULE_FW_UPDATE\n#ifdef ANJAY_WITH_MODULE_ADVANCED_FW_UPDATE\n        { \"afu-tcp-request-timeout\",       required_argument, 0, 334 },\n#endif // ANJAY_WITH_MODULE_ADVANCED_FW_UPDATE\n#ifdef ANJAY_WITH_MODULE_SW_MGMT\n        { \"delayed-sw-mgmt-result\",        required_argument, 0, 335 },\n        { \"sw-mgmt-terminate-after-downloading\", no_argument, 0, 336 },\n        { \"sw-mgmt-disable-repeated-activation-deactivation\", no_argument, 0, 337 },\n#   if defined(AVS_COMMONS_WITH_AVS_PERSISTENCE) && defined(AVS_COMMONS_STREAM_WITH_FILE)\n        { \"sw-mgmt-persistence-file\",      required_argument, 0, 338 },\n#   endif // defined(AVS_COMMONS_WITH_AVS_PERSISTENCE) && defined(AVS_COMMONS_STREAM_WITH_FILE)\n#    ifdef ANJAY_WITH_DOWNLOADER\n        { \"sw-mgmt-cert-file\",             required_argument, 0, 339 },\n        { \"sw-mgmt-cert-path\",             required_argument, 0, 340 },\n        { \"sw-mgmt-psk-identity\",          required_argument, 0, 341 },\n        { \"sw-mgmt-psk-key\",               required_argument, 0, 342 },\n        { \"sw-mgmt-ack-random-factor\",     required_argument, 0, 343 },\n        { \"sw-mgmt-ack-timeout\",           required_argument, 0, 344 },\n        { \"sw-mgmt-max-retransmit\",        required_argument, 0, 345 },\n        { \"sw-mgmt-auto-suspend\",          no_argument,       0, 346 },\n        { \"sw-mgmt-tcp-request-timeout\",   required_argument, 0, 347 },\n#    endif // ANJAY_WITH_DOWNLOADER\n#endif // ANJAY_WITH_MODULE_SW_MGMT\n        { \"connection-error-is-registration-failure\", no_argument, 0, 348 },\n#ifdef ANJAY_WITH_LWM2M_GATEWAY\n        { \"lwm2m_gateway\",                 no_argument,       0, 'g' },\n#endif // ANJAY_WITH_LWM2M_GATEWAY\n#ifdef ANJAY_WITH_DOWNLOADER\n        {\"coap-downloader-retry-count\", required_argument, 0, 349},\n        {\"coap-downloader-retry-delay\", required_argument, 0, 350},\n#endif // ANJAY_WITH_DOWNLOADER\n#ifdef ANJAY_WITH_LWM2M11\n        {\"certificate-usage\", required_argument, 0, 351},\n#endif // ANJAY_WITH_LWM2M11\n#ifdef WITH_DEMO_TRAFFIC_INTERCEPTOR\n{ \"traffic_interceptor_path\", required_argument, 0, 357 },\n#endif// WITH_DEMO_TRAFFIC_INTERCEPTOR\n        { 0, 0, 0, 0 }\n        // clang-format on\n    };\n\n    int retval = -1;\n\n    *parsed_args = DEFAULT_CMDLINE_ARGS;\n\n    char *default_cert_path =\n            (char *) avs_malloc(arg0_prefix_length + sizeof(DEFAULT_CERT_FILE));\n    char *default_key_path =\n            (char *) avs_malloc(arg0_prefix_length + sizeof(DEFAULT_KEY_FILE));\n    const char *cert_path = default_cert_path;\n    const char *key_path = default_key_path;\n    const char *server_public_key_path = NULL;\n#ifdef ANJAY_WITH_SECURITY_STRUCTURED\n    bool use_external_security_info = false;\n#endif // ANJAY_WITH_SECURITY_STRUCTURED\n\n    if (!default_cert_path || !default_key_path) {\n        demo_log(ERROR, \"Out of memory\");\n        goto finish;\n    }\n\n    memcpy(default_cert_path, argv[0], arg0_prefix_length);\n    strcpy(default_cert_path + arg0_prefix_length, DEFAULT_CERT_FILE);\n\n    memcpy(default_key_path, argv[0], arg0_prefix_length);\n    strcpy(default_key_path + arg0_prefix_length, DEFAULT_KEY_FILE);\n\n    char getopt_str[3 * AVS_ARRAY_SIZE(options)];\n    build_getopt_string(options, getopt_str, sizeof(getopt_str));\n\n    while (true) {\n        int option_index = 0;\n\n        switch (getopt_long(argc, argv, getopt_str, options, &option_index)) {\n        case '?':\n            demo_log(ERROR, \"unrecognized cmdline argument: %s\",\n                     argv[option_index]);\n            goto finish;\n        case -1:\n            if (optind >= argc) {\n                goto process;\n            }\n            demo_log(ERROR, \"unrecognized free argument: %s\", argv[optind]);\n            goto finish;\n#ifdef ANJAY_WITH_MODULE_ACCESS_CONTROL\n        case 'a': {\n            uint16_t oid;\n            uint16_t iid;\n            uint16_t ssid;\n            uint16_t mask;\n            if (sscanf(optarg, \"/%\" SCNu16 \"/%\" SCNu16 \",%\" SCNu16 \",%\" SCNu16,\n                       &oid, &iid, &ssid, &mask)\n                    != 4) {\n                demo_log(ERROR, \"insufficient arguments\");\n                goto finish;\n            }\n            AVS_LIST(access_entry_t) entry =\n                    AVS_LIST_NEW_ELEMENT(access_entry_t);\n            if (!entry) {\n                goto finish;\n            }\n            entry->oid = (anjay_oid_t) oid;\n            entry->iid = (anjay_iid_t) iid;\n            entry->ssid = (anjay_ssid_t) ssid;\n            entry->mask = (anjay_access_mask_t) mask;\n            AVS_LIST_INSERT(&parsed_args->access_entries, entry);\n            break;\n        }\n#endif // ANJAY_WITH_MODULE_ACCESS_CONTROL\n#ifdef ANJAY_WITH_BOOTSTRAP\n        case 'b': {\n            int idx = num_servers == 0 ? 0 : num_servers - 1;\n            parsed_args->connection_args.servers[idx].is_bootstrap = true;\n            if (optarg && *optarg) {\n                if (strcmp(optarg, \"client-initiated-only\") == 0) {\n                    parsed_args->disable_legacy_server_initiated_bootstrap =\n                            true;\n                } else {\n                    demo_log(ERROR,\n                             \"Invalid bootstrap optional argument: \\\"%s\\\"; \"\n                             \"available options: client-initiated-only\",\n                             optarg);\n                    goto finish;\n                }\n            }\n            break;\n        }\n        case 'H':\n            if (parse_i32(optarg,\n                          &parsed_args->connection_args.bootstrap_holdoff_s)) {\n                goto finish;\n            }\n            break;\n        case 'T':\n            if (parse_i32(optarg,\n                          &parsed_args->connection_args.bootstrap_timeout_s)) {\n                goto finish;\n            }\n            break;\n#endif // ANJAY_WITH_BOOTSTRAP\n        case 'e':\n            parsed_args->endpoint_name = optarg;\n            break;\n        case 'h': {\n            if (optarg && *optarg) {\n                if (strcmp(optarg, \"full\") == 0) {\n                    print_help_full(options);\n                } else {\n                    demo_log(ERROR,\n                             \"Invalid help optional argument: \\\"%s\\\"; \"\n                             \"for full help run with --help=full\",\n                             optarg);\n                }\n            } else {\n                print_help_short(argv[0]);\n            }\n            goto finish;\n        }\n#ifndef _WIN32\n        case 't':\n            parsed_args->disable_stdin = true;\n            break;\n#endif // _WIN32\n        case 'l':\n            if (parse_i32(optarg, &parsed_args->connection_args.lifetime)) {\n                goto finish;\n            }\n            break;\n        case 'L':\n            if (parse_size(optarg, &parsed_args->stored_notification_limit)) {\n                goto finish;\n            }\n            break;\n        case 'c':\n            parsed_args->location_csv = optarg;\n            break;\n        case 'f': {\n            long freq;\n            if (demo_parse_long(optarg, &freq) || freq <= 0\n                    || freq > INT32_MAX) {\n                demo_log(ERROR, \"invalid location update frequency: %s\",\n                         optarg);\n                goto finish;\n            }\n\n            parsed_args->location_update_frequency_s = (time_t) freq;\n            break;\n        }\n        case 'p': {\n            long port;\n            if (demo_parse_long(optarg, &port) || port <= 0\n                    || port > UINT16_MAX) {\n                demo_log(ERROR, \"invalid UDP port number: %s\", optarg);\n                goto finish;\n            }\n\n            parsed_args->udp_listen_port = (uint16_t) port;\n            break;\n        }\n        case 'i':\n            if (parse_hexstring(parsed_args, optarg,\n                                &parsed_args->connection_args\n                                         .public_cert_or_psk_identity,\n                                &parsed_args->connection_args\n                                         .public_cert_or_psk_identity_size)) {\n                demo_log(ERROR, \"Invalid identity\");\n                goto finish;\n            }\n            break;\n        case 'C':\n            cert_path = optarg;\n            break;\n        case 'k':\n            if (parse_hexstring(\n                        parsed_args, optarg,\n                        &parsed_args->connection_args.private_cert_or_psk_key,\n                        &parsed_args->connection_args\n                                 .private_cert_or_psk_key_size)) {\n                demo_log(ERROR, \"Invalid key\");\n                goto finish;\n            }\n            break;\n        case 'K':\n            key_path = optarg;\n            break;\n        case 'P':\n            server_public_key_path = optarg;\n            break;\n        case 'q': {\n            if (num_servers == 0) {\n                demo_log(ERROR, \"Undefined server. Use --server-uri/-u first\");\n                goto finish;\n            }\n            int idx = num_servers - 1;\n            if (parsed_args->connection_args.servers[idx].binding_mode\n                    != NULL) {\n                demo_log(ERROR,\n                         \"Binding mode already defined for the current server\");\n                goto finish;\n            }\n            parsed_args->connection_args.servers[idx].binding_mode = optarg;\n            break;\n        }\n        case 'D': {\n            if (num_servers == 0) {\n                demo_log(ERROR, \"Undefined server. Use --server-uri/-u first\");\n                goto finish;\n            }\n            int idx = num_servers - 1;\n            if (parsed_args->connection_args.servers[idx].security_iid\n                    != ANJAY_ID_INVALID) {\n                demo_log(ERROR, \"Security IID already defined\");\n                goto finish;\n            }\n            if (parse_u16(optarg,\n                          &parsed_args->connection_args.servers[idx]\n                                   .security_iid)) {\n                goto finish;\n            }\n            break;\n        }\n        case 's':\n            if (parse_security_mode(\n                        optarg, &parsed_args->connection_args.security_mode)) {\n                goto finish;\n            }\n            break;\n        case 'd': {\n            if (num_servers == 0) {\n                demo_log(ERROR, \"Undefined server. Use --server-uri/-u first\");\n                goto finish;\n            }\n            int idx = num_servers - 1;\n            if (parsed_args->connection_args.servers[idx].server_iid\n                    != ANJAY_ID_INVALID) {\n                demo_log(ERROR, \"Server IID already defined\");\n                goto finish;\n            }\n            if (parse_u16(optarg,\n                          &parsed_args->connection_args.servers[idx]\n                                   .server_iid)) {\n                goto finish;\n            }\n            break;\n        }\n        case 'u': {\n            AVS_ASSERT(num_servers < MAX_SERVERS, \"Too many servers\");\n            const server_entry_t *prev_entry = NULL;\n            if (num_servers > 0) {\n                prev_entry =\n                        &parsed_args->connection_args.servers[num_servers - 1];\n            }\n            server_entry_t *entry =\n                    &parsed_args->connection_args.servers[num_servers++];\n            if (prev_entry) {\n                memcpy(entry, prev_entry, sizeof(*prev_entry));\n                entry->security_iid = ANJAY_ID_INVALID;\n                entry->server_iid = ANJAY_ID_INVALID;\n                entry->binding_mode = NULL;\n                entry->is_bootstrap = false;\n            }\n\n            entry->uri = optarg;\n\n            break;\n        }\n        case 'I':\n            if (parse_i32(optarg, &parsed_args->inbuf_size)\n                    || parsed_args->inbuf_size <= 0) {\n                goto finish;\n            }\n            break;\n        case 'O':\n            if (parse_i32(optarg, &parsed_args->outbuf_size)\n                    || parsed_args->outbuf_size <= 0) {\n                goto finish;\n            }\n            break;\n        case '$':\n            if (parse_i32(optarg, &parsed_args->msg_cache_size)\n                    || parsed_args->msg_cache_size < 0) {\n                goto finish;\n            }\n            break;\n        case 'N':\n            parsed_args->confirmable_notifications = true;\n            break;\n#ifdef ANJAY_WITH_LWM2M11\n        case 'v':\n            if (parse_lwm2m_version(\n                        optarg,\n                        &parsed_args->lwm2m_version_config.minimum_version)) {\n                goto finish;\n            }\n            break;\n        case 'V':\n            if (parse_lwm2m_version(\n                        optarg,\n                        &parsed_args->lwm2m_version_config.maximum_version)) {\n                goto finish;\n            }\n            break;\n#endif // ANJAY_WITH_LWM2M11\n#ifdef ANJAY_WITH_MODULE_FW_UPDATE\n        case 'r': {\n            int result;\n            if (parse_i32(optarg, &result)\n                    || result < (int) ANJAY_FW_UPDATE_RESULT_INITIAL\n                    || result > (int) ANJAY_FW_UPDATE_RESULT_UNSUPPORTED_PROTOCOL) {\n                demo_log(ERROR, \"invalid update result value: %s\", optarg);\n                goto finish;\n            }\n            parsed_args->fw_update_delayed_result =\n                    (anjay_fw_update_result_t) result;\n            break;\n        }\n        case 256:\n            parsed_args->fw_updated_marker_path = optarg;\n            break;\n        case 257: {\n            if (parsed_args->fw_security_info.mode\n                    != (avs_net_security_mode_t) -1) {\n                demo_log(ERROR, \"Multiple incompatible security information \"\n                                \"specified for firmware upgrade\");\n                goto finish;\n            }\n\n            const avs_net_certificate_info_t cert_info = {\n                .server_cert_validation = true,\n                .trusted_certs =\n                        avs_crypto_certificate_chain_info_from_file(optarg)\n            };\n            parsed_args->fw_security_info =\n                    avs_net_security_info_from_certificates(cert_info);\n            break;\n        }\n        case 258: {\n            if (parsed_args->fw_security_info.mode\n                    != (avs_net_security_mode_t) -1) {\n                demo_log(ERROR, \"Multiple incompatible security information \"\n                                \"specified for firmware upgrade\");\n                goto finish;\n            }\n            const avs_net_certificate_info_t cert_info = {\n                .server_cert_validation = true,\n                .trusted_certs =\n                        avs_crypto_certificate_chain_info_from_path(optarg)\n            };\n            parsed_args->fw_security_info =\n                    avs_net_security_info_from_certificates(cert_info);\n            break;\n        }\n        case 259: {\n            if (parsed_args->fw_security_info.mode != AVS_NET_SECURITY_PSK\n                    && parsed_args->fw_security_info.mode\n                                   != (avs_net_security_mode_t) -1) {\n                demo_log(ERROR, \"Multiple incompatible security information \"\n                                \"specified for firmware upgrade\");\n                goto finish;\n            }\n            if (parsed_args->fw_security_info.mode == AVS_NET_SECURITY_PSK\n                    && parsed_args->fw_security_info.data.psk.identity.desc\n                                       .source\n                                   != AVS_CRYPTO_DATA_SOURCE_EMPTY) {\n                demo_log(ERROR, \"--fw-psk-identity specified more than once\");\n                goto finish;\n            }\n            uint8_t *identity_buf = NULL;\n            size_t identity_size = 0;\n            if (parse_hexstring(parsed_args, optarg, &identity_buf,\n                                &identity_size)) {\n                demo_log(ERROR, \"Invalid PSK identity for firmware upgrade\");\n                goto finish;\n            }\n            parsed_args->fw_security_info.mode = AVS_NET_SECURITY_PSK;\n            parsed_args->fw_security_info.data.psk.identity =\n                    avs_crypto_psk_identity_info_from_buffer(identity_buf,\n                                                             identity_size);\n            break;\n        }\n        case 260: {\n            if (parsed_args->fw_security_info.mode != AVS_NET_SECURITY_PSK\n                    && parsed_args->fw_security_info.mode\n                                   != (avs_net_security_mode_t) -1) {\n                demo_log(ERROR, \"Multiple incompatible security information \"\n                                \"specified for firmware upgrade\");\n                goto finish;\n            }\n            if (parsed_args->fw_security_info.mode == AVS_NET_SECURITY_PSK\n                    && parsed_args->fw_security_info.data.psk.key.desc.source\n                                   != AVS_CRYPTO_DATA_SOURCE_EMPTY) {\n                demo_log(ERROR, \"--fw-psk-key specified more than once\");\n                goto finish;\n            }\n            uint8_t *psk_buf = NULL;\n            size_t psk_size = 0;\n            if (parse_hexstring(parsed_args, optarg, &psk_buf, &psk_size)) {\n                demo_log(ERROR, \"Invalid pre-shared key for firmware upgrade\");\n                goto finish;\n            }\n            parsed_args->fw_security_info.mode = AVS_NET_SECURITY_PSK;\n            parsed_args->fw_security_info.data.psk.key =\n                    avs_crypto_psk_key_info_from_buffer(psk_buf, psk_size);\n            break;\n        }\n#endif // ANJAY_WITH_MODULE_FW_UPDATE\n#if defined(ANJAY_WITH_ATTR_STORAGE) && defined(AVS_COMMONS_STREAM_WITH_FILE)\n        case 261:\n            parsed_args->attr_storage_file = optarg;\n            break;\n#endif // defined(ANJAY_WITH_ATTR_STORAGE) &&\n       // defined(AVS_COMMONS_STREAM_WITH_FILE)\n#ifdef ANJAY_WITH_MODULE_FACTORY_PROVISIONING\n        case 'F':\n            if (!optarg || strlen(optarg) < 1) {\n                goto finish;\n            }\n            parsed_args->provisioning_file = optarg;\n            break;\n#endif // ANJAY_WITH_MODULE_FACTORY_PROVISIONING\n        case 267:\n            if (parse_double(optarg,\n                             &parsed_args->tx_params.ack_random_factor)) {\n                demo_log(ERROR, \"Expected ACK_RANDOM_FACTOR to be a floating \"\n                                \"point number\");\n                goto finish;\n            }\n            break;\n        case 268: {\n            double ack_timeout_s;\n            if (parse_double(optarg, &ack_timeout_s)) {\n                demo_log(ERROR,\n                         \"Expected ACK_TIMEOUT to be a floating point number\");\n                goto finish;\n            }\n            parsed_args->tx_params.ack_timeout =\n                    avs_time_duration_from_fscalar(ack_timeout_s, AVS_TIME_S);\n            break;\n        }\n        case 269: {\n            uint32_t max_retransmit;\n            if (parse_u32(optarg, &max_retransmit)) {\n                demo_log(ERROR, \"Expected MAX_RETRANSMIT to be an unsigned \"\n                                \"integer\");\n                goto finish;\n            }\n            parsed_args->tx_params.max_retransmit = (unsigned) max_retransmit;\n            break;\n        }\n        case 270: {\n            double min_wait_s;\n            if (parse_double(optarg, &min_wait_s) || min_wait_s <= 0) {\n                demo_log(ERROR, \"Expected DTLS_HS_RETRY_WAIT_MIN > 0\");\n                goto finish;\n            }\n            parsed_args->dtls_hs_tx_params.min =\n                    avs_time_duration_from_fscalar(min_wait_s, AVS_TIME_S);\n            break;\n        }\n        case 271: {\n            double max_wait_s;\n            if (parse_double(optarg, &max_wait_s) || max_wait_s <= 0) {\n                demo_log(ERROR, \"Expected DTLS_HS_RETRY_WAIT_MAX > 0\");\n                goto finish;\n            }\n            parsed_args->dtls_hs_tx_params.max =\n                    avs_time_duration_from_fscalar(max_wait_s, AVS_TIME_S);\n            break;\n        }\n#ifdef ANJAY_WITH_MODULE_FW_UPDATE\n        case 272:\n            if (parse_double(optarg,\n                             &parsed_args->fwu_tx_params.ack_random_factor)) {\n                demo_log(ERROR, \"Expected ACK_RANDOM_FACTOR to be a floating \"\n                                \"point number\");\n                goto finish;\n            }\n            parsed_args->fwu_tx_params_modified = true;\n            break;\n        case 273: {\n            double ack_timeout_s;\n            if (parse_double(optarg, &ack_timeout_s)) {\n                demo_log(ERROR,\n                         \"Expected ACK_TIMEOUT to be a floating point number\");\n                goto finish;\n            }\n            parsed_args->fwu_tx_params.ack_timeout =\n                    avs_time_duration_from_fscalar(ack_timeout_s, AVS_TIME_S);\n            parsed_args->fwu_tx_params_modified = true;\n            break;\n        }\n        case 274: {\n            uint32_t max_retransmit;\n            if (parse_u32(optarg, &max_retransmit)) {\n                demo_log(ERROR, \"Expected MAX_RETRANSMIT to be an unsigned \"\n                                \"integer\");\n                goto finish;\n            }\n            parsed_args->fwu_tx_params.max_retransmit =\n                    (unsigned) max_retransmit;\n            parsed_args->fwu_tx_params_modified = true;\n            break;\n        }\n#endif // ANJAY_WITH_MODULE_FW_UPDATE\n        case 275:\n            parsed_args->prefer_hierarchical_formats = true;\n            break;\n#ifdef ANJAY_WITH_LWM2M11\n        case 276: {\n            int idx = num_servers == 0 ? 0 : num_servers - 1;\n            parsed_args->connection_args.servers[idx].sni = optarg;\n            break;\n        }\n#endif // ANJAY_WITH_LWM2M11\n        case 277:\n            parsed_args->use_connection_id = true;\n            break;\n        case 278: {\n            char *saveptr = NULL;\n            char *str = optarg;\n            const char *token;\n            while ((token = avs_strtok(str, \",\", &saveptr))) {\n                uint32_t *reallocated = (uint32_t *) avs_realloc(\n                        parsed_args->default_ciphersuites,\n                        sizeof(*parsed_args->default_ciphersuites)\n                                * ++parsed_args->default_ciphersuites_count);\n                if (!reallocated) {\n                    demo_log(ERROR, \"Out of memory\");\n                    goto finish;\n                }\n                parsed_args->default_ciphersuites = reallocated;\n                if (parse_u32(token,\n                              &parsed_args->default_ciphersuites\n                                       [parsed_args->default_ciphersuites_count\n                                        - 1])) {\n                    demo_log(ERROR, \"Invalid ciphersuite ID: %s\", token);\n                    goto finish;\n                }\n                str = NULL;\n            }\n            break;\n        }\n#ifdef ANJAY_WITH_LWM2M11\n        case 279: {\n            int idx = num_servers == 0 ? 0 : num_servers - 1;\n            if (parse_u32(optarg, &parsed_args->connection_args.servers[idx]\n                                           .retry_count)) {\n                demo_log(ERROR, \"Invalid Retry Count value: %s\", optarg);\n                goto finish;\n            }\n            break;\n        }\n        case 280: {\n            int idx = num_servers == 0 ? 0 : num_servers - 1;\n            if (parse_u32(optarg, &parsed_args->connection_args.servers[idx]\n                                           .retry_timer)) {\n                demo_log(ERROR, \"Invalid Retry Timer value: %s\", optarg);\n                goto finish;\n            }\n            break;\n        }\n        case 281: {\n            int idx = num_servers == 0 ? 0 : num_servers - 1;\n            if (parse_u32(optarg, &parsed_args->connection_args.servers[idx]\n                                           .sequence_retry_count)) {\n                demo_log(ERROR, \"Invalid Sequence Retry Count value: %s\",\n                         optarg);\n                goto finish;\n            }\n            break;\n        }\n        case 282: {\n            int idx = num_servers == 0 ? 0 : num_servers - 1;\n            if (parse_u32(optarg, &parsed_args->connection_args.servers[idx]\n                                           .sequence_delay_timer)) {\n                demo_log(ERROR, \"Invalid Sequence Delay Timer value: %s\",\n                         optarg);\n                goto finish;\n            }\n            break;\n        }\n#endif // ANJAY_WITH_LWM2M11\n        case 283:\n            parsed_args->prefer_same_socket_downloads = true;\n            break;\n        case 284:\n            if (parse_size(optarg, &parsed_args->tx_params.nstart)) {\n                demo_log(ERROR,\n                         \"Invalid NSTART value, expected non-negative integer, \"\n                         \"got %s\",\n                         optarg);\n                goto finish;\n            }\n            break;\n#if defined(ANJAY_WITH_SEND) && defined(ANJAY_WITH_MODULE_FW_UPDATE)\n        case 287:\n            parsed_args->fw_update_use_send = true;\n            break;\n#endif // defined(ANJAY_WITH_SEND) && defined(ANJAY_WITH_MODULE_FW_UPDATE)\n#ifdef ANJAY_WITH_LWM2M11\n        case 288:\n            parsed_args->pkix_trust_store = optarg;\n            break;\n#endif // ANJAY_WITH_LWM2M11\n#if defined(AVS_COMMONS_WITH_AVS_PERSISTENCE) \\\n        && defined(AVS_COMMONS_STREAM_WITH_FILE)\n        case 289:\n            parsed_args->dm_persistence_file = optarg;\n            break;\n#endif // defined(AVS_COMMONS_WITH_AVS_PERSISTENCE) &&\n       // defined(AVS_COMMONS_STREAM_WITH_FILE)\n#ifdef ANJAY_WITH_SECURITY_STRUCTURED\n        case 298:\n            use_external_security_info = true;\n            break;\n#endif // ANJAY_WITH_SECURITY_STRUCTURED\n#ifdef ANJAY_WITH_LWM2M11\n        case 299:\n            parsed_args->rebuild_client_cert_chain = true;\n            break;\n#endif // ANJAY_WITH_LWM2M11\n        case 306:\n            parsed_args->alternative_logger = true;\n            break;\n        case 307: {\n            const size_t identity_length = optarg ? strlen(optarg) : 0;\n            if (parsed_args->connection_args.public_cert_or_psk_identity != NULL\n                    || identity_length == 0) {\n                demo_log(ERROR, \"Invalid identity, either identity was set \"\n                                \"twice or empty parameter was passed\");\n                goto finish;\n            }\n            if (clone_buffer(parsed_args,\n                             &parsed_args->connection_args\n                                      .public_cert_or_psk_identity,\n                             &parsed_args->connection_args\n                                      .public_cert_or_psk_identity_size,\n                             optarg, identity_length)) {\n                retval = -ENOMEM;\n                demo_log(ERROR,\n                         \"Error copying identity string, out of memory?\");\n                goto finish;\n            }\n            break;\n        }\n        case 308: {\n            const size_t key_length = optarg ? strlen(optarg) : 0;\n            if (parsed_args->connection_args.private_cert_or_psk_key != NULL\n                    || key_length == 0) {\n                demo_log(ERROR, \"Invalid key, either key was set \"\n                                \"twice or empty parameter was passed\");\n                goto finish;\n            }\n            if (clone_buffer(\n                        parsed_args,\n                        &parsed_args->connection_args.private_cert_or_psk_key,\n                        &parsed_args->connection_args\n                                 .private_cert_or_psk_key_size,\n                        optarg, key_length)) {\n                retval = -ENOMEM;\n                demo_log(ERROR, \"Error copying key string, out of memory?\");\n                goto finish;\n            }\n            break;\n        }\n        case 317:\n            if (parse_tls_version(optarg, &parsed_args->dtls_version)) {\n                goto finish;\n            }\n            break;\n#if defined(ANJAY_WITH_LWM2M11) && defined(WITH_AVS_COAP_TCP)\n        case 319: {\n            double tcp_request_timeout;\n            if (parse_double(optarg, &tcp_request_timeout)) {\n                demo_log(ERROR, \"Expected TCP request timeout to be a floating \"\n                                \"point number\");\n                goto finish;\n            }\n            parsed_args->tcp_request_timeout =\n                    avs_time_duration_from_fscalar(tcp_request_timeout,\n                                                   AVS_TIME_S);\n            break;\n        }\n#endif // defined(ANJAY_WITH_LWM2M11) && defined(WITH_AVS_COAP_TCP)\n        case 320:\n            parsed_args->update_immediately_on_dm_change = true;\n            break;\n        case 321:\n            parsed_args->enable_self_notify = true;\n            break;\n#ifdef ANJAY_WITH_MODULE_ADVANCED_FW_UPDATE\n        case 322:\n            parsed_args->original_img_file_path = optarg;\n            break;\n#    if defined(AVS_COMMONS_WITH_AVS_PERSISTENCE) \\\n            && defined(AVS_COMMONS_STREAM_WITH_FILE)\n        case 323:\n            parsed_args->advanced_fw_updated_marker_path = optarg;\n            break;\n#    endif // defined(AVS_COMMONS_WITH_AVS_PERSISTENCE) &&\n           // defined(AVS_COMMONS_STREAM_WITH_FILE)\n        case 324: {\n            if (parsed_args->advanced_fw_security_info.mode\n                    != (avs_net_security_mode_t) -1) {\n                demo_log(ERROR, \"Multiple incompatible security information \"\n                                \"specified for advanced firmware upgrade\");\n                goto finish;\n            }\n            const avs_net_certificate_info_t cert_info = {\n                .server_cert_validation = true,\n                .trusted_certs =\n                        avs_crypto_certificate_chain_info_from_file(optarg)\n            };\n            parsed_args->advanced_fw_security_info =\n                    avs_net_security_info_from_certificates(cert_info);\n            break;\n        }\n        case 325: {\n            int result;\n            if (parse_i32(optarg, &result)\n                    || result < (int) ANJAY_ADVANCED_FW_UPDATE_RESULT_INITIAL\n                    || result > (int) ANJAY_ADVANCED_FW_UPDATE_RESULT_UNSUPPORTED_PROTOCOL) {\n                demo_log(ERROR, \"invalid update result value: %s\", optarg);\n                goto finish;\n            }\n            parsed_args->advanced_fw_update_delayed_result =\n                    (anjay_advanced_fw_update_result_t) result;\n            break;\n        }\n#    ifdef ANJAY_WITH_SEND\n        case 326:\n            parsed_args->advanced_fw_update_use_send = true;\n            break;\n#    endif // ANJAY_WITH_SEND\n        case 327: {\n            double ack_timeout_s;\n            if (parse_double(optarg, &ack_timeout_s)) {\n                demo_log(ERROR,\n                         \"Expected ACK_TIMEOUT to be a floating point number\");\n                goto finish;\n            }\n            parsed_args->advanced_fwu_tx_params.ack_timeout =\n                    avs_time_duration_from_fscalar(ack_timeout_s, AVS_TIME_S);\n            parsed_args->advanced_fwu_tx_params_modified = true;\n            break;\n        }\n#endif // ANJAY_WITH_MODULE_ADVANCED_FW_UPDATE\n        case 328:\n            parsed_args->start_offline = true;\n            break;\n#ifdef ANJAY_WITH_MODULE_FW_UPDATE\n        case 329:\n            parsed_args->fw_update_auto_suspend = true;\n            break;\n#endif // ANJAY_WITH_MODULE_FW_UPDATE\n#ifdef ANJAY_WITH_MODULE_ADVANCED_FW_UPDATE\n        case 330:\n            parsed_args->advanced_fw_update_auto_suspend = true;\n            break;\n        case 331: {\n            if (parsed_args->advanced_fw_security_info.mode\n                            != AVS_NET_SECURITY_PSK\n                    && parsed_args->advanced_fw_security_info.mode\n                                   != (avs_net_security_mode_t) -1) {\n                demo_log(ERROR, \"Multiple incompatible security information \"\n                                \"specified for advanced firmware upgrade\");\n                goto finish;\n            }\n            if (parsed_args->advanced_fw_security_info.mode\n                            == AVS_NET_SECURITY_PSK\n                    && parsed_args->advanced_fw_security_info.data.psk.identity\n                                       .desc.source\n                                   != AVS_CRYPTO_DATA_SOURCE_EMPTY) {\n                demo_log(ERROR, \"--afu-psk-identity specified more than once\");\n                goto finish;\n            }\n            uint8_t *identity_buf = NULL;\n            size_t identity_size = 0;\n            if (parse_hexstring(parsed_args, optarg, &identity_buf,\n                                &identity_size)) {\n                demo_log(ERROR,\n                         \"Invalid PSK identity for advanced firmware upgrade\");\n                goto finish;\n            }\n            parsed_args->advanced_fw_security_info.mode = AVS_NET_SECURITY_PSK;\n            parsed_args->advanced_fw_security_info.data.psk.identity =\n                    avs_crypto_psk_identity_info_from_buffer(identity_buf,\n                                                             identity_size);\n            break;\n        }\n        case 332: {\n            if (parsed_args->advanced_fw_security_info.mode\n                            != AVS_NET_SECURITY_PSK\n                    && parsed_args->advanced_fw_security_info.mode\n                                   != (avs_net_security_mode_t) -1) {\n                demo_log(ERROR, \"Multiple incompatible security information \"\n                                \"specified for advanced firmware upgrade\");\n                goto finish;\n            }\n            if (parsed_args->advanced_fw_security_info.mode\n                            == AVS_NET_SECURITY_PSK\n                    && parsed_args->advanced_fw_security_info.data.psk.key.desc\n                                       .source\n                                   != AVS_CRYPTO_DATA_SOURCE_EMPTY) {\n                demo_log(ERROR, \"--afu-psk-key specified more than once\");\n                goto finish;\n            }\n            uint8_t *psk_buf = NULL;\n            size_t psk_size = 0;\n            if (parse_hexstring(parsed_args, optarg, &psk_buf, &psk_size)) {\n                demo_log(\n                        ERROR,\n                        \"Invalid pre-shared key for advanced firmware upgrade\");\n                goto finish;\n            }\n            parsed_args->advanced_fw_security_info.mode = AVS_NET_SECURITY_PSK;\n            parsed_args->advanced_fw_security_info.data.psk.key =\n                    avs_crypto_psk_key_info_from_buffer(psk_buf, psk_size);\n            break;\n        }\n#endif // ANJAY_WITH_MODULE_ADVANCED_FW_UPDATE\n#ifdef ANJAY_WITH_MODULE_FW_UPDATE\n        case 333: {\n            double timeout_s;\n            if (parse_double(optarg, &timeout_s) || !isfinite(timeout_s)\n                    || timeout_s <= 0.0) {\n                demo_log(ERROR, \"Expected TCP request timeout to be a positive \"\n                                \"floating point number\");\n                goto finish;\n            }\n            parsed_args->fwu_tcp_request_timeout =\n                    avs_time_duration_from_fscalar(timeout_s, AVS_TIME_S);\n            break;\n        }\n#endif // ANJAY_WITH_MODULE_FW_UPDATE\n#ifdef ANJAY_WITH_MODULE_ADVANCED_FW_UPDATE\n        case 334: {\n            double timeout_s;\n            if (parse_double(optarg, &timeout_s) || !isfinite(timeout_s)\n                    || timeout_s <= 0.0) {\n                demo_log(ERROR, \"Expected TCP request timeout to be a positive \"\n                                \"floating point number\");\n                goto finish;\n            }\n            parsed_args->advanced_fwu_tcp_request_timeout =\n                    avs_time_duration_from_fscalar(timeout_s, AVS_TIME_S);\n            break;\n        }\n#endif // ANJAY_WITH_MODULE_ADVANCED_FW_UPDATE\n#ifdef ANJAY_WITH_MODULE_SW_MGMT\n        case 335: {\n            uint16_t result;\n            if (parse_u16(optarg, &result) || (result != 0 && result != 1)) {\n                demo_log(ERROR, \"invalid install result value: %s\", optarg);\n                goto finish;\n            }\n            parsed_args->sw_mgmt_delayed_first_instance_install_result =\n                    (uint8_t) result;\n            break;\n        }\n        case 336: {\n            parsed_args->sw_mgmt_terminate_after_downloading = true;\n            break;\n        }\n        case 337: {\n            parsed_args->sw_mgmt_disable_repeated_activation_deactivation =\n                    true;\n            break;\n        }\n#    if defined(AVS_COMMONS_WITH_AVS_PERSISTENCE) \\\n            && defined(AVS_COMMONS_STREAM_WITH_FILE)\n        case 338: {\n            parsed_args->sw_mgmt_persistence_file = optarg;\n            break;\n        }\n#    endif // defined(AVS_COMMONS_WITH_AVS_PERSISTENCE) &&\n           // defined(AVS_COMMONS_STREAM_WITH_FILE)\n#    ifdef ANJAY_WITH_DOWNLOADER\n        case 339: {\n            if (parsed_args->sw_mgmt_security_info.mode\n                    != (avs_net_security_mode_t) -1) {\n                demo_log(ERROR, \"Multiple incompatible security information \"\n                                \"specified for software managment\");\n                goto finish;\n            }\n\n            const avs_net_certificate_info_t cert_info = {\n                .server_cert_validation = true,\n                .trusted_certs =\n                        avs_crypto_certificate_chain_info_from_file(optarg)\n            };\n            parsed_args->sw_mgmt_security_info =\n                    avs_net_security_info_from_certificates(cert_info);\n            break;\n        }\n        case 340: {\n            if (parsed_args->sw_mgmt_security_info.mode\n                    != (avs_net_security_mode_t) -1) {\n                demo_log(ERROR, \"Multiple incompatible security information \"\n                                \"specified for software managment\");\n                goto finish;\n            }\n            const avs_net_certificate_info_t cert_info = {\n                .server_cert_validation = true,\n                .trusted_certs =\n                        avs_crypto_certificate_chain_info_from_path(optarg)\n            };\n            parsed_args->sw_mgmt_security_info =\n                    avs_net_security_info_from_certificates(cert_info);\n            break;\n        }\n        case 341: {\n            if (parsed_args->sw_mgmt_security_info.mode != AVS_NET_SECURITY_PSK\n                    && parsed_args->sw_mgmt_security_info.mode\n                                   != (avs_net_security_mode_t) -1) {\n                demo_log(ERROR, \"Multiple incompatible security information \"\n                                \"specified for software managment\");\n                goto finish;\n            }\n            if (parsed_args->sw_mgmt_security_info.mode == AVS_NET_SECURITY_PSK\n                    && parsed_args->sw_mgmt_security_info.data.psk.identity.desc\n                                       .source\n                                   != AVS_CRYPTO_DATA_SOURCE_EMPTY) {\n                demo_log(ERROR,\n                         \"--sw-mgmt-psk-identity specified more than once\");\n                goto finish;\n            }\n            uint8_t *identity_buf = NULL;\n            size_t identity_size = 0;\n            if (parse_hexstring(parsed_args, optarg, &identity_buf,\n                                &identity_size)) {\n                demo_log(ERROR, \"Invalid PSK identity for software managment\");\n                goto finish;\n            }\n            parsed_args->sw_mgmt_security_info.mode = AVS_NET_SECURITY_PSK;\n            parsed_args->sw_mgmt_security_info.data.psk.identity =\n                    avs_crypto_psk_identity_info_from_buffer(identity_buf,\n                                                             identity_size);\n            break;\n        }\n        case 342: {\n            if (parsed_args->sw_mgmt_security_info.mode != AVS_NET_SECURITY_PSK\n                    && parsed_args->sw_mgmt_security_info.mode\n                                   != (avs_net_security_mode_t) -1) {\n                demo_log(ERROR, \"Multiple incompatible security information \"\n                                \"specified for software managment\");\n                goto finish;\n            }\n            if (parsed_args->sw_mgmt_security_info.mode == AVS_NET_SECURITY_PSK\n                    && parsed_args->sw_mgmt_security_info.data.psk.key.desc\n                                       .source\n                                   != AVS_CRYPTO_DATA_SOURCE_EMPTY) {\n                demo_log(ERROR, \"--sw-mgmt-psk-key specified more than once\");\n                goto finish;\n            }\n            uint8_t *psk_buf = NULL;\n            size_t psk_size = 0;\n            if (parse_hexstring(parsed_args, optarg, &psk_buf, &psk_size)) {\n                demo_log(ERROR,\n                         \"Invalid pre-shared key for software managment\");\n                goto finish;\n            }\n            parsed_args->sw_mgmt_security_info.mode = AVS_NET_SECURITY_PSK;\n            parsed_args->sw_mgmt_security_info.data.psk.key =\n                    avs_crypto_psk_key_info_from_buffer(psk_buf, psk_size);\n            break;\n        }\n        case 343:\n            if (parse_double(\n                        optarg,\n                        &parsed_args->sw_mgmt_tx_params.ack_random_factor)) {\n                demo_log(ERROR, \"Expected ACK_RANDOM_FACTOR to be a floating \"\n                                \"point number\");\n                goto finish;\n            }\n            parsed_args->sw_mgmt_tx_params_modified = true;\n            break;\n        case 344: {\n            double ack_timeout_s;\n            if (parse_double(optarg, &ack_timeout_s)) {\n                demo_log(ERROR,\n                         \"Expected ACK_TIMEOUT to be a floating point number\");\n                goto finish;\n            }\n            parsed_args->sw_mgmt_tx_params.ack_timeout =\n                    avs_time_duration_from_fscalar(ack_timeout_s, AVS_TIME_S);\n            parsed_args->sw_mgmt_tx_params_modified = true;\n            break;\n        }\n        case 345: {\n            uint32_t max_retransmit;\n            if (parse_u32(optarg, &max_retransmit)) {\n                demo_log(ERROR, \"Expected MAX_RETRANSMIT to be an unsigned \"\n                                \"integer\");\n                goto finish;\n            }\n            parsed_args->sw_mgmt_tx_params.max_retransmit =\n                    (unsigned) max_retransmit;\n            parsed_args->sw_mgmt_tx_params_modified = true;\n            break;\n        }\n        case 346:\n            parsed_args->sw_mgmt_auto_suspend = true;\n            break;\n        case 347: {\n            double timeout_s;\n            if (parse_double(optarg, &timeout_s) || !isfinite(timeout_s)\n                    || timeout_s <= 0.0) {\n                demo_log(ERROR, \"Expected TCP request timeout to be a positive \"\n                                \"floating point number\");\n                goto finish;\n            }\n            parsed_args->sw_mgmt_tcp_request_timeout =\n                    avs_time_duration_from_fscalar(timeout_s, AVS_TIME_S);\n            break;\n        }\n#    endif // ANJAY_WITH_DOWNLOADER\n#endif     // ANJAY_WITH_MODULE_SW_MGMT\n        case 348:\n            parsed_args->connection_error_is_registration_failure = true;\n            break;\n#ifdef ANJAY_WITH_LWM2M_GATEWAY\n        case 'g': {\n            parsed_args->lwm2m_gateway_enabled = true;\n            break;\n        }\n#endif // ANJAY_WITH_LWM2M_GATEWAY\n#ifdef ANJAY_WITH_DOWNLOADER\n        case 349:\n            if (parse_size(optarg, &parsed_args->coap_downloader_retry_count)) {\n                demo_log(ERROR, \"Invalid retry count value: %s\", optarg);\n                goto finish;\n            }\n            break;\n        case 350: {\n            double delay_s;\n            if (parse_double(optarg, &delay_s) || !isfinite(delay_s)\n                    || delay_s <= 0.0) {\n                demo_log(ERROR, \"Expected retry delay to be a positive \"\n                                \"floating point number\");\n                goto finish;\n            }\n            parsed_args->coap_downloader_retry_delay =\n                    avs_time_duration_from_fscalar(delay_s, AVS_TIME_S);\n            break;\n        }\n#endif // ANJAY_WITH_DOWNLOADER\n#ifdef ANJAY_WITH_LWM2M11\n        case 351: {\n            if (num_servers == 0) {\n                demo_log(ERROR, \"Undefined server. Use --server-uri/-u first\");\n                goto finish;\n            }\n            int idx = num_servers - 1;\n            if (parse_u16(optarg,\n                          &parsed_args->connection_args.servers[idx]\n                                   .certificate_usage)) {\n                goto finish;\n            }\n            break;\n        }\n#endif // ANJAY_WITH_LWM2M11\n#ifdef WITH_DEMO_TRAFFIC_INTERCEPTOR\n        case 357: {\n            parsed_args->traffic_intercept_path = optarg;\n        }\n#endif // WITH_DEMO_TRAFFIC_INTERCEPTOR\n        case 0:\n            goto process;\n        }\n    }\nprocess:\n    if (argc <= 1) {\n        print_help_short(argv[0]);\n        goto finish;\n    }\n\n    retval = 0;\n    if (!parsed_args->endpoint_name) {\n        demo_log(ERROR,\n                 \"Endpoint name not specified, please use the -e option\");\n        retval = -1;\n    }\n    if (num_servers == 0\n#ifdef AVS_COMMONS_STREAM_WITH_FILE\n#    ifdef AVS_COMMONS_WITH_AVS_PERSISTENCE\n            && !(parsed_args->dm_persistence_file\n#        ifndef _WIN32\n                 && !access(parsed_args->dm_persistence_file, R_OK)\n#        endif // _WIN32\n                         )\n#    endif // AVS_COMMONS_WITH_AVS_PERSISTENCE\n#    ifdef ANJAY_WITH_MODULE_FACTORY_PROVISIONING\n            && !parsed_args->provisioning_file\n#    endif // ANJAY_WITH_MODULE_FACTORY_PROVISIONING\n#endif     // AVS_COMMONS_STREAM_WITH_FILE\n    ) {\n        demo_log(ERROR, \"At least one LwM2M Server URI needs to be specified, \"\n                        \"please use the -u option\");\n        retval = -1;\n    }\n    for (int i = 0; i < num_servers; ++i) {\n        server_entry_t *entry = &parsed_args->connection_args.servers[i];\n        entry->id = (anjay_ssid_t) (i + 1);\n        if (entry->security_iid == ANJAY_ID_INVALID) {\n            entry->security_iid = (anjay_iid_t) entry->id;\n        }\n        if (entry->server_iid == ANJAY_ID_INVALID) {\n            entry->server_iid = (anjay_iid_t) entry->id;\n        }\n    }\n    identity_set =\n            !!parsed_args->connection_args.public_cert_or_psk_identity_size;\n    key_set = !!parsed_args->connection_args.private_cert_or_psk_key_size;\n    if ((identity_set && (cert_path != default_cert_path))\n            || (key_set && (key_path != default_key_path))) {\n        demo_log(ERROR, \"Certificate information cannot be loaded both from \"\n                        \"file and immediate hex data at the same time\");\n        parsed_args->connection_args.security_mode = ANJAY_SECURITY_NOSEC;\n        retval = -1;\n    }\n    if (parsed_args->connection_args.security_mode == ANJAY_SECURITY_PSK) {\n        if (!identity_set\n                && clone_buffer(parsed_args,\n                                &parsed_args->connection_args\n                                         .public_cert_or_psk_identity,\n                                &parsed_args->connection_args\n                                         .public_cert_or_psk_identity_size,\n                                DEFAULT_PSK_IDENTITY,\n                                sizeof(DEFAULT_PSK_IDENTITY) - 1)) {\n            retval = -1;\n        }\n        if (!key_set\n                && clone_buffer(parsed_args,\n                                &parsed_args->connection_args\n                                         .private_cert_or_psk_key,\n                                &parsed_args->connection_args\n                                         .private_cert_or_psk_key_size,\n                                DEFAULT_PSK_KEY, sizeof(DEFAULT_PSK_KEY) - 1)) {\n            retval = -1;\n        }\n#ifdef ANJAY_WITH_SECURITY_STRUCTURED\n        if (use_external_security_info) {\n            // NOTE: psk_identity and psk_key take priority in\n            // security_object_reload()\n            parsed_args->connection_args\n                    .psk_identity = avs_crypto_psk_identity_info_from_buffer(\n                    parsed_args->connection_args.public_cert_or_psk_identity,\n                    parsed_args->connection_args\n                            .public_cert_or_psk_identity_size);\n            parsed_args->connection_args\n                    .psk_key = avs_crypto_psk_key_info_from_buffer(\n                    parsed_args->connection_args.private_cert_or_psk_key,\n                    parsed_args->connection_args.private_cert_or_psk_key_size);\n        }\n#endif // ANJAY_WITH_SECURITY_STRUCTURED\n    } else if (parsed_args->connection_args.security_mode\n                       == ANJAY_SECURITY_CERTIFICATE\n               || parsed_args->connection_args.security_mode\n                          == ANJAY_SECURITY_EST) {\n        if (identity_set ^ key_set) {\n            demo_log(ERROR, \"Setting public cert but not private cert (and \"\n                            \"other way around) makes little sense\");\n            retval = -1;\n        } else if (!identity_set) {\n#ifdef ANJAY_WITH_SECURITY_STRUCTURED\n            if (use_external_security_info) {\n                parsed_args->connection_args.public_cert =\n                        avs_crypto_certificate_chain_info_from_file(cert_path);\n                parsed_args->connection_args.private_key =\n                        avs_crypto_private_key_info_from_file(key_path, NULL);\n            } else\n#endif // ANJAY_WITH_SECURITY_STRUCTURED\n            {\n                if (load_buffer_from_file(\n                            parsed_args,\n                            &parsed_args->connection_args\n                                     .public_cert_or_psk_identity,\n                            &parsed_args->connection_args\n                                     .public_cert_or_psk_identity_size,\n                            cert_path)) {\n                    demo_log(ERROR, \"Could not load certificate from %s\",\n                             cert_path);\n                    retval = -1;\n                }\n                if (load_buffer_from_file(\n                            parsed_args,\n                            &parsed_args->connection_args\n                                     .private_cert_or_psk_key,\n                            &parsed_args->connection_args\n                                     .private_cert_or_psk_key_size,\n                            key_path)) {\n                    demo_log(ERROR, \"Could not load private key from %s\",\n                             key_path);\n                    retval = -1;\n                }\n            }\n        }\n#ifdef ANJAY_WITH_SECURITY_STRUCTURED\n        else if (use_external_security_info) {\n            // NOTE: public_cert and private_key take priority in\n            // security_object_reload()\n            parsed_args->connection_args.public_cert =\n                    avs_crypto_certificate_chain_info_from_buffer(\n                            parsed_args->connection_args\n                                    .public_cert_or_psk_identity,\n                            parsed_args->connection_args\n                                    .public_cert_or_psk_identity_size);\n            parsed_args->connection_args\n                    .private_key = avs_crypto_private_key_info_from_buffer(\n                    parsed_args->connection_args.private_cert_or_psk_key,\n                    parsed_args->connection_args.private_cert_or_psk_key_size,\n                    NULL);\n        }\n#endif // ANJAY_WITH_SECURITY_STRUCTURED\n        if (server_public_key_path\n                && load_buffer_from_file(\n                           parsed_args,\n                           &parsed_args->connection_args.server_public_key,\n                           &parsed_args->connection_args.server_public_key_size,\n                           server_public_key_path)) {\n            demo_log(ERROR, \"Could not load server public key from %s\",\n                     server_public_key_path);\n            retval = -1;\n        }\n    }\n#ifdef ANJAY_WITH_MODULE_FW_UPDATE\n    if (parsed_args->fw_security_info.mode == AVS_NET_SECURITY_PSK\n            && (parsed_args->fw_security_info.data.psk.identity.desc.source\n                        == AVS_CRYPTO_DATA_SOURCE_EMPTY\n                || parsed_args->fw_security_info.data.psk.key.desc.source\n                           == AVS_CRYPTO_DATA_SOURCE_EMPTY)) {\n        demo_log(ERROR, \"Both identity and key must be provided when using PSK \"\n                        \"for firmware upgrade security\");\n        retval = -1;\n    }\n#endif // ANJAY_WITH_MODULE_FW_UPDATE\nfinish:\n    if (retval) {\n        AVS_LIST_CLEAR(&parsed_args->allocated_buffers);\n#ifdef ANJAY_WITH_MODULE_ACCESS_CONTROL\n        AVS_LIST_CLEAR(&parsed_args->access_entries);\n#endif // ANJAY_WITH_MODULE_ACCESS_CONTROL\n        avs_free(parsed_args->default_ciphersuites);\n        parsed_args->default_ciphersuites = NULL;\n    }\n    avs_free(default_cert_path);\n    avs_free(default_key_path);\n    return retval;\n}\n"
  },
  {
    "path": "demo/demo_args.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef DEMO_ARGS_H\n#define DEMO_ARGS_H\n\n#include <anjay/access_control.h>\n#include <anjay/anjay.h>\n#include <anjay/anjay_config.h>\n\n#ifdef ANJAY_WITH_MODULE_FW_UPDATE\n#    include <anjay/fw_update.h>\n#endif // ANJAY_WITH_MODULE_FW_UPDATE\n\n#ifdef ANJAY_WITH_MODULE_ADVANCED_FW_UPDATE\n#    include <anjay/advanced_fw_update.h>\n#endif // ANJAY_WITH_MODULE_ADVANCED_FW_UPDATE\n\n#ifdef ANJAY_WITH_MODULE_SW_MGMT\n#    include \"software_mgmt.h\"\n#endif // ANJAY_WITH_MODULE_SW_MGMT\n\n#include \"demo_utils.h\"\n#include \"net_traffic_interceptor.h\"\n#include \"objects.h\"\n\ntypedef struct access_entry {\n    anjay_ssid_t ssid;\n    anjay_oid_t oid;\n    anjay_iid_t iid;\n    anjay_access_mask_t mask;\n} access_entry_t;\n\ntypedef struct cmdline_args {\n    const char *endpoint_name;\n    uint16_t udp_listen_port;\n    avs_net_ssl_version_t dtls_version;\n    server_connection_args_t connection_args;\n    const char *location_csv;\n    time_t location_update_frequency_s;\n#ifdef ANJAY_WITH_MODULE_ACCESS_CONTROL\n    AVS_LIST(access_entry_t) access_entries;\n#endif // ANJAY_WITH_MODULE_ACCESS_CONTROL\n    int32_t inbuf_size;\n    int32_t outbuf_size;\n    int32_t msg_cache_size;\n    bool confirmable_notifications;\n    bool disable_stdin;\n#ifdef ANJAY_WITH_DOWNLOADER\n    size_t coap_downloader_retry_count;\n    avs_time_duration_t coap_downloader_retry_delay;\n#endif // ANJAY_WITH_DOWNLOADER\n#ifdef ANJAY_WITH_MODULE_FW_UPDATE\n    const char *fw_updated_marker_path;\n    avs_net_security_info_t fw_security_info;\n    /**\n     * If nonzero (not @ref ANJAY_FW_UPDATE_RESULT_INITIAL), Firmware Update\n     * object will be initialized in UPDATING state. In that case,\n     * @ref anjay_fw_update_set_result will be used after a while to trigger a\n     * transition to this update result. This simulates a FOTA procedure during\n     * which the client is restarted while upgrade is still in progress.\n     */\n    anjay_fw_update_result_t fw_update_delayed_result;\n#    ifdef ANJAY_WITH_SEND\n    bool fw_update_use_send;\n#    endif // ANJAY_WITH_SEND\n    bool fw_update_auto_suspend;\n#endif // ANJAY_WITH_MODULE_FW_UPDATE\n#ifdef ANJAY_WITH_MODULE_ADVANCED_FW_UPDATE\n    const char *advanced_fw_updated_marker_path;\n    avs_net_security_info_t advanced_fw_security_info;\n    /**\n     * If nonzero (not @ref ANJAY_ADVANCED_FW_UPDATE_RESULT_INITIAL),\n     * Advanced Firmware Update object will be initialized in UPDATING state.\n     * In that case, @ref anjay_advanced_fw_update_set_state_and_result will be\n     * used after a while to trigger a transition to this update result.\n     * This simulates a FOTA procedure during which the client is restarted\n     * while upgrade is still in progress.\n     */\n    anjay_advanced_fw_update_result_t advanced_fw_update_delayed_result;\n#    ifdef ANJAY_WITH_SEND\n    bool advanced_fw_update_use_send;\n#    endif // ANJAY_WITH_SEND\n    bool advanced_fw_update_auto_suspend;\n    /**\n     * This is a file path to file with original image. After additional\n     * image is downloaded, update can be performed. Updating additional\n     * image (other than main application) includes only files comparison.\n     */\n    const char *original_img_file_path;\n#endif // ANJAY_WITH_MODULE_ADVANCED_FW_UPDATE\n#ifdef ANJAY_WITH_MODULE_SW_MGMT\n    const char *sw_mgmt_persistence_file;\n    bool sw_mgmt_terminate_after_downloading;\n    bool sw_mgmt_disable_repeated_activation_deactivation;\n#    ifdef ANJAY_WITH_DOWNLOADER\n    avs_net_security_info_t sw_mgmt_security_info;\n#    endif // ANJAY_WITH_DOWNLOADER\n    /**\n     * If delayed-sw-mgmt-result is passed, first (iid=0) Software Management\n     * object will be initialized in\n     * @ref ANJAY_SW_MGMT_INITIAL_STATE_DELIVERED state. In that case, @ref\n     * anjay_sw_mgmt_finish_pkg_install will be used after a while to make\n     * transition to Installed state or report installation error. This\n     * simulates a installation procedure during which the client is restarted\n     * while the procedure is still in progress.\n     */\n    uint8_t sw_mgmt_delayed_first_instance_install_result;\n    bool sw_mgmt_auto_suspend;\n#endif // ANJAY_WITH_MODULE_SW_MGMT\n#ifdef AVS_COMMONS_STREAM_WITH_FILE\n#    ifdef ANJAY_WITH_ATTR_STORAGE\n    const char *attr_storage_file;\n#    endif // ANJAY_WITH_ATTR_STORAGE\n#    ifdef AVS_COMMONS_WITH_AVS_PERSISTENCE\n    const char *dm_persistence_file;\n#    endif // AVS_COMMONS_WITH_AVS_PERSISTENCE\n#endif     // AVS_COMMONS_STREAM_WITH_FILE\n\n    bool disable_legacy_server_initiated_bootstrap;\n#ifdef AVS_COMMONS_STREAM_WITH_FILE\n#endif // AVS_COMMONS_STREAM_WITH_FILE\n\n#ifdef ANJAY_WITH_MODULE_FACTORY_PROVISIONING\n    const char *provisioning_file;\n#endif // ANJAY_WITH_MODULE_FACTORY_PROVISIONING\n    avs_coap_udp_tx_params_t tx_params;\n    avs_net_dtls_handshake_timeouts_t dtls_hs_tx_params;\n#ifdef ANJAY_WITH_MODULE_FW_UPDATE\n    /**\n     * This flag allows to enable callback providing tx_params for firmware\n     * update only if some of parameters were changed by passing proper command\n     * line argument to demo. Otherwise tx_params should be inherited from\n     * Anjay.\n     */\n    bool fwu_tx_params_modified;\n    avs_coap_udp_tx_params_t fwu_tx_params;\n    avs_time_duration_t fwu_tcp_request_timeout;\n#endif // ANJAY_WITH_MODULE_FW_UPDATE\n#ifdef ANJAY_WITH_MODULE_ADVANCED_FW_UPDATE\n    /**\n     * This flag allows to enable callback providing tx_params for firmware\n     * update only if some of parameters were changed by passing proper command\n     * line argument to demo. Otherwise tx_params should be inherited from\n     * Anjay.\n     */\n    bool advanced_fwu_tx_params_modified;\n    avs_coap_udp_tx_params_t advanced_fwu_tx_params;\n    avs_time_duration_t advanced_fwu_tcp_request_timeout;\n#endif // ANJAY_WITH_MODULE_ADVANCED_FW_UPDATE\n#ifdef ANJAY_WITH_MODULE_SW_MGMT\n    /**\n     * This flag allows to enable callback providing tx_params for software\n     * management only if some of parameters were changed by passing proper\n     * command line argument to demo. Otherwise tx_params should be inherited\n     * from Anjay.\n     */\n    bool sw_mgmt_tx_params_modified;\n    avs_coap_udp_tx_params_t sw_mgmt_tx_params;\n    avs_time_duration_t sw_mgmt_tcp_request_timeout;\n#endif // ANJAY_WITH_MODULE_SW_MGMT\n#ifdef ANJAY_WITH_LWM2M11\n    anjay_lwm2m_version_config_t lwm2m_version_config;\n#endif // ANJAY_WITH_LWM2M11\n    size_t stored_notification_limit;\n\n    bool prefer_hierarchical_formats;\n    bool update_immediately_on_dm_change;\n    bool enable_self_notify;\n    bool connection_error_is_registration_failure;\n    bool use_connection_id;\n    bool start_offline;\n\n    uint32_t *default_ciphersuites;\n    size_t default_ciphersuites_count;\n    bool prefer_same_socket_downloads;\n#ifdef ANJAY_WITH_LWM2M11\n    const char *pkix_trust_store;\n    bool rebuild_client_cert_chain;\n#endif // ANJAY_WITH_LWM2M11\n\n    bool alternative_logger;\n\n#if defined(ANJAY_WITH_LWM2M11) && defined(WITH_AVS_COAP_TCP)\n    avs_time_duration_t tcp_request_timeout;\n#endif // defined(ANJAY_WITH_LWM2M11) && defined(WITH_AVS_COAP_TCP)\n\n    AVS_LIST(anjay_demo_allocated_buffer_t) allocated_buffers;\n#ifdef ANJAY_WITH_LWM2M_GATEWAY\n    bool lwm2m_gateway_enabled;\n#endif // ANJAY_WITH_LWM2M_GAYEWAY\n#ifdef WITH_DEMO_TRAFFIC_INTERCEPTOR\n    const char *traffic_intercept_path;\n#endif // WITH_DEMO_TRAFFIC_INTERCEPTOR\n} cmdline_args_t;\n\nint demo_parse_argv(cmdline_args_t *parsed_args, int argc, char **argv);\n\n#endif\n"
  },
  {
    "path": "demo/demo_cmds.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include \"demo_cmds.h\"\n#include \"demo.h\"\n#include \"demo_utils.h\"\n#ifdef ANJAY_WITH_MODULE_FW_UPDATE\n#    include \"firmware_update.h\"\n#endif // ANJAY_WITH_MODULE_FW_UPDATE\n\n#ifdef ANJAY_WITH_MODULE_ADVANCED_FW_UPDATE\n#    include \"advanced_firmware_update.h\"\n#endif // ANJAY_WITH_MODULE_ADVANCED_FW_UPDATE\n\n#ifdef ANJAY_WITH_MODULE_SW_MGMT\n#    include \"software_mgmt.h\"\n#endif // ANJAY_WITH_MODULE_SW_MGMT\n\n#include <ctype.h>\n#include <errno.h>\n#include <inttypes.h>\n#include <string.h>\n\n#include <anjay/anjay.h>\n#include <anjay/attr_storage.h>\n#include <anjay/ipso_objects.h>\n\n#ifdef WITH_DEMO_USE_STANDALONE_OBJECTS\n#    include \"../standalone/security/standalone_security.h\"\n#else // WITH_DEMO_USE_STANDALONE_OBJECTS\n#    include <anjay/security.h>\n#endif // WITH_DEMO_USE_STANDALONE_OBJECTS\n\n#ifdef ANJAY_WITH_SEND\n#    include <anjay/lwm2m_send.h>\n#endif // ANJAY_WITH_SEND\n\n#ifdef ANJAY_WITH_LWM2M_GATEWAY\n#    include \"lwm2m_gateway.h\"\n#    include <anjay/lwm2m_gateway.h>\n#endif // ANJAY_WITH_LWM2M_GATEWAY\n\n#include <avsystem/commons/avs_memory.h>\n\n#define MAX_SEND_RESOURCES 32\n\n#ifdef ANJAY_WITH_LWM2M_GATEWAY\n#    define LWM2M_GATEWAY_PATH_LOG_PART \" | dev_id /OID/IID/RID\"\n#    define LWM2M_GATEWAY_DEVID_LOG_PART \\\n        \" dev_id must be in range \"      \\\n        \"[0,\" AVS_QUOTE_MACRO(LWM2M_GATEWAY_END_DEVICE_RANGE) \"].\"\n\nstatic inline int verify_device_count(anjay_iid_t end_dev_iid) {\n    if (end_dev_iid >= LWM2M_GATEWAY_END_DEVICE_COUNT) {\n        demo_log(ERROR, LWM2M_GATEWAY_DEVID_LOG_PART);\n        return -1;\n    }\n    return 0;\n}\n#else // ANJAY_WITH_LWM2M_GATEWAY\n#    define LWM2M_GATEWAY_PATH_LOG_PART \"\"\n#    define LWM2M_GATEWAY_DEVID_LOG_PART \"\"\n#endif // ANJAY_WITH_LWM2M_GATEWAY\n\nstatic int parse_ssid(const char *text, anjay_ssid_t *out_ssid) {\n    unsigned id;\n    if (sscanf(text, \"%u\", &id) < 1 || id > UINT16_MAX) {\n        return -1;\n    }\n    *out_ssid = (uint16_t) id;\n    return 0;\n}\n\nstatic void cmd_send_register(anjay_demo_t *demo, const char *args_string) {\n    anjay_ssid_t ssid = ANJAY_SSID_ANY;\n    if (*args_string && parse_ssid(args_string, &ssid)) {\n        demo_log(ERROR, \"invalid Short Server ID: %s\", args_string);\n        return;\n    }\n\n    if (anjay_schedule_register(demo->anjay, ssid)) {\n        demo_log(ERROR, \"could not schedule registration\");\n    } else if (ssid == ANJAY_SSID_ANY) {\n        demo_log(INFO, \"registration scheduled for all servers\");\n    } else {\n        demo_log(INFO, \"registration scheduled for server %\" PRIu16, ssid);\n    }\n}\n\nstatic void cmd_send_update(anjay_demo_t *demo, const char *args_string) {\n    anjay_ssid_t ssid = ANJAY_SSID_ANY;\n    if (*args_string && parse_ssid(args_string, &ssid)) {\n        demo_log(ERROR, \"invalid Short Server ID: %s\", args_string);\n        return;\n    }\n\n    if (anjay_schedule_registration_update(demo->anjay, ssid)) {\n        demo_log(ERROR, \"could not schedule registration update\");\n    } else if (ssid == ANJAY_SSID_ANY) {\n        demo_log(INFO, \"registration update scheduled for all servers\");\n    } else {\n        demo_log(INFO, \"registration update scheduled for server %\" PRIu16,\n                 ssid);\n    }\n}\n\nstatic void cmd_reconnect_server(anjay_demo_t *demo, const char *args_string) {\n    anjay_ssid_t ssid = ANJAY_SSID_ANY;\n    if (*args_string && parse_ssid(args_string, &ssid)) {\n        demo_log(ERROR, \"invalid Short Server ID: %s\", args_string);\n        return;\n    }\n\n    if (anjay_server_schedule_reconnect(demo->anjay, ssid)) {\n        demo_log(ERROR, \"could not enable server with SSID %\" PRIu16, ssid);\n        return;\n    }\n}\n\nstatic int parse_transports(const char *text,\n                            anjay_transport_set_t *out_transport_set) {\n    char *text_copy = avs_strdup(text);\n    if (!text_copy) {\n        demo_log(ERROR, \"Out of memory\");\n        return -1;\n    }\n    *out_transport_set = ANJAY_TRANSPORT_SET_ALL;\n    bool found = false;\n    bool error = false;\n    char *text_arg = text_copy;\n    char *saveptr = NULL;\n    const char *token = NULL;\n    while ((token = avs_strtok(text_arg, AVS_SPACES \",\", &saveptr))) {\n        text_arg = NULL;\n        if (!found) {\n            memset(out_transport_set, 0, sizeof(*out_transport_set));\n            found = true;\n        }\n        if (strcmp(token, \"ip\") == 0) {\n            out_transport_set->udp = true;\n            out_transport_set->tcp = true;\n        } else if (strcmp(token, \"udp\") == 0) {\n            out_transport_set->udp = true;\n        } else if (strcmp(token, \"tcp\") == 0) {\n            out_transport_set->tcp = true;\n        } else {\n            demo_log(ERROR, \"Unrecognized transport: %s\", token);\n            error = true;\n        }\n    }\n    avs_free(text_copy);\n    return error ? -1 : 0;\n}\n\nstatic void cmd_reconnect(anjay_demo_t *demo, const char *args_string) {\n    anjay_transport_set_t transport_set;\n    if (!parse_transports(args_string, &transport_set)) {\n        if (anjay_transport_schedule_reconnect(demo->anjay, transport_set)) {\n            demo_log(ERROR, \"could not schedule reconnect\");\n        } else {\n            demo_log(INFO, \"reconnect scheduled\");\n        }\n    }\n}\n\n#ifdef ANJAY_WITH_MODULE_FW_UPDATE\nstatic void cmd_set_fw_package_path(anjay_demo_t *demo,\n                                    const char *args_string) {\n    const char *path = args_string;\n    while (isspace(*path)) {\n        ++path;\n    }\n\n    firmware_update_set_package_path(&demo->fw_update, path);\n}\n#endif // ANJAY_WITH_MODULE_FW_UPDATE\n\n#ifdef ANJAY_WITH_MODULE_ADVANCED_FW_UPDATE\nstatic void cmd_set_afu_package_path(anjay_demo_t *demo,\n                                     const char *args_string) {\n    const char *path = args_string;\n    while (isspace(*path)) {\n        ++path;\n    }\n\n    /* This allows setting package path only for first (APP) image */\n    advanced_fw_update_logic_t *fw_logic_app =\n            demo->advanced_fw_update_logic_table;\n    assert(fw_logic_app->iid == FW_UPDATE_IID_APP);\n    advanced_firmware_update_set_package_path(fw_logic_app, path);\n}\n\nstatic void cmd_get_afu_deadline(anjay_demo_t *demo, const char *args_string) {\n    (void) args_string;\n    int64_t update_deadline_timestamp = 0;\n    avs_time_real_to_scalar(&update_deadline_timestamp, AVS_TIME_S,\n                            anjay_advanced_fw_update_get_deadline(\n                                    demo->anjay, FW_UPDATE_IID_APP));\n    printf(\"AFU_APP_UPDATE_DEADLINE==%\" PRId64 \"\\n\", update_deadline_timestamp);\n}\n\nstatic void cmd_set_afu_result(anjay_demo_t *demo, const char *args_string) {\n    int result;\n    if (sscanf(args_string, \" %d\", &result) != 1) {\n        demo_log(ERROR, \"Advanced Firmware Update result not specified\");\n        return;\n    }\n    if (anjay_advanced_fw_update_set_state_and_result(\n                demo->anjay,\n                FW_UPDATE_IID_APP,\n                (anjay_advanced_fw_update_state_t)\n                        ANJAY_ADVANCED_FW_UPDATE_STATE_IDLE,\n                (anjay_advanced_fw_update_result_t) result)) {\n        demo_log(ERROR,\n                 \"Advanced Firmware Update result set for APP image at runtime \"\n                 \"failed.\");\n    }\n}\n\nstatic void cmd_afu_suspend(anjay_demo_t *demo, const char *args_string) {\n    (void) args_string;\n    anjay_advanced_fw_update_pull_suspend(demo->anjay);\n}\n\nstatic void cmd_afu_reconnect(anjay_demo_t *demo, const char *args_string) {\n    (void) args_string;\n    anjay_advanced_fw_update_pull_reconnect(demo->anjay);\n}\n#endif // ANJAY_WITH_MODULE_ADVANCED_FW_UPDATE\n\n#ifdef ANJAY_WITH_MODULE_SW_MGMT\nstatic void cmd_set_sw_mgmt_package_path(anjay_demo_t *demo,\n                                         const char *args_string) {\n    anjay_iid_t iid;\n\n    char *path = (char *) avs_malloc(strlen(args_string) + 1);\n    if (!path) {\n        demo_log(ERROR, \"Out of memory\");\n        return;\n    }\n    path[0] = '\\0';\n\n    if (sscanf(args_string, \"%\" SCNu16 \"%s\", &iid, path) != 2\n            || iid >= SW_MGMT_PACKAGE_COUNT) {\n        demo_log(ERROR, \"invalid parameters\");\n        avs_free(path);\n        return;\n    }\n\n    sw_mgmt_set_package_path(&demo->sw_mgmt_table[iid], path);\n    avs_free(path);\n}\n\nstatic void cmd_set_sw_mgmt_install_result(anjay_demo_t *demo,\n                                           const char *args_string) {\n    anjay_iid_t iid;\n    uint8_t result;\n\n    if (sscanf(args_string, \"%\" SCNu16 \"%\" SCNu8, &iid, &result) != 2\n            || (result != 0 && result != 1)) {\n        demo_log(ERROR, \"invalid parameters\");\n        return;\n    }\n\n    anjay_sw_mgmt_finish_pkg_install(\n            demo->anjay, iid,\n            result == 1 ? ANJAY_SW_MGMT_FINISH_PKG_INSTALL_SUCCESS_INACTIVE\n                        : ANJAY_SW_MGMT_FINISH_PKG_INSTALL_FAILURE);\n}\n\nstatic void cmd_sw_mgmt_suspend(anjay_demo_t *demo, const char *args_string) {\n    (void) args_string;\n    anjay_sw_mgmt_pull_suspend(demo->anjay);\n}\n\nstatic void cmd_sw_mgmt_reconnect(anjay_demo_t *demo, const char *args_string) {\n    (void) args_string;\n    anjay_sw_mgmt_pull_reconnect(demo->anjay);\n}\n#endif // ANJAY_WITH_MODULE_SW_MGMT\n\nstatic void cmd_open_location_csv(anjay_demo_t *demo, const char *args_string) {\n    const anjay_dm_object_def_t **location_obj =\n            demo_find_object(demo, DEMO_OID_LOCATION);\n    if (!location_obj) {\n        demo_log(ERROR, \"Location object not registered\");\n        return;\n    }\n\n    char *filename = (char *) avs_malloc(strlen(args_string) + 1);\n    if (!filename) {\n        demo_log(ERROR, \"Out of memory\");\n        return;\n    }\n    filename[0] = '\\0';\n    unsigned long frequency_s = 1;\n    sscanf(args_string, \"%s %lu\", filename, &frequency_s);\n    if (!location_open_csv(location_obj, filename, (time_t) frequency_s)) {\n        demo_log(INFO, \"Successfully opened CSV file\");\n    }\n    avs_free(filename);\n}\n\nstatic size_t count_servers(const server_connection_args_t *args) {\n    size_t num_servers = 0;\n    const server_entry_t *server;\n    DEMO_FOREACH_SERVER_ENTRY(server, args) {\n        ++num_servers;\n    }\n    return num_servers;\n}\n\nstatic int add_server(anjay_demo_t *demo, const char *uri) {\n    size_t num_servers = count_servers(demo->connection_args);\n    if (num_servers >= MAX_SERVERS) {\n        demo_log(ERROR, \"Maximum number of servers reached\");\n        return -1;\n    }\n    size_t uri_size = strlen(uri) + 1;\n    AVS_LIST(anjay_demo_allocated_buffer_t) copied_uri = (AVS_LIST(\n            anjay_demo_allocated_buffer_t)) AVS_LIST_NEW_BUFFER(uri_size);\n    if (!copied_uri) {\n        demo_log(ERROR, \"Out of memory\");\n        return -1;\n    }\n    memcpy(copied_uri->data, uri, uri_size);\n    AVS_LIST_INSERT(&demo->allocated_buffers, copied_uri);\n\n    server_entry_t *entry = &demo->connection_args->servers[num_servers];\n    *entry = demo->connection_args->servers[num_servers - 1];\n    entry->id = (anjay_ssid_t) (num_servers + 1);\n    entry->uri = copied_uri->data;\n    entry->security_iid = (anjay_iid_t) entry->id;\n    entry->server_iid = (anjay_iid_t) entry->id;\n    demo_log(INFO, \"Added new server, ID == %d\", (int) (num_servers + 1));\n    return 0;\n}\n\nstatic void cmd_add_server(anjay_demo_t *demo, const char *args_string) {\n    const char *uri = args_string;\n    while (isspace(*uri)) {\n        ++uri;\n    }\n\n    if (add_server(demo, uri)) {\n        return;\n    }\n    demo_reload_servers(demo);\n}\n\nstatic void cmd_trim_servers(anjay_demo_t *demo, const char *args_string) {\n    size_t num_servers = count_servers(demo->connection_args);\n    unsigned number;\n    if (sscanf(args_string, \"%u\", &number) != 1 || number > num_servers) {\n        demo_log(ERROR, \"Invalid servers number: %s\", args_string);\n        return;\n    }\n\n    for (size_t i = number; i < num_servers; ++i) {\n        demo->connection_args->servers[i].uri = NULL;\n    }\n    demo_reload_servers(demo);\n}\n\nstatic void cmd_socket_count(anjay_demo_t *demo, const char *args_string) {\n    (void) args_string;\n    printf(\"SOCKET_COUNT==%lu\\n\",\n           (unsigned long) AVS_LIST_SIZE(anjay_get_sockets(demo->anjay)));\n}\n\nstatic void cmd_get_port(anjay_demo_t *demo, const char *args_string) {\n    int index;\n    if (sscanf(args_string, \"%d\", &index) != 1) {\n        demo_log(ERROR, \"Invalid index: %s\", args_string);\n        return;\n    }\n\n    AVS_LIST(avs_net_socket_t *const) sockets = anjay_get_sockets(demo->anjay);\n    int num_sockets = (int) AVS_LIST_SIZE(sockets);\n    if (index < 0) {\n        index = num_sockets + index;\n    }\n    if (index < 0 || index >= num_sockets) {\n        demo_log(ERROR, \"Index out of range: %d; num_sockets == %d\", index,\n                 num_sockets);\n    }\n    char port[16] = \"0\";\n    AVS_LIST(avs_net_socket_t *const) socket =\n            AVS_LIST_NTH(sockets, (size_t) index);\n    if (socket && *socket) {\n        avs_net_socket_get_local_port(*socket, port, sizeof(port));\n    }\n    printf(\"PORT==%s\\n\", port);\n}\n\nstatic void cmd_get_transport(anjay_demo_t *demo, const char *args_string) {\n    int index;\n    if (sscanf(args_string, \"%d\", &index) != 1) {\n        demo_log(ERROR, \"Invalid index: %s\", args_string);\n        return;\n    }\n\n    AVS_LIST(const anjay_socket_entry_t) entries =\n            anjay_get_socket_entries(demo->anjay);\n    int num_sockets = (int) AVS_LIST_SIZE(entries);\n    if (index < 0) {\n        index = num_sockets + index;\n    }\n    if (index < 0 || index >= num_sockets) {\n        demo_log(ERROR, \"Index out of range: %d; num_sockets == %d\", index,\n                 num_sockets);\n        return;\n    }\n    AVS_LIST(const anjay_socket_entry_t) entry =\n            AVS_LIST_NTH(entries, (size_t) index);\n    switch (entry->transport) {\n    case ANJAY_SOCKET_TRANSPORT_UDP:\n        puts(\"TRANSPORT==UDP\");\n        break;\n    case ANJAY_SOCKET_TRANSPORT_TCP:\n        puts(\"TRANSPORT==TCP\");\n        break;\n    default:\n        printf(\"TRANSPORT==%d\\n\", (int) entry->transport);\n    }\n}\n\nstatic void cmd_non_lwm2m_socket_count(anjay_demo_t *demo,\n                                       const char *args_string) {\n    (void) args_string;\n    AVS_LIST(const anjay_socket_entry_t) entry =\n            anjay_get_socket_entries(demo->anjay);\n    unsigned long non_lwm2m_sockets = 0;\n    AVS_LIST_ITERATE(entry) {\n        if (entry->ssid == ANJAY_SSID_ANY\n                && entry->transport != ANJAY_SOCKET_TRANSPORT_SMS) {\n            ++non_lwm2m_sockets;\n        }\n    }\n    printf(\"NON_LWM2M_SOCKET_COUNT==%lu\\n\", non_lwm2m_sockets);\n}\n\nstatic void cmd_enter_offline(anjay_demo_t *demo, const char *args_string) {\n    anjay_transport_set_t transport_set;\n    if (!parse_transports(args_string, &transport_set)) {\n        int result = anjay_transport_enter_offline(demo->anjay, transport_set);\n        demo_log(INFO, \"anjay_transport_enter_offline(), result == %d\", result);\n    }\n}\n\nstatic void cmd_exit_offline(anjay_demo_t *demo, const char *args_string) {\n    anjay_transport_set_t transport_set;\n    if (!parse_transports(args_string, &transport_set)) {\n        int result = anjay_transport_exit_offline(demo->anjay, transport_set);\n        demo_log(INFO, \"anjay_transport_exit_offline(), result == %d\", result);\n    }\n}\n\nstatic void cmd_notify(anjay_demo_t *demo, const char *args_string) {\n    anjay_oid_t oid;\n    anjay_iid_t iid;\n    anjay_rid_t rid;\n    if (sscanf(args_string, \" /%\" SCNu16 \"/%\" SCNu16 \"/%\" SCNu16, &oid, &iid,\n               &rid)\n            == 3) {\n        (void) anjay_notify_changed(demo->anjay, oid, iid, rid);\n    } else if (sscanf(args_string, \" /%\" SCNu16, &oid) == 1) {\n        (void) anjay_notify_instances_changed(demo->anjay, oid);\n    } else {\n        demo_log(WARNING, \"notify usage:\\n\"\n                          \"1. notify /OID\\n\"\n                          \"2. notify /OID/IID/RID\");\n        return;\n    }\n}\n\n#ifdef ANJAY_WITH_SEND\nstatic void send_finished_handler(anjay_t *anjay,\n                                  anjay_ssid_t ssid,\n                                  const anjay_send_batch_t *batch,\n                                  int result,\n                                  void *data) {\n    (void) anjay;\n    (void) ssid;\n    (void) batch;\n    (void) data;\n    demo_log(INFO, \"SEND FINISHED HANDLER: %d\", result);\n}\n\ntypedef anjay_send_result_t\nanjay_send_func_t(anjay_t *anjay,\n                  anjay_ssid_t ssid,\n                  const anjay_send_batch_t *data,\n                  anjay_send_finished_handler_t *finished_handler,\n                  void *finished_handler_data);\n\nstatic void print_send_usage(const char *command) {\n    demo_log(WARNING,\n             \"%s usage: %s SSID (/OID/IID/RID\" LWM2M_GATEWAY_PATH_LOG_PART\n             \") [...]\",\n             command, command);\n}\n\n#    ifdef ANJAY_WITH_LWM2M_GATEWAY\nstatic bool all_paths_have_same_iid(const anjay_iid_t *iids,\n                                    size_t paths_count) {\n    anjay_iid_t iid = iids[0];\n    for (size_t i = 1; i < paths_count; i++) {\n        if (iids[i] != iid) {\n            return false;\n        }\n    }\n    return true;\n}\n#    endif // ANJAY_WITH_LWM2M_GATEWAY\n\nstatic void cmd_send_impl(anjay_demo_t *demo,\n                          const char *command,\n                          anjay_send_func_t *send_func,\n                          const char *args_string) {\n    anjay_ssid_t ssid;\n    if (sscanf(args_string, \" %\" SCNu16, &ssid) != 1) {\n        print_send_usage(command);\n        return;\n    }\n    args_string++;\n\n    anjay_send_batch_builder_t *builder = anjay_send_batch_builder_new();\n    if (!builder) {\n        demo_log(ERROR, \"Out of memory\");\n        return;\n    }\n\n    anjay_send_resource_path_t paths[MAX_SEND_RESOURCES];\n#    ifdef ANJAY_WITH_LWM2M_GATEWAY\n    anjay_iid_t gateway_iids[MAX_SEND_RESOURCES];\n    memset(gateway_iids, ANJAY_ID_INVALID, sizeof(gateway_iids));\n#    endif // ANJAY_WITH_LWM2M_GATEWAY\n    size_t paths_count = 0;\n\n    while ((args_string = strchr(args_string, ' '))) {\n        if (paths_count == MAX_SEND_RESOURCES) {\n            demo_log(ERROR, \"Max. %d resources allowed in Send\",\n                     MAX_SEND_RESOURCES);\n            anjay_send_batch_builder_cleanup(&builder);\n        }\n\n        args_string++;\n        anjay_oid_t oid;\n        anjay_iid_t iid;\n        anjay_rid_t rid;\n        int consumed;\n#    ifdef ANJAY_WITH_LWM2M_GATEWAY\n        anjay_iid_t gateway_iid;\n        if (sscanf(args_string,\n                   \"%\" SCNu16 \" /%\" SCNu16 \"/%\" SCNu16 \"/%\" SCNu16 \"%n\",\n                   &gateway_iid, &oid, &iid, &rid, &consumed)\n                == 4) {\n            gateway_iids[paths_count] = gateway_iid;\n        } else\n#    endif // ANJAY_WITH_LWM2M_GATEWAY\n                if (sscanf(args_string,\n                           \"/%\" SCNu16 \"/%\" SCNu16 \"/%\" SCNu16 \"%n\", &oid, &iid,\n                           &rid, &consumed)\n                    != 3) {\n            print_send_usage(command);\n            anjay_send_batch_builder_cleanup(&builder);\n            return;\n        }\n        args_string += consumed;\n\n        paths[paths_count++] = (anjay_send_resource_path_t) { oid, iid, rid };\n    }\n\n    int result = 0;\n#    ifdef ANJAY_WITH_LWM2M_GATEWAY\n    if (paths_count > 1 && all_paths_have_same_iid(gateway_iids, paths_count)) {\n        // For this function timestamp value is encoded only once - this is\n        // required by Integration Tests\n        if (gateway_iids[0] == ANJAY_ID_INVALID) {\n            result = anjay_send_batch_data_add_current_multiple(\n                    builder, demo->anjay, paths, paths_count);\n        } else {\n            result = anjay_lwm2m_gateway_send_batch_data_add_current_multiple(\n                    builder, demo->anjay, gateway_iids[0], paths, paths_count);\n        }\n    } else {\n        for (size_t i = 0; i < paths_count; i++) {\n            if (gateway_iids[i] == ANJAY_ID_INVALID) {\n                result = anjay_send_batch_data_add_current(builder, demo->anjay,\n                                                           paths[i].oid,\n                                                           paths[i].iid,\n                                                           paths[i].rid);\n            } else {\n                result = anjay_lwm2m_gateway_send_batch_data_add_current(\n                        builder, demo->anjay, gateway_iids[i], paths[i].oid,\n                        paths[i].iid, paths[i].rid);\n            }\n            if (result) {\n                break;\n            }\n        }\n    }\n#    else  // ANJAY_WITH_LWM2M_GATEWAY\n    if (paths_count == 1) {\n        result = anjay_send_batch_data_add_current(\n                builder, demo->anjay, paths[0].oid, paths[0].iid, paths[0].rid);\n    } else if (paths_count) {\n        result =\n                anjay_send_batch_data_add_current_multiple(builder, demo->anjay,\n                                                           paths, paths_count);\n    }\n#    endif // ANJAY_WITH_LWM2M_GATEWAY\n    if (result) {\n        demo_log(ERROR, \"Error during reading values from data model\");\n        anjay_send_batch_builder_cleanup(&builder);\n        return;\n    }\n\n    anjay_send_batch_t *data = anjay_send_batch_builder_compile(&builder);\n    if (!data) {\n        demo_log(ERROR, \"Out of memory\");\n        anjay_send_batch_builder_cleanup(&builder);\n        return;\n    }\n\n    const anjay_send_result_t send_result =\n            send_func(demo->anjay, ssid, data, send_finished_handler, NULL);\n    if (send_result) {\n        demo_log(ERROR, \"cannot perform LwM2M Send, result: %d\",\n                 (int) send_result);\n    }\n\n    anjay_send_batch_release(&data);\n}\n\nstatic void cmd_send(anjay_demo_t *demo, const char *args_string) {\n    cmd_send_impl(demo, \"send\", anjay_send, args_string);\n}\n\nstatic void cmd_send_deferrable(anjay_demo_t *demo, const char *args_string) {\n    cmd_send_impl(demo, \"send_deferrable\", anjay_send_deferrable, args_string);\n}\n#endif // ANJAY_WITH_SEND\n\nstatic void cmd_unregister_object(anjay_demo_t *demo, const char *args_string) {\n    int oid;\n    if (sscanf(args_string, \"%d\", &oid) != 1 || oid < 0 || oid > UINT16_MAX) {\n        demo_log(ERROR, \"Invalid OID: %s\", args_string);\n        return;\n    }\n\n    AVS_LIST(anjay_demo_object_t) *object_entry_ptr;\n    AVS_LIST_FOREACH_PTR(object_entry_ptr, &demo->objects) {\n        if ((*(*object_entry_ptr)->obj_ptr)->oid == oid) {\n            if (anjay_unregister_object(demo->anjay,\n                                        (*object_entry_ptr)->obj_ptr)) {\n                demo_log(ERROR, \"Could not unregister object %d\", oid);\n                return;\n            }\n            return;\n        }\n    }\n\n    demo_log(ERROR, \"No such object to unregister: %d\", oid);\n}\n\nstatic void cmd_reregister_object(anjay_demo_t *demo, const char *args_string) {\n    int oid;\n    if (sscanf(args_string, \"%d\", &oid) != 1 || oid < 0 || oid > UINT16_MAX) {\n        demo_log(ERROR, \"Invalid OID: %s\", args_string);\n        return;\n    }\n\n    AVS_LIST(anjay_demo_object_t) *object_entry_ptr;\n    AVS_LIST_FOREACH_PTR(object_entry_ptr, &demo->objects) {\n        if ((*(*object_entry_ptr)->obj_ptr)->oid == oid) {\n            if (anjay_register_object(demo->anjay,\n                                      (*object_entry_ptr)->obj_ptr)) {\n                demo_log(ERROR, \"Could not re-register object %d\", oid);\n                return;\n            }\n            return;\n        }\n    }\n\n    demo_log(ERROR, \"No such object to register: %d\", oid);\n}\n\ntypedef struct {\n    size_t skip_at;\n    size_t skip_to;\n} demo_download_skip_def_t;\n\ntypedef struct {\n    anjay_download_handle_t handle;\n    FILE *f;\n    AVS_LIST(demo_download_skip_def_t) skips;\n    size_t current_offset;\n} demo_download_user_data_t;\n\nstatic void demo_download_user_data_destroy(demo_download_user_data_t *data) {\n    if (data) {\n        if (data->f) {\n            fclose(data->f);\n        }\n        AVS_LIST_CLEAR(&data->skips);\n        avs_free(data);\n    }\n}\n\nstatic avs_error_t dl_write_next_block_new(anjay_t *anjay,\n                                           const uint8_t *data,\n                                           size_t data_size,\n                                           const anjay_etag_t *etag,\n                                           void *user_data_) {\n    demo_download_user_data_t *user_data =\n            (demo_download_user_data_t *) user_data_;\n    (void) etag;\n\n    size_t to_write = data_size;\n    if (user_data->skips\n            && user_data->skips->skip_at\n                           <= user_data->current_offset + data_size) {\n        to_write = user_data->skips->skip_at - user_data->current_offset;\n        user_data->current_offset = user_data->skips->skip_to;\n        AVS_LIST_DELETE(&user_data->skips);\n        avs_error_t err =\n                anjay_download_set_next_block_offset(anjay, user_data->handle,\n                                                     user_data->current_offset);\n        if (avs_is_err(err)) {\n            demo_log(ERROR, \"anjay_download_set_next_block_offset() failed\");\n            return err;\n        }\n    } else {\n        user_data->current_offset += to_write;\n    }\n\n    if (fwrite(data, to_write, 1, user_data->f) != 1) {\n        demo_log(ERROR, \"fwrite() failed\");\n        return avs_errno(AVS_UNKNOWN_ERROR);\n    }\n\n    return AVS_OK;\n}\n\nstatic void dl_finished_new(anjay_t *anjay,\n                            anjay_download_status_t status,\n                            void *user_data) {\n    (void) anjay;\n    demo_download_user_data_destroy((demo_download_user_data_t *) user_data);\n    demo_log(INFO, \"download finished, result == %d\", (int) status.result);\n}\n\nstatic void cmd_download(anjay_demo_t *demo, const char *args_string) {\n    char url[256];\n    char target_file[256];\n    char psk_identity[256] = \"\";\n    char psk_key[256] = \"\";\n\n    if (sscanf(args_string, \"%255s %255s %255s %255s\", url, target_file,\n               psk_identity, psk_key)\n            < 2) {\n        demo_log(ERROR, \"invalid URL or target file in: %s\", args_string);\n        return;\n    }\n\n    demo_download_user_data_t *user_data =\n            (demo_download_user_data_t *) avs_calloc(\n                    1, sizeof(demo_download_user_data_t));\n    if (!user_data || !(user_data->f = fopen(target_file, \"wb\"))) {\n        demo_log(ERROR, \"could not open file: %s\", target_file);\n        demo_download_user_data_destroy(user_data);\n        return;\n    }\n\n    avs_net_psk_info_t psk = {\n        .key = avs_crypto_psk_key_info_from_buffer(psk_key, strlen(psk_key)),\n        .identity = avs_crypto_psk_identity_info_from_buffer(\n                psk_identity, strlen(psk_identity))\n    };\n    anjay_download_config_t cfg = {\n        .url = url,\n        .on_next_block = dl_write_next_block_new,\n        .on_download_finished = dl_finished_new,\n        .user_data = user_data,\n        .security_config = {\n            .security_info = avs_net_security_info_from_psk(psk)\n        }\n    };\n\n    if (avs_is_err(anjay_download(demo->anjay, &cfg, &user_data->handle))) {\n        demo_log(ERROR, \"could not schedule download\");\n        demo_download_user_data_destroy(user_data);\n    } else {\n        printf(\"DOWNLOAD_HANDLE==%\" PRIxPTR \"\\n\",\n               (uintptr_t) user_data->handle);\n    }\n}\n\nstatic void cmd_download_blocks_impl(anjay_demo_t *demo, char *args_string) {\n    char url[256];\n    char target_file[256];\n    int offsets_offset;\n\n    if (sscanf(args_string, \"%255s %255s %n\", url, target_file, &offsets_offset)\n            < 2) {\n        demo_log(ERROR, \"invalid URL or target file in: %s\", args_string);\n        return;\n    }\n\n    demo_download_user_data_t *user_data =\n            (demo_download_user_data_t *) avs_calloc(\n                    1, sizeof(demo_download_user_data_t));\n    if (!user_data || !(user_data->f = fopen(target_file, \"wb\"))) {\n        demo_log(ERROR, \"could not open file: %s\", target_file);\n        demo_download_user_data_destroy(user_data);\n        return;\n    }\n\n    anjay_download_config_t cfg = {\n        .url = url,\n        .on_next_block = dl_write_next_block_new,\n        .on_download_finished = dl_finished_new,\n        .user_data = user_data\n    };\n\n    long last_end_offset = -1;\n    AVS_LIST(demo_download_skip_def_t) last_skip = NULL;\n\n    char *offsets_text = &args_string[offsets_offset];\n    char *saveptr = NULL;\n    const char *token = NULL;\n    while ((token = avs_strtok(offsets_text, AVS_SPACES, &saveptr))) {\n        offsets_text = NULL;\n        char *endptr;\n        long start_offset = strtol(token, &endptr, 0);\n        if (start_offset <= last_end_offset\n                || (endptr && *endptr && *endptr != '-')) {\n            goto parse_error;\n        }\n        long end_offset = LONG_MAX;\n        if (endptr && endptr[0] == '-' && endptr[1]) {\n            end_offset = strtol(&endptr[1], &endptr, 0);\n            if (end_offset <= start_offset || (endptr && *endptr)) {\n                goto parse_error;\n            }\n        }\n\n        if (last_skip) {\n            last_skip->skip_to = (size_t) start_offset;\n        } else {\n            cfg.start_offset = (size_t) start_offset;\n            user_data->current_offset = cfg.start_offset;\n        }\n        if (end_offset < LONG_MAX) {\n            AVS_LIST(demo_download_skip_def_t) skip =\n                    AVS_LIST_NEW_ELEMENT(demo_download_skip_def_t);\n            if (!skip) {\n                demo_log(ERROR, \"out of memory\");\n                demo_download_user_data_destroy(user_data);\n                return;\n            }\n            skip->skip_at = (size_t) end_offset;\n            skip->skip_to = SIZE_MAX;\n            AVS_LIST_APPEND(&last_skip, skip);\n            if (!user_data->skips) {\n                user_data->skips = skip;\n            } else {\n                AVS_LIST_ADVANCE(&last_skip);\n            }\n            assert(last_skip == skip);\n        }\n\n        last_end_offset = end_offset;\n    }\n\n    if (avs_is_err(anjay_download(demo->anjay, &cfg, &user_data->handle))) {\n        demo_log(ERROR, \"could not schedule download\");\n        demo_download_user_data_destroy(user_data);\n    }\n    return;\nparse_error:\n    demo_log(ERROR, \"Invalid block definition: %s\", token);\n    demo_download_user_data_destroy(user_data);\n}\n\nstatic void cmd_download_blocks(anjay_demo_t *demo, const char *args_string) {\n    char *args_string_copy = avs_strdup(args_string);\n    if (!args_string_copy) {\n        demo_log(ERROR, \"out of memory\");\n        return;\n    }\n    cmd_download_blocks_impl(demo, args_string_copy);\n    avs_free(args_string_copy);\n}\n\n#ifdef ANJAY_WITH_ATTR_STORAGE\nstatic void cmd_set_attrs(anjay_demo_t *demo, const char *args_string) {\n    char *path = (char *) avs_malloc(strlen(args_string) + 1);\n    if (!path) {\n        demo_log(ERROR, \"Out of memory\");\n        return;\n    }\n    int path_len = 0;\n    const char *args = NULL, *pmin = NULL, *pmax = NULL, *lt = NULL, *gt = NULL,\n               *st = NULL, *epmin = NULL, *epmax = NULL;\n#    ifdef ANJAY_WITH_LWM2M12\n    const char *hqmax = NULL;\n#    endif // ANJAY_WITH_LWM2M12\n    anjay_dm_r_attributes_t attrs;\n    int ssid;\n\n    if (sscanf(args_string, \"%s %d%n\", path, &ssid, &path_len) != 2) {\n        goto error;\n    }\n\n    if (ssid < 0 || UINT16_MAX <= ssid) {\n        demo_log(ERROR, \"invalid SSID: expected 0 <= ssid < 65535, got %d\",\n                 ssid);\n        goto error;\n    }\n\n    args = args_string + path_len;\n    attrs = ANJAY_DM_R_ATTRIBUTES_EMPTY;\n    pmin = strstr(args, \"pmin=\");\n    pmax = strstr(args, \"pmax=\");\n    epmin = strstr(args, \"epmin=\");\n    epmax = strstr(args, \"epmax=\");\n    lt = strstr(args, \"lt=\");\n    gt = strstr(args, \"gt=\");\n    st = strstr(args, \"st=\");\n#    ifdef ANJAY_WITH_LWM2M12\n    hqmax = strstr(args, \"hqmax=\");\n#    endif // ANJAY_WITH_LWM2M12\n    if (pmin) {\n        (void) sscanf(pmin, \"pmin=%\" PRId32, &attrs.common.min_period);\n    }\n    if (pmax) {\n        (void) sscanf(pmax, \"pmax=%\" PRId32, &attrs.common.max_period);\n    }\n    if (epmin) {\n        (void) sscanf(epmin, \"epmin=%\" PRId32, &attrs.common.min_eval_period);\n    }\n    if (epmax) {\n        (void) sscanf(epmax, \"epmax=%\" PRId32, &attrs.common.max_eval_period);\n    }\n    if (lt) {\n        (void) sscanf(lt, \"lt=%lf\", &attrs.less_than);\n    }\n    if (gt) {\n        (void) sscanf(gt, \"gt=%lf\", &attrs.greater_than);\n    }\n    if (st) {\n        (void) sscanf(st, \"st=%lf\", &attrs.step);\n    }\n#    ifdef ANJAY_WITH_LWM2M12\n    if (hqmax) {\n        (void) sscanf(hqmax, \"hqmax=%\" PRId32, &attrs.common.hqmax);\n    }\n#    endif // ANJAY_WITH_LWM2M12\n\n    int oid, iid, rid;\n#    ifdef ANJAY_WITH_LWM2M11\n    int riid;\n#    endif // ANJAY_WITH_LWM2M11\n    switch (sscanf(path,\n                   \"/%d/%d/%d\"\n#    ifdef ANJAY_WITH_LWM2M11\n                   \"/%d\"\n#    endif // ANJAY_WITH_LWM2M11\n                   ,\n                   &oid, &iid, &rid\n#    ifdef ANJAY_WITH_LWM2M11\n                   ,\n                   &riid\n#    endif // ANJAY_WITH_LWM2M11\n                   )) {\n#    ifdef ANJAY_WITH_LWM2M11\n    case 4:\n        if (anjay_attr_storage_set_resource_instance_attrs(\n                    demo->anjay, (anjay_ssid_t) ssid, (anjay_oid_t) oid,\n                    (anjay_iid_t) iid, (anjay_rid_t) rid, (anjay_riid_t) riid,\n                    &attrs)) {\n            demo_log(ERROR, \"failed to set resource instance level attributes\");\n        }\n        goto finish;\n#    endif // ANJAY_WITH_LWM2M11\n    case 3:\n        if (anjay_attr_storage_set_resource_attrs(\n                    demo->anjay, (anjay_ssid_t) ssid, (anjay_oid_t) oid,\n                    (anjay_iid_t) iid, (anjay_rid_t) rid, &attrs)) {\n            demo_log(ERROR, \"failed to set resource level attributes\");\n        }\n        goto finish;\n    case 2:\n        if (anjay_attr_storage_set_instance_attrs(\n                    demo->anjay, (anjay_ssid_t) ssid, (anjay_oid_t) oid,\n                    (anjay_iid_t) iid, &attrs.common)) {\n            demo_log(ERROR, \"failed to set instance level attributes\");\n        }\n        goto finish;\n    case 1:\n        if (anjay_attr_storage_set_object_attrs(\n                    demo->anjay, (anjay_ssid_t) ssid, (anjay_oid_t) oid,\n                    &attrs.common)) {\n            demo_log(ERROR, \"failed to set object level attributes\");\n        }\n        goto finish;\n    }\nerror:\n    demo_log(ERROR, \"bad syntax - see help\");\nfinish:\n    avs_free(path);\n}\n#endif // ANJAY_WITH_ATTR_STORAGE\n\nstatic void cmd_disable_server(anjay_demo_t *demo, const char *args_string) {\n    unsigned ssid;\n    int timeout_s;\n    if (sscanf(args_string, \"%u %d\", &ssid, &timeout_s) < 2\n            || ssid > UINT16_MAX) {\n        demo_log(ERROR, \"invalid arguments\");\n        return;\n    }\n\n    avs_time_duration_t timeout = AVS_TIME_DURATION_INVALID;\n    if (timeout_s >= 0) {\n        timeout = avs_time_duration_from_scalar(timeout_s, AVS_TIME_S);\n    }\n\n    if (anjay_disable_server_with_timeout(demo->anjay, (anjay_ssid_t) ssid,\n                                          timeout)) {\n        demo_log(ERROR, \"could not disable server with SSID %u\", ssid);\n        return;\n    }\n}\n\nstatic void cmd_enable_server(anjay_demo_t *demo, const char *args_string) {\n    anjay_ssid_t ssid = ANJAY_SSID_ANY;\n    if (*args_string && parse_ssid(args_string, &ssid)) {\n        demo_log(ERROR, \"invalid Short Server ID: %s\", args_string);\n        return;\n    }\n\n    if (anjay_enable_server(demo->anjay, ssid)) {\n        demo_log(ERROR, \"could not enable server with SSID %\" PRIu16, ssid);\n        return;\n    }\n}\n\nstatic void cmd_all_connections_failed(anjay_demo_t *demo,\n                                       const char *unused_args) {\n    (void) unused_args;\n    printf(\"ALL_CONNECTIONS_FAILED==%d\\n\",\n           (int) anjay_all_connections_failed(demo->anjay));\n}\n\nstatic void cmd_schedule_update_on_exit(anjay_demo_t *demo,\n                                        const char *unused_args) {\n    (void) unused_args;\n    demo->schedule_update_on_exit = true;\n}\n\n#ifdef ANJAY_WITH_LWM2M11\nstatic void cmd_set_queue_mode_preference(anjay_demo_t *demo,\n                                          const char *args_string) {\n    while (isspace(*args_string)) {\n        ++args_string;\n    }\n    anjay_queue_mode_preference_t value;\n    if (avs_strcasecmp(args_string, \"FORCE_QUEUE_MODE\") == 0) {\n        value = ANJAY_FORCE_QUEUE_MODE;\n    } else if (avs_strcasecmp(args_string, \"PREFER_QUEUE_MODE\") == 0) {\n        value = ANJAY_PREFER_QUEUE_MODE;\n    } else if (avs_strcasecmp(args_string, \"PREFER_ONLINE_MODE\") == 0) {\n        value = ANJAY_PREFER_ONLINE_MODE;\n    } else if (avs_strcasecmp(args_string, \"FORCE_ONLINE_MODE\") == 0) {\n        value = ANJAY_FORCE_ONLINE_MODE;\n    } else {\n        demo_log(ERROR, \"Invaild queue mode preference; supported values: \"\n                        \"FORCE_QUEUE_MODE, PREFER_QUEUE_MODE, \"\n                        \"PREFER_ONLINE_MODE, FORCE_ONLINE_MODE\");\n        return;\n    }\n    anjay_set_queue_mode_preference(demo->anjay, value);\n}\n#endif // ANJAY_WITH_LWM2M11\n\n#ifdef ANJAY_WITH_OBSERVATION_STATUS\nstatic void\nlog_observation_status(const anjay_resource_observation_status_t *status) {\n    demo_log(INFO,\n             \"anjay_resource_observation_status, is_observed == %s, \"\n             \"min_period == %\" PRId32 \", max_eval_period == %\" PRId32,\n             status->is_observed ? \"true\" : \"false\", status->min_period,\n             status->max_eval_period);\n\n#    if (ANJAY_MAX_OBSERVATION_SERVERS_REPORTED_NUMBER > 0)\n    if (status->servers_number > 0) {\n        char *ssid_list =\n                (char *) avs_calloc((AVS_UINT_STR_BUF_SIZE(anjay_ssid_t) + 2)\n                                            * status->servers_number,\n                                    1);\n        if (ssid_list) {\n            for (uint16_t i = 0; i < status->servers_number; i++) {\n                char ssid_string[AVS_UINT_STR_BUF_SIZE(anjay_ssid_t) + 2];\n                sprintf(ssid_string, \" %\" PRIu16 \",\", status->servers[i]);\n                strcat(ssid_list, ssid_string);\n            }\n            ssid_list[strlen(ssid_list) - 1] = '\\0'; // remove trailing comma\n            demo_log(INFO, \"SSIDs of servers observing given path:%s\",\n                     ssid_list);\n            avs_free(ssid_list);\n        }\n    }\n#    endif // (ANJAY_MAX_OBSERVATION_SERVERS_REPORTED_NUMBER > 0)\n}\n\nstatic void cmd_observation_status(anjay_demo_t *demo,\n                                   const char *args_string) {\n    anjay_oid_t oid;\n    anjay_iid_t iid;\n    anjay_rid_t rid;\n\n#    ifdef ANJAY_WITH_LWM2M_GATEWAY\n    anjay_iid_t end_dev_iid = ANJAY_ID_INVALID;\n    if (sscanf(args_string, \" %\" SCNu16 \" /%\" SCNu16 \"/%\" SCNu16 \"/%\" SCNu16,\n               &end_dev_iid, &oid, &iid, &rid)\n            == 4) {\n        if (verify_device_count(end_dev_iid)) {\n            return;\n        }\n        // even if there is no end-device with given dev id, we still want to\n        // print that such resource is not observed due to tests\n        anjay_resource_observation_status_t status =\n                anjay_lwm2m_gateway_resource_observation_status(\n                        demo->anjay, end_dev_iid, oid, iid, rid);\n        log_observation_status(&status);\n    } else\n#    endif // ANJAY_WITH_LWM2M_GATEWAY\n            if (sscanf(args_string, \" /%\" SCNu16 \"/%\" SCNu16 \"/%\" SCNu16, &oid,\n                       &iid, &rid)\n                == 3) {\n        anjay_resource_observation_status_t status =\n                anjay_resource_observation_status(demo->anjay, oid, iid, rid);\n        log_observation_status(&status);\n    } else {\n        demo_log(WARNING, \"observation-status usage: \"\n                          \"(/OID/IID/RID\" LWM2M_GATEWAY_PATH_LOG_PART \")\");\n    }\n}\n#endif // ANJAY_WITH_OBSERVATION_STATUS\n\nstatic void cmd_badc_write(anjay_demo_t *demo, const char *args_string) {\n    anjay_iid_t iid;\n    anjay_riid_t riid;\n    int length;\n    if (sscanf(args_string, \" %\" SCNu16 \" %\" SCNu16 \" %n\", &iid, &riid, &length)\n            < 2) {\n        demo_log(ERROR, \"invalid format\");\n        return;\n    }\n    binary_app_data_container_write(demo->anjay, demo_find_object(demo, 19),\n                                    iid, riid, &args_string[length]);\n}\n\nstatic void cmd_advance_time(anjay_demo_t *demo, const char *args_string) {\n    (void) demo;\n    double delta_s = 0;\n    if (sscanf(args_string, \" %lf\", &delta_s) != 1) {\n        demo_log(ERROR,\n                 \"bad time format, expected seconds as floating point number\");\n        return;\n    }\n    demo_advance_time(avs_time_duration_from_fscalar(delta_s, AVS_TIME_S));\n}\n\nstatic void cmd_set_event_log_data(anjay_demo_t *demo,\n                                   const char *args_string) {\n    const anjay_dm_object_def_t **obj_def =\n            demo_find_object(demo, DEMO_OID_EVENT_LOG);\n    if (!obj_def) {\n        demo_log(ERROR, \"failed to find Event Log object\");\n    }\n    size_t data_size = strlen(args_string);\n    const char *data_ptr = args_string;\n    if (data_size) {\n        // Discard the space character\n        ++data_ptr;\n        --data_size;\n    }\n\n    if (event_log_write_data(demo->anjay, obj_def, data_ptr, data_size)) {\n        demo_log(ERROR, \"failed to write Event Log data\");\n    }\n}\n\n#ifdef ANJAY_WITH_MODULE_FW_UPDATE\nstatic void cmd_set_fw_update_result(anjay_demo_t *demo,\n                                     const char *args_string) {\n    int result;\n    if (sscanf(args_string, \" %d\", &result) != 1) {\n        demo_log(ERROR, \"Firmware Update result not specified\");\n        return;\n    }\n    anjay_fw_update_set_result(demo->anjay, (anjay_fw_update_result_t) result);\n}\n\nstatic void cmd_fw_update_suspend(anjay_demo_t *demo, const char *args_string) {\n    (void) args_string;\n    anjay_fw_update_pull_suspend(demo->anjay);\n}\n\nstatic void cmd_fw_update_reconnect(anjay_demo_t *demo,\n                                    const char *args_string) {\n    (void) args_string;\n    anjay_fw_update_pull_reconnect(demo->anjay);\n}\n\n#    if defined(ANJAY_WITH_LWM2M11) \\\n            && defined(ANJAY_WITH_MODULE_FW_UPDATE_V11_RESOURCES)\nstatic void cmd_get_fw_update_deadline(anjay_demo_t *demo,\n                                       const char *args_string) {\n    (void) args_string;\n    int64_t update_deadline_timestamp = 0;\n    avs_time_real_to_scalar(&update_deadline_timestamp, AVS_TIME_S,\n                            anjay_fw_update_get_deadline(demo->anjay));\n    printf(\"FW_UPDATE_DEADLINE==%\" PRId64 \"\\n\", update_deadline_timestamp);\n}\n#    endif /* defined(ANJAY_WITH_LWM2M11) && \\\n              defined(ANJAY_WITH_MODULE_FW_UPDATE_V11_RESOURCES) */\n#endif     // ANJAY_WITH_MODULE_FW_UPDATE\n\nstatic void cmd_ongoing_registration_exists(anjay_demo_t *demo,\n                                            const char *args_string) {\n    (void) args_string;\n    printf(\"ONGOING_REGISTRATION==%s\\n\",\n           anjay_ongoing_registration_exists(demo->anjay) ? \"true\" : \"false\");\n}\n\nstatic void cmd_set_lifetime(anjay_demo_t *demo, const char *args_string) {\n    anjay_iid_t iid;\n    int32_t lifetime;\n    if (sscanf(args_string, \"%\" SCNu16 \" %\" SCNd32, &iid, &lifetime) != 2) {\n        demo_log(ERROR, \"The command requires both Instance ID and Lifetime\");\n        return;\n    }\n    if (\n#ifdef WITH_DEMO_USE_STANDALONE_OBJECTS\n                standalone_server_object_set_lifetime(demo->server_obj_ptr, iid,\n                                                      lifetime)\n#else  // WITH_DEMO_USE_STANDALONE_OBJECTS\n                anjay_server_object_set_lifetime(demo->anjay, iid, lifetime)\n#endif // WITH_DEMO_USE_STANDALONE_OBJECTS\n    ) {\n        demo_log(ERROR, \"Could not set server lifetime to the desired value\");\n    }\n}\n\nstatic void cmd_registration_expiration_time(anjay_demo_t *demo,\n                                             const char *args_string) {\n    anjay_ssid_t ssid;\n    if (parse_ssid(args_string, &ssid)) {\n        demo_log(ERROR, \"invalid Short Server ID: %s\", args_string);\n        return;\n    }\n\n    demo_log(INFO, \"REGISTRATION_EXPIRATION_TIME=%s\",\n             AVS_TIME_DURATION_AS_STRING(\n                     anjay_registration_expiration_time_with_status(demo->anjay,\n                                                                    ssid, NULL)\n                             .since_real_epoch));\n}\n\nstatic void cmd_next_lifecycle_operation(anjay_demo_t *demo,\n                                         const char *args_string) {\n    avs_time_real_t result = AVS_TIME_REAL_INVALID;\n    anjay_ssid_t ssid = ANJAY_SSID_ANY;\n    anjay_transport_set_t transport_set;\n    if (!*args_string || !parse_ssid(args_string, &ssid)) {\n        result = anjay_next_planned_lifecycle_operation(demo->anjay, ssid);\n    } else if (!parse_transports(args_string, &transport_set)) {\n        result =\n                anjay_transport_next_planned_lifecycle_operation(demo->anjay,\n                                                                 transport_set);\n    } else {\n        return;\n    }\n\n    demo_log(INFO, \"NEXT_LIFECYCLE_OPERATION=%s\",\n             AVS_TIME_DURATION_AS_STRING(result.since_real_epoch));\n}\n\nstatic void cmd_next_planned_notify(anjay_demo_t *demo,\n                                    const char *args_string) {\n    avs_time_real_t result = AVS_TIME_REAL_INVALID;\n    anjay_ssid_t ssid = ANJAY_SSID_ANY;\n    anjay_transport_set_t transport_set;\n    if (!*args_string || !parse_ssid(args_string, &ssid)) {\n        result = anjay_next_planned_notify_trigger(demo->anjay, ssid);\n    } else if (!parse_transports(args_string, &transport_set)) {\n        result = anjay_transport_next_planned_notify_trigger(demo->anjay,\n                                                             transport_set);\n    } else {\n        return;\n    }\n\n    demo_log(INFO, \"NEXT_PLANNED_NOTIFY=%s\",\n             AVS_TIME_DURATION_AS_STRING(result.since_real_epoch));\n}\n\nstatic void cmd_next_planned_pmax_notify(anjay_demo_t *demo,\n                                         const char *args_string) {\n    avs_time_real_t result = AVS_TIME_REAL_INVALID;\n    anjay_ssid_t ssid = ANJAY_SSID_ANY;\n    anjay_transport_set_t transport_set;\n    if (!*args_string || !parse_ssid(args_string, &ssid)) {\n        result = anjay_next_planned_pmax_notify_trigger(demo->anjay, ssid);\n    } else if (!parse_transports(args_string, &transport_set)) {\n        result =\n                anjay_transport_next_planned_pmax_notify_trigger(demo->anjay,\n                                                                 transport_set);\n    } else {\n        return;\n    }\n\n    demo_log(INFO, \"NEXT_PLANNED_PMAX_NOTIFY=%s\",\n             AVS_TIME_DURATION_AS_STRING(result.since_real_epoch));\n}\n\nstatic void cmd_has_unsent_notifications(anjay_demo_t *demo,\n                                         const char *args_string) {\n    bool result = false;\n    anjay_ssid_t ssid = ANJAY_SSID_ANY;\n    anjay_transport_set_t transport_set;\n    if (!*args_string || !parse_ssid(args_string, &ssid)) {\n        result = anjay_has_unsent_notifications(demo->anjay, ssid);\n    } else if (!parse_transports(args_string, &transport_set)) {\n        result = anjay_transport_has_unsent_notifications(demo->anjay,\n                                                          transport_set);\n    } else {\n        return;\n    }\n\n    demo_log(INFO, \"HAS_UNSENT_NOTIFICATIONS=%s\", result ? \"true\" : \"false\");\n}\n\nstatic void cmd_temperature_add_instance(anjay_demo_t *demo,\n                                         const char *args_string) {\n    anjay_iid_t iid;\n    if (sscanf(args_string, \" %\" SCNu16, &iid) < 1) {\n        demo_log(ERROR, \"invalid format\");\n        return;\n    }\n\n    temperature_add_instance(demo->anjay, iid);\n}\n\nstatic void cmd_temperature_remove_instance(anjay_demo_t *demo,\n                                            const char *args_string) {\n    anjay_iid_t iid;\n    if (sscanf(args_string, \" %\" SCNu16, &iid) < 1) {\n        demo_log(ERROR, \"invalid format\");\n        return;\n    }\n\n    temperature_remove_instance(demo->anjay, iid);\n}\n\nstatic void cmd_accelerometer_add_instance(anjay_demo_t *demo,\n                                           const char *args_string) {\n    anjay_iid_t iid;\n    if (sscanf(args_string, \" %\" SCNu16, &iid) < 1) {\n        demo_log(ERROR, \"invalid format\");\n        return;\n    }\n\n    accelerometer_add_instance(demo->anjay, iid);\n}\n\nstatic void cmd_accelerometer_remove_instance(anjay_demo_t *demo,\n                                              const char *args_string) {\n    anjay_iid_t iid;\n    if (sscanf(args_string, \" %\" SCNu16, &iid) < 1) {\n        demo_log(ERROR, \"invalid format\");\n        return;\n    }\n\n    accelerometer_remove_instance(demo->anjay, iid);\n}\n\nstatic void cmd_push_button_add_instance(anjay_demo_t *demo,\n                                         const char *args_string) {\n    anjay_iid_t iid;\n    char application_type[40];\n    if (sscanf(args_string, \" %\" SCNu16 \" %39s\", &iid, application_type) < 2) {\n        demo_log(ERROR, \"invalid format\");\n        return;\n    }\n\n    anjay_ipso_button_instance_add(demo->anjay, iid, application_type);\n}\n\nstatic void cmd_push_button_remove_instance(anjay_demo_t *demo,\n                                            const char *args_string) {\n    anjay_iid_t iid;\n    if (sscanf(args_string, \" %\" SCNu16, &iid) < 1) {\n        demo_log(ERROR, \"invalid format\");\n        return;\n    }\n\n    anjay_ipso_button_instance_remove(demo->anjay, iid);\n}\n\nstatic void cmd_push_button_press(anjay_demo_t *demo, const char *args_string) {\n    anjay_iid_t iid;\n    if (sscanf(args_string, \" %\" SCNu16, &iid) < 1) {\n        demo_log(ERROR, \"invalid format\");\n        return;\n    }\n\n    anjay_ipso_button_update(demo->anjay, iid, true);\n}\n\nstatic void cmd_push_button_release(anjay_demo_t *demo,\n                                    const char *args_string) {\n    anjay_iid_t iid;\n    if (sscanf(args_string, \" %\" SCNu16, &iid) < 1) {\n        demo_log(ERROR, \"invalid format\");\n        return;\n    }\n\n    anjay_ipso_button_update(demo->anjay, iid, false);\n}\n\nstatic void cmd_set_tx_params(anjay_demo_t *demo, const char *args_string) {\n    avs_coap_udp_tx_params_t tx_params;\n    double ack_timeout_s;\n    char transport_str[16];\n    if (sscanf(args_string, \" %15s %lf %lf %u %lu\", transport_str,\n               &ack_timeout_s, &tx_params.ack_random_factor,\n               &tx_params.max_retransmit, &tx_params.nstart)\n            < 5) {\n        demo_log(ERROR, \"invalid format\");\n        return;\n    }\n\n    tx_params.ack_timeout =\n            avs_time_duration_from_fscalar(ack_timeout_s, AVS_TIME_S);\n\n    anjay_transport_set_t transport_set;\n    if (parse_transports(transport_str, &transport_set)) {\n        return;\n    }\n\n    anjay_update_transport_tx_params(demo->anjay, transport_set, &tx_params);\n}\n\nstatic void cmd_set_coap_exchange_timeout(anjay_demo_t *demo,\n                                          const char *args_string) {\n    double exchange_timeout_s;\n    char transport_str[16];\n    if (sscanf(args_string, \" %15s %lf\", transport_str, &exchange_timeout_s)\n            < 2) {\n        demo_log(ERROR, \"invalid format\");\n        return;\n    }\n\n    avs_time_duration_t exchange_timeout =\n            avs_time_duration_from_fscalar(exchange_timeout_s, AVS_TIME_S);\n\n    anjay_transport_set_t transport_set;\n    if (parse_transports(transport_str, &transport_set)) {\n        return;\n    }\n\n    anjay_update_coap_exchange_timeout(demo->anjay, transport_set,\n                                       exchange_timeout);\n}\n\nstatic void cmd_set_dtls_timeouts(anjay_demo_t *demo, const char *args_string) {\n    double min_timeout_s, max_timeout_s;\n    if (sscanf(args_string, \"%lf %lf\", &min_timeout_s, &max_timeout_s) < 2) {\n        demo_log(ERROR, \"invalid format\");\n        return;\n    }\n\n    avs_net_dtls_handshake_timeouts_t dtls_handshake_timeouts = {\n        .min = avs_time_duration_from_fscalar(min_timeout_s, AVS_TIME_S),\n        .max = avs_time_duration_from_fscalar(max_timeout_s, AVS_TIME_S)\n    };\n\n    anjay_update_dtls_handshake_timeouts(demo->anjay, dtls_handshake_timeouts);\n}\n\n#ifdef ANJAY_WITH_COMMUNICATION_TIMESTAMP_API\nstatic void cmd_last_registration_time(anjay_demo_t *demo,\n                                       const char *args_string) {\n    avs_time_real_t result = AVS_TIME_REAL_INVALID;\n    anjay_ssid_t ssid = ANJAY_SSID_ANY;\n\n    if (*args_string && parse_ssid(args_string, &ssid)) {\n        demo_log(ERROR, \"invalid Short Server ID: %s\", args_string);\n        return;\n    }\n    avs_error_t err =\n            anjay_get_server_last_registration_time(demo->anjay, ssid, &result);\n\n    if (!avs_is_err(err)) {\n        demo_log(INFO, \"LAST_REGISTRATION_TIME=%s\",\n                 AVS_TIME_DURATION_AS_STRING(result.since_real_epoch));\n    } else {\n        demo_log(INFO, \"Failed to get last registration time\");\n    }\n}\n\nstatic void cmd_next_update_time(anjay_demo_t *demo, const char *args_string) {\n    avs_time_real_t result = AVS_TIME_REAL_INVALID;\n    anjay_ssid_t ssid = ANJAY_SSID_ANY;\n\n    if (*args_string && parse_ssid(args_string, &ssid)) {\n        demo_log(ERROR, \"invalid Short Server ID: %s\", args_string);\n        return;\n    }\n    avs_error_t err =\n            anjay_get_server_next_update_time(demo->anjay, ssid, &result);\n\n    if (!avs_is_err(err)) {\n        demo_log(INFO, \"NEXT_UPDATE_TIME=%s\",\n                 AVS_TIME_DURATION_AS_STRING(result.since_real_epoch));\n    } else {\n        demo_log(INFO, \"Failed to get next update time\");\n    }\n}\n\nstatic void cmd_last_communication_time(anjay_demo_t *demo,\n                                        const char *args_string) {\n    avs_time_real_t result = AVS_TIME_REAL_INVALID;\n    anjay_ssid_t ssid = ANJAY_SSID_ANY;\n\n    if (*args_string && parse_ssid(args_string, &ssid)) {\n        demo_log(ERROR, \"invalid Short Server ID: %s\", args_string);\n        return;\n    }\n    avs_error_t err = anjay_get_server_last_communication_time(demo->anjay,\n                                                               ssid, &result);\n    if (!avs_is_err(err)) {\n        demo_log(INFO, \"LAST_COMMUNICATION_TIME=%s\",\n                 AVS_TIME_DURATION_AS_STRING(result.since_real_epoch));\n    } else {\n        demo_log(INFO, \"Failed to get last communication time\");\n    }\n}\n#endif // ANJAY_WITH_COMMUNICATION_TIMESTAMP_API\n\n#ifdef ANJAY_WITH_CONN_STATUS_API\nstatic void cmd_get_server_connection_status(anjay_demo_t *demo,\n                                             const char *args_string) {\n    anjay_ssid_t ssid = ANJAY_SSID_ANY;\n    anjay_server_conn_status_t result;\n\n    if (*args_string && parse_ssid(args_string, &ssid)) {\n        demo_log(ERROR, \"invalid Short Server ID: %s\", args_string);\n        return;\n    }\n\n    result = anjay_get_server_connection_status(demo->anjay, ssid);\n\n    demo_log(INFO, \"Current server connection status: %s\",\n             translate_server_connection_status_enum_to_str(result));\n}\n#endif // ANJAY_WITH_CONN_STATUS_API\n\n#ifdef ANJAY_WITH_LWM2M_GATEWAY\nstatic void cmd_setup_end_device(anjay_demo_t *demo, const char *args_string) {\n    anjay_iid_t end_dev_iid;\n    if (sscanf(args_string, \" %\" SCNu16, &end_dev_iid) != 1) {\n        demo_log(ERROR, \"invalid format\");\n        return;\n    }\n    if (verify_device_count(end_dev_iid)) {\n        return;\n    }\n    if (lwm2m_gateway_setup_end_device(demo->anjay, end_dev_iid)) {\n        demo_log(ERROR, \"Failed to set up Gateway End Device id = %\" PRIu16,\n                 end_dev_iid);\n    } else {\n        demo_log(INFO, \"Successfully set up Gateway End Device id = %\" PRIu16,\n                 end_dev_iid);\n    }\n}\n\nstatic void cmd_cleanup_end_device(anjay_demo_t *demo,\n                                   const char *args_string) {\n    anjay_iid_t end_dev_iid;\n    if (sscanf(args_string, \" %\" SCNu16, &end_dev_iid) < 1) {\n        demo_log(ERROR, \"invalid format\");\n        return;\n    }\n    if (verify_device_count(end_dev_iid)) {\n        return;\n    }\n    lwm2m_gateway_cleanup_end_device(demo->anjay, end_dev_iid);\n}\n\nstatic void cmd_press_button_end_device(anjay_demo_t *demo,\n                                        const char *args_string) {\n    (void) demo;\n    anjay_iid_t end_dev_iid;\n    if (sscanf(args_string, \" %\" SCNu16, &end_dev_iid) < 1) {\n        demo_log(ERROR, \"invalid format\");\n        return;\n    }\n    if (verify_device_count(end_dev_iid)) {\n        return;\n    }\n    lwm2m_gateway_press_button_end_device(demo->anjay, end_dev_iid);\n}\n\nstatic void cmd_release_button_end_device(anjay_demo_t *demo,\n                                          const char *args_string) {\n    (void) demo;\n    anjay_iid_t end_dev_iid;\n    if (sscanf(args_string, \" %\" SCNu16, &end_dev_iid) < 1) {\n        demo_log(ERROR, \"invalid format\");\n        return;\n    }\n    if (verify_device_count(end_dev_iid)) {\n        return;\n    }\n    lwm2m_gateway_release_button_end_device(demo->anjay, end_dev_iid);\n}\n\nstatic void cmd_badc_write_end_device(anjay_demo_t *demo,\n                                      const char *args_string) {\n    anjay_iid_t iid;\n    anjay_riid_t riid;\n    int length;\n    anjay_riid_t end_dev_iid;\n    if (sscanf(args_string, \" %\" SCNu16 \" %\" SCNu16 \" %\" SCNu16 \" %n\",\n               &end_dev_iid, &iid, &riid, &length)\n            < 3) {\n        demo_log(ERROR, \"invalid format\");\n        return;\n    }\n\n    if (verify_device_count(end_dev_iid)) {\n        return;\n    }\n    lwm2m_gateway_binary_app_data_container_write(demo->anjay, end_dev_iid, iid,\n                                                  riid, &args_string[length]);\n}\n#endif // ANJAY_WITH_LWM2M_GATEWAY\n\nstatic void cmd_help(anjay_demo_t *demo, const char *args_string);\n\nstruct cmd_handler_def {\n    const char *cmd_name;\n    size_t cmd_name_length;\n    void (*handler)(anjay_demo_t *, const char *);\n    const char *help_args;\n    const char *help_descr;\n};\n\n#define CMD_HANDLER(name, args, func, help) \\\n    { (name), sizeof(name) - 1, (func), (args), (help) }\nstatic const struct cmd_handler_def COMMAND_HANDLERS[] = {\n    // clang-format off\n    CMD_HANDLER(\"send-register\", \"[ssid=0]\",\n                cmd_send_register, \"Sends Register messages to LwM2M servers\"),\n    CMD_HANDLER(\"send-update\", \"[ssid=0]\",\n                cmd_send_update, \"Sends Update messages to LwM2M servers\"),\n    CMD_HANDLER(\"reconnect-server\", \"ssid\", cmd_reconnect_server,\n                \"Reconnects a server with given SSID\"),\n    CMD_HANDLER(\"reconnect\", \"[transports...]\", cmd_reconnect,\n                \"Reconnects to LwM2M servers and sends Update messages\"),\n#ifdef ANJAY_WITH_MODULE_FW_UPDATE\n    CMD_HANDLER(\"set-fw-package-path\", \"PATH\", cmd_set_fw_package_path,\n                \"Sets the path where the firmware package will be saved when \"\n                \"Write /5/0/0 is performed\"),\n#endif // ANJAY_WITH_MODULE_FW_UPDATE\n#ifdef ANJAY_WITH_MODULE_ADVANCED_FW_UPDATE\n    CMD_HANDLER(\"set-afu-package-path\", \"PATH\", cmd_set_afu_package_path,\n                \"Sets the path where the firmware package will be saved when \"\n                \"Write /\" AVS_QUOTE_MACRO(ANJAY_ADVANCED_FW_UPDATE_OID) \"/0/0 is performed. Only applied to instance 0.\"),\n    CMD_HANDLER(\"get-afu-deadline\", \"\", cmd_get_afu_deadline,\n                \"Gets the Advanced Firmware Update deadline (only for main APP \"\n                \"image)\"),\n    CMD_HANDLER(\"set-afu-result\", \"RESULT\", cmd_set_afu_result,\n                \"Attempts to set Advanced Firmware Update Result of instance \"\n                \"/\" AVS_QUOTE_MACRO(ANJAY_ADVANCED_FW_UPDATE_OID) \"/0 (APP) at runtime\"),\n    CMD_HANDLER(\"afu-suspend\", \"\", cmd_afu_suspend,\n                \"Suspends the operation of PULL-mode downloads in the Advanced \"\n                \"Firmware Update module\"),\n    CMD_HANDLER(\"afu-reconnect\", \"\", cmd_afu_reconnect,\n                \"Reconnects any ongoing PULL-mode downloads in the Advanced \"\n                \"Firmware Update module and if PULL-mode downloads are \"\n                \"suspended, resumes normal operation\"),\n#endif // ANJAY_WITH_MODULE_ADVANCED_FW_UPDATE\n#ifdef ANJAY_WITH_MODULE_SW_MGMT\n    CMD_HANDLER(\"set-sw-mgmt-package-path\", \"IID PATH\", cmd_set_sw_mgmt_package_path,\n                \"Sets the path where the software package will be saved when \"\n                \"Write to /9/x/2 or /9/x/3 is performed\"),\n    CMD_HANDLER(\"set-sw-mgmt-install-result\", \"IID RESULT\", cmd_set_sw_mgmt_install_result,\n                \"Attempts to set Software Install Result at runtime\"),\n    CMD_HANDLER(\"sw-mgmt-suspend\", \"\", cmd_sw_mgmt_suspend,\n                \"Suspends the operation of PULL-mode downloads in the Software Management module\"),\n    CMD_HANDLER(\"sw-mgmt-reconnect\", \"\", cmd_sw_mgmt_reconnect,\n                \"Reconnects any ongoing PULL-mode downloads in the Software Management module\"\n                \"and if PULL-mode downloads are suspended, resumes normal operation\"),\n#endif // ANJAY_WITH_MODULE_SW_MGMT\n    CMD_HANDLER(\"open-location-csv\", \"filename frequency=1\",\n                cmd_open_location_csv,\n                \"Opens a CSV file and starts using it for location information\"),\n    CMD_HANDLER(\"add-server\", \"uri\",\n                cmd_add_server, \"Adds another LwM2M Server to connect to\"),\n    CMD_HANDLER(\"trim-servers\", \"number\",\n                cmd_trim_servers,\n                \"Remove LwM2M Servers with specified ID and higher from the \"\n                \"set of servers provided on the command line, and reload the \"\n                \"Server Accounts. Note that any changes to the Security and \"\n                \"Server objects performed by the Bootstrap Server will be \"\n                \"discarded.\"),\n    CMD_HANDLER(\"socket-count\", \"\", cmd_socket_count,\n                \"Display number of sockets currently listening\"),\n    CMD_HANDLER(\"get-port\", \"index\", cmd_get_port,\n                \"Display listening port number of a socket with the specified \"\n                \"index (also supports Python-like negative indices)\"),\n    CMD_HANDLER(\"non-lwm2m-socket-count\", \"\", cmd_non_lwm2m_socket_count,\n                \"Display number of sockets currently listening that are not \"\n                \"affiliated to any LwM2M server connetion\"),\n    CMD_HANDLER(\"get-transport\", \"index\", cmd_get_transport,\n                \"Display transport used by a socket with the specified index \"\n                \"(also supports Python-like negative indices)\"),\n    CMD_HANDLER(\"enter-offline\", \"[transports...]\", cmd_enter_offline,\n                \"Enters Offline mode\"),\n    CMD_HANDLER(\"exit-offline\", \"[transports...]\", cmd_exit_offline,\n                \"Exits Offline mode\"),\n    CMD_HANDLER(\"notify\", \"\", cmd_notify,\n                \"Executes anjay_notify_* on a specified path\"),\n#ifdef ANJAY_WITH_SEND\n    CMD_HANDLER(\"send_deferrable\", \"SSID (/OID/IID/RID\" LWM2M_GATEWAY_PATH_LOG_PART \") [...]\",\n                cmd_send_deferrable,\n                \"Executes anjay_send_deferrable on a specified path.\" LWM2M_GATEWAY_DEVID_LOG_PART\n                \"\\ne.g. send_deferrable 1 /3/0/0\"),\n    CMD_HANDLER(\"send\", \"SSID (/OID/IID/RID\" LWM2M_GATEWAY_PATH_LOG_PART \") [...]\", cmd_send,\n                \"Executes anjay_send on a specified path.\" LWM2M_GATEWAY_DEVID_LOG_PART\n                \"\\ne.g. send 1 /3/0/0\"),\n#endif // ANJAY_WITH_SEND\n    CMD_HANDLER(\"unregister-object\", \"oid\", cmd_unregister_object,\n                \"Unregister an LwM2M Object\"),\n    CMD_HANDLER(\"reregister-object\", \"oid\", cmd_reregister_object,\n                \"Re-register a previously unregistered LwM2M Object\"),\n    CMD_HANDLER(\"download-blocks\",\n                \"url target_file [offset1-offset2 [offset3-[offset4 [...]]]]\",\n                cmd_download_blocks,\n                \"Download portions of a given URL to target_file.\"),\n    CMD_HANDLER(\"download\", \"url target_file [psk_identity psk_key]\",\n                cmd_download,\n                \"Download a file from given URL to target_file.\"),\n#ifdef ANJAY_WITH_ATTR_STORAGE\n#    ifdef ANJAY_WITH_LWM2M12\n#        define SUPPORTED_ATTRS \"pmin,pmax,lt,gt,st,epmin,epmax,hqmax\"\n#    else // ANJAY_WITH_LWM2M12\n#        define SUPPORTED_ATTRS \"pmin,pmax,lt,gt,st,epmin,epmax\"\n#    endif // ANJAY_WITH_LWM2M12\n    CMD_HANDLER(\"set-attrs\", \"\", cmd_set_attrs, \"Syntax [/a [/b [/c [/d] ] ] ] \"\n                \"ssid [\" SUPPORTED_ATTRS \"] - e.g. /a/b 1 pmin=3,pmax=4\"),\n#    undef SUPPORTED_ATTRS\n#endif // ANJAY_WITH_ATTR_STORAGE\n    CMD_HANDLER(\"disable-server\", \"ssid reactivate_timeout\", cmd_disable_server,\n                \"Disables a server with given SSID for a given time \"\n                \"(use -1 to disable idefinitely).\"),\n    CMD_HANDLER(\"enable-server\", \"ssid\", cmd_enable_server,\n                \"Enables a server with given SSID.\"),\n    CMD_HANDLER(\"get-all-connections-failed\", \"\", cmd_all_connections_failed,\n                \"Returns the result of anjay_all_connections_failed()\"),\n    CMD_HANDLER(\"schedule-update-on-exit\", \"\", cmd_schedule_update_on_exit,\n                \"Ensure Registration Update is scheduled for immediate \"\n                \"execution at the point of calling anjay_delete()\"),\n#ifdef ANJAY_WITH_LWM2M11\n    CMD_HANDLER(\"set-queue-mode-preference\", \"PREFERENCE\",\n                cmd_set_queue_mode_preference,\n                \"Sets queue mode preference; one of: FORCE_QUEUE_MODE, \"\n                \"PREFER_QUEUE_MODE, PREFER_ONLINE_MODE, FORCE_ONLINE_MODE\"),\n#endif // ANJAY_WITH_LWM2M11\n    CMD_HANDLER(\"set-lifetime\", \"IID LIFETIME\", cmd_set_lifetime,\n                \"Sets the lifetime for the specified Server Instance ID\"),\n    CMD_HANDLER(\"advance-time\", \"\", cmd_advance_time,\n                \"Advances real and monotonic clock readings by specified \"\n                \"number of seconds\"),\n#ifdef ANJAY_WITH_OBSERVATION_STATUS\n    CMD_HANDLER(\"observation-status\", \"(/OID/IID/RID\" LWM2M_GATEWAY_PATH_LOG_PART \")\",\n                cmd_observation_status,\n                \"Queries the observation status of a given Resource.\"\n                LWM2M_GATEWAY_DEVID_LOG_PART),\n#endif // ANJAY_WITH_OBSERVATION_STATUS\n    CMD_HANDLER(\"badc-write\", \"IID RIID value\", cmd_badc_write,\n                \"Writes new value to Binary App Data Container object\"),\n    CMD_HANDLER(\"set-event-log-data\", \"data\", cmd_set_event_log_data,\n                \"Sets LogData resource in Log Event object\"),\n#ifdef ANJAY_WITH_MODULE_FW_UPDATE\n    CMD_HANDLER(\"set-fw-update-result\", \"RESULT\", cmd_set_fw_update_result,\n                \"Attempts to set Firmware Update Result at runtime\"),\n    CMD_HANDLER(\"fw-update-suspend\", \"\", cmd_fw_update_suspend,\n                \"Suspends the operation of PULL-mode downloads in the Firmware \"\n                \"Update module\"),\n    CMD_HANDLER(\"fw-update-reconnect\", \"\", cmd_fw_update_reconnect,\n                \"Reconnects any ongoing PULL-mode downloads in the Firmware \"\n                \"Update module and if PULL-mode downloads are suspended, \"\n                \"resumes normal operation\"),\n#if defined(ANJAY_WITH_LWM2M11) && defined(ANJAY_WITH_MODULE_FW_UPDATE_V11_RESOURCES)\n    CMD_HANDLER(\"get-fw-update-deadline\", \"\", cmd_get_fw_update_deadline,\n                \"Gets the Firmware Update deadline\"),\n#endif /* defined(ANJAY_WITH_LWM2M11) && defined(ANJAY_WITH_MODULE_FW_UPDATE_V11_RESOURCES) */\n#endif // ANJAY_WITH_MODULE_FW_UPDATE\n    CMD_HANDLER(\"ongoing-registration-exists\", \"\",\n                cmd_ongoing_registration_exists,\n                \"Display information about ongoing registrations\"),\n    CMD_HANDLER(\"temperature-add-instance\", \"IID\",\n                cmd_temperature_add_instance,\n                \"Adds a new instance of the fake Temperature object. Maximal \"\n                \"IID of such instance is 16.\"),\n    CMD_HANDLER(\"temperature-remove-instance\", \"IID\",\n                cmd_temperature_remove_instance,\n                \"Removes instance of the fake Temperature object\"),\n    CMD_HANDLER(\"accelerometer-add-instance\", \"IID\",\n                cmd_accelerometer_add_instance,\n                \"Adds a new instance of the fake Accelerometer object. Maximal \"\n                \"IID of such instance is 16\"),\n    CMD_HANDLER(\"accelerometer-remove-instance\", \"IID\",\n                cmd_accelerometer_remove_instance,\n                \"Removes instance of the fake Accelerometer object\"),\n    CMD_HANDLER(\"push-button-add-instance\", \"IID application_type\",\n                cmd_push_button_add_instance,\n                \"Adds new instance of the fake Push Button object. Maximal \"\n                \"IID of such instance is 16. The initial value of the \\\"Application type\\\" \"\n                \"string will be set to application_type.\"),\n    CMD_HANDLER(\"push-button-remove-instance\", \"IID\",\n                cmd_push_button_remove_instance,\n                \"Removes the selected instance of the fake Push Button object\"),\n    CMD_HANDLER(\"push-button-press\", \"IID\",\n                cmd_push_button_press,\n                \"Presses the selected instance of the fake Push Button object\"),\n    CMD_HANDLER(\"push-button-release\", \"IID\",\n                cmd_push_button_release,\n                \"Releases the selected instance of the fake Push Button object.\"),\n    CMD_HANDLER(\"registration-expiration-time\", \"SSID\",\n                cmd_registration_expiration_time,\n                \"Displays time when registration with a given server expires\"),\n    CMD_HANDLER(\"next-lifecycle-operation\", \"[SSID|transports...]\",\n                cmd_next_lifecycle_operation,\n                \"Displays time when next lifecycle operation is scheduled for \"\n                \"any server (if no arguments specified), a given server (if \"\n                \"numeric SSID argument given) or a given set of transports \"\n                \"(if transport names given)\"),\n    CMD_HANDLER(\"next-planned-notify\", \"[SSID|transports...]\",\n                cmd_next_planned_notify,\n                \"Displays time when next planned notification trigger is \"\n                \"scheduled for any server (if no arguments specified), a given \"\n                \"server (if numeric SSID argument given) or a given set of \"\n                \"transports (if transport names given)\"),\n    CMD_HANDLER(\"next-planned-pmax-notify\", \"[SSID|transports...]\",\n                cmd_next_planned_pmax_notify,\n                \"Displays time when next planned notification trigger based on \"\n                \"the Maximum Period attribute is scheduled for any server (if \"\n                \"no arguments specified), a given server (if numeric SSID \"\n                \"argument given) or a given set of transports (if transport \"\n                \"names given)\"),\n    CMD_HANDLER(\"has-unsent-notifications\", \"[SSID|transports...]\",\n                cmd_has_unsent_notifications,\n                \"Checks whether there are some notifications which have been \"\n                \"postponed to be sent later for any server (if no arguments \"\n                \"specified), a given server (if numeric SSID argument given) \"\n                \"or a given set of transports (if transport names given)\"),\n    CMD_HANDLER(\"set-tx-param\", \"transport ack_timeout ack_random_factor \"\n                \"max_retransmit nstart\", cmd_set_tx_params,\n                \"Sets transmission parameters for a given transport. Available \"\n                \"transports are sms, udp and nidd.\"),\n    CMD_HANDLER(\"set-coap-exchange-timeout\", \"transport timeout\",\n                cmd_set_coap_exchange_timeout,\n                \"Sets maximal length of the CoAP exchange.\"),\n    CMD_HANDLER(\"set-dtls-handshake-timeout\", \"min max\", cmd_set_dtls_timeouts,\n                \"Sets DTLS handshake timeouts for all of the used DTLS sockets.\"),\n#ifdef ANJAY_WITH_COMMUNICATION_TIMESTAMP_API\n    CMD_HANDLER(\"last-registration-time\", \"[SSID]\",\n                cmd_last_registration_time,\n                \"Displays time of the last registration operation with any \"\n                \"server (if no argument specified) or a given server (if \"\n                \"numeric SSID argument given).\"),\n    CMD_HANDLER(\"next-update-time\", \"[SSID]\",\n                cmd_next_update_time,\n                \"Displays time when next update operation is scheduled for \"\n                \"any server (if no argument specified) or a given server (if \"\n                \"numeric SSID argument given).\"),\n    CMD_HANDLER(\"last-communication-time\", \"[SSID]\",\n                cmd_last_communication_time,\n                \"Displays time of the last communication with any server (if \"\n                \"no argument specified) or a given server (if numeric SSID \"\n                \"argument given).\"),\n#endif // ANJAY_WITH_COMMUNICATION_TIMESTAMP_API\n#ifdef ANJAY_WITH_CONN_STATUS_API\n    CMD_HANDLER(\"get-server-connection-status\", \"[SSID]\",\n                cmd_get_server_connection_status,\n                \"Displays current connection status for the server with SSID \"\n                \"specified by the argument.\"),\n#endif // ANJAY_WITH_CONN_STATUS_API\n#ifdef ANJAY_WITH_LWM2M_GATEWAY\n    CMD_HANDLER(\"gw_register\", \"dev_id\",\n                cmd_setup_end_device,\n                \"Registers End Device in LwM2M gateway context.\" LWM2M_GATEWAY_DEVID_LOG_PART),\n    CMD_HANDLER(\"gw_deregister\", \"dev_id\",\n                cmd_cleanup_end_device,\n                \"Deregisters End Device in LwM2M gateway context. \" LWM2M_GATEWAY_DEVID_LOG_PART),\n    CMD_HANDLER(\"gw_press_button\", \"dev_id\",\n                cmd_press_button_end_device,\n                \"Simulates pressing the button on End Device. \" LWM2M_GATEWAY_DEVID_LOG_PART),\n    CMD_HANDLER(\"gw_release_button\", \"dev_id\",\n                cmd_release_button_end_device,\n                \"Simulates releasing the button on End Device. \" LWM2M_GATEWAY_DEVID_LOG_PART),\n    CMD_HANDLER(\"gw-badc-write\", \"dev_id IID RIID value\",\n                cmd_badc_write_end_device,\n                \"Writes new value to Binary App Data Container object on End Device. \" LWM2M_GATEWAY_DEVID_LOG_PART),\n#endif // ANJAY_WITH_LWM2M_GATEWAY\n    CMD_HANDLER(\"help\", \"\", cmd_help, \"Prints this message\")\n    // clang-format on\n};\n#undef CMD_HANDLER\n\nstatic void print_line_with_indent(const char *line, const char *end) {\n    static const int INDENT = 5;\n    static const int SCREEN_WIDTH = 80;\n    const int MAX_LINE_LENGTH = SCREEN_WIDTH - INDENT - 1;\n    if (end - line > MAX_LINE_LENGTH) {\n        const char *prev = line;\n        const char *last = line;\n        while (last && (last - line) <= MAX_LINE_LENGTH) {\n            prev = last;\n            last = strchr(last + 1, ' ');\n        }\n        if (prev == line) {\n            prev = last;\n        }\n        if (prev && prev != end) {\n            print_line_with_indent(line, prev);\n            print_line_with_indent(prev + 1, end);\n            return;\n        }\n    }\n    for (int i = 0; i < INDENT; ++i) {\n        putchar(' ');\n    }\n    fwrite(line, 1, (size_t) (end - line), stdout);\n    putchar('\\n');\n}\n\nstatic void print_with_indent(const char *text) {\n    while (*text) {\n        const char *end = strchr(text, '\\n');\n        if (!end) {\n            end = text + strlen(text);\n        }\n        print_line_with_indent(text, end);\n        text = end;\n        if (*text) {\n            ++text;\n        }\n    }\n}\n\nstatic void cmd_help(anjay_demo_t *demo, const char *args_string) {\n    (void) demo;\n    (void) args_string;\n\n    puts(\"---\");\n    puts(\"LwM2M Demo client\");\n    puts(\"Available commands:\");\n    for (size_t idx = 0; idx < AVS_ARRAY_SIZE(COMMAND_HANDLERS); ++idx) {\n        const struct cmd_handler_def *cmd = &COMMAND_HANDLERS[idx];\n        printf(\"\\n%s %s\\n\", cmd->cmd_name, cmd->help_args);\n        print_with_indent(cmd->help_descr);\n    }\n    puts(\"---\");\n}\n\nstatic void handle_command(avs_sched_t *sched, const void *invocation_) {\n    (void) sched;\n    const demo_command_invocation_t *invocation =\n            (const demo_command_invocation_t *) invocation_;\n    if (invocation->cmd[0]) {\n        const struct cmd_handler_def *cmd = NULL;\n\n        for (size_t idx = 0; idx < AVS_ARRAY_SIZE(COMMAND_HANDLERS); ++idx) {\n            const struct cmd_handler_def *candidate_cmd =\n                    &COMMAND_HANDLERS[idx];\n\n            if (strncmp(invocation->cmd, candidate_cmd->cmd_name,\n                        candidate_cmd->cmd_name_length)\n                    == 0) {\n                cmd = candidate_cmd;\n                break;\n            }\n        }\n\n        if (cmd) {\n            demo_log(INFO, \"command: %s\", invocation->cmd);\n            cmd->handler(invocation->demo,\n                         invocation->cmd + cmd->cmd_name_length);\n        } else {\n            demo_log(ERROR, \"unrecognized command: %s\", invocation->cmd);\n        }\n    }\n\n    fprintf(stdout, \"(DEMO)>\");\n    fflush(stdout);\n}\n\nvoid demo_command_dispatch(const demo_command_invocation_t *invocation) {\n    if (AVS_SCHED_NOW(anjay_get_scheduler(invocation->demo->anjay), NULL,\n                      handle_command, invocation,\n                      offsetof(demo_command_invocation_t, cmd)\n                              + strlen(invocation->cmd) + 1)) {\n        demo_log(ERROR, \"Could not schedule handle_command\");\n    }\n}\n"
  },
  {
    "path": "demo/demo_cmds.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef DEMO_CMDS_H\n#define DEMO_CMDS_H\n\ntypedef struct {\n    struct anjay_demo_struct *demo;\n    char cmd[];\n} demo_command_invocation_t;\n\nvoid demo_command_dispatch(const demo_command_invocation_t *invocation);\n\n#endif\n"
  },
  {
    "path": "demo/demo_time.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n#define _GNU_SOURCE\n#include <pthread.h>\n#include <time.h>\n\n#include <avsystem/commons/avs_time.h>\n\n#include \"demo.h\"\n\nstatic avs_time_duration_t TIME_OFFSET;\nstatic pthread_mutex_t TIME_OFFSET_MUTEX = PTHREAD_MUTEX_INITIALIZER;\n\navs_time_real_t avs_time_real_now(void) {\n    struct timespec system_value;\n    avs_time_real_t result;\n    clock_gettime(CLOCK_REALTIME, &system_value);\n    result.since_real_epoch.seconds = system_value.tv_sec;\n    result.since_real_epoch.nanoseconds = (int32_t) system_value.tv_nsec;\n    pthread_mutex_lock(&TIME_OFFSET_MUTEX);\n    result = avs_time_real_add(result, TIME_OFFSET);\n    pthread_mutex_unlock(&TIME_OFFSET_MUTEX);\n    return result;\n}\n\navs_time_monotonic_t avs_time_monotonic_now(void) {\n    struct timespec system_value;\n    avs_time_monotonic_t result;\n#ifdef CLOCK_MONOTONIC\n    if (clock_gettime(CLOCK_MONOTONIC, &system_value))\n#endif\n    {\n        // CLOCK_MONOTONIC is not mandatory in POSIX;\n        // fallback to REALTIME if we don't have it\n        clock_gettime(CLOCK_REALTIME, &system_value);\n    }\n    result.since_monotonic_epoch.seconds = system_value.tv_sec;\n    result.since_monotonic_epoch.nanoseconds = (int32_t) system_value.tv_nsec;\n    pthread_mutex_lock(&TIME_OFFSET_MUTEX);\n    result = avs_time_monotonic_add(result, TIME_OFFSET);\n    pthread_mutex_unlock(&TIME_OFFSET_MUTEX);\n    return result;\n}\n\nvoid demo_advance_time(avs_time_duration_t duration) {\n    pthread_mutex_lock(&TIME_OFFSET_MUTEX);\n    TIME_OFFSET = avs_time_duration_add(TIME_OFFSET, duration);\n    pthread_mutex_unlock(&TIME_OFFSET_MUTEX);\n}\n"
  },
  {
    "path": "demo/demo_utils.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#if !defined(_POSIX_C_SOURCE) && !defined(__APPLE__)\n#    define _POSIX_C_SOURCE 200809L\n#endif\n\n#include <assert.h>\n#include <errno.h>\n#include <limits.h>\n#include <math.h>\n#include <stdlib.h>\n#include <string.h>\n#include <unistd.h>\n\n#include <sys/stat.h>\n#include <sys/types.h>\n\n#include \"demo_utils.h\"\n\nstatic struct {\n    size_t argc;\n    char **argv;\n} g_saved_args;\n\n#ifdef ANJAY_WITH_CONN_STATUS_API\nconst char *translate_server_connection_status_enum_to_str(\n        anjay_server_conn_status_t status) {\n    static const char *const demo_server_connection_states_str[] = {\n        [ANJAY_SERV_CONN_STATUS_INVALID] = \"INVALID\",\n        [ANJAY_SERV_CONN_STATUS_ERROR] = \"ERROR\",\n        [ANJAY_SERV_CONN_STATUS_INITIAL] = \"INITIAL\",\n        [ANJAY_SERV_CONN_STATUS_CONNECTING] = \"CONNECTING\",\n        [ANJAY_SERV_CONN_STATUS_BOOTSTRAPPING] = \"BOOTSTRAPPING\",\n        [ANJAY_SERV_CONN_STATUS_BOOTSTRAPPED] = \"BOOTSTRAPPED\",\n        [ANJAY_SERV_CONN_STATUS_REGISTERING] = \"REGISTERING\",\n        [ANJAY_SERV_CONN_STATUS_REGISTERED] = \"REGISTERED\",\n        [ANJAY_SERV_CONN_STATUS_REG_FAILURE] = \"REG_FAILURE\",\n        [ANJAY_SERV_CONN_STATUS_DEREGISTERING] = \"DEREGISTERING\",\n        [ANJAY_SERV_CONN_STATUS_DEREGISTERED] = \"DEREGISTERED\",\n        [ANJAY_SERV_CONN_STATUS_SUSPENDING] = \"SUSPENDING\",\n        [ANJAY_SERV_CONN_STATUS_SUSPENDED] = \"SUSPENDED\",\n        [ANJAY_SERV_CONN_STATUS_REREGISTERING] = \"REREGISTERING\",\n        [ANJAY_SERV_CONN_STATUS_UPDATING] = \"UPDATING\"\n    };\n    return demo_server_connection_states_str[status];\n}\n#endif // ANJAY_WITH_CONN_STATUS_API\n\nchar **argv_get(void) {\n    AVS_ASSERT(g_saved_args.argv, \"argv_store not called before argv_get\");\n    return g_saved_args.argv;\n}\n\nint argv_store(int argc, char **argv) {\n    AVS_ASSERT(argc >= 0, \"unexpected negative value of argc\");\n\n    char **argv_copy = (char **) avs_calloc((size_t) argc + 1, sizeof(char *));\n    if (!argv_copy) {\n        return -1;\n    }\n\n    for (size_t i = 0; i < (size_t) argc; ++i) {\n        argv_copy[i] = argv[i];\n    }\n\n    avs_free(g_saved_args.argv);\n    g_saved_args.argv = argv_copy;\n    g_saved_args.argc = (size_t) argc;\n    return 0;\n}\n\nint argv_append(const char *arg) {\n    assert(arg);\n\n    size_t new_argc = g_saved_args.argc + 1;\n\n    char **new_argv = (char **) avs_realloc(g_saved_args.argv,\n                                            (new_argc + 1) * sizeof(char *));\n    if (new_argv == NULL) {\n        return -1;\n    }\n\n    new_argv[new_argc - 1] = (char *) (intptr_t) arg;\n    new_argv[new_argc] = NULL;\n    g_saved_args.argv = new_argv;\n    g_saved_args.argc = new_argc;\n    return 0;\n}\n\nstatic double geo_distance_m_with_radians(double lat1,\n                                          double lon1,\n                                          double lat2,\n                                          double lon2) {\n    static const double MEAN_EARTH_PERIMETER_M = 12742017.6;\n    // Haversine formula\n    // code heavily inspired from http://stackoverflow.com/a/21623206\n    double a = 0.5 - 0.5 * cos(lat2 - lat1)\n               + cos(lat1) * cos(lat2) * 0.5 * (1.0 - cos(lon2 - lon1));\n    return MEAN_EARTH_PERIMETER_M * asin(sqrt(a));\n}\n\ndouble geo_distance_m(double lat1, double lon1, double lat2, double lon2) {\n    return geo_distance_m_with_radians(deg2rad(lat1), deg2rad(lon1),\n                                       deg2rad(lat2), deg2rad(lon2));\n}\n\nint demo_parse_long(const char *str, long *out_value) {\n    if (!str) {\n        return -1;\n    }\n\n    char *endptr = NULL;\n\n    errno = 0;\n    long value = strtol(str, &endptr, 0);\n\n    if ((errno == ERANGE && (value == LONG_MAX || value == LONG_MIN))\n            || (errno != 0 && value == 0) || endptr == str || !endptr\n            || *endptr != '\\0') {\n        demo_log(ERROR, \"could not parse number: %s\", str);\n        return -1;\n    }\n\n    *out_value = value;\n    return 0;\n}\n\nint fetch_bytes(anjay_input_ctx_t *ctx, void **buffer, size_t *out_size) {\n    char tmp[1024];\n    bool finished = 0;\n    int result;\n    // This will be used as a counter now.\n    *out_size = 0;\n    do {\n        size_t bytes_read = 0;\n        if ((result = anjay_get_bytes(ctx, &bytes_read, &finished, tmp,\n                                      sizeof(tmp)))) {\n            goto error;\n        }\n        if (*out_size + bytes_read == 0) {\n            avs_free(*buffer);\n            *buffer = NULL;\n            return 0;\n        }\n        void *block = avs_realloc(*buffer, *out_size + bytes_read);\n        if (!block) {\n            result = ANJAY_ERR_INTERNAL;\n            goto error;\n        }\n        memcpy((char *) block + *out_size, tmp, bytes_read);\n        *buffer = block;\n        *out_size += bytes_read;\n    } while (!finished);\n    return 0;\n\nerror:\n    avs_free(*buffer);\n    *buffer = NULL;\n    return result;\n}\n\nstatic int open_temporary_file(char *path) {\n    mode_t old_umask = (mode_t) umask(S_IRGRP | S_IWGRP | S_IXGRP | S_IROTH\n                                      | S_IWOTH | S_IXOTH);\n    int fd = mkstemp(path);\n    umask(old_umask);\n    return fd;\n}\n\nchar *generate_random_target_filepath(void) {\n    char *result = NULL;\n    if (!(result = avs_strdup(\"/tmp/anjay-fw-XXXXXX\"))) {\n        return NULL;\n    }\n\n    int fd = open_temporary_file(result);\n    if (fd == -1) {\n        demo_log(ERROR, \"could not generate firmware filename: %s\",\n                 strerror(errno));\n        avs_free(result);\n        return NULL;\n    }\n    close(fd);\n    return result;\n}\n\nint copy_file_contents(FILE *dst, FILE *src) {\n    while (!feof(src)) {\n        char buf[4096];\n\n        size_t bytes_read = fread(buf, 1, sizeof(buf), src);\n        if (bytes_read == 0 && ferror(src)) {\n            return -1;\n        }\n\n        if (fwrite(buf, 1, bytes_read, dst) != bytes_read) {\n            return -1;\n        }\n    }\n    return 0;\n}\n\n// CRC32 code adapted from http://home.thep.lu.se/~bjorn/crc/\nstatic uint32_t crc32_for_byte(uint8_t value) {\n    uint32_t result = value;\n    for (int i = 0; i < 8; ++i) {\n        if (result & 1) {\n            result >>= 1;\n        } else {\n            result = (result >> 1) ^ (uint32_t) 0xEDB88320UL;\n        }\n    }\n    return result ^ (uint32_t) 0xFF000000UL;\n}\n\nstatic void crc32(uint32_t *inout_crc, const uint8_t *data, size_t size) {\n    static uint32_t LOOKUP_TABLE[256];\n    if (!*LOOKUP_TABLE) {\n        for (size_t i = 0; i < AVS_ARRAY_SIZE(LOOKUP_TABLE); ++i) {\n            LOOKUP_TABLE[i] = crc32_for_byte((uint8_t) i);\n        }\n    }\n\n    for (size_t i = 0; i < size; ++i) {\n        *inout_crc = LOOKUP_TABLE[data[i] ^ (uint8_t) *inout_crc]\n                     ^ (*inout_crc >> 8);\n    }\n}\n\nint calc_file_crc32(const char *filename, uint32_t *out_crc) {\n    FILE *f = fopen(filename, \"rb\");\n    if (!f) {\n        demo_log(ERROR, \"could not open %s\", filename);\n        return -1;\n    }\n\n    *out_crc = 0;\n    unsigned char buf[4096];\n    int result = -1;\n\n    while (!feof(f)) {\n        size_t bytes_read = fread(buf, 1, sizeof(buf), f);\n        if (bytes_read == 0 && ferror(f)) {\n            demo_log(ERROR, \"could not read from %s: %s\", filename,\n                     strerror(errno));\n            goto cleanup;\n        }\n\n        crc32(out_crc, buf, bytes_read);\n    }\n\n    result = 0;\n\ncleanup:\n    fclose(f);\n    return result;\n}\n\n#if defined(AVS_COMMONS_WITH_AVS_PERSISTENCE) \\\n        && defined(AVS_COMMONS_STREAM_WITH_FILE)\navs_error_t store_etag(avs_persistence_context_t *ctx,\n                       const anjay_etag_t *etag) {\n    bool use_etag = (etag != NULL);\n    avs_error_t err;\n\n    (void) (avs_is_err((err = avs_persistence_bool(ctx, &use_etag))) || !etag\n            || avs_is_err((err = avs_persistence_u8(\n                                   ctx, (uint8_t *) (intptr_t) &etag->size)))\n            || avs_is_err((err = avs_persistence_bytes(\n                                   ctx, (uint8_t *) (intptr_t) etag->value,\n                                   etag->size))));\n    return err;\n}\n\navs_error_t restore_etag(avs_persistence_context_t *ctx, anjay_etag_t **etag) {\n    assert(etag && !*etag);\n    bool use_etag;\n    avs_error_t err;\n    uint8_t size8;\n    if (avs_is_err((err = avs_persistence_bool(ctx, &use_etag))) || !use_etag\n            || avs_is_err((err = avs_persistence_u8(ctx, &size8)))\n            || avs_is_err((err = ((*etag = anjay_etag_new(size8))\n                                          ? avs_errno(AVS_NO_ERROR)\n                                          : avs_errno(AVS_ENOMEM))))) {\n        return err;\n    }\n\n    if (avs_is_err(err = avs_persistence_bytes(ctx, (*etag)->value, size8))) {\n        avs_free(*etag);\n        *etag = NULL;\n    }\n    return err;\n}\n#endif // defined(AVS_COMMONS_WITH_AVS_PERSISTENCE) &&\n       // defined(AVS_COMMONS_STREAM_WITH_FILE)\n"
  },
  {
    "path": "demo/demo_utils.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef DEMO_UTILS_H\n#define DEMO_UTILS_H\n\n#include <math.h>\n#include <stdbool.h>\n#include <time.h>\n\n#include <avsystem/commons/avs_log.h>\n#include <avsystem/commons/avs_time.h>\n\n#include <anjay/anjay.h>\n\n#define demo_log(level, ...) avs_log(demo, level, __VA_ARGS__)\n\nchar **argv_get(void);\nint argv_store(int argc, char **argv);\nint argv_append(const char *arg);\n\nstatic inline unsigned time_to_rand(void) {\n    return 1103515245u * (unsigned) avs_time_real_now().since_real_epoch.seconds\n           + 12345u;\n}\n\n// this is the most precise representation possible using the double type\n#define PI_OVER_180 0.017453292519943295\n\nstatic inline double deg2rad(double deg) {\n    return deg * PI_OVER_180;\n}\n\nstatic inline double rad2deg(double rad) {\n    // double(pi/180) has lower relative error than double(180/pi)\n    // hence I decided to use division rather than defining 180/pi as a constant\n    return rad / PI_OVER_180;\n}\n\nstatic inline bool latitude_valid(double value) {\n    return isfinite(value) && value >= -90.0 && value <= 90.0;\n}\n\nstatic inline bool longitude_valid(double value) {\n    return isfinite(value) && value >= -180.0 && value < 180.0;\n}\n\nstatic inline bool velocity_mps_valid(double value) {\n    return !isnan(value) && value >= 0.0;\n}\n\nstatic inline bool velocity_bearing_deg_cw_n_valid(double value) {\n    return isfinite(value) && value >= 0.0 && value < 360.0;\n}\n\ndouble geo_distance_m(double lat1, double lon1, double lat2, double lon2);\n\nint demo_parse_long(const char *str, long *out_value);\n\nint fetch_bytes(anjay_input_ctx_t *ctx, void **buffer, size_t *out_size);\n\nchar *generate_random_target_filepath(void);\n\nint copy_file_contents(FILE *dst, FILE *src);\n\nint calc_file_crc32(const char *filename, uint32_t *out_crc);\n\n#ifdef ANJAY_WITH_CONN_STATUS_API\nconst char *translate_server_connection_status_enum_to_str(\n        anjay_server_conn_status_t status);\n#endif // ANJAY_WITH_CONN_STATUS_API\n\n#if defined(AVS_COMMONS_WITH_AVS_PERSISTENCE) \\\n        && defined(AVS_COMMONS_STREAM_WITH_FILE)\navs_error_t store_etag(avs_persistence_context_t *ctx,\n                       const anjay_etag_t *etag);\navs_error_t restore_etag(avs_persistence_context_t *ctx, anjay_etag_t **etag);\n#endif // defined(AVS_COMMONS_WITH_AVS_PERSISTENCE) &&\n       // defined(AVS_COMMONS_STREAM_WITH_FILE)\n\ntypedef struct {\n    char data[1]; // actually a VLA, but struct cannot be empty\n} anjay_demo_allocated_buffer_t;\n\n#endif // DEMO_UTILS_H\n"
  },
  {
    "path": "demo/firmware_update.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include \"firmware_update.h\"\n#include \"demo.h\"\n#include \"demo_utils.h\"\n\n#include <errno.h>\n#include <stdint.h>\n#include <stdio.h>\n#include <string.h>\n\n#include <sys/stat.h>\n#include <sys/types.h>\n#include <unistd.h>\n\n#include <avsystem/commons/avs_persistence.h>\n\n#include <avsystem/commons/avs_memory.h>\n#include <avsystem/commons/avs_stream_file.h>\n#include <avsystem/commons/avs_utils.h>\n\n#define FORCE_ERROR_OUT_OF_MEMORY 1\n#define FORCE_ERROR_FAILED_UPDATE 2\n#define FORCE_DELAYED_SUCCESS 3\n#define FORCE_DELAYED_ERROR_FAILED_UPDATE 4\n#define FORCE_SET_SUCCESS_FROM_PERFORM_UPGRADE 5\n#define FORCE_SET_FAILURE_FROM_PERFORM_UPGRADE 6\n#define FORCE_DO_NOTHING 7\n#ifdef ANJAY_WITH_LWM2M11\n#    define FORCE_DEFER 8\n#endif // ANJAY_WITH_LWM2M11\n\n#define HEADER_VER_FW 2\n\nstatic int maybe_create_firmware_file(fw_update_logic_t *fw) {\n    if (fw->next_target_path) {\n        return 0;\n    }\n    if (fw->administratively_set_target_path) {\n        fw->next_target_path = avs_strdup(fw->administratively_set_target_path);\n    } else {\n        fw->next_target_path = generate_random_target_filepath();\n    }\n    if (!fw->next_target_path) {\n        return -1;\n    }\n    demo_log(INFO, \"Created %s\", fw->next_target_path);\n    return 0;\n}\n\nstatic void maybe_delete_firmware_file(fw_update_logic_t *fw) {\n    if (fw->next_target_path) {\n        unlink(fw->next_target_path);\n        demo_log(INFO, \"Deleted %s\", fw->next_target_path);\n        avs_free(fw->next_target_path);\n        fw->next_target_path = NULL;\n    }\n}\n\nvoid firmware_update_set_package_path(fw_update_logic_t *fw, const char *path) {\n    if (fw->stream) {\n        demo_log(ERROR,\n                 \"cannot set package path while a download is in progress\");\n        return;\n    }\n    char *new_target_path = avs_strdup(path);\n    if (!new_target_path) {\n        demo_log(ERROR, \"out of memory\");\n        return;\n    }\n\n    avs_free(fw->administratively_set_target_path);\n    fw->administratively_set_target_path = new_target_path;\n    demo_log(INFO, \"firmware package path set to %s\",\n             fw->administratively_set_target_path);\n}\n\nstatic void fix_fw_meta_endianness(fw_metadata_t *meta) {\n    meta->header_ver = avs_convert_be16(meta->header_ver);\n    meta->force_error_case = avs_convert_be16(meta->force_error_case);\n    meta->crc = avs_convert_be32(meta->crc);\n}\n\nstatic int read_fw_meta_from_file(FILE *f, fw_metadata_t *out_metadata) {\n    fw_metadata_t m;\n    memset(&m, 0, sizeof(m));\n\n    if (fread(m.magic, sizeof(m.magic), 1, f) != 1\n            || fread(&m.header_ver, sizeof(m.header_ver), 1, f) != 1\n            || fread(&m.force_error_case, sizeof(m.force_error_case), 1, f) != 1\n            || fread(&m.crc, sizeof(m.crc), 1, f) != 1\n            || fread(&m.pkg_ver_len, sizeof(m.pkg_ver_len), 1, f) != 1) {\n        demo_log(ERROR, \"could not read firmware metadata\");\n        return -1;\n    }\n\n    if (m.pkg_ver_len > IMG_VER_STR_MAX_LEN || m.pkg_ver_len == 0) {\n        demo_log(ERROR, \"Wrong pkg version len\");\n        return ANJAY_FW_UPDATE_ERR_UNSUPPORTED_PACKAGE_TYPE;\n    }\n\n    if (fread(m.pkg_ver, m.pkg_ver_len, 1, f) != 1) {\n        demo_log(ERROR, \"could not read firmware metadata\");\n        return -1;\n    }\n\n    fix_fw_meta_endianness(&m);\n    *out_metadata = m;\n    return 0;\n}\n\nstatic int unpack_fw_to_file(const char *fw_pkg_path,\n                             const char *target_path,\n                             fw_metadata_t *out_metadata) {\n    int result = -1;\n    FILE *fw = fopen(fw_pkg_path, \"rb\");\n    FILE *tmp = NULL;\n\n    if (!fw) {\n        demo_log(ERROR, \"could not open file: %s\", fw_pkg_path);\n        goto cleanup;\n    }\n\n    tmp = fopen(target_path, \"wb\");\n    if (!tmp) {\n        demo_log(ERROR, \"could not open file: %s\", target_path);\n        goto cleanup;\n    }\n\n    result = read_fw_meta_from_file(fw, out_metadata);\n    if (result) {\n        demo_log(ERROR, \"could not read metadata from file: %s\", fw_pkg_path);\n        goto cleanup;\n    }\n    result = copy_file_contents(tmp, fw);\n    if (result) {\n        demo_log(ERROR, \"could not copy firmware from %s to %s\", fw_pkg_path,\n                 target_path);\n        goto cleanup;\n    }\n\n    result = 0;\n\ncleanup:\n    if (fw) {\n        fclose(fw);\n    }\n    if (tmp) {\n        fclose(tmp);\n    }\n    return result;\n}\n\nstatic int unpack_firmware_in_place(fw_update_logic_t *fw) {\n    char *tmp_path = generate_random_target_filepath();\n    if (!tmp_path) {\n        return -1;\n    }\n\n    int result =\n            unpack_fw_to_file(fw->next_target_path, tmp_path, &fw->metadata);\n    if (result) {\n        goto cleanup;\n    }\n\n    if ((result = rename(tmp_path, fw->next_target_path)) == -1) {\n        demo_log(ERROR, \"could not rename %s to %s: %s\", tmp_path,\n                 fw->next_target_path, strerror(errno));\n        goto cleanup;\n    }\n    if ((result = chmod(fw->next_target_path, 0700)) == -1) {\n        demo_log(ERROR, \"could not set permissions for %s: %s\",\n                 fw->next_target_path, strerror(errno));\n        goto cleanup;\n    }\n\ncleanup:\n    unlink(tmp_path);\n    avs_free(tmp_path);\n    if (result) {\n        maybe_delete_firmware_file(fw);\n    }\n\n    return result;\n}\n\nstatic bool fw_magic_valid(const fw_metadata_t *meta) {\n    if (memcmp(meta->magic, \"ANJAY_FW\", sizeof(meta->magic))) {\n        demo_log(ERROR, \"invalid firmware magic\");\n        return false;\n    }\n\n    return true;\n}\n\nstatic bool fw_header_version_valid(const fw_metadata_t *meta) {\n    if (meta->header_ver != HEADER_VER_FW) {\n        demo_log(ERROR, \"wrong header version\");\n        return false;\n    }\n\n    return true;\n}\n\nstatic bool fw_version_supported(const fw_metadata_t *meta) {\n    if (memcmp(meta->pkg_ver, VER_DEFAULT, meta->pkg_ver_len)) {\n        demo_log(ERROR, \"unsupported firmware version: %s\", meta->pkg_ver);\n        return false;\n    }\n\n    return true;\n}\n\nstatic int validate_firmware(fw_update_logic_t *fw) {\n    if (!fw_magic_valid(&fw->metadata)\n            || !fw_header_version_valid(&fw->metadata)\n            || !fw_version_supported(&fw->metadata)) {\n        return ANJAY_FW_UPDATE_ERR_UNSUPPORTED_PACKAGE_TYPE;\n    }\n\n    uint32_t actual_crc;\n    int result = calc_file_crc32(fw->next_target_path, &actual_crc);\n\n    if (result) {\n        demo_log(WARNING, \"unable to check firmware CRC\");\n        return ANJAY_FW_UPDATE_ERR_INTEGRITY_FAILURE;\n    }\n\n    if (fw->metadata.crc != actual_crc) {\n        demo_log(WARNING, \"CRC mismatch: expected %08x != %08x actual\",\n                 fw->metadata.crc, actual_crc);\n        return ANJAY_FW_UPDATE_ERR_INTEGRITY_FAILURE;\n    }\n\n    switch (fw->metadata.force_error_case) {\n    case FORCE_ERROR_OUT_OF_MEMORY:\n        return ANJAY_FW_UPDATE_ERR_OUT_OF_MEMORY;\n    default:\n        break;\n    }\n\n    return 0;\n}\n\nstatic int preprocess_firmware(fw_update_logic_t *fw) {\n    if (unpack_firmware_in_place(fw)) {\n        return ANJAY_FW_UPDATE_ERR_UNSUPPORTED_PACKAGE_TYPE;\n    }\n\n    int result = validate_firmware(fw);\n    if (!result) {\n        demo_log(INFO, \"firmware downloaded successfully\");\n    }\n    return result;\n}\n\n#if defined(AVS_COMMONS_WITH_AVS_PERSISTENCE) \\\n        && defined(AVS_COMMONS_STREAM_WITH_FILE)\n\nstatic int write_persistence_file(const char *path,\n                                  anjay_fw_update_initial_result_t result,\n                                  const char *uri,\n                                  char *download_file,\n                                  bool filename_administratively_set,\n                                  const anjay_etag_t *etag,\n                                  anjay_fw_update_severity_t severity,\n                                  avs_time_real_t last_state_change_time,\n                                  avs_time_real_t update_deadline) {\n#    if !defined(ANJAY_WITH_LWM2M11) \\\n            || !defined(ANJAY_WITH_MODULE_FW_UPDATE_V11_RESOURCES)\n    (void) severity;\n    (void) last_state_change_time;\n    (void) update_deadline;\n#    endif /* !defined(ANJAY_WITH_LWM2M11) && \\\n              !defined(ANJAY_WITH_MODULE_FW_UPDATE_V11_RESOURCES) */\n    avs_stream_t *stream = avs_stream_file_create(path, AVS_STREAM_FILE_WRITE);\n    avs_persistence_context_t ctx =\n            avs_persistence_store_context_create(stream);\n    int8_t result8 = (int8_t) result;\n#    if defined(ANJAY_WITH_LWM2M11) \\\n            && defined(ANJAY_WITH_MODULE_FW_UPDATE_V11_RESOURCES)\n    uint8_t severity8 = (uint8_t) severity;\n    int64_t last_state_change_timestamp = 0;\n    avs_time_real_to_scalar(&last_state_change_timestamp, AVS_TIME_S,\n                            last_state_change_time);\n    int64_t update_timestamp = 0;\n    avs_time_real_to_scalar(&update_timestamp, AVS_TIME_S, update_deadline);\n#    endif /* defined(ANJAY_WITH_LWM2M11) && \\\n              defined(ANJAY_WITH_MODULE_FW_UPDATE_V11_RESOURCES) */\n    int retval = 0;\n    if (!stream\n            || avs_is_err(avs_persistence_bytes(&ctx, (uint8_t *) &result8, 1))\n            || avs_is_err(\n                       avs_persistence_string(&ctx, (char **) (intptr_t) &uri))\n            || avs_is_err(avs_persistence_string(&ctx, &download_file))\n            || avs_is_err(avs_persistence_bool(&ctx,\n                                               &filename_administratively_set))\n            || avs_is_err(store_etag(&ctx, etag))\n#    if defined(ANJAY_WITH_LWM2M11) \\\n            && defined(ANJAY_WITH_MODULE_FW_UPDATE_V11_RESOURCES)\n            || avs_is_err(avs_persistence_bytes(&ctx, &severity8, 1))\n            || avs_is_err(\n                       avs_persistence_i64(&ctx, &last_state_change_timestamp))\n            || avs_is_err(avs_persistence_i64(&ctx, &update_timestamp))\n#    endif /* defined(ANJAY_WITH_LWM2M11) && \\\n              defined(ANJAY_WITH_MODULE_FW_UPDATE_V11_RESOURCES) */\n    ) {\n        demo_log(ERROR, \"Could not write firmware state persistence file\");\n        retval = -1;\n    }\n    if (stream) {\n        avs_stream_cleanup(&stream);\n    }\n    if (retval) {\n        unlink(path);\n    }\n    return retval;\n}\n\nstatic void delete_persistence_file(const fw_update_logic_t *fw) {\n    unlink(fw->persistence_file);\n}\n#else  // defined(AVS_COMMONS_WITH_AVS_PERSISTENCE) &&\n       // defined(AVS_COMMONS_STREAM_WITH_FILE)\nstatic int write_persistence_file(const char *path,\n                                  anjay_fw_update_initial_result_t result,\n                                  const char *uri,\n                                  char *download_file,\n                                  bool filename_administratively_set,\n                                  const anjay_etag_t *etag,\n                                  anjay_fw_update_severity_t severity,\n                                  avs_time_real_t last_state_change_time,\n                                  avs_time_real_t update_deadline) {\n    (void) path;\n    (void) result;\n    (void) uri;\n    (void) download_file;\n    (void) filename_administratively_set;\n    (void) etag;\n    (void) severity;\n    (void) last_state_change_time;\n    (void) update_deadline;\n    demo_log(WARNING, \"Persistence not compiled in\");\n    return 0;\n}\n\nstatic void delete_persistence_file(const fw_update_logic_t *fw) {\n    (void) fw;\n    demo_log(WARNING, \"Persistence not compiled in\");\n}\n#endif // defined(AVS_COMMONS_WITH_AVS_PERSISTENCE) &&\n       // defined(AVS_COMMONS_STREAM_WITH_FILE)\n\nstatic void fw_reset(void *fw_) {\n    fw_update_logic_t *fw = (fw_update_logic_t *) fw_;\n    if (fw->stream) {\n        fclose(fw->stream);\n        fw->stream = NULL;\n    }\n    avs_free(fw->package_uri);\n    fw->package_uri = NULL;\n    maybe_delete_firmware_file(fw);\n    delete_persistence_file(fw);\n    if (fw->auto_suspend) {\n        anjay_fw_update_pull_suspend(fw->anjay);\n    }\n}\n\nstatic int fw_stream_open(void *fw_,\n                          const char *package_uri,\n                          const struct anjay_etag *package_etag) {\n    fw_update_logic_t *fw = (fw_update_logic_t *) fw_;\n\n    assert(!fw->stream);\n\n    char *uri = NULL;\n    if (package_uri && !(uri = avs_strdup(package_uri))) {\n        demo_log(ERROR, \"Out of memory\");\n        return -1;\n    }\n\n    if (maybe_create_firmware_file(fw)) {\n        avs_free(uri);\n        return -1;\n    }\n\n    if (!(fw->stream = fopen(fw->next_target_path, \"wb\"))) {\n        demo_log(ERROR, \"could not open file: %s\", fw->next_target_path);\n        avs_free(uri);\n        return -1;\n    }\n\n    avs_free(fw->package_uri);\n    fw->package_uri = uri;\n#if defined(ANJAY_WITH_LWM2M11) \\\n        && defined(ANJAY_WITH_MODULE_FW_UPDATE_V11_RESOURCES)\n    if (write_persistence_file(\n                fw->persistence_file, ANJAY_FW_UPDATE_INITIAL_DOWNLOADING,\n                package_uri, fw->next_target_path,\n                !!fw->administratively_set_target_path, package_etag,\n                anjay_fw_update_get_severity(fw->anjay),\n                anjay_fw_update_get_last_state_change_time(fw->anjay),\n                anjay_fw_update_get_deadline(fw->anjay))) {\n#else  /* defined(ANJAY_WITH_LWM2M11) && \\\n          defined(ANJAY_WITH_MODULE_FW_UPDATE_V11_RESOURCES) */\n    if (write_persistence_file(\n                fw->persistence_file, ANJAY_FW_UPDATE_INITIAL_DOWNLOADING,\n                package_uri, fw->next_target_path,\n                !!fw->administratively_set_target_path, package_etag,\n                (anjay_fw_update_severity_t) 0, (avs_time_real_t) { { 0, 0 } },\n                (avs_time_real_t) { { 0, 0 } })) {\n#endif /* defined(ANJAY_WITH_LWM2M11) && \\\n          defined(ANJAY_WITH_MODULE_FW_UPDATE_V11_RESOURCES) */\n        fw_reset(fw_);\n        return -1;\n    }\n\n    return 0;\n}\n\nstatic int fw_stream_write(void *fw_, const void *data, size_t length) {\n    fw_update_logic_t *fw = (fw_update_logic_t *) fw_;\n    if (!fw->stream) {\n        demo_log(ERROR, \"stream not open\");\n        return -1;\n    }\n    if (length\n            && (fwrite(data, length, 1, fw->stream) != 1\n                // Firmware update integration tests measure download\n                // progress by checking file size, so avoiding buffering\n                // is required.\n                || fflush(fw->stream) != 0)) {\n        demo_log(ERROR, \"fwrite or fflush failed: %s\", strerror(errno));\n        return ANJAY_FW_UPDATE_ERR_NOT_ENOUGH_SPACE;\n    }\n\n    return 0;\n}\n\nstatic int fw_stream_finish(void *fw_) {\n    fw_update_logic_t *fw = (fw_update_logic_t *) fw_;\n    if (fw->auto_suspend) {\n        anjay_fw_update_pull_suspend(fw->anjay);\n    }\n    if (!fw->stream) {\n        demo_log(ERROR, \"stream not open\");\n        return -1;\n    }\n    fclose(fw->stream);\n    fw->stream = NULL;\n\n    int result;\n    if ((result = preprocess_firmware(fw))) {\n        return result;\n    }\n#if defined(ANJAY_WITH_LWM2M11) \\\n        && defined(ANJAY_WITH_MODULE_FW_UPDATE_V11_RESOURCES)\n    result = write_persistence_file(\n            fw->persistence_file, ANJAY_FW_UPDATE_INITIAL_DOWNLOADED,\n            fw->package_uri, fw->next_target_path,\n            !!fw->administratively_set_target_path, NULL,\n            anjay_fw_update_get_severity(fw->anjay),\n            anjay_fw_update_get_last_state_change_time(fw->anjay),\n            anjay_fw_update_get_deadline(fw->anjay));\n#else  /* defined(ANJAY_WITH_LWM2M11) && \\\n          defined(ANJAY_WITH_MODULE_FW_UPDATE_V11_RESOURCES) */\n    result = write_persistence_file(fw->persistence_file,\n                                    ANJAY_FW_UPDATE_INITIAL_DOWNLOADED,\n                                    fw->package_uri, fw->next_target_path,\n                                    !!fw->administratively_set_target_path,\n                                    NULL, (anjay_fw_update_severity_t) 0,\n                                    (avs_time_real_t) { { 0, 0 } },\n                                    (avs_time_real_t) { { 0, 0 } });\n#endif /* defined(ANJAY_WITH_LWM2M11) && \\\n          defined(ANJAY_WITH_MODULE_FW_UPDATE_V11_RESOURCES) */\n    if (result) {\n        fw_reset(fw);\n    }\n    return result;\n}\n\nstatic const char *fw_get_name(void *fw) {\n    (void) fw;\n    return \"Cute Firmware\";\n}\n\nstatic const char *fw_get_version(void *fw) {\n    (void) fw;\n    return \"1.0\";\n}\n\nstatic int fw_perform_upgrade(void *fw_) {\n    fw_update_logic_t *fw = (fw_update_logic_t *) fw_;\n\n    int result;\n#if defined(ANJAY_WITH_LWM2M11) \\\n        && defined(ANJAY_WITH_MODULE_FW_UPDATE_V11_RESOURCES)\n    int64_t update_deadline_timestamp;\n    avs_time_real_t update_deadline = anjay_fw_update_get_deadline(fw->anjay);\n    anjay_fw_update_severity_t update_severity =\n            anjay_fw_update_get_severity(fw->anjay);\n    if (avs_time_real_to_scalar(&update_deadline_timestamp, AVS_TIME_S,\n                                update_deadline)) {\n        demo_log(INFO, \"Firmware Update Deadline is not present\");\n    } else {\n        char update_deadline_text[64];\n        strftime(update_deadline_text, sizeof(update_deadline_text),\n                 \"%Y-%m-%d %H:%M:%S\",\n                 localtime((const time_t *) &update_deadline_timestamp));\n        demo_log(INFO, \"Firmware Update Deadline: %s, Severity: %d\",\n                 update_deadline_text, update_severity);\n    }\n    result = write_persistence_file(\n            fw->persistence_file, ANJAY_FW_UPDATE_INITIAL_SUCCESS, NULL,\n            fw->next_target_path, !!fw->administratively_set_target_path, NULL,\n            update_severity,\n            anjay_fw_update_get_last_state_change_time(fw->anjay),\n            update_deadline);\n#else  /* defined(ANJAY_WITH_LWM2M11) && \\\n          defined(ANJAY_WITH_MODULE_FW_UPDATE_V11_RESOURCES) */\n    result = write_persistence_file(\n            fw->persistence_file, ANJAY_FW_UPDATE_INITIAL_SUCCESS, NULL,\n            fw->next_target_path, !!fw->administratively_set_target_path, NULL,\n            (anjay_fw_update_severity_t) 0, (avs_time_real_t) { { 0, 0 } },\n            (avs_time_real_t) { { 0, 0 } });\n#endif /* defined(ANJAY_WITH_LWM2M11) && \\\n          defined(ANJAY_WITH_MODULE_FW_UPDATE_V11_RESOURCES) */\n\n    if (result) {\n        delete_persistence_file(fw);\n        return -1;\n    }\n\n    demo_log(INFO, \"*** FIRMWARE UPDATE: %s ***\", fw->next_target_path);\n    switch (fw->metadata.force_error_case) {\n    case FORCE_ERROR_FAILED_UPDATE:\n        demo_log(ERROR, \"update failed\");\n        delete_persistence_file(fw);\n        return -1;\n    case FORCE_DELAYED_SUCCESS:\n        if (argv_append(\"--delayed-upgrade-result\") || argv_append(\"1\")) {\n            demo_log(ERROR, \"could not append delayed result to argv\");\n            return -1;\n        }\n        break;\n    case FORCE_DELAYED_ERROR_FAILED_UPDATE:\n        if (argv_append(\"--delayed-upgrade-result\") || argv_append(\"8\")) {\n            demo_log(ERROR, \"could not append delayed result to argv\");\n            return -1;\n        }\n        break;\n    case FORCE_SET_SUCCESS_FROM_PERFORM_UPGRADE:\n        if (anjay_fw_update_set_result(fw->anjay,\n                                       ANJAY_FW_UPDATE_RESULT_SUCCESS)) {\n            demo_log(ERROR, \"anjay_fw_update_set_result failed\");\n            return -1;\n        }\n        return 0;\n    case FORCE_SET_FAILURE_FROM_PERFORM_UPGRADE:\n        if (anjay_fw_update_set_result(fw->anjay,\n                                       ANJAY_FW_UPDATE_RESULT_FAILED)) {\n            demo_log(ERROR, \"anjay_fw_update_set_result failed\");\n            return -1;\n        }\n        return 0;\n    case FORCE_DO_NOTHING:\n        return 0;\n#ifdef ANJAY_WITH_LWM2M11\n    case FORCE_DEFER:\n        if (anjay_fw_update_set_result(fw->anjay,\n                                       ANJAY_FW_UPDATE_RESULT_DEFERRED)) {\n            demo_log(ERROR, \"anjay_fw_update_set_result failed\");\n            return -1;\n        }\n        return 0;\n#endif // ANJAY_WITH_LWM2M11\n    default:\n        break;\n    }\n\n    execv(fw->next_target_path, argv_get());\n\n    demo_log(ERROR, \"execv failed (%s)\", strerror(errno));\n    delete_persistence_file(fw);\n    return -1;\n}\n\nstatic int fw_get_security_config(void *fw_,\n                                  anjay_security_config_t *out_security_config,\n                                  const char *download_uri) {\n    fw_update_logic_t *fw = (fw_update_logic_t *) fw_;\n    (void) download_uri;\n    memset(out_security_config, 0, sizeof(*out_security_config));\n    out_security_config->security_info = fw->security_info;\n    return 0;\n}\n\nstatic avs_coap_udp_tx_params_t\nfw_get_coap_tx_params(void *fw_, const char *download_uri) {\n    fw_update_logic_t *fw = (fw_update_logic_t *) fw_;\n    (void) download_uri;\n    if (fw->auto_suspend) {\n        anjay_fw_update_pull_reconnect(fw->anjay);\n    }\n    return fw->coap_tx_params;\n}\n\nstatic avs_time_duration_t\nfw_get_tcp_request_timeout(void *fw_, const char *download_uri) {\n    fw_update_logic_t *fw = (fw_update_logic_t *) fw_;\n    (void) download_uri;\n    return fw->tcp_request_timeout;\n}\n\nstatic anjay_fw_update_handlers_t FW_UPDATE_HANDLERS = {\n    .stream_open = fw_stream_open,\n    .stream_write = fw_stream_write,\n    .stream_finish = fw_stream_finish,\n    .reset = fw_reset,\n    .get_name = fw_get_name,\n    .get_version = fw_get_version,\n    .perform_upgrade = fw_perform_upgrade\n};\n\nstatic bool is_valid_result(int8_t result) {\n    switch (result) {\n    case ANJAY_FW_UPDATE_INITIAL_DOWNLOADED:\n    case ANJAY_FW_UPDATE_INITIAL_DOWNLOADING:\n    case ANJAY_FW_UPDATE_INITIAL_NEUTRAL:\n    case ANJAY_FW_UPDATE_INITIAL_SUCCESS:\n    case ANJAY_FW_UPDATE_INITIAL_INTEGRITY_FAILURE:\n    case ANJAY_FW_UPDATE_INITIAL_FAILED:\n        return true;\n    default:\n        return false;\n    }\n}\n\ntypedef struct {\n    anjay_fw_update_initial_result_t result;\n    char *uri;\n    char *download_file;\n    bool filename_administratively_set;\n    anjay_etag_t *etag;\n#if defined(ANJAY_WITH_LWM2M11) \\\n        && defined(ANJAY_WITH_MODULE_FW_UPDATE_V11_RESOURCES)\n    anjay_fw_update_severity_t severity;\n    avs_time_real_t last_state_change_time;\n    avs_time_real_t update_deadline;\n#endif /* defined(ANJAY_WITH_LWM2M11) && \\\n          defined(ANJAY_WITH_MODULE_FW_UPDATE_V11_RESOURCES) */\n} persistence_file_data_t;\n\n#if defined(AVS_COMMONS_WITH_AVS_PERSISTENCE) \\\n        && defined(AVS_COMMONS_STREAM_WITH_FILE)\nstatic persistence_file_data_t read_persistence_file(const char *path) {\n    persistence_file_data_t data;\n    memset(&data, 0, sizeof(data));\n    avs_stream_t *stream = NULL;\n    int8_t result8 = (int8_t) ANJAY_FW_UPDATE_INITIAL_NEUTRAL;\n    if ((stream = avs_stream_file_create(path, AVS_STREAM_FILE_READ))) {\n        // invalid or empty but existing file still signifies success\n        result8 = (int8_t) ANJAY_FW_UPDATE_INITIAL_SUCCESS;\n    }\n    avs_persistence_context_t ctx =\n            avs_persistence_restore_context_create(stream);\n#    if defined(ANJAY_WITH_LWM2M11) \\\n            && defined(ANJAY_WITH_MODULE_FW_UPDATE_V11_RESOURCES)\n    uint8_t severity8 = (uint8_t) ANJAY_FW_UPDATE_SEVERITY_MANDATORY;\n    int64_t last_state_change_timestamp = 0;\n    int64_t update_timestamp = 0;\n#    endif /* defined(ANJAY_WITH_LWM2M11) && \\\n              defined(ANJAY_WITH_MODULE_FW_UPDATE_V11_RESOURCES) */\n    if (!stream\n            || avs_is_err(avs_persistence_bytes(&ctx, (uint8_t *) &result8, 1))\n            || !is_valid_result(result8)\n            || avs_is_err(avs_persistence_string(&ctx, &data.uri))\n            || avs_is_err(avs_persistence_string(&ctx, &data.download_file))\n            || avs_is_err(avs_persistence_bool(\n                       &ctx, &data.filename_administratively_set))\n            || avs_is_err(restore_etag(&ctx, &data.etag))\n#    if defined(ANJAY_WITH_LWM2M11) \\\n            && defined(ANJAY_WITH_MODULE_FW_UPDATE_V11_RESOURCES)\n            || avs_is_err(avs_persistence_bytes(&ctx, &severity8, 1))\n            || avs_is_err(\n                       avs_persistence_i64(&ctx, &last_state_change_timestamp))\n            || avs_is_err(avs_persistence_i64(&ctx, &update_timestamp))\n#    endif /* defined(ANJAY_WITH_LWM2M11) && \\\n              defined(ANJAY_WITH_MODULE_FW_UPDATE_V11_RESOURCES) */\n    ) {\n        demo_log(WARNING,\n                 \"Invalid data in the firmware state persistence file\");\n        avs_free(data.uri);\n        avs_free(data.download_file);\n        memset(&data, 0, sizeof(data));\n    }\n    data.result = (anjay_fw_update_initial_result_t) result8;\n#    if defined(ANJAY_WITH_LWM2M11) \\\n            && defined(ANJAY_WITH_MODULE_FW_UPDATE_V11_RESOURCES)\n    data.severity = (anjay_fw_update_severity_t) severity8;\n    data.last_state_change_time =\n            avs_time_real_from_scalar(last_state_change_timestamp, AVS_TIME_S);\n    data.update_deadline =\n            avs_time_real_from_scalar(update_timestamp, AVS_TIME_S);\n#    endif /* defined(ANJAY_WITH_LWM2M11) && \\\n              defined(ANJAY_WITH_MODULE_FW_UPDATE_V11_RESOURCES) */\n    if (stream) {\n        avs_stream_cleanup(&stream);\n    }\n    return data;\n}\n#else  // defined(AVS_COMMONS_WITH_AVS_PERSISTENCE) &&\n       // defined(AVS_COMMONS_STREAM_WITH_FILE)\nstatic persistence_file_data_t read_persistence_file(const char *path) {\n    (void) path;\n    demo_log(WARNING, \"Persistence not compiled in\");\n    persistence_file_data_t retval;\n    memset(&retval, 0, sizeof(retval));\n    return retval;\n}\n#endif // defined(AVS_COMMONS_WITH_AVS_PERSISTENCE) &&\n       // defined(AVS_COMMONS_STREAM_WITH_FILE)\n\ntypedef struct {\n    anjay_t *anjay;\n    anjay_fw_update_result_t delayed_result;\n} set_delayed_fw_update_result_args_t;\n\nstatic void set_delayed_fw_update_result(avs_sched_t *sched, const void *arg) {\n    (void) sched;\n    const set_delayed_fw_update_result_args_t *args =\n            (const set_delayed_fw_update_result_args_t *) arg;\n\n    anjay_fw_update_set_result(args->anjay, args->delayed_result);\n}\n\nint firmware_update_install(anjay_t *anjay,\n                            fw_update_logic_t *fw,\n                            const char *persistence_file,\n                            const avs_net_security_info_t *security_info,\n                            const avs_coap_udp_tx_params_t *tx_params,\n                            avs_time_duration_t tcp_request_timeout,\n                            anjay_fw_update_result_t delayed_result,\n                            bool prefer_same_socket_downloads,\n#ifdef ANJAY_WITH_SEND\n                            bool use_lwm2m_send,\n#endif // ANJAY_WITH_SEND\n                            bool auto_suspend) {\n    int result = -1;\n\n    fw->anjay = anjay;\n    fw->persistence_file = persistence_file;\n    if (security_info) {\n        memcpy(&fw->security_info, security_info, sizeof(fw->security_info));\n        FW_UPDATE_HANDLERS.get_security_config = fw_get_security_config;\n    } else {\n        FW_UPDATE_HANDLERS.get_security_config = NULL;\n    }\n\n    if (tx_params || auto_suspend) {\n        if (tx_params) {\n            fw->coap_tx_params = *tx_params;\n        } else {\n            fw->coap_tx_params = AVS_COAP_DEFAULT_UDP_TX_PARAMS;\n        }\n        fw->auto_suspend = auto_suspend;\n        FW_UPDATE_HANDLERS.get_coap_tx_params = fw_get_coap_tx_params;\n    } else {\n        FW_UPDATE_HANDLERS.get_coap_tx_params = NULL;\n    }\n\n    if (avs_time_duration_valid(tcp_request_timeout)) {\n        fw->tcp_request_timeout = tcp_request_timeout;\n        FW_UPDATE_HANDLERS.get_tcp_request_timeout = fw_get_tcp_request_timeout;\n    } else {\n        FW_UPDATE_HANDLERS.get_tcp_request_timeout = NULL;\n    }\n\n    persistence_file_data_t data = read_persistence_file(persistence_file);\n    delete_persistence_file(fw);\n    demo_log(INFO, \"Initial firmware upgrade state result: %d\",\n             (int) data.result);\n    if ((fw->next_target_path = data.download_file)\n            && data.filename_administratively_set\n            && !(fw->administratively_set_target_path =\n                         avs_strdup(data.download_file))) {\n        demo_log(WARNING, \"Could not administratively set firmware path\");\n    }\n    anjay_fw_update_initial_state_t state = {\n        .result = data.result,\n        .persisted_uri = data.uri,\n        .resume_offset = 0,\n        .resume_etag = data.etag,\n        .prefer_same_socket_downloads = prefer_same_socket_downloads,\n#ifdef ANJAY_WITH_SEND\n        .use_lwm2m_send = use_lwm2m_send,\n#endif // ANJAY_WITH_SEND\n#if defined(ANJAY_WITH_LWM2M11) \\\n        && defined(ANJAY_WITH_MODULE_FW_UPDATE_V11_RESOURCES)\n        .persisted_severity = data.severity,\n        .persisted_last_state_change_time = data.last_state_change_time,\n        .persisted_update_deadline = data.update_deadline\n#endif /* defined(ANJAY_WITH_LWM2M11) && \\\n          defined(ANJAY_WITH_MODULE_FW_UPDATE_V11_RESOURCES) */\n    };\n\n    if (delayed_result != ANJAY_FW_UPDATE_RESULT_INITIAL) {\n        demo_log(INFO,\n                 \"delayed_result == %d; initializing Firmware Update in \"\n                 \"UPDATING state\",\n                 (int) delayed_result);\n        state.result = ANJAY_FW_UPDATE_INITIAL_UPDATING;\n\n        // Simulate FOTA process that finishes after the LwM2M client starts by\n        // changing the Update Result later at runtime\n        set_delayed_fw_update_result_args_t args = {\n            .anjay = anjay,\n            .delayed_result = delayed_result\n        };\n        if (AVS_SCHED_NOW(anjay_get_scheduler(anjay), NULL,\n                          set_delayed_fw_update_result, &args, sizeof(args))) {\n            goto exit;\n        }\n    }\n\n    if (state.result == ANJAY_FW_UPDATE_INITIAL_DOWNLOADING) {\n        long offset;\n        if (!fw->next_target_path\n                || !(fw->stream = fopen(fw->next_target_path, \"ab\"))\n                || (offset = ftell(fw->stream)) < 0) {\n            if (fw->stream) {\n                fclose(fw->stream);\n                fw->stream = NULL;\n            }\n            state.result = ANJAY_FW_UPDATE_INITIAL_NEUTRAL;\n        } else {\n            state.resume_offset = (size_t) offset;\n        }\n    }\n    if (state.result >= 0) {\n        // we're initializing in the \"Idle\" state, so the firmware file is not\n        // supposed to exist; delete it if we have it for any weird reason\n        maybe_delete_firmware_file(fw);\n    }\n\n    result = anjay_fw_update_install(anjay, &FW_UPDATE_HANDLERS, fw, &state);\n    if (!result && auto_suspend) {\n        anjay_fw_update_pull_suspend(anjay);\n    }\n\nexit:\n    avs_free(data.uri);\n    avs_free(data.etag);\n    if (result) {\n        firmware_update_destroy(fw);\n    }\n    return result;\n}\n\nvoid firmware_update_destroy(fw_update_logic_t *fw_update) {\n    assert(fw_update);\n    if (fw_update->stream) {\n        fclose(fw_update->stream);\n    }\n    avs_free(fw_update->package_uri);\n    avs_free(fw_update->administratively_set_target_path);\n    avs_free(fw_update->next_target_path);\n}\n"
  },
  {
    "path": "demo/firmware_update.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef FIRMWARE_UPDATE_H\n#define FIRMWARE_UPDATE_H\n\n#include <stddef.h>\n#include <stdio.h>\n\n#include \"objects.h\"\n#include <anjay/anjay_config.h>\n#include <anjay/fw_update.h>\n\ntypedef struct firmware_metadata {\n    uint8_t magic[8]; // \"ANJAY_FW\"\n    uint16_t header_ver;\n    uint16_t force_error_case;\n    uint32_t crc;\n    uint8_t pkg_ver_len;\n    uint8_t pkg_ver[IMG_VER_STR_MAX_LEN + 1];\n} fw_metadata_t;\n\ntypedef struct {\n    anjay_t *anjay;\n    fw_metadata_t metadata;\n    char *administratively_set_target_path;\n    char *next_target_path;\n    char *package_uri;\n    const char *persistence_file;\n    FILE *stream;\n    avs_net_security_info_t security_info;\n    avs_coap_udp_tx_params_t coap_tx_params;\n    avs_time_duration_t tcp_request_timeout;\n    bool auto_suspend;\n} fw_update_logic_t;\n\nint firmware_update_install(anjay_t *anjay,\n                            fw_update_logic_t *fw,\n                            const char *persistence_file,\n                            const avs_net_security_info_t *security_info,\n                            const avs_coap_udp_tx_params_t *tx_params,\n                            avs_time_duration_t tcp_request_timeout,\n                            anjay_fw_update_result_t delayed_result,\n                            bool prefer_same_socket_downloads,\n#ifdef ANJAY_WITH_SEND\n                            bool use_lwm2m_send,\n#endif // ANJAY_WITH_SEND\n                            bool auto_suspend);\n\nvoid firmware_update_destroy(fw_update_logic_t *fw_update);\n\nvoid firmware_update_set_package_path(fw_update_logic_t *fw_update,\n                                      const char *path);\n\n#endif /* FIRMWARE_UPDATE_H */\n"
  },
  {
    "path": "demo/lwm2m_gateway.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n#include <anjay/anjay_config.h>\n#ifdef ANJAY_WITH_LWM2M_GATEWAY\n\n#    include <inttypes.h>\n\n#    include <anjay/anjay.h>\n#    include <anjay/lwm2m_gateway.h>\n\n#    include <avsystem/commons/avs_sched.h>\n#    include <avsystem/commons/avs_time.h>\n\n#    include \"demo_utils.h\"\n#    include \"lwm2m_gateway.h\"\n#    include \"objects/gateway_end_devices/binary_app_data_container.h\"\n#    include \"objects/gateway_end_devices/push_button_object.h\"\n#    include \"objects/gateway_end_devices/temperature_object.h\"\n\nstatic struct end_dev {\n    const anjay_dm_object_def_t **push_button_object;\n    const anjay_dm_object_def_t **temperature_object;\n    const anjay_dm_object_def_t **binary_app_data_container;\n    const char *device_id;\n    // lwm2m_gateway_setup() sets the IDs equally to this array index, but\n    // ANJAY_ID_INVALID set to this field helps determining whether the device\n    // is initialized or not\n    anjay_iid_t end_dev_iid;\n    avs_sched_handle_t notify_job_handle;\n} devs[] = {\n    {\n        .device_id = \"urn:dev:001234\",\n        .end_dev_iid = ANJAY_ID_INVALID\n    },\n    {\n        .device_id = \"urn:dev:556789\",\n        .end_dev_iid = ANJAY_ID_INVALID\n    }\n};\n\nAVS_STATIC_ASSERT(AVS_ARRAY_SIZE(devs) == LWM2M_GATEWAY_END_DEVICE_COUNT,\n                  changing_dev_count_requires_setting_dev_id);\nAVS_STATIC_ASSERT(LWM2M_GATEWAY_END_DEVICE_COUNT - 1\n                          == LWM2M_GATEWAY_END_DEVICE_RANGE,\n                  end_dev_range_equal_to_dev_count_minus_one);\n\ntypedef struct {\n    anjay_t *anjay;\n    struct end_dev *dev;\n} notify_job_args_t;\n\n// Periodically notifies the library about Resource value changes\nstatic void notify_job(avs_sched_t *sched, const void *args_ptr) {\n    const notify_job_args_t *args = (const notify_job_args_t *) args_ptr;\n\n    temperature_object_update_value(args->anjay, args->dev->temperature_object);\n\n    // Schedule run of the same function after 1 second\n    AVS_SCHED_DELAYED(sched, &args->dev->notify_job_handle,\n                      avs_time_duration_from_scalar(1, AVS_TIME_S), notify_job,\n                      args, sizeof(*args));\n}\n\nstatic struct end_dev *get_dev_ptr(anjay_iid_t end_dev_iid) {\n    for (uint16_t i = 0; i < AVS_ARRAY_SIZE(devs); i++) {\n        if (devs[i].end_dev_iid == end_dev_iid) {\n            return &devs[i];\n        }\n    }\n    return NULL;\n}\n\nint lwm2m_gateway_setup_end_device(anjay_t *anjay, anjay_iid_t end_dev_iid) {\n    if (end_dev_iid >= LWM2M_GATEWAY_END_DEVICE_COUNT) {\n        // invalid iid\n        return -1;\n    }\n    struct end_dev *dev = &devs[end_dev_iid];\n\n    if (dev->end_dev_iid != ANJAY_ID_INVALID) {\n        demo_log(ERROR, \"End Device id = %\" PRIu16 \" already registered!\",\n                 end_dev_iid);\n        return -1;\n    }\n\n    if (anjay_lwm2m_gateway_register_device(anjay, dev->device_id,\n                                            &dev->end_dev_iid)) {\n        demo_log(ERROR, \"Failed to add End Device id = %\" PRIu16, end_dev_iid);\n        return -1;\n    }\n\n    dev->push_button_object = push_button_object_create(dev->end_dev_iid);\n    if (!dev->push_button_object\n            || anjay_lwm2m_gateway_register_object(anjay, dev->end_dev_iid,\n                                                   dev->push_button_object)) {\n        demo_log(ERROR, \"Failed to create Push Button Object %\" PRIu16,\n                 end_dev_iid);\n        return -1;\n    }\n\n    dev->temperature_object = temperature_object_create(dev->end_dev_iid);\n    if (!dev->temperature_object\n            || anjay_lwm2m_gateway_register_object(anjay, dev->end_dev_iid,\n                                                   dev->temperature_object)) {\n        demo_log(ERROR, \"Failed to create Temperature Object %\" PRIu16,\n                 end_dev_iid);\n        return -1;\n    }\n\n    dev->binary_app_data_container =\n            gw_binary_app_data_container_object_create(dev->end_dev_iid);\n    if (!dev->binary_app_data_container\n            || anjay_lwm2m_gateway_register_object(\n                       anjay, dev->end_dev_iid,\n                       dev->binary_app_data_container)) {\n        demo_log(ERROR,\n                 \"Failed to create Binary Data Container Object %\" PRIu16,\n                 end_dev_iid);\n        return -1;\n    }\n\n    notify_job(anjay_get_scheduler(anjay),\n               &(const notify_job_args_t) {\n                   .anjay = anjay,\n                   .dev = dev\n               });\n    return 0;\n}\n\nvoid lwm2m_gateway_cleanup_end_device(anjay_t *anjay, anjay_iid_t end_dev_iid) {\n    if (end_dev_iid >= LWM2M_GATEWAY_END_DEVICE_COUNT) {\n        // invalid iid\n        return;\n    }\n    struct end_dev *dev = &devs[end_dev_iid];\n\n    if (dev->end_dev_iid == ANJAY_ID_INVALID) {\n        // device already deregistered\n        return;\n    }\n\n    if (anjay_lwm2m_gateway_unregister_object(anjay, dev->end_dev_iid,\n                                              dev->push_button_object)) {\n        AVS_UNREACHABLE(\"Failed to unregister Time Object\");\n    }\n\n    if (anjay_lwm2m_gateway_unregister_object(anjay, dev->end_dev_iid,\n                                              dev->temperature_object)) {\n        AVS_UNREACHABLE(\"Failed to unregister Temperature Object\");\n    }\n\n    if (anjay_lwm2m_gateway_unregister_object(anjay, dev->end_dev_iid,\n                                              dev->binary_app_data_container)) {\n        AVS_UNREACHABLE(\n                \"Failed to unregister Binary App Data Container Object\");\n    }\n\n    if (anjay_lwm2m_gateway_deregister_device(anjay, dev->end_dev_iid)) {\n        AVS_UNREACHABLE(\"Failed to deregister End Device\");\n    }\n\n    avs_sched_del(&dev->notify_job_handle);\n    push_button_object_release(dev->push_button_object);\n    temperature_object_release(dev->temperature_object);\n    gw_binary_app_data_container_object_release(dev->binary_app_data_container);\n    dev->end_dev_iid = ANJAY_ID_INVALID;\n}\n\nint lwm2m_gateway_setup(anjay_t *anjay) {\n    if (anjay_lwm2m_gateway_install(anjay)) {\n        demo_log(ERROR, \"Failed to add /25 Gateway Object\");\n        return -1;\n    }\n\n    for (anjay_iid_t end_dev_iid = 0; end_dev_iid < AVS_ARRAY_SIZE(devs);\n         ++end_dev_iid) {\n        if (lwm2m_gateway_setup_end_device(anjay, end_dev_iid)) {\n            demo_log(ERROR, \"Failed to setup End Device id = %\" PRIu16,\n                     end_dev_iid);\n            return -1;\n        }\n    }\n\n    return 0;\n}\n\nvoid lwm2m_gateway_cleanup(anjay_t *anjay) {\n    for (anjay_iid_t end_dev_iid = 0; end_dev_iid <= AVS_ARRAY_SIZE(devs);\n         ++end_dev_iid) {\n        lwm2m_gateway_cleanup_end_device(anjay, end_dev_iid);\n    }\n}\n\nvoid lwm2m_gateway_press_button_end_device(anjay_t *anjay,\n                                           anjay_iid_t end_dev_iid) {\n    struct end_dev *dev = get_dev_ptr(end_dev_iid);\n    if (!dev) {\n        return;\n    }\n    const anjay_dm_object_def_t **obj = dev->push_button_object;\n\n    push_button_press(anjay, obj);\n}\n\nvoid lwm2m_gateway_release_button_end_device(anjay_t *anjay,\n                                             anjay_iid_t end_dev_iid) {\n    struct end_dev *dev = get_dev_ptr(end_dev_iid);\n    if (!dev) {\n        return;\n    }\n    const anjay_dm_object_def_t **obj = dev->push_button_object;\n\n    push_button_release(anjay, obj);\n}\n\nvoid lwm2m_gateway_binary_app_data_container_write(anjay_t *anjay,\n                                                   anjay_iid_t end_dev_iid,\n                                                   anjay_iid_t iid,\n                                                   anjay_riid_t riid,\n                                                   const char *value) {\n    struct end_dev *dev = get_dev_ptr(end_dev_iid);\n    if (!dev) {\n        return;\n    }\n    const anjay_dm_object_def_t **obj = dev->binary_app_data_container;\n\n    gw_binary_app_data_container_write(anjay, obj, iid, riid, value);\n}\n\n#endif // ANJAY_WITH_LWM2M_GATEWAY\n"
  },
  {
    "path": "demo/lwm2m_gateway.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef LWM2M_GATEWAY_H\n#define LWM2M_GATEWAY_H\n\n#include <anjay/anjay_config.h>\n\n#ifdef ANJAY_WITH_LWM2M_GATEWAY\n\n#    include <anjay/anjay.h>\n\n#    define LWM2M_GATEWAY_END_DEVICE_COUNT 2\n// keep it equal to LWM2M_GATEWAY_END_DEVICE_COUNT - 1\n#    define LWM2M_GATEWAY_END_DEVICE_RANGE 1\n\nint lwm2m_gateway_setup(anjay_t *anjay);\nvoid lwm2m_gateway_cleanup(anjay_t *anjay);\n\nint lwm2m_gateway_setup_end_device(anjay_t *anjay, anjay_iid_t iid);\nvoid lwm2m_gateway_cleanup_end_device(anjay_t *anjay, anjay_iid_t iid);\n\nvoid lwm2m_gateway_press_button_end_device(anjay_t *anjay, anjay_iid_t iid);\nvoid lwm2m_gateway_release_button_end_device(anjay_t *anjay, anjay_iid_t iid);\n\nvoid lwm2m_gateway_binary_app_data_container_write(anjay_t *anjay,\n                                                   uint16_t dev_no,\n                                                   anjay_iid_t iid,\n                                                   anjay_riid_t riid,\n                                                   const char *value);\n\n#endif // ANJAY_WITH_LWM2M_GATEWAY\n\n#endif // LWM2M_GATEWAY_H\n"
  },
  {
    "path": "demo/net_traffic_interceptor.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <avsystem/commons/avs_commons_config.h>\n\n#ifdef WITH_DEMO_TRAFFIC_INTERCEPTOR\n\n#    include <errno.h>\n#    include <netinet/in.h>\n#    include <sys/socket.h>\n#    include <sys/un.h>\n#    include <unistd.h>\n\n#    include <avsystem/coap/udp.h>\n#    include <avsystem/commons/avs_net.h>\n#    include <avsystem/commons/avs_socket_v_table.h>\n#    include <avsystem/commons/avs_stream_membuf.h>\n#    include <avsystem/commons/avs_time.h>\n#    include <avsystem/commons/avs_utils.h>\n\n#    include \"demo_utils.h\"\n#    include \"net_traffic_interceptor.h\"\n\nstatic int g_interceptor_sock;\nstatic const char *g_endpoint_name;\n\nstatic char *\ndirection_to_string(avs_net_traffic_interceptor_direction_t direction) {\n    switch (direction) {\n    case AVS_NET_TRAFFIC_INTERCEPTOR_INCOMING:\n        return \"incoming\";\n    case AVS_NET_TRAFFIC_INTERCEPTOR_OUTGOING:\n        return \"outgoing\";\n    default:\n        AVS_UNREACHABLE();\n    }\n}\n\nstatic char *transport_to_string(avs_net_socket_type_t type) {\n    switch (type) {\n    case AVS_NET_TCP_SOCKET:\n        return \"TCP\";\n    case AVS_NET_UDP_SOCKET:\n        return \"UDP\";\n    case AVS_NET_SSL_SOCKET:\n        return \"SSL\";\n    case AVS_NET_DTLS_SOCKET:\n        return \"DTLS\";\n    default:\n        AVS_UNREACHABLE();\n    }\n}\n\nvoid _avs_net_traffic_interceptor(\n        avs_net_socket_t *socket,\n        const void *data,\n        size_t data_length,\n        avs_net_socket_type_t type,\n        avs_net_traffic_interceptor_direction_t direction) {\n    if (g_interceptor_sock < 0) {\n        return;\n    }\n    char *buffer = NULL;\n    avs_stream_t *membuf = avs_stream_membuf_create();\n    if (!membuf) {\n        demo_log(ERROR, \"out of memory\");\n        return;\n    }\n\n    char *remote_host = (char *) malloc(INET6_ADDRSTRLEN);\n    if (!remote_host) {\n        demo_log(ERROR, \"Out of memory\");\n        goto clean_up;\n    }\n    avs_net_socket_get_remote_host(socket, remote_host, INET6_ADDRSTRLEN);\n    char remote_port[sizeof(uint16_t) * 2 + 1];\n    avs_net_socket_get_remote_port(socket, remote_port, sizeof(remote_port));\n    long timestamp;\n    avs_time_real_to_scalar(&timestamp, AVS_TIME_S, avs_time_real_now());\n    if (avs_is_err(avs_stream_write_f(membuf,\n                                      \"{\\n\"\n                                      \"  \\\"endpoint_name\\\": \\\"%s\\\",\\n\"\n                                      \"  \\\"remote_host\\\": \\\"%s\\\",\\n\"\n                                      \"  \\\"remote_port\\\": \\\"%s\\\",\\n\"\n                                      \"  \\\"direction\\\": \\\"%s\\\",\\n\"\n                                      \"  \\\"timestamp\\\": %ld,\\n\"\n                                      \"  \\\"transport\\\": \\\"%s\\\",\\n\"\n                                      \"  \\\"transport_payload\\\": \\\"\",\n                                      g_endpoint_name, remote_host, remote_port,\n                                      direction_to_string(direction), timestamp,\n                                      transport_to_string(type)))) {\n        demo_log(ERROR, \"avs_stream_write_f failed\");\n        goto clean_up;\n    }\n\n    size_t hexlified_len;\n    buffer = (char *) malloc(data_length * 2 + 1);\n    if (avs_hexlify(buffer, data_length * 2 + 1, &hexlified_len, data,\n                    data_length)\n            || hexlified_len != data_length) {\n        demo_log(ERROR, \"avs_hexlify failed\");\n        goto clean_up;\n    }\n    hexlified_len *= 2;\n    if (avs_is_err(avs_stream_write_f(membuf, \"%s\\\"\\n}\", buffer))) {\n        demo_log(ERROR, \"avs_stream_write_f failed\");\n        goto clean_up;\n    }\n    void *buf_ptr;\n    size_t buf_len;\n    if (avs_is_err(\n                avs_stream_membuf_take_ownership(membuf, &buf_ptr, &buf_len))) {\n        demo_log(ERROR, \"avs_stream_membuf_take_ownership failed\");\n        goto clean_up;\n    }\n    send(g_interceptor_sock, buf_ptr, buf_len, 0);\n    free(buf_ptr);\nclean_up:\n    if (avs_is_err(avs_stream_cleanup(&membuf))) {\n        demo_log(ERROR, \"avs_stream_cleanup failed\");\n    }\n    free(buffer);\n    free(remote_host);\n}\n\nint interceptor_init(const char *socket_path, const char *endpoint_name) {\n    int fd = socket(AF_UNIX, SOCK_SEQPACKET, 0);\n    if (fd < 0) {\n        return -1;\n    }\n    struct sockaddr_un addr;\n    memset(&addr, 0, sizeof(addr));\n    addr.sun_family = AF_UNIX;\n    strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path) - 1);\n    errno = 0;\n    int res =\n            connect(fd, (struct sockaddr *) &addr, sizeof(struct sockaddr_un));\n    if (res < 0) {\n        demo_log(ERROR, \"Traffic interceptor failed connecting to: %s\",\n                 socket_path);\n        close(fd);\n        return -1;\n    }\n    g_interceptor_sock = fd;\n    g_endpoint_name = endpoint_name;\n    return 0;\n}\n\nint interceptor_deinit(void) {\n    if (g_interceptor_sock >= 0) {\n        const int fd = g_interceptor_sock;\n        g_interceptor_sock = -1;\n        return close(fd);\n    }\n    g_interceptor_sock = -1;\n    return 0;\n}\n\n#endif // WITH_DEMO_TRAFFIC_INTERCEPTOR\n"
  },
  {
    "path": "demo/net_traffic_interceptor.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef NET_TRAFFIC_INTERCEPTOR_H\n#define NET_TRAFFIC_INTERCEPTOR_H\n\n#ifdef WITH_DEMO_TRAFFIC_INTERCEPTOR\nint interceptor_init(const char *socket_path, const char *endpoint_name);\nint interceptor_deinit(void);\n#endif // WITH_DEMO_TRAFFIC_INTERCEPTOR\n\n#endif // NET_TRAFFIC_INTERCEPTOR_H\n"
  },
  {
    "path": "demo/objects/apn_conn_profile.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include \"../demo_utils.h\"\n#include \"../objects.h\"\n\n#include <assert.h>\n#include <stdio.h>\n\n#define APNCP_RES_PROFILE_NAME 0                           // string\n#define APNCP_RES_APN 1                                    // string\n#define APNCP_RES_AUTO_SELECT_APN_BY_DEVICE 2              // bool\n#define APNCP_RES_ENABLE_STATUS 3                          // bool\n#define APNCP_RES_AUTHENTICATION_TYPE 4                    // int\n#define APNCP_RES_USER_NAME 5                              // string\n#define APNCP_RES_SECRET 6                                 // string\n#define APNCP_RES_RECONNECT_SCHEDULE 7                     // string\n#define APNCP_RES_VALIDITY 8                               // string\n#define APNCP_RES_CONNECTION_ESTABLISHMENT_TIME 9          // time\n#define APNCP_RES_CONNECTION_ESTABLISHMENT_RESULT 10       // int\n#define APNCP_RES_CONNECTION_ESTABLISHMENT_REJECT_CAUSE 11 // int[0:111]\n#define APNCP_RES_CONNECTION_END_TIME 12                   // time\n#define APNCP_RES_TOTAL_BYTES_SENT 13                      // int\n#define APNCP_RES_TOTAL_BYTES_RECEIVED 14                  // int\n#define APNCP_RES_IP_ADDRESS 15                            // string\n#define APNCP_RES_PREFIX_LENGTH 16                         // string\n#define APNCP_RES_SUBNET_MASK 17                           // string\n#define APNCP_RES_GATEWAY 18                               // string\n#define APNCP_RES_PRIMARY_DNS_ADDRESS 19                   // string\n#define APNCP_RES_SECONDARY_DNS_ADDRESS 20                 // string\n#define APNCP_RES_QCI 21                                   // int[1:9]\n#define APNCP_RES_VENDOR_SPECIFIC_EXTENSIONS 22            // objlnk\n\ntypedef enum {\n    AUTH_PAP = 0,\n    AUTH_CHAP,\n    AUTH_PAP_OR_CHAP,\n    AUTH_NONE,\n\n    AUTH_END_\n} apn_auth_type_t;\n\ntypedef struct {\n    anjay_iid_t iid;\n\n    bool has_profile_name;\n    bool has_auth_type;\n    char profile_name[256];\n    apn_auth_type_t auth_type;\n    bool enabled;\n} apn_conn_profile_t;\n\ntypedef struct {\n    const anjay_dm_object_def_t *def;\n    AVS_LIST(apn_conn_profile_t) instances;\n    AVS_LIST(apn_conn_profile_t) saved_instances;\n} apn_conn_profile_repr_t;\n\nstatic inline apn_conn_profile_repr_t *\nget_apncp(const anjay_dm_object_def_t *const *obj_ptr) {\n    assert(obj_ptr);\n    return AVS_CONTAINER_OF(obj_ptr, apn_conn_profile_repr_t, def);\n}\n\nstatic apn_conn_profile_t *find_instance(const apn_conn_profile_repr_t *repr,\n                                         anjay_iid_t iid) {\n    AVS_LIST(apn_conn_profile_t) it;\n    AVS_LIST_FOREACH(it, repr->instances) {\n        if (it->iid == iid) {\n            return it;\n        }\n    }\n\n    return NULL;\n}\n\nstatic int apncp_list_instances(anjay_t *anjay,\n                                const anjay_dm_object_def_t *const *obj_ptr,\n                                anjay_dm_list_ctx_t *ctx) {\n    (void) anjay;\n\n    AVS_LIST(apn_conn_profile_t) it;\n    AVS_LIST_FOREACH(it, get_apncp(obj_ptr)->instances) {\n        anjay_dm_emit(ctx, it->iid);\n    }\n    return 0;\n}\n\nstatic int apncp_instance_create(anjay_t *anjay,\n                                 const anjay_dm_object_def_t *const *obj_ptr,\n                                 anjay_iid_t iid) {\n    (void) anjay;\n    apn_conn_profile_repr_t *repr = get_apncp(obj_ptr);\n\n    AVS_LIST(apn_conn_profile_t) created =\n            AVS_LIST_NEW_ELEMENT(apn_conn_profile_t);\n    if (!created) {\n        return ANJAY_ERR_INTERNAL;\n    }\n\n    created->iid = iid;\n\n    AVS_LIST(apn_conn_profile_t) *ptr;\n    AVS_LIST_FOREACH_PTR(ptr, &repr->instances) {\n        if ((*ptr)->iid > created->iid) {\n            break;\n        }\n    }\n\n    AVS_LIST_INSERT(ptr, created);\n    return 0;\n}\n\nstatic int apncp_instance_remove(anjay_t *anjay,\n                                 const anjay_dm_object_def_t *const *obj_ptr,\n                                 anjay_iid_t iid) {\n    (void) anjay;\n    apn_conn_profile_repr_t *repr = get_apncp(obj_ptr);\n\n    AVS_LIST(apn_conn_profile_t) *it;\n    AVS_LIST_FOREACH_PTR(it, &repr->instances) {\n        if ((*it)->iid == iid) {\n            AVS_LIST_DELETE(it);\n            return 0;\n        }\n    }\n\n    assert(0);\n    return ANJAY_ERR_NOT_FOUND;\n}\n\nstatic int apncp_list_resources(anjay_t *anjay,\n                                const anjay_dm_object_def_t *const *obj_ptr,\n                                anjay_iid_t iid,\n                                anjay_dm_resource_list_ctx_t *ctx) {\n    (void) anjay;\n    (void) obj_ptr;\n    (void) iid;\n\n    anjay_dm_emit_res(ctx, APNCP_RES_PROFILE_NAME, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, APNCP_RES_ENABLE_STATUS, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, APNCP_RES_AUTHENTICATION_TYPE, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_PRESENT);\n    return 0;\n}\n\nstatic int apncp_resource_read(anjay_t *anjay,\n                               const anjay_dm_object_def_t *const *obj_ptr,\n                               anjay_iid_t iid,\n                               anjay_rid_t rid,\n                               anjay_riid_t riid,\n                               anjay_output_ctx_t *ctx) {\n    (void) anjay;\n    (void) obj_ptr;\n    (void) riid;\n    assert(riid == ANJAY_ID_INVALID);\n\n    apn_conn_profile_t *inst = find_instance(get_apncp(obj_ptr), iid);\n    assert(inst);\n\n    switch (rid) {\n    case APNCP_RES_PROFILE_NAME:\n        return anjay_ret_string(ctx, inst->profile_name);\n    case APNCP_RES_ENABLE_STATUS:\n        return anjay_ret_bool(ctx, inst->enabled);\n    case APNCP_RES_AUTHENTICATION_TYPE:\n        return anjay_ret_i32(ctx, (int32_t) inst->auth_type);\n    default:\n        AVS_UNREACHABLE(\"Read called on unknown resource\");\n        return ANJAY_ERR_NOT_FOUND;\n    }\n}\n\nstatic int apncp_resource_write(anjay_t *anjay,\n                                const anjay_dm_object_def_t *const *obj_ptr,\n                                anjay_iid_t iid,\n                                anjay_rid_t rid,\n                                anjay_riid_t riid,\n                                anjay_input_ctx_t *ctx) {\n    (void) anjay;\n    (void) obj_ptr;\n    (void) riid;\n    assert(riid == ANJAY_ID_INVALID);\n\n    apn_conn_profile_t *inst = find_instance(get_apncp(obj_ptr), iid);\n    assert(inst);\n\n    switch (rid) {\n    case APNCP_RES_PROFILE_NAME: {\n        char buf[sizeof(inst->profile_name)];\n        if (anjay_get_string(ctx, buf, sizeof(buf)) < 0) {\n            return ANJAY_ERR_INTERNAL;\n        }\n\n        int result = snprintf(inst->profile_name, sizeof(inst->profile_name),\n                              \"%s\", buf);\n        if (result < 0 || (size_t) result >= sizeof(inst->profile_name)) {\n            return ANJAY_ERR_INTERNAL;\n        }\n        inst->has_profile_name = true;\n        return 0;\n    }\n    case APNCP_RES_ENABLE_STATUS:\n        return anjay_get_bool(ctx, &inst->enabled);\n    case APNCP_RES_AUTHENTICATION_TYPE: {\n        int new_val = 0;\n        if (anjay_get_i32(ctx, &new_val)) {\n            return ANJAY_ERR_INTERNAL;\n        }\n\n        if (new_val < 0 || new_val >= AUTH_END_) {\n            return ANJAY_ERR_BAD_REQUEST;\n        }\n\n        inst->auth_type = (apn_auth_type_t) new_val;\n        inst->has_auth_type = true;\n        return 0;\n    }\n    default:\n        AVS_UNREACHABLE(\"Write called on unknown resource\");\n        return ANJAY_ERR_NOT_FOUND;\n    }\n}\n\nstatic int\napncp_transaction_begin(anjay_t *anjay,\n                        const anjay_dm_object_def_t *const *obj_ptr) {\n    (void) anjay;\n    apn_conn_profile_repr_t *repr = get_apncp(obj_ptr);\n    if (!repr->instances) {\n        return 0;\n    }\n    repr->saved_instances = AVS_LIST_SIMPLE_CLONE(repr->instances);\n    if (!repr->saved_instances) {\n        demo_log(ERROR, \"cannot clone APN Connection Profile repr\");\n        return ANJAY_ERR_INTERNAL;\n    }\n    return 0;\n}\n\nstatic int\napncp_transaction_validate(anjay_t *anjay,\n                           const anjay_dm_object_def_t *const *obj_ptr) {\n    (void) anjay;\n    AVS_LIST(apn_conn_profile_t) it;\n    AVS_LIST_FOREACH(it, get_apncp(obj_ptr)->instances) {\n        if (!it->has_profile_name || !it->has_auth_type) {\n            return ANJAY_ERR_BAD_REQUEST;\n        }\n    }\n    return 0;\n}\n\nstatic int\napncp_transaction_commit(anjay_t *anjay,\n                         const anjay_dm_object_def_t *const *obj_ptr) {\n    (void) anjay;\n    AVS_LIST_CLEAR(&get_apncp(obj_ptr)->saved_instances);\n    return 0;\n}\n\nstatic int\napncp_transaction_rollback(anjay_t *anjay,\n                           const anjay_dm_object_def_t *const *obj_ptr) {\n    (void) anjay;\n    apn_conn_profile_repr_t *repr = get_apncp(obj_ptr);\n    AVS_LIST_CLEAR(&repr->instances);\n    repr->instances = repr->saved_instances;\n    repr->saved_instances = NULL;\n    return 0;\n}\n\nstatic int apncp_instance_reset(anjay_t *anjay,\n                                const anjay_dm_object_def_t *const *obj_ptr,\n                                anjay_iid_t iid) {\n    (void) anjay;\n    apn_conn_profile_t *inst = find_instance(get_apncp(obj_ptr), iid);\n    AVS_ASSERT(inst, \"could not find instance\");\n    inst->has_auth_type = false;\n    inst->has_profile_name = false;\n    inst->enabled = false;\n    return 0;\n}\n\nstatic const anjay_dm_object_def_t apn_conn_profile = {\n    .oid = DEMO_OID_APN_CONN_PROFILE,\n    .handlers = {\n        .list_instances = apncp_list_instances,\n        .instance_create = apncp_instance_create,\n        .instance_remove = apncp_instance_remove,\n        .instance_reset = apncp_instance_reset,\n        .list_resources = apncp_list_resources,\n        .resource_read = apncp_resource_read,\n        .resource_write = apncp_resource_write,\n        .transaction_begin = apncp_transaction_begin,\n        .transaction_validate = apncp_transaction_validate,\n        .transaction_commit = apncp_transaction_commit,\n        .transaction_rollback = apncp_transaction_rollback\n    }\n};\n\nconst anjay_dm_object_def_t **apn_conn_profile_object_create(void) {\n    apn_conn_profile_repr_t *repr = (apn_conn_profile_repr_t *) avs_calloc(\n            1, sizeof(apn_conn_profile_repr_t));\n    if (!repr) {\n        return NULL;\n    }\n\n    repr->def = &apn_conn_profile;\n\n    return &repr->def;\n}\n\nint apn_conn_profile_get_instances(const anjay_dm_object_def_t **def,\n                                   AVS_LIST(anjay_iid_t) *out) {\n    apn_conn_profile_repr_t *apncp = get_apncp(def);\n    assert(!*out);\n    AVS_LIST(apn_conn_profile_t) it;\n    AVS_LIST_FOREACH(it, apncp->instances) {\n        if (!(*out = AVS_LIST_NEW_ELEMENT(anjay_iid_t))) {\n            demo_log(ERROR, \"out of memory\");\n            return -1;\n        }\n        **out = it->iid;\n        AVS_LIST_ADVANCE_PTR(&out);\n    }\n    return 0;\n}\n\nvoid apn_conn_profile_object_release(const anjay_dm_object_def_t **def) {\n    if (def) {\n        apn_conn_profile_repr_t *apncp = get_apncp(def);\n        AVS_LIST_CLEAR(&apncp->instances);\n        AVS_LIST_CLEAR(&apncp->saved_instances);\n        avs_free(apncp);\n    }\n}\n\nAVS_LIST(anjay_iid_t)\napn_conn_profile_list_activated(const anjay_dm_object_def_t **def) {\n    AVS_LIST(anjay_iid_t) iids = NULL;\n    AVS_LIST(anjay_iid_t) *tail = &iids;\n\n    AVS_LIST(apn_conn_profile_t) it;\n    AVS_LIST_FOREACH(it, get_apncp(def)->instances) {\n        if (!it->enabled) {\n            continue;\n        }\n\n        AVS_LIST(anjay_iid_t) elem = AVS_LIST_NEW_ELEMENT(anjay_iid_t);\n        if (!elem) {\n            AVS_LIST_CLEAR(&iids);\n            return NULL;\n        }\n\n        *elem = it->iid;\n        AVS_LIST_INSERT(tail, elem);\n        tail = AVS_LIST_NEXT_PTR(tail);\n    }\n\n    return iids;\n}\n"
  },
  {
    "path": "demo/objects/binary_app_data_container.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include \"../objects.h\"\n\n#include <assert.h>\n#include <inttypes.h>\n#include <stdbool.h>\n\n#include <anjay/anjay.h>\n#include <avsystem/commons/avs_defs.h>\n#include <avsystem/commons/avs_list.h>\n#include <avsystem/commons/avs_memory.h>\n\n/**\n * Data: RW, Multiple, Mandatory\n * type: opaque, range: N/A, unit: N/A\n * Indicates the application data content.\n */\n#define RID_DATA 0\n\n/**\n * Data Priority: RW, Single, Optional\n * type: integer, range: 1 bytes, unit: N/A\n * Indicates the Application data priority: 0:Immediate 1:BestEffort\n * 2:Latest 3-100: Reserved for future use. 101-254: Proprietary mode.\n */\n#define RID_DATA_PRIORITY 1\n\n/**\n * Data Creation Time: RW, Single, Optional\n * type: time, range: N/A, unit: N/A\n * Indicates the Data instance creation timestamp.\n */\n#define RID_DATA_CREATION_TIME 2\n\n/**\n * Data Description: RW, Single, Optional\n * type: string, range: 32 bytes, unit: N/A\n * Indicates the data description. e.g. \"meter reading\".\n */\n#define RID_DATA_DESCRIPTION 3\n\n/**\n * Data Format: RW, Single, Optional\n * type: string, range: 32 bytes, unit: N/A\n * Indicates the format of the Application Data. e.g. YG-Meter-Water-\n * Reading UTF8-string\n */\n#define RID_DATA_FORMAT 4\n\n/**\n * App ID: RW, Single, Optional\n * type: integer, range: 2 bytes, unit: N/A\n * Indicates the destination Application ID.\n */\n#define RID_APP_ID 5\n\n#define MAX_BINARY_DATA_SIZE 1024\n\ntypedef struct {\n    anjay_riid_t riid;\n    uint8_t data[MAX_BINARY_DATA_SIZE];\n    size_t data_length;\n} data_resource_instance_t;\n\ntypedef struct binary_app_data_container_instance_struct {\n    anjay_iid_t iid;\n    AVS_LIST(data_resource_instance_t) data_list;\n} binary_app_data_container_instance_t;\n\ntypedef struct binary_app_data_container_struct {\n    const anjay_dm_object_def_t *def;\n    AVS_LIST(binary_app_data_container_instance_t) instances;\n    AVS_LIST(binary_app_data_container_instance_t) saved_instances;\n} binary_app_data_container_t;\n\nstatic inline binary_app_data_container_t *\nget_obj(const anjay_dm_object_def_t *const *obj_ptr) {\n    assert(obj_ptr);\n    return AVS_CONTAINER_OF(obj_ptr, binary_app_data_container_t, def);\n}\n\nstatic binary_app_data_container_instance_t *\nfind_instance(const binary_app_data_container_t *obj, anjay_iid_t iid) {\n    AVS_LIST(binary_app_data_container_instance_t) it;\n    AVS_LIST_FOREACH(it, obj->instances) {\n        if (it->iid == iid) {\n            return it;\n        } else if (it->iid > iid) {\n            break;\n        }\n    }\n\n    return NULL;\n}\n\nstatic int list_instances(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr,\n                          anjay_dm_list_ctx_t *ctx) {\n    (void) anjay;\n\n    AVS_LIST(binary_app_data_container_instance_t) it;\n    AVS_LIST_FOREACH(it, get_obj(obj_ptr)->instances) {\n        anjay_dm_emit(ctx, it->iid);\n    }\n\n    return 0;\n}\n\nstatic int init_instance(binary_app_data_container_instance_t *inst,\n                         anjay_iid_t iid) {\n    assert(iid != ANJAY_ID_INVALID);\n\n    inst->iid = iid;\n    return 0;\n}\n\nstatic void release_instance(binary_app_data_container_instance_t *inst) {\n    AVS_LIST_CLEAR(&inst->data_list);\n}\n\nstatic int instance_create(anjay_t *anjay,\n                           const anjay_dm_object_def_t *const *obj_ptr,\n                           anjay_iid_t iid) {\n    (void) anjay;\n    binary_app_data_container_t *obj = get_obj(obj_ptr);\n\n    AVS_LIST(binary_app_data_container_instance_t) created =\n            AVS_LIST_NEW_ELEMENT(binary_app_data_container_instance_t);\n    if (!created) {\n        return ANJAY_ERR_INTERNAL;\n    }\n\n    int result = init_instance(created, iid);\n    if (result) {\n        AVS_LIST_CLEAR(&created);\n        return result;\n    }\n\n    AVS_LIST(binary_app_data_container_instance_t) *ptr;\n    AVS_LIST_FOREACH_PTR(ptr, &obj->instances) {\n        if ((*ptr)->iid > created->iid) {\n            break;\n        }\n    }\n\n    AVS_LIST_INSERT(ptr, created);\n    return 0;\n}\n\nstatic int instance_remove(anjay_t *anjay,\n                           const anjay_dm_object_def_t *const *obj_ptr,\n                           anjay_iid_t iid) {\n    (void) anjay;\n    binary_app_data_container_t *obj = get_obj(obj_ptr);\n\n    AVS_LIST(binary_app_data_container_instance_t) *it;\n    AVS_LIST_FOREACH_PTR(it, &obj->instances) {\n        if ((*it)->iid == iid) {\n            release_instance(*it);\n            AVS_LIST_DELETE(it);\n            return 0;\n        } else if ((*it)->iid > iid) {\n            break;\n        }\n    }\n\n    assert(0);\n    return ANJAY_ERR_NOT_FOUND;\n}\n\nstatic int instance_reset(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr,\n                          anjay_iid_t iid) {\n    (void) anjay;\n\n    binary_app_data_container_t *obj = get_obj(obj_ptr);\n    binary_app_data_container_instance_t *inst = find_instance(obj, iid);\n    assert(inst);\n\n    AVS_LIST_CLEAR(&inst->data_list);\n    return 0;\n}\n\nstatic int list_resources(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr,\n                          anjay_iid_t iid,\n                          anjay_dm_resource_list_ctx_t *ctx) {\n    (void) anjay;\n    (void) obj_ptr;\n    (void) iid;\n\n    anjay_dm_emit_res(ctx, RID_DATA, ANJAY_DM_RES_RWM, ANJAY_DM_RES_PRESENT);\n    return 0;\n}\n\nstatic int resource_read(anjay_t *anjay,\n                         const anjay_dm_object_def_t *const *obj_ptr,\n                         anjay_iid_t iid,\n                         anjay_rid_t rid,\n                         anjay_riid_t riid,\n                         anjay_output_ctx_t *ctx) {\n    (void) anjay;\n\n    binary_app_data_container_t *obj = get_obj(obj_ptr);\n    binary_app_data_container_instance_t *inst = find_instance(obj, iid);\n    assert(inst);\n\n    switch (rid) {\n    case RID_DATA: {\n        data_resource_instance_t *it;\n        AVS_LIST_FOREACH(it, inst->data_list) {\n            if (it->riid == riid) {\n                break;\n            }\n        }\n        if (!it) {\n            return ANJAY_ERR_NOT_FOUND;\n        }\n        return anjay_ret_bytes(ctx, it->data, it->data_length);\n    }\n\n    default:\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\nstatic AVS_LIST(data_resource_instance_t) *\nresource_instance_create(anjay_t *anjay,\n                         binary_app_data_container_instance_t *inst,\n                         anjay_riid_t riid) {\n    (void) anjay;\n\n    AVS_LIST(data_resource_instance_t) created =\n            AVS_LIST_NEW_ELEMENT(data_resource_instance_t);\n    if (!created) {\n        return NULL;\n    }\n\n    created->riid = riid;\n\n    AVS_LIST(data_resource_instance_t) *ptr;\n    AVS_LIST_FOREACH_PTR(ptr, &inst->data_list) {\n        if ((*ptr)->riid > created->riid) {\n            break;\n        }\n    }\n\n    AVS_LIST_INSERT(ptr, created);\n    return ptr;\n}\n\nstatic int resource_write(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr,\n                          anjay_iid_t iid,\n                          anjay_rid_t rid,\n                          anjay_riid_t riid,\n                          anjay_input_ctx_t *ctx) {\n    (void) anjay;\n\n    binary_app_data_container_t *obj = get_obj(obj_ptr);\n    binary_app_data_container_instance_t *inst = find_instance(obj, iid);\n    assert(inst);\n\n    switch (rid) {\n    case RID_DATA: {\n        AVS_LIST(data_resource_instance_t) *it;\n        AVS_LIST_FOREACH_PTR(it, &inst->data_list) {\n            if ((*it)->riid >= riid) {\n                break;\n            }\n        }\n        bool created = false;\n        if (!(*it) || (*it)->riid != riid) {\n            if (!(it = resource_instance_create(anjay, inst, riid))) {\n                return ANJAY_ERR_INTERNAL;\n            }\n            created = true;\n        }\n\n        bool finished;\n        int retval = anjay_get_bytes(ctx, &(*it)->data_length, &finished,\n                                     (*it)->data, sizeof((*it)->data));\n\n        if (!retval && !finished) {\n            retval = ANJAY_ERR_INTERNAL;\n        }\n\n        if (retval) {\n            if (created) {\n                AVS_LIST_DELETE(it);\n            } else {\n                (*it)->data_length = 0;\n            }\n        }\n        return retval;\n    }\n\n    default:\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\nstatic int resource_reset(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr,\n                          anjay_iid_t iid,\n                          anjay_rid_t rid) {\n    (void) anjay;\n\n    binary_app_data_container_t *obj = get_obj(obj_ptr);\n    binary_app_data_container_instance_t *inst = find_instance(obj, iid);\n    assert(inst);\n\n    switch (rid) {\n    case RID_DATA:\n        AVS_LIST_CLEAR(&inst->data_list);\n        return 0;\n\n    default:\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\n#ifdef ANJAY_WITH_LWM2M12\nstatic int resource_instance_remove(anjay_t *anjay,\n                                    const anjay_dm_object_def_t *const *obj_ptr,\n                                    anjay_iid_t iid,\n                                    anjay_rid_t rid,\n                                    anjay_riid_t riid) {\n    (void) anjay;\n\n    binary_app_data_container_t *obj = get_obj(obj_ptr);\n    binary_app_data_container_instance_t *inst = find_instance(obj, iid);\n    assert(inst);\n\n    switch (rid) {\n    case RID_DATA: {\n        AVS_LIST(data_resource_instance_t) *it;\n        AVS_LIST_FOREACH_PTR(it, &inst->data_list) {\n            if ((*it)->riid == riid) {\n                break;\n            }\n        }\n        if (!it || !*it) {\n            AVS_UNREACHABLE(\n                    \"Attempted to remove a non-existent Resource Instance\");\n            return ANJAY_ERR_NOT_FOUND;\n        }\n        AVS_LIST_DELETE(it);\n        return 0;\n    }\n    default:\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n#endif // ANJAY_WITH_LWM2M12\n\nstatic int list_resource_instances(anjay_t *anjay,\n                                   const anjay_dm_object_def_t *const *obj_ptr,\n                                   anjay_iid_t iid,\n                                   anjay_rid_t rid,\n                                   anjay_dm_list_ctx_t *ctx) {\n    (void) anjay;\n    (void) ctx;\n\n    binary_app_data_container_t *obj = get_obj(obj_ptr);\n    binary_app_data_container_instance_t *inst = find_instance(obj, iid);\n    assert(inst);\n\n    switch (rid) {\n    case RID_DATA: {\n        data_resource_instance_t *it;\n        AVS_LIST_FOREACH(it, inst->data_list) {\n            anjay_dm_emit(ctx, it->riid);\n        }\n        return 0;\n    }\n\n    default:\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\nstatic int transaction_begin(anjay_t *anjay,\n                             const anjay_dm_object_def_t *const *obj_ptr) {\n    (void) anjay;\n    binary_app_data_container_t *repr = get_obj(obj_ptr);\n    if (!repr->instances) {\n        return 0;\n    }\n\n    AVS_LIST(binary_app_data_container_instance_t) *saved_instances_tail =\n            &repr->saved_instances;\n    AVS_LIST(binary_app_data_container_instance_t) instance;\n    bool failed = false;\n    AVS_LIST_FOREACH(instance, repr->instances) {\n        AVS_LIST(binary_app_data_container_instance_t) saved_instance =\n                AVS_LIST_APPEND_NEW(binary_app_data_container_instance_t,\n                                    saved_instances_tail);\n        if (!saved_instance) {\n            demo_log(ERROR, \"cannot allocate a new instance\");\n            failed = true;\n            break;\n        }\n        if (instance->data_list) {\n            saved_instance->data_list =\n                    AVS_LIST_SIMPLE_CLONE(instance->data_list);\n            if (!saved_instance->data_list) {\n                demo_log(ERROR, \"cannot clone resource instances list\");\n                failed = true;\n                break;\n            }\n        }\n        saved_instance->iid = instance->iid;\n        AVS_LIST_ADVANCE_PTR(&saved_instances_tail);\n    }\n\n    if (failed) {\n        AVS_LIST_CLEAR(&repr->saved_instances) {\n            AVS_LIST_CLEAR(&repr->saved_instances->data_list);\n        }\n        return ANJAY_ERR_INTERNAL;\n    }\n    return 0;\n}\n\nstatic int transaction_commit(anjay_t *anjay,\n                              const anjay_dm_object_def_t *const *obj_ptr) {\n    (void) anjay;\n    binary_app_data_container_t *repr = get_obj(obj_ptr);\n    AVS_LIST_CLEAR(&repr->saved_instances) {\n        AVS_LIST_CLEAR(&repr->saved_instances->data_list);\n    }\n    return 0;\n}\n\nstatic int transaction_rollback(anjay_t *anjay,\n                                const anjay_dm_object_def_t *const *obj_ptr) {\n    (void) anjay;\n    binary_app_data_container_t *repr = get_obj(obj_ptr);\n    AVS_LIST_CLEAR(&repr->instances) {\n        AVS_LIST_CLEAR(&repr->instances->data_list);\n    }\n    repr->instances = repr->saved_instances;\n    repr->saved_instances = NULL;\n    return 0;\n}\n\nstatic const anjay_dm_object_def_t OBJ_DEF = {\n    .oid = 19,\n    .handlers = {\n        .list_instances = list_instances,\n        .instance_create = instance_create,\n        .instance_remove = instance_remove,\n        .instance_reset = instance_reset,\n\n        .list_resources = list_resources,\n        .resource_read = resource_read,\n        .resource_write = resource_write,\n        .resource_reset = resource_reset,\n        .list_resource_instances = list_resource_instances,\n\n        .transaction_begin = transaction_begin,\n        .transaction_validate = anjay_dm_transaction_NOOP,\n        .transaction_commit = transaction_commit,\n        .transaction_rollback = transaction_rollback\n#ifdef ANJAY_WITH_LWM2M12\n        ,\n        .resource_instance_remove = resource_instance_remove\n#endif // ANJAY_WITH_LWM2M12\n    }\n};\n\nconst anjay_dm_object_def_t **binary_app_data_container_object_create(void) {\n    binary_app_data_container_t *obj =\n            (binary_app_data_container_t *) avs_calloc(\n                    1, sizeof(binary_app_data_container_t));\n    if (!obj) {\n        return NULL;\n    }\n    obj->def = &OBJ_DEF;\n\n    return &obj->def;\n}\n\nvoid binary_app_data_container_object_release(\n        const anjay_dm_object_def_t **def) {\n    if (def) {\n        binary_app_data_container_t *obj = get_obj(def);\n        AVS_LIST_CLEAR(&obj->instances) {\n            release_instance(obj->instances);\n        }\n\n        avs_free(obj);\n    }\n}\n\nint binary_app_data_container_get_instances(const anjay_dm_object_def_t **def,\n                                            AVS_LIST(anjay_iid_t) *out) {\n    binary_app_data_container_t *obj = get_obj(def);\n    assert(!*out);\n    AVS_LIST(binary_app_data_container_instance_t) it;\n    AVS_LIST_FOREACH(it, obj->instances) {\n        if (!(*out = AVS_LIST_NEW_ELEMENT(anjay_iid_t))) {\n            demo_log(ERROR, \"out of memory\");\n            return -1;\n        }\n        **out = it->iid;\n        AVS_LIST_ADVANCE_PTR(&out);\n    }\n    return 0;\n}\n\nint binary_app_data_container_write(anjay_t *anjay,\n                                    const anjay_dm_object_def_t **def,\n                                    anjay_iid_t iid,\n                                    anjay_riid_t riid,\n                                    const char *value) {\n    size_t length = strlen(value);\n    if (length > MAX_BINARY_DATA_SIZE) {\n        demo_log(ERROR, \"Value too long: %s\", value);\n        return -1;\n    }\n\n    binary_app_data_container_t *obj = get_obj(def);\n    binary_app_data_container_instance_t *inst = find_instance(obj, iid);\n    if (!inst) {\n        demo_log(ERROR, \"No such instance: %\" PRIu16, iid);\n        return -1;\n    }\n    AVS_LIST(data_resource_instance_t) *it;\n    AVS_LIST_FOREACH_PTR(it, &inst->data_list) {\n        if ((*it)->riid >= riid) {\n            break;\n        }\n    }\n    if (!(*it) || (*it)->riid != riid) {\n        if (!(it = resource_instance_create(anjay, inst, riid))) {\n            return -1;\n        }\n    }\n\n    memcpy((*it)->data, value, length);\n    (*it)->data_length = length;\n    anjay_notify_changed(anjay, (*def)->oid, iid, RID_DATA);\n    return 0;\n}\n"
  },
  {
    "path": "demo/objects/cell_connectivity.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include \"../demo.h\"\n#include \"../demo_utils.h\"\n#include \"../objects.h\"\n\n#include <assert.h>\n#include <stdio.h>\n\n#define CELL_RES_SMSC_ADDRESS 0               // string\n#define CELL_RES_DISABLE_RADIO_PERIOD 1       // int[0:86400]\n#define CELL_RES_MODULE_ACTIVATION_CODE 2     // string\n#define CELL_RES_VENDOR_SPECIFIC_EXTENSIONS 3 // objlnk\n#define CELL_RES_PSM_TIMER 4                  // int [600:85708800]\n#define CELL_RES_ACTIVE_TIMER 5               // int [2:1860]\n#define CELL_RES_SERVING_PLMN_RATE_CONTROL 6  // int\n#define CELL_RES_EDRX_PARAMS_WBS1 8           // bytes\n#define CELL_RES_EDRX_PARAMS_NBS1 9           // bytes\n#define CELL_RES_ACTIVATED_PROFILE_NAMES 11   // objlnk[]\n#define CELL_RES_POWER_SAVING_MODES 13        // int16_t\n#define CELL_RES_ACTIVE_POWER_SAVING_MODES 14 // int16_t\n\n#define PSM_TIMER_MIN 600      // seconds (10 min)\n#define PSM_TIMER_MAX 85708800 // seconds (992 days)\n#define ACTIVE_TIMER_MIN 2     // seconds\n#define ACTIVE_TIMER_MAX 1860  // seconds (31 min)\n\ntypedef enum {\n    PS_PSM = (1 << 0),\n    PS_eDRX = (1 << 1),\n\n    PS_ALL_AVILABLE_MODES = PS_PSM | PS_eDRX\n} cell_power_saving_mode_t;\n\ntypedef struct {\n    uint16_t active_power_saving_modes;\n    int32_t psm_timer;\n    int32_t active_timer;\n    uint8_t edrx_wbs1;\n    uint8_t edrx_nbs1;\n} cell_connectivity_data_t;\n\ntypedef struct {\n    const anjay_dm_object_def_t *def;\n    anjay_demo_t *demo;\n\n    cell_connectivity_data_t actual_data;\n    cell_connectivity_data_t backup_data;\n} cell_connectivity_repr_t;\n\nstatic inline cell_connectivity_repr_t *\nget_cell(const anjay_dm_object_def_t *const *obj_ptr) {\n    assert(obj_ptr);\n    return AVS_CONTAINER_OF(obj_ptr, cell_connectivity_repr_t, def);\n}\n\nstatic int cell_instance_reset(anjay_t *anjay,\n                               const anjay_dm_object_def_t *const *obj_ptr,\n                               anjay_iid_t iid) {\n    (void) anjay;\n    (void) iid;\n\n    cell_connectivity_repr_t *cell = get_cell(obj_ptr);\n    cell->actual_data = (cell_connectivity_data_t) {\n        .psm_timer = PSM_TIMER_MIN,\n        .active_timer = ACTIVE_TIMER_MIN\n    };\n\n    return 0;\n}\n\nstatic int cell_list_resources(anjay_t *anjay,\n                               const anjay_dm_object_def_t *const *obj_ptr,\n                               anjay_iid_t iid,\n                               anjay_dm_resource_list_ctx_t *ctx) {\n    (void) anjay;\n    (void) obj_ptr;\n    (void) iid;\n    anjay_dm_emit_res(ctx, CELL_RES_PSM_TIMER, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, CELL_RES_ACTIVE_TIMER, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, CELL_RES_SERVING_PLMN_RATE_CONTROL, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, CELL_RES_EDRX_PARAMS_WBS1, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, CELL_RES_EDRX_PARAMS_NBS1, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, CELL_RES_ACTIVATED_PROFILE_NAMES, ANJAY_DM_RES_RM,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, CELL_RES_POWER_SAVING_MODES, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, CELL_RES_ACTIVE_POWER_SAVING_MODES, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_PRESENT);\n    return 0;\n}\n\nstatic int cell_resource_read(anjay_t *anjay,\n                              const anjay_dm_object_def_t *const *obj_ptr,\n                              anjay_iid_t iid,\n                              anjay_rid_t rid,\n                              anjay_riid_t riid,\n                              anjay_output_ctx_t *ctx) {\n    (void) anjay;\n    (void) iid;\n\n    cell_connectivity_repr_t *cell = get_cell(obj_ptr);\n    switch (rid) {\n    case CELL_RES_PSM_TIMER:\n        return anjay_ret_i32(ctx, cell->actual_data.psm_timer);\n    case CELL_RES_ACTIVE_TIMER:\n        return anjay_ret_i32(ctx, cell->actual_data.active_timer);\n    case CELL_RES_EDRX_PARAMS_WBS1: {\n        anjay_ret_bytes_ctx_t *bytes_ctx =\n                anjay_ret_bytes_begin(ctx, sizeof(cell->actual_data.edrx_wbs1));\n        if (!bytes_ctx) {\n            return -1;\n        }\n        return anjay_ret_bytes_append(bytes_ctx, &cell->actual_data.edrx_wbs1,\n                                      sizeof(cell->actual_data.edrx_wbs1));\n    }\n    case CELL_RES_EDRX_PARAMS_NBS1: {\n        anjay_ret_bytes_ctx_t *bytes_ctx =\n                anjay_ret_bytes_begin(ctx, sizeof(cell->actual_data.edrx_nbs1));\n        if (!bytes_ctx) {\n            return -1;\n        }\n        return anjay_ret_bytes_append(bytes_ctx, &cell->actual_data.edrx_nbs1,\n                                      sizeof(cell->actual_data.edrx_nbs1));\n    }\n    case CELL_RES_SERVING_PLMN_RATE_CONTROL:\n        return anjay_ret_i32(ctx, 0);\n    case CELL_RES_ACTIVATED_PROFILE_NAMES: {\n        int result = ANJAY_ERR_NOT_FOUND;\n        AVS_LIST(anjay_iid_t) profile_iids = NULL;\n        AVS_LIST(anjay_iid_t) it = NULL;\n\n        const anjay_dm_object_def_t **apn_conn_profile =\n                demo_find_object(cell->demo, DEMO_OID_APN_CONN_PROFILE);\n        if (!apn_conn_profile) {\n            return ANJAY_ERR_INTERNAL;\n        }\n\n        profile_iids = apn_conn_profile_list_activated(apn_conn_profile);\n        AVS_LIST_FOREACH(it, profile_iids) {\n            // Activated Profile Names is an identity map (IID => IID),\n            // that's why we're comparing IID to RIID.\n            if (*it == riid) {\n                result = anjay_ret_objlnk(ctx, DEMO_OID_APN_CONN_PROFILE, *it);\n                break;\n            }\n        }\n        AVS_LIST_CLEAR(&profile_iids);\n        return result;\n    }\n    case CELL_RES_POWER_SAVING_MODES:\n        return anjay_ret_i32(ctx, PS_ALL_AVILABLE_MODES);\n    case CELL_RES_ACTIVE_POWER_SAVING_MODES:\n        return anjay_ret_i32(ctx, cell->actual_data.active_power_saving_modes);\n    default:\n        AVS_UNREACHABLE(\"Read called on unknown resource\");\n        return ANJAY_ERR_NOT_FOUND;\n    }\n}\n\nstatic int get_edrx_params(anjay_input_ctx_t *ctx, uint8_t *edrx_data) {\n    bool message_finished;\n    size_t bytes_read;\n    int result = anjay_get_bytes(ctx, &bytes_read, &message_finished, edrx_data,\n                                 sizeof(*edrx_data));\n    if (result) {\n        return result;\n    }\n    if (bytes_read != sizeof(*edrx_data) || !message_finished) {\n        return ANJAY_ERR_BAD_REQUEST;\n    }\n    return 0;\n}\n\nstatic int cell_resource_write(anjay_t *anjay,\n                               const anjay_dm_object_def_t *const *obj_ptr,\n                               anjay_iid_t iid,\n                               anjay_rid_t rid,\n                               anjay_rid_t riid,\n                               anjay_input_ctx_t *ctx) {\n    (void) anjay;\n    (void) iid;\n    (void) riid;\n\n    cell_connectivity_repr_t *cell = get_cell(obj_ptr);\n\n    switch (rid) {\n    case CELL_RES_PSM_TIMER: {\n        int32_t value;\n        int retval = anjay_get_i32(ctx, &value);\n        if (retval) {\n            return retval;\n        }\n        if (value < PSM_TIMER_MIN || value > PSM_TIMER_MAX) {\n            return ANJAY_ERR_BAD_REQUEST;\n        }\n        cell->actual_data.psm_timer = value;\n        return 0;\n    }\n    case CELL_RES_ACTIVE_TIMER: {\n        int32_t value;\n        int retval = anjay_get_i32(ctx, &value);\n        if (retval) {\n            return retval;\n        }\n        if (value < ACTIVE_TIMER_MIN || value > ACTIVE_TIMER_MAX) {\n            return ANJAY_ERR_BAD_REQUEST;\n        }\n        cell->actual_data.active_timer = value;\n        return 0;\n    }\n    case CELL_RES_EDRX_PARAMS_WBS1:\n        return get_edrx_params(ctx, &cell->actual_data.edrx_wbs1);\n    case CELL_RES_EDRX_PARAMS_NBS1:\n        return get_edrx_params(ctx, &cell->actual_data.edrx_nbs1);\n    case CELL_RES_ACTIVE_POWER_SAVING_MODES: {\n        assert(riid == ANJAY_ID_INVALID);\n        int32_t i32_val;\n        int result = anjay_get_i32(ctx, &i32_val);\n        if (result) {\n            return result;\n        }\n        if ((uint16_t) i32_val != i32_val\n                || (uint16_t) i32_val & ~PS_ALL_AVILABLE_MODES) {\n            return ANJAY_ERR_BAD_REQUEST;\n        }\n\n        cell->actual_data.active_power_saving_modes = (uint16_t) i32_val;\n        return 0;\n    }\n    default:\n        // Bootstrap Server may try to write to other resources,\n        // so no AVS_UNREACHABLE() here\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\nstatic int\ncell_list_resource_instances(anjay_t *anjay,\n                             const anjay_dm_object_def_t *const *obj_ptr,\n                             anjay_iid_t iid,\n                             anjay_rid_t rid,\n                             anjay_dm_list_ctx_t *ctx) {\n    (void) anjay;\n    (void) obj_ptr;\n    (void) iid;\n\n    cell_connectivity_repr_t *cell = get_cell(obj_ptr);\n    switch (rid) {\n    case CELL_RES_ACTIVATED_PROFILE_NAMES: {\n        const anjay_dm_object_def_t **apn_conn_profile =\n                demo_find_object(cell->demo, DEMO_OID_APN_CONN_PROFILE);\n        if (!apn_conn_profile) {\n            return ANJAY_ERR_INTERNAL;\n        }\n\n        AVS_LIST(anjay_iid_t) profile_iids =\n                apn_conn_profile_list_activated(apn_conn_profile);\n        AVS_LIST_CLEAR(&profile_iids) {\n            anjay_dm_emit(ctx, *profile_iids);\n        }\n        return 0;\n    }\n    default:\n        AVS_UNREACHABLE(\n                \"Attempted to list instances in a single-instance resource\");\n        return ANJAY_ERR_INTERNAL;\n    }\n}\n\nstatic int cell_transaction_begin(anjay_t *anjay,\n                                  const anjay_dm_object_def_t *const *obj_ptr) {\n    (void) anjay;\n\n    cell_connectivity_repr_t *cell = get_cell(obj_ptr);\n    cell->backup_data = cell->actual_data;\n\n    return 0;\n}\n\nstatic int\ncell_transaction_rollback(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr) {\n    (void) anjay;\n\n    cell_connectivity_repr_t *cell = get_cell(obj_ptr);\n    cell->actual_data = cell->backup_data;\n\n    return 0;\n}\n\nstatic const anjay_dm_object_def_t cell_connectivity = {\n    .oid = DEMO_OID_CELL_CONNECTIVITY,\n    .version = \"1.1\",\n    .handlers = {\n        .list_instances = anjay_dm_list_instances_SINGLE,\n        .instance_reset = cell_instance_reset,\n        .list_resources = cell_list_resources,\n        .resource_read = cell_resource_read,\n        .resource_write = cell_resource_write,\n        .list_resource_instances = cell_list_resource_instances,\n        .transaction_begin = cell_transaction_begin,\n        .transaction_validate = anjay_dm_transaction_NOOP,\n        .transaction_commit = anjay_dm_transaction_NOOP,\n        .transaction_rollback = cell_transaction_rollback\n    }\n};\n\nconst anjay_dm_object_def_t **\ncell_connectivity_object_create(anjay_demo_t *demo) {\n    cell_connectivity_repr_t *repr = (cell_connectivity_repr_t *) avs_calloc(\n            1, sizeof(cell_connectivity_repr_t));\n    if (!repr) {\n        return NULL;\n    }\n\n    repr->actual_data.active_timer = ACTIVE_TIMER_MIN;\n    repr->actual_data.psm_timer = PSM_TIMER_MIN;\n\n    repr->def = &cell_connectivity;\n    repr->demo = demo;\n\n    return &repr->def;\n}\n\nvoid cell_connectivity_object_release(const anjay_dm_object_def_t **def) {\n    if (def) {\n        avs_free(get_cell(def));\n    }\n}\n"
  },
  {
    "path": "demo/objects/conn_monitoring.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include \"../demo_utils.h\"\n#include \"../objects.h\"\n\n#include <assert.h>\n\n#define CM_RES_NETWORK_BEARER 0           /* int */\n#define CM_RES_AVAILABLE_NETWORK_BEARER 1 /* array<int> */\n#define CM_RES_RADIO_SIGNAL_STRENGTH 2    /* int */\n#define CM_RES_LINK_QUALITY 3             /* int */\n#define CM_RES_IP_ADDRESSES 4             /* array<string> */\n#define CM_RES_ROUTER_IP_ADDRESSES 5      /* array<string> */\n#define CM_RES_LINK_UTILIZATION 6         /* int */\n#define CM_RES_APN 7                      /* array<string> */\n#define CM_RES_CELL_ID 8                  /* int */\n#define CM_RES_SMNC 9                     /* int */\n#define CM_RES_SMCC 10                    /* int */\n\ntypedef struct {\n    const anjay_dm_object_def_t *def;\n} conn_monitoring_repr_t;\n\nstatic inline conn_monitoring_repr_t *\nget_cm(const anjay_dm_object_def_t *const *obj_ptr) {\n    assert(obj_ptr);\n    return AVS_CONTAINER_OF(obj_ptr, conn_monitoring_repr_t, def);\n}\n\nstatic int signal_strength_dbm(void) {\n    // range should be -110 .. -48\n    // RNG-like predictable generation in range -80 .. -65\n    return (int) (time_to_rand() % 16) - 80;\n}\n\nstatic int cm_list_resources(anjay_t *anjay,\n                             const anjay_dm_object_def_t *const *obj_ptr,\n                             anjay_iid_t iid,\n                             anjay_dm_resource_list_ctx_t *ctx) {\n    (void) anjay;\n    (void) obj_ptr;\n    (void) iid;\n\n    anjay_dm_emit_res(\n            ctx, CM_RES_NETWORK_BEARER, ANJAY_DM_RES_R, ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx,\n                      CM_RES_AVAILABLE_NETWORK_BEARER,\n                      ANJAY_DM_RES_RM,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx,\n                      CM_RES_RADIO_SIGNAL_STRENGTH,\n                      ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(\n            ctx, CM_RES_LINK_QUALITY, ANJAY_DM_RES_R, ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(\n            ctx, CM_RES_IP_ADDRESSES, ANJAY_DM_RES_RM, ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx,\n                      CM_RES_ROUTER_IP_ADDRESSES,\n                      ANJAY_DM_RES_RM,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(\n            ctx, CM_RES_LINK_UTILIZATION, ANJAY_DM_RES_R, ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, CM_RES_APN, ANJAY_DM_RES_RM, ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(\n            ctx, CM_RES_CELL_ID, ANJAY_DM_RES_R, ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, CM_RES_SMNC, ANJAY_DM_RES_R, ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, CM_RES_SMCC, ANJAY_DM_RES_R, ANJAY_DM_RES_PRESENT);\n    return 0;\n}\n\nstatic int cm_resource_read(anjay_t *anjay,\n                            const anjay_dm_object_def_t *const *obj_ptr,\n                            anjay_iid_t iid,\n                            anjay_rid_t rid,\n                            anjay_riid_t riid,\n                            anjay_output_ctx_t *ctx) {\n    (void) anjay;\n    (void) obj_ptr;\n    (void) iid;\n\n    enum {\n        NB_CELLULAR_GSM = 0,\n        NB_CELLULAR_TD_SCDMA,\n        NB_CELLULAR_WCDMA,\n        NB_CELLULAR_CDMA2000,\n        NB_CELLULAR_WIMAX,\n        NB_CELLULAR_LTE_TDD,\n        NB_CELLULAR_LTE_FDD,\n\n        NB_WIRELESS_WLAN = 21,\n        NB_WIRELESS_BLUETOOTH,\n        NB_WIRELESS_802_15_4,\n\n        NB_WIRED_ETHERNET = 41,\n        NB_WIRED_DSL,\n        NB_WIRED_PLC\n    };\n\n    switch (rid) {\n    case CM_RES_NETWORK_BEARER:\n        assert(riid == ANJAY_ID_INVALID);\n        return anjay_ret_i32(ctx, NB_CELLULAR_WCDMA);\n    case CM_RES_AVAILABLE_NETWORK_BEARER:\n        switch (riid) {\n        case 0:\n            return anjay_ret_i32(ctx, NB_CELLULAR_GSM);\n        case 1:\n            return anjay_ret_i32(ctx, NB_CELLULAR_WCDMA);\n        case 2:\n            return anjay_ret_i32(ctx, NB_CELLULAR_LTE_FDD);\n        case 3:\n            return anjay_ret_i32(ctx, NB_WIRELESS_WLAN);\n        case 4:\n            return anjay_ret_i32(ctx, NB_WIRELESS_BLUETOOTH);\n        default:\n            AVS_UNREACHABLE(\n                    \"unexpected RIID for CM_RES_AVAILABLE_NETWORK_BEARER\");\n            return ANJAY_ERR_NOT_FOUND;\n        }\n    case CM_RES_RADIO_SIGNAL_STRENGTH:\n        assert(riid == ANJAY_ID_INVALID);\n        return anjay_ret_i32(ctx, signal_strength_dbm());\n    case CM_RES_LINK_QUALITY:\n        assert(riid == ANJAY_ID_INVALID);\n        return anjay_ret_i32(ctx, 255);\n    case CM_RES_IP_ADDRESSES:\n        assert(riid == 0);\n        return anjay_ret_string(ctx, \"10.10.53.53\");\n    case CM_RES_ROUTER_IP_ADDRESSES:\n        assert(riid == 0);\n        return anjay_ret_string(ctx, \"10.10.0.1\");\n    case CM_RES_LINK_UTILIZATION:\n        assert(riid == ANJAY_ID_INVALID);\n        return anjay_ret_i32(ctx, 50);\n    case CM_RES_APN:\n        assert(riid == 0);\n        return anjay_ret_string(ctx, \"internet\");\n    case CM_RES_CELL_ID:\n        assert(riid == ANJAY_ID_INVALID);\n        return anjay_ret_i32(ctx, 12345);\n    case CM_RES_SMNC:\n        assert(riid == ANJAY_ID_INVALID);\n        return anjay_ret_i32(ctx, 0);\n    case CM_RES_SMCC:\n        assert(riid == ANJAY_ID_INVALID);\n        return anjay_ret_i32(ctx, 0);\n    default:\n        AVS_UNREACHABLE(\n                \"Read handler called on unknown or non-readable resource\");\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\nstatic int\ncm_list_resource_instances(anjay_t *anjay,\n                           const anjay_dm_object_def_t *const *obj_ptr,\n                           anjay_iid_t iid,\n                           anjay_rid_t rid,\n                           anjay_dm_list_ctx_t *ctx) {\n    (void) anjay;\n    (void) obj_ptr;\n    (void) iid;\n\n    switch (rid) {\n    case CM_RES_AVAILABLE_NETWORK_BEARER:\n        anjay_dm_emit(ctx, 0);\n        anjay_dm_emit(ctx, 1);\n        anjay_dm_emit(ctx, 2);\n        anjay_dm_emit(ctx, 3);\n        anjay_dm_emit(ctx, 4);\n        return 0;\n    case CM_RES_IP_ADDRESSES:\n    case CM_RES_ROUTER_IP_ADDRESSES:\n    case CM_RES_APN:\n        anjay_dm_emit(ctx, 0);\n        return 0;\n    default:\n        AVS_UNREACHABLE(\n                \"Attempted to list instances in a single-instance resource\");\n        return ANJAY_ERR_INTERNAL;\n    }\n}\n\nstatic const anjay_dm_object_def_t CONN_MONITORING = {\n    .oid = DEMO_OID_CONN_MONITORING,\n    .handlers = {\n        .list_instances = anjay_dm_list_instances_SINGLE,\n        .list_resources = cm_list_resources,\n        .resource_read = cm_resource_read,\n        .list_resource_instances = cm_list_resource_instances\n    }\n};\n\nconst anjay_dm_object_def_t **cm_object_create(void) {\n    conn_monitoring_repr_t *repr = (conn_monitoring_repr_t *) avs_calloc(\n            1, sizeof(conn_monitoring_repr_t));\n    if (!repr) {\n        return NULL;\n    }\n\n    repr->def = &CONN_MONITORING;\n\n    return &repr->def;\n}\n\nvoid cm_object_release(const anjay_dm_object_def_t **def) {\n    if (def) {\n        avs_free(get_cm(def));\n    }\n}\n\nvoid cm_notify_time_dependent(anjay_t *anjay,\n                              const anjay_dm_object_def_t **def) {\n    anjay_notify_changed(anjay, (*def)->oid, 0, CM_RES_RADIO_SIGNAL_STRENGTH);\n}\n"
  },
  {
    "path": "demo/objects/conn_statistics.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include \"../demo_utils.h\"\n#include \"../objects.h\"\n\n#include <assert.h>\n#include <inttypes.h>\n#include <string.h>\n\ntypedef enum {\n    CS_SMS_TX_COUNTER = 0,\n    CS_SMS_RX_COUNTER = 1,\n    CS_TX_KB = 2,\n    CS_RX_KB = 3,\n    CS_MAX_MSG_SIZE = 4,\n    CS_AVG_MSG_SIZE = 5,\n    CS_START = 6,\n    CS_STOP = 7,\n    CS_COLLECTION_PERIOD = 8,\n} conn_stats_res_t;\n\ntypedef struct {\n    const anjay_dm_object_def_t *def;\n    uint64_t last_tx_bytes;\n    uint64_t last_rx_bytes;\n    bool is_collecting;\n    uint32_t collection_period;\n} conn_stats_repr_t;\n\nstatic conn_stats_repr_t *get_cs(const anjay_dm_object_def_t *const *obj_ptr) {\n    assert(obj_ptr);\n    return AVS_CONTAINER_OF(obj_ptr, conn_stats_repr_t, def);\n}\n\nstatic int read_uint64_from_file(uint64_t *number, const char *filename) {\n    int result = 0;\n    FILE *rx_bytes_file = fopen(filename, \"r\");\n\n    if (!rx_bytes_file) {\n        return -1;\n    }\n    if (fscanf(rx_bytes_file, \"%\" SCNu64, number) != 1) {\n        result = -1;\n    }\n    fclose(rx_bytes_file);\n\n    return result;\n}\n\nstatic int ifname_for_first_socket(anjay_t *anjay,\n                                   avs_net_socket_interface_name_t *if_name) {\n    avs_net_socket_t *const *first = anjay_get_sockets(anjay);\n    if (!first) {\n        return -1;\n    }\n    return avs_is_ok(avs_net_socket_interface_name(*first, if_name)) ? 0 : -1;\n}\n\nstatic const char *RX_STATS = \"rx_bytes\";\nstatic const char *TX_STATS = \"tx_bytes\";\n\nstatic int stats_getter(avs_net_socket_interface_name_t *if_name,\n                        uint64_t *bytes,\n                        const char *stats) {\n    char file_name[128];\n    sprintf(file_name, \"/sys/class/net/%s/statistics/%s\", *if_name, stats);\n    return read_uint64_from_file(bytes, file_name);\n}\n\nstatic uint64_t first_socket_stats(anjay_t *anjay, const char *stats) {\n    avs_net_socket_interface_name_t if_name;\n    memset(&if_name, 0, sizeof(if_name));\n    if (ifname_for_first_socket(anjay, &if_name)) {\n        return 0;\n    }\n    uint64_t value = 0;\n    stats_getter(&if_name, &value, stats);\n    return value;\n}\n\nstatic uint64_t get_rx_stats(anjay_t *anjay, conn_stats_repr_t *repr) {\n    if (repr->is_collecting) {\n        return first_socket_stats(anjay, RX_STATS) - repr->last_rx_bytes;\n    } else {\n        return repr->last_rx_bytes;\n    }\n}\n\nstatic uint64_t get_tx_stats(anjay_t *anjay, conn_stats_repr_t *repr) {\n    if (repr->is_collecting) {\n        return first_socket_stats(anjay, TX_STATS) - repr->last_tx_bytes;\n    } else {\n        return repr->last_tx_bytes;\n    }\n}\n\nstatic int cs_instance_reset(anjay_t *anjay,\n                             const anjay_dm_object_def_t *const *obj_ptr,\n                             anjay_iid_t iid) {\n    (void) anjay;\n    (void) iid;\n\n    conn_stats_repr_t *repr = get_cs(obj_ptr);\n\n    repr->last_tx_bytes = 0;\n    repr->last_rx_bytes = 0;\n    repr->is_collecting = false;\n    repr->collection_period = 0;\n    return 0;\n}\n\nstatic int cs_list_resources(anjay_t *anjay,\n                             const anjay_dm_object_def_t *const *obj_ptr,\n                             anjay_iid_t iid,\n                             anjay_dm_resource_list_ctx_t *ctx) {\n    (void) anjay;\n    (void) obj_ptr;\n    (void) iid;\n\n    anjay_dm_emit_res(\n            ctx, CS_SMS_TX_COUNTER, ANJAY_DM_RES_R, ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(\n            ctx, CS_SMS_RX_COUNTER, ANJAY_DM_RES_R, ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, CS_TX_KB, ANJAY_DM_RES_R, ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, CS_RX_KB, ANJAY_DM_RES_R, ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(\n            ctx, CS_MAX_MSG_SIZE, ANJAY_DM_RES_R, ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(\n            ctx, CS_AVG_MSG_SIZE, ANJAY_DM_RES_R, ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, CS_START, ANJAY_DM_RES_E, ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, CS_STOP, ANJAY_DM_RES_E, ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(\n            ctx, CS_COLLECTION_PERIOD, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT);\n    return 0;\n}\n\nstatic int cs_resource_execute(anjay_t *anjay,\n                               const anjay_dm_object_def_t *const *obj_ptr,\n                               anjay_iid_t iid,\n                               anjay_iid_t rid,\n                               anjay_execute_ctx_t *ctx) {\n    (void) anjay;\n    (void) iid;\n    (void) ctx;\n    conn_stats_repr_t *repr = get_cs(obj_ptr);\n    switch ((conn_stats_res_t) rid) {\n    case CS_START:\n        // TODO: actually use Collection Period resource\n        repr->last_tx_bytes = first_socket_stats(anjay, TX_STATS);\n        repr->last_rx_bytes = first_socket_stats(anjay, RX_STATS);\n        repr->is_collecting = true;\n        return 0;\n    case CS_STOP:\n        if (!repr->is_collecting) {\n            return ANJAY_ERR_BAD_REQUEST;\n        }\n        repr->last_tx_bytes =\n                first_socket_stats(anjay, TX_STATS) - repr->last_tx_bytes;\n        repr->last_rx_bytes =\n                first_socket_stats(anjay, RX_STATS) - repr->last_rx_bytes;\n        repr->is_collecting = false;\n        return 0;\n    default:\n        AVS_UNREACHABLE(\"Execute called on unknown or non-executable resource\");\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\nstatic int cs_resource_read(anjay_t *anjay,\n                            const anjay_dm_object_def_t *const *obj_ptr,\n                            anjay_iid_t iid,\n                            anjay_rid_t rid,\n                            anjay_riid_t riid,\n                            anjay_output_ctx_t *ctx) {\n    (void) iid;\n    (void) riid;\n    assert(riid == ANJAY_ID_INVALID);\n    conn_stats_repr_t *repr = get_cs(obj_ptr);\n    switch (rid) {\n    case CS_SMS_TX_COUNTER:\n    case CS_SMS_RX_COUNTER:\n    case CS_MAX_MSG_SIZE:\n    case CS_AVG_MSG_SIZE:\n        return anjay_ret_i32(ctx, 0);\n    case CS_TX_KB:\n        return anjay_ret_i64(ctx, (int64_t) (get_tx_stats(anjay, repr) / 1024));\n    case CS_RX_KB:\n        return anjay_ret_i64(ctx, (int64_t) (get_rx_stats(anjay, repr) / 1024));\n    case CS_COLLECTION_PERIOD:\n        return anjay_ret_i64(ctx, (int64_t) repr->collection_period);\n    default:\n        AVS_UNREACHABLE(\"Read called on unknown or non-readable resource\");\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\nstatic int cs_resource_write(anjay_t *anjay,\n                             const anjay_dm_object_def_t *const *obj_ptr,\n                             anjay_iid_t iid,\n                             anjay_rid_t rid,\n                             anjay_riid_t riid,\n                             anjay_input_ctx_t *ctx) {\n    (void) anjay;\n    (void) iid;\n    (void) riid;\n    assert(riid == ANJAY_ID_INVALID);\n    conn_stats_repr_t *repr = get_cs(obj_ptr);\n    switch (rid) {\n    case CS_COLLECTION_PERIOD: {\n        int32_t val;\n        int result;\n        if ((result = anjay_get_i32(ctx, &val))) {\n            return result;\n        } else if (val < 0) {\n            return ANJAY_ERR_BAD_REQUEST;\n        }\n\n        repr->collection_period = (uint32_t) val;\n        return 0;\n    }\n    default:\n        // Bootstrap Server may try to write to other resources,\n        // so no AVS_UNREACHABLE() here\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\nstatic const anjay_dm_object_def_t CONN_STATISTICS = {\n    .oid = DEMO_OID_CONN_STATISTICS,\n    .handlers = {\n        .list_instances = anjay_dm_list_instances_SINGLE,\n        .instance_reset = cs_instance_reset,\n        .list_resources = cs_list_resources,\n        .resource_execute = cs_resource_execute,\n        .resource_read = cs_resource_read,\n        .resource_write = cs_resource_write,\n        .transaction_begin = anjay_dm_transaction_NOOP,\n        .transaction_validate = anjay_dm_transaction_NOOP,\n        .transaction_commit = anjay_dm_transaction_NOOP,\n        .transaction_rollback = anjay_dm_transaction_NOOP\n    }\n};\n\nconst anjay_dm_object_def_t **cs_object_create(void) {\n    conn_stats_repr_t *repr =\n            (conn_stats_repr_t *) avs_calloc(1, sizeof(conn_stats_repr_t));\n    if (!repr) {\n        return NULL;\n    }\n    repr->def = &CONN_STATISTICS;\n    return &repr->def;\n}\n\nvoid cs_object_release(const anjay_dm_object_def_t **def) {\n    if (def) {\n        avs_free(get_cs(def));\n    }\n}\n"
  },
  {
    "path": "demo/objects/device.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include \"../demo_utils.h\"\n#include \"../objects.h\"\n\n#include <sys/param.h>\n\n#include <assert.h>\n#include <string.h>\n#include <unistd.h>\n\n#include <avsystem/commons/avs_memory.h>\n#include <avsystem/commons/avs_utils.h>\n\n#define DEV_RES_MANUFACTURER 0     // string\n#define DEV_RES_MODEL_NUMBER 1     // string\n#define DEV_RES_SERIAL_NUMBER 2    // string\n#define DEV_RES_FIRMWARE_VERSION 3 // string\n#define DEV_RES_REBOOT 4\n#define DEV_RES_FACTORY_RESET 5\n#define DEV_RES_AVAILABLE_POWER_SOURCES 6 // array<int>\n#define DEV_RES_POWER_SOURCE_VOLTAGE 7    // array<int>\n#define DEV_RES_POWER_SOURCE_CURRENT 8    // array<int>\n#define DEV_RES_BATTERY_LEVEL 9           // int\n#define DEV_RES_MEMORY_FREE 10            // int\n#define DEV_RES_ERROR_CODE 11             // int\n#define DEV_RES_RESET_ERROR_CODE 12\n#define DEV_RES_CURRENT_TIME 13                // time\n#define DEV_RES_UTC_OFFSET 14                  // string\n#define DEV_RES_TIMEZONE 15                    // string\n#define DEV_RES_SUPPORTED_BINDING_AND_MODES 16 // string\n#define DEV_RES_DEVICE_TYPE 17                 // string\n#define DEV_RES_HARDWARE_VERSION 18            // string\n#define DEV_RES_SOFTWARE_VERSION 19            // string\n#define DEV_RES_BATTERY_STATUS 20              // int\n#define DEV_RES_MEMORY_TOTAL 21                // int\n#define DEV_RES_EXTDEVINFO 22                  // objlnk\n\ntypedef enum {\n    DEV_ERR_NO_ERROR = 0,\n    DEV_ERR_LOW_BATTERY_POWER,\n    DEV_ERR_EXTERNAL_POWER_SUPPLY_OFF,\n    DEV_ERR_GPS_MODULE_FAILURE,\n    DEV_ERR_LOW_RECEIVED_SIGNAL_STRENGTH,\n    DEV_ERR_OUT_OF_MEMORY,\n    DEV_ERR_SMS_FAILURE,\n    DEV_ERR_IP_CONNECTIVITY_FAILURE,\n    DEV_ERR_PERIPHERAL_MALFUNCTION\n} dev_error_t;\n\ntypedef enum {\n    POWER_SOURCE_DC = 0,\n    POWER_SOURCE_INTERNAL_BATTERY = 1,\n    POWER_SOURCE_EXTERNAL_BATTERY = 2,\n    POWER_SOURCE_ETHERNET = 3,\n    POWER_SOURCE_USB = 4,\n    POWER_SOURCE_AC = 5,\n    POWER_SOURCE_SOLAR = 6\n} power_source_type_t;\n\ntypedef struct {\n    const anjay_dm_object_def_t *def;\n    dev_error_t last_error;\n\n    char manufacturer[256];\n    char serial_number[256];\n\n    time_t current_time_offset;\n    char utc_offset[16];\n    char timezone[32];\n\n    time_t saved_current_time_offset;\n    char saved_utc_offset[16];\n    char saved_timezone[32];\n} dev_repr_t;\n\nstatic inline dev_repr_t *get_dev(const anjay_dm_object_def_t *const *obj_ptr) {\n    assert(obj_ptr);\n    return AVS_CONTAINER_OF(obj_ptr, dev_repr_t, def);\n}\n\nstatic int32_t randint_from_range(int32_t min_value, int32_t max_value) {\n    assert(min_value <= max_value);\n\n    // RNG-like predictable generation in range [min_value, max_value]\n    uint32_t diff = (uint32_t) (max_value - min_value);\n    return min_value + (int32_t) (time_to_rand() % (diff + 1));\n}\n\nstatic int32_t get_dc_voltage_mv(void) {\n    return randint_from_range(32 * 1000 - 500, 32 * 1000 + 500);\n}\n\nstatic int32_t get_dc_current_ma(void) {\n    return randint_from_range(10 - 1, 10 + 1);\n}\n\nstatic int dev_resources(anjay_t *anjay,\n                         const anjay_dm_object_def_t *const *obj_ptr,\n                         anjay_iid_t iid,\n                         anjay_dm_resource_list_ctx_t *ctx) {\n    (void) anjay;\n    (void) obj_ptr;\n    (void) iid;\n\n    anjay_dm_emit_res(ctx, DEV_RES_MANUFACTURER, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, DEV_RES_MODEL_NUMBER, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, DEV_RES_SERIAL_NUMBER, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, DEV_RES_FIRMWARE_VERSION, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, DEV_RES_REBOOT, ANJAY_DM_RES_E,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, DEV_RES_FACTORY_RESET, ANJAY_DM_RES_E,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, DEV_RES_AVAILABLE_POWER_SOURCES, ANJAY_DM_RES_RM,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, DEV_RES_POWER_SOURCE_VOLTAGE, ANJAY_DM_RES_RM,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, DEV_RES_POWER_SOURCE_CURRENT, ANJAY_DM_RES_RM,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, DEV_RES_BATTERY_LEVEL, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, DEV_RES_MEMORY_FREE, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, DEV_RES_ERROR_CODE, ANJAY_DM_RES_RM,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, DEV_RES_CURRENT_TIME, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, DEV_RES_UTC_OFFSET, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, DEV_RES_TIMEZONE, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, DEV_RES_SUPPORTED_BINDING_AND_MODES, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, DEV_RES_DEVICE_TYPE, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, DEV_RES_HARDWARE_VERSION, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, DEV_RES_SOFTWARE_VERSION, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, DEV_RES_BATTERY_STATUS, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, DEV_RES_MEMORY_TOTAL, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, DEV_RES_EXTDEVINFO, ANJAY_DM_RES_RM,\n                      ANJAY_DM_RES_PRESENT);\n    return 0;\n}\n\nstatic int dev_read(anjay_t *anjay,\n                    const anjay_dm_object_def_t *const *obj_ptr,\n                    anjay_iid_t iid,\n                    anjay_rid_t rid,\n                    anjay_riid_t riid,\n                    anjay_output_ctx_t *ctx) {\n    (void) anjay;\n    (void) iid;\n    (void) riid;\n\n    dev_repr_t *dev = get_dev(obj_ptr);\n\n    switch (rid) {\n    case DEV_RES_MANUFACTURER:\n        assert(riid == ANJAY_ID_INVALID);\n        return anjay_ret_string(ctx, dev->manufacturer);\n    case DEV_RES_MODEL_NUMBER:\n        assert(riid == ANJAY_ID_INVALID);\n        return anjay_ret_string(ctx, \"demo-client\");\n    case DEV_RES_SERIAL_NUMBER:\n        assert(riid == ANJAY_ID_INVALID);\n        return anjay_ret_string(ctx, dev->serial_number);\n    case DEV_RES_FIRMWARE_VERSION:\n        assert(riid == ANJAY_ID_INVALID);\n        return anjay_ret_string(ctx, anjay_get_version());\n    case DEV_RES_AVAILABLE_POWER_SOURCES:\n        assert(riid == 0);\n        return anjay_ret_i32(ctx, (int32_t) POWER_SOURCE_DC);\n    case DEV_RES_POWER_SOURCE_VOLTAGE:\n        assert(riid == 0);\n        return anjay_ret_i32(ctx, get_dc_voltage_mv());\n    case DEV_RES_POWER_SOURCE_CURRENT:\n        assert(riid == 0);\n        return anjay_ret_i32(ctx, get_dc_current_ma());\n    case DEV_RES_BATTERY_LEVEL:\n    case DEV_RES_MEMORY_FREE:\n        assert(riid == ANJAY_ID_INVALID);\n        return anjay_ret_i32(ctx, 0);\n    case DEV_RES_ERROR_CODE:\n        assert(riid == 0);\n        return anjay_ret_i32(ctx, (int32_t) dev->last_error);\n    case DEV_RES_CURRENT_TIME:\n        assert(riid == ANJAY_ID_INVALID);\n        return anjay_ret_i64(ctx, avs_time_real_now().since_real_epoch.seconds\n                                          + dev->current_time_offset);\n    case DEV_RES_UTC_OFFSET:\n        assert(riid == ANJAY_ID_INVALID);\n        return anjay_ret_string(ctx, dev->utc_offset);\n    case DEV_RES_TIMEZONE:\n        assert(riid == ANJAY_ID_INVALID);\n        return anjay_ret_string(ctx, dev->timezone);\n    case DEV_RES_DEVICE_TYPE:\n        assert(riid == ANJAY_ID_INVALID);\n        return anjay_ret_string(ctx, \"\");\n    case DEV_RES_SUPPORTED_BINDING_AND_MODES:\n        assert(riid == ANJAY_ID_INVALID);\n        return anjay_ret_string(ctx, \"UQ\");\n    case DEV_RES_EXTDEVINFO:\n        assert(riid == 0);\n        return anjay_ret_objlnk(ctx, DEMO_OID_EXT_DEV_INFO, 0);\n    case DEV_RES_HARDWARE_VERSION:\n    case DEV_RES_SOFTWARE_VERSION:\n        assert(riid == ANJAY_ID_INVALID);\n        return anjay_ret_string(ctx, \"\");\n    case DEV_RES_BATTERY_STATUS:\n    case DEV_RES_MEMORY_TOTAL:\n        assert(riid == ANJAY_ID_INVALID);\n        return anjay_ret_i32(ctx, 0);\n    default:\n        AVS_UNREACHABLE(\n                \"Read handler called on unknown or non-readable resource\");\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\nstatic int\nread_string(anjay_input_ctx_t *ctx, char *buffer, size_t buffer_size) {\n    char *tmp = (char *) avs_malloc(buffer_size);\n    if (!tmp) {\n        demo_log(ERROR, \"Out of memory\");\n        return -1;\n    }\n    int result = anjay_get_string(ctx, tmp, buffer_size);\n    if (result < 0) {\n        goto finish;\n    } else if (result == ANJAY_BUFFER_TOO_SHORT) {\n        demo_log(DEBUG, \"buffer too short to fit full value\");\n        result = ANJAY_ERR_INTERNAL;\n        goto finish;\n    }\n\n    memcpy(buffer, tmp, buffer_size);\n    result = 0;\nfinish:\n    avs_free(tmp);\n    return result;\n}\n\nstatic int dev_write(anjay_t *anjay,\n                     const anjay_dm_object_def_t *const *obj_ptr,\n                     anjay_iid_t iid,\n                     anjay_rid_t rid,\n                     anjay_riid_t riid,\n                     anjay_input_ctx_t *ctx) {\n    (void) anjay;\n    (void) iid;\n    (void) riid;\n\n    dev_repr_t *dev = get_dev(obj_ptr);\n\n    switch (rid) {\n    case DEV_RES_CURRENT_TIME: {\n        assert(riid == ANJAY_ID_INVALID);\n        int64_t new_time;\n        int result = anjay_get_i64(ctx, &new_time);\n        if (result) {\n            return result;\n        }\n\n        time_t now = time(NULL);\n        dev->current_time_offset = (time_t) new_time - now;\n        return 0;\n    }\n    case DEV_RES_UTC_OFFSET:\n        assert(riid == ANJAY_ID_INVALID);\n        return read_string(ctx, dev->utc_offset, sizeof(dev->utc_offset));\n    case DEV_RES_TIMEZONE:\n        assert(riid == ANJAY_ID_INVALID);\n        return read_string(ctx, dev->timezone, sizeof(dev->timezone));\n    default:\n        // Bootstrap Server may try to write to other resources,\n        // so no AVS_UNREACHABLE() here\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\nstatic void dev_instance_reset_impl(dev_repr_t *dev) {\n    dev->current_time_offset = 0;\n    snprintf(dev->utc_offset, sizeof(dev->utc_offset), \"+01:00\");\n    snprintf(dev->timezone, sizeof(dev->timezone), \"Europe/Warsaw\");\n}\n\nstatic int dev_instance_reset(anjay_t *anjay,\n                              const anjay_dm_object_def_t *const *obj_ptr,\n                              anjay_iid_t iid) {\n    (void) anjay;\n    (void) iid;\n\n    dev_instance_reset_impl(get_dev(obj_ptr));\n    return 0;\n}\n\nstatic void perform_reboot(avs_sched_t *sched, const void *unused) {\n    (void) sched;\n    (void) unused;\n    char exe_path[256];\n#ifdef __APPLE__\n    extern int _NSGetExecutablePath(char *buf, uint32_t *bufsize);\n    if (_NSGetExecutablePath(exe_path, &(uint32_t) { sizeof(exe_path) })) {\n        demo_log(ERROR, \"could not get executable path\");\n    } else\n#elif defined(BSD)\n    strcpy(exe_path, \"/proc/curproc/file\");\n#else // !__APPLE__ && !BSD\n    strcpy(exe_path, \"/proc/self/exe\");\n#endif\n    {\n        demo_log(INFO, \"*** REBOOT ***\");\n        execv(exe_path, argv_get());\n    }\n    demo_log(ERROR, \"could not reboot\");\n}\n\nstatic int dev_execute(anjay_t *anjay,\n                       const anjay_dm_object_def_t *const *obj_ptr,\n                       anjay_iid_t iid,\n                       anjay_rid_t rid,\n                       anjay_execute_ctx_t *ctx) {\n    (void) obj_ptr;\n    (void) iid;\n    (void) ctx;\n\n    switch (rid) {\n    case DEV_RES_REBOOT:\n    case DEV_RES_FACTORY_RESET:\n        if (AVS_SCHED_NOW(anjay_get_scheduler(anjay), NULL, perform_reboot,\n                          NULL, 0)) {\n            return ANJAY_ERR_INTERNAL;\n        }\n\n        return 0;\n    default:\n        AVS_UNREACHABLE(\"Executable handler called on unknown or \"\n                        \"non-executable resource\");\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\nstatic int dev_resource_instances(anjay_t *anjay,\n                                  const anjay_dm_object_def_t *const *obj_ptr,\n                                  anjay_iid_t iid,\n                                  anjay_rid_t rid,\n                                  anjay_dm_list_ctx_t *ctx) {\n    (void) anjay;\n    (void) obj_ptr;\n    (void) iid;\n\n    switch (rid) {\n    case DEV_RES_AVAILABLE_POWER_SOURCES:\n    case DEV_RES_POWER_SOURCE_VOLTAGE:\n    case DEV_RES_POWER_SOURCE_CURRENT:\n    case DEV_RES_ERROR_CODE:\n    case DEV_RES_EXTDEVINFO:\n        anjay_dm_emit(ctx, 0);\n        return 0;\n    default:\n        AVS_UNREACHABLE(\n                \"Attempted to list instances in a single-instance resource\");\n        return ANJAY_ERR_INTERNAL;\n    }\n}\n\nstatic int dev_transaction_begin(anjay_t *anjay,\n                                 const anjay_dm_object_def_t *const *obj_ptr) {\n    (void) anjay;\n    dev_repr_t *repr = get_dev(obj_ptr);\n    repr->saved_current_time_offset = repr->current_time_offset;\n    strcpy(repr->saved_utc_offset, repr->utc_offset);\n    strcpy(repr->saved_timezone, repr->timezone);\n    return 0;\n}\n\nstatic int\ndev_transaction_rollback(anjay_t *anjay,\n                         const anjay_dm_object_def_t *const *obj_ptr) {\n    (void) anjay;\n    dev_repr_t *repr = get_dev(obj_ptr);\n    repr->current_time_offset = repr->saved_current_time_offset;\n    strcpy(repr->utc_offset, repr->saved_utc_offset);\n    strcpy(repr->timezone, repr->saved_timezone);\n    return 0;\n}\n\nstatic const anjay_dm_object_def_t DEVICE = {\n    .oid = DEMO_OID_DEVICE,\n    .handlers = {\n        .list_instances = anjay_dm_list_instances_SINGLE,\n        .instance_reset = dev_instance_reset,\n        .list_resources = dev_resources,\n        .resource_read = dev_read,\n        .resource_write = dev_write,\n        .resource_execute = dev_execute,\n        .list_resource_instances = dev_resource_instances,\n        .transaction_begin = dev_transaction_begin,\n        .transaction_validate = anjay_dm_transaction_NOOP,\n        .transaction_commit = anjay_dm_transaction_NOOP,\n        .transaction_rollback = dev_transaction_rollback\n    }\n};\n\nstatic void extract_device_info(const char *endpoint_name,\n                                char *out_manufacturer,\n                                size_t manufacturer_size,\n                                char *out_serial,\n                                size_t serial_size) {\n    int result = 0;\n    // skip everything up to (and including) last colon\n    // this throws away urn:dev:os: prefix, if any\n    const char *at = endpoint_name;\n    const char *last_colon = strrchr(at, ':');\n    at = last_colon ? last_colon + 1 : at;\n\n    // anything before dash character is used as manufacturer name\n    const char *dash = strchr(at, '-');\n    if (!dash || at == dash) {\n        demo_log(WARNING, \"empty manufacturer part of endpoint name\");\n        result = avs_simple_snprintf(out_manufacturer, manufacturer_size,\n                                     \"Anjay\");\n        assert(result >= 0);\n    } else {\n        AVS_ASSERT((size_t) (dash - at) < manufacturer_size,\n                   \"manufacturer part of endpoint name too long\");\n        (void) manufacturer_size;\n        strncpy(out_manufacturer, at, (size_t) (dash - at));\n        at = dash + 1;\n    }\n\n    // everything after the dash becomes the serial number\n    size_t serial_length = strlen(at);\n    if (serial_length == 0) {\n        demo_log(WARNING, \"empty serial number part of endpoint name\");\n        result = avs_simple_snprintf(out_serial, serial_size, \"000001\");\n        assert(result >= 0);\n    } else {\n        AVS_ASSERT(serial_length < serial_size,\n                   \"serial number part of endpoint name too long\");\n        strcpy(out_serial, at);\n    }\n\n    demo_log(DEBUG, \"manufacturer: %s; serial number: %s\", out_manufacturer,\n             out_serial);\n    (void) result;\n}\n\nconst anjay_dm_object_def_t **device_object_create(const char *endpoint_name) {\n    dev_repr_t *repr = (dev_repr_t *) avs_calloc(1, sizeof(dev_repr_t));\n    if (!repr) {\n        return NULL;\n    }\n\n    repr->def = &DEVICE;\n    repr->last_error = DEV_ERR_NO_ERROR;\n\n    dev_instance_reset_impl(repr);\n    extract_device_info(endpoint_name, repr->manufacturer,\n                        sizeof(repr->manufacturer), repr->serial_number,\n                        sizeof(repr->serial_number));\n\n    return &repr->def;\n}\n\nvoid device_object_release(const anjay_dm_object_def_t **def) {\n    if (def) {\n        avs_free(get_dev(def));\n    }\n}\n\nvoid device_notify_time_dependent(anjay_t *anjay,\n                                  const anjay_dm_object_def_t **def) {\n    anjay_notify_changed(anjay, (*def)->oid, 0, DEV_RES_POWER_SOURCE_VOLTAGE);\n    anjay_notify_changed(anjay, (*def)->oid, 0, DEV_RES_POWER_SOURCE_CURRENT);\n    anjay_notify_changed(anjay, (*def)->oid, 0, DEV_RES_CURRENT_TIME);\n}\n"
  },
  {
    "path": "demo/objects/download_diagnostics.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include \"../demo_utils.h\"\n#include \"../objects.h\"\n#include <assert.h>\n#include <inttypes.h>\n#include <string.h>\n#include <time.h>\n#include <unistd.h>\n\ntypedef enum {\n    DOWNLOAD_DIAG_STATE = 0,\n    DOWNLOAD_DIAG_URL = 1,\n    DOWNLOAD_DIAG_ROM_TIME_US = 2,\n    /* begin of transmission time */\n    DOWNLOAD_DIAG_BOM_TIME_US = 3,\n    /* end of transmission time */\n    DOWNLOAD_DIAG_EOM_TIME_US = 4,\n    /* total number of bytes transmitted between BOM_TIME and EOM_TIME */\n    DOWNLOAD_DIAG_TOTAL_BYTES = 5,\n    DOWNLOAD_DIAG_RUN = 6\n} download_diag_res_t;\n\ntypedef enum {\n    DIAG_STATE_NONE = 0,\n    DIAG_STATE_REQUESTED = 1,\n    DIAG_STATE_COMPLETED = 2,\n    DIAG_STATE_TRANSFER_FAILED = 3\n} download_diag_state_t;\n\ntypedef struct {\n    avs_time_real_t beg;\n    avs_time_real_t end;\n    size_t bytes_received;\n} download_diag_stats_t;\n\ntypedef struct {\n    const anjay_dm_object_def_t *def;\n    char download_url[1024];\n    anjay_download_handle_t dl_handle;\n    download_diag_stats_t stats;\n    download_diag_state_t state;\n} download_diag_repr_t;\n\nstatic download_diag_repr_t *\nget_diag(const anjay_dm_object_def_t *const *obj_ptr) {\n    assert(obj_ptr);\n    return AVS_CONTAINER_OF(obj_ptr, download_diag_repr_t, def);\n}\n\nstatic void set_state(anjay_t *anjay,\n                      download_diag_repr_t *repr,\n                      download_diag_state_t state) {\n    (void) anjay;\n    if (repr->state != state) {\n        repr->state = state;\n        anjay_notify_changed(anjay, DEMO_OID_DOWNLOAD_DIAG, 0,\n                             DOWNLOAD_DIAG_STATE);\n    }\n}\n\nstatic void reset_diagnostic(download_diag_repr_t *repr) {\n    memset(repr->download_url, 0, sizeof(repr->download_url));\n}\n\nstatic void update_times(download_diag_repr_t *repr) {\n    avs_time_real_t now = avs_time_real_now();\n    if (!avs_time_real_valid(repr->stats.beg)) {\n        repr->stats.beg = now;\n    }\n    repr->stats.end = now;\n}\n\nstatic avs_error_t dl_block_callback(anjay_t *anjay,\n                                     const uint8_t *data,\n                                     size_t data_size,\n                                     const anjay_etag_t *etag,\n                                     void *user_data) {\n    (void) anjay;\n    (void) data;\n    (void) etag;\n    download_diag_repr_t *repr = (download_diag_repr_t *) user_data;\n    repr->stats.bytes_received += data_size;\n    update_times(repr);\n    return AVS_OK;\n}\n\nstatic void dl_finish_callback(anjay_t *anjay,\n                               anjay_download_status_t status,\n                               void *user_data) {\n    download_diag_repr_t *repr = (download_diag_repr_t *) user_data;\n    update_times(repr);\n    if (status.result == ANJAY_DOWNLOAD_FINISHED) {\n        set_state(anjay, repr, DIAG_STATE_COMPLETED);\n    } else {\n        set_state(anjay, repr, DIAG_STATE_TRANSFER_FAILED);\n    }\n}\n\nstatic int diag_download_run(anjay_t *anjay, download_diag_repr_t *repr) {\n    if (repr->dl_handle) {\n        demo_log(ERROR, \"download diagnostic already in progress\");\n        return -1;\n    }\n\n    const anjay_download_config_t config = {\n        .url = repr->download_url,\n        .on_next_block = dl_block_callback,\n        .on_download_finished = dl_finish_callback,\n        .user_data = repr\n    };\n    if (avs_is_err(anjay_download(anjay, &config, &repr->dl_handle))) {\n        set_state(anjay, repr, DIAG_STATE_TRANSFER_FAILED);\n        demo_log(ERROR, \"cannot schedule download diagnostic\");\n        return -1;\n    }\n    repr->stats.beg = AVS_TIME_REAL_INVALID;\n    repr->stats.end = AVS_TIME_REAL_INVALID;\n    repr->stats.bytes_received = 0;\n    set_state(anjay, repr, DIAG_STATE_REQUESTED);\n    return 0;\n}\n\nstatic int diag_resource_execute(anjay_t *anjay,\n                                 const anjay_dm_object_def_t *const *obj_ptr,\n                                 anjay_iid_t iid,\n                                 anjay_iid_t rid,\n                                 anjay_execute_ctx_t *ctx) {\n    (void) anjay;\n    (void) iid;\n    (void) rid;\n    (void) ctx;\n    assert(rid == DOWNLOAD_DIAG_RUN);\n    return diag_download_run(anjay, get_diag(obj_ptr));\n}\n\nstatic int read_stats_resource(anjay_rid_t rid,\n                               download_diag_repr_t *repr,\n                               anjay_output_ctx_t *ctx) {\n    if (repr->state != DIAG_STATE_COMPLETED) {\n        return ANJAY_ERR_INTERNAL;\n    }\n\n    int64_t tmp;\n    switch ((download_diag_res_t) rid) {\n    case DOWNLOAD_DIAG_ROM_TIME_US:\n    case DOWNLOAD_DIAG_BOM_TIME_US:\n        if (avs_time_real_to_scalar(&tmp, AVS_TIME_US, repr->stats.beg)) {\n            return ANJAY_ERR_INTERNAL;\n        }\n        return anjay_ret_i64(ctx, tmp);\n    case DOWNLOAD_DIAG_EOM_TIME_US:\n        if (avs_time_real_to_scalar(&tmp, AVS_TIME_US, repr->stats.end)) {\n            return ANJAY_ERR_INTERNAL;\n        }\n        return anjay_ret_i64(ctx, tmp);\n    case DOWNLOAD_DIAG_TOTAL_BYTES:\n        tmp = INT64_MAX;\n        if (repr->stats.bytes_received < INT64_MAX) {\n            tmp = (int64_t) repr->stats.bytes_received;\n        }\n        return anjay_ret_i64(ctx, tmp);\n    default:\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\nstatic int diag_list_resources(anjay_t *anjay,\n                               const anjay_dm_object_def_t *const *obj_ptr,\n                               anjay_iid_t iid,\n                               anjay_dm_resource_list_ctx_t *ctx) {\n    (void) anjay;\n    (void) iid;\n\n    anjay_dm_emit_res(ctx, DOWNLOAD_DIAG_STATE, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, DOWNLOAD_DIAG_URL, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_resource_presence_t diag_stat_presence =\n            (get_diag(obj_ptr)->state == DIAG_STATE_COMPLETED)\n                    ? ANJAY_DM_RES_PRESENT\n                    : ANJAY_DM_RES_ABSENT;\n    anjay_dm_emit_res(ctx, DOWNLOAD_DIAG_ROM_TIME_US, ANJAY_DM_RES_R,\n                      diag_stat_presence);\n    anjay_dm_emit_res(ctx, DOWNLOAD_DIAG_BOM_TIME_US, ANJAY_DM_RES_R,\n                      diag_stat_presence);\n    anjay_dm_emit_res(ctx, DOWNLOAD_DIAG_EOM_TIME_US, ANJAY_DM_RES_R,\n                      diag_stat_presence);\n    anjay_dm_emit_res(ctx, DOWNLOAD_DIAG_TOTAL_BYTES, ANJAY_DM_RES_R,\n                      diag_stat_presence);\n    anjay_dm_emit_res(ctx, DOWNLOAD_DIAG_RUN, ANJAY_DM_RES_E,\n                      ANJAY_DM_RES_PRESENT);\n    return 0;\n}\n\nstatic int diag_resource_read(anjay_t *anjay,\n                              const anjay_dm_object_def_t *const *obj_ptr,\n                              anjay_iid_t iid,\n                              anjay_rid_t rid,\n                              anjay_riid_t riid,\n                              anjay_output_ctx_t *ctx) {\n    (void) anjay;\n    (void) iid;\n    (void) riid;\n    assert(riid == ANJAY_ID_INVALID);\n    download_diag_repr_t *repr = get_diag(obj_ptr);\n    switch ((download_diag_res_t) rid) {\n    case DOWNLOAD_DIAG_STATE:\n        return anjay_ret_i32(ctx, (int32_t) repr->state);\n    case DOWNLOAD_DIAG_URL:\n        return anjay_ret_string(ctx, repr->download_url);\n    case DOWNLOAD_DIAG_ROM_TIME_US:\n    case DOWNLOAD_DIAG_BOM_TIME_US:\n    case DOWNLOAD_DIAG_EOM_TIME_US:\n    case DOWNLOAD_DIAG_TOTAL_BYTES:\n        return read_stats_resource(rid, repr, ctx);\n    default:\n        AVS_UNREACHABLE(\n                \"Read handler called on unknown or non-readable resource\");\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\nstatic int diag_resource_write(anjay_t *anjay,\n                               const anjay_dm_object_def_t *const *obj_ptr,\n                               anjay_iid_t iid,\n                               anjay_rid_t rid,\n                               anjay_riid_t riid,\n                               anjay_input_ctx_t *ctx) {\n    (void) anjay;\n    (void) obj_ptr;\n    (void) iid;\n    (void) riid;\n    assert(riid == ANJAY_ID_INVALID);\n    download_diag_repr_t *repr = get_diag(obj_ptr);\n    switch ((download_diag_res_t) rid) {\n    case DOWNLOAD_DIAG_URL: {\n        if (repr->state == DIAG_STATE_REQUESTED) {\n            demo_log(ERROR,\n                     \"Cancelling a diagnostic in progress is not supported\");\n            return ANJAY_ERR_BAD_REQUEST;\n        }\n        reset_diagnostic(repr);\n        int result = anjay_get_string(ctx, repr->download_url,\n                                      sizeof(repr->download_url));\n        if (result < 0 || result == ANJAY_BUFFER_TOO_SHORT) {\n            reset_diagnostic(repr);\n            result = result < 0 ? result : ANJAY_ERR_BAD_REQUEST;\n        }\n        return result;\n    }\n    default:\n        // Bootstrap Server may try to write to other resources,\n        // so no AVS_UNREACHABLE() here\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\nstatic const anjay_dm_object_def_t DOWNLOAD_DIAG = {\n    .oid = DEMO_OID_DOWNLOAD_DIAG,\n    .handlers = {\n        .list_instances = anjay_dm_list_instances_SINGLE,\n        .list_resources = diag_list_resources,\n        .resource_execute = diag_resource_execute,\n        .resource_read = diag_resource_read,\n        .resource_write = diag_resource_write,\n        .transaction_begin = anjay_dm_transaction_NOOP,\n        .transaction_validate = anjay_dm_transaction_NOOP,\n        .transaction_commit = anjay_dm_transaction_NOOP,\n        .transaction_rollback = anjay_dm_transaction_NOOP\n    }\n};\n\nconst anjay_dm_object_def_t **download_diagnostics_object_create(void) {\n    download_diag_repr_t *repr =\n            (download_diag_repr_t *) avs_calloc(1,\n                                                sizeof(download_diag_repr_t));\n    if (!repr) {\n        return NULL;\n    }\n    repr->def = &DOWNLOAD_DIAG;\n    return &repr->def;\n}\n\nvoid download_diagnostics_object_release(const anjay_dm_object_def_t **def) {\n    if (def) {\n        download_diag_repr_t *repr = get_diag(def);\n        assert(!repr->dl_handle);\n        avs_free(repr);\n    }\n}\n"
  },
  {
    "path": "demo/objects/event_log.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <assert.h>\n#include <inttypes.h>\n#include <stdbool.h>\n\n#include <anjay/anjay.h>\n#include <avsystem/commons/avs_defs.h>\n#include <avsystem/commons/avs_memory.h>\n\n#include \"../objects.h\"\n\n/**\n * LogClass: RW, Single, Optional\n * type: integer, range: 255, unit: N/A\n * Define the Log Event Class: 0: generic (default)  1: system   2:\n * security  3: event   4: trace   5: panic   6: charging [7-99]:\n * reserved [100-255]: vendor specific\n */\n#define RID_LOGCLASS 4010\n\n/**\n * LogStart: E, Single, Optional\n * type: N/A, range: N/A, unit: N/A\n * Actions: a) Start data collection(DC) b) LogStatus is set to 0\n * (running) c) DC is emptied (default) or extended according arg'0'\n * value  Arguments definitions are described in the table below.\n */\n#define RID_LOGSTART 4011\n\n/**\n * LogStop: E, Single, Optional\n * type: N/A, range: N/A, unit: N/A\n * Actions: a) Stop data collection(DC) b)  1st LSB of LogStatus is set\n * to \"1\"(stopped) c) DC is kept (default) or emptied according arg'0'\n * value Arguments definitions are described in the table below.\n */\n#define RID_LOGSTOP 4012\n\n/**\n * LogStatus: R, Single, Optional\n * type: integer, range: 8-Bits, unit: N/A\n * Data Collection process status: Each bit of this Resource Instance\n * value defines specific status: 1st LSB 0=running, 1=stopped 2nd LSB\n * 1=LogData contains Valid Data 0=LogData doesn’t contain Valid Data 3rd\n * LSB 1=Error occurred during Data Collection 0=No error [4th -7th ]\n * LSB:reserved 8th LSB: vendor specific.\n */\n#define RID_LOGSTATUS 4013\n\n/**\n * LogData: R, Single, Mandatory\n * type: opaque, range: N/A, unit: N/A\n * Read Access on that Resource returns the Data Collection associated to\n * the current Object Instance.\n */\n#define RID_LOGDATA 4014\n\n/**\n * LogDataFormat: RW, Single, Optional\n * type: integer, range: 255, unit: N/A\n * when set by the Server, this Resource indicates to the Client, what is\n * the Server preferred data format to use when the LogData Resource is\n * returned . when retrieved by the Server, this Resource indicates which\n * specific data format is used when the LogData Resource is returned to\n * the Server  0  or Resource not present : no specific data format\n * (sequence of bytes) 1 : OMA-LwM2M TLV format 2 : OMA-LwM2M JSON format\n * 3:  OMA-LwM2M CBOR format [4..99] reserved [100..255] vendor specific\n * data format\n */\n#define RID_LOGDATAFORMAT 4015\n\n#define MIN_LOG_CLASS 0\n#define MAX_LOG_CLASS 255\n\n#define MIN_LOG_DATA_FORMAT 0\n#define MAX_LOG_DATA_FORMAT 255\n\ntypedef struct event_log_struct {\n    const anjay_dm_object_def_t *def;\n\n    avs_sched_handle_t stop_log_job_handle;\n\n    bool log_running;\n    bool log_data_valid;\n    uint8_t log_class;\n    uint8_t log_data[1024];\n    size_t log_data_size;\n} event_log_t;\n\nstatic inline event_log_t *\nget_obj(const anjay_dm_object_def_t *const *obj_ptr) {\n    assert(obj_ptr);\n    return AVS_CONTAINER_OF(obj_ptr, event_log_t, def);\n}\n\nstatic int instance_reset(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr,\n                          anjay_iid_t iid) {\n    (void) anjay;\n    (void) iid;\n\n    event_log_t *obj = get_obj(obj_ptr);\n    assert(iid == 0);\n\n    obj->log_running = false;\n    obj->log_data_valid = false;\n    obj->log_class = 0;\n    obj->log_data_size = 0;\n    avs_sched_del(&obj->stop_log_job_handle);\n\n    return 0;\n}\n\nstatic int list_resources(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr,\n                          anjay_iid_t iid,\n                          anjay_dm_resource_list_ctx_t *ctx) {\n    (void) anjay;\n    (void) obj_ptr;\n    (void) iid;\n\n    anjay_dm_emit_res(ctx, RID_LOGCLASS, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, RID_LOGSTART, ANJAY_DM_RES_E, ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, RID_LOGSTOP, ANJAY_DM_RES_E, ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, RID_LOGSTATUS, ANJAY_DM_RES_R, ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, RID_LOGDATA, ANJAY_DM_RES_R, ANJAY_DM_RES_PRESENT);\n    return 0;\n}\n\nstatic int resource_read(anjay_t *anjay,\n                         const anjay_dm_object_def_t *const *obj_ptr,\n                         anjay_iid_t iid,\n                         anjay_rid_t rid,\n                         anjay_riid_t riid,\n                         anjay_output_ctx_t *ctx) {\n    (void) anjay;\n    (void) iid;\n    (void) riid;\n\n    event_log_t *obj = get_obj(obj_ptr);\n    assert(iid == 0);\n\n    switch (rid) {\n    case RID_LOGCLASS:\n        assert(riid == ANJAY_ID_INVALID);\n        return anjay_ret_i32(ctx, obj->log_class);\n\n    case RID_LOGSTATUS: {\n        assert(riid == ANJAY_ID_INVALID);\n        const int32_t status = (obj->log_data_valid << 1) | !obj->log_running;\n        return anjay_ret_i32(ctx, status);\n    }\n\n    case RID_LOGDATA:\n        assert(riid == ANJAY_ID_INVALID);\n        return anjay_ret_bytes(ctx, obj->log_data, obj->log_data_size);\n\n    default:\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\nstatic int resource_write(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr,\n                          anjay_iid_t iid,\n                          anjay_rid_t rid,\n                          anjay_riid_t riid,\n                          anjay_input_ctx_t *ctx) {\n    (void) anjay;\n    (void) iid;\n    (void) riid;\n\n    event_log_t *obj = get_obj(obj_ptr);\n    assert(iid == 0);\n\n    switch (rid) {\n    case RID_LOGCLASS: {\n        assert(riid == ANJAY_ID_INVALID);\n        int32_t value;\n        int retval = anjay_get_i32(ctx, &value);\n        if (retval) {\n            return retval;\n        }\n        if (value < MIN_LOG_CLASS || value > MAX_LOG_CLASS) {\n            return ANJAY_ERR_BAD_REQUEST;\n        }\n        obj->log_class = (uint8_t) value;\n        return 0;\n    }\n\n    case RID_LOGDATAFORMAT: {\n        assert(riid == ANJAY_ID_INVALID);\n        int32_t value;\n        int retval = anjay_get_i32(ctx, &value);\n        if (retval) {\n            return retval;\n        }\n        if (value < MIN_LOG_DATA_FORMAT || value > MAX_LOG_DATA_FORMAT) {\n            return ANJAY_ERR_BAD_REQUEST;\n        }\n        return 0;\n    }\n\n    default:\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\nstatic int get_next_argument_value(anjay_execute_ctx_t *arg_ctx,\n                                   int32_t *out_number,\n                                   int64_t *out_value,\n                                   bool *out_has_value) {\n    int result = anjay_execute_get_next_arg(arg_ctx, out_number, out_has_value);\n    if (result) {\n        return result;\n    }\n\n    if (!*out_has_value) {\n        return 0;\n    }\n\n    char value[AVS_INT_STR_BUF_SIZE(int64_t)];\n    if ((result = anjay_execute_get_arg_value(arg_ctx, NULL, value,\n                                              sizeof(value)))\n            < 0) {\n        return result;\n    }\n\n    int num_read;\n    if (sscanf(value, \"%\" SCNd64 \"%n\", out_value, &num_read) != 1\n            || value[num_read] != '\\0' || *out_value < 0) {\n        return ANJAY_ERR_BAD_REQUEST;\n    }\n    return 0;\n}\n\ntypedef struct {\n    event_log_t *event_log_obj;\n} disable_log_arg_t;\n\nstatic void disable_log(avs_sched_t *sched, const void *arg_) {\n    (void) sched;\n    const disable_log_arg_t *arg = (const disable_log_arg_t *) arg_;\n    arg->event_log_obj->log_running = false;\n}\n\nstatic int parse_logstart_arguments(anjay_execute_ctx_t *arg_ctx,\n                                    bool *out_clear_log,\n                                    int64_t *out_disable_log_delay) {\n    // Logs are cleared by default.\n    *out_clear_log = true;\n    *out_disable_log_delay = 0;\n    int32_t arg_number;\n    int64_t arg_value;\n    bool has_value;\n    int result;\n    while (!(result = get_next_argument_value(arg_ctx, &arg_number, &arg_value,\n                                              &has_value))) {\n        if (has_value) {\n            switch (arg_number) {\n            case 0:\n                if (arg_value == 1) {\n                    *out_clear_log = false;\n                } else if (arg_value) {\n                    return ANJAY_ERR_BAD_REQUEST;\n                }\n                break;\n\n            case 1:\n                if (arg_value > 0) {\n                    *out_disable_log_delay = arg_value;\n                } else if (arg_value < 0) {\n                    return ANJAY_ERR_BAD_REQUEST;\n                }\n                break;\n\n            default:\n                return ANJAY_ERR_BAD_REQUEST;\n            }\n        }\n    }\n    return (result == ANJAY_EXECUTE_GET_ARG_END) ? 0 : result;\n}\n\nstatic int parse_logstop_arguments(anjay_execute_ctx_t *arg_ctx,\n                                   bool *out_clear_log) {\n    // Logs are not cleared by default.\n    *out_clear_log = false;\n    int32_t arg_number;\n    int64_t arg_value;\n    bool has_value;\n    int result;\n    while (!(result = get_next_argument_value(arg_ctx, &arg_number, &arg_value,\n                                              &has_value))) {\n        if (has_value) {\n            switch (arg_number) {\n            case 0:\n                if (arg_value == 1) {\n                    *out_clear_log = true;\n                } else if (arg_value) {\n                    return ANJAY_ERR_BAD_REQUEST;\n                }\n                break;\n\n            default:\n                return ANJAY_ERR_BAD_REQUEST;\n            }\n        }\n    }\n    return (result == ANJAY_EXECUTE_GET_ARG_END) ? 0 : result;\n}\n\nstatic int resource_execute(anjay_t *anjay,\n                            const anjay_dm_object_def_t *const *obj_ptr,\n                            anjay_iid_t iid,\n                            anjay_rid_t rid,\n                            anjay_execute_ctx_t *arg_ctx) {\n    (void) arg_ctx;\n    (void) anjay;\n    (void) iid;\n\n    event_log_t *obj = get_obj(obj_ptr);\n    assert(iid == 0);\n\n    switch (rid) {\n    case RID_LOGSTART: {\n        bool clear_log;\n        int64_t disable_log_delay;\n        int result = parse_logstart_arguments(arg_ctx, &clear_log,\n                                              &disable_log_delay);\n        if (result) {\n            return result;\n        }\n        avs_sched_del(&obj->stop_log_job_handle);\n        if (disable_log_delay) {\n            if (AVS_SCHED_DELAYED(anjay_get_scheduler(anjay),\n                                  &obj->stop_log_job_handle,\n                                  avs_time_duration_from_scalar(\n                                          disable_log_delay, AVS_TIME_S),\n                                  disable_log,\n                                  &(disable_log_arg_t) {\n                                      .event_log_obj = obj\n                                  },\n                                  sizeof(disable_log_arg_t))) {\n                return -1;\n            }\n        }\n        if (clear_log) {\n            obj->log_data_size = 0;\n        }\n        obj->log_running = true;\n        return 0;\n    }\n\n    case RID_LOGSTOP: {\n        bool clear_log;\n        int result = parse_logstop_arguments(arg_ctx, &clear_log);\n        if (result) {\n            return result;\n        }\n        if (clear_log) {\n            obj->log_data_size = 0;\n        }\n        avs_sched_del(&obj->stop_log_job_handle);\n        obj->log_running = false;\n        return 0;\n    }\n\n    default:\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\nstatic const anjay_dm_object_def_t OBJ_DEF = {\n    .oid = 20,\n    .handlers = {\n        .list_instances = anjay_dm_list_instances_SINGLE,\n        .instance_reset = instance_reset,\n\n        .list_resources = list_resources,\n        .resource_read = resource_read,\n        .resource_write = resource_write,\n        .resource_execute = resource_execute,\n\n        // TODO: implement these if transactional write/create is required\n        .transaction_begin = anjay_dm_transaction_NOOP,\n        .transaction_validate = anjay_dm_transaction_NOOP,\n        .transaction_commit = anjay_dm_transaction_NOOP,\n        .transaction_rollback = anjay_dm_transaction_NOOP\n    }\n};\n\nconst anjay_dm_object_def_t **event_log_object_create(void) {\n    event_log_t *obj = (event_log_t *) avs_calloc(1, sizeof(event_log_t));\n    if (!obj) {\n        return NULL;\n    }\n\n    obj->def = &OBJ_DEF;\n    return &obj->def;\n}\n\nvoid event_log_object_release(const anjay_dm_object_def_t **def) {\n    if (def) {\n        event_log_t *obj = get_obj(def);\n        avs_free(obj);\n    }\n}\n\nint event_log_write_data(anjay_t *anjay,\n                         const anjay_dm_object_def_t *const *obj_ptr,\n                         const void *data,\n                         size_t data_size) {\n    if (!obj_ptr) {\n        return -1;\n    }\n\n    event_log_t *obj = get_obj(obj_ptr);\n    if (sizeof(obj->log_data) < data_size) {\n        return -1;\n    }\n\n    memcpy(obj->log_data, data, data_size);\n    obj->log_data_size = data_size;\n    obj->log_data_valid = true;\n    const anjay_iid_t iid = 0;\n    anjay_notify_changed(anjay, (*obj_ptr)->oid, iid, RID_LOGDATA);\n    return 0;\n}\n"
  },
  {
    "path": "demo/objects/ext_dev_info.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include \"../demo_utils.h\"\n#include \"../objects.h\"\n\n#include <assert.h>\n#include <time.h>\n\n#include <anjay/stats.h>\n\n#define EXT_DEV_RES_OBU_ID 0                        // string\n#define EXT_DEV_RES_PLATE_NUMBER 1                  // string\n#define EXT_DEV_RES_IMEI 2                          // string\n#define EXT_DEV_RES_IMSI 3                          // string\n#define EXT_DEV_RES_ICCID 4                         // string\n#define EXT_DEV_RES_GPRS_RSSI 5                     // int\n#define EXT_DEV_RES_GPRS_PLMN 6                     // int\n#define EXT_DEV_RES_GPRS_ULMODULATION 7             // string\n#define EXT_DEV_RES_GPRS_DLMODULATION 8             // string\n#define EXT_DEV_RES_GPRS_ULFREQUENCY 9              // int\n#define EXT_DEV_RES_GPRS_DLFREQUENCY 10             // int\n#define EXT_DEV_RES_RX_BYTES 11                     // uint64\n#define EXT_DEV_RES_TX_BYTES 12                     // uint64\n#define EXT_DEV_RES_NUM_INCOMING_RETRANSMISSIONS 13 // uint64\n#define EXT_DEV_RES_NUM_OUTGOING_RETRANSMISSIONS 14 // uint64\n#define EXT_DEV_RES_UPTIME 15                       // double\n\ntypedef struct {\n    const anjay_dm_object_def_t *def;\n    avs_time_monotonic_t init_time;\n} extdev_repr_t;\n\nstatic inline extdev_repr_t *\nget_extdev(const anjay_dm_object_def_t *const *obj_ptr) {\n    assert(obj_ptr);\n    return AVS_CONTAINER_OF(obj_ptr, extdev_repr_t, def);\n}\n\nstatic int generate_fake_rssi_value(void) {\n    return 50 + (int) (time_to_rand() % 20);\n}\n\nstatic int dev_resources(anjay_t *anjay,\n                         const anjay_dm_object_def_t *const *obj_ptr,\n                         anjay_iid_t iid,\n                         anjay_dm_resource_list_ctx_t *ctx) {\n    (void) anjay;\n    (void) obj_ptr;\n    (void) iid;\n\n    anjay_dm_emit_res(ctx, EXT_DEV_RES_OBU_ID, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, EXT_DEV_RES_PLATE_NUMBER, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, EXT_DEV_RES_IMEI, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, EXT_DEV_RES_IMSI, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, EXT_DEV_RES_ICCID, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, EXT_DEV_RES_GPRS_RSSI, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, EXT_DEV_RES_GPRS_PLMN, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, EXT_DEV_RES_GPRS_ULMODULATION, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, EXT_DEV_RES_GPRS_DLMODULATION, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, EXT_DEV_RES_GPRS_ULFREQUENCY, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, EXT_DEV_RES_GPRS_DLFREQUENCY, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, EXT_DEV_RES_RX_BYTES, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, EXT_DEV_RES_TX_BYTES, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, EXT_DEV_RES_NUM_INCOMING_RETRANSMISSIONS,\n                      ANJAY_DM_RES_R, ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, EXT_DEV_RES_NUM_OUTGOING_RETRANSMISSIONS,\n                      ANJAY_DM_RES_R, ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, EXT_DEV_RES_UPTIME, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_PRESENT);\n    return 0;\n}\n\nstatic int dev_read(anjay_t *anjay,\n                    const anjay_dm_object_def_t *const *obj_ptr,\n                    anjay_iid_t iid,\n                    anjay_rid_t rid,\n                    anjay_riid_t riid,\n                    anjay_output_ctx_t *ctx) {\n    (void) anjay;\n    (void) iid;\n    (void) riid;\n    assert(riid == ANJAY_ID_INVALID);\n\n    switch (rid) {\n    case EXT_DEV_RES_OBU_ID:\n        return anjay_ret_string(ctx, \"Dummy_OBU_ID\");\n    case EXT_DEV_RES_PLATE_NUMBER:\n        return anjay_ret_string(ctx, \"PL 473N0\");\n    case EXT_DEV_RES_IMEI:\n        return anjay_ret_string(ctx, \"01-345678-901234\");\n    case EXT_DEV_RES_IMSI:\n        return anjay_ret_string(ctx, \"26000007\");\n    case EXT_DEV_RES_ICCID:\n        return anjay_ret_string(ctx, \"8926000000073\");\n    case EXT_DEV_RES_GPRS_RSSI:\n        return anjay_ret_i32(ctx, generate_fake_rssi_value());\n    case EXT_DEV_RES_GPRS_PLMN:\n        return anjay_ret_i32(ctx, 26001);\n    case EXT_DEV_RES_GPRS_ULMODULATION:\n        return anjay_ret_string(ctx, \"GMSK\");\n    case EXT_DEV_RES_GPRS_DLMODULATION:\n        return anjay_ret_string(ctx, \"GMSK\");\n    case EXT_DEV_RES_GPRS_ULFREQUENCY:\n        return anjay_ret_i32(ctx, 1950);\n    case EXT_DEV_RES_GPRS_DLFREQUENCY:\n        return anjay_ret_i32(ctx, 2140);\n    case EXT_DEV_RES_RX_BYTES:\n        return anjay_ret_i64(ctx, (int64_t) anjay_get_rx_bytes(anjay));\n    case EXT_DEV_RES_TX_BYTES:\n        return anjay_ret_i64(ctx, (int64_t) anjay_get_tx_bytes(anjay));\n    case EXT_DEV_RES_NUM_INCOMING_RETRANSMISSIONS:\n        return anjay_ret_i64(\n                ctx, (int64_t) anjay_get_num_incoming_retransmissions(anjay));\n    case EXT_DEV_RES_NUM_OUTGOING_RETRANSMISSIONS:\n        return anjay_ret_i64(\n                ctx, (int64_t) anjay_get_num_outgoing_retransmissions(anjay));\n    case EXT_DEV_RES_UPTIME: {\n        avs_time_duration_t diff =\n                avs_time_monotonic_diff(avs_time_monotonic_now(),\n                                        get_extdev(obj_ptr)->init_time);\n\n        return anjay_ret_double(ctx, (double) diff.seconds\n                                             + (double) diff.nanoseconds / 1e9);\n    }\n    default:\n        AVS_UNREACHABLE(\"Read handler called on unknown resource\");\n        return ANJAY_ERR_NOT_IMPLEMENTED;\n    }\n}\n\nstatic const anjay_dm_object_def_t EXT_DEV_INFO = {\n    .oid = DEMO_OID_EXT_DEV_INFO,\n    .handlers = {\n        .list_instances = anjay_dm_list_instances_SINGLE,\n        .list_resources = dev_resources,\n        .resource_read = dev_read\n    }\n};\n\nconst anjay_dm_object_def_t **ext_dev_info_object_create(void) {\n    extdev_repr_t *repr =\n            (extdev_repr_t *) avs_calloc(1, sizeof(extdev_repr_t));\n    if (!repr) {\n        return NULL;\n    }\n\n    repr->def = &EXT_DEV_INFO;\n    repr->init_time = avs_time_monotonic_now();\n\n    return &repr->def;\n}\n\nvoid ext_dev_info_object_release(const anjay_dm_object_def_t **def) {\n    if (def) {\n        avs_free(get_extdev(def));\n    }\n}\n\nvoid ext_dev_info_notify_time_dependent(anjay_t *anjay,\n                                        const anjay_dm_object_def_t **def) {\n    anjay_notify_changed(anjay, (*def)->oid, 0, EXT_DEV_RES_GPRS_RSSI);\n    anjay_notify_changed(anjay, (*def)->oid, 0, EXT_DEV_RES_UPTIME);\n}\n"
  },
  {
    "path": "demo/objects/gateway_end_devices/binary_app_data_container.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <assert.h>\n#include <inttypes.h>\n#include <stdbool.h>\n\n#include <anjay/anjay.h>\n#include <anjay/lwm2m_gateway.h>\n#include <avsystem/commons/avs_defs.h>\n#include <avsystem/commons/avs_list.h>\n#include <avsystem/commons/avs_memory.h>\n\n#include \"../../demo_utils.h\"\n#include \"binary_app_data_container.h\"\n\n/**\n * Data: RW, Multiple, Mandatory\n * type: opaque, range: N/A, unit: N/A\n * Indicates the application data content.\n */\n#define RID_DATA 0\n\n/**\n * Data Priority: RW, Single, Optional\n * type: integer, range: 1 bytes, unit: N/A\n * Indicates the Application data priority: 0:Immediate 1:BestEffort\n * 2:Latest 3-100: Reserved for future use. 101-254: Proprietary mode.\n */\n#define RID_DATA_PRIORITY 1\n\n/**\n * Data Creation Time: RW, Single, Optional\n * type: time, range: N/A, unit: N/A\n * Indicates the Data instance creation timestamp.\n */\n#define RID_DATA_CREATION_TIME 2\n\n/**\n * Data Description: RW, Single, Optional\n * type: string, range: 32 bytes, unit: N/A\n * Indicates the data description. e.g. \"meter reading\".\n */\n#define RID_DATA_DESCRIPTION 3\n\n/**\n * Data Format: RW, Single, Optional\n * type: string, range: 32 bytes, unit: N/A\n * Indicates the format of the Application Data. e.g. YG-Meter-Water-\n * Reading UTF8-string\n */\n#define RID_DATA_FORMAT 4\n\n/**\n * App ID: RW, Single, Optional\n * type: integer, range: 2 bytes, unit: N/A\n * Indicates the destination Application ID.\n */\n#define RID_APP_ID 5\n\n#define MAX_BINARY_DATA_SIZE 1024\n\ntypedef struct {\n    anjay_riid_t riid;\n    uint8_t data[MAX_BINARY_DATA_SIZE];\n    size_t data_length;\n} data_resource_instance_t;\n\ntypedef struct binary_app_data_container_instance_struct {\n    anjay_iid_t iid;\n    AVS_LIST(data_resource_instance_t) data_list;\n} binary_app_data_container_instance_t;\n\ntypedef struct binary_app_data_container_struct {\n    const anjay_dm_object_def_t *def;\n    AVS_LIST(binary_app_data_container_instance_t) instances;\n    AVS_LIST(binary_app_data_container_instance_t) saved_instances;\n    anjay_iid_t end_device_iid;\n} binary_app_data_container_t;\n\nstatic inline binary_app_data_container_t *\nget_obj(const anjay_dm_object_def_t *const *obj_ptr) {\n    assert(obj_ptr);\n    return AVS_CONTAINER_OF(obj_ptr, binary_app_data_container_t, def);\n}\n\nstatic binary_app_data_container_instance_t *\nfind_instance(const binary_app_data_container_t *obj, anjay_iid_t iid) {\n    AVS_LIST(binary_app_data_container_instance_t) it;\n    AVS_LIST_FOREACH(it, obj->instances) {\n        if (it->iid == iid) {\n            return it;\n        } else if (it->iid > iid) {\n            break;\n        }\n    }\n\n    return NULL;\n}\n\nstatic int list_instances(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr,\n                          anjay_dm_list_ctx_t *ctx) {\n    (void) anjay;\n\n    AVS_LIST(binary_app_data_container_instance_t) it;\n    AVS_LIST_FOREACH(it, get_obj(obj_ptr)->instances) {\n        anjay_dm_emit(ctx, it->iid);\n    }\n\n    return 0;\n}\n\nstatic int init_instance(binary_app_data_container_instance_t *inst,\n                         anjay_iid_t iid) {\n    assert(iid != ANJAY_ID_INVALID);\n\n    inst->iid = iid;\n    return 0;\n}\n\nstatic void release_instance(binary_app_data_container_instance_t *inst) {\n    AVS_LIST_CLEAR(&inst->data_list);\n}\n\nstatic binary_app_data_container_instance_t *\nadd_instance(binary_app_data_container_t *obj, anjay_iid_t iid) {\n    assert(find_instance(obj, iid) == NULL);\n\n    AVS_LIST(binary_app_data_container_instance_t) created =\n            AVS_LIST_NEW_ELEMENT(binary_app_data_container_instance_t);\n    if (!created) {\n        return NULL;\n    }\n\n    int result = init_instance(created, iid);\n    if (result) {\n        AVS_LIST_CLEAR(&created);\n        return NULL;\n    }\n\n    AVS_LIST(binary_app_data_container_instance_t) *ptr;\n    AVS_LIST_FOREACH_PTR(ptr, &obj->instances) {\n        if ((*ptr)->iid > created->iid) {\n            break;\n        }\n    }\n\n    AVS_LIST_INSERT(ptr, created);\n    return created;\n}\n\nstatic int instance_create(anjay_t *anjay,\n                           const anjay_dm_object_def_t *const *obj_ptr,\n                           anjay_iid_t iid) {\n    (void) anjay;\n    binary_app_data_container_t *obj = get_obj(obj_ptr);\n\n    return add_instance(obj, iid) ? 0 : ANJAY_ERR_INTERNAL;\n}\n\nstatic int instance_remove(anjay_t *anjay,\n                           const anjay_dm_object_def_t *const *obj_ptr,\n                           anjay_iid_t iid) {\n    (void) anjay;\n    binary_app_data_container_t *obj = get_obj(obj_ptr);\n\n    AVS_LIST(binary_app_data_container_instance_t) *it;\n    AVS_LIST_FOREACH_PTR(it, &obj->instances) {\n        if ((*it)->iid == iid) {\n            release_instance(*it);\n            AVS_LIST_DELETE(it);\n            return 0;\n        } else if ((*it)->iid > iid) {\n            break;\n        }\n    }\n\n    assert(0);\n    return ANJAY_ERR_NOT_FOUND;\n}\n\nstatic int instance_reset(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr,\n                          anjay_iid_t iid) {\n    (void) anjay;\n\n    binary_app_data_container_t *obj = get_obj(obj_ptr);\n    binary_app_data_container_instance_t *inst = find_instance(obj, iid);\n    assert(inst);\n\n    AVS_LIST_CLEAR(&inst->data_list);\n    return 0;\n}\n\nstatic int list_resources(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr,\n                          anjay_iid_t iid,\n                          anjay_dm_resource_list_ctx_t *ctx) {\n    (void) anjay;\n    (void) obj_ptr;\n    (void) iid;\n\n    anjay_dm_emit_res(ctx, RID_DATA, ANJAY_DM_RES_RWM, ANJAY_DM_RES_PRESENT);\n    return 0;\n}\n\nstatic int resource_read(anjay_t *anjay,\n                         const anjay_dm_object_def_t *const *obj_ptr,\n                         anjay_iid_t iid,\n                         anjay_rid_t rid,\n                         anjay_riid_t riid,\n                         anjay_output_ctx_t *ctx) {\n    (void) anjay;\n\n    binary_app_data_container_t *obj = get_obj(obj_ptr);\n    binary_app_data_container_instance_t *inst = find_instance(obj, iid);\n    assert(inst);\n\n    switch (rid) {\n    case RID_DATA: {\n        data_resource_instance_t *it;\n        AVS_LIST_FOREACH(it, inst->data_list) {\n            if (it->riid == riid) {\n                break;\n            }\n        }\n        if (!it) {\n            return ANJAY_ERR_NOT_FOUND;\n        }\n        return anjay_ret_bytes(ctx, it->data, it->data_length);\n    }\n\n    default:\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\nstatic AVS_LIST(data_resource_instance_t) *\nresource_instance_create(binary_app_data_container_instance_t *inst,\n                         anjay_riid_t riid) {\n    AVS_LIST(data_resource_instance_t) created =\n            AVS_LIST_NEW_ELEMENT(data_resource_instance_t);\n    if (!created) {\n        return NULL;\n    }\n\n    created->riid = riid;\n\n    AVS_LIST(data_resource_instance_t) *ptr;\n    AVS_LIST_FOREACH_PTR(ptr, &inst->data_list) {\n        if ((*ptr)->riid > created->riid) {\n            break;\n        }\n    }\n\n    AVS_LIST_INSERT(ptr, created);\n    return ptr;\n}\n\nstatic int resource_write(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr,\n                          anjay_iid_t iid,\n                          anjay_rid_t rid,\n                          anjay_riid_t riid,\n                          anjay_input_ctx_t *ctx) {\n    (void) anjay;\n\n    binary_app_data_container_t *obj = get_obj(obj_ptr);\n    binary_app_data_container_instance_t *inst = find_instance(obj, iid);\n    assert(inst);\n\n    switch (rid) {\n    case RID_DATA: {\n        AVS_LIST(data_resource_instance_t) *it;\n        AVS_LIST_FOREACH_PTR(it, &inst->data_list) {\n            if ((*it)->riid >= riid) {\n                break;\n            }\n        }\n        bool created = false;\n        if (!(*it) || (*it)->riid != riid) {\n            if (!(it = resource_instance_create(inst, riid))) {\n                return ANJAY_ERR_INTERNAL;\n            }\n            created = true;\n        }\n\n        bool finished;\n        int retval = anjay_get_bytes(ctx, &(*it)->data_length, &finished,\n                                     (*it)->data, sizeof((*it)->data));\n\n        if (!retval && !finished) {\n            retval = ANJAY_ERR_INTERNAL;\n        }\n\n        if (retval) {\n            if (created) {\n                AVS_LIST_DELETE(it);\n            } else {\n                (*it)->data_length = 0;\n            }\n        }\n        return retval;\n    }\n\n    default:\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\nstatic int resource_reset(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr,\n                          anjay_iid_t iid,\n                          anjay_rid_t rid) {\n    (void) anjay;\n\n    binary_app_data_container_t *obj = get_obj(obj_ptr);\n    binary_app_data_container_instance_t *inst = find_instance(obj, iid);\n    assert(inst);\n\n    switch (rid) {\n    case RID_DATA:\n        AVS_LIST_CLEAR(&inst->data_list);\n        return 0;\n\n    default:\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\n#ifdef ANJAY_WITH_LWM2M12\nstatic int resource_instance_remove(anjay_t *anjay,\n                                    const anjay_dm_object_def_t *const *obj_ptr,\n                                    anjay_iid_t iid,\n                                    anjay_rid_t rid,\n                                    anjay_riid_t riid) {\n    (void) anjay;\n\n    binary_app_data_container_t *obj = get_obj(obj_ptr);\n    binary_app_data_container_instance_t *inst = find_instance(obj, iid);\n    assert(inst);\n\n    switch (rid) {\n    case RID_DATA: {\n        AVS_LIST(data_resource_instance_t) *it;\n        AVS_LIST_FOREACH_PTR(it, &inst->data_list) {\n            if ((*it)->riid == riid) {\n                break;\n            }\n        }\n        if (!it || !*it) {\n            AVS_UNREACHABLE(\n                    \"Attempted to remove a non-existent Resource Instance\");\n            return ANJAY_ERR_NOT_FOUND;\n        }\n        AVS_LIST_DELETE(it);\n        return 0;\n    }\n    default:\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n#endif // ANJAY_WITH_LWM2M12\n\nstatic int list_resource_instances(anjay_t *anjay,\n                                   const anjay_dm_object_def_t *const *obj_ptr,\n                                   anjay_iid_t iid,\n                                   anjay_rid_t rid,\n                                   anjay_dm_list_ctx_t *ctx) {\n    (void) anjay;\n    (void) ctx;\n\n    binary_app_data_container_t *obj = get_obj(obj_ptr);\n    binary_app_data_container_instance_t *inst = find_instance(obj, iid);\n    assert(inst);\n\n    switch (rid) {\n    case RID_DATA: {\n        data_resource_instance_t *it;\n        AVS_LIST_FOREACH(it, inst->data_list) {\n            anjay_dm_emit(ctx, it->riid);\n        }\n        return 0;\n    }\n\n    default:\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\nstatic int transaction_begin(anjay_t *anjay,\n                             const anjay_dm_object_def_t *const *obj_ptr) {\n    (void) anjay;\n    binary_app_data_container_t *repr = get_obj(obj_ptr);\n    if (!repr->instances) {\n        return 0;\n    }\n\n    AVS_LIST(binary_app_data_container_instance_t) *saved_instances_tail =\n            &repr->saved_instances;\n    AVS_LIST(binary_app_data_container_instance_t) instance;\n    bool failed = false;\n    AVS_LIST_FOREACH(instance, repr->instances) {\n        AVS_LIST(binary_app_data_container_instance_t) saved_instance =\n                AVS_LIST_APPEND_NEW(binary_app_data_container_instance_t,\n                                    saved_instances_tail);\n        if (!saved_instance) {\n            demo_log(ERROR, \"cannot allocate a new instance\");\n            failed = true;\n            break;\n        }\n        if (instance->data_list) {\n            saved_instance->data_list =\n                    AVS_LIST_SIMPLE_CLONE(instance->data_list);\n            if (!saved_instance->data_list) {\n                demo_log(ERROR, \"cannot clone resource instances list\");\n                failed = true;\n                break;\n            }\n        }\n        saved_instance->iid = instance->iid;\n        AVS_LIST_ADVANCE_PTR(&saved_instances_tail);\n    }\n\n    if (failed) {\n        AVS_LIST_CLEAR(&repr->saved_instances) {\n            AVS_LIST_CLEAR(&repr->saved_instances->data_list);\n        }\n        return ANJAY_ERR_INTERNAL;\n    }\n    return 0;\n}\n\nstatic int transaction_commit(anjay_t *anjay,\n                              const anjay_dm_object_def_t *const *obj_ptr) {\n    (void) anjay;\n    binary_app_data_container_t *repr = get_obj(obj_ptr);\n    AVS_LIST_CLEAR(&repr->saved_instances) {\n        AVS_LIST_CLEAR(&repr->saved_instances->data_list);\n    }\n    return 0;\n}\n\nstatic int transaction_rollback(anjay_t *anjay,\n                                const anjay_dm_object_def_t *const *obj_ptr) {\n    (void) anjay;\n    binary_app_data_container_t *repr = get_obj(obj_ptr);\n    AVS_LIST_CLEAR(&repr->instances) {\n        AVS_LIST_CLEAR(&repr->instances->data_list);\n    }\n    repr->instances = repr->saved_instances;\n    repr->saved_instances = NULL;\n    return 0;\n}\n\nstatic const anjay_dm_object_def_t OBJ_DEF = {\n    .oid = 19,\n    .handlers = {\n        .list_instances = list_instances,\n        .instance_create = instance_create,\n        .instance_remove = instance_remove,\n        .instance_reset = instance_reset,\n\n        .list_resources = list_resources,\n        .resource_read = resource_read,\n        .resource_write = resource_write,\n        .resource_reset = resource_reset,\n        .list_resource_instances = list_resource_instances,\n\n        .transaction_begin = transaction_begin,\n        .transaction_validate = anjay_dm_transaction_NOOP,\n        .transaction_commit = transaction_commit,\n        .transaction_rollback = transaction_rollback\n#ifdef ANJAY_WITH_LWM2M12\n        ,\n        .resource_instance_remove = resource_instance_remove\n#endif // ANJAY_WITH_LWM2M12\n    }\n};\n\nconst anjay_dm_object_def_t **\ngw_binary_app_data_container_object_create(anjay_iid_t id) {\n    binary_app_data_container_t *obj =\n            (binary_app_data_container_t *) avs_calloc(\n                    1, sizeof(binary_app_data_container_t));\n    if (!obj) {\n        return NULL;\n    }\n    obj->def = &OBJ_DEF;\n    obj->end_device_iid = id;\n\n    /* Create at least one instance for the End Devices */\n    binary_app_data_container_instance_t *inst = add_instance(obj, 0);\n    resource_instance_create(inst, 0);\n\n    return &obj->def;\n}\n\nvoid gw_binary_app_data_container_object_release(\n        const anjay_dm_object_def_t **def) {\n    if (def) {\n        binary_app_data_container_t *obj = get_obj(def);\n        AVS_LIST_CLEAR(&obj->instances) {\n            release_instance(obj->instances);\n        }\n\n        avs_free(obj);\n    }\n}\n\nint gw_binary_app_data_container_write(anjay_t *anjay,\n                                       const anjay_dm_object_def_t **def,\n                                       anjay_iid_t iid,\n                                       anjay_riid_t riid,\n                                       const char *value) {\n    (void) anjay;\n\n    size_t length = strlen(value);\n    if (length > MAX_BINARY_DATA_SIZE) {\n        demo_log(ERROR, \"Value too long: %s\", value);\n        return -1;\n    }\n\n    binary_app_data_container_t *obj = get_obj(def);\n    binary_app_data_container_instance_t *inst = find_instance(obj, iid);\n    if (!inst) {\n        demo_log(ERROR, \"No such instance: %\" PRIu16, iid);\n        return -1;\n    }\n    AVS_LIST(data_resource_instance_t) *it;\n    AVS_LIST_FOREACH_PTR(it, &inst->data_list) {\n        if ((*it)->riid >= riid) {\n            break;\n        }\n    }\n    if (!(*it) || (*it)->riid != riid) {\n        if (!(it = resource_instance_create(inst, riid))) {\n            return -1;\n        }\n    }\n\n    memcpy((*it)->data, value, length);\n    (*it)->data_length = length;\n    (void) anjay_lwm2m_gateway_notify_changed(anjay, obj->end_device_iid,\n                                              (*def)->oid, iid, RID_DATA);\n    return 0;\n}\n"
  },
  {
    "path": "demo/objects/gateway_end_devices/binary_app_data_container.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n#ifndef GW_BINARY_APP_DATA_CONTAINER_H\n#define GW_BINARY_APP_DATA_CONTAINER_H\n\n#include <anjay/anjay.h>\n\n#include <avsystem/commons/avs_list.h>\n\nconst anjay_dm_object_def_t **\ngw_binary_app_data_container_object_create(anjay_iid_t id);\nvoid gw_binary_app_data_container_object_release(\n        const anjay_dm_object_def_t **def);\nint gw_binary_app_data_container_write(anjay_t *anjay,\n                                       const anjay_dm_object_def_t **def,\n                                       anjay_iid_t iid,\n                                       anjay_riid_t riid,\n                                       const char *value);\n\n#endif // GW_BINARY_APP_DATA_CONTAINER_H\n"
  },
  {
    "path": "demo/objects/gateway_end_devices/push_button_object.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n#include <assert.h>\n#include <stdbool.h>\n\n#include <anjay/anjay.h>\n#include <anjay/lwm2m_gateway.h>\n#include <avsystem/commons/avs_defs.h>\n#include <avsystem/commons/avs_memory.h>\n\n#include \"push_button_object.h\"\n\n/**\n * Digital Input State: R, Single, Mandatory\n * type: boolean, range: N/A, unit: N/A\n * The current state of a digital input.\n */\n#define RID_DIGITAL_INPUT_STATE 5500\n\n/**\n * Digital Input Counter: R, Single, Optional\n * type: integer, range: N/A, unit: N/A\n * The cumulative value of active state detected.\n */\n#define RID_DIGITAL_INPUT_COUNTER 5501\n\n/**\n * Application Type: RW, Single, Optional\n * type: string, range: N/A, unit: N/A\n * The application type of the sensor or actuator as a string depending\n * on the use case.\n */\n#define RID_APPLICATION_TYPE 5750\n\ntypedef struct push_button_instance_struct {\n    bool digital_input_state;\n    int32_t digital_input_counter;\n    char application_type[64];\n    char application_type_backup[64];\n} push_button_instance_t;\n\ntypedef struct push_button_object_struct {\n    const anjay_dm_object_def_t *def;\n    push_button_instance_t instances[1];\n    anjay_iid_t end_device_iid;\n} push_button_object_t;\n\nstatic inline push_button_object_t *\nget_obj(const anjay_dm_object_def_t *const *obj_ptr) {\n    assert(obj_ptr);\n    return AVS_CONTAINER_OF(obj_ptr, push_button_object_t, def);\n}\n\nstatic int list_instances(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr,\n                          anjay_dm_list_ctx_t *ctx) {\n    (void) anjay;\n\n    push_button_object_t *obj = get_obj(obj_ptr);\n    for (anjay_iid_t iid = 0; iid < AVS_ARRAY_SIZE(obj->instances); iid++) {\n        anjay_dm_emit(ctx, iid);\n    }\n\n    return 0;\n}\n\nstatic int instance_reset(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr,\n                          anjay_iid_t iid) {\n    (void) anjay;\n\n    push_button_object_t *obj = get_obj(obj_ptr);\n    assert(iid < AVS_ARRAY_SIZE(obj->instances));\n    push_button_instance_t *inst = &obj->instances[iid];\n\n    inst->digital_input_state = false;\n    inst->digital_input_counter = 0;\n    inst->application_type[0] = '\\0';\n\n    return 0;\n}\n\nstatic int list_resources(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr,\n                          anjay_iid_t iid,\n                          anjay_dm_resource_list_ctx_t *ctx) {\n    (void) anjay;\n    (void) obj_ptr;\n    (void) iid;\n\n    anjay_dm_emit_res(ctx, RID_DIGITAL_INPUT_STATE, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, RID_DIGITAL_INPUT_COUNTER, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, RID_APPLICATION_TYPE, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_PRESENT);\n    return 0;\n}\n\nstatic int resource_read(anjay_t *anjay,\n                         const anjay_dm_object_def_t *const *obj_ptr,\n                         anjay_iid_t iid,\n                         anjay_rid_t rid,\n                         anjay_riid_t riid,\n                         anjay_output_ctx_t *ctx) {\n    (void) anjay;\n\n    push_button_object_t *obj = get_obj(obj_ptr);\n    assert(iid < AVS_ARRAY_SIZE(obj->instances));\n    push_button_instance_t *inst = &obj->instances[iid];\n\n    switch (rid) {\n    case RID_DIGITAL_INPUT_STATE:\n        assert(riid == ANJAY_ID_INVALID);\n        return anjay_ret_bool(ctx, inst->digital_input_state);\n\n    case RID_DIGITAL_INPUT_COUNTER:\n        assert(riid == ANJAY_ID_INVALID);\n        return anjay_ret_i32(ctx, inst->digital_input_counter);\n\n    case RID_APPLICATION_TYPE:\n        assert(riid == ANJAY_ID_INVALID);\n        return anjay_ret_string(ctx, inst->application_type);\n\n    default:\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\nstatic int resource_write(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr,\n                          anjay_iid_t iid,\n                          anjay_rid_t rid,\n                          anjay_riid_t riid,\n                          anjay_input_ctx_t *ctx) {\n    (void) anjay;\n\n    push_button_object_t *obj = get_obj(obj_ptr);\n    assert(iid < AVS_ARRAY_SIZE(obj->instances));\n    push_button_instance_t *inst = &obj->instances[iid];\n\n    switch (rid) {\n    case RID_APPLICATION_TYPE: {\n        assert(riid == ANJAY_ID_INVALID);\n        return anjay_get_string(ctx, inst->application_type,\n                                sizeof(inst->application_type));\n    }\n\n    default:\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\nstatic int transaction_begin(anjay_t *anjay,\n                             const anjay_dm_object_def_t *const *obj_ptr) {\n    (void) anjay;\n\n    push_button_object_t *obj = get_obj(obj_ptr);\n    push_button_instance_t *instance = &obj->instances[0];\n    strcpy(instance->application_type_backup, instance->application_type);\n\n    return 0;\n}\n\nstatic int transaction_rollback(anjay_t *anjay,\n                                const anjay_dm_object_def_t *const *obj_ptr) {\n    (void) anjay;\n\n    push_button_object_t *obj = get_obj(obj_ptr);\n    push_button_instance_t *instance = &obj->instances[0];\n    strcpy(instance->application_type, instance->application_type_backup);\n\n    return 0;\n}\n\nconst anjay_dm_object_def_t OBJ_DEF = {\n    .oid = 3347,\n    .handlers = {\n        .list_instances = list_instances,\n        .instance_reset = instance_reset,\n        .list_resources = list_resources,\n        .resource_read = resource_read,\n        .resource_write = resource_write,\n        .transaction_begin = transaction_begin,\n        .transaction_validate = anjay_dm_transaction_NOOP,\n        .transaction_commit = anjay_dm_transaction_NOOP,\n        .transaction_rollback = transaction_rollback\n    }\n};\n\nconst anjay_dm_object_def_t **push_button_object_create(anjay_iid_t id) {\n    push_button_object_t *obj =\n            (push_button_object_t *) avs_calloc(1,\n                                                sizeof(push_button_object_t));\n    if (!obj) {\n        return NULL;\n    }\n    obj->def = &OBJ_DEF;\n    obj->instances[0].digital_input_state = false;\n    obj->instances[0].digital_input_counter = 0;\n    sprintf(obj->instances[0].application_type, \"Button %d\", id);\n    obj->end_device_iid = id;\n\n    return &obj->def;\n}\n\nvoid push_button_object_release(const anjay_dm_object_def_t **def) {\n    if (def) {\n        push_button_object_t *obj = get_obj(def);\n        avs_free(obj);\n    }\n}\n\nvoid push_button_press(anjay_t *anjay, const anjay_dm_object_def_t **def) {\n    push_button_object_t *obj = get_obj(def);\n    if (!obj->instances[0].digital_input_state) {\n        obj->instances[0].digital_input_counter++;\n        (void) anjay_lwm2m_gateway_notify_changed(\n                anjay, obj->end_device_iid, 3347, 0, RID_DIGITAL_INPUT_COUNTER);\n    }\n    obj->instances[0].digital_input_state = true;\n    (void) anjay_lwm2m_gateway_notify_changed(anjay, obj->end_device_iid, 3347,\n                                              0, RID_DIGITAL_INPUT_STATE);\n}\n\nvoid push_button_release(anjay_t *anjay, const anjay_dm_object_def_t **def) {\n    push_button_object_t *obj = get_obj(def);\n    obj->instances[0].digital_input_state = false;\n    (void) anjay_lwm2m_gateway_notify_changed(anjay, obj->end_device_iid, 3347,\n                                              0, RID_DIGITAL_INPUT_STATE);\n}\n"
  },
  {
    "path": "demo/objects/gateway_end_devices/push_button_object.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n#ifndef PUSH_BUTTON_OBJECT_H\n#define PUSH_BUTTON_OBJECT_H\n\n#include <anjay/anjay.h>\n\nconst anjay_dm_object_def_t **push_button_object_create(anjay_iid_t id);\nvoid push_button_object_release(const anjay_dm_object_def_t **def);\nvoid push_button_press(anjay_t *anjay, const anjay_dm_object_def_t **def);\nvoid push_button_release(anjay_t *anjay, const anjay_dm_object_def_t **def);\n\n#endif // PUSH_BUTTON_OBJECT_H\n"
  },
  {
    "path": "demo/objects/gateway_end_devices/temperature_object.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n#include <assert.h>\n#include <stdbool.h>\n\n#include <anjay/anjay.h>\n#include <anjay/lwm2m_gateway.h>\n#include <avsystem/commons/avs_defs.h>\n#include <avsystem/commons/avs_list.h>\n#include <avsystem/commons/avs_memory.h>\n\n#include \"temperature_object.h\"\n\n/**\n * Min Measured Value: R, Single, Optional\n * type: float, range: N/A, unit: N/A\n * The minimum value measured by the sensor since power ON or reset.\n */\n#define RID_MIN_MEASURED_VALUE 5601\n\n/**\n * Max Measured Value: R, Single, Optional\n * type: float, range: N/A, unit: N/A\n * The maximum value measured by the sensor since power ON or reset.\n */\n#define RID_MAX_MEASURED_VALUE 5602\n\n/**\n * Reset Min and Max Measured Values: E, Single, Optional\n * type: N/A, range: N/A, unit: N/A\n * Reset the Min and Max Measured Values to Current Value.\n */\n#define RID_RESET_MIN_AND_MAX_MEASURED_VALUES 5605\n\n/**\n * Sensor Value: R, Single, Mandatory\n * type: float, range: N/A, unit: N/A\n * Last or Current Measured Value from the Sensor.\n */\n#define RID_SENSOR_VALUE 5700\n\n/**\n * Application Type: RW, Single, Optional\n * type: string, range: N/A, unit: N/A\n * The application type of the sensor or actuator as a string depending\n * on the use case.\n */\n#define RID_APPLICATION_TYPE 5750\n\ntypedef struct temperature_instance_struct {\n    anjay_iid_t iid;\n\n    double value;\n    double min_measured;\n    double max_measured;\n    char application_type[64];\n    char application_type_backup[64];\n} temperature_instance_t;\n\ntypedef struct temperature_object_struct {\n    const anjay_dm_object_def_t *def;\n    AVS_LIST(temperature_instance_t) instances;\n    anjay_iid_t end_device_iid;\n} temperature_object_t;\n\nstatic inline temperature_object_t *\nget_obj(const anjay_dm_object_def_t *const *obj_ptr) {\n    assert(obj_ptr);\n    return AVS_CONTAINER_OF(obj_ptr, temperature_object_t, def);\n}\n\nstatic temperature_instance_t *find_instance(const temperature_object_t *obj,\n                                             anjay_iid_t iid) {\n    AVS_LIST(temperature_instance_t) it;\n    AVS_LIST_FOREACH(it, obj->instances) {\n        if (it->iid == iid) {\n            return it;\n        } else if (it->iid > iid) {\n            break;\n        }\n    }\n\n    return NULL;\n}\n\nstatic int list_instances(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr,\n                          anjay_dm_list_ctx_t *ctx) {\n    (void) anjay;\n\n    AVS_LIST(temperature_instance_t) it;\n    AVS_LIST_FOREACH(it, get_obj(obj_ptr)->instances) {\n        anjay_dm_emit(ctx, it->iid);\n    }\n\n    return 0;\n}\n\nstatic int init_instance(temperature_instance_t *inst, anjay_iid_t iid) {\n    assert(iid != ANJAY_ID_INVALID);\n\n    inst->iid = iid;\n    return 0;\n}\n\nstatic void release_instance(temperature_instance_t *inst) {\n    (void) inst;\n}\n\nstatic temperature_instance_t *add_instance(temperature_object_t *obj,\n                                            anjay_iid_t iid) {\n    assert(find_instance(obj, iid) == NULL);\n\n    AVS_LIST(temperature_instance_t) created =\n            AVS_LIST_NEW_ELEMENT(temperature_instance_t);\n    if (!created) {\n        return NULL;\n    }\n\n    int result = init_instance(created, iid);\n    if (result) {\n        AVS_LIST_CLEAR(&created);\n        return NULL;\n    }\n\n    AVS_LIST(temperature_instance_t) *ptr;\n    AVS_LIST_FOREACH_PTR(ptr, &obj->instances) {\n        if ((*ptr)->iid > created->iid) {\n            break;\n        }\n    }\n\n    AVS_LIST_INSERT(ptr, created);\n    return created;\n}\n\nstatic int instance_create(anjay_t *anjay,\n                           const anjay_dm_object_def_t *const *obj_ptr,\n                           anjay_iid_t iid) {\n    (void) anjay;\n    temperature_object_t *obj = get_obj(obj_ptr);\n\n    return add_instance(obj, iid) ? 0 : ANJAY_ERR_INTERNAL;\n}\n\nstatic int instance_remove(anjay_t *anjay,\n                           const anjay_dm_object_def_t *const *obj_ptr,\n                           anjay_iid_t iid) {\n    (void) anjay;\n    temperature_object_t *obj = get_obj(obj_ptr);\n\n    AVS_LIST(temperature_instance_t) *it;\n    AVS_LIST_FOREACH_PTR(it, &obj->instances) {\n        if ((*it)->iid == iid) {\n            release_instance(*it);\n            AVS_LIST_DELETE(it);\n            return 0;\n        } else if ((*it)->iid > iid) {\n            break;\n        }\n    }\n\n    assert(0);\n    return ANJAY_ERR_NOT_FOUND;\n}\n\nstatic int instance_reset(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr,\n                          anjay_iid_t iid) {\n    (void) anjay;\n\n    temperature_object_t *obj = get_obj(obj_ptr);\n    temperature_instance_t *inst = find_instance(obj, iid);\n    assert(inst);\n\n    inst->application_type[0] = '\\0';\n    return 0;\n}\n\nstatic int list_resources(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr,\n                          anjay_iid_t iid,\n                          anjay_dm_resource_list_ctx_t *ctx) {\n    (void) anjay;\n    (void) obj_ptr;\n    (void) iid;\n\n    anjay_dm_emit_res(ctx, RID_MIN_MEASURED_VALUE, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, RID_MAX_MEASURED_VALUE, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, RID_RESET_MIN_AND_MAX_MEASURED_VALUES,\n                      ANJAY_DM_RES_E, ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, RID_SENSOR_VALUE, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, RID_APPLICATION_TYPE, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_PRESENT);\n    return 0;\n}\n\nstatic int resource_read(anjay_t *anjay,\n                         const anjay_dm_object_def_t *const *obj_ptr,\n                         anjay_iid_t iid,\n                         anjay_rid_t rid,\n                         anjay_riid_t riid,\n                         anjay_output_ctx_t *ctx) {\n    (void) anjay;\n\n    temperature_object_t *obj = get_obj(obj_ptr);\n    temperature_instance_t *inst = find_instance(obj, iid);\n    assert(inst);\n\n    switch (rid) {\n    case RID_MIN_MEASURED_VALUE:\n        assert(riid == ANJAY_ID_INVALID);\n        return anjay_ret_double(ctx, inst->min_measured);\n\n    case RID_MAX_MEASURED_VALUE:\n        assert(riid == ANJAY_ID_INVALID);\n        return anjay_ret_double(ctx, inst->max_measured);\n\n    case RID_SENSOR_VALUE:\n        assert(riid == ANJAY_ID_INVALID);\n        return anjay_ret_double(ctx, inst->value);\n\n    case RID_APPLICATION_TYPE:\n        assert(riid == ANJAY_ID_INVALID);\n        return anjay_ret_string(ctx, inst->application_type);\n\n    default:\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\nstatic int resource_write(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr,\n                          anjay_iid_t iid,\n                          anjay_rid_t rid,\n                          anjay_riid_t riid,\n                          anjay_input_ctx_t *ctx) {\n    (void) anjay;\n\n    temperature_object_t *obj = get_obj(obj_ptr);\n    temperature_instance_t *inst = find_instance(obj, iid);\n    assert(inst);\n\n    switch (rid) {\n    case RID_APPLICATION_TYPE: {\n        assert(riid == ANJAY_ID_INVALID);\n        return anjay_get_string(ctx, inst->application_type,\n                                sizeof(inst->application_type));\n    }\n\n    default:\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\nstatic int resource_execute(anjay_t *anjay,\n                            const anjay_dm_object_def_t *const *obj_ptr,\n                            anjay_iid_t iid,\n                            anjay_rid_t rid,\n                            anjay_execute_ctx_t *arg_ctx) {\n    (void) anjay;\n    (void) arg_ctx;\n\n    temperature_object_t *obj = get_obj(obj_ptr);\n    temperature_instance_t *inst = find_instance(obj, iid);\n    assert(inst);\n\n    switch (rid) {\n    case RID_RESET_MIN_AND_MAX_MEASURED_VALUES:\n        inst->min_measured = NAN;\n        inst->max_measured = NAN;\n        return 0;\n\n    default:\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\nstatic int transaction_begin(anjay_t *anjay,\n                             const anjay_dm_object_def_t *const *obj_ptr) {\n    (void) anjay;\n\n    temperature_object_t *obj = get_obj(obj_ptr);\n\n    temperature_instance_t *element;\n    AVS_LIST_FOREACH(element, obj->instances) {\n        strcpy(element->application_type_backup, element->application_type);\n    }\n    return 0;\n}\n\nstatic int transaction_rollback(anjay_t *anjay,\n                                const anjay_dm_object_def_t *const *obj_ptr) {\n    (void) anjay;\n\n    temperature_object_t *obj = get_obj(obj_ptr);\n\n    temperature_instance_t *element;\n    AVS_LIST_FOREACH(element, obj->instances) {\n        strcpy(element->application_type, element->application_type_backup);\n    }\n    return 0;\n}\n\nstatic const anjay_dm_object_def_t OBJ_DEF = {\n    .oid = 3303,\n    .version = \"1.1\",\n    .handlers = {\n        .list_instances = list_instances,\n        .instance_create = instance_create,\n        .instance_remove = instance_remove,\n        .instance_reset = instance_reset,\n\n        .list_resources = list_resources,\n        .resource_read = resource_read,\n        .resource_write = resource_write,\n        .resource_execute = resource_execute,\n\n        .transaction_begin = transaction_begin,\n        .transaction_validate = anjay_dm_transaction_NOOP,\n        .transaction_commit = anjay_dm_transaction_NOOP,\n        .transaction_rollback = transaction_rollback\n    }\n};\n\nconst anjay_dm_object_def_t **temperature_object_create(anjay_iid_t id) {\n    temperature_object_t *obj =\n            (temperature_object_t *) avs_calloc(1,\n                                                sizeof(temperature_object_t));\n    if (!obj) {\n        return NULL;\n    }\n    obj->def = &OBJ_DEF;\n    obj->end_device_iid = id;\n\n    temperature_instance_t *inst = add_instance(obj, 0);\n    sprintf(inst->application_type, \"Sensor %d\", id);\n\n    return &obj->def;\n}\n\nvoid temperature_object_release(const anjay_dm_object_def_t **def) {\n    if (def) {\n        temperature_object_t *obj = get_obj(def);\n        AVS_LIST_CLEAR(&obj->instances) {\n            release_instance(obj->instances);\n        }\n        avs_free(obj);\n    }\n}\n\nvoid temperature_object_update_value(anjay_t *anjay,\n                                     const anjay_dm_object_def_t **def) {\n    assert(anjay);\n    temperature_object_t *obj = get_obj(def);\n\n    AVS_LIST(temperature_instance_t) *ptr;\n    AVS_LIST_FOREACH_PTR(ptr, &obj->instances) {\n        double new_value = ((double) (rand() % 12600) / 100.0) - 40.0;\n\n        (*ptr)->value = new_value;\n\n        if (isnan((*ptr)->min_measured) || new_value < (*ptr)->min_measured) {\n            (*ptr)->min_measured = new_value;\n        }\n        if (isnan((*ptr)->max_measured) || new_value > (*ptr)->max_measured) {\n            (*ptr)->max_measured = new_value;\n        }\n        anjay_lwm2m_gateway_notify_changed(anjay, obj->end_device_iid,\n                                           (*def)->oid, (*ptr)->iid,\n                                           RID_SENSOR_VALUE);\n    }\n}\n"
  },
  {
    "path": "demo/objects/gateway_end_devices/temperature_object.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n#ifndef TEMPERATURE_OBJECT_H\n#define TEMPERATURE_OBJECT_H\n\n#include <anjay/dm.h>\n\nconst anjay_dm_object_def_t **temperature_object_create(anjay_iid_t id);\nvoid temperature_object_release(const anjay_dm_object_def_t **def);\nvoid temperature_object_update_value(anjay_t *anjay,\n                                     const anjay_dm_object_def_t **def);\n\n#endif // TEMPERATURE_OBJECT_H\n"
  },
  {
    "path": "demo/objects/geopoints.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include \"../demo.h\"\n#include \"../demo_utils.h\"\n#include \"../objects.h\"\n\n#include <assert.h>\n#include <math.h>\n#include <stdbool.h>\n#include <string.h>\n\n#define GEOPOINTS_LATITUDE 0    // double, degrees\n#define GEOPOINTS_LONGITUDE 1   // double, degrees\n#define GEOPOINTS_RADIUS 2      // double, meters\n#define GEOPOINTS_DESCRIPTION 3 // string\n#define GEOPOINTS_INSIDE 4      // bool\n\ntypedef struct {\n    anjay_iid_t iid;\n\n    double latitude;\n    double longitude;\n    double radius_m;\n    char description[1024];\n    bool inside;\n\n    bool has_latitude;\n    bool has_longitude;\n    bool has_radius_m;\n} geopoint_t;\n\ntypedef struct {\n    const anjay_dm_object_def_t *def;\n    anjay_demo_t *demo;\n    AVS_LIST(geopoint_t) instances;\n    AVS_LIST(geopoint_t) saved_instances;\n} geopoints_t;\n\nstatic inline geopoints_t *\nget_geopoints(const anjay_dm_object_def_t *const *obj_ptr) {\n    assert(obj_ptr);\n    return AVS_CONTAINER_OF(obj_ptr, geopoints_t, def);\n}\n\nstatic geopoint_t *find_instance(const geopoints_t *repr, anjay_iid_t iid) {\n    AVS_LIST(geopoint_t) it;\n    AVS_LIST_FOREACH(it, repr->instances) {\n        if (it->iid == iid) {\n            return it;\n        } else if (it->iid > iid) {\n            break;\n        }\n    }\n\n    return NULL;\n}\n\nstatic int geopoints_list_instances(anjay_t *anjay,\n                                    const anjay_dm_object_def_t *const *obj_ptr,\n                                    anjay_dm_list_ctx_t *ctx) {\n    (void) anjay;\n\n    AVS_LIST(geopoint_t) it;\n    AVS_LIST_FOREACH(it, get_geopoints(obj_ptr)->instances) {\n        anjay_dm_emit(ctx, it->iid);\n    }\n    return 0;\n}\n\nstatic int\ngeopoints_instance_create(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr,\n                          anjay_iid_t iid) {\n    (void) anjay;\n    geopoints_t *repr = get_geopoints(obj_ptr);\n\n    AVS_LIST(geopoint_t) created = AVS_LIST_NEW_ELEMENT(geopoint_t);\n    if (!created) {\n        return ANJAY_ERR_INTERNAL;\n    }\n\n    created->iid = iid;\n\n    AVS_LIST(geopoint_t) *ptr;\n    AVS_LIST_FOREACH_PTR(ptr, &repr->instances) {\n        if ((*ptr)->iid > created->iid) {\n            break;\n        }\n    }\n\n    AVS_LIST_INSERT(ptr, created);\n    return 0;\n}\n\nstatic int\ngeopoints_instance_remove(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr,\n                          anjay_iid_t iid) {\n    (void) anjay;\n    geopoints_t *repr = get_geopoints(obj_ptr);\n\n    AVS_LIST(geopoint_t) *it;\n    AVS_LIST_FOREACH_PTR(it, &repr->instances) {\n        if ((*it)->iid == iid) {\n            AVS_LIST_DELETE(it);\n            return 0;\n        }\n    }\n\n    assert(0);\n    return ANJAY_ERR_NOT_FOUND;\n}\n\nstatic int geopoints_list_resources(anjay_t *anjay,\n                                    const anjay_dm_object_def_t *const *obj_ptr,\n                                    anjay_iid_t iid,\n                                    anjay_dm_resource_list_ctx_t *ctx) {\n    (void) anjay;\n    (void) obj_ptr;\n    (void) iid;\n\n    anjay_dm_emit_res(ctx, GEOPOINTS_LATITUDE, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, GEOPOINTS_LONGITUDE, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, GEOPOINTS_RADIUS, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, GEOPOINTS_DESCRIPTION, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, GEOPOINTS_INSIDE, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_PRESENT);\n    return 0;\n}\n\nstatic int geopoints_resource_read(anjay_t *anjay,\n                                   const anjay_dm_object_def_t *const *obj_ptr,\n                                   anjay_iid_t iid,\n                                   anjay_rid_t rid,\n                                   anjay_riid_t riid,\n                                   anjay_output_ctx_t *ctx) {\n    (void) anjay;\n    (void) riid;\n    assert(riid == ANJAY_ID_INVALID);\n\n    geopoint_t *inst = find_instance(get_geopoints(obj_ptr), iid);\n    assert(inst);\n\n    switch (rid) {\n    case GEOPOINTS_LATITUDE:\n        return anjay_ret_double(ctx, inst->latitude);\n    case GEOPOINTS_LONGITUDE:\n        return anjay_ret_double(ctx, inst->longitude);\n    case GEOPOINTS_RADIUS:\n        return anjay_ret_double(ctx, inst->radius_m);\n    case GEOPOINTS_DESCRIPTION:\n        return anjay_ret_string(ctx, inst->description);\n    case GEOPOINTS_INSIDE:\n        return anjay_ret_bool(ctx, inst->inside);\n    default:\n        AVS_UNREACHABLE(\"Read called on unknown resource\");\n        return ANJAY_ERR_NOT_FOUND;\n    }\n}\n\nstatic int geopoints_resource_write(anjay_t *anjay,\n                                    const anjay_dm_object_def_t *const *obj_ptr,\n                                    anjay_iid_t iid,\n                                    anjay_rid_t rid,\n                                    anjay_riid_t riid,\n                                    anjay_input_ctx_t *ctx) {\n    (void) anjay;\n    (void) riid;\n    assert(riid == ANJAY_ID_INVALID);\n\n    geopoint_t *inst = find_instance(get_geopoints(obj_ptr), iid);\n    assert(inst);\n\n    double value;\n    int result;\n\n    switch (rid) {\n    case GEOPOINTS_LATITUDE:\n        result = anjay_get_double(ctx, &value);\n        if (result) {\n            return result;\n        } else if (!latitude_valid(value)) {\n            return ANJAY_ERR_BAD_REQUEST;\n        }\n        inst->latitude = value;\n        inst->has_latitude = true;\n        return 0;\n    case GEOPOINTS_LONGITUDE:\n        result = anjay_get_double(ctx, &value);\n        if (result) {\n            return result;\n        } else if (!longitude_valid(value)) {\n            return ANJAY_ERR_BAD_REQUEST;\n        }\n        inst->longitude = value;\n        inst->has_longitude = true;\n        return 0;\n    case GEOPOINTS_RADIUS:\n        result = anjay_get_double(ctx, &value);\n        if (result) {\n            return result;\n        } else if (!isfinite(value) || value < 0.0) {\n            return ANJAY_ERR_BAD_REQUEST;\n        }\n        inst->radius_m = value;\n        inst->has_radius_m = true;\n        return 0;\n    case GEOPOINTS_DESCRIPTION: {\n        char buf[sizeof(inst->description)];\n        result = anjay_get_string(ctx, buf, sizeof(buf));\n        if (result) {\n            return result;\n        } else {\n            strcpy(inst->description, buf);\n            return 0;\n        }\n    }\n    default:\n        // Bootstrap Server may try to write to GEOPOINTS_INSIDE,\n        // so no AVS_UNREACHABLE() here\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\nstatic int\ngeopoints_transaction_begin(anjay_t *anjay,\n                            const anjay_dm_object_def_t *const *obj_ptr) {\n    (void) anjay;\n    geopoints_t *repr = get_geopoints(obj_ptr);\n    if (!repr->instances) {\n        return 0;\n    }\n    repr->saved_instances = AVS_LIST_SIMPLE_CLONE(repr->instances);\n    if (!repr->saved_instances) {\n        demo_log(ERROR, \"cannot clone Geopoint instances\");\n        return ANJAY_ERR_INTERNAL;\n    }\n    return 0;\n}\n\nstatic int\ngeopoints_transaction_validate(anjay_t *anjay,\n                               const anjay_dm_object_def_t *const *obj_ptr) {\n    (void) anjay;\n    AVS_LIST(geopoint_t) it;\n    AVS_LIST_FOREACH(it, get_geopoints(obj_ptr)->instances) {\n        if (!it->has_latitude || !it->has_longitude || !it->has_radius_m) {\n            return ANJAY_ERR_BAD_REQUEST;\n        }\n    }\n    return 0;\n}\n\nstatic int\ngeopoints_transaction_commit(anjay_t *anjay,\n                             const anjay_dm_object_def_t *const *obj_ptr) {\n    (void) anjay;\n    AVS_LIST_CLEAR(&get_geopoints(obj_ptr)->saved_instances);\n    return 0;\n}\n\nstatic int\ngeopoints_transaction_rollback(anjay_t *anjay,\n                               const anjay_dm_object_def_t *const *obj_ptr) {\n    (void) anjay;\n    geopoints_t *repr = get_geopoints(obj_ptr);\n    AVS_LIST_CLEAR(&repr->instances);\n    repr->instances = repr->saved_instances;\n    repr->saved_instances = NULL;\n    return 0;\n}\n\nstatic int geopoints_instance_reset(anjay_t *anjay,\n                                    const anjay_dm_object_def_t *const *obj_ptr,\n                                    anjay_iid_t iid) {\n    (void) anjay;\n    geopoint_t *inst = find_instance(get_geopoints(obj_ptr), iid);\n    AVS_ASSERT(inst, \"could not find instance\");\n    memset(inst, 0, sizeof(geopoint_t));\n    inst->iid = iid;\n    return 0;\n}\n\nstatic const anjay_dm_object_def_t GEOPOINTS = {\n    .oid = DEMO_OID_GEOPOINTS,\n    .handlers = {\n        .list_instances = geopoints_list_instances,\n        .instance_create = geopoints_instance_create,\n        .instance_remove = geopoints_instance_remove,\n        .instance_reset = geopoints_instance_reset,\n        .list_resources = geopoints_list_resources,\n        .resource_read = geopoints_resource_read,\n        .resource_write = geopoints_resource_write,\n        .transaction_begin = geopoints_transaction_begin,\n        .transaction_validate = geopoints_transaction_validate,\n        .transaction_commit = geopoints_transaction_commit,\n        .transaction_rollback = geopoints_transaction_rollback\n    }\n};\n\nconst anjay_dm_object_def_t **geopoints_object_create(anjay_demo_t *demo) {\n    geopoints_t *repr = (geopoints_t *) avs_calloc(1, sizeof(geopoints_t));\n    if (!repr) {\n        return NULL;\n    }\n\n    repr->def = &GEOPOINTS;\n    repr->demo = demo;\n\n    return &repr->def;\n}\n\nvoid geopoints_object_release(const anjay_dm_object_def_t **def) {\n    if (def) {\n        geopoints_t *geopoints = get_geopoints(def);\n        AVS_LIST_CLEAR(&geopoints->instances);\n        AVS_LIST_CLEAR(&geopoints->saved_instances);\n        avs_free(geopoints);\n    }\n}\n\nint geopoints_get_instances(const anjay_dm_object_def_t **def,\n                            AVS_LIST(anjay_iid_t) *out) {\n    geopoints_t *geopoints = get_geopoints(def);\n    assert(!*out);\n    AVS_LIST(geopoint_t) it;\n    AVS_LIST_FOREACH(it, geopoints->instances) {\n        if (!(*out = AVS_LIST_NEW_ELEMENT(anjay_iid_t))) {\n            demo_log(ERROR, \"out of memory\");\n            return -1;\n        }\n        **out = it->iid;\n        AVS_LIST_ADVANCE_PTR(&out);\n    }\n    return 0;\n}\n\nvoid geopoints_notify_time_dependent(anjay_t *anjay,\n                                     const anjay_dm_object_def_t **def) {\n    geopoints_t *geopoints = get_geopoints(def);\n\n    const anjay_dm_object_def_t **location_obj_ptr =\n            demo_find_object(geopoints->demo, DEMO_OID_LOCATION);\n    if (!location_obj_ptr) {\n        demo_log(ERROR, \"Could not update geopoints, Location not installed\");\n        return;\n    }\n\n    double latitude, longitude;\n    location_get(location_obj_ptr, &latitude, &longitude);\n\n    AVS_LIST(geopoint_t) point;\n    AVS_LIST_FOREACH(point, geopoints->instances) {\n        bool inside = (geo_distance_m(latitude, longitude, point->latitude,\n                                      point->longitude)\n                       < point->radius_m);\n        if (inside != point->inside) {\n            point->inside = inside;\n            anjay_notify_changed(anjay, (*def)->oid, point->iid,\n                                 GEOPOINTS_INSIDE);\n        }\n    }\n}\n"
  },
  {
    "path": "demo/objects/ip_ping.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#if !defined(_POSIX_C_SOURCE) && !defined(__APPLE__)\n#    define _POSIX_C_SOURCE 200809L\n#endif\n\n#include \"../demo.h\"\n#include \"../demo_utils.h\"\n#include \"../objects.h\"\n\n#include <assert.h>\n#include <stdio.h>\n#include <string.h>\n\n#include <pthread.h>\n\n#include <avsystem/commons/avs_utils.h>\n\n#define IP_PING_HOSTNAME 0\n#define IP_PING_REPETITIONS 1\n#define IP_PING_TIMEOUT_MS 2\n#define IP_PING_BLOCK_SIZE 3\n#define IP_PING_DSCP 4\n#define IP_PING_RUN 5\n#define IP_PING_STATE 6\n#define IP_PING_SUCCESS_COUNT 7\n#define IP_PING_ERROR_COUNT 8\n#define IP_PING_AVG_TIME_MS 9\n#define IP_PING_MIN_TIME_MS 10\n#define IP_PING_MAX_TIME_MS 11\n#define IP_PING_TIME_STDEV_US 12\n\ntypedef enum {\n    IP_PING_STATE_NONE,\n    IP_PING_STATE_IN_PROGRESS,\n    IP_PING_STATE_COMPLETE,\n    IP_PING_STATE_ERROR_HOST_NAME,\n    IP_PING_STATE_ERROR_INTERNAL,\n    IP_PING_STATE_ERROR_OTHER\n} ip_ping_state_t;\n\ntypedef struct {\n    char hostname[257];\n    uint32_t repetitions;\n    uint32_t ms_timeout;\n    uint16_t block_size;\n    uint8_t dscp;\n} ip_ping_conf_t;\n\ntypedef struct {\n    volatile atomic_int state_;\n    volatile atomic_uint success_count_;\n    volatile atomic_uint error_count_;\n    volatile atomic_uint avg_response_time_;\n    volatile atomic_uint min_response_time_;\n    volatile atomic_uint max_response_time_;\n    volatile atomic_uint response_time_stdev_us_;\n} ip_ping_stats_t;\n\ntypedef struct {\n    FILE *ping_pipe;\n    anjay_t *anjay;\n} ip_ping_command_state_t;\n\ntypedef struct {\n    const anjay_dm_object_def_t *def;\n    ip_ping_conf_t configuration;\n    ip_ping_conf_t saved_configuration;\n    ip_ping_stats_t stats;\n    ip_ping_command_state_t command_state;\n} ip_ping_t;\n\nstatic inline ip_ping_t *\nget_ip_ping(const anjay_dm_object_def_t *const *obj_ptr) {\n    assert(obj_ptr);\n    return AVS_CONTAINER_OF(obj_ptr, ip_ping_t, def);\n}\n\nstatic int ip_ping_list_resources(anjay_t *anjay,\n                                  const anjay_dm_object_def_t *const *obj_ptr,\n                                  anjay_iid_t iid,\n                                  anjay_dm_resource_list_ctx_t *ctx) {\n    (void) anjay;\n    (void) obj_ptr;\n    (void) iid;\n\n    anjay_dm_emit_res(ctx, IP_PING_HOSTNAME, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, IP_PING_REPETITIONS, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, IP_PING_TIMEOUT_MS, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, IP_PING_BLOCK_SIZE, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, IP_PING_DSCP, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, IP_PING_RUN, ANJAY_DM_RES_E, ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, IP_PING_STATE, ANJAY_DM_RES_R, ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, IP_PING_SUCCESS_COUNT, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, IP_PING_ERROR_COUNT, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, IP_PING_AVG_TIME_MS, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, IP_PING_MIN_TIME_MS, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, IP_PING_MAX_TIME_MS, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, IP_PING_TIME_STDEV_US, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_PRESENT);\n    return 0;\n}\n\nstatic int ip_ping_resource_read(anjay_t *anjay,\n                                 const anjay_dm_object_def_t *const *obj_ptr,\n                                 anjay_iid_t iid,\n                                 anjay_rid_t rid,\n                                 anjay_riid_t riid,\n                                 anjay_output_ctx_t *ctx) {\n    (void) anjay;\n    (void) iid;\n    (void) riid;\n    assert(riid == ANJAY_ID_INVALID);\n    ip_ping_t *ping = get_ip_ping(obj_ptr);\n\n    switch (rid) {\n    case IP_PING_HOSTNAME:\n        return anjay_ret_string(ctx, ping->configuration.hostname);\n    case IP_PING_REPETITIONS:\n        return anjay_ret_i64(ctx, ping->configuration.repetitions);\n    case IP_PING_TIMEOUT_MS:\n        return anjay_ret_i64(ctx, ping->configuration.ms_timeout);\n    case IP_PING_BLOCK_SIZE:\n        return anjay_ret_i32(ctx, ping->configuration.block_size);\n    case IP_PING_DSCP:\n        return anjay_ret_i32(ctx, ping->configuration.dscp);\n    case IP_PING_STATE:\n        return anjay_ret_i32(ctx, (int32_t) atomic_load(&ping->stats.state_));\n    case IP_PING_SUCCESS_COUNT:\n        return anjay_ret_i64(ctx, atomic_load(&ping->stats.success_count_));\n    case IP_PING_ERROR_COUNT:\n        return anjay_ret_i64(ctx, atomic_load(&ping->stats.error_count_));\n    case IP_PING_AVG_TIME_MS:\n        return anjay_ret_i64(ctx, atomic_load(&ping->stats.avg_response_time_));\n    case IP_PING_MIN_TIME_MS:\n        return anjay_ret_i64(ctx, atomic_load(&ping->stats.min_response_time_));\n    case IP_PING_MAX_TIME_MS:\n        return anjay_ret_i64(ctx, atomic_load(&ping->stats.max_response_time_));\n    case IP_PING_TIME_STDEV_US:\n        return anjay_ret_i64(ctx,\n                             atomic_load(&ping->stats.response_time_stdev_us_));\n    default:\n        AVS_UNREACHABLE(\n                \"Read handler called on unknown or non-readable resource\");\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\nstatic int ip_ping_reset_diagnostic_state(anjay_t *anjay, ip_ping_t *ipping) {\n    int state = IP_PING_STATE_COMPLETE;\n    bool reset = false;\n    while (!(reset = atomic_compare_exchange_weak(&ipping->stats.state_, &state,\n                                                  IP_PING_STATE_NONE))\n           && state != IP_PING_STATE_NONE && state != IP_PING_STATE_IN_PROGRESS)\n        ;\n    if (reset) {\n        anjay_notify_changed(anjay, ipping->def->oid, 0, IP_PING_STATE);\n    } else if (state == IP_PING_STATE_IN_PROGRESS) {\n        demo_log(ERROR, \"Canceling a diagnostic in progress is not supported\");\n        return ANJAY_ERR_INTERNAL;\n    }\n    return 0;\n}\n\n#define DECLARE_GET_NUM(Type, Base)                                            \\\n    static int get_##Type(anjay_input_ctx_t *ctx, Type##_t *out, Type##_t min, \\\n                          Type##_t max) {                                      \\\n        int##Base##_t base;                                                    \\\n        int result = anjay_get_i##Base(ctx, &base);                            \\\n        if (result) {                                                          \\\n            return result;                                                     \\\n        }                                                                      \\\n        if (base < (int##Base##_t) min || base > (int##Base##_t) max) {        \\\n            return ANJAY_ERR_BAD_REQUEST;                                      \\\n        }                                                                      \\\n        *out = (Type##_t) base;                                                \\\n        return 0;                                                              \\\n    }\n\nDECLARE_GET_NUM(uint8, 32)\nDECLARE_GET_NUM(uint16, 32)\nDECLARE_GET_NUM(uint32, 64)\n\nstatic int ip_ping_resource_write(anjay_t *anjay,\n                                  const anjay_dm_object_def_t *const *obj_ptr,\n                                  anjay_iid_t iid,\n                                  anjay_rid_t rid,\n                                  anjay_riid_t riid,\n                                  anjay_input_ctx_t *ctx) {\n    (void) anjay;\n    (void) iid;\n    (void) riid;\n    assert(riid == ANJAY_ID_INVALID);\n    ip_ping_t *ping = get_ip_ping(obj_ptr);\n    int result;\n\n    switch (rid) {\n    case IP_PING_HOSTNAME:\n        (void) ((result = ip_ping_reset_diagnostic_state(anjay, ping))\n                || (result = anjay_get_string(\n                            ctx, ping->configuration.hostname,\n                            sizeof(ping->configuration.hostname))));\n        return result;\n    case IP_PING_REPETITIONS:\n        (void) ((result = ip_ping_reset_diagnostic_state(anjay, ping))\n                || (result = get_uint32(ctx, &ping->configuration.repetitions,\n                                        1, UINT32_MAX)));\n        return result;\n    case IP_PING_TIMEOUT_MS:\n        (void) ((result = ip_ping_reset_diagnostic_state(anjay, ping))\n                || (result = get_uint32(ctx, &ping->configuration.ms_timeout, 1,\n                                        UINT32_MAX)));\n        return result;\n    case IP_PING_BLOCK_SIZE:\n        (void) ((result = ip_ping_reset_diagnostic_state(anjay, ping))\n                || (result = get_uint16(ctx, &ping->configuration.block_size, 1,\n                                        UINT16_MAX)));\n        return result;\n    case IP_PING_DSCP:\n        (void) ((result = ip_ping_reset_diagnostic_state(anjay, ping))\n                || (result = get_uint8(ctx, &ping->configuration.dscp, 0, 63)));\n        return result;\n    default:\n        // Bootstrap Server may try to write to other resources,\n        // so no AVS_UNREACHABLE() here\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\nstatic void update_response_times(ip_ping_t *ping,\n                                  unsigned min,\n                                  unsigned max,\n                                  unsigned avg,\n                                  unsigned mdev_us) {\n    atomic_store(&ping->stats.min_response_time_, min);\n    anjay_notify_changed(ping->command_state.anjay, ping->def->oid, 0,\n                         IP_PING_MIN_TIME_MS);\n    atomic_store(&ping->stats.avg_response_time_, max);\n    anjay_notify_changed(ping->command_state.anjay, ping->def->oid, 0,\n                         IP_PING_AVG_TIME_MS);\n    atomic_store(&ping->stats.max_response_time_, avg);\n    anjay_notify_changed(ping->command_state.anjay, ping->def->oid, 0,\n                         IP_PING_MAX_TIME_MS);\n    atomic_store(&ping->stats.response_time_stdev_us_, mdev_us);\n    anjay_notify_changed(ping->command_state.anjay, ping->def->oid, 0,\n                         IP_PING_TIME_STDEV_US);\n}\n\nstatic void ip_ping_command_state_cleanup(ip_ping_t *ping) {\n    if (ping->command_state.ping_pipe) {\n        pclose(ping->command_state.ping_pipe);\n        ping->command_state.ping_pipe = NULL;\n    }\n}\n\nstatic void *ip_ping_thread(void *ping_) {\n    ip_ping_t *ping = (ip_ping_t *) ping_;\n    char line[512];\n\n    typedef enum {\n        IP_PING_HANDLER_HEADER,\n        IP_PING_HANDLER_SKIP1,\n        IP_PING_HANDLER_SKIP2,\n        IP_PING_HANDLER_COUNTS,\n        IP_PING_HANDLER_RTT\n    } ip_ping_handler_state_t;\n\n    ip_ping_handler_state_t state = IP_PING_HANDLER_HEADER;\n\n    while (fgets(line, sizeof(line), ping->command_state.ping_pipe)) {\n        switch (state) {\n        case IP_PING_HANDLER_HEADER:\n            if (strstr(line, \"unknown\")) {\n                demo_log(ERROR, \"Unknown host: %s\",\n                         ping->configuration.hostname);\n                atomic_store(&ping->stats.state_,\n                             IP_PING_STATE_ERROR_HOST_NAME);\n                goto finish;\n            }\n            break;\n        case IP_PING_HANDLER_SKIP1:\n        case IP_PING_HANDLER_SKIP2:\n        default:\n            break;\n        case IP_PING_HANDLER_COUNTS: {\n            unsigned total, success;\n            if (sscanf(line, \"%u %*s %*s %u\", &total, &success) != 2) {\n                goto finish;\n            }\n            atomic_store(&ping->stats.success_count_, success);\n            anjay_notify_changed(ping->command_state.anjay, ping->def->oid, 0,\n                                 IP_PING_SUCCESS_COUNT);\n            atomic_store(&ping->stats.error_count_, total - success);\n            anjay_notify_changed(ping->command_state.anjay, ping->def->oid, 0,\n                                 IP_PING_ERROR_COUNT);\n            if (success == 0) {\n                atomic_store(&ping->stats.state_, IP_PING_STATE_COMPLETE);\n                update_response_times(ping, 0, 0, 0, 0);\n                goto finish;\n            }\n            break;\n        }\n        case IP_PING_HANDLER_RTT: {\n            const char *ptr = strstr(line, \"=\");\n            if (!ptr) {\n                demo_log(ERROR, \"Invalid output format of ping.\");\n                goto finish;\n            }\n            ptr += 2;\n            float min, avg, max, mdev;\n            if (sscanf(ptr, \"%f/%f/%f/%f\", &min, &avg, &max, &mdev) != 4) {\n                goto finish;\n            }\n            atomic_store(&ping->stats.state_, IP_PING_STATE_COMPLETE);\n            update_response_times(ping,\n                                  (unsigned) min,\n                                  (unsigned) avg,\n                                  (unsigned) max,\n                                  (unsigned) (mdev * 1000.0f));\n        }\n        }\n        state = (ip_ping_handler_state_t) (state + 1);\n    }\n\nfinish:\n    ip_ping_command_state_cleanup(ping);\n    atomic_compare_exchange_strong(\n            &ping->stats.state_, &(unsigned int) { IP_PING_STATE_IN_PROGRESS },\n            IP_PING_STATE_ERROR_INTERNAL);\n    anjay_notify_changed(ping->command_state.anjay, ping->def->oid, 0,\n                         IP_PING_STATE);\n    return NULL;\n}\n\nstatic ip_ping_state_t start_ip_ping(anjay_t *anjay, ip_ping_t *ping) {\n    if (!ping->configuration.repetitions || !ping->configuration.ms_timeout\n            || !ping->configuration.block_size\n            || !ping->configuration.hostname[0]) {\n        return IP_PING_STATE_ERROR_OTHER;\n    }\n\n    char command[320];\n    unsigned timeout_s = ping->configuration.ms_timeout / 1000;\n    if (!timeout_s) {\n        timeout_s = 1;\n    }\n\n    if (avs_simple_snprintf(command, sizeof(command),\n                            \"ping -q -c %u -Q 0x%x -W %u -s %u %s 2>&1\",\n                            ping->configuration.repetitions,\n                            ping->configuration.dscp << 2, timeout_s,\n                            ping->configuration.block_size,\n                            ping->configuration.hostname)\n            < 0) {\n        demo_log(ERROR, \"Cannot prepare ping command\");\n        return IP_PING_STATE_ERROR_INTERNAL;\n    }\n\n    if (!(ping->command_state.ping_pipe = popen(command, \"r\"))) {\n        demo_log(ERROR, \"Cannot start child process. Command: %s\", command);\n        return IP_PING_STATE_ERROR_INTERNAL;\n    }\n\n    ping->command_state.anjay = anjay;\n    pthread_t thread;\n    if (pthread_create(&thread, NULL, ip_ping_thread, NULL)) {\n        pclose(ping->command_state.ping_pipe);\n        ping->command_state.ping_pipe = NULL;\n        return IP_PING_STATE_ERROR_INTERNAL;\n    }\n\n    pthread_detach(thread);\n    return IP_PING_STATE_IN_PROGRESS;\n}\n\nstatic int ip_ping_resource_execute(anjay_t *anjay,\n                                    const anjay_dm_object_def_t *const *obj_ptr,\n                                    anjay_iid_t iid,\n                                    anjay_rid_t rid,\n                                    anjay_execute_ctx_t *arg_ctx) {\n    (void) arg_ctx;\n    (void) rid;\n    ip_ping_t *ping = get_ip_ping(obj_ptr);\n    int result;\n\n    assert(rid == IP_PING_RUN);\n    if ((result = ip_ping_reset_diagnostic_state(anjay, ping))) {\n        return result;\n    }\n    ip_ping_state_t state = start_ip_ping(anjay, ping);\n    if (atomic_compare_exchange_strong(&ping->stats.state_,\n                                       &(int) { IP_PING_STATE_NONE }, state)) {\n        anjay_notify_changed(anjay, (*obj_ptr)->oid, iid, IP_PING_STATE);\n    }\n    return 0;\n}\n\nstatic int\nip_ping_transaction_begin(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr) {\n    (void) anjay;\n    ip_ping_t *repr = get_ip_ping(obj_ptr);\n    repr->saved_configuration = repr->configuration;\n    return 0;\n}\n\nstatic int\nip_ping_transaction_rollback(anjay_t *anjay,\n                             const anjay_dm_object_def_t *const *obj_ptr) {\n    (void) anjay;\n    ip_ping_t *repr = get_ip_ping(obj_ptr);\n    repr->configuration = repr->saved_configuration;\n    return 0;\n}\n\nstatic const anjay_dm_object_def_t IP_PING = {\n    .oid = DEMO_OID_IP_PING,\n    .handlers = {\n        .list_instances = anjay_dm_list_instances_SINGLE,\n        .list_resources = ip_ping_list_resources,\n        .resource_read = ip_ping_resource_read,\n        .resource_write = ip_ping_resource_write,\n        .resource_execute = ip_ping_resource_execute,\n        .transaction_begin = ip_ping_transaction_begin,\n        .transaction_validate = anjay_dm_transaction_NOOP,\n        .transaction_commit = anjay_dm_transaction_NOOP,\n        .transaction_rollback = ip_ping_transaction_rollback\n    }\n};\n\nconst anjay_dm_object_def_t **ip_ping_object_create(void) {\n    ip_ping_t *repr = (ip_ping_t *) avs_calloc(1, sizeof(ip_ping_t));\n    if (!repr) {\n        return NULL;\n    }\n\n    repr->def = &IP_PING;\n\n    return &repr->def;\n}\n\nvoid ip_ping_object_release(const anjay_dm_object_def_t **def) {\n    if (def) {\n        ip_ping_t *ping = get_ip_ping(def);\n        ip_ping_command_state_cleanup(ping);\n        avs_free(ping);\n    }\n}\n"
  },
  {
    "path": "demo/objects/ipso_objects.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay/ipso_objects.h>\n#include <anjay/ipso_objects_v2.h>\n\n#include <avsystem/commons/avs_log.h>\n\n#include \"../demo_utils.h\"\n#include \"../objects.h\"\n\n#define ANJAY_DEMO_TEMPERATURE_UNIT \"Cel\"\n#define ANJAY_DEMO_TEMPERATURE_OID 3303\n#define ANJAY_DEMO_TEMPERATURE_MAX_VALUE 42\n#define ANJAY_DEMO_TEMPERATURE_CHANGE 13\n\n#define ANJAY_DEMO_TEMPERATURE_MAX_INSTANCE_NUM 16\n\ntypedef struct {\n    uint64_t value;\n} thermometer_t;\nstatic thermometer_t THERMOMETER;\n\nstatic double get_temperature(thermometer_t *thermometer) {\n    thermometer->value = (thermometer->value + ANJAY_DEMO_TEMPERATURE_CHANGE)\n                         % (ANJAY_DEMO_TEMPERATURE_MAX_VALUE + 1);\n    return (double) (thermometer->value);\n}\n\nint install_temperature_object(anjay_t *anjay) {\n    if (anjay_ipso_v2_basic_sensor_install(\n                anjay,\n                ANJAY_DEMO_TEMPERATURE_OID,\n                NULL,\n                ANJAY_DEMO_TEMPERATURE_MAX_INSTANCE_NUM)) {\n        avs_log(ipso, ERROR, \"Could not install Temperature object\");\n        return -1;\n    }\n\n    temperature_add_instance(anjay, 0);\n\n    return 0;\n}\n\nvoid temperature_update_handler(anjay_t *anjay) {\n    (void) anjay_ipso_v2_basic_sensor_value_update(anjay,\n                                                   ANJAY_DEMO_TEMPERATURE_OID,\n                                                   0,\n                                                   get_temperature(\n                                                           &THERMOMETER));\n}\n\nvoid temperature_add_instance(anjay_t *anjay, anjay_iid_t iid) {\n    (void) anjay_ipso_v2_basic_sensor_instance_add(\n            anjay,\n            ANJAY_DEMO_TEMPERATURE_OID,\n            iid,\n            get_temperature(&THERMOMETER),\n            &(anjay_ipso_v2_basic_sensor_meta_t) {\n                .unit = ANJAY_DEMO_TEMPERATURE_UNIT,\n                .min_max_measured_value_present = true,\n                .min_range_value = 0,\n                .max_range_value = (double) ANJAY_DEMO_TEMPERATURE_MAX_VALUE\n            });\n}\n\nvoid temperature_remove_instance(anjay_t *anjay, anjay_iid_t iid) {\n    (void) anjay_ipso_v2_basic_sensor_instance_remove(\n            anjay, ANJAY_DEMO_TEMPERATURE_OID, iid);\n}\n\n#define ANJAY_DEMO_ACCELEROMETER_UNIT \"m/s2\"\n#define ANJAY_DEMO_ACCELEROMETER_OID 3313\n#define ANJAY_DEMO_ACCELEROMETER_MAX 42\n#define ANJAY_DEMO_ACCELEROMETER_CHANGE 17\n\n#define ANJAY_DEMO_ACCELEROMETER_MAX_INSTANCE_NUM 16\n\nstatic anjay_ipso_v2_3d_sensor_value_t get_accelerometer_value(void) {\n    static int counter = 1;\n    double x_value = (double) counter;\n    counter = (counter + ANJAY_DEMO_ACCELEROMETER_CHANGE)\n              % (ANJAY_DEMO_ACCELEROMETER_MAX + 1);\n    double y_value = (double) counter;\n    counter = (counter + ANJAY_DEMO_ACCELEROMETER_CHANGE)\n              % (ANJAY_DEMO_ACCELEROMETER_MAX + 1);\n    double z_value = (double) counter;\n    counter = (counter + ANJAY_DEMO_ACCELEROMETER_CHANGE)\n              % (ANJAY_DEMO_ACCELEROMETER_MAX + 1);\n    return (anjay_ipso_v2_3d_sensor_value_t) {\n        .x = x_value,\n        .y = y_value,\n        .z = z_value\n    };\n}\n\nint install_accelerometer_object(anjay_t *anjay) {\n    anjay_ipso_v2_3d_sensor_value_t value = get_accelerometer_value();\n    if (anjay_ipso_v2_3d_sensor_install(\n                anjay,\n                ANJAY_DEMO_ACCELEROMETER_OID,\n                NULL,\n                ANJAY_DEMO_ACCELEROMETER_MAX_INSTANCE_NUM)\n            || anjay_ipso_v2_3d_sensor_instance_add(\n                       anjay,\n                       ANJAY_DEMO_ACCELEROMETER_OID,\n                       0,\n                       &value,\n                       &(anjay_ipso_v2_3d_sensor_meta_t) {\n                           .unit = ANJAY_DEMO_ACCELEROMETER_UNIT,\n                           .min_range_value = 0.0,\n                           .max_range_value =\n                                   (double) ANJAY_DEMO_ACCELEROMETER_MAX,\n                           .y_axis_present = true,\n                           .z_axis_present = true\n\n                       })) {\n        avs_log(ipso, ERROR, \"Could not install Accelerometer object\");\n        return -1;\n    }\n\n    return 0;\n}\n\nvoid accelerometer_update_handler(anjay_t *anjay) {\n    anjay_ipso_v2_3d_sensor_value_t value = get_accelerometer_value();\n    (void) anjay_ipso_v2_3d_sensor_value_update(\n            anjay, ANJAY_DEMO_ACCELEROMETER_OID, 0, &value);\n}\n\nvoid accelerometer_add_instance(anjay_t *anjay, anjay_iid_t iid) {\n    anjay_ipso_v2_3d_sensor_value_t value = get_accelerometer_value();\n    (void) anjay_ipso_v2_3d_sensor_instance_add(\n            anjay,\n            ANJAY_DEMO_ACCELEROMETER_OID,\n            iid,\n            &value,\n            &(anjay_ipso_v2_3d_sensor_meta_t) {\n                .unit = ANJAY_DEMO_ACCELEROMETER_UNIT,\n                .min_range_value = 0.0,\n                .max_range_value = (double) ANJAY_DEMO_ACCELEROMETER_MAX,\n                .y_axis_present = true,\n                .z_axis_present = true\n            });\n}\n\nvoid accelerometer_remove_instance(anjay_t *anjay, anjay_iid_t iid) {\n    (void) anjay_ipso_v2_3d_sensor_instance_remove(\n            anjay, ANJAY_DEMO_ACCELEROMETER_OID, iid);\n}\n\n#define ANJAY_DEMO_PUSH_BUTTON_MAX_INSTANCE_NUM 16\n\nint install_push_button_object(anjay_t *anjay) {\n    if (anjay_ipso_button_install(anjay,\n                                  ANJAY_DEMO_PUSH_BUTTON_MAX_INSTANCE_NUM)\n            || anjay_ipso_button_instance_add(anjay, 0, \"Fake demo Button\")) {\n        avs_log(ipso, ERROR, \"Could not install Push Button object\");\n        return -1;\n    }\n\n    return 0;\n}\n"
  },
  {
    "path": "demo/objects/location.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#if !defined(_POSIX_C_SOURCE) && !defined(__APPLE__)\n#    define _POSIX_C_SOURCE 200809L\n#endif\n\n#include \"../demo_utils.h\"\n#include \"../objects.h\"\n\n#include <assert.h>\n#include <math.h>\n#include <stdio.h>\n#include <string.h>\n\n#include <avsystem/commons/avs_utils.h>\n\n#define LOCATION_LATITUDE 0\n#define LOCATION_LONGITUDE 1\n#define LOCATION_ALTITUDE 2\n#define LOCATION_RADIUS 3\n#define LOCATION_VELOCITY 4\n#define LOCATION_TIMESTAMP 5\n#define LOCATION_SPEED 6\n\ntypedef struct {\n    double value_mps;\n    double bearing_deg_cw_n;\n} velocity_t;\n\ntypedef struct {\n    const anjay_dm_object_def_t *def;\n    time_t timestamp;\n    unsigned rand_seed;\n    double latitude;\n    double longitude;\n    velocity_t velocity;\n    FILE *csv;\n    time_t csv_frequency;\n} location_t;\n\nstatic inline location_t *\nget_location(const anjay_dm_object_def_t *const *obj_ptr) {\n    assert(obj_ptr);\n    return AVS_CONTAINER_OF(obj_ptr, location_t, def);\n}\n\nstatic int ret_velocity(anjay_output_ctx_t *ctx, const velocity_t *velocity) {\n    // see http://www.3gpp.org/DynaReport/23032.htm section 8 for details\n    // we are using the \"Horizontal Velocity\" mode only\n    uint8_t data[4];\n\n    uint16_t bearing = (uint16_t) velocity->bearing_deg_cw_n;\n    data[0] = (uint8_t) ((bearing >> 8) & 1);\n    data[1] = (uint8_t) (bearing & UINT8_MAX);\n\n    double value_kph = velocity->value_mps * 3.6;\n    uint16_t value_kph_u16;\n    if (!isfinite(value_kph)) {\n        value_kph_u16 = 0;\n    } else if (value_kph > UINT16_MAX) {\n        value_kph_u16 = UINT16_MAX;\n    } else {\n        value_kph_u16 = (uint16_t) (value_kph + 0.5);\n    }\n    data[2] = (uint8_t) (value_kph_u16 >> 8);\n    data[3] = (uint8_t) (value_kph_u16 & UINT8_MAX);\n\n    return anjay_ret_bytes(ctx, data, sizeof(data));\n}\n\nstatic int location_list_resources(anjay_t *anjay,\n                                   const anjay_dm_object_def_t *const *obj_ptr,\n                                   anjay_iid_t iid,\n                                   anjay_dm_resource_list_ctx_t *ctx) {\n    (void) anjay;\n    (void) obj_ptr;\n    (void) iid;\n\n    anjay_dm_emit_res(ctx, LOCATION_LATITUDE, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, LOCATION_LONGITUDE, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, LOCATION_ALTITUDE, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, LOCATION_RADIUS, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, LOCATION_VELOCITY, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, LOCATION_TIMESTAMP, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_PRESENT);\n    return 0;\n}\n\nstatic int location_resource_read(anjay_t *anjay,\n                                  const anjay_dm_object_def_t *const *obj_ptr,\n                                  anjay_iid_t iid,\n                                  anjay_rid_t rid,\n                                  anjay_riid_t riid,\n                                  anjay_output_ctx_t *ctx) {\n    (void) anjay;\n    (void) obj_ptr;\n    (void) iid;\n    (void) riid;\n    assert(riid == ANJAY_ID_INVALID);\n    location_t *location = get_location(obj_ptr);\n\n    switch (rid) {\n    case LOCATION_LATITUDE:\n        return anjay_ret_double(ctx, location->latitude);\n    case LOCATION_LONGITUDE:\n        return anjay_ret_double(ctx, location->longitude);\n    case LOCATION_ALTITUDE:\n        return anjay_ret_double(ctx, 0.0);\n    case LOCATION_RADIUS:\n        return anjay_ret_double(ctx, 0.0);\n    case LOCATION_VELOCITY:\n        return ret_velocity(ctx, &location->velocity);\n    case LOCATION_TIMESTAMP:\n        return anjay_ret_i64(ctx, location->timestamp);\n    default:\n        AVS_UNREACHABLE(\"Read called on unknown resource\");\n        return ANJAY_ERR_NOT_FOUND;\n    }\n}\n\nstatic void normalize_angle(double *value) {\n    *value = fmod(fmod(*value + 180.0, 360.0) + 360.0, 360.0) - 180.0;\n}\n\nstatic void normalize_location(location_t *location) {\n    // some extremely weird values, including non-finite ones,\n    // may occur with coordinates close to the North or South Pole\n\n    // longitude\n    if (!isfinite(location->longitude)) {\n        location->longitude = 0.0;\n    }\n    normalize_angle(&location->longitude);\n\n    // latitude\n    if (!isfinite(location->latitude)) {\n        if (location->latitude < 0.0) {\n            location->latitude = -90.0;\n        } else {\n            location->latitude = 90.0;\n        }\n    }\n    normalize_angle(&location->latitude);\n    if (location->latitude > 90.0) {\n        location->latitude = 180.0 - location->latitude;\n        location->longitude += 180.0;\n        normalize_angle(&location->longitude);\n    } else if (location->latitude < -90.0) {\n        location->latitude = -180.0 - location->latitude;\n        location->longitude += 180.0;\n        normalize_angle(&location->longitude);\n    }\n}\n\nstatic void get_meters_per_degree(double *out_m_per_deg_lat,\n                                  double *out_m_per_deg_lon,\n                                  double latitude) {\n    double lat_rad = deg2rad(latitude);\n    // The formulas come from\n    // https://en.wikipedia.org/wiki/Geographic_coordinate_system#Expressing_latitude_and_longitude_as_linear_units\n    // (retrieved 2016-01-12)\n    *out_m_per_deg_lat = 111132.92 - 559.82 * cos(2.0 * lat_rad)\n                         + 1.175 * cos(4.0 * lat_rad)\n                         - 0.0023 * cos(6.0 * lat_rad);\n    *out_m_per_deg_lon = 111412.84 * cos(lat_rad) - 93.5 * cos(3.0 * lat_rad)\n                         - 0.118 * cos(5.0 * lat_rad);\n}\n\nstatic double rand_double(unsigned *seed, double min, double max) {\n    return min + (max - min) * avs_rand_r(seed) / (double) AVS_RAND_MAX;\n}\n\nstatic void calculate_velocity(velocity_t *out,\n                               double lat1,\n                               double lon1,\n                               double lat2,\n                               double lon2,\n                               double time_change_s) {\n    out->value_mps = geo_distance_m(lat1, lon1, lat2, lon2) / time_change_s;\n\n    double dlon = lon2 - lon1;\n    normalize_angle(&dlon);\n    double dlat = lat2 - lat1;\n    normalize_angle(&dlat);\n    out->bearing_deg_cw_n = rad2deg(atan2(dlon, dlat));\n    if (!isfinite(out->bearing_deg_cw_n)) {\n        out->bearing_deg_cw_n = 0.0;\n    } else if (out->bearing_deg_cw_n < 0.0) {\n        out->bearing_deg_cw_n += 360.0;\n    }\n}\n\nstatic int update_location_random(location_t *location) {\n    double m_per_deg_lat, m_per_deg_lon;\n    get_meters_per_degree(&m_per_deg_lat, &m_per_deg_lon, location->latitude);\n\n    // random movement of at most 1 m in each direction\n    double lat_change = rand_double(&location->rand_seed, -1.0 / m_per_deg_lat,\n                                    1.0 / m_per_deg_lat);\n    double lon_change = rand_double(&location->rand_seed, -1.0 / m_per_deg_lon,\n                                    1.0 / m_per_deg_lon);\n\n    double old_lat = location->latitude;\n    double old_lon = location->longitude;\n    location->latitude += lat_change;\n    location->longitude += lon_change;\n    normalize_location(location);\n\n    calculate_velocity(&location->velocity, old_lat, old_lon,\n                       location->latitude, location->longitude, 1.0);\n    return 1;\n}\n\nstatic int\ntry_parse_location_line(location_t *location, char *line, size_t line_length) {\n    if (line_length > 1 && line[line_length - 1] == '\\n') {\n        line[--line_length] = '\\0';\n    }\n\n    double latitude = 0.0, longitude = 0.0;\n    double vel_mps = 0.0, vel_bearing_deg_cw_n = 0.0;\n    int chars_read = 0;\n    int scanf_result =\n            sscanf(line, \" %lf , %lf %n\", &latitude, &longitude, &chars_read);\n    if (scanf_result < 2 || !latitude_valid(latitude)\n            || !longitude_valid(longitude)) {\n        goto invalid;\n    }\n\n    if ((size_t) chars_read < line_length) {\n        int more_chars_read;\n        scanf_result = sscanf(line + chars_read, \", %lf , %lf %n\", &vel_mps,\n                              &vel_bearing_deg_cw_n, &more_chars_read);\n        if (scanf_result < 2 || !velocity_mps_valid(vel_mps)\n                || !velocity_bearing_deg_cw_n_valid(vel_bearing_deg_cw_n)\n                || (size_t) (chars_read + more_chars_read) != line_length) {\n            goto invalid;\n        }\n        location->velocity.value_mps = vel_mps;\n        location->velocity.bearing_deg_cw_n = vel_bearing_deg_cw_n;\n    } else {\n        calculate_velocity(&location->velocity, location->latitude,\n                           location->longitude, latitude, longitude,\n                           (double) location->csv_frequency);\n    }\n    location->latitude = latitude;\n    location->longitude = longitude;\n    return 1;\n\ninvalid:\n    demo_log(DEBUG, \"Invalid CSV line, ignoring: %s\", line);\n    return 0;\n}\n\nstatic int update_location_csv(location_t *location) {\n    if (!location->csv) {\n        return -1;\n    }\n\n    if (location->timestamp % location->csv_frequency != 0) {\n        return 0;\n    }\n\n    int result = 0;\n    while (!feof(location->csv) && result == 0) {\n        char line[256];\n        if (fgets(line, sizeof(line), location->csv)) {\n            size_t length = strlen(line);\n            result = try_parse_location_line(location, line, length);\n        } else if (ferror(location->csv)) {\n            result = -1;\n        }\n    }\n\n    if (result < 0) {\n        demo_log(ERROR, \"Could not read data from CSV, \"\n                        \"switching back to random location mode\");\n        fclose(location->csv);\n        location->csv = NULL;\n    }\n    return result;\n}\n\nstatic bool update_location(location_t *location) {\n    int result = update_location_csv(location);\n    if (result < 0) {\n        result = update_location_random(location);\n    }\n    return result;\n}\n\nstatic const anjay_dm_object_def_t LOCATION = {\n    .oid = DEMO_OID_LOCATION,\n    .handlers = {\n        .list_instances = anjay_dm_list_instances_SINGLE,\n        .list_resources = location_list_resources,\n        .resource_read = location_resource_read\n    }\n};\n\nconst anjay_dm_object_def_t **location_object_create(void) {\n    location_t *repr = (location_t *) avs_calloc(1, sizeof(location_t));\n    if (!repr) {\n        return NULL;\n    }\n\n    repr->def = &LOCATION;\n    repr->timestamp = avs_time_real_now().since_real_epoch.seconds;\n    repr->rand_seed = (unsigned) repr->timestamp;\n\n    // initial coordinates are of the AVSystem HQ\n    repr->latitude = 50.083463;\n    repr->longitude = 19.901325;\n\n    return &repr->def;\n}\n\nvoid location_object_release(const anjay_dm_object_def_t **def) {\n    if (def) {\n        avs_free(get_location(def));\n    }\n}\n\nvoid location_notify_time_dependent(anjay_t *anjay,\n                                    const anjay_dm_object_def_t **def) {\n    location_t *repr = get_location(def);\n    time_t current_time = time(NULL);\n    if (current_time != repr->timestamp) {\n        bool updated = false;\n        do {\n            updated = (update_location(repr) || updated);\n        } while (++repr->timestamp < current_time);\n        if (updated) {\n            anjay_notify_changed(anjay, (*def)->oid, 0, LOCATION_LATITUDE);\n            anjay_notify_changed(anjay, (*def)->oid, 0, LOCATION_LONGITUDE);\n            anjay_notify_changed(anjay, (*def)->oid, 0, LOCATION_VELOCITY);\n            anjay_notify_changed(anjay, (*def)->oid, 0, LOCATION_TIMESTAMP);\n        }\n    }\n}\n\nvoid location_get(const anjay_dm_object_def_t **def,\n                  double *out_latitude,\n                  double *out_longitude) {\n    location_t *repr = get_location(def);\n    *out_latitude = repr->latitude;\n    *out_longitude = repr->longitude;\n}\n\nint location_open_csv(const anjay_dm_object_def_t **def,\n                      const char *file_name,\n                      time_t frequency_s) {\n    location_t *location = get_location(def);\n    if (frequency_s <= 0) {\n        demo_log(ERROR, \"Invalid CSV time frequency: %ld\", (long) frequency_s);\n        return -1;\n    }\n    FILE *csv = fopen(file_name, \"r\");\n    if (!csv) {\n        demo_log(ERROR, \"Could not open CSV: %s\", file_name);\n        return -1;\n    }\n    if (location->csv) {\n        fclose(location->csv);\n    }\n    location->csv = csv;\n    location->csv_frequency = frequency_s;\n    demo_log(INFO, \"CSV loaded: %s (frequency_s = %ld)\", file_name,\n             frequency_s);\n    return 0;\n}\n"
  },
  {
    "path": "demo/objects/portfolio.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n/**\n *\n * LwM2M Object: Portfolio\n * ID: 16, URN: urn:oma:lwm2m:oma:16, Optional, Multiple\n *\n * The Portfolio Object allows to extend the data storage capability of\n * other Object Instances in the LWM2M system, as well as the services which\n * may be used to authenticate and to protect privacy of data contained in\n * those extensions. In addition, a service of data encryption is also defined\n */\n#include \"../objects.h\"\n\n#include <assert.h>\n#include <stdbool.h>\n#include <string.h>\n\n#include <anjay/anjay.h>\n#include <avsystem/commons/avs_defs.h>\n#include <avsystem/commons/avs_list.h>\n\n/**\n * Identity: RW, Multiple, Mandatory\n * type: string, range: N/A, unit: N/A\n * Data Storage extension for other Object Instances.\n *  e.g  for [GSMA]  :\n * 0 : Host Device ID,\n * 1:  Host Device Manufacturer\n * 2:  Host Device Model\n * 3:  Host Device Software Version,\n *\n * This Resource contains data that the GetAuthData executable Resource can\n * work with.\n */\n#define RID_IDENTITY 0\n\n/**\n * GetAuthData: E, Single, Optional\n * type: N/A, range: N/A, unit: N/A\n * Executable resource to trigger Services described in the portfolio object\n * specification, Section 5.2.2. Arguments definitions are described in\n * Section 5.2.1 as well as in table 2.\n */\n#define RID_GETAUTHDATA 1\n\n/**\n * AuthData: R, Multiple, Optional\n * type: string, range: N/A, unit: N/A\n * Buffer which contains the data generated by the  process triggered by a\n * GetAuthData request\n */\n#define RID_AUTHDATA 2\n\n/**\n * AuthStatus: R, Single, Optional\n * type: integer, range: [0-2], unit: N/A\n * This Resource contains the state related to the process triggered by\n * GetAuthData request.\n * 0 :  IDLE_STATE :  AuthData doesn’t contain any valid data\n * 1 :  DATA_AVAIL_STATE : AuthData  contains a valid data\n * 2 :  ERROR_STATE :  an error occurred\n * This state is reset to IDLE_STATE, when the executable resource\n * \"GetAuthData\" is triggered or when the AuthData resource has been\n * returned to the LWM2M Server (READ / NOTIFY).\n */\n#define RID_AUTHSTATUS 3\n\ntypedef enum {\n    HOST_DEVICE_ID = 0,\n    HOST_DEVICE_MANUFACTURER = 1,\n    HOST_DEVICE_MODEL = 2,\n    HOST_DEVICE_SOFTWARE_VERSION = 3,\n    _MAX_IDENTITY_TYPE,\n} portfolio_identity_type_t;\n\n#define MAX_IDENTITY_VALUE_SIZE 256\n\ntypedef struct portfolio_instance_struct {\n    anjay_iid_t iid;\n\n    bool has_identity[_MAX_IDENTITY_TYPE];\n    char identity_value[_MAX_IDENTITY_TYPE][MAX_IDENTITY_VALUE_SIZE];\n} portfolio_instance_t;\n\ntypedef struct portfolio_struct {\n    const anjay_dm_object_def_t *def;\n    AVS_LIST(portfolio_instance_t) instances;\n    AVS_LIST(portfolio_instance_t) backup;\n} portfolio_t;\n\nstatic inline portfolio_t *\nget_obj(const anjay_dm_object_def_t *const *obj_ptr) {\n    assert(obj_ptr);\n    return AVS_CONTAINER_OF(obj_ptr, portfolio_t, def);\n}\n\nstatic portfolio_instance_t *find_instance(const portfolio_t *obj,\n                                           anjay_iid_t iid) {\n    AVS_LIST(portfolio_instance_t) it;\n    AVS_LIST_FOREACH(it, obj->instances) {\n        if (it->iid == iid) {\n            return it;\n        } else if (it->iid > iid) {\n            break;\n        }\n    }\n\n    return NULL;\n}\n\nstatic int list_instances(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr,\n                          anjay_dm_list_ctx_t *ctx) {\n    (void) anjay;\n\n    AVS_LIST(portfolio_instance_t) it;\n    AVS_LIST_FOREACH(it, get_obj(obj_ptr)->instances) {\n        anjay_dm_emit(ctx, it->iid);\n    }\n    return 0;\n}\n\nstatic int instance_create(anjay_t *anjay,\n                           const anjay_dm_object_def_t *const *obj_ptr,\n                           anjay_iid_t iid) {\n    (void) anjay;\n    portfolio_t *obj = get_obj(obj_ptr);\n\n    AVS_LIST(portfolio_instance_t) created =\n            AVS_LIST_NEW_ELEMENT(portfolio_instance_t);\n    if (!created) {\n        return ANJAY_ERR_INTERNAL;\n    }\n\n    created->iid = iid;\n\n    AVS_LIST(portfolio_instance_t) *ptr;\n    AVS_LIST_FOREACH_PTR(ptr, &obj->instances) {\n        if ((*ptr)->iid > created->iid) {\n            break;\n        }\n    }\n\n    AVS_LIST_INSERT(ptr, created);\n    return 0;\n}\n\nstatic int instance_remove(anjay_t *anjay,\n                           const anjay_dm_object_def_t *const *obj_ptr,\n                           anjay_iid_t iid) {\n    (void) anjay;\n    portfolio_t *obj = get_obj(obj_ptr);\n\n    AVS_LIST(portfolio_instance_t) *it;\n    AVS_LIST_FOREACH_PTR(it, &obj->instances) {\n        if ((*it)->iid == iid) {\n            AVS_LIST_DELETE(it);\n            return 0;\n        } else if ((*it)->iid > iid) {\n            break;\n        }\n    }\n\n    assert(0);\n    return ANJAY_ERR_NOT_FOUND;\n}\n\nstatic int instance_reset(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr,\n                          anjay_iid_t iid) {\n    (void) anjay;\n\n    portfolio_instance_t *inst = find_instance(get_obj(obj_ptr), iid);\n    assert(inst);\n\n    memset(inst->identity_value, 0, sizeof(inst->identity_value));\n    memset(inst->has_identity, 0, sizeof(inst->has_identity));\n    return 0;\n}\n\nstatic int list_resources(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr,\n                          anjay_iid_t iid,\n                          anjay_dm_resource_list_ctx_t *ctx) {\n    (void) anjay;\n    (void) obj_ptr;\n    (void) iid;\n    anjay_dm_emit_res(\n            ctx, RID_IDENTITY, ANJAY_DM_RES_RWM, ANJAY_DM_RES_PRESENT);\n    return 0;\n}\n\nstatic int resource_read(anjay_t *anjay,\n                         const anjay_dm_object_def_t *const *obj_ptr,\n                         anjay_iid_t iid,\n                         anjay_rid_t rid,\n                         anjay_riid_t riid,\n                         anjay_output_ctx_t *ctx) {\n    (void) anjay;\n\n    portfolio_t *obj = get_obj(obj_ptr);\n    portfolio_instance_t *inst = find_instance(obj, iid);\n    assert(inst);\n\n    switch (rid) {\n    case RID_IDENTITY:\n        assert(riid < _MAX_IDENTITY_TYPE);\n        assert(inst->has_identity[riid]);\n        return anjay_ret_string(ctx, inst->identity_value[riid]);\n    default:\n        AVS_UNREACHABLE(\"Read called on unknown resource\");\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\nstatic int resource_write(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr,\n                          anjay_iid_t iid,\n                          anjay_rid_t rid,\n                          anjay_riid_t riid,\n                          anjay_input_ctx_t *ctx) {\n    (void) anjay;\n\n    portfolio_t *obj = get_obj(obj_ptr);\n    portfolio_instance_t *inst = find_instance(obj, iid);\n    assert(inst);\n\n    switch (rid) {\n    case RID_IDENTITY: {\n        if (riid >= _MAX_IDENTITY_TYPE) {\n            return ANJAY_ERR_NOT_FOUND;\n        }\n        char value[MAX_IDENTITY_VALUE_SIZE];\n        int result = anjay_get_string(ctx, value, sizeof(value));\n        if (!result) {\n            inst->has_identity[riid] = true;\n            strcpy(inst->identity_value[riid], value);\n        }\n        return result;\n    }\n\n    default:\n        AVS_UNREACHABLE(\"Write called on unknown resource\");\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\nstatic int resource_reset(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr,\n                          anjay_iid_t iid,\n                          anjay_rid_t rid) {\n    (void) anjay;\n    (void) rid;\n\n    portfolio_t *obj = get_obj(obj_ptr);\n    portfolio_instance_t *inst = find_instance(obj, iid);\n    assert(inst);\n\n    assert(rid == RID_IDENTITY);\n    memset(inst->has_identity, 0, sizeof(inst->has_identity));\n    return 0;\n}\n\n#ifdef ANJAY_WITH_LWM2M12\nstatic int resource_instance_remove(anjay_t *anjay,\n                                    const anjay_dm_object_def_t *const *obj_ptr,\n                                    anjay_iid_t iid,\n                                    anjay_rid_t rid,\n                                    anjay_riid_t riid) {\n    (void) anjay;\n    (void) rid;\n\n    portfolio_t *obj = get_obj(obj_ptr);\n    portfolio_instance_t *inst = find_instance(obj, iid);\n    assert(inst);\n\n    assert(rid == RID_IDENTITY);\n    assert(riid < _MAX_IDENTITY_TYPE);\n    assert(inst->has_identity[riid]);\n    inst->has_identity[riid] = false;\n    return 0;\n}\n#endif // ANJAY_WITH_LWM2M12\n\nstatic int list_resource_instances(anjay_t *anjay,\n                                   const anjay_dm_object_def_t *const *obj_ptr,\n                                   anjay_iid_t iid,\n                                   anjay_rid_t rid,\n                                   anjay_dm_list_ctx_t *ctx) {\n    (void) anjay;\n\n    portfolio_t *obj = get_obj(obj_ptr);\n    portfolio_instance_t *inst = find_instance(obj, iid);\n    assert(inst);\n\n    switch (rid) {\n    case RID_IDENTITY: {\n        for (anjay_riid_t i = 0; i < _MAX_IDENTITY_TYPE; ++i) {\n            if (inst->has_identity[i]) {\n                anjay_dm_emit(ctx, i);\n            }\n        }\n        return 0;\n    }\n    default:\n        AVS_UNREACHABLE(\n                \"Attempted to list instances in a single-instance resource\");\n        return ANJAY_ERR_INTERNAL;\n    }\n}\n\nstatic int transaction_begin(anjay_t *anjay,\n                             const anjay_dm_object_def_t *const *obj_ptr) {\n    (void) anjay;\n    portfolio_t *obj = get_obj(obj_ptr);\n    assert(!obj->backup);\n    obj->backup = AVS_LIST_SIMPLE_CLONE(obj->instances);\n    if (!obj->backup && obj->instances) {\n        return ANJAY_ERR_INTERNAL;\n    }\n    return 0;\n}\n\nstatic int transaction_commit(anjay_t *anjay,\n                              const anjay_dm_object_def_t *const *obj_ptr) {\n    (void) anjay;\n    portfolio_t *obj = get_obj(obj_ptr);\n    AVS_LIST_CLEAR(&obj->backup);\n    return 0;\n}\n\nstatic int transaction_rollback(anjay_t *anjay,\n                                const anjay_dm_object_def_t *const *obj_ptr) {\n    (void) anjay;\n    portfolio_t *obj = get_obj(obj_ptr);\n    AVS_LIST_CLEAR(&obj->instances);\n    obj->instances = obj->backup;\n    obj->backup = NULL;\n    return 0;\n}\n\nstatic const anjay_dm_object_def_t OBJ_DEF = {\n    .oid = 16,\n    .handlers = {\n        .list_instances = list_instances,\n        .instance_create = instance_create,\n        .instance_remove = instance_remove,\n        .instance_reset = instance_reset,\n\n        .list_resources = list_resources,\n        .resource_read = resource_read,\n        .resource_write = resource_write,\n        .resource_reset = resource_reset,\n        .list_resource_instances = list_resource_instances,\n\n        .transaction_begin = transaction_begin,\n        .transaction_validate = anjay_dm_transaction_NOOP,\n        .transaction_commit = transaction_commit,\n        .transaction_rollback = transaction_rollback\n#ifdef ANJAY_WITH_LWM2M12\n        ,\n        .resource_instance_remove = resource_instance_remove\n#endif // ANJAY_WITH_LWM2M12\n    }\n};\n\nconst anjay_dm_object_def_t **portfolio_object_create(void) {\n    portfolio_t *obj = (portfolio_t *) avs_calloc(1, sizeof(portfolio_t));\n    if (!obj) {\n        return NULL;\n    }\n    obj->def = &OBJ_DEF;\n    return &obj->def;\n}\n\nvoid portfolio_object_release(const anjay_dm_object_def_t **def) {\n    if (def) {\n        portfolio_t *obj = get_obj(def);\n        AVS_LIST_CLEAR(&obj->instances);\n        avs_free(obj);\n    }\n}\n\nint portfolio_get_instances(const anjay_dm_object_def_t **def,\n                            AVS_LIST(anjay_iid_t) *out) {\n    portfolio_t *obj = get_obj(def);\n    assert(!*out);\n    AVS_LIST(portfolio_instance_t) it;\n    AVS_LIST_FOREACH(it, obj->instances) {\n        if (!(*out = AVS_LIST_NEW_ELEMENT(anjay_iid_t))) {\n            demo_log(ERROR, \"out of memory\");\n            return -1;\n        }\n        **out = it->iid;\n        AVS_LIST_ADVANCE_PTR(&out);\n    }\n    return 0;\n}\n"
  },
  {
    "path": "demo/objects/test.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include \"../demo.h\"\n#include \"../demo_utils.h\"\n#include \"../objects.h\"\n\n#include <assert.h>\n#include <string.h>\n\n#include <avsystem/commons/avs_memory.h>\n#include <avsystem/commons/avs_utils.h>\n#include <avsystem/commons/avs_vector.h>\n\n#define TEST_RES_TIMESTAMP 0\n#define TEST_RES_COUNTER 1\n#define TEST_RES_INCREMENT_COUNTER 2\n#define TEST_RES_INT_ARRAY 3\n#define TEST_RES_LAST_EXEC_ARGS_ARRAY 4\n#define TEST_RES_BYTES 5\n#define TEST_RES_BYTES_SIZE 6\n#define TEST_RES_BYTES_BURST 7\n// ID 8 was historically used for TEST_RES_EMPTY\n#define TEST_RES_INIT_INT_ARRAY 9\n#define TEST_RES_RAW_BYTES 10\n#define TEST_RES_OPAQUE_ARRAY 11\n#define TEST_RES_INT 12\n#define TEST_RES_BOOL 13\n#define TEST_RES_FLOAT 14\n#define TEST_RES_STRING 15\n#define TEST_RES_OBJLNK 16\n#define TEST_RES_BYTES_ZERO_BEGIN 17\n#define TEST_RES_DOUBLE 18\n#ifdef ANJAY_WITH_LWM2M11\n#    define TEST_RES_UINT 19\n#    define TEST_RES_ULONG 20\n#endif // ANJAY_WITH_LWM2M11\n#define TEST_RES_TOGGLE_BOOL 21\n#define TEST_RES_BOOL_ARRAY 22\n#define TEST_RES_INIT_BOOL_ARRAY 23\n\ntypedef struct test_int_array_entry_struct {\n    anjay_riid_t index;\n    int32_t value;\n} test_int_array_entry_t;\n\ntypedef struct test_bool_array_entry_struct {\n    anjay_riid_t index;\n    bool value;\n} test_bool_array_entry_t;\n\ntypedef struct {\n    int number;\n    char *value;\n} test_exec_arg_t;\n\ntypedef struct test_instance_struct {\n    anjay_iid_t iid;\n    int32_t execute_counter;\n    bool volatile_res_present;\n    int32_t volatile_res_value;\n    int32_t bytes_size;\n    int32_t bytes_burst;\n    bool bytes_zero_begin;\n    void *raw_bytes;\n    size_t raw_bytes_size;\n    AVS_LIST(test_int_array_entry_t) int_array;\n    AVS_LIST(test_exec_arg_t) last_exec_args;\n    int32_t test_res_int;\n#ifdef ANJAY_WITH_LWM2M11\n    uint32_t test_res_uint;\n    uint64_t test_res_ulong;\n#endif // ANJAY_WITH_LWM2M11\n    bool test_res_bool;\n    float test_res_float;\n    double test_res_double;\n    char test_res_string[128];\n    struct {\n        anjay_oid_t oid;\n        anjay_iid_t iid;\n    } test_res_objlnk;\n    AVS_LIST(test_bool_array_entry_t) bool_array;\n} test_instance_t;\n\ntypedef struct {\n    const anjay_dm_object_def_t *def;\n    AVS_LIST(struct test_instance_struct) instances;\n} test_repr_t;\n\nstatic inline test_repr_t *\nget_test(const anjay_dm_object_def_t *const *obj_ptr) {\n    assert(obj_ptr);\n    return AVS_CONTAINER_OF(obj_ptr, test_repr_t, def);\n}\n\nstatic test_instance_t *find_instance(const test_repr_t *repr,\n                                      anjay_iid_t iid) {\n    AVS_LIST(test_instance_t) it;\n    AVS_LIST_FOREACH(it, repr->instances) {\n        if (it->iid == iid) {\n            return it;\n        }\n    }\n\n    return NULL;\n}\n\nstatic int test_list_instances(anjay_t *anjay,\n                               const anjay_dm_object_def_t *const *obj_ptr,\n                               anjay_dm_list_ctx_t *ctx) {\n    (void) anjay;\n\n    test_repr_t *test = get_test(obj_ptr);\n    AVS_LIST(test_instance_t) it;\n    AVS_LIST_FOREACH(it, test->instances) {\n        anjay_dm_emit(ctx, it->iid);\n    }\n    return 0;\n}\n\nstatic int test_instance_create(anjay_t *anjay,\n                                const anjay_dm_object_def_t *const *obj_ptr,\n                                anjay_iid_t iid) {\n    (void) anjay;\n    test_repr_t *test = get_test(obj_ptr);\n\n    AVS_LIST(test_instance_t) created = AVS_LIST_NEW_ELEMENT(test_instance_t);\n    if (!created) {\n        return ANJAY_ERR_INTERNAL;\n    }\n\n    created->iid = iid;\n    created->execute_counter = 0;\n    created->bytes_size = 0;\n    created->bytes_burst = 1000;\n    created->bytes_zero_begin = true;\n\n    AVS_LIST(test_instance_t) *ptr;\n    AVS_LIST_FOREACH_PTR(ptr, &test->instances) {\n        if ((*ptr)->iid > created->iid) {\n            break;\n        }\n    }\n\n    AVS_LIST_INSERT(ptr, created);\n    return 0;\n}\n\nstatic void release_instance(test_instance_t *inst) {\n    AVS_LIST_CLEAR(&inst->last_exec_args) {\n        avs_free(inst->last_exec_args->value);\n    }\n\n    avs_free(inst->raw_bytes);\n    AVS_LIST_CLEAR(&inst->int_array);\n    AVS_LIST_CLEAR(&inst->bool_array);\n}\n\nstatic int test_instance_remove(anjay_t *anjay,\n                                const anjay_dm_object_def_t *const *obj_ptr,\n                                anjay_iid_t iid) {\n    (void) anjay;\n    test_repr_t *test = get_test(obj_ptr);\n\n    AVS_LIST(test_instance_t) *it;\n    AVS_LIST_FOREACH_PTR(it, &test->instances) {\n        if ((*it)->iid == iid) {\n            release_instance(*it);\n            AVS_LIST_DELETE(it);\n            return 0;\n        }\n    }\n\n    assert(0);\n    return ANJAY_ERR_NOT_FOUND;\n}\n\nstatic int test_instance_reset(anjay_t *anjay,\n                               const anjay_dm_object_def_t *const *obj_ptr,\n                               anjay_iid_t iid) {\n    (void) anjay;\n\n    test_instance_t *inst = find_instance(get_test(obj_ptr), iid);\n    assert(inst);\n    inst->volatile_res_present = false;\n    return 0;\n}\n\nstatic int test_list_resources(anjay_t *anjay,\n                               const anjay_dm_object_def_t *const *obj_ptr,\n                               anjay_iid_t iid,\n                               anjay_dm_resource_list_ctx_t *ctx) {\n    (void) anjay;\n    (void) obj_ptr;\n    (void) iid;\n\n    anjay_dm_emit_res(ctx, TEST_RES_TIMESTAMP, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, TEST_RES_COUNTER, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, TEST_RES_INCREMENT_COUNTER, ANJAY_DM_RES_E,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, TEST_RES_INT_ARRAY, ANJAY_DM_RES_RWM,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, TEST_RES_LAST_EXEC_ARGS_ARRAY, ANJAY_DM_RES_RM,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, TEST_RES_BYTES, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, TEST_RES_BYTES_SIZE, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, TEST_RES_BYTES_BURST, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, TEST_RES_INIT_INT_ARRAY, ANJAY_DM_RES_E,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, TEST_RES_RAW_BYTES, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, TEST_RES_OPAQUE_ARRAY, ANJAY_DM_RES_RM,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, TEST_RES_INT, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, TEST_RES_BOOL, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, TEST_RES_FLOAT, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, TEST_RES_STRING, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, TEST_RES_OBJLNK, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, TEST_RES_BYTES_ZERO_BEGIN, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, TEST_RES_DOUBLE, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_PRESENT);\n#ifdef ANJAY_WITH_LWM2M11\n    anjay_dm_emit_res(ctx, TEST_RES_UINT, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, TEST_RES_ULONG, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_PRESENT);\n#endif // ANJAY_WITH_LWM2M11\n    anjay_dm_emit_res(ctx, TEST_RES_TOGGLE_BOOL, ANJAY_DM_RES_E,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, TEST_RES_BOOL_ARRAY, ANJAY_DM_RES_RWM,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, TEST_RES_INIT_BOOL_ARRAY, ANJAY_DM_RES_E,\n                      ANJAY_DM_RES_PRESENT);\n    return 0;\n}\n\nstatic int test_resource_read(anjay_t *anjay,\n                              const anjay_dm_object_def_t *const *obj_ptr,\n                              anjay_iid_t iid,\n                              anjay_rid_t rid,\n                              anjay_riid_t riid,\n                              anjay_output_ctx_t *ctx) {\n    (void) anjay;\n\n    test_repr_t *test = get_test(obj_ptr);\n    test_instance_t *inst = find_instance(test, iid);\n    assert(inst);\n\n    switch (rid) {\n    case TEST_RES_TIMESTAMP:\n        assert(riid == ANJAY_ID_INVALID);\n        return anjay_ret_i64(ctx, avs_time_real_now().since_real_epoch.seconds);\n    case TEST_RES_COUNTER:\n        assert(riid == ANJAY_ID_INVALID);\n        return anjay_ret_i32(ctx, (int32_t) inst->execute_counter);\n    case TEST_RES_INT_ARRAY: {\n        test_int_array_entry_t *it;\n        AVS_LIST_FOREACH(it, inst->int_array) {\n            if (it->index == riid) {\n                return anjay_ret_i32(ctx, it->value);\n            }\n        }\n        return ANJAY_ERR_NOT_FOUND;\n    }\n    case TEST_RES_LAST_EXEC_ARGS_ARRAY: {\n        test_exec_arg_t *it = NULL;\n        AVS_LIST_FOREACH(it, inst->last_exec_args) {\n            if ((anjay_riid_t) it->number == riid) {\n                return anjay_ret_string(ctx, it->value ? it->value : \"\");\n            }\n        }\n        return ANJAY_ERR_NOT_FOUND;\n    }\n    case TEST_RES_BYTES: {\n        assert(riid == ANJAY_ID_INVALID);\n        int result = 0;\n        if (!inst->bytes_size && !inst->bytes_zero_begin) {\n            // We used to have a bug that caused the library to segfault\n            // if a resource_read handler does not call any anjay_ret_*\n            // function. This case is used to check whether we do not\n            // crash in such situations. See T832.\n            return 0;\n        }\n        anjay_ret_bytes_ctx_t *bytes_ctx =\n                anjay_ret_bytes_begin(ctx, (size_t) inst->bytes_size);\n        if (!bytes_ctx) {\n            return ANJAY_ERR_INTERNAL;\n        }\n        if (inst->bytes_size) {\n            int32_t bytes_burst = inst->bytes_burst;\n            if (bytes_burst <= 0) {\n                bytes_burst = inst->bytes_size;\n            }\n            char *buffer = (char *) avs_malloc((size_t) bytes_burst);\n            if (!buffer) {\n                demo_log(ERROR, \"Out of memory\");\n                return -1;\n            }\n            int32_t counter = 0;\n            for (int32_t offset = 0; offset < inst->bytes_size;\n                 offset += bytes_burst) {\n                int32_t bytes_to_write = inst->bytes_size - offset;\n                if (bytes_to_write > bytes_burst) {\n                    bytes_to_write = bytes_burst;\n                }\n                for (int32_t i = 0; i < bytes_to_write; ++i) {\n                    buffer[i] = (char) (counter++ % 128);\n                }\n                result = anjay_ret_bytes_append(bytes_ctx, buffer,\n                                                (size_t) bytes_to_write);\n                if (result) {\n                    break;\n                }\n            }\n            avs_free(buffer);\n        }\n        return result;\n    }\n    case TEST_RES_RAW_BYTES:\n        assert(riid == ANJAY_ID_INVALID);\n        return anjay_ret_bytes(ctx, inst->raw_bytes, inst->raw_bytes_size);\n    case TEST_RES_BYTES_SIZE:\n        assert(riid == ANJAY_ID_INVALID);\n        return anjay_ret_i32(ctx, inst->bytes_size);\n    case TEST_RES_BYTES_BURST:\n        assert(riid == ANJAY_ID_INVALID);\n        return anjay_ret_i32(ctx, inst->bytes_burst);\n    case TEST_RES_OPAQUE_ARRAY: {\n        test_int_array_entry_t *it;\n        AVS_LIST_FOREACH(it, inst->int_array) {\n            if (it->index == riid) {\n                break;\n            }\n        }\n        if (!it) {\n            return ANJAY_ERR_NOT_FOUND;\n        }\n        const uint32_t value = avs_convert_be32((uint32_t) it->value);\n        return anjay_ret_bytes(ctx, &value, sizeof(value));\n    }\n    case TEST_RES_INT:\n        assert(riid == ANJAY_ID_INVALID);\n        return anjay_ret_i32(ctx, inst->test_res_int);\n#ifdef ANJAY_WITH_LWM2M11\n    case TEST_RES_UINT:\n        assert(riid == ANJAY_ID_INVALID);\n        return anjay_ret_u32(ctx, inst->test_res_uint);\n    case TEST_RES_ULONG:\n        assert(riid == ANJAY_ID_INVALID);\n        return anjay_ret_u64(ctx, inst->test_res_ulong);\n#endif // ANJAY_WITH_LWM2M11\n    case TEST_RES_BOOL:\n        assert(riid == ANJAY_ID_INVALID);\n        return anjay_ret_bool(ctx, inst->test_res_bool);\n    case TEST_RES_FLOAT:\n        assert(riid == ANJAY_ID_INVALID);\n        return anjay_ret_float(ctx, inst->test_res_float);\n    case TEST_RES_DOUBLE:\n        assert(riid == ANJAY_ID_INVALID);\n        return anjay_ret_double(ctx, inst->test_res_double);\n    case TEST_RES_STRING:\n        assert(riid == ANJAY_ID_INVALID);\n        return anjay_ret_string(ctx, inst->test_res_string);\n    case TEST_RES_OBJLNK:\n        assert(riid == ANJAY_ID_INVALID);\n        return anjay_ret_objlnk(ctx, inst->test_res_objlnk.oid,\n                                inst->test_res_objlnk.iid);\n    case TEST_RES_BYTES_ZERO_BEGIN:\n        assert(riid == ANJAY_ID_INVALID);\n        return anjay_ret_bool(ctx, inst->bytes_zero_begin);\n    case TEST_RES_BOOL_ARRAY: {\n        test_bool_array_entry_t *it;\n        AVS_LIST_FOREACH(it, inst->bool_array) {\n            if (it->index == riid) {\n                return anjay_ret_bool(ctx, it->value);\n            }\n        }\n        return ANJAY_ERR_NOT_FOUND;\n    }\n    default:\n        AVS_UNREACHABLE(\"Read called on unknown or non-readable resource\");\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\nstatic int\ntest_resource_write_to_int_array(AVS_LIST(test_int_array_entry_t) *inst_array,\n                                 anjay_riid_t riid,\n                                 anjay_input_ctx_t *ctx) {\n    assert(inst_array);\n    assert(ctx);\n    int32_t value;\n\n    /* New element will be inserted to the array, value of existing one will get\n     * overwritten. */\n    if (anjay_get_i32(ctx, &value)) {\n        demo_log(ERROR, \"could not read integer\");\n        return ANJAY_ERR_INTERNAL;\n    }\n    bool value_updated = false;\n    AVS_LIST(test_int_array_entry_t) *it;\n    AVS_LIST_FOREACH_PTR(it, inst_array) {\n        if ((*it)->index >= riid) {\n            if ((*it)->index == riid) {\n                (*it)->value = value;\n                value_updated = true;\n            }\n            break;\n        }\n    }\n\n    if (!value_updated) {\n        AVS_LIST(test_int_array_entry_t) list_entry =\n                AVS_LIST_NEW_ELEMENT(test_int_array_entry_t);\n        if (!list_entry) {\n            demo_log(ERROR, \"out of memory\");\n            return ANJAY_ERR_INTERNAL;\n        }\n        list_entry->index = riid;\n        list_entry->value = value;\n        AVS_LIST_INSERT(it, list_entry);\n    }\n    return 0;\n}\n\nstatic int\ntest_resource_write_to_bool_array(AVS_LIST(test_bool_array_entry_t) *inst_array,\n                                  anjay_riid_t riid,\n                                  anjay_input_ctx_t *ctx) {\n    assert(inst_array);\n    assert(ctx);\n    bool value;\n\n    /* New element will be inserted to the array, value of existing one will get\n     * overwritten. */\n    if (anjay_get_bool(ctx, &value)) {\n        demo_log(ERROR, \"could not read bool\");\n        return ANJAY_ERR_INTERNAL;\n    }\n    bool value_updated = false;\n    AVS_LIST(test_bool_array_entry_t) *it;\n    AVS_LIST_FOREACH_PTR(it, inst_array) {\n        if ((*it)->index >= riid) {\n            if ((*it)->index == riid) {\n                (*it)->value = value;\n                value_updated = true;\n            }\n            break;\n        }\n    }\n\n    if (!value_updated) {\n        AVS_LIST(test_bool_array_entry_t) list_entry =\n                AVS_LIST_NEW_ELEMENT(test_bool_array_entry_t);\n        if (!list_entry) {\n            demo_log(ERROR, \"out of memory\");\n            return ANJAY_ERR_INTERNAL;\n        }\n        list_entry->index = riid;\n        list_entry->value = value;\n        AVS_LIST_INSERT(it, list_entry);\n    }\n    return 0;\n}\n\nstatic int test_resource_write(anjay_t *anjay,\n                               const anjay_dm_object_def_t *const *obj_ptr,\n                               anjay_iid_t iid,\n                               anjay_rid_t rid,\n                               anjay_riid_t riid,\n                               anjay_input_ctx_t *ctx) {\n    (void) anjay;\n\n    test_repr_t *test = get_test(obj_ptr);\n    test_instance_t *inst = find_instance(test, iid);\n    assert(inst);\n\n    switch (rid) {\n    case TEST_RES_COUNTER:\n        assert(riid == ANJAY_ID_INVALID);\n        return anjay_get_i32(ctx, &inst->execute_counter);\n    case TEST_RES_INT_ARRAY:\n        return test_resource_write_to_int_array(&inst->int_array, riid, ctx);\n    case TEST_RES_BYTES_SIZE: {\n        assert(riid == ANJAY_ID_INVALID);\n        int32_t value;\n        int result = anjay_get_i32(ctx, &value);\n        if (result) {\n            return result;\n        }\n        if (value < 0) {\n            return ANJAY_ERR_BAD_REQUEST;\n        }\n        inst->bytes_size = value;\n        return anjay_notify_changed(anjay, (*obj_ptr)->oid, iid,\n                                    TEST_RES_BYTES);\n    }\n    case TEST_RES_BYTES_BURST: {\n        assert(riid == ANJAY_ID_INVALID);\n        int32_t value;\n        int result = anjay_get_i32(ctx, &value);\n        if (result) {\n            return result;\n        }\n        /* Prevent infinite loop while bursting data. */\n        if (value <= 0) {\n            return ANJAY_ERR_BAD_REQUEST;\n        }\n        inst->bytes_burst = value;\n        return 0;\n    }\n    case TEST_RES_RAW_BYTES:\n        assert(riid == ANJAY_ID_INVALID);\n        return fetch_bytes(ctx, &inst->raw_bytes, &inst->raw_bytes_size);\n    case TEST_RES_INT:\n        assert(riid == ANJAY_ID_INVALID);\n        return anjay_get_i32(ctx, &inst->test_res_int);\n#ifdef ANJAY_WITH_LWM2M11\n    case TEST_RES_UINT:\n        assert(riid == ANJAY_ID_INVALID);\n        return anjay_get_u32(ctx, &inst->test_res_uint);\n    case TEST_RES_ULONG:\n        assert(riid == ANJAY_ID_INVALID);\n        return anjay_get_u64(ctx, &inst->test_res_ulong);\n#endif // ANJAY_WITH_LWM2M11\n    case TEST_RES_BOOL:\n        assert(riid == ANJAY_ID_INVALID);\n        return anjay_get_bool(ctx, &inst->test_res_bool);\n    case TEST_RES_FLOAT:\n        assert(riid == ANJAY_ID_INVALID);\n        return anjay_get_float(ctx, &inst->test_res_float);\n    case TEST_RES_DOUBLE:\n        assert(riid == ANJAY_ID_INVALID);\n        return anjay_get_double(ctx, &inst->test_res_double);\n    case TEST_RES_STRING:\n        assert(riid == ANJAY_ID_INVALID);\n        return anjay_get_string(ctx, inst->test_res_string,\n                                sizeof(inst->test_res_string));\n    case TEST_RES_OBJLNK:\n        assert(riid == ANJAY_ID_INVALID);\n        return anjay_get_objlnk(ctx, &inst->test_res_objlnk.oid,\n                                &inst->test_res_objlnk.iid);\n    case TEST_RES_BYTES_ZERO_BEGIN:\n        assert(riid == ANJAY_ID_INVALID);\n        return anjay_get_bool(ctx, &inst->bytes_zero_begin);\n    case TEST_RES_BOOL_ARRAY:\n        return test_resource_write_to_bool_array(&inst->bool_array, riid, ctx);\n    default:\n        // Bootstrap Server may try to write to other resources,\n        // so no AVS_UNREACHABLE() here\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\nstatic int read_exec_arg_value(anjay_execute_ctx_t *arg_ctx,\n                               char **out_string) {\n#define VALUE_CHUNK_SIZE 256\n    size_t total_bytes_read = 0;\n    int result = 0;\n\n    while (true) {\n        size_t new_value_size = total_bytes_read + VALUE_CHUNK_SIZE;\n        size_t bytes_read;\n\n        char *new_string = (char *) avs_realloc(*out_string, new_value_size);\n        if (!new_string) {\n            demo_log(ERROR, \"out of memory\");\n            result = ANJAY_ERR_INTERNAL;\n            goto fail;\n        }\n\n        *out_string = new_string;\n\n        result = anjay_execute_get_arg_value(arg_ctx, &bytes_read,\n                                             new_string + total_bytes_read,\n                                             (size_t) VALUE_CHUNK_SIZE);\n\n        if (result < 0) {\n            demo_log(ERROR, \"could not read arg value: %d\", (int) result);\n            goto fail;\n        } else if (result == 0) {\n            // nothing more to read, we're done\n            break;\n        }\n\n        // incomplete read; bigger buffer required\n        assert(result == ANJAY_BUFFER_TOO_SHORT);\n        total_bytes_read += bytes_read;\n    }\n\n    return 0;\n\nfail:\n    avs_free(*out_string);\n    *out_string = NULL;\n    return result;\n}\n\nstatic int read_exec_arg(anjay_execute_ctx_t *arg_ctx,\n                         AVS_LIST(test_exec_arg_t) *list_ptr,\n                         AVS_LIST(test_exec_arg_t) *out_element) {\n    int arg_number;\n    bool has_value;\n\n    int result = anjay_execute_get_next_arg(arg_ctx, &arg_number, &has_value);\n    if (result) {\n        return result;\n    }\n\n    assert(!*out_element);\n    *out_element = AVS_LIST_NEW_ELEMENT(test_exec_arg_t);\n    if (!*out_element) {\n        demo_log(ERROR, \"out of memory\");\n        return ANJAY_ERR_INTERNAL;\n    }\n\n    (*out_element)->number = arg_number;\n\n    if (has_value) {\n        if ((result = read_exec_arg_value(arg_ctx, &(*out_element)->value))) {\n            AVS_LIST_DELETE(out_element);\n            demo_log(ERROR, \"could not get read arg %d value\", arg_number);\n            return result;\n        }\n    }\n\n    AVS_LIST(test_exec_arg_t) *insert_ptr = list_ptr;\n    while (*insert_ptr && (*insert_ptr)->number < (*out_element)->number) {\n        AVS_LIST_ADVANCE_PTR(&insert_ptr);\n    }\n    AVS_LIST_INSERT(insert_ptr, *out_element);\n    return 0;\n}\n\nstatic int read_exec_args(anjay_execute_ctx_t *arg_ctx,\n                          AVS_LIST(test_exec_arg_t) *out_args) {\n    AVS_LIST_CLEAR(out_args) {\n        avs_free((*out_args)->value);\n    }\n\n    int result;\n\n    AVS_LIST(test_exec_arg_t) element = NULL;\n    while (!(result = read_exec_arg(arg_ctx, out_args, &element))) {\n        demo_log(DEBUG, \"got arg %d\", element->number);\n        element = NULL;\n    }\n\n    if (result == ANJAY_EXECUTE_GET_ARG_END) {\n        return 0;\n    }\n\n    AVS_LIST_CLEAR(out_args) {\n        avs_free((*out_args)->value);\n    }\n    return result;\n}\n\nstatic int init_int_array_read_element(test_int_array_entry_t *out_entry,\n                                       anjay_execute_ctx_t *arg_ctx) {\n    int arg_number;\n    bool has_value;\n\n    int result = anjay_execute_get_next_arg(arg_ctx, &arg_number, &has_value);\n    if (result) {\n        return result;\n    }\n\n    char value_buf[16];\n    if ((result = anjay_execute_get_arg_value(arg_ctx, NULL, value_buf,\n                                              sizeof(value_buf)))\n            < 0) {\n        return result;\n    }\n\n    long value;\n    if (demo_parse_long(value_buf, &value) || value < INT32_MIN\n            || value > INT32_MAX) {\n        demo_log(WARNING, \"invalid resource %d value\", arg_number);\n        return ANJAY_ERR_BAD_REQUEST;\n    }\n\n    assert(0 <= arg_number && arg_number <= UINT16_MAX);\n    out_entry->index = (anjay_riid_t) arg_number;\n    out_entry->value = (int32_t) value;\n    return 0;\n}\n\nstatic int init_bool_array_read_element(test_bool_array_entry_t *out_entry,\n                                        anjay_execute_ctx_t *arg_ctx) {\n    int arg_number;\n    bool has_value;\n\n    int result = anjay_execute_get_next_arg(arg_ctx, &arg_number, &has_value);\n    if (result) {\n        return result;\n    }\n\n    char value_buf[16];\n    if ((result = anjay_execute_get_arg_value(arg_ctx, NULL, value_buf,\n                                              sizeof(value_buf)))\n            < 0) {\n        return result;\n    }\n\n    long value;\n    if (demo_parse_long(value_buf, &value) || value < INT32_MIN\n            || value > INT32_MAX) {\n        demo_log(WARNING, \"invalid resource %d value\", arg_number);\n        return ANJAY_ERR_BAD_REQUEST;\n    }\n\n    assert(0 <= arg_number && arg_number <= UINT16_MAX);\n    out_entry->index = (anjay_riid_t) arg_number;\n    out_entry->value = (bool) value;\n    return 0;\n}\n\nstatic int init_int_array(AVS_LIST(test_int_array_entry_t) *out_array,\n                          anjay_execute_ctx_t *arg_ctx) {\n    AVS_LIST(test_int_array_entry_t) new_array = NULL;\n\n    int result = 0;\n    while (!result) {\n        AVS_LIST(test_int_array_entry_t) list_entry =\n                AVS_LIST_NEW_ELEMENT(test_int_array_entry_t);\n        if (!list_entry) {\n            demo_log(ERROR, \"out of memory\");\n            result = ANJAY_ERR_INTERNAL;\n        } else if ((result =\n                            init_int_array_read_element(list_entry, arg_ctx))) {\n            AVS_LIST_DELETE(&list_entry);\n        } else {\n            AVS_LIST(test_int_array_entry_t) *insert_ptr = &new_array;\n            while (*insert_ptr && (*insert_ptr)->index < list_entry->index) {\n                AVS_LIST_ADVANCE_PTR(&insert_ptr);\n            }\n            AVS_LIST_INSERT(insert_ptr, list_entry);\n        }\n    }\n\n    if (result != ANJAY_EXECUTE_GET_ARG_END) {\n        AVS_LIST_CLEAR(&new_array);\n        return result;\n    }\n\n    AVS_LIST_CLEAR(out_array);\n    *out_array = new_array;\n    return 0;\n}\n\nstatic int init_bool_array(AVS_LIST(test_bool_array_entry_t) *out_array,\n                           anjay_execute_ctx_t *arg_ctx) {\n    AVS_LIST(test_bool_array_entry_t) new_array = NULL;\n\n    int result = 0;\n    while (!result) {\n        AVS_LIST(test_bool_array_entry_t) list_entry =\n                AVS_LIST_NEW_ELEMENT(test_bool_array_entry_t);\n        if (!list_entry) {\n            demo_log(ERROR, \"out of memory\");\n            result = ANJAY_ERR_INTERNAL;\n        } else if ((result = init_bool_array_read_element(list_entry,\n                                                          arg_ctx))) {\n            AVS_LIST_DELETE(&list_entry);\n        } else {\n            AVS_LIST(test_bool_array_entry_t) *insert_ptr = &new_array;\n            while (*insert_ptr && (*insert_ptr)->index < list_entry->index) {\n                AVS_LIST_ADVANCE_PTR(&insert_ptr);\n            }\n            AVS_LIST_INSERT(insert_ptr, list_entry);\n        }\n    }\n\n    if (result != ANJAY_EXECUTE_GET_ARG_END) {\n        AVS_LIST_CLEAR(&new_array);\n        return result;\n    }\n\n    AVS_LIST_CLEAR(out_array);\n    *out_array = new_array;\n    return 0;\n}\n\nstatic int test_resource_execute(anjay_t *anjay,\n                                 const anjay_dm_object_def_t *const *obj_ptr,\n                                 anjay_iid_t iid,\n                                 anjay_rid_t rid,\n                                 anjay_execute_ctx_t *arg_ctx) {\n    (void) arg_ctx;\n\n    test_repr_t *test = get_test(obj_ptr);\n    test_instance_t *inst = find_instance(test, iid);\n    assert(inst);\n\n    switch (rid) {\n    case TEST_RES_INCREMENT_COUNTER: {\n        int result = read_exec_args(arg_ctx, &inst->last_exec_args);\n        if (result) {\n            demo_log(ERROR, \"could not save Execute arguments\");\n            return result;\n        }\n\n        ++inst->execute_counter;\n        anjay_notify_changed(anjay, (*obj_ptr)->oid, iid, TEST_RES_COUNTER);\n        anjay_notify_changed(anjay, (*obj_ptr)->oid, iid,\n                             TEST_RES_LAST_EXEC_ARGS_ARRAY);\n        return 0;\n    }\n    case TEST_RES_INIT_INT_ARRAY: {\n        int result = init_int_array(&inst->int_array, arg_ctx);\n        if (result) {\n            return result;\n        }\n\n        anjay_notify_changed(anjay, (*obj_ptr)->oid, iid, TEST_RES_INT_ARRAY);\n        anjay_notify_changed(anjay, (*obj_ptr)->oid, iid,\n                             TEST_RES_OPAQUE_ARRAY);\n        return 0;\n    }\n    case TEST_RES_TOGGLE_BOOL:\n        inst->test_res_bool = !inst->test_res_bool;\n        anjay_notify_changed(anjay, (*obj_ptr)->oid, iid, TEST_RES_BOOL);\n\n        if (inst->bool_array) {\n            inst->bool_array->value = !inst->bool_array->value;\n            anjay_notify_changed(anjay, (*obj_ptr)->oid, iid,\n                                 TEST_RES_BOOL_ARRAY);\n        }\n        return 0;\n    case TEST_RES_INIT_BOOL_ARRAY: {\n        int result = init_bool_array(&inst->bool_array, arg_ctx);\n        if (result) {\n            return result;\n        }\n\n        anjay_notify_changed(anjay, (*obj_ptr)->oid, iid, TEST_RES_BOOL_ARRAY);\n        return 0;\n    }\n    default:\n        AVS_UNREACHABLE(\"Execute called on unknown or non-executable resource\");\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\nstatic int test_resource_reset(anjay_t *anjay,\n                               const anjay_dm_object_def_t *const *obj_ptr,\n                               anjay_iid_t iid,\n                               anjay_rid_t rid) {\n    (void) anjay;\n    (void) rid;\n\n    test_repr_t *test = get_test(obj_ptr);\n    test_instance_t *inst = find_instance(test, iid);\n    assert(inst);\n\n    switch (rid) {\n    case TEST_RES_INT_ARRAY:\n        AVS_LIST_CLEAR(&inst->int_array);\n        break;\n    case TEST_RES_BOOL_ARRAY:\n        AVS_LIST_CLEAR(&inst->bool_array);\n        break;\n    default:\n        AVS_UNREACHABLE(\n                \"Resource reset called on non-mutliple-instance resource\");\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n    return 0;\n}\n\n#ifdef ANJAY_WITH_LWM2M12\nstatic int\ntest_resource_instance_remove(anjay_t *anjay,\n                              const anjay_dm_object_def_t *const *obj_ptr,\n                              anjay_iid_t iid,\n                              anjay_rid_t rid,\n                              anjay_riid_t riid) {\n    (void) anjay;\n\n    test_repr_t *test = get_test(obj_ptr);\n    test_instance_t *inst = find_instance(test, iid);\n    assert(inst);\n\n    switch (rid) {\n    case TEST_RES_INT_ARRAY: {\n        AVS_LIST(test_int_array_entry_t) *it;\n        AVS_LIST_FOREACH_PTR(it, &inst->int_array) {\n            if ((*it)->index == riid) {\n                break;\n            }\n        }\n        assert(it && *it && (*it)->index == riid);\n        AVS_LIST_DELETE(it);\n        break;\n    }\n    case TEST_RES_BOOL_ARRAY: {\n        AVS_LIST(test_bool_array_entry_t) *it;\n        AVS_LIST_FOREACH_PTR(it, &inst->bool_array) {\n            if ((*it)->index == riid) {\n                break;\n            }\n        }\n        assert(it && *it && (*it)->index == riid);\n        AVS_LIST_DELETE(it);\n        break;\n    }\n    default:\n        AVS_UNREACHABLE(\"Resource instance remove called on \"\n                        \"non-mutliple-instance resource\");\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n    return 0;\n}\n#endif // ANJAY_WITH_LWM2M12\n\nstatic int\ntest_list_resource_instances(anjay_t *anjay,\n                             const anjay_dm_object_def_t *const *obj_ptr,\n                             anjay_iid_t iid,\n                             anjay_rid_t rid,\n                             anjay_dm_list_ctx_t *ctx) {\n    (void) anjay;\n\n    test_repr_t *test = get_test(obj_ptr);\n    test_instance_t *inst = find_instance(test, iid);\n    assert(inst);\n\n    switch (rid) {\n    case TEST_RES_INT_ARRAY:\n    case TEST_RES_OPAQUE_ARRAY: {\n        AVS_LIST(test_int_array_entry_t) it;\n        AVS_LIST_FOREACH(it, inst->int_array) {\n            anjay_dm_emit(ctx, it->index);\n        }\n        return 0;\n    }\n    case TEST_RES_LAST_EXEC_ARGS_ARRAY: {\n        AVS_LIST(test_exec_arg_t) it;\n        AVS_LIST_FOREACH(it, inst->last_exec_args) {\n            anjay_dm_emit(ctx, (anjay_riid_t) it->number);\n        }\n        return 0;\n    }\n    case TEST_RES_BOOL_ARRAY: {\n        AVS_LIST(test_bool_array_entry_t) it;\n        AVS_LIST_FOREACH(it, inst->bool_array) {\n            anjay_dm_emit(ctx, it->index);\n        }\n        return 0;\n    }\n    default:\n        AVS_UNREACHABLE(\"Attempted to list instances in a single-instance \"\n                        \"resource\");\n        return ANJAY_ERR_INTERNAL;\n    }\n}\n\nconst anjay_dm_object_def_t TEST_OBJECT = {\n    .oid = DEMO_OID_TEST,\n    .handlers = {\n        .list_instances = test_list_instances,\n        .instance_create = test_instance_create,\n        .instance_remove = test_instance_remove,\n        .instance_reset = test_instance_reset,\n        .list_resources = test_list_resources,\n        .resource_read = test_resource_read,\n        .resource_write = test_resource_write,\n        .resource_execute = test_resource_execute,\n        .resource_reset = test_resource_reset,\n        .list_resource_instances = test_list_resource_instances,\n        .transaction_begin = anjay_dm_transaction_NOOP,\n        .transaction_validate = anjay_dm_transaction_NOOP,\n        .transaction_commit = anjay_dm_transaction_NOOP,\n        .transaction_rollback = anjay_dm_transaction_NOOP\n#ifdef ANJAY_WITH_LWM2M12\n        ,\n        .resource_instance_remove = test_resource_instance_remove\n#endif // ANJAY_WITH_LWM2M12\n    }\n};\n\nconst anjay_dm_object_def_t **test_object_create(void) {\n    test_repr_t *repr = (test_repr_t *) avs_calloc(1, sizeof(test_repr_t));\n    if (!repr) {\n        return NULL;\n    }\n    repr->def = &TEST_OBJECT;\n\n    return &repr->def;\n}\n\nvoid test_object_release(const anjay_dm_object_def_t **def) {\n    if (def) {\n        test_repr_t *repr = get_test(def);\n        AVS_LIST_CLEAR(&repr->instances) {\n            release_instance(repr->instances);\n        }\n        avs_free(repr);\n    }\n}\n\nint test_get_instances(const anjay_dm_object_def_t **def,\n                       AVS_LIST(anjay_iid_t) *out) {\n    test_repr_t *repr = get_test(def);\n    assert(!*out);\n    AVS_LIST(test_instance_t) it;\n    AVS_LIST_FOREACH(it, repr->instances) {\n        if (!(*out = AVS_LIST_NEW_ELEMENT(anjay_iid_t))) {\n            demo_log(ERROR, \"out of memory\");\n            return -1;\n        }\n        **out = it->iid;\n        AVS_LIST_ADVANCE_PTR(&out);\n    }\n    return 0;\n}\n\nvoid test_notify_time_dependent(anjay_t *anjay,\n                                const anjay_dm_object_def_t **def) {\n    test_repr_t *repr = get_test(def);\n    struct test_instance_struct *it;\n    AVS_LIST_FOREACH(it, repr->instances) {\n        anjay_notify_changed(anjay, (*def)->oid, it->iid, TEST_RES_TIMESTAMP);\n    }\n}\n"
  },
  {
    "path": "demo/objects.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef DEMO_OBJECTS_H\n#define DEMO_OBJECTS_H\n\n#include \"demo_utils.h\"\n\n#include <stdbool.h>\n#include <stdint.h>\n\n#include <avsystem/commons/avs_list.h>\n#include <avsystem/commons/avs_time.h>\n\n#include <anjay/access_control.h>\n#include <anjay/anjay.h>\n\n#ifdef WITH_DEMO_USE_STANDALONE_OBJECTS\n#    include \"../standalone/server/standalone_server.h\"\n#else // WITH_DEMO_USE_STANDALONE_OBJECTS\n#    include <anjay/server.h>\n#endif // WITH_DEMO_USE_STANDALONE_OBJECTS\n\n#define IMG_VER_STR_MAX_LEN (sizeof(\"255.255.65535.4294967295\") - 1)\n#define VER_DEFAULT \"1.0\"\n\ntypedef struct anjay_demo_struct anjay_demo_t;\n\n#define DEMO_OID_SECURITY 0\n#define DEMO_OID_SERVER 1\n#define DEMO_OID_DEVICE 3\n#define DEMO_OID_CONN_MONITORING 4\n#define DEMO_OID_FIRMWARE_UPDATE 5\n#define DEMO_OID_LOCATION 6\n#define DEMO_OID_CONN_STATISTICS 7\n#define DEMO_OID_CELL_CONNECTIVITY 10\n#define DEMO_OID_APN_CONN_PROFILE 11\n#define DEMO_OID_EVENT_LOG 20\n#define DEMO_OID_TEST 33605\n#define DEMO_OID_EXT_DEV_INFO 33606\n#define DEMO_OID_IP_PING 33607\n#define DEMO_OID_GEOPOINTS 33608\n#define DEMO_OID_DOWNLOAD_DIAG 33609\n\nconst anjay_dm_object_def_t **device_object_create(const char *endpoint_name);\nvoid device_object_release(const anjay_dm_object_def_t **def);\nvoid device_notify_time_dependent(anjay_t *anjay,\n                                  const anjay_dm_object_def_t **def);\n\n#define MAX_SERVERS 1024\n\ntypedef struct {\n    anjay_iid_t security_iid;\n    anjay_iid_t server_iid;\n    anjay_ssid_t id;\n    bool is_bootstrap;\n    const char *uri;\n    const char *binding_mode;\n#ifdef ANJAY_WITH_LWM2M11\n    const char *sni;\n    uint32_t retry_count;\n    uint32_t retry_timer;\n    uint32_t sequence_retry_count;\n    uint32_t sequence_delay_timer;\n    uint16_t certificate_usage;\n#endif // ANJAY_WITH_LWM2M11\n} server_entry_t;\n\ntypedef struct {\n    server_entry_t servers[MAX_SERVERS];\n#ifdef ANJAY_WITH_BOOTSTRAP\n    int32_t bootstrap_holdoff_s;\n    int32_t bootstrap_timeout_s;\n#endif // ANJAY_WITH_BOOTSTRAP\n    int32_t lifetime;\n    anjay_security_mode_t security_mode;\n    uint8_t *public_cert_or_psk_identity;\n    size_t public_cert_or_psk_identity_size;\n\n    uint8_t *private_cert_or_psk_key;\n    size_t private_cert_or_psk_key_size;\n\n#ifdef ANJAY_WITH_SECURITY_STRUCTURED\n    avs_crypto_certificate_chain_info_t public_cert;\n    avs_crypto_private_key_info_t private_key;\n    avs_crypto_psk_identity_info_t psk_identity;\n    avs_crypto_psk_key_info_t psk_key;\n#endif // ANJAY_WITH_SECURITY_STRUCTURED\n\n    uint8_t *server_public_key;\n    size_t server_public_key_size;\n} server_connection_args_t;\n\n#define DEMO_FOREACH_SERVER_ENTRY(It, ConnArgs)                 \\\n    for ((It) = &(ConnArgs)->servers[0];                        \\\n         (It) < &(ConnArgs)->servers[MAX_SERVERS] && (It)->uri; \\\n         ++(It))\n\n#define UNDEFINED_LIFETIME -1\n\nconst anjay_dm_object_def_t **test_object_create(void);\nvoid test_object_release(const anjay_dm_object_def_t **def);\nint test_get_instances(const anjay_dm_object_def_t **def,\n                       AVS_LIST(anjay_iid_t) *iids);\nvoid test_notify_time_dependent(anjay_t *anjay,\n                                const anjay_dm_object_def_t **def);\n\nconst anjay_dm_object_def_t **cm_object_create(void);\nvoid cm_object_release(const anjay_dm_object_def_t **def);\nvoid cm_notify_time_dependent(anjay_t *anjay,\n                              const anjay_dm_object_def_t **def);\n\nconst anjay_dm_object_def_t **cs_object_create(void);\nvoid cs_object_release(const anjay_dm_object_def_t **def);\n\nconst anjay_dm_object_def_t **download_diagnostics_object_create(void);\nvoid download_diagnostics_object_release(const anjay_dm_object_def_t **def);\n\nconst anjay_dm_object_def_t **ext_dev_info_object_create(void);\nvoid ext_dev_info_object_release(const anjay_dm_object_def_t **def);\nvoid ext_dev_info_notify_time_dependent(anjay_t *anjay,\n                                        const anjay_dm_object_def_t **def);\n\nconst anjay_dm_object_def_t **ip_ping_object_create(void);\nvoid ip_ping_object_release(const anjay_dm_object_def_t **def);\n\nconst anjay_dm_object_def_t **apn_conn_profile_object_create(void);\nint apn_conn_profile_get_instances(const anjay_dm_object_def_t **def,\n                                   AVS_LIST(anjay_iid_t) *out);\nvoid apn_conn_profile_object_release(const anjay_dm_object_def_t **def);\n\nAVS_LIST(anjay_iid_t)\napn_conn_profile_list_activated(const anjay_dm_object_def_t **def);\n\nconst anjay_dm_object_def_t **\ncell_connectivity_object_create(anjay_demo_t *demo);\nvoid cell_connectivity_object_release(const anjay_dm_object_def_t **def);\n\nconst anjay_dm_object_def_t **location_object_create(void);\nvoid location_object_release(const anjay_dm_object_def_t **def);\nvoid location_notify_time_dependent(anjay_t *anjay,\n                                    const anjay_dm_object_def_t **def);\nvoid location_get(const anjay_dm_object_def_t **def,\n                  double *out_latitude,\n                  double *out_longitude);\nint location_open_csv(const anjay_dm_object_def_t **def,\n                      const char *file_name,\n                      time_t frequency_s);\n\nconst anjay_dm_object_def_t **geopoints_object_create(anjay_demo_t *demo);\nvoid geopoints_object_release(const anjay_dm_object_def_t **def);\nint geopoints_get_instances(const anjay_dm_object_def_t **def,\n                            AVS_LIST(anjay_iid_t) *out);\nvoid geopoints_notify_time_dependent(anjay_t *anjay,\n                                     const anjay_dm_object_def_t **def);\n\nconst anjay_dm_object_def_t **portfolio_object_create(void);\nvoid portfolio_object_release(const anjay_dm_object_def_t **def);\nint portfolio_get_instances(const anjay_dm_object_def_t **def,\n                            AVS_LIST(anjay_iid_t) *out);\n\nconst anjay_dm_object_def_t **binary_app_data_container_object_create(void);\nvoid binary_app_data_container_object_release(\n        const anjay_dm_object_def_t **def);\nint binary_app_data_container_get_instances(const anjay_dm_object_def_t **def,\n                                            AVS_LIST(anjay_iid_t) *out);\nint binary_app_data_container_write(anjay_t *anjay,\n                                    const anjay_dm_object_def_t **def,\n                                    anjay_iid_t iid,\n                                    anjay_riid_t riid,\n                                    const char *value);\n\nconst anjay_dm_object_def_t **event_log_object_create(void);\nvoid event_log_object_release(const anjay_dm_object_def_t **def);\nint event_log_write_data(anjay_t *anjay,\n                         const anjay_dm_object_def_t *const *obj_ptr,\n                         const void *data,\n                         size_t data_size);\n\nint install_temperature_object(anjay_t *anjay);\nvoid temperature_update_handler(anjay_t *anjay);\nvoid temperature_add_instance(anjay_t *anjay, anjay_iid_t iid);\nvoid temperature_remove_instance(anjay_t *anjay, anjay_iid_t iid);\n\nint install_accelerometer_object(anjay_t *anjay);\nvoid accelerometer_update_handler(anjay_t *anjay);\nvoid accelerometer_add_instance(anjay_t *anjay, anjay_iid_t iid);\nvoid accelerometer_remove_instance(anjay_t *anjay, anjay_iid_t iid);\n\nint install_push_button_object(anjay_t *anjay);\n\n#endif // DEMO_OBJECTS_H\n"
  },
  {
    "path": "demo/software_mgmt.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include \"software_mgmt.h\"\n#include \"demo.h\"\n#include \"demo_utils.h\"\n\n#include <errno.h>\n#include <inttypes.h>\n#include <unistd.h>\n\n#include <sys/stat.h>\n\n#include <avsystem/commons/avs_stream_file.h>\n#include <avsystem/commons/avs_utils.h>\n\n#define FORCE_ERROR_FAILED_INSTALL 1\n#define FORCE_DELAYED_SUCCESS_INSTALL 2\n#define FORCE_DELAYED_ERROR_FAILED_INSTALL 3\n#define FORCE_SET_SUCCESS_FROM_PERFORM_INSTALL 4\n#define FORCE_SET_SUCCESS_FROM_PERFORM_INSTALL_ACTIVATE 5\n#define FORCE_SET_FAILURE_FROM_PERFORM_INSTALL 6\n#define FORCE_SET_FAILURE_FROM_PERFORM_UNINSTALL 7\n#define FORCE_SET_FAILURE_FROM_PERFORM_ACTIVATION 8\n#define FORCE_SET_FAILURE_FROM_PERFORM_DEACTIVATION 9\n#define FORCE_SET_FAILURE_FROM_PREPARE_FOR_UPDATE 10\n#define FORCE_DO_NOTHING_SW 11\n\n#define DEFAULT_INSTANCE_COUNT 2\n\n#define HEADER_VER_SW 2\n\nstatic const char *SW_NAME[SW_MGMT_PACKAGE_COUNT] = {\n    [0] = \"Cute software 0\",\n    [1] = \"Cute software 1\",\n    [2] = \"Secret software\"\n};\n\nvoid sw_mgmt_set_package_path(sw_mgmt_logic_t *sw_mgmt, const char *path) {\n    if (sw_mgmt->stream) {\n        demo_log(ERROR,\n                 \"cannot set software package path while a download is in \"\n                 \"progress\");\n        return;\n    }\n    char *new_target_path = avs_strdup(path);\n    if (!new_target_path) {\n        demo_log(ERROR, \"out of memory\");\n        return;\n    }\n    avs_free(sw_mgmt->administratively_set_target_path);\n\n    sw_mgmt->administratively_set_target_path = new_target_path;\n    demo_log(INFO, \"software package path set to %s\",\n             sw_mgmt->administratively_set_target_path);\n}\n\nstatic int maybe_create_software_file(sw_mgmt_logic_t *sw_mgmt) {\n    if (sw_mgmt->next_target_path) {\n        return 0;\n    }\n    if (sw_mgmt->administratively_set_target_path) {\n        sw_mgmt->next_target_path =\n                avs_strdup(sw_mgmt->administratively_set_target_path);\n    } else {\n        sw_mgmt->next_target_path = generate_random_target_filepath();\n    }\n    if (!sw_mgmt->next_target_path) {\n        return -1;\n    }\n    demo_log(INFO, \"Created %s\", sw_mgmt->next_target_path);\n    return 0;\n}\n\nstatic void maybe_delete_software_file(sw_mgmt_logic_t *sw_mgmt) {\n    if (sw_mgmt->next_target_path) {\n        unlink(sw_mgmt->next_target_path);\n        demo_log(INFO, \"Deleted %s\", sw_mgmt->next_target_path);\n        avs_free(sw_mgmt->next_target_path);\n        sw_mgmt->next_target_path = NULL;\n    }\n}\n\nstatic void fix_sw_meta_endianness(sw_metadata_t *meta) {\n    meta->header_ver = avs_convert_be16(meta->header_ver);\n    meta->force_error_case = avs_convert_be16(meta->force_error_case);\n    meta->crc = avs_convert_be32(meta->crc);\n}\n\nstatic int read_sw_meta_from_file(FILE *f, sw_metadata_t *out_metadata) {\n    sw_metadata_t m;\n    memset(&m, 0, sizeof(m));\n\n    if (fread(m.magic, sizeof(m.magic), 1, f) != 1\n            || fread(&m.header_ver, sizeof(m.header_ver), 1, f) != 1\n            || fread(&m.force_error_case, sizeof(m.force_error_case), 1, f) != 1\n            || fread(&m.crc, sizeof(m.crc), 1, f) != 1\n            || fread(&m.pkg_ver_len, sizeof(m.pkg_ver_len), 1, f) != 1) {\n        demo_log(ERROR, \"could not read software metadata\");\n        return -1;\n    }\n\n    if (m.pkg_ver_len > IMG_VER_STR_MAX_LEN || m.pkg_ver_len == 0) {\n        demo_log(ERROR, \"Wrong pkg version len\");\n        return ANJAY_SW_MGMT_ERR_UNSUPPORTED_PACKAGE_TYPE;\n    }\n\n    if (fread(m.pkg_ver, m.pkg_ver_len, 1, f) != 1) {\n        demo_log(ERROR, \"could not read software metadata\");\n        return -1;\n    }\n\n    fix_sw_meta_endianness(&m);\n    *out_metadata = m;\n    return 0;\n}\n\nstatic int unpack_sw_to_file(const char *sw_pkg_path,\n                             const char *target_path,\n                             sw_metadata_t *out_metadata) {\n    int result = -1;\n    FILE *sw = fopen(sw_pkg_path, \"rb\");\n    FILE *tmp = NULL;\n\n    if (!sw) {\n        demo_log(ERROR, \"could not open file: %s\", sw_pkg_path);\n        goto cleanup;\n    }\n\n    tmp = fopen(target_path, \"wb\");\n    if (!tmp) {\n        demo_log(ERROR, \"could not open file: %s\", target_path);\n        goto cleanup;\n    }\n\n    result = read_sw_meta_from_file(sw, out_metadata);\n    if (result) {\n        demo_log(ERROR, \"could not read metadata from file: %s\", sw_pkg_path);\n        goto cleanup;\n    }\n    result = copy_file_contents(tmp, sw);\n    if (result) {\n        demo_log(ERROR, \"could not copy software from %s to %s\", sw_pkg_path,\n                 target_path);\n        goto cleanup;\n    }\n\n    result = 0;\n\ncleanup:\n    if (sw) {\n        fclose(sw);\n    }\n    if (tmp) {\n        fclose(tmp);\n    }\n    return result;\n}\n\nstatic int unpack_software_in_place(sw_mgmt_logic_t *sw_mgmt) {\n    char *tmp_path = generate_random_target_filepath();\n    if (!tmp_path) {\n        return -1;\n    }\n\n    int result = unpack_sw_to_file(sw_mgmt->next_target_path, tmp_path,\n                                   &sw_mgmt->metadata);\n    if (result) {\n        goto cleanup;\n    }\n\n    if ((result = rename(tmp_path, sw_mgmt->next_target_path)) == -1) {\n        demo_log(ERROR, \"could not rename %s to %s: %s\", tmp_path,\n                 sw_mgmt->next_target_path, strerror(errno));\n        goto cleanup;\n    }\n\n    if ((result = chmod(sw_mgmt->next_target_path, 0700)) == -1) {\n        demo_log(ERROR, \"could not set permissions for %s: %s\",\n                 sw_mgmt->next_target_path, strerror(errno));\n        goto cleanup;\n    }\n\ncleanup:\n    unlink(tmp_path);\n    avs_free(tmp_path);\n    if (result) {\n        maybe_delete_software_file(sw_mgmt);\n    }\n\n    return result;\n}\n\nstatic bool sw_magic_valid(const sw_metadata_t *meta) {\n    if (memcmp(meta->magic, \"ANJAY_SW\", sizeof(meta->magic))) {\n        demo_log(ERROR, \"invalid software magic\");\n        return false;\n    }\n\n    return true;\n}\n\nstatic bool sw_header_version_valid(const sw_metadata_t *meta) {\n    if (meta->header_ver != HEADER_VER_SW) {\n        demo_log(ERROR, \"wrong header version\");\n        return false;\n    }\n\n    return true;\n}\n\nstatic bool sw_version_supported(const sw_metadata_t *meta) {\n    if (memcmp(meta->pkg_ver, VER_DEFAULT, meta->pkg_ver_len)) {\n        demo_log(ERROR, \"unsupported software version: %s\", meta->pkg_ver);\n        return false;\n    }\n\n    return true;\n}\n\nstatic int validate_software(sw_mgmt_logic_t *sw_mgmt) {\n    if (!sw_magic_valid(&sw_mgmt->metadata)\n            || !sw_header_version_valid(&sw_mgmt->metadata)\n            || !sw_version_supported(&sw_mgmt->metadata)) {\n        return ANJAY_SW_MGMT_ERR_UNSUPPORTED_PACKAGE_TYPE;\n    }\n\n    uint32_t actual_crc;\n    int result = calc_file_crc32(sw_mgmt->next_target_path, &actual_crc);\n\n    if (result) {\n        demo_log(WARNING, \"unable to check software CRC\");\n        return ANJAY_SW_MGMT_ERR_INTEGRITY_FAILURE;\n    }\n\n    if (sw_mgmt->metadata.crc != actual_crc) {\n        demo_log(WARNING, \"CRC mismatch: expected %08x != %08x actual\",\n                 sw_mgmt->metadata.crc, actual_crc);\n        return ANJAY_SW_MGMT_ERR_INTEGRITY_FAILURE;\n    }\n\n    return 0;\n}\n\nstatic void sw_mgmt_destroy_inst(sw_mgmt_logic_t *sw_mgmt) {\n    if (sw_mgmt->stream) {\n        fclose(sw_mgmt->stream);\n    }\n    avs_free(sw_mgmt->administratively_set_target_path);\n    avs_free(sw_mgmt->next_target_path);\n}\n\nvoid sw_mgmt_update_destroy(sw_mgmt_logic_t *sw_mgmt_table) {\n    assert(sw_mgmt_table);\n\n    for (size_t iid = 0; iid < SW_MGMT_PACKAGE_COUNT; iid++) {\n        sw_mgmt_destroy_inst(&sw_mgmt_table[iid]);\n    }\n}\n\ntypedef struct {\n    anjay_sw_mgmt_initial_state_t result[SW_MGMT_PACKAGE_COUNT];\n    char *download_file[SW_MGMT_PACKAGE_COUNT];\n    bool filename_administratively_set[SW_MGMT_PACKAGE_COUNT];\n    bool exists[SW_MGMT_PACKAGE_COUNT];\n} persistence_file_data_t;\n\n#if defined(AVS_COMMONS_WITH_AVS_PERSISTENCE) \\\n        && defined(AVS_COMMONS_STREAM_WITH_FILE)\nstatic bool is_valid_result(uint8_t result) {\n    switch (result) {\n    case ANJAY_SW_MGMT_INITIAL_STATE_IDLE:\n    case ANJAY_SW_MGMT_INITIAL_STATE_DOWNLOADED:\n    case ANJAY_SW_MGMT_INITIAL_STATE_DELIVERED:\n    case ANJAY_SW_MGMT_INITIAL_STATE_INSTALLING:\n    case ANJAY_SW_MGMT_INITIAL_STATE_INSTALLED_DEACTIVATED:\n    case ANJAY_SW_MGMT_INITIAL_STATE_INSTALLED_ACTIVATED:\n        return true;\n    default:\n        return false;\n    }\n}\n\nstatic int write_persistence_file(const char *path,\n                                  persistence_file_data_t *data) {\n    avs_stream_t *stream = avs_stream_file_create(path, AVS_STREAM_FILE_WRITE);\n    avs_persistence_context_t ctx =\n            avs_persistence_store_context_create(stream);\n    int retval = 0;\n\n    for (size_t iid = 0; iid < SW_MGMT_PACKAGE_COUNT; iid++) {\n        uint8_t result8 = (uint8_t) data->result[iid];\n        if (!stream || avs_is_err(avs_persistence_bytes(&ctx, &result8, 1))\n                || avs_is_err(avs_persistence_string(&ctx,\n                                                     &data->download_file[iid]))\n                || avs_is_err(avs_persistence_bool(\n                           &ctx, &data->filename_administratively_set[iid]))\n                || avs_is_err(avs_persistence_bool(&ctx, &data->exists[iid]))) {\n            demo_log(ERROR, \"Could not write software state persistence file\");\n            retval = -1;\n            break;\n        }\n    }\n    if (stream) {\n        avs_stream_cleanup(&stream);\n    }\n    if (retval) {\n        unlink(path);\n    }\n    return retval;\n}\n#endif // defined(AVS_COMMONS_WITH_AVS_PERSISTENCE) &&\n       // defined(AVS_COMMONS_STREAM_WITH_FILE)\n\nstatic int read_persistence_file(const char *path,\n                                 persistence_file_data_t *data) {\n#if defined(AVS_COMMONS_WITH_AVS_PERSISTENCE) \\\n        && defined(AVS_COMMONS_STREAM_WITH_FILE)\n    int ret = 0;\n    memset(data, 0, sizeof(*data));\n    avs_stream_t *stream = NULL;\n    uint8_t result8 = (uint8_t) ANJAY_SW_MGMT_INITIAL_STATE_IDLE;\n    uint8_t result8_first = (uint8_t) ANJAY_SW_MGMT_INITIAL_STATE_IDLE;\n\n    if ((stream = avs_stream_file_create(path, AVS_STREAM_FILE_READ))) {\n        // invalid or empty but existing file still signifies success but only\n        // for first instance\n        result8_first = (uint8_t) ANJAY_SW_MGMT_INITIAL_STATE_INSTALLING;\n    }\n    avs_persistence_context_t ctx =\n            avs_persistence_restore_context_create(stream);\n\n    for (size_t iid = 0; iid < SW_MGMT_PACKAGE_COUNT; iid++) {\n        if (!stream || avs_is_err(avs_persistence_bytes(&ctx, &result8, 1))\n                || !is_valid_result(result8)\n                || avs_is_err(avs_persistence_string(&ctx,\n                                                     &data->download_file[iid]))\n                || avs_is_err(avs_persistence_bool(\n                           &ctx, &data->filename_administratively_set[iid]))\n                || avs_is_err(avs_persistence_bool(&ctx, &data->exists[iid]))) {\n            demo_log(WARNING,\n                     \"Invalid data in the software state persistence file %s\",\n                     path);\n            for (size_t i = 0; i < SW_MGMT_PACKAGE_COUNT; i++) {\n                avs_free(data->download_file[i]);\n            }\n            memset(data, 0, sizeof(*data));\n            data->result[0] = (anjay_sw_mgmt_initial_state_t) result8_first;\n            ret = -1;\n            break;\n        }\n        data->result[iid] = (anjay_sw_mgmt_initial_state_t) result8;\n    }\n    if (stream) {\n        avs_stream_cleanup(&stream);\n    }\n    return ret;\n#else  // defined(AVS_COMMONS_WITH_AVS_PERSISTENCE) &&\n       // defined(AVS_COMMONS_STREAM_WITH_FILE)\n    (void) path;\n    (void) data;\n    demo_log(WARNING, \"Persistence not compiled in\");\n    return 0;\n#endif // defined(AVS_COMMONS_WITH_AVS_PERSISTENCE) &&\n       // defined(AVS_COMMONS_STREAM_WITH_FILE)\n}\n\nstatic void delete_persistence_file(const sw_mgmt_common_logic_t *sw) {\n#if defined(AVS_COMMONS_WITH_AVS_PERSISTENCE) \\\n        && defined(AVS_COMMONS_STREAM_WITH_FILE)\n    unlink(sw->persistence_file);\n#else  // defined(AVS_COMMONS_WITH_AVS_PERSISTENCE) &&\n    // defined(AVS_COMMONS_STREAM_WITH_FILE)\n    (void) sw;\n    demo_log(WARNING, \"Persistence not compiled in\");\n#endif // defined(AVS_COMMONS_WITH_AVS_PERSISTENCE) &&\n    // defined(AVS_COMMONS_STREAM_WITH_FILE)\n}\n\nstatic int update_persistence_file(const char *path,\n                                   sw_mgmt_logic_t *sw,\n                                   anjay_sw_mgmt_initial_state_t result,\n                                   anjay_iid_t iid) {\n#if defined(AVS_COMMONS_WITH_AVS_PERSISTENCE) \\\n        && defined(AVS_COMMONS_STREAM_WITH_FILE)\n    int res = 0;\n    persistence_file_data_t data;\n    read_persistence_file(path, &data);\n    avs_free(data.download_file[iid]);\n    data.result[iid] = result;\n    data.download_file[iid] = sw->next_target_path;\n    data.filename_administratively_set[iid] =\n            sw->administratively_set_target_path;\n    data.exists[iid] = true;\n    res = write_persistence_file(path, &data);\n    for (size_t i = 0; i < SW_MGMT_PACKAGE_COUNT; i++) {\n        if (i != iid) {\n            avs_free(data.download_file[i]);\n        }\n    }\n    return res;\n#else  // defined(AVS_COMMONS_WITH_AVS_PERSISTENCE) &&\n       // defined(AVS_COMMONS_STREAM_WITH_FILE)\n    (void) path;\n    (void) sw;\n    (void) result;\n    (void) iid;\n    return 0;\n#endif // defined(AVS_COMMONS_WITH_AVS_PERSISTENCE) &&\n       // defined(AVS_COMMONS_STREAM_WITH_FILE)\n}\n\nstatic int update_persisted_instance_existence(const char *path,\n                                               bool exists,\n                                               anjay_iid_t iid) {\n#if defined(AVS_COMMONS_WITH_AVS_PERSISTENCE) \\\n        && defined(AVS_COMMONS_STREAM_WITH_FILE)\n    int res = 0;\n    persistence_file_data_t data;\n    read_persistence_file(path, &data);\n    data.exists[iid] = exists;\n    res = write_persistence_file(path, &data);\n    for (size_t i = 0; i < SW_MGMT_PACKAGE_COUNT; i++) {\n        avs_free(data.download_file[i]);\n    }\n    return res;\n#else  // defined(AVS_COMMONS_WITH_AVS_PERSISTENCE) &&\n       // defined(AVS_COMMONS_STREAM_WITH_FILE)\n    (void) path;\n    (void) exists;\n    (void) iid;\n    return 0;\n#endif // defined(AVS_COMMONS_WITH_AVS_PERSISTENCE) &&\n       // defined(AVS_COMMONS_STREAM_WITH_FILE)\n}\n\nstatic int sw_mgmt_stream_open(void *obj_ctx, anjay_iid_t iid, void *inst_ctx) {\n    (void) obj_ctx;\n    (void) iid;\n    sw_mgmt_logic_t *sw = (sw_mgmt_logic_t *) inst_ctx;\n    assert(!sw->stream);\n\n    if (maybe_create_software_file(sw)) {\n        return -1;\n    }\n\n    if (!(sw->stream = fopen(sw->next_target_path, \"wb\"))) {\n        demo_log(ERROR, \"could not open file: %s\", sw->next_target_path);\n        return -1;\n    }\n\n    return 0;\n}\n\nstatic int sw_mgmt_stream_write(void *obj_ctx,\n                                anjay_iid_t iid,\n                                void *inst_ctx,\n                                const void *data,\n                                size_t length) {\n    (void) obj_ctx;\n    (void) iid;\n    sw_mgmt_logic_t *sw = (sw_mgmt_logic_t *) inst_ctx;\n\n    if (!sw->stream) {\n        demo_log(ERROR, \"stream not open\");\n        return -1;\n    }\n\n    if (length\n            && (fwrite(data, length, 1, sw->stream) != 1\n                // Software management integration tests measure download\n                // progress by checking file size, so avoiding buffering\n                // is required.\n                || fflush(sw->stream) != 0)) {\n        demo_log(ERROR, \"fwrite or fflush failed: %s\", strerror(errno));\n        return ANJAY_SW_MGMT_ERR_NOT_ENOUGH_SPACE;\n    }\n\n    return 0;\n}\n\nstatic int\nsw_mgmt_stream_finish(void *obj_ctx, anjay_iid_t iid, void *inst_ctx) {\n    sw_mgmt_common_logic_t *sw_common = (sw_mgmt_common_logic_t *) obj_ctx;\n    sw_mgmt_logic_t *sw = (sw_mgmt_logic_t *) inst_ctx;\n    if (sw_common->auto_suspend) {\n        anjay_sw_mgmt_pull_suspend(sw_common->anjay);\n    }\n    if (!sw->stream) {\n        demo_log(ERROR, \"stream not open\");\n        return -1;\n    }\n    fclose(sw->stream);\n    sw->stream = NULL;\n\n    (void) update_persistence_file(sw_common->persistence_file, sw,\n                                   ANJAY_SW_MGMT_INITIAL_STATE_DOWNLOADED, iid);\n\n    if (sw_common->terminate_after_downloading && iid == 0) {\n        anjay_event_loop_interrupt(sw_common->anjay);\n    }\n\n    return 0;\n}\n\nstatic int\nsw_mgmt_check_integrity(void *obj_ctx, anjay_iid_t iid, void *inst_ctx) {\n    int result = 0;\n    sw_mgmt_common_logic_t *sw_common = (sw_mgmt_common_logic_t *) obj_ctx;\n    sw_mgmt_logic_t *sw = (sw_mgmt_logic_t *) inst_ctx;\n    if (!(sw_common->terminate_after_downloading && iid == 0)) {\n\n        if (unpack_software_in_place(sw)) {\n            return ANJAY_SW_MGMT_ERR_UNSUPPORTED_PACKAGE_TYPE;\n        }\n\n        result = validate_software(sw);\n        if (!result) {\n            demo_log(INFO, \"software downloaded successfully\");\n            (void) update_persistence_file(\n                    sw_common->persistence_file, sw,\n                    ANJAY_SW_MGMT_INITIAL_STATE_DELIVERED, iid);\n        }\n    }\n    return result;\n}\n\nstatic void sw_mgmt_reset(void *obj_ctx, anjay_iid_t iid, void *inst_ctx) {\n    (void) iid;\n    sw_mgmt_common_logic_t *sw_common = (sw_mgmt_common_logic_t *) obj_ctx;\n    sw_mgmt_logic_t *sw = (sw_mgmt_logic_t *) inst_ctx;\n\n    if (sw->stream) {\n        fclose(sw->stream);\n        sw->stream = NULL;\n    }\n\n    maybe_delete_software_file(sw);\n    delete_persistence_file(sw_common);\n    if (sw_common->auto_suspend) {\n        anjay_sw_mgmt_pull_suspend(sw_common->anjay);\n    }\n}\n\nstatic const char *\nsw_mgmt_get_name(void *obj_ctx, anjay_iid_t iid, void *inst_ctx) {\n    (void) obj_ctx;\n    (void) inst_ctx;\n    return SW_NAME[iid];\n}\n\nstatic const char *\nsw_mgmt_get_version(void *obj_ctx, anjay_iid_t iid, void *inst_ctx) {\n    (void) obj_ctx;\n    (void) iid;\n    (void) inst_ctx;\n    return \"1.0\";\n}\n\nstatic int sw_mgmt_pkg_install(void *obj_ctx, anjay_iid_t iid, void *inst_ctx) {\n    sw_mgmt_common_logic_t *sw_common = (sw_mgmt_common_logic_t *) obj_ctx;\n    sw_mgmt_logic_t *sw = (sw_mgmt_logic_t *) inst_ctx;\n\n    demo_log(INFO, \"*** SOFTWARE INSTALL: %s ***\", sw->next_target_path);\n    switch (sw->metadata.force_error_case) {\n    case FORCE_ERROR_FAILED_INSTALL:\n        demo_log(ERROR, \"install failed\");\n        delete_persistence_file(sw_common);\n        return -1;\n    case FORCE_DELAYED_SUCCESS_INSTALL:\n        if (argv_append(\"--delayed-sw-mgmt-result\") || argv_append(\"1\")) {\n            demo_log(ERROR, \"could not append delayed result to argv\");\n            return -1;\n        }\n        break;\n    case FORCE_DELAYED_ERROR_FAILED_INSTALL:\n        if (argv_append(\"--delayed-sw-mgmt-result\") || argv_append(\"0\")) {\n            demo_log(ERROR, \"could not append delayed result to argv\");\n            return -1;\n        }\n        break;\n    case FORCE_SET_SUCCESS_FROM_PERFORM_INSTALL:\n        if (anjay_sw_mgmt_finish_pkg_install(\n                    sw_common->anjay, iid,\n                    ANJAY_SW_MGMT_FINISH_PKG_INSTALL_SUCCESS_INACTIVE)) {\n            demo_log(ERROR, \"anjay_sw_mgmt_finish_pkg_install failed\");\n            return -1;\n        }\n        return 0;\n    case FORCE_SET_SUCCESS_FROM_PERFORM_INSTALL_ACTIVATE:\n        if (anjay_sw_mgmt_finish_pkg_install(\n                    sw_common->anjay, iid,\n                    ANJAY_SW_MGMT_FINISH_PKG_INSTALL_SUCCESS_ACTIVE)) {\n            demo_log(ERROR, \"anjay_sw_mgmt_finish_pkg_install failed\");\n            return -1;\n        }\n        return 0;\n    case FORCE_SET_FAILURE_FROM_PERFORM_INSTALL:\n        if (anjay_sw_mgmt_finish_pkg_install(\n                    sw_common->anjay, iid,\n                    ANJAY_SW_MGMT_FINISH_PKG_INSTALL_FAILURE)) {\n            demo_log(ERROR, \"anjay_sw_mgmt_finish_pkg_install failed\");\n            return -1;\n        }\n        return 0;\n    case FORCE_DO_NOTHING_SW:\n        return 0;\n    default:\n        break;\n    }\n\n    if (sw->metadata.force_error_case == FORCE_DELAYED_SUCCESS_INSTALL\n            || sw->metadata.force_error_case\n                           == FORCE_DELAYED_ERROR_FAILED_INSTALL) {\n        execv(sw->next_target_path, argv_get());\n        demo_log(ERROR, \"execv failed (%s)\", strerror(errno));\n        delete_persistence_file(sw_common);\n        return -1;\n    } else {\n        if (system(sw->next_target_path)) {\n            demo_log(ERROR, \"execution of shell command failed\");\n            return -1;\n        }\n        anjay_sw_mgmt_finish_pkg_install(\n                sw_common->anjay, iid,\n                ANJAY_SW_MGMT_FINISH_PKG_INSTALL_SUCCESS_INACTIVE);\n        (void) update_persistence_file(\n                sw_common->persistence_file, sw,\n                ANJAY_SW_MGMT_INITIAL_STATE_INSTALLED_DEACTIVATED, iid);\n        return 0;\n    }\n}\n\nstatic int\nsw_mgmt_pkg_uninstall(void *obj_ctx, anjay_iid_t iid, void *inst_ctx) {\n    sw_mgmt_common_logic_t *sw_common = (sw_mgmt_common_logic_t *) obj_ctx;\n    sw_mgmt_logic_t *sw = (sw_mgmt_logic_t *) inst_ctx;\n\n    if (sw->metadata.force_error_case\n            == FORCE_SET_FAILURE_FROM_PERFORM_UNINSTALL) {\n        return -1;\n    } else {\n        (void) update_persistence_file(sw_common->persistence_file, sw,\n                                       ANJAY_SW_MGMT_INITIAL_STATE_IDLE, iid);\n    }\n\n    return 0;\n}\n\nstatic int\nsw_mgmt_prepare_for_update(void *obj_ctx, anjay_iid_t iid, void *inst_ctx) {\n    sw_mgmt_common_logic_t *sw_common = (sw_mgmt_common_logic_t *) obj_ctx;\n    sw_mgmt_logic_t *sw = (sw_mgmt_logic_t *) inst_ctx;\n\n    if (sw->metadata.force_error_case\n            == FORCE_SET_FAILURE_FROM_PREPARE_FOR_UPDATE) {\n        return -1;\n    } else {\n        (void) update_persistence_file(sw_common->persistence_file, sw,\n                                       ANJAY_SW_MGMT_INITIAL_STATE_IDLE, iid);\n    }\n\n    return 0;\n}\n\nstatic int sw_mgmt_activate(void *obj_ctx, anjay_iid_t iid, void *inst_ctx) {\n    sw_mgmt_common_logic_t *sw_common = (sw_mgmt_common_logic_t *) obj_ctx;\n    sw_mgmt_logic_t *sw = (sw_mgmt_logic_t *) inst_ctx;\n    bool activate;\n\n    if ((sw_common->disable_repeated_activation_deactivation\n         && (anjay_sw_mgmt_get_activation_state(sw_common->anjay, iid,\n                                                &activate)\n             || activate))\n            || sw->metadata.force_error_case\n                           == FORCE_SET_FAILURE_FROM_PERFORM_ACTIVATION) {\n        return -1;\n    } else {\n        (void) update_persistence_file(\n                sw_common->persistence_file, sw,\n                ANJAY_SW_MGMT_INITIAL_STATE_INSTALLED_ACTIVATED, iid);\n    }\n\n    return 0;\n}\n\nstatic int sw_mgmt_deactivate(void *obj_ctx, anjay_iid_t iid, void *inst_ctx) {\n    sw_mgmt_common_logic_t *sw_common = (sw_mgmt_common_logic_t *) obj_ctx;\n    sw_mgmt_logic_t *sw = (sw_mgmt_logic_t *) inst_ctx;\n    bool activate;\n\n    if ((sw_common->disable_repeated_activation_deactivation\n         && (anjay_sw_mgmt_get_activation_state(sw_common->anjay, iid,\n                                                &activate)\n             || !activate))\n            || sw->metadata.force_error_case\n                           == FORCE_SET_FAILURE_FROM_PERFORM_DEACTIVATION) {\n        return -1;\n    } else {\n        (void) update_persistence_file(\n                sw_common->persistence_file, sw,\n                ANJAY_SW_MGMT_INITIAL_STATE_INSTALLED_DEACTIVATED, iid);\n    }\n\n    return 0;\n}\n\n#ifdef ANJAY_WITH_DOWNLOADER\nstatic int\nsw_mgmt_get_security_config(void *obj_ctx,\n                            anjay_iid_t iid,\n                            void *inst_ctx,\n                            const char *download_uri,\n                            anjay_security_config_t *out_security_info) {\n    sw_mgmt_common_logic_t *sw_common = (sw_mgmt_common_logic_t *) obj_ctx;\n    (void) download_uri;\n    (void) iid;\n    (void) inst_ctx;\n    memset(out_security_info, 0, sizeof(*out_security_info));\n    out_security_info->security_info = *sw_common->security_info;\n    return 0;\n}\n\nstatic avs_time_duration_t\nsw_mgmt_get_tcp_request_timeout(void *obj_ctx,\n                                anjay_iid_t iid,\n                                void *inst_ctx,\n                                const char *download_uri) {\n    sw_mgmt_common_logic_t *sw_common = (sw_mgmt_common_logic_t *) obj_ctx;\n    (void) download_uri;\n    (void) iid;\n    (void) inst_ctx;\n    return *sw_common->tcp_request_timeout;\n}\n#    ifdef ANJAY_WITH_COAP_DOWNLOAD\nstatic avs_coap_udp_tx_params_t\nsw_mgmt_get_coap_tx_params(void *obj_ctx,\n                           anjay_iid_t iid,\n                           void *inst_ctx,\n                           const char *download_uri) {\n    sw_mgmt_common_logic_t *sw_common = (sw_mgmt_common_logic_t *) obj_ctx;\n    (void) download_uri;\n    (void) iid;\n    (void) inst_ctx;\n    if (sw_common->auto_suspend) {\n        anjay_sw_mgmt_pull_reconnect(sw_common->anjay);\n    }\n    return sw_common->coap_tx_params != NULL ? *sw_common->coap_tx_params\n                                             : AVS_COAP_DEFAULT_UDP_TX_PARAMS;\n}\n#    endif // ANJAY_WITH_COAP_DOWNLOAD\n#endif     // ANJAY_WITH_DOWNLOADER\n\nstatic int\nsw_mgmt_add_handler(void *obj_ctx, anjay_iid_t iid, void **out_inst_ctx) {\n    sw_mgmt_common_logic_t *sw_common = (sw_mgmt_common_logic_t *) obj_ctx;\n\n    if (iid < SW_MGMT_PACKAGE_COUNT\n            && !update_persisted_instance_existence(sw_common->persistence_file,\n                                                    true, iid)) {\n        *out_inst_ctx = (void *) &sw_common->sw_mgmt_table[iid];\n    } else {\n        return -1;\n    }\n\n    return 0;\n}\n\nstatic int\nsw_mgmt_remove_handler(void *obj_ctx, anjay_iid_t iid, void *inst_ctx) {\n    sw_mgmt_common_logic_t *sw_common = (sw_mgmt_common_logic_t *) obj_ctx;\n    sw_mgmt_logic_t *sw = (sw_mgmt_logic_t *) inst_ctx;\n\n    if (iid < SW_MGMT_PACKAGE_COUNT\n            && !update_persisted_instance_existence(sw_common->persistence_file,\n                                                    false, iid)) {\n        sw_mgmt_destroy_inst(sw);\n    } else {\n        return -1;\n    }\n\n    return 0;\n}\n\nstatic anjay_sw_mgmt_handlers_t g_handlers = {\n    .stream_open = sw_mgmt_stream_open,\n    .stream_write = sw_mgmt_stream_write,\n    .stream_finish = sw_mgmt_stream_finish,\n    .check_integrity = sw_mgmt_check_integrity,\n    .reset = sw_mgmt_reset,\n    .get_name = sw_mgmt_get_name,\n    .get_version = sw_mgmt_get_version,\n    .pkg_install = sw_mgmt_pkg_install,\n    .pkg_uninstall = sw_mgmt_pkg_uninstall,\n    .prepare_for_update = sw_mgmt_prepare_for_update,\n    .activate = sw_mgmt_activate,\n    .deactivate = sw_mgmt_deactivate,\n    .add_handler = sw_mgmt_add_handler,\n    .remove_handler = sw_mgmt_remove_handler\n};\n\ntypedef struct {\n    anjay_t *anjay;\n    bool delayed_result;\n} set_delayed_sw_mgmt_update_result_args_t;\n\nstatic void set_delayed_sw_mgmt_update_result(avs_sched_t *sched,\n                                              const void *arg) {\n    (void) sched;\n    const set_delayed_sw_mgmt_update_result_args_t *args =\n            (const set_delayed_sw_mgmt_update_result_args_t *) arg;\n    anjay_sw_mgmt_finish_pkg_install(\n            args->anjay, 0,\n            args->delayed_result\n                    ? ANJAY_SW_MGMT_FINISH_PKG_INSTALL_SUCCESS_INACTIVE\n                    : ANJAY_SW_MGMT_FINISH_PKG_INSTALL_FAILURE);\n}\n\nint sw_mgmt_install(anjay_t *anjay,\n                    sw_mgmt_common_logic_t *sw_mgmt_common,\n                    sw_mgmt_logic_t *sw_mgmt_table,\n                    const char *persistence_file,\n                    bool prefer_same_socket_downloads,\n                    uint8_t delayed_first_instance_install_result,\n                    bool terminate_after_downloading,\n                    bool disable_repeated_activation_deactivation\n#ifdef ANJAY_WITH_DOWNLOADER\n                    ,\n                    avs_net_security_info_t *security_info,\n                    avs_coap_udp_tx_params_t *tx_params,\n                    avs_time_duration_t *tcp_request_timeout,\n                    bool auto_suspend\n#endif // ANJAY_WITH_DOWNLOADER\n) {\n    int ret = 0;\n    sw_mgmt_logic_t *sw_logic = NULL;\n    sw_mgmt_common->anjay = anjay;\n    sw_mgmt_common->sw_mgmt_table = sw_mgmt_table;\n    sw_mgmt_common->persistence_file = persistence_file;\n    sw_mgmt_common->terminate_after_downloading = terminate_after_downloading;\n    sw_mgmt_common->disable_repeated_activation_deactivation =\n            disable_repeated_activation_deactivation;\n#ifdef ANJAY_WITH_DOWNLOADER\n    if (security_info) {\n        sw_mgmt_common->security_info = security_info;\n        g_handlers.get_security_config = sw_mgmt_get_security_config;\n    } else {\n        g_handlers.get_security_config = NULL;\n    }\n\n    if (tx_params || auto_suspend) {\n        sw_mgmt_common->auto_suspend = auto_suspend;\n        sw_mgmt_common->coap_tx_params = tx_params;\n        g_handlers.get_coap_tx_params = sw_mgmt_get_coap_tx_params;\n    } else {\n        g_handlers.get_coap_tx_params = NULL;\n    }\n\n    if (tcp_request_timeout && avs_time_duration_valid(*tcp_request_timeout)) {\n        sw_mgmt_common->tcp_request_timeout = tcp_request_timeout;\n        g_handlers.get_tcp_request_timeout = sw_mgmt_get_tcp_request_timeout;\n    } else {\n        g_handlers.get_tcp_request_timeout = NULL;\n    }\n#endif // ANJAY_WITH_DOWNLOADER\n\n    const anjay_sw_mgmt_settings_t settings = {\n        .handlers = &g_handlers,\n        .obj_ctx = sw_mgmt_common,\n#ifdef ANJAY_WITH_DOWNLOADER\n        .prefer_same_socket_downloads = prefer_same_socket_downloads\n#endif // ANJAY_WITH_DOWNLOADER\n    };\n\n    persistence_file_data_t data;\n    if (read_persistence_file(persistence_file, &data)) {\n        for (size_t i = 0; i < DEFAULT_INSTANCE_COUNT; i++) {\n            data.exists[i] = true;\n        }\n    }\n    delete_persistence_file(sw_mgmt_common);\n\n    if (anjay_sw_mgmt_install(anjay, &settings)) {\n        ret = -1;\n        goto exit;\n    }\n\n    for (anjay_iid_t iid = 0; iid < SW_MGMT_PACKAGE_COUNT; iid++) {\n#if defined(AVS_COMMONS_WITH_AVS_PERSISTENCE) \\\n        && defined(AVS_COMMONS_STREAM_WITH_FILE)\n        if (data.exists[iid])\n#endif // defined(AVS_COMMONS_WITH_AVS_PERSISTENCE) &&\n       // defined(AVS_COMMONS_STREAM_WITH_FILE)\n        {\n            sw_logic = &sw_mgmt_table[iid];\n\n            if ((sw_logic->next_target_path = data.download_file[iid])\n                    && data.filename_administratively_set[iid]\n                    && !(sw_logic->administratively_set_target_path =\n                                 avs_strdup(data.download_file[iid]))) {\n                demo_log(WARNING,\n                         \"Could not administratively set firmware path for %d\",\n                         iid);\n            }\n\n            anjay_sw_mgmt_instance_initializer_t inst_settings = {\n                .iid = iid,\n                .initial_state = data.result[iid],\n                .inst_ctx = sw_logic\n            };\n\n            if (iid == 0\n                    && (delayed_first_instance_install_result == 0\n                        || delayed_first_instance_install_result == 1)) {\n                demo_log(INFO,\n                         \"delayed_result == %d; initializing Software \"\n                         \"Management \"\n                         \"in DELIVERED state\",\n                         (int) delayed_first_instance_install_result);\n                inst_settings.initial_state =\n                        ANJAY_SW_MGMT_INITIAL_STATE_INSTALLING;\n\n                // Simulate installing process that finishes after the LwM2M\n                // client starts by changing the Update Result later at runtime\n                set_delayed_sw_mgmt_update_result_args_t args = {\n                    .anjay = anjay,\n                    .delayed_result = delayed_first_instance_install_result == 1\n                };\n\n                if (AVS_SCHED_DELAYED(anjay_get_scheduler(anjay), NULL,\n                                      avs_time_duration_from_scalar(1,\n                                                                    AVS_TIME_S),\n                                      set_delayed_sw_mgmt_update_result, &args,\n                                      sizeof(args))) {\n                    ret = -1;\n                    goto exit;\n                }\n            }\n\n            if (anjay_sw_mgmt_add_instance(anjay, &inst_settings)) {\n                ret = -1;\n                goto exit;\n            }\n\n            if (auto_suspend) {\n                anjay_sw_mgmt_pull_suspend(anjay);\n            }\n        }\n    }\nexit:\n    if (ret) {\n        sw_mgmt_update_destroy(sw_mgmt_table);\n    }\n    return ret;\n}\n"
  },
  {
    "path": "demo/software_mgmt.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef SOFTWARE_MGMT_H\n#define SOFTWARE_MGMT_H\n\n#include <stdbool.h>\n#include <stdint.h>\n\n#include \"objects.h\"\n#include <anjay/anjay.h>\n#include <anjay/sw_mgmt.h>\n\n#define SW_MGMT_PACKAGE_COUNT 3\n\ntypedef struct software_metadata {\n    uint8_t magic[8];\n    uint16_t header_ver;\n    uint16_t force_error_case;\n    uint32_t crc;\n    uint8_t pkg_ver_len;\n    uint8_t pkg_ver[IMG_VER_STR_MAX_LEN + 1];\n} sw_metadata_t;\n\ntypedef struct {\n    FILE *stream;\n    char *administratively_set_target_path;\n    char *next_target_path;\n    sw_metadata_t metadata;\n} sw_mgmt_logic_t;\n\ntypedef struct {\n    anjay_t *anjay;\n    const char *persistence_file;\n    avs_net_security_info_t *security_info;\n    avs_coap_udp_tx_params_t *coap_tx_params;\n    avs_time_duration_t *tcp_request_timeout;\n    bool auto_suspend;\n    bool terminate_after_downloading;\n    bool disable_repeated_activation_deactivation;\n    sw_mgmt_logic_t *sw_mgmt_table;\n} sw_mgmt_common_logic_t;\n\nint sw_mgmt_install(anjay_t *anjay,\n                    sw_mgmt_common_logic_t *sw_mgmt_common,\n                    sw_mgmt_logic_t *sw_table,\n                    const char *persistence_file,\n                    bool prefer_same_socket_downloads,\n                    uint8_t delayed_first_instance_install_result,\n                    bool terminate_after_downloading,\n                    bool disable_repeated_activation_deactivation\n#ifdef ANJAY_WITH_DOWNLOADER\n                    ,\n                    avs_net_security_info_t *security_info,\n                    avs_coap_udp_tx_params_t *tx_params,\n                    avs_time_duration_t *tcp_request_timeout,\n                    bool auto_suspend\n#endif // ANJAY_WITH_DOWNLOADER\n);\n\nvoid sw_mgmt_update_destroy(sw_mgmt_logic_t *sw_mgmt_table);\n\nvoid sw_mgmt_set_package_path(sw_mgmt_logic_t *sw_mgmt, const char *path);\n\n#endif // SOFTWARE_MGMT_H\n"
  },
  {
    "path": "deps/avs_coap/.gitignore",
    "content": "# binaries\n*.o\n*.a\n*.so\n*.so.*\navs_*_test\n/tools/dtls_echo_server\n/certs\n/ensure-no-warnings-from-headers-if-cpp.cpp\n\n# NetBeans Project\nnbproject\n\n# VSCode\n.vscode/\n\n# logs\n*.log\n\n# build configuration autogenerated files\nCMakeFiles/\nCMakeTmp/\nMakefile\nCMakeCache.txt\nCMakeDoxyfile.in\nCMakeDoxygenDefaults.cmake\nCPackConfig.cmake\nCPackSourceConfig.cmake\nCTestTestfile.cmake\navs_coap-config.cmake\navs_coap-version.cmake\ncmake_install.cmake\ncompile_commands.json\ninstall_manifest.txt\n_CPack_Packages/\nTesting/\n/cmake/avs_coap-config.cmake\n/build*\n/coverage/\n/examples/build\n/include_public/avsystem/coap/avs_coap_config.h\n/tools/__pycache__\ngithub_staging/\n\n# documentation\n/doc/doxygen\n/doc/sphinx/conf.py\n/doc/sphinx/html\n\n# dist package\n*.tar.gz\n*.tgz\n*.deb\n\n# gdb stuff\n.gdb_history\n\n# python\n__pycache__\nvenv/\nbuild/\ndist/\n*.egg-info/\n"
  },
  {
    "path": "deps/avs_coap/CMakeLists.txt",
    "content": "# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem CoAP library\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\ncmake_minimum_required(VERSION 3.16.0)\nproject(avs_coap C)\n\ninclude(CMakeDependentOption)\n\nset(DEFAULT_AVS_COAP_VERSION \"\")\nfind_package(Git)\nif(GIT_FOUND)\n    execute_process(COMMAND ${GIT_EXECUTABLE} describe --tags --always\n                    OUTPUT_VARIABLE DEFAULT_AVS_COAP_VERSION\n                    OUTPUT_STRIP_TRAILING_WHITESPACE\n                    WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR})\nendif()\nif(DEFAULT_AVS_COAP_VERSION STREQUAL \"\")\n    set(DEFAULT_AVS_COAP_VERSION \"1.0-SNAPSHOT\")\nendif()\n\nset(AVS_COAP_VERSION \"${DEFAULT_AVS_COAP_VERSION}\" CACHE STRING \"avs_coap library version\")\n\nset(AVS_COAP_SOURCE_DIR \"${CMAKE_CURRENT_LIST_DIR}\")\n\n### library options\n\noption(WITH_TEST \"Compile unit tests\" OFF)\noption(WITH_POISONING \"Poison libc symbols that shall not be used\" OFF)\noption(WITH_AVS_COAP_DIAGNOSTIC_MESSAGES \"Include diagnostic payload in Abort messages\" ON)\noption(WITH_AVS_COAP_UDP \"Enable CoAP over UDP support\" ON)\noption(WITH_AVS_COAP_TCP \"Enable CoAP over TCP support\" ON)\n\n\noption(WITH_AVS_COAP_STREAMING_API \"Enable streaming API\" ON)\noption(WITH_AVS_COAP_OBSERVE \"Enable support for observations\" ON)\noption(WITH_AVS_COAP_OBSERVE_CANCEL_ON_TIMEOUT \"Turn on cancelling observation on a timeout \" OFF)\noption(WITH_AVS_COAP_OBSERVE_FORCE_CANCEL_ON_UNACKED_ERROR \"Force cancelling observation on unacked error\" ON)\ncmake_dependent_option(WITH_AVS_COAP_OBSERVE_PERSISTENCE \"Enable observations persistence\" ON \"WITH_AVS_COAP_OBSERVE\" OFF)\noption(WITH_AVS_COAP_BLOCK \"Enable support for BLOCK/BERT transfers\" ON)\n\noption(WITH_AVS_COAP_LOGS \"Enable logging\" ON)\ncmake_dependent_option(WITH_AVS_COAP_TRACE_LOGS \"Enable TRACE-level logging\" ON \"WITH_AVS_COAP_LOGS;NOT EXTERNAL_LOG_LEVELS_HEADER\" OFF)\n\nset(COAP_UDP_NOTIFY_CACHE_SIZE 4 CACHE STRING \"Maximum number of notification tokens stored to match Reset responses to\")\n\n### depedencies\n\nset(AVS_COMMONS_REQUIRED_COMPONENTS avs_buffer avs_compat_threading avs_list avs_net avs_sched avs_utils avs_crypto)\n\nif(WITH_AVS_COAP_LOGS)\n    list(APPEND AVS_COMMONS_REQUIRED_COMPONENTS avs_log)\nendif()\n\nif(WITH_AVS_COAP_STREAMING_API)\n    list(APPEND AVS_COMMONS_REQUIRED_COMPONENTS avs_stream)\nendif()\n\nif(WITH_AVS_COAP_OBSERVE_PERSISTENCE)\n    list(APPEND AVS_COMMONS_REQUIRED_COMPONENTS avs_persistence)\nendif()\n\nif(WITH_TEST)\n    list(APPEND AVS_COMMONS_REQUIRED_COMPONENTS avs_unit)\nendif()\n\n# TODO: is there a better way of detecting if avs_commons is already included?\nif(NOT TARGET avs_commons_global_headers)\n    # enable only necessary components - this prevents compiling \"old\" avs_coap\n    foreach(MOD IN LISTS AVS_COMMONS_REQUIRED_COMPONENTS)\n        string(TOUPPER \"${MOD}\" MOD_UPPER)\n        set(WITH_${MOD_UPPER} ON CACHE STRING \"\")\n    endforeach()\n\n    add_subdirectory(deps/avs_commons)\n    set(AVS_COMMONS_INCLUDE_DIRS\n       \"${CMAKE_CURRENT_SOURCE_DIR}/deps/avs_commons/include_public\")\nendif()\n\n### feature support\n\nset(WITH_AVS_COAP_POISONING \"${WITH_POISONING}\")\n\nconfigure_file(include_public/avsystem/coap/avs_coap_config.h.in\n               include_public/avsystem/coap/avs_coap_config.h)\n\n### targets\n\nset(PUBLIC_INCLUDES\n    include_public/avsystem/coap/streaming.h\n    include_public/avsystem/coap/async.h\n    include_public/avsystem/coap/coap.h\n    include_public/avsystem/coap/async_client.h\n    include_public/avsystem/coap/code.h\n    include_public/avsystem/coap/tcp.h\n    include_public/avsystem/coap/option.h\n    include_public/avsystem/coap/token.h\n    include_public/avsystem/coap/async_exchange.h\n    include_public/avsystem/coap/udp.h\n    include_public/avsystem/coap/writer.h\n    include_public/avsystem/coap/async_server.h\n    include_public/avsystem/coap/ctx.h\n    include_public/avsystem/coap/observe.h\n    ${CMAKE_CURRENT_BINARY_DIR}/include_public/avsystem/coap/avs_coap_config.h)\n\nset(SOURCES\n    ${PUBLIC_INCLUDES}\n\n    src/async/avs_coap_async_client.c\n    src/async/avs_coap_async_client.h\n    src/async/avs_coap_async_server.c\n    src/async/avs_coap_async_server.h\n    src/async/avs_coap_exchange.c\n    src/async/avs_coap_exchange.h\n    src/avs_coap_init.h\n    src/avs_coap_code_utils.c\n    src/avs_coap_code_utils.h\n    src/avs_coap_common_utils.c\n    src/avs_coap_common_utils.h\n    src/avs_coap_ctx.c\n    src/avs_coap_ctx.h\n    src/avs_coap_ctx_vtable.h\n    src/avs_coap_parse_utils.h\n\n    src/options/avs_coap_iterator.c\n    src/options/avs_coap_iterator.h\n    src/options/avs_coap_option.c\n    src/options/avs_coap_option.h\n    src/options/avs_coap_options.c\n    src/options/avs_coap_options.h\n\n    src/udp/avs_coap_udp_ctx.c\n    src/udp/avs_coap_udp_ctx.h\n    src/udp/avs_coap_udp_header.h\n    src/udp/avs_coap_udp_msg.h\n    src/udp/avs_coap_udp_msg.c\n    src/udp/avs_coap_udp_msg_cache.c\n    src/udp/avs_coap_udp_msg_cache.h\n    src/udp/avs_coap_udp_tx_params.c\n    src/udp/avs_coap_udp_tx_params.h\n    src/tcp/avs_coap_tcp_ctx.c\n    src/tcp/avs_coap_tcp_ctx.h\n    src/tcp/avs_coap_tcp_header.c\n    src/tcp/avs_coap_tcp_header.h\n    src/tcp/avs_coap_tcp_msg.c\n    src/tcp/avs_coap_tcp_msg.h\n    src/tcp/avs_coap_tcp_pending_requests.c\n    src/tcp/avs_coap_tcp_pending_requests.h\n    src/tcp/avs_coap_tcp_signaling.c\n    src/tcp/avs_coap_tcp_signaling.h\n    src/tcp/avs_coap_tcp_utils.c\n    src/tcp/avs_coap_tcp_utils.h\n\n\n    src/streaming/avs_coap_streaming_client.c\n    src/streaming/avs_coap_streaming_client.h\n    src/streaming/avs_coap_streaming_server.c\n    src/streaming/avs_coap_streaming_server.h\n\n    src/avs_coap_observe.c\n    src/avs_coap_observe.h)\n\n# avs_coap_library(NAME name\n#                  [ PRIVATE_FLAGS flags... ])\nfunction(add_coap_library)\n    cmake_parse_arguments(add_coap_library \"\" \"NAME\" \"PRIVATE_DEFINITIONS\" ${ARGN})\n    if (NOT add_coap_library_NAME)\n        message(FATAL_ERROR \"NAME is not specified\")\n    endif()\n\n    set(name ${add_coap_library_NAME})\n    set(private_definitions ${add_coap_library_PRIVATE_DEFINITIONS})\n    add_library(${name} STATIC ${SOURCES})\n\n    target_include_directories(${name} PRIVATE src)\n    target_include_directories(${name} PUBLIC\n                               $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/include_public>\n                               $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include_public>\n                               $<INSTALL_INTERFACE:include>)\n    target_link_libraries(${name} PUBLIC ${AVS_COMMONS_REQUIRED_COMPONENTS})\n    target_compile_definitions(${name} PRIVATE ${private_definitions})\nendfunction()\n\nadd_coap_library(NAME avs_coap)\n\n### tests\n\nif(WITH_TEST)\n    enable_testing()\n\n    add_coap_library(NAME avs_coap_for_tests\n                     PRIVATE_DEFINITIONS AVS_UNIT_TESTING)\n    set_target_properties(avs_coap_for_tests PROPERTIES EXCLUDE_FROM_ALL TRUE)\n\n    set(TEST_SOURCES\n        tests/tcp/async_client.c\n        tests/tcp/async_server.c\n        tests/tcp/csm.c\n        tests/tcp/ctx.c\n        tests/tcp/header.c\n        tests/tcp/payload_escaper.c\n        tests/tcp/responding.c\n        tests/tcp/requesting.c\n        tests/tcp/setsock.c\n\n        tests/socket.c\n        tests/socket.h\n        tests/mock_clock.c\n        tests/mock_clock.h\n        tests/utils.h\n        tests/utils.c\n\n        tests/options/option.c\n        tests/options/options.c\n\n        tests/udp/async_client_with_big_data.c\n        tests/udp/async_client.c\n        tests/udp/async_server.c\n        tests/udp/big_data.h\n        tests/udp/fuzzer_cases.c\n        tests/udp/msg_cache.c\n        tests/udp/msg.c\n        tests/udp/udp_tx_params.c\n        tests/udp/utils.h\n        tests/udp/setsock.c\n\n        tests/udp/async_observe.c\n\n        tests/udp/streaming_client.c\n        tests/udp/streaming_server.c\n\n        tests/udp/streaming_observe.c)\n\n    add_executable(avs_coap_test EXCLUDE_FROM_ALL ${TEST_SOURCES})\n    target_include_directories(avs_coap_test PRIVATE src)\n    target_include_directories(avs_coap_test PRIVATE\n                               \"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>\")\n    # dl required for mock_clock\n    target_link_libraries(avs_coap_test PRIVATE avs_coap_for_tests avs_unit dl)\n    target_compile_options(avs_coap_test PRIVATE\n                           -Wno-c++-compat -Wno-missing-field-initializers)\n    # disable identifier poisoning for tests\n    target_compile_definitions(avs_coap_test PRIVATE AVS_UNIT_TESTING)\n\n    find_program(VALGRIND_EXECUTABLE valgrind)\n    if(VALGRIND_EXECUTABLE)\n        # This is enabled in devconfig only, and should be disabled in CMakeLists.txt,\n        # to avoid cross-compilation errors on stations with valgrind installed.\n        option(WITH_VALGRIND \"Enable usage of valgrind during unit tests\" ON)\n        set(VALGRIND ${VALGRIND_EXECUTABLE} --leak-check=full --track-origins=yes -q --error-exitcode=63 --log-file=VALGRIND.avs_coap_test.log)\n    endif()\n    if(VALGRIND AND WITH_VALGRIND)\n        set(VALGRIND_CMD ${VALGRIND})\n    else()\n        set(VALGRIND_CMD)\n    endif()\n\n    add_test(NAME avs_coap_test COMMAND ${VALGRIND_CMD} $<TARGET_FILE:avs_coap_test>)\n\n    add_custom_target(avs_coap_check COMMAND ${CMAKE_CTEST_COMMAND} -V -R \"^avs_coap_test$\" DEPENDS avs_coap_test)\n    if(\"${CMAKE_CURRENT_SOURCE_DIR}\" STREQUAL \"${CMAKE_SOURCE_DIR}\")\n        # only add \"check\" target if building avs_coap standalone\n        add_custom_target(check)\n        add_dependencies(check avs_coap_check)\n    endif()\n\n    # Source validation\n    if(DEFINED AVS_COMMONS_SOURCE_DIR)\n        set(ABSOLUTE_HEADERS)\n        foreach(F ${SOURCES})\n            add_test(NAME test_avs_coap_${F}_visibility\n                     COMMAND \"${AVS_COMMONS_SOURCE_DIR}/test_visibility.py\" \"${F}\"\n                     WORKING_DIRECTORY \"${CMAKE_CURRENT_SOURCE_DIR}\")\n            add_test(NAME test_avs_coap_${F}_headers\n                     COMMAND \"${AVS_COMMONS_SOURCE_DIR}/test_headers.py\" \"${F}\" tools/conditional_headers_whitelist.json\n                     WORKING_DIRECTORY \"${CMAKE_CURRENT_SOURCE_DIR}\")\n            if(F MATCHES [.]h$)\n                list(APPEND ABSOLUTE_HEADERS \"${CMAKE_CURRENT_SOURCE_DIR}/${F}\")\n            endif()\n        endforeach()\n\n        add_custom_target(avs_coap_visibility_check COMMAND ${CMAKE_CTEST_COMMAND} -R \"'^test_avs_coap_.*_visibility$$'\")\n        add_dependencies(avs_coap_check avs_coap_visibility_check)\n\n        add_custom_target(avs_coap_headers_check COMMAND ${CMAKE_CTEST_COMMAND} -R \"'^test_avs_coap_.*_headers$$'\")\n        add_dependencies(avs_coap_check avs_coap_headers_check)\n\n        set(ALLOWED_SYMBOLS avs_coap_ AVS_COAP_ \"__odr_asan[.]\")\n        if(NOT BUILD_SHARED_LIBS)\n            set(ALLOWED_SYMBOLS ${ALLOWED_SYMBOLS} _avs_coap_ _AVS_COAP_)\n        endif()\n        add_test(NAME test_avs_coap_symbols COMMAND \"${AVS_COMMONS_SOURCE_DIR}/test_symbols.sh\" $<TARGET_FILE:avs_coap> ${ALLOWED_SYMBOLS})\n        add_custom_target(avs_coap_symbols_check COMMAND ${CMAKE_CTEST_COMMAND} -R \"'^test_avs_coap_symbols$$'\" --output-on-failure)\n        add_dependencies(avs_coap_symbols_check avs_coap)\n        add_dependencies(avs_coap_check avs_coap_symbols_check)\n    endif()\n\n    add_subdirectory(tests/fuzz)\n\n    include(cmake/AddHeaderSelfSufficiencyTests.cmake)\n    add_header_self_sufficiency_tests(TARGET avs_coap_public_header_self_sufficiency_check\n                                      TARGET_PREFIX avs_coap\n                                      DIRECTORIES include_public\n                                      LIBS avs_coap_for_tests)\n    add_header_self_sufficiency_tests(TARGET avs_coap_internal_header_self_sufficiency_check\n                                      TARGET_PREFIX avs_coap\n                                      INCLUDES\n                                      \"avs_coap_init.h\"\n                                      \"avs_coap_x_log_config.h\"\n                                      INCLUDE_DIRECTORIES\n                                      \"${CMAKE_CURRENT_BINARY_DIR}/src\"\n                                      \"${CMAKE_CURRENT_SOURCE_DIR}/src\"\n                                      COMPILE_OPTIONS\n                                      # disable test-specific warnings\n                                      $<TARGET_PROPERTY:avs_coap_test,COMPILE_OPTIONS>\n                                      -DMODULE_NAME=header_self_sufficiency_test\n                                      DIRECTORIES src\n                                      EXCLUDE_PATTERNS \".*/test/.*\"\n                                      LIBS avs_coap_for_tests avs_unit)\n    add_custom_target(avs_coap_header_self_sufficiency_check)\n    add_dependencies(avs_coap_header_self_sufficiency_check\n                     avs_coap_public_header_self_sufficiency_check\n                     avs_coap_internal_header_self_sufficiency_check)\n    add_dependencies(avs_coap_check avs_coap_header_self_sufficiency_check)\n\n    add_custom_target(avs_coap_filename_check\n                      COMMAND ! find src -name \"'*.[ch]'\" | sed -e \"'s|^.*/||'\" | grep -v \"'^avs_coap_'\"\n                      COMMAND ! find src -name \"'*.[ch]'\" | sed -e \"'s|^.*/||'\" | sort | uniq -c | grep -v \"'^ *1 '\"\n                      WORKING_DIRECTORY \"${CMAKE_CURRENT_SOURCE_DIR}\")\n    add_dependencies(avs_coap_check avs_coap_filename_check)\n\n    function(add_cpp_header_warnings_check)\n        set(options)\n        set(one_value_args TARGET)\n        set(multi_value_args INCLUDES)\n        cmake_parse_arguments(HWC \"${options}\" \"${one_value_args}\" \"${multi_value_args}\" ${ARGN})\n\n        set(all_includes)\n        foreach(include IN LISTS HWC_INCLUDES)\n            string(REGEX REPLACE \"^include_public/\" \"\" include \"${include}\")\n            list(APPEND all_includes \"#include <${include}>\")\n        endforeach()\n        string(REPLACE \"#\" \"\\\\#\" all_includes \"${all_includes}\")\n        string(REPLACE \"<\" \"\\\\<\" all_includes \"${all_includes}\")\n        string(REPLACE \">\" \"\\\\>\" all_includes \"${all_includes}\")\n        string(REPLACE \"\\\\;\" \"\\\\n\" all_includes \"${all_includes}\")\n\n        set(source_file \"${CMAKE_CURRENT_BINARY_DIR}/ensure-no-warnings-from-headers-if-cpp.cpp\")\n        add_custom_command(OUTPUT \"${source_file}\"\n                           COMMAND /bin/echo > \"${source_file}\"\n                           COMMAND for F in ${all_includes} \\\\; do /bin/echo \"$$F\" >> \"${source_file}\" \\\\; done)\n        add_library(\"${HWC_TARGET}\" OBJECT \"${source_file}\")\n        target_include_directories(\"${HWC_TARGET}\" PRIVATE $<TARGET_PROPERTY:avs_coap,INTERFACE_INCLUDE_DIRECTORIES>)\n        target_compile_options(\"${HWC_TARGET}\" PRIVATE -std=c++11 -Wall -Wextra -pedantic -Werror)\n        set_target_properties(\"${HWC_TARGET}\" PROPERTIES EXCLUDE_FROM_ALL TRUE)\n        add_dependencies(avs_coap_check \"${HWC_TARGET}\")\n    endfunction()\n\n    enable_language(CXX)\n    add_cpp_header_warnings_check(TARGET avs_coap_cpp_header_check\n                                  INCLUDES ${PUBLIC_INCLUDES})\nendif()\n\nadd_subdirectory(doc)\nif(IS_DIRECTORY \"${CMAKE_CURRENT_SOURCE_DIR}/deps/avs_commons\")\n    add_subdirectory(examples)\nendif()\n\n### distribution\n\ninstall(TARGETS avs_coap EXPORT avs_coap-targets DESTINATION lib)\ninstall(EXPORT avs_coap-targets DESTINATION lib/avs_coap)\ninstall(DIRECTORY include_public/\n        DESTINATION include\n        FILES_MATCHING REGEX \"[.]h$\")\ninstall(FILES \"${CMAKE_CURRENT_BINARY_DIR}/include_public/avsystem/coap/avs_coap_config.h\"\n        DESTINATION include/avsystem/coap)\n\n# see https://cmake.org/cmake/help/v3.4/module/CMakePackageConfigHelpers.html#example-generating-package-files\ninclude(CMakePackageConfigHelpers)\n\nconfigure_file(cmake/avs_coap-config.cmake.in\n               cmake/avs_coap-config.cmake)\nconfigure_package_config_file(cmake/avs_coap-config.cmake.in\n                              ${CMAKE_CURRENT_BINARY_DIR}/avs_coap-config.cmake\n                              INSTALL_DESTINATION lib/avs_coap)\nwrite_basic_package_version_file(${CMAKE_CURRENT_BINARY_DIR}/avs_coap-version.cmake\n                                 VERSION \"${AVS_COAP_VERSION}\"\n                                 COMPATIBILITY SameMajorVersion)\ninstall(FILES\n        \"${CMAKE_CURRENT_BINARY_DIR}/avs_coap-config.cmake\"\n        \"${CMAKE_CURRENT_BINARY_DIR}/avs_coap-version.cmake\"\n        DESTINATION lib/avs_coap)\n\ninstall(SCRIPT cmake/fill-placeholders.cmake)\n"
  },
  {
    "path": "deps/avs_coap/Doxyfile.in",
    "content": "# Doxyfile 1.8.18\n\n# This file describes the settings to be used by the documentation system\n# doxygen (www.doxygen.org) for a project.\n#\n# All text after a double hash (##) is considered a comment and is placed in\n# front of the TAG it is preceding.\n#\n# All text after a single hash (#) is considered a comment and will be ignored.\n# The format is:\n# TAG = value [value, ...]\n# For lists, items can also be appended using:\n# TAG += value [value, ...]\n# Values that contain spaces should be placed between quotes (\\\" \\\").\n\n#---------------------------------------------------------------------------\n# Project related configuration options\n#---------------------------------------------------------------------------\n\n# This tag specifies the encoding used for all characters in the configuration\n# file that follow. The default is UTF-8 which is also the encoding used for all\n# text before the first occurrence of this tag. Doxygen uses libiconv (or the\n# iconv built into libc) for the transcoding. See\n# https://www.gnu.org/software/libiconv/ for the list of possible encodings.\n# The default value is: UTF-8.\n\nDOXYFILE_ENCODING      = UTF-8\n\n# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by\n# double-quotes, unless you are using Doxywizard) that should identify the\n# project for which the documentation is generated. This name is used in the\n# title of most generated pages and in a few other places.\n# The default value is: My Project.\n\nPROJECT_NAME           = avs_coap\n\n# The PROJECT_NUMBER tag can be used to enter a project or revision number. This\n# could be handy for archiving the generated documentation or if some version\n# control system is used.\n\nPROJECT_NUMBER         =\n\n# Using the PROJECT_BRIEF tag one can provide an optional one line description\n# for a project that appears at the top of each page and should give viewer a\n# quick idea about the purpose of the project. Keep the description short.\n\nPROJECT_BRIEF          =\n\n# With the PROJECT_LOGO tag one can specify a logo or an icon that is included\n# in the documentation. The maximum height of the logo should not exceed 55\n# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy\n# the logo to the output directory.\n\nPROJECT_LOGO           =\n\n# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path\n# into which the generated documentation will be written. If a relative path is\n# entered, it will be relative to the location where doxygen was started. If\n# left blank the current directory will be used.\n\nOUTPUT_DIRECTORY       = @DOXYGEN_OUTPUT_DIR@\n\n# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub-\n# directories (in 2 levels) under the output directory of each output format and\n# will distribute the generated files over these directories. Enabling this\n# option can be useful when feeding doxygen a huge amount of source files, where\n# putting all generated files in the same directory would otherwise causes\n# performance problems for the file system.\n# The default value is: NO.\n\nCREATE_SUBDIRS         = NO\n\n# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII\n# characters to appear in the names of generated files. If set to NO, non-ASCII\n# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode\n# U+3044.\n# The default value is: NO.\n\nALLOW_UNICODE_NAMES    = NO\n\n# The OUTPUT_LANGUAGE tag is used to specify the language in which all\n# documentation generated by doxygen is written. Doxygen will use this\n# information to generate all constant output in the proper language.\n# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese,\n# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States),\n# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian,\n# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages),\n# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian,\n# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian,\n# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish,\n# Ukrainian and Vietnamese.\n# The default value is: English.\n\nOUTPUT_LANGUAGE        = English\n\n# The OUTPUT_TEXT_DIRECTION tag is used to specify the direction in which all\n# documentation generated by doxygen is written. Doxygen will use this\n# information to generate all generated output in the proper direction.\n# Possible values are: None, LTR, RTL and Context.\n# The default value is: None.\n\nOUTPUT_TEXT_DIRECTION  = None\n\n# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member\n# descriptions after the members that are listed in the file and class\n# documentation (similar to Javadoc). Set to NO to disable this.\n# The default value is: YES.\n\nBRIEF_MEMBER_DESC      = YES\n\n# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief\n# description of a member or function before the detailed description\n#\n# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the\n# brief descriptions will be completely suppressed.\n# The default value is: YES.\n\nREPEAT_BRIEF           = YES\n\n# This tag implements a quasi-intelligent brief description abbreviator that is\n# used to form the text in various listings. Each string in this list, if found\n# as the leading text of the brief description, will be stripped from the text\n# and the result, after processing the whole list, is used as the annotated\n# text. Otherwise, the brief description is used as-is. If left blank, the\n# following values are used ($name is automatically replaced with the name of\n# the entity):The $name class, The $name widget, The $name file, is, provides,\n# specifies, contains, represents, a, an and the.\n\nABBREVIATE_BRIEF       = \"The $name class\" \\\n                         \"The $name widget\" \\\n                         \"The $name file\" \\\n                         is \\\n                         provides \\\n                         specifies \\\n                         contains \\\n                         represents \\\n                         a \\\n                         an \\\n                         the\n\n# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then\n# doxygen will generate a detailed section even if there is only a brief\n# description.\n# The default value is: NO.\n\nALWAYS_DETAILED_SEC    = NO\n\n# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all\n# inherited members of a class in the documentation of that class as if those\n# members were ordinary class members. Constructors, destructors and assignment\n# operators of the base classes will not be shown.\n# The default value is: NO.\n\nINLINE_INHERITED_MEMB  = NO\n\n# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path\n# before files name in the file list and in the header files. If set to NO the\n# shortest path that makes the file name unique will be used\n# The default value is: YES.\n\nFULL_PATH_NAMES        = YES\n\n# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path.\n# Stripping is only done if one of the specified strings matches the left-hand\n# part of the path. The tag can be used to show relative paths in the file list.\n# If left blank the directory from which doxygen is run is used as the path to\n# strip.\n#\n# Note that you can specify absolute paths here, but also relative paths, which\n# will be relative from the directory where doxygen is started.\n# This tag requires that the tag FULL_PATH_NAMES is set to YES.\n\nSTRIP_FROM_PATH        =\n\n# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the\n# path mentioned in the documentation of a class, which tells the reader which\n# header file to include in order to use a class. If left blank only the name of\n# the header file containing the class definition is used. Otherwise one should\n# specify the list of include paths that are normally passed to the compiler\n# using the -I flag.\n\nSTRIP_FROM_INC_PATH    =\n\n# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but\n# less readable) file names. This can be useful is your file systems doesn't\n# support long names like on DOS, Mac, or CD-ROM.\n# The default value is: NO.\n\nSHORT_NAMES            = NO\n\n# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the\n# first line (until the first dot) of a Javadoc-style comment as the brief\n# description. If set to NO, the Javadoc-style will behave just like regular Qt-\n# style comments (thus requiring an explicit @brief command for a brief\n# description.)\n# The default value is: NO.\n\nJAVADOC_AUTOBRIEF      = NO\n\n# If the JAVADOC_BANNER tag is set to YES then doxygen will interpret a line\n# such as\n# /***************\n# as being the beginning of a Javadoc-style comment \"banner\". If set to NO, the\n# Javadoc-style will behave just like regular comments and it will not be\n# interpreted by doxygen.\n# The default value is: NO.\n\nJAVADOC_BANNER         = NO\n\n# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first\n# line (until the first dot) of a Qt-style comment as the brief description. If\n# set to NO, the Qt-style will behave just like regular Qt-style comments (thus\n# requiring an explicit \\brief command for a brief description.)\n# The default value is: NO.\n\nQT_AUTOBRIEF           = NO\n\n# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a\n# multi-line C++ special comment block (i.e. a block of //! or /// comments) as\n# a brief description. This used to be the default behavior. The new default is\n# to treat a multi-line C++ comment block as a detailed description. Set this\n# tag to YES if you prefer the old behavior instead.\n#\n# Note that setting this tag to YES also means that rational rose comments are\n# not recognized any more.\n# The default value is: NO.\n\nMULTILINE_CPP_IS_BRIEF = NO\n\n# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the\n# documentation from any documented member that it re-implements.\n# The default value is: YES.\n\nINHERIT_DOCS           = YES\n\n# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new\n# page for each member. If set to NO, the documentation of a member will be part\n# of the file/class/namespace that contains it.\n# The default value is: NO.\n\nSEPARATE_MEMBER_PAGES  = NO\n\n# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen\n# uses this value to replace tabs by spaces in code fragments.\n# Minimum value: 1, maximum value: 16, default value: 4.\n\nTAB_SIZE               = 4\n\n# This tag can be used to specify a number of aliases that act as commands in\n# the documentation. An alias has the form:\n# name=value\n# For example adding\n# \"sideeffect=@par Side Effects:\\n\"\n# will allow you to put the command \\sideeffect (or @sideeffect) in the\n# documentation, which will result in a user-defined paragraph with heading\n# \"Side Effects:\". You can put \\n's in the value part of an alias to insert\n# newlines (in the resulting output). You can put ^^ in the value part of an\n# alias to insert a newline as if a physical newline was in the original file.\n# When you need a literal { or } or , in the value part of an alias you have to\n# escape them by means of a backslash (\\), this can lead to conflicts with the\n# commands \\{ and \\} for these it is advised to use the version @{ and @} or use\n# a double escape (\\\\{ and \\\\})\n\nALIASES                =\n\n# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources\n# only. Doxygen will then generate output that is more tailored for C. For\n# instance, some of the names that are used will be different. The list of all\n# members will be omitted, etc.\n# The default value is: NO.\n\nOPTIMIZE_OUTPUT_FOR_C  = YES\n\n# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or\n# Python sources only. Doxygen will then generate output that is more tailored\n# for that language. For instance, namespaces will be presented as packages,\n# qualified scopes will look different, etc.\n# The default value is: NO.\n\nOPTIMIZE_OUTPUT_JAVA   = NO\n\n# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran\n# sources. Doxygen will then generate output that is tailored for Fortran.\n# The default value is: NO.\n\nOPTIMIZE_FOR_FORTRAN   = NO\n\n# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL\n# sources. Doxygen will then generate output that is tailored for VHDL.\n# The default value is: NO.\n\nOPTIMIZE_OUTPUT_VHDL   = NO\n\n# Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice\n# sources only. Doxygen will then generate output that is more tailored for that\n# language. For instance, namespaces will be presented as modules, types will be\n# separated into more groups, etc.\n# The default value is: NO.\n\nOPTIMIZE_OUTPUT_SLICE  = NO\n\n# Doxygen selects the parser to use depending on the extension of the files it\n# parses. With this tag you can assign which parser to use for a given\n# extension. Doxygen has a built-in mapping, but you can override or extend it\n# using this tag. The format is ext=language, where ext is a file extension, and\n# language is one of the parsers supported by doxygen: IDL, Java, JavaScript,\n# Csharp (C#), C, C++, D, PHP, md (Markdown), Objective-C, Python, Slice, VHDL,\n# Fortran (fixed format Fortran: FortranFixed, free formatted Fortran:\n# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser\n# tries to guess whether the code is fixed or free formatted code, this is the\n# default for Fortran type files). For instance to make doxygen treat .inc files\n# as Fortran files (default is PHP), and .f files as C (default is Fortran),\n# use: inc=Fortran f=C.\n#\n# Note: For files without extension you can use no_extension as a placeholder.\n#\n# Note that for custom extensions you also need to set FILE_PATTERNS otherwise\n# the files are not read by doxygen.\n\nEXTENSION_MAPPING      =\n\n# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments\n# according to the Markdown format, which allows for more readable\n# documentation. See https://daringfireball.net/projects/markdown/ for details.\n# The output of markdown processing is further processed by doxygen, so you can\n# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in\n# case of backward compatibilities issues.\n# The default value is: YES.\n\nMARKDOWN_SUPPORT       = YES\n\n# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up\n# to that level are automatically included in the table of contents, even if\n# they do not have an id attribute.\n# Note: This feature currently applies only to Markdown headings.\n# Minimum value: 0, maximum value: 99, default value: 5.\n# This tag requires that the tag MARKDOWN_SUPPORT is set to YES.\n\nTOC_INCLUDE_HEADINGS   = 5\n\n# When enabled doxygen tries to link words that correspond to documented\n# classes, or namespaces to their corresponding documentation. Such a link can\n# be prevented in individual cases by putting a % sign in front of the word or\n# globally by setting AUTOLINK_SUPPORT to NO.\n# The default value is: YES.\n\nAUTOLINK_SUPPORT       = YES\n\n# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want\n# to include (a tag file for) the STL sources as input, then you should set this\n# tag to YES in order to let doxygen match functions declarations and\n# definitions whose arguments contain STL classes (e.g. func(std::string);\n# versus func(std::string) {}). This also make the inheritance and collaboration\n# diagrams that involve STL classes more complete and accurate.\n# The default value is: NO.\n\nBUILTIN_STL_SUPPORT    = NO\n\n# If you use Microsoft's C++/CLI language, you should set this option to YES to\n# enable parsing support.\n# The default value is: NO.\n\nCPP_CLI_SUPPORT        = NO\n\n# Set the SIP_SUPPORT tag to YES if your project consists of sip (see:\n# https://www.riverbankcomputing.com/software/sip/intro) sources only. Doxygen\n# will parse them like normal C++ but will assume all classes use public instead\n# of private inheritance when no explicit protection keyword is present.\n# The default value is: NO.\n\nSIP_SUPPORT            = NO\n\n# For Microsoft's IDL there are propget and propput attributes to indicate\n# getter and setter methods for a property. Setting this option to YES will make\n# doxygen to replace the get and set methods by a property in the documentation.\n# This will only work if the methods are indeed getting or setting a simple\n# type. If this is not the case, or you want to show the methods anyway, you\n# should set this option to NO.\n# The default value is: YES.\n\nIDL_PROPERTY_SUPPORT   = YES\n\n# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC\n# tag is set to YES then doxygen will reuse the documentation of the first\n# member in the group (if any) for the other members of the group. By default\n# all members of a group must be documented explicitly.\n# The default value is: NO.\n\nDISTRIBUTE_GROUP_DOC   = NO\n\n# If one adds a struct or class to a group and this option is enabled, then also\n# any nested class or struct is added to the same group. By default this option\n# is disabled and one has to add nested compounds explicitly via \\ingroup.\n# The default value is: NO.\n\nGROUP_NESTED_COMPOUNDS = NO\n\n# Set the SUBGROUPING tag to YES to allow class member groups of the same type\n# (for instance a group of public functions) to be put as a subgroup of that\n# type (e.g. under the Public Functions section). Set it to NO to prevent\n# subgrouping. Alternatively, this can be done per class using the\n# \\nosubgrouping command.\n# The default value is: YES.\n\nSUBGROUPING            = YES\n\n# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions\n# are shown inside the group in which they are included (e.g. using \\ingroup)\n# instead of on a separate page (for HTML and Man pages) or section (for LaTeX\n# and RTF).\n#\n# Note that this feature does not work in combination with\n# SEPARATE_MEMBER_PAGES.\n# The default value is: NO.\n\nINLINE_GROUPED_CLASSES = NO\n\n# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions\n# with only public data fields or simple typedef fields will be shown inline in\n# the documentation of the scope in which they are defined (i.e. file,\n# namespace, or group documentation), provided this scope is documented. If set\n# to NO, structs, classes, and unions are shown on a separate page (for HTML and\n# Man pages) or section (for LaTeX and RTF).\n# The default value is: NO.\n\nINLINE_SIMPLE_STRUCTS  = NO\n\n# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or\n# enum is documented as struct, union, or enum with the name of the typedef. So\n# typedef struct TypeS {} TypeT, will appear in the documentation as a struct\n# with name TypeT. When disabled the typedef will appear as a member of a file,\n# namespace, or class. And the struct will be named TypeS. This can typically be\n# useful for C code in case the coding convention dictates that all compound\n# types are typedef'ed and only the typedef is referenced, never the tag name.\n# The default value is: NO.\n\nTYPEDEF_HIDES_STRUCT   = NO\n\n# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This\n# cache is used to resolve symbols given their name and scope. Since this can be\n# an expensive process and often the same symbol appears multiple times in the\n# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small\n# doxygen will become slower. If the cache is too large, memory is wasted. The\n# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range\n# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536\n# symbols. At the end of a run doxygen will report the cache usage and suggest\n# the optimal cache size from a speed point of view.\n# Minimum value: 0, maximum value: 9, default value: 0.\n\nLOOKUP_CACHE_SIZE      = 0\n\n#---------------------------------------------------------------------------\n# Build related configuration options\n#---------------------------------------------------------------------------\n\n# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in\n# documentation are documented, even if no documentation was available. Private\n# class members and static file members will be hidden unless the\n# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES.\n# Note: This will also disable the warnings about undocumented members that are\n# normally produced when WARNINGS is set to YES.\n# The default value is: NO.\n\nEXTRACT_ALL            = YES\n\n# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will\n# be included in the documentation.\n# The default value is: NO.\n\nEXTRACT_PRIVATE        = NO\n\n# If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual\n# methods of a class will be included in the documentation.\n# The default value is: NO.\n\nEXTRACT_PRIV_VIRTUAL   = NO\n\n# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal\n# scope will be included in the documentation.\n# The default value is: NO.\n\nEXTRACT_PACKAGE        = NO\n\n# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be\n# included in the documentation.\n# The default value is: NO.\n\nEXTRACT_STATIC         = YES\n\n# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined\n# locally in source files will be included in the documentation. If set to NO,\n# only classes defined in header files are included. Does not have any effect\n# for Java sources.\n# The default value is: YES.\n\nEXTRACT_LOCAL_CLASSES  = YES\n\n# This flag is only useful for Objective-C code. If set to YES, local methods,\n# which are defined in the implementation section but not in the interface are\n# included in the documentation. If set to NO, only methods in the interface are\n# included.\n# The default value is: NO.\n\nEXTRACT_LOCAL_METHODS  = NO\n\n# If this flag is set to YES, the members of anonymous namespaces will be\n# extracted and appear in the documentation as a namespace called\n# 'anonymous_namespace{file}', where file will be replaced with the base name of\n# the file that contains the anonymous namespace. By default anonymous namespace\n# are hidden.\n# The default value is: NO.\n\nEXTRACT_ANON_NSPACES   = NO\n\n# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all\n# undocumented members inside documented classes or files. If set to NO these\n# members will be included in the various overviews, but no documentation\n# section is generated. This option has no effect if EXTRACT_ALL is enabled.\n# The default value is: NO.\n\nHIDE_UNDOC_MEMBERS     = NO\n\n# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all\n# undocumented classes that are normally visible in the class hierarchy. If set\n# to NO, these classes will be included in the various overviews. This option\n# has no effect if EXTRACT_ALL is enabled.\n# The default value is: NO.\n\nHIDE_UNDOC_CLASSES     = NO\n\n# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend\n# declarations. If set to NO, these declarations will be included in the\n# documentation.\n# The default value is: NO.\n\nHIDE_FRIEND_COMPOUNDS  = NO\n\n# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any\n# documentation blocks found inside the body of a function. If set to NO, these\n# blocks will be appended to the function's detailed documentation block.\n# The default value is: NO.\n\nHIDE_IN_BODY_DOCS      = NO\n\n# The INTERNAL_DOCS tag determines if documentation that is typed after a\n# \\internal command is included. If the tag is set to NO then the documentation\n# will be excluded. Set it to YES to include the internal documentation.\n# The default value is: NO.\n\nINTERNAL_DOCS          = NO\n\n# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file\n# names in lower-case letters. If set to YES, upper-case letters are also\n# allowed. This is useful if you have classes or files whose names only differ\n# in case and if your file system supports case sensitive file names. Windows\n# (including Cygwin) ands Mac users are advised to set this option to NO.\n# The default value is: system dependent.\n\nCASE_SENSE_NAMES       = YES\n\n# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with\n# their full class and namespace scopes in the documentation. If set to YES, the\n# scope will be hidden.\n# The default value is: NO.\n\nHIDE_SCOPE_NAMES       = NO\n\n# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will\n# append additional text to a page's title, such as Class Reference. If set to\n# YES the compound reference will be hidden.\n# The default value is: NO.\n\nHIDE_COMPOUND_REFERENCE= NO\n\n# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of\n# the files that are included by a file in the documentation of that file.\n# The default value is: YES.\n\nSHOW_INCLUDE_FILES     = YES\n\n# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each\n# grouped member an include statement to the documentation, telling the reader\n# which file to include in order to use the member.\n# The default value is: NO.\n\nSHOW_GROUPED_MEMB_INC  = NO\n\n# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include\n# files with double quotes in the documentation rather than with sharp brackets.\n# The default value is: NO.\n\nFORCE_LOCAL_INCLUDES   = NO\n\n# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the\n# documentation for inline members.\n# The default value is: YES.\n\nINLINE_INFO            = YES\n\n# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the\n# (detailed) documentation of file and class members alphabetically by member\n# name. If set to NO, the members will appear in declaration order.\n# The default value is: YES.\n\nSORT_MEMBER_DOCS       = YES\n\n# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief\n# descriptions of file, namespace and class members alphabetically by member\n# name. If set to NO, the members will appear in declaration order. Note that\n# this will also influence the order of the classes in the class list.\n# The default value is: NO.\n\nSORT_BRIEF_DOCS        = NO\n\n# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the\n# (brief and detailed) documentation of class members so that constructors and\n# destructors are listed first. If set to NO the constructors will appear in the\n# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS.\n# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief\n# member documentation.\n# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting\n# detailed member documentation.\n# The default value is: NO.\n\nSORT_MEMBERS_CTORS_1ST = NO\n\n# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy\n# of group names into alphabetical order. If set to NO the group names will\n# appear in their defined order.\n# The default value is: NO.\n\nSORT_GROUP_NAMES       = NO\n\n# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by\n# fully-qualified names, including namespaces. If set to NO, the class list will\n# be sorted only by class name, not including the namespace part.\n# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.\n# Note: This option applies only to the class list, not to the alphabetical\n# list.\n# The default value is: NO.\n\nSORT_BY_SCOPE_NAME     = NO\n\n# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper\n# type resolution of all parameters of a function it will reject a match between\n# the prototype and the implementation of a member function even if there is\n# only one candidate or it is obvious which candidate to choose by doing a\n# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still\n# accept a match between prototype and implementation in such cases.\n# The default value is: NO.\n\nSTRICT_PROTO_MATCHING  = NO\n\n# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo\n# list. This list is created by putting \\todo commands in the documentation.\n# The default value is: YES.\n\nGENERATE_TODOLIST      = YES\n\n# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test\n# list. This list is created by putting \\test commands in the documentation.\n# The default value is: YES.\n\nGENERATE_TESTLIST      = YES\n\n# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug\n# list. This list is created by putting \\bug commands in the documentation.\n# The default value is: YES.\n\nGENERATE_BUGLIST       = YES\n\n# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO)\n# the deprecated list. This list is created by putting \\deprecated commands in\n# the documentation.\n# The default value is: YES.\n\nGENERATE_DEPRECATEDLIST= YES\n\n# The ENABLED_SECTIONS tag can be used to enable conditional documentation\n# sections, marked by \\if <section_label> ... \\endif and \\cond <section_label>\n# ... \\endcond blocks.\n\nENABLED_SECTIONS       =\n\n# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the\n# initial value of a variable or macro / define can have for it to appear in the\n# documentation. If the initializer consists of more lines than specified here\n# it will be hidden. Use a value of 0 to hide initializers completely. The\n# appearance of the value of individual variables and macros / defines can be\n# controlled using \\showinitializer or \\hideinitializer command in the\n# documentation regardless of this setting.\n# Minimum value: 0, maximum value: 10000, default value: 30.\n\nMAX_INITIALIZER_LINES  = 30\n\n# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at\n# the bottom of the documentation of classes and structs. If set to YES, the\n# list will mention the files that were used to generate the documentation.\n# The default value is: YES.\n\nSHOW_USED_FILES        = YES\n\n# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This\n# will remove the Files entry from the Quick Index and from the Folder Tree View\n# (if specified).\n# The default value is: YES.\n\nSHOW_FILES             = YES\n\n# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces\n# page. This will remove the Namespaces entry from the Quick Index and from the\n# Folder Tree View (if specified).\n# The default value is: YES.\n\nSHOW_NAMESPACES        = YES\n\n# The FILE_VERSION_FILTER tag can be used to specify a program or script that\n# doxygen should invoke to get the current version for each file (typically from\n# the version control system). Doxygen will invoke the program by executing (via\n# popen()) the command command input-file, where command is the value of the\n# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided\n# by doxygen. Whatever the program writes to standard output is used as the file\n# version. For an example see the documentation.\n\nFILE_VERSION_FILTER    =\n\n# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed\n# by doxygen. The layout file controls the global structure of the generated\n# output files in an output format independent way. To create the layout file\n# that represents doxygen's defaults, run doxygen with the -l option. You can\n# optionally specify a file name after the option, if omitted DoxygenLayout.xml\n# will be used as the name of the layout file.\n#\n# Note that if you run doxygen from a directory containing a file called\n# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE\n# tag is left empty.\n\nLAYOUT_FILE            =\n\n# The CITE_BIB_FILES tag can be used to specify one or more bib files containing\n# the reference definitions. This must be a list of .bib files. The .bib\n# extension is automatically appended if omitted. This requires the bibtex tool\n# to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info.\n# For LaTeX the style of the bibliography can be controlled using\n# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the\n# search path. See also \\cite for info how to create references.\n\nCITE_BIB_FILES         =\n\n#---------------------------------------------------------------------------\n# Configuration options related to warning and progress messages\n#---------------------------------------------------------------------------\n\n# The QUIET tag can be used to turn on/off the messages that are generated to\n# standard output by doxygen. If QUIET is set to YES this implies that the\n# messages are off.\n# The default value is: NO.\n\nQUIET                  = NO\n\n# The WARNINGS tag can be used to turn on/off the warning messages that are\n# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES\n# this implies that the warnings are on.\n#\n# Tip: Turn warnings on while writing the documentation.\n# The default value is: YES.\n\nWARNINGS               = YES\n\n# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate\n# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag\n# will automatically be disabled.\n# The default value is: YES.\n\nWARN_IF_UNDOCUMENTED   = YES\n\n# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for\n# potential errors in the documentation, such as not documenting some parameters\n# in a documented function, or documenting parameters that don't exist or using\n# markup commands wrongly.\n# The default value is: YES.\n\nWARN_IF_DOC_ERROR      = YES\n\n# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that\n# are documented, but have no documentation for their parameters or return\n# value. If set to NO, doxygen will only warn about wrong or incomplete\n# parameter documentation, but not about the absence of documentation. If\n# EXTRACT_ALL is set to YES then this flag will automatically be disabled.\n# The default value is: NO.\n\nWARN_NO_PARAMDOC       = NO\n\n# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when\n# a warning is encountered.\n# The default value is: NO.\n\nWARN_AS_ERROR          = NO\n\n# The WARN_FORMAT tag determines the format of the warning messages that doxygen\n# can produce. The string should contain the $file, $line, and $text tags, which\n# will be replaced by the file and line number from which the warning originated\n# and the warning text. Optionally the format may contain $version, which will\n# be replaced by the version of the file (if it could be obtained via\n# FILE_VERSION_FILTER)\n# The default value is: $file:$line: $text.\n\nWARN_FORMAT            = \"$file:$line: $text\"\n\n# The WARN_LOGFILE tag can be used to specify a file to which warning and error\n# messages should be written. If left blank the output is written to standard\n# error (stderr).\n\nWARN_LOGFILE           =\n\n#---------------------------------------------------------------------------\n# Configuration options related to the input files\n#---------------------------------------------------------------------------\n\n# The INPUT tag is used to specify the files and/or directories that contain\n# documented source files. You may enter file names like myfile.cpp or\n# directories like /usr/src/myproject. Separate the files or directories with\n# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING\n# Note: If this tag is empty the current directory is searched.\n\nINPUT                  = @DOXYGEN_INPUT_PATHS@\n\n# This tag can be used to specify the character encoding of the source files\n# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses\n# libiconv (or the iconv built into libc) for the transcoding. See the libiconv\n# documentation (see: https://www.gnu.org/software/libiconv/) for the list of\n# possible encodings.\n# The default value is: UTF-8.\n\nINPUT_ENCODING         = UTF-8\n\n# If the value of the INPUT tag contains directories, you can use the\n# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and\n# *.h) to filter out the source-files in the directories.\n#\n# Note that for custom extensions or not directly supported extensions you also\n# need to set EXTENSION_MAPPING for the extension otherwise the files are not\n# read by doxygen.\n#\n# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp,\n# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h,\n# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc,\n# *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C comment),\n# *.doc (to be provided as doxygen C comment), *.txt (to be provided as doxygen\n# C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd,\n# *.vhdl, *.ucf, *.qsf and *.ice.\n\nFILE_PATTERNS          = *.c \\\n                         *.cc \\\n                         *.cxx \\\n                         *.cpp \\\n                         *.c++ \\\n                         *.java \\\n                         *.ii \\\n                         *.ixx \\\n                         *.ipp \\\n                         *.i++ \\\n                         *.inl \\\n                         *.idl \\\n                         *.ddl \\\n                         *.odl \\\n                         *.h \\\n                         *.hh \\\n                         *.hxx \\\n                         *.hpp \\\n                         *.h++ \\\n                         *.cs \\\n                         *.d \\\n                         *.php \\\n                         *.php4 \\\n                         *.php5 \\\n                         *.phtml \\\n                         *.inc \\\n                         *.m \\\n                         *.markdown \\\n                         *.md \\\n                         *.mm \\\n                         *.dox \\\n                         *.doc \\\n                         *.txt \\\n                         *.py \\\n                         *.pyw \\\n                         *.f90 \\\n                         *.f95 \\\n                         *.f03 \\\n                         *.f08 \\\n                         *.f18 \\\n                         *.f \\\n                         *.for \\\n                         *.vhd \\\n                         *.vhdl \\\n                         *.ucf \\\n                         *.qsf \\\n                         *.ice\n\n# The RECURSIVE tag can be used to specify whether or not subdirectories should\n# be searched for input files as well.\n# The default value is: NO.\n\nRECURSIVE              = YES\n\n# The EXCLUDE tag can be used to specify files and/or directories that should be\n# excluded from the INPUT source files. This way you can easily exclude a\n# subdirectory from a directory tree whose root is specified with the INPUT tag.\n#\n# Note that relative paths are relative to the directory from which doxygen is\n# run.\n\nEXCLUDE                =\n\n# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or\n# directories that are symbolic links (a Unix file system feature) are excluded\n# from the input.\n# The default value is: NO.\n\nEXCLUDE_SYMLINKS       = NO\n\n# If the value of the INPUT tag contains directories, you can use the\n# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude\n# certain files from those directories.\n#\n# Note that the wildcards are matched against the file with absolute path, so to\n# exclude all test directories for example use the pattern */test/*\n\nEXCLUDE_PATTERNS       =\n\n# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names\n# (namespaces, classes, functions, etc.) that should be excluded from the\n# output. The symbol name can be a fully qualified name, a word, or if the\n# wildcard * is used, a substring. Examples: ANamespace, AClass,\n# AClass::ANamespace, ANamespace::*Test\n#\n# Note that the wildcards are matched against the file with absolute path, so to\n# exclude all test directories use the pattern */test/*\n\nEXCLUDE_SYMBOLS        =\n\n# The EXAMPLE_PATH tag can be used to specify one or more files or directories\n# that contain example code fragments that are included (see the \\include\n# command).\n\nEXAMPLE_PATH           =\n\n# If the value of the EXAMPLE_PATH tag contains directories, you can use the\n# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and\n# *.h) to filter out the source-files in the directories. If left blank all\n# files are included.\n\nEXAMPLE_PATTERNS       = *\n\n# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be\n# searched for input files to be used with the \\include or \\dontinclude commands\n# irrespective of the value of the RECURSIVE tag.\n# The default value is: NO.\n\nEXAMPLE_RECURSIVE      = NO\n\n# The IMAGE_PATH tag can be used to specify one or more files or directories\n# that contain images that are to be included in the documentation (see the\n# \\image command).\n\nIMAGE_PATH             =\n\n# The INPUT_FILTER tag can be used to specify a program that doxygen should\n# invoke to filter for each input file. Doxygen will invoke the filter program\n# by executing (via popen()) the command:\n#\n# <filter> <input-file>\n#\n# where <filter> is the value of the INPUT_FILTER tag, and <input-file> is the\n# name of an input file. Doxygen will then use the output that the filter\n# program writes to standard output. If FILTER_PATTERNS is specified, this tag\n# will be ignored.\n#\n# Note that the filter must not add or remove lines; it is applied before the\n# code is scanned, but not when the output code is generated. If lines are added\n# or removed, the anchors will not be placed correctly.\n#\n# Note that for custom extensions or not directly supported extensions you also\n# need to set EXTENSION_MAPPING for the extension otherwise the files are not\n# properly processed by doxygen.\n\nINPUT_FILTER           =\n\n# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern\n# basis. Doxygen will compare the file name with each pattern and apply the\n# filter if there is a match. The filters are a list of the form: pattern=filter\n# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how\n# filters are used. If the FILTER_PATTERNS tag is empty or if none of the\n# patterns match the file name, INPUT_FILTER is applied.\n#\n# Note that for custom extensions or not directly supported extensions you also\n# need to set EXTENSION_MAPPING for the extension otherwise the files are not\n# properly processed by doxygen.\n\nFILTER_PATTERNS        =\n\n# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using\n# INPUT_FILTER) will also be used to filter the input files that are used for\n# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES).\n# The default value is: NO.\n\nFILTER_SOURCE_FILES    = NO\n\n# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file\n# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and\n# it is also possible to disable source filtering for a specific pattern using\n# *.ext= (so without naming a filter).\n# This tag requires that the tag FILTER_SOURCE_FILES is set to YES.\n\nFILTER_SOURCE_PATTERNS =\n\n# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that\n# is part of the input, its contents will be placed on the main page\n# (index.html). This can be useful if you have a project on for instance GitHub\n# and want to reuse the introduction page also for the doxygen output.\n\nUSE_MDFILE_AS_MAINPAGE =\n\n#---------------------------------------------------------------------------\n# Configuration options related to source browsing\n#---------------------------------------------------------------------------\n\n# If the SOURCE_BROWSER tag is set to YES then a list of source files will be\n# generated. Documented entities will be cross-referenced with these sources.\n#\n# Note: To get rid of all source code in the generated output, make sure that\n# also VERBATIM_HEADERS is set to NO.\n# The default value is: NO.\n\nSOURCE_BROWSER         = NO\n\n# Setting the INLINE_SOURCES tag to YES will include the body of functions,\n# classes and enums directly into the documentation.\n# The default value is: NO.\n\nINLINE_SOURCES         = NO\n\n# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any\n# special comment blocks from generated source code fragments. Normal C, C++ and\n# Fortran comments will always remain visible.\n# The default value is: YES.\n\nSTRIP_CODE_COMMENTS    = YES\n\n# If the REFERENCED_BY_RELATION tag is set to YES then for each documented\n# entity all documented functions referencing it will be listed.\n# The default value is: NO.\n\nREFERENCED_BY_RELATION = NO\n\n# If the REFERENCES_RELATION tag is set to YES then for each documented function\n# all documented entities called/used by that function will be listed.\n# The default value is: NO.\n\nREFERENCES_RELATION    = NO\n\n# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set\n# to YES then the hyperlinks from functions in REFERENCES_RELATION and\n# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will\n# link to the documentation.\n# The default value is: YES.\n\nREFERENCES_LINK_SOURCE = YES\n\n# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the\n# source code will show a tooltip with additional information such as prototype,\n# brief description and links to the definition and documentation. Since this\n# will make the HTML file larger and loading of large files a bit slower, you\n# can opt to disable this feature.\n# The default value is: YES.\n# This tag requires that the tag SOURCE_BROWSER is set to YES.\n\nSOURCE_TOOLTIPS        = YES\n\n# If the USE_HTAGS tag is set to YES then the references to source code will\n# point to the HTML generated by the htags(1) tool instead of doxygen built-in\n# source browser. The htags tool is part of GNU's global source tagging system\n# (see https://www.gnu.org/software/global/global.html). You will need version\n# 4.8.6 or higher.\n#\n# To use it do the following:\n# - Install the latest version of global\n# - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file\n# - Make sure the INPUT points to the root of the source tree\n# - Run doxygen as normal\n#\n# Doxygen will invoke htags (and that will in turn invoke gtags), so these\n# tools must be available from the command line (i.e. in the search path).\n#\n# The result: instead of the source browser generated by doxygen, the links to\n# source code will now point to the output of htags.\n# The default value is: NO.\n# This tag requires that the tag SOURCE_BROWSER is set to YES.\n\nUSE_HTAGS              = NO\n\n# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a\n# verbatim copy of the header file for each class for which an include is\n# specified. Set to NO to disable this.\n# See also: Section \\class.\n# The default value is: YES.\n\nVERBATIM_HEADERS       = YES\n\n# If the CLANG_ASSISTED_PARSING tag is set to YES then doxygen will use the\n# clang parser (see: http://clang.llvm.org/) for more accurate parsing at the\n# cost of reduced performance. This can be particularly helpful with template\n# rich C++ code for which doxygen's built-in parser lacks the necessary type\n# information.\n# Note: The availability of this option depends on whether or not doxygen was\n# generated with the -Duse_libclang=ON option for CMake.\n# The default value is: NO.\n\nCLANG_ASSISTED_PARSING = NO\n\n# If clang assisted parsing is enabled you can provide the compiler with command\n# line options that you would normally use when invoking the compiler. Note that\n# the include paths will already be set by doxygen for the files and directories\n# specified with INPUT and INCLUDE_PATH.\n# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES.\n\nCLANG_OPTIONS          =\n\n# If clang assisted parsing is enabled you can provide the clang parser with the\n# path to the compilation database (see:\n# http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html) used when the files\n# were built. This is equivalent to specifying the \"-p\" option to a clang tool,\n# such as clang-check. These options will then be passed to the parser.\n# Note: The availability of this option depends on whether or not doxygen was\n# generated with the -Duse_libclang=ON option for CMake.\n\nCLANG_DATABASE_PATH    =\n\n#---------------------------------------------------------------------------\n# Configuration options related to the alphabetical class index\n#---------------------------------------------------------------------------\n\n# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all\n# compounds will be generated. Enable this if the project contains a lot of\n# classes, structs, unions or interfaces.\n# The default value is: YES.\n\nALPHABETICAL_INDEX     = YES\n\n# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in\n# which the alphabetical index list will be split.\n# Minimum value: 1, maximum value: 20, default value: 5.\n# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.\n\nCOLS_IN_ALPHA_INDEX    = 5\n\n# In case all classes in a project start with a common prefix, all classes will\n# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag\n# can be used to specify a prefix (or a list of prefixes) that should be ignored\n# while generating the index headers.\n# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.\n\nIGNORE_PREFIX          =\n\n#---------------------------------------------------------------------------\n# Configuration options related to the HTML output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output\n# The default value is: YES.\n\nGENERATE_HTML          = YES\n\n# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a\n# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of\n# it.\n# The default directory is: html.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_OUTPUT            = html\n\n# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each\n# generated HTML page (for example: .htm, .php, .asp).\n# The default value is: .html.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_FILE_EXTENSION    = .html\n\n# The HTML_HEADER tag can be used to specify a user-defined HTML header file for\n# each generated HTML page. If the tag is left blank doxygen will generate a\n# standard header.\n#\n# To get valid HTML the header file that includes any scripts and style sheets\n# that doxygen needs, which is dependent on the configuration options used (e.g.\n# the setting GENERATE_TREEVIEW). It is highly recommended to start with a\n# default header using\n# doxygen -w html new_header.html new_footer.html new_stylesheet.css\n# YourConfigFile\n# and then modify the file new_header.html. See also section \"Doxygen usage\"\n# for information on how to generate the default header that doxygen normally\n# uses.\n# Note: The header is subject to change so you typically have to regenerate the\n# default header when upgrading to a newer version of doxygen. For a description\n# of the possible markers and block names see the documentation.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_HEADER            =\n\n# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each\n# generated HTML page. If the tag is left blank doxygen will generate a standard\n# footer. See HTML_HEADER for more information on how to generate a default\n# footer and what special commands can be used inside the footer. See also\n# section \"Doxygen usage\" for information on how to generate the default footer\n# that doxygen normally uses.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_FOOTER            =\n\n# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style\n# sheet that is used by each HTML page. It can be used to fine-tune the look of\n# the HTML output. If left blank doxygen will generate a default style sheet.\n# See also section \"Doxygen usage\" for information on how to generate the style\n# sheet that doxygen normally uses.\n# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as\n# it is more robust and this tag (HTML_STYLESHEET) will in the future become\n# obsolete.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_STYLESHEET        =\n\n# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined\n# cascading style sheets that are included after the standard style sheets\n# created by doxygen. Using this option one can overrule certain style aspects.\n# This is preferred over using HTML_STYLESHEET since it does not replace the\n# standard style sheet and is therefore more robust against future updates.\n# Doxygen will copy the style sheet files to the output directory.\n# Note: The order of the extra style sheet files is of importance (e.g. the last\n# style sheet in the list overrules the setting of the previous ones in the\n# list). For an example see the documentation.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_EXTRA_STYLESHEET  =\n\n# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or\n# other source files which should be copied to the HTML output directory. Note\n# that these files will be copied to the base HTML output directory. Use the\n# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these\n# files. In the HTML_STYLESHEET file, use the file name only. Also note that the\n# files will be copied as-is; there are no commands or markers available.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_EXTRA_FILES       =\n\n# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen\n# will adjust the colors in the style sheet and background images according to\n# this color. Hue is specified as an angle on a colorwheel, see\n# https://en.wikipedia.org/wiki/Hue for more information. For instance the value\n# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300\n# purple, and 360 is red again.\n# Minimum value: 0, maximum value: 359, default value: 220.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_COLORSTYLE_HUE    = 220\n\n# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors\n# in the HTML output. For a value of 0 the output will use grayscales only. A\n# value of 255 will produce the most vivid colors.\n# Minimum value: 0, maximum value: 255, default value: 100.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_COLORSTYLE_SAT    = 100\n\n# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the\n# luminance component of the colors in the HTML output. Values below 100\n# gradually make the output lighter, whereas values above 100 make the output\n# darker. The value divided by 100 is the actual gamma applied, so 80 represents\n# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not\n# change the gamma.\n# Minimum value: 40, maximum value: 240, default value: 80.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_COLORSTYLE_GAMMA  = 80\n\n# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML\n# page will contain the date and time when the page was generated. Setting this\n# to YES can help to show when doxygen was last run and thus if the\n# documentation is up to date.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_TIMESTAMP         = NO\n\n# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML\n# documentation will contain a main index with vertical navigation menus that\n# are dynamically created via JavaScript. If disabled, the navigation index will\n# consists of multiple levels of tabs that are statically embedded in every HTML\n# page. Disable this option to support browsers that do not have JavaScript,\n# like the Qt help browser.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_DYNAMIC_MENUS     = YES\n\n# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML\n# documentation will contain sections that can be hidden and shown after the\n# page has loaded.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_DYNAMIC_SECTIONS  = NO\n\n# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries\n# shown in the various tree structured indices initially; the user can expand\n# and collapse entries dynamically later on. Doxygen will expand the tree to\n# such a level that at most the specified number of entries are visible (unless\n# a fully collapsed tree already exceeds this amount). So setting the number of\n# entries 1 will produce a full collapsed tree by default. 0 is a special value\n# representing an infinite number of entries and will result in a full expanded\n# tree by default.\n# Minimum value: 0, maximum value: 9999, default value: 100.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_INDEX_NUM_ENTRIES = 100\n\n# If the GENERATE_DOCSET tag is set to YES, additional index files will be\n# generated that can be used as input for Apple's Xcode 3 integrated development\n# environment (see: https://developer.apple.com/xcode/), introduced with OSX\n# 10.5 (Leopard). To create a documentation set, doxygen will generate a\n# Makefile in the HTML output directory. Running make will produce the docset in\n# that directory and running make install will install the docset in\n# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at\n# startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy\n# genXcode/_index.html for more information.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nGENERATE_DOCSET        = NO\n\n# This tag determines the name of the docset feed. A documentation feed provides\n# an umbrella under which multiple documentation sets from a single provider\n# (such as a company or product suite) can be grouped.\n# The default value is: Doxygen generated docs.\n# This tag requires that the tag GENERATE_DOCSET is set to YES.\n\nDOCSET_FEEDNAME        = \"Doxygen generated docs\"\n\n# This tag specifies a string that should uniquely identify the documentation\n# set bundle. This should be a reverse domain-name style string, e.g.\n# com.mycompany.MyDocSet. Doxygen will append .docset to the name.\n# The default value is: org.doxygen.Project.\n# This tag requires that the tag GENERATE_DOCSET is set to YES.\n\nDOCSET_BUNDLE_ID       = org.doxygen.Project\n\n# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify\n# the documentation publisher. This should be a reverse domain-name style\n# string, e.g. com.mycompany.MyDocSet.documentation.\n# The default value is: org.doxygen.Publisher.\n# This tag requires that the tag GENERATE_DOCSET is set to YES.\n\nDOCSET_PUBLISHER_ID    = org.doxygen.Publisher\n\n# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher.\n# The default value is: Publisher.\n# This tag requires that the tag GENERATE_DOCSET is set to YES.\n\nDOCSET_PUBLISHER_NAME  = Publisher\n\n# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three\n# additional HTML index files: index.hhp, index.hhc, and index.hhk. The\n# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop\n# (see: https://www.microsoft.com/en-us/download/details.aspx?id=21138) on\n# Windows.\n#\n# The HTML Help Workshop contains a compiler that can convert all HTML output\n# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML\n# files are now used as the Windows 98 help format, and will replace the old\n# Windows help format (.hlp) on all Windows platforms in the future. Compressed\n# HTML files also contain an index, a table of contents, and you can search for\n# words in the documentation. The HTML workshop also contains a viewer for\n# compressed HTML files.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nGENERATE_HTMLHELP      = NO\n\n# The CHM_FILE tag can be used to specify the file name of the resulting .chm\n# file. You can add a path in front of the file if the result should not be\n# written to the html output directory.\n# This tag requires that the tag GENERATE_HTMLHELP is set to YES.\n\nCHM_FILE               =\n\n# The HHC_LOCATION tag can be used to specify the location (absolute path\n# including file name) of the HTML help compiler (hhc.exe). If non-empty,\n# doxygen will try to run the HTML help compiler on the generated index.hhp.\n# The file has to be specified with full path.\n# This tag requires that the tag GENERATE_HTMLHELP is set to YES.\n\nHHC_LOCATION           =\n\n# The GENERATE_CHI flag controls if a separate .chi index file is generated\n# (YES) or that it should be included in the master .chm file (NO).\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTMLHELP is set to YES.\n\nGENERATE_CHI           = NO\n\n# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc)\n# and project file content.\n# This tag requires that the tag GENERATE_HTMLHELP is set to YES.\n\nCHM_INDEX_ENCODING     =\n\n# The BINARY_TOC flag controls whether a binary table of contents is generated\n# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it\n# enables the Previous and Next buttons.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTMLHELP is set to YES.\n\nBINARY_TOC             = NO\n\n# The TOC_EXPAND flag can be set to YES to add extra items for group members to\n# the table of contents of the HTML help documentation and to the tree view.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTMLHELP is set to YES.\n\nTOC_EXPAND             = NO\n\n# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and\n# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that\n# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help\n# (.qch) of the generated HTML documentation.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nGENERATE_QHP           = NO\n\n# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify\n# the file name of the resulting .qch file. The path specified is relative to\n# the HTML output folder.\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQCH_FILE               =\n\n# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help\n# Project output. For more information please see Qt Help Project / Namespace\n# (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace).\n# The default value is: org.doxygen.Project.\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQHP_NAMESPACE          = org.doxygen.Project\n\n# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt\n# Help Project output. For more information please see Qt Help Project / Virtual\n# Folders (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-\n# folders).\n# The default value is: doc.\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQHP_VIRTUAL_FOLDER     = doc\n\n# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom\n# filter to add. For more information please see Qt Help Project / Custom\n# Filters (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-\n# filters).\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQHP_CUST_FILTER_NAME   =\n\n# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the\n# custom filter to add. For more information please see Qt Help Project / Custom\n# Filters (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-\n# filters).\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQHP_CUST_FILTER_ATTRS  =\n\n# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this\n# project's filter section matches. Qt Help Project / Filter Attributes (see:\n# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes).\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQHP_SECT_FILTER_ATTRS  =\n\n# The QHG_LOCATION tag can be used to specify the location of Qt's\n# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the\n# generated .qhp file.\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQHG_LOCATION           =\n\n# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be\n# generated, together with the HTML files, they form an Eclipse help plugin. To\n# install this plugin and make it available under the help contents menu in\n# Eclipse, the contents of the directory containing the HTML and XML files needs\n# to be copied into the plugins directory of eclipse. The name of the directory\n# within the plugins directory should be the same as the ECLIPSE_DOC_ID value.\n# After copying Eclipse needs to be restarted before the help appears.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nGENERATE_ECLIPSEHELP   = NO\n\n# A unique identifier for the Eclipse help plugin. When installing the plugin\n# the directory name containing the HTML and XML files should also have this\n# name. Each documentation set should have its own identifier.\n# The default value is: org.doxygen.Project.\n# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES.\n\nECLIPSE_DOC_ID         = org.doxygen.Project\n\n# If you want full control over the layout of the generated HTML pages it might\n# be necessary to disable the index and replace it with your own. The\n# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top\n# of each HTML page. A value of NO enables the index and the value YES disables\n# it. Since the tabs in the index contain the same information as the navigation\n# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nDISABLE_INDEX          = NO\n\n# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index\n# structure should be generated to display hierarchical information. If the tag\n# value is set to YES, a side panel will be generated containing a tree-like\n# index structure (just like the one that is generated for HTML Help). For this\n# to work a browser that supports JavaScript, DHTML, CSS and frames is required\n# (i.e. any modern browser). Windows users are probably better off using the\n# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can\n# further fine-tune the look of the index. As an example, the default style\n# sheet generated by doxygen has an example that shows how to put an image at\n# the root of the tree instead of the PROJECT_NAME. Since the tree basically has\n# the same information as the tab index, you could consider setting\n# DISABLE_INDEX to YES when enabling this option.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nGENERATE_TREEVIEW      = YES\n\n# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that\n# doxygen will group on one line in the generated HTML documentation.\n#\n# Note that a value of 0 will completely suppress the enum values from appearing\n# in the overview section.\n# Minimum value: 0, maximum value: 20, default value: 4.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nENUM_VALUES_PER_LINE   = 4\n\n# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used\n# to set the initial width (in pixels) of the frame in which the tree is shown.\n# Minimum value: 0, maximum value: 1500, default value: 250.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nTREEVIEW_WIDTH         = 250\n\n# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to\n# external symbols imported via tag files in a separate window.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nEXT_LINKS_IN_WINDOW    = NO\n\n# If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg\n# tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see\n# https://inkscape.org) to generate formulas as SVG images instead of PNGs for\n# the HTML output. These images will generally look nicer at scaled resolutions.\n# Possible values are: png The default and svg Looks nicer but requires the\n# pdf2svg tool.\n# The default value is: png.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_FORMULA_FORMAT    = png\n\n# Use this tag to change the font size of LaTeX formulas included as images in\n# the HTML documentation. When you change the font size after a successful\n# doxygen run you need to manually remove any form_*.png images from the HTML\n# output directory to force them to be regenerated.\n# Minimum value: 8, maximum value: 50, default value: 10.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nFORMULA_FONTSIZE       = 10\n\n# Use the FORMULA_TRANSPARENT tag to determine whether or not the images\n# generated for formulas are transparent PNGs. Transparent PNGs are not\n# supported properly for IE 6.0, but are supported on all modern browsers.\n#\n# Note that when changing this option you need to delete any form_*.png files in\n# the HTML output directory before the changes have effect.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nFORMULA_TRANSPARENT    = YES\n\n# The FORMULA_MACROFILE can contain LaTeX \\newcommand and \\renewcommand commands\n# to create new LaTeX commands to be used in formulas as building blocks. See\n# the section \"Including formulas\" for details.\n\nFORMULA_MACROFILE      =\n\n# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see\n# https://www.mathjax.org) which uses client side JavaScript for the rendering\n# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX\n# installed or if you want to formulas look prettier in the HTML output. When\n# enabled you may also need to install MathJax separately and configure the path\n# to it using the MATHJAX_RELPATH option.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nUSE_MATHJAX            = NO\n\n# When MathJax is enabled you can set the default output format to be used for\n# the MathJax output. See the MathJax site (see:\n# http://docs.mathjax.org/en/latest/output.html) for more details.\n# Possible values are: HTML-CSS (which is slower, but has the best\n# compatibility), NativeMML (i.e. MathML) and SVG.\n# The default value is: HTML-CSS.\n# This tag requires that the tag USE_MATHJAX is set to YES.\n\nMATHJAX_FORMAT         = HTML-CSS\n\n# When MathJax is enabled you need to specify the location relative to the HTML\n# output directory using the MATHJAX_RELPATH option. The destination directory\n# should contain the MathJax.js script. For instance, if the mathjax directory\n# is located at the same level as the HTML output directory, then\n# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax\n# Content Delivery Network so you can quickly see the result without installing\n# MathJax. However, it is strongly recommended to install a local copy of\n# MathJax from https://www.mathjax.org before deployment.\n# The default value is: https://cdn.jsdelivr.net/npm/mathjax@2.\n# This tag requires that the tag USE_MATHJAX is set to YES.\n\nMATHJAX_RELPATH        = https://cdn.jsdelivr.net/npm/mathjax@2\n\n# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax\n# extension names that should be enabled during MathJax rendering. For example\n# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols\n# This tag requires that the tag USE_MATHJAX is set to YES.\n\nMATHJAX_EXTENSIONS     =\n\n# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces\n# of code that will be used on startup of the MathJax code. See the MathJax site\n# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an\n# example see the documentation.\n# This tag requires that the tag USE_MATHJAX is set to YES.\n\nMATHJAX_CODEFILE       =\n\n# When the SEARCHENGINE tag is enabled doxygen will generate a search box for\n# the HTML output. The underlying search engine uses javascript and DHTML and\n# should work on any modern browser. Note that when using HTML help\n# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET)\n# there is already a search function so this one should typically be disabled.\n# For large projects the javascript based search engine can be slow, then\n# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to\n# search using the keyboard; to jump to the search box use <access key> + S\n# (what the <access key> is depends on the OS and browser, but it is typically\n# <CTRL>, <ALT>/<option>, or both). Inside the search box use the <cursor down\n# key> to jump into the search results window, the results can be navigated\n# using the <cursor keys>. Press <Enter> to select an item or <escape> to cancel\n# the search. The filter options can be selected when the cursor is inside the\n# search box by pressing <Shift>+<cursor down>. Also here use the <cursor keys>\n# to select a filter and <Enter> or <escape> to activate or cancel the filter\n# option.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nSEARCHENGINE           = YES\n\n# When the SERVER_BASED_SEARCH tag is enabled the search engine will be\n# implemented using a web server instead of a web client using JavaScript. There\n# are two flavors of web server based searching depending on the EXTERNAL_SEARCH\n# setting. When disabled, doxygen will generate a PHP script for searching and\n# an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing\n# and searching needs to be provided by external tools. See the section\n# \"External Indexing and Searching\" for details.\n# The default value is: NO.\n# This tag requires that the tag SEARCHENGINE is set to YES.\n\nSERVER_BASED_SEARCH    = NO\n\n# When EXTERNAL_SEARCH tag is enabled doxygen will no longer generate the PHP\n# script for searching. Instead the search results are written to an XML file\n# which needs to be processed by an external indexer. Doxygen will invoke an\n# external search engine pointed to by the SEARCHENGINE_URL option to obtain the\n# search results.\n#\n# Doxygen ships with an example indexer (doxyindexer) and search engine\n# (doxysearch.cgi) which are based on the open source search engine library\n# Xapian (see: https://xapian.org/).\n#\n# See the section \"External Indexing and Searching\" for details.\n# The default value is: NO.\n# This tag requires that the tag SEARCHENGINE is set to YES.\n\nEXTERNAL_SEARCH        = NO\n\n# The SEARCHENGINE_URL should point to a search engine hosted by a web server\n# which will return the search results when EXTERNAL_SEARCH is enabled.\n#\n# Doxygen ships with an example indexer (doxyindexer) and search engine\n# (doxysearch.cgi) which are based on the open source search engine library\n# Xapian (see: https://xapian.org/). See the section \"External Indexing and\n# Searching\" for details.\n# This tag requires that the tag SEARCHENGINE is set to YES.\n\nSEARCHENGINE_URL       =\n\n# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed\n# search data is written to a file for indexing by an external tool. With the\n# SEARCHDATA_FILE tag the name of this file can be specified.\n# The default file is: searchdata.xml.\n# This tag requires that the tag SEARCHENGINE is set to YES.\n\nSEARCHDATA_FILE        = searchdata.xml\n\n# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the\n# EXTERNAL_SEARCH_ID tag can be used as an identifier for the project. This is\n# useful in combination with EXTRA_SEARCH_MAPPINGS to search through multiple\n# projects and redirect the results back to the right project.\n# This tag requires that the tag SEARCHENGINE is set to YES.\n\nEXTERNAL_SEARCH_ID     =\n\n# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen\n# projects other than the one defined by this configuration file, but that are\n# all added to the same external search index. Each project needs to have a\n# unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id of\n# to a relative location where the documentation can be found. The format is:\n# EXTRA_SEARCH_MAPPINGS = tagname1=loc1 tagname2=loc2 ...\n# This tag requires that the tag SEARCHENGINE is set to YES.\n\nEXTRA_SEARCH_MAPPINGS  =\n\n#---------------------------------------------------------------------------\n# Configuration options related to the LaTeX output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output.\n# The default value is: YES.\n\nGENERATE_LATEX         = NO\n\n# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If a\n# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of\n# it.\n# The default directory is: latex.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_OUTPUT           = latex\n\n# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be\n# invoked.\n#\n# Note that when not enabling USE_PDFLATEX the default is latex when enabling\n# USE_PDFLATEX the default is pdflatex and when in the later case latex is\n# chosen this is overwritten by pdflatex. For specific output languages the\n# default can have been set differently, this depends on the implementation of\n# the output language.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_CMD_NAME         =\n\n# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate\n# index for LaTeX.\n# Note: This tag is used in the Makefile / make.bat.\n# See also: LATEX_MAKEINDEX_CMD for the part in the generated output file\n# (.tex).\n# The default file is: makeindex.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nMAKEINDEX_CMD_NAME     = makeindex\n\n# The LATEX_MAKEINDEX_CMD tag can be used to specify the command name to\n# generate index for LaTeX. In case there is no backslash (\\) as first character\n# it will be automatically added in the LaTeX code.\n# Note: This tag is used in the generated output file (.tex).\n# See also: MAKEINDEX_CMD_NAME for the part in the Makefile / make.bat.\n# The default value is: makeindex.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_MAKEINDEX_CMD    = makeindex\n\n# If the COMPACT_LATEX tag is set to YES, doxygen generates more compact LaTeX\n# documents. This may be useful for small projects and may help to save some\n# trees in general.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nCOMPACT_LATEX          = NO\n\n# The PAPER_TYPE tag can be used to set the paper type that is used by the\n# printer.\n# Possible values are: a4 (210 x 297 mm), letter (8.5 x 11 inches), legal (8.5 x\n# 14 inches) and executive (7.25 x 10.5 inches).\n# The default value is: a4.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nPAPER_TYPE             = a4\n\n# The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names\n# that should be included in the LaTeX output. The package can be specified just\n# by its name or with the correct syntax as to be used with the LaTeX\n# \\usepackage command. To get the times font for instance you can specify :\n# EXTRA_PACKAGES=times or EXTRA_PACKAGES={times}\n# To use the option intlimits with the amsmath package you can specify:\n# EXTRA_PACKAGES=[intlimits]{amsmath}\n# If left blank no extra packages will be included.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nEXTRA_PACKAGES         =\n\n# The LATEX_HEADER tag can be used to specify a personal LaTeX header for the\n# generated LaTeX document. The header should contain everything until the first\n# chapter. If it is left blank doxygen will generate a standard header. See\n# section \"Doxygen usage\" for information on how to let doxygen write the\n# default header to a separate file.\n#\n# Note: Only use a user-defined header if you know what you are doing! The\n# following commands have a special meaning inside the header: $title,\n# $datetime, $date, $doxygenversion, $projectname, $projectnumber,\n# $projectbrief, $projectlogo. Doxygen will replace $title with the empty\n# string, for the replacement values of the other commands the user is referred\n# to HTML_HEADER.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_HEADER           =\n\n# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for the\n# generated LaTeX document. The footer should contain everything after the last\n# chapter. If it is left blank doxygen will generate a standard footer. See\n# LATEX_HEADER for more information on how to generate a default footer and what\n# special commands can be used inside the footer.\n#\n# Note: Only use a user-defined footer if you know what you are doing!\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_FOOTER           =\n\n# The LATEX_EXTRA_STYLESHEET tag can be used to specify additional user-defined\n# LaTeX style sheets that are included after the standard style sheets created\n# by doxygen. Using this option one can overrule certain style aspects. Doxygen\n# will copy the style sheet files to the output directory.\n# Note: The order of the extra style sheet files is of importance (e.g. the last\n# style sheet in the list overrules the setting of the previous ones in the\n# list).\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_EXTRA_STYLESHEET =\n\n# The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or\n# other source files which should be copied to the LATEX_OUTPUT output\n# directory. Note that the files will be copied as-is; there are no commands or\n# markers available.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_EXTRA_FILES      =\n\n# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated is\n# prepared for conversion to PDF (using ps2pdf or pdflatex). The PDF file will\n# contain links (just like the HTML output) instead of page references. This\n# makes the output suitable for online browsing using a PDF viewer.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nPDF_HYPERLINKS         = YES\n\n# If the USE_PDFLATEX tag is set to YES, doxygen will use pdflatex to generate\n# the PDF file directly from the LaTeX files. Set this option to YES, to get a\n# higher quality PDF documentation.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nUSE_PDFLATEX           = YES\n\n# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode\n# command to the generated LaTeX files. This will instruct LaTeX to keep running\n# if errors occur, instead of asking the user for help. This option is also used\n# when generating formulas in HTML.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_BATCHMODE        = NO\n\n# If the LATEX_HIDE_INDICES tag is set to YES then doxygen will not include the\n# index chapters (such as File Index, Compound Index, etc.) in the output.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_HIDE_INDICES     = NO\n\n# If the LATEX_SOURCE_CODE tag is set to YES then doxygen will include source\n# code with syntax highlighting in the LaTeX output.\n#\n# Note that which sources are shown also depends on other settings such as\n# SOURCE_BROWSER.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_SOURCE_CODE      = NO\n\n# The LATEX_BIB_STYLE tag can be used to specify the style to use for the\n# bibliography, e.g. plainnat, or ieeetr. See\n# https://en.wikipedia.org/wiki/BibTeX and \\cite for more info.\n# The default value is: plain.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_BIB_STYLE        = plain\n\n# If the LATEX_TIMESTAMP tag is set to YES then the footer of each generated\n# page will contain the date and time when the page was generated. Setting this\n# to NO can help when comparing the output of multiple runs.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_TIMESTAMP        = NO\n\n# The LATEX_EMOJI_DIRECTORY tag is used to specify the (relative or absolute)\n# path from which the emoji images will be read. If a relative path is entered,\n# it will be relative to the LATEX_OUTPUT directory. If left blank the\n# LATEX_OUTPUT directory will be used.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_EMOJI_DIRECTORY  =\n\n#---------------------------------------------------------------------------\n# Configuration options related to the RTF output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_RTF tag is set to YES, doxygen will generate RTF output. The\n# RTF output is optimized for Word 97 and may not look too pretty with other RTF\n# readers/editors.\n# The default value is: NO.\n\nGENERATE_RTF           = NO\n\n# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. If a\n# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of\n# it.\n# The default directory is: rtf.\n# This tag requires that the tag GENERATE_RTF is set to YES.\n\nRTF_OUTPUT             = rtf\n\n# If the COMPACT_RTF tag is set to YES, doxygen generates more compact RTF\n# documents. This may be useful for small projects and may help to save some\n# trees in general.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_RTF is set to YES.\n\nCOMPACT_RTF            = NO\n\n# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated will\n# contain hyperlink fields. The RTF file will contain links (just like the HTML\n# output) instead of page references. This makes the output suitable for online\n# browsing using Word or some other Word compatible readers that support those\n# fields.\n#\n# Note: WordPad (write) and others do not support links.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_RTF is set to YES.\n\nRTF_HYPERLINKS         = NO\n\n# Load stylesheet definitions from file. Syntax is similar to doxygen's\n# configuration file, i.e. a series of assignments. You only have to provide\n# replacements, missing definitions are set to their default value.\n#\n# See also section \"Doxygen usage\" for information on how to generate the\n# default style sheet that doxygen normally uses.\n# This tag requires that the tag GENERATE_RTF is set to YES.\n\nRTF_STYLESHEET_FILE    =\n\n# Set optional variables used in the generation of an RTF document. Syntax is\n# similar to doxygen's configuration file. A template extensions file can be\n# generated using doxygen -e rtf extensionFile.\n# This tag requires that the tag GENERATE_RTF is set to YES.\n\nRTF_EXTENSIONS_FILE    =\n\n# If the RTF_SOURCE_CODE tag is set to YES then doxygen will include source code\n# with syntax highlighting in the RTF output.\n#\n# Note that which sources are shown also depends on other settings such as\n# SOURCE_BROWSER.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_RTF is set to YES.\n\nRTF_SOURCE_CODE        = NO\n\n#---------------------------------------------------------------------------\n# Configuration options related to the man page output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_MAN tag is set to YES, doxygen will generate man pages for\n# classes and files.\n# The default value is: NO.\n\nGENERATE_MAN           = NO\n\n# The MAN_OUTPUT tag is used to specify where the man pages will be put. If a\n# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of\n# it. A directory man3 will be created inside the directory specified by\n# MAN_OUTPUT.\n# The default directory is: man.\n# This tag requires that the tag GENERATE_MAN is set to YES.\n\nMAN_OUTPUT             = man\n\n# The MAN_EXTENSION tag determines the extension that is added to the generated\n# man pages. In case the manual section does not start with a number, the number\n# 3 is prepended. The dot (.) at the beginning of the MAN_EXTENSION tag is\n# optional.\n# The default value is: .3.\n# This tag requires that the tag GENERATE_MAN is set to YES.\n\nMAN_EXTENSION          = .3\n\n# The MAN_SUBDIR tag determines the name of the directory created within\n# MAN_OUTPUT in which the man pages are placed. If defaults to man followed by\n# MAN_EXTENSION with the initial . removed.\n# This tag requires that the tag GENERATE_MAN is set to YES.\n\nMAN_SUBDIR             =\n\n# If the MAN_LINKS tag is set to YES and doxygen generates man output, then it\n# will generate one additional man file for each entity documented in the real\n# man page(s). These additional files only source the real man page, but without\n# them the man command would be unable to find the correct page.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_MAN is set to YES.\n\nMAN_LINKS              = NO\n\n#---------------------------------------------------------------------------\n# Configuration options related to the XML output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_XML tag is set to YES, doxygen will generate an XML file that\n# captures the structure of the code including all documentation.\n# The default value is: NO.\n\nGENERATE_XML           = NO\n\n# The XML_OUTPUT tag is used to specify where the XML pages will be put. If a\n# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of\n# it.\n# The default directory is: xml.\n# This tag requires that the tag GENERATE_XML is set to YES.\n\nXML_OUTPUT             = xml\n\n# If the XML_PROGRAMLISTING tag is set to YES, doxygen will dump the program\n# listings (including syntax highlighting and cross-referencing information) to\n# the XML output. Note that enabling this will significantly increase the size\n# of the XML output.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_XML is set to YES.\n\nXML_PROGRAMLISTING     = YES\n\n# If the XML_NS_MEMB_FILE_SCOPE tag is set to YES, doxygen will include\n# namespace members in file scope as well, matching the HTML output.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_XML is set to YES.\n\nXML_NS_MEMB_FILE_SCOPE = NO\n\n#---------------------------------------------------------------------------\n# Configuration options related to the DOCBOOK output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_DOCBOOK tag is set to YES, doxygen will generate Docbook files\n# that can be used to generate PDF.\n# The default value is: NO.\n\nGENERATE_DOCBOOK       = NO\n\n# The DOCBOOK_OUTPUT tag is used to specify where the Docbook pages will be put.\n# If a relative path is entered the value of OUTPUT_DIRECTORY will be put in\n# front of it.\n# The default directory is: docbook.\n# This tag requires that the tag GENERATE_DOCBOOK is set to YES.\n\nDOCBOOK_OUTPUT         = docbook\n\n# If the DOCBOOK_PROGRAMLISTING tag is set to YES, doxygen will include the\n# program listings (including syntax highlighting and cross-referencing\n# information) to the DOCBOOK output. Note that enabling this will significantly\n# increase the size of the DOCBOOK output.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_DOCBOOK is set to YES.\n\nDOCBOOK_PROGRAMLISTING = NO\n\n#---------------------------------------------------------------------------\n# Configuration options for the AutoGen Definitions output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an\n# AutoGen Definitions (see http://autogen.sourceforge.net/) file that captures\n# the structure of the code including all documentation. Note that this feature\n# is still experimental and incomplete at the moment.\n# The default value is: NO.\n\nGENERATE_AUTOGEN_DEF   = NO\n\n#---------------------------------------------------------------------------\n# Configuration options related to the Perl module output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_PERLMOD tag is set to YES, doxygen will generate a Perl module\n# file that captures the structure of the code including all documentation.\n#\n# Note that this feature is still experimental and incomplete at the moment.\n# The default value is: NO.\n\nGENERATE_PERLMOD       = NO\n\n# If the PERLMOD_LATEX tag is set to YES, doxygen will generate the necessary\n# Makefile rules, Perl scripts and LaTeX code to be able to generate PDF and DVI\n# output from the Perl module output.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_PERLMOD is set to YES.\n\nPERLMOD_LATEX          = NO\n\n# If the PERLMOD_PRETTY tag is set to YES, the Perl module output will be nicely\n# formatted so it can be parsed by a human reader. This is useful if you want to\n# understand what is going on. On the other hand, if this tag is set to NO, the\n# size of the Perl module output will be much smaller and Perl will parse it\n# just the same.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_PERLMOD is set to YES.\n\nPERLMOD_PRETTY         = YES\n\n# The names of the make variables in the generated doxyrules.make file are\n# prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. This is useful\n# so different doxyrules.make files included by the same Makefile don't\n# overwrite each other's variables.\n# This tag requires that the tag GENERATE_PERLMOD is set to YES.\n\nPERLMOD_MAKEVAR_PREFIX =\n\n#---------------------------------------------------------------------------\n# Configuration options related to the preprocessor\n#---------------------------------------------------------------------------\n\n# If the ENABLE_PREPROCESSING tag is set to YES, doxygen will evaluate all\n# C-preprocessor directives found in the sources and include files.\n# The default value is: YES.\n\nENABLE_PREPROCESSING   = YES\n\n# If the MACRO_EXPANSION tag is set to YES, doxygen will expand all macro names\n# in the source code. If set to NO, only conditional compilation will be\n# performed. Macro expansion can be done in a controlled way by setting\n# EXPAND_ONLY_PREDEF to YES.\n# The default value is: NO.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nMACRO_EXPANSION        = YES\n\n# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then\n# the macro expansion is limited to the macros specified with the PREDEFINED and\n# EXPAND_AS_DEFINED tags.\n# The default value is: NO.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nEXPAND_ONLY_PREDEF     = YES\n\n# If the SEARCH_INCLUDES tag is set to YES, the include files in the\n# INCLUDE_PATH will be searched if a #include is found.\n# The default value is: YES.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nSEARCH_INCLUDES        = YES\n\n# The INCLUDE_PATH tag can be used to specify one or more directories that\n# contain include files that are not input files but should be processed by the\n# preprocessor.\n# This tag requires that the tag SEARCH_INCLUDES is set to YES.\n\nINCLUDE_PATH           = @AVS_COMMONS_INCLUDE_DIRS@\n\n# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard\n# patterns (like *.h and *.hpp) to filter out the header-files in the\n# directories. If left blank, the patterns specified with FILE_PATTERNS will be\n# used.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nINCLUDE_FILE_PATTERNS  =\n\n# The PREDEFINED tag can be used to specify one or more macro names that are\n# defined before the preprocessor is started (similar to the -D option of e.g.\n# gcc). The argument of the tag is a list of macros of the form: name or\n# name=definition (no spaces). If the definition and the \"=\" are omitted, \"=1\"\n# is assumed. To prevent a macro definition from being undefined via #undef or\n# recursively expanded use the := operator instead of the = operator.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nPREDEFINED             = @DOXYFILE_PREDEFINED_MACROS@ AVS_LIST(x)=AVS_LIST[x]\n\n# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this\n# tag can be used to specify a list of macro names that should be expanded. The\n# macro definition that is found in the sources will be used. Use the PREDEFINED\n# tag if you want to use a different macro definition that overrules the\n# definition found in the source code.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nEXPAND_AS_DEFINED      =\n\n# If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will\n# remove all references to function-like macros that are alone on a line, have\n# an all uppercase name, and do not end with a semicolon. Such function macros\n# are typically used for boiler-plate code, and will confuse the parser if not\n# removed.\n# The default value is: YES.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nSKIP_FUNCTION_MACROS   = YES\n\n#---------------------------------------------------------------------------\n# Configuration options related to external references\n#---------------------------------------------------------------------------\n\n# The TAGFILES tag can be used to specify one or more tag files. For each tag\n# file the location of the external documentation should be added. The format of\n# a tag file without this location is as follows:\n# TAGFILES = file1 file2 ...\n# Adding location for the tag files is done as follows:\n# TAGFILES = file1=loc1 \"file2 = loc2\" ...\n# where loc1 and loc2 can be relative or absolute paths or URLs. See the\n# section \"Linking to external documentation\" for more information about the use\n# of tag files.\n# Note: Each tag file must have a unique name (where the name does NOT include\n# the path). If a tag file is not located in the directory in which doxygen is\n# run, you must also specify the path to the tagfile here.\n\nTAGFILES               =\n\n# When a file name is specified after GENERATE_TAGFILE, doxygen will create a\n# tag file that is based on the input files it reads. See section \"Linking to\n# external documentation\" for more information about the usage of tag files.\n\nGENERATE_TAGFILE       =\n\n# If the ALLEXTERNALS tag is set to YES, all external class will be listed in\n# the class index. If set to NO, only the inherited external classes will be\n# listed.\n# The default value is: NO.\n\nALLEXTERNALS           = NO\n\n# If the EXTERNAL_GROUPS tag is set to YES, all external groups will be listed\n# in the modules index. If set to NO, only the current project's groups will be\n# listed.\n# The default value is: YES.\n\nEXTERNAL_GROUPS        = YES\n\n# If the EXTERNAL_PAGES tag is set to YES, all external pages will be listed in\n# the related pages index. If set to NO, only the current project's pages will\n# be listed.\n# The default value is: YES.\n\nEXTERNAL_PAGES         = YES\n\n#---------------------------------------------------------------------------\n# Configuration options related to the dot tool\n#---------------------------------------------------------------------------\n\n# If the CLASS_DIAGRAMS tag is set to YES, doxygen will generate a class diagram\n# (in HTML and LaTeX) for classes with base or super classes. Setting the tag to\n# NO turns the diagrams off. Note that this option also works with HAVE_DOT\n# disabled, but it is recommended to install and use dot, since it yields more\n# powerful graphs.\n# The default value is: YES.\n\nCLASS_DIAGRAMS         = YES\n\n# You can include diagrams made with dia in doxygen documentation. Doxygen will\n# then run dia to produce the diagram and insert it in the documentation. The\n# DIA_PATH tag allows you to specify the directory where the dia binary resides.\n# If left empty dia is assumed to be found in the default search path.\n\nDIA_PATH               =\n\n# If set to YES the inheritance and collaboration graphs will hide inheritance\n# and usage relations if the target is undocumented or is not a class.\n# The default value is: YES.\n\nHIDE_UNDOC_RELATIONS   = YES\n\n# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is\n# available from the path. This tool is part of Graphviz (see:\n# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent\n# Bell Labs. The other options in this section have no effect if this option is\n# set to NO\n# The default value is: YES.\n\nHAVE_DOT               = NO\n\n# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed\n# to run in parallel. When set to 0 doxygen will base this on the number of\n# processors available in the system. You can set it explicitly to a value\n# larger than 0 to get control over the balance between CPU load and processing\n# speed.\n# Minimum value: 0, maximum value: 32, default value: 0.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_NUM_THREADS        = 0\n\n# When you want a differently looking font in the dot files that doxygen\n# generates you can specify the font name using DOT_FONTNAME. You need to make\n# sure dot is able to find the font, which can be done by putting it in a\n# standard location or by setting the DOTFONTPATH environment variable or by\n# setting DOT_FONTPATH to the directory containing the font.\n# The default value is: Helvetica.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_FONTNAME           = Helvetica\n\n# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of\n# dot graphs.\n# Minimum value: 4, maximum value: 24, default value: 10.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_FONTSIZE           = 10\n\n# By default doxygen will tell dot to use the default font as specified with\n# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set\n# the path where dot can find it using this tag.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_FONTPATH           =\n\n# If the CLASS_GRAPH tag is set to YES then doxygen will generate a graph for\n# each documented class showing the direct and indirect inheritance relations.\n# Setting this tag to YES will force the CLASS_DIAGRAMS tag to NO.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nCLASS_GRAPH            = YES\n\n# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a\n# graph for each documented class showing the direct and indirect implementation\n# dependencies (inheritance, containment, and class references variables) of the\n# class with other documented classes.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nCOLLABORATION_GRAPH    = YES\n\n# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for\n# groups, showing the direct groups dependencies.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nGROUP_GRAPHS           = YES\n\n# If the UML_LOOK tag is set to YES, doxygen will generate inheritance and\n# collaboration diagrams in a style similar to the OMG's Unified Modeling\n# Language.\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nUML_LOOK               = NO\n\n# If the UML_LOOK tag is enabled, the fields and methods are shown inside the\n# class node. If there are many fields or methods and many nodes the graph may\n# become too big to be useful. The UML_LIMIT_NUM_FIELDS threshold limits the\n# number of items for each type to make the size more manageable. Set this to 0\n# for no limit. Note that the threshold may be exceeded by 50% before the limit\n# is enforced. So when you set the threshold to 10, up to 15 fields may appear,\n# but if the number exceeds 15, the total amount of fields shown is limited to\n# 10.\n# Minimum value: 0, maximum value: 100, default value: 10.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nUML_LIMIT_NUM_FIELDS   = 10\n\n# If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and\n# collaboration graphs will show the relations between templates and their\n# instances.\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nTEMPLATE_RELATIONS     = NO\n\n# If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to\n# YES then doxygen will generate a graph for each documented file showing the\n# direct and indirect include dependencies of the file with other documented\n# files.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nINCLUDE_GRAPH          = YES\n\n# If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are\n# set to YES then doxygen will generate a graph for each documented file showing\n# the direct and indirect include dependencies of the file with other documented\n# files.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nINCLUDED_BY_GRAPH      = YES\n\n# If the CALL_GRAPH tag is set to YES then doxygen will generate a call\n# dependency graph for every global function or class method.\n#\n# Note that enabling this option will significantly increase the time of a run.\n# So in most cases it will be better to enable call graphs for selected\n# functions only using the \\callgraph command. Disabling a call graph can be\n# accomplished by means of the command \\hidecallgraph.\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nCALL_GRAPH             = NO\n\n# If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller\n# dependency graph for every global function or class method.\n#\n# Note that enabling this option will significantly increase the time of a run.\n# So in most cases it will be better to enable caller graphs for selected\n# functions only using the \\callergraph command. Disabling a caller graph can be\n# accomplished by means of the command \\hidecallergraph.\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nCALLER_GRAPH           = NO\n\n# If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical\n# hierarchy of all classes instead of a textual one.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nGRAPHICAL_HIERARCHY    = YES\n\n# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the\n# dependencies a directory has on other directories in a graphical way. The\n# dependency relations are determined by the #include relations between the\n# files in the directories.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDIRECTORY_GRAPH        = YES\n\n# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images\n# generated by dot. For an explanation of the image formats see the section\n# output formats in the documentation of the dot tool (Graphviz (see:\n# http://www.graphviz.org/)).\n# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order\n# to make the SVG files visible in IE 9+ (other browsers do not have this\n# requirement).\n# Possible values are: png, png:cairo, png:cairo:cairo, png:cairo:gd, png:gd,\n# png:gd:gd, jpg, jpg:cairo, jpg:cairo:gd, jpg:gd, jpg:gd:gd, gif, gif:cairo,\n# gif:cairo:gd, gif:gd, gif:gd:gd, svg, png:gd, png:gd:gd, png:cairo,\n# png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and\n# png:gdiplus:gdiplus.\n# The default value is: png.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_IMAGE_FORMAT       = png\n\n# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to\n# enable generation of interactive SVG images that allow zooming and panning.\n#\n# Note that this requires a modern browser other than Internet Explorer. Tested\n# and working are Firefox, Chrome, Safari, and Opera.\n# Note: For IE 9+ you need to set HTML_FILE_EXTENSION to xhtml in order to make\n# the SVG files visible. Older versions of IE do not have SVG support.\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nINTERACTIVE_SVG        = NO\n\n# The DOT_PATH tag can be used to specify the path where the dot tool can be\n# found. If left blank, it is assumed the dot tool can be found in the path.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_PATH               =\n\n# The DOTFILE_DIRS tag can be used to specify one or more directories that\n# contain dot files that are included in the documentation (see the \\dotfile\n# command).\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOTFILE_DIRS           =\n\n# The MSCFILE_DIRS tag can be used to specify one or more directories that\n# contain msc files that are included in the documentation (see the \\mscfile\n# command).\n\nMSCFILE_DIRS           =\n\n# The DIAFILE_DIRS tag can be used to specify one or more directories that\n# contain dia files that are included in the documentation (see the \\diafile\n# command).\n\nDIAFILE_DIRS           =\n\n# When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the\n# path where java can find the plantuml.jar file. If left blank, it is assumed\n# PlantUML is not used or called during a preprocessing step. Doxygen will\n# generate a warning when it encounters a \\startuml command in this case and\n# will not generate output for the diagram.\n\nPLANTUML_JAR_PATH      =\n\n# When using plantuml, the PLANTUML_CFG_FILE tag can be used to specify a\n# configuration file for plantuml.\n\nPLANTUML_CFG_FILE      =\n\n# When using plantuml, the specified paths are searched for files specified by\n# the !include statement in a plantuml block.\n\nPLANTUML_INCLUDE_PATH  =\n\n# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes\n# that will be shown in the graph. If the number of nodes in a graph becomes\n# larger than this value, doxygen will truncate the graph, which is visualized\n# by representing a node as a red box. Note that doxygen if the number of direct\n# children of the root node in a graph is already larger than\n# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note that\n# the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.\n# Minimum value: 0, maximum value: 10000, default value: 50.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_GRAPH_MAX_NODES    = 50\n\n# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the graphs\n# generated by dot. A depth value of 3 means that only nodes reachable from the\n# root by following a path via at most 3 edges will be shown. Nodes that lay\n# further from the root node will be omitted. Note that setting this option to 1\n# or 2 may greatly reduce the computation time needed for large code bases. Also\n# note that the size of a graph can be further restricted by\n# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.\n# Minimum value: 0, maximum value: 1000, default value: 0.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nMAX_DOT_GRAPH_DEPTH    = 0\n\n# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent\n# background. This is disabled by default, because dot on Windows does not seem\n# to support this out of the box.\n#\n# Warning: Depending on the platform used, enabling this option may lead to\n# badly anti-aliased labels on the edges of a graph (i.e. they become hard to\n# read).\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_TRANSPARENT        = NO\n\n# Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output\n# files in one run (i.e. multiple -o and -T options on the command line). This\n# makes dot run faster, but since only newer versions of dot (>1.8.10) support\n# this, this feature is disabled by default.\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_MULTI_TARGETS      = NO\n\n# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page\n# explaining the meaning of the various boxes and arrows in the dot generated\n# graphs.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nGENERATE_LEGEND        = YES\n\n# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate dot\n# files that are used to generate the various graphs.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_CLEANUP            = YES\n"
  },
  {
    "path": "deps/avs_coap/LICENSE",
    "content": "AVSystem Anjay LwM2M Client SDK - Non-Commercial License\n========================================================\n\nVersion 1.0\nEffective Date: May 2025\nCopyright 2017-2026 AVSystem Sp. z o.o.\n\nSubject to the terms and conditions set forth herein, AVSystem Sp. z o.o.\n(\"Licensor\") hereby grants any person or legal entity obtaining a copy of this\nsoftware and associated documentation files (collectively, the \"Software\") a\nlimited, non-exclusive, non-transferable, royalty-free license to use,\nreproduce, modify, and distribute the Software solely for Non-Commercial\nPurposes, as defined below.\n\n1. DEFINITIONS\n\n   \"Non-Commercial Purposes\" means use of the Software:\n   - for internal evaluation or prototyping by individuals or legal entities;\n   - in academic or research institutions for educational or scientific\n     purposes;\n   - in personal, non-revenue-generating projects;\n   - in open-source, community, or not-for-profit settings, provided such use is\n     not part of any direct or indirect commercial activity.\n\n   \"Commercial Use\" includes, but is not limited to:\n   - incorporating the Software into any commercial product or service;\n   - using the Software in production systems operated by for-profit entities;\n   - using the Software in any revenue-generating or business-critical\n     operations;\n   - deploying the Software in any manner that supports commercial offerings,\n     including SaaS, OEM integrations, or professional services.\n\n2. LICENSE CONDITIONS\n\n   Subject to compliance with this License, the Licensee may:\n   1. use the Software for Non-Commercial Purposes;\n   2. copy and modify the Software;\n   3. distribute unmodified or modified versions of the Software, provided that:\n      - Redistribution of source code must retain AVSystem’s copyright notice\n        and include the list of usage conditions and AVSystem’s disclaimer;\n      - Redistribution in binary form, except as embedded in a physical device,\n        must include the following acknowledgment in accompanying documentation\n        or user interface: \"This product includes software developed by AVSystem\n        Sp. z o.o.\", this list of conditions of use and the following disclaimer\n        in the documentation and/or other materials provided with the software.\n      - Neither the name of AVSystem nor its collaborators (if any) may be used\n        to offer or promote products derived from this software without prior\n        written consent of the owner.\n\n3. DISCLAIMER OF WARRANTY\n\n   THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n   IMPLIED, INCLUDING, WITHOUT LIMITATION, WARRANTIES OF MERCHANTABILITY,\n   FITNESS FOR A PARTICULAR PURPOSE, TITLE, OR NON-INFRINGEMENT.\n   IN NO EVENT SHALL THE LICENSOR, ITS AFFILIATES, OR CONTRIBUTORS BE LIABLE FOR\n   ANY DAMAGES, INCLUDING DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n   CONSEQUENTIAL DAMAGES (INCLUDING BUT NOT LIMITED TO PROCUREMENT OF SUBSTITUTE\n   GOODS OR SERVICES, LOSS OF USE, DATA, OR PROFITS, OR BUSINESS INTERRUPTION)\n   HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n   LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE), ARISING IN ANY WAY\n   OUT OF THE USE OF THE SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH\n   DAMAGE.\n\n4. COMMERCIAL LICENSING\n\n   Any use of the Software falling outside the scope of Non-Commercial Purposes\n   constitutes Commercial Use and is expressly prohibited under this License.\n   To obtain rights for Commercial Use, including deployment, integration into\n   commercial products, or support for business operations, the Licensee must\n   acquire a Commercial License from AVSystem. Commercial Licenses are issued on\n   a per-SKU (Vendor Device Model) basis and may include:\n   - Free-of-charge usage for up to 10,000 devices per SKU;\n   - Access to premium features (subject to additional fees);\n   - Technical support packages (subject to additional fees);\n   - Additional licensing packages (subject to additional fees);\n\nTo register for a Commercial License, visit:\nhttps://go.avsystem.com/anjay-registration\n\nFor commercial inquiries, contact:\nsales@avsystem.com\n\n5. GOVERNING LAW\n\n   This License shall be governed by and construed in accordance with the laws\n   of the Republic of Poland, without regard to its conflict of law provisions.\n   All disputes arising from or relating to this License shall be subject to the\n   exclusive jurisdiction of the competent courts located in Kraków, Poland.\n\n============================================\nEND OF AVSYSTEM ANJAY NON-COMMERCIAL LICENSE\n============================================\n"
  },
  {
    "path": "deps/avs_coap/NOTICE",
    "content": "AVSystem CoAP Library\nCopyright 2017-2026 AVSystem\n\nThis product includes software developed at AVSystem (www.avsystem.com).\n\nThis product bundles the AVSystem Commons Library\n(https://github.com/AVSystem/avs_commons), licensed under the terms of the\nApache License, version 2.0. See deps/avs_commons/NOTICE file for details.\n"
  },
  {
    "path": "deps/avs_coap/README.md",
    "content": "# AVSystem CoAP Library\n"
  },
  {
    "path": "deps/avs_coap/cmake/AddHeaderSelfSufficiencyTests.cmake",
    "content": "# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem CoAP library\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n#\n# Every header must be self-sufficient, i.e. one must be able to include it\n# without having to also include anything else before.\n#\n# This function generates targets that for every passed header file generate\n# an empty translation unit that includes just that header (and, optionally,\n# some extra config headers before it). If that generated file compiles,\n# that means all symbols it depends on are correctly included.\n#\n# Note that such file does not necessarily need to link; only compilation is\n# checked.\n#\n# Arguments:\n# - TARGET <name>                 - name of the custom target defined by the\n#                                   function. All generated per-file targets\n#                                   are set as dependencies of this one\n# - TARGET_PREFIX <prefix>        - string prepended to all generated per-file\n#                                   targets\n# - FILES <path>...               - list of files to generate check targets for\n# - DIRECTORIES <path>...         - list of directories to search for header\n#                                   files (*.h) to generate checks for.\n# - EXCLUDE_PATTERNS <regex>...   - list of regexes that will be matched\n#                                   against file paths to determine if a\n#                                   particular header file should be excluded\n#                                   from checks.\n# - INCLUDES <path>...            - list of files that need to be included\n#                                   before any header file (e.g. avs_coap_config.h)\n# - INCLUDE_DIRECTORIES <path>... - list of additional include directories to\n#                                   add when compiling\n# - COMPILE_OPTIONS <opt>...      - list of extra compilation options\n# - LIBS <target>...              - list of targets to retrieve include\n#                                   directories from\nfunction(add_header_self_sufficiency_tests)\n    set(options)\n    set(one_value_args TARGET_PREFIX TARGET)\n    set(multi_value_args FILES DIRECTORIES INCLUDES EXCLUDE_PATTERNS INCLUDE_DIRECTORIES COMPILE_OPTIONS LIBS)\n    cmake_parse_arguments(SST \"${options}\" \"${one_value_args}\" \"${multi_value_args}\" ${ARGN})\n\n    add_custom_target(${SST_TARGET})\n\n    set(includes)\n    foreach(inc IN LISTS SST_INCLUDES)\n        string(APPEND includes \"#include <${inc}>\\n\")\n    endforeach()\n\n    set(headers ${SST_FILES})\n    foreach(dir IN LISTS SST_DIRECTORIES)\n        file(GLOB_RECURSE dir_headers \"${dir}/*.h\")\n        list(APPEND headers ${dir_headers})\n    endforeach()\n\n    foreach(header IN LISTS headers)\n        set(is_excluded FALSE)\n        foreach(exclude_regex IN LISTS SST_EXCLUDE_PATTERNS)\n            if(\"${header}\" MATCHES \"${exclude_regex}\")\n                message(STATUS \"add_header_self_sufficiency_tests: excluding ${header} (match: ${exclude_regex})\")\n                set(is_excluded TRUE)\n                break()\n            endif()\n        endforeach()\n\n        if(is_excluded)\n            continue()\n        endif()\n\n        string(REGEX REPLACE \"[^a-zA-Z0-9]\" _ target_name \"${header}\")\n        set(input \"${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/CMakeTmp/${target_name}.c\")\n        set(target_name ${SST_PREFIX}_header_self_sufficiency_test_${target_name})\n\n        file(GENERATE OUTPUT \"${input}\"\n             CONTENT \"${includes}\\n#include <${header}>\\nint main() { return 0; }\\n\")\n        add_library(${target_name} OBJECT EXCLUDE_FROM_ALL \"${input}\")\n        foreach(lib IN LISTS SST_LIBS)\n            target_include_directories(${target_name} PRIVATE\n                                       $<TARGET_PROPERTY:${lib},INTERFACE_INCLUDE_DIRECTORIES>)\n        endforeach()\n        target_include_directories(${target_name} PRIVATE ${SST_INCLUDE_DIRECTORIES})\n\n        # make sure to fail compilation on any implicit dependency, including\n        # functions used without forward-declarations\n        target_compile_options(${target_name} PRIVATE\n                               ${SST_COMPILE_OPTIONS}\n                               -Werror=implicit)\n\n        add_test(NAME ${target_name}\n                 COMMAND ${CMAKE_COMMAND} --build ${CMAKE_BINARY_DIR} --target ${target_name})\n        add_dependencies(${SST_TARGET} ${target_name})\n    endforeach()\nendfunction()\n"
  },
  {
    "path": "deps/avs_coap/cmake/avs_coap-config.cmake.in",
    "content": "# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem CoAP library\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nset(AVS_COAP_VERSION \"@AVS_COAP_VERSION@\")\n\nif(NOT DEFINED avs_commons_DIR)\n    set(avs_commons_DIR \"${CMAKE_CURRENT_LIST_DIR}/../avs_commons\")\nendif()\nfind_package(avs_commons REQUIRED COMPONENTS @AVS_COMMONS_REQUIRED_COMPONENTS@)\n\n@PACKAGE_INIT@\n\ncheck_required_components(avs_coap)\n\ninclude(${CMAKE_CURRENT_LIST_DIR}/avs_coap-targets.cmake)\n"
  },
  {
    "path": "deps/avs_coap/cmake/fill-placeholders.cmake",
    "content": "# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem CoAP library\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nstring(TIMESTAMP current_year \"%Y\")\n\nforeach(file IN LISTS CMAKE_INSTALL_MANIFEST_FILES)\n    if(file MATCHES \".(h|hpp|c|cpp|cmake|py|sh)$\")\n        file(READ ${file} file_contents)\n        string(REPLACE \"2017-2026\" \"${current_year}\" file_contents_replaced \"${file_contents}\")\n        file(WRITE ${file} \"${file_contents_replaced}\")\n    endif()\nendforeach()\n"
  },
  {
    "path": "deps/avs_coap/devconfig",
    "content": "#!/usr/bin/env bash\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem CoAP library\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nset -e\n\nif [[ -d '.git' ]]; then\n    git submodule update --init --recursive\nfi\n\nEXTRA_FLAGS=()\n\nrm -f CMakeCache.txt\nrm -rf CMakeFiles\ncmake -D CMAKE_BUILD_TYPE=Debug \\\n      -D CMAKE_C_FLAGS=\"-Wall -Wextra -Wshadow -Winit-self -Wmissing-declarations -Wc++-compat -Wsign-conversion -Wconversion -Wcast-qual -Wvla -Wno-variadic-macros -Wno-long-long -Wjump-misses-init -Werror -Wno-error=deprecated-declarations\" \\\n      -D CMAKE_EXPORT_COMPILE_COMMANDS=ON \\\n      -D WITH_INTERNAL_LOGS=ON \\\n      -D WITH_INTERNAL_TRACE=ON \\\n      -D AVS_LOG_WITH_TRACE=ON \\\n      -D WITH_VALGRIND=ON \\\n      -D WITH_TEST=ON \\\n      -D WITH_POISONING=ON \\\n      -D WITH_MBEDTLS=ON \\\n      \"${EXTRA_FLAGS[@]}\" \\\n      \"$@\" \"$(dirname \"$0\")\" &&\nmake clean\n\n# ensure venv is used\nVENV_DIR=\"${VENV_DIR:-venv}\"\nif python3 -c 'import sys; exit(0) if sys.base_prefix != sys.prefix else exit(1)'; then\n    echo \"Using already activated Python venv\"\nelse\n    if [ ! -d \"${VENV_DIR}\" ]; then\n        echo \"Creating Python venv in ${VENV_DIR}\"\n        python3 -m venv \"${VENV_DIR}\"\n    fi\n\n    echo \"Activating Python venv in ${VENV_DIR}\"\n\n    source \"${VENV_DIR}/bin/activate\"\n    python3 -m pip install --upgrade pip\n    python3 -m pip install -r requirements.txt\nfi\n"
  },
  {
    "path": "deps/avs_coap/doc/CMakeLists.txt",
    "content": "# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem CoAP library\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nadd_custom_target(avs_coap_doc)\nif(NOT TARGET doc)\n    add_custom_target(doc)\nendif()\nadd_dependencies(doc avs_coap_doc)\n\nset(AVS_COAP_SPHINX_DOC_ROOT_DIR \"${CMAKE_CURRENT_SOURCE_DIR}/sphinx/source\")\nset(AVS_COAP_SPHINX_DOC_CONF_DIR \"${CMAKE_CURRENT_BINARY_DIR}/sphinx\")\n\nif(EXISTS \"${AVS_COAP_SPHINX_DOC_ROOT_DIR}/conf.py.in\")\n    find_program(SPHINX_BUILD_EXECUTABLE sphinx-build\n                 HINTS $ENV{SPHINX_DIR} PATH_SUFFIXES bin)\n\n    configure_file(${AVS_COAP_SPHINX_DOC_ROOT_DIR}/conf.py.in\n                   ${AVS_COAP_SPHINX_DOC_CONF_DIR}/conf.py\n                   @ONLY)\n    add_custom_target(avs_coap_doc_sphinx\n                      COMMAND ${SPHINX_BUILD_EXECUTABLE}\n                              -b html\n                              -c ${AVS_COAP_SPHINX_DOC_CONF_DIR}\n                              ${AVS_COAP_SPHINX_DOC_ROOT_DIR}\n                              ${AVS_COAP_SPHINX_DOC_CONF_DIR}/html\n                      WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/sphinx)\n    add_custom_command(TARGET avs_coap_doc_sphinx POST_BUILD\n                       COMMAND ${CMAKE_COMMAND} -E remove -f api\n                       COMMAND ${CMAKE_COMMAND} -E create_symlink ../../doxygen/html api\n                       WORKING_DIRECTORY \"${AVS_COAP_SPHINX_DOC_CONF_DIR}/html\")\n\n    add_dependencies(avs_coap_doc avs_coap_doc_sphinx)\nendif()\n\nif(EXISTS \"${AVS_COAP_SOURCE_DIR}/Doxyfile.in\")\n    find_package(Doxygen)\n\n    get_target_property(DOXYGEN_INPUT_PATHS avs_coap INTERFACE_INCLUDE_DIRECTORIES)\n    # doxygen expects whitespace-separated list, cmake stores them as\n    # semicolon-separated strings\n    string(REPLACE \";\" \" \" DOXYGEN_INPUT_PATHS \"${DOXYGEN_INPUT_PATHS}\")\n\n    set(DOXYFILE_PREDEFINED_MACROS)\n    # TODO: List these flags automatically\n    foreach(FLAG WITH_AVS_COAP_BLOCK\n                 WITH_AVS_COAP_OBSERVE\n                 WITH_AVS_COAP_OBSERVE_CANCEL_ON_TIMEOUT\n                 WITH_AVS_COAP_OBSERVE_PERSISTENCE\n                 WITH_AVS_COAP_STREAMING_API\n                 WITH_AVS_COAP_TCP\n                 WITH_AVS_COAP_UDP)\n        if(\"${${FLAG}}\")\n            set(DOXYFILE_PREDEFINED_MACROS \"${DOXYFILE_PREDEFINED_MACROS} ${FLAG}\")\n        endif()\n    endforeach()\n\n    set(DOXYGEN_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/doxygen)\n\n    # configure_file does not expand CMake generator expressions; file(GENERATE) does\n    configure_file(${AVS_COAP_SOURCE_DIR}/Doxyfile.in\n                   ${CMAKE_CURRENT_BINARY_DIR}/doxygen/Doxyfile.with-cmake-generator-expressions\n                   @ONLY)\n    file(GENERATE OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/doxygen/Doxyfile\n         INPUT ${CMAKE_CURRENT_BINARY_DIR}/doxygen/Doxyfile.with-cmake-generator-expressions)\n\n    add_custom_target(avs_coap_doc_doxygen\n                      COMMAND ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/doxygen/Doxyfile\n                      WORKING_DIRECTORY ${AVS_COAP_SOURCE_DIR}\n                      DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/doxygen/Doxyfile)\n\n    if(TARGET avs_coap_doc_sphinx)\n        add_dependencies(avs_coap_doc_sphinx avs_coap_doc_doxygen)\n    endif()\n    add_dependencies(avs_coap_doc avs_coap_doc_doxygen)\nendif()\n"
  },
  {
    "path": "deps/avs_coap/doc/sphinx/make.bat",
    "content": "@ECHO OFF\r\n\r\npushd %~dp0\r\n\r\nREM Command file for Sphinx documentation\r\n\r\nif \"%SPHINXBUILD%\" == \"\" (\r\n\tset SPHINXBUILD=sphinx-build\r\n)\r\nset SOURCEDIR=source\r\nset BUILDDIR=build\r\n\r\nif \"%1\" == \"\" goto help\r\n\r\n%SPHINXBUILD% >NUL 2>NUL\r\nif errorlevel 9009 (\r\n\techo.\r\n\techo.The 'sphinx-build' command was not found. Make sure you have Sphinx\r\n\techo.installed, then set the SPHINXBUILD environment variable to point\r\n\techo.to the full path of the 'sphinx-build' executable. Alternatively you\r\n\techo.may add the Sphinx directory to PATH.\r\n\techo.\r\n\techo.If you don't have Sphinx installed, grab it from\r\n\techo.http://sphinx-doc.org/\r\n\texit /b 1\r\n)\r\n\r\n%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%\r\ngoto end\r\n\r\n:help\r\n%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%\r\n\r\n:end\r\npopd\r\n"
  },
  {
    "path": "deps/avs_coap/doc/sphinx/source/ErrorHandling.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem CoAP library\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nError handling\n==============\n\nSince late September 2019 and Anjay 2.2.0 release, ``avs_coap`` has been\nrefactored to use the ``avs_error_t`` type for error handling.\n\nThere are no global or context-wide error states, and specific error codes are\npropagated up to public APIs whenever possible.\n\nCoAP-specific errors will have the ``category`` field set to\n``AVS_COAP_ERR_CATEGORY``. ``avs_coap`` functions may also return generic\n``AVS_ERRNO_CATEGORY`` errors, in particular:\n\n- ``AVS_EBADF`` - if the streaming API is misused, i.e., when the streams are\n  used in invalid states\n- ``AVS_EBADMSG`` - in case of invalid data encountered during observation\n  persist/restore operations\n- ``AVS_EINVAL`` - generic \"invalid arguments\" error\n- ``AVS_ENOBUFS`` - buffer overflow in the streaming API\n- ``AVS_ENOENT`` - ``avs_coap_client_set_next_response_payload_offset()`` called\n  for a nonexistent exchange\n- ``AVS_ENOMEM`` - generic \"out of memory\" error\n- ``AVS_ENOMSG`` - OSCORE encryption/decryption error (OSCORE feature only)\n- ``AVS_EPROTO`` - message digest calculation error in OSCORE (OSCORE feature only)\n- ``AVS_ERANGE`` - value out of range when trying to add CoAP option\n- other errors, as propagated from the network socket layer\n\nAdditional information about errors from the ``AVS_COAP_ERR_CATEGORY`` category\ncan be obtained using ``avs_coap_error_class()`` and\n``avs_coap_error_recovery_action()`` APIs. There is also ``avs_coap_strerror()``\nfor stringifying such errors, which will automatically delegate to\n``avs_strerror()`` for ``AVS_ERRNO_CATEGORY`` errors.\n"
  },
  {
    "path": "deps/avs_coap/doc/sphinx/source/Fuzzing.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem CoAP library\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nFuzzing avs_coap\n================\n\n``tests/fuzz`` directory contains a few tests intended for use with\n`AFL <http://lcamtuf.coredump.cx/afl/>`_ fuzzer.\n\nThese tests work by basically calling public APIs in random order with random\ndata until something crashes. Every such test reads the sequence of API calls\nand its arguments from stdin, so that crashes can be reproduced later.\n\nBasic usage\n-----------\n\n- configure ``avs_coap`` with ``afl-gcc`` and compile everything. When\n  ``afl-gcc`` is detected as the compiler, ``make`` will build fuzz tests as\n  well::\n\n      ./devconfig -DCMAKE_C_COMPILER=$HOME/tools/afl/latest/afl-gcc \\\n                  -DCMAKE_C_FLAGS='--coverage'\n      make -j\n\n- start ``afl-fuzz``, passing it a directory with initial set of tests (there\n  are some under ``tests/fuzz``), the directory to store findings in, and the\n  application to be fuzzed::\n\n      afl-fuzz -i ./tests/fuzz/input/coap_async_api_udp \\\n               -o fuzz-out \\\n               ./tests/fuzz/avs_coap_async_api_udp\n\n- when AFL finds an input that causes a crash, it is put into ``crashes``\n  subdirectory. To reproduce the crash, compile the application with a regular\n  compiler (*not* ``afl-gcc``) - out-of-source builds are helpful here; while\n  fuzz tests binaries are not normally compiled if the compiler is not\n  ``afl-gcc``, you can manually build it by running e.g.\n  ``make avs_coap_async_api_udp``. Pass the input to stdin of the built binary.\n  Use ``VERBOSE`` environment variable to enable detailed logging if necessary::\n\n      VERBOSE=1 ./tests/fuzz/avs_coap_async_api_udp < fuzz-out/crashes/id:000000,sig:06,src:000003,op:havoc,rep:64\n\n  .. note::\n\n     AFL can find pretty crazy cases. Using `RR <https://rr-project.org/>`_ is\n     often extremely helpful when figuring out what exactly happened.\n\nSetting up initial test cases\n-----------------------------\n\nTest inputs are binary data, which can be hard to figure out. For that reason,\n``tests/fuzz/input/*.hex`` directories contain annotated versions of input\ntests. These kind-of-human-readable inputs can be converted into binary form\nusing ``tests/fuzz/input/hex-to-fuzz-inputs.sh`` script:\n\n    ./hex-to-fuzz-inputs.py < input.hex > input.binary\n\nThe script strips all \"line comments\" starting with #. You may use them freely\nin annotated input files.\n\nFuzz coverage reports\n---------------------\n\nTo figure out what parts of the code did AFL reach, use\n`afl-cov <https://github.com/mrash/afl-cov>`_. You will need to pass\n``--coverage`` to ``CMAKE_C_FLAGS`` when configuring ``avs_coap`` to make this\nwork.\n\nExample usage::\n\n    afl-cov --afl-fuzzing-dir ./fuzz-out --coverage-cmd \"./tests/fuzz/avs_coap_async_api_udp < AFL_FILE\" --code-dir .\n    # after this finishes, open ./fuzz-out/cov/web/ in a web browser\n"
  },
  {
    "path": "deps/avs_coap/doc/sphinx/source/Overview.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem CoAP library\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nOverview\n========\n\nDirectory structure\n-------------------\n\n- ``src/``\n\n  - ``async/`` - :ref:`async-api`\n  - ``options/`` - :ref:`avs_coap_options_t-implementation`\n  - ``oscore/`` (OSCORE feature only)\n  - ``streaming/`` - :ref:`streaming-api`\n  - ``tcp/`` - :ref:`transport-specific-impls`\n  - ``udp/`` - :ref:`transport-specific-impls`\n\n\nCoAP context\n------------\n\nAll CoAP send/receive operations in the library require an ``avs_coap_ctx_t``\nobject. That object contains all the state required for parsing an incoming\npacket or constructing an outgoing one. It can be used simultaneously as a\nclient and server, and abstracts away:\n\n- BLOCK-wise transfers,\n- Observations,\n- Transport-specific parts of CoAP, like packet encoding/decoding, handling\n  transport-specific messages.\n\nThe ``avs_coap_ctx_t`` can be thought of as an abstract class that has a set of\nprivate pure virtual methods (``avs_coap_ctx_vtable_t``), and implements 4\npublic interfaces based on these vtable methods:\n\n- CoAP asynchronous server,\n- CoAP asynchronous client,\n- CoAP streaming server,\n- CoAP streaming client.\n\nAsynchronous interfaces avoid blocking on socket operations whenever possible\nand make heavy use of callbacks. Streaming interfaces do not return until\nrequest handling is fully finished.\n\nThe CoAP context uses two *shared buffers* for message input and output. The\nsame set of buffers may be used in multiple CoAP context objects if the\napplication can guarantee that at most one CoAP context will perform IO\noperations at any time.\n\n\n.. _transport-specific-impls:\n\n``avs_coap_ctx_vtable_t`` methods (e.g. ``src/tcp``, ``src/udp/``)\n------------------------------------------------------------------\n\nPure virtual ``avs_coap_ctx_vtable_t`` represent operations that are\ntransport-specific. Their responsibilities include:\n\n- serializing outgoing messages,\n- parsing incoming messages,\n- ensuring message delivery, by handling retransmissions and timeouts,\n- matching sent requests to responses,\n- internally handing all transport-specific details:\n\n  - UDP:\n\n    - message type\n    - message ID\n    - Separate Responses\n    - CoAP Ping\n    - Observe cancel via Reset response\n\n  - TCP:\n\n    - CSM and other 7.xx signaling messages\n\n\nAsync API (``src/async/``)\n--------------------------\n\nAsynchronous API makes heavy use of *exchanges*, represented by\n``avs_coap_exchange_t`` struct. An exchange abstracts away a single\nrequest-response pair, but here, unlike the virtual method layer, a \"single\nrequest-response pair\" may include more than two packets if either request or\nresponse uses CoAP BLOCK.\n\nAn exchange can be either:\n\n- *client exchange*, if it represents a request made by ``avs_coap`` to\n  a remote server and its response, or\n- *server exchange*, if it represents a request made by a remote client that\n  ``avs_coap`` is handling, and the generated response. Note that this includes\n  Observe notifications, which are in essence repeated responses to previously\n  received requests.\n\nBoth kinds of exchanges use the same ``avs_coap_exchange_t`` type. All client\nexchanges are put onto ``avs_coap_base_t::client_exchanges`` list, and server\nexchanges onto ``avs_coap_base_t::server_exchanges``.\n``src/async/avs_coap_exchange.c`` contains logic common to both server and\nclient exchanges.\n\n.. note::\n\n    There should be two separate types for client and server exchanges, that\n    share one base exchange type in a pattern similar to ``avs_coap_ctx_t`` and\n    transport-specific contexts.\n\nEvery exchange is uniquely identified by a numeric identifier,\n``avs_coap_exchange_id_t``, which allows the user to refer to a specific one\nwhen required (e.g. when a single handler is shared between multiple concurrent\nrequests, or when the user wants to cancel handling of a specific request).\n\nThis layer is responsible for handling CoAP features that are independent from\nthe specific transport being used, including:\n\n- BLOCK-wise transport handling,\n- Observe establishment and cancellation.\n\nEstablished observations are represented by ``avs_coap_observe_t`` objects,\nwhich contain information necessary for handling (possibly block-wise) observes,\nas well as user-defined \"on observation canceled\" handler. That type is also\nused to ensure that Observe option values are strictly increasing, by setting it\nto consecutive values starting from 0 (assigned to a response to the original\nObserve request). From the user perspective, observations are identified using\n``avs_coap_observe_id_t``, which is a CoAP token wrapped into a struct to make\nit a separate type.\n\nHaving a valid ``avs_coap_observe_id_t`` allows the user to send a notification\nassociated with the observation. Every notification spawns an exchange object\nthat behaves as a repeated Read request.\n\n.. note::\n\n   Lifetime of spawned notifications is independent from ``avs_coap_observe_t``.\n   In particular, if ``avs_coap`` starts sending a BLOCK-wise notification, and\n   the remote client cancels the observation, the started notification can still\n   be fully delivered.\n\n\n.. _async-api:\n\nAsync API source files\n~~~~~~~~~~~~~~~~~~~~~~\n\n- ``src/async/avs_coap_exchange.c`` - logic common to both *client* and *server\n  exchanges*:\n\n  - read a chunk of user-provided payload (outgoing request/response),\n  - add initial (``seq_num == 0``) BLOCK1/BLOCK2 option if necessary\n    (message options do not contain BLOCK option and payload is larger than max\n    block size),\n  - pass message headers + payload to transport-specific layer.\n\n- ``src/async/avs_coap_async_client.c`` - async client API implementation:\n\n  - handle 2.31 Continue response to a request (repeat request with incremented\n    BLOCK1 ``seq_num``),\n  - send requests for further payload (repeat request with incremented BLOCK2\n    ``seq_num``).\n\n- ``src/async/avs_coap_async_server.c`` - async server API implementation:\n\n  - match incoming requests to existing exchanges or create new ones\n    (i.e. assemble multiple incoming BLOCK1 requests into one logical request),\n  - detect timeouts of a BLOCK-wise request (when the remote endpoint stops\n    sending messages related to an exchange),\n  - handle Observe establishment or cancellation with Observe option,\n  - implement API for sending async notifications.\n\n\nStreaming API (``src/streaming/``)\n----------------------------------\n\nStreaming API builds upon the async one, providing an interface that uses\n``avs_stream_t`` objects for passing payload data around instead of passing\nbuffers to user-provided callbacks. While this approach is a bit more convenient\nto use, it comes with a cost: the API blocks for the whole time of transmitting\nthe request and following response. It is recommended to use the async API\ninstead if possible.\n\n\n.. _streaming-api:\n\nStreaming API source files\n~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n- ``src/streaming/avs_coap_streaming_client.c`` - ``avs_stream_t``-compatible\n  wrapper around ``async_client`` API:\n\n  - loop until transfer of a whole client exchange is complete,\n  - wrap request payload ``avs_coap_streaming_writer_t`` into\n    ``avs_coap_payload_writer_t`` adapter used by ``async_client`` API,\n  - expose received response payload as ``avs_stream_t``.\n\n- ``src/streaming/avs_coap_streaming_server.c`` - ``avs_stream_t``-compatible\n  wrapper around ``async_server`` API:\n\n  - loop until transfer of a whole server exchange is complete,\n  - expose received request payload as ``avs_stream_t``,\n  - wrap response payload ``avs_coap_streaming_writer_t`` into\n    ``avs_coap_payload_writer_t`` adapter used by ``async_server`` API.\n\n\nCoAP context object and scheduler\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nCoAP context objects use the ``avs_sched_t`` object to handle any kinds of\ntimeouts. Because it is expected that the same scheduler object is also used for\nother tasks, possibly also related to LwM2M, executing arbitrary tasks while\nhandling a streaming request might have disastrous consequences - for example,\nhandling two BLOCK-wise PUT/POST request concurrently.\n\nFor that reason, all CoAP context logic that depends on scheduler is\nencapsulated in a single scheduler job\n(``_avs_coap_retry_or_request_expired_job``). The streaming API then calls this\njob directly, bypassing the scheduler.\n\nThat also means streaming API effectively prevents the scheduler from running,\nand delays any scheduled tasks until after request handling is complete.\n\n\n.. _avs_coap_options_t-implementation:\n\n``avs_coap_options_t`` implementation\n-------------------------------------\n\nAll code that operates on ``avs_coap_options_t``. This code should not touch\nany \"larger\" structures of ``avs_coap`` (like CoAP context or exchange object).\n"
  },
  {
    "path": "deps/avs_coap/doc/sphinx/source/_static/theme_overrides.css",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n/**\n * See: http://rackerlabs.github.io/docs-rackspace/tools/rtd-tables.html\n */\n\n/* override table width restrictions */\n@media screen and (min-width: 767px) {\n\n   .wy-table-responsive table td {\n      /* !important prevents the common CSS stylesheets from overriding\n         this as on RTD they are loaded after this stylesheet */\n      white-space: normal !important;\n   }\n\n   .wy-table-responsive {\n      overflow: visible !important;\n   }\n\n   /* https://stackoverflow.com/questions/23211695/modifying-content-width-of-the-sphinx-theme-read-the-docs */\n   .wy-nav-content {\n        max-width: 960px !important;\n    }\n}\n\n/* AVSystem color scheme */\n.wy-side-nav-search>a {\n    color: #000 !important;\n}\n\n.wy-side-nav-search>a:after {\n    content: \"avs_coap\";\n}\n\n.wy-side-nav-search>div.version {\n    color: #000;\n}\n\n.wy-side-nav-search input[type=text] {\n    border-radius: 0 !important;\n    border: none !important;\n    box-shadow: none !important;\n}\n\n.wy-menu-vertical a:active {\n    background-color: #ffd500;\n    color: #000;\n}\n\na {\n    color: #000;\n    text-decoration: underline;\n    text-decoration-thickness: 1px;\n}\n\n.wy-nav-content a:visited,\n.wy-nav-content a:hover {\n    color: #8a8a8a;\n}\n\nnav a {\n    text-decoration: none !important;\n}\n\n.highlight {\n    background: #ffffff;\n}\n\n.highlight .hll {\n    background: #f3f6f6;\n}\n"
  },
  {
    "path": "deps/avs_coap/doc/sphinx/source/conf.py.in",
    "content": "# Configuration file for the Sphinx documentation builder.\n#\n# This file only contains a selection of the most common options. For a full\n# list see the documentation:\n# https://www.sphinx-doc.org/en/master/usage/configuration.html\n\n# -- Path setup --------------------------------------------------------------\n\n# If extensions (or modules to document with autodoc) are in another directory,\n# add these directories to sys.path here. If the directory is relative to the\n# documentation root, use os.path.abspath to make it absolute, like shown here.\n#\n# import os\n# import sys\n# sys.path.insert(0, os.path.abspath('.'))\n\n\n# -- Project information -----------------------------------------------------\n\nproject = 'avs_coap'\ncopyright = '2017-2026, AVSystem'\nauthor = 'AVSystem'\nversion = '@AVS_COAP_VERSION@'\nrelease = version\n\n\n# -- General configuration ---------------------------------------------------\n\n# Add any Sphinx extension module names here, as strings. They can be\n# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom\n# ones.\nextensions = [\n]\n\n# Add any paths that contain templates here, relative to this directory.\ntemplates_path = ['_templates']\n\n# List of patterns, relative to source directory, that match files and\n# directories to ignore when looking for source files.\n# This pattern also affects html_static_path and html_extra_path.\nexclude_patterns = []\n\n# Define master documentation document to index.rst\nmaster_doc = 'index'\n\n# -- Options for HTML output -------------------------------------------------\n\n# The theme to use for HTML and HTML Help pages.  See the documentation for\n# a list of builtin themes.\n#\nhtml_theme = 'sphinx_rtd_theme'\n\n# Theme options are theme-specific and customize the look and feel of a theme\n# further.  For a list of options available for each theme, see the\n# documentation.\nhtml_theme_options = {\n    'logo_only': True,\n    'style_nav_header_background': '#ffd500'\n}\n\n# The name of an image file (relative to this directory) to place at the top\n# of the sidebar.\nhtml_logo = '@AVS_COAP_SPHINX_DOC_ROOT_DIR@/avsystem_header.png'\n\n# Add any paths that contain custom static files (such as style sheets) here,\n# relative to this directory. They are copied after the builtin static files,\n# so a file named \"default.css\" will overwrite the builtin \"default.css\".\nhtml_static_path = ['@AVS_COAP_SPHINX_DOC_ROOT_DIR@/_static']\n\n# This is necessary to fix issues with tables in \"Read the Docs\" style.\nhtml_context = {\n    'css_files': [ '_static/theme_overrides.css' ]\n}\n"
  },
  {
    "path": "deps/avs_coap/doc/sphinx/source/index.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem CoAP library\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nWelcome to AVSystem CoAP library documentation!\n===============================================\n\nContents\n--------\n\n.. toctree::\n   :numbered:\n   :titlesonly:\n\n   Overview\n   ErrorHandling\n   Fuzzing\n\nLinks\n-----\n\n* `Source repository <https://github.com/AVSystem/Anjay/tree/master/deps/avs_coap>`_\n* `Doxygen-generated API documentation <api/index.html>`_\n\nIndices and tables\n==================\n\n* :ref:`search`\n"
  },
  {
    "path": "deps/avs_coap/examples/CMakeLists.txt",
    "content": "# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem CoAP library\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nset(EXAMPLES_BUILD_DIR \"${CMAKE_CURRENT_BINARY_DIR}/build\")\nset(AVS_COAP_SOURCE_DIR \"${CMAKE_CURRENT_SOURCE_DIR}/..\")\nset(AVS_COAP_BUILD_DIR \"${EXAMPLES_BUILD_DIR}/avs_coap-build\")\nset(AVS_COAP_INSTALL_DIR \"${EXAMPLES_BUILD_DIR}/avs_coap\")\n\nadd_custom_target(avs_coap_examples_install)\nadd_custom_command(TARGET avs_coap_examples_install\n                   COMMAND ${CMAKE_COMMAND} -E make_directory \"${EXAMPLES_BUILD_DIR}\"\n                   COMMAND ${CMAKE_COMMAND} -E make_directory \"${AVS_COAP_INSTALL_DIR}\"\n                   COMMAND ${CMAKE_COMMAND} -E make_directory \"${AVS_COAP_BUILD_DIR}\")\n\nadd_custom_command(TARGET avs_coap_examples_install\n                   COMMAND ${CMAKE_COMMAND}\n                        -H${AVS_COAP_SOURCE_DIR}\n                        -B.\n                        -DCMAKE_INSTALL_PREFIX=\"${AVS_COAP_INSTALL_DIR}\"\n                   COMMAND ${CMAKE_COMMAND} --build . --target install -- -j${NPROC}\n                   WORKING_DIRECTORY ${AVS_COAP_BUILD_DIR})\n\nadd_custom_target(avs_coap_examples)\nif(TARGET avs_coap_check)\n    add_dependencies(avs_coap_check avs_coap_examples)\nendif()\n\nfunction(add_example NAME)\n    set(TARGET_NAME avs_coap_example-${NAME})\n    set(BUILD_DIR \"${EXAMPLES_BUILD_DIR}/${NAME}\")\n\n    add_custom_target(${TARGET_NAME})\n    add_custom_command(TARGET ${TARGET_NAME}\n                       COMMAND ${CMAKE_COMMAND} -E make_directory \"${BUILD_DIR}\")\n    add_custom_command(TARGET ${TARGET_NAME}\n                       COMMAND ${CMAKE_COMMAND}\n                            -H${CMAKE_CURRENT_SOURCE_DIR}/${NAME}/\n                            -B.\n                            -DCMAKE_PREFIX_PATH=\"${AVS_COAP_INSTALL_DIR}\"\n                       COMMAND ${CMAKE_COMMAND} --build . -- VERBOSE=1 -j${NPROC}\n                       WORKING_DIRECTORY \"${BUILD_DIR}\")\n\n    add_dependencies(${TARGET_NAME} avs_coap_examples_install)\n    add_dependencies(avs_coap_examples ${TARGET_NAME})\nendfunction()\n\nadd_example(async-client)\n"
  },
  {
    "path": "deps/avs_coap/examples/async-client/CMakeLists.txt",
    "content": "# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem CoAP library\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\ncmake_minimum_required(VERSION 3.16)\nproject(async-client C)\n\nfind_package(avs_coap REQUIRED)\n\nadd_executable(async-client src/main.c)\ntarget_link_libraries(async-client PRIVATE avs_coap)\n"
  },
  {
    "path": "deps/avs_coap/examples/async-client/src/main.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <inttypes.h>\n\n#include <unistd.h>\n\n#include <avsystem/commons/avs_log.h>\n#include <avsystem/commons/avs_net.h>\n#include <avsystem/commons/avs_prng.h>\n#include <avsystem/commons/avs_sched.h>\n#include <avsystem/commons/avs_utils.h>\n\n#include <avsystem/coap/coap.h>\n#include <avsystem/coap/udp.h>\n\nstatic void response_handler(avs_coap_ctx_t *ctx,\n                             avs_coap_exchange_id_t exchange_id,\n                             avs_coap_client_request_state_t result,\n                             const avs_coap_client_async_response_t *response,\n                             avs_error_t err,\n                             void *finished_) {\n    (void) ctx;\n    (void) err;\n\n    printf(\"exchange %s: result %u, response code %d\\n\",\n           AVS_UINT64_AS_STRING(exchange_id.value), result,\n           response ? response->header.code : -1);\n\n    *(bool *) finished_ = true;\n}\n\nint main() {\n    avs_net_socket_t *sock = NULL;\n    avs_sched_t *sched = NULL;\n    avs_shared_buffer_t *in_buf = NULL;\n    avs_shared_buffer_t *out_buf = NULL;\n    avs_coap_ctx_t *ctx = NULL;\n\n    int result = 0;\n\n    avs_log_set_default_level(AVS_LOG_TRACE);\n\n    avs_crypto_prng_ctx_t *prng_ctx = avs_crypto_prng_new(NULL, NULL);\n\n    if (avs_is_err(avs_net_udp_socket_create(&sock, NULL))\n            || !(sched = avs_sched_new(\"sched\", NULL))\n            || !(in_buf = avs_shared_buffer_new(4096))\n            || !(out_buf = avs_shared_buffer_new(4096))) {\n        result = -1;\n    } else if (avs_is_err(avs_net_socket_connect(sock, \"127.0.0.1\", \"5683\"))) {\n        result = -2;\n    } else if (!(ctx = avs_coap_udp_ctx_create(sched,\n                                               &AVS_COAP_DEFAULT_UDP_TX_PARAMS,\n                                               in_buf, out_buf, NULL, prng_ctx))\n               || avs_is_err(avs_coap_ctx_set_socket(ctx, sock))) {\n        result = -1;\n    } else {\n        bool finished = false;\n\n        avs_coap_request_header_t req = {\n            .code = AVS_COAP_CODE_GET\n        };\n        avs_coap_client_send_async_request(ctx, NULL, &req, NULL, NULL,\n                                           response_handler, &finished);\n        while (!finished) {\n            avs_sched_run(sched);\n            sleep(1);\n        }\n    }\n\n    avs_coap_ctx_cleanup(&ctx);\n    avs_free(in_buf);\n    avs_free(out_buf);\n    avs_sched_cleanup(&sched);\n    avs_net_socket_cleanup(&sock);\n    avs_crypto_prng_free(&prng_ctx);\n    return result;\n}\n"
  },
  {
    "path": "deps/avs_coap/include_public/avsystem/coap/async.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef AVSYSTEM_COAP_ASYNC_H\n#define AVSYSTEM_COAP_ASYNC_H\n\n#include <avsystem/coap/avs_coap_config.h>\n\n#include <stdint.h>\n\n#include <avsystem/coap/async_client.h>\n#include <avsystem/coap/async_exchange.h>\n#include <avsystem/coap/async_server.h>\n#include <avsystem/coap/ctx.h>\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n/**\n * Receives as much data from the socket associated with @p ctx as possible to\n * receive in a non-blocking way, and handles it as appropriate.\n *\n * [TCP] If received data is not a complete CoAP message or if it doesn't\n * contain at least a part of payload, this function does nothing except of\n * buffering these data internally. It is considered as successfull call if\n * there is enough space in the buffer.\n *\n * If the packet is recognized as part of a known ongoing exchange, such\n * message is handled internally without calling @p handle_request . Otherwise,\n * incoming message is passed to @p handle_request .\n *\n * This function should be called every time when user detect new data arrived\n * on the socket assigned to @p ctx .\n *\n * When this function calls the receive method on the socket, the receive\n * timeout is always set to zero. If you wish to perform a blocking receive\n * operation, please use <c>poll()</c> or a similar system API first.\n *\n * @param ctx                     CoAP context associated with the socket to\n *                                receive the message from.\n *\n * @param[in]  handle_request     Callback used to handle incoming requests. May\n *                                be NULL, in which case it will only handle\n *                                responses to asynchronous requests and ignore\n *                                incoming requests.\n *\n * @param[in]  handle_request_arg An opaque argument passed to\n *                                @p handle_request .\n *\n * @returns <c>AVS_OK</c> for success, or an error condition for which the\n *          operation failed.\n */\navs_error_t avs_coap_async_handle_incoming_packet(\n        avs_coap_ctx_t *ctx,\n        avs_coap_server_new_async_request_handler_t *handle_request,\n        void *handle_request_arg);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif // AVSYSTEM_COAP_ASYNC_H\n"
  },
  {
    "path": "deps/avs_coap/include_public/avsystem/coap/async_client.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef AVSYSTEM_COAP_ASYNC_CLIENT_H\n#define AVSYSTEM_COAP_ASYNC_CLIENT_H\n\n#include <avsystem/coap/avs_coap_config.h>\n\n#include <stdint.h>\n\n#include <avsystem/coap/async_exchange.h>\n#include <avsystem/coap/ctx.h>\n#include <avsystem/coap/writer.h>\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n/** Response to an asynchronous request. */\ntypedef struct {\n    /** Object that contains response code and options. */\n    avs_coap_response_header_t header;\n\n    /** Offset of the payload within a full response payload. */\n    size_t payload_offset;\n\n    /** Pointer to the response payload. Never NULL. */\n    const void *payload;\n\n    /**\n     * Number of bytes available to read from\n     * @ref avs_coap_client_async_response_t#payload .\n     */\n    size_t payload_size;\n} avs_coap_client_async_response_t;\n\ntypedef enum {\n    /**\n     * Reception of the async request was acknowledged by the remote host. Full\n     * response payload was received.\n     */\n    AVS_COAP_CLIENT_REQUEST_OK,\n\n    /**\n     * A response was received, but the available payload is not complete yet.\n     *\n     * This may mean a BLOCK [UDP] or BERT [TCP] download is in progress, and\n     * there is still more data to be requested. In such case, a sequence of\n     * @ref AVS_COAP_CLIENT_REQUEST_PARTIAL_CONTENT calls will yield sequential\n     * chunks of data, and will be followed by @ref AVS_COAP_CLIENT_REQUEST_OK\n     * call, which means \"this is the last block of data being downloaded\".\n     */\n    AVS_COAP_CLIENT_REQUEST_PARTIAL_CONTENT,\n\n    /**\n     * The library was unable to successfully deliver the request.\n     *\n     * - [UDP] All retransmissions were sent, but no response was received\n     *   on time (either because of a timeout or a network layer error).\n     *\n     * - [UDP] A Reset response to a request was received.\n     *\n     * - [TCP] No response was received in time defined during creation of\n     *   CoAP/TCP context.\n     */\n    AVS_COAP_CLIENT_REQUEST_FAIL,\n\n    /**\n     * The application requests cancellation of the exchange, either\n     * explicitly (@ref avs_coap_exchange_cancel) or by deleting the CoAP\n     * context.\n     */\n    AVS_COAP_CLIENT_REQUEST_CANCEL\n} avs_coap_client_request_state_t;\n\n/**\n * @param[in] exchange_id ID of the asynchronous request this function is\n *                        being called for.\n *\n * @param[in] result      The result of the asynchronous packet exchange, as\n *                        established by the library.\n *\n * @param[in] response    Asynchronous message response. Not NULL only when\n *                        @p result is AVS_COAP_CLIENT_REQUEST_OK or\n *                        AVS_COAP_CLIENT_REQUEST_PARTIAL_CONTENT.\n *\n * @param[in] err         Specific error code for which delivering the request\n *                        failed. Valid only if @p result is\n *                        AVS_COAP_CLIENT_REQUEST_FAIL.\n *\n * @param[in] arg         Opaque user-defined data.\n */\ntypedef void avs_coap_client_async_response_handler_t(\n        avs_coap_ctx_t *ctx,\n        avs_coap_exchange_id_t exchange_id,\n        avs_coap_client_request_state_t result,\n        const avs_coap_client_async_response_t *response,\n        avs_error_t err,\n        void *arg);\n\n/**\n * Sends a request in an asynchronous way.\n *\n * @param ctx                  CoAP context to use for determining the request\n *                             recipient.\n *\n * @param[out] out_exchange_id On success, set to an ID that may be used to\n *                             identify a specific asynchronous request,\n *                             or to AVS_COAP_EXCHANGE_ID_INVALID if\n *                             no response is expected.\n *\n * @param req                  Request to send. If its options include a\n *                             BLOCK2 option (BLOCK [UDP] / BERT [TCP]),\n *                             it is assumed that the request is a continuation\n *                             of a partially complete download, and the\n *                             response will yield all payload chunks starting\n *                             from the one indicated by the BLOCK2 option.\n *\n *                             In case the intention was to download just a\n *                             single block of data, the @p response_handler\n *                             should cancel the exchange using\n *                             @ref avs_coap_exchange_cancel .\n *                             to avoid downloading following blocks.\n *\n *                             NOTE: A deep-copy of this parameter is made,\n *                             meaning that one may safely free any resources\n *                             associated with @p req when this function\n *                             returns.\n *\n * @param request_writer       Function to call when the library is ready to\n *                             send a chunk of payload data. See\n *                             @ref avs_coap_payload_writer_t for details.\n *\n * @param request_writer_arg   An opaque argument passed to @p request_writer\n *\n * @param response_handler     Function to call when the request is delivered\n *                             (and the remote host provides some kind of\n *                             response) or an error occurs. May be NULL (see\n *                             notes).\n *\n *                             IMPORTANT: The operation of receiving a response\n *                             is realized by @ref\n *                             avs_coap_async_handle_incoming_packet , refer to\n *                             its documentation for more details.\n *\n * @param response_handler_arg An opaque argument passed to\n *                             @p response_handler .\n *\n * @returns\n *  - <c>AVS_OK</c> for success\n *  - <c>avs_errno(AVS_EINVAL)</c> if an invalid header has been passed\n *  - <c>avs_errno(AVS_ENOMEM)</c> for an out-of-memory condition\n *  - error code cause by network communication error\n *\n * In case of an error, @p response_handler is NEVER called.\n *\n * Notes:\n *\n * - [UDP] Retransmissions for unconfirmed requests are sent by the scheduler\n *   associated with @p ctx .\n *\n * - If @p response_handler is NULL, the packet is considered nonessential. For\n *   unreliable transports, this means @p ctx will not attempt to retransmit\n *   such packets.\n *\n *   - [UDP] the packet will be sent as Non-Confirmable request. Any response\n *     to such packet will be ignored.\n *   - [UDP] in this case, Block-wise transfers are not supported. If request\n *     payload does not fit in a single datagram, this function fails.\n *   - [TCP] it's guaranteed by TCP stack that message will arrive, but any\n *     response to such packet will be ignored.\n *\n * - If @p response_handler is not NULL and @p payload needs to be split into\n *   multiple message exchanges, the handler is called whenever the server\n *   acknowledges the entire request, or when an error happens.\n *\n *   - [UDP] The library will attempt to send consecutive BLOCK packets\n *     sequentially, waiting for a confirmation after sending each one. The\n *     request is considered delivered when the server responds with code other\n *     than 2.31 Continue.\n */\navs_error_t avs_coap_client_send_async_request(\n        avs_coap_ctx_t *ctx,\n        avs_coap_exchange_id_t *out_exchange_id,\n        const avs_coap_request_header_t *req,\n        avs_coap_payload_writer_t *request_writer,\n        void *request_writer_arg,\n        avs_coap_client_async_response_handler_t *response_handler,\n        void *response_handler_arg);\n\n/**\n * Changes the offset of the remote resource that the user wants to receive the\n * next response data chunk from.\n *\n * This function is only intended to be called from within an implementation\n * of @ref avs_coap_client_async_response_handler_t, or immediately after a\n * successful call to @ref avs_coap_client_send_async_request (before executing\n * any subsequent scheduler jobs).\n *\n * The offset can only be moved forward relative to the last known starting\n * offset. Attempting to set it to an offset of byte that was either already\n * received in a previously finished call to\n * @ref avs_coap_client_async_response_handler_t during this exchange, or is\n * smaller than an offset already passed to this function, will result in an\n * error.\n *\n * When called from within @ref avs_coap_client_async_response_handler_t, it is\n * permitted to set @p next_response_payload_offset to a position that lies\n * within the <c>response-&gt;payload</c> buffer passed to it (but further than\n * the current offset). If a position within the buffer is passed, the response\n * handler will be called again with a portion of the same buffer, starting at\n * the desired offset.\n *\n * If this function is never called during a call to\n * @ref avs_coap_client_async_response_handler_t, the pointer is implicitly\n * moved by the whole size of the buffer passed to it.\n *\n * As an additional exception, when called immediately after\n * @ref avs_coap_client_send_async_request, it is permitted to specify\n * @p next_response_payload_offset equal to zero. This is treated as a no-op.\n *\n * It is guaranteed that the next response chunk passed to the user code will\n * either start exactly on @p next_response_payload_offset, be empty (in case if\n * EOF is before the requested offset) or NULL (if no content is received from\n * the server).\n *\n * @param ctx                          CoAP context to operate on.\n *\n * @param exchange_id                  ID of the exchange to operate on. Note\n *                                     that passing an exchange other than the\n *                                     one currently handled may result in\n *                                     unexpected behavior.\n *\n * @param next_response_payload_offset Response payload offset to set.\n *\n * @returns\n *  - <c>AVS_OK</c> for success\n *  - <c>avs_errno(AVS_ENOENT)</c> if @p exchange_id is not an ID of existing\n *    client exchange\n *  - <c>avs_errno(AVS_EINVAL)</c> if @p next_response_payload_offset is smaller\n *    than the currently recognized value\n */\navs_error_t avs_coap_client_set_next_response_payload_offset(\n        avs_coap_ctx_t *ctx,\n        avs_coap_exchange_id_t exchange_id,\n        size_t next_response_payload_offset);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif // AVSYSTEM_COAP_ASYNC_CLIENT_H\n"
  },
  {
    "path": "deps/avs_coap/include_public/avsystem/coap/async_exchange.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef AVSYSTEM_COAP_ASYNC_EXCHANGE_H\n#define AVSYSTEM_COAP_ASYNC_EXCHANGE_H\n\n#include <avsystem/coap/avs_coap_config.h>\n\n#include <stdint.h>\n\n#include <avsystem/coap/ctx.h>\n#include <avsystem/coap/writer.h>\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n/**\n * An ID used to uniquely identify an asynchronous request within CoAP context.\n */\ntypedef struct {\n    uint64_t value;\n} avs_coap_exchange_id_t;\n\n/**\n * Placeholder exchange ID that is guaranteed <em>not</em> to identify any\n * exchange existing at any point of time.\n */\nstatic const avs_coap_exchange_id_t AVS_COAP_EXCHANGE_ID_INVALID = { 0 };\n\nstatic inline bool avs_coap_exchange_id_equal(avs_coap_exchange_id_t a,\n                                              avs_coap_exchange_id_t b) {\n    return a.value == b.value;\n}\n\nstatic inline bool avs_coap_exchange_id_valid(avs_coap_exchange_id_t id) {\n    return !avs_coap_exchange_id_equal(id, AVS_COAP_EXCHANGE_ID_INVALID);\n}\n\n/**\n * Releases all memory associated with not-yet-delivered request.\n * If the exchange is a request and <c>response_handler</c> has been set to\n * non-NULL when creating it, it is called with\n * @ref AVS_COAP_CLIENT_REQUEST_CANCEL .\n *\n * @param ctx         CoAP context to operate on.\n *\n * @param exchange_id ID of the undelivered request that should be canceled.\n *                    If the request was already delivered or represents a\n *                    request not known by the @p ctx, nothing happens.\n */\nvoid avs_coap_exchange_cancel(avs_coap_ctx_t *ctx,\n                              avs_coap_exchange_id_t exchange_id);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif // AVSYSTEM_COAP_ASYNC_EXCHANGE_H\n"
  },
  {
    "path": "deps/avs_coap/include_public/avsystem/coap/async_server.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef AVSYSTEM_COAP_ASYNC_SERVER_H\n#define AVSYSTEM_COAP_ASYNC_SERVER_H\n\n#include <avsystem/coap/avs_coap_config.h>\n\n#include <stdint.h>\n\n#include <avsystem/coap/async_exchange.h>\n#include <avsystem/coap/ctx.h>\n#include <avsystem/coap/observe.h>\n#include <avsystem/coap/writer.h>\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n/**\n * Internal CoAP context types.\n *\n * Note: these types exist only to ensure correct function call flow, i.e.:\n *\n * avs_coap_async_handle_incoming_packet\n * '-> handle_new_request (avs_coap_server_new_async_request_handler_t)\n *     '-> avs_coap_server_accept_async_request\n *         '-> handle_request (avs_coap_server_async_request_handler_t)\n *             '-> avs_coap_server_setup_async_response\n *\n * It does not seem to make much sense to \"accept a request\" if there is none;\n * or to send a response if we aren't processing any request.\n */\ntypedef struct avs_coap_server_ctx avs_coap_server_ctx_t;\ntypedef struct avs_coap_request_ctx avs_coap_request_ctx_t;\n\ntypedef struct {\n    /** Object that contains request code and options. */\n    avs_coap_request_header_t header;\n\n    /** Offset of the payload within a full request payload. */\n    size_t payload_offset;\n\n    /** Pointer to the request payload. Never NULL. */\n    const void *payload;\n\n    /**\n     * Number of bytes available to read from\n     * @ref avs_coap_server_async_request_t#payload .\n     */\n    size_t payload_size;\n} avs_coap_server_async_request_t;\n\ntypedef enum {\n    /** Non-final request block received. */\n    AVS_COAP_SERVER_REQUEST_PARTIAL_CONTENT,\n\n    /**\n     * Full request was received: either a non-block one, or the last block of\n     * a multi-block request. There will be no more payload blocks, but the\n     * @ref avs_coap_server_async_request_handler_t will be called again with\n     * @ref AVS_COAP_SERVER_REQUEST_CLEANUP state.\n     */\n    AVS_COAP_SERVER_REQUEST_RECEIVED,\n\n    /**\n     * Used in one of following cases:\n     * * BLOCK request is incomplete, but no new blocks recevied for enough\n     *   time to consider the request aborted,\n     * * Request exchange handling finished,\n     * * Exchange canceled on user request (by @ref avs_coap_exchange_cancel\n     *   or the context is being cleaned up).\n     *\n     * The handler will not be called any more.\n     */\n    AVS_COAP_SERVER_REQUEST_CLEANUP,\n} avs_coap_server_request_state_t;\n\n/**\n * This handler is always called with @p state equal to\n * @ref AVS_COAP_SERVER_REQUEST_CLEANUP at the end of exchange.\n *\n * @param ctx        Pointer to request context. NULL if @p state is\n *                   @ref AVS_COAP_SERVER_REQUEST_CLEANUP .\n *\n * @param request_id ID that uniquely identifies incoming request.\n *\n * @param state      Reason to call this handler.\n *\n * @param request    Received request data.\n *\n * @param observe_id If not NULL, indicates that the incoming request\n *                   establishes a CoAP Observe. In such case, it should\n *                   be passed to the @ref avs_coap_observe_async_start\n *                   function <strong>before starting to generate response\n *                   payload</strong>. Not calling\n *                   @ref avs_coap_observe_async_start will cause the request\n *                   to be interpreted as plain GET.\n *\n * @param arg        Opaque argument, as passed to\n *                   @ref avs_coap_async_handle_incoming_packet\n *\n * @returns:\n *\n *   @li If @p state was @ref AVS_COAP_SERVER_REQUEST_PARTIAL_CONTENT or\n *       @ref AVS_COAP_SERVER_REQUEST_RECEIVED :\n *\n *       @li If one of @ref avs_coap_code_constants valid for responses is\n *           returned, a proper message is set up with this code and no payload.\n *           Response set up by calling\n *           @ref avs_coap_server_setup_async_response in handler is ignored.\n *\n *       @li Otherwise, if the return value is nonzero, an Internal Server\n *           Error response is sent with no payload. Response set up by calling\n *           @ref avs_coap_server_setup_async_response in handler is ignored.\n *\n *       @li Otherwise, if return value is 0 and\n *           @ref avs_coap_server_setup_async_response was called, that response\n *           is sent.\n *\n *       @li Otherwise, if @p state was\n *           @ref AVS_COAP_SERVER_REQUEST_PARTIAL_CONTENT and return value is 0\n *           and message wasn't set up, a 2.31 Continue response is sent if\n *           neccessary.\n *\n *       @li Otherwise, if @p state was @ref AVS_COAP_SERVER_REQUEST_RECEIVED,\n *           return value is 0 and message wasn't set up, then Internal Server\n *           Error is sent.\n *\n *       If @p state was @ref AVS_COAP_SERVER_REQUEST_RECEIVED, response isn't\n *       set up and return value is 0, then this handler will be called again\n *       with more request payload chunks.\n *\n *   @li Otherwise (if @p state was @ref AVS_COAP_SERVER_REQUEST_CLEANUP) the\n *       return value is ignored. No message will be sent and the exchange will\n *       be terminated. This handler will not be called again.\n */\ntypedef int avs_coap_server_async_request_handler_t(\n        avs_coap_request_ctx_t *ctx,\n        avs_coap_exchange_id_t request_id,\n        avs_coap_server_request_state_t state,\n        const avs_coap_server_async_request_t *request,\n        const avs_coap_observe_id_t *observe_id,\n        void *arg);\n\n/**\n * Creates an exchange object representing a single request handled by the\n * server.\n *\n * @param ctx\n *\n * @param request_handler     Callback that should be used to handle request\n *                            payload.\n *\n * @param request_handler_arg Opaque argument that is going to be passed to\n *                            @p request_handler .\n *\n * @returns ID of created exchange object that may later be used to identify it,\n *          or @ref AVS_COAP_EXCHANGE_ID_INVALID in case of error.\n */\navs_coap_exchange_id_t avs_coap_server_accept_async_request(\n        avs_coap_server_ctx_t *ctx,\n        avs_coap_server_async_request_handler_t *request_handler,\n        void *request_handler_arg);\n\n/**\n * Called from @ref avs_coap_async_handle_incoming_packet whenever a new\n * request is received.\n *\n * If the request is going to be handled,\n * @ref avs_coap_server_accept_async_request shall be called.\n *\n * @param ctx\n *\n * @param request Received CoAP request.\n *\n * @param arg     Opaque argument, as passed to\n *                @ref avs_coap_async_handle_incoming_packet .\n *\n * @returns:\n *\n *   @li 0 if the application is willing to handle the request. Note: if\n *       @ref avs_coap_server_accept_async_request was not called, an Internal\n *       Server Error is sent to the client.\n *\n *   @li One of @ref avs_coap_code_constants related to response to be sent to\n *       the client otherwise. If this value is neither a client or server\n *       error, an Internal Server Error response is sent instead.\n */\ntypedef int avs_coap_server_new_async_request_handler_t(\n        avs_coap_server_ctx_t *ctx,\n        const avs_coap_request_header_t *request,\n        void *arg);\n\n/**\n * Sets up a response that should be sent in response to a request being\n * currently handled.\n *\n * @param ctx\n *\n * @param response            Header of the response to use.\n *\n * @param response_writer     Function to call when library is ready to send a\n *                            chunk of payload data. See\n *                            @ref avs_coap_payload_writer_t for details.\n *\n * @param response_writer_arg An opaque argument passed to @p response_writer .\n *\n * @returns\n *  - <c>AVS_OK</c> for success\n *  - <c>avs_errno(AVS_EINVAL)</c> if an invalid header has been passed\n *  - <c>avs_errno(AVS_ENOMEM)</c> for an out-of-memory condition\n *\n * TODO: should calling this function outside\n * @ref avs_coap_server_async_request_handler_t be allowed? That could allow us\n * to implement Separate Responses for UDP, and asynchronous out-of-order\n * responses for both UDP and TCP - if only we had the ability to prevent the\n * library from sending a response after\n * @ref avs_coap_server_async_request_handler_t returns.\n */\navs_error_t\navs_coap_server_setup_async_response(avs_coap_request_ctx_t *ctx,\n                                     const avs_coap_response_header_t *response,\n                                     avs_coap_payload_writer_t *response_writer,\n                                     void *response_writer_arg);\n\n#ifdef WITH_AVS_COAP_OBSERVE\n/**\n * Informs the CoAP context that an observation request was accepted and the\n * user will send resource value updates with @ref avs_coap_notify_async or\n * @ref avs_coap_notify_streaming .\n *\n * Should only be used along with @ref avs_coap_server_setup_async_response ,\n * or when the return value of @ref avs_coap_server_async_request_handler_t\n * is one of AVS_COAP_CODE_* constants representing a success.\n *\n * Not fulfilling that condition results in immediate cancellation of\n * established observation after @ref avs_coap_server_async_request_handler_t\n * returns.\n *\n * If an observation with the same @p id already exists, it is canceled and\n * replaced with a new observation.\n *\n * @param ctx            CoAP request context associated with the Observe\n *                       request.\n *\n * @param id             Observation ID, as passed to\n *                       @ref avs_coap_server_async_request_handler_t .\n *\n * @param cancel_handler Optional user-defined handler to be called whenever\n *                       the observation is canceled for any reason.\n *\n *                       After a successful call to this function,\n *                       @p cancel_handler is guaranteed to be called at some\n *                       point.\n *\n * @param handler_arg    Opaque argument to pass to @p cancel_handler.\n *\n * @returns\n *  - <c>AVS_OK</c> for success\n *  - <c>avs_errno(AVS_EINVAL)</c> if an invalid @p ctx has been passed\n *  - <c>avs_errno(AVS_ENOMEM)</c> for an out-of-memory condition\n *  - @ref AVS_COAP_ERR_FEATURE_DISABLED if Observe support is not available in\n *    this build of the library\n */\navs_error_t\navs_coap_observe_async_start(avs_coap_request_ctx_t *ctx,\n                             avs_coap_observe_id_t id,\n                             avs_coap_observe_cancel_handler_t *cancel_handler,\n                             void *handler_arg);\n#endif // WITH_AVS_COAP_OBSERVE\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif // AVSYSTEM_COAP_ASYNC_SERVER_H\n"
  },
  {
    "path": "deps/avs_coap/include_public/avsystem/coap/avs_coap_config.h.in",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef AVS_COAP_CONFIG_H\n#define AVS_COAP_CONFIG_H\n\n/**\n * @file avs_coap_config.h\n *\n * avs_coap library configuration.\n *\n * The preferred way to compile avs_coap is to use CMake, in which case this\n * file will be generated automatically by CMake.\n *\n * However, to provide compatibility with various build systems used especially\n * by embedded platforms, it is alternatively supported to compile avs_coap by\n * other means, in which case this file will need to be provided manually.\n *\n * <strong>NOTE: To compile this library without using CMake, you need to\n * configure avs_commons first. Please refer to documentation in the\n * <c>avs_commons_config.h</c> file (provided in the repository as\n * <c>avs_commons_config.h.in</c>) for details.</strong>\n *\n * <strong>avs_coap requires the following avs_commons components to be\n * enabled:</strong>\n *\n * - @c avs_buffer\n * - @c avs_compat_threading\n * - @c avs_list\n * - @c avs_net\n * - @c avs_sched\n * - @c avs_stream\n * - @c avs_utils\n * - @c avs_log (if @c WITH_AVS_COAP_LOGS is enabled)\n * - @c avs_persistence (if @c WITH_AVS_COAP_OBSERVE_PERSISTENCE is enabled)\n * - @c avs_crypto (if @c WITH_AVS_COAP_OSCORE is enabled)\n *\n * In the repository, this file is provided as <c>avs_coap_config.h.in</c>,\n * intended to be processed by CMake. If editing this file manually, please copy\n * or rename it to <c>avs_coap_config.h</c> and for each of the\n * <c>\\#cmakedefine</c> directives, please either replace it with regular\n * <c>\\#define</c> to enable it, or comment it out to disable. You may also need\n * to replace variables wrapped in <c>\\@</c> signs with concrete values. Please\n * refer to the comments above each of the specific definition for details.\n *\n * If you are editing a file previously generated by CMake, these\n * <c>\\#cmakedefine</c>s will be already replaced by either <c>\\#define</c> or\n * commented out <c>\\#undef</c> directives.\n */\n\n/**\n * Enable support for block-wise transfers (RFC 7959).\n *\n * If this flag is disabled, attempting to send a message bigger than the\n * internal buffer will fail; incoming messages may still carry BLOCK1 or BLOCK2\n * options, but they will not be interpreted by the library in any way.\n */\n#cmakedefine WITH_AVS_COAP_BLOCK\n\n/**\n * Enable support for observations (RFC 7641).\n */\n#cmakedefine WITH_AVS_COAP_OBSERVE\n\n/**\n * Turn on cancelling observation on a timeout.\n *\n * Only meaningful if <c>WITH_AVS_COAP_OBSERVE</c> is enabled.\n *\n * NOTE: LwM2M specification requires LwM2M server to send Cancel Observation\n * request. Meanwhile CoAP RFC 7641 states that timeout on notification should\n * cancel it. This setting is to enable both of these behaviors with default\n * focused on keeping observations in case of bad connectivity.\n */\n#cmakedefine WITH_AVS_COAP_OBSERVE_CANCEL_ON_TIMEOUT\n\n/**\n * Force cancelling observation, even if a confirmable notification yielding\n * an error response is not acknowledged or rejected with RST by the observer.\n *\n * This is a circumvention for some non-compliant servers that respond with an\n * RST message to a confirmable notification yielding an error response. This\n * setting makes the library cancel the observation in such cases, even though\n * the notification is formally rejected. Additionally, it will also make the\n * library cancel the observation if no response is received at all.\n */\n#cmakedefine WITH_AVS_COAP_OBSERVE_FORCE_CANCEL_ON_UNACKED_ERROR\n\n/**\n * Enable support for observation persistence (<c>avs_coap_observe_persist()</c>\n * and <c>avs_coap_observe_restore()</c> calls).\n *\n * Only meaningful if <c>WITH_AVS_COAP_OBSERVE</c> is enabled.\n */\n#cmakedefine WITH_AVS_COAP_OBSERVE_PERSISTENCE\n\n/**\n * Enable support for the streaming API\n */\n#cmakedefine WITH_AVS_COAP_STREAMING_API\n\n/**\n * Enable support for UDP transport.\n *\n * NOTE: Enabling at least one transport is necessary for the library to be\n * useful.\n */\n#cmakedefine WITH_AVS_COAP_UDP\n\n/**\n * Enable support for TCP transport (RFC 8323).\n *\n * NOTE: Enabling at least one transport is necessary for the library to be\n * useful.\n */\n#cmakedefine WITH_AVS_COAP_TCP\n\n/**\n * Enable support for OSCORE (RFC 8613).\n *\n * IMPORTANT: Only available with the OSCORE feature. Ignored in the open\n * source version.\n */\n#cmakedefine WITH_AVS_COAP_OSCORE\n\n/**\n * Use OSCORE version from draft-ietf-core-object-security-08 instead of the\n * final version (RFC 8613).\n *\n * Only meaningful if <c>WITH_AVS_COAP_OSCORE</c> is enabled.\n *\n * IMPORTANT: Only available with the OSCORE feature. Ignored in the open\n * source version.\n */\n#cmakedefine WITH_AVS_COAP_OSCORE_DRAFT_8\n\n/**\n * Maximum size in bytes supported for the Master Secret.\n *\n * If editing this file manually, set it to a positive integer literal.\n *\n * The default value defined in CMake build scripts is 32.\n *\n * IMPORTANT: Only available with the OSCORE feature. Ignored in the open\n * source version.\n */\n#cmakedefine AVS_COAP_OSCORE_MASTER_SECRET_SIZE @AVS_COAP_OSCORE_MASTER_SECRET_SIZE@\n\n/**\n * Maximum size in bytes supported for the Master Salt.\n *\n * If editing this file manually, set it to a positive integer literal.\n *\n * The default value defined in CMake build scripts is 16.\n *\n * IMPORTANT: Only available with the OSCORE feature. Ignored in the open\n * source version.\n */\n#cmakedefine AVS_COAP_OSCORE_MASTER_SALT_SIZE @AVS_COAP_OSCORE_MASTER_SALT_SIZE@\n\n/**\n * Maximum number of notification tokens stored to match Reset responses to.\n *\n * Only meaningful if <c>WITH_AVS_COAP_OBSERVE</c> and <c>WITH_AVS_COAP_UDP</c>\n * are enabled.\n *\n * If editing this file manually, <c>@COAP_UDP_NOTIFY_CACHE_SIZE@</c> shall be\n * replaced with a positive integer literal. The default value defined in CMake\n * build scripts is 4.\n */\n#define AVS_COAP_UDP_NOTIFY_CACHE_SIZE @COAP_UDP_NOTIFY_CACHE_SIZE@\n\n/**\n * Enable sending diagnostic payload in error responses.\n */\n#cmakedefine WITH_AVS_COAP_DIAGNOSTIC_MESSAGES\n\n/**\n * Enable logging in avs_coap.\n *\n * If this flag is disabled, no logging is compiled into the binary at all.\n */\n#cmakedefine WITH_AVS_COAP_LOGS\n\n/**\n * Enable TRACE-level logs in avs_coap.\n *\n * Only meaningful if <c>WITH_AVS_COAP_LOGS</c> is enabled.\n */\n#cmakedefine WITH_AVS_COAP_TRACE_LOGS\n\n/**\n * Enable poisoning of unwanted symbols when compiling avs_coap.\n *\n * Requires a compiler that supports <c>\\#pragma GCC poison</c>.\n *\n * This is mostly useful during development, to ensure that avs_commons do not\n * attempt to call functions considered harmful in this library, such as printf.\n * This is not guaranteed to work as intended on every platform.\n */\n#cmakedefine WITH_AVS_COAP_POISONING\n\n#endif // AVS_COAP_CONFIG_H\n"
  },
  {
    "path": "deps/avs_coap/include_public/avsystem/coap/coap.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef AVSYSTEM_COAP_H\n#define AVSYSTEM_COAP_H\n\n#include <avsystem/coap/avs_coap_config.h>\n\n/**\n * The general idea is to provide a hybrid blocking (stream-based) + async API\n * so that we can extract most of CoAP handling logic into commons without\n * throwing the whole Anjay upside down.\n *\n * Brief overview:\n *\n * - streaming API:\n *\n *   - typedefs:\n *\n *     - @ref avs_coap_streaming_writer_t - callback used to pass\n *       payload to outgoing streaming requests, see\n *       @ref avs_coap_streaming_send_request .\n *\n *     - @ref avs_coap_streaming_request_handler_t - callback used to handle an\n *       incoming request, see @ref avs_coap_async_handle_incoming_packet .\n *\n *   - functions:\n *\n *     - @ref avs_coap_streaming_send_request - sends a (possibly BLOCK-wise)\n *       request.\n *\n *     - @ref avs_coap_async_handle_incoming_packet - receives a packet from a\n *       socket, calls @ref avs_coap_streaming_request_handler_t if it's an\n *       incoming request (note: this function is common to streaming and\n *       async APIs).\n *\n * - async API:\n *\n *   - typedefs:\n *\n *     - @ref avs_coap_client_async_response_handler_t - handler called after\n *       async request delivery is confirmed and a response was received.\n *\n *   - functions:\n *\n *     - @ref avs_coap_client_send_async_request - sends an asynchronous request\n *       and possibly registers a function to be called when a response is\n *       received.\n *\n *     - @ref avs_coap_exchange_cancel - cancels an asynchronous request if it's\n *       still in progress.\n *\n *     - @ref avs_coap_async_handle_incoming_packet - receives a packet from a\n *       socket, calls appropriate @ref avs_coap_client_async_response_handler_t\n *       if a delivery confirmation was received (note: this function is common\n *       to streaming and async APIs).\n *\n * - notification API:\n *\n *   - typedefs:\n *\n *     - @ref avs_coap_observe_cancel_handler_t - handler called whenever the\n *       remote endpoint cancels an observation.\n *\n *   - functions:\n *\n *      - @ref avs_coap_observe_streaming_start - function that may be called\n *        from within @ref avs_coap_streaming_request_handler_t to indicate that\n *        an Observe request was established and the user will proceed to send\n *        notifications.\n *\n *      - @ref avs_coap_observe_async_start - function that may be called from\n *        within @ref avs_coap_server_async_request_handler_t to indicate that\n *        an Observe request was established and the user will proceed to send\n *        notifications.\n *\n *      - @ref avs_coap_notify_streaming and\n *        @ref avs_coap_notify_async - functions that may be called at\n *        any time after an observation is established to send a notification.\n *\n *\n * The API is supposed to be independent from the underlying transport and\n * expose only common parts of CoAP:\n *\n * - message code,\n * - message token,\n * - options,\n * - payload.\n *\n * Transport-specific details are abstracted away:\n *\n * - UDP:\n *   - message ID,\n *   - message type (CON/NON/ACK/RST),\n *   - retransmissions,\n *   - BLOCK\n *\n * - TCP:\n *   - Signaling options, including Capabilities and Settings Messages (CSM)\n *   - Block-wise transfers over Reliable Transports (BERT)\n */\n\n#include <avsystem/coap/async.h>\n#include <avsystem/coap/code.h>\n#include <avsystem/coap/ctx.h>\n#include <avsystem/coap/observe.h>\n#include <avsystem/coap/option.h>\n#include <avsystem/coap/streaming.h>\n#include <avsystem/coap/tcp.h>\n#include <avsystem/coap/token.h>\n#include <avsystem/coap/udp.h>\n\n#endif // AVSYSTEM_COAP_H\n"
  },
  {
    "path": "deps/avs_coap/include_public/avsystem/coap/code.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef AVSYSTEM_COAP_CODE_H\n#define AVSYSTEM_COAP_CODE_H\n\n#include <avsystem/coap/avs_coap_config.h>\n\n#include <stdbool.h>\n#include <stdint.h>\n\n#include <avsystem/commons/avs_defs.h>\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n/**\n * Internal macros used for constructing/parsing CoAP codes.\n * @{\n */\n#define _AVS_COAP_CODE_CLASS_MASK 0xE0\n#define _AVS_COAP_CODE_CLASS_SHIFT 5\n#define _AVS_COAP_CODE_DETAIL_MASK 0x1F\n#define _AVS_COAP_CODE_DETAIL_SHIFT 0\n\n#define AVS_COAP_CODE(cls, detail)                                       \\\n    ((((cls) << _AVS_COAP_CODE_CLASS_SHIFT) & _AVS_COAP_CODE_CLASS_MASK) \\\n     | (((detail) << _AVS_COAP_CODE_DETAIL_SHIFT)                        \\\n        & _AVS_COAP_CODE_DETAIL_MASK))\n/** @} */\n\n/**\n * @anchor avs_coap_code_constants\n * @name avs_coap_code_constants\n *\n * CoAP code constants, as defined in RFC7252/RFC7959.\n *\n * For detailed description of their semantics, refer to appropriate RFCs.\n * @{\n */\n// clang-format off\n#define AVS_COAP_CODE_EMPTY  AVS_COAP_CODE(0, 0)\n\n#define AVS_COAP_CODE_GET    AVS_COAP_CODE(0, 1)\n#define AVS_COAP_CODE_POST   AVS_COAP_CODE(0, 2)\n#define AVS_COAP_CODE_PUT    AVS_COAP_CODE(0, 3)\n#define AVS_COAP_CODE_DELETE AVS_COAP_CODE(0, 4)\n/** https://tools.ietf.org/html/rfc8132#section-4 */\n#define AVS_COAP_CODE_FETCH  AVS_COAP_CODE(0, 5)\n#define AVS_COAP_CODE_PATCH  AVS_COAP_CODE(0, 6)\n#define AVS_COAP_CODE_IPATCH AVS_COAP_CODE(0, 7)\n\n#define AVS_COAP_CODE_CREATED  AVS_COAP_CODE(2, 1)\n#define AVS_COAP_CODE_DELETED  AVS_COAP_CODE(2, 2)\n#define AVS_COAP_CODE_VALID    AVS_COAP_CODE(2, 3)\n#define AVS_COAP_CODE_CHANGED  AVS_COAP_CODE(2, 4)\n#define AVS_COAP_CODE_CONTENT  AVS_COAP_CODE(2, 5)\n#define AVS_COAP_CODE_CONTINUE AVS_COAP_CODE(2, 31)\n\n#define AVS_COAP_CODE_BAD_REQUEST                AVS_COAP_CODE(4, 0)\n#define AVS_COAP_CODE_UNAUTHORIZED               AVS_COAP_CODE(4, 1)\n#define AVS_COAP_CODE_BAD_OPTION                 AVS_COAP_CODE(4, 2)\n#define AVS_COAP_CODE_FORBIDDEN                  AVS_COAP_CODE(4, 3)\n#define AVS_COAP_CODE_NOT_FOUND                  AVS_COAP_CODE(4, 4)\n#define AVS_COAP_CODE_METHOD_NOT_ALLOWED         AVS_COAP_CODE(4, 5)\n#define AVS_COAP_CODE_NOT_ACCEPTABLE             AVS_COAP_CODE(4, 6)\n#define AVS_COAP_CODE_REQUEST_ENTITY_INCOMPLETE  AVS_COAP_CODE(4, 8)\n#define AVS_COAP_CODE_PRECONDITION_FAILED        AVS_COAP_CODE(4, 12)\n#define AVS_COAP_CODE_REQUEST_ENTITY_TOO_LARGE   AVS_COAP_CODE(4, 13)\n#define AVS_COAP_CODE_UNSUPPORTED_CONTENT_FORMAT AVS_COAP_CODE(4, 15)\n\n#define AVS_COAP_CODE_INTERNAL_SERVER_ERROR  AVS_COAP_CODE(5, 0)\n#define AVS_COAP_CODE_NOT_IMPLEMENTED        AVS_COAP_CODE(5, 1)\n#define AVS_COAP_CODE_BAD_GATEWAY            AVS_COAP_CODE(5, 2)\n#define AVS_COAP_CODE_SERVICE_UNAVAILABLE    AVS_COAP_CODE(5, 3)\n#define AVS_COAP_CODE_GATEWAY_TIMEOUT        AVS_COAP_CODE(5, 4)\n#define AVS_COAP_CODE_PROXYING_NOT_SUPPORTED AVS_COAP_CODE(5, 5)\n// clang-format on\n/** @} */\n\n/**\n * CoAP code class accessor. See RFC7252 for details.\n */\nuint8_t avs_coap_code_get_class(uint8_t code);\n\n/**\n * CoAP code detail accessor. See RFC7252 for details.\n */\nuint8_t avs_coap_code_get_detail(uint8_t code);\n\n/**\n * @returns true if @p code belongs to a \"client error\" code class,\n *          false otherwise.\n */\nbool avs_coap_code_is_client_error(uint8_t code);\n\n/**\n * @returns true if @p code belongs to a \"server error\" code class,\n *          false otherwise.\n */\nbool avs_coap_code_is_server_error(uint8_t code);\n\n/**\n * @returns true if @p code is either a \"server error\" or a \"client error\"\n */\nstatic inline bool avs_coap_code_is_error(uint8_t code) {\n    return avs_coap_code_is_server_error(code)\n           || avs_coap_code_is_client_error(code);\n}\n\n/**\n * @returns true if @p code belongs to a \"success\" code class, false otherwise.\n */\nbool avs_coap_code_is_success(uint8_t code);\n\n/**\n * @returns true if @p code represents a request, false otherwise.\n *          Note: Empty (0.00) is NOT considered a request. See RFC7252\n *          for details.\n */\nbool avs_coap_code_is_request(uint8_t code);\n\n/** @returns true if @p code represents a response, false otherwise. */\nbool avs_coap_code_is_response(uint8_t code);\n\n/**\n * Converts CoAP code to a human-readable form.\n *\n * @param code     CoAP code to convert.\n * @param buf      Buffer to store the code string in.\n * @param buf_size @p buf capacity, in bytes.\n *\n * @returns @p buf on success, a pointer to a implementation-defined constant\n *          string if @p code is unknown or @p buf is too small.\n */\nconst char *avs_coap_code_to_string(uint8_t code, char *buf, size_t buf_size);\n\n/**\n * Convenience macro that calls @ref avs_coap_code_to_string with\n * a stack-allocated buffer big enough to store any CoAP code string.\n */\n#define AVS_COAP_CODE_STRING(Code) \\\n    avs_coap_code_to_string((Code), &(char[32]){ 0 }[0], 32)\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif // AVSYSTEM_COAP_CODE_H\n"
  },
  {
    "path": "deps/avs_coap/include_public/avsystem/coap/ctx.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef AVSYSTEM_COAP_CTX_H\n#define AVSYSTEM_COAP_CTX_H\n\n#include <avsystem/coap/avs_coap_config.h>\n\n#include <avsystem/commons/avs_sched.h>\n#include <avsystem/commons/avs_socket.h>\n\n#include <avsystem/coap/option.h>\n#include <avsystem/coap/token.h>\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\ntypedef struct {\n    /**\n     * Number of retransmitted messages. For CoAP/TCP it's always 0.\n     */\n    uint32_t outgoing_retransmissions_count;\n\n    /**\n     * Number of incoming retransmissions. For CoAP/TCP it's always 0.\n     */\n    uint32_t incoming_retransmissions_count;\n} avs_coap_stats_t;\n\ntypedef struct avs_coap_request_header {\n    /**\n     * Request code. See @ref AVS_COAP_CODE or @ref avs_coap_code_constants .\n     *\n     * NOTE: only 0.xx codes other than 0.00 are allowed on requests.\n     */\n    uint8_t code;\n\n    /** Request options. See @ref avs_coap_options_add . */\n    avs_coap_options_t options;\n} avs_coap_request_header_t;\n\ntypedef struct avs_coap_response_header {\n    /**\n     * Response code. See @ref AVS_COAP_CODE or @ref avs_coap_code_constants .\n     *\n     * NOTE: only 2.xx/4.xx/5.xx codes are allowed on responses.\n     */\n    uint8_t code;\n\n    /** Response options. See @ref avs_coap_options_add . */\n    avs_coap_options_t options;\n} avs_coap_response_header_t;\n\n/**\n * CoAP context object.\n *\n * The context must be able to associate async packets with some remote\n * endpoint to know where to send packets. For UDP, we could in theory use a\n * single socket for all CoAP traffic (which would require some DTLS session\n * state shenanigans); for TCP we're unable to do such thing though.\n *\n * The easiest way to achieve multi-protocol support is to have a separate\n * socket object for each remote endpoint, and to associate a separate CoAP\n * context with each one.\n *\n * Despite being tied to a specific socket, the context *does not* own the\n * socket it uses, and *does not* manage the socket connection in any way.\n */\ntypedef struct avs_coap_ctx avs_coap_ctx_t;\n\n/**\n * Associates the socket with @p ctx .\n *\n * In case of an error, all CoAP context state remains untouched -- except the\n * context error, which is set by this function on failure.\n *\n * NOTE [TCP]: This function will block until Capabilities and Settings Message\n * will be sent and peer's CSM will be received. This function will wait for\n * peer's CSM until request timeout defined during creation of TCP context will\n * pass.\n *\n * NOTE: This function can be used once per context entire lifetime. It is\n * either implicitly called by an appropriate context constructor, or by the\n * user.\n *\n * @param ctx    CoAP context object to modify.\n * @param socket New (and not yet used to send / receive data) socket to be\n *               associated with @p ctx .\n *\n *               The context does not take ownership of the socket. Passed\n *               socket object MUST outlive the context, i.e. @p socket MUST\n *               be valid until @ref avs_coap_ctx_cleanup is called on @p ctx .\n *\n *               The socket MUST be in connected state.\n *\n * @returns <c>AVS_OK</c> for success, or an error condition for which the\n *          operation failed.\n */\navs_error_t avs_coap_ctx_set_socket(avs_coap_ctx_t *ctx,\n                                    avs_net_socket_t *socket);\n\n/**\n * @returns true if socket was already set with @ref avs_coap_ctx_set_socket(),\n * false otherwise.\n */\nbool avs_coap_ctx_has_socket(avs_coap_ctx_t *ctx);\n\n/**\n * Frees all resources associated with @p ctx .\n *\n * Calls <c>response_handler</c> with @ref AVS_COAP_CLIENT_REQUEST_CANCEL result\n * for all unconfirmed asynchronous requests associated with the context.\n *\n * @param ctx Pointer to the CoAP context to delete. After the call to this\n *            function, <c>*ctx</c> is set to NULL.\n *\n * Note: because the context object does not own the socket it is associated\n * with, the socket is not affected by a call to this function.\n */\nvoid avs_coap_ctx_cleanup(avs_coap_ctx_t **ctx);\n\n/**\n * Calculates the maximum transport-specific message payload size able to be\n * received in a single CoAP message given the expected @p options set and\n * @p message_code .\n *\n * This function can be used to plan an asynchronous BLOCK-wise request, to make\n * sure the response would fit into internal receive buffer.\n *\n * For example, one can provide the function with a worst-case option set\n * expected to be received from the peer. Then, one can take the maximum\n * power of two that is less than or equal the value returned to be the BLOCK2\n * option size.\n *\n * NOTE: It is fine to not use this function, if one accepts the additional\n * network level overhead of BLOCK-wise renegotiations taking place underneath,\n * or if one can't predict in any way the response size from the peer.\n *\n * @param ctx          CoAP context to operate on.\n * @param options      Option set expected to be received.\n * @param message_code Expected CoAP code to be received in message.\n */\nsize_t avs_coap_max_incoming_message_payload(avs_coap_ctx_t *ctx,\n                                             const avs_coap_options_t *options,\n                                             uint8_t message_code);\n\n/**\n * Default maximal time of the CoAP exchange.\n *\n * This is currently equal to 5 minutes.\n */\nextern const avs_time_duration_t AVS_COAP_DEFAULT_EXCHANGE_MAX_TIME;\n\n/**\n * Maximal time of the coap exchange getter.\n *\n * @param ctx CoAP context to operate on.\n *\n * @returns Maximal CoAP exchange time.\n */\navs_time_duration_t avs_coap_get_exchange_max_time(avs_coap_ctx_t *ctx);\n\n/**\n * This function can be used to set the maximal time of the coap exchange.\n *\n * @param ctx  CoAP context to operate on.\n * @param time New maximal CoAP exchange time.\n */\nvoid avs_coap_set_exchange_max_time(avs_coap_ctx_t *ctx,\n                                    const avs_time_duration_t time);\n\n/**\n * <c>avs_error_t</c> category for values of type @ref avs_coap_error_t.\n */\n#define AVS_COAP_ERR_CATEGORY 22627 // 'acoap' on phone keypad\n\n/**\n * Suggested recovery action after encountering a specific kind of error.\n */\ntypedef enum avs_coap_error_recovery_action {\n    /** CoAP context is still usable. No recovery action is required. */\n    AVS_COAP_ERR_RECOVERY_NONE = 0x1000,\n    /**\n     * CoAP context needs to be recreated to be useful again. If the underlying\n     * socket needs to keep any kind of state (e.g. TCP, or even DTLS over UDP),\n     * its state is indeterminate. Recreating it (or at least reconnecting) is\n     * most probably necessary.\n     */\n    AVS_COAP_ERR_RECOVERY_RECREATE_CONTEXT = 0x2000,\n    /**\n     * The error happened on a different layer than avs_coap, it is unclear\n     * whether the context may still be used.\n     */\n    AVS_COAP_ERR_RECOVERY_UNKNOWN = 0x3000\n} avs_coap_error_recovery_action_t;\n\n/**\n * General classes of avs_coap errors, indicating whether the error is fatal or\n * not, and what should be done in order to mitigate the error.\n */\ntypedef enum avs_coap_error_class {\n    /**\n     * Recoverable errors caused by data received from the remote endpoint.\n     * The CoAP context is still usable, user code is free to ignore these\n     * errors.\n     */\n    AVS_COAP_ERR_CLASS_INPUT_RECOVERABLE = AVS_COAP_ERR_RECOVERY_NONE | 0x000,\n    /**\n     * Recoverable errors caused by unexpected/improper API usage. Correcting\n     * them requires user code modification. The library may not behave in the\n     * way user expected, but the CoAP context is still usable. User code is\n     * free to ignore these errors.\n     */\n    AVS_COAP_ERR_CLASS_BUG_USER = AVS_COAP_ERR_RECOVERY_NONE | 0x100,\n    /**\n     * Recoverable errors caused by resource limitations, unexpected OS API\n     * behavior, or unimplemented/disabled features. The CoAP context is still\n     * usable, but fixing the root cause requires a change in the user or\n     * library code, or even the hardware itself.\n     */\n    AVS_COAP_ERR_CLASS_RUNTIME = AVS_COAP_ERR_RECOVERY_NONE | 0x200,\n    /**\n     * Unrecoverable errors caused by data received from the remote endpoint.\n     * The CoAP context is unusable and should be destroyed.\n     */\n    AVS_COAP_ERR_CLASS_INPUT_FATAL =\n            AVS_COAP_ERR_RECOVERY_RECREATE_CONTEXT | 0x000,\n    /**\n     * Unrecoverable errors caused by implementation bugs. Correcting them\n     * requires fixing avs_coap code. CoAP context becomes unusable, any attempt\n     * to recover must involve recreating it anew.\n     */\n    AVS_COAP_ERR_CLASS_BUG_LIBRARY =\n            AVS_COAP_ERR_RECOVERY_RECREATE_CONTEXT | 0x100,\n    /**\n     * Errors that may or may not be severe; more information is necessary\n     * to determine the correct course of action (e.g. inspecting underlying\n     * socket errno)\n     */\n    AVS_COAP_ERR_CLASS_OTHER = AVS_COAP_ERR_RECOVERY_UNKNOWN | 0x000\n} avs_coap_error_class_t;\n\ntypedef enum {\n    // AVS_COAP_ERR_CLASS_INPUT_RECOVERABLE class errors.\n    /**\n     * Received a CoAP/UDP Reset response to sent message. Remote host refuses\n     * to accept the message, retransmitting it further is pointless. In case\n     * of Observe notifications, Reset response implies cancelling the\n     * observation.\n     */\n    AVS_COAP_ERR_UDP_RESET_RECEIVED = AVS_COAP_ERR_CLASS_INPUT_RECOVERABLE,\n    /** Data could not be parsed as valid CoAP message. */\n    AVS_COAP_ERR_MALFORMED_MESSAGE,\n    /**\n     * Data contains a valid CoAP header, but the data that follows it\n     * (options list/payload marker) is malformed.\n     */\n    AVS_COAP_ERR_MALFORMED_OPTIONS,\n    /**\n     * Remote endpoint requested sending request payload in blocks larger than\n     * before.\n     */\n    AVS_COAP_ERR_BLOCK_SIZE_RENEGOTIATION_INVALID,\n    /** Received a truncated message. */\n    AVS_COAP_ERR_TRUNCATED_MESSAGE_RECEIVED,\n    /**\n     * BLOCK sequence number overflowed as a result of block size\n     * renegotiation. Block transfer cannot be continued.\n     */\n    AVS_COAP_ERR_BLOCK_SEQ_NUM_OVERFLOW,\n    /**\n     * Received ETag option is different than expected, indicating that\n     * download continuation is impossible.\n     */\n    AVS_COAP_ERR_ETAG_MISMATCH,\n    /** Received 2.31 Continue response when it was not expected. */\n    AVS_COAP_ERR_UNEXPECTED_CONTINUE_RESPONSE,\n    /**\n     * Exchange timed out. This may mean either:\n     * - all retransmissions of a confirmable message were sent but no reply\n     *   was received on time,\n     * - remote client started a BLOCK-wise request but later stopped sending\n     *   requests for further blocks of data.\n     */\n    AVS_COAP_ERR_TIMEOUT,\n    /**\n     * Message received over streaming transport is incomplete. It is expected\n     * to be finished on subsequent recv() call, but the socket does not report\n     * any data available.\n     */\n    AVS_COAP_ERR_MORE_DATA_REQUIRED,\n    /** Incoming message doesn't contain an OSCORE option. */\n    AVS_COAP_ERR_OSCORE_OPTION_MISSING,\n\n    // AVS_COAP_ERR_CLASS_BUG_USER class errors.\n    /**\n     * User requested an operation that requires large buffer space while the\n     * shared message buffer associated with the context is already in use.\n     * This may happen e.g. when requesting to receive a message while another\n     * one is being processed, or to send a message while another is being\n     * constructed.\n     */\n    AVS_COAP_ERR_SHARED_BUFFER_IN_USE = AVS_COAP_ERR_CLASS_BUG_USER,\n    /** Attempted to set a socket on a context that already has one. */\n    AVS_COAP_ERR_SOCKET_ALREADY_SET,\n    /** User-defined payload_writer failed. Message could not be constructed. */\n    AVS_COAP_ERR_PAYLOAD_WRITER_FAILED,\n\n    // AVS_COAP_ERR_CLASS_RUNTIME class errors.\n    /**\n     * A message could not be constructed because either the internal buffer or\n     * socket MTU is too small; or incoming message is too large to fit in\n     * internal buffer.\n     */\n    AVS_COAP_ERR_MESSAGE_TOO_BIG = AVS_COAP_ERR_CLASS_RUNTIME,\n    /**\n     * Calculated time/duration invalid, possibly as a result of dubious\n     * retransmission parameters set for the context, or system clock broken.\n     */\n    AVS_COAP_ERR_TIME_INVALID,\n    /** Feature not implemented by the library. */\n    AVS_COAP_ERR_NOT_IMPLEMENTED,\n    /** Operation support disabled at compile-time. */\n    AVS_COAP_ERR_FEATURE_DISABLED,\n    /** Data created in OSCORE context is too big. */\n    AVS_COAP_ERR_OSCORE_DATA_TOO_BIG,\n    /** Error caused by PRNG failure. */\n    AVS_COAP_ERR_PRNG_FAIL,\n\n    // AVS_COAP_ERR_CLASS_INPUT_FATAL class errors\n    /** CoAP/TCP: Abort message was sent because of an unrecoverable failure. */\n    AVS_COAP_ERR_TCP_ABORT_SENT = AVS_COAP_ERR_CLASS_INPUT_FATAL,\n    /** CoAP/TCP: Abort message was received. */\n    AVS_COAP_ERR_TCP_ABORT_RECEIVED,\n    /** CoAP/TCP: Release message was received. */\n    AVS_COAP_ERR_TCP_RELEASE_RECEIVED,\n    /** CoAP/TCP: CSM message not received when expected. */\n    AVS_COAP_ERR_TCP_CSM_NOT_RECEIVED,\n    /**\n     * CoAP/TCP: unable to parse incoming CSM because of malformed options list.\n     */\n    AVS_COAP_ERR_TCP_MALFORMED_CSM_OPTIONS_RECEIVED,\n    /** CoAP/TCP: unsupported \"critical\" class CSM option received. */\n    AVS_COAP_ERR_TCP_UNKNOWN_CSM_CRITICAL_OPTION_RECEIVED,\n    /** TCP connection closed by peer. */\n    AVS_COAP_ERR_TCP_CONN_CLOSED,\n    /**\n     * OSCORE security context is outdated, because too many messages have been\n     * sent using current keys. New parameters must be established.\n     */\n    AVS_COAP_ERR_OSCORE_NEEDS_RECREATE,\n\n    // AVS_COAP_ERR_CLASS_BUG_LIBRARY class errors.\n    /** Assertion failure in release mode. */\n    AVS_COAP_ERR_ASSERT_FAILED = AVS_COAP_ERR_CLASS_BUG_LIBRARY,\n\n    /** User handler canceled an exchange the CoAP context was operating on. */\n    AVS_COAP_ERR_EXCHANGE_CANCELED = AVS_COAP_ERR_CLASS_OTHER\n} avs_coap_error_t;\n\nstatic inline avs_coap_error_class_t avs_coap_error_class(avs_error_t err) {\n    if (avs_is_ok(err) || err.category != AVS_COAP_ERR_CATEGORY) {\n        return AVS_COAP_ERR_CLASS_OTHER;\n    } else {\n        static const uint16_t ERROR_CLASS_MASK = 0xff00;\n        return (avs_coap_error_class_t) (err.code & ERROR_CLASS_MASK);\n    }\n}\n\nstatic inline avs_coap_error_recovery_action_t\navs_coap_error_recovery_action(avs_error_t err) {\n    if (avs_is_ok(err)) {\n        return AVS_COAP_ERR_RECOVERY_NONE;\n    } else {\n        static const unsigned RECOVERY_ACTION_MASK = 0xf000;\n        return (avs_coap_error_recovery_action_t) ((unsigned)\n                                                           avs_coap_error_class(\n                                                                   err)\n                                                   & RECOVERY_ACTION_MASK);\n    }\n}\n\n/**\n * Converts an error returned by some avs_coap function into a human-readable\n * string.\n *\n * @param error    The error to report as a string value\n *\n * @param buf      The buffer that may be used to stringify numeric values of an\n *                 unknown error into.\n *\n * @param buf_size Number of bytes available in @p buf .\n *\n * @returns a human-readable string for an error returned by any <c>avs_coap</c>\n *          API. May be either @p buf (if the error is unknown and there is\n *          enough space there) or some statically allocated string.\n */\nconst char *avs_coap_strerror(avs_error_t error, char *buf, size_t buf_size);\n\n/**\n * Wrapper over @ref avs_coap_strerror that creates a temporary stack-allocated\n * buffer for stringifying unknown errors.\n *\n * The pointer returned by this macro is safe to use within the same statement\n * (e.g. as an argument to a logging function).\n */\n#define AVS_COAP_STRERROR(Err) avs_coap_strerror((Err), &(char[64]){ 0 }[0], 64)\n\n/**\n * Getter for statistics of CoAP context. See docs for @ref avs_coap_stats_t\n * for more details.\n *\n * If it's not implemented, returns @ref avs_coap_stats_t filled with zeros.\n *\n * @param ctx       Context to get statistics from.\n */\navs_coap_stats_t avs_coap_get_stats(avs_coap_ctx_t *ctx);\n\n/**\n * A callback that determines whether given option number is appropriate for\n * a message with specific CoAP code.\n *\n * @param msg_code Code of the CoAP message.\n * @param optnum   Option number to check. This will always be a number\n *                 referring to a critical option (as defined in RFC7252).\n *\n * @returns Should return true if the option is acceptable, false otherwise.\n */\ntypedef bool avs_coap_critical_option_validator_t(uint8_t msg_code,\n                                                  uint32_t optnum);\n\n/**\n * Checks whether critical options from @p msg are valid. BLOCK1 and BLOCK2\n * options are handled internally, other options need to be checked\n * by @p validator.\n *\n * @param request_header CoAP Message header to validation options in.\n * @param validator      Callback that checks validity of a critical option.\n *                       Must not be NULL.\n *\n * @returns 0 if all critical options are considered valid, a negative value\n *          otherwise.\n */\nint avs_coap_options_validate_critical(\n        const avs_coap_request_header_t *request_header,\n        avs_coap_critical_option_validator_t validator);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif // AVSYSTEM_COAP_CTX_H\n"
  },
  {
    "path": "deps/avs_coap/include_public/avsystem/coap/observe.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef AVSYSTEM_COAP_OBSERVE_H\n#define AVSYSTEM_COAP_OBSERVE_H\n\n#include <avsystem/coap/avs_coap_config.h>\n\n#include <avsystem/coap/async_exchange.h>\n#include <avsystem/coap/ctx.h>\n#include <avsystem/coap/token.h>\n#include <avsystem/coap/writer.h>\n\n#ifdef WITH_AVS_COAP_OBSERVE_PERSISTENCE\n#    include <avsystem/commons/avs_persistence.h>\n#endif // WITH_AVS_COAP_OBSERVE_PERSISTENCE\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n/**\n * ID uniquely identifying an observation.\n *\n * Note: using just the token should be unique enough if we assume separate\n * avs_coap_ctx_t per server.\n */\ntypedef struct {\n    avs_coap_token_t token;\n} avs_coap_observe_id_t;\n\ntypedef enum {\n    /**\n     * The caller does not care if the notification gets delivered successfully\n     * or not. Implementation is free to send it as non-confirmable if such\n     * messages are supported by underlying transport.\n     */\n    AVS_COAP_NOTIFY_PREFER_NON_CONFIRMABLE,\n\n    /**\n     * The caller needs to reliably know if the notification was delivered\n     * successfully or not.\n     */\n    AVS_COAP_NOTIFY_PREFER_CONFIRMABLE,\n} avs_coap_notify_reliability_hint_t;\n\n/**\n * A function called whenever the CoAP context object receives a request for\n * Observe cancellation.\n *\n * @param id Identifier of the cancelled observation.\n *\n * @param arg Opaque user-defined data, as passed to\n *            @ref avs_coap_observe_start.\n *\n * - [UDP] RST response to a Notify may also cancel a notification, not only\n *   GET with Observe=1\n */\ntypedef void avs_coap_observe_cancel_handler_t(avs_coap_observe_id_t id,\n                                               void *arg);\n\n/**\n * A function called whenever the confirmation of notification delivery is\n * received or notification is cancelled and known to be never sent again.\n *\n * @param result Result of sending notification.\n * @param err    AVS_OK if the delivery was successful, or the reason for which\n *               the delivery failed.\n * @param arg    Opaque user-defined data, as passed to\n *               @ref avs_coap_notify_async .\n */\ntypedef void avs_coap_delivery_status_handler_t(avs_coap_ctx_t *ctx,\n                                                avs_error_t err,\n                                                void *arg);\n\n#ifdef WITH_AVS_COAP_OBSERVE_PERSISTENCE\n\n/**\n * Stores Observe information (for the Observe entry as specified by @p id)\n * using @p persistence context.\n *\n * The information can be used later on with new CoAP context using function\n * @ref avs_coap_observe_restore().\n *\n * @param ctx           CoAP context to operate on.\n * @param id            Unique observation ID (request token).\n * @param persistence   Persistence context to operate on.\n *\n * @returns\n *  - <c>AVS_OK</c> for success\n *  - <c>avs_errno(AVS_EINVAL)</c> if there is no observation with such @p id\n *  - @ref AVS_COAP_ERR_NOT_IMPLEMENTED if observation options are too long\n *  - any I/O error forwarded from the underlying stream\n */\navs_error_t avs_coap_observe_persist(avs_coap_ctx_t *ctx,\n                                     avs_coap_observe_id_t id,\n                                     avs_persistence_context_t *persistence);\n\n/**\n * Restores single Observe entry from the specified @p persistence context.\n *\n * Restoring observation with identifier that already exists in given CoAP\n * context, will result in error being returned.\n *\n * IMPORTANT: If CoAP context is already initialized with socket (see @ref\n * avs_coap_ctx_set_socket()), the restore operation is not possible and an\n * error will be returned.\n *\n * NOTE: In case of error, nothing in CoAP context is changed.\n *\n * @param ctx            CoAP context to operate on.\n *\n * @param cancel_handler Optional user-defined handler to be called whenever\n *                       the observation is cancelled for any reason.\n *\n *                       After a successful call to this function,\n *                       @p cancel_handler is guaranteed to be called at some\n *                       point.\n *\n * @param handler_arg    Opaque argument to pass to @p cancel_handler.\n *\n * @param out_id         Pointer to a variable that (if not NULL) will be filled\n *                       with the ID of the restored observation.\n *\n * @param persistence    Persistence context to operate on.\n *\n * @returns\n *  - <c>AVS_OK</c> for success\n *  - <c>avs_errno(AVS_EBADMSG)</c> for malformed stream data\n *  - <c>avs_errno(AVS_ENOMEM)</c> for an out-of-memory condition\n *  - <c>avs_errno(AVS_EINVAL)</c> if the CoAP context is already initialized\n *  - any I/O error forwarded from the underlying stream\n */\navs_error_t avs_coap_observe_restore_with_id(\n        avs_coap_ctx_t *ctx,\n        avs_coap_observe_cancel_handler_t *cancel_handler,\n        void *handler_arg,\n        avs_coap_observe_id_t *out_id,\n        avs_persistence_context_t *persistence);\n\n/**\n * Restores single Observe entry from the specified @p persistence context. This\n * is a version of @ref avs_coap_observe_restore_with_id without the\n * <c>out_id</c> argument declared to maitain backwards compatibility.\n */\nstatic inline avs_error_t\navs_coap_observe_restore(avs_coap_ctx_t *ctx,\n                         avs_coap_observe_cancel_handler_t *cancel_handler,\n                         void *handler_arg,\n                         avs_persistence_context_t *persistence) {\n    return avs_coap_observe_restore_with_id(\n            ctx, cancel_handler, handler_arg, NULL, persistence);\n}\n#endif // WITH_AVS_COAP_OBSERVE_PERSISTENCE\n\n#ifdef WITH_AVS_COAP_OBSERVE\n\n/**\n * Informs the CoAP context that it should establish an observation without\n * explicit client request.\n *\n * This may happen in case of restoring observations from persistent storage.\n * In such case, one MUST make sure that @p req exactly matches the request\n * object used for an original observation. Using different request options\n * than these included in original Observe request are not allowed by CoAP\n * Observe RFC and may cause CoAP clients to react in unexpected ways.\n *\n * Request details passed to this function are copied for later use by\n * notification sending functions. The copy is released whenever the\n * observation gets invalidated.\n *\n * If an observation with the same @p id already exists, it is canceled and\n * replaced with a new observation.\n *\n * @param ctx            CoAP context to operate on.\n *\n * @param id             Unique observation ID (request token).\n *\n * @param req            Header of the original observation request.\n *                       MUST NOT be NULL.\n *\n * @param cancel_handler Optional user-defined handler to be called whenever\n *                       the observation is cancelled for any reason.\n *\n *                       After a successful call to this function,\n *                       @p cancel_handler is guaranteed to be called at some\n *                       point.\n *\n * @param handler_arg    Opaque argument to pass to @p cancel_handler.\n *\n * @returns\n *  - <c>AVS_OK</c> for success\n *  - <c>avs_errno(AVS_EINVAL)</c> if an invalid @p ctx has been passed\n *  - <c>avs_errno(AVS_ENOMEM)</c> for an out-of-memory condition\n *  - @ref AVS_COAP_ERR_FEATURE_DISABLED if Observe support is not available in\n *    this build of the library\n *\n * On failure, any previously established observation with the same @p id is NOT\n * canceled.\n */\navs_error_t\navs_coap_observe_start(avs_coap_ctx_t *ctx,\n                       avs_coap_observe_id_t id,\n                       const avs_coap_request_header_t *req,\n                       avs_coap_observe_cancel_handler_t *cancel_handler,\n                       void *handler_arg);\n\n/**\n * Cancels an observation previously started using @ref avs_coap_observe_start .\n *\n * This function is called automatically by avs_coap when an observe\n * cancellation request is received over the network, or when an attempt to\n * deliver a confirmable notification times out. This function shall be called\n * by the user only when an observation shall be cancelled manually.\n *\n * <strong>IMPORTANT:</strong> The remote endpoint is not notified of the\n * cancellation in any way. Cancelling observations programmatically is likely\n * to violate the RFC 7641 requirements, unless observe state is synchronized\n * through a higher-layer protocol.\n *\n * <strong>NOTE:</strong> If notification exchanges started using\n * @ref avs_coap_notify_async or @ref avs_coap_notify_streaming are still in\n * progress, they will <strong>NOT</strong> be cancelled automatically.\n *\n * <strong>NOTE:</strong> This function will call the <c>cancel_handler</c>\n * originally passed to @ref avs_coap_observe_start .\n *\n * @param ctx CoAP context to operate on.\n *\n * @param id  Unique observation ID (request token).\n *\n * @returns\n *  - <c>AVS_OK</c> for success\n *  - <c>avs_errno(AVS_EINVAL)</c> if an invalid @p ctx has been passed, or\n *    @p id does not refer to any know observation\n */\navs_error_t avs_coap_observe_cancel(avs_coap_ctx_t *ctx,\n                                    avs_coap_observe_id_t id);\n\n/**\n * Sends a CoAP Notification in an asynchronous mode. This function returns\n * immediately.\n *\n * @param ctx                  CoAP context object to operate on. Indicates\n *                             where should the notification be sent.\n *\n * @param[out] out_exchange_id On success, set to an ID that may be used to\n *                             identify a specific asynchronous notification,\n *                             or to AVS_COAP_EXCHANGE_ID_INVALID if\n *                             no response is expected.\n *\n * @param observe_id           Unique observation ID for which a notification\n *                             is being sent.\n *\n * @param response_header      CoAP code and options to include in sent\n *                             notification.\n *\n *                             Note: sending a notification with an error code\n *                             (4.xx or 5.xx) implicitly cancels the\n *                             observation.\n *\n * @param reliability_hint     Indicates whether the implementation should make\n *                             sure to deliver the notification reliably or is\n *                             allowed to use non-reliable messages if\n *                             supported.\n *\n *                             [UDP] @ref AVS_COAP_NOTIFY_PREFER_NON_CONFIRMABLE\n *                             causes the library to use NON message.\n *\n * @param write_payload        A callback used to pass notification payload.\n *                             If NULL, no payload is included in the\n *                             notification.\n *\n * @param write_payload_arg    An opaque argument passed to @p write_payload .\n *\n * @param delivery_handler     Handler called after success or failure of\n *                             sending notification. It is guaranteed to be\n *                             called exactly once, after any @p write_payload\n *                             calls the library may need to do.\n *\n *                             If @p reliability_hint is\n *                             @ref AVS_COAP_NOTIFY_PREFER_NON_CONFIRMABLE , the\n *                             <c>err</c> argument passed to this handler will\n *                             have a non-success value only if there was a\n *                             definite failure. Otherwise, <c>AVS_OK</c> will\n *                             be passed, even if actual success or failure of\n *                             the delivery cannot be determined.\n *\n *                             If @c NULL, @p reliability_hint MUST be set to\n *                             @ref AVS_COAP_NOTIFY_PREFER_NON_CONFIRMABLE, and\n *                             @p write_payload_arg MUST NOT require any\n *                             cleanup.\n *\n * @param delivery_handler_arg An opaque argument passed to @p delivery_handler\n *\n * @returns <c>AVS_OK</c> for success, or an error condition for which the\n *          operation failed.\n *\n *          In case of an error, @p delivery_handler is NEVER called.\n *\n * Notes:\n *\n * - Successful result of this function doesn't guarantee arrival of the\n *   notification. In case reliable delivery is necessary, @p delivery_handler\n *   should be used.\n *\n * - It is not guaranteed that the @p write_payload will be called until the\n *   payload is read to end. If @p write_payload_arg requires any cleanup,\n *   it should be performed in @p delivery_handler .\n */\navs_error_t\navs_coap_notify_async(avs_coap_ctx_t *ctx,\n                      avs_coap_exchange_id_t *out_exchange_id,\n                      avs_coap_observe_id_t observe_id,\n                      const avs_coap_response_header_t *response_header,\n                      avs_coap_notify_reliability_hint_t reliability_hint,\n                      avs_coap_payload_writer_t *write_payload,\n                      void *write_payload_arg,\n                      avs_coap_delivery_status_handler_t *delivery_handler,\n                      void *delivery_handler_arg);\n\n#    ifdef WITH_AVS_COAP_STREAMING_API\n\n/**\n * Sends a Confirmable CoAP Notification in a blocking mode. Does not return\n * until the full notification is sent, or until an error happens.\n *\n * - [UDP] setting @p reliability_hint to\n *   @ref AVS_COAP_NOTIFY_PREFER_NON_CONFIRMABLE causes the library to use NON\n *   message for the first notification block and ACK for following ones. The\n *   function will return immediately after writing the payload finishes and\n *   indicate success if a request for the last payload block was received.\n *\n * - [UDP] setting @p reliability_hint to\n *   @ref AVS_COAP_NOTIFY_PREFER_CONFIRMABLE forces the use of Separate\n *   Responses for *all* notification blocks; the function will then succeed\n *   only after receiving an ACK to the last payload block.\n *\n * - [TCP] the function returns immediately after passing the whole\n *   notification payload to the socket.\n *\n * @param ctx               CoAP context object to operate on. Indicates where\n *                          should the notification be sent to.\n *\n * @param observe_id        Unique observation ID for which a notification is\n *                          being sent.\n *\n * @param response_header   CoAP code and options to include in sent\n *                          notification.\n *\n *                          Note: sending a notification with an error code\n *                          (4.xx or 5.xx) implicitly cancels the observation.\n *\n * @param reliability_hint  Indicates whether the implementation should make\n *                          sure to deliver the notification reliably or is\n *                          allowed to use non-reliable messages if\n *                          supported.\n *\n * @param write_payload     A callback used to pass notification payload.\n *                          If NULL, no payload is included in the notification.\n *\n * @param write_payload_arg An opaque argument passed to @p write_payload .\n *\n * @returns <c>AVS_OK</c> for success, or an error condition for which the\n *          operation failed.\n */\navs_error_t\navs_coap_notify_streaming(avs_coap_ctx_t *ctx,\n                          avs_coap_observe_id_t observe_id,\n                          const avs_coap_response_header_t *response_header,\n                          avs_coap_notify_reliability_hint_t reliability_hint,\n                          avs_coap_streaming_writer_t *write_payload,\n                          void *write_payload_arg);\n\n#    endif // WITH_AVS_COAP_STREAMING_API\n\n#endif // WITH_AVS_COAP_OBSERVE\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif // AVSYSTEM_COAP_OBSERVE_H\n"
  },
  {
    "path": "deps/avs_coap/include_public/avsystem/coap/option.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef AVSYSTEM_COAP_OPTION_H\n#define AVSYSTEM_COAP_OPTION_H\n\n#include <avsystem/coap/avs_coap_config.h>\n\n#include <avsystem/commons/avs_errno.h>\n#include <avsystem/commons/avs_list.h>\n#include <avsystem/commons/avs_utils.h>\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n/**\n * CoAP Content-Formats, as defined in \"Constrained RESTful Environments (CoRE)\n * Parameters\":\n * https://www.iana.org/assignments/core-parameters/core-parameters.xhtml\n * @{\n */\n#define AVS_COAP_FORMAT_PLAINTEXT 0\n#define AVS_COAP_FORMAT_COSE_ENCRYPT0 16\n#define AVS_COAP_FORMAT_COSE_MAC0 17\n#define AVS_COAP_FORMAT_COSE_SIGN1 18\n#define AVS_COAP_FORMAT_LINK_FORMAT 40\n#define AVS_COAP_FORMAT_XML 41\n#define AVS_COAP_FORMAT_OCTET_STREAM 42\n#define AVS_COAP_FORMAT_EXI 47\n#define AVS_COAP_FORMAT_JSON 50\n#define AVS_COAP_FORMAT_JSON_PATCH_JSON 51\n#define AVS_COAP_FORMAT_MERGE_PATCH_JSON 52\n#define AVS_COAP_FORMAT_CBOR 60\n#define AVS_COAP_FORMAT_CWT 61\n#define AVS_COAP_FORMAT_COSE_ENCRYPT 96\n#define AVS_COAP_FORMAT_COSE_MAC 97\n#define AVS_COAP_FORMAT_COSE_SIGN 98\n#define AVS_COAP_FORMAT_COSE_KEY 101\n#define AVS_COAP_FORMAT_COSE_KEY_SET 102\n#define AVS_COAP_FORMAT_SENML_JSON 110\n#define AVS_COAP_FORMAT_SENSML_JSON 111\n#define AVS_COAP_FORMAT_SENML_CBOR 112\n#define AVS_COAP_FORMAT_SENSML_CBOR 113\n#define AVS_COAP_FORMAT_SENML_EXI 114\n#define AVS_COAP_FORMAT_SENSML_EXI 115\n#define AVS_COAP_FORMAT_COAP_GROUP_JSON 256\n#define AVS_COAP_FORMAT_PKCS7_SERVER_GENERATED_KEY 280\n#define AVS_COAP_FORMAT_PKCS7_CERTS_ONLY 281\n#define AVS_COAP_FORMAT_PKCS8 284\n#define AVS_COAP_FORMAT_CSR_ATTRS 285\n#define AVS_COAP_FORMAT_PKCS10 286\n#define AVS_COAP_FORMAT_PKIX_CERT 287\n#define AVS_COAP_FORMAT_SENML_XML 310\n#define AVS_COAP_FORMAT_SENSML_XML 311\n#define AVS_COAP_FORMAT_SENML_ETCH_JSON 320\n#define AVS_COAP_FORMAT_SENML_ETCH_CBOR 322\n#define AVS_COAP_FORMAT_OCF_CBOR 10000\n#define AVS_COAP_FORMAT_OSCORE 10001\n#define AVS_COAP_FORMAT_JSON_DEFLATE 11050\n#define AVS_COAP_FORMAT_CBOR_DEFLATE 11060\n#define AVS_COAP_FORMAT_OMA_LWM2M_TLV 11542\n#define AVS_COAP_FORMAT_OMA_LWM2M_JSON 11543\n#define AVS_COAP_FORMAT_OMA_LWM2M_CBOR 11544\n/** @} */\n\n/**\n * CoAP option numbers, as defined in RFC7252/RFC7641/RFC7959.\n * @{\n */\n#define AVS_COAP_OPTION_IF_MATCH 1\n#define AVS_COAP_OPTION_URI_HOST 3\n#define AVS_COAP_OPTION_ETAG 4\n#define AVS_COAP_OPTION_IF_NONE_MATCH 5\n#define AVS_COAP_OPTION_OBSERVE 6\n#define AVS_COAP_OPTION_URI_PORT 7\n#define AVS_COAP_OPTION_LOCATION_PATH 8\n#define AVS_COAP_OPTION_OSCORE 9\n#define AVS_COAP_OPTION_URI_PATH 11\n#define AVS_COAP_OPTION_CONTENT_FORMAT 12\n#define AVS_COAP_OPTION_MAX_AGE 14\n#define AVS_COAP_OPTION_URI_QUERY 15\n#define AVS_COAP_OPTION_ACCEPT 17\n#define AVS_COAP_OPTION_LOCATION_QUERY 20\n#define AVS_COAP_OPTION_BLOCK2 23\n#define AVS_COAP_OPTION_BLOCK1 27\n#define AVS_COAP_OPTION_PROXY_URI 35\n#define AVS_COAP_OPTION_PROXY_SCHEME 39\n#define AVS_COAP_OPTION_SIZE1 60\n/** @} */\n\n/** Minimum size, in bytes, of a CoAP BLOCK message payload. */\n#define AVS_COAP_BLOCK_MIN_SIZE (1 << 4)\n/** Maximum size, in bytes, of a CoAP BLOCK message payload. */\n#define AVS_COAP_BLOCK_MAX_SIZE (1 << 10)\n/** Maximum value of a BLOCK sequence number (2^20-1) allowed by RFC7959. */\n#define AVS_COAP_BLOCK_MAX_SEQ_NUMBER 0xFFFFF\n\n/**\n * A magic value used to indicate the absence of the Content-Format option.\n * Mainly used during CoAP message parsing, passing it to the opts object does\n * nothing.\n * */\n#define AVS_COAP_FORMAT_NONE UINT16_MAX\n\n/** Maximum size of ETag option, as defined in RFC7252. */\n#define AVS_COAP_MAX_ETAG_LENGTH 8\n\ntypedef struct {\n    uint8_t size;\n    char bytes[AVS_COAP_MAX_ETAG_LENGTH];\n} avs_coap_etag_t;\n\n/**\n * BLOCK1/BLOCK2 option helpers.\n * @{\n */\n\n/**\n * Helper enum used to distinguish BLOCK1 and BLOCK2 transfers in BLOCK APIs.\n */\ntypedef enum {\n    AVS_COAP_BLOCK1, //< Block-wise request\n    AVS_COAP_BLOCK2  //< Block-wise response\n} avs_coap_option_block_type_t;\n\n/**\n * Parsed CoAP BLOCK option.\n *\n * If is_bert is true, size is set to 1024. It doesn't indicate actual payload\n * size, because BERT message may contain multiple blocks of 1024 bytes each.\n * See RFC8323 for more details.\n */\ntypedef struct avs_coap_option_block {\n    avs_coap_option_block_type_t type;\n    uint32_t seq_num;\n    bool has_more;\n    uint16_t size;\n    bool is_bert;\n} avs_coap_option_block_t;\n\n/** @} */\n\n/**\n * Note: this struct MUST be initialized with either\n * @ref avs_coap_options_create_empty or @ref avs_coap_options_dynamic_init\n * before it is used.\n */\ntypedef struct avs_coap_options {\n    void *begin;\n    size_t size;\n    size_t capacity;\n\n    /**\n     * If true, @ref avs_coap_options#begin is a heap-allocated buffer owned\n     * by the options object (allocated using <c>avs_malloc()</c>). This means\n     * avs_coap_options_add_* functions are free to reallocate it as necessary.\n     * In that case, @ref avs_coap_options_cleanup MUST be used to free the\n     * memory.\n     */\n    bool allocated;\n} avs_coap_options_t;\n\n/**\n * @returns true if @p etag1 and @p etag2 are equal, false otherwise.\n */\nstatic inline bool avs_coap_etag_equal(const avs_coap_etag_t *etag1,\n                                       const avs_coap_etag_t *etag2) {\n    return etag1->size == etag2->size\n           && !memcmp(etag1->bytes, etag2->bytes, etag1->size);\n}\n\n/**\n * A structure containing hex representation of a ETag that may be created by\n * @ref avs_coap_etag_hex().\n */\ntypedef struct {\n    char buf[AVS_COAP_MAX_ETAG_LENGTH * 2 + 1];\n} avs_coap_etag_hex_t;\n\nstatic inline const char *avs_coap_etag_hex(avs_coap_etag_hex_t *out_value,\n                                            const avs_coap_etag_t *etag) {\n    assert(etag->size <= 8);\n    if (avs_hexlify(out_value->buf, sizeof(out_value->buf), NULL, etag->bytes,\n                    etag->size)) {\n        AVS_UNREACHABLE(\"avs_hexlify() failed\");\n    }\n    return out_value->buf;\n}\n\n#define AVS_COAP_ETAG_HEX(Etag) \\\n    (avs_coap_etag_hex(&(avs_coap_etag_hex_t) { { 0 } }, (Etag)))\n\n/**\n * @param buffer   Buffer to use for option storage.\n *\n *                 Note: the buffer MUST live at least as long as returned\n *                 options object.\n *\n * @param capacity Number of bytes available in @p buffer.\n *\n * @return An empty options object. It may be filled with CoAP options with\n *         @ref avs_coap_options_add functions.\n */\nstatic inline avs_coap_options_t\navs_coap_options_create_empty(void *buffer, size_t capacity) {\n    avs_coap_options_t opts;\n    opts.begin = buffer;\n    opts.size = 0;\n    opts.capacity = capacity;\n    opts.allocated = false;\n    return opts;\n}\n\n/**\n * Resets @p opts to an empty state, cleaning up memory owned by @p opts if\n * applicable.\n *\n * After this function returns, @p opts should be considered deleted and MUST\n * NOT be used in any avs_coap_options_* call other than\n * @ref avs_coap_options_cleanup .\n */\nstatic inline void avs_coap_options_cleanup(avs_coap_options_t *opts) {\n    if (opts->allocated) {\n        avs_free(opts->begin);\n    }\n\n    opts->begin = NULL;\n    opts->size = 0;\n    opts->capacity = 0;\n    opts->allocated = false;\n}\n\n/**\n * Initializes an @ref avs_coap_options_t object so that it is backed by a\n * buffer allocated with <c>avs_malloc()</c>, and can be resized as required\n * when adding new options.\n *\n * @param opts             Uninitialized options object. Note: this function\n *                         MUST NOT be called on an already initialized object.\n *                         Doing so MAY result in resource leaks.\n *\n * @param initial_capacity Desired initial capacity of the options object.\n *\n * @returns <c>AVS_OK</c> for success, or <c>avs_errno(AVS_ENOMEM)</c> if there\n *          is not enough memory. After this function returns, it is safe to\n *          call @ref avs_coap_options_cleanup on @p opts , regardless of the\n *          initialization result.\n */\nstatic inline avs_error_t\navs_coap_options_dynamic_init_with_size(avs_coap_options_t *opts,\n                                        size_t initial_capacity) {\n    opts->begin = initial_capacity ? avs_malloc(initial_capacity) : NULL;\n    opts->size = 0;\n    opts->capacity = initial_capacity;\n    opts->allocated = true;\n\n    if (initial_capacity && !opts->begin) {\n        avs_coap_options_cleanup(opts);\n        return avs_errno(AVS_ENOMEM);\n    }\n\n    return AVS_OK;\n}\n\n#define AVS_COAP_DYNAMIC_OPTIONS_DEFAULT_SIZE 128\n\n/**\n * Initializes an @ref avs_coap_options_t object with default initial capacity.\n * It's literally an \"overload\" for @ref avs_coap_options_dynamic_init_with_size\n * using @ref AVS_COAP_DYNAMIC_OPTIONS_DEFAULT_SIZE .\n *\n * @param opts Uninitialized options object. Note: this function MUST NOT be\n *             called on an already initialized object. Doing so MAY result in\n *             resource leaks.\n *\n * @returns <c>AVS_OK</c> for success, or <c>avs_errno(AVS_ENOMEM)</c> if there\n *          is not enough memory. After this function returns, it is safe to\n *          call @ref avs_coap_options_cleanup on @p opts , regardless of the\n *          initialization result.\n */\nstatic inline avs_error_t\navs_coap_options_dynamic_init(avs_coap_options_t *opts) {\n    return avs_coap_options_dynamic_init_with_size(\n            opts, AVS_COAP_DYNAMIC_OPTIONS_DEFAULT_SIZE);\n}\n\n/**\n * Removes all options with given @p option_number added to @p opts.\n */\nvoid avs_coap_options_remove_by_number(avs_coap_options_t *opts,\n                                       uint16_t option_number);\n\n/**\n * @anchor avs_coap_options_add\n * @name avs_coap_options_add\n * Functions that may be used to set up CoAP options.\n *\n * @{\n */\n\n/**\n * Sets a Content-Format Option (@ref AVS_COAP_OPTION_CONTENT_FORMAT = 12) in\n * the options list.\n *\n * @param opts    Options object to operate on.\n * @param format  Numeric value of the Content-Format option. May be one of the\n *                AVS_COAP_FORMAT_* contants. Calling this function with\n *                @ref AVS_COAP_FORMAT_NONE removes the Content-Format option.\n *\n * @returns <c>AVS_OK</c> for success, or an error condition for which the\n *          operation failed.\n */\navs_error_t avs_coap_options_set_content_format(avs_coap_options_t *opts,\n                                                uint16_t format);\n\n#ifdef WITH_AVS_COAP_BLOCK\n/**\n * Adds the Block1 or Block2 Option to the message being built.\n *\n * @param opts  Options object to operate on.\n * @param block BLOCK option content to set.\n *\n * @returns <c>AVS_OK</c> for success, or an error condition for which the\n *          operation failed.\n */\navs_error_t avs_coap_options_add_block(avs_coap_options_t *opts,\n                                       const avs_coap_option_block_t *block);\n#endif // WITH_AVS_COAP_BLOCK\n\n#ifdef WITH_AVS_COAP_OBSERVE\n/**\n * Adds the Observe option to @p opts . Options value is encoded as 24 least\n * significant bits of @p value , as defined in RFC7641.\n *\n * @returns <c>AVS_OK</c> for success, or an error condition for which the\n *          operation failed.\n */\navs_error_t avs_coap_options_add_observe(avs_coap_options_t *opts,\n                                         uint32_t value);\n\n/**\n * Gets the Observe option from @p opts .\n *\n * @returns 0 on success, @ref AVS_COAP_OPTION_MISSING if option isn't present,\n *          a negative value if option is malformed (eg. it's longer than 3\n *          bytes).\n */\nint avs_coap_options_get_observe(const avs_coap_options_t *opts,\n                                 uint32_t *out_value);\n#endif // WITH_AVS_COAP_OBSERVE\n\n/**\n * Adds an arbitrary CoAP option with custom value.\n *\n * Repeated calls to this function APPEND additional instances of a CoAP option.\n *\n * @param opts          Options object to operate on.\n * @param opt_number    CoAP Option Number to set.\n * @param opt_data      Option value.\n * @param opt_data_size Number of bytes in the @p opt_data buffer.\n *\n * @returns\n *  - <c>AVS_OK</c> for success\n *  - <c>avs_errno(AVS_ENOMEM)</c> for an out-of-memory condition\n *  - @ref AVS_COAP_ERR_MESSAGE_TOO_BIG if @p opts is not dynamically allocated\n *    and is too small to fit the new option\n */\navs_error_t avs_coap_options_add_opaque(avs_coap_options_t *opts,\n                                        uint16_t opt_number,\n                                        const void *opt_data,\n                                        uint16_t opt_data_size);\n\n/**\n * Equivalent to:\n *\n * @code\n * avs_coap_options_add_opaque(opts, opt_number,\n *                             opt_data, strlen(opt_data))\n * @endcode\n */\navs_error_t avs_coap_options_add_string(avs_coap_options_t *opts,\n                                        uint16_t opt_number,\n                                        const char *opt_data);\n\n/**\n * Adds an option with a printf-style formatted string value.\n *\n * @param opts       Options object to operate on.\n * @param opt_number CoAP Option Number to set.\n * @param format     Format string to pass to snprintf().\n * @param ...        Format arguments.\n *\n * @returns <c>AVS_OK</c> for success, or an error condition for which the\n *          operation failed.\n */\navs_error_t avs_coap_options_add_string_f(avs_coap_options_t *opts,\n                                          uint16_t opt_number,\n                                          const char *format,\n                                          ...) AVS_F_PRINTF(3, 4);\n\n/**\n * va_list variant of @ref avs_coap_options_add_string_f .\n */\navs_error_t avs_coap_options_add_string_fv(avs_coap_options_t *opts,\n                                           uint16_t opt_number,\n                                           const char *format,\n                                           va_list list);\n\n/**\n * Adds an arbitrary CoAP option with no value.\n * See @ref avs_coap_options_add_opaque for more opts.\n */\navs_error_t avs_coap_options_add_empty(avs_coap_options_t *opts,\n                                       uint16_t opt_number);\n\n/**\n * Adds an ETag option.\n *\n * @param opts Options object to operate on.\n * @param etag ETag option to add.\n *\n * @returns\n *  - <c>AVS_OK</c> for success\n *  - <c>avs_errno(AVS_EINVAL)</c> if @p etag is invalid\n *  - <c>avs_errno(AVS_ENOMEM)</c> for an out-of-memory condition\n *  - @ref AVS_COAP_ERR_MESSAGE_TOO_BIG if @p opts is not dynamically allocated\n *    and is too small to fit the new option\n */\navs_error_t avs_coap_options_add_etag(avs_coap_options_t *opts,\n                                      const avs_coap_etag_t *etag);\n\n/**\n * Functions below add an arbitrary CoAP option with an integer value. The value\n * is encoded in the most compact way available, so e.g. for @p value equal to 0\n * the option has no payload when added using any of them.\n *\n * See @ref avs_coap_options_add_opaque for more opts.\n */\navs_error_t avs_coap_options_add_uint(avs_coap_options_t *opts,\n                                      uint16_t opt_number,\n                                      const void *value,\n                                      size_t value_size);\n\nstatic inline avs_error_t avs_coap_options_add_u16(avs_coap_options_t *opts,\n                                                   uint16_t opt_number,\n                                                   uint16_t value) {\n    return avs_coap_options_add_uint(opts, opt_number, &value, sizeof(value));\n}\n\nstatic inline avs_error_t avs_coap_options_add_u32(avs_coap_options_t *opts,\n                                                   uint16_t opt_number,\n                                                   uint32_t value) {\n    return avs_coap_options_add_uint(opts, opt_number, &value, sizeof(value));\n}\n\n/** @} */\n\n/**\n * Iterator object used to access CoAP message options. It is not supposed\n * to be modified by the user after initialization.\n */\ntypedef struct {\n    avs_coap_options_t *opts;\n    void *curr_opt;\n    uint32_t prev_opt_number;\n} avs_coap_option_iterator_t;\n\n/** Empty iterator object initializer. */\nstatic const avs_coap_option_iterator_t AVS_COAP_OPTION_ITERATOR_EMPTY = { NULL,\n                                                                           NULL,\n                                                                           0 };\n\n/**\n * @name avs_coap_options_get\n * Functions that may be used to get CoAP options.\n *\n * @{\n */\n\n/**\n * Constant returned from some of option-retrieving functions, indicating\n * the absence of requested option.\n */\n#define AVS_COAP_OPTION_MISSING 1\n\n/**\n * @param[in]  opts      CoAP options to retrieve Content-Format option from.\n * @param[out] out_value Retrieved value of the Content-Format CoAP option.\n *\n * NOTE: Content-Format Option is not critical, thus only the first one found\n * (if any) will be returned.\n *\n * @returns @li 0 if the Content-Format was successfully retrieved and written\n *              to <c>*out_value</c>, or the option was missing, in which case\n *              <c>*out_value</c> is set to @ref AVS_COAP_FORMAT_NONE ,\n *          @li A negative value if the option was malformed.\n */\nint avs_coap_options_get_content_format(const avs_coap_options_t *opts,\n                                        uint16_t *out_value);\n\n#ifdef WITH_AVS_COAP_BLOCK\n/**\n * Attempts to obtain block info of given block @p type.\n *\n * @param[in]  opts      CoAP options to operate on.\n * @param[in]  type      Type of the BLOCK option to retrieve\n *                       (@ref avs_coap_option_block_type_t)\n * @param[out] out_block @ref avs_coap_option_block_t struct to store parsed\n *                       BLOCK option in.\n *\n * @returns @li 0 if the BLOCK option was successfully retrieved,\n *          @li @ref AVS_COAP_OPTION_MISSING if requested BLOCK option is not\n *              present,\n *          @li -1 in case of error, including cases where the option is\n *              malformed or duplicated.\n */\nint avs_coap_options_get_block(const avs_coap_options_t *opts,\n                               avs_coap_option_block_type_t type,\n                               avs_coap_option_block_t *out_block);\n#endif // WITH_AVS_COAP_BLOCK\n\n/**\n * Skips the option pointed by @p inout_it without reading it.\n *\n * @returns 0 if option was successfully skipped, negative value if there is\n *          nothing to skip. After successfull call, @p inout_it points to the\n *          next option.\n */\nint avs_coap_options_skip_it(avs_coap_option_iterator_t *inout_it);\n\n/**\n * Iterates over CoAP options from @p opts that match given @p option_number ,\n * yielding their values as opaque byte arrays.\n *\n * @param[in]    opts            CoAP options to operate on.\n * @param[in]    option_number   CoAP option number to look for.\n * @param[inout] it              Option iterator object that holds iteration\n *                               state. When starting the iteration, it MUST be\n *                               set with @ref AVS_COAP_OPTION_ITERATOR_EMPTY .\n *                               Points to the next CoAP option after successful\n *                               call.\n * @param[out]   out_option_size Size of the option value. After successful call\n *                               this is equal to number of bytes written to\n *                               @p buffer.\n * @param[out]   buffer          Buffer to put option value into.\n * @param[in]    buffer_size     Number of bytes available in @p buffer .\n *\n * NOTES:\n * - When iterating over options using this function, @p option_number MUST\n *   remain unchanged, otherwise the behavior is undefined.\n * - The iterator state MUST NOT be changed by user code during the iteration.\n *   Doing so causes the behavior of this function to be undefined.\n * - If call isn't successful, function may be called again using the same\n *   @p it and a new @p buffer of size @p out_option_size to read the option\n *   value again. Option may be also skipped by using\n *   @ref avs_coap_options_skip_it .\n *\n * @returns @li 0 on success,\n *          @li AVS_COAP_OPTION_MISSING when there are no more options with\n *              given @p option_number to retrieve,\n *          @li a negative value if @p buffer is not big enough to hold the\n *              option value or.\n */\nint avs_coap_options_get_bytes_it(const avs_coap_options_t *opts,\n                                  uint16_t option_number,\n                                  avs_coap_option_iterator_t *it,\n                                  size_t *out_option_size,\n                                  void *buffer,\n                                  size_t buffer_size);\n\n/**\n * Getter for value of the first occurrence of option with number\n * @p option_number .\n *\n * Works like @ref avs_coap_options_get_bytes_it , but doesn't use iterators\n * to read repeated options, so it shouldn't be used if options are repeatable.\n */\nstatic inline int avs_coap_options_get_bytes(const avs_coap_options_t *opts,\n                                             uint16_t option_number,\n                                             size_t *out_option_size,\n                                             void *buffer,\n                                             size_t buffer_size) {\n    avs_coap_option_iterator_t it = AVS_COAP_OPTION_ITERATOR_EMPTY;\n    return avs_coap_options_get_bytes_it(opts, option_number, &it,\n                                         out_option_size, buffer, buffer_size);\n}\n\n/**\n * Iterates over CoAP options from @p opts that match given @p option_number ,\n * yielding their values as zero-terminated strings.\n *\n * @param[in]    opts            CoAP options to operate on.\n * @param[in]    option_number   CoAP option number to look for.\n * @param[inout] it              Option iterator object that holds iteration\n *                               state. When starting the iteration, it MUST be\n *                               set with @ref AVS_COAP_OPTION_ITERATOR_EMPTY .\n *                               Points to the next CoAP option after successful\n *                               call.\n * @param[out]   out_option_size Size of the option value including terminating\n *                               nullbyte. After successful call this is equal\n *                               to number of bytes written to @p buffer.\n * @param[out]   buffer          Buffer to put option value into.\n * @param[in]    buffer_size     Number of bytes available in @p buffer .\n *\n * NOTES:\n * - When iterating over options using this function, @p option_number MUST\n *   remain unchanged, otherwise the behavior is undefined.\n * - The iterator state MUST NOT be changed by user code during the iteration.\n *   Doing so causes the behavior if this function to be undefined.\n * - If call isn't successful, function may be called again using the same\n *   @p it and a new @p buffer of size @p out_option_size to read the option\n *   value again. Option may be also skipped by using\n *   @ref avs_coap_options_skip_it .\n *\n * @returns @li 0 on success,\n *          @li AVS_COAP_OPTION_MISSING when there are no more options with\n *              given @p option_number to retrieve,\n *          @li a negative value if @p buffer is not big enough to hold the\n *              option value or terminating nullbyte.\n */\nint avs_coap_options_get_string_it(const avs_coap_options_t *opts,\n                                   uint16_t option_number,\n                                   avs_coap_option_iterator_t *it,\n                                   size_t *out_option_size,\n                                   char *buffer,\n                                   size_t buffer_size);\n\n/**\n * Getter for value of the first occurrence of option with number\n * @p option_number .\n *\n * Works like @ref avs_coap_options_get_string_it , but doesn't use iterators\n * to read repeated options, so it shouldn't be used if options are repeatable.\n */\nstatic inline int avs_coap_options_get_string(const avs_coap_options_t *opts,\n                                              uint16_t option_number,\n                                              size_t *out_option_size,\n                                              char *buffer,\n                                              size_t buffer_size) {\n    avs_coap_option_iterator_t it = AVS_COAP_OPTION_ITERATOR_EMPTY;\n    return avs_coap_options_get_string_it(opts, option_number, &it,\n                                          out_option_size, buffer, buffer_size);\n}\n\n/**\n * Finds a unique CoAP option with a 16-bit unsigned integer value.\n *\n * @param[in]  opts          CoAP options to operate on.\n * @param[in]  option_number CoAP option number to find.\n * @param[out] out_value     Pointer to variable to store the option value in.\n *\n * @returns @li 0 if exactly one option with given @p option_number was found,\n *              and its integer value was successfully put into @p out_value,\n *          @li AVS_COAP_OPTION_MISSING if @p opts does not contain any option\n *              with given @p option_number ,\n *          @li a negative value if multiple options with given @p option_number\n *              were found or the option value is too large to be stored in a\n *              16-bit variable\n */\nint avs_coap_options_get_u16(const avs_coap_options_t *opts,\n                             uint16_t option_number,\n                             uint16_t *out_value);\n\n/**\n * Finds a unique CoAP option with a 32-bit unsigned integer value.\n *\n * @param[in]  opts          CoAP options to operate on.\n * @param[in]  option_number CoAP option number to find.\n * @param[out] out_value     Pointer to variable to store the option value in.\n *\n * @returns @li 0 if exactly one option with given @p option_number was found,\n *              and its integer value was successfully put into @p out_value,\n *          @li AVS_COAP_OPTION_MISSING if @p opts does not contain any option\n *              with given @p option_number ,\n *          @li a negative value if multiple options with given @p option_number\n *              were found or the option value is too large to be stored in a\n *              32-bit variable\n */\nint avs_coap_options_get_u32(const avs_coap_options_t *opts,\n                             uint16_t option_number,\n                             uint32_t *out_value);\n\n/**\n * Iterates over ETag options from @p opts .\n *\n * @param[in]    opts     CoAP options to operate on.\n * @param[inout] it       Option iterator object that holds iteration state.\n *                        When starting the iteration, it MUST be\n *                        set with @ref AVS_COAP_OPTION_ITERATOR_EMPTY .\n * @param[out]   out_etag Pointer to store ETag to\n *\n * NOTES:\n * - The iterator state MUST NOT be changed by user code during the iteration.\n *   Doing so causes the behavior of this function to be undefined.\n *\n * @returns @li 0 on success,\n *          @li AVS_COAP_OPTION_MISSING when there are no more ETags,\n *          @li a negative value if ETag is longer than\n *              @ref AVS_COAP_MAX_ETAG_LENGTH .\n *\n * If ETag is missing or malformed, @p out_etag is filled with zeros.\n */\nint avs_coap_options_get_etag_it(const avs_coap_options_t *opts,\n                                 avs_coap_option_iterator_t *it,\n                                 avs_coap_etag_t *out_etag);\n\n/**\n * Getter for the first occurrence of ETag in @p opts .\n *\n * Works like @ref avs_coap_options_get_etag_it , but doesn't use iterators to\n * read repeated options. It shouldn't be used to retrieve ETags from requests,\n * because they might be repeated.\n */\nstatic inline int avs_coap_options_get_etag(const avs_coap_options_t *opts,\n                                            avs_coap_etag_t *out_etag) {\n    avs_coap_option_iterator_t it = AVS_COAP_OPTION_ITERATOR_EMPTY;\n    return avs_coap_options_get_etag_it(opts, &it, out_etag);\n}\n\n/** @} */\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif // AVSYSTEM_COAP_OPTION_H\n"
  },
  {
    "path": "deps/avs_coap/include_public/avsystem/coap/streaming.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef AVSYSTEM_COAP_STREAMING_H\n#define AVSYSTEM_COAP_STREAMING_H\n\n#include <avsystem/coap/avs_coap_config.h>\n\n#include <avsystem/commons/avs_stream.h>\n\n#include <avsystem/coap/ctx.h>\n#include <avsystem/coap/observe.h>\n#include <avsystem/coap/writer.h>\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n/**\n * Internal CoAP context type.\n *\n * Note: this type exists only to ensure correct function call flow, i.e.:\n *\n * avs_coap_streaming_handle_incoming_packet\n * '-> handle_request (avs_coap_streaming_request_handler_t)\n *     '-> avs_coap_streaming_setup_response\n */\ntypedef struct avs_coap_streaming_request_ctx avs_coap_streaming_request_ctx_t;\n\n/**\n * @param[in]  request        Incoming request to handle.\n *\n * @param[in]  payload_stream Stream that may be used to retrieve request\n *                            payload.\n *\n * @param[out] observe_id     If not NULL, indicates that the incoming request\n *                            establishes a CoAP Observe. In such case, it\n *                            should be passed to the\n *                            @ref avs_coap_observe_start function *before\n *                            starting to generate response payload*.\n *                            Not calling @ref avs_coap_observe_start will\n *                            cause the request to be interpreted as plain GET.\n * @param[in]  arg            Opaque user-defined data.\n *\n * @returns @li 0 on success. If @ref avs_coap_streaming_setup_response is\n *              called within the handler, such response is sent to the server.\n *              If it was NOT called by the handler, Internal Server Error is\n *              sent instead.\n *          @li a non-zero value in case of error. In case the returned value\n *              is one of AVS_COAP_CODE_* constants, an appropriate response\n *              will be sent. Otherwise, Internal Server Error response will\n *              be sent.\n */\ntypedef int\navs_coap_streaming_request_handler_t(avs_coap_streaming_request_ctx_t *ctx,\n                                     const avs_coap_request_header_t *request,\n                                     avs_stream_t *payload_stream,\n                                     const avs_coap_observe_id_t *observe_id,\n                                     void *arg);\n\n#ifdef WITH_AVS_COAP_STREAMING_API\n\n/**\n * Sends a CoAP request in a blocking way, returning when a response is\n * received or a network-layer error occurs.\n *\n * @param[in]  ctx                 CoAP context object to operate on. Indicates\n *                                 where should the request be sent to.\n *\n * @param[in]  request             CoAP request to send.\n *\n * @param[in]  write_payload       A callback that may be used to pass request\n *                                 payload. If NULL, a message with empty\n *                                 payload is sent.\n *\n * @param[in]  write_payload_arg   An opaque argument passed to\n *                                 @p write_payload .\n *\n * @param[out] out_response        On success, it is filled with details of\n *                                 received response.\n *\n *                                 MUST NOT be NULL.\n *\n *                                 On success, the caller MUST cleanup the\n *                                 options associated with response header (@p\n *                                 avs_coap_options_cleanup()). On error, it is\n *                                 valid to do so, but not required.\n *\n * @param[out] out_response_stream If not NULL, after a successful execution\n *                                 <c>*out_response_stream</c> is set to a\n *                                 non-NULL stream object that may be used to\n *                                 retrieve the response payload.\n *\n *                                 The stream object is owned by the @p ctx\n *                                 object and MUST NOT be deleted.\n *\n * @returns <c>AVS_OK</c> for success, or an error condition for which the\n *          operation failed.\n *\n * Notes:\n *\n * - Using output methods (e.g. <c>avs_stream_write()</c>) on the payload stream\n *   associated with the returned response object is undefined.\n * - The function may return success even if @p write_payload failed, but some\n *   kind of valid response (e.g. to a partially sent payload) has been\n *   received.\n * - [UDP] Requests are always sent as Confirmable messages.\n * - [UDP] Transparently handles Separate Responses and BLOCK-wise requests\n *   as required. This means the call may block for extended periods of time\n *   in case of severe packet loss or a malicious server.\n */\navs_error_t\navs_coap_streaming_send_request(avs_coap_ctx_t *ctx,\n                                const avs_coap_request_header_t *request,\n                                avs_coap_streaming_writer_t *write_payload,\n                                void *write_payload_arg,\n                                avs_coap_response_header_t *out_response,\n                                avs_stream_t **out_response_stream);\n\n/**\n * Sets up a response that should be sent in response to a previously received\n * request.\n *\n * @param ctx      Context of a request to respond to.\n *\n * @param response Response object to set up.\n *\n * @returns @li A non-NULL stream object that may be used to attach payload to\n *              sent response on success,\n *          @li NULL in case of error.\n */\navs_stream_t *\navs_coap_streaming_setup_response(avs_coap_streaming_request_ctx_t *ctx,\n                                  const avs_coap_response_header_t *response);\n\n/**\n * Receives a CoAP messages from the socket associated with @p ctx and handles\n * them as appropriate.\n *\n * Initially, the receive method on the underlying socket is called with receive\n * timeout set to zero. Subsequent receive requests may block with non-zero\n * timeout values when e.g. waiting for retransmissions or subsequent BLOCK\n * chunks - this is necessary to hide this complexity from the user callbacks in\n * streaming mode.\n *\n * This function may handle more than one request at once, possibly calling\n * @p handle_request multiple times. Upon successful return, it is guaranteed\n * that there is no more data to be received on the socket at the moment.\n *\n * If the packet is recognized as a response to an asynchronous request, such\n * message is handled internally without calling @p handle_request . Otherwise,\n * incoming message is passed to @p handle_request .\n *\n * @param ctx            CoAP context associated with the socket to receive\n *                       the message from.\n *\n * @param handle_request Callback used to handle incoming requests. May be\n *                       NULL, in which case it will only handle responses\n *                       to asynchronous requests and ignore incoming requests.\n *\n * @param handler_arg    An opaque argument passed to @p handle_request .\n *\n * @returns <c>AVS_OK</c> for success, or an error condition for which the\n *          operation failed.\n */\navs_error_t avs_coap_streaming_handle_incoming_packet(\n        avs_coap_ctx_t *ctx,\n        avs_coap_streaming_request_handler_t *handle_request,\n        void *handler_arg);\n\n#    ifdef WITH_AVS_COAP_OBSERVE\n\n/**\n * Informs the CoAP context that an observation request was accepted and the\n * user will send resource value updates with @ref avs_coap_notify_async or\n * @ref avs_coap_notify_streaming .\n *\n * Should only be used along with\n * @ref avs_coap_streaming_setup_response , or when the return value of\n * @ref avs_coap_streaming_request_handler_t is one of <c>AVS_COAP_CODE_*</c>\n * constants representing a success.\n *\n * Not fulfilling that condition results in immediate cancellation of\n * established observation after @ref avs_coap_streaming_request_handler_t\n * returns.\n *\n * If an observation with the same @p id already exists, it is canceled and\n * replaced with a new observation.\n *\n * @param ctx            CoAP request context associated with the Observe\n *                       request.\n *\n * @param id             Observation ID, as passed to\n *                       @ref avs_coap_streaming_request_handler_t .\n *\n * @param cancel_handler Optional user-defined handler to be called whenever\n *                       the observation is canceled for any reason.\n *\n *                       After a successful call to this function,\n *                       @p cancel_handler is guaranteed to be called at some\n *                       point.\n *\n * @param handler_arg    Opaque argument to pass to @p cancel_handler.\n *\n * @returns <c>AVS_OK</c> for success, or an error condition for which the\n *          operation failed. On failure, any previously established observation\n *          with the same @p id is NOT canceled.\n */\navs_error_t avs_coap_observe_streaming_start(\n        avs_coap_streaming_request_ctx_t *ctx,\n        avs_coap_observe_id_t id,\n        avs_coap_observe_cancel_handler_t *cancel_handler,\n        void *handler_arg);\n\n#    endif // WITH_AVS_COAP_OBSERVE\n\n#endif // WITH_AVS_COAP_STREAMING_API\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif // AVSYSTEM_COAP_STREAMING_H\n"
  },
  {
    "path": "deps/avs_coap/include_public/avsystem/coap/tcp.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef AVSYSTEM_COAP_TCP_H\n#define AVSYSTEM_COAP_TCP_H\n\n#include <avsystem/coap/avs_coap_config.h>\n\n#include <avsystem/commons/avs_sched.h>\n#include <avsystem/commons/avs_shared_buffer.h>\n#include <avsystem/commons/avs_socket.h>\n\n#include <avsystem/coap/ctx.h>\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n#ifdef WITH_AVS_COAP_TCP\n\n/**\n * Creates a CoAP/TCP context without associated socket.\n *\n * IMPORTANT: The socket MUST be set via @ref avs_coap_ctx_set_socket() before\n * any operations on the context are performed. Otherwise the behavior is\n * undefined.\n *\n * @param sched           Scheduler object that will be used to detect cases\n *                        where the server does not respond to our request.\n *\n *                        MUST NOT be NULL. Created context object does not take\n *                        ownership of the scheduler, which MUST outlive created\n *                        CoAP context object.\n *\n * @param in_buffer       Pointer to a shared input buffer.\n *\n * @param out_buffer      Pointer to a shared output buffer.\n *\n * @param max_opts_size   Size of buffer which will be allocated to handle\n *                        options. Any message with options longer than\n *                        @p max_opts_size will not be handled and an error\n *                        will be returned from on_data_available method.\n *                        MUST BE equal or greater than\n *                        @ref AVS_COAP_MAX_TOKEN_LENGTH .\n *\n * @param request_timeout Time to wait for incoming response after sending a\n *                        request. After this time request is considered\n *                        unsuccessful and response handler is called with\n *                        result indicating failure.\n *                        Used also as time to wait for initial CSM.\n *\n * @param prng_ctx      PRNG context to use for token generation. MUST NOT be\n *                      @c NULL . MUST outlive the created CoAP context.\n *\n * @returns Created CoAP/TCP context on success, NULL if there isn't enough\n *          memory to create the context or buffer sizes requirements are not\n *          met.\n */\navs_coap_ctx_t *avs_coap_tcp_ctx_create(avs_sched_t *sched,\n                                        avs_shared_buffer_t *in_buffer,\n                                        avs_shared_buffer_t *out_buffer,\n                                        size_t max_opts_size,\n                                        avs_time_duration_t request_timeout,\n                                        avs_crypto_prng_ctx_t *prng_ctx);\n\n#endif // WITH_AVS_COAP_TCP\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif // AVSYSTEM_COAP_TCP_H\n"
  },
  {
    "path": "deps/avs_coap/include_public/avsystem/coap/token.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef AVSYSTEM_COAP_TOKEN_H\n#define AVSYSTEM_COAP_TOKEN_H\n\n#include <avsystem/coap/avs_coap_config.h>\n\n#include <assert.h>\n#include <stdbool.h>\n#include <stdint.h>\n#include <string.h>\n\n#include <avsystem/commons/avs_utils.h>\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n/** Maximum size, in bytes, of a CoAP token allowed by RFC7252. */\n#define AVS_COAP_MAX_TOKEN_LENGTH 8\n\n/** CoAP token object. */\ntypedef struct {\n    uint8_t size;\n    char bytes[AVS_COAP_MAX_TOKEN_LENGTH];\n} avs_coap_token_t;\n\n/** All-zeros CoAP token initializer. */\n#define AVS_COAP_TOKEN_EMPTY ((avs_coap_token_t) { 0 })\n\n/**\n * @returns true if @p first and @p second CoAP tokens are equal,\n *          false otherwise.\n */\nstatic inline bool avs_coap_token_equal(const avs_coap_token_t *first,\n                                        const avs_coap_token_t *second) {\n    return first->size == second->size\n           && !memcmp(first->bytes, second->bytes, first->size);\n}\n\nstatic inline bool avs_coap_token_valid(const avs_coap_token_t *token) {\n    return token->size <= AVS_COAP_MAX_TOKEN_LENGTH;\n}\n\n/**\n * A structure containing hex representation of a token that may be created by\n * @ref avs_coap_token_hex().\n */\ntypedef struct {\n    char buf[AVS_COAP_MAX_TOKEN_LENGTH * 2 + 1];\n} avs_coap_token_hex_t;\n\nstatic inline const char *avs_coap_token_hex(avs_coap_token_hex_t *out_value,\n                                             const avs_coap_token_t *token) {\n    assert(token->size <= 8);\n    if (avs_hexlify(out_value->buf, sizeof(out_value->buf), NULL, token->bytes,\n                    token->size)) {\n        AVS_UNREACHABLE(\"avs_hexlify() failed\");\n    }\n    return out_value->buf;\n}\n\n#define AVS_COAP_TOKEN_HEX(Tok) \\\n    (avs_coap_token_hex(&(avs_coap_token_hex_t) { { 0 } }, (Tok)))\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif // AVSYSTEM_COAP_TOKEN_H\n"
  },
  {
    "path": "deps/avs_coap/include_public/avsystem/coap/udp.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef AVSYSTEM_COAP_UDP_H\n#define AVSYSTEM_COAP_UDP_H\n\n#include <avsystem/coap/avs_coap_config.h>\n\n#include <avsystem/commons/avs_sched.h>\n#include <avsystem/commons/avs_shared_buffer.h>\n#include <avsystem/commons/avs_socket.h>\n\n#include <avsystem/coap/ctx.h>\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n/** CoAP transmission params object. */\ntypedef struct {\n    /** RFC 7252: ACK_TIMEOUT */\n    avs_time_duration_t ack_timeout;\n    /** RFC 7252: ACK_RANDOM_FACTOR */\n    double ack_random_factor;\n    /** RFC 7252: MAX_RETRANSMIT */\n    unsigned max_retransmit;\n    /** RFC 7252: NSTART */\n    size_t nstart;\n} avs_coap_udp_tx_params_t;\n\n/**\n * Fixed-size CoAP UDP response cache object, used to avoid handling requests\n * duplicates.\n *\n * Every sent non-confirmable CoAP response is stored within this object\n * for up to EXCHANGE_LIFETIME [RFC7252]. Whenever a request is received,\n * this cache is looked up first for a response with matching Message ID.\n * If one is found, the request is interpreted as a duplicate of a previously\n * sent and handled one, and the cached response is sent instead of calling\n * an user-defined request handler.\n */\ntypedef struct avs_coap_udp_response_cache avs_coap_udp_response_cache_t;\n\n/** Default CoAP/UDP transmission parameters, as defined by RFC7252 */\nextern const avs_coap_udp_tx_params_t AVS_COAP_DEFAULT_UDP_TX_PARAMS;\n\n#ifdef WITH_AVS_COAP_UDP\n\n/**\n * @param[in]  tx_params     Transmission parameters to check.\n * @param[out] error_details If not NULL, <c>*error_details</c> is set to\n *                           a string describing what part of @p tx_params\n *                           is invalid, or to NULL if @p tx_params are valid.\n *\n * @returns true if @p tx_params are valid according to RFC7252,\n *          false otherwise.\n */\nbool avs_coap_udp_tx_params_valid(const avs_coap_udp_tx_params_t *tx_params,\n                                  const char **error_details);\n\n/**\n * @returns MAX_TRANSMIT_SPAN value derived from @p tx_params according to the\n *          formula specified in RFC7252.\n */\navs_time_duration_t\navs_coap_udp_max_transmit_span(const avs_coap_udp_tx_params_t *tx_params);\n\n/**\n * @returns MAX_TRANSMIT_WAIT value derived from @p tx_params according to the\n *          formula specified in RFC7252.\n */\navs_time_duration_t\navs_coap_udp_max_transmit_wait(const avs_coap_udp_tx_params_t *tx_params);\n\n/**\n * @returns EXCHANGE_LIFETIME value derived from @p tx_params according\n *          to the formula specified in RFC7252.\n */\navs_time_duration_t\navs_coap_udp_exchange_lifetime(const avs_coap_udp_tx_params_t *tx_params);\n\n/**\n * Creates a response cache object.\n *\n * @param capacity Number of bytes the cache should be able to hold.\n *\n * @return Created response cache object, or NULL if there is not enough memory\n *         or @p capacity is 0.\n *\n * NOTE: NULL @ref avs_coap_udp_response_cache_t object is equivalent to a\n * correct, always-empty cache object.\n */\navs_coap_udp_response_cache_t *\navs_coap_udp_response_cache_create(size_t capacity);\n\n/**\n * Frees any resources used by given @p cache_ptr and sets <c>*cache_ptr</c>\n * to NULL.\n *\n * @param cache_ptr Pointer to the cache object to free. May be NULL, or point\n *                  to NULL, in which case a call to this function is a no-op.\n */\nvoid avs_coap_udp_response_cache_release(\n        avs_coap_udp_response_cache_t **cache_ptr);\n\n/**\n * Creates a CoAP/UDP context without associated socket.\n *\n * IMPORTANT: The socket MUST be set via @ref avs_coap_ctx_set_socket() before\n * any operations on the context are performed. Otherwise the behavior is\n * undefined.\n *\n * @param sched         Scheduler object that will be used to manage\n *                      retransmissions.\n *\n *                      MUST NOT be NULL. Created context object does not take\n *                      ownership of the scheduler, which MUST outlive created\n *                      CoAP context object.\n *\n * @param udp_tx_params UDP transmission parameters used by the CoAP context.\n *                      They are copied into CoAP context object, so the\n *                      pointer does not need to be kept valid after the call.\n *\n * @param in_buffer     Buffer used for temporary storage of incoming packets.\n *\n *                      MUST NOT be NULL and MUST be different from\n *                      @p out_buffer . Created context object does not take\n *                      ownership of the buffer, which MUST outlive created\n *                      CoAP context object.\n *\n * @param out_buffer    Buffer used for temporary storage of outgoing packets.\n *\n *                      MUST NOT be NULL and MUST be different from\n *                      @p in_buffer . Created context object does not take\n *                      ownership of the buffer, which MUST outlive created\n *                      CoAP context object.\n *\n * @param cache         Response cache to use for handling duplicate requests.\n *\n *                      MAY be NULL or shared between multiple CoAP context\n *                      objects, but MUST outlive all CoAP context objects it\n *                      is passed to.\n *\n * @param prng_ctx      PRNG context to use for token generation. MUST NOT be\n *                      @c NULL . MUST outlive the created CoAP context.\n *\n * @returns Created CoAP/UDP context on success, NULL on error.\n *\n * NOTE: @p in_buffer and @p out_buffer may be reused across different CoAP\n * contexts if they are not used concurrently.\n */\navs_coap_ctx_t *\navs_coap_udp_ctx_create(avs_sched_t *sched,\n                        const avs_coap_udp_tx_params_t *udp_tx_params,\n                        avs_shared_buffer_t *in_buffer,\n                        avs_shared_buffer_t *out_buffer,\n                        avs_coap_udp_response_cache_t *cache,\n                        avs_crypto_prng_ctx_t *prng_ctx);\n\n/**\n * Sets forced incoming MTU on a CoAP/UDP context.\n *\n * This value will be used when calculating BLOCK size to request from the\n * remote endpoint when performing renegotiation, and will have impact on the\n * result of @ref avs_coap_max_incoming_message_payload.\n *\n * @param ctx                 CoAP/UDP context to operate on, previously created\n *                            using @ref avs_coap_udp_ctx_create.\n *\n * @param forced_incoming_mtu Number of bytes expected to be the upper limit of\n *                            incoming message size, calculated on the datagram\n *                            layer (similar to @c AVS_NET_SOCKET_OPT_INNER_MTU)\n *                            or @c 0 to disable this mechanism and use MTU\n *                            reported by socket instead.\n *\n * @returns 0 on success, or -1 if @p ctx is not a CoAP/UDP context created\n *          by @ref avs_coap_udp_ctx_create.\n */\nint avs_coap_udp_ctx_set_forced_incoming_mtu(avs_coap_ctx_t *ctx,\n                                             size_t forced_incoming_mtu);\n\n/**\n * Sets CoAP/UDP context transmission params.\n *\n * @param ctx    CoAP/UDP context to operate on.\n * @param params New UDP transmission params. Passing <c>NULL</c>\n *               will cause default transmission params to be set.\n *\n * @returns 0 on success, negative value if passed context is not\n *          a CoAP/UDP one or transmission params are invalid.\n */\nint avs_coap_udp_ctx_set_tx_params(avs_coap_ctx_t *ctx,\n                                   const avs_coap_udp_tx_params_t *tx_params);\n\n/**\n * Gets CoAP/UDP context transmission params.\n *\n * @param ctx CoAP/UDP context to operate on.\n *\n * @returns UDP transmission params on success,\n *          <c>NULL</c> on failure.\n */\nconst avs_coap_udp_tx_params_t *\navs_coap_udp_ctx_get_tx_params(avs_coap_ctx_t *ctx);\n\n#endif // WITH_AVS_COAP_UDP\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif // AVSYSTEM_COAP_UDP_H\n"
  },
  {
    "path": "deps/avs_coap/include_public/avsystem/coap/writer.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef AVSYSTEM_COAP_WRITER_H\n#define AVSYSTEM_COAP_WRITER_H\n\n#include <stddef.h>\n\n#include <avsystem/coap/avs_coap_config.h>\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\nstruct avs_stream_struct;\n\n/**\n * Handler that generates payload to be sent with a streaming request.\n *\n * [BLOCK] whenever avs_stream_write on @p out_stream fills up an entire BLOCK,\n * it blocks the execution until receiving confirmation or exhausting all\n * retransmissions.\n *\n * @param out_stream Stream to write the payload to. MUST NOT be released by\n *                   the handler.\n *\n * @param arg        Opaque user-defined data.\n *\n * @returns 0 on success, a negative value in case of error.\n */\ntypedef int avs_coap_streaming_writer_t(struct avs_stream_struct *out_stream,\n                                        void *arg);\n\n/**\n * Callback that is called via the scheduler whenever the library needs payload\n * data to send for a CoAP exchange configured using\n * @c avs_coap_client_send_async_request ,\n * @c avs_coap_server_setup_async_response or @c avs_coap_notify_async .\n *\n * @param[in]    payload_offset         Offset (in bytes) within the CoAP\n *                                      response payload that the data provided\n *                                      by the function into @p payload_buf will\n *                                      correspond to. This is an absolute\n *                                      offset within the same domain as the\n *                                      corresponding BLOCK option value, if\n *                                      applicable and sent.\n *\n * @param[inout] payload_buf            Pointer to a buffer of\n *                                      @p payload_buf_size bytes that the\n *                                      function is supposed to fill with a\n *                                      chunk of payload data.\n *\n * @param[in]    payload_buf_size       Number of bytes allocated within\n *                                      @p payload_buf .\n *\n * @param[out]   out_payload_chunk_size Pointer to a variable that SHOULD be set\n *                                      to a number of bytes actually written\n *                                      into @p payload_buf . On entry, that\n *                                      variable is guaranteed to be zero.\n *\n * @param[in]    arg                    Opaque user-defined data.\n *\n * @returns\n * - 0 on success.\n *   - If @p out_payload_chunk_size is set to less than @p payload_buf_size\n *     (including zero), this is treated as end of payload. This function will\n *     never be called again for a given exchange. The library will proceed to\n *     receiving the response, and start calling\n *     @ref avs_coap_client_async_response_handler_t accordingly.\n *   - If @p out_payload_chunk_size is set to exactly @p payload_buf_size, this\n *     function will be called again later with @p payload_offset increased by\n *     @p payload_buf_size, requesting more data.\n *   - The result when @p out_payload_chunk_size is set to a value greater than\n *     @p payload_buf_size is undefined. On debug builds, the program will abort\n *     due to a failed assertion.\n * - A non-zero value in case of error. The library will cancel the exchange in\n *   a transport-specific way. In case of an outgoing request,\n *   @ref avs_coap_client_async_response_handler_t will be called with\n *   @ref AVS_COAP_CLIENT_REQUEST_CANCEL result argument.\n */\ntypedef int avs_coap_payload_writer_t(size_t payload_offset,\n                                      void *payload_buf,\n                                      size_t payload_buf_size,\n                                      size_t *out_payload_chunk_size,\n                                      void *arg);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif // AVSYSTEM_COAP_WRITER_H\n"
  },
  {
    "path": "deps/avs_coap/requirements.txt",
    "content": "pyparsing\npyyaml\n"
  },
  {
    "path": "deps/avs_coap/src/async/avs_coap_async_client.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n/*\n * Implementation of client-side asynchronous operations on\n * @ref avs_coap_exchange_t .\n */\n\n#include <avs_coap_init.h>\n\n#include <avsystem/commons/avs_errno.h>\n#include <avsystem/commons/avs_utils.h>\n\n#include <avsystem/coap/async_client.h>\n\n#include \"avs_coap_code_utils.h\"\n#include \"avs_coap_exchange.h\"\n\n#include \"async/avs_coap_async_client.h\"\n\n#define MODULE_NAME coap\n#include <avs_coap_x_log_config.h>\n\n#include \"avs_coap_ctx.h\"\n#include \"options/avs_coap_options.h\"\n\nVISIBILITY_SOURCE_BEGIN\n\ntypedef struct state_with_error {\n    avs_coap_client_request_state_t state;\n    avs_error_t error; // success iff state != FAIL\n} state_with_error_t;\n\nstatic inline state_with_error_t\nsuccess_state(avs_coap_client_request_state_t state) {\n    assert(state != AVS_COAP_CLIENT_REQUEST_FAIL);\n    return (state_with_error_t) {\n        .state = state\n    };\n}\n\nstatic inline state_with_error_t failure_state(avs_error_t error) {\n    assert(avs_is_err(error));\n    return (state_with_error_t) {\n        .state = AVS_COAP_CLIENT_REQUEST_FAIL,\n        .error = error\n    };\n}\n\nstatic avs_error_t client_exchange_send_next_chunk(\n        avs_coap_ctx_t *ctx, AVS_LIST(avs_coap_exchange_t) **exchange_ptr_ptr) {\n    assert(exchange_ptr_ptr && *exchange_ptr_ptr && **exchange_ptr_ptr);\n    AVS_ASSERT(AVS_LIST_FIND_PTR(&_avs_coap_get_base(ctx)->client_exchanges,\n                                 **exchange_ptr_ptr)\n                       != NULL,\n               \"not a started client exchange\");\n\n    avs_coap_exchange_id_t id = (**exchange_ptr_ptr)->id;\n\n    // every request needs to have an unique token\n    avs_coap_token_t old_token = (**exchange_ptr_ptr)->token;\n    avs_error_t err =\n            _avs_coap_ctx_generate_token(_avs_coap_get_base(ctx)->prng_ctx,\n                                         &(**exchange_ptr_ptr)->token);\n    if (avs_is_ok(err)) {\n        err = _avs_coap_exchange_send_next_chunk(\n                ctx, **exchange_ptr_ptr,\n                (**exchange_ptr_ptr)->by_type.client.send_result_handler,\n                (**exchange_ptr_ptr)->by_type.client.send_result_handler_arg);\n        *exchange_ptr_ptr = _avs_coap_find_client_exchange_ptr_by_id(ctx, id);\n        assert(!*exchange_ptr_ptr || **exchange_ptr_ptr);\n    }\n    if (*exchange_ptr_ptr && avs_is_err(err)) {\n        (**exchange_ptr_ptr)->token = old_token;\n    }\n    return err;\n}\n\n#ifdef WITH_AVS_COAP_BLOCK\nstatic inline size_t\ninitial_block2_option_size(avs_coap_ctx_t *ctx,\n                           const avs_coap_exchange_t *exchange) {\n    assert(exchange->by_type.client.next_response_payload_offset > 0);\n    (void) exchange;\n\n    char buffer[64];\n    avs_coap_options_t expected_options =\n            avs_coap_options_create_empty(buffer, sizeof(buffer));\n    avs_error_t err =\n            avs_coap_options_add_block(&expected_options,\n                                       &(avs_coap_option_block_t) {\n                                           .type = AVS_COAP_BLOCK2,\n                                           .seq_num = UINT16_MAX,\n                                           .size = AVS_COAP_BLOCK_MAX_SIZE\n                                       });\n    assert(avs_is_ok(err));\n    (void) err;\n\n    size_t block_size = avs_max_power_of_2_not_greater_than(\n            avs_coap_max_incoming_message_payload(ctx, &expected_options,\n                                                  AVS_COAP_CODE_CONTENT));\n    if (block_size > AVS_COAP_BLOCK_MAX_SIZE) {\n        block_size = AVS_COAP_BLOCK_MAX_SIZE;\n    } else if (block_size < AVS_COAP_BLOCK_MIN_SIZE) {\n        block_size = AVS_COAP_BLOCK_MIN_SIZE;\n    }\n    return block_size;\n}\n#endif // WITH_AVS_COAP_BLOCK\n\navs_error_t _avs_coap_client_exchange_send_first_chunk(\n        avs_coap_ctx_t *ctx, AVS_LIST(avs_coap_exchange_t) **exchange_ptr_ptr) {\n    assert(exchange_ptr_ptr && *exchange_ptr_ptr && **exchange_ptr_ptr);\n    avs_error_t err = AVS_OK;\n#ifdef WITH_AVS_COAP_BLOCK\n    if ((**exchange_ptr_ptr)->by_type.client.next_response_payload_offset > 0\n            && !_avs_coap_options_find_first_opt(&(**exchange_ptr_ptr)->options,\n                                                 AVS_COAP_OPTION_BLOCK2)) {\n        const size_t block_size =\n                initial_block2_option_size(ctx, **exchange_ptr_ptr);\n        if ((**exchange_ptr_ptr)->by_type.client.next_response_payload_offset\n                >= block_size) {\n            err = avs_coap_options_add_block(\n                    &(**exchange_ptr_ptr)->options,\n                    &(avs_coap_option_block_t) {\n                        .type = AVS_COAP_BLOCK2,\n                        .seq_num =\n                                (uint32_t) ((**exchange_ptr_ptr)\n                                                    ->by_type.client\n                                                    .next_response_payload_offset\n                                            / block_size),\n                        .size = (uint16_t) block_size\n                    });\n        }\n    }\n#endif // WITH_AVS_COAP_BLOCK\n    if (avs_is_ok(err)) {\n        err = client_exchange_send_next_chunk(ctx, exchange_ptr_ptr);\n    }\n    return err;\n}\n\nstatic inline bool request_header_valid(const avs_coap_request_header_t *req) {\n    if (!avs_coap_code_is_request(req->code)) {\n        LOG(WARNING, _(\"non-request code \") \"%s\" _(\" used in request header\"),\n            AVS_COAP_CODE_STRING(req->code));\n        return false;\n    }\n\n    return _avs_coap_options_valid(&req->options);\n}\n\nstatic inline const char *\nrequest_state_string(avs_coap_client_request_state_t result) {\n    switch (result) {\n    case AVS_COAP_CLIENT_REQUEST_OK:\n        return \"ok\";\n    case AVS_COAP_CLIENT_REQUEST_PARTIAL_CONTENT:\n        return \"partial content\";\n    case AVS_COAP_CLIENT_REQUEST_FAIL:\n        return \"fail\";\n    case AVS_COAP_CLIENT_REQUEST_CANCEL:\n        return \"cancel\";\n    }\n\n    return \"<unknown>\";\n}\n\n#ifdef WITH_AVS_COAP_BLOCK\nstatic int get_response_block_option(const avs_coap_borrowed_msg_t *response,\n                                     avs_coap_option_block_t *out_block2) {\n    switch (avs_coap_options_get_block(&response->options, AVS_COAP_BLOCK2,\n                                       out_block2)) {\n    case 0:\n        return 0;\n    case AVS_COAP_OPTION_MISSING:\n        return -1;\n    default:\n        AVS_UNREACHABLE(\"malformed option got through packet validation\");\n        return -1;\n    }\n}\n\nstatic size_t\nget_response_payload_offset(const avs_coap_borrowed_msg_t *response) {\n    avs_coap_option_block_t block2;\n    // response->payload_offset refers to payload offset in a single CoAP\n    // message payload if it's received in chunks, which can happen if CoAP/TCP\n    // is used.\n    if (get_response_block_option(response, &block2)) {\n        return response->payload_offset;\n    }\n    return block2.seq_num * block2.size + response->payload_offset;\n}\n#else  // WITH_AVS_COAP_BLOCK\nstatic size_t\nget_response_payload_offset(const avs_coap_borrowed_msg_t *response) {\n    return response->payload_offset;\n}\n#endif // WITH_AVS_COAP_BLOCK\n\nstatic void\ncall_exchange_response_handler(avs_coap_ctx_t *ctx,\n                               avs_coap_exchange_t *exchange,\n                               const avs_coap_borrowed_msg_t *response_msg,\n                               size_t response_payload_offset,\n                               state_with_error_t request_state) {\n    assert(ctx);\n    assert(exchange);\n\n    LOG(TRACE, _(\"exchange \") \"%s\" _(\": \") \"%s\",\n        AVS_UINT64_AS_STRING(exchange->id.value),\n        request_state_string(request_state.state));\n\n    // TODO: T2243\n    // Try to not create exchange if response handler isn't defined\n    if (!exchange->by_type.client.handle_response) {\n        return;\n    }\n\n    size_t expected_payload_offset =\n            AVS_MIN(exchange->by_type.client.next_response_payload_offset,\n                    response_payload_offset\n                            + (response_msg ? response_msg->payload_size : 0));\n    assert(expected_payload_offset >= response_payload_offset);\n    assert(expected_payload_offset - response_payload_offset\n           <= (response_msg ? response_msg->payload_size : 0));\n    const avs_coap_client_async_response_t *exchange_response =\n            !response_msg ? NULL\n                          : &(const avs_coap_client_async_response_t) {\n                                .header = (const avs_coap_response_header_t) {\n                                    .code = response_msg->code,\n                                    .options = response_msg->options\n                                },\n                                .payload_offset = expected_payload_offset,\n                                .payload = (const char *) response_msg->payload\n                                           + (expected_payload_offset\n                                              - response_payload_offset),\n                                .payload_size = response_msg->payload_size\n                                                - (expected_payload_offset\n                                                   - response_payload_offset)\n                            };\n\n    exchange->by_type.client.handle_response(\n            ctx, exchange->id, request_state.state, exchange_response,\n            request_state.error, exchange->by_type.client.handle_response_arg);\n}\n\nstatic void\ncall_partial_response_handler(avs_coap_ctx_t *ctx,\n                              AVS_LIST(avs_coap_exchange_t) **exchange_ptr_ptr,\n                              const avs_coap_borrowed_msg_t *response) {\n    assert(response);\n    assert(exchange_ptr_ptr && *exchange_ptr_ptr && **exchange_ptr_ptr);\n\n    avs_coap_exchange_id_t exchange_id = (**exchange_ptr_ptr)->id;\n    size_t response_payload_offset = get_response_payload_offset(response);\n\n    // do not report PARTIAL_CONTENT unless there is some actual content\n    // this avoids calling the handler for empty 2.31 Continue responses\n    if (response->payload) {\n        while (*exchange_ptr_ptr\n               && response_payload_offset + response->payload_size\n                          > (**exchange_ptr_ptr)\n                                    ->by_type.client\n                                    .next_response_payload_offset) {\n            size_t expected_payload_offset =\n                    (**exchange_ptr_ptr)\n                            ->by_type.client.next_response_payload_offset;\n            call_exchange_response_handler(\n                    ctx, **exchange_ptr_ptr, response, response_payload_offset,\n                    success_state(AVS_COAP_CLIENT_REQUEST_PARTIAL_CONTENT));\n            if ((*exchange_ptr_ptr = _avs_coap_find_client_exchange_ptr_by_id(\n                         ctx, exchange_id))\n                    && (**exchange_ptr_ptr)\n                                       ->by_type.client\n                                       .next_response_payload_offset\n                                   == expected_payload_offset) {\n                (**exchange_ptr_ptr)\n                        ->by_type.client.next_response_payload_offset =\n                        response_payload_offset + response->payload_size;\n            }\n        }\n    }\n}\n\nstatic void cleanup_exchange(avs_coap_ctx_t *ctx,\n                             avs_coap_exchange_t *exchange,\n                             const avs_coap_borrowed_msg_t *final_msg,\n                             state_with_error_t request_state) {\n    assert(ctx);\n    assert(exchange);\n    AVS_ASSERT(!AVS_LIST_FIND_PTR(&_avs_coap_get_base(ctx)->client_exchanges,\n                                  exchange),\n               \"exchange must be detached\");\n    AVS_ASSERT(request_state.state != AVS_COAP_CLIENT_REQUEST_PARTIAL_CONTENT,\n               \"cleanup_exchange must not be used for intermediate responses\");\n\n    size_t response_payload_offset =\n            final_msg ? get_response_payload_offset(final_msg) : 0;\n    call_exchange_response_handler(ctx, exchange, final_msg,\n                                   response_payload_offset, request_state);\n    AVS_LIST_DELETE(&exchange);\n}\n\n#ifdef WITH_AVS_COAP_BLOCK\nstatic bool\nexchange_expects_continue_response(const avs_coap_exchange_t *exchange) {\n    avs_coap_option_block_t request_block1;\n    return avs_coap_code_is_request(exchange->code)\n           && avs_coap_options_get_block(&exchange->options, AVS_COAP_BLOCK1,\n                                         &request_block1)\n                      == 0\n           && request_block1.has_more;\n}\n\nstatic avs_error_t handle_request_block_size_renegotiation(\n        avs_coap_option_block_t *request_block,\n        const avs_coap_option_block_t *response_block) {\n    assert(request_block);\n    assert(response_block);\n\n    if (request_block->size == response_block->size) {\n        return AVS_OK;\n    } else if (request_block->size > response_block->size) {\n        // TODO: should this be only allowed at the start of block-wise\n        // transfer?\n        assert(request_block->size % response_block->size == 0);\n\n        uint32_t multiplier =\n                (uint32_t) (request_block->size / response_block->size);\n        uint32_t new_seq_num = request_block->seq_num * multiplier;\n\n        if (new_seq_num > AVS_COAP_BLOCK_MAX_SEQ_NUMBER) {\n            LOG(DEBUG,\n                _(\"BLOCK\") \"%d\" _(\" size renegotiation impossible: seq_num \"\n                                  \"overflows (\") \"%\" PRIu32 _(\" >= \") \"%\" PRIu32\n                        _(\" == 2^20), ignoring size renegotiation request\"),\n                request_block->type == AVS_COAP_BLOCK1 ? 1 : 2, new_seq_num,\n                (uint32_t) AVS_COAP_BLOCK_MAX_SEQ_NUMBER);\n        } else {\n            LOG(DEBUG,\n                _(\"BLOCK\") \"%d\" _(\" size renegotiated: \") \"%\" PRIu16\n                        _(\" -> \") \"%\" PRIu16 _(\"; seq_num \") \"%\" PRIu32 _(\n                                \" -> \") \"%\" PRIu32,\n                request_block->type == AVS_COAP_BLOCK1 ? 1 : 2,\n                request_block->size, response_block->size,\n                request_block->seq_num, new_seq_num);\n\n            request_block->seq_num = new_seq_num;\n            request_block->size = response_block->size;\n        }\n        return AVS_OK;\n    } else {\n        assert(request_block->size < response_block->size);\n        LOG(DEBUG,\n            _(\"invalid BLOCK\") \"%d\" _(\" size increase requested (\") \"%\" PRIu16\n                    _(\" -> \") \"%\" PRIu16 _(\"), ignoring\"),\n            request_block->type == AVS_COAP_BLOCK1 ? 1 : 2, request_block->size,\n            response_block->size);\n        return _avs_coap_err(AVS_COAP_ERR_BLOCK_SIZE_RENEGOTIATION_INVALID);\n    }\n}\n\nstatic avs_error_t update_exchange_for_next_request_block(\n        avs_coap_exchange_t *exchange,\n        const avs_coap_option_block_t *response_block1) {\n    assert(exchange_expects_continue_response(exchange));\n    assert(!response_block1 || response_block1->type == AVS_COAP_BLOCK1);\n\n    // Sending another block of a request requires keeping the same\n    // set of CoAP options as the previous one, except for BLOCK1, whose\n    // seq_num needs to be incremented.\n    //\n    // The CoAP server may also request the use of smaller blocks by sending\n    // a response containing BLOCK1 option with the requested size.\n\n    avs_coap_option_block_t request_block1;\n    int opts_result =\n            avs_coap_options_get_block(&exchange->options, AVS_COAP_BLOCK1,\n                                       &request_block1);\n    AVS_ASSERT(opts_result == 0, \"BLOCK1 option invalid or missing in request\");\n    // request is controlled by us, it should be valid\n\n    ++request_block1.seq_num;\n    avs_error_t err;\n    if (response_block1\n            && avs_is_err((err = handle_request_block_size_renegotiation(\n                                   &request_block1, response_block1)))) {\n        return err;\n    }\n\n    if (request_block1.seq_num > AVS_COAP_BLOCK_MAX_SEQ_NUMBER) {\n        LOG(ERROR,\n            _(\"BLOCK1 sequence number (\") \"%\" PRIu32 _(\n                    \") exceeds maximum acceptable value (\") \"%\" PRIu32 _(\")\"),\n            request_block1.seq_num, (uint32_t) AVS_COAP_BLOCK_MAX_SEQ_NUMBER);\n        return _avs_coap_err(AVS_COAP_ERR_BLOCK_SEQ_NUM_OVERFLOW);\n    }\n\n    avs_coap_options_remove_by_number(&exchange->options,\n                                      AVS_COAP_OPTION_BLOCK1);\n    avs_error_t opts_err =\n            avs_coap_options_add_block(&exchange->options, &request_block1);\n    AVS_ASSERT(avs_is_ok(opts_err),\n               \"options buffer is supposed to have enough space for options\");\n\n    return (!opts_result && avs_is_ok(opts_err))\n                   ? AVS_OK\n                   : _avs_coap_err(AVS_COAP_ERR_ASSERT_FAILED);\n}\n\nstatic state_with_error_t\nhandle_continue_response(avs_coap_ctx_t *ctx,\n                         AVS_LIST(avs_coap_exchange_t) **exchange_ptr_ptr,\n                         const avs_coap_borrowed_msg_t *response) {\n    avs_coap_option_block_t response_block1;\n    switch (avs_coap_options_get_block(&response->options, AVS_COAP_BLOCK1,\n                                       &response_block1)) {\n    case 0: {\n        assert(exchange_ptr_ptr && *exchange_ptr_ptr && **exchange_ptr_ptr);\n        // TODO: T2172 check that response_block1 matches request block1;\n        // FAIL if it doesn't\n\n        // TODO: should other response options be checked?\n\n        avs_error_t err =\n                update_exchange_for_next_request_block(**exchange_ptr_ptr,\n                                                       &response_block1);\n        if (avs_is_err(err)) {\n            return failure_state(err);\n        }\n\n        call_partial_response_handler(ctx, exchange_ptr_ptr, response);\n\n        // the call might have canceled the exchange\n        // If we're finished with a single response packet, but not with the\n        // whole exchange, then request more data from the server.\n        assert(!*exchange_ptr_ptr || **exchange_ptr_ptr);\n        if (*exchange_ptr_ptr\n                && avs_is_err((err = client_exchange_send_next_chunk(\n                                       ctx, exchange_ptr_ptr)))) {\n            return failure_state(err);\n        }\n\n        return success_state(AVS_COAP_CLIENT_REQUEST_PARTIAL_CONTENT);\n    }\n\n    case AVS_COAP_OPTION_MISSING:\n        LOG(DEBUG, _(\"BLOCK1 option missing in \") \"%s\" _(\" response\"),\n            AVS_COAP_CODE_STRING(response->code));\n        return failure_state(_avs_coap_err(AVS_COAP_ERR_MALFORMED_OPTIONS));\n\n    default:\n        LOG(DEBUG, _(\"malformed BLOCK1 option in \") \"%s\" _(\" response\"),\n            AVS_COAP_CODE_STRING(response->code));\n        return failure_state(_avs_coap_err(AVS_COAP_ERR_MALFORMED_OPTIONS));\n    }\n}\n\nstatic avs_error_t update_request_for_next_response_block(\n        avs_coap_exchange_t *exchange,\n        const avs_coap_option_block_t *response_block2) {\n    assert(exchange);\n    assert(response_block2);\n    assert(response_block2->type == AVS_COAP_BLOCK2);\n\n    // To request response blocks after the first one, we need to keep the same\n    // set of CoAP options as in original one, except for:\n    // * BLOCK1, which should be removed,\n    // * BLOCK2, which should have seq_num incremented.\n    //\n    // Additionally, message token need to be changed\n\n    // add/update BLOCK2 option in the request\n    avs_coap_option_block_t block2;\n    int opts_result = avs_coap_options_get_block(&exchange->options,\n                                                 AVS_COAP_BLOCK2, &block2);\n    AVS_ASSERT(opts_result >= 0,\n               \"exchange is supposed to have up to a single BLOCK2 option\");\n    const bool request_has_block2 = (opts_result != AVS_COAP_OPTION_MISSING);\n    const uint32_t expected_offset =\n            request_has_block2 ? block2.seq_num * block2.size : 0;\n    const uint32_t actual_offset =\n            response_block2->seq_num * response_block2->size;\n\n    if (expected_offset != actual_offset) {\n        LOG(DEBUG,\n            _(\"mismatched response block offset (expected \") \"%\" PRIu32 _(\n                    \", got \") \"%\" PRIu32 _(\")\"),\n            expected_offset, actual_offset);\n        return _avs_coap_err(AVS_COAP_ERR_MALFORMED_OPTIONS);\n    }\n\n    avs_error_t err;\n    // If the request didn't have BLOCK2 option, any size is OK\n    if (request_has_block2 && (block2.size != response_block2->size)) {\n        err = handle_request_block_size_renegotiation(&block2, response_block2);\n        if (avs_is_err(err)) {\n            return err;\n        }\n    }\n\n    // remove BLOCK1 option in the request (if any)\n    avs_coap_options_remove_by_number(&exchange->options,\n                                      AVS_COAP_OPTION_BLOCK1);\n    avs_coap_options_remove_by_number(&exchange->options,\n                                      AVS_COAP_OPTION_BLOCK2);\n    block2 = (avs_coap_option_block_t) {\n        .type = AVS_COAP_BLOCK2,\n        .seq_num = (uint32_t) (exchange->by_type.client\n                                       .next_response_payload_offset\n                               / response_block2->size),\n        .size = response_block2->size,\n        .is_bert = response_block2->is_bert\n    };\n    AVS_ASSERT(block2.is_bert || block2.seq_num > response_block2->seq_num,\n               \"bug: invalid seq_num\");\n    if (avs_is_err((err = avs_coap_options_add_block(&exchange->options,\n                                                     &block2)))) {\n        AVS_ASSERT(err.category != AVS_COAP_ERR_CATEGORY\n                           || err.code != AVS_COAP_ERR_MESSAGE_TOO_BIG,\n                   \"exchange is supposed to have enough space for adding extra \"\n                   \"BLOCK option\");\n        return err;\n    }\n\n    // do not include payload any more\n    exchange->write_payload = NULL;\n    exchange->write_payload_arg = NULL;\n    return AVS_OK;\n}\n\nstatic bool etag_matches(avs_coap_exchange_t *exchange,\n                         const avs_coap_borrowed_msg_t *msg) {\n    avs_coap_etag_t etag;\n    int retval = avs_coap_options_get_etag(&msg->options, &etag);\n    if (retval < 0) {\n        return false;\n    }\n    if (!exchange->by_type.client.etag_stored) {\n        exchange->by_type.client.etag = etag;\n        // Empty ETag is stored if it isn't present in options.\n        exchange->by_type.client.etag_stored = true;\n        return true;\n    }\n    if (!avs_coap_etag_equal(&etag, &exchange->by_type.client.etag)) {\n        LOG(WARNING,\n            _(\"Response ETag mismatch: previous: \") \"%s\" _(\", current: \") \"%s\",\n            AVS_COAP_ETAG_HEX(&exchange->by_type.client.etag),\n            AVS_COAP_ETAG_HEX(&etag));\n        return false;\n    }\n    return true;\n}\n\nstatic state_with_error_t\nhandle_final_response(avs_coap_ctx_t *ctx,\n                      AVS_LIST(avs_coap_exchange_t) **exchange_ptr_ptr,\n                      const avs_coap_borrowed_msg_t *response) {\n    assert(exchange_ptr_ptr && *exchange_ptr_ptr && **exchange_ptr_ptr);\n    assert(response);\n\n    // do not include any more payload in further requests\n    (**exchange_ptr_ptr)->write_payload = NULL;\n    (**exchange_ptr_ptr)->write_payload_arg = NULL;\n    (**exchange_ptr_ptr)->eof_cache.empty = true;\n\n    if (!etag_matches(**exchange_ptr_ptr, response)) {\n        return failure_state(_avs_coap_err(AVS_COAP_ERR_ETAG_MISMATCH));\n    }\n\n    avs_coap_option_block_t request_block2;\n    int opts_result =\n            avs_coap_options_get_block(&(**exchange_ptr_ptr)->options,\n                                       AVS_COAP_BLOCK2, &request_block2);\n    AVS_ASSERT(opts_result == 0 || opts_result == AVS_COAP_OPTION_MISSING,\n               \"library allowed for construction of a malformed request\");\n    bool request_has_block2 = (opts_result != AVS_COAP_OPTION_MISSING);\n\n    avs_coap_option_block_t response_block2;\n    switch (avs_coap_options_get_block(&response->options, AVS_COAP_BLOCK2,\n                                       &response_block2)) {\n    case 0: {\n        // BLOCK response to a request, which may or may not have had an\n        // explicit BLOCK2 option\n\n        size_t request_off = request_has_block2 ? request_block2.seq_num\n                                                          * request_block2.size\n                                                : 0;\n        size_t response_off = response_block2.seq_num * response_block2.size;\n        if (request_off != response_off) {\n            // We asked the server for one block of data, but it returned\n            // another one. This is clearly a server-side error.\n            avs_coap_option_block_string_buf_t request_block2_str;\n            avs_coap_option_block_string_buf_t response_block2_str;\n            LOG(WARNING, _(\"expected \") \"%s\" _(\", got \") \"%s\",\n                _avs_coap_option_block_string(&request_block2_str,\n                                              &request_block2),\n                _avs_coap_option_block_string(&response_block2_str,\n                                              &response_block2));\n            return failure_state(_avs_coap_err(AVS_COAP_ERR_MALFORMED_OPTIONS));\n        }\n\n        // TODO T2123: check that all options other than BLOCK2 are identical\n        // across responses\n\n#    ifdef AVS_LOG_WITH_TRACE\n        avs_coap_option_block_string_buf_t response_block2_str;\n        LOG(TRACE, _(\"exchange \") \"%s\" _(\": \") \"%s\",\n            AVS_UINT64_AS_STRING((**exchange_ptr_ptr)->id.value),\n            _avs_coap_option_block_string(&response_block2_str,\n                                          &response_block2));\n#    endif // AVS_LOG_WITH_TRACE\n\n        if (response_block2.has_more) {\n            call_partial_response_handler(ctx, exchange_ptr_ptr, response);\n\n            // the call might have canceled the exchange\n            assert(!*exchange_ptr_ptr || **exchange_ptr_ptr);\n            if (*exchange_ptr_ptr) {\n                // We're finished with a single response packet, but not with\n                // the whole exchange. Request more data from the server.\n                avs_error_t err = update_request_for_next_response_block(\n                        **exchange_ptr_ptr, &response_block2);\n                if (err.category == AVS_ERRNO_CATEGORY\n                        && err.code == AVS_ERANGE) {\n                    // Requested offset larger than allowed by CoAP spec -\n                    // treat this as the end of the transfer\n                    return success_state(AVS_COAP_CLIENT_REQUEST_OK);\n                }\n                if (avs_is_err(err)\n                        || avs_is_err((err = client_exchange_send_next_chunk(\n                                               ctx, exchange_ptr_ptr)))) {\n                    return failure_state(err);\n                }\n            }\n\n            return success_state(AVS_COAP_CLIENT_REQUEST_PARTIAL_CONTENT);\n        } else {\n            // final block of a BLOCK2 response\n            return success_state(AVS_COAP_CLIENT_REQUEST_OK);\n        }\n    }\n\n    case AVS_COAP_OPTION_MISSING:\n        // We asked the server for a block of data, but server responded\n        // with a non-BLOCK response. This most likely indicates a server\n        // error.\n        if (request_has_block2) {\n            avs_coap_option_block_string_buf_t request_block2_str;\n            LOG(DEBUG, _(\"expected \") \"%s\" _(\", but BLOCK2 option not found\"),\n                _avs_coap_option_block_string(&request_block2_str,\n                                              &request_block2));\n            return failure_state(_avs_coap_err(AVS_COAP_ERR_MALFORMED_OPTIONS));\n        }\n\n        // Non-BLOCK response to a non-BLOCK request\n        return success_state(AVS_COAP_CLIENT_REQUEST_OK);\n\n    default:\n        LOG(DEBUG, _(\"malformed BLOCK2 option\"));\n        return failure_state(_avs_coap_err(AVS_COAP_ERR_MALFORMED_OPTIONS));\n    }\n}\n#else  // WITH_AVS_COAP_BLOCK\nstatic avs_error_t update_exchange_for_next_request_block(\n        avs_coap_exchange_t *exchange,\n        const avs_coap_option_block_t *response_block1) {\n    (void) exchange;\n    (void) response_block1;\n\n    AVS_UNREACHABLE(\n            \"More data to send even though BLOCK is disabled - this should be \"\n            \"handled in _avs_coap_exchange_send_next_chunk()\");\n    return _avs_coap_err(AVS_COAP_ERR_ASSERT_FAILED);\n}\n\nstatic state_with_error_t\nhandle_final_response(avs_coap_ctx_t *ctx,\n                      AVS_LIST(avs_coap_exchange_t) **exchange_ptr_ptr,\n                      const avs_coap_borrowed_msg_t *response) {\n    (void) ctx;\n    assert(exchange_ptr_ptr && *exchange_ptr_ptr && **exchange_ptr_ptr);\n    assert(response);\n\n    (**exchange_ptr_ptr)->write_payload = NULL;\n    (**exchange_ptr_ptr)->write_payload_arg = NULL;\n    (**exchange_ptr_ptr)->eof_cache.empty = true;\n\n    return success_state(AVS_COAP_CLIENT_REQUEST_OK);\n}\n#endif // WITH_AVS_COAP_BLOCK\n\nstatic avs_error_t\nclient_exchange_send_all(avs_coap_ctx_t *ctx,\n                         AVS_LIST(avs_coap_exchange_t) **exchange_ptr_ptr) {\n    assert(exchange_ptr_ptr && *exchange_ptr_ptr && **exchange_ptr_ptr);\n    assert((**exchange_ptr_ptr)->by_type.client.handle_response == NULL);\n\n    avs_error_t err = client_exchange_send_next_chunk(ctx, exchange_ptr_ptr);\n    while (avs_is_ok(err) && *exchange_ptr_ptr\n           && !(**exchange_ptr_ptr)->eof_cache.empty) {\n        (void) (avs_is_err((err = update_exchange_for_next_request_block(\n                                    **exchange_ptr_ptr, NULL)))\n                || avs_is_err((err = client_exchange_send_next_chunk(\n                                       ctx, exchange_ptr_ptr))));\n    }\n\n    if (*exchange_ptr_ptr) {\n        if (avs_is_err(err)) {\n            cleanup_exchange(ctx, AVS_LIST_DETACH(*exchange_ptr_ptr), NULL,\n                             failure_state(err));\n        } else {\n            avs_coap_exchange_cancel(ctx, (**exchange_ptr_ptr)->id);\n        }\n        *exchange_ptr_ptr = NULL;\n    }\n    return err;\n}\n\nbool _avs_coap_client_exchange_request_sent(\n        const avs_coap_exchange_t *exchange) {\n    // Token is initialized in _avs_coap_client_exchange_send_next_chunk() and\n    // zero-length tokens are never used. Hence, zero-length token means that\n    // no request packets have been sent yet.\n    return exchange->token.size > 0;\n}\n\nstatic avs_error_t\nclient_exchange_start(avs_coap_ctx_t *ctx,\n                      AVS_LIST(avs_coap_exchange_t) *exchange_ptr,\n                      avs_coap_exchange_id_t *out_id) {\n    assert(exchange_ptr);\n    assert(*exchange_ptr);\n\n    AVS_LIST(avs_coap_exchange_t) *insert_ptr =\n            &_avs_coap_get_base(ctx)->client_exchanges;\n    // client_exchanges list containts exchanges for which the first request\n    // packet has not been sent yet at the beginning. Add the new exchange after\n    // all such existing exchanges, but before any others.\n    while (*insert_ptr\n           && !_avs_coap_client_exchange_request_sent(*insert_ptr)) {\n        AVS_LIST_ADVANCE_PTR(&insert_ptr);\n    }\n    AVS_LIST_INSERT(insert_ptr, *exchange_ptr);\n    assert(*insert_ptr == *exchange_ptr);\n    (*exchange_ptr)->id = _avs_coap_generate_exchange_id(ctx);\n\n    avs_error_t err = AVS_OK;\n    if ((*exchange_ptr)->by_type.client.handle_response) {\n        *out_id = (*exchange_ptr)->id;\n        _avs_coap_reschedule_retry_or_request_expired_job(\n                ctx, avs_time_monotonic_now());\n    } else {\n        *out_id = AVS_COAP_EXCHANGE_ID_INVALID;\n        err = client_exchange_send_all(ctx, &insert_ptr);\n    }\n\n    *exchange_ptr = NULL;\n    return err;\n}\n\nstatic state_with_error_t\nhandle_response(avs_coap_ctx_t *ctx,\n                AVS_LIST(avs_coap_exchange_t) **exchange_ptr_ptr,\n                const avs_coap_borrowed_msg_t *response) {\n    assert(response);\n    assert(exchange_ptr_ptr && *exchange_ptr_ptr && **exchange_ptr_ptr);\n\n    switch (response->code) {\n    case AVS_COAP_CODE_CONTINUE:\n#ifdef WITH_AVS_COAP_BLOCK\n        if (!exchange_expects_continue_response(**exchange_ptr_ptr)) {\n            LOG(DEBUG, _(\"unexpected \") \"%s\" _(\" response\"),\n                AVS_COAP_CODE_STRING(response->code));\n            return failure_state(\n                    _avs_coap_err(AVS_COAP_ERR_UNEXPECTED_CONTINUE_RESPONSE));\n        }\n\n        return handle_continue_response(ctx, exchange_ptr_ptr, response);\n#else  // WITH_AVS_COAP_BLOCK\n        LOG(DEBUG, _(\"unexpected \") \"%s\" _(\" response\"),\n            AVS_COAP_CODE_STRING(response->code));\n        return failure_state(_avs_coap_err(AVS_COAP_ERR_FEATURE_DISABLED));\n#endif // WITH_AVS_COAP_BLOCK\n\n    case AVS_COAP_CODE_REQUEST_ENTITY_TOO_LARGE:\n        // TODO: T2171 handle Request Entity Too Large\n        return failure_state(_avs_coap_err(AVS_COAP_ERR_NOT_IMPLEMENTED));\n\n    default:\n        return handle_final_response(ctx, exchange_ptr_ptr, response);\n    }\n}\n\n#ifdef WITH_AVS_COAP_BLOCK\nstatic state_with_error_t\nhandle_failure(avs_coap_ctx_t *ctx,\n               AVS_LIST(avs_coap_exchange_t) **exchange_ptr_ptr,\n               const avs_coap_borrowed_msg_t *response,\n               avs_error_t fail_err) {\n    assert(exchange_ptr_ptr && *exchange_ptr_ptr && **exchange_ptr_ptr);\n    if (fail_err.category != AVS_COAP_ERR_CATEGORY\n            || fail_err.code != AVS_COAP_ERR_TRUNCATED_MESSAGE_RECEIVED\n            || !response) {\n        return failure_state(fail_err);\n    }\n    // We received response, but it was too big to be held in our internal\n    // buffer. Since we know our internal buffer size we may try resending\n    // our request but with BLOCK2 option adjusted accordingly.\n    avs_coap_option_block_t block2;\n    int result = avs_coap_options_get_block(&response->options, AVS_COAP_BLOCK2,\n                                            &block2);\n    assert(result == 0 || result == AVS_COAP_OPTION_MISSING);\n\n    size_t max_payload_size = ctx->vtable->max_incoming_payload_size(\n            ctx, response->token.size, &response->options, response->code);\n    if (result == AVS_COAP_OPTION_MISSING) {\n        // There were no BLOCK2 in response, but we intend to use it, which'd\n        // force the peer to repeat it, thus increasing the message overhead.\n        if (max_payload_size >= AVS_COAP_OPT_BLOCK_MAX_SIZE) {\n            max_payload_size -= AVS_COAP_OPT_BLOCK_MAX_SIZE;\n        }\n        block2 = (avs_coap_option_block_t) {\n            .type = AVS_COAP_BLOCK2\n        };\n    }\n    size_t new_max_block_size =\n            avs_max_power_of_2_not_greater_than(max_payload_size);\n\n    if (new_max_block_size > AVS_COAP_BLOCK_MAX_SIZE) {\n        new_max_block_size = AVS_COAP_BLOCK_MAX_SIZE;\n    }\n    if (new_max_block_size < AVS_COAP_BLOCK_MIN_SIZE) {\n        return failure_state(fail_err);\n    }\n    assert(new_max_block_size != block2.size);\n\n    assert(block2.type == AVS_COAP_BLOCK2);\n    block2.size = (uint16_t) new_max_block_size;\n    block2.seq_num =\n            (uint32_t) ((**exchange_ptr_ptr)\n                                ->by_type.client.next_response_payload_offset\n                        / new_max_block_size);\n\n    // Replace or add BLOCK2 option to our request, so that the response would\n    // likely fit into the input buffer.\n    avs_coap_options_remove_by_number(&(**exchange_ptr_ptr)->options,\n                                      AVS_COAP_OPTION_BLOCK2);\n\n    avs_error_t send_err;\n    if (avs_is_err((send_err = avs_coap_options_add_block(\n                            &(**exchange_ptr_ptr)->options, &block2)))\n            || avs_is_err((send_err = client_exchange_send_next_chunk(\n                                   ctx, exchange_ptr_ptr)))) {\n        return failure_state(avs_is_ok(fail_err) ? send_err : fail_err);\n    }\n    return success_state(AVS_COAP_CLIENT_REQUEST_OK);\n}\n#else  // WITH_AVS_COAP_BLOCK\nstatic state_with_error_t\nhandle_failure(avs_coap_ctx_t *ctx,\n               AVS_LIST(avs_coap_exchange_t) **exchange_ptr_ptr,\n               const avs_coap_borrowed_msg_t *response,\n               avs_error_t fail_err) {\n    (void) ctx;\n    (void) exchange_ptr_ptr;\n    (void) response;\n    return failure_state(fail_err);\n}\n#endif // WITH_AVS_COAP_BLOCK\n\nstatic avs_coap_send_result_handler_result_t\non_request_delivery_finished(avs_coap_ctx_t *ctx,\n                             avs_coap_send_result_t result,\n                             avs_error_t fail_err,\n                             const avs_coap_borrowed_msg_t *response,\n                             void *exchange) {\n    assert(!response || avs_coap_code_is_response(response->code));\n\n    avs_coap_exchange_id_t exchange_id = ((avs_coap_exchange_t *) exchange)->id;\n    AVS_LIST(avs_coap_exchange_t) *exchange_ptr =\n            _avs_coap_find_client_exchange_ptr_by_id(ctx, exchange_id);\n    if (!exchange_ptr) {\n        assert(result == AVS_COAP_SEND_RESULT_CANCEL);\n        return AVS_COAP_RESPONSE_ACCEPTED;\n    }\n    assert(*exchange_ptr == exchange);\n\n    state_with_error_t request_state;\n    switch (result) {\n    case AVS_COAP_SEND_RESULT_PARTIAL_CONTENT:\n        call_partial_response_handler(ctx, &exchange_ptr, response);\n        return AVS_COAP_RESPONSE_ACCEPTED;\n\n    case AVS_COAP_SEND_RESULT_OK:\n        request_state = handle_response(ctx, &exchange_ptr, response);\n        if (request_state.state == AVS_COAP_CLIENT_REQUEST_PARTIAL_CONTENT) {\n            return AVS_COAP_RESPONSE_ACCEPTED;\n        }\n        break;\n\n    case AVS_COAP_SEND_RESULT_FAIL:\n        request_state = handle_failure(ctx, &exchange_ptr, response, fail_err);\n        if (request_state.state == AVS_COAP_CLIENT_REQUEST_OK) {\n            // we recovered from failure\n            return AVS_COAP_RESPONSE_ACCEPTED;\n        }\n        break;\n\n    case AVS_COAP_SEND_RESULT_CANCEL:\n        request_state = success_state(AVS_COAP_CLIENT_REQUEST_CANCEL);\n        break;\n    }\n\n    if (request_state.state == AVS_COAP_CLIENT_REQUEST_FAIL) {\n        // We may end up here if a response was received, but during handling\n        // at this layer we realize it is not well-formed, or that we cannot\n        // continue a BLOCK-wise transfer.\n        response = NULL;\n    }\n\n    assert(!exchange_ptr || *exchange_ptr);\n    if (exchange_ptr) {\n        cleanup_exchange(ctx, AVS_LIST_DETACH(exchange_ptr), response,\n                         request_state);\n    }\n\n    return AVS_COAP_RESPONSE_ACCEPTED;\n}\n\nstatic AVS_LIST(avs_coap_exchange_t) client_exchange_create(\n        uint8_t code,\n        const avs_coap_options_t *options,\n        avs_coap_payload_writer_t *payload_writer,\n        void *payload_writer_arg,\n        avs_coap_client_async_response_handler_t *response_handler,\n        void *response_handler_arg) {\n    assert(avs_coap_code_is_request(code));\n\n    // Add a few extra bytes for BLOCK1 option in case the request turns out\n    // to be large\n    size_t options_capacity = options->capacity + AVS_COAP_OPT_BLOCK_MAX_SIZE;\n\n    AVS_LIST(avs_coap_exchange_t) exchange =\n            (AVS_LIST(avs_coap_exchange_t)) AVS_LIST_NEW_BUFFER(\n                    sizeof(avs_coap_exchange_t) + options_capacity);\n    if (!exchange) {\n        return NULL;\n    }\n\n    size_t next_response_payload_offset = 0;\n#ifdef WITH_AVS_COAP_BLOCK\n    avs_coap_option_block_t block2;\n    if (avs_coap_options_get_block(options, AVS_COAP_BLOCK2, &block2) == 0) {\n        next_response_payload_offset = block2.seq_num * block2.size;\n    }\n#endif // WITH_AVS_COAP_BLOCK\n\n    *exchange = (avs_coap_exchange_t) {\n        .id = AVS_COAP_EXCHANGE_ID_INVALID,\n        .write_payload = payload_writer,\n        .write_payload_arg = payload_writer_arg,\n        .code = code,\n        .eof_cache = {\n            .empty = true\n        },\n        .by_type = {\n            .client = {\n                .handle_response = response_handler,\n                .handle_response_arg = response_handler_arg,\n                .send_result_handler =\n                        response_handler ? on_request_delivery_finished : NULL,\n                .send_result_handler_arg = exchange,\n                .next_response_payload_offset = next_response_payload_offset\n            }\n        },\n        .options_buffer_size = options_capacity\n    };\n\n    // T2393 [COMPOUND-LITERAL-FAM-ASSIGNMENT-TRAP]\n    //\n    // Putting this initializer within the compound literal above makes some\n    // compilers overwrite initial bytes of exchange->options_buffer with\n    // nullbytes *after* options are copied, resulting in overwriting options\n    // data.\n    //\n    // This is caused by combination of a few facts:\n    // - FAM may be inserted in place of existing padding at the end of the\n    //   struct,\n    // - value of padding bytes is indeterminate,\n    // - assignment of a compound literal is split into two parts:\n    //   initialization of the compound literal itself, and copying it to actual\n    //   destination.\n    //\n    // In particular, this means that the assignment above may include:\n    //\n    // 1. initializing the compound literal:\n    //\n    //    a. invoking any side-effects of expressions used for initialization\n    //       of struct members,\n    //    b. optionally filling padding bytes in the struct with zeros\n    //\n    // 2. copying the compound literal, including all padding bytes, to the\n    //    target destination.\n    //\n    // Now, exchange->options_buffer is filled with usable data in step 1a.\n    // If the exchange->options_buffer happens to be a FAM that overlaps with\n    // padding bytes at the end of *exchange, and the compiler decides to do\n    // step 1b, usable data will get overwritten with zeros in step 2.\n    //\n    // This behavior was observed on 32-bit armv7 (GCC 8.2 and Clang 7.0).\n    // x86_64 miraculously works, even when compiling with -m32.\n    exchange->options =\n            _avs_coap_options_copy(options, exchange->options_buffer,\n                                   options_capacity);\n\n    return exchange;\n}\n\navs_error_t avs_coap_client_send_async_request(\n        avs_coap_ctx_t *ctx,\n        avs_coap_exchange_id_t *out_exchange_id,\n        const avs_coap_request_header_t *req,\n        avs_coap_payload_writer_t *request_writer,\n        void *request_writer_arg,\n        avs_coap_client_async_response_handler_t *response_handler,\n        void *response_handler_arg) {\n    assert(ctx);\n    assert(ctx->vtable);\n    assert(req);\n\n    if (!request_header_valid(req)) {\n        return avs_errno(AVS_EINVAL);\n    }\n\n    AVS_LIST(avs_coap_exchange_t) exchange =\n            client_exchange_create(req->code, &req->options, request_writer,\n                                   request_writer_arg, response_handler,\n                                   response_handler_arg);\n    if (!exchange) {\n        return avs_errno(AVS_ENOMEM);\n    }\n\n    avs_coap_exchange_id_t exchange_id;\n    avs_error_t err = client_exchange_start(ctx, &exchange, &exchange_id);\n    assert(!exchange);\n\n    if (avs_is_err(err)) {\n        if (avs_coap_exchange_id_valid(exchange_id)) {\n            AVS_LIST(avs_coap_exchange_t) *exchange_ptr =\n                    _avs_coap_find_client_exchange_ptr_by_id(ctx, exchange_id);\n            if (exchange_ptr) {\n                // Not using _avs_coap_client_exchange_cleanup() or\n                // cleanup_exchange(), because this function's docs say that\n                // response_handler is not called on error.\n                AVS_LIST_DELETE(exchange_ptr);\n            }\n        }\n        return err;\n    }\n\n    if (out_exchange_id) {\n        *out_exchange_id = exchange_id;\n    }\n    return AVS_OK;\n}\n\nvoid _avs_coap_client_exchange_cleanup(avs_coap_ctx_t *ctx,\n                                       avs_coap_exchange_t *exchange,\n                                       avs_error_t err) {\n    AVS_ASSERT(!AVS_LIST_FIND_PTR(&_avs_coap_get_base(ctx)->client_exchanges,\n                                  exchange),\n               \"exchange must be detached\");\n    assert(avs_coap_code_is_request(exchange->code));\n\n    if (_avs_coap_client_exchange_request_sent(exchange)) {\n        ctx->vtable->abort_delivery(ctx, AVS_COAP_EXCHANGE_CLIENT_REQUEST,\n                                    &exchange->token,\n                                    avs_is_err(err)\n                                            ? AVS_COAP_SEND_RESULT_FAIL\n                                            : AVS_COAP_SEND_RESULT_CANCEL,\n                                    err);\n    }\n    cleanup_exchange(ctx, exchange, NULL,\n                     avs_is_err(err)\n                             ? failure_state(err)\n                             : success_state(AVS_COAP_CLIENT_REQUEST_CANCEL));\n}\n\navs_error_t avs_coap_client_set_next_response_payload_offset(\n        avs_coap_ctx_t *ctx,\n        avs_coap_exchange_id_t exchange_id,\n        size_t next_response_payload_offset) {\n    avs_coap_exchange_t *exchange = NULL;\n    if (avs_coap_exchange_id_valid(exchange_id)) {\n        exchange = _avs_coap_find_client_exchange_by_id(ctx, exchange_id);\n    }\n    if (!exchange) {\n        return avs_errno(AVS_ENOENT);\n    }\n    // NOTE: The second clause creates a special exception that allows\n    // explicitly setting offset to 0 if the first request has not been sent yet\n    if (next_response_payload_offset\n                    <= exchange->by_type.client.next_response_payload_offset\n            && (next_response_payload_offset > 0 || exchange->token.size > 0)) {\n        return avs_errno(AVS_EINVAL);\n    }\n    exchange->by_type.client.next_response_payload_offset =\n            next_response_payload_offset;\n    return AVS_OK;\n}\n"
  },
  {
    "path": "deps/avs_coap/src/async/avs_coap_async_client.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef AVS_COAP_SRC_ASYNC_CLIENT_H\n#define AVS_COAP_SRC_ASYNC_CLIENT_H\n\n#include <avsystem/coap/async_client.h>\n\n#include \"avs_coap_ctx_vtable.h\"\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\n/**\n * Additional exchange data required by outgoing requests currently being\n * processed by us (acting as a CoAP client).\n */\ntypedef struct avs_coap_client_exchange_data {\n    /**\n     * User-defined handler to be called whenever a response to sent message\n     * is received.\n     *\n     * Note: called by the async layer from within\n     * @ref avs_coap_exchange_handlers_t#send_result_handler .\n     */\n    avs_coap_client_async_response_handler_t *handle_response;\n    void *handle_response_arg;\n\n    /**\n     * Internal handler used by async layer to handle intermediate responses,\n     * (e.g. 2.31 Continue).\n     */\n    avs_coap_send_result_handler_t *send_result_handler;\n    void *send_result_handler_arg;\n\n    /**\n     * Used to update BLOCK2 option in requests for more response payload.\n     * This is required because BERT may make the offset increment by more than\n     * a single block size.\n     */\n    size_t next_response_payload_offset;\n\n    /** ETag from the first response. */\n    avs_coap_etag_t etag;\n    /** Indicating that ETag from the first response was stored. */\n    bool etag_stored;\n} avs_coap_client_exchange_data_t;\n\nstruct avs_coap_exchange;\n\navs_error_t _avs_coap_client_exchange_send_first_chunk(\n        avs_coap_ctx_t *ctx,\n        AVS_LIST(struct avs_coap_exchange) **exchange_ptr_ptr);\n\nbool _avs_coap_client_exchange_request_sent(\n        const struct avs_coap_exchange *exchange);\n\n/** Cleans up any resources associated with client-side @p exchange . */\nvoid _avs_coap_client_exchange_cleanup(avs_coap_ctx_t *ctx,\n                                       struct avs_coap_exchange *exchange,\n                                       avs_error_t err);\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif // AVS_COAP_SRC_ASYNC_CLIENT_H\n"
  },
  {
    "path": "deps/avs_coap/src/async/avs_coap_async_server.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n/*\n * Implementation of server-side asynchronous operations on\n * @ref avs_coap_exchange_t .\n *\n * Handling a request is a multi-step process.\n *\n * Step 1: _avs_coap_async_incoming_packet_handle_single()\n *\n *                                 handle_request\n *                                  |    |    |\n *                                  |    |    | request for next\n *                     new request? |    |    | response block?\n *                   .--------------'    |    '----------------.\n *                   v                   |  next               |\n *          call on_new_request          | request             |\n *             |   |   |   |             |  block?             |\n *      not    |  (E) (R)  |             |                     v\n *   accepted? |           | accepted?   |      server_exchange_send_next_chunk\n *             v           |             |            call payload_writer\n *        send empty       |             |               |          |\n *         5.00 ISE        v             |               v         (E)\n *                       create          |         send response    |\n *                avs_coap_exchange_t    |          with payload    |\n *                         |             |               |          |\n *                         |    .--------'               v          v\n *                         v    v                   exchange is NOT returned\n *                  exchange is returned                PROCESSING ENDS\n *\n * Step 2: _avs_coap_async_incoming_packet_call_request_handler()\n *         This calls request_handler provided by the user.\n *\n * Step 3: _avs_coap_async_incoming_packet_send_response()\n *\n *                            call request_handler\n *                              |    |    |    | response set up and\n *       response not set up    |   (E)  (R)   | handler returned 0\n *       and handler returned 0 |              '--------------.\n *                              |                             |\n *          request has_more=1? | request complete?           v\n *                    .---------'--------.              call payload_writer\n *                    |                  |                 |          |\n *                    v                  v                 v         (E)\n *                send empty         send empty      send response\n *               2.31 Continue        5.00 ISE        with payload\n *\n * (R) - user handler returned a valid CoAP response code.\n * - the response exchange object is deleted if one exists,\n * - a response with given response code and without payload is sent.\n *\n * (E) - user handler returned an unexpected result.\n * - the response exchange object is deleted if one exists,\n * - a response with 5.00 ISE code and without payload is sent.\n *\n * Note: payload_writer cannot trigger a non-error response by returning\n * a valid CoAP code.\n *\n * Other remarks:\n * - Exchange is deleted either after sending the last response block, or\n *   if no incoming packets are matched to the exchange for at least\n *   EXCHANGE_LIFETIME (see RFC 7959, 2.4 \"Using the Block2 Option\")\n */\n\n#include <avs_coap_init.h>\n\n#include <avsystem/commons/avs_errno.h>\n#include <avsystem/commons/avs_utils.h>\n\n#include <avsystem/coap/async_server.h>\n#include <avsystem/coap/option.h>\n\n#include \"avs_coap_code_utils.h\"\n#include \"avs_coap_exchange.h\"\n#include \"avs_coap_observe.h\"\n\n#define MODULE_NAME coap\n#include <avs_coap_x_log_config.h>\n\n#include \"avs_coap_ctx.h\"\n#include \"options/avs_coap_options.h\"\n\nVISIBILITY_SOURCE_BEGIN\n\nstatic avs_time_monotonic_t get_exchange_deadline(avs_coap_ctx_t *ctx) {\n    const avs_coap_base_t *base = _avs_coap_get_base(ctx);\n    return avs_time_monotonic_add(avs_time_monotonic_now(),\n                                  base->max_exchange_update_time);\n}\n\ntypedef struct {\n    avs_coap_exchange_id_t exchange_id;\n\n    const avs_coap_borrowed_msg_t *request;\n\n    uint8_t response_code;\n    const avs_coap_options_t *response_options;\n\n    avs_coap_payload_writer_t *response_writer;\n    void *response_writer_arg;\n\n    avs_coap_server_async_request_handler_t *request_handler;\n    void *request_handler_arg;\n\n    avs_coap_notify_reliability_hint_t reliability_hint;\n    avs_coap_delivery_status_handler_t *delivery_handler;\n    void *delivery_handler_arg;\n\n    const uint32_t *observe_option_value;\n} server_exchange_create_args_t;\n\nstatic AVS_LIST(avs_coap_exchange_t)\nserver_exchange_create(avs_coap_ctx_t *ctx,\n                       const server_exchange_create_args_t *args) {\n    assert(avs_coap_code_is_response(args->response_code));\n\n    // Add a few extra bytes for BLOCK1/2 options in case request/response\n    // turns out to be large\n    const size_t request_key_options_capacity =\n            _avs_coap_options_request_key_size(&args->request->options)\n            + AVS_COAP_OPT_BLOCK_MAX_SIZE;\n    // We may need to add both BLOCK1 and BLOCK2 to response options\n    const size_t response_options_capacity =\n            args->response_options->capacity + AVS_COAP_OPT_BLOCK_MAX_SIZE * 2\n            + (args->observe_option_value ? AVS_COAP_OPT_OBSERVE_MAX_SIZE : 0);\n\n    AVS_LIST(avs_coap_exchange_t) exchange =\n            (AVS_LIST(avs_coap_exchange_t)) AVS_LIST_NEW_BUFFER(\n                    sizeof(avs_coap_exchange_t) + request_key_options_capacity\n                    + response_options_capacity);\n    if (!exchange) {\n        return NULL;\n    }\n\n    char *request_key_options_buffer =\n            ((char *) exchange) + sizeof(avs_coap_exchange_t);\n    char *response_options_buffer =\n            request_key_options_buffer + request_key_options_capacity;\n\n    *exchange = (avs_coap_exchange_t) {\n        .id = args->exchange_id,\n        .write_payload = args->response_writer,\n        .write_payload_arg = args->response_writer_arg,\n        .code = args->response_code,\n        .token = args->request->token,\n        .eof_cache = {\n            .empty = true\n        },\n        .by_type = {\n            .server = {\n                .request_handler = args->request_handler,\n                .request_handler_arg = args->request_handler_arg,\n                .reliability_hint = args->reliability_hint,\n                .delivery_handler = args->delivery_handler,\n                .delivery_handler_arg = args->delivery_handler_arg,\n                .exchange_deadline = get_exchange_deadline(ctx),\n                .request_code = args->request->code,\n                .request_key_options = _avs_coap_options_copy_request_key(\n                        &args->request->options, request_key_options_buffer,\n                        request_key_options_capacity),\n                .request_key_options_buffer = request_key_options_buffer\n            }\n        },\n        .options_buffer_size =\n                response_options_capacity + request_key_options_capacity\n    };\n\n    // DO NOT attempt to put this within the compound literal above.\n    // See T2393 or [COMPOUND-LITERAL-FAM-ASSIGNMENT-TRAP]\n    exchange->options = _avs_coap_options_copy(args->response_options,\n                                               response_options_buffer,\n                                               response_options_capacity);\n\n#ifdef WITH_AVS_COAP_OBSERVE\n    if (args->observe_option_value) {\n        avs_coap_options_remove_by_number(&exchange->options,\n                                          AVS_COAP_OPTION_OBSERVE);\n        if (avs_is_err(avs_coap_options_add_observe(\n                    &exchange->options,\n                    (uint32_t) *args->observe_option_value))) {\n            AVS_UNREACHABLE();\n        }\n    }\n#endif // WITH_AVS_COAP_OBSERVE\n\n    return exchange;\n}\n\n#ifdef WITH_AVS_COAP_BLOCK\nstatic bool is_last_response_block_sent(const avs_coap_exchange_t *exchange) {\n    avs_coap_option_block_t block;\n\n    // BLOCK response? Exchange is done after sending the last response block\n    int result = avs_coap_options_get_block(&exchange->options, AVS_COAP_BLOCK2,\n                                            &block);\n    if (result == 0 && block.has_more) {\n        return false;\n    }\n\n    result = avs_coap_options_get_block(&exchange->options, AVS_COAP_BLOCK1,\n                                        &block);\n    // Non-BLOCK response to a non-BLOCK request? Exchange is done after\n    // sending the response\n    if (result != 0) {\n        return true;\n    }\n\n    // Non-BLOCK response to a BLOCK request? Exchange is done after sending\n    // a response to last request block\n    return !block.has_more;\n}\n#endif // WITH_AVS_COAP_BLOCK\n\nstatic bool is_exchange_done(const avs_coap_exchange_t *exchange) {\n    switch (exchange->by_type.server.reliability_hint) {\n    case AVS_COAP_NOTIFY_PREFER_CONFIRMABLE:\n        // CON response? we're not done until the initial CON request has been\n        // ACKed, which is handled in send_result_handler\n        if (!exchange->by_type.server.con_block_acked) {\n            return false;\n        }\n        break;\n    case AVS_COAP_NOTIFY_PREFER_NON_CONFIRMABLE:\n        break;\n    }\n\n#ifdef WITH_AVS_COAP_BLOCK\n    return is_last_response_block_sent(exchange);\n#else  // WITH_AVS_COAP_BLOCK\n    return true;\n#endif // WITH_AVS_COAP_BLOCK\n}\n\nstatic void cancel_notification_on_error(avs_coap_ctx_t *ctx,\n                                         avs_coap_observe_id_t observe_id,\n                                         uint8_t response_code) {\n    if (!avs_coap_code_is_success(response_code)) {\n        LOG(DEBUG,\n            _(\"Non-success notification code (\") \"%s\" _(\n                    \"): cancelling observation\"),\n            AVS_COAP_CODE_STRING(response_code));\n\n#ifdef WITH_AVS_COAP_OBSERVE\n        avs_coap_observe_cancel(ctx, observe_id);\n#endif // WITH_AVS_COAP_OBSERVE\n        (void) ctx;\n        (void) observe_id;\n    }\n}\n\nstatic avs_coap_send_result_handler_result_t\nsend_result_handler(avs_coap_ctx_t *ctx,\n                    avs_coap_send_result_t send_result,\n                    avs_error_t fail_err,\n                    const avs_coap_borrowed_msg_t *response,\n                    void *exchange_) {\n    AVS_ASSERT(response == NULL, \"response to a response makes no sense; this \"\n                                 \"should be detected by lower layers\");\n    (void) response;\n\n    avs_coap_exchange_t *exchange = (avs_coap_exchange_t *) exchange_;\n    avs_coap_exchange_id_t exchange_id = exchange->id;\n\n    if (!_avs_coap_find_server_exchange_ptr_by_id(ctx, exchange_id)) {\n        // This might happen if we sent a notification, the observation got\n        // already cancelled and only now is the transport layer giving up on\n        // transmissions.\n        assert(send_result == AVS_COAP_SEND_RESULT_CANCEL);\n        return AVS_COAP_RESPONSE_ACCEPTED;\n    }\n\n    switch (send_result) {\n    case AVS_COAP_SEND_RESULT_PARTIAL_CONTENT:\n    case AVS_COAP_SEND_RESULT_OK:\n        AVS_ASSERT(avs_is_ok(fail_err),\n                   \"Error code passed for successful send\");\n        break;\n    default:\n        AVS_ASSERT(avs_is_err(fail_err),\n                   \"No error code passed for failed send\");\n        break;\n    }\n\n    avs_coap_server_exchange_data_t *server = &exchange->by_type.server;\n    if (avs_is_ok(fail_err)) {\n        server->con_block_acked = true;\n#ifdef WITH_AVS_COAP_BLOCK\n        if (!is_last_response_block_sent(exchange)) {\n            return AVS_COAP_RESPONSE_ACCEPTED;\n        }\n#endif // WITH_AVS_COAP_BLOCK\n    }\n\n    AVS_ASSERT(server->delivery_handler,\n               \"send_result_handler called for an exchange without \"\n               \"user-defined delivery handler; this should not happen\");\n\n    uint8_t code = exchange->code;\n    avs_coap_token_t token = exchange->token;\n\n    server->delivery_handler(ctx, fail_err, server->delivery_handler_arg);\n\n    bool is_timeout = fail_err.category == AVS_COAP_ERR_CATEGORY\n                      && fail_err.code == AVS_COAP_ERR_TIMEOUT;\n    bool is_rst = fail_err.category == AVS_COAP_ERR_CATEGORY\n                  && fail_err.code == AVS_COAP_ERR_UDP_RESET_RECEIVED;\n    (void) is_timeout;\n    (void) is_rst;\n\n    bool do_cancel_on_error = avs_is_ok(fail_err);\n#ifdef WITH_AVS_COAP_OBSERVE_FORCE_CANCEL_ON_UNACKED_ERROR\n    do_cancel_on_error = do_cancel_on_error || is_timeout || is_rst;\n#endif // WITH_AVS_COAP_OBSERVE_FORCE_CANCEL_ON_UNACKED_ERROR\n\n    if (do_cancel_on_error) {\n        cancel_notification_on_error(ctx,\n                                     (avs_coap_observe_id_t) {\n                                         .token = token\n                                     },\n                                     code);\n    }\n#if defined(WITH_AVS_COAP_OBSERVE) \\\n        && defined(WITH_AVS_COAP_OBSERVE_CANCEL_ON_TIMEOUT)\n    else if (is_timeout) {\n        avs_coap_observe_cancel(ctx, (avs_coap_observe_id_t) {\n                                         .token = token\n                                     });\n    }\n#endif /* defined(WITH_AVS_COAP_OBSERVE) && \\\n          defined(WITH_AVS_COAP_OBSERVE_CANCEL_ON_TIMEOUT) */\n\n    // delivery status handler or observe cancel handler\n    // might have canceled the exchange\n    AVS_LIST(avs_coap_exchange_t) *exchange_ptr =\n            _avs_coap_find_server_exchange_ptr_by_id(ctx, exchange_id);\n    if (!exchange_ptr) {\n        return AVS_COAP_RESPONSE_ACCEPTED;\n    }\n\n    // make sure we won't call the handler again during exchange cleanup\n    (*exchange_ptr)->by_type.server.delivery_handler = NULL;\n    _avs_coap_server_exchange_cleanup(ctx, AVS_LIST_DETACH(exchange_ptr),\n                                      fail_err);\n\n    return AVS_COAP_RESPONSE_ACCEPTED;\n}\n\nstatic avs_error_t send_ise(avs_coap_ctx_t *ctx,\n                            const avs_coap_token_t *token,\n                            avs_coap_send_result_handler_t *result_handler,\n                            void *result_handler_arg) {\n    avs_coap_borrowed_msg_t msg = {\n        .code = AVS_COAP_CODE_INTERNAL_SERVER_ERROR,\n        .token = *token\n    };\n\n    return ctx->vtable->send_message(ctx, &msg, result_handler,\n                                     result_handler_arg);\n}\n\nstatic avs_error_t server_exchange_send_next_chunk(\n        avs_coap_ctx_t *ctx,\n        AVS_LIST(avs_coap_exchange_t) *exchange_ptr,\n        avs_coap_notify_reliability_hint_t reliability_hint) {\n    AVS_ASSERT(AVS_LIST_FIND_PTR(&_avs_coap_get_base(ctx)->server_exchanges,\n                                 *exchange_ptr)\n                       != NULL,\n               \"not a started server exchange\");\n\n    avs_coap_exchange_id_t id = (*exchange_ptr)->id;\n    avs_coap_send_result_handler_t *handler = NULL;\n    if (reliability_hint == AVS_COAP_NOTIFY_PREFER_CONFIRMABLE) {\n        handler = send_result_handler;\n    }\n\n    // store token, exchange may be cancelled by user\n    avs_coap_token_t token = (*exchange_ptr)->token;\n\n    // token not changed: responses MUST echo token of the request\n    avs_error_t err =\n            _avs_coap_exchange_send_next_chunk(ctx, *exchange_ptr, handler,\n                                               *exchange_ptr);\n\n    if (avs_is_err(err)) {\n        if (err.category == AVS_COAP_ERR_CATEGORY\n                && (err.code == AVS_COAP_ERR_MESSAGE_TOO_BIG\n                    || err.code == AVS_COAP_ERR_PAYLOAD_WRITER_FAILED)) {\n            // err is already set, don't overwrite it\n            send_ise(ctx, &token, handler, *exchange_ptr);\n        } else if (err.category == AVS_COAP_ERR_CATEGORY\n                   && err.code == AVS_COAP_ERR_EXCHANGE_CANCELED) {\n            // err is already set, don't overwrite it\n            send_ise(ctx, &token, NULL, NULL);\n        } else {\n            return err;\n        }\n    }\n\n    // If the element *before* current exchange gets canceled as a result of\n    // calling user-defined payload_writer, exchange_ptr is not valid any more\n    // even though _avs_coap_exchange_send_next_chunk does not detect it was\n    // canceled - because it wasn't\n    exchange_ptr = _avs_coap_find_server_exchange_ptr_by_id(ctx, id);\n\n    if (exchange_ptr && is_exchange_done(*exchange_ptr)) {\n        _avs_coap_server_exchange_cleanup(ctx, AVS_LIST_DETACH(exchange_ptr),\n                                          AVS_OK);\n    }\n    return err;\n}\n\navs_time_monotonic_t\n_avs_coap_async_server_abort_timedout_exchanges(avs_coap_ctx_t *ctx) {\n    avs_coap_base_t *coap_base = _avs_coap_get_base(ctx);\n    while (coap_base->server_exchanges\n           && avs_time_monotonic_valid(coap_base->server_exchanges->by_type\n                                               .server.exchange_deadline)\n           && !avs_time_monotonic_before(avs_time_monotonic_now(),\n                                         coap_base->server_exchanges->by_type\n                                                 .server.exchange_deadline)) {\n        LOG(DEBUG, _(\"exchange \") \"%s\" _(\" timed out\"),\n            AVS_UINT64_AS_STRING(coap_base->server_exchanges->id.value));\n\n        _avs_coap_server_exchange_cleanup(\n                ctx, AVS_LIST_DETACH(&coap_base->server_exchanges),\n                _avs_coap_err(AVS_COAP_ERR_TIMEOUT));\n    }\n\n    if (coap_base->server_exchanges) {\n        return coap_base->server_exchanges->by_type.server.exchange_deadline;\n    } else {\n        return AVS_TIME_MONOTONIC_INVALID;\n    }\n}\n\nstatic AVS_LIST(avs_coap_exchange_t) *\ninsert_server_exchange(avs_coap_ctx_t *ctx, avs_coap_exchange_t *new_exchange) {\n    avs_coap_base_t *coap_base = _avs_coap_get_base(ctx);\n    AVS_LIST(avs_coap_exchange_t) *insert_ptr = &coap_base->server_exchanges;\n    AVS_LIST_ITERATE_PTR(insert_ptr) {\n        if (avs_time_monotonic_before(\n                    new_exchange->by_type.server.exchange_deadline,\n                    (*insert_ptr)->by_type.server.exchange_deadline)) {\n            break;\n        }\n    }\n\n    AVS_LIST_INSERT(insert_ptr, new_exchange);\n    _avs_coap_reschedule_retry_or_request_expired_job(\n            ctx, coap_base->server_exchanges->by_type.server.exchange_deadline);\n\n    return insert_ptr;\n}\n\nstatic AVS_LIST(avs_coap_exchange_t) *\nrefresh_exchange(avs_coap_ctx_t *ctx,\n                 AVS_LIST(avs_coap_exchange_t) *exchange_ptr) {\n    (*exchange_ptr)->by_type.server.exchange_deadline =\n            get_exchange_deadline(ctx);\n    return insert_server_exchange(ctx, AVS_LIST_DETACH(exchange_ptr));\n}\n\navs_coap_exchange_id_t avs_coap_server_accept_async_request(\n        avs_coap_server_ctx_t *ctx,\n        avs_coap_server_async_request_handler_t *request_handler,\n        void *request_handler_arg) {\n    if (!ctx) {\n        LOG(ERROR, _(\"server_ctx must not be NULL\"));\n        return AVS_COAP_EXCHANGE_ID_INVALID;\n    }\n    if (!request_handler) {\n        LOG(ERROR, _(\"request_handler must not be NULL\"));\n        return AVS_COAP_EXCHANGE_ID_INVALID;\n    }\n    if (avs_coap_exchange_id_valid(ctx->exchange_id)) {\n        LOG(ERROR, _(\"cannot accept a request twice\"));\n        return AVS_COAP_EXCHANGE_ID_INVALID;\n    }\n\n    // ID assigned here will be kept through the _avs_coap_exchange_start call\n    // to allow referring to this exchange for its whole lifetime\n    avs_coap_exchange_id_t id = _avs_coap_generate_exchange_id(ctx->coap_ctx);\n\n    // NOTE: we create a temporary exchange with empty response options,\n    // because we don't know how much space we'll need for them - it will\n    // only become known after the user calls\n    // @ref avs_coap_async_server_setup_response . We still need some space\n    // for storing request options in order to match future requests that are\n    // a part of the same exchange.\n    //\n    // Each call to @ref server_exchange_send_next_chunk sets up a CoAP message\n    // based on current contents of the exchange, so we use 2.31 Continue code\n    // here to ensure exactly that kind of response.\n    avs_coap_options_t empty_options = avs_coap_options_create_empty(NULL, 0);\n\n    server_exchange_create_args_t exchange_create_args = {\n        .exchange_id = id,\n        .request = ctx->request,\n        .response_code = AVS_COAP_CODE_CONTINUE,\n        .response_options = &empty_options,\n        .request_handler = request_handler,\n        .request_handler_arg = request_handler_arg\n    };\n    AVS_LIST(avs_coap_exchange_t) response_exchange =\n            server_exchange_create(ctx->coap_ctx, &exchange_create_args);\n    if (!response_exchange) {\n        return AVS_COAP_EXCHANGE_ID_INVALID;\n    }\n\n    ctx->exchange_id = id;\n    insert_server_exchange(ctx->coap_ctx, response_exchange);\n\n    return ctx->exchange_id;\n}\n\nbool _avs_coap_response_header_valid(const avs_coap_response_header_t *res) {\n    if (!avs_coap_code_is_response(res->code)) {\n        LOG(WARNING, _(\"non-response code \") \"%s\" _(\" used in response header\"),\n            AVS_COAP_CODE_STRING(res->code));\n        return false;\n    }\n    if (res->code == AVS_COAP_CODE_CONTINUE) {\n        LOG(WARNING,\n            \"%s\" _(\" responses are handled internally and not allowed \"\n                   \"in avs_coap_server_setup_async_response\"),\n            AVS_COAP_CODE_STRING(res->code));\n        return false;\n    }\n\n    return _avs_coap_options_valid(&res->options);\n}\n\navs_error_t\navs_coap_server_setup_async_response(avs_coap_request_ctx_t *ctx,\n                                     const avs_coap_response_header_t *response,\n                                     avs_coap_payload_writer_t *response_writer,\n                                     void *response_writer_arg) {\n    if (!ctx) {\n        LOG(ERROR, _(\"no request to respond to\"));\n        return avs_errno(AVS_EINVAL);\n    }\n    if (!response) {\n        LOG(ERROR, _(\"response must be provided\"));\n        return avs_errno(AVS_EINVAL);\n    }\n\n    avs_coap_ctx_t *coap_ctx = _avs_coap_ctx_from_request_ctx(ctx);\n    AVS_LIST(avs_coap_exchange_t) *response_exchange_ptr =\n            _avs_coap_find_server_exchange_ptr_by_id(coap_ctx,\n                                                     ctx->exchange_id);\n    if (!response_exchange_ptr) {\n        LOG(ERROR, _(\"invalid exchange ID: \") \"%s\",\n            AVS_UINT64_AS_STRING(ctx->exchange_id.value));\n        return avs_errno(AVS_EINVAL);\n    }\n\n    if (!_avs_coap_response_header_valid(response)) {\n        return avs_errno(AVS_EINVAL);\n    }\n\n    if (!ctx->observe_established\n            && _avs_coap_option_exists(&response->options,\n                                       AVS_COAP_OPTION_OBSERVE)) {\n        LOG(ERROR,\n            _(\"Observe option in response, but observe is not established\"));\n        return avs_errno(AVS_EINVAL);\n    }\n\n    // Now that we actually know what options are included in the response,\n    // recreate the exchange object with the same ID.\n    const uint32_t *observe_option_value =\n            ctx->observe_established\n                    ? &(uint32_t) { _avs_coap_observe_initial_option_value() }\n                    : NULL;\n    server_exchange_create_args_t exchange_create_args = {\n        .exchange_id = ctx->exchange_id,\n        .request = &ctx->request,\n        .response_code = response->code,\n        .response_options = &response->options,\n        .response_writer = response_writer,\n        .response_writer_arg = response_writer_arg,\n        .request_handler =\n                (*response_exchange_ptr)->by_type.server.request_handler,\n        .request_handler_arg =\n                (*response_exchange_ptr)->by_type.server.request_handler_arg,\n        .observe_option_value = observe_option_value\n    };\n    AVS_LIST(avs_coap_exchange_t) new_exchange =\n            server_exchange_create(ctx->coap_ctx, &exchange_create_args);\n\n    if (!new_exchange) {\n        return avs_errno(AVS_ENOMEM);\n    }\n\n    // NOTE: it might seem tempting to delete the exchange before recreating\n    // it, but that is problematic. If we delete the old exchange object, and\n    // fail to create a new one, the caller doesn't know if this function\n    // failed because of invalid input, or because of out-of-memory condition,\n    // so it is impossible for them to know whether to free\n    // request_handler_arg or not. And if we call request_handler with CLEANUP\n    // status, this results in a recursive call, and the original caller will\n    // have to be wary of use-after-free.\n    //\n    // Deleting the old exchange only if we're sure we have a new copy seems\n    // the most robust solution.\n    AVS_LIST_DELETE(response_exchange_ptr);\n    insert_server_exchange(coap_ctx, new_exchange);\n\n    ctx->response_setup = true;\n    return AVS_OK;\n}\n\n#ifdef WITH_AVS_COAP_BLOCK\nstatic int get_request_block_option(const avs_coap_borrowed_msg_t *request,\n                                    avs_coap_option_block_t *out_block1) {\n    switch (avs_coap_options_get_block(&request->options, AVS_COAP_BLOCK1,\n                                       out_block1)) {\n    case 0:\n        return 0;\n    case AVS_COAP_OPTION_MISSING:\n        return -1;\n    default:\n        AVS_UNREACHABLE(\"malformed option got through packet validation\");\n        return -1;\n    }\n}\n\nstatic size_t\nget_request_payload_offset(const avs_coap_borrowed_msg_t *request) {\n    avs_coap_option_block_t block1;\n    // request->payload_offset refers to payload offset in a single CoAP message\n    // payload if it's received in chunks, which can happen if CoAP/TCP is used.\n    if (get_request_block_option(request, &block1)) {\n        return request->payload_offset;\n    }\n    return block1.seq_num * block1.size + request->payload_offset;\n}\n#else  // WITH_AVS_COAP_BLOCK\nstatic size_t\nget_request_payload_offset(const avs_coap_borrowed_msg_t *request) {\n    return request->payload_offset;\n}\n#endif // WITH_AVS_COAP_BLOCK\n\nstatic bool\nis_request_message_finished(const avs_coap_borrowed_msg_t *request) {\n    return request->payload_offset + request->payload_size\n           == request->total_payload_size;\n}\n\nstatic bool is_entire_request_finished(const avs_coap_borrowed_msg_t *request) {\n    if (!is_request_message_finished(request)) {\n        return false;\n    }\n#ifdef WITH_AVS_COAP_BLOCK\n    avs_coap_option_block_t block1;\n    if (get_request_block_option(request, &block1)) {\n        return true;\n    }\n    return !block1.has_more;\n#else  // WITH_AVS_COAP_BLOCK\n    return true;\n#endif // WITH_AVS_COAP_BLOCK\n}\n\nstatic uint8_t response_code_from_result(int result) {\n    static const uint8_t DEFAULT_CODE = AVS_COAP_CODE_INTERNAL_SERVER_ERROR;\n\n    if (_avs_coap_code_in_range(result)) {\n        if (avs_coap_code_is_response((uint8_t) result)) {\n            return (uint8_t) result;\n        }\n\n        LOG(WARNING,\n            \"%s\" _(\" is not a valid response code, sending \") \"%s\" _(\n                    \" instead\"),\n            AVS_COAP_CODE_STRING((uint8_t) result),\n            AVS_COAP_CODE_STRING(DEFAULT_CODE));\n    } else {\n        LOG(DEBUG,\n            \"%d\" _(\" does not represent a correct CoAP code, sending \") \"%s\" _(\n                    \" instead\"),\n            result, AVS_COAP_CODE_STRING(DEFAULT_CODE));\n    }\n\n    return DEFAULT_CODE;\n}\n\nstatic avs_error_t send_empty_response(avs_coap_ctx_t *ctx,\n                                       const avs_coap_token_t *request_token,\n                                       uint8_t code) {\n    avs_coap_borrowed_msg_t msg = {\n        .code = code,\n        .token = *request_token\n    };\n    return ctx->vtable->send_message(ctx, &msg, NULL, NULL);\n}\n\ntypedef enum {\n    OBSERVE_MISSING = -1,\n    OBSERVE_REGISTER = 0,\n    OBSERVE_DEREGISTER = 1\n} observe_value_t;\n\n#ifdef WITH_AVS_COAP_OBSERVE\nstatic observe_value_t\nget_observe_option(const avs_coap_borrowed_msg_t *request) {\n    uint32_t observe_value;\n    if (avs_coap_options_get_observe(&request->options, &observe_value) != 0) {\n        return OBSERVE_MISSING;\n    }\n\n    switch (observe_value) {\n    case 0:\n        return OBSERVE_REGISTER;\n    case 1:\n        return OBSERVE_DEREGISTER;\n    default:\n        LOG(DEBUG, _(\"invalid Observe value: \") \"%\" PRIu32, observe_value);\n        return OBSERVE_MISSING;\n    }\n}\n\nstatic observe_value_t\nhandle_observe_option(avs_coap_ctx_t *ctx,\n                      const avs_coap_borrowed_msg_t *request) {\n    observe_value_t observe_value = get_observe_option(request);\n    if (observe_value == OBSERVE_MISSING) {\n        return OBSERVE_MISSING;\n    }\n\n    if (request->payload_offset == 0) {\n        // Cancel observe, if already exists. This ensure that the user\n        // request-handler always is in a position where an old observation\n        // state is removed.\n        //\n        // Make sure to only do this for the first chunk of the message, to\n        // not cancel observe multiple times unnecessarily.\n        //\n        // TODO: this should probably only be called for the first request\n        // block; currently this is repeated for every single one.\n        const avs_coap_observe_id_t observe_id = {\n            .token = request->token\n        };\n        avs_coap_observe_cancel(ctx, observe_id);\n    }\n    return observe_value;\n}\n#endif // WITH_AVS_COAP_OBSERVE\n\nint _avs_coap_async_incoming_packet_call_request_handler(\n        avs_coap_ctx_t *ctx, avs_coap_exchange_t *exchange) {\n    assert(ctx);\n    assert(exchange->by_type.server.request_handler);\n    avs_coap_base_t *coap_base = _avs_coap_get_base(ctx);\n    assert(avs_coap_exchange_id_equal(coap_base->request_ctx.exchange_id,\n                                      exchange->id));\n\n    const avs_coap_observe_id_t *observe_id = NULL;\n#ifdef WITH_AVS_COAP_OBSERVE\n    if (handle_observe_option(ctx, &coap_base->request_ctx.request)\n            == OBSERVE_REGISTER) {\n        // avs_coap_observe_id_t is basically just a blessed\n        // avs_coap_token_t, so let's pretend the token is actually an\n        // observe ID, it should be safe\n        AVS_STATIC_ASSERT(sizeof(coap_base->request_ctx.request.token)\n                                  == sizeof(*observe_id),\n                          observe_id_and_token_have_the_same_size);\n        observe_id = AVS_CONTAINER_OF(&coap_base->request_ctx.request.token,\n                                      const avs_coap_observe_id_t, token);\n    }\n#endif // WITH_AVS_COAP_OBSERVE\n    bool entire_request_finished =\n            is_entire_request_finished(&coap_base->request_ctx.request);\n    avs_coap_server_request_state_t state =\n            entire_request_finished ? AVS_COAP_SERVER_REQUEST_RECEIVED\n                                    : AVS_COAP_SERVER_REQUEST_PARTIAL_CONTENT;\n\n    const avs_coap_server_async_request_t async_request = {\n        .header = {\n            .code = coap_base->request_ctx.request.code,\n            .options = coap_base->request_ctx.request.options,\n        },\n        .payload_offset =\n                get_request_payload_offset(&coap_base->request_ctx.request),\n        .payload = coap_base->request_ctx.request.payload,\n        .payload_size = coap_base->request_ctx.request.payload_size\n    };\n\n    LOG(DEBUG, _(\"exchange \") \"%s\" _(\": request_handler, \") \"%s\",\n        AVS_UINT64_AS_STRING(exchange->id.value),\n        entire_request_finished ? \"full request\" : \"partial content\");\n\n    return exchange->by_type.server.request_handler(\n            &coap_base->request_ctx, exchange->id, state, &async_request,\n            observe_id, exchange->by_type.server.request_handler_arg);\n}\n\nstatic bool matching_request_options(uint16_t opt_number) {\n    return _avs_coap_option_is_critical(opt_number)\n           || opt_number == AVS_COAP_OPTION_CONTENT_FORMAT;\n}\n\nstatic bool request_matches_exchange(const avs_coap_borrowed_msg_t *request,\n                                     const avs_coap_exchange_t *exchange) {\n    if (exchange->by_type.server.request_code != request->code) {\n        LOG(TRACE, _(\"looking for CoAP code \") \"%s\" _(\", got \") \"%s\",\n            AVS_COAP_CODE_STRING(exchange->by_type.server.request_code),\n            AVS_COAP_CODE_STRING(request->code));\n        return false;\n    }\n    /* Check if token, critical options, and content-format match, as the\n     * request may be the non-blockwise continuation [TCP] */\n    if (request->payload_offset > 0\n            && avs_coap_token_equal(&request->token, &exchange->token)\n            && _avs_coap_selected_options_equal(\n                       &request->options,\n                       &exchange->by_type.server.request_key_options,\n                       matching_request_options)) {\n        return true;\n    }\n#ifdef WITH_AVS_COAP_BLOCK\n    /* If that failed, the request may still be a blockwise transfer\n     * continuation */\n    return _avs_coap_options_is_sequential_block_request(\n            &exchange->options,\n            &exchange->by_type.server.request_key_options,\n            &request->options,\n            exchange->by_type.server.expected_request_payload_offset);\n#else  // WITH_AVS_COAP_BLOCK\n    return false;\n#endif // WITH_AVS_COAP_BLOCK\n}\n\nstatic AVS_LIST(avs_coap_exchange_t) *\nfind_existing_response_exchange_ptr(avs_coap_ctx_t *ctx,\n                                    const avs_coap_borrowed_msg_t *request) {\n    AVS_LIST(avs_coap_exchange_t) *it;\n    AVS_LIST_FOREACH_PTR(it, &_avs_coap_get_base(ctx)->server_exchanges) {\n        if (avs_coap_code_is_response((*it)->code)\n                && request_matches_exchange(request, *it)) {\n            return it;\n        }\n    }\n    return NULL;\n}\n\n#ifdef WITH_AVS_COAP_BLOCK\nstatic void\nupdate_exchange_block1_option(avs_coap_exchange_t *exchange,\n                              const avs_coap_borrowed_msg_t *request,\n                              bool response_set_up) {\n    // Echo request BLOCK1 option, if any. Remove it if not present.\n    avs_coap_option_block_t block1;\n    int result = get_request_block_option(request, &block1);\n    avs_coap_options_remove_by_number(&exchange->options,\n                                      AVS_COAP_OPTION_BLOCK1);\n\n    if (result) {\n        return;\n    }\n\n    // Set has_more flag to false if response is set up. If user already started\n    // to respond to the message, then probably more request payload chunks\n    // aren't required.\n    block1.has_more = !response_set_up;\n\n    if (avs_is_err(avs_coap_options_add_block(&exchange->options, &block1))) {\n        AVS_UNREACHABLE(\"options buffer too small\");\n    }\n    exchange->by_type.server.expected_request_payload_offset +=\n            request->total_payload_size;\n}\n\nstatic void\nupdate_exchange_block2_option(avs_coap_exchange_t *exchange,\n                              const avs_coap_borrowed_msg_t *request) {\n    avs_coap_option_block_t block2;\n    int result = avs_coap_options_get_block(&request->options, AVS_COAP_BLOCK2,\n                                            &block2);\n    avs_coap_options_remove_by_number(&exchange->options,\n                                      AVS_COAP_OPTION_BLOCK2);\n\n    if (result == AVS_COAP_OPTION_MISSING) {\n        return;\n    }\n\n    // this will be set to false later when EOF is encountered when calling\n    // payload_writer\n    block2.has_more = true;\n\n    if (avs_is_err(avs_coap_options_add_block(&exchange->options, &block2))) {\n        AVS_UNREACHABLE(\"options buffer too small\");\n    }\n}\n#endif // WITH_AVS_COAP_BLOCK\n\nstatic avs_error_t\nsetup_response_from_nonzero_result(avs_coap_request_ctx_t *request_ctx,\n                                   int result) {\n    return avs_coap_server_setup_async_response(\n            request_ctx,\n            &(avs_coap_response_header_t) {\n                .code = response_code_from_result(result)\n            },\n            NULL, NULL);\n}\n\nstatic int validate_request_exchange_state(avs_coap_ctx_t *ctx) {\n    avs_coap_base_t *coap_base = _avs_coap_get_base(ctx);\n    if (!_avs_coap_find_server_exchange_ptr_by_id(\n                ctx, coap_base->request_ctx.exchange_id)) {\n        LOG(DEBUG, _(\"exchange \") \"%s\" _(\" canceled by user handler\"),\n            AVS_UINT64_AS_STRING(coap_base->request_ctx.exchange_id.value));\n        return AVS_COAP_CODE_INTERNAL_SERVER_ERROR;\n    }\n\n    if (!coap_base->request_ctx.response_setup\n            && is_entire_request_finished(&coap_base->request_ctx.request)) {\n        LOG(DEBUG,\n            _(\"request \") \"%s\" _(\" finished, but response still not set up and \"\n                                 \"request handler returned 0\"),\n            AVS_UINT64_AS_STRING(coap_base->request_ctx.exchange_id.value));\n        return AVS_COAP_CODE_INTERNAL_SERVER_ERROR;\n    }\n\n    return 0;\n}\n\nstatic avs_error_t continue_request_exchange(avs_coap_ctx_t *ctx) {\n    avs_coap_base_t *coap_base = _avs_coap_get_base(ctx);\n    AVS_LIST(avs_coap_exchange_t) *exchange_ptr =\n            _avs_coap_find_server_exchange_ptr_by_id(\n                    ctx, coap_base->request_ctx.exchange_id);\n    AVS_ASSERT(exchange_ptr, \"exchange cancellation by user handler should be \"\n                             \"detected in validate_exchange_state\");\n\n#ifdef WITH_AVS_COAP_BLOCK\n    update_exchange_block1_option(*exchange_ptr,\n                                  &coap_base->request_ctx.request,\n                                  coap_base->request_ctx.response_setup);\n    update_exchange_block2_option(*exchange_ptr,\n                                  &coap_base->request_ctx.request);\n#endif // WITH_AVS_COAP_BLOCK\n    return server_exchange_send_next_chunk(\n            ctx, exchange_ptr,\n            (*exchange_ptr)->by_type.server.reliability_hint);\n}\n\nstatic void cleanup_exchange_by_id(avs_coap_ctx_t *ctx,\n                                   const avs_coap_exchange_id_t exchange_id,\n                                   avs_error_t err) {\n    AVS_LIST(avs_coap_exchange_t) *exchange_ptr =\n            _avs_coap_find_server_exchange_ptr_by_id(ctx, exchange_id);\n    if (exchange_ptr) {\n        _avs_coap_server_exchange_cleanup(ctx, AVS_LIST_DETACH(exchange_ptr),\n                                          err);\n    }\n}\n\nstatic avs_error_t\nsend_response_to_request_chunk(avs_coap_ctx_t *ctx,\n                               int exchange_request_handler_result) {\n    int result = exchange_request_handler_result;\n    avs_error_t err = AVS_OK;\n\n    if (!result) {\n        result = validate_request_exchange_state(ctx);\n    }\n\n    avs_coap_base_t *coap_base = _avs_coap_get_base(ctx);\n    if (result) {\n        err = setup_response_from_nonzero_result(&coap_base->request_ctx,\n                                                 result);\n    }\n\n    if (avs_is_err(err)) {\n        // setup_response_from_nonzero_result() failed because exchange was\n        // canceled or another error occured during setting up a response.\n        // Exchange cannot be continued.\n        cleanup_exchange_by_id(ctx, coap_base->request_ctx.exchange_id, err);\n        return send_empty_response(ctx, &coap_base->request_ctx.request.token,\n                                   AVS_COAP_CODE_INTERNAL_SERVER_ERROR);\n    }\n\n    if (is_request_message_finished(&coap_base->request_ctx.request)\n            || coap_base->request_ctx.response_setup) {\n        return continue_request_exchange(ctx);\n    }\n\n    AVS_ASSERT(!is_entire_request_finished(&coap_base->request_ctx.request),\n               \"finished request without response setup is supposed to be \"\n               \"handled by validate_exchange_state\");\n    // There will be more payload for this message.\n    return AVS_OK;\n}\n\ntypedef struct {\n    avs_coap_server_new_async_request_handler_t *on_new_request;\n    void *on_new_request_arg;\n} user_defined_request_handler_t;\n\nstatic avs_error_t\nhandle_new_request(avs_coap_ctx_t *ctx,\n                   const avs_coap_borrowed_msg_t *request,\n                   user_defined_request_handler_t *user_handler,\n                   AVS_LIST(avs_coap_exchange_t) *out_exchange) {\n    if (!user_handler->on_new_request) {\n        LOG(ERROR, _(\"rejecting incoming \") \"%s\" _(\": on_new_request NULL\"),\n            AVS_COAP_CODE_STRING(request->code));\n        return send_empty_response(ctx, &request->token,\n                                   AVS_COAP_CODE_INTERNAL_SERVER_ERROR);\n    }\n\n    avs_coap_server_ctx_t server_ctx = {\n        .coap_ctx = ctx,\n        .request = request,\n        .exchange_id = AVS_COAP_EXCHANGE_ID_INVALID\n    };\n    const avs_coap_request_header_t request_header = {\n        .code = request->code,\n        .options = request->options\n    };\n\n    // NOTE: this function should never be called if any error happens on our\n    // way here, so we pass 0 as the error code.\n    int result = user_handler->on_new_request(&server_ctx, &request_header,\n                                              user_handler->on_new_request_arg);\n    if (result) {\n        avs_coap_exchange_cancel(ctx, server_ctx.exchange_id);\n        return send_empty_response(ctx, &request->token,\n                                   response_code_from_result(result));\n    }\n\n    if (!avs_coap_exchange_id_valid(server_ctx.exchange_id)) {\n        LOG(WARNING, _(\"on_new_request suceeded, but \") \"%s\" _(\" not accepted\"),\n            AVS_COAP_CODE_STRING(request->code));\n\n        return send_empty_response(ctx, &request->token,\n                                   AVS_COAP_CODE_INTERNAL_SERVER_ERROR);\n    }\n\n    if (!(*out_exchange = _avs_coap_find_server_exchange_by_id(\n                  ctx, server_ctx.exchange_id))) {\n        LOG(DEBUG,\n            _(\"on_new_request handler canceled exchange \") \"%s\" _(\n                    \" immediately after accepting it\"),\n            AVS_UINT64_AS_STRING(server_ctx.exchange_id.value));\n    }\n    return AVS_OK;\n}\n\nstatic avs_error_t handle_request(avs_coap_ctx_t *ctx,\n                                  const avs_coap_borrowed_msg_t *request,\n                                  user_defined_request_handler_t *user_handler,\n                                  AVS_LIST(avs_coap_exchange_t) *out_exchange) {\n    assert(avs_coap_code_is_request(request->code));\n    AVS_ASSERT(request->payload_offset + request->payload_size\n                       <= request->total_payload_size,\n               \"bug: payload_offset + payload_size > total_payload_size\");\n\n    AVS_LIST(avs_coap_exchange_t) *existing_exchange_ptr =\n            find_existing_response_exchange_ptr(ctx, request);\n\n    if (!existing_exchange_ptr) {\n        return handle_new_request(ctx, request, user_handler, out_exchange);\n    }\n\n    // Getting here means that incoming request was successfully matched to an\n    // existing response, and that it either contains more request payload, or\n    // requests more response payload.\n    (*existing_exchange_ptr)->token = request->token;\n    existing_exchange_ptr = refresh_exchange(ctx, existing_exchange_ptr);\n\n    avs_coap_server_exchange_data_t *server_data =\n            &(*existing_exchange_ptr)->by_type.server;\n\n    // scan-build for some reason assumes server_data may be NULL\n    assert(server_data);\n    assert(server_data->request_key_options.capacity\n           >= _avs_coap_options_request_key_size(&request->options));\n\n    server_data->request_key_options = _avs_coap_options_copy_request_key(\n            &request->options,\n            server_data->request_key_options.begin,\n            server_data->request_key_options.capacity);\n\n#ifdef WITH_AVS_COAP_BLOCK\n    // If the user didn't setup a final response yet, exchange code is set to\n    // Continue as initialized in @ref avs_coap_server_accept_async_request .\n    // That means we haven't finished receiving request payload yet, and the\n    // user needs more to decide what to do with the request.\n    if ((*existing_exchange_ptr)->code == AVS_COAP_CODE_CONTINUE) {\n        *out_exchange = *existing_exchange_ptr;\n        return AVS_OK;\n    } else {\n        // If exchange code is not Continue, it means the user called\n        // @ref avs_coap_server_setup_async_response , and we're currently\n        // sending response blocks (second or later BLOCK2), so we can drop\n        // BLOCK1 option completely.\n        avs_coap_options_remove_by_number(&(*existing_exchange_ptr)->options,\n                                          AVS_COAP_OPTION_BLOCK1);\n        update_exchange_block2_option(*existing_exchange_ptr, request);\n        // In case of Observe notifications, only the first block\n        // is supposed to have the Observe option; see RFC 7959,\n        // Figure 12: \"Observe Sequence with Block-Wise Response\"\n        avs_coap_options_remove_by_number(&(*existing_exchange_ptr)->options,\n                                          AVS_COAP_OPTION_OBSERVE);\n        // Also, subsequent blocks in a blockwise notification are just regular\n        // responses; don't send them as Confirmable because we don't support\n        // acknowledging Separate Responses properly\n        return server_exchange_send_next_chunk(\n                ctx, existing_exchange_ptr,\n                AVS_COAP_NOTIFY_PREFER_NON_CONFIRMABLE);\n    }\n#else  // WITH_AVS_COAP_BLOCK\n    *out_exchange = *existing_exchange_ptr;\n    return AVS_OK;\n#endif // WITH_AVS_COAP_BLOCK\n}\n\navs_error_t _avs_coap_async_incoming_packet_handle_single(\n        avs_coap_ctx_t *ctx,\n        uint8_t *in_buffer,\n        size_t in_buffer_size,\n        avs_coap_server_new_async_request_handler_t *on_new_request,\n        void *on_new_request_arg,\n        AVS_LIST(struct avs_coap_exchange) *out_exchange) {\n    assert(ctx);\n    assert(ctx->vtable);\n    assert(ctx->vtable->receive_message);\n\n    avs_coap_base_t *coap_base = _avs_coap_get_base(ctx);\n\n    memset(&coap_base->request_ctx, 0, sizeof(coap_base->request_ctx));\n    coap_base->request_ctx.coap_ctx = ctx;\n    avs_error_t err =\n            ctx->vtable->receive_message(ctx, in_buffer, in_buffer_size,\n                                         &coap_base->request_ctx.request);\n    if (avs_is_ok(err)\n            && avs_coap_code_is_request(coap_base->request_ctx.request.code)) {\n        assert(coap_base->request_ctx.request.payload_size > 0\n               || coap_base->request_ctx.request.payload_offset\n                          == coap_base->request_ctx.request.total_payload_size);\n        avs_coap_exchange_t *exchange = NULL;\n        user_defined_request_handler_t args = {\n            .on_new_request = on_new_request,\n            .on_new_request_arg = on_new_request_arg\n        };\n        err = handle_request(ctx, &coap_base->request_ctx.request, &args,\n                             &exchange);\n        if (exchange) {\n            assert(avs_is_ok(err));\n            coap_base->request_ctx.exchange_id = exchange->id;\n            *out_exchange = exchange;\n            return AVS_OK;\n        }\n    }\n\n    *out_exchange = NULL;\n    memset(&coap_base->request_ctx, 0, sizeof(coap_base->request_ctx));\n    return err;\n}\n\navs_error_t _avs_coap_async_incoming_packet_send_response(avs_coap_ctx_t *ctx,\n                                                          int call_result) {\n    avs_coap_base_t *coap_base = _avs_coap_get_base(ctx);\n    assert(avs_coap_exchange_id_valid(coap_base->request_ctx.exchange_id));\n    avs_error_t err = send_response_to_request_chunk(ctx, call_result);\n    memset(&coap_base->request_ctx, 0, sizeof(coap_base->request_ctx));\n    return err;\n}\n\navs_error_t _avs_coap_async_incoming_packet_simple_handle_single(\n        avs_coap_ctx_t *ctx,\n        uint8_t *in_buffer,\n        size_t in_buffer_size,\n        avs_coap_server_new_async_request_handler_t *on_new_request,\n        void *on_new_request_arg) {\n    avs_coap_exchange_t *exchange;\n    avs_error_t err = _avs_coap_async_incoming_packet_handle_single(\n            ctx, in_buffer, in_buffer_size, on_new_request, on_new_request_arg,\n            &exchange);\n    if (exchange) {\n        assert(avs_is_ok(err));\n        err = _avs_coap_async_incoming_packet_send_response(\n                ctx, _avs_coap_async_incoming_packet_call_request_handler(\n                             ctx, exchange));\n    }\n    return err;\n}\n\navs_error_t _avs_coap_async_incoming_packet_handle_and_exhaust_socket(\n        avs_coap_ctx_t *ctx,\n        uint8_t *in_buffer,\n        size_t in_buffer_size,\n        avs_coap_server_new_async_request_handler_t *on_new_request,\n        void *on_new_request_arg) {\n    avs_error_t err;\n    avs_net_socket_t *socket = _avs_coap_get_base(ctx)->socket;\n    avs_net_socket_opt_value_t socket_timeout;\n    if (avs_is_err((err = avs_net_socket_get_opt(\n                            socket, AVS_NET_SOCKET_OPT_RECV_TIMEOUT,\n                            &socket_timeout)))\n            || avs_is_err((err = avs_net_socket_set_opt(\n                                   socket, AVS_NET_SOCKET_OPT_RECV_TIMEOUT,\n                                   (avs_net_socket_opt_value_t) {\n                                       .recv_timeout = AVS_TIME_DURATION_ZERO\n                                   })))) {\n        return err;\n    }\n    while (avs_is_ok(err)) {\n        err = _avs_coap_async_incoming_packet_simple_handle_single(\n                ctx, in_buffer, in_buffer_size, on_new_request,\n                on_new_request_arg);\n        if (avs_is_ok(err) && _avs_coap_socket_definitely_exhausted(ctx)) {\n            // We can conclusively say that the socket is already exhausted,\n            // so no need to try receiving more packets.\n            break;\n        }\n    }\n    if (err.category == AVS_ERRNO_CATEGORY && err.code == AVS_ETIMEDOUT) {\n        // No more data possible to receive in a non-blocking way,\n        // it's not an error\n        err = AVS_OK;\n    }\n    avs_error_t restore_err =\n            avs_net_socket_set_opt(socket, AVS_NET_SOCKET_OPT_RECV_TIMEOUT,\n                                   socket_timeout);\n    return avs_is_ok(err) ? restore_err : err;\n}\n\navs_error_t avs_coap_async_handle_incoming_packet(\n        avs_coap_ctx_t *ctx,\n        avs_coap_server_new_async_request_handler_t *on_new_request,\n        void *on_new_request_arg) {\n    uint8_t *acquired_in_buffer;\n    size_t acquired_in_buffer_size;\n    avs_error_t err = _avs_coap_in_buffer_acquire(ctx, &acquired_in_buffer,\n                                                  &acquired_in_buffer_size);\n    if (avs_is_err(err)) {\n        return err;\n    }\n\n    err = _avs_coap_async_incoming_packet_handle_and_exhaust_socket(\n            ctx, acquired_in_buffer, acquired_in_buffer_size, on_new_request,\n            on_new_request_arg);\n    _avs_coap_in_buffer_release(ctx);\n    return err;\n}\n\nvoid _avs_coap_server_exchange_cleanup(avs_coap_ctx_t *ctx,\n                                       avs_coap_exchange_t *exchange,\n                                       avs_error_t err) {\n    avs_coap_base_t *coap_base = _avs_coap_get_base(ctx);\n    AVS_ASSERT(!AVS_LIST_FIND_PTR(&coap_base->server_exchanges, exchange),\n               \"exchange must be detached\");\n    assert(avs_coap_code_is_response(exchange->code));\n\n    avs_coap_server_exchange_data_t *server = &exchange->by_type.server;\n\n    if (server->request_handler) {\n        LOG(DEBUG, _(\"exchange \") \"%s\" _(\": request_handler, cleanup\"),\n            AVS_UINT64_AS_STRING(exchange->id.value));\n\n        server->request_handler(NULL, exchange->id,\n                                AVS_COAP_SERVER_REQUEST_CLEANUP, NULL, NULL,\n                                server->request_handler_arg);\n    } else {\n        avs_coap_send_result_t send_result;\n        avs_error_t abort_err;\n        if (avs_is_ok(err)) {\n            send_result = AVS_COAP_SEND_RESULT_OK;\n            abort_err = AVS_OK;\n        } else if (err.category == AVS_COAP_ERR_CATEGORY\n                   && err.code == AVS_COAP_ERR_EXCHANGE_CANCELED) {\n            send_result = AVS_COAP_SEND_RESULT_CANCEL;\n            abort_err = AVS_OK;\n        } else {\n            send_result = AVS_COAP_SEND_RESULT_FAIL;\n            abort_err = err;\n        }\n        // Notify exchanges don't have a request handler\n        ctx->vtable->abort_delivery(ctx, AVS_COAP_EXCHANGE_SERVER_NOTIFICATION,\n                                    &exchange->token, send_result, abort_err);\n    }\n\n    if (server->delivery_handler) {\n        server->delivery_handler(ctx, err, server->delivery_handler_arg);\n    }\n\n    AVS_LIST_DELETE(&exchange);\n    if (coap_base->server_exchanges) {\n        _avs_coap_reschedule_retry_or_request_expired_job(\n                ctx,\n                coap_base->server_exchanges->by_type.server.exchange_deadline);\n    }\n}\n\n#ifdef WITH_AVS_COAP_OBSERVE\navs_error_t\navs_coap_observe_async_start(avs_coap_request_ctx_t *ctx,\n                             avs_coap_observe_id_t id,\n                             avs_coap_observe_cancel_handler_t *cancel_handler,\n                             void *handler_arg) {\n    const avs_coap_request_header_t request_header = {\n        .code = ctx->request.code,\n        .options = ctx->request.options\n    };\n\n    avs_error_t err =\n            avs_coap_observe_start(_avs_coap_ctx_from_request_ctx(ctx), id,\n                                   &request_header, cancel_handler,\n                                   handler_arg);\n\n    ctx->observe_established = avs_is_ok(err);\n    return err;\n}\n\navs_error_t\navs_coap_notify_async(avs_coap_ctx_t *ctx,\n                      avs_coap_exchange_id_t *out_exchange_id,\n                      avs_coap_observe_id_t observe_id,\n                      const avs_coap_response_header_t *response_header,\n                      avs_coap_notify_reliability_hint_t reliability_hint,\n                      avs_coap_payload_writer_t *write_payload,\n                      void *write_payload_arg,\n                      avs_coap_delivery_status_handler_t *delivery_handler,\n                      void *delivery_handler_arg) {\n    if (!avs_coap_code_is_response(response_header->code)) {\n        LOG(ERROR, \"%s\" _(\" is not a valid response code\"),\n            AVS_COAP_CODE_STRING(response_header->code));\n        avs_errno(AVS_EINVAL);\n    }\n\n    if (!delivery_handler\n            && reliability_hint != AVS_COAP_NOTIFY_PREFER_NON_CONFIRMABLE) {\n        LOG(ERROR,\n            _(\"delivery_handler is mandatory for reliable notifications\"));\n        avs_errno(AVS_EINVAL);\n    }\n\n    // FIXME: Unsolicited non-confirmable notifications with an error code are\n    // currently broken, because of lack of the Observe option for error values,\n    // the lower, UDP layer assumes the message to be an ACK, not a NON.\n    //\n    // For reference, see assumptions in choose_msg_type() in avs_coap_udp_ctx.c\n    if (reliability_hint == AVS_COAP_NOTIFY_PREFER_NON_CONFIRMABLE\n            && !avs_coap_code_is_success(response_header->code)) {\n        LOG(ERROR,\n            _(\"Unsolicited notifications with an error code are currently \"\n              \"broken\"));\n        return avs_errno(AVS_EINVAL);\n    }\n\n    avs_coap_observe_notify_t notify;\n\n    avs_error_t err = _avs_coap_observe_setup_notify(ctx, &observe_id, &notify);\n    if (avs_is_err(err)) {\n        return err;\n    }\n\n    // Create a server exchange in a \"receiving request payload finished,\n    // response not sent yet\" state\n    server_exchange_create_args_t exchange_create_args = {\n        .exchange_id = _avs_coap_generate_exchange_id(ctx),\n        // setup a fake request similar to the original Observe\n        .request = &(avs_coap_borrowed_msg_t) {\n            .code = notify.request_code,\n            .token = observe_id.token,\n            .options = notify.request_key\n        },\n        .response_code = response_header->code,\n        .response_options = &response_header->options,\n        .response_writer = write_payload,\n        .response_writer_arg = write_payload_arg,\n        .reliability_hint = reliability_hint,\n        .delivery_handler = delivery_handler,\n        .delivery_handler_arg = delivery_handler_arg,\n        // RFC 7641, 3.2. Notifications:\n        // \"Non-2.xx responses do not include an Observe Option.\"\n        .observe_option_value = avs_coap_code_is_success(response_header->code)\n                                        ? &notify.observe_option_value\n                                        : NULL\n    };\n    AVS_LIST(avs_coap_exchange_t) exchange =\n            server_exchange_create(ctx, &exchange_create_args);\n    if (!exchange) {\n        return avs_errno(AVS_ENOMEM);\n    }\n\n    avs_coap_base_t *coap_base = _avs_coap_get_base(ctx);\n\n    AVS_LIST_INSERT(&coap_base->server_exchanges, exchange);\n\n    if (reliability_hint == AVS_COAP_NOTIFY_PREFER_NON_CONFIRMABLE) {\n        cancel_notification_on_error(ctx, observe_id, response_header->code);\n    }\n\n#    ifdef WITH_AVS_COAP_BLOCK\n    update_exchange_block2_option(exchange, exchange_create_args.request);\n#    endif // WITH_AVS_COAP_BLOCK\n    err = server_exchange_send_next_chunk(\n            ctx, &coap_base->server_exchanges,\n            coap_base->server_exchanges->by_type.server.reliability_hint);\n    AVS_LIST(avs_coap_exchange_t) *exchange_ptr =\n            _avs_coap_find_server_exchange_ptr_by_id(\n                    ctx, exchange_create_args.exchange_id);\n    if (avs_is_err(err)) {\n        if (exchange_ptr) {\n            // Not using _avs_coap_server_exchange_cleanup(), because this\n            // function's docs say that delivery_handler is not called on error.\n            AVS_LIST_DELETE(exchange_ptr);\n        }\n        return err;\n    }\n\n    if (out_exchange_id) {\n        *out_exchange_id = (exchange_ptr ? (*exchange_ptr)->id\n                                         : AVS_COAP_EXCHANGE_ID_INVALID);\n    }\n    return AVS_OK;\n}\n#endif // WITH_AVS_COAP_OBSERVE\n"
  },
  {
    "path": "deps/avs_coap/src/async/avs_coap_async_server.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef AVS_COAP_SRC_ASYNC_SERVER_H\n#define AVS_COAP_SRC_ASYNC_SERVER_H\n\n#include <avsystem/coap/async_server.h>\n\n#include \"avs_coap_ctx_vtable.h\"\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\n/**\n * Additional exchange data required by incoming requests currently being\n * processed by us (acting as a CoAP server).\n */\ntypedef struct avs_coap_server_exchange_data {\n    /**\n     * Internal handler used by async_server to handle incoming requests.\n     */\n    avs_coap_server_async_request_handler_t *request_handler;\n    void *request_handler_arg;\n\n    /** Flag indicating whether NON messages may be used if supported. */\n    avs_coap_notify_reliability_hint_t reliability_hint;\n\n    /**\n     * Flag indicating whether the initial CON block of a Confirmable\n     * notification has been ACKed by the remote endpoint.\n     */\n    bool con_block_acked;\n\n    /**\n     * User-defined response delivery handler. May be non-NULL for Observe\n     * notifications.\n     */\n    avs_coap_delivery_status_handler_t *delivery_handler;\n    void *delivery_handler_arg;\n\n    /**\n     * A time point at which the exchange should be considered failed.\n     *\n     * Used for response exchanges to detect when the remote client stops\n     * requesting further response blocks.\n     */\n    avs_time_monotonic_t exchange_deadline;\n\n    /**\n     * CoAP code of the last received request message.\n     * Used to match individual request blocks to a specific exchange.\n     */\n    uint8_t request_code;\n    /**\n     * CoAP options of the last received request message.\n     * Used to match individual request blocks to a specific exchange.\n     */\n    avs_coap_options_t request_key_options;\n\n    /** avs_coap_server_exchange_t#request_key_options storage. */\n    char *request_key_options_buffer;\n\n    /**\n     * Used to check if requests' BLOCK1s are received sequentially. This is\n     * required because BERT may make the offset increment by more than a single\n     * block size.\n     */\n    size_t expected_request_payload_offset;\n} avs_coap_server_exchange_data_t;\n\nstruct avs_coap_server_ctx {\n    avs_coap_ctx_t *coap_ctx;\n\n    /**\n     * ID of the server exchange created by user call to\n     * @ref avs_coap_server_accept_async_request .\n     *\n     * Stored within this context to:\n     * - prevent the user from accepting the same request more than once (i.e.\n     *   allocating more than 1 exchange for processing the same request),\n     * - delay sending the Continue response until after\n     *   @ref avs_coap_server_new_async_request_handler_t finishes. This allows\n     *   us to handle all nonzero return values from that handler as error\n     *   response codes; otherwise we'd end up sending two responses to the\n     *   same request (2.31 Continue first and then an error response later).\n     */\n    avs_coap_exchange_id_t exchange_id;\n\n    /** Incoming request message we're currently processing. */\n    const avs_coap_borrowed_msg_t *request;\n};\n\nstruct avs_coap_request_ctx {\n    /** Incoming request message we're currently processing. */\n    avs_coap_borrowed_msg_t request;\n\n    /** ID of the exchange that we're currently processing. */\n    avs_coap_exchange_id_t exchange_id;\n\n    /** Associated CoAP context. */\n    avs_coap_ctx_t *coap_ctx;\n\n    /**\n     * Set to true after the user calls\n     * @ref avs_coap_server_setup_async_response . Used for:\n     * - preventing further calls to @ref avs_coap_async_request_handler after\n     *   user decides no more request payload is necessary to determine final\n     *   operation result,\n     * - detect the case where the user does not setup any response despite\n     *   having received complete request.\n     */\n    bool response_setup;\n\n    /**\n     * Set to true after the user calls @ref avs_coap_observe_async_start\n     * successfully. Used to determine whether @ref avs_coap_observe_t object\n     * has to be deleted after a failed call to user request_handler.\n     */\n    bool observe_established;\n};\n\n/**\n * @returns time at which next exchange timeout occurs, or\n *          @ref AVS_TIME_MONOTONIC_INVALID if there are no more exchanges\n *          that could time out.\n */\navs_time_monotonic_t\n_avs_coap_async_server_abort_timedout_exchanges(avs_coap_ctx_t *ctx);\n\nstruct avs_coap_exchange;\n\nvoid _avs_coap_server_exchange_cleanup(avs_coap_ctx_t *ctx,\n                                       struct avs_coap_exchange *exchange,\n                                       avs_error_t error);\n\nbool _avs_coap_response_header_valid(const avs_coap_response_header_t *res);\n\n/**\n * Handles an incoming packet.\n *\n * If the incoming packet is either:\n * - an invalid message\n * - [UDP] a CoAP ping message\n * - [TCP] a CoAP signaling message\n * - a response to a client-side request\n * - a request for a next block of a response that is already being sent\n * then the packet is handled entirely within this call and the variable pointed\n * to by @p out_exchange is set to <c>NULL</c>.\n *\n * If the incoming packet is a new request that does not match any existing\n * exchange, then @p on_new_request is called. If it returns failure, then\n * failure response sending is handled entirely within this call and the\n * variable pointed to by @p out_exchange is set to <c>NULL</c> as well.\n *\n * If the @p on_new_request call was successful, or if the incoming packet is\n * further request payload block of an ongoing exchange, then the\n * <c>request_handler</c> needs to be called. It is not done within this call.\n * Instead, the variable pointed to by @p out_exchange is set to the matched\n * (or newly created) exchange. The caller then SHOULD call\n * @p _avs_coap_async_incoming_packet_call_request_handler to execute the\n * request handler and MUST call\n * @p _avs_coap_async_incoming_packet_send_response . Calling this function\n * without following with @p _avs_coap_async_incoming_packet_send_response\n * when <c>*out_exchange</c> has been set to non-NULL is undefined behaviour.\n *\n * @param coap_ctx           CoAP context to operate on.\n *\n * @param on_new_request     Handler to execute when a new request is to be\n *                           created. If <c>NULL</c>, then all such requests are\n *                           rejected with 5.00 Internal Server Error.\n *\n * @param on_new_request_arg Opaque argument to pass to @p on_new_request .\n *\n * @param out_exchange       Pointer to a variable that will be set to a pointer\n *                           to the exchange for which the request handler needs\n *                           to be called, or to <c>NULL</c> if there is no such\n *                           exchange.\n *\n *                           This function will never set <c>*out_exchange</c>\n *                           to non-NULL and return nonzero code at the same\n *                           time.\n *\n * @returns @ref AVS_OK for success, or an error condition for which the\n *          operation failed.\n */\navs_error_t _avs_coap_async_incoming_packet_handle_single(\n        avs_coap_ctx_t *coap_ctx,\n        uint8_t *in_buffer,\n        size_t in_buffer_size,\n        avs_coap_server_new_async_request_handler_t *on_new_request,\n        void *on_new_request_arg,\n        AVS_LIST(struct avs_coap_exchange) *out_exchange);\n\n/**\n * Calls the request handler for a given request context and exchange pointer.\n * May only be called with value returned from\n * @ref _avs_coap_async_incoming_packet_handle_single, see its documentation for\n * details.\n */\nint _avs_coap_async_incoming_packet_call_request_handler(\n        avs_coap_ctx_t *ctx, struct avs_coap_exchange *exchange);\n\n/**\n * Sends a response to an incoming request that could not be handled entirely\n * within @ref _avs_coap_async_incoming_packet_handle_single; see its\n * documentation for details.\n *\n * @param coap_ctx    CoAP context to operate on.\n *\n * @param call_result Return value from\n *                    @ref _avs_coap_async_incoming_packet_call_request_handler\n *                    or any equivalent routine performed instead of or in\n *                    addition to it. If nonzero, then an error response will\n *                    be sent according to the rules documented for\n *                    @ref avs_coap_server_new_async_request_handler_t .\n *\n * @returns @ref AVS_OK for success (regardless of whether success or error was\n *          being sent), or an error condition for which the operation failed.\n */\navs_error_t _avs_coap_async_incoming_packet_send_response(avs_coap_ctx_t *ctx,\n                                                          int call_result);\n\n/**\n * Combines the calls to @ref _avs_coap_async_incoming_packet_handle_single and,\n * if applicable, @ref _avs_coap_async_incoming_packet_call_request_handler and\n * @ref _avs_coap_async_incoming_packet_send_response .\n */\navs_error_t _avs_coap_async_incoming_packet_simple_handle_single(\n        avs_coap_ctx_t *ctx,\n        uint8_t *in_buffer,\n        size_t in_buffer_size,\n        avs_coap_server_new_async_request_handler_t *on_new_request,\n        void *on_new_request_arg);\n\n/**\n * Calls @ref _avs_coap_async_incoming_packet_simple_handle_single in a loop,\n * until there is no more data to be received on the socket.\n *\n * The stop condition is checked either using\n * @ref _avs_coap_socket_definitely_exhausted (the loop stops if that function\n * returns true) or by waiting for the socket receive operation to time out\n * (while the receive timeout is set to zero).\n *\n * @ref _avs_coap_async_incoming_packet_simple_handle_single is called at least\n * once.\n */\navs_error_t _avs_coap_async_incoming_packet_handle_and_exhaust_socket(\n        avs_coap_ctx_t *ctx,\n        uint8_t *in_buffer,\n        size_t in_buffer_size,\n        avs_coap_server_new_async_request_handler_t *on_new_request,\n        void *on_new_request_arg);\n\n/**\n * Calls @ref _avs_coap_async_incoming_packet_handle_and_exhaust_socket, unless\n * the socket already can be conclusively determined to have no data already\n * buffered.\n *\n * This is intended to make sure that all the incoming data available on the\n * socket is exhausted after performing some other receive operation.\n */\nstatic inline avs_error_t _avs_coap_async_incoming_packet_exhaust_socket(\n        avs_coap_ctx_t *ctx,\n        uint8_t *in_buffer,\n        size_t in_buffer_size,\n        avs_coap_server_new_async_request_handler_t *on_new_request,\n        void *on_new_request_arg) {\n    if (_avs_coap_socket_definitely_exhausted(ctx)) {\n        return AVS_OK;\n    }\n    return _avs_coap_async_incoming_packet_handle_and_exhaust_socket(\n            ctx, in_buffer, in_buffer_size, on_new_request, on_new_request_arg);\n}\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif // AVS_COAP_SRC_ASYNC_SERVER_H\n"
  },
  {
    "path": "deps/avs_coap/src/async/avs_coap_exchange.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n/**\n * Implementation of asynchronous @ref avs_coap_exchange_t operations that are\n * supposed to work for both client-side and server-side code.\n */\n\n#include <avs_coap_init.h>\n\n#include <avsystem/commons/avs_errno.h>\n\n#include <avsystem/coap/async_client.h>\n\n#include \"avs_coap_code_utils.h\"\n#include \"avs_coap_exchange.h\"\n#include \"options/avs_coap_option.h\"\n\n#define MODULE_NAME coap\n#include <avs_coap_x_log_config.h>\n\n#include \"avs_coap_ctx.h\"\n#include \"options/avs_coap_options.h\"\n\nVISIBILITY_SOURCE_BEGIN\n\nstatic inline bool is_pointer_part_of_enqueued_exchange(avs_coap_ctx_t *ctx,\n                                                        const void *buffer) {\n    const uint8_t *u8_buf = (const uint8_t *) buffer;\n\n    AVS_LIST(avs_coap_exchange_t) it;\n    AVS_LIST_FOREACH(it, _avs_coap_get_base(ctx)->client_exchanges) {\n        avs_coap_exchange_t *exchange = (avs_coap_exchange_t *) it;\n        const uint8_t *exchange_begin = (const uint8_t *) exchange;\n        const uint8_t *exchange_end = (exchange_begin + sizeof(*exchange)\n                                       + exchange->options_buffer_size);\n\n        if (exchange_begin <= u8_buf && u8_buf < exchange_end) {\n            return true;\n        }\n    }\n\n    return false;\n}\n\nstatic avs_error_t call_payload_writer(avs_coap_payload_writer_t *write_payload,\n                                       void *write_payload_arg,\n                                       size_t payload_offset,\n                                       void *buffer,\n                                       size_t bytes_to_read,\n                                       size_t *out_bytes_read) {\n    int result = write_payload(payload_offset, buffer, bytes_to_read,\n                               out_bytes_read, write_payload_arg);\n    LOG(TRACE,\n        _(\"write_payload(offset = \") \"%u\" _(\", size \") \"%u\" _(\") = \") \"%d\" _(\n                \"; read \") \"%u\" _(\" B\"),\n        (unsigned) payload_offset, (unsigned) bytes_to_read, result,\n        (unsigned) *out_bytes_read);\n\n    if (result) {\n        LOG(DEBUG, _(\"unable to get request payload (result = \") \"%d\" _(\")\"),\n            result);\n        return _avs_coap_err(AVS_COAP_ERR_PAYLOAD_WRITER_FAILED);\n    }\n\n    AVS_ASSERT(*out_bytes_read <= bytes_to_read,\n               \"write_payload handler reported writing more bytes than \"\n               \"requested - buffer overflow could have happened\");\n    return AVS_OK;\n}\n\n/*\n * Calls the user-defined payload provider handler to retrieve next block of\n * request payload.\n *\n * Only attempts to read at most a single block of data. Size of that block\n * is bounded by @p buffer_size , which MUST be at least 1 byte bigger than\n * desired block size to allow detecting EOF condition.\n *\n * NOTE: it ALWAYS reads less than buffer_size bytes!\n */\nstatic avs_error_t\nfetch_payload_with_cache(avs_coap_ctx_t *ctx,\n                         avs_coap_payload_writer_t *write_payload,\n                         void *write_payload_arg,\n                         size_t payload_offset,\n                         uint8_t *buffer,\n                         size_t buffer_size,\n                         size_t *out_bytes_read,\n                         eof_cache_t *cache) {\n    (void) ctx;\n    AVS_ASSERT(!is_pointer_part_of_enqueued_exchange(ctx, cache),\n               \"Passed eof_cache may be destroyed if the user-defined handler \"\n               \"calls avs_coap_exchange_cancel. Use stack-allocated copy \"\n               \"instead.\");\n    AVS_ASSERT(!is_pointer_part_of_enqueued_exchange(ctx, buffer)\n                       && !is_pointer_part_of_enqueued_exchange(\n                                  ctx, buffer + buffer_size),\n               \"Passed buffer may be destroyed if the user-defined handler \"\n               \"calls avs_coap_exchange_cancel. The buffer must not be a part \"\n               \"of the exchange object.\");\n\n    *out_bytes_read = 0;\n    if (!write_payload) {\n        return AVS_OK;\n    }\n\n    size_t bytes_read;\n    size_t bytes_read_with_cache;\n    size_t bytes_to_read;\n\n    if (cache->empty) {\n        // The cache is only empty when we're reading the initial payload block.\n        //\n        // Attempt to read one byte more than the block size. If the buffer\n        // gets fully filled, that means we need to trigger a BLOCK-wise\n        // transfer and store the cached byte for later use.\n        bytes_to_read = buffer_size;\n        avs_error_t err = call_payload_writer(write_payload, write_payload_arg,\n                                              payload_offset, buffer,\n                                              bytes_to_read, &bytes_read);\n        if (avs_is_err(err)) {\n            return err;\n        }\n\n        bytes_read_with_cache = bytes_read;\n    } else {\n        // When reading following request blocks, put the cached byte at the\n        // start of buffer, then read block_size more bytes, storing the last\n        // one in cache again.\n        ++payload_offset;\n        bytes_to_read = buffer_size - 1;\n        avs_error_t err = call_payload_writer(write_payload, write_payload_arg,\n                                              payload_offset, &buffer[1],\n                                              bytes_to_read, &bytes_read);\n        if (avs_is_err(err)) {\n            return err;\n        }\n\n        buffer[0] = cache->value;\n        cache->empty = true;\n        bytes_read_with_cache = bytes_read + 1;\n    }\n\n    if (bytes_to_read != bytes_read) {\n        // EOF reached\n        *out_bytes_read = bytes_read_with_cache;\n    } else {\n        // No EOF yet, there's at least 1 byte more than a full block.\n        // Put that byte into cache.\n        cache->value = buffer[bytes_read_with_cache - 1];\n        cache->empty = false;\n        *out_bytes_read = bytes_read_with_cache - 1;\n    }\n\n    return AVS_OK;\n}\n\n#ifdef WITH_AVS_COAP_BLOCK\nstatic avs_error_t lower_block_size(avs_coap_exchange_t *exchange,\n                                    size_t max_payload_size) {\n    assert(exchange);\n\n    avs_coap_option_block_t block;\n    bool has_block;\n    avs_error_t err = _avs_coap_options_get_block_by_code(\n            &exchange->options, exchange->code, &block, &has_block);\n\n    if (avs_is_err(err)) {\n        return err;\n    }\n    assert(has_block);\n\n    size_t new_block_size = avs_max_power_of_2_not_greater_than(\n            AVS_MIN(max_payload_size, AVS_COAP_BLOCK_MAX_SIZE));\n    if (!_avs_coap_is_valid_block_size((uint16_t) new_block_size)) {\n        LOG(DEBUG,\n            _(\"CoAP context unable to handle payload size declared \"\n              \"in BLOCK option (max size = \") \"%u\" _(\"; required = \") \"%u\" _(\")\"),\n            (unsigned) max_payload_size, (unsigned) block.size);\n        return _avs_coap_err(AVS_COAP_ERR_MESSAGE_TOO_BIG);\n    }\n\n    LOG(DEBUG, _(\"lowering block size: \") \"%u\" _(\" -> \") \"%u\",\n        (unsigned) block.size, (unsigned) new_block_size);\n\n    // Reducing block size may overflow seq_num\n    size_t scale_factor = block.size / new_block_size;\n    if (block.seq_num * scale_factor > AVS_COAP_BLOCK_MAX_SEQ_NUMBER) {\n        LOG(DEBUG,\n            _(\"lowering block size overflows seq_num (\") \"%lu\" _(\" > \") \"%lu\" _(\n                    \")\"),\n            (unsigned long) block.seq_num * scale_factor,\n            (unsigned long) AVS_COAP_BLOCK_MAX_SEQ_NUMBER);\n        return _avs_coap_err(AVS_COAP_ERR_BLOCK_SEQ_NUM_OVERFLOW);\n    }\n\n    if (block.is_bert && block.size != AVS_COAP_BLOCK_MAX_SIZE) {\n        AVS_UNREACHABLE(\"bug: BERT option with size less than 1024\");\n    }\n\n    if (block.size == AVS_COAP_BLOCK_MAX_SIZE && block.is_bert) {\n        block.is_bert = false;\n    }\n    block.seq_num = (uint32_t) (block.seq_num * scale_factor);\n    block.size = (uint16_t) new_block_size;\n\n    avs_coap_options_remove_by_number(&exchange->options,\n                                      _avs_coap_option_num_from_block_type(\n                                              block.type));\n    if (avs_is_err(avs_coap_options_add_block(&exchange->options, &block))) {\n        AVS_UNREACHABLE(\"cannot rewrite BLOCK option even though its size did \"\n                        \"not change\");\n    }\n\n    return AVS_OK;\n}\n#endif // WITH_AVS_COAP_BLOCK\n\nstatic avs_error_t\nexchange_get_next_outgoing_chunk_payload_size(avs_coap_ctx_t *ctx,\n                                              avs_coap_exchange_t *exchange,\n                                              size_t *out_payload_chunk_size) {\n    avs_error_t err = _avs_coap_get_max_block_size(\n            ctx, exchange->code, &exchange->options, out_payload_chunk_size);\n    /*\n     * RFC 7959 allows us to lower block size for both requests and\n     * responses:\n     *\n     * - In https://tools.ietf.org/html/rfc7959#section-2.3 :\n     *   > [...] the client SHOULD use this block size or a smaller one in\n     *   > all further requests in the transfer sequence, even if that\n     *   > means changing the block size (and possibly scaling the block\n     *   > number accordingly) from now on.\n     *\n     * - In https://tools.ietf.org/html/rfc7959#section-2.4 :\n     *   > The server uses the block size indicated in the request option\n     *   > or a smaller size [...]\n     */\n#ifdef WITH_AVS_COAP_BLOCK\n    if (err.category == AVS_COAP_ERR_CATEGORY\n            && err.code == AVS_COAP_ERR_MESSAGE_TOO_BIG) {\n        if (*out_payload_chunk_size < AVS_COAP_BLOCK_MIN_SIZE) {\n            LOG(WARNING,\n                _(\"calculated payload size too small to handle even \"\n                  \"smallest possible BLOCK (size \") \"%u\" _(\" < \") \"%u\" _(\")\"),\n                (unsigned) *out_payload_chunk_size,\n                (unsigned) AVS_COAP_BLOCK_MIN_SIZE);\n        } else {\n            err = lower_block_size(exchange, *out_payload_chunk_size);\n        }\n    }\n#endif // WITH_AVS_COAP_BLOCK\n\n    return err;\n}\n\navs_error_t _avs_coap_exchange_get_next_outgoing_chunk_payload_size(\n        avs_coap_ctx_t *ctx,\n        avs_coap_exchange_id_t id,\n        size_t *out_payload_chunk_size) {\n    AVS_ASSERT(avs_coap_exchange_id_valid(id),\n               \"Calculating payload size for an exchange that does not exist \"\n               \"does not make sense. If you mean to calculate size for a \"\n               \"message when no exchange object is available, use \"\n               \"_avs_coap_get_next_outgoing_chunk_payload_size instead\");\n\n    AVS_LIST(avs_coap_exchange_t) exchange =\n            _avs_coap_find_exchange_by_id(ctx, id);\n    if (!exchange) {\n        return avs_errno(AVS_EINVAL);\n    }\n\n    // poor man's method of detecting if we sent the first payload chunk\n    AVS_ASSERT(!exchange->eof_cache.empty,\n               \"This function does not account for extra byte for EOF \"\n               \"detection. When calculating the size for an initial payload \"\n               \"block, use _avs_coap_get_first_outgoing_chunk_payload_size \"\n               \"instead\");\n\n    return exchange_get_next_outgoing_chunk_payload_size(\n            ctx, exchange, out_payload_chunk_size);\n}\n\n#ifdef WITH_AVS_COAP_BLOCK\nstatic avs_error_t get_payload_offset(const avs_coap_exchange_t *exchange,\n                                      size_t *out_offset) {\n    avs_coap_option_block_t block;\n    bool has_block;\n    avs_error_t err = _avs_coap_options_get_block_by_code(\n            &exchange->options, exchange->code, &block, &has_block);\n    if (avs_is_err(err)) {\n        return err;\n    }\n\n    if (has_block) {\n        *out_offset = block.seq_num * block.size;\n    } else {\n        *out_offset = 0;\n    }\n    return AVS_OK;\n}\n\nstatic avs_error_t\nexchange_add_initial_block_option(avs_coap_exchange_t *exchange,\n                                  size_t payload_offset,\n                                  size_t payload_size) {\n    assert(!exchange->eof_cache.empty);\n    assert(payload_size <= UINT16_MAX);\n    assert(_avs_coap_is_valid_block_size((uint16_t) payload_size));\n    assert(payload_offset % payload_size == 0);\n    assert(payload_offset / payload_size <= UINT_MAX);\n\n    avs_coap_option_block_t block = {\n        .type = avs_coap_code_is_request(exchange->code) ? AVS_COAP_BLOCK1\n                                                         : AVS_COAP_BLOCK2,\n        .seq_num = (unsigned) (payload_offset / payload_size),\n        .has_more = true,\n        .size = (uint16_t) payload_size\n    };\n\n    if (avs_is_err(avs_coap_options_add_block(&exchange->options, &block))) {\n        return _avs_coap_err(AVS_COAP_ERR_MESSAGE_TOO_BIG);\n    }\n    return AVS_OK;\n}\n\nstatic avs_error_t exchange_update_block_option(avs_coap_exchange_t *exchange,\n                                                size_t payload_offset,\n                                                size_t payload_size) {\n    avs_coap_option_block_t block;\n    bool has_block;\n    avs_error_t err = _avs_coap_options_get_block_by_code(\n            &exchange->options, exchange->code, &block, &has_block);\n    if (avs_is_err(err)) {\n        return err;\n    }\n\n    if (!has_block) {\n        if (!exchange->eof_cache.empty) {\n            // cache not empty and no BLOCK option yet\n            return exchange_add_initial_block_option(exchange, payload_offset,\n                                                     payload_size);\n        }\n    } else if (block.has_more == exchange->eof_cache.empty) {\n        uint16_t opt_num =\n                (block.type == AVS_COAP_BLOCK1 ? AVS_COAP_OPTION_BLOCK1\n                                               : AVS_COAP_OPTION_BLOCK2);\n        avs_coap_options_remove_by_number(&exchange->options, opt_num);\n        block.has_more = !exchange->eof_cache.empty;\n        if (avs_is_err(\n                    avs_coap_options_add_block(&exchange->options, &block))) {\n            return _avs_coap_err(AVS_COAP_ERR_MESSAGE_TOO_BIG);\n        }\n    }\n    return AVS_OK;\n}\n#endif // WITH_AVS_COAP_BLOCK\n\navs_error_t _avs_coap_exchange_send_next_chunk(\n        avs_coap_ctx_t *ctx,\n        avs_coap_exchange_t *exchange,\n        avs_coap_send_result_handler_t *send_result_handler,\n        void *send_result_handler_arg) {\n    avs_coap_exchange_id_t id = exchange->id;\n\n    // 1 byte extra to handle eof_cache\n    uint8_t payload_buf[AVS_COAP_EXCHANGE_OUTGOING_CHUNK_PAYLOAD_MAX_SIZE];\n    size_t bytes_to_read;\n    avs_error_t err =\n            exchange_get_next_outgoing_chunk_payload_size(ctx, exchange,\n                                                          &bytes_to_read);\n    if (avs_is_err(err)) {\n        return err;\n    }\n    assert(bytes_to_read < sizeof(payload_buf));\n\n    size_t payload_offset = 0;\n#ifdef WITH_AVS_COAP_BLOCK\n    err = get_payload_offset(exchange, &payload_offset);\n    if (avs_is_err(err)) {\n        return err;\n    }\n#endif // WITH_AVS_COAP_BLOCK\n\n    size_t payload_size;\n    eof_cache_t eof_cache = exchange->eof_cache;\n    err = fetch_payload_with_cache(ctx, exchange->write_payload,\n                                   exchange->write_payload_arg, payload_offset,\n                                   payload_buf, bytes_to_read + 1,\n                                   &payload_size, &eof_cache);\n\n    if (!_avs_coap_find_exchange_by_id(ctx, id)) {\n        // exchange canceled by user handler\n        return _avs_coap_err(AVS_COAP_ERR_EXCHANGE_CANCELED);\n    }\n\n    if (avs_is_err(err)) {\n        return err;\n    }\n\n    exchange->eof_cache = eof_cache;\n\n#ifdef WITH_AVS_COAP_BLOCK\n    err = exchange_update_block_option(exchange, payload_offset, payload_size);\n#else  // WITH_AVS_COAP_BLOCK\n    if (!exchange->eof_cache.empty) {\n        err = _avs_coap_err(AVS_COAP_ERR_MESSAGE_TOO_BIG);\n    }\n#endif // WITH_AVS_COAP_BLOCK\n\n    if (avs_is_err(err)) {\n        return err;\n    }\n\n    avs_coap_borrowed_msg_t msg = {\n        .code = exchange->code,\n        .token = exchange->token,\n        .options = exchange->options,\n        .payload = payload_buf,\n        .payload_size = payload_size,\n        .total_payload_size = payload_size\n    };\n\n    return ctx->vtable->send_message(ctx, &msg, send_result_handler,\n                                     send_result_handler_arg);\n}\n"
  },
  {
    "path": "deps/avs_coap/src/async/avs_coap_exchange.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef AVS_COAP_SRC_EXCHANGE_H\n#define AVS_COAP_SRC_EXCHANGE_H\n\n#include <avsystem/commons/avs_list.h>\n\n#include <avsystem/coap/async.h>\n#include <avsystem/coap/ctx.h>\n\n#include \"avs_coap_async_client.h\"\n#include \"avs_coap_async_server.h\"\n#include \"options/avs_coap_option.h\"\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\ntypedef struct {\n    bool empty;\n    uint8_t value;\n} eof_cache_t;\n\n/**\n * An object representing a single request-response pair, regardless of payload\n * size of either. Abstracts away BLOCK-wise transfers.\n */\ntypedef struct avs_coap_exchange {\n    /** Unique ID used to identify an exchange in user code. */\n    avs_coap_exchange_id_t id;\n\n    /** User-defined handler used to provide payload for sent message. */\n    avs_coap_payload_writer_t *write_payload;\n    void *write_payload_arg;\n\n    /**\n     * CoAP code of the message being sent, configured by the user (request\n     * code for outgoing exchanges; final response code for incoming exchanges).\n     */\n    uint8_t code;\n    /** CoAP token of the last message sent. */\n    avs_coap_token_t token;\n    /**\n     * Set of options included in last sent message. Uses\n     * @ref avs_coap_exchange_t#options_buffer as storage. Initialized with\n     * user-provided CoAP options, changes during exchange lifetime as\n     * necessary to handle BLOCK transfers.\n     */\n    avs_coap_options_t options;\n\n    /**\n     * A cache with optional<byte> semantics, used when reading user-provided\n     * payload to detect EOF case.\n     */\n    eof_cache_t eof_cache;\n\n    union {\n        avs_coap_client_exchange_data_t client;\n        avs_coap_server_exchange_data_t server;\n    } by_type;\n\n    /**\n     * Number of bytes available in @ref avs_coap_exchange_t#options_buffer . It\n     * may be different than @ref avs_coap_exchange_t#options.capacity .\n     */\n    size_t options_buffer_size;\n    /**\n     * Mostly @ref avs_coap_exchange_t#options storage, but it also may contain\n     * some other (Client or Server specific) data.\n     */\n    char options_buffer[];\n} avs_coap_exchange_t;\n\n#define AVS_COAP_EXCHANGE_OUTGOING_CHUNK_PAYLOAD_MAX_SIZE \\\n    (AVS_COAP_BLOCK_MAX_SIZE + 1)\n\n/**\n * Queries the expected size of the chunk that will be requested during the\n * next call to @ref avs_coap_payload_writer_t for a given exchange.\n *\n * NOTE: it is assumed that at the point of calling this function the first\n * exchange block was already sent, and accounting for EOF detection is not\n * necessary. For that particular case, use\n * @ref _avs_coap_get_first_outgoing_chunk_payload_size instead .\n *\n * @param ctx\n * CoAP context to operate on.\n *\n * @param id\n * ID of the exchange for which to perform the calculations. The function fails\n * if it does not refer to a valid exchange.\n *\n * @param out_payload_chunk_size\n * Pointer to a variable which will be filled with the calculated payload chunk\n * size. The size is guaranteed to be no larger than\n * @ref AVS_COAP_EXCHANGE_OUTGOING_CHUNK_PAYLOAD_MAX_SIZE . The actual size\n * passed to the next call to @ref avs_coap_payload_writer_t is guaranteed to be\n * no larger than the size returned from this function beforehand.\n *\n * @returns @ref AVS_OK for success, or an error condition for which the\n *          operation failed.\n */\navs_error_t _avs_coap_exchange_get_next_outgoing_chunk_payload_size(\n        avs_coap_ctx_t *ctx,\n        avs_coap_exchange_id_t id,\n        size_t *out_payload_chunk_size);\n\n/**\n * Fetches next chunk of payload associated with @p exchange to @p payload_buf .\n * Adds a BLOCK1/2 option to @p exchange CoAP options if necessary (not yet\n * present, but @p payload_buf too small to hold whole payload). Finally,\n * sends next chunk of the exchange, using @p send_result_handler as delivery\n * confirmation handler.\n *\n * @returns\n * @ref AVS_OK for success, or an error condition for which the operation\n * failed, which includes:\n * - @p ctx reporting not being able to handle packets with at least\n *   @ref AVS_COAP_BLOCK_MIN_SIZE bytes of payload, considering CoAP options\n *   currently held within @p exchange object,\n * - user-defined payload writer failure,\n * - @p exchange cancellation from within user-defined payload writer,\n * - insufficient space for inserting the BLOCK1/2 option.\n * - ctx->vtable->send_message failure.\n */\navs_error_t _avs_coap_exchange_send_next_chunk(\n        avs_coap_ctx_t *ctx,\n        avs_coap_exchange_t *exchange,\n        avs_coap_send_result_handler_t *send_result_handler,\n        void *send_result_handler_arg);\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif // AVS_COAP_SRC_EXCHANGE_H\n"
  },
  {
    "path": "deps/avs_coap/src/avs_coap_code_utils.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <avs_coap_init.h>\n\n#include <avsystem/commons/avs_utils.h>\n\n#include <avsystem/coap/code.h>\n\n#define MODULE_NAME coap_code\n#include <avs_coap_x_log_config.h>\n\n#include \"avs_coap_code_utils.h\"\n#include \"tcp/avs_coap_tcp_signaling.h\"\n\nVISIBILITY_SOURCE_BEGIN\n\nconst char *avs_coap_code_to_string(uint8_t code, char *buf, size_t buf_size) {\n    static const struct {\n        uint8_t code;\n        const char *name;\n    } CODE_NAMES[] = {\n        // clang-format off\n        { AVS_COAP_CODE_EMPTY,                      \"Empty\"                      },\n\n        { AVS_COAP_CODE_GET,                        \"Get\"                        },\n        { AVS_COAP_CODE_POST,                       \"Post\"                       },\n        { AVS_COAP_CODE_PUT,                        \"Put\"                        },\n        { AVS_COAP_CODE_DELETE,                     \"Delete\"                     },\n        { AVS_COAP_CODE_FETCH,                      \"Fetch\"                      },\n        { AVS_COAP_CODE_PATCH,                      \"Patch\"                      },\n        { AVS_COAP_CODE_IPATCH,                     \"iPatch\"                     },\n\n        { AVS_COAP_CODE_CREATED,                    \"Created\"                    },\n        { AVS_COAP_CODE_DELETED,                    \"Deleted\"                    },\n        { AVS_COAP_CODE_VALID,                      \"Valid\"                      },\n        { AVS_COAP_CODE_CHANGED,                    \"Changed\"                    },\n        { AVS_COAP_CODE_CONTENT,                    \"Content\"                    },\n        { AVS_COAP_CODE_CONTINUE,                   \"Continue\"                   },\n\n        { AVS_COAP_CODE_BAD_REQUEST,                \"Bad Request\"                },\n        { AVS_COAP_CODE_UNAUTHORIZED,               \"Unauthorized\"               },\n        { AVS_COAP_CODE_BAD_OPTION,                 \"Bad Option\"                 },\n        { AVS_COAP_CODE_FORBIDDEN,                  \"Forbidden\"                  },\n        { AVS_COAP_CODE_NOT_FOUND,                  \"Not Found\"                  },\n        { AVS_COAP_CODE_METHOD_NOT_ALLOWED,         \"Method Not Allowed\"         },\n        { AVS_COAP_CODE_NOT_ACCEPTABLE,             \"Not Acceptable\"             },\n        { AVS_COAP_CODE_REQUEST_ENTITY_INCOMPLETE,  \"Request Entity Incomplete\"  },\n        { AVS_COAP_CODE_PRECONDITION_FAILED,        \"Precondition Failed\"        },\n        { AVS_COAP_CODE_REQUEST_ENTITY_TOO_LARGE,   \"Entity Too Large\"           },\n        { AVS_COAP_CODE_UNSUPPORTED_CONTENT_FORMAT, \"Unsupported Content Format\" },\n\n        { AVS_COAP_CODE_INTERNAL_SERVER_ERROR,      \"Internal Server Error\"      },\n        { AVS_COAP_CODE_NOT_IMPLEMENTED,            \"Not Implemented\"            },\n        { AVS_COAP_CODE_BAD_GATEWAY,                \"Bad Gateway\"                },\n        { AVS_COAP_CODE_SERVICE_UNAVAILABLE,        \"Service Unavailable\"        },\n        { AVS_COAP_CODE_GATEWAY_TIMEOUT,            \"Gateway Timeout\"            },\n        { AVS_COAP_CODE_PROXYING_NOT_SUPPORTED,     \"Proxying Not Supported\"     },\n\n        { AVS_COAP_CODE_CSM,                        \"CSM\"                        },\n        { AVS_COAP_CODE_PING,                       \"Ping\"                       },\n        { AVS_COAP_CODE_PONG,                       \"Pong\"                       },\n        { AVS_COAP_CODE_RELEASE,                    \"Release\"                    },\n        { AVS_COAP_CODE_ABORT,                      \"Abort\"                      },\n        // clang-format on\n    };\n\n    const char *name = \"unknown\";\n    for (size_t i = 0; i < AVS_ARRAY_SIZE(CODE_NAMES); ++i) {\n        if (CODE_NAMES[i].code == code) {\n            name = CODE_NAMES[i].name;\n            break;\n        }\n    }\n\n    if (avs_simple_snprintf(buf, buf_size, \"%u.%02u %s\",\n                            avs_coap_code_get_class(code),\n                            avs_coap_code_get_detail(code), name)\n            < 0) {\n        AVS_UNREACHABLE(\"buffer too small for CoAP msg code string\");\n        return \"<error>\";\n    }\n\n    return buf;\n}\n\nuint8_t avs_coap_code_get_class(uint8_t code) {\n    return (uint8_t) _AVS_FIELD_GET(code, _AVS_COAP_CODE_CLASS_MASK,\n                                    _AVS_COAP_CODE_CLASS_SHIFT);\n}\n\nuint8_t avs_coap_code_get_detail(uint8_t code) {\n    return (uint8_t) _AVS_FIELD_GET(code, _AVS_COAP_CODE_DETAIL_MASK,\n                                    _AVS_COAP_CODE_DETAIL_SHIFT);\n}\n\nbool avs_coap_code_is_client_error(uint8_t code) {\n    return avs_coap_code_get_class(code) == 4;\n}\n\nbool avs_coap_code_is_server_error(uint8_t code) {\n    return avs_coap_code_get_class(code) == 5;\n}\n\nbool avs_coap_code_is_success(uint8_t code) {\n    return avs_coap_code_get_class(code) == 2;\n}\n\nbool avs_coap_code_is_request(uint8_t code) {\n    return avs_coap_code_get_class(code) == 0\n           && avs_coap_code_get_detail(code) > 0;\n}\n\nbool avs_coap_code_is_response(uint8_t code) {\n    return avs_coap_code_is_success(code) || avs_coap_code_is_client_error(code)\n           || avs_coap_code_is_server_error(code);\n}\n"
  },
  {
    "path": "deps/avs_coap/src/avs_coap_code_utils.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef AVS_COAP_SRC_CODE_UTILS_H\n#define AVS_COAP_SRC_CODE_UTILS_H\n\n#include <stddef.h>\n#include <stdint.h>\n\n#include \"avs_coap_parse_utils.h\"\n\n#include <avsystem/coap/code.h>\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\n/** @{\n * CoAP code class/detail accessors. See RFC7252 for details.\n */\n\nstatic inline void _avs_coap_code_set_class(uint8_t *code, uint8_t cls) {\n    assert(cls < 8);\n    _AVS_FIELD_SET(*code, _AVS_COAP_CODE_CLASS_MASK, _AVS_COAP_CODE_CLASS_SHIFT,\n                   cls);\n}\n\nstatic inline void _avs_coap_code_set_detail(uint8_t *code, uint8_t detail) {\n    assert(detail < 32);\n    _AVS_FIELD_SET(*code, _AVS_COAP_CODE_DETAIL_MASK,\n                   _AVS_COAP_CODE_DETAIL_SHIFT, detail);\n}\n\n/** @} */\n\n/** @returns true if @p code is in correct range. */\nstatic inline bool _avs_coap_code_in_range(int code) {\n    return (code >= 0 && code <= UINT8_MAX);\n}\n\n/**\n * @returns true if @p code represents a signaling message, false otherwise.\n *          Note: only 7.01 to 7.05 codes are supported, as defined in RFC 8323.\n */\nstatic inline bool _avs_coap_code_is_signaling_message(uint8_t code) {\n    // According to RFC8323, all codes from range 7.00-7.31 refer to signaling\n    // messages. Only codes from range 7.01-7.05 are currently defined.\n    return avs_coap_code_get_class(code) == 7;\n}\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif // AVS_COAP_SRC_CODE_UTILS_H\n"
  },
  {
    "path": "deps/avs_coap/src/avs_coap_common_utils.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <avs_coap_init.h>\n\n#include <avsystem/commons/avs_errno.h>\n\n#include <inttypes.h>\n\n#include \"options/avs_coap_iterator.h\"\n\n#define MODULE_NAME coap_utils\n#include <avs_coap_x_log_config.h>\n\n#include \"avs_coap_common_utils.h\"\n#include \"avs_coap_ctx.h\"\n\nVISIBILITY_SOURCE_BEGIN\n\nint _avs_coap_bytes_append(bytes_appender_t *appender,\n                           const void *data,\n                           size_t size_bytes) {\n    if (appender->bytes_left < size_bytes) {\n        LOG(DEBUG,\n            _(\"not enough space: required \") \"%u\" _(\" free bytes, got \") \"%u\",\n            (unsigned) size_bytes, (unsigned) appender->bytes_left);\n        return -1;\n    }\n    if (!data && size_bytes) {\n        LOG(DEBUG, _(\"NULL data with non-zero size\"));\n        return -1;\n    }\n    if (size_bytes) {\n        memcpy(appender->write_ptr, data, size_bytes);\n        appender->write_ptr += size_bytes;\n        appender->bytes_left -= size_bytes;\n    }\n    return 0;\n}\n\nint _avs_coap_bytes_extract(bytes_dispenser_t *dispenser,\n                            void *out,\n                            size_t size_bytes) {\n    if (dispenser->bytes_left < size_bytes) {\n        LOG(DEBUG,\n            _(\"incomplete data: tried to read \") \"%u\" _(\" bytes, got \") \"%u\",\n            (unsigned) size_bytes, (unsigned) dispenser->bytes_left);\n        return -1;\n    }\n\n    if (out) {\n        memcpy(out, dispenser->read_ptr, size_bytes);\n    }\n\n    dispenser->read_ptr += size_bytes;\n    dispenser->bytes_left -= size_bytes;\n    return 0;\n}\n\navs_error_t _avs_coap_parse_token(avs_coap_token_t *out_token,\n                                  uint8_t token_size,\n                                  bytes_dispenser_t *dispenser) {\n    out_token->size = token_size;\n\n    AVS_ASSERT(token_size <= sizeof(out_token->bytes),\n               \"bug: not enough space for valid token\");\n\n    if (_avs_coap_bytes_extract(dispenser, out_token->bytes, out_token->size)) {\n        LOG(DEBUG, _(\"truncated token\"));\n        return _avs_coap_err(AVS_COAP_ERR_MALFORMED_MESSAGE);\n    }\n\n    return AVS_OK;\n}\n\n#ifdef WITH_AVS_COAP_LOGS\nvoid _avs_coap_log_oom__(void) {\n    LOG(ERROR, _(\"out of memory\"));\n}\n#endif // WITH_AVS_COAP_LOGS\n"
  },
  {
    "path": "deps/avs_coap/src/avs_coap_common_utils.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef AVS_COAP_SRC_COMMON_UTILS_H\n#define AVS_COAP_SRC_COMMON_UTILS_H\n\n#include \"avs_coap_ctx.h\"\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\ntypedef struct {\n    uint8_t *write_ptr;\n    size_t bytes_left;\n} bytes_appender_t;\n\ntypedef struct {\n    const uint8_t *read_ptr;\n    size_t bytes_left;\n} bytes_dispenser_t;\n\nint _avs_coap_bytes_append(bytes_appender_t *appender,\n                           const void *data,\n                           size_t size_bytes);\n\nint _avs_coap_bytes_extract(bytes_dispenser_t *dispenser,\n                            void *out,\n                            size_t size_bytes);\n\navs_error_t _avs_coap_parse_token(avs_coap_token_t *out_token,\n                                  uint8_t token_size,\n                                  bytes_dispenser_t *dispenser);\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif // AVS_COAP_SRC_COMMON_UTILS_H\n"
  },
  {
    "path": "deps/avs_coap/src/avs_coap_ctx.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <avs_coap_init.h>\n\n#include <avsystem/commons/avs_utils.h>\n\n#include <avsystem/coap/code.h>\n\n#include \"options/avs_coap_iterator.h\"\n\n#include \"async/avs_coap_async_client.h\"\n#include \"async/avs_coap_exchange.h\"\n\n#define MODULE_NAME coap\n#include <avs_coap_x_log_config.h>\n\n#include \"avs_coap_ctx.h\"\n#include \"options/avs_coap_options.h\"\n\nVISIBILITY_SOURCE_BEGIN\n\navs_error_t _avs_coap_in_buffer_acquire(avs_coap_ctx_t *ctx,\n                                        uint8_t **out_in_buffer,\n                                        size_t *out_in_buffer_size) {\n    avs_coap_base_t *coap_base = _avs_coap_get_base(ctx);\n    if (coap_base->in_buffer_in_use) {\n        LOG(WARNING,\n            _(\"double use of shared buffer. Note: calling \"\n              \"handle_incoming_packet from within request handler is not \"\n              \"supported\"));\n        return _avs_coap_err(AVS_COAP_ERR_SHARED_BUFFER_IN_USE);\n    }\n\n    coap_base->in_buffer_in_use = true;\n    *out_in_buffer = avs_shared_buffer_acquire(coap_base->in_buffer);\n    *out_in_buffer_size = coap_base->in_buffer->capacity;\n    return AVS_OK;\n}\n\nvoid avs_coap_ctx_cleanup(avs_coap_ctx_t **ctx) {\n    if (ctx && *ctx) {\n        assert((*ctx)->vtable);\n        assert((*ctx)->vtable->cleanup);\n\n        avs_coap_base_t *coap_base = _avs_coap_get_base(*ctx);\n\n        while (coap_base->client_exchanges) {\n            avs_coap_exchange_cancel(*ctx, coap_base->client_exchanges->id);\n        }\n        while (coap_base->server_exchanges) {\n            avs_coap_exchange_cancel(*ctx, coap_base->server_exchanges->id);\n        }\n#ifdef WITH_AVS_COAP_OBSERVE\n        while (coap_base->observes) {\n            avs_coap_observe_cancel(*ctx, coap_base->observes->id);\n        }\n#endif // WITH_AVS_COAP_OBSERVE\n#ifdef WITH_AVS_COAP_STREAMING_API\n        _avs_coap_stream_cleanup(&coap_base->coap_stream);\n#endif // WITH_AVS_COAP_STREAMING_API\n\n        avs_sched_del(&coap_base->retry_or_request_expired_job);\n\n        (*ctx)->vtable->cleanup(*ctx);\n        *ctx = NULL;\n    }\n}\n\navs_error_t _avs_coap_ctx_generate_token(avs_crypto_prng_ctx_t *prng_ctx,\n                                         avs_coap_token_t *out_token) {\n    /**\n     * One might be tempted to use sequential tokens to avoid collisions as\n     * much as possible, but that is explicitly discouraged by the CoAP spec\n     * (RFC 7252, 5.3.1 \"Token\"):\n     *\n     * > A client sending a request without using Transport Layer Security\n     * > (Section 9) SHOULD use a nontrivial, randomized token to guard\n     * > against spoofing of responses (Section 11.4).\n     */\n\n    if (avs_crypto_prng_bytes(prng_ctx, (unsigned char *) &out_token->bytes,\n                              sizeof(out_token->bytes))) {\n        LOG(ERROR, _(\"failed to generate token\"));\n        out_token->size = 0;\n        return _avs_coap_err(AVS_COAP_ERR_PRNG_FAIL);\n    }\n    out_token->size = sizeof(out_token->bytes);\n    return AVS_OK;\n}\n\nAVS_LIST(avs_coap_exchange_t) *\n_avs_coap_find_exchange_ptr_by_id(AVS_LIST(avs_coap_exchange_t) *list_ptr,\n                                  avs_coap_exchange_id_t id) {\n    AVS_LIST(avs_coap_exchange_t) *it;\n    AVS_LIST_FOREACH_PTR(it, list_ptr) {\n        if (avs_coap_exchange_id_equal(id, (*it)->id)) {\n            return it;\n        }\n    }\n    return NULL;\n}\n\nvoid avs_coap_exchange_cancel(avs_coap_ctx_t *ctx, avs_coap_exchange_id_t id) {\n    if (!avs_coap_exchange_id_valid(id)) {\n        return;\n    }\n\n    AVS_LIST(avs_coap_exchange_t) *exchange_ptr;\n\n    exchange_ptr = _avs_coap_find_client_exchange_ptr_by_id(ctx, id);\n    if (exchange_ptr) {\n        _avs_coap_client_exchange_cleanup(ctx, AVS_LIST_DETACH(exchange_ptr),\n                                          AVS_OK);\n        return;\n    }\n\n    exchange_ptr = _avs_coap_find_server_exchange_ptr_by_id(ctx, id);\n    if (exchange_ptr) {\n        _avs_coap_server_exchange_cleanup(\n                ctx, AVS_LIST_DETACH(exchange_ptr),\n                _avs_coap_err(AVS_COAP_ERR_EXCHANGE_CANCELED));\n    }\n}\n\nconst char *avs_coap_strerror(avs_error_t error, char *buf, size_t buf_size) {\n    if (avs_is_ok(error)) {\n        return \"no error\";\n    }\n    switch (error.category) {\n    case AVS_COAP_ERR_CATEGORY:\n        switch ((avs_coap_error_t) error.code) {\n        case AVS_COAP_ERR_SHARED_BUFFER_IN_USE:\n            return \"shared buffer in use\";\n        case AVS_COAP_ERR_SOCKET_ALREADY_SET:\n            return \"socket already set\";\n        case AVS_COAP_ERR_PAYLOAD_WRITER_FAILED:\n            return \"payload writer failed\";\n        case AVS_COAP_ERR_MESSAGE_TOO_BIG:\n            return \"message too big\";\n        case AVS_COAP_ERR_TIME_INVALID:\n            return \"time invalid\";\n        case AVS_COAP_ERR_EXCHANGE_CANCELED:\n            return \"exchange canceled\";\n        case AVS_COAP_ERR_UDP_RESET_RECEIVED:\n            return \"UDP Reset received\";\n        case AVS_COAP_ERR_MALFORMED_MESSAGE:\n            return \"malformed message\";\n        case AVS_COAP_ERR_MALFORMED_OPTIONS:\n            return \"malformed options list\";\n        case AVS_COAP_ERR_BLOCK_SIZE_RENEGOTIATION_INVALID:\n            return \"block size renegotiation invalid\";\n        case AVS_COAP_ERR_TRUNCATED_MESSAGE_RECEIVED:\n            return \"truncated message received\";\n        case AVS_COAP_ERR_BLOCK_SEQ_NUM_OVERFLOW:\n            return \"block seq num overflow\";\n        case AVS_COAP_ERR_ETAG_MISMATCH:\n            return \"ETag mismatch\";\n        case AVS_COAP_ERR_UNEXPECTED_CONTINUE_RESPONSE:\n            return \"unexpected Continue response\";\n        case AVS_COAP_ERR_TIMEOUT:\n            return \"timeout\";\n        case AVS_COAP_ERR_MORE_DATA_REQUIRED:\n            return \"more data required\";\n        case AVS_COAP_ERR_TCP_ABORT_SENT:\n            return \"TCP Abort sent\";\n        case AVS_COAP_ERR_TCP_ABORT_RECEIVED:\n            return \"TCP Abort received\";\n        case AVS_COAP_ERR_TCP_RELEASE_RECEIVED:\n            return \"TCP Release received\";\n        case AVS_COAP_ERR_TCP_CSM_NOT_RECEIVED:\n            return \"TCP CSM options not received\";\n        case AVS_COAP_ERR_TCP_MALFORMED_CSM_OPTIONS_RECEIVED:\n            return \"TCP malformed CSM options received\";\n        case AVS_COAP_ERR_TCP_UNKNOWN_CSM_CRITICAL_OPTION_RECEIVED:\n            return \"TCP unknown CSM critical option received\";\n        case AVS_COAP_ERR_TCP_CONN_CLOSED:\n            return \"TCP connection closed by peer\";\n        case AVS_COAP_ERR_ASSERT_FAILED:\n            return \"assert failed\";\n        case AVS_COAP_ERR_NOT_IMPLEMENTED:\n            return \"feature not implemented\";\n        case AVS_COAP_ERR_FEATURE_DISABLED:\n            return \"feature disabled\";\n        case AVS_COAP_ERR_OSCORE_DATA_TOO_BIG:\n            return \"OSCORE data too big\";\n        case AVS_COAP_ERR_OSCORE_NEEDS_RECREATE:\n            return \"OSCORE security context outdated\";\n        case AVS_COAP_ERR_OSCORE_OPTION_MISSING:\n            return \"OSCORE option missing in message received by OSCORE \"\n                   \"context\";\n        case AVS_COAP_ERR_PRNG_FAIL:\n            return \"PRNG failure\";\n        }\n        break;\n\n    case AVS_ERRNO_CATEGORY:\n        return avs_strerror((avs_errno_t) error.code);\n    }\n\n    if (buf_size\n            && avs_simple_snprintf(buf, buf_size,\n                                   \"unknown error, category %\" PRIu16\n                                   \", code %\" PRIu16,\n                                   error.category, error.code)\n                           >= 0) {\n        return buf;\n    }\n    return \"unknown error\";\n}\n\nstatic void retry_or_request_expired_job(avs_sched_t *sched,\n                                         const void *ctx_ptr) {\n    (void) sched;\n\n    avs_coap_ctx_t *ctx = *(avs_coap_ctx_t *const *) ctx_ptr;\n    _avs_coap_retry_or_request_expired_job(ctx);\n}\n\nvoid _avs_coap_reschedule_retry_or_request_expired_job(\n        avs_coap_ctx_t *ctx, avs_time_monotonic_t target_time) {\n    avs_coap_base_t *coap_base = _avs_coap_get_base(ctx);\n    if (coap_base->retry_or_request_expired_job\n            && !avs_time_monotonic_before(\n                       target_time,\n                       avs_sched_time(\n                               &coap_base->retry_or_request_expired_job))) {\n        return;\n    }\n\n    if (AVS_SCHED_AT(coap_base->sched, &coap_base->retry_or_request_expired_job,\n                     target_time, retry_or_request_expired_job, &ctx,\n                     sizeof(ctx))) {\n        LOG(ERROR, _(\"unable to reschedule timeout job\"));\n    }\n}\n\navs_time_monotonic_t\n_avs_coap_retry_or_request_expired_job(avs_coap_ctx_t *ctx) {\n    avs_sched_del(&_avs_coap_get_base(ctx)->retry_or_request_expired_job);\n\n    AVS_LIST(avs_coap_exchange_t) *exchange_head =\n            &_avs_coap_get_base(ctx)->client_exchanges;\n    AVS_LIST(avs_coap_exchange_t) *exchange_ptr = exchange_head;\n    while (*exchange_ptr\n           && !_avs_coap_client_exchange_request_sent(*exchange_ptr)) {\n        AVS_LIST(avs_coap_exchange_t) *exchange_ptr_copy = exchange_ptr;\n        avs_error_t err =\n                _avs_coap_client_exchange_send_first_chunk(ctx,\n                                                           &exchange_ptr_copy);\n        if (avs_is_err(err) && exchange_ptr_copy) {\n            _avs_coap_client_exchange_cleanup(\n                    ctx, AVS_LIST_DETACH(exchange_ptr_copy), err);\n            exchange_ptr_copy = NULL;\n        }\n        if (exchange_ptr_copy) {\n            exchange_ptr = exchange_ptr_copy;\n            AVS_LIST_ADVANCE_PTR(&exchange_ptr);\n        } else {\n            // ANY exchange pointers may have been invalidated. We need to find\n            // our iteration pointer anew. Please note that if we have already\n            // sent some packets during this iteration, the portion of the list\n            // with unsent messages may be temporarily in the middle.\n            exchange_ptr = exchange_head;\n            while (*exchange_ptr\n                   && _avs_coap_client_exchange_request_sent(*exchange_ptr)) {\n                AVS_LIST_ADVANCE_PTR(&exchange_ptr);\n            }\n        }\n    }\n\n    avs_time_monotonic_t next_timeout =\n            _avs_coap_async_server_abort_timedout_exchanges(ctx);\n\n    if (ctx->vtable->on_timeout) {\n        avs_time_monotonic_t transport_timeout = ctx->vtable->on_timeout(ctx);\n        if (!avs_time_monotonic_valid(next_timeout)\n                || avs_time_monotonic_before(transport_timeout, next_timeout)) {\n            next_timeout = transport_timeout;\n        }\n    }\n\n    if (avs_time_monotonic_valid(next_timeout)) {\n        _avs_coap_reschedule_retry_or_request_expired_job(ctx, next_timeout);\n    }\n\n    return next_timeout;\n}\n\navs_error_t avs_coap_ctx_set_socket(avs_coap_ctx_t *ctx,\n                                    avs_net_socket_t *socket) {\n    if (!ctx->vtable->setsock) {\n        return _avs_coap_err(AVS_COAP_ERR_NOT_IMPLEMENTED);\n    }\n    return ctx->vtable->setsock(ctx, socket);\n}\n\nbool avs_coap_ctx_has_socket(avs_coap_ctx_t *ctx) {\n    return _avs_coap_get_base(ctx)->socket != NULL;\n}\n\nsize_t avs_coap_max_incoming_message_payload(avs_coap_ctx_t *ctx,\n                                             const avs_coap_options_t *options,\n                                             uint8_t code) {\n    return ctx->vtable->max_incoming_payload_size(\n            ctx, AVS_COAP_MAX_TOKEN_LENGTH, options, code);\n}\n\nconst avs_time_duration_t AVS_COAP_DEFAULT_EXCHANGE_MAX_TIME = {\n    .seconds = 300,\n    .nanoseconds = 0\n};\n\navs_time_duration_t avs_coap_get_exchange_max_time(avs_coap_ctx_t *ctx) {\n    const avs_coap_base_t *base = _avs_coap_get_base(ctx);\n    return base->max_exchange_update_time;\n}\n\nvoid avs_coap_set_exchange_max_time(avs_coap_ctx_t *ctx,\n                                    const avs_time_duration_t time) {\n    avs_coap_base_t *base = _avs_coap_get_base(ctx);\n    base->max_exchange_update_time = time;\n}\n\n#ifdef WITH_AVS_COAP_BLOCK\nstatic avs_error_t get_payload_chunk_size(avs_coap_ctx_t *ctx,\n                                          uint8_t code,\n                                          const avs_coap_option_block_t *block,\n                                          const avs_coap_options_t *options,\n                                          size_t *out_payload_chunk_size) {\n    assert(options);\n\n    if (block) {\n        const size_t max_payload_size = ctx->vtable->max_outgoing_payload_size(\n                ctx, AVS_COAP_MAX_TOKEN_LENGTH, options, code);\n\n        *out_payload_chunk_size = avs_max_power_of_2_not_greater_than(\n                AVS_MIN(AVS_COAP_BLOCK_MAX_SIZE,\n                        AVS_MIN(max_payload_size, block->size)));\n        if (*out_payload_chunk_size < block->size) {\n            return _avs_coap_err(AVS_COAP_ERR_MESSAGE_TOO_BIG);\n        }\n        return AVS_OK;\n    } else {\n        size_t max_payload_size = ctx->vtable->max_outgoing_payload_size(\n                ctx, AVS_COAP_MAX_TOKEN_LENGTH, options, code);\n\n        /**\n         * We're sending the first block of a request, or a response for which\n         * the requester indicated no block size preference. The transfer may\n         * not even need BLOCK. We can freely choose any payload size.\n         *\n         * When calculating max_payload_size, take into account that we may\n         * need to add a BLOCK option if the payload turns out to be large.\n         */\n        if (max_payload_size > AVS_COAP_OPT_BLOCK_MAX_SIZE) {\n            max_payload_size -= AVS_COAP_OPT_BLOCK_MAX_SIZE;\n        } else {\n            max_payload_size = 0;\n        }\n\n        *out_payload_chunk_size = avs_max_power_of_2_not_greater_than(\n                AVS_MIN(max_payload_size, AVS_COAP_BLOCK_MAX_SIZE));\n        if (*out_payload_chunk_size < AVS_COAP_BLOCK_MIN_SIZE) {\n            return _avs_coap_err(AVS_COAP_ERR_MESSAGE_TOO_BIG);\n        }\n\n        return AVS_OK;\n    }\n}\n#else  // WITH_AVS_COAP_BLOCK\nstatic avs_error_t\nget_payload_chunk_size(avs_coap_ctx_t *ctx,\n                       uint8_t code,\n                       const avs_coap_option_block_t *block_opt,\n                       const avs_coap_options_t *options,\n                       size_t *out_payload_chunk_size) {\n    (void) block_opt;\n\n    const size_t max_payload_size = ctx->vtable->max_outgoing_payload_size(\n            ctx, AVS_COAP_MAX_TOKEN_LENGTH, options, code);\n    *out_payload_chunk_size =\n            AVS_MIN(max_payload_size,\n                    AVS_COAP_EXCHANGE_OUTGOING_CHUNK_PAYLOAD_MAX_SIZE - 1);\n    return AVS_OK;\n}\n#endif // WITH_AVS_COAP_BLOCK\n\navs_error_t _avs_coap_get_max_block_size(avs_coap_ctx_t *ctx,\n                                         uint8_t code,\n                                         const avs_coap_options_t *options,\n                                         size_t *out_payload_chunk_size) {\n    assert(options);\n\n    avs_coap_option_block_t block;\n    bool has_block = false;\n\n#ifdef WITH_AVS_COAP_BLOCK\n    avs_error_t err = _avs_coap_options_get_block_by_code(options, code, &block,\n                                                          &has_block);\n    if (avs_is_err(err)) {\n        return err;\n    }\n#endif // WITH_AVS_COAP_BLOCK\n\n    return get_payload_chunk_size(ctx, code, has_block ? &block : NULL, options,\n                                  out_payload_chunk_size);\n}\n\nbool _avs_coap_socket_definitely_exhausted(avs_coap_ctx_t *ctx) {\n    avs_net_socket_opt_value_t has_buffered_data;\n    return avs_is_ok(avs_net_socket_get_opt(_avs_coap_get_base(ctx)->socket,\n                                            AVS_NET_SOCKET_HAS_BUFFERED_DATA,\n                                            &has_buffered_data))\n           && !has_buffered_data.flag;\n}\n\navs_coap_stats_t avs_coap_get_stats(avs_coap_ctx_t *ctx) {\n    if (ctx->vtable->get_stats) {\n        return ctx->vtable->get_stats(ctx);\n    }\n    return (avs_coap_stats_t) { 0 };\n}\n\nstatic bool\nis_critical_opt_valid(uint8_t msg_code,\n                      uint32_t opt_number,\n                      avs_coap_critical_option_validator_t fallback_validator) {\n    switch (opt_number) {\n    case AVS_COAP_OPTION_BLOCK1:\n        return msg_code == AVS_COAP_CODE_PUT || msg_code == AVS_COAP_CODE_POST\n               || msg_code == AVS_COAP_CODE_FETCH\n               || msg_code == AVS_COAP_CODE_IPATCH;\n    case AVS_COAP_OPTION_BLOCK2:\n        return msg_code == AVS_COAP_CODE_GET || msg_code == AVS_COAP_CODE_PUT\n               || msg_code == AVS_COAP_CODE_POST\n               || msg_code == AVS_COAP_CODE_FETCH\n               || msg_code == AVS_COAP_CODE_IPATCH;\n    default:\n        return fallback_validator(msg_code, opt_number);\n    }\n}\n\nint avs_coap_options_validate_critical(\n        const avs_coap_request_header_t *request_header,\n        avs_coap_critical_option_validator_t validator) {\n    avs_coap_request_header_t *const_cast_request_header =\n            (avs_coap_request_header_t *) (intptr_t) request_header;\n\n    avs_coap_option_iterator_t it;\n    for (it = _avs_coap_optit_begin(&const_cast_request_header->options);\n         !_avs_coap_optit_end(&it);\n         _avs_coap_optit_next(&it)) {\n        const uint32_t opt_number = _avs_coap_optit_number(&it);\n        if (_avs_coap_option_is_critical((uint16_t) opt_number)\n                && !is_critical_opt_valid(request_header->code, opt_number,\n                                          validator)) {\n            LOG(DEBUG,\n                _(\"warning: invalid critical option in query \") \"%s\" _(\n                        \": \") \"%\" PRIu32,\n                AVS_COAP_CODE_STRING(request_header->code), opt_number);\n            return -1;\n        }\n    }\n    return 0;\n}\n"
  },
  {
    "path": "deps/avs_coap/src/avs_coap_ctx.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef AVS_COAP_SRC_CTX_H\n#define AVS_COAP_SRC_CTX_H\n\n#include <avsystem/commons/avs_errno.h>\n#include <avsystem/commons/avs_list.h>\n#include <avsystem/commons/avs_prng.h>\n#include <avsystem/commons/avs_shared_buffer.h>\n\n#include <avsystem/coap/async.h>\n#include <avsystem/coap/ctx.h>\n\n#ifdef WITH_AVS_COAP_OBSERVE\n#    include \"avs_coap_observe.h\"\n#endif // WITH_AVS_COAP_OBSERVE\n\n#ifdef WITH_AVS_COAP_STREAMING_API\n#    include \"streaming/avs_coap_streaming_client.h\"\n#endif // WITH_COAP_STREAMING_API\n\n#include \"async/avs_coap_async_server.h\"\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\nstatic inline avs_error_t _avs_coap_err(avs_coap_error_t error) {\n    assert(error > 0 && error <= UINT16_MAX);\n    return (avs_error_t) {\n        .category = AVS_COAP_ERR_CATEGORY,\n        .code = (uint16_t) error\n    };\n}\n\nstruct avs_coap_ctx_vtable;\n\n/**\n * Abstract CoAP context.\n */\nstruct avs_coap_ctx {\n    const struct avs_coap_ctx_vtable *vtable;\n};\n\n/**\n * CoAP base, containing only stuff that's completely independent from the\n * transport protocol used.\n */\nstruct avs_coap_base {\n    /** Last assigned exchange ID. */\n    avs_coap_exchange_id_t last_exchange_id;\n\n    /**\n     * All unfinished asynchronous request exchanges initiated by us acting\n     * as a CoAP client (outgoing requests/incoming responses).\n     *\n     * NOTE: Exchanges for which the initial request packet has not yet been\n     * sent are always kept at the beginning of this list.\n     */\n    AVS_LIST(struct avs_coap_exchange) client_exchanges;\n\n    /**\n     * All unfinished asynchronous request exchanges initiated by remote CoAP\n     * client (incoming requests/outgoing responses).\n     */\n    AVS_LIST(struct avs_coap_exchange) server_exchanges;\n\n#ifdef WITH_AVS_COAP_OBSERVE\n    /** Active observations. */\n    AVS_LIST(avs_coap_observe_t) observes;\n#endif // WITH_AVS_COAP_OBSERVE\n\n    /** PRNG context. */\n    avs_crypto_prng_ctx_t *prng_ctx;\n\n#ifdef WITH_AVS_COAP_STREAMING_API\n    /** Stream object used by streaming API. */\n    coap_stream_t coap_stream;\n#endif // WITH_AVS_COAP_STREAMING_API\n\n    avs_net_socket_t *socket;\n    avs_shared_buffer_t *in_buffer;\n    avs_shared_buffer_t *out_buffer;\n\n    avs_sched_t *sched;\n\n    /* Maximum allowed time between the CoAP exchange updates */\n    avs_time_duration_t max_exchange_update_time;\n\n    /**\n     * Scheduler job used to detect cases where the remote host lost interest\n     * in a block-wise request before it completed, or to handle any\n     * time-dependent actions required by the transport (e.g. retransmissions\n     * or request timeouts).\n     */\n    avs_sched_handle_t retry_or_request_expired_job;\n\n    /* Used to ensure in_buffer is not used twice. */\n    bool in_buffer_in_use;\n\n    /* State necessary for handling incoming requests. */\n    avs_coap_request_ctx_t request_ctx;\n};\n\nstatic inline avs_coap_base_t *_avs_coap_get_base(avs_coap_ctx_t *ctx) {\n    return ctx->vtable->get_base(ctx);\n}\n\navs_error_t _avs_coap_in_buffer_acquire(avs_coap_ctx_t *ctx,\n                                        uint8_t **out_in_buffer,\n                                        size_t *out_in_buffer_size);\n\nstatic inline void _avs_coap_in_buffer_release(avs_coap_ctx_t *ctx) {\n    avs_coap_base_t *coap_base = _avs_coap_get_base(ctx);\n    assert(coap_base->in_buffer_in_use);\n    avs_shared_buffer_release(coap_base->in_buffer);\n    coap_base->in_buffer_in_use = false;\n}\n\n#if defined(WITH_AVS_COAP_POISONING) && !defined(AVS_UNIT_TESTING)\n// No CoAP context should use scheduler jobs other than\n// @ref avs_coap_ctx_t#retry_or_request_expired_job ; this is necessary to\n// properly support streaming API\n#    pragma GCC poison avs_sched_handle_t\n#endif\n\n/**\n * Utility methods not specific to any particular protocol.\n * @{\n */\n\nstatic inline void _avs_coap_base_init(avs_coap_base_t *base,\n                                       avs_coap_ctx_t *coap_ctx,\n                                       avs_shared_buffer_t *in_buffer,\n                                       avs_shared_buffer_t *out_buffer,\n                                       avs_sched_t *sched,\n                                       avs_crypto_prng_ctx_t *prng_ctx) {\n    base->last_exchange_id = AVS_COAP_EXCHANGE_ID_INVALID;\n    base->client_exchanges = NULL;\n    base->server_exchanges = NULL;\n    base->prng_ctx = prng_ctx;\n    base->socket = NULL;\n    base->in_buffer = in_buffer;\n    base->out_buffer = out_buffer;\n    base->sched = sched;\n    base->max_exchange_update_time = AVS_COAP_DEFAULT_EXCHANGE_MAX_TIME;\n\n#ifdef WITH_AVS_COAP_STREAMING_API\n    _avs_coap_stream_init(&base->coap_stream, coap_ctx);\n#else  // WITH_AVS_COAP_STREAMING_API\n    (void) coap_ctx;\n#endif // WITH_AVS_COAP_STREAMING_API\n}\n\nstatic inline avs_coap_ctx_t *\n_avs_coap_ctx_from_request_ctx(avs_coap_request_ctx_t *request_ctx) {\n    return request_ctx->coap_ctx;\n}\n\nstatic inline avs_error_t\n_avs_coap_ctx_set_socket_base(avs_coap_ctx_t *ctx, avs_net_socket_t *socket) {\n    avs_coap_base_t *coap_base = _avs_coap_get_base(ctx);\n    if (coap_base->socket != NULL) {\n        LOG(ERROR, _(\"cannot set socket: it was already set\"));\n        return _avs_coap_err(AVS_COAP_ERR_SOCKET_ALREADY_SET);\n    }\n    coap_base->socket = socket;\n    return AVS_OK;\n}\n\nstatic inline avs_coap_exchange_id_t\n_avs_coap_generate_exchange_id(avs_coap_ctx_t *ctx) {\n    avs_coap_base_t *coap_base = _avs_coap_get_base(ctx);\n    // TODO: handle 64bit value overflow?\n    ++coap_base->last_exchange_id.value;\n    return coap_base->last_exchange_id;\n}\n\nAVS_LIST(struct avs_coap_exchange) *\n_avs_coap_find_exchange_ptr_by_id(AVS_LIST(struct avs_coap_exchange) *list_ptr,\n                                  avs_coap_exchange_id_t id);\n\nAVS_LIST(struct avs_coap_exchange) *_avs_coap_find_exchange_ptr_by_token(\n        AVS_LIST(struct avs_coap_exchange) *list_ptr,\n        const avs_coap_token_t *token);\n\nstatic inline AVS_LIST(struct avs_coap_exchange) *\n_avs_coap_find_client_exchange_ptr_by_id(avs_coap_ctx_t *ctx,\n                                         avs_coap_exchange_id_t id) {\n    return _avs_coap_find_exchange_ptr_by_id(\n            &_avs_coap_get_base(ctx)->client_exchanges, id);\n}\n\nstatic inline AVS_LIST(struct avs_coap_exchange) *\n_avs_coap_find_server_exchange_ptr_by_id(avs_coap_ctx_t *ctx,\n                                         avs_coap_exchange_id_t id) {\n    return _avs_coap_find_exchange_ptr_by_id(\n            &_avs_coap_get_base(ctx)->server_exchanges, id);\n}\n\nstatic inline AVS_LIST(struct avs_coap_exchange)\n_avs_coap_find_client_exchange_by_id(avs_coap_ctx_t *ctx,\n                                     avs_coap_exchange_id_t id) {\n    AVS_LIST(struct avs_coap_exchange) *ptr =\n            _avs_coap_find_client_exchange_ptr_by_id(ctx, id);\n    if (ptr) {\n        return *ptr;\n    }\n    return NULL;\n}\n\nstatic inline AVS_LIST(struct avs_coap_exchange)\n_avs_coap_find_server_exchange_by_id(avs_coap_ctx_t *ctx,\n                                     avs_coap_exchange_id_t id) {\n    AVS_LIST(struct avs_coap_exchange) *ptr =\n            _avs_coap_find_server_exchange_ptr_by_id(ctx, id);\n    if (ptr) {\n        return *ptr;\n    }\n    return NULL;\n}\n\n#ifdef WITH_AVS_COAP_OBSERVE\nstatic inline bool _avs_coap_is_observe(avs_coap_ctx_t *ctx,\n                                        const avs_coap_token_t *token) {\n    AVS_LIST(avs_coap_observe_t) it;\n    AVS_LIST_FOREACH(it, _avs_coap_get_base(ctx)->observes) {\n        if (avs_coap_token_equal(&it->id.token, token)) {\n            return true;\n        }\n    }\n    return false;\n}\n#endif // WITH_AVS_COAP_OBSERVE\n\nstatic inline AVS_LIST(struct avs_coap_exchange)\n_avs_coap_find_exchange_by_id(avs_coap_ctx_t *ctx, avs_coap_exchange_id_t id) {\n    AVS_LIST(struct avs_coap_exchange) exchange =\n            _avs_coap_find_client_exchange_by_id(ctx, id);\n    if (!exchange) {\n        exchange = _avs_coap_find_server_exchange_by_id(ctx, id);\n    }\n    return exchange;\n}\n\navs_error_t\n_avs_coap_ctx_generate_token(avs_crypto_prng_ctx_t *prng_ctx,\n                             avs_coap_token_t *out_token) WEAK_IN_TESTS;\n\n/**\n * Reschedules @ref _avs_coap_retry_or_request_expired_job to be called no\n * later than at @p target_time. If it is already scheduled for an earlier\n * time point, this call does nothing.\n */\nvoid _avs_coap_reschedule_retry_or_request_expired_job(\n        avs_coap_ctx_t *ctx, avs_time_monotonic_t target_time);\n\n/**\n * Calls @ref avs_coap_ctx_vtable_t#on_retry_or_request_expired handler and\n * handles any transport-agnostic timeouts as necessary.\n *\n * Reschedules itself for execution at appropriate time.\n *\n * Note: the streaming API uses this function outside the scheduler to be able\n * to handle any retransmissions required for synchronous request processing.\n *\n * @returns Time point at which next execution of this job was scheduled\n */\navs_time_monotonic_t\n_avs_coap_retry_or_request_expired_job(avs_coap_ctx_t *ctx);\n\n/**\n * Queries the maximum number of payload bytes possible to include in a CoAP\n * message with given @p code and @p options when sending it using @p ctx .\n *\n * @param ctx\n * CoAP context to operate on.\n *\n * @param code\n * CoAP code of the message to calculate size for. Used to determine if the\n * message is a request or response, necessary for inspecting the BLOCK option.\n *\n * @param options\n * CoAP options list that will be included in sent message.\n *\n * @param[out] out_payload_chunk_size\n * On successful call, it is set to the calculated number of payload bytes.\n *\n * @returns\n *\n * @li @ref AVS_OK when the size was calculated correctly,\n * @li @ref AVS_COAP_ERR_MESSAGE_TOO_BIG when either:\n *\n *     @li calculated payload size is smaller than smallest possible block size,\n *         which means that payload size is limited to just a few bytes and\n *         BLOCK-wise transfer is impossible,\n *     @li @p options contains a BLOCK option with size larger than the\n *         calculated one, making it impossible to include that much data in\n *         a packet sent using @p ctx . The caller may attempt to lower that\n *         BLOCK size to be able to continue.\n *\n *     In either case, @p out_payload_chunk_size is set to the calculated\n *     payload size.\n *\n * @li other error code, in which case @p out_payload_chunk_size value is\n *     undefined.\n */\navs_error_t _avs_coap_get_max_block_size(avs_coap_ctx_t *ctx,\n                                         uint8_t code,\n                                         const avs_coap_options_t *options,\n                                         size_t *out_payload_chunk_size);\n\n/*\n * Queries the expected size of the chunk that will be requested during the\n * first call to @ref avs_coap_payload_writer_t for an newly created exchange\n * with given @p code and @p options .\n *\n * NOTE: this accounts for the BLOCK size *and* extra byte required for EOF\n * detection.\n */\nstatic inline avs_error_t _avs_coap_get_first_outgoing_chunk_payload_size(\n        avs_coap_ctx_t *ctx,\n        uint8_t code,\n        const avs_coap_options_t *options,\n        size_t *out_payload_chunk_size) {\n    avs_error_t err = _avs_coap_get_max_block_size(ctx, code, options,\n                                                   out_payload_chunk_size);\n    if (avs_is_ok(err)) {\n        // +1 for EOF detection\n        ++*out_payload_chunk_size;\n    }\n    return err;\n}\n\n/** @} */\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif // AVS_COAP_SRC_CTX_H\n"
  },
  {
    "path": "deps/avs_coap/src/avs_coap_ctx_vtable.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef AVS_COAP_SRC_CTX_VTABLE_H\n#define AVS_COAP_SRC_CTX_VTABLE_H\n\n#include <avsystem/coap/ctx.h>\n#include <avsystem/coap/observe.h>\n#include <avsystem/coap/option.h>\n#include <avsystem/coap/token.h>\n\n#include \"avs_coap_observe.h\"\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\ntypedef struct avs_coap_base avs_coap_base_t;\n\n/**\n * A CoAP message whose options and payload point to storage not owned by the\n * object itself.\n *\n * It may contain only a part of the payload.\n */\ntypedef struct {\n    uint8_t code;\n    avs_coap_token_t token;\n    avs_coap_options_t options;\n\n    /**\n     * Offset within the original CoAP message's payload that corresponds to\n     * offset 0 of data pointed to by the <c>payload</c> field.\n     */\n    size_t payload_offset;\n\n    /**\n     * Pointer to memory that contains the part of the payload represented by\n     * this object.\n     */\n    const void *payload;\n\n    /**\n     * Number of bytes of valid data at the location pointed to by the\n     * <c>payload</c> field.\n     */\n    size_t payload_size;\n\n    /**\n     * Length of the entire payload in the original CoAP message.\n     */\n    size_t total_payload_size;\n} avs_coap_borrowed_msg_t;\n\ntypedef enum {\n    AVS_COAP_SEND_RESULT_PARTIAL_CONTENT,\n    AVS_COAP_SEND_RESULT_OK,\n    AVS_COAP_SEND_RESULT_FAIL,\n    AVS_COAP_SEND_RESULT_CANCEL\n} avs_coap_send_result_t;\n\ntypedef enum {\n    AVS_COAP_RESPONSE_ACCEPTED,\n    AVS_COAP_RESPONSE_NOT_ACCEPTED\n} avs_coap_send_result_handler_result_t;\n\n/**\n * Handler called whenever:\n *\n * - the context is sure a message was delivered:\n *\n *   - if the message was NOT a request, result is AVS_COAP_SEND_RESULT_OK and\n *     @p response is NULL.\n *\n *   - if the message was a request, @p response is not NULL and result may be:\n *\n *     - AVS_COAP_SEND_RESULT_PARTIAL_CONTENT, if @p response contains a\n *       partial response payload. The handler will be called again later with\n *       further data.\n *\n *     - AVS_COAP_SEND_RESULT_OK, if @p response contains a full response or the\n *       last part of a response. The handler will not be called again later.\n *       Note: in case of a sequence of AVS_COAP_SEND_RESULT_PARTIAL_CONTENT\n *       + AVS_COAP_SEND_RESULT_OK calls, @p response payload contains\n *       consecutive chunks of data (i.e. no data will be passed to the handler\n *       twice).\n *\n * - the message was not delivered (AVS_COAP_SEND_RESULT_FAIL), in which case\n *   @p fail_errno is set to a specific error code.\n *\n * - @ref avs_coap_cancel_delivery_t was called (AVS_COAP_SEND_RESULT_CANCEL).\n *\n * If @p result is @ref AVS_COAP_SEND_RESULT_OK and @p response is not NULL,\n * this handler is supposed to return @ref AVS_COAP_RESPONSE_ACCEPTED if it\n * accepts the response or @ref AVS_COAP_RESPONSE_NOT_ACCEPTED if it doesn't. In\n * other cases return value is ignored.\n * If the response is not accepted, pending request will not be deleted in\n * CoAP ctx and further responses to the same request will be accepted.\n * This is used in OSCORE, to ignore unencrypted responses and prevent possible\n * attacks trying to make the CoAP client unusable.\n */\ntypedef avs_coap_send_result_handler_result_t\navs_coap_send_result_handler_t(avs_coap_ctx_t *ctx,\n                               avs_coap_send_result_t result,\n                               avs_error_t fail_err,\n                               const avs_coap_borrowed_msg_t *response,\n                               void *arg);\n\n/**\n * Virtual method typedefs.\n *\n * These are CoAP context methods that need to be implemented for each\n * supported transport protocol.\n *\n * Vtable methods are supposed to only deal with:\n * - packet encoding/decoding\n * - retransmissions (if required)\n * - token-based request-response matching\n * - transport-specific stuff, e.g.:\n *   - UDP:\n *     - message IDs\n *     - Separate Responses\n *     - message types\n *     - Observe cancellation with Reset response\n *   - TCP:\n *     - CSM messages\n *\n * In particular, vtable methods SHOULD NOT:\n * - handle avs_coap_exchange_t objects,\n * - handle any CoAP options,\n * - handle BLOCK options or split messages into multiple separate packets,\n * - assign tokens\n *\n * @{\n */\ntypedef void avs_coap_cleanup_t(avs_coap_ctx_t *ctx);\n\n/**\n * Returns base of @p ctx .\n */\ntypedef avs_coap_base_t *avs_coap_get_base_t(avs_coap_ctx_t *ctx);\n\n/**\n * @returns Maximum number of bytes possible to include in a single CoAP packet\n *          with specified @p token_size, @p options and @p message_code .\n *\n *          Upper layers are supposed to split payload into BLOCK/BERT chunks\n *          in case the whole logical request/response payload is larger than\n *          the value returned by this function.\n *\n *          If this returns 0, @ref avs_coap_send_message_t may fail even if\n *          no payload is passed.\n *\n * Note: @p options may be NULL, which is interpreted as no options.\n *       @p message_code is used only in OSCORE context to properly determine\n *          actual options size. It's ignored if @p options is NULL.\n */\ntypedef size_t\navs_coap_max_outgoing_payload_size_t(avs_coap_ctx_t *ctx,\n                                     size_t token_size,\n                                     const avs_coap_options_t *options,\n                                     uint8_t message_code);\n\n/**\n * @returns Maximum number of bytes possible to receive in a single CoAP packet\n *          with specified @p token_size, @p options and @p message_code .\n *\n *          If this returns 0, @ref avs_coap_recv_message_t may fail even if\n *          no payload is passed.\n */\ntypedef size_t\navs_coap_max_incoming_payload_size_t(avs_coap_ctx_t *ctx,\n                                     size_t token_size,\n                                     const avs_coap_options_t *options,\n                                     uint8_t message_code);\n\n/**\n * Sends a single CoAP message and optionally registers a callback to be\n * executed when a response is received.\n *\n * @returns @ref AVS_OK for success, or an error condition for which the\n *          operation failed.\n */\ntypedef avs_error_t\navs_coap_send_message_t(avs_coap_ctx_t *ctx,\n                        const avs_coap_borrowed_msg_t *msg,\n                        avs_coap_send_result_handler_t *send_result_handler,\n                        void *send_result_handler_arg);\n\ntypedef enum {\n    AVS_COAP_EXCHANGE_CLIENT_REQUEST,\n    AVS_COAP_EXCHANGE_SERVER_NOTIFICATION\n} avs_coap_exchange_direction_t;\n\n/**\n * Unregisters a callback configured to run when a response to message with\n * @p token is received, and aborts its retransmissions if any.\n *\n * @p result and @p fail_errno are passed to the appropriate\n * @ref avs_coap_send_result_handler_t .\n */\ntypedef void avs_coap_abort_delivery_t(avs_coap_ctx_t *ctx,\n                                       avs_coap_exchange_direction_t direction,\n                                       const avs_coap_token_t *token,\n                                       avs_coap_send_result_t result,\n                                       avs_error_t fail_err);\n\n/**\n * Forces current request's incoming payload chunks to be ignored. User handler\n * for this request won't be called again.\n *\n * If currently processed message is not a request or @p token doesn't match the\n * token of it, then this function is a no-op.\n *\n * Note:\n * This operation is a noop for transports which receive entire message in a\n * single call to receive_message method.\n */\ntypedef void avs_coap_ignore_current_request_t(avs_coap_ctx_t *ctx,\n                                               const avs_coap_token_t *token);\n\n/**\n * Receives data from the socket associated with @p ctx .\n *\n * If received data is a response to a message previously sent by\n * @ref avs_coap_send_message_t , handles it internally, calling its\n * send_result_handler if it exists.\n *\n * If received data includes a request (with complete options and at least\n * partial payload), or if more payload data is received for a request that\n * was not fully received yet, @p out_request (which shall NEVER be NULL) is\n * filled with information about that request; the options and payload pointers\n * may point to either of:\n *\n * - inside @p in_buffer - in that case the caller is responsible for managing\n *   the lifetime of the buffer passed; @p in_buffer shall NEVER be NULL\n * - internal buffers allocated within the transport-specific part of @p ctx -\n *   in that case the data shall remain valid until the next call to this\n *   handler\n *\n * No request to handle is indicated by out_request->code value that is not\n * a valid request code.\n *\n * @returns @ref AVS_OK for success, or an error condition for which the\n *          operation failed.\n */\ntypedef avs_error_t\navs_coap_receive_message_t(avs_coap_ctx_t *ctx,\n                           uint8_t *in_buffer,\n                           size_t in_buffer_size,\n                           avs_coap_borrowed_msg_t *out_request);\n\n/**\n * Function called whenever a new observation request is accepted.\n *\n * @p observation MUST be initialized valid observation before call to this\n * function.\n */\ntypedef avs_error_t\navs_coap_accept_observation_t(avs_coap_ctx_t *ctx,\n                              avs_coap_observe_t *observation);\n\n/**\n * Function called whenever a scheduler job associated with @p ctx is run.\n *\n * The transport-specific backend MUST NOT allocate any scheduler jobs on\n * @p ctx scheduler object for retransmission/timeout purposes. This function\n * MUST be used instead.\n *\n * The implementation should use @ref _avs_coap_reschedule_timeout_job whenever\n * it knows when it should be notified.\n *\n * Note: spurious calls to this function may occur.\n *\n * @returns Next time this function should be called, or\n *          @ref AVS_TIME_MONOTONIC_INVALID if at the point of calling this\n *          the implementation does not need to perform any scheduled actions.\n */\ntypedef avs_time_monotonic_t avs_coap_on_timeout_t(avs_coap_ctx_t *ctx);\n\n/**\n * Getter for context's statistics. May be not implemented if context does not\n * want to report any.\n */\ntypedef avs_coap_stats_t avs_coap_get_stats_t(avs_coap_ctx_t *ctx);\n\n/**\n * Function called whenever someone executes @ref avs_coap_ctx_set_socket on a\n * context @p ctx.\n *\n * If the call fails, the underlying context state @p ctx MUST NOT be modified.\n * The only exception is context errno, which MAY be set by this function on\n * failure.\n *\n * @returns @ref AVS_OK for success, or an error condition for which the\n *          operation failed.\n */\ntypedef avs_error_t avs_coap_setsock_t(avs_coap_ctx_t *ctx,\n                                       avs_net_socket_t *socket);\n\n/**\n * Used to get the next Observe option value basing on value from the last\n * notification.\n */\ntypedef uint32_t avs_coap_next_observe_option_value_t(avs_coap_ctx_t *ctx,\n                                                      uint32_t last_value);\n\n/** @} */\n\ntypedef struct avs_coap_ctx_vtable {\n    avs_coap_get_base_t *get_base;\n    avs_coap_cleanup_t *cleanup;\n    avs_coap_setsock_t *setsock;\n    avs_coap_max_outgoing_payload_size_t *max_outgoing_payload_size;\n    avs_coap_max_incoming_payload_size_t *max_incoming_payload_size;\n    avs_coap_send_message_t *send_message;\n    avs_coap_abort_delivery_t *abort_delivery;\n    avs_coap_ignore_current_request_t *ignore_current_request;\n    avs_coap_receive_message_t *receive_message;\n    avs_coap_accept_observation_t *accept_observation;\n    avs_coap_on_timeout_t *on_timeout;\n    avs_coap_get_stats_t *get_stats;\n    avs_coap_next_observe_option_value_t *next_observe_option_value;\n} avs_coap_ctx_vtable_t;\n\n/**\n * Checks whether the socket is definitely exhausted, e.g. that we can\n * conclusively say that there is no more data ready to be retrieved from\n * internal buffers (without calling the receive operation on the system\n * socket).\n *\n * Note that if getting the AVS_NET_SOCKET_HAS_BUFFERED_DATA option fails or is\n * not implemented, this will always return false.\n *\n * Also note: This declaration would be more fitting in avs_coap_ctx.h, but is\n * declared here due to include file dependency madness.\n */\nbool _avs_coap_socket_definitely_exhausted(avs_coap_ctx_t *ctx);\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif // AVS_COAP_SRC_CTX_VTABLE_H\n"
  },
  {
    "path": "deps/avs_coap/src/avs_coap_init.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <avsystem/commons/avs_defs.h>\n\n#include <avsystem/coap/avs_coap_config.h>\n\n#if !defined(AVS_COMMONS_WITH_AVS_BUFFER)                  \\\n        || !defined(AVS_COMMONS_WITH_AVS_COMPAT_THREADING) \\\n        || !defined(AVS_COMMONS_WITH_AVS_LIST)             \\\n        || !defined(AVS_COMMONS_WITH_AVS_NET)              \\\n        || !defined(AVS_COMMONS_WITH_AVS_SCHED)            \\\n        || !defined(AVS_COMMONS_WITH_AVS_UTILS)\n#    error \"avs_coap requires following avs_commons components to be enabled: avs_buffer avs_compat_threading avs_list avs_net avs_sched avs_utils\"\n#endif\n\n#if defined(WITH_AVS_COAP_LOGS) && !defined(AVS_COMMONS_WITH_AVS_LOG)\n#    error \"WITH_AVS_COAP_LOGS requires avs_log to be enabled\"\n#endif\n\n#if defined(WITH_AVS_COAP_STREAMING_API) \\\n        && !defined(AVS_COMMONS_WITH_AVS_STREAM)\n#    error \"WITH_AVS_COAP_STREAMING_API requires avs_stream to be enabled\"\n#endif\n\n#if defined(WITH_AVS_COAP_OBSERVE_PERSISTENCE) \\\n        && !defined(AVS_COMMONS_WITH_AVS_PERSISTENCE)\n#    error \"WITH_AVS_COAP_OBSERVE_PERSISTENCE requires avs_persistence to be enabled\"\n#endif\n\n#ifdef AVS_COMMONS_HAVE_VISIBILITY\n/* set default visibility for external symbols */\n#    pragma GCC visibility push(default)\n#    define VISIBILITY_SOURCE_BEGIN _Pragma(\"GCC visibility push(hidden)\")\n#    define VISIBILITY_PRIVATE_HEADER_BEGIN \\\n        _Pragma(\"GCC visibility push(hidden)\")\n#    define VISIBILITY_PRIVATE_HEADER_END _Pragma(\"GCC visibility pop\")\n#else\n#    define VISIBILITY_SOURCE_BEGIN\n#    define VISIBILITY_PRIVATE_HEADER_BEGIN\n#    define VISIBILITY_PRIVATE_HEADER_END\n#endif\n\n#if defined(WITH_AVS_COAP_POISONING) && !defined(AVS_UNIT_TESTING)\n#    include \"avs_coap_poison.h\"\n#endif\n\n#if defined(AVS_UNIT_TESTING) && defined(__GNUC__)\n#    define WEAK_IN_TESTS __attribute__((weak))\n#elif defined(AVS_UNIT_TESTING)\n#    error \"Tests require GCC compatible compiler\"\n#else\n#    define WEAK_IN_TESTS\n#endif\n\n#define _(Arg) AVS_DISPOSABLE_LOG(Arg)\n"
  },
  {
    "path": "deps/avs_coap/src/avs_coap_observe.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <avs_coap_init.h>\n\n#ifdef WITH_AVS_COAP_OBSERVE\n\n#    include <avsystem/commons/avs_errno.h>\n#    include <avsystem/commons/avs_persistence.h>\n\n#    include <avsystem/coap/observe.h>\n\n#    include \"avs_coap_code_utils.h\"\n\n#    define MODULE_NAME coap\n#    include <avs_coap_x_log_config.h>\n\n#    include \"avs_coap_ctx.h\"\n#    include \"options/avs_coap_options.h\"\n\nVISIBILITY_SOURCE_BEGIN\n\nstatic AVS_LIST(avs_coap_observe_t)\ncreate_observe(avs_coap_observe_id_t id,\n               const avs_coap_request_header_t *req,\n               avs_coap_observe_cancel_handler_t *cancel_handler,\n               void *handler_arg) {\n    const size_t options_capacity =\n            _avs_coap_options_request_key_size(&req->options);\n\n    AVS_LIST(avs_coap_observe_t) observe =\n            (AVS_LIST(avs_coap_observe_t)) AVS_LIST_NEW_BUFFER(\n                    sizeof(avs_coap_observe_t) + options_capacity);\n    if (!observe) {\n        LOG_OOM();\n        return NULL;\n    }\n\n    *observe = (avs_coap_observe_t) {\n        .id = id,\n        .cancel_handler = cancel_handler,\n        .cancel_handler_arg = handler_arg,\n        .request_code = req->code,\n        .request_key = _avs_coap_options_copy_request_key(\n                &req->options, observe->options_storage, options_capacity)\n    };\n\n    // BLOCK1 option is not necessary; we won't receive any request payload\n    // while sending Notify\n    avs_coap_options_remove_by_number(&observe->request_key,\n                                      AVS_COAP_OPTION_BLOCK1);\n\n    return observe;\n}\n\nstatic AVS_LIST(avs_coap_observe_t) *\nfind_observe_ptr_by_id(avs_coap_ctx_t *ctx, const avs_coap_observe_id_t *id) {\n    AVS_LIST(avs_coap_observe_t) *observe_ptr;\n    AVS_LIST_FOREACH_PTR(observe_ptr, &_avs_coap_get_base(ctx)->observes) {\n        if (avs_coap_token_equal(&(*observe_ptr)->id.token, &id->token)) {\n            return observe_ptr;\n        }\n    }\n\n    return NULL;\n}\n\navs_error_t\navs_coap_observe_start(avs_coap_ctx_t *ctx,\n                       avs_coap_observe_id_t id,\n                       const avs_coap_request_header_t *req,\n                       avs_coap_observe_cancel_handler_t *cancel_handler,\n                       void *handler_arg) {\n    if (!avs_coap_code_is_request(req->code)) {\n        LOG(ERROR, \"%s\" _(\" is not a valid request code\"),\n            AVS_COAP_CODE_STRING(req->code));\n        return avs_errno(AVS_EINVAL);\n    }\n\n    AVS_LIST(avs_coap_observe_t) observe =\n            create_observe(id, req, cancel_handler, handler_arg);\n    if (!observe) {\n        return avs_errno(AVS_ENOMEM);\n    }\n\n    avs_error_t err = ctx->vtable->accept_observation(ctx, observe);\n\n    if (avs_is_err(err)) {\n        avs_free(observe);\n        return err;\n    }\n\n    // make sure to *replace* existing observation with same ID if one exists\n    avs_coap_observe_cancel(ctx, id);\n\n    LOG(DEBUG, _(\"Observe start: \") \"%s\", AVS_COAP_TOKEN_HEX(&id.token));\n\n    AVS_LIST_INSERT(&_avs_coap_get_base(ctx)->observes, observe);\n    return AVS_OK;\n}\n\nstatic avs_coap_observe_t *find_observe_by_id(avs_coap_ctx_t *ctx,\n                                              const avs_coap_observe_id_t *id) {\n    AVS_LIST(avs_coap_observe_t) *observe_ptr = find_observe_ptr_by_id(ctx, id);\n    return observe_ptr ? *observe_ptr : NULL;\n}\n\navs_error_t\n_avs_coap_observe_setup_notify(avs_coap_ctx_t *ctx,\n                               const avs_coap_observe_id_t *id,\n                               avs_coap_observe_notify_t *out_notify) {\n    avs_coap_observe_t *observe = find_observe_by_id(ctx, id);\n    if (!observe) {\n        LOG(DEBUG, _(\"observation \") \"%s\" _(\" does not exist\"),\n            AVS_COAP_TOKEN_HEX(&id->token));\n        return avs_errno(AVS_EINVAL);\n    }\n\n    *out_notify = (avs_coap_observe_notify_t) {\n        .request_code = observe->request_code,\n        .request_key = observe->request_key,\n        .observe_option_value = ctx->vtable->next_observe_option_value(\n                ctx, observe->last_observe_option_value)\n    };\n    observe->last_observe_option_value = out_notify->observe_option_value;\n    return AVS_OK;\n}\n\navs_error_t avs_coap_observe_cancel(avs_coap_ctx_t *ctx,\n                                    avs_coap_observe_id_t id) {\n    AVS_LIST(avs_coap_observe_t) *observe_ptr = NULL;\n    if (ctx) {\n        observe_ptr = find_observe_ptr_by_id(ctx, &id);\n    }\n    if (!observe_ptr) {\n        LOG(TRACE, _(\"observation \") \"%s\" _(\" does not exist\"),\n            AVS_COAP_TOKEN_HEX(&id.token));\n        return avs_errno(AVS_EINVAL);\n    }\n\n    LOG(DEBUG, _(\"Observe cancel: \") \"%s\", AVS_COAP_TOKEN_HEX(&id.token));\n\n    avs_coap_observe_t *observe = AVS_LIST_DETACH(observe_ptr);\n    if (observe->cancel_handler) {\n        observe->cancel_handler(id, observe->cancel_handler_arg);\n    }\n    AVS_LIST_DELETE(&observe);\n    return AVS_OK;\n}\n\n#    ifdef WITH_AVS_COAP_OBSERVE_PERSISTENCE\n\nstatic const char OBSERVE_ENTRY_MAGIC[] = { 'O', 'B', 'S', '\\0' };\n\nstatic avs_error_t\npersistence_common_fields(avs_persistence_context_t *persistence,\n                          avs_coap_token_t *token,\n                          uint32_t *last_observe_option_value,\n                          uint8_t *request_code,\n                          uint16_t *options_size) {\n    avs_error_t err;\n    (void) (avs_is_err((err = avs_persistence_magic(\n                                persistence, OBSERVE_ENTRY_MAGIC,\n                                sizeof(OBSERVE_ENTRY_MAGIC))))\n            || avs_is_err((err = avs_persistence_u8(persistence, &token->size)))\n            || avs_is_err((err = (avs_coap_token_valid(token)\n                                          ? AVS_OK\n                                          : avs_errno(AVS_EBADMSG))))\n            || avs_is_err((err = avs_persistence_bytes(\n                                   persistence, token->bytes, token->size)))\n            || avs_is_err((err = avs_persistence_u32(\n                                   persistence, last_observe_option_value)))\n            || avs_is_err((err = avs_persistence_u8(persistence, request_code)))\n            || avs_is_err(\n                       (err = avs_persistence_u16(persistence, options_size))));\n    return err;\n}\n\navs_error_t avs_coap_observe_persist(avs_coap_ctx_t *ctx,\n                                     avs_coap_observe_id_t id,\n                                     avs_persistence_context_t *persistence) {\n    if (avs_persistence_direction(persistence) != AVS_PERSISTENCE_STORE) {\n        return avs_errno(AVS_EINVAL);\n    }\n    avs_coap_observe_t *observe = find_observe_by_id(ctx, &id);\n    if (!observe) {\n        LOG(ERROR,\n            _(\"Cannot persist observation \") \"%s\" _(\": it does not exist\"),\n            AVS_COAP_TOKEN_HEX(&id.token));\n        return avs_errno(AVS_EINVAL);\n    }\n    uint16_t options_size = (uint16_t) observe->request_key.size;\n    if (options_size != observe->request_key.size) {\n        LOG(ERROR, _(\"Options longer than \") \"%u\" _(\" are not supported\"),\n            (unsigned) UINT16_MAX);\n        return _avs_coap_err(AVS_COAP_ERR_NOT_IMPLEMENTED);\n    }\n\n    avs_error_t err =\n            persistence_common_fields(persistence, &id.token,\n                                      &observe->last_observe_option_value,\n                                      &observe->request_code, &options_size);\n    if (avs_is_ok(err)) {\n        err = avs_persistence_bytes(persistence, observe->request_key.begin,\n                                    options_size);\n    }\n    return err;\n}\n\navs_error_t avs_coap_observe_restore_with_id(\n        avs_coap_ctx_t *ctx,\n        avs_coap_observe_cancel_handler_t *cancel_handler,\n        void *handler_arg,\n        avs_coap_observe_id_t *out_id,\n        avs_persistence_context_t *persistence) {\n    if (avs_persistence_direction(persistence) != AVS_PERSISTENCE_RESTORE) {\n        return avs_errno(AVS_EINVAL);\n    }\n    avs_coap_base_t *coap_base = _avs_coap_get_base(ctx);\n    if (coap_base->socket) {\n        LOG(ERROR, _(\"CoAP context is already initialized, cannot restore \"\n                     \"observation state\"));\n        return avs_errno(AVS_EINVAL);\n    }\n\n    avs_coap_observe_id_t id = { AVS_COAP_TOKEN_EMPTY };\n    uint32_t last_observe_option_value;\n    uint8_t request_code;\n    uint16_t options_size = 0;\n    AVS_LIST(avs_coap_observe_t) observe;\n    avs_error_t err = persistence_common_fields(persistence, &id.token,\n                                                &last_observe_option_value,\n                                                &request_code, &options_size);\n    if (avs_is_err(err)) {\n        return err;\n    }\n\n    if (find_observe_by_id(ctx, &id)) {\n        LOG(ERROR, _(\"Observe \") \"%s\" _(\" already exists\"),\n            AVS_COAP_TOKEN_HEX(&id.token));\n        // persistence data likely malformed\n        return avs_errno(AVS_EBADMSG);\n    }\n\n    observe = (AVS_LIST(avs_coap_observe_t)) AVS_LIST_NEW_BUFFER(\n            sizeof(avs_coap_observe_t) + options_size);\n    if (!observe) {\n        LOG_OOM();\n        return avs_errno(AVS_ENOMEM);\n    }\n    *observe = (avs_coap_observe_t) {\n        .id = id,\n        .cancel_handler = cancel_handler,\n        .cancel_handler_arg = handler_arg,\n        .last_observe_option_value = last_observe_option_value,\n        .request_code = request_code,\n        .request_key = avs_coap_options_create_empty(observe->options_storage,\n                                                     options_size)\n    };\n    observe->request_key.size = options_size;\n    if (avs_is_err((err = avs_persistence_bytes(persistence,\n                                                observe->options_storage,\n                                                options_size)))) {\n        AVS_LIST_DELETE(&observe);\n        return err;\n    }\n    LOG(DEBUG, _(\"Observe (restored) start: \") \"%s\",\n        AVS_COAP_TOKEN_HEX(&id.token));\n    AVS_LIST_INSERT(&coap_base->observes, observe);\n    if (out_id) {\n        *out_id = id;\n    }\n    return AVS_OK;\n}\n\n#    endif // WITH_AVS_COAP_OBSERVE_PERSISTENCE\n\n#endif // WITH_AVS_COAP_OBSERVE\n"
  },
  {
    "path": "deps/avs_coap/src/avs_coap_observe.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef AVS_COAP_SRC_OBSERVE_H\n#define AVS_COAP_SRC_OBSERVE_H\n\n#include <avsystem/coap/observe.h>\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\ntypedef struct {\n    /** An ID (CoAP token) that uniquely identifies an observation. */\n    avs_coap_observe_id_t id;\n\n    /** Function to call when the observation is canceled. */\n    avs_coap_observe_cancel_handler_t *cancel_handler;\n    void *cancel_handler_arg;\n\n    /** Last Observe option value sent to the server. */\n    uint32_t last_observe_option_value;\n\n    /**\n     * Values present in the original Observe request.\n     * Saved to match requests for notification blocks past the first one\n     * in case of block-wise notifications.\n     */\n    uint8_t request_code;\n    avs_coap_options_t request_key;\n\n    /** Storage space for options. */\n    uint8_t options_storage[];\n} avs_coap_observe_t;\n\nstatic inline uint32_t _avs_coap_observe_initial_option_value(void) {\n    // Response to the original Observe request always set the option to 0.\n    // Further notifications use larger values.\n    return 0;\n}\n\ntypedef struct {\n    uint8_t request_code;\n    avs_coap_options_t request_key;\n    uint32_t observe_option_value;\n} avs_coap_observe_notify_t;\n\navs_error_t\n_avs_coap_observe_setup_notify(avs_coap_ctx_t *ctx,\n                               const avs_coap_observe_id_t *id,\n                               avs_coap_observe_notify_t *out_notify);\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif // AVS_COAP_SRC_OBSERVE_H\n"
  },
  {
    "path": "deps/avs_coap/src/avs_coap_parse_utils.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef AVS_COAP_SRC_PARSE_UTILS_H\n#define AVS_COAP_SRC_PARSE_UTILS_H\n\n#include <stddef.h>\n#include <stdint.h>\n#include <string.h>\n\n#include <avsystem/commons/avs_utils.h>\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\n#define _AVS_FIELD_GET(field, mask, shift) (((field) & (mask)) >> (shift))\n#define _AVS_FIELD_SET(field, mask, shift, value) \\\n    ((field) = (uint8_t) (((field) & ~(mask))     \\\n                          | (uint8_t) (((value) << (shift)) & (mask))))\n\nstatic inline uint16_t extract_u16(const uint8_t *data) {\n    uint16_t result;\n    memcpy(&result, data, sizeof(uint16_t));\n    return avs_convert_be16(result);\n}\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif // AVS_COAP_SRC_PARSE_UTILS_H\n"
  },
  {
    "path": "deps/avs_coap/src/avs_coap_poison.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef AVS_COAP_POISON_H\n#define AVS_COAP_POISON_H\n\n// This file ensures that some functions we \"don't like\" from the standard\n// library (for reasons described below) are not used in any of the source\n// files. This file is included only when compiling using GCC\n//\n// Also note that some functions (such as time()) are blacklisted with whole\n// headers, through test_headers.py.\n\n#include <avsystem/commons/avs_defs.h>\n\n// STDIO ///////////////////////////////////////////////////////////////////////\n\n// Forward inclusion of standard headers, before poisoning all of their names\n#include <stdio.h>\n#include <stdlib.h>\n\n#undef stdin\n#undef stdout\n#undef stderr\n#undef getc\n#undef putc\n\n#pragma GCC poison fclose\n#pragma GCC poison fflush\n#pragma GCC poison fgetc\n#pragma GCC poison fgetpos\n#pragma GCC poison fgets\n#pragma GCC poison fgetwc\n#pragma GCC poison fgetws\n#pragma GCC poison fopen\n#pragma GCC poison fprintf\n#pragma GCC poison fputc\n#pragma GCC poison fputs\n#pragma GCC poison fputwc\n#pragma GCC poison fputws\n#pragma GCC poison freopen\n#pragma GCC poison fscanf\n#pragma GCC poison fsetpos\n#pragma GCC poison fwprintf\n#pragma GCC poison fwrite\n#pragma GCC poison fwscanf\n#pragma GCC poison getc\n#pragma GCC poison getchar\n#pragma GCC poison gets\n#pragma GCC poison getwc\n#pragma GCC poison getwchar\n#pragma GCC poison perror\n#pragma GCC poison printf\n#pragma GCC poison putc\n#pragma GCC poison putchar\n#pragma GCC poison puts\n#pragma GCC poison putwc\n#pragma GCC poison putwchar\n#pragma GCC poison remove\n#pragma GCC poison rename\n#pragma GCC poison rewind\n#pragma GCC poison scanf\n#pragma GCC poison setbuf\n#pragma GCC poison setvbuf\n#pragma GCC poison stderr\n#pragma GCC poison stdin\n#pragma GCC poison stdout\n#pragma GCC poison tmpfile\n#pragma GCC poison tmpnam\n#pragma GCC poison ungetc\n#pragma GCC poison ungetwc\n#pragma GCC poison vfprintf\n#pragma GCC poison vfscanf\n#pragma GCC poison vfwprintf\n#pragma GCC poison vfwscanf\n#pragma GCC poison vprintf\n#pragma GCC poison vscanf\n#pragma GCC poison vwprintf\n#pragma GCC poison vwscanf\n#pragma GCC poison wprintf\n#pragma GCC poison wscanf\n\n#pragma GCC poison malloc\n#pragma GCC poison calloc\n#pragma GCC poison realloc\n#pragma GCC poison free\n\n#pragma GCC poison atexit\n#pragma GCC poison exit\n#pragma GCC poison getenv\n#pragma GCC poison abort\n\n// System program control flow functions\n#pragma GCC poison _Exit\n#pragma GCC poison system\n\n// Default (and not thread-safe) PRNG\n#pragma GCC poison rand\n#pragma GCC poison srand\n\n// multibyte character conversions\n#pragma GCC poison mblen\n#pragma GCC poison mbtowc\n#pragma GCC poison mbstowc\n#pragma GCC poison wcstombs\n#pragma GCC poison wctomb\n\n#endif /* AVS_COAP_POISON_H */\n"
  },
  {
    "path": "deps/avs_coap/src/avs_coap_x_log_config.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifdef LOG\n#    undef LOG\n#endif\n\n#ifndef MODULE_NAME\n#    error \"You need to define MODULE_NAME before including this header\"\n#endif\n\n#ifdef WITH_AVS_COAP_LOGS\n// these macros interfere with avs_log() macro implementation\n#    ifdef TRACE\n#        undef TRACE\n#    endif\n#    ifdef DEBUG\n#        undef DEBUG\n#    endif\n#    ifdef INFO\n#        undef INFO\n#    endif\n#    ifdef WARNING\n#        undef WARNING\n#    endif\n#    ifdef ERROR\n#        undef ERROR\n#    endif\n\n#    ifdef WITH_AVS_COAP_TRACE_LOGS\n#        define AVS_LOG_WITH_TRACE\n#    endif\n#    include <avsystem/commons/avs_log.h>\n#    define LOG(...) avs_log(MODULE_NAME, __VA_ARGS__)\nvoid _avs_coap_log_oom__(void);\n#    define LOG_OOM() _avs_coap_log_oom__()\n#else // WITH_AVS_COAP_LOGS\n#    define LOG(...) ((void) 0)\n#    define LOG_OOM() ((void) 0)\n// used by tcp_ctx\n#    define avs_log_internal_l__(...) ((void) 0)\n#endif // WITH_AVS_COAP_LOG\n"
  },
  {
    "path": "deps/avs_coap/src/options/avs_coap_iterator.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <avs_coap_init.h>\n\n#include <string.h>\n\n#include <avsystem/commons/avs_utils.h>\n\n#include \"options/avs_coap_iterator.h\"\n#include \"options/avs_coap_option.h\"\n\n#define MODULE_NAME coap\n#include <avs_coap_x_log_config.h>\n\nVISIBILITY_SOURCE_BEGIN\n\navs_coap_option_iterator_t _avs_coap_optit_begin(avs_coap_options_t *opts) {\n    avs_coap_option_iterator_t optit = {\n        .opts = opts,\n        .curr_opt = opts->begin,\n        .prev_opt_number = 0\n    };\n\n    return optit;\n}\n\navs_coap_option_iterator_t *\n_avs_coap_optit_next(avs_coap_option_iterator_t *optit) {\n    assert(!_avs_coap_optit_end(optit));\n    const avs_coap_option_t *opt = _avs_coap_optit_current(optit);\n    optit->prev_opt_number += _avs_coap_option_delta(opt);\n    optit->curr_opt =\n            (uint8_t *) optit->curr_opt + _avs_coap_option_sizeof(opt);\n    return optit;\n}\n\nstatic size_t optit_offset(const avs_coap_option_iterator_t *optit) {\n    const uint8_t *curr = (const uint8_t *) optit->curr_opt;\n    const uint8_t *begin = (const uint8_t *) optit->opts->begin;\n    assert(curr >= begin);\n\n    return (size_t) (curr - begin);\n}\n\n/*\n * Moves option pointed-to by @p src over @p dst . Both iterators must point\n * to options within the same @ref avs_coap_options_t object, additionally\n * @p src must immediately follow @p dst .\n *\n * @returns Number of bytes occupied by reserialized @p src .\n */\nstatic size_t move_option_back(avs_coap_option_iterator_t *dst,\n                               const avs_coap_option_iterator_t *src) {\n    const avs_coap_option_t *src_opt = _avs_coap_optit_current(src);\n    const size_t src_offset = optit_offset(src);\n    const size_t src_sizeof = _avs_coap_option_sizeof(src_opt);\n    const uint32_t src_number = _avs_coap_optit_number(src);\n    const uint32_t src_length = _avs_coap_option_content_length(src_opt);\n    const uint8_t *src_content = _avs_coap_option_value(src_opt);\n\n    avs_coap_option_t *dst_opt = _avs_coap_optit_current(dst);\n    const size_t dst_offset = optit_offset(dst);\n    const size_t dst_sizeof = _avs_coap_option_sizeof(dst_opt);\n\n    AVS_ASSERT(src->opts == dst->opts,\n               \"this function assumes both src and dst point to the same \"\n               \"options object\");\n    AVS_ASSERT(dst_offset + dst_sizeof == src_offset,\n               \"this function assumes src immediately follows dst\");\n\n    const size_t new_delta = src_number - dst->prev_opt_number;\n    const size_t new_sizeof =\n            _avs_coap_get_opt_header_size(new_delta, src_length);\n\n    AVS_ASSERT(new_sizeof <= src_sizeof + dst_sizeof,\n               \"moving the option makes its header grow too large to avoid \"\n               \"overwriting its payload\");\n    (void) dst_offset;\n    (void) new_sizeof;\n    (void) src_offset;\n\n    assert(new_delta < UINT16_MAX);\n    assert(src_length < UINT16_MAX);\n\n    return _avs_coap_option_serialize((uint8_t *) dst_opt,\n                                      src_sizeof + dst_sizeof,\n                                      (uint16_t) new_delta,\n                                      src_content,\n                                      (uint16_t) src_length);\n}\n\navs_coap_option_iterator_t *\n_avs_coap_optit_erase(avs_coap_option_iterator_t *optit) {\n    assert(optit);\n    assert(!_avs_coap_optit_end(optit));\n\n    /*\n     *                                                 rest_begin\n     *                                                      |\n     *                                |<--- next_sizeof --->|<- rest_sizeof ...\n     *                                |                     v\n     * -----+------------+------------+---------------------+------------------\n     *      |  prev_opt  | erased_opt |       next_opt      |\n     *  ... |- - - - - - |- - - - - - | - - - - - - - - - - | rest ...\n     *      | hdr | data | hdr | data | hdr |      data     |\n     * -----+------------+------------+---------------------+------------------\n     *                                |                     |\n     * [1]               .------------'             .-------'\n     *                   v                          v\n     * -----+------------+--------------------------+-------+------------------\n     *      |  prev_opt  |         moved_opt        |       |\n     *  ... |- - - - - - | - - - - - - - - - - - - -|       | rest ...\n     *      | hdr | data |  hdr'  |       data      |       |\n     * -----+------------+--------------------------+-------+-----------------\n     *                                                      |\n     * [2]                                          .-------'\n     *                                              v\n     * -----+------------+--------------------------+-----------------\n     *      |  prev_opt  |         moved_opt        |\n     *  ... |- - - - - - | - - - - - - - - - - - - -| rest ...\n     *      | hdr | data |  hdr'  |      data       |\n     * -----+------------+--------------------------+-----------------\n     *                   |                          |\n     *                   |<----- moved_sizeof ----->|\n     *                   |     (>= next_sizeof)     |\n     *\n     * erased_opt needs to be removed. Unfortunately, we can't achieve that\n     * with a simple memmove(), because option number delta in next_opt may\n     * need to be updated.\n     *\n     * To achieve that, we serialize next_opt again //over// erased_opt first\n     * [1] and only then do memmove() on all options that follow [2].\n     *\n     * Why doesn't writing over erased_opt overwrite next_opt data?\n     *\n     * - If the erased_opt does not affect next_opt option delta (i.e.\n     *   erased_opt option delta == 0), next_opt header does not get modified\n     *   so everything degrades to plain memmove().\n     *\n     * - The problematic case is when erased_opt option delta > 0. That means\n     *   next_opt' may be larger than next_opt. The worst possible scenario is\n     *   when the option is empty - because otherwise we have more room for\n     *   next_opt. So let's assume the option consist only of a 1-byte header\n     *   and possibly extended option delta field, which, according to the RFC\n     *   can have at most 2 bytes.\n     *\n     *   - If erased_opt option delta < 13, there is no extended delta field\n     *     in the option header, so erased_opt has exactly 1 byte. Increasing\n     *     next_opt option delta by 13 can only grow its header by 1 byte, so\n     *     we're fine because we get that byte from erased_opt.\n     *\n     *   - If erased_opt option delta is in [13, 13+255] range, extended delta\n     *     field has 1 byte, and the entire size of erased_opt header is 2\n     *     bytes. Increasing next_opt option delta in this case can grow its\n     *     header by at most 2 bytes (when erased_opt.delta == 13+255,\n     *     next_opt.delta == 1, next_opt'.delta becomes 13+255+1) - so again,\n     *     we're fine.\n     *\n     *   - If erased_opt option delta is larger than 13+255, extended delta\n     *     field has 2 bytes and sizeof(erased_opt) == 3. next_opt may only\n     *     grow by at most 2 bytes (if there was no extended delta field, and\n     *     it grows to max possible size of 2 bytes). We have more than enough\n     *     room to spare.\n     */\n\n    avs_coap_option_t *erased_opt = _avs_coap_optit_current(optit);\n    const size_t erased_sizeof = _avs_coap_option_sizeof(erased_opt);\n    const size_t erased_offset = optit_offset(optit);\n\n    avs_coap_option_iterator_t next_optit = *optit;\n    _avs_coap_optit_next(&next_optit);\n    if (_avs_coap_optit_end(&next_optit)) {\n        // no next option - just move the end pointer\n        optit->opts->size = erased_offset;\n        return optit;\n    }\n\n    const avs_coap_option_t *next_opt = _avs_coap_optit_current(&next_optit);\n    const size_t next_sizeof = _avs_coap_option_sizeof(next_opt);\n\n    avs_coap_option_iterator_t rest_optit = next_optit;\n    _avs_coap_optit_next(&rest_optit);\n\n    const uint8_t *rest_begin = (const uint8_t *) rest_optit.curr_opt;\n    const size_t rest_offset = optit_offset(&rest_optit);\n    const size_t rest_sizeof = optit->opts->size - rest_offset;\n\n    // [1] Reserialize next_opt over erased_opt\n    const size_t moved_sizeof = move_option_back(optit, &next_optit);\n    assert(moved_sizeof > 0);\n    uint8_t *moved_end = (uint8_t *) erased_opt + moved_sizeof;\n\n    // [2] memmove() all options past next_opt\n    memmove(moved_end, rest_begin, rest_sizeof);\n\n    optit->opts->size -= (erased_sizeof + next_sizeof - moved_sizeof);\n    return optit;\n}\n\nbool _avs_coap_optit_end(const avs_coap_option_iterator_t *optit) {\n    return optit_offset(optit) >= optit->opts->size\n           || *(const uint8_t *) optit->curr_opt == AVS_COAP_PAYLOAD_MARKER;\n}\n\nuint32_t _avs_coap_optit_number(const avs_coap_option_iterator_t *optit) {\n    return optit->prev_opt_number\n           + _avs_coap_option_delta(_avs_coap_optit_current(optit));\n}\n"
  },
  {
    "path": "deps/avs_coap/src/options/avs_coap_iterator.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef AVS_COAP_SRC_UDP_OLD_MSG_H\n#define AVS_COAP_SRC_UDP_OLD_MSG_H\n\n#include <assert.h>\n#include <stdint.h>\n\n#include <avsystem/commons/avs_defs.h>\n\n#include <avsystem/coap/option.h>\n\n#include \"options/avs_coap_option.h\"\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\n/**\n * @param opts Option set to iterate over.\n * @returns An CoAP Option iterator object.\n */\navs_coap_option_iterator_t _avs_coap_optit_begin(avs_coap_options_t *opts);\n\n/**\n * Advances the @p optit iterator to the next CoAP Option.\n *\n * @param optit CoAP Option iterator to operate on.\n * @returns @p optit.\n */\navs_coap_option_iterator_t *\n_avs_coap_optit_next(avs_coap_option_iterator_t *optit);\n\n/**\n * Erases the option @p optit is currently pointing to and updates it so that\n * it points to the following option.\n *\n * @returns @p optit.\n */\navs_coap_option_iterator_t *\n_avs_coap_optit_erase(avs_coap_option_iterator_t *optit);\n\n/**\n * Checks if the @p optit points to the area after CoAP options list.\n *\n * @param optit Iterator to check.\n * @returns true if there are no more Options to iterate over (i.e. the iterator\n *          is invalidated), false if it points to a valid Option.\n */\nbool _avs_coap_optit_end(const avs_coap_option_iterator_t *optit);\n\n/**\n * @param optit Iterator to operate on.\n * @returns Number of the option currently pointed to by @p optit\n */\nuint32_t _avs_coap_optit_number(const avs_coap_option_iterator_t *optit);\n\nstatic inline avs_coap_option_t *\n_avs_coap_optit_current(const avs_coap_option_iterator_t *optit) {\n    assert(optit);\n    assert(!_avs_coap_optit_end(optit));\n\n    return (avs_coap_option_t *) optit->curr_opt;\n}\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif // AVS_COAP_SRC_UDP_OLD_MSG_H\n"
  },
  {
    "path": "deps/avs_coap/src/options/avs_coap_option.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <avs_coap_init.h>\n\n#include <assert.h>\n#include <inttypes.h>\n#include <stdio.h>\n#include <string.h>\n\n#include <avsystem/commons/avs_utils.h>\n\n#include <avsystem/coap/option.h>\n\n#include \"options/avs_coap_option.h\"\n\n#define MODULE_NAME coap\n#include <avs_coap_x_log_config.h>\n\nVISIBILITY_SOURCE_BEGIN\n\nstatic inline size_t get_ext_field_size(uint8_t base_value) {\n    assert(base_value < _AVS_COAP_EXT_RESERVED);\n\n    switch (base_value) {\n    case _AVS_COAP_EXT_U8:\n        return sizeof(uint8_t);\n    case _AVS_COAP_EXT_U16:\n        return sizeof(uint16_t);\n    default:\n        return 0;\n    }\n}\n\nstatic inline uint32_t decode_ext_value(uint8_t base_value,\n                                        const uint8_t *ext_value_ptr) {\n    assert(base_value < _AVS_COAP_EXT_RESERVED);\n\n    switch (base_value) {\n    case _AVS_COAP_EXT_U8:\n        return (uint32_t) * (const uint8_t *) ext_value_ptr\n               + _AVS_COAP_EXT_U8_BASE;\n    case _AVS_COAP_EXT_U16:\n        return (uint32_t) extract_u16(ext_value_ptr) + _AVS_COAP_EXT_U16_BASE;\n    default:\n        return base_value;\n    }\n}\n\nstatic inline bool ext_value_overflows(uint8_t base_value,\n                                       const uint8_t *ext_value_ptr) {\n    return base_value == _AVS_COAP_EXT_U16\n           && extract_u16(ext_value_ptr) > UINT16_MAX - _AVS_COAP_EXT_U16_BASE;\n}\n\nstatic inline const uint8_t *ext_delta_ptr(const avs_coap_option_t *opt) {\n    return opt->content;\n}\n\nstatic inline const uint8_t *ext_length_ptr(const avs_coap_option_t *opt) {\n    return opt->content\n           + get_ext_field_size(_avs_coap_option_get_short_delta(opt));\n}\n\nconst uint8_t *_avs_coap_option_value(const avs_coap_option_t *opt) {\n    return ext_length_ptr(opt)\n           + get_ext_field_size(_avs_coap_option_get_short_length(opt));\n}\n\nint _avs_coap_option_u16_value(const avs_coap_option_t *opt,\n                               uint16_t *out_value) {\n    const uint8_t *value_data = _avs_coap_option_value(opt);\n    uint32_t length = _avs_coap_option_content_length(opt);\n    if (length > sizeof(*out_value)) {\n        return -1;\n    }\n    *out_value = 0;\n    for (size_t i = 0; i < length; ++i) {\n        *out_value = (uint16_t) (*out_value << 8);\n        *out_value = (uint16_t) (*out_value | value_data[i]);\n    }\n    return 0;\n}\n\nint _avs_coap_option_u32_value(const avs_coap_option_t *opt,\n                               uint32_t *out_value) {\n    const uint8_t *value_data = _avs_coap_option_value(opt);\n    uint32_t length = _avs_coap_option_content_length(opt);\n    if (length > sizeof(*out_value)) {\n        return -1;\n    }\n    *out_value = 0;\n    for (size_t i = 0; i < length; ++i) {\n        *out_value = (uint32_t) (*out_value << 8);\n        *out_value = (uint32_t) (*out_value | value_data[i]);\n    }\n    return 0;\n}\n\nint _avs_coap_option_string_value(const avs_coap_option_t *opt,\n                                  size_t *out_option_size,\n                                  char *buffer,\n                                  size_t buffer_size) {\n    size_t str_length = _avs_coap_option_content_length(opt);\n    *out_option_size = str_length + 1;\n    if (buffer_size < *out_option_size) {\n        return -1;\n    }\n    memcpy(buffer, _avs_coap_option_value(opt), str_length);\n    buffer[str_length] = '\\0';\n    return 0;\n}\n\nint _avs_coap_option_block_seq_number(const avs_coap_option_t *opt,\n                                      uint32_t *out_seq_num) {\n    uint32_t value;\n    if (_avs_coap_option_u32_value(opt, &value) || value >= (1 << 24)) {\n        return -1;\n    }\n\n    *out_seq_num = (value >> 4);\n    return 0;\n}\n\nint _avs_coap_option_block_has_more(const avs_coap_option_t *opt,\n                                    bool *out_has_more) {\n    uint32_t value;\n    if (_avs_coap_option_u32_value(opt, &value) || value >= (1 << 24)) {\n        return -1;\n    }\n\n    *out_has_more = !!(value & 0x08);\n    return 0;\n}\n\nint _avs_coap_option_block_size(const avs_coap_option_t *opt,\n                                uint16_t *out_size,\n                                bool *is_bert) {\n    assert(out_size);\n    assert(is_bert);\n    uint32_t value;\n    if (_avs_coap_option_u32_value(opt, &value) || value >= (1 << 24)) {\n        return -1;\n    }\n    uint8_t size_exponent = value & 0x07;\n    *is_bert = (size_exponent == AVS_COAP_OPT_BERT_SZX);\n\n    if (*is_bert) {\n        // From RFC8323:\n        // \"In descriptive usage, a BERT Option is interpreted in the same way\n        //  as the equivalent Option with SZX == 6, except that the payload is\n        //  also allowed to contain multiple blocks.\"\n        size_exponent = AVS_COAP_OPT_BLOCK_MAX_SZX;\n    }\n\n    *out_size = (uint16_t) (1 << (size_exponent + 4));\n    AVS_ASSERT(_avs_coap_is_valid_block_size(*out_size),\n               \"bug in out_size calculations\");\n    return 0;\n}\n\nuint32_t _avs_coap_option_delta(const avs_coap_option_t *opt) {\n    uint32_t delta = decode_ext_value(_avs_coap_option_get_short_delta(opt),\n                                      ext_delta_ptr(opt));\n    assert(delta <= UINT16_MAX + _AVS_COAP_EXT_U16_BASE);\n    return delta;\n}\n\nuint32_t _avs_coap_option_content_length(const avs_coap_option_t *opt) {\n    uint32_t length = decode_ext_value(_avs_coap_option_get_short_length(opt),\n                                       ext_length_ptr(opt));\n    assert(length <= UINT16_MAX + _AVS_COAP_EXT_U16_BASE);\n    return length;\n}\n\nstatic inline bool is_delta_valid(const avs_coap_option_t *opt,\n                                  size_t max_opt_bytes) {\n    uint8_t short_delta = _avs_coap_option_get_short_delta(opt);\n    if (short_delta == _AVS_COAP_EXT_RESERVED) {\n        return false;\n    }\n\n    size_t required_bytes = 1 + get_ext_field_size(short_delta);\n    return required_bytes <= max_opt_bytes\n           && !ext_value_overflows(short_delta, ext_delta_ptr(opt));\n}\n\nstatic inline bool is_length_valid(const avs_coap_option_t *opt,\n                                   size_t max_opt_bytes) {\n    uint8_t short_length = _avs_coap_option_get_short_length(opt);\n    if (short_length == _AVS_COAP_EXT_RESERVED) {\n        return false;\n    }\n\n    uint8_t short_delta = _avs_coap_option_get_short_delta(opt);\n    size_t required_bytes = 1 + get_ext_field_size(short_delta)\n                            + get_ext_field_size(short_length);\n    return required_bytes <= max_opt_bytes\n           && !ext_value_overflows(short_length, ext_length_ptr(opt));\n}\n\nbool _avs_coap_option_is_valid(const avs_coap_option_t *opt,\n                               size_t max_opt_bytes) {\n    if (max_opt_bytes == 0 || !is_delta_valid(opt, max_opt_bytes)\n            || !is_length_valid(opt, max_opt_bytes)) {\n        return false;\n    }\n\n    uint32_t length = (uint32_t) _avs_coap_option_sizeof(opt);\n    return (uintptr_t) opt->content + length >= (uintptr_t) opt->content\n           && length <= max_opt_bytes;\n}\n\nsize_t _avs_coap_option_sizeof(const avs_coap_option_t *opt) {\n    const uint8_t *endptr =\n            _avs_coap_option_value(opt) + _avs_coap_option_content_length(opt);\n\n    assert((const uint8_t *) opt < endptr);\n    return (size_t) (endptr - (const uint8_t *) opt);\n}\n\nstatic inline size_t encode_ext_value(uint8_t *ptr, uint16_t ext_value) {\n    if (ext_value >= _AVS_COAP_EXT_U16_BASE) {\n        uint16_t value_net_byte_order = avs_convert_be16(\n                (uint16_t) (ext_value - _AVS_COAP_EXT_U16_BASE));\n        avs_unaligned_put(ptr, value_net_byte_order);\n        return sizeof(value_net_byte_order);\n    } else if (ext_value >= _AVS_COAP_EXT_U8_BASE) {\n        *ptr = (uint8_t) (ext_value - _AVS_COAP_EXT_U8_BASE);\n        return 1;\n    }\n\n    return 0;\n}\n\nstatic inline size_t\nopt_write_header(uint8_t *ptr, uint16_t opt_number_delta, uint16_t opt_length) {\n    avs_coap_option_t *opt = (avs_coap_option_t *) ptr;\n    ptr = opt->content;\n\n    if (opt_number_delta >= _AVS_COAP_EXT_U16_BASE) {\n        _avs_coap_option_set_short_delta(opt, _AVS_COAP_EXT_U16);\n    } else if (opt_number_delta >= _AVS_COAP_EXT_U8_BASE) {\n        _avs_coap_option_set_short_delta(opt, _AVS_COAP_EXT_U8);\n    } else {\n        _avs_coap_option_set_short_delta(opt,\n                                         (uint8_t) (opt_number_delta & 0xF));\n    }\n\n    if (opt_length >= _AVS_COAP_EXT_U16_BASE) {\n        _avs_coap_option_set_short_length(opt, _AVS_COAP_EXT_U16);\n    } else if (opt_length >= _AVS_COAP_EXT_U8_BASE) {\n        _avs_coap_option_set_short_length(opt, _AVS_COAP_EXT_U8);\n    } else {\n        _avs_coap_option_set_short_length(opt, (uint8_t) (opt_length & 0xF));\n    }\n\n    ptr += encode_ext_value(ptr, opt_number_delta);\n    ptr += encode_ext_value(ptr, opt_length);\n\n    return (size_t) (ptr - (uint8_t *) opt);\n}\n\nstatic inline bool memory_regions_overlap(const void *a,\n                                          size_t a_size,\n                                          const void *b,\n                                          size_t b_size) {\n    /*\n     * Source: https://stackoverflow.com/a/3269471/2339636\n     *\n     * If ranges [x1, x2) and [y1, y2) overlap, there exists N such that\n     *\n     *     x1 <= N < x2 && y1 <= N < y2\n     */\n\n    const void *a_end = (const char *) a + a_size;\n    const void *b_end = (const char *) b + b_size;\n    return a < b_end && b < a_end;\n}\n\nsize_t _avs_coap_option_serialize(uint8_t *buffer,\n                                  size_t buffer_size,\n                                  size_t opt_number_delta,\n                                  const void *opt_data,\n                                  size_t opt_data_size) {\n    size_t opt_header_size =\n            _avs_coap_get_opt_header_size(opt_number_delta, opt_data_size);\n\n    if (opt_header_size + opt_data_size > buffer_size) {\n        LOG(ERROR, _(\"not enough space to serialize option\"));\n        return 0;\n    }\n\n    assert(opt_number_delta <= UINT16_MAX);\n    assert(opt_data_size <= UINT16_MAX);\n    size_t header_bytes_written =\n            opt_write_header(buffer, (uint16_t) opt_number_delta,\n                             (uint16_t) opt_data_size);\n\n    assert(header_bytes_written == opt_header_size);\n    assert(header_bytes_written + opt_data_size <= buffer_size);\n\n    /*\n     * NOTE: buffer and opt_data  regions may overlap. This allows for resizing\n     * options in-place, as long as opt_write_header call above does not touch\n     * option data.\n     */\n    assert(!memory_regions_overlap(buffer, header_bytes_written, opt_data,\n                                   opt_data_size));\n\n    memmove(buffer + header_bytes_written,\n            opt_data ? opt_data : \"\",\n            opt_data_size);\n    return header_bytes_written + opt_data_size;\n}\n\nsize_t _avs_coap_get_opt_header_size(size_t opt_number_delta,\n                                     size_t opt_data_size) {\n    assert(opt_number_delta <= UINT16_MAX);\n    assert(opt_data_size <= UINT16_MAX);\n\n    size_t header_size = 1;\n\n    if (opt_number_delta >= _AVS_COAP_EXT_U16_BASE) {\n        header_size += 2;\n    } else if (opt_number_delta >= _AVS_COAP_EXT_U8_BASE) {\n        header_size += 1;\n    }\n\n    if (opt_data_size >= _AVS_COAP_EXT_U16_BASE) {\n        header_size += 2;\n    } else if (opt_data_size >= _AVS_COAP_EXT_U8_BASE) {\n        header_size += 1;\n    }\n\n    return header_size;\n}\n\nconst char *\n_avs_coap_option_block_string(avs_coap_option_block_string_buf_t *buf,\n                              const avs_coap_option_block_t *block) {\n    assert(buf);\n    assert(block);\n    int result = avs_simple_snprintf(buf->str, sizeof(buf->str),\n                                     \"BLOCK%d(seq_num %\" PRIu32\n                                     \", size %\" PRIu16 \", more %d)\",\n                                     block->type == AVS_COAP_BLOCK1 ? 1 : 2,\n                                     block->seq_num, block->size,\n                                     (int) block->has_more);\n    assert(result >= 0);\n    (void) result;\n    return buf->str;\n}\n"
  },
  {
    "path": "deps/avs_coap/src/options/avs_coap_option.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef AVS_COAP_SRC_UDP_OPTIONS_OPTION_H\n#define AVS_COAP_SRC_UDP_OPTIONS_OPTION_H\n\n#include <inttypes.h>\n#include <stdbool.h>\n#include <stddef.h>\n#include <stdint.h>\n#include <stdio.h>\n#include <string.h>\n\n#include <avsystem/coap/option.h>\n\n#include \"avs_coap_parse_utils.h\"\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\n/**\n * From RFC8323:\n * \"BERT Option:\n *  A Block1 or Block2 option that includes an SZX (block size)\n *  value of 7.\"\n */\n#define AVS_COAP_OPT_BERT_SZX 7\n\n#define AVS_COAP_OPT_BLOCK_MAX_SZX 6\n\n/**\n * Maximum size, in bytes, required for encoding a BLOCK1/BLOCK2 option.\n *\n * Technically, CoAP options may contain up to 2 bytes of extended option number\n * and up to 2 bytes of extended length. This should never be required for BLOCK\n * options. Why? 2-byte extended values are required for interpreting values\n * >= 269. BLOCK uses 23/27 option numbers and allows up to 3 content bytes.\n * Therefore correct BLOCK options will use at most 1 byte for extended number\n * (since wrapping is not allowed) and will never use extended length field.\n */\n#define AVS_COAP_OPT_BLOCK_MAX_SIZE \\\n    (1    /* option header   */     \\\n     + 1  /* extended number */     \\\n     + 3) /* block option value */\n\n/**\n * CoAP Observe option has number 6, so it never requires an extended nuber\n * field. Its content is up to 3 bytes, so extended length is not required\n * either.\n */\n#define AVS_COAP_OPT_OBSERVE_MAX_SIZE \\\n    (1    /* option header */         \\\n     + 3) /* option value */\n\n/**\n * @returns CoAP option number appropriate for BLOCK transfer of given @p type .\n */\nstatic inline uint16_t\n_avs_coap_option_num_from_block_type(avs_coap_option_block_type_t type) {\n    return type == AVS_COAP_BLOCK1 ? AVS_COAP_OPTION_BLOCK1\n                                   : AVS_COAP_OPTION_BLOCK2;\n}\n\n/**\n * @returns true if @p size is an acceptable CoAP BLOCK size.\n */\nstatic inline bool _avs_coap_is_valid_block_size(uint16_t size) {\n    return avs_is_power_of_2(size) && size <= AVS_COAP_BLOCK_MAX_SIZE\n           && size >= AVS_COAP_BLOCK_MIN_SIZE;\n}\n\n/**\n * Magic value defined in RFC7252, used internally when constructing/parsing\n * CoAP packets.\n */\n#define AVS_COAP_PAYLOAD_MARKER ((uint8_t) { 0xFF })\n\nAVS_STATIC_ASSERT(sizeof(AVS_COAP_PAYLOAD_MARKER) == 1,\n                  payload_marker_must_be_1_byte_long);\n\n/** Serialized CoAP option. */\ntypedef struct avs_coap_option {\n    /**\n     * Note: when working with CoAP options do not access these fields directly,\n     * since they may not represent the actual encoded values. Use\n     * @ref _avs_coap_option_value, @ref _avs_coap_option_delta and\n     * @ref _avs_coap_option_content_length instead.\n     */\n    uint8_t delta_length;\n    uint8_t content[];\n} avs_coap_option_t;\n\n/**\n * @param opt Option to operate on.\n *\n * @returns Pointer to the start of the option content.\n */\nconst uint8_t *_avs_coap_option_value(const avs_coap_option_t *opt);\n\n/**\n * Retrieves a 16-bit integer option value.\n *\n * @param[in]  opt            CoAP option to retrieve value from.\n * @param[out] out_value      Pointer to variable to store the option value in.\n *\n * @returns 0 on success, a negative value if @p out_value is too small\n *          to hold the integer value of @p opt .\n */\nint _avs_coap_option_u16_value(const avs_coap_option_t *opt,\n                               uint16_t *out_value);\n\n/**\n * Retrieves a 32-bit integer option value.\n *\n * @param[in]  opt            CoAP option to retrieve value from.\n * @param[out] out_value      Pointer to variable to store the option value in.\n *\n * @returns 0 on success, a negative value if @p out_value is too small\n *          to hold the integer value of @p opt .\n */\nint _avs_coap_option_u32_value(const avs_coap_option_t *opt,\n                               uint32_t *out_value);\n\n/**\n * Retrieves an CoAP option value as a zero-terminated string.\n *\n * @param[in]  opt             Option to retrieve value from.\n * @param[out] out_option_size Size of the option value, including terminating\n *                             nullbyte. After successful call, it's equal to\n *                             the number of bytes written to @p buffer.\n * @param[out] buffer          Buffer to store the retrieved value in.\n * @param[in]  buffer_size     Number of bytes available in @p buffer .\n *\n * @returns @li 0 on success, in which case @p out_bytes_read contains\n *              the number of bytes successfully written to @p buffer .\n *              String written to @p buffer is guaranteed to be zero-terminated.\n *          @li A negative value if @p buffer is too small to hold the option\n *              value. In such case, @p buffer contents are not modified and\n *              @p out_bytes_read is not set.\n */\nint _avs_coap_option_string_value(const avs_coap_option_t *opt,\n                                  size_t *out_option_size,\n                                  char *buffer,\n                                  size_t buffer_size);\n\n/**\n * Retrieves a BLOCK sequence number from a CoAP option.\n *\n * Note: the function does not check whether @p opt is indeed a BLOCK option.\n * Calling this function on non-BLOCK options causes undefined behavior.\n *\n * @param[in]  opt         CoAP option to read sequence number from.\n * @param[out] out_seq_num Read BLOCK sequence number.\n *\n * @returns @li 0 on success, in which case @p out_seq_num is set,\n *          @li -1 if the option value is too big to be a correct BLOCK option.\n */\nint _avs_coap_option_block_seq_number(const avs_coap_option_t *opt,\n                                      uint32_t *out_seq_num);\n\n/**\n * Retrieves a \"More\" marker from a CoAP BLOCK option.\n *\n * Note: the function does not check whether @p opt is indeed a BLOCK option.\n * Calling this function on non-BLOCK options causes undefined behavior.\n *\n * @param[in]  opt          CoAP option to read sequence number from.\n * @param[out] out_has_more Value of the \"More\" flag of a BLOCK option.\n *\n * @returns @li 0 on success, in which case @p out_has_more is set,\n *          @li -1 if the option value is too big to be a correct BLOCK option.\n */\nint _avs_coap_option_block_has_more(const avs_coap_option_t *opt,\n                                    bool *out_has_more);\n\n/**\n * Retrieves a block size from a CoAP BLOCK option.\n *\n * Note: the function does not check whether @p opt is indeed a BLOCK option.\n * Calling this function on non-BLOCK options causes undefined behavior.\n *\n * @param[in]  opt         CoAP option to read block size from.\n * @param[out] out_size    Block size, in bytes, encoded in the option. MUST NOT\n *                         be NULL.\n * @param[out] out_is_bert If true, then BLOCK option is a BERT (allowed only in\n *                         CoAP/TCP). MUST NOT be NULL.\n *\n * @returns @li 0 on success, in which case @p out_has_more is set,\n *          @li -1 if the option value is too big to be a correct BLOCK option\n *              or if the option is malformed.\n */\nint _avs_coap_option_block_size(const avs_coap_option_t *opt,\n                                uint16_t *out_size,\n                                bool *out_is_bert);\n\n/**\n * @param opt Option to operate on.\n *\n * @returns Option Delta (as per RFC7252 section 3.1).\n */\nuint32_t _avs_coap_option_delta(const avs_coap_option_t *opt);\n\n/**\n * @param opt Option to operate on.\n *\n * @returns Length of the option content, in bytes.\n */\nuint32_t _avs_coap_option_content_length(const avs_coap_option_t *opt);\n\n/**\n * @param opt           Option to operate on.\n * @param max_opt_bytes Number of valid bytes available for the @p opt.\n *                      Used to prevent out-of-bounds buffer access.\n *\n * @returns True if the option has a valid format, false otherwise.\n */\nbool _avs_coap_option_is_valid(const avs_coap_option_t *opt,\n                               size_t max_opt_bytes);\n\n/**\n * @param opt Option to operate on.\n *\n * @returns Total size of the option including content, in bytes.\n */\nsize_t _avs_coap_option_sizeof(const avs_coap_option_t *opt);\n\n/**\n * @param buffer           Buffer to serialize the option to.\n * @param buffer_size      Number of bytes available in @p buffer.\n * @param opt_number_delta Option number delta.\n * @param opt_data         Option content bytes.\n * @param opt_data_size    Number of content bytes to write.\n *\n * @returns Number of bytes written to @p buffer on success, 0 on error.\n *\n * NOTE: it is only safe to use this function to overwrite an option with\n * itself if the new @p opt_number_delta is no larger that previous one.\n */\nsize_t _avs_coap_option_serialize(uint8_t *buffer,\n                                  size_t buffer_size,\n                                  size_t opt_number_delta,\n                                  const void *opt_data,\n                                  size_t opt_data_size);\n\n#define _AVS_COAP_EXT_U8 13\n#define _AVS_COAP_EXT_U16 14\n#define _AVS_COAP_EXT_RESERVED 15\n\n#define _AVS_COAP_EXT_U8_BASE ((uint32_t) 13)\n#define _AVS_COAP_EXT_U16_BASE ((uint32_t) 269)\n\n#define _AVS_COAP_OPTION_DELTA_MASK 0xF0\n#define _AVS_COAP_OPTION_DELTA_SHIFT 4\n#define _AVS_COAP_OPTION_LENGTH_MASK 0x0F\n#define _AVS_COAP_OPTION_LENGTH_SHIFT 0\n\nstatic inline uint8_t\n_avs_coap_option_get_short_delta(const avs_coap_option_t *opt) {\n    return _AVS_FIELD_GET(opt->delta_length,\n                          _AVS_COAP_OPTION_DELTA_MASK,\n                          _AVS_COAP_OPTION_DELTA_SHIFT);\n}\n\nstatic inline void _avs_coap_option_set_short_delta(avs_coap_option_t *opt,\n                                                    uint8_t delta) {\n    assert(delta <= _AVS_COAP_EXT_RESERVED);\n    _AVS_FIELD_SET(opt->delta_length, _AVS_COAP_OPTION_DELTA_MASK,\n                   _AVS_COAP_OPTION_DELTA_SHIFT, delta);\n}\n\nstatic inline uint8_t\n_avs_coap_option_get_short_length(const avs_coap_option_t *opt) {\n    return _AVS_FIELD_GET(opt->delta_length,\n                          _AVS_COAP_OPTION_LENGTH_MASK,\n                          _AVS_COAP_OPTION_LENGTH_SHIFT);\n}\n\nstatic inline void _avs_coap_option_set_short_length(avs_coap_option_t *opt,\n                                                     uint8_t length) {\n    assert(length <= _AVS_COAP_EXT_RESERVED);\n    _AVS_FIELD_SET(opt->delta_length, _AVS_COAP_OPTION_LENGTH_MASK,\n                   _AVS_COAP_OPTION_LENGTH_SHIFT, length);\n}\n\nsize_t _avs_coap_get_opt_header_size(size_t opt_number_delta,\n                                     size_t opt_data_size);\n\ntypedef struct {\n    char str[48];\n} avs_coap_option_block_string_buf_t;\n\nAVS_STATIC_ASSERT(\n        sizeof(avs_coap_option_block_string_buf_t)\n                >= sizeof(\"BLOCK1(seq_num 4294967295, size 65535, more 1)\"),\n        avs_coap_option_block_string_buf_size_enough);\n\nconst char *\n_avs_coap_option_block_string(avs_coap_option_block_string_buf_t *buf,\n                              const avs_coap_option_block_t *block);\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif // AVS_COAP_SRC_UDP_OPTIONS_OPTION_H\n"
  },
  {
    "path": "deps/avs_coap/src/options/avs_coap_options.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <avs_coap_init.h>\n\n#include <string.h>\n\n#include <avsystem/commons/avs_utils.h>\n\n#include <avsystem/coap/code.h>\n#include <avsystem/coap/option.h>\n\n#include \"options/avs_coap_iterator.h\"\n#include \"options/avs_coap_option.h\"\n\n#define MODULE_NAME coap\n#include <avs_coap_x_log_config.h>\n\n#include \"options/avs_coap_options.h\"\n\n#define MAX_OBSERVE_OPTION_VALUE (0xFFFFFF)\n\nVISIBILITY_SOURCE_BEGIN\n\n#ifdef WITH_AVS_COAP_BLOCK\n\nstatic int fill_block_data(const avs_coap_option_t *block_opt,\n                           uint32_t opt_number,\n                           avs_coap_option_block_t *out_info) {\n    assert(opt_number == AVS_COAP_OPTION_BLOCK1\n           || opt_number == AVS_COAP_OPTION_BLOCK2);\n    out_info->type = (opt_number == AVS_COAP_OPTION_BLOCK1) ? AVS_COAP_BLOCK1\n                                                            : AVS_COAP_BLOCK2;\n\n    // RFC 7959, Table 1 defines BLOCK1/2 option length as 0-3 bytes\n    static const uint32_t MAX_BLOCK_DATA_SIZE = 3;\n\n    if (_avs_coap_option_content_length(block_opt) > MAX_BLOCK_DATA_SIZE\n            || _avs_coap_option_block_seq_number(block_opt, &out_info->seq_num)\n            || _avs_coap_option_block_has_more(block_opt, &out_info->has_more)\n            || _avs_coap_option_block_size(block_opt, &out_info->size,\n                                           &out_info->is_bert)) {\n        LOG(DEBUG, _(\"malformed BLOCK\") \"%d\" _(\" option\"),\n            opt_number == AVS_COAP_OPTION_BLOCK1 ? 1 : 2);\n        return -1;\n    }\n    return 0;\n}\n\nstatic bool is_block_option_content_valid(const avs_coap_option_t *block_opt,\n                                          uint32_t opt_number) {\n    // Attempt to parse the BLOCK1/BLOCK2 option. This operation will fail in\n    // case the option content is not well-formed.\n    avs_coap_option_block_t opt = {\n        .type = AVS_COAP_BLOCK1\n    };\n    return (fill_block_data(block_opt, opt_number, &opt) == 0);\n}\n\nstatic avs_error_t\nblock_type_from_code(uint8_t code, avs_coap_option_block_type_t *out_type) {\n    if (avs_coap_code_is_request(code)) {\n        *out_type = AVS_COAP_BLOCK1;\n        return AVS_OK;\n    } else if (avs_coap_code_is_response(code)) {\n        *out_type = AVS_COAP_BLOCK2;\n        return AVS_OK;\n    } else {\n        LOG(DEBUG, \"%s\" _(\" is neither a request nor response\"),\n            AVS_COAP_CODE_STRING(code));\n        return avs_errno(AVS_EINVAL);\n    }\n}\n\navs_error_t\n_avs_coap_options_get_block_by_code(const avs_coap_options_t *options,\n                                    uint8_t code,\n                                    avs_coap_option_block_t *out_block,\n                                    bool *out_has_block) {\n    avs_coap_option_block_type_t type;\n    avs_error_t err = block_type_from_code(code, &type);\n    if (avs_is_err(err)) {\n        return err;\n    }\n\n    int opts_result = avs_coap_options_get_block(options, type, out_block);\n    switch (opts_result) {\n    case 0:\n    case AVS_COAP_OPTION_MISSING:\n        *out_has_block = (opts_result == 0);\n        return AVS_OK;\n    default:\n        AVS_UNREACHABLE(\"malformed options got through packet validation\");\n        return _avs_coap_err(AVS_COAP_ERR_ASSERT_FAILED);\n    }\n}\n\n#endif // WITH_AVS_COAP_BLOCK\n\nbool _avs_coap_options_valid_until_payload_marker(\n        const avs_coap_options_t *opts,\n        size_t *out_actual_size,\n        bool *out_truncated,\n        bool *out_payload_marker_reached) {\n    if (out_truncated) {\n        *out_truncated = false;\n    }\n    if (out_payload_marker_reached) {\n        *out_payload_marker_reached = false;\n    }\n\n    if (opts->size > opts->capacity) {\n        LOG(DEBUG, _(\"unexpected size (\") \"%s\" _(\") > capacity (\") \"%s\" _(\")\"),\n            AVS_UINT64_AS_STRING(opts->size),\n            AVS_UINT64_AS_STRING(opts->capacity));\n        return false;\n    }\n\n    // non-repeatable critical options must not be present more than once\n    struct {\n        const uint16_t number;\n        bool found;\n    } non_repeatable_critical_options[] = {\n        // clang-format off\n        { AVS_COAP_OPTION_URI_HOST,       false },\n        { AVS_COAP_OPTION_IF_NONE_MATCH,  false },\n        { AVS_COAP_OPTION_URI_PORT,       false },\n        { AVS_COAP_OPTION_OSCORE,         false },\n        { AVS_COAP_OPTION_ACCEPT,         false },\n        { AVS_COAP_OPTION_BLOCK2,         false },\n        { AVS_COAP_OPTION_BLOCK1,         false },\n        { AVS_COAP_OPTION_PROXY_URI,      false },\n        { AVS_COAP_OPTION_PROXY_SCHEME,   false }\n        // clang-format on\n    };\n\n    avs_coap_option_iterator_t it =\n            _avs_coap_optit_begin((avs_coap_options_t *) (intptr_t) opts);\n\n    for (; !_avs_coap_optit_end(&it); _avs_coap_optit_next(&it)) {\n        assert(it.curr_opt >= opts->begin);\n\n        const uint8_t *opts_begin = (const uint8_t *) opts->begin;\n        size_t opt_offset =\n                (size_t) ((const uint8_t *) it.curr_opt - opts_begin);\n\n        assert(opts->size >= opt_offset);\n        size_t bytes_available = opts->size - opt_offset;\n\n        const avs_coap_option_t *opt = _avs_coap_optit_current(&it);\n        if (!_avs_coap_option_is_valid(opt, bytes_available)) {\n            LOG(DEBUG, _(\"malformed CoAP option at offset \") \"%u\",\n                (unsigned) ((const uint8_t *) it.curr_opt - opts_begin));\n            if (out_truncated) {\n                *out_truncated = true;\n            }\n            return false;\n        }\n\n        uint32_t opt_number = _avs_coap_optit_number(&it);\n        if (opt_number > UINT16_MAX) {\n            LOG(DEBUG,\n                _(\"invalid CoAP option number (\") \"%\" PRIu32 _(\" > 65535)\"),\n                opt_number);\n            return false;\n        }\n\n        for (size_t i = 0; i < AVS_ARRAY_SIZE(non_repeatable_critical_options);\n             ++i) {\n            AVS_ASSERT(_avs_coap_option_is_critical(\n                               non_repeatable_critical_options[i].number),\n                       \"every elective option can be repeated\");\n            if (non_repeatable_critical_options[i].number == opt_number) {\n                if (non_repeatable_critical_options[i].found) {\n                    LOG(DEBUG,\n                        _(\"duplicated non-repeatable critical CoAP \"\n                          \"option \") \"%\" PRIu32,\n                        opt_number);\n                    return false;\n                }\n                non_repeatable_critical_options[i].found = true;\n            }\n        }\n\n        switch (opt_number) {\n        case AVS_COAP_OPTION_BLOCK1:\n        case AVS_COAP_OPTION_BLOCK2:\n#ifdef WITH_AVS_COAP_BLOCK\n            if (!is_block_option_content_valid(opt, opt_number)) {\n                return false;\n            }\n            break;\n#else  // WITH_AVS_COAP_BLOCK\n            LOG(DEBUG, _(\"BLOCK option received, but BLOCKs are disabled\"));\n            return false;\n#endif // WITH_AVS_COAP_BLOCK\n        default:\n            break;\n        }\n    }\n\n    AVS_ASSERT(out_actual_size, \"use _avs_coap_options_valid instead\");\n    *out_actual_size = (size_t) ((const uint8_t *) it.curr_opt\n                                 - (const uint8_t *) it.opts->begin);\n    // If options parser didn't reach the end of buffer and the next byte\n    // is a payload marker, *out_payload_marker_reached is set. Otherwise,\n    // it's possible that more options will arrive in the next packet (if TCP\n    // is used).\n    if (out_payload_marker_reached) {\n        const bool all_bytes_parsed = (*out_actual_size == opts->capacity);\n        if (!all_bytes_parsed\n                && *(const uint8_t *) it.curr_opt == AVS_COAP_PAYLOAD_MARKER) {\n            *out_payload_marker_reached = true;\n        }\n    }\n\n    return true;\n}\n\nbool _avs_coap_options_valid(const avs_coap_options_t *opts) {\n    size_t actual_size;\n\n    if (!_avs_coap_options_valid_until_payload_marker(opts, &actual_size, NULL,\n                                                      NULL)) {\n        return false;\n    }\n\n    if (opts->size != actual_size) {\n        LOG(DEBUG,\n            _(\"size mismatch: declared \") \"%\" PRIu32 _(\", actual \") \"%\" PRIu32,\n            (uint32_t) opts->size, (uint32_t) actual_size);\n        return false;\n    }\n\n    return true;\n}\n\nvoid avs_coap_options_remove_by_number(avs_coap_options_t *opts,\n                                       uint16_t option_number) {\n    avs_coap_option_iterator_t optit = _avs_coap_optit_begin(opts);\n\n    while (!_avs_coap_optit_end(&optit)\n           && _avs_coap_optit_number(&optit) < option_number) {\n        _avs_coap_optit_next(&optit);\n    }\n\n    while (!_avs_coap_optit_end(&optit)\n           && _avs_coap_optit_number(&optit) == option_number) {\n        _avs_coap_optit_erase(&optit);\n    }\n}\n\navs_error_t avs_coap_options_set_content_format(avs_coap_options_t *opts,\n                                                uint16_t format) {\n    avs_coap_options_remove_by_number(opts, AVS_COAP_OPTION_CONTENT_FORMAT);\n\n    if (format == AVS_COAP_FORMAT_NONE) {\n        return AVS_OK;\n    }\n\n    return avs_coap_options_add_u16(opts, AVS_COAP_OPTION_CONTENT_FORMAT,\n                                    format);\n}\n\navs_error_t avs_coap_options_add_etag(avs_coap_options_t *opts,\n                                      const avs_coap_etag_t *etag) {\n    if (etag->size > AVS_COAP_MAX_ETAG_LENGTH) {\n        LOG(ERROR, _(\"invalid ETag with length >\") \"%d\" _(\" bytes\"),\n            AVS_COAP_MAX_ETAG_LENGTH);\n        return avs_errno(AVS_EINVAL);\n    }\n    return avs_coap_options_add_opaque(opts, AVS_COAP_OPTION_ETAG, etag->bytes,\n                                       etag->size);\n}\n\n#ifdef WITH_AVS_COAP_BLOCK\n\nstatic avs_error_t encode_block_size(uint16_t size,\n                                     uint8_t *out_size_exponent) {\n    switch (size) {\n    case 16:\n        *out_size_exponent = 0;\n        break;\n    case 32:\n        *out_size_exponent = 1;\n        break;\n    case 64:\n        *out_size_exponent = 2;\n        break;\n    case 128:\n        *out_size_exponent = 3;\n        break;\n    case 256:\n        *out_size_exponent = 4;\n        break;\n    case 512:\n        *out_size_exponent = 5;\n        break;\n    case 1024:\n        *out_size_exponent = 6;\n        break;\n    default:\n        LOG(ERROR,\n            _(\"invalid block size: \") \"%d\" _(\", expected power of 2 between 16 \"\n                                             \"and 1024 (inclusive)\"),\n            (int) size);\n        return avs_errno(AVS_EINVAL);\n    }\n\n    return AVS_OK;\n}\n\nstatic avs_error_t add_block_opt(avs_coap_options_t *opts,\n                                 uint16_t option_number,\n                                 uint32_t seq_number,\n                                 bool is_last_chunk,\n                                 uint16_t size,\n                                 bool is_bert) {\n    uint8_t size_exponent;\n    avs_error_t err = encode_block_size(size, &size_exponent);\n    if (avs_is_err(err)) {\n        return err;\n    }\n\n    // [TCP] If peer sent a BERT request for example, we have to respond with\n    // message with BERT option, to avoid size renegotiation, which may be\n    // confusing, because in CSM we said, that we support BERT.\n    if (is_bert) {\n        if (size_exponent == AVS_COAP_OPT_BLOCK_MAX_SZX) {\n            size_exponent = AVS_COAP_OPT_BERT_SZX;\n        } else {\n            LOG(ERROR,\n                _(\"unexpected size_exponent \") \"%d\" _(\n                        \" for option with BERT flag set, size should be 1024\"),\n                size_exponent);\n            return avs_errno(AVS_EINVAL);\n        }\n    }\n\n    AVS_STATIC_ASSERT(sizeof(int) >= sizeof(int32_t), int_type_too_small);\n    if (seq_number >= (1 << 20)) {\n        LOG(ERROR, _(\"block sequence number must be less than 2^20\"));\n        return avs_errno(AVS_ERANGE);\n    }\n\n    uint32_t value = ((seq_number & 0x000fffff) << 4)\n                     | ((uint32_t) is_last_chunk << 3)\n                     | (uint32_t) size_exponent;\n    return avs_coap_options_add_u32(opts, option_number, value);\n}\n\navs_error_t avs_coap_options_add_block(avs_coap_options_t *opts,\n                                       const avs_coap_option_block_t *block) {\n    return add_block_opt(opts,\n                         _avs_coap_option_num_from_block_type(block->type),\n                         block->seq_num, block->has_more, block->size,\n                         block->is_bert);\n}\n\n#endif // WITH_AVS_COAP_BLOCK\n\n#ifdef WITH_AVS_COAP_OBSERVE\n\navs_error_t avs_coap_options_add_observe(avs_coap_options_t *opts,\n                                         uint32_t value) {\n    value &= MAX_OBSERVE_OPTION_VALUE;\n    return avs_coap_options_add_u32(opts, AVS_COAP_OPTION_OBSERVE, value);\n}\n\nint avs_coap_options_get_observe(const avs_coap_options_t *opts,\n                                 uint32_t *value) {\n    int result = avs_coap_options_get_u32(opts, AVS_COAP_OPTION_OBSERVE, value);\n    if (!result && *value > MAX_OBSERVE_OPTION_VALUE) {\n        result = -1;\n    }\n    return result;\n}\n\n#endif // WITH_AVS_COAP_OBSERVE\n\n/*\n * Changing option delta field on a CoAP option may shorten its header\n * by a byte or two.\n */\nstatic size_t bytes_gained_by_reducing_delta(const avs_coap_option_t *opt,\n                                             uint16_t new_opt_delta) {\n    size_t content_length = _avs_coap_option_content_length(opt);\n\n    size_t old_opt_delta = _avs_coap_option_delta(opt);\n    assert(new_opt_delta <= old_opt_delta);\n    size_t old_hdr_size =\n            _avs_coap_get_opt_header_size(old_opt_delta, content_length);\n\n    size_t new_hdr_size =\n            _avs_coap_get_opt_header_size(new_opt_delta, content_length);\n    assert(old_hdr_size >= new_hdr_size);\n    return old_hdr_size - new_hdr_size;\n}\n\n/**\n * Will a new option with @p new_opt_number, which is @p new_opt_sizeof bytes\n * in size (including header) fit into options object associated with\n * @p insert_it when it would be inserted in the place @p insert_it currently\n * points to?\n */\nstatic bool new_option_fits(const avs_coap_option_iterator_t *insert_it,\n                            uint16_t new_opt_number,\n                            size_t new_opt_sizeof) {\n    size_t bytes_available = insert_it->opts->capacity - insert_it->opts->size;\n\n    if (!_avs_coap_optit_end(insert_it)) {\n        uint32_t delta = _avs_coap_optit_number(insert_it) - new_opt_number;\n        assert(delta <= UINT16_MAX);\n\n        const avs_coap_option_t *opt = _avs_coap_optit_current(insert_it);\n        bytes_available +=\n                bytes_gained_by_reducing_delta(opt, (uint16_t) delta);\n    }\n\n    return bytes_available >= new_opt_sizeof;\n}\n\n/**\n * Rewrite option delta field of the CoAP option currently pointed to by @p it\n * with @p new_delta, which MUST be no larger than current one.\n *\n * Size of the <c>it->opts</c> object is adjusted accordingly. @p it is still\n * valid after the function returns, and points to the redelta'd option.\n */\nstatic void update_option_delta_in_place(const avs_coap_option_iterator_t *it,\n                                         uint16_t new_delta) {\n    avs_coap_option_t *opt = _avs_coap_optit_current(it);\n\n    /* Only reducing option delta is supported */\n    assert(new_delta <= _avs_coap_option_delta(opt));\n\n    size_t opt_sizeof = _avs_coap_option_sizeof(opt);\n    const uint8_t *old_opt_end = (const uint8_t *) opt + opt_sizeof;\n\n    /*\n     * NOTE: this ends up overwriting the option with itself, but never\n     * with a *longer* value.\n     */\n    size_t written =\n            _avs_coap_option_serialize((uint8_t *) opt, opt_sizeof, new_delta,\n                                       _avs_coap_option_value(opt),\n                                       _avs_coap_option_content_length(opt));\n\n    /*\n     * If rewriting changed header size, previous steps leave a gap between\n     * redelta'd option and all other ones. Shift all following options to\n     * remove that gap.\n     */\n    uint8_t *new_opt_end = (uint8_t *) opt + written;\n    assert(old_opt_end >= new_opt_end);\n\n    const uint8_t *old_options_end =\n            (const uint8_t *) it->opts->begin + it->opts->size;\n    assert(old_opt_end <= old_options_end);\n    memmove(new_opt_end, old_opt_end, (size_t) (old_options_end - old_opt_end));\n\n    size_t gap_size = (size_t) (old_opt_end - new_opt_end);\n    it->opts->size -= gap_size;\n}\n\nstatic avs_error_t grow_if_required(avs_coap_options_t *opts,\n                                    uint16_t new_data_size) {\n    if (!opts->allocated) {\n        return AVS_OK;\n    }\n\n    // 1 header + 2 ext delta + 2 ext length\n    static const size_t MAX_OPT_HEADER_SIZE = 5;\n    size_t desired_capacity = opts->size + MAX_OPT_HEADER_SIZE + new_data_size;\n\n    if (opts->capacity < desired_capacity) {\n        void *new_buf = avs_realloc(opts->begin, desired_capacity);\n        if (!new_buf) {\n            return avs_errno(AVS_ENOMEM);\n        }\n\n        opts->begin = new_buf;\n        opts->capacity = desired_capacity;\n    }\n\n    return AVS_OK;\n}\n\navs_error_t avs_coap_options_add_opaque(avs_coap_options_t *opts,\n                                        uint16_t opt_number,\n                                        const void *opt_data,\n                                        uint16_t opt_data_size) {\n    avs_error_t err = grow_if_required(opts, opt_data_size);\n    if (avs_is_err(err)) {\n        return err;\n    }\n\n    avs_coap_option_iterator_t insert_it = _avs_coap_optit_begin(opts);\n    while (!_avs_coap_optit_end(&insert_it)\n           && _avs_coap_optit_number(&insert_it) <= opt_number) {\n        _avs_coap_optit_next(&insert_it);\n    }\n\n    assert(opt_number >= insert_it.prev_opt_number);\n    size_t opt_num_delta = (uint32_t) opt_number - insert_it.prev_opt_number;\n    assert(opt_num_delta <= UINT16_MAX);\n\n    size_t bytes_required =\n            _avs_coap_get_opt_header_size(opt_num_delta, opt_data_size)\n            + opt_data_size;\n\n    if (!new_option_fits(&insert_it, opt_number, bytes_required)) {\n        LOG(ERROR, _(\"options buffer too small to fit another option\"));\n        return _avs_coap_err(AVS_COAP_ERR_MESSAGE_TOO_BIG);\n    }\n\n    /*\n     * Insert a new option into a buffer full of serialized options.\n     *\n     * insert_ptr -.                                 .- old_opts_end\n     *             v                                 v\n     *        -----+--------------+------------     -+\n     *         ... |   next_opt   | other opts  ...  |\n     *        -----+--------------+------------     -+\n     *  [1]        |           .--'\n     *             v           v\n     *        -----+-----------+---------------\n     *         ... | next_opt' | other opts...\n     *        -----+-----------+---------------\n     *  [2]        |           '- - - - - - - - - .\n     *             '------------------.           |\n     *                                v           v\n     *        -----+------------------+-----------+---------------\n     *         ... | [bytes_required] | next_opt' | other opts...\n     *        -----+------------------+-----------+---------------\n     *  [3]        |                  |\n     *             v                  v\n     *        -----+------------------+-----------+---------------\n     *         ... |    new option    | next_opt' | other opts...\n     *        -----+------------------+-----------+---------------\n     *\n     * TODO: remove an extra memmove by moving other opts right first\n     * and then rewriting next_opt' in its final place\n     */\n\n    if (!_avs_coap_optit_end(&insert_it)) {\n        /*\n         * [1] next_opt option, if it exists, may require updating its option\n         * number delta. This may even shorten its header by a byte or two.\n         */\n        uint32_t new_next_delta =\n                _avs_coap_optit_number(&insert_it) - opt_number;\n        assert(new_next_delta <= UINT16_MAX);\n        update_option_delta_in_place(&insert_it, (uint16_t) new_next_delta);\n    }\n\n    /*\n     * [2] Now move next_opt' and other opts forward to make bytes_required\n     * free space for the new option.\n     *\n     * NOTE: _avs_coap_optit_current is not supposed to be used when the\n     * iterator points to \"end\"; use curr_opt directly instead\n     */\n    uint8_t *insert_ptr = (uint8_t *) insert_it.curr_opt;\n    assert(insert_ptr >= (uint8_t *) opts->begin);\n    size_t insert_offset = (size_t) (insert_ptr - (uint8_t *) opts->begin);\n\n    memmove(insert_ptr + bytes_required, insert_ptr,\n            opts->size - insert_offset);\n\n    opts->size += bytes_required;\n    assert(opts->size <= opts->capacity);\n\n    /*\n     * [3] Finally, serialize the new option into freed space.\n     */\n    const size_t written =\n            _avs_coap_option_serialize(insert_ptr, bytes_required,\n                                       opt_num_delta, opt_data, opt_data_size);\n    assert(written == bytes_required);\n    (void) written;\n    return AVS_OK;\n}\n\navs_error_t avs_coap_options_add_string(avs_coap_options_t *opts,\n                                        uint16_t opt_number,\n                                        const char *opt_data) {\n    size_t size = strlen(opt_data);\n    if (size > UINT16_MAX) {\n        return avs_errno(AVS_ERANGE);\n    }\n\n    return avs_coap_options_add_opaque(opts, opt_number, opt_data,\n                                       (uint16_t) size);\n}\n\navs_error_t avs_coap_options_add_string_fv(avs_coap_options_t *opts,\n                                           uint16_t opt_number,\n                                           const char *format,\n                                           va_list list) {\n    va_list list2;\n    va_copy(list2, list);\n    int result = vsnprintf(NULL, 0, format, list2);\n    va_end(list2);\n\n    if (result < 0 || result > UINT16_MAX) {\n        LOG(DEBUG,\n            _(\"invalid formatted option size: \") \"%d\" _(\", expected integer in \"\n                                                        \"range [0; 65535]\"),\n            result);\n        return avs_errno(AVS_ERANGE);\n    }\n\n    uint16_t size = (uint16_t) result;\n    char *buf = (char *) avs_malloc(size + 1u); // +1 for nullbyte\n    if (!buf) {\n        LOG_OOM();\n        return avs_errno(AVS_ENOMEM);\n    }\n\n    int written = vsnprintf(buf, size + 1u, format, list);\n    assert(written == (int) size);\n    (void) written;\n\n    avs_error_t err = avs_coap_options_add_opaque(opts, opt_number, buf, size);\n    avs_free(buf);\n    return err;\n}\n\navs_error_t avs_coap_options_add_string_f(avs_coap_options_t *opts,\n                                          uint16_t opt_number,\n                                          const char *format,\n                                          ...) {\n    va_list list;\n    va_start(list, format);\n    avs_error_t err =\n            avs_coap_options_add_string_fv(opts, opt_number, format, list);\n    va_end(list);\n    return err;\n}\n\navs_error_t avs_coap_options_add_empty(avs_coap_options_t *opts,\n                                       uint16_t opt_number) {\n    return avs_coap_options_add_opaque(opts, opt_number, \"\", 0);\n}\n\navs_error_t avs_coap_options_add_uint(avs_coap_options_t *opts,\n                                      uint16_t opt_number,\n                                      const void *value,\n                                      size_t value_size) {\n#ifdef AVS_COMMONS_BIG_ENDIAN\n    const uint8_t *converted = (const uint8_t *) value;\n#else\n    AVS_ASSERT(value_size <= 8,\n               \"uint options larger than 64 bits are not supported\");\n    uint8_t converted[8];\n    for (size_t i = 0; i < value_size; ++i) {\n        converted[value_size - 1 - i] = ((const uint8_t *) value)[i];\n    }\n#endif\n    size_t start = 0;\n    while (start < value_size && !converted[start]) {\n        ++start;\n    }\n    return avs_coap_options_add_opaque(opts, opt_number, &converted[start],\n                                       (uint16_t) (value_size - start));\n}\n\nconst avs_coap_option_t *\n_avs_coap_options_find_first_opt(const avs_coap_options_t *opts,\n                                 uint16_t opt_number) {\n    // TODO: const_cast; maybe const_iterator could be nice?\n    for (avs_coap_option_iterator_t it =\n                 _avs_coap_optit_begin((avs_coap_options_t *) (intptr_t) opts);\n         !_avs_coap_optit_end(&it);\n         _avs_coap_optit_next(&it)) {\n        uint32_t curr_opt_number = _avs_coap_optit_number(&it);\n\n        if (curr_opt_number == opt_number) {\n            return _avs_coap_optit_current(&it);\n        } else if (curr_opt_number > opt_number) {\n            return NULL;\n        }\n    }\n\n    return NULL;\n}\n\nint avs_coap_options_get_content_format(const avs_coap_options_t *opts,\n                                        uint16_t *out_value) {\n    assert(opts);\n    assert(out_value);\n    const avs_coap_option_t *opt =\n            _avs_coap_options_find_first_opt(opts,\n                                             AVS_COAP_OPTION_CONTENT_FORMAT);\n    if (!opt) {\n        *out_value = AVS_COAP_FORMAT_NONE;\n        return 0;\n    }\n    return _avs_coap_option_u16_value(opt, out_value);\n}\n\n#ifdef WITH_AVS_COAP_BLOCK\nint avs_coap_options_get_block(const avs_coap_options_t *opts,\n                               avs_coap_option_block_type_t type,\n                               avs_coap_option_block_t *out_info) {\n    assert(opts);\n    assert(out_info);\n    uint16_t opt_number = type == AVS_COAP_BLOCK1 ? AVS_COAP_OPTION_BLOCK1\n                                                  : AVS_COAP_OPTION_BLOCK2;\n    memset(out_info, 0, sizeof(*out_info));\n    const avs_coap_option_t *opt =\n            _avs_coap_options_find_first_opt(opts, opt_number);\n    if (!opt) {\n        return AVS_COAP_OPTION_MISSING;\n    }\n    return fill_block_data(opt, opt_number, out_info);\n}\n#endif // WITH_AVS_COAP_BLOCK\n\nint avs_coap_options_get_u16(const avs_coap_options_t *opts,\n                             uint16_t option_number,\n                             uint16_t *out_value) {\n    const avs_coap_option_t *opt =\n            _avs_coap_options_find_first_opt(opts, option_number);\n    if (!opt) {\n        LOG(TRACE, _(\"option \") \"%d\" _(\" not found\"), option_number);\n        return AVS_COAP_OPTION_MISSING;\n    }\n\n    return _avs_coap_option_u16_value(opt, out_value);\n}\n\nint avs_coap_options_get_u32(const avs_coap_options_t *opts,\n                             uint16_t option_number,\n                             uint32_t *out_value) {\n    const avs_coap_option_t *opt =\n            _avs_coap_options_find_first_opt(opts, option_number);\n    if (!opt) {\n        LOG(TRACE, _(\"option \") \"%d\" _(\" not found\"), option_number);\n        return AVS_COAP_OPTION_MISSING;\n    }\n\n    return _avs_coap_option_u32_value(opt, out_value);\n}\n\nstatic int get_option_it(const avs_coap_options_t *opts,\n                         uint16_t option_number,\n                         avs_coap_option_iterator_t *it,\n                         size_t *out_opt_size,\n                         void *buffer,\n                         size_t buffer_size,\n                         int (*fetch_value)(const avs_coap_option_t *opt,\n                                            size_t *out_opt_size,\n                                            void *buffer,\n                                            size_t buffer_size)) {\n    if (!it->opts) {\n        // TODO: const_cast; maybe const_iterator could be nice?\n        *it = _avs_coap_optit_begin((avs_coap_options_t *) (intptr_t) opts);\n    } else {\n        assert(it->opts == opts);\n    }\n\n    int retval = AVS_COAP_OPTION_MISSING;\n    for (; !_avs_coap_optit_end(it); _avs_coap_optit_next(it)) {\n        if (_avs_coap_optit_number(it) == option_number) {\n            retval = fetch_value(_avs_coap_optit_current(it), out_opt_size,\n                                 buffer, buffer_size);\n            break;\n        }\n    }\n\n    if (!retval) {\n        _avs_coap_optit_next(it);\n    }\n\n    return retval;\n}\n\nstatic int fetch_bytes(const avs_coap_option_t *opt,\n                       size_t *out_option_size,\n                       void *buffer,\n                       size_t buffer_size) {\n    *out_option_size = _avs_coap_option_content_length(opt);\n\n    if (buffer_size < *out_option_size) {\n        LOG(DEBUG, _(\"buffer too small to hold entire option\"));\n        return -1;\n    }\n\n    memcpy(buffer, _avs_coap_option_value(opt), *out_option_size);\n    return 0;\n}\n\nint avs_coap_options_skip_it(avs_coap_option_iterator_t *inout_it) {\n    if (!_avs_coap_optit_end(inout_it)) {\n        _avs_coap_optit_next(inout_it);\n        return 0;\n    }\n    return -1;\n}\n\nint avs_coap_options_get_bytes_it(const avs_coap_options_t *opts,\n                                  uint16_t option_number,\n                                  avs_coap_option_iterator_t *it,\n                                  size_t *out_option_size,\n                                  void *buffer,\n                                  size_t buffer_size) {\n    return get_option_it(opts, option_number, it, out_option_size, buffer,\n                         buffer_size, fetch_bytes);\n}\n\nint avs_coap_options_get_etag_it(const avs_coap_options_t *opts,\n                                 avs_coap_option_iterator_t *it,\n                                 avs_coap_etag_t *out_etag) {\n    size_t bytes_read = 0;\n    int retval = get_option_it(opts, AVS_COAP_OPTION_ETAG, it, &bytes_read,\n                               out_etag->bytes, sizeof(out_etag->bytes),\n                               fetch_bytes);\n    if (!retval) {\n        assert(bytes_read <= sizeof(out_etag->bytes));\n        out_etag->size = (uint8_t) bytes_read;\n    } else {\n        *out_etag = (avs_coap_etag_t) { 0 };\n    }\n    return retval;\n}\n\nstatic int fetch_string(const avs_coap_option_t *opt,\n                        size_t *out_option_size,\n                        void *buffer,\n                        size_t buffer_size) {\n    return _avs_coap_option_string_value(opt, out_option_size, (char *) buffer,\n                                         buffer_size);\n}\n\nint avs_coap_options_get_string_it(const avs_coap_options_t *opts,\n                                   uint16_t option_number,\n                                   avs_coap_option_iterator_t *it,\n                                   size_t *out_option_size,\n                                   char *buffer,\n                                   size_t buffer_size) {\n    return get_option_it(opts, option_number, it, out_option_size, buffer,\n                         buffer_size, fetch_string);\n}\n\nbool _avs_coap_option_exists(const avs_coap_options_t *opts,\n                             uint16_t opt_number) {\n    return _avs_coap_options_find_first_opt(opts, opt_number) != NULL;\n}\n\nstatic bool is_option_identical(avs_coap_option_t *a, avs_coap_option_t *b) {\n    size_t len_a = _avs_coap_option_content_length(a);\n    size_t len_b = _avs_coap_option_content_length(b);\n    return len_a == len_b\n           && memcmp(_avs_coap_option_value(a), _avs_coap_option_value(b),\n                     len_a)\n                      == 0;\n}\n\nstatic void optit_skip_until(avs_coap_option_iterator_t *optit,\n                             bool (*predicate)(uint16_t)) {\n    while (!_avs_coap_optit_end(optit)) {\n        uint32_t opt_num = _avs_coap_optit_number(optit);\n        AVS_ASSERT(opt_num <= UINT16_MAX, \"malformed options\");\n        if (predicate((uint16_t) opt_num)) {\n            return;\n        }\n\n        _avs_coap_optit_next(optit);\n    }\n}\n\nstatic void optit_next_matching(avs_coap_option_iterator_t *optit,\n                                bool (*selector)(uint16_t)) {\n    _avs_coap_optit_next(optit);\n    // NOTE: elective == !critical\n    optit_skip_until(optit, selector);\n}\n\nbool _avs_coap_selected_options_equal(const avs_coap_options_t *first,\n                                      const avs_coap_options_t *second,\n                                      bool (*selector)(uint16_t)) {\n    avs_coap_option_iterator_t it_first =\n            _avs_coap_optit_begin((avs_coap_options_t *) (intptr_t) first);\n    avs_coap_option_iterator_t it_second =\n            _avs_coap_optit_begin((avs_coap_options_t *) (intptr_t) second);\n\n    optit_skip_until(&it_first, selector);\n    optit_skip_until(&it_second, selector);\n\n    while (!_avs_coap_optit_end(&it_first)\n           && !_avs_coap_optit_end(&it_second)) {\n        uint32_t opt_num_first = _avs_coap_optit_number(&it_first);\n        uint32_t opt_num_second = _avs_coap_optit_number(&it_second);\n\n        if (opt_num_first != opt_num_second) {\n            LOG(TRACE,\n                _(\"some option only exists in one set (\") \"%\" PRIu32\n                                                          \"/%\" PRIu32 _(\")\"),\n                opt_num_first, opt_num_second);\n            return false;\n        }\n\n        avs_coap_option_t *opt_first = _avs_coap_optit_current(&it_first);\n        avs_coap_option_t *opt_second = _avs_coap_optit_current(&it_second);\n        if (!is_option_identical(opt_first, opt_second)) {\n            LOG(TRACE, _(\"different value of option \") \"%\" PRIu32,\n                opt_num_first);\n            return false;\n        }\n\n        optit_next_matching(&it_first, selector);\n        optit_next_matching(&it_second, selector);\n    }\n\n    if (!_avs_coap_optit_end(&it_first)) {\n        LOG(TRACE, _(\"excess \") \"%\" PRIu32 _(\" option in `first` set\"),\n            _avs_coap_optit_number(&it_first));\n        return false;\n    } else if (!_avs_coap_optit_end(&it_second)) {\n        LOG(TRACE, _(\"excess \") \"%\" PRIu32 _(\" option in `second` set\"),\n            _avs_coap_optit_number(&it_second));\n        return false;\n    }\n\n    return true;\n}\n\nstatic bool option_must_not_change_during_transfer(uint16_t opt_num) {\n    return (_avs_coap_option_is_critical(opt_num)\n            // BLOCK options *do* change during block transfer, even though\n            // they are \"critical\"\n            && opt_num != AVS_COAP_OPTION_BLOCK1\n            && opt_num != AVS_COAP_OPTION_BLOCK2)\n           // Content-Format is not critical, but if it changes, that's a pretty\n           // big WTF.\n           || opt_num == AVS_COAP_OPTION_CONTENT_FORMAT;\n}\n\n#ifdef WITH_AVS_COAP_BLOCK\n\nstatic size_t get_block_offset(const avs_coap_options_t *opts,\n                               avs_coap_option_block_type_t type,\n                               size_t seq_num_offset) {\n    avs_coap_option_block_t block;\n    int result = avs_coap_options_get_block(opts, type, &block);\n\n    if (result == AVS_COAP_OPTION_MISSING) {\n        LOG(TRACE, _(\"BLOCK\") \"%d\" _(\" option missing, returning 0\"),\n            type == AVS_COAP_BLOCK1 ? 1 : 2);\n        return 0;\n    }\n\n    return (block.seq_num + seq_num_offset) * block.size;\n}\n\nstatic inline size_t next_block1_offset(const avs_coap_options_t *prev) {\n    return get_block_offset(prev, AVS_COAP_BLOCK1, 1);\n}\n\nstatic size_t block1_offset(const avs_coap_options_t *prev) {\n    return get_block_offset(prev, AVS_COAP_BLOCK1, 0);\n}\n\nstatic size_t next_block2_offset(const avs_coap_options_t *prev) {\n    return get_block_offset(prev, AVS_COAP_BLOCK2, 1);\n}\n\nstatic size_t block2_offset(const avs_coap_options_t *prev) {\n    return get_block_offset(prev, AVS_COAP_BLOCK2, 0);\n}\n\nstatic bool block1_offset_matches(size_t expected_offset,\n                                  const avs_coap_options_t *curr_request) {\n    size_t actual_offset = block1_offset(curr_request);\n    if (expected_offset != actual_offset) {\n        LOG(TRACE, _(\"expected BLOCK1 offset \") \"%u\" _(\", got \") \"%u\",\n            (unsigned) expected_offset, (unsigned) actual_offset);\n        return false;\n    }\n    return true;\n}\n\nstatic bool block2_offset_matches(const avs_coap_options_t *prev_response,\n                                  const avs_coap_options_t *curr_request) {\n    size_t expected_offset = next_block2_offset(prev_response);\n    size_t actual_offset = block2_offset(curr_request);\n    if (expected_offset != actual_offset) {\n        LOG(TRACE, _(\"expected BLOCK2 offset \") \"%u\" _(\", got \") \"%u\",\n            (unsigned) expected_offset, (unsigned) actual_offset);\n        return false;\n    }\n    return true;\n}\n\n/**\n * This function checks if expected request payload offset (calculated from\n * previous response to BLOCK request) matches the offset calculated using\n * incoming requests payload sizes.\n *\n * For BERT, expected offset calculated by next_block1_offset() may be smaller\n * than actually expected one, because BERT messages may contain multiple\n * BLOCKs.\n */\nstatic inline bool request_block1_offset_valid(const avs_coap_options_t *prev,\n                                               size_t offset) {\n    avs_coap_option_block_t block;\n    int result = avs_coap_options_get_block(prev, AVS_COAP_BLOCK1, &block);\n    if (result == AVS_COAP_OPTION_MISSING) {\n        LOG(TRACE, _(\"BLOCK1 option missing\"));\n        return (offset == 0);\n    }\n    size_t expected_offset_if_block = next_block1_offset(prev);\n    if (!block.is_bert) {\n        return (expected_offset_if_block == offset);\n    } else {\n        return (expected_offset_if_block <= offset);\n    }\n}\n\nbool _avs_coap_options_is_sequential_block_request(\n        const avs_coap_options_t *prev_response,\n        const avs_coap_options_t *prev,\n        const avs_coap_options_t *curr,\n        size_t expected_request_payload_offset) {\n    if (!_avs_coap_selected_options_equal(\n                prev, curr, option_must_not_change_during_transfer)) {\n        return false;\n    }\n    /**\n     * Current request is said to match previous response in the following cases\n     * only:\n     *\n     *  +-------------------+--------------------+\n     *  |   PREV RESPONSE   |    CURR REQUEST    |\n     *  +-------------------+--------------------+\n     *  | BLOCK1(N-1, *)    |  BLOCK1(N, *)      | <- continuation of BLOCK1\n     *  |                   |                    |    request\n     *  +-------------------+--------------------+\n     *  | BLOCK1(N-1, *)    |  BLOCK1(N, FINAL), | <- last part of BLOCK1\n     *  |                   |  BLOCK2(0, *)      |    request, and client\n     *  |                   |                    |    expects blockwise resp.\n     *  |                   |                    |    (handled in lower layer)\n     *  +-------------------+--------------------+\n     *  | BLOCK2(N-1, MORE) |  BLOCK2(N, *)      | <- continuation of BLOCK2\n     *  |                   |                    |    response\n     *  +-------------------+--------------------+\n     *  | BLOCK1(N, FINAL), |  BLOCK2(1, *)      | <- we accepted last BLOCK1\n     *  | BLOCK2(0, *)      |                    |    request, and initiated\n     *  +-------------------+--------------------+    blockwise response\n     *\n     * NOTE: For simplicity of the illustration, it was assumed that all BLOCKs\n     * are of the same size, and thus size was omitted. BLOCK(k, has more)\n     * means: it is a k-th (in terms of sequence number) block in exchange.\n     */\n\n    const bool prev_response_has_block1 =\n            _avs_coap_option_exists(prev_response, AVS_COAP_OPTION_BLOCK1);\n    const bool prev_response_has_block2 =\n            _avs_coap_option_exists(prev_response, AVS_COAP_OPTION_BLOCK2);\n    const bool curr_request_has_block1 =\n            _avs_coap_option_exists(curr, AVS_COAP_OPTION_BLOCK1);\n    const bool curr_request_has_block2 =\n            _avs_coap_option_exists(curr, AVS_COAP_OPTION_BLOCK2);\n\n    /* First case from the table above. */\n    if (prev_response_has_block1 && !prev_response_has_block2) {\n        /* NOTE: We are omitting second case check, becuase it is already\n         * verified at the stage of parsing CoAP options. */\n        AVS_ASSERT(request_block1_offset_valid(prev_response,\n                                               expected_request_payload_offset),\n                   \"bug: expected_request_offset invalid\");\n        return curr_request_has_block1\n               && block1_offset_matches(expected_request_payload_offset, curr);\n    }\n\n    /* Third and fourth case */\n    if (prev_response_has_block2) {\n        return !curr_request_has_block1 && curr_request_has_block2\n               && block2_offset_matches(prev_response, curr);\n    }\n\n    return false;\n}\n\nstatic avs_error_t\nvalidate_block2_in_block1_request(const avs_coap_options_t *opts) {\n    /**\n     * 2.2.  Structure of a Block Option:\n     * [...]\n     * > When a Block2 Option is used in a request to retrieve a specific block\n     * > number (\"control usage\"), the M bit MUST be sent as zero and ignored\n     * > on reception.\n     *\n     * Since it is a \"MUST\", we report Bad Option if the received request\n     * contains incorrect BLOCK2 option.\n     */\n    avs_coap_option_block_t block1;\n    memset(&block1, 0, sizeof(block1));\n\n    int result = avs_coap_options_get_block(opts, AVS_COAP_BLOCK1, &block1);\n    if (result == AVS_COAP_OPTION_MISSING\n            || !_avs_coap_option_exists(opts, AVS_COAP_OPTION_BLOCK2)) {\n        return AVS_OK;\n    }\n    AVS_ASSERT(!result, \"BUG: malformed option passed option validation\");\n\n    if (block1.has_more) {\n        LOG(TRACE, _(\"BLOCK2 can be used in conjunction with BLOCK1 only in \"\n                     \"final BLOCK1 request exchange\"));\n        return _avs_coap_err(AVS_COAP_ERR_MALFORMED_OPTIONS);\n    }\n\n    return AVS_OK;\n}\n\nbool _avs_coap_options_block_payload_valid(const avs_coap_options_t *opts,\n                                           uint8_t coap_code,\n                                           size_t payload_size) {\n    avs_coap_option_block_type_t type;\n    if (avs_coap_code_is_request(coap_code)) {\n        type = AVS_COAP_BLOCK1;\n    } else if (avs_coap_code_is_response(coap_code)) {\n        type = AVS_COAP_BLOCK2;\n    } else {\n        return true;\n    }\n\n    avs_coap_option_block_t block;\n    int get_block_result = avs_coap_options_get_block(opts, type, &block);\n    AVS_ASSERT(get_block_result >= 0,\n               \"bug: block option should pass validation before\");\n\n    if (get_block_result == AVS_COAP_OPTION_MISSING || !block.has_more) {\n        return true;\n    }\n    if (block.is_bert) {\n        return payload_size && payload_size % block.size == 0;\n    } else {\n        return payload_size == block.size;\n    }\n}\n#endif // WITH_AVS_COAP_BLOCK\n\nstatic bool is_request_key_option(uint16_t opt_num) {\n    return option_must_not_change_during_transfer(opt_num)\n           || opt_num == AVS_COAP_OPTION_BLOCK1\n           || opt_num == AVS_COAP_OPTION_BLOCK2;\n}\n\navs_error_t _avs_coap_options_parse(avs_coap_options_t *out_opts,\n                                    bytes_dispenser_t *dispenser,\n                                    bool *out_truncated_options,\n                                    bool *out_payload_marker_reached) {\n    /*\n     * Temporarily assume the rest of a packet is options. We will adjust the\n     * size accordingly later after validating options.\n     *\n     * TODO: const_cast\n     */\n    *out_opts = (avs_coap_options_t) {\n        .begin = (void *) (intptr_t) dispenser->read_ptr,\n        .size = dispenser->bytes_left,\n        .capacity = dispenser->bytes_left\n    };\n\n    if (!_avs_coap_options_valid_until_payload_marker(\n                out_opts,\n                &out_opts->size,\n                out_truncated_options,\n                out_payload_marker_reached)) {\n        return _avs_coap_err(AVS_COAP_ERR_MALFORMED_OPTIONS);\n    }\n\n    if (_avs_coap_bytes_extract(dispenser, NULL, out_opts->size)) {\n        AVS_UNREACHABLE(\"parsed options size > bytes available: option \"\n                        \"validation code is incorrect\");\n    }\n\n    // TODO: maybe capacity == 0 could indicate \"read-only\" options?\n    out_opts->capacity = out_opts->size;\n\n#ifdef WITH_AVS_COAP_BLOCK\n    /**\n     * NOTE: we are assuming that whatever we parse is a BLOCK1 request (issued\n     * by some Client, not by us). The same check could make sense, if the\n     * tables were turned -- that is, if we ever were a Client-side that pushes\n     * BLOCK1 requests. The thing is, we never are, and that's why we don't do\n     * the validation in the other direction anywhere.\n     */\n    return validate_block2_in_block1_request(out_opts);\n#else // WITH_AVS_COAP_BLOCK\n    return AVS_OK;\n#endif\n}\n\nsize_t _avs_coap_options_request_key_size(const avs_coap_options_t *opts) {\n    avs_coap_option_iterator_t it =\n            _avs_coap_optit_begin((avs_coap_options_t *) (intptr_t) opts);\n\n    size_t space_required = 0;\n    uint32_t prev_opt_num = 0;\n\n    for (; !_avs_coap_optit_end(&it); _avs_coap_optit_next(&it)) {\n        const uint32_t opt_num = _avs_coap_optit_number(&it);\n        assert(opt_num <= UINT16_MAX);\n\n        if (is_request_key_option((uint16_t) opt_num)) {\n            const avs_coap_option_t *opt = _avs_coap_optit_current(&it);\n            const size_t delta = opt_num - prev_opt_num;\n            const size_t size = _avs_coap_option_content_length(opt);\n\n            // skipping some options may change header size of others, so\n            // we need to recalculate header size\n            space_required += _avs_coap_get_opt_header_size(delta, size) + size;\n            prev_opt_num = opt_num;\n        }\n    }\n\n    return space_required;\n}\n\navs_coap_options_t _avs_coap_options_copy_request_key(\n        const avs_coap_options_t *opts, void *buffer, size_t buffer_size) {\n    AVS_ASSERT(_avs_coap_options_request_key_size(opts) <= buffer_size,\n               \"buffer too small\");\n\n    avs_coap_options_t copy =\n            avs_coap_options_create_empty(buffer, buffer_size);\n    avs_coap_option_iterator_t it =\n            _avs_coap_optit_begin((avs_coap_options_t *) (intptr_t) opts);\n\n    for (; !_avs_coap_optit_end(&it); _avs_coap_optit_next(&it)) {\n        const uint32_t opt_num = _avs_coap_optit_number(&it);\n        assert(opt_num <= UINT16_MAX);\n\n        if (is_request_key_option((uint16_t) opt_num)) {\n            const avs_coap_option_t *opt = _avs_coap_optit_current(&it);\n            const void *data = _avs_coap_option_value(opt);\n            const uint32_t size = _avs_coap_option_content_length(opt);\n            assert(size <= UINT16_MAX);\n\n            if (avs_is_err(avs_coap_options_add_opaque(\n                        &copy, (uint16_t) opt_num, data, (uint16_t) size))) {\n                AVS_UNREACHABLE();\n            }\n        }\n    }\n\n    return copy;\n}\n"
  },
  {
    "path": "deps/avs_coap/src/options/avs_coap_options.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef AVS_COAP_SRC_UDP_OPTIONS_OPTIONS_H\n#define AVS_COAP_SRC_UDP_OPTIONS_OPTIONS_H\n\n#include <assert.h>\n#include <stddef.h>\n#include <string.h>\n\n#include <avsystem/coap/option.h>\n\n#include \"options/avs_coap_option.h\"\n\n#include \"../avs_coap_common_utils.h\"\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\nstatic inline avs_error_t\n_avs_coap_options_copy_into(avs_coap_options_t *out_dest,\n                            const avs_coap_options_t *src) {\n    if (out_dest->capacity < src->size) {\n        return _avs_coap_err(AVS_COAP_ERR_MESSAGE_TOO_BIG);\n    }\n    if (src->size > 0) {\n        memcpy(out_dest->begin, src->begin, src->size);\n    }\n    out_dest->size = src->size;\n    return AVS_OK;\n}\n\nstatic inline avs_coap_options_t _avs_coap_options_copy(\n        const avs_coap_options_t *opts, void *buffer, size_t capacity) {\n    avs_coap_options_t copy = {\n        .begin = buffer,\n        .size = opts->size,\n        .capacity = capacity\n    };\n    avs_error_t err = _avs_coap_options_copy_into(&copy, opts);\n    assert(avs_is_ok(err));\n    (void) err;\n    return copy;\n}\n\nstatic inline avs_error_t\n_avs_coap_options_copy_as_dynamic(avs_coap_options_t *out_dest,\n                                  const avs_coap_options_t *src) {\n    assert(!out_dest->allocated);\n    avs_error_t err =\n            avs_coap_options_dynamic_init_with_size(out_dest, src->size);\n    if (avs_is_ok(err)) {\n        err = _avs_coap_options_copy_into(out_dest, src);\n        assert(avs_is_ok(err));\n    }\n    return err;\n}\n\nstatic inline void _avs_coap_options_shrink_to_fit(avs_coap_options_t *opts) {\n    assert(opts->size <= opts->capacity);\n    opts->capacity = opts->size;\n}\n\n/**\n * Checks syntactic validity of options in @p opts . Calculates actual options\n * size (either opts->size or offset at which @ref AVS_COAP_PAYLOAD_MARKER was\n * encounterd) and puts it into @p out_actual_size .\n *\n * If options are truncated, false is returned and @p out_truncated_options is\n * set to true (if not NULL).\n *\n * If payload marker was reached during parsing, @p out_payload_marker_reached\n * is set to true (if not NULL).\n */\nbool _avs_coap_options_valid_until_payload_marker(\n        const avs_coap_options_t *opts,\n        size_t *out_actual_size,\n        bool *out_truncated_options,\n        bool *out_payload_marker_reached);\n\n/**\n * Like @ref _avs_coap_options_valid_until_payload_marker, but also validates\n * that opts->size is correct.\n */\nbool _avs_coap_options_valid(const avs_coap_options_t *opts);\n\nbool _avs_coap_option_exists(const avs_coap_options_t *opts,\n                             uint16_t opt_number);\n\nstatic inline bool _avs_coap_option_is_critical(uint16_t opt_number) {\n    /*\n     * RFC 7252, 5.4.6 (https://tools.ietf.org/html/rfc7252#section-5.4.6):\n     * > [...] odd numbers indicate a critical option, while even numbers\n     * > indicate an elective option. Note that this is not just a convention,\n     * > it is a feature of the protocol: Whether an option is elective or\n     * > critical is entirely determined by whether its option number is even\n     * > or odd.\n     */\n    return (opt_number % 2) == 1;\n}\n\n/**\n * Checks if options selected by the @p selector function are the same\n * in both option sets.\n *\n * @param first     First set of options to consider.\n * @param second    Second set of options to consider.\n * @param selector  A callback function that shall return true, if the certain\n *                  option is of interest, and false otherwise.\n *\n * @returns true if selected options are equal in both sets, false otherwise.\n */\nbool _avs_coap_selected_options_equal(const avs_coap_options_t *first,\n                                      const avs_coap_options_t *second,\n                                      bool (*selector)(uint16_t));\n\n#ifdef WITH_AVS_COAP_BLOCK\n/**\n * Checks if a message with options @p curr can be considered a continuation\n * of a BLOCK-wise exchange whose previous request options were @p prev .\n *\n * @param prev_response                   Set of options used in a response sent\n *                                        after receiving last BLOCK request.\n * @param prev                            Set of options received in previous\n *                                        BLOCK request.\n * @param curr                            Set of options received in a current\n *                                        request.\n * @param expected_request_payload_offset Expected offset of BLOCK1 request\n *                                        payload.\n *\n * @returns true if @p curr are similar to @p prev enough to consider them\n *          consecutive parts of a single logical exchange.\n */\nbool _avs_coap_options_is_sequential_block_request(\n        const avs_coap_options_t *prev_response,\n        const avs_coap_options_t *prev,\n        const avs_coap_options_t *curr,\n        size_t expected_request_payload_offset);\n\n/**\n * Returns false if payload in message with BLOCK/BERT option with More Flag set\n * has invalid size. This happens in following cases:\n * - request with BLOCK1 and Block Size != @p payload_size\n * - response with BLOCK2 and Block Size != @p payload size\n * - request with BERT1 and @p payload_size % 1024 != 0 or @p payload_size == 0\n * - response with BERT2 and @p payload_size % 1024 != 0 or @p payload_size == 0\n *\n * Returns true if:\n * - @p payload_size is valid for appropriate BLOCK/BERT option\n * - @p coap_code isn't a request or response code\n * - appropriate BLOCK/BERT option isn't present\n */\nbool _avs_coap_options_block_payload_valid(const avs_coap_options_t *opts,\n                                           uint8_t coap_code,\n                                           size_t payload_size);\n/**\n * For a packet with given @p code and @p options, finds a BLOCK option\n * describing the packed payload (i.e. BLOCK1 for requests, BLOCK2 for\n * responses) if one exists.\n */\navs_error_t\n_avs_coap_options_get_block_by_code(const avs_coap_options_t *options,\n                                    uint8_t code,\n                                    avs_coap_option_block_t *out_block,\n                                    bool *out_has_block);\n\n#endif // WITH_AVS_COAP_BLOCK\n\navs_error_t _avs_coap_options_parse(avs_coap_options_t *out_opts,\n                                    bytes_dispenser_t *dispenser,\n                                    bool *out_truncated_options,\n                                    bool *out_payload_marker_reached);\n\n/**\n * Returns the size, in bytes, required to store subset of CoAP options given\n * in @p that is used in @ref _avs_coap_options_is_sequential_block_request .\n */\nsize_t _avs_coap_options_request_key_size(const avs_coap_options_t *opts);\n\n/**\n * Creates a new options list that uses @p buffer for storage and initializes\n * it with the subset of CoAP options from @p opts that is used in\n * @p ref _avs_coap_options_is_sequential_block_request .\n *\n * The behavior is undefined if @p buffer_size is smaller than required.\n * @ref _avs_coap_options_request_key_size should be used to derive number\n * of required bytes.\n */\navs_coap_options_t _avs_coap_options_copy_request_key(\n        const avs_coap_options_t *opts, void *buffer, size_t buffer_size);\n\n/**\n * Finds first option with given @p opt_number .\n *\n * Returns NULL if options is not found in @p opts .\n */\nconst avs_coap_option_t *\n_avs_coap_options_find_first_opt(const avs_coap_options_t *opts,\n                                 uint16_t opt_number);\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif // AVS_COAP_SRC_UDP_OPTIONS_OPTIONS_H\n"
  },
  {
    "path": "deps/avs_coap/src/streaming/avs_coap_streaming_client.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <avs_coap_init.h>\n\n#ifdef WITH_AVS_COAP_STREAMING_API\n\n#    include <inttypes.h>\n\n#    include <avsystem/commons/avs_errno.h>\n#    include <avsystem/commons/avs_stream_v_table.h>\n#    include <avsystem/commons/avs_utils.h>\n\n#    include \"async/avs_coap_async_client.h\"\n#    include \"async/avs_coap_async_server.h\"\n#    include \"avs_coap_code_utils.h\"\n#    include \"avs_coap_streaming_client.h\"\n\n#    define MODULE_NAME coap_stream\n#    include <avs_coap_x_log_config.h>\n\n#    include \"avs_coap_ctx.h\"\n#    include \"options/avs_coap_options.h\"\n\nVISIBILITY_SOURCE_BEGIN\n\nstatic const char *coap_stream_state_string(coap_stream_state_t state) {\n    switch (state) {\n    case COAP_STREAM_STATE_UNINITIALIZED:\n        return \"UNINITIALIZED\";\n    case COAP_STREAM_STATE_SENDING_REQUEST:\n        return \"SENDING_REQUEST\";\n    case COAP_STREAM_STATE_RECEIVING_RESPONSE:\n        return \"RECEIVING_RESPONSE\";\n    }\n    AVS_UNREACHABLE(\"unexpected enum value\");\n    return \"???\";\n}\n\nstatic bool state_transition_allowed(coap_stream_state_t old_state,\n                                     coap_stream_state_t new_state) {\n    switch (old_state) {\n    case COAP_STREAM_STATE_UNINITIALIZED:\n        return true;\n\n    case COAP_STREAM_STATE_SENDING_REQUEST:\n        return (new_state == COAP_STREAM_STATE_UNINITIALIZED\n                || new_state == COAP_STREAM_STATE_RECEIVING_RESPONSE);\n\n    case COAP_STREAM_STATE_RECEIVING_RESPONSE:\n        return new_state == COAP_STREAM_STATE_UNINITIALIZED;\n    }\n\n    AVS_UNREACHABLE(\"unexpected enum value\");\n    return false;\n}\n\nstatic inline bool coap_stream_valid(coap_stream_t *stream) {\n    switch (stream->state) {\n    case COAP_STREAM_STATE_UNINITIALIZED:\n        return stream->chunk_buffer == NULL;\n\n    default:\n        return stream->chunk_buffer != NULL;\n    }\n}\n\nstatic inline void coap_stream_set_state(coap_stream_t *stream,\n                                         coap_stream_state_t new_state) {\n    LOG(DEBUG, _(\"coap_stream state: \") \"%s\" _(\" -> \") \"%s\",\n        coap_stream_state_string(stream->state),\n        coap_stream_state_string(new_state));\n\n    if (state_transition_allowed(stream->state, new_state)) {\n        stream->state = new_state;\n        assert(coap_stream_valid(stream));\n    } else {\n        LOG(ERROR,\n            _(\"unexpected coap_stream state change: \") \"%s\" _(\" -> \") \"%s\",\n            coap_stream_state_string(stream->state),\n            coap_stream_state_string(new_state));\n        AVS_UNREACHABLE(\"coap_stream misused\");\n    }\n}\n\nstatic void coap_stream_set_error(coap_stream_t *stream, avs_error_t err) {\n    if (avs_is_ok(stream->err)) {\n        stream->err = err;\n    } else {\n        LOG(DEBUG, _(\"Suppressing error: \") \"%s\", AVS_COAP_STRERROR(err));\n    }\n}\n\nstatic int feed_payload_chunk(size_t payload_offset,\n                              void *payload_buf,\n                              size_t payload_buf_size,\n                              size_t *out_payload_chunk_size,\n                              void *stream_) {\n    (void) payload_offset;\n\n    coap_stream_t *stream = (coap_stream_t *) stream_;\n    AVS_ASSERT(stream->next_outgoing_chunk.expected_offset == payload_offset,\n               \"payload is supposed to be read sequentially\");\n    assert(stream->state == COAP_STREAM_STATE_SENDING_REQUEST);\n\n    *out_payload_chunk_size = avs_buffer_data_size(stream->chunk_buffer);\n    if (payload_buf_size < *out_payload_chunk_size) {\n        *out_payload_chunk_size = payload_buf_size;\n    }\n    memcpy(payload_buf, avs_buffer_data(stream->chunk_buffer),\n           *out_payload_chunk_size);\n    stream->next_outgoing_chunk.expected_offset += *out_payload_chunk_size;\n    stream->next_outgoing_chunk.expected_payload_size = 0;\n    avs_buffer_consume_bytes(stream->chunk_buffer, *out_payload_chunk_size);\n\n    return 0;\n}\n\nstatic void handle_response(avs_coap_ctx_t *ctx,\n                            avs_coap_exchange_id_t exchange_id,\n                            avs_coap_client_request_state_t result,\n                            const avs_coap_client_async_response_t *response,\n                            avs_error_t err,\n                            void *stream_) {\n    coap_stream_t *stream = (coap_stream_t *) stream_;\n    assert(avs_coap_exchange_id_equal(exchange_id, stream->exchange_id));\n    (void) exchange_id;\n\n    if (stream->state != COAP_STREAM_STATE_RECEIVING_RESPONSE) {\n        avs_buffer_reset(stream->chunk_buffer);\n        coap_stream_set_state(stream, COAP_STREAM_STATE_RECEIVING_RESPONSE);\n    }\n\n    if (response) {\n        avs_coap_options_cleanup(&stream->response_header.options);\n        stream->response_header.code = response->header.code;\n        if (avs_is_err((err = _avs_coap_options_copy_as_dynamic(\n                                &stream->response_header.options,\n                                &response->header.options)))) {\n            LOG(ERROR, _(\"could not copy options: \") \"%s\",\n                AVS_COAP_STRERROR(err));\n            coap_stream_set_error(stream, err);\n            // note that this will recursively call this handler\n            avs_coap_exchange_cancel(ctx, stream->exchange_id);\n        } else {\n            assert(avs_buffer_data_size(stream->chunk_buffer) == 0);\n            assert(response->payload_size\n                   <= avs_buffer_capacity(stream->chunk_buffer));\n            avs_buffer_append_bytes(stream->chunk_buffer, response->payload,\n                                    response->payload_size);\n        }\n    }\n\n    switch (result) {\n    case AVS_COAP_CLIENT_REQUEST_PARTIAL_CONTENT:\n        break;\n\n    case AVS_COAP_CLIENT_REQUEST_FAIL:\n        coap_stream_set_error(stream, err);\n        // fall through\n    case AVS_COAP_CLIENT_REQUEST_OK:\n    case AVS_COAP_CLIENT_REQUEST_CANCEL:\n        // exchange finished\n        stream->exchange_id = AVS_COAP_EXCHANGE_ID_INVALID;\n    }\n}\n\nstatic int reject_request(avs_coap_server_ctx_t *ctx,\n                          const avs_coap_request_header_t *request,\n                          void *arg) {\n    (void) ctx;\n    (void) arg;\n\n    LOG(DEBUG,\n        \"%s\" _(\" received while handling a streaming CoAP transfer; sending \"\n               \"Service Unavailable response\"),\n        AVS_COAP_CODE_STRING(request->code));\n\n    return AVS_COAP_CODE_SERVICE_UNAVAILABLE;\n}\n\nstatic avs_coap_ctx_t *coap_stream_owner_ctx(coap_stream_t *stream) {\n    return stream->coap_ctx;\n}\n\nstatic avs_error_t\nacquire_in_buffer_and_handle_incoming_packet(coap_stream_t *stream) {\n    avs_coap_ctx_t *ctx = coap_stream_owner_ctx(stream);\n    uint8_t *acquired_in_buffer;\n    size_t acquired_in_buffer_size;\n    avs_error_t err = _avs_coap_in_buffer_acquire(ctx, &acquired_in_buffer,\n                                                  &acquired_in_buffer_size);\n    if (avs_is_err(err)) {\n        return err;\n    }\n    err = _avs_coap_async_incoming_packet_simple_handle_single(\n            ctx, acquired_in_buffer, acquired_in_buffer_size, reject_request,\n            NULL);\n    if (avs_is_ok(err) && !avs_coap_exchange_id_valid(stream->exchange_id)) {\n        // We have just received a final response, the exchange is no longer\n        // valid. We want to exhaust all the data that might be still buffered\n        // in the socket before returning control to the user.\n        // This might cause sending 5.03 Service Unavailable even though we'd\n        // probably be capable of perfectly handling that request, but it's\n        // lesser evil than requiring the end user to worry about multiple\n        // layers of in-socket buffering.\n        err = _avs_coap_async_incoming_packet_exhaust_socket(\n                ctx, acquired_in_buffer, acquired_in_buffer_size,\n                reject_request, NULL);\n    }\n    _avs_coap_in_buffer_release(ctx);\n    return err;\n}\n\nstatic avs_error_t try_wait_for_response(coap_stream_t *stream) {\n    LOG(TRACE,\n        _(\"waiting for response to \") \"%s\" _(\" (exchange ID \") \"%s\" _(\")\"),\n        AVS_COAP_CODE_STRING(stream->request_header.code),\n        AVS_UINT64_AS_STRING(stream->exchange_id.value));\n\n    avs_coap_ctx_t *ctx = coap_stream_owner_ctx(stream);\n    // We are outside of the event loop, so we need to call the timeout handlers\n    // manually. This may include handling timeouts for our own exchange, but\n    // also for any other that might be ongoing.\n    avs_time_monotonic_t next_timeout =\n            _avs_coap_retry_or_request_expired_job(ctx);\n\n    avs_error_t err = AVS_OK;\n    if (!avs_coap_exchange_id_valid(stream->exchange_id)) {\n        // exchange failed e.g. due to reaching MAX_RETRANSMIT number of\n        // retransmissions\n        assert(stream->state == COAP_STREAM_STATE_RECEIVING_RESPONSE);\n        assert(avs_is_err(stream->err));\n    } else if (avs_is_ok(stream->err)) {\n        // next_timeout is the time until the next time\n        // _avs_coap_retry_or_request_expired_job() is supposed to be called,\n        // so we use that as the socket timeout.\n        assert(avs_time_monotonic_valid(next_timeout));\n\n        avs_net_socket_opt_value_t recv_timeout;\n        recv_timeout.recv_timeout =\n                avs_time_monotonic_diff(next_timeout, avs_time_monotonic_now());\n\n        avs_net_socket_opt_value_t orig_recv_timeout;\n\n        avs_net_socket_t *socket = _avs_coap_get_base(ctx)->socket;\n        if (avs_is_err((err = avs_net_socket_get_opt(\n                                socket, AVS_NET_SOCKET_OPT_RECV_TIMEOUT,\n                                &orig_recv_timeout)))\n                || avs_is_err((err = avs_net_socket_set_opt(\n                                       socket,\n                                       AVS_NET_SOCKET_OPT_RECV_TIMEOUT,\n                                       recv_timeout)))) {\n            LOG(ERROR, _(\"could not set socket timeout\"));\n        } else {\n            // _avs_coap_async_incoming_packet_handle_and_exhaust_socket()\n            // cannot be used here, because we want to receive precisely one\n            // packet here. The possible cases to be handled here:\n            // - If we're called from flush_chunk(), the goal is to receive the\n            //   2.31 Continue response, send the next request chunk (note that\n            //   _avs_coap_async_incoming_packet_simple_handle() calls\n            //   handle_response() and feed_payload_chunk() and sends that) and\n            //   return the control - if that's the last chunk of request we\n            //   just sent, we shall now proceed to receiving the response,\n            //   which requires us to return control to the user so that they\n            //   get the stream to read the response from, so we cannot receive\n            //   actual response here - so we cannot receive more than one\n            //   packet.\n            // - If we're called from the end of perform_request() or from\n            //   ensure_data_is_available_to_read(), the goal is to receive a\n            //   chunk of the actual response. handle_response() will cache it\n            //   in the buffer, and the async layer will send a request for the\n            //   next BLOCK2 chunk if applicable, or finish the exchange\n            //   otherwise.\n            err = acquire_in_buffer_and_handle_incoming_packet(stream);\n            if (err.category == AVS_ERRNO_CATEGORY\n                    && err.code == AVS_ETIMEDOUT) {\n                err = AVS_OK;\n            }\n            if (avs_is_err(\n                        avs_net_socket_set_opt(socket,\n                                               AVS_NET_SOCKET_OPT_RECV_TIMEOUT,\n                                               orig_recv_timeout))) {\n                LOG(ERROR, _(\"could not restore socket timeout\"));\n            }\n        }\n        if (avs_is_err(err)) {\n            avs_coap_exchange_cancel(ctx, stream->exchange_id);\n            coap_stream_set_error(stream, err);\n        }\n    }\n\n    return stream->err;\n}\n\nstatic avs_error_t flush_chunk(coap_stream_t *stream) {\n    assert(stream->state == COAP_STREAM_STATE_SENDING_REQUEST);\n\n    avs_coap_ctx_t *ctx = coap_stream_owner_ctx(stream);\n    if (!avs_coap_exchange_id_valid(stream->exchange_id)) {\n        // We need to send the first (or only) request chunk, so we need to\n        // create the underlying async exchange. feed_payload_chunk() will be\n        // called during _avs_coap_retry_or_request_expired_job();\n        // handle_response() is just configured, but not called just yet - the\n        // response is received and handled later, within\n        // try_wait_for_response() - see comments there for details.\n        avs_error_t err = avs_coap_client_send_async_request(\n                ctx, &stream->exchange_id, &stream->request_header,\n                feed_payload_chunk, stream, handle_response, stream);\n        if (avs_is_err(err)) {\n            coap_stream_set_error(stream, err);\n        } else {\n            _avs_coap_retry_or_request_expired_job(ctx);\n        }\n        return stream->err;\n    }\n\n    // This is done in a loop, because try_wait_for_response() intentionally\n    // returns success on timeout, and also might return success if it handled\n    // something unrelated to this exchange (other async exchanges might be\n    // handled \"in the background\").\n    while (stream->state == COAP_STREAM_STATE_SENDING_REQUEST\n           && stream->next_outgoing_chunk.expected_payload_size > 0) {\n        // We need to send some non-first request chunk. We are being called\n        // either from coap_write(), or just after payload writer; anyway, the\n        // logic we are in is all about writing. To send another chunk, we need\n        // to first receive the 2.31 Continue that we expect in response to the\n        // previously sent chunk.\n        // try_wait_for_response() will actually also call feed_payload_chunk()\n        // and send that chunk. See comments inside for details.\n        avs_error_t err = try_wait_for_response(stream);\n        if (avs_is_err(err)) {\n            return err;\n        }\n    }\n    return AVS_OK;\n}\n\nstatic avs_error_t\nget_next_outgoing_chunk_payload_size(coap_stream_t *stream,\n                                     size_t *out_payload_size) {\n    if (!stream->next_outgoing_chunk.expected_payload_size) {\n        avs_error_t err;\n\n        if (avs_coap_exchange_id_valid(stream->exchange_id)) {\n            err = _avs_coap_exchange_get_next_outgoing_chunk_payload_size(\n                    coap_stream_owner_ctx(stream), stream->exchange_id,\n                    &stream->next_outgoing_chunk.expected_payload_size);\n        } else {\n            err = _avs_coap_get_first_outgoing_chunk_payload_size(\n                    coap_stream_owner_ctx(stream),\n                    stream->request_header.code,\n                    &stream->request_header.options,\n                    &stream->next_outgoing_chunk.expected_payload_size);\n        }\n\n        if (avs_is_err(err)) {\n            return err;\n        }\n    }\n    *out_payload_size = stream->next_outgoing_chunk.expected_payload_size;\n    return AVS_OK;\n}\n\nstatic avs_error_t\ncoap_write(avs_stream_t *stream_, const void *data, size_t *data_length) {\n    coap_stream_t *stream = (coap_stream_t *) stream_;\n    if (stream->state != COAP_STREAM_STATE_SENDING_REQUEST) {\n        LOG(ERROR,\n            _(\"Could not write to CoAP stream: exchange already processed\"));\n        return avs_errno(AVS_EBADF);\n    }\n\n    size_t bytes_written = 0;\n    while (bytes_written < *data_length) {\n        size_t bytes_to_write =\n                AVS_MIN(*data_length - bytes_written,\n                        avs_buffer_space_left(stream->chunk_buffer));\n        avs_buffer_append_bytes(stream->chunk_buffer,\n                                (const char *) data + bytes_written,\n                                bytes_to_write);\n        bytes_written += bytes_to_write;\n        size_t next_outgoing_chunk_payload_size;\n        avs_error_t err = AVS_OK;\n        while (avs_is_ok(err)\n               && stream->state == COAP_STREAM_STATE_SENDING_REQUEST\n               && avs_is_ok((err = get_next_outgoing_chunk_payload_size(\n                                     stream,\n                                     &next_outgoing_chunk_payload_size)))) {\n            assert(avs_buffer_capacity(stream->chunk_buffer)\n                   >= next_outgoing_chunk_payload_size);\n            if (avs_buffer_data_size(stream->chunk_buffer)\n                    < next_outgoing_chunk_payload_size) {\n                break;\n            }\n            // Buffer filled, let's send the request packet\n            err = flush_chunk(stream);\n        }\n        if (avs_is_err(err)) {\n            return err;\n        }\n    }\n    return AVS_OK;\n}\n\nstatic void\nmove_dynamic_response_header(coap_stream_t *stream,\n                             avs_coap_response_header_t *out_header) {\n    assert(stream->state == COAP_STREAM_STATE_RECEIVING_RESPONSE);\n    assert(stream->response_header.options.allocated\n           || !stream->response_header.options.capacity);\n    // move the response header\n    *out_header = stream->response_header;\n    // reset the internal variable, so that there is only one copy of the\n    // dynamically allocated options data\n    stream->response_header.options = avs_coap_options_create_empty(NULL, 0);\n}\n\nstatic avs_error_t ensure_data_is_available_to_read(coap_stream_t *stream) {\n    AVS_ASSERT(stream->state == COAP_STREAM_STATE_RECEIVING_RESPONSE,\n               \"coap_stream misused\");\n\n    // The purpose of this function is to ensure that at least one byte can be\n    // read from the chunk_buffer.\n    avs_error_t err = AVS_OK;\n    while (avs_is_ok(err) && avs_buffer_data_size(stream->chunk_buffer) == 0) {\n        if (!avs_coap_exchange_id_valid(stream->exchange_id)) {\n            return stream->err;\n        }\n        // If the buffer is empty and if the exchange is still ongoing, it means\n        // that we need to receive next BLOCK2 chunk of response\n        err = try_wait_for_response(stream);\n    }\n    return err;\n}\n\nstatic avs_error_t coap_read(avs_stream_t *stream_,\n                             size_t *out_bytes_read,\n                             bool *out_message_finished,\n                             void *buffer,\n                             size_t buffer_length) {\n    coap_stream_t *stream = (coap_stream_t *) stream_;\n    avs_error_t err = ensure_data_is_available_to_read(stream);\n    if (avs_is_err(err)) {\n        return err;\n    }\n\n    size_t bytes_to_read =\n            AVS_MIN(buffer_length, avs_buffer_data_size(stream->chunk_buffer));\n    memcpy(buffer, avs_buffer_data(stream->chunk_buffer), bytes_to_read);\n    avs_buffer_consume_bytes(stream->chunk_buffer, bytes_to_read);\n    if (out_bytes_read) {\n        *out_bytes_read = bytes_to_read;\n    }\n    if (out_message_finished) {\n        *out_message_finished =\n                (avs_buffer_data_size(stream->chunk_buffer) == 0\n                 && !avs_coap_exchange_id_valid(stream->exchange_id));\n    }\n    return AVS_OK;\n}\n\nstatic avs_error_t\ncoap_peek(avs_stream_t *stream_, size_t offset, char *out_value) {\n    coap_stream_t *stream = (coap_stream_t *) stream_;\n    avs_error_t err = ensure_data_is_available_to_read(stream);\n    if (avs_is_err(err)) {\n        return err;\n    }\n\n    if (offset >= avs_buffer_data_size(stream->chunk_buffer)) {\n        return AVS_EOF;\n    }\n    *out_value = avs_buffer_data(stream->chunk_buffer)[offset];\n    return AVS_OK;\n}\n\nconst avs_stream_v_table_t _AVS_COAP_STREAM_VTABLE = {\n    .write_some = coap_write,\n    .read = coap_read,\n    .peek = coap_peek,\n    .extension_list = AVS_STREAM_V_TABLE_NO_EXTENSIONS\n};\n\nstatic avs_error_t perform_request(coap_stream_t *coap_stream,\n                                   const avs_coap_request_header_t *req,\n                                   avs_coap_streaming_writer_t *write_payload,\n                                   void *write_payload_arg) {\n    if (coap_stream->state != COAP_STREAM_STATE_UNINITIALIZED) {\n        LOG(DEBUG, _(\"discarding unread response data\"));\n        _avs_coap_stream_cleanup(coap_stream);\n    }\n\n    assert(coap_stream->vtable == &_AVS_COAP_STREAM_VTABLE);\n    AVS_ASSERT(coap_stream->chunk_buffer == NULL,\n               \"chunk_buffer is not supposed to exist in UNINITIALIZED state\");\n\n    coap_stream->err = AVS_OK;\n    coap_stream->request_header.code = req->code;\n    avs_error_t err = _avs_coap_options_copy_as_dynamic(\n            &coap_stream->request_header.options, &req->options);\n    if (avs_is_err(err)) {\n        LOG(ERROR, _(\"could not copy options: \") \"%s\", AVS_COAP_STRERROR(err));\n        return err;\n    }\n\n    size_t buffer_size;\n    if (avs_is_err((err = get_next_outgoing_chunk_payload_size(\n                            coap_stream, &buffer_size)))) {\n        return err;\n    }\n    avs_coap_ctx_t *coap_ctx = coap_stream_owner_ctx(coap_stream);\n    const size_t in_buffer_capacity =\n            _avs_coap_get_base(coap_ctx)->in_buffer->capacity;\n    if (in_buffer_capacity > buffer_size) {\n        buffer_size = in_buffer_capacity;\n    }\n    if (avs_buffer_create(&coap_stream->chunk_buffer, buffer_size)) {\n        LOG_OOM();\n        return avs_errno(AVS_ENOMEM);\n    }\n\n    coap_stream_set_state(coap_stream, COAP_STREAM_STATE_SENDING_REQUEST);\n    memset(&coap_stream->next_outgoing_chunk, 0,\n           sizeof(coap_stream->next_outgoing_chunk));\n    // write_payload() is expected to call coap_write(),\n    // so see there for what happens next.\n    if (write_payload\n            && write_payload((avs_stream_t *) coap_stream, write_payload_arg)) {\n        err = _avs_coap_err(AVS_COAP_ERR_PAYLOAD_WRITER_FAILED);\n    }\n\n    if (coap_stream->state != COAP_STREAM_STATE_SENDING_REQUEST) {\n        // We have already received some kind of response. This might happen\n        // even if write_payload() failed, e.g. if we received something else\n        // than 2.31 Continue in response to a Block1 request. This might also\n        // be an error (e.g. after receiving a UDP Reset message).\n        err = coap_stream->err;\n    } else if (avs_is_ok(err)) {\n        // If we end up here, it means that coap_write() has either not been\n        // called at all, or its calls have not filled the buffer enough to\n        // send a BLOCK1 request - so let's send a non-BLOCK request now.\n        err = flush_chunk(coap_stream);\n        assert(avs_is_err(err)\n               || avs_buffer_data_size(coap_stream->chunk_buffer) == 0);\n    }\n    // Now we ensure that we have at least one chunk of response data actually\n    // buffered in the buffer - this will indirectly call handle_response().\n    while (avs_is_ok(err)\n           && avs_buffer_data_size(coap_stream->chunk_buffer) == 0\n           && avs_coap_exchange_id_valid(coap_stream->exchange_id)) {\n        err = try_wait_for_response(coap_stream);\n    }\n    return err;\n}\n\navs_error_t\navs_coap_streaming_send_request(avs_coap_ctx_t *ctx,\n                                const avs_coap_request_header_t *request,\n                                avs_coap_streaming_writer_t *write_payload,\n                                void *write_payload_arg,\n                                avs_coap_response_header_t *out_response,\n                                avs_stream_t **out_response_stream) {\n    avs_coap_base_t *coap_base = _avs_coap_get_base(ctx);\n    out_response->options = avs_coap_options_create_empty(NULL, 0);\n    avs_error_t err = perform_request(&coap_base->coap_stream, request,\n                                      write_payload, write_payload_arg);\n    if (avs_is_ok(err)) {\n        // We have the first (possibly, but not necessarily, only) chunk of\n        // response buffered. Let's return control to the user so that they can\n        // read the response through the stream. If there are more chunks to be\n        // received, coap_read() or coap_peek() will call\n        // ensure_data_is_available_to_read(), so see there for what happens\n        // next.\n        move_dynamic_response_header(&coap_base->coap_stream, out_response);\n        if (out_response_stream) {\n            *out_response_stream = (avs_stream_t *) &coap_base->coap_stream;\n            return AVS_OK;\n        }\n    }\n    _avs_coap_stream_cleanup(&coap_base->coap_stream);\n    return err;\n}\n\nvoid _avs_coap_stream_cleanup(coap_stream_t *stream) {\n    avs_coap_exchange_cancel(coap_stream_owner_ctx(stream),\n                             stream->exchange_id);\n    avs_buffer_free(&stream->chunk_buffer);\n    avs_coap_options_cleanup(&stream->request_header.options);\n    avs_coap_options_cleanup(&stream->response_header.options);\n    coap_stream_set_state(stream, COAP_STREAM_STATE_UNINITIALIZED);\n}\n\n#endif // WITH_AVS_COAP_STREAMING_API\n"
  },
  {
    "path": "deps/avs_coap/src/streaming/avs_coap_streaming_client.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef COAP_SRC_STREAMING_CLIENT_H\n#define COAP_SRC_STREAMING_CLIENT_H\n\n#include <avsystem/coap/coap.h>\n\n#include <avsystem/commons/avs_buffer.h>\n#include <avsystem/commons/avs_stream_membuf.h>\n#include <avsystem/commons/avs_stream_v_table.h>\n\n#include \"async/avs_coap_exchange.h\"\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\ntypedef enum {\n    COAP_STREAM_STATE_UNINITIALIZED,\n    COAP_STREAM_STATE_SENDING_REQUEST,\n    COAP_STREAM_STATE_RECEIVING_RESPONSE\n} coap_stream_state_t;\n\n/**\n * NOTE: because of container_of usage, coap_stream_t can only be used as a\n * avs_coap_stream_t#coap_stream .\n */\ntypedef struct coap_stream {\n    const avs_stream_v_table_t *vtable;\n\n    avs_buffer_t *chunk_buffer;\n\n    coap_stream_state_t state;\n    avs_coap_exchange_id_t exchange_id;\n    avs_error_t err;\n\n    struct {\n        size_t expected_offset;\n        size_t expected_payload_size;\n    } next_outgoing_chunk;\n\n    avs_coap_request_header_t request_header;\n    avs_coap_response_header_t response_header;\n\n    avs_coap_ctx_t *coap_ctx;\n} coap_stream_t;\n\nextern const avs_stream_v_table_t _AVS_COAP_STREAM_VTABLE;\n\nstatic inline void _avs_coap_stream_init(coap_stream_t *stream,\n                                         avs_coap_ctx_t *ctx) {\n    *stream = (coap_stream_t) {\n        .vtable = &_AVS_COAP_STREAM_VTABLE,\n        .coap_ctx = ctx\n    };\n}\n\nvoid _avs_coap_stream_cleanup(coap_stream_t *stream);\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif // COAP_SRC_STREAMING_CLIENT_H\n"
  },
  {
    "path": "deps/avs_coap/src/streaming/avs_coap_streaming_server.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <avs_coap_init.h>\n\n#ifdef WITH_AVS_COAP_STREAMING_API\n\n#    include <avsystem/commons/avs_errno.h>\n#    include <avsystem/commons/avs_utils.h>\n\n#    include <avsystem/coap/code.h>\n#    include <avsystem/coap/streaming.h>\n\n#    include \"async/avs_coap_async_server.h\"\n#    include \"avs_coap_observe.h\"\n#    include \"streaming/avs_coap_streaming_server.h\"\n\n#    define MODULE_NAME coap\n#    include <avs_coap_x_log_config.h>\n\n#    include \"avs_coap_ctx.h\"\n#    include \"options/avs_coap_options.h\"\n\nVISIBILITY_SOURCE_BEGIN\n\nstatic bool\nhas_received_request_chunk(const avs_coap_streaming_server_ctx_t *ctx) {\n    return ctx->state == AVS_COAP_STREAMING_SERVER_RECEIVED_REQUEST_CHUNK\n           || ctx->state\n                      == AVS_COAP_STREAMING_SERVER_RECEIVED_LAST_REQUEST_CHUNK;\n}\n\nstatic bool\nis_sending_response_chunk(const avs_coap_streaming_server_ctx_t *ctx) {\n    return ctx->state == AVS_COAP_STREAMING_SERVER_SENDING_FIRST_RESPONSE_CHUNK\n           || ctx->state == AVS_COAP_STREAMING_SERVER_SENDING_RESPONSE_CHUNK;\n}\n\nstatic int feed_payload_chunk(size_t payload_offset,\n                              void *payload_buf,\n                              size_t payload_buf_size,\n                              size_t *out_payload_chunk_size,\n                              void *streaming_server_ctx_) {\n    (void) payload_offset;\n\n    avs_coap_streaming_server_ctx_t *streaming_server_ctx =\n            (avs_coap_streaming_server_ctx_t *) streaming_server_ctx_;\n    AVS_ASSERT(streaming_server_ctx->expected_next_outgoing_chunk_offset\n                       == payload_offset,\n               \"payload is supposed to be read sequentially\");\n    assert(is_sending_response_chunk(streaming_server_ctx));\n\n    *out_payload_chunk_size =\n            avs_buffer_data_size(streaming_server_ctx->chunk_buffer);\n    if (payload_buf_size <= *out_payload_chunk_size) {\n        *out_payload_chunk_size = payload_buf_size;\n        streaming_server_ctx->state =\n                AVS_COAP_STREAMING_SERVER_SENDING_RESPONSE_CHUNK;\n    } else {\n        streaming_server_ctx->state =\n                AVS_COAP_STREAMING_SERVER_SENT_LAST_RESPONSE_CHUNK;\n    }\n    memcpy(payload_buf, avs_buffer_data(streaming_server_ctx->chunk_buffer),\n           *out_payload_chunk_size);\n    streaming_server_ctx->expected_next_outgoing_chunk_offset +=\n            *out_payload_chunk_size;\n    avs_buffer_consume_bytes(streaming_server_ctx->chunk_buffer,\n                             *out_payload_chunk_size);\n\n    return 0;\n}\n\navs_stream_t *\navs_coap_streaming_setup_response(avs_coap_streaming_request_ctx_t *ctx,\n                                  const avs_coap_response_header_t *response) {\n    if (!ctx) {\n        LOG(ERROR, _(\"no request to respond to\"));\n        return NULL;\n    }\n    if (!response) {\n        LOG(ERROR, _(\"response must be provided\"));\n        return NULL;\n    }\n    if (!_avs_coap_response_header_valid(response)) {\n        return NULL;\n    }\n    if (!has_received_request_chunk(&ctx->server_ctx)) {\n        LOG(ERROR,\n            _(\"Attempted to call avs_coap_streaming_setup_response() in \"\n              \"an invalid state\"));\n        return NULL;\n    }\n\n    avs_coap_options_cleanup(&ctx->response_header.options);\n    if (avs_is_err(_avs_coap_options_copy_as_dynamic(\n                &ctx->response_header.options, &response->options))) {\n        LOG(ERROR, _(\"Could not copy response options\"));\n        return NULL;\n    }\n\n    ctx->response_header.code = response->code;\n    return (avs_stream_t *) ctx;\n}\n\nstatic avs_error_t\ntry_enter_sending_state(avs_coap_streaming_request_ctx_t *ctx) {\n    if (!has_received_request_chunk(&ctx->server_ctx)) {\n        return avs_errno(AVS_EINVAL);\n    }\n    if (!avs_coap_code_is_response(ctx->response_header.code)) {\n        LOG(WARNING, _(\"Response not set up\"));\n        return avs_errno(AVS_EINVAL);\n    }\n    // Note: this is supposed to be called after the calls to\n    // called _avs_coap_async_incoming_packet_handle() and\n    // _avs_coap_async_incoming_packet_call_request_handler() (supposedly from\n    // within handle_incoming_packet()), but before\n    // _avs_coap_async_incoming_packet_send_response(). So effectively, we are\n    // in the middle of the logic that would usually be handled through\n    // _avs_coap_async_incoming_packet_simple_handle().\n    // This function is called either from coap_write(), or after returning from\n    // the user-provided request handler if the user has not called coap_write()\n    // at all. So we know for sure that we're done with the receiving phase,\n    // thus we're setting up the response - this code can be treated as the\n    // continuation of request_handler(), now that the necessary data from the\n    // user is available.\n    // Note: _avs_coap_server_setup_async_response() does not call\n    // feed_payload_chunk(). It will be called by the following call to\n    // _avs_coap_async_incoming_packet_send_response().\n    avs_error_t err = avs_coap_server_setup_async_response(\n            &_avs_coap_get_base(ctx->server_ctx.coap_ctx)->request_ctx,\n            &ctx->response_header, feed_payload_chunk, &ctx->server_ctx);\n    if (avs_is_ok(err)) {\n        if (avs_buffer_data_size(ctx->server_ctx.chunk_buffer) > 0) {\n            LOG(WARNING,\n                _(\"Ignoring \") \"%s\" _(\" unread bytes of request\"),\n                AVS_UINT64_AS_STRING(\n                        avs_buffer_data_size(ctx->server_ctx.chunk_buffer)));\n            avs_buffer_reset(ctx->server_ctx.chunk_buffer);\n        }\n        ctx->server_ctx.state =\n                AVS_COAP_STREAMING_SERVER_SENDING_FIRST_RESPONSE_CHUNK;\n    }\n    return err;\n}\n\nstatic avs_error_t\ninit_chunk_buffer(avs_coap_ctx_t *ctx,\n                  avs_buffer_t **out_buffer,\n                  const avs_coap_server_async_request_t *request,\n                  const avs_coap_response_header_t *response) {\n    /**\n     * In case of incoming requests, the buffer must be able to hold either\n     * request or response payload, so we need to take maximum of:\n     * - maximum estimated request chunk size - for BLOCK transfers, this will\n     *   be the BLOCK1 size of a first request chunk (it can never grow during\n     *   the transfer); for non-BLOCK transfers, we need to take in_buffer_size\n     *   instead,\n     * - maximum estimated response chunk size - here we use @ref\n     *   _avs_coap_get_next_outgoing_chunk_payload_size , assuming an arbitrary\n     *   response code and empty options list (effectively calculating the\n     *   biggest possible response payload chunk size).\n     *\n     * In case of notifications, we know the response headers in advance, so\n     * we use this information instead of dummy values. In this case, we will\n     * never receive any request payload, so @p request is NULL.\n     */\n    size_t max_request_chunk_size = 0;\n    if (request) {\n        max_request_chunk_size = _avs_coap_get_base(ctx)->in_buffer->capacity;\n#    ifdef WITH_AVS_COAP_BLOCK\n        avs_coap_option_block_t req_block1;\n        switch (avs_coap_options_get_block(&request->header.options,\n                                           AVS_COAP_BLOCK1, &req_block1)) {\n        case 0:\n            max_request_chunk_size =\n                    AVS_MIN(req_block1.size, max_request_chunk_size);\n            break;\n        case AVS_COAP_OPTION_MISSING:\n            break;\n        default:\n            AVS_UNREACHABLE(\"malformed options got through packet validation\");\n            return _avs_coap_err(AVS_COAP_ERR_MALFORMED_OPTIONS);\n        }\n#    endif // WITH_AVS_COAP_BLOCK\n    }\n\n    size_t max_response_chunk_size;\n    avs_coap_options_t empty_opts = avs_coap_options_create_empty(NULL, 0);\n    avs_error_t err = _avs_coap_get_first_outgoing_chunk_payload_size(\n            ctx,\n            response ? response->code : AVS_COAP_CODE_CONTENT,\n            response ? &response->options : &empty_opts,\n            &max_response_chunk_size);\n    if (avs_is_err(err)) {\n        LOG(DEBUG, _(\"get_next_outgoing_chunk_payload_size failed: \") \"%s\",\n            AVS_COAP_STRERROR(err));\n        return err;\n    }\n\n    avs_buffer_free(out_buffer);\n    if (avs_buffer_create(out_buffer, AVS_MAX(max_request_chunk_size,\n                                              max_response_chunk_size))) {\n        return avs_errno(AVS_ENOMEM);\n    }\n\n    return AVS_OK;\n}\n\nstatic int request_handler(avs_coap_request_ctx_t *request_ctx,\n                           avs_coap_exchange_id_t request_id,\n                           avs_coap_server_request_state_t state,\n                           const avs_coap_server_async_request_t *request,\n                           const avs_coap_observe_id_t *observe_id,\n                           void *streaming_req_ctx_) {\n    avs_coap_streaming_request_ctx_t *streaming_req_ctx =\n            (avs_coap_streaming_request_ctx_t *) streaming_req_ctx_;\n\n    (void) request_ctx;\n    (void) request_id;\n\n    if (state == AVS_COAP_SERVER_REQUEST_CLEANUP) {\n        // NOTE: while this may be called on either success or failure, it's\n        // the client that should be concerned about delivering the whole\n        // request or receiving the whole response. It should be fine to handle\n        // any kind of cleanup as success.\n        avs_buffer_free(&streaming_req_ctx->server_ctx.chunk_buffer);\n        avs_coap_options_cleanup(&streaming_req_ctx->request_header.options);\n        avs_coap_options_cleanup(&streaming_req_ctx->response_header.options);\n        streaming_req_ctx->server_ctx.exchange_id =\n                AVS_COAP_EXCHANGE_ID_INVALID;\n        streaming_req_ctx->server_ctx.state =\n                AVS_COAP_STREAMING_SERVER_FINISHED;\n        // return value is ignored for CLEANUP anyway\n        return 0;\n    }\n\n    if (streaming_req_ctx->error_response_code) {\n        return streaming_req_ctx->error_response_code;\n    }\n\n    if (request->payload_offset == 0) {\n        // This means that it's the first chunk of the request.\n        assert(!streaming_req_ctx->server_ctx.chunk_buffer);\n        assert(!streaming_req_ctx->request_has_observe_id);\n        if (observe_id) {\n            streaming_req_ctx->request_has_observe_id = true;\n            streaming_req_ctx->request_observe_id = *observe_id;\n        }\n\n        if (avs_is_err(init_chunk_buffer(\n                    streaming_req_ctx->server_ctx.coap_ctx,\n                    &streaming_req_ctx->server_ctx.chunk_buffer, request,\n                    NULL))) {\n            return AVS_COAP_CODE_INTERNAL_SERVER_ERROR;\n        }\n\n        assert(!streaming_req_ctx->request_header.options.allocated);\n        streaming_req_ctx->request_header.code = request->header.code;\n        if (avs_is_err(_avs_coap_options_copy_as_dynamic(\n                    &streaming_req_ctx->request_header.options,\n                    &request->header.options))) {\n            return AVS_COAP_CODE_INTERNAL_SERVER_ERROR;\n        }\n    }\n\n    if (!streaming_req_ctx->server_ctx.chunk_buffer) {\n        // The buffer was never created, meaning that we never received a\n        // request with payload_offset == 0.\n        return AVS_COAP_CODE_REQUEST_ENTITY_INCOMPLETE;\n    }\n    assert(request->payload_size\n           <= avs_buffer_space_left(\n                      streaming_req_ctx->server_ctx.chunk_buffer));\n    avs_buffer_append_bytes(streaming_req_ctx->server_ctx.chunk_buffer,\n                            request->payload, request->payload_size);\n    assert(request_ctx\n           == &_avs_coap_get_base(streaming_req_ctx->server_ctx.coap_ctx)\n                       ->request_ctx);\n    assert(streaming_req_ctx->server_ctx.state\n           == AVS_COAP_STREAMING_SERVER_RECEIVING_REQUEST);\n\n    switch (state) {\n    case AVS_COAP_SERVER_REQUEST_PARTIAL_CONTENT:\n        streaming_req_ctx->server_ctx.state =\n                AVS_COAP_STREAMING_SERVER_RECEIVED_REQUEST_CHUNK;\n        // This will be continued in ensure_data_is_available_to_read()\n        return 0;\n\n    case AVS_COAP_SERVER_REQUEST_RECEIVED: {\n#    ifdef WITH_AVS_COAP_BLOCK\n        avs_coap_option_block_t req_block2;\n        switch (avs_coap_options_get_block(&request->header.options,\n                                           AVS_COAP_BLOCK2, &req_block2)) {\n        case 0:\n            if (req_block2.seq_num != 0) {\n                LOG(WARNING, _(\"Server requested a response from the middle\"));\n                return AVS_COAP_CODE_SERVICE_UNAVAILABLE;\n            }\n            break;\n        case AVS_COAP_OPTION_MISSING:\n            break;\n        default:\n            AVS_UNREACHABLE(\"malformed options got through packet validation\");\n            return AVS_COAP_CODE_INTERNAL_SERVER_ERROR;\n        }\n#    endif // WITH_AVS_COAP_BLOCK\n        streaming_req_ctx->server_ctx.state =\n                AVS_COAP_STREAMING_SERVER_RECEIVED_LAST_REQUEST_CHUNK;\n        // This will be continued in ensure_data_is_available_to_read()\n        return 0;\n    }\n    case AVS_COAP_SERVER_REQUEST_CLEANUP:;\n    }\n\n    AVS_UNREACHABLE(\"invalid enum value\");\n    return -1;\n}\n\nstatic int handle_new_request(avs_coap_server_ctx_t *server_ctx,\n                              const avs_coap_request_header_t *request,\n                              void *streaming_req_ctx_) {\n    (void) request;\n\n    avs_coap_streaming_request_ctx_t *streaming_req_ctx =\n            (avs_coap_streaming_request_ctx_t *) streaming_req_ctx_;\n\n    assert(avs_coap_exchange_id_valid(streaming_req_ctx->server_ctx.exchange_id)\n           == !!streaming_req_ctx->server_ctx.chunk_buffer);\n    if (!streaming_req_ctx->server_ctx.chunk_buffer) {\n        if (!avs_coap_exchange_id_valid(\n                    (streaming_req_ctx->server_ctx.exchange_id =\n                             avs_coap_server_accept_async_request(\n                                     server_ctx, request_handler,\n                                     streaming_req_ctx)))) {\n            LOG(ERROR, _(\"accept_async_request failed\"));\n            return AVS_COAP_CODE_INTERNAL_SERVER_ERROR;\n        }\n        return 0;\n    } else {\n        // another request is being handled\n        return AVS_COAP_CODE_SERVICE_UNAVAILABLE;\n    }\n}\n\nstatic int reject_new_request(avs_coap_server_ctx_t *server_ctx,\n                              const avs_coap_request_header_t *request,\n                              void *args_) {\n    (void) server_ctx;\n    (void) request;\n    (void) args_;\n    return AVS_COAP_CODE_SERVICE_UNAVAILABLE;\n}\n\nstatic avs_error_t\nupdate_recv_timeout(avs_net_socket_t *socket,\n                    avs_time_duration_t next_timeout,\n                    avs_net_socket_opt_value_t *orig_recv_timeout) {\n    avs_net_socket_opt_value_t recv_timeout = {\n        .recv_timeout = next_timeout\n    };\n    avs_error_t err;\n    if (avs_is_err((err = avs_net_socket_get_opt(\n                            socket, AVS_NET_SOCKET_OPT_RECV_TIMEOUT,\n                            orig_recv_timeout)))\n            || avs_is_err((err = avs_net_socket_set_opt(\n                                   socket, AVS_NET_SOCKET_OPT_RECV_TIMEOUT,\n                                   recv_timeout)))) {\n        LOG(ERROR, _(\"could not set socket timeout\"));\n    }\n    return err;\n}\n\nstatic avs_error_t\ntry_wait_for_next_chunk_request(const avs_coap_streaming_server_ctx_t *ctx,\n                                const avs_error_t *abort_request_reason) {\n    avs_time_monotonic_t next_deadline =\n            _avs_coap_retry_or_request_expired_job(ctx->coap_ctx);\n\n    if (abort_request_reason && avs_is_err(*abort_request_reason)) {\n        return *abort_request_reason;\n    }\n\n    if (!avs_coap_exchange_id_valid(ctx->exchange_id)) {\n        // exchange failed e.g. due to not receiving request for another block\n        assert(ctx->state == AVS_COAP_STREAMING_SERVER_FINISHED);\n        return _avs_coap_err(AVS_COAP_ERR_TIMEOUT);\n    }\n\n    avs_net_socket_t *socket = _avs_coap_get_base(ctx->coap_ctx)->socket;\n    avs_time_duration_t recv_timeout =\n            avs_time_monotonic_diff(next_deadline, avs_time_monotonic_now());\n    assert(avs_time_duration_valid(recv_timeout));\n    avs_net_socket_opt_value_t orig_recv_timeout;\n    avs_error_t err =\n            update_recv_timeout(socket, recv_timeout, &orig_recv_timeout);\n    if (avs_is_ok(err)) {\n        // In a normal flow, this will receive the request for another BLOCK2\n        // chunk, and send the response. This does not require interaction with\n        // user code in the middle, so\n        // _avs_coap_async_incoming_packet_simple_handle() can be used, unlike\n        // handle_incoming_packet().\n        err = _avs_coap_async_incoming_packet_simple_handle_single(\n                ctx->coap_ctx, ctx->acquired_in_buffer,\n                ctx->acquired_in_buffer_size, reject_new_request, NULL);\n        if (err.category == AVS_ERRNO_CATEGORY && err.code == AVS_ETIMEDOUT) {\n            // timeout is expected; ignore\n            err = AVS_OK;\n        }\n\n        if (avs_is_err(avs_net_socket_set_opt(socket,\n                                              AVS_NET_SOCKET_OPT_RECV_TIMEOUT,\n                                              orig_recv_timeout))) {\n            LOG(ERROR, _(\"could not restore socket timeout\"));\n        }\n    }\n    return err;\n}\n\nstatic avs_error_t flush_response_chunk(avs_coap_streaming_request_ctx_t *ctx) {\n    if (avs_is_ok(ctx->err)) {\n        switch (ctx->server_ctx.state) {\n        case AVS_COAP_STREAMING_SERVER_RECEIVED_REQUEST_CHUNK:\n        case AVS_COAP_STREAMING_SERVER_RECEIVED_LAST_REQUEST_CHUNK:\n        case AVS_COAP_STREAMING_SERVER_SENDING_FIRST_RESPONSE_CHUNK:\n            // This call concludes the replication of\n            // _avs_coap_async_incoming_packet_simple_handle(). Note that in the\n            // AVS_COAP_STREAMING_SERVER_SENDING_FIRST_RESPONSE_CHUNK case,\n            // feed_payload_chunk() will be called here.\n            return _avs_coap_async_incoming_packet_send_response(\n                    ctx->server_ctx.coap_ctx, ctx->error_response_code);\n\n        case AVS_COAP_STREAMING_SERVER_SENDING_RESPONSE_CHUNK:\n            // For the non-first chunk, we are not in the middle of\n            // incoming_packet_handle logic, so we need to handle this case\n            // differently.\n            return try_wait_for_next_chunk_request(&ctx->server_ctx, NULL);\n\n        default:;\n        }\n    }\n\n    assert(avs_is_err(ctx->err));\n    LOG(ERROR, _(\"invalid state for flush_request_chunk(), aborting exchange\"));\n    avs_coap_exchange_cancel(ctx->server_ctx.coap_ctx,\n                             ctx->server_ctx.exchange_id);\n    return avs_is_err(ctx->err) ? ctx->err\n                                : _avs_coap_err(AVS_COAP_ERR_ASSERT_FAILED);\n}\n\nstatic avs_error_t\ncoap_write(avs_stream_t *stream_, const void *data, size_t *data_length) {\n    avs_coap_streaming_request_ctx_t *streaming_req_ctx =\n            (avs_coap_streaming_request_ctx_t *) stream_;\n    avs_error_t err = streaming_req_ctx->err;\n    if (avs_is_ok(err)\n            && !is_sending_response_chunk(&streaming_req_ctx->server_ctx)) {\n        err = try_enter_sending_state(streaming_req_ctx);\n    }\n    if (avs_is_err(err)) {\n        LOG(ERROR, _(\"CoAP server stream not ready for writing\"));\n        return err;\n    }\n\n    size_t bytes_written = 0;\n    while (bytes_written < *data_length) {\n        size_t bytes_to_write =\n                AVS_MIN(*data_length - bytes_written,\n                        avs_buffer_space_left(\n                                streaming_req_ctx->server_ctx.chunk_buffer));\n        avs_buffer_append_bytes(streaming_req_ctx->server_ctx.chunk_buffer,\n                                (const char *) data + bytes_written,\n                                bytes_to_write);\n        bytes_written += bytes_to_write;\n        // Once the buffer is full, flush_response_chunk() needs to be called.\n        if (!is_sending_response_chunk(&streaming_req_ctx->server_ctx)) {\n            return avs_errno(AVS_ENOBUFS);\n        }\n        if (avs_buffer_space_left(streaming_req_ctx->server_ctx.chunk_buffer)\n                        == 0\n                && avs_is_err((streaming_req_ctx->err = flush_response_chunk(\n                                       streaming_req_ctx)))) {\n            return streaming_req_ctx->err;\n        }\n    }\n    return AVS_OK;\n}\n\nstatic avs_error_t\nhandle_incoming_packet(avs_coap_streaming_request_ctx_t *streaming_req_ctx,\n                       avs_time_duration_t recv_timeout) {\n    assert(streaming_req_ctx->server_ctx.state\n           == AVS_COAP_STREAMING_SERVER_RECEIVING_REQUEST);\n    avs_net_socket_t *socket =\n            _avs_coap_get_base(streaming_req_ctx->server_ctx.coap_ctx)->socket;\n    avs_net_socket_opt_value_t orig_recv_timeout;\n    avs_error_t err =\n            update_recv_timeout(socket, recv_timeout, &orig_recv_timeout);\n    if (avs_is_err(err)) {\n        return err;\n    }\n    avs_coap_exchange_t *exchange = NULL;\n    // The possible cases to be handled here:\n    // - The first packet of the incoming request is received. In this case,\n    //   streaming_req_ctx->server_ctx.exchange_id is invalid,\n    //   handle_new_request() will actually call\n    //   _avs_coap_server_accept_async_request().\n    // - Any following packet of the incoming request is received.\n    // - Concurrect incoming request is received while another one is already\n    //   being handled. 5.03 Service Unavailable will be sent.\n    err = _avs_coap_async_incoming_packet_handle_single(\n            streaming_req_ctx->server_ctx.coap_ctx,\n            streaming_req_ctx->server_ctx.acquired_in_buffer,\n            streaming_req_ctx->server_ctx.acquired_in_buffer_size,\n            handle_new_request, streaming_req_ctx, &exchange);\n    if (exchange) {\n        // Note that we've just called _avs_coap_async_incoming_packet_handle(),\n        // not _avs_coap_async_incoming_packet_simple_handle(). That function\n        // was created in D8245 by splitting the old\n        // _avs_coap_async_handle_incoming_packet(), which was equivalent to the\n        // modern \"simple\" version, i.e. always called the request handler and\n        // sent the response immediately after receiving the incoming response.\n        // The whole reason why we needed the \"non-simple\" version is that in\n        // this streaming server API, we sometimes want to defer calling\n        // _avs_coap_async_incoming_packet_send_response() until we actually\n        // get the contents of that response from the user. And since we might\n        // be called from _within_ the user code (via coap_read()), we cannot\n        // _call_ user code, we need to _return_, which makes the whole logic\n        // somewhat complicated.\n        // Note: is_streaming_request will be false if a message pertaining to\n        // another exchange (some \"background\" async exchange) is received.\n        bool is_streaming_request =\n                (exchange->by_type.server.request_handler == request_handler);\n        // If is_streaming_request == true, this will call request_handler().\n        int call_result = _avs_coap_async_incoming_packet_call_request_handler(\n                streaming_req_ctx->server_ctx.coap_ctx, exchange);\n        if (!call_result && is_streaming_request\n                && has_received_request_chunk(&streaming_req_ctx->server_ctx)) {\n            // This is supposed to correspond with the \"This will be continued\n            // in ensure_data_is_available_to_read()\" cases, as commented in\n            // request_handler(). Note that this mean that the whole request\n            // handling logic is not finished yet, we're just waiting for\n            // interaction with the user. ensure_data_is_available_to_read() may\n            // be called from coap_read() or coap_peek() - see there for more\n            // information on what it does.\n            err = AVS_OK;\n        } else {\n            // Otherwise, we just replicate the logic of\n            // _avs_coap_async_incoming_packet_simple_handle().\n            err = _avs_coap_async_incoming_packet_send_response(\n                    streaming_req_ctx->server_ctx.coap_ctx, call_result);\n        }\n    }\n    if (avs_is_err(avs_net_socket_set_opt(\n                socket, AVS_NET_SOCKET_OPT_RECV_TIMEOUT, orig_recv_timeout))) {\n        LOG(ERROR, _(\"could not restore socket timeout\"));\n    }\n    return err;\n}\n\nstatic avs_error_t ensure_data_is_available_to_read(\n        avs_coap_streaming_request_ctx_t *streaming_req_ctx) {\n    if (avs_is_err(streaming_req_ctx->err)) {\n        return streaming_req_ctx->err;\n    }\n    // Note: there is a distinct\n    // AVS_COAP_STREAMING_SERVER_RECEIVED_LAST_REQUEST_CHUNK state, so if we\n    // enter the condition below, we know that the next packet to receive is\n    // supposed to be another chunk of request.\n    if (streaming_req_ctx->server_ctx.state\n                    == AVS_COAP_STREAMING_SERVER_RECEIVED_REQUEST_CHUNK\n            && avs_buffer_data_size(streaming_req_ctx->server_ctx.chunk_buffer)\n                           == 0) {\n        // All data from the previously received chunk has been consumed by the\n        // user. We now can send the response, concluding the replication of\n        // _avs_coap_async_incoming_packet_simple_handle() logic. We could do it\n        // earlier, but that would require further differentiating logic between\n        // the RECEIVED_REQUEST_CHUNK and RECEIVED_LAST_REQUEST_CHUNK cases.\n        // flush_response_chunk() may be called instead of this function, and we\n        // want the states in which the two functions may be called to be\n        // equivalent.\n        if (avs_is_err((streaming_req_ctx->err =\n                                _avs_coap_async_incoming_packet_send_response(\n                                        streaming_req_ctx->server_ctx.coap_ctx,\n                                        0)))) {\n            return streaming_req_ctx->err;\n        }\n        // Now we need to receive the next chunk of the request. This replicates\n        // the logic in handle_incoming_packet_with_acquired_in_buffer().\n        streaming_req_ctx->server_ctx.state =\n                AVS_COAP_STREAMING_SERVER_RECEIVING_REQUEST;\n        while (streaming_req_ctx->server_ctx.state\n               == AVS_COAP_STREAMING_SERVER_RECEIVING_REQUEST) {\n            avs_time_monotonic_t next_deadline =\n                    _avs_coap_retry_or_request_expired_job(\n                            streaming_req_ctx->server_ctx.coap_ctx);\n            if (streaming_req_ctx->server_ctx.state\n                    != AVS_COAP_STREAMING_SERVER_RECEIVING_REQUEST) {\n                // The exchange has been cleaned up by\n                // _avs_coap_retry_or_request_expired_job()\n                assert(streaming_req_ctx->server_ctx.state\n                       == AVS_COAP_STREAMING_SERVER_FINISHED);\n                streaming_req_ctx->err = _avs_coap_err(AVS_COAP_ERR_TIMEOUT);\n                return streaming_req_ctx->err;\n            }\n            avs_time_duration_t recv_timeout =\n                    avs_time_monotonic_diff(next_deadline,\n                                            avs_time_monotonic_now());\n            assert(avs_time_duration_valid(recv_timeout));\n            streaming_req_ctx->err =\n                    handle_incoming_packet(streaming_req_ctx, recv_timeout);\n            if (streaming_req_ctx->err.category == AVS_ERRNO_CATEGORY\n                    && streaming_req_ctx->err.code == AVS_ETIMEDOUT) {\n                // timeout is expected; ignore\n                streaming_req_ctx->err = AVS_OK;\n            } else if (avs_is_err(streaming_req_ctx->err)) {\n                return streaming_req_ctx->err;\n            }\n        }\n    }\n\n    if (avs_is_err(streaming_req_ctx->err)) {\n        return streaming_req_ctx->err;\n    } else if (!has_received_request_chunk(&streaming_req_ctx->server_ctx)) {\n        LOG(ERROR, _(\"CoAP streaming_server read called in invalid state\"));\n        return avs_errno(AVS_EBADF);\n    }\n    return AVS_OK;\n}\n\nstatic avs_error_t coap_read(avs_stream_t *stream_,\n                             size_t *out_bytes_read,\n                             bool *out_message_finished,\n                             void *buffer,\n                             size_t buffer_length) {\n    avs_coap_streaming_request_ctx_t *streaming_req_ctx =\n            (avs_coap_streaming_request_ctx_t *) stream_;\n    avs_error_t err = ensure_data_is_available_to_read(streaming_req_ctx);\n    if (avs_is_err(err)) {\n        return err;\n    }\n\n    size_t bytes_to_read = AVS_MIN(\n            buffer_length,\n            avs_buffer_data_size(streaming_req_ctx->server_ctx.chunk_buffer));\n    memcpy(buffer, avs_buffer_data(streaming_req_ctx->server_ctx.chunk_buffer),\n           bytes_to_read);\n    avs_buffer_consume_bytes(streaming_req_ctx->server_ctx.chunk_buffer,\n                             bytes_to_read);\n    if (out_bytes_read) {\n        *out_bytes_read = bytes_to_read;\n    }\n    if (out_message_finished) {\n        *out_message_finished =\n                (avs_buffer_data_size(\n                         streaming_req_ctx->server_ctx.chunk_buffer)\n                         == 0\n                 && streaming_req_ctx->server_ctx.state\n                            == AVS_COAP_STREAMING_SERVER_RECEIVED_LAST_REQUEST_CHUNK);\n    }\n    return AVS_OK;\n}\n\nstatic avs_error_t\ncoap_peek(avs_stream_t *stream_, size_t offset, char *out_value) {\n    avs_coap_streaming_request_ctx_t *streaming_req_ctx =\n            (avs_coap_streaming_request_ctx_t *) stream_;\n    avs_error_t err = ensure_data_is_available_to_read(streaming_req_ctx);\n    if (avs_is_err(err)) {\n        return err;\n    }\n\n    if (offset >= avs_buffer_data_size(\n                          streaming_req_ctx->server_ctx.chunk_buffer)) {\n        return AVS_EOF;\n    }\n    *out_value =\n            avs_buffer_data(streaming_req_ctx->server_ctx.chunk_buffer)[offset];\n    return AVS_OK;\n}\n\nstatic const avs_stream_v_table_t _AVS_COAP_STREAMING_REQUEST_CTX_VTABLE = {\n    .write_some = coap_write,\n    .read = coap_read,\n    .peek = coap_peek,\n    .extension_list = AVS_STREAM_V_TABLE_NO_EXTENSIONS\n};\n\nstatic avs_error_t handle_incoming_packet_with_acquired_in_buffer(\n        avs_coap_ctx_t *coap_ctx,\n        uint8_t *acquired_in_buffer,\n        size_t acquired_in_buffer_size,\n        avs_coap_streaming_request_handler_t *handle_request,\n        void *handler_arg) {\n    while (true) {\n        avs_coap_streaming_request_ctx_t streaming_req_ctx = {\n            .vtable = &_AVS_COAP_STREAMING_REQUEST_CTX_VTABLE,\n            .server_ctx = {\n                .coap_ctx = coap_ctx,\n                .acquired_in_buffer = acquired_in_buffer,\n                .acquired_in_buffer_size = acquired_in_buffer_size,\n                .state = AVS_COAP_STREAMING_SERVER_RECEIVING_REQUEST\n            }\n        };\n        // While this function \"handles incoming packet\" in a generic way, the\n        // only case it handles that actually requires some interaction with the\n        // user code is handling an incoming _request_. See inside for more\n        // details.\n        streaming_req_ctx.err = handle_incoming_packet(&streaming_req_ctx,\n                                                       AVS_TIME_DURATION_ZERO);\n        if (streaming_req_ctx.err.category == AVS_ERRNO_CATEGORY\n                && streaming_req_ctx.err.code == AVS_ETIMEDOUT) {\n            // Timeout - as the contract of this function does not mandate that\n            // we must always receive anything, we just return success.\n            return AVS_OK;\n        } else if (avs_is_ok(streaming_req_ctx.err)\n                   && streaming_req_ctx.server_ctx.chunk_buffer) {\n            if (has_received_request_chunk(&streaming_req_ctx.server_ctx)) {\n                // We have successfully received some data, so passing\n                // AVS_OK as error code makes sense here.\n                // The user-provided request handler is supposed to call\n                // coap_read(), possibly followed by coap_write().\n                streaming_req_ctx.error_response_code = handle_request(\n                        &streaming_req_ctx,\n                        &streaming_req_ctx.request_header,\n                        (avs_stream_t *) &streaming_req_ctx,\n                        streaming_req_ctx.request_has_observe_id\n                                ? &streaming_req_ctx.request_observe_id\n                                : NULL,\n                        handler_arg);\n                // Update state if the response has been set up, but\n                // coap_write() has not been called\n                try_enter_sending_state(&streaming_req_ctx);\n                if (!streaming_req_ctx.error_response_code\n                        && has_received_request_chunk(\n                                   &streaming_req_ctx.server_ctx)) {\n                    // request handler returned success, but\n                    // _avs_coap_streaming_setup_response() has not been\n                    // successfully called\n                    streaming_req_ctx.error_response_code =\n                            AVS_COAP_CODE_INTERNAL_SERVER_ERROR;\n                }\n            }\n            // We might have some data buffered to be sent, but not send yet -\n            // send it here. This is done in a loop, because we might end up\n            // receiving messages unrelated to this exchange in between.\n            while (streaming_req_ctx.server_ctx.state\n                   != AVS_COAP_STREAMING_SERVER_FINISHED) {\n                streaming_req_ctx.err =\n                        flush_response_chunk(&streaming_req_ctx);\n            }\n        }\n\n        // make sure everything is cleaned up\n        assert(!streaming_req_ctx.server_ctx.chunk_buffer);\n        assert(!streaming_req_ctx.request_header.options.allocated);\n        assert(!streaming_req_ctx.response_header.options.allocated);\n        if (avs_is_err(streaming_req_ctx.err)) {\n            // error\n            return streaming_req_ctx.err;\n        }\n\n        if (_avs_coap_socket_definitely_exhausted(coap_ctx)) {\n            // We can conclusively say that the socket is already exhausted,\n            // so no need to try receiving more packets.\n            return AVS_OK;\n        }\n        // Otherwise we loop again to make sure the socket is exhausted.\n    }\n}\n\navs_error_t avs_coap_streaming_handle_incoming_packet(\n        avs_coap_ctx_t *coap_ctx,\n        avs_coap_streaming_request_handler_t *handle_request,\n        void *handler_arg) {\n    uint8_t *acquired_in_buffer;\n    size_t acquired_in_buffer_size;\n    avs_error_t err = _avs_coap_in_buffer_acquire(coap_ctx, &acquired_in_buffer,\n                                                  &acquired_in_buffer_size);\n    if (avs_is_ok(err)) {\n        err = handle_incoming_packet_with_acquired_in_buffer(\n                coap_ctx, acquired_in_buffer, acquired_in_buffer_size,\n                handle_request, handler_arg);\n        _avs_coap_in_buffer_release(coap_ctx);\n    }\n    return err;\n}\n\n#    ifdef WITH_AVS_COAP_OBSERVE\navs_error_t avs_coap_observe_streaming_start(\n        avs_coap_streaming_request_ctx_t *ctx,\n        avs_coap_observe_id_t id,\n        avs_coap_observe_cancel_handler_t *cancel_handler,\n        void *handler_arg) {\n    return avs_coap_observe_async_start(\n            &_avs_coap_get_base(ctx->server_ctx.coap_ctx)->request_ctx, id,\n            cancel_handler, handler_arg);\n}\n\ntypedef struct {\n    const avs_stream_v_table_t *vtable;\n    avs_coap_streaming_server_ctx_t server_ctx;\n    avs_coap_observe_id_t observe_id;\n    const avs_coap_response_header_t *response_header;\n    avs_coap_notify_reliability_hint_t reliability_hint;\n    bool required_receiving;\n    avs_error_t err;\n} notify_streaming_ctx_t;\n\nstatic void notify_delivery_status_handler(avs_coap_ctx_t *ctx,\n                                           avs_error_t err,\n                                           void *arg) {\n    (void) ctx;\n\n    notify_streaming_ctx_t *notify_streaming_ctx =\n            (notify_streaming_ctx_t *) arg;\n    notify_streaming_ctx->server_ctx.state = AVS_COAP_STREAMING_SERVER_FINISHED;\n    if (avs_is_ok(notify_streaming_ctx->err) && avs_is_err(err)) {\n        notify_streaming_ctx->err = err;\n    }\n}\n\nstatic avs_error_t flush_notify_chunk(notify_streaming_ctx_t *ctx) {\n    switch (ctx->server_ctx.state) {\n    case AVS_COAP_STREAMING_SERVER_SENDING_FIRST_RESPONSE_CHUNK: {\n        // We need to send the first (or only) notification chunk, so we need\n        // to create the underlying async exchange.\n        // feed_payload_chunk() will be called during this call;\n        // notify_delivery_status_handler() may be called if this is a\n        // single-block, non-confirmable notification.\n        avs_error_t err = avs_coap_notify_async(\n                ctx->server_ctx.coap_ctx, &ctx->server_ctx.exchange_id,\n                ctx->observe_id, ctx->response_header, ctx->reliability_hint,\n                feed_payload_chunk, &ctx->server_ctx,\n                notify_delivery_status_handler, ctx);\n        if (avs_is_err(err)) {\n            ctx->server_ctx.state = AVS_COAP_STREAMING_SERVER_FINISHED;\n            ctx->err = err;\n        } else if (avs_coap_exchange_id_valid(ctx->server_ctx.exchange_id)) {\n            // If we have the exchange ID here, it means that it is either a\n            // Confirmable notification, and/or requires a blockwise transfer.\n            // Either way, we'll need to flush the socket buffer afterwards.\n            ctx->required_receiving = true;\n        }\n        return err;\n    }\n    case AVS_COAP_STREAMING_SERVER_SENDING_RESPONSE_CHUNK:\n    case AVS_COAP_STREAMING_SERVER_SENT_LAST_RESPONSE_CHUNK:\n        // We need to send some non-first notification chunk. We are being\n        // called either from notify_write(), or just after payload writer;\n        // anyway, the logic we are in is all about writing. To send another\n        // chunk, we need to first receive a BLOCK2 request for the next block.\n        // try_wait_for_next_chunk_request() will actually also call\n        // feed_payload_chunk() and send that chunk. See comments inside for\n        // details.\n        ctx->err = try_wait_for_next_chunk_request(&ctx->server_ctx, &ctx->err);\n        return ctx->err;\n\n    default:\n        AVS_UNREACHABLE(\"invalid state for flush_notify_chunk()\");\n        return _avs_coap_err(AVS_COAP_ERR_ASSERT_FAILED);\n    }\n}\n\nstatic avs_error_t\nnotify_write(avs_stream_t *stream_, const void *data, size_t *data_length) {\n    notify_streaming_ctx_t *notify_streaming_ctx =\n            (notify_streaming_ctx_t *) stream_;\n    if (!is_sending_response_chunk(&notify_streaming_ctx->server_ctx)) {\n        LOG(ERROR, _(\"CoAP notification stream not ready for writing\"));\n        return avs_errno(AVS_EBADF);\n    }\n    if (avs_is_err(notify_streaming_ctx->err)) {\n        LOG(ERROR, _(\"CoAP notification stream already in a failed state\"));\n        return notify_streaming_ctx->err;\n    }\n\n    size_t bytes_written = 0;\n    while (bytes_written < *data_length) {\n        size_t bytes_to_write =\n                AVS_MIN(*data_length - bytes_written,\n                        avs_buffer_space_left(\n                                notify_streaming_ctx->server_ctx.chunk_buffer));\n        avs_buffer_append_bytes(notify_streaming_ctx->server_ctx.chunk_buffer,\n                                (const char *) data + bytes_written,\n                                bytes_to_write);\n        bytes_written += bytes_to_write;\n        // Let's send the notification packet once the buffer is filled.\n        if (!is_sending_response_chunk(&notify_streaming_ctx->server_ctx)) {\n            return avs_errno(AVS_ENOBUFS);\n        }\n        if (avs_buffer_space_left(notify_streaming_ctx->server_ctx.chunk_buffer)\n                == 0) {\n            avs_error_t err = flush_notify_chunk(notify_streaming_ctx);\n            if (avs_is_err(err)) {\n                return err;\n            }\n        }\n    }\n    return AVS_OK;\n}\n\nstatic const avs_stream_v_table_t _AVS_COAP_STREAMING_NOTIFY_CTX_VTABLE = {\n    .write_some = notify_write,\n    .extension_list = AVS_STREAM_V_TABLE_NO_EXTENSIONS\n};\n\navs_error_t\navs_coap_notify_streaming(avs_coap_ctx_t *ctx,\n                          avs_coap_observe_id_t observe_id,\n                          const avs_coap_response_header_t *response_header,\n                          avs_coap_notify_reliability_hint_t reliability_hint,\n                          avs_coap_streaming_writer_t *write_payload,\n                          void *write_payload_arg) {\n    notify_streaming_ctx_t notify_streaming_ctx = {\n        .vtable = &_AVS_COAP_STREAMING_NOTIFY_CTX_VTABLE,\n        .server_ctx = {\n            .coap_ctx = ctx,\n            .state = AVS_COAP_STREAMING_SERVER_SENDING_FIRST_RESPONSE_CHUNK\n        },\n        .observe_id = observe_id,\n        .response_header = response_header,\n        .reliability_hint = reliability_hint\n    };\n    notify_streaming_ctx.err = _avs_coap_in_buffer_acquire(\n            ctx, &notify_streaming_ctx.server_ctx.acquired_in_buffer,\n            &notify_streaming_ctx.server_ctx.acquired_in_buffer_size);\n    if (avs_is_err(notify_streaming_ctx.err)) {\n        return notify_streaming_ctx.err;\n    }\n\n    if (avs_is_err((notify_streaming_ctx.err = init_chunk_buffer(\n                            ctx,\n                            &notify_streaming_ctx.server_ctx.chunk_buffer,\n                            NULL,\n                            response_header)))) {\n        goto finish;\n    }\n\n    if (write_payload) {\n        // write_payload() is expected to call notify_write(),\n        // so see there for what happens next.\n        int write_result = write_payload((avs_stream_t *) &notify_streaming_ctx,\n                                         write_payload_arg);\n        if (write_result) {\n            LOG(DEBUG,\n                _(\"unable to write notification payload, result = \") \"%d\",\n                write_result);\n            if (avs_is_ok(notify_streaming_ctx.err)) {\n                notify_streaming_ctx.err =\n                        _avs_coap_err(AVS_COAP_ERR_PAYLOAD_WRITER_FAILED);\n            }\n        }\n    }\n    // If notify_write() has either not been called at all, or its calls have\n    // not filled the buffer enough to send a BLOCK1 request, we need to\n    // actually send the notification here.\n    while (avs_is_ok(notify_streaming_ctx.err)\n           && notify_streaming_ctx.server_ctx.state\n                      != AVS_COAP_STREAMING_SERVER_FINISHED) {\n        notify_streaming_ctx.err = flush_notify_chunk(&notify_streaming_ctx);\n    }\n\n    if (avs_is_err(notify_streaming_ctx.err)\n            && avs_coap_exchange_id_valid(\n                       notify_streaming_ctx.server_ctx.exchange_id)) {\n        LOG(DEBUG, _(\"unable to send notification, result = \") \"%s\",\n            AVS_COAP_STRERROR(notify_streaming_ctx.err));\n        if (notify_streaming_ctx.server_ctx.state\n                != AVS_COAP_STREAMING_SERVER_FINISHED) {\n            avs_coap_exchange_cancel(\n                    ctx, notify_streaming_ctx.server_ctx.exchange_id);\n        }\n    }\n    assert(notify_streaming_ctx.server_ctx.state\n           == AVS_COAP_STREAMING_SERVER_FINISHED);\n\nfinish:\n    avs_buffer_free(&notify_streaming_ctx.server_ctx.chunk_buffer);\n    if (avs_is_ok(notify_streaming_ctx.err)\n            && notify_streaming_ctx.required_receiving) {\n        // We needed to receive some data from the socket (most likely an ACK\n        // for a confirmable notification), so we also need to make sure that\n        // all data that might be internally buffered in the socket structures\n        // is exhausted, so that the user can safely use select()/poll().\n        notify_streaming_ctx.err =\n                _avs_coap_async_incoming_packet_exhaust_socket(\n                        ctx, notify_streaming_ctx.server_ctx.acquired_in_buffer,\n                        notify_streaming_ctx.server_ctx.acquired_in_buffer_size,\n                        reject_new_request, NULL);\n    }\n    _avs_coap_in_buffer_release(ctx);\n    return notify_streaming_ctx.err;\n}\n#    endif // WITH_AVS_COAP_OBSERVE\n\n#endif // WITH_AVS_COAP_STREAMING_API\n"
  },
  {
    "path": "deps/avs_coap/src/streaming/avs_coap_streaming_server.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef COAP_SRC_STREAMING_SERVER_H\n#define COAP_SRC_STREAMING_SERVER_H\n\n#include <avsystem/commons/avs_buffer.h>\n#include <avsystem/commons/avs_stream_v_table.h>\n\n#include <avsystem/coap/async_server.h>\n#include <avsystem/coap/observe.h>\n#include <avsystem/coap/streaming.h>\n\n#include \"async/avs_coap_async_server.h\"\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\ntypedef enum {\n    AVS_COAP_STREAMING_SERVER_RECEIVING_REQUEST,\n    AVS_COAP_STREAMING_SERVER_RECEIVED_REQUEST_CHUNK,\n    AVS_COAP_STREAMING_SERVER_RECEIVED_LAST_REQUEST_CHUNK,\n    AVS_COAP_STREAMING_SERVER_SENDING_FIRST_RESPONSE_CHUNK,\n    AVS_COAP_STREAMING_SERVER_SENDING_RESPONSE_CHUNK,\n    AVS_COAP_STREAMING_SERVER_SENT_LAST_RESPONSE_CHUNK,\n    AVS_COAP_STREAMING_SERVER_FINISHED\n} avs_coap_streaming_server_state_t;\n\ntypedef struct {\n    avs_coap_ctx_t *coap_ctx;\n    uint8_t *acquired_in_buffer;\n    size_t acquired_in_buffer_size;\n\n    avs_coap_exchange_id_t exchange_id;\n    avs_coap_streaming_server_state_t state;\n    size_t expected_next_outgoing_chunk_offset;\n\n    /**\n     * Depending on stream state, this buffer may be used either for *request*\n     * payload (RECEIVING_REQUEST, RECEIVING_REQUEST_CHUNK,\n     * RECEIVED_LAST_REQUEST_CHUNK) or *response* payload\n     * (SENDING_FIRST_RESPONSE_CHUNK, SENDING_RESPONSE_CHUNK,\n     * SENT_LAST_RESPONSE_CHUNK)\n     */\n    avs_buffer_t *chunk_buffer;\n} avs_coap_streaming_server_ctx_t;\n\nstruct avs_coap_streaming_request_ctx {\n    const avs_stream_v_table_t *vtable;\n\n    avs_coap_streaming_server_ctx_t server_ctx;\n\n    /**\n     * CoAP code directly returned from the user handler, to be used in an\n     * empty response.\n     */\n    int error_response_code;\n    avs_error_t err;\n\n    bool request_has_observe_id;\n    avs_coap_observe_id_t request_observe_id;\n\n    avs_coap_request_header_t request_header;\n    avs_coap_response_header_t response_header;\n};\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif // COAP_SRC_STREAMING_SERVER_H\n"
  },
  {
    "path": "deps/avs_coap/src/tcp/avs_coap_tcp_ctx.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <inttypes.h>\n\n#include <avs_coap_init.h>\n\n#ifdef WITH_AVS_COAP_TCP\n\n#    include <avsystem/coap/tcp.h>\n#    include <avsystem/commons/avs_buffer.h>\n#    include <avsystem/commons/avs_errno.h>\n#    include <avsystem/commons/avs_socket.h>\n#    include <avsystem/commons/avs_utils.h>\n\n#    include \"avs_coap_code_utils.h\"\n#    include \"options/avs_coap_iterator.h\"\n\n#    define MODULE_NAME coap_tcp\n#    include <avs_coap_x_log_config.h>\n\n#    include \"avs_coap_tcp_ctx.h\"\n\n#    include \"avs_coap_common_utils.h\"\n#    include \"tcp/avs_coap_tcp_pending_requests.h\"\n#    include \"tcp/avs_coap_tcp_signaling.h\"\n\n// Base value defined in RFC8323\n#    define CSM_MAX_MESSAGE_SIZE_BASE_VALUE 1152\n\n#    ifdef WITH_AVS_COAP_DIAGNOSTIC_MESSAGES\n#        define SET_DIAGNOSTIC_MESSAGE(Ctx, Message) \\\n            ((Ctx->err_details) = (Message))\n#    else\n#        define SET_DIAGNOSTIC_MESSAGE(Ctx, Message)\n#    endif // WITH_AVS_COAP_DIAGNOSTIC_MESSAGES\n\n#    ifdef WITH_AVS_COAP_DIAGNOSTIC_MESSAGES\n#        define GET_DIAGNOSTIC_MESSAGE(Ctx) (Ctx->err_details)\n#    else\n#        define GET_DIAGNOSTIC_MESSAGE(Ctx) NULL\n#    endif // WITH_AVS_COAP_DIAGNOSTIC_MESSAGES\n\nVISIBILITY_SOURCE_BEGIN\n\n// Maximum length of the entire message (all chunks received, etc.) we are able\n// to receive, if we had enough memory (note that this is not related to input\n// buffer size, because we can actually receive packets in chunks over TCP).\nstatic const uint32_t INCOMING_MESSAGE_MAX_TOTAL_SIZE =\n        (uint32_t) AVS_MIN(SIZE_MAX, UINT32_MAX);\n\nstatic void log_tcp_msg_summary(const char *info,\n                                const avs_coap_borrowed_msg_t *msg) {\n    char observe_str[24] = \"\";\n#    ifdef WITH_AVS_COAP_OBSERVE\n    uint32_t observe;\n    if (avs_coap_options_get_observe(&msg->options, &observe) == 0) {\n        snprintf(observe_str, sizeof(observe_str), \", Observe %\" PRIu32,\n                 observe);\n    }\n#    endif // WITH_AVS_COAP_OBSERVE\n\n#    ifdef WITH_AVS_COAP_BLOCK\n    avs_coap_option_block_t block1;\n    bool has_block1 =\n            (avs_coap_options_get_block(&msg->options, AVS_COAP_BLOCK1, &block1)\n             == 0);\n    avs_coap_option_block_string_buf_t block1_str_buf = { \"\" };\n    if (has_block1) {\n        _avs_coap_option_block_string(&block1_str_buf, &block1);\n    }\n\n    avs_coap_option_block_t block2;\n    bool has_block2 =\n            (avs_coap_options_get_block(&msg->options, AVS_COAP_BLOCK2, &block2)\n             == 0);\n    avs_coap_option_block_string_buf_t block2_str_buf = { \"\" };\n    if (has_block2) {\n        _avs_coap_option_block_string(&block2_str_buf, &block2);\n    }\n\n    LOG(DEBUG, \"%s: %s (token: %s)%s%s%s%s%s, payload: %u B\", info,\n        AVS_COAP_CODE_STRING(msg->code), AVS_COAP_TOKEN_HEX(&msg->token),\n        has_block1 ? \", \" : \"\", block1_str_buf.str, has_block2 ? \", \" : \"\",\n        block2_str_buf.str, observe_str, (unsigned) msg->total_payload_size);\n#    else  // WITH_AVS_COAP_BLOCK\n    LOG(DEBUG, \"%s: %s (token: %s)%s, payload: %u B\", info,\n        AVS_COAP_CODE_STRING(msg->code), AVS_COAP_TOKEN_HEX(&msg->token),\n        observe_str, (unsigned) msg->total_payload_size);\n#    endif // WITH_AVS_COAP_BLOCK\n}\n\navs_error_t _avs_coap_tcp_send_msg(avs_coap_tcp_ctx_t *ctx,\n                                   const avs_coap_borrowed_msg_t *msg) {\n    void *out_buf = avs_shared_buffer_acquire(ctx->base.out_buffer);\n    size_t buf_size = ctx->base.out_buffer->capacity;\n    size_t msg_size;\n    avs_error_t err =\n            _avs_coap_tcp_serialize_msg(msg, out_buf, buf_size, &msg_size);\n\n    if (avs_is_ok(err)) {\n        log_tcp_msg_summary(\"send\", msg);\n        if (avs_is_err((err = avs_net_socket_send(ctx->base.socket, out_buf,\n                                                  msg_size)))) {\n            LOG(DEBUG, _(\"send failed: \") \"%s\", AVS_COAP_STRERROR(err));\n            SET_DIAGNOSTIC_MESSAGE(ctx, \"send failed\");\n        }\n    }\n\n    avs_shared_buffer_release(ctx->base.out_buffer);\n    return err;\n}\n\nstatic inline void handle_response(avs_coap_tcp_ctx_t *ctx,\n                                   const avs_coap_tcp_cached_msg_t *msg) {\n    const avs_coap_tcp_pending_request_status_t result =\n            msg->remaining_bytes ? PENDING_REQUEST_STATUS_PARTIAL_CONTENT\n                                 : PENDING_REQUEST_STATUS_COMPLETED;\n    _avs_coap_tcp_handle_pending_request(ctx, &msg->content, result, AVS_OK);\n}\n\nstatic avs_error_t send_simple_msg(avs_coap_tcp_ctx_t *ctx,\n                                   uint8_t code,\n                                   avs_coap_token_t *token,\n                                   const char *payload) {\n    assert(token);\n    size_t payload_size = payload ? strlen(payload) : 0;\n    avs_coap_borrowed_msg_t msg = {\n        .code = code,\n        .token = *token,\n        .payload = payload,\n        .payload_size = payload_size,\n        .total_payload_size = payload_size\n    };\n\n    return _avs_coap_tcp_send_msg(ctx, &msg);\n}\n\nstatic avs_error_t handle_cached_msg(avs_coap_tcp_ctx_t *ctx,\n                                     avs_coap_borrowed_msg_t *out_request) {\n    avs_coap_tcp_cached_msg_t *msg = &ctx->cached_msg;\n\n    const uint8_t code = msg->content.code;\n\n    LOG(DEBUG,\n        _(\"handling incoming \") \"%s\" _(\", token: \") \"%s\" _(\n                \", payload: \") \"%u\" _(\" B\"),\n        AVS_COAP_CODE_STRING(ctx->cached_msg.content.code),\n        AVS_COAP_TOKEN_HEX(&ctx->cached_msg.content.token),\n        (unsigned) ctx->cached_msg.content.payload_size);\n\n    if (_avs_coap_code_is_signaling_message(code)\n            && (!avs_time_monotonic_valid(ctx->peer_csm.recv_deadline)\n                || msg->content.code == AVS_COAP_CODE_CSM)) {\n        return _avs_coap_tcp_handle_signaling_message(ctx, &ctx->peer_csm,\n                                                      &msg->content);\n    }\n\n    if (avs_time_monotonic_valid(ctx->peer_csm.recv_deadline)) {\n        LOG(DEBUG, _(\"CSM not received as the first message on connection\"));\n        return _avs_coap_err(AVS_COAP_ERR_TCP_CSM_NOT_RECEIVED);\n    }\n\n    if (avs_coap_code_is_request(code)) {\n        if (out_request && !msg->ignore_request) {\n            AVS_ASSERT(msg->content.payload_offset + msg->content.payload_size\n                               <= msg->content.total_payload_size,\n                       \"bug: sum of payload_offset and payload_size should not \"\n                       \"be greater than total_payload_length\");\n            *out_request = ctx->cached_msg.content;\n        }\n    } else if (avs_coap_code_is_response(code)) {\n        handle_response(ctx, msg);\n        return AVS_OK;\n    } else if (code == AVS_COAP_CODE_EMPTY) {\n        // \"Empty messages (Code 0.00) can always be sent and MUST be ignored by\n        //  the recipient. This provides a basic keepalive function that can\n        //  refresh NAT bindings.\"\n        if (msg->content.options.size || msg->content.payload_size) {\n            LOG(DEBUG, _(\"non-empty message with Code 0.00\"));\n        }\n    } else {\n        LOG(DEBUG, _(\"Unexpected CoAP code: \") \"%s\" _(\", ignoring\"),\n            AVS_COAP_CODE_STRING(code));\n    }\n    return AVS_OK;\n}\n\nstatic avs_error_t set_recv_timeout(avs_net_socket_t *socket,\n                                    avs_time_duration_t timeout) {\n    avs_error_t err =\n            avs_net_socket_set_opt(socket, AVS_NET_SOCKET_OPT_RECV_TIMEOUT,\n                                   (avs_net_socket_opt_value_t) {\n                                       .recv_timeout = timeout\n                                   });\n    if (avs_is_err(err)) {\n        LOG(ERROR, _(\"failed to set recv timeout\"));\n    }\n    return err;\n}\n\nstatic avs_error_t get_recv_timeout(avs_net_socket_t *socket,\n                                    avs_time_duration_t *out_timeout) {\n    avs_net_socket_opt_value_t socket_timeout;\n    avs_error_t err =\n            avs_net_socket_get_opt(socket, AVS_NET_SOCKET_OPT_RECV_TIMEOUT,\n                                   &socket_timeout);\n    if (avs_is_err(err)) {\n        LOG(ERROR, _(\"failed to get recv timeout\"));\n    } else {\n        *out_timeout = socket_timeout.recv_timeout;\n    }\n    return err;\n}\n\nstatic avs_error_t coap_tcp_recv_data(avs_coap_tcp_ctx_t *ctx,\n                                      void *buffer,\n                                      size_t buffer_size,\n                                      size_t *out_bytes_received) {\n    if (!buffer_size) {\n        LOG(ERROR, _(\"no space in input buffer\"));\n        return _avs_coap_err(AVS_COAP_ERR_MESSAGE_TOO_BIG);\n    }\n\n    avs_error_t err =\n            avs_net_socket_receive(ctx->base.socket, out_bytes_received, buffer,\n                                   buffer_size);\n\n    if (avs_is_err(err)) {\n        LOG(TRACE, _(\"recv failed: \") \"%s\", AVS_COAP_STRERROR(err));\n        return err;\n    }\n\n    if (avs_is_err(err = set_recv_timeout(ctx->base.socket,\n                                          AVS_TIME_DURATION_ZERO))) {\n        return err;\n    }\n\n    if (!*out_bytes_received) {\n        return _avs_coap_err(AVS_COAP_ERR_TCP_CONN_CLOSED);\n    }\n\n    return AVS_OK;\n}\n\nstatic inline void opt_cache_finish_message(avs_coap_tcp_opt_cache_t *cache) {\n    avs_buffer_reset(cache->buffer);\n    cache->state = AVS_COAP_TCP_OPT_CACHE_STATE_RECEIVING_HEADER;\n}\n\nstatic void finish_message_handling(avs_coap_tcp_ctx_t *ctx) {\n    memset(&ctx->cached_msg, 0, sizeof(ctx->cached_msg));\n    opt_cache_finish_message(&ctx->opt_cache);\n}\n\nstatic inline void send_abort(avs_coap_tcp_ctx_t *ctx) {\n    if (!ctx->aborted) {\n        ctx->aborted = true;\n        (void) send_simple_msg(ctx, AVS_COAP_CODE_ABORT,\n                               &ctx->cached_msg.content.token,\n                               GET_DIAGNOSTIC_MESSAGE(ctx));\n    }\n}\n\nstatic void coap_tcp_cleanup(avs_coap_ctx_t *ctx_) {\n    avs_coap_tcp_ctx_t *ctx = (avs_coap_tcp_ctx_t *) ctx_;\n\n    // TODO T2262\n    // Send the Release message and wait for completion of pending requests.\n    // \"The peer responding to the Release message SHOULD delay the closing of\n    //  the connection until it has responded to all requests received by it\n    //  before the Release message.\"\n    _avs_coap_tcp_cancel_all_pending_requests(ctx);\n    avs_buffer_free(&ctx->opt_cache.buffer);\n    avs_free(ctx);\n}\n\nstatic size_t max_payload_size(size_t buffer_capacity,\n                               size_t csm_max_message_size,\n                               size_t token_size,\n                               size_t options_size) {\n    // TODO: use actual header length\n    size_t length_until_payload = token_size + options_size\n                                  + _AVS_COAP_TCP_MAX_HEADER_LENGTH\n                                  + sizeof(AVS_COAP_PAYLOAD_MARKER);\n    if (buffer_capacity <= length_until_payload\n            || csm_max_message_size <= length_until_payload) {\n        return 0;\n    }\n    size_t buffer_space = buffer_capacity - length_until_payload;\n    size_t peer_capability = csm_max_message_size - length_until_payload;\n    return AVS_MIN(buffer_space, peer_capability);\n}\n\nstatic size_t\ncoap_tcp_max_outgoing_payload_size(avs_coap_ctx_t *ctx_,\n                                   size_t token_size,\n                                   const avs_coap_options_t *options,\n                                   uint8_t code) {\n    (void) code;\n    avs_coap_tcp_ctx_t *ctx = (avs_coap_tcp_ctx_t *) ctx_;\n    return max_payload_size(ctx->base.out_buffer->capacity,\n                            ctx->peer_csm.max_message_size, token_size,\n                            options ? options->size : 0);\n}\n\nstatic size_t\ncoap_tcp_max_incoming_payload_size(avs_coap_ctx_t *ctx_,\n                                   size_t token_size,\n                                   const avs_coap_options_t *options,\n                                   uint8_t code) {\n    (void) code;\n    avs_coap_tcp_ctx_t *ctx = (avs_coap_tcp_ctx_t *) ctx_;\n    return max_payload_size(ctx->base.in_buffer->capacity,\n                            INCOMING_MESSAGE_MAX_TOTAL_SIZE, token_size,\n                            options ? options->size : 0);\n}\n\nstatic void coap_tcp_ignore_current_request(avs_coap_ctx_t *ctx_,\n                                            const avs_coap_token_t *token) {\n    avs_coap_tcp_ctx_t *ctx = (avs_coap_tcp_ctx_t *) ctx_;\n\n    // Ensure that it's currently processed request.\n    if (avs_coap_token_equal(&ctx->cached_msg.content.token, token)\n            && avs_coap_code_is_request(ctx->cached_msg.content.code)) {\n        ctx->cached_msg.ignore_request = true;\n    }\n}\n\n/**\n * Note: tries to send Abort message if network error occured. It may not be\n *       successfully sent though.\n */\nstatic avs_error_t\ncoap_tcp_send_message(avs_coap_ctx_t *ctx_,\n                      const avs_coap_borrowed_msg_t *msg,\n                      avs_coap_send_result_handler_t *send_result_handler,\n                      void *send_result_handler_arg) {\n    avs_coap_tcp_ctx_t *ctx = (avs_coap_tcp_ctx_t *) ctx_;\n    if (ctx->aborted) {\n        LOG(ERROR,\n            _(\"Abort message was sent and context shouldn't be used anymore\"));\n        return _avs_coap_err(AVS_COAP_ERR_TCP_ABORT_SENT);\n    }\n    SET_DIAGNOSTIC_MESSAGE(ctx, NULL);\n\n    AVS_LIST(avs_coap_tcp_pending_request_t) *req = NULL;\n    if (send_result_handler && avs_coap_code_is_request(msg->code)) {\n        avs_error_t err =\n                _avs_coap_tcp_create_pending_request(ctx, &req, &msg->token,\n                                                     send_result_handler,\n                                                     send_result_handler_arg);\n        assert(avs_is_ok(err) == !!req);\n        if (avs_is_err(err)) {\n            return err;\n        }\n    } else if (avs_coap_code_is_response(msg->code)) {\n        // Response may be sent before receiving the entire request, don't pass\n        // more payload chunks to the upper layer.\n        coap_tcp_ignore_current_request(ctx_, &msg->token);\n    }\n\n    avs_error_t err = _avs_coap_tcp_send_msg(ctx, msg);\n    if (avs_is_ok(err)) {\n        if (send_result_handler && !avs_coap_code_is_request(msg->code)) {\n            send_result_handler(ctx_, AVS_COAP_SEND_RESULT_OK, AVS_OK, NULL,\n                                send_result_handler_arg);\n        }\n    } else {\n        if (req) {\n            _avs_coap_tcp_remove_pending_request(req);\n        }\n        send_abort(ctx);\n    }\n    return err;\n}\n\nstatic void coap_tcp_abort_delivery(avs_coap_ctx_t *ctx_,\n                                    avs_coap_exchange_direction_t direction,\n                                    const avs_coap_token_t *token,\n                                    avs_coap_send_result_t result,\n                                    avs_error_t fail_err) {\n    // notifications are never explicitly confirmed over TCP\n    if (direction != AVS_COAP_EXCHANGE_SERVER_NOTIFICATION) {\n        avs_coap_tcp_ctx_t *ctx = (avs_coap_tcp_ctx_t *) ctx_;\n        _avs_coap_tcp_abort_pending_request_by_token(ctx, token, result,\n                                                     fail_err);\n    }\n}\n\nstatic avs_error_t coap_tcp_accept_observation(avs_coap_ctx_t *ctx_,\n                                               avs_coap_observe_t *observe) {\n    (void) ctx_;\n    (void) observe;\n\n#    ifdef WITH_AVS_COAP_OBSERVE\n    return AVS_OK;\n#    else  // WITH_AVS_COAP_OBSERVE\n    LOG(WARNING, _(\"Observes support disabled\"));\n    return _avs_coap_err(AVS_COAP_ERR_FEATURE_DISABLED);\n#    endif // WITH_AVS_COAP_OBSERVE\n}\n\nstatic inline avs_error_t receive_missing_payload(avs_coap_tcp_ctx_t *ctx,\n                                                  uint8_t *buf,\n                                                  size_t buf_size,\n                                                  size_t *out_bytes_received) {\n    assert(ctx->cached_msg.options_cached);\n    size_t bytes_to_read = AVS_MIN(buf_size, ctx->cached_msg.remaining_bytes);\n    avs_error_t err =\n            coap_tcp_recv_data(ctx, buf, bytes_to_read, out_bytes_received);\n    if (avs_is_err(err)) {\n        SET_DIAGNOSTIC_MESSAGE(ctx, \"recv failed\");\n    }\n    return err;\n}\n\nstatic inline void\npack_payload_from_opts_buffer(avs_coap_tcp_cached_msg_t *inout_msg,\n                              avs_buffer_t *data,\n                              size_t payload_offset) {\n    const uint8_t *payload_ptr =\n            (const uint8_t *) avs_buffer_data(data) + payload_offset;\n    size_t data_size = avs_buffer_data_size(data) - payload_offset;\n    AVS_ASSERT(data_size <= inout_msg->remaining_bytes,\n               \"bug: more than one message in buffer\");\n    _avs_coap_tcp_pack_payload(inout_msg, payload_ptr, data_size);\n}\n\nstatic avs_error_t recv_to_internal_buffer_with_bytes_limit(\n        avs_coap_tcp_ctx_t *ctx, size_t limit, size_t *out_bytes_read) {\n    size_t bytes_read = 0;\n    const size_t space_left = avs_buffer_space_left(ctx->opt_cache.buffer);\n    const size_t bytes_to_read = AVS_MIN(space_left, limit);\n    avs_error_t err =\n            coap_tcp_recv_data(ctx,\n                               avs_buffer_raw_insert_ptr(ctx->opt_cache.buffer),\n                               bytes_to_read, &bytes_read);\n    if (avs_is_err(err)) {\n        return err;\n    }\n\n    avs_buffer_advance_ptr(ctx->opt_cache.buffer, bytes_read);\n    if (out_bytes_read) {\n        *out_bytes_read = bytes_read;\n    }\n    return AVS_OK;\n}\n\nstatic void ignore_data_for_current_msg(avs_coap_tcp_ctx_t *ctx) {\n    const size_t bytes_in_buffer = avs_buffer_data_size(ctx->opt_cache.buffer);\n    const size_t bytes_to_ignore =\n            AVS_MIN(ctx->cached_msg.remaining_bytes, bytes_in_buffer);\n\n    avs_buffer_consume_bytes(ctx->opt_cache.buffer, bytes_to_ignore);\n    ctx->cached_msg.remaining_bytes -= bytes_to_ignore;\n}\n\nstatic avs_error_t receive_header(avs_coap_tcp_ctx_t *ctx) {\n    if (!ctx->cached_msg.remaining_header_bytes) {\n        ctx->cached_msg.remaining_header_bytes =\n                _AVS_COAP_TCP_MIN_HEADER_LENGTH;\n    }\n\n    avs_error_t err;\n    avs_coap_tcp_header_t header;\n\n    // Stops if:\n    // - less bytes than required were received from the socket,\n    // - header is invalid,\n    // - header was parsed successfully.\n    //\n    // Note: in the first iteration, it tries to receive just two bytes of\n    // header and parse them. If header is longer than 2 bytes, then\n    // ctx->cached_msg.remaining_header_bytes is updated and recv function\n    // is called again to obtain remaining bytes.\n    while (ctx->cached_msg.remaining_header_bytes) {\n        size_t bytes_read;\n        err = recv_to_internal_buffer_with_bytes_limit(\n                ctx, ctx->cached_msg.remaining_header_bytes, &bytes_read);\n        if (avs_is_err(err)) {\n            return err;\n        }\n\n        ctx->cached_msg.remaining_header_bytes -= bytes_read;\n        if (ctx->cached_msg.remaining_header_bytes) {\n            return _avs_coap_err(AVS_COAP_ERR_MORE_DATA_REQUIRED);\n        }\n\n        bytes_dispenser_t dispenser = {\n            .read_ptr =\n                    (const uint8_t *) avs_buffer_data(ctx->opt_cache.buffer),\n            .bytes_left = avs_buffer_data_size(ctx->opt_cache.buffer)\n        };\n\n        err = _avs_coap_tcp_header_parse(\n                &header, &dispenser, &ctx->cached_msg.remaining_header_bytes);\n        if (err.category == AVS_COAP_ERR_CATEGORY\n                && err.code == AVS_COAP_ERR_MALFORMED_MESSAGE) {\n            return err;\n        }\n    }\n\n    ctx->cached_msg = (avs_coap_tcp_cached_msg_t) {\n        .content = (avs_coap_borrowed_msg_t) {\n            .code = header.code,\n            .token = (avs_coap_token_t) {\n                .size = header.token_len\n            },\n            .options = { 0 }\n        },\n        .remaining_bytes = header.opts_and_payload_len + header.token_len\n    };\n    avs_buffer_reset(ctx->opt_cache.buffer);\n    return AVS_OK;\n}\n\nstatic avs_error_t receive_token(avs_coap_tcp_ctx_t *ctx) {\n    size_t bytes_read;\n\n    size_t remaining_token_bytes =\n            ctx->cached_msg.content.token.size\n            - avs_buffer_data_size(ctx->opt_cache.buffer);\n\n    if (remaining_token_bytes) {\n        avs_error_t err = recv_to_internal_buffer_with_bytes_limit(\n                ctx, remaining_token_bytes, &bytes_read);\n        if (avs_is_err(err)) {\n            return err;\n        }\n        if (remaining_token_bytes != bytes_read) {\n            return _avs_coap_err(AVS_COAP_ERR_MORE_DATA_REQUIRED);\n        }\n    }\n\n    memcpy(ctx->cached_msg.content.token.bytes,\n           avs_buffer_data(ctx->opt_cache.buffer),\n           ctx->cached_msg.content.token.size);\n    avs_buffer_reset(ctx->opt_cache.buffer);\n    ctx->cached_msg.remaining_bytes -= ctx->cached_msg.content.token.size;\n    return AVS_OK;\n}\n\nstatic avs_error_t receive_options(avs_coap_tcp_ctx_t *ctx) {\n    // cached_msg.remaining_bytes indicates how many bytes of the message wasn't\n    // parsed, but some of them may be already received and present in\n    // opt_cache.buffer.\n    assert(ctx->cached_msg.remaining_bytes\n           > avs_buffer_data_size(ctx->opt_cache.buffer));\n    size_t bytes_to_receive = ctx->cached_msg.remaining_bytes\n                              - avs_buffer_data_size(ctx->opt_cache.buffer);\n    avs_error_t err =\n            recv_to_internal_buffer_with_bytes_limit(ctx, bytes_to_receive,\n                                                     NULL);\n    if (avs_is_err(err)) {\n        return err;\n    }\n\n    err = _avs_coap_tcp_pack_options(&ctx->cached_msg, ctx->opt_cache.buffer);\n    if (avs_is_ok(err)) {\n        ctx->cached_msg.options_cached = true;\n        return AVS_OK;\n    }\n    if (err.category == AVS_COAP_ERR_CATEGORY) {\n        switch ((avs_coap_error_t) err.code) {\n        case AVS_COAP_ERR_MORE_DATA_REQUIRED:\n            /**\n             * If options are truncated and entire buffer is filled with data,\n             * we'll not be able to receive remaining options and message has to\n             * be ignored.\n             */\n            if (avs_buffer_data_size(ctx->opt_cache.buffer)\n                    == avs_buffer_capacity(ctx->opt_cache.buffer)) {\n                return _avs_coap_err(AVS_COAP_ERR_TRUNCATED_MESSAGE_RECEIVED);\n            }\n            return err;\n\n        case AVS_COAP_ERR_MALFORMED_OPTIONS:\n            LOG(DEBUG, _(\"invalid or malformed options\"));\n            return err;\n\n        case AVS_COAP_ERR_MALFORMED_MESSAGE:\n            LOG(DEBUG, _(\"malformed message\"));\n            return err;\n\n        default:;\n        }\n    }\n\n    AVS_UNREACHABLE(\"bug: some case not handled\");\n    return _avs_coap_err(AVS_COAP_ERR_ASSERT_FAILED);\n}\n\nstatic avs_error_t pack_payload_from_internal_buffer_and_handle_msg(\n        avs_coap_tcp_ctx_t *ctx, avs_coap_borrowed_msg_t *out_request) {\n    avs_error_t err = AVS_OK;\n\n    const size_t data_size = avs_buffer_data_size(ctx->opt_cache.buffer);\n\n    if (ctx->cached_msg.content.total_payload_size && data_size) {\n        const size_t options_size = ctx->cached_msg.content.options.size;\n        const size_t payload_offset =\n                options_size + sizeof(AVS_COAP_PAYLOAD_MARKER);\n\n        AVS_ASSERT(data_size - payload_offset\n                           <= ctx->cached_msg.content.total_payload_size,\n                   \"bug: more than one message in buffer\");\n\n        if (payload_offset < data_size) {\n            pack_payload_from_opts_buffer(\n                    &ctx->cached_msg, ctx->opt_cache.buffer, payload_offset);\n        }\n    }\n\n    if (ctx->cached_msg.content.payload_size\n            || !ctx->cached_msg.remaining_bytes) {\n        err = handle_cached_msg(ctx, out_request);\n    }\n\n    if (avs_is_err(err)) {\n        return err;\n    }\n    return ctx->cached_msg.remaining_bytes\n                   ? _avs_coap_err(AVS_COAP_ERR_MORE_DATA_REQUIRED)\n                   : AVS_OK;\n}\n\nstatic avs_error_t ignore_invalid_msg(avs_coap_tcp_ctx_t *ctx) {\n    avs_error_t err = _avs_coap_err(AVS_COAP_ERR_MORE_DATA_REQUIRED);\n    ignore_data_for_current_msg(ctx);\n\n    if (avs_coap_code_is_response(ctx->cached_msg.content.code)) {\n        _avs_coap_tcp_handle_pending_request(\n                ctx,\n                &ctx->cached_msg.content,\n                ctx->cached_msg.remaining_bytes\n                        ? PENDING_REQUEST_STATUS_IGNORE\n                        : PENDING_REQUEST_STATUS_FINISH_IGNORE,\n                ctx->ignoring_error);\n    } else if (!ctx->cached_msg.remaining_bytes) {\n        assert(avs_is_err(ctx->ignoring_error));\n        if (ctx->ignoring_error.category == AVS_COAP_ERR_CATEGORY\n                && ctx->ignoring_error.code\n                               == AVS_COAP_ERR_TRUNCATED_MESSAGE_RECEIVED) {\n            SET_DIAGNOSTIC_MESSAGE(ctx, \"options too big\");\n        }\n        err = ctx->ignoring_error;\n    }\n\n    return err;\n}\n\nstatic avs_error_t\nreceive_to_internal_buffer_and_handle(avs_coap_tcp_ctx_t *ctx,\n                                      avs_coap_borrowed_msg_t *out_request) {\n    avs_error_t err = AVS_OK;\n    switch (ctx->opt_cache.state) {\n    case AVS_COAP_TCP_OPT_CACHE_STATE_RECEIVING_HEADER:\n        err = receive_header(ctx);\n        if (avs_is_err(err)) {\n            return err;\n        }\n        ctx->opt_cache.state = AVS_COAP_TCP_OPT_CACHE_STATE_RECEIVING_TOKEN;\n    // fall-through\n    case AVS_COAP_TCP_OPT_CACHE_STATE_RECEIVING_TOKEN:\n        err = receive_token(ctx);\n        if (avs_is_err(err)) {\n            return err;\n        }\n        ctx->opt_cache.state = AVS_COAP_TCP_OPT_CACHE_STATE_RECEIVING_OPTIONS;\n    // fall-through\n    case AVS_COAP_TCP_OPT_CACHE_STATE_RECEIVING_OPTIONS:\n        if (ctx->cached_msg.remaining_bytes) {\n            err = receive_options(ctx);\n            if (err.category == AVS_COAP_ERR_CATEGORY\n                    && (err.code == AVS_COAP_ERR_TRUNCATED_MESSAGE_RECEIVED\n                        || err.code == AVS_COAP_ERR_MALFORMED_OPTIONS\n                        || err.code == AVS_COAP_ERR_MALFORMED_MESSAGE)) {\n                ctx->ignoring_error = err;\n                ctx->opt_cache.state = AVS_COAP_TCP_OPT_CACHE_STATE_IGNORING;\n                return ignore_invalid_msg(ctx);\n            }\n            if (avs_is_err(err)) {\n                return err;\n            }\n        }\n        ctx->opt_cache.state = AVS_COAP_TCP_OPT_CACHE_STATE_RECEIVING_PAYLOAD;\n        log_tcp_msg_summary(\"recv\", &ctx->cached_msg.content);\n    // fall-through\n    case AVS_COAP_TCP_OPT_CACHE_STATE_RECEIVING_PAYLOAD:\n        err = pack_payload_from_internal_buffer_and_handle_msg(ctx,\n                                                               out_request);\n        break;\n    case AVS_COAP_TCP_OPT_CACHE_STATE_IGNORING:\n        err = recv_to_internal_buffer_with_bytes_limit(\n                ctx, ctx->cached_msg.remaining_bytes, NULL);\n        if (avs_is_err(err)) {\n            return err;\n        }\n        err = ignore_invalid_msg(ctx);\n        break;\n    }\n    return err;\n}\n\nstatic avs_error_t\nreceive_to_shared_buffer_and_handle(avs_coap_tcp_ctx_t *ctx,\n                                    uint8_t *in_buffer,\n                                    size_t in_buffer_capacity,\n                                    avs_coap_borrowed_msg_t *out_request) {\n    size_t bytes_read = 0;\n    avs_error_t err = receive_missing_payload(ctx, in_buffer,\n                                              in_buffer_capacity, &bytes_read);\n    if (avs_is_ok(err)) {\n        _avs_coap_tcp_pack_payload(&ctx->cached_msg, in_buffer, bytes_read);\n        err = handle_cached_msg(ctx, out_request);\n    }\n    return err;\n}\n\nstatic avs_error_t handle_error(avs_coap_tcp_ctx_t *ctx, avs_error_t err) {\n    if (avs_is_err(err)) {\n        if (err.category == AVS_COAP_ERR_CATEGORY) {\n            switch ((avs_coap_error_t) err.code) {\n            case AVS_COAP_ERR_MORE_DATA_REQUIRED:\n            case AVS_COAP_ERR_TIMEOUT:\n                return AVS_OK;\n\n            case AVS_COAP_ERR_MALFORMED_OPTIONS:\n                return send_simple_msg(ctx, AVS_COAP_CODE_BAD_OPTION,\n                                       &ctx->cached_msg.content.token, NULL);\n\n            case AVS_COAP_ERR_TRUNCATED_MESSAGE_RECEIVED:\n                return send_simple_msg(ctx, AVS_COAP_CODE_INTERNAL_SERVER_ERROR,\n                                       &ctx->cached_msg.content.token,\n                                       GET_DIAGNOSTIC_MESSAGE(ctx));\n\n            default:;\n            }\n        }\n        if (err.category != AVS_ERRNO_CATEGORY || err.code != AVS_ETIMEDOUT) {\n            LOG(ERROR, _(\"failure (\") \"%s\" _(\"), aborting\"),\n                AVS_COAP_STRERROR(err));\n            send_abort(ctx);\n        }\n    }\n    return err;\n}\n\nstatic avs_error_t\nreceive_and_handle_message(avs_coap_tcp_ctx_t *ctx,\n                           uint8_t *in_buffer,\n                           size_t in_buffer_capacity,\n                           avs_coap_borrowed_msg_t *out_request) {\n    avs_error_t err;\n    if (!ctx->cached_msg.options_cached) {\n        // Use internal buffer to cache options. If some payload is received,\n        // then handle it.\n        err = receive_to_internal_buffer_and_handle(ctx, out_request);\n    } else {\n        // Use shared buffer to receive only remaining payload.\n        err = receive_to_shared_buffer_and_handle(\n                ctx, in_buffer, in_buffer_capacity, out_request);\n    }\n    return handle_error(ctx, err);\n}\n\nstatic avs_error_t\ncoap_tcp_receive_message(avs_coap_ctx_t *ctx_,\n                         uint8_t *in_buffer,\n                         size_t in_buffer_capacity,\n                         avs_coap_borrowed_msg_t *out_request) {\n    avs_coap_tcp_ctx_t *ctx = (avs_coap_tcp_ctx_t *) ctx_;\n    memset(out_request, 0, sizeof(*out_request));\n    if (ctx->aborted) {\n        LOG(ERROR,\n            _(\"Abort message was sent and context shouldn't be used anymore\"));\n        return _avs_coap_err(AVS_COAP_ERR_TCP_ABORT_SENT);\n    }\n    SET_DIAGNOSTIC_MESSAGE(ctx, NULL);\n\n    avs_time_duration_t timeout;\n    avs_error_t err = get_recv_timeout(ctx->base.socket, &timeout);\n    if (avs_is_err(err)) {\n        return err;\n    }\n\n    if (ctx->cached_msg.remaining_bytes == 0\n            && ctx->cached_msg.remaining_header_bytes == 0) {\n        finish_message_handling(ctx);\n        AVS_ASSERT(avs_buffer_data_size(ctx->opt_cache.buffer) == 0,\n                   \"bug: data in buffer after finishing message handling\");\n    }\n\n    err = receive_and_handle_message(ctx, in_buffer, in_buffer_capacity,\n                                     out_request);\n\n    avs_error_t restore_err = set_recv_timeout(ctx->base.socket, timeout);\n    return avs_is_ok(err) ? restore_err : err;\n}\n\nstatic void update_timeout(avs_time_monotonic_t *result_ptr,\n                           avs_time_monotonic_t candidate) {\n    if (!avs_time_monotonic_valid(*result_ptr)\n            || avs_time_monotonic_before(candidate, *result_ptr)) {\n        *result_ptr = candidate;\n    }\n}\n\nstatic avs_time_monotonic_t coap_tcp_on_timeout(avs_coap_ctx_t *ctx_) {\n    avs_coap_tcp_ctx_t *ctx = (avs_coap_tcp_ctx_t *) ctx_;\n    avs_time_monotonic_t result = AVS_TIME_MONOTONIC_INVALID;\n    if (avs_coap_ctx_has_socket(ctx_)\n            && avs_time_monotonic_valid(ctx->peer_csm.recv_deadline)) {\n        if (avs_time_monotonic_before(avs_time_monotonic_now(),\n                                      ctx->peer_csm.recv_deadline)) {\n            update_timeout(&result, ctx->peer_csm.recv_deadline);\n        } else {\n            LOG(ERROR, _(\"CSM not received within timeout\"));\n            SET_DIAGNOSTIC_MESSAGE(ctx, \"CSM not received within timeout\");\n            send_abort(ctx);\n        }\n    }\n    update_timeout(&result, _avs_coap_tcp_fail_expired_pending_requests(ctx));\n    return result;\n}\n\nstatic avs_error_t send_csm(avs_coap_tcp_ctx_t *ctx) {\n    uint8_t opts[16];\n    avs_coap_borrowed_msg_t msg = {\n        .code = AVS_COAP_CODE_CSM,\n        .options = avs_coap_options_create_empty(opts, sizeof(opts))\n    };\n\n    avs_error_t err =\n            _avs_coap_ctx_generate_token(ctx->base.prng_ctx, &msg.token);\n    if (avs_is_err(err)) {\n        return err;\n    }\n\n#    ifdef WITH_AVS_COAP_BLOCK\n    // From RFC 8323: \"If a Max-Message-Size Option is indicated with a value\n    // that is greater than 1152 (in the same CSM or a different CSM), the\n    // Block-Wise-Transfer Option also indicates support for BERT\"\n    (void) avs_coap_options_add_empty(\n            &msg.options, _AVS_COAP_OPTION_BLOCK_WISE_TRANSFER_CAPABILITY);\n#    endif // WITH_AVS_COAP_BLOCK\n    (void) avs_coap_options_add_u32(&msg.options,\n                                    _AVS_COAP_OPTION_MAX_MESSAGE_SIZE,\n                                    INCOMING_MESSAGE_MAX_TOTAL_SIZE);\n\n    err = _avs_coap_tcp_send_msg(ctx, &msg);\n    if (avs_is_err(err)) {\n        LOG(ERROR, _(\"failed to send CSM\"));\n    }\n    return err;\n}\n\nstatic avs_error_t coap_tcp_setsock(avs_coap_ctx_t *ctx_,\n                                    avs_net_socket_t *socket) {\n    avs_coap_tcp_ctx_t *ctx = (avs_coap_tcp_ctx_t *) ctx_;\n    avs_error_t err = _avs_coap_ctx_set_socket_base(ctx_, socket);\n    if (avs_is_err(err)) {\n        return err;\n    }\n\n    ctx->peer_csm.recv_deadline = AVS_TIME_MONOTONIC_INVALID;\n    err = avs_errno(AVS_EOVERFLOW);\n    if (_avs_coap_tcp_update_recv_deadline(ctx, &ctx->peer_csm.recv_deadline)\n            || !avs_time_monotonic_valid(ctx->peer_csm.recv_deadline)\n            || avs_is_err((err = send_csm(ctx)))) {\n        SET_DIAGNOSTIC_MESSAGE(ctx, \"failed to send CSM\");\n        send_abort(ctx);\n        return err;\n    }\n    _avs_coap_reschedule_retry_or_request_expired_job(\n            ctx_, ctx->peer_csm.recv_deadline);\n    return AVS_OK;\n}\n\nstatic avs_coap_base_t *coap_tcp_get_base(avs_coap_ctx_t *ctx_) {\n    avs_coap_tcp_ctx_t *ctx = (avs_coap_tcp_ctx_t *) ctx_;\n    return &ctx->base;\n}\n\nstatic uint32_t coap_tcp_next_observe_option_value(avs_coap_ctx_t *ctx,\n                                                   uint32_t last_value) {\n    (void) ctx;\n    (void) last_value;\n    return 0;\n}\n\nstatic const avs_coap_ctx_vtable_t COAP_TCP_VTABLE = {\n    .cleanup = coap_tcp_cleanup,\n    .get_base = coap_tcp_get_base,\n    .setsock = coap_tcp_setsock,\n    .max_outgoing_payload_size = coap_tcp_max_outgoing_payload_size,\n    .max_incoming_payload_size = coap_tcp_max_incoming_payload_size,\n    .send_message = coap_tcp_send_message,\n    .abort_delivery = coap_tcp_abort_delivery,\n    .accept_observation = coap_tcp_accept_observation,\n    .ignore_current_request = coap_tcp_ignore_current_request,\n    .receive_message = coap_tcp_receive_message,\n    .on_timeout = coap_tcp_on_timeout,\n    .next_observe_option_value = coap_tcp_next_observe_option_value\n};\n\navs_coap_ctx_t *avs_coap_tcp_ctx_create(avs_sched_t *sched,\n                                        avs_shared_buffer_t *in_buffer,\n                                        avs_shared_buffer_t *out_buffer,\n                                        size_t max_opts_size,\n                                        avs_time_duration_t request_timeout,\n                                        avs_crypto_prng_ctx_t *prng_ctx) {\n    assert(in_buffer);\n    assert(out_buffer);\n    assert(prng_ctx);\n\n    if (out_buffer->capacity < _AVS_COAP_TCP_MAX_HEADER_LENGTH) {\n        LOG(ERROR,\n            _(\"output buffer capacity must be at least \") \"%u\" _(\" bytes\"),\n            (unsigned) _AVS_COAP_TCP_MAX_HEADER_LENGTH);\n        return NULL;\n    }\n    if (max_opts_size < AVS_COAP_MAX_TOKEN_LENGTH) {\n        LOG(ERROR, _(\"max_opts_size must be at least \") \"%u\",\n            (unsigned) AVS_COAP_MAX_TOKEN_LENGTH);\n        return NULL;\n    }\n    if (!avs_time_duration_valid(request_timeout)) {\n        LOG(ERROR, _(\"invalid timeout specified\"));\n        return NULL;\n    }\n\n    avs_coap_tcp_ctx_t *ctx =\n            (avs_coap_tcp_ctx_t *) avs_calloc(1, sizeof(avs_coap_tcp_ctx_t));\n    if (!ctx) {\n        return NULL;\n    }\n\n    const size_t buf_size = max_opts_size + sizeof(AVS_COAP_PAYLOAD_MARKER);\n\n    if (avs_buffer_create(&ctx->opt_cache.buffer, buf_size)) {\n        avs_free(ctx);\n        return NULL;\n    }\n\n    _avs_coap_base_init(&ctx->base, (avs_coap_ctx_t *) ctx, in_buffer,\n                        out_buffer, sched, prng_ctx);\n\n    ctx->vtable = &COAP_TCP_VTABLE;\n    ctx->peer_csm.recv_deadline = avs_time_monotonic_now();\n    ctx->peer_csm.max_message_size = CSM_MAX_MESSAGE_SIZE_BASE_VALUE;\n    ctx->request_timeout = request_timeout;\n\n    return (avs_coap_ctx_t *) ctx;\n}\n\n#endif // WITH_AVS_COAP_TCP\n"
  },
  {
    "path": "deps/avs_coap/src/tcp/avs_coap_tcp_ctx.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include \"avs_coap_ctx.h\"\n\n#include \"tcp/avs_coap_tcp_msg.h\"\n#include \"tcp/avs_coap_tcp_pending_requests.h\"\n#include \"tcp/avs_coap_tcp_signaling.h\"\n\n#ifndef AVS_COAP_SRC_TCP_CTX_H\n#    define AVS_COAP_SRC_TCP_CTX_H\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\ntypedef enum {\n    AVS_COAP_TCP_OPT_CACHE_STATE_RECEIVING_HEADER = 0,\n    AVS_COAP_TCP_OPT_CACHE_STATE_RECEIVING_TOKEN,\n    AVS_COAP_TCP_OPT_CACHE_STATE_RECEIVING_OPTIONS,\n    AVS_COAP_TCP_OPT_CACHE_STATE_RECEIVING_PAYLOAD,\n    AVS_COAP_TCP_OPT_CACHE_STATE_IGNORING\n} avs_coap_tcp_opt_cache_state_t;\n\ntypedef struct {\n    avs_buffer_t *buffer;\n    avs_coap_tcp_opt_cache_state_t state;\n} avs_coap_tcp_opt_cache_t;\n\ntypedef struct avs_coap_tcp_ctx_struct {\n    const struct avs_coap_ctx_vtable *vtable;\n\n    avs_coap_base_t base;\n    avs_coap_tcp_opt_cache_t opt_cache;\n    avs_coap_tcp_cached_msg_t cached_msg;\n    avs_coap_tcp_csm_t peer_csm;\n    // Sorted by @ref avs_coap_tcp_pending_request_t#expire_time\n    AVS_LIST(avs_coap_tcp_pending_request_t) pending_requests;\n    // Timeout defined during creation of CoAP TCP context.\n    avs_time_duration_t request_timeout;\n\n#    ifdef WITH_AVS_COAP_DIAGNOSTIC_MESSAGES\n    const char *err_details;\n#    endif // WITH_AVS_COAP_DIAGNOSTIC_MESSAGES\n\n    // Indicating that Abort message was sent to prevent sending Release\n    // message in cleanup.\n    bool aborted;\n\n    // Error set when incoming message is set up to be ignored, returned to user\n    // when message is finished. It has to be stored, because we want to delay\n    // reporting the error until the whole message is received.\n    avs_error_t ignoring_error;\n} avs_coap_tcp_ctx_t;\n\n/** Sends previously constructed message over CoAP/TCP. */\navs_error_t _avs_coap_tcp_send_msg(avs_coap_tcp_ctx_t *ctx,\n                                   const avs_coap_borrowed_msg_t *msg);\n\nstatic inline int\n_avs_coap_tcp_update_recv_deadline(avs_coap_tcp_ctx_t *ctx,\n                                   avs_time_monotonic_t *inout_deadline) {\n    *inout_deadline = avs_time_monotonic_add(avs_time_monotonic_now(),\n                                             ctx->request_timeout);\n    return 0;\n}\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif // AVS_COAP_SRC_TCP_CTX_H\n"
  },
  {
    "path": "deps/avs_coap/src/tcp/avs_coap_tcp_header.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <avs_coap_init.h>\n\n#ifdef WITH_AVS_COAP_TCP\n\n#    include <avsystem/commons/avs_errno.h>\n\n#    include \"options/avs_coap_option.h\"\n\n#    define MODULE_NAME coap_tcp\n#    include <avs_coap_x_log_config.h>\n\n#    include \"avs_coap_tcp_header.h\"\n\nVISIBILITY_SOURCE_BEGIN\n\n// As defined in RFC8323\n#    define MIN_8BIT_EXT_LEN 13U\n#    define MIN_16BIT_EXT_LEN 269U\n#    define MIN_32BIT_EXT_LEN 65805U\n\n#    define HEADER_LEN_MASK 0xF0\n#    define HEADER_LEN_SHIFT 4\n#    define HEADER_TKL_MASK 0x0F\n#    define HEADER_TKL_SHIFT 0\n\n#    define LEN_TKL_OFFSET 0\n#    define EXT_LEN_OFFSET 1\n\n#    define EXTENDED_LENGTH_UINT8 13\n#    define EXTENDED_LENGTH_UINT16 14\n#    define EXTENDED_LENGTH_UINT32 15\n\navs_coap_tcp_header_t _avs_coap_tcp_header_init(size_t payload_size,\n                                                size_t options_size,\n                                                uint8_t token_size,\n                                                uint8_t code) {\n    assert(token_size <= 8);\n    assert((payload_size ? sizeof(AVS_COAP_PAYLOAD_MARKER) : 0) + payload_size\n                   + options_size\n           <= (uint64_t) UINT32_MAX + MIN_32BIT_EXT_LEN);\n    avs_coap_tcp_header_t header = {\n        // Length of the message in bytes, including options field, payload\n        // marker and payload data, as defined in RFC8323.\n        .opts_and_payload_len =\n                (payload_size ? sizeof(AVS_COAP_PAYLOAD_MARKER) : 0)\n                + payload_size + options_size,\n        .token_len = token_size,\n        .code = code\n    };\n    return header;\n}\n\nstatic void set_len_field(uint8_t *len_tkl, size_t value) {\n    AVS_ASSERT(value < 16, \"len field can't be set to value bigger than 15\");\n    _AVS_FIELD_SET(*len_tkl, HEADER_LEN_MASK, HEADER_LEN_SHIFT, value);\n}\n\nsize_t _avs_coap_tcp_header_serialize(const avs_coap_tcp_header_t *header,\n                                      uint8_t *buf,\n                                      size_t buf_size) {\n    assert(buf_size >= _AVS_COAP_TCP_MAX_HEADER_LENGTH);\n    (void) buf_size;\n\n    uint8_t len_tkl = 0;\n    _AVS_FIELD_SET(len_tkl, HEADER_TKL_MASK, HEADER_TKL_SHIFT,\n                   header->token_len);\n\n    size_t code_offset = EXT_LEN_OFFSET;\n\n    if (header->opts_and_payload_len < MIN_8BIT_EXT_LEN) {\n        set_len_field(&len_tkl, header->opts_and_payload_len);\n    } else if (header->opts_and_payload_len < MIN_16BIT_EXT_LEN) {\n        set_len_field(&len_tkl, EXTENDED_LENGTH_UINT8);\n        uint8_t ext_len =\n                (uint8_t) (header->opts_and_payload_len - MIN_8BIT_EXT_LEN);\n        buf[EXT_LEN_OFFSET] = ext_len;\n        code_offset += sizeof(ext_len);\n    } else if (header->opts_and_payload_len < MIN_32BIT_EXT_LEN) {\n        set_len_field(&len_tkl, EXTENDED_LENGTH_UINT16);\n        uint16_t ext_len = avs_convert_be16(\n                (uint16_t) (header->opts_and_payload_len - MIN_16BIT_EXT_LEN));\n        memcpy(buf + EXT_LEN_OFFSET, &ext_len, sizeof(ext_len));\n        code_offset += sizeof(ext_len);\n    } else {\n        set_len_field(&len_tkl, EXTENDED_LENGTH_UINT32);\n        uint32_t ext_len = avs_convert_be32(\n                (uint32_t) (header->opts_and_payload_len - MIN_32BIT_EXT_LEN));\n        memcpy(buf + EXT_LEN_OFFSET, &ext_len, sizeof(ext_len));\n        code_offset += sizeof(ext_len);\n    }\n\n    buf[LEN_TKL_OFFSET] = len_tkl;\n    buf[code_offset] = header->code;\n\n    return code_offset + 1;\n}\n\n/**\n * Returns length of extended length field and code.\n */\nstatic inline size_t remaining_header_bytes(uint8_t len_value) {\n    size_t ext_len_size = 0;\n    switch (len_value) {\n    case EXTENDED_LENGTH_UINT8:\n        ext_len_size = sizeof(uint8_t);\n        break;\n    case EXTENDED_LENGTH_UINT16:\n        ext_len_size = sizeof(uint16_t);\n        break;\n    case EXTENDED_LENGTH_UINT32:\n        ext_len_size = sizeof(uint32_t);\n        break;\n    default:\n        break;\n    }\n    return ext_len_size + sizeof(uint8_t); // add length of code\n}\n\navs_error_t _avs_coap_tcp_header_parse(avs_coap_tcp_header_t *header,\n                                       bytes_dispenser_t *dispenser,\n                                       size_t *out_header_bytes_missing) {\n    uint8_t len_tkl;\n    if (dispenser->bytes_left < sizeof(len_tkl)) {\n        *out_header_bytes_missing = sizeof(len_tkl);\n        return _avs_coap_err(AVS_COAP_ERR_MORE_DATA_REQUIRED);\n    }\n\n    (void) _avs_coap_bytes_extract(dispenser, &len_tkl, sizeof(len_tkl));\n\n    uint8_t short_len =\n            _AVS_FIELD_GET(len_tkl, HEADER_LEN_MASK, HEADER_LEN_SHIFT);\n    assert(short_len < 16);\n\n    size_t remaining_bytes = remaining_header_bytes(short_len);\n    if (remaining_bytes > dispenser->bytes_left) {\n        *out_header_bytes_missing = remaining_bytes - dispenser->bytes_left;\n        return _avs_coap_err(AVS_COAP_ERR_MORE_DATA_REQUIRED);\n    }\n\n    header->token_len =\n            _AVS_FIELD_GET(len_tkl, HEADER_TKL_MASK, HEADER_TKL_SHIFT);\n    if (header->token_len > AVS_COAP_MAX_TOKEN_LENGTH) {\n        LOG(DEBUG, _(\"invalid token longer than \") \"%u\" _(\" bytes\"),\n            (unsigned) AVS_COAP_MAX_TOKEN_LENGTH);\n        return _avs_coap_err(AVS_COAP_ERR_MALFORMED_MESSAGE);\n    }\n\n    if (short_len < MIN_8BIT_EXT_LEN) {\n        header->opts_and_payload_len = short_len;\n    } else if (short_len == EXTENDED_LENGTH_UINT8) {\n        uint8_t ext_len;\n        (void) _avs_coap_bytes_extract(dispenser, &ext_len, sizeof(ext_len));\n        header->opts_and_payload_len = (uint64_t) ext_len + MIN_8BIT_EXT_LEN;\n    } else if (short_len == EXTENDED_LENGTH_UINT16) {\n        uint16_t ext_len;\n        (void) _avs_coap_bytes_extract(dispenser, &ext_len, sizeof(ext_len));\n        header->opts_and_payload_len =\n                (uint64_t) avs_convert_be16(ext_len) + MIN_16BIT_EXT_LEN;\n    } else {\n        uint32_t ext_len;\n        (void) _avs_coap_bytes_extract(dispenser, &ext_len, sizeof(ext_len));\n        header->opts_and_payload_len =\n                (uint64_t) avs_convert_be32(ext_len) + MIN_32BIT_EXT_LEN;\n    }\n\n    *out_header_bytes_missing = 0;\n    (void) _avs_coap_bytes_extract(dispenser, &header->code,\n                                   sizeof(header->code));\n    return AVS_OK;\n}\n\n#endif // WITH_AVS_COAP_TCP\n"
  },
  {
    "path": "deps/avs_coap/src/tcp/avs_coap_tcp_header.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef AVS_COAP_SRC_TCP_HEADER_H\n#define AVS_COAP_SRC_TCP_HEADER_H\n\n#include \"avs_coap_common_utils.h\"\n#include \"avs_coap_parse_utils.h\"\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\n//  0           1           2           3           4           5           6\n// +-----------+-----------+-----------+-----------+-----------+-----------+\n// | Len | TKL | Extended Length (optional)                    | Code      |\n// +-----------+-----------+-----------+-----------+-----------+-----------+\n#define _AVS_COAP_TCP_MAX_HEADER_LENGTH 6\n#define _AVS_COAP_TCP_MIN_HEADER_LENGTH 2\n\n/** CoAP TCP message header. For internal use only. */\ntypedef struct avs_coap_tcp_header {\n    uint64_t opts_and_payload_len;\n    uint8_t token_len;\n    uint8_t code;\n} avs_coap_tcp_header_t;\n\nsize_t _avs_coap_tcp_header_serialize(const avs_coap_tcp_header_t *header,\n                                      uint8_t *buf,\n                                      size_t buf_size);\n\navs_error_t _avs_coap_tcp_header_parse(avs_coap_tcp_header_t *header,\n                                       bytes_dispenser_t *dispenser,\n                                       size_t *out_header_bytes_missing);\n\navs_coap_tcp_header_t _avs_coap_tcp_header_init(size_t payload_size,\n                                                size_t options_size,\n                                                uint8_t token_size,\n                                                uint8_t code);\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif // AVS_COAP_SRC_TCP_HEADER_H\n"
  },
  {
    "path": "deps/avs_coap/src/tcp/avs_coap_tcp_msg.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <avs_coap_init.h>\n\n#ifdef WITH_AVS_COAP_TCP\n\n#    include <avsystem/commons/avs_buffer.h>\n#    include <avsystem/commons/avs_errno.h>\n\n#    include \"avs_coap_code_utils.h\"\n\n#    define MODULE_NAME coap_tcp\n#    include <avs_coap_x_log_config.h>\n\n#    include \"options/avs_coap_options.h\"\n#    include \"tcp/avs_coap_tcp_msg.h\"\n\nVISIBILITY_SOURCE_BEGIN\n\navs_error_t _avs_coap_tcp_serialize_msg(const avs_coap_borrowed_msg_t *msg,\n                                        void *buf,\n                                        size_t buf_size,\n                                        size_t *out_msg_size) {\n    assert(buf_size >= _AVS_COAP_TCP_MAX_HEADER_LENGTH);\n    assert(buf);\n    assert(out_msg_size);\n\n    avs_coap_tcp_header_t header =\n            _avs_coap_tcp_header_init(msg->payload_size, msg->options.size,\n                                      msg->token.size, msg->code);\n    size_t header_size =\n            _avs_coap_tcp_header_serialize(&header, (uint8_t *) buf, buf_size);\n\n    bytes_appender_t appender = {\n        .write_ptr = (uint8_t *) buf + header_size,\n        .bytes_left = buf_size - header_size\n    };\n\n    int retval;\n    (void) ((retval = _avs_coap_bytes_append(&appender, msg->token.bytes,\n                                             msg->token.size))\n            || (retval = _avs_coap_bytes_append(&appender, msg->options.begin,\n                                                msg->options.size)));\n    if (!retval && msg->payload_size) {\n        (void) ((retval = _avs_coap_bytes_append(\n                         &appender, &AVS_COAP_PAYLOAD_MARKER,\n                         sizeof(AVS_COAP_PAYLOAD_MARKER)))\n                || (retval = _avs_coap_bytes_append(&appender, msg->payload,\n                                                    msg->payload_size)));\n    }\n\n    if (retval) {\n        LOG(ERROR, _(\"message too big to fit into output buffer\"));\n        return _avs_coap_err(AVS_COAP_ERR_MESSAGE_TOO_BIG);\n    }\n    *out_msg_size = buf_size - appender.bytes_left;\n    return AVS_OK;\n}\n\navs_error_t _avs_coap_tcp_pack_options(avs_coap_tcp_cached_msg_t *inout_msg,\n                                       const avs_buffer_t *data) {\n    assert(inout_msg);\n    assert(data);\n\n    if (!inout_msg->remaining_bytes) {\n        return AVS_OK;\n    }\n\n    const uint8_t *data_ptr = (const uint8_t *) avs_buffer_data(data);\n    const size_t data_size = avs_buffer_data_size(data);\n    AVS_ASSERT(data_size <= inout_msg->remaining_bytes,\n               \"bug: more than one message in buffer\");\n    bytes_dispenser_t dispenser = {\n        .read_ptr = data_ptr,\n        .bytes_left = data_size\n    };\n\n    bool payload_marker_reached;\n    bool truncated;\n    avs_error_t err =\n            _avs_coap_options_parse(&inout_msg->content.options, &dispenser,\n                                    &truncated, &payload_marker_reached);\n    if (avs_is_err(err)) {\n        // There must be check if we didn't receive a complete message,\n        // because single option may be truncated, but there'll be no more data.\n        return (truncated && data_size != inout_msg->remaining_bytes)\n                       ? _avs_coap_err(AVS_COAP_ERR_MORE_DATA_REQUIRED)\n                       : err;\n    }\n\n    if (!payload_marker_reached\n            && inout_msg->content.options.size < inout_msg->remaining_bytes) {\n        // Payload exists after options and marker isn't parsed yet.\n        return _avs_coap_err(AVS_COAP_ERR_MORE_DATA_REQUIRED);\n    }\n\n    size_t bytes_parsed = data_size - dispenser.bytes_left;\n    if (payload_marker_reached) {\n        assert(*dispenser.read_ptr == AVS_COAP_PAYLOAD_MARKER);\n        _avs_coap_bytes_extract(&dispenser, NULL,\n                                sizeof(AVS_COAP_PAYLOAD_MARKER));\n        bytes_parsed += sizeof(AVS_COAP_PAYLOAD_MARKER);\n        if (inout_msg->remaining_bytes - bytes_parsed == 0) {\n            // not MALFORMED_MESSAGE, because the header is still valid\n            LOG(DEBUG, _(\"invalid message - no payload after payload marker\"));\n            return _avs_coap_err(AVS_COAP_ERR_MALFORMED_OPTIONS);\n        }\n    }\n\n    size_t total_payload_length = inout_msg->remaining_bytes - bytes_parsed;\n\n#    ifdef WITH_AVS_COAP_BLOCK\n    if (!_avs_coap_options_block_payload_valid(&inout_msg->content.options,\n                                               inout_msg->content.code,\n                                               total_payload_length)) {\n        return _avs_coap_err(AVS_COAP_ERR_MALFORMED_OPTIONS);\n    }\n#    endif // WITH_AVS_COAP_BLOCK\n\n    inout_msg->remaining_bytes -= bytes_parsed;\n    inout_msg->content.total_payload_size = total_payload_length;\n    return AVS_OK;\n}\n\nvoid _avs_coap_tcp_pack_payload(avs_coap_tcp_cached_msg_t *inout_msg,\n                                const uint8_t *data,\n                                size_t data_size) {\n    assert(data_size <= inout_msg->remaining_bytes);\n    assert(!data_size || data);\n\n    inout_msg->content.payload_offset =\n            inout_msg->content.total_payload_size - inout_msg->remaining_bytes;\n\n    inout_msg->remaining_bytes -= data_size;\n    inout_msg->content.payload = data;\n    inout_msg->content.payload_size = data_size;\n}\n\n#endif // WITH_AVS_COAP_TCP\n"
  },
  {
    "path": "deps/avs_coap/src/tcp/avs_coap_tcp_msg.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef AVS_COAP_SRC_TCP_MSG_H\n#define AVS_COAP_SRC_TCP_MSG_H\n\n#include <avsystem/commons/avs_buffer.h>\n\n#include \"avs_coap_common_utils.h\"\n#include \"avs_coap_ctx_vtable.h\"\n#include \"options/avs_coap_option.h\"\n#include \"tcp/avs_coap_tcp_header.h\"\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\ntypedef struct {\n    // Message which will be passed to user's handlers. It may contain an\n    // entire payload or just a part of it (consecutive chunks).\n    avs_coap_borrowed_msg_t content;\n\n    // Remaining bytes to receive entire message. Includes options, payload\n    // marker and payload.\n    size_t remaining_bytes;\n\n    // Indicates how many bytes should be received in receive_header() in\n    // current call.\n    size_t remaining_header_bytes;\n\n    // True if options were parsed and are available in the content field.\n    // Indicates that message is ready to be passed to user's handler.\n    bool options_cached;\n\n    // Indicating that message should be ignored if it's a request.\n    bool ignore_request;\n} avs_coap_tcp_cached_msg_t;\n\n/**\n * Serializes given CoAP message to @p buf of size @p buf_size .\n *\n * @p msg          Pointer to message to serialize.\n * @p buf          Buffer to write data to.\n * @p buf_size     Size of the buffer.\n * @p out_msg_size Size of serialized message.\n */\navs_error_t _avs_coap_tcp_serialize_msg(const avs_coap_borrowed_msg_t *msg,\n                                        void *buf,\n                                        size_t buf_size,\n                                        size_t *out_msg_size);\n\n/**\n * Packs options from buffer to @p inout_msg .\n *\n * @p inout_msg        Pointer to message to pack options to.\n * @p data             Buffer to parse data from.\n *\n * Imporant note:\n * Bytes from @p data buffer are not consumed. @p data buffer MUST NOT be used\n * between call to this function and passing @p inout_msg to user, because\n * options are not copied.\n */\navs_error_t _avs_coap_tcp_pack_options(avs_coap_tcp_cached_msg_t *inout_msg,\n                                       const avs_buffer_t *data);\n\n/**\n * Packs payload to @p inout_msg.\n *\n * @p inout_msg Pointer to message which will contain pointer to payload after\n *              successful call.\n * @p data      Pointer to payload.\n * @p size      Size of the data in buffer.\n *\n * Note: passed data shouldn't contain anything else than payload for current\n * message.\n */\nvoid _avs_coap_tcp_pack_payload(avs_coap_tcp_cached_msg_t *inout_msg,\n                                const uint8_t *data,\n                                size_t size);\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif // AVS_COAP_SRC_TCP_MSG_H\n"
  },
  {
    "path": "deps/avs_coap/src/tcp/avs_coap_tcp_pending_requests.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <avs_coap_init.h>\n\n#ifdef WITH_AVS_COAP_TCP\n\n#    include <avsystem/coap/token.h>\n#    include <avsystem/commons/avs_errno.h>\n#    include <avsystem/commons/avs_list.h>\n#    include <avsystem/commons/avs_sched.h>\n\n#    define MODULE_NAME coap_tcp\n#    include <avs_coap_x_log_config.h>\n\n#    include \"tcp/avs_coap_tcp_ctx.h\"\n\nVISIBILITY_SOURCE_BEGIN\n\nstruct avs_coap_tcp_pending_request_struct {\n    avs_coap_tcp_response_handler_t handler;\n    avs_coap_token_t token;\n    avs_time_monotonic_t expire_time;\n};\n\nstatic avs_coap_send_result_handler_result_t\ncall_pending_request_response_handler(\n        avs_coap_tcp_ctx_t *ctx,\n        avs_coap_tcp_pending_request_t *pending_request,\n        const avs_coap_borrowed_msg_t *response_msg,\n        avs_coap_send_result_t result,\n        avs_error_t err) {\n    assert(pending_request);\n\n    AVS_ASSERT(pending_request->handler.handle_result,\n               \"pending request with no response handler shouldn't be created\");\n\n    return pending_request->handler.handle_result(\n            (avs_coap_ctx_t *) ctx, result, err, response_msg,\n            pending_request->handler.handle_result_arg);\n}\n\nstatic inline bool\nis_list_ordered_by_expire_time(AVS_LIST(avs_coap_tcp_pending_request_t) list) {\n    AVS_LIST_ITERATE(list) {\n        AVS_LIST(avs_coap_tcp_pending_request_t) next = list;\n        AVS_LIST_ADVANCE(&next);\n        if (next\n                && !avs_time_monotonic_before(list->expire_time,\n                                              next->expire_time)) {\n            return false;\n        }\n    }\n    return true;\n}\n\nstatic AVS_LIST(avs_coap_tcp_pending_request_t) *\ninsert_pending_request(AVS_LIST(avs_coap_tcp_pending_request_t) *list_ptr,\n                       AVS_LIST(avs_coap_tcp_pending_request_t) req) {\n    AVS_LIST_ITERATE_PTR(list_ptr) {\n        if (avs_time_monotonic_before(req->expire_time,\n                                      (*list_ptr)->expire_time)) {\n            break;\n        }\n    }\n    AVS_LIST_INSERT(list_ptr, req);\n\n    assert(*list_ptr == req);\n    AVS_ASSERT(is_list_ordered_by_expire_time(*list_ptr),\n               \"pending request list must be ordered by expire_time\");\n    return list_ptr;\n}\n\nstatic avs_coap_tcp_pending_request_t *detach_pending_request(\n        AVS_LIST(avs_coap_tcp_pending_request_t) *pending_request_ptr) {\n    assert(pending_request_ptr);\n    assert(*pending_request_ptr);\n    return AVS_LIST_DETACH(pending_request_ptr);\n}\n\nstatic void finish_pending_request_with_error(\n        avs_coap_tcp_ctx_t *ctx,\n        AVS_LIST(avs_coap_tcp_pending_request_t) *pending_request_ptr,\n        avs_coap_send_result_t result,\n        avs_error_t err) {\n    AVS_ASSERT(result == AVS_COAP_SEND_RESULT_CANCEL\n                       || result == AVS_COAP_SEND_RESULT_FAIL,\n               \"use try_finish_pending_request instead\");\n    // Element must be detached to avoid finishing the timed-out request twice\n    // when sched_run() is called in response handler.\n    avs_coap_tcp_pending_request_t *detached_request =\n            detach_pending_request(pending_request_ptr);\n    LOG(TRACE, _(\"finishing pending request, token \") \"%s\",\n        AVS_COAP_TOKEN_HEX(&detached_request->token));\n    (void) call_pending_request_response_handler(ctx, detached_request, NULL,\n                                                 result, err);\n    AVS_LIST_DELETE(&detached_request);\n}\n\nstatic void try_finish_pending_request(\n        avs_coap_tcp_ctx_t *ctx,\n        AVS_LIST(avs_coap_tcp_pending_request_t) *pending_request_ptr,\n        const avs_coap_borrowed_msg_t *msg,\n        avs_coap_send_result_t result,\n        avs_error_t err) {\n    // Element must be detached to avoid finishing the timed-out request twice\n    // when sched_run() is called in response handler.\n    avs_coap_tcp_pending_request_t *detached_request =\n            detach_pending_request(pending_request_ptr);\n    LOG(TRACE, _(\"finishing pending request, token \") \"%s\",\n        AVS_COAP_TOKEN_HEX(&detached_request->token));\n    avs_coap_send_result_handler_result_t handler_result =\n            call_pending_request_response_handler(ctx, detached_request, msg,\n                                                  result, err);\n    if (msg && result == AVS_COAP_SEND_RESULT_OK\n            && handler_result != AVS_COAP_RESPONSE_ACCEPTED) {\n        insert_pending_request(&ctx->pending_requests, detached_request);\n    } else {\n        AVS_LIST_DELETE(&detached_request);\n    }\n}\n\nstatic AVS_LIST(avs_coap_tcp_pending_request_t) *\nfind_pending_request_ptr_by_token(\n        AVS_LIST(avs_coap_tcp_pending_request_t) *pending_requests,\n        const avs_coap_token_t *token) {\n    AVS_LIST_ITERATE_PTR(pending_requests) {\n        if (avs_coap_token_equal(&(*pending_requests)->token, token)) {\n            return pending_requests;\n        }\n    }\n    return NULL;\n}\n\navs_time_monotonic_t\n_avs_coap_tcp_fail_expired_pending_requests(avs_coap_tcp_ctx_t *ctx) {\n    while (ctx->pending_requests\n           && avs_time_monotonic_valid(ctx->pending_requests->expire_time)\n           && !avs_time_monotonic_before(avs_time_monotonic_now(),\n                                         ctx->pending_requests->expire_time)) {\n        finish_pending_request_with_error(ctx, &ctx->pending_requests,\n                                          AVS_COAP_SEND_RESULT_FAIL,\n                                          _avs_coap_err(AVS_COAP_ERR_TIMEOUT));\n    }\n\n    if (ctx->pending_requests) {\n        return ctx->pending_requests->expire_time;\n    } else {\n        return AVS_TIME_MONOTONIC_INVALID;\n    }\n}\n\nstatic inline void\nrefresh_timeout(avs_coap_tcp_ctx_t *ctx,\n                AVS_LIST(avs_coap_tcp_pending_request_t) *req_ptr) {\n    assert(ctx);\n    assert(req_ptr);\n    assert(*req_ptr);\n    assert(avs_time_monotonic_valid((*req_ptr)->expire_time));\n\n    if (_avs_coap_tcp_update_recv_deadline(ctx, &(*req_ptr)->expire_time)) {\n        finish_pending_request_with_error(ctx, req_ptr,\n                                          AVS_COAP_SEND_RESULT_FAIL,\n                                          avs_errno(AVS_UNKNOWN_ERROR));\n    } else {\n        insert_pending_request(&ctx->pending_requests,\n                               AVS_LIST_DETACH(req_ptr));\n        _avs_coap_reschedule_retry_or_request_expired_job(\n                (avs_coap_ctx_t *) ctx, (*req_ptr)->expire_time);\n    }\n}\n\nvoid _avs_coap_tcp_handle_pending_request(\n        avs_coap_tcp_ctx_t *ctx,\n        const avs_coap_borrowed_msg_t *msg,\n        avs_coap_tcp_pending_request_status_t status,\n        avs_error_t err) {\n    AVS_LIST(avs_coap_tcp_pending_request_t) *pending_req_ptr =\n            find_pending_request_ptr_by_token(&ctx->pending_requests,\n                                              &msg->token);\n    if (!pending_req_ptr) {\n        LOG(DEBUG,\n            _(\"received response does not match any known request, ignoring\"));\n        return;\n    }\n\n    switch (status) {\n    case PENDING_REQUEST_STATUS_COMPLETED:\n        try_finish_pending_request(ctx, pending_req_ptr, msg,\n                                   AVS_COAP_SEND_RESULT_OK, AVS_OK);\n        return;\n\n    case PENDING_REQUEST_STATUS_PARTIAL_CONTENT:\n        call_pending_request_response_handler(\n                ctx, *pending_req_ptr, msg,\n                AVS_COAP_SEND_RESULT_PARTIAL_CONTENT, AVS_OK);\n        // Request may be canceled in call above - not directly, but by\n        // calling avs_sched_run() in user's handler for example.\n        pending_req_ptr =\n                find_pending_request_ptr_by_token(&ctx->pending_requests,\n                                                  &msg->token);\n        if (pending_req_ptr) {\n            refresh_timeout(ctx, pending_req_ptr);\n        }\n        return;\n\n    case PENDING_REQUEST_STATUS_IGNORE:\n        refresh_timeout(ctx, pending_req_ptr);\n        return;\n\n    case PENDING_REQUEST_STATUS_FINISH_IGNORE:\n        finish_pending_request_with_error(ctx, pending_req_ptr,\n                                          AVS_COAP_SEND_RESULT_FAIL, err);\n        // error already reported; do not propagate it further\n        return;\n    }\n\n    AVS_UNREACHABLE(\"invalid enum value\");\n}\n\navs_error_t _avs_coap_tcp_create_pending_request(\n        avs_coap_tcp_ctx_t *ctx,\n        AVS_LIST(avs_coap_tcp_pending_request_t) **out_request,\n        const avs_coap_token_t *token,\n        avs_coap_send_result_handler_t *handler,\n        void *handler_arg) {\n    assert(out_request && !*out_request);\n    AVS_LIST(avs_coap_tcp_pending_request_t) req =\n            AVS_LIST_NEW_ELEMENT(avs_coap_tcp_pending_request_t);\n    if (!req) {\n        LOG_OOM();\n        return avs_errno(AVS_ENOMEM);\n    }\n\n    *req = (avs_coap_tcp_pending_request_t) {\n        .handler = (avs_coap_tcp_response_handler_t) {\n            .handle_result = handler,\n            .handle_result_arg = handler_arg\n        },\n        .token = *token,\n        .expire_time = AVS_TIME_MONOTONIC_INVALID\n    };\n    if (_avs_coap_tcp_update_recv_deadline(ctx, &req->expire_time)) {\n        AVS_LIST_DELETE(&req);\n        LOG(DEBUG, _(\"failed to create pending request - cannot calculate \"\n                     \"receive deadline\"));\n        return avs_errno(AVS_UNKNOWN_ERROR);\n    }\n\n    *out_request = insert_pending_request(&ctx->pending_requests, req);\n    _avs_coap_reschedule_retry_or_request_expired_job((avs_coap_ctx_t *) ctx,\n                                                      req->expire_time);\n\n    return AVS_OK;\n}\n\nvoid _avs_coap_tcp_remove_pending_request(\n        AVS_LIST(avs_coap_tcp_pending_request_t) *pending_request_ptr) {\n    assert(pending_request_ptr);\n    assert(*pending_request_ptr);\n    LOG(TRACE, _(\"removing request with token \") \"%s\",\n        AVS_COAP_TOKEN_HEX(&(*pending_request_ptr)->token));\n    AVS_LIST_DELETE(pending_request_ptr);\n}\n\nvoid _avs_coap_tcp_abort_pending_request_by_token(avs_coap_tcp_ctx_t *ctx,\n                                                  const avs_coap_token_t *token,\n                                                  avs_coap_send_result_t result,\n                                                  avs_error_t fail_err) {\n    AVS_ASSERT(result == AVS_COAP_SEND_RESULT_CANCEL\n                       || result == AVS_COAP_SEND_RESULT_FAIL,\n               \"abort called with success result\");\n    AVS_LIST(avs_coap_tcp_pending_request_t) *pending_request_ptr =\n            find_pending_request_ptr_by_token(&ctx->pending_requests, token);\n    if (pending_request_ptr) {\n        LOG(TRACE, _(\"aborting request with token \") \"%s\",\n            AVS_COAP_TOKEN_HEX(&(*pending_request_ptr)->token));\n        finish_pending_request_with_error(ctx, pending_request_ptr, result,\n                                          fail_err);\n    }\n}\n\nvoid _avs_coap_tcp_cancel_all_pending_requests(avs_coap_tcp_ctx_t *ctx) {\n    while (ctx->pending_requests) {\n        finish_pending_request_with_error(\n                ctx, &ctx->pending_requests, AVS_COAP_SEND_RESULT_CANCEL,\n                _avs_coap_err(AVS_COAP_ERR_EXCHANGE_CANCELED));\n    }\n}\n\n#endif // WITH_AVS_COAP_TCP\n"
  },
  {
    "path": "deps/avs_coap/src/tcp/avs_coap_tcp_pending_requests.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef AVS_COAP_SRC_TCP_PENDING_REQUESTS_H\n#define AVS_COAP_SRC_TCP_PENDING_REQUESTS_H\n\n#include <avsystem/commons/avs_list.h>\n\n#include \"avs_coap_ctx.h\"\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\nstruct avs_coap_tcp_ctx_struct;\n\ntypedef struct avs_coap_tcp_pending_request_struct\n        avs_coap_tcp_pending_request_t;\n\ntypedef enum {\n    PENDING_REQUEST_STATUS_COMPLETED = 0,\n    PENDING_REQUEST_STATUS_PARTIAL_CONTENT,\n    PENDING_REQUEST_STATUS_IGNORE,\n    PENDING_REQUEST_STATUS_FINISH_IGNORE\n} avs_coap_tcp_pending_request_status_t;\n\ntypedef struct {\n    avs_coap_send_result_handler_t *handle_result;\n    void *handle_result_arg;\n} avs_coap_tcp_response_handler_t;\n\navs_error_t _avs_coap_tcp_create_pending_request(\n        struct avs_coap_tcp_ctx_struct *ctx,\n        AVS_LIST(avs_coap_tcp_pending_request_t) **out_request,\n        const avs_coap_token_t *token,\n        avs_coap_send_result_handler_t *handler,\n        void *handler_arg);\n\nvoid _avs_coap_tcp_handle_pending_request(\n        struct avs_coap_tcp_ctx_struct *ctx,\n        const avs_coap_borrowed_msg_t *msg,\n        avs_coap_tcp_pending_request_status_t result,\n        avs_error_t err);\n\n/**\n * Cancels pending request without calling user's handler.\n */\nvoid _avs_coap_tcp_remove_pending_request(\n        AVS_LIST(avs_coap_tcp_pending_request_t) *pending_request_ptr);\n\nvoid _avs_coap_tcp_abort_pending_request_by_token(\n        struct avs_coap_tcp_ctx_struct *ctx,\n        const avs_coap_token_t *token,\n        avs_coap_send_result_t result,\n        avs_error_t fail_err);\n\nvoid _avs_coap_tcp_cancel_all_pending_requests(\n        struct avs_coap_tcp_ctx_struct *ctx);\n\navs_time_monotonic_t _avs_coap_tcp_fail_expired_pending_requests(\n        struct avs_coap_tcp_ctx_struct *ctx);\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif // AVS_COAP_SRC_TCP_PENDING_REQUESTS_H\n"
  },
  {
    "path": "deps/avs_coap/src/tcp/avs_coap_tcp_signaling.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <avs_coap_init.h>\n\n#ifdef WITH_AVS_COAP_TCP\n\n#    include <avsystem/commons/avs_errno.h>\n\n#    include \"avs_coap_tcp_utils.h\"\n\n#    include \"avs_coap_code_utils.h\"\n#    include \"options/avs_coap_iterator.h\"\n\n#    define MODULE_NAME coap_tcp\n#    include <avs_coap_x_log_config.h>\n\n#    include \"avs_coap_tcp_ctx.h\"\n#    include \"avs_coap_tcp_signaling.h\"\n#    include \"options/avs_coap_options.h\"\n\nVISIBILITY_SOURCE_BEGIN\n\nstatic avs_error_t handle_csm(avs_coap_tcp_csm_t *csm,\n                              const avs_coap_borrowed_msg_t *msg) {\n    csm->recv_deadline = AVS_TIME_MONOTONIC_INVALID;\n    bool size_updated = false;\n    bool block_updated = false;\n    const avs_coap_options_t *opts = &msg->options;\n    avs_coap_option_iterator_t it =\n            _avs_coap_optit_begin((avs_coap_options_t *) (intptr_t) opts);\n\n    // Options are guaranteed to be valid here, because they were checked\n    // during receiving of the message.\n    assert(_avs_coap_options_valid(opts));\n\n    for (; !_avs_coap_optit_end(&it); _avs_coap_optit_next(&it)) {\n        assert(it.curr_opt >= opts->begin);\n        const avs_coap_option_t *opt = _avs_coap_optit_current(&it);\n        uint32_t opt_number = _avs_coap_optit_number(&it);\n\n        switch (opt_number) {\n        case _AVS_COAP_OPTION_MAX_MESSAGE_SIZE: {\n            if (!size_updated) {\n                uint32_t max_msg_size;\n                if (_avs_coap_option_u32_value(opt, &max_msg_size)) {\n                    LOG(DEBUG, _(\"Max Message Size: value too big\"));\n                    // TODO Add Bad-CSM-Option to Abort message?\n                    return _avs_coap_err(\n                            AVS_COAP_ERR_TCP_MALFORMED_CSM_OPTIONS_RECEIVED);\n                }\n                csm->max_message_size = max_msg_size;\n                size_updated = true;\n            }\n            break;\n        }\n        case _AVS_COAP_OPTION_BLOCK_WISE_TRANSFER_CAPABILITY:\n            // TODO T2251\n            // Inform upper layer that blocks are not supported by peer.\n            // Currently we assume, that blocks are supported.\n            if (!block_updated) {\n                csm->block_wise_transfer_capable = true;\n                block_updated = true;\n            }\n            break;\n        default:\n            // Options passed validation before this function was called, so\n            // opt_number can be safely casted here.\n            if (_avs_coap_option_is_critical((uint16_t) opt_number)) {\n                LOG(DEBUG, _(\"unknown critical option\"));\n                return _avs_coap_err(\n                        AVS_COAP_ERR_TCP_UNKNOWN_CSM_CRITICAL_OPTION_RECEIVED);\n            }\n            break;\n        }\n    }\n\n    if (size_updated || block_updated) {\n        LOG(DEBUG,\n            _(\"Peer's Capabilities and Settings updated. \"\n              \"Max-Message-Size: \") \"%u\" _(\", Block-Wise-Transfer \"\n                                           \"Capability: \") \"%s\",\n            (unsigned) csm->max_message_size,\n            csm->block_wise_transfer_capable ? \"yes\" : \"no\");\n    }\n\n    return AVS_OK;\n}\n\nstatic avs_error_t send_pong(avs_coap_tcp_ctx_t *ctx,\n                             const avs_coap_borrowed_msg_t *msg) {\n    uint8_t buf[8];\n    avs_coap_borrowed_msg_t pong = {\n        .code = AVS_COAP_CODE_PONG,\n        .token = msg->token,\n        .options = avs_coap_options_create_empty(buf, sizeof(buf))\n    };\n    (void) avs_coap_options_add_empty(&pong.options, _AVS_COAP_OPTION_CUSTODY);\n    return _avs_coap_tcp_send_msg(ctx, &pong);\n}\n\nstatic void handle_abort(const avs_coap_borrowed_msg_t *msg) {\n    LOG(DEBUG, _(\"Abort message received, the context should be destroyed\"));\n    if (msg->payload_size) {\n        size_t bytes_escaped = 0;\n        char escaped_string[128];\n        do {\n            bytes_escaped += _avs_coap_tcp_escape_payload(\n                    (const char *) msg->payload + bytes_escaped,\n                    msg->payload_size - bytes_escaped,\n                    escaped_string,\n                    sizeof(escaped_string));\n            LOG(DEBUG, _(\"diagnostic payload: \") \"%s\", escaped_string);\n        } while (bytes_escaped < msg->payload_size);\n    }\n}\n\navs_error_t\n_avs_coap_tcp_handle_signaling_message(avs_coap_tcp_ctx_t *ctx,\n                                       avs_coap_tcp_csm_t *peer_csm,\n                                       const avs_coap_borrowed_msg_t *msg) {\n    if (msg->payload_offset + msg->payload_size != msg->total_payload_size) {\n        LOG(DEBUG, _(\"ignoring non-last chunk of Signaling message\"));\n        return AVS_OK;\n    }\n\n    switch (msg->code) {\n    case AVS_COAP_CODE_CSM:\n        return handle_csm(peer_csm, msg);\n    case AVS_COAP_CODE_PING:\n        return send_pong(ctx, msg);\n    case AVS_COAP_CODE_PONG:\n        LOG(DEBUG, _(\"unexpected Pong message arrived, ignoring\"));\n        break;\n    case AVS_COAP_CODE_RELEASE:\n        // All responses to incoming requests were sent already. If there is\n        // some not completed block request, we can ignore it because:\n        // \"It is NOT RECOMMENDED for the sender of a Release message to\n        //  continue sending requests on the connection it already indicated to\n        //  be released: the peer might close the connection at any time and\n        //  miss those requests.  The peer is not obligated to check for this\n        //  condition, though.\"\n        LOG(DEBUG,\n            _(\"Release message received, the context should be destroyed\"));\n        return _avs_coap_err(AVS_COAP_ERR_TCP_RELEASE_RECEIVED);\n    case AVS_COAP_CODE_ABORT:\n        handle_abort(msg);\n        return _avs_coap_err(AVS_COAP_ERR_TCP_ABORT_RECEIVED);\n    default:\n        LOG(DEBUG, _(\"unknown Signaling Message code, ignoring\"));\n    }\n    return AVS_OK;\n}\n\n#endif // WITH_AVS_COAP_TCP\n"
  },
  {
    "path": "deps/avs_coap/src/tcp/avs_coap_tcp_signaling.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef AVS_COAP_SRC_TCP_SIGNALING_H\n#define AVS_COAP_SRC_TCP_SIGNALING_H\n\n#include <avsystem/coap/option.h>\n#include <avsystem/coap/token.h>\n\n#include \"tcp/avs_coap_tcp_msg.h\"\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\nstruct avs_coap_tcp_ctx_struct;\n\ntypedef struct {\n    // if recv_deadline is invalid, it means that peer CSM has been received.\n    avs_time_monotonic_t recv_deadline;\n    // max_message_size is a maximum single message size (starting from first\n    // byte of the header and ending at the end of the message payload) which\n    // peer can receive.\n    size_t max_message_size;\n    bool block_wise_transfer_capable;\n} avs_coap_tcp_csm_t;\n\n/**\n * CoAP Signaling option codes, as defined in RFC 8323.\n * Codes reused between different options. Meaning depends on message code.\n */\n// clang-format off\n#define _AVS_COAP_OPTION_MAX_MESSAGE_SIZE               2\n#define _AVS_COAP_OPTION_BLOCK_WISE_TRANSFER_CAPABILITY 4\n#define _AVS_COAP_OPTION_CUSTODY                        2\n#define _AVS_COAP_OPTION_ALTERNATIVE_ADDRESS            2\n#define _AVS_COAP_OPTION_HOLD_OFF                       4\n#define _AVS_COAP_OPTION_BAD_CSM_OPTION                 2\n\n#define AVS_COAP_CODE_CSM     AVS_COAP_CODE(7, 1)\n#define AVS_COAP_CODE_PING    AVS_COAP_CODE(7, 2)\n#define AVS_COAP_CODE_PONG    AVS_COAP_CODE(7, 3)\n#define AVS_COAP_CODE_RELEASE AVS_COAP_CODE(7, 4)\n#define AVS_COAP_CODE_ABORT   AVS_COAP_CODE(7, 5)\n// clang-format on\n\navs_error_t\n_avs_coap_tcp_handle_signaling_message(struct avs_coap_tcp_ctx_struct *ctx,\n                                       avs_coap_tcp_csm_t *peer_csm,\n                                       const avs_coap_borrowed_msg_t *msg);\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif // AVS_COAP_SRC_TCP_SIGNALING_H\n"
  },
  {
    "path": "deps/avs_coap/src/tcp/avs_coap_tcp_utils.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <avs_coap_init.h>\n\n#ifdef WITH_AVS_COAP_TCP\n\n#    include <ctype.h>\n\n#    include <avsystem/commons/avs_utils.h>\n\n#    include \"avs_coap_tcp_utils.h\"\n\nVISIBILITY_SOURCE_BEGIN\n\nstatic int\nadd_escaped_char(char *escaped_buf, size_t remaining_size, char to_escape) {\n    char *format = NULL;\n    if (isprint((unsigned char) to_escape)) {\n        switch (to_escape) {\n        case '\\\"':\n        case '\\'':\n        case '\\\\':\n            format = \"\\\\%c\";\n            break;\n\n        default:\n            format = \"%c\";\n            break;\n        }\n    } else {\n        format = \"\\\\x%02X\";\n    }\n\n    return avs_simple_snprintf(\n            escaped_buf, remaining_size, format, (unsigned char) to_escape);\n}\n\nsize_t _avs_coap_tcp_escape_payload(const char *payload,\n                                    size_t payload_size,\n                                    char *escaped_buf,\n                                    size_t escaped_buf_size) {\n    assert(escaped_buf);\n    assert(escaped_buf_size);\n\n    size_t offset = 0;\n    size_t i = 0;\n    for (; i < payload_size; i++) {\n        int bytes_written = add_escaped_char(\n                escaped_buf + offset, escaped_buf_size - offset, payload[i]);\n        if (bytes_written <= 0) {\n            break;\n        }\n        offset += (size_t) bytes_written;\n    }\n    escaped_buf[offset] = '\\0';\n    return i;\n}\n\n#endif // WITH_AVS_COAP_TCP\n"
  },
  {
    "path": "deps/avs_coap/src/tcp/avs_coap_tcp_utils.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef AVS_COAP_SRC_TCP_UTILS_H\n#define AVS_COAP_SRC_TCP_UTILS_H\n\n#include <avs_coap_init.h>\n\n#include <stdint.h>\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\n/**\n * Converts @p payload, which may contain non-printable characters, to printable\n * string.\n *\n * @returns Number of bytes escaped. If it's not equal to @p payload_size, this\n *          function may be called again with @p payload pointer incremented by\n *          number of bytes escaped to convert further chunks of data.\n *\n * Note: @p converted message is always ended with NULL character.\n */\nsize_t _avs_coap_tcp_escape_payload(const char *payload,\n                                    size_t payload_size,\n                                    char *escaped_buf,\n                                    size_t escaped_buf_size);\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif // AVS_COAP_SRC_TCP_UTILS_H\n"
  },
  {
    "path": "deps/avs_coap/src/udp/avs_coap_udp_ctx.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <avs_coap_init.h>\n\n#ifdef WITH_AVS_COAP_UDP\n\n#    include <assert.h>\n#    include <inttypes.h>\n\n#    include <avsystem/commons/avs_errno.h>\n#    include <avsystem/commons/avs_shared_buffer.h>\n#    include <avsystem/commons/avs_socket.h>\n#    include <avsystem/commons/avs_utils.h>\n\n#    include <avsystem/coap/option.h>\n#    include <avsystem/coap/udp.h>\n\n#    include \"avs_coap_code_utils.h\"\n#    include \"avs_coap_ctx_vtable.h\"\n#    include \"options/avs_coap_option.h\"\n\n#    define MODULE_NAME coap_udp\n#    include <avs_coap_x_log_config.h>\n\n#    include \"udp/avs_coap_udp_ctx.h\"\n#    include \"udp/avs_coap_udp_msg_cache.h\"\n\n#    include \"avs_coap_common_utils.h\"\n#    include \"options/avs_coap_options.h\"\n#    include \"udp/avs_coap_udp_tx_params.h\"\n\nVISIBILITY_SOURCE_BEGIN\n\n#    ifdef AVS_UNIT_TESTING\n__typeof__(_avs_coap_udp_initial_retry_state) *AVS_UNIT_MOCK(\n        _avs_coap_udp_initial_retry_state);\n\nvoid _avs_unit_mock_constructor_avs_coap_udp_initial_retry_state(void) {\n    avs_unit_mock_add__((avs_unit_mock_func_ptr *) &AVS_UNIT_MOCK(\n            _avs_coap_udp_initial_retry_state));\n}\n#    endif // AVS_UNIT_TESTING\n\n#    ifdef WITH_AVS_COAP_OBSERVE\nAVS_STATIC_ASSERT(AVS_COAP_UDP_NOTIFY_CACHE_SIZE > 0,\n                  notify_cache_must_have_at_least_one_element);\n\nstatic inline const avs_coap_token_t *\ncoap_udp_notify_cache_get(const avs_coap_udp_notify_cache_t *cache,\n                          uint16_t msg_id) {\n    for (size_t i = 0; i < cache->size; ++i) {\n        if (cache->entries[i].msg_id == msg_id) {\n            return &cache->entries[i].token;\n        }\n    }\n\n    return NULL;\n}\n\nstatic inline void\ncoap_udp_notify_cache_drop_entry(avs_coap_udp_notify_cache_t *cache,\n                                 avs_coap_udp_sent_notify_t *entry) {\n    const avs_coap_udp_sent_notify_t *end = cache->entries + cache->size;\n\n    assert(cache->entries <= entry && entry < cache->entries + cache->size);\n    assert(entry < end);\n\n    memmove(entry, entry + 1,\n            (size_t) (end - entry - 1) * sizeof(cache->entries[0]));\n    --cache->size;\n}\n\nstatic inline void\ncoap_udp_notify_cache_drop(avs_coap_udp_notify_cache_t *cache,\n                           uint16_t msg_id) {\n    for (size_t i = 0; i < cache->size; ++i) {\n        if (cache->entries[i].msg_id == msg_id) {\n            coap_udp_notify_cache_drop_entry(cache, &cache->entries[i]);\n\n            // cache is not supposed to have more than one entry with the same\n            // ID at the same time\n            assert(coap_udp_notify_cache_get(cache, msg_id) == NULL);\n            return;\n        }\n    }\n}\n\nstatic inline void coap_udp_notify_cache_put(avs_coap_udp_notify_cache_t *cache,\n                                             uint16_t msg_id,\n                                             const avs_coap_token_t *token) {\n    if (cache->size == AVS_ARRAY_SIZE(cache->entries)) {\n        coap_udp_notify_cache_drop_entry(cache, &cache->entries[0]);\n    }\n    assert(cache->size < AVS_ARRAY_SIZE(cache->entries));\n\n    cache->entries[cache->size] = (avs_coap_udp_sent_notify_t) {\n        .msg_id = msg_id,\n        .token = *token\n    };\n\n    ++cache->size;\n}\n#    endif // WITH_AVS_COAP_OBSERVE\n\nAVS_STATIC_ASSERT(offsetof(avs_coap_udp_ctx_t, vtable) == 0,\n                  vtable_field_must_be_first_in_udp_ctx_t);\n\nstatic void update_last_mtu_from_socket(avs_coap_udp_ctx_t *ctx) {\n    avs_net_socket_opt_value_t opt_value;\n\n    if (avs_is_err(avs_net_socket_get_opt(\n                ctx->base.socket, AVS_NET_SOCKET_OPT_INNER_MTU, &opt_value))) {\n        LOG(DEBUG, _(\"socket MTU unknown\"));\n    } else if (opt_value.mtu <= 0) {\n        LOG(DEBUG, _(\"socket MTU invalid: \") \"%d\", opt_value.mtu);\n    } else {\n        if ((size_t) opt_value.mtu != ctx->last_mtu) {\n            LOG(DEBUG, _(\"socket MTU changed: \") \"%u\" _(\" -> \") \"%d\",\n                (unsigned) ctx->last_mtu, opt_value.mtu);\n        } else {\n            LOG(TRACE, _(\"socket MTU: \") \"%d\", opt_value.mtu);\n        }\n\n        ctx->last_mtu = (size_t) opt_value.mtu;\n    }\n}\n\nstatic size_t udp_max_payload_size(size_t buffer_capacity,\n                                   size_t mtu,\n                                   size_t token_size,\n                                   size_t options_size) {\n    const size_t msg_size = (sizeof(avs_coap_udp_header_t) + token_size\n                             + options_size + sizeof(AVS_COAP_PAYLOAD_MARKER));\n    const size_t max_msg_size = AVS_MIN(mtu, buffer_capacity);\n\n    if (msg_size > max_msg_size) {\n        return 0;\n    }\n    return max_msg_size - msg_size;\n}\n\nstatic size_t\ncoap_udp_max_outgoing_payload_size(avs_coap_ctx_t *ctx_,\n                                   size_t token_size,\n                                   const avs_coap_options_t *options,\n                                   uint8_t code) {\n    (void) code;\n    avs_coap_udp_ctx_t *ctx = (avs_coap_udp_ctx_t *) ctx_;\n    update_last_mtu_from_socket(ctx);\n    return udp_max_payload_size(ctx->base.out_buffer->capacity, ctx->last_mtu,\n                                token_size, options ? options->size : 0);\n}\n\nstatic size_t\ncoap_udp_max_incoming_payload_size(avs_coap_ctx_t *ctx_,\n                                   size_t token_size,\n                                   const avs_coap_options_t *options,\n                                   uint8_t code) {\n    (void) code;\n    avs_coap_udp_ctx_t *ctx = (avs_coap_udp_ctx_t *) ctx_;\n    size_t incoming_mtu = ctx->forced_incoming_mtu;\n    if (incoming_mtu <= 0) {\n        update_last_mtu_from_socket(ctx);\n        incoming_mtu = ctx->last_mtu;\n    }\n    return udp_max_payload_size(ctx->base.in_buffer->capacity, incoming_mtu,\n                                token_size, options ? options->size : 0);\n}\n\nstatic uint16_t generate_id(avs_coap_udp_ctx_t *ctx) {\n    return ctx->last_msg_id++;\n}\n\nstatic size_t current_nstart(const avs_coap_udp_ctx_t *ctx) {\n    size_t started = 0;\n\n    AVS_LIST(avs_coap_udp_unconfirmed_msg_t) msg;\n    AVS_LIST_FOREACH(msg, ctx->unconfirmed_messages) {\n        if (!msg->hold) {\n            ++started;\n        } else {\n            break;\n        }\n    }\n\n    return started;\n}\n\nstatic size_t effective_nstart(const avs_coap_udp_ctx_t *ctx) {\n    // equivalent to:\n    // AVS_MIN(ctx->tx_params.nstart, AVS_LIST_SIZE(ctx->unconfirmed_messages))\n    size_t result = 0;\n    AVS_LIST(avs_coap_udp_unconfirmed_msg_t) msg;\n    AVS_LIST_FOREACH(msg, ctx->unconfirmed_messages) {\n        ++result;\n        if (result >= ctx->tx_params.nstart) {\n            break;\n        }\n    }\n    return result;\n}\n\nstatic void log_udp_msg_summary(const char *info,\n                                const avs_coap_udp_msg_t *msg) {\n    char observe_str[24] = \"\";\n#    ifdef WITH_AVS_COAP_OBSERVE\n    uint32_t observe;\n    if (avs_coap_options_get_observe(&msg->options, &observe) == 0) {\n        snprintf(observe_str, sizeof(observe_str), \", Observe %\" PRIu32,\n                 observe);\n    }\n#    endif // WITH_AVS_COAP_OBSERVE\n\n#    ifdef WITH_AVS_COAP_BLOCK\n    avs_coap_option_block_t block1;\n    bool has_block1 =\n            (avs_coap_options_get_block(&msg->options, AVS_COAP_BLOCK1, &block1)\n             == 0);\n    avs_coap_option_block_string_buf_t block1_str_buf = { \"\" };\n    if (has_block1) {\n        _avs_coap_option_block_string(&block1_str_buf, &block1);\n    }\n\n    avs_coap_option_block_t block2;\n    bool has_block2 =\n            (avs_coap_options_get_block(&msg->options, AVS_COAP_BLOCK2, &block2)\n             == 0);\n    avs_coap_option_block_string_buf_t block2_str_buf = { \"\" };\n    if (has_block2) {\n        _avs_coap_option_block_string(&block2_str_buf, &block2);\n    }\n\n    LOG(DEBUG, \"%s: %s (ID: %\" PRIu16 \", token: %s)%s%s%s%s%s, payload: %u B\",\n        info, AVS_COAP_CODE_STRING(msg->header.code),\n        _avs_coap_udp_header_get_id(&msg->header),\n        AVS_COAP_TOKEN_HEX(&msg->token), has_block1 ? \", \" : \"\",\n        block1_str_buf.str, has_block2 ? \", \" : \"\", block2_str_buf.str,\n        observe_str, (unsigned) msg->payload_size);\n#    else  // WITH_AVS_COAP_BLOCK\n    LOG(DEBUG, \"%s: %s (ID: %\" PRIu16 \", token: %s)%s, payload: %u B\", info,\n        AVS_COAP_CODE_STRING(msg->header.code),\n        _avs_coap_udp_header_get_id(&msg->header),\n        AVS_COAP_TOKEN_HEX(&msg->token), observe_str,\n        (unsigned) msg->payload_size);\n#    endif // WITH_AVS_COAP_BLOCK\n}\n\nstatic void try_cache_response(avs_coap_udp_ctx_t *ctx,\n                               const avs_coap_udp_msg_t *res) {\n#    ifdef WITH_AVS_COAP_OBSERVE\n    uint16_t msg_id = _avs_coap_udp_header_get_id(&res->header);\n    // if the cache still contains an entry with the same ID, drop it to not\n    // confuse Reset response to new message with a Cancel Observe to a\n    // previously sent Notify\n    coap_udp_notify_cache_drop(&ctx->notify_cache, msg_id);\n\n    if (!avs_coap_code_is_response(res->header.code)) {\n        return;\n    }\n\n    avs_coap_udp_type_t type = _avs_coap_udp_header_get_type(&res->header);\n    if ((type == AVS_COAP_UDP_TYPE_CONFIRMABLE\n         || type == AVS_COAP_UDP_TYPE_NON_CONFIRMABLE)\n            && _avs_coap_option_exists(&res->options,\n                                       AVS_COAP_OPTION_OBSERVE)) {\n        // Note: Reset response is only expected for CON/NON messages, so we\n        // don't store anything for other types.\n        coap_udp_notify_cache_put(&ctx->notify_cache, msg_id, &res->token);\n    }\n#    endif // WITH_AVS_COAP_OBSERVE\n\n    if (!ctx->response_cache) {\n        return;\n    }\n\n    char addr[AVS_ADDRSTRLEN];\n    char port[sizeof(\"65535\")];\n    if (avs_is_err(avs_net_socket_get_remote_host(ctx->base.socket, addr,\n                                                  sizeof(addr)))\n            || avs_is_err(avs_net_socket_get_remote_port(ctx->base.socket, port,\n                                                         sizeof(port)))) {\n        LOG(DEBUG, _(\"could not get remote host/port\"));\n        return;\n    }\n\n    (void) _avs_coap_udp_response_cache_add(ctx->response_cache, addr, port,\n                                            res, &ctx->tx_params);\n}\n\nstatic avs_error_t coap_udp_send_serialized_msg(avs_coap_udp_ctx_t *ctx,\n                                                const avs_coap_udp_msg_t *msg,\n                                                const void *msg_buf,\n                                                size_t msg_size) {\n    log_udp_msg_summary(\"send\", msg);\n\n    try_cache_response(ctx, msg);\n\n    avs_error_t err = avs_net_socket_send(ctx->base.socket, msg_buf, msg_size);\n    if (avs_is_err(err)) {\n        LOG(DEBUG, _(\"send failed: \") \"%s\", AVS_COAP_STRERROR(err));\n    }\n    return err;\n}\n\nstatic AVS_LIST(avs_coap_udp_unconfirmed_msg_t) *\nfind_unconfirmed_insert_ptr(avs_coap_udp_ctx_t *ctx,\n                            const avs_coap_udp_unconfirmed_msg_t *new_elem) {\n    AVS_LIST(avs_coap_udp_unconfirmed_msg_t) *list_ptr = (AVS_LIST(\n            avs_coap_udp_unconfirmed_msg_t) *) &ctx->unconfirmed_messages;\n\n    AVS_LIST_ITERATE_PTR(list_ptr) {\n        if (!new_elem->hold && (*list_ptr)->hold) {\n            return list_ptr;\n        } else if (new_elem->hold == (*list_ptr)->hold\n                   && avs_time_monotonic_before(new_elem->next_retransmit,\n                                                (*list_ptr)->next_retransmit)) {\n            return list_ptr;\n        }\n    }\n\n    assert(list_ptr);\n    assert(*list_ptr == NULL);\n    return list_ptr;\n}\n\nstatic AVS_LIST(avs_coap_udp_unconfirmed_msg_t) *\nfind_first_held_unconfirmed_ptr(avs_coap_udp_ctx_t *ctx) {\n    AVS_LIST(avs_coap_udp_unconfirmed_msg_t) *unconfirmed_ptr;\n\n    AVS_LIST_FOREACH_PTR(unconfirmed_ptr, &ctx->unconfirmed_messages) {\n        if ((*unconfirmed_ptr)->hold) {\n            return unconfirmed_ptr;\n        }\n    }\n\n    return NULL;\n}\n\nstatic void reschedule_retransmission_job(avs_coap_udp_ctx_t *ctx) {\n    if (ctx->unconfirmed_messages) {\n        avs_time_monotonic_t target_time;\n        if (current_nstart(ctx) < effective_nstart(ctx)) {\n            // There are requests we need to send ASAP\n            target_time = avs_time_monotonic_now();\n        } else {\n            target_time = ((avs_coap_udp_unconfirmed_msg_t *)\n                                   ctx->unconfirmed_messages)\n                                  ->next_retransmit;\n        }\n        _avs_coap_reschedule_retry_or_request_expired_job(\n                (avs_coap_ctx_t *) ctx, target_time);\n    }\n}\n\nstatic inline avs_coap_borrowed_msg_t\nborrowed_msg_from_udp_msg(const avs_coap_udp_msg_t *msg) {\n    return (avs_coap_borrowed_msg_t) {\n        .code = msg->header.code,\n        .token = msg->token,\n        .options = msg->options,\n        .payload = msg->payload,\n        .payload_size = msg->payload_size,\n        .total_payload_size = msg->payload_size\n    };\n}\n\nstatic avs_coap_send_result_handler_result_t\ncall_send_result_handler(avs_coap_udp_ctx_t *ctx,\n                         avs_coap_udp_unconfirmed_msg_t *unconfirmed,\n                         const avs_coap_udp_msg_t *response_msg,\n                         avs_coap_send_result_t result,\n                         avs_error_t fail_err) {\n    assert(ctx);\n    assert(unconfirmed);\n    AVS_ASSERT(unconfirmed->send_result_handler,\n               \"unconfirmed_msg objects with no send_result_handler \"\n               \"are not supposed to be created\");\n    (void) ctx;\n    if (result == AVS_COAP_SEND_RESULT_FAIL) {\n        AVS_ASSERT(avs_is_err(fail_err), \"fail_errno not set on failure\");\n    } else {\n        AVS_ASSERT(avs_is_ok(fail_err), \"fail_errno set on success\");\n    }\n\n    avs_coap_borrowed_msg_t response_buf;\n    const avs_coap_borrowed_msg_t *response = NULL;\n    if (response_msg) {\n        response_buf = borrowed_msg_from_udp_msg(response_msg);\n        response = &response_buf;\n    }\n\n    return unconfirmed->send_result_handler(\n            (avs_coap_ctx_t *) ctx, result, fail_err, response,\n            unconfirmed->send_result_handler_arg);\n}\n\nstatic inline const char *send_result_string(avs_coap_send_result_t result) {\n    switch (result) {\n    case AVS_COAP_SEND_RESULT_PARTIAL_CONTENT:\n        return \"partial content\";\n    case AVS_COAP_SEND_RESULT_OK:\n        return \"ok\";\n    case AVS_COAP_SEND_RESULT_FAIL:\n        return \"fail\";\n    case AVS_COAP_SEND_RESULT_CANCEL:\n        return \"cancel\";\n    }\n\n    return \"<unknown>\";\n}\n\nstatic void resume_next_unconfirmed(avs_coap_udp_ctx_t *ctx) {\n    AVS_LIST(avs_coap_udp_unconfirmed_msg_t) *unconfirmed_ptr =\n            find_first_held_unconfirmed_ptr(ctx);\n    if (!unconfirmed_ptr) {\n        return;\n    }\n\n    avs_time_monotonic_t next_retransmit = avs_time_monotonic_add(\n            avs_time_monotonic_now(),\n            (*unconfirmed_ptr)->retry_state.recv_timeout);\n    if (!avs_time_monotonic_valid(next_retransmit)) {\n        LOG(ERROR,\n            _(\"unable to schedule retransmit: calculated retransmit time is \"\n              \"invalid; either the monotonic clock, UDP tx params are too \"\n              \"large to handle or PRNG failed\"));\n\n        // We can't rely on getting valid times for any held job. Fail all\n        // of them immediately.\n\n        // Detach held messages so that they can't get unheld in the send result\n        // handler\n        AVS_LIST(avs_coap_udp_unconfirmed_msg_t) held_messages =\n                *unconfirmed_ptr;\n        *unconfirmed_ptr = NULL;\n\n        while (held_messages) {\n            // Do not use fail_unconfirmed - it indirectly calls this function\n            // again, which may result in\n            // AVS_LIST_SIZE(ctx->unconfirmed_messages) recursive calls .\n            //\n            // Note: this loop may be infinite in the most degenerate case\n            // where next_retransmit is an invalid time **just once** and every\n            // response handler calls avs_coap_client_send_async_request, adding\n            // a new held entry to the context.\n            avs_coap_udp_unconfirmed_msg_t *unconfirmed =\n                    AVS_LIST_DETACH(&held_messages);\n            (void) call_send_result_handler(\n                    ctx, unconfirmed, NULL, AVS_COAP_SEND_RESULT_FAIL,\n                    _avs_coap_err(AVS_COAP_ERR_TIME_INVALID));\n            AVS_LIST_DELETE(&unconfirmed);\n        }\n\n        return;\n    }\n\n    AVS_LIST(avs_coap_udp_unconfirmed_msg_t) unconfirmed =\n            AVS_LIST_DETACH(unconfirmed_ptr);\n    unconfirmed->hold = false;\n    unconfirmed->next_retransmit = next_retransmit;\n\n    LOG(DEBUG, _(\"msg \") \"%s\" _(\" resumed\"),\n        AVS_COAP_TOKEN_HEX(&unconfirmed->msg.token));\n\n    avs_error_t send_err =\n            coap_udp_send_serialized_msg(ctx, &unconfirmed->msg,\n                                         unconfirmed->packet,\n                                         unconfirmed->packet_size);\n    if (avs_is_err(send_err)) {\n        (void) call_send_result_handler(ctx, unconfirmed, NULL,\n                                        AVS_COAP_SEND_RESULT_FAIL, send_err);\n        AVS_LIST_DELETE(&unconfirmed);\n    } else {\n        // the msg may need to be retransmitted before other started ones\n        AVS_LIST_INSERT(find_unconfirmed_insert_ptr(ctx, unconfirmed),\n                        unconfirmed);\n    }\n}\n\nstatic void resume_unconfirmed_messages(avs_coap_udp_ctx_t *ctx) {\n    // nothing can be resumed\n    if (current_nstart(ctx) >= ctx->tx_params.nstart) {\n        return;\n    }\n\n    const size_t resumed_msgs = current_nstart(ctx);\n    const size_t all_msgs = AVS_LIST_SIZE(ctx->unconfirmed_messages);\n    const size_t held_msgs = all_msgs - resumed_msgs;\n\n    const size_t msgs_to_resume =\n            AVS_MIN(ctx->tx_params.nstart - resumed_msgs, held_msgs);\n    LOG(DEBUG, \"%u/%u\" _(\" msgs held; resuming \") \"%u\", (unsigned) held_msgs,\n        (unsigned) all_msgs, (unsigned) msgs_to_resume);\n\n    // Ending up resuming 0 messages here indicates one of:\n    //\n    // - A held unconfirmed message was canceled (OK),\n    // - There is no more held messages to resume (OK),\n    // - While handling cleanup of this message, a new one was created and\n    //   given higher priority than already enqueued one. This is pretty bad,\n    //   as it may result in delaying \"old\" enqueued messages infinitely.\n    //   This is not supposed to happen and indicates a bug in avs_coap.\n    //\n    // Adding an assert would require passing quite a lot of data from the call\n    // site, so I'm just leaving a comment instead in hopes it will help in\n    // debugging if the starving case happens at some point.\n\n    // resume_next_unconfirmed() might call handlers which may resume messages\n    // themselves, so\n    while (current_nstart(ctx) < effective_nstart(ctx)) {\n        resume_next_unconfirmed(ctx);\n    }\n}\n\nstatic void try_cleanup_unconfirmed(avs_coap_udp_ctx_t *ctx,\n                                    avs_coap_udp_unconfirmed_msg_t *unconfirmed,\n                                    const avs_coap_udp_msg_t *response,\n                                    avs_coap_send_result_t result,\n                                    avs_error_t fail_err) {\n    assert(ctx);\n    assert(unconfirmed);\n    AVS_ASSERT(!AVS_LIST_FIND_PTR(&ctx->unconfirmed_messages, unconfirmed),\n               \"unconfirmed must be detached\");\n    LOG(DEBUG, _(\"msg \") \"%s\" _(\": \") \"%s\",\n        AVS_COAP_TOKEN_HEX(&unconfirmed->msg.token),\n        send_result_string(result));\n\n    avs_coap_send_result_handler_result_t handler_result =\n            call_send_result_handler(ctx, unconfirmed, response, result,\n                                     fail_err);\n\n    if (response && result == AVS_COAP_SEND_RESULT_OK\n            && handler_result != AVS_COAP_RESPONSE_ACCEPTED) {\n        AVS_LIST_INSERT(find_unconfirmed_insert_ptr(ctx, unconfirmed),\n                        unconfirmed);\n    } else {\n        reschedule_retransmission_job(ctx);\n        AVS_LIST_DELETE(&unconfirmed);\n    }\n}\n\ntypedef enum {\n    AVS_COAP_UDP_EXCHANGE_ANY,\n    AVS_COAP_UDP_EXCHANGE_CLIENT_REQUEST,\n    AVS_COAP_UDP_EXCHANGE_SERVER_NOTIFICATION\n} avs_coap_udp_exchange_direction_t;\n\nstatic avs_coap_udp_exchange_direction_t direction_from_code(uint8_t code) {\n    return avs_coap_code_is_request(code)\n                   ? AVS_COAP_UDP_EXCHANGE_CLIENT_REQUEST\n                   : AVS_COAP_UDP_EXCHANGE_SERVER_NOTIFICATION;\n}\n\nstatic AVS_LIST(avs_coap_udp_unconfirmed_msg_t) *\nfind_unconfirmed_ptr(avs_coap_udp_ctx_t *ctx,\n                     avs_coap_udp_exchange_direction_t direction,\n                     const avs_coap_token_t *token,\n                     const uint16_t *id) {\n    AVS_LIST(avs_coap_udp_unconfirmed_msg_t) *list_ptr =\n            &ctx->unconfirmed_messages;\n\n    AVS_LIST_ITERATE_PTR(list_ptr) {\n        const avs_coap_udp_msg_t *msg = &(*list_ptr)->msg;\n        if ((direction == AVS_COAP_UDP_EXCHANGE_ANY\n             || direction == direction_from_code(msg->header.code))\n                && (!token || avs_coap_token_equal(&msg->token, token))\n                && (!id || _avs_coap_udp_header_get_id(&msg->header) == *id)) {\n            return list_ptr;\n        }\n    }\n\n    return NULL;\n}\n\nstatic inline AVS_LIST(avs_coap_udp_unconfirmed_msg_t) *\nfind_unconfirmed_ptr_by_token(avs_coap_udp_ctx_t *ctx,\n                              avs_coap_udp_exchange_direction_t direction,\n                              const avs_coap_token_t *token) {\n    return find_unconfirmed_ptr(ctx, direction, token, NULL);\n}\n\nstatic inline AVS_LIST(avs_coap_udp_unconfirmed_msg_t) *\nfind_unconfirmed_ptr_by_msg_id(avs_coap_udp_ctx_t *ctx, uint16_t msg_id) {\n    return find_unconfirmed_ptr(ctx, AVS_COAP_UDP_EXCHANGE_ANY, NULL, &msg_id);\n}\n\nstatic inline AVS_LIST(avs_coap_udp_unconfirmed_msg_t) *\nfind_unconfirmed_ptr_by_response(avs_coap_udp_ctx_t *ctx,\n                                 const avs_coap_udp_msg_t *msg) {\n    assert(avs_coap_code_is_response(msg->header.code));\n\n    uint16_t id = _avs_coap_udp_header_get_id(&msg->header);\n\n    switch (_avs_coap_udp_header_get_type(&msg->header)) {\n    case AVS_COAP_UDP_TYPE_CONFIRMABLE:\n    case AVS_COAP_UDP_TYPE_NON_CONFIRMABLE:\n        return find_unconfirmed_ptr_by_token(\n                ctx, AVS_COAP_UDP_EXCHANGE_CLIENT_REQUEST, &msg->token);\n    case AVS_COAP_UDP_TYPE_ACKNOWLEDGEMENT:\n        return find_unconfirmed_ptr(ctx, AVS_COAP_UDP_EXCHANGE_CLIENT_REQUEST,\n                                    &msg->token, &id);\n    case AVS_COAP_UDP_TYPE_RESET:\n        // this should be detected at packet validation\n        AVS_UNREACHABLE(\"According to RFC7252 Reset MUST be empty\");\n    }\n    AVS_UNREACHABLE(\"invalid enum value\");\n    return NULL;\n}\n\nstatic avs_coap_udp_unconfirmed_msg_t *\ndetach_unconfirmed_by_token(avs_coap_udp_ctx_t *ctx,\n                            avs_coap_udp_exchange_direction_t direction,\n                            const avs_coap_token_t *token) {\n    AVS_LIST(avs_coap_udp_unconfirmed_msg_t) *msg_ptr =\n            find_unconfirmed_ptr_by_token(ctx, direction, token);\n\n    if (msg_ptr) {\n        return AVS_LIST_DETACH(msg_ptr);\n    }\n    return NULL;\n}\n\nstatic void\nconfirm_unconfirmed(avs_coap_udp_ctx_t *ctx,\n                    AVS_LIST(avs_coap_udp_unconfirmed_msg_t) *msg_ptr,\n                    const avs_coap_udp_msg_t *response) {\n    assert(ctx);\n    assert(msg_ptr);\n    assert(*msg_ptr);\n    AVS_ASSERT(AVS_LIST_FIND_PTR(&ctx->unconfirmed_messages, *msg_ptr),\n               \"unconfirmed_msg must be enqueued\");\n\n    avs_coap_udp_unconfirmed_msg_t *msg = AVS_LIST_DETACH(msg_ptr);\n    try_cleanup_unconfirmed(ctx, msg, response, AVS_COAP_SEND_RESULT_OK,\n                            AVS_OK);\n}\n\nstatic void fail_unconfirmed(avs_coap_udp_ctx_t *ctx,\n                             AVS_LIST(avs_coap_udp_unconfirmed_msg_t) *msg_ptr,\n                             const avs_coap_udp_msg_t *truncated_msg,\n                             avs_error_t err) {\n    assert(ctx);\n    assert(msg_ptr);\n    assert(*msg_ptr);\n    AVS_ASSERT(AVS_LIST_FIND_PTR(&ctx->unconfirmed_messages, *msg_ptr),\n               \"unconfirmed_msg must be enqueued\");\n\n    avs_coap_udp_unconfirmed_msg_t *msg = AVS_LIST_DETACH(msg_ptr);\n    try_cleanup_unconfirmed(ctx, msg, truncated_msg, AVS_COAP_SEND_RESULT_FAIL,\n                            err);\n}\n\nstatic avs_coap_udp_exchange_direction_t\nudp_direction(avs_coap_exchange_direction_t direction) {\n    switch (direction) {\n    case AVS_COAP_EXCHANGE_CLIENT_REQUEST:\n        return AVS_COAP_UDP_EXCHANGE_CLIENT_REQUEST;\n    case AVS_COAP_EXCHANGE_SERVER_NOTIFICATION:\n        return AVS_COAP_UDP_EXCHANGE_SERVER_NOTIFICATION;\n    }\n    AVS_UNREACHABLE(\"invalid value of avs_coap_exchange_direction_t\");\n    return AVS_COAP_UDP_EXCHANGE_ANY;\n}\n\nstatic void coap_udp_abort_delivery(avs_coap_ctx_t *ctx_,\n                                    avs_coap_exchange_direction_t direction,\n                                    const avs_coap_token_t *token,\n                                    avs_coap_send_result_t result,\n                                    avs_error_t fail_err) {\n    avs_coap_udp_ctx_t *ctx = (avs_coap_udp_ctx_t *) ctx_;\n    AVS_LIST(avs_coap_udp_unconfirmed_msg_t) msg =\n            detach_unconfirmed_by_token(ctx, udp_direction(direction), token);\n    if (!msg) {\n        return;\n    }\n    try_cleanup_unconfirmed(ctx, msg, NULL, result, fail_err);\n}\n\nstatic void coap_udp_ignore_current_request(avs_coap_ctx_t *ctx_,\n                                            const avs_coap_token_t *token) {\n    (void) token;\n    avs_coap_udp_ctx_t *ctx = (avs_coap_udp_ctx_t *) ctx_;\n    if (ctx->current_request.exists) {\n        assert(avs_coap_token_equal(&ctx->current_request.token, token));\n        ctx->current_request.exists = false;\n    }\n}\n\nstatic void\nretransmit_next_message_without_reschedule(avs_coap_udp_ctx_t *ctx) {\n    avs_coap_udp_unconfirmed_msg_t *unconfirmed = ctx->unconfirmed_messages;\n    if (!unconfirmed\n            || avs_time_monotonic_before(avs_time_monotonic_now(),\n                                         unconfirmed->next_retransmit)) {\n        return;\n    }\n\n    if (_avs_coap_udp_all_retries_sent(&unconfirmed->retry_state)) {\n        LOG(DEBUG,\n            _(\"msg \") \"%s\" _(\": MAX_RETRANSMIT reached without response from \"\n                             \"the server\"),\n            AVS_COAP_TOKEN_HEX(&unconfirmed->msg.token));\n\n        // retransmission_job is rescheduled by fail_unconfirmed()\n        fail_unconfirmed(ctx, &ctx->unconfirmed_messages, NULL,\n                         _avs_coap_err(AVS_COAP_ERR_TIMEOUT));\n        return;\n    }\n\n    if (_avs_coap_udp_update_retry_state(ctx, &unconfirmed->retry_state)) {\n        fail_unconfirmed(ctx, &ctx->unconfirmed_messages, NULL,\n                         _avs_coap_err(AVS_COAP_ERR_TIME_INVALID));\n        return;\n    }\n\n    LOG(DEBUG, _(\"msg \") \"%s\" _(\": retry \") \"%u/%u\",\n        AVS_COAP_TOKEN_HEX(&unconfirmed->msg.token),\n        ctx->tx_params.max_retransmit - unconfirmed->retry_state.retries_left,\n        ctx->tx_params.max_retransmit);\n\n    avs_error_t err = coap_udp_send_serialized_msg(ctx, &unconfirmed->msg,\n                                                   unconfirmed->packet,\n                                                   unconfirmed->packet_size);\n    if (avs_is_err(err)) {\n        fail_unconfirmed(ctx, &ctx->unconfirmed_messages, NULL, err);\n        return;\n    }\n    ++ctx->stats.outgoing_retransmissions_count;\n\n    avs_time_monotonic_t next_retransmit =\n            avs_time_monotonic_add(unconfirmed->next_retransmit,\n                                   unconfirmed->retry_state.recv_timeout);\n    if (!avs_time_monotonic_valid(unconfirmed->next_retransmit)) {\n        LOG(ERROR,\n            _(\"unable to schedule message retransmission: next_retransmit time \"\n              \"invalid; either the monotonic clock malfunctioned or UDP tx \"\n              \"params are too large to handle\"));\n        fail_unconfirmed(ctx, &ctx->unconfirmed_messages, NULL,\n                         _avs_coap_err(AVS_COAP_ERR_TIME_INVALID));\n        return;\n    }\n\n    unconfirmed->next_retransmit = next_retransmit;\n    unconfirmed = AVS_LIST_DETACH(&ctx->unconfirmed_messages);\n    AVS_LIST_INSERT(find_unconfirmed_insert_ptr(ctx, unconfirmed), unconfirmed);\n}\n\nstatic avs_time_monotonic_t coap_udp_on_timeout(avs_coap_ctx_t *ctx_) {\n    avs_coap_udp_ctx_t *ctx = (avs_coap_udp_ctx_t *) ctx_;\n    resume_unconfirmed_messages(ctx);\n    retransmit_next_message_without_reschedule(ctx);\n\n    if (ctx->unconfirmed_messages) {\n        avs_coap_udp_unconfirmed_msg_t *unconfirmed = ctx->unconfirmed_messages;\n\n        LOG(DEBUG, _(\"next UDP retransmission: \") \"%s\",\n            AVS_TIME_DURATION_AS_STRING(\n                    unconfirmed->next_retransmit.since_monotonic_epoch));\n        return ctx->unconfirmed_messages->next_retransmit;\n    } else {\n        return AVS_TIME_MONOTONIC_INVALID;\n    }\n}\n\nstatic avs_error_t\nenqueue_unconfirmed(avs_coap_udp_ctx_t *ctx,\n                    AVS_LIST(avs_coap_udp_unconfirmed_msg_t) unconfirmed) {\n    LOG(TRACE, _(\"msg \") \"%s\" _(\": enqueue\"),\n        AVS_COAP_TOKEN_HEX(&unconfirmed->msg.token));\n\n    // do not send the message unless there is no other one waiting to be sent\n    // that is held for longer than this one\n    assert(ctx->tx_params.nstart > 0);\n    unconfirmed->hold =\n            (AVS_LIST_NTH(ctx->unconfirmed_messages, ctx->tx_params.nstart - 1)\n             != NULL);\n\n    // use current time for all held jobs to not cause accidental reordering\n    // due to ACK_RANDOM_FACTOR\n    avs_time_monotonic_t next_retransmit = avs_time_monotonic_now();\n    if (!unconfirmed->hold) {\n        next_retransmit =\n                avs_time_monotonic_add(next_retransmit,\n                                       unconfirmed->retry_state.recv_timeout);\n    }\n    if (!avs_time_monotonic_valid(unconfirmed->next_retransmit)) {\n        LOG(ERROR,\n            _(\"unable to enqueue msg: next_retransmit time invalid; \"\n              \"either the monotonic clock malfunctioned, UDP tx params are too \"\n              \"large to handle or PRNG failed\"));\n        return _avs_coap_err(AVS_COAP_ERR_TIME_INVALID);\n    }\n\n    unconfirmed->next_retransmit = next_retransmit;\n\n    if (unconfirmed->hold) {\n        LOG(DEBUG, _(\"msg \") \"%s\" _(\" held due to NSTART = \") \"%u\",\n            AVS_COAP_TOKEN_HEX(&unconfirmed->msg.token),\n            (unsigned) ctx->tx_params.nstart);\n    } else {\n        avs_error_t err =\n                coap_udp_send_serialized_msg(ctx, &unconfirmed->msg,\n                                             unconfirmed->packet,\n                                             unconfirmed->packet_size);\n        if (avs_is_err(err)) {\n            return err;\n        }\n    }\n\n    AVS_LIST_INSERT(find_unconfirmed_insert_ptr(ctx, unconfirmed), unconfirmed);\n    reschedule_retransmission_job(ctx);\n    return AVS_OK;\n}\n\nstatic avs_error_t create_unconfirmed(\n        avs_coap_udp_ctx_t *ctx,\n        const avs_coap_udp_msg_t *msg,\n        AVS_LIST(avs_coap_udp_unconfirmed_msg_t) *out_unconfirmed_msg,\n        avs_coap_send_result_handler_t *send_result_handler,\n        void *send_result_handler_arg) {\n    const size_t msg_size = _avs_coap_udp_msg_size(msg);\n\n    AVS_LIST(avs_coap_udp_unconfirmed_msg_t) unconfirmed_msg =\n            (AVS_LIST(avs_coap_udp_unconfirmed_msg_t)) AVS_LIST_NEW_BUFFER(\n                    sizeof(avs_coap_udp_unconfirmed_msg_t) + msg_size);\n    if (!unconfirmed_msg) {\n        return avs_errno(AVS_ENOMEM);\n    }\n\n    *unconfirmed_msg = (avs_coap_udp_unconfirmed_msg_t) {\n        .send_result_handler = send_result_handler,\n        .send_result_handler_arg = send_result_handler_arg,\n        .packet_size = msg_size\n    };\n\n    avs_error_t err;\n\n    if (avs_is_err((err = _avs_coap_udp_initial_retry_state(\n                            ctx, &unconfirmed_msg->retry_state)))) {\n        LOG(ERROR, _(\"PRNG failed\"));\n        AVS_LIST_CLEAR(&unconfirmed_msg);\n        return err;\n    }\n\n    if (avs_is_err((err = _avs_coap_udp_msg_copy(msg, &unconfirmed_msg->msg,\n                                                 unconfirmed_msg->packet,\n                                                 msg_size)))) {\n        LOG(ERROR,\n            _(\"Could not serialize the message as a valid CoAP/UDP packet\"));\n        AVS_LIST_CLEAR(&unconfirmed_msg);\n        return err;\n    }\n\n    *out_unconfirmed_msg = unconfirmed_msg;\n    return AVS_OK;\n}\n\nstatic avs_coap_udp_type_t choose_msg_type(const avs_coap_udp_ctx_t *ctx,\n                                           const avs_coap_borrowed_msg_t *msg,\n                                           bool has_send_result_handler) {\n    if (avs_coap_code_is_request(msg->code)) {\n        /* Use CON if the user requests delivery confirmation, NON otherwise. */\n        return has_send_result_handler ? AVS_COAP_UDP_TYPE_CONFIRMABLE\n                                       : AVS_COAP_UDP_TYPE_NON_CONFIRMABLE;\n    }\n\n    if (avs_coap_code_is_response(msg->code)) {\n        /*\n         * This may either be a \"regular\" response, or an Observe notification.\n         * Because this layer MUST know what Observes are active (to be able\n         * to handle Observe cancellation with Reset response), we may use\n         * the message token to distinguish these two cases.\n         *\n         * - For \"regular\" responses: use ACK (Piggybacked Response)\n         * - For Observe notifications, we should use either CON or NON,\n         *   depending on whether delivery confirmation is required.\n         */\n        if (_avs_coap_option_exists(&msg->options, AVS_COAP_OPTION_OBSERVE)) {\n            if (has_send_result_handler) {\n                return AVS_COAP_UDP_TYPE_CONFIRMABLE;\n            }\n\n            /*\n             * HACK: if we're currently processing //some// input message,\n             * any message with Observe option is probably a direct response\n             * to an Observe request, which should use ACK instead of NON.\n             */\n            return ctx->current_request.exists\n                           ? AVS_COAP_UDP_TYPE_ACKNOWLEDGEMENT\n                           : AVS_COAP_UDP_TYPE_NON_CONFIRMABLE;\n        }\n\n        return has_send_result_handler ? AVS_COAP_UDP_TYPE_CONFIRMABLE\n                                       : AVS_COAP_UDP_TYPE_ACKNOWLEDGEMENT;\n    }\n\n    /* Code should be either a request, response or 0.00 Empty */\n    assert(msg->code == AVS_COAP_CODE_EMPTY);\n\n    /*\n     * 0.00 Empty has specific semantics. It may be either:\n     * - CoAP Ping message (if CON/NON),\n     * - Separate Response (if ACK),\n     * - Reset (RST; the only code allowed for valid RST messages).\n     *\n     * Neither has a clear analog in other transports (e.g. CoAP/TCP), so let's\n     * arbitrarily assume this means a Separate Response.\n     *\n     * Note: that a Separate Response will have to use CON (i.e. have to set\n     * the delivery handler); otherwise such response will be sent as ACK\n     * that does not seem to be allowed. To allow NON Separate Responses,\n     * udp_ctx would need to keep track of tokens for sent Separate ACKs.\n     * This sounds a bit similar to Observe handling.\n     */\n    return AVS_COAP_UDP_TYPE_ACKNOWLEDGEMENT;\n}\n\nstatic uint16_t assign_id(avs_coap_udp_ctx_t *ctx,\n                          const avs_coap_borrowed_msg_t *msg,\n                          const avs_coap_udp_type_t type) {\n    if (ctx->current_request.exists && avs_coap_code_is_response(msg->code)\n            && avs_coap_token_equal(&msg->token, &ctx->current_request.token)) {\n        ctx->current_request.exists = false;\n        if ((type == AVS_COAP_UDP_TYPE_ACKNOWLEDGEMENT\n             || type == AVS_COAP_UDP_TYPE_RESET)) {\n            return ctx->current_request.msg_id;\n        }\n    }\n\n    return generate_id(ctx);\n}\n\nstatic avs_error_t\ncoap_udp_send_message(avs_coap_ctx_t *ctx_,\n                      const avs_coap_borrowed_msg_t *msg,\n                      avs_coap_send_result_handler_t *send_result_handler,\n                      void *send_result_handler_arg) {\n    avs_coap_udp_ctx_t *ctx = (avs_coap_udp_ctx_t *) ctx_;\n\n    uint8_t *out_buffer = avs_shared_buffer_acquire(ctx->base.out_buffer);\n\n    const avs_coap_udp_type_t type =\n            choose_msg_type(ctx, msg, !!send_result_handler);\n\n    avs_coap_udp_msg_t shared_buffer_msg = {\n        .header = _avs_coap_udp_header_init(type, msg->token.size, msg->code,\n                                            assign_id(ctx, msg, type)),\n        .token = msg->token,\n        .options = msg->options,\n        .payload = msg->payload,\n        .payload_size = msg->payload_size\n    };\n\n    size_t shared_buffer_msg_size;\n    avs_error_t err =\n            _avs_coap_udp_msg_serialize(&shared_buffer_msg, out_buffer,\n                                        ctx->base.out_buffer->capacity,\n                                        &shared_buffer_msg_size);\n    if (avs_is_err(err)) {\n        goto end;\n    }\n\n    if (type == AVS_COAP_UDP_TYPE_CONFIRMABLE) {\n        // The user actually cares about message delivery.\n        // We need to store the packet for possible retransmissions.\n        AVS_LIST(avs_coap_udp_unconfirmed_msg_t) unconfirmed = NULL;\n        err = create_unconfirmed(ctx, &shared_buffer_msg, &unconfirmed,\n                                 send_result_handler, send_result_handler_arg);\n        if (avs_is_err(err)) {\n            goto end;\n        }\n\n        assert(unconfirmed);\n        err = enqueue_unconfirmed(ctx, unconfirmed);\n        if (avs_is_err(err)) {\n            // don't call try_cleanup_unconfirmed to avoid calling user-defined\n            // handler\n            AVS_LIST_DELETE(&unconfirmed);\n        }\n    } else {\n        assert(type != AVS_COAP_UDP_TYPE_CONFIRMABLE);\n        // NON/ACK/RST messages ignore NSTART - they are not considered\n        // \"outstanding interactions\" according to RFC7252, 4.7 Congestion\n        // Control.\n        err = coap_udp_send_serialized_msg(ctx, &shared_buffer_msg, out_buffer,\n                                           shared_buffer_msg_size);\n    }\n\nend:\n    avs_shared_buffer_release(ctx->base.out_buffer);\n    return err;\n}\n\nstatic avs_error_t coap_udp_recv_msg(avs_coap_udp_ctx_t *ctx,\n                                     avs_coap_udp_msg_t *out_msg,\n                                     uint8_t *buf,\n                                     size_t buf_size) {\n    size_t packet_size;\n    avs_error_t err = avs_net_socket_receive(ctx->base.socket, &packet_size,\n                                             buf, buf_size);\n    if (avs_is_err(err)) {\n        LOG(TRACE, _(\"recv failed\"));\n        return err;\n    }\n\n    err = _avs_coap_udp_msg_parse(out_msg, buf, packet_size);\n    if (avs_is_err(err)) {\n        LOG(DEBUG, _(\"recv: malformed packet\"));\n        return err;\n    }\n\n    log_udp_msg_summary(\"recv\", out_msg);\n    return AVS_OK;\n}\n\nstatic avs_error_t try_send_cached_response(avs_coap_udp_ctx_t *ctx,\n                                            const avs_coap_udp_msg_t *msg,\n                                            bool *out_cache_hit) {\n    assert(avs_coap_code_is_request(msg->header.code));\n    assert(ctx->current_request.exists);\n    if (!ctx->response_cache) {\n        *out_cache_hit = false;\n        return AVS_OK;\n    }\n\n    char addr[AVS_ADDRSTRLEN];\n    char port[sizeof(\"65535\")];\n    if (avs_is_err(avs_net_socket_get_remote_host(ctx->base.socket, addr,\n                                                  sizeof(addr)))\n            || avs_is_err(avs_net_socket_get_remote_port(ctx->base.socket, port,\n                                                         sizeof(port)))) {\n        LOG(DEBUG, _(\"could not get remote remote host/port\"));\n        *out_cache_hit = false;\n        return AVS_OK;\n    }\n\n    uint16_t msg_id = _avs_coap_udp_header_get_id(&msg->header);\n    avs_coap_udp_cached_response_t cached_response;\n    if (avs_is_ok(_avs_coap_udp_response_cache_get(\n                ctx->response_cache, addr, port, msg_id, &cached_response))) {\n        *out_cache_hit = true;\n        ctx->current_request.exists = false;\n        return coap_udp_send_serialized_msg(ctx, &cached_response.msg,\n                                            cached_response.packet,\n                                            cached_response.packet_size);\n    } else {\n        *out_cache_hit = false;\n        return AVS_OK;\n    }\n}\n\nstatic avs_error_t handle_request(avs_coap_udp_ctx_t *ctx,\n                                  const avs_coap_udp_msg_t *msg,\n                                  bool *out_should_handle) {\n    *out_should_handle = false;\n    switch (_avs_coap_udp_header_get_type(&msg->header)) {\n    case AVS_COAP_UDP_TYPE_CONFIRMABLE:\n    case AVS_COAP_UDP_TYPE_NON_CONFIRMABLE: {\n        bool cache_hit;\n        avs_error_t err = try_send_cached_response(ctx, msg, &cache_hit);\n        if (cache_hit) {\n            ++ctx->stats.incoming_retransmissions_count;\n            return err;\n        }\n\n        *out_should_handle = true;\n        return AVS_OK;\n    }\n\n    case AVS_COAP_UDP_TYPE_ACKNOWLEDGEMENT:\n        // this should be detected at packet validation\n        AVS_UNREACHABLE(\"Requests with ACK type make no sense\");\n        return _avs_coap_err(AVS_COAP_ERR_ASSERT_FAILED);\n\n    case AVS_COAP_UDP_TYPE_RESET:\n        // this should be detected at packet validation\n        AVS_UNREACHABLE(\"According to RFC7252 Reset MUST be empty\");\n        return _avs_coap_err(AVS_COAP_ERR_ASSERT_FAILED);\n    }\n\n    AVS_UNREACHABLE(\"switch above is supposed to be exhaustive\");\n    return _avs_coap_err(AVS_COAP_ERR_ASSERT_FAILED);\n}\n\nstatic avs_error_t\nsend_empty(avs_coap_udp_ctx_t *ctx, avs_coap_udp_type_t type, uint16_t msg_id) {\n    // an Empty message MUST NOT have neither options nor payload, and MUST\n    // have a 0-byte token\n    avs_coap_udp_msg_t msg = {\n        .header =\n                _avs_coap_udp_header_init(type, 0, AVS_COAP_CODE_EMPTY, msg_id)\n    };\n\n    return coap_udp_send_serialized_msg(ctx, &msg, &msg.header,\n                                        sizeof(msg.header));\n}\n\nstatic avs_error_t send_separate_ack(avs_coap_udp_ctx_t *ctx, uint16_t msg_id) {\n    return send_empty(ctx, AVS_COAP_UDP_TYPE_ACKNOWLEDGEMENT, msg_id);\n}\n\nstatic avs_error_t send_reset(avs_coap_udp_ctx_t *ctx, uint16_t msg_id) {\n    return send_empty(ctx, AVS_COAP_UDP_TYPE_RESET, msg_id);\n}\n\nstatic avs_error_t handle_response(avs_coap_udp_ctx_t *ctx,\n                                   const avs_coap_udp_msg_t *msg) {\n    AVS_LIST(avs_coap_udp_unconfirmed_msg_t) *unconfirmed_ptr =\n            find_unconfirmed_ptr_by_response(ctx, msg);\n    if (!unconfirmed_ptr) {\n        bool is_confirmable = (_avs_coap_udp_header_get_type(&msg->header)\n                               == AVS_COAP_UDP_TYPE_CONFIRMABLE);\n        LOG(DEBUG,\n            _(\"Received response does not match any known request, \") \"%s\",\n            is_confirmable ? \"rejecting\" : \"ignoring\");\n        if (is_confirmable) {\n            return send_reset(ctx, _avs_coap_udp_header_get_id(&msg->header));\n        }\n        return AVS_OK;\n    }\n\n    switch (_avs_coap_udp_header_get_type(&msg->header)) {\n    case AVS_COAP_UDP_TYPE_CONFIRMABLE: {\n        // Separate response\n\n        avs_error_t err =\n                send_separate_ack(ctx,\n                                  _avs_coap_udp_header_get_id(&msg->header));\n        if (avs_is_err(err)) {\n            fail_unconfirmed(ctx, unconfirmed_ptr, NULL, err);\n            return err;\n        }\n        break;\n    }\n    case AVS_COAP_UDP_TYPE_NON_CONFIRMABLE:\n        // Separate Response with NON\n        //\n        // RFC7252, 5.2.2. Separate\n        // > When the server finally has obtained the resource representation,\n        // > it sends the response. [...] (It may also be sent as a\n        // > Non-confirmable message; see Section 5.2.3.)\n        break;\n\n    case AVS_COAP_UDP_TYPE_ACKNOWLEDGEMENT:\n        // Piggybacked Response\n        break;\n\n    case AVS_COAP_UDP_TYPE_RESET:\n        // this is detected at packet validation\n        AVS_UNREACHABLE(\"According to RFC7252 Reset MUST be empty\");\n        return _avs_coap_err(AVS_COAP_ERR_ASSERT_FAILED);\n    }\n\n    confirm_unconfirmed(ctx, unconfirmed_ptr, msg);\n    return AVS_OK;\n}\n\nstatic void\nack_request(avs_coap_udp_ctx_t *ctx,\n            AVS_LIST(avs_coap_udp_unconfirmed_msg_t) *unconfirmed_ptr) {\n    assert(ctx);\n    assert(unconfirmed_ptr);\n    assert(*unconfirmed_ptr);\n\n    // Wait EXCHANGE_LIFETIME for the actual response\n    avs_time_monotonic_t next_retransmit = avs_time_monotonic_add(\n            avs_time_monotonic_now(),\n            avs_coap_udp_exchange_lifetime(&ctx->tx_params));\n\n    if (!avs_time_monotonic_valid((*unconfirmed_ptr)->next_retransmit)) {\n        LOG(ERROR,\n            _(\"unable to schedule msg retransmission: next_retransmit time \"\n              \"invalid; either the monotonic clock malfunctioned or UDP tx \"\n              \"params are too large to handle\"));\n        fail_unconfirmed(ctx, unconfirmed_ptr, NULL,\n                         _avs_coap_err(AVS_COAP_ERR_TIME_INVALID));\n        return;\n    }\n\n    avs_coap_udp_unconfirmed_msg_t *unconfirmed =\n            AVS_LIST_DETACH(unconfirmed_ptr);\n    // disable further retransmissions\n    unconfirmed->retry_state.retries_left = 0;\n    unconfirmed->next_retransmit = next_retransmit;\n\n    AVS_LIST_INSERT(find_unconfirmed_insert_ptr(ctx, unconfirmed), unconfirmed);\n    reschedule_retransmission_job(ctx);\n}\n\nstatic avs_error_t handle_empty(avs_coap_udp_ctx_t *ctx,\n                                const avs_coap_udp_msg_t *msg) {\n    uint16_t msg_id = _avs_coap_udp_header_get_id(&msg->header);\n    AVS_LIST(avs_coap_udp_unconfirmed_msg_t) *unconfirmed_ptr =\n            find_unconfirmed_ptr_by_msg_id(ctx, msg_id);\n\n    switch (_avs_coap_udp_header_get_type(&msg->header)) {\n    case AVS_COAP_UDP_TYPE_CONFIRMABLE:\n        // CoAP Ping.\n        return send_reset(ctx, msg_id);\n    case AVS_COAP_UDP_TYPE_NON_CONFIRMABLE:\n        return AVS_OK;\n\n    case AVS_COAP_UDP_TYPE_ACKNOWLEDGEMENT:\n        // Separate ACK\n        if (unconfirmed_ptr) {\n            if (avs_coap_code_is_request((*unconfirmed_ptr)->msg.header.code)) {\n                // we still need to wait for a response\n                ack_request(ctx, unconfirmed_ptr);\n            } else {\n                // Separate ACK to Separate Response sent by us\n                confirm_unconfirmed(ctx, unconfirmed_ptr, NULL);\n            }\n            return AVS_OK;\n        } else {\n            LOG(DEBUG,\n                _(\"Unexpected Separate ACK (ID \") \"%#04\" PRIx16 _(\n                        \"), ignoring\"),\n                msg_id);\n            return AVS_OK;\n        }\n\n    case AVS_COAP_UDP_TYPE_RESET: {\n        if (unconfirmed_ptr) {\n            // Reset response to our CON request\n            fail_unconfirmed(ctx, unconfirmed_ptr, NULL,\n                             _avs_coap_err(AVS_COAP_ERR_UDP_RESET_RECEIVED));\n        }\n\n#    ifdef WITH_AVS_COAP_OBSERVE\n        const avs_coap_token_t *observe_token =\n                coap_udp_notify_cache_get(&ctx->notify_cache, msg_id);\n        if (observe_token) {\n            avs_coap_observe_id_t observe_id = {\n                .token = *observe_token\n            };\n            avs_coap_observe_cancel((avs_coap_ctx_t *) ctx, observe_id);\n        }\n#    endif // WITH_AVS_COAP_OBSERVE\n\n        return AVS_OK;\n    }\n    }\n\n    AVS_UNREACHABLE(\"switch above is supposed to be exhaustive\");\n    return _avs_coap_err(AVS_COAP_ERR_ASSERT_FAILED);\n}\n\nstatic void store_request_id(avs_coap_udp_ctx_t *ctx,\n                             const avs_coap_udp_msg_t *msg) {\n    assert(!ctx->current_request.exists);\n\n    ctx->current_request.exists = true;\n    ctx->current_request.msg_id = _avs_coap_udp_header_get_id(&msg->header);\n    ctx->current_request.token = msg->token;\n}\n\nstatic avs_error_t handle_msg(avs_coap_udp_ctx_t *ctx,\n                              const avs_coap_udp_msg_t *msg,\n                              bool *out_should_handle_request) {\n    *out_should_handle_request = false;\n    if (avs_coap_code_is_request(msg->header.code)) {\n        store_request_id(ctx, msg);\n        return handle_request(ctx, msg, out_should_handle_request);\n    } else if (avs_coap_code_is_response(msg->header.code)) {\n        return handle_response(ctx, msg);\n    } else if (msg->header.code == AVS_COAP_CODE_EMPTY) {\n        return handle_empty(ctx, msg);\n    } else {\n        LOG(DEBUG, _(\"Unexpected CoAP code: \") \"%s\" _(\", ignoring\"),\n            AVS_COAP_CODE_STRING(msg->header.code));\n    }\n    return AVS_OK;\n}\n\nstatic avs_error_t send_empty_response(avs_coap_udp_ctx_t *ctx,\n                                       const avs_coap_udp_msg_t *request,\n                                       uint8_t response_code) {\n    const avs_coap_udp_msg_t msg = {\n        .header = _avs_coap_udp_header_init(\n                AVS_COAP_UDP_TYPE_ACKNOWLEDGEMENT, request->token.size,\n                response_code, _avs_coap_udp_header_get_id(&request->header)),\n        .token = request->token\n    };\n\n    uint8_t buf[sizeof(avs_coap_udp_header_t) + sizeof(avs_coap_token_t)];\n    size_t msg_size = 0;\n    if (avs_is_err(_avs_coap_udp_msg_serialize(&msg, buf, sizeof(buf),\n                                               &msg_size))) {\n        AVS_UNREACHABLE();\n    }\n\n    return coap_udp_send_serialized_msg(ctx, &msg, buf, msg_size);\n}\n\nstatic avs_error_t\nhandle_truncated_request(avs_coap_udp_ctx_t *ctx,\n                         const avs_coap_udp_msg_t *truncated_msg) {\n    log_udp_msg_summary(\"recv [truncated request]\", truncated_msg);\n    assert(avs_coap_code_is_request(truncated_msg->header.code));\n    return send_empty_response(ctx, truncated_msg,\n                               AVS_COAP_CODE_REQUEST_ENTITY_TOO_LARGE);\n}\n\nstatic void handle_truncated_response(avs_coap_udp_ctx_t *ctx,\n                                      const avs_coap_udp_msg_t *truncated_msg) {\n    log_udp_msg_summary(\"recv [truncated response]\", truncated_msg);\n\n    assert(avs_coap_code_is_response(truncated_msg->header.code));\n    // Truncated response: notify the owner about failure. The handler will\n    // be able to detect that truncation happened by inspecting socket errno\n    AVS_LIST(avs_coap_udp_unconfirmed_msg_t) *unconfirmed_ptr =\n            find_unconfirmed_ptr_by_response(ctx, truncated_msg);\n    if (unconfirmed_ptr) {\n        fail_unconfirmed(ctx, unconfirmed_ptr, truncated_msg,\n                         _avs_coap_err(\n                                 AVS_COAP_ERR_TRUNCATED_MESSAGE_RECEIVED));\n    }\n}\n\nstatic avs_error_t handle_truncated_msg(avs_coap_udp_ctx_t *ctx,\n                                        uint8_t *message_buf,\n                                        size_t message_buf_size,\n                                        avs_coap_udp_msg_t *truncated_msg) {\n    bool has_token;\n    bool has_options;\n    _avs_coap_udp_msg_parse_truncated(truncated_msg, message_buf,\n                                      message_buf_size, &has_token,\n                                      &has_options);\n    if (!has_token) {\n        LOG(DEBUG, _(\"received truncated CoAP message with incomplete token; \"\n                     \"ignoring\"));\n        return AVS_OK;\n    }\n\n    avs_error_t err = AVS_OK;\n    if (avs_coap_code_is_request(truncated_msg->header.code)) {\n        err = handle_truncated_request(ctx, truncated_msg);\n    } else if (avs_coap_code_is_response(truncated_msg->header.code)) {\n        if (has_options) {\n            handle_truncated_response(ctx, truncated_msg);\n        } else {\n            err = _avs_coap_err(AVS_COAP_ERR_TRUNCATED_MESSAGE_RECEIVED);\n        }\n    } else {\n        // Neither request nor response - ignore\n    }\n    return err;\n}\n\nstatic avs_error_t\ncoap_udp_receive_message(avs_coap_ctx_t *ctx_,\n                         uint8_t *in_buffer,\n                         size_t in_buffer_capacity,\n                         avs_coap_borrowed_msg_t *out_request) {\n    avs_coap_udp_ctx_t *ctx = (avs_coap_udp_ctx_t *) ctx_;\n\n    avs_coap_udp_msg_t msg;\n    memset(&msg.header, 0, sizeof(msg.header));\n    memset(out_request, 0, sizeof(*out_request));\n\n    avs_error_t err =\n            coap_udp_recv_msg(ctx, &msg, in_buffer, in_buffer_capacity);\n    if (avs_is_ok(err)) {\n        bool should_handle_request;\n        err = handle_msg(ctx, &msg, &should_handle_request);\n        if (should_handle_request) {\n            *out_request = borrowed_msg_from_udp_msg(&msg);\n        }\n        return err;\n    }\n    if (err.category == AVS_COAP_ERR_CATEGORY) {\n        switch ((avs_coap_error_t) err.code) {\n        case AVS_COAP_ERR_MALFORMED_MESSAGE:\n            LOG(DEBUG, _(\"malformed CoAP message received, ignoring\"));\n            return AVS_OK;\n\n        case AVS_COAP_ERR_MALFORMED_OPTIONS: {\n            if (avs_coap_code_is_request(msg.header.code)) {\n                // As defined in RFC7252, a CoAP message with Bad Option code\n                // should be send if options are unrecognized or malformed.\n                return send_empty_response(ctx, &msg, AVS_COAP_CODE_BAD_OPTION);\n            } else if (avs_coap_code_is_response(msg.header.code)) {\n                // At this point token and ID are available in the msg\n                // struct.\n                AVS_LIST(avs_coap_udp_unconfirmed_msg_t) *unconfirmed_ptr =\n                        find_unconfirmed_ptr_by_response(ctx, &msg);\n                if (unconfirmed_ptr) {\n                    fail_unconfirmed(ctx, unconfirmed_ptr, NULL, err);\n                }\n                const avs_coap_udp_type_t type =\n                        _avs_coap_udp_header_get_type(&msg.header);\n                if (type == AVS_COAP_UDP_TYPE_CONFIRMABLE\n                        || type == AVS_COAP_UDP_TYPE_NON_CONFIRMABLE) {\n                    return send_reset(ctx,\n                                      _avs_coap_udp_header_get_id(&msg.header));\n                }\n            }\n            return AVS_OK;\n        }\n        default:\n            break;\n        }\n    } else if (err.category == AVS_ERRNO_CATEGORY) {\n        if (err.code == AVS_EMSGSIZE) {\n            return handle_truncated_msg(ctx, in_buffer, in_buffer_capacity,\n                                        &msg);\n        } else if (err.code == AVS_ETIMEDOUT) {\n            // AVS_ETIMEDOUT is expected in some cases,\n            // so don't log it as unexpected\n            return err;\n        }\n    }\n\n    LOG(DEBUG,\n        _(\"unhandled error (\") \"%s\" _(\") returned from coap_udp_recv_msg()\"),\n        AVS_COAP_STRERROR(err));\n    return err;\n}\n\nstatic avs_error_t coap_udp_accept_observation(avs_coap_ctx_t *ctx_,\n                                               avs_coap_observe_t *observe) {\n    (void) ctx_;\n    (void) observe;\n\n#    ifdef WITH_AVS_COAP_OBSERVE\n    return AVS_OK;\n#    else  // WITH_AVS_COAP_OBSERVE\n    LOG(WARNING, _(\"Observes support disabled\"));\n    return _avs_coap_err(AVS_COAP_ERR_FEATURE_DISABLED);\n#    endif // WITH_AVS_COAP_OBSERVE\n}\n\nstatic void coap_udp_cleanup(avs_coap_ctx_t *ctx_) {\n    avs_coap_udp_ctx_t *ctx = (avs_coap_udp_ctx_t *) ctx_;\n\n    while (ctx->unconfirmed_messages) {\n        avs_coap_udp_unconfirmed_msg_t *unconfirmed =\n                AVS_LIST_DETACH(&ctx->unconfirmed_messages);\n        try_cleanup_unconfirmed(ctx, unconfirmed, NULL,\n                                AVS_COAP_SEND_RESULT_CANCEL, AVS_OK);\n    }\n    avs_free(ctx);\n}\n\nstatic avs_coap_base_t *coap_udp_get_base(avs_coap_ctx_t *ctx_) {\n    avs_coap_udp_ctx_t *ctx = (avs_coap_udp_ctx_t *) ctx_;\n    return &ctx->base;\n}\n\nstatic avs_coap_stats_t coap_udp_get_stats(avs_coap_ctx_t *ctx_) {\n    avs_coap_udp_ctx_t *ctx = (avs_coap_udp_ctx_t *) ctx_;\n    return ctx->stats;\n}\n\nstatic avs_error_t coap_udp_setsock(avs_coap_ctx_t *ctx,\n                                    avs_net_socket_t *socket) {\n    return _avs_coap_ctx_set_socket_base(ctx, socket);\n}\n\nstatic uint32_t coap_udp_next_observe_option_value(avs_coap_ctx_t *ctx,\n                                                   uint32_t last_value) {\n    (void) ctx;\n    return last_value + 1;\n}\n\nstatic const avs_coap_ctx_vtable_t COAP_UDP_VTABLE = {\n    .cleanup = coap_udp_cleanup,\n    .get_base = coap_udp_get_base,\n    .setsock = coap_udp_setsock,\n    .max_outgoing_payload_size = coap_udp_max_outgoing_payload_size,\n    .max_incoming_payload_size = coap_udp_max_incoming_payload_size,\n    .send_message = coap_udp_send_message,\n    .abort_delivery = coap_udp_abort_delivery,\n    .ignore_current_request = coap_udp_ignore_current_request,\n    .receive_message = coap_udp_receive_message,\n    .accept_observation = coap_udp_accept_observation,\n    .on_timeout = coap_udp_on_timeout,\n    .get_stats = coap_udp_get_stats,\n    .next_observe_option_value = coap_udp_next_observe_option_value\n};\n\nstatic bool are_tx_params_sane(const avs_coap_udp_tx_params_t *tx_params) {\n    return avs_time_duration_valid(avs_time_duration_fmul(\n            tx_params->ack_timeout, tx_params->ack_random_factor));\n}\n\navs_coap_ctx_t *\navs_coap_udp_ctx_create(avs_sched_t *sched,\n                        const avs_coap_udp_tx_params_t *udp_tx_params,\n                        avs_shared_buffer_t *in_buffer,\n                        avs_shared_buffer_t *out_buffer,\n                        avs_coap_udp_response_cache_t *cache,\n                        avs_crypto_prng_ctx_t *prng_ctx) {\n    assert(in_buffer);\n    assert(out_buffer);\n    assert(prng_ctx);\n\n    const char *error;\n    if (udp_tx_params) {\n        if (!avs_coap_udp_tx_params_valid(udp_tx_params, &error)) {\n            LOG(ERROR, _(\"invalid UDP transmission parameters: \") \"%s\", error);\n            return NULL;\n        }\n        if (!are_tx_params_sane(udp_tx_params)) {\n            LOG(ERROR,\n                _(\"UDP transmission parameters cause ack_timeout overflow\"));\n            return NULL;\n        }\n    }\n\n    uint16_t last_msg_id;\n#    ifdef AVS_UNIT_TESTING\n    last_msg_id = 0;\n#    else  // AVS_UNIT_TESTING\n    if (avs_crypto_prng_bytes(prng_ctx, (unsigned char *) &last_msg_id,\n                              sizeof(last_msg_id))) {\n        LOG(ERROR, \"failed to generate random initial msg ID\");\n        return NULL;\n    }\n#    endif // AVS_UNIT_TESTING\n\n    avs_coap_udp_ctx_t *ctx =\n            (avs_coap_udp_ctx_t *) avs_calloc(1, sizeof(avs_coap_udp_ctx_t));\n    if (!ctx) {\n        return NULL;\n    }\n\n    _avs_coap_base_init(&ctx->base, (avs_coap_ctx_t *) ctx, in_buffer,\n                        out_buffer, sched, prng_ctx);\n\n    ctx->vtable = &COAP_UDP_VTABLE;\n    ctx->last_mtu = SIZE_MAX;\n    ctx->tx_params =\n            udp_tx_params ? *udp_tx_params : AVS_COAP_DEFAULT_UDP_TX_PARAMS;\n    ctx->response_cache = cache;\n    ctx->last_msg_id = last_msg_id;\n\n    return (avs_coap_ctx_t *) ctx;\n}\n\nint avs_coap_udp_ctx_set_forced_incoming_mtu(avs_coap_ctx_t *ctx,\n                                             size_t forced_incoming_mtu) {\n    if (!ctx || ctx->vtable != &COAP_UDP_VTABLE) {\n        LOG(ERROR, _(\"avs_coap_udp_ctx_set_forced_incoming_mtu() called on a \"\n                     \"NULL or non-UDP context\"));\n        return -1;\n    }\n\n    ((avs_coap_udp_ctx_t *) ctx)->forced_incoming_mtu = forced_incoming_mtu;\n    return 0;\n}\n\nint avs_coap_udp_ctx_set_tx_params(avs_coap_ctx_t *ctx,\n                                   const avs_coap_udp_tx_params_t *tx_params) {\n    if (!ctx || ctx->vtable != &COAP_UDP_VTABLE) {\n        LOG(ERROR, _(\"avs_coap_udp_ctx_set_tx_params() called on a NULL or \"\n                     \"non-UDP context\"));\n        return -1;\n    } else if (!tx_params) {\n        LOG(ERROR, _(\"given transmission parameters are NULL\"));\n        return -1;\n    } else {\n        const char *error_message;\n        if (!avs_coap_udp_tx_params_valid(tx_params, &error_message)) {\n            assert(error_message);\n            LOG(ERROR,\n                _(\"UDP transmission params validation failed with the \"\n                  \"following error message: \") \"%s\",\n                error_message);\n            return -1;\n        }\n    }\n\n    avs_coap_udp_ctx_t *udp_ctx = (avs_coap_udp_ctx_t *) ctx;\n    udp_ctx->tx_params = *tx_params;\n\n    return 0;\n}\n\nconst avs_coap_udp_tx_params_t *\navs_coap_udp_ctx_get_tx_params(avs_coap_ctx_t *ctx) {\n    if (!ctx || ctx->vtable != &COAP_UDP_VTABLE) {\n        LOG(ERROR, _(\"avs_coap_udp_ctx_set_tx_params() called on a NULL or \"\n                     \"non-UDP context\"));\n        return NULL;\n    }\n\n    return &((avs_coap_udp_ctx_t *) ctx)->tx_params;\n}\n\n#endif // WITH_AVS_COAP_UDP\n"
  },
  {
    "path": "deps/avs_coap/src/udp/avs_coap_udp_ctx.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef AVS_COAP_SRC_UDP_UDP_CTX_H\n#define AVS_COAP_SRC_UDP_UDP_CTX_H\n\n#include \"../avs_coap_ctx.h\"\n#include \"avs_coap_udp_msg.h\"\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\n/** Retry state object used to calculate retransmission timeouts. */\ntypedef struct {\n    /**\n     * Number of original packet retransmissions left to try.\n     *\n     * The value of retries_left shall vary between 0 and MAX_RETRANSMIT\n     * inclusively.\n     */\n    unsigned retries_left;\n    /**\n     * Amount of time to wait for the response (either to an initial packet or a\n     * retransmitted one).\n     */\n    avs_time_duration_t recv_timeout;\n} avs_coap_retry_state_t;\n\n/**\n * Owning wrapper around an unconfirmed outgoing CoAP/UDP message.\n *\n * List of CoAP/UDP exchanges is kept sorted by (hold, next_retransmit) tuple:\n *\n * - up to NSTART first entries are \"not held\", i.e. are currently being\n *   retransmitted,\n *\n * - if more than NSTART exchanges were created, the rest is \"held\",\n *   i.e. not transmitted at all to honor NSTART defined by RFC7252.\n *\n * Whenever an exchange is retransmitted, next_retransmit is updated to the\n * time of a next retransmission, and the exchange entry moved to appropriate\n * place in the exchange list to keep described ordering.\n */\ntypedef struct {\n    /** Handler to call when context is done with the message */\n    avs_coap_send_result_handler_t *send_result_handler;\n    /** Opaque argument to pass to send_result_handler */\n    void *send_result_handler_arg;\n\n    /** Current state of retransmission timeout calculation. */\n    avs_coap_retry_state_t retry_state;\n\n    /** If true, exchange retransmissions are disabled due to NSTART */\n    bool hold;\n\n    /** Time at which this packet has to be retransmitted next time. */\n    avs_time_monotonic_t next_retransmit;\n\n    /** CoAP message view. Points to @ref avs_coap_udp_exchange_t#packet . */\n    avs_coap_udp_msg_t msg;\n\n    /** Number of initialized bytes in @ref avs_coap_udp_exchange_t#packet . */\n    size_t packet_size;\n\n    /** Serialized packet data. */\n    uint8_t packet[];\n} avs_coap_udp_unconfirmed_msg_t;\n\n#ifdef WITH_AVS_COAP_OBSERVE\ntypedef struct {\n    uint16_t msg_id;\n    avs_coap_token_t token;\n} avs_coap_udp_sent_notify_t;\n\n/**\n * Fixed-size cache with queue semantics used to store (message ID, token)\n * pairs of recently sent notification messages.\n *\n * RFC 7641 defines Reset response to sent notification to be a preferred\n * method of cancelling an established observation. This cache allows us to\n * match incoming Reset messages to established observations so that we can\n * cancel them.\n *\n * Technically, entries in this cache should expire after MAX_TRANSMIT_WAIT\n * since the first retransmission, but we keep them around as long as there\n * is enough space and we don't try to reuse the same message ID. That means\n * some Reset messages may not cancel observations if notifications are\n * generated at a high rate, or that Reset messages that come later are still\n * handled as valid observe cancellation.\n *\n * This implementation trades correctness in all cases for simplicity.\n */\ntypedef struct {\n    avs_coap_udp_sent_notify_t entries[AVS_COAP_UDP_NOTIFY_CACHE_SIZE];\n    size_t size;\n} avs_coap_udp_notify_cache_t;\n#endif // WITH_AVS_COAP_OBSERVE\n\ntypedef struct {\n    const struct avs_coap_ctx_vtable *vtable;\n\n    avs_coap_base_t base;\n\n    AVS_LIST(avs_coap_udp_unconfirmed_msg_t) unconfirmed_messages;\n\n    avs_net_socket_t *socket;\n    size_t last_mtu;\n    size_t forced_incoming_mtu;\n    avs_coap_udp_tx_params_t tx_params;\n\n    avs_coap_stats_t stats;\n\n    uint16_t last_msg_id;\n\n    /**\n     * Any Piggybacked response we send MUST echo message ID of received\n     * request. Its ID/token pair is stored here to ensure that.\n     */\n    struct {\n        /** true if we're currently processing a request */\n        bool exists;\n        uint16_t msg_id;\n        avs_coap_token_t token;\n    } current_request;\n\n    avs_coap_udp_response_cache_t *response_cache;\n#ifdef WITH_AVS_COAP_OBSERVE\n    avs_coap_udp_notify_cache_t notify_cache;\n#endif // WITH_AVS_COAP_OBSERVE\n} avs_coap_udp_ctx_t;\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif // AVS_COAP_SRC_UDP_UDP_CTX_H\n"
  },
  {
    "path": "deps/avs_coap/src/udp/avs_coap_udp_header.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef AVS_COAP_SRC_UDP_UDP_HEADER_H\n#define AVS_COAP_SRC_UDP_UDP_HEADER_H\n\n#include <stddef.h>\n#include <stdint.h>\n\n#include <avsystem/coap/token.h>\n\n#include \"avs_coap_parse_utils.h\"\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\n/**\n * CoAP message type, as defined in RFC 7252.\n *\n * This is a library-specific representation of the \"type\" sub-field of the\n * first byte of the CoAP header. @ref _avs_coap_udp_header_get_type and\n * @ref _avs_coap_udp_header_set_type can be used to access this value within\n * @ref avs_coap_udp_header_t.\n */\ntypedef enum avs_coap_udp_type {\n    AVS_COAP_UDP_TYPE_CONFIRMABLE,\n    AVS_COAP_UDP_TYPE_NON_CONFIRMABLE,\n    AVS_COAP_UDP_TYPE_ACKNOWLEDGEMENT,\n    AVS_COAP_UDP_TYPE_RESET,\n\n    _AVS_COAP_UDP_TYPE_FIRST = AVS_COAP_UDP_TYPE_CONFIRMABLE,\n    _AVS_COAP_UDP_TYPE_LAST = AVS_COAP_UDP_TYPE_RESET\n} avs_coap_udp_type_t;\n\n/**\n * Serialized CoAP message header.\n *\n * This type directly corresponds to the first four bytes of the UDP CoAP\n * header, as defined in RFC 7252, and can be directly serialized and\n * deserialized in place of those.\n *\n * The library has static assertions which ensure that size and alignment\n * requirements of this type satisfy these requirements. The library will fail\n * to compile on architectures that would e.g. introduce padding.\n */\ntypedef struct avs_coap_udp_header {\n    /**\n     * The first byte of the CoAP header, encoding version, type and token\n     * length.\n     *\n     * This field is NOT designed to be accessed directly. Please instead use:\n     * - @ref _avs_coap_udp_header_get_version\n     * - @ref _avs_coap_udp_header_set_version\n     * - @ref _avs_coap_udp_header_get_token_length\n     * - @ref _avs_coap_udp_header_set_token_length\n     * - @ref _avs_coap_udp_header_get_type\n     * - @ref _avs_coap_udp_header_set_type\n     */\n    uint8_t version_type_token_length;\n\n    /**\n     * CoAP message code.\n     *\n     * While this field can be accessed directly, utility functions and\n     * constants in code.h can be used for easier handling of this value.\n     */\n    uint8_t code;\n\n    /**\n     * CoAP message ID.\n     *\n     * @ref _avs_coap_udp_header_get_id and @ref _avs_coap_udp_header_set_id\n     * can be used to access this value as a single 16-bit integer, encoded as\n     * big-endian.\n     */\n    uint8_t message_id[2];\n} avs_coap_udp_header_t;\n\nAVS_STATIC_ASSERT(AVS_ALIGNOF(avs_coap_udp_header_t) == 1,\n                  avs_coap_udp_header_t_must_always_be_properly_aligned);\n\n/** @{\n * Sanity checks that ensure no padding is inserted anywhere inside\n * @ref avs_coap_udp_header_t .\n */\nAVS_STATIC_ASSERT(offsetof(avs_coap_udp_header_t, version_type_token_length)\n                          == 0,\n                  vttl_field_is_at_start_of_avs_coap_udp_header_t);\nAVS_STATIC_ASSERT(offsetof(avs_coap_udp_header_t, code) == 1,\n                  no_padding_before_code_field_of_avs_coap_udp_header_t);\nAVS_STATIC_ASSERT(offsetof(avs_coap_udp_header_t, message_id) == 2,\n                  no_padding_before_message_id_field_of_avs_coap_udp_header_t);\nAVS_STATIC_ASSERT(sizeof(avs_coap_udp_header_t) == 4,\n                  no_padding_in_avs_coap_udp_header_t);\n/** @} */\n\n#define _AVS_COAP_UDP_HEADER_VERSION_MASK 0xC0\n#define _AVS_COAP_UDP_HEADER_VERSION_SHIFT 6\n\n/**\n * Extracts the version field from a CoAP header.\n *\n * Note that <c>1</c> is currently the only valid version.\n */\nstatic inline uint8_t\n_avs_coap_udp_header_get_version(const avs_coap_udp_header_t *hdr) {\n    int val = _AVS_FIELD_GET(hdr->version_type_token_length,\n                             _AVS_COAP_UDP_HEADER_VERSION_MASK,\n                             _AVS_COAP_UDP_HEADER_VERSION_SHIFT);\n    assert(val >= 0 && val <= 3);\n    return (uint8_t) val;\n}\n\n/**\n * Sets the version field inside a CoAP header.\n *\n * Note that <c>1</c> is currently the only valid version.\n */\nstatic inline void _avs_coap_udp_header_set_version(avs_coap_udp_header_t *hdr,\n                                                    uint8_t version) {\n    assert(version <= 3);\n    _AVS_FIELD_SET(hdr->version_type_token_length,\n                   _AVS_COAP_UDP_HEADER_VERSION_MASK,\n                   _AVS_COAP_UDP_HEADER_VERSION_SHIFT, version);\n}\n\n#define _AVS_COAP_UDP_HEADER_TOKEN_LENGTH_MASK 0x0F\n#define _AVS_COAP_UDP_HEADER_TOKEN_LENGTH_SHIFT 0\n\nstatic inline uint8_t\n_avs_coap_udp_header_get_token_length(const avs_coap_udp_header_t *hdr) {\n    int val = _AVS_FIELD_GET(hdr->version_type_token_length,\n                             _AVS_COAP_UDP_HEADER_TOKEN_LENGTH_MASK,\n                             _AVS_COAP_UDP_HEADER_TOKEN_LENGTH_SHIFT);\n    assert(val >= 0 && val <= _AVS_COAP_UDP_HEADER_TOKEN_LENGTH_MASK);\n    return (uint8_t) val;\n}\n\nstatic inline void\n_avs_coap_udp_header_set_token_length(avs_coap_udp_header_t *hdr,\n                                      uint8_t token_length) {\n    assert(token_length <= AVS_COAP_MAX_TOKEN_LENGTH);\n    _AVS_FIELD_SET(hdr->version_type_token_length,\n                   _AVS_COAP_UDP_HEADER_TOKEN_LENGTH_MASK,\n                   _AVS_COAP_UDP_HEADER_TOKEN_LENGTH_SHIFT, token_length);\n}\n\n/** @{\n * Internal macros used for retrieving CoAP message type from\n * @ref coap_msg_header_t .\n */\n#define _AVS_COAP_UDP_HEADER_TYPE_MASK 0x30\n#define _AVS_COAP_UDP_HEADER_TYPE_SHIFT 4\n/** @} */\n\n/**\n * Extracts the message type from a CoAP header.\n */\nstatic inline avs_coap_udp_type_t\n_avs_coap_udp_header_get_type(const avs_coap_udp_header_t *hdr) {\n    int val = _AVS_FIELD_GET(hdr->version_type_token_length,\n                             _AVS_COAP_UDP_HEADER_TYPE_MASK,\n                             _AVS_COAP_UDP_HEADER_TYPE_SHIFT);\n    assert(val >= _AVS_COAP_UDP_TYPE_FIRST && val <= _AVS_COAP_UDP_TYPE_LAST);\n    return (avs_coap_udp_type_t) val;\n}\n\n/**\n * Sets the message type inside a CoAP header.\n */\nstatic inline void _avs_coap_udp_header_set_type(avs_coap_udp_header_t *hdr,\n                                                 avs_coap_udp_type_t type) {\n    _AVS_FIELD_SET(hdr->version_type_token_length,\n                   _AVS_COAP_UDP_HEADER_TYPE_MASK,\n                   _AVS_COAP_UDP_HEADER_TYPE_SHIFT, type);\n}\n\n/**\n * Extracts the message ID from a CoAP header as a single 16-bit unsigned\n * integer.\n *\n * The value is returned in native byte order, converted as necessary from the\n * big-endian order used in serialized messages.\n */\nstatic inline uint16_t\n_avs_coap_udp_header_get_id(const avs_coap_udp_header_t *hdr) {\n    return extract_u16(hdr->message_id);\n}\n\n/**\n * Sets a single 16-bit unsigned integer as the message ID inside a CoAP header.\n *\n * The value is converted as necessary from native byte order into the\n * big-endian order used in serialized messages.\n */\nstatic inline void _avs_coap_udp_header_set_id(avs_coap_udp_header_t *hdr,\n                                               uint16_t msg_id) {\n    uint16_t msg_id_nbo = avs_convert_be16(msg_id);\n    memcpy(hdr->message_id, &msg_id_nbo, sizeof(msg_id_nbo));\n}\n\nstatic inline void _avs_coap_udp_header_set(avs_coap_udp_header_t *hdr,\n                                            avs_coap_udp_type_t type,\n                                            uint8_t token_length,\n                                            uint8_t code,\n                                            uint16_t id) {\n    _avs_coap_udp_header_set_version(hdr, 1);\n    _avs_coap_udp_header_set_type(hdr, type);\n    _avs_coap_udp_header_set_token_length(hdr, token_length);\n    hdr->code = code;\n    _avs_coap_udp_header_set_id(hdr, id);\n}\n\nstatic inline avs_coap_udp_header_t\n_avs_coap_udp_header_init(avs_coap_udp_type_t type,\n                          uint8_t token_length,\n                          uint8_t code,\n                          uint16_t id) {\n    avs_coap_udp_header_t hdr = { 0 };\n    _avs_coap_udp_header_set(&hdr, type, token_length, code, id);\n    return hdr;\n}\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif // AVS_COAP_SRC_UDP_UDP_HEADER_H\n"
  },
  {
    "path": "deps/avs_coap/src/udp/avs_coap_udp_msg.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <avs_coap_init.h>\n\n#ifdef WITH_AVS_COAP_UDP\n\n#    include <inttypes.h>\n\n#    include <avsystem/coap/ctx.h>\n\n#    include \"udp/avs_coap_udp_msg.h\"\n\n#    include \"avs_coap_code_utils.h\"\n#    include \"options/avs_coap_iterator.h\"\n\n#    define MODULE_NAME coap_udp\n#    include <avs_coap_x_log_config.h>\n\n#    include \"avs_coap_common_utils.h\"\n#    include \"options/avs_coap_options.h\"\n\nVISIBILITY_SOURCE_BEGIN\n\nstatic bool is_msg_header_valid(const avs_coap_udp_header_t *hdr) {\n    uint8_t version = _avs_coap_udp_header_get_version(hdr);\n    if (version != 1) {\n        LOG(DEBUG, _(\"unsupported CoAP version: \") \"%u\", version);\n        return false;\n    }\n\n    if (_avs_coap_udp_header_get_token_length(hdr)\n            > AVS_COAP_MAX_TOKEN_LENGTH) {\n        LOG(DEBUG, _(\"invalid token longer than \") \"%u\" _(\" bytes\"),\n            (unsigned) AVS_COAP_MAX_TOKEN_LENGTH);\n        return false;\n    }\n\n    const avs_coap_udp_type_t type = _avs_coap_udp_header_get_type(hdr);\n\n    switch (type) {\n    case AVS_COAP_UDP_TYPE_ACKNOWLEDGEMENT:\n        if (avs_coap_code_is_request(hdr->code)) {\n            LOG(DEBUG,\n                _(\"Request code (\") \"%s\" _(\n                        \") on an Acknowledgement makes no sense\"),\n                AVS_COAP_CODE_STRING(hdr->code));\n            return false;\n        }\n        break;\n\n    case AVS_COAP_UDP_TYPE_RESET:\n        if (hdr->code != AVS_COAP_CODE_EMPTY) {\n            LOG(DEBUG,\n                _(\"Reset message must use \") \"%s\" _(\" CoAP code (got \") \"%s\" _(\n                        \")\"),\n                AVS_COAP_CODE_STRING(AVS_COAP_CODE_EMPTY),\n                AVS_COAP_CODE_STRING(hdr->code));\n            return false;\n        }\n        break;\n\n    default:\n        break;\n    }\n\n    return true;\n}\n\nstatic avs_error_t parse_header(avs_coap_udp_header_t *out_hdr,\n                                bytes_dispenser_t *dispenser) {\n    if (_avs_coap_bytes_extract(dispenser, out_hdr, sizeof(*out_hdr))\n            || !is_msg_header_valid(out_hdr)) {\n        LOG(DEBUG, _(\"malformed CoAP/UDP header\"));\n        return _avs_coap_err(AVS_COAP_ERR_MALFORMED_MESSAGE);\n    }\n\n    if (out_hdr->code == AVS_COAP_CODE_EMPTY && dispenser->bytes_left > 0) {\n        LOG(DEBUG, \"%s\" _(\" message must not have token, options nor payload\"),\n            AVS_COAP_CODE_STRING(AVS_COAP_CODE_EMPTY));\n        return _avs_coap_err(AVS_COAP_ERR_MALFORMED_MESSAGE);\n    }\n\n    return AVS_OK;\n}\n\nstatic avs_error_t parse_payload(const void **out_payload,\n                                 size_t *out_payload_size,\n                                 bytes_dispenser_t *dispenser) {\n    *out_payload = dispenser->read_ptr;\n    *out_payload_size = dispenser->bytes_left;\n\n    if (*out_payload_size == 0) {\n        // no payload after options\n        return AVS_OK;\n    }\n\n    // ensured by parse_options\n    assert(*dispenser->read_ptr == AVS_COAP_PAYLOAD_MARKER);\n\n    *out_payload = dispenser->read_ptr + 1;\n    *out_payload_size -= 1;\n\n    if (*out_payload_size == 0) {\n        // not MALFORMED_MESSAGE, because the header is still valid\n        LOG(DEBUG, _(\"payload marker must be omitted if there is no payload\"));\n        return _avs_coap_err(AVS_COAP_ERR_MALFORMED_OPTIONS);\n    }\n\n    return AVS_OK;\n}\n\nstatic inline avs_error_t parse_token(avs_coap_udp_msg_t *out_msg,\n                                      bytes_dispenser_t *dispenser) {\n    return _avs_coap_parse_token(\n            &out_msg->token,\n            _avs_coap_udp_header_get_token_length(&out_msg->header), dispenser);\n}\n\n#    ifdef WITH_AVS_COAP_BLOCK\nstatic inline avs_error_t\nvalidate_block_opt(avs_coap_options_t *opts,\n                   avs_coap_option_block_type_t block_type) {\n    avs_coap_option_block_t block;\n    if (avs_coap_options_get_block(opts, block_type, &block) == 0\n            && block.is_bert) {\n        LOG(DEBUG, _(\"BERT option in CoAP/UDP message\"));\n        return _avs_coap_err(AVS_COAP_ERR_MALFORMED_OPTIONS);\n    }\n    return AVS_OK;\n}\n#    endif // WITH_AVS_COAP_BLOCK\n\navs_error_t _avs_coap_udp_msg_parse(avs_coap_udp_msg_t *out_msg,\n                                    const void *packet,\n                                    size_t packet_size) {\n    bytes_dispenser_t dispenser = {\n        .read_ptr = (const uint8_t *) packet,\n        .bytes_left = packet_size\n    };\n\n    avs_error_t err;\n    (void) (avs_is_err((err = parse_header(&out_msg->header, &dispenser)))\n            || avs_is_err((err = parse_token(out_msg, &dispenser)))\n            || avs_is_err((err = _avs_coap_options_parse(\n                                   &out_msg->options, &dispenser, NULL, NULL)))\n            || avs_is_err((err = parse_payload(&out_msg->payload,\n                                               &out_msg->payload_size,\n                                               &dispenser))));\n\n#    ifdef WITH_AVS_COAP_BLOCK\n    if (avs_is_ok(err)) {\n        (void) (avs_is_err((err = validate_block_opt(&out_msg->options,\n                                                     AVS_COAP_BLOCK1)))\n                || avs_is_err((err = validate_block_opt(&out_msg->options,\n                                                        AVS_COAP_BLOCK2))));\n    }\n\n    if (avs_is_ok(err)\n            && !_avs_coap_options_block_payload_valid(&out_msg->options,\n                                                      out_msg->header.code,\n                                                      out_msg->payload_size)) {\n        err = _avs_coap_err(AVS_COAP_ERR_MALFORMED_OPTIONS);\n    }\n#    endif // WITH_AVS_COAP_BLOCK\n\n    return err;\n}\n\nvoid _avs_coap_udp_msg_parse_truncated(avs_coap_udp_msg_t *out_msg,\n                                       const uint8_t *packet,\n                                       size_t packet_size,\n                                       bool *out_has_token,\n                                       bool *out_has_options) {\n    bytes_dispenser_t dispenser = {\n        .read_ptr = packet,\n        .bytes_left = packet_size\n    };\n\n    memset(out_msg, 0, sizeof(*out_msg));\n\n    *out_has_token = false;\n    *out_has_options = false;\n    if (avs_is_err(parse_header(&out_msg->header, &dispenser))\n            || avs_is_err(_avs_coap_parse_token(\n                       &out_msg->token,\n                       _avs_coap_udp_header_get_token_length(&out_msg->header),\n                       &dispenser))) {\n        return;\n    }\n    *out_has_token = true;\n\n    if (avs_is_ok(_avs_coap_options_parse(&out_msg->options, &dispenser, NULL,\n                                          NULL))) {\n        *out_has_options = true;\n    }\n}\n\navs_error_t _avs_coap_udp_msg_serialize(const avs_coap_udp_msg_t *msg,\n                                        uint8_t *buf,\n                                        size_t buf_size,\n                                        size_t *out_bytes_written) {\n    assert(msg);\n    assert(buf);\n    assert(out_bytes_written);\n\n    assert(_avs_coap_udp_header_get_token_length(&msg->header)\n           == msg->token.size);\n\n    bytes_appender_t appender = {\n        .write_ptr = buf,\n        .bytes_left = buf_size\n    };\n\n    if (_avs_coap_bytes_append(&appender, &msg->header, sizeof(msg->header))\n            || _avs_coap_bytes_append(&appender, msg->token.bytes,\n                                      msg->token.size)\n            || _avs_coap_bytes_append(&appender, msg->options.begin,\n                                      msg->options.size)) {\n        return _avs_coap_err(AVS_COAP_ERR_MESSAGE_TOO_BIG);\n    }\n\n    if (msg->payload && msg->payload_size > 0) {\n        if (_avs_coap_bytes_append(&appender, &AVS_COAP_PAYLOAD_MARKER,\n                                   sizeof(AVS_COAP_PAYLOAD_MARKER))\n                || _avs_coap_bytes_append(&appender, msg->payload,\n                                          msg->payload_size)) {\n            return _avs_coap_err(AVS_COAP_ERR_MESSAGE_TOO_BIG);\n        }\n    }\n\n    *out_bytes_written = buf_size - appender.bytes_left;\n    return AVS_OK;\n}\n\navs_error_t _avs_coap_udp_msg_copy(const avs_coap_udp_msg_t *src,\n                                   avs_coap_udp_msg_t *dst,\n                                   uint8_t *packet_buf,\n                                   size_t packet_buf_size) {\n    size_t written;\n    avs_error_t err = _avs_coap_udp_msg_serialize(src, packet_buf,\n                                                  packet_buf_size, &written);\n    if (avs_is_err(err)) {\n        return err;\n    }\n\n    // TODO: optimize\n    return _avs_coap_udp_msg_parse(dst, packet_buf, written);\n}\n\n#endif // WITH_AVS_COAP_UDP\n"
  },
  {
    "path": "deps/avs_coap/src/udp/avs_coap_udp_msg.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef AVS_COAP_SRC_UDP_UDP_MSG_H\n#define AVS_COAP_SRC_UDP_UDP_MSG_H\n\n#include <stddef.h>\n\n#include <avsystem/coap/ctx.h>\n#include <avsystem/coap/option.h>\n#include <avsystem/coap/token.h>\n\n#include \"options/avs_coap_option.h\"\n#include \"udp/avs_coap_udp_header.h\"\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\n/**\n * Non-owning wrapper around a CoAP/UDP packet buffer.\n *\n * This is a representation of a parsed UDP CoAP message. Limited-size header\n * fields (the first 4 bytes, the token) are copied into respective fields,\n * while the dynamic-size fields (options and payload) are normally stored as\n * pointers into the original buffer.\n *\n * Objects of this type thus do NOT normally require explicit creation or\n * destruction.\n */\ntypedef struct {\n    /**\n     * The first four bytes of the UDP CoAP packet.\n     *\n     * When parsing an incoming packet, this information is copied.\n     */\n    avs_coap_udp_header_t header;\n\n    /**\n     * Token used to correlate requests and responses, if any.\n     *\n     * When parsing an incoming packet, this information is copied.\n     */\n    avs_coap_token_t token;\n\n    /**\n     * Structure describing the CoAP options present in the message.\n     *\n     * When parsing an incoming packet, this structure will describe a block of\n     * data pointing inside the buffer being parsed. No actual data is copied.\n     *\n     * These options can be examined using the @ref avs_coap_options_get group\n     * of functions declared in option.h.\n     */\n    avs_coap_options_t options;\n\n    /**\n     * Pointer to the start of content payload.\n     *\n     * It may be a non-NULL value even if the message has no payload.\n     * Please use <c>payload_size</c> instead to check if payload is present.\n     *\n     * When parsing an incoming packet, this pointer will point inside the\n     * buffer being parsed. No actual data is copied.\n     */\n    const void *payload;\n\n    /**\n     * Size of the content payload, i.e. number of valid bytes in the buffer\n     * pointed to by the <c>payload</c> field.\n     */\n    size_t payload_size;\n} avs_coap_udp_msg_t;\n\n/**\n * Parses a UDP CoAP packet stored in a buffer.\n *\n * See the documentation of @ref avs_coap_udp_msg_t for more information about\n * the resulting structure.\n *\n * @param[out] out_msg     Pointer to a user-allocated structure that will be\n *                         filled with parsed information about the message.\n *\n * @param[in]  packet      Pointer to the packet data to parse.\n *\n * @param[in]  packet_size Size of the message pointed to by <c>packet</c>.\n *\n * @returns\n * - <c>AVS_OK</c> for success\n * - <c>{ AVS_COAP_ERR_CATEGORY, AVS_COAP_ERR_MALFORMED_OPTIONS }</c> if the\n *   CoAP options could not be parsed or are invalid\n * - <c>{ AVS_COAP_ERR_CATEGORY, AVS_COAP_ERR_MALFORMED_MESSAGE }</c> in case\n *   of other parsing failure\n */\navs_error_t _avs_coap_udp_msg_parse(avs_coap_udp_msg_t *out_msg,\n                                    const void *packet,\n                                    size_t packet_size);\n\n/* Parses just the CoAP/UDP header and token */\nvoid _avs_coap_udp_msg_parse_truncated(avs_coap_udp_msg_t *out_msg,\n                                       const uint8_t *packet,\n                                       size_t packet_size,\n                                       bool *out_has_token,\n                                       bool *out_has_options);\n\navs_error_t _avs_coap_udp_msg_serialize(const avs_coap_udp_msg_t *msg,\n                                        uint8_t *buf,\n                                        size_t buf_size,\n                                        size_t *out_bytes_written);\n\navs_error_t _avs_coap_udp_msg_copy(const avs_coap_udp_msg_t *src,\n                                   avs_coap_udp_msg_t *dst,\n                                   uint8_t *packet_buf,\n                                   size_t packet_buf_size);\n\nstatic inline size_t _avs_coap_udp_msg_size(const avs_coap_udp_msg_t *msg) {\n    return sizeof(msg->header) + msg->token.size + msg->options.size\n           + (msg->payload_size == 0\n                      ? 0\n                      : sizeof(AVS_COAP_PAYLOAD_MARKER) + msg->payload_size);\n}\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif // AVS_COAP_SRC_UDP_UDP_MSG_H\n"
  },
  {
    "path": "deps/avs_coap/src/udp/avs_coap_udp_msg_cache.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <avs_coap_init.h>\n\n#ifdef WITH_AVS_COAP_UDP\n\n#    include <assert.h>\n#    include <inttypes.h>\n\n#    include <avsystem/commons/avs_buffer.h>\n#    include <avsystem/commons/avs_defs.h>\n#    include <avsystem/commons/avs_list.h>\n#    include <avsystem/commons/avs_memory.h>\n#    include <avsystem/commons/avs_time.h>\n#    include <avsystem/commons/avs_utils.h>\n\n#    include <avsystem/coap/udp.h>\n\n#    include \"udp/avs_coap_udp_msg_cache.h\"\n\n#    define MODULE_NAME coap_udp\n#    include <avs_coap_x_log_config.h>\n\n#    include \"udp/avs_coap_udp_tx_params.h\"\n\nVISIBILITY_SOURCE_BEGIN\n\ntypedef struct endpoint {\n    size_t refcount;\n    char addr[AVS_ADDRSTRLEN];\n    char port[sizeof(\"65535\")];\n} endpoint_t;\n\nstruct avs_coap_udp_response_cache {\n    AVS_LIST(endpoint_t) endpoints; // sorted by id\n\n    // priority queue of cache_entry_t, sorted by expiration_time\n    avs_buffer_t *buffer;\n};\n\ntypedef struct cache_entry {\n    endpoint_t *endpoint;\n    avs_time_monotonic_t expiration_time;\n    uint16_t msg_size;\n    const uint8_t\n            data[1]; // actually a FAM: serialized avs_coap_udp_msg_t + padding\n} cache_entry_t;\n\navs_coap_udp_response_cache_t *\navs_coap_udp_response_cache_create(size_t capacity) {\n    if (capacity == 0) {\n        return NULL;\n    }\n\n    avs_coap_udp_response_cache_t *cache =\n            (avs_coap_udp_response_cache_t *) avs_calloc(\n                    1, sizeof(avs_coap_udp_response_cache_t));\n    if (!cache) {\n        return NULL;\n    }\n\n    if (avs_buffer_create(&cache->buffer, capacity)) {\n        avs_free(cache);\n        return NULL;\n    }\n\n    assert((size_t) (uintptr_t) avs_buffer_raw_insert_ptr(cache->buffer)\n                   % AVS_ALIGNOF(cache_entry_t)\n           == 0);\n    return cache;\n}\n\nvoid avs_coap_udp_response_cache_release(\n        avs_coap_udp_response_cache_t **cache_ptr) {\n    if (cache_ptr && *cache_ptr) {\n        avs_buffer_free(&(*cache_ptr)->buffer);\n        AVS_LIST_CLEAR(&(*cache_ptr)->endpoints);\n        avs_free(*cache_ptr);\n        *cache_ptr = NULL;\n    }\n}\n\nstatic endpoint_t *cache_endpoint_add_ref(avs_coap_udp_response_cache_t *cache,\n                                          const char *remote_addr,\n                                          const char *remote_port) {\n    assert(remote_addr);\n    assert(remote_port);\n\n    AVS_LIST(endpoint_t) *ep_ptr;\n    AVS_LIST_FOREACH_PTR(ep_ptr, &cache->endpoints) {\n        if (!strcmp(remote_addr, (*ep_ptr)->addr)\n                && !strcmp(remote_port, (*ep_ptr)->port)) {\n            if ((*ep_ptr)->refcount == SIZE_MAX) {\n                LOG(WARNING, _(\"msg_cache: endpoint refcount overflow\"));\n                return NULL;\n            }\n            ++(*ep_ptr)->refcount;\n            return *ep_ptr;\n        }\n    }\n\n    AVS_LIST(endpoint_t) new_ep = AVS_LIST_NEW_ELEMENT(endpoint_t);\n    if (!new_ep) {\n        LOG_OOM();\n        return NULL;\n    }\n\n    if (avs_simple_snprintf(new_ep->addr, sizeof(new_ep->addr), \"%s\",\n                            remote_addr)\n                    < 0\n            || avs_simple_snprintf(new_ep->port, sizeof(new_ep->port), \"%s\",\n                                   remote_port)\n                           < 0) {\n        LOG(WARNING,\n            _(\"endpoint address or port too long: addr = \") \"%s\" _(\n                    \", port = \") \"%s\",\n            remote_addr, remote_port);\n        AVS_LIST_DELETE(&new_ep);\n        return NULL;\n    }\n\n    new_ep->refcount = 1;\n    AVS_LIST_INSERT(&cache->endpoints, new_ep);\n\n    LOG(TRACE, _(\"added cache endpoint: \") \"%s:%s\", new_ep->addr, new_ep->port);\n    return new_ep;\n}\n\nstatic void cache_endpoint_del_ref(avs_coap_udp_response_cache_t *cache,\n                                   endpoint_t *endpoint) {\n    assert(endpoint->refcount > 0);\n    if (--endpoint->refcount == 0) {\n        AVS_LIST(endpoint_t) *ep_ptr =\n                (AVS_LIST(endpoint_t) *) AVS_LIST_FIND_PTR(&cache->endpoints,\n                                                           endpoint);\n        assert(ep_ptr && *ep_ptr && *ep_ptr == endpoint);\n        LOG(TRACE, _(\"removed cache endpoint: \") \"%s:%s\", (*ep_ptr)->addr,\n            (*ep_ptr)->port);\n        AVS_LIST_DELETE(ep_ptr);\n    }\n}\n\nstatic size_t padding_bytes_after_msg(size_t msg_size) {\n    static const size_t entry_alignment = AVS_ALIGNOF(cache_entry_t);\n    const size_t entry_length = offsetof(cache_entry_t, data) + msg_size;\n    if (entry_length % entry_alignment) {\n        return entry_alignment - entry_length % entry_alignment;\n    } else {\n        return 0;\n    }\n}\n\nstatic void cache_put_entry(avs_coap_udp_response_cache_t *cache,\n                            const avs_time_monotonic_t *expiration_time,\n                            endpoint_t *endpoint,\n                            const avs_coap_udp_msg_t *msg) {\n    size_t msg_size = _avs_coap_udp_msg_size(msg);\n    AVS_ASSERT(msg_size <= UINT16_MAX,\n               \"messages larger than 2^16-1 are not supposed to be used with \"\n               \"UDP\");\n\n    cache_entry_t entry = {\n        .endpoint = endpoint,\n        .expiration_time = *expiration_time,\n        .msg_size = (uint16_t) msg_size\n    };\n\n    assert(avs_buffer_data_size(cache->buffer) % AVS_ALIGNOF(cache_entry_t)\n           == 0);\n    int res;\n    res = avs_buffer_append_bytes(cache->buffer, &entry,\n                                  offsetof(cache_entry_t, data));\n    assert(!res);\n    size_t written;\n    avs_error_t err = _avs_coap_udp_msg_serialize(\n            msg, (uint8_t *) avs_buffer_raw_insert_ptr(cache->buffer),\n            avs_buffer_space_left(cache->buffer), &written);\n    assert(avs_is_ok(err));\n    (void) err;\n    res = avs_buffer_advance_ptr(cache->buffer, written);\n    assert(!res);\n    res = avs_buffer_fill_bytes(cache->buffer, '\\xDD',\n                                padding_bytes_after_msg(msg_size));\n    assert(!res);\n    assert(avs_buffer_data_size(cache->buffer) % AVS_ALIGNOF(cache_entry_t)\n           == 0);\n    (void) res;\n}\n\nstatic const cache_entry_t *\nentry_first(const avs_coap_udp_response_cache_t *cache) {\n    const cache_entry_t *result =\n            (const cache_entry_t *) avs_buffer_data(cache->buffer);\n    assert((size_t) (uintptr_t) result % AVS_ALIGNOF(cache_entry_t) == 0);\n    return result;\n}\n\nstatic bool entry_valid(const avs_coap_udp_response_cache_t *cache,\n                        const cache_entry_t *entry) {\n    assert((const char *) entry >= avs_buffer_data(cache->buffer));\n    size_t entry_offset =\n            (size_t) ((const char *) entry - avs_buffer_data(cache->buffer));\n    assert(entry_offset % AVS_ALIGNOF(cache_entry_t) == 0);\n    // NOTE: NEVER use avs_buffer_raw_insert_ptr() during iteration,\n    // as it may defragment the buffer and cause UB\n    return entry_offset < avs_buffer_data_size(cache->buffer);\n}\n\nstatic bool entry_expired(const cache_entry_t *entry,\n                          const avs_time_monotonic_t *now) {\n    return !avs_time_monotonic_before(*now, entry->expiration_time);\n}\n\n/* returns total size of avs_coap_udp_msg_t, including length field\n * and padding after the message */\nstatic size_t entry_msg_size(const cache_entry_t *entry) {\n    return entry->msg_size + padding_bytes_after_msg(entry->msg_size);\n}\n\n/* returns total size of cache entry, including header, message, and\n * and padding */\nstatic size_t entry_size(const cache_entry_t *entry) {\n    size_t result = offsetof(cache_entry_t, data) + entry_msg_size(entry);\n    assert(result % AVS_ALIGNOF(cache_entry_t) == 0);\n    return result;\n}\n\nstatic uint16_t entry_id(const cache_entry_t *entry) {\n    return _avs_coap_udp_header_get_id(\n            (const avs_coap_udp_header_t *) entry->data);\n}\n\nstatic const cache_entry_t *entry_next(const cache_entry_t *entry) {\n    const cache_entry_t *result =\n            (const cache_entry_t *) ((const char *) entry + entry_size(entry));\n    assert((size_t) (uintptr_t) result % AVS_ALIGNOF(cache_entry_t) == 0);\n    return result;\n}\n\nstatic void cache_free_bytes(avs_coap_udp_response_cache_t *cache,\n                             size_t bytes_required) {\n    assert(bytes_required <= avs_buffer_capacity(cache->buffer));\n\n    size_t bytes_free = avs_buffer_space_left(cache->buffer);\n\n    const cache_entry_t *entry;\n    for (entry = entry_first(cache); bytes_free < bytes_required;\n         entry = entry_next(entry)) {\n        assert(entry_valid(cache, entry));\n\n        LOG(TRACE,\n            _(\"msg_cache: dropping msg (id = \") \"%u\" _(\n                    \") to make room for a new one (size = \") \"%lu\" _(\")\"),\n            entry_id(entry), (unsigned long) bytes_required);\n        cache_endpoint_del_ref(cache, entry->endpoint);\n        bytes_free += entry_size(entry);\n    }\n\n    size_t expired_bytes = (uintptr_t) entry - (uintptr_t) entry_first(cache);\n    int res = avs_buffer_consume_bytes(cache->buffer, expired_bytes);\n    assert(!res);\n    (void) res;\n}\n\nstatic void cache_drop_expired(avs_coap_udp_response_cache_t *cache,\n                               const avs_time_monotonic_t *now) {\n    const cache_entry_t *entry;\n    for (entry = entry_first(cache); entry_valid(cache, entry);\n         entry = entry_next(entry)) {\n        if (entry_expired(entry, now)) {\n            LOG(TRACE, _(\"msg_cache: dropping expired msg (id = \") \"%u\" _(\")\"),\n                entry_id(entry));\n            cache_endpoint_del_ref(cache, entry->endpoint);\n        } else {\n            break;\n        }\n    }\n\n    size_t expired_bytes = (uintptr_t) entry - (uintptr_t) entry_first(cache);\n    int res = avs_buffer_consume_bytes(cache->buffer, expired_bytes);\n    assert(!res);\n    (void) res;\n}\n\nstatic const cache_entry_t *\nfind_entry(const avs_coap_udp_response_cache_t *cache,\n           const char *remote_addr,\n           const char *remote_port,\n           uint16_t msg_id) {\n    for (const cache_entry_t *entry = entry_first(cache);\n         entry_valid(cache, entry);\n         entry = entry_next(entry)) {\n        if (entry_id(entry) == msg_id\n                && !strcmp(entry->endpoint->addr, remote_addr)\n                && !strcmp(entry->endpoint->port, remote_port)) {\n            return entry;\n        }\n    }\n\n    return NULL;\n}\n\nint _avs_coap_udp_response_cache_add(\n        avs_coap_udp_response_cache_t *cache,\n        const char *remote_addr,\n        const char *remote_port,\n        const avs_coap_udp_msg_t *msg,\n        const avs_coap_udp_tx_params_t *tx_params) {\n    if (!cache) {\n        return -1;\n    }\n\n    size_t cap_req = (_avs_coap_udp_response_cache_overhead(msg)\n                      + _avs_coap_udp_msg_size(msg));\n    if (avs_buffer_capacity(cache->buffer) < cap_req) {\n        LOG(DEBUG,\n            _(\"msg_cache: not enough space for \") \"%\" PRIu32 _(\" B message\"),\n            (uint32_t) _avs_coap_udp_msg_size(msg));\n        return -1;\n    }\n\n    avs_time_monotonic_t now = avs_time_monotonic_now();\n    cache_drop_expired(cache, &now);\n\n    uint16_t msg_id = _avs_coap_udp_header_get_id(&msg->header);\n    if (find_entry(cache, remote_addr, remote_port, msg_id)) {\n        LOG(DEBUG, _(\"msg_cache: message ID \") \"%u\" _(\" already in cache\"),\n            msg_id);\n        return AVS_COAP_MSG_CACHE_DUPLICATE;\n    }\n\n    endpoint_t *ep = cache_endpoint_add_ref(cache, remote_addr, remote_port);\n    if (!ep) {\n        return -1;\n    }\n\n    cache_free_bytes(cache, cap_req);\n\n    const avs_time_duration_t exchange_lifetime =\n            avs_coap_udp_exchange_lifetime(tx_params);\n    avs_time_monotonic_t expiration_time =\n            avs_time_monotonic_add(now, exchange_lifetime);\n\n    cache_put_entry(cache, &expiration_time, ep, msg);\n    return 0;\n}\n\navs_error_t\n_avs_coap_udp_response_cache_get(avs_coap_udp_response_cache_t *cache,\n                                 const char *remote_addr,\n                                 const char *remote_port,\n                                 uint16_t msg_id,\n                                 avs_coap_udp_cached_response_t *out_response) {\n    if (!cache) {\n        return avs_errno(AVS_EINVAL);\n    }\n\n    avs_time_monotonic_t now = avs_time_monotonic_now();\n    cache_drop_expired(cache, &now);\n\n    const cache_entry_t *entry =\n            find_entry(cache, remote_addr, remote_port, msg_id);\n    if (!entry) {\n        return avs_errno(AVS_ENOENT);\n    }\n\n    assert(!entry_expired(entry, &now));\n\n    LOG(TRACE, _(\"msg_cache hit (id = \") \"%u\" _(\")\"), msg_id);\n    out_response->packet = entry->data;\n    out_response->packet_size = entry->msg_size;\n    return _avs_coap_udp_msg_parse(&out_response->msg, entry->data,\n                                   entry->msg_size);\n}\n\nsize_t _avs_coap_udp_response_cache_overhead(const avs_coap_udp_msg_t *msg) {\n    return offsetof(cache_entry_t, data)\n           + padding_bytes_after_msg(_avs_coap_udp_msg_size(msg));\n}\n\n#endif // WITH_AVS_COAP_UDP\n"
  },
  {
    "path": "deps/avs_coap/src/udp/avs_coap_udp_msg_cache.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef AVS_COAP_MSG_CACHE_H\n#define AVS_COAP_MSG_CACHE_H\n\n#include <stddef.h>\n#include <stdint.h>\n\n#include <avsystem/coap/udp.h>\n\n#include \"udp/avs_coap_udp_msg.h\"\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\n#define AVS_COAP_MSG_CACHE_DUPLICATE (-2)\n\ntypedef struct {\n    avs_coap_udp_msg_t msg;\n    const void *packet;\n    size_t packet_size;\n} avs_coap_udp_cached_response_t;\n\n/**\n * Adds a message to cache. Drops oldest cache entries if needed to fit\n * @p msg, even if they did not expire yet.\n *\n * Cached message expires after EXCHANGE_LIFETIME from being added to the cache.\n *\n * @param cache       Cache object to put message in.\n * @param remote_addr Message recipient address. Messages sent to one recipient\n *                    will not be considered valid for another one.\n * @param remote_port Message recipient port.\n * @param msg         Message to cache.\n * @param tx_params   Transmission params. Determine cached message lifetime.\n *\n * @return 0 on success, a negative value if:\n *         @li @p cache is NULL,\n *         @li there was not enough memory to allocate endpoint data,\n *         @li @p cache is too small to fit @p msg,\n *         @li @p cache already contains a message with the same remote endpoint\n *             and message ID (@ref AVS_COAP_MSG_CACHE_DUPLICATE).\n *\n * NOTE: this function intentionally fails if a message with the same remote\n * endpoint and message ID is already present. If there is a valid one in the\n * cache, we should have used it instead of preparing a new response, so that\n * indicates a bug hiding somewhere.\n */\nint _avs_coap_udp_response_cache_add(avs_coap_udp_response_cache_t *cache,\n                                     const char *remote_addr,\n                                     const char *remote_port,\n                                     const avs_coap_udp_msg_t *msg,\n                                     const avs_coap_udp_tx_params_t *tx_params);\n\n/**\n * Looks up @p cache for a message with given @p msg_id and returns it if found.\n *\n * @param      cache       Cache object to look into, or NULL.\n * @param      remote_addr Message recipient address. Messages sent to one\n * recipient will not be considered valid for another one.\n * @param      remote_port Message recipient port.\n * @param      msg_id      CoAP message ID to look for.\n * @param[out] out_msg     Message object to fill on success.\n *\n * @return AVS_OK if a message matching @p msg_id was found in the cache and\n *         returned via @p out_msg, or an error condition for which the\n *         operation failed.\n */\navs_error_t\n_avs_coap_udp_response_cache_get(avs_coap_udp_response_cache_t *cache,\n                                 const char *remote_addr,\n                                 const char *remote_port,\n                                 uint16_t msg_id,\n                                 avs_coap_udp_cached_response_t *out_response);\n\n/**\n * @return Extra overhead, in bytes, required to put @p msg in cache. Total\n *         number of bytes used by a message is:\n *         <c>_avs_coap_udp_response_cache_overhead(msg)\n *         + _avs_coap_udp_msg_size(msg)</c>\n */\nsize_t _avs_coap_udp_response_cache_overhead(const avs_coap_udp_msg_t *msg);\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif // AVS_COAP_MSG_CACHE_H\n"
  },
  {
    "path": "deps/avs_coap/src/udp/avs_coap_udp_tx_params.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <avs_coap_init.h>\n\n#ifdef WITH_AVS_COAP_UDP\n\n#    include <avsystem/commons/avs_time.h>\n#    include <avsystem/commons/avs_utils.h>\n\n#    include <avsystem/coap/udp.h>\n\n#    define MODULE_NAME coap_udp\n#    include <avs_coap_x_log_config.h>\n\nVISIBILITY_SOURCE_BEGIN\n\nconst avs_coap_udp_tx_params_t AVS_COAP_DEFAULT_UDP_TX_PARAMS = {\n    .ack_timeout = { 2, 0 },\n    .ack_random_factor = 1.5,\n    .max_retransmit = 4,\n    .nstart = 1\n};\n\nbool avs_coap_udp_tx_params_valid(const avs_coap_udp_tx_params_t *tx_params,\n                                  const char **error_details) {\n    // ACK_TIMEOUT below 1 second would violate the guidelines of [RFC5405].\n    // -- RFC 7252, 4.8.1\n    if (avs_time_duration_less(tx_params->ack_timeout,\n                               avs_time_duration_from_scalar(1, AVS_TIME_S))) {\n        if (error_details) {\n            *error_details = \"ACK_TIMEOUT below 1000 milliseconds\";\n        }\n        return false;\n    }\n\n    // ACK_RANDOM_FACTOR MUST NOT be decreased below 1.0, and it SHOULD have\n    // a value that is sufficiently different from 1.0 to provide some\n    // protection from synchronization effects.\n    // -- RFC 7252, 4.8.1\n    if (tx_params->ack_random_factor < 1.0) {\n        if (error_details) {\n            *error_details = \"ACK_RANDOM_FACTOR less than 1.0\";\n        }\n        return false;\n    }\n\n    if (tx_params->nstart == 0) {\n        if (error_details) {\n            *error_details = \"NSTART less than 1 is useless\";\n        }\n        return false;\n    }\n\n    if (error_details) {\n        *error_details = NULL;\n    }\n    return true;\n}\n\navs_time_duration_t\navs_coap_udp_max_transmit_wait(const avs_coap_udp_tx_params_t *tx_params) {\n    return avs_time_duration_fmul(tx_params->ack_timeout,\n                                  ((1 << (tx_params->max_retransmit + 1)) - 1)\n                                          * tx_params->ack_random_factor);\n}\n\navs_time_duration_t\navs_coap_udp_max_transmit_span(const avs_coap_udp_tx_params_t *tx_params) {\n    return avs_time_duration_fmul(tx_params->ack_timeout,\n                                  (double) ((1 << tx_params->max_retransmit)\n                                            - 1)\n                                          * tx_params->ack_random_factor);\n}\n\n// See https://tools.ietf.org/html/rfc7252#section-4.8.2\nstatic const avs_time_duration_t MAX_LATENCY = { 100, 0 };\n\navs_time_duration_t\navs_coap_udp_exchange_lifetime(const avs_coap_udp_tx_params_t *tx_params) {\n    return avs_time_duration_add(\n            avs_time_duration_add(avs_coap_udp_max_transmit_span(tx_params),\n                                  avs_time_duration_mul(MAX_LATENCY, 2)),\n            tx_params->ack_timeout);\n}\n\n#endif // WITH_AVS_COAP_UDP\n"
  },
  {
    "path": "deps/avs_coap/src/udp/avs_coap_udp_tx_params.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef AVS_COAP_SRC_UDP_UDP_TX_PARAMS_H\n#define AVS_COAP_SRC_UDP_UDP_TX_PARAMS_H\n\n#include <stdint.h>\n\n#include <avsystem/commons/avs_time.h>\n\n#include <avsystem/coap/udp.h>\n\n#include \"avs_coap_udp_ctx.h\"\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\n/**\n * @returns MAX_TRANSMIT_SPAN value derived from @p tx_params according\n *          to the formula specified in RFC7252.\n */\nstatic inline avs_time_duration_t\n_avs_coap_udp_max_transmit_span(const avs_coap_udp_tx_params_t *tx_params) {\n    return avs_time_duration_fmul(tx_params->ack_timeout,\n                                  (double) ((1 << tx_params->max_retransmit)\n                                            - 1)\n                                          * tx_params->ack_random_factor);\n}\n\nstatic inline avs_error_t\n_avs_coap_udp_initial_retry_state(avs_coap_udp_ctx_t *ctx,\n                                  avs_coap_retry_state_t *out_retry_state) {\n    uint32_t random;\n    if (avs_crypto_prng_bytes(ctx->base.prng_ctx,\n                              (unsigned char *) &random,\n                              sizeof(random))) {\n        return _avs_coap_err(AVS_COAP_ERR_PRNG_FAIL);\n    }\n\n    double random_factor = ((double) random / (double) UINT32_MAX)\n                           * (ctx->tx_params.ack_random_factor - 1.0);\n\n    *out_retry_state = (avs_coap_retry_state_t) {\n        .retries_left = ctx->tx_params.max_retransmit,\n        .recv_timeout = avs_time_duration_fmul(ctx->tx_params.ack_timeout,\n                                               1.0 + random_factor)\n    };\n    return AVS_OK;\n}\n\n#ifdef AVS_UNIT_TESTING\n#    include \"../tests/udp/tx_params_mock.h\"\n#endif // AVS_UNIT_TESTING\n\nstatic inline int\n_avs_coap_udp_update_retry_state(avs_coap_udp_ctx_t *ctx,\n                                 avs_coap_retry_state_t *retry_state) {\n    retry_state->recv_timeout =\n            avs_time_duration_mul(retry_state->recv_timeout, 2);\n    --retry_state->retries_left;\n    if (!avs_time_duration_valid(retry_state->recv_timeout)) {\n        return -1;\n    }\n    (void) ctx;\n    return 0;\n}\n\n/**\n * @returns true if all packets in a retransmission sequence were already\n * sent, false otherwise.\n */\nstatic inline bool\n_avs_coap_udp_all_retries_sent(const avs_coap_retry_state_t *retry_state) {\n    return retry_state->retries_left <= 0;\n}\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif // AVS_COAP_SRC_UDP_UDP_TX_PARAMS_H\n"
  },
  {
    "path": "deps/avs_coap/tests/fuzz/CMakeLists.txt",
    "content": "# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem CoAP library\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nif(NOT CMAKE_C_COMPILER MATCHES \"/afl-.*$\")\n    set(FUZZ_TEST_EXCLUDE_FROM_ALL EXCLUDE_FROM_ALL)\nendif()\n\nadd_executable(avs_coap_parse_stdin ${FUZZ_TEST_EXCLUDE_FROM_ALL}\n               coap_parse.c)\ntarget_include_directories(avs_coap_parse_stdin PRIVATE\n                           $<TARGET_PROPERTY:avs_coap,INCLUDE_DIRECTORIES>)\ntarget_link_libraries(avs_coap_parse_stdin PRIVATE avs_coap)\n\nadd_executable(avs_coap_async_api_udp ${FUZZ_TEST_EXCLUDE_FROM_ALL}\n               coap_async_api_udp.c)\ntarget_include_directories(avs_coap_async_api_udp PRIVATE\n                           $<TARGET_PROPERTY:avs_coap,INCLUDE_DIRECTORIES>)\ntarget_link_libraries(avs_coap_async_api_udp PRIVATE avs_coap)\nadd_executable(avs_coap_async_api_tcp ${FUZZ_TEST_EXCLUDE_FROM_ALL}\n               coap_async_api_tcp.c)\ntarget_include_directories(avs_coap_async_api_tcp PRIVATE\n                           $<TARGET_PROPERTY:avs_coap,INCLUDE_DIRECTORIES>)\ntarget_link_libraries(avs_coap_async_api_tcp PRIVATE avs_coap)\n\n"
  },
  {
    "path": "deps/avs_coap/tests/fuzz/coap_async_api_tcp.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#define AVS_COAP_POISON_H // disable libc poisoning\n#include <avs_coap_init.h>\n\n#include <avsystem/coap/coap.h>\n#include <avsystem/coap/tcp.h>\n\n#include <avsystem/commons/avs_log.h>\n#include <avsystem/commons/avs_socket.h>\n#include <avsystem/commons/avs_socket_v_table.h>\n#include <avsystem/commons/avs_utils.h>\n\n#include <inttypes.h>\n#include <stdio.h>\n\n#define MODULE_NAME fuzz\n#include <avs_coap_x_log_config.h>\n\nstatic uint16_t g_mtu = 1500;\nstatic avs_sched_t *g_sched = NULL;\n\nstatic avs_error_t unexpected() {\n    abort();\n    return avs_errno(AVS_UNKNOWN_ERROR);\n}\n\nstatic const void *unexpected_system_socket() {\n    abort();\n    return NULL;\n}\n\nstatic bool read_flag(void) {\n    uint8_t value = false;\n    size_t bytes_read = fread(&value, sizeof(value), 1, stdin);\n    (void) bytes_read;\n    LOG(DEBUG, \"read_flag: %02x\", (unsigned) value);\n    return value;\n}\n\nstatic void dump_buffer(const char *label, const void *buffer, size_t size) {\n    LOG(DEBUG, \"%s\", label);\n    if (avs_log_should_log__(AVS_LOG_DEBUG, \"fuzz\")) {\n#define CHUNK_SIZE 256\n        for (size_t offset = 0; offset < size; offset += CHUNK_SIZE) {\n            char buf[CHUNK_SIZE * 2 + 1];\n            avs_hexlify(buf, sizeof(buf), NULL, (const char *) buffer + offset,\n                        size - offset);\n            fprintf(stderr, \"%s\", buf);\n        }\n        fprintf(stderr, \"\\n\");\n    }\n}\n\nstatic avs_coap_options_t read_options__(char buf[static 65535]) {\n    avs_coap_options_t opts = { 0 };\n\n    uint16_t options_size;\n    uint16_t options_capacity;\n    if (fread(&options_size, sizeof(options_size), 1, stdin) != 1\n            || fread(&options_capacity, sizeof(options_capacity), 1, stdin)\n                           != 1) {\n        LOG(DEBUG, \"read_options: EOF\");\n        return (avs_coap_options_t) { 0 };\n    }\n\n    opts.size = options_size;\n    opts.capacity = options_capacity;\n    opts.begin = buf;\n    if (fread(buf, 1, opts.size, stdin) != opts.size) {\n        LOG(DEBUG, \"read options: EOF\");\n        return (avs_coap_options_t) { 0 };\n    }\n\n    dump_buffer(\"opts\", opts.begin, opts.size);\n    return opts;\n}\n\n#define read_options() read_options__(&(char[65535]){ 0 }[0])\n\nstatic avs_error_t get_opt(avs_net_socket_t *socket,\n                           avs_net_socket_opt_key_t option_key,\n                           avs_net_socket_opt_value_t *out_option_value) {\n    (void) socket;\n\n    if (option_key == AVS_NET_SOCKET_OPT_INNER_MTU) {\n        out_option_value->mtu = g_mtu;\n        return AVS_OK;\n    } else if (option_key == AVS_NET_SOCKET_OPT_RECV_TIMEOUT) {\n        out_option_value->recv_timeout =\n                avs_time_duration_from_scalar(1, AVS_TIME_S);\n        return AVS_OK;\n    }\n    return avs_errno(AVS_ENOTSUP);\n}\n\nstatic avs_error_t set_opt(avs_net_socket_t *socket,\n                           avs_net_socket_opt_key_t option_key,\n                           avs_net_socket_opt_value_t option_value) {\n    (void) socket;\n    (void) option_value;\n    if (option_key == AVS_NET_SOCKET_OPT_RECV_TIMEOUT) {\n        return AVS_OK;\n    }\n    return avs_errno(AVS_ENOTSUP);\n}\n\nuint8_t g_last_send[14];\n\nstatic uint8_t get_length(uint8_t first_byte) {\n    return (first_byte & 0xF0) >> 4;\n}\n\n#define EXTENDED_LENGTH_UINT8 13\n#define EXTENDED_LENGTH_UINT16 14\n#define EXTENDED_LENGTH_UINT32 15\n\n/**\n * Extended length field hasn't fixed size. Len field indicates how many bytes\n * of extended length are present in the message.\n */\nstatic size_t get_token_offset(uint8_t first_byte) {\n    size_t offset = 2 * sizeof(uint8_t); // LenTkl + Code\n    size_t len = get_length(first_byte);\n    switch (len) {\n    case EXTENDED_LENGTH_UINT8:\n        offset += sizeof(uint8_t);\n        break;\n    case EXTENDED_LENGTH_UINT16:\n        offset += sizeof(uint16_t);\n        break;\n    case EXTENDED_LENGTH_UINT32:\n        offset += sizeof(uint32_t);\n        break;\n    default:\n        break;\n    }\n    return offset;\n}\n\nstatic uint8_t get_token_length(uint8_t first_byte) {\n    return first_byte & 0x0F;\n}\n\nstatic avs_error_t mock_recv(avs_net_socket_t *socket,\n                             size_t *out_bytes_received,\n                             void *buffer_,\n                             size_t buffer_length) {\n    (void) socket;\n\n    uint16_t msg_size;\n    if (fread(&msg_size, sizeof(msg_size), 1, stdin) != 1\n            || msg_size == UINT16_MAX) {\n        LOG(DEBUG, \"mock_recv: fail (size == %\" PRIu64 \")\",\n            (uint64_t) msg_size);\n        return avs_errno(AVS_EIO);\n    }\n\n    if (!msg_size) {\n        return avs_errno(AVS_ETIMEDOUT);\n    }\n\n    char *tmp_buf = (char *) malloc(msg_size);\n    if (!tmp_buf) {\n        return avs_errno(AVS_ENOMEM);\n    }\n\n    *out_bytes_received = fread(tmp_buf, 1, msg_size, stdin);\n    *out_bytes_received = AVS_MIN(*out_bytes_received, buffer_length);\n    memcpy(buffer_, tmp_buf, *out_bytes_received);\n\n    uint8_t *buffer = (uint8_t *) buffer_;\n\n    if (read_flag()) {\n        uint8_t prev_toklen = get_token_length(g_last_send[0]);\n        size_t prev_token_offset = get_token_offset(g_last_send[0]);\n        size_t curr_token_offset = get_token_offset(buffer[0]);\n        if (prev_toklen <= 8\n                && buffer_length >= prev_toklen + curr_token_offset) {\n            LOG(DEBUG, \"mock_recv: echo token (%u B)\", (unsigned) prev_toklen);\n            memcpy(buffer + curr_token_offset,\n                   &g_last_send[prev_token_offset],\n                   prev_toklen);\n            buffer[0] = (uint8_t) (get_length(buffer[0]) << 4) | prev_toklen;\n        }\n    }\n\n    dump_buffer(\"recv\", buffer, *out_bytes_received);\n    LOG(DEBUG, \"mock_recv: OK, %\" PRIu64 \" B received, %\" PRIu64 \" B reported\",\n        (uint64_t) msg_size, (uint64_t) *out_bytes_received);\n\n    free(tmp_buf);\n    return AVS_OK;\n}\n\nstatic avs_error_t\nmock_send(avs_net_socket_t *socket, const void *buffer, size_t size) {\n    (void) socket;\n    (void) buffer;\n\n    memcpy(g_last_send, buffer, AVS_MIN(size, sizeof(g_last_send)));\n\n    dump_buffer(\"send\", buffer, size);\n    if (read_flag()) {\n        LOG(DEBUG, \"mock_send: fail\");\n        return avs_errno(AVS_EIO);\n    }\n    LOG(DEBUG, \"mock_send: OK\");\n    return AVS_OK;\n}\n\nstatic avs_error_t empty_string_getter(avs_net_socket_t *socket,\n                                       char *out_buffer,\n                                       size_t out_buffer_size) {\n    (void) socket;\n    (void) out_buffer_size;\n\n    assert(out_buffer_size > 0);\n    *out_buffer = '\\0';\n    return AVS_OK;\n}\n\nstatic const avs_net_socket_v_table_t MOCK_SOCKET_VTABLE = {\n    .receive = mock_recv,\n    .accept = (avs_net_socket_accept_t) unexpected,\n    .bind = (avs_net_socket_bind_t) unexpected,\n    .cleanup = (avs_net_socket_cleanup_t) unexpected,\n    .close = (avs_net_socket_close_t) unexpected,\n    .connect = (avs_net_socket_connect_t) unexpected,\n    .decorate = (avs_net_socket_decorate_t) unexpected,\n    .get_interface_name = (avs_net_socket_get_interface_t) unexpected,\n    .get_local_port = (avs_net_socket_get_local_port_t) unexpected,\n    .get_opt = get_opt,\n    .get_remote_host = empty_string_getter,\n    .get_remote_port = empty_string_getter,\n    .get_system_socket = unexpected_system_socket,\n    .receive_from = (avs_net_socket_receive_from_t) unexpected,\n    .send = mock_send,\n    .send_to = (avs_net_socket_send_to_t) unexpected,\n    .set_opt = set_opt,\n    .shutdown = (avs_net_socket_shutdown_t) unexpected,\n};\n\nconst avs_net_socket_v_table_t *g_vtable_ptr = &MOCK_SOCKET_VTABLE;\navs_net_socket_t *const g_mocksock = (avs_net_socket_t *) &g_vtable_ptr;\n\nstatic void do_stuff(avs_coap_ctx_t *ctx);\n\nstatic int payload_writer(size_t payload_offset,\n                          void *payload_buf,\n                          size_t payload_buf_size,\n                          size_t *out_payload_chunk_size,\n                          void *arg) {\n    (void) payload_offset;\n\n    do_stuff((avs_coap_ctx_t *) arg);\n\n    if (read_flag()) {\n        LOG(DEBUG, \"payload_writer: fail\");\n        return -1;\n    }\n\n    uint16_t payload_bytes;\n    if (fread(&payload_bytes, sizeof(payload_bytes), 1, stdin) != 1) {\n        payload_bytes = 0;\n    }\n    *out_payload_chunk_size = AVS_MIN(payload_bytes, payload_buf_size);\n    memset(payload_buf, 0, *out_payload_chunk_size);\n\n    LOG(DEBUG, \"payload_writer read %\" PRIu64 \" / %\" PRIu64,\n        (uint64_t) *out_payload_chunk_size, (uint64_t) payload_buf_size);\n    dump_buffer(\"payload_writer\", payload_buf, *out_payload_chunk_size);\n    return 0;\n}\n\nstatic void response_handler(avs_coap_ctx_t *ctx,\n                             avs_coap_exchange_id_t exchange_id,\n                             avs_coap_client_request_state_t state,\n                             const avs_coap_client_async_response_t *response,\n                             avs_error_t err,\n                             void *arg) {\n    (void) exchange_id;\n    (void) state;\n    (void) response;\n    (void) err;\n    (void) arg;\n\n    LOG(DEBUG, \"response_handler\");\n    if (response) {\n        dump_buffer(\"response payload\", response->payload,\n                    response->payload_size);\n    }\n\n    do_stuff(ctx);\n}\n\nstatic int handle_request(avs_coap_request_ctx_t *ctx,\n                          avs_coap_exchange_id_t request_id,\n                          avs_coap_server_request_state_t state,\n                          const avs_coap_server_async_request_t *request,\n                          const avs_coap_observe_id_t *observe_id,\n                          void *arg) {\n    avs_coap_ctx_t *coap_ctx = (avs_coap_ctx_t *) arg;\n\n    (void) request_id;\n    (void) state;\n    (void) request;\n    (void) observe_id;\n\n    do_stuff(coap_ctx);\n\n    if (read_flag()) {\n        int result = 0;\n        if (fread(&result, sizeof(result), 1, stdin) != 1) {\n            LOG(DEBUG, \"handle_request: EOF\");\n            return 0;\n        }\n        LOG(DEBUG, \"handle_request: early return, result = %d\", result);\n        return result;\n    }\n\n    avs_coap_response_header_t response;\n    if (fread(&response.code, sizeof(response.code), 1, stdin) != 1) {\n        LOG(DEBUG, \"handle_request: EOF\");\n        return 0;\n    }\n    LOG(DEBUG, \"handle_request: response code = %u\", response.code);\n    response.options = read_options();\n\n    avs_error_t err = avs_coap_server_setup_async_response(\n            ctx, &response, read_flag() ? payload_writer : NULL, coap_ctx);\n    LOG(DEBUG, \"handle_request: avs_coap_server_setup_async_response: %s\",\n        AVS_COAP_STRERROR(err));\n\n    do_stuff(coap_ctx);\n\n    int result = 0;\n    if (fread(&result, sizeof(result), 1, stdin) != 1) {\n        LOG(DEBUG, \"handle_request: EOF\");\n        return 0;\n    }\n    LOG(DEBUG, \"handle_request: result = %d\", result);\n    return result;\n}\n\nstatic int handle_new_request(avs_coap_server_ctx_t *ctx,\n                              const avs_coap_request_header_t *request,\n                              void *arg) {\n    avs_coap_ctx_t *coap_ctx = (avs_coap_ctx_t *) arg;\n\n    (void) request;\n\n    do_stuff(coap_ctx);\n\n    if (read_flag()) {\n        int result = 0;\n        if (fread(&result, sizeof(result), 1, stdin) != 1) {\n            LOG(DEBUG, \"handle_new_request: EOF\");\n            return 0;\n        }\n        LOG(DEBUG, \"handle_new_request: early return, result = %d\", result);\n        return result;\n    }\n\n    avs_coap_exchange_id_t id =\n            avs_coap_server_accept_async_request(ctx, handle_request, coap_ctx);\n    LOG(DEBUG, \"handle_new_request: accept ID = %\" PRIu64, id.value);\n\n    do_stuff(coap_ctx);\n\n    int result = 0;\n    if (fread(&result, sizeof(result), 1, stdin) != 1) {\n        LOG(DEBUG, \"handle_new_request: EOF\");\n        return 0;\n    }\n    LOG(DEBUG, \"handle_new_request: result = %d\", result);\n    return result;\n}\n\nstatic void do_stuff_unconditionally(avs_coap_ctx_t *ctx) {\n    enum {\n        FLAG_PASS_ID = (1 << 0),\n        FLAG_PASS_WRITER = (1 << 1),\n        FLAG_PASS_HANDLER = (1 << 2),\n    };\n\n    enum {\n        OP_NOOP,\n        OP_SEND_ASYNC_REQUEST,\n        OP_EXCHANGE_CANCEL,\n        OP_HANDLE_INCOMING_PACKET,\n        OP_SCHED_RUN,\n    };\n\n    uint8_t operation;\n    if (fread(&operation, sizeof(operation), 1, stdin) != 1) {\n        LOG(DEBUG, \"do_stuff: EOF\");\n        return;\n    }\n    LOG(DEBUG, \"do_stuff: %u\", operation);\n\n    switch (operation) {\n    case OP_NOOP:\n        LOG(DEBUG, \"noop\");\n        return;\n    case OP_SEND_ASYNC_REQUEST: {\n        uint8_t flags;\n        if (fread(&flags, sizeof(flags), 1, stdin) != 1) {\n            LOG(DEBUG, \"read flags: EOF\");\n            return;\n        }\n\n        bool pass_id = (flags & FLAG_PASS_ID);\n        bool pass_writer = (flags & FLAG_PASS_WRITER);\n        bool pass_handler = (flags & FLAG_PASS_HANDLER);\n\n        avs_coap_exchange_id_t id;\n        avs_coap_request_header_t req = { 0 };\n        if (fread(&req.code, sizeof(req.code), 1, stdin) != 1) {\n            LOG(DEBUG, \"read details: EOF\");\n            return;\n        }\n\n        req.options = read_options();\n        LOG(DEBUG, \"avs_coap_client_send_async_request\");\n        avs_coap_client_send_async_request(ctx,\n                                           pass_id ? &id : NULL,\n                                           &req,\n                                           pass_writer ? payload_writer : NULL,\n                                           ctx,\n                                           pass_handler ? response_handler\n                                                        : NULL,\n                                           ctx);\n\n        break;\n    }\n    case OP_EXCHANGE_CANCEL: {\n        avs_coap_exchange_id_t id;\n        if (fread(&id, sizeof(id), 1, stdin) != 1) {\n            LOG(DEBUG, \"read ID: EOF\");\n            return;\n        }\n        LOG(DEBUG, \"avs_coap_exchange_cancel %\" PRIu64, id.value);\n        avs_coap_exchange_cancel(ctx, id);\n        break;\n    }\n    case OP_HANDLE_INCOMING_PACKET: {\n        LOG(DEBUG, \"avs_coap_async_handle_incoming_packet\");\n        avs_coap_async_handle_incoming_packet(ctx, handle_new_request, ctx);\n        break;\n    }\n    case OP_SCHED_RUN: {\n        LOG(DEBUG, \"avs_sched_run\");\n        avs_sched_run(g_sched);\n        break;\n    }\n    default:\n        break;\n    }\n}\n\nstatic void do_stuff(avs_coap_ctx_t *ctx) {\n    static const size_t RECURSION_LIMIT = 20;\n    static size_t recursion_depth = 0;\n\n    if (recursion_depth >= RECURSION_LIMIT) {\n        LOG(DEBUG, \"do_stuff: recursion limit reached, returning\");\n        return;\n    }\n\n    ++recursion_depth;\n    do_stuff_unconditionally(ctx);\n    --recursion_depth;\n}\n\nint main() {\n    if (getenv(\"VERBOSE\")) {\n        avs_log_set_default_level(AVS_LOG_TRACE);\n    }\n\n    avs_shared_buffer_t *in_buffer = NULL;\n    avs_shared_buffer_t *out_buffer = NULL;\n    avs_crypto_prng_ctx_t *prng_ctx = NULL;\n    avs_coap_ctx_t *ctx = NULL;\n\n    uint16_t in_buf_size;\n    uint16_t out_buf_size;\n    uint16_t opts_buf_size;\n    uint8_t timeout_s;\n\n    if (!(g_sched = avs_sched_new(\"sched\", NULL))\n            || fread(&in_buf_size, sizeof(in_buf_size), 1, stdin) != 1\n            || fread(&out_buf_size, sizeof(out_buf_size), 1, stdin) != 1\n            || fread(&opts_buf_size, sizeof(opts_buf_size), 1, stdin) != 1\n            || fread(&timeout_s, sizeof(timeout_s), 1, stdin) != 1\n            || (read_flag() && fread(&g_mtu, sizeof(g_mtu), 1, stdin) != 1)\n            || !(in_buffer = avs_shared_buffer_new(in_buf_size))\n            || !(out_buffer = avs_shared_buffer_new(out_buf_size))\n            || !(prng_ctx = avs_crypto_prng_new(NULL, NULL))\n            || !(ctx = avs_coap_tcp_ctx_create(\n                         g_sched, in_buffer, out_buffer, opts_buf_size,\n                         avs_time_duration_from_scalar(timeout_s, AVS_TIME_S),\n                         prng_ctx))\n            || avs_is_err(avs_coap_ctx_set_socket(ctx, g_mocksock))) {\n        goto exit;\n    }\n\n    g_mtu %= (1 << 16);\n\n    while (!feof(stdin)) {\n        do_stuff(ctx);\n    }\n\nexit:\n    avs_coap_ctx_cleanup(&ctx);\n    avs_sched_cleanup(&g_sched);\n    avs_free(in_buffer);\n    avs_free(out_buffer);\n    avs_crypto_prng_free(&prng_ctx);\n}\n"
  },
  {
    "path": "deps/avs_coap/tests/fuzz/coap_async_api_udp.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#define AVS_COAP_POISON_H // disable libc poisoning\n#include <avs_coap_init.h>\n\n#include <avsystem/coap/coap.h>\n\n#include <avsystem/commons/avs_log.h>\n#include <avsystem/commons/avs_prng.h>\n#include <avsystem/commons/avs_socket.h>\n#include <avsystem/commons/avs_socket_v_table.h>\n#include <avsystem/commons/avs_utils.h>\n\n#include <inttypes.h>\n#include <stdio.h>\n\n#define MODULE_NAME fuzz\n#include <avs_coap_x_log_config.h>\n\n#include \"avs_coap_ctx.h\"\n\nstatic uint16_t g_mtu = 1500;\nstatic avs_sched_t *g_sched = NULL;\n\nstatic avs_error_t unexpected() {\n    abort();\n    return avs_errno(AVS_UNKNOWN_ERROR);\n}\n\nstatic const void *unexpected_system_socket() {\n    abort();\n    return NULL;\n}\n\nstatic bool read_flag(void) {\n    uint8_t value = false;\n    size_t bytes_read = fread(&value, sizeof(value), 1, stdin);\n    (void) bytes_read;\n    LOG(DEBUG, \"read_flag: %02x\", (unsigned) value);\n    return value;\n}\n\nstatic void dump_buffer(const char *label, const void *buffer, size_t size) {\n    LOG(DEBUG, \"%s\", label);\n    if (avs_log_should_log__(AVS_LOG_DEBUG, \"fuzz\")) {\n#define CHUNK_SIZE 256\n        for (size_t offset = 0; offset < size; offset += CHUNK_SIZE) {\n            char buf[CHUNK_SIZE * 2 + 1];\n            avs_hexlify(buf, sizeof(buf), NULL, (const char *) buffer + offset,\n                        size - offset);\n            fprintf(stderr, \"%s\", buf);\n        }\n        fprintf(stderr, \"\\n\");\n    }\n}\n\nstatic avs_coap_options_t read_options__(char buf[static 65535]) {\n    avs_coap_options_t opts = { 0 };\n\n    uint16_t options_size;\n    uint16_t options_capacity;\n    if (fread(&options_size, sizeof(options_size), 1, stdin) != 1\n            || fread(&options_capacity, sizeof(options_capacity), 1, stdin)\n                           != 1) {\n        LOG(DEBUG, \"read_options: EOF\");\n        return (avs_coap_options_t) { 0 };\n    }\n\n    opts.size = options_size;\n    opts.capacity = options_capacity;\n    opts.begin = buf;\n    if (fread(buf, 1, opts.size, stdin) != opts.size) {\n        LOG(DEBUG, \"read options: EOF\");\n        return (avs_coap_options_t) { 0 };\n    }\n\n    dump_buffer(\"opts\", opts.begin, opts.size);\n    return opts;\n}\n\n#define read_options() read_options__(&(char[65535]){ 0 }[0])\n\nstatic avs_error_t get_opt(avs_net_socket_t *socket,\n                           avs_net_socket_opt_key_t option_key,\n                           avs_net_socket_opt_value_t *out_option_value) {\n    (void) socket;\n\n    if (option_key == AVS_NET_SOCKET_OPT_INNER_MTU) {\n        out_option_value->mtu = g_mtu;\n        return AVS_OK;\n    }\n    return avs_errno(AVS_ENOTSUP);\n}\n\nstatic uint8_t get_token_length(uint8_t first_byte) {\n    return first_byte & 0x0F;\n}\n\nuint8_t g_last_send[12];\n\nstatic avs_error_t mock_recv(avs_net_socket_t *socket,\n                             size_t *out_bytes_received,\n                             void *buffer,\n                             size_t buffer_length) {\n    (void) socket;\n\n    uint16_t msg_size;\n    if (fread(&msg_size, sizeof(msg_size), 1, stdin) != 1\n            || msg_size == UINT16_MAX) {\n        LOG(DEBUG, \"mock_recv: fail (size == %\" PRIu64 \")\",\n            (uint64_t) msg_size);\n        return avs_errno(AVS_EIO);\n    }\n\n    char *tmp_buf = (char *) malloc(msg_size);\n    if (!tmp_buf) {\n        return avs_errno(AVS_ENOMEM);\n    }\n\n    *out_bytes_received = fread(tmp_buf, 1, msg_size, stdin);\n    *out_bytes_received = AVS_MIN(*out_bytes_received, buffer_length);\n    memcpy(buffer, tmp_buf, *out_bytes_received);\n\n    const size_t token_offset = 4;\n    uint8_t new_token_length = get_token_length(((uint8_t *) buffer)[0]);\n    size_t options_offset = token_offset + new_token_length;\n    if (read_flag() && new_token_length <= 8\n            && *out_bytes_received >= options_offset) {\n        size_t options_and_payload_size = *out_bytes_received - options_offset;\n        uint8_t last_toklen = get_token_length(g_last_send[0]);\n        if (options_and_payload_size + last_toklen + token_offset\n                <= buffer_length) {\n            // copy options and payload\n            memcpy(buffer + token_offset + last_toklen, buffer + options_offset,\n                   options_and_payload_size);\n            LOG(DEBUG, \"mock_recv: echo token (%u B)\", (unsigned) last_toklen);\n            memcpy(buffer + token_offset,\n                   &g_last_send[token_offset],\n                   last_toklen);\n            ((uint8_t *) buffer)[0] &= 0xF0;\n            ((uint8_t *) buffer)[0] |= (uint8_t) last_toklen;\n            LOG(DEBUG, \"mock_recv: echo ID\");\n            memcpy(&(((char *) buffer)[2]), &g_last_send[2], 2);\n        }\n    }\n\n    dump_buffer(\"recv\", buffer, *out_bytes_received);\n    LOG(DEBUG, \"mock_recv: OK, %\" PRIu64 \" B received, %\" PRIu64 \" B reported\",\n        (uint64_t) msg_size, (uint64_t) *out_bytes_received);\n\n    free(tmp_buf);\n    return AVS_OK;\n}\n\nstatic avs_error_t\nmock_send(avs_net_socket_t *socket, const void *buffer, size_t size) {\n    (void) socket;\n    (void) buffer;\n\n    memcpy(g_last_send, buffer, AVS_MIN(size, sizeof(g_last_send)));\n\n    dump_buffer(\"send\", buffer, size);\n    if (read_flag()) {\n        LOG(DEBUG, \"mock_send: fail\");\n        return avs_errno(AVS_EIO);\n    }\n    LOG(DEBUG, \"mock_send: OK\");\n    return AVS_OK;\n}\n\nstatic avs_error_t empty_string_getter(avs_net_socket_t *socket,\n                                       char *out_buffer,\n                                       size_t out_buffer_size) {\n    (void) socket;\n    (void) out_buffer_size;\n\n    assert(out_buffer_size > 0);\n    *out_buffer = '\\0';\n    return AVS_OK;\n}\n\nstatic const avs_net_socket_v_table_t MOCK_SOCKET_VTABLE = {\n    .receive = mock_recv,\n    .accept = (avs_net_socket_accept_t) unexpected,\n    .bind = (avs_net_socket_bind_t) unexpected,\n    .cleanup = (avs_net_socket_cleanup_t) unexpected,\n    .close = (avs_net_socket_close_t) unexpected,\n    .connect = (avs_net_socket_connect_t) unexpected,\n    .decorate = (avs_net_socket_decorate_t) unexpected,\n    .get_interface_name = (avs_net_socket_get_interface_t) unexpected,\n    .get_local_port = (avs_net_socket_get_local_port_t) unexpected,\n    .get_opt = get_opt,\n    .get_remote_host = empty_string_getter,\n    .get_remote_port = empty_string_getter,\n    .get_system_socket = unexpected_system_socket,\n    .receive_from = (avs_net_socket_receive_from_t) unexpected,\n    .send = mock_send,\n    .send_to = (avs_net_socket_send_to_t) unexpected,\n    .set_opt = (avs_net_socket_set_opt_t) unexpected,\n    .shutdown = (avs_net_socket_shutdown_t) unexpected,\n};\n\nconst avs_net_socket_v_table_t *g_vtable_ptr = &MOCK_SOCKET_VTABLE;\navs_net_socket_t *const g_mocksock = (avs_net_socket_t *) &g_vtable_ptr;\n\nstatic void do_stuff(avs_coap_ctx_t *ctx);\n\nstatic int payload_writer(size_t payload_offset,\n                          void *payload_buf,\n                          size_t payload_buf_size,\n                          size_t *out_payload_chunk_size,\n                          void *arg) {\n    (void) payload_offset;\n\n    do_stuff((avs_coap_ctx_t *) arg);\n\n    if (read_flag()) {\n        LOG(DEBUG, \"payload_writer: fail\");\n        return -1;\n    }\n\n    uint16_t payload_bytes;\n    if (fread(&payload_bytes, sizeof(payload_bytes), 1, stdin) != 1) {\n        payload_bytes = 0;\n    }\n    *out_payload_chunk_size = AVS_MIN(payload_bytes, payload_buf_size);\n    memset(payload_buf, 0, *out_payload_chunk_size);\n\n    LOG(DEBUG, \"payload_writer read %\" PRIu64 \" / %\" PRIu64,\n        (uint64_t) *out_payload_chunk_size, (uint64_t) payload_buf_size);\n    dump_buffer(\"payload_writer\", payload_buf, *out_payload_chunk_size);\n    return 0;\n}\n\nstatic void response_handler(avs_coap_ctx_t *ctx,\n                             avs_coap_exchange_id_t exchange_id,\n                             avs_coap_client_request_state_t state,\n                             const avs_coap_client_async_response_t *response,\n                             avs_error_t err,\n                             void *arg) {\n    (void) exchange_id;\n    (void) state;\n    (void) response;\n    (void) err;\n    (void) arg;\n\n    LOG(DEBUG, \"response_handler\");\n    if (response) {\n        dump_buffer(\"response payload\", response->payload,\n                    response->payload_size);\n    }\n\n    do_stuff(ctx);\n}\n\nstatic int handle_request(avs_coap_request_ctx_t *ctx,\n                          avs_coap_exchange_id_t request_id,\n                          avs_coap_server_request_state_t state,\n                          const avs_coap_server_async_request_t *request,\n                          const avs_coap_observe_id_t *observe_id,\n                          void *arg) {\n    avs_coap_ctx_t *coap_ctx = (avs_coap_ctx_t *) arg;\n\n    (void) request_id;\n    (void) state;\n    (void) request;\n    (void) observe_id;\n\n    do_stuff(coap_ctx);\n\n    if (read_flag()) {\n        int result = 0;\n        if (fread(&result, sizeof(result), 1, stdin) != 1) {\n            LOG(DEBUG, \"handle_request: EOF\");\n            return 0;\n        }\n        LOG(DEBUG, \"handle_request: early return, result = %d\", result);\n        return result;\n    }\n\n    avs_coap_response_header_t response;\n    if (fread(&response.code, sizeof(response.code), 1, stdin) != 1) {\n        LOG(DEBUG, \"handle_request: EOF\");\n        return 0;\n    }\n    LOG(DEBUG, \"handle_request: response code = %u\", response.code);\n    response.options = read_options();\n\n    avs_error_t err = AVS_OK;\n    if (ctx) {\n        err = avs_coap_server_setup_async_response(\n                ctx, &response, read_flag() ? payload_writer : NULL, coap_ctx);\n        LOG(DEBUG, \"handle_request: avs_coap_server_setup_async_response: %s\",\n            AVS_COAP_STRERROR(err));\n    }\n\n    do_stuff(coap_ctx);\n\n    int result = 0;\n    if (fread(&result, sizeof(result), 1, stdin) != 1) {\n        LOG(DEBUG, \"handle_request: EOF\");\n        return 0;\n    }\n    LOG(DEBUG, \"handle_request: result = %d\", result);\n    return result;\n}\n\nstatic int handle_new_request(avs_coap_server_ctx_t *ctx,\n                              const avs_coap_request_header_t *request,\n                              void *arg) {\n    avs_coap_ctx_t *coap_ctx = (avs_coap_ctx_t *) arg;\n\n    (void) request;\n\n    do_stuff(coap_ctx);\n\n    if (read_flag()) {\n        int result = 0;\n        if (fread(&result, sizeof(result), 1, stdin) != 1) {\n            LOG(DEBUG, \"handle_new_request: EOF\");\n            return 0;\n        }\n        LOG(DEBUG, \"handle_new_request: early return, result = %d\", result);\n        return result;\n    }\n\n    avs_coap_exchange_id_t id =\n            avs_coap_server_accept_async_request(ctx, handle_request, coap_ctx);\n    LOG(DEBUG, \"handle_new_request: accept ID = %\" PRIu64, id.value);\n\n    do_stuff(coap_ctx);\n\n    int result = 0;\n    if (fread(&result, sizeof(result), 1, stdin) != 1) {\n        LOG(DEBUG, \"handle_new_request: EOF\");\n        return 0;\n    }\n    LOG(DEBUG, \"handle_new_request: result = %d\", result);\n    return result;\n}\n\nstatic void do_stuff_unconditionally(avs_coap_ctx_t *ctx) {\n    enum {\n        FLAG_PASS_ID = (1 << 0),\n        FLAG_PASS_WRITER = (1 << 1),\n        FLAG_PASS_HANDLER = (1 << 2),\n    };\n\n    enum {\n        OP_NOOP,\n        OP_SEND_ASYNC_REQUEST,\n        OP_EXCHANGE_CANCEL,\n        OP_HANDLE_INCOMING_PACKET,\n        OP_SCHED_RUN,\n    };\n\n    uint8_t operation;\n    if (fread(&operation, sizeof(operation), 1, stdin) != 1) {\n        LOG(DEBUG, \"do_stuff: EOF\");\n        return;\n    }\n    LOG(DEBUG, \"do_stuff: %u\", operation);\n\n    switch (operation) {\n    case OP_NOOP:\n        LOG(DEBUG, \"noop\");\n        return;\n    case OP_SEND_ASYNC_REQUEST: {\n        uint8_t flags;\n        if (fread(&flags, sizeof(flags), 1, stdin) != 1) {\n            LOG(DEBUG, \"read flags: EOF\");\n            return;\n        }\n\n        bool pass_id = (flags & FLAG_PASS_ID);\n        bool pass_writer = (flags & FLAG_PASS_WRITER);\n        bool pass_handler = (flags & FLAG_PASS_HANDLER);\n\n        avs_coap_exchange_id_t id;\n        avs_coap_request_header_t req = { 0 };\n        if (fread(&req.code, sizeof(req.code), 1, stdin) != 1) {\n            LOG(DEBUG, \"read details: EOF\");\n            return;\n        }\n\n        req.options = read_options();\n        LOG(DEBUG, \"avs_coap_client_send_async_request\");\n        avs_coap_client_send_async_request(ctx,\n                                           pass_id ? &id : NULL,\n                                           &req,\n                                           pass_writer ? payload_writer : NULL,\n                                           ctx,\n                                           pass_handler ? response_handler\n                                                        : NULL,\n                                           ctx);\n\n        break;\n    }\n    case OP_EXCHANGE_CANCEL: {\n        avs_coap_exchange_id_t id;\n        if (fread(&id, sizeof(id), 1, stdin) != 1) {\n            LOG(DEBUG, \"read ID: EOF\");\n            return;\n        }\n        LOG(DEBUG, \"avs_coap_exchange_cancel %\" PRIu64, id.value);\n        avs_coap_exchange_cancel(ctx, id);\n        break;\n    }\n    case OP_HANDLE_INCOMING_PACKET: {\n        LOG(DEBUG, \"avs_coap_async_handle_incoming_packet\");\n        avs_coap_async_handle_incoming_packet(ctx, handle_new_request, ctx);\n        break;\n    }\n    case OP_SCHED_RUN: {\n        LOG(DEBUG, \"avs_sched_run\");\n        avs_sched_run(g_sched);\n        break;\n    }\n    default:\n        break;\n    }\n}\n\nstatic void do_stuff(avs_coap_ctx_t *ctx) {\n    static const size_t RECURSION_LIMIT = 20;\n    static size_t recursion_depth = 0;\n\n    if (recursion_depth >= RECURSION_LIMIT) {\n        LOG(DEBUG, \"do_stuff: recursion limit reached, returning\");\n        return;\n    }\n\n    ++recursion_depth;\n    do_stuff_unconditionally(ctx);\n    --recursion_depth;\n}\n\nint main() {\n    if (getenv(\"VERBOSE\")) {\n        avs_log_set_default_level(AVS_LOG_TRACE);\n    }\n\n    avs_shared_buffer_t *in_buffer = NULL;\n    avs_shared_buffer_t *out_buffer = NULL;\n    avs_crypto_prng_ctx_t *prng_ctx = NULL;\n    avs_coap_ctx_t *ctx = NULL;\n    avs_coap_udp_response_cache_t *cache = NULL;\n\n    uint16_t in_buf_size;\n    uint16_t out_buf_size;\n    uint16_t cache_size = 0;\n    avs_coap_udp_tx_params_t tx_params = AVS_COAP_DEFAULT_UDP_TX_PARAMS;\n\n    if (!(g_sched = avs_sched_new(\"sched\", NULL))\n            || fread(&in_buf_size, sizeof(in_buf_size), 1, stdin) != 1\n            || fread(&out_buf_size, sizeof(out_buf_size), 1, stdin) != 1\n            || (read_flag()\n                && fread(&tx_params, sizeof(tx_params), 1, stdin) != 1)\n            || (read_flag()\n                && fread(&cache_size, sizeof(cache_size), 1, stdin) != 1)\n            || (read_flag() && fread(&g_mtu, sizeof(g_mtu), 1, stdin) != 1)\n            || !(in_buffer = avs_shared_buffer_new(in_buf_size))\n            || !(out_buffer = avs_shared_buffer_new(out_buf_size))\n            || !(prng_ctx = avs_crypto_prng_new(NULL, NULL))\n            || (cache_size\n                && !(cache = avs_coap_udp_response_cache_create(cache_size)))\n            || !(ctx = avs_coap_udp_ctx_create(g_sched, &tx_params, in_buffer,\n                                               out_buffer, cache, prng_ctx))\n            || avs_is_err(avs_coap_ctx_set_socket(ctx, g_mocksock))) {\n        goto exit;\n    }\n\n    g_mtu %= (1 << 16);\n\n    while (!feof(stdin)) {\n        do_stuff(ctx);\n    }\n\nexit:\n    avs_coap_ctx_cleanup(&ctx);\n    avs_sched_cleanup(&g_sched);\n    avs_free(in_buffer);\n    avs_free(out_buffer);\n    avs_crypto_prng_free(&prng_ctx);\n}\n"
  },
  {
    "path": "deps/avs_coap/tests/fuzz/coap_parse.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#define AVS_COAP_POISON_H // disable libc poisoning\n#include <avs_coap_init.h>\n\n#include <stdio.h>\n\n#include \"udp/avs_coap_udp_msg.h\"\n\nint main() {\n    uint8_t buf[65536];\n    size_t read = fread(buf, 1, sizeof(buf), stdin);\n\n    avs_coap_udp_msg_t msg;\n    return avs_is_err(_avs_coap_udp_msg_parse(&msg, buf, read));\n}\n"
  },
  {
    "path": "deps/avs_coap/tests/fuzz/input/coap_async_api_tcp.hex/recv_get_send_content",
    "content": "0008 # in buf size\n0008 # out buf size\n0008 # opts buf size\n05 # request timeout in seconds\n00 # default mtu\n\n00 # mock_send: don't fail during sending CSM\n\n# CSM\n0200 # mock_recv: msg size\n00 # mock_recv: msg length 0, token length 0\nE1 # mock_recv: code = CSM\n00 # mock_recv: do not override msg token with last sent\n\n03 # handle incoming packet\n0200 # mock_recv: msg size\n00 # mock_recv: msg length 0, token length 0\n01 # mock_recv: code = Get\n00 # mock_recv: do not override msg token with last sent\n\n00 # handle_new_request: do nothing in do_stuff\n00 # handle_new_request: don't return early\n00 # handle_new_request: do nothing in do_stuff\n00000000 # handle_new_request: succeed\n\n00 # handle_request: do nothing in do_stuff\n00 # handle_request: don't return early\n45 # handle_request: 2.05 Content\n0000 # handle_request: empty options\n0000 # handle_request: zero capacity\n00 # handle_request: don't pass payload_writer\n00 # handle_request: do nothing in do_stuff\n00000000 # handle_request: succeed\n00 # mock_send: don't fail\n\n00 # handle_request (cleanup): do nothing in do_stuff\n01 # handle_request (cleanup): return early\n00000000 # handle_request (cleanup): succeed\n\n0000 # mock_recv: msg size\n"
  },
  {
    "path": "deps/avs_coap/tests/fuzz/input/coap_async_api_udp.hex/recv_get_send_content",
    "content": "0008 # in buf size\n0008 # out buf size\n00 # default tx params\n00 # no response cache\n00 # default mtu\n\n03 # handle incoming packet\n0400 # mock_recv: msg size\n40 # mock_recv: CON, empty token\n01 # mock_recv: code = Get\n0000 # mock_recv: msg id\n00 # mock_recv: do not override msd id/token with last sent\n\n00 # handle_new_request: do nothing in do_stuff\n00 # handle_new_request: don't return early\n00 # handle_new_request: do nothing in do_stuff\n00000000 # handle_new_request: succeed\n\n00 # handle_request: do nothing in do_stuff\n00 # handle_request: don't return early\n45 # handle_request: 2.05 Content\n0000 # handle_request: empty options\n0000 # handle_request: zero capacity\n00 # handle_request: don't pass payload_writer\n00 # handle_request: do nothing in do_stuff\n00000000 # handle_request: suceed\n"
  },
  {
    "path": "deps/avs_coap/tests/fuzz/input/coap_async_api_udp.hex/send_block_put_recv_changed",
    "content": "0008 # in buf size = 2 KB\n2200 # out buf size = 34 B\n00 # default tx params\n00 # no response cache\n00 # default mtu\n\n01 # send request\n07 # pass ID, writer, handler\n03 # code: POST\n0000 # options size\n0000 # options capacity\n00 # payload_writer: do nothing in do_stuff\n00 # payload_writer: do not fail\n1100 # payload_writer: payload size = 17\n00 # mock_send: do not fail\n\n03 # handle incoming packet\n0700 # mock_recv: msg size = 7\n60 # mock_recv: ACK, empty token\n5f # mock_recv: code = Continue\n0000 # mock_recv: msg id\nd10e00 # mock_recv: BLOCK1, seq_num 0, size 16, more 1\n01 # mock_recv: override msd id/token with last sent\n\n# request for the next block\n00 # payload_writer: do nothing in do_stuff\n00 # payload_writer: do not fail\n0f00 # payload_writer: payload size = 15\n00 # mock_send: do not fail\n\n03 # handle incoming packet\n0f00 # mock_recv: msg size = 15\n68 # mock_recv: ACK, 8B token\n44 # mock_recv: code = Changed\n0000 # mock_recv: msg id\n0000000000000000 # mock_recv: token\nd10e10 # mock_recv: BLOCK1, seq_num 1, size 16, more 0\n01 # mock_recv: override msd id/token with last sent\n00 # response_handler: do nothing in do_stuff\n"
  },
  {
    "path": "deps/avs_coap/tests/fuzz/input/coap_async_api_udp.hex/send_get_recv_block_content",
    "content": "0008 # in buf size\n0008 # out buf size\n00 # default tx params\n00 # no response cache\n00 # default mtu\n\n01 # send request\n07 # pass ID, writer, handler\n01 # code: GET\n0000 # options size\n0000 # options capacity\n00 # payload_writer: do nothing in do_stuff\n00 # payload_writer: do not fail\n0000 # payload_writer: payload size\n00 # mock_send: do not fail\n\n03 # handle incoming packet\n1800 # mock_recv: msg size = 24\n60 # mock_recv: ACK, empty token\n45 # mock_recv: code = Content\n0000 # mock_recv: msg id\nd10a08 # mock_recv: BLOCK2, seq_num 0, size 16, more 1\nff # payload marker\n31323334353637383960616263646520 # \"123456789abcdef \"\n01 # mock_recv: override msd id/token with last sent\n00 # response_handler: do nothing in do_stuff\n\n# request for the next block\n00 # mock_send: do not fail\n\n03 # handle incoming packet\n2000 # mock_recv: msg size = 32\n68 # mock_recv: ACK, 8B token\n45 # mock_recv: code = Content\n0000 # mock_recv: msg id\n0000000000000000 # mock_recv: token\nd10a10 # mock_recv: BLOCK2, seq_num 1, size 16, more 0\nff # payload marker\n31323334353637383960616263646520 # \"123456789abcdef \"\n01 # mock_recv: override msd id/token with last sent\n00 # response_handler: do nothing in do_stuff\n"
  },
  {
    "path": "deps/avs_coap/tests/fuzz/input/coap_async_api_udp.hex/send_get_recv_content",
    "content": "0008 # in buf size\n0008 # out buf size\n00 # default tx params\n00 # no response cache\n00 # default mtu\n\n01 # send request\n07 # pass ID, writer, handler\n01 # code: GET\n0000 # options size\n0000 # options capacity\n00 # payload_writer: do nothing in do_stuff\n00 # payload_writer: do not fail\n0000 # payload_writer: payload size\n00 # mock_send: do not fail\n\n03 # handle incoming packet\n0400 # mock_recv: msg size\n60 # mock_recv: ACK, empty token\n45 # mock_recv: code = Content\n0000 # mock_recv: msg id\n01 # mock_recv: override msd id/token with last sent\n"
  },
  {
    "path": "deps/avs_coap/tests/fuzz/input/hex-to-fuzz-input.sh",
    "content": "#!/usr/bin/env bash\n\nsed -e 's/#.*$//g' | paste -sd\\  | sed -e 's/[^0-9a-fA-F]//g' | xxd -r -p\n"
  },
  {
    "path": "deps/avs_coap/tests/mock_clock.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#define _GNU_SOURCE // for RTLD_NEXT\n#include <avs_coap_init.h>\n\n#ifdef AVS_UNIT_TESTING\n\n#    include <dlfcn.h>\n#    include <time.h>\n\n#    include <avsystem/commons/avs_list.h>\n#    include <avsystem/commons/avs_unit_test.h>\n\n#    include \"./mock_clock.h\"\n\n#    define MODULE_NAME test\n#    include <avs_coap_x_log_config.h>\n\nstatic avs_time_monotonic_t MOCK_CLOCK = { { 0, -1 } };\n\nvoid _avs_mock_clock_start(const avs_time_monotonic_t t) {\n    AVS_UNIT_ASSERT_FALSE(avs_time_monotonic_valid(MOCK_CLOCK));\n    AVS_UNIT_ASSERT_TRUE(avs_time_monotonic_valid(t));\n    MOCK_CLOCK = t;\n}\n\nvoid _avs_mock_clock_advance(const avs_time_duration_t t) {\n    AVS_UNIT_ASSERT_TRUE(avs_time_monotonic_valid(MOCK_CLOCK));\n    AVS_UNIT_ASSERT_TRUE(avs_time_duration_valid(t));\n    MOCK_CLOCK = avs_time_monotonic_add(MOCK_CLOCK, t);\n}\n\nvoid _avs_mock_clock_finish(void) {\n    AVS_UNIT_ASSERT_TRUE(avs_time_monotonic_valid(MOCK_CLOCK));\n    MOCK_CLOCK = AVS_TIME_MONOTONIC_INVALID;\n}\n\nstatic int (*orig_clock_gettime)(clockid_t, struct timespec *);\n\nAVS_UNIT_GLOBAL_INIT(verbose) {\n    (void) verbose;\n    typedef int (*clock_gettime_t)(clockid_t, struct timespec *);\n    orig_clock_gettime =\n            (clock_gettime_t) (intptr_t) dlsym(RTLD_NEXT, \"clock_gettime\");\n}\n\nint clock_gettime(clockid_t clock, struct timespec *t) {\n    if (avs_time_monotonic_valid(MOCK_CLOCK)) {\n        // all clocks are equivalent for our purposes, so ignore clock\n        t->tv_sec = (time_t) MOCK_CLOCK.since_monotonic_epoch.seconds;\n        t->tv_nsec = MOCK_CLOCK.since_monotonic_epoch.nanoseconds;\n        return 0;\n    } else {\n        return orig_clock_gettime(clock, t);\n    }\n}\n\n#endif // AVS_UNIT_TESTING\n"
  },
  {
    "path": "deps/avs_coap/tests/mock_clock.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef AVS_TEST_MOCK_CLOCK_H\n#define AVS_TEST_MOCK_CLOCK_H\n\n#include <avsystem/commons/avs_time.h>\n\nvoid _avs_mock_clock_start(const avs_time_monotonic_t t);\nvoid _avs_mock_clock_advance(const avs_time_duration_t t);\nvoid _avs_mock_clock_finish(void);\n\n#endif /* AVS_TEST_MOCK_CLOCK_H */\n"
  },
  {
    "path": "deps/avs_coap/tests/options/option.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <avs_coap_init.h>\n\n#ifdef AVS_UNIT_TESTING\n\n#    include \"src/options/avs_coap_option.h\"\n\n#    define AVS_UNIT_ENABLE_SHORT_ASSERTS\n#    include <avsystem/commons/avs_unit_test.h>\n\n#    define MODULE_NAME test\n#    include <avs_coap_x_log_config.h>\n\nAVS_UNIT_TEST(coap_option, sizeof) {\n    uint8_t buffer[512] = \"\";\n    const avs_coap_option_t *opt = (const avs_coap_option_t *) buffer;\n\n    buffer[0] = 0x00;\n    // header byte + extended delta + extended length + value\n    ASSERT_EQ(_avs_coap_option_sizeof(opt), 1 + 0 + 0 + 0);\n\n    buffer[0] = 0xC0;\n    ASSERT_EQ(_avs_coap_option_sizeof(opt), 1 + 0 + 0 + 0);\n\n    buffer[0] = 0xD0;\n    ASSERT_EQ(_avs_coap_option_sizeof(opt), 1 + 1 + 0 + 0);\n\n    buffer[0] = 0xE0;\n    ASSERT_EQ(_avs_coap_option_sizeof(opt), 1 + 2 + 0 + 0);\n\n    buffer[0] = 0x01;\n    ASSERT_EQ(_avs_coap_option_sizeof(opt), 1 + 0 + 0 + 1);\n\n    buffer[0] = 0x0C;\n    ASSERT_EQ(_avs_coap_option_sizeof(opt), 1 + 0 + 0 + 12);\n\n    buffer[0] = 0x0D;\n    ASSERT_EQ(_avs_coap_option_sizeof(opt), 1 + 0 + 1 + 13);\n\n    buffer[0] = 0x0E;\n    ASSERT_EQ(_avs_coap_option_sizeof(opt), 1 + 0 + 2 + 269);\n\n    buffer[0] = 0x11;\n    ASSERT_EQ(_avs_coap_option_sizeof(opt), 1 + 0 + 0 + 1);\n\n    buffer[0] = 0xCC;\n    ASSERT_EQ(_avs_coap_option_sizeof(opt), 1 + 0 + 0 + 12);\n\n    buffer[0] = 0xDD;\n    ASSERT_EQ(_avs_coap_option_sizeof(opt), 1 + 1 + 1 + 13);\n\n    buffer[0] = 0xEE;\n    ASSERT_EQ(_avs_coap_option_sizeof(opt), 1 + 2 + 2 + 269);\n}\n\nAVS_UNIT_TEST(coap_option_serialize, empty) {\n    uint8_t buffer[512] = \"\";\n    const size_t delta = 0;\n\n    size_t written =\n            _avs_coap_option_serialize(buffer, sizeof(buffer), delta, NULL, 0);\n    //   1 - option header\n    static const size_t SIZE = 1;\n    ASSERT_EQ(written, SIZE);\n    ASSERT_EQ_BYTES_SIZED(\"\\x00\", buffer, SIZE);\n}\n\nAVS_UNIT_TEST(coap_option_serialize, ext8_delta) {\n    uint8_t buffer[512] = \"\";\n    const size_t delta = _AVS_COAP_EXT_U8_BASE + 0x12;\n\n    size_t written =\n            _avs_coap_option_serialize(buffer, sizeof(buffer), delta, NULL, 0);\n    //   1 - option header\n    // + 1 - extended length\n    static const size_t SIZE = 2;\n    ASSERT_EQ(written, SIZE);\n    ASSERT_EQ_BYTES_SIZED(\"\\xd0\\x12payload\", buffer, SIZE);\n}\n\nAVS_UNIT_TEST(coap_option_serialize, ext16_delta) {\n    uint8_t buffer[512] = \"\";\n    const size_t delta = _AVS_COAP_EXT_U16_BASE + 0x1234;\n\n    size_t written =\n            _avs_coap_option_serialize(buffer, sizeof(buffer), delta, NULL, 0);\n    //   1 - option header\n    // + 2 - extended length\n    static const size_t SIZE = 3;\n    ASSERT_EQ(written, SIZE);\n    ASSERT_EQ_BYTES_SIZED(\"\\xe0\\x12\\x34payload\", buffer, SIZE);\n}\n\nAVS_UNIT_TEST(coap_option_serialize, ext8_size) {\n    uint8_t buffer[65536] = \"\";\n    uint8_t data[_AVS_COAP_EXT_U8_BASE + 0x12];\n    memset(data, 'A', sizeof(data));\n\n    const size_t delta = 0;\n    const size_t length = _AVS_COAP_EXT_U8_BASE + 0x12;\n    size_t written = _avs_coap_option_serialize(buffer, sizeof(buffer), delta,\n                                                data, length);\n    //   1 - option header\n    // + 1 - extended length\n    static const size_t HDR_SIZE = 2;\n\n    ASSERT_EQ(written, HDR_SIZE + length);\n    ASSERT_EQ_BYTES_SIZED(\"\\x0d\\x12\", buffer, HDR_SIZE);\n    ASSERT_EQ_BYTES_SIZED(data, buffer + HDR_SIZE, length);\n}\n\nAVS_UNIT_TEST(coap_option_serialize, ext16_size) {\n    uint8_t buffer[65536] = \"\";\n    uint8_t data[_AVS_COAP_EXT_U16_BASE + 0x1234];\n    memset(data, 'A', sizeof(data));\n\n    const size_t delta = 0;\n    const size_t length = _AVS_COAP_EXT_U16_BASE + 0x1234;\n    size_t written = _avs_coap_option_serialize(buffer, sizeof(buffer), delta,\n                                                data, length);\n    //   1 - option header\n    // + 2 - extended length\n    static const size_t HDR_SIZE = 3;\n\n    ASSERT_EQ(written, HDR_SIZE + length);\n    ASSERT_EQ_BYTES_SIZED(\"\\x0e\\x12\\x34\", buffer, HDR_SIZE);\n    ASSERT_EQ_BYTES_SIZED(data, buffer + HDR_SIZE, length);\n}\n\n#endif // AVS_UNIT_TESTING\n"
  },
  {
    "path": "deps/avs_coap/tests/options/options.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <avs_coap_init.h>\n\n#ifdef AVS_UNIT_TESTING\n\n#    include <stdlib.h>\n\n#    include <avsystem/coap/code.h>\n#    include <avsystem/coap/option.h>\n\n#    include \"options/avs_coap_iterator.h\"\n\n#    define AVS_UNIT_ENABLE_SHORT_ASSERTS\n#    include <avsystem/commons/avs_unit_test.h>\n\n#    define MODULE_NAME test\n#    include <avs_coap_x_log_config.h>\n\n#    include \"options/avs_coap_options.h\"\n\nAVS_UNIT_TEST(coap_options, erase_all_from_front) {\n    uint8_t OPTS[] = \"\\x00\" // delta = 0, empty\n                     \"\\x23\"\n                     \"foo\"       // delta = 2, 3b payload\n                     \"\\x10\"      // delta = 1, empty\n                     \"\\x11\\xDD\"; // delta = 1, 1b payload\n\n    avs_coap_options_t opts = {\n        .begin = OPTS,\n        .size = sizeof(OPTS) - 1,\n        .capacity = sizeof(OPTS) - 1\n    };\n\n    avs_coap_option_iterator_t optit = _avs_coap_optit_begin(&opts);\n\n    ASSERT_FALSE(_avs_coap_optit_end(&optit));\n    ASSERT_TRUE(&optit == _avs_coap_optit_erase(&optit));\n\n    ASSERT_EQ(opts.size, 7);\n    ASSERT_EQ_BYTES(OPTS,\n                    \"\\x23\"\n                    \"foo\"\n                    \"\\x10\\x11\\xDD\");\n\n    ASSERT_FALSE(_avs_coap_optit_end(&optit));\n    ASSERT_TRUE(&optit == _avs_coap_optit_erase(&optit));\n\n    ASSERT_EQ(opts.size, 3);\n    ASSERT_EQ_BYTES(OPTS, \"\\x30\\x11\\xDD\");\n\n    ASSERT_FALSE(_avs_coap_optit_end(&optit));\n    ASSERT_TRUE(&optit == _avs_coap_optit_erase(&optit));\n\n    ASSERT_EQ(opts.size, 2);\n    ASSERT_EQ_BYTES(OPTS, \"\\x41\\xDD\");\n\n    ASSERT_FALSE(_avs_coap_optit_end(&optit));\n    ASSERT_TRUE(&optit == _avs_coap_optit_erase(&optit));\n\n    ASSERT_EQ(opts.size, 0);\n\n    ASSERT_TRUE(_avs_coap_optit_end(&optit));\n}\n\nstatic void optit_advance(avs_coap_option_iterator_t *optit, size_t n) {\n    for (size_t i = 0; i < n; ++i) {\n        ASSERT_FALSE(_avs_coap_optit_end(optit));\n        ASSERT_TRUE(optit == _avs_coap_optit_next(optit));\n    }\n}\n\nstatic void erase_nth_option(avs_coap_options_t *opts, size_t n) {\n    avs_coap_option_iterator_t optit = _avs_coap_optit_begin(opts);\n    optit_advance(&optit, n);\n    ASSERT_FALSE(_avs_coap_optit_end(&optit));\n    ASSERT_TRUE(&optit == _avs_coap_optit_erase(&optit));\n}\n\nAVS_UNIT_TEST(coap_options, erase_all_from_back) {\n    uint8_t OPTS[] = \"\\x00\" // delta = 0, empty\n                     \"\\x23\"\n                     \"foo\"       // delta = 2, 3b payload\n                     \"\\x10\"      // delta = 1, empty\n                     \"\\x11\\xDD\"; // delta = 1, 1b payload\n\n    avs_coap_options_t opts = {\n        .begin = OPTS,\n        .size = sizeof(OPTS) - 1,\n        .capacity = sizeof(OPTS) - 1\n    };\n\n    erase_nth_option(&opts, 3);\n    ASSERT_EQ(opts.size, 6);\n    ASSERT_EQ_BYTES(OPTS,\n                    \"\\x00\\x23\"\n                    \"foo\"\n                    \"\\x10\");\n\n    erase_nth_option(&opts, 2);\n    ASSERT_EQ(opts.size, 5);\n    ASSERT_EQ_BYTES(OPTS,\n                    \"\\x00\\x23\"\n                    \"foo\");\n\n    erase_nth_option(&opts, 1);\n    ASSERT_EQ(opts.size, 1);\n    ASSERT_EQ_BYTES(OPTS, \"\\x00\");\n\n    erase_nth_option(&opts, 0);\n    ASSERT_EQ(opts.size, 0);\n}\n\nAVS_UNIT_TEST(coap_options, erase_with_header_expansion) {\n    uint8_t OPTS[] = \"\\xC0\"                  // delta = 12, empty\n                     \"\\x14\\xAA\\xBB\\xCC\\xDD\"; // delta = 1, \"\\xAA\\xBB\\xCC\\xDD\"\n\n    avs_coap_options_t opts = {\n        .begin = OPTS,\n        .size = sizeof(OPTS) - 1,\n        .capacity = sizeof(OPTS) - 1\n    };\n\n    avs_coap_option_iterator_t optit = _avs_coap_optit_begin(&opts);\n\n    ASSERT_FALSE(_avs_coap_optit_end(&optit));\n    ASSERT_TRUE(&optit == _avs_coap_optit_erase(&optit));\n\n    ASSERT_EQ(opts.size, 6);\n    ASSERT_EQ_BYTES_SIZED(OPTS, \"\\xD4\\x00\\xAA\\xBB\\xCC\\xDD\", 6);\n\n    ASSERT_TRUE(&optit == _avs_coap_optit_next(&optit));\n    ASSERT_TRUE(_avs_coap_optit_end(&optit));\n}\n\nAVS_UNIT_TEST(coap_options, insert_not_enough_space) {\n    avs_coap_options_t opts = avs_coap_options_create_empty(NULL, 0);\n    ASSERT_FAIL(avs_coap_options_add_empty(&opts, 0));\n\n    uint8_t buffer[128] = \"\"; // buffer full of empty options 0\n    opts = (avs_coap_options_t) {\n        .begin = buffer,\n        .size = sizeof(buffer),\n        .capacity = sizeof(buffer)\n    };\n    ASSERT_FAIL(avs_coap_options_add_empty(&opts, 0));\n\n    opts = (avs_coap_options_t) {\n        .begin = buffer,\n        .size = sizeof(buffer) - 1,\n        .capacity = sizeof(buffer)\n    };\n    ASSERT_FAIL(avs_coap_options_add_opaque(&opts, 0, \"A\", 1));\n}\n\nstatic void deref_free(void **p) {\n    free(*p);\n}\n\nAVS_UNIT_TEST(coap_options, insert_last) {\n    static const size_t buffer_size = 512;\n    void *buffer __attribute__((__cleanup__(deref_free))) = malloc(buffer_size);\n    avs_coap_options_t opts =\n            avs_coap_options_create_empty(buffer, buffer_size);\n\n#    ifdef WITH_AVS_COAP_BLOCK\n    const avs_coap_option_block_t block = {\n        .type = AVS_COAP_BLOCK1,\n        .seq_num = 0x1234,\n        .has_more = true,\n        .size = 1024\n    };\n#    endif // WITH_AVS_COAP_BLOCK\n\n    ASSERT_OK(avs_coap_options_add_opaque(&opts, 0, \"0\", 1));      // num  0\n    ASSERT_OK(avs_coap_options_add_string(&opts, 1, \"1\"));         // num  1\n    ASSERT_OK(avs_coap_options_add_empty(&opts, 2));               // num  2\n    ASSERT_OK(avs_coap_options_add_u16(&opts, 3, 0x1234));         // num  3\n    ASSERT_OK(avs_coap_options_add_u32(&opts, 4, 0x12345678));     // num  4\n    ASSERT_OK(avs_coap_options_set_content_format(&opts, 0x4444)); // num 12\n#    ifdef WITH_AVS_COAP_BLOCK\n    ASSERT_OK(avs_coap_options_add_block(&opts, &block)); // num 27\n#    endif                                                // WITH_AVS_COAP_BLOCK\n\n    const uint8_t EXPECTED[] =\n            \"\\x01\\x30\"             // num  0 (+0), \"0\"\n            \"\\x11\\x31\"             // num  1 (+1), \"1\"\n            \"\\x10\"                 // num  2 (+1), empty\n            \"\\x12\\x12\\x34\"         // num  3 (+1), 0x1234\n            \"\\x14\\x12\\x34\\x56\\x78\" // num  4 (+1), 0x12345678\n            \"\\x82\\x44\\x44\"         // num 12 (+8), 0x4444\n#    ifdef WITH_AVS_COAP_BLOCK\n            \"\\xd3\\x02\\x01\\x23\\x4e\" // num 27 (+15), ext size (0x02),\n                                   // BLOCK1(0x1234, more=1 (0x08) | size=1024\n                                   // (0x06))\n#    endif                         // WITH_AVS_COAP_BLOCK\n            ;\n\n    ASSERT_EQ_BYTES_SIZED(buffer, EXPECTED, sizeof(EXPECTED) - 1);\n}\n\nAVS_UNIT_TEST(coap_options, insert_first) {\n    static const size_t buffer_size = 512;\n    void *buffer __attribute__((__cleanup__(deref_free))) = malloc(buffer_size);\n    avs_coap_options_t opts =\n            avs_coap_options_create_empty(buffer, buffer_size);\n\n#    ifdef WITH_AVS_COAP_BLOCK\n    const avs_coap_option_block_t block = {\n        .type = AVS_COAP_BLOCK1,\n        .seq_num = 0x1234,\n        .has_more = true,\n        .size = 1024\n    };\n\n    ASSERT_OK(avs_coap_options_add_block(&opts, &block)); // num 27\n#    endif                                                // WITH_AVS_COAP_BLOCK\n    ASSERT_OK(avs_coap_options_set_content_format(&opts, 0x4444)); // num 12\n    ASSERT_OK(avs_coap_options_add_u32(&opts, 4, 0x12345678));     // num  4\n    ASSERT_OK(avs_coap_options_add_u16(&opts, 3, 0x1234));         // num  3\n    ASSERT_OK(avs_coap_options_add_empty(&opts, 2));               // num  2\n    ASSERT_OK(avs_coap_options_add_string(&opts, 1, \"1\"));         // num  1\n    ASSERT_OK(avs_coap_options_add_opaque(&opts, 0, \"0\", 1));      // num  0\n\n    const uint8_t EXPECTED[] =\n            \"\\x01\\x30\"             // num  0 (+0), \"0\"\n            \"\\x11\\x31\"             // num  1 (+1), \"1\"\n            \"\\x10\"                 // num  2 (+1), empty\n            \"\\x12\\x12\\x34\"         // num  3 (+1), 0x1234\n            \"\\x14\\x12\\x34\\x56\\x78\" // num  4 (+1), 0x12345678\n            \"\\x82\\x44\\x44\"         // num 12 (+8), 0x4444\n#    ifdef WITH_AVS_COAP_BLOCK\n            \"\\xd3\\x02\\x01\\x23\\x4e\" // num 27 (+15), ext size (0x02),\n                                   // BLOCK1(0x1234, more=1 (0x08) | size=1024\n                                   // (0x06))\n#    endif                         // WITH_AVS_COAP_BLOCK\n            ;\n\n    ASSERT_EQ_BYTES_SIZED(buffer, EXPECTED, sizeof(EXPECTED) - 1);\n}\n\nAVS_UNIT_TEST(coap_options, insert_middle) {\n    static const size_t buffer_size = 512;\n    void *buffer __attribute__((__cleanup__(deref_free))) = malloc(buffer_size);\n    avs_coap_options_t opts =\n            avs_coap_options_create_empty(buffer, buffer_size);\n\n#    ifdef WITH_AVS_COAP_BLOCK\n    const avs_coap_option_block_t block = {\n        .type = AVS_COAP_BLOCK1,\n        .seq_num = 0x1234,\n        .has_more = true,\n        .size = 1024\n    };\n#    endif // WITH_AVS_COAP_BLOCK\n\n    ASSERT_OK(avs_coap_options_add_opaque(&opts, 0, \"0\", 1)); // num  0\n#    ifdef WITH_AVS_COAP_BLOCK\n    ASSERT_OK(avs_coap_options_add_block(&opts, &block)); // num 27\n#    endif                                                // WITH_AVS_COAP_BLOCK\n    ASSERT_OK(avs_coap_options_add_string(&opts, 1, \"1\"));         // num  1\n    ASSERT_OK(avs_coap_options_set_content_format(&opts, 0x4444)); // num 12\n    ASSERT_OK(avs_coap_options_add_empty(&opts, 2));               // num  2\n    ASSERT_OK(avs_coap_options_add_u32(&opts, 4, 0x12345678));     // num  4\n    ASSERT_OK(avs_coap_options_add_u16(&opts, 3, 0x1234));         // num  3\n\n    const uint8_t EXPECTED[] =\n            \"\\x01\\x30\"             // num  0 (+0), \"0\"\n            \"\\x11\\x31\"             // num  1 (+1), \"1\"\n            \"\\x10\"                 // num  2 (+1), empty\n            \"\\x12\\x12\\x34\"         // num  3 (+1), 0x1234\n            \"\\x14\\x12\\x34\\x56\\x78\" // num  4 (+1), 0x12345678\n            \"\\x82\\x44\\x44\"         // num 12 (+8), 0x4444\n#    ifdef WITH_AVS_COAP_BLOCK\n            \"\\xd3\\x02\\x01\\x23\\x4e\" // num 27 (+15), ext size (0x02),\n                                   // BLOCK1(0x1234, more=1 (0x08) | size=1024\n                                   // (0x06))\n#    endif                         // WITH_AVS_COAP_BLOCK\n            ;\n\n    ASSERT_EQ_BYTES_SIZED(buffer, EXPECTED, sizeof(EXPECTED) - 1);\n}\n\nAVS_UNIT_TEST(coap_options, insert_with_header_shortening) {\n    uint8_t OPTS[] =\n            \"\\xd4\\x00\\xAA\\xBB\\xCC\\xDD\" // delta = 13, payload \"\\xAA\\xBB\\xCC\\xDD\"\n            \"\\x02\\x11\\x22\";            // delta = 0, payload \"\\x11\\x22\"\n    avs_coap_options_t opts = {\n        .begin = OPTS,\n        .size = sizeof(OPTS) - 1,\n        .capacity = sizeof(OPTS) - 1\n    };\n\n    // make sure we only have two options\n    avs_coap_option_iterator_t optit = _avs_coap_optit_begin(&opts);\n    ASSERT_FALSE(_avs_coap_optit_end(&optit));\n    ASSERT_EQ(_avs_coap_optit_number(&optit), 13);\n    ASSERT_EQ(_avs_coap_option_delta(_avs_coap_optit_current(&optit)), 13);\n    ASSERT_EQ(_avs_coap_option_content_length(_avs_coap_optit_current(&optit)),\n              4);\n    ASSERT_EQ_BYTES_SIZED(_avs_coap_option_value(\n                                  _avs_coap_optit_current(&optit)),\n                          \"\\xAA\\xBB\\xCC\\xDD\", 4);\n\n    ASSERT_TRUE(_avs_coap_optit_next(&optit) == &optit);\n    ASSERT_FALSE(_avs_coap_optit_end(&optit));\n    ASSERT_EQ(_avs_coap_optit_number(&optit), 13);\n    ASSERT_EQ(_avs_coap_option_delta(_avs_coap_optit_current(&optit)), 0);\n    ASSERT_EQ(_avs_coap_option_content_length(_avs_coap_optit_current(&optit)),\n              2);\n    ASSERT_EQ_BYTES_SIZED(_avs_coap_option_value(\n                                  _avs_coap_optit_current(&optit)),\n                          \"\\x11\\x22\", 2);\n\n    ASSERT_TRUE(_avs_coap_optit_next(&optit) == &optit);\n    ASSERT_TRUE(_avs_coap_optit_end(&optit));\n\n    // at this point, the buffer is full, but inserting an option with number\n    // in [1; 12] range and no payload will shorten the header of existing\n    // option to make enough room for insertion\n\n    ASSERT_FAIL(avs_coap_options_add_empty(&opts, 0));\n    ASSERT_FAIL(avs_coap_options_add_empty(&opts, 13));\n\n    ASSERT_OK(avs_coap_options_add_empty(&opts, 1));\n\n    // make sure the option was successfully inserted\n    optit = _avs_coap_optit_begin(&opts);\n    ASSERT_FALSE(_avs_coap_optit_end(&optit));\n\n    ASSERT_EQ(_avs_coap_optit_number(&optit), 1);\n    ASSERT_EQ(_avs_coap_option_delta(_avs_coap_optit_current(&optit)), 1);\n    ASSERT_EQ(_avs_coap_option_content_length(_avs_coap_optit_current(&optit)),\n              0);\n\n    ASSERT_TRUE(_avs_coap_optit_next(&optit) == &optit);\n    ASSERT_FALSE(_avs_coap_optit_end(&optit));\n\n    ASSERT_EQ(_avs_coap_optit_number(&optit), 13);\n    ASSERT_EQ(_avs_coap_option_delta(optit.curr_opt), 12);\n    ASSERT_EQ(_avs_coap_option_content_length(optit.curr_opt), 4);\n    ASSERT_EQ_BYTES_SIZED(_avs_coap_option_value(optit.curr_opt),\n                          \"\\xAA\\xBB\\xCC\\xDD\", 4);\n\n    ASSERT_TRUE(_avs_coap_optit_next(&optit) == &optit);\n    ASSERT_FALSE(_avs_coap_optit_end(&optit));\n\n    ASSERT_EQ(_avs_coap_optit_number(&optit), 13);\n    ASSERT_EQ(_avs_coap_option_delta(optit.curr_opt), 0);\n    ASSERT_EQ(_avs_coap_option_content_length(optit.curr_opt), 2);\n    ASSERT_EQ_BYTES_SIZED(_avs_coap_option_value(optit.curr_opt), \"\\x11\\x22\",\n                          2);\n\n    ASSERT_TRUE(_avs_coap_optit_next(&optit) == &optit);\n    ASSERT_TRUE(_avs_coap_optit_end(&optit));\n}\n\nAVS_UNIT_TEST(coap_options, set_content_format) {\n    static const size_t buffer_size = 512;\n    void *buffer __attribute__((__cleanup__(deref_free))) = malloc(buffer_size);\n    avs_coap_options_t opts =\n            avs_coap_options_create_empty(buffer, buffer_size);\n\n    ASSERT_OK(avs_coap_options_set_content_format(&opts, 0));\n    ASSERT_EQ(opts.size, 1);\n    ASSERT_EQ_BYTES(opts.begin, \"\\xC0\");\n\n    // overwrite with longer\n    ASSERT_OK(avs_coap_options_set_content_format(&opts, 10));\n    ASSERT_EQ(opts.size, 2);\n    ASSERT_EQ_BYTES(opts.begin, \"\\xC1\\x0A\");\n\n    // overwrite with same length\n    ASSERT_OK(avs_coap_options_set_content_format(&opts, 0xDD));\n    ASSERT_EQ(opts.size, 2);\n    ASSERT_EQ_BYTES(opts.begin, \"\\xC1\\xDD\");\n\n    // remove option\n    ASSERT_OK(avs_coap_options_set_content_format(&opts, AVS_COAP_FORMAT_NONE));\n    ASSERT_EQ(opts.size, 0);\n\n    // set to long value\n    ASSERT_OK(avs_coap_options_set_content_format(&opts, 0xC000));\n    ASSERT_EQ(opts.size, 3);\n    ASSERT_EQ_BYTES(opts.begin, \"\\xC2\\xC0\\x00\");\n\n    // overwrite with shorter\n    ASSERT_OK(avs_coap_options_set_content_format(&opts, 3));\n    ASSERT_EQ(opts.size, 2);\n    ASSERT_EQ_BYTES(opts.begin, \"\\xC1\\x03\");\n}\n\nAVS_UNIT_TEST(coap_options, iterate) {\n#    pragma GCC diagnostic push\n#    pragma GCC diagnostic ignored \"-Wpedantic\"\n    const uint8_t CONTENT[] = {\n        // clang-format off\n        [0]  = 0x00,                                                  // empty option\n        [1]  = 0x10,                                                  // delta = 1\n        [2]  = 0xD0, [3]         = 0x00,                              // extended delta (1b)\n        [4]  = 0xE0, [5 ... 6]   = 0x00,                              // extended delta (2b)\n        [7]  = 0x01, [8]         = 0x00,                              // length = 1\n        [9]  = 0x0D, [10]        = 0x00, [11 ... 11+13-1]     = 0x00, // extended length (1b)\n        [24] = 0x0E, [25 ... 26] = 0x00, [27 ... 27+13+256-1] = 0x00  // extended length (2b)\n        // clang-format on\n    };\n#    pragma GCC diagnostic pop\n\n    // TODO: ugly const_cast\n    avs_coap_options_t opts = {\n        .begin = (void *) (intptr_t) CONTENT,\n        .size = sizeof(CONTENT),\n        .capacity = sizeof(CONTENT)\n    };\n\n    avs_coap_option_iterator_t it = _avs_coap_optit_begin(&opts);\n    size_t expected_opt_number = 0;\n    const uint8_t *expected_opt_ptr = CONTENT;\n\n    ASSERT_FALSE(_avs_coap_optit_end(&it));\n    ASSERT_EQ(_avs_coap_optit_number(&it), expected_opt_number);\n    ASSERT_TRUE((const uint8_t *) _avs_coap_optit_current(&it)\n                == expected_opt_ptr);\n    expected_opt_ptr += 1;\n\n    expected_opt_number += 1;\n    ASSERT_TRUE(_avs_coap_optit_next(&it) == &it);\n    ASSERT_FALSE(_avs_coap_optit_end(&it));\n    ASSERT_EQ(_avs_coap_optit_number(&it), expected_opt_number);\n    ASSERT_TRUE((const uint8_t *) _avs_coap_optit_current(&it)\n                == expected_opt_ptr);\n    expected_opt_ptr += 1;\n\n    expected_opt_number += 13;\n    ASSERT_TRUE(_avs_coap_optit_next(&it) == &it);\n    ASSERT_FALSE(_avs_coap_optit_end(&it));\n    ASSERT_EQ(_avs_coap_optit_number(&it), expected_opt_number);\n    ASSERT_TRUE((const uint8_t *) _avs_coap_optit_current(&it)\n                == expected_opt_ptr);\n    expected_opt_ptr += 2;\n\n    expected_opt_number += 13 + 256;\n    ASSERT_TRUE(_avs_coap_optit_next(&it) == &it);\n    ASSERT_FALSE(_avs_coap_optit_end(&it));\n    ASSERT_EQ(_avs_coap_optit_number(&it), expected_opt_number);\n    ASSERT_TRUE((const uint8_t *) _avs_coap_optit_current(&it)\n                == expected_opt_ptr);\n    expected_opt_ptr += 3;\n\n    ASSERT_TRUE(_avs_coap_optit_next(&it) == &it);\n    ASSERT_FALSE(_avs_coap_optit_end(&it));\n    ASSERT_EQ(_avs_coap_optit_number(&it), expected_opt_number);\n    ASSERT_TRUE((const uint8_t *) _avs_coap_optit_current(&it)\n                == expected_opt_ptr);\n    expected_opt_ptr += 1 + 1;\n\n    ASSERT_TRUE(_avs_coap_optit_next(&it) == &it);\n    ASSERT_FALSE(_avs_coap_optit_end(&it));\n    ASSERT_EQ(_avs_coap_optit_number(&it), expected_opt_number);\n    ASSERT_TRUE((const uint8_t *) _avs_coap_optit_current(&it)\n                == expected_opt_ptr);\n    expected_opt_ptr += 1 + 1 + 13;\n\n    ASSERT_TRUE(_avs_coap_optit_next(&it) == &it);\n    ASSERT_FALSE(_avs_coap_optit_end(&it));\n    ASSERT_EQ(_avs_coap_optit_number(&it), expected_opt_number);\n    ASSERT_TRUE((const uint8_t *) _avs_coap_optit_current(&it)\n                == expected_opt_ptr);\n    expected_opt_ptr += 1 + 2 + 13 + 256;\n\n    ASSERT_TRUE(_avs_coap_optit_next(&it) == &it);\n    ASSERT_TRUE(_avs_coap_optit_end(&it));\n}\n\nAVS_UNIT_TEST(coap_options, block_too_long) {\n    const uint8_t CONTENT[] = \"\\xd4\\x0a\"          // num: 23 (13 + 10), size: 4\n                              \"\\x00\\x00\\x00\\x00\"; // BLOCK2 option\n\n    avs_coap_options_t opts = {\n        .begin = (void *) (intptr_t) CONTENT,\n        .size = sizeof(CONTENT) - 1,\n        .capacity = sizeof(CONTENT) - 1\n    };\n\n    ASSERT_FALSE(_avs_coap_options_valid(&opts));\n}\n\nAVS_UNIT_TEST(coap_options, fuzz_heap_overflow) {\n    const uint8_t CONTENT[] = \"\\x74\\xff\\xff\\x7f\\xff\"\n                              \"\\x31\\x32\"\n                              \"\\x60\"\n                              \"\\x45\\x00\\x05\\x0b\\x00\\x00\"\n                              \"\\x32\\x00\\x19\"\n                              \"\\x31\\x5c\";\n\n    static const size_t buffer_size = sizeof(CONTENT) + 12;\n    void *buffer __attribute__((__cleanup__(deref_free))) =\n            calloc(1, buffer_size);\n    memcpy(buffer, CONTENT, sizeof(CONTENT) - 1);\n\n    avs_coap_options_t opts = {\n        .begin = buffer,\n        .size = sizeof(CONTENT) - 1,\n        .capacity = buffer_size\n    };\n\n    avs_coap_options_remove_by_number(&opts, AVS_COAP_OPTION_BLOCK2);\n}\n\n#    ifdef WITH_AVS_COAP_BLOCK\nstatic avs_coap_options_t init_options(void *buf, size_t buf_size, ...) {\n    va_list list;\n    va_start(list, buf_size);\n\n    avs_coap_options_t opts = avs_coap_options_create_empty(buf, buf_size);\n    while (true) {\n        // uint16_t is promoted to int when passed to ...\n        uint16_t opt_num = (uint16_t) va_arg(list, int);\n\n        // reserved option number; this function uses it as end marker\n        if (opt_num == 0) {\n            break;\n        }\n\n        switch (opt_num) {\n        case AVS_COAP_OPTION_URI_HOST:\n        case AVS_COAP_OPTION_LOCATION_PATH:\n        case AVS_COAP_OPTION_URI_PATH:\n        case AVS_COAP_OPTION_URI_QUERY:\n        case AVS_COAP_OPTION_LOCATION_QUERY:\n        case AVS_COAP_OPTION_PROXY_URI:\n        case AVS_COAP_OPTION_PROXY_SCHEME: {\n            const char *value = va_arg(list, const char *);\n            ASSERT_OK(avs_coap_options_add_string(&opts, opt_num, value));\n            break;\n        }\n\n        case AVS_COAP_OPTION_IF_MATCH:\n        case AVS_COAP_OPTION_ETAG:\n        case AVS_COAP_OPTION_IF_NONE_MATCH: {\n            const void *opt_buf = va_arg(list, const void *);\n            size_t opt_buf_size = va_arg(list, size_t);\n            // sanity check in case we read garbage because int was passed\n            // instead of size_t\n            ASSERT_TRUE(opt_buf_size < 8);\n            ASSERT_OK(avs_coap_options_add_opaque(&opts, opt_num, opt_buf,\n                                                  (uint16_t) opt_buf_size));\n            break;\n        }\n\n#        ifdef WITH_AVS_COAP_OBSERVE\n        case AVS_COAP_OPTION_OBSERVE: {\n            // shorter values promote to int when passed to ...\n            int value = va_arg(list, int);\n            ASSERT_OK(avs_coap_options_add_observe(&opts, (uint32_t) value));\n            break;\n        }\n#        endif // WITH_AVS_COAP_OBSERVE\n\n        case AVS_COAP_OPTION_URI_PORT:\n        case AVS_COAP_OPTION_CONTENT_FORMAT:\n        case AVS_COAP_OPTION_MAX_AGE:\n        case AVS_COAP_OPTION_ACCEPT:\n        case AVS_COAP_OPTION_SIZE1: {\n            // shorter values promote to int when passed to ...\n            int value = va_arg(list, int);\n            ASSERT_OK(\n                    avs_coap_options_add_u32(&opts, opt_num, (uint32_t) value));\n            break;\n        }\n\n        case AVS_COAP_OPTION_BLOCK2:\n        case AVS_COAP_OPTION_BLOCK1: {\n            const avs_coap_option_block_t *block =\n                    va_arg(list, const avs_coap_option_block_t *);\n            ASSERT_OK(avs_coap_options_add_block(&opts, block));\n            break;\n        }\n\n        default:\n            ASSERT_TRUE(!\"unexpected option number\");\n            break;\n        }\n    }\n\n    va_end(list);\n\n    return opts;\n}\n\n#        define INIT_OPTIONS(...) \\\n            init_options(&(char[256]){ 0 }[0], 256, __VA_ARGS__)\n#        define BLOCK1(SeqNum, Size, HasMore) \\\n            &(avs_coap_option_block_t) {      \\\n                .type = AVS_COAP_BLOCK1,      \\\n                .seq_num = (SeqNum),          \\\n                .size = (Size),               \\\n                .has_more = (HasMore),        \\\n            }\n#        define BLOCK2(SeqNum, Size, HasMore) \\\n            &(avs_coap_option_block_t) {      \\\n                .type = AVS_COAP_BLOCK2,      \\\n                .seq_num = (SeqNum),          \\\n                .size = (Size),               \\\n                .has_more = (HasMore),        \\\n            }\n\nAVS_UNIT_TEST(coap_options_is_sequential_block_request, block1_simple) {\n    avs_coap_options_t prev_req =\n            INIT_OPTIONS(AVS_COAP_OPTION_BLOCK1, BLOCK1(1, 1024, true), 0);\n    avs_coap_options_t prev_res = prev_req;\n    avs_coap_options_t curr_req =\n            INIT_OPTIONS(AVS_COAP_OPTION_BLOCK1, BLOCK1(2, 1024, true), 0);\n\n    ASSERT_TRUE(_avs_coap_options_is_sequential_block_request(\n            &prev_res, &prev_req, &curr_req, 2048));\n}\n\nAVS_UNIT_TEST(coap_options_is_sequential_block_request, block2_simple) {\n    avs_coap_options_t prev_req =\n            INIT_OPTIONS(AVS_COAP_OPTION_BLOCK2, BLOCK2(1, 1024, true), 0);\n    avs_coap_options_t prev_res = prev_req;\n    avs_coap_options_t curr_req =\n            INIT_OPTIONS(AVS_COAP_OPTION_BLOCK2, BLOCK2(2, 1024, true), 0);\n\n    ASSERT_TRUE(_avs_coap_options_is_sequential_block_request(\n            &prev_res, &prev_req, &curr_req, 0));\n}\n\nAVS_UNIT_TEST(coap_options_is_sequential_block_request, block1_size_change) {\n    avs_coap_options_t prev_req =\n            INIT_OPTIONS(AVS_COAP_OPTION_BLOCK1, BLOCK1(1, 1024, true), 0);\n    avs_coap_options_t prev_res = prev_req;\n    avs_coap_options_t curr_req =\n            INIT_OPTIONS(AVS_COAP_OPTION_BLOCK1, BLOCK1(4, 512, true), 0);\n\n    ASSERT_TRUE(_avs_coap_options_is_sequential_block_request(\n            &prev_res, &prev_req, &curr_req, 2048));\n}\n\nAVS_UNIT_TEST(coap_options_is_sequential_block_request, block2_size_change) {\n    avs_coap_options_t prev_req =\n            INIT_OPTIONS(AVS_COAP_OPTION_BLOCK2, BLOCK2(1, 1024, true), 0);\n    avs_coap_options_t prev_res = prev_req;\n    avs_coap_options_t curr_req =\n            INIT_OPTIONS(AVS_COAP_OPTION_BLOCK2, BLOCK2(4, 512, true), 0);\n\n    ASSERT_TRUE(_avs_coap_options_is_sequential_block_request(\n            &prev_res, &prev_req, &curr_req, 2048));\n}\n\nAVS_UNIT_TEST(coap_options_is_sequential_block_request,\n              block1_elective_mismatch) {\n    avs_coap_options_t prev_req =\n            INIT_OPTIONS(AVS_COAP_OPTION_BLOCK1, BLOCK1(1, 1024, true),\n                         AVS_COAP_OPTION_URI_PATH, \"chcialem\",\n                         AVS_COAP_OPTION_LOCATION_QUERY, \"byc=marynarzem\", 0);\n    avs_coap_options_t prev_res = prev_req;\n    avs_coap_options_t curr_req =\n            INIT_OPTIONS(AVS_COAP_OPTION_BLOCK1, BLOCK1(2, 1024, true),\n                         AVS_COAP_OPTION_URI_PATH, \"chcialem\",\n                         AVS_COAP_OPTION_LOCATION_QUERY,\n                         \"byc=\"\n                         \"operatorem dzwigu budowlanego ktory podnosi pionowo \"\n                         \"zelbetowy strop o masie m=1500kg z przyspieszeniem \"\n                         \"a=2m/s^2\",\n                         0);\n\n    ASSERT_TRUE(_avs_coap_options_is_sequential_block_request(\n            &prev_res, &prev_req, &curr_req, 2048));\n}\n\nAVS_UNIT_TEST(coap_options_is_sequential_block_request, block1_critical_match) {\n    avs_coap_options_t prev_req =\n            INIT_OPTIONS(AVS_COAP_OPTION_BLOCK1, BLOCK1(1, 1024, true),\n                         AVS_COAP_OPTION_URI_PATH, \"chcialem\",\n                         AVS_COAP_OPTION_URI_QUERY, \"miec=tatuaze\", 0);\n    avs_coap_options_t prev_res = prev_req;\n    avs_coap_options_t curr_req =\n            INIT_OPTIONS(AVS_COAP_OPTION_BLOCK1, BLOCK1(2, 1024, true),\n                         AVS_COAP_OPTION_URI_PATH, \"chcialem\",\n                         AVS_COAP_OPTION_URI_QUERY, \"miec=tatuaze\", 0);\n\n    ASSERT_TRUE(_avs_coap_options_is_sequential_block_request(\n            &prev_res, &prev_req, &curr_req, 2048));\n}\n\nAVS_UNIT_TEST(coap_options_is_sequential_block_request,\n              block1_elective_dropped) {\n    avs_coap_options_t prev_req =\n            INIT_OPTIONS(AVS_COAP_OPTION_BLOCK1, BLOCK1(1, 1024, true),\n                         AVS_COAP_OPTION_LOCATION_QUERY, \"now look at this net\",\n                         0);\n    avs_coap_options_t prev_res = prev_req;\n    avs_coap_options_t curr_req =\n            INIT_OPTIONS(AVS_COAP_OPTION_BLOCK1, BLOCK1(2, 1024, true), 0);\n\n    ASSERT_TRUE(_avs_coap_options_is_sequential_block_request(\n            &prev_res, &prev_req, &curr_req, 2048));\n}\n\nAVS_UNIT_TEST(coap_options_is_sequential_block_request,\n              block1_elective_inserted) {\n    avs_coap_options_t prev_req =\n            INIT_OPTIONS(AVS_COAP_OPTION_BLOCK1, BLOCK1(1, 1024, true), 0);\n    avs_coap_options_t prev_res = prev_req;\n    avs_coap_options_t curr_req =\n            INIT_OPTIONS(AVS_COAP_OPTION_BLOCK1, BLOCK1(2, 1024, true),\n                         AVS_COAP_OPTION_LOCATION_QUERY, \"that i just found\",\n                         0);\n\n    ASSERT_TRUE(_avs_coap_options_is_sequential_block_request(\n            &prev_res, &prev_req, &curr_req, 2048));\n}\n\nAVS_UNIT_TEST(coap_options_is_sequential_block_request,\n              block1_offset_mismatch) {\n    avs_coap_options_t prev_req =\n            INIT_OPTIONS(AVS_COAP_OPTION_BLOCK1, BLOCK1(1, 1024, true), 0);\n    avs_coap_options_t prev_res = prev_req;\n    avs_coap_options_t curr_req =\n            INIT_OPTIONS(AVS_COAP_OPTION_BLOCK1, BLOCK1(3, 1024, true), 0);\n\n    ASSERT_FALSE(_avs_coap_options_is_sequential_block_request(\n            &prev_res, &prev_req, &curr_req, 2048));\n}\n\nAVS_UNIT_TEST(coap_options_is_sequential_block_request,\n              block2_offset_mismatch) {\n    avs_coap_options_t prev_req =\n            INIT_OPTIONS(AVS_COAP_OPTION_BLOCK2, BLOCK2(1, 1024, true), 0);\n    avs_coap_options_t prev_res = prev_req;\n    avs_coap_options_t curr_req =\n            INIT_OPTIONS(AVS_COAP_OPTION_BLOCK2, BLOCK2(3, 1024, true), 0);\n\n    ASSERT_FALSE(_avs_coap_options_is_sequential_block_request(\n            &prev_res, &prev_req, &curr_req, 0));\n}\n\nAVS_UNIT_TEST(coap_options_is_sequential_block_request, critical_mismatch) {\n    avs_coap_options_t prev_req =\n            INIT_OPTIONS(AVS_COAP_OPTION_BLOCK1, BLOCK1(1, 1024, true),\n                         AVS_COAP_OPTION_URI_PATH, \"when\",\n                         AVS_COAP_OPTION_URI_QUERY, \"i say=go\", 0);\n    avs_coap_options_t prev_res = prev_req;\n    avs_coap_options_t curr_req =\n            INIT_OPTIONS(AVS_COAP_OPTION_BLOCK1, BLOCK1(2, 1024, true),\n                         AVS_COAP_OPTION_URI_PATH, \"get ready\",\n                         AVS_COAP_OPTION_URI_QUERY, \"to=throw\", 0);\n\n    ASSERT_FALSE(_avs_coap_options_is_sequential_block_request(\n            &prev_res, &prev_req, &curr_req, 1024));\n}\n\nAVS_UNIT_TEST(coap_options_is_sequential_block_request,\n              content_format_mismatch) {\n    avs_coap_options_t prev_req =\n            INIT_OPTIONS(AVS_COAP_OPTION_BLOCK1, BLOCK1(1, 1024, true),\n                         AVS_COAP_OPTION_CONTENT_FORMAT, 42, 0);\n    avs_coap_options_t prev_res = prev_req;\n    avs_coap_options_t curr_req =\n            INIT_OPTIONS(AVS_COAP_OPTION_BLOCK1, BLOCK1(2, 1024, true),\n                         AVS_COAP_OPTION_CONTENT_FORMAT, 1042, 0);\n\n    ASSERT_FALSE(_avs_coap_options_is_sequential_block_request(\n            &prev_res, &prev_req, &curr_req, 1024));\n}\n#    endif // WITH_AVS_COAP_BLOCK\n\nAVS_UNIT_TEST(coap_options_dynamic, grow) {\n    avs_coap_options_t opts;\n    ASSERT_OK(avs_coap_options_dynamic_init_with_size(&opts, 0));\n\n    static const uint16_t NUMS[] = {\n        0,\n        _AVS_COAP_EXT_U8_BASE,\n        _AVS_COAP_EXT_U16_BASE,\n    };\n    static const uint8_t ZEROS[_AVS_COAP_EXT_U16_BASE] = \"\";\n\n    for (uint16_t size = 0; size < AVS_ARRAY_SIZE(NUMS); ++size) {\n        for (size_t num = 0; num < AVS_ARRAY_SIZE(NUMS); ++num) {\n            uint16_t opt_num = NUMS[num];\n            uint16_t opt_size = NUMS[size];\n\n            ASSERT_OK(avs_coap_options_add_opaque(&opts, opt_num, ZEROS,\n                                                  opt_size));\n        }\n    }\n\n    avs_coap_options_cleanup(&opts);\n}\n\nAVS_UNIT_TEST(coap_options_dynamic, double_cleanup) {\n    avs_coap_options_t opts;\n    ASSERT_OK(avs_coap_options_dynamic_init(&opts));\n    ASSERT_OK(avs_coap_options_add_empty(&opts, 100));\n\n    avs_coap_options_cleanup(&opts);\n    avs_coap_options_cleanup(&opts);\n}\n\nAVS_UNIT_TEST(coap_options, cleanup_is_safe_on_static_options) {\n    uint8_t buf[128];\n    avs_coap_options_t opts = avs_coap_options_create_empty(buf, sizeof(buf));\n    ASSERT_OK(avs_coap_options_add_empty(&opts, 100));\n\n    avs_coap_options_cleanup(&opts);\n    avs_coap_options_cleanup(&opts);\n}\n\nAVS_UNIT_TEST(coap_options, repeated_non_repeatable_elective_options) {\n    /**\n     * If a message includes an option with more occurrences than the option\n     * is defined for, each supernumerary option occurrence that appears\n     * subsequently in the message MUST be treated like an unrecognized\n     * option (see Section 5.4.1).\n     * (...)\n     * Upon reception, unrecognized options of class \"elective\" MUST be silently\n     * ignored.\n     */\n    uint8_t buf[128];\n    avs_coap_options_t opts = avs_coap_options_create_empty(buf, sizeof(buf));\n    avs_coap_options_add_u16(&opts, AVS_COAP_OPTION_CONTENT_FORMAT, 19);\n    avs_coap_options_add_u16(&opts, AVS_COAP_OPTION_CONTENT_FORMAT, 69);\n\n    ASSERT_TRUE(_avs_coap_options_valid(&opts));\n\n    uint16_t value = 0;\n    ASSERT_OK(avs_coap_options_get_u16(&opts, AVS_COAP_OPTION_CONTENT_FORMAT,\n                                       &value));\n    ASSERT_EQ(value, 19);\n}\n\nAVS_UNIT_TEST(coap_options, add_string_f) {\n    uint8_t buf[128];\n    avs_coap_options_t opts = avs_coap_options_create_empty(buf, sizeof(buf));\n    ASSERT_OK(avs_coap_options_add_string_f(&opts, AVS_COAP_OPTION_URI_PATH,\n                                            \"jogurty w %s tylko %d.%02d zl\",\n                                            \"realu\", 1, 29));\n    ASSERT_OK(avs_coap_options_add_string_f(&opts, AVS_COAP_OPTION_URI_QUERY,\n                                            \"nowe, nieuzywane %s do %s\",\n                                            \"kierunkowskazy\", \"prodiza\"));\n\n    char str[64];\n    avs_coap_option_iterator_t it = AVS_COAP_OPTION_ITERATOR_EMPTY;\n    size_t option_size;\n\n    ASSERT_OK(avs_coap_options_get_string_it(&opts, AVS_COAP_OPTION_URI_PATH,\n                                             &it, &option_size, str,\n                                             sizeof(str)));\n#    define EXPECTED_VALUE \"jogurty w realu tylko 1.29 zl\"\n    ASSERT_EQ(option_size, sizeof(EXPECTED_VALUE));\n    ASSERT_EQ_STR(str, EXPECTED_VALUE);\n#    undef EXPECTED_VALUE\n\n    it = AVS_COAP_OPTION_ITERATOR_EMPTY;\n    ASSERT_OK(avs_coap_options_get_string_it(&opts, AVS_COAP_OPTION_URI_QUERY,\n                                             &it, &option_size, str,\n                                             sizeof(str)));\n#    define EXPECTED_VALUE \"nowe, nieuzywane kierunkowskazy do prodiza\"\n    ASSERT_EQ(option_size, sizeof(EXPECTED_VALUE));\n    ASSERT_EQ_STR(str, EXPECTED_VALUE);\n#    undef EXPECTED_VALUE\n}\n\nAVS_UNIT_TEST(coap_options, add_string_f_nullbyte) {\n    uint8_t buf[32];\n    avs_coap_options_t opts = avs_coap_options_create_empty(buf, sizeof(buf));\n    ASSERT_OK(avs_coap_options_add_string_f(&opts, AVS_COAP_OPTION_URI_PATH,\n                                            \"lol %c nullbyte\", '\\0'));\n\n    uint8_t bytes[32];\n    avs_coap_option_iterator_t it = AVS_COAP_OPTION_ITERATOR_EMPTY;\n    size_t option_size;\n\n    ASSERT_OK(avs_coap_options_get_bytes_it(&opts, AVS_COAP_OPTION_URI_PATH,\n                                            &it, &option_size, bytes,\n                                            sizeof(bytes)));\n#    define EXPECTED_VALUE \"lol \\0 nullbyte\"\n    ASSERT_EQ(option_size, sizeof(EXPECTED_VALUE) - 1);\n    ASSERT_EQ_BYTES(bytes, EXPECTED_VALUE);\n#    undef EXPECTED_VALUE\n}\n\n#    define ETAG_FROM_STRING(Data)   \\\n        (avs_coap_etag_t) {          \\\n            .bytes = (Data),         \\\n            .size = sizeof(Data) - 1 \\\n        }\n\nAVS_UNIT_TEST(coap_options, two_etags) {\n    uint8_t buf[32];\n    avs_coap_options_t opts = avs_coap_options_create_empty(buf, sizeof(buf));\n    const avs_coap_etag_t etag1 = ETAG_FROM_STRING(\"tag\");\n    const avs_coap_etag_t etag2 = ETAG_FROM_STRING(\"napraw\");\n\n    ASSERT_OK(avs_coap_options_add_etag(&opts, &etag1));\n    ASSERT_OK(avs_coap_options_add_etag(&opts, &etag2));\n\n    avs_coap_option_iterator_t it = AVS_COAP_OPTION_ITERATOR_EMPTY;\n    avs_coap_etag_t out_etag;\n\n    ASSERT_OK(avs_coap_options_get_etag_it(&opts, &it, &out_etag));\n    ASSERT_TRUE(avs_coap_etag_equal(&etag1, &out_etag));\n\n    ASSERT_OK(avs_coap_options_get_etag_it(&opts, &it, &out_etag));\n    ASSERT_TRUE(avs_coap_etag_equal(&etag2, &out_etag));\n}\n\nAVS_UNIT_TEST(coap_options, get_string) {\n#    define OPTION1 \"opt1\"\n#    define OPTION2 \"opt2\"\n    uint8_t buf[32];\n    avs_coap_options_t opts = avs_coap_options_create_empty(buf, sizeof(buf));\n    ASSERT_OK(avs_coap_options_add_string(&opts, AVS_COAP_OPTION_URI_PATH,\n                                          OPTION1));\n    ASSERT_OK(avs_coap_options_add_string(&opts, AVS_COAP_OPTION_URI_PATH,\n                                          OPTION2));\n\n    char bytes[32];\n    size_t option_size;\n\n    ASSERT_OK(avs_coap_options_get_string(&opts, AVS_COAP_OPTION_URI_PATH,\n                                          &option_size, bytes, sizeof(bytes)));\n    ASSERT_EQ(option_size, sizeof(OPTION1));\n    ASSERT_EQ_BYTES(bytes, OPTION1);\n#    undef OPTION1\n#    undef OPTION2\n}\n\nAVS_UNIT_TEST(coap_options, reread_bytes_to_bigger_buffer) {\n#    define OPTION1 \"opcja 1\"\n#    define OPTION2 \"opcja 2\"\n    uint8_t buf[32];\n    avs_coap_options_t opts = avs_coap_options_create_empty(buf, sizeof(buf));\n    ASSERT_OK(avs_coap_options_add_string(&opts, AVS_COAP_OPTION_URI_PATH,\n                                          OPTION1));\n    ASSERT_OK(avs_coap_options_add_string(&opts, AVS_COAP_OPTION_URI_PATH,\n                                          OPTION2));\n\n    // too short buffer\n    char buffer_short[sizeof(OPTION1) - 1];\n\n    char buffer_long[sizeof(OPTION1)];\n    size_t option_size;\n\n    avs_coap_option_iterator_t it = AVS_COAP_OPTION_ITERATOR_EMPTY;\n    ASSERT_FAIL(avs_coap_options_get_string_it(&opts, AVS_COAP_OPTION_URI_PATH,\n                                               &it, &option_size, buffer_short,\n                                               sizeof(buffer_short)));\n    ASSERT_EQ(option_size, sizeof(OPTION1));\n\n    ASSERT_OK(avs_coap_options_get_string_it(&opts, AVS_COAP_OPTION_URI_PATH,\n                                             &it, &option_size, buffer_long,\n                                             sizeof(buffer_long)));\n\n    ASSERT_EQ(option_size, sizeof(OPTION1));\n    ASSERT_EQ_BYTES(buffer_long, OPTION1);\n\n#    undef OPTION1\n#    undef OPTION2\n}\n\nAVS_UNIT_TEST(coap_options, skip_option) {\n#    define OPTION1 \"opcja 1\"\n#    define OPTION2 \"opcja 2\"\n    uint8_t buf[32];\n    avs_coap_options_t opts = avs_coap_options_create_empty(buf, sizeof(buf));\n    ASSERT_OK(avs_coap_options_add_string(&opts, AVS_COAP_OPTION_URI_PATH,\n                                          OPTION1));\n    ASSERT_OK(avs_coap_options_add_string(&opts, AVS_COAP_OPTION_URI_PATH,\n                                          OPTION2));\n\n    // too short buffer\n    char buffer_short[sizeof(OPTION1) - 1];\n\n    char buffer_long[sizeof(OPTION2)];\n    size_t option_size;\n\n    avs_coap_option_iterator_t it = AVS_COAP_OPTION_ITERATOR_EMPTY;\n    ASSERT_FAIL(avs_coap_options_get_string_it(&opts, AVS_COAP_OPTION_URI_PATH,\n                                               &it, &option_size, buffer_short,\n                                               sizeof(buffer_short)));\n    ASSERT_OK(avs_coap_options_skip_it(&it));\n\n    ASSERT_OK(avs_coap_options_get_string_it(&opts, AVS_COAP_OPTION_URI_PATH,\n                                             &it, &option_size, buffer_long,\n                                             sizeof(buffer_long)));\n    ASSERT_FAIL(avs_coap_options_skip_it(&it));\n\n    ASSERT_EQ(option_size, sizeof(OPTION2));\n    ASSERT_EQ_BYTES(buffer_long, OPTION2);\n\n#    undef OPTION1\n#    undef OPTION2\n}\n\n#    ifdef WITH_AVS_COAP_OBSERVE\nAVS_UNIT_TEST(coap_options, observe) {\n    uint8_t buf[32];\n    avs_coap_options_t opts = avs_coap_options_create_empty(buf, sizeof(buf));\n    uint32_t value;\n\n    ASSERT_OK(avs_coap_options_add_observe(&opts, 0x1000000));\n    ASSERT_OK(avs_coap_options_get_observe(&opts, &value));\n    ASSERT_EQ(value, 0);\n    avs_coap_options_remove_by_number(&opts, AVS_COAP_OPTION_OBSERVE);\n\n    ASSERT_OK(avs_coap_options_add_observe(&opts, 0xFFFFFF));\n    ASSERT_OK(avs_coap_options_get_observe(&opts, &value));\n    ASSERT_EQ(value, 0xFFFFFF);\n}\n\n#        ifdef WITH_AVS_COAP_BLOCK\nstatic avs_coap_request_header_t request_header_init(uint8_t coap_code) {\n    static uint8_t buf[64];\n    memset(buf, 0, sizeof(buf));\n    return (avs_coap_request_header_t) {\n        .code = coap_code,\n        .options = avs_coap_options_create_empty(buf, sizeof(buf))\n    };\n}\n\nstatic bool critical_option_validator(uint8_t msg_code, uint32_t optnum) {\n    switch (msg_code) {\n    case AVS_COAP_CODE_GET:\n        return optnum == AVS_COAP_OPTION_URI_PATH\n               || optnum == AVS_COAP_OPTION_ACCEPT;\n    case AVS_COAP_CODE_PUT:\n    case AVS_COAP_CODE_POST:\n        return optnum == AVS_COAP_OPTION_URI_PATH\n               || optnum == AVS_COAP_OPTION_URI_QUERY\n               || optnum == AVS_COAP_OPTION_ACCEPT;\n    case AVS_COAP_CODE_DELETE:\n        return optnum == AVS_COAP_OPTION_URI_PATH;\n    case AVS_COAP_CODE_FETCH:\n        return optnum == AVS_COAP_OPTION_ACCEPT;\n    default:\n        return false;\n    }\n}\n\nAVS_UNIT_TEST(coap_options, critical_option_validator) {\n    avs_coap_request_header_t req_header;\n\n    // AVS_COAP_CODE_GET\n\n    req_header = request_header_init(AVS_COAP_CODE_GET);\n    ASSERT_OK(avs_coap_options_add_string(\n            &req_header.options, AVS_COAP_OPTION_URI_PATH, \"der_Kran\"));\n    ASSERT_OK(avs_coap_options_add_u16(\n            &req_header.options, AVS_COAP_OPTION_ACCEPT, AVS_COAP_FORMAT_JSON));\n    ASSERT_OK(avs_coap_options_validate_critical(&req_header,\n                                                 critical_option_validator));\n    // Observe is not critical\n    ASSERT_OK(avs_coap_options_add_observe(&req_header.options, 1));\n    ASSERT_OK(avs_coap_options_validate_critical(&req_header,\n                                                 critical_option_validator));\n    ASSERT_OK(avs_coap_options_add_block(&req_header.options,\n                                         &(avs_coap_option_block_t) {\n                                             .type = AVS_COAP_BLOCK2,\n                                             .seq_num = 0,\n                                             .has_more = false,\n                                             .size = 256,\n                                             .is_bert = false\n                                         }));\n    ASSERT_OK(avs_coap_options_validate_critical(&req_header,\n                                                 critical_option_validator));\n    // BLOCK1 cannot be present if code == GET\n    ASSERT_OK(avs_coap_options_add_block(&req_header.options,\n                                         &(avs_coap_option_block_t) {\n                                             .type = AVS_COAP_BLOCK1,\n                                             .seq_num = 0,\n                                             .has_more = false,\n                                             .size = 256,\n                                             .is_bert = false\n                                         }));\n    ASSERT_FAIL(avs_coap_options_validate_critical(&req_header,\n                                                   critical_option_validator));\n\n    // AVS_COAP_CODE_PUT\n    req_header = request_header_init(AVS_COAP_CODE_PUT);\n    ASSERT_OK(avs_coap_options_add_string(\n            &req_header.options, AVS_COAP_OPTION_URI_QUERY, \"omae_mou=dzwig\"));\n    ASSERT_OK(avs_coap_options_validate_critical(&req_header,\n                                                 critical_option_validator));\n    ASSERT_OK(avs_coap_options_add_string(&req_header.options,\n                                          AVS_COAP_OPTION_PROXY_URI,\n                                          \"bijcie masterczulki\"));\n    ASSERT_FAIL(avs_coap_options_validate_critical(&req_header,\n                                                   critical_option_validator));\n}\n#        endif // WITH_AVS_COAP_BLOCK\n\n#    endif // WITH_AVS_COAP_OBSERVE\n\n#endif // AVS_UNIT_TESTING\n"
  },
  {
    "path": "deps/avs_coap/tests/socket.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <avs_coap_init.h>\n\n#ifdef AVS_UNIT_TESTING\n\n#    include \"./socket.h\"\n\n#    define MODULE_NAME test\n#    include <avs_coap_x_log_config.h>\n\nvoid _avs_mocksock_create(avs_net_socket_t **mocksock, int inner_mtu, int mtu) {\n    avs_unit_mocksock_create(mocksock);\n    if (inner_mtu >= 0) {\n        avs_unit_mocksock_enable_inner_mtu_getopt(*mocksock, inner_mtu);\n    }\n    if (mtu >= 0) {\n        avs_unit_mocksock_enable_mtu_getopt(*mocksock, mtu);\n    }\n}\n\n#endif // AVS_UNIT_TESTING\n"
  },
  {
    "path": "deps/avs_coap/tests/socket.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef AVS_TEST_COAP_SOCKET_H\n#define AVS_TEST_COAP_SOCKET_H\n\n#include <avsystem/commons/avs_unit_mocksock.h>\n\n/**\n * NOTE: inner_mtu / mtu may be set to a negative value, in which case\n * they are not automatically handled by mocksock_get_opt()\n */\nvoid _avs_mocksock_create(avs_net_socket_t **mocksock, int inner_mtu, int mtu);\n\n#endif /* AVS_TEST_COAP_SOCKET_H */\n"
  },
  {
    "path": "deps/avs_coap/tests/tcp/async_client.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <avs_coap_init.h>\n\n#if defined(AVS_UNIT_TESTING) && defined(WITH_AVS_COAP_TCP)\n\n#    include \"tests/utils.h\"\n\n#    define MODULE_NAME test\n#    include <avs_coap_x_log_config.h>\n\n#    include \"./helper_functions.h\"\n\n#    define REQ_HEADER_FROM_REQ(Req)           \\\n        &(avs_coap_request_header_t) {         \\\n            .code = (Req)->request_header.code \\\n        }\n\ntypedef struct {\n    avs_coap_exchange_id_t exchange_id;\n    avs_coap_client_request_state_t result;\n    size_t payload_offset;\n    const void *payload;\n    size_t payload_size;\n} expected_response_t;\n\ntypedef AVS_LIST(expected_response_t) expected_responses_list_t;\n\ntypedef struct {\n    size_t next_offset;\n    expected_responses_list_t expected_responses;\n} response_handler_args_t;\n\nstatic response_handler_args_t setup_response_handler_args(void) {\n    response_handler_args_t args = { 0 };\n    return args;\n}\n\nstatic void cleanup_response_handler_args(response_handler_args_t *args) {\n    ASSERT_NULL(args->expected_responses);\n}\n\nstatic void expect_response_handler_call(response_handler_args_t *args,\n                                         avs_coap_exchange_id_t exchange_id,\n                                         avs_coap_client_request_state_t result,\n                                         const void *full_payload,\n                                         size_t payload_size) {\n    expected_response_t *expect = AVS_LIST_NEW_ELEMENT(expected_response_t);\n\n    *expect = (expected_response_t) {\n        .exchange_id = exchange_id,\n        .result = result,\n        .payload_offset = args->next_offset,\n        .payload = (const uint8_t *) full_payload + args->next_offset,\n        .payload_size = payload_size\n    };\n\n    AVS_LIST_APPEND(&args->expected_responses, expect);\n    args->next_offset += payload_size;\n}\n\nstatic void expect_cancel(response_handler_args_t *args,\n                          avs_coap_exchange_id_t exchange_id) {\n    expect_response_handler_call(args, exchange_id,\n                                 AVS_COAP_CLIENT_REQUEST_CANCEL, NULL, 0);\n}\n\nstatic void expect_fail(response_handler_args_t *args,\n                        avs_coap_exchange_id_t exchange_id) {\n    expect_response_handler_call(args, exchange_id,\n                                 AVS_COAP_CLIENT_REQUEST_FAIL, NULL, 0);\n}\n\nstatic void expect_partial_content(response_handler_args_t *args,\n                                   avs_coap_exchange_id_t exchange_id,\n                                   const void *full_payload,\n                                   size_t payload_size) {\n    expect_response_handler_call(args, exchange_id,\n                                 AVS_COAP_CLIENT_REQUEST_PARTIAL_CONTENT,\n                                 full_payload, payload_size);\n}\n\nstatic void expect_finished_response(response_handler_args_t *args,\n                                     avs_coap_exchange_id_t exchange_id,\n                                     const void *full_payload,\n                                     size_t payload_size) {\n    expect_response_handler_call(args, exchange_id, AVS_COAP_CLIENT_REQUEST_OK,\n                                 full_payload, payload_size);\n}\n\nstatic void handle_response(avs_coap_ctx_t *ctx,\n                            avs_coap_exchange_id_t exchange_id,\n                            avs_coap_client_request_state_t result,\n                            const avs_coap_client_async_response_t *response,\n                            avs_error_t err,\n                            void *arg) {\n    (void) ctx;\n    (void) err;\n\n    response_handler_args_t *args = (response_handler_args_t *) arg;\n\n    ASSERT_NOT_NULL(arg);\n    expected_response_t *expected =\n            (expected_response_t *) args->expected_responses;\n    ASSERT_NOT_NULL(expected);\n\n    ASSERT_TRUE(avs_coap_exchange_id_equal(exchange_id, expected->exchange_id));\n    ASSERT_EQ(result, expected->result);\n\n    switch (result) {\n    case AVS_COAP_CLIENT_REQUEST_PARTIAL_CONTENT:\n    case AVS_COAP_CLIENT_REQUEST_OK:\n        ASSERT_EQ(response->payload_offset, expected->payload_offset);\n        ASSERT_EQ(response->payload_size, expected->payload_size);\n        ASSERT_EQ_BYTES_SIZED(response->payload, expected->payload,\n                              response->payload_size);\n        break;\n\n    case AVS_COAP_CLIENT_REQUEST_CANCEL:\n    case AVS_COAP_CLIENT_REQUEST_FAIL:\n        ASSERT_NULL(response);\n        break;\n    }\n\n    AVS_LIST_DELETE(&args->expected_responses);\n}\n\nAVS_UNIT_TEST(tcp_async_client,\n              cancel_exchange_after_receiving_first_chunk_of_response) {\n#    define RESPONSE_PAYLOAD \"raz dwa trzy\"\n    test_env_t env __attribute__((cleanup(test_teardown))) = test_setup();\n    response_handler_args_t args_res1\n            __attribute__((cleanup(cleanup_response_handler_args))) =\n                    setup_response_handler_args();\n    response_handler_args_t args_res2\n            __attribute__((cleanup(cleanup_response_handler_args))) =\n                    setup_response_handler_args();\n\n    const test_msg_t *req = COAP_MSG(GET, TOKEN(nth_token(1)));\n    const test_msg_t *res =\n            COAP_MSG(CONTENT, TOKEN(nth_token(1)), PAYLOAD(RESPONSE_PAYLOAD));\n\n    avs_coap_exchange_id_t id;\n    ASSERT_OK(avs_coap_client_send_async_request(\n            env.coap_ctx, &id, REQ_HEADER_FROM_REQ(req), NULL, NULL,\n            handle_response, &args_res1));\n\n    expect_send(&env, req);\n    expect_sliced_recv(&env, res, res->payload_offset + 1);\n    avs_sched_run(env.sched);\n\n    expect_partial_content(&args_res1, id, RESPONSE_PAYLOAD, 1);\n    ASSERT_OK(handle_incoming_packet(env.coap_ctx, NULL, NULL));\n\n    expect_cancel(&args_res1, id);\n    avs_coap_exchange_cancel(env.coap_ctx, id);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(handle_incoming_packet(env.coap_ctx, NULL, NULL));\n\n    req = COAP_MSG(GET, TOKEN(nth_token(2)));\n    res = COAP_MSG(CONTENT, TOKEN(nth_token(2)), PAYLOAD(RESPONSE_PAYLOAD));\n\n    // Second request\n    ASSERT_OK(avs_coap_client_send_async_request(\n            env.coap_ctx, &id, REQ_HEADER_FROM_REQ(req), NULL, NULL,\n            handle_response, &args_res2));\n\n    expect_send(&env, req);\n    expect_recv(&env, res);\n    avs_sched_run(env.sched);\n\n    expect_finished_response(&args_res2, id, res->msg.content.payload,\n                             res->msg.content.payload_size);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(handle_incoming_packet(env.coap_ctx, NULL, NULL));\n\n#    undef RESPONSE_PAYLOAD\n}\n\nAVS_UNIT_TEST(tcp_async_client, repeated_non_repeatable_critical_option) {\n    test_env_t env __attribute__((cleanup(test_teardown))) = test_setup();\n    response_handler_args_t args\n            __attribute__((cleanup(cleanup_response_handler_args))) =\n                    setup_response_handler_args();\n\n    const test_msg_t *request = COAP_MSG(PUT, TOKEN(nth_token(1)));\n    // Accept option in response only for test purposes.\n    const test_msg_t *response = COAP_MSG(BAD_OPTION, TOKEN(nth_token(1)),\n                                          ACCEPT(1), DUPLICATED_ACCEPT(2));\n\n    avs_coap_exchange_id_t id;\n    ASSERT_OK(avs_coap_client_send_async_request(\n            env.coap_ctx, &id,\n            &(avs_coap_request_header_t) {\n                .code = request->request_header.code\n            },\n            NULL, NULL, handle_response, &args));\n\n    expect_send(&env, request);\n    expect_recv(&env, response);\n    avs_sched_run(env.sched);\n\n    expect_fail(&args, id);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(handle_incoming_packet(env.coap_ctx, NULL, NULL));\n}\n\n#    ifdef WITH_AVS_COAP_BLOCK\n\n#        define INVALID_BLOCK2(Seq, Size, Payload) \\\n            .block1 = {                            \\\n                .type = AVS_COAP_BLOCK2,           \\\n                .seq_num = Seq,                    \\\n                .size = Size,                      \\\n                .has_more = true                   \\\n            },                                     \\\n            .payload = Payload,                    \\\n            .payload_size =                        \\\n                    (assert(sizeof(Payload) - 1 < Size), sizeof(Payload) - 1)\n\nAVS_UNIT_TEST(tcp_async_client, invalid_block_opt_in_response) {\n    // response with BLOCK2.has_more == 1 and BLOCK2.size != payload size\n    test_env_t env __attribute__((cleanup(test_teardown))) = test_setup();\n    response_handler_args_t args\n            __attribute__((cleanup(cleanup_response_handler_args))) =\n                    setup_response_handler_args();\n\n    const test_msg_t *request =\n            COAP_MSG(GET, TOKEN(nth_token(1)), BLOCK2_REQ(0, 1024));\n    const test_msg_t *response = COAP_MSG(BAD_OPTION, TOKEN(nth_token(1)),\n                                          INVALID_BLOCK2(0, 1024, DATA_32B));\n\n    avs_coap_exchange_id_t id;\n    ASSERT_OK(avs_coap_client_send_async_request(env.coap_ctx, &id,\n                                                 &request->request_header, NULL,\n                                                 NULL, handle_response, &args));\n\n    expect_send(&env, request);\n    expect_recv(&env, response);\n    avs_sched_run(env.sched);\n\n    expect_has_buffered_data_check(&env, true);\n    expect_fail(&args, id);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(handle_incoming_packet(env.coap_ctx, NULL, NULL));\n\n    ASSERT_NULL(args.expected_responses);\n}\n\nAVS_UNIT_TEST(tcp_async_client, sliced_block_response) {\n    test_env_t env __attribute__((cleanup(test_teardown))) = test_setup();\n    response_handler_args_t args\n            __attribute__((cleanup(cleanup_response_handler_args))) =\n                    setup_response_handler_args();\n\n    // This is a second message sent by CoAP/TCP context.\n    const test_msg_t *req = COAP_MSG(GET, TOKEN(nth_token(1)));\n    const test_msg_t *res =\n            COAP_MSG(CONTENT, TOKEN(nth_token(1)), BLOCK2_RES(0, 16, DATA_16B));\n\n    avs_coap_exchange_id_t id;\n    avs_coap_client_send_async_request(env.coap_ctx, &id,\n                                       REQ_HEADER_FROM_REQ(req), NULL, NULL,\n                                       handle_response, &args);\n\n    expect_send(&env, req);\n    avs_sched_run(env.sched);\n\n    expect_sliced_recv(&env, res, res->payload_offset + 11);\n    expect_partial_content(&args, id, DATA_16B, 11);\n    ASSERT_OK(handle_incoming_packet(env.coap_ctx, NULL, NULL));\n\n    expect_finished_response(&args, id, DATA_16B, 5);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(handle_incoming_packet(env.coap_ctx, NULL, NULL));\n}\n\nAVS_UNIT_TEST(tcp_async_client, server_responded_with_bert_2049b) {\n#        define RESPONSE_PAYLOAD DATA_2KB \"?\"\n\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_with_custom_sized_buffers(2048, 2048);\n    response_handler_args_t args\n            __attribute__((cleanup(cleanup_response_handler_args))) =\n                    setup_response_handler_args();\n\n    const test_msg_t *requests[] = { COAP_MSG(GET, TOKEN(nth_token(1))),\n                                     COAP_MSG(GET, TOKEN(nth_token(2)),\n                                              BERT2_REQ(2)) };\n\n    const test_msg_t *responses[] = {\n        COAP_MSG(CONTENT, TOKEN(nth_token(1)),\n                 BERT2_RES(0, 2048, RESPONSE_PAYLOAD)),\n        COAP_MSG(CONTENT, TOKEN(nth_token(2)),\n                 BERT2_RES(2, 2048, RESPONSE_PAYLOAD))\n    };\n\n    avs_coap_exchange_id_t id;\n    ASSERT_OK(avs_coap_client_send_async_request(\n            env.coap_ctx, &id,\n            &(avs_coap_request_header_t) {\n                .code = requests[0]->request_header.code\n            },\n            NULL, NULL, handle_response, &args));\n\n    expect_send(&env, requests[0]);\n    expect_recv(&env, responses[0]);\n    expect_send(&env, requests[1]);\n    expect_recv(&env, responses[1]);\n    avs_sched_run(env.sched);\n\n    size_t first_chunk_size =\n            OPTS_BUFFER_SIZE\n            - (responses[0]->payload_offset - responses[0]->options_offset);\n    expect_partial_content(&args, id, RESPONSE_PAYLOAD, first_chunk_size);\n    expect_has_buffered_data_check(&env, true);\n    expect_partial_content(&args, id, RESPONSE_PAYLOAD,\n                           responses[0]->msg.content.payload_size\n                                   - first_chunk_size);\n    expect_has_buffered_data_check(&env, true);\n    expect_finished_response(&args, id, RESPONSE_PAYLOAD,\n                             responses[1]->msg.content.payload_size);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(handle_incoming_packet(env.coap_ctx, NULL, NULL));\n\n#        undef RESPONSE_PAYLOAD\n}\n\nAVS_UNIT_TEST(tcp_async_client, server_responded_with_bert_3073b) {\n#        define RESPONSE_PAYLOAD DATA_2KB DATA_1KB \"?\"\n\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_with_custom_sized_buffers(2048, 2048);\n    response_handler_args_t args\n            __attribute__((cleanup(cleanup_response_handler_args))) =\n                    setup_response_handler_args();\n\n    const test_msg_t *requests[] = { COAP_MSG(GET, TOKEN(nth_token(1))),\n                                     COAP_MSG(GET, TOKEN(nth_token(2)),\n                                              BERT2_REQ(2)) };\n\n    const test_msg_t *responses[] = {\n        COAP_MSG(CONTENT, TOKEN(nth_token(1)),\n                 BERT2_RES(0, 2048, RESPONSE_PAYLOAD)),\n        COAP_MSG(CONTENT, TOKEN(nth_token(2)),\n                 BERT2_RES(2, 2048, RESPONSE_PAYLOAD))\n    };\n\n    avs_coap_exchange_id_t id;\n    ASSERT_OK(avs_coap_client_send_async_request(\n            env.coap_ctx, &id,\n            &(avs_coap_request_header_t) {\n                .code = requests[0]->request_header.code\n            },\n            NULL, NULL, handle_response, &args));\n\n    expect_send(&env, requests[0]);\n    expect_recv(&env, responses[0]);\n    expect_send(&env, requests[1]);\n    expect_recv(&env, responses[1]);\n    avs_sched_run(env.sched);\n\n    size_t first_chunk_size =\n            OPTS_BUFFER_SIZE\n            - (responses[0]->payload_offset - responses[0]->options_offset);\n    expect_partial_content(&args, id, RESPONSE_PAYLOAD, first_chunk_size);\n    expect_has_buffered_data_check(&env, true);\n    expect_partial_content(&args, id, RESPONSE_PAYLOAD,\n                           responses[0]->msg.content.payload_size\n                                   - first_chunk_size);\n    expect_has_buffered_data_check(&env, true);\n    first_chunk_size =\n            OPTS_BUFFER_SIZE\n            - (responses[0]->payload_offset - responses[0]->options_offset);\n    expect_partial_content(&args, id, RESPONSE_PAYLOAD, first_chunk_size);\n    expect_has_buffered_data_check(&env, true);\n    expect_finished_response(&args, id, RESPONSE_PAYLOAD,\n                             responses[1]->msg.content.payload_size\n                                     - first_chunk_size);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(handle_incoming_packet(env.coap_ctx, NULL, NULL));\n\n#        undef RESPONSE_PAYLOAD\n}\n\nAVS_UNIT_TEST(tcp_async_client, server_responded_with_sliced_bert) {\n#        define RESPONSE_PAYLOAD DATA_2KB \"?\"\n\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_with_custom_sized_buffers(2048, 2048);\n    response_handler_args_t args\n            __attribute__((cleanup(cleanup_response_handler_args))) =\n                    setup_response_handler_args();\n\n    const test_msg_t *requests[] = { COAP_MSG(GET, TOKEN(nth_token(1))),\n                                     COAP_MSG(GET, TOKEN(nth_token(2)),\n                                              BERT2_REQ(2)) };\n\n    const test_msg_t *responses[] = {\n        COAP_MSG(CONTENT, TOKEN(nth_token(1)),\n                 BERT2_RES(0, 2048, RESPONSE_PAYLOAD)),\n        COAP_MSG(CONTENT, TOKEN(nth_token(2)),\n                 BERT2_RES(2, 2048, RESPONSE_PAYLOAD))\n    };\n\n    avs_coap_exchange_id_t id;\n    ASSERT_OK(avs_coap_client_send_async_request(\n            env.coap_ctx, &id,\n            &(avs_coap_request_header_t) {\n                .code = requests[0]->request_header.code\n            },\n            NULL, NULL, handle_response, &args));\n\n    const size_t slice_pos = 512;\n    expect_send(&env, requests[0]);\n    expect_sliced_recv(&env, responses[0], slice_pos);\n    expect_send(&env, requests[1]);\n    expect_recv(&env, responses[1]);\n    avs_sched_run(env.sched);\n\n    size_t first_chunk_size =\n            OPTS_BUFFER_SIZE\n            - (responses[0]->payload_offset - responses[0]->options_offset);\n    expect_partial_content(&args, id, RESPONSE_PAYLOAD, first_chunk_size);\n\n    size_t offset = first_chunk_size;\n    size_t chunk_size = slice_pos - offset - responses[0]->payload_offset;\n    expect_partial_content(&args, id, RESPONSE_PAYLOAD, chunk_size);\n    expect_has_buffered_data_check(&env, true);\n    ASSERT_OK(handle_incoming_packet(env.coap_ctx, NULL, NULL));\n\n    offset += chunk_size;\n    expect_partial_content(&args, id, RESPONSE_PAYLOAD,\n                           responses[0]->msg.content.payload_size - offset);\n    expect_has_buffered_data_check(&env, true);\n    offset = responses[0]->msg.content.payload_size;\n    expect_finished_response(&args, id, RESPONSE_PAYLOAD,\n                             responses[1]->msg.content.payload_size);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(handle_incoming_packet(env.coap_ctx, NULL, NULL));\n\n#        undef RESPONSE_PAYLOAD\n}\n\nAVS_UNIT_TEST(tcp_async_client, block_response_with_too_big_options) {\n    test_env_t env __attribute__((cleanup(test_teardown))) = test_setup();\n    response_handler_args_t args\n            __attribute__((cleanup(cleanup_response_handler_args))) =\n                    setup_response_handler_args();\n    const test_msg_t *req = COAP_MSG(GET, TOKEN(nth_token(1)));\n    // Path option in response only for test purposes.\n    const test_msg_t *res =\n            COAP_MSG(CONTENT, TOKEN(nth_token(1)), BLOCK2_RES(0, 16, DATA_16B),\n                     PATH(\"why are you okay? you are okay\"));\n    ASSERT_TRUE(res->response_header.options.size > MAX_OPTS_SIZE);\n\n    avs_coap_exchange_id_t id;\n    avs_coap_client_send_async_request(env.coap_ctx, &id,\n                                       REQ_HEADER_FROM_REQ(req), NULL, NULL,\n                                       handle_response, &args);\n\n    expect_send(&env, req);\n    expect_recv(&env, res);\n    avs_sched_run(env.sched);\n\n    expect_has_buffered_data_check(&env, true);\n    expect_fail(&args, id);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(handle_incoming_packet(env.coap_ctx, NULL, NULL));\n}\n\n#    endif // WITH_AVS_COAP_BLOCK\n\n#endif // defined(AVS_UNIT_TESTING) && defined(WITH_AVS_COAP_TCP)\n"
  },
  {
    "path": "deps/avs_coap/tests/tcp/async_server.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <avs_coap_init.h>\n\n#if defined(AVS_UNIT_TESTING) && defined(WITH_AVS_COAP_TCP)\n\n#    include \"tests/utils.h\"\n\n#    define MODULE_NAME test\n#    include <avs_coap_x_log_config.h>\n\n#    include \"./helper_functions.h\"\n\n#    define EXCHANGE_ID(Id)        \\\n        (avs_coap_exchange_id_t) { \\\n            .value = (Id)          \\\n        }\n\ntypedef enum {\n    ACTION_NONE = 0,\n    ACTION_FAIL,\n    ACTION_CANCEL,\n    ACTION_SETUP_NOT_FOUND_RESPONSE,\n    ACTION_SETUP_VALID_RESPONSE,\n#    ifdef WITH_AVS_COAP_OBSERVE\n    ACTION_ACCEPT_OBSERVE,\n#    endif // WITH_AVS_COAP_OBSERVE\n    ACTION_HANDLE_INCOMING_PACKET,\n    ACTION_SEND_REQUEST\n} request_handler_action_t;\n\ntypedef struct {\n    char *data;\n    size_t size;\n} payload_buf_t;\n\ntypedef struct {\n    avs_coap_server_request_state_t result;\n    size_t payload_offset;\n    const void *payload;\n    size_t payload_size;\n\n    payload_buf_t *payload_writer_arg;\n    // Optional action executed regardless of request state for simulating\n    // non-standard behavior.\n    request_handler_action_t action;\n} expected_request_t;\n\ntypedef AVS_LIST(expected_request_t) expected_requests_list_t;\n\ntypedef struct {\n    avs_coap_ctx_t *coap_ctx;\n    size_t next_offset;\n    avs_coap_exchange_id_t exchange_id;\n\n    expected_requests_list_t expected_requests;\n} request_handler_args_t;\n\nstatic request_handler_args_t\nsetup_request_handler_args(avs_coap_ctx_t *ctx,\n                           avs_coap_exchange_id_t exchange_id) {\n    request_handler_args_t args = {\n        .coap_ctx = ctx,\n        .exchange_id = exchange_id\n    };\n    return args;\n}\n\nstatic void cleanup_request_handler_args(request_handler_args_t *args) {\n    ASSERT_NULL(args->expected_requests);\n}\n\nstatic int test_payload_writer(size_t payload_offset,\n                               void *payload_buf,\n                               size_t payload_buf_size,\n                               size_t *out_payload_chunk_size,\n                               void *arg) {\n    payload_buf_t *payload = (payload_buf_t *) arg;\n    ASSERT_TRUE(payload_offset <= payload->size);\n\n    *out_payload_chunk_size =\n            AVS_MIN(payload_buf_size, payload->size - payload_offset);\n    memcpy(payload_buf, payload->data + payload_offset,\n           *out_payload_chunk_size);\n    return 0;\n}\n\nstatic void expect_request_handler_call(request_handler_args_t *args,\n                                        avs_coap_server_request_state_t result,\n                                        const void *full_payload,\n                                        size_t payload_size,\n                                        request_handler_action_t action,\n                                        payload_buf_t *payload_writer_arg) {\n    expected_request_t *expect = AVS_LIST_NEW_ELEMENT(expected_request_t);\n    ASSERT_NOT_NULL(expect);\n\n    *expect = (expected_request_t) {\n        .result = result,\n        .payload_offset = args->next_offset,\n        .payload = full_payload + args->next_offset,\n        .payload_size = payload_size,\n        .payload_writer_arg = payload_writer_arg,\n        .action = action\n    };\n    AVS_LIST_APPEND(&args->expected_requests, expect);\n\n    args->next_offset += payload_size;\n}\n\nstatic void expect_partial_content(request_handler_args_t *args,\n                                   const void *payload,\n                                   size_t payload_size,\n                                   request_handler_action_t action,\n                                   payload_buf_t *payload_writer_arg) {\n    expect_request_handler_call(args, AVS_COAP_SERVER_REQUEST_PARTIAL_CONTENT,\n                                payload, payload_size, action,\n                                payload_writer_arg);\n}\n\nstatic void expect_last_chunk(request_handler_args_t *args,\n                              const void *payload,\n                              size_t payload_size,\n                              request_handler_action_t action,\n                              payload_buf_t *payload_writer_arg) {\n    expect_request_handler_call(args, AVS_COAP_SERVER_REQUEST_RECEIVED, payload,\n                                payload_size, action, payload_writer_arg);\n}\n\nstatic void expect_cleanup(request_handler_args_t *args) {\n    expect_request_handler_call(args, AVS_COAP_SERVER_REQUEST_CLEANUP, NULL, 0,\n                                false, NULL);\n}\n\nstatic void validate_request(const avs_coap_server_async_request_t *actual,\n                             const expected_request_t *expected) {\n    ASSERT_NOT_NULL(actual);\n    ASSERT_EQ(actual->header.code, AVS_COAP_CODE_GET);\n    ASSERT_EQ(actual->payload_offset, expected->payload_offset);\n    ASSERT_EQ(actual->payload_size, expected->payload_size);\n    ASSERT_EQ_BYTES_SIZED(actual->payload, expected->payload,\n                          actual->payload_size);\n}\n\nstatic int handle_new_request(avs_coap_server_ctx_t *ctx,\n                              const avs_coap_request_header_t *request,\n                              void *arg);\n\nstatic int test_request_handler(avs_coap_request_ctx_t *ctx,\n                                avs_coap_exchange_id_t exchange_id,\n                                avs_coap_server_request_state_t result,\n                                const avs_coap_server_async_request_t *request,\n                                const avs_coap_observe_id_t *observe_id,\n                                void *arg) {\n    (void) observe_id;\n\n    ASSERT_NOT_NULL(arg);\n    request_handler_args_t *args = (request_handler_args_t *) arg;\n    expected_request_t *expected =\n            (expected_request_t *) args->expected_requests;\n    ASSERT_NOT_NULL(expected);\n\n    ASSERT_TRUE(avs_coap_exchange_id_equal(exchange_id, args->exchange_id));\n    ASSERT_EQ(result, expected->result);\n\n    if (result == AVS_COAP_SERVER_REQUEST_CLEANUP) {\n        ASSERT_NULL(request);\n        AVS_LIST_DELETE(&args->expected_requests);\n        return 0;\n    }\n\n    validate_request(request, expected);\n\n    int retval = 0;\n    if (args->expected_requests->action != ACTION_NONE) {\n        switch (args->expected_requests->action) {\n        case ACTION_CANCEL:\n            // Delete from list first, because cancel will call this handler\n            // again.\n            AVS_LIST_DELETE(&args->expected_requests);\n            avs_coap_exchange_cancel(args->coap_ctx, exchange_id);\n            return 0;\n\n        case ACTION_SETUP_NOT_FOUND_RESPONSE:\n        case ACTION_SETUP_VALID_RESPONSE:\n            ASSERT_OK(avs_coap_server_setup_async_response(\n                    ctx,\n                    &(avs_coap_response_header_t) {\n                        .code = (args->expected_requests->action\n                                 == ACTION_SETUP_VALID_RESPONSE)\n                                        ? AVS_COAP_CODE_VALID\n                                        : AVS_COAP_CODE_NOT_FOUND\n                    },\n                    expected->payload_writer_arg ? test_payload_writer : NULL,\n                    expected->payload_writer_arg));\n            break;\n\n#    ifdef WITH_AVS_COAP_OBSERVE\n        case ACTION_ACCEPT_OBSERVE:\n            ASSERT_OK(\n                    avs_coap_observe_async_start(ctx, *observe_id, NULL, NULL));\n            retval = AVS_COAP_CODE_CONTENT;\n            break;\n#    endif // WITH_AVS_COAP_OBSERVE\n\n        case ACTION_FAIL:\n            retval = -1;\n            break;\n\n        case ACTION_HANDLE_INCOMING_PACKET:\n            ASSERT_FAIL(handle_incoming_packet(ctx->coap_ctx,\n                                               handle_new_request, NULL));\n            retval = AVS_COAP_CODE_CONTENT;\n            break;\n\n        case ACTION_SEND_REQUEST: {\n            avs_coap_request_header_t header = {\n                .code = AVS_COAP_CODE_GET\n            };\n            ASSERT_OK(avs_coap_client_send_async_request(\n                    ctx->coap_ctx, NULL, &header, NULL, NULL, NULL, NULL));\n            retval = AVS_COAP_CODE_CONTENT;\n            break;\n        }\n\n        default:\n            ASSERT_NULL(\"unexpected\");\n        }\n        AVS_LIST_DELETE(&args->expected_requests);\n        return retval;\n    }\n\n    retval = -1;\n    switch (result) {\n    case AVS_COAP_SERVER_REQUEST_PARTIAL_CONTENT:\n        retval = 0;\n        break;\n\n    case AVS_COAP_SERVER_REQUEST_RECEIVED:\n        if (expected->payload_writer_arg) {\n            ASSERT_OK(avs_coap_server_setup_async_response(\n                    ctx,\n                    &(avs_coap_response_header_t) {\n                        .code = AVS_COAP_CODE_CONTENT\n                    },\n                    test_payload_writer, expected->payload_writer_arg));\n            retval = 0;\n        } else {\n            retval = AVS_COAP_CODE_CONTENT;\n        }\n        break;\n\n    default:\n        break;\n    }\n\n    AVS_LIST_DELETE(&args->expected_requests);\n    return retval;\n}\n\nstatic int handle_new_request(avs_coap_server_ctx_t *ctx,\n                              const avs_coap_request_header_t *request,\n                              void *arg) {\n    (void) request;\n\n    avs_coap_exchange_id_t id =\n            avs_coap_server_accept_async_request(ctx, test_request_handler,\n                                                 arg);\n    ASSERT_TRUE(avs_coap_exchange_id_valid(id));\n    return 0;\n}\n\nAVS_UNIT_TEST(tcp_async_server, handle_request_partial) {\n    test_env_t env __attribute__((cleanup(test_teardown))) = test_setup();\n    request_handler_args_t args\n            __attribute__((cleanup(cleanup_request_handler_args))) =\n                    setup_request_handler_args(env.coap_ctx, EXCHANGE_ID(1));\n\n    const test_msg_t *req =\n            COAP_MSG(GET, TOKEN(nth_token(0)), PAYLOAD(\"PlacLaduj\"));\n    const test_msg_t *res = COAP_MSG(CONTENT, TOKEN(nth_token(0)));\n\n    expect_sliced_recv(&env, req, req->payload_offset + 4);\n\n    expect_partial_content(&args, req->msg.content.payload, 4, ACTION_NONE,\n                           NULL);\n    ASSERT_OK(handle_incoming_packet(env.coap_ctx, handle_new_request, &args));\n\n    expect_send(&env, res);\n\n    expect_last_chunk(&args, req->msg.content.payload, 5, ACTION_NONE, NULL);\n    expect_cleanup(&args);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(handle_incoming_packet(env.coap_ctx, NULL, NULL));\n}\n\nAVS_UNIT_TEST(tcp_async_server,\n              send_response_after_receiving_first_payload_chunk) {\n    test_env_t env __attribute__((cleanup(test_teardown))) = test_setup();\n    request_handler_args_t args\n            __attribute__((cleanup(cleanup_request_handler_args))) =\n                    setup_request_handler_args(env.coap_ctx, EXCHANGE_ID(1));\n\n    const test_msg_t *req =\n            COAP_MSG(GET, TOKEN(nth_token(0)), PAYLOAD(\"PlacLaduj\"));\n    const test_msg_t *res = COAP_MSG(VALID, TOKEN(nth_token(0)));\n\n    avs_unit_mocksock_input(env.mocksock, req->data, req->size - 1);\n    expect_send(&env, res);\n    expect_has_buffered_data_check(&env, false);\n\n    expect_partial_content(&args, req->msg.content.payload,\n                           sizeof(\"PlacLaduj\") - 2, ACTION_SETUP_VALID_RESPONSE,\n                           NULL);\n    expect_cleanup(&args);\n    ASSERT_OK(handle_incoming_packet(env.coap_ctx, handle_new_request, &args));\n\n    avs_unit_mocksock_input(env.mocksock, req->data + req->size - 1, 1);\n    expect_has_buffered_data_check(&env, false);\n    // Request handler shouldn't be called\n    ASSERT_OK(handle_incoming_packet(env.coap_ctx, handle_new_request, &args));\n}\n\nAVS_UNIT_TEST(tcp_async_server, handle_incoming_packet_in_request_handler) {\n    test_env_t env __attribute__((cleanup(test_teardown))) = test_setup();\n    request_handler_args_t args\n            __attribute__((cleanup(cleanup_request_handler_args))) =\n                    setup_request_handler_args(env.coap_ctx, EXCHANGE_ID(1));\n\n    const test_msg_t *req =\n            COAP_MSG(GET, TOKEN(nth_token(0)), PAYLOAD(\"PlacLaduj\"));\n    const test_msg_t *res = COAP_MSG(CONTENT, TOKEN(nth_token(0)));\n\n    expect_recv(&env, req);\n    expect_send(&env, res);\n    expect_has_buffered_data_check(&env, false);\n    expect_last_chunk(&args, req->msg.content.payload,\n                      req->msg.content.payload_size,\n                      ACTION_HANDLE_INCOMING_PACKET, NULL);\n    expect_cleanup(&args);\n    ASSERT_OK(handle_incoming_packet(env.coap_ctx, handle_new_request, &args));\n}\n\nAVS_UNIT_TEST(tcp_async_server, connection_closed_by_peer) {\n    test_env_t env __attribute__((cleanup(test_teardown))) = test_setup();\n    expect_send(&env, COAP_MSG(ABORT));\n    env.aborted = true;\n    // Check if there's no infinite loop inside.\n    // mocksock is returning success and 0 bytes received. IRL it means, that\n    // connection was closed by peer.\n    avs_error_t err =\n            handle_incoming_packet(env.coap_ctx, handle_new_request, NULL);\n    ASSERT_TRUE(avs_is_err(err));\n    ASSERT_EQ(err.code, AVS_COAP_ERR_TCP_CONN_CLOSED);\n}\n\nAVS_UNIT_TEST(tcp_async_server, handle_request_with_options_partial) {\n    test_env_t env __attribute__((cleanup(test_teardown))) = test_setup();\n    request_handler_args_t args\n            __attribute__((cleanup(cleanup_request_handler_args))) =\n                    setup_request_handler_args(env.coap_ctx, EXCHANGE_ID(1));\n\n    const test_msg_t *req = COAP_MSG(GET, TOKEN(nth_token(0)), ACCEPT(123),\n                                     PAYLOAD(\"PlacLaduj\"));\n    const test_msg_t *res = COAP_MSG(CONTENT, TOKEN(nth_token(0)));\n\n    expect_sliced_recv(&env, req, req->payload_offset + 4);\n\n    expect_partial_content(&args, req->msg.content.payload, 4, ACTION_NONE,\n                           NULL);\n    ASSERT_OK(handle_incoming_packet(env.coap_ctx, handle_new_request, &args));\n\n    expect_send(&env, res);\n\n    expect_last_chunk(&args, req->msg.content.payload, 5, ACTION_NONE, NULL);\n    expect_cleanup(&args);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(handle_incoming_packet(env.coap_ctx, NULL, NULL));\n}\n\nAVS_UNIT_TEST(tcp_async_server, empty_token) {\n    test_env_t env __attribute__((cleanup(test_teardown))) = test_setup();\n    request_handler_args_t args\n            __attribute__((cleanup(cleanup_request_handler_args))) =\n                    setup_request_handler_args(env.coap_ctx, EXCHANGE_ID(1));\n\n    const test_msg_t *req = COAP_MSG(GET, PAYLOAD(\"PlacLaduj\"));\n    const test_msg_t *res = COAP_MSG(CONTENT);\n\n    expect_recv(&env, req);\n    expect_last_chunk(&args, req->msg.content.payload, sizeof(\"PlacLaduj\") - 1,\n                      ACTION_NONE, NULL);\n    expect_send(&env, res);\n    expect_cleanup(&args);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(handle_incoming_packet(env.coap_ctx, handle_new_request, &args));\n}\n\nAVS_UNIT_TEST(tcp_async_server,\n              cancel_exchange_after_receiving_first_chunk_of_request) {\n    test_env_t env __attribute__((cleanup(test_teardown))) = test_setup();\n    request_handler_args_t args\n            __attribute__((cleanup(cleanup_request_handler_args))) =\n                    setup_request_handler_args(env.coap_ctx, EXCHANGE_ID(1));\n\n    const test_msg_t *req =\n            COAP_MSG(GET, TOKEN(nth_token(1)), PAYLOAD(\"poprosze\"));\n    const test_msg_t *res =\n            COAP_MSG(INTERNAL_SERVER_ERROR, TOKEN(nth_token(1)));\n\n    avs_unit_mocksock_input(env.mocksock, req->data, req->size - 5);\n    expect_send(&env, res);\n\n    expect_partial_content(&args, req->msg.content.payload, 3, ACTION_CANCEL,\n                           NULL);\n    expect_cleanup(&args);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(handle_incoming_packet(env.coap_ctx, handle_new_request, &args));\n\n    // Handler shouldn't be called again.\n    avs_unit_mocksock_input(env.mocksock, req->data + req->size - 5, 5);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(handle_incoming_packet(env.coap_ctx, handle_new_request, &args));\n}\n\nAVS_UNIT_TEST(tcp_async_server,\n              setup_response_after_receiving_first_chunk_of_request) {\n    test_env_t env __attribute__((cleanup(test_teardown))) = test_setup();\n    request_handler_args_t args\n            __attribute__((cleanup(cleanup_request_handler_args))) =\n                    setup_request_handler_args(env.coap_ctx, EXCHANGE_ID(1));\n\n    const test_msg_t *req =\n            COAP_MSG(GET, TOKEN(nth_token(1)), PAYLOAD(\"poprosze\"));\n    const test_msg_t *res = COAP_MSG(VALID, TOKEN(nth_token(1)));\n\n    avs_unit_mocksock_input(env.mocksock, req->data, req->size - 5);\n    expect_send(&env, res);\n\n    expect_partial_content(&args, req->msg.content.payload, 3,\n                           ACTION_SETUP_VALID_RESPONSE, NULL);\n    expect_cleanup(&args);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(handle_incoming_packet(env.coap_ctx, handle_new_request, &args));\n\n    // Handler shouldn't be called again.\n    avs_unit_mocksock_input(env.mocksock, req->data + req->size - 5, 5);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(handle_incoming_packet(env.coap_ctx, handle_new_request, &args));\n}\n\nAVS_UNIT_TEST(tcp_async_server,\n              message_with_bad_options_and_then_valid_message) {\n    test_env_t env __attribute__((cleanup(test_teardown))) = test_setup();\n    request_handler_args_t args\n            __attribute__((cleanup(cleanup_request_handler_args))) =\n                    setup_request_handler_args(env.coap_ctx, EXCHANGE_ID(1));\n\n    const test_msg_t *requests[] = { COAP_MSG(GET, TOKEN(nth_token(0)),\n                                              ACCEPT(1), DUPLICATED_ACCEPT(2)),\n                                     COAP_MSG(GET, TOKEN(nth_token(1))) };\n\n    const test_msg_t *responses[] = { COAP_MSG(BAD_OPTION, TOKEN(nth_token(0))),\n                                      COAP_MSG(CONTENT, TOKEN(nth_token(1))) };\n\n    expect_recv(&env, requests[0]);\n    expect_send(&env, responses[0]);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(handle_incoming_packet(env.coap_ctx, handle_new_request, &args));\n\n    expect_recv(&env, requests[1]);\n    expect_send(&env, responses[1]);\n    expect_last_chunk(&args, NULL, 0, ACTION_NONE, NULL);\n    expect_cleanup(&args);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(handle_incoming_packet(env.coap_ctx, handle_new_request, &args));\n}\n\nAVS_UNIT_TEST(tcp_async_server, too_long_option_and_then_valid_message) {\n    test_env_t env __attribute__((cleanup(test_teardown))) = test_setup();\n    request_handler_args_t args\n            __attribute__((cleanup(cleanup_request_handler_args))) =\n                    setup_request_handler_args(env.coap_ctx, EXCHANGE_ID(1));\n\n    const test_msg_t *requests[] = {\n        COAP_MSG(GET, TOKEN(nth_token(0)),\n                 PATH(\"ilecietrzebacenictentylkosiedowie \")),\n        COAP_MSG(GET, TOKEN(nth_token(1)))\n    };\n\n    const test_msg_t *responses[] = { COAP_MSG(INTERNAL_SERVER_ERROR,\n                                               TOKEN(nth_token(0))\n#    ifdef WITH_AVS_COAP_DIAGNOSTIC_MESSAGES\n                                                       ,\n                                               PAYLOAD(\"options too big\")\n#    endif // WITH_AVS_COAP_DIAGNOSTIC_MESSAGES\n                                                       ),\n                                      COAP_MSG(CONTENT, TOKEN(nth_token(1))) };\n\n    expect_recv(&env, requests[0]);\n    expect_send(&env, responses[0]);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(handle_incoming_packet(env.coap_ctx, NULL, NULL));\n\n    expect_last_chunk(&args, NULL, 0, ACTION_NONE, NULL);\n    expect_has_buffered_data_check(&env, true);\n    expect_cleanup(&args);\n    expect_recv(&env, requests[1]);\n    expect_send(&env, responses[1]);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(handle_incoming_packet(env.coap_ctx, handle_new_request, &args));\n}\n\nAVS_UNIT_TEST(tcp_async_server, malformed_options) {\n    test_env_t env __attribute__((cleanup(test_teardown))) = test_setup();\n\n    // 0.01 Get, 1 byte of truncated option.\n    // Such message should be handled and Bad Option response should be sent.\n    uint8_t buf[] = { 0x10, 0x01, 0x11 };\n    avs_unit_mocksock_input(env.mocksock, buf, sizeof(buf));\n    expect_send(&env, COAP_MSG(BAD_OPTION));\n    expect_has_buffered_data_check(&env, false);\n\n    ASSERT_OK(handle_incoming_packet(env.coap_ctx, NULL, NULL));\n}\n\nAVS_UNIT_TEST(tcp_async_server, message_sliced_after_valid_option) {\n    test_env_t env __attribute__((cleanup(test_teardown))) = test_setup();\n\n    uint8_t buf[] = {\n        0x20, // 2 bytes of options + payload\n        0x41, // 4.01 Created\n        0x10, // If-Match empty option\n        0x10, // If-Match empty option\n        0x20  // first byte of the next message\n    };\n    const size_t first_input_size = 3;\n    avs_unit_mocksock_input(env.mocksock, buf, first_input_size);\n    expect_has_buffered_data_check(&env, true);\n    avs_unit_mocksock_input(env.mocksock, buf + first_input_size,\n                            sizeof(buf) - first_input_size);\n    expect_has_buffered_data_check(&env, true);\n\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(handle_incoming_packet(env.coap_ctx, NULL, NULL));\n}\n\nAVS_UNIT_TEST(tcp_async_server, receive_ping_with_payload) {\n#    define PAYLOAD_DATA \\\n        \"abcdefgh12345678abcdefgh12345678abcdefgh12345678abcdefgh12345678\"\n\n    test_env_t env __attribute__((cleanup(test_teardown))) = test_setup();\n\n    const test_msg_t *ping = COAP_MSG(PING, PAYLOAD(PAYLOAD_DATA));\n    const test_msg_t *pong = COAP_MSG(PONG, CUSTODY);\n\n    expect_recv(&env, ping);\n    expect_has_buffered_data_check(&env, true);\n    expect_has_buffered_data_check(&env, true);\n    expect_send(&env, pong);\n\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(handle_incoming_packet(env.coap_ctx, NULL, NULL));\n}\n\nAVS_UNIT_TEST(tcp_async_server, send_request_in_request_handler) {\n    test_env_t env __attribute__((cleanup(test_teardown))) = test_setup();\n\n    request_handler_args_t args\n            __attribute__((cleanup(cleanup_request_handler_args))) =\n                    setup_request_handler_args(env.coap_ctx, EXCHANGE_ID(1));\n\n    const test_msg_t *incoming_request = COAP_MSG(GET, MAKE_TOKEN(\"A token\"));\n    const test_msg_t *outgoing_response =\n            COAP_MSG(CONTENT, MAKE_TOKEN(\"A token\"));\n\n    const test_msg_t *outgoing_request = COAP_MSG(GET, TOKEN(nth_token(1)));\n    const test_msg_t *incoming_response =\n            COAP_MSG(CONTENT, TOKEN(nth_token(1)));\n\n    expect_recv(&env, incoming_request);\n    expect_last_chunk(&args, NULL, 0, ACTION_SEND_REQUEST, NULL);\n\n    expect_send(&env, outgoing_request);\n    expect_send(&env, outgoing_response);\n\n    expect_cleanup(&args);\n\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(handle_incoming_packet(env.coap_ctx, handle_new_request, &args));\n\n    expect_recv(&env, incoming_response);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(handle_incoming_packet(env.coap_ctx, NULL, NULL));\n}\n\n#    ifdef WITH_AVS_COAP_BLOCK\n\nAVS_UNIT_TEST(tcp_async_server, incoming_small_bert1_request) {\n    test_env_t env __attribute__((cleanup(test_teardown))) = test_setup();\n    request_handler_args_t args\n            __attribute__((cleanup(cleanup_request_handler_args))) =\n                    setup_request_handler_args(env.coap_ctx, EXCHANGE_ID(1));\n\n    const test_msg_t *req =\n            COAP_MSG(GET, TOKEN(nth_token(0)), BERT1_REQ(0, 2048, DATA_16B));\n    const test_msg_t *res =\n            COAP_MSG(CONTENT, TOKEN(nth_token(0)), BERT1_RES(0, false));\n\n    expect_recv(&env, req);\n    expect_send(&env, res);\n    expect_last_chunk(&args, DATA_16B, 16, false, NULL);\n    expect_cleanup(&args);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(handle_incoming_packet(env.coap_ctx, handle_new_request, &args));\n}\n\nAVS_UNIT_TEST(tcp_async_server, incoming_big_bert1_request) {\n#        define REQUEST_PAYLOAD DATA_2KB \"?\"\n\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_with_custom_sized_buffers(2048, 2048);\n    request_handler_args_t args\n            __attribute__((cleanup(cleanup_request_handler_args))) =\n                    setup_request_handler_args(env.coap_ctx, EXCHANGE_ID(1));\n\n    const test_msg_t *requests[] = {\n        COAP_MSG(GET, TOKEN(nth_token(0)), BERT1_REQ(0, 2048, REQUEST_PAYLOAD)),\n        COAP_MSG(GET, TOKEN(nth_token(1)), BERT1_REQ(2, 2048, REQUEST_PAYLOAD))\n    };\n\n    const test_msg_t *responses[] = {\n        COAP_MSG(CONTINUE, TOKEN(nth_token(0)), BERT1_RES(0, true)),\n        COAP_MSG(CONTENT, TOKEN(nth_token(1)), BERT1_RES(2, false))\n    };\n\n    expect_recv(&env, requests[0]);\n    expect_send(&env, responses[0]);\n    expect_partial_content(&args, REQUEST_PAYLOAD, 28, false, NULL);\n    expect_has_buffered_data_check(&env, true);\n    expect_partial_content(&args, REQUEST_PAYLOAD, 2020, false, NULL);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(handle_incoming_packet(env.coap_ctx, handle_new_request, &args));\n\n    expect_recv(&env, requests[1]);\n    expect_send(&env, responses[1]);\n    expect_last_chunk(&args, REQUEST_PAYLOAD, 1, false, NULL);\n    expect_cleanup(&args);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(handle_incoming_packet(env.coap_ctx, NULL, NULL));\n\n#        undef REQUEST_PAYLOAD\n}\n\nAVS_UNIT_TEST(tcp_async_server, incoming_bigger_bert1_request) {\n#        define REQUEST_PAYLOAD DATA_2KB DATA_2KB \"?\"\n\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_with_custom_sized_buffers(2048, 2048);\n    request_handler_args_t args\n            __attribute__((cleanup(cleanup_request_handler_args))) =\n                    setup_request_handler_args(env.coap_ctx, EXCHANGE_ID(1));\n\n    const test_msg_t *requests[] = {\n        COAP_MSG(GET, TOKEN(nth_token(0)), BERT1_REQ(0, 2048, REQUEST_PAYLOAD)),\n        COAP_MSG(GET, TOKEN(nth_token(1)), BERT1_REQ(2, 2048, REQUEST_PAYLOAD)),\n        COAP_MSG(GET, TOKEN(nth_token(2)), BERT1_REQ(4, 2048, REQUEST_PAYLOAD))\n    };\n\n    const test_msg_t *responses[] = {\n        COAP_MSG(CONTINUE, TOKEN(nth_token(0)), BERT1_RES(0, true)),\n        COAP_MSG(CONTINUE, TOKEN(nth_token(1)), BERT1_RES(2, true)),\n        COAP_MSG(CONTENT, TOKEN(nth_token(2)), BERT1_RES(4, false))\n    };\n\n    expect_recv(&env, requests[0]);\n    expect_send(&env, responses[0]);\n    expect_partial_content(&args, REQUEST_PAYLOAD, 28, false, NULL);\n    expect_has_buffered_data_check(&env, true);\n    expect_partial_content(&args, REQUEST_PAYLOAD, 2020, false, NULL);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(handle_incoming_packet(env.coap_ctx, handle_new_request, &args));\n\n    expect_recv(&env, requests[1]);\n    expect_send(&env, responses[1]);\n    expect_partial_content(&args, REQUEST_PAYLOAD, 28, false, NULL);\n    expect_has_buffered_data_check(&env, true);\n    expect_partial_content(&args, REQUEST_PAYLOAD, 2020, false, NULL);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(handle_incoming_packet(env.coap_ctx, NULL, NULL));\n\n    expect_recv(&env, requests[2]);\n    expect_send(&env, responses[2]);\n    expect_last_chunk(&args, REQUEST_PAYLOAD, 1, false, NULL);\n    expect_cleanup(&args);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(handle_incoming_packet(env.coap_ctx, NULL, NULL));\n\n#        undef REQUEST_PAYLOAD\n}\n\nAVS_UNIT_TEST(tcp_async_server, incoming_bert2_request) {\n#        define RESPONSE_PAYLOAD DATA_1KB DATA_1KB \"?\"\n\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_with_custom_sized_buffers(2048, 2048);\n    request_handler_args_t args\n            __attribute__((cleanup(cleanup_request_handler_args))) =\n                    setup_request_handler_args(env.coap_ctx, EXCHANGE_ID(1));\n\n    const test_msg_t *requests[] = {\n        COAP_MSG(GET, TOKEN(nth_token(0)), BERT2_REQ(0)),\n        COAP_MSG(GET, TOKEN(nth_token(1)), BERT2_REQ(1)),\n        COAP_MSG(GET, TOKEN(nth_token(2)), BERT2_REQ(2))\n    };\n\n    const test_msg_t *responses[] = {\n        COAP_MSG(CONTENT, TOKEN(nth_token(0)),\n                 BERT2_RES(0, 1024, RESPONSE_PAYLOAD)),\n        COAP_MSG(CONTENT, TOKEN(nth_token(1)),\n                 BERT2_RES(1, 1024, RESPONSE_PAYLOAD)),\n        COAP_MSG(CONTENT, TOKEN(nth_token(2)),\n                 BERT2_RES(2, 1024, RESPONSE_PAYLOAD))\n    };\n\n    expect_recv(&env, requests[0]);\n    expect_send(&env, responses[0]);\n    expect_last_chunk(&args, NULL, 0, false,\n                      &(payload_buf_t) {\n                          .data = RESPONSE_PAYLOAD,\n                          .size = sizeof(RESPONSE_PAYLOAD) - 1\n                      });\n    expect_cleanup(&args);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(handle_incoming_packet(env.coap_ctx, handle_new_request, &args));\n\n    expect_recv(&env, requests[1]);\n    expect_send(&env, responses[1]);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(handle_incoming_packet(env.coap_ctx, NULL, NULL));\n\n    expect_recv(&env, requests[2]);\n    expect_send(&env, responses[2]);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(handle_incoming_packet(env.coap_ctx, NULL, NULL));\n\n#        undef RESPONSE_PAYLOAD\n}\n\nAVS_UNIT_TEST(tcp_async_server, sliced_block_request) {\n    test_env_t env __attribute__((cleanup(test_teardown))) = test_setup();\n    request_handler_args_t args\n            __attribute__((cleanup(cleanup_request_handler_args))) =\n                    setup_request_handler_args(env.coap_ctx, EXCHANGE_ID(1));\n\n    const test_msg_t *req =\n            COAP_MSG(GET, TOKEN(nth_token(0)), BLOCK1_REQ(0, 16, DATA_16B));\n    const test_msg_t *res =\n            COAP_MSG(CONTENT, BLOCK1_RES(0, 16, false), TOKEN(nth_token(0)));\n\n    expect_sliced_recv(&env, req, req->payload_offset + 11);\n    expect_partial_content(&args, req->msg.content.payload, 11, ACTION_NONE,\n                           NULL);\n    ASSERT_OK(handle_incoming_packet(env.coap_ctx, handle_new_request, &args));\n\n    expect_send(&env, res);\n    expect_last_chunk(&args, req->msg.content.payload, 5, false, NULL);\n    expect_cleanup(&args);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(handle_incoming_packet(env.coap_ctx, NULL, NULL));\n}\n\nAVS_UNIT_TEST(tcp_async_server, incoming_request_for_big_payload) {\n#        define RESPONSE_PAYLOAD DATA_1KB DATA_1KB \"?\"\n\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_with_custom_sized_buffers(2048, 2048);\n    request_handler_args_t args\n            __attribute__((cleanup(cleanup_request_handler_args))) =\n                    setup_request_handler_args(env.coap_ctx, EXCHANGE_ID(1));\n\n    const test_msg_t *requests[] = {\n        COAP_MSG(GET, TOKEN(nth_token(0))),\n        COAP_MSG(GET, TOKEN(nth_token(1)), BLOCK2_REQ(1, 1024)),\n        COAP_MSG(GET, TOKEN(nth_token(2)), BLOCK2_REQ(2, 1024))\n    };\n\n    const test_msg_t *responses[] = {\n        COAP_MSG(CONTENT, TOKEN(nth_token(0)),\n                 BLOCK2_RES(0, 1024, RESPONSE_PAYLOAD)),\n        COAP_MSG(CONTENT, TOKEN(nth_token(1)),\n                 BLOCK2_RES(1, 1024, RESPONSE_PAYLOAD)),\n        COAP_MSG(CONTENT, TOKEN(nth_token(2)),\n                 BLOCK2_RES(2, 1024, RESPONSE_PAYLOAD))\n    };\n\n    expect_recv(&env, requests[0]);\n    expect_send(&env, responses[0]);\n    expect_last_chunk(&args, NULL, 0, false,\n                      &(payload_buf_t) {\n                          .data = RESPONSE_PAYLOAD,\n                          .size = sizeof(RESPONSE_PAYLOAD) - 1\n                      });\n    expect_cleanup(&args);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(handle_incoming_packet(env.coap_ctx, handle_new_request, &args));\n\n    expect_recv(&env, requests[1]);\n    expect_send(&env, responses[1]);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(handle_incoming_packet(env.coap_ctx, NULL, NULL));\n\n    expect_recv(&env, requests[2]);\n    expect_send(&env, responses[2]);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(handle_incoming_packet(env.coap_ctx, NULL, NULL));\n\n#        undef RESPONSE_PAYLOAD\n}\n\nAVS_UNIT_TEST(tcp_async_server, incoming_request_block_response) {\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_with_custom_sized_buffers(2048, 2048);\n    request_handler_args_t args\n            __attribute__((cleanup(cleanup_request_handler_args))) =\n                    setup_request_handler_args(env.coap_ctx, EXCHANGE_ID(1));\n\n#        define REQUEST_PAYLOAD DATA_2KB DATA_1KB \"?\"\n#        define RESPONSE_PAYLOAD \"!\" DATA_1KB\n\n    const test_msg_t *requests[] = {\n        COAP_MSG(GET, TOKEN(nth_token(0)), BERT1_REQ(0, 2048, REQUEST_PAYLOAD)),\n        COAP_MSG(GET, TOKEN(nth_token(1)), BERT1_REQ(2, 2048, REQUEST_PAYLOAD)),\n        COAP_MSG(GET, TOKEN(nth_token(2)), BLOCK2_REQ(1, 1024))\n    };\n    const test_msg_t *responses[] = {\n        COAP_MSG(CONTINUE, TOKEN(nth_token(0)), BERT1_RES(0, true)),\n        COAP_MSG(CONTENT, TOKEN(nth_token(1)),\n                 BERT1_AND_BLOCK2_RES(2, 1024, RESPONSE_PAYLOAD)),\n        COAP_MSG(CONTENT, TOKEN(nth_token(2)),\n                 BLOCK2_RES(1, 1024, RESPONSE_PAYLOAD))\n    };\n\n    expect_recv(&env, requests[0]);\n    expect_send(&env, responses[0]);\n    expect_partial_content(&args, REQUEST_PAYLOAD, 28, false, NULL);\n    expect_has_buffered_data_check(&env, true);\n    expect_partial_content(&args, REQUEST_PAYLOAD, 2020, false, NULL);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(handle_incoming_packet(env.coap_ctx, handle_new_request, &args));\n\n    expect_recv(&env, requests[1]);\n    expect_send(&env, responses[1]);\n    expect_partial_content(&args, REQUEST_PAYLOAD, 28, false, NULL);\n    expect_has_buffered_data_check(&env, true);\n    expect_last_chunk(&args, REQUEST_PAYLOAD, 997, false,\n                      &(payload_buf_t) {\n                          .data = RESPONSE_PAYLOAD,\n                          .size = sizeof(RESPONSE_PAYLOAD) - 1\n                      });\n    expect_cleanup(&args);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(handle_incoming_packet(env.coap_ctx, NULL, NULL));\n\n    expect_recv(&env, requests[2]);\n    expect_send(&env, responses[2]);\n\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(handle_incoming_packet(env.coap_ctx, NULL, NULL));\n\n#        undef REQUEST_PAYLOAD\n#        undef RESPONSE_PAYLOAD\n}\n\nAVS_UNIT_TEST(tcp_async_server, big_error_after_first_block) {\n#        define RESPONSE_PAYLOAD DATA_2KB\n    test_env_t env = test_setup_with_custom_sized_buffers(2048, 2048);\n    request_handler_args_t args =\n            setup_request_handler_args(env.coap_ctx, EXCHANGE_ID(1));\n\n    const test_msg_t *requests[] = {\n        COAP_MSG(GET, TOKEN(nth_token(0)), BLOCK1_REQ(0, 16, DATA_32B)),\n        COAP_MSG(GET, TOKEN(nth_token(1)), BLOCK2_REQ(1, 1024))\n    };\n\n    const test_msg_t *responses[] = {\n        COAP_MSG(NOT_FOUND, TOKEN(nth_token(0)),\n                 BLOCK1_AND_2_RES(0, 16, 1024, RESPONSE_PAYLOAD)),\n        COAP_MSG(NOT_FOUND, TOKEN(nth_token(1)),\n                 BLOCK2_RES(1, 1024, RESPONSE_PAYLOAD))\n    };\n\n    expect_recv(&env, requests[0]);\n    expect_send(&env, responses[0]);\n    expect_has_buffered_data_check(&env, true);\n    expect_recv(&env, requests[1]);\n    expect_send(&env, responses[1]);\n    expect_has_buffered_data_check(&env, false);\n\n    expect_partial_content(&args, DATA_32B, 16, ACTION_SETUP_NOT_FOUND_RESPONSE,\n                           &(payload_buf_t) {\n                               .data = RESPONSE_PAYLOAD,\n                               .size = sizeof(RESPONSE_PAYLOAD) - 1\n                           });\n    expect_cleanup(&args);\n    ASSERT_OK(handle_incoming_packet(env.coap_ctx, handle_new_request, &args));\n\n    test_teardown(&env);\n    cleanup_request_handler_args(&args);\n#        undef RESPONSE_PAYLOAD\n}\n\nAVS_UNIT_TEST(tcp_async_server, block1_req_after_sent_response) {\n#        define RESPONSE_PAYLOAD DATA_2KB\n    test_env_t env = test_setup_with_custom_sized_buffers(2048, 2048);\n    request_handler_args_t args1 =\n            setup_request_handler_args(env.coap_ctx, EXCHANGE_ID(1));\n    request_handler_args_t args2 =\n            setup_request_handler_args(env.coap_ctx, EXCHANGE_ID(2));\n\n    const test_msg_t *requests[] = {\n        COAP_MSG(GET, TOKEN(nth_token(0)), BLOCK1_REQ(0, 16, DATA_64B)),\n        COAP_MSG(GET, TOKEN(nth_token(1)), BLOCK1_REQ(1, 16, DATA_64B)),\n        COAP_MSG(GET, TOKEN(nth_token(2)), BLOCK1_REQ(2, 16, DATA_64B))\n    };\n\n    const test_msg_t *responses[] = {\n        COAP_MSG(CONTINUE, TOKEN(nth_token(0)), BLOCK1_RES(0, 16, true)),\n        COAP_MSG(NOT_FOUND, TOKEN(nth_token(1)),\n                 BLOCK1_AND_2_RES(1, 16, 1024, RESPONSE_PAYLOAD)),\n        COAP_MSG(CONTINUE, TOKEN(nth_token(2)), BLOCK1_RES(2, 16, true))\n    };\n\n    expect_recv(&env, requests[0]);\n    expect_send(&env, responses[0]);\n    expect_has_buffered_data_check(&env, false);\n    expect_partial_content(&args1, DATA_64B, 16, ACTION_NONE, NULL);\n    ASSERT_OK(handle_incoming_packet(env.coap_ctx, handle_new_request, &args1));\n\n    expect_recv(&env, requests[1]);\n    expect_send(&env, responses[1]);\n    expect_has_buffered_data_check(&env, false);\n    expect_partial_content(&args1, DATA_64B, 16,\n                           ACTION_SETUP_NOT_FOUND_RESPONSE,\n                           &(payload_buf_t) {\n                               .data = RESPONSE_PAYLOAD,\n                               .size = sizeof(RESPONSE_PAYLOAD) - 1\n                           });\n    ASSERT_OK(handle_incoming_packet(env.coap_ctx, handle_new_request, &args1));\n\n    expect_recv(&env, requests[2]);\n    expect_send(&env, responses[2]);\n    expect_has_buffered_data_check(&env, false);\n    args2.next_offset = 32;\n    expect_partial_content(&args2, DATA_64B, 16, ACTION_NONE, NULL);\n    ASSERT_OK(handle_incoming_packet(env.coap_ctx, handle_new_request, &args2));\n\n    expect_cleanup(&args1);\n    expect_cleanup(&args2);\n    test_teardown(&env);\n    cleanup_request_handler_args(&args1);\n    cleanup_request_handler_args(&args2);\n#        undef RESPONSE_PAYLOAD\n}\n\nAVS_UNIT_TEST(tcp_async_server, valid_response_after_first_block) {\n    test_env_t env = test_setup();\n    request_handler_args_t args_req1 =\n            setup_request_handler_args(env.coap_ctx, EXCHANGE_ID(1));\n    request_handler_args_t args_req2 =\n            setup_request_handler_args(env.coap_ctx, EXCHANGE_ID(2));\n\n    const test_msg_t *requests[] = {\n        COAP_MSG(GET, TOKEN(nth_token(0)), BLOCK1_REQ(0, 16, DATA_32B)),\n        COAP_MSG(GET, TOKEN(nth_token(1)), BLOCK1_REQ(1, 16, DATA_32B))\n    };\n\n    const test_msg_t *responses[] = {\n        COAP_MSG(VALID, BLOCK1_RES(0, 16, false), TOKEN(nth_token(0))),\n        COAP_MSG(VALID, BLOCK1_RES(1, 16, false), TOKEN(nth_token(1)))\n    };\n\n    expect_recv(&env, requests[0]);\n    expect_send(&env, responses[0]);\n    expect_has_buffered_data_check(&env, false);\n    expect_partial_content(&args_req1, DATA_32B, 16,\n                           ACTION_SETUP_VALID_RESPONSE, NULL);\n    expect_cleanup(&args_req1);\n    ASSERT_OK(handle_incoming_packet(env.coap_ctx, handle_new_request,\n                                     &args_req1));\n\n    expect_recv(&env, requests[1]);\n    expect_send(&env, responses[1]);\n    // New exchange, starting from block no 1.\n    args_req2.next_offset = 16;\n    expect_last_chunk(&args_req2, DATA_32B, 16, ACTION_SETUP_VALID_RESPONSE,\n                      NULL);\n    expect_cleanup(&args_req2);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(handle_incoming_packet(env.coap_ctx, handle_new_request,\n                                     &args_req2));\n\n    cleanup_request_handler_args(&args_req1);\n    cleanup_request_handler_args(&args_req2);\n    test_teardown(&env);\n}\n\nAVS_UNIT_TEST(tcp_async_server, valid_response_after_second_block) {\n#        define REQUEST_PAYLOAD DATA_32B DATA_16B\n    test_env_t env = test_setup();\n    request_handler_args_t args_req1 =\n            setup_request_handler_args(env.coap_ctx, EXCHANGE_ID(1));\n    request_handler_args_t args_req2 =\n            setup_request_handler_args(env.coap_ctx, EXCHANGE_ID(2));\n\n    const test_msg_t *requests[] = {\n        COAP_MSG(GET, TOKEN(nth_token(0)), BLOCK1_REQ(0, 16, REQUEST_PAYLOAD)),\n        COAP_MSG(GET, TOKEN(nth_token(1)), BLOCK1_REQ(1, 16, REQUEST_PAYLOAD)),\n        COAP_MSG(GET, TOKEN(nth_token(2)), BLOCK1_REQ(2, 16, REQUEST_PAYLOAD)),\n    };\n\n    const test_msg_t *responses[] = {\n        COAP_MSG(CONTINUE, BLOCK1_RES(0, 16, true), TOKEN(nth_token(0))),\n        COAP_MSG(VALID, BLOCK1_RES(1, 16, false), TOKEN(nth_token(1))),\n        COAP_MSG(VALID, BLOCK1_RES(2, 16, false), TOKEN(nth_token(2)))\n    };\n\n    expect_recv(&env, requests[0]);\n    expect_send(&env, responses[0]);\n    expect_partial_content(&args_req1, REQUEST_PAYLOAD, 16, ACTION_NONE, NULL);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(handle_incoming_packet(env.coap_ctx, handle_new_request,\n                                     &args_req1));\n\n    expect_recv(&env, requests[1]);\n    expect_send(&env, responses[1]);\n    expect_partial_content(&args_req1, REQUEST_PAYLOAD, 16,\n                           ACTION_SETUP_VALID_RESPONSE, NULL);\n    expect_cleanup(&args_req1);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(handle_incoming_packet(env.coap_ctx, NULL, NULL));\n\n    expect_recv(&env, requests[2]);\n    expect_send(&env, responses[2]);\n    // New exchange, starting from block no 2.\n    args_req2.next_offset = 32;\n    expect_last_chunk(&args_req2, REQUEST_PAYLOAD, 16,\n                      ACTION_SETUP_VALID_RESPONSE, NULL);\n    expect_cleanup(&args_req2);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(handle_incoming_packet(env.coap_ctx, handle_new_request,\n                                     &args_req2));\n\n    cleanup_request_handler_args(&args_req1);\n    cleanup_request_handler_args(&args_req2);\n    test_teardown(&env);\n#        undef REQUEST_PAYLOAD\n}\n\nAVS_UNIT_TEST(tcp_async_server, error_after_first_block) {\n    test_env_t env = test_setup();\n    request_handler_args_t args =\n            setup_request_handler_args(env.coap_ctx, EXCHANGE_ID(1));\n\n    const test_msg_t *request =\n            COAP_MSG(GET, TOKEN(nth_token(0)), BLOCK1_REQ(0, 16, DATA_32B));\n    const test_msg_t *response =\n            COAP_MSG(NOT_FOUND, TOKEN(nth_token(0)), BLOCK1_RES(0, 16, false));\n\n    expect_recv(&env, request);\n    expect_send(&env, response);\n\n    expect_partial_content(&args, DATA_32B, 16, ACTION_SETUP_NOT_FOUND_RESPONSE,\n                           NULL);\n    expect_cleanup(&args);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(handle_incoming_packet(env.coap_ctx, handle_new_request, &args));\n\n    cleanup_request_handler_args(&args);\n    test_teardown(&env);\n}\n\nAVS_UNIT_TEST(tcp_async_server, next_block_when_it_is_not_expected) {\n    test_env_t env = test_setup();\n    request_handler_args_t args_req1 =\n            setup_request_handler_args(env.coap_ctx, EXCHANGE_ID(1));\n    request_handler_args_t args_req2 =\n            setup_request_handler_args(env.coap_ctx, EXCHANGE_ID(2));\n\n    const test_msg_t *requests[] = {\n        COAP_MSG(GET, TOKEN(nth_token(0)), BLOCK1_REQ(0, 16, DATA_32B)),\n        COAP_MSG(GET, TOKEN(nth_token(1)), BLOCK1_REQ(1, 16, DATA_32B))\n    };\n\n    const test_msg_t *responses[] = {\n        COAP_MSG(NOT_FOUND, TOKEN(nth_token(0)), BLOCK1_RES(0, 16, false)),\n        COAP_MSG(CONTENT, TOKEN(nth_token(1)), BLOCK1_RES(1, 16, false))\n    };\n\n    expect_recv(&env, requests[0]);\n    expect_send(&env, responses[0]);\n    expect_partial_content(&args_req1, DATA_32B, 16,\n                           ACTION_SETUP_NOT_FOUND_RESPONSE, NULL);\n    expect_cleanup(&args_req1);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(handle_incoming_packet(env.coap_ctx, handle_new_request,\n                                     &args_req1));\n\n    expect_recv(&env, requests[1]);\n    expect_send(&env, responses[1]);\n    args_req2.next_offset = 16;\n    expect_last_chunk(&args_req2, DATA_32B, 16, ACTION_NONE, NULL);\n    expect_cleanup(&args_req2);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(handle_incoming_packet(env.coap_ctx, handle_new_request,\n                                     &args_req2));\n    cleanup_request_handler_args(&args_req2);\n\n    test_teardown(&env);\n    cleanup_request_handler_args(&args_req1);\n}\n\nAVS_UNIT_TEST(tcp_async_server, incomplete_request) {\n    test_env_t env __attribute__((cleanup(test_teardown))) = test_setup();\n    request_handler_args_t args\n            __attribute__((cleanup(cleanup_request_handler_args))) =\n                    setup_request_handler_args(env.coap_ctx, EXCHANGE_ID(1));\n\n    const test_msg_t *request =\n            COAP_MSG(GET, TOKEN(nth_token(0)), BLOCK1_REQ(1, 16, DATA_32B));\n    const test_msg_t *response =\n            COAP_MSG(CONTENT, TOKEN(nth_token(0)), BLOCK1_RES(1, 16, false));\n\n    expect_recv(&env, request);\n    expect_send(&env, response);\n\n    // Faked counter, incoming message payload offset will be 16.\n    args.next_offset = 16;\n    expect_last_chunk(&args, DATA_32B, 16, ACTION_NONE, NULL);\n    expect_cleanup(&args);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(handle_incoming_packet(env.coap_ctx, handle_new_request, &args));\n}\n\nAVS_UNIT_TEST(tcp_async_server, bad_order_of_blocks) {\n    test_env_t env = test_setup();\n    request_handler_args_t args_req1 =\n            setup_request_handler_args(env.coap_ctx, EXCHANGE_ID(1));\n    request_handler_args_t args_req2 =\n            setup_request_handler_args(env.coap_ctx, EXCHANGE_ID(2));\n\n    const test_msg_t *requests[] = {\n        COAP_MSG(GET, TOKEN(nth_token(1)), BLOCK1_REQ(1, 16, DATA_32B)),\n        COAP_MSG(GET, TOKEN(nth_token(0)), BLOCK1_REQ(0, 16, DATA_32B))\n    };\n\n    const test_msg_t *responses[] = {\n        COAP_MSG(CONTENT, TOKEN(nth_token(1)), BLOCK1_RES(1, 16, false)),\n        COAP_MSG(CONTINUE, TOKEN(nth_token(0)), BLOCK1_RES(0, 16, true))\n    };\n\n    expect_recv(&env, requests[0]);\n    expect_send(&env, responses[0]);\n    // Faked counter, incoming message payload offset will be 16.\n    args_req1.next_offset = 16;\n    expect_last_chunk(&args_req1, DATA_32B, 16, ACTION_NONE, NULL);\n    expect_cleanup(&args_req1);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(handle_incoming_packet(env.coap_ctx, handle_new_request,\n                                     &args_req1));\n    cleanup_request_handler_args(&args_req1);\n\n    expect_recv(&env, requests[1]);\n    expect_send(&env, responses[1]);\n    expect_partial_content(&args_req2, DATA_32B, 16, ACTION_NONE, NULL);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(handle_incoming_packet(env.coap_ctx, handle_new_request,\n                                     &args_req2));\n    expect_cleanup(&args_req2);\n\n    test_teardown(&env);\n    cleanup_request_handler_args(&args_req2);\n}\n\nAVS_UNIT_TEST(tcp_async_server, repeated_non_repeatable_critical_option) {\n    test_env_t env __attribute__((cleanup(test_teardown))) = test_setup();\n\n    const test_msg_t *request =\n            COAP_MSG(GET, TOKEN(nth_token(0)), ACCEPT(1), DUPLICATED_ACCEPT(2));\n    const test_msg_t *response = COAP_MSG(BAD_OPTION, TOKEN(nth_token(0)));\n\n    expect_recv(&env, request);\n    expect_send(&env, response);\n\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(handle_incoming_packet(env.coap_ctx, NULL, NULL));\n}\n\n#    endif // WITH_AVS_COAP_BLOCK\n\n#    ifdef WITH_AVS_COAP_OBSERVE\n\n// Not specified in RFC 7252 and RFC 7641, but specified in RFC 8613. Another\n// request with the same token shouldn't affect already registered observation\n// with the same token.\nAVS_UNIT_TEST(tcp_async_server, request_with_the_same_token_as_observe_token) {\n#        define NOTIFY_PAYLOAD \"Notifaj\"\n    test_env_t env __attribute__((cleanup(test_teardown))) = test_setup();\n    request_handler_args_t args1\n            __attribute__((cleanup(cleanup_request_handler_args))) =\n                    setup_request_handler_args(env.coap_ctx, EXCHANGE_ID(1));\n    request_handler_args_t args2\n            __attribute__((cleanup(cleanup_request_handler_args))) =\n                    setup_request_handler_args(env.coap_ctx, EXCHANGE_ID(2));\n\n    const test_msg_t *requests[] = { COAP_MSG(GET, MAKE_TOKEN(\"1234\"),\n                                              OBSERVE(0)),\n                                     COAP_MSG(GET, MAKE_TOKEN(\"1234\")) };\n\n    const test_msg_t *responses[] = {\n        COAP_MSG(CONTENT, MAKE_TOKEN(\"1234\"), OBSERVE(0)),\n        COAP_MSG(CONTENT, MAKE_TOKEN(\"1234\")),\n        COAP_MSG(CONTENT, MAKE_TOKEN(\"1234\"), OBSERVE(0),\n                 PAYLOAD(NOTIFY_PAYLOAD)),\n    };\n\n    // Request with Observe option\n    expect_recv(&env, requests[0]);\n    expect_last_chunk(&args1, NULL, 0, ACTION_ACCEPT_OBSERVE, NULL);\n    expect_send(&env, responses[0]);\n    expect_cleanup(&args1);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(handle_incoming_packet(env.coap_ctx, handle_new_request, &args1));\n\n    // Request without Observe option and the same token\n    expect_recv(&env, requests[1]);\n    expect_last_chunk(&args2, NULL, 0, ACTION_NONE, NULL);\n    expect_send(&env, responses[1]);\n    expect_cleanup(&args2);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(handle_incoming_packet(env.coap_ctx, handle_new_request, &args2));\n\n    avs_coap_observe_id_t observe_id = {\n        .token = requests[0]->msg.content.token\n    };\n\n    payload_buf_t test_payload = {\n        .data = NOTIFY_PAYLOAD,\n        .size = sizeof(NOTIFY_PAYLOAD) - 1\n    };\n\n    expect_send(&env, responses[2]);\n\n    avs_coap_exchange_id_t id;\n    ASSERT_OK(avs_coap_notify_async(\n            env.coap_ctx, &id, observe_id, &responses[1]->response_header,\n            AVS_COAP_NOTIFY_PREFER_NON_CONFIRMABLE, test_payload_writer,\n            &test_payload, NULL, NULL));\n    ASSERT_FALSE(avs_coap_exchange_id_valid(id));\n\n#        undef NOTIFY_PAYLOAD\n}\n\n#    endif // WITH_AVS_COAP_OBSERVE\n\n#endif // defined(AVS_UNIT_TESTING) && defined(WITH_AVS_COAP_TCP)\n"
  },
  {
    "path": "deps/avs_coap/tests/tcp/csm.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <avs_coap_init.h>\n\n#if defined(AVS_UNIT_TESTING) && defined(WITH_AVS_COAP_TCP)\n\n#    define MODULE_NAME test\n#    include <avs_coap_x_log_config.h>\n\n#    include \"./helper_functions.h\"\n\ntypedef struct {\n    avs_coap_send_result_t result;\n    avs_error_t err;\n    bool has_response;\n    avs_coap_borrowed_msg_t response;\n} test_handler_expected_response_t;\n\ntypedef AVS_LIST(\n        test_handler_expected_response_t) test_handler_expect_responses_list_t;\n\ntypedef struct {\n    test_handler_expect_responses_list_t expect_responses_list;\n} response_handler_args_t;\n\nstatic avs_coap_send_result_handler_result_t\ntest_response_handler(avs_coap_ctx_t *ctx,\n                      avs_coap_send_result_t result,\n                      avs_error_t err,\n                      const avs_coap_borrowed_msg_t *response,\n                      void *arg) {\n    (void) ctx;\n    response_handler_args_t *args = (response_handler_args_t *) arg;\n\n    test_handler_expect_responses_list_t *expect_list =\n            &args->expect_responses_list;\n    ASSERT_NOT_NULL(expect_list);\n    ASSERT_NOT_NULL(*expect_list);\n    const test_handler_expected_response_t *expected = *expect_list;\n\n    ASSERT_EQ(result, expected->result);\n    if (avs_is_ok(expected->err)) {\n        ASSERT_OK(err);\n    } else {\n        ASSERT_EQ(err.category, expected->err.category);\n        ASSERT_EQ(err.code, expected->err.code);\n    }\n\n    if (expected->has_response) {\n        const avs_coap_borrowed_msg_t *actual_res = response;\n        const avs_coap_borrowed_msg_t *expected_res = &expected->response;\n\n        ASSERT_EQ(actual_res->code, expected_res->code);\n        ASSERT_TRUE(\n                avs_coap_token_equal(&actual_res->token, &expected_res->token));\n        if (result != AVS_COAP_SEND_RESULT_FAIL) {\n            ASSERT_EQ(actual_res->options.size, expected_res->options.size);\n            ASSERT_EQ_BYTES_SIZED(actual_res->options.begin,\n                                  expected_res->options.begin,\n                                  actual_res->options.size);\n            ASSERT_EQ(actual_res->payload_size, expected_res->payload_size);\n            ASSERT_EQ_BYTES_SIZED(actual_res->payload, expected_res->payload,\n                                  actual_res->payload_size);\n        }\n    } else {\n        ASSERT_NULL(response);\n    }\n\n    AVS_LIST_DELETE(expect_list);\n\n    return AVS_COAP_RESPONSE_ACCEPTED;\n}\n\nstatic void expect_response_handler_call(response_handler_args_t *args,\n                                         avs_coap_send_result_t result,\n                                         avs_error_t err,\n                                         const test_msg_t *msg) {\n    test_handler_expected_response_t *expect =\n            AVS_LIST_NEW_ELEMENT(test_handler_expected_response_t);\n\n    expect->result = result;\n    expect->err = err;\n    expect->has_response = (msg != NULL);\n    if (msg) {\n        expect->response = msg->msg.content;\n    }\n\n    AVS_LIST_APPEND(&args->expect_responses_list, expect);\n}\n\nstatic response_handler_args_t setup_response_handler_args(void) {\n    return (response_handler_args_t) { 0 };\n}\n\nstatic void cleanup_response_handler_args(response_handler_args_t *args) {\n    ASSERT_NULL(args->expect_responses_list);\n}\n\nAVS_UNIT_TEST(coap_tcp_csm, request_before_peer_csm) {\n    _avs_mock_clock_start(avs_time_monotonic_from_scalar(0, AVS_TIME_S));\n    avs_shared_buffer_t *inbuf = avs_shared_buffer_new(IN_BUFFER_SIZE);\n    ASSERT_NOT_NULL(inbuf);\n    avs_shared_buffer_t *outbuf = avs_shared_buffer_new(OUT_BUFFER_SIZE);\n    ASSERT_NOT_NULL(outbuf);\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_with_external_buffers_without_mock_clock_and_peer_csm(\n                    inbuf, outbuf);\n    response_handler_args_t args\n            __attribute__((cleanup(cleanup_response_handler_args))) =\n                    setup_response_handler_args();\n\n    const test_msg_t *request = COAP_MSG(GET, MAKE_TOKEN(\"A token\"));\n    const test_msg_t *response = COAP_MSG(CONTENT, MAKE_TOKEN(\"A token\"));\n\n    expect_send(&env, request);\n    ASSERT_OK(\n            send_request(env.coap_ctx, request, test_response_handler, &args));\n\n    expect_recv(&env, COAP_MSG(CSM));\n    avs_coap_borrowed_msg_t borrowed_request;\n    ASSERT_OK(receive_nonrequest_message(env.coap_ctx, &borrowed_request));\n\n    expect_recv(&env, response);\n    expect_response_handler_call(&args, AVS_COAP_SEND_RESULT_OK, AVS_OK,\n                                 response);\n    ASSERT_OK(receive_nonrequest_message(env.coap_ctx, &borrowed_request));\n}\n\nAVS_UNIT_TEST(coap_tcp_csm, no_peer_csm) {\n    _avs_mock_clock_start(avs_time_monotonic_from_scalar(0, AVS_TIME_S));\n    response_handler_args_t args\n            __attribute__((cleanup(cleanup_response_handler_args))) =\n                    setup_response_handler_args();\n\n    avs_shared_buffer_t *inbuf = avs_shared_buffer_new(IN_BUFFER_SIZE);\n    ASSERT_NOT_NULL(inbuf);\n    avs_shared_buffer_t *outbuf = avs_shared_buffer_new(OUT_BUFFER_SIZE);\n    ASSERT_NOT_NULL(outbuf);\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_with_external_buffers_without_mock_clock_and_peer_csm(\n                    inbuf, outbuf);\n\n    const test_msg_t *request = COAP_MSG(GET, MAKE_TOKEN(\"A token\"));\n    const test_msg_t *response = COAP_MSG(CONTENT, MAKE_TOKEN(\"A token\"));\n\n    expect_send(&env, request);\n    ASSERT_OK(\n            send_request(env.coap_ctx, request, test_response_handler, &args));\n\n    expect_recv(&env, response);\n    expect_send(&env, COAP_MSG(ABORT, MAKE_TOKEN(\"A token\")));\n    avs_coap_borrowed_msg_t borrowed_request;\n    avs_error_t err =\n            receive_nonrequest_message(env.coap_ctx, &borrowed_request);\n    ASSERT_FAIL(err);\n    ASSERT_EQ(err.category, AVS_COAP_ERR_CATEGORY);\n    ASSERT_EQ(err.code, AVS_COAP_ERR_TCP_CSM_NOT_RECEIVED);\n\n    expect_response_handler_call(&args, AVS_COAP_SEND_RESULT_CANCEL,\n                                 _avs_coap_err(AVS_COAP_ERR_EXCHANGE_CANCELED),\n                                 NULL);\n}\n\nAVS_UNIT_TEST(coap_tcp_csm, signalling_without_peer_csm) {\n    _avs_mock_clock_start(avs_time_monotonic_from_scalar(0, AVS_TIME_S));\n    avs_shared_buffer_t *inbuf = avs_shared_buffer_new(IN_BUFFER_SIZE);\n    ASSERT_NOT_NULL(inbuf);\n    avs_shared_buffer_t *outbuf = avs_shared_buffer_new(OUT_BUFFER_SIZE);\n    ASSERT_NOT_NULL(outbuf);\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_with_external_buffers_without_mock_clock_and_peer_csm(\n                    inbuf, outbuf);\n\n    expect_recv(&env, COAP_MSG(PING, MAKE_TOKEN(\"A token\")));\n    expect_send(&env, COAP_MSG(ABORT, MAKE_TOKEN(\"A token\")));\n    avs_coap_borrowed_msg_t borrowed_request;\n    avs_error_t err =\n            receive_nonrequest_message(env.coap_ctx, &borrowed_request);\n    ASSERT_FAIL(err);\n    ASSERT_EQ(err.category, AVS_COAP_ERR_CATEGORY);\n    ASSERT_EQ(err.code, AVS_COAP_ERR_TCP_CSM_NOT_RECEIVED);\n}\n\nAVS_UNIT_TEST(coap_tcp_csm, peer_csm_timeout) {\n    _avs_mock_clock_start(avs_time_monotonic_from_scalar(0, AVS_TIME_S));\n    response_handler_args_t args\n            __attribute__((cleanup(cleanup_response_handler_args))) =\n                    setup_response_handler_args();\n\n    avs_shared_buffer_t *inbuf = avs_shared_buffer_new(IN_BUFFER_SIZE);\n    ASSERT_NOT_NULL(inbuf);\n    avs_shared_buffer_t *outbuf = avs_shared_buffer_new(OUT_BUFFER_SIZE);\n    ASSERT_NOT_NULL(outbuf);\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_with_external_buffers_without_mock_clock_and_peer_csm(\n                    inbuf, outbuf);\n\n    const test_msg_t *request = COAP_MSG(GET, MAKE_TOKEN(\"A token\"));\n\n    _avs_mock_clock_advance(avs_time_duration_from_scalar(1, AVS_TIME_S));\n\n    expect_send(&env, request);\n    ASSERT_OK(\n            send_request(env.coap_ctx, request, test_response_handler, &args));\n\n    avs_time_duration_t time_to_expiry = avs_sched_time_to_next(env.sched);\n    ASSERT_TRUE(avs_time_duration_valid(time_to_expiry));\n\n    _avs_mock_clock_advance(time_to_expiry);\n\n    expect_send(&env,\n                COAP_MSG(ABORT, PAYLOAD(\"CSM not received within timeout\")));\n    avs_sched_run(env.sched);\n\n    expect_response_handler_call(&args, AVS_COAP_SEND_RESULT_CANCEL,\n                                 _avs_coap_err(AVS_COAP_ERR_EXCHANGE_CANCELED),\n                                 NULL);\n}\n\nAVS_UNIT_TEST(coap_tcp_csm, error_sending_csm) {\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_without_socket();\n\n    ASSERT_NULL(env.mocksock);\n    avs_unit_mocksock_create(&env.mocksock);\n    avs_unit_mocksock_enable_recv_timeout_getsetopt(\n            env.mocksock, avs_time_duration_from_scalar(0, AVS_TIME_S));\n    ASSERT_NOT_NULL(env.mocksock);\n    avs_unit_mocksock_expect_connect(env.mocksock, NULL, NULL);\n    avs_net_socket_connect(env.mocksock, NULL, NULL);\n\n    // Attempt to send CSM\n    avs_unit_mocksock_output_fail(env.mocksock, avs_errno(AVS_ECONNRESET));\n    // Failed, now send Abort\n    expect_send(&env, COAP_MSG(ABORT, PAYLOAD(\"failed to send CSM\")));\n    ASSERT_FAIL(avs_coap_ctx_set_socket(env.coap_ctx, env.mocksock));\n}\n\n#endif // defined(AVS_UNIT_TESTING) && defined(WITH_AVS_COAP_TCP)\n"
  },
  {
    "path": "deps/avs_coap/tests/tcp/ctx.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <avs_coap_init.h>\n\n#if defined(AVS_UNIT_TESTING) && defined(WITH_AVS_COAP_TCP)\n\n#    include \"tests/utils.h\"\n\n#    define MODULE_NAME test\n#    include <avs_coap_x_log_config.h>\n\n#    include \"./helper_functions.h\"\n\nAVS_UNIT_TEST(coap_tcp_ctx, create_ctx_and_delete) {\n    test_env_t env __attribute__((cleanup(test_teardown))) = test_setup();\n}\n\nAVS_UNIT_TEST(coap_tcp_ctx, unexpected_response) {\n    test_env_t env __attribute__((cleanup(test_teardown))) = test_setup();\n\n    const test_msg_t *response = COAP_MSG(CONTENT, MAKE_TOKEN(\"123\"));\n\n    expect_recv(&env, response);\n    avs_coap_borrowed_msg_t request;\n    ASSERT_OK(receive_nonrequest_message(env.coap_ctx, &request));\n}\n\nAVS_UNIT_TEST(coap_tcp_ctx, unexpected_response_with_too_big_options) {\n    test_env_t env __attribute__((cleanup(test_teardown))) = test_setup();\n\n    // PATH option in response is used only for test purposes.\n    const test_msg_t *response =\n            COAP_MSG(CONTENT, MAKE_TOKEN(\"123\"),\n                     PATH(\"deszcz na jeziorach deszcz na jeziorach\"));\n\n    expect_recv(&env, response);\n\n    avs_coap_borrowed_msg_t request;\n    ASSERT_OK(receive_nonrequest_message(env.coap_ctx, &request));\n    ASSERT_OK(receive_nonrequest_message(env.coap_ctx, &request));\n}\n\n#endif // defined(AVS_UNIT_TESTING) && defined(WITH_AVS_COAP_TCP)\n"
  },
  {
    "path": "deps/avs_coap/tests/tcp/env.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <avsystem/coap/coap.h>\n\n#define AVS_UNIT_ENABLE_SHORT_ASSERTS\n#include <avsystem/commons/avs_unit_mocksock.h>\n#include <avsystem/commons/avs_unit_test.h>\n\n#include \"./utils.h\"\n#include \"tests/mock_clock.h\"\n#include \"tests/utils.h\"\n\n#define IN_BUFFER_SIZE 32\n#define OUT_BUFFER_SIZE 1024\n\n#define MAX_OPTS_SIZE (IN_BUFFER_SIZE - sizeof(AVS_COAP_PAYLOAD_MARKER))\n#define OPTS_BUFFER_SIZE (MAX_OPTS_SIZE + sizeof(AVS_COAP_PAYLOAD_MARKER))\n\ntypedef struct {\n    avs_net_socket_t *mocksock;\n\n    avs_sched_t *sched;\n    avs_time_duration_t timeout;\n    avs_shared_buffer_t *inbuf;\n    avs_shared_buffer_t *outbuf;\n    avs_coap_ctx_t *coap_ctx;\n    avs_crypto_prng_ctx_t *prng_ctx;\n\n    // Set if Abort message is expected to be sent and we don't expect Release\n    // message.\n    bool aborted;\n} test_env_t;\n\ntypedef struct {\n    avs_net_socket_t *mocksock;\n    avs_shared_buffer_t *inbuf;\n    avs_shared_buffer_t *outbuf;\n} test_env_args_t;\n\nstatic inline void expect_send(test_env_t *env, const test_msg_t *msg) {\n    avs_unit_mocksock_expect_output(env->mocksock, msg->data, msg->size);\n}\n\nstatic inline void expect_recv(test_env_t *env, const test_msg_t *msg) {\n    avs_unit_mocksock_input(env->mocksock, msg->data, msg->size);\n}\n\nstatic inline void expect_recv_with_limited_size(test_env_t *env,\n                                                 const test_msg_t *msg,\n                                                 size_t size) {\n    size_t size_to_send = AVS_MIN(size, msg->size);\n    avs_unit_mocksock_input(env->mocksock, msg->data, size_to_send);\n}\n\nstatic inline void expect_has_buffered_data_check(test_env_t *env,\n                                                  bool has_buffered_data) {\n    avs_unit_mocksock_expect_get_opt(env->mocksock,\n                                     AVS_NET_SOCKET_HAS_BUFFERED_DATA,\n                                     (avs_net_socket_opt_value_t) {\n                                         .flag = has_buffered_data\n                                     });\n}\n\nstatic inline test_env_t test_setup_from_args(const test_env_args_t *args) {\n    avs_sched_t *sched = avs_sched_new(\"test\", NULL);\n    ASSERT_NOT_NULL(sched);\n\n    avs_time_duration_t timeout = avs_time_duration_from_scalar(5, AVS_TIME_S);\n\n    avs_crypto_prng_ctx_t *prng_ctx = avs_crypto_prng_new(NULL, NULL);\n    ASSERT_NOT_NULL(prng_ctx);\n\n    test_env_t env = {\n        .mocksock = args->mocksock,\n        .sched = sched,\n        .inbuf = args->inbuf,\n        .outbuf = args->outbuf,\n        .timeout = timeout,\n#ifdef WITH_AVS_COAP_TCP\n        // Workaround for self-sufficiency test. This header should never be\n        // included if WITH_AVS_COAP_TCP is not defined. If somehow it'll be,\n        // then assertion will fail.\n        .coap_ctx = avs_coap_tcp_ctx_create(sched,\n                                            args->inbuf,\n                                            args->outbuf,\n                                            MAX_OPTS_SIZE,\n                                            timeout,\n                                            prng_ctx),\n#endif // WITH_AVS_COAP_TCP\n        .prng_ctx = prng_ctx\n    };\n\n    ASSERT_NOT_NULL(env.coap_ctx);\n    return env;\n}\n\nstatic inline test_env_t test_setup_without_socket(void) {\n    avs_shared_buffer_t *inbuf = avs_shared_buffer_new(IN_BUFFER_SIZE);\n    ASSERT_NOT_NULL(inbuf);\n    avs_shared_buffer_t *outbuf = avs_shared_buffer_new(OUT_BUFFER_SIZE);\n    ASSERT_NOT_NULL(outbuf);\n    _avs_mock_clock_start(avs_time_monotonic_from_scalar(0, AVS_TIME_S));\n    return test_setup_from_args(&(const test_env_args_t) {\n        .inbuf = inbuf,\n        .outbuf = outbuf\n    });\n}\n\nstatic inline test_env_t\ntest_setup_with_external_buffers_without_mock_clock_and_peer_csm(\n        avs_shared_buffer_t *inbuf, avs_shared_buffer_t *outbuf) {\n    reset_token_generator();\n\n    avs_net_socket_t *socket = NULL;\n    avs_unit_mocksock_create(&socket);\n    avs_unit_mocksock_enable_recv_timeout_getsetopt(\n            socket, avs_time_duration_from_scalar(0, AVS_TIME_S));\n    ASSERT_NOT_NULL(socket);\n\n    avs_unit_mocksock_expect_connect(socket, NULL, NULL);\n    avs_net_socket_connect(socket, NULL, NULL);\n\n    const test_msg_t *csm = COAP_MSG(CSM,\n                                     TOKEN(nth_token(0)),\n#ifdef WITH_AVS_COAP_BLOCK\n                                     BLOCK_WISE_TRANSFER_CAPABLE,\n#endif // WITH_AVS_COAP_BLOCK\n                                     MAX_MESSAGE_SIZE(SIZE_MAX));\n    avs_unit_mocksock_expect_output(socket, csm->data, csm->size);\n\n    test_env_t env = test_setup_from_args(&(const test_env_args_t) {\n        .mocksock = socket,\n        .inbuf = inbuf,\n        .outbuf = outbuf\n    });\n\n    ASSERT_NOT_NULL(env.coap_ctx);\n    ASSERT_OK(avs_coap_ctx_set_socket(env.coap_ctx, socket));\n\n    return env;\n}\n\nstatic inline test_env_t test_setup_with_external_buffers_without_mock_clock(\n        avs_shared_buffer_t *inbuf, avs_shared_buffer_t *outbuf) {\n    test_env_t env =\n            test_setup_with_external_buffers_without_mock_clock_and_peer_csm(\n                    inbuf, outbuf);\n    ASSERT_NOT_NULL(env.coap_ctx);\n\n    const test_msg_t *peer_csm = COAP_MSG(CSM);\n    avs_unit_mocksock_input(env.mocksock, peer_csm->data, peer_csm->size);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n\n    return env;\n}\n\nstatic inline test_env_t\ntest_setup_with_external_buffers(avs_shared_buffer_t *inbuf,\n                                 avs_shared_buffer_t *outbuf) {\n    test_env_t env =\n            test_setup_with_external_buffers_without_mock_clock(inbuf, outbuf);\n    _avs_mock_clock_start(avs_time_monotonic_from_scalar(0, AVS_TIME_S));\n    return env;\n}\n\nstatic inline test_env_t\ntest_setup_with_custom_sized_buffers(size_t inbuf_size, size_t outbuf_size) {\n    avs_shared_buffer_t *inbuf = avs_shared_buffer_new(inbuf_size);\n    ASSERT_NOT_NULL(inbuf);\n    avs_shared_buffer_t *outbuf = avs_shared_buffer_new(outbuf_size);\n    ASSERT_NOT_NULL(outbuf);\n    return test_setup_with_external_buffers(inbuf, outbuf);\n}\n\nstatic inline test_env_t test_setup(void) {\n    avs_shared_buffer_t *inbuf = avs_shared_buffer_new(IN_BUFFER_SIZE);\n    ASSERT_NOT_NULL(inbuf);\n    avs_shared_buffer_t *outbuf = avs_shared_buffer_new(OUT_BUFFER_SIZE);\n    ASSERT_NOT_NULL(outbuf);\n    return test_setup_with_external_buffers(inbuf, outbuf);\n}\n\nstatic inline void test_teardown_impl(test_env_t *env) {\n    avs_coap_ctx_cleanup(&env->coap_ctx);\n    if (env->mocksock) {\n        avs_unit_mocksock_assert_expects_met(env->mocksock);\n    }\n    avs_net_socket_cleanup(&env->mocksock);\n    avs_crypto_prng_free(&env->prng_ctx);\n}\n\nstatic inline void test_teardown_without_freeing_coap_ctx(test_env_t *env) {\n    if (env->mocksock) {\n        avs_unit_mocksock_assert_expects_met(env->mocksock);\n    }\n    avs_net_socket_cleanup(&env->mocksock);\n    avs_sched_cleanup(&env->sched);\n    avs_free(env->inbuf);\n    avs_free(env->outbuf);\n    avs_crypto_prng_free(&env->prng_ctx);\n    _avs_mock_clock_finish();\n}\n\nstatic inline void\ntest_teardown_without_freeing_shared_buffers_and_mock_clock(test_env_t *env) {\n    test_teardown_impl(env);\n    avs_sched_cleanup(&env->sched);\n}\n\nstatic inline void\ntest_teardown_without_freeing_shared_buffers(test_env_t *env) {\n    test_teardown_without_freeing_shared_buffers_and_mock_clock(env);\n    _avs_mock_clock_finish();\n}\n\nstatic inline void test_teardown(test_env_t *env) {\n    test_teardown_without_freeing_shared_buffers(env);\n    avs_free(env->inbuf);\n    avs_free(env->outbuf);\n}\n\nstatic inline void test_teardown_without_freeing_scheduler(test_env_t *env) {\n    test_teardown_impl(env);\n    _avs_mock_clock_finish();\n    avs_free(env->inbuf);\n    avs_free(env->outbuf);\n}\n"
  },
  {
    "path": "deps/avs_coap/tests/tcp/header.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <avs_coap_init.h>\n\n#if defined(AVS_UNIT_TESTING) && defined(WITH_AVS_COAP_TCP)\n\n#    include <avsystem/commons/avs_memory.h>\n\n#    define AVS_UNIT_ENABLE_SHORT_ASSERTS\n#    include <avsystem/commons/avs_unit_test.h>\n\n#    include <avsystem/coap/code.h>\n\n#    include \"options/avs_coap_option.h\"\n\n#    define MODULE_NAME test\n#    include <avs_coap_x_log_config.h>\n\n#    include \"tcp/avs_coap_tcp_msg.h\"\n\n#    include \"./utils.h\"\n\n#    define TEST_DATA(Data) \\\n        .data = Data,       \\\n        .data_size = sizeof(Data) - 1\n\ntypedef struct header_serialize_test_struct {\n    size_t payload_length;\n    size_t options_length;\n    uint8_t token_length;\n    uint8_t code;\n    // +1 to not get unterminated-string-initialization warnings\n    uint8_t data[_AVS_COAP_TCP_MAX_HEADER_LENGTH + 1];\n    size_t data_size;\n} header_test_data_t;\n\nconst header_test_data_t test_headers[] = {\n    { 0, 0, 0, AVS_COAP_CODE(0, 0), TEST_DATA(\"\\x00\\x00\") },\n    { 0, 0, 0, AVS_COAP_CODE(1, 3), TEST_DATA(\"\\x00\\x23\") },\n    { 1, 0, 0, AVS_COAP_CODE(0, 0), TEST_DATA(\"\\x20\\x00\") },\n    { 12, 0, 0, AVS_COAP_CODE(1, 3), TEST_DATA(\"\\xD0\\x00\\x23\") },\n    { 13, 0, 0, AVS_COAP_CODE(0, 0), TEST_DATA(\"\\xD0\\x01\\x00\") },\n    { 267, 0, 0, AVS_COAP_CODE(0, 0), TEST_DATA(\"\\xD0\\xFF\\x00\") },\n    { 268, 0, 0, AVS_COAP_CODE(1, 3), TEST_DATA(\"\\xE0\\x00\\x00\\x23\") },\n    { 269, 0, 0, AVS_COAP_CODE(0, 0), TEST_DATA(\"\\xE0\\x00\\x01\\x00\") },\n    { 65803, 0, 0, AVS_COAP_CODE(0, 0), TEST_DATA(\"\\xE0\\xFF\\xFF\\x00\") },\n    { 65804, 0, 0, AVS_COAP_CODE(1, 3), TEST_DATA(\"\\xF0\\x00\\x00\\x00\\x00\\x23\") },\n    { 65805, 0, 0, AVS_COAP_CODE(0, 0), TEST_DATA(\"\\xF0\\x00\\x00\\x00\\x01\\x00\") },\n\n    { 0, 0, 1, AVS_COAP_CODE(0, 0), TEST_DATA(\"\\x01\\x00\") },\n    { 0, 0, 2, AVS_COAP_CODE(0, 0), TEST_DATA(\"\\x02\\x00\") },\n    { 0, 0, 4, AVS_COAP_CODE(0, 0), TEST_DATA(\"\\x04\\x00\") },\n    { 0, 0, 8, AVS_COAP_CODE(0, 0), TEST_DATA(\"\\x08\\x00\") },\n    { 0, 1, 0, AVS_COAP_CODE(0, 0), TEST_DATA(\"\\x10\\x00\") },\n\n#    if SIZE_MAX > UINT32_MAX\n    { 4295033098, 1, 8, AVS_COAP_CODE(0, 0),\n      TEST_DATA(\"\\xF8\\xFF\\xFF\\xFF\\xFF\\x00\") },\n    { 4295033099, 0, 0, AVS_COAP_CODE(0, 0),\n      TEST_DATA(\"\\xF0\\xFF\\xFF\\xFF\\xFF\\x00\") }\n#    endif\n};\nconst size_t cases_count = AVS_ARRAY_SIZE(test_headers);\n\nstatic avs_coap_tcp_header_t init_header(const header_test_data_t *test_data) {\n    avs_coap_tcp_header_t header =\n            _avs_coap_tcp_header_init(test_data->payload_length,\n                                      test_data->options_length,\n                                      test_data->token_length,\n                                      test_data->code);\n    return header;\n}\n\nAVS_UNIT_TEST(coap_tcp_header, serialize) {\n    for (size_t i = 0; i < cases_count; i++) {\n        void *buf = avs_calloc(1, _AVS_COAP_TCP_MAX_HEADER_LENGTH);\n        ASSERT_NOT_NULL(buf);\n        avs_coap_tcp_header_t header = init_header(&test_headers[i]);\n        size_t bytes_written =\n                _avs_coap_tcp_header_serialize(&header, buf,\n                                               _AVS_COAP_TCP_MAX_HEADER_LENGTH);\n        ASSERT_EQ(bytes_written, test_headers[i].data_size);\n        ASSERT_EQ_BYTES_SIZED(buf, test_headers[i].data, bytes_written);\n        avs_free(buf);\n    }\n}\n\nstatic void validate_header(avs_coap_tcp_header_t *header,\n                            const header_test_data_t *test_data) {\n    ASSERT_EQ(header->code, test_data->code);\n    ASSERT_EQ(header->opts_and_payload_len,\n              test_data->payload_length + !!test_data->payload_length\n                      + test_data->options_length);\n    ASSERT_EQ(header->token_len, test_data->token_length);\n}\n\nAVS_UNIT_TEST(coap_tcp_header, parse) {\n    for (size_t i = 0; i < cases_count; i++) {\n        bytes_dispenser_t dispenser = {\n            .read_ptr = test_headers[i].data,\n            .bytes_left = test_headers[i].data_size\n        };\n        avs_coap_tcp_header_t header = { 0 };\n        size_t missing;\n        ASSERT_OK(_avs_coap_tcp_header_parse(&header, &dispenser, &missing));\n        validate_header(&header, &test_headers[i]);\n    }\n}\n\n#endif // defined(AVS_UNIT_TESTING) && defined(WITH_AVS_COAP_TCP)\n"
  },
  {
    "path": "deps/avs_coap/tests/tcp/helper_functions.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <avsystem/coap/coap.h>\n\n#include <avsystem/commons/avs_errno.h>\n\n#define AVS_UNIT_ENABLE_SHORT_ASSERTS\n#include <avsystem/commons/avs_unit_mocksock.h>\n#include <avsystem/commons/avs_unit_test.h>\n\n#include \"./env.h\"\n#include \"./utils.h\"\n\nstatic inline avs_error_t\nsend_request(avs_coap_ctx_t *ctx,\n             const test_msg_t *msg,\n             avs_coap_send_result_handler_t *send_result_handler,\n             void *send_result_handler_arg) {\n    assert(ctx->vtable->send_message);\n    return ctx->vtable->send_message(ctx, &msg->msg.content,\n                                     send_result_handler,\n                                     send_result_handler_arg);\n}\n\nstatic inline avs_error_t send_response(avs_coap_ctx_t *ctx,\n                                        const avs_coap_borrowed_msg_t *msg) {\n    assert(ctx->vtable->send_message);\n    return ctx->vtable->send_message(ctx, msg, NULL, NULL);\n}\n\nstatic inline avs_error_t\nreceive_message(avs_coap_ctx_t *ctx, avs_coap_borrowed_msg_t *out_request) {\n    assert(ctx->vtable->receive_message);\n    avs_coap_base_t *coap_base = _avs_coap_get_base(ctx);\n    assert(!coap_base->in_buffer_in_use);\n    coap_base->in_buffer_in_use = true;\n    uint8_t *buf = avs_shared_buffer_acquire(coap_base->in_buffer);\n    memset(out_request, 0, sizeof(*out_request));\n    avs_error_t err = ctx->vtable->receive_message(\n            ctx, buf, coap_base->in_buffer->capacity, out_request);\n    avs_shared_buffer_release(coap_base->in_buffer);\n    coap_base->in_buffer_in_use = false;\n    return err;\n}\n\nstatic inline avs_error_t\nreceive_nonrequest_message(avs_coap_ctx_t *ctx,\n                           avs_coap_borrowed_msg_t *out_request) {\n    avs_error_t err = receive_message(ctx, out_request);\n    ASSERT_FALSE(avs_coap_code_is_request(out_request->code));\n    return err;\n}\n\nstatic inline avs_error_t\nreceive_request_message(avs_coap_ctx_t *ctx,\n                        avs_coap_borrowed_msg_t *out_request) {\n    avs_error_t err = receive_message(ctx, out_request);\n    ASSERT_TRUE(avs_coap_code_is_request(out_request->code));\n    return err;\n}\n\nstatic inline avs_error_t\nhandle_incoming_packet(avs_coap_ctx_t *ctx,\n                       avs_coap_server_new_async_request_handler_t *handler,\n                       void *handler_args) {\n    return avs_coap_async_handle_incoming_packet(ctx, handler, handler_args);\n}\n\nstatic inline void cancel_delivery(avs_coap_ctx_t *ctx,\n                                   const avs_coap_token_t *token) {\n    assert(ctx->vtable->abort_delivery);\n    ctx->vtable->abort_delivery(ctx, AVS_COAP_EXCHANGE_CLIENT_REQUEST, token,\n                                AVS_COAP_SEND_RESULT_CANCEL,\n                                _avs_coap_err(AVS_COAP_ERR_EXCHANGE_CANCELED));\n}\n\nstatic inline void ignore_request(avs_coap_ctx_t *ctx,\n                                  const avs_coap_token_t *token) {\n    assert(ctx->vtable->ignore_current_request);\n    ctx->vtable->ignore_current_request(ctx, token);\n}\n\nstatic inline void\nexpect_sliced_recv(test_env_t *env, const test_msg_t *msg, size_t slice_pos) {\n    assert(slice_pos > 0 && slice_pos < msg->size);\n\n    avs_unit_mocksock_input(env->mocksock, msg->data, slice_pos);\n    expect_has_buffered_data_check(env, false);\n    avs_unit_mocksock_input(env->mocksock, msg->data + slice_pos,\n                            msg->size - slice_pos);\n}\n"
  },
  {
    "path": "deps/avs_coap/tests/tcp/payload_escaper.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <avs_coap_init.h>\n\n#if defined(AVS_UNIT_TESTING) && defined(WITH_AVS_COAP_TCP)\n\n#    define AVS_UNIT_ENABLE_SHORT_ASSERTS\n#    include <avsystem/commons/avs_unit_test.h>\n\n#    include <avsystem/commons/avs_utils.h>\n\n#    include \"tcp/avs_coap_tcp_utils.h\"\n\n#    include <stdio.h>\n\ntypedef struct {\n    char *to_escape;\n    size_t to_escape_size;\n    char *escaped;\n    size_t escaped_size;\n} test_data_t;\n\n#    define TEST_DATA(ToEscape, Escaped)              \\\n        {                                             \\\n            .to_escape = (ToEscape),                  \\\n            .to_escape_size = (sizeof(ToEscape) - 1), \\\n            .escaped = (Escaped),                     \\\n            .escaped_size = (sizeof(Escaped) - 1)     \\\n        }\n\nAVS_UNIT_TEST(payload_escaper, escape_test) {\n    // clang-format off\n    test_data_t payloads[] = {\n        TEST_DATA(\"a\", \"a\"),\n        TEST_DATA(\"\\0\", \"\\\\x00\"),\n        TEST_DATA(\"\\\\\", \"\\\\\\\\\"),\n        TEST_DATA(\"%\", \"%\"),\n        TEST_DATA(\"\\\"\", \"\\\\\\\"\"),\n        TEST_DATA(\"\\'\", \"\\\\\\'\"),\n        TEST_DATA(\"\\\\\\\\x00%c\", \"\\\\\\\\\\\\\\\\x00%c\"),\n        TEST_DATA(\"\\r\", \"\\\\x0D\"),\n        TEST_DATA(\"\\xFF\", \"\\\\xFF\"),\n        TEST_DATA(\" \", \" \"),\n        TEST_DATA(\"~\", \"~\"),\n        TEST_DATA(\"ABCDEFGH1234567\", \"ABCDEFGH1234567\"),\n        TEST_DATA(\"ABCDEFGH12345678\", \"ABCDEFGH1234567\")\n    };\n    // clang-format on\n\n    for (size_t i = 0; i < AVS_ARRAY_SIZE(payloads); i++) {\n        char escaped_string[16];\n        _avs_coap_tcp_escape_payload(payloads[i].to_escape,\n                                     payloads[i].to_escape_size,\n                                     escaped_string,\n                                     sizeof(escaped_string));\n\n        char log_message[16];\n        avs_simple_snprintf(log_message, sizeof(log_message), \"%s\",\n                            escaped_string);\n        ASSERT_EQ(strlen(log_message), payloads[i].escaped_size);\n        ASSERT_EQ_BYTES_SIZED(log_message, payloads[i].escaped,\n                              payloads[i].escaped_size);\n    }\n}\n\nAVS_UNIT_TEST(payload_escaper, convert_truncated) {\n    const char *data = \"abcdefgh12345678\";\n\n    char escaped_string[9];\n\n    size_t bytes_escaped =\n            _avs_coap_tcp_escape_payload(data, strlen(data), escaped_string,\n                                         sizeof(escaped_string));\n    ASSERT_EQ(bytes_escaped, sizeof(escaped_string) - 1);\n    ASSERT_EQ_BYTES_SIZED(escaped_string, \"abcdefgh\", sizeof(\"abcdefgh\"));\n\n    bytes_escaped = _avs_coap_tcp_escape_payload(data + bytes_escaped,\n                                                 strlen(data) - bytes_escaped,\n                                                 escaped_string,\n                                                 sizeof(escaped_string));\n    ASSERT_EQ(bytes_escaped, sizeof(escaped_string) - 1);\n    ASSERT_EQ_BYTES_SIZED(escaped_string, \"12345678\", sizeof(\"12345678\"));\n}\n\n#endif // defined(AVS_UNIT_TESTING) && defined(WITH_AVS_COAP_TCP)\n"
  },
  {
    "path": "deps/avs_coap/tests/tcp/requesting.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <avs_coap_init.h>\n\n#if defined(AVS_UNIT_TESTING) && defined(WITH_AVS_COAP_TCP)\n\n#    include <math.h>\n\n#    define MODULE_NAME test\n#    include <avs_coap_x_log_config.h>\n\n#    include \"./helper_functions.h\"\n\ntypedef struct {\n    avs_coap_send_result_t result;\n    avs_error_t err;\n    bool has_response;\n    avs_coap_borrowed_msg_t response;\n} test_handler_expected_response_t;\n\ntypedef AVS_LIST(\n        test_handler_expected_response_t) test_handler_expect_responses_list_t;\n\ntypedef struct {\n    test_handler_expect_responses_list_t expect_responses_list;\n} response_handler_args_t;\n\nstatic avs_coap_send_result_handler_result_t\ntest_response_handler(avs_coap_ctx_t *ctx,\n                      avs_coap_send_result_t result,\n                      avs_error_t err,\n                      const avs_coap_borrowed_msg_t *response,\n                      void *arg) {\n    (void) ctx;\n    response_handler_args_t *args = (response_handler_args_t *) arg;\n\n    test_handler_expect_responses_list_t *expect_list =\n            &args->expect_responses_list;\n    ASSERT_NOT_NULL(expect_list);\n    ASSERT_NOT_NULL(*expect_list);\n    const test_handler_expected_response_t *expected = *expect_list;\n\n    ASSERT_EQ(result, expected->result);\n    if (avs_is_ok(expected->err)) {\n        ASSERT_OK(err);\n    } else {\n        ASSERT_EQ(err.category, expected->err.category);\n        ASSERT_EQ(err.code, expected->err.code);\n    }\n\n    if (expected->has_response) {\n        const avs_coap_borrowed_msg_t *actual_res = response;\n        const avs_coap_borrowed_msg_t *expected_res = &expected->response;\n\n        ASSERT_EQ(actual_res->code, expected_res->code);\n        ASSERT_TRUE(\n                avs_coap_token_equal(&actual_res->token, &expected_res->token));\n        if (result != AVS_COAP_SEND_RESULT_FAIL) {\n            ASSERT_EQ(actual_res->options.size, expected_res->options.size);\n            ASSERT_EQ_BYTES_SIZED(actual_res->options.begin,\n                                  expected_res->options.begin,\n                                  actual_res->options.size);\n            ASSERT_EQ(actual_res->payload_size, expected_res->payload_size);\n            ASSERT_EQ_BYTES_SIZED(actual_res->payload, expected_res->payload,\n                                  actual_res->payload_size);\n        }\n    } else {\n        ASSERT_NULL(response);\n    }\n\n    AVS_LIST_DELETE(expect_list);\n\n    return AVS_COAP_RESPONSE_ACCEPTED;\n}\n\nstatic void expect_response_handler_call(response_handler_args_t *args,\n                                         avs_coap_send_result_t result,\n                                         avs_error_t err,\n                                         const test_msg_t *msg) {\n    test_handler_expected_response_t *expect =\n            AVS_LIST_NEW_ELEMENT(test_handler_expected_response_t);\n\n    expect->result = result;\n    expect->err = err;\n    expect->has_response = (msg != NULL);\n    if (msg) {\n        expect->response = msg->msg.content;\n    }\n\n    AVS_LIST_APPEND(&args->expect_responses_list, expect);\n}\n\nstatic void test_synchronous_requests(test_env_t *env,\n                                      response_handler_args_t *args,\n                                      test_exchange_t exchanges[],\n                                      size_t count) {\n    for (size_t i = 0; i < count; i++) {\n        test_exchange_t exchange = exchanges[i];\n        expect_send(env, exchange.request);\n        ASSERT_OK(send_request(env->coap_ctx, exchange.request,\n                               test_response_handler, args));\n\n        expect_recv(env, exchange.response);\n\n        size_t payload_chunks = (size_t) ceil((double) exchange.response->size\n                                              / IN_BUFFER_SIZE);\n\n        // Single call if entire payload fits into buffer or if there's no\n        // payload.\n        if (payload_chunks <= 1) {\n            expect_response_handler_call(args, AVS_COAP_SEND_RESULT_OK, AVS_OK,\n                                         exchange.response);\n        } else {\n            const uint8_t *payload_ptr =\n                    (const uint8_t *) exchange.response->msg.content.payload;\n            size_t first_chunk_size = IN_BUFFER_SIZE\n                                      - (exchange.response->payload_offset\n                                         - exchange.response->options_offset);\n            expect_response_handler_call(\n                    args, AVS_COAP_SEND_RESULT_PARTIAL_CONTENT, AVS_OK,\n                    COAP_MSG(CONTENT,\n                             TOKEN(exchange.response->msg.content.token),\n                             PAYLOAD_EXTERNAL(payload_ptr, first_chunk_size)));\n            payload_ptr += first_chunk_size;\n\n            for (size_t chunk_no = 1; chunk_no < payload_chunks - 1;\n                 chunk_no++) {\n                expect_response_handler_call(\n                        args, AVS_COAP_SEND_RESULT_PARTIAL_CONTENT, AVS_OK,\n                        COAP_MSG(CONTENT,\n                                 TOKEN(exchange.response->msg.content.token),\n                                 PAYLOAD_EXTERNAL(payload_ptr,\n                                                  IN_BUFFER_SIZE)));\n                payload_ptr += IN_BUFFER_SIZE;\n            }\n\n            size_t remaining_bytes =\n                    (exchange.response->msg.content.payload_size\n                     - first_chunk_size)\n                    % IN_BUFFER_SIZE;\n            expect_response_handler_call(\n                    args, AVS_COAP_SEND_RESULT_OK, AVS_OK,\n                    COAP_MSG(CONTENT,\n                             TOKEN(exchange.response->msg.content.token),\n                             PAYLOAD_EXTERNAL(payload_ptr, remaining_bytes)));\n        }\n\n        for (size_t call = 0; call < payload_chunks; call++) {\n            avs_coap_borrowed_msg_t request;\n            ASSERT_OK(receive_nonrequest_message(env->coap_ctx, &request));\n        }\n    }\n}\n\nstatic void send_all_requests(test_env_t *env,\n                              response_handler_args_t *args,\n                              test_exchange_t exchanges[],\n                              size_t count) {\n    for (size_t i = 0; i < count; i++) {\n        expect_send(env, exchanges[i].request);\n        ASSERT_OK(send_request(env->coap_ctx, exchanges[i].request,\n                               test_response_handler, args));\n    }\n}\n\nstatic void expect_concatenated_responses(test_env_t *env,\n                                          response_handler_args_t *args,\n                                          test_exchange_t exchanges[],\n                                          size_t count) {\n    // Bytes have to be concatenated before passing to avs_unit_mocksock_input.\n    size_t len = 0;\n    uint8_t data[65536];\n\n    for (size_t i = 0; i < count; i++) {\n        assert(len + exchanges[i].response->size <= sizeof(data));\n        memcpy(data + len, exchanges[i].response->data,\n               exchanges[i].response->size);\n        len += exchanges[i].response->size;\n        expect_response_handler_call(args, AVS_COAP_SEND_RESULT_OK, AVS_OK,\n                                     exchanges[i].response);\n    }\n    avs_unit_mocksock_input(env->mocksock, data, len);\n}\n\nstatic void\nexpect_concatenated_responses_reversed(test_env_t *env,\n                                       response_handler_args_t *args,\n                                       test_exchange_t exchanges[],\n                                       size_t count) {\n    // Bytes have to be concatenated before passing to avs_unit_mocksock_input.\n    size_t len = 0;\n    uint8_t data[65536];\n\n    for (ptrdiff_t i = (ptrdiff_t) count - 1; i >= 0; --i) {\n        assert(len + exchanges[i].response->size <= sizeof(data));\n        memcpy(data + len, exchanges[i].response->data,\n               exchanges[i].response->size);\n        len += exchanges[i].response->size;\n        expect_response_handler_call(args, AVS_COAP_SEND_RESULT_OK, AVS_OK,\n                                     exchanges[i].response);\n    }\n\n    avs_unit_mocksock_input(env->mocksock, data, len);\n}\n\nstatic response_handler_args_t setup_response_handler_args(void) {\n    return (response_handler_args_t) { 0 };\n}\n\nstatic void cleanup_response_handler_args(response_handler_args_t *args) {\n    ASSERT_NULL(args->expect_responses_list);\n}\n\nAVS_UNIT_TEST(coap_tcp_requesting, single_request) {\n    test_env_t env __attribute__((cleanup(test_teardown))) = test_setup();\n    response_handler_args_t args\n            __attribute__((cleanup(cleanup_response_handler_args))) =\n                    setup_response_handler_args();\n\n    const test_msg_t *request = COAP_MSG(GET, MAKE_TOKEN(\"A token\"));\n    const test_msg_t *response = COAP_MSG(CONTENT, MAKE_TOKEN(\"A token\"));\n\n    expect_send(&env, request);\n    ASSERT_OK(\n            send_request(env.coap_ctx, request, test_response_handler, &args));\n\n    expect_recv(&env, response);\n    expect_response_handler_call(&args, AVS_COAP_SEND_RESULT_OK, AVS_OK,\n                                 response);\n    avs_coap_borrowed_msg_t borrowed_request;\n    ASSERT_OK(receive_nonrequest_message(env.coap_ctx, &borrowed_request));\n}\n\nAVS_UNIT_TEST(coap_tcp_requesting, two_synchronous_requests) {\n    test_env_t env __attribute__((cleanup(test_teardown))) = test_setup();\n    response_handler_args_t args\n            __attribute__((cleanup(cleanup_response_handler_args))) =\n                    setup_response_handler_args();\n\n    test_exchange_t exchanges[] = {\n        {\n            .request = COAP_MSG(GET, MAKE_TOKEN(\"1234\")),\n            .response = COAP_MSG(CONTENT, MAKE_TOKEN(\"1234\"))\n        },\n        {\n            .request = COAP_MSG(GET, MAKE_TOKEN(\"5678\")),\n            .response = COAP_MSG(CONTENT, MAKE_TOKEN(\"5678\"))\n        }\n    };\n\n    test_synchronous_requests(&env, &args, exchanges,\n                              AVS_ARRAY_SIZE(exchanges));\n}\n\nAVS_UNIT_TEST(coap_tcp_requesting, two_synchronous_requests_with_payload) {\n    test_env_t env __attribute__((cleanup(test_teardown))) = test_setup();\n    response_handler_args_t args\n            __attribute__((cleanup(cleanup_response_handler_args))) =\n                    setup_response_handler_args();\n\n    test_exchange_t exchanges[] = {\n        {\n            .request = COAP_MSG(GET, MAKE_TOKEN(\"1234\"), PAYLOAD(\"ABCDE\")),\n            .response = COAP_MSG(CONTENT, MAKE_TOKEN(\"1234\"))\n        },\n        {\n            .request = COAP_MSG(GET, MAKE_TOKEN(\"5678\"), PAYLOAD(\"FGHIJ\")),\n            .response = COAP_MSG(CONTENT, MAKE_TOKEN(\"5678\"))\n        }\n    };\n\n    test_synchronous_requests(&env, &args, exchanges,\n                              AVS_ARRAY_SIZE(exchanges));\n}\n\nAVS_UNIT_TEST(coap_tcp_requesting, two_asynchronous_requests) {\n    test_env_t env __attribute__((cleanup(test_teardown))) = test_setup();\n    response_handler_args_t args\n            __attribute__((cleanup(cleanup_response_handler_args))) =\n                    setup_response_handler_args();\n\n    test_exchange_t exchanges[] = {\n        {\n            .request = COAP_MSG(GET, MAKE_TOKEN(\"AA token\")),\n            .response = COAP_MSG(CONTENT, MAKE_TOKEN(\"AA token\"))\n        },\n        {\n            .request = COAP_MSG(GET, MAKE_TOKEN(\"BB token\")),\n            .response = COAP_MSG(CONTENT, MAKE_TOKEN(\"BB token\"))\n        }\n    };\n\n    send_all_requests(&env, &args, exchanges, AVS_ARRAY_SIZE(exchanges));\n    expect_concatenated_responses(&env, &args, exchanges,\n                                  AVS_ARRAY_SIZE(exchanges));\n\n    avs_coap_borrowed_msg_t request;\n    ASSERT_OK(receive_nonrequest_message(env.coap_ctx, &request));\n    ASSERT_OK(receive_nonrequest_message(env.coap_ctx, &request));\n}\n\nAVS_UNIT_TEST(coap_tcp_requesting, two_asynchronous_requests_with_options) {\n    test_env_t env __attribute__((cleanup(test_teardown))) = test_setup();\n    response_handler_args_t args\n            __attribute__((cleanup(cleanup_response_handler_args))) =\n                    setup_response_handler_args();\n\n    test_exchange_t exchanges[] = {\n        {\n            .request = COAP_MSG(GET, MAKE_TOKEN(\"1\")),\n            // Test requires any option, so PATH macro can be used.\n            .response = COAP_MSG(CONTENT, MAKE_TOKEN(\"1\"), PATH(\"first\"))\n        },\n        {\n            .request = COAP_MSG(GET, MAKE_TOKEN(\"2\")),\n            .response = COAP_MSG(CONTENT, MAKE_TOKEN(\"2\"), PATH(\"second\"))\n        }\n    };\n\n    send_all_requests(&env, &args, exchanges, AVS_ARRAY_SIZE(exchanges));\n    expect_concatenated_responses(&env, &args, exchanges,\n                                  AVS_ARRAY_SIZE(exchanges));\n\n    avs_coap_borrowed_msg_t request;\n    ASSERT_OK(receive_nonrequest_message(env.coap_ctx, &request));\n    ASSERT_OK(receive_nonrequest_message(env.coap_ctx, &request));\n}\n\nAVS_UNIT_TEST(coap_tcp_requesting,\n              two_asynchronous_requests_with_payload_in_response) {\n    test_env_t env __attribute__((cleanup(test_teardown))) = test_setup();\n    response_handler_args_t args\n            __attribute__((cleanup(cleanup_response_handler_args))) =\n                    setup_response_handler_args();\n\n    test_exchange_t exchanges[] = {\n        {\n            .request = COAP_MSG(GET, MAKE_TOKEN(\"AA token\")),\n            .response = COAP_MSG(CONTENT, MAKE_TOKEN(\"AA token\"),\n                                 PAYLOAD(\"12345678 12345678\"))\n        },\n        {\n            .request = COAP_MSG(GET, MAKE_TOKEN(\"BB token\")),\n            .response = COAP_MSG(CONTENT, MAKE_TOKEN(\"BB token\"),\n                                 PAYLOAD(\"87654321 87654321\"))\n        }\n    };\n\n    send_all_requests(&env, &args, exchanges, AVS_ARRAY_SIZE(exchanges));\n    expect_concatenated_responses(&env, &args, exchanges,\n                                  AVS_ARRAY_SIZE(exchanges));\n\n    // TODO eagain if message isn't finished\n    avs_coap_borrowed_msg_t request;\n    ASSERT_OK(receive_nonrequest_message(env.coap_ctx, &request));\n    ASSERT_OK(receive_nonrequest_message(env.coap_ctx, &request));\n}\n\nAVS_UNIT_TEST(coap_tcp_requesting,\n              two_asynchronous_requests_with_reversed_responses_order) {\n    test_env_t env __attribute__((cleanup(test_teardown))) = test_setup();\n    response_handler_args_t args\n            __attribute__((cleanup(cleanup_response_handler_args))) =\n                    setup_response_handler_args();\n\n    test_exchange_t exchanges[] = {\n        {\n            .request = COAP_MSG(GET, MAKE_TOKEN(\"AA token\")),\n            .response = COAP_MSG(CONTENT, MAKE_TOKEN(\"AA token\"))\n        },\n        {\n            .request = COAP_MSG(GET, MAKE_TOKEN(\"BB token\")),\n            .response = COAP_MSG(CONTENT, MAKE_TOKEN(\"BB token\"))\n        }\n    };\n\n    send_all_requests(&env, &args, exchanges, AVS_ARRAY_SIZE(exchanges));\n    expect_concatenated_responses_reversed(&env, &args, exchanges,\n                                           AVS_ARRAY_SIZE(exchanges));\n\n    avs_coap_borrowed_msg_t request;\n    ASSERT_OK(receive_nonrequest_message(env.coap_ctx, &request));\n    ASSERT_OK(receive_nonrequest_message(env.coap_ctx, &request));\n}\n\nAVS_UNIT_TEST(coap_tcp_requesting, sliced_response) {\n    test_exchange_t exchange = {\n        .request = COAP_MSG(GET, MAKE_TOKEN(\"12345678\")),\n        .response = COAP_MSG(CONTENT, MAKE_TOKEN(\"12345678\"))\n    };\n\n    for (size_t pos = 1; pos < exchange.response->size; pos++) {\n        test_env_t env = test_setup();\n        response_handler_args_t args = setup_response_handler_args();\n\n        expect_send(&env, exchange.request);\n        ASSERT_OK(send_request(env.coap_ctx, exchange.request,\n                               test_response_handler, &args));\n\n        expect_response_handler_call(&args, AVS_COAP_SEND_RESULT_OK, AVS_OK,\n                                     exchange.response);\n\n        avs_coap_borrowed_msg_t request;\n        avs_unit_mocksock_input(env.mocksock, exchange.response->data, pos);\n        avs_unit_mocksock_input(env.mocksock,\n                                exchange.response->data + pos,\n                                exchange.response->size - pos);\n\n        ASSERT_OK(receive_nonrequest_message(env.coap_ctx, &request));\n        if (pos != exchange.response->token_offset\n                && pos != exchange.response->options_offset) {\n            ASSERT_OK(receive_nonrequest_message(env.coap_ctx, &request));\n        }\n\n        cleanup_response_handler_args(&args);\n        test_teardown(&env);\n    }\n}\n\nAVS_UNIT_TEST(coap_tcp_requesting, sliced_response_with_payload) {\n    test_exchange_t exchange = {\n        .request = COAP_MSG(GET, MAKE_TOKEN(\"12345678\")),\n        .response = COAP_MSG(CONTENT, MAKE_TOKEN(\"12345678\"), PAYLOAD(\"123\"))\n    };\n\n    for (size_t pos = 1; pos < exchange.response->size; pos++) {\n        test_env_t env = test_setup();\n        response_handler_args_t args = setup_response_handler_args();\n        bool payload_splitted =\n                (pos > exchange.response->size\n                               - exchange.response->msg.content.payload_size)\n                && pos < exchange.response->size;\n        size_t first_chunk_size = 0;\n        const uint8_t *payload_ptr =\n                (const uint8_t *) exchange.response->msg.content.payload;\n\n        if (payload_splitted) {\n            first_chunk_size = exchange.response->msg.content.payload_size\n                               - (exchange.response->size - pos);\n            expect_response_handler_call(\n                    &args, AVS_COAP_SEND_RESULT_PARTIAL_CONTENT, AVS_OK,\n                    COAP_MSG(CONTENT, MAKE_TOKEN(\"12345678\"),\n                             PAYLOAD_EXTERNAL(payload_ptr, first_chunk_size)));\n            payload_ptr += first_chunk_size;\n        }\n        expect_response_handler_call(\n                &args, AVS_COAP_SEND_RESULT_OK, AVS_OK,\n                COAP_MSG(CONTENT, MAKE_TOKEN(\"12345678\"),\n                         PAYLOAD_EXTERNAL(\n                                 payload_ptr,\n                                 exchange.response->msg.content.payload_size\n                                         - first_chunk_size)));\n        expect_send(&env, exchange.request);\n        ASSERT_OK(send_request(env.coap_ctx, exchange.request,\n                               test_response_handler, &args));\n\n        avs_unit_mocksock_input(env.mocksock, exchange.response->data, pos);\n        avs_unit_mocksock_input(env.mocksock, exchange.response->data + pos,\n                                exchange.response->size - pos);\n\n        avs_coap_borrowed_msg_t request;\n        ASSERT_OK(receive_nonrequest_message(env.coap_ctx, &request));\n        if (pos != exchange.response->token_offset\n                && pos != exchange.response->options_offset) {\n            ASSERT_OK(receive_nonrequest_message(env.coap_ctx, &request));\n        }\n\n        cleanup_response_handler_args(&args);\n        test_teardown(&env);\n    }\n}\n\nAVS_UNIT_TEST(coap_tcp_requesting, payload_as_big_as_buffer) {\n    test_env_t env __attribute__((cleanup(test_teardown))) = test_setup();\n    response_handler_args_t args\n            __attribute__((cleanup(cleanup_response_handler_args))) =\n                    setup_response_handler_args();\n\n    test_exchange_t exchange = {\n        .request = COAP_MSG(GET, MAKE_TOKEN(\"A token\")),\n        .response = COAP_MSG(CONTENT, MAKE_TOKEN(\"A token\"),\n                             PAYLOAD(\"xzznzzhmupjhnwwvgqtnwvayipxmjift\"))\n    };\n\n    test_synchronous_requests(&env, &args, &exchange, 1);\n}\n\nAVS_UNIT_TEST(coap_tcp_requesting, payload_bigger_than_buffer) {\n    test_env_t env __attribute__((cleanup(test_teardown))) = test_setup();\n    response_handler_args_t args\n            __attribute__((cleanup(cleanup_response_handler_args))) =\n                    setup_response_handler_args();\n\n    test_exchange_t exchange = {\n        .request = COAP_MSG(GET, MAKE_TOKEN(\"A token\")),\n        .response = COAP_MSG(CONTENT, MAKE_TOKEN(\"A token\"),\n                             PAYLOAD(\"grngmywzejbodfbfvnmnqoueynsbqnsmt\"))\n    };\n\n    test_synchronous_requests(&env, &args, &exchange, 1);\n}\n\nAVS_UNIT_TEST(coap_tcp_requesting, two_big_synchronous_requests) {\n    test_env_t env __attribute__((cleanup(test_teardown))) = test_setup();\n    response_handler_args_t args\n            __attribute__((cleanup(cleanup_response_handler_args))) =\n                    setup_response_handler_args();\n\n    test_exchange_t exchanges[] = {\n        {\n            .request = COAP_MSG(GET, MAKE_TOKEN(\"123\")),\n            .response = COAP_MSG(CONTENT, MAKE_TOKEN(\"123\"),\n                                 PAYLOAD(\"erbzjattddxdxajluqtdenmsbfwinsvutafcg\"\n                                         \"nwhsmhbqzsapxkhtdspirrvrssdm\"))\n        },\n        {\n            .request = COAP_MSG(GET, MAKE_TOKEN(\"456\")),\n            .response = COAP_MSG(CONTENT, MAKE_TOKEN(\"456\"),\n                                 PAYLOAD(\"podfmebmwkesgalzwkatwzvybxzihwcnrxsco\"\n                                         \"lnibrymgdzjflhtjvovlqwqcinpe\"))\n        }\n    };\n\n    test_synchronous_requests(&env, &args, exchanges,\n                              AVS_ARRAY_SIZE(exchanges));\n}\n\nAVS_UNIT_TEST(coap_tcp_requesting, empty_message) {\n    test_env_t env __attribute__((cleanup(test_teardown))) = test_setup();\n    response_handler_args_t args\n            __attribute__((cleanup(cleanup_response_handler_args))) =\n                    setup_response_handler_args();\n\n    // Request with empty message should be ignored.\n    const test_msg_t *request = COAP_MSG(EMPTY, MAKE_TOKEN(\"123\"));\n    expect_recv(&env, request);\n\n    avs_coap_borrowed_msg_t borrowed_request;\n    ASSERT_OK(receive_nonrequest_message(env.coap_ctx, &borrowed_request));\n}\n\nAVS_UNIT_TEST(coap_tcp_requesting, create_exchange_and_do_nothing) {\n    response_handler_args_t args\n            __attribute__((cleanup(cleanup_response_handler_args))) =\n                    setup_response_handler_args();\n    test_env_t env __attribute__((cleanup(test_teardown))) = test_setup();\n\n    const test_msg_t *request = COAP_MSG(GET, MAKE_TOKEN(\"123\"));\n\n    expect_send(&env, request);\n    ASSERT_OK(\n            send_request(env.coap_ctx, request, test_response_handler, &args));\n\n    expect_response_handler_call(&args, AVS_COAP_SEND_RESULT_CANCEL,\n                                 _avs_coap_err(AVS_COAP_ERR_EXCHANGE_CANCELED),\n                                 NULL);\n}\n\nAVS_UNIT_TEST(coap_tcp_requesting, cancel_exchange) {\n    test_env_t env __attribute__((cleanup(test_teardown))) = test_setup();\n    response_handler_args_t args\n            __attribute__((cleanup(cleanup_response_handler_args))) =\n                    setup_response_handler_args();\n\n    test_exchange_t exchange = {\n        .request = COAP_MSG(GET, MAKE_TOKEN(\"123\")),\n        .response = COAP_MSG(CONTENT, MAKE_TOKEN(\"123\"))\n    };\n\n    expect_send(&env, exchange.request);\n    expect_recv(&env, exchange.response);\n\n    ASSERT_OK(send_request(env.coap_ctx, exchange.request,\n                           test_response_handler, &args));\n    expect_response_handler_call(&args, AVS_COAP_SEND_RESULT_CANCEL,\n                                 _avs_coap_err(AVS_COAP_ERR_EXCHANGE_CANCELED),\n                                 NULL);\n    cancel_delivery(env.coap_ctx, &exchange.request->msg.content.token);\n\n    // Incoming data is interpreted as a response to canceled request and\n    // ignored.\n    avs_coap_borrowed_msg_t request;\n    ASSERT_OK(receive_nonrequest_message(env.coap_ctx, &request));\n}\n\nAVS_UNIT_TEST(coap_tcp_requesting, cancel_during_receiving_of_response) {\n    test_env_t env __attribute__((cleanup(test_teardown))) = test_setup();\n    response_handler_args_t args\n            __attribute__((cleanup(cleanup_response_handler_args))) =\n                    setup_response_handler_args();\n\n    test_exchange_t exchange1 = {\n        .request = COAP_MSG(GET, MAKE_TOKEN(\"123\")),\n        .response = COAP_MSG(CONTENT, MAKE_TOKEN(\"123\"),\n                             PAYLOAD(\"payload requiring two calls to handler\"))\n    };\n    expect_send(&env, exchange1.request);\n    expect_recv(&env, exchange1.response);\n\n    ASSERT_OK(send_request(env.coap_ctx, exchange1.request,\n                           test_response_handler, &args));\n    expect_response_handler_call(\n            &args, AVS_COAP_SEND_RESULT_PARTIAL_CONTENT, AVS_OK,\n            COAP_MSG(CONTENT, MAKE_TOKEN(\"123\"),\n                     PAYLOAD(\"payload requiring two calls to \")));\n    avs_coap_borrowed_msg_t request;\n    ASSERT_OK(receive_nonrequest_message(env.coap_ctx, &request));\n\n    expect_response_handler_call(&args, AVS_COAP_SEND_RESULT_CANCEL,\n                                 _avs_coap_err(AVS_COAP_ERR_EXCHANGE_CANCELED),\n                                 NULL);\n    cancel_delivery(env.coap_ctx, &exchange1.request->msg.content.token);\n\n    ASSERT_OK(receive_nonrequest_message(env.coap_ctx, &request));\n\n    // Make additional exchange to ensure that payload which should be ignored\n    // isn't interpreted as the next message.\n\n    test_exchange_t exchange2 = {\n        .request = COAP_MSG(GET, MAKE_TOKEN(\"123\")),\n        .response = COAP_MSG(CONTENT, MAKE_TOKEN(\"123\"))\n    };\n\n    expect_send(&env, exchange2.request);\n    expect_recv(&env, exchange2.response);\n\n    ASSERT_OK(send_request(env.coap_ctx, exchange1.request,\n                           test_response_handler, &args));\n    expect_response_handler_call(&args, AVS_COAP_SEND_RESULT_OK, AVS_OK,\n                                 exchange2.response);\n    ASSERT_OK(receive_nonrequest_message(env.coap_ctx, &request));\n}\n\nAVS_UNIT_TEST(coap_tcp_requesting, cancel_during_receiving_too_big_options) {\n    test_env_t env __attribute__((cleanup(test_teardown))) = test_setup();\n    response_handler_args_t args\n            __attribute__((cleanup(cleanup_response_handler_args))) =\n                    setup_response_handler_args();\n\n    test_exchange_t exchange1 = {\n        .request = COAP_MSG(GET, MAKE_TOKEN(\"123\")),\n        .response = COAP_MSG(CONTENT, MAKE_TOKEN(\"123\"),\n                             PATH(\"long option value wwwwwwwwwwwwwwwwww\"))\n    };\n    expect_send(&env, exchange1.request);\n    expect_recv(&env, exchange1.response);\n\n    ASSERT_OK(send_request(env.coap_ctx, exchange1.request,\n                           test_response_handler, &args));\n    avs_coap_borrowed_msg_t request;\n    ASSERT_OK(receive_nonrequest_message(env.coap_ctx, &request));\n\n    expect_response_handler_call(&args, AVS_COAP_SEND_RESULT_CANCEL,\n                                 _avs_coap_err(AVS_COAP_ERR_EXCHANGE_CANCELED),\n                                 NULL);\n    cancel_delivery(env.coap_ctx, &exchange1.request->msg.content.token);\n\n    ASSERT_OK(receive_nonrequest_message(env.coap_ctx, &request));\n\n    // Make additional exchange to ensure that payload which should be ignored\n    // isn't interpreted as the next message.\n\n    test_exchange_t exchange2 = {\n        .request = COAP_MSG(GET, MAKE_TOKEN(\"123\")),\n        .response = COAP_MSG(CONTENT, MAKE_TOKEN(\"123\"))\n    };\n\n    expect_send(&env, exchange2.request);\n    expect_recv(&env, exchange2.response);\n\n    ASSERT_OK(send_request(env.coap_ctx, exchange1.request,\n                           test_response_handler, &args));\n    expect_response_handler_call(&args, AVS_COAP_SEND_RESULT_OK, AVS_OK,\n                                 exchange2.response);\n    ASSERT_OK(receive_nonrequest_message(env.coap_ctx, &request));\n}\n\nAVS_UNIT_TEST(coap_tcp_requesting, response_with_too_big_option) {\n    test_env_t env __attribute__((cleanup(test_teardown))) = test_setup();\n    response_handler_args_t args\n            __attribute__((cleanup(cleanup_response_handler_args))) =\n                    setup_response_handler_args();\n\n    test_exchange_t exchange = {\n        .request = COAP_MSG(GET, MAKE_TOKEN(\"12345678\")),\n        .response =\n                COAP_MSG(CONTENT,\n                         MAKE_TOKEN(\"12345678\"),\n                         // Test requires any option, so PATH macro can be used.\n                         PATH(\"this is really long option value wwwwww\"))\n    };\n\n    expect_send(&env, exchange.request);\n    expect_recv(&env, exchange.response);\n\n    ASSERT_OK(send_request(env.coap_ctx, exchange.request,\n                           test_response_handler, &args));\n    expect_response_handler_call(\n            &args, AVS_COAP_SEND_RESULT_FAIL,\n            _avs_coap_err(AVS_COAP_ERR_TRUNCATED_MESSAGE_RECEIVED), NULL);\n\n    avs_coap_borrowed_msg_t request;\n    ASSERT_OK(receive_nonrequest_message(env.coap_ctx, &request));\n\n    ASSERT_OK(receive_nonrequest_message(env.coap_ctx, &request));\n}\n\nAVS_UNIT_TEST(coap_tcp_requesting, error_in_response) {\n    test_env_t env __attribute__((cleanup(test_teardown))) = test_setup();\n    response_handler_args_t args\n            __attribute__((cleanup(cleanup_response_handler_args))) =\n                    setup_response_handler_args();\n\n    test_exchange_t exchange = {\n        .request = COAP_MSG(GET, MAKE_TOKEN(\"12345678\")),\n        .response = COAP_MSG(INTERNAL_SERVER_ERROR, MAKE_TOKEN(\"12345678\"))\n    };\n\n    expect_send(&env, exchange.request);\n    expect_recv(&env, exchange.response);\n\n    ASSERT_OK(send_request(env.coap_ctx, exchange.request,\n                           test_response_handler, &args));\n    expect_response_handler_call(&args, AVS_COAP_SEND_RESULT_OK, AVS_OK,\n                                 exchange.response);\n\n    avs_coap_borrowed_msg_t request;\n    ASSERT_OK(receive_nonrequest_message(env.coap_ctx, &request));\n}\n\nstatic inline bool has_scheduled_job(avs_sched_t *sched) {\n    return avs_time_duration_valid(avs_sched_time_to_next(sched));\n}\n\nAVS_UNIT_TEST(coap_tcp_requesting, fail_on_timeout) {\n    test_env_t env __attribute__((cleanup(test_teardown))) = test_setup();\n    response_handler_args_t args\n            __attribute__((cleanup(cleanup_response_handler_args))) =\n                    setup_response_handler_args();\n\n    const test_msg_t *request = COAP_MSG(GET, MAKE_TOKEN(\"12345678\"));\n    expect_send(&env, request);\n\n    ASSERT_OK(\n            send_request(env.coap_ctx, request, test_response_handler, &args));\n    ASSERT_TRUE(has_scheduled_job(env.sched));\n    expect_response_handler_call(&args, AVS_COAP_SEND_RESULT_FAIL,\n                                 _avs_coap_err(AVS_COAP_ERR_TIMEOUT), NULL);\n\n    _avs_mock_clock_advance(env.timeout);\n    avs_sched_run(env.sched);\n    ASSERT_FALSE(has_scheduled_job(env.sched));\n}\n\nAVS_UNIT_TEST(coap_tcp_requesting,\n              send_request_then_close_context_and_run_scheduler) {\n    test_env_t env = test_setup();\n    response_handler_args_t args\n            __attribute__((cleanup(cleanup_response_handler_args))) =\n                    setup_response_handler_args();\n\n    const test_msg_t *request = COAP_MSG(GET, MAKE_TOKEN(\"12345678\"));\n    expect_send(&env, request);\n\n    ASSERT_OK(\n            send_request(env.coap_ctx, request, test_response_handler, &args));\n    expect_response_handler_call(&args, AVS_COAP_SEND_RESULT_CANCEL,\n                                 _avs_coap_err(AVS_COAP_ERR_EXCHANGE_CANCELED),\n                                 NULL);\n    ASSERT_TRUE(has_scheduled_job(env.sched));\n\n    test_teardown_without_freeing_scheduler(&env);\n\n    ASSERT_FALSE(has_scheduled_job(env.sched));\n    avs_sched_cleanup(&env.sched);\n}\n\nAVS_UNIT_TEST(coap_tcp_requesting, reschedule_on_partial_content) {\n    test_env_t env __attribute__((cleanup(test_teardown))) = test_setup();\n    response_handler_args_t args\n            __attribute__((cleanup(cleanup_response_handler_args))) =\n                    setup_response_handler_args();\n\n    test_exchange_t exchange = {\n        .request = COAP_MSG(GET, MAKE_TOKEN(\"1234567\")),\n        .response =\n                COAP_MSG(CONTENT, MAKE_TOKEN(\"1234567\"),\n                         PAYLOAD(\"litwo ojczyzno moja ty jestes jak zdrowie\"))\n    };\n\n    expect_send(&env, exchange.request);\n    ASSERT_OK(send_request(env.coap_ctx, exchange.request,\n                           test_response_handler, &args));\n\n    expect_recv(&env, exchange.response);\n\n    _avs_mock_clock_advance(avs_time_duration_diff(\n            env.timeout, avs_time_duration_from_scalar(1, AVS_TIME_S)));\n    avs_sched_run(env.sched);\n\n    expect_response_handler_call(\n            &args, AVS_COAP_SEND_RESULT_PARTIAL_CONTENT, AVS_OK,\n            COAP_MSG(CONTENT, MAKE_TOKEN(\"1234567\"),\n                     PAYLOAD(\"litwo ojczyzno moja ty jestes j\")));\n    avs_coap_borrowed_msg_t request;\n    ASSERT_OK(receive_nonrequest_message(env.coap_ctx, &request));\n    ASSERT_TRUE(has_scheduled_job(env.sched));\n\n    _avs_mock_clock_advance(avs_time_duration_diff(\n            env.timeout, avs_time_duration_from_scalar(1, AVS_TIME_S)));\n    avs_sched_run(env.sched);\n\n    expect_response_handler_call(&args, AVS_COAP_SEND_RESULT_OK, AVS_OK,\n                                 COAP_MSG(CONTENT, MAKE_TOKEN(\"1234567\"),\n                                          PAYLOAD(\"ak zdrowie\")));\n    ASSERT_OK(receive_nonrequest_message(env.coap_ctx, &request));\n\n    // implementation detail: a no-op job is still scheduled, but executing it\n    // should result in nothing being scheduled for later\n    _avs_mock_clock_advance(avs_time_duration_from_scalar(1, AVS_TIME_S));\n    avs_sched_run(env.sched);\n    ASSERT_FALSE(has_scheduled_job(env.sched));\n}\n\nAVS_UNIT_TEST(coap_tcp_requesting, reschedule_when_ignoring_message) {\n    test_env_t env __attribute__((cleanup(test_teardown))) = test_setup();\n    response_handler_args_t args\n            __attribute__((cleanup(cleanup_response_handler_args))) =\n                    setup_response_handler_args();\n\n    test_exchange_t exchange = {\n        .request = COAP_MSG(GET, MAKE_TOKEN(\"1234567\")),\n        .response = COAP_MSG(CONTENT, MAKE_TOKEN(\"1234567\"),\n                             PATH(\"ile cie trzeba cenic ten tylko sie dowie\"))\n    };\n\n    expect_send(&env, exchange.request);\n    ASSERT_OK(send_request(env.coap_ctx, exchange.request,\n                           test_response_handler, &args));\n\n    expect_recv(&env, exchange.response);\n\n    expect_response_handler_call(\n            &args, AVS_COAP_SEND_RESULT_FAIL,\n            _avs_coap_err(AVS_COAP_ERR_TRUNCATED_MESSAGE_RECEIVED), NULL);\n\n    _avs_mock_clock_advance(avs_time_duration_diff(\n            env.timeout, avs_time_duration_from_scalar(1, AVS_TIME_S)));\n    avs_coap_borrowed_msg_t request;\n    ASSERT_OK(receive_nonrequest_message(env.coap_ctx, &request));\n    ASSERT_TRUE(has_scheduled_job(env.sched));\n\n    _avs_mock_clock_advance(avs_time_duration_diff(\n            env.timeout, avs_time_duration_from_scalar(1, AVS_TIME_S)));\n    ASSERT_OK(receive_nonrequest_message(env.coap_ctx, &request));\n    ASSERT_TRUE(has_scheduled_job(env.sched));\n\n    _avs_mock_clock_advance(avs_time_duration_diff(\n            env.timeout, avs_time_duration_from_scalar(1, AVS_TIME_S)));\n\n    // implementation detail: a no-op job is still scheduled, but executing it\n    // should result in nothing being scheduled for later\n    _avs_mock_clock_advance(avs_time_duration_from_scalar(1, AVS_TIME_S));\n    avs_sched_run(env.sched);\n    ASSERT_FALSE(has_scheduled_job(env.sched));\n}\n\nAVS_UNIT_TEST(coap_tcp_requesting,\n              send_two_requests_and_cancel_the_second_one) {\n    test_env_t env = test_setup();\n    response_handler_args_t args\n            __attribute__((cleanup(cleanup_response_handler_args))) =\n                    setup_response_handler_args();\n\n    const test_msg_t *request1 = COAP_MSG(GET, MAKE_TOKEN(\"12345678\"));\n    const test_msg_t *request2 = COAP_MSG(GET, MAKE_TOKEN(\"87654321\"));\n\n    expect_send(&env, request1);\n    expect_send(&env, request2);\n\n    ASSERT_OK(\n            send_request(env.coap_ctx, request1, test_response_handler, &args));\n    ASSERT_OK(\n            send_request(env.coap_ctx, request2, test_response_handler, &args));\n\n    expect_response_handler_call(&args, AVS_COAP_SEND_RESULT_CANCEL,\n                                 _avs_coap_err(AVS_COAP_ERR_EXCHANGE_CANCELED),\n                                 NULL);\n    cancel_delivery(env.coap_ctx, &request2->msg.content.token);\n\n    expect_response_handler_call(&args, AVS_COAP_SEND_RESULT_CANCEL,\n                                 _avs_coap_err(AVS_COAP_ERR_EXCHANGE_CANCELED),\n                                 NULL);\n\n    test_teardown_without_freeing_scheduler(&env);\n\n    ASSERT_FALSE(has_scheduled_job(env.sched));\n    avs_sched_cleanup(&env.sched);\n}\n\n#endif // defined(AVS_UNIT_TESTING) && defined(WITH_AVS_COAP_TCP)\n"
  },
  {
    "path": "deps/avs_coap/tests/tcp/responding.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <avs_coap_init.h>\n\n#if defined(AVS_UNIT_TESTING) && defined(WITH_AVS_COAP_TCP)\n\n#    include \"tests/utils.h\"\n\n#    define MODULE_NAME test\n#    include <avs_coap_x_log_config.h>\n\n#    include \"./helper_functions.h\"\n\nstatic void test_request_handler_impl(avs_coap_ctx_t *ctx,\n                                      const avs_coap_borrowed_msg_t *request,\n                                      avs_buffer_t *payload_buffer,\n                                      const test_msg_t *msg,\n                                      size_t payload_offset,\n                                      size_t payload_size,\n                                      bool ignore_request_flag) {\n    bool request_finished = (request->payload_offset + request->payload_size\n                             == request->total_payload_size);\n\n    ASSERT_EQ(request->payload_size, payload_size);\n    ASSERT_EQ_BYTES_SIZED(request->token.bytes, msg->msg.content.token.bytes,\n                          msg->msg.content.token.size);\n    ASSERT_EQ_BYTES_SIZED(request->payload,\n                          msg->msg.content.payload + payload_offset,\n                          payload_size);\n\n    if (ignore_request_flag) {\n        ignore_request(ctx, &request->token);\n    }\n\n    if (request->payload_size) {\n        avs_buffer_append_bytes(payload_buffer, request->payload,\n                                request->payload_size);\n    }\n\n    ASSERT_EQ(request_finished,\n              (payload_offset + payload_size == msg->msg.content.payload_size));\n    if (request_finished) {\n        avs_coap_borrowed_msg_t borrowed_msg = {\n            .code = AVS_COAP_CODE_CONTENT,\n            .token = request->token,\n            .payload = avs_buffer_data(payload_buffer),\n            .payload_size = avs_buffer_data_size(payload_buffer)\n        };\n\n        if (request_finished) {\n            ASSERT_OK(send_response(ctx, &borrowed_msg));\n            avs_buffer_reset(payload_buffer);\n        }\n    }\n}\n\nstatic void test_request_handler(avs_coap_ctx_t *ctx,\n                                 const avs_coap_borrowed_msg_t *request,\n                                 avs_buffer_t *payload_buffer,\n                                 const test_msg_t *msg,\n                                 size_t payload_offset,\n                                 size_t payload_size) {\n    return test_request_handler_impl(ctx, request, payload_buffer, msg,\n                                     payload_offset, payload_size, false);\n}\n\nstatic void\ntest_canceling_request_handler(avs_coap_ctx_t *ctx,\n                               const avs_coap_borrowed_msg_t *request,\n                               avs_buffer_t *payload_buffer,\n                               const test_msg_t *msg,\n                               size_t payload_offset,\n                               size_t payload_size) {\n    return test_request_handler_impl(ctx, request, payload_buffer, msg,\n                                     payload_offset, payload_size, true);\n}\n\nstatic avs_buffer_t *setup_request_handler_payload_buffer(void) {\n    avs_buffer_t *result = NULL;\n    ASSERT_OK(avs_buffer_create(&result, 1024));\n    return result;\n}\n\nAVS_UNIT_TEST(coap_tcp_responding, single_get_request) {\n    test_env_t env __attribute__((cleanup(test_teardown))) = test_setup();\n    avs_buffer_t *payload_buffer __attribute__((cleanup(avs_buffer_free))) =\n            setup_request_handler_payload_buffer();\n\n    test_exchange_t exchange = {\n        .request = COAP_MSG(GET, MAKE_TOKEN(\"123\"), PAYLOAD(\"DATA\")),\n        .response = COAP_MSG(CONTENT, MAKE_TOKEN(\"123\"), PAYLOAD(\"DATA\"))\n    };\n\n    expect_recv(&env, exchange.request);\n    expect_send(&env, exchange.response);\n\n    avs_coap_borrowed_msg_t request;\n    ASSERT_OK(receive_request_message(env.coap_ctx, &request));\n    test_request_handler(env.coap_ctx, &request, payload_buffer,\n                         exchange.request, 0,\n                         exchange.request->msg.content.payload_size);\n}\n\nAVS_UNIT_TEST(coap_tcp_responding, request_with_one_byte_option) {\n    test_env_t env __attribute__((cleanup(test_teardown))) = test_setup();\n    avs_buffer_t *payload_buffer __attribute__((cleanup(avs_buffer_free))) =\n            setup_request_handler_payload_buffer();\n\n    test_exchange_t exchange = {\n        .request = COAP_MSG(GET, MAKE_TOKEN(\"123\"), PATH(\"\")),\n        .response = COAP_MSG(CONTENT, MAKE_TOKEN(\"123\"))\n    };\n\n    expect_recv(&env, exchange.request);\n    expect_send(&env, exchange.response);\n\n    avs_coap_borrowed_msg_t request;\n    ASSERT_OK(receive_request_message(env.coap_ctx, &request));\n    test_request_handler(env.coap_ctx, &request, payload_buffer,\n                         exchange.request, 0,\n                         exchange.request->msg.content.payload_size);\n}\n\nAVS_UNIT_TEST(coap_tcp_responding, multiple_get_request) {\n    test_env_t env __attribute__((cleanup(test_teardown))) = test_setup();\n    avs_buffer_t *payload_buffer __attribute__((cleanup(avs_buffer_free))) =\n            setup_request_handler_payload_buffer();\n\n    test_exchange_t exchanges[] = {\n        {\n            .request = COAP_MSG(GET, MAKE_TOKEN(\"1234\"), PAYLOAD(\"ABCDE\")),\n            .response = COAP_MSG(CONTENT, MAKE_TOKEN(\"1234\"), PAYLOAD(\"ABCDE\"))\n        },\n        {\n            .request = COAP_MSG(GET, MAKE_TOKEN(\"5678\"), PAYLOAD(\"FGHIJ\")),\n            .response = COAP_MSG(CONTENT, MAKE_TOKEN(\"5678\"), PAYLOAD(\"FGHIJ\"))\n        }\n    };\n\n    expect_recv(&env, exchanges[0].request);\n    expect_send(&env, exchanges[0].response);\n\n    expect_recv(&env, exchanges[1].request);\n    expect_send(&env, exchanges[1].response);\n\n    avs_coap_borrowed_msg_t request;\n    ASSERT_OK(receive_request_message(env.coap_ctx, &request));\n    test_request_handler(env.coap_ctx, &request, payload_buffer,\n                         exchanges[0].request, 0,\n                         exchanges[0].request->msg.content.payload_size);\n    ASSERT_OK(receive_request_message(env.coap_ctx, &request));\n    test_request_handler(env.coap_ctx, &request, payload_buffer,\n                         exchanges[1].request, 0,\n                         exchanges[0].request->msg.content.payload_size);\n}\n\nAVS_UNIT_TEST(coap_tcp_responding, payload_always_nonzero) {\n    test_env_t env __attribute__((cleanup(test_teardown))) = test_setup();\n    avs_buffer_t *payload_buffer __attribute__((cleanup(avs_buffer_free))) =\n            setup_request_handler_payload_buffer();\n\n    test_exchange_t exchanges[] = {\n        {\n            .request = COAP_MSG(GET, MAKE_TOKEN(\"12345678\"),\n                                PAYLOAD(\"some payload\"),\n                                PATH(\"opts will occupy whole buffer\")),\n            .response = COAP_MSG(CONTENT, MAKE_TOKEN(\"12345678\"),\n                                 PAYLOAD(\"some payload\"))\n        },\n        {\n            .request = COAP_MSG(GET, MAKE_TOKEN(\"12345678\"),\n                                PAYLOAD(\"some payload\"),\n                                PATH(\"options'll be 1 byte shorter\")),\n            .response = COAP_MSG(CONTENT, MAKE_TOKEN(\"12345678\"),\n                                 PAYLOAD(\"some payload\"))\n        }\n    };\n\n    expect_recv(&env, exchanges[0].request);\n    expect_send(&env, exchanges[0].response);\n\n    avs_coap_borrowed_msg_t request;\n    ASSERT_OK(receive_nonrequest_message(env.coap_ctx, &request));\n    ASSERT_OK(receive_request_message(env.coap_ctx, &request));\n    test_request_handler(env.coap_ctx, &request, payload_buffer,\n                         exchanges[0].request, 0,\n                         exchanges[0].request->msg.content.payload_size);\n\n    expect_recv(&env, exchanges[1].request);\n    expect_send(&env, exchanges[1].response);\n\n    ASSERT_OK(receive_request_message(env.coap_ctx, &request));\n    test_request_handler(env.coap_ctx, &request, payload_buffer,\n                         exchanges[1].request, 0, 1);\n    ASSERT_OK(receive_request_message(env.coap_ctx, &request));\n    test_request_handler(env.coap_ctx, &request, payload_buffer,\n                         exchanges[1].request, 1,\n                         exchanges[1].request->msg.content.payload_size - 1);\n}\n\nstatic void test_too_big_option(test_env_t *env) {\n    test_exchange_t exchange = {\n        .request =\n                COAP_MSG(GET, MAKE_TOKEN(\"12345678\"), PAYLOAD(\"some payload\"),\n                         PATH(\"this is really long option value wwwwww\")),\n        .response = COAP_MSG(INTERNAL_SERVER_ERROR,\n                             MAKE_TOKEN(\"12345678\")\n#    ifdef WITH_AVS_COAP_DIAGNOSTIC_MESSAGES\n                                     ,\n                             PAYLOAD(\"options too big\")\n#    endif // WITH_AVS_COAP_DIAGNOSTIC_MESSAGES\n                                     )\n    };\n\n    expect_recv(env, exchange.request);\n    expect_send(env, exchange.response);\n\n    avs_coap_borrowed_msg_t request;\n    ASSERT_OK(receive_nonrequest_message(env->coap_ctx, &request));\n    ASSERT_OK(receive_nonrequest_message(env->coap_ctx, &request));\n}\n\nAVS_UNIT_TEST(coap_tcp_responding, request_with_too_big_option) {\n    test_env_t env __attribute__((cleanup(test_teardown))) = test_setup();\n    test_too_big_option(&env);\n}\n\nAVS_UNIT_TEST(coap_tcp_responding,\n              request_with_too_big_options_and_then_valid_request) {\n    test_env_t env __attribute__((cleanup(test_teardown))) = test_setup();\n    avs_buffer_t *payload_buffer __attribute__((cleanup(avs_buffer_free))) =\n            setup_request_handler_payload_buffer();\n\n    test_too_big_option(&env);\n\n    test_exchange_t exchange = {\n        .request = COAP_MSG(GET, MAKE_TOKEN(\"12345678\"),\n                            PAYLOAD(\"some payload\"), PATH(\"opt\")),\n        .response = COAP_MSG(CONTENT, MAKE_TOKEN(\"12345678\"),\n                             PAYLOAD(\"some payload\"))\n    };\n\n    expect_recv(&env, exchange.request);\n    expect_send(&env, exchange.response);\n\n    avs_coap_borrowed_msg_t request;\n    ASSERT_OK(receive_request_message(env.coap_ctx, &request));\n    test_request_handler(env.coap_ctx, &request, payload_buffer,\n                         exchange.request, 0,\n                         exchange.request->msg.content.payload_size);\n}\n\nAVS_UNIT_TEST(coap_tcp_responding, big_request) {\n    test_env_t env __attribute__((cleanup(test_teardown))) = test_setup();\n    avs_buffer_t *payload_buffer __attribute__((cleanup(avs_buffer_free))) =\n            setup_request_handler_payload_buffer();\n\n    const char *payload_32B = \"abcdefghijklmnopqrstuvwxyz123456\";\n\n    const test_msg_t *full_request =\n            COAP_MSG(GET, MAKE_TOKEN(\"12345678\"),\n                     PAYLOAD_EXTERNAL(payload_32B, strlen(payload_32B)));\n    size_t first_chunk_size =\n            IN_BUFFER_SIZE\n            - (full_request->payload_offset - full_request->options_offset);\n\n    const test_msg_t *response =\n            COAP_MSG(CONTENT, MAKE_TOKEN(\"12345678\"),\n                     PAYLOAD_EXTERNAL(payload_32B, strlen(payload_32B)));\n\n    expect_recv(&env, full_request);\n    expect_send(&env, response);\n\n    avs_coap_borrowed_msg_t request;\n    ASSERT_OK(receive_request_message(env.coap_ctx, &request));\n    test_request_handler(env.coap_ctx, &request, payload_buffer, full_request,\n                         0, first_chunk_size);\n    ASSERT_OK(receive_request_message(env.coap_ctx, &request));\n    test_request_handler(env.coap_ctx, &request, payload_buffer, full_request,\n                         first_chunk_size,\n                         full_request->msg.content.payload_size\n                                 - first_chunk_size);\n}\n\nAVS_UNIT_TEST(coap_tcp_responding, request_sliced_after_short_header) {\n#    define MSG_PAYLOAD \"raz\"\n    test_env_t env __attribute__((cleanup(test_teardown))) = test_setup();\n    avs_buffer_t *payload_buffer __attribute__((cleanup(avs_buffer_free))) =\n            setup_request_handler_payload_buffer();\n\n    const test_msg_t *request =\n            COAP_MSG(GET, MAKE_TOKEN(\"12345678\"), PAYLOAD(MSG_PAYLOAD));\n    const test_msg_t *response =\n            COAP_MSG(CONTENT, MAKE_TOKEN(\"12345678\"), PAYLOAD(MSG_PAYLOAD));\n\n    avs_unit_mocksock_input(env.mocksock, request->data, request->token_offset);\n\n    avs_coap_borrowed_msg_t borrowed_request;\n\n    avs_unit_mocksock_input(env.mocksock, request->data + request->token_offset,\n                            request->size - request->token_offset);\n    expect_send(&env, response);\n    ASSERT_OK(receive_request_message(env.coap_ctx, &borrowed_request));\n    test_request_handler(env.coap_ctx, &borrowed_request, payload_buffer,\n                         request, 0, request->msg.content.payload_size);\n#    undef MSG_PAYLOAD\n}\n\nAVS_UNIT_TEST(coap_tcp_responding, request_sliced_between_begin_and_payload) {\n#    define MSG_PAYLOAD \"raz dwa trzy cztery piec\"\n\n    const test_msg_t *request =\n            COAP_MSG(GET, MAKE_TOKEN(\"12345678\"), PATH(\"test\", \"path\"),\n                     PAYLOAD(MSG_PAYLOAD));\n    const test_msg_t *response =\n            COAP_MSG(CONTENT, MAKE_TOKEN(\"12345678\"), PAYLOAD(MSG_PAYLOAD));\n\n    for (size_t slice_pos = 1; slice_pos < request->payload_offset;\n         slice_pos++) {\n        test_env_t env = test_setup();\n        avs_buffer_t *payload_buffer = setup_request_handler_payload_buffer();\n        avs_unit_mocksock_input(env.mocksock, request->data, slice_pos);\n\n        avs_coap_borrowed_msg_t borrowed_request;\n        if (slice_pos != 2 // because TCP ctx tries to read 2 bytes first\n                && slice_pos != request->token_offset\n                && slice_pos != request->options_offset) {\n            // non-request, because it isn't a complete message yet\n            ASSERT_OK(receive_nonrequest_message(env.coap_ctx,\n                                                 &borrowed_request));\n        }\n\n        avs_unit_mocksock_input(env.mocksock, request->data + slice_pos,\n                                request->size - slice_pos);\n        size_t first_chunk_size =\n                IN_BUFFER_SIZE\n                - (request->payload_offset - request->options_offset);\n        ASSERT_OK(receive_request_message(env.coap_ctx, &borrowed_request));\n        test_request_handler(env.coap_ctx, &borrowed_request, payload_buffer,\n                             request, 0, first_chunk_size);\n\n        expect_send(&env, response);\n        ASSERT_OK(receive_request_message(env.coap_ctx, &borrowed_request));\n\n        test_request_handler(env.coap_ctx, &borrowed_request, payload_buffer,\n                             request, first_chunk_size,\n                             request->msg.content.payload_size\n                                     - first_chunk_size);\n\n        avs_buffer_free(&payload_buffer);\n        test_teardown(&env);\n    }\n\n#    undef MSG_PAYLOAD\n}\n\nAVS_UNIT_TEST(coap_tcp_responding, request_sliced_after_options) {\n#    define MSG_PAYLOAD \"raz dwa trzy cztery piec\"\n\n    const test_msg_t *request =\n            COAP_MSG(GET, MAKE_TOKEN(\"12345678\"), PATH(\"test\", \"path\"),\n                     PAYLOAD(MSG_PAYLOAD));\n    const test_msg_t *response =\n            COAP_MSG(CONTENT, MAKE_TOKEN(\"12345678\"), PAYLOAD(MSG_PAYLOAD));\n\n    size_t slice_pos = request->payload_offset;\n    test_env_t env = test_setup();\n    avs_buffer_t *payload_buffer = setup_request_handler_payload_buffer();\n    avs_unit_mocksock_input(env.mocksock, request->data, slice_pos);\n\n    avs_coap_borrowed_msg_t borrowed_request;\n    ASSERT_OK(receive_nonrequest_message(env.coap_ctx, &borrowed_request));\n\n    avs_unit_mocksock_input(env.mocksock, request->data + slice_pos,\n                            request->size - slice_pos);\n    expect_send(&env, response);\n    ASSERT_OK(receive_request_message(env.coap_ctx, &borrowed_request));\n    test_request_handler(env.coap_ctx, &borrowed_request, payload_buffer,\n                         request, 0, request->msg.content.payload_size);\n    avs_buffer_free(&payload_buffer);\n    test_teardown(&env);\n\n#    undef MSG_PAYLOAD\n}\n\nAVS_UNIT_TEST(coap_tcp_responding, request_payload_sliced) {\n#    define MSG_PAYLOAD \"raz dwa trzy cztery piec\"\n\n    const test_msg_t *request =\n            COAP_MSG(GET, MAKE_TOKEN(\"12345678\"), PATH(\"test\", \"path\"),\n                     PAYLOAD(MSG_PAYLOAD));\n    const test_msg_t *response =\n            COAP_MSG(CONTENT, MAKE_TOKEN(\"12345678\"), PAYLOAD(MSG_PAYLOAD));\n\n    for (size_t slice_pos = request->payload_offset + 1;\n         slice_pos < IN_BUFFER_SIZE;\n         slice_pos++) {\n        test_env_t env = test_setup();\n        avs_buffer_t *payload_buffer = setup_request_handler_payload_buffer();\n        avs_unit_mocksock_input(env.mocksock, request->data, slice_pos);\n\n        size_t first_chunk_size = slice_pos - request->payload_offset;\n\n        avs_coap_borrowed_msg_t borrowed_request;\n        ASSERT_OK(receive_request_message(env.coap_ctx, &borrowed_request));\n        test_request_handler(env.coap_ctx, &borrowed_request, payload_buffer,\n                             request, 0, first_chunk_size);\n\n        avs_unit_mocksock_input(env.mocksock, request->data + slice_pos,\n                                request->size - slice_pos);\n        expect_send(&env, response);\n        ASSERT_OK(receive_request_message(env.coap_ctx, &borrowed_request));\n        test_request_handler(env.coap_ctx, &borrowed_request, payload_buffer,\n                             request, first_chunk_size,\n                             request->msg.content.payload_size\n                                     - first_chunk_size);\n\n        avs_buffer_free(&payload_buffer);\n        test_teardown(&env);\n    }\n\n#    undef MSG_PAYLOAD\n}\n\nAVS_UNIT_TEST(coap_tcp_responding, request_payload_sliced_twice) {\n#    define MSG_PAYLOAD \"raz dwa trzy cztery piec\"\n\n    const test_msg_t *request =\n            COAP_MSG(GET, MAKE_TOKEN(\"12345678\"), PATH(\"test\", \"path\"),\n                     PAYLOAD(MSG_PAYLOAD));\n    const test_msg_t *response =\n            COAP_MSG(CONTENT, MAKE_TOKEN(\"12345678\"), PAYLOAD(MSG_PAYLOAD));\n\n    for (size_t slice_pos = IN_BUFFER_SIZE + 1; slice_pos < IN_BUFFER_SIZE;\n         slice_pos++) {\n        test_env_t env = test_setup();\n        avs_buffer_t *payload_buffer = setup_request_handler_payload_buffer();\n        avs_unit_mocksock_input(env.mocksock, request->data, slice_pos);\n\n        size_t first_chunk_size =\n                IN_BUFFER_SIZE\n                - (request->payload_offset - request->options_offset);\n\n        avs_coap_borrowed_msg_t borrowed_request;\n        ASSERT_OK(receive_request_message(env.coap_ctx, &borrowed_request));\n        test_request_handler(env.coap_ctx, &borrowed_request, payload_buffer,\n                             request, 0, first_chunk_size);\n\n        size_t second_chunk_size =\n                slice_pos - request->payload_offset - first_chunk_size;\n        ASSERT_OK(receive_request_message(env.coap_ctx, &borrowed_request));\n        test_request_handler(env.coap_ctx, &borrowed_request, payload_buffer,\n                             request, first_chunk_size, second_chunk_size);\n\n        size_t third_chunk_size = request->msg.content.payload_size\n                                  - first_chunk_size - second_chunk_size;\n\n        avs_unit_mocksock_input(env.mocksock, request->data + slice_pos,\n                                request->size - slice_pos);\n        expect_send(&env, response);\n        ASSERT_OK(receive_request_message(env.coap_ctx, &borrowed_request));\n        test_request_handler(env.coap_ctx, &borrowed_request, payload_buffer,\n                             request, first_chunk_size + second_chunk_size,\n                             third_chunk_size);\n\n        avs_buffer_free(&payload_buffer);\n        test_teardown(&env);\n    }\n\n#    undef MSG_PAYLOAD\n}\n\nAVS_UNIT_TEST(coap_tcp_responding, duplicated_non_repeatable_critical_options) {\n    test_env_t env __attribute__((cleanup(test_teardown))) = test_setup();\n\n    const char *payload = \"raz dwa trzy cztery piec\";\n\n    const test_exchange_t exchange = {\n        .request = COAP_MSG(GET, MAKE_TOKEN(\"12345678\"), ACCEPT(1),\n                            DUPLICATED_ACCEPT(2),\n                            PAYLOAD_EXTERNAL(payload, strlen(payload))),\n        .response = COAP_MSG(BAD_OPTION, MAKE_TOKEN(\"12345678\"))\n    };\n\n    expect_recv(&env, exchange.request);\n    expect_send(&env, exchange.response);\n\n    avs_coap_borrowed_msg_t request;\n    ASSERT_OK(receive_nonrequest_message(env.coap_ctx, &request));\n}\n\nAVS_UNIT_TEST(coap_tcp_responding, requests_from_two_contexts) {\n    avs_shared_buffer_t *inbuf = avs_shared_buffer_new(IN_BUFFER_SIZE);\n    avs_shared_buffer_t *outbuf = avs_shared_buffer_new(OUT_BUFFER_SIZE);\n\n    test_env_t env1 =\n            test_setup_with_external_buffers_without_mock_clock(inbuf, outbuf);\n    test_env_t env2 =\n            test_setup_with_external_buffers_without_mock_clock(inbuf, outbuf);\n    _avs_mock_clock_start(avs_time_monotonic_from_scalar(0, AVS_TIME_S));\n\n    avs_buffer_t *payload_buffer1 __attribute__((cleanup(avs_buffer_free))) =\n            setup_request_handler_payload_buffer();\n    avs_buffer_t *payload_buffer2 __attribute__((cleanup(avs_buffer_free))) =\n            setup_request_handler_payload_buffer();\n\n    const char *payload1 = \"some payload which has to use shared buffer\";\n    test_exchange_t exchange1 = {\n        .request = COAP_MSG(GET, MAKE_TOKEN(\"firstctx\"),\n                            PAYLOAD_EXTERNAL(payload1, strlen(payload1))),\n        .response = COAP_MSG(CONTENT, MAKE_TOKEN(\"firstctx\"),\n                             PAYLOAD_EXTERNAL(payload1, strlen(payload1)))\n    };\n    size_t first_chunk_size = IN_BUFFER_SIZE\n                              - (exchange1.request->payload_offset\n                                 - exchange1.request->options_offset);\n    expect_recv(&env1, exchange1.request);\n    expect_send(&env1, exchange1.response);\n\n    const char *payload2 = \"another payload which will use shared buffer\";\n    test_exchange_t exchange2 = {\n        .request = COAP_MSG(GET, MAKE_TOKEN(\"seconctx\"),\n                            PAYLOAD_EXTERNAL(payload2, strlen(payload2))),\n        .response = COAP_MSG(CONTENT, MAKE_TOKEN(\"seconctx\"),\n                             PAYLOAD_EXTERNAL(payload2, strlen(payload2)))\n    };\n\n    size_t second_chunk_size = IN_BUFFER_SIZE\n                               - (exchange2.request->payload_offset\n                                  - exchange2.request->options_offset);\n\n    expect_recv(&env2, exchange2.request);\n    expect_send(&env2, exchange2.response);\n\n    avs_coap_borrowed_msg_t request;\n    ASSERT_OK(receive_request_message(env1.coap_ctx, &request));\n    test_request_handler(env1.coap_ctx, &request, payload_buffer1,\n                         exchange1.request, 0, first_chunk_size);\n    ASSERT_OK(receive_request_message(env1.coap_ctx, &request));\n    test_request_handler(env1.coap_ctx, &request, payload_buffer1,\n                         exchange1.request, first_chunk_size,\n                         exchange1.request->msg.content.payload_size\n                                 - first_chunk_size);\n\n    ASSERT_OK(receive_request_message(env2.coap_ctx, &request));\n    test_request_handler(env2.coap_ctx, &request, payload_buffer2,\n                         exchange2.request, 0, second_chunk_size);\n    ASSERT_OK(receive_request_message(env2.coap_ctx, &request));\n    test_request_handler(env2.coap_ctx, &request, payload_buffer2,\n                         exchange2.request, second_chunk_size,\n                         exchange2.request->msg.content.payload_size\n                                 - first_chunk_size);\n\n    test_teardown_without_freeing_shared_buffers_and_mock_clock(&env1);\n    test_teardown_without_freeing_shared_buffers_and_mock_clock(&env2);\n    _avs_mock_clock_finish();\n\n    avs_free(inbuf);\n    avs_free(outbuf);\n}\n\nAVS_UNIT_TEST(coap_tcp_responding, cancel_exchange_while_receiving_request) {\n    test_env_t env __attribute__((cleanup(test_teardown))) = test_setup();\n    avs_buffer_t *payload_buffer __attribute__((cleanup(avs_buffer_free))) =\n            setup_request_handler_payload_buffer();\n\n    const char *payload_32B = \"abcdefghijklmnopqrstuvwxyz123456\";\n\n    const test_msg_t *full_request =\n            COAP_MSG(GET, MAKE_TOKEN(\"12345678\"),\n                     PAYLOAD_EXTERNAL(payload_32B, strlen(payload_32B)));\n    size_t first_chunk_size =\n            IN_BUFFER_SIZE\n            - (full_request->payload_offset - full_request->options_offset);\n\n    expect_recv(&env, full_request);\n\n    avs_coap_borrowed_msg_t request;\n    ASSERT_OK(receive_request_message(env.coap_ctx, &request));\n    test_canceling_request_handler(env.coap_ctx, &request, payload_buffer,\n                                   full_request, 0, first_chunk_size);\n    ASSERT_OK(receive_nonrequest_message(env.coap_ctx, &request));\n}\n\n#endif // defined(AVS_UNIT_TESTING) && defined(WITH_AVS_COAP_TCP)\n"
  },
  {
    "path": "deps/avs_coap/tests/tcp/setsock.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <avs_coap_init.h>\n\n#if defined(AVS_UNIT_TESTING) && defined(WITH_AVS_COAP_TCP)\n\n#    include \"tests/utils.h\"\n\n#    define MODULE_NAME test\n#    include <avs_coap_x_log_config.h>\n\n#    include \"./helper_functions.h\"\n\nAVS_UNIT_TEST(tcp_setsock, callable_only_once) {\n    test_env_t env __attribute__((cleanup(test_teardown))) = test_setup();\n    // socket already set by test_setup()\n    ASSERT_FAIL(avs_coap_ctx_set_socket(env.coap_ctx, env.mocksock));\n}\n\nAVS_UNIT_TEST(tcp_setsock, cleanup_possible_without_socket) {\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_without_socket();\n    avs_coap_ctx_cleanup(&env.coap_ctx);\n}\n\n#endif // defined(AVS_UNIT_TESTING) && defined(WITH_AVS_COAP_TCP)\n"
  },
  {
    "path": "deps/avs_coap/tests/tcp/utils.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef AVS_COAP_SRC_TCP_TEST_UTILS_H\n#define AVS_COAP_SRC_TCP_TEST_UTILS_H\n\n#define AVS_UNIT_ENABLE_SHORT_ASSERTS\n\n#include <avsystem/coap/coap.h>\n#include <avsystem/commons/avs_unit_test.h>\n\n#include \"tcp/avs_coap_tcp_msg.h\"\n#include \"tcp/avs_coap_tcp_signaling.h\"\n\n#include \"tests/utils.h\"\n\ntypedef struct {\n    avs_coap_tcp_cached_msg_t msg;\n    avs_coap_request_header_t request_header;\n    avs_coap_response_header_t response_header;\n    size_t payload_offset;\n    size_t options_offset;\n    size_t token_offset;\n    size_t size;\n    uint8_t data[];\n} test_msg_t;\n\ntypedef struct test_exchange_struct {\n    const test_msg_t *request;\n    const test_msg_t *response;\n} test_exchange_t;\n\n/* Custody option for Ping and Pong messages. */\n#define CUSTODY .custody_opt = true\n\n#define MAX_MESSAGE_SIZE(Size) .max_msg_size = Size\n\n#define BLOCK_WISE_TRANSFER_CAPABLE .block_wise_transfer_capable = true\n\n#define PAYLOAD_INCOMPLETE .payload_partial = true\n\nstruct coap_msg_args {\n    uint8_t code;\n    avs_coap_token_t token;\n\n    const void *payload;\n    size_t payload_size;\n    bool payload_partial;\n\n    // 15 = arbitrary limit on path segments\n    const char *uri_path[16];\n    const uint16_t *accept;\n    const uint16_t *duplicated_accept;\n    const uint32_t *observe;\n\n    const char uri_host[64];\n\n    size_t max_msg_size;\n    bool block_wise_transfer_capable;\n\n    bool custody_opt;\n\n#ifdef WITH_AVS_COAP_BLOCK\n    const avs_coap_option_block_t block1;\n    const avs_coap_option_block_t block2;\n#endif // WITH_AVS_COAP_BLOCK\n};\n\nstatic inline void add_string_opts(avs_coap_options_t *opts,\n                                   uint16_t opt_num,\n                                   const char *const *const strings) {\n    for (size_t i = 0; strings[i]; ++i) {\n        ASSERT_OK(avs_coap_options_add_string(opts, opt_num, strings[i]));\n    }\n}\n\nstatic inline const test_msg_t *\ncoap_msg__(uint8_t *buf, size_t buf_size, const struct coap_msg_args *args) {\n    char opts_buf[4096];\n    avs_coap_options_t opts =\n            avs_coap_options_create_empty(opts_buf, sizeof(opts_buf));\n\n    if (strlen(args->uri_host)) {\n        ASSERT_OK(avs_coap_options_add_string(&opts, AVS_COAP_OPTION_URI_HOST,\n                                              args->uri_host));\n    }\n\n#ifdef WITH_AVS_COAP_BLOCK\n    if (args->block1.size > 0) {\n        ASSERT_OK(avs_coap_options_add_block(&opts, &args->block1));\n    }\n    if (args->block2.size > 0) {\n        ASSERT_OK(avs_coap_options_add_block(&opts, &args->block2));\n    }\n#endif // WITH_AVS_COAP_BLOCK\n\n    if (args->code == AVS_COAP_CODE_CSM) {\n        if (args->max_msg_size) {\n            ASSERT_OK(\n                    avs_coap_options_add_u32(&opts,\n                                             _AVS_COAP_OPTION_MAX_MESSAGE_SIZE,\n                                             (uint32_t) args->max_msg_size));\n        }\n        if (args->block_wise_transfer_capable) {\n            ASSERT_OK(avs_coap_options_add_empty(\n                    &opts, _AVS_COAP_OPTION_BLOCK_WISE_TRANSFER_CAPABILITY));\n        }\n    }\n\n    add_string_opts(&opts, AVS_COAP_OPTION_URI_PATH, args->uri_path);\n\n    if (args->accept) {\n        ASSERT_OK(avs_coap_options_add_u16(&opts, AVS_COAP_OPTION_ACCEPT,\n                                           *args->accept));\n    }\n\n    if (args->duplicated_accept) {\n        ASSERT_OK(avs_coap_options_add_u16(&opts, AVS_COAP_OPTION_ACCEPT,\n                                           *args->duplicated_accept));\n    }\n    if (args->observe) {\n        ASSERT_OK(avs_coap_options_add_u32(&opts, AVS_COAP_OPTION_OBSERVE,\n                                           *args->observe));\n    }\n\n    if (args->custody_opt) {\n        ASSERT_OK(avs_coap_options_add_empty(&opts, _AVS_COAP_OPTION_CUSTODY));\n    }\n\n    ASSERT_EQ((uintptr_t) buf % AVS_ALIGNOF(size_t), 0);\n    test_msg_t *test_msg = (test_msg_t *) buf;\n\n    test_msg->msg = (avs_coap_tcp_cached_msg_t) {\n        .content = (avs_coap_borrowed_msg_t) {\n            .code = args->code,\n            .token = args->token,\n            .options = opts,\n            .payload = args->payload,\n            .payload_size = args->payload_size\n        },\n        // Hack for test purposes:\n        // There may be more than one byte remaining. We don't care, because\n        // it's used only for setting a boolean flag passed to request handler.\n        .remaining_bytes = (size_t) args->payload_partial\n    };\n\n    uint8_t header_buf[_AVS_COAP_TCP_MAX_HEADER_LENGTH];\n    avs_coap_tcp_header_t header =\n            _avs_coap_tcp_header_init(args->payload_size, opts.size,\n                                      args->token.size, args->code);\n    size_t header_size = _avs_coap_tcp_header_serialize(&header, header_buf,\n                                                        sizeof(header_buf));\n    test_msg->token_offset = header_size;\n\n    size_t initial_size = buf_size - sizeof(test_msg_t);\n    bytes_appender_t appender = {\n        .write_ptr = test_msg->data,\n        .bytes_left = initial_size\n    };\n\n    ASSERT_OK(_avs_coap_bytes_append(&appender, header_buf, header_size));\n    ASSERT_OK(_avs_coap_bytes_append(&appender,\n                                     test_msg->msg.content.token.bytes,\n                                     test_msg->msg.content.token.size));\n    test_msg->options_offset = initial_size - appender.bytes_left;\n    ASSERT_OK(_avs_coap_bytes_append(&appender,\n                                     test_msg->msg.content.options.begin,\n                                     test_msg->msg.content.options.size));\n    if (test_msg->msg.content.payload_size) {\n        ASSERT_OK(\n                _avs_coap_bytes_append(&appender, &AVS_COAP_PAYLOAD_MARKER, 1));\n    }\n    test_msg->payload_offset = initial_size - appender.bytes_left;\n    ASSERT_OK(_avs_coap_bytes_append(&appender, test_msg->msg.content.payload,\n                                     test_msg->msg.content.payload_size));\n    test_msg->size = initial_size - appender.bytes_left;\n\n    // Adjust options field to point to test_msg and not to the stack-allocated\n    // buffer. We could use _avs_coap_tcp_msg_parse, but this function is also\n    // used to construct invalid messages, which makes parse fail.\n    test_msg->msg.content.options = (avs_coap_options_t) {\n        .begin = &test_msg->data[test_msg->options_offset],\n        .size = opts.size,\n        .capacity = opts.size\n    };\n\n    test_msg->request_header = (avs_coap_request_header_t) {\n        .code = test_msg->msg.content.code,\n        .options = test_msg->msg.content.options\n    };\n    test_msg->response_header = (avs_coap_response_header_t) {\n        .code = test_msg->msg.content.code,\n        .options = test_msg->msg.content.options\n    };\n\n    return test_msg;\n}\n\n/* Allocates a 64k buffer on the stack, constructs a message inside it and\n * returns the message pointer.\n *\n * @p Code    - suffix of one of AVS_COAP_CODE_* constants, e.g. GET\n *              or BAD_REQUEST.\n * @p Opts... - additional options, e.g. ETAG(), PATH(), QUERY().\n *\n * Example usage:\n * @code\n * const avs_coap_msg_t *msg = COAP_MSG(GET, ID(0), NO_PAYLOAD);\n * @endcode\n */\n#define COAP_MSG(Code, ... /* Payload, Opts... */)                           \\\n    coap_msg__(                                                              \\\n            (uint8_t *) (size_t[(65535 + sizeof(size_t)) / sizeof(size_t)]){ \\\n                    0 },                                                     \\\n            65536,                                                           \\\n            &(struct coap_msg_args) {                                        \\\n                .code = CODE__(Code), __VA_ARGS__                            \\\n            })\n\n#endif // AVS_COAP_SRC_TCP_TEST_UTILS_H\n"
  },
  {
    "path": "deps/avs_coap/tests/udp/async_client.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <avs_coap_init.h>\n\n#if defined(AVS_UNIT_TESTING) && defined(WITH_AVS_COAP_UDP)\n\n#    include <avsystem/commons/avs_errno.h>\n\n#    include <avsystem/coap/coap.h>\n\n#    define MODULE_NAME test\n#    include <avs_coap_x_log_config.h>\n\n#    include \"./utils.h\"\n\nAVS_UNIT_TEST(udp_async_client, send_request_empty_get) {\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_default();\n\n    const test_msg_t *request = COAP_MSG(CON, GET, ID(0), TOKEN(nth_token(0)));\n    const test_msg_t *response =\n            COAP_MSG(ACK, CONTENT, ID(0), TOKEN(nth_token(0)));\n    avs_coap_exchange_id_t id;\n\n    // a request should be sent\n    ASSERT_OK(avs_coap_client_send_async_request(\n            env.coap_ctx, &id, &request->request_header, NULL, NULL,\n            test_response_handler, &env.expects_list));\n    ASSERT_TRUE(avs_coap_exchange_id_valid(id));\n\n    expect_send(&env, request);\n    avs_sched_run(env.sched);\n\n    // receiving response should make the context call handler\n    expect_recv(&env, response);\n    expect_handler_call(&env, &id, AVS_COAP_CLIENT_REQUEST_OK, response);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n}\n\nAVS_UNIT_TEST(udp_async_client, send_non_request) {\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_default();\n\n    const test_msg_t *request = COAP_MSG(NON, GET, ID(0), TOKEN(nth_token(0)));\n    const test_msg_t *response =\n            COAP_MSG(ACK, CONTENT, ID(0), TOKEN(nth_token(0)));\n    avs_coap_exchange_id_t id;\n\n    // a request should be sent\n    expect_send(&env, request);\n    ASSERT_OK(avs_coap_client_send_async_request(env.coap_ctx, &id,\n                                                 &request->request_header, NULL,\n                                                 NULL, NULL, NULL));\n\n    // A response to NON request is not expected and should be ignored.\n    // Because CoAP context does not associate any state with sent NON\n    // requests, no ID is returned.\n    ASSERT_FALSE(avs_coap_exchange_id_valid(id));\n\n    expect_recv(&env, response);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n}\n\nAVS_UNIT_TEST(udp_async_client, send_request_multiple_response_in_order) {\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_default();\n\n    const test_msg_t *requests[] = {\n        COAP_MSG(CON, GET, ID(0), TOKEN(nth_token(0))),\n        COAP_MSG(CON, PUT, ID(1), TOKEN(nth_token(1))),\n        COAP_MSG(CON, POST, ID(2), TOKEN(nth_token(2)))\n    };\n    const test_msg_t *responses[] = {\n        COAP_MSG(ACK, CONTENT, ID(0), TOKEN(nth_token(0))),\n        COAP_MSG(ACK, BAD_REQUEST, ID(1), TOKEN(nth_token(1))),\n        COAP_MSG(RST, EMPTY, ID(2), NO_PAYLOAD),\n    };\n    avs_coap_exchange_id_t ids[AVS_ARRAY_SIZE(requests)];\n\n    for (size_t i = 0; i < AVS_ARRAY_SIZE(requests); ++i) {\n        // send each request\n        ASSERT_OK(avs_coap_client_send_async_request(\n                env.coap_ctx, &ids[i], &requests[i]->request_header, NULL, NULL,\n                test_response_handler, &env.expects_list));\n        ASSERT_TRUE(avs_coap_exchange_id_valid(ids[i]));\n\n        expect_send(&env, requests[i]);\n        avs_sched_run(env.sched);\n    }\n\n    expect_recv(&env, responses[0]);\n    expect_handler_call(&env, &ids[0], AVS_COAP_CLIENT_REQUEST_OK,\n                        responses[0]);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n\n    expect_recv(&env, responses[1]);\n    expect_handler_call(&env, &ids[1], AVS_COAP_CLIENT_REQUEST_OK,\n                        responses[1]);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n\n    expect_recv(&env, responses[2]);\n    expect_handler_call(&env, &ids[2], AVS_COAP_CLIENT_REQUEST_FAIL, NULL);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n}\n\nAVS_UNIT_TEST(udp_async_client, send_request_separate_response) {\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_default();\n\n    const test_msg_t *request = COAP_MSG(CON, GET, ID(0), TOKEN(nth_token(0)));\n    const test_msg_t *separate_ack0 = COAP_MSG(ACK, EMPTY, ID(0), NO_PAYLOAD);\n    const test_msg_t *response =\n            COAP_MSG(CON, CONTENT, ID(1), TOKEN(nth_token(0)));\n    const test_msg_t *separate_ack1 = COAP_MSG(ACK, EMPTY, ID(1), NO_PAYLOAD);\n\n    avs_coap_exchange_id_t id;\n\n    // a request should be sent\n    ASSERT_OK(avs_coap_client_send_async_request(\n            env.coap_ctx, &id, &request->request_header, NULL, NULL,\n            test_response_handler, &env.expects_list));\n    ASSERT_TRUE(avs_coap_exchange_id_valid(id));\n\n    expect_send(&env, request);\n    avs_sched_run(env.sched);\n\n    // receiving separate ACK should not call the handler yet\n    expect_recv(&env, separate_ack0);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n\n    // handler should be called after receiving the actual response\n    // the library should also send separate ACK\n    expect_recv(&env, response);\n    expect_send(&env, separate_ack1);\n    expect_handler_call(&env, &id, AVS_COAP_CLIENT_REQUEST_OK, response);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n}\n\nAVS_UNIT_TEST(udp_async_client, send_request_separate_response_failed_to_send) {\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_default();\n\n    const test_msg_t *request = COAP_MSG(CON, GET, ID(0), TOKEN(nth_token(0)));\n    const test_msg_t *separate_ack0 = COAP_MSG(ACK, EMPTY, ID(0), NO_PAYLOAD);\n    const test_msg_t *response =\n            COAP_MSG(CON, CONTENT, ID(1), TOKEN(nth_token(0)));\n\n    avs_coap_exchange_id_t id;\n\n    // a request should be sent\n    ASSERT_OK(avs_coap_client_send_async_request(\n            env.coap_ctx, &id, &request->request_header, NULL, NULL,\n            test_response_handler, &env.expects_list));\n    ASSERT_TRUE(avs_coap_exchange_id_valid(id));\n\n    expect_send(&env, request);\n    avs_sched_run(env.sched);\n\n    // receiving separate ACK should not call the handler yet\n    expect_recv(&env, separate_ack0);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n\n    // handler should be called after receiving the actual response\n    // the library should also send separate ACK, but it fails, thus\n    // the exchange fails.\n    expect_recv(&env, response);\n    avs_unit_mocksock_output_fail(env.mocksock, avs_errno(AVS_ECONNREFUSED));\n    expect_handler_call(&env, &id, AVS_COAP_CLIENT_REQUEST_FAIL, NULL);\n    ASSERT_FAIL(\n            avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n}\n\nAVS_UNIT_TEST(udp_async_client, send_request_separate_response_without_ack) {\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_default();\n\n    const test_msg_t *request = COAP_MSG(CON, GET, ID(0), TOKEN(nth_token(0)));\n    const test_msg_t *response =\n            COAP_MSG(CON, CONTENT, ID(1), TOKEN(nth_token(0)));\n    const test_msg_t *separate_ack1 = COAP_MSG(ACK, EMPTY, ID(1), NO_PAYLOAD);\n\n    avs_coap_exchange_id_t id;\n\n    // a request should be sent\n    ASSERT_OK(avs_coap_client_send_async_request(\n            env.coap_ctx, &id, &request->request_header, NULL, NULL,\n            test_response_handler, &env.expects_list));\n    ASSERT_TRUE(avs_coap_exchange_id_valid(id));\n\n    expect_send(&env, request);\n    avs_sched_run(env.sched);\n\n    // handler should be called after receiving the actual response even if\n    // it's a Separate Response and separate ACK was not seen\n    //\n    // the library should also send separate ACK\n    expect_recv(&env, response);\n    expect_send(&env, separate_ack1);\n    expect_handler_call(&env, &id, AVS_COAP_CLIENT_REQUEST_OK, response);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n}\n\nAVS_UNIT_TEST(udp_async_client, send_request_separate_non_response) {\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_default();\n\n    const test_msg_t *request = COAP_MSG(CON, GET, ID(0), TOKEN(nth_token(0)));\n    const test_msg_t *response =\n            COAP_MSG(NON, CONTENT, ID(1), TOKEN(nth_token(0)));\n\n    avs_coap_exchange_id_t id;\n\n    // a request should be sent\n    ASSERT_OK(avs_coap_client_send_async_request(\n            env.coap_ctx, &id, &request->request_header, NULL, NULL,\n            test_response_handler, &env.expects_list));\n    ASSERT_TRUE(avs_coap_exchange_id_valid(id));\n\n    expect_send(&env, request);\n    avs_sched_run(env.sched);\n\n    // handler should be called after receiving the actual response even if\n    // it's a Separate Response and separate ACK was not seen\n    //\n    // the library should NOT send ACK for NON response\n    expect_recv(&env, response);\n    expect_handler_call(&env, &id, AVS_COAP_CLIENT_REQUEST_OK, response);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n}\n\nAVS_UNIT_TEST(udp_async_client, send_request_put_with_payload) {\n#    define CONTENT \"shut up and take my payload\"\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_default();\n\n    test_payload_writer_args_t test_payload = {\n        .payload = CONTENT,\n        .payload_size = sizeof(CONTENT) - 1\n    };\n\n    const test_msg_t *request =\n            COAP_MSG(CON, PUT, ID(0), TOKEN(nth_token(0)), PAYLOAD(CONTENT));\n    const test_msg_t *response =\n            COAP_MSG(ACK, CHANGED, ID(0), TOKEN(nth_token(0)));\n\n    avs_coap_exchange_id_t id;\n\n    // a request should be sent\n    ASSERT_OK(avs_coap_client_send_async_request(\n            env.coap_ctx, &id, &request->request_header, test_payload_writer,\n            &test_payload, test_response_handler, &env.expects_list));\n    ASSERT_TRUE(avs_coap_exchange_id_valid(id));\n\n    expect_send(&env, request);\n    avs_sched_run(env.sched);\n\n    // receiving response should make the context call handler\n    expect_recv(&env, response);\n    expect_handler_call(&env, &id, AVS_COAP_CLIENT_REQUEST_OK, response);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n\n#    undef CONTENT\n}\n\nAVS_UNIT_TEST(udp_async_client, send_request_multiple_with_nstart) {\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_with_nstart(1);\n\n    const test_msg_t *requests[] = {\n        COAP_MSG(CON, GET, ID(0), TOKEN(nth_token(0))),\n        COAP_MSG(CON, PUT, ID(1), TOKEN(nth_token(1))),\n        COAP_MSG(CON, POST, ID(2), TOKEN(nth_token(2)))\n    };\n    const test_msg_t *responses[] = {\n        COAP_MSG(ACK, CONTENT, ID(0), TOKEN(nth_token(0))),\n        COAP_MSG(ACK, BAD_REQUEST, ID(1), TOKEN(nth_token(1))),\n        COAP_MSG(RST, EMPTY, ID(2), NO_PAYLOAD),\n    };\n    AVS_STATIC_ASSERT(AVS_ARRAY_SIZE(requests) == AVS_ARRAY_SIZE(responses),\n                      mismatched_requests_responses_lists);\n\n    avs_coap_exchange_id_t ids[AVS_ARRAY_SIZE(requests)];\n\n    // Start all requests. Only the first one should be sent because of NSTART.\n    for (size_t i = 0; i < AVS_ARRAY_SIZE(requests); ++i) {\n        ASSERT_OK(avs_coap_client_send_async_request(\n                env.coap_ctx, &ids[i], &requests[i]->request_header, NULL, NULL,\n                test_response_handler, &env.expects_list));\n        ASSERT_TRUE(avs_coap_exchange_id_valid(ids[i]));\n    }\n\n    expect_send(&env, requests[0]);\n    avs_sched_run(env.sched);\n\n    // handlers should be called only after receiving responses\n    expect_recv(&env, responses[0]);\n    expect_handler_call(&env, &ids[0], AVS_COAP_CLIENT_REQUEST_OK,\n                        responses[0]);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n\n    expect_send(&env, requests[1]);\n    avs_sched_run(env.sched);\n\n    expect_recv(&env, responses[1]);\n    expect_handler_call(&env, &ids[1], AVS_COAP_CLIENT_REQUEST_OK,\n                        responses[1]);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n\n    expect_send(&env, requests[2]);\n    avs_sched_run(env.sched);\n\n    expect_recv(&env, responses[2]);\n    expect_handler_call(&env, &ids[2], AVS_COAP_CLIENT_REQUEST_FAIL, NULL);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n}\n\nAVS_UNIT_TEST(udp_async_client, send_request_with_retransmissions) {\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_default();\n\n    const test_msg_t *request = COAP_MSG(CON, GET, ID(0), TOKEN(nth_token(0)));\n    const test_msg_t *response =\n            COAP_MSG(ACK, CONTENT, ID(0), TOKEN(nth_token(0)));\n    avs_coap_exchange_id_t id;\n\n    expect_send(&env, request);\n    ASSERT_OK(avs_coap_client_send_async_request(\n            env.coap_ctx, &id, &request->request_header, NULL, NULL,\n            test_response_handler, &env.expects_list));\n    ASSERT_TRUE(avs_coap_exchange_id_valid(id));\n\n    // no jobs should be executed yet\n    avs_sched_run(env.sched);\n    avs_coap_stats_t stats = avs_coap_get_stats(env.coap_ctx);\n    ASSERT_EQ(stats.outgoing_retransmissions_count, 0);\n\n    // retransmissions should be handled by the scheduler\n    _avs_mock_clock_advance(avs_sched_time_to_next(env.sched));\n    expect_send(&env, request);\n    avs_sched_run(env.sched);\n    stats = avs_coap_get_stats(env.coap_ctx);\n    ASSERT_EQ(stats.outgoing_retransmissions_count, 1);\n    ASSERT_EQ(stats.incoming_retransmissions_count, 0);\n\n    // the handler should only be called at this point\n    expect_recv(&env, response);\n    expect_handler_call(&env, &id, AVS_COAP_CLIENT_REQUEST_OK, response);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n}\n\nAVS_UNIT_TEST(udp_async_client, fail_if_no_response_after_retransmissions) {\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_default();\n\n    const test_msg_t *request = COAP_MSG(CON, GET, ID(0), TOKEN(nth_token(0)));\n    avs_coap_exchange_id_t id;\n\n    // send original request\n    expect_send(&env, request);\n    ASSERT_OK(avs_coap_client_send_async_request(\n            env.coap_ctx, &id, &request->request_header, NULL, NULL,\n            test_response_handler, &env.expects_list));\n    ASSERT_TRUE(avs_coap_exchange_id_valid(id));\n\n    // no jobs should be executed yet\n    avs_sched_run(env.sched);\n\n    // retransmissions should be handled by the scheduler\n    for (size_t i = 0; i < env.tx_params.max_retransmit; ++i) {\n        _avs_mock_clock_advance(avs_sched_time_to_next(env.sched));\n        expect_send(&env, request);\n        avs_sched_run(env.sched);\n        avs_coap_stats_t stats = avs_coap_get_stats(env.coap_ctx);\n        ASSERT_EQ(stats.outgoing_retransmissions_count, i + 1);\n        ASSERT_EQ(stats.incoming_retransmissions_count, 0);\n    }\n\n    // At this point all retransmissions are done, and we are waiting for a\n    // response to the last retransmission. After this time, scheduler should\n    // call user-defined handler indicating failure.\n    expect_handler_call(&env, &id, AVS_COAP_CLIENT_REQUEST_FAIL, NULL);\n    _avs_mock_clock_advance(avs_sched_time_to_next(env.sched));\n    avs_sched_run(env.sched);\n}\n\nAVS_UNIT_TEST(udp_async_client, cancel_single) {\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_default();\n\n    const test_msg_t *request = COAP_MSG(CON, GET, ID(0), TOKEN(nth_token(0)));\n    avs_coap_exchange_id_t id;\n\n    // send original request\n    ASSERT_OK(avs_coap_client_send_async_request(\n            env.coap_ctx, &id, &request->request_header, NULL, NULL,\n            test_response_handler, &env.expects_list));\n    ASSERT_TRUE(avs_coap_exchange_id_valid(id));\n\n    expect_send(&env, request);\n    avs_sched_run(env.sched);\n\n    // a retransmission job should be scheduled\n    ASSERT_TRUE(avs_time_monotonic_valid(avs_sched_time_of_next(env.sched)));\n\n    expect_handler_call(&env, &id, AVS_COAP_CLIENT_REQUEST_CANCEL, NULL);\n    avs_coap_exchange_cancel(env.coap_ctx, id);\n}\n\nAVS_UNIT_TEST(udp_async_client, invalid_cancel) {\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_default();\n\n    avs_coap_exchange_id_t id = {\n        .value = 42\n    };\n    avs_coap_exchange_cancel(env.coap_ctx, id);\n}\n\nAVS_UNIT_TEST(udp_async_client, invalid_send) {\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_default();\n\n    avs_coap_exchange_id_t id;\n    const test_msg_t *response =\n            COAP_MSG(CON, CONTENT, ID(0), TOKEN(nth_token(0)));\n    ASSERT_FAIL(avs_coap_client_send_async_request(\n            env.coap_ctx, &id, &response->request_header, NULL, NULL,\n            test_response_handler, NULL));\n}\n\nAVS_UNIT_TEST(udp_async_client, malformed_packets_are_ignored) {\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_default();\n\n    const test_msg_t *request = COAP_MSG(CON, GET, ID(0), TOKEN(nth_token(0)));\n    const test_msg_t *response =\n            COAP_MSG(ACK, CONTENT, ID(0), TOKEN(nth_token(0)));\n\n    avs_coap_exchange_id_t id;\n\n    // a request should be sent\n    ASSERT_OK(avs_coap_client_send_async_request(\n            env.coap_ctx, &id, &request->request_header, NULL, NULL,\n            test_response_handler, &env.expects_list));\n    ASSERT_TRUE(avs_coap_exchange_id_valid(id));\n\n    expect_send(&env, request);\n    avs_sched_run(env.sched);\n\n    // garbage should be ignored\n    avs_unit_mocksock_input(env.mocksock, \"\\x00\", 1);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n\n    avs_unit_mocksock_input(env.mocksock, \"\\x40\\x00\\x00\\x00\\x00\", 5);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n\n    // receiving response should make the context call handler\n    expect_recv(&env, response);\n    expect_handler_call(&env, &id, AVS_COAP_CLIENT_REQUEST_OK, response);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n}\n\nAVS_UNIT_TEST(udp_async_client, cancels_all_exchanges_on_cleanup) {\n    test_env_t env __attribute__((cleanup(test_teardown_late_expects_check))) =\n            test_setup_deterministic();\n\n    const test_msg_t *requests[] = {\n        COAP_MSG(CON, GET, ID(0), TOKEN(nth_token(0))),\n        COAP_MSG(CON, PUT, ID(1), TOKEN(nth_token(1))),\n        COAP_MSG(CON, POST, ID(2), TOKEN(nth_token(2)))\n    };\n    avs_coap_exchange_id_t ids[AVS_ARRAY_SIZE(requests)];\n\n    // only the first one should be sent; others are suspended because of\n    // NSTART = 1\n    for (size_t i = 0; i < AVS_ARRAY_SIZE(requests); ++i) {\n        ASSERT_OK(avs_coap_client_send_async_request(\n                env.coap_ctx, &ids[i], &requests[i]->request_header, NULL, NULL,\n                test_response_handler, &env.expects_list));\n        ASSERT_TRUE(avs_coap_exchange_id_valid(ids[i]));\n    }\n\n    expect_send(&env, requests[0]);\n    avs_sched_run(env.sched);\n\n    expect_handler_call(&env, &ids[0], AVS_COAP_CLIENT_REQUEST_CANCEL, NULL);\n    expect_handler_call(&env, &ids[1], AVS_COAP_CLIENT_REQUEST_CANCEL, NULL);\n    expect_handler_call(&env, &ids[2], AVS_COAP_CLIENT_REQUEST_CANCEL, NULL);\n    // test_teardown will call avs_coap_ctx_cleanup() that fullfills expected\n    // handler calls. If it does not, this test will fail on ASSERT_NULL()\n    // in test_teardown.\n}\n\nAVS_UNIT_TEST(udp_async_client,\n              send_request_piggybacked_response_matched_by_id_and_token) {\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_default();\n\n    const test_msg_t *request = COAP_MSG(CON, GET, ID(0), TOKEN(nth_token(0)));\n    const test_msg_t *res_bad_id =\n            COAP_MSG(ACK, CONTENT, ID(1), TOKEN(nth_token(0)));\n    const test_msg_t *res_bad_token =\n            COAP_MSG(ACK, CONTENT, ID(0), TOKEN(nth_token(1)));\n    const test_msg_t *response =\n            COAP_MSG(ACK, CONTENT, ID(0), TOKEN(nth_token(0)));\n\n    avs_coap_exchange_id_t id;\n\n    // a request should be sent\n    ASSERT_OK(avs_coap_client_send_async_request(\n            env.coap_ctx, &id, &request->request_header, NULL, NULL,\n            test_response_handler, &env.expects_list));\n    ASSERT_TRUE(avs_coap_exchange_id_valid(id));\n\n    expect_send(&env, request);\n    avs_sched_run(env.sched);\n\n    // Piggybacked Response with mismatched message ID or token should be\n    // ignored as invalid\n    expect_recv(&env, res_bad_id);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n\n    expect_recv(&env, res_bad_token);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n\n    // No response received yet, we should see a retransmission\n    _avs_mock_clock_advance(avs_sched_time_to_next(env.sched));\n    expect_send(&env, request);\n    avs_sched_run(env.sched);\n\n    // handler should be called after receiving the actual response\n    // the library should also send separate ACK\n    expect_recv(&env, response);\n    expect_handler_call(&env, &id, AVS_COAP_CLIENT_REQUEST_OK, response);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n}\n\nAVS_UNIT_TEST(udp_async_client,\n              repeated_non_repeatable_critical_option_in_piggybacked_response) {\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_default();\n\n    const test_msg_t *request = COAP_MSG(CON, GET, ID(0), TOKEN(nth_token(0)));\n    // Accept option in response only for test purposes.\n    const test_msg_t *response =\n            COAP_MSG(ACK, CONTENT, ID(0), TOKEN(nth_token(0)), ACCEPT(1),\n                     DUPLICATED_ACCEPT(2));\n\n    avs_coap_exchange_id_t id;\n    avs_coap_client_send_async_request(env.coap_ctx, &id,\n                                       &(avs_coap_request_header_t) {\n                                           .code = request->msg.header.code\n                                       },\n                                       NULL, NULL, test_response_handler,\n                                       &env.expects_list);\n\n    expect_send(&env, request);\n    avs_sched_run(env.sched);\n\n    expect_recv(&env, response);\n    expect_handler_call(&env, &id, AVS_COAP_CLIENT_REQUEST_FAIL, NULL);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n}\n\nAVS_UNIT_TEST(udp_async_client,\n              repeated_non_repeatable_critical_option_in_response) {\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_default();\n\n    const test_msg_t *request = COAP_MSG(CON, GET, ID(0), TOKEN(nth_token(0)));\n    // Accept option in response only for test purposes.\n    const test_msg_t *ack = COAP_MSG(ACK, EMPTY, ID(0));\n    const test_msg_t *response =\n            COAP_MSG(CON, CONTENT, ID(0), TOKEN(nth_token(0)), ACCEPT(1),\n                     DUPLICATED_ACCEPT(2));\n    const test_msg_t *reset = COAP_MSG(RST, EMPTY, ID(0));\n\n    avs_coap_exchange_id_t id;\n    avs_coap_client_send_async_request(env.coap_ctx, &id,\n                                       &(avs_coap_request_header_t) {\n                                           .code = request->msg.header.code\n                                       },\n                                       NULL, NULL, test_response_handler,\n                                       &env.expects_list);\n\n    expect_send(&env, request);\n    avs_sched_run(env.sched);\n\n    expect_recv(&env, ack);\n    expect_has_buffered_data_check(&env, true);\n    expect_recv(&env, response);\n    expect_has_buffered_data_check(&env, false);\n    expect_send(&env, reset);\n    expect_handler_call(&env, &id, AVS_COAP_CLIENT_REQUEST_FAIL, NULL);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n}\n\nAVS_UNIT_TEST(udp_async_client, invalid_ack_should_be_ignored) {\n    test_env_t env __attribute__((cleanup(test_teardown_late_expects_check))) =\n            test_setup_default();\n\n    const test_msg_t *request = COAP_MSG(CON, GET, ID(0), TOKEN(nth_token(0)));\n    const test_msg_t *request_ack =\n            COAP_MSG(ACK, EMPTY, ID(0), TOKEN(nth_token(0)));\n\n    avs_coap_exchange_id_t id;\n    ASSERT_OK(avs_coap_client_send_async_request(\n            env.coap_ctx, &id, &request->request_header, NULL, NULL,\n            test_response_handler, &env.expects_list));\n\n    expect_send(&env, request);\n    avs_sched_run(env.sched);\n\n    expect_recv(&env, request_ack);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n    _avs_mock_clock_advance(avs_sched_time_to_next(env.sched));\n\n    // retransmission\n    expect_send(&env, request);\n    avs_sched_run(env.sched);\n\n    expect_handler_call(&env, &id, AVS_COAP_CLIENT_REQUEST_CANCEL, NULL);\n}\n\nAVS_UNIT_TEST(udp_async_client, send_error) {\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_default();\n\n    const test_msg_t *request = COAP_MSG(CON, GET, ID(0), TOKEN(nth_token(0)));\n    avs_coap_exchange_id_t id;\n\n    ASSERT_OK(avs_coap_client_send_async_request(\n            env.coap_ctx, &id, &request->request_header, NULL, NULL,\n            test_response_handler, &env.expects_list));\n\n    avs_unit_mocksock_output_fail(env.mocksock, avs_errno(AVS_ECONNREFUSED));\n    expect_handler_call(&env, &id, AVS_COAP_CLIENT_REQUEST_FAIL, NULL);\n    avs_sched_run(env.sched);\n}\n\n#    ifdef WITH_AVS_COAP_BLOCK\n\nAVS_UNIT_TEST(udp_async_client, block_response) {\n#        define REQUEST_PAYLOAD \"gib payload pls\"\n#        define DATA_33B \"123456789 123456789 123456789 123\"\n\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_default();\n\n    test_payload_writer_args_t test_payload = {\n        .payload = REQUEST_PAYLOAD,\n        .payload_size = sizeof(REQUEST_PAYLOAD) - 1\n    };\n\n    const test_msg_t *requests[] = {\n        COAP_MSG(CON, GET, ID(0), TOKEN(nth_token(0)),\n                 PAYLOAD(REQUEST_PAYLOAD)),\n        COAP_MSG(CON, GET, ID(1), TOKEN(nth_token(1)), BLOCK2_REQ(1, 16)),\n        COAP_MSG(CON, GET, ID(2), TOKEN(nth_token(2)), BLOCK2_REQ(2, 16)),\n    };\n    const test_msg_t *responses[] = {\n        COAP_MSG(ACK, CONTENT, ID(0), TOKEN(nth_token(0)),\n                 BLOCK2_RES(0, 16, DATA_33B)),\n        COAP_MSG(ACK, CONTENT, ID(1), TOKEN(nth_token(1)),\n                 BLOCK2_RES(1, 16, DATA_33B)),\n        COAP_MSG(ACK, CONTENT, ID(2), TOKEN(nth_token(2)),\n                 BLOCK2_RES(2, 16, DATA_33B)),\n    };\n    AVS_STATIC_ASSERT(AVS_ARRAY_SIZE(requests) == AVS_ARRAY_SIZE(responses),\n                      mismatched_requests_responses_lists);\n\n    avs_coap_exchange_id_t id;\n\n    // start the request\n    ASSERT_OK(avs_coap_client_send_async_request(\n            env.coap_ctx, &id, &requests[0]->request_header,\n            test_payload_writer, &test_payload, test_response_handler,\n            &env.expects_list));\n    ASSERT_TRUE(avs_coap_exchange_id_valid(id));\n\n    expect_send(&env, requests[0]);\n    avs_sched_run(env.sched);\n\n    // handlers should be called only after receiving responses\n\n    expect_recv(&env, responses[0]);\n    expect_send(&env, requests[1]);\n    expect_handler_call(&env, &id, AVS_COAP_CLIENT_REQUEST_PARTIAL_CONTENT,\n                        responses[0]);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n\n    expect_recv(&env, responses[1]);\n    expect_send(&env, requests[2]);\n    expect_handler_call(&env, &id, AVS_COAP_CLIENT_REQUEST_PARTIAL_CONTENT,\n                        responses[1]);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n\n    expect_recv(&env, responses[2]);\n    expect_handler_call(&env, &id, AVS_COAP_CLIENT_REQUEST_OK, responses[2]);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n\n#        undef DATA_33B\n#        undef REQUEST_PAYLOAD\n}\n\nAVS_UNIT_TEST(udp_async_client, block_response_interrupt) {\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_default();\n\n    const test_msg_t *request =\n            COAP_MSG(CON, GET, ID(0), TOKEN(nth_token(0)), NO_PAYLOAD);\n    const test_msg_t *response =\n            COAP_MSG(ACK, CONTENT, ID(0), TOKEN(nth_token(0)),\n                     BLOCK2_RES(0, 16, DATA_1KB));\n\n    avs_coap_exchange_id_t id;\n\n    // start the request\n    ASSERT_OK(avs_coap_client_send_async_request(\n            env.coap_ctx, &id, &request->request_header, NULL, NULL,\n            test_response_abort_handler, &env.expects_list));\n    ASSERT_TRUE(avs_coap_exchange_id_valid(id));\n\n    expect_send(&env, request);\n    avs_sched_run(env.sched);\n\n    // the used-defined handler aborts the exchange, causing another handler\n    // call\n    expect_recv(&env, response);\n    expect_handler_call(&env, &id, AVS_COAP_CLIENT_REQUEST_PARTIAL_CONTENT,\n                        response);\n    expect_handler_call(&env, &id, AVS_COAP_CLIENT_REQUEST_CANCEL, NULL);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n}\n\nAVS_UNIT_TEST(udp_async_client, block_response_last_block_without_block2_opt) {\n#        define REQUEST_PAYLOAD \"gib payload pls\"\n#        define DATA_17B \"123456789 1234567\"\n\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_with_max_retransmit(1);\n\n    test_payload_writer_args_t test_payload = {\n        .payload = REQUEST_PAYLOAD,\n        .payload_size = sizeof(REQUEST_PAYLOAD) - 1\n    };\n\n    // Receiving a response without BLOCK2 should cause a failure\n    const test_msg_t *requests[] = {\n        COAP_MSG(CON, GET, ID(0), TOKEN(nth_token(0)),\n                 PAYLOAD(REQUEST_PAYLOAD)),\n        COAP_MSG(CON, GET, ID(1), TOKEN(nth_token(1)), BLOCK2_REQ(1, 16)),\n    };\n    const test_msg_t *responses[] = {\n        COAP_MSG(ACK, CONTENT, ID(0), TOKEN(nth_token(0)),\n                 BLOCK2_RES(0, 16, DATA_17B)),\n        COAP_MSG(ACK, CONTENT, ID(1), TOKEN(nth_token(1)), PAYLOAD(\"1\")),\n    };\n\n    avs_coap_exchange_id_t id;\n\n    // start the request\n    ASSERT_OK(avs_coap_client_send_async_request(\n            env.coap_ctx, &id, &requests[0]->request_header,\n            test_payload_writer, &test_payload, test_response_handler,\n            &env.expects_list));\n    ASSERT_TRUE(avs_coap_exchange_id_valid(id));\n\n    expect_send(&env, requests[0]);\n    avs_sched_run(env.sched);\n\n    // handlers should be called only after receiving responses\n\n    // receiving first response should make the context call handler and send\n    // request for next block\n    expect_recv(&env, responses[0]);\n    expect_send(&env, requests[1]);\n    expect_handler_call(&env, &id, AVS_COAP_CLIENT_REQUEST_PARTIAL_CONTENT,\n                        responses[0]);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n\n    // receiving a response without BLOCK2 should cause exchange failure\n    expect_recv(&env, responses[1]);\n    expect_handler_call(&env, &id, AVS_COAP_CLIENT_REQUEST_FAIL, NULL);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n\n#        undef DATA_17B\n#        undef REQUEST_PAYLOAD\n}\n\nAVS_UNIT_TEST(udp_async_client, block_request_with_explicit_block1) {\n#        define REQUEST_PAYLOAD DATA_1KB DATA_1KB \"?\"\n\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_with_max_retransmit(0);\n\n    test_payload_writer_args_t test_payload = {\n        .payload = REQUEST_PAYLOAD,\n        .payload_size = sizeof(REQUEST_PAYLOAD) - 1\n    };\n\n    const test_msg_t *requests[] = {\n        COAP_MSG(CON, GET, ID(0), TOKEN(nth_token(0)),\n                 BLOCK1_REQ(0, 1024, REQUEST_PAYLOAD)),\n        COAP_MSG(CON, GET, ID(1), TOKEN(nth_token(1)),\n                 BLOCK1_REQ(1, 1024, REQUEST_PAYLOAD)),\n        COAP_MSG(CON, GET, ID(2), TOKEN(nth_token(2)),\n                 BLOCK1_REQ(2, 1024, REQUEST_PAYLOAD)),\n    };\n    const test_msg_t *responses[] = {\n        COAP_MSG(ACK, CONTINUE, ID(0), TOKEN(nth_token(0)),\n                 BLOCK1_RES(0, 1024, true)),\n        COAP_MSG(ACK, CONTINUE, ID(1), TOKEN(nth_token(1)),\n                 BLOCK1_RES(1, 1024, true)),\n        COAP_MSG(ACK, CONTENT, ID(2), TOKEN(nth_token(2)),\n                 BLOCK1_RES(2, 1024, false)),\n    };\n    AVS_STATIC_ASSERT(AVS_ARRAY_SIZE(requests) == AVS_ARRAY_SIZE(responses),\n                      mismatched_requests_responses_lists);\n\n    avs_coap_exchange_id_t id;\n\n    // start the request\n    ASSERT_OK(avs_coap_client_send_async_request(\n            env.coap_ctx, &id, &requests[0]->request_header,\n            test_payload_writer, &test_payload, test_response_handler,\n            &env.expects_list));\n    ASSERT_TRUE(avs_coap_exchange_id_valid(id));\n\n    expect_send(&env, requests[0]);\n    avs_sched_run(env.sched);\n\n    // first Continue\n    expect_recv(&env, responses[0]);\n    expect_send(&env, requests[1]);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n\n    // second Continue\n    expect_recv(&env, responses[1]);\n    expect_send(&env, requests[2]);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n\n    // upon receiving the response, handler should be called and no more\n    // retransmissions scheduled\n    expect_recv(&env, responses[2]);\n    expect_handler_call(&env, &id, AVS_COAP_CLIENT_REQUEST_OK, responses[2]);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n\n#        undef REQUEST_PAYLOAD\n}\n\nAVS_UNIT_TEST(udp_async_client, block_request_with_broken_block1) {\n#        define REQUEST_PAYLOAD DATA_1KB DATA_1KB \"?\"\n\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_with_max_retransmit(0);\n\n    test_payload_writer_args_t test_payload = {\n        .payload = REQUEST_PAYLOAD,\n        .payload_size = sizeof(REQUEST_PAYLOAD) - 1\n    };\n\n    const test_msg_t *requests[] = {\n        COAP_MSG(CON, GET, ID(0), TOKEN(nth_token(0)),\n                 BLOCK1_REQ(0, 1024, REQUEST_PAYLOAD)),\n        COAP_MSG(CON, GET, ID(1), TOKEN(nth_token(1)),\n                 BLOCK1_REQ(1, 1024, REQUEST_PAYLOAD)),\n        COAP_MSG(CON, GET, ID(2), TOKEN(nth_token(2)),\n                 BLOCK1_REQ(2, 1024, REQUEST_PAYLOAD)),\n    };\n    const test_msg_t *responses[] = {\n        COAP_MSG(ACK, CONTINUE, ID(0), TOKEN(nth_token(0)),\n                 BLOCK1_RES(0, 1024, true)),\n        COAP_MSG(ACK, CONTINUE, ID(1), TOKEN(nth_token(1)),\n                 BLOCK1_RES(1, 1024, true)),\n        COAP_MSG(ACK, CONTENT, ID(2), TOKEN(nth_token(2)),\n                 BLOCK1_RES(2, 1024, false)),\n    };\n    AVS_STATIC_ASSERT(AVS_ARRAY_SIZE(requests) == AVS_ARRAY_SIZE(responses),\n                      mismatched_requests_responses_lists);\n\n    avs_coap_exchange_id_t id;\n\n    // set \"has_more\" flag to false in the requested header, even though there\n    // actually is more data - this flag will be overwritten before sending\n    ASSERT_OK(avs_coap_client_send_async_request(\n            env.coap_ctx, &id,\n            &COAP_MSG(CON, GET, ID(0), TOKEN(nth_token(0)),\n                      .block1 = {\n                          .type = AVS_COAP_BLOCK1,\n                          .seq_num = 0,\n                          .size = 1024,\n                          .has_more = false\n                      },\n                      PAYLOAD(REQUEST_PAYLOAD))\n                     ->request_header,\n            test_payload_writer, &test_payload, test_response_handler,\n            &env.expects_list));\n    ASSERT_TRUE(avs_coap_exchange_id_valid(id));\n\n    expect_send(&env, requests[0]);\n    avs_sched_run(env.sched);\n\n    // first Continue\n    expect_recv(&env, responses[0]);\n    expect_send(&env, requests[1]);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n\n    // second Continue\n    expect_recv(&env, responses[1]);\n    expect_send(&env, requests[2]);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n\n    // upon receiving the response, handler should be called and no more\n    // retransmissions scheduled\n    expect_recv(&env, responses[2]);\n    expect_handler_call(&env, &id, AVS_COAP_CLIENT_REQUEST_OK, responses[2]);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n\n#        undef REQUEST_PAYLOAD\n}\n\nAVS_UNIT_TEST(udp_async_client, block_request_without_explicit_block1) {\n#        define REQUEST_PAYLOAD DATA_1KB DATA_1KB \"?\"\n\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_with_max_retransmit(0);\n\n    test_payload_writer_args_t test_payload = {\n        .payload = REQUEST_PAYLOAD,\n        .payload_size = sizeof(REQUEST_PAYLOAD) - 1\n    };\n\n    const avs_coap_request_header_t *request_header_without_block1 =\n            &COAP_MSG(CON, GET, ID(0), TOKEN(nth_token(0)), NO_PAYLOAD)\n                     ->request_header;\n\n    const test_msg_t *requests[] = {\n        COAP_MSG(CON, GET, ID(0), TOKEN(nth_token(0)),\n                 BLOCK1_REQ(0, 1024, REQUEST_PAYLOAD)),\n        COAP_MSG(CON, GET, ID(1), TOKEN(nth_token(1)),\n                 BLOCK1_REQ(1, 1024, REQUEST_PAYLOAD)),\n        COAP_MSG(CON, GET, ID(2), TOKEN(nth_token(2)),\n                 BLOCK1_REQ(2, 1024, REQUEST_PAYLOAD)),\n    };\n    const test_msg_t *responses[] = {\n        COAP_MSG(ACK, CONTINUE, ID(0), TOKEN(nth_token(0)),\n                 BLOCK1_RES(0, 1024, true)),\n        COAP_MSG(ACK, CONTINUE, ID(1), TOKEN(nth_token(1)),\n                 BLOCK1_RES(1, 1024, true)),\n        COAP_MSG(ACK, CONTENT, ID(2), TOKEN(nth_token(2)),\n                 BLOCK1_RES(2, 1024, false)),\n    };\n    AVS_STATIC_ASSERT(AVS_ARRAY_SIZE(requests) == AVS_ARRAY_SIZE(responses),\n                      mismatched_requests_responses_lists);\n\n    avs_coap_exchange_id_t id;\n\n    // start the request\n    ASSERT_OK(avs_coap_client_send_async_request(\n            env.coap_ctx, &id, request_header_without_block1,\n            test_payload_writer, &test_payload, test_response_handler,\n            &env.expects_list));\n    ASSERT_TRUE(avs_coap_exchange_id_valid(id));\n\n    expect_send(&env, requests[0]);\n    avs_sched_run(env.sched);\n\n    // first Continue\n    expect_recv(&env, responses[0]);\n    expect_send(&env, requests[1]);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n\n    // second Continue\n    expect_recv(&env, responses[1]);\n    expect_send(&env, requests[2]);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n\n    // upon receiving the response, handler should be called and no more\n    // retransmissions scheduled\n    expect_recv(&env, responses[2]);\n    expect_handler_call(&env, &id, AVS_COAP_CLIENT_REQUEST_OK, responses[2]);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n\n#        undef REQUEST_PAYLOAD\n}\n\nAVS_UNIT_TEST(udp_async_client, nonconfirmable_block_request) {\n#        define REQUEST_PAYLOAD DATA_1KB DATA_1KB \"?\"\n\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_with_max_retransmit(0);\n\n    test_payload_writer_args_t test_payload = {\n        .payload = REQUEST_PAYLOAD,\n        .payload_size = sizeof(REQUEST_PAYLOAD) - 1\n    };\n\n    const avs_coap_request_header_t *request_header_without_block1 =\n            &COAP_MSG(CON, GET, ID(0), TOKEN(nth_token(0)), NO_PAYLOAD)\n                     ->request_header;\n\n    const test_msg_t *requests[] = {\n        COAP_MSG(NON, GET, ID(0), TOKEN(nth_token(0)),\n                 BLOCK1_REQ(0, 1024, REQUEST_PAYLOAD)),\n        COAP_MSG(NON, GET, ID(1), TOKEN(nth_token(1)),\n                 BLOCK1_REQ(1, 1024, REQUEST_PAYLOAD)),\n        COAP_MSG(NON, GET, ID(2), TOKEN(nth_token(2)),\n                 BLOCK1_REQ(2, 1024, REQUEST_PAYLOAD)),\n    };\n\n    expect_send(&env, requests[0]);\n    expect_send(&env, requests[1]);\n    expect_send(&env, requests[2]);\n    ASSERT_OK(avs_coap_client_send_async_request(\n            env.coap_ctx, NULL, request_header_without_block1,\n            test_payload_writer, &test_payload, NULL, NULL));\n#        undef REQUEST_PAYLOAD\n}\n\nAVS_UNIT_TEST(udp_async_client, block_request_with_cancel_in_payload_writer) {\n#        define REQUEST_PAYLOAD DATA_1KB \"?\"\n\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_with_max_retransmit(0);\n\n    avs_coap_exchange_id_t id;\n\n    test_payload_writer_args_t test_payload = {\n        .payload = REQUEST_PAYLOAD,\n        .payload_size = sizeof(REQUEST_PAYLOAD) - 1,\n        .coap_ctx = env.coap_ctx,\n        .cancel_exchange = false\n    };\n\n    const test_msg_t *request = COAP_MSG(CON, GET, ID(0), TOKEN(nth_token(0)),\n                                         BLOCK1_REQ(0, 1024, REQUEST_PAYLOAD));\n    const test_msg_t *response =\n            COAP_MSG(ACK, CONTINUE, ID(0), TOKEN(nth_token(0)),\n                     BLOCK1_RES(0, 1024, true));\n\n    // start the request\n    ASSERT_OK(avs_coap_client_send_async_request(\n            env.coap_ctx, &id, &request->request_header, test_payload_writer,\n            &test_payload, test_response_handler, &env.expects_list));\n    ASSERT_TRUE(avs_coap_exchange_id_valid(id));\n\n    expect_send(&env, request);\n    avs_sched_run(env.sched);\n\n    // first Continue\n    test_payload.exchange_id = id;\n    test_payload.cancel_exchange = true;\n\n    expect_recv(&env, response);\n    expect_handler_call(&env, &id, AVS_COAP_CLIENT_REQUEST_CANCEL, NULL);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n\n    // after receiving first Continue, payload_writer call is supposed to\n    // cancel the exchange\n\n#        undef REQUEST_PAYLOAD\n}\n\nAVS_UNIT_TEST(udp_async_client, block_request_block1_renegotiation) {\n#        define REQUEST_PAYLOAD DATA_1KB DATA_16B \"?\"\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_with_max_retransmit(0);\n\n    test_payload_writer_args_t test_payload = {\n        .payload = REQUEST_PAYLOAD,\n        .payload_size = sizeof(REQUEST_PAYLOAD) - 1\n    };\n\n    const test_msg_t *requests[] = {\n        COAP_MSG(CON, GET, ID(0), TOKEN(nth_token(0)),\n                 BLOCK1_REQ(0, 1024, REQUEST_PAYLOAD)),\n        COAP_MSG(CON, GET, ID(1), TOKEN(nth_token(1)),\n                 BLOCK1_REQ(64, 16, REQUEST_PAYLOAD)),\n        COAP_MSG(CON, GET, ID(2), TOKEN(nth_token(2)),\n                 BLOCK1_REQ(65, 16, REQUEST_PAYLOAD))\n    };\n    const test_msg_t *responses[] = {\n        COAP_MSG(ACK, CONTINUE, ID(0), TOKEN(nth_token(0)),\n                 BLOCK1_RES(0, 16, true)),\n        COAP_MSG(ACK, CONTINUE, ID(1), TOKEN(nth_token(1)),\n                 BLOCK1_RES(64, 16, true)),\n        COAP_MSG(ACK, CONTENT, ID(2), TOKEN(nth_token(2)),\n                 BLOCK1_RES(65, 16, true))\n    };\n\n    avs_coap_exchange_id_t id;\n\n    // start the request\n    ASSERT_OK(avs_coap_client_send_async_request(\n            env.coap_ctx, &id, &requests[0]->request_header,\n            test_payload_writer, &test_payload, test_response_handler,\n            &env.expects_list));\n    ASSERT_TRUE(avs_coap_exchange_id_valid(id));\n\n    expect_send(&env, requests[0]);\n    avs_sched_run(env.sched);\n\n    expect_recv(&env, responses[0]);\n    expect_send(&env, requests[1]);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n\n    expect_recv(&env, responses[1]);\n    expect_send(&env, requests[2]);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n\n    expect_recv(&env, responses[2]);\n    expect_handler_call(&env, &id, AVS_COAP_CLIENT_REQUEST_OK, responses[2]);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n#        undef REQUEST_PAYLOAD\n}\n\nAVS_UNIT_TEST(udp_async_client, block_request_block2_renegotiation) {\n#        define RESPONSE_PAYLOAD DATA_1KB\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_with_max_retransmit(0);\n\n    const test_msg_t *requests[] = {\n        COAP_MSG(CON, GET, ID(0), TOKEN(nth_token(0)), BLOCK2_REQ(0, 1024)),\n        COAP_MSG(CON, GET, ID(1), TOKEN(nth_token(1)), BLOCK2_REQ(1, 512)),\n        COAP_MSG(CON, GET, ID(2), TOKEN(nth_token(2)), BLOCK2_REQ(3, 256))\n    };\n    const test_msg_t *responses[] = {\n        // The server responds with a smaller block size than requested. We\n        // should use that size for all further blocks.\n        COAP_MSG(ACK, CONTENT, ID(0), TOKEN(nth_token(0)),\n                 BLOCK2_RES(0, 512, RESPONSE_PAYLOAD)),\n        COAP_MSG(ACK, CONTENT, ID(1), TOKEN(nth_token(1)),\n                 BLOCK2_RES(2, 256, RESPONSE_PAYLOAD)),\n        COAP_MSG(ACK, CONTENT, ID(2), TOKEN(nth_token(2)),\n                 BLOCK2_RES(3, 256, RESPONSE_PAYLOAD))\n    };\n\n    avs_coap_exchange_id_t id;\n\n    // start the request\n    ASSERT_OK(avs_coap_client_send_async_request(\n            env.coap_ctx, &id, &requests[0]->request_header, NULL, NULL,\n            test_response_handler, &env.expects_list));\n    ASSERT_TRUE(avs_coap_exchange_id_valid(id));\n\n    expect_send(&env, requests[0]);\n    avs_sched_run(env.sched);\n\n    expect_recv(&env, responses[0]);\n    expect_send(&env, requests[1]);\n    expect_handler_call(&env, &id, AVS_COAP_CLIENT_REQUEST_PARTIAL_CONTENT,\n                        responses[0]);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n\n    expect_recv(&env, responses[1]);\n    expect_send(&env, requests[2]);\n    expect_handler_call(&env, &id, AVS_COAP_CLIENT_REQUEST_PARTIAL_CONTENT,\n                        responses[1]);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n\n    expect_recv(&env, responses[2]);\n    expect_handler_call(&env, &id, AVS_COAP_CLIENT_REQUEST_OK, responses[2]);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n#        undef RESPONSE_PAYLOAD\n}\n\nAVS_UNIT_TEST(udp_async_client, block_request_early_block2_response) {\n    // Server may issue a non-Continue response even though we're not done\n    // sending the request yet. In such case, we should stop generating any\n    // more requests and start handling the response instead.\n    //\n    // The server may send a BLOCK-wise response to the BLOCK request. We\n    // need to make sure we can handle it.\n\n#        define REQUEST_PAYLOAD DATA_1KB \"?\"\n#        define RESPONSE_PAYLOAD DATA_1KB \"?\"\n\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_with_max_retransmit(0);\n\n    test_payload_writer_args_t test_payload = {\n        .payload = REQUEST_PAYLOAD,\n        .payload_size = sizeof(REQUEST_PAYLOAD) - 1\n    };\n\n    const test_msg_t *requests[] = {\n        COAP_MSG(CON, GET, ID(0), TOKEN(nth_token(0)),\n                 BLOCK1_REQ(0, 1024, REQUEST_PAYLOAD)),\n        COAP_MSG(CON, GET, ID(1), TOKEN(nth_token(1)), BLOCK2_REQ(1, 1024))\n    };\n    const test_msg_t *responses[] = {\n        COAP_MSG(ACK, CONTENT, ID(0), TOKEN(nth_token(0)),\n                 BLOCK1_AND_2_RES(0, 1024, 1024, RESPONSE_PAYLOAD)),\n        COAP_MSG(ACK, CONTENT, ID(1), TOKEN(nth_token(1)),\n                 BLOCK2_RES(1, 1024, RESPONSE_PAYLOAD))\n    };\n\n    avs_coap_exchange_id_t id;\n\n    // start the request\n    ASSERT_OK(avs_coap_client_send_async_request(\n            env.coap_ctx, &id, &requests[0]->request_header,\n            test_payload_writer, &test_payload, test_response_handler,\n            &env.expects_list));\n    ASSERT_TRUE(avs_coap_exchange_id_valid(id));\n\n    expect_send(&env, requests[0]);\n    avs_sched_run(env.sched);\n\n    expect_recv(&env, responses[0]);\n    expect_send(&env, requests[1]);\n    expect_handler_call(&env, &id, AVS_COAP_CLIENT_REQUEST_PARTIAL_CONTENT,\n                        responses[0]);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n\n    expect_recv(&env, responses[1]);\n    expect_handler_call(&env, &id, AVS_COAP_CLIENT_REQUEST_OK, responses[1]);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n#        undef REQUEST_PAYLOAD\n#        undef RESPONSE_PAYLOAD\n}\n\nAVS_UNIT_TEST(udp_async_client, request_for_non_first_block_of_payload) {\n#        define RESPONSE_PAYLOAD DATA_1KB DATA_1KB DATA_1KB DATA_1KB\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_default();\n\n    const test_msg_t *requests[] = {\n        COAP_MSG(CON, GET, ID(0), TOKEN(nth_token(0)), BLOCK2_REQ(2, 1024)),\n        COAP_MSG(CON, GET, ID(1), TOKEN(nth_token(1)), BLOCK2_REQ(3, 1024))\n    };\n    const test_msg_t *responses[] = {\n        COAP_MSG(ACK, CONTENT, ID(0), TOKEN(nth_token(0)),\n                 BLOCK2_RES(2, 1024, RESPONSE_PAYLOAD)),\n        COAP_MSG(ACK, CONTENT, ID(1), TOKEN(nth_token(1)),\n                 BLOCK2_RES(3, 1024, RESPONSE_PAYLOAD)),\n    };\n\n    avs_coap_exchange_id_t id;\n    ASSERT_OK(avs_coap_client_send_async_request(\n            env.coap_ctx, &id, &requests[0]->request_header, NULL, NULL,\n            test_response_handler, &env.expects_list));\n    ASSERT_TRUE(avs_coap_exchange_id_valid(id));\n\n    expect_send(&env, requests[0]);\n    avs_sched_run(env.sched);\n\n    expect_recv(&env, responses[0]);\n    expect_send(&env, requests[1]);\n    expect_handler_call(&env, &id, AVS_COAP_CLIENT_REQUEST_PARTIAL_CONTENT,\n                        responses[0]);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n\n    expect_recv(&env, responses[1]);\n    expect_handler_call(&env, &id, AVS_COAP_CLIENT_REQUEST_OK, responses[1]);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n#        undef RESPONSE_PAYLOAD\n}\n\nAVS_UNIT_TEST(udp_async_client, block2_request_and_too_big_response) {\n#        define RESPONSE_PAYLOAD DATA_1KB\n    const size_t input_buffer_size = 1024;\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup(&AVS_COAP_DEFAULT_UDP_TX_PARAMS, input_buffer_size, 4096,\n                       NULL);\n\n    const test_msg_t *requests[] = {\n        COAP_MSG(CON, GET, ID(0), TOKEN(nth_token(0)), BLOCK2_REQ(0, 1024)),\n        // the server responded with packet that did not fit into input buffer,\n        // and async layer decided to retry the request with smaller block size\n        COAP_MSG(CON, GET, ID(1), TOKEN(nth_token(1)), BLOCK2_REQ(0, 512)),\n        COAP_MSG(CON, GET, ID(2), TOKEN(nth_token(2)), BLOCK2_REQ(1, 512))\n    };\n    const test_msg_t *responses[] = {\n        COAP_MSG(ACK, CONTENT, ID(0), TOKEN(nth_token(0)),\n                 BLOCK2_RES(0, 1024, RESPONSE_PAYLOAD)),\n        COAP_MSG(ACK, CONTENT, ID(1), TOKEN(nth_token(1)),\n                 BLOCK2_RES(0, 512, RESPONSE_PAYLOAD)),\n        COAP_MSG(ACK, CONTENT, ID(2), TOKEN(nth_token(2)),\n                 BLOCK2_RES(1, 512, RESPONSE_PAYLOAD))\n    };\n\n    for (size_t i = 0; i < AVS_ARRAY_SIZE(requests); ++i) {\n        expect_send(&env, requests[i]);\n        expect_recv(&env, responses[i]);\n        expect_has_buffered_data_check(&env, i + 1 < AVS_ARRAY_SIZE(requests));\n    }\n\n    avs_coap_exchange_id_t id;\n    ASSERT_OK(avs_coap_client_send_async_request(\n            env.coap_ctx, &id, &requests[0]->request_header, NULL, NULL,\n            test_response_handler, &env.expects_list));\n    avs_sched_run(env.sched);\n\n    // the library sent a retry request with smaller block size, and we need to\n    // handle response to it\n    expect_handler_call(&env, &id, AVS_COAP_CLIENT_REQUEST_PARTIAL_CONTENT,\n                        responses[1]);\n\n    // regular blockwise transfer continuation\n    expect_handler_call(&env, &id, AVS_COAP_CLIENT_REQUEST_OK, responses[2]);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n\n#        undef RESPONSE_PAYLOAD\n}\n\nAVS_UNIT_TEST(udp_async_client, valid_etag_in_blocks) {\n#        define RESPONSE_PAYLOAD DATA_1KB \"!\"\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_default();\n\n    const test_msg_t *requests[] = {\n        COAP_MSG(CON, GET, ID(0), TOKEN(nth_token(0))),\n        COAP_MSG(CON, GET, ID(1), TOKEN(nth_token(1)), BLOCK2_REQ(1, 1024))\n    };\n\n    const test_msg_t *responses[] = {\n        COAP_MSG(ACK, CONTENT, ID(0), TOKEN(nth_token(0)),\n                 BLOCK2_RES(0, 1024, RESPONSE_PAYLOAD), ETAG(\"tag\")),\n        COAP_MSG(ACK, CONTENT, ID(1), TOKEN(nth_token(1)),\n                 BLOCK2_RES(1, 1024, RESPONSE_PAYLOAD), ETAG(\"tag\"))\n    };\n\n    avs_coap_exchange_id_t id;\n    ASSERT_OK(avs_coap_client_send_async_request(\n            env.coap_ctx, &id,\n            &(avs_coap_request_header_t) {\n                .code = requests[0]->request_header.code\n            },\n            NULL, NULL, test_response_handler, &env.expects_list));\n\n    expect_send(&env, requests[0]);\n    avs_sched_run(env.sched);\n\n    expect_recv(&env, responses[0]);\n    expect_has_buffered_data_check(&env, true);\n    expect_send(&env, requests[1]);\n    expect_recv(&env, responses[1]);\n    expect_has_buffered_data_check(&env, false);\n\n    expect_handler_call(&env, &id, AVS_COAP_CLIENT_REQUEST_PARTIAL_CONTENT,\n                        responses[0]);\n    expect_handler_call(&env, &id, AVS_COAP_CLIENT_REQUEST_OK, responses[1]);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n\n#        undef RESPONSE_PAYLOAD\n}\n\nAVS_UNIT_TEST(udp_async_client, regular_request_and_too_big_response) {\n#        define RESPONSE_PAYLOAD DATA_1KB\n    const size_t input_buffer_size = 1024;\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup(&AVS_COAP_DEFAULT_UDP_TX_PARAMS, input_buffer_size, 4096,\n                       NULL);\n\n    const test_msg_t *requests[] = {\n        // NOTE: non-block request\n        COAP_MSG(CON, GET, ID(0), TOKEN(nth_token(0))),\n        // the server responded with packet that did not fit into input buffer,\n        // and async layer decided to retry the request with smaller block size\n        COAP_MSG(CON, GET, ID(1), TOKEN(nth_token(1)), BLOCK2_REQ(0, 512)),\n        COAP_MSG(CON, GET, ID(2), TOKEN(nth_token(2)), BLOCK2_REQ(1, 512))\n    };\n    const test_msg_t *responses[] = {\n        COAP_MSG(ACK, CONTENT, ID(0), TOKEN(nth_token(0)),\n                 BLOCK2_RES(0, 1024, RESPONSE_PAYLOAD)),\n        COAP_MSG(ACK, CONTENT, ID(1), TOKEN(nth_token(1)),\n                 BLOCK2_RES(0, 512, RESPONSE_PAYLOAD)),\n        COAP_MSG(ACK, CONTENT, ID(2), TOKEN(nth_token(2)),\n                 BLOCK2_RES(1, 512, RESPONSE_PAYLOAD))\n    };\n\n    for (size_t i = 0; i < AVS_ARRAY_SIZE(requests); ++i) {\n        expect_send(&env, requests[i]);\n        expect_recv(&env, responses[i]);\n        expect_has_buffered_data_check(&env, i + 1 < AVS_ARRAY_SIZE(requests));\n    }\n\n    avs_coap_exchange_id_t id;\n    ASSERT_OK(avs_coap_client_send_async_request(\n            env.coap_ctx, &id, &requests[0]->request_header, NULL, NULL,\n            test_response_handler, &env.expects_list));\n    avs_sched_run(env.sched);\n\n    // the library sent a retry request with smaller block size, and we need to\n    // handle response to it\n    expect_handler_call(&env, &id, AVS_COAP_CLIENT_REQUEST_PARTIAL_CONTENT,\n                        responses[1]);\n\n    // regular blockwise transfer continuation\n    expect_handler_call(&env, &id, AVS_COAP_CLIENT_REQUEST_OK, responses[2]);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n\n#        undef RESPONSE_PAYLOAD\n}\n\nAVS_UNIT_TEST(udp_async_client,\n              regular_request_with_payload_and_too_big_response) {\n#        define RESPONSE_PAYLOAD DATA_1KB\n#        define REQUEST_PAYLOAD \"RandomStuff\"\n    const size_t input_buffer_size = 1024;\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup(&AVS_COAP_DEFAULT_UDP_TX_PARAMS, input_buffer_size, 4096,\n                       NULL);\n\n    const test_msg_t *requests[] = {\n        COAP_MSG(CON, GET, ID(0), TOKEN(nth_token(0)),\n                 PAYLOAD(REQUEST_PAYLOAD)),\n        // the server responded with BLOCK2 that did not fit into input buffer,\n        // and async layer decided to retry the request with smaller block size\n        COAP_MSG(CON, GET, ID(1), TOKEN(nth_token(1)),\n                 BLOCK2_REQ_WITH_REGULAR_PAYLOAD(0, 512, REQUEST_PAYLOAD)),\n        COAP_MSG(CON, GET, ID(2), TOKEN(nth_token(2)), BLOCK2_REQ(1, 512))\n    };\n    const test_msg_t *responses[] = {\n        COAP_MSG(ACK, CONTENT, ID(0), TOKEN(nth_token(0)),\n                 BLOCK2_RES(0, 1024, RESPONSE_PAYLOAD)),\n        COAP_MSG(ACK, CONTENT, ID(1), TOKEN(nth_token(1)),\n                 BLOCK2_RES(0, 512, RESPONSE_PAYLOAD)),\n        COAP_MSG(ACK, CONTENT, ID(2), TOKEN(nth_token(2)),\n                 BLOCK2_RES(1, 512, RESPONSE_PAYLOAD))\n    };\n\n    for (size_t i = 0; i < AVS_ARRAY_SIZE(requests); ++i) {\n        expect_send(&env, requests[i]);\n        expect_recv(&env, responses[i]);\n        expect_has_buffered_data_check(&env, i + 1 < AVS_ARRAY_SIZE(requests));\n    }\n\n    avs_coap_exchange_id_t id;\n    test_payload_writer_args_t test_payload = {\n        .payload = REQUEST_PAYLOAD,\n        .payload_size = sizeof(REQUEST_PAYLOAD) - 1,\n        .coap_ctx = env.coap_ctx,\n        .cancel_exchange = false\n    };\n    ASSERT_OK(avs_coap_client_send_async_request(\n            env.coap_ctx, &id, &requests[0]->request_header,\n            test_payload_writer, &test_payload, test_response_handler,\n            &env.expects_list));\n    avs_sched_run(env.sched);\n\n    test_payload.expected_payload_offset = 0;\n    test_payload.exchange_id = id;\n\n    // the library sent a retry request with smaller block size, and we need to\n    // handle response to it\n    expect_handler_call(&env, &id, AVS_COAP_CLIENT_REQUEST_PARTIAL_CONTENT,\n                        responses[1]);\n\n    // regular blockwise transfer continuation\n    expect_handler_call(&env, &id, AVS_COAP_CLIENT_REQUEST_OK, responses[2]);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n\n#        undef REQUEST_PAYLOAD\n#        undef RESPONSE_PAYLOAD\n}\n\nAVS_UNIT_TEST(udp_async_client, regular_request_and_too_big_nonblock_response) {\n#        define RESPONSE_PAYLOAD DATA_1KB\n    const size_t input_buffer_size = 1024;\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup(&AVS_COAP_DEFAULT_UDP_TX_PARAMS, input_buffer_size, 4096,\n                       NULL);\n\n    const test_msg_t *requests[] = {\n        // NOTE: non-block request\n        COAP_MSG(CON, GET, ID(0), TOKEN(nth_token(0))),\n        // the server responded with packet that did not fit into input buffer,\n        // and async layer decided to retry the request with smaller block size\n        COAP_MSG(CON, GET, ID(1), TOKEN(nth_token(1)), BLOCK2_REQ(0, 512)),\n        COAP_MSG(CON, GET, ID(2), TOKEN(nth_token(2)), BLOCK2_REQ(1, 512))\n    };\n    const test_msg_t *responses[] = {\n        // NOTE: non-block response\n        COAP_MSG(ACK, CONTENT, ID(0), TOKEN(nth_token(0)),\n                 PAYLOAD(RESPONSE_PAYLOAD)),\n        COAP_MSG(ACK, CONTENT, ID(1), TOKEN(nth_token(1)),\n                 BLOCK2_RES(0, 512, RESPONSE_PAYLOAD)),\n        COAP_MSG(ACK, CONTENT, ID(2), TOKEN(nth_token(2)),\n                 BLOCK2_RES(1, 512, RESPONSE_PAYLOAD))\n    };\n\n    for (size_t i = 0; i < AVS_ARRAY_SIZE(requests); ++i) {\n        expect_send(&env, requests[i]);\n        expect_recv(&env, responses[i]);\n        expect_has_buffered_data_check(&env, i + 1 < AVS_ARRAY_SIZE(requests));\n    }\n\n    avs_coap_exchange_id_t id;\n    ASSERT_OK(avs_coap_client_send_async_request(\n            env.coap_ctx, &id, &requests[0]->request_header, NULL, NULL,\n            test_response_handler, &env.expects_list));\n    avs_sched_run(env.sched);\n\n    // the library sent a retry request with smaller block size, and we need to\n    // handle response to it\n    expect_handler_call(&env, &id, AVS_COAP_CLIENT_REQUEST_PARTIAL_CONTENT,\n                        responses[1]);\n\n    // regular blockwise transfer continuation\n    expect_handler_call(&env, &id, AVS_COAP_CLIENT_REQUEST_OK, responses[2]);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n\n#        undef RESPONSE_PAYLOAD\n}\n\nAVS_UNIT_TEST(udp_async_client, invalid_etag_in_blocks) {\n#        define RESPONSE_PAYLOAD DATA_1KB DATA_1KB \"!\"\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_default();\n\n    const test_msg_t *requests[] = {\n        COAP_MSG(CON, GET, ID(0), TOKEN(nth_token(0))),\n        COAP_MSG(CON, GET, ID(1), TOKEN(nth_token(1)), BLOCK2_REQ(1, 1024))\n    };\n\n    const test_msg_t *responses[] = {\n        COAP_MSG(ACK, CONTENT, ID(0), TOKEN(nth_token(0)),\n                 BLOCK2_RES(0, 1024, RESPONSE_PAYLOAD), ETAG(\"tag\")),\n        COAP_MSG(ACK, CONTENT, ID(1), TOKEN(nth_token(1)),\n                 BLOCK2_RES(1, 1024, RESPONSE_PAYLOAD), ETAG(\"nje\"))\n    };\n\n    avs_coap_exchange_id_t id;\n    ASSERT_OK(avs_coap_client_send_async_request(\n            env.coap_ctx, &id,\n            &(avs_coap_request_header_t) {\n                .code = requests[0]->request_header.code\n            },\n            NULL, NULL, test_response_handler, &env.expects_list));\n\n    expect_send(&env, requests[0]);\n    avs_sched_run(env.sched);\n\n    expect_recv(&env, responses[0]);\n    expect_has_buffered_data_check(&env, true);\n    expect_send(&env, requests[1]);\n    expect_recv(&env, responses[1]);\n    expect_has_buffered_data_check(&env, false);\n\n    expect_handler_call(&env, &id, AVS_COAP_CLIENT_REQUEST_PARTIAL_CONTENT,\n                        responses[0]);\n\n    expect_handler_call(&env, &id, AVS_COAP_CLIENT_REQUEST_FAIL, NULL);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n\n#        undef RESPONSE_PAYLOAD\n}\n\nAVS_UNIT_TEST(udp_async_client, etag_in_not_all_responses) {\n#        define RESPONSE_PAYLOAD DATA_1KB \"!\"\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_default();\n\n    const test_msg_t *requests[] = {\n        COAP_MSG(CON, GET, ID(0), TOKEN(nth_token(0))),\n        COAP_MSG(CON, GET, ID(1), TOKEN(nth_token(1)), BLOCK2_REQ(1, 1024))\n    };\n\n    const test_msg_t *responses[] = {\n        COAP_MSG(ACK, CONTENT, ID(0), TOKEN(nth_token(0)),\n                 BLOCK2_RES(0, 1024, RESPONSE_PAYLOAD), ETAG(\"tag\")),\n        COAP_MSG(ACK, CONTENT, ID(1), TOKEN(nth_token(1)),\n                 BLOCK2_RES(1, 1024, RESPONSE_PAYLOAD))\n    };\n\n    avs_coap_exchange_id_t id;\n    ASSERT_OK(avs_coap_client_send_async_request(\n            env.coap_ctx, &id,\n            &(avs_coap_request_header_t) {\n                .code = requests[0]->request_header.code\n            },\n            NULL, NULL, test_response_handler, &env.expects_list));\n\n    expect_send(&env, requests[0]);\n    avs_sched_run(env.sched);\n\n    expect_recv(&env, responses[0]);\n    expect_has_buffered_data_check(&env, true);\n    expect_send(&env, requests[1]);\n    expect_recv(&env, responses[1]);\n    expect_has_buffered_data_check(&env, false);\n\n    expect_handler_call(&env, &id, AVS_COAP_CLIENT_REQUEST_PARTIAL_CONTENT,\n                        responses[0]);\n\n    expect_handler_call(&env, &id, AVS_COAP_CLIENT_REQUEST_FAIL, NULL);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n\n#        undef RESPONSE_PAYLOAD\n}\n\n#        define INVALID_BLOCK2(Seq, Size, Payload) \\\n            .block2 = {                            \\\n                .type = AVS_COAP_BLOCK2,           \\\n                .seq_num = Seq,                    \\\n                .size = Size,                      \\\n                .has_more = true                   \\\n            },                                     \\\n            .payload = Payload,                    \\\n            .payload_size =                        \\\n                    (assert(sizeof(Payload) - 1 < Size), sizeof(Payload) - 1)\n\nAVS_UNIT_TEST(udp_async_client, invalid_block_opt_in_response) {\n    // response with BLOCK2.has_more == 1 and BLOCK2.size != payload size\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_default();\n\n    const test_msg_t *request =\n            COAP_MSG(CON, GET, ID(0), TOKEN(nth_token(0)), BLOCK2_REQ(0, 1024));\n    const test_msg_t *response =\n            COAP_MSG(ACK, BAD_OPTION, ID(0), TOKEN(nth_token(0)),\n                     INVALID_BLOCK2(0, 1024, \"test\"));\n\n    avs_coap_exchange_id_t id;\n    ASSERT_OK(avs_coap_client_send_async_request(\n            env.coap_ctx, &id, &request->request_header, NULL, NULL,\n            test_response_handler, &env.expects_list));\n\n    expect_send(&env, request);\n    avs_sched_run(env.sched);\n\n    expect_recv(&env, response);\n    expect_handler_call(&env, &id, AVS_COAP_CLIENT_REQUEST_FAIL, NULL);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n    ASSERT_NULL(env.expects_list);\n}\n\nAVS_UNIT_TEST(udp_async_client, block_response_skip) {\n#        define REQUEST_PAYLOAD \"gib payload pls\"\n#        define DATA_49B \"123456789 123456789 123456789 123456789 123456789\"\n\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_default();\n\n    test_payload_writer_args_t test_payload = {\n        .payload = REQUEST_PAYLOAD,\n        .payload_size = sizeof(REQUEST_PAYLOAD) - 1\n    };\n\n    const test_msg_t *requests[] = {\n        COAP_MSG(CON, GET, ID(0), TOKEN(nth_token(0)),\n                 PAYLOAD(REQUEST_PAYLOAD)),\n        COAP_MSG(CON, GET, ID(1), TOKEN(nth_token(1)), BLOCK2_REQ(2, 16)),\n        COAP_MSG(CON, GET, ID(2), TOKEN(nth_token(2)), BLOCK2_REQ(3, 16)),\n    };\n    const test_msg_t *responses[] = {\n        COAP_MSG(ACK, CONTENT, ID(0), TOKEN(nth_token(0)),\n                 BLOCK2_RES(0, 16, DATA_49B)),\n        COAP_MSG(ACK, CONTENT, ID(1), TOKEN(nth_token(1)),\n                 BLOCK2_RES(2, 16, DATA_49B)),\n        COAP_MSG(ACK, CONTENT, ID(2), TOKEN(nth_token(2)),\n                 BLOCK2_RES(3, 16, DATA_49B)),\n    };\n    AVS_STATIC_ASSERT(AVS_ARRAY_SIZE(requests) == AVS_ARRAY_SIZE(responses),\n                      mismatched_requests_responses_lists);\n\n    avs_coap_exchange_id_t id;\n\n    // start the request\n    ASSERT_OK(avs_coap_client_send_async_request(\n            env.coap_ctx, &id, &requests[0]->request_header,\n            test_payload_writer, &test_payload, test_response_handler,\n            &env.expects_list));\n    ASSERT_TRUE(avs_coap_exchange_id_valid(id));\n\n    expect_send(&env, requests[0]);\n    avs_sched_run(env.sched);\n\n    // handlers should be called only after receiving responses\n\n    expect_recv(&env, responses[0]);\n    expect_send(&env, requests[1]);\n    expect_handler_call(&env, &id, AVS_COAP_CLIENT_REQUEST_PARTIAL_CONTENT,\n                        responses[0],\n                        .next_response_payload_offset = 40);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n\n    expect_recv(&env, responses[1]);\n    expect_send(&env, requests[2]);\n    expect_handler_call(&env, &id, AVS_COAP_CLIENT_REQUEST_PARTIAL_CONTENT,\n                        responses[1],\n                        .expected_payload_offset = 8);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n\n    expect_recv(&env, responses[2]);\n    expect_handler_call(&env, &id, AVS_COAP_CLIENT_REQUEST_OK, responses[2]);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n\n#        undef DATA_49B\n#        undef REQUEST_PAYLOAD\n}\n\nAVS_UNIT_TEST(udp_async_client, block_response_initial_skip) {\n    static const char RESPONSE_PAYLOAD[] = DATA_1KB DATA_1KB DATA_1KB \"?\";\n\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_default();\n\n    const test_msg_t *requests[] = {\n        COAP_MSG(CON, GET, ID(0), TOKEN(nth_token(0)), BLOCK2_REQ(1, 1024)),\n        COAP_MSG(CON, GET, ID(1), TOKEN(nth_token(1)), BLOCK2_REQ(3, 1024))\n    };\n    const test_msg_t *responses[] = {\n        COAP_MSG(ACK, CONTENT, ID(0), TOKEN(nth_token(0)),\n                 BLOCK2_RES(1, 1024, RESPONSE_PAYLOAD)),\n        COAP_MSG(ACK, CONTENT, ID(1), TOKEN(nth_token(1)),\n                 BLOCK2_RES(3, 1024, RESPONSE_PAYLOAD))\n    };\n    AVS_STATIC_ASSERT(AVS_ARRAY_SIZE(requests) == AVS_ARRAY_SIZE(responses),\n                      mismatched_requests_responses_lists);\n\n    avs_coap_exchange_id_t id;\n\n    // start the request\n    ASSERT_OK(avs_coap_client_send_async_request(\n            env.coap_ctx, &id,\n            &(const avs_coap_request_header_t) {\n                .code = AVS_COAP_CODE_GET\n            },\n            NULL, NULL, test_response_handler, &env.expects_list));\n    ASSERT_TRUE(avs_coap_exchange_id_valid(id));\n\n    ASSERT_OK(avs_coap_client_set_next_response_payload_offset(env.coap_ctx, id,\n                                                               1500));\n\n    expect_send(&env, requests[0]);\n    avs_sched_run(env.sched);\n\n    expect_recv(&env, responses[0]);\n    expect_send(&env, requests[1]);\n    expect_handler_call(&env, &id, AVS_COAP_CLIENT_REQUEST_PARTIAL_CONTENT,\n                        responses[0],\n                        .next_response_payload_offset = 3072,\n                        .expected_payload_offset = 476);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n\n    expect_recv(&env, responses[1]);\n    expect_handler_call(&env, &id, AVS_COAP_CLIENT_REQUEST_OK, responses[1]);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n}\n\n#    else // WITH_AVS_COAP_BLOCK\n\nAVS_UNIT_TEST(udp_async_client_no_block, block2_response) {\n#        define RESPONSE_PAYLOAD \"test\"\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_default();\n\n    const test_msg_t *request = COAP_MSG(CON, GET, ID(0), TOKEN(nth_token(0)));\n\n    // Equivalent to\n    // COAP_MSG(ACK, CONTENT, ID(0), TOKEN(nth_token(0)),\n    //          BLOCK2_RES(0, 16, RESPONSE_PAYLOAD))\n    // but we're unable to easily construct such message if BLOCK support is\n    // disabled.\n    const uint8_t response[] = { 0x68, 0x45, 0x00, 0x00, 0x00, 0x00, 0x00,\n                                 0x00, 0x00, 0x00, 0x00, 0x00, 0xD0, 0x0A,\n                                 0xFF, 0x74, 0x65, 0x73, 0x74 };\n    avs_coap_exchange_id_t id;\n\n    ASSERT_OK(avs_coap_client_send_async_request(env.coap_ctx,\n                                                 &id,\n                                                 &request->request_header,\n                                                 NULL,\n                                                 NULL,\n                                                 test_response_handler,\n                                                 &env.expects_list));\n    ASSERT_TRUE(avs_coap_exchange_id_valid(id));\n\n    expect_send(&env, request);\n    avs_sched_run(env.sched);\n\n    avs_unit_mocksock_input(env.mocksock, response, sizeof(response));\n    expect_handler_call(&env, &id, AVS_COAP_CLIENT_REQUEST_FAIL, NULL);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n#        undef RESPONSE_PAYLOAD\n}\n\n#    endif // WITH_AVS_COAP_BLOCK\n\n#endif // defined(AVS_UNIT_TESTING) && defined(WITH_AVS_COAP_UDP)\n"
  },
  {
    "path": "deps/avs_coap/tests/udp/async_client_with_big_data.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <avs_coap_init.h>\n\n#if defined(AVS_UNIT_TESTING) && defined(WITH_AVS_COAP_UDP)\n\n#    include <avsystem/coap/coap.h>\n\n#    define MODULE_NAME test\n#    include <avs_coap_x_log_config.h>\n\n#    include \"./big_data.h\"\n#    include \"./utils.h\"\n\n#    ifdef WITH_AVS_COAP_BLOCK\n\nAVS_UNIT_TEST(udp_async_client_with_big_data,\n              block_request_renegotiation_seq_num_overflow) {\n    // Server may ask the client to send smaller blocks than the initial one.\n    // In that case, seq_num is recalculated accordingly for further blocks\n    // (i.e. multiplied by prev_size/new_size). BLOCK option sequence numbers\n    // are limited to 20 bits though, and seq_num recalculation may increase\n    // seq_num past the limit.\n\n    // MIN_BLOCK_SIZE (16 == 2**4) * 2**20 == 2**24 == 16MB\n    static const char REQUEST_PAYLOAD[] = DATA_16MB DATA_1KB \"?\";\n\n    test_env_t env __attribute__((cleanup(test_teardown_late_expects_check))) =\n            test_setup_with_max_retransmit(0);\n\n    test_payload_writer_args_t test_payload = {\n        .payload = REQUEST_PAYLOAD,\n        .expected_payload_offset = 1024 * (sizeof(REQUEST_PAYLOAD) / 1024 - 1),\n        .payload_size = sizeof(REQUEST_PAYLOAD) - 1\n    };\n\n    const test_msg_t *requests[] = {\n        COAP_MSG(CON, GET, ID(0), TOKEN(nth_token(0)),\n                 BLOCK1_REQ(sizeof(REQUEST_PAYLOAD) / 1024 - 1, 1024,\n                            REQUEST_PAYLOAD)),\n        COAP_MSG(CON, GET, ID(1), TOKEN(nth_token(1)),\n                 BLOCK1_REQ(sizeof(REQUEST_PAYLOAD) / 1024, 1024,\n                            REQUEST_PAYLOAD))\n    };\n    const test_msg_t *responses[] = { COAP_MSG(ACK, CONTINUE, ID(0),\n                                               TOKEN(nth_token(0)),\n                                               BLOCK1_RES(0, 16, true)) };\n\n    avs_coap_exchange_id_t id;\n\n    // start the request\n    ASSERT_OK(avs_coap_client_send_async_request(\n            env.coap_ctx, &id, &requests[0]->request_header,\n            test_payload_writer, &test_payload, test_response_handler,\n            &env.expects_list));\n    ASSERT_TRUE(avs_coap_exchange_id_valid(id));\n\n    expect_send(&env, requests[0]);\n    avs_sched_run(env.sched);\n\n    // Block size renegotiation should be ignored and the request should\n    // continue with previous block size.\n    expect_recv(&env, responses[0]);\n    expect_send(&env, requests[1]);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n\n    // the exchange is not resolved - cleanup should call the handler\n    expect_handler_call(&env, &id, AVS_COAP_CLIENT_REQUEST_CANCEL, NULL);\n}\n\n#    endif // WITH_AVS_COAP_BLOCK\n\n#endif // defined(AVS_UNIT_TESTING) && defined(WITH_AVS_COAP_UDP)\n"
  },
  {
    "path": "deps/avs_coap/tests/udp/async_observe.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <avs_coap_init.h>\n\n#if defined(AVS_UNIT_TESTING) && defined(WITH_AVS_COAP_UDP) \\\n        && defined(WITH_AVS_COAP_OBSERVE)\n\n#    include <avsystem/commons/avs_errno.h>\n\n#    include <avsystem/coap/coap.h>\n\n#    define MODULE_NAME test\n#    include <avs_coap_x_log_config.h>\n\n#    include \"./utils.h\"\n\nAVS_UNIT_TEST(udp_observe, start) {\n    test_env_t env __attribute__((cleanup(test_teardown_late_expects_check))) =\n            test_setup_default();\n\n    const test_msg_t *request = COAP_MSG(CON, GET, ID(0), MAKE_TOKEN(\"Obserw\"),\n                                         OBSERVE(0), NO_PAYLOAD);\n    // Note: Observe option values start at 0 (in a response to the initial\n    // Observe) and get incremented by one with each sent notification\n    const test_msg_t *response =\n            COAP_MSG(ACK, CONTENT, ID(0), MAKE_TOKEN(\"Obserw\"), OBSERVE(0),\n                     NO_PAYLOAD);\n\n    expect_recv(&env, request);\n    expect_request_handler_call(&env, AVS_COAP_SERVER_REQUEST_RECEIVED, request,\n                                &(avs_coap_response_header_t) {\n                                    .code = response->response_header.code\n                                },\n                                NULL);\n    expect_observe_start(&env, MAKE_TOKEN(\"Obserw\"));\n\n    expect_send(&env, response);\n    expect_request_handler_call(&env, AVS_COAP_SERVER_REQUEST_CLEANUP, NULL,\n                                NULL, NULL);\n\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(\n            env.coap_ctx, test_accept_new_request, &env));\n\n    // should be canceled by cleanup\n    expect_observe_cancel(&env, MAKE_TOKEN(\"Obserw\"));\n}\n\nAVS_UNIT_TEST(udp_observe, start_twice) {\n    test_env_t env __attribute__((cleanup(test_teardown_late_expects_check))) =\n            test_setup_default();\n\n    const test_msg_t *requests[] = {\n        COAP_MSG(CON, GET, ID(0), MAKE_TOKEN(\"Obserw\"), OBSERVE(0), NO_PAYLOAD),\n        COAP_MSG(CON, GET, ID(1), MAKE_TOKEN(\"Obserw\"), OBSERVE(0), NO_PAYLOAD)\n    };\n\n    const test_msg_t *responses[] = { COAP_MSG(ACK, CONTENT, ID(0),\n                                               MAKE_TOKEN(\"Obserw\"), OBSERVE(0),\n                                               NO_PAYLOAD),\n                                      COAP_MSG(ACK, CONTENT, ID(1),\n                                               MAKE_TOKEN(\"Obserw\"), OBSERVE(0),\n                                               NO_PAYLOAD) };\n\n    for (size_t i = 0; i < AVS_ARRAY_SIZE(requests); i++) {\n        expect_recv(&env, requests[i]);\n        expect_request_handler_call(\n                &env, AVS_COAP_SERVER_REQUEST_RECEIVED, requests[i],\n                &(avs_coap_response_header_t) {\n                    .code = responses[i]->response_header.code\n                },\n                NULL);\n        expect_observe_start(&env, MAKE_TOKEN(\"Obserw\"));\n        expect_send(&env, responses[i]);\n        expect_request_handler_call(&env, AVS_COAP_SERVER_REQUEST_CLEANUP, NULL,\n                                    NULL, NULL);\n        expect_has_buffered_data_check(&env, false);\n        ASSERT_OK(avs_coap_async_handle_incoming_packet(\n                env.coap_ctx, test_accept_new_request, &env));\n        expect_observe_cancel(&env, MAKE_TOKEN(\"Obserw\"));\n    }\n}\n\nAVS_UNIT_TEST(udp_observe, cancel_with_observe_option) {\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_default();\n\n    const test_msg_t *requests[] = {\n        COAP_MSG(CON, GET, ID(0), MAKE_TOKEN(\"Obserw\"), OBSERVE(0), NO_PAYLOAD),\n        COAP_MSG(CON, GET, ID(1), MAKE_TOKEN(\"Obserw\"), OBSERVE(1), NO_PAYLOAD),\n    };\n    const test_msg_t *responses[] = {\n        COAP_MSG(ACK, CONTENT, ID(0), MAKE_TOKEN(\"Obserw\"), OBSERVE(0),\n                 NO_PAYLOAD),\n        COAP_MSG(ACK, CONTENT, ID(1), MAKE_TOKEN(\"Obserw\"), NO_PAYLOAD),\n    };\n\n    expect_recv(&env, requests[0]);\n    expect_request_handler_call(&env, AVS_COAP_SERVER_REQUEST_RECEIVED,\n                                requests[0],\n                                &(avs_coap_response_header_t) {\n                                    .code = responses[0]->response_header.code\n                                },\n                                NULL);\n    expect_observe_start(&env, MAKE_TOKEN(\"Obserw\"));\n    expect_send(&env, responses[0]);\n    expect_request_handler_call(&env, AVS_COAP_SERVER_REQUEST_CLEANUP, NULL,\n                                NULL, NULL);\n\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(\n            env.coap_ctx, test_accept_new_request, &env));\n\n    expect_recv(&env, requests[1]);\n    expect_observe_cancel(&env, MAKE_TOKEN(\"Obserw\"));\n    expect_request_handler_call(&env, AVS_COAP_SERVER_REQUEST_RECEIVED,\n                                requests[1],\n                                &(avs_coap_response_header_t) {\n                                    .code = responses[1]->response_header.code\n                                },\n                                NULL);\n    expect_send(&env, responses[1]);\n    expect_request_handler_call(&env, AVS_COAP_SERVER_REQUEST_CLEANUP, NULL,\n                                NULL, NULL);\n\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(\n            env.coap_ctx, test_accept_new_request, &env));\n}\n\nAVS_UNIT_TEST(udp_observe, notify_async) {\n#    define NOTIFY_PAYLOAD \"Notifaj\"\n    test_env_t env __attribute__((cleanup(test_teardown_late_expects_check))) =\n            test_setup_default();\n\n    const test_msg_t *request =\n            COAP_MSG(CON, GET, ID(100), MAKE_TOKEN(\"Obserw\"), OBSERVE(0),\n                     NO_PAYLOAD);\n    const test_msg_t *responses[] = {\n        COAP_MSG(ACK, CONTENT, ID(100), MAKE_TOKEN(\"Obserw\"), OBSERVE(0),\n                 NO_PAYLOAD),\n        COAP_MSG(NON, CONTENT, ID(0), MAKE_TOKEN(\"Obserw\"), OBSERVE(1),\n                 PAYLOAD(NOTIFY_PAYLOAD)),\n    };\n\n    expect_recv(&env, request);\n    expect_request_handler_call(&env, AVS_COAP_SERVER_REQUEST_RECEIVED, request,\n                                &(avs_coap_response_header_t) {\n                                    .code = responses[0]->response_header.code\n                                },\n                                NULL);\n    expect_observe_start(&env, MAKE_TOKEN(\"Obserw\"));\n    expect_send(&env, responses[0]);\n    expect_request_handler_call(&env, AVS_COAP_SERVER_REQUEST_CLEANUP, NULL,\n                                NULL, NULL);\n\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(\n            env.coap_ctx, test_accept_new_request, &env));\n\n    avs_coap_observe_id_t observe_id = {\n        .token = request->msg.token\n    };\n    test_payload_writer_args_t test_payload = {\n        .payload = NOTIFY_PAYLOAD,\n        .payload_size = sizeof(NOTIFY_PAYLOAD) - 1\n    };\n\n    expect_send(&env, responses[1]);\n\n    avs_coap_exchange_id_t id;\n    ASSERT_OK(avs_coap_notify_async(\n            env.coap_ctx, &id, observe_id, &responses[1]->response_header,\n            AVS_COAP_NOTIFY_PREFER_NON_CONFIRMABLE, test_payload_writer,\n            &test_payload, NULL, NULL));\n    ASSERT_FALSE(avs_coap_exchange_id_valid(id));\n\n    // should be canceled by cleanup\n    expect_observe_cancel(&env, MAKE_TOKEN(\"Obserw\"));\n#    undef NOTIFY_PAYLOAD\n}\n\nAVS_UNIT_TEST(udp_observe, notify_async_confirmable) {\n#    define NOTIFY_PAYLOAD \"Notifaj\"\n    test_env_t env __attribute__((cleanup(test_teardown_late_expects_check))) =\n            test_setup_default();\n\n    const test_msg_t *requests[] = {\n        COAP_MSG(CON, GET, ID(100), MAKE_TOKEN(\"Obserw\"), OBSERVE(0),\n                 NO_PAYLOAD),\n        COAP_MSG(ACK, EMPTY, ID(0), NO_PAYLOAD),\n    };\n    const test_msg_t *responses[] = {\n        COAP_MSG(ACK, CONTENT, ID(100), MAKE_TOKEN(\"Obserw\"), OBSERVE(0),\n                 NO_PAYLOAD),\n        COAP_MSG(CON, CONTENT, ID(0), MAKE_TOKEN(\"Obserw\"), OBSERVE(1),\n                 PAYLOAD(NOTIFY_PAYLOAD)),\n    };\n\n    expect_recv(&env, requests[0]);\n    expect_request_handler_call(&env, AVS_COAP_SERVER_REQUEST_RECEIVED,\n                                requests[0],\n                                &(avs_coap_response_header_t) {\n                                    .code = responses[0]->response_header.code\n                                },\n                                NULL);\n    expect_observe_start(&env, MAKE_TOKEN(\"Obserw\"));\n    expect_send(&env, responses[0]);\n    expect_request_handler_call(&env, AVS_COAP_SERVER_REQUEST_CLEANUP, NULL,\n                                NULL, NULL);\n\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(\n            env.coap_ctx, test_accept_new_request, &env));\n\n    avs_coap_observe_id_t observe_id = {\n        .token = requests[0]->msg.token\n    };\n    test_payload_writer_args_t test_payload = {\n        .payload = NOTIFY_PAYLOAD,\n        .payload_size = sizeof(NOTIFY_PAYLOAD) - 1\n    };\n\n    expect_send(&env, responses[1]);\n\n    avs_coap_exchange_id_t id;\n    ASSERT_OK(avs_coap_notify_async(\n            env.coap_ctx, &id, observe_id, &responses[1]->response_header,\n            AVS_COAP_NOTIFY_PREFER_CONFIRMABLE, test_payload_writer,\n            &test_payload, test_observe_delivery_handler, &env));\n    ASSERT_TRUE(avs_coap_exchange_id_valid(id));\n\n    expect_recv(&env, requests[1]);\n    expect_observe_delivery(&env, AVS_OK);\n\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n\n    // should be canceled by cleanup\n    expect_observe_cancel(&env, MAKE_TOKEN(\"Obserw\"));\n#    undef NOTIFY_PAYLOAD\n}\n\nAVS_UNIT_TEST(udp_observe, notify_async_cancel_with_error_response) {\n    // FIXME: Unsolicited non-confirmable notifications with an error code are\n    // currently broken, because of lack of the Observe option for error values,\n    // the lower, UDP layer assumes the message to be an ACK, not a NON.\n    //\n    // For reference, see assumptions in choose_msg_type() in avs_coap_udp_ctx.c\n    return;\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_default();\n\n    const test_msg_t *request =\n            COAP_MSG(CON, GET, ID(100), MAKE_TOKEN(\"Obserw\"), OBSERVE(0),\n                     NO_PAYLOAD);\n    const test_msg_t *responses[] = {\n        COAP_MSG(ACK, CONTENT, ID(100), MAKE_TOKEN(\"Obserw\"), OBSERVE(0),\n                 NO_PAYLOAD),\n        COAP_MSG(NON, NOT_FOUND, ID(0), MAKE_TOKEN(\"Obserw\"), NO_PAYLOAD),\n    };\n\n    expect_recv(&env, request);\n    expect_request_handler_call(&env, AVS_COAP_SERVER_REQUEST_RECEIVED, request,\n                                &(avs_coap_response_header_t) {\n                                    .code = responses[0]->response_header.code\n                                },\n                                NULL);\n    expect_observe_start(&env, request->msg.token);\n    expect_send(&env, responses[0]);\n    expect_request_handler_call(&env, AVS_COAP_SERVER_REQUEST_CLEANUP, NULL,\n                                NULL, NULL);\n\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(\n            env.coap_ctx, test_accept_new_request, &env));\n    avs_coap_observe_id_t observe_id = {\n        .token = request->msg.token\n    };\n\n    expect_send(&env, responses[1]);\n    expect_observe_cancel(&env, observe_id.token);\n\n    avs_coap_exchange_id_t id;\n    ASSERT_OK(avs_coap_notify_async(\n            env.coap_ctx, &id, observe_id, &responses[1]->response_header,\n            AVS_COAP_NOTIFY_PREFER_NON_CONFIRMABLE, NULL, NULL, NULL, NULL));\n    ASSERT_FALSE(avs_coap_exchange_id_valid(id));\n}\n\nAVS_UNIT_TEST(udp_observe,\n              notify_async_cancel_with_confirmable_error_response) {\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_default();\n\n    const test_msg_t *requests[] = {\n        COAP_MSG(CON, GET, ID(100), MAKE_TOKEN(\"Obserw\"), OBSERVE(0),\n                 NO_PAYLOAD),\n        COAP_MSG(ACK, EMPTY, ID(0), NO_PAYLOAD),\n    };\n    const test_msg_t *responses[] = {\n        COAP_MSG(ACK, CONTENT, ID(100), MAKE_TOKEN(\"Obserw\"), OBSERVE(0),\n                 NO_PAYLOAD),\n        COAP_MSG(CON, NOT_FOUND, ID(0), MAKE_TOKEN(\"Obserw\"), NO_PAYLOAD),\n    };\n\n    expect_recv(&env, requests[0]);\n    expect_request_handler_call(&env, AVS_COAP_SERVER_REQUEST_RECEIVED,\n                                requests[0],\n                                &(avs_coap_response_header_t) {\n                                    .code = responses[0]->response_header.code\n                                },\n                                NULL);\n    expect_observe_start(&env, requests[0]->msg.token);\n    expect_send(&env, responses[0]);\n    expect_request_handler_call(&env, AVS_COAP_SERVER_REQUEST_CLEANUP, NULL,\n                                NULL, NULL);\n\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(\n            env.coap_ctx, test_accept_new_request, &env));\n    avs_coap_observe_id_t observe_id = {\n        .token = requests[0]->msg.token\n    };\n\n    expect_send(&env, responses[1]);\n\n    avs_coap_exchange_id_t id;\n    ASSERT_OK(avs_coap_notify_async(env.coap_ctx, &id, observe_id,\n                                    &responses[1]->response_header,\n                                    AVS_COAP_NOTIFY_PREFER_CONFIRMABLE, NULL,\n                                    NULL, test_observe_delivery_handler, &env));\n    ASSERT_TRUE(avs_coap_exchange_id_valid(id));\n\n    expect_recv(&env, requests[1]);\n    expect_observe_delivery(&env, AVS_OK);\n    expect_observe_cancel(&env, observe_id.token);\n\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n}\n\nAVS_UNIT_TEST(udp_observe,\n              notify_async_rst_to_cancel_with_confirmable_error_response) {\n    test_env_t env __attribute__((cleanup(test_teardown_late_expects_check))) =\n            test_setup_default();\n\n    const test_msg_t *requests[] = {\n        COAP_MSG(CON, GET, ID(100), MAKE_TOKEN(\"Obserw\"), OBSERVE(0),\n                 NO_PAYLOAD),\n        COAP_MSG(RST, EMPTY, ID(0), NO_PAYLOAD),\n    };\n    const test_msg_t *responses[] = {\n        COAP_MSG(ACK, CONTENT, ID(100), MAKE_TOKEN(\"Obserw\"), OBSERVE(0),\n                 NO_PAYLOAD),\n        COAP_MSG(CON, NOT_FOUND, ID(0), MAKE_TOKEN(\"Obserw\"), NO_PAYLOAD),\n    };\n\n    expect_recv(&env, requests[0]);\n    expect_request_handler_call(&env, AVS_COAP_SERVER_REQUEST_RECEIVED,\n                                requests[0],\n                                &(avs_coap_response_header_t) {\n                                    .code = responses[0]->response_header.code\n                                },\n                                NULL);\n    expect_observe_start(&env, requests[0]->msg.token);\n    expect_send(&env, responses[0]);\n    expect_request_handler_call(&env, AVS_COAP_SERVER_REQUEST_CLEANUP, NULL,\n                                NULL, NULL);\n\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(\n            env.coap_ctx, test_accept_new_request, &env));\n    avs_coap_observe_id_t observe_id = {\n        .token = requests[0]->msg.token\n    };\n\n    expect_send(&env, responses[1]);\n\n    avs_coap_exchange_id_t id;\n    ASSERT_OK(avs_coap_notify_async(env.coap_ctx, &id, observe_id,\n                                    &responses[1]->response_header,\n                                    AVS_COAP_NOTIFY_PREFER_CONFIRMABLE, NULL,\n                                    NULL, test_observe_delivery_handler, &env));\n    ASSERT_TRUE(avs_coap_exchange_id_valid(id));\n\n    expect_recv(&env, requests[1]);\n    // Reset response should trigger FAIL result\n    expect_observe_delivery(&env,\n                            _avs_coap_err(AVS_COAP_ERR_UDP_RESET_RECEIVED));\n\n    // Whether the observations gets actually canceled depends on the config\n#    ifdef WITH_AVS_COAP_OBSERVE_FORCE_CANCEL_ON_UNACKED_ERROR\n    expect_observe_cancel(&env, observe_id.token);\n#    endif // WITH_AVS_COAP_OBSERVE_FORCE_CANCEL_ON_UNACKED_ERROR\n\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n\n#    ifdef WITH_AVS_COAP_OBSERVE_FORCE_CANCEL_ON_UNACKED_ERROR\n    // on exit, the observation should already be canceled\n    ASSERT_NULL(env.expects_list);\n#    else  // WITH_AVS_COAP_OBSERVE_FORCE_CANCEL_ON_UNACKED_ERROR\n    // we're using late_expects_check to capture the implicit cancellation when\n    // cleaning up the test\n    expect_observe_cancel(&env, observe_id.token);\n#    endif // WITH_AVS_COAP_OBSERVE_FORCE_CANCEL_ON_UNACKED_ERROR\n}\n\nAVS_UNIT_TEST(udp_observe,\n              notify_async_timeout_of_cancel_with_confirmable_error_response) {\n    avs_coap_udp_tx_params_t tx_params = AVS_COAP_DEFAULT_UDP_TX_PARAMS;\n    tx_params.max_retransmit = 0;\n    test_env_t env __attribute__((cleanup(test_teardown_late_expects_check))) =\n            test_setup(&tx_params, 4096, 4096, NULL);\n\n    const test_msg_t *requests[] = { COAP_MSG(\n            CON, GET, ID(100), MAKE_TOKEN(\"Obserw\"), OBSERVE(0), NO_PAYLOAD) };\n    const test_msg_t *responses[] = {\n        COAP_MSG(ACK, CONTENT, ID(100), MAKE_TOKEN(\"Obserw\"), OBSERVE(0),\n                 NO_PAYLOAD),\n        COAP_MSG(CON, NOT_FOUND, ID(0), MAKE_TOKEN(\"Obserw\"), NO_PAYLOAD),\n    };\n\n    expect_recv(&env, requests[0]);\n    expect_request_handler_call(&env, AVS_COAP_SERVER_REQUEST_RECEIVED,\n                                requests[0],\n                                &(avs_coap_response_header_t) {\n                                    .code = responses[0]->response_header.code\n                                },\n                                NULL);\n    expect_observe_start(&env, requests[0]->msg.token);\n    expect_send(&env, responses[0]);\n    expect_request_handler_call(&env, AVS_COAP_SERVER_REQUEST_CLEANUP, NULL,\n                                NULL, NULL);\n\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(\n            env.coap_ctx, test_accept_new_request, &env));\n    avs_coap_observe_id_t observe_id = {\n        .token = requests[0]->msg.token\n    };\n\n    expect_send(&env, responses[1]);\n\n    avs_coap_exchange_id_t id;\n    ASSERT_OK(avs_coap_notify_async(env.coap_ctx, &id, observe_id,\n                                    &responses[1]->response_header,\n                                    AVS_COAP_NOTIFY_PREFER_CONFIRMABLE, NULL,\n                                    NULL, test_observe_delivery_handler, &env));\n    ASSERT_TRUE(avs_coap_exchange_id_valid(id));\n\n    const avs_time_duration_t EPSILON =\n            avs_time_duration_from_scalar(1, AVS_TIME_S);\n\n    _avs_mock_clock_advance(avs_time_duration_add(\n            avs_coap_udp_max_transmit_wait(&tx_params), EPSILON));\n\n    // Timeout should trigger FAIL result\n    expect_observe_delivery(&env, _avs_coap_err(AVS_COAP_ERR_TIMEOUT));\n\n    // Whether the observations gets actually canceled depends on the config\n#    ifdef WITH_AVS_COAP_OBSERVE_FORCE_CANCEL_ON_UNACKED_ERROR\n    expect_observe_cancel(&env, observe_id.token);\n#    endif // WITH_AVS_COAP_OBSERVE_FORCE_CANCEL_ON_UNACKED_ERROR\n\n    expect_has_buffered_data_check(&env, false);\n    avs_sched_run(env.sched);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n\n#    ifdef WITH_AVS_COAP_OBSERVE_FORCE_CANCEL_ON_UNACKED_ERROR\n    // on exit, the observation should already be canceled\n    ASSERT_NULL(env.expects_list);\n#    else  // WITH_AVS_COAP_OBSERVE_FORCE_CANCEL_ON_UNACKED_ERROR\n    // we're using late_expects_check to capture the implicit cancellation when\n    // cleaning up the test\n    expect_observe_cancel(&env, observe_id.token);\n#    endif // WITH_AVS_COAP_OBSERVE_FORCE_CANCEL_ON_UNACKED_ERROR\n}\n\nAVS_UNIT_TEST(udp_observe, notify_async_confirmable_reset_response) {\n#    define NOTIFY_PAYLOAD \"Notifaj\"\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_default();\n\n    const test_msg_t *requests[] = {\n        COAP_MSG(CON, GET, ID(100), MAKE_TOKEN(\"Obserw\"), OBSERVE(0),\n                 NO_PAYLOAD),\n        COAP_MSG(RST, EMPTY, ID(0), NO_PAYLOAD),\n    };\n    const test_msg_t *responses[] = {\n        COAP_MSG(ACK, CONTENT, ID(100), MAKE_TOKEN(\"Obserw\"), OBSERVE(0),\n                 NO_PAYLOAD),\n        COAP_MSG(CON, CONTENT, ID(0), MAKE_TOKEN(\"Obserw\"), OBSERVE(1),\n                 PAYLOAD(NOTIFY_PAYLOAD)),\n    };\n\n    expect_recv(&env, requests[0]);\n    expect_request_handler_call(&env, AVS_COAP_SERVER_REQUEST_RECEIVED,\n                                requests[0],\n                                &(avs_coap_response_header_t) {\n                                    .code = responses[0]->response_header.code\n                                },\n                                NULL);\n    expect_observe_start(&env, MAKE_TOKEN(\"Obserw\"));\n    expect_send(&env, responses[0]);\n    expect_request_handler_call(&env, AVS_COAP_SERVER_REQUEST_CLEANUP, NULL,\n                                NULL, NULL);\n\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(\n            env.coap_ctx, test_accept_new_request, &env));\n\n    avs_coap_observe_id_t observe_id = {\n        .token = requests[0]->msg.token\n    };\n    test_payload_writer_args_t test_payload = {\n        .payload = NOTIFY_PAYLOAD,\n        .payload_size = sizeof(NOTIFY_PAYLOAD) - 1\n    };\n\n    expect_send(&env, responses[1]);\n\n    avs_coap_exchange_id_t id;\n    ASSERT_OK(avs_coap_notify_async(\n            env.coap_ctx, &id, observe_id, &responses[1]->response_header,\n            AVS_COAP_NOTIFY_PREFER_CONFIRMABLE, test_payload_writer,\n            &test_payload, test_observe_delivery_handler, &env));\n    ASSERT_TRUE(avs_coap_exchange_id_valid(id));\n\n    expect_recv(&env, requests[1]);\n    // Reset response should trigger FAIL result and observe cancellation\n    expect_observe_delivery(&env,\n                            _avs_coap_err(AVS_COAP_ERR_UDP_RESET_RECEIVED));\n    expect_observe_cancel(&env, MAKE_TOKEN(\"Obserw\"));\n\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n#    undef NOTIFY_PAYLOAD\n}\n\nAVS_UNIT_TEST(udp_observe, notify_async_non_confirmable_reset_response) {\n#    define NOTIFY_PAYLOAD \"Notifaj\"\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_default();\n\n    const test_msg_t *requests[] = {\n        COAP_MSG(CON, GET, ID(100), MAKE_TOKEN(\"Obserw\"), OBSERVE(0),\n                 NO_PAYLOAD),\n        COAP_MSG(RST, EMPTY, ID(0), NO_PAYLOAD),\n    };\n    const test_msg_t *responses[] = {\n        COAP_MSG(ACK, CONTENT, ID(100), MAKE_TOKEN(\"Obserw\"), OBSERVE(0),\n                 NO_PAYLOAD),\n        COAP_MSG(NON, CONTENT, ID(0), MAKE_TOKEN(\"Obserw\"), OBSERVE(1),\n                 PAYLOAD(NOTIFY_PAYLOAD)),\n    };\n\n    expect_recv(&env, requests[0]);\n    expect_request_handler_call(&env, AVS_COAP_SERVER_REQUEST_RECEIVED,\n                                requests[0],\n                                &(avs_coap_response_header_t) {\n                                    .code = responses[0]->response_header.code\n                                },\n                                NULL);\n    expect_observe_start(&env, MAKE_TOKEN(\"Obserw\"));\n    expect_send(&env, responses[0]);\n    expect_request_handler_call(&env, AVS_COAP_SERVER_REQUEST_CLEANUP, NULL,\n                                NULL, NULL);\n\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(\n            env.coap_ctx, test_accept_new_request, &env));\n\n    avs_coap_observe_id_t observe_id = {\n        .token = requests[0]->msg.token\n    };\n    test_payload_writer_args_t test_payload = {\n        .payload = NOTIFY_PAYLOAD,\n        .payload_size = sizeof(NOTIFY_PAYLOAD) - 1\n    };\n\n    expect_send(&env, responses[1]);\n\n    avs_coap_exchange_id_t id;\n    ASSERT_OK(avs_coap_notify_async(\n            env.coap_ctx, &id, observe_id, &responses[1]->response_header,\n            AVS_COAP_NOTIFY_PREFER_NON_CONFIRMABLE, test_payload_writer,\n            &test_payload, NULL, NULL));\n    ASSERT_FALSE(avs_coap_exchange_id_valid(id));\n\n    expect_recv(&env, requests[1]);\n    // Reset response should trigger observe cancellation\n    expect_observe_cancel(&env, MAKE_TOKEN(\"Obserw\"));\n\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n#    undef NOTIFY_PAYLOAD\n}\n\nAVS_UNIT_TEST(udp_observe, notify_async_delayed_reset_response) {\n#    define NOTIFY_PAYLOAD \"Notifaj\"\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_default();\n\n    const test_msg_t *request =\n            COAP_MSG(CON, GET, ID(100), MAKE_TOKEN(\"Obserw\"), OBSERVE(0),\n                     NO_PAYLOAD);\n    const test_msg_t *response =\n            COAP_MSG(ACK, CONTENT, ID(100), MAKE_TOKEN(\"Obserw\"), OBSERVE(0),\n                     NO_PAYLOAD);\n\n    expect_recv(&env, request);\n    expect_request_handler_call(&env, AVS_COAP_SERVER_REQUEST_RECEIVED, request,\n                                &(avs_coap_response_header_t) {\n                                    .code = response->response_header.code\n                                },\n                                NULL);\n    expect_observe_start(&env, MAKE_TOKEN(\"Obserw\"));\n    expect_send(&env, response);\n    expect_request_handler_call(&env, AVS_COAP_SERVER_REQUEST_CLEANUP, NULL,\n                                NULL, NULL);\n\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(\n            env.coap_ctx, test_accept_new_request, &env));\n\n    avs_coap_observe_id_t observe_id = {\n        .token = request->msg.token\n    };\n    test_payload_writer_args_t test_payload = {\n        .payload = NOTIFY_PAYLOAD,\n        .payload_size = sizeof(NOTIFY_PAYLOAD) - 1\n    };\n\n    // Send multiple notifications, make sure a delayed response to the first\n    // one causes cancellation if the cache is big enough\n    static const size_t NUM_NOTIFICATIONS = AVS_COAP_UDP_NOTIFY_CACHE_SIZE;\n    for (size_t i = 0; i < NUM_NOTIFICATIONS; ++i) {\n        const test_msg_t *notify =\n                COAP_MSG(NON, CONTENT, ID((uint16_t) i), MAKE_TOKEN(\"Obserw\"),\n                         OBSERVE((uint32_t) (i + 1)), PAYLOAD(NOTIFY_PAYLOAD));\n\n        expect_send(&env, notify);\n\n        avs_coap_exchange_id_t id;\n        test_payload.expected_payload_offset = 0;\n        ASSERT_OK(avs_coap_notify_async(\n                env.coap_ctx, &id, observe_id, &notify->response_header,\n                AVS_COAP_NOTIFY_PREFER_NON_CONFIRMABLE, test_payload_writer,\n                &test_payload, NULL, NULL));\n        ASSERT_FALSE(avs_coap_exchange_id_valid(id));\n    };\n\n    // first Notify had ID = 0\n    const uint16_t oldest_id_in_cache = 0;\n    const test_msg_t *reset =\n            COAP_MSG(RST, EMPTY, ID(oldest_id_in_cache), NO_PAYLOAD);\n\n    expect_recv(&env, reset);\n    // Reset response should trigger observe cancellation\n    expect_observe_cancel(&env, MAKE_TOKEN(\"Obserw\"));\n\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n#    undef NOTIFY_PAYLOAD\n}\n\nAVS_UNIT_TEST(udp_observe, notify_async_send_error) {\n#    define NOTIFY_PAYLOAD \"Notifaj\"\n    test_env_t env __attribute__((cleanup(test_teardown_late_expects_check))) =\n            test_setup_default();\n\n    const test_msg_t *request =\n            COAP_MSG(CON, GET, ID(100), MAKE_TOKEN(\"Obserw\"), OBSERVE(0),\n                     NO_PAYLOAD);\n    const test_msg_t *responses[] = {\n        COAP_MSG(ACK, CONTENT, ID(100), MAKE_TOKEN(\"Obserw\"), OBSERVE(0),\n                 NO_PAYLOAD),\n        COAP_MSG(NON, CONTENT, ID(0), MAKE_TOKEN(\"Obserw\"), OBSERVE(1),\n                 PAYLOAD(NOTIFY_PAYLOAD)),\n    };\n\n    expect_recv(&env, request);\n    expect_request_handler_call(&env, AVS_COAP_SERVER_REQUEST_RECEIVED, request,\n                                &(avs_coap_response_header_t) {\n                                    .code = responses[0]->response_header.code\n                                },\n                                NULL);\n    expect_observe_start(&env, MAKE_TOKEN(\"Obserw\"));\n    expect_send(&env, responses[0]);\n    expect_request_handler_call(&env, AVS_COAP_SERVER_REQUEST_CLEANUP, NULL,\n                                NULL, NULL);\n\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(\n            env.coap_ctx, test_accept_new_request, &env));\n\n    avs_coap_observe_id_t observe_id = {\n        .token = request->msg.token\n    };\n    test_payload_writer_args_t test_payload = {\n        .payload = NOTIFY_PAYLOAD,\n        .payload_size = sizeof(NOTIFY_PAYLOAD) - 1\n    };\n\n    avs_unit_mocksock_output_fail(env.mocksock, avs_errno(AVS_ECONNREFUSED));\n\n    ASSERT_FAIL(avs_coap_notify_async(\n            env.coap_ctx, NULL, observe_id, &responses[1]->response_header,\n            AVS_COAP_NOTIFY_PREFER_NON_CONFIRMABLE, test_payload_writer,\n            &test_payload, NULL, NULL));\n\n    // should be canceled by cleanup\n    expect_observe_cancel(&env, MAKE_TOKEN(\"Obserw\"));\n#    undef NOTIFY_PAYLOAD\n}\n\nAVS_UNIT_TEST(udp_observe, notify_async_confirmable_send_error) {\n#    define NOTIFY_PAYLOAD \"Notifaj\"\n    test_env_t env __attribute__((cleanup(test_teardown_late_expects_check))) =\n            test_setup_default();\n\n    const test_msg_t *requests[] = {\n        COAP_MSG(CON, GET, ID(100), MAKE_TOKEN(\"Obserw\"), OBSERVE(0),\n                 NO_PAYLOAD),\n        COAP_MSG(ACK, EMPTY, ID(0), NO_PAYLOAD),\n    };\n    const test_msg_t *responses[] = {\n        COAP_MSG(ACK, CONTENT, ID(100), MAKE_TOKEN(\"Obserw\"), OBSERVE(0),\n                 NO_PAYLOAD),\n        COAP_MSG(CON, CONTENT, ID(0), MAKE_TOKEN(\"Obserw\"), OBSERVE(1),\n                 PAYLOAD(NOTIFY_PAYLOAD)),\n    };\n\n    expect_recv(&env, requests[0]);\n    expect_request_handler_call(&env, AVS_COAP_SERVER_REQUEST_RECEIVED,\n                                requests[0],\n                                &(avs_coap_response_header_t) {\n                                    .code = responses[0]->response_header.code\n                                },\n                                NULL);\n    expect_observe_start(&env, MAKE_TOKEN(\"Obserw\"));\n    expect_send(&env, responses[0]);\n    expect_request_handler_call(&env, AVS_COAP_SERVER_REQUEST_CLEANUP, NULL,\n                                NULL, NULL);\n\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(\n            env.coap_ctx, test_accept_new_request, &env));\n\n    avs_coap_observe_id_t observe_id = {\n        .token = requests[0]->msg.token\n    };\n    test_payload_writer_args_t test_payload = {\n        .payload = NOTIFY_PAYLOAD,\n        .payload_size = sizeof(NOTIFY_PAYLOAD) - 1\n    };\n\n    avs_unit_mocksock_output_fail(env.mocksock, avs_errno(AVS_ECONNREFUSED));\n\n    ASSERT_FAIL(avs_coap_notify_async(\n            env.coap_ctx, NULL, observe_id, &responses[1]->response_header,\n            AVS_COAP_NOTIFY_PREFER_CONFIRMABLE, test_payload_writer,\n            &test_payload, test_observe_delivery_handler, &env));\n\n    // should be canceled by cleanup\n    expect_observe_cancel(&env, MAKE_TOKEN(\"Obserw\"));\n#    undef NOTIFY_PAYLOAD\n}\n\n#    ifdef WITH_AVS_COAP_BLOCK\n\nAVS_UNIT_TEST(udp_observe, notify_async_block) {\n#        define NOTIFY_PAYLOAD DATA_1KB \"Notifaj\"\n    test_env_t env __attribute__((cleanup(test_teardown_late_expects_check))) =\n            test_setup_default();\n\n    const test_msg_t *requests[] = {\n        COAP_MSG(CON, GET, ID(100), MAKE_TOKEN(\"Obserw\"), OBSERVE(0),\n                 NO_PAYLOAD),\n\n        // request for second block of Notify\n        COAP_MSG(CON, GET, ID(101), MAKE_TOKEN(\"Notifaj\"), BLOCK2_REQ(1, 1024)),\n    };\n    const test_msg_t *responses[] = {\n        COAP_MSG(ACK, CONTENT, ID(100), MAKE_TOKEN(\"Obserw\"), OBSERVE(0),\n                 NO_PAYLOAD),\n\n        // BLOCK Notify\n        COAP_MSG(NON, CONTENT, ID(0), MAKE_TOKEN(\"Obserw\"), OBSERVE(1),\n                 BLOCK2_RES(0, 1024, NOTIFY_PAYLOAD)),\n        // Note: further blocks should not contain the Observe option\n        // see RFC 7959, Figure 12: \"Observe Sequence with Block-Wise Response\"\n        COAP_MSG(ACK, CONTENT, ID(101), MAKE_TOKEN(\"Notifaj\"),\n                 BLOCK2_RES(1, 1024, NOTIFY_PAYLOAD)),\n    };\n\n    expect_recv(&env, requests[0]);\n    expect_request_handler_call(&env, AVS_COAP_SERVER_REQUEST_RECEIVED,\n                                requests[0],\n                                &(avs_coap_response_header_t) {\n                                    .code = responses[0]->response_header.code\n                                },\n                                NULL);\n    expect_observe_start(&env, requests[0]->msg.token);\n    expect_send(&env, responses[0]);\n    expect_request_handler_call(&env, AVS_COAP_SERVER_REQUEST_CLEANUP, NULL,\n                                NULL, NULL);\n\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(\n            env.coap_ctx, test_accept_new_request, &env));\n    avs_coap_observe_id_t observe_id = {\n        .token = requests[0]->msg.token\n    };\n    test_payload_writer_args_t test_payload = {\n        .payload = NOTIFY_PAYLOAD,\n        .payload_size = sizeof(NOTIFY_PAYLOAD) - 1\n    };\n\n    expect_send(&env, responses[1]);\n\n    avs_coap_exchange_id_t id;\n    ASSERT_OK(avs_coap_notify_async(\n            env.coap_ctx, &id, observe_id, &responses[1]->response_header,\n            AVS_COAP_NOTIFY_PREFER_NON_CONFIRMABLE, test_payload_writer,\n            &test_payload, NULL, NULL));\n    ASSERT_TRUE(avs_coap_exchange_id_valid(id));\n\n    expect_recv(&env, requests[1]);\n    expect_send(&env, responses[2]);\n\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n#        undef NOTIFY_PAYLOAD\n\n    // should be canceled by cleanup\n    expect_observe_cancel(&env, requests[0]->msg.token);\n}\n\nstatic void free_deref_arg_delivery_handler(avs_coap_ctx_t *ctx,\n                                            avs_error_t err,\n                                            void *arg) {\n    (void) ctx;\n\n    ASSERT_OK(err);\n    void **free_me_ptr = (void **) arg;\n    free(*free_me_ptr);\n    *free_me_ptr = NULL;\n}\n\nAVS_UNIT_TEST(udp_observe, notify_async_non_confirmable_block_with_cleanup) {\n#        define NOTIFY_PAYLOAD DATA_1KB \"Notifaj\"\n    test_env_t env __attribute__((cleanup(test_teardown_late_expects_check))) =\n            test_setup_default();\n\n    const test_msg_t *requests[] = {\n        COAP_MSG(CON, GET, ID(100), MAKE_TOKEN(\"Obserw\"), OBSERVE(0),\n                 NO_PAYLOAD),\n\n        // request for second block of Notify\n        COAP_MSG(CON, GET, ID(101), MAKE_TOKEN(\"Notifaj\"), BLOCK2_REQ(1, 1024)),\n    };\n    const test_msg_t *responses[] = {\n        COAP_MSG(ACK, CONTENT, ID(100), MAKE_TOKEN(\"Obserw\"), OBSERVE(0),\n                 NO_PAYLOAD),\n\n        // BLOCK Notify\n        COAP_MSG(NON, CONTENT, ID(0), MAKE_TOKEN(\"Obserw\"), OBSERVE(1),\n                 BLOCK2_RES(0, 1024, NOTIFY_PAYLOAD)),\n        // Note: further blocks should not contain the Observe option\n        // see RFC 7959, Figure 12: \"Observe Sequence with Block-Wise Response\"\n        COAP_MSG(ACK, CONTENT, ID(101), MAKE_TOKEN(\"Notifaj\"),\n                 BLOCK2_RES(1, 1024, NOTIFY_PAYLOAD)),\n    };\n\n    expect_recv(&env, requests[0]);\n    expect_request_handler_call(&env, AVS_COAP_SERVER_REQUEST_RECEIVED,\n                                requests[0],\n                                &(avs_coap_response_header_t) {\n                                    .code = responses[0]->response_header.code\n                                },\n                                NULL);\n    expect_observe_start(&env, MAKE_TOKEN(\"Obserw\"));\n    expect_send(&env, responses[0]);\n    expect_request_handler_call(&env, AVS_COAP_SERVER_REQUEST_CLEANUP, NULL,\n                                NULL, NULL);\n\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(\n            env.coap_ctx, test_accept_new_request, &env));\n    avs_coap_observe_id_t observe_id = {\n        .token = requests[0]->msg.token\n    };\n\n    test_payload_writer_args_t *test_payload =\n            (test_payload_writer_args_t *) malloc(sizeof(*test_payload));\n    *test_payload = (test_payload_writer_args_t) {\n        .payload = NOTIFY_PAYLOAD,\n        .payload_size = sizeof(NOTIFY_PAYLOAD) - 1\n    };\n\n    expect_send(&env, responses[1]);\n\n    avs_coap_exchange_id_t id;\n    ASSERT_OK(avs_coap_notify_async(\n            env.coap_ctx, &id, observe_id, &responses[1]->response_header,\n            AVS_COAP_NOTIFY_PREFER_NON_CONFIRMABLE, test_payload_writer,\n            test_payload, free_deref_arg_delivery_handler, &test_payload));\n    ASSERT_TRUE(avs_coap_exchange_id_valid(id));\n\n    // request for the second notification block should be handled\n    expect_recv(&env, requests[1]);\n    expect_send(&env, responses[2]);\n\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n\n    // if all went well, free_arg_delivery_handler was called\n    ASSERT_NULL(test_payload);\n\n    // should be canceled by cleanup\n    expect_observe_cancel(&env, requests[0]->msg.token);\n#        undef NOTIFY_PAYLOAD\n}\n\n#    endif // WITH_AVS_COAP_BLOCK\n\n// Not specified in RFC 7252 and RFC 7641, but specified in RFC 8613. Another\n// request with the same token shouldn't affect already registered observation\n// with the same token.\nAVS_UNIT_TEST(udp_observe, request_with_the_same_token_as_observe_token) {\n#    define NOTIFY_PAYLOAD \"Notifaj\"\n    test_env_t env __attribute__((cleanup(test_teardown_late_expects_check))) =\n            test_setup_default();\n\n    const test_msg_t *requests[] = {\n        COAP_MSG(CON, GET, ID(100), MAKE_TOKEN(\"Obserw\"), OBSERVE(0),\n                 NO_PAYLOAD),\n        COAP_MSG(CON, GET, ID(101), MAKE_TOKEN(\"Obserw\"), NO_PAYLOAD)\n    };\n\n    const test_msg_t *responses[] = {\n        COAP_MSG(ACK, CONTENT, ID(100), MAKE_TOKEN(\"Obserw\"), OBSERVE(0),\n                 NO_PAYLOAD),\n        COAP_MSG(ACK, CONTENT, ID(101), MAKE_TOKEN(\"Obserw\"), NO_PAYLOAD),\n        COAP_MSG(NON, CONTENT, ID(0), MAKE_TOKEN(\"Obserw\"), OBSERVE(1),\n                 PAYLOAD(NOTIFY_PAYLOAD)),\n    };\n\n    // Request with Observe option\n    expect_recv(&env, requests[0]);\n    expect_request_handler_call(&env, AVS_COAP_SERVER_REQUEST_RECEIVED,\n                                requests[0],\n                                &(avs_coap_response_header_t) {\n                                    .code = responses[0]->response_header.code\n                                },\n                                NULL);\n    expect_observe_start(&env, MAKE_TOKEN(\"Obserw\"));\n    expect_send(&env, responses[0]);\n    expect_request_handler_call(&env, AVS_COAP_SERVER_REQUEST_CLEANUP, NULL,\n                                NULL, NULL);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(\n            env.coap_ctx, test_accept_new_request, &env));\n\n    // Request without Observe option and the same token\n    expect_recv(&env, requests[1]);\n    expect_request_handler_call(&env, AVS_COAP_SERVER_REQUEST_RECEIVED,\n                                requests[1],\n                                &(avs_coap_response_header_t) {\n                                    .code = responses[0]->response_header.code\n                                },\n                                NULL);\n    expect_send(&env, responses[1]);\n    expect_request_handler_call(&env, AVS_COAP_SERVER_REQUEST_CLEANUP, NULL,\n                                NULL, NULL);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(\n            env.coap_ctx, test_accept_new_request, &env));\n\n    avs_coap_observe_id_t observe_id = {\n        .token = requests[0]->msg.token\n    };\n    test_payload_writer_args_t test_payload = {\n        .payload = NOTIFY_PAYLOAD,\n        .payload_size = sizeof(NOTIFY_PAYLOAD) - 1\n    };\n\n    expect_send(&env, responses[2]);\n\n    avs_coap_exchange_id_t id;\n    ASSERT_OK(avs_coap_notify_async(\n            env.coap_ctx, &id, observe_id, &responses[1]->response_header,\n            AVS_COAP_NOTIFY_PREFER_NON_CONFIRMABLE, test_payload_writer,\n            &test_payload, NULL, NULL));\n    ASSERT_FALSE(avs_coap_exchange_id_valid(id));\n\n    // should be canceled by cleanup\n    expect_observe_cancel(&env, MAKE_TOKEN(\"Obserw\"));\n#    undef NOTIFY_PAYLOAD\n}\n\nAVS_UNIT_TEST(udp_observe, cancel_confirmable_notification) {\n#    define NOTIFY_PAYLOAD \"Notifaj\"\n    test_env_t env __attribute__((cleanup(test_teardown_late_expects_check))) =\n            test_setup_default();\n\n    const test_msg_t *requests[] = { COAP_MSG(\n            CON, GET, ID(100), MAKE_TOKEN(\"Obserw\"), OBSERVE(0), NO_PAYLOAD) };\n    const test_msg_t *responses[] = {\n        COAP_MSG(ACK, CONTENT, ID(100), MAKE_TOKEN(\"Obserw\"), OBSERVE(0),\n                 NO_PAYLOAD),\n        COAP_MSG(CON, CONTENT, ID(0), MAKE_TOKEN(\"Obserw\"), OBSERVE(1),\n                 PAYLOAD(NOTIFY_PAYLOAD)),\n    };\n\n    expect_recv(&env, requests[0]);\n    expect_request_handler_call(&env, AVS_COAP_SERVER_REQUEST_RECEIVED,\n                                requests[0],\n                                &(avs_coap_response_header_t) {\n                                    .code = responses[0]->response_header.code\n                                },\n                                NULL);\n    expect_observe_start(&env, MAKE_TOKEN(\"Obserw\"));\n    expect_send(&env, responses[0]);\n    expect_request_handler_call(&env, AVS_COAP_SERVER_REQUEST_CLEANUP, NULL,\n                                NULL, NULL);\n\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(\n            env.coap_ctx, test_accept_new_request, &env));\n\n    avs_coap_observe_id_t observe_id = {\n        .token = requests[0]->msg.token\n    };\n    test_payload_writer_args_t test_payload = {\n        .payload = NOTIFY_PAYLOAD,\n        .payload_size = sizeof(NOTIFY_PAYLOAD) - 1\n    };\n\n    expect_send(&env, responses[1]);\n\n    avs_coap_exchange_id_t id;\n    ASSERT_OK(avs_coap_notify_async(\n            env.coap_ctx, &id, observe_id, &responses[1]->response_header,\n            AVS_COAP_NOTIFY_PREFER_CONFIRMABLE, test_payload_writer,\n            &test_payload, test_observe_delivery_handler, &env));\n    ASSERT_TRUE(avs_coap_exchange_id_valid(id));\n\n    ASSERT_NOT_NULL(\n            _avs_coap_get_base(env.coap_ctx)->retry_or_request_expired_job);\n\n    expect_observe_delivery(&env,\n                            _avs_coap_err(AVS_COAP_ERR_EXCHANGE_CANCELED));\n    avs_coap_exchange_cancel(env.coap_ctx, id);\n\n    ASSERT_FALSE(avs_time_monotonic_valid(\n            _avs_coap_retry_or_request_expired_job(env.coap_ctx)));\n\n    // should be canceled by cleanup\n    expect_observe_cancel(&env, MAKE_TOKEN(\"Obserw\"));\n#    undef NOTIFY_PAYLOAD\n}\n\n#endif // defined(AVS_UNIT_TESTING) && defined(WITH_AVS_COAP_UDP) &&\n       // defined(WITH_AVS_COAP_OBSERVE)\n"
  },
  {
    "path": "deps/avs_coap/tests/udp/async_server.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <avs_coap_init.h>\n\n#if defined(AVS_UNIT_TESTING) && defined(WITH_AVS_COAP_UDP)\n\n#    include <avsystem/commons/avs_errno.h>\n#    include <avsystem/commons/avs_time.h>\n\n#    include <avsystem/coap/coap.h>\n\n#    define MODULE_NAME test\n#    include <avs_coap_x_log_config.h>\n\n#    include \"./utils.h\"\n\nAVS_UNIT_TEST(udp_async_server, coap_ping) {\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_default();\n\n    const test_msg_t *ping = COAP_MSG(CON, EMPTY, ID(0), NO_PAYLOAD);\n    const test_msg_t *pong = COAP_MSG(RST, EMPTY, ID(0), NO_PAYLOAD);\n\n    // the library should handle CoAP ping internally\n    expect_recv(&env, ping);\n    expect_send(&env, pong);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n}\n\nAVS_UNIT_TEST(udp_async_server, non_request_non_response_non_empty_is_ignored) {\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_default();\n\n#    define AVS_COAP_CODE_WTF AVS_COAP_CODE(6, 6)\n    const test_msg_t *unknown = COAP_MSG(CON, WTF, ID(0), NO_PAYLOAD);\n#    undef AVS_COAP_CODE_WTF\n\n    // the library should ignore such request\n    expect_recv(&env, unknown);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n}\n\nstatic int\nfailing_nonblock_request_handler(avs_coap_server_ctx_t *ctx,\n                                 const avs_coap_request_header_t *request,\n                                 void *result) {\n    (void) ctx;\n    (void) request;\n\n    return *(int *) result;\n}\n\nAVS_UNIT_TEST(udp_async_server, incoming_request_error_response) {\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_default();\n\n    const test_msg_t *request =\n            COAP_MSG(CON, GET, ID(0), MAKE_TOKEN(\"A token\"));\n    const test_msg_t *response =\n            COAP_MSG(ACK, NOT_FOUND, ID(0), MAKE_TOKEN(\"A token\"));\n\n    int result_to_return = AVS_COAP_CODE_NOT_FOUND;\n\n    expect_recv(&env, request);\n    expect_send(&env, response);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(\n            env.coap_ctx, failing_nonblock_request_handler, &result_to_return));\n}\n\nAVS_UNIT_TEST(udp_async_server, incoming_request_content_response) {\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_default();\n\n#    define PAYLOAD_CONTENT \"It's dangerous to go alone, take this\"\n\n    const test_msg_t *request =\n            COAP_MSG(CON, GET, ID(0), MAKE_TOKEN(\"A token\"), NO_PAYLOAD);\n    const test_msg_t *response =\n            COAP_MSG(ACK, CONTENT, ID(0), MAKE_TOKEN(\"A token\"),\n                     PAYLOAD(PAYLOAD_CONTENT));\n\n    test_payload_writer_args_t response_payload = {\n        .payload = PAYLOAD_CONTENT,\n        .payload_size = sizeof(PAYLOAD_CONTENT) - 1\n    };\n\n    expect_recv(&env, request);\n    expect_request_handler_call(&env, AVS_COAP_SERVER_REQUEST_RECEIVED, request,\n                                &(avs_coap_response_header_t) {\n                                    .code = response->response_header.code\n                                },\n                                &response_payload);\n    expect_send(&env, response);\n    expect_request_handler_call(&env, AVS_COAP_SERVER_REQUEST_CLEANUP, NULL,\n                                NULL, NULL);\n\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(\n            env.coap_ctx, test_accept_new_request, &env));\n\n#    undef PAYLOAD_CONTENT\n}\n\nstatic void payload_writer_fail_case(bool cancel_exchange) {\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_default();\n\n    const test_msg_t *request =\n            COAP_MSG(CON, GET, ID(0), MAKE_TOKEN(\"A token\"), NO_PAYLOAD);\n    const test_msg_t *response = COAP_MSG(ACK, INTERNAL_SERVER_ERROR, ID(0),\n                                          MAKE_TOKEN(\"A token\"), NO_PAYLOAD);\n\n    test_payload_writer_args_t response_payload = {\n        .coap_ctx = env.coap_ctx,\n        .messages_until_fail = 1,\n        .cancel_exchange = cancel_exchange\n    };\n\n    expect_recv(&env, request);\n    expect_request_handler_call(&env, AVS_COAP_SERVER_REQUEST_RECEIVED, request,\n                                &(avs_coap_response_header_t) {\n                                    .code = response->response_header.code\n                                },\n                                &response_payload);\n    expect_send(&env, response);\n    expect_request_handler_call(&env, AVS_COAP_SERVER_REQUEST_CLEANUP, NULL,\n                                NULL, NULL);\n\n    avs_error_t err = avs_coap_async_handle_incoming_packet(\n            env.coap_ctx, test_accept_new_request, &env);\n    AVS_UNIT_ASSERT_EQUAL(err.category, AVS_COAP_ERR_CATEGORY);\n    AVS_UNIT_ASSERT_EQUAL(err.code,\n                          cancel_exchange ? AVS_COAP_ERR_EXCHANGE_CANCELED\n                                          : AVS_COAP_ERR_PAYLOAD_WRITER_FAILED);\n}\n\nAVS_UNIT_TEST(udp_async_server, incoming_request_payload_writer_fail) {\n    payload_writer_fail_case(false);\n}\n\nAVS_UNIT_TEST(udp_async_server,\n              incoming_request_payload_writer_fail_and_cancel_exchange) {\n    payload_writer_fail_case(true);\n}\n\nAVS_UNIT_TEST(udp_async_server, send_request_in_request_handler) {\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_default();\n\n    const test_msg_t *incoming_request =\n            COAP_MSG(CON, GET, ID(123), MAKE_TOKEN(\"A token\"), NO_PAYLOAD);\n    const test_msg_t *outgoing_response =\n            COAP_MSG(ACK, CONTENT, ID(123), MAKE_TOKEN(\"A token\"), NO_PAYLOAD);\n\n    const test_msg_t *outgoing_request =\n            COAP_MSG(NON, GET, ID(0), TOKEN(nth_token(0)), NO_PAYLOAD);\n    const test_msg_t *incoming_response =\n            COAP_MSG(ACK, CONTENT, ID(0), TOKEN(nth_token(0)), NO_PAYLOAD);\n\n    expect_recv(&env, incoming_request);\n    expect_request_handler_call_and_force_sending_request(\n            &env, AVS_COAP_SERVER_REQUEST_RECEIVED, incoming_request,\n            &(avs_coap_response_header_t) {\n                .code = outgoing_response->response_header.code\n            },\n            NULL);\n    expect_send(&env, outgoing_request);\n    expect_send(&env, outgoing_response);\n\n    expect_request_handler_call(&env, AVS_COAP_SERVER_REQUEST_CLEANUP, NULL,\n                                NULL, NULL);\n\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(\n            env.coap_ctx, test_accept_new_request, &env));\n\n    expect_recv(&env, incoming_response);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n}\n\nAVS_UNIT_TEST(udp_async_server, incoming_request_echo_content) {\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_default();\n\n#    define PAYLOAD_CONTENT \"It's dangerous to go alone, take this\"\n\n    const test_msg_t *request = COAP_MSG(CON, GET, ID(0), MAKE_TOKEN(\"A token\"),\n                                         PAYLOAD(PAYLOAD_CONTENT));\n    const test_msg_t *response =\n            COAP_MSG(ACK, CONTENT, ID(0), MAKE_TOKEN(\"A token\"),\n                     PAYLOAD(PAYLOAD_CONTENT));\n\n    test_payload_writer_args_t response_payload = {\n        .payload = PAYLOAD_CONTENT,\n        .payload_size = sizeof(PAYLOAD_CONTENT) - 1\n    };\n\n    expect_recv(&env, request);\n    expect_request_handler_call(&env, AVS_COAP_SERVER_REQUEST_RECEIVED, request,\n                                &(avs_coap_response_header_t) {\n                                    .code = response->response_header.code\n                                },\n                                &response_payload);\n    expect_send(&env, response);\n    expect_request_handler_call(&env, AVS_COAP_SERVER_REQUEST_CLEANUP, NULL,\n                                NULL, NULL);\n\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(\n            env.coap_ctx, test_accept_new_request, &env));\n\n#    undef PAYLOAD_CONTENT\n}\n\nAVS_UNIT_TEST(udp_async_server, cached_response) {\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_with_cache(1024);\n\n#    define PAYLOAD_CONTENT                                                \\\n        \"Krzysztofie, motyla noga, to jest glin o czystosci technicznej. \" \\\n        \"Smiem watpic, abys zdolal go pomalowac.\"\n\n    const test_msg_t *request =\n            COAP_MSG(CON, GET, ID(0), MAKE_TOKEN(\"4m3l1num\"),\n                     PAYLOAD(PAYLOAD_CONTENT));\n    const test_msg_t *response =\n            COAP_MSG(ACK, CONTENT, ID(0), MAKE_TOKEN(\"4m3l1num\"),\n                     PAYLOAD(PAYLOAD_CONTENT));\n\n    test_payload_writer_args_t response_payload = {\n        .payload = PAYLOAD_CONTENT,\n        .payload_size = sizeof(PAYLOAD_CONTENT) - 1\n    };\n\n    expect_recv(&env, request);\n    expect_request_handler_call(&env, AVS_COAP_SERVER_REQUEST_RECEIVED, request,\n                                &(avs_coap_response_header_t) {\n                                    .code = response->response_header.code\n                                },\n                                &response_payload);\n    expect_send(&env, response);\n    expect_request_handler_call(&env, AVS_COAP_SERVER_REQUEST_CLEANUP, NULL,\n                                NULL, NULL);\n\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(\n            env.coap_ctx, test_accept_new_request, &env));\n\n    // duplicate request is supposed to be handled internally by repeating\n    // cached response\n    expect_recv(&env, request);\n    expect_send(&env, response);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n\n    avs_coap_stats_t stats = avs_coap_get_stats(env.coap_ctx);\n    ASSERT_EQ(stats.incoming_retransmissions_count, 1);\n    ASSERT_EQ(stats.outgoing_retransmissions_count, 0);\n\n    // another duplicated request\n    expect_recv(&env, request);\n    expect_send(&env, response);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n\n    stats = avs_coap_get_stats(env.coap_ctx);\n    ASSERT_EQ(stats.incoming_retransmissions_count, 2);\n    ASSERT_EQ(stats.outgoing_retransmissions_count, 0);\n\n#    undef PAYLOAD_CONTENT\n}\n\nAVS_UNIT_TEST(udp_async_server, truncated_request_full_token) {\n    // 8 bytes for input buffer: enough for CoAP/UDP header and 4-byte token\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup(NULL, 8, 1024, NULL);\n\n    // messages with full tokens should get Request Entity Too Large response\n    const test_msg_t *full_token_req =\n            COAP_MSG(CON, GET, ID(0), MAKE_TOKEN(\"AAA\"), PAYLOAD(\"a\"));\n    const test_msg_t *full_token_res =\n            COAP_MSG(ACK, REQUEST_ENTITY_TOO_LARGE, ID(0), MAKE_TOKEN(\"AAA\"),\n                     NO_PAYLOAD);\n\n    expect_recv(&env, full_token_req);\n\n    expect_send(&env, full_token_res);\n\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, &env));\n}\n\nAVS_UNIT_TEST(udp_async_server, truncated_request_incomplete_token) {\n    // 8 bytes for input buffer: enough for CoAP/UDP header and 4-byte token\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup(NULL, 8, 1024, NULL);\n\n    // messages with incomplete tokens should be ignored\n    const test_msg_t *truncated_token_req =\n            COAP_MSG(CON, GET, ID(0), MAKE_TOKEN(\"BBBBB\"), NO_PAYLOAD);\n\n    expect_recv(&env, truncated_token_req);\n\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, &env));\n}\n\nAVS_UNIT_TEST(udp_async_server, truncated_response_full_token) {\n    // 12 bytes for input buffer: enough for CoAP/UDP header and 8-byte token\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup(NULL, 12, 1024, NULL);\n\n    // messages with full tokens should get Request Entity Too Large response\n    const test_msg_t *full_token_req =\n            COAP_MSG(CON, GET, ID(0), TOKEN(nth_token(0)));\n    const test_msg_t *full_token_res =\n            COAP_MSG(ACK, CONTENT, ID(0), TOKEN(nth_token(0)), PAYLOAD(\"a\"));\n\n    avs_coap_exchange_id_t id;\n\n    // a request should be sent\n    ASSERT_OK(avs_coap_client_send_async_request(\n            env.coap_ctx, &id, &full_token_req->request_header, NULL, NULL,\n            test_response_handler, &env.expects_list));\n    ASSERT_TRUE(avs_coap_exchange_id_valid(id));\n\n    expect_send(&env, full_token_req);\n    avs_sched_run(env.sched);\n\n    // receiving response should make the context call handler\n    expect_recv(&env, full_token_res);\n    expect_handler_call(&env, &id, AVS_COAP_CLIENT_REQUEST_FAIL, NULL);\n\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n}\n\nAVS_UNIT_TEST(udp_async_server, truncated_response_incomplete_token) {\n    // 11 bytes for input buffer: enough for CoAP/UDP header and 7-byte token\n    test_env_t env __attribute__((cleanup(test_teardown_late_expects_check))) =\n            test_setup(NULL, 11, 1024, NULL);\n\n    // messages with incomplete tokens should be ignored\n    const test_msg_t *truncated_token_req =\n            COAP_MSG(CON, GET, ID(0), TOKEN(nth_token(0)), NO_PAYLOAD);\n    const test_msg_t *truncated_token_res =\n            COAP_MSG(ACK, CONTENT, ID(0), TOKEN(nth_token(0)), NO_PAYLOAD);\n\n    avs_coap_exchange_id_t id;\n\n    ASSERT_OK(avs_coap_client_send_async_request(\n            env.coap_ctx, &id, &truncated_token_req->request_header, NULL, NULL,\n            test_response_handler, &env.expects_list));\n    ASSERT_TRUE(avs_coap_exchange_id_valid(id));\n\n    expect_send(&env, truncated_token_req);\n    avs_sched_run(env.sched);\n\n    expect_recv(&env, truncated_token_res);\n\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, &env));\n\n    // this needs to be cleaned up during teardown\n    expect_handler_call(&env, &id, AVS_COAP_CLIENT_REQUEST_CANCEL, NULL);\n}\n\nAVS_UNIT_TEST(udp_async_server, repeated_non_repeatable_critical_option) {\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_default();\n\n    // From RFC7252:\n    // 5.4.5:\n    // \"If a message includes an option with more occurrences than the option\n    //  is defined for, each supernumerary option occurrence that appears\n    //  subsequently in the message MUST be treated like an unrecognized option\n    //  (see Section 5.4.1).\"\n    //\n    // 5.4.1:\n    // \"Unrecognized options of class \"critical\" that occur in a Confirmable\n    //  request MUST cause the return of a 4.02 (Bad Option) response. This\n    //  response SHOULD include a diagnostic payload describing the unrecognized\n    //  option(s) (see Section 5.5.2).\"\n    const test_msg_t *request = COAP_MSG(CON, PUT, ID(0), TOKEN(nth_token(0)),\n                                         ACCEPT(1), DUPLICATED_ACCEPT(2));\n    const test_msg_t *response =\n            COAP_MSG(ACK, BAD_OPTION, ID(0), TOKEN(nth_token(0)));\n    expect_recv(&env, request);\n    expect_send(&env, response);\n\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n}\n\nAVS_UNIT_TEST(udp_async_server, nonempty_empty_messages) {\n    const test_msg_t *requests[] = {\n        COAP_MSG(ACK, EMPTY, ID(0), TOKEN(nth_token(0))),\n        COAP_MSG(ACK, EMPTY, ID(0), CONTENT_FORMAT_VALUE(1)),\n        COAP_MSG(ACK, EMPTY, ID(0), PAYLOAD(\"zadowolony\")),\n        COAP_MSG(CON, EMPTY, ID(0), TOKEN(nth_token(0)))\n    };\n\n    for (size_t i = 0; i < AVS_ARRAY_SIZE(requests); i++) {\n        test_env_t env = test_setup_default();\n        expect_recv(&env, requests[i]);\n        expect_has_buffered_data_check(&env, false);\n        ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL,\n                                                        NULL));\n        test_teardown(&env);\n    }\n}\n\n#    ifdef WITH_AVS_COAP_BLOCK\n\nAVS_UNIT_TEST(udp_async_server, incoming_request_block_response) {\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_default();\n\n#        define REQUEST_PAYLOAD DATA_1KB \"?\"\n\n    const test_msg_t *requests[] = {\n        COAP_MSG(CON, GET, ID(0), TOKEN(nth_token(0)),\n                 BLOCK1_REQ(0, 1024, REQUEST_PAYLOAD)),\n        COAP_MSG(CON, GET, ID(1), TOKEN(nth_token(1)),\n                 BLOCK1_REQ(1, 1024, REQUEST_PAYLOAD)),\n        COAP_MSG(CON, GET, ID(2), TOKEN(nth_token(2)), BLOCK2_REQ(1, 1024))\n    };\n    const test_msg_t *responses[] = {\n        COAP_MSG(ACK, CONTINUE, ID(0), TOKEN(nth_token(0)),\n                 BLOCK1_RES(0, 1024, true)),\n        COAP_MSG(ACK, CONTENT, ID(1), TOKEN(nth_token(1)),\n                 BLOCK1_AND_2_RES(1, 1024, 1024, REQUEST_PAYLOAD)),\n        COAP_MSG(ACK, CONTENT, ID(2), TOKEN(nth_token(2)),\n                 BLOCK2_RES(1, 1024, REQUEST_PAYLOAD))\n    };\n\n    test_payload_writer_args_t response_payload = {\n        .payload = REQUEST_PAYLOAD,\n        .payload_size = sizeof(REQUEST_PAYLOAD) - 1\n    };\n\n    expect_recv(&env, requests[0]);\n    expect_request_handler_call(&env, AVS_COAP_SERVER_REQUEST_PARTIAL_CONTENT,\n                                requests[0], NULL, NULL);\n    expect_send(&env, responses[0]);\n\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(\n            env.coap_ctx, test_accept_new_request, &env));\n\n    expect_recv(&env, requests[1]);\n    expect_request_handler_call(&env, AVS_COAP_SERVER_REQUEST_RECEIVED,\n                                requests[1],\n                                &(avs_coap_response_header_t) {\n                                    .code = responses[1]->response_header.code\n                                },\n                                &response_payload);\n    expect_send(&env, responses[1]);\n\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n\n    expect_recv(&env, requests[2]);\n    expect_request_handler_call(&env, AVS_COAP_SERVER_REQUEST_CLEANUP, NULL,\n                                NULL, NULL);\n    expect_send(&env, responses[2]);\n\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n\n#        undef REQUEST_PAYLOAD\n}\n\nAVS_UNIT_TEST(udp_async_server, incoming_block_request_nonblock_response) {\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_default();\n\n#        define REQUEST_PAYLOAD DATA_1KB \"?\"\n#        define RESPONSE_PAYLOAD \"abcd1234\"\n\n    const test_msg_t *requests[] = {\n        COAP_MSG(CON, GET, ID(0), TOKEN(nth_token(0)),\n                 BLOCK1_REQ(0, 1024, REQUEST_PAYLOAD)),\n        COAP_MSG(CON, GET, ID(1), TOKEN(nth_token(1)),\n                 BLOCK1_REQ(1, 1024, REQUEST_PAYLOAD))\n    };\n    const test_msg_t *responses[] = {\n        COAP_MSG(ACK, CONTINUE, ID(0), TOKEN(nth_token(0)),\n                 BLOCK1_RES(0, 1024, true)),\n        COAP_MSG(ACK, CONTENT, ID(1), TOKEN(nth_token(1)),\n                 BLOCK1_RES(1, 1024, false), PAYLOAD(RESPONSE_PAYLOAD))\n    };\n\n    test_payload_writer_args_t response_payload = {\n        .payload = RESPONSE_PAYLOAD,\n        .payload_size = sizeof(RESPONSE_PAYLOAD) - 1\n    };\n\n    expect_recv(&env, requests[0]);\n    expect_request_handler_call(&env, AVS_COAP_SERVER_REQUEST_PARTIAL_CONTENT,\n                                requests[0], NULL, NULL);\n    expect_send(&env, responses[0]);\n\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(\n            env.coap_ctx, test_accept_new_request, &env));\n\n    expect_recv(&env, requests[1]);\n    expect_request_handler_call(&env, AVS_COAP_SERVER_REQUEST_RECEIVED,\n                                requests[1],\n                                &(avs_coap_response_header_t) {\n                                    .code = responses[1]->response_header.code\n                                },\n                                &response_payload);\n    expect_request_handler_call(&env, AVS_COAP_SERVER_REQUEST_CLEANUP, NULL,\n                                NULL, NULL);\n    expect_send(&env, responses[1]);\n\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n\n#        undef REQUEST_PAYLOAD\n#        undef RESPONSE_PAYLOAD\n}\n\nAVS_UNIT_TEST(udp_async_server, incoming_request_block_response_weird_sizes) {\n    avs_coap_udp_tx_params_t tx_params = AVS_COAP_DEFAULT_UDP_TX_PARAMS;\n    tx_params.nstart = 999;\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup(&tx_params, 4096, 800, NULL);\n\n#        define REQUEST_PAYLOAD DATA_1KB \"?\"\n\n    const test_msg_t *requests[] = {\n        COAP_MSG(CON, GET, ID(0), TOKEN(nth_token(0)),\n                 BLOCK1_REQ(0, 512, REQUEST_PAYLOAD)),\n        COAP_MSG(CON, GET, ID(1), TOKEN(nth_token(1)),\n                 BLOCK1_REQ(2, 256, REQUEST_PAYLOAD)),\n        COAP_MSG(CON, GET, ID(2), TOKEN(nth_token(2)),\n                 BLOCK1_REQ(3, 256, REQUEST_PAYLOAD)),\n        COAP_MSG(CON, GET, ID(3), TOKEN(nth_token(3)),\n                 BLOCK1_REQ(2, 512, REQUEST_PAYLOAD)),\n        COAP_MSG(CON, GET, ID(4), TOKEN(nth_token(4)), BLOCK2_REQ(2, 256)),\n        COAP_MSG(CON, GET, ID(5), TOKEN(nth_token(5)), BLOCK2_REQ(3, 256)),\n        COAP_MSG(CON, GET, ID(6), TOKEN(nth_token(6)), BLOCK2_REQ(2, 512))\n    };\n    const test_msg_t *responses[] = {\n        COAP_MSG(ACK, CONTINUE, ID(0), TOKEN(nth_token(0)),\n                 BLOCK1_RES(0, 512, true)),\n        COAP_MSG(ACK, CONTINUE, ID(1), TOKEN(nth_token(1)),\n                 BLOCK1_RES(2, 256, true)),\n        COAP_MSG(ACK, CONTINUE, ID(2), TOKEN(nth_token(2)),\n                 BLOCK1_RES(3, 256, true)),\n        COAP_MSG(ACK, CONTENT, ID(3), TOKEN(nth_token(3)),\n                 BLOCK1_AND_2_RES(2, 512, 512, REQUEST_PAYLOAD)),\n        COAP_MSG(ACK, CONTENT, ID(4), TOKEN(nth_token(4)),\n                 BLOCK2_RES(2, 256, REQUEST_PAYLOAD)),\n        COAP_MSG(ACK, CONTENT, ID(5), TOKEN(nth_token(5)),\n                 BLOCK2_RES(3, 256, REQUEST_PAYLOAD)),\n        COAP_MSG(ACK, CONTENT, ID(6), TOKEN(nth_token(6)),\n                 BLOCK2_RES(2, 512, REQUEST_PAYLOAD))\n    };\n\n    test_payload_writer_args_t response_payload = {\n        .payload = REQUEST_PAYLOAD,\n        .payload_size = sizeof(REQUEST_PAYLOAD) - 1\n    };\n\n    expect_recv(&env, requests[0]);\n    expect_request_handler_call(&env, AVS_COAP_SERVER_REQUEST_PARTIAL_CONTENT,\n                                requests[0], NULL, NULL);\n    expect_send(&env, responses[0]);\n\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(\n            env.coap_ctx, test_accept_new_request, &env));\n\n    expect_recv(&env, requests[1]);\n    expect_request_handler_call(&env, AVS_COAP_SERVER_REQUEST_PARTIAL_CONTENT,\n                                requests[1], NULL, NULL);\n    expect_send(&env, responses[1]);\n\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(\n            env.coap_ctx, test_accept_new_request, &env));\n\n    expect_recv(&env, requests[2]);\n    expect_request_handler_call(&env, AVS_COAP_SERVER_REQUEST_PARTIAL_CONTENT,\n                                requests[2], NULL, NULL);\n    expect_send(&env, responses[2]);\n\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(\n            env.coap_ctx, test_accept_new_request, &env));\n\n    expect_recv(&env, requests[3]);\n    expect_request_handler_call(&env, AVS_COAP_SERVER_REQUEST_RECEIVED,\n                                requests[3],\n                                &(avs_coap_response_header_t) {\n                                    .code = responses[3]->response_header.code\n                                },\n                                &response_payload);\n    expect_send(&env, responses[3]);\n\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n\n    expect_recv(&env, requests[4]);\n    expect_send(&env, responses[4]);\n\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n\n    expect_recv(&env, requests[5]);\n    expect_send(&env, responses[5]);\n\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n\n    expect_recv(&env, requests[6]);\n    expect_request_handler_call(&env, AVS_COAP_SERVER_REQUEST_CLEANUP, NULL,\n                                NULL, NULL);\n    expect_send(&env, responses[6]);\n\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n\n#        undef REQUEST_PAYLOAD\n}\n\nAVS_UNIT_TEST(udp_async_server, block2_request_from_the_middle) {\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_default();\n\n#        define RESPONSE_PAYLOAD DATA_1KB DATA_1KB DATA_1KB \"!\"\n\n    const test_msg_t *requests[] = {\n        COAP_MSG(CON, GET, ID(0), TOKEN(nth_token(0)), BLOCK2_REQ(2, 1024)),\n        COAP_MSG(CON, GET, ID(1), TOKEN(nth_token(1)), BLOCK2_REQ(3, 1024))\n    };\n    const test_msg_t *responses[] = {\n        COAP_MSG(ACK, CONTENT, ID(0), TOKEN(nth_token(0)),\n                 BLOCK2_RES(2, 1024, RESPONSE_PAYLOAD)),\n        COAP_MSG(ACK, CONTENT, ID(1), TOKEN(nth_token(1)),\n                 BLOCK2_RES(3, 1024, RESPONSE_PAYLOAD))\n    };\n\n    test_payload_writer_args_t response_payload = {\n        .payload = RESPONSE_PAYLOAD,\n        .expected_payload_offset = 2048,\n        .payload_size = sizeof(RESPONSE_PAYLOAD) - 1\n    };\n\n    expect_recv(&env, requests[0]);\n    expect_request_handler_call(&env, AVS_COAP_SERVER_REQUEST_RECEIVED,\n                                requests[0],\n                                &(avs_coap_response_header_t) {\n                                    .code = responses[0]->response_header.code\n                                },\n                                &response_payload);\n    expect_request_handler_call(&env, AVS_COAP_SERVER_REQUEST_CLEANUP, NULL,\n                                NULL, NULL);\n    expect_send(&env, responses[0]);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(\n            env.coap_ctx, test_accept_new_request, &env));\n\n    expect_recv(&env, requests[1]);\n    expect_send(&env, responses[1]);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n#        undef RESPONSE_PAYLOAD\n}\n\nAVS_UNIT_TEST(udp_async_server, block2_request_not_in_order) {\n    test_env_t env __attribute__((cleanup(test_teardown_late_expects_check))) =\n            test_setup_default();\n\n#        define RESPONSE_PAYLOAD DATA_1KB DATA_1KB \"!\"\n\n    /**\n     * avs_coap treats requests with BLOCK2 option where blocks numbers are not\n     * in order as separate requests, not a single exchange. Treating them as\n     * a single exchange would break contract for\n     * @ref avs_coap_payload_writer_t .\n     */\n    const test_msg_t *requests[] = {\n        COAP_MSG(CON, GET, ID(0), TOKEN(nth_token(0)), BLOCK2_REQ(2, 1024)),\n        COAP_MSG(CON, GET, ID(1), TOKEN(nth_token(1)), BLOCK2_REQ(1, 1024))\n    };\n    const test_msg_t *responses[] = {\n        COAP_MSG(ACK, CONTENT, ID(0), TOKEN(nth_token(0)),\n                 BLOCK2_RES(2, 1024, RESPONSE_PAYLOAD)),\n        COAP_MSG(ACK, CONTENT, ID(1), TOKEN(nth_token(1)),\n                 BLOCK2_RES(1, 1024, RESPONSE_PAYLOAD))\n    };\n\n    test_payload_writer_args_t response_payload = {\n        .payload = RESPONSE_PAYLOAD,\n        .expected_payload_offset = 2048,\n        .payload_size = sizeof(RESPONSE_PAYLOAD) - 1\n    };\n\n    expect_recv(&env, requests[0]);\n    expect_request_handler_call(&env, AVS_COAP_SERVER_REQUEST_RECEIVED,\n                                requests[0],\n                                &(avs_coap_response_header_t) {\n                                    .code = responses[0]->response_header.code\n                                },\n                                &response_payload);\n    expect_request_handler_call(&env, AVS_COAP_SERVER_REQUEST_CLEANUP, NULL,\n                                NULL, NULL);\n    expect_send(&env, responses[0]);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(\n            env.coap_ctx, test_accept_new_request, &env));\n\n    response_payload.expected_payload_offset = 1024;\n    expect_recv(&env, requests[1]);\n    expect_request_handler_call(&env, AVS_COAP_SERVER_REQUEST_RECEIVED,\n                                requests[1],\n                                &(avs_coap_response_header_t) {\n                                    .code = responses[1]->response_header.code\n                                },\n                                &response_payload);\n    expect_send(&env, responses[1]);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(\n            env.coap_ctx, test_accept_new_request, &env));\n\n    expect_request_handler_call(&env, AVS_COAP_SERVER_REQUEST_CLEANUP, NULL,\n                                NULL, NULL);\n#        undef RESPONSE_PAYLOAD\n}\n\nAVS_UNIT_TEST(udp_async_server, request_timeout_refresh) {\n#        define REQUEST_PAYLOAD DATA_1KB \"?\"\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_default();\n\n    const test_msg_t *requests[] = {\n        COAP_MSG(CON, GET, ID(0), TOKEN(nth_token(0)),\n                 BLOCK1_REQ(0, 1024, REQUEST_PAYLOAD)),\n        COAP_MSG(CON, GET, ID(1), TOKEN(nth_token(1)),\n                 BLOCK1_REQ(1, 1024, REQUEST_PAYLOAD)),\n    };\n    const test_msg_t *responses[] = {\n        COAP_MSG(ACK, CONTINUE, ID(0), TOKEN(nth_token(0)),\n                 BLOCK1_RES(0, 1024, true)),\n        COAP_MSG(ACK, CONTENT, ID(1), TOKEN(nth_token(1)),\n                 BLOCK1_AND_2_RES(1, 1024, 1024, REQUEST_PAYLOAD)),\n    };\n\n    test_payload_writer_args_t response_payload = {\n        .payload = REQUEST_PAYLOAD,\n        .payload_size = sizeof(REQUEST_PAYLOAD) - 1\n    };\n\n    expect_recv(&env, requests[0]);\n    expect_request_handler_call(&env, AVS_COAP_SERVER_REQUEST_PARTIAL_CONTENT,\n                                requests[0], NULL, NULL);\n    expect_send(&env, responses[0]);\n\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(\n            env.coap_ctx, test_accept_new_request, &env));\n\n    // a timeout job should be scheduled\n    ASSERT_TRUE(avs_time_duration_valid(avs_sched_time_to_next(env.sched)));\n\n    const avs_time_duration_t EPSILON =\n            avs_time_duration_from_scalar(1, AVS_TIME_S);\n\n    _avs_mock_clock_advance(\n            avs_time_duration_diff(avs_sched_time_to_next(env.sched), EPSILON));\n\n    // receiving another request within deadline should refresh the timeout\n    expect_recv(&env, requests[1]);\n    expect_request_handler_call(&env, AVS_COAP_SERVER_REQUEST_RECEIVED,\n                                requests[1],\n                                &(avs_coap_response_header_t) {\n                                    .code = responses[1]->response_header.code\n                                },\n                                &response_payload);\n    expect_send(&env, responses[1]);\n\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n\n    _avs_mock_clock_advance(EPSILON);\n    avs_sched_run(env.sched);\n    ASSERT_TRUE(avs_time_duration_valid(avs_sched_time_to_next(env.sched)));\n\n    // timeout job should still be running\n    expect_request_handler_call(&env, AVS_COAP_SERVER_REQUEST_CLEANUP, NULL,\n                                NULL, NULL);\n\n    _avs_mock_clock_advance(avs_sched_time_to_next(env.sched));\n    avs_sched_run(env.sched);\n#        undef REQUEST_PAYLOAD\n}\n\nAVS_UNIT_TEST(udp_async_server, request_timeout) {\n#        define REQUEST_PAYLOAD DATA_1KB \"?\"\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_default();\n\n    const test_msg_t *request = COAP_MSG(CON, PUT, ID(0), TOKEN(nth_token(0)),\n                                         BLOCK1_REQ(0, 1024, REQUEST_PAYLOAD));\n    const test_msg_t *response =\n            COAP_MSG(ACK, CONTINUE, ID(0), TOKEN(nth_token(0)),\n                     BLOCK1_RES(0, 1024, true));\n\n    expect_recv(&env, request);\n    expect_request_handler_call(&env, AVS_COAP_SERVER_REQUEST_PARTIAL_CONTENT,\n                                request, NULL, NULL);\n    expect_send(&env, response);\n\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(\n            env.coap_ctx, test_accept_new_request, &env));\n\n    // a timeout job should be scheduled\n    ASSERT_TRUE(avs_time_duration_valid(avs_sched_time_to_next(env.sched)));\n\n    // the scheduler should call cancel handler\n    expect_request_handler_call(&env, AVS_COAP_SERVER_REQUEST_CLEANUP, NULL,\n                                NULL, NULL);\n\n    _avs_mock_clock_advance(avs_sched_time_to_next(env.sched));\n    avs_sched_run(env.sched);\n\n    ASSERT_FALSE(avs_time_duration_valid(avs_sched_time_to_next(env.sched)));\n\n#        undef REQUEST_PAYLOAD\n}\n\nAVS_UNIT_TEST(udp_async_server, short_exchange_update_time) {\n#        define REQUEST_PAYLOAD DATA_1KB \"?\"\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_default();\n\n    const test_msg_t *request = COAP_MSG(CON, GET, ID(0), TOKEN(nth_token(0)),\n                                         BLOCK1_REQ(0, 1024, REQUEST_PAYLOAD));\n    const test_msg_t *response =\n            COAP_MSG(ACK, CONTINUE, ID(0), TOKEN(nth_token(0)),\n                     BLOCK1_RES(0, 1024, true));\n\n    // First, assert that the exchange time is initially set to 5 mins\n    ASSERT_TRUE(avs_time_duration_equal(\n            avs_coap_get_exchange_max_time(env.coap_ctx),\n            avs_time_duration_from_scalar(5, AVS_TIME_MIN)));\n\n    // Second, we want to set some really short exchange time ...\n    avs_time_duration_t short_exchange_time =\n            avs_time_duration_from_scalar(5, AVS_TIME_MS);\n    avs_coap_set_exchange_max_time(env.coap_ctx, short_exchange_time);\n\n    // .. check if it is properly set\n    ASSERT_TRUE(avs_time_duration_equal(\n            avs_coap_get_exchange_max_time(env.coap_ctx), short_exchange_time));\n\n    expect_recv(&env, request);\n    expect_request_handler_call(&env, AVS_COAP_SERVER_REQUEST_PARTIAL_CONTENT,\n                                request, NULL, NULL);\n    expect_send(&env, response);\n\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(\n            env.coap_ctx, test_accept_new_request, &env));\n\n    // a timeout job should be scheduled\n    ASSERT_TRUE(avs_time_duration_valid(avs_sched_time_to_next(env.sched)));\n\n    // the scheduler should call cancel handler\n    expect_request_handler_call(&env, AVS_COAP_SERVER_REQUEST_CLEANUP, NULL,\n                                NULL, NULL);\n\n    // despite waiting for only short period of time, with such short max\n    // exchange update time, the cancel handler will be called ..\n    _avs_mock_clock_advance(avs_time_duration_from_scalar(10, AVS_TIME_MS));\n    avs_sched_run(env.sched);\n\n    // .. and we have no more tasks in out scheduler\n    ASSERT_FALSE(avs_time_duration_valid(avs_sched_time_to_next(env.sched)));\n#        undef REQUEST_PAYLOAD\n}\n\nAVS_UNIT_TEST(udp_async_server, long_exchange_update_time) {\n#        define REQUEST_PAYLOAD DATA_1KB \"?\"\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_default();\n\n    const test_msg_t *requests[] = {\n        COAP_MSG(CON, GET, ID(0), TOKEN(nth_token(0)),\n                 BLOCK1_REQ(0, 1024, REQUEST_PAYLOAD)),\n        COAP_MSG(CON, GET, ID(1), TOKEN(nth_token(1)),\n                 BLOCK1_REQ(1, 1024, REQUEST_PAYLOAD)),\n    };\n    const test_msg_t *responses[] = {\n        COAP_MSG(ACK, CONTINUE, ID(0), TOKEN(nth_token(0)),\n                 BLOCK1_RES(0, 1024, true)),\n        COAP_MSG(ACK, CONTENT, ID(1), TOKEN(nth_token(1)),\n                 BLOCK1_AND_2_RES(1, 1024, 1024, REQUEST_PAYLOAD)),\n    };\n\n    test_payload_writer_args_t response_payload = {\n        .payload = REQUEST_PAYLOAD,\n        .payload_size = sizeof(REQUEST_PAYLOAD) - 1\n    };\n\n    expect_recv(&env, requests[0]);\n    expect_request_handler_call(&env, AVS_COAP_SERVER_REQUEST_PARTIAL_CONTENT,\n                                requests[0], NULL, NULL);\n    expect_send(&env, responses[0]);\n    expect_has_buffered_data_check(&env, false);\n\n    // the first part is the same as earlier\n\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(\n            env.coap_ctx, test_accept_new_request, &env));\n\n    // a timeout job should be scheduled\n    ASSERT_TRUE(avs_time_duration_valid(avs_sched_time_to_next(env.sched)));\n\n    // max exchange time is now long, so the exanchange has a lot of time after\n    // this clock advance\n    _avs_mock_clock_advance(avs_time_duration_from_scalar(10, AVS_TIME_MS));\n\n    // and the scheduler runs no cancel handler\n    avs_sched_run(env.sched);\n\n    // so the cancel handler is still scheduled\n    ASSERT_TRUE(avs_time_duration_valid(avs_sched_time_to_next(env.sched)));\n\n    // we get ready for recieving the next request\n    expect_recv(&env, requests[1]);\n    expect_request_handler_call(&env, AVS_COAP_SERVER_REQUEST_RECEIVED,\n                                requests[1],\n                                &(avs_coap_response_header_t) {\n                                    .code = responses[1]->response_header.code\n                                },\n                                &response_payload);\n    expect_send(&env, responses[1]);\n    expect_has_buffered_data_check(&env, false);\n\n    // Then we handle the second (and the last) request\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(\n            env.coap_ctx, test_accept_new_request, &env));\n\n    ASSERT_TRUE(avs_time_duration_valid(avs_sched_time_to_next(env.sched)));\n\n    // timeout job should still be running\n    expect_request_handler_call(&env, AVS_COAP_SERVER_REQUEST_CLEANUP, NULL,\n                                NULL, NULL);\n\n    _avs_mock_clock_advance(avs_sched_time_to_next(env.sched));\n    avs_sched_run(env.sched);\n    _avs_mock_clock_advance(avs_sched_time_to_next(env.sched));\n    avs_sched_run(env.sched);\n\n    ASSERT_FALSE(avs_time_duration_valid(avs_sched_time_to_next(env.sched)));\n#        undef REQUEST_PAYLOAD\n}\n\n#        define INVALID_BLOCK1(Seq, Size, Payload) \\\n            .block1 = {                            \\\n                .type = AVS_COAP_BLOCK1,           \\\n                .seq_num = Seq,                    \\\n                .size = Size,                      \\\n                .has_more = true                   \\\n            },                                     \\\n            .payload = Payload,                    \\\n            .payload_size =                        \\\n                    (assert(sizeof(Payload) - 1 < Size), sizeof(Payload) - 1)\n\nAVS_UNIT_TEST(udp_async_server, invalid_block_opt_in_request) {\n    // BLOCK1.has_more == 1 and BLOCK1.size != payload size\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_default();\n\n    const test_msg_t *request = COAP_MSG(CON, GET, ID(0), TOKEN(nth_token(0)),\n                                         INVALID_BLOCK1(0, 1024, \"test\"));\n    const test_msg_t *response =\n            COAP_MSG(ACK, BAD_OPTION, ID(0), TOKEN(nth_token(0)));\n\n    expect_recv(&env, request);\n    expect_send(&env, response);\n\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(\n            env.coap_ctx, test_accept_new_request, &env));\n}\n\nAVS_UNIT_TEST(udp_async_server, duplicated_block_requests) {\n#        define RESPONSE_PAYLOAD DATA_1KB \"?\"\n    test_env_t env __attribute__((cleanup(test_teardown_late_expects_check))) =\n            test_setup_default();\n\n    const test_msg_t *request = COAP_MSG(CON, GET, ID(0), TOKEN(nth_token(0)));\n    const test_msg_t *response =\n            COAP_MSG(ACK, CONTENT, ID(0), TOKEN(nth_token(0)),\n                     BLOCK2_RES(0, 1024, RESPONSE_PAYLOAD));\n    const test_msg_t *error =\n            COAP_MSG(ACK, INTERNAL_SERVER_ERROR, ID(0), TOKEN(nth_token(0)));\n\n    test_payload_writer_args_t response_payload = {\n        .payload = RESPONSE_PAYLOAD,\n        .payload_size = sizeof(RESPONSE_PAYLOAD) - 1\n    };\n\n    expect_recv(&env, request);\n    expect_request_handler_call(&env, AVS_COAP_SERVER_REQUEST_RECEIVED, request,\n                                &(avs_coap_response_header_t) {\n                                    .code = response->response_header.code\n                                },\n                                &response_payload);\n    expect_send(&env, response);\n\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(\n            env.coap_ctx, test_accept_new_request, &env));\n\n    // assert that the duplicated request will be treated as a new request,\n    // so it will not call the existing request handler\n    expect_recv(&env, request);\n    expect_send(&env, error);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n\n    expect_request_handler_call(&env, AVS_COAP_SERVER_REQUEST_CLEANUP, NULL,\n                                NULL, NULL);\n#        undef RESPONSE_PAYLOAD\n}\n\n#    else // WITH_AVS_COAP_BLOCK\n\nAVS_UNIT_TEST(udp_async_server_no_block, block1_request) {\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_default();\n\n    // Equivalent to\n    // COAP_MSG(CON, GET, ID(0), TOKEN(nth_token(0)),\n    //          BLOCK1_REQ(0, 1024, \"test\"))\n    // but we're unable to easily construct such message if BLOCK support is\n    // disabled.\n    const uint8_t request[] = { 0x48, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,\n                                0x00, 0x00, 0x00, 0x00, 0x00, 0xD1, 0x0E,\n                                0x06, 0xFF, 0x74, 0x65, 0x73, 0x74 };\n    const test_msg_t *response =\n            COAP_MSG(ACK, BAD_OPTION, ID(0), TOKEN(nth_token(0)));\n\n    avs_unit_mocksock_input(env.mocksock, request, sizeof(request));\n    expect_send(&env, response);\n\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n}\n\n#    endif // WITH_AVS_COAP_BLOCK\n\n#endif // defined(AVS_UNIT_TESTING) && defined(WITH_AVS_COAP_UDP)\n"
  },
  {
    "path": "deps/avs_coap/tests/udp/big_data.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef AVS_COAP_SRC_UDP_TEST_BIG_DATA_H\n#define AVS_COAP_SRC_UDP_TEST_BIG_DATA_H\n\n#include \"./utils.h\"\n\n#define DATA_4KB DATA_1KB DATA_1KB DATA_1KB DATA_1KB\n#define DATA_16KB DATA_4KB DATA_4KB DATA_4KB DATA_4KB\n#define DATA_64KB DATA_16KB DATA_16KB DATA_16KB DATA_16KB\n#define DATA_256KB DATA_64KB DATA_64KB DATA_64KB DATA_64KB\n#define DATA_1MB DATA_256KB DATA_256KB DATA_256KB DATA_256KB\n#define DATA_4MB DATA_1MB DATA_1MB DATA_1MB DATA_1MB\n#define DATA_16MB DATA_4MB DATA_4MB DATA_4MB DATA_4MB\n\n#endif // AVS_COAP_SRC_UDP_TEST_BIG_DATA_H\n"
  },
  {
    "path": "deps/avs_coap/tests/udp/fuzzer_cases.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <avs_coap_init.h>\n\n#if defined(AVS_UNIT_TESTING) && defined(WITH_AVS_COAP_UDP)\n\n#    include <avsystem/commons/avs_errno.h>\n\n#    include <avsystem/coap/coap.h>\n\n#    define MODULE_NAME test\n#    include <avs_coap_x_log_config.h>\n\n#    include \"./big_data.h\"\n#    include \"./utils.h\"\n\ntypedef struct {\n    test_env_t *env;\n    const test_msg_t *msg;\n    avs_coap_exchange_id_t *exchange_id;\n} test_env_with_msg_t;\n\nstatic void\nmsg_sending_response_handler(avs_coap_ctx_t *ctx,\n                             avs_coap_exchange_id_t exchange_id,\n                             avs_coap_client_request_state_t result,\n                             const avs_coap_client_async_response_t *response,\n                             avs_error_t err,\n                             void *arg_) {\n    (void) exchange_id;\n    (void) result;\n    (void) response;\n    (void) err;\n\n    test_env_with_msg_t *arg = (test_env_with_msg_t *) arg_;\n\n    ASSERT_OK(avs_coap_client_send_async_request(\n            ctx, arg->exchange_id, &arg->msg->request_header, NULL, NULL,\n            test_response_handler, &arg->env->expects_list));\n}\n\nAVS_UNIT_TEST(udp_fuzzer, send_in_response_handler_while_message_is_held) {\n    // - NSTART = 1\n    // - CON message 1 is sent\n    // - CON message 2 is sent\n    // - Response to message is received, but has malformed options\n    // - Message 1 is removed from ctx->unconfirmed_messages to disallow\n    //   cancelling it from user-defined handler while we are operating on it\n    // - User-defined handler for message 1 is called with \"fail\" state\n    // - Response handler sends CON message 3. At this point,\n    //   ctx->unconfirmed_messages contains just one entry - message 2 - which\n    //   is held until handling of another message finishes to not exceed\n    //   NSTART. enqueue_unconfirmed is called, finds out that current_nstart ==\n    //   0, so message 3 is sent immediately and marked as \"not held\".\n    // - Program exits user-defined handler\n    // - UDP context figures out that handling a message was done, so next held\n    //   message (2) can be resumed without violating NSTART\n    // - We end up with 2 \"not held\" messages, but NSTART = 1, so an assertion\n    //   fails.\n    //\n    // Case fixed by https://phabricator.avsystem.com/D9067\n    test_env_t env __attribute__((cleanup(test_teardown_late_expects_check))) =\n            test_setup_with_nstart(1);\n\n    const test_msg_t *requests[] = {\n        COAP_MSG(CON, GET, ID(0), TOKEN(nth_token(0))),\n        COAP_MSG(CON, GET, ID(1), TOKEN(nth_token(1))),\n        COAP_MSG(CON, GET, ID(2), TOKEN(nth_token(2)))\n    };\n    const test_msg_t *response =\n            COAP_MSG(ACK, CONTENT, ID(0), TOKEN(nth_token(0)));\n\n    const size_t malformed_response_size = response->size + 1;\n    uint8_t *malformed_response = (uint8_t *) malloc(malformed_response_size);\n    memcpy(malformed_response, response->data, response->size);\n    // invalid option value: 1b of option data is expected, but there is none\n    malformed_response[response->size] = 0x01;\n\n    avs_coap_exchange_id_t ids[3];\n\n    // a request should be sent\n    ASSERT_OK(avs_coap_client_send_async_request(\n            env.coap_ctx, &ids[0], &requests[0]->request_header, NULL, NULL,\n            msg_sending_response_handler,\n            &(test_env_with_msg_t) {\n                .env = &env,\n                .msg = requests[2],\n                .exchange_id = &ids[2]\n            }));\n\n    // second one should be held due to NSTART = 1\n    ASSERT_OK(avs_coap_client_send_async_request(\n            env.coap_ctx, &ids[1], &requests[1]->request_header, NULL, NULL,\n            test_response_handler, &env.expects_list));\n\n    expect_send(&env, requests[0]);\n    avs_sched_run(env.sched);\n\n    // Receiving response should make the context call handler,\n    // which attempts to send requests[2]. That message is supposed to be held\n    // until we receive response to requests[1] instead.\n    avs_unit_mocksock_input(env.mocksock, malformed_response,\n                            malformed_response_size);\n    expect_has_buffered_data_check(&env, false);\n\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n\n    expect_send(&env, requests[1]);\n    avs_sched_run(env.sched);\n\n    // It doesn't matter much, but why are these cleaned up in reverse order?\n    expect_handler_call(&env, &ids[2], AVS_COAP_CLIENT_REQUEST_CANCEL, NULL);\n    expect_handler_call(&env, &ids[1], AVS_COAP_CLIENT_REQUEST_CANCEL, NULL);\n\n    free(malformed_response);\n}\n\n#    ifdef WITH_AVS_COAP_BLOCK\nAVS_UNIT_TEST(udp_fuzzer, udp_bert_request) {\n    static const char REQUEST_PAYLOAD[] = DATA_1KB \"?\";\n\n    test_env_t env __attribute__((cleanup(test_teardown_late_expects_check))) =\n            test_setup_with_nstart(1);\n\n    const test_msg_t *requests[] = {\n        COAP_MSG(CON, GET, ID(0), TOKEN(nth_token(0)),\n                 BERT1_REQ(0, 1024, REQUEST_PAYLOAD))\n    };\n\n    avs_coap_exchange_id_t id;\n    ASSERT_OK(avs_coap_client_send_async_request(\n            env.coap_ctx, &id, &requests[0]->request_header, NULL, NULL,\n            test_response_handler, &env.expects_list));\n    ASSERT_TRUE(avs_coap_exchange_id_valid(id));\n\n    expect_handler_call(&env, &id, AVS_COAP_CLIENT_REQUEST_FAIL, NULL);\n    avs_sched_run(env.sched);\n}\n\nAVS_UNIT_TEST(udp_fuzzer, nonconfirmable_broken_block_size_recalculation) {\n    static const char REQUEST_PAYLOAD[] = DATA_16KB;\n\n    test_env_t env __attribute__((cleanup(test_teardown_late_expects_check))) =\n            test_setup(&AVS_COAP_DEFAULT_UDP_TX_PARAMS, 4096, 32, NULL);\n\n    const test_msg_t *requests[] = {\n        COAP_MSG(NON, PUT, ID(0), TOKEN(nth_token(0)),\n                 BLOCK1_REQ(13, 1024, REQUEST_PAYLOAD))\n    };\n\n    ASSERT_FAIL(avs_coap_client_send_async_request(\n            env.coap_ctx, NULL, &requests[0]->request_header,\n            test_payload_writer,\n            &(test_payload_writer_args_t) {\n                .payload = REQUEST_PAYLOAD,\n                .payload_size = sizeof(REQUEST_PAYLOAD) - 1,\n                .expected_payload_offset = 13 * 1024\n            },\n            NULL, NULL));\n}\n#    endif // WITH_AVS_COAP_BLOCK\n\ntypedef struct {\n    test_env_t *env;\n    avs_coap_exchange_id_t *out_id;\n} call_avs_sched_run_handler_args_t;\n\nstatic void\ncall_avs_sched_run_handler(avs_coap_ctx_t *ctx,\n                           avs_coap_exchange_id_t exchange_id,\n                           avs_coap_client_request_state_t result,\n                           const avs_coap_client_async_response_t *response,\n                           avs_error_t err,\n                           void *args_) {\n    (void) exchange_id;\n    ASSERT_NOT_NULL(ctx);\n    ASSERT_EQ(result, AVS_COAP_CLIENT_REQUEST_FAIL);\n    ASSERT_NULL(response);\n    ASSERT_EQ(err.category, AVS_ERRNO_CATEGORY);\n    ASSERT_EQ(err.code, AVS_ECONNREFUSED);\n\n    call_avs_sched_run_handler_args_t *args =\n            (call_avs_sched_run_handler_args_t *) args_;\n    ASSERT_OK(avs_coap_client_send_async_request(\n            ctx, args->out_id,\n            &(const avs_coap_request_header_t) {\n                .code = AVS_COAP_CODE_GET\n            },\n            NULL, NULL, test_response_handler, &args->env->expects_list));\n    avs_sched_run(args->env->sched);\n}\n\nAVS_UNIT_TEST(udp_fuzzer, recursive_sched_run_nstart) {\n    test_env_t env __attribute__((cleanup(test_teardown_late_expects_check))) =\n            test_setup_with_nstart(1);\n\n    const test_msg_t *requests[] = {\n        COAP_MSG(CON, GET, ID(0), TOKEN(nth_token(0))),\n        COAP_MSG(CON, GET, ID(1), TOKEN(nth_token(1))),\n        COAP_MSG(CON, GET, ID(2), TOKEN(nth_token(2))),\n        COAP_MSG(CON, GET, ID(3), TOKEN(nth_token(3)))\n    };\n    avs_coap_exchange_id_t ids[4];\n    ASSERT_OK(avs_coap_client_send_async_request(\n            env.coap_ctx, &ids[0], &requests[0]->request_header, NULL, NULL,\n            test_response_handler, &env.expects_list));\n    ASSERT_OK(avs_coap_client_send_async_request(\n            env.coap_ctx, &ids[1],\n            &(const avs_coap_request_header_t) {\n                .code = AVS_COAP_CODE_GET\n            },\n            NULL, NULL, call_avs_sched_run_handler,\n            &(call_avs_sched_run_handler_args_t) {\n                .env = &env,\n                .out_id = &ids[3]\n            }));\n    ASSERT_OK(avs_coap_client_send_async_request(\n            env.coap_ctx, &ids[2],\n            &(const avs_coap_request_header_t) {\n                .code = AVS_COAP_CODE_GET\n            },\n            NULL, NULL, test_response_handler, &env.expects_list));\n\n    expect_send(&env, requests[0]);\n    avs_sched_run(env.sched);\n\n    expect_handler_call(&env, &ids[0], AVS_COAP_CLIENT_REQUEST_CANCEL, NULL);\n    avs_coap_exchange_cancel(env.coap_ctx, ids[0]);\n\n    // Now, id[1] will attempt to be sent. Let's fail the send operation.\n    // This will cause call_avs_sched_run_handler() to be called.\n    avs_unit_mocksock_output_fail(env.mocksock, avs_errno(AVS_ECONNREFUSED));\n    // id[2] will then be sent normally\n    expect_send(&env, requests[2]);\n    avs_sched_run(env.sched);\n\n    expect_handler_call(&env, &ids[3], AVS_COAP_CLIENT_REQUEST_CANCEL, NULL);\n    expect_handler_call(&env, &ids[2], AVS_COAP_CLIENT_REQUEST_CANCEL, NULL);\n}\n\n#    ifdef WITH_AVS_COAP_BLOCK\nAVS_UNIT_TEST(udp_fuzzer, cancel_nonconfirmable_in_payload_writer) {\n#        define CONTENT DATA_1KB DATA_1KB\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_default();\n\n    const test_msg_t *requests[] = { COAP_MSG(NON, GET, ID(0),\n                                              TOKEN(nth_token(0)),\n                                              BLOCK1_REQ(0, 1024, CONTENT)) };\n\n    ASSERT_FAIL(avs_coap_client_send_async_request(\n            env.coap_ctx, NULL, &requests[0]->request_header,\n            test_payload_writer,\n            &(test_payload_writer_args_t) {\n                .payload = CONTENT,\n                .payload_size = sizeof(CONTENT) - 1,\n                .coap_ctx = env.coap_ctx,\n                // Exchange IDs of non-confirmable requests are not exposed\n                // publicly, but the user may pass a \"random\" value that happens\n                // to match. Let's not segfault in that case.\n                .exchange_id = { 1 },\n                .cancel_exchange = true\n            },\n            NULL, NULL));\n#        undef CONTENT\n}\n#    endif // WITH_AVS_COAP_BLOCK\n\ntypedef struct {\n    avs_coap_ctx_t *coap_ctx;\n    avs_coap_exchange_id_t *exchange_ids;\n    size_t exchange_id_count;\n} cancel_exchanges_payload_writer_args_t;\n\nstatic int cancel_exchanges_payload_writer(size_t payload_offset,\n                                           void *payload_buf,\n                                           size_t payload_buf_size,\n                                           size_t *out_payload_chunk_size,\n                                           void *args_) {\n    (void) payload_offset;\n    (void) payload_buf;\n    (void) payload_buf_size;\n    *out_payload_chunk_size = 0;\n    cancel_exchanges_payload_writer_args_t *args =\n            (cancel_exchanges_payload_writer_args_t *) args_;\n    for (size_t i = 0; i < args->exchange_id_count; ++i) {\n        avs_coap_exchange_cancel(args->coap_ctx, args->exchange_ids[i]);\n    }\n    return 0;\n}\n\nAVS_UNIT_TEST(udp_fuzzer, complicated_deferred_send_iteration) {\n    test_env_t env __attribute__((cleanup(test_teardown_late_expects_check))) =\n            test_setup_default();\n\n    const test_msg_t *requests[] = {\n        COAP_MSG(CON, GET, ID(0), TOKEN(nth_token(0))),\n        COAP_MSG(CON, GET, ID(1), TOKEN(nth_token(1))),\n        COAP_MSG(CON, GET, ID(2), TOKEN(nth_token(2))),\n        COAP_MSG(CON, GET, ID(2), TOKEN(nth_token(3)))\n    };\n\n    avs_coap_exchange_id_t ids[4];\n    ASSERT_OK(avs_coap_client_send_async_request(\n            env.coap_ctx, &ids[0], &requests[0]->request_header, NULL, NULL,\n            test_response_handler, &env.expects_list));\n    ASSERT_OK(avs_coap_client_send_async_request(\n            env.coap_ctx, &ids[1], &requests[1]->request_header, NULL, NULL,\n            test_response_handler, &env.expects_list));\n    ASSERT_OK(avs_coap_client_send_async_request(\n            env.coap_ctx, &ids[2], &requests[2]->request_header,\n            cancel_exchanges_payload_writer,\n            &(cancel_exchanges_payload_writer_args_t) {\n                .coap_ctx = env.coap_ctx,\n                .exchange_ids = &ids[1],\n                .exchange_id_count = 2\n            },\n            test_response_handler, &env.expects_list));\n    ASSERT_OK(avs_coap_client_send_async_request(\n            env.coap_ctx, &ids[3], &requests[3]->request_header, NULL, NULL,\n            test_response_handler, &env.expects_list));\n\n    expect_send(&env, requests[0]);\n    expect_send(&env, requests[1]);\n    expect_handler_call(&env, &ids[1], AVS_COAP_CLIENT_REQUEST_CANCEL, NULL);\n    expect_handler_call(&env, &ids[2], AVS_COAP_CLIENT_REQUEST_CANCEL, NULL);\n    expect_send(&env, requests[3]);\n    avs_sched_run(env.sched);\n\n    expect_handler_call(&env, &ids[0], AVS_COAP_CLIENT_REQUEST_CANCEL, NULL);\n    expect_handler_call(&env, &ids[3], AVS_COAP_CLIENT_REQUEST_CANCEL, NULL);\n}\n\nAVS_UNIT_TEST(udp_fuzzer, complicated_deferred_send_iteration_2) {\n    test_env_t env __attribute__((cleanup(test_teardown_late_expects_check))) =\n            test_setup_default();\n\n    const test_msg_t *requests[] = {\n        COAP_MSG(CON, GET, ID(0), TOKEN(nth_token(0))),\n        COAP_MSG(CON, GET, ID(1), TOKEN(nth_token(1)))\n    };\n\n    avs_coap_exchange_id_t ids[4];\n    ASSERT_OK(avs_coap_client_send_async_request(\n            env.coap_ctx, &ids[0], &requests[0]->request_header, NULL, NULL,\n            test_response_handler, &env.expects_list));\n    ASSERT_OK(avs_coap_client_send_async_request(\n            env.coap_ctx, &ids[1], &requests[1]->request_header,\n            cancel_exchanges_payload_writer,\n            &(cancel_exchanges_payload_writer_args_t) {\n                .coap_ctx = env.coap_ctx,\n                .exchange_ids = &ids[0],\n                .exchange_id_count = 1\n            },\n            test_response_handler, &env.expects_list));\n\n    expect_send(&env, requests[0]);\n    expect_send(&env, requests[1]);\n    expect_handler_call(&env, &ids[0], AVS_COAP_CLIENT_REQUEST_CANCEL, NULL);\n    avs_sched_run(env.sched);\n\n    expect_handler_call(&env, &ids[1], AVS_COAP_CLIENT_REQUEST_CANCEL, NULL);\n}\n\n#endif // defined(AVS_UNIT_TESTING) && defined(WITH_AVS_COAP_UDP)\n"
  },
  {
    "path": "deps/avs_coap/tests/udp/msg.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <avs_coap_init.h>\n\n#if defined(AVS_UNIT_TESTING) && defined(WITH_AVS_COAP_UDP)\n\n#    include <avsystem/commons/avs_memory.h>\n\n#    define AVS_UNIT_ENABLE_SHORT_ASSERTS\n#    include <avsystem/commons/avs_unit_test.h>\n\n#    include <avsystem/coap/code.h>\n\n#    include \"options/avs_coap_option.h\"\n#    include \"udp/avs_coap_udp_msg.h\"\n\n#    define MODULE_NAME test\n#    include <avs_coap_x_log_config.h>\n\n#    include \"./utils.h\"\n\nstatic void free_ptr(void **p) {\n    avs_free(*p);\n}\n\nAVS_UNIT_TEST(coap_udp_serialize, header) {\n    static const size_t buf_size = sizeof(avs_coap_udp_header_t);\n    void *buf __attribute__((cleanup(free_ptr))) = avs_calloc(1, buf_size);\n\n    avs_coap_udp_msg_t msg = {\n        .header = _avs_coap_udp_header_init(AVS_COAP_UDP_TYPE_ACKNOWLEDGEMENT,\n                                            /* token length = */ 0,\n                                            AVS_COAP_CODE(3, 4),\n                                            /* msg_id = */ 0x0506)\n    };\n\n    size_t written;\n    ASSERT_OK(_avs_coap_udp_msg_serialize(&msg, buf, buf_size, &written));\n    ASSERT_EQ(buf_size, written);\n\n    //      version\n    //      |  type\n    //      |  |  token length\n    //      v  v  v     .- code .  .-- message id --.\n    //      01 10 0000  011 00100  00000101  00000110\n    // hex:     6    0      6   4     0   5     0   6\n    ASSERT_EQ_BYTES(buf, \"\\x60\\x64\\x05\\x06\");\n}\n\nAVS_UNIT_TEST(coap_udp_serialize, header_and_token) {\n#    define TOKEN_BYTES \"\\x01\\x02\\x03\\x04\\x05\\x06\\x07\"\n    static const avs_coap_token_t token = {\n        .size = 7,\n        .bytes = TOKEN_BYTES\n    };\n    static const size_t buf_size =\n            sizeof(avs_coap_udp_header_t) + sizeof(TOKEN_BYTES) - 1;\n    void *buf __attribute__((cleanup(free_ptr))) = avs_calloc(1, buf_size);\n\n    avs_coap_udp_msg_t msg = {\n        .header = _avs_coap_udp_header_init(AVS_COAP_UDP_TYPE_RESET, token.size,\n                                            AVS_COAP_CODE(7, 31),\n                                            /* msg_id = */ 0xffff),\n        .token = token\n    };\n\n    size_t written;\n    ASSERT_OK(_avs_coap_udp_msg_serialize(&msg, buf, buf_size, &written));\n    ASSERT_EQ(buf_size, written);\n\n    //      version\n    //      |  type\n    //      |  |  token length\n    //      v  v  v     .- code .  .-- message id --.\n    //      01 11 0111  111 11111  11111111  11111111\n    // hex:     7    7      f   f     f   f     f   f\n    ASSERT_EQ_BYTES(buf, \"\\x77\\xff\\xff\\xff\" TOKEN_BYTES);\n#    undef TOKEN_BYTES\n}\n\nAVS_UNIT_TEST(coap_udp_serialize, header_and_payload) {\n#    define CONTENT \"http://www.staggeringbeauty.com/\"\n    uint8_t content[] = CONTENT;\n\n    const size_t buf_size =\n            (sizeof(avs_coap_udp_header_t) + sizeof(AVS_COAP_PAYLOAD_MARKER)\n             + sizeof(content) - 1);\n    void *buf __attribute__((cleanup(free_ptr))) = avs_calloc(1, buf_size);\n\n    const avs_coap_udp_msg_t msg = {\n        .header = _avs_coap_udp_header_init(AVS_COAP_UDP_TYPE_ACKNOWLEDGEMENT,\n                                            /* token length = */ 0,\n                                            AVS_COAP_CODE(3, 4),\n                                            /* msg_id = */ 0x0506),\n        .payload = content,\n        .payload_size = sizeof(content) - 1\n    };\n\n    size_t written;\n    ASSERT_OK(_avs_coap_udp_msg_serialize(&msg, buf, buf_size, &written));\n\n    ASSERT_EQ(written, buf_size);\n    ASSERT_EQ_BYTES(buf, \"\\x60\\x64\\x05\\x06\"\n                         \"\\xff\" CONTENT);\n#    undef CONTENT\n}\n\nAVS_UNIT_TEST(coap_msg_serialize, buffer_too_small_for_header) {\n    const size_t buf_size = sizeof(avs_coap_udp_header_t) - 1;\n    void *buf __attribute__((cleanup(free_ptr))) = avs_calloc(1, buf_size);\n\n    const avs_coap_udp_msg_t msg = {\n        .header = _avs_coap_udp_header_init(AVS_COAP_UDP_TYPE_ACKNOWLEDGEMENT,\n                                            /* token length = */ 0,\n                                            AVS_COAP_CODE(3, 4),\n                                            /* msg_id = */ 0x0506)\n    };\n\n    size_t written;\n    ASSERT_FAIL(_avs_coap_udp_msg_serialize(&msg, buf, buf_size, &written));\n}\n\nAVS_UNIT_TEST(coap_msg_serialize, buffer_too_small_for_token) {\n#    define TOKEN_BYTES \"\\x01\\x02\\x03\\x04\\x05\\x06\\x07\"\n    static const avs_coap_token_t token = {\n        .size = 7,\n        .bytes = TOKEN_BYTES\n    };\n    static const size_t buf_size =\n            sizeof(avs_coap_udp_header_t) + sizeof(TOKEN_BYTES) - 2;\n    void *buf __attribute__((cleanup(free_ptr))) = avs_calloc(1, buf_size);\n\n    const avs_coap_udp_msg_t msg = {\n        .header = _avs_coap_udp_header_init(AVS_COAP_UDP_TYPE_ACKNOWLEDGEMENT,\n                                            /* token length = */ token.size,\n                                            AVS_COAP_CODE(3, 4),\n                                            /* msg_id = */ 0x0506),\n        .token = token\n    };\n\n    size_t written;\n    ASSERT_FAIL(_avs_coap_udp_msg_serialize(&msg, buf, buf_size, &written));\n#    undef TOKEN_BYTES\n}\n\nAVS_UNIT_TEST(coap_msg_serialize, buffer_too_small_for_options) {\n    static const size_t buf_size = sizeof(avs_coap_udp_header_t);\n    void *buf __attribute__((cleanup(free_ptr))) = avs_calloc(1, buf_size);\n\n    uint8_t opts_buf[128];\n    avs_coap_options_t opts =\n            avs_coap_options_create_empty(opts_buf, sizeof(opts_buf));\n    ASSERT_OK(avs_coap_options_add_empty(&opts, 0));\n\n    const avs_coap_udp_msg_t msg = {\n        .header = _avs_coap_udp_header_init(AVS_COAP_UDP_TYPE_ACKNOWLEDGEMENT,\n                                            /* token length = */ 0,\n                                            AVS_COAP_CODE(3, 4),\n                                            /* msg_id = */ 0x0506),\n        .options = opts\n    };\n\n    size_t written;\n    ASSERT_FAIL(_avs_coap_udp_msg_serialize(&msg, buf, buf_size, &written));\n}\n\nAVS_UNIT_TEST(coap_msg_serialize, buffer_too_small_for_payload_marker) {\n    static const size_t buf_size = sizeof(avs_coap_udp_header_t);\n    void *buf __attribute__((cleanup(free_ptr))) = avs_calloc(1, buf_size);\n\n    const avs_coap_udp_msg_t msg = {\n        .header = _avs_coap_udp_header_init(AVS_COAP_UDP_TYPE_ACKNOWLEDGEMENT,\n                                            /* token length = */ 0,\n                                            AVS_COAP_CODE(3, 4),\n                                            /* msg_id = */ 0x0506),\n        .payload = \"such pay, very load\",\n        .payload_size = sizeof(\"such pay, very load\")\n    };\n\n    size_t written;\n    ASSERT_FAIL(_avs_coap_udp_msg_serialize(&msg, buf, buf_size, &written));\n}\n\nAVS_UNIT_TEST(coap_msg_serialize, buffer_too_small_for_payload_content) {\n    static const size_t buf_size =\n            sizeof(avs_coap_udp_header_t) + sizeof(AVS_COAP_PAYLOAD_MARKER);\n    void *buf __attribute__((cleanup(free_ptr))) = avs_calloc(1, buf_size);\n\n    const avs_coap_udp_msg_t msg = {\n        .header = _avs_coap_udp_header_init(AVS_COAP_UDP_TYPE_ACKNOWLEDGEMENT,\n                                            /* token length = */ 0,\n                                            AVS_COAP_CODE(3, 4),\n                                            /* msg_id = */ 0x0506),\n        .payload = \"such pay, very load\",\n        .payload_size = sizeof(\"such pay, very load\")\n    };\n\n    size_t written;\n    ASSERT_FAIL(_avs_coap_udp_msg_serialize(&msg, buf, buf_size, &written));\n}\n\nstatic inline void assert_header_eq(const avs_coap_udp_header_t *a,\n                                    const avs_coap_udp_header_t *b) {\n    ASSERT_EQ(_avs_coap_udp_header_get_version(a),\n              _avs_coap_udp_header_get_version(b));\n    ASSERT_EQ(_avs_coap_udp_header_get_type(a),\n              _avs_coap_udp_header_get_type(b));\n    ASSERT_EQ(_avs_coap_udp_header_get_token_length(a),\n              _avs_coap_udp_header_get_token_length(b));\n    ASSERT_EQ(_avs_coap_udp_header_get_id(a), _avs_coap_udp_header_get_id(b));\n}\n\nAVS_UNIT_TEST(coap_udp_parse, header_valid) {\n    const uint8_t MSG[] = \"\\x60\\x64\\x05\\x06\";\n\n    avs_coap_udp_msg_t msg;\n    ASSERT_OK(_avs_coap_udp_msg_parse(&msg, MSG, sizeof(MSG) - 1));\n\n    const avs_coap_udp_header_t expected_hdr =\n            _avs_coap_udp_header_init(AVS_COAP_UDP_TYPE_ACKNOWLEDGEMENT,\n                                      /* token length = */ 0,\n                                      AVS_COAP_CODE(3, 4),\n                                      /* msg_id = */ 0x0506);\n\n    assert_header_eq(&msg.header, &expected_hdr);\n    ASSERT_EQ(msg.token.size, 0);\n    ASSERT_EQ(msg.options.size, 0);\n    ASSERT_EQ(msg.payload_size, 0);\n}\n\nAVS_UNIT_TEST(coap_udp_parse, header_invalid_version) {\n    avs_coap_udp_msg_t msg;\n\n    const uint8_t MSG_V0[] = \"\\x20\\x64\\x05\\x06\";\n    ASSERT_FAIL(_avs_coap_udp_msg_parse(&msg, MSG_V0, sizeof(MSG_V0) - 1));\n\n    const uint8_t MSG_V2[] = \"\\xa0\\x64\\x05\\x06\";\n    ASSERT_FAIL(_avs_coap_udp_msg_parse(&msg, MSG_V2, sizeof(MSG_V2) - 1));\n\n    const uint8_t MSG_V3[] = \"\\xc0\\x64\\x05\\x06\";\n    ASSERT_FAIL(_avs_coap_udp_msg_parse(&msg, MSG_V3, sizeof(MSG_V3) - 1));\n}\n\nAVS_UNIT_TEST(coap_udp_parse, header_invalid_token_length) {\n    avs_coap_udp_msg_t msg;\n\n    const uint8_t MSG_9B_TOKEN[32] = \"\\x69\\x64\\x05\\x06\";\n    ASSERT_FAIL(_avs_coap_udp_msg_parse(&msg, MSG_9B_TOKEN,\n                                        sizeof(avs_coap_udp_header_t) + 9));\n\n    const uint8_t MSG_10B_TOKEN[32] = \"\\x6a\\x64\\x05\\x06\";\n    ASSERT_FAIL(_avs_coap_udp_msg_parse(&msg, MSG_10B_TOKEN,\n                                        sizeof(avs_coap_udp_header_t) + 10));\n\n    const uint8_t MSG_11B_TOKEN[32] = \"\\x6b\\x64\\x05\\x06\";\n    ASSERT_FAIL(_avs_coap_udp_msg_parse(&msg, MSG_11B_TOKEN,\n                                        sizeof(avs_coap_udp_header_t) + 11));\n\n    const uint8_t MSG_12B_TOKEN[32] = \"\\x6c\\x64\\x05\\x06\";\n    ASSERT_FAIL(_avs_coap_udp_msg_parse(&msg, MSG_12B_TOKEN,\n                                        sizeof(avs_coap_udp_header_t) + 12));\n\n    const uint8_t MSG_13B_TOKEN[32] = \"\\x6d\\x64\\x05\\x06\";\n    ASSERT_FAIL(_avs_coap_udp_msg_parse(&msg, MSG_13B_TOKEN,\n                                        sizeof(avs_coap_udp_header_t) + 13));\n\n    const uint8_t MSG_14B_TOKEN[32] = \"\\x6e\\x64\\x05\\x06\";\n    ASSERT_FAIL(_avs_coap_udp_msg_parse(&msg, MSG_14B_TOKEN,\n                                        sizeof(avs_coap_udp_header_t) + 14));\n\n    const uint8_t MSG_15B_TOKEN[32] = \"\\x6f\\x64\\x05\\x06\";\n    ASSERT_FAIL(_avs_coap_udp_msg_parse(&msg, MSG_15B_TOKEN,\n                                        sizeof(avs_coap_udp_header_t) + 15));\n}\n\nAVS_UNIT_TEST(coap_msg_parse, request_code_on_ack) {\n    const test_msg_t *test = COAP_MSG(ACK, GET, NO_PAYLOAD);\n\n    avs_coap_udp_msg_t msg;\n    ASSERT_FAIL(_avs_coap_udp_msg_parse(&msg, test->data, test->size));\n}\n\nAVS_UNIT_TEST(coap_msg_parse, reset_non_empty) {\n    const test_msg_t *test = COAP_MSG(RST, GET, NO_PAYLOAD);\n\n    avs_coap_udp_msg_t msg;\n    ASSERT_FAIL(_avs_coap_udp_msg_parse(&msg, test->data, test->size));\n}\n\nAVS_UNIT_TEST(coap_msg_parse, reset_empty) {\n    const test_msg_t *test = COAP_MSG(RST, EMPTY, NO_PAYLOAD);\n\n    avs_coap_udp_msg_t msg;\n    ASSERT_OK(_avs_coap_udp_msg_parse(&msg, test->data, test->size));\n\n    const avs_coap_udp_header_t expected_hdr =\n            _avs_coap_udp_header_init(AVS_COAP_UDP_TYPE_RESET,\n                                      /* token length = */ 0,\n                                      AVS_COAP_CODE_EMPTY,\n                                      /* msg_id = */ 0);\n    assert_header_eq(&msg.header, &expected_hdr);\n}\n\nAVS_UNIT_TEST(coap_msg_parse, empty) {\n    const test_msg_t *test = COAP_MSG(CON, EMPTY, NO_PAYLOAD);\n\n    avs_coap_udp_msg_t msg;\n    ASSERT_OK(_avs_coap_udp_msg_parse(&msg, test->data, test->size));\n}\n\nAVS_UNIT_TEST(coap_msg_parse, empty_with_token) {\n    const test_msg_t *test = COAP_MSG(CON, EMPTY, TOKEN(MAKE_TOKEN(\"A token\")));\n\n    avs_coap_udp_msg_t msg;\n    ASSERT_FAIL(_avs_coap_udp_msg_parse(&msg, test->data, test->size));\n}\n\nAVS_UNIT_TEST(coap_msg_parse, empty_with_options) {\n    const test_msg_t *test = COAP_MSG(CON, EMPTY, CONTENT_FORMAT_VALUE(1));\n\n    avs_coap_udp_msg_t msg;\n    ASSERT_FAIL(_avs_coap_udp_msg_parse(&msg, test->data, test->size));\n}\n\nAVS_UNIT_TEST(coap_msg_parse, empty_with_payload) {\n    const test_msg_t *test = COAP_MSG(CON, EMPTY, PAYLOAD(\"http://doger.io\"));\n\n    avs_coap_udp_msg_t msg;\n    ASSERT_FAIL(_avs_coap_udp_msg_parse(&msg, test->data, test->size));\n}\n\nAVS_UNIT_TEST(coap_msg_parse, token) {\n    const test_msg_t *test = COAP_MSG(CON, GET, TOKEN(MAKE_TOKEN(\"A token\")));\n\n    avs_coap_udp_msg_t msg;\n    ASSERT_OK(_avs_coap_udp_msg_parse(&msg, test->data, test->size));\n\n    ASSERT_EQ(msg.token.size, sizeof(\"A token\") - 1);\n    ASSERT_EQ_BYTES(msg.token.bytes, \"A token\");\n\n    ASSERT_EQ(msg.options.size, 0);\n    ASSERT_EQ(msg.payload_size, 0);\n}\n\n#    define CON_GET_ID_0_EMPTY_TOKEN \"\\x40\\x01\\x00\\x00\"\n#    define CON_GET_ID_0_8B_TOKEN \"\\x48\\x01\\x00\\x00\"\n\nAVS_UNIT_TEST(coap_msg_parse, opt_length_overflow) {\n    // CoAP options are limited to 16-bit unsigned integers\n    // 2-byte extended option delta + 0xffff = 13 + 256 + 0xffff > 0xffff\n    uint8_t packet[] = CON_GET_ID_0_EMPTY_TOKEN \"\\xe0\\xff\\xff\";\n\n    avs_coap_udp_msg_t msg;\n    ASSERT_FAIL(_avs_coap_udp_msg_parse(&msg, packet, sizeof(packet) - 1));\n}\n\nAVS_UNIT_TEST(coap_msg_parse, payload_marker_but_no_payload) {\n    // Payload marker MUST be omitted in case of empty packets\n    uint8_t packet[] = CON_GET_ID_0_EMPTY_TOKEN \"\\xff\";\n\n    avs_coap_udp_msg_t msg;\n    ASSERT_FAIL(_avs_coap_udp_msg_parse(&msg, packet, sizeof(packet) - 1));\n}\n\nAVS_UNIT_TEST(coap_msg_parse, token_options_payload) {\n    uint8_t packet[] =\n            CON_GET_ID_0_8B_TOKEN \"\\x01\\x02\\x03\\x04\\x05\\x06\\x07\\x08\" // token\n                                                                     // options\n                                  \"\\x00\"         // num delta 0, length 0\n                                  \"\\xd0\\x00\"     // num delta 13, length 0\n                                  \"\\xe0\\x00\\x00\" // num delta 13+256, length 0\n                                  \"\\xff\"         // payload marker\n                                  \"foo bar baz\"; // payload\n\n    avs_coap_udp_msg_t msg;\n    ASSERT_OK(_avs_coap_udp_msg_parse(&msg, packet, sizeof(packet) - 1));\n\n    ASSERT_EQ(msg.token.size, 8);\n    ASSERT_EQ_BYTES(msg.token.bytes, \"\\x01\\x02\\x03\\x04\\x05\\x06\\x07\\x08\");\n\n    uint32_t value;\n    ASSERT_OK(avs_coap_options_get_u32(&msg.options, 0, &value));\n    ASSERT_EQ(value, 0);\n    value = 0xDEADBEEF;\n    ASSERT_OK(avs_coap_options_get_u32(&msg.options, 13, &value));\n    ASSERT_EQ(value, 0);\n    value = 0xDEADBEEF;\n    ASSERT_OK(avs_coap_options_get_u32(&msg.options, 13 + 13 + 256, &value));\n    ASSERT_EQ(value, 0);\n\n    ASSERT_EQ(msg.payload_size, sizeof(\"foo bar baz\") - 1);\n    ASSERT_EQ_BYTES(msg.payload, \"foo bar baz\");\n}\n\nAVS_UNIT_TEST(coap_msg_parse, max_valid_option_number) {\n    uint8_t packet[] = CON_GET_ID_0_EMPTY_TOKEN\n            \"\\xe0\\xfe\\xf2\"; // num 13 + 256 + 65266 = 65535\n\n    avs_coap_udp_msg_t msg;\n    ASSERT_OK(_avs_coap_udp_msg_parse(&msg, packet, sizeof(packet) - 1));\n}\n\nAVS_UNIT_TEST(coap_msg_parse, invalid_option_number) {\n    uint8_t packet[] = CON_GET_ID_0_EMPTY_TOKEN\n            \"\\xe0\\xfe\\xf3\"; // num 13 + 256 + 65267 = 65536\n\n    avs_coap_udp_msg_t msg;\n    ASSERT_FAIL(_avs_coap_udp_msg_parse(&msg, packet, sizeof(packet) - 1));\n}\n\nAVS_UNIT_TEST(coap_msg_parse, invalid_option_number_sum) {\n    uint8_t packet[] = CON_GET_ID_0_EMPTY_TOKEN\n            \"\\xe0\\xfe\\xf2\" // num 13 + 256 + 65266 = 65535\n            \"\\x10\";        // num 65536 (+1)\n\n    avs_coap_udp_msg_t msg;\n    ASSERT_FAIL(_avs_coap_udp_msg_parse(&msg, packet, sizeof(packet) - 1));\n}\n\nAVS_UNIT_TEST(coap_msg, fuzz_1_missing_token) {\n    uint8_t packet[] = \"\\x68\\x64\\x05\\x06\\x0a\";\n\n    avs_coap_udp_msg_t msg;\n    ASSERT_FAIL(_avs_coap_udp_msg_parse(&msg, packet, sizeof(packet) - 1));\n}\n\nAVS_UNIT_TEST(coap_msg, fuzz_2_missing_option_ext_length) {\n    uint8_t packet[] = \"\\x60\\x64\\x05\\x06\\xfa\";\n\n    avs_coap_udp_msg_t msg;\n    ASSERT_FAIL(_avs_coap_udp_msg_parse(&msg, packet, sizeof(packet) - 1));\n}\n\nAVS_UNIT_TEST(coap_msg, fuzz_3_token_and_options) {\n    uint8_t packet[] = \"\\x64\\x2d\\x8d\\x20\" // header\n                       \"\\x50\\x16\\xf8\\x5b\" // token\n                       \"\\x73\\x77\\x4c\\x4f\\x03\\xe8\\x0a\";\n\n    avs_coap_udp_msg_t msg;\n    ASSERT_FAIL(_avs_coap_udp_msg_parse(&msg, packet, sizeof(packet) - 1));\n}\n\n#endif // defined(AVS_UNIT_TESTING) && defined(WITH_AVS_COAP_UDP)\n"
  },
  {
    "path": "deps/avs_coap/tests/udp/msg_cache.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <avs_coap_init.h>\n\n#if defined(AVS_UNIT_TESTING) && defined(WITH_AVS_COAP_UDP)\n\n#    include <time.h>\n\n#    include <avsystem/commons/avs_defs.h>\n#    include <avsystem/commons/avs_memory.h>\n\n#    include <avsystem/coap/code.h>\n\n#    define AVS_UNIT_ENABLE_SHORT_ASSERTS\n#    include <avsystem/commons/avs_unit_test.h>\n\n#    include \"udp/avs_coap_udp_msg.h\"\n#    include \"udp/avs_coap_udp_msg_cache.h\"\n\n#    include \"tests/mock_clock.h\"\n\n#    define MODULE_NAME test\n#    include <avs_coap_x_log_config.h>\n\nstatic const avs_coap_udp_tx_params_t tx_params = {\n    .ack_timeout = { 2, 0 },\n    .ack_random_factor = 1.5,\n    .max_retransmit = 4\n};\n\ntypedef struct {\n    avs_coap_udp_msg_t udp_msg;\n    uint8_t *storage;\n    size_t storage_size;\n} test_udp_msg_t;\n\nstatic test_udp_msg_t setup_msg_with_id(uint16_t msg_id, const char *payload) {\n    avs_coap_udp_msg_t msg = {\n        .header = _avs_coap_udp_header_init(AVS_COAP_UDP_TYPE_ACKNOWLEDGEMENT,\n                                            0, AVS_COAP_CODE(3, 4), msg_id),\n        .payload = payload,\n        .payload_size = strlen(payload)\n    };\n\n    size_t size = _avs_coap_udp_msg_size(&msg);\n    uint8_t *buf = (uint8_t *) malloc(size);\n    size_t written = 0;\n    ASSERT_OK(_avs_coap_udp_msg_serialize(&msg, buf, size, &written));\n    ASSERT_EQ(size, written);\n\n    return (test_udp_msg_t) {\n        .udp_msg = msg,\n        .storage = buf,\n        .storage_size = size\n    };\n}\n\nstatic void free_msg(test_udp_msg_t *msg) {\n    free(msg->storage);\n}\n\nstatic void free_msg_array(test_udp_msg_t (*msgs)[3]) {\n    for (size_t i = 0; i < 3; ++i) {\n        free_msg(&(*msgs)[i]);\n    }\n}\n\nstatic void assert_udp_msg_equal(avs_coap_udp_msg_t m1, avs_coap_udp_msg_t m2) {\n    ASSERT_EQ(m1.header.code, m2.header.code);\n    ASSERT_EQ(_avs_coap_udp_header_get_version(&m1.header),\n              _avs_coap_udp_header_get_version(&m2.header));\n    ASSERT_EQ(_avs_coap_udp_header_get_token_length(&m1.header),\n              _avs_coap_udp_header_get_token_length(&m2.header));\n    ASSERT_EQ(_avs_coap_udp_header_get_id(&m1.header),\n              _avs_coap_udp_header_get_id(&m2.header));\n\n    ASSERT_EQ(m1.token.size, m2.token.size);\n    ASSERT_EQ_BYTES_SIZED(m1.token.bytes, m2.token.bytes, m1.token.size);\n\n    ASSERT_EQ(m1.options.size, m2.options.size);\n    ASSERT_EQ_BYTES_SIZED(m1.options.begin, m2.options.begin, m1.options.size);\n\n    ASSERT_EQ(m1.payload_size, m2.payload_size);\n    ASSERT_EQ_BYTES_SIZED(m1.payload, m2.payload, m1.payload_size);\n}\n\nAVS_UNIT_TEST(coap_msg_cache, null) {\n    static const uint16_t id = 123;\n    test_udp_msg_t msg __attribute__((cleanup(free_msg))) =\n            setup_msg_with_id(id, \"\");\n\n    ASSERT_NULL(avs_coap_udp_response_cache_create(0));\n    ASSERT_FAIL(_avs_coap_udp_response_cache_add(NULL, \"host\", \"port\",\n                                                 &msg.udp_msg, &tx_params));\n    ASSERT_FAIL(_avs_coap_udp_response_cache_get(\n            NULL, \"host\", \"port\", id, &(avs_coap_udp_cached_response_t) { 0 }));\n\n    // these should not crash\n    avs_coap_udp_response_cache_release(\n            &(avs_coap_udp_response_cache_t *) { NULL });\n}\n\nAVS_UNIT_TEST(coap_msg_cache, hit_single) {\n    avs_coap_udp_response_cache_t *cache =\n            avs_coap_udp_response_cache_create(1024);\n\n    static const uint16_t id = 123;\n    test_udp_msg_t msg __attribute__((cleanup(free_msg))) =\n            setup_msg_with_id(id, \"\");\n\n    ASSERT_OK(_avs_coap_udp_response_cache_add(cache, \"host\", \"port\",\n                                               &msg.udp_msg, &tx_params));\n\n    // request message existing in cache\n    avs_coap_udp_cached_response_t cached_msg;\n    ASSERT_OK(_avs_coap_udp_response_cache_get(cache, \"host\", \"port\", id,\n                                               &cached_msg));\n    assert_udp_msg_equal(msg.udp_msg, cached_msg.msg);\n\n    avs_coap_udp_response_cache_release(&cache);\n}\n\nAVS_UNIT_TEST(coap_msg_cache, hit_multiple) {\n    avs_coap_udp_response_cache_t *cache =\n            avs_coap_udp_response_cache_create(1024);\n\n    static const uint16_t id = 123;\n    test_udp_msg_t msg[] __attribute__((cleanup(\n            free_msg_array))) = { setup_msg_with_id((uint16_t) (id + 0), \"\"),\n                                  setup_msg_with_id((uint16_t) (id + 1), \"\"),\n                                  setup_msg_with_id((uint16_t) (id + 2), \"\") };\n\n    for (size_t i = 0; i < AVS_ARRAY_SIZE(msg) - 1; ++i) {\n        ASSERT_OK(_avs_coap_udp_response_cache_add(\n                cache, \"host\", \"port\", &msg[i].udp_msg, &tx_params));\n    }\n\n    // request message existing in cache\n    for (uint16_t i = 0; i < AVS_ARRAY_SIZE(msg) - 1; ++i) {\n        avs_coap_udp_cached_response_t cached_msg;\n        ASSERT_OK(_avs_coap_udp_response_cache_get(\n                cache, \"host\", \"port\", (uint16_t) (id + i), &cached_msg));\n        assert_udp_msg_equal(msg[i].udp_msg, cached_msg.msg);\n    }\n\n    avs_coap_udp_response_cache_release(&cache);\n}\n\nAVS_UNIT_TEST(coap_msg_cache, hit_expired) {\n    avs_coap_udp_response_cache_t *cache =\n            avs_coap_udp_response_cache_create(1024);\n\n    static const uint16_t id = 123;\n    test_udp_msg_t msg __attribute__((cleanup(free_msg))) =\n            setup_msg_with_id(id, \"\");\n\n    _avs_mock_clock_start((avs_time_monotonic_t) { AVS_TIME_DURATION_ZERO });\n\n    AVS_UNIT_ASSERT_SUCCESS(_avs_coap_udp_response_cache_add(\n            cache, \"host\", \"port\", &msg.udp_msg, &tx_params));\n    _avs_mock_clock_advance(avs_time_duration_from_scalar(247, AVS_TIME_S));\n\n    // request expired message existing in cache\n    ASSERT_FAIL(_avs_coap_udp_response_cache_get(\n            cache, \"host\", \"port\", id,\n            &(avs_coap_udp_cached_response_t) { 0 }));\n\n    avs_coap_udp_response_cache_release(&cache);\n\n    _avs_mock_clock_finish();\n}\n\nAVS_UNIT_TEST(coap_msg_cache, hit_after_expiration) {\n    avs_coap_udp_response_cache_t *cache =\n            avs_coap_udp_response_cache_create(1024);\n\n    static const uint16_t id1 = 123;\n    static const uint16_t id2 = 321;\n\n    test_udp_msg_t msg1 __attribute__((cleanup(free_msg))) =\n            setup_msg_with_id(id1, \"\");\n    test_udp_msg_t msg2 __attribute__((cleanup(free_msg))) =\n            setup_msg_with_id(id2, \"\");\n\n    _avs_mock_clock_start((avs_time_monotonic_t) { AVS_TIME_DURATION_ZERO });\n\n    ASSERT_OK(_avs_coap_udp_response_cache_add(cache, \"host\", \"port\",\n                                               &msg1.udp_msg, &tx_params));\n    _avs_mock_clock_advance(avs_time_duration_from_scalar(60, AVS_TIME_S));\n    ASSERT_OK(_avs_coap_udp_response_cache_add(cache, \"host\", \"port\",\n                                               &msg2.udp_msg, &tx_params));\n    _avs_mock_clock_advance(avs_time_duration_from_scalar(60, AVS_TIME_S));\n\n    // request expired message existing in cache\n    avs_coap_udp_cached_response_t cached_msg;\n    ASSERT_OK(_avs_coap_udp_response_cache_get(cache, \"host\", \"port\", id2,\n                                               &cached_msg));\n    assert_udp_msg_equal(msg2.udp_msg, cached_msg.msg);\n\n    avs_coap_udp_response_cache_release(&cache);\n\n    _avs_mock_clock_finish();\n}\n\nAVS_UNIT_TEST(coap_msg_cache, miss_empty) {\n    avs_coap_udp_response_cache_t *cache =\n            avs_coap_udp_response_cache_create(1024);\n    static const uint16_t id = 123;\n\n    // request message from empty cache\n    ASSERT_FAIL(_avs_coap_udp_response_cache_get(\n            cache, \"host\", \"port\", id,\n            &(avs_coap_udp_cached_response_t) { 0 }));\n\n    avs_coap_udp_response_cache_release(&cache);\n}\n\nAVS_UNIT_TEST(coap_msg_cache, miss_non_empty) {\n    avs_coap_udp_response_cache_t *cache =\n            avs_coap_udp_response_cache_create(1024);\n\n    static const uint16_t id = 123;\n    test_udp_msg_t msg __attribute__((cleanup(free_msg))) =\n            setup_msg_with_id(id, \"\");\n\n    ASSERT_OK(_avs_coap_udp_response_cache_add(cache, \"host\", \"port\",\n                                               &msg.udp_msg, &tx_params));\n\n    // request message not in cache\n    ASSERT_FAIL(_avs_coap_udp_response_cache_get(\n            cache, \"host\", \"port\", (uint16_t) (id + 1),\n            &(avs_coap_udp_cached_response_t) { 0 }));\n\n    avs_coap_udp_response_cache_release(&cache);\n}\n\nAVS_UNIT_TEST(coap_msg_cache, add_existing) {\n    avs_coap_udp_response_cache_t *cache =\n            avs_coap_udp_response_cache_create(1024);\n\n    static const uint16_t id = 123;\n    test_udp_msg_t msg __attribute__((cleanup(free_msg))) =\n            setup_msg_with_id(id, \"\");\n\n    // replacing existing non-expired cached messages with updated ones\n    // is not allowed\n    ASSERT_OK(_avs_coap_udp_response_cache_add(cache, \"host\", \"port\",\n                                               &msg.udp_msg, &tx_params));\n    ASSERT_FAIL(_avs_coap_udp_response_cache_add(cache, \"host\", \"port\",\n                                                 &msg.udp_msg, &tx_params));\n\n    avs_coap_udp_response_cache_release(&cache);\n}\n\nAVS_UNIT_TEST(coap_msg_cache, add_existing_expired) {\n    avs_coap_udp_response_cache_t *cache =\n            avs_coap_udp_response_cache_create(1024);\n\n    static const uint16_t id = 123;\n    test_udp_msg_t msg __attribute__((cleanup(free_msg))) =\n            setup_msg_with_id(id, \"\");\n\n    _avs_mock_clock_start((avs_time_monotonic_t) { AVS_TIME_DURATION_ZERO });\n\n    // replacing existing expired cached messages is not allowed\n    ASSERT_OK(_avs_coap_udp_response_cache_add(cache, \"host\", \"port\",\n                                               &msg.udp_msg, &tx_params));\n    _avs_mock_clock_advance(avs_time_duration_from_scalar(247, AVS_TIME_S));\n    ASSERT_OK(_avs_coap_udp_response_cache_add(cache, \"host\", \"port\",\n                                               &msg.udp_msg, &tx_params));\n\n    avs_coap_udp_response_cache_release(&cache);\n\n    _avs_mock_clock_finish();\n}\n\nAVS_UNIT_TEST(coap_msg_cache, add_evict) {\n    static const uint16_t id = 123;\n    test_udp_msg_t msg[] __attribute__((cleanup(\n            free_msg_array))) = { setup_msg_with_id((uint16_t) (id + 0), \"\"),\n                                  setup_msg_with_id((uint16_t) (id + 1), \"\"),\n                                  setup_msg_with_id((uint16_t) (id + 2), \"\") };\n    avs_coap_udp_cached_response_t cached_msg;\n\n    const uint16_t msg_size =\n            (uint16_t) _avs_coap_udp_msg_size(&msg[0].udp_msg);\n    avs_coap_udp_response_cache_t *cache = avs_coap_udp_response_cache_create(\n            (_avs_coap_udp_response_cache_overhead(&msg[0].udp_msg) + msg_size)\n            * 2);\n\n    // message with another ID removes oldest existing entry if extra space\n    // is required\n    ASSERT_OK(_avs_coap_udp_response_cache_add(cache, \"host\", \"port\",\n                                               &msg[0].udp_msg, &tx_params));\n    ASSERT_OK(_avs_coap_udp_response_cache_add(cache, \"host\", \"port\",\n                                               &msg[1].udp_msg, &tx_params));\n    ASSERT_OK(_avs_coap_udp_response_cache_add(cache, \"host\", \"port\",\n                                               &msg[2].udp_msg, &tx_params));\n\n    // oldest entry was removed\n    ASSERT_FAIL(_avs_coap_udp_response_cache_get(\n            cache, \"host\", \"port\", id,\n            &(avs_coap_udp_cached_response_t) { 0 }));\n\n    // newer entry still exists\n    ASSERT_OK(_avs_coap_udp_response_cache_get(\n            cache, \"host\", \"port\", (uint16_t) (id + 1), &cached_msg));\n    assert_udp_msg_equal(msg[1].udp_msg, cached_msg.msg);\n\n    // newest entry was inserted\n    ASSERT_OK(_avs_coap_udp_response_cache_get(\n            cache, \"host\", \"port\", (uint16_t) (id + 2), &cached_msg));\n    assert_udp_msg_equal(msg[2].udp_msg, cached_msg.msg);\n\n    avs_coap_udp_response_cache_release(&cache);\n}\n\nAVS_UNIT_TEST(coap_msg_cache, add_evict_multiple) {\n    static const uint16_t id = 123;\n    test_udp_msg_t msg[] __attribute((cleanup(free_msg_array))) = {\n        setup_msg_with_id((uint16_t) (id + 0), \"\"),\n        setup_msg_with_id((uint16_t) (id + 1), \"\"),\n        setup_msg_with_id((uint16_t) (id + 2), \"\\xFF\"\n                                               \"foobarbaz\")\n    };\n\n    const uint16_t msg_size =\n            (uint16_t) _avs_coap_udp_msg_size(&msg[0].udp_msg);\n    avs_coap_udp_response_cache_t *cache = avs_coap_udp_response_cache_create(\n            (_avs_coap_udp_response_cache_overhead(&msg[0].udp_msg) + msg_size)\n            * 2);\n\n    // message with another ID removes oldest existing entries if extra space\n    // is required\n    ASSERT_OK(_avs_coap_udp_response_cache_add(cache, \"host\", \"port\",\n                                               &msg[0].udp_msg, &tx_params));\n    ASSERT_OK(_avs_coap_udp_response_cache_add(cache, \"host\", \"port\",\n                                               &msg[1].udp_msg, &tx_params));\n    ASSERT_OK(_avs_coap_udp_response_cache_add(cache, \"host\", \"port\",\n                                               &msg[2].udp_msg, &tx_params));\n\n    // oldest entries were removed\n    ASSERT_FAIL(_avs_coap_udp_response_cache_get(\n            cache, \"host\", \"port\", id,\n            &(avs_coap_udp_cached_response_t) { 0 }));\n    ASSERT_FAIL(_avs_coap_udp_response_cache_get(\n            cache, \"host\", \"port\", (uint16_t) (id + 1),\n            &(avs_coap_udp_cached_response_t) { 0 }));\n\n    // newest entry was inserted\n    avs_coap_udp_cached_response_t cached_msg;\n    ASSERT_OK(_avs_coap_udp_response_cache_get(\n            cache, \"host\", \"port\", (uint16_t) (id + 2), &cached_msg));\n    assert_udp_msg_equal(msg[2].udp_msg, cached_msg.msg);\n\n    avs_coap_udp_response_cache_release(&cache);\n}\n\nAVS_UNIT_TEST(coap_msg_cache, add_too_big) {\n    static const uint16_t id = 123;\n    test_udp_msg_t m1 __attribute__((cleanup(free_msg))) =\n            setup_msg_with_id((uint16_t) (id + 0), \"\");\n    test_udp_msg_t m2 __attribute__((cleanup(free_msg))) =\n            setup_msg_with_id((uint16_t) (id + 1), \"\\xFF\"\n                                                   \"foobarbaz\");\n\n    const uint16_t msg_size = (uint16_t) _avs_coap_udp_msg_size(&m1.udp_msg);\n    avs_coap_udp_response_cache_t *cache = avs_coap_udp_response_cache_create(\n            _avs_coap_udp_response_cache_overhead(&m1.udp_msg) + msg_size);\n\n    // message too long to put into cache should be ignored\n    ASSERT_OK(_avs_coap_udp_response_cache_add(cache, \"host\", \"port\",\n                                               &m1.udp_msg, &tx_params));\n    ASSERT_FAIL(_avs_coap_udp_response_cache_add(cache, \"host\", \"port\",\n                                                 &m2.udp_msg, &tx_params));\n\n    // previously-added entry is still there\n    avs_coap_udp_cached_response_t cached_msg;\n    ASSERT_OK(_avs_coap_udp_response_cache_get(cache, \"host\", \"port\", id,\n                                               &cached_msg));\n    assert_udp_msg_equal(m1.udp_msg, cached_msg.msg);\n\n    // \"too big\" entry was not inserted\n    ASSERT_FAIL(_avs_coap_udp_response_cache_get(\n            cache, \"host\", \"port\", (uint16_t) (id + 1),\n            &(avs_coap_udp_cached_response_t) { 0 }));\n\n    avs_coap_udp_response_cache_release(&cache);\n}\n\nAVS_UNIT_TEST(coap_msg_cache, multiple_hosts_same_ids) {\n    static const uint16_t id = 123;\n    test_udp_msg_t m1 __attribute__((cleanup(free_msg))) =\n            setup_msg_with_id(id, \"\");\n    test_udp_msg_t m2 __attribute__((cleanup(free_msg))) =\n            setup_msg_with_id(id, \"\\xFF\"\n                                  \"foobarbaz\");\n\n    avs_coap_udp_response_cache_t *cache =\n            avs_coap_udp_response_cache_create(4096);\n\n    ASSERT_OK(_avs_coap_udp_response_cache_add(cache, \"h1\", \"port\", &m1.udp_msg,\n                                               &tx_params));\n    ASSERT_OK(_avs_coap_udp_response_cache_add(cache, \"h2\", \"port\", &m2.udp_msg,\n                                               &tx_params));\n\n    // both entries should be present despite having identical IDs\n    avs_coap_udp_cached_response_t cached_msg;\n    ASSERT_OK(_avs_coap_udp_response_cache_get(cache, \"h1\", \"port\", id,\n                                               &cached_msg));\n    assert_udp_msg_equal(m1.udp_msg, cached_msg.msg);\n\n    ASSERT_OK(_avs_coap_udp_response_cache_get(cache, \"h2\", \"port\", id,\n                                               &cached_msg));\n    assert_udp_msg_equal(m2.udp_msg, cached_msg.msg);\n\n    avs_coap_udp_response_cache_release(&cache);\n}\n\n#endif // defined(AVS_UNIT_TESTING) && defined(WITH_AVS_COAP_UDP)\n"
  },
  {
    "path": "deps/avs_coap/tests/udp/setsock.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <avs_coap_init.h>\n\n#if defined(AVS_UNIT_TESTING) && defined(WITH_AVS_COAP_UDP)\n\n#    include <avsystem/coap/coap.h>\n#    include <avsystem/commons/avs_errno.h>\n\n#    define MODULE_NAME test\n#    include <avs_coap_x_log_config.h>\n\n#    include \"./utils.h\"\n\nAVS_UNIT_TEST(udp_setsock, callable_only_once) {\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup(&AVS_COAP_DEFAULT_UDP_TX_PARAMS, 1024, 1024, NULL);\n    // socket already set by test_setup()\n    ASSERT_FAIL(avs_coap_ctx_set_socket(env.coap_ctx, env.mocksock));\n}\n\nAVS_UNIT_TEST(udp_setsock, cleanup_possible_without_socket) {\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_without_socket(NULL, 1024, 1024, NULL);\n    avs_coap_ctx_cleanup(&env.coap_ctx);\n}\n\n#endif // defined(AVS_UNIT_TESTING) && defined(WITH_AVS_COAP_UDP)\n"
  },
  {
    "path": "deps/avs_coap/tests/udp/streaming_client.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <avs_coap_init.h>\n\n#if defined(AVS_UNIT_TESTING) && defined(WITH_AVS_COAP_UDP) \\\n        && defined(WITH_AVS_COAP_STREAMING_API)\n\n#    include <avsystem/coap/coap.h>\n\n#    define MODULE_NAME test\n#    include <avs_coap_x_log_config.h>\n\n#    include \"./utils.h\"\n\nAVS_UNIT_TEST(udp_streaming_client, streaming_request) {\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_default();\n\n    avs_unit_mocksock_enable_recv_timeout_getsetopt(\n            env.mocksock, avs_time_duration_from_scalar(1, AVS_TIME_S));\n\n#    define PAYLOAD_CONTENT DATA_1KB \"?\"\n\n    const test_msg_t *request =\n            COAP_MSG(CON, GET, ID(0), TOKEN(nth_token(0)), NO_PAYLOAD);\n    const test_msg_t *response =\n            COAP_MSG(ACK, CONTENT, ID(0), TOKEN(nth_token(0)),\n                     PAYLOAD(PAYLOAD_CONTENT));\n\n    expect_send(&env, request);\n    expect_recv(&env, response);\n    expect_has_buffered_data_check(&env, false);\n\n    avs_stream_t *stream = NULL;\n    avs_coap_response_header_t response_header;\n\n    ASSERT_OK(avs_coap_streaming_send_request(env.coap_ctx,\n                                              &request->request_header, NULL,\n                                              NULL, &response_header, &stream));\n    avs_coap_options_cleanup(&response_header.options);\n\n    char buf[sizeof(PAYLOAD_CONTENT)];\n    size_t bytes_read;\n    bool finished;\n    ASSERT_OK(\n            avs_stream_read(stream, &bytes_read, &finished, buf, sizeof(buf)));\n\n    ASSERT_EQ_BYTES(PAYLOAD_CONTENT, buf);\n    ASSERT_EQ(bytes_read, sizeof(PAYLOAD_CONTENT) - 1);\n    ASSERT_TRUE(finished);\n\n#    undef PAYLOAD_CONTENT\n}\n\nAVS_UNIT_TEST(udp_streaming_client, reset_in_response) {\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_default();\n\n    avs_unit_mocksock_enable_recv_timeout_getsetopt(\n            env.mocksock, avs_time_duration_from_scalar(1, AVS_TIME_S));\n\n    const test_msg_t *expected_request =\n            COAP_MSG(CON, POST, ID(0), TOKEN(nth_token(0)), NO_PAYLOAD);\n    const test_msg_t *expected_response = COAP_MSG(RST, EMPTY, ID(0));\n\n    expect_send(&env, expected_request);\n    expect_recv(&env, expected_response);\n    expect_has_buffered_data_check(&env, false);\n\n    avs_coap_response_header_t response;\n    ASSERT_FAIL(avs_coap_streaming_send_request(\n            env.coap_ctx, &expected_request->request_header, NULL, NULL,\n            &response, NULL));\n    avs_coap_options_cleanup(&response.options);\n}\n\n#    ifdef WITH_AVS_COAP_BLOCK\n\nAVS_UNIT_TEST(udp_streaming_client, streaming_request_block_response) {\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_default();\n\n    avs_unit_mocksock_enable_recv_timeout_getsetopt(\n            env.mocksock, avs_time_duration_from_scalar(1, AVS_TIME_S));\n\n#        define PAYLOAD_CONTENT DATA_1KB \"?\"\n\n    const test_msg_t *requests[] = {\n        COAP_MSG(CON, GET, ID(0), TOKEN(nth_token(0)), NO_PAYLOAD),\n        COAP_MSG(CON, GET, ID(1), TOKEN(nth_token(1)), BLOCK2_REQ(1, 1024))\n    };\n    const test_msg_t *responses[] = {\n        COAP_MSG(ACK, CONTENT, ID(0), TOKEN(nth_token(0)),\n                 BLOCK2_RES(0, 1024, PAYLOAD_CONTENT)),\n        COAP_MSG(ACK, CONTENT, ID(1), TOKEN(nth_token(1)),\n                 BLOCK2_RES(1, 1024, PAYLOAD_CONTENT))\n    };\n\n    expect_send(&env, requests[0]);\n    expect_recv(&env, responses[0]);\n    expect_send(&env, requests[1]);\n    expect_recv(&env, responses[1]);\n    expect_has_buffered_data_check(&env, false);\n\n    avs_stream_t *stream = NULL;\n    avs_coap_response_header_t response;\n\n    ASSERT_OK(avs_coap_streaming_send_request(env.coap_ctx,\n                                              &requests[0]->request_header,\n                                              NULL, NULL, &response, &stream));\n    avs_coap_options_cleanup(&response.options);\n\n    char buf[sizeof(PAYLOAD_CONTENT)];\n    size_t bytes_read_total = 0;\n    size_t bytes_read;\n    bool finished = false;\n    while (!finished) {\n        ASSERT_OK(avs_stream_read(stream, &bytes_read, &finished,\n                                  buf + bytes_read_total,\n                                  sizeof(buf) - bytes_read_total));\n        bytes_read_total += bytes_read;\n    }\n\n    ASSERT_EQ_BYTES(PAYLOAD_CONTENT, buf);\n    ASSERT_EQ(bytes_read_total, sizeof(PAYLOAD_CONTENT) - 1);\n\n#        undef PAYLOAD_CONTENT\n}\n\nAVS_UNIT_TEST(udp_streaming_client,\n              streaming_request_mismatched_first_block_response) {\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_default();\n\n    avs_unit_mocksock_enable_recv_timeout_getsetopt(\n            env.mocksock, avs_time_duration_from_scalar(1, AVS_TIME_S));\n\n#        define PAYLOAD_CONTENT DATA_1KB \"?\"\n\n    const test_msg_t *request =\n            COAP_MSG(CON, GET, ID(0), TOKEN(nth_token(0)), NO_PAYLOAD);\n    const test_msg_t *response =\n            COAP_MSG(ACK, CONTENT, ID(0), TOKEN(nth_token(0)),\n                     BLOCK2_RES(1, 1024, PAYLOAD_CONTENT));\n\n    expect_send(&env, request);\n    expect_recv(&env, response);\n    expect_has_buffered_data_check(&env, false);\n\n    avs_stream_t *stream = NULL;\n    avs_coap_response_header_t response_header;\n\n    avs_error_t err =\n            avs_coap_streaming_send_request(env.coap_ctx,\n                                            &request->request_header, NULL,\n                                            NULL, &response_header, &stream);\n\n    ASSERT_EQ(err.category, AVS_COAP_ERR_CATEGORY);\n    ASSERT_EQ(err.code, AVS_COAP_ERR_MALFORMED_OPTIONS);\n\n#        undef PAYLOAD_CONTENT\n}\n\nAVS_UNIT_TEST(udp_streaming_client,\n              streaming_request_mismatched_block_response) {\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_default();\n\n    avs_unit_mocksock_enable_recv_timeout_getsetopt(\n            env.mocksock, avs_time_duration_from_scalar(1, AVS_TIME_S));\n\n#        define PAYLOAD_CONTENT DATA_1KB DATA_1KB \"?\"\n\n    const test_msg_t *requests[] = {\n        COAP_MSG(CON, GET, ID(0), TOKEN(nth_token(0)), NO_PAYLOAD),\n        COAP_MSG(CON, GET, ID(1), TOKEN(nth_token(1)), BLOCK2_REQ(1, 1024)),\n    };\n    const test_msg_t *responses[] = {\n        COAP_MSG(ACK, CONTENT, ID(0), TOKEN(nth_token(0)),\n                 BLOCK2_RES(0, 1024, PAYLOAD_CONTENT)),\n        COAP_MSG(ACK, CONTENT, ID(1), TOKEN(nth_token(1)),\n                 BLOCK2_RES(2, 1024, PAYLOAD_CONTENT))\n    };\n\n    expect_send(&env, requests[0]);\n    expect_recv(&env, responses[0]);\n    expect_send(&env, requests[1]);\n    expect_recv(&env, responses[1]);\n    expect_has_buffered_data_check(&env, false);\n\n    avs_stream_t *stream = NULL;\n    avs_coap_response_header_t response;\n\n    ASSERT_OK(avs_coap_streaming_send_request(env.coap_ctx,\n                                              &requests[0]->request_header,\n                                              NULL, NULL, &response, &stream));\n    avs_coap_options_cleanup(&response.options);\n\n    char buf[sizeof(PAYLOAD_CONTENT)];\n    size_t bytes_read_total = 0;\n    size_t bytes_read;\n    avs_error_t err;\n    while (avs_is_ok((err = avs_stream_read(stream, &bytes_read, NULL,\n                                            buf + bytes_read_total,\n                                            sizeof(buf) - bytes_read_total)))) {\n        bytes_read_total += bytes_read;\n    }\n\n    ASSERT_EQ(err.category, AVS_COAP_ERR_CATEGORY);\n    ASSERT_EQ(err.code, AVS_COAP_ERR_MALFORMED_OPTIONS);\n\n#        undef PAYLOAD_CONTENT\n}\n\nAVS_UNIT_TEST(udp_streaming_client, streaming_request_peek) {\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_default();\n\n    avs_unit_mocksock_enable_recv_timeout_getsetopt(\n            env.mocksock, avs_time_duration_from_scalar(1, AVS_TIME_S));\n\n#        define PAYLOAD_CONTENT (DATA_1KB \"?\")\n\n    const test_msg_t *requests[] = {\n        COAP_MSG(CON, GET, ID(0), TOKEN(nth_token(0)), NO_PAYLOAD),\n        COAP_MSG(CON, GET, ID(1), TOKEN(nth_token(1)), BLOCK2_REQ(1, 1024))\n    };\n    const test_msg_t *responses[] = {\n        COAP_MSG(ACK, CONTENT, ID(0), TOKEN(nth_token(0)),\n                 BLOCK2_RES(0, 1024, PAYLOAD_CONTENT)),\n        COAP_MSG(ACK, CONTENT, ID(1), TOKEN(nth_token(1)),\n                 BLOCK2_RES(1, 1024, PAYLOAD_CONTENT))\n    };\n\n    expect_send(&env, requests[0]);\n    expect_recv(&env, responses[0]);\n    expect_send(&env, requests[1]);\n    expect_recv(&env, responses[1]);\n    expect_has_buffered_data_check(&env, false);\n\n    avs_stream_t *stream = NULL;\n    avs_coap_response_header_t response;\n\n    ASSERT_OK(avs_coap_streaming_send_request(env.coap_ctx,\n                                              &requests[0]->request_header,\n                                              NULL, NULL, &response, &stream));\n    avs_coap_options_cleanup(&response.options);\n\n    char buf[sizeof(PAYLOAD_CONTENT) / 64];\n    size_t bytes_read_total = 0;\n    size_t bytes_read;\n    bool finished = false;\n    while (!finished) {\n        char ch;\n        ASSERT_OK(avs_stream_peek(stream, 0, &ch));\n        ASSERT_OK(avs_stream_read(stream, &bytes_read, &finished, buf,\n                                  sizeof(buf)));\n        ASSERT_EQ(buf[0], ch);\n        bytes_read_total += bytes_read;\n    }\n\n    ASSERT_EQ(bytes_read_total, sizeof(PAYLOAD_CONTENT) - 1);\n\n#        undef PAYLOAD_CONTENT\n}\n\nAVS_UNIT_TEST(udp_streaming_client, streaming_request_block_error) {\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_default();\n\n    avs_unit_mocksock_enable_recv_timeout_getsetopt(\n            env.mocksock, avs_time_duration_from_scalar(1, AVS_TIME_S));\n\n#        define PAYLOAD_CONTENT DATA_1KB \"?\"\n\n    const test_msg_t *requests[] = {\n        COAP_MSG(CON, GET, ID(0), TOKEN(nth_token(0)), NO_PAYLOAD),\n        COAP_MSG(CON, GET, ID(1), TOKEN(nth_token(1)), BLOCK2_REQ(1, 1024))\n    };\n    const test_msg_t *responses[] = {\n        COAP_MSG(ACK, CONTENT, ID(0), TOKEN(nth_token(0)),\n                 BLOCK2_RES(0, 1024, PAYLOAD_CONTENT))\n    };\n\n    expect_send(&env, requests[0]);\n    expect_recv(&env, responses[0]);\n    expect_send(&env, requests[1]);\n\n    avs_stream_t *stream = NULL;\n    avs_coap_response_header_t response;\n\n    ASSERT_OK(avs_coap_streaming_send_request(env.coap_ctx,\n                                              &requests[0]->request_header,\n                                              NULL, NULL, &response, &stream));\n    avs_coap_options_cleanup(&response.options);\n\n    char buf[sizeof(PAYLOAD_CONTENT)];\n    size_t bytes_read;\n    bool finished = false;\n    ASSERT_OK(\n            avs_stream_read(stream, &bytes_read, &finished, buf, sizeof(buf)));\n    ASSERT_EQ(bytes_read, 1024);\n    ASSERT_FALSE(finished);\n    ASSERT_EQ_BYTES_SIZED(PAYLOAD_CONTENT, buf, bytes_read);\n\n    avs_unit_mocksock_input_fail(env.mocksock, avs_errno(AVS_ECONNREFUSED));\n\n    avs_error_t err = avs_stream_peek(stream, 0, &(char) { 0 });\n    ASSERT_FAIL(err);\n    ASSERT_FALSE(avs_is_eof(err));\n    ASSERT_FAIL(\n            avs_stream_read(stream, &bytes_read, &finished, buf, sizeof(buf)));\n\n#        undef PAYLOAD_CONTENT\n}\n\nAVS_UNIT_TEST(udp_streaming_client, streaming_block_request) {\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_default();\n\n    avs_unit_mocksock_enable_recv_timeout_getsetopt(\n            env.mocksock, avs_time_duration_from_scalar(1, AVS_TIME_S));\n\n#        define PAYLOAD_CONTENT DATA_1KB \"?\"\n\n    test_streaming_payload_t payload = {\n        .data = PAYLOAD_CONTENT,\n        .size = sizeof(PAYLOAD_CONTENT) - 1\n    };\n\n    const test_msg_t *requests[] = {\n        COAP_MSG(CON, GET, ID(0), TOKEN(nth_token(0)),\n                 BLOCK1_REQ(0, 1024, PAYLOAD_CONTENT)),\n        COAP_MSG(CON, GET, ID(1), TOKEN(nth_token(1)),\n                 BLOCK1_REQ(1, 1024, PAYLOAD_CONTENT))\n    };\n    const test_msg_t *responses[] = { COAP_MSG(ACK, CONTINUE, ID(0),\n                                               TOKEN(nth_token(0)),\n                                               BLOCK1_RES(0, 1024, true)),\n                                      COAP_MSG(ACK, CONTENT, ID(1),\n                                               TOKEN(nth_token(1)),\n                                               BLOCK1_RES(1, 1024, false)) };\n\n    expect_send(&env, requests[0]);\n    expect_recv(&env, responses[0]);\n    expect_send(&env, requests[1]);\n    expect_recv(&env, responses[1]);\n    expect_has_buffered_data_check(&env, false);\n\n    avs_stream_t *stream = NULL;\n    avs_coap_response_header_t response;\n    ASSERT_OK(avs_coap_streaming_send_request(\n            env.coap_ctx, &requests[0]->request_header, test_streaming_writer,\n            &payload, &response, &stream));\n    avs_coap_options_cleanup(&response.options);\n\n    char buf[1];\n    size_t bytes_read;\n    bool finished;\n    ASSERT_OK(\n            avs_stream_read(stream, &bytes_read, &finished, buf, sizeof(buf)));\n\n    ASSERT_EQ(bytes_read, 0);\n    ASSERT_TRUE(finished);\n\n#        undef PAYLOAD_CONTENT\n}\n\nAVS_UNIT_TEST(udp_streaming_client, small_block_request) {\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_default();\n\n    avs_unit_mocksock_enable_recv_timeout_getsetopt(\n            env.mocksock, avs_time_duration_from_scalar(1, AVS_TIME_S));\n\n#        define PAYLOAD_CONTENT DATA_16B \"?\"\n\n    test_streaming_payload_t payload = {\n        .data = PAYLOAD_CONTENT,\n        .size = sizeof(PAYLOAD_CONTENT) - 1\n    };\n\n    // request packets & MTU crafted specifically so that accounting for option\n    // size makes avs_coap use lower block size than without them. This used to\n    // cause an assertion failure in streaming_client API (T2533)\n    avs_unit_mocksock_enable_inner_mtu_getopt(env.mocksock, 75);\n\n    const test_msg_t *requests[] = {\n        COAP_MSG(CON, GET, ID(0), TOKEN(nth_token(0)),\n                 PATH(\"string that requires a lot of space\"),\n                 BLOCK1_REQ(0, 16, PAYLOAD_CONTENT)),\n        COAP_MSG(CON, GET, ID(1), TOKEN(nth_token(1)),\n                 PATH(\"string that requires a lot of space\"),\n                 BLOCK1_REQ(1, 16, PAYLOAD_CONTENT))\n    };\n    const test_msg_t *responses[] = { COAP_MSG(ACK, CONTINUE, ID(0),\n                                               TOKEN(nth_token(0)),\n                                               BLOCK1_RES(0, 16, true)),\n                                      COAP_MSG(ACK, CONTENT, ID(1),\n                                               TOKEN(nth_token(1)),\n                                               BLOCK1_RES(1, 16, false)) };\n\n    expect_send(&env, requests[0]);\n    expect_recv(&env, responses[0]);\n    expect_send(&env, requests[1]);\n    expect_recv(&env, responses[1]);\n    expect_has_buffered_data_check(&env, false);\n\n    avs_stream_t *stream = NULL;\n    avs_coap_response_header_t response;\n    ASSERT_OK(avs_coap_streaming_send_request(\n            env.coap_ctx, &requests[0]->request_header, test_streaming_writer,\n            &payload, &response, &stream));\n    avs_coap_options_cleanup(&response.options);\n\n    char buf[1];\n    size_t bytes_read;\n    bool finished;\n    ASSERT_OK(\n            avs_stream_read(stream, &bytes_read, &finished, buf, sizeof(buf)));\n\n    ASSERT_EQ(bytes_read, 0);\n    ASSERT_TRUE(finished);\n\n#        undef PAYLOAD_CONTENT\n}\n\nAVS_UNIT_TEST(udp_streaming_client, write_equal_to_block_size) {\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_default();\n\n    avs_unit_mocksock_enable_recv_timeout_getsetopt(\n            env.mocksock, avs_time_duration_from_scalar(1, AVS_TIME_S));\n\n#        define PAYLOAD_CONTENT DATA_1KB \"?\"\n\n    test_streaming_payload_t payload = {\n        .data = PAYLOAD_CONTENT,\n        .size = sizeof(PAYLOAD_CONTENT) - 1,\n        // Force test_streaming_writer to call avs_stream_write with data\n        // chunks of size exactly equal to block size used. This used to\n        // confuse streaming_client API enough to incorrectly assume there's\n        // only 1024 bytes of request data because of having not enough data in\n        // streaming API buffer.\n        .chunk_size = 1024\n    };\n\n    const test_msg_t *requests[] = {\n        COAP_MSG(CON, GET, ID(0), TOKEN(nth_token(0)),\n                 BLOCK1_REQ(0, 1024, PAYLOAD_CONTENT)),\n        COAP_MSG(CON, GET, ID(1), TOKEN(nth_token(1)),\n                 BLOCK1_REQ(1, 1024, PAYLOAD_CONTENT))\n    };\n    const test_msg_t *responses[] = { COAP_MSG(ACK, CONTINUE, ID(0),\n                                               TOKEN(nth_token(0)),\n                                               BLOCK1_RES(0, 1024, true)),\n                                      COAP_MSG(ACK, CONTENT, ID(1),\n                                               TOKEN(nth_token(1)),\n                                               BLOCK1_RES(1, 1024, false)) };\n\n    expect_send(&env, requests[0]);\n    expect_recv(&env, responses[0]);\n    expect_send(&env, requests[1]);\n    expect_recv(&env, responses[1]);\n    expect_has_buffered_data_check(&env, false);\n\n    avs_coap_request_header_t req_without_block1 = requests[0]->request_header;\n    ASSERT_OK(_avs_coap_options_copy_as_dynamic(\n            &req_without_block1.options, &requests[0]->request_header.options));\n    avs_coap_options_remove_by_number(&req_without_block1.options,\n                                      AVS_COAP_OPTION_BLOCK1);\n\n    avs_stream_t *stream = NULL;\n    avs_coap_response_header_t response;\n    ASSERT_OK(avs_coap_streaming_send_request(env.coap_ctx, &req_without_block1,\n                                              test_streaming_writer, &payload,\n                                              &response, &stream));\n    avs_coap_options_cleanup(&response.options);\n\n    char buf[1];\n    size_t bytes_read;\n    bool finished;\n    ASSERT_OK(\n            avs_stream_read(stream, &bytes_read, &finished, buf, sizeof(buf)));\n\n    ASSERT_EQ(bytes_read, 0);\n    ASSERT_TRUE(finished);\n\n    avs_coap_options_cleanup(&req_without_block1.options);\n\n#        undef PAYLOAD_CONTENT\n}\n\n#    endif // WITH_AVS_COAP_BLOCK\n\n#endif // defined(AVS_UNIT_TESTING) && defined(WITH_AVS_COAP_UDP) &&\n       // defined(WITH_AVS_COAP_STREAMING_API)\n"
  },
  {
    "path": "deps/avs_coap/tests/udp/streaming_observe.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <avs_coap_init.h>\n\n#if defined(AVS_UNIT_TESTING) && defined(WITH_AVS_COAP_UDP) \\\n        && defined(WITH_AVS_COAP_STREAMING_API)             \\\n        && defined(WITH_AVS_COAP_OBSERVE)\n\n#    include <avsystem/commons/avs_errno.h>\n#    include <avsystem/commons/avs_stream_membuf.h>\n\n#    include <avsystem/coap/coap.h>\n\n#    define MODULE_NAME test\n#    include <avs_coap_x_log_config.h>\n\n#    include \"./utils.h\"\n\ntypedef struct {\n    test_env_t *env;\n\n    avs_coap_request_header_t expected_request_header;\n    const char *expected_request_data;\n    size_t expected_request_data_size;\n\n    avs_coap_response_header_t response_header;\n    const char *response_data;\n    size_t response_data_size;\n} streaming_handle_request_args_t;\n\nstatic void on_observe_cancel(avs_coap_observe_id_t id, void *env) {\n    assert_observe_state_change_expected((test_env_t *) env, OBSERVE_CANCEL,\n                                         id);\n}\n\nstatic int streaming_handle_request(avs_coap_streaming_request_ctx_t *ctx,\n                                    const avs_coap_request_header_t *request,\n                                    avs_stream_t *payload_stream,\n                                    const avs_coap_observe_id_t *observe_id,\n                                    void *args_) {\n    streaming_handle_request_args_t *args =\n            (streaming_handle_request_args_t *) args_;\n\n    (void) observe_id;\n\n    ASSERT_NOT_NULL(ctx);\n    ASSERT_NOT_NULL(request);\n    ASSERT_NOT_NULL(payload_stream);\n\n    ASSERT_EQ(request->code, args->expected_request_header.code);\n    ASSERT_EQ(request->options.size,\n              args->expected_request_header.options.size);\n    ASSERT_EQ_BYTES_SIZED(request->options.begin,\n                          args->expected_request_header.options.begin,\n                          request->options.size);\n\n    size_t offset = 0;\n    bool finished = false;\n    while (!finished) {\n        size_t bytes_read;\n        char buf[4096];\n\n        ASSERT_OK(avs_stream_read(payload_stream, &bytes_read, &finished, buf,\n                                  sizeof(buf)));\n        ASSERT_EQ_BYTES_SIZED(buf, args->expected_request_data + offset,\n                              bytes_read);\n\n        offset += bytes_read;\n    }\n\n    if (observe_id) {\n        ASSERT_OK(avs_coap_observe_streaming_start(\n                ctx, *observe_id, on_observe_cancel, args->env));\n    }\n\n    ASSERT_EQ(args->expected_request_data_size, offset);\n\n    avs_stream_t *response_stream =\n            avs_coap_streaming_setup_response(ctx, &args->response_header);\n    ASSERT_NOT_NULL(response_stream);\n    ASSERT_OK(avs_stream_write(response_stream, args->response_data,\n                               args->response_data_size));\n    return 0;\n}\n\nAVS_UNIT_TEST(udp_streaming_observe, start) {\n    test_env_t env __attribute__((cleanup(test_teardown_late_expects_check))) =\n            test_setup_default();\n\n    const test_msg_t *request =\n            COAP_MSG(CON, GET, ID(0), TOKEN(MAKE_TOKEN(\"Obserw\")), OBSERVE(0),\n                     NO_PAYLOAD);\n    // Note: Observe option values start at 0 (in a response to the initial\n    // Observe) and get incremented by one with each sent notification\n    const test_msg_t *response =\n            COAP_MSG(ACK, CONTENT, ID(0), TOKEN(MAKE_TOKEN(\"Obserw\")),\n                     OBSERVE(0), NO_PAYLOAD);\n\n    streaming_handle_request_args_t args = {\n        .env = &env,\n        .expected_request_header = request->request_header,\n        .response_header = {\n            .code = response->response_header.code\n        }\n    };\n\n    avs_unit_mocksock_enable_recv_timeout_getsetopt(\n            env.mocksock, avs_time_duration_from_scalar(1, AVS_TIME_S));\n\n    expect_recv(&env, request);\n    expect_send(&env, response);\n    expect_has_buffered_data_check(&env, false);\n\n    ASSERT_OK(avs_coap_streaming_handle_incoming_packet(\n            env.coap_ctx, streaming_handle_request, &args));\n\n    // should be canceled by cleanup\n    expect_observe_cancel(&env, MAKE_TOKEN(\"Obserw\"));\n}\n\nAVS_UNIT_TEST(udp_streaming_observe, notify) {\n#    define NOTIFY_PAYLOAD \"Notifaj\"\n    test_env_t env __attribute__((cleanup(test_teardown_late_expects_check))) =\n            test_setup_default();\n\n    const test_msg_t *request =\n            COAP_MSG(CON, GET, ID(100), TOKEN(MAKE_TOKEN(\"Obserw\")), OBSERVE(0),\n                     NO_PAYLOAD);\n    // Note: Observe option values start at 0 (in a response to the initial\n    // Observe) and get incremented by one with each sent notification\n    const test_msg_t *responses[] = {\n        COAP_MSG(ACK, CONTENT, ID(100), TOKEN(MAKE_TOKEN(\"Obserw\")), OBSERVE(0),\n                 NO_PAYLOAD),\n        COAP_MSG(NON, CONTENT, ID(0), TOKEN(MAKE_TOKEN(\"Obserw\")), OBSERVE(1),\n                 PAYLOAD(NOTIFY_PAYLOAD)),\n    };\n\n    streaming_handle_request_args_t args = {\n        .env = &env,\n        .expected_request_header = request->request_header,\n        .response_header = {\n            .code = responses[0]->response_header.code\n        }\n    };\n\n    avs_unit_mocksock_enable_recv_timeout_getsetopt(\n            env.mocksock, avs_time_duration_from_scalar(1, AVS_TIME_S));\n\n    expect_recv(&env, request);\n    expect_send(&env, responses[0]);\n    expect_has_buffered_data_check(&env, false);\n\n    ASSERT_OK(avs_coap_streaming_handle_incoming_packet(\n            env.coap_ctx, streaming_handle_request, &args));\n\n    avs_coap_observe_id_t observe_id = {\n        .token = request->msg.token\n    };\n    test_streaming_payload_t test_payload = {\n        .data = NOTIFY_PAYLOAD,\n        .size = sizeof(NOTIFY_PAYLOAD) - 1\n    };\n\n    expect_send(&env, responses[1]);\n\n    ASSERT_OK(avs_coap_notify_streaming(\n            env.coap_ctx, observe_id,\n            &(avs_coap_response_header_t) {\n                .code = responses[1]->response_header.code\n            },\n            AVS_COAP_NOTIFY_PREFER_NON_CONFIRMABLE, test_streaming_writer,\n            &test_payload));\n\n    // should be canceled by cleanup\n    expect_observe_cancel(&env, MAKE_TOKEN(\"Obserw\"));\n#    undef NOTIFY_PAYLOAD\n}\n\nAVS_UNIT_TEST(udp_streaming_observe, notify_confirmable) {\n#    define NOTIFY_PAYLOAD \"Notifaj\"\n    test_env_t env __attribute__((cleanup(test_teardown_late_expects_check))) =\n            test_setup_default();\n\n    const test_msg_t *requests[] = {\n        COAP_MSG(CON, GET, ID(100), TOKEN(MAKE_TOKEN(\"Obserw\")), OBSERVE(0),\n                 NO_PAYLOAD),\n        COAP_MSG(ACK, EMPTY, ID(0), NO_PAYLOAD),\n    };\n    // Note: Observe option values start at 0 (in a response to the initial\n    // Observe) and get incremented by one with each sent notification\n    const test_msg_t *responses[] = {\n        COAP_MSG(ACK, CONTENT, ID(100), TOKEN(MAKE_TOKEN(\"Obserw\")), OBSERVE(0),\n                 NO_PAYLOAD),\n        COAP_MSG(CON, CONTENT, ID(0), TOKEN(MAKE_TOKEN(\"Obserw\")), OBSERVE(1),\n                 PAYLOAD(NOTIFY_PAYLOAD)),\n    };\n\n    streaming_handle_request_args_t args = {\n        .env = &env,\n        .expected_request_header = requests[0]->request_header,\n        .response_header = {\n            .code = responses[0]->response_header.code\n        }\n    };\n\n    avs_unit_mocksock_enable_recv_timeout_getsetopt(\n            env.mocksock, avs_time_duration_from_scalar(1, AVS_TIME_S));\n\n    expect_recv(&env, requests[0]);\n    expect_send(&env, responses[0]);\n    expect_has_buffered_data_check(&env, false);\n\n    ASSERT_OK(avs_coap_streaming_handle_incoming_packet(\n            env.coap_ctx, streaming_handle_request, &args));\n\n    avs_coap_observe_id_t observe_id = {\n        .token = requests[0]->msg.token\n    };\n    test_streaming_payload_t test_payload = {\n        .data = NOTIFY_PAYLOAD,\n        .size = sizeof(NOTIFY_PAYLOAD) - 1\n    };\n\n    expect_send(&env, responses[1]);\n    expect_recv(&env, requests[1]);\n    expect_has_buffered_data_check(&env, false);\n\n    ASSERT_OK(avs_coap_notify_streaming(\n            env.coap_ctx, observe_id,\n            &(avs_coap_response_header_t) {\n                .code = responses[1]->response_header.code\n            },\n            AVS_COAP_NOTIFY_PREFER_CONFIRMABLE, test_streaming_writer,\n            &test_payload));\n\n    // should be canceled by cleanup\n    expect_observe_cancel(&env, MAKE_TOKEN(\"Obserw\"));\n#    undef NOTIFY_PAYLOAD\n}\n\nAVS_UNIT_TEST(udp_streaming_observe, notify_and_connection_refused) {\n#    define NOTIFY_PAYLOAD \"Notifaj\"\n    test_env_t env __attribute__((cleanup(test_teardown_late_expects_check))) =\n            test_setup_default();\n\n    const test_msg_t *requests[] = {\n        COAP_MSG(CON, GET, ID(100), TOKEN(MAKE_TOKEN(\"Obserw\")), OBSERVE(0),\n                 NO_PAYLOAD),\n    };\n    const test_msg_t *responses[] = {\n        COAP_MSG(ACK, CONTENT, ID(100), TOKEN(MAKE_TOKEN(\"Obserw\")), OBSERVE(0),\n                 NO_PAYLOAD),\n\n        // notify to which no response will be received\n        COAP_MSG(CON, CONTENT, ID(0), TOKEN(MAKE_TOKEN(\"Obserw\")), OBSERVE(1),\n                 PAYLOAD(NOTIFY_PAYLOAD)),\n    };\n\n    streaming_handle_request_args_t args = {\n        .env = &env,\n        .expected_request_header = requests[0]->request_header,\n        .response_header = {\n            .code = responses[0]->response_header.code\n        }\n    };\n\n    avs_unit_mocksock_enable_recv_timeout_getsetopt(\n            env.mocksock, avs_time_duration_from_scalar(1, AVS_TIME_S));\n\n    expect_recv(&env, requests[0]);\n    expect_send(&env, responses[0]);\n    expect_has_buffered_data_check(&env, false);\n\n    ASSERT_OK(avs_coap_streaming_handle_incoming_packet(\n            env.coap_ctx, streaming_handle_request, &args));\n\n    avs_coap_observe_id_t observe_id = {\n        .token = requests[0]->msg.token\n    };\n    test_streaming_payload_t test_payload = {\n        .data = NOTIFY_PAYLOAD,\n        .size = sizeof(NOTIFY_PAYLOAD) - 1\n    };\n\n    expect_send(&env, responses[1]);\n    avs_unit_mocksock_input_fail(env.mocksock, avs_errno(AVS_ECONNREFUSED));\n    expect_observe_cancel(&env, requests[0]->msg.token);\n\n    avs_error_t err = avs_coap_notify_streaming(\n            env.coap_ctx, observe_id,\n            &(avs_coap_response_header_t) {\n                .code = responses[1]->response_header.code\n            },\n            AVS_COAP_NOTIFY_PREFER_CONFIRMABLE, test_streaming_writer,\n            &test_payload);\n    ASSERT_EQ(err.category, AVS_ERRNO_CATEGORY);\n    ASSERT_EQ(err.code, AVS_ECONNREFUSED);\n\n#    undef NOTIFY_PAYLOAD\n}\n\n#    ifdef WITH_AVS_COAP_BLOCK\nAVS_UNIT_TEST(udp_streaming_observe, notify_block) {\n#        define NOTIFY_PAYLOAD DATA_1KB \"Notifaj\"\n    test_env_t env __attribute__((cleanup(test_teardown_late_expects_check))) =\n            test_setup_default();\n\n    const test_msg_t *requests[] = {\n        COAP_MSG(CON, GET, ID(100), TOKEN(MAKE_TOKEN(\"Obserw\")), OBSERVE(0),\n                 NO_PAYLOAD),\n\n        // request for second block of Notify\n        COAP_MSG(CON, GET, ID(101), TOKEN(MAKE_TOKEN(\"Notifaj\")),\n                 BLOCK2_REQ(1, 1024)),\n    };\n    const test_msg_t *responses[] = {\n        COAP_MSG(ACK, CONTENT, ID(100), TOKEN(MAKE_TOKEN(\"Obserw\")), OBSERVE(0),\n                 NO_PAYLOAD),\n\n        // BLOCK Notify\n        COAP_MSG(NON, CONTENT, ID(0), TOKEN(MAKE_TOKEN(\"Obserw\")), OBSERVE(1),\n                 BLOCK2_RES(0, 1024, NOTIFY_PAYLOAD)),\n        // Note: further blocks should not contain the Observe option\n        // see RFC 7959, Figure 12: \"Observe Sequence with Block-Wise Response\"\n        COAP_MSG(ACK, CONTENT, ID(101), TOKEN(MAKE_TOKEN(\"Notifaj\")),\n                 BLOCK2_RES(1, 1024, NOTIFY_PAYLOAD)),\n    };\n\n    streaming_handle_request_args_t args = {\n        .env = &env,\n        .expected_request_header = requests[0]->request_header,\n        .response_header = {\n            .code = responses[0]->response_header.code\n        }\n    };\n\n    avs_unit_mocksock_enable_recv_timeout_getsetopt(\n            env.mocksock, avs_time_duration_from_scalar(1, AVS_TIME_S));\n\n    expect_recv(&env, requests[0]);\n    expect_send(&env, responses[0]);\n    expect_has_buffered_data_check(&env, false);\n\n    ASSERT_OK(avs_coap_streaming_handle_incoming_packet(\n            env.coap_ctx, streaming_handle_request, &args));\n\n    avs_coap_observe_id_t observe_id = {\n        .token = requests[0]->msg.token\n    };\n    test_streaming_payload_t test_payload = {\n        .data = NOTIFY_PAYLOAD,\n        .size = sizeof(NOTIFY_PAYLOAD) - 1\n    };\n\n    expect_send(&env, responses[1]);\n    expect_recv(&env, requests[1]);\n    expect_send(&env, responses[2]);\n    expect_has_buffered_data_check(&env, false);\n\n    ASSERT_OK(avs_coap_notify_streaming(\n            env.coap_ctx, observe_id,\n            &(avs_coap_response_header_t) {\n                .code = responses[1]->response_header.code\n            },\n            AVS_COAP_NOTIFY_PREFER_NON_CONFIRMABLE, test_streaming_writer,\n            &test_payload));\n\n    // should be canceled by cleanup\n    expect_observe_cancel(&env, requests[0]->msg.token);\n#        undef NOTIFY_PAYLOAD\n}\n\nAVS_UNIT_TEST(udp_streaming_observe, notify_block_missing) {\n#        define NOTIFY_PAYLOAD DATA_1KB DATA_1KB \"Notifaj\"\n    test_env_t env __attribute__((cleanup(test_teardown_late_expects_check))) =\n            test_setup_default();\n\n    const test_msg_t *requests[] = {\n        COAP_MSG(CON, GET, ID(100), TOKEN(MAKE_TOKEN(\"Obserw\")), OBSERVE(0),\n                 NO_PAYLOAD),\n\n        // request for third block of Notify\n        COAP_MSG(CON, GET, ID(101), TOKEN(MAKE_TOKEN(\"Notifaj\")),\n                 BLOCK2_REQ(2, 1024)),\n    };\n    const test_msg_t *responses[] = {\n        COAP_MSG(ACK, CONTENT, ID(100), TOKEN(MAKE_TOKEN(\"Obserw\")), OBSERVE(0),\n                 NO_PAYLOAD),\n\n        // BLOCK Notify\n        COAP_MSG(NON, CONTENT, ID(0), TOKEN(MAKE_TOKEN(\"Obserw\")), OBSERVE(1),\n                 BLOCK2_RES(0, 1024, NOTIFY_PAYLOAD)),\n        COAP_MSG(ACK, SERVICE_UNAVAILABLE, ID(101),\n                 TOKEN(MAKE_TOKEN(\"Notifaj\")), NO_PAYLOAD),\n    };\n\n    streaming_handle_request_args_t args = {\n        .env = &env,\n        .expected_request_header = requests[0]->request_header,\n        .response_header = {\n            .code = responses[0]->response_header.code\n        }\n    };\n\n    avs_unit_mocksock_enable_recv_timeout_getsetopt(\n            env.mocksock, avs_time_duration_from_scalar(1, AVS_TIME_S));\n\n    expect_recv(&env, requests[0]);\n    expect_send(&env, responses[0]);\n    expect_has_buffered_data_check(&env, false);\n\n    ASSERT_OK(avs_coap_streaming_handle_incoming_packet(\n            env.coap_ctx, streaming_handle_request, &args));\n\n    avs_coap_observe_id_t observe_id = {\n        .token = requests[0]->msg.token\n    };\n    test_streaming_payload_t test_payload = {\n        .data = NOTIFY_PAYLOAD,\n        .size = sizeof(NOTIFY_PAYLOAD) - 1\n    };\n\n    expect_send(&env, responses[1]);\n    expect_recv(&env, requests[1]);\n    expect_send(&env, responses[2]);\n    avs_unit_mocksock_input_fail(env.mocksock, avs_errno(AVS_ETIMEDOUT),\n                                 .and_then = advance_mockclock,\n                                 .and_then_arg = &(avs_time_duration_t) {\n                                     .seconds = 300\n                                 });\n\n    ASSERT_FAIL(avs_coap_notify_streaming(\n            env.coap_ctx, observe_id,\n            &(avs_coap_response_header_t) {\n                .code = responses[1]->response_header.code\n            },\n            AVS_COAP_NOTIFY_PREFER_NON_CONFIRMABLE, test_streaming_writer,\n            &test_payload));\n\n    // should be canceled by cleanup\n    expect_observe_cancel(&env, requests[0]->msg.token);\n#        undef NOTIFY_PAYLOAD\n}\n\nAVS_UNIT_TEST(udp_streaming_observe, notify_block_send_fail) {\n#        define NOTIFY_PAYLOAD DATA_1KB DATA_1KB DATA_1KB \"Notifaj\"\n    avs_coap_udp_tx_params_t tx_params = AVS_COAP_DEFAULT_UDP_TX_PARAMS;\n    tx_params.nstart = 999;\n    test_env_t env __attribute__((cleanup(test_teardown_late_expects_check))) =\n            test_setup(&tx_params, 4096, 1200, NULL);\n\n    const test_msg_t *requests[] = {\n        COAP_MSG(CON, GET, ID(100), TOKEN(MAKE_TOKEN(\"Obserw\")), OBSERVE(0),\n                 NO_PAYLOAD),\n\n        // request for more blocks of Notify\n        COAP_MSG(CON, GET, ID(101), TOKEN(MAKE_TOKEN(\"Notifaj\")),\n                 BLOCK2_REQ(1, 1024)),\n        COAP_MSG(CON, GET, ID(102), TOKEN(MAKE_TOKEN(\"Notifaj\")),\n                 BLOCK2_REQ(2, 1024)),\n        COAP_MSG(CON, GET, ID(103), TOKEN(MAKE_TOKEN(\"Notifaj\")),\n                 BLOCK2_REQ(2, 1024)),\n    };\n    const test_msg_t *responses[] = {\n        COAP_MSG(ACK, CONTENT, ID(100), TOKEN(MAKE_TOKEN(\"Obserw\")), OBSERVE(0),\n                 NO_PAYLOAD),\n\n        // BLOCK Notify\n        COAP_MSG(NON, CONTENT, ID(0), TOKEN(MAKE_TOKEN(\"Obserw\")), OBSERVE(1),\n                 BLOCK2_RES(0, 1024, NOTIFY_PAYLOAD)),\n        // Note: further blocks should not contain the Observe option\n        // see RFC 7959, Figure 12: \"Observe Sequence with Block-Wise Response\"\n        COAP_MSG(ACK, CONTENT, ID(101), TOKEN(MAKE_TOKEN(\"Notifaj\")),\n                 BLOCK2_RES(1, 1024, NOTIFY_PAYLOAD)),\n        COAP_MSG(ACK, CONTENT, ID(102), TOKEN(MAKE_TOKEN(\"Notifaj\")),\n                 BLOCK2_RES(2, 1024, NOTIFY_PAYLOAD)),\n    };\n\n    streaming_handle_request_args_t args = {\n        .env = &env,\n        .expected_request_header = requests[0]->request_header,\n        .response_header = {\n            .code = responses[0]->response_header.code\n        }\n    };\n\n    avs_unit_mocksock_enable_recv_timeout_getsetopt(\n            env.mocksock, avs_time_duration_from_scalar(1, AVS_TIME_S));\n\n    expect_recv(&env, requests[0]);\n    expect_send(&env, responses[0]);\n    expect_has_buffered_data_check(&env, false);\n\n    ASSERT_OK(avs_coap_streaming_handle_incoming_packet(\n            env.coap_ctx, streaming_handle_request, &args));\n\n    avs_coap_observe_id_t observe_id = {\n        .token = requests[0]->msg.token\n    };\n    test_streaming_payload_t test_payload = {\n        .data = NOTIFY_PAYLOAD,\n        .size = sizeof(NOTIFY_PAYLOAD) - 1\n    };\n\n    expect_send(&env, responses[1]);\n    expect_recv(&env, requests[1]);\n    expect_send(&env, responses[2]);\n    expect_recv(&env, requests[2]);\n    avs_unit_mocksock_output_fail(env.mocksock, avs_errno(AVS_ENODEV));\n\n    avs_error_t err = avs_coap_notify_streaming(\n            env.coap_ctx, observe_id,\n            &(avs_coap_response_header_t) {\n                .code = responses[1]->response_header.code\n            },\n            AVS_COAP_NOTIFY_PREFER_NON_CONFIRMABLE, test_streaming_writer,\n            &test_payload);\n    ASSERT_EQ(err.category, AVS_ERRNO_CATEGORY);\n    ASSERT_EQ(err.code, AVS_ENODEV);\n\n    // should be canceled by cleanup\n    expect_observe_cancel(&env, requests[0]->msg.token);\n#        undef NOTIFY_PAYLOAD\n}\n\nAVS_UNIT_TEST(udp_streaming_observe, increasing_block_size) {\n#        define NOTIFY_PAYLOAD DATA_1KB \"Notifaj\"\n    avs_coap_udp_tx_params_t tx_params = AVS_COAP_DEFAULT_UDP_TX_PARAMS;\n    tx_params.nstart = 999;\n    test_env_t env __attribute__((cleanup(test_teardown_late_expects_check))) =\n            test_setup(&tx_params, 32, 4096, NULL);\n\n    const test_msg_t *requests[] = {\n        COAP_MSG(CON, GET, ID(100), TOKEN(MAKE_TOKEN(\"Obserw\")), OBSERVE(0),\n                 BLOCK2_REQ(0, 16)),\n\n        // request for further blocks of Notify\n        COAP_MSG(CON, GET, ID(101), TOKEN(MAKE_TOKEN(\"Notifaj\")),\n                 BLOCK2_REQ(1, 16)),\n        COAP_MSG(CON, GET, ID(102), TOKEN(MAKE_TOKEN(\"Notifaj\")),\n                 BLOCK2_REQ(1, 32)),\n        COAP_MSG(CON, GET, ID(103), TOKEN(MAKE_TOKEN(\"Notifaj\")),\n                 BLOCK2_REQ(1, 64)),\n        COAP_MSG(CON, GET, ID(104), TOKEN(MAKE_TOKEN(\"Notifaj\")),\n                 BLOCK2_REQ(1, 128)),\n        COAP_MSG(CON, GET, ID(105), TOKEN(MAKE_TOKEN(\"Notifaj\")),\n                 BLOCK2_REQ(1, 256)),\n        COAP_MSG(CON, GET, ID(106), TOKEN(MAKE_TOKEN(\"Notifaj\")),\n                 BLOCK2_REQ(1, 512)),\n        COAP_MSG(CON, GET, ID(107), TOKEN(MAKE_TOKEN(\"Notifaj\")),\n                 BLOCK2_REQ(1, 1024)),\n    };\n    const test_msg_t *responses[] = {\n        COAP_MSG(ACK, CONTENT, ID(100), TOKEN(MAKE_TOKEN(\"Obserw\")), OBSERVE(0),\n                 BLOCK2_RES(0, 16, \"\")),\n\n        // BLOCK Notify\n        COAP_MSG(NON, CONTENT, ID(0), TOKEN(MAKE_TOKEN(\"Obserw\")), OBSERVE(1),\n                 BLOCK2_RES(0, 16, NOTIFY_PAYLOAD)),\n        // Note: further blocks should not contain the Observe option\n        // see RFC 7959, Figure 12: \"Observe Sequence with Block-Wise Response\"\n        COAP_MSG(ACK, CONTENT, ID(101), TOKEN(MAKE_TOKEN(\"Notifaj\")),\n                 BLOCK2_RES(1, 16, NOTIFY_PAYLOAD)),\n        COAP_MSG(ACK, CONTENT, ID(102), TOKEN(MAKE_TOKEN(\"Notifaj\")),\n                 BLOCK2_RES(1, 32, NOTIFY_PAYLOAD)),\n        COAP_MSG(ACK, CONTENT, ID(103), TOKEN(MAKE_TOKEN(\"Notifaj\")),\n                 BLOCK2_RES(1, 64, NOTIFY_PAYLOAD)),\n        COAP_MSG(ACK, CONTENT, ID(104), TOKEN(MAKE_TOKEN(\"Notifaj\")),\n                 BLOCK2_RES(1, 128, NOTIFY_PAYLOAD)),\n        COAP_MSG(ACK, CONTENT, ID(105), TOKEN(MAKE_TOKEN(\"Notifaj\")),\n                 BLOCK2_RES(1, 256, NOTIFY_PAYLOAD)),\n        COAP_MSG(ACK, CONTENT, ID(106), TOKEN(MAKE_TOKEN(\"Notifaj\")),\n                 BLOCK2_RES(1, 512, NOTIFY_PAYLOAD)),\n        COAP_MSG(ACK, CONTENT, ID(107), TOKEN(MAKE_TOKEN(\"Notifaj\")),\n                 BLOCK2_RES(1, 1024, NOTIFY_PAYLOAD)),\n    };\n\n    streaming_handle_request_args_t args = {\n        .env = &env,\n        .expected_request_header = requests[0]->request_header,\n        .response_header = {\n            .code = responses[0]->response_header.code\n        }\n    };\n\n    avs_unit_mocksock_enable_recv_timeout_getsetopt(\n            env.mocksock, avs_time_duration_from_scalar(1, AVS_TIME_S));\n\n    expect_recv(&env, requests[0]);\n    expect_send(&env, responses[0]);\n    expect_has_buffered_data_check(&env, false);\n\n    ASSERT_OK(avs_coap_streaming_handle_incoming_packet(\n            env.coap_ctx, streaming_handle_request, &args));\n\n    avs_coap_observe_id_t observe_id = {\n        .token = requests[0]->msg.token\n    };\n    test_streaming_payload_t test_payload = {\n        .data = NOTIFY_PAYLOAD,\n        .size = sizeof(NOTIFY_PAYLOAD) - 1\n    };\n\n    for (size_t i = 1; i < AVS_ARRAY_SIZE(requests); ++i) {\n        expect_send(&env, responses[i]);\n        expect_recv(&env, requests[i]);\n    }\n    expect_send(&env, responses[AVS_ARRAY_SIZE(requests)]);\n    expect_has_buffered_data_check(&env, false);\n\n    ASSERT_OK(avs_coap_notify_streaming(\n            env.coap_ctx, observe_id,\n            &(avs_coap_response_header_t) {\n                .code = responses[1]->response_header.code\n            },\n            AVS_COAP_NOTIFY_PREFER_NON_CONFIRMABLE, test_streaming_writer,\n            &test_payload));\n\n    // should be canceled by cleanup\n    expect_observe_cancel(&env, requests[0]->msg.token);\n#        undef NOTIFY_PAYLOAD\n}\n\nAVS_UNIT_TEST(udp_streaming_observe, notify_block_confirmable) {\n#        define NOTIFY_PAYLOAD DATA_1KB \"Notifaj\"\n    test_env_t env __attribute__((cleanup(test_teardown_late_expects_check))) =\n            test_setup_default();\n\n    const test_msg_t *requests[] = {\n        COAP_MSG(CON, GET, ID(100), TOKEN(MAKE_TOKEN(\"Obserw\")), OBSERVE(0),\n                 NO_PAYLOAD),\n        COAP_MSG(ACK, EMPTY, ID(0), NO_PAYLOAD),\n\n        // request for second block of Notify\n        COAP_MSG(CON, GET, ID(101), TOKEN(MAKE_TOKEN(\"Notifaj\")),\n                 BLOCK2_REQ(1, 1024)),\n    };\n    const test_msg_t *responses[] = {\n        COAP_MSG(ACK, CONTENT, ID(100), TOKEN(MAKE_TOKEN(\"Obserw\")), OBSERVE(0),\n                 NO_PAYLOAD),\n\n        // BLOCK Notify\n        COAP_MSG(CON, CONTENT, ID(0), TOKEN(MAKE_TOKEN(\"Obserw\")), OBSERVE(1),\n                 BLOCK2_RES(0, 1024, NOTIFY_PAYLOAD)),\n        // Note: further blocks should not contain the Observe option\n        // see RFC 7959, Figure 12: \"Observe Sequence with Block-Wise Response\"\n        COAP_MSG(ACK, CONTENT, ID(101), TOKEN(MAKE_TOKEN(\"Notifaj\")),\n                 BLOCK2_RES(1, 1024, NOTIFY_PAYLOAD)),\n    };\n\n    streaming_handle_request_args_t args = {\n        .env = &env,\n        .expected_request_header = requests[0]->request_header,\n        .response_header = {\n            .code = responses[0]->response_header.code\n        }\n    };\n\n    avs_unit_mocksock_enable_recv_timeout_getsetopt(\n            env.mocksock, avs_time_duration_from_scalar(1, AVS_TIME_S));\n\n    expect_recv(&env, requests[0]);\n    expect_send(&env, responses[0]);\n    expect_has_buffered_data_check(&env, false);\n\n    ASSERT_OK(avs_coap_streaming_handle_incoming_packet(\n            env.coap_ctx, streaming_handle_request, &args));\n\n    avs_coap_observe_id_t observe_id = {\n        .token = requests[0]->msg.token\n    };\n    test_streaming_payload_t test_payload = {\n        .data = NOTIFY_PAYLOAD,\n        .size = sizeof(NOTIFY_PAYLOAD) - 1\n    };\n\n    expect_send(&env, responses[1]);\n    expect_recv(&env, requests[1]);\n    expect_recv(&env, requests[2]);\n    expect_send(&env, responses[2]);\n    expect_has_buffered_data_check(&env, false);\n\n    ASSERT_OK(avs_coap_notify_streaming(\n            env.coap_ctx, observe_id,\n            &(avs_coap_response_header_t) {\n                .code = responses[1]->response_header.code\n            },\n            AVS_COAP_NOTIFY_PREFER_CONFIRMABLE, test_streaming_writer,\n            &test_payload));\n\n    // should be canceled by cleanup\n    expect_observe_cancel(&env, requests[0]->msg.token);\n#        undef NOTIFY_PAYLOAD\n}\n\nAVS_UNIT_TEST(udp_streaming_observe, notify_block_confirmable_late_ack) {\n#        define NOTIFY_PAYLOAD DATA_1KB \"Notifaj\"\n    test_env_t env __attribute__((cleanup(test_teardown_late_expects_check))) =\n            test_setup_default();\n\n    const test_msg_t *requests[] = {\n        COAP_MSG(CON, GET, ID(100), TOKEN(MAKE_TOKEN(\"Obserw\")), OBSERVE(0),\n                 NO_PAYLOAD),\n        // request for second block of Notify\n        COAP_MSG(CON, GET, ID(101), TOKEN(MAKE_TOKEN(\"Notifaj\")),\n                 BLOCK2_REQ(1, 1024)),\n        // late ACK for the first block\n        COAP_MSG(ACK, EMPTY, ID(0), NO_PAYLOAD),\n    };\n    const test_msg_t *responses[] = {\n        COAP_MSG(ACK, CONTENT, ID(100), TOKEN(MAKE_TOKEN(\"Obserw\")), OBSERVE(0),\n                 NO_PAYLOAD),\n\n        // BLOCK Notify\n        COAP_MSG(CON, CONTENT, ID(0), TOKEN(MAKE_TOKEN(\"Obserw\")), OBSERVE(1),\n                 BLOCK2_RES(0, 1024, NOTIFY_PAYLOAD)),\n        // Note: further blocks should not contain the Observe option\n        // see RFC 7959, Figure 12: \"Observe Sequence with Block-Wise Response\"\n        COAP_MSG(ACK, CONTENT, ID(101), TOKEN(MAKE_TOKEN(\"Notifaj\")),\n                 BLOCK2_RES(1, 1024, NOTIFY_PAYLOAD)),\n    };\n\n    streaming_handle_request_args_t args = {\n        .env = &env,\n        .expected_request_header = requests[0]->request_header,\n        .response_header = {\n            .code = responses[0]->response_header.code\n        }\n    };\n\n    avs_unit_mocksock_enable_recv_timeout_getsetopt(\n            env.mocksock, avs_time_duration_from_scalar(1, AVS_TIME_S));\n\n    expect_recv(&env, requests[0]);\n    expect_send(&env, responses[0]);\n    expect_has_buffered_data_check(&env, false);\n\n    ASSERT_OK(avs_coap_streaming_handle_incoming_packet(\n            env.coap_ctx, streaming_handle_request, &args));\n\n    avs_coap_observe_id_t observe_id = {\n        .token = requests[0]->msg.token\n    };\n    test_streaming_payload_t test_payload = {\n        .data = NOTIFY_PAYLOAD,\n        .size = sizeof(NOTIFY_PAYLOAD) - 1\n    };\n\n    expect_send(&env, responses[1]);\n    expect_recv(&env, requests[1]);\n    expect_send(&env, responses[2]);\n    expect_recv(&env, requests[2]);\n    expect_has_buffered_data_check(&env, false);\n\n    ASSERT_OK(avs_coap_notify_streaming(\n            env.coap_ctx, observe_id,\n            &(avs_coap_response_header_t) {\n                .code = responses[1]->response_header.code\n            },\n            AVS_COAP_NOTIFY_PREFER_CONFIRMABLE, test_streaming_writer,\n            &test_payload));\n\n    // should be canceled by cleanup\n    expect_observe_cancel(&env, requests[0]->msg.token);\n#        undef NOTIFY_PAYLOAD\n}\n\nAVS_UNIT_TEST(udp_streaming_observe, notify_block_missing_confirmable) {\n#        define NOTIFY_PAYLOAD DATA_1KB DATA_1KB \"Notifaj\"\n    test_env_t env __attribute__((cleanup(test_teardown_late_expects_check))) =\n            test_setup_default();\n\n    const test_msg_t *requests[] = {\n        COAP_MSG(CON, GET, ID(100), TOKEN(MAKE_TOKEN(\"Obserw\")), OBSERVE(0),\n                 NO_PAYLOAD),\n        COAP_MSG(ACK, EMPTY, ID(0), NO_PAYLOAD),\n\n        // request for third block of Notify\n        COAP_MSG(CON, GET, ID(101), TOKEN(MAKE_TOKEN(\"Notifaj\")),\n                 BLOCK2_REQ(2, 1024)),\n    };\n    const test_msg_t *responses[] = {\n        COAP_MSG(ACK, CONTENT, ID(100), TOKEN(MAKE_TOKEN(\"Obserw\")), OBSERVE(0),\n                 NO_PAYLOAD),\n\n        // BLOCK Notify\n        COAP_MSG(CON, CONTENT, ID(0), TOKEN(MAKE_TOKEN(\"Obserw\")), OBSERVE(1),\n                 BLOCK2_RES(0, 1024, NOTIFY_PAYLOAD)),\n        // Note: Service Unavailable response is non-confirmable because it\n        // cannot be matched to the actual notification exchange\n        COAP_MSG(ACK, SERVICE_UNAVAILABLE, ID(101),\n                 TOKEN(MAKE_TOKEN(\"Notifaj\")), NO_PAYLOAD),\n    };\n\n    streaming_handle_request_args_t args = {\n        .env = &env,\n        .expected_request_header = requests[0]->request_header,\n        .response_header = {\n            .code = responses[0]->response_header.code\n        }\n    };\n\n    avs_unit_mocksock_enable_recv_timeout_getsetopt(\n            env.mocksock, avs_time_duration_from_scalar(1, AVS_TIME_S));\n\n    expect_recv(&env, requests[0]);\n    expect_send(&env, responses[0]);\n    expect_has_buffered_data_check(&env, false);\n\n    ASSERT_OK(avs_coap_streaming_handle_incoming_packet(\n            env.coap_ctx, streaming_handle_request, &args));\n\n    avs_coap_observe_id_t observe_id = {\n        .token = requests[0]->msg.token\n    };\n    test_streaming_payload_t test_payload = {\n        .data = NOTIFY_PAYLOAD,\n        .size = sizeof(NOTIFY_PAYLOAD) - 1\n    };\n\n    expect_send(&env, responses[1]);\n    expect_recv(&env, requests[1]);\n    expect_recv(&env, requests[2]);\n    expect_send(&env, responses[2]);\n    avs_unit_mocksock_input_fail(env.mocksock, avs_errno(AVS_ETIMEDOUT),\n                                 .and_then = advance_mockclock,\n                                 .and_then_arg = &(avs_time_duration_t) {\n                                     .seconds = 300\n                                 });\n\n    ASSERT_FAIL(avs_coap_notify_streaming(\n            env.coap_ctx, observe_id,\n            &(avs_coap_response_header_t) {\n                .code = responses[1]->response_header.code\n            },\n            AVS_COAP_NOTIFY_PREFER_CONFIRMABLE, test_streaming_writer,\n            &test_payload));\n\n    // should be canceled by cleanup\n    expect_observe_cancel(&env, requests[0]->msg.token);\n#        undef NOTIFY_PAYLOAD\n}\n\nAVS_UNIT_TEST(udp_streaming_observe, increasing_block_size_confirmable) {\n#        define NOTIFY_PAYLOAD DATA_1KB \"Notifaj\"\n    avs_coap_udp_tx_params_t tx_params = AVS_COAP_DEFAULT_UDP_TX_PARAMS;\n    tx_params.nstart = 999;\n    test_env_t env __attribute__((cleanup(test_teardown_late_expects_check))) =\n            test_setup(&tx_params, 32, 4096, NULL);\n\n    const test_msg_t *requests[] = {\n        COAP_MSG(CON, GET, ID(100), TOKEN(MAKE_TOKEN(\"Obserw\")), OBSERVE(0),\n                 BLOCK2_REQ(0, 16)),\n        COAP_MSG(ACK, EMPTY, ID(0), NO_PAYLOAD),\n\n        // requests and separate response ACKs for further blocks of Notify\n        COAP_MSG(CON, GET, ID(101), TOKEN(MAKE_TOKEN(\"Notifaj\")),\n                 BLOCK2_REQ(1, 16)),\n        COAP_MSG(CON, GET, ID(102), TOKEN(MAKE_TOKEN(\"Notifaj\")),\n                 BLOCK2_REQ(1, 32)),\n        COAP_MSG(CON, GET, ID(103), TOKEN(MAKE_TOKEN(\"Notifaj\")),\n                 BLOCK2_REQ(1, 64)),\n        COAP_MSG(CON, GET, ID(104), TOKEN(MAKE_TOKEN(\"Notifaj\")),\n                 BLOCK2_REQ(1, 128)),\n        COAP_MSG(CON, GET, ID(105), TOKEN(MAKE_TOKEN(\"Notifaj\")),\n                 BLOCK2_REQ(1, 256)),\n        COAP_MSG(CON, GET, ID(106), TOKEN(MAKE_TOKEN(\"Notifaj\")),\n                 BLOCK2_REQ(1, 512)),\n        COAP_MSG(CON, GET, ID(107), TOKEN(MAKE_TOKEN(\"Notifaj\")),\n                 BLOCK2_REQ(1, 1024)),\n    };\n    const test_msg_t *responses[] = {\n        COAP_MSG(ACK, CONTENT, ID(100), TOKEN(MAKE_TOKEN(\"Obserw\")), OBSERVE(0),\n                 BLOCK2_RES(0, 16, \"\")),\n\n        // BLOCK Notify\n        COAP_MSG(CON, CONTENT, ID(0), TOKEN(MAKE_TOKEN(\"Obserw\")), OBSERVE(1),\n                 BLOCK2_RES(0, 16, NOTIFY_PAYLOAD)),\n        // Note: further blocks should not contain the Observe option\n        // see RFC 7959, Figure 12: \"Observe Sequence with Block-Wise Response\"\n        COAP_MSG(ACK, CONTENT, ID(101), TOKEN(MAKE_TOKEN(\"Notifaj\")),\n                 BLOCK2_RES(1, 16, NOTIFY_PAYLOAD)),\n        COAP_MSG(ACK, CONTENT, ID(102), TOKEN(MAKE_TOKEN(\"Notifaj\")),\n                 BLOCK2_RES(1, 32, NOTIFY_PAYLOAD)),\n        COAP_MSG(ACK, CONTENT, ID(103), TOKEN(MAKE_TOKEN(\"Notifaj\")),\n                 BLOCK2_RES(1, 64, NOTIFY_PAYLOAD)),\n        COAP_MSG(ACK, CONTENT, ID(104), TOKEN(MAKE_TOKEN(\"Notifaj\")),\n                 BLOCK2_RES(1, 128, NOTIFY_PAYLOAD)),\n        COAP_MSG(ACK, CONTENT, ID(105), TOKEN(MAKE_TOKEN(\"Notifaj\")),\n                 BLOCK2_RES(1, 256, NOTIFY_PAYLOAD)),\n        COAP_MSG(ACK, CONTENT, ID(106), TOKEN(MAKE_TOKEN(\"Notifaj\")),\n                 BLOCK2_RES(1, 512, NOTIFY_PAYLOAD)),\n        COAP_MSG(ACK, CONTENT, ID(107), TOKEN(MAKE_TOKEN(\"Notifaj\")),\n                 BLOCK2_RES(1, 1024, NOTIFY_PAYLOAD)),\n    };\n\n    streaming_handle_request_args_t args = {\n        .env = &env,\n        .expected_request_header = requests[0]->request_header,\n        .response_header = {\n            .code = responses[0]->response_header.code\n        }\n    };\n\n    avs_unit_mocksock_enable_recv_timeout_getsetopt(\n            env.mocksock, avs_time_duration_from_scalar(1, AVS_TIME_S));\n\n    expect_recv(&env, requests[0]);\n    expect_send(&env, responses[0]);\n    expect_has_buffered_data_check(&env, false);\n\n    ASSERT_OK(avs_coap_streaming_handle_incoming_packet(\n            env.coap_ctx, streaming_handle_request, &args));\n\n    avs_coap_observe_id_t observe_id = {\n        .token = requests[0]->msg.token\n    };\n    test_streaming_payload_t test_payload = {\n        .data = NOTIFY_PAYLOAD,\n        .size = sizeof(NOTIFY_PAYLOAD) - 1\n    };\n\n    expect_send(&env, responses[1]);\n    expect_recv(&env, requests[1]);\n    for (size_t i = 2; i < AVS_ARRAY_SIZE(responses); ++i) {\n        expect_recv(&env, requests[i]);\n        expect_send(&env, responses[i]);\n    }\n    expect_has_buffered_data_check(&env, false);\n\n    ASSERT_OK(avs_coap_notify_streaming(\n            env.coap_ctx, observe_id,\n            &(avs_coap_response_header_t) {\n                .code = responses[1]->response_header.code\n            },\n            AVS_COAP_NOTIFY_PREFER_CONFIRMABLE, test_streaming_writer,\n            &test_payload));\n\n    // should be canceled by cleanup\n    expect_observe_cancel(&env, requests[0]->msg.token);\n#        undef NOTIFY_PAYLOAD\n}\n#    endif // WITH_AVS_COAP_BLOCK\n\n#    ifdef WITH_AVS_COAP_OBSERVE_PERSISTENCE\nAVS_UNIT_TEST(observe_persistence, simple) {\n#        define NOTIFY_PAYLOAD \"Notifaj\"\n    const test_msg_t *requests[] = {\n        COAP_MSG(CON, GET, ID(0), TOKEN(MAKE_TOKEN(\"Obserw\")), OBSERVE(0),\n                 NO_PAYLOAD),\n        COAP_MSG(ACK, EMPTY, ID(0), NO_PAYLOAD),\n    };\n    const test_msg_t *responses[] = {\n        COAP_MSG(ACK, CONTENT, ID(0), TOKEN(MAKE_TOKEN(\"Obserw\")), OBSERVE(0),\n                 NO_PAYLOAD),\n        COAP_MSG(CON, CONTENT, ID(0), TOKEN(MAKE_TOKEN(\"Obserw\")), OBSERVE(1),\n                 PAYLOAD(NOTIFY_PAYLOAD))\n    };\n    avs_coap_observe_id_t observe_id = {\n        .token = requests[0]->msg.token\n    };\n\n    avs_stream_t *stream = avs_stream_membuf_create();\n    ASSERT_NOT_NULL(stream);\n    {\n        test_env_t env\n                __attribute__((cleanup(test_teardown_late_expects_check))) =\n                        test_setup_default();\n\n        streaming_handle_request_args_t args = {\n            .env = &env,\n            .expected_request_header = requests[0]->request_header,\n            .response_header = responses[0]->response_header\n        };\n\n        avs_unit_mocksock_enable_recv_timeout_getsetopt(\n                env.mocksock, avs_time_duration_from_scalar(1, AVS_TIME_S));\n\n        expect_recv(&env, requests[0]);\n        expect_send(&env, responses[0]);\n        expect_has_buffered_data_check(&env, false);\n\n        ASSERT_OK(avs_coap_streaming_handle_incoming_packet(\n                env.coap_ctx, streaming_handle_request, &args));\n\n        avs_persistence_context_t persistence =\n                avs_persistence_store_context_create(stream);\n        ASSERT_OK(avs_coap_observe_persist(env.coap_ctx, observe_id,\n                                           &persistence));\n\n        // Canceled by cleanup.\n        expect_observe_cancel(&env, MAKE_TOKEN(\"Obserw\"));\n    }\n\n    {\n        test_env_t env\n                __attribute__((cleanup(test_teardown_late_expects_check))) =\n                        test_setup_without_socket(NULL, 1024, 1024, NULL);\n\n        avs_persistence_context_t persistence =\n                avs_persistence_restore_context_create(stream);\n        avs_coap_observe_id_t restored_id;\n        ASSERT_OK(avs_coap_observe_restore_with_id(env.coap_ctx,\n                                                   on_observe_cancel, &env,\n                                                   &restored_id, &persistence));\n        ASSERT_TRUE(\n                avs_coap_token_equal(&observe_id.token, &restored_id.token));\n\n        avs_net_socket_t *socket = NULL;\n        avs_unit_mocksock_create_datagram(&socket);\n        avs_unit_mocksock_enable_inner_mtu_getopt(socket, 1500);\n\n        avs_unit_mocksock_expect_connect(socket, NULL, NULL);\n        avs_net_socket_connect(socket, NULL, NULL);\n\n        ASSERT_OK(avs_coap_ctx_set_socket(env.coap_ctx, socket));\n        env.mocksock = socket;\n\n        test_streaming_payload_t test_payload = {\n            .data = NOTIFY_PAYLOAD,\n            .size = sizeof(NOTIFY_PAYLOAD) - 1\n        };\n\n        avs_unit_mocksock_enable_recv_timeout_getsetopt(\n                env.mocksock, avs_time_duration_from_scalar(1, AVS_TIME_S));\n\n        expect_send(&env, responses[1]);\n        expect_recv(&env, requests[1]);\n        expect_has_buffered_data_check(&env, false);\n        ASSERT_OK(avs_coap_notify_streaming(\n                env.coap_ctx, observe_id, &responses[1]->response_header,\n                AVS_COAP_NOTIFY_PREFER_CONFIRMABLE, test_streaming_writer,\n                &test_payload));\n        // Canceled by cleanup.\n        expect_observe_cancel(&env, MAKE_TOKEN(\"Obserw\"));\n    }\n    avs_stream_cleanup(&stream);\n#        undef NOTIFY_PAYLOAD\n}\n#    endif // WITH_AVS_COAP_OBSERVE_PERSISTENCE\n\n#endif // defined(AVS_UNIT_TESTING) && defined(WITH_AVS_COAP_UDP) &&\n       // defined(WITH_AVS_COAP_STREAMING_API) && defined(WITH_AVS_COAP_OBSERVE)\n"
  },
  {
    "path": "deps/avs_coap/tests/udp/streaming_server.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <avs_coap_init.h>\n\n#if defined(AVS_UNIT_TESTING) && defined(WITH_AVS_COAP_UDP) \\\n        && defined(WITH_AVS_COAP_STREAMING_API)\n\n#    include <avsystem/coap/coap.h>\n\n#    define MODULE_NAME test\n#    include <avs_coap_x_log_config.h>\n\n#    include \"./utils.h\"\n\ntypedef struct {\n    avs_coap_request_header_t expected_request_header;\n    const char *expected_request_data;\n    size_t expected_request_data_size;\n    bool ignore_overlong_request;\n    bool expect_failure;\n    bool use_peek;\n\n    avs_coap_response_header_t response_header;\n    const char *response_data;\n    size_t response_data_size;\n} streaming_handle_request_args_t;\n\nstatic int streaming_handle_request(avs_coap_streaming_request_ctx_t *ctx,\n                                    const avs_coap_request_header_t *request,\n                                    avs_stream_t *payload_stream,\n                                    const avs_coap_observe_id_t *observe_id,\n                                    void *args_) {\n    streaming_handle_request_args_t *args =\n            (streaming_handle_request_args_t *) args_;\n\n    (void) observe_id;\n\n    ASSERT_NOT_NULL(ctx);\n    ASSERT_NOT_NULL(request);\n    ASSERT_NOT_NULL(payload_stream);\n\n    ASSERT_EQ(request->code, args->expected_request_header.code);\n    ASSERT_EQ(request->options.size,\n              args->expected_request_header.options.size);\n    ASSERT_EQ_BYTES_SIZED(request->options.begin,\n                          args->expected_request_header.options.begin,\n                          request->options.size);\n\n    size_t offset = 0;\n    bool finished = false;\n    while (!finished) {\n        size_t bytes_read;\n        unsigned char buf[4096];\n        size_t buf_size = sizeof(buf);\n        if (args->ignore_overlong_request) {\n            buf_size = AVS_MIN(buf_size,\n                               args->expected_request_data_size - offset);\n            if (!buf_size) {\n                break;\n            }\n        }\n\n        char ch;\n        avs_error_t peek_err;\n        if (args->use_peek) {\n            peek_err = avs_stream_peek(payload_stream, 0, &ch);\n        }\n        avs_error_t err = avs_stream_read(payload_stream, &bytes_read,\n                                          &finished, buf, buf_size);\n        if (!args->expect_failure) {\n            ASSERT_OK(err);\n        } else if (avs_is_err(err)) {\n            if (args->use_peek) {\n                ASSERT_FAIL(peek_err);\n                ASSERT_FALSE(avs_is_eof(peek_err));\n            }\n            return -1;\n        }\n\n        ASSERT_EQ_BYTES_SIZED(buf, args->expected_request_data + offset,\n                              bytes_read);\n        if (args->use_peek) {\n            ASSERT_EQ(ch, bytes_read ? buf[0] : EOF);\n            if (ch == EOF) {\n                ASSERT_TRUE(finished);\n            }\n        }\n\n        offset += bytes_read;\n    }\n    ASSERT_FALSE(args->expect_failure);\n\n    ASSERT_EQ(args->expected_request_data_size, offset);\n\n    avs_stream_t *response_stream =\n            avs_coap_streaming_setup_response(ctx, &args->response_header);\n    if (avs_coap_code_is_response(args->response_header.code)) {\n        ASSERT_NOT_NULL(response_stream);\n        if (args->response_data_size) {\n            ASSERT_OK(avs_stream_write(response_stream, args->response_data,\n                                       args->response_data_size));\n        }\n        return 0;\n    } else {\n        ASSERT_NULL(response_stream);\n        return -1;\n    }\n}\n\nAVS_UNIT_TEST(udp_streaming_server, no_payload) {\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_default();\n\n    const test_msg_t *request = COAP_MSG(CON, GET, ID(0), TOKEN(nth_token(0)));\n    const test_msg_t *response =\n            COAP_MSG(ACK, CONTENT, ID(0), TOKEN(nth_token(0)));\n\n    streaming_handle_request_args_t args = {\n        .expected_request_header = request->request_header,\n        .response_header = {\n            .code = response->response_header.code\n        },\n    };\n\n    avs_unit_mocksock_enable_recv_timeout_getsetopt(\n            env.mocksock, avs_time_duration_from_scalar(1, AVS_TIME_S));\n\n    expect_recv(&env, request);\n    expect_send(&env, response);\n    expect_has_buffered_data_check(&env, false);\n\n    ASSERT_OK(avs_coap_streaming_handle_incoming_packet(\n            env.coap_ctx, streaming_handle_request, &args));\n}\n\nAVS_UNIT_TEST(udp_streaming_server, small_payload) {\n#    define REQUEST_PAYLOAD \"Actually,\"\n#    define RESPONSE_PAYLOAD \"fish\"\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_default();\n\n    const test_msg_t *request = COAP_MSG(CON, PUT, ID(0), TOKEN(nth_token(0)),\n                                         PAYLOAD(REQUEST_PAYLOAD));\n    const test_msg_t *response =\n            COAP_MSG(ACK, CONTENT, ID(0), TOKEN(nth_token(0)),\n                     PAYLOAD(RESPONSE_PAYLOAD));\n\n    streaming_handle_request_args_t args = {\n        .expected_request_header = request->request_header,\n        .expected_request_data = REQUEST_PAYLOAD,\n        .expected_request_data_size = sizeof(REQUEST_PAYLOAD) - 1,\n        .response_header = {\n            .code = response->response_header.code\n        },\n        .response_data = RESPONSE_PAYLOAD,\n        .response_data_size = sizeof(RESPONSE_PAYLOAD) - 1\n    };\n\n    avs_unit_mocksock_enable_recv_timeout_getsetopt(\n            env.mocksock, avs_time_duration_from_scalar(1, AVS_TIME_S));\n\n    expect_recv(&env, request);\n    expect_send(&env, response);\n    expect_has_buffered_data_check(&env, false);\n\n    ASSERT_OK(avs_coap_streaming_handle_incoming_packet(\n            env.coap_ctx, streaming_handle_request, &args));\n#    undef REQUEST_PAYLOAD\n#    undef RESPONSE_PAYLOAD\n}\n\n#    ifdef WITH_AVS_COAP_BLOCK\n\nAVS_UNIT_TEST(udp_streaming_server, large_payload) {\n#        define REQUEST_PAYLOAD DATA_1KB \"?\"\n#        define RESPONSE_PAYLOAD DATA_1KB \"!\"\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_default();\n\n    const test_msg_t *requests[] = {\n        COAP_MSG(CON, PUT, ID(0), TOKEN(nth_token(0)),\n                 BLOCK1_REQ(0, 1024, REQUEST_PAYLOAD)),\n        COAP_MSG(CON, PUT, ID(1), TOKEN(nth_token(1)),\n                 BLOCK1_REQ(1, 1024, REQUEST_PAYLOAD)),\n        COAP_MSG(CON, PUT, ID(2), TOKEN(nth_token(2)), BLOCK2_REQ(1, 1024)),\n    };\n    const test_msg_t *responses[] = {\n        COAP_MSG(ACK, CONTINUE, ID(0), TOKEN(nth_token(0)),\n                 BLOCK1_RES(0, 1024, true)),\n        COAP_MSG(ACK, CONTENT, ID(1), TOKEN(nth_token(1)),\n                 BLOCK1_AND_2_RES(1, 1024, 1024, RESPONSE_PAYLOAD)),\n        COAP_MSG(ACK, CONTENT, ID(2), TOKEN(nth_token(2)),\n                 BLOCK2_RES(1, 1024, RESPONSE_PAYLOAD)),\n    };\n\n    streaming_handle_request_args_t args = {\n        // NOTE: user handler is given the first BLOCK1 request header\n        .expected_request_header = requests[0]->request_header,\n        .expected_request_data = REQUEST_PAYLOAD,\n        .expected_request_data_size = sizeof(REQUEST_PAYLOAD) - 1,\n        .response_header = {\n            .code = responses[1]->response_header.code\n        },\n        .response_data = RESPONSE_PAYLOAD,\n        .response_data_size = sizeof(RESPONSE_PAYLOAD) - 1\n    };\n\n    avs_unit_mocksock_enable_recv_timeout_getsetopt(\n            env.mocksock, avs_time_duration_from_scalar(1, AVS_TIME_S));\n\n    AVS_STATIC_ASSERT(AVS_ARRAY_SIZE(requests) == AVS_ARRAY_SIZE(responses),\n                      mismatched_request_response_count);\n    for (size_t i = 0; i < AVS_ARRAY_SIZE(requests); ++i) {\n        expect_recv(&env, requests[i]);\n        expect_send(&env, responses[i]);\n    }\n    expect_has_buffered_data_check(&env, false);\n\n    ASSERT_OK(avs_coap_streaming_handle_incoming_packet(\n            env.coap_ctx, streaming_handle_request, &args));\n#        undef REQUEST_PAYLOAD\n#        undef RESPONSE_PAYLOAD\n}\n\nAVS_UNIT_TEST(udp_streaming_server, invalid_block1_req) {\n#        define REQUEST_PAYLOAD DATA_1KB \"?\"\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_default();\n\n    const test_msg_t *request = COAP_MSG(CON, PUT, ID(0), TOKEN(nth_token(0)),\n                                         BLOCK1_REQ(1, 1024, REQUEST_PAYLOAD));\n    const test_msg_t *response =\n            COAP_MSG(ACK, REQUEST_ENTITY_INCOMPLETE, ID(0), TOKEN(nth_token(0)),\n                     BLOCK1_RES(1, 1024, false));\n\n    streaming_handle_request_args_t args = {\n        // NOTE: user handler is given the first BLOCK1 request header\n        .expected_request_header = request->request_header,\n        .expected_request_data = REQUEST_PAYLOAD,\n        .expected_request_data_size = sizeof(REQUEST_PAYLOAD) - 1,\n        .response_header = {\n            .code = response->response_header.code\n        }\n    };\n\n    avs_unit_mocksock_enable_recv_timeout_getsetopt(\n            env.mocksock, avs_time_duration_from_scalar(1, AVS_TIME_S));\n\n    expect_recv(&env, request);\n    expect_send(&env, response);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_streaming_handle_incoming_packet(\n            env.coap_ctx, streaming_handle_request, &args));\n#        undef REQUEST_PAYLOAD\n}\n\nAVS_UNIT_TEST(udp_streaming_server, missing_block1_req) {\n#        define REQUEST_PAYLOAD DATA_1KB DATA_1KB \"?\"\n#        define RESPONSE_PAYLOAD DATA_1KB DATA_1KB \"!\"\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_default();\n\n    const test_msg_t *requests[] = {\n        COAP_MSG(CON, PUT, ID(0), TOKEN(nth_token(0)),\n                 BLOCK1_REQ(0, 1024, REQUEST_PAYLOAD)),\n        COAP_MSG(CON, PUT, ID(1), TOKEN(nth_token(1)),\n                 BLOCK1_REQ(2, 1024, REQUEST_PAYLOAD)),\n    };\n    const test_msg_t *responses[] = {\n        COAP_MSG(ACK, CONTINUE, ID(0), TOKEN(nth_token(0)),\n                 BLOCK1_RES(0, 1024, true)),\n        COAP_MSG(ACK, SERVICE_UNAVAILABLE, ID(1), TOKEN(nth_token(1)),\n                 NO_PAYLOAD),\n    };\n\n    streaming_handle_request_args_t args = {\n        // NOTE: user handler is given the first BLOCK1 request header\n        .expected_request_header = requests[0]->request_header,\n        .expected_request_data = REQUEST_PAYLOAD,\n        .expected_request_data_size = sizeof(REQUEST_PAYLOAD) - 1,\n        .expect_failure = true,\n        .response_header = {\n            .code = responses[1]->response_header.code\n        },\n        .response_data = RESPONSE_PAYLOAD,\n        .response_data_size = sizeof(RESPONSE_PAYLOAD) - 1\n    };\n\n    avs_unit_mocksock_enable_recv_timeout_getsetopt(\n            env.mocksock, avs_time_duration_from_scalar(1, AVS_TIME_S));\n\n    AVS_STATIC_ASSERT(AVS_ARRAY_SIZE(requests) == AVS_ARRAY_SIZE(responses),\n                      mismatched_request_response_count);\n    for (size_t i = 0; i < AVS_ARRAY_SIZE(requests); ++i) {\n        expect_recv(&env, requests[i]);\n        expect_send(&env, responses[i]);\n    }\n    avs_unit_mocksock_input_fail(env.mocksock, avs_errno(AVS_ETIMEDOUT),\n                                 .and_then = advance_mockclock,\n                                 .and_then_arg = &(avs_time_duration_t) {\n                                     .seconds = 300\n                                 });\n\n    ASSERT_FAIL(avs_coap_streaming_handle_incoming_packet(\n            env.coap_ctx, streaming_handle_request, &args));\n#        undef REQUEST_PAYLOAD\n#        undef RESPONSE_PAYLOAD\n}\n\nAVS_UNIT_TEST(udp_streaming_server, invalid_block2_req) {\n#        define RESPONSE_PAYLOAD DATA_1KB \"!\"\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_default();\n\n    const test_msg_t *request =\n            COAP_MSG(CON, PUT, ID(0), TOKEN(nth_token(0)), BLOCK2_REQ(1, 1024));\n    const test_msg_t *response =\n            COAP_MSG(ACK, SERVICE_UNAVAILABLE, ID(0), TOKEN(nth_token(0)),\n                     BLOCK2_REQ(1, 1024));\n\n    streaming_handle_request_args_t args = {\n        .expected_request_header = request->request_header,\n        .response_header = {\n            .code = response->response_header.code\n        },\n        .response_data = RESPONSE_PAYLOAD,\n        .response_data_size = sizeof(RESPONSE_PAYLOAD) - 1\n    };\n\n    avs_unit_mocksock_enable_recv_timeout_getsetopt(\n            env.mocksock, avs_time_duration_from_scalar(1, AVS_TIME_S));\n\n    expect_recv(&env, request);\n    expect_send(&env, response);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_streaming_handle_incoming_packet(\n            env.coap_ctx, streaming_handle_request, &args));\n#        undef RESPONSE_PAYLOAD\n}\n\nAVS_UNIT_TEST(udp_streaming_server, invalid_block2_req_after_block1) {\n#        define REQUEST_PAYLOAD DATA_1KB \"?\"\n#        define RESPONSE_PAYLOAD DATA_1KB \"!\"\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_default();\n\n    const test_msg_t *requests[] = {\n        COAP_MSG(CON, PUT, ID(0), TOKEN(nth_token(0)),\n                 BLOCK1_REQ(0, 1024, REQUEST_PAYLOAD)),\n        COAP_MSG(CON, PUT, ID(1), TOKEN(nth_token(1)),\n                 .block1 = {\n                     .type = AVS_COAP_BLOCK1,\n                     .seq_num = 1,\n                     .size = 1024,\n                     .has_more = false\n                 },\n                 .block2 = {\n                     .type = AVS_COAP_BLOCK2,\n                     .seq_num = 1,\n                     .size = 1024,\n                     .has_more = false\n                 },\n                 .payload = REQUEST_PAYLOAD + 1024,\n                 .payload_size = sizeof(REQUEST_PAYLOAD) - 1 - 1024),\n    };\n    const test_msg_t *responses[] = {\n        COAP_MSG(ACK, CONTINUE, ID(0), TOKEN(nth_token(0)),\n                 BLOCK1_RES(0, 1024, true)),\n        COAP_MSG(ACK, SERVICE_UNAVAILABLE, ID(1), TOKEN(nth_token(1)),\n                 .block1 = {\n                     .type = AVS_COAP_BLOCK1,\n                     .seq_num = 1,\n                     .size = 1024,\n                     .has_more = false\n                 },\n                 .block2 = {\n                     .type = AVS_COAP_BLOCK2,\n                     .seq_num = 1,\n                     .size = 1024,\n                     .has_more = false\n                 }),\n    };\n\n    streaming_handle_request_args_t args = {\n        // NOTE: user handler is given the first BLOCK1 request header\n        .expected_request_header = requests[0]->request_header,\n        .expected_request_data = REQUEST_PAYLOAD,\n        .expected_request_data_size = sizeof(REQUEST_PAYLOAD) - 1,\n        .expect_failure = true,\n        .response_header = {\n            .code = responses[1]->response_header.code\n        },\n        .response_data = RESPONSE_PAYLOAD,\n        .response_data_size = sizeof(RESPONSE_PAYLOAD) - 1\n    };\n\n    avs_unit_mocksock_enable_recv_timeout_getsetopt(\n            env.mocksock, avs_time_duration_from_scalar(1, AVS_TIME_S));\n\n    AVS_STATIC_ASSERT(AVS_ARRAY_SIZE(requests) == AVS_ARRAY_SIZE(responses),\n                      mismatched_request_response_count);\n    for (size_t i = 0; i < AVS_ARRAY_SIZE(requests); ++i) {\n        expect_recv(&env, requests[i]);\n        expect_send(&env, responses[i]);\n    }\n    expect_has_buffered_data_check(&env, false);\n\n    ASSERT_OK(avs_coap_streaming_handle_incoming_packet(\n            env.coap_ctx, streaming_handle_request, &args));\n#        undef REQUEST_PAYLOAD\n#        undef RESPONSE_PAYLOAD\n}\n\nAVS_UNIT_TEST(udp_streaming_server, missing_block2_req) {\n#        define RESPONSE_PAYLOAD DATA_1KB DATA_1KB \"!\"\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_default();\n\n    const test_msg_t *requests[] = {\n        COAP_MSG(CON, PUT, ID(0), TOKEN(nth_token(0)), NO_PAYLOAD),\n        COAP_MSG(CON, PUT, ID(1), TOKEN(nth_token(1)), BLOCK2_REQ(2, 1024)),\n    };\n    const test_msg_t *responses[] = {\n        COAP_MSG(ACK, CONTENT, ID(0), TOKEN(nth_token(0)),\n                 BLOCK2_RES(0, 1024, RESPONSE_PAYLOAD)),\n        COAP_MSG(ACK, SERVICE_UNAVAILABLE, ID(1), TOKEN(nth_token(1)),\n                 NO_PAYLOAD),\n    };\n\n    streaming_handle_request_args_t args = {\n        // NOTE: user handler is given the first BLOCK1 request header\n        .expected_request_header = requests[0]->request_header,\n        .response_header = {\n            .code = responses[0]->response_header.code\n        },\n        .response_data = RESPONSE_PAYLOAD,\n        .response_data_size = sizeof(RESPONSE_PAYLOAD) - 1\n    };\n\n    avs_unit_mocksock_enable_recv_timeout_getsetopt(\n            env.mocksock, avs_time_duration_from_scalar(1, AVS_TIME_S));\n\n    AVS_STATIC_ASSERT(AVS_ARRAY_SIZE(requests) == AVS_ARRAY_SIZE(responses),\n                      mismatched_request_response_count);\n    for (size_t i = 0; i < AVS_ARRAY_SIZE(requests); ++i) {\n        expect_recv(&env, requests[i]);\n        expect_send(&env, responses[i]);\n    }\n    avs_unit_mocksock_input_fail(env.mocksock, avs_errno(AVS_ETIMEDOUT),\n                                 .and_then = advance_mockclock,\n                                 .and_then_arg = &(avs_time_duration_t) {\n                                     .seconds = 300\n                                 });\n\n    ASSERT_FAIL(avs_coap_streaming_handle_incoming_packet(\n            env.coap_ctx, streaming_handle_request, &args));\n#        undef RESPONSE_PAYLOAD\n}\n\nAVS_UNIT_TEST(udp_streaming_server, weird_block_sizes) {\n#        define REQUEST_PAYLOAD DATA_1KB \"?\"\n#        define RESPONSE_PAYLOAD DATA_1KB \"!\"\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_default();\n\n    const test_msg_t *requests[] = {\n        COAP_MSG(CON, PUT, ID(0), TOKEN(nth_token(0)),\n                 BLOCK1_REQ(0, 512, REQUEST_PAYLOAD)),\n        COAP_MSG(CON, PUT, ID(1), TOKEN(nth_token(1)),\n                 BLOCK1_REQ(2, 256, REQUEST_PAYLOAD)),\n        COAP_MSG(CON, PUT, ID(2), TOKEN(nth_token(2)),\n                 BLOCK1_REQ(3, 256, REQUEST_PAYLOAD)),\n        COAP_MSG(CON, PUT, ID(3), TOKEN(nth_token(3)),\n                 BLOCK1_REQ_AND_2_RES(2, 512, 512, REQUEST_PAYLOAD)),\n        COAP_MSG(CON, PUT, ID(4), TOKEN(nth_token(4)), BLOCK2_REQ(2, 256)),\n        COAP_MSG(CON, PUT, ID(5), TOKEN(nth_token(5)), BLOCK2_REQ(3, 256)),\n        COAP_MSG(CON, PUT, ID(6), TOKEN(nth_token(6)), BLOCK2_REQ(2, 512)),\n    };\n    const test_msg_t *responses[] = {\n        COAP_MSG(ACK, CONTINUE, ID(0), TOKEN(nth_token(0)),\n                 BLOCK1_RES(0, 512, true)),\n        COAP_MSG(ACK, CONTINUE, ID(1), TOKEN(nth_token(1)),\n                 BLOCK1_RES(2, 256, true)),\n        COAP_MSG(ACK, CONTINUE, ID(2), TOKEN(nth_token(2)),\n                 BLOCK1_RES(3, 256, true)),\n        COAP_MSG(ACK, CONTENT, ID(3), TOKEN(nth_token(3)),\n                 BLOCK1_AND_2_RES(2, 512, 512, RESPONSE_PAYLOAD)),\n        COAP_MSG(ACK, CONTENT, ID(4), TOKEN(nth_token(4)),\n                 BLOCK2_RES(2, 256, RESPONSE_PAYLOAD)),\n        COAP_MSG(ACK, CONTENT, ID(5), TOKEN(nth_token(5)),\n                 BLOCK2_RES(3, 256, RESPONSE_PAYLOAD)),\n        COAP_MSG(ACK, CONTENT, ID(6), TOKEN(nth_token(6)),\n                 BLOCK2_RES(2, 512, RESPONSE_PAYLOAD)),\n    };\n\n    streaming_handle_request_args_t args = {\n        // NOTE: user handler is given the first BLOCK1 request header\n        .expected_request_header = requests[0]->request_header,\n        .expected_request_data = REQUEST_PAYLOAD,\n        .expected_request_data_size = sizeof(REQUEST_PAYLOAD) - 1,\n        .response_header = {\n            .code = responses[3]->response_header.code\n        },\n        .response_data = RESPONSE_PAYLOAD,\n        .response_data_size = sizeof(RESPONSE_PAYLOAD) - 1\n    };\n\n    avs_unit_mocksock_enable_recv_timeout_getsetopt(\n            env.mocksock, avs_time_duration_from_scalar(1, AVS_TIME_S));\n\n    AVS_STATIC_ASSERT(AVS_ARRAY_SIZE(requests) == AVS_ARRAY_SIZE(responses),\n                      mismatched_request_response_count);\n    for (size_t i = 0; i < AVS_ARRAY_SIZE(requests); ++i) {\n        expect_recv(&env, requests[i]);\n        expect_send(&env, responses[i]);\n    }\n    expect_has_buffered_data_check(&env, false);\n\n    ASSERT_OK(avs_coap_streaming_handle_incoming_packet(\n            env.coap_ctx, streaming_handle_request, &args));\n#        undef REQUEST_PAYLOAD\n#        undef RESPONSE_PAYLOAD\n}\n\nAVS_UNIT_TEST(udp_streaming_server, weird_block_sizes_peek) {\n#        define REQUEST_PAYLOAD DATA_1KB \"?\"\n#        define RESPONSE_PAYLOAD DATA_1KB \"!\"\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_default();\n\n    const test_msg_t *requests[] = {\n        COAP_MSG(CON, PUT, ID(0), TOKEN(nth_token(0)),\n                 BLOCK1_REQ(0, 512, REQUEST_PAYLOAD)),\n        COAP_MSG(CON, PUT, ID(1), TOKEN(nth_token(1)),\n                 BLOCK1_REQ(2, 256, REQUEST_PAYLOAD)),\n        COAP_MSG(CON, PUT, ID(2), TOKEN(nth_token(2)),\n                 BLOCK1_REQ(3, 256, REQUEST_PAYLOAD)),\n        COAP_MSG(CON, PUT, ID(3), TOKEN(nth_token(3)),\n                 BLOCK1_REQ_AND_2_RES(2, 512, 512, REQUEST_PAYLOAD)),\n        COAP_MSG(CON, PUT, ID(4), TOKEN(nth_token(4)), BLOCK2_REQ(2, 256)),\n        COAP_MSG(CON, PUT, ID(5), TOKEN(nth_token(5)), BLOCK2_REQ(3, 256)),\n        COAP_MSG(CON, PUT, ID(6), TOKEN(nth_token(6)), BLOCK2_REQ(2, 512)),\n    };\n    const test_msg_t *responses[] = {\n        COAP_MSG(ACK, CONTINUE, ID(0), TOKEN(nth_token(0)),\n                 BLOCK1_RES(0, 512, true)),\n        COAP_MSG(ACK, CONTINUE, ID(1), TOKEN(nth_token(1)),\n                 BLOCK1_RES(2, 256, true)),\n        COAP_MSG(ACK, CONTINUE, ID(2), TOKEN(nth_token(2)),\n                 BLOCK1_RES(3, 256, true)),\n        COAP_MSG(ACK, CONTENT, ID(3), TOKEN(nth_token(3)),\n                 BLOCK1_AND_2_RES(2, 512, 512, RESPONSE_PAYLOAD)),\n        COAP_MSG(ACK, CONTENT, ID(4), TOKEN(nth_token(4)),\n                 BLOCK2_RES(2, 256, RESPONSE_PAYLOAD)),\n        COAP_MSG(ACK, CONTENT, ID(5), TOKEN(nth_token(5)),\n                 BLOCK2_RES(3, 256, RESPONSE_PAYLOAD)),\n        COAP_MSG(ACK, CONTENT, ID(6), TOKEN(nth_token(6)),\n                 BLOCK2_RES(2, 512, RESPONSE_PAYLOAD)),\n    };\n\n    streaming_handle_request_args_t args = {\n        // NOTE: user handler is given the first BLOCK1 request header\n        .expected_request_header = requests[0]->request_header,\n        .expected_request_data = REQUEST_PAYLOAD,\n        .expected_request_data_size = sizeof(REQUEST_PAYLOAD) - 1,\n        .use_peek = true,\n        .response_header = {\n            .code = responses[3]->response_header.code\n        },\n        .response_data = RESPONSE_PAYLOAD,\n        .response_data_size = sizeof(RESPONSE_PAYLOAD) - 1\n    };\n\n    avs_unit_mocksock_enable_recv_timeout_getsetopt(\n            env.mocksock, avs_time_duration_from_scalar(1, AVS_TIME_S));\n\n    AVS_STATIC_ASSERT(AVS_ARRAY_SIZE(requests) == AVS_ARRAY_SIZE(responses),\n                      mismatched_request_response_count);\n    for (size_t i = 0; i < AVS_ARRAY_SIZE(requests); ++i) {\n        expect_recv(&env, requests[i]);\n        expect_send(&env, responses[i]);\n    }\n    expect_has_buffered_data_check(&env, false);\n\n    ASSERT_OK(avs_coap_streaming_handle_incoming_packet(\n            env.coap_ctx, streaming_handle_request, &args));\n#        undef REQUEST_PAYLOAD\n#        undef RESPONSE_PAYLOAD\n}\n\nAVS_UNIT_TEST(udp_streaming_server, increasing_block2_size) {\n#        define RESPONSE_PAYLOAD DATA_1KB \"!\"\n    avs_coap_udp_tx_params_t tx_params = AVS_COAP_DEFAULT_UDP_TX_PARAMS;\n    tx_params.nstart = 999;\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup(&tx_params, 16, 4096, NULL);\n\n    const test_msg_t *requests[] = {\n        COAP_MSG(CON, GET, ID(0), TOKEN(nth_token(0)), BLOCK2_REQ(0, 16)),\n        COAP_MSG(CON, GET, ID(1), TOKEN(nth_token(1)), BLOCK2_REQ(1, 16)),\n        COAP_MSG(CON, GET, ID(2), TOKEN(nth_token(2)), BLOCK2_REQ(1, 32)),\n        COAP_MSG(CON, GET, ID(3), TOKEN(nth_token(3)), BLOCK2_REQ(1, 64)),\n        COAP_MSG(CON, GET, ID(4), TOKEN(nth_token(4)), BLOCK2_REQ(1, 128)),\n        COAP_MSG(CON, GET, ID(5), TOKEN(nth_token(5)), BLOCK2_REQ(1, 256)),\n        COAP_MSG(CON, GET, ID(6), TOKEN(nth_token(6)), BLOCK2_REQ(1, 512)),\n        COAP_MSG(CON, GET, ID(7), TOKEN(nth_token(7)), BLOCK2_REQ(1, 1024)),\n    };\n    const test_msg_t *responses[] = {\n        COAP_MSG(ACK, CONTENT, ID(0), TOKEN(nth_token(0)),\n                 BLOCK2_RES(0, 16, RESPONSE_PAYLOAD)),\n        COAP_MSG(ACK, CONTENT, ID(1), TOKEN(nth_token(1)),\n                 BLOCK2_RES(1, 16, RESPONSE_PAYLOAD)),\n        COAP_MSG(ACK, CONTENT, ID(2), TOKEN(nth_token(2)),\n                 BLOCK2_RES(1, 32, RESPONSE_PAYLOAD)),\n        COAP_MSG(ACK, CONTENT, ID(3), TOKEN(nth_token(3)),\n                 BLOCK2_RES(1, 64, RESPONSE_PAYLOAD)),\n        COAP_MSG(ACK, CONTENT, ID(4), TOKEN(nth_token(4)),\n                 BLOCK2_RES(1, 128, RESPONSE_PAYLOAD)),\n        COAP_MSG(ACK, CONTENT, ID(5), TOKEN(nth_token(5)),\n                 BLOCK2_RES(1, 256, RESPONSE_PAYLOAD)),\n        COAP_MSG(ACK, CONTENT, ID(6), TOKEN(nth_token(6)),\n                 BLOCK2_RES(1, 512, RESPONSE_PAYLOAD)),\n        COAP_MSG(ACK, CONTENT, ID(7), TOKEN(nth_token(7)),\n                 BLOCK2_RES(1, 1024, RESPONSE_PAYLOAD)),\n    };\n\n    streaming_handle_request_args_t args = {\n        // NOTE: user handler is given the first BLOCK1 request header\n        .expected_request_header = requests[0]->request_header,\n        .response_header = {\n            .code = responses[0]->response_header.code\n        },\n        .response_data = RESPONSE_PAYLOAD,\n        .response_data_size = sizeof(RESPONSE_PAYLOAD) - 1\n    };\n\n    avs_unit_mocksock_enable_recv_timeout_getsetopt(\n            env.mocksock, avs_time_duration_from_scalar(1, AVS_TIME_S));\n\n    AVS_STATIC_ASSERT(AVS_ARRAY_SIZE(requests) == AVS_ARRAY_SIZE(responses),\n                      mismatched_request_response_count);\n    for (size_t i = 0; i < AVS_ARRAY_SIZE(requests); ++i) {\n        expect_recv(&env, requests[i]);\n        expect_send(&env, responses[i]);\n    }\n    expect_has_buffered_data_check(&env, false);\n\n    ASSERT_OK(avs_coap_streaming_handle_incoming_packet(\n            env.coap_ctx, streaming_handle_request, &args));\n#        undef RESPONSE_PAYLOAD\n}\n\nAVS_UNIT_TEST(udp_streaming_server, setup_response_error) {\n#        define REQUEST_PAYLOAD DATA_1KB \"?\"\n#        define RESPONSE_PAYLOAD DATA_1KB \"!\"\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_default();\n\n    const test_msg_t *requests[] = {\n        COAP_MSG(CON, PUT, ID(0), TOKEN(nth_token(0)),\n                 BLOCK1_REQ(0, 1024, REQUEST_PAYLOAD)),\n        COAP_MSG(CON, PUT, ID(1), TOKEN(nth_token(1)),\n                 BLOCK1_REQ(1, 1024, REQUEST_PAYLOAD))\n    };\n    const test_msg_t *responses[] = { COAP_MSG(ACK, CONTINUE, ID(0),\n                                               TOKEN(nth_token(0)),\n                                               BLOCK1_RES(0, 1024, true)),\n                                      COAP_MSG(ACK, INTERNAL_SERVER_ERROR,\n                                               ID(1), TOKEN(nth_token(1)),\n                                               BLOCK1_RES(1, 1024, false)) };\n\n    streaming_handle_request_args_t args = {\n        // NOTE: user handler is given the first BLOCK1 request header\n        .expected_request_header = requests[0]->request_header,\n        .expected_request_data = REQUEST_PAYLOAD,\n        .expected_request_data_size = sizeof(REQUEST_PAYLOAD) - 1,\n        .response_data = RESPONSE_PAYLOAD,\n        .response_data_size = sizeof(RESPONSE_PAYLOAD) - 1\n    };\n\n    avs_unit_mocksock_enable_recv_timeout_getsetopt(\n            env.mocksock, avs_time_duration_from_scalar(1, AVS_TIME_S));\n\n    AVS_STATIC_ASSERT(AVS_ARRAY_SIZE(requests) == AVS_ARRAY_SIZE(responses),\n                      mismatched_request_response_count);\n    for (size_t i = 0; i < AVS_ARRAY_SIZE(requests); ++i) {\n        expect_recv(&env, requests[i]);\n        expect_send(&env, responses[i]);\n    }\n    expect_has_buffered_data_check(&env, false);\n\n    ASSERT_OK(avs_coap_streaming_handle_incoming_packet(\n            env.coap_ctx, streaming_handle_request, &args));\n#        undef REQUEST_PAYLOAD\n#        undef RESPONSE_PAYLOAD\n}\n\nAVS_UNIT_TEST(udp_streaming_server, large_payload_ignored) {\n#        define REQUEST_PAYLOAD DATA_1KB \"?\"\n#        define RESPONSE_PAYLOAD DATA_1KB \"!\"\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_default();\n\n    const test_msg_t *requests[] = {\n        COAP_MSG(CON, PUT, ID(0), TOKEN(nth_token(0)),\n                 BLOCK1_REQ(0, 1024, REQUEST_PAYLOAD)),\n        COAP_MSG(CON, PUT, ID(1), TOKEN(nth_token(1)), BLOCK2_REQ(1, 1024)),\n    };\n    const test_msg_t *responses[] = {\n        COAP_MSG(ACK, CONTENT, ID(0), TOKEN(nth_token(0)),\n                 BLOCK1_AND_2_RES(0, 1024, 1024, RESPONSE_PAYLOAD)),\n        COAP_MSG(ACK, CONTENT, ID(1), TOKEN(nth_token(1)),\n                 BLOCK2_RES(1, 1024, RESPONSE_PAYLOAD)),\n    };\n\n    streaming_handle_request_args_t args = {\n        // NOTE: user handler is given the first BLOCK1 request header\n        .expected_request_header = requests[0]->request_header,\n        .expected_request_data = REQUEST_PAYLOAD,\n        .expected_request_data_size = 100,\n        .ignore_overlong_request = true,\n        .response_header = {\n            .code = responses[1]->response_header.code\n        },\n        .response_data = RESPONSE_PAYLOAD,\n        .response_data_size = sizeof(RESPONSE_PAYLOAD) - 1\n    };\n\n    avs_unit_mocksock_enable_recv_timeout_getsetopt(\n            env.mocksock, avs_time_duration_from_scalar(1, AVS_TIME_S));\n\n    AVS_STATIC_ASSERT(AVS_ARRAY_SIZE(requests) == AVS_ARRAY_SIZE(responses),\n                      mismatched_request_response_count);\n    for (size_t i = 0; i < AVS_ARRAY_SIZE(requests); ++i) {\n        expect_recv(&env, requests[i]);\n        expect_send(&env, responses[i]);\n    }\n    expect_has_buffered_data_check(&env, false);\n\n    ASSERT_OK(avs_coap_streaming_handle_incoming_packet(\n            env.coap_ctx, streaming_handle_request, &args));\n#        undef REQUEST_PAYLOAD\n#        undef RESPONSE_PAYLOAD\n}\n\nAVS_UNIT_TEST(udp_streaming_server, incorrect_block2_in_block1_request) {\n#        define REQUEST_PAYLOAD DATA_1KB\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_default();\n\n    const test_msg_t *requests[] = {\n        COAP_MSG(CON, PUT, ID(0), TOKEN(nth_token(0)),\n                 BLOCK1_REQ(0, 256, REQUEST_PAYLOAD)),\n        COAP_MSG(CON, PUT, ID(1), TOKEN(nth_token(1)),\n                 BLOCK1_REQ_AND_2_RES(1, 256, 32, REQUEST_PAYLOAD)),\n        COAP_MSG(CON, PUT, ID(2), TOKEN(nth_token(2)),\n                 BLOCK1_REQ(1, 256, REQUEST_PAYLOAD)),\n        COAP_MSG(CON, PUT, ID(3), TOKEN(nth_token(3)),\n                 BLOCK1_REQ(2, 256, REQUEST_PAYLOAD)),\n        COAP_MSG(CON, PUT, ID(4), TOKEN(nth_token(4)),\n                 BLOCK1_REQ(3, 256, REQUEST_PAYLOAD))\n    };\n    const test_msg_t *responses[] = {\n        COAP_MSG(ACK, CONTINUE, ID(0), TOKEN(nth_token(0)),\n                 BLOCK1_RES(0, 256, true)),\n        COAP_MSG(ACK, BAD_OPTION, ID(1), TOKEN(nth_token(1))),\n        COAP_MSG(ACK, CONTINUE, ID(2), TOKEN(nth_token(2)),\n                 BLOCK1_RES(1, 256, true)),\n        COAP_MSG(ACK, CONTINUE, ID(3), TOKEN(nth_token(3)),\n                 BLOCK1_RES(2, 256, true)),\n        COAP_MSG(ACK, CHANGED, ID(4), TOKEN(nth_token(4)),\n                 BLOCK1_RES(3, 256, false))\n    };\n\n    streaming_handle_request_args_t args = {\n        // NOTE: user handler is given the first BLOCK1 request header\n        .expected_request_header = requests[0]->request_header,\n        .expected_request_data = REQUEST_PAYLOAD,\n        .expected_request_data_size = sizeof(REQUEST_PAYLOAD) - 1,\n        .response_header = {\n            .code = responses[4]->request_header.code\n        },\n    };\n\n    avs_unit_mocksock_enable_recv_timeout_getsetopt(\n            env.mocksock, avs_time_duration_from_scalar(1, AVS_TIME_S));\n\n    AVS_STATIC_ASSERT(AVS_ARRAY_SIZE(requests) == AVS_ARRAY_SIZE(responses),\n                      mismatched_request_response_count);\n    for (size_t i = 0; i < AVS_ARRAY_SIZE(requests); ++i) {\n        expect_recv(&env, requests[i]);\n        expect_send(&env, responses[i]);\n    }\n    expect_has_buffered_data_check(&env, false);\n\n    ASSERT_OK(avs_coap_streaming_handle_incoming_packet(\n            env.coap_ctx, streaming_handle_request, &args));\n\n#        undef REQUEST_PAYLOAD\n}\n\nAVS_UNIT_TEST(udp_streaming_server, block1_receive_timed_out) {\n#        define REQUEST_PAYLOAD DATA_1KB\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_deterministic();\n\n    const test_msg_t *request = { COAP_MSG(CON, PUT, ID(0), TOKEN(nth_token(0)),\n                                           BLOCK1_REQ(0, 16,\n                                                      REQUEST_PAYLOAD)) };\n    const test_msg_t *response = { COAP_MSG(ACK, CONTINUE, ID(0),\n                                            TOKEN(nth_token(0)),\n                                            BLOCK1_RES(0, 16, true)) };\n\n    streaming_handle_request_args_t args = {\n        // NOTE: user handler is given the first BLOCK1 request header\n        .expected_request_header = request->request_header,\n        .expected_request_data = REQUEST_PAYLOAD,\n        .expected_request_data_size = sizeof(REQUEST_PAYLOAD) - 1,\n        .expect_failure = true,\n        .response_header = {\n            .code = AVS_COAP_CODE_CREATED\n        },\n    };\n\n    avs_unit_mocksock_enable_recv_timeout_getsetopt(\n            env.mocksock, avs_time_duration_from_scalar(1, AVS_TIME_S));\n\n    expect_recv(&env, request);\n    expect_send(&env, response);\n    avs_unit_mocksock_input_fail(env.mocksock, avs_errno(AVS_ETIMEDOUT),\n                                 .and_then = advance_mockclock,\n                                 .and_then_arg = &(avs_time_duration_t) {\n                                     .seconds = 300\n                                 });\n\n    ASSERT_FAIL(avs_coap_streaming_handle_incoming_packet(\n            env.coap_ctx, streaming_handle_request, &args));\n\n#        undef REQUEST_PAYLOAD\n}\n\nAVS_UNIT_TEST(udp_streaming_server, block2_receive_timed_out) {\n#        define RESPONSE_PAYLOAD DATA_1KB \"?\"\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_deterministic();\n\n    const test_msg_t *request = { COAP_MSG(CON, GET, ID(0),\n                                           TOKEN(nth_token(0))) };\n    const test_msg_t *response = {\n        COAP_MSG(ACK, CONTENT, ID(0), TOKEN(nth_token(0)),\n                 BLOCK2_RES(0, 1024, RESPONSE_PAYLOAD))\n    };\n\n    streaming_handle_request_args_t args = {\n        // NOTE: user handler is given the first BLOCK1 request header\n        .expected_request_header = request->request_header,\n        .response_header = {\n            .code = AVS_COAP_CODE_CONTENT\n        },\n        .response_data = RESPONSE_PAYLOAD,\n        .response_data_size = sizeof(RESPONSE_PAYLOAD) - 1,\n    };\n\n    avs_unit_mocksock_enable_recv_timeout_getsetopt(\n            env.mocksock, avs_time_duration_from_scalar(1, AVS_TIME_S));\n\n    expect_recv(&env, request);\n    expect_send(&env, response);\n    avs_unit_mocksock_input_fail(env.mocksock, avs_errno(AVS_ETIMEDOUT),\n                                 .and_then = advance_mockclock,\n                                 .and_then_arg = &(avs_time_duration_t) {\n                                     .seconds = 300\n                                 });\n\n    ASSERT_FAIL(avs_coap_streaming_handle_incoming_packet(\n            env.coap_ctx, streaming_handle_request, &args));\n\n#        undef RESPONSE_PAYLOAD\n}\n\nAVS_UNIT_TEST(udp_streaming_server, connection_closed) {\n#        define REQUEST_PAYLOAD DATA_1KB\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_deterministic();\n\n    const test_msg_t *request = { COAP_MSG(CON, PUT, ID(0), TOKEN(nth_token(0)),\n                                           BLOCK1_REQ(0, 16,\n                                                      REQUEST_PAYLOAD)) };\n    const test_msg_t *response = { COAP_MSG(ACK, CONTINUE, ID(0),\n                                            TOKEN(nth_token(0)),\n                                            BLOCK1_RES(0, 16, true)) };\n\n    streaming_handle_request_args_t args = {\n        // NOTE: user handler is given the first BLOCK1 request header\n        .expected_request_header = request->request_header,\n        .expected_request_data = REQUEST_PAYLOAD,\n        .expected_request_data_size = sizeof(REQUEST_PAYLOAD) - 1,\n        .expect_failure = true,\n        .response_header = {\n            .code = AVS_COAP_CODE_CREATED\n        },\n    };\n\n    avs_unit_mocksock_enable_recv_timeout_getsetopt(\n            env.mocksock, avs_time_duration_from_scalar(1, AVS_TIME_S));\n\n    expect_recv(&env, request);\n    expect_send(&env, response);\n    avs_unit_mocksock_input_fail(env.mocksock, avs_errno(AVS_ECONNREFUSED));\n\n    ASSERT_FAIL(avs_coap_streaming_handle_incoming_packet(\n            env.coap_ctx, streaming_handle_request, &args));\n\n#        undef REQUEST_PAYLOAD\n}\n\nAVS_UNIT_TEST(udp_streaming_server, connection_closed_peek) {\n#        define REQUEST_PAYLOAD DATA_1KB\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_deterministic();\n\n    const test_msg_t *request = { COAP_MSG(CON, PUT, ID(0), TOKEN(nth_token(0)),\n                                           BLOCK1_REQ(0, 16,\n                                                      REQUEST_PAYLOAD)) };\n    const test_msg_t *response = { COAP_MSG(ACK, CONTINUE, ID(0),\n                                            TOKEN(nth_token(0)),\n                                            BLOCK1_RES(0, 16, true)) };\n\n    streaming_handle_request_args_t args = {\n        // NOTE: user handler is given the first BLOCK1 request header\n        .expected_request_header = request->request_header,\n        .expected_request_data = REQUEST_PAYLOAD,\n        .expected_request_data_size = sizeof(REQUEST_PAYLOAD) - 1,\n        .expect_failure = true,\n        .use_peek = true,\n        .response_header = {\n            .code = AVS_COAP_CODE_CREATED\n        },\n    };\n\n    avs_unit_mocksock_enable_recv_timeout_getsetopt(\n            env.mocksock, avs_time_duration_from_scalar(1, AVS_TIME_S));\n\n    expect_recv(&env, request);\n    expect_send(&env, response);\n    avs_unit_mocksock_input_fail(env.mocksock, avs_errno(AVS_ECONNREFUSED));\n\n    ASSERT_FAIL(avs_coap_streaming_handle_incoming_packet(\n            env.coap_ctx, streaming_handle_request, &args));\n\n#        undef REQUEST_PAYLOAD\n}\n\nstatic int broken_handle_request(avs_coap_streaming_request_ctx_t *ctx,\n                                 const avs_coap_request_header_t *request,\n                                 avs_stream_t *payload_stream,\n                                 const avs_coap_observe_id_t *observe_id,\n                                 void *args_) {\n    (void) ctx;\n    (void) request;\n    (void) observe_id;\n    (void) args_;\n    bool finished = false;\n    avs_error_t err = AVS_OK;\n    while (!finished && avs_is_ok(err)) {\n        size_t bytes_read;\n        unsigned char buf[4096];\n        err = avs_stream_read(payload_stream, &bytes_read, &finished, buf,\n                              sizeof(buf));\n    }\n    ASSERT_EQ(err.category, AVS_ERRNO_CATEGORY);\n    ASSERT_EQ(err.code, AVS_ENODEV);\n    // Intentionally return success even though we're not really that successful\n    return 0;\n}\n\nAVS_UNIT_TEST(udp_streaming_server, connection_closed_send_broken_handler) {\n#        define REQUEST_PAYLOAD DATA_1KB \"?\"\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_default();\n\n    const test_msg_t *request = COAP_MSG(CON, PUT, ID(0), TOKEN(nth_token(0)),\n                                         BLOCK1_REQ(0, 1024, REQUEST_PAYLOAD));\n\n    avs_unit_mocksock_enable_recv_timeout_getsetopt(\n            env.mocksock, avs_time_duration_from_scalar(1, AVS_TIME_S));\n\n    expect_recv(&env, request);\n    avs_unit_mocksock_output_fail(env.mocksock, avs_errno(AVS_ENODEV));\n\n    avs_error_t err = avs_coap_streaming_handle_incoming_packet(\n            env.coap_ctx, broken_handle_request, NULL);\n    ASSERT_EQ(err.category, AVS_ERRNO_CATEGORY);\n    ASSERT_EQ(err.code, AVS_ENODEV);\n#        undef REQUEST_PAYLOAD\n}\n\nAVS_UNIT_TEST(udp_streaming_server,\n              connection_closed_block_send_broken_handler) {\n#        define REQUEST_PAYLOAD DATA_1KB DATA_1KB \"?\"\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_default();\n\n    const test_msg_t *requests[] = {\n        COAP_MSG(CON, PUT, ID(0), TOKEN(nth_token(0)),\n                 BLOCK1_REQ(0, 1024, REQUEST_PAYLOAD)),\n        COAP_MSG(CON, PUT, ID(1), TOKEN(nth_token(1)),\n                 BLOCK1_REQ(1, 1024, REQUEST_PAYLOAD))\n    };\n    const test_msg_t *responses[] = { COAP_MSG(ACK, CONTINUE, ID(0),\n                                               TOKEN(nth_token(0)),\n                                               BLOCK1_RES(0, 1024, true)) };\n\n    avs_unit_mocksock_enable_recv_timeout_getsetopt(\n            env.mocksock, avs_time_duration_from_scalar(1, AVS_TIME_S));\n\n    expect_recv(&env, requests[0]);\n    expect_send(&env, responses[0]);\n    expect_recv(&env, requests[1]);\n    avs_unit_mocksock_output_fail(env.mocksock, avs_errno(AVS_ENODEV));\n\n    avs_error_t err = avs_coap_streaming_handle_incoming_packet(\n            env.coap_ctx, broken_handle_request, NULL);\n    ASSERT_EQ(err.category, AVS_ERRNO_CATEGORY);\n    ASSERT_EQ(err.code, AVS_ENODEV);\n#        undef REQUEST_PAYLOAD\n}\n\nstatic int broken_write_handle_request(avs_coap_streaming_request_ctx_t *ctx,\n                                       const avs_coap_request_header_t *request,\n                                       avs_stream_t *payload_stream,\n                                       const avs_coap_observe_id_t *observe_id,\n                                       void *payload) {\n    (void) ctx;\n    (void) request;\n    (void) observe_id;\n    bool finished = false;\n    while (!finished) {\n        size_t bytes_read;\n        unsigned char buf[4096];\n        ASSERT_OK(avs_stream_read(payload_stream, &bytes_read, &finished, buf,\n                                  sizeof(buf)));\n    }\n    avs_stream_t *response_stream = avs_coap_streaming_setup_response(\n            ctx, &(const avs_coap_response_header_t) {\n                     .code = AVS_COAP_CODE_CONTENT\n                 });\n    ASSERT_NOT_NULL(response_stream);\n    avs_stream_write(response_stream, payload, strlen((const char *) payload));\n    // Intentionally ignore return value of the above\n    return 0;\n}\n\nAVS_UNIT_TEST(udp_streaming_server,\n              connection_closed_block_send_broken_write_handler) {\n#        define REQUEST_PAYLOAD DATA_1KB \"?\"\n#        define RESPONSE_PAYLOAD DATA_1KB \"!\"\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_default();\n\n    const test_msg_t *requests[] = {\n        COAP_MSG(CON, PUT, ID(0), TOKEN(nth_token(0)),\n                 BLOCK1_REQ(0, 1024, REQUEST_PAYLOAD)),\n        COAP_MSG(CON, PUT, ID(1), TOKEN(nth_token(1)),\n                 BLOCK1_REQ(1, 1024, REQUEST_PAYLOAD)),\n        COAP_MSG(CON, PUT, ID(2), TOKEN(nth_token(2)), BLOCK2_REQ(1, 1024)),\n    };\n    const test_msg_t *responses[] = {\n        COAP_MSG(ACK, CONTINUE, ID(0), TOKEN(nth_token(0)),\n                 BLOCK1_RES(0, 1024, true)),\n        COAP_MSG(ACK, CONTENT, ID(1), TOKEN(nth_token(1)),\n                 BLOCK1_AND_2_RES(1, 1024, 1024, RESPONSE_PAYLOAD))\n    };\n\n    avs_unit_mocksock_enable_recv_timeout_getsetopt(\n            env.mocksock, avs_time_duration_from_scalar(1, AVS_TIME_S));\n\n    expect_recv(&env, requests[0]);\n    expect_send(&env, responses[0]);\n    expect_recv(&env, requests[1]);\n    expect_send(&env, responses[1]);\n    expect_recv(&env, requests[2]);\n    avs_unit_mocksock_output_fail(env.mocksock, avs_errno(AVS_ENODEV));\n\n    avs_error_t err = avs_coap_streaming_handle_incoming_packet(\n            env.coap_ctx, broken_write_handle_request, RESPONSE_PAYLOAD);\n    ASSERT_EQ(err.category, AVS_ERRNO_CATEGORY);\n    ASSERT_EQ(err.code, AVS_ENODEV);\n#        undef REQUEST_PAYLOAD\n#        undef RESPONSE_PAYLOAD\n}\n\nAVS_UNIT_TEST(udp_streaming_server,\n              connection_closed_block_send_broken_bigger_write_handler) {\n#        define REQUEST_PAYLOAD DATA_1KB \"?\"\n#        define RESPONSE_PAYLOAD DATA_1KB DATA_1KB DATA_1KB \"!\"\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_default();\n\n    const test_msg_t *requests[] = {\n        COAP_MSG(CON, PUT, ID(0), TOKEN(nth_token(0)),\n                 BLOCK1_REQ(0, 1024, REQUEST_PAYLOAD)),\n        COAP_MSG(CON, PUT, ID(1), TOKEN(nth_token(1)),\n                 BLOCK1_REQ(1, 1024, REQUEST_PAYLOAD)),\n        COAP_MSG(CON, PUT, ID(2), TOKEN(nth_token(2)), BLOCK2_REQ(1, 1024)),\n        COAP_MSG(CON, PUT, ID(3), TOKEN(nth_token(3)), BLOCK2_REQ(2, 1024))\n    };\n    const test_msg_t *responses[] = {\n        COAP_MSG(ACK, CONTINUE, ID(0), TOKEN(nth_token(0)),\n                 BLOCK1_RES(0, 1024, true)),\n        COAP_MSG(ACK, CONTENT, ID(1), TOKEN(nth_token(1)),\n                 BLOCK1_AND_2_RES(1, 1024, 1024, RESPONSE_PAYLOAD)),\n        COAP_MSG(ACK, CONTENT, ID(2), TOKEN(nth_token(2)),\n                 BLOCK2_RES(1, 1024, RESPONSE_PAYLOAD)),\n    };\n\n    avs_unit_mocksock_enable_recv_timeout_getsetopt(\n            env.mocksock, avs_time_duration_from_scalar(1, AVS_TIME_S));\n\n    expect_recv(&env, requests[0]);\n    expect_send(&env, responses[0]);\n    expect_recv(&env, requests[1]);\n    expect_send(&env, responses[1]);\n    expect_recv(&env, requests[2]);\n    expect_send(&env, responses[2]);\n    expect_recv(&env, requests[3]);\n    avs_unit_mocksock_output_fail(env.mocksock, avs_errno(AVS_ENODEV));\n\n    avs_error_t err = avs_coap_streaming_handle_incoming_packet(\n            env.coap_ctx, broken_write_handle_request, RESPONSE_PAYLOAD);\n    ASSERT_EQ(err.category, AVS_ERRNO_CATEGORY);\n    ASSERT_EQ(err.code, AVS_ENODEV);\n#        undef REQUEST_PAYLOAD\n#        undef RESPONSE_PAYLOAD\n}\n\n#    endif // WITH_AVS_COAP_BLOCK\n\n#endif // defined(AVS_UNIT_TESTING) && defined(WITH_AVS_COAP_UDP) &&\n       // defined(WITH_AVS_COAP_STREAMING_API)\n"
  },
  {
    "path": "deps/avs_coap/tests/udp/tx_params_mock.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef AVS_COAP_TEST_TX_PARAMS_MOCK_H\n#define AVS_COAP_TEST_TX_PARAMS_MOCK_H\n\n#include <avsystem/commons/avs_unit_mock_helpers.h>\n\nextern __typeof__(_avs_coap_udp_initial_retry_state) *AVS_UNIT_MOCK(\n        _avs_coap_udp_initial_retry_state);\nvoid _avs_unit_mock_constructor_avs_coap_udp_initial_retry_state(void)\n        __attribute__((constructor));\n#define _avs_coap_udp_initial_retry_state(...) \\\n    AVS_UNIT_MOCK_WRAPPER(_avs_coap_udp_initial_retry_state)(__VA_ARGS__)\n\n#endif /* AVS_COAP_TEST_TX_PARAMS_MOCK_H */\n"
  },
  {
    "path": "deps/avs_coap/tests/udp/udp_tx_params.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <avs_coap_init.h>\n\n#if defined(AVS_UNIT_TESTING) && defined(WITH_AVS_COAP_UDP)\n\n#    include <avsystem/coap/udp.h>\n\n#    include <avsystem/commons/avs_unit_mock_helpers.h>\n\n#    define MODULE_NAME test\n#    include <avs_coap_x_log_config.h>\n\n#    include \"./utils.h\"\n#    include \"udp/avs_coap_udp_tx_params.h\"\n\nstatic const avs_coap_udp_tx_params_t DETERMINISTIC_TX_PARAMS = {\n    .ack_timeout = {\n        .seconds = 2,\n        .nanoseconds = 0\n    },\n    .ack_random_factor = 1.0,\n    .max_retransmit = 4,\n    .nstart = 1\n};\n\nAVS_UNIT_TEST(udp_tx_params, correct_backoff) {\n    avs_coap_udp_ctx_t ctx = {\n        .base.prng_ctx = avs_crypto_prng_new(NULL, NULL),\n        .tx_params = DETERMINISTIC_TX_PARAMS\n    };\n    avs_coap_retry_state_t state;\n    ASSERT_OK(_avs_coap_udp_initial_retry_state(&ctx, &state));\n    size_t backoff_s = (size_t) DETERMINISTIC_TX_PARAMS.ack_timeout.seconds;\n    ASSERT_EQ(state.retries_left, 4);\n    ASSERT_EQ(state.recv_timeout.seconds, backoff_s);\n\n    for (size_t i = 0; i < DETERMINISTIC_TX_PARAMS.max_retransmit; ++i) {\n        ASSERT_FALSE(_avs_coap_udp_all_retries_sent(&state));\n        ASSERT_OK(_avs_coap_udp_update_retry_state(&ctx, &state));\n        backoff_s *= 2;\n        ASSERT_EQ(state.recv_timeout.seconds, backoff_s);\n    }\n    ASSERT_TRUE(_avs_coap_udp_all_retries_sent(&state));\n    avs_crypto_prng_free(&ctx.base.prng_ctx);\n}\n\nstatic void assert_tx_params_equal(const avs_coap_udp_tx_params_t *actual,\n                                   const avs_coap_udp_tx_params_t *expected) {\n    AVS_UNIT_ASSERT_EQUAL(actual->ack_timeout.seconds,\n                          expected->ack_timeout.seconds);\n    AVS_UNIT_ASSERT_EQUAL(actual->ack_timeout.nanoseconds,\n                          expected->ack_timeout.nanoseconds);\n    AVS_UNIT_ASSERT_EQUAL(actual->ack_random_factor,\n                          expected->ack_random_factor);\n    AVS_UNIT_ASSERT_EQUAL(actual->max_retransmit, expected->max_retransmit);\n    AVS_UNIT_ASSERT_EQUAL(actual->nstart, expected->nstart);\n}\n\nAVS_UNIT_TEST(udp_tx_params, getting_and_setting_udp_tx_params) {\n    // We need to set nstart to the default value, because in our tests\n    // it is set to 999 by default\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup_with_nstart(1);\n\n    // First, check if the initial tx params are the default ones\n    // (default params are specified by RFC 7252)\n    const avs_coap_udp_tx_params_t *params =\n            avs_coap_udp_ctx_get_tx_params(env.coap_ctx);\n    ASSERT_NOT_NULL(params);\n    assert_tx_params_equal(params, &AVS_COAP_DEFAULT_UDP_TX_PARAMS);\n\n    // Try to set some invalid params (according to RFC 7252 ACK_TIMEOUT should\n    // not be shorter than 1 second)\n    avs_coap_udp_tx_params_t invalid_params = AVS_COAP_DEFAULT_UDP_TX_PARAMS;\n    invalid_params.ack_timeout = (avs_time_duration_t) {\n        .seconds = 0,\n        .nanoseconds = 500000000\n    };\n    ASSERT_FAIL(avs_coap_udp_ctx_set_tx_params(env.coap_ctx, &invalid_params));\n\n    // Make sure that the params are still default\n    params = avs_coap_udp_ctx_get_tx_params(env.coap_ctx);\n    ASSERT_NOT_NULL(params);\n    assert_tx_params_equal(params, &AVS_COAP_DEFAULT_UDP_TX_PARAMS);\n\n    // Set some valid parameters different than default ones\n    ASSERT_OK(avs_coap_udp_ctx_set_tx_params(env.coap_ctx,\n                                             &DETERMINISTIC_TX_PARAMS));\n\n    // Try setting invalid params again\n    ASSERT_FAIL(avs_coap_udp_ctx_set_tx_params(env.coap_ctx, &invalid_params));\n\n    // Make sure that the params are unchanged - i.e. they are not set\n    // to the invalid params nor reset\n    params = avs_coap_udp_ctx_get_tx_params(env.coap_ctx);\n    ASSERT_NOT_NULL(params);\n    assert_tx_params_equal(params, &DETERMINISTIC_TX_PARAMS);\n}\n\nAVS_UNIT_TEST(udp_tx_params, ack_timeout_change) {\n    // With deterministic setup it will be easier to measure the\n    // differences when using different ACK timeouts\n    avs_coap_udp_tx_params_t tx_params = AVS_COAP_DEFAULT_UDP_TX_PARAMS;\n    tx_params.ack_random_factor = 1.0;\n    tx_params.max_retransmit = 0;\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup(&tx_params, 4096, 4096, NULL);\n\n    const test_msg_t *failing_request =\n            COAP_MSG(CON, GET, ID(0), TOKEN(nth_token(0)));\n    const test_msg_t *request = COAP_MSG(CON, GET, ID(1), TOKEN(nth_token(1)));\n    const test_msg_t *response =\n            COAP_MSG(ACK, CONTENT, ID(1), TOKEN(nth_token(1)));\n    avs_coap_exchange_id_t id;\n\n    // a request should be sent\n    ASSERT_OK(avs_coap_client_send_async_request(\n            env.coap_ctx, &id, &failing_request->request_header, NULL, NULL,\n            test_response_handler, &env.expects_list));\n    ASSERT_TRUE(avs_coap_exchange_id_valid(id));\n\n    expect_send(&env, failing_request);\n    avs_sched_run(env.sched);\n\n    _avs_mock_clock_advance(avs_time_duration_from_scalar(2, AVS_TIME_S));\n\n    // because the timeout expired, we expect a failure\n    expect_handler_call(&env, &id, AVS_COAP_CLIENT_REQUEST_FAIL, NULL);\n    avs_sched_run(env.sched);\n\n    // we change the timeout using TX params setting function\n    tx_params.ack_timeout = avs_time_duration_from_scalar(4, AVS_TIME_S);\n    ASSERT_OK(avs_coap_udp_ctx_set_tx_params(env.coap_ctx, &tx_params));\n\n    // and try to send a request once more\n    ASSERT_OK(avs_coap_client_send_async_request(\n            env.coap_ctx, &id, &request->request_header, NULL, NULL,\n            test_response_handler, &env.expects_list));\n    ASSERT_TRUE(avs_coap_exchange_id_valid(id));\n\n    expect_send(&env, request);\n    avs_sched_run(env.sched);\n\n    _avs_mock_clock_advance(avs_time_duration_from_scalar(2, AVS_TIME_S));\n\n    // this time we are still waiting after 2 seconds\n    avs_sched_run(env.sched);\n\n    // and we can still handle the response\n    expect_recv(&env, response);\n    expect_handler_call(&env, &id, AVS_COAP_CLIENT_REQUEST_OK, response);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n}\n\nstatic inline double avg_factor(double factor) {\n    return ((factor - 1.0) / 2.0) + 1.0;\n}\n\nstatic avs_error_t\nfake_avs_coap_udp_initial_retry_state(avs_coap_udp_ctx_t *ctx,\n                                      avs_coap_retry_state_t *out_retry_state) {\n    if (!ctx || !out_retry_state) {\n        return avs_errno(AVS_EINVAL);\n    }\n\n    out_retry_state->retries_left = 0;\n    out_retry_state->recv_timeout = avs_time_duration_fmul(\n            ctx->tx_params.ack_timeout,\n            avg_factor(ctx->tx_params.ack_random_factor));\n\n    return AVS_OK;\n}\n\nAVS_UNIT_TEST(udp_tx_params, ack_random_factor_change) {\n    AVS_UNIT_MOCK(_avs_coap_udp_initial_retry_state) =\n            fake_avs_coap_udp_initial_retry_state;\n\n    // factor which we will test here\n    const double factor = 9.0;\n    const int wait_s = 2;\n\n    avs_coap_udp_tx_params_t tx_params = {\n        .ack_timeout = avs_time_duration_from_scalar(wait_s, AVS_TIME_S),\n        .ack_random_factor = factor,\n        .max_retransmit = 0,\n        .nstart = 1\n    };\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup(&tx_params, 4096, 4096, NULL);\n\n    const test_msg_t *failing_request =\n            COAP_MSG(CON, GET, ID(0), TOKEN(nth_token(0)));\n    const test_msg_t *request = COAP_MSG(CON, GET, ID(1), TOKEN(nth_token(1)));\n    const test_msg_t *response =\n            COAP_MSG(ACK, CONTENT, ID(1), TOKEN(nth_token(1)));\n    avs_coap_exchange_id_t id;\n\n    ASSERT_OK(avs_coap_client_send_async_request(\n            env.coap_ctx, &id, &failing_request->request_header, NULL, NULL,\n            test_response_handler, &env.expects_list));\n    ASSERT_TRUE(avs_coap_exchange_id_valid(id));\n\n    expect_send(&env, failing_request);\n    avs_sched_run(env.sched);\n\n    // we wait longer than the random generator draws\n    _avs_mock_clock_advance(avs_time_duration_fmul(tx_params.ack_timeout,\n                                                   avg_factor(factor) + 1.0));\n\n    // because the timeout expired, we expect a failure\n    expect_handler_call(&env, &id, AVS_COAP_CLIENT_REQUEST_FAIL, NULL);\n    avs_sched_run(env.sched);\n\n    // it failed - let's try again\n    tx_params.ack_timeout = avs_time_duration_from_scalar(4, AVS_TIME_S);\n    ASSERT_OK(avs_coap_udp_ctx_set_tx_params(env.coap_ctx, &tx_params));\n\n    // and try to send a request once more\n    ASSERT_OK(avs_coap_client_send_async_request(\n            env.coap_ctx, &id, &request->request_header, NULL, NULL,\n            test_response_handler, &env.expects_list));\n    ASSERT_TRUE(avs_coap_exchange_id_valid(id));\n\n    expect_send(&env, request);\n    avs_sched_run(env.sched);\n\n    // but now we wait shorter than the random generator draws\n    _avs_mock_clock_advance(avs_time_duration_fmul(tx_params.ack_timeout,\n                                                   avg_factor(factor) - 1.0));\n\n    // this time we are still waiting after 2 seconds\n    avs_sched_run(env.sched);\n\n    // and we can still handle the response\n    expect_recv(&env, response);\n    expect_handler_call(&env, &id, AVS_COAP_CLIENT_REQUEST_OK, response);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n}\n\nAVS_UNIT_TEST(udp_tx_params, max_retransmit_change) {\n    // We use deterministic setup with no random factor\n    avs_coap_udp_tx_params_t tx_params = AVS_COAP_DEFAULT_UDP_TX_PARAMS;\n    tx_params.ack_random_factor = 1.0;\n    tx_params.max_retransmit = 1;\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup(&tx_params, 4096, 4096, NULL);\n\n    const test_msg_t *failing_request =\n            COAP_MSG(CON, GET, ID(0), TOKEN(nth_token(0)));\n    const test_msg_t *request = COAP_MSG(CON, GET, ID(1), TOKEN(nth_token(1)));\n    const test_msg_t *response =\n            COAP_MSG(ACK, CONTENT, ID(1), TOKEN(nth_token(1)));\n    avs_coap_exchange_id_t id;\n\n    // a request should be sent\n    ASSERT_OK(avs_coap_client_send_async_request(\n            env.coap_ctx, &id, &failing_request->request_header, NULL, NULL,\n            test_response_handler, &env.expects_list));\n    ASSERT_TRUE(avs_coap_exchange_id_valid(id));\n\n    expect_send(&env, failing_request);\n    avs_sched_run(env.sched);\n\n    _avs_mock_clock_advance(avs_time_duration_from_scalar(2, AVS_TIME_S));\n\n    expect_send(&env, failing_request);\n    avs_sched_run(env.sched);\n\n    _avs_mock_clock_advance(avs_time_duration_from_scalar(4, AVS_TIME_S));\n\n    // after two trials the request should fail\n    expect_handler_call(&env, &id, AVS_COAP_CLIENT_REQUEST_FAIL, NULL);\n    avs_sched_run(env.sched);\n\n    // we change the timeout using TX params setting function\n    tx_params.max_retransmit = 2;\n    ASSERT_OK(avs_coap_udp_ctx_set_tx_params(env.coap_ctx, &tx_params));\n\n    // and try to send a request once more\n    ASSERT_OK(avs_coap_client_send_async_request(\n            env.coap_ctx, &id, &request->request_header, NULL, NULL,\n            test_response_handler, &env.expects_list));\n    ASSERT_TRUE(avs_coap_exchange_id_valid(id));\n\n    expect_send(&env, request);\n    avs_sched_run(env.sched);\n\n    _avs_mock_clock_advance(avs_time_duration_from_scalar(2, AVS_TIME_S));\n\n    expect_send(&env, request);\n    avs_sched_run(env.sched);\n\n    _avs_mock_clock_advance(avs_time_duration_from_scalar(4, AVS_TIME_S));\n\n    // but this time it is sent the third time\n    expect_send(&env, request);\n    avs_sched_run(env.sched);\n\n    // and receives some response\n    expect_recv(&env, response);\n    expect_handler_call(&env, &id, AVS_COAP_CLIENT_REQUEST_OK, response);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n}\n\nAVS_UNIT_TEST(udp_tx_params, nstart_increase) {\n    // to this test the best will be high, deterministic timeout\n    // with no retransmissions, for the sake of simplicity\n    // and nstart=2 to test it\n    avs_coap_udp_tx_params_t tx_params = {\n        .ack_timeout = avs_time_duration_from_scalar(10, AVS_TIME_S),\n        .ack_random_factor = 1.0,\n        .max_retransmit = 0,\n        .nstart = 1\n    };\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup(&tx_params, 4096, 4096, NULL);\n\n    const test_msg_t *requests[] = {\n        COAP_MSG(CON, GET, ID(0), TOKEN(nth_token(0))),\n        COAP_MSG(CON, GET, ID(1), TOKEN(nth_token(1))),\n        COAP_MSG(CON, GET, ID(2), TOKEN(nth_token(2)))\n    };\n    const test_msg_t *responses[] = {\n        COAP_MSG(ACK, CONTENT, ID(0), TOKEN(nth_token(0))),\n        COAP_MSG(ACK, CONTENT, ID(1), TOKEN(nth_token(1))),\n        COAP_MSG(ACK, CONTENT, ID(2), TOKEN(nth_token(2)))\n    };\n    avs_coap_exchange_id_t id[3];\n\n    // a request should be sent\n    ASSERT_OK(avs_coap_client_send_async_request(\n            env.coap_ctx, &id[0], &requests[0]->request_header, NULL, NULL,\n            test_response_handler, &env.expects_list));\n    ASSERT_TRUE(avs_coap_exchange_id_valid(id[0]));\n    expect_send(&env, requests[0]);\n\n    // the second one should be sent as well\n    ASSERT_OK(avs_coap_client_send_async_request(\n            env.coap_ctx, &id[1], &requests[1]->request_header, NULL, NULL,\n            test_response_handler, &env.expects_list));\n    ASSERT_TRUE(avs_coap_exchange_id_valid(id[1]));\n\n    avs_sched_run(env.sched);\n\n    expect_recv(&env, responses[0]);\n    expect_handler_call(&env, &id[0], AVS_COAP_CLIENT_REQUEST_OK, responses[0]);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n\n    // we expect it to be sent after receiving the response\n    expect_send(&env, requests[1]);\n\n    _avs_mock_clock_advance(avs_sched_time_to_next(env.sched));\n    avs_sched_run(env.sched);\n\n    // but now we increase the nstart parameter\n    tx_params.nstart = 2;\n    ASSERT_OK(avs_coap_udp_ctx_set_tx_params(env.coap_ctx, &tx_params));\n\n    // so the next request can be sent before recieving the response\n    ASSERT_OK(avs_coap_client_send_async_request(\n            env.coap_ctx, &id[2], &requests[2]->request_header, NULL, NULL,\n            test_response_handler, &env.expects_list));\n    ASSERT_TRUE(avs_coap_exchange_id_valid(id[2]));\n\n    expect_send(&env, requests[2]);\n\n    _avs_mock_clock_advance(avs_sched_time_to_next(env.sched));\n    avs_sched_run(env.sched);\n\n    for (int i = 1; i <= 2; i++) {\n        expect_recv(&env, responses[i]);\n        expect_handler_call(&env, &id[i], AVS_COAP_CLIENT_REQUEST_OK,\n                            responses[i]);\n        expect_has_buffered_data_check(&env, false);\n        ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL,\n                                                        NULL));\n    }\n}\n\nAVS_UNIT_TEST(udp_tx_params, nstart_decrease) {\n    // to this test the best will be high, deterministic timeout\n    // with no retransmissions, for the sake of simplicity\n    // and nstart=2 to test it\n    avs_coap_udp_tx_params_t tx_params = {\n        .ack_timeout = avs_time_duration_from_scalar(10, AVS_TIME_S),\n        .ack_random_factor = 1.0,\n        .max_retransmit = 0,\n        .nstart = 2\n    };\n    test_env_t env __attribute__((cleanup(test_teardown))) =\n            test_setup(&tx_params, 4096, 4096, NULL);\n\n    const test_msg_t *requests[] = {\n        COAP_MSG(CON, GET, ID(0), TOKEN(nth_token(0))),\n        COAP_MSG(CON, GET, ID(1), TOKEN(nth_token(1))),\n        COAP_MSG(CON, GET, ID(2), TOKEN(nth_token(2)))\n    };\n    const test_msg_t *responses[] = {\n        COAP_MSG(ACK, CONTENT, ID(0), TOKEN(nth_token(0))),\n        COAP_MSG(ACK, CONTENT, ID(1), TOKEN(nth_token(1))),\n        COAP_MSG(ACK, CONTENT, ID(2), TOKEN(nth_token(2)))\n    };\n    avs_coap_exchange_id_t id[3];\n\n    // both requests should be sent\n    for (int request = 0; request <= 1; request++) {\n        ASSERT_OK(avs_coap_client_send_async_request(\n                env.coap_ctx, &id[request], &requests[request]->request_header,\n                NULL, NULL, test_response_handler, &env.expects_list));\n        ASSERT_TRUE(avs_coap_exchange_id_valid(id[request]));\n        expect_send(&env, requests[request]);\n    }\n\n    avs_sched_run(env.sched);\n\n    // the first one gets a response\n    expect_recv(&env, responses[0]);\n    expect_handler_call(&env, &id[0], AVS_COAP_CLIENT_REQUEST_OK, responses[0]);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n\n    avs_sched_run(env.sched);\n\n    // but now we decrease the nstart parameter\n    tx_params.nstart = 1;\n    ASSERT_OK(avs_coap_udp_ctx_set_tx_params(env.coap_ctx, &tx_params));\n\n    // so the next request must wait for the response for the previous one\n    ASSERT_OK(avs_coap_client_send_async_request(\n            env.coap_ctx, &id[2], &requests[2]->request_header, NULL, NULL,\n            test_response_handler, &env.expects_list));\n    ASSERT_TRUE(avs_coap_exchange_id_valid(id[2]));\n\n    avs_sched_run(env.sched);\n\n    expect_recv(&env, responses[1]);\n    expect_handler_call(&env, &id[1], AVS_COAP_CLIENT_REQUEST_OK, responses[1]);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n\n    expect_send(&env, requests[2]);\n\n    avs_sched_run(env.sched);\n\n    expect_recv(&env, responses[2]);\n    expect_handler_call(&env, &id[2], AVS_COAP_CLIENT_REQUEST_OK, responses[2]);\n    expect_has_buffered_data_check(&env, false);\n    ASSERT_OK(avs_coap_async_handle_incoming_packet(env.coap_ctx, NULL, NULL));\n}\n\n#endif // defined(AVS_UNIT_TESTING) && defined(WITH_AVS_COAP_UDP)\n"
  },
  {
    "path": "deps/avs_coap/tests/udp/utils.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef AVS_COAP_SRC_UDP_TEST_UTILS_H\n#define AVS_COAP_SRC_UDP_TEST_UTILS_H\n\n#include <avsystem/commons/avs_list.h>\n#include <avsystem/commons/avs_memory.h>\n#include <avsystem/commons/avs_sched.h>\n#include <avsystem/commons/avs_shared_buffer.h>\n\n#define AVS_UNIT_ENABLE_SHORT_ASSERTS\n#include <avsystem/commons/avs_unit_mocksock.h>\n#include <avsystem/commons/avs_unit_test.h>\n\n#include <avsystem/coap/coap.h>\n\n#include \"avs_coap_code_utils.h\"\n#include \"options/avs_coap_options.h\"\n#include \"udp/avs_coap_udp_msg.h\"\n\n#include \"tests/mock_clock.h\"\n#include \"tests/utils.h\"\n\n// Workaround for failing self-sufficiency test\n#ifdef WITH_AVS_COAP_UDP\n\nstruct coap_msg_args {\n    avs_coap_udp_type_t type;\n    uint8_t code;\n    uint16_t id;\n    avs_coap_token_t token;\n\n    const uint16_t *content_format;\n    const uint16_t *accept;\n    const uint16_t *duplicated_accept;\n    const uint32_t *observe;\n    avs_coap_etag_t *etag;\n\n#    ifdef WITH_AVS_COAP_BLOCK\n    const avs_coap_option_block_t block1;\n    const avs_coap_option_block_t block2;\n#    endif // WITH_AVS_COAP_BLOCK\n\n    const void *payload;\n    size_t payload_size;\n\n    // 15 = arbitrary limit on path segments\n    const char *location_path[16];\n    const char *uri_path[16];\n    const char *uri_query[16];\n\n    const char uri_host[64];\n};\n\nstatic inline void add_string_opts(avs_coap_options_t *opts,\n                                   uint16_t opt_num,\n                                   const char *const *const strings) {\n    for (size_t i = 0; strings[i]; ++i) {\n        ASSERT_OK(avs_coap_options_add_string(opts, opt_num, strings[i]));\n    }\n}\n\ntypedef struct {\n    avs_coap_udp_msg_t msg;\n    avs_coap_request_header_t request_header;\n    avs_coap_response_header_t response_header;\n    size_t size;\n    uint8_t data[];\n} test_msg_t;\n\nstatic inline const test_msg_t *\ncoap_msg__(uint8_t *buf, size_t buf_size, const struct coap_msg_args *args) {\n    char opts_buf[4096];\n    avs_coap_options_t opts =\n            avs_coap_options_create_empty(opts_buf, sizeof(opts_buf));\n\n    add_string_opts(&opts, AVS_COAP_OPTION_LOCATION_PATH, args->location_path);\n    add_string_opts(&opts, AVS_COAP_OPTION_URI_PATH, args->uri_path);\n    add_string_opts(&opts, AVS_COAP_OPTION_URI_QUERY, args->uri_query);\n\n    if (strlen(args->uri_host)) {\n        ASSERT_OK(avs_coap_options_add_string(&opts, AVS_COAP_OPTION_URI_HOST,\n                                              args->uri_host));\n    }\n\n#    ifdef WITH_AVS_COAP_BLOCK\n    if (args->block1.size > 0) {\n        ASSERT_OK(avs_coap_options_add_block(&opts, &args->block1));\n    }\n    if (args->block2.size > 0) {\n        ASSERT_OK(avs_coap_options_add_block(&opts, &args->block2));\n    }\n#    endif // WITH_AVS_COAP_BLOCK\n\n    if (args->content_format) {\n        ASSERT_OK(avs_coap_options_set_content_format(&opts,\n                                                      *args->content_format));\n    }\n    if (args->accept) {\n        ASSERT_OK(avs_coap_options_add_u16(&opts, AVS_COAP_OPTION_ACCEPT,\n                                           *args->accept));\n    }\n    if (args->duplicated_accept) {\n        ASSERT_OK(avs_coap_options_add_u16(&opts, AVS_COAP_OPTION_ACCEPT,\n                                           *args->duplicated_accept));\n    }\n#    ifdef WITH_AVS_COAP_OBSERVE\n    if (args->observe) {\n        ASSERT_OK(avs_coap_options_add_observe(&opts, *args->observe));\n    }\n#    endif // WITH_AVS_COAP_OBSERVE\n    if (args->etag) {\n        ASSERT_OK(avs_coap_options_add_etag(&opts, args->etag));\n    }\n\n    ASSERT_EQ((uintptr_t) buf % AVS_ALIGNOF(size_t), 0);\n    test_msg_t *test_msg = (test_msg_t *) buf;\n\n    test_msg->msg = (avs_coap_udp_msg_t) {\n        .header = _avs_coap_udp_header_init(args->type, args->token.size,\n                                            args->code, args->id),\n        .token = args->token,\n        // NOTE: this uses a stack-allocated buffer to hold options that gets\n        // invalidated at the end of this function\n        .options = opts,\n        .payload = args->payload,\n        .payload_size = args->payload_size\n    };\n\n    ASSERT_OK(_avs_coap_udp_msg_serialize(&test_msg->msg, test_msg->data,\n                                          buf_size - sizeof(test_msg_t),\n                                          &test_msg->size));\n    // Adjust options field to point to test_msg and not to the stack-allocated\n    // buffer. We could use _avs_coap_udp_msg_parse, but this function is also\n    // used to construct invalid messages, which makes parse fail.\n    test_msg->msg.options = (avs_coap_options_t) {\n        .begin = &test_msg->data[sizeof(avs_coap_udp_header_t)\n                                 + args->token.size],\n        .size = opts.size,\n        .capacity = opts.size\n    };\n\n    test_msg->request_header = (avs_coap_request_header_t) {\n        .code = test_msg->msg.header.code,\n        .options = test_msg->msg.options\n    };\n    test_msg->response_header = (avs_coap_response_header_t) {\n        .code = test_msg->msg.header.code,\n        .options = test_msg->msg.options\n    };\n\n    return test_msg;\n}\n\n/* Convenience macros for use in COAP_MSG */\n#    define CON AVS_COAP_UDP_TYPE_CONFIRMABLE\n#    define NON AVS_COAP_UDP_TYPE_NON_CONFIRMABLE\n#    define ACK AVS_COAP_UDP_TYPE_ACKNOWLEDGEMENT\n#    define RST AVS_COAP_UDP_TYPE_RESET\n\n/* Allocates a 64k buffer on the stack, constructs a message inside it and\n * returns the message pointer.\n *\n * @p Type    - one of AVS_COAP_MSG_* constants or CON, NON, ACK, RST.\n * @p Code    - suffix of one of AVS_COAP_CODE_* constants, e.g. GET\n *              or BAD_REQUEST.\n * @p Opts... - additional options, e.g. ETAG(), PATH(), QUERY().\n *\n * Example usage:\n * @code\n * const avs_coap_msg_t *msg = COAP_MSG(CON, GET, ID(0), NO_PAYLOAD);\n * const avs_coap_msg_t *msg = COAP_MSG(ACK, CONTENT, ID(0),\n *                                      BLOCK2(0, 16, \"full_payload\"));\n * @endcode\n */\n#    define COAP_MSG(Type, Code, ... /* Payload, Opts... */)    \\\n        coap_msg__((uint8_t *) (size_t[(65535 + sizeof(size_t)) \\\n                                       / sizeof(size_t)]){ 0 }, \\\n                   65536,                                       \\\n                   &(struct coap_msg_args) {                    \\\n                       .type = (Type),                          \\\n                       .code = CODE__(Code),                    \\\n                       __VA_ARGS__                              \\\n                   })\n\n/* Used in COAP_MSG() to define message ID. */\n#    define ID(MsgId) .id = (MsgId)\n\n/* Used in COAP_MSG() to specify ETag option value. */\n#    define ETAG(Tag)                \\\n        .etag = &(avs_coap_etag_t) { \\\n            .size = sizeof(Tag) - 1, \\\n            .bytes = Tag             \\\n        }\n\n/* Used in COAP_MSG() to specify a list of Location-Path options. */\n#    define LOCATION_PATH(... /* Segments */) .location_path = { __VA_ARGS__ }\n\n/* Used in COAP_MSG() to specify a list of Uri-Query options. */\n#    define QUERY(... /* Segments */) .uri_query = { __VA_ARGS__ }\n\n/* Used in COAP_MSG() to specify the Content-Format option even with\n * unsupported value. */\n#    define CONTENT_FORMAT_VALUE(Format)        \\\n        .content_format = (const uint16_t[1]) { \\\n            (Format)                            \\\n        }\n\n/* Used in COAP_MSG() to specify the Content-Format option using predefined\n * constants. */\n#    define CONTENT_FORMAT(Format) CONTENT_FORMAT_VALUE(FORMAT__(Format))\n\ntypedef struct {\n    avs_coap_exchange_id_t exchange_id;\n    avs_coap_client_request_state_t result;\n    bool has_response;\n    avs_coap_client_async_response_t response;\n    size_t next_response_payload_offset;\n} test_response_handler_expected_t;\n\ntypedef struct {\n    const void *payload;\n    size_t expected_payload_offset;\n    size_t payload_size;\n    avs_coap_ctx_t *coap_ctx;\n    avs_coap_exchange_id_t exchange_id;\n    bool cancel_exchange;\n    size_t messages_until_fail;\n} test_payload_writer_args_t;\n\ntypedef struct {\n    avs_coap_server_request_state_t state;\n    avs_coap_server_async_request_t request;\n    avs_coap_observe_id_t observe_id;\n\n    const avs_coap_response_header_t *response;\n    avs_coap_payload_writer_t *response_writer;\n    test_payload_writer_args_t *response_writer_args;\n\n    bool start_observe;\n    bool send_request;\n} test_request_handler_expected_t;\n\ntypedef enum {\n    OBSERVE_START,\n    OBSERVE_CANCEL,\n} test_observe_state_change_t;\n\ntypedef struct {\n    test_observe_state_change_t state;\n    avs_coap_observe_id_t id;\n} test_observe_expect_t;\n\ntypedef struct {\n    enum {\n        EXPECT_RESPONSE_HANDLER,\n        EXPECT_REQUEST_HANDLER,\n        EXPECT_OBSERVE,\n        EXPECT_OBSERVE_DELIVERY\n    } type;\n    union {\n        test_response_handler_expected_t response_handler;\n        test_request_handler_expected_t request_handler;\n        test_observe_expect_t observe;\n        avs_error_t observe_delivery;\n    } impl;\n} test_handler_expected_t;\n\ntypedef struct {\n    avs_sched_t *sched;\n    avs_net_socket_t *mocksock;\n    avs_coap_udp_tx_params_t tx_params;\n    avs_shared_buffer_t *in_buffer;\n    avs_shared_buffer_t *out_buffer;\n    AVS_LIST(test_handler_expected_t) expects_list;\n\n    avs_coap_ctx_t *coap_ctx;\n    avs_coap_udp_response_cache_t *response_cache;\n    avs_crypto_prng_ctx_t *prng_ctx;\n} test_env_t;\n\nstatic inline test_env_t\ntest_setup_without_socket(const avs_coap_udp_tx_params_t *tx_params,\n                          size_t in_buffer_size,\n                          size_t out_buffer_size,\n                          avs_coap_udp_response_cache_t *cache) {\n    reset_token_generator();\n    avs_shared_buffer_t *in_buf = avs_shared_buffer_new(in_buffer_size);\n    avs_shared_buffer_t *out_buf = avs_shared_buffer_new(out_buffer_size);\n\n    avs_sched_t *sched = avs_sched_new(\"udp_ctx_test\", NULL);\n    ASSERT_NOT_NULL(sched);\n\n    avs_crypto_prng_ctx_t *prng_ctx = avs_crypto_prng_new(NULL, NULL);\n    ASSERT_NOT_NULL(prng_ctx);\n\n    test_env_t env = {\n        .sched = sched,\n        .mocksock = NULL,\n        .tx_params = tx_params ? *tx_params : AVS_COAP_DEFAULT_UDP_TX_PARAMS,\n        .in_buffer = in_buf,\n        .out_buffer = out_buf,\n        .coap_ctx = avs_coap_udp_ctx_create(sched, &env.tx_params, in_buf,\n                                            out_buf, cache, prng_ctx),\n        .response_cache = cache,\n        .prng_ctx = prng_ctx\n    };\n\n    ASSERT_NOT_NULL(in_buf);\n    ASSERT_NOT_NULL(out_buf);\n    ASSERT_NOT_NULL(env.coap_ctx);\n\n    _avs_mock_clock_start(avs_time_monotonic_from_scalar(0, AVS_TIME_S));\n\n    return env;\n}\n\nstatic inline test_env_t test_setup(const avs_coap_udp_tx_params_t *tx_params,\n                                    size_t in_buffer_size,\n                                    size_t out_buffer_size,\n                                    avs_coap_udp_response_cache_t *cache) {\n    test_env_t env = test_setup_without_socket(tx_params, in_buffer_size,\n                                               out_buffer_size, cache);\n\n    avs_net_socket_t *socket = NULL;\n    avs_unit_mocksock_create_datagram(&socket);\n    avs_unit_mocksock_enable_inner_mtu_getopt(socket, 1500);\n    avs_unit_mocksock_enable_recv_timeout_getsetopt(\n            socket, avs_time_duration_from_scalar(30, AVS_TIME_S));\n    env.mocksock = socket;\n\n    avs_unit_mocksock_expect_connect(socket, NULL, NULL);\n    avs_net_socket_connect(socket, NULL, NULL);\n\n    ASSERT_OK(avs_coap_ctx_set_socket(env.coap_ctx, socket));\n\n    avs_unit_mocksock_enable_remote_host(socket, \"7.7.7.7\");\n    avs_unit_mocksock_enable_remote_port(socket, \"997\");\n\n    return env;\n}\n\nstatic inline test_env_t test_setup_with_nstart(size_t nstart) {\n    avs_coap_udp_tx_params_t tx_params = AVS_COAP_DEFAULT_UDP_TX_PARAMS;\n    tx_params.nstart = nstart;\n    return test_setup(&tx_params, 4096, 4096, NULL);\n}\n\nstatic inline test_env_t\ntest_setup_with_max_retransmit(unsigned max_retransmit) {\n    avs_coap_udp_tx_params_t tx_params = AVS_COAP_DEFAULT_UDP_TX_PARAMS;\n    tx_params.max_retransmit = max_retransmit;\n    return test_setup(&tx_params, 4096, 4096, NULL);\n}\n\nstatic inline test_env_t test_setup_default(void) {\n    return test_setup_with_nstart(999);\n}\n\nstatic inline test_env_t test_setup_with_cache(size_t size) {\n    return test_setup(NULL, 4096, 4096,\n                      avs_coap_udp_response_cache_create(size));\n}\n\nstatic inline test_env_t test_setup_deterministic(void) {\n    avs_coap_udp_tx_params_t tx_params = AVS_COAP_DEFAULT_UDP_TX_PARAMS;\n    tx_params.ack_random_factor = 1.0;\n    return test_setup(&tx_params, 4096, 4096, NULL);\n}\n\nstatic inline void test_teardown_no_expects_check(test_env_t *env) {\n    avs_coap_ctx_cleanup(&env->coap_ctx);\n    avs_sched_cleanup(&env->sched);\n    if (env->mocksock) {\n        avs_unit_mocksock_assert_expects_met(env->mocksock);\n    }\n    avs_net_socket_cleanup(&env->mocksock);\n    avs_free(env->in_buffer);\n    avs_free(env->out_buffer);\n    avs_coap_udp_response_cache_release(&env->response_cache);\n    avs_crypto_prng_free(&env->prng_ctx);\n    _avs_mock_clock_finish();\n}\n\nstatic inline void test_teardown_late_expects_check(test_env_t *env) {\n    test_teardown_no_expects_check(env);\n    ASSERT_NULL(env->expects_list); // all expected handler calls done?\n}\n\nstatic inline void test_teardown(test_env_t *env) {\n    ASSERT_NULL(env->expects_list); // all expected handler calls done?\n    test_teardown_no_expects_check(env);\n}\n\nstatic inline void expect_send(test_env_t *env, const test_msg_t *msg) {\n    avs_unit_mocksock_expect_output(env->mocksock, msg->data, msg->size);\n}\n\nstatic inline void expect_recv(test_env_t *env, const test_msg_t *msg) {\n    avs_unit_mocksock_input(env->mocksock, msg->data, msg->size);\n}\n\nstatic inline void expect_has_buffered_data_check(test_env_t *env,\n                                                  bool has_buffered_data) {\n    avs_unit_mocksock_expect_get_opt(env->mocksock,\n                                     AVS_NET_SOCKET_HAS_BUFFERED_DATA,\n                                     (avs_net_socket_opt_value_t) {\n                                         .flag = has_buffered_data\n                                     });\n}\n\ntypedef struct {\n    test_env_t *env;\n    const avs_coap_exchange_id_t *id;\n    avs_coap_client_request_state_t result;\n    const test_msg_t *msg;\n    size_t next_response_payload_offset;\n    size_t expected_payload_offset;\n} expect_handler_call_args_t;\n\nstatic inline void\nexpect_handler_call_impl(const expect_handler_call_args_t *args) {\n    test_handler_expected_t *expect =\n            AVS_LIST_NEW_ELEMENT(test_handler_expected_t);\n\n    expect->type = EXPECT_RESPONSE_HANDLER;\n    expect->impl.response_handler.exchange_id = *args->id;\n    expect->impl.response_handler.result = args->result;\n    expect->impl.response_handler.has_response = (args->msg != NULL);\n    if (args->msg) {\n        assert(args->expected_payload_offset <= args->msg->msg.payload_size);\n        expect->impl.response_handler.response =\n                (avs_coap_client_async_response_t) {\n                    .header = {\n                        .code = args->msg->msg.header.code,\n                        .options = args->msg->msg.options\n                    },\n                    .payload = (const char *) args->msg->msg.payload\n                               + args->expected_payload_offset,\n                    .payload_size = args->msg->msg.payload_size\n                                    - args->expected_payload_offset\n                };\n    }\n    expect->impl.response_handler.next_response_payload_offset =\n            args->next_response_payload_offset;\n\n    AVS_LIST_APPEND(&args->env->expects_list, expect);\n}\n\n#    define expect_handler_call(Env, Id, Result, ... /* Msg */)        \\\n        expect_handler_call_impl(&(const expect_handler_call_args_t) { \\\n            .env = (Env),                                              \\\n            .id = (Id),                                                \\\n            .result = (Result),                                        \\\n            .msg = __VA_ARGS__                                         \\\n        })\n\nstatic inline void\ntest_response_handler(avs_coap_ctx_t *ctx,\n                      avs_coap_exchange_id_t exchange_id,\n                      avs_coap_client_request_state_t result,\n                      const avs_coap_client_async_response_t *response,\n                      avs_error_t err,\n                      void *expects_list_) {\n    (void) ctx;\n    (void) err;\n\n    AVS_LIST(test_handler_expected_t) *expects_list =\n            (AVS_LIST(test_handler_expected_t) *) expects_list_;\n    ASSERT_NOT_NULL(expects_list);\n    ASSERT_NOT_NULL(*expects_list);\n    ASSERT_EQ((*expects_list)->type, EXPECT_RESPONSE_HANDLER);\n\n    const test_response_handler_expected_t *expected =\n            &(*expects_list)->impl.response_handler;\n\n    ASSERT_TRUE(avs_coap_exchange_id_equal(exchange_id, expected->exchange_id));\n    ASSERT_EQ(result, expected->result);\n\n    if (expected->has_response) {\n        const avs_coap_client_async_response_t *actual_res = response;\n        const avs_coap_client_async_response_t *expected_res =\n                &expected->response;\n\n        ASSERT_EQ(actual_res->header.code, expected_res->header.code);\n        ASSERT_EQ(actual_res->header.options.size,\n                  expected_res->header.options.size);\n        ASSERT_EQ(actual_res->payload_size, expected_res->payload_size);\n        ASSERT_EQ_BYTES_SIZED(actual_res->payload, expected_res->payload,\n                              actual_res->payload_size);\n    } else {\n        ASSERT_NULL(response);\n    }\n\n    if (expected->next_response_payload_offset) {\n        ASSERT_OK(avs_coap_client_set_next_response_payload_offset(\n                ctx, exchange_id, expected->next_response_payload_offset));\n    }\n    AVS_LIST_DELETE(expects_list);\n}\n\nstatic inline void\ntest_response_abort_handler(avs_coap_ctx_t *ctx,\n                            avs_coap_exchange_id_t exchange_id,\n                            avs_coap_client_request_state_t result,\n                            const avs_coap_client_async_response_t *response,\n                            avs_error_t err,\n                            void *expects_list) {\n    test_response_handler(ctx, exchange_id, result, response, err,\n                          expects_list);\n    avs_coap_exchange_cancel(ctx, exchange_id);\n}\n\nstatic inline int test_payload_writer(size_t payload_offset,\n                                      void *payload_buf,\n                                      size_t payload_buf_size,\n                                      size_t *out_payload_chunk_size,\n                                      void *arg) {\n    test_payload_writer_args_t *args = (test_payload_writer_args_t *) arg;\n\n    ASSERT_EQ(payload_offset, args->expected_payload_offset);\n    ASSERT_TRUE(payload_offset <= args->payload_size);\n\n    *out_payload_chunk_size =\n            AVS_MIN(payload_buf_size, args->payload_size - payload_offset);\n    args->expected_payload_offset += *out_payload_chunk_size;\n    memcpy(payload_buf, (const uint8_t *) args->payload + payload_offset,\n           *out_payload_chunk_size);\n\n    if (args->cancel_exchange) {\n        avs_coap_exchange_cancel(args->coap_ctx, args->exchange_id);\n    }\n    if (args->messages_until_fail && !--args->messages_until_fail) {\n        return -1;\n    }\n    return 0;\n}\n\nstatic inline void\nexpect_observe_state_change(test_env_t *env,\n                            test_observe_state_change_t state,\n                            const avs_coap_token_t token) {\n    *AVS_LIST_APPEND_NEW(test_handler_expected_t, &env->expects_list) =\n            (test_handler_expected_t) {\n                .type = EXPECT_OBSERVE,\n                .impl = {\n                    .observe = {\n                        .state = state,\n                        .id = {\n                            .token = token\n                        }\n                    }\n                }\n            };\n}\n\nstatic inline void expect_observe_start(test_env_t *env,\n                                        const avs_coap_token_t token) {\n    expect_observe_state_change(env, OBSERVE_START, token);\n}\n\nstatic inline void expect_observe_cancel(test_env_t *env,\n                                         const avs_coap_token_t token) {\n    expect_observe_state_change(env, OBSERVE_CANCEL, token);\n}\n\nstatic inline void\nassert_observe_state_change_expected(test_env_t *env,\n                                     test_observe_state_change_t state,\n                                     avs_coap_observe_id_t id) {\n    ASSERT_NOT_NULL(env->expects_list);\n    ASSERT_EQ(env->expects_list->type, EXPECT_OBSERVE);\n    ASSERT_EQ(env->expects_list->impl.observe.state, state);\n    ASSERT_TRUE(avs_coap_token_equal(&env->expects_list->impl.observe.id.token,\n                                     &id.token));\n\n    AVS_LIST_DELETE(&env->expects_list);\n}\n\nstatic inline void expect_observe_delivery(test_env_t *env, avs_error_t err) {\n    *AVS_LIST_APPEND_NEW(test_handler_expected_t, &env->expects_list) =\n            (test_handler_expected_t) {\n                .type = EXPECT_OBSERVE_DELIVERY,\n                .impl = {\n                    .observe_delivery = err\n                }\n            };\n}\n\nstatic inline void test_observe_delivery_handler(avs_coap_ctx_t *ctx,\n                                                 avs_error_t err,\n                                                 void *env_) {\n    (void) ctx;\n    test_env_t *env = (test_env_t *) env_;\n\n    ASSERT_NOT_NULL(env->expects_list);\n    ASSERT_EQ(env->expects_list->type, EXPECT_OBSERVE_DELIVERY);\n    if (avs_is_ok(env->expects_list->impl.observe_delivery)) {\n        ASSERT_OK(err);\n    } else {\n        ASSERT_EQ(env->expects_list->impl.observe_delivery.category,\n                  err.category);\n        ASSERT_EQ(env->expects_list->impl.observe_delivery.code, err.code);\n    }\n    AVS_LIST_DELETE(&env->expects_list);\n}\n\n#    ifdef WITH_AVS_COAP_OBSERVE\nstatic void test_on_observe_cancel(avs_coap_observe_id_t id, void *env) {\n    assert_observe_state_change_expected((test_env_t *) env, OBSERVE_CANCEL,\n                                         id);\n}\n#    endif // WITH_AVS_COAP_OBSERVE\n\nstatic inline int\ntest_handle_request(avs_coap_request_ctx_t *ctx,\n                    avs_coap_exchange_id_t request_id,\n                    avs_coap_server_request_state_t state,\n                    const avs_coap_server_async_request_t *request,\n                    const avs_coap_observe_id_t *observe_id,\n                    void *arg_) {\n    test_env_t *env = (test_env_t *) arg_;\n\n    ASSERT_NOT_NULL(env->expects_list);\n    ASSERT_EQ(env->expects_list->type, EXPECT_REQUEST_HANDLER);\n    ASSERT_EQ(env->expects_list->impl.request_handler.state, state);\n\n    AVS_LIST(test_handler_expected_t) expected =\n            AVS_LIST_DETACH(&env->expects_list);\n\n    if (state == AVS_COAP_SERVER_REQUEST_CLEANUP) {\n        ASSERT_NULL(request);\n        ASSERT_NULL(observe_id);\n    } else {\n        ASSERT_NOT_NULL(ctx);\n        ASSERT_TRUE(avs_coap_exchange_id_valid(request_id));\n\n        const avs_coap_server_async_request_t *expected_request =\n                &expected->impl.request_handler.request;\n\n        ASSERT_EQ(expected_request->header.code, request->header.code);\n        ASSERT_EQ(expected_request->header.options.size,\n                  request->header.options.size);\n        ASSERT_OK(memcmp(expected_request->header.options.begin,\n                         request->header.options.begin,\n                         expected_request->header.options.size));\n        ASSERT_EQ(expected_request->payload_offset, request->payload_offset);\n        ASSERT_EQ(expected_request->payload_size, request->payload_size);\n        ASSERT_OK(memcmp(expected_request->payload, request->payload,\n                         expected_request->payload_size));\n\n#    ifdef WITH_AVS_COAP_OBSERVE\n        uint32_t observe_value;\n        if (avs_coap_options_get_observe(&expected_request->header.options,\n                                         &observe_value)\n                        == 0\n                // 0 == request observe; 1 == cancel\n                && observe_value == 0) {\n            ASSERT_NOT_NULL(observe_id);\n            ASSERT_TRUE(avs_coap_token_equal(\n                    &expected->impl.request_handler.observe_id.token,\n                    &observe_id->token));\n        } else {\n            ASSERT_NULL(observe_id);\n        }\n#    endif // WITH_AVS_COAP_OBSERVE\n\n        if (expected->impl.request_handler.start_observe) {\n#    ifdef WITH_AVS_COAP_OBSERVE\n            assert_observe_state_change_expected((test_env_t *) env,\n                                                 OBSERVE_START, *observe_id);\n            ASSERT_OK(avs_coap_observe_async_start(\n                    ctx, *observe_id, test_on_observe_cancel, env));\n#    else  // WITH_AVS_COAP_OBSERVE\n            ASSERT_TRUE(!\"observe test, but observes are disabled\");\n#    endif // WITH_AVS_COAP_OBSERVE\n        }\n\n        if (expected->impl.request_handler.response) {\n            ASSERT_OK(avs_coap_server_setup_async_response(\n                    ctx, expected->impl.request_handler.response,\n                    expected->impl.request_handler.response_writer,\n                    (void *) (intptr_t) expected->impl.request_handler\n                            .response_writer_args));\n        }\n\n        if (expected->impl.request_handler.send_request) {\n            avs_coap_request_header_t header = {\n                .code = AVS_COAP_CODE_GET\n            };\n            ASSERT_OK(avs_coap_client_send_async_request(\n                    ctx->coap_ctx, NULL, &header, NULL, NULL, NULL, NULL));\n        }\n    }\n\n    AVS_LIST_DELETE(&expected);\n    return 0;\n}\n\nstatic inline void _expect_request_handler_call_impl(\n        test_env_t *env,\n        avs_coap_server_request_state_t state,\n        const test_msg_t *request,\n        const avs_coap_response_header_t *response,\n        test_payload_writer_args_t *response_writer_args,\n        bool send_request) {\n    test_handler_expected_t *expected =\n            AVS_LIST_APPEND_NEW(test_handler_expected_t, &env->expects_list);\n\n    *expected = (test_handler_expected_t) {\n        .type = EXPECT_REQUEST_HANDLER,\n        .impl = {\n            .request_handler = {\n                .state = state\n            }\n        }\n    };\n\n    if (request) {\n        avs_coap_option_block_t block1 = {\n            .seq_num = 0,\n            .size = 0\n        };\n#    ifdef WITH_AVS_COAP_BLOCK\n        avs_coap_options_get_block(&request->msg.options, AVS_COAP_BLOCK1,\n                                   &block1);\n#    endif // WITH_AVS_COAP_BLOCK\n        expected->impl.request_handler.request =\n                (avs_coap_server_async_request_t) {\n                    .header = request->request_header,\n                    .payload_offset = block1.seq_num * block1.size,\n                    .payload = request->msg.payload,\n                    .payload_size = request->msg.payload_size\n                };\n\n        expected->impl.request_handler.observe_id = (avs_coap_observe_id_t) {\n            .token = request->msg.token\n        };\n\n        expected->impl.request_handler.send_request = send_request;\n    }\n\n    if (response) {\n        expected->impl.request_handler.response = response;\n\n        if (response_writer_args) {\n            expected->impl.request_handler.response_writer =\n                    test_payload_writer;\n            expected->impl.request_handler.response_writer_args =\n                    response_writer_args;\n        }\n\n#    ifdef WITH_AVS_COAP_OBSERVE\n        uint32_t observe_opt;\n        if (request\n                && !avs_coap_options_get_observe(&request->msg.options,\n                                                 &observe_opt)) {\n            expected->impl.request_handler.start_observe = (observe_opt == 0);\n        }\n#    endif // WITH_AVS_COAP_OBSERVE\n    }\n}\n\n/**\n * @p env                  Test environment object to use.\n *\n * @p state                Expected value of the \"state\" argument to the\n *                         request handler.\n *\n * @p request              Request message that is supposed to be passed to\n *                         the request handler.\n *\n * @p response             If not NULL, indicates that\n *                         @ref avs_coap_server_setup_async_response should be\n *                         called from the request_handler call. Response code\n *                         and options are passed through this argument, and\n *                         payload - if any - through @p response_writer_args .\n *\n *                         If options included in @p response contain the\n *                         Observe option, @ref avs_coap_observe_async_start\n *                         will be called before setting up the response.\n *\n * @p response_writer_args If @p response is not NULL - the payload that should\n *                         be passed to the lib for inclusion in configured\n *                         response. Note: @ref test_payload_writer will be\n *                         used to feed that payload to the library.\n *\n *                         MUST live at least as long as @p env .\n *\n * If this function is used, @ref avs_coap_async_handle_incoming_packet\n * MUST be called with @ref avs_coap_async_handle_incoming_packet (as \"request\n * handler\") and @p env (as \"request handler arg\") whenever a request is\n * expected to be handled.\n */\nstatic inline void\nexpect_request_handler_call(test_env_t *env,\n                            avs_coap_server_request_state_t state,\n                            const test_msg_t *request,\n                            const avs_coap_response_header_t *response,\n                            test_payload_writer_args_t *response_writer_args) {\n    _expect_request_handler_call_impl(env, state, request, response,\n                                      response_writer_args, false);\n}\n\n/**\n * Works like @ref expect_request_handler_call but also forces sending a new\n * request from request handler.\n */\nstatic inline void expect_request_handler_call_and_force_sending_request(\n        test_env_t *env,\n        avs_coap_server_request_state_t state,\n        const test_msg_t *request,\n        const avs_coap_response_header_t *response,\n        test_payload_writer_args_t *response_writer_args) {\n    _expect_request_handler_call_impl(env, state, request, response,\n                                      response_writer_args, true);\n}\n\nstatic inline int\ntest_accept_new_request(avs_coap_server_ctx_t *ctx,\n                        const avs_coap_request_header_t *request,\n                        void *env_) {\n    (void) request;\n\n    avs_coap_exchange_id_t id =\n            avs_coap_server_accept_async_request(ctx, test_handle_request,\n                                                 env_);\n    if (!avs_coap_exchange_id_valid(id)) {\n        return AVS_COAP_CODE_INTERNAL_SERVER_ERROR;\n    }\n\n    test_env_t *env = (test_env_t *) env_;\n\n    test_handler_expected_t *element = AVS_LIST_NTH(env->expects_list, 0);\n    if (element && element->impl.request_handler.response_writer_args) {\n        element->impl.request_handler.response_writer_args->exchange_id = id;\n    }\n    return 0;\n}\n\ntypedef struct {\n    const void *data;\n    size_t size;\n    size_t chunk_size;\n} test_streaming_payload_t;\n\nstatic inline int test_streaming_writer(avs_stream_t *stream, void *payload_) {\n    test_streaming_payload_t *payload = (test_streaming_payload_t *) payload_;\n\n    const char *const begin = (const char *) payload->data;\n    const char *const end = begin + payload->size;\n    const size_t chunk_size =\n            payload->chunk_size == 0 ? payload->size : payload->chunk_size;\n\n    for (const char *p = begin; p < end; p += chunk_size) {\n        size_t to_write = AVS_MIN(chunk_size, (size_t) (end - p));\n        avs_stream_write(stream, p, to_write);\n    }\n    return 0;\n}\n\nstatic inline void advance_mockclock(avs_net_socket_t *socket, void *timeout) {\n    (void) socket;\n    _avs_mock_clock_advance(*(const avs_time_duration_t *) timeout);\n}\n\n#endif // WITH_AVS_COAP_UDP\n\n#endif // AVS_COAP_SRC_UDP_TEST_UTILS_H\n"
  },
  {
    "path": "deps/avs_coap/tests/utils.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <avs_coap_init.h>\n\n#ifdef AVS_UNIT_TESTING\n\n#    include <avsystem/coap/coap.h>\n#    include <avsystem/commons/avs_utils.h>\n\n#    include \"utils.h\"\n\n#    define MODULE_NAME test\n#    include <avs_coap_x_log_config.h>\n\nstatic uint64_t GLOBAL_TOKEN_VALUE;\n\nvoid reset_token_generator(void) {\n    GLOBAL_TOKEN_VALUE = 0;\n}\n\navs_error_t _avs_coap_ctx_generate_token(avs_coap_ctx_t *ctx,\n                                         avs_coap_token_t *out_token);\n\navs_error_t _avs_coap_ctx_generate_token(avs_coap_ctx_t *ctx,\n                                         avs_coap_token_t *out_token) {\n    (void) ctx;\n    *out_token = nth_token(GLOBAL_TOKEN_VALUE++);\n    return AVS_OK;\n}\n\navs_coap_token_t nth_token(uint64_t k) {\n    union {\n        uint8_t bytes[sizeof(uint64_t)];\n        uint64_t value;\n    } v;\n    v.value = avs_convert_be64(k);\n\n    avs_coap_token_t token;\n    token.size = sizeof(v.bytes);\n    memcpy(token.bytes, v.bytes, sizeof(v.bytes));\n    return token;\n}\n\navs_coap_token_t current_token(void) {\n    return nth_token(GLOBAL_TOKEN_VALUE);\n}\n\n#endif // AVS_UNIT_TESTING\n"
  },
  {
    "path": "deps/avs_coap/tests/utils.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef AVS_TEST_UTILS_H\n#define AVS_TEST_UTILS_H\n\n#include <math.h>\n\n#include <avsystem/coap/token.h>\n\n#define SCOPED_PTR(Type, Deleter) __attribute__((__cleanup__(Deleter))) Type *\n\navs_coap_token_t nth_token(uint64_t k);\navs_coap_token_t current_token(void);\nvoid reset_token_generator(void);\n\nstatic inline avs_coap_token_t from_bytes(const void *bytes, size_t size) {\n    avs_coap_token_t token;\n    memcpy(token.bytes, bytes, size);\n    token.size = (uint8_t) size;\n    return token;\n}\n\n#define MAKE_TOKEN(Bytes)                                                    \\\n    from_bytes((Bytes),                                                      \\\n               (ASSERT_TRUE(sizeof(Bytes) - 1 <= AVS_COAP_MAX_TOKEN_LENGTH), \\\n                sizeof(Bytes) - 1))\n\n/* Convenience macro for use in COAP_MSG, to allow skipping AVS_COAP_CODE_\n * prefix */\n#define CODE__(x) AVS_COAP_CODE_##x\n\n/* Convenience macro for use in COAP_MSG, to allow skipping AVS_COAP_FORMAT_\n * prefix */\n#define FORMAT__(x) AVS_COAP_FORMAT_##x\n\n// NOTE: The macros below is the common logic between tcp/utils.h and\n// udp/utils.h. They refer to the COAP_MSG() macro and fields of test_msg_t,\n// which differ between those two variants.\n\n/* Used in COAP_MSG() to pass message token. */\n#define TOKEN(Token) .token = (Token)\n\n/* Used in COAP_MSG() to define a non-block message payload from external\n * variable (not only string literal). */\n#define PAYLOAD_EXTERNAL(Payload, PayloadSize) \\\n    .payload = Payload,                        \\\n    .payload_size = PayloadSize\n\n/* Used in COAP_MSG() to define a non-block message payload (string literal).\n * Terminating nullbyte is not considered part of the payload. */\n#define PAYLOAD(Payload) PAYLOAD_EXTERNAL(Payload, sizeof(Payload) - 1)\n\n/* Used in COAP_MSG() to specify a list of Uri-Path options. */\n#define PATH(... /* Segments */) .uri_path = { __VA_ARGS__ }\n\n/* Used in COAP_MSG() to specify an Uri-Host option. */\n#define HOST(Host) .uri_host = Host\n\n/* Used in COAP_MSG() to specify the Accept option. */\n#define ACCEPT(Format)              \\\n    .accept = (const uint16_t[1]) { \\\n        (Format)                    \\\n    }\n\n#define DUPLICATED_ACCEPT(Format)              \\\n    .duplicated_accept = (const uint16_t[1]) { \\\n        (Format)                               \\\n    }\n\n/* Used in COAP_MSG() to specify the Observe option. */\n#define OBSERVE(Value)               \\\n    .observe = (const uint32_t[1]) { \\\n        (Value)                      \\\n    }\n\n/* Used in COAP_MSG() to define a message with no payload or BLOCK options. */\n#define NO_PAYLOAD   \\\n    .payload = NULL, \\\n    .payload_size = 0\n\n#define _BLOCK_WITH_PAYLOAD(N, Seq, Size, Payload)                            \\\n    .block1 = {                                                               \\\n        .type = AVS_COAP_BLOCK1,                                              \\\n        .seq_num = (assert((Seq) < (1 << 23)), (uint32_t) (Seq)),             \\\n        .size = (uint16_t) (((N) == 1) ? (assert((Size) < (1 << 15)), (Size)) \\\n                                       : 0),                                  \\\n        .has_more = ((Seq + 1) * (Size) + 1 < sizeof(Payload))                \\\n    },                                                                        \\\n    .block2 = {                                                               \\\n        .type = AVS_COAP_BLOCK2,                                              \\\n        .seq_num = (assert((Seq) < (1 << 23)), (uint32_t) (Seq)),             \\\n        .size = (uint16_t) (((N) == 2) ? (assert((Size) < (1 << 15)), (Size)) \\\n                                       : 0),                                  \\\n        .has_more = ((Seq + 1) * (Size) + 1 < sizeof(Payload))                \\\n    },                                                                        \\\n    .payload = ((const uint8_t *) (Payload)) + (Seq) * (Size),                \\\n    .payload_size =                                                           \\\n            sizeof(Payload) == sizeof(\"\")                                     \\\n                    ? 0                                                       \\\n                    : ((((Seq) + 1) * (Size) + 1 < sizeof(Payload))           \\\n                               ? (Size)                                       \\\n                               : (sizeof(Payload) - 1 - (Seq) * (Size)))\n\n#define _BLOCK_WITH_UNSPECIFIED_PAYLOAD(N, Seq, Size, HasMore)                \\\n    .block1 = {                                                               \\\n        .type = AVS_COAP_BLOCK1,                                              \\\n        .seq_num = (assert((Seq) < (1 << 23)), (uint32_t) (Seq)),             \\\n        .size = (uint16_t) (((N) == 1) ? (assert((Size) < (1 << 15)), (Size)) \\\n                                       : 0),                                  \\\n        .has_more = (HasMore)                                                 \\\n    },                                                                        \\\n    .block2 = {                                                               \\\n        .type = AVS_COAP_BLOCK2,                                              \\\n        .seq_num = (assert((Seq) < (1 << 23)), (uint32_t) (Seq)),             \\\n        .size = (uint16_t) (((N) == 2) ? (assert((Size) < (1 << 15)), (Size)) \\\n                                       : 0),                                  \\\n        .has_more = (HasMore)                                                 \\\n    }\n\n#define _BLOCK_WITHOUT_PAYLOAD(N, Seq, Size, HasMore)               \\\n    _BLOCK_WITH_UNSPECIFIED_PAYLOAD((N), (Seq), (Size), (HasMore)), \\\n            .payload = NULL,                                        \\\n            .payload_size = 0\n\n#define _BLOCK_12_WITH_PAYLOAD(Seq1, Size1, Size2, Payload)         \\\n    .block1 = {                                                     \\\n        .type = AVS_COAP_BLOCK1,                                    \\\n        .seq_num = (assert((Seq1) < (1 << 23)), (uint32_t) (Seq1)), \\\n        .size = (uint16_t) (assert((Size1) < (1 << 15)), (Size1)),  \\\n        .has_more = false                                           \\\n    },                                                              \\\n    .block2 = {                                                     \\\n        .type = AVS_COAP_BLOCK2,                                    \\\n        .seq_num = 0,                                               \\\n        .size = (uint16_t) (assert((Size2) < (1 << 15)), (Size2)),  \\\n        .has_more = ((Size2) + 1 < sizeof(Payload))                 \\\n    },                                                              \\\n    .payload = ((const uint8_t *) (Payload)),                       \\\n    .payload_size = sizeof(Payload) == sizeof(\"\")                   \\\n                            ? 0                                     \\\n                            : ((Size2) + 1 < sizeof(Payload))       \\\n                                      ? (Size2)                     \\\n                                      : (sizeof(Payload) - 1)\n\n/**\n * Used in COAP_MSG to define BLOCK1 option and define request payload.\n * @p Seq     - the block sequence number.\n * @p Size    - block size.\n * @p Payload - if specified, FULL PAYLOAD OF WHOLE BLOCK-WISE TRANSFER (!).\n *              The macro will extract the portion of it based on Seq and Size.\n *              Terminating nullbyte is not considered part of the payload.\n */\n#define BLOCK1_REQ(Seq, Size, ... /* Payload */) \\\n    _BLOCK_WITH_PAYLOAD(1, (Seq), (Size), __VA_ARGS__)\n\n/**\n * Used in COAP_MSG to define BLOCK1 option for use in responses to BLOCK\n * requests.\n *\n * @p Seq     - the block sequence number.\n * @p Size    - block size.\n * @p HasMore - false if the packet is a response to last request block,\n *              true otherwise.\n */\n#define BLOCK1_RES(Seq, Size, HasMore) \\\n    _BLOCK_WITH_UNSPECIFIED_PAYLOAD(1, (Seq), (Size), (HasMore))\n\n/**\n * Used in COAP_MSG to define BLOCK2 option for use in requests for BLOCK\n * payloads.\n *\n * @p Seq     - the block sequence number.\n * @p Size    - block size.\n */\n#define BLOCK2_REQ(Seq, Size) _BLOCK_WITHOUT_PAYLOAD(2, (Seq), (Size), false)\n\n/**\n * Used in COAP_MSG to define BLOCK2 option for use in requests for BLOCK\n * payloads. Also, it allows to set some unrelated payload in the message.\n *\n * @p Seq     - the block sequence number.\n * @p Size    - block size.\n * @p Payload - message payload.\n */\n#define BLOCK2_REQ_WITH_REGULAR_PAYLOAD(Seq, Size, Payload)       \\\n    .block2 = {                                                   \\\n        .type = AVS_COAP_BLOCK2,                                  \\\n        .seq_num = (assert((Seq) < (1 << 23)), (uint32_t) (Seq)), \\\n        .size = (uint16_t) (assert((Size) < (1 << 15)), (Size)),  \\\n        .has_more = false                                         \\\n    },                                                            \\\n    .payload = ((const uint8_t *) (Payload)),                     \\\n    .payload_size = sizeof(Payload) == sizeof(\"\") ? 0 : (sizeof(Payload) - 1)\n\n/**\n * Used in COAP_MSG to define BLOCK2 option and define response payload.\n * @p Seq     - the block sequence number.\n * @p Size    - block size.\n * @p Payload - if specified, FULL PAYLOAD OF WHOLE BLOCK-WISE TRANSFER (!).\n *              The macro will extract the portion of it based on Seq and Size.\n *              Terminating nullbyte is not considered part of the payload.\n */\n#define BLOCK2_RES(Seq, Size, ... /* Payload */) \\\n    _BLOCK_WITH_PAYLOAD(2, (Seq), (Size), __VA_ARGS__)\n\n/**\n * Used in COAP_MSG to define final BLOCK1, initial BLOCK2 and payload.\n *\n * Implies:\n * - BLOCK1.has_more == false\n * - BLOCK2.seq_num == 0\n *\n * @p Seq1    - the BLOCK1 sequence number.\n * @p Size1   - request block size.\n * @p Size2   - response block size.\n * @p Payload - if specified, FULL PAYLOAD OF WHOLE BLOCK-WISE RESPONSE (!),\n *              given as a string literal. The macro will extract the\n *              first portion based on Size. Terminating nullbyte is not\n *              considered part of the payload.\n */\n#define BLOCK1_AND_2_RES(Seq1, Size1, Size2, ... /* Payload */) \\\n    _BLOCK_12_WITH_PAYLOAD((Seq1), (Size1), (Size2), \"\" __VA_ARGS__)\n\n/**\n * Used in COAP_MSG to define not-necessairly final BLOCK1 with payload, and\n * some BLOCK2.\n *\n * Implies:\n * - BLOCK2.has_more == false\n *\n * @p Seq1     - the BLOCK1 sequence number.\n * @p Size1    - request block size.\n * @p Size2    - response block size.\n * @p Payload1 - if specified, FULL PAYLOAD OF WHOLE BLOCK-WISE RESPONSE (!),\n *               given as a string literal. The macro will extract the\n *               first portion based on Size. Terminating nullbyte is not\n *               considered part of the payload.\n */\n#define BLOCK1_REQ_AND_2_RES(Seq1, Size1, Size2, Payload1)             \\\n    .block1 = {                                                        \\\n        .type = AVS_COAP_BLOCK1,                                       \\\n        .seq_num = (assert((Seq1) < (1 << 23)), (uint32_t) (Seq1)),    \\\n        .size = (uint16_t) (assert((Size1) < (1 << 15)), (Size1)),     \\\n        .has_more = ((Seq1 + 1) * (Size1) + 1 < sizeof(Payload1))      \\\n    },                                                                 \\\n    .block2 = {                                                        \\\n        .type = AVS_COAP_BLOCK2,                                       \\\n        .seq_num = 0,                                                  \\\n        .size = (uint16_t) (assert((Size2) < (1 << 15)), (Size2)),     \\\n        .has_more = false                                              \\\n    },                                                                 \\\n    .payload = ((const uint8_t *) (Payload1)) + (Seq1) * (Size1),      \\\n    .payload_size =                                                    \\\n            sizeof(Payload1) == sizeof(\"\")                             \\\n                    ? 0                                                \\\n                    : ((((Seq1) + 1) * (Size1) + 1 < sizeof(Payload1)) \\\n                               ? (Size1)                               \\\n                               : (sizeof(Payload1) - 1 - (Seq1) * (Size1)))\n\n// As defined in RFC8323, BERT option indicates multiple blocks of size 1024\n#define BERT_BLOCK_SIZE 1024\n\n#define _BERT_WITH_PAYLOAD(N, Seq, Size, Payload)                              \\\n    .block1 = {                                                                \\\n        .type = AVS_COAP_BLOCK1,                                               \\\n        .seq_num = (assert((Seq) < (1 << 23)), (uint32_t) (Seq)),              \\\n        .size = (uint16_t) (((N) == 1) ? (assert((Size) >= BERT_BLOCK_SIZE),   \\\n                                          BERT_BLOCK_SIZE)                     \\\n                                       : 0),                                   \\\n        .has_more = ((Seq) *BERT_BLOCK_SIZE + (Size) + 1 < sizeof(Payload)),   \\\n        .is_bert = ((N) == 1)                                                  \\\n    },                                                                         \\\n    .block2 = {                                                                \\\n        .type = AVS_COAP_BLOCK2,                                               \\\n        .seq_num = (assert((Seq) < (1 << 23)), (uint32_t) (Seq)),              \\\n        .size = (uint16_t) (((N) == 2) ? (assert((Size) >= BERT_BLOCK_SIZE),   \\\n                                          BERT_BLOCK_SIZE)                     \\\n                                       : 0),                                   \\\n        .has_more = ((Seq) *BERT_BLOCK_SIZE + (Size) + 1 < sizeof(Payload)),   \\\n        .is_bert = ((N) == 2)                                                  \\\n    },                                                                         \\\n    .payload = ((const uint8_t *) (Payload)) + (Seq) *BERT_BLOCK_SIZE,         \\\n    .payload_size =                                                            \\\n            sizeof(Payload) == sizeof(\"\")                                      \\\n                    ? 0                                                        \\\n                    : (((Seq) *BERT_BLOCK_SIZE + (Size) + 1 < sizeof(Payload)) \\\n                               ? (Size)                                        \\\n                               : (sizeof(Payload) - 1                          \\\n                                  - (Seq) *BERT_BLOCK_SIZE))\n\n#define _BERT_WITHOUT_PAYLOAD(N, Seq, HasMore)                    \\\n    .block1 = {                                                   \\\n        .type = AVS_COAP_BLOCK1,                                  \\\n        .seq_num = (assert((Seq) < (1 << 23)), (uint32_t) (Seq)), \\\n        .size = ((N) == 1) ? BERT_BLOCK_SIZE : 0,                 \\\n        .has_more = (HasMore),                                    \\\n        .is_bert = ((N) == 1)                                     \\\n    },                                                            \\\n    .block2 = {                                                   \\\n        .type = AVS_COAP_BLOCK2,                                  \\\n        .seq_num = (assert((Seq) < (1 << 23)), (uint32_t) (Seq)), \\\n        .size = ((N) == 2) ? BERT_BLOCK_SIZE : 0,                 \\\n        .has_more = (HasMore),                                    \\\n        .is_bert = ((N) == 2)                                     \\\n    },                                                            \\\n    .payload = NULL,                                              \\\n    .payload_size = 0\n\n#define _BERT1_BLOCK2_WITH_PAYLOAD(Seq1, Size2, Payload)            \\\n    .block1 = {                                                     \\\n        .type = AVS_COAP_BLOCK1,                                    \\\n        .seq_num = (assert((Seq1) < (1 << 23)), (uint32_t) (Seq1)), \\\n        .size = BERT_BLOCK_SIZE,                                    \\\n        .has_more = false,                                          \\\n        .is_bert = true                                             \\\n    },                                                              \\\n    .block2 = {                                                     \\\n        .type = AVS_COAP_BLOCK2,                                    \\\n        .seq_num = 0,                                               \\\n        .size = (uint16_t) (assert((Size2) < (1 << 15)), (Size2)),  \\\n        .has_more = ((Size2) + 1 < sizeof(Payload))                 \\\n    },                                                              \\\n    .payload = ((const uint8_t *) (Payload)),                       \\\n    .payload_size = sizeof(Payload) == sizeof(\"\")                   \\\n                            ? 0                                     \\\n                            : ((Size2) + 1 < sizeof(Payload))       \\\n                                      ? (Size2)                     \\\n                                      : (sizeof(Payload) - 1)\n\n// Don't use macros below with Size smaller than 1024 bytes. Use BLOCK macros\n// instead.\n#define BERT1_REQ(Seq, Size, ... /* Payload */) \\\n    _BERT_WITH_PAYLOAD(1, (Seq), (Size), __VA_ARGS__)\n\n#define BERT1_RES(Seq, HasMore) _BERT_WITHOUT_PAYLOAD(1, (Seq), (HasMore))\n\n#define BERT2_REQ(Seq) _BERT_WITHOUT_PAYLOAD(2, (Seq), false)\n\n#define BERT2_RES(Seq, Size, ... /* Payload */) \\\n    _BERT_WITH_PAYLOAD(2, (Seq), (Size), __VA_ARGS__)\n\n#define BERT2_REQ(Seq) _BERT_WITHOUT_PAYLOAD(2, (Seq), false)\n\n#define BERT2_RES(Seq, Size, ... /* Payload */) \\\n    _BERT_WITH_PAYLOAD(2, (Seq), (Size), __VA_ARGS__)\n\n#define BERT1_AND_BLOCK2_RES(Seq1, Size2, ... /* Payload */) \\\n    _BERT1_BLOCK2_WITH_PAYLOAD((Seq1), (Size2), \"\" __VA_ARGS__)\n\n#define DATA_16B \"123456789abcdef \"\n#define DATA_32B DATA_16B DATA_16B\n#define DATA_64B DATA_32B DATA_32B\n#define DATA_256B DATA_64B DATA_64B DATA_64B DATA_64B\n#define DATA_1KB DATA_256B DATA_256B DATA_256B DATA_256B\n#define DATA_2KB DATA_1KB DATA_1KB\n\n#endif /* AVS_TEST_UTILS_H */\n"
  },
  {
    "path": "deps/avs_coap/tools/conditional_headers_whitelist.json",
    "content": "{\n    \"\": [\n        \"avs_coap_init\\\\.h\",\n        \"avs_coap_x_log_config\\\\.h\",\n        \"avsystem/coap/[^.]*\\\\.h\",\n        \"avsystem/commons/[^.]*\\\\.h\"\n    ]\n}\n"
  },
  {
    "path": "deps/avs_coap/tools/coverage",
    "content": "#!/usr/bin/env bash\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem CoAP library\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nset -e\n\n. \"$(dirname \"$0\")/deploy-utils/utils.sh\"\n\nfunction die() {\n    echo -e \"$@\" >&2\n    exit 1\n}\n\nwhich gcc || die \"gcc not found, exiting\"\nwhich lcov || die \"lcov not found, exiting\"\nwhich genhtml || die \"genhtml not found, exiting\"\n\nGCC_VERSION=$(gcc --version 2>&1 | head -n 1 | awk 'END {print $NF}')\nGCC_MAJOR_VERSION=${GCC_VERSION%%.*}\nLCOV_VERSION=$(lcov --version 2>&1 | head -n 1 | awk 'END {print $NF}')\nLCOV_MAJOR_VERSION=${LCOV_VERSION%%.*}\n\nif [ \"$LCOV_MAJOR_VERSION\" -gt 1 ]; then\n    LCOV_ADDITIONAL_OPTS=\"--rc branch_coverage=1 --ignore-errors mismatch\"\nelse\n    LCOV_ADDITIONAL_OPTS=\"--rc lcov_branch_coverage=1\"\nfi\n\n[[ \"$PROJECT_ROOT\" ]] || PROJECT_ROOT=\"$(dirname \"$(dirname \"$(canonicalize \"$0\")\")\")\"\n\nrm -rf \"$PROJECT_ROOT/build/coverage\"\nmkdir -p \"$PROJECT_ROOT/build/coverage\"\npushd \"$PROJECT_ROOT/build/coverage\"\n    \"$PROJECT_ROOT/devconfig\" -DCMAKE_C_FLAGS=\"-std=c99 -D_POSIX_C_SOURCE=200809L -g -O0 --coverage\" -DCMAKE_EXE_LINKER_FLAGS=\"--coverage\" \"$@\"\n    make check -j$(num_processors)\n    mkdir -p \"$PROJECT_ROOT/coverage\"\n    lcov $LCOV_ADDITIONAL_OPTS -c -d . -o coverage.info --gcov-tool /usr/bin/gcov-$GCC_MAJOR_VERSION\n    lcov $LCOV_ADDITIONAL_OPTS --remove coverage.info \"$PROJECT_ROOT/tests/*\" \"/usr/*\" \"$PROJECT_ROOT/include_public/*\" \"$PROJECT_ROOT/deps/*\" -o coverage.info\n    genhtml coverage.info --branch-coverage --function-coverage --output-directory \"$PROJECT_ROOT/coverage\"\npopd\n\ncat <<EOF\n\n-----\nCoverage report generated in $PROJECT_ROOT/coverage\nEOF\n"
  },
  {
    "path": "devconfig",
    "content": "#!/usr/bin/env bash\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\n. \"$(dirname \"$0\")/tools/utils.sh\"\n\nSCRIPT_DIR=\"$(dirname \"$(canonicalize \"$0\")\")\"\nrm -rf avs_commons/install\nrm -rf deps/avs_commons/CMakeFiles\nrm -f deps/avs_commons/CMakeCache.txt\n\nANJAY_VERSION=\"unknown\"\n\nif [[ -d '.git' ]]; then\n    echo \"Updating submodules\" && git submodule update --init --recursive || exit 1\n    ANJAY_VERSION=$(git rev-parse --short HEAD)\nfi\n\nC_FLAGS='-g -std=c99 -Werror=implicit-function-declaration'\n\nEXTRA_C_FLAGS=\nEXTRA_FLAGS=()\n\nMODULE_DYNAMIC_LIBS='ON'\nMODULE_STATIC_LIBS='ON'\nDTLS_BACKEND=\"mbedtls\"\nWITH_STATIC_ANALYSIS='ON'\nWITH_EXAMPLES='ON'\n\nif valgrind --version >/dev/null 2>/dev/null; then\n    WITH_VALGRIND=ON\n    WITH_ASAN=OFF\nelse\n    WITH_VALGRIND=OFF\n    WITH_ASAN=ON\nfi\n\nif ! which afl-gcc &>/dev/null; then\n    if [[ -d \"$HOME/tools/afl/latest\" ]]; then\n        AFL_FUZZER_DIR=\"$HOME/tools/afl/latest\"\n    fi\nfi\n\n# in case both CMake 2 and 3 is available, prefer newer\nCMAKE_COMMAND=cmake\nwhich cmake3 &>/dev/null && CMAKE_COMMAND=cmake3\n\n# argument parsing\nwhile [ $# -gt 0 ]; do\n    case \"$1\" in\n        '--c-flags')\n            shift\n            C_FLAGS=\"$1\"\n            ;;\n        '--fuzz-tests')\n            EXTRA_FLAGS[${#EXTRA_FLAGS[@]}]=\"-DCMAKE_TOOLCHAIN_FILE=$SCRIPT_DIR/cmake/toolchain/afl-gcc.cmake\"\n            EXTRA_FLAGS[${#EXTRA_FLAGS[@]}]=\"-DWITH_FUZZ_TESTS=ON\"\n            [[ \"$AFL_FUZZER_DIR\" ]] && EXTRA_FLAGS[${#EXTRA_FLAGS[@]}]=\"-DAFL_FUZZER_DIR=$AFL_FUZZER_DIR\"\n            ;;\n        '--with-mbedtls')\n            DTLS_BACKEND=\"mbedtls\"\n            ;;\n        '--with-openssl')\n            DTLS_BACKEND=\"openssl\"\n            ;;\n        '--with-tinydtls')\n            DTLS_BACKEND=\"tinydtls\"\n            ;;\n        '--without-dtls')\n            DTLS_BACKEND=\n            ;;\n        '--with-valgrind')\n            WITH_ASAN=OFF\n            WITH_VALGRIND=ON\n            ;;\n        '--with-asan')\n            WITH_VALGRIND=OFF\n            WITH_ASAN=ON\n            ;;\n        '--without-memcheck')\n            WITH_ASAN=OFF\n            WITH_VALGRIND=OFF\n            ;;\n        '--without-analysis')\n            WITH_STATIC_ANALYSIS=OFF\n            ;;\n        '--no-examples')\n            WITH_EXAMPLES=OFF\n            ;;\n        '--tiny')\n            EXTRA_FLAGS[${#EXTRA_FLAGS[@]}]=\"-DMAX_PK_OR_IDENTITY_SIZE=256\"\n            EXTRA_FLAGS[${#EXTRA_FLAGS[@]}]=\"-DMAX_SECRET_KEY_SIZE=128\"\n            EXTRA_FLAGS[${#EXTRA_FLAGS[@]}]=\"-DMAX_DOUBLE_STRING_SIZE=64\"\n            EXTRA_FLAGS[${#EXTRA_FLAGS[@]}]=\"-DMAX_URI_SEGMENT_SIZE=64\"\n            EXTRA_FLAGS[${#EXTRA_FLAGS[@]}]=\"-DMAX_URI_QUERY_SEGMENT_SIZE=64\"\n            EXTRA_FLAGS[${#EXTRA_FLAGS[@]}]=\"-DMAX_HOLDOFF_TIME=20\"\n            ;;\n        -DDTLS_BACKEND=*)\n            DTLS_BACKEND=\"${1#-DDTLS_BACKEND=}\"\n            ;;\n        -D)\n            if [[ $# -gt 1 && $2 =~ ^DTLS_BACKEND= ]]; then\n                DTLS_BACKEND=\"${2#DTLS_BACKEND=}\"\n                shift\n            else\n                EXTRA_FLAGS[${#EXTRA_FLAGS[@]}]=\"$1\"\n            fi\n            ;;\n        *)\n            EXTRA_FLAGS[${#EXTRA_FLAGS[@]}]=\"$1\"\n            ;;\n    esac\n    shift\ndone\n\nif [ \"$WITH_ASAN\" == \"ON\" ]; then\n    EXTRA_C_FLAGS=\"${EXTRA_C_FLAGS} -fsanitize=address\"\nfi\n\n# use Homebrew-installed OpenSSL on macOS if available\nBREW_OPENSSL=\"$(brew --prefix openssl 2>/dev/null)\"\nif [ \"$BREW_OPENSSL\" ]; then\n    EXTRA_FLAGS[${#EXTRA_FLAGS[@]}]=\"-DOPENSSL_ROOT_DIR=$BREW_OPENSSL\"\nfi\n\n# sysctl for macOS, nproc for most of everything else, echo 1 as a last resort\nNPROC=\"$((nproc || sysctl -n hw.logicalcpu || echo 1) 2>/dev/null)\"\n\n# ensure venv is used\nVENV_DIR=\"${VENV_DIR:-venv}\"\nif python3 -c 'import sys; exit(0) if sys.base_prefix != sys.prefix else exit(1)'; then\n    echo \"Using already activated Python venv\"\nelse\n    if [ ! -d \"${VENV_DIR}\" ]; then\n        echo \"Creating Python venv in ${VENV_DIR}\"\n        python3 -m venv \"${VENV_DIR}\"\n    fi\n\n    echo \"Activating Python venv in ${VENV_DIR}\"\n\n    # shellcheck disable=SC1091\n    source \"${VENV_DIR}/bin/activate\"\n    python3 -m pip install --upgrade pip\n    python3 -m pip install -r $SCRIPT_DIR/requirements.txt\n\nfi\n\n# Not placing it in dockerfile to allow easier updates and ci runs\n# without rebuilding the images\npython3 -m pip install -e $SCRIPT_DIR/tools/test-framework-tools/tools\npython3 -m pip install -e $SCRIPT_DIR/tests/integration/framework\n\nrm -f CMakeCache.txt\nrm -rf CMakeFiles\n${CMAKE_COMMAND} \\\n    -D WITH_TEST=ON \\\n    -D WITH_LICENSE_TEST=ON \\\n    -D WITH_DEMO=ON \\\n    -D WITH_DOCS=ON \\\n    -D WITH_EXTRA_WARNINGS=ON \\\n    -D WITH_CON_ATTR=ON \\\n    -D WITH_HTTP_DOWNLOAD=ON \\\n    -D WITH_THREAD_SAFETY=ON \\\n    -D WITH_VALGRIND=${WITH_VALGRIND} \\\n    -D WITH_INTEGRATION_TESTS=ON \\\n    -D WITH_DOC_CHECK=ON \\\n    -D WITH_STATIC_ANALYSIS=${WITH_STATIC_ANALYSIS} \\\n    -D WITH_MODULE_advanced_fw_update=ON \\\n    -D WITH_MODULE_sw_mgmt=ON \\\n    -D DTLS_BACKEND=\"${DTLS_BACKEND}\" \\\n    -D AVS_LOG_WITH_TRACE=ON \\\n    -D WITH_EXAMPLES=${WITH_EXAMPLES} \\\n    -D CMAKE_C_FLAGS=\"${C_FLAGS} ${EXTRA_C_FLAGS}\" \\\n    -D CMAKE_CXX_FLAGS=\"${EXTRA_C_FLAGS}\" \\\n    -D ANJAY_VERSION=\"${ANJAY_VERSION}\" \\\n    -D NPROC=\"$((3 * NPROC))\" \\\n    \"${EXTRA_FLAGS[@]}\" \\\n    -H\"$(dirname \"$0\")\" -B. &&\nmake clean\n"
  },
  {
    "path": "doc/CMakeLists.txt",
    "content": "# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\ninclude(../cmake/requirePython3venv.cmake)\nget_venv_check_command(CHECK_VENV)\n\nset(ANJAY_DOC_SOURCE_MAIN \"${CMAKE_CURRENT_SOURCE_DIR}/sphinx/source\")\nset(ANJAY_DOC_SOURCE_API \"${CMAKE_CURRENT_SOURCE_DIR}/sphinx/source_api\")\n\nset(ANJAY_DOC_CONF_DIR_BASE \"${ANJAY_BUILD_OUTPUT_DIR}/doc/sphinx\")\nset(ANJAY_DOC_OUTPUT_DIR \"${ANJAY_DOC_CONF_DIR_BASE}/html\")\n\nset(DOXYGEN_OUTPUT_DIR \"${ANJAY_BUILD_OUTPUT_DIR}/doc/doxygen\")\nset(DOXYGEN_INPUT_PATHS \"${CMAKE_BINARY_DIR}/output/doc/doxygen/config\")\nset(DOXYGEN_PREDEFINED_FILE \"${CMAKE_CURRENT_BINARY_DIR}/doxygen/Doxyfile.defines\")\n\ncmake_dependent_option(WITH_DOC_CHECK \"Enables/disables sphinx documentation validation\" OFF WITH_TEST OFF)\ncmake_dependent_option(WITH_URL_CHECK \"Enables/disables URL validation\" OFF \"WITH_TEST;WITH_DOC_CHECK\" OFF)\ncmake_dependent_option(WITH_STRICT_URL_CHECK \"Enables/disables strict URL validation\" OFF \"WITH_TEST;WITH_DOC_CHECK;WITH_URL_CHECK\" OFF)\n\nfind_program(SPHINX_BUILD_EXECUTABLE sphinx-build\n                 HINTS $ENV{SPHINX_DIR} PATH_SUFFIXES bin)\nfind_package(Doxygen)\n\n# make sure both programs are available and templates for configs exist\nif(SPHINX_BUILD_EXECUTABLE AND DOXYGEN_FOUND AND\n   EXISTS \"${ANJAY_DOC_SOURCE_MAIN}/conf.py.in\" AND\n   EXISTS \"${ANJAY_DOC_SOURCE_API}/conf.py.in\" AND\n   EXISTS \"${ANJAY_SOURCE_DIR}/Doxyfile.in\")\n\n    # generate all-enabled config and extract the options to DOXYGEN_PREDEFINED_FILE\n    # see tools/generate_doxygen_config.py script description\n    execute_process(\n        COMMAND python3 ${CMAKE_SOURCE_DIR}/tools/generate_doxygen_config.py\n                \"${DOXYGEN_PREDEFINED_FILE}\"\n        RESULT_VARIABLE _gen_result\n    )\n\n    if(NOT _gen_result EQUAL 0)\n        message(FATAL_ERROR \"Failed to generate config and predefined macros\")\n    endif()\n\n    # read from DOXYGEN_PREDEFINED_FILE file and list all the options to DOXYFILE_PREDEFINED_MACROS\n    file(READ \"${DOXYGEN_PREDEFINED_FILE}\" DOXYFILE_PREDEFINED_MACROS)\n    string(STRIP \"${DOXYFILE_PREDEFINED_MACROS}\" DOXYFILE_PREDEFINED_MACROS)\n\n    # generatge Doxyfile basing on set variables and Doxyfile.in template\n    configure_file(${ANJAY_SOURCE_DIR}/Doxyfile.in\n                   ${CMAKE_CURRENT_BINARY_DIR}/doxygen/Doxyfile.with-cmake-generator-expressions\n                   @ONLY)\n    file(GENERATE OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/doxygen/Doxyfile\n         INPUT ${CMAKE_CURRENT_BINARY_DIR}/doxygen/Doxyfile.with-cmake-generator-expressions)\n\n    # configure Sphinx config file for main documentation\n    set(CURRENT_TEMPLATES_DIR \"${ANJAY_DOC_SOURCE_MAIN}/_templates\")\n    configure_file(${ANJAY_DOC_SOURCE_MAIN}/conf.py.in\n                   ${ANJAY_DOC_CONF_DIR_BASE}/conf_main/conf.py\n                   @ONLY)\n\n    # configure Sphinx config file for API docs\n    set(CURRENT_TEMPLATES_DIR \"${ANJAY_DOC_SOURCE_API}/_templates\")\n    configure_file(${ANJAY_DOC_SOURCE_API}/conf.py.in\n                   ${ANJAY_DOC_CONF_DIR_BASE}/conf_api/conf.py\n                   @ONLY)\n\n    # declare the common target for docs\n    add_custom_target(anjay_doc\n        # ensure venv is active with funciton and command imported from requirePython3venv.cmake\n        COMMAND ${CHECK_VENV}\n\n        # ensure headers in output folder are up-to-date and generate Doxygen XML files\n        # (smart/conditional copy and build inside this script)\n        COMMAND python3 ${CMAKE_SOURCE_DIR}/tools/build_doxygen_docs.py\n                \"${DOXYGEN_INPUT_PATHS}\"\n                \"${CMAKE_CURRENT_BINARY_DIR}/doxygen/Doxyfile\"\n                \"${DOXYGEN_OUTPUT_DIR}/doxygen.md5\"\n\n        # generate Sphinx API docs\n        COMMAND python3 ${CMAKE_SOURCE_DIR}/tools/build_sphinx_docs.py\n                \"${ANJAY_DOC_SOURCE_API}\"\n                \"${ANJAY_DOC_CONF_DIR_BASE}/conf_api\"\n                \"${ANJAY_DOC_OUTPUT_DIR}/api\"\n                \"${ANJAY_DOC_OUTPUT_DIR}/api/sphinx.md5\"\n                \"${DOXYGEN_OUTPUT_DIR}/xml\"\n\n        # generate Sphinx main documentation\n        COMMAND python3 ${CMAKE_SOURCE_DIR}/tools/build_sphinx_docs.py\n                \"${ANJAY_DOC_SOURCE_MAIN}\"\n                \"${ANJAY_DOC_CONF_DIR_BASE}/conf_main\"\n                \"${ANJAY_DOC_OUTPUT_DIR}\"\n                \"${ANJAY_DOC_OUTPUT_DIR}/sphinx.md5\"\n\n        WORKING_DIRECTORY ${ANJAY_SOURCE_DIR}\n        COMMENT \"Building Complete Documentation (Doxygen -> Sphinx API -> Sphinx Main)\"\n        VERBATIM\n    )\n\n    # add target and append it to doc (if exists)\n    if(NOT TARGET doc)\n        add_custom_target(doc)\n    endif()\n    add_dependencies(doc anjay_doc)\n\n    # optional documentation tests\n    if(WITH_DOC_CHECK)\n        # sphinx-based .. snippet-source:: validation\n        add_test(NAME test_doc_snippet\n                 COMMAND ${SPHINX_BUILD_EXECUTABLE}\n                         -Q -b snippet_source_lint\n                         -c ${ANJAY_DOC_CONF_DIR_BASE}/conf_main\n                         ${ANJAY_DOC_SOURCE_MAIN}\n                         ${ANJAY_DOC_CONF_DIR_BASE}/lint\n                 WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/sphinx)\n        if(WITH_URL_CHECK)\n            if(WITH_STRICT_URL_CHECK)\n                add_test(NAME test_doc_url\n                        COMMAND ./runtest.py --strict\n                        WORKING_DIRECTORY ${CMAKE_HOME_DIRECTORY}/tests/doc)\n            else()\n                add_test(NAME test_doc_url\n                        COMMAND ./runtest.py\n                        WORKING_DIRECTORY ${CMAKE_HOME_DIRECTORY}/tests/doc)\n            endif()\n        endif()\n        set_property(TEST test_doc_snippet APPEND PROPERTY ENVIRONMENT\n                     \"ANJAY_DOC_CONF_DIR_BASE=${ANJAY_DOC_CONF_DIR_BASE}\"\n                     \"ANJAY_SPHINX_DOC_ROOT_DIR=${ANJAY_DOC_SOURCE_MAIN}\"\n                     \"CMAKE_SOURCE_DIR=${CMAKE_SOURCE_DIR}\"\n                     \"SNIPPET_SOURCE_MD5FILE=${CMAKE_CURRENT_SOURCE_DIR}/sphinx/snippet_sources.md5\")\n\n        add_custom_target(anjay_doc_check COMMAND ${CMAKE_CTEST_COMMAND} -V -R \"'^test_doc_.*$$'\")\n        add_dependencies(anjay_unit_check anjay_doc_check)\n    endif()\nelse()\n    message(STATUS \"Documentation build disabled: Sphinx or Doxygen not found, or configuration files missing.\")\nendif()\n"
  },
  {
    "path": "doc/sphinx/extensions/builders/__init__.py",
    "content": "# -*- coding: utf-8 -*-\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n"
  },
  {
    "path": "doc/sphinx/extensions/builders/dummy.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\ntry:\n    from sphinx.builders.dummy import DummyBuilder\nexcept ImportError:\n    # dummy builder available in sphinx 1.4+\n    from sphinx.builders import Builder\n\n\n    class DummyBuilder(Builder):\n        name = 'dummy'\n        allow_parallel = True\n\n        def init(self): pass\n\n        def get_outdated_docs(self): return self.env.found_docs\n\n        def get_target_uri(self, docname, typ=None): return ''\n\n        def prepare_writing(self, docnames): pass\n\n        def write_doc(self, docname, doctree): pass\n\n        def finish(self): pass\n"
  },
  {
    "path": "doc/sphinx/extensions/builders/snippet_source_linter.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nimport collections\nimport os\nimport re\n\nfrom builders.dummy import DummyBuilder\nfrom file_dirtiness_checker import FileDirtinessChecker\nfrom snippet_source import SnippetSourceNode\n\n\nclass CodeChunk:\n    \"\"\"\n    A chunk of code that should be found in source file (+/- indents).\n    \"\"\"\n\n    def __init__(self,\n                 doc_source_path,\n                 doc_source_start_lineno,\n                 code_source_path,\n                 chunk_code):\n        \"\"\"\n        @param doc_source_path: str\n                - path to a documentation source @p chunk_code was\n                  extracted from.\n        @param doc_source_start_lineno: int\n                - line number at which @p chunk_code can be found in\n                  @p doc_source_path, used to provide extra debug info.\n        @param code_source_path: str\n                - path to the source code file the chunk should be in.\n        @param chunk_code: str\n                - code snippet, as found in docs.\n        \"\"\"\n        self.doc_source_path = doc_source_path\n        self.doc_source_start_lineno = doc_source_start_lineno\n        self.code_source_path = code_source_path\n        self.code = chunk_code\n\n    def to_regex(self):\n        \"\"\"\n        @returns: str\n                - regex matching the chunk represented by SELF +/- indents.\n        \"\"\"\n        return r'\\s+'.join(re.escape(line.strip())\n                           for line in self.code.split('\\n') if line.strip())\n\n    def __str__(self):\n        return ('----- BEGIN CHUNK -----\\n'\n                'doc:  %s line %d\\n'\n                'code: %s\\n'\n                '----- CODE -----\\n'\n                '%s\\n'\n                '----- END CHUNK -----'\n                % (self.doc_source_path, self.doc_source_start_lineno,\n                   self.code_source_path, self.code))\n\n\nclass CodeSnippet:\n    def __init__(self,\n                 doc_source_path,\n                 doc_source_start_lineno,\n                 code_source_path,\n                 code):\n        \"\"\"\n        Code snippet may consist of multiple chunks separated by\n        SNIPPET_SEPARATOR (i.e. \"// ... something here\" comment). In an actual\n        source file there may be some code in place of the separator - that\n        should not trigger a mismatch error.\n\n        @param doc_source_path: str\n                - path to a documentation source @p code_lines were\n                  extracted from.\n        @param doc_source_start_lineno: int\n                - line number of the first line in @p code_lines in\n                  @p doc_source_path, used to provide extra debug info.\n        @param code_source_path: str\n                - path to a source file @p code_lines associated with\n                  @p code_lines (through \".. snippet-source:\" comment).\n                  Relative to PROJECT_SOURCE_ROOT.\n        @param code: str\n                - code snippet extracted from @p doc_source_path file.\n        \"\"\"\n        SNIPPET_SEPARATOR = r'^\\s*//\\s*\\.\\.\\..*$'\n\n        self.code_source_path = code_source_path\n        self.chunks = []\n\n        chunk_lines = []\n        start_idx = 0\n\n        for idx, line in enumerate(code.split('\\n')):\n            if re.match(SNIPPET_SEPARATOR, line):\n                self.chunks.append(CodeChunk(doc_source_path,\n                                             doc_source_start_lineno + start_idx,\n                                             code_source_path,\n                                             '\\n'.join(chunk_lines)))\n                chunk_lines = []\n                start_idx = idx + 1\n            else:\n                chunk_lines.append(line)\n\n        self.chunks.append(CodeChunk(doc_source_path,\n                                     doc_source_start_lineno + start_idx,\n                                     code_source_path,\n                                     '\\n'.join(chunk_lines)))\n\n    def get_invalid_chunks(self):\n        \"\"\"\n        @returns: List[CodeChunk]\n                - a list of chunks that were not found in associated source\n                  code files.\n        \"\"\"\n        with open(os.path.join(os.environ['CMAKE_SOURCE_DIR'], self.code_source_path)) as f:\n            source = f.read()\n\n        invalid_chunks = []\n\n        for chunk in self.chunks:\n            if not re.search(chunk.to_regex(), source):\n                invalid_chunks.append(chunk)\n\n        return invalid_chunks\n\n\nDocSourceErrors = collections.namedtuple('DocSourceErrors',\n                                         ['invalid_chunks',  # List[CodeChunk]\n                                          'dirty_referenced_paths',  # Set[source_path: str]\n                                          'missing_referenced_paths'])  # Dict[source_path: str, List[line: int]]\n\n\nclass SnippetSourceLintBuilder(DummyBuilder):\n    name = 'snippet_source_lint'\n\n    def __init__(self, *args, **kwargs):\n        super(SnippetSourceLintBuilder, self).__init__(*args, **kwargs)\n\n        # doc_filename: str -> DocSourceErrors\n        self.possibly_invalid_docs = dict()\n\n    def write_doc(self, docname, doctree):\n        dirtiness_checker = FileDirtinessChecker(os.environ['SNIPPET_SOURCE_MD5FILE'])\n        invalid_chunks = []\n        dirty_referenced_paths = set()\n        missing_referenced_paths = collections.defaultdict(lambda: [])\n\n        check_commercial = False\n\n        for node in doctree.traverse(SnippetSourceNode):\n            if node['commercial'] is True and check_commercial is False:\n                continue\n\n            try:\n                snip = CodeSnippet(docname,\n                                   node.line,\n                                   node.source_filepath,\n                                   node.astext())\n                invalid_chunks += snip.get_invalid_chunks()\n\n                if dirtiness_checker.is_file_dirty(node.source_filepath):\n                    dirty_referenced_paths.add(node.source_filepath)\n            except (OSError, IOError):\n                missing_referenced_paths[node.source_filepath].append(node.line)\n\n        if invalid_chunks or dirty_referenced_paths or missing_referenced_paths:\n            self.possibly_invalid_docs[docname] = DocSourceErrors(invalid_chunks, dirty_referenced_paths,\n                                                                  missing_referenced_paths)\n\n    def finish(self):\n        if self.possibly_invalid_docs:\n            print('')\n            print('Some potential errors detected in documentation:')\n            print('')\n\n        for doc_filepath, errors in sorted(self.possibly_invalid_docs.items()):\n            print('- %s:' % (doc_filepath,))\n            if errors.missing_referenced_paths:\n                print('  - %s references to missing files:' % (len(errors.missing_referenced_paths),))\n                for path, lines in sorted(errors.missing_referenced_paths.items()):\n                    print('    - %s at line%s %s' % (\n                        path, 's' if len(lines) > 1 else '', ', '.join(str(l) for l in lines)))\n\n            if errors.dirty_referenced_paths:\n                print('  - %d references to recently modified files:' % (len(errors.dirty_referenced_paths),))\n                for path in sorted(errors.dirty_referenced_paths):\n                    print('    - %s' % (path,))\n\n            if errors.invalid_chunks:\n                print('  - %s chunks missing from sources' % (len(errors.invalid_chunks),))\n                print('')\n                for chunk in errors.invalid_chunks:\n                    print(chunk)\n\n            print('')\n\n        if self.possibly_invalid_docs:\n            print('Resolve errors above, then use following command to update md5 hash cache:')\n            print('')\n            print('    cd \"%s\"; sphinx-build -Q -b snippet_source_list_references -c \"%s/conf_main\" \"%s\" /tmp | sort | xargs md5sum > \"%s\"' % (\n                    os.environ['CMAKE_SOURCE_DIR'], os.environ['ANJAY_DOC_CONF_DIR_BASE'],\n                    os.environ['ANJAY_SPHINX_DOC_ROOT_DIR'], os.environ['SNIPPET_SOURCE_MD5FILE']))\n            print('')\n\n            raise Exception('Lint errors occurred')\n        else:\n            print('snippet-source lint OK')\n\n\ndef setup(app):\n    app.add_builder(SnippetSourceLintBuilder)\n"
  },
  {
    "path": "doc/sphinx/extensions/builders/snippet_source_list_references.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nfrom builders.dummy import DummyBuilder\nfrom snippet_source import SnippetSourceNode\n\n\nclass SnippetSourceListReferencesBuilder(DummyBuilder):\n    name = 'snippet_source_list_references'\n\n    def __init__(self, *args, **kwargs):\n        super(SnippetSourceListReferencesBuilder, self).__init__(*args, **kwargs)\n\n        self.referenced_docs = set()\n\n    def write_doc(self, docname, doctree):\n        list_commercial = False\n\n        for node in doctree.traverse(SnippetSourceNode):\n            if list_commercial or not node['commercial']:\n                self.referenced_docs.add(node.source_filepath)\n\n    def finish(self):\n        print('\\n'.join(self.referenced_docs))\n\n\ndef setup(app):\n    app.add_builder(SnippetSourceListReferencesBuilder)\n"
  },
  {
    "path": "doc/sphinx/extensions/file_dirtiness_checker.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nimport hashlib\nimport os\n\nSCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))\nPROJECT_ROOT = os.path.abspath(os.path.join(SCRIPT_DIR, '../../..'))\n\n\nclass FileDirtinessChecker:\n    def __init__(self, md5hashes_file):\n        \"\"\"\n        @param md5hashes_file: str\n                - absolute path to a MD5 hashes file. Each line should match\n                  following format:\n\n                  MD5HASH <whitespace> FILEPATH_RELATIVE_TO_PROJECT_ROOT\n        \"\"\"\n        try:\n            with open(md5hashes_file) as f:\n                lines = f.readlines()\n        except OSError:\n            print('***')\n            print('*** could not read hash file: %s' % (md5hashes_file,))\n            print('***')\n            lines = []\n\n        tuples = [line.split(None, 1) for line in lines]\n        if any(len(tpl) != 2 for tpl in tuples):\n            print('***')\n            print('*** malformed hash file: %s' % (md5hashes_file,))\n            print('*** expected format:')\n            print('*** MD5HASH <whitespace> FILEPATH_RELATIVE_TO_PROJECT_ROOT')\n            print('***')\n            self.file_to_md5 = {}\n        else:\n            self.file_to_md5 = dict((tpl[1].rstrip('\\n'), tpl[0]) for tpl in tuples)\n\n    def is_file_dirty(self, filepath):\n        \"\"\"\n        Compares file MD5 against pre-computed hashes stored in a file\n        to determine whether FILEPATH was changed.\n\n        @param filepath: str\n                - file path to check for modifications, relative to project root.\n\n        @returns bool\n                - True if the was changed (loaded hash file does not contain\n                  a hash for FILEPATH or it's different than expected)\n        \"\"\"\n        if filepath not in self.file_to_md5:\n            return True\n\n        with open(os.path.join(PROJECT_ROOT, filepath), 'rb') as f:\n            actual = hashlib.md5(f.read()).hexdigest().lower()\n            expected = self.file_to_md5.get(filepath, '').lower()\n            return actual != expected\n"
  },
  {
    "path": "doc/sphinx/extensions/small_literal.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nfrom docutils import nodes\nfrom sphinx.application import Sphinx\n\ndef small_literal_role(name, rawtext, text, lineno, inliner, options={}, content=[]):\n    return [nodes.literal(rawtext, text, classes=['small-literal'])], []\n\ndef setup(app: Sphinx):\n    app.add_role('small-literal', small_literal_role)\n"
  },
  {
    "path": "doc/sphinx/extensions/snippet_source.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nimport docutils\nimport sphinx.writers.html\nimport sphinx.writers.latex\nimport sphinx.writers.text\nfrom docutils.parsers.rst import directives\nfrom sphinx.application import Sphinx\nfrom sphinx.directives.code import container_wrapper\nfrom sphinx.transforms.post_transforms.code import HighlightLanguageVisitor\nfrom sphinx.util import parselinenos\nfrom sphinx.util.docutils import SphinxDirective\n\n\nclass SnippetSourceNode(docutils.nodes.literal_block):\n    def __init__(self, source_filepath, lineno, rawsource, text):\n        super(SnippetSourceNode, self).__init__(rawsource, text)\n\n        self.source_filepath = source_filepath\n        if self.line is None:\n            # line number is *sometimes* not passed\n            self.line = lineno\n\n\nclass SnippetSourceDirective(SphinxDirective):\n    '''\n    .. snippet-source:: filepath_relative_to_project_root\n\n       [code]\n    '''\n    required_arguments = 1\n    optional_arguments = 1\n    has_content = True\n\n    option_spec = {\n        'emphasize-lines': directives.unchanged_required,\n        'caption': directives.unchanged_required,\n        'commercial': directives.flag,\n    }\n\n    def run(self):\n        document = self.state.document\n        source_file = self.arguments[0]\n        code = u'\\n'.join(self.content)\n\n        # heavily inspired by https://github.com/sphinx-doc/sphinx/blob/3.x/sphinx/directives/code.py#L134\n        emphasized_lines = self.options.get('emphasize-lines')\n        if emphasized_lines:\n            try:\n                nlines = len(self.content)\n                hl_lines = parselinenos(emphasized_lines, nlines)\n                if any(i >= nlines for i in hl_lines):\n                    raise RuntimeError('line number spec out of range 1-%d: %r' % (\n                        (nlines, self.options['emphasize-lines'])))\n                hl_lines = [i + 1 for i in hl_lines]\n            except ValueError as err:\n                return [document.reporter.warning(err, line=self.lineno)]\n        else:\n            hl_lines = None\n\n        node = SnippetSourceNode(source_file, self.lineno, code, code)\n        if hl_lines is not None:\n            node['highlight_args'] = {\n                'hl_lines': hl_lines\n            }\n\n        node['commercial'] = ('commercial' in self.options)\n\n        caption = self.options.get('caption')\n        if caption:\n            try:\n                node = container_wrapper(self, node, caption)\n            except ValueError as exc:\n                return [document.reporter.warning(exc, line=self.lineno)]\n\n        # See: https://github.com/sphinx-doc/sphinx/blob/3.x/sphinx/directives/code.py#L184\n        self.add_name(node)\n\n        return [node]\n\n\ndef setup(app: Sphinx):\n    app.add_node(SnippetSourceNode,\n                 html=(sphinx.writers.html.HTMLTranslator.visit_literal_block,\n                       sphinx.writers.html.HTMLTranslator.depart_literal_block),\n                 latex=(sphinx.writers.latex.LaTeXTranslator.visit_literal_block,\n                        sphinx.writers.latex.LaTeXTranslator.depart_literal_block),\n                 text=(sphinx.writers.text.TextTranslator.visit_literal_block,\n                       sphinx.writers.text.TextTranslator.depart_literal_block))\n\n    app.add_directive('snippet-source', SnippetSourceDirective)\n\n    HighlightLanguageVisitor.visit_SnippetSourceNode = HighlightLanguageVisitor.visit_literal_block\n"
  },
  {
    "path": "doc/sphinx/snippet_sources.md5",
    "content": "a6980b23d8f182b848de19a062891318\tdeps/avs_coap/include_public/avsystem/coap/tcp.h\nf428d96a58fe40a4817e3c7f8b780e30\tdeps/avs_coap/include_public/avsystem/coap/udp.h\n4b4183fa27477a18a869b3c9b6d94eef\tdeps/avs_commons/include_public/avsystem/commons/avs_addrinfo.h\nebc7e0729e1a99287fd3d11076db3297\tdeps/avs_commons/include_public/avsystem/commons/avs_base64.h\nfac3b552cfdf9fc8de302abddd512ebd\tdeps/avs_commons/include_public/avsystem/commons/avs_crypto_common.h\nc3ca4435fae5f27507c9754c4b24b2c5\tdeps/avs_commons/include_public/avsystem/commons/avs_crypto_pki.h\n73fe35bfbe47acbc33e95d42ecc69199\tdeps/avs_commons/include_public/avsystem/commons/avs_crypto_psk.h\n378b4b6131df09e81791282dba746b74\tdeps/avs_commons/include_public/avsystem/commons/avs_socket.h\n051de081a75db5a9ddf34e7b1f9a3fa9\tdeps/avs_commons/include_public/avsystem/commons/avs_socket_v_table.h\nfd6d767b36c77b3639217a18b5f1454a\tdeps/avs_commons/include_public/avsystem/commons/avs_utils.h\nf4be2568f472a76b7a3759ce375c3d31\tdeps/avs_commons/src/net/avs_net_global.h\nf116045218318d27434a5ff815436a23\tdeps/avs_commons/src/net/avs_net_impl.h\nf49209af54c33005925827db7e717f90\tdeps/avs_commons/src/utils/compat/posix/avs_compat_time.c\n9bba955ab235e4c315b28aa83589e010\texamples/commercial-features/CF-CorePersistence/src/main.c\n4613be67a0c66ba130fe5601e75ef5f0\texamples/commercial-features/CF-EST-PKCS11/src/main.c\nbcefabd7777e025ed9b0503b81365a59\texamples/commercial-features/CF-EST/src/main.c\n96597e45241b1981802b7c3976a539fe\texamples/commercial-features/CF-NIDD/src/main.c\n671d403eecc85d842c0faec745e8889c\texamples/commercial-features/CF-NIDD/src/nidd_demo_driver.c\nf7b0898ab6ee5fe363e45f010468c8bc\texamples/commercial-features/CF-OSCORE/src/main.c\n89a682ada219f100e1e258a490e4dbd2\texamples/commercial-features/CF-PKCS11/src/main.c\n19e4754142557be668224ff4467558ef\texamples/commercial-features/CF-PSA-PKI/src/main.c\n74902f7718bc3c298efbc1e1908a71b1\texamples/commercial-features/CF-PSA-PSK/src/main.c\nfabf0d50997dac05e8b3ddc63db092c9\texamples/commercial-features/CF-PSA-bootstrap/src/main.c\n4202016537ea33e39cdf82cce280c3cd\texamples/commercial-features/CF-PSA-management/src/main.c\n38bc584eca8c9bd861c92229c982a493\texamples/commercial-features/CF-SMS-PSK/src/main.c\nc5b41490db6e8e468f40f296678270ca\texamples/commercial-features/CF-SMS-UDP/src/main.c\nfb58cbc5152b4fdca9a73a9ca277aa4e\texamples/commercial-features/CF-SMS/src/main.c\nd95709d6755012199650d59a4250516c\texamples/commercial-features/CF-SmartCardBootstrap/src/main.c\n2a6ee93b98cc56fa4354753180979352\texamples/custom-network/bind/src/net_impl.c\n010ee84dd04f376758493666e283f9bd\texamples/custom-network/ip-stickiness/src/net_impl.c\n63749d3b5cae7c7383360d141706bfb3\texamples/custom-network/minimal/CMakeLists.txt\nd591e56ad78070b63f95e26cf3b84d2e\texamples/custom-network/minimal/src/net_impl.c\n41aa341e9670d5b9e2ff07ed9bba7fff\texamples/custom-network/remote-host-port/src/net_impl.c\nc9fdbce9762414d5c4cd16460e8f568d\texamples/custom-network/shutdown-remote-hostname/src/net_impl.c\n7516e0fd17e6b4de6eabb11dbc3b5c30\texamples/custom-network/stats/src/net_impl.c\ndcb7ff67bf0b99453f55569eb62b183a\texamples/custom-tls/certificates-advanced-fake-dane/src/tls_impl.c\n32a6cbb335a7a9fed5b03f4490e2cac7\texamples/custom-tls/certificates-advanced/src/tls_impl.c\n5c8d9fc4ea44cd8e63bfd94436bb5f0a\texamples/custom-tls/certificates-basic/src/main.c\nbef7bc49656c45538c3bd7e09ab85b87\texamples/custom-tls/certificates-basic/src/tls_impl.c\n9f3fab5124c23596af28fe122351d8ab\texamples/custom-tls/config-features/src/tls_impl.c\n39d28cc0305d9920f1f316781ee5e6c2\texamples/custom-tls/minimal/src/tls_impl.c\ne4f714b2685f6460b210d1e1ddd50cca\texamples/custom-tls/resumption-buffer/src/tls_impl.c\n1f3b183f97aa448b9bf4ffea575d44dc\texamples/custom-tls/resumption-simple/src/tls_impl.c\n27a3ed9abaf9e6baf7482e50d7b2edc8\texamples/custom-tls/stub/CMakeLists.txt\n78e11bc69226e8228177175fad7e503d\texamples/custom-tls/stub/src/tls_impl.c\nd983a72f9198ce620476cb1151a02ba0\texamples/custom-tls/tcp-support/src/firmware_update.c\nedfab5eebdf3b2675f967b9172627017\texamples/custom-tls/tcp-support/src/tls_impl.c\nf2cabad50ff2b6a78b2fbb2eb992a720\texamples/tutorial/AT-AccessControl/src/main.c\n6e75c939090365895c0bc0924b688090\texamples/tutorial/AT-Bootstrap/src/main.c\n7a0d0c3bb7ad12bfe6fa28ddf818a9e5\texamples/tutorial/AT-Certificates/src/main.c\n846287eed779ac484bf2a18064d32cc2\texamples/tutorial/AT-CustomEventLoop/src/main.c\n14b8dfaa6d5e2bf225fd8c7383e95491\texamples/tutorial/AT-CustomObjects/bootstrap-awareness/src/main.c\n546eb60e1055f83767d38d4389c2a322\texamples/tutorial/AT-CustomObjects/multi-instance-dynamic/src/test_object.c\n41b588c2e19a29c33251f3cce8b433bc\texamples/tutorial/AT-CustomObjects/multi-instance-resources-dynamic/src/test_object.c\nbc7422f705aa8a760391867aefa708ba\texamples/tutorial/AT-CustomObjects/read-only-multiple-fixed/src/main.c\na1fc955b8941a2f8814aa7e842b80628\texamples/tutorial/AT-CustomObjects/read-only-with-executable/src/main.c\n3ea659d6c3e4b1c4e22d9b561390a954\texamples/tutorial/AT-CustomObjects/read-only/src/main.c\n9a194ea46051b2a5a8f5e801b33f5651\texamples/tutorial/AT-CustomObjects/writable-multiple-fixed-transactional/src/main.c\n2131cbb07e912a50b4ee37d14b6cefba\texamples/tutorial/AT-CustomObjects/writable-multiple-fixed/src/main.c\n5f3d0ee04c3ecdf78ec187731e30ced0\texamples/tutorial/AT-IpsoObjects/src/main.c\nb0e29570c700dcb88514ee8bbf6415d4\texamples/tutorial/AT-Persistence/src/main.c\n6b45471274e11216994cd173ad0238b4\texamples/tutorial/BC-Initialization/CMakeLists.txt\n19350ae71ff9e7c3db83a14d2d915247\texamples/tutorial/BC-Initialization/src/main.c\n74fd5e5506a06946a45cf41a26ce4909\texamples/tutorial/BC-MandatoryObjects/src/main.c\n670925ad428c850ed1ef0390a0dd75bc\texamples/tutorial/BC-Notifications/src/main.c\n63d99ace88ebbf31763a1fbe8563050c\texamples/tutorial/BC-Notifications/src/time_object.c\nf1516f08b1fdc34192d7ae8db4332980\texamples/tutorial/BC-Notifications/src/time_object.h\nee7a9f9dcef214d0a6e643f792529ce6\texamples/tutorial/BC-ObjectImplementation/CMakeLists.txt\nb0f25486e93e6736a7ee61891401e49c\texamples/tutorial/BC-ObjectImplementation/src/main.c\n4c43b921c25a63b76edbdb9f2531ac88\texamples/tutorial/BC-ObjectImplementation/src/time_object.c\n2cc2aa2169283d1553696b4022de6110\texamples/tutorial/BC-ObjectImplementation/src/time_object.h\n9a6cf0cdfc0c98c7386c235bccc05c0d\texamples/tutorial/BC-Security/src/main.c\n8df3bab8e3f3cc186617373926cdac35\texamples/tutorial/BC-Send/src/main.c\n7aa4a9eb11d14dbf0fedef72e1699b76\texamples/tutorial/BC-Send/src/time_object.c\nb9e1ed0eeaa7bf74af92278ce6e37fc5\texamples/tutorial/BC-Send/src/time_object.h\nf4052879186b3181b6a45b4042084ff6\texamples/tutorial/BC-ThreadSafety/CMakeLists.txt\nf8b2e5f86df6174cfea8befc709e5ec0\texamples/tutorial/BC-ThreadSafety/src/main.c\n14138aa1ed29ec5fad3bee0b12f83555\texamples/tutorial/BC-ThreadSafety/src/time_object.c\nabce3c19d5ee5d6047149d0cbe941ac2\texamples/tutorial/LwM2M-Gateway/src/gateway_server.c\n6844a05b52b8f5a7114294d1bcb4e825\texamples/tutorial/LwM2M-Gateway/src/main.c\n6990f616a230d2a2cbdcb55c7b3ce6fe\texamples/tutorial/LwM2M-Gateway/src/temperature_object.c\n55313e5351782caf5bcb91190f07c974\texamples/tutorial/firmware-update/advanced-firmware-update/src/advanced_firmware_update.c\n3250ba9efc66031e101d2213e170f30a\texamples/tutorial/firmware-update/advanced-firmware-update/src/advanced_firmware_update.h\nf971df7872a8f052bb72ae64610a8031\texamples/tutorial/firmware-update/basic-implementation/src/firmware_update.c\n8e79fe9e76c36fef28476e89140ec43b\texamples/tutorial/firmware-update/basic-implementation/src/firmware_update.h\nced98fa62f4d788a4ebad730e7884fe8\texamples/tutorial/firmware-update/basic-implementation/src/main.c\n844cd7d9d0ebe80007dc5ae8d6a719b0\texamples/tutorial/firmware-update/download-resumption/src/firmware_update.c\n077f6b89dad59ef3b9b58e2abe93aff6\texamples/tutorial/firmware-update/secure-downloads/src/firmware_update.c\na118f81f6958fd3a36abd4ee8fbc2c99\tinclude_public/anjay/advanced_fw_update.h\n984623e977e831917c046e7d38991ae5\tinclude_public/anjay/attr_storage.h\n8ded5701ab5dd2ebe743b08fc4d6fbd0\tinclude_public/anjay/core.h\ndd9d5e2b622ded186cd5d4cfbba10540\tinclude_public/anjay/dm.h\n3967fd6330fe56ca11501de331e12691\tinclude_public/anjay/fw_update.h\n2f3fd1ea45b98a99fc2e9df23b199475\tinclude_public/anjay/io.h\n8b7b0d7bcb2c0322b50813334bcc1a3e\tinclude_public/anjay/security.h\nbbedb0b551e0b8d60e0e9b8ec93d5f4f\ttools/provisioning-tool/factory_prov/factory_prov.py\n4a8b23c196dbfd36fa578b1ac4f49bcf\ttools/provisioning-tool/ptool.py\n91a1b850aff74a4a3b8bf3f33947439a\ttools/test-framework-tools/tools/framework_tools/lwm2m/objlink.py\n"
  },
  {
    "path": "doc/sphinx/source/APIReference.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nAPI Reference\n=============\n\n.. meta::\n   :http-equiv=Refresh: 0; url=api/index.html\n\n.. raw:: html\n\n   <script type=\"text/javascript\">\n       window.location.href = \"api/index.html\";\n   </script>\n\nIf you are not redirected automatically, follow this link to the `API Reference <api/index.html>`_.\n"
  },
  {
    "path": "doc/sphinx/source/AdvancedTopics/AT-AccessControl.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nAccess Control in multi-server environment\n==========================================\n\nLwM2M Client may be connected to more than one LwM2M Server. In such situation,\nrestricting Server access to some part of the :ref:`data model <data-model>`\nmay be required.\n\nTo resolve this problem the LwM2M Specification defines an Access Control\nObject that allows setting specific access rights on data model Instances\neither during a Bootstrap Phase or normal runtime by properly authorized\nLwM2M Server.\n\nIn a multi-server environment every Object Instance (except Instances of\nthe Access Control Object) has associated Access Control Instance:\n\n+----------------------+-------------+------------------------------------------------+\n| Resource             | Resource ID | Meaning of the value                           |\n+======================+=============+================================================+\n| Object ID            | 0           | ID of the Object this instance targets         |\n+----------------------+-------------+------------------------------------------------+\n| Object Instance ID   | 1           | Object Instance ID this instance targets       |\n|                      |             | (also see the note below)                      |\n+----------------------+-------------+------------------------------------------------+\n|                      |             | List of pairs (`Short Server ID`,              |\n| ACL                  | 2           | `Access mask`) describing access rights        |\n|                      |             | assigned to LwM2M Servers                      |\n+----------------------+-------------+------------------------------------------------+\n|                      |             | Short Server ID of the Server which owns       |\n| Access Control Owner | 3           | this Access Control Instance (i.e. the         |\n|                      |             | one that can modify access rights)             |\n+----------------------+-------------+------------------------------------------------+\n\n.. note::\n    ``65535`` is a special `Object Instance ID` value reserved for use during\n    the Bootstrap Phase and is valid only in combination with **Create**\n    access flag.\n\nACL Resource\n------------\n\nACL Resource of the Access Control Object Instance is populated with pairs\nof form (`Short Server ID`, `Access mask`). Access mask is a combination of\nthe following access flags (combined by bitwise OR operator):\n\n+-----------------------+--------------------------+-------------------------------+\n| Access flag           | Allowed LwM2M Operations | Anjay representation          |\n+=======================+==========================+===============================+\n| ``R`` (00001 binary)  | Read, Observe            | ``ANJAY_ACCESS_MASK_READ``    |\n+-----------------------+--------------------------+-------------------------------+\n| ``W`` (00010 binary)  | Write                    | ``ANJAY_ACCESS_MASK_WRITE``   |\n+-----------------------+--------------------------+-------------------------------+\n| ``E`` (00100 binary)  | Execute                  | ``ANJAY_ACCESS_MASK_EXECUTE`` |\n+-----------------------+--------------------------+-------------------------------+\n| ``D`` (01000 binary)  | Delete                   | ``ANJAY_ACCESS_MASK_DELETE``  |\n+-----------------------+--------------------------+-------------------------------+\n| ``C`` (10000 binary)  | Create                   | ``ANJAY_ACCESS_MASK_CREATE``  |\n+-----------------------+--------------------------+-------------------------------+\n\n.. note::\n    Discover operation is **always** allowed and LwM2M protocol does not provide\n    a mechanism for forbidding it.\n\nNote on data-model instances lifetime\n-------------------------------------\n\nAccess Control Object also helps in managing Object Instance lifetime. Whenever\nsome Object Instance is orphaned (i.e. no LwM2M Server is an Access Control\nOwner of the Access Control Instance associated with this Object Instance) it\nMUST be removed by the Client. Anjay's pre-implemented Access Control Object\ndoes this automatically.\n\n.. note::\n    Of course, if necessary, you may implement your own Access Control\n    Object and use it instead. Nonetheless it is worth noting that it\n    is an extremely complex Object to implement in a correct way.\n\nExample usage\n-------------\n\nIn this example, we are going to setup multiple-server\nenvironment. We will assign LwM2M Server with SSID 1 the **Create**\npermission on the :doc:`Test Object developed in another tutorial\n<AT-CustomObjects/AT_CO_MultiInstanceDynamic>`.\n\nAdditionally, we will allow both LwM2M Servers to read their respective LwM2M\nServer Instances.\n\n.. note::\n    What we do here is roughly equivalent to **Factory Bootstrap** phase,\n    which is just one of the bootstrapping options. Same thing could be\n    achieved by a Bootstrap Server, which in conformance with the LwM2M\n    Specification would instantiate Access Control Object, and assign access\n    permissions on its own in a multiple-server environment.\n\n.. highlight:: c\n\nWe start with installation of the Access Control, Security Object and Server\nObject modules:\n\n.. snippet-source:: examples/tutorial/AT-AccessControl/src/main.c\n\n    int result = 0;\n    if (anjay_access_control_install(anjay)\n            || anjay_security_object_install(anjay)\n            || anjay_server_object_install(anjay)) {\n        result = -1;\n    }\n\nThen we setup two LwM2M Servers:\n\n.. snippet-source:: examples/tutorial/AT-AccessControl/src/main.c\n\n    // LwM2M Server account with SSID = 1\n    const anjay_security_instance_t security_instance1 = {\n        .ssid = 1,\n        .server_uri = \"coap://eu.iot.avsystem.cloud:5683\",\n        .security_mode = ANJAY_SECURITY_NOSEC\n    };\n\n    const anjay_server_instance_t server_instance1 = {\n        .ssid = 1,\n        .lifetime = 86400,\n        .default_min_period = -1,\n        .default_max_period = -1,\n        .disable_timeout = -1,\n        .binding = \"U\"\n    };\n\n    // LwM2M Server account with SSID = 2\n    const anjay_security_instance_t security_instance2 = {\n        .ssid = 2,\n        .server_uri = \"coap://127.0.0.1:5683\",\n        .security_mode = ANJAY_SECURITY_NOSEC\n    };\n\n    const anjay_server_instance_t server_instance2 = {\n        .ssid = 2,\n        .lifetime = 86400,\n        .default_min_period = -1,\n        .default_max_period = -1,\n        .disable_timeout = -1,\n        .binding = \"U\"\n    };\n\n    // Setup first LwM2M Server\n    anjay_iid_t server_instance_iid1 = ANJAY_ID_INVALID;\n    anjay_security_object_add_instance(anjay, &security_instance1,\n                                       &(anjay_iid_t) { ANJAY_ID_INVALID });\n    anjay_server_object_add_instance(anjay, &server_instance1,\n                                     &server_instance_iid1);\n\n    // Setup second LwM2M Server\n    anjay_iid_t server_instance_iid2 = ANJAY_ID_INVALID;\n    anjay_security_object_add_instance(anjay, &security_instance2,\n                                       &(anjay_iid_t) { ANJAY_ID_INVALID });\n    anjay_server_object_add_instance(anjay, &server_instance2,\n                                     &server_instance_iid2);\n\nAnd finally, we are ready to set access lists:\n\n.. snippet-source:: examples/tutorial/AT-AccessControl/src/main.c\n\n    // Make SSID = 1 the owner of the Test object\n    anjay_access_control_set_owner(anjay, 1234, ANJAY_ID_INVALID,\n                                   server_instance1.ssid, NULL);\n\n    // Set LwM2M Create permission rights for it as well\n    anjay_access_control_set_acl(anjay, 1234, ANJAY_ID_INVALID,\n                                 server_instance1.ssid,\n                                 ANJAY_ACCESS_MASK_CREATE);\n\n    // Allow both LwM2M Servers to read their Server Instances\n    anjay_access_control_set_acl(anjay, 1, server_instance_iid1,\n                                 server_instance1.ssid, ANJAY_ACCESS_MASK_READ);\n    anjay_access_control_set_acl(anjay, 1, server_instance_iid2,\n                                 server_instance2.ssid, ANJAY_ACCESS_MASK_READ);\n\nThat way we have ensured an exclusive access of Server with SSID 1 to Test\nObject (``/1234``) Instances.\n\nLater on, this Server will be able to set some access rights for other Servers,\nby writing to proper Access Control Instances (i.e. Instances this Server\nis an owner of, which corresponds to instances it has created), but that's\noutside of the scope of this tutorial. We recommend you to look at the LwM2M\nSpecification for more details on Access Control Object, as well as at our\n`API docs <../api/index.html>`_.\n\n.. note::\n\n    Please notice ``cleanup`` tag at end of ``main()`` function. It is important\n    to delete your own implemented objects after calling ``anjay_delete()``, as\n    during the instance destruction Anjay may still try to refer to object's\n    data and premature object deletion could be disastrous in effects.\n\n    .. snippet-source:: examples/tutorial/AT-AccessControl/src/main.c\n\n        cleanup:\n            anjay_delete(anjay);\n            delete_test_object(test_obj);\n            return result;\n"
  },
  {
    "path": "doc/sphinx/source/AdvancedTopics/AT-AttributeStorage.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nAttribute storage\n=================\n\n.. highlight:: c\n\nThe Write Attributes and Discover operations, as well as the Information\nReporting interface, use a concept of Attributes that may be set for Resources,\nObject Instances or Objects.\n\n.. seealso::\n    :doc:`/LwM2M` chapter contains more information about LwM2M Attributes.\n\n\nWhen the library needs to read or write these attributes, it calls\n``resource_read_attrs`` or ``resource_write_attrs`` handlers, respectively, in\ncase of Resources, or appropriately ``instance_read_default_attrs``,\n``instance_write_default_attrs``, ``object_read_default_attrs`` or\n``object_write_default_attrs``, in case of Object Instances or Objects. Note\nthat the library will automatically call the \"default\" handlers if there are\nsome unset attributes on the more specific level.\n\nPre-implemented attribute storage subsystem\n-------------------------------------------\n\nAs the cases described above are very common and generic -- and as such, usually\nimplemented in exactly the same manner for all objects in the code base, the\nlibrary includes a subsystem that implements all the attribute-related handlers\nin a generic and reusable way.\n\nTo use the Attribute Storage subsystem, make sure that the library is compiled\nwith it enabled, which means that:\n\n* If building using CMake: the ``WITH_ATTR_STORAGE`` CMake option needs to be\n  enabled, e.g. by specifying ``-DWITH_ATTR_STORAGE``. Note that it is enabled\n  by default.\n* If using an alternative build system: the ``ANJAY_WITH_ATTR_STORAGE`` macro\n  needs to be defined in the ``anjay_config.h`` file. Note that this is true for\n  all the sample configurations from the ``example_configs`` directory.\n\nNo additional steps are necessary - the Attribute Storage subsystem will provide\nimplementations of attribute-related handlers in the original objects with its\nown implementation, unless some handlers are actually already implemented (set\nto anything else than ``NULL``). For a detailed description on how does the\nhandler replacement behave when only some of the handlers are implemented, refer\nto the `documentation <../api/attr__storage_8h.html>`_.\n\n.. _persistence:\n\nPersistence\n^^^^^^^^^^^\n\nTo facilitate storing attribute values between executions of the program, the\nAttribute Storage subsystem contains a persistence code, that can be used to\nserialize and deserialize all the stored attributes to some kind of external\nmemory.\n\nTo use the persistence code, you first need to include the appropriate header:\n\n.. code-block:: c\n\n    #include <anjay/attr_storage.h>\n\nIt contains declarations of the two functions that can be used to manage the\npersistent information:\n\n.. snippet-source:: include_public/anjay/attr_storage.h\n\n    avs_error_t anjay_attr_storage_persist(anjay_t *anjay,\n                                           avs_stream_t *out_stream);\n\n.. snippet-source:: include_public/anjay/attr_storage.h\n\n    avs_error_t anjay_attr_storage_restore(anjay_t *anjay, avs_stream_t *in_stream);\n\n\nThe data are read or written to and from objects of the\n``avs_stream_t`` type. Please refer to documentation of\n`AVSystem Commons <https://github.com/AVSystem/avs_commons>`_ for information on\nwhat it is and how to create one.\n\nFor the simple case, the ``avs_stream_file_`` family of functions may be useful.\n\n.. note:: The persistence functions shall be called after registering all the\n          LwM2M Objects in the Anjay object and fully loading the data model\n          structure (i.e. instantiating all the Object Instances that are\n          supposed to be instantiated). Otherwise, attributes stored for\n          non-existent Objects or their Instances will be discarded.\n"
  },
  {
    "path": "doc/sphinx/source/AdvancedTopics/AT-Bootstrap.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nBootstrap\n=========\n\nThe LwM2M Protocol Specification defines the Bootstrap Interface, whose primary\nrole is to provision LwM2M-enabled devices with the necessary configuration and\ncredentials required to establish a connection with the LwM2M Server.\n\nThe most common use case of this interface, and the one covered in this example,\ninvolves delivering the LwM2M Server Object Instance together with appropriate\nsecurity credentials. However, the bootstrap process is far more versatile.\n\n**LwM2M Bootstrap Server**\n\nA LwM2M Bootstrap Server is a special entity in the LwM2M architecture, as it is\nallowed to modify object instances and resources that are otherwise inaccessible\nto regular LwM2M Servers, ignoring the Read-Only property.\n\nSecurity Object instance that is related to the connection with LwM2M Bootstrap\nServer (has ``Bootstrap-Server`` Resource set to true, as well as URI and security\ncredentials for LwM2M Bootstrap Server) is often called a\n**LwM2M Bootstrap-Server Account**. LwM2M Bootstrap Server connection requires\nonly ``/0`` Security Object instance, without a corresponding ``/1`` Server\nObject instance (with matching SSID).\n\n**Key Operations**\n\n- ``Bootstrap-Delete /0``: Removes all Security Object instances except the one related to the Bootstrap Server.\n\n- ``Bootstrap-Discover``: Identifies the Security Object instance ID for the Bootstrap Server.\n\n- ``Bootstrap-Write``: Updates server URI or credentials.\n\nBootstrap Interface support is enabled with ``ANJAY_WITH_BOOTSTRAP`` configuration\nflag or, if using CMake, with ``WITH_BOOTSTRAP`` option.\n\n.. note::\n\n    Complete code of this example can be found in\n    `examples/tutorial/AT-Bootstrap` subdirectory of main Anjay project\n    repository.\n    Comparing it to `examples/tutorial/BC-MandatoryObjects` can give a good\n    insight on the difference between how LwM2M Bootstrap server is handled.\n\n\nAdd a Bootstrap Account in Anjay\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\nThe Security Object holds connection parameters for the LwM2M server. In this\nexample, we configure a non-secure connection to the Coiote IoT Device\nManagement platform. `anjay_security_instance_t.bootstrap_server <https://docs.avsystem.com/hubfs/Anjay_Docs/api/api_generated/structanjay__security__instance__t.html#_CPPv4N25anjay_security_instance_t16bootstrap_serverE>`_\nflag needs to be set to `true`. Also, LwM2M Bootstrap Server has a different IP\nport than a regular LwM2M Server.\n\n.. highlight:: c\n.. snippet-source:: examples/tutorial/AT-Bootstrap/src/main.c\n    :emphasize-lines: 11-12\n\n    // Installs Security Object and adds an instance of it.\n    // An instance of Security Object provides information needed to connect to\n    // LwM2M Bootstrap server.\n    static int setup_security_object(anjay_t *anjay) {\n        if (anjay_security_object_install(anjay)) {\n            return -1;\n        }\n\n        const anjay_security_instance_t security_instance = {\n            .ssid = 1,\n            .bootstrap_server = true,\n            .server_uri = \"coap://eu.iot.avsystem.cloud:5693\",\n            .security_mode = ANJAY_SECURITY_NOSEC,\n        };\n\n        // Anjay will assign Instance ID automatically\n        anjay_iid_t security_instance_id = ANJAY_ID_INVALID;\n        if (anjay_security_object_add_instance(anjay, &security_instance,\n                                            &security_instance_id)) {\n            return -1;\n        }\n\n        return 0;\n    }\n\nThe LwM2M Bootstrap Server doesn't have a /1 Server Object instance. However,\nyou must still install the Server Object in Anjay data model to allow the\nBootstrap Server to create the Server Object dynamically.\n\n.. highlight:: c\n.. snippet-source:: examples/tutorial/AT-Bootstrap/src/main.c\n\n    // Installs Server Object but does not add any instances of it. This is\n    // necessary to allow LwM2M Bootstrap Server to create Server Object instances.\n    static int setup_server_object(anjay_t *anjay) {\n        if (anjay_server_object_install(anjay)) {\n            return -1;\n        }\n\n        return 0;\n    }\n\n\nConfigure Bootstrap\n^^^^^^^^^^^^^^^^^^^\n\nAnjay will automatically try to connect to the LwM2M Bootstrap Server if it\ndoes not have a LwM2M Server configured in the data model, or if the connection\nto the LwM2M Server has failed.\n\nThe Bootstrap Procedure is considered failed if a LwM2M Client does not receive\nthe \"Bootstrap-Finish\" operation after the last received Bootstrap-Server command\nin a certain period. The LwM2M Specification suggest setting it to the\nvalue of CoAP Parameter ``EXCHANGE_LIFETIME`` and it is calculated based on \n`anjay_configuration_t::udp_tx_params <https://docs.avsystem.com/hubfs/Anjay_Docs/api/api_generated/structanjay__configuration.html#_CPPv4N19anjay_configuration13udp_tx_paramsE>`_ or `anjay_configuration_t::coap_tcp_request_timeout <https://docs.avsystem.com/hubfs/Anjay_Docs/api/api_generated/structanjay__configuration.html#_CPPv4N19anjay_configuration24coap_tcp_request_timeoutE>`_.\n\nThe default values are as follows:\n - 247 seconds for UDP\n - 215.5 seconds for TCP\n\nThe following Bootstrap-related Resources are also implemented in the Anjay's\nbuild-in Security Object:\n\n- `anjay_security_instance_t::client_holdoff_s <https://docs.avsystem.com/hubfs/Anjay_Docs/api/api_generated/structanjay__security__instance__t.html#_CPPv4N25anjay_security_instance_t16client_holdoff_sE>`_ - the time that Anjay waits\n  before performing a Client Initiated Bootstrap once it determines that it\n  should initiate this bootstrap mode.\n\n- `anjay_security_instance_t::bootstrap_timeout_s <https://docs.avsystem.com/hubfs/Anjay_Docs/api/api_generated/structanjay__security__instance__t.html#_CPPv4N25anjay_security_instance_t19bootstrap_timeout_sE>`_ - if set, Anjay will automatically\n  purge the LwM2M Bootstrap-Server Account after this timeout value if a Bootstrap\n  procedure ends successfully. By default, the Bootstrap-Server Account lifetime\n  is infinite.\n\nThere is also a legacy Server-Initiated Bootstrap mechanism based on an\ninterpretation of LwM2M 1.0 TS. To learn more, see\n`anjay_configuration_t::disable_legacy_server_initiated_bootstrap <https://docs.avsystem.com/hubfs/Anjay_Docs/api/api_generated/structanjay__configuration.html#_CPPv4N19anjay_configuration41disable_legacy_server_initiated_bootstrapE>`_.\n\nCoiote LwM2M Server\n^^^^^^^^^^^^^^^^^^^\n\nTo Bootstrap your device using AVSystem Coiote LwM2M Server, refer to\n`Add device via the Bootstrap server guide <https://eu.iot.avsystem.cloud/doc/user/getting-started/add-devices/#add-device-via-the-bootstrap-server>`_ \nin the Coiote documentation.\n"
  },
  {
    "path": "doc/sphinx/source/AdvancedTopics/AT-CertificateUsage.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nCertificate Usage\n=================\n\nThe Certificate Usage resource in the LwM2M Security Object defines how Anjay\ninterprets and applies the Server Public Key resource. This setting decides whether\nthe certificate provided in the Security Object is used as a trust anchor, as\nthe exact server certificate, or in some other way.\n\nConfiguring certificate usage correctly is crucial for secure communication. Anjay\ncan also use an extra PKIX trust store, depending on the chosen certificate usage,\nor it can ignore the trust store completely during server certificate verification.\nBy understanding each option, you can configure Anjay to establish safe and reliable\nconnections to your servers.\n\nCertificate Usage Settings in Anjay\n-----------------------------------\n\nThe `Certificate Usage <https://www.openmobilealliance.org/release/LightweightM2M/V1_2-20201110-A/HTML-Version/OMA-TS-LightweightM2M_Transport-V1_2-20201110-A.html#5-2-9-7-0-5297-Certificate-Usage-Field>`_\nvalues in LwM2M correspond to the semantics from `RFC 6698 (DANE TLSA) <https://www.rfc-editor.org/rfc/rfc6698.html>`_.\nEach value specifies how the certificate supplied in the Security Object (the\nServer Public Key resource) is used when validating the server’s identity. During\nthe TLS/DTLS handshake, depending on this setting, Anjay behaves as follows:\n\n- PKIX-TA (0 – CA constraint)\n    Anjay performs **standard PKIX** verification of the rebuild certificate chain using the\n    local trust store. Additionally, the CA named in the \"Server Public Key\"\n    resource **must appear in the verified chain as well as in the trust store**.\n\n    - A **trust store is required**. Without it, PKIX cannot anchor the chain and the handshake fails.\n    - The TLSA value **must reference a CA** (intermediate or root), not a leaf certificate.\n\n- PKIX-EE (1 – Service certificate constraint)\n    Anjay performs standard PKIX verification and also requires that the handshake\n    leaf certificate exactly matches the certificate stored in the \"Server Public Key\"\n    resource.\n\n    - A **trust store is required** to anchor PKIX.\n    - Self-signed deployments can work if:\n\n        - The \"Server Public Key\" contains the self-signed leaf.\n        - The same leaf is present in the trust store to allow PKIX to succeed.\n\n- DANE-TA (2 – Trust anchor assertion)\n    Anjay treats the \"Server Public Key\" certificate as the **DANE trust anchor**\n    for this server. It attempts to build and verify the path from the handshake\n    **leaf** to that **anchor** using the certificates received in the handshake.\n\n    - The **local trust store is not required** and is **ignored** for the accept/reject decision when a DANE anchor is present.\n    - The \"Server Public Key\" **must be a CA/root**. A leaf value is invalid for usage 2.\n\n    .. note::\n\n        If the \"Server Public Key\" **is empty**, Anjay falls back to PKIX\n        verification if a trust store is available. If neither the DM key nor a trust\n        store is provided, Anjay **does not validate** the server certificate for\n        Certificate Usage 2 and will connect to the server. Consider the security\n        impact before relying on this mode.\n\n- DANE-EE (3 – Domain-issued certificate)\n    Anjay skips PKIX chain building and compares the handshake leaf directly to\n    the certificate in \"Server Public Key\".\n\n    - The value must be the leaf certificate; a CA/root will be rejected.\n    - The trust store is not used for the decision when the DM key is present.\n\n    .. note:: \n\n        If the \"Server Public Key\" **is empty**, Anjay falls back to PKIX when\n        verification if a trust store is available. If neither the DM key nor a trust\n        store is provided, Anjay **does not validate** the server certificate for\n        Certificate Usage 3 and will connect to the server. Consider the security\n        impact before relying on this mode.\n\n.. important:: \n\n   Crypto backends and servers commonly omit the root certificate in the handshake.\n   Validation typically proceeds as leaf → intermediates → (trusted root from store).\n   Depending on what is in your trust store, the backend may anchor the chain at\n   the root or stop early at an intermediate that is already trusted.\n\n\n\"Server Public Key\" in DM is empty\n----------------------------------\n\nWhen the \"Server Public Key\" resource is empty, Anjay behaves as follows:\n\n- Trust store present:\n\n    - Usage 0 (PKIX-TA) and Usage 1 (PKIX-EE): Perform normal PKIX. If the chain validates to a trust anchor within the trust store, the connection succeeds.\n\n    - Usage 2 (DANE-TA) and 3 (DANE-EE): Fall back to PKIX; if the chain validates, the connection succeeds.\n\n- No trust store:\n\n    - Usage 0/1: Reject the connection (PKIX cannot be performed).\n\n    - Usage 2/3: Accept the connection without server certificate validation (as exercised in tests). Carefully assess the security risks of operating in this mode.\n"
  },
  {
    "path": "doc/sphinx/source/AdvancedTopics/AT-Certificates.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nDTLS connection using certificates\n==================================\n\nIn :doc:`/BasicClient/BC-Security` section you learned how to use PSK to enable\nsecure connection in Anjay using DTLS. In this section, we will show how to use\ncertificates instead of PSK.\n\nPreparing a LwM2M Client written using Anjay to use X.509 certificates requires\nessentially the same steps as using the PSK mode. However, it is very likely\nthat you would like to load the certificates from files.\n\n.. note::\n\n   The full code for the following example can be found in the\n   ``examples/tutorial/AT-Certificates`` directory in Anjay sources.\n\nAll actual parsing is performed by the TLS backend library, so it is enough to\njust load contents of certificate files in DER format into memory. The code from\nlisting below is based on :doc:`/BasicClient/BC-MandatoryObjects` example and\nhighlights the modified parts.\n\n.. highlight:: c\n.. snippet-source:: examples/tutorial/AT-Certificates/src/main.c\n    :emphasize-lines: 6-39,52,55-84\n\n    #include <anjay/anjay.h>\n    #include <anjay/security.h>\n    #include <anjay/server.h>\n    #include <avsystem/commons/avs_log.h>\n\n    #include <string.h>\n\n    static int\n    load_buffer_from_file(uint8_t **out, size_t *out_size, const char *filename) {\n        FILE *f = fopen(filename, \"rb\");\n        if (!f) {\n            avs_log(tutorial, ERROR, \"could not open %s\", filename);\n            return -1;\n        }\n        int result = -1;\n        if (fseek(f, 0, SEEK_END)) {\n            goto finish;\n        }\n        long size = ftell(f);\n        if (size < 0 || (unsigned long) size > SIZE_MAX || fseek(f, 0, SEEK_SET)) {\n            goto finish;\n        }\n        *out_size = (size_t) size;\n        if (!(*out = (uint8_t *) avs_malloc(*out_size))) {\n            goto finish;\n        }\n        if (fread(*out, *out_size, 1, f) != 1) {\n            avs_free(*out);\n            *out = NULL;\n            goto finish;\n        }\n        result = 0;\n    finish:\n        fclose(f);\n        if (result) {\n            avs_log(tutorial, ERROR, \"could not read %s\", filename);\n        }\n        return result;\n    }\n\n    // Installs Security Object and adds an instance of it.\n    // An instance of Security Object provides information needed to connect to\n    // LwM2M server.\n    static int setup_security_object(anjay_t *anjay) {\n        if (anjay_security_object_install(anjay)) {\n            return -1;\n        }\n\n        anjay_security_instance_t security_instance = {\n            .ssid = 1,\n            .server_uri = \"coaps://eu.iot.avsystem.cloud:5684\",\n            .security_mode = ANJAY_SECURITY_CERTIFICATE\n        };\n\n        int result = 0;\n\n        if (load_buffer_from_file(\n                    (uint8_t **) &security_instance.public_cert_or_psk_identity,\n                    &security_instance.public_cert_or_psk_identity_size,\n                    \"client_cert.der\")\n                || load_buffer_from_file(\n                           (uint8_t **) &security_instance.private_cert_or_psk_key,\n                           &security_instance.private_cert_or_psk_key_size,\n                           \"client_key.der\")\n                || load_buffer_from_file(\n                           (uint8_t **) &security_instance.server_public_key,\n                           &security_instance.server_public_key_size,\n                           \"server_cert.der\")) {\n            result = -1;\n            goto cleanup;\n        }\n\n        // Anjay will assign Instance ID automatically\n        anjay_iid_t security_instance_id = ANJAY_ID_INVALID;\n        if (anjay_security_object_add_instance(anjay, &security_instance,\n                                               &security_instance_id)) {\n            result = -1;\n        }\n\n    cleanup:\n        avs_free((uint8_t *) security_instance.public_cert_or_psk_identity);\n        avs_free((uint8_t *) security_instance.private_cert_or_psk_key);\n        avs_free((uint8_t *) security_instance.server_public_key);\n\n        return result;\n    }\n\n    // Installs Server Object and adds an instance of it.\n    // An instance of Server Object provides the data related to a LwM2M Server.\n    static int setup_server_object(anjay_t *anjay) {\n        if (anjay_server_object_install(anjay)) {\n            return -1;\n        }\n\n        const anjay_server_instance_t server_instance = {\n            // Server Short ID\n            .ssid = 1,\n            // Client will send Update message often than every 60 seconds\n            .lifetime = 60,\n            // Disable Default Minimum Period resource\n            .default_min_period = -1,\n            // Disable Default Maximum Period resource\n            .default_max_period = -1,\n            // Disable Disable Timeout resource\n            .disable_timeout = -1,\n            // Sets preferred transport to UDP\n            .binding = \"U\"\n        };\n\n        // Anjay will assign Instance ID automatically\n        anjay_iid_t server_instance_id = ANJAY_ID_INVALID;\n        if (anjay_server_object_add_instance(anjay, &server_instance,\n                                             &server_instance_id)) {\n            return -1;\n        }\n\n        return 0;\n    }\n\n    int main(int argc, char *argv[]) {\n        if (argc != 2) {\n            avs_log(tutorial, ERROR, \"usage: %s ENDPOINT_NAME\", argv[0]);\n            return -1;\n        }\n\n        const anjay_configuration_t CONFIG = {\n            .endpoint_name = argv[1],\n            .in_buffer_size = 4000,\n            .out_buffer_size = 4000,\n            .msg_cache_size = 4000\n        };\n\n        anjay_t *anjay = anjay_new(&CONFIG);\n        if (!anjay) {\n            avs_log(tutorial, ERROR, \"Could not create Anjay object\");\n            return -1;\n        }\n\n        int result = 0;\n        // Setup necessary objects\n        if (setup_security_object(anjay) || setup_server_object(anjay)) {\n            result = -1;\n        }\n\n        if (!result) {\n            result = anjay_event_loop_run(\n                    anjay, avs_time_duration_from_scalar(1, AVS_TIME_S));\n        }\n\n        anjay_delete(anjay);\n        return result;\n    }\n\n.. note::\n\n    ``anjay_security_object_add_instance()`` copies the buffers present in the\n    ``anjay_security_instance_t`` structure into the internal state of the\n    ``security`` module, so it is safe to release the memory allocated by the\n    file loading routine.\n"
  },
  {
    "path": "doc/sphinx/source/AdvancedTopics/AT-CustomEventLoop.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nCustom event loop\n=================\n\n.. highlight:: c\n\nThe ``anjay_event_loop_run()`` function that is used in other examples carries\nout all the tasks necessary to handle events related to LwM2M. However, there\nare scenarios in which you might not want to use this function, including:\n\n* A strictly single-threaded application that needs to handle external input\n  other than LwM2M sockets\n* Use of a non-standard network socket layer that does not support ``poll()``\n  or ``select()`` - the event loop API will not be available in that case\n* A need to perform other tasks within the LwM2M thread that cannot be modelled\n  using the scheduler API\n\nAnjay includes lower-level APIs that allow implementing the event loop by the\napplication programmer. In fact, that was the only way before Anjay 2.14.\n\nThe events that need to be handled in the event loop may come from a number of\nsources:\n\n- network packets from LwM2M Servers,\n- Anjay's internal task scheduler,\n- external events specific to the client application.\n\nOn POSIX compliant systems, a simple and portable way to handle all these events\nis to use the ``poll()`` system call.\n\nIncoming network packets\n^^^^^^^^^^^^^^^^^^^^^^^^\n\nUDP sockets that Anjay uses to communicate with LwM2M Servers are accessible\nvia the ``anjay_get_sockets()`` call. All the used sockets are returned as an\n``AVS_LIST`` (implemented and documented in the `AVSystem Commons Library\n<https://github.com/AVSystem/avs_commons>`_).\n\n.. note::\n\n    To retrieve a low-level operating system handle (on POSIX systems it is\n    a file descriptor) use the ``avs_net_socket_get_system()`` call.\n\nWhen there is a new incoming packet on some socket, ``anjay_serve()`` shall\nbe called to handle it.\n\nTherefore our first version of the event loop could look like this:\n\n.. _incomplete-event-loop-idea:\n\n#. Obtain all sources of incoming packets (i.e. server sockets) from the\n   library.\n\n#. Call ``poll()`` on them and wait for an event to arrive.\n\n#. When an event occurs, call ``anjay_serve()`` that will handle the message.\n\nBefore doing that though, you should also understand a very important concept\nAnjay is using, the task scheduler.\n\n.. _task-scheduler:\n\nTask scheduler\n^^^^^^^^^^^^^^\n\nAnjay uses an internal scheduler for executing a number of tasks, including\nautomated sending of Registration Updates and notifications, among others.\n\nDuring runtime, Anjay schedules jobs with specific deadlines, and\nexpects them to be executed when the right time comes -- it is relying on\n``anjay_sched_run()`` being regularly invoked.\n\nThe ``anjay_sched_run()`` iterates over the scheduled jobs (checking if some\nof them should be run at this particular moment) and executes them if\nnecessary.\n\nThe requirement of regularly invoking ``anjay_sched_run()`` is a direct\nconsequence of Anjay being a single-threaded application. It is not a problem\nhowever, as you may call ``anjay_sched_run()`` repeatedly in the main program\nloop that you would have to write anyway.\n\nA naive implementation could cause unnecessary waste of CPU time. This problem\nis addressed by ``anjay_sched_calculate_wait_time_ms()`` which returns the\nnumber of milliseconds before the next scheduled job - so, unless there is a\ncommunication going on, it would be the amount of time the CPU can sleep. In the\nnext example we will use this value as a ``poll()`` timeout.\n\n.. _basic-event-loop:\n\nEvent loop implementation\n^^^^^^^^^^^^^^^^^^^^^^^^^\n\nTaking into account previous subsections we could modify the event loop\npresented :ref:`before <incomplete-event-loop-idea>` as follows:\n\n#. Obtain all sources of incoming packets (i.e. server sockets) from the library.\n\n#. Determine what is the expected time to the next job that has to be executed.\n\n#. Use ``poll()`` to wait for the incoming communication event (but not for too\n   long, to prevent missing any pending scheduler jobs).\n\n#. Call ``anjay_serve()`` on packet arrival, which will handle the message.\n\n#. Run the scheduler by calling ``anjay_sched_run()`` to execute any outstanding\n   jobs.\n\n\n.. topic:: Wait, why do I have to repeatedly get the sources of packets?\n\n    There are at least two reasons for doing that:\n\n    #. At some point network error might have happened, forcing the library\n       to reconnect the socket, possibly changing its underlying descriptor.\n\n    #. The data model might have changed (for instance due to spontaneous Bootstrap\n       Write) and some Server were added / removed or their credentials were\n       modified.\n\nSo, it could be written like this:\n\n.. snippet-source:: examples/tutorial/AT-CustomEventLoop/src/main.c\n    :emphasize-lines: 6-50,141\n\n    #include <anjay/anjay.h>\n    #include <anjay/security.h>\n    #include <anjay/server.h>\n    #include <avsystem/commons/avs_log.h>\n\n    #include <poll.h>\n\n    int main_loop(anjay_t *anjay) {\n        while (true) {\n            // Obtain all network data sources\n            AVS_LIST(avs_net_socket_t *const) sockets = anjay_get_sockets(anjay);\n\n            // Prepare to poll() on them\n            size_t numsocks = AVS_LIST_SIZE(sockets);\n            struct pollfd pollfds[numsocks];\n            size_t i = 0;\n            AVS_LIST(avs_net_socket_t *const) sock;\n            AVS_LIST_FOREACH(sock, sockets) {\n                pollfds[i].fd = *(const int *) avs_net_socket_get_system(*sock);\n                pollfds[i].events = POLLIN;\n                pollfds[i].revents = 0;\n                ++i;\n            }\n\n            const int max_wait_time_ms = 1000;\n            // Determine the expected time to the next job in milliseconds.\n            // If there is no job we will wait till something arrives for\n            // at most 1 second (i.e. max_wait_time_ms).\n            int wait_ms =\n                    anjay_sched_calculate_wait_time_ms(anjay, max_wait_time_ms);\n\n            // Wait for the events if necessary, and handle them.\n            if (poll(pollfds, numsocks, wait_ms) > 0) {\n                int socket_id = 0;\n                AVS_LIST(avs_net_socket_t *const) socket = NULL;\n                AVS_LIST_FOREACH(socket, sockets) {\n                    if (pollfds[socket_id].revents) {\n                        if (anjay_serve(anjay, *socket)) {\n                            avs_log(tutorial, ERROR, \"anjay_serve failed\");\n                        }\n                    }\n                    ++socket_id;\n                }\n            }\n\n            // Finally run the scheduler\n            anjay_sched_run(anjay);\n        }\n        return 0;\n    }\n\n    // Installs Security Object and adds an instance of it.\n    // An instance of Security Object provides information needed to connect to\n    // LwM2M server.\n    static int setup_security_object(anjay_t *anjay) {\n        if (anjay_security_object_install(anjay)) {\n            return -1;\n        }\n\n        static const char PSK_IDENTITY[] = \"identity\";\n        static const char PSK_KEY[] = \"P4s$w0rd\";\n\n        anjay_security_instance_t security_instance = {\n            .ssid = 1,\n            .server_uri = \"coaps://eu.iot.avsystem.cloud:5684\",\n            .security_mode = ANJAY_SECURITY_PSK,\n            .public_cert_or_psk_identity = (const uint8_t *) PSK_IDENTITY,\n            .public_cert_or_psk_identity_size = strlen(PSK_IDENTITY),\n            .private_cert_or_psk_key = (const uint8_t *) PSK_KEY,\n            .private_cert_or_psk_key_size = strlen(PSK_KEY)\n        };\n\n        // Anjay will assign Instance ID automatically\n        anjay_iid_t security_instance_id = ANJAY_ID_INVALID;\n        if (anjay_security_object_add_instance(anjay, &security_instance,\n                                               &security_instance_id)) {\n            return -1;\n        }\n\n        return 0;\n    }\n\n    // Installs Server Object and adds an instance of it.\n    // An instance of Server Object provides the data related to a LwM2M Server.\n    static int setup_server_object(anjay_t *anjay) {\n        if (anjay_server_object_install(anjay)) {\n            return -1;\n        }\n\n        const anjay_server_instance_t server_instance = {\n            // Server Short ID\n            .ssid = 1,\n            // Client will send Update message often than every 60 seconds\n            .lifetime = 60,\n            // Disable Default Minimum Period resource\n            .default_min_period = -1,\n            // Disable Default Maximum Period resource\n            .default_max_period = -1,\n            // Disable Disable Timeout resource\n            .disable_timeout = -1,\n            // Sets preferred transport to UDP\n            .binding = \"U\"\n        };\n\n        // Anjay will assign Instance ID automatically\n        anjay_iid_t server_instance_id = ANJAY_ID_INVALID;\n        if (anjay_server_object_add_instance(anjay, &server_instance,\n                                             &server_instance_id)) {\n            return -1;\n        }\n\n        return 0;\n    }\n\n    int main(int argc, char *argv[]) {\n        if (argc != 2) {\n            avs_log(tutorial, ERROR, \"usage: %s ENDPOINT_NAME\", argv[0]);\n            return -1;\n        }\n\n        const anjay_configuration_t CONFIG = {\n            .endpoint_name = argv[1],\n            .in_buffer_size = 4000,\n            .out_buffer_size = 4000,\n            .msg_cache_size = 4000\n        };\n\n        anjay_t *anjay = anjay_new(&CONFIG);\n        if (!anjay) {\n            avs_log(tutorial, ERROR, \"Could not create Anjay object\");\n            return -1;\n        }\n\n        int result = 0;\n        // Setup necessary objects\n        if (setup_security_object(anjay) || setup_server_object(anjay)) {\n            result = -1;\n        }\n\n        if (!result) {\n            result = main_loop(anjay);\n        }\n\n        anjay_delete(anjay);\n        return result;\n    }\n\n.. note::\n\n    Complete code of this example can be found in\n    `examples/tutorial/AT-CustomEventLoop` subdirectory of main Anjay project\n    repository.\n\nAs we've been discussing, the code above is enough to handle all events that\nmay happen within the Anjay library itself. Of course, the application usually\nneeds to handle its own functionality, this is however outside of the scope of\nthis tutorial, but the presented code may be used as a good starting point.\n\nanjay_serve_any()\n^^^^^^^^^^^^^^^^^\n\n`anjay_serve_any() <../api/core_8h.html#ac436ac24095cb10ef008c8fd91126b31>`_ is\na simplified API that allows writing a simple event loop that the user retains\ncontrol of.\n\nAs mentioned in the API documentation, any\n``anjay_event_loop_run(anjay, max_wait_time)`` call may be translated into::\n\n    while (true) {\n        anjay_serve_any(anjay, max_wait_time);\n        anjay_sched_run(anjay);\n    }\n\nThe above is true as long as ``anjay_event_loop_interrupt()`` is never called -\nin this variant, ``anjay_event_loop_interrupt()`` is not handled at all and the\nuser is fully responsible for introducing exit mechanisms if needed.\n\nPlease note that ``anjay_serve_any()`` depends on at least one of the ``poll()``\nor ``select()`` functions being available, so this is not a feasible solution\nfor non-standard network socket layers.\n"
  },
  {
    "path": "doc/sphinx/source/AdvancedTopics/AT-CustomObjects/AT_CO1_SingleInstanceReadOnly.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\n:orphan:\n\n.. meta::\n\n    :http-equiv=Refresh: 1; url=AT_CO_SingleInstanceReadOnly.html\n\n.. title:: Redirection\n\n↳ :doc:`AT_CO_SingleInstanceReadOnly`\n=====================================\n"
  },
  {
    "path": "doc/sphinx/source/AdvancedTopics/AT-CustomObjects/AT_CO2_SingleInstanceExecutableAndReadOnly.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\n:orphan:\n\n.. meta::\n\n    :http-equiv=Refresh: 1; url=AT_CO_SingleInstanceExecutableAndReadOnly.html\n\n.. title:: Redirection\n\n↳ :doc:`AT_CO_SingleInstanceExecutableAndReadOnly`\n==================================================\n"
  },
  {
    "path": "doc/sphinx/source/AdvancedTopics/AT-CustomObjects/AT_CO3_MultiInstanceReadOnlyFixed.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\n:orphan:\n\n.. meta::\n\n    :http-equiv=Refresh: 1; url=AT_CO_MultiInstanceReadOnlyFixed.html\n\n.. title:: Redirection\n\n↳ :doc:`AT_CO_MultiInstanceReadOnlyFixed`\n=========================================\n"
  },
  {
    "path": "doc/sphinx/source/AdvancedTopics/AT-CustomObjects/AT_CO4_FixedInstanceWritable.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\n:orphan:\n\n.. meta::\n\n    :http-equiv=Refresh: 1; url=AT_CO_FixedInstanceWritable.html\n\n.. title:: Redirection\n\n↳ :doc:`AT_CO_FixedInstanceWritable`\n====================================\n"
  },
  {
    "path": "doc/sphinx/source/AdvancedTopics/AT-CustomObjects/AT_CO5_MultiInstanceDynamic.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\n:orphan:\n\n.. meta::\n\n    :http-equiv=Refresh: 1; url=AT_CO_MultiInstanceDynamic.html\n\n.. title:: Redirection\n\n↳ :doc:`AT_CO_MultiInstanceDynamic`\n===================================\n"
  },
  {
    "path": "doc/sphinx/source/AdvancedTopics/AT-CustomObjects/AT_CO6_MultipleResourceInstances.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\n:orphan:\n\n.. meta::\n\n    :http-equiv=Refresh: 1; url=AT_CO_MultipleResourceInstances.html\n\n.. title:: Redirection\n\n↳ :doc:`AT_CO_MultipleResourceInstances`\n========================================\n"
  },
  {
    "path": "doc/sphinx/source/AdvancedTopics/AT-CustomObjects/AT_CO7_BootstrapAwareness.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\n:orphan:\n\n.. meta::\n\n    :http-equiv=Refresh: 1; url=AT_CO_BootstrapAwareness.html\n\n.. title:: Redirection\n\n↳ :doc:`AT_CO_BootstrapAwareness`\n=================================\n"
  },
  {
    "path": "doc/sphinx/source/AdvancedTopics/AT-CustomObjects/AT_CO_BootstrapAwareness.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nBootstrap awareness\n===================\n\nIn this tutorial you will learn how to setup Resources writable only by the\nLwM2M Bootstrap Server.\n\n\nHandling LwM2M Bootstrap Server\n-------------------------------\n\nLwM2M Bootstrap Server is an unusual entity: it is allowed to modify Instances\nand Resources not accessible to \"regular\" LwM2M Servers. That is useful for\nsetting up sensitive data that servers should not change (or even read) during\nnormal operation of the device - DTLS keys or certificates, for example.\n\nWhenever a LwM2M Bootstrap Server sends a Bootstrap Write request, Anjay uses\nthe same ``instance_create`` and ``resource_write`` handlers as for \"regular\"\nLwM2M Server operations. The only difference is that Anjay *ignores the\noperations declared through the* ``kind`` *argument to* ``anjay_dm_emit_res()``\n*calls inside the* ``list_resources`` *handler* for bootstrap requests, allowing\nBootstrap Server to do anything it wants.\n\nIf a device has to implement a Resource that must only be writable by the LwM2M\nBootstrap Server, its handlers should be implemented like so:\n\n- ``resource_write`` handler must be able to set the value of a Resource,\n  as if it was a writable one,\n\n- ``list_resources`` handler should pass a ``kind`` argument that does not allow\n  writing to ``anjay_dm_emit_res()``. That will prevent Anjay from calling the\n  ``resource_write`` handler for this particular Resource when the request is\n  issued by a non-bootstrap server.\n\n\nExample: bootstrap-writable Resource\n------------------------------------\n\nAs an example, we will modify the Test Object from\n:doc:`AT_CO_FixedInstanceWritable` to prevent changing the value of ``Label``\nResource by non-bootstrap servers:\n\n+-------------+-----------+-----------+\n| Name        | Object ID | Instances |\n+=============+===========+===========+\n| Test object | 1234      | Multiple  |\n+-------------+-----------+-----------+\n\nEach Object Instance has two Resources:\n\n+-------+-------------+------------+-----------+-----------+---------+\n| Name  | Resource ID | Operations | Instances | Mandatory | Type    |\n+=======+=============+============+===========+===========+=========+\n| Label | 0           | Read       | Single    | Mandatory | String  |\n+-------+-------------+------------+-----------+-----------+---------+\n| Value | 1           | Read/Write | Single    | Mandatory | Integer |\n+-------+-------------+------------+-----------+-----------+---------+\n\n\nTo achieve that, we need to update the ``test_list_resources`` handler so that\nit disallows writing to Resource 0:\n\n.. highlight:: c\n.. snippet-source:: examples/tutorial/AT-CustomObjects/bootstrap-awareness/src/main.c\n\n    static int test_list_resources(anjay_t *anjay,\n                                   const anjay_dm_object_def_t *const *obj_ptr,\n                                   anjay_iid_t iid,\n                                   anjay_dm_resource_list_ctx_t *ctx) {\n        // ...\n\n        // only allow reading Resource 0 by LwM2M Servers\n        // this will be ignored for LwM2M Bootstrap Server\n        anjay_dm_emit_res(ctx, 0, ANJAY_DM_RES_R, ANJAY_DM_RES_PRESENT);\n        // Value Resource can be read/written by LwM2M Servers\n        anjay_dm_emit_res(ctx, 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT);\n        return 0;\n    }\n\n\nThat leaves one more issue with the example project: it has a pre-configured\nLwM2M Server that is not a bootstrap one. Changing it to a LwM2M Bootstrap\nServer is a matter of setting ``bootstrap_server = true`` on\n``anjay_security_instance_t`` struct when initializing the Security Object:\n\n.. snippet-source:: examples/tutorial/AT-CustomObjects/bootstrap-awareness/src/main.c\n\n    const anjay_security_instance_t security_instance = {\n        .ssid = 1,\n        .bootstrap_server = true,\n        .server_uri = \"coap://eu.iot.avsystem.cloud:5693\",\n        .security_mode = ANJAY_SECURITY_NOSEC\n    };\n\n\nIt is worth noting that the LwM2M Bootstrap Server has only a Security Object\ninstance and no Server Object instances. For that reason, the example project\ndeliberately does not initialize any Server Object Instances.\n\n.. note::\n\n    Complete code of this example can be found in\n    `examples/tutorial/AT-CustomObjects/bootstrap-awareness` subdirectory of main\n    Anjay project repository.\n"
  },
  {
    "path": "doc/sphinx/source/AdvancedTopics/AT-CustomObjects/AT_CO_FixedInstanceWritable.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nMulti-instance writable object with fixed number of instances\n=============================================================\n\n.. include:: Anjay_codegen_note.rst\n\nIn this tutorial you will learn:\n\n- how to implement ``resource_write`` to create a writeable Resource,\n- what are Partial Update and Replace variants of the LwM2M Write request,\n- how to setup ``instance_reset`` handler to handle LwM2M Write (Replace),\n- basics of transaction handling in Anjay.\n\nImplemented object is based on :doc:`AT_CO_MultiInstanceReadOnlyFixed`,\nbut this time accepts Write requests.\n\n+-------------+-----------+-----------+\n| Name        | Object ID | Instances |\n+=============+===========+===========+\n| Test object | 1234      | Multiple  |\n+-------------+-----------+-----------+\n\nEach Object Instance has two Resources:\n\n+-------+-------------+------------+-----------+-----------+---------+\n| Name  | Resource ID | Operations | Instances | Mandatory | Type    |\n+=======+=============+============+===========+===========+=========+\n| Label | 0           | Read/Write | Single    | Mandatory | String  |\n+-------+-------------+------------+-----------+-----------+---------+\n| Value | 1           | Read/Write | Single    | Mandatory | Integer |\n+-------+-------------+------------+-----------+-----------+---------+\n\n\nSimple variant\n--------------\n\nThe ``test_instance_t`` needs to be able to store arbitrary data. Let us set\nan arbitrary limit of 32 characters in length (including terminating nullbyte):\n\n.. highlight:: c\n.. snippet-source:: examples/tutorial/AT-CustomObjects/writable-multiple-fixed/src/main.c\n\n    typedef struct test_instance {\n        char label[32];\n        int32_t value;\n    } test_instance_t;\n\n\nNow the ``anjay_dm_resource_write_t`` handler implementation:\n\n.. highlight:: c\n.. snippet-source:: examples/tutorial/AT-CustomObjects/writable-multiple-fixed/src/main.c\n\n    static int test_resource_write(anjay_t *anjay,\n                                   const anjay_dm_object_def_t *const *obj_ptr,\n                                   anjay_iid_t iid,\n                                   anjay_rid_t rid,\n                                   anjay_riid_t riid,\n                                   anjay_input_ctx_t *ctx) {\n        (void) anjay; // unused\n\n        test_object_t *test = get_test_object(obj_ptr);\n\n        // IID validity was checked by the `anjay_dm_list_instances_t` handler.\n        // If the Object Instance set does not change, or can only be modified\n        // via LwM2M Create/Delete requests, it is safe to assume IID is correct.\n        assert((size_t) iid < NUM_INSTANCES);\n        struct test_instance *current_instance = &test->instances[iid];\n\n        // We have no Multiple-Instance Resources, so it is safe to assume\n        // that RIID is never set.\n        assert(riid == ANJAY_ID_INVALID);\n\n        switch (rid) {\n        case 0: {\n            // `anjay_get_string` may return a chunk of data instead of the\n            // whole value - we need to make sure the client is able to hold\n            // the entire value\n            char buffer[sizeof(current_instance->label)];\n            int result = anjay_get_string(ctx, buffer, sizeof(buffer));\n\n            if (result == 0) {\n                // value OK - save it\n                memcpy(current_instance->label, buffer, sizeof(buffer));\n            } else if (result == ANJAY_BUFFER_TOO_SHORT) {\n                // the value is too long to store in the buffer\n                result = ANJAY_ERR_BAD_REQUEST;\n            }\n\n            return result;\n        }\n\n        case 1:\n            // reading primitive values can be done directly - the value will only\n            // be written to the output variable if everything went fine\n            return anjay_get_i32(ctx, &current_instance->value);\n\n        default:\n            // control will never reach this part due to test_list_resources\n            return ANJAY_ERR_INTERNAL;\n        }\n    }\n\n\nWe also need to update the ``test_list_resources`` handler with the information\nthat the resources are writable:\n\n.. highlight:: c\n.. snippet-source:: examples/tutorial/AT-CustomObjects/writable-multiple-fixed/src/main.c\n\n    static int test_list_resources(anjay_t *anjay,\n                                   const anjay_dm_object_def_t *const *obj_ptr,\n                                   anjay_iid_t iid,\n                                   anjay_dm_resource_list_ctx_t *ctx) {\n        // ...\n        anjay_dm_emit_res(ctx, 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT);\n        anjay_dm_emit_res(ctx, 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT);\n        return 0;\n    }\n\n\nEverything that was left to do is plugging in handlers. There is a catch though:\nany modifying operation (writing a value, creating or deleting an Object\nInstance) requires explicitly defined transaction handlers.\n``anjay_dm_transaction_NOOP`` placeholder will be used for now, see\n:ref:`FixedInstanceWritable-transactional` for an actual implementation of\nthese.\n\n.. highlight:: c\n.. snippet-source:: examples/tutorial/AT-CustomObjects/writable-multiple-fixed/src/main.c\n\n    static const anjay_dm_object_def_t OBJECT_DEF = {\n        // ...\n        .handlers = {\n            // ...\n\n            .resource_write = test_resource_write,\n\n            .transaction_begin = anjay_dm_transaction_NOOP,\n            .transaction_validate = anjay_dm_transaction_NOOP,\n            .transaction_commit = anjay_dm_transaction_NOOP,\n            .transaction_rollback = anjay_dm_transaction_NOOP\n        }\n   };\n\n\n.. note::\n\n    Complete code of this example can be found in\n    `examples/tutorial/AT-CustomObjects/writable-multiple-fixed` subdirectory of\n    main Anjay project repository.\n\n\nLwM2M Write operation modes\n^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nA LwM2M Server may perform a Write on the *entire Object Instance*. Two\nvariants of such requests are available - both replace values of Instance\nResources with received values, but they differ in what happens to Resources\nnot present in the request:\n\n- Partial Update - they are leaved unchanged,\n\n- Replace - Optional Resources are reset to \"missing\" state. All mandatory\n  Resources MUST be specified in the request, otherwise it MUST be considered\n  invalid.\n\n  .. note::\n      Anjay does not distinguish Mandatory and Optional Resources - the user\n      is responsible for ensuring the state of an Object is still valid after\n      handling a request. This topic is covered in detail in\n      :ref:`FixedInstanceWritable-transactional`; we will ignore it in this\n      particular example.\n\nAnjay implements Partial Update as a series of ``resource_write`` calls and\nReplace one as ``instance_reset`` + series of ``resource_write`` calls.\nTo properly support both Write variants, ``instance_reset`` needs to be\nimplemented too.\n\n\nAnjay transaction handlers\n^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nIn some cases, like LwM2M Write requests targeting an Object Instance, Anjay\ncalls multiple handlers modifying the data model to perform a single atomic\noperation. In such case, transactions ensure that data model is not left\nin an inconsistent state when one of handler calls fails. To achieve correct\nbehavior, used-defined handlers should behave as described below:\n\n- ``anjay_dm_resource_write_t`` - needs to ensure that the value being written\n  is correct and store new value in such a way that rolling back to the previous\n  one is still possible. It must not check constraints that depend on values\n  of other Resources, as these Resources may also change during the same\n  transaction.\n\n- ``anjay_dm_transaction_begin_t`` - always called before any operation that\n  changes the data model (LwM2M Write, Create, Delete). It should do whatever\n  actions are necessary for correct transaction handling - in the simplest\n  possible case, it could save a snapshot of the current state of the Object\n  to restore it when rollback is required.\n\n- ``anjay_dm_transaction_validate_t`` - called after all write/create/remove\n  handlers succeed. It should verify cross-Resource or cross-Instance\n  constraints and fail if the data model state is not consistent.\n  **Example**: LwM2M requires that at most one Security (/0) object instance\n  has Bootstrap Server Resource set to 1. If a LwM2M Create operation adds\n  an instance with this Bootstrap Server = 1 when there already was one, this\n  handler needs to fail.\n\n- ``anjay_dm_transaction_commit_t`` - called after the transaction was\n  successfully validated by the ``anjay_dm_transaction_validate_t`` handler.\n  It should atomically apply all changes performed since last call to\n  ``anjay_dm_transaction_begin_t`` for the same object.\n\n  .. warning::\n      This handler must not fail, unless a critical and unrecoverable error\n      (e.g. hardware failure) occurs.\n\n- ``anjay_dm_transaction_rollback_t`` - called after one of\n  write/create/remove/validate handlers fails. It should atomically restore\n  the object to a state it was at the last ``anjay_dm_transaction_begin_t``\n  call.\n\n  .. warning::\n      This handler must not fail, unless a critical and unrecoverable error\n      (e.g. hardware failure) occurs.\n\n\nThis tutorial shows an example of implementing transactions by storing\na snapshot of the entire state of a LwM2M Object.\n\n.. warning::\n\n    Such implementation, while simple, effectively doubles amount of RAM used\n    by the Object.\n\n\n.. _FixedInstanceWritable-transactional:\n\nTransactional variant\n---------------------\n\nKnowing all about different LwM2M Write variants and Anjay transaction handlers,\nwe can start implementing a transaction-aware client capable of handling\nall LwM2M Write requests.\n\nLet's start by implementing the ``instance_reset`` handler. Two boolean flags\nneed to be added to ``test_instance_t`` to detect a situation where a value\nis considered \"unset\":\n\n.. highlight:: c\n.. snippet-source:: examples/tutorial/AT-CustomObjects/writable-multiple-fixed-transactional/src/main.c\n\n    typedef struct test_instance {\n        bool has_label;\n        char label[32];\n\n        bool has_value;\n        int32_t value;\n    } test_instance_t;\n\n\n``instance_reset`` and ``resource_write`` should then use these to appropriately\nmark Resources as set/unset:\n\n.. highlight:: c\n.. snippet-source:: examples/tutorial/AT-CustomObjects/writable-multiple-fixed-transactional/src/main.c\n\n\n    static int test_instance_reset(anjay_t *anjay,\n                                   const anjay_dm_object_def_t *const *obj_ptr,\n                                   anjay_iid_t iid) {\n        (void) anjay; // unused\n\n        test_object_t *test = get_test_object(obj_ptr);\n\n        // IID validity was checked by the `anjay_dm_list_instances_t` handler.\n        // If the Object Instance set does not change, or can only be modified\n        // via LwM2M Create/Delete requests, it is safe to assume IID is correct.\n        assert((size_t) iid < NUM_INSTANCES);\n\n        // mark all Resource values for Object Instance `iid` as unset\n        test->instances[iid].has_label = false;\n        test->instances[iid].has_value = false;\n        return 0;\n    }\n\n    // ...\n\n    static int test_resource_write(anjay_t *anjay,\n                                   const anjay_dm_object_def_t *const *obj_ptr,\n                                   anjay_iid_t iid,\n                                   anjay_rid_t rid,\n                                   anjay_riid_t riid,\n                                   anjay_input_ctx_t *ctx) {\n        // ...\n\n        switch (rid) {\n        case 0: {\n                // ...\n\n                // value OK - save it\n                memcpy(current_instance->label, buffer, sizeof(buffer));\n                current_instance->has_label = true;\n\n                // ...\n        }\n\n        case 1: {\n            // reading primitive values can be done directly - the value will only\n            // be written to the output variable if everything went fine\n            int result = anjay_get_i32(ctx, &current_instance->value);\n            if (result == 0) {\n                current_instance->has_value = true;\n            }\n            return result;\n        }\n\n        // ...\n    }\n\n\nHaving ``has_label``/``has_value`` flags ready, we can finally implement\ntransaction handlers:\n\n.. highlight:: c\n.. snippet-source:: examples/tutorial/AT-CustomObjects/writable-multiple-fixed-transactional/src/main.c\n\n    static int test_transaction_begin(anjay_t *anjay,\n                                      const anjay_dm_object_def_t *const *obj_ptr) {\n        (void) anjay; // unused\n\n        test_object_t *test = get_test_object(obj_ptr);\n\n        // store a snapshot of object state\n        memcpy(test->backup_instances, test->instances, sizeof(test->instances));\n        return 0;\n    }\n\n    static int\n    test_transaction_validate(anjay_t *anjay,\n                              const anjay_dm_object_def_t *const *obj_ptr) {\n        (void) anjay; // unused\n\n        test_object_t *test = get_test_object(obj_ptr);\n\n        // ensure all Object Instances contain all Mandatory Resources\n        for (size_t i = 0; i < NUM_INSTANCES; ++i) {\n            if (!test->instances[i].has_label || !test->instances[i].has_value) {\n                // validation failed: Object state invalid, rollback required\n                return ANJAY_ERR_BAD_REQUEST;\n            }\n        }\n\n        // validation successful, can commit\n        return 0;\n    }\n\n    static int\n    test_transaction_commit(anjay_t *anjay,\n                            const anjay_dm_object_def_t *const *obj_ptr) {\n        (void) anjay;   // unused\n        (void) obj_ptr; // unused\n\n        // no action required in this implementation; if object state snapshot was\n        // dynamically allocated, this would be the place for releasing it\n        return 0;\n    }\n\n    static int\n    test_transaction_rollback(anjay_t *anjay,\n                              const anjay_dm_object_def_t *const *obj_ptr) {\n        (void) anjay; // unused\n\n        test_object_t *test = get_test_object(obj_ptr);\n\n        // restore saved object state\n        memcpy(test->instances, test->backup_instances, sizeof(test->instances));\n        return 0;\n    }\n\n    // ...\n\n    static const anjay_dm_object_def_t OBJECT_DEF = {\n        // ...\n        .handlers = {\n            // ...\n\n            .instance_reset = test_instance_reset,\n\n            // ...\n\n            .transaction_begin = test_transaction_begin,\n            .transaction_validate = test_transaction_validate,\n            .transaction_commit = test_transaction_commit,\n            .transaction_rollback = test_transaction_rollback\n        }\n    };\n\n\nThat is everything one needs to set up a complete, transaction-aware writable\nResource.\n\n.. note::\n\n    Complete code of this example can be found in\n    `examples/tutorial/AT-CustomObjects/writable-multiple-fixed-transactional`\n    subdirectory of main Anjay project repository.\n"
  },
  {
    "path": "doc/sphinx/source/AdvancedTopics/AT-CustomObjects/AT_CO_MultiInstanceDynamic.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nMulti-instance writable object with dynamic number of instances\n===============================================================\n\n.. include:: Anjay_codegen_note.rst\n\nIn this tutorial you will learn:\n\n- how to implement ``instance_create`` handler to create Instances,\n- how to implement ``instance_remove`` handler to remove Instances,\n- how to assign instance identifiers to a newly created Instances,\n- basics of the ``AVS_LIST`` utility library.\n\nImplemented object will be roughly based on :doc:`AT_CO_FixedInstanceWritable`.\n\n+-------------+-----------+-----------+\n| Name        | Object ID | Instances |\n+=============+===========+===========+\n| Test object | 1234      | Multiple  |\n+-------------+-----------+-----------+\n\nAs before, each Object Instance has two Resources:\n\n+-------+-------------+------------+-----------+-----------+---------+\n| Name  | Resource ID | Operations | Instances | Mandatory | Type    |\n+=======+=============+============+===========+===========+=========+\n| Label | 0           | Read/Write | Single    | Mandatory | String  |\n+-------+-------------+------------+-----------+-----------+---------+\n| Value | 1           | Read/Write | Single    | Mandatory | Integer |\n+-------+-------------+------------+-----------+-----------+---------+\n\nThe code is based on :doc:`previous tutorial <AT_CO_FixedInstanceWritable>`,\nyet in this chapter all Test object related code was moved to separate files to\nkeep everything clean.\n\nUpdating the object structure\n-----------------------------\n\nFirst of all, we have to update our ``test_object_t`` structure to support\nstoring multiple object instances. For that, we need some kind of dynamically\nsized container. We could choose plain-old, manually managed C arrays, but\nthat comes with unnecessary distraction, and as a matter of fact is a bit\nerror-prone.  Therefore, in this tutorial, we are going to use ``AVS_LIST``,\nwhich is an abstraction over singly linked list, and has been used in Anjay\nwith success since the beginning of the project.\n\n.. highlight:: c\n.. snippet-source:: examples/tutorial/AT-CustomObjects/multi-instance-dynamic/src/test_object.c\n\n    typedef struct test_object {\n        // handlers\n        const anjay_dm_object_def_t *obj_def;\n\n        // object state\n        AVS_LIST(test_instance_t) instances;\n\n        AVS_LIST(test_instance_t) backup_instances;\n    } test_object_t;\n\n.. note::\n    ``AVS_LIST(x)`` is actually a macro that expands to a pointer of type ``x``.\n    It therefore has pointer semantics and can be treated like that (standard\n    dereferencing and dereferencing with assignment will work as expected). This\n    is however, not everything you need to know about them, as they are more\n    complicated than that. We recommend you to refer to the `documentation\n    <https://github.com/AVSystem/avs_commons/blob/master/include_public/avsystem/commons/avs_list.h>`_.\n\nIn the previous tutorial, our Instances had hardcoded Instance IDs.  We no\nlonger have such comfort, and have to be able to uniquely identify Object\nInstances. As a consequence, we will add ``anjay_iid_t iid`` field to\n``test_instance_t``:\n\n.. snippet-source:: examples/tutorial/AT-CustomObjects/multi-instance-dynamic/src/test_object.c\n\n    typedef struct test_instance {\n        anjay_iid_t iid;\n\n        bool has_label;\n        char label[32];\n\n        bool has_value;\n        int32_t value;\n    } test_instance_t;\n\nInitialization and cleanup\n--------------------------\n\nWe must reconsider the way our Test object is being initialized. Up to this point\nit was allocated on stack and required no cleanup. Again, times have changed, and\nwe won't be able to proceed further without allocating memory on demand.\n\n.. topic:: Wait, you could still allocate objects on stack, and initialize them later!\n\n    Yes, but it may be considered bad coding style for at least two reasons:\n\n    1. Having partially initialized or uninitialized objects floating around is\n       almost always a bad idea, as it is easy to forget about initialization\n       and accessing uninitialized data is undefined behavior. Allocating\n       objects on a heap however, allows the implementation to fully initialize\n       an object before returning a reference to it.\n\n    2. End user of an Object should NOT be exposed to its internal representation,\n       i.e. notice how handles to all objects shown to you so far were returned as\n       pointers to ``anjay_dm_object_def_t`` -- clearly, it indicates to the user\n       that they have nothing interesting to do with such reference (besides being\n       able to register it), unless some additional public API is provided.\n\nTo achieve proper control over object lifetime and initialization, we are\ngoing to introduce two functions, namely ``create_test_object``:\n\n.. snippet-source:: examples/tutorial/AT-CustomObjects/multi-instance-dynamic/src/test_object.c\n\n    const anjay_dm_object_def_t **create_test_object(void) {\n        test_object_t *repr =\n                (test_object_t *) avs_calloc(1, sizeof(test_object_t));\n        if (repr) {\n            repr->obj_def = &OBJECT_DEF;\n            return &repr->obj_def;\n        }\n        return NULL;\n    }\n\n.. topic:: Shouldn't ``test_object_t::instances`` and ``test_object_t::backup_instances`` be\n           be initialized in some special way?\n\n        No, ``NULL`` is a valid (and only) representation of an empty ``AVS_LIST``.\n\nand ``delete_test_object``:\n\n.. snippet-source:: examples/tutorial/AT-CustomObjects/multi-instance-dynamic/src/test_object.c\n\n    void delete_test_object(const anjay_dm_object_def_t **obj) {\n        if (!obj) {\n            return;\n        }\n        test_object_t *repr = get_test_object(obj);\n        AVS_LIST_CLEAR(&repr->instances);\n        AVS_LIST_CLEAR(&repr->backup_instances);\n        avs_free(repr);\n    }\n\nAs you can see, dynamic memory management is semi-automatically handled by ``AVS_LIST``.\n\n.. note::\n    ``AVS_LIST_CLEAR`` iterates over the list, in the process frees memory allocated for\n    each element, and in the end sets list handle to ``NULL``.\n\nUsing functions defined above to create, register and free the Test object is similar\nas in previous tutorials.\n\n.. note::\n\n    Before calling ``delete_test_object()`` the object must be unregistered. You\n    can do this by calling ``anjay_unregister_object()``. Also ``anjay_delete()``\n    will deregister (but not delete) all of registered objects before destruction\n    of Anjay instance.\n\nUpdating old, already implemented handlers to use ``AVS_LIST``\n--------------------------------------------------------------\n\nTo simplify matters, we have to agree upon one contract:\n\n#. We establish a natural Instance ordering on their Instance IDs, exploiting\n   the fact they MUST be unique.\n\n#. We store Instances of the Test object in an ordered (as above) list.\n\nOK, now that we made our assumptions, we are ready to implement utility function\n``get_instance`` which retrieves Test object instance with specified Instance ID.\nIt will happen to be very useful in the next couple of subsections:\n\n.. snippet-source:: examples/tutorial/AT-CustomObjects/multi-instance-dynamic/src/test_object.c\n\n    static AVS_LIST(test_instance_t) get_instance(test_object_t *repr,\n                                                  anjay_iid_t iid) {\n        AVS_LIST(test_instance_t) it;\n        AVS_LIST_FOREACH(it, repr->instances) {\n            if (it->iid == iid) {\n                return it;\n            } else if (it->iid > iid) {\n                // Since list of instances is sorted by Instance ID,\n                // Instance with given iid does not exist on that list\n                break;\n            }\n        }\n        // Instance was not found.\n        return NULL;\n    }\n\nAnd one more method, presenting another functionality of ``AVS_LISTs``\nbefore going into details of ``instance_create`` and ``instance_remove``\nand leaving the rest of the work of this kind as an exercise for the reader\n(or, if they are lazy, they can always look at the code):\n\n.. snippet-source:: examples/tutorial/AT-CustomObjects/multi-instance-dynamic/src/test_object.c\n\n    static int test_list_instances(anjay_t *anjay,\n                                   const anjay_dm_object_def_t *const *obj_ptr,\n                                   anjay_dm_list_ctx_t *ctx) {\n        (void) anjay; // unused\n\n        // iterate over all instances and return their IDs\n        AVS_LIST(test_instance_t) it;\n        AVS_LIST_FOREACH(it, get_test_object(obj_ptr)->instances) {\n            anjay_dm_emit(ctx, it->iid);\n        }\n\n        return 0;\n    }\n\nNote that as we keep the Instances in sorted order, this implementation\nsatisfies the contract for this handler.\n\nThat's all for this section. As noted above, implementation of other methods\nis as always available in the source code provided with the tutorial. We\ndo however strongly recommend you to port the methods to use ``AVS_LISTs``\non your own, especially **remember about updating transaction handlers**.\n\n``instance_create`` handler\n---------------------------\n\nLet's have a look on ``anjay_dm_instance_create_t`` handler type signature:\n\n.. snippet-source:: include_public/anjay/dm.h\n\n    typedef int\n    anjay_dm_instance_create_t(anjay_t *anjay,\n                               const anjay_dm_object_def_t *const *obj_ptr,\n                               anjay_iid_t iid);\n\nThe ``iid`` parameter is the most important for us at the moment, as this is the\nID of the Instance we need to create.\n\nLwM2M Create requests do not necessarily have to contain preferred Instance ID.\nHowever, Anjay makes it transparent to the application - if the Instance ID is\nnot specified by the server, it will iterate over existing instances using the\n``anjay_dm_list_instances_t`` handler, and find the lowest ID that is not\nalready occupied. If the Instance ID is specified by the server, Anjay will call\nthe ``anjay_dm_list_instances_t`` handler and ensure that there is no such\nInstance ID already existing.\n\n.. snippet-source:: examples/tutorial/AT-CustomObjects/multi-instance-dynamic/src/test_object.c\n\n    static int test_instance_create(anjay_t *anjay,\n                                    const anjay_dm_object_def_t *const *obj_ptr,\n                                    anjay_iid_t iid) {\n        (void) anjay; // unused\n\n        test_object_t *repr = get_test_object(obj_ptr);\n\n        AVS_LIST(test_instance_t) new_instance =\n                AVS_LIST_NEW_ELEMENT(test_instance_t);\n\n        if (!new_instance) {\n            // out of memory\n            return ANJAY_ERR_INTERNAL;\n        }\n\n        new_instance->iid = iid;\n\n        // find a place where instance should be inserted,\n        // insert it and claim a victory\n        AVS_LIST(test_instance_t) *insert_ptr;\n        AVS_LIST_FOREACH_PTR(insert_ptr, &repr->instances) {\n            if ((*insert_ptr)->iid > new_instance->iid) {\n                break;\n            }\n        }\n        AVS_LIST_INSERT(insert_ptr, new_instance);\n        return 0;\n    }\n\n.. note::\n    There is a lot going on in this function, also new concepts regarding\n    ``AVS_LIST`` are being used. We advise you to look at ``AVS_LIST_FOREACH_PTR``,\n    ``AVS_LIST_NEW_ELEMENT`` and ``AVS_LIST_INSERT`` documentation for more details.\n\n``instance_remove`` handler\n---------------------------\n\n``instance_remove`` handler does not have to perform anything other than\nremoving the instance from our list.\n\n.. snippet-source:: examples/tutorial/AT-CustomObjects/multi-instance-dynamic/src/test_object.c\n\n    static int test_instance_remove(anjay_t *anjay,\n                                    const anjay_dm_object_def_t *const *obj_ptr,\n                                    anjay_iid_t iid) {\n        (void) anjay; // unused\n        test_object_t *repr = get_test_object(obj_ptr);\n\n        AVS_LIST(test_instance_t) *it;\n        AVS_LIST_FOREACH_PTR(it, &repr->instances) {\n            if ((*it)->iid == iid) {\n                AVS_LIST_DELETE(it);\n                return 0;\n            }\n        }\n        // should never happen as Anjay checks whether instance is present\n        // prior to issuing instance_remove\n        return ANJAY_ERR_INTERNAL;\n    }\n\n.. note::\n\n    Complete code of this example can be found in\n    `examples/tutorial/AT-CustomObjects/multi-instance-dynamic` subdirectory\n    of main Anjay project repository.\n"
  },
  {
    "path": "doc/sphinx/source/AdvancedTopics/AT-CustomObjects/AT_CO_MultiInstanceReadOnlyFixed.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nMulti-instance read-only object with fixed number of instances\n==============================================================\n\n.. include:: Anjay_codegen_note.rst\n\nIn this example you will learn how to implement ``list_instances`` handler for a\nmulti-instance LwM2M Object.\n\nThe implemented Object will look as in :doc:`AT_CO_SingleInstanceReadOnly`,\nbut will now support multiple Object Instances:\n\n+-------------+-----------+-----------+\n| Name        | Object ID | Instances |\n+=============+===========+===========+\n| Test object | 1234      | Multiple  |\n+-------------+-----------+-----------+\n\nEach Object Instance has two Resources:\n\n+-------+-------------+------------+-----------+-----------+---------+\n| Name  | Resource ID | Operations | Instances | Mandatory | Type    |\n+=======+=============+============+===========+===========+=========+\n| Label | 0           | Read       | Single    | Mandatory | String  |\n+-------+-------------+------------+-----------+-----------+---------+\n| Value | 1           | Read       | Single    | Mandatory | Integer |\n+-------+-------------+------------+-----------+-----------+---------+\n\n\n.. note::\n\n    The code is based on :doc:`AT_CO_SingleInstanceReadOnly`.\n\n\nFirst of all, let us define a structure that will hold both handlers and object\nstate, initialize and register it:\n\n.. highlight:: c\n.. snippet-source:: examples/tutorial/AT-CustomObjects/read-only-multiple-fixed/src/main.c\n\n    typedef struct test_instance {\n        const char *label;\n        int32_t value;\n    } test_instance_t;\n\n    typedef struct test_object {\n        // handlers\n        const anjay_dm_object_def_t *obj_def;\n\n        // object state\n        test_instance_t instances[2];\n    } test_object_t;\n\n    // ...\n\n    // initialize and register the test object\n    const test_object_t test_object = {\n        .obj_def = &OBJECT_DEF,\n        .instances = { { \"First\", 1 }, { \"Second\", 2 } }\n    };\n\n    anjay_register_object(anjay, &test_object.obj_def);\n\n\nNow, to inform the library the Object contains two Instances with IDs 0 and 1,\none additional handler is required:\n\n- ``list_instances`` - used by the library to enumerate all existing Object\n  Instance IDs:\n\n  .. highlight:: c\n  .. snippet-source:: examples/tutorial/AT-CustomObjects/read-only-multiple-fixed/src/main.c\n\n        static int test_list_instances(anjay_t *anjay,\n                                       const anjay_dm_object_def_t *const *obj_ptr,\n                                       anjay_dm_list_ctx_t *ctx) {\n            (void) anjay; // unused\n\n            test_object_t *test = get_test_object(obj_ptr);\n\n            for (anjay_iid_t iid = 0;\n                 (size_t) iid < sizeof(test->instances) / sizeof(test->instances[0]);\n                 ++iid) {\n                anjay_dm_emit(ctx, iid);\n            }\n\n            return 0;\n        }\n\n  Note that the instances MUST be returned in a strictly ascending, sorted\n  order.\n\n.. topic:: Why is enumerating Object Instances necessary?\n\n   LwM2M does not require existing Object Instances to have consecutive IDs.\n   It is perfectly fine to implement an Object that only contains Instances\n   3 and 40235. Without ``list_instances`` handler the library would need\n   to iterate over all possible Instance IDs to be able to prepare a list\n   of available Object Instances whenever the LwM2M Server requests one.\n\n\nHaving done that, ``resource_read`` handler needs to be slightly modified\nto correctly handle requests to different Object Instance IDs.\n\n.. highlight:: c\n.. snippet-source:: examples/tutorial/AT-CustomObjects/read-only-multiple-fixed/src/main.c\n\n    static int test_resource_read(anjay_t *anjay,\n                                  const anjay_dm_object_def_t *const *obj_ptr,\n                                  anjay_iid_t iid,\n                                  anjay_rid_t rid,\n                                  anjay_riid_t riid,\n                                  anjay_output_ctx_t *ctx) {\n        (void) anjay; // unused\n\n        test_object_t *test = get_test_object(obj_ptr);\n\n        // IID validity was checked by the `anjay_dm_list_instances_t` handler.\n        // If the Object Instance set does not change, or can only be modified\n        // via LwM2M Create/Delete requests, it is safe to assume IID is correct.\n        assert((size_t) iid < sizeof(test->instances) / sizeof(test->instances[0]));\n        const struct test_instance *current_instance = &test->instances[iid];\n\n        // We have no Multiple-Instance Resources, so it is safe to assume\n        // that RIID is never set.\n        assert(riid == ANJAY_ID_INVALID);\n\n        switch (rid) {\n        case 0:\n            return anjay_ret_string(ctx, current_instance->label);\n        case 1:\n            return anjay_ret_i32(ctx, current_instance->value);\n        default:\n            // control will never reach this part due to test_list_resources\n            return ANJAY_ERR_INTERNAL;\n        }\n    }\n\n\nThe only thing left to do is plugging created handler into the Object Definition\nstruct:\n\n.. highlight:: c\n.. snippet-source:: examples/tutorial/AT-CustomObjects/read-only-multiple-fixed/src/main.c\n\n    static const anjay_dm_object_def_t OBJECT_DEF = {\n        // Object ID\n        .oid = 1234,\n\n        .handlers = {\n            .list_instances = test_list_instances,\n\n            // ... other handlers\n        }\n    };\n\n\n.. note::\n\n    Complete code of this example can be found in\n    `examples/tutorial/AT-CustomObjects/read-only-multiple-fixed` subdirectory of\n    main Anjay project repository.\n"
  },
  {
    "path": "doc/sphinx/source/AdvancedTopics/AT-CustomObjects/AT_CO_MultipleResourceInstances.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nObjects with Multiple Instance Resources\n========================================\n\n.. include:: Anjay_codegen_note.rst\n\nIn this tutorial you will learn:\n\n- how to retrieve a Multiple Instance Resource using Anjay API,\n- how to send a Multiple Instance Resource using Anjay API.\n\nWe will extend the Test object from :doc:`previous tutorial\n<AT_CO_MultiInstanceDynamic>` by allowing `Value` Resource to contain multiple\nvalues.\n\n\nAPI for Multiple Instance Resources management\n----------------------------------------------\n\nDealing with Multiple Instance Resources in the data model requires implementing\nadditional handlers. The most important in the ``list_resource_instances``\nhandler:\n\n.. highlight:: c\n.. snippet-source:: include_public/anjay/dm.h\n\n    typedef int\n    anjay_dm_list_resource_instances_t(anjay_t *anjay,\n                                       const anjay_dm_object_def_t *const *obj_ptr,\n                                       anjay_iid_t iid,\n                                       anjay_rid_t rid,\n                                       anjay_dm_list_ctx_t *ctx);\n\nThis handler needs to be implemented for any Object that has some Multiple\nInstance Resource. It will only be called on Multiple Resources, as determined\nby the ``kind`` argument passed to ``anjay_dm_emit_res()`` in the\n``list_resources`` handler. It shall list (via the passed\n``anjay_dm_list_ctx_t``) all the currently existing instances of the resource.\n\nTo allow writing to Multiple Instance Resources, the ``resource_reset`` handler\nneeds to be implemented as well:\n\n.. snippet-source:: include_public/anjay/dm.h\n\n    typedef int\n    anjay_dm_resource_reset_t(anjay_t *anjay,\n                              const anjay_dm_object_def_t *const *obj_ptr,\n                              anjay_iid_t iid,\n                              anjay_rid_t rid);\n\nThis handler will only be called on Resources that has been determined to have\nmultiple instances. It shall put the Resource in a state that it is present, but\nhaving zero instances.\n\nThe actual reads and writes are performed using the usual ``resource_read`` and\n``resource_write`` handler. The ``riid`` argument that we have previously been\nignoring, is used to determine the Resource Instance that is targeted.\n\n\nPreparing Test object for Multiple Instance Resources\n-----------------------------------------------------\n\nFirst of all, we need to update the List Resources handler so that the library\nknows that Resource 1 now has multiple instances:\n\n.. snippet-source:: examples/tutorial/AT-CustomObjects/multi-instance-resources-dynamic/src/test_object.c\n\n    static int test_list_resources(anjay_t *anjay,\n                                   const anjay_dm_object_def_t *const *obj_ptr,\n                                   anjay_iid_t iid,\n                                   anjay_dm_resource_list_ctx_t *ctx) {\n        // ...\n        anjay_dm_emit_res(ctx, 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT);\n        anjay_dm_emit_res(ctx, 1, ANJAY_DM_RES_RWM, ANJAY_DM_RES_PRESENT);\n        return 0;\n    }\n\n\nWe define following structure to represent a single Instance of our Multiple\nInstance Resource:\n\n.. snippet-source:: examples/tutorial/AT-CustomObjects/multi-instance-resources-dynamic/src/test_object.c\n\n    typedef struct test_value_instance {\n        anjay_riid_t index;\n        int32_t value;\n    } test_value_instance_t;\n\n.. note::\n\n    ``anjay_riid_t`` is used for the first time in the tutorial. It is a data type\n    able to store all valid Resource Instance IDs.\n\nWe also edit ``test_instance_t`` structure definition:\n\n.. snippet-source:: examples/tutorial/AT-CustomObjects/multi-instance-resources-dynamic/src/test_object.c\n\n    typedef struct test_instance {\n        anjay_iid_t iid;\n\n        bool has_label;\n        char label[32];\n\n        bool has_values;\n        AVS_LIST(test_value_instance_t) values;\n    } test_instance_t;\n\n.. topic::  Why does ``test_instance_t`` still contain boolean flag indicating presence of the value?\n\n    There is certainly a difference between lack of presence of Multiple\n    Instance Resource value, and Multiple Instance Resource containing\n    zero Instances (i.e. lack of list presence vs an empty list).\n\n\nImplementing the List Resource Instances handler\n------------------------------------------------\n\nHere is how the List Resource Instances is implemented for our test object:\n\n.. snippet-source:: examples/tutorial/AT-CustomObjects/multi-instance-resources-dynamic/src/test_object.c\n\n    static int\n    test_list_resource_instances(anjay_t *anjay,\n                                 const anjay_dm_object_def_t *const *obj_ptr,\n                                 anjay_iid_t iid,\n                                 anjay_rid_t rid,\n                                 anjay_dm_list_ctx_t *ctx) {\n        (void) anjay; // unused\n        test_instance_t *current_instance =\n                (test_instance_t *) get_instance(get_test_object(obj_ptr), iid);\n\n        // this handler can only be called for Multiple-Instance Resources\n        assert(rid == 1);\n\n        AVS_LIST(test_value_instance_t) it;\n        AVS_LIST_FOREACH(it, current_instance->values) {\n            anjay_dm_emit(ctx, it->index);\n        }\n        return 0;\n    }\n\nAs you can see, the ``anjay_dm_emit()`` function is used to pass all the\nexisting Resource Instances to Anjay, similar to the ``list_instances`` and\n``list_resources`` handlers.\n\nNote that the resource instances MUST be returned in a strictly ascending,\nsorted order. We will keep the resource instances in sorted order, so this\nimplementation satisfies this contract.\n\nHandling Multiple Instance Resources in Read operation\n------------------------------------------------------\n\n``resource_read`` handler is being called by Anjay for each Resource Instance\nreferenced by the server, giving the control to the user. Thus, the read handler\ncould look like this:\n\n.. snippet-source:: examples/tutorial/AT-CustomObjects/multi-instance-resources-dynamic/src/test_object.c\n\n    static int test_resource_read(anjay_t *anjay,\n                                  const anjay_dm_object_def_t *const *obj_ptr,\n                                  anjay_iid_t iid,\n                                  anjay_rid_t rid,\n                                  anjay_riid_t riid,\n                                  anjay_output_ctx_t *ctx) {\n        // ...\n        switch (rid) {\n        // ...\n        case 1: {\n            AVS_LIST(const test_value_instance_t) it;\n            AVS_LIST_FOREACH(it, current_instance->values) {\n                if (it->index == riid) {\n                    return anjay_ret_i32(ctx, it->value);\n                }\n            }\n            // Resource Instance not found\n            return ANJAY_ERR_NOT_FOUND;\n        }\n        // ...\n        }\n    }\n\n\nImplementing the Resource Reset handler\n---------------------------------------\n\n.. topic:: General flow of function calls when LwM2M Write operation was\n           issued on Multiple Instance Resource.\n\n    1. ``resource_reset`` handler is being called by Anjay, to clear the\n       Multiple Instance Resource.\n\n    2. ``resource_write`` handler is being called by Anjay for each Resource\n       Instance referenced by the server, giving the control to the user. The\n       handler shall add or replace the Resource with the instance it is being\n       called for.\n\nThe above means that the Resource Reset handler is rather simple to implement,\nas it only needs to clear the resource:\n\n.. snippet-source:: examples/tutorial/AT-CustomObjects/multi-instance-resources-dynamic/src/test_object.c\n\n    static int test_resource_reset(anjay_t *anjay,\n                                   const anjay_dm_object_def_t *const *obj_ptr,\n                                   anjay_iid_t iid,\n                                   anjay_rid_t rid) {\n        (void) anjay; // unused\n\n        test_instance_t *current_instance =\n                (test_instance_t *) get_instance(get_test_object(obj_ptr), iid);\n\n        // this handler can only be called for Multiple-Instance Resources\n        assert(rid == 1);\n\n        // free memory associated with old values\n        AVS_LIST_CLEAR(&current_instance->values);\n        current_instance->has_values = true;\n        return 0;\n    }\n\n\nImplementing the Resource Instance Remove handler\n-------------------------------------------------\n\nLwM2M 1.2 introduced the possibility for the Delete operation to be called on\nResource Instances. If compatibility with the 1.2 standard is aimed for, the\n``resource_instance_remove`` handler needs to be implemented.\n\nThis handler is actually very similar to the Resource Reset in implementation.\nWe just need to make sure that only a single Resource Instance is removed,\nrather than all of them:\n\n.. snippet-source:: examples/tutorial/AT-CustomObjects/multi-instance-resources-dynamic/src/test_object.c\n\n    static int\n    test_resource_instance_remove(anjay_t *anjay,\n                                  const anjay_dm_object_def_t *const *obj_ptr,\n                                  anjay_iid_t iid,\n                                  anjay_rid_t rid,\n                                  anjay_riid_t riid) {\n        (void) anjay; // unused\n\n        test_instance_t *current_instance =\n                (test_instance_t *) get_instance(get_test_object(obj_ptr), iid);\n\n        // this handler can only be called for Multiple-Instance Resources\n        assert(rid == 1);\n\n        // find the Resource Instance entry\n        AVS_LIST(test_value_instance_t) *it;\n        AVS_LIST_FOREACH_PTR(it, &current_instance->values) {\n            if ((*it)->index == riid) {\n                break;\n            }\n        }\n\n        // this handler can only be called for existing Resource Instances\n        assert(it && *it && (*it)->index == riid);\n\n        // free memory associated with the instance\n        AVS_LIST_DELETE(it);\n        current_instance->has_values = true;\n        return 0;\n    }\n\n\nHandling Multiple Instance Resources in Write operation\n-------------------------------------------------------\n\nNow we are ready to actually implement the write operation. We will create a\nhelper function for actually updating the Resource Instance list with a newly\nwritten value.\n\n.. snippet-source:: examples/tutorial/AT-CustomObjects/multi-instance-resources-dynamic/src/test_object.c\n\n    static int test_array_write(AVS_LIST(test_value_instance_t) *out_instances,\n                                anjay_riid_t index,\n                                anjay_input_ctx_t *input_ctx) {\n        test_value_instance_t instance = {\n            .index = index\n        };\n\n        if (anjay_get_i32(input_ctx, &instance.value)) {\n            // An error occurred during the read.\n            return ANJAY_ERR_INTERNAL;\n        }\n\n        AVS_LIST(test_value_instance_t) *insert_it;\n\n        // Searching for the place to insert;\n        // note that it makes the whole function O(n).\n        AVS_LIST_FOREACH_PTR(insert_it, out_instances) {\n            if ((*insert_it)->index >= instance.index) {\n                break;\n            }\n        }\n\n        if ((*insert_it)->index != instance.index) {\n            AVS_LIST(test_value_instance_t) new_element =\n                    AVS_LIST_NEW_ELEMENT(test_value_instance_t);\n\n            if (!new_element) {\n                // out of memory\n                return ANJAY_ERR_INTERNAL;\n            }\n\n            AVS_LIST_INSERT(insert_it, new_element);\n        }\n\n        assert((*insert_it)->index == instance.index);\n        **insert_it = instance;\n\n        return 0;\n    }\n\nLast thing to do is to modify ``test_resource_write`` implementation to make use\nof our helper function:\n\n.. snippet-source:: examples/tutorial/AT-CustomObjects/multi-instance-resources-dynamic/src/test_object.c\n\n    static int test_resource_write(anjay_t *anjay,\n                                   const anjay_dm_object_def_t *const *obj_ptr,\n                                   anjay_iid_t iid,\n                                   anjay_rid_t rid,\n                                   anjay_riid_t riid,\n                                   anjay_input_ctx_t *ctx) {\n        // ...\n        switch (rid) {\n        // ...\n        case 1: {\n            int result = test_array_write(&current_instance->values, riid, ctx);\n            if (!result) {\n                current_instance->has_values = true;\n            }\n\n            // either test_array_write succeeded and result is 0, or not\n            // in which case result contains appropriate error code.\n            return result;\n        }\n        // ...\n        }\n    }\n\n.. note::\n\n    Complete code of this example can be found in\n    `examples/tutorial/AT-CustomObjects/multi-instance-resources-dynamic`\n    subdirectory of main Anjay project repository.\n"
  },
  {
    "path": "doc/sphinx/source/AdvancedTopics/AT-CustomObjects/AT_CO_SingleInstanceExecutableAndReadOnly.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nSingle-instance read-only object with an executable resource\n============================================================\n\n.. include:: Anjay_codegen_note.rst\n\nIn this example you will learn:\n\n- what are LwM2M Execute arguments,\n- how to parse them using Anjay's API,\n- how to implement ``resource_execute`` handler.\n\nThe implemented Object will be be based on the previous tutorial\n:doc:`AT_CO_SingleInstanceReadOnly`, but with additional executable resource:\n\n+-------------+-----------+-----------+\n| Name        | Object ID | Instances |\n+=============+===========+===========+\n| Test object | 1234      | Multiple  |\n+-------------+-----------+-----------+\n\nEach Object Instance has three Resources:\n\n+------------+-------------+------------+-----------+-----------+---------+\n| Name       | Resource ID | Operations | Instances | Mandatory | Type    |\n+============+=============+============+===========+===========+=========+\n| Label      | 0           | Read       | Single    | Mandatory | String  |\n+------------+-------------+------------+-----------+-----------+---------+\n| Value      | 1           | Read       | Single    | Mandatory | Integer |\n+------------+-------------+------------+-----------+-----------+---------+\n| Add        | 2           | Execute    | Single    | Mandatory |         |\n+------------+-------------+------------+-----------+-----------+---------+\n\nOur new `Add` resource will be used to perform an addition of integers,\nstoring the result in the `Value` resource. The integers are to be specified\nas arguments to the LwM2M Execute operation.\n\nLwM2M Execute arguments\n~~~~~~~~~~~~~~~~~~~~~~~\n\nThe LwM2M specification defines a syntax of the Execute argument list as\na formal ABNF grammar.\n\n.. note::\n\n    Just to give you an idea of how the syntax looks like (without getting\n    into details of the grammar), here are few examples of valid argument list:\n\n        - ``5``,\n        - ``2='10.3'``,\n        - ``7,0='https://www.avsystem.com/'``\n        - ``0,1,2,3,4``\n        - an empty string.\n\n    Also note that argument indices are limited to range 0-9 inclusively.\n\n\n.. note::\n\n    It is up to the implementation on how to interpret argument lists. One\n    may, for example, think of each ``[0-9]='value'`` as a mapping between\n    argument index and a corresponding value.\n\n    Then the value of the argument of index `k` could be applied as a `k-th`\n    argument to some function for further processing. (we will be, in fact,\n    using this interpretation in this tutorial)\n\nWhile the grammar itself is rather simple, it could be tedious to implement a\ncorrect parser accepting it (with support for all corner-cases). In Anjay\nthere are methods designed specifically to solve this problem, namely:\n\n.. highlight:: c\n.. snippet-source:: include_public/anjay/io.h\n\n    // ... One that returns the next argument from the Execute argument list\n    int anjay_execute_get_next_arg(anjay_execute_ctx_t *ctx,\n                                   int *out_arg,\n                                   bool *out_has_value);\n\n    // ... And the one that obtains its value (if any)\n    int anjay_execute_get_arg_value(anjay_execute_ctx_t *ctx,\n                                          size_t *out_bytes_read,\n                                          char *out_buf,\n                                          size_t buf_size);\n\nThey will greatly simplify parsing process, as you will see in the next section.\n\nImplementation\n~~~~~~~~~~~~~~\n\nWe start with adding our Resource to the list of supported Resources:\n\n.. highlight:: c\n.. snippet-source:: examples/tutorial/AT-CustomObjects/read-only-with-executable/src/main.c\n\n    static int test_list_resources(anjay_t *anjay,\n                                   const anjay_dm_object_def_t *const *obj_ptr,\n                                   anjay_iid_t iid,\n                                   anjay_dm_resource_list_ctx_t *ctx) {\n        // ...\n        anjay_dm_emit_res(ctx, 2, ANJAY_DM_RES_E, ANJAY_DM_RES_PRESENT);\n        return 0;\n    }\n\n\nNote that the ``kind`` argument is set to ``ANJAY_DM_RES_E`` to signify an\nexecutable resource.\n\nWe can now implement ``resource_execute`` handler. Since our new resource will\nbe used to sum integers we have to store the addition result somewhere. For\nsimplicity we are going to use a ``static`` variable:\n\n.. highlight:: c\n.. snippet-source:: examples/tutorial/AT-CustomObjects/read-only-with-executable/src/main.c\n\n    static long addition_result;\n\n\nAnd ``resource_execute`` could be implemented as follows:\n\n.. highlight:: c\n.. snippet-source:: examples/tutorial/AT-CustomObjects/read-only-with-executable/src/main.c\n\n    static int test_resource_execute(anjay_t *anjay,\n                                     const anjay_dm_object_def_t *const *obj_ptr,\n                                     anjay_iid_t iid,\n                                     anjay_rid_t rid,\n                                     anjay_execute_ctx_t *ctx) {\n        switch (rid) {\n        case 2: {\n            long sum = 0;\n            int result;\n            do {\n                int arg_value = 0;\n                if ((result = get_arg_value(ctx, &arg_value)) == 0) {\n                    sum += arg_value;\n                }\n            } while (!result);\n\n            if (result != ANJAY_EXECUTE_GET_ARG_END) {\n                return result;\n            }\n            addition_result = sum;\n            return 0;\n        }\n        default:\n            // no other resource is executable\n            return ANJAY_ERR_METHOD_NOT_ALLOWED;\n        }\n    }\n\nWhere `get_arg_value` function is:\n\n.. highlight:: c\n.. snippet-source:: examples/tutorial/AT-CustomObjects/read-only-with-executable/src/main.c\n\n    static int get_arg_value(anjay_execute_ctx_t *ctx, int *out_value) {\n        // we expect arguments of form <0-9>='<integer>'\n        int arg_number;\n        bool has_value;\n        int result = anjay_execute_get_next_arg(ctx, &arg_number, &has_value);\n        // note that we do not check against duplicated argument ids\n        (void) arg_number;\n\n        if (result < 0 || result == ANJAY_EXECUTE_GET_ARG_END) {\n            // an error occured or there is just nothing more to read\n            return result;\n        }\n        if (!has_value) {\n            // we expect arguments with values only\n            return ANJAY_ERR_BAD_REQUEST;\n        }\n\n        char value_buffer[10];\n        if (anjay_execute_get_arg_value(ctx, NULL, value_buffer,\n                                              sizeof(value_buffer))\n                != 0) {\n            // the value must have been malformed or it is too long - either way, we\n            // don't like it\n            return ANJAY_ERR_BAD_REQUEST;\n        }\n        char *endptr = NULL;\n        long value = strtol(value_buffer, &endptr, 10);\n        if (!endptr || *endptr != '\\0' || value < INT_MIN || value > INT_MAX) {\n            // either not an integer or the number is too small / too big\n            return ANJAY_ERR_BAD_REQUEST;\n        }\n        *out_value = (int) value;\n        return 0;\n    }\n\nNow, we need to update ``resource_read`` handler, so that:\n    - it returns 4.05 Method Not Allowed when an attempt to read Executable resource is made,\n    - it returns an addition result, when a Read is performed on a `Value` resource.\n\n.. warning::\n\n    It is worth to mention that the LwM2M specification **explicitly forbids**\n    existence of executable resources that could be read at the same time.\n\n\nSo, here is how it could look like:\n\n.. highlight:: c\n.. snippet-source:: examples/tutorial/AT-CustomObjects/read-only-with-executable/src/main.c\n\n    static int test_resource_read(anjay_t *anjay,\n                                  const anjay_dm_object_def_t *const *obj_ptr,\n                                  anjay_iid_t iid,\n                                  anjay_rid_t rid,\n                                  anjay_riid_t riid,\n                                  anjay_output_ctx_t *ctx) {\n        // ...\n        switch (rid) {\n        // ...\n        case 1:\n            return anjay_ret_i64(ctx, addition_result);\n        case 2:\n            return ANJAY_ERR_METHOD_NOT_ALLOWED;\n        default:\n            // control will never reach this part due to test_list_resources\n            return 0;\n        }\n    }\n\nFinally, we need to revisit an object definition to make sure our execute\nhandler is set as a ``resource_execute`` implementation:\n\n.. highlight:: c\n.. snippet-source:: examples/tutorial/AT-CustomObjects/read-only-with-executable/src/main.c\n\n    static const anjay_dm_object_def_t OBJECT_DEF = {\n    // ...\n        .handlers = {\n            // ...\n            .resource_read = test_resource_read,\n            .resource_execute = test_resource_execute\n            // ...\n        }\n    // ...\n    };\n\nAnd that's it.\n\n.. note::\n\n    As before, you can find full source-code of this example in\n    `examples/tutorial/AT-CustomObjects/read-only-with-executable` subdirectory\n    of the Anjay root source dir.\n\n    More examples of the Execute handlers can be found in object\n    implementations of the demo client (in the `demo` subdirectory). Just\n    grep for the `resource_execute` keyword.\n"
  },
  {
    "path": "doc/sphinx/source/AdvancedTopics/AT-CustomObjects/AT_CO_SingleInstanceReadOnly.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nSingle-instance read-only object\n================================\n\n.. include:: Anjay_codegen_note.rst\n\nThis is the simplest possible case. Let us implement following custom Object:\n\n+-------------+-----------+-----------+\n| Name        | Object ID | Instances |\n+=============+===========+===========+\n| Test object | 1234      | Single    |\n+-------------+-----------+-----------+\n\nWith two simple Resources:\n\n+-------------+-------------+------------+-----------+-----------+---------+\n| Name        | Resource ID | Operations | Instances | Mandatory | Type    |\n+=============+=============+============+===========+===========+=========+\n| Object name | 0           | Read       | Single    | Mandatory | String  |\n+-------------+-------------+------------+-----------+-----------+---------+\n| Timestamp   | 1           | Read       | Single    | Mandatory | Integer |\n+-------------+-------------+------------+-----------+-----------+---------+\n\nIn this case, the most interesting handler type is ``anjay_dm_resource_read_t``,\ncalled whenever the library needs to get a value of a Resource. This might\nhappen if:\n\n- a LwM2M Server sends a Read request,\n\n- the Resource is being observed and the library needs to send a Notify message,\n\n- value of the Resource is required for the library to function correctly\n  (mostly related to Objects 0 (Security), 1 (Server) and 2 (Access Control)).\n\nThe Read handler for our test object might be implemented as follows:\n\n.. highlight:: c\n.. snippet-source:: examples/tutorial/AT-CustomObjects/read-only/src/main.c\n\n    static int test_resource_read(anjay_t *anjay,\n                                  const anjay_dm_object_def_t *const *obj_ptr,\n                                  anjay_iid_t iid,\n                                  anjay_rid_t rid,\n                                  anjay_riid_t riid,\n                                  anjay_output_ctx_t *ctx) {\n        // These arguments may seem superfluous now, but they will come in handy\n        // while defining more complex objects\n        (void) anjay;   // unused\n        (void) obj_ptr; // unused: the object holds no state\n        (void) iid;     // unused: will always be 0 for single-instance Objects\n        (void) riid;    // unused: will always be ANJAY_ID_INVALID\n\n        switch (rid) {\n        case 0:\n            return anjay_ret_string(ctx, \"Test object\");\n        case 1:\n            return anjay_ret_i64(ctx, avs_time_real_now().since_real_epoch.seconds);\n        default:\n            // control will never reach this part due to test_list_resources\n            return 0;\n        }\n    }\n\n\nWhat happens here?\n\n- ``rid`` value is compared against all known Resource IDs to determine what\n  value should be returned to the library.\n- Resource value is passed to the library via one of ``anjay_ret_*`` functions,\n  depending on the actual data type of a Resource. The value returned\n  by an appropriate call is then forwarded up - this ensures correct error\n  handling in case anything goes wrong.\n\n\nThe code above makes reference to a ``test_list_resources`` function - this is\nanother handler, used by the library to determine which resources are supported\nand present in a given Object Instance. A LwM2M Client may be able to handle a\nResource that has no default value - in that case, the Resource is always\n*supported*, but becomes *present* only after a LwM2M Server sets its value\nfirst. Before that, it can be treated as non-existent - it will not be reported\nvia the Discover operation, for example. Examples include Default Minimum Period\nand Default Maximum Period Resources of the LwM2M Server object.\n\nIn our case, Resources 0 and 1 are always present in the only Instance we have,\nso we can implement the ``test_resource_preset`` handler simply as:\n\n.. highlight:: c\n.. snippet-source:: examples/tutorial/AT-CustomObjects/read-only/src/main.c\n\n    static int test_list_resources(anjay_t *anjay,\n                                   const anjay_dm_object_def_t *const *obj_ptr,\n                                   anjay_iid_t iid,\n                                   anjay_dm_resource_list_ctx_t *ctx) {\n        (void) anjay;   // unused\n        (void) obj_ptr; // unused\n        (void) iid;     // unused\n\n        anjay_dm_emit_res(ctx, 0, ANJAY_DM_RES_R, ANJAY_DM_RES_PRESENT);\n        anjay_dm_emit_res(ctx, 1, ANJAY_DM_RES_R, ANJAY_DM_RES_PRESENT);\n        return 0;\n    }\n\n\nThe ``anjay_dm_emit_res()`` function, used in the above snippet, is declared as:\n\n.. highlight:: c\n.. snippet-source:: include_public/anjay/io.h\n\n    void anjay_dm_emit_res(anjay_dm_resource_list_ctx_t *ctx,\n                           anjay_rid_t rid,\n                           anjay_dm_resource_kind_t kind,\n                           anjay_dm_resource_presence_t presence);\n\nIt shall be called for all *supported* resources. The ``presence`` argument\ninforms the library whether a given resource is *present* at the moment.\n\nThe ``kind`` argument informs the library what kind of operations are legal to\nperform on the resource. Valid values are:\n\n   * ``ANJAY_DM_RES_R`` - read-only single instance resource\n   * ``ANJAY_DM_RES_W`` - write-only single instance resource\n   * ``ANJAY_DM_RES_RW`` - read/write single instance resource\n   * ``ANJAY_DM_RES_RM`` - read-only multiple instance resource\n   * ``ANJAY_DM_RES_WM`` - write-only multiple instance resource\n   * ``ANJAY_DM_RES_RWM`` - read/write multiple instance resource\n   * ``ANJAY_DM_RES_E`` - executable resource\n\nNote that when communicating with a Bootstrap Server may be able to ignore this\ninformation, see :doc:`AT_CO_BootstrapAwareness` for more information.\n\nNote that the resources MUST be returned in a strictly ascending, sorted order.\n\nHaving the Read and List Resources handlers implemented, one can initialize the\n``anjay_dm_object_def_t`` structure:\n\n.. highlight:: c\n.. snippet-source:: examples/tutorial/AT-CustomObjects/read-only/src/main.c\n\n    static const anjay_dm_object_def_t OBJECT_DEF = {\n        // Object ID\n        .oid = 1234,\n\n        .handlers = {\n            // single-instance Objects can use this pre-implemented handler:\n            .list_instances = anjay_dm_list_instances_SINGLE,\n\n            .list_resources = test_list_resources,\n            .resource_read = test_resource_read\n\n            // all other handlers can be left NULL if only Read operation is\n            // required\n        }\n    };\n\n\n.. topic:: Why are all these handlers required?\n\n   When the library attempts to perform an operation (e.g. Read) on a Resource\n   it first performs a number of checks to ensure the target path is correct\n   and the operation itself is allowed. Assuming a LwM2M Server requests\n   some operation on the path /1/2/3:\n\n   #. First, the library checks whether an Object with ID = 1 is registered.\n      If not, a Not Found response is issued.\n\n   #. ``list_instances`` handler of Object 1 is called to determine whether\n      Instance 2 exists. If not, a Not Found response is issued.\n\n   #. If multiple LwM2M Servers are configured, the library inspects Access\n      Control Object to check whether the server requesting an operation should\n      be allowed to perform it.\n\n      .. note::\n\n          More info: :doc:`../AT-AccessControl`\n\n   #. ``list_resources`` handler is called to ensure that Resource 3\n      is supported and present for Object Instance 2. If the handler returns 0,\n      a Not Found response is issued.\n\n   #. Finally, if all other checks succeeded, a specific handler (e.g.\n      ``resource_read`` for Read operation) is called.\n\n   Any of the handlers above may also fail with a specific CoAP error code\n   (see `ANJAY_ERR_* constants <../../api/core_8h.html>`_), aborting the\n   sequence early and - if the Read was triggered by a server request - causing\n   the library to respond with returned error code.\n\n\nWhen the Object Definition is ready, the only thing left to do is registering\nit in the library:\n\n.. snippet-source:: examples/tutorial/AT-CustomObjects/read-only/src/main.c\n\n   int main(int argc, char *argv[]) {\n       // ... Anjay initialization\n\n       // note: in this simple case the object does not have any state,\n       // so it's fine to use a plain double pointer to its definition struct\n       const anjay_dm_object_def_t *test_object_def_ptr = &OBJECT_DEF;\n\n       anjay_register_object(anjay, &test_object_def_ptr);\n\n       // ... event loop\n   }\n\n\nAfter registering the object, whenever a LwM2M Server issues a Read request\non Object 1234 or any of its Resources, Anjay will take care of preparing\na response containing the value of requested Resource.\n\n.. note::\n\n    Complete code of this example can be found in\n    `examples/tutorial/AT-CustomObjects/read-only` subdirectory of main Anjay\n    project repository.\n"
  },
  {
    "path": "doc/sphinx/source/AdvancedTopics/AT-CustomObjects/Anjay_codegen_note.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\n\n.. note::\n\n    This section describes in details the implementation of custom Objects in\n    Anjay, either defined in `OMA LwM2M Object and Resource Registry\n    <https://technical.openmobilealliance.org/OMNA/LwM2M/LwM2MRegistry.html>`_\n    or designed by user.\n\n    Although most of the Object's code can be generated using\n    :ref:`anjay-object-stub-generator` if you have Object's definition in XML,\n    it is recommended to read this section to have a clear understanding on what\n    various parts of the LwM2M Object code are for.\n"
  },
  {
    "path": "doc/sphinx/source/AdvancedTopics/AT-CustomObjects.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nCustom LwM2M objects\n====================\n\n.. include:: AT-CustomObjects/Anjay_codegen_note.rst\n\nLwM2M Objects are described using the ``anjay_dm_object_def_t`` struct,\nwhich holds:\n\n- Object ID,\n- a list of Resource IDs that the Object might contain,\n- a set of handlers used by the library to access and possibly modify the Object.\n\n.. highlight:: c\n.. snippet-source:: include_public/anjay/dm.h\n\n    /** A struct containing pointers to Object handlers. */\n    typedef struct {\n        /**\n         * Get default Object attributes, @ref anjay_dm_object_read_default_attrs_t\n         *\n         * Required for handling *LwM2M Discover* and *LwM2M Observe* operations.\n         *\n         * Can be NULL if the *Attribute Storage* feature is enabled. Non-NULL\n         * handler overrides *Attribute Storage* logic.\n         */\n        anjay_dm_object_read_default_attrs_t *object_read_default_attrs;\n\n        /**\n         * Set default Object attributes,\n         * @ref anjay_dm_object_write_default_attrs_t\n         *\n         * Required for handling *LwM2M Write-Attributes* operation.\n         *\n         * Can be NULL when *Attribute Storage* feature is enabled. Non-NULL handler\n         * overrides *Attribute Storage* logic.\n         */\n        anjay_dm_object_write_default_attrs_t *object_write_default_attrs;\n\n        /**\n         * Enumerate available Object Instances, @ref anjay_dm_list_instances_t\n         *\n         * Required for every LwM2M operation.\n         *\n         * **Must not be NULL.** @ref anjay_dm_list_instances_SINGLE can be used\n         * here.\n         */\n        anjay_dm_list_instances_t *list_instances;\n\n        /**\n         * Resets an Object Instance, @ref anjay_dm_instance_reset_t\n         *\n         * Required for handling *LwM2M Write* operation in *replace mode*.\n         *\n         * Can be NULL if the object does not contain writable resources.\n         */\n        anjay_dm_instance_reset_t *instance_reset;\n\n        /**\n         * Create an Object Instance, @ref anjay_dm_instance_create_t\n         *\n         * Required for handling *LwM2M Create* operation.\n         *\n         * Can be NULL for single instance objects.\n         */\n        anjay_dm_instance_create_t *instance_create;\n\n        /**\n         * Delete an Object Instance, @ref anjay_dm_instance_remove_t\n         *\n         * Required for handling *LwM2M Delete* operation performed on Object\n         * Instances.\n         *\n         * Can be NULL for single instance objects.\n         */\n        anjay_dm_instance_remove_t *instance_remove;\n\n        /**\n         * Get default Object Instance attributes,\n         * @ref anjay_dm_instance_read_default_attrs_t\n         *\n         * Required for handling *LwM2M Discover* and *LwM2M Observe* operations.\n         *\n         * Can be NULL when *Attribute Storage* feature is enabled. Non-NULL handler\n         * overrides *Attribute Storage* logic.\n         */\n        anjay_dm_instance_read_default_attrs_t *instance_read_default_attrs;\n\n        /**\n         * Set default Object Instance attributes,\n         * @ref anjay_dm_instance_write_default_attrs_t\n         *\n         * Required for handling *LwM2M Write-Attributes* operation.\n         *\n         * Can be NULL when *Attribute Storage* feature is enabled. Non-NULL handler\n         * overrides *Attribute Storage* logic.\n         */\n        anjay_dm_instance_write_default_attrs_t *instance_write_default_attrs;\n\n        /**\n         * Enumerate PRESENT Resources in a given Object Instance,\n         * @ref anjay_dm_list_resources_t\n         *\n         * Required for every LwM2M operation.\n         *\n         * **Must not be NULL.**\n         */\n        anjay_dm_list_resources_t *list_resources;\n\n        /**\n         * Get Resource value, @ref anjay_dm_resource_read_t\n         *\n         * Required for *LwM2M Read* operation.\n         *\n         * Can be NULL if the object does not contain readable resources.\n         */\n        anjay_dm_resource_read_t *resource_read;\n\n        /**\n         * Set Resource value, @ref anjay_dm_resource_write_t\n         *\n         * Required for *LwM2M Write* operation.\n         *\n         * Can be NULL if the object does not contain writable resources.\n         */\n        anjay_dm_resource_write_t *resource_write;\n\n        /**\n         * Perform Execute action on a Resource, @ref anjay_dm_resource_execute_t\n         *\n         * Required for *LwM2M Execute* operation.\n         *\n         * Can be NULL if the object does not contain executable resources.\n         */\n        anjay_dm_resource_execute_t *resource_execute;\n\n        /**\n         * Remove all Resource Instances from a Multiple Resource,\n         * @ref anjay_dm_resource_reset_t\n         *\n         * Required for *LwM2M Write* operation performed on multiple-instance\n         * resources.\n         *\n         * Can be NULL if the object does not contain multiple writable resources.\n         */\n        anjay_dm_resource_reset_t *resource_reset;\n\n        /**\n         * Enumerate available Resource Instances,\n         * @ref anjay_dm_list_resource_instances_t\n         *\n         * Required for *LwM2M Read*, *LwM2M Write* and *LwM2M Discover* operations\n         * performed on multiple-instance resources..\n         *\n         * Can be NULL if the object does not contain multiple resources.\n         */\n        anjay_dm_list_resource_instances_t *list_resource_instances;\n\n        /**\n         * Get Resource attributes, @ref anjay_dm_resource_read_attrs_t\n         *\n         * Required for handling *LwM2M Discover* and *LwM2M Observe* operations.\n         *\n         * Can be NULL when *Attribute Storage* feature is enabled. Non-NULL handler\n         * overrides *Attribute Storage* logic.\n         */\n        anjay_dm_resource_read_attrs_t *resource_read_attrs;\n\n        /**\n         * Set Resource attributes, @ref anjay_dm_resource_write_attrs_t\n         *\n         * Required for handling *LwM2M Write-Attributes* operation.\n         *\n         * Can be NULL when *Attribute Storage* feature is enabled. Non-NULL handler\n         * overrides *Attribute Storage* logic.\n         */\n        anjay_dm_resource_write_attrs_t *resource_write_attrs;\n\n        /**\n         * Begin a transaction on this Object, @ref anjay_dm_transaction_begin_t\n         *\n         * Required for handling modifying operation: *LwM2M Write*, *LwM2M Create*\n         * or *LwM2M Delete*.\n         *\n         * Can be NULL for read-only objects. @ref anjay_dm_transaction_NOOP can be\n         * used here.\n         */\n        anjay_dm_transaction_begin_t *transaction_begin;\n\n        /**\n         * Validate whether a transaction on this Object can be cleanly committed.\n         * See @ref anjay_dm_transaction_validate_t\n         *\n         * Required for handling modifying operation: *LwM2M Write*, *LwM2M Create*\n         * or *LwM2M Delete*.\n         *\n         * Can be NULL for read-only objects. @ref anjay_dm_transaction_NOOP can be\n         * used here.\n         */\n        anjay_dm_transaction_validate_t *transaction_validate;\n\n        /**\n         * Commit changes made in a transaction, @ref anjay_dm_transaction_commit_t\n         *\n         * Required for handling modifying operation: *LwM2M Write*, *LwM2M Create*\n         * or *LwM2M Delete*.\n         *\n         * Can be NULL for read-only objects. @ref anjay_dm_transaction_NOOP can be\n         * used here.\n         */\n        anjay_dm_transaction_commit_t *transaction_commit;\n\n        /**\n         * Rollback changes made in a transaction,\n         * @ref anjay_dm_transaction_rollback_t\n         *\n         * Required for handling modifying operation: *LwM2M Write*, *LwM2M Create*\n         * or *LwM2M Delete*.\n         *\n         * Can be NULL for read-only objects. @ref anjay_dm_transaction_NOOP can be\n         * used here.\n         */\n        anjay_dm_transaction_rollback_t *transaction_rollback;\n\n        /**\n         * Get Resource Instance attributes, @ref\n         * anjay_dm_resource_instance_read_attrs_t\n         *\n         * Required for handling *LwM2M Discover* and *LwM2M Observe* operations.\n         *\n         * Can be NULL if the object does not contain multiple resources, when\n         * *Attribute Storage* feature is enabled, or when the application only\n         * targets compliance with LwM2M TS 1.0. Non-NULL handler overrides\n         * *Attribute Storage* logic.\n         */\n        anjay_dm_resource_instance_read_attrs_t *resource_instance_read_attrs;\n\n        /**\n         * Set Resource Instance attributes, @ref\n         * anjay_dm_resource_instance_write_attrs_t\n         *\n         * Required for handling *LwM2M Write-Attributes* operation.\n         *\n         * Can be NULL if the object does not contain multiple resources, when\n         * *Attribute Storage* feature is enabled, or when the application only\n         * targets compliance with LwM2M TS 1.0. Non-NULL handler overrides\n         * *Attribute Storage* logic.\n         */\n        anjay_dm_resource_instance_write_attrs_t *resource_instance_write_attrs;\n\n    #ifdef ANJAY_WITH_LWM2M12\n        /**\n         * Delete a Resource Instance from a Multiple Resource,\n         * @ref anjay_dm_resource_instance_remove_t\n         *\n         * Required for handling *LwM2M Delete* operation performed on Resource\n         * Instances.\n         *\n         * Can be NULL if the object does not contain multiple writable resources.\n         *\n         * NOTE: This operation is new to the LwM2M 1.2 standard. It will never be\n         * called when communicating using protocol version 1.0 or 1.1. If you do\n         * not aim for LwM2M 1.2 compliance, it can also be NULL.\n         */\n        anjay_dm_resource_instance_remove_t *resource_instance_remove;\n    #endif // ANJAY_WITH_LWM2M12\n    } anjay_dm_handlers_t;\n\n    /** A struct defining a LwM2M Object. */\n    struct anjay_dm_object_def_struct {\n        /** Object ID; MUST not be <c>ANJAY_ID_INVALID</c> (65535) */\n        anjay_oid_t oid;\n\n        /**\n         * Object version: a string with static lifetime, containing two digits\n         * separated by a dot (for example: \"1.1\").\n         * If left NULL, client will not include the \"ver=\" attribute in Register\n         * and Discover messages. This implies:\n         * 1. Version 1.0 for Non-Core Objects.\n         * 2. The version corresponding to the version in the LwM2M Enabler for Core\n         * Objects.\n         */\n        const char *version;\n\n        /** Handler callbacks for this object. */\n        anjay_dm_handlers_t handlers;\n    };\n\nSee `API docs <../api/structanjay__dm__object__def__struct.html>`_ for detailed\ninformation about each of those fields.\n\nThese structures themselves may seem intimidating at the first glance. In\nreality, most use cases will not require setting up all possible handlers - this\ntutorial will show multiple possible implementations, from the simplest cases to\nthe most complex ones.\n\n.. toctree::\n   :glob:\n   :titlesonly:\n\n   AT-CustomObjects/AT_CO_SingleInstanceReadOnly\n   AT-CustomObjects/AT_CO_SingleInstanceExecutableAndReadOnly\n   AT-CustomObjects/AT_CO_MultiInstanceReadOnlyFixed\n   AT-CustomObjects/AT_CO_FixedInstanceWritable\n   AT-CustomObjects/AT_CO_MultiInstanceDynamic\n   AT-CustomObjects/AT_CO_MultipleResourceInstances\n   AT-CustomObjects/AT_CO_BootstrapAwareness\n"
  },
  {
    "path": "doc/sphinx/source/AdvancedTopics/AT-EventLoopNotes.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nNotes on event loop APIs\n========================\n\n.. _single-request-single-function-call:\n\nSingle request - single function call\n-------------------------------------\n\nTo start, we first need to establish that Anjay LwM2M Client API assumes a\nsingle-threaded mode of operation. It relies on the user calling\n``anjay_serve()`` whenever some packet is received and ``anjay_sched_run()``\nregularly, as described in the :doc:`previous chapter <AT-CustomEventLoop>`.\nBoth of the methods block until the incoming message or (respectively) a job is\nprocessed.\n\nAn incoming message may either be fully contained in a single packet, or split\nbetween multiple packets, however ``anjay_serve()`` is called just for the\nfirst one. Other parts (if any) are fetched internally by the library. In other\nwords: ``anjay_serve()`` blocks until the message gets handled completely.\n\n.. figure:: _images/anjay-server-request.svg\n   :width: 100%\n\n   Handling a single LwM2M request. The application gets notified a packet\n   was received on a socket using ``poll()``/``select()`` and calls\n   ``anjay_serve()`` on that socket, allowing the library to interpret the\n   request and create a response.\n\n\nWhen considering a request from the LwM2M Server, the block-wise transfer may\nbe initiated either by server (e.g. a big Write request) or by the client\n(e.g. a large response to a Read request).\n\n.. figure:: _images/anjay-server-block-request.svg\n   :width: 100%\n\n   Handling a block-wise request from the LwM2M Server. The server realizes\n   its request is too big to fit in a single packet and explicitly initiates\n   block-wise transfer by adding CoAP BLOCK option to the request. A single\n   call to ``anjay_serve()`` blocks the client until the request is completely\n   handled.\n\n.. figure:: _images/anjay-block-response-to-server-request.svg\n   :width: 100%\n\n   A block-wise response to a non-block request from the LwM2M Server. In\n   this case, the server performs a simple Read request. The client realizes\n   that returned data is too big, and adds a BLOCK option to the response.\n   The server then requests further blocks of a response. The call to\n   ``anjay_serve()`` only returns after the last block of a response is sent.\n\n\nSimilar situation arises when the client attempts to send a Register or Update\nLwM2M request to the server with a large list of available Object Instances,\nor a big Notify message. The difference is that the client sends its own\nrequests from within ``anjay_sched_run()`` call instead of ``anjay_serve()``.\n\n.. figure:: _images/anjay-client-request.svg\n   :width: 100%\n\n   A simple request from the LwM2M Client.\n\n.. figure:: _images/anjay-block-client-request.svg\n   :width: 100%\n\n   A block-wise request from the LwM2M Client. ``anjay_sched_run()`` call\n   blocks until the full transfer is complete.\n\n\nBecause ``anjay_serve()`` blocks after a packet arrives, the library can\nhandle at most one LwM2M Server at time, which makes its usage convenient,\nas one does not have to worry about data model being accessed or modified\nby multiple LwM2M Servers at the same time. Unfortunately it may happen to\nbe a problem, as during blockwise transfers the library is unable to respond\nto other LwM2M Servers with anything else than 5.03 Service Unavailable.\n\nBefore getting worried about it too much, one shall realize that the above\nbehavior happens only when a blockwise transfer is issued on some part of\nthe data model - i.e. for that to become a problem one would have to store\nand transfer big amounts of data regularly through LwM2M which, in context of\nresource constrained environments targeted by the LwM2M protocol might not\nbe the best fit.\n\n.. note::\n\n   The blocking behavior does not apply to firmware downloaded using the PULL\n   method. See :ref:`firmware-transfer` for details.\n\n\nTransactions and ``anjay_serve()``\n----------------------------------\n\nOur data model supports transactional operations. They are here to ensure that\nwhenever something goes wrong during a transaction, all changes applied since\nits beginning can be reverted - keeping the LwM2M Client in a consistent state.\n\nAs we already know, calling ``anjay_serve()`` corresponds to processing a\nsingle LwM2M request. This, along with properly implemented transaction\nhandlers guarantees that if the LwM2M Client was in a consistent state\nbefore request had been received, then it will remain in a consistent state\nafter the request is processed. Moreover, because of single-threaded mode of\noperation no other LwM2M Server can see the LwM2M Client being in partially\nconsistent state.\n\nThings work a bit different during the Bootstrap Sequence though. When the\nClient/Server initiated Bootstrap begins, the library fires transaction\nhandlers for all data model entities. At the same time, it enters the state\nwhere requests originated from Bootstrap Server only are handled - there may be\nmore than one such request, and so ``anjay_serve()`` could get called multiple\ntimes. This again does not hurt consistency in any way, because according to\nthe LwM2M Specification, the LwM2M Client may ignore other servers during that\nspecial time, and the library is doing just that - meaning\nthat they won't be able to observe intermediate initialization state.\n\nAfter the Bootstrap Sequence finishes the library checks that the data model is\nvalid, and if it isn't the previous correct state will be restored, which\nproves the point.\n\n\nNotifications\n-------------\n\nAnjay uses its scheduler to track pending notifications. Whenever\na notification has to be sent, it is done from within ``anjay_sched_run()``\nfunction.\n\n.. note::\n\n   Calling ``anjay_notify_changed()`` or ``anjay_notify_instances_changed()``\n   does not send notifications immediately - they use the scheduler instead.\n\n\n.. figure:: _images/anjay-notification.svg\n   :width: 100%\n\n   Sending a LwM2M Notify message.\n"
  },
  {
    "path": "doc/sphinx/source/AdvancedTopics/AT-IpsoObjects.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\n.. highlight:: c\n\nIPSO objects implementation\n=================================\n\n.. contents:: :local:\n\nIntroduction\n------------\n\nIPSO (Internet Protocol for Smart Objects) objects are a collection of LwM2M\nobjects that can be used to expose some common features of many IoT devices,\nlike sensors, buttons, actuators or control switches. Using predefined objects\nfor these purposes enables higher interoperability of applications, i.e. all\ndevices that have a temperature sensor can report the readings in a standardized\nway, making it possible to easily process such measurements from different,\nnonhomogeneous devices on the cloud.\n\nIn practice, IPSO objects are most importantly a convenient way to report sensor\ndata over LwM2M. All IPSO objects of aforementioned certain kinds share\ncommon set of resources, and thanks to that these objects in a large part can be\neasily preimplemented.\n\nAnjay provides a ready-to-use implementation of:\n\n- basic (i.e. scalar) sensor objects (e.g. Temperature Object or Pressure\n  Object),\n- three-axis sensor objects (e.g. Accelerometer Object or Magnetometer Object),\n- Push Button Object.\n\nUser's only responsibility is to retrieve those values from actual sensors\nand supply them to Anjay, making it very easy to implement LwM2M devices with\nsensor support.\n\nThe API is declared in ``include_public/anjay/ipso_objects.h`` and\n``include_public/anjay/ipso_objects_v2.h`` headers. To use them, enable them\nfirst by either defining ``ANJAY_WITH_MODULE_IPSO_OBJECTS`` and/or\n``ANJAY_WITH_MODULE_IPSO_OBJECTS_V2`` in the Anjay's configuration files, or, if\nusing CMake, enabling ``WITH_MODULE_ipso_objects`` and/or\n``WITH_MODULE_ipso_objects_v2`` options.\n\n.. important::\n\n    The APIs for basic and 3D IPSO sensors objects explained in this tutorial\n    are new, experimental variants declared in\n    ``include_public/anjay/ipso_objects_v2.h``.\n\nSupported objects\n-----------------\n\nImplementation of IPSO objects in Anjay supports objects that have the following\nset of resources:\n\n.. flat-table::\n   :header-rows: 2\n\n   * - :cspan:`3` **Basic (scalar) sensor objects**\n   * - **Resource ID**\n     - **Resource Name**\n     - **Must be supported by object**\n   * - 5601\n     - Min Measured Value\n     - no\n   * - 5602\n     - Max Measured Value\n     - no\n   * - 5603\n     - Min Range Value\n     - no\n   * - 5604\n     - Max Range Value\n     - no\n   * - 5605\n     - Reset Min and Max Measured Values\n     - no\n   * - 5700\n     - Sensor Value\n     - **yes**\n   * - 5701\n     - Sensor Units\n     - no\n\n.. flat-table::\n   :header-rows: 2\n\n   * - :cspan:`3` **Three-axis sensor objects**\n   * - **Resource ID**\n     - **Resource Name**\n     - **Must be supported by object**\n   * - 5508\n     - Min X Value\n     - no\n   * - 5509\n     - Max X Value\n     - no\n   * - 5510\n     - Min Y Value\n     - no\n   * - 5511\n     - Max Y Value\n     - no\n   * - 5512\n     - Min Z Value\n     - no\n   * - 5513\n     - Max Z Value\n     - no\n   * - 5603\n     - Min Range Value\n     - no\n   * - 5604\n     - Max Range Value\n     - no\n   * - 5605\n     - Reset Min and Max Measured Values\n     - no\n   * - 5701\n     - Sensor Units\n     - no\n   * - 5702\n     - X Value\n     - **yes**\n   * - 5703\n     - Y Value\n     - no\n   * - 5704\n     - Z Value\n     - no\n\nAs of December 13th, 2023, objects registered by IPSO Alliance that meet these\nrequirements are:\n3300 (Generic Sensor),\n3301 (Illuminance),\n3303 (Temperature),\n3304 (Humidity),\n3313 (Accelerometer),\n3314 (Magnetometer),\n3315 (Barometer),\n3316 (Voltage),\n3317 (Current),\n3318 (Frequency),\n3319 (Depth),\n3320 (Percentage),\n3321 (Altitude),\n3322 (Load),\n3323 (Pressure),\n3324 (Loudness),\n3325 (Concentration),\n3326 (Acidity),\n3327 (Conductivity),\n3328 (Power),\n3329 (Power Factor),\n3330 (Distance),\n3334 (Gyrometer),\n3345 (Multiple Axis Joystick),\n3346 (Rate).\n\nAdditionally, object 3347 (Push Button) is supported with a separate API.\n\nUsage example\n-------------\n\nThis tutorial builds up on the :doc:`../../BasicClient/BC-MandatoryObjects`\ntutorial which contains an implementation of a minimal, but complete LwM2M\nclient.\n\n.. note::\n\n    Complete code of this example can be found in\n    `examples/tutorial/AT-IpsoObjects` subdirectory of main Anjay project\n    repository.\n\nIn this example we'll implement a simple application that simulates a few\nthermometers, accelerometers and buttons.\n\nInstalling objects and instances\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nTo setup an IPSO object, you must install it first using one of the following\nmethods:\n\n * `anjay_ipso_v2_basic_sensor_install <../api/ipso__objects__v2_8h.html#ac3200c3c61ea62f76eb4e606adfcd90f>`_,\n * `anjay_ipso_v2_3d_sensor_install <../api/ipso__objects__v2_8h.html#a154a62e2adafe9890cbd66c91bb8f20a>`_,\n * `anjay_ipso_button_install <../api/ipso__objects_8h.html#a11e68bd571d70da7d17ee5c73cff6e0d>`_.\n\nFor sensors, the API accepts Object ID, object version and maximum number of\ninstances that'll be installed later. For button, the Object ID and version is\ndefined upfront.\n\n.. important::\n\n    It's important to set appropriate object version number. Without configuring\n    it a LwM2M server may fail to interpret resources that were added in newer\n    versions of an object. Such an example is Gyrometer Object, which has the\n    \"Reset Min and Max Measured Values\" resource available only since version\n    1.1.\n\n    In this example all enabled resources are available in version 1.0 of these\n    objects, to which passing ``NULL`` defaults to.\n\nAfter installing objects, instances of these objects can be added using\nfollowing APIs:\n\n * `anjay_ipso_v2_basic_sensor_instance_add <../api/ipso__objects__v2_8h.html#ae92a38b4eba14909b00233088e6256b5>`_,\n * `anjay_ipso_v2_3d_sensor_instance_add <../api/ipso__objects__v2_8h.html#a760f33f44690447409e77066b4c86295>`_,\n * `anjay_ipso_button_instance_add <../api/ipso__objects_8h.html#ae981fe67ce9c2e9032284f26fa5fb3c3>`_.\n\nFor basic and 3D sensors, these methods accept an initial value of the sensor\nand a structure that provides metadata about each instance:\n`anjay_ipso_v2_basic_sensor_meta_t <../api/ipso__objects__v2_8h.html#a2e0cd9b35002025a91edb96842cd29cf>`_\nand\n`anjay_ipso_v2_3d_sensor_meta_t <../api/ipso__objects__v2_8h.html#a34fe615fc03fa7313a2dffabd326058f>`_,\nrespectively.\n\nThese structs are used to configure unit, reported minimum and maximum values\nthat can be measured by a sensor, and presence of optional Y and Z axis in case\nof 3D objects.\n\nIn our example, let's define some macros and necessary metadata structs first:\n\n.. snippet-source:: examples/tutorial/AT-IpsoObjects/src/main.c\n\n    #define TEMPERATURE_OBJ_OID 3303\n    #define ACCELEROMETER_OBJ_OID 3313\n\n    #define THERMOMETER_COUNT 3\n    #define ACCELEROMETER_COUNT 2\n    #define BUTTON_COUNT 4\n\n    static const anjay_ipso_v2_basic_sensor_meta_t thermometer_meta = {\n        .unit = \"Cel\",\n        .min_max_measured_value_present = true,\n        .min_range_value = -20.0,\n        .max_range_value = 120.0\n    };\n\n    static const anjay_ipso_v2_3d_sensor_meta_t accelerometer_meta = {\n        .unit = \"m/s2\",\n        .min_range_value = -20.0,\n        .max_range_value = 20.0,\n        .y_axis_present = true,\n        .z_axis_present = true\n    };\n\n.. note::\n\n    It's a good practice to report values using units defined in\n    `SenML Units Registry <https://www.rfc-editor.org/rfc/rfc8428.html#section-12.1>`_,\n    the up to date list can be\n    `found here <https://www.iana.org/assignments/senml/senml.xhtml>`_.\n\nThen, let's introduce some helper methods that will install our sensor objects\nand add all instances upfront:\n\n.. snippet-source:: examples/tutorial/AT-IpsoObjects/src/main.c\n\n    static int setup_temperature_object(anjay_t *anjay) {\n        if (anjay_ipso_v2_basic_sensor_install(anjay, TEMPERATURE_OBJ_OID, NULL,\n                                              THERMOMETER_COUNT)) {\n            return -1;\n        }\n\n        for (anjay_iid_t iid = 0; iid < THERMOMETER_COUNT; iid++) {\n            if (anjay_ipso_v2_basic_sensor_instance_add(\n                        anjay, TEMPERATURE_OBJ_OID, iid, 20.0, &thermometer_meta)) {\n                return -1;\n            }\n        }\n\n        return 0;\n    }\n\n    static int setup_accelerometer_object(anjay_t *anjay) {\n        if (anjay_ipso_v2_3d_sensor_install(anjay, ACCELEROMETER_OBJ_OID, NULL,\n                                            ACCELEROMETER_COUNT)) {\n            return -1;\n        }\n\n        for (anjay_iid_t iid = 0; iid < ACCELEROMETER_COUNT; iid++) {\n            anjay_ipso_v2_3d_sensor_value_t initial_value = {\n                .x = 0.0,\n                .y = 0.0,\n                .z = 0.0\n            };\n\n            if (anjay_ipso_v2_3d_sensor_instance_add(anjay, ACCELEROMETER_OBJ_OID,\n                                                    iid, &initial_value,\n                                                    &accelerometer_meta)) {\n                return -1;\n            }\n        }\n\n        return 0;\n    }\n\n    static int setup_button_object(anjay_t *anjay) {\n        if (anjay_ipso_button_install(anjay, BUTTON_COUNT)) {\n            return -1;\n        }\n\n        for (anjay_iid_t iid = 0; iid < BUTTON_COUNT; iid++) {\n            if (anjay_ipso_button_instance_add(anjay, iid, \"\")) {\n                return -1;\n            }\n        }\n\n        return 0;\n    }\n\nFinally, let's call these methods in initialization code, in ``main()`` method:\n\n.. snippet-source:: examples/tutorial/AT-IpsoObjects/src/main.c\n    :emphasize-lines: 5-7\n\n    int main(int argc, char *argv[]) {\n        // ...\n\n        if (setup_security_object(anjay) || setup_server_object(anjay)\n                || setup_temperature_object(anjay)\n                || setup_accelerometer_object(anjay)\n                || setup_button_object(anjay)) {\n            result = -1;\n        }\n\n        // ...\n    }\n\nUpdating values\n^^^^^^^^^^^^^^^\n\nTo update reported value of a sensor, use one of following methods:\n\n * `anjay_ipso_v2_basic_sensor_value_update <../api/ipso__objects__v2_8h.html#ab9ee3d855e885a2dc25ae73f466dd228>`_,\n * `anjay_ipso_v2_3d_sensor_value_update <../api/ipso__objects__v2_8h.html#a2166bd5daae8fb235f96064d8b97c740>`_,\n * `anjay_ipso_button_update <../api/ipso__objects_8h.html#a84a9bf58b9cff7e1bd5fe9083576cfa2>`_.\n\n.. important::\n\n    Keep in mind that a LwM2M Server is allowed to configure resource\n    observations with attributes that require the client to report the data very\n    frequently or when some threshold value is exceeded, even for a very short\n    moment. If you want to ensure that server is notified of every change of\n    resource value that could meet such conditions, **you must update the value\n    very frequently**.\n\n.. important::\n\n    These methods (as all methods in Anjay's public API) cannot be called from\n    an interrupt. In case ``ANJAY_WITH_THREAD_SAFETY`` is disabled Anjay APIs\n    are not safe to call from other contexts than method which runs event loop\n    and ``avs_sched`` tasks, while if ``ANJAY_WITH_THREAD_SAFETY`` is enabled\n    calling such methods will attempt to lock a mutex from an interrupt which\n    also is wrong.\n\n    If your application retrieves new sensor values and/or button state changes\n    in an interrupt, you must find a way to pass these values to a non-interrupt\n    execution context.\n\nIn our example we're simulating values of these sensors, so let's add some\nutility methods first:\n\n.. snippet-source:: examples/tutorial/AT-IpsoObjects/src/main.c\n\n    static double get_random_in_range(double min, double max) {\n        return min + (max - min) * rand() / RAND_MAX;\n    }\n\n    static double get_thermometer_value(void) {\n        return get_random_in_range(thermometer_meta.min_range_value,\n                                  thermometer_meta.max_range_value);\n    }\n\n    static anjay_ipso_v2_3d_sensor_value_t get_accelerometer_value(void) {\n        return (anjay_ipso_v2_3d_sensor_value_t) {\n            .x = get_random_in_range(accelerometer_meta.min_range_value,\n                                    accelerometer_meta.max_range_value),\n            .y = get_random_in_range(accelerometer_meta.min_range_value,\n                                    accelerometer_meta.max_range_value),\n            .z = get_random_in_range(accelerometer_meta.min_range_value,\n                                    accelerometer_meta.max_range_value)\n        };\n    }\n\n    static bool get_button_state(void) {\n        return rand() % 2 == 0;\n    }\n\nThen, let's implement a scheduler task that will update all sensors. The task\nschedules itself to run every second:\n\n.. snippet-source:: examples/tutorial/AT-IpsoObjects/src/main.c\n\n    static void update_sensor_values(avs_sched_t *sched, const void *anjay_ptr) {\n        anjay_t *anjay = *(anjay_t *const *) anjay_ptr;\n\n        for (anjay_iid_t iid = 0; iid < THERMOMETER_COUNT; iid++) {\n            (void) anjay_ipso_v2_basic_sensor_value_update(\n                    anjay, TEMPERATURE_OBJ_OID, iid, get_thermometer_value());\n        }\n\n        for (anjay_iid_t iid = 0; iid < ACCELEROMETER_COUNT; iid++) {\n            anjay_ipso_v2_3d_sensor_value_t value = get_accelerometer_value();\n\n            (void) anjay_ipso_v2_3d_sensor_value_update(\n                    anjay, ACCELEROMETER_OBJ_OID, iid, &value);\n        }\n\n        for (anjay_iid_t iid = 0; iid < BUTTON_COUNT; iid++) {\n            (void) anjay_ipso_button_update(anjay, iid, get_button_state());\n        }\n\n        AVS_SCHED_DELAYED(sched, NULL, avs_time_duration_from_scalar(1, AVS_TIME_S),\n                          update_sensor_values, &anjay, sizeof(anjay));\n    }\n\nLastly, let's call this method once before entering event loop. From that moment\nthe task will keep running infinitely.\n\n.. snippet-source:: examples/tutorial/AT-IpsoObjects/src/main.c\n    :emphasize-lines: 5\n\n    int main(int argc, char *argv[]) {\n        // ...\n\n        if (!result) {\n            update_sensor_values(anjay_get_scheduler(anjay), &anjay);\n            result = anjay_event_loop_run(\n                    anjay, avs_time_duration_from_scalar(1, AVS_TIME_S));\n        }\n\n        // ...\n    }\n\nRemoving instances\n^^^^^^^^^^^^^^^^^^\n\nIn case you need to change the set of instances of installed IPSO objects, those\ninstances can be removed using following methods:\n\n * `anjay_ipso_v2_basic_sensor_instance_remove <../api/ipso__objects__v2_8h.html#af53a1881ef4ed8de52cb000700a0dbb9>`_,\n * `anjay_ipso_v2_3d_sensor_instance_remove <../api/ipso__objects__v2_8h.html#a2bd255f62cf4817ea567b65ddae6644c>`_,\n * `anjay_ipso_button_instance_remove <../api/ipso__objects_8h.html#af53a1881ef4ed8de52cb000700a0dbb9>`_.\n\nIn our example instance set doesn't change. All objects and instances are\nautomatically deleted when ``anjay_delete()`` is called.\n"
  },
  {
    "path": "doc/sphinx/source/AdvancedTopics/AT-NetworkErrorHandling.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nNetwork error handling\n======================\n\nLike any software that needs to communicate with other hosts over the network,\nAnjay needs to be prepared to handle communication errors. This page documents\nthe library's behavior during various error conditions.\n\nOutgoing request error handling table\n-------------------------------------\n\nThe following table describes the behavior of Anjay when various error\nconditions happen while performing each of the client-initiated operations.\n\n+-----------------+------------------+------------------+------------------+-------------+-------------------+\n|                 | Request          | Register         | Update           | De-register | Notify            |\n|                 | Bootstrap        |                  |                  |             | (confirmable)     |\n+=================+==================+==================+==================+=============+===================+\n| **Timeout       | Retry DTLS       | Retry DTLS       | Fall back        | Ignored     | Ignored by        |\n| (DTLS)** [#t]_  | handshake [#hs]_ | handshake [#hs]_ | to Register      |             | default;          |\n+-----------------+------------------+------------------+                  |             | configurable;     |\n| **Timeout       | Abort all        | :ref:`Abort      |                  |             | will be retried   |\n| (NoSec)** [#t]_ | communication    | registration     |                  |             | whenever          |\n|                 | [#a]_            | <err-abort-reg>` |                  |             | next notification |\n|                 |                  |                  |                  |             | is scheduled      |\n+-----------------+                  |                  +------------------+             +-------------------+\n| **Network       |                  |                  | Fall back to     |             | Fall back to      |\n| (e.g. ICMP)     |                  |                  | Client-Initiated |             | Client-Initiated  |\n| error**         |                  |                  | Bootstrap [#bs]_ |             | Bootstrap [#bs]_  |\n+-----------------+                  |                  +------------------+             +-------------------+\n| **CoAP error    |                  |                  | Fall back        |             | n/a               |\n| (4.xx, 5.xx)**  |                  |                  | to Register      |             |                   |\n+-----------------+                  |                  +------------------+             +-------------------+\n| **CoAP Reset**  |                  |                  | Fall back to     |             | Cancel            |\n|                 |                  |                  | Client-Initiated |             | observation       |\n+-----------------+                  |                  | Bootstrap [#bs]_ |             +-------------------+\n| **Internal      |                  |                  |                  |             | Cancel            |\n| error**         |                  |                  |                  |             | observation if    |\n|                 |                  |                  |                  |             | \"Notification     |\n|                 |                  |                  |                  |             | storing\" is       |\n|                 |                  |                  |                  |             | disabled          |\n+-----------------+------------------+------------------+------------------+-------------+-------------------+\n\n.. _err-abort-reg:\n\nThe \"Abort registration\" condition\n----------------------------------\n\nThis condition corresponds to the registration failure as used in the\n`Bootstrap and LwM2M Server Registration Mechanisms\n<http://www.openmobilealliance.org/release/LightweightM2M/V1_1_1-20190617-A/HTML-Version/OMA-TS-LightweightM2M_Core-V1_1_1-20190617-A.html#6-2-1-1-0-6211-Bootstrap-and-LwM2M-Server-Registration-Mechanisms>`_\nsection of LwM2M Core TS 1.1.\n\nIf the ``ANJAY_WITH_LWM2M11`` compile-time configuration option is enabled, the\nretry procedures as described in that section of the 1.1 TS will be performed,\nwith respect to settings stored in the appropriate Server object instance, or\nthe defaults values listen in the\n`\"Registration Procedures Default Values\" table\n<http://www.openmobilealliance.org/release/LightweightM2M/V1_1_1-20190617-A/HTML-Version/OMA-TS-LightweightM2M_Core-V1_1_1-20190617-A.html#Table-6211-1-Registration-Procedures-Default-Values>`_.\nAccording to this configuration, further failures may result in the \"abort all\ncommunication\" [#a]_ or \"fall back to Client-Initiated Bootstrap\" [#bs]_\ncondition.\n\nIn builds of Anjay that do not support LwM2M 1.1, the \"abort registration\"\ncondition is equivalent with the \"fall back to Client-Initiated Bootstrap\"\n[#bs]_ condition.\n\nOther error conditions\n----------------------\n\n* **Connect operation errors** can occur for several reasons, the most common\n  being:\n\n  * **(D)TLS handshake errors.** Handshakes are performed by the TLS backend\n    library used. This includes handling non-fatal errors and retransmissions.\n    In case of no response from the server, DTLS handshake retransmissions are\n    expected to follow `RFC 6347, Section 4.2.4.  Timeout and Retransmission\n    <https://tools.ietf.org/html/rfc6347#section-4.2.4>`_. The handshake timers\n    can be customized during Anjay initialization, by setting\n    `anjay_configuration_t::udp_dtls_hs_tx_params\n    <../api/structanjay__configuration.html#ab8ca076537138e7d78bd1ee5d5e2031a>`_.\n\n    Ultimate timeout, network-layer errors, and internal errors during the\n    handshake attempt will be treated as a failure of the \"connect\" operation.\n\n  * **Domain name resolution errors.** If the ``getaddrinfo()`` call (or\n    equivalent) fails to return any usable IP address, this is also treated as\n    a failure of the \"connect\" operation.\n\n  * **TCP handshake errors.** While the actual socket-level \"connect\" operation\n    does not involve any network communication for UDP and as such can almost\n    never fail, it performs actual handshake in case of TCP. Failure of this\n    handshake is also treated in the same way as the other cases mentioned here.\n\n  * In some cases, **inconsistent data model state** may be treated equivalently\n    to a connection error, e.g. when there is no Security object instance that\n    would match a given Server object instance.\n\n  Note that all of the operations mentioned above (domain name resolution and\n  both TCP and (D)TLS handshakes) are performed synchronously and will block all\n  other operations.\n\n  If any of the above conditions happen, Anjay will, by default, fall back to\n  Client-Initiated Bootstrap [#bs]_ or, if the attempt was to connect to\n  a Bootstrap Server, cease any attempts to communicate with it (note that\n  unless regular Server accounts are available, this will mean abortion of all\n  communication [#a]_).\n\n  This behavior can be changed by enabling the\n  `connection_error_is_registration_failure\n  <../api/structanjay__configuration.html#adcc95609ca645a5bd6a572f4c99a83fb>`_.\n  In that case, connection errors will trigger :ref:`err-abort-reg`, and thus\n  the automatic retry flow described in \"Bootstrap and LwM2M Server Registration\n  Mechanisms\" section mentioned above will be respected.\n\n* Errors while receiving an incoming request, or any unrecognized incoming\n  packets, will be ignored\n\n* Errors during `anjay_download()\n  <../api/download_8h.html#a7a4d736c0a4ada68f0770e5eb45a84ce>`_ data transfers\n  will be passed to the appropriate callback handler, see the `documentation to\n  anjay_download_finished_handler_t\n  <../api/download_8h.html#a44f0d37ec9ef8123bf88aa9ea9ee7291>`_ for details.\n  CoAP downloads support automatic resumption of downloads after network errors,\n  see the :ref:`how-can-we-ensure-higher-success-rate` for details.\n\n.. rubric:: Footnotes\n\n.. [#t]  Retransmissions, as specified in\n         `RFC 7252, Section 4.2.  Messages Transmitted Reliably\n         <https://tools.ietf.org/html/rfc7252#section-4.2>`_, are attempted\n         before performing the actions described above. The `transmission\n         parameters <https://tools.ietf.org/html/rfc7252#section-4.8>`_ that\n         affect specific retransmission timing can be customized during Anjay\n         initialization, by setting the `udp_tx_params\n         <../api/structanjay__configuration.html#a9690621b087639e06dd0c747206d0679>`_\n         and `sms_tx_params\n         <../api/structanjay__configuration.html#ab656e5dad737416e5b66272f917df108>`_\n         (in versions that include the SMS feature) fields in\n         `anjay_configuration_t <../api/structanjay__configuration.html>`_.\n\n.. [#hs] To prevent infinite loop of handshakes, DTLS handshake is only retried\n         if the failed operation was **not** performed immediately after the\n         previous handshake; otherwise the behavior described in \"Timeout\n         (NoSec)\" is used.\n\n.. [#a]  Communication with all servers will be aborted and\n         `anjay_all_connections_failed()\n         <../api/core_8h.html#a4329b620520c565fd61b526ba760e59f>`_ will start\n         returning ``true``. Operation can be restored by calling\n         `anjay_transport_schedule_reconnect()\n         <../api/core_8h.html#ad895be5694083d015ffcd8d0b87d0b2a>`_ or\n         `anjay_enable_server()\n         <../api/core_8h.html#abc4b554e51a56da874238f3e64bff074>`_.\n\n.. [#bs] Client-Initiated Bootstrap will be performed only if all the following\n         preconditions are met:\n\n         - a Bootstrap Server Account exists\n         - no other LwM2M Server has usable connection\n         - the Bootstrap Server Account has not aborted due to previous errors\n\n         Otherwise, further communication with the server with which the\n         operation failed will be aborted. This may cause\n         `anjay_all_connections_failed()\n         <../api/core_8h.html#a4329b620520c565fd61b526ba760e59f>`_ to start\n         returning ``true`` if that was the last operational connection.\n         Connection can be retried by calling `anjay_enable_server()\n         <../api/core_8h.html#abc4b554e51a56da874238f3e64bff074>`_ or\n         `anjay_transport_schedule_reconnect()\n         <../api/core_8h.html#ad895be5694083d015ffcd8d0b87d0b2a>`_.\n"
  },
  {
    "path": "doc/sphinx/source/AdvancedTopics/AT-OtherFeatures.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nOther library features\n======================\n\n.. _coap-pull-download:\n\nCoAP PULL download\n------------------\n\nIf the LwM2M Client needs to download a large file from an external CoAP server,\nit may use the `anjay_download API <../api/download_8h.html>`_. The built-in\ndownloader supports CoAP, CoAP/DTLS and HTTP(S) connections and is able to\nperform transfers without interrupting regular LwM2M operations.\n\nFor a simple example, see `examples/tutorial/AT-Downloader` subdirectory of main\nAnjay project repository.\n"
  },
  {
    "path": "doc/sphinx/source/AdvancedTopics/AT-Persistence.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nPersistence support\n===================\n\n.. highlight:: c\n\nAnjay's persistence in general\n------------------------------\n\nAnjay supports persistence of data to ``avs_commons`` generic `stream`\n(``avs_stream_t``). Underlying implementation of a stream may\nabstract any kind of storage access. ``avs_commons`` itself provides:\n\n- file streams,\n- network streams,\n- memory buffered streams.\n\nAny of them can be used with Anjay's persistence API. Additionally, one\ncan easily adapt other types of storages to be used by persistence API,\nby implementing their own stream.\n\nPersistence module is designed to abstract all endianness related issues,\nwhich makes persisted chunk of information on arbitrary architecture\n(having arbitrary endianness) easily restorable on any other architecture.\n\nPersistence of pre-implemented objects/modules\n----------------------------------------------\n\nAnjay's pre-implemented objects (Security, Server, Access Control) and Attribute\nstorage module all support persistence, for which the following functions can be\nused:\n\n- Security Object:\n\n   * ``anjay_security_object_persist()``\n   * ``anjay_security_object_restore()``\n\n- Server Object:\n\n   * ``anjay_server_object_persist()``\n   * ``anjay_server_object_restore()``\n\n- Access Control Object:\n\n   * ``anjay_access_control_persist()``\n   * ``anjay_access_control_restore()``\n\n- Attribute storage:\n\n   * ``anjay_attr_storage_persist()``\n   * ``anjay_attr_storage_restore()``\n\n.. note::\n    All of the mentioned objects have complicated semantics, which is why you\n    should refer to their `documentation <../api/index.html>`_ for more details\n    on how persistence functions behave under different conditions.\n\nExample\n-------\n\nAs an example we'll modify the code from the\n:doc:`../BasicClient/BC-ObjectImplementation` tutorial. We would like to persist\nObject data when the LwM2M Client finishes its work and restore it on startup\n(if a valid persistence file exists).\n\n.. snippet-source:: examples/tutorial/AT-Persistence/src/main.c\n\n    #define PERSISTENCE_FILENAME \"at2-persistence.dat\"\n\n    int persist_objects(anjay_t *anjay) {\n        avs_log(tutorial, INFO, \"Persisting objects to %s\", PERSISTENCE_FILENAME);\n\n        avs_stream_t *file_stream =\n                avs_stream_file_create(PERSISTENCE_FILENAME, AVS_STREAM_FILE_WRITE);\n\n        if (!file_stream) {\n            avs_log(tutorial, ERROR, \"Could not open file for writing\");\n            return -1;\n        }\n\n        int result = -1;\n\n        if (avs_is_err(anjay_security_object_persist(anjay, file_stream))) {\n            avs_log(tutorial, ERROR, \"Could not persist Security Object\");\n            goto finish;\n        }\n\n        if (avs_is_err(anjay_server_object_persist(anjay, file_stream))) {\n            avs_log(tutorial, ERROR, \"Could not persist Server Object\");\n            goto finish;\n        }\n\n        if (avs_is_err(anjay_attr_storage_persist(anjay, file_stream))) {\n            avs_log(tutorial, ERROR, \"Could not persist LwM2M attribute storage\");\n            goto finish;\n        }\n\n        result = 0;\n    finish:\n        avs_stream_cleanup(&file_stream);\n        return result;\n    }\n\n.. snippet-source:: examples/tutorial/AT-Persistence/src/main.c\n\n    int restore_objects_if_possible(anjay_t *anjay) {\n        avs_log(tutorial, INFO, \"Attempting to restore objects from persistence\");\n        int result;\n\n        errno = 0;\n        if ((result = access(PERSISTENCE_FILENAME, F_OK))) {\n            switch (errno) {\n            case ENOENT:\n            case ENOTDIR:\n                // no persistence file means there is nothing to restore\n                return 1;\n            default:\n                // some other unpredicted error\n                return result;\n            }\n        } else if ((result = access(PERSISTENCE_FILENAME, R_OK))) {\n            // most likely file is just not readable\n            return result;\n        }\n\n        avs_stream_t *file_stream =\n                avs_stream_file_create(PERSISTENCE_FILENAME, AVS_STREAM_FILE_READ);\n\n        if (!file_stream) {\n            return -1;\n        }\n\n        result = -1;\n\n        if (avs_is_err(anjay_security_object_restore(anjay, file_stream))) {\n            avs_log(tutorial, ERROR, \"Could not restore Security Object\");\n            goto finish;\n        }\n\n        if (avs_is_err(anjay_server_object_restore(anjay, file_stream))) {\n            avs_log(tutorial, ERROR, \"Could not restore Server Object\");\n            goto finish;\n        }\n\n        if (avs_is_err(anjay_attr_storage_restore(anjay, file_stream))) {\n            avs_log(tutorial, ERROR, \"Could not restore LwM2M attribute storage\");\n            goto finish;\n        }\n\n        result = 0;\n    finish:\n        avs_stream_cleanup(&file_stream);\n        return result;\n    }\n\n.. note::\n    Persisting as well as restoring functions MUST be both called in the same\n    order because objects' data is being stored sequentially.\n\nPersistence API\n---------------\n\nPlease refer to the `documentation of the avs_persistence component\n<https://github.com/AVSystem/avs_commons/blob/master/include_public/avsystem/commons/avs_persistence.h>`_.\n"
  },
  {
    "path": "doc/sphinx/source/AdvancedTopics/AT-RetransmissionsTimeoutsCaching.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nRetransmissions, timeouts & response caching\n============================================\n\nDue to potential network instability, the need to retransmit a message\nbetween a Client and a Server may sometimes occur. Detecting retransmissions\nis especially important if the operation has some observable side effects.\n\nMotivational examples\n---------------------\n\nImagine a LwM2M Object with a numeric, executable Resource, whose value is\nincremented every time a LwM2M Server performs Execute on it. Now, consider\nthe following scenario:\n\n- the LwM2M Server performs an Execute on this specific Resource,\n- the LwM2M Client receives the request, bumps the value, and sends a response,\n- the response is lost due to unfortunate network conditions,\n- the LwM2M Server attempts to increment the Resource again (sending exactly\n  the same Execute request as before),\n- the LwM2M Client receives the request.\n\nLwM2M Client obtained both requests, and if it was unable to classify the\nsecond one as a retransmission of the first, the resource would be incremented\ntwice, even though it would make much more sense to increment it just once.\nOn the other hand, caching the response, and detecting a retransmission,\nwould improve Client-Server communication integrity by preventing this\nfrom happening.\n\nAnother scenario could be that the response is computationally expensive\n(and time-consuming) to generate. In this case caching mechanism would\nyield measurable performance benefits.\n\nCaching mechanism\n-----------------\n\nAnjay provides a built-in message cache - when the request is received, Anjay\nchecks if there exists an appropriate response to it in the cache already. In\ncase there is one, it is retransmitted. Otherwise Anjay processes the request as\nusual, in the end placing response in the cache for future use.\n\n.. note::\n    Cached response, matching a specific CoAP Request is identified by the\n    following triplet:\n\n     - CoAP Message Token,\n     - CoAP Message ID,\n     - Server endpoint name (host and port).\n\nEvery response in the cache sits there for at most ``MAX_TRANSMIT_SPAN``\nas defined in `RFC7252 <https://tools.ietf.org/html/rfc7252>`_, in\n`Section 4.8.2.  Time Values Derived from Transmission Parameters\n<https://tools.ietf.org/html/rfc7252#section-4.8.2>`_, and after that time\nit is automatically removed.\n\nCache size\n----------\n\nThe size of the cache is specified at Anjay instantiation time by setting\n``anjay_configuration_t::msg_cache_size`` to a non-zero value (zero disables\nany caching). This limits the number of bytes used to store cached responses.\n\n.. note::\n    The cache size limit is global for all Servers - i.e. all responses,\n    to all Servers are stored within a single cache.\n\nLimitations\n-----------\n\n- If a response is too big to fit into the cache, it is **not cached**,\n- If a response would fit into the cache, but the cache is currently full,\n  responses (starting from the oldest) are **dropped** from the cache (even if\n  they are still considered valid in terms of mentioned ``MAX_TRANSMIT_SPAN``),\n  till the new response fits.\n\n\n.. _coap-retransmission-parameters:\n\nConfiguring retransmissions and timeouts\n----------------------------------------\n\nBackground\n~~~~~~~~~~\n\nTo provide custom retransmission policy, affecting CoAP layer across\nthe library, one needs to set ``anjay_configuration_t::udp_tx_params``\naccordingly prior library instantiation with ``anjay_new()``.\n\n``anjay_configuration_t::udp_tx_params`` is a ``avs_coap_udp_tx_params_t``\nstructure, defined as follows:\n\n.. code-block:: c\n\n    /** CoAP transmission params object. */\n    typedef struct {\n        /** RFC 7252: ACK_TIMEOUT */\n        avs_time_duration_t ack_timeout;\n        /** RFC 7252: ACK_RANDOM_FACTOR */\n        double ack_random_factor;\n        /** RFC 7252: MAX_RETRANSMIT */\n        unsigned max_retransmit;\n        /** RFC 7252: NSTART */\n        size_t nstart;\n    } avs_coap_udp_tx_params_t;\n\n\nIt should be noted that without any additional configuration,\nAnjay uses default values as specified in the `Section 4.8 of RFC7252\n<https://tools.ietf.org/html/rfc7252#section-4.8>`_:\n\n\n+-----------------------+---------------+-----------------------------------------------------+\n| Parameter             | Default value | Corresponding field in ``avs_coap_udp_tx_params_t`` |\n+=======================+===============+=====================================================+\n| ``ACK_TIMEOUT``       | 2 seconds     | ``ack_timeout``                                     |\n+-----------------------+---------------+-----------------------------------------------------+\n| ``ACK_RANDOM_FACTOR`` | 1.5           | ``ack_random_factor``                               |\n+-----------------------+---------------+-----------------------------------------------------+\n| ``MAX_RETRANSMIT``    | 4             | ``max_retransmit``                                  |\n+-----------------------+---------------+-----------------------------------------------------+\n| ``NSTART``            | 1             | ``nstart``                                          |\n+-----------------------+---------------+-----------------------------------------------------+\n\n\nMeaning of each parameter, calculations of timeouts and the number of retransmissions\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n``ACK_RANDOM_FACTOR``\n^^^^^^^^^^^^^^^^^^^^^\n\nConfigures the amount of random perturbation to a timeout to a response to\nan initial message (``ACK_TIMEOUT``, see next subsection). Its value has to\nbe at least ``1.0``. The randomness is mixed in as follows:\n\n   * generate a random number ``r`` from a closed range ``[1.0, ACK_RANDOM_FACTOR]``,\n   * multiply the ``ACK_TIMEOUT`` by ``r`` and use it as initial timeout.\n\n.. admonition:: Example\n   :class: hint\n\n   Say the library has ``ACK_TIMEOUT`` set to `16s`.\n\n   Now, if the ``ACK_RANDOM_FACTOR`` is ``1.0``, no random behavior is\n   introduced, because the library is forced to pick a random number from\n   a trivial interval ``[1.0, 1.0]``.\n\n   However, if the ``ACK_RANDOM_FACTOR`` is, say, ``1.5``, the number picked\n   may lie in range ``[1.0, 1.5]``, thus the actual time the library would wait\n   may vary between ``[16, 24]`` seconds.\n\n\n``ACK_TIMEOUT``\n^^^^^^^^^^^^^^^\n\nConfigures the amount of time the library shall wait for the response to the\ninitial confirmable message (not retransmission).\n\n.. admonition:: Example\n   :class: hint\n\n   Say the library wants to send a confirmable message.\n\n   If ``ACK_TIMEOUT`` is set to, say, `10` seconds, the library sends the\n   message and then waits ``10 * r`` seconds (``r`` is defined as in the\n   above discussion about ``ACK_RANDOM_FACTOR``) for the initial response.\n\n\n``MAX_RETRANSMIT``\n^^^^^^^^^^^^^^^^^^\n\nConfigures the total number of retransmissions the library is allowed to\nperform before giving up on message delivery.\n\n.. admonition:: Example\n   :class: hint\n\n   If ``MAX_RETRANSMIT`` is set to, say, `4`, the library would send `1`\n   initial message + up to `4` retransmissions, accounting for up to `5`\n   messages in total.\n\n   If ``MAX_RETRANSMIT`` is set to `0`, no retransmission would be attempted,\n   and the library would give up if no response arrived after ``ACK_TIMEOUT *\n   r`` seconds.\n\n\n``NSTART``\n^^^^^^^^^^\n\nConfigures the maximum number of exchanges that may be ongoing at the same time\nwith a given remote CoAP endpoint (i.e., a LwM2M Server).\n\nIn Anjay, it is mostly ignored. It is not recommended to set it to any other\nvalue than the default of 1.\n\nHigher values may be useful when writing applications using the low-level CoAP\nAPIs.\n\nExponential back-off\n^^^^^^^^^^^^^^^^^^^^\n\nAfter waiting for a response for ``t`` seconds , the wait time for the next\nretransmission (in the absence of response) would be ``2 * t`` seconds. In\nother words, retransmissions are performed with exponential back-off.\n\nExample configuration\n~~~~~~~~~~~~~~~~~~~~~\n\nAs an example, we may configure the library as follows:\n\n.. code-block:: c\n\n   avs_coap_udp_tx_params_t udp_tx_params = {\n      // Wait at least 4 seconds for the initial response.\n      .ack_timeout = avs_time_duration_from_scalar(4, AVS_TIME_S),\n      // Do not randomize wait times for simplicity of the discussion,\n      // thus \"at least\" in the comment above should be thought of as\n      // \"exactly\".\n      .ack_random_factor = 1.0,\n      // Allow up to 4 retransmissions.\n      .max_retransmit = 4,\n      // leave the NSTART parameter at the default value of 1\n      .nstart = 1\n   };\n\n   anjay_configuration_t configuration = {\n      // Some other configuration ...\n      .udp_tx_params = &udp_tx_params\n   };\n\n   // Create Anjay instance with custom transmission parameters\n   anjay_t *anjay = anjay_new(&configuration);\n\n\nThe above configuration would result in the following retransmission times to a confirmable\nmessage:\n\n+----------+--------------+--------------------------------+----------------------------+\n| Time [s] | Retry number | Wait time for the response [s] | Action by the library      |\n+==========+==============+================================+============================+\n| 0        | 0            | 4                              | send initial message       |\n+----------+--------------+--------------------------------+----------------------------+\n| 4        | 1            | 8                              | 1st retransmission         |\n+----------+--------------+--------------------------------+----------------------------+\n| 12       | 2            | 16                             | 2nd retransmission         |\n+----------+--------------+--------------------------------+----------------------------+\n| 28       | 3            | 32                             | 3rd retransmission         |\n+----------+--------------+--------------------------------+----------------------------+\n| 60       | 4            | 64                             | 4th (final) retransmission |\n+----------+--------------+--------------------------------+----------------------------+\n| 124      | --           | --                             | give up                    |\n+----------+--------------+--------------------------------+----------------------------+\n\nOther retransmission parameters\n-------------------------------\n\nWhile setting ``anjay_configuration_t::udp_tx_params`` parameter\ncovers most cases, there are also means to configure:\n\n- DTLS handshake retransmissions\n  (``anjay_configuration_t::udp_dtls_hs_tx_params`` `docs\n  <../api/structanjay__configuration.html#ab8ca076537138e7d78bd1ee5d5e2031a>`__),\n\n- firmware update module retransmissions (by implementing\n  custom ``anjay_fw_update_get_coap_tx_params_t`` handler `docs\n  <../api/fw__update_8h.html#a50900e2aaff21e91df693795965136b2>`__),\n\n- additional fields in ``anjay_configuration_t`` that configure transmission\n  parameters for non-UDP transports.\n\nWe recommend to refer to the doxygen documentation for more details.\n"
  },
  {
    "path": "doc/sphinx/source/AdvancedTopics.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nAdvanced topics\n===============\n\n.. toctree::\n   :glob:\n   :titlesonly:\n\n   AdvancedTopics/AT-Bootstrap\n   AdvancedTopics/AT-AccessControl\n   AdvancedTopics/AT-AttributeStorage\n   AdvancedTopics/AT-Certificates\n   AdvancedTopics/AT-CertificateUsage\n   AdvancedTopics/AT-CustomObjects\n   AdvancedTopics/AT-NetworkErrorHandling\n   AdvancedTopics/AT-OtherFeatures\n   AdvancedTopics/AT-Persistence\n   AdvancedTopics/AT-RetransmissionsTimeoutsCaching\n   AdvancedTopics/AT-CustomEventLoop\n   AdvancedTopics/AT-EventLoopNotes\n   AdvancedTopics/AT-IpsoObjects\n"
  },
  {
    "path": "doc/sphinx/source/BasicClient/BC-Initialization.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nInitialize Anjay client\n=======================\n\nProject structure\n^^^^^^^^^^^^^^^^^\n\nCreate the following directory layout for your client project:\n\n.. code-block:: none\n\n    example/\n      ├── CMakeLists.txt\n      └── src/\n          └── main.c\n\n.. note::\n\n    All code found in this tutorial is available under ``examples/tutorial/BC*``\n    in Anjay source directory. Each tutorial project follows the same project\n    directory layout.\n\nConfigure the build system\n^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nCreate a ``CMakeLists.txt`` file in the root of your project directory with the\nfollowing content:\n\n.. highlight:: cmake\n.. snippet-source:: examples/tutorial/BC-Initialization/CMakeLists.txt\n\n    cmake_minimum_required(VERSION 3.16)\n    project(anjay-bc-initialization C)\n\n    set(CMAKE_C_STANDARD 99)\n    set(CMAKE_C_EXTENSIONS OFF)\n\n    add_compile_options(-Wall -Wextra)\n\n    find_package(anjay REQUIRED)\n\n    add_executable(${PROJECT_NAME} src/main.c)\n    target_link_libraries(${PROJECT_NAME} PRIVATE anjay)\n\nThis file configures the CMake build system to compile your client and link it\nwith the Anjay library.\n\n.. _anjay-hello-world:\n\nImplement the Hello World client\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nCreate a ``main.c`` file in the ``src/`` directory with the following content:\n\n.. highlight:: c\n.. snippet-source:: examples/tutorial/BC-Initialization/src/main.c\n\n    #include <anjay/anjay.h>\n    #include <avsystem/commons/avs_log.h>\n\n    int main(int argc, char *argv[]) {\n        if (argc != 2) {\n            avs_log(tutorial, ERROR, \"usage: %s ENDPOINT_NAME\", argv[0]);\n            return -1;\n        }\n\n        const anjay_configuration_t CONFIG = {\n            .endpoint_name = argv[1],\n            .in_buffer_size = 4000,\n            .out_buffer_size = 4000,\n            .msg_cache_size = 4000\n        };\n\n        anjay_t *anjay = anjay_new(&CONFIG);\n        if (!anjay) {\n            avs_log(tutorial, ERROR, \"Could not create Anjay object\");\n            return -1;\n        }\n\n        anjay_event_loop_run(anjay, avs_time_duration_from_scalar(1, AVS_TIME_S));\n\n        anjay_delete(anjay);\n        return 0;\n    }\n\n.. note::\n\n    Complete code of this example can be found in\n    `examples/tutorial/BC-Initialization` subdirectory of main Anjay project\n    repository.\n\n**Code explanation:**\n\n.. note::\n\n    We recommend you to look at the doxygen generated\n    `API documentation <../api/>`_ if something isn't immediately\n    clear to you.\n\nFirst, we call the `anjay_new()\n<../api/core_8h.html#a9d95a5005ff7c3b1d76573616c57d4cc>`_ function that\ninitializes the client. It needs to be passed an `anjay_configuration_t\n<../api/structanjay__configuration.html>`_ structure that contains basic runtime\nconfiguration of the client.\n\nThe example code above configures the basic values that are most essential:\n\n* `endpoint_name\n  <../api/structanjay__configuration.html#aafab5578aa377788d6208d5ea6dc2da9>`_\n  sets the Endpoint Client Name - see :ref:`clients-and-servers`.\n* `in_buffer_size\n  <../api/structanjay__configuration.html#a0be70dc47a294104527cac8e84786f02>`_\n  and `out_buffer_size\n  <../api/structanjay__configuration.html#a44513f6007ea6db2c75a517dbfa77df4>`_\n  control sizes of the buffers used for network communication.\n* `msg_cache_size\n  <../api/structanjay__configuration.html#a3bb16de58b283370b1ab20698dd4849a>`_\n  sets the size of the message cache - this is not strictly necessary for the\n  client to work, but it is used to internally cache responses so that\n  retransmitted packets are properly handled as duplicates. The bigger this\n  buffer, the older packets the library will be able to detect as\n  retransmissions.\n\nAfter initializing the library, `anjay_event_loop_run()\n<../api/core_8h.html#a95c229caf3ee8ce7de556256f4307507>`_ is called. This\nfunction doesn't return unless there is a fatal error, instead acting as the\nmain loop of the LwM2M client.\n\nIn more complicated applications, this function would typically be run in a\ndedicated thread, while other threads would perform tasks not directly related\nto LwM2M and communicate with the LwM2M thread when necessary.\n\n.. important::\n\n    If you intend to run Anjay event loop in a dedicated thread, please make\n    sure that the code is properly synchronized. The ``WITH_THREAD_SAFETY`` and\n    ``WITH_SCHEDULER_THREAD_SAFE`` compile-time configuration options may be\n    helpful in achieving this goal.\n\nThe second argument specifies the maximum time for which the loop is allowed to\nwait for incoming events in a single iteration - 1 second in this example. The\nshorter the time, the more responsive the loop will be in handling asynchronous\nrequests (e.g. jobs scheduled from another threads), but the average CPU usage\nlevel of the main loop may be higher.\n\nIn case the event loop finishes, `anjay_delete()\n<api/core_8h.html#a243f18f976bca57b5a7b0714bfb99095>`_ - a function that cleans\nup all resources used by the client - is called.\n\nBuild and run the client\n^^^^^^^^^^^^^^^^^^^^^^^^\n\nIn your project directory, build the client:\n\n.. code-block:: sh\n\n    $ cmake . && make\n\nIf the build succeeds, you can run the client. Provide an endpoint name as an argument.\nWhen the communication with the server takes place later in the project this will\nbe a name that the client uses to identify itself to the server. Please look into\nthe :ref:`brief description of LwM2M <clients-and-servers>` for details on\nrecommended formats of the endpoint name.\n\nAt this point, you can use the local hostname to simulate an endpoint name:\n\n.. code-block:: sh\n\n    $ ./anjay-bc-initialization urn:dev:os:$(hostname)\n\n.. important::\n\n   Project will not be configured successfully until you install Anjay library,\n   see :doc:`/Compiling_client_applications` for details how to do it.\n\nYou will see only some logs and the application will appear to freeze - that's\nbecause without any server configuration, there are no tasks to do. However, if\nyou do not see \"Could not create Anjay object\" there, then Anjay was properly\ninitialized.\n\nThe connection to the LwM2M Server will be described in the next steps.\n"
  },
  {
    "path": "doc/sphinx/source/BasicClient/BC-MandatoryObjects.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nInstalling mandatory Objects\n============================\n\nTo connect to a LwM2M server and handle incoming packets, the client must support the following mandatory LwM2M Objects:\n  \n  - `LwM2M Security <https://www.openmobilealliance.org/tech/profiles/LWM2M_Security-v1_0.xml>`_ (``/0``)\n  - `LwM2M Server <https://www.openmobilealliance.org/tech/profiles/LWM2M_Server-v1_0.xml>`_ (``/1``)\n\nAnjay provides pre-implemented modules for all two objects, making setup\nstraightforward.\n\n.. note::\n    Users can still provide their own implementation of these Objects if needed\n    — Anjay remains fully flexible.\n\nWhen Anjay is first instantiated (as in our previous :ref:`hello world\n<anjay-hello-world>` example), it has no knowledge about the Data Model, i.e.,\nno LwM2M Objects are registered within it. You must explicitly install the\nrequired Objects, as shown below.\n\nInstalling Objects\n^^^^^^^^^^^^^^^^^^\n\nEach LwM2M Object is defined by an instance of the ``anjay_dm_object_def_t``\nstructure. To add support for a new Object, you'd need to:\n\n  - fill the ``anjay_dm_object_def_t`` structure,\n  - implement appropriate callback functions,\n  - register created object in Anjay.\n\nHowever, for now, we are going to install our pre-implemented LwM2M Objects\n(Security, Server), so that you don't have to worry about initializing the\nstructure and object registration on your own. In case you are interested in\nthis topic, :doc:`BC-ObjectImplementation` section provides more information on\nthis subject.\n\nUse the following functions to install the Objects:\n\n  - ``anjay_security_object_install()``\n  - ``anjay_server_object_install()``\n\n.. important::\n\n    To use these function you must include the following headers:\n\n      - ``anjay/security.h``\n      - ``anjay/server.h``\n\nSetting up Server and Security Objects\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nThis section shows how to implement and register the mandatory Objects:\nSecurity and Server. It builds upon the setup from the\n:ref:`previous tutorial <anjay-hello-world>`.\n\nSecurity Object\n---------------\n\nThe Security Object holds connection parameters for the LwM2M server. In this\nexample, we configure a non-secure connection to the Coiote IoT Device\nManagement platform. A secure connection setup will be described in a later\nsection.\n\nTo use Coiote:\n\n  - Create an account at avsystem.com/coiote-iot-device-management-platform.\n  - Add your device entry in the Coiote interface using the following URI for\n    the connection: ``coap://eu.iot.avsystem.cloud:5683``\n\nIf you are using another server, replace the URI with your target address.\n\n.. highlight:: c\n.. snippet-source:: examples/tutorial/BC-MandatoryObjects/src/main.c\n\n    // Installs Security Object and adds an instance of it.\n    // An instance of Security Object provides information needed to connect to\n    // LwM2M server.\n    static int setup_security_object(anjay_t *anjay) {\n        if (anjay_security_object_install(anjay)) {\n            return -1;\n        }\n\n        const anjay_security_instance_t security_instance = {\n            .ssid = 1,\n            .server_uri = \"coap://eu.iot.avsystem.cloud:5683\",\n            .security_mode = ANJAY_SECURITY_NOSEC\n        };\n\n        // Anjay will assign Instance ID automatically\n        anjay_iid_t security_instance_id = ANJAY_ID_INVALID;\n        if (anjay_security_object_add_instance(anjay, &security_instance,\n                                               &security_instance_id)) {\n            return -1;\n        }\n\n        return 0;\n    }\n\nServer Object\n-------------\n\nThe Server Object defines registration parameters like lifetime and binding\nmode.\n\n.. highlight:: c\n.. snippet-source:: examples/tutorial/BC-MandatoryObjects/src/main.c\n\n    // Installs Server Object and adds an instance of it.\n    // An instance of Server Object provides the data related to a LwM2M Server.\n    static int setup_server_object(anjay_t *anjay) {\n        if (anjay_server_object_install(anjay)) {\n            return -1;\n        }\n\n        const anjay_server_instance_t server_instance = {\n            // Server Short ID\n            .ssid = 1,\n            // Client will send Update message often than every 60 seconds\n            .lifetime = 60,\n            // Disable Default Minimum Period resource\n            .default_min_period = -1,\n            // Disable Default Maximum Period resource\n            .default_max_period = -1,\n            // Disable Disable Timeout resource\n            .disable_timeout = -1,\n            // Sets preferred transport to UDP\n            .binding = \"U\"\n        };\n\n        // Anjay will assign Instance ID automatically\n        anjay_iid_t server_instance_id = ANJAY_ID_INVALID;\n        if (anjay_server_object_add_instance(anjay, &server_instance,\n                                             &server_instance_id)) {\n            return -1;\n        }\n\n        return 0;\n    }\n\nBoth Security and Server instances are linked together by the Short Server ID\nResource (``ssid``). That is why the ssid value must match between the Security\nand Server instances.\n\nIntegrate Object Installation\n-----------------------------\n\nOnce the installation functions are implemented, call them from your ``main()``\nfunction:\n\n.. highlight:: c\n.. snippet-source:: examples/tutorial/BC-MandatoryObjects/src/main.c\n    :emphasize-lines: 21-24\n\n    int main(int argc, char *argv[]) {\n        if (argc != 2) {\n            avs_log(tutorial, ERROR, \"usage: %s ENDPOINT_NAME\", argv[0]);\n            return -1;\n        }\n\n        const anjay_configuration_t CONFIG = {\n            .endpoint_name = argv[1],\n            .in_buffer_size = 4000,\n            .out_buffer_size = 4000,\n            .msg_cache_size = 4000\n        };\n\n        anjay_t *anjay = anjay_new(&CONFIG);\n        if (!anjay) {\n            avs_log(tutorial, ERROR, \"Could not create Anjay object\");\n            return -1;\n        }\n\n        int result = 0;\n        // Setup necessary objects\n        if (setup_security_object(anjay) || setup_server_object(anjay)) {\n            result = -1;\n        }\n\n        if (!result) {\n            result = anjay_event_loop_run(\n                    anjay, avs_time_duration_from_scalar(1, AVS_TIME_S));\n        }\n\n        anjay_delete(anjay);\n        return result;\n    }\n\n.. note::\n\n    ``anjay_delete()`` will automatically delete installed modules after\n    destruction of Anjay instance.\n\n.. note::\n\n    Complete code of this example can be found in\n    `examples/tutorial/BC-MandatoryObjects` subdirectory of main Anjay project\n    repository.\n\nLogs example\n~~~~~~~~~~~~\n\nAfter running the client, you should see ``registration successful, location =\n/rd/<server-dependent identifier>`` once and ``registration successfully\nupdated`` every 30 seconds in logs. It means, that the client has connected to\nthe server and successfully sends Update messages. You can now perform\noperations like Read from the server side.\n\nApplication events\n^^^^^^^^^^^^^^^^^^\n\nThe example code shown above covers events managed internally by the Anjay\nlibrary. However, most real-world applications also need to handle their own\nlogic. How to implement application-specific functionality will be explained\nin the following sections.\n\nCoiote experience\n^^^^^^^^^^^^^^^^^\n\nAt this stage, you can log in to Coiote IoT Device Management and open the\n**Device Center** for your registered device to explore the platform\nfunctionality. Check the **Data Model tab** to see which LwM2M Objects are\ncurrently exposed. You will notice that the Server object is visible, but the\nSecurity object is not. This is expected behavior defined by the LwM2M\nspecification — the Security object is neither readable nor discoverable from\nthe device to protect sensitive configuration data.\n\n"
  },
  {
    "path": "doc/sphinx/source/BasicClient/BC-Notifications.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nNotifications support\n=====================\n\nSome Resources may represent values that change over time, like sensor readings.\nA LwM2M Server may be interested in variance of such values and use the Observe\noperation to request notifications when they change, or when their value meets\ncertain criteria.\n\n.. seealso::\n    More information about available notification criteria can be found under\n    **<NOTIFICATION>** Class Attributes description in :ref:`lwm2m-attributes`.\n\nWhen some part of the data model changes by means other than LwM2M, one has to\ntell the library about it by calling an appropriate function:\n\n- if a Resource value changed - ``anjay_notify_changed()``,\n- if one or more Object Instances were created or removed -\n  ``anjay_notify_instances_changed()``.\n\nAnjay then decides if the notification shall be sent, based on the currently\nassigned Attributes (to the part of the data model being changed) and LwM2M\nServers that are interested in seeing the change.\n\n.. note::\n    One should not call ``anjay_notify_changed()``/``anjay_notify_instances_changed()``\n    when the value change was directly caused by LwM2M (e.g. by Write or Create\n    request). Anjay handles these cases internally.\n\n.. seealso::\n    Detailed description of these functions can be found in `API docs\n    <../../api>`_.\n\nCalling ``anjay_notify_changed()``/``anjay_notify_instances_changed()`` does not\nsend notifications immediately, but schedules a task to be run on next event\nloop iteration. That way, notifications for multiple values can be handled as a\nbatch, for example in case where the server observes an entire Object Instance.\n\nLwM2M attributes\n----------------\n\nCorrect handling of LwM2M Observe requests requires being able to store\nObject/Instance/Resource attributes. For that, one needs to either implement\na set of attribute handlers, or use the pre-defined\n:doc:`Attribute Storage subsystem <../AdvancedTopics/AT-AttributeStorage>`. In\nthis tutorial, we use pre-defined Attribute Storage subsystem here.\n\nExample\n-------\n\nAs an example we'll add notification support for the Time Object implemented\nin previous :doc:`BC-ObjectImplementation` section. It contains a Current Time\nResource, whose value changes every second. We need to periodically notify the\nlibrary about that fact and for this purpose, we create a\n``time_object_notify()`` function in the ``time_object.c`` file, but first, we\nneed to modify ``time_instance_t`` a little. This will allow to store the last\ntimestamp when the ``anjay_notify_changed()`` was successfully called, to avoid\ncalling it twice during one second. Although it will not result in sending two\nnotifications with the same Resource value, because Anjay checks it internally,\nit will lead to performing some unnecessary actions (like calling read handler\nfor example).\n\n.. highlight:: c\n.. snippet-source:: examples/tutorial/BC-Notifications/src/time_object.c\n    :emphasize-lines: 5\n\n    typedef struct time_instance_struct {\n        anjay_iid_t iid;\n        char application_type[64];\n        char application_type_backup[64];\n        int64_t last_notify_timestamp;\n    } time_instance_t;\n\n.. highlight:: c\n.. snippet-source:: examples/tutorial/BC-Notifications/src/time_object.c\n\n    void time_object_notify(anjay_t *anjay, const anjay_dm_object_def_t **def) {\n        if (!anjay || !def) {\n            return;\n        }\n        time_object_t *obj = get_obj(def);\n\n        int64_t current_timestamp;\n        if (avs_time_real_to_scalar(&current_timestamp, AVS_TIME_S,\n                                    avs_time_real_now())) {\n            return;\n        }\n\n        AVS_LIST(time_instance_t) it;\n        AVS_LIST_FOREACH(it, obj->instances) {\n            if (it->last_notify_timestamp != current_timestamp) {\n                if (!anjay_notify_changed(anjay, 3333, it->iid, RID_CURRENT_TIME)) {\n                    it->last_notify_timestamp = current_timestamp;\n                }\n            }\n        }\n    }\n\nAt last, we need to declare the function in the object's header file.\n\n.. highlight:: c\n.. snippet-source:: examples/tutorial/BC-Notifications/src/time_object.h\n    :caption: time_object.h\n    :emphasize-lines: 8\n\n    #ifndef TIME_OBJECT_H\n    #define TIME_OBJECT_H\n\n    #include <anjay/dm.h>\n\n    const anjay_dm_object_def_t **time_object_create(void);\n    void time_object_release(const anjay_dm_object_def_t **def);\n    void time_object_notify(anjay_t *anjay, const anjay_dm_object_def_t **def);\n\n    #endif // TIME_OBJECT_H\n\nNow we need to somehow call this function while the Anjay main loop is running.\nThis may be performed in several ways - additional tasks may be handled in a\nseparate thread, or a :doc:`../AdvancedTopics/AT-CustomEventLoop` may be\nimplemented instead of using ``anjay_event_loop_run()``. However, the simplest\nsolution is to utilize Anjay's internal scheduler.\n\nBefore calling ``anjay_event_loop_run()``, our application extracts the\nscheduler object by calling `anjay_get_scheduler()\n<../api/core_8h.html#abb564689d6abd23010b5782bf4967819>`_ and schedules a\nspecially crafted ``notify_job()`` function to run, using `AVS_SCHED_DELAYED()\n<https://github.com/AVSystem/avs_commons/blob/2998769a4314f9b609951218dec85cb53b019775/include_public/avsystem/commons/avs_sched.h#L322>`_.\nTo run the function periodically, this call to ``AVS_SCHED_DELAYED()`` is in\nfact called at the end of ``notify_job()``, and ``notify_job()`` itself is\ncalled from the main function to schedule the first run for simplicity.\n\n.. highlight:: c\n.. snippet-source:: examples/tutorial/BC-Notifications/src/main.c\n    :caption: main.c\n    :emphasize-lines: 8-22,123-128\n\n    #include <anjay/anjay.h>\n    #include <anjay/security.h>\n    #include <anjay/server.h>\n    #include <avsystem/commons/avs_log.h>\n\n    #include \"time_object.h\"\n\n    typedef struct {\n        anjay_t *anjay;\n        const anjay_dm_object_def_t **time_object;\n    } notify_job_args_t;\n\n    // Periodically notifies the library about Resource value changes\n    static void notify_job(avs_sched_t *sched, const void *args_ptr) {\n        const notify_job_args_t *args = (const notify_job_args_t *) args_ptr;\n\n        time_object_notify(args->anjay, args->time_object);\n\n        // Schedule run of the same function after 1 second\n        AVS_SCHED_DELAYED(sched, NULL, avs_time_duration_from_scalar(1, AVS_TIME_S),\n                          notify_job, args, sizeof(*args));\n    }\n\n    // Installs Security Object and adds an instance of it.\n    // An instance of Security Object provides information needed to connect to\n    // LwM2M server.\n    static int setup_security_object(anjay_t *anjay) {\n        if (anjay_security_object_install(anjay)) {\n            return -1;\n        }\n\n        static const char PSK_IDENTITY[] = \"identity\";\n        static const char PSK_KEY[] = \"P4s$w0rd\";\n\n        anjay_security_instance_t security_instance = {\n            .ssid = 1,\n            .server_uri = \"coaps://eu.iot.avsystem.cloud:5684\",\n            .security_mode = ANJAY_SECURITY_PSK,\n            .public_cert_or_psk_identity = (const uint8_t *) PSK_IDENTITY,\n            .public_cert_or_psk_identity_size = strlen(PSK_IDENTITY),\n            .private_cert_or_psk_key = (const uint8_t *) PSK_KEY,\n            .private_cert_or_psk_key_size = strlen(PSK_KEY)\n        };\n\n        // Anjay will assign Instance ID automatically\n        anjay_iid_t security_instance_id = ANJAY_ID_INVALID;\n        if (anjay_security_object_add_instance(anjay, &security_instance,\n                                               &security_instance_id)) {\n            return -1;\n        }\n\n        return 0;\n    }\n\n    // Installs Server Object and adds an instance of it.\n    // An instance of Server Object provides the data related to a LwM2M Server.\n    static int setup_server_object(anjay_t *anjay) {\n        if (anjay_server_object_install(anjay)) {\n            return -1;\n        }\n\n        const anjay_server_instance_t server_instance = {\n            // Server Short ID\n            .ssid = 1,\n            // Client will send Update message often than every 60 seconds\n            .lifetime = 60,\n            // Disable Default Minimum Period resource\n            .default_min_period = -1,\n            // Disable Default Maximum Period resource\n            .default_max_period = -1,\n            // Disable Disable Timeout resource\n            .disable_timeout = -1,\n            // Sets preferred transport to UDP\n            .binding = \"U\"\n        };\n\n        // Anjay will assign Instance ID automatically\n        anjay_iid_t server_instance_id = ANJAY_ID_INVALID;\n        if (anjay_server_object_add_instance(anjay, &server_instance,\n                                             &server_instance_id)) {\n            return -1;\n        }\n\n        return 0;\n    }\n\n    int main(int argc, char *argv[]) {\n        if (argc != 2) {\n            avs_log(tutorial, ERROR, \"usage: %s ENDPOINT_NAME\", argv[0]);\n            return -1;\n        }\n\n        const anjay_configuration_t CONFIG = {\n            .endpoint_name = argv[1],\n            .in_buffer_size = 4000,\n            .out_buffer_size = 4000,\n            .msg_cache_size = 4000\n        };\n\n        anjay_t *anjay = anjay_new(&CONFIG);\n        if (!anjay) {\n            avs_log(tutorial, ERROR, \"Could not create Anjay object\");\n            return -1;\n        }\n\n        int result = 0;\n        // Setup necessary objects\n        if (setup_security_object(anjay) || setup_server_object(anjay)) {\n            result = -1;\n        }\n\n        const anjay_dm_object_def_t **time_object = NULL;\n        if (!result) {\n            time_object = time_object_create();\n            if (time_object) {\n                result = anjay_register_object(anjay, time_object);\n            } else {\n                result = -1;\n            }\n        }\n\n        if (!result) {\n            // Run notify_job the first time;\n            // this will schedule periodic calls to itself via the scheduler\n            notify_job(anjay_get_scheduler(anjay), &(const notify_job_args_t) {\n                                                       .anjay = anjay,\n                                                       .time_object = time_object\n                                                   });\n\n            result = anjay_event_loop_run(\n                    anjay, avs_time_duration_from_scalar(1, AVS_TIME_S));\n        }\n\n        anjay_delete(anjay);\n        time_object_release(time_object);\n        return result;\n    }\n\nThat's all you need to make your client support LwM2M Observe/Notify operations!\n\n.. note::\n\n    Complete code of this example can be found in\n    `examples/tutorial/BC-Notifications` subdirectory of main Anjay project\n    repository.\n"
  },
  {
    "path": "doc/sphinx/source/BasicClient/BC-ObjectImplementation.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nImplementing standard Object\n============================\n\nThis section describes implementation of a standard Object defined in\n`OMA LwM2M Registry <https://technical.openmobilealliance.org/OMNA/LwM2M/LwM2MRegistry.html>`_.\nIf you are interested in implementing your own Objects, please jump to\n:doc:`/AdvancedTopics/AT-CustomObjects` section or prepare an Object definition\nin XML on your own.\n\nAs an example, we are going to implement\n`Time Object with ID 3333 in version 1.0 <https://raw.githubusercontent.com/OpenMobileAlliance/lwm2m-registry/prod/version_history/3333-1_0.xml>`_.\nIt is one of the simplest Objects defined in OMA LwM2M Registry, which allows to\nunderstand basics of operations on Objects.\n\n.. note::\n\n   For conformance with the LwM2M specification, the\n   `Device Object <https://www.openmobilealliance.org/tech/profiles/LWM2M_Device-v1_0_3.xml>`_\n   must be implemented. We are implementing Time Object instead for simplicity,\n   because it contains less resources.\n\nThis objects contains definition of three resources. They are presented in the\ntable below with their most important attributes.\n\n+------+------------------+------------+-----------+--------+----------------------------------------------------------------------------------------------------------+\n| ID   | Name             | Operations | Mandatory | Type   | Description                                                                                              |\n+======+==================+============+===========+========+==========================================================================================================+\n| 5506 | Current Time     | RW         | Mandatory | Time   | Unix Time. A signed integer representing the number of seconds since Jan 1st, 1970 in the UTC time zone. |\n+------+------------------+------------+-----------+--------+----------------------------------------------------------------------------------------------------------+\n| 5507 | Fractional Time  | RW         | Optional  | Float  | Fractional part of the time when sub-second precision is used (e.g., 0.23 for 230 ms).                   |\n+------+------------------+------------+-----------+--------+----------------------------------------------------------------------------------------------------------+\n| 5750 | Application Type | RW         | Optional  | String | The application type of the sensor or actuator as a string depending on the use case.                    |\n+------+------------------+------------+-----------+--------+----------------------------------------------------------------------------------------------------------+\n\n* ID - number used to identify the particular Resource. Different Objects may\n  use the same Resource IDs for different purposes.\n\n* Operations - RW indicates, that Resource is Readable and Writable.\n\n* Mandatory - not all Resources defined for standard object must be implemented\n  to be compliant with specification. In this case only the Current Time\n  resource is mandatory.\n\nAlthough Current Time and Fractional Time resources are writable, we will not\nfocus on setting system time and not implement this operation for these two\nresources.\n\nImplementing the Object\n-----------------------\n\n**Generating base source code**\n\nTo generate layout of Object's implementation, we will use the ``anjay_codegen.py``\nscript, which is bundled with Anjay library. Without going into details, which\nare described in :doc:`/Tools` section, we may just call\n``./tools/lwm2m_object_registry.py --get-xml 3333 -v 1.0 | ./tools/anjay_codegen.py -i - -o time_object.c``\nfrom the Anjay root directory. This command downloads Object's definition from\nOMA LwM2M registry and converts it to source code with a lot of TODOs. Now we\nare going to replace them with actual code.\n\n**Keeping Instance and Object state**\n\nThe actual data of a LwM2M Object sits in its Instances and Resources of that\nInstance, so we must have at least one Instance to operate on some real data.\n\nThe state of our Time Object Instance will be placed in ``time_instance_t``\nstruct. The only thing we must keep there is Instance ID (`iid`) and a value of\nApplication Type Resource, because for Current Time Resource we will be using a\nsystem clock source directly, whenever a read handler is called\n\nNote that there is also a second array for keeping backup of Application Type -\nthis will be required for implementation of transactions. We will back to it\nat the end of this tutorial.\n\nThere is also the ``time_object_t`` structure, but we do not need to keep\nanything related to the entire Time Object, so we can leave the default\nimplementation, which just keeps the Object definition and the list of its\ninstances.\n\n.. highlight:: c\n.. snippet-source:: examples/tutorial/BC-ObjectImplementation/src/time_object.c\n    :emphasize-lines: 3-4\n\n    typedef struct time_instance_struct {\n        anjay_iid_t iid;\n        char application_type[64];\n        char application_type_backup[64];\n    } time_instance_t;\n\n    typedef struct time_object_struct {\n        const anjay_dm_object_def_t *def;\n        AVS_LIST(time_instance_t) instances;\n    } time_object_t;\n\n**Initializing, releasing and resetting the instance**\n\nNext we have to implement ``init_instance()`` and ``release_instance()``\nfunctions. These functions are used during creation and deletion of instances,\nperformed by LwM2M Server for example.\n\nIn this case, all we have to do is to initialize Application Type with some\nvalue. Since it is a C-string, it is enough to set the first byte to ``\\0``.\n\n.. highlight:: c\n.. snippet-source:: examples/tutorial/BC-ObjectImplementation/src/time_object.c\n    :emphasize-lines: 5\n\n    static int init_instance(time_instance_t *inst, anjay_iid_t iid) {\n        assert(iid != ANJAY_ID_INVALID);\n\n        inst->iid = iid;\n        inst->application_type[0] = '\\0';\n\n        return 0;\n    }\n\nIf you decide to allocate the memory for the Application Type dynamically\ninstead of using fixed-size buffers, then it should be freed in\n``release_instance()`` function. In this case, ``release_instance()`` may do\nnothing and the default implementation can be left.\n\nThe next function to implement is ``instance_reset()``, which should reset\nthe Instance to its default state, which means the empty Application Type in our\ncase.\n\n.. highlight:: c\n.. snippet-source:: examples/tutorial/BC-ObjectImplementation/src/time_object.c\n    :emphasize-lines: 10\n\n    static int instance_reset(anjay_t *anjay,\n                              const anjay_dm_object_def_t *const *obj_ptr,\n                              anjay_iid_t iid) {\n        (void) anjay;\n\n        time_object_t *obj = get_obj(obj_ptr);\n        time_instance_t *inst = find_instance(obj, iid);\n        assert(inst);\n\n        inst->application_type[0] = '\\0';\n\n        return 0;\n    }\n\nWe can also disable the presence of one of the Resources in the\n``list_resources()`` function. It is done by changing\n``ANJAY_DM_RES_PRESENT`` to ``ANJAY_DM_RES_ABSENT`` in the\n``anjay_dm_emit_res()`` call. This change will simplify implementation of Read\nhandler and Observe/Notifications support in the next section.\n\n.. highlight:: c\n.. snippet-source:: examples/tutorial/BC-ObjectImplementation/src/time_object.c\n    :emphasize-lines: 11-12\n\n    static int list_resources(anjay_t *anjay,\n                              const anjay_dm_object_def_t *const *obj_ptr,\n                              anjay_iid_t iid,\n                              anjay_dm_resource_list_ctx_t *ctx) {\n        (void) anjay;\n        (void) obj_ptr;\n        (void) iid;\n\n        anjay_dm_emit_res(ctx, RID_CURRENT_TIME, ANJAY_DM_RES_RW,\n                          ANJAY_DM_RES_PRESENT);\n        anjay_dm_emit_res(ctx, RID_FRACTIONAL_TIME, ANJAY_DM_RES_RW,\n                          ANJAY_DM_RES_ABSENT);\n        anjay_dm_emit_res(ctx, RID_APPLICATION_TYPE, ANJAY_DM_RES_RW,\n                          ANJAY_DM_RES_PRESENT);\n        return 0;\n    }\n\n.. note::\n\n   Using ``-r`` command line option in ``anjay_codegen.py`` you can generate\n   Object's stub with specified Resources only.\n\n**Read and Write handlers**\n\nNow we are ready to implement ``resource_read()`` and ``resource_write()``\nhandlers. These handlers will be called every time LwM2M Server performs Read\nor Write operation.\n\nWe may use ``avs_time_real_now()`` to get the current time.\n\n.. highlight:: c\n.. snippet-source:: examples/tutorial/BC-ObjectImplementation/src/time_object.c\n    :emphasize-lines: 14-26\n\n    static int resource_read(anjay_t *anjay,\n                             const anjay_dm_object_def_t *const *obj_ptr,\n                             anjay_iid_t iid,\n                             anjay_rid_t rid,\n                             anjay_riid_t riid,\n                             anjay_output_ctx_t *ctx) {\n        (void) anjay;\n\n        time_object_t *obj = get_obj(obj_ptr);\n        time_instance_t *inst = find_instance(obj, iid);\n        assert(inst);\n\n        switch (rid) {\n        case RID_CURRENT_TIME: {\n            assert(riid == ANJAY_ID_INVALID);\n            int64_t timestamp;\n            if (avs_time_real_to_scalar(&timestamp, AVS_TIME_S,\n                                        avs_time_real_now())) {\n                return -1;\n            }\n            return anjay_ret_i64(ctx, timestamp);\n        }\n\n        case RID_APPLICATION_TYPE:\n            assert(riid == ANJAY_ID_INVALID);\n            return anjay_ret_string(ctx, inst->application_type);\n\n        default:\n            return ANJAY_ERR_METHOD_NOT_ALLOWED;\n        }\n    }\n\nAs you remember, we do not want to set the system time, so Write operation is\nallowed only on Application Type resource.\n\n.. highlight:: c\n.. snippet-source:: examples/tutorial/BC-ObjectImplementation/src/time_object.c\n    :emphasize-lines: 14-17\n\n    static int resource_write(anjay_t *anjay,\n                              const anjay_dm_object_def_t *const *obj_ptr,\n                              anjay_iid_t iid,\n                              anjay_rid_t rid,\n                              anjay_riid_t riid,\n                              anjay_input_ctx_t *ctx) {\n        (void) anjay;\n\n        time_object_t *obj = get_obj(obj_ptr);\n        time_instance_t *inst = find_instance(obj, iid);\n        assert(inst);\n\n        switch (rid) {\n        case RID_APPLICATION_TYPE:\n            assert(riid == ANJAY_ID_INVALID);\n            return anjay_get_string(ctx, inst->application_type,\n                                    sizeof(inst->application_type));\n\n        default:\n            return ANJAY_ERR_METHOD_NOT_ALLOWED;\n        }\n    }\n\n**Initialization of the Object**\n\nThere is one function left to implement to have the basic functionality:\n``time_object_create()``. By default, there is no Object Instance created, so no\ndata could be read unless LwM2M Server creates it. However, we are able to\nadd an Instance right now, by calling ``add_instance()``.\n\n.. highlight:: c\n.. snippet-source:: examples/tutorial/BC-ObjectImplementation/src/time_object.c\n    :emphasize-lines: 8-14\n\n    const anjay_dm_object_def_t **time_object_create(void) {\n        time_object_t *obj = (time_object_t *) avs_calloc(1, sizeof(time_object_t));\n        if (!obj) {\n            return NULL;\n        }\n        obj->def = &OBJ_DEF;\n\n        time_instance_t *inst = add_instance(obj, 0);\n        if (inst) {\n            strcpy(inst->application_type, \"Clock 0\");\n        } else {\n            avs_free(obj);\n            return NULL;\n        }\n\n        return &obj->def;\n    }\n\nSince we do not allocate memory for anything else during object creation, we may\nleave the default implementation of ``time_object_release()``, which will remove\nthe created instance.\n\n.. _registering-objects:\n\n**Registering the Object in Anjay**\n\nThe last things to do is to create header file for implemented object, register\nit in Anjay and update ``CMakeLists.txt`` file.\n\n.. highlight:: c\n.. snippet-source:: examples/tutorial/BC-ObjectImplementation/src/time_object.h\n    :caption: time.h\n\n    #ifndef TIME_OBJECT_H\n    #define TIME_OBJECT_H\n\n    #include <anjay/dm.h>\n\n    const anjay_dm_object_def_t **time_object_create(void);\n    void time_object_release(const anjay_dm_object_def_t **def);\n\n    #endif // TIME_OBJECT_H\n\n.. highlight:: c\n.. snippet-source:: examples/tutorial/BC-ObjectImplementation/src/main.c\n    :caption: main.c\n    :emphasize-lines: 26-34,42\n\n    int main(int argc, char *argv[]) {\n        if (argc != 2) {\n            avs_log(tutorial, ERROR, \"usage: %s ENDPOINT_NAME\", argv[0]);\n            return -1;\n        }\n\n        const anjay_configuration_t CONFIG = {\n            .endpoint_name = argv[1],\n            .in_buffer_size = 4000,\n            .out_buffer_size = 4000,\n            .msg_cache_size = 4000\n        };\n\n        anjay_t *anjay = anjay_new(&CONFIG);\n        if (!anjay) {\n            avs_log(tutorial, ERROR, \"Could not create Anjay object\");\n            return -1;\n        }\n\n        int result = 0;\n        // Setup necessary objects\n        if (setup_security_object(anjay) || setup_server_object(anjay)) {\n            result = -1;\n        }\n\n        const anjay_dm_object_def_t **time_object = NULL;\n        if (!result) {\n            time_object = time_object_create();\n            if (time_object) {\n                result = anjay_register_object(anjay, time_object);\n            } else {\n                result = -1;\n            }\n        }\n\n        if (!result) {\n            result = anjay_event_loop_run(\n                    anjay, avs_time_duration_from_scalar(1, AVS_TIME_S));\n        }\n\n        anjay_delete(anjay);\n        time_object_release(time_object);\n        return result;\n    }\n\n.. highlight:: cmake\n.. snippet-source:: examples/tutorial/BC-ObjectImplementation/CMakeLists.txt\n   :caption: CMakeLists.txt\n   :emphasize-lines: 11-12\n\n    cmake_minimum_required(VERSION 3.16)\n    project(anjay-bc-object-implementation C)\n\n    set(CMAKE_C_STANDARD 99)\n    set(CMAKE_C_EXTENSIONS OFF)\n\n    add_compile_options(-Wall -Wextra)\n\n    find_package(anjay REQUIRED)\n\n    add_executable(${PROJECT_NAME}\n                   src/main.c\n                   src/time_object.h\n                   src/time_object.c)\n    target_link_libraries(${PROJECT_NAME} PRIVATE anjay)\n\nNow the client is ready to be built and connected to LwM2M Server, allowing it\nto read the Time object.\n\n.. important::\n\n   Custom objects are not automatically managed by Anjay. Remember to release\n   created object **after** deleting the Anjay object.\n\nSupporting transactional writes\n-------------------------------\n\nConsider the following scenario: LwM2M Server tries to write to two or more\nresources at once. The write on Application Type will probably succeed, but we\nare sure, that in this case write on the Current Time will fail. Without\nsupporting transactions, the entire Write operation will fail, but the\nApplication Type resource will be changed.\n\nBy default, transaction handlers are set to ``anjay_dm_transaction_NOOP``\nand do nothing. To properly support Writes on the object implemented in this\ntutorial, it is enough to implement only two handlers: ``transaction_begin``,\nwhich makes a backup of Application Type value and ``transaction_rollback``,\nwhich reverts Application Type to its initial value (before Write is performed).\nThis is why we need ``application_type_backup`` array.\n\n.. highlight:: c\n.. snippet-source:: examples/tutorial/BC-ObjectImplementation/src/time_object.c\n    :emphasize-lines: 1-25,39,42\n\n    int transaction_begin(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr) {\n        (void) anjay;\n\n        time_object_t *obj = get_obj(obj_ptr);\n\n        time_instance_t *element;\n        AVS_LIST_FOREACH(element, obj->instances) {\n            strcpy(element->application_type_backup, element->application_type);\n        }\n        return 0;\n    }\n\n    int transaction_rollback(anjay_t *anjay,\n                             const anjay_dm_object_def_t *const *obj_ptr) {\n        (void) anjay;\n\n        time_object_t *obj = get_obj(obj_ptr);\n\n        time_instance_t *element;\n        AVS_LIST_FOREACH(element, obj->instances) {\n            strcpy(element->application_type, element->application_type_backup);\n        }\n        return 0;\n    }\n\n    static const anjay_dm_object_def_t OBJ_DEF = {\n        .oid = 3333,\n        .handlers = {\n            .list_instances = list_instances,\n            .instance_create = instance_create,\n            .instance_remove = instance_remove,\n            .instance_reset = instance_reset,\n\n            .list_resources = list_resources,\n            .resource_read = resource_read,\n            .resource_write = resource_write,\n\n            .transaction_begin = transaction_begin,\n            .transaction_validate = anjay_dm_transaction_NOOP,\n            .transaction_commit = anjay_dm_transaction_NOOP,\n            .transaction_rollback = transaction_rollback\n        }\n    };\n\n.. note::\n\n    Complete code of this example can be found in\n    `examples/tutorial/BC-ObjectImplementation` subdirectory of main Anjay\n    project repository.\n"
  },
  {
    "path": "doc/sphinx/source/BasicClient/BC-Security.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nEnabling secure communication\n=============================\n\nIf Anjay is compiled with support for DTLS and linked with one of the\nsupported DTLS libraries, connection encryption is automatically handled\naccording to values of Resources in the Security (``/0``) Object.\n\nThis automatic configuration will be performed regardless of whether you are\nusing the ``security`` module that pre-implements the Security Object, or if you\nperhaps decide to implement the Security Object yourself from scratch. The\nlibrary will always read the necessary DTLS configuration from the data model.\n\n.. note:: **mbed TLS 2.0 or newer** or **OpenSSL 1.1 or newer** or\n          **tinydtls 0.9 or newer** is required for proper, conformant support\n          for the security modes defined in the LwM2M specification.\n\n.. warning:: Anjay will likely compile successfully with older DTLS library\n             versions, but some REQUIRED cipher suites won't be supported and\n             serious interoperability problems may arise.\n\nSupported security modes\n------------------------\n\nThe security mode is determined based on the *Security Mode* Resource in a\ngiven instance of the Security Object (``/0/*/2``). Supported values are:\n\n* ``0`` - **Pre-Shared Key mode** - In this mode, communication is symmetrically\n  encrypted and authenticated using the same secret key, shared between the\n  server and the client.\n\n  * The TLS-PSK identity is stored in the *Public Key or Identity* Resource\n    (``/0/*/3``). It is a string identifying the key being used, so that the\n    server can uniquely determine which key to use for communication. This\n    string shall be directly stored in the aforementioned Resource.\n\n  * The *Secret Key* (``/0/*/5``) Resource shall contain the secret pre-shared\n    key itself, directly in an opaque binary format appropriate for the\n    cipher suite used by the server.\n\n* ``2`` - **Certificate mode** - In this mode, an asymmetrical public-key\n  cryptographic algorithm is used to authenticate the connection endpoints and\n  initialize payload encryption.\n\n  Appropriate certificates need to be generated for both the LwM2M Client and\n  the LwM2M Server. Public certificates of both parties are mutually available,\n  and each party also has access to its own corresponding private key.\n\n  * In this mode, the *Public Key or Identity* (``/0/*/3``) Resource shall\n    contain the Client's public certificate in binary, DER-encoded X.509\n    format.\n\n  * The *Server Public Key* (``/0/*/4``) Resource shall contain the Server's\n    public certificate, also in binary, DER-encoded X.509 format.\n\n  * The *Secret Key* (``/0/*/5``) Resource shall contain the Client's\n    private key, corresponding to the public key contained in the *Public Key or\n    Identity* Resource. It needs to be in a format defined in\n    `RFC 5958 <https://tools.ietf.org/html/rfc5958>`_ (also known as PKCS#8, the\n    name which was used in previous versions of the format), DER-encoded into a\n    binary value.\n\n  Note that in the Certificate mode, it is not enough if the Server's\n  certificate *just matches* the one stored in the *Server Public Key* resource.\n  It is also verified that the certificate is issued for the same domain name\n  that the Server URI points to and that it is signed by a trusted CA.\n\n* ``3`` - **NoSec mode** - In this mode, encryption and authentication are\n  disabled completely and the CoAP messages are passed in plain text over the\n  network. It shall not be used in production environments, unless end-to-end\n  security is provided on a lower layer (e.g. IPsec). It is also useful for\n  development, testing and debugging purposes.\n\n* ``4`` - **Certificate mode with EST** - In this mode, a certificate-based\n  methods of encryption and authentication are used along with *Enrollment\n  over Secure Transport* certificate management protocol. Support for this\n  security mode is available as a commercial feature in Anjay. For more information,\n  see the description in its :doc:`dedicated article<../CommercialFeatures/CF-EST>`.\n\nThe *Raw Public Key* mode described in the LwM2M specification is not currently\nsupported. \n\nIn this tutorial, we will focus on enabling security using the PSK mode. If you\nare interested in using certificates, please refer to\n:doc:`../AdvancedTopics/AT-Certificates`.\n\nProvisioning security configuration\n-----------------------------------\n\nAccording to the LwM2M specification, the aforementioned Resources shall be\nprovisioned during the Bootstrap Phase. However, if Bootstrap from Smartcard is\nnot used, the Client will need to contain some factory defaults for connecting\nto a LwM2M Server or a LwM2M Bootstrap Server. In this section, we will learn\nhow to implement such factory defaults for DTLS connection.\n\nThe full code for the following example can be also found in the\n``examples/tutorial/BC-Security`` directory in Anjay sources.\n\n**Configuring encryption keys**\n\nAs mentioned above, in the case of PSK mode, the security-related data that the\nLwM2M Client is operating on, is raw data. They are set using\n``public_cert_or_psk_identity``, ``public_cert_or_psk_identity_size``,\n``private_cert_or_psk_key`` and ``private_cert_or_psk_key_size`` fields of\n``anjay_security_instance_t`` struct.\n\n**Complete code**\n\nContinuing the previous tutorial, we can modify ``setup_security_object()`` and\n``main()`` as follows:\n\n.. highlight:: c\n.. snippet-source:: examples/tutorial/BC-Security/src/main.c\n    :emphasize-lines: 14-25\n\n    #include <anjay/anjay.h>\n    #include <anjay/security.h>\n    #include <anjay/server.h>\n    #include <avsystem/commons/avs_log.h>\n\n    // Installs Security Object and adds an instance of it.\n    // An instance of Security Object provides information needed to connect to\n    // LwM2M server.\n    static int setup_security_object(anjay_t *anjay) {\n        if (anjay_security_object_install(anjay)) {\n            return -1;\n        }\n\n        static const char PSK_IDENTITY[] = \"identity\";\n        static const char PSK_KEY[] = \"P4s$w0rd\";\n\n        anjay_security_instance_t security_instance = {\n            .ssid = 1,\n            .server_uri = \"coaps://eu.iot.avsystem.cloud:5684\",\n            .security_mode = ANJAY_SECURITY_PSK,\n            .public_cert_or_psk_identity = (const uint8_t *) PSK_IDENTITY,\n            .public_cert_or_psk_identity_size = strlen(PSK_IDENTITY),\n            .private_cert_or_psk_key = (const uint8_t *) PSK_KEY,\n            .private_cert_or_psk_key_size = strlen(PSK_KEY)\n        };\n\n        // Anjay will assign Instance ID automatically\n        anjay_iid_t security_instance_id = ANJAY_ID_INVALID;\n        if (anjay_security_object_add_instance(anjay, &security_instance,\n                                               &security_instance_id)) {\n            return -1;\n        }\n\n        return 0;\n    }\n\n    // Installs Server Object and adds an instance of it.\n    // An instance of Server Object provides the data related to a LwM2M Server.\n    static int setup_server_object(anjay_t *anjay) {\n        if (anjay_server_object_install(anjay)) {\n            return -1;\n        }\n\n        const anjay_server_instance_t server_instance = {\n            // Server Short ID\n            .ssid = 1,\n            // Client will send Update message often than every 60 seconds\n            .lifetime = 60,\n            // Disable Default Minimum Period resource\n            .default_min_period = -1,\n            // Disable Default Maximum Period resource\n            .default_max_period = -1,\n            // Disable Disable Timeout resource\n            .disable_timeout = -1,\n            // Sets preferred transport to UDP\n            .binding = \"U\"\n        };\n\n        // Anjay will assign Instance ID automatically\n        anjay_iid_t server_instance_id = ANJAY_ID_INVALID;\n        if (anjay_server_object_add_instance(anjay, &server_instance,\n                                             &server_instance_id)) {\n            return -1;\n        }\n\n        return 0;\n    }\n\n    int main(int argc, char *argv[]) {\n        if (argc != 2) {\n            avs_log(tutorial, ERROR, \"usage: %s ENDPOINT_NAME\", argv[0]);\n            return -1;\n        }\n\n        const anjay_configuration_t CONFIG = {\n            .endpoint_name = argv[1],\n            .in_buffer_size = 4000,\n            .out_buffer_size = 4000,\n            .msg_cache_size = 4000\n        };\n\n        anjay_t *anjay = anjay_new(&CONFIG);\n        if (!anjay) {\n            avs_log(tutorial, ERROR, \"Could not create Anjay object\");\n            return -1;\n        }\n\n        int result = 0;\n        // Setup necessary objects\n        if (setup_security_object(anjay) || setup_server_object(anjay)) {\n            result = -1;\n        }\n\n        if (!result) {\n            result = anjay_event_loop_run(\n                    anjay, avs_time_duration_from_scalar(1, AVS_TIME_S));\n        }\n\n        anjay_delete(anjay);\n        return result;\n    }\n\n.. note::\n\n    Complete code of this example can be found in\n    `examples/tutorial/BC-Security` subdirectory of main Anjay project\n    repository.\n\nPlease note, that ``server_uri`` field changed too. Now there is ``coaps``\nURI scheme and port ``5684`` (default for secure CoAP).\n\nAll remaining activities related to establishing a secure communication channel\nwith the LwM2M Server are performed automatically by Anjay.\n\n.. note::\n\n    For many LwM2M Servers, including the `Coiote IoT Device Management platform\n    <https://avsystem.com/coiote-iot-device-management-platform/>`_, you will\n    need to change server-side configuration if you previously used NoSec\n    connectivity for the same endpoint name.\n\n    The simplest solution might often be to remove the device entry completely\n    and create it from scratch.\n"
  },
  {
    "path": "doc/sphinx/source/BasicClient/BC-Send.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nSend method\n===========\n\nThe \"Send\" operation is used by the LwM2M Client to send data to the LwM2M\nServer without explicit request by that LwM2M Server. Messages are created using\n``anjay_send_batch_builder`` which allows to build a payload with the data to be\nsent to the LwM2M Server. Payload can consist of multiple values from different\nresources. Calling ``anjay_send()`` does not send batch immediately, but\nschedules a task to be run on next iteration of the event loop.\n\nExample\n-------\n\nAs an example we'll add send method support for the Time Object implemented\npreviously in :doc:`BC-Notifications` section. It contains Application Type and\nCurrent Time resources, which we will send to the server for demonstration\npurposes. We create ``send_finished_handler()`` and ``time_object_send()``\nfunctions in the ``time_object.c`` file.\n\n.. highlight:: c\n.. snippet-source:: examples/tutorial/BC-Send/src/time_object.c\n    :caption: time_object.c\n\n    static void send_finished_handler(anjay_t *anjay,\n                                    anjay_ssid_t ssid,\n                                    const anjay_send_batch_t *batch,\n                                    int result,\n                                    void *data) {\n        (void) anjay;\n        (void) ssid;\n        (void) batch;\n        (void) data;\n\n        if (result != ANJAY_SEND_SUCCESS) {\n            avs_log(time_object, ERROR, \"Send failed, result: %d\", result);\n        } else {\n            avs_log(time_object, TRACE, \"Send successful\");\n        }\n    }\n\n    void time_object_send(anjay_t *anjay, const anjay_dm_object_def_t **def) {\n        if (!anjay || !def) {\n            return;\n        }\n        time_object_t *obj = get_obj(def);\n        const anjay_ssid_t server_ssid = 1;\n\n        // Allocate new batch builder.\n        anjay_send_batch_builder_t *builder = anjay_send_batch_builder_new();\n\n        if (!builder) {\n            avs_log(time_object, ERROR, \"Failed to allocate batch builder\");\n            return;\n        }\n\n        int res = 0;\n\n        AVS_LIST(time_instance_t) it;\n        AVS_LIST_FOREACH(it, obj->instances) {\n            // Add current values of resources from Time Object.\n            if (anjay_send_batch_data_add_current(builder, anjay, obj->def->oid,\n                                                it->iid, RID_CURRENT_TIME)\n                    || anjay_send_batch_data_add_current(builder, anjay,\n                                                        obj->def->oid, it->iid,\n                                                        RID_APPLICATION_TYPE)) {\n                anjay_send_batch_builder_cleanup(&builder);\n                avs_log(time_object, ERROR, \"Failed to add batch data, result: %d\",\n                        res);\n                return;\n            }\n        }\n        // After adding all values, compile our batch for sending.\n        anjay_send_batch_t *batch = anjay_send_batch_builder_compile(&builder);\n\n        if (!batch) {\n            anjay_send_batch_builder_cleanup(&builder);\n            avs_log(time_object, ERROR, \"Batch compile failed\");\n            return;\n        }\n\n        // Schedule our send to be run on next `anjay_sched_run()` call.\n        res = anjay_send(anjay, server_ssid, batch, send_finished_handler, NULL);\n\n        if (res) {\n            avs_log(time_object, ERROR, \"Failed to send, result: %d\", res);\n        }\n\n        // After scheduling, we can release our batch.\n        anjay_send_batch_release(&batch);\n    }\n\n\nAnd include ``anjay/lwm2m_send.h`` and ``<avsystem/commons/avs_log.h>`` in\n``time_object.c``.\n\n.. highlight:: c\n.. snippet-source:: examples/tutorial/BC-Send/src/time_object.c\n    :caption: time_object.c\n    :emphasize-lines: 5, 9\n\n    #include <assert.h>\n    #include <stdbool.h>\n\n    #include <anjay/anjay.h>\n    #include <anjay/lwm2m_send.h>\n    #include <avsystem/commons/avs_defs.h>\n    #include <avsystem/commons/avs_list.h>\n    #include <avsystem/commons/avs_log.h>\n    #include <avsystem/commons/avs_memory.h>\n\nAt last, we need to declare the function in the object's header file.\n\n.. highlight:: c\n.. snippet-source:: examples/tutorial/BC-Send/src/time_object.h\n    :caption: time_object.h\n    :emphasize-lines: 9\n\n    #ifndef TIME_OBJECT_H\n    #define TIME_OBJECT_H\n\n    #include <anjay/dm.h>\n\n    const anjay_dm_object_def_t **time_object_create(void);\n    void time_object_release(const anjay_dm_object_def_t **def);\n    void time_object_notify(anjay_t *anjay, const anjay_dm_object_def_t **def);\n    void time_object_send(anjay_t *anjay, const anjay_dm_object_def_t **def);\n\n    #endif // TIME_OBJECT_H\n\nNow we can add another scheduler job that will call this function. In the\nexample, for test purposes, we create a ``send_job()`` function that will be set\nup the same way as ``notify_job()``, but run every 10 seconds.\n\nPlease note that the ``notify_job_args_t`` has additionally been renamed to\n``time_object_job_args_t`` because it is now shared between ``notify_job()`` and\n``send_job()``.\n\n.. highlight:: c\n.. snippet-source:: examples/tutorial/BC-Send/src/main.c\n    :caption: main.c\n    :emphasize-lines: 25-36,143-146\n\n    #include <anjay/anjay.h>\n    #include <anjay/security.h>\n    #include <anjay/server.h>\n    #include <avsystem/commons/avs_log.h>\n\n    #include \"time_object.h\"\n\n    typedef struct {\n        anjay_t *anjay;\n        const anjay_dm_object_def_t **time_object;\n    } time_object_job_args_t;\n\n    // Periodically notifies the library about Resource value changes\n    static void notify_job(avs_sched_t *sched, const void *args_ptr) {\n        const time_object_job_args_t *args =\n                (const time_object_job_args_t *) args_ptr;\n\n        time_object_notify(args->anjay, args->time_object);\n\n        // Schedule run of the same function after 1 second\n        AVS_SCHED_DELAYED(sched, NULL, avs_time_duration_from_scalar(1, AVS_TIME_S),\n                          notify_job, args, sizeof(*args));\n    }\n\n    // Periodically issues a Send message with application type and current time\n    static void send_job(avs_sched_t *sched, const void *args_ptr) {\n        const time_object_job_args_t *args =\n                (const time_object_job_args_t *) args_ptr;\n\n        time_object_send(args->anjay, args->time_object);\n\n        // Schedule run of the same function after 10 seconds\n        AVS_SCHED_DELAYED(sched, NULL,\n                          avs_time_duration_from_scalar(10, AVS_TIME_S), send_job,\n                          args, sizeof(*args));\n    }\n\n    // Installs Security Object and adds an instance of it.\n    // An instance of Security Object provides information needed to connect to\n    // LwM2M server.\n    static int setup_security_object(anjay_t *anjay) {\n        if (anjay_security_object_install(anjay)) {\n            return -1;\n        }\n\n        static const char PSK_IDENTITY[] = \"identity\";\n        static const char PSK_KEY[] = \"P4s$w0rd\";\n\n        anjay_security_instance_t security_instance = {\n            .ssid = 1,\n            .server_uri = \"coaps://eu.iot.avsystem.cloud:5684\",\n            .security_mode = ANJAY_SECURITY_PSK,\n            .public_cert_or_psk_identity = (const uint8_t *) PSK_IDENTITY,\n            .public_cert_or_psk_identity_size = strlen(PSK_IDENTITY),\n            .private_cert_or_psk_key = (const uint8_t *) PSK_KEY,\n            .private_cert_or_psk_key_size = strlen(PSK_KEY)\n        };\n\n        // Anjay will assign Instance ID automatically\n        anjay_iid_t security_instance_id = ANJAY_ID_INVALID;\n        if (anjay_security_object_add_instance(anjay, &security_instance,\n                                               &security_instance_id)) {\n            return -1;\n        }\n\n        return 0;\n    }\n\n    // Installs Server Object and adds an instance of it.\n    // An instance of Server Object provides the data related to a LwM2M Server.\n    static int setup_server_object(anjay_t *anjay) {\n        if (anjay_server_object_install(anjay)) {\n            return -1;\n        }\n\n        const anjay_server_instance_t server_instance = {\n            // Server Short ID\n            .ssid = 1,\n            // Client will send Update message often than every 60 seconds\n            .lifetime = 60,\n            // Disable Default Minimum Period resource\n            .default_min_period = -1,\n            // Disable Default Maximum Period resource\n            .default_max_period = -1,\n            // Disable Disable Timeout resource\n            .disable_timeout = -1,\n            // Sets preferred transport to UDP\n            .binding = \"U\"\n        };\n\n        // Anjay will assign Instance ID automatically\n        anjay_iid_t server_instance_id = ANJAY_ID_INVALID;\n        if (anjay_server_object_add_instance(anjay, &server_instance,\n                                             &server_instance_id)) {\n            return -1;\n        }\n\n        return 0;\n    }\n\n    int main(int argc, char *argv[]) {\n        if (argc != 2) {\n            avs_log(tutorial, ERROR, \"usage: %s ENDPOINT_NAME\", argv[0]);\n            return -1;\n        }\n\n        const anjay_configuration_t CONFIG = {\n            .endpoint_name = argv[1],\n            .in_buffer_size = 4000,\n            .out_buffer_size = 4000,\n            .msg_cache_size = 4000\n        };\n\n        anjay_t *anjay = anjay_new(&CONFIG);\n        if (!anjay) {\n            avs_log(tutorial, ERROR, \"Could not create Anjay object\");\n            return -1;\n        }\n\n        int result = 0;\n        // Setup necessary objects\n        if (setup_security_object(anjay) || setup_server_object(anjay)) {\n            result = -1;\n        }\n\n        const anjay_dm_object_def_t **time_object = NULL;\n        if (!result) {\n            time_object = time_object_create();\n            if (time_object) {\n                result = anjay_register_object(anjay, time_object);\n            } else {\n                result = -1;\n            }\n        }\n\n        if (!result) {\n            // Run notify_job and send_job the first time;\n            // this will schedule periodic calls to themselves via the scheduler\n            notify_job(anjay_get_scheduler(anjay), &(const time_object_job_args_t) {\n                                                       .anjay = anjay,\n                                                       .time_object = time_object\n                                                   });\n            send_job(anjay_get_scheduler(anjay), &(const time_object_job_args_t) {\n                                                     .anjay = anjay,\n                                                     .time_object = time_object\n                                                 });\n\n            result = anjay_event_loop_run(\n                    anjay, avs_time_duration_from_scalar(1, AVS_TIME_S));\n        }\n\n        anjay_delete(anjay);\n        time_object_release(time_object);\n        return result;\n    }\n\n\nThat's all you need to make your client support LwM2M Send operation!\n"
  },
  {
    "path": "doc/sphinx/source/BasicClient/BC-ThreadSafety.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nThread safety in Anjay\n======================\n\nAll the examples in this tutorial so far have been single-threaded applications.\nSome degree of asynchronicity has been provided through use of the internal\nscheduler. However, in some cases, this approach might not be flexible enough.\n\nStarting with Anjay 2.13, the library includes internal provisions for thread\nsafety. This means that Anjay APIs can be safely called from concurrent threads\nand the library will take care of necessary synchronization.\n\nIn this tutorial, we will modify the application written in the\n:doc:`BC-Notifications` chapter so that the notifications are controlled from a\nseparate thread instead of a scheduler job.\n\nMaking sure that thread safety is enabled\n-----------------------------------------\n\nLocking and unlocking mutexes in every public API function takes a sizable\namount of code - this can take even up to 20 KiB of the executable binary when\nthe library is in a full-featured configuration. For this reason, all the thread\nsafety features are optional, controlled by a compile-time setting.\n\nThis setting is on by default on full-featured operating systems such as Linux\nor Windows, and off by default on other platforms.\n\nTo control whether Anjay is compiled with thread safety enabled or disabled,\nplease add the ``-DWITH_THREAD_SAFETY=<ON|OFF>`` to the CMake invocation\ncommand. By default, it will control the thread safety features both in Anjay\nAPI itself, and in the ``avs_sched`` component. The latter can be overridden\nusing the ``-DWITH_SCHEDULER_THREAD_SAFE=<ON|OFF>`` flag.\n\nIf you are compiling Anjay without using CMake, these features are controlled\nindependently by the ``ANJAY_WITH_THREAD_SAFETY`` flag in ``anjay_config.h``,\nand the ``AVS_COMMONS_SCHED_THREAD_SAFE`` flag in ``avs_commons_config.h``,\nrespectively.\n\nRelying on thread safety of an API while it is not actually assured may result\nin critical bugs that could be very hard to debug. If you are writing an\napplication that depends on the thread safety features of Anjay, you can add a\ncheck like the following to make sure that it is enabled.\n\n.. highlight:: c\n.. snippet-source:: examples/tutorial/BC-ThreadSafety/src/main.c\n\n    #if !defined(ANJAY_WITH_THREAD_SAFETY) \\\n            || !defined(AVS_COMMONS_SCHED_THREAD_SAFE)\n    #    error \"This example requires Anjay compiled with thread safety enabled\"\n    #endif // !defined(ANJAY_WITH_THREAD_SAFETY) ||\n           // !defined(AVS_COMMONS_SCHED_THREAD_SAFE)\n\nUpdating your own code to be thread-safe\n----------------------------------------\n\nEven if Anjay APIs have thread safety guarantees, you still need to ensure\nthread safety for your own code.\n\nFor the purpose of this example, we will use the POSIX Threads API that is\nwidely available on modern Unix-like systems such as Linux and macOS, as well as\nthrough compatibility layers on other systems, including Windows (e.g. MinGW's\nwinpthreads) and Zephyr. Anjay itself is agnostic with regards to the\nunderlying threading API (see also:\n:doc:`../PortingGuideForNonPOSIXPlatforms/ThreadingAPI`), so the same concepts\nshall apply on other platforms.\n\nUsing the POSIX Threads API requires minor adjustments in the ``CMakeLists.txt``\nfile so that the application is properly linked with the appropriate threading\nlibrary:\n\n.. highlight:: cmake\n.. snippet-source:: examples/tutorial/BC-ThreadSafety/CMakeLists.txt\n    :caption: CMakeLists.txt\n    :emphasize-lines: 5,10,16\n\n    cmake_minimum_required(VERSION 3.16)\n    project(anjay-bc-thread-safety C)\n\n    set(CMAKE_C_STANDARD 99)\n    set(CMAKE_C_EXTENSIONS ON)\n\n    add_compile_options(-Wall -Wextra)\n\n    find_package(anjay REQUIRED)\n    find_package(Threads REQUIRED)\n\n    add_executable(${PROJECT_NAME}\n                   src/main.c\n                   src/time_object.h\n                   src/time_object.c)\n    target_link_libraries(${PROJECT_NAME} PRIVATE anjay ${CMAKE_THREAD_LIBS_INIT})\n\nWe will update the previous example so that ``time_object_notify()`` will be\ncalled from a different thread than the one running Anjay event loop. This means\nthat the Time object data structure will be accessed concurrently by both\nthreads - which means that the Time object implementation itself needs to be\nproperly guarded by a mutex:\n\n.. highlight:: c\n.. snippet-source:: examples/tutorial/BC-ThreadSafety/src/time_object.c\n    :caption: time_object.c\n    :emphasize-lines: 4,46,77,81,129-135,144,158,168,172,202,227,240,254,263,\n                      268,277,282,306-312,320-327,332-338,346,350-351,361,375\n\n    #include <assert.h>\n    #include <stdbool.h>\n\n    #include <pthread.h>\n\n    #include <anjay/anjay.h>\n    #include <avsystem/commons/avs_defs.h>\n    #include <avsystem/commons/avs_list.h>\n    #include <avsystem/commons/avs_memory.h>\n\n    #include \"time_object.h\"\n\n    /**\n     * Current Time: RW, Single, Mandatory\n     * type: time, range: N/A, unit: N/A\n     * Unix Time. A signed integer representing the number of seconds since\n     * Jan 1st, 1970 in the UTC time zone.\n     */\n    #define RID_CURRENT_TIME 5506\n\n    /**\n     * Fractional Time: RW, Single, Optional\n     * type: float, range: 0..1, unit: s\n     * Fractional part of the time when sub-second precision is used (e.g.,\n     * 0.23 for 230 ms).\n     */\n    #define RID_FRACTIONAL_TIME 5507\n\n    /**\n     * Application Type: RW, Single, Optional\n     * type: string, range: N/A, unit: N/A\n     * The application type of the sensor or actuator as a string depending\n     * on the use case.\n     */\n    #define RID_APPLICATION_TYPE 5750\n\n    typedef struct time_instance_struct {\n        anjay_iid_t iid;\n        char application_type[64];\n        char application_type_backup[64];\n        int64_t last_notify_timestamp;\n    } time_instance_t;\n\n    typedef struct time_object_struct {\n        const anjay_dm_object_def_t *def;\n        pthread_mutex_t mutex;\n        AVS_LIST(time_instance_t) instances;\n    } time_object_t;\n\n    static inline time_object_t *\n    get_obj(const anjay_dm_object_def_t *const *obj_ptr) {\n        assert(obj_ptr);\n        return AVS_CONTAINER_OF(obj_ptr, time_object_t, def);\n    }\n\n    static time_instance_t *find_instance(const time_object_t *obj,\n                                          anjay_iid_t iid) {\n        AVS_LIST(time_instance_t) it;\n        AVS_LIST_FOREACH(it, obj->instances) {\n            if (it->iid == iid) {\n                return it;\n            } else if (it->iid > iid) {\n                break;\n            }\n        }\n\n        return NULL;\n    }\n\n    static int list_instances(anjay_t *anjay,\n                              const anjay_dm_object_def_t *const *obj_ptr,\n                              anjay_dm_list_ctx_t *ctx) {\n        (void) anjay;\n        time_object_t *obj = get_obj(obj_ptr);\n\n        pthread_mutex_lock(&obj->mutex);\n        AVS_LIST(time_instance_t) it;\n        AVS_LIST_FOREACH(it, obj->instances) {\n            anjay_dm_emit(ctx, it->iid);\n        }\n        pthread_mutex_unlock(&obj->mutex);\n        return 0;\n    }\n\n    static int init_instance(time_instance_t *inst, anjay_iid_t iid) {\n        assert(iid != ANJAY_ID_INVALID);\n\n        inst->iid = iid;\n        inst->application_type[0] = '\\0';\n\n        return 0;\n    }\n\n    static void release_instance(time_instance_t *inst) {\n        (void) inst;\n    }\n\n    static time_instance_t *add_instance(time_object_t *obj, anjay_iid_t iid) {\n        assert(find_instance(obj, iid) == NULL);\n\n        AVS_LIST(time_instance_t) created = AVS_LIST_NEW_ELEMENT(time_instance_t);\n        if (!created) {\n            return NULL;\n        }\n\n        int result = init_instance(created, iid);\n        if (result) {\n            AVS_LIST_CLEAR(&created);\n            return NULL;\n        }\n\n        AVS_LIST(time_instance_t) *ptr;\n        AVS_LIST_FOREACH_PTR(ptr, &obj->instances) {\n            if ((*ptr)->iid > created->iid) {\n                break;\n            }\n        }\n\n        AVS_LIST_INSERT(ptr, created);\n        return created;\n    }\n\n    static int instance_create(anjay_t *anjay,\n                               const anjay_dm_object_def_t *const *obj_ptr,\n                               anjay_iid_t iid) {\n        (void) anjay;\n        time_object_t *obj = get_obj(obj_ptr);\n\n        pthread_mutex_lock(&obj->mutex);\n        int result = 0;\n        if (add_instance(obj, iid)) {\n            result = ANJAY_ERR_INTERNAL;\n        }\n        pthread_mutex_unlock(&obj->mutex);\n        return result;\n    }\n\n    static int instance_remove(anjay_t *anjay,\n                               const anjay_dm_object_def_t *const *obj_ptr,\n                               anjay_iid_t iid) {\n        (void) anjay;\n        time_object_t *obj = get_obj(obj_ptr);\n\n        pthread_mutex_lock(&obj->mutex);\n        int result = ANJAY_ERR_NOT_FOUND;\n        AVS_LIST(time_instance_t) *it;\n        AVS_LIST_FOREACH_PTR(it, &obj->instances) {\n            if ((*it)->iid == iid) {\n                release_instance(*it);\n                AVS_LIST_DELETE(it);\n                result = 0;\n                break;\n            } else if ((*it)->iid > iid) {\n                break;\n            }\n        }\n        assert(!result);\n        pthread_mutex_unlock(&obj->mutex);\n        return result;\n    }\n\n    static int instance_reset(anjay_t *anjay,\n                              const anjay_dm_object_def_t *const *obj_ptr,\n                              anjay_iid_t iid) {\n        (void) anjay;\n        time_object_t *obj = get_obj(obj_ptr);\n\n        pthread_mutex_lock(&obj->mutex);\n        time_instance_t *inst = find_instance(obj, iid);\n        assert(inst);\n        inst->application_type[0] = '\\0';\n        pthread_mutex_unlock(&obj->mutex);\n        return 0;\n    }\n\n    static int list_resources(anjay_t *anjay,\n                              const anjay_dm_object_def_t *const *obj_ptr,\n                              anjay_iid_t iid,\n                              anjay_dm_resource_list_ctx_t *ctx) {\n        (void) anjay;\n        (void) obj_ptr;\n        (void) iid;\n\n        anjay_dm_emit_res(ctx, RID_CURRENT_TIME, ANJAY_DM_RES_RW,\n                          ANJAY_DM_RES_PRESENT);\n        anjay_dm_emit_res(ctx, RID_FRACTIONAL_TIME, ANJAY_DM_RES_RW,\n                          ANJAY_DM_RES_ABSENT);\n        anjay_dm_emit_res(ctx, RID_APPLICATION_TYPE, ANJAY_DM_RES_RW,\n                          ANJAY_DM_RES_PRESENT);\n        return 0;\n    }\n\n    static int resource_read(anjay_t *anjay,\n                             const anjay_dm_object_def_t *const *obj_ptr,\n                             anjay_iid_t iid,\n                             anjay_rid_t rid,\n                             anjay_riid_t riid,\n                             anjay_output_ctx_t *ctx) {\n        (void) anjay;\n        time_object_t *obj = get_obj(obj_ptr);\n\n        pthread_mutex_lock(&obj->mutex);\n        time_instance_t *inst = find_instance(obj, iid);\n        assert(inst);\n        int result;\n        switch (rid) {\n        case RID_CURRENT_TIME: {\n            assert(riid == ANJAY_ID_INVALID);\n            int64_t timestamp;\n            if (avs_time_real_to_scalar(&timestamp, AVS_TIME_S,\n                                        avs_time_real_now())) {\n                result = -1;\n            } else {\n                result = anjay_ret_i64(ctx, timestamp);\n            }\n            break;\n        }\n\n        case RID_APPLICATION_TYPE:\n            assert(riid == ANJAY_ID_INVALID);\n            result = anjay_ret_string(ctx, inst->application_type);\n            break;\n\n        default:\n            result = ANJAY_ERR_METHOD_NOT_ALLOWED;\n        }\n        pthread_mutex_unlock(&obj->mutex);\n        return result;\n    }\n\n    static int resource_write(anjay_t *anjay,\n                              const anjay_dm_object_def_t *const *obj_ptr,\n                              anjay_iid_t iid,\n                              anjay_rid_t rid,\n                              anjay_riid_t riid,\n                              anjay_input_ctx_t *ctx) {\n        (void) anjay;\n        time_object_t *obj = get_obj(obj_ptr);\n\n        pthread_mutex_lock(&obj->mutex);\n        time_instance_t *inst = find_instance(obj, iid);\n        assert(inst);\n        int result;\n        switch (rid) {\n        case RID_APPLICATION_TYPE:\n            assert(riid == ANJAY_ID_INVALID);\n            result = anjay_get_string(ctx, inst->application_type,\n                                      sizeof(inst->application_type));\n            break;\n\n        default:\n            result = ANJAY_ERR_METHOD_NOT_ALLOWED;\n        }\n        pthread_mutex_unlock(&obj->mutex);\n        return result;\n    }\n\n    int transaction_begin(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr) {\n        (void) anjay;\n        time_object_t *obj = get_obj(obj_ptr);\n\n        pthread_mutex_lock(&obj->mutex);\n        time_instance_t *element;\n        AVS_LIST_FOREACH(element, obj->instances) {\n            strcpy(element->application_type_backup, element->application_type);\n        }\n        pthread_mutex_unlock(&obj->mutex);\n        return 0;\n    }\n\n    int transaction_rollback(anjay_t *anjay,\n                             const anjay_dm_object_def_t *const *obj_ptr) {\n        (void) anjay;\n        time_object_t *obj = get_obj(obj_ptr);\n\n        pthread_mutex_lock(&obj->mutex);\n        time_instance_t *element;\n        AVS_LIST_FOREACH(element, obj->instances) {\n            strcpy(element->application_type, element->application_type_backup);\n        }\n        pthread_mutex_unlock(&obj->mutex);\n        return 0;\n    }\n\n    static const anjay_dm_object_def_t OBJ_DEF = {\n        .oid = 3333,\n        .handlers = {\n            .list_instances = list_instances,\n            .instance_create = instance_create,\n            .instance_remove = instance_remove,\n            .instance_reset = instance_reset,\n\n            .list_resources = list_resources,\n            .resource_read = resource_read,\n            .resource_write = resource_write,\n\n            .transaction_begin = transaction_begin,\n            .transaction_validate = anjay_dm_transaction_NOOP,\n            .transaction_commit = anjay_dm_transaction_NOOP,\n            .transaction_rollback = transaction_rollback\n        }\n    };\n\n    const anjay_dm_object_def_t **time_object_create(void) {\n        pthread_mutexattr_t attr;\n        if (pthread_mutexattr_init(&attr)) {\n            return NULL;\n        }\n        // anjay_dm_emit() and anjay_dm_emit_res() may call other handlers,\n        // so we need a recursive mutex\n        pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);\n\n        time_object_t *obj = (time_object_t *) avs_calloc(1, sizeof(time_object_t));\n        if (!obj) {\n            return NULL;\n        }\n        obj->def = &OBJ_DEF;\n\n        if (pthread_mutex_init(&obj->mutex, &attr)) {\n            pthread_mutexattr_destroy(&attr);\n            avs_free(obj);\n            return NULL;\n        }\n\n        pthread_mutexattr_destroy(&attr);\n        pthread_mutex_lock(&obj->mutex);\n        time_instance_t *inst = add_instance(obj, 0);\n        if (inst) {\n            strcpy(inst->application_type, \"Clock 0\");\n        }\n        pthread_mutex_unlock(&obj->mutex);\n\n        if (!inst) {\n            pthread_mutex_destroy(&obj->mutex);\n            avs_free(obj);\n            return NULL;\n        }\n\n        return &obj->def;\n    }\n\n    void time_object_release(const anjay_dm_object_def_t **def) {\n        if (def) {\n            time_object_t *obj = get_obj(def);\n            pthread_mutex_lock(&obj->mutex);\n            AVS_LIST_CLEAR(&obj->instances) {\n                release_instance(obj->instances);\n            }\n            pthread_mutex_unlock(&obj->mutex);\n            pthread_mutex_destroy(&obj->mutex);\n            avs_free(obj);\n        }\n    }\n\n    void time_object_notify(anjay_t *anjay, const anjay_dm_object_def_t **def) {\n        if (!anjay || !def) {\n            return;\n        }\n        time_object_t *obj = get_obj(def);\n        pthread_mutex_lock(&obj->mutex);\n        int64_t current_timestamp;\n        if (!avs_time_real_to_scalar(&current_timestamp, AVS_TIME_S,\n                                     avs_time_real_now())) {\n            AVS_LIST(time_instance_t) it;\n            AVS_LIST_FOREACH(it, obj->instances) {\n                if (it->last_notify_timestamp != current_timestamp) {\n                    if (!anjay_notify_changed(anjay, 3333, it->iid,\n                                              RID_CURRENT_TIME)) {\n                        it->last_notify_timestamp = current_timestamp;\n                    }\n                }\n            }\n        }\n        pthread_mutex_unlock(&obj->mutex);\n    }\n\nMost of the relevant changes are highlighted. Please note that some additional\nrefactoring has been made, mostly to move ``return`` calls out of the blocks\nmarked by ``pthread_mutex_lock()``/``pthread_mutex_unlock()`` call pairs.\n\nNote that a recursive mutex is used here. This is because data model handlers\nmay be called recursively from ``anjay_dm_emit()`` and ``anjay_dm_emit_res()``\nfunctions that are used to return data from the ``list_instances`` and\n``list_resources`` callbacks. Using a simple mutex instead would result in\ndeadlocks in those scenarios.\n\nSimilar extra caution should be taken when using APIs such as\n``anjay_send_batch_data_add_current()`` - that also invokes the relevant data\nmodel callbacks.\n\nRunning the event loop in a separate thread\n-------------------------------------------\n\nLet's now refactor the ``main.c`` file so that it runs the event loop in a\nseparate thread - the main one will then be free to call\n``time_object_notify()`` periodically:\n\n.. highlight:: c\n.. snippet-source:: examples/tutorial/BC-ThreadSafety/src/main.c\n    :caption: main.c\n    :emphasize-lines: 1-2,11-15,80-84,121-133\n\n    #include <pthread.h>\n    #include <unistd.h>\n\n    #include <anjay/anjay.h>\n    #include <anjay/security.h>\n    #include <anjay/server.h>\n    #include <avsystem/commons/avs_log.h>\n\n    #include \"time_object.h\"\n\n    #if !defined(ANJAY_WITH_THREAD_SAFETY) \\\n            || !defined(AVS_COMMONS_SCHED_THREAD_SAFE)\n    #    error \"This example requires Anjay compiled with thread safety enabled\"\n    #endif // !defined(ANJAY_WITH_THREAD_SAFETY) ||\n           // !defined(AVS_COMMONS_SCHED_THREAD_SAFE)\n\n    // Installs Security Object and adds an instance of it.\n    // An instance of Security Object provides information needed to connect to\n    // LwM2M server.\n    static int setup_security_object(anjay_t *anjay) {\n        if (anjay_security_object_install(anjay)) {\n            return -1;\n        }\n\n        static const char PSK_IDENTITY[] = \"identity\";\n        static const char PSK_KEY[] = \"P4s$w0rd\";\n\n        anjay_security_instance_t security_instance = {\n            .ssid = 1,\n            .server_uri = \"coaps://eu.iot.avsystem.cloud:5684\",\n            .security_mode = ANJAY_SECURITY_PSK,\n            .public_cert_or_psk_identity = (const uint8_t *) PSK_IDENTITY,\n            .public_cert_or_psk_identity_size = strlen(PSK_IDENTITY),\n            .private_cert_or_psk_key = (const uint8_t *) PSK_KEY,\n            .private_cert_or_psk_key_size = strlen(PSK_KEY)\n        };\n\n        // Anjay will assign Instance ID automatically\n        anjay_iid_t security_instance_id = ANJAY_ID_INVALID;\n        if (anjay_security_object_add_instance(anjay, &security_instance,\n                                               &security_instance_id)) {\n            return -1;\n        }\n\n        return 0;\n    }\n\n    // Installs Server Object and adds an instance of it.\n    // An instance of Server Object provides the data related to a LwM2M Server.\n    static int setup_server_object(anjay_t *anjay) {\n        if (anjay_server_object_install(anjay)) {\n            return -1;\n        }\n\n        const anjay_server_instance_t server_instance = {\n            // Server Short ID\n            .ssid = 1,\n            // Client will send Update message often than every 60 seconds\n            .lifetime = 60,\n            // Disable Default Minimum Period resource\n            .default_min_period = -1,\n            // Disable Default Maximum Period resource\n            .default_max_period = -1,\n            // Disable Disable Timeout resource\n            .disable_timeout = -1,\n            // Sets preferred transport to UDP\n            .binding = \"U\"\n        };\n\n        // Anjay will assign Instance ID automatically\n        anjay_iid_t server_instance_id = ANJAY_ID_INVALID;\n        if (anjay_server_object_add_instance(anjay, &server_instance,\n                                             &server_instance_id)) {\n            return -1;\n        }\n\n        return 0;\n    }\n\n    static void *event_loop_func(void *anjay) {\n        intptr_t result = anjay_event_loop_run(\n                (anjay_t *) anjay, avs_time_duration_from_scalar(100, AVS_TIME_MS));\n        return (void *) result;\n    }\n\n    int main(int argc, char *argv[]) {\n        if (argc != 2) {\n            avs_log(tutorial, ERROR, \"usage: %s ENDPOINT_NAME\", argv[0]);\n            return -1;\n        }\n\n        const anjay_configuration_t CONFIG = {\n            .endpoint_name = argv[1],\n            .in_buffer_size = 4000,\n            .out_buffer_size = 4000,\n            .msg_cache_size = 4000\n        };\n\n        anjay_t *anjay = anjay_new(&CONFIG);\n        if (!anjay) {\n            avs_log(tutorial, ERROR, \"Could not create Anjay object\");\n            return -1;\n        }\n\n        int result = 0;\n        // Setup necessary objects\n        if (setup_security_object(anjay) || setup_server_object(anjay)) {\n            result = -1;\n        }\n\n        const anjay_dm_object_def_t **time_object = NULL;\n        if (!result) {\n            time_object = time_object_create();\n            if (time_object) {\n                result = anjay_register_object(anjay, time_object);\n            } else {\n                result = -1;\n            }\n        }\n\n        pthread_t event_loop_thread;\n        if (!result) {\n            result = pthread_create(&event_loop_thread, NULL, event_loop_func,\n                                    anjay);\n        }\n\n        if (!result) {\n            // Periodically notify the library about Resource value changes\n            while (true) {\n                sleep(1);\n                time_object_notify(anjay, time_object);\n            }\n        }\n\n        anjay_delete(anjay);\n        time_object_release(time_object);\n        return result;\n    }\n\nNote that ``anjay_event_loop_run()`` and ``time_object_notify()`` (which calls\n``anjay_notify_changed()``) are called in concurrent threads without explicit\nsynchronization. This is entirely permitted as long as thread safety is enabled\nin Anjay at compile time.\n\nPlease also note that the wait period passed to ``anjay_event_loop_run()`` has\nbeen reduced from 1 second in all previous examples to 100 milliseconds here.\nThis is to make the application more responsive - ``anjay_notify_changed()``\ncreates a scheduler job for sending the notification if appropriate; because of\nAnjay's limitations, this might not wake up the event loop thread immediately.\nReducing the wait period reduces the time after which the job will actually be\nexecuted.\n\n.. note::\n\n    Complete code of this example can be found in\n    `examples/tutorial/BC-ThreadSafety` subdirectory of main Anjay project\n    repository.\n"
  },
  {
    "path": "doc/sphinx/source/BasicClient.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nBasic client\n============\n\nIn this tutorial we will focus on creating simple LwM2M Client based on Anjay\nlibrary, featuring:\n\n- secure connection to a LwM2M Server,\n- Object with writable and readable resources.\n\nThis client can be a good starting point for creating more complex applications.\n\n.. toctree::\n   :glob:\n   :titlesonly:\n\n   BasicClient/BC-Initialization\n   BasicClient/BC-MandatoryObjects\n   BasicClient/BC-Security\n   BasicClient/BC-ObjectImplementation\n   BasicClient/BC-Notifications\n   BasicClient/BC-Send\n   BasicClient/BC-ThreadSafety\n"
  },
  {
    "path": "doc/sphinx/source/CommercialFeatures/CF-CorePersistence.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nCore Persistence\n================\n\n.. contents:: :local:\n\nGeneral description\n-------------------\n\nLwM2M protocol, although designed to be as effective as possible in terms of\nbandwidth and energy consumption, features mechanisms which might require\nsending larger amounts of data during connection initialization and client\nregistration processes.\n\nSuch mechanisms include:\n\n* **(D)TLS session handshake** - both parties have to agree upon which\n  ciphersuite will be used, and potentially verify each other's certificates and\n  exchange encryption keys,\n* **LwM2M Register** operation, which contains parameters of the registration\n  such as endpoint name, used LwM2M version, list of present objects, etc.,\n* **LwM2M Observe** operation, which must be issued for every previously\n  configured observation, since the server assumes that the client is not aware\n  of them,\n* **LwM2M Discover** operation, which some servers send immediately for every\n  object manifested in Register operation. It's used to discover present object\n  instances and resources.\n\nIn applications where the device is deactivated most of the time and\ncommunicates with the server infrequently, going through the registration\nprocess before every update is a large communication overhead. One can avoid\nthat by attempting to resume a session, but that requires keeping the library\nalive, which means it is not allowed to disable the device completely, as\napplication memory (RAM) vanishes when it's powered down.\n\nBy using **Core Persistence** feature it is possible to persist core library\nstate to non-volatile storage, allowing a device to shut down completely and\nonce it's up and running again resume the session using state loaded from the\nmemory.\n\nExample savings summary\n^^^^^^^^^^^^^^^^^^^^^^^\n\nTo demonstrate how Core Persistence feature might save bandwidth in cases\ndescribed above, a benchmark of the example application implemented further in\nthis article was done. Table below summarizes how much data was exchanged with\nAVSystem's Coiote DM LwM2M server during initial registration, and later, during\nreconnection with successful resumption. The application was configured using\ndefault settings, with 5 resources observed, all with a single attribute.\n\nInitial connection, no resumption:\n\n+------------------+-----------------------------------+---------------------------+\n| Step             | Packets sent (inbound + outbound) | Sum of UDP packet lengths |\n+==================+===================================+===========================+\n| DTLS handshake   | 6                                 | 1058                      |\n+------------------+-----------------------------------+---------------------------+\n| LwM2M operations | 36                                | 4104                      |\n| (Observe, Read,  |                                   |                           |\n| Discover)        |                                   |                           |\n+------------------+-----------------------------------+---------------------------+\n\nReconnection, successful resumption:\n\n+------------------+-----------------------------------+---------------------------+\n| Step             | Packets sent (inbound + outbound) | Sum of UDP packet lengths |\n+==================+===================================+===========================+\n| DTLS handshake   | 3                                 | 639                       |\n+------------------+-----------------------------------+---------------------------+\n| LwM2M operations | 0                                 | 0                         |\n| (Observe, Read,  |                                   |                           |\n| Discover)        |                                   |                           |\n+------------------+-----------------------------------+---------------------------+\n\nThe difference is **4523 bytes (87.6 % reduction)**.\n\n.. note::\n   Core Persistence must not be used in LwM2M Clients acting as LwM2M Gateway.\n\nTechnical documentation\n-----------------------\n\nIntroduced APIs\n^^^^^^^^^^^^^^^\n\nIf Core Persistence feature is available in your version of Anjay, relevant APIs\ncan be enabled either by defining ``ANJAY_WITH_CORE_PERSISTENCE`` in\n``anjay_config.h`` or, if using CMake, enabling ``WITH_CORE_PERSISTENCE``\noption.\n\nCore Persistence feature introduces two functions:\n\n* `anjay_new_from_core_persistence()\n  <../api/core_8h.html#a6fc17768db5909343831fc04a1dbd8c3>`_, which instantiates\n  Anjay using previously saved client state,\n* `anjay_delete_with_core_persistence()\n  <../api/core_8h.html#a1ad2e0995f6ba822c300ccab819e4526>`_, which deinitializes\n  Anjay and attempts to save its state to given stream.\n\nUsage example\n^^^^^^^^^^^^^\n\n.. note::\n\n   The full code for the following example can be found in the\n   ``examples/commercial-features/CF-CorePersistence`` directory in Anjay\n   sources. Note that to compile and run it, you need to have access to a\n   commercial version of Anjay that includes the Core Persistence feature.\n\nAs an example, we'll modify the code from the\n:doc:`../AdvancedTopics/AT-Persistence` tutorial by adding core state\npersistence capability to the application.\n\nAs core library's state will be saved and loaded in different order than\nAnjay's pre-implemented objects (which are persisted with ``anjay_*_persist()``\nfunctions), it is not possible to use a single stream for all purposes. Two\nseparate files will be used now.\n\n.. important::\n\n   Anjay, as described in :doc:`../AdvancedTopics/AT-Persistence` tutorial,\n   uses generic `stream` (``avs_stream_t``) interface for storage operations.\n   If this feature is used on a platform which doesn't support file operations\n   with standard file API, one must implement that interface on their own, e.g.\n   make use of microcontroller's flash memory. This interface may be either\n   implemented from ground up, or in simple use cases, ``avs_stream_simple_*``\n   methods can be used.\n\n.. highlight:: c\n.. snippet-source:: examples/commercial-features/CF-CorePersistence/src/main.c\n\n    #define OBJECT_PERSISTENCE_FILENAME \"cf-object-persistence.dat\"\n    #define CORE_PERSISTENCE_FILENAME \"cf-core-persistence.dat\"\n\nNext, we'll introduce two helper functions for initialization and\ndeinitialization of Anjay.\n\nIn case any core persistence data is available, we'll try to use that to\ninstantiate Anjay and possibly resume our connection to the server. If the\npersistence file is not accessible or an attempt to use it is unsuccessful, we\nshould fall back to normal `anjay_new()\n<../api/core_8h.html#a077b9b3db59c5b4539271e190508c520>`_ call.\n\n.. highlight:: c\n.. snippet-source:: examples/commercial-features/CF-CorePersistence/src/main.c\n\n    anjay_t *\n    anjay_new_try_from_core_persistence(const anjay_configuration_t *config) {\n        avs_log(tutorial, INFO,\n                \"Attempting to initialize Anjay from core persistence\");\n\n        avs_stream_t *file_stream =\n                avs_stream_file_create(CORE_PERSISTENCE_FILENAME,\n                                       AVS_STREAM_FILE_READ);\n\n        anjay_t *result;\n        if (!file_stream\n                || !(result = anjay_new_from_core_persistence(config,\n                                                              file_stream))) {\n            result = anjay_new(config);\n        }\n\n        avs_stream_cleanup(&file_stream);\n        // remove persistence file to prevent client from reading\n        // outdated state in case it doesn't shut down gracefully\n        unlink(CORE_PERSISTENCE_FILENAME);\n        return result;\n    }\n\nSimilarly, if core persistence file is not accessible due to some error, we\nwant to resort to default `anjay_delete()\n<../api/core_8h.html#a243f18f976bca57b5a7b0714bfb99095>`_ call.\n\n.. highlight:: c\n.. snippet-source:: examples/commercial-features/CF-CorePersistence/src/main.c\n\n    int anjay_delete_try_with_core_persistence(anjay_t *anjay) {\n        avs_log(tutorial, INFO,\n                \"Attempting to shut down Anjay and persist its state\");\n\n        avs_stream_t *file_stream =\n                avs_stream_file_create(CORE_PERSISTENCE_FILENAME,\n                                       AVS_STREAM_FILE_WRITE);\n        if (file_stream) {\n            int result = anjay_delete_with_core_persistence(anjay, file_stream);\n            avs_stream_cleanup(&file_stream);\n            if (result) {\n                unlink(CORE_PERSISTENCE_FILENAME);\n            }\n            return result;\n        } else {\n            anjay_delete(anjay);\n            return -1;\n        }\n    }\n\n.. important::\n   It's worth noting that Core Persistence feature doesn't maintain all of the\n   information about observations - observation parameters are plain LwM2M\n   attributes managed by attribute storage subsystem, thus its state should be\n   persisted too. The relevant code was already implemented in\n   :doc:`../AdvancedTopics/AT-Persistence` tutorial.\n\nSince registration resumption is allowed in Anjay only in case when security\ncontext is reused, we'll convert our example to use PSK security mode.\n\n.. note::\n\n   Technically speaking, `LwM2M TS: Transport Bindings` allows for registration\n   resumption also for NoSec mode, in case the IP address of a client doesn't\n   change. Anjay always assumes that the IP address has changed, as it's\n   generally not possible to reliably determine whether the address visible to\n   the server is still the same; it might be affected by e.g. how routing and\n   Network Address Translation is configured between the parties.\n\n.. highlight:: c\n.. snippet-source:: examples/commercial-features/CF-CorePersistence/src/main.c\n   :emphasize-lines: 2-3, 8-12, 17\n\n    void initialize_objects_with_default_settings(anjay_t *anjay) {\n        static const char PSK_IDENTITY[] = \"identity\";\n        static const char PSK_KEY[] = \"P4s$w0rd\";\n\n        const anjay_security_instance_t security_instance = {\n            .ssid = 1,\n            .server_uri = \"coaps://eu.iot.avsystem.cloud:5684\",\n            .security_mode = ANJAY_SECURITY_PSK,\n            .public_cert_or_psk_identity = (const uint8_t *) PSK_IDENTITY,\n            .public_cert_or_psk_identity_size = strlen(PSK_IDENTITY),\n            .private_cert_or_psk_key = (const uint8_t *) PSK_KEY,\n            .private_cert_or_psk_key_size = strlen(PSK_KEY)\n        };\n\n        const anjay_server_instance_t server_instance = {\n            .ssid = 1,\n            .lifetime = 60,\n            .default_min_period = -1,\n            .default_max_period = -1,\n            .disable_timeout = -1,\n            .binding = \"U\"\n        };\n\n        anjay_iid_t security_instance_id = ANJAY_ID_INVALID;\n        anjay_iid_t server_instance_id = ANJAY_ID_INVALID;\n        anjay_security_object_add_instance(anjay, &security_instance,\n                                           &security_instance_id);\n        anjay_server_object_add_instance(anjay, &server_instance,\n                                         &server_instance_id);\n    }\n\n.. important::\n\n   Please note that the example application uses IPv4 and UDP protocol, thus\n   the `lifetime` parameter is set to a relatively low value to prevent NAT\n   entries in routers between the parties from expiring. After shutting the\n   client down, the registration will expire quickly - practical implementations\n   should update their `lifetime` parameter to some larger value before\n   disconnecting, or use other Layer 3/Layer 4 protocol combination which\n   doesn't require frequent communication with the server.\n\nLet's use all the functions we have implemented above.\n\n.. highlight:: c\n.. snippet-source:: examples/commercial-features/CF-CorePersistence/src/main.c\n   :emphasize-lines: 15, 45-50\n\n    int main(int argc, char *argv[]) {\n        if (argc != 2) {\n            avs_log(tutorial, ERROR, \"usage: %s ENDPOINT_NAME\", argv[0]);\n            return -1;\n        }\n\n        signal(SIGINT, signal_handler);\n\n        const anjay_configuration_t CONFIG = {\n            .endpoint_name = argv[1],\n            .in_buffer_size = 4000,\n            .out_buffer_size = 4000\n        };\n\n        g_anjay = anjay_new_try_from_core_persistence(&CONFIG);\n        if (!g_anjay) {\n            avs_log(tutorial, ERROR, \"Could not create Anjay object\");\n            return -1;\n        }\n\n        int result = -1;\n\n        // Setup necessary objects\n        if (anjay_security_object_install(g_anjay)\n                || anjay_server_object_install(g_anjay)) {\n            goto cleanup;\n        }\n\n        int restore_retval = restore_objects_if_possible(g_anjay);\n        if (restore_retval < 0) {\n            goto cleanup;\n        } else if (restore_retval > 0) {\n            initialize_objects_with_default_settings(g_anjay);\n        }\n\n        result = anjay_event_loop_run(g_anjay,\n                                      avs_time_duration_from_scalar(1, AVS_TIME_S));\n\n        int persist_result = persist_objects(g_anjay);\n        if (!result) {\n            result = persist_result;\n        }\n\n    cleanup:\n        if (result) {\n            anjay_delete(g_anjay);\n        } else {\n            result = anjay_delete_try_with_core_persistence(g_anjay);\n        }\n        return result;\n    }\n\nTo see how this feature affects data sent during the connection, we encourage to\nnot only analyze application's log output, but use some packet analyzer software\nlike Wireshark.\n\n.. important::\n\n   Because Core Persistence feature maintains some data that depends on real\n   time (time at which disabled servers should be reenabled, registration expiry\n   time), this feature requires a properly implemented real clock\n   (``avs_time_real_now()``,\n   :ref:`see porting article <timeapi_avs_time_real_now>`) to work correctly.\n   Also, make sure to synchronize it first (by using e.g. RTC or NTP protocol)\n   before starting the client, otherwise disabled servers will be not reenabled\n   as expected and the registration will be incorrectly deemed to be up to date.\n   Alternatively, if it's not possible to synchronize the clock before, make\n   sure to manually enable those disabled servers back by using\n   ``anjay_enable_server()`` and schedule a registration update using\n   ``anjay_schedule_registration_update()``.\n\n"
  },
  {
    "path": "doc/sphinx/source/CommercialFeatures/CF-CustomHardwareSupport.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nCustom Hardware Support\n=======================\n\nAnjay can be used on several different platforms. By default, the library\ntargets POSIX-like operating systems and their standard interfaces for\nnetworking, multithreading, etc., which means that Anjay can be easily compiled\nto run on many OSs, including:\n\n* Linux,\n* macOS,\n* FreeBSD,\n* Windows (compiled using `MSYS2\n  <https://github.com/AVSystem/Anjay/blob/master/README.Windows.md>`_).\n\nFor embedded platforms, AVSystem provides a couple of integration layers and\nexample applications for many popular SDKs and prototyping kits:\n\n.. list-table::\n   :header-rows: 1\n\n   * - Target platform/SDK\n     - Integration layer\n     - Example application\n   * - `Zephyr <https://zephyrproject.org/>`_ and `nRF Connect SDK\n       <https://www.nordicsemi.com/Products/Development-software/nrf-connect-sdk>`_\n     - `Anjay-zephyr <https://github.com/AVSystem/Anjay-zephyr>`_\n     - `Anjay-zephyr-client <https://github.com/AVSystem/Anjay-zephyr-client>`_\n   * - `STM32Cube\n       <https://www.st.com/content/st_com/en/products/ecosystems/stm32-open-development-environment/stm32cube.html>`_\n       w/ `FreeRTOS <https://www.freertos.org/>`_ and `X-CUBE-CELLULAR\n       <https://www.st.com/en/embedded-software/x-cube-cellular.html>`_\n     - *contained in the app*\n     - `Anjay-freertos-client\n       <https://github.com/AVSystem/Anjay-freertos-client>`_\n   * - `STM32Cube\n       <https://www.st.com/content/st_com/en/products/ecosystems/stm32-open-development-environment/stm32cube.html>`_\n       w/ `Azure RTOS <https://threadx.io/>`_ and `X-CUBE-CELLULAR\n       <https://www.st.com/en/embedded-software/x-cube-cellular.html>`_\n     - *contained in the app*\n     - `Anjay-stm32-azurertos-client\n       <https://github.com/AVSystem/Anjay-stm32-azurertos-client>`_\n   * - `Raspberry Pi Pico SDK <https://github.com/raspberrypi/pico-sdk>`_\n     - *contained in the app*\n     - `Anjay-pico-client <https://github.com/AVSystem/Anjay-pico-client>`_\n\nIf the desired platform isn't listed above, it means that custom implementation for\ntime, threading, networking and (D)TLS APIs **must be provided** as described in\n:doc:`../PortingGuideForNonPOSIXPlatforms`. Integrating with networking\nperipherals and APIs (e.g. cellular modems) is often **non-trivial**, thus\nAVSystem is open to providing help with developing code tailored for a specific\nplatform. In such case, please visit our `contact page\n<https://www.avsystem.com/contact/>`_.\n\nExamples of application aspects that need custom integration with a software platform:\n\n * Device Object that gathers various hardware and software parameters,\n * FOTA integration with platform NVM memory (Flash, EEPROM) drivers and bootloader,\n * Connectivity-related LwM2M Objects implementations that rely on data\n   exchanged with the modem (e.g. Connectivity Monitoring or APN Connection Profile),\n * GPS Location data acquired by GNSS modem,\n * Factory provisioning with PC-based tools that communicate with the device via\n   serial UART port and pre-configure the device with connectivity credentials,\n * Security based on a Hardware Security Module that's not PKCS11 or PSA standards\n   compliant (see: :doc:`HSM Commercial Feature <CF-HSM>`).\n\nDuring the development process, AVSystem can also include your custom hardware in\nCI/CD pipeline to ensure proper performance, configuration and interoperability.\nSuch continuous integration testing is an extension of Custom Hardware Support\nincluded in `Anjay Support Packages <https://avsystem.com/anjay-iot-sdk/features/>`_.\n\nWe can also assist with making many **improvements and optimizations**, even if\nthe basic functionality works out of the box. For example, Anjay-zephyr contains\ncode specific to the nRF9160 SoC which is capable of encrypting network traffic\ndirectly on the modem instead of using an additional (D)TLS library, which normally\nis a part of the application. Thanks to that we were able to completely remove\nmbedTLS library, saving 80 kB of flash memory, and use nRF9160's ability to\ncache (D)TLS sessions over power cycles, greatly reducing the overhead of secure\nconnections.\n"
  },
  {
    "path": "doc/sphinx/source/CommercialFeatures/CF-EST.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nEnrollment over Secure Transport\n================================\n\n.. contents:: :local:\n\nGeneral description\n-------------------\n\n**Enrollment over Secure Transport** (EST, `RFC 7030\n<https://datatracker.ietf.org/doc/html/rfc7030>`_), to quote its specification,\nis a certificate management protocol targeting Public Key Infrastructure (PKI)\nclients that need to acquire client certificates and associated Certification\nAuthority (CA) certificates.  It also supports client-generated public/private\nkey pairs as well as key pairs generated by the CA.\n\n**EST-coaps** (`draft-ietf-ace-coap-est-18\n<https://datatracker.ietf.org/doc/html/draft-ietf-ace-coap-est-18>`_, also\nreferenced in the LwM2M Core TS) is a protocol that uses CoAP instead of HTTP\nfor message exchanges, which allows constrained, low-resource devices to use\nexisting EST functionality for provisioning certificates.\n\nAn implementation of the **EST-coaps client** is provided as a commercial\nfeature in Anjay. This enhances security provided by the (D)TLS Certificate\nmode, by allowing the device to generate a key pair locally, with the private\nkey never leaving the device, as well as allowing the server to update the\ndevice's PKIX trust store without a full firmware update.\n\nBy using the EST-coaps protocol, you can:\n\n* Achieve the highest level of security available for the LwM2M protocol,\n  especially when paired with Hardware Security Module support\n\n  * The chain of trust for client and server certificates can be configured in a\n    more robust way than with the standard Certificate mode\n\n  * Generating client keys on the device means that they do not leak even if the\n    server is compromised\n\n* Make key enrollment procedures in production environments streamlined, simpler\n  and more scalable\n\n  * Only the bootstrap credentials need to be provided at manufacture time,\n    while keys for the main credentials can be provisioned remotely at first use\n\n  * Thanks to standardized means of updating the trust store, including\n    certificate revocation lists, there are more options for updating the\n    security credentials and handling cases of compromised keys\n\nSupported features\n------------------\n\nThe following features are implemented:\n\n* Distribution of CA Certificates (``/est/crts``), supporting both a single raw\n  X.509 certificate and PKCS#7 cert-only as response payload formats\n* Enrollment of Clients (``/est/sen``), supporting both raw X.509 and PKCS#7\n  cert-only as response payload formats\n* Re-enrollment of Clients (``/est/sren``) - the client certificate validity\n  time is tracked and the ``/est/sren`` request is issued automatically when the\n  certificate is nearing expiration, with the exact amount of time being\n  configurable\n* The keys and certificates can be stored in local memory, processed in software\n  and persisted to local non-volatile storage, or processed and stored by the\n  Hardware Security Module (requires HSM integration code, some are available as\n  additional commercial features)\n\nIt is assumed that the same CoAP endpoint is used as both the LwM2M Bootstrap\nServer and the EST-coaps server.\n\nTechnical documentation\n-----------------------\n\nEnabling EST support\n^^^^^^^^^^^^^^^^^^^^\n\nIf support for EST is available in your version of Anjay, it can be enabled at\ncompile time by enabling the ``ANJAY_WITH_EST`` macro in the ``anjay_config.h``\nfile or, if using CMake, enabling the corresponding ``WITH_EST`` CMake option.\n\nWhen enabled at compile time, EST support will be automatically available.\nHowever, for the EST requests to be made, the following conditions must be met:\n\n* LwM2M Bootstrap Server Account must exist; EST requests (other than\n  ``/est/sren``) are only performed after receiving the Bootstrap Finish\n  request over the Bootstrap interface (see also:\n  :doc:`../AdvancedTopics/AT-CustomObjects/AT_CO_BootstrapAwareness`)\n* At least one LwM2M Server Account (provisioned by the Bootstrap Server) must\n  be configured with the Security Mode resource (``/0/*/2``) set to 4\n  (Certificate mode with EST).\n\nPlease note that the EST-coaps specification requires that the connection used\nfor EST requests (which is the Bootstrap Server Account in case of LwM2M) is\nconfigured to use the certificate mode as well. Anjay does not enforce this\nrequirement, but some LwM2M Bootstrap Servers (including Coiote DM from\nAVSystem) may refuse performing EST operations over a transport that does not\nuse certificates. See :doc:`../AdvancedTopics/AT-Certificates` for more\ninformation on configuring certificate-based security.\n\nYou may additionally set the Bootstrap Server Account to use the\n``ANJAY_SECURITY_EST`` mode instead of ``ANJAY_SECURITY_CERTIFICATE``, which\nwill enable the use of EST-provisioned client certificate for subsequent\nconnections to the bootstrap server (the certificate from the data model is used\notherwise).\n\nIn this default configuration, the EST subsystem will use the following\nconfiguration:\n\n* ``/est/sren`` requests are issued 30 days before the certificate expiration\n  date, or after 90% of the time between its provisioning and expiration dates\n  has passed, whichever is later,\n* ``/est/crts`` requests are performed if there is at least one Server Account\n  provisioned to use the EST mode; the resulting trust store is additionally\n  used for subsequent connections with the Bootstrap Server as well,\n* keys and certificates provisioned using EST are stored in local memory and\n  processed in software.\n\nThese settings can be changed when initializing the Anjay object, see the\n:ref:`cf-est-config` section for details.\n\n.. important::\n\n    For EST logic to work properly across restarts of the client application,\n    persistence of EST state needs to be implemented. This is explained in the\n    section below.\n\nPersisting EST state\n^^^^^^^^^^^^^^^^^^^^\n\n.. note::\n\n   The full code for the following example can be found in the\n   ``examples/commercial-features/CF-EST`` directory in Anjay sources. Note that\n   to compile and run it, you need to have access to a commercial version of\n   Anjay that includes the EST feature.\n\nThe EST state needs to be kept in sync with the state of the Security object, so\nit is usually a good idea to store those two blocks of data in the same place.\nThus, the persist and restore routines from the\n:doc:`../AdvancedTopics/AT-Persistence` tutorial can be extended as follows:\n\n.. highlight:: c\n.. snippet-source:: examples/commercial-features/CF-EST/src/main.c\n    :emphasize-lines: 16-19\n\n    if (avs_is_err(anjay_security_object_persist(anjay, file_stream))) {\n        avs_log(tutorial, ERROR, \"Could not persist Security Object\");\n        goto finish;\n    }\n\n    if (avs_is_err(anjay_server_object_persist(anjay, file_stream))) {\n        avs_log(tutorial, ERROR, \"Could not persist Server Object\");\n        goto finish;\n    }\n\n    if (avs_is_err(anjay_attr_storage_persist(anjay, file_stream))) {\n        avs_log(tutorial, ERROR, \"Could not persist LwM2M attribute storage\");\n        goto finish;\n    }\n\n    if (avs_is_err(anjay_est_state_persist(anjay, file_stream))) {\n        avs_log(tutorial, ERROR, \"Could not persist EST state\");\n        goto finish;\n    }\n\n.. highlight:: c\n.. snippet-source:: examples/commercial-features/CF-EST/src/main.c\n    :emphasize-lines: 16-19\n\n    if (avs_is_err(anjay_security_object_restore(anjay, file_stream))) {\n        avs_log(tutorial, ERROR, \"Could not restore Security Object\");\n        goto finish;\n    }\n\n    if (avs_is_err(anjay_server_object_restore(anjay, file_stream))) {\n        avs_log(tutorial, ERROR, \"Could not restore Server Object\");\n        goto finish;\n    }\n\n    if (avs_is_err(anjay_attr_storage_restore(anjay, file_stream))) {\n        avs_log(tutorial, ERROR, \"Could not restore LwM2M attribute storage\");\n        goto finish;\n    }\n\n    if (avs_is_err(anjay_est_state_restore(anjay, file_stream))) {\n        avs_log(tutorial, ERROR, \"Could not restore EST state\");\n        goto finish;\n    }\n\nIn the example, the state is persisted when exiting the application, but it\nmight be a good idea to do it periodically or after each change. The\n`anjay_est_state_is_ready_for_persistence()\n<../api/core_8h.html#a02394aecc7e6c616ced0ed6157da8ff7>`_ function can be useful\nfor this purpose. It is intended to be used in a similar manner to functions\nsuch as `anjay_security_object_is_modified()\n<../api/security_8h.html#a25a9fbd4f84f05a8a2ce50f526c7bc77>`_, however it will\nalso return ``false`` if EST operation is in progress, because in contrast to\ndata model objects, the EST state cannot be rolled back.\n\nThe simplest way to use these functions would be to skip persisting data if\nthere is no new data ready to be persisted:\n\n.. highlight:: c\n.. snippet-source:: examples/commercial-features/CF-EST/src/main.c\n    :emphasize-lines: 4\n\n    if ((!anjay_security_object_is_modified(anjay)\n         && !anjay_server_object_is_modified(anjay)\n         && !anjay_attr_storage_is_modified(anjay))\n            || !anjay_est_state_is_ready_for_persistence(anjay)) {\n        avs_log(tutorial, INFO,\n                \"Persistence not necessary - NOT persisting objects\");\n        return 0;\n    }\n\nHowever, please take note of the following warning:\n\n.. important::\n\n    If ``anjay_est_state_is_ready_for_persistence()`` returns ``false``, the\n    subsequent call to ``anjay_est_state_persist()`` will return an error. This\n    error needs to be handled, and to make sure that *some* valid data is\n    persisted, you should make sure that the original state of the persistence\n    file (or flash memory block, etc.) is restored in that case.\n\n    In the example, this is done by never calling\n    ``anjay_est_state_is_ready_for_persistence()`` in such a situation.\n\n.. important::\n\n    Unless :ref:`HSM-based security <cf-est-hsm>` is used, the persistence\n    stream **will contain cryptographic credentials, including private keys**.\n    Please consider this when choosing the storage location for this data.\n\n.. _cf-est-config:\n\nConfiguring EST behavior\n^^^^^^^^^^^^^^^^^^^^^^^^\n\nThe EST example also contains a sample non-default configuration of the EST\nsubsystem:\n\n.. highlight:: c\n.. snippet-source:: examples/commercial-features/CF-EST/src/main.c\n    :emphasize-lines: 7-14\n\n    const anjay_configuration_t CONFIG = {\n        .endpoint_name = argv[1],\n        .in_buffer_size = 4000,\n        .out_buffer_size = 4000,\n        .msg_cache_size = 4000,\n\n        .trust_store_certs = avs_crypto_certificate_chain_info_from_file(\n                \"/etc/ssl/certs/ca-certificates.crt\"),\n        .est_reenroll_config = &(const anjay_est_reenroll_config_t) {\n            .enable = true,\n            .nominal_usage = 0.8,\n            .max_margin = avs_time_duration_from_scalar(7, AVS_TIME_DAY)\n        },\n        .est_cacerts_policy = ANJAY_EST_CACERTS_FOR_EST_SECURITY\n    };\n\n    g_anjay = anjay_new(&CONFIG);\n\nHere's a quick description of the settings used:\n\n* `trust_store_certs\n  <../api/structanjay__configuration.html#a6eb46a8b375d73a4c2fb609ac17748db>`_\n  is not strictly related to EST, but it can be used to configure the initial\n  trust store for use when the `Certificate Usage field\n  <http://www.openmobilealliance.org/release/LightweightM2M/V1_2-20201110-A/HTML-Version/OMA-TS-LightweightM2M_Transport-V1_2-20201110-A.html#5-2-9-7-0-5297-Certificate-Usage-Field>`_\n  (see also: `anjay_security_instance_t::certificate_usage\n  <../api/structanjay__security__instance__t.html#ac972ac4a1d3ff7ea8501c31f08773d4c>`_)\n  is set to 0 or 1, used before a new one is obtained via ``/est/crts``\n\n  * See also related: `use_system_trust_store\n    <../api/structanjay__configuration.html#a84b693c1bb9e83bbf67a0a9b304d5e88>`_\n    and `trust_store_crls\n    <../api/structanjay__configuration.html#aa0a29edbd09fca4542ac146b39663657>`_\n\n* `est_reenroll_config\n  <../api/structanjay__configuration.html#af360bcadeb344ff6d1b27451920515cc>`_\n  can be set to change the default schedule of performing the ``/est/sren``\n  requests; detailed semantics of its fields are available in the\n  `anjay_est_reenroll_config_t Struct Reference\n  <../api/structanjay__est__reenroll__config__t.html>`_\n\n  * The settings in the example mean that the new certificate is requested\n    7 days before its expiry date, or after 80% of its validity period have\n    passeed, whichever comes later\n\n* `est_cacerts_policy\n  <api/structanjay__configuration.html#aa77f9d5291f4bee4d92efaebe02f5a4c>`_\n  allows changing the details on when the ``/est/crts`` request is performed\n  and what the provisioned trust store is used for; detailed semantics of each\n  available mode is available in the `anjay_est_cacerts_policy_t Enum Reference\n  <../api/core_8h.html#a150879e082c4c2355393dab23bacddba>`_\n\n.. _cf-est-hsm:\n\nUsing EST with hardware security modules\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n.. note::\n\n   The full code for the following example can be found in the\n   ``examples/commercial-features/CF-EST-PKCS11`` directory in Anjay sources.\n   Note that to compile and run it, you need to have access to a commercial\n   version of Anjay that includes the EST and HSM features.\n\nWhen Anjay also has the hardware security module support (available as a\nseparate commercial feature) compiled in, the EST module can easily be\nconfigured to use it for generating and storing the security credentials.\n\nThe variant of the example application that uses PKCS#11 hardware cryptography\nengine specifies additional fields in the ``anjay_configuration_t`` structure:\n\n.. highlight:: c\n.. snippet-source:: examples/commercial-features/CF-EST-PKCS11/src/main.c\n    :emphasize-lines: 20-25\n\n    char EST_CACERTS_ADDRESS_BUF[256];\n\n    srand(time(NULL));\n\n    const anjay_configuration_t CONFIG = {\n        .endpoint_name = argv[1],\n        .in_buffer_size = 4000,\n        .out_buffer_size = 4000,\n        .msg_cache_size = 4000,\n\n        .trust_store_certs = avs_crypto_certificate_chain_info_from_file(\n                \"/etc/ssl/certs/ca-certificates.crt\"),\n        .est_reenroll_config = &(const anjay_est_reenroll_config_t) {\n            .enable = true,\n            .nominal_usage = 0.8,\n            .max_margin = avs_time_duration_from_scalar(7, AVS_TIME_DAY)\n        },\n        .est_cacerts_policy = ANJAY_EST_CACERTS_FOR_EST_SECURITY,\n\n        .est_engine_key_address =\n                \"pkcs11:token=MyToken;object=EstClientKey;pin-value=1234\",\n        .est_engine_cert_address =\n                \"pkcs11:token=MyToken;object=EstClientCert;pin-value=1234\",\n        .est_engine_cacerts_address_gen_cb = est_crts_address_gen,\n        .est_engine_cacerts_address_gen_cb_arg = EST_CACERTS_ADDRESS_BUF\n    };\n\n.. important::\n\n    The \"addresses\" (also sometimes referred to as \"query strings\") in this\n    example are PKCS#11 URIs. However, the format of these strings is\n    dependent on the hardware security engine used. Please refer to the\n    documentation of the module you're using (PKCS#11, PSA, IoT SAFE etc.), or\n    its implementation if you have implemented one yourself.\n\n.. highlight:: none\n\n.. note::\n\n    When the examples are compiled automatically (e.g. using\n    ``make commercial_feature_examples``), the Anjay library is configured to\n    use the PKCS#11 backend.\n\n    To run it, the PKCS#11 module library needs to be specified via the\n    ``PKCS11_MODULE_PATH`` environment variable, for example::\n\n        env PKCS11_MODULE_PATH=/usr/lib/softhsm/libsofthsm2.so ./anjay-est-pkcs11 {endpoint-name}\n\nHere's a quick description of the settings used:\n\n* `est_engine_key_address\n  <../api/structanjay__configuration.html#abbcfd5912e1138f4576509c500ed1a9a>`_\n  specifies the address at which the client's private key will be generated\n  during the EST enrollment process\n\n  * Note: in case of PKCS#11, two objects will be generated, separate for the\n    private and public keys; these will share the same label\n\n* `est_engine_cert_address\n  <../api/structanjay__configuration.html#aecdf44d9bd9d87fbdca6672d6f9aaf11>`_\n  specifies the address at which the client's public certificate will be stored\n  after having been provisioned by the EST server\n\n* `est_engine_cacerts_address_gen_cb\n  <../api/structanjay__configuration.html#abc4fdd9feb2cb58a47408a96308dbf6e>`_\n  is set to a pointer to the function that will be used for generating addresses\n  at which the trusted certificates provisioned via the ``/est/crts`` operation\n  will be stored\n\n  * This generation function is necessary because there might be multiple\n    trusted certificates, so a single address is not sufficient\n\n  * `est_engine_cacerts_address_gen_cb_arg\n    <../api/structanjay__configuration.html#a695689ab4fc248793ad63f631a0c630f>`_\n    can be used to specify any opaque argument that will be passed to that\n    function; in this example it points to a buffer that will be used to store\n    the generated addresses, but you are free to use this argument in any way\n    you wish\n\n  * In this example application, the following function is used:\n\n    .. highlight:: c\n    .. snippet-source:: examples/commercial-features/CF-EST-PKCS11/src/main.c\n\n        static const char *est_crts_address_gen(void *arg,\n                                                const void *x509_der_data,\n                                                size_t x509_der_data_size) {\n            (void) x509_der_data;\n            (void) x509_der_data_size;\n\n            char *buf = (char *) arg;\n            sprintf(buf, \"pkcs11:token=MyToken;object=CaCert%d;pin-value=1234\", rand());\n            return buf;\n        }\n\n  * The example function generates the addresses based on a random number;\n    however, the actual DER-encoded certificate is passed to this function as\n    well so that you may choose to generate the address based on the contents of\n    the certificate\n\n  * The returned string is copied by the library, so reusing the same buffer for\n    multiple calls is OK. See `anjay_est_engine_cacert_address_gen_t\n    <../api/core_8h.html#ae8604feaa669414289ed99d75a03d6fb>`_ for details on\n    this callback's semantics.\n\n.. note::\n\n    You may specify only some of the above arguments, and leave others as\n    ``NULL``. Software handling will be used for any credentials for which\n    hardware engine addresses are not provided.\n\nHSM-based EST and persistence\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nWhen using hardware security engine for EST, the persistence stream will only\ncontain references (addresses) to the security credentials stored there.\n\nThe EST module manages the lifecycle of these credentials, so that old ones are\nautomatically removed when e.g. a new certificate is re-enrolled.\n\n.. important::\n\n    To ensure that the credentials stored on the HSM and the references in the\n    persistence stream are kept in sync, **all EST-related security credentials\n    stored in the HSM will be removed from it** when cleaning up the Anjay\n    object, unless there have been no changes to them since the last call to\n    ``anjay_est_state_persist()`` or ``anjay_est_state_restore()``.\n\n"
  },
  {
    "path": "doc/sphinx/source/CommercialFeatures/CF-FSDM.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nFile System Data Model\n======================\n\n.. contents:: :local:\n\nGeneral description\n-------------------\n\nFile System Data Model feature is implemented in a separate application called\nSvetovid that uses Anjay library for communication with a LwM2M server. Svetovid\nruns on any Linux-based system what makes it ideal for quick prototyping and \nintegration with existing infrastructure. File System Data Model is easy\nto use. After installing Svetovid LwM2M objects can be represented as scripts \non the file system using any programming language. This allows creating LwM2M \nobjects without C/C++ programming knowledge. Additionally the File System Data\nModel comes with a stub generator for Python and shell script to create a LwM2M\nobject template. \n\nSupported features\n------------------\n\nThe following features are implemented:\n\n* Mapping of directory on the file system to LwM2M Objects, Instances and Resources\n* Handling of multi-instance objects\n* FSDM key-value store for keeping volatile object state\n* Object stub generator for Python and sh to get started easily\n\nTechnical documentation\n-----------------------\n\nDirectory mapping\n^^^^^^^^^^^^^^^^^\n\nWhen enabled, File System Data Model maps a specific directory to LwM2M Objects,\nInstances and Resources. Default mapped directory is ``/etc/svetovid/dm`` but it\ncan be changed in the ``/etc/svetovid/config/fsdm.json`` file. The mapped directory\nis expected to have the following structure:\n\n- ``/etc/svetovid/dm/`` (default)\n\n  - ``$OBJECT_ID/`` - directory representing a LwM2M Object with given ID.\n\n    - ``resources/`` - directory containing scripts used to access individual\n      Resources.\n\n      - ``$RESOURCE_ID`` - executable scripts representing individual Resource \n        of a given ID.\n\n    - ``instances`` - (optional) executable script used for managing object\n      instances.\n\n    - ``transaction`` - (optional) executable script used to handle\n      transactional processing of object resources.\n\nInstalling Svetovid\n^^^^^^^^^^^^^^^^^^^\nTo install Svetovid from source and use File System Data Model run those commands:\n\n.. code-block:: bash\n\n    $ ./devconfig -DTARGET_PLATFORM=[TARGET]\n    $ make\n    $ sudo make install\n\n.. note::\n   Use \"generic\" as ``TARGET`` to compile Svetovid on a standard Linux distribution.  \n\n.. note:: \n   Note that this installs a ``svetovid.service`` systemd service, automatically\n   enabled, and starts-up the Client immediately. You may want to disable the \n   service using systemctl command.\n\nConfiguring Svetovid\n^^^^^^^^^^^^^^^^^^^^\nSvetovid is configured using JSONs files. The default location of those JSONs is \n``/etc/svetovid/config``. \n\n.. note::\n   Default configuration directory may be overwritten by passing ``--conf-dir`` as\n   a command line argument when starting a Svetovid binary. \n\nThe configuration is stored in those files: \n\n* ``security.json`` - represents the Security LwM2M object\n* ``server.json`` - represents the Server LwM2M object\n* ``svd.json`` - global settings\n\nExample of ``security.json``:\n\n.. code-block:: json\n\n    {\n        \"2\": {\n                \"server_uri\": \"coaps://eu.iot.avsystem.cloud:5684\",\n                \"security_mode\": \"psk\",\n                \"pubkey_or_identity_hex\": \"6d7a2d737665746f766964\",\n                \"privkey_or_psk_hex\": \"737665746f76696431323334\",\n                \"ssid\": \"7\",\n                \"is_bootstrap\": \"0\"\n        }\n    }\n\nExample of ``server.json``:\n\n.. code-block:: json\n\n    {\n        \"0\": {\n                \"ssid\": \"7\",\n                \"lifetime\": \"60\",\n                \"binding\": \"U\"\n        }\n    }\n\nExample of ``svd.json``:\n\n.. code-block:: json\n\n    {\n        \"device\": {\n                \"endpoint_name\": \"svetovid-example\"\n        },\n        \"logging\": {\n                \"default_log_level\": \"info\",\n                \"log_level\": {\n                    \"svd\": \"debug\"\n                }\n        },\n        \"in_buffer_size_b\": 10240,\n        \"out_buffer_size_b\": 10240,\n        \"msg_cache_size_b\": 65536\n    }\n\n.. note:: \n   For detailed description of configuration files format please refer to the \n   full documentation.\n\nDeveloping custom object example\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nAfter installation Svetovid is run as a service and can be controlled by\nsystemctl\n\nTo implement a Time Object (/3333) you can start by generating a stub\n\n.. code-block:: bash\n\n   $ sudo svetovid-fsdmtool generate --object 3333 --output-dir /etc/svetovid/dm --generator python\n\nThis will create ``/etc/svetovid/dm/3333`` directory containing Python scripts \nthat represent LwM2M resources. The hierarchy of the filesystem mapped to a LwM2M \nobject is:\n\n.. code-block:: bash\n\n    /etc/svetovid/dm/3333\n    ├── Application_Type -> resources/5750\n    ├── Current_Time -> resources/5506\n    ├── Fractional_Time -> resources/5507\n    ├── instances\n    ├── Measurement_Quality_Indicator -> resources/6042\n    ├── Measurement_Quality_Level -> resources/6049\n    └── resources\n        ├── 5506\n        ├── 5507\n        ├── 5750\n        ├── 6042\n        └── 6049\n\nThe scripts generated this way contain placeholders for the ``read``, ``write`` \nand ``reset`` functions that need to be filled out by the user. For example\nthe ``Fraction Time`` (/3333/\\*/5507) resource can be implemented as:\n\n.. code-block:: python\n\n    #!/usr/bin/env python\n    # -*- encoding: utf-8 -*-\n    \n    from fsdm import ResourceHandler, CoapError, DataType, KvStore\n    \n    \n    class ResourceHandler_3333_5506(ResourceHandler):\n        NAME = \"Current Time\"\n        DESCRIPTION = '''\\\n    Unix Time. A signed integer representing the number of seconds since\n     * Jan 1st, 1970 in the UTC time zone.'''\n        DATATYPE = DataType.TIME\n        EXTERNAL_NOTIFY = False\n    \n        def read(self,\n                 instance_id,            # int\n                 resource_instance_id):  # int for multiple resources, None otherwise\n            # It's just that simple!\n            import time\n            sys.stdout.write(str(int(time.time())))\n    \n    \n        def write(self,\n                  instance_id,            # int\n                  resource_instance_id):  # int for multiple resources, None otherwise\n            # NOTE: Implement this if you want to be able to change time on your system.\n            raise CoapError.NOT_IMPLEMENTED\n    \n        def reset(self,\n                  instance_id):  # int\n            # NOTE: reset resource to its original state. You can either set it to\n            # a default value or delete the resource.\n            pass\n    \n    \n    \n    if __name__ == '__main__':\n        ResourceHandler_3333_5506().main()\n\nTo implement ``Application Type`` resource (/3333/\\*/5750) we can use a simple\nimplementation of a key-value store which is accessible from Python via ``KvStore``\nclass. The class implements a simple interface:\n\n* ``KvStore(namespace)`` - constructor that takes ``namespace`` as an argument.\n  This can be set as anything as long as it can be uniquely distinguished between different\n  objects. A good idea is to use an Object ID as a ``namespace`` used by the ``KvStore``.\n* ``get(key, default=None)`` - method for getting a value for a given ``key``.\n  If the value is not present it will return the ``default`` value.\n* ``set(key, value)`` - method for setting a ``value`` for a given ``key``.\n* ``delete(key)`` - method for deleting a ``key`` with associated value from\n  the ``KvStore``.  \n\n\n.. code-block:: python\n\n    #!/usr/bin/env python\n    # -*- encoding: utf-8 -*-\n\n    from fsdm import ResourceHandler, CoapError, DataType, KvStore\n\n    import sys # for sys.stdout.write() and sys.stdin.read()\n\n    class ResourceHandler_3333_5750(ResourceHandler):\n        NAME = \"Application Type\"\n        DESCRIPTION = '''\\\n    The application type of the sensor or actuator as a string depending\n     * on the use case.'''\n        DATATYPE = DataType.STRING\n        EXTERNAL_NOTIFY = False\n\n        def read(self,\n                 instance_id,            # int\n                 resource_instance_id):  # int for multiple resources, None otherwise\n            value = KvStore(namespace=3333).get('application_type')\n            if value is None:\n                # The value was not set, so it's not found.\n                raise CoapError.NOT_FOUND\n\n            # The value is present within the store, thus we can print it on stdout.\n            # The important thing here is to remember to return string-typed resources\n            # with sys.stdout.write(), as print() adds unnecessary newline character, so\n            # if we used it instead, the value presented to the server would contain that\n            # trailing newline character.\n            sys.stdout.write(value)\n\n\n        def write(self,\n                  instance_id,            # int\n                  resource_instance_id):  # int for multiple resources, None otherwise\n            # All we need to do is to assign a value to the application_type key.\n            KvStore(namespace=3333).set('application_type', sys.stdin.read())\n\n\n        def reset(self,\n                  instance_id):  # int\n            # We reset the resource to its original state by simply deleting the application_type\n            # key\n            KvStore(namespace=3333).delete('application_type')\n\n\n\n    if __name__ == '__main__':\n        ResourceHandler_3333_5750().main()\n"
  },
  {
    "path": "doc/sphinx/source/CommercialFeatures/CF-HSM.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nHardware Security Module\n========================\n\n.. contents:: :local:\n\n**Hardware Security Module (HSM)** is a piece of hardware designed to increase\nsecurity of the device by keeping the vulnerable data safe (mainly private\nor secret keys, but also certificates) and performing operations like:\n\n * key generation,\n * signing/verification,\n * encryption/decryption,\n\nwhile the used private and secret keys are not leaving their secure memory.\nBecause such idea might have a lot of various implementations, some generic APIs\nwere created. Commercial feature of Anjay, HSM, includes integrations with two\nof them: `PKCS11 <https://datatracker.ietf.org/doc/html/rfc7512>`_ and\n`PSA <https://developer.arm.com/architectures/architecture-security-features/platform-security>`_.\n\n\nTo increase the safety of the IoT client even more\nHSMs are often used to maintain credentials used in Enrollment over Security\nTransport (EST). To make this easier, when Anjay is used with both HSM and\n:doc:`CF-EST` commercial features it includes also an additional integration\nbetween them, which allows to easily setup such a secure\nclient (see CF-EST-PKCS11 example).\n\n\nSupported features\n------------------\n\nThe following features are implemented:\n\n* integration with *PKCS11* API - working with both *OpenSSL* (using PKI)\n  and *mbed TLS* (using PKI),\n* integration with *Platform Security Architecure (PSA)* API - working with\n  *mbed TLS* (using PKI or PSK),\n* integration with the *EST* feature (see :doc:`CF-EST`) which allows to\n  keep the private keys and certificates used by EST operations.\n\nTechnical documentation\n-----------------------\n\nEnabling Hardware Security Module support\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nThe integrations with the HSM APIs (i.e. PKCS11 and PSA) are available as\nseparate commercial features and the first requirement to make it work is to use\na version of Anjay containing them.\n\nFrom Anjay's perspective PKCS11 and PSA engines are used in quite similar way and\nare considered as backends for the APIs enabled by the corresponding macros:\n\n* ``AVS_COMMONS_WITH_AVS_CRYPTO_PKI_ENGINE`` - for PKI support,\n* ``AVS_COMMONS_WITH_AVS_CRYPTO_PSK_ENGINE`` - for PSK support.\n\nTo enable support for PKCS11 backend one has to enable (depending on which\ncryptographic library is used):\n\n* ``AVS_COMMONS_WITH_MBEDTLS_PKCS11_ENGINE`` - while using mbed TLS,\n* ``AVS_COMMONS_WITH_OPENSSL_PKCS11_ENGINE`` - while using OpenSSL.\n\nIn the case of PSA only mbed TLS support is available, so the proper macro\nis ``AVS_COMMONS_WITH_MBEDTLS_PSA_ENGINE``. Additionally, the used mbed TLS\nversion must be compiled with ``MBEDTLS_USE_PSA_CRYPTO`` flag. If there is\nPSA Protcted Storage API available and you want Anjay to be able to use it, you\nneed also ``AVS_COMMONS_WITH_MBEDTLS_PSA_ENGINE_PROTECTED_STORAGE`` to be\ndefined.\n\n\nAn alternative method to enable a certain integration is enabling the proper\nmacro in the ``avs_commons_config.h`` file (where \"proper\" means that its name\nconsists of ``AVS_COMMONS_`` and corresponding CMake option).\n\n\nThere are also a few macros which can be defined for the support of the\nHSM-stored credentials in Anjay:\n\n* ``ANJAY_WITH_SECURITY_STRUCTURED`` - enable support for handling complex types\n  of security credentials in the data model using structured <c>avs_crypto</c>\n  types. In particular, it allows to keep credentials in the form of their HSM\n  address.\n\n* ``ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT`` - enables the automatic\n  moving to HSM the credentials stored in the built-in Anjay Security object.\n\n* ``ANJAY_WITH_EST_ENGINE_SUPPORT`` - when the EST commercial feature is\n  available, it enables the support for storing on HSM the credentials used\n  during EST operations.\n\nAs before, this macros might be defined directly in ``anjay_config.h`` file,\nor set using CMake.\n\n\nAddressing Hardware Security Module objects\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nObjects in PSA and PKCS11 are addressed in a slightly different ways - in PSA\nan object is called *key* and to access it just its identifier is required,\nwhich is simply an integer. Thus, PSA query used by Anjay consists of a single\nparameter: ``kid=KEY_ID``, where *KEY_ID* is the hex-encoded key identifier.\nWhen PSA Protected Storage API is available and enabled in Anjay (it needs the\nmacro ``AVS_COMMONS_WITH_MBEDTLS_PSA_ENGINE_PROTECTED_STORAGE`` to be defined),\nit may keep raw data addressed with some *ID*. In which case query will be quite\nsimilar: ``uid=ID``.\n\nQueries which are used to address the PKCS11 objects were defined in\n`PKCS11 RFC <https://datatracker.ietf.org/doc/html/rfc7512>`_ as PKCS11 URI.\nThey may contain a lot of various fields, but usually three of them are used in\nAnjay clients: *token*, *pin* and *label* (or *id* instead of label). In such\ncase, the PKCS11 query looks like:\n``pkcs11:token=TOKEN;object=LABEL;pin-value=PIN``.\n\n\nUsing security objects already stored in HSM\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n.. note::\n\n    The full code for the following example can be found in the\n    ``examples/commercial-features/CF-PSA-PSK``,\n    ``examples/commercial-features/CF-PSA-PKI`` and\n    ``examples/commercial-features/CF-PKCS11`` directories in Anjay sources.\n    Note that to compile and run it, you need to have access to\n    a commercial version of Anjay that includes HSM feature.\n\nWhen using HSM to store the Security objects, they shouldn't be stored in the\napplication memory, so they can't be kept in the buffers in the security object\ninstances, so some other kind of structures is needed for them. For this purpose\nan additional set of fields, which are able to store them, was introduced in\n`anjay_security_instance_t <../api/structanjay__security__instance__t.html>`_\n(to make them available ``ANJAY_WITH_SECURITY_STRUCTURED`` macro must be\ndefined):\n\n.. highlight:: c\n.. snippet-source:: include_public/anjay/security.h\n\n    /** Resource: Public Key Or Identity;\n    * This is an alternative to the @p public_cert_or_psk_identity and\n    * @p psk_identity fields that may be used only if @p security_mode is\n    * either @ref ANJAY_SECURITY_CERTIFICATE or @ref ANJAY_SECURITY_EST; it is\n    * also an error to specify non-empty values for more than one of these\n    * fields at the same time. */\n    avs_crypto_certificate_chain_info_t public_cert;\n    /** Resource: Secret Key;\n    * This is an alternative to the @p private_cert_or_psk_key and @ref psk_key\n    * fields that may be used only if @p security_mode is either\n    * @ref ANJAY_SECURITY_CERTIFICATE or @ref ANJAY_SECURITY_EST; it is also an\n    * error to specify non-empty values for more than one of these fields at\n    * the same time. */\n    avs_crypto_private_key_info_t private_key;\n    /** Resource: Public Key Or Identity;\n    * This is an alternative to the @p public_cert_or_psk_identity and\n    * @ref public_cert fields that may be used only if @p security_mode is\n    * @ref ANJAY_SECURITY_PSK; it is also an error to specify non-empty values\n    * for more than one of these fields at the same time. */\n    avs_crypto_psk_identity_info_t psk_identity;\n    /** Resource: Secret Key;\n    * This is an alternative to the @p private_cert_or_psk_key and\n    * @ref private_key fields that may be used only if @p security_mode is\n    * @ref ANJAY_SECURITY_PSK; it is also an error to specify non-empty values\n    * for more than one of these fields at the same time. */\n    avs_crypto_psk_key_info_t psk_key;\n\n\nThere is also a set of functions which can turn an HSM query pointing to the\nrequired object stored on the HSM to the struct which can be used by the\ninstance of the Security object:\n\n* ``avs_crypto_certificate_chain_info_from_engine()`` - creates certificate chain\n  descriptor used later on to load a certificate from the engine,\n\n* ``avs_crypto_private_key_info_from_engine()`` - creates private key descriptor\n  used later on to load private key from the engine,\n\n* ``avs_crypto_psk_key_info_from_engine()`` - creates pre-shared key descriptor\n  used later on to load pre-shared key from the engine,\n\n* ``avs_crypto_psk_identity_info_from_engine()`` - creates pre-shared key identity\n  descriptor used later on to load pre-shared key identity from the engine.\n\n\nOne may notice that the first two of them (as well as first two mentioned\n`anjay_security_instance_t <../api/structanjay__security__instance__t.html>`_\nfields) are used when the connection is secured using PKI, while the latter are\nused with PSK. Let's see how they work with a Security object instance in PKI\nmode in the PKCS11 example:\n\n.. highlight:: c\n.. snippet-source:: examples/commercial-features/CF-PKCS11/src/main.c\n\n    #define KEY_QUERY \"pkcs11:token=MyToken;object=ClientKey;pin-value=1234\"\n    #define CERTIFICATE_QUERY \\\n        \"pkcs11:token=MyToken;object=ClientCert;pin-value=1234\"\n\n    // ...\n\n    const anjay_security_instance_t security_instance = {\n        .ssid = 1,\n        .server_uri = \"coaps://eu.iot.avsystem.cloud:5684\",\n        .security_mode = ANJAY_SECURITY_CERTIFICATE,\n        .public_cert = avs_crypto_certificate_chain_info_from_engine(\n                CERTIFICATE_QUERY),\n        .private_key = avs_crypto_private_key_info_from_engine(KEY_QUERY)\n    };\n\nThe only thing that must be changed to use keys and certificates on HSM which\nuses PSA API are the queries for the key and the certificate:\n\n.. highlight:: c\n.. snippet-source:: examples/commercial-features/CF-PSA-PKI/src/main.c\n\n    #define KEY_QUERY \"kid=0x00000001\"\n    #define CERTIFICATE_QUERY \"kid=0x00000002\"\n\n    // ...\n\n    const anjay_security_instance_t security_instance = {\n        .ssid = 1,\n        .server_uri = \"coaps://eu.iot.avsystem.cloud:5684\",\n        .security_mode = ANJAY_SECURITY_CERTIFICATE,\n        .public_cert = avs_crypto_certificate_chain_info_from_engine(\n                CERTIFICATE_QUERY),\n        .private_key = avs_crypto_private_key_info_from_engine(KEY_QUERY),\n    };\n\nAnd in the similar way, we can use PSA for keeping credentials for the PSK mode\n(CF-PSA-PSK example):\n\n.. highlight:: c\n.. snippet-source:: examples/commercial-features/CF-PSA-PSK/src/main.c\n\n    #define IDENTITY_QUERY \"kid=0x00000001\"\n    #define KEY_QUERY \"kid=0x00000002\"\n\n    // ...\n\n    const anjay_security_instance_t security_instance = {\n        .ssid = 1,\n        .server_uri = \"coaps://eu.iot.avsystem.cloud:5684\",\n        .security_mode = ANJAY_SECURITY_PSK,\n        .psk_identity =\n                avs_crypto_psk_identity_info_from_engine(IDENTITY_QUERY),\n        .psk_key = avs_crypto_psk_key_info_from_engine(KEY_QUERY),\n    };\n\n\nStoring and removing objects from HSM\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n.. note::\n\n    The full code for the following example can be found in the\n    ``examples/commercial-features/CF-PSA-management`` directory in Anjay\n    sources. Note that to compile and run it, you need to have access to\n    a commercial version of Anjay that includes HSM feature.\n\n\nThe avs_commons provides following functions for storing PKI private keys and\ncertificates in the HSM:\n\n* ``avs_crypto_pki_engine_key_store()``,\n\n* ``avs_crypto_pki_engine_certificate_store()``\n\nand corresponding functions for their removal:\n\n* ``avs_crypto_pki_engine_key_rm()``,\n\n* ``avs_crypto_pki_engine_certificate_rm()``.\n\nAn example of how they can be used to manage PKI objects is shown in\nCF-PSA-management example:\n\n.. highlight:: c\n.. snippet-source:: examples/commercial-features/CF-PSA-management/src/main.c\n\n    if (!strcmp(argv[2], \"pkey\")) {\n        if (avs_is_err(avs_crypto_pki_engine_key_rm(query))) {\n            avs_log(tutorial, ERROR, \"Private key removal failed\");\n            return -1;\n        }\n    } else if (!strcmp(argv[2], \"certificate\")) {\n        if (avs_is_err(avs_crypto_pki_engine_certificate_rm(query))) {\n            avs_log(tutorial, ERROR, \"Certificate removal failed\");\n            return -1;\n        }\n    } else if (!strcmp(argv[2], \"psk_key\")) {\n\n    // ...\n\n    if (!strcmp(argv[2], \"pkey\")) {\n        avs_crypto_private_key_info_t key_info =\n                avs_crypto_private_key_info_from_file(argv[4], NULL);\n        if (avs_is_err(avs_crypto_pki_engine_key_store(\n                    query, &key_info, NULL))) {\n            avs_log(tutorial, ERROR, \"Storing private key failed\");\n            return -1;\n        }\n    } else if (!strcmp(argv[2], \"certificate\")) {\n        avs_crypto_certificate_chain_info_t cert_info =\n                avs_crypto_certificate_chain_info_from_file(argv[4]);\n        if (avs_is_err(avs_crypto_pki_engine_certificate_store(\n                    query, &cert_info))) {\n            avs_log(tutorial, ERROR, \"Storing certificate failed\");\n            return -1;\n        }\n    } else if (!strcmp(argv[2], \"psk_key\")) {\n\nAnalogous set of functions is also available for the PSK cryptography:\n\n* ``avs_crypto_psk_engine_key_store()``,\n\n* ``avs_crypto_psk_engine_identity_store()``,\n\n* ``avs_crypto_psk_engine_key_store()`` and\n\n* ``avs_crypto_psk_engine_identity_store()``.\n\nThere is also a similar example of their usage in the CF-PSA-management example.\n\nIn PKI engine API there are also functions for private key generation,\n``avs_crypto_pki_engine_key_gen``, but to use the generated key, we need to\nprepare a certificate for it. This is done typically during the EST enrollment.\nPlease see the EST feature documentation for more information on this topic.\n\nUse the HSM in the implicit way\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n.. note::\n\n    The full code for the following example can be found in the\n    ``examples/commercial-features/CF-PSA-boostrap`` directory in Anjay\n    sources. Note that to compile and run it, you need to have access to\n    a commercial version of Anjay that includes HSM feature.\n\nAn alternative, and probably more elegant, approach to store and use credentials\non HSM is to use the function `anjay_security_object_install_with_hsm\n<../api/security_8h.html#ad7cf8eb206cabb407aad57777dc0a144>`_ to install the\nSecurity object and then use it in the same way as the standard one - it will\nmove the provided credentials to HSM memory. This function, comparing to default\n`anjay_security_object_install\n<../api/security_8h.html#a5fffaeedfc5c2933e58ac1446fd0401d>`_, needs an\nadditional argument - ``hsm_config`` which is basically a set of callbacks (and\ntheir arguments) required to generate the HSM adresses for new HSM objects:\n\n\n.. highlight:: c\n.. snippet-source:: include_public/anjay/security.h\n    :commercial:\n\n    /**\n    * Configuration of the callbacks for generating the query string addresses\n    * under which different kinds of security credentials will be stored on the\n    * hardware security engine.\n    */\n    typedef struct {\n        /**\n        * Callback function that will be called whenever a public client\n        * certificate needs to be stored in an external security engine.\n        *\n        * If NULL, public client certificates will be stored in main system memory\n        * unless explicitly requested via either EST or the <c>public_cert</c>\n        * field in @ref anjay_security_instance_t.\n        */\n        anjay_security_hsm_query_cb_t *public_cert_cb;\n\n        /**\n        * Opaque argument that will be passed to the function configured in the\n        * <c>public_cert_cb</c> field.\n        *\n        * If <c>public_cert_cb</c> is NULL, this field is ignored.\n        */\n        void *public_cert_cb_arg;\n\n        /**\n        * Callback function that will be called whenever a client private key needs\n        * to be stored in an external security engine.\n        *\n        * If NULL, client private keys will be stored in main system memory unless\n        * explicitly requested via either EST or the <c>private_key</c> field in\n        * @ref anjay_security_instance_t.\n        */\n        anjay_security_hsm_query_cb_t *private_key_cb;\n\n        /**\n        * Opaque argument that will be passed to the function configured in the\n        * <c>private_key_cb</c> field.\n        *\n        * If <c>private_key_cb</c> is NULL, this field is ignored.\n        */\n        void *private_key_cb_arg;\n\n        /**\n        * Callback function that will be called whenever a PSK identity for use\n        * with the main connection needs to be stored in an external security\n        * engine.\n        *\n        * If NULL, PSK identities for use with the main connection will be stored\n        * in main system memory unless explicitly requested via the\n        * <c>psk_identity</c> field in @ref anjay_security_instance_t.\n        */\n        anjay_security_hsm_query_cb_t *psk_identity_cb;\n\n        /**\n        * Opaque argument that will be passed to the function configured in the\n        * <c>psk_identity_cb</c> field.\n        *\n        * If <c>psk_identity_cb</c> is NULL, this field is ignored.\n        */\n        void *psk_identity_cb_arg;\n\n        /**\n        * Callback function that will be called whenever a PSK key for use with the\n        * main connection needs to be stored in an external security engine.\n        *\n        * If NULL, PSK keys for use with the main connection will be stored in main\n        * system memory unless explicitly requested via the <c>psk_key</c> field in\n        * @ref anjay_security_instance_t.\n        */\n        anjay_security_hsm_query_cb_t *psk_key_cb;\n\n        /**\n        * Opaque argument that will be passed to the function configured in the\n        * <c>psk_key_cb</c> field.\n        *\n        * If <c>psk_key_cb</c> is NULL, this field is ignored.\n        */\n        void *psk_key_cb_arg;\n    #    ifdef ANJAY_WITH_SMS\n        /**\n        * Callback function that will be called whenever a PSK identity for use\n        * with SMS binding needs to be stored in an external security engine.\n        *\n        * If NULL, PSK identities for use with SMS binding will be stored in main\n        * system memory unless explicitly requested via the <c>sms_psk_identity</c>\n        * field in @ref anjay_security_instance_t.\n        */\n        anjay_security_hsm_query_cb_t *sms_psk_identity_cb;\n\n        /**\n        * Opaque argument that will be passed to the function configured in the\n        * <c>sms_psk_identity_cb</c> field.\n        *\n        * If <c>sms_psk_identity_cb</c> is NULL, this field is ignored.\n        */\n        void *sms_psk_identity_cb_arg;\n\n        /**\n        * Callback function that will be called whenever a PSK key for use with SMS\n        * binding needs to be stored in an external security engine.\n        *\n        * If NULL, PSK keys for use with SMS binding will be stored in main system\n        * memory unless explicitly requested via the <c>sms_psk_key</c> field in\n        * @ref anjay_security_instance_t.\n        */\n        anjay_security_hsm_query_cb_t *sms_psk_key_cb;\n\n        /**\n        * Opaque argument that will be passed to the function configured in the\n        * <c>sms_psk_key_cb</c> field.\n        *\n        * If <c>sms_psk_key_cb</c> is NULL, this field is ignored.\n        */\n        void *sms_psk_key_cb_arg;\n    #    endif // ANJAY_WITH_SMS\n    } anjay_security_hsm_configuration_t;\n\n\nThis approach is particularly useful when using Bootstrap server - in this case\nAnjay will automatically move the credentials received from the Bootstrap server\nto the HSM memory. This is what we can see in action in CF-PSA-boostrap example.\nAs you can see, the changes we need to make are quite subtle:\n\n.. highlight:: c\n.. snippet-source:: examples/commercial-features/CF-PSA-bootstrap/src/main.c\n\n    anjay_security_hsm_configuration_t HSM_CONFIG = {\n        .psk_identity_cb = generate_hsm_address,\n        .psk_key_cb = generate_hsm_address\n    };\n\n    // ...\n\n    if (anjay_security_object_install_with_hsm(anjay, &HSM_CONFIG)) {\n        return -1;\n    }\n\nWhere ``generate_hsm_address`` is a function for PSA address generation, in this\ncase pseudo-random:\n\n.. highlight:: c\n.. snippet-source:: examples/commercial-features/CF-PSA-bootstrap/src/main.c\n\n    static const char *generate_hsm_address(anjay_iid_t iid,\n                                            anjay_ssid_t ssid,\n                                            const void *data,\n                                            size_t data_size,\n                                            void *arg) {\n        (void) iid;\n        (void) ssid;\n        (void) data;\n        (void) data_size;\n        (void) arg;\n\n        static size_t offset = 0ul;\n        static char buffer[1024];\n\n        if (offset + sizeof(HSM_TEMPLATE) > sizeof(buffer)) {\n            avs_log(tutorial, ERROR, \"Wrong HSM address\");\n            return NULL;\n        }\n\n        static avs_rand_seed_t SEED;\n        if (!SEED) {\n            SEED = (avs_rand_seed_t) time(NULL);\n        }\n\n        char *result = buffer + offset;\n        offset += sizeof(HSM_TEMPLATE);\n        strcpy(result, HSM_TEMPLATE);\n\n        for (int i = 0; result[i]; i++) {\n            if (result[i] == '.') {\n                result[i] = HSM_ALPHABET[(size_t) avs_rand_r(&SEED)\n                                        % (sizeof(HSM_ALPHABET) - 1)];\n            }\n        }\n\n        return result;\n    }\n\n\n"
  },
  {
    "path": "doc/sphinx/source/CommercialFeatures/CF-IoTSAFE.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nIoT SAFE\n========\n\nSIM cards can be considered a great way to deliver the data needed for the\ndevice provisioning. They may provide the bootstrap data (used, for example, by\nthe :doc:`CF-SmartCardBootstrap` Anjay commercial feature), but also they are a\ndecent component for generating, storing and using security credentials such as\nkeys and certificates.\n\n\n**IoT SAFE** (`IoT SIM Applet For Secure End-2-End\n<https://www.gsma.com/iot/iot-safe/>`_) was designed as a general\napproach to use SIM card as a root of trust. In particular, it allows the user\nto use a SIM card for:\n\n* providing credentials for (D)TLS authentication using symmetric or asymmetric\n  cryptography,\n\n* generating keys and other security data, being good source of the\n  pseudo-randomness,\n\n* using the stored credentials for the security operations, like singing,\n  verification, encryption and decryption.\n\n\nIoT SAFE commercial feature of Anjay is one of the \"on demand\" commercial\nfeatures, which means that it will be developed for a particular IoT solution.\nIts scope may differ, depending on IoT SAFE middleware used (if any), the\nhardware used and particular customer needs.\n\nThe basic case of using IoT SAFE for credential provisioning is when the\nmanagement server, to which the device will be connected, is known during\nthe SIM card creation process - in such case the provisioned credentials are\njust used to connect to it. If it isn't known the credentials stored\ninitially on the SIM card might be used only to connect to some bootstrap\nserver which provides the proper credentials to the management server - the\nsafest solution, in such case, is to use EST (see :doc:`CF-EST`) together with\non-SIM keypair generation, to create a new certificate for the device, signed\nby the SIM provider's bootstrap server.\n\n.. note::\n\n    When the IoT SAFE integration is included in the project, it is controlled\n    using the same APIs as the :doc:`CF-HSM` feature, simply with a different\n    format of the query strings. In fact, some IoT SAFE middlewares may provide\n    access to the card's capabilities using the PSA or PKCS#11 API, in which\n    case it might be possible to use the HSM feature directly.\n"
  },
  {
    "path": "doc/sphinx/source/CommercialFeatures/CF-NIDD.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nNon-IP Data Delivery\n====================\n\n.. contents:: :local:\n\nGeneral description\n-------------------\n\nUsing Non-IP Data Delivery (NIDD) feature allows for efficient communication\nbetween IoT devices and a LwM2M server. The communication is performed through\nthe cellular network that ensures security while reducing transmission overhead\nrequired by a classic network stack (IP, TCP and UDP protocols), still\nallowing to send up to 1500 bytes in a single transmission. Utilizing an IP\nprotocol stack for data delivery is considered to be a power hungry process. By\nusing Non-IP Data Delivery instead, the device can reduce its power consumption.\n\nNon-IP Data Delivery feature provides easy support for different modem devices by\nallowing the user to define device wrappers that will be used to communicate\nwith the hardware. Anjay comes with one implementation of such device wrapper\nfor the Quectel BG96 modem.\n\nTechnical documentation\n-----------------------\n\nEnabling NIDD support\n^^^^^^^^^^^^^^^^^^^^^\n\nIf Non-IP Data Delivery support is available in your version of Anjay, it can be enabled at\ncompile time by enabling the ``ANJAY_WITH_NIDD`` macro in the ``anjay_config.h``\nfile or, if using CMake, enabling the corresponding ``WITH_NIDD`` CMake option.\n\n.. note::\n\n   The example in this documentation uses the BG96 wrapper for the NIDD driver\n   that must be enabled at compile time by setting the ``ANJAY_WITH_MODULE_BG96_NIDD``\n   macro in the ``anjay_config.h`` file or by using ``WITH_MODULE_bg96_nidd`` CMake option.\n\nUsage example\n^^^^^^^^^^^^^\n\n.. note::\n\n    The full code for the following example can be found in the\n    ``examples/commercial-features/CF-NIDD`` directory in Anjay sources. Note\n    that to compile and run it, you need to have access to a commercial version\n    of Anjay that includes the Non-IP Data Delivery feature.\n\nIn this example we will use a BG96 wrapper for an NIDD driver that is part of Anjay.\nTo run this example on a Linux-based system you will need to have a\n`Quectel BG96 <https://www.quectel.com/product/lpwa-bg96-cat-m1-nb1-egprs>`_ modem connected\nto your PC or utilize the Python script found in `tests/integration/framework/framework/nidd/modem.py`\nthat will simulate BG96 modem and act as a proxy to connect to a LwM2M server.\n\n.. note::\n    The Python `modem.py` script creates a pseudo-terminal device which acts like\n    an AT modem. NIDD network requests generated by Anjay are sent over UDP socket.\n\n.. note::\n    To set up the proxy with the Python script run ``./modem.py --host HOST_IP_ADDRESS --port PORT_NUMBER``\n    command before launching the example. After starting the script it will display\n    the path of the device that simulates the a BG96 modem.\n\nFirstly we will introduce a helper function that will open the device associated\nwith the modem proxy and create an NIDD driver for communication.\n\n.. highlight:: c\n.. snippet-source:: examples/commercial-features/CF-NIDD/src/nidd_demo_driver.c\n   :emphasize-lines: 1\n\n    anjay_nidd_driver_t **demo_nidd_driver_create(const char *modem_device) {\n        demo_nidd_driver_t *driver =\n                (demo_nidd_driver_t *) avs_calloc(1, sizeof(*driver));\n        if (!driver) {\n            return NULL;\n        }\n\n        const anjay_bg96_nidd_config_t config = {\n            .system_descriptor = &driver->pts_fd,\n            .user_context = driver,\n            .modem_getline = modem_getline,\n            .modem_write = modem_write,\n            .modem_get_parameter = modem_get_parameter\n        };\n        if (fifo_init(&driver->fifo)) {\n            avs_log(tutorial, ERROR, \"could not initialize FIFO\");\n            goto fail;\n        }\n        if ((driver->pts_fd = open(modem_device, O_RDWR)) < 0) {\n            avs_log(tutorial, ERROR, \"could not open modem device %s: %s\",\n                    modem_device, strerror(errno));\n            goto fail;\n        }\n\n        if (!(driver->bg96_nidd = anjay_bg96_nidd_driver_create(&config))) {\n            avs_log(tutorial, ERROR, \"could not create AT NIDD driver\");\n            goto fail;\n        }\n        return &driver->bg96_nidd;\n\n    fail:\n        driver_cleanup(driver);\n        return NULL;\n    }\n\n.. important::\n\n   If the user does not use the BG96 driver delivered with Anjay, a set of functions\n   has to be to implemented that will allow communication with the modem device.\n   More information about the needed functions can be found here:\n\n\n   * `NIDD callback functions <../api/nidd_8h.html>`_\n   * `NIDD driver structure <../api/structanjay__nidd__driver__struct.html>`_\n\nThis function calls ``anjay_bg96_nidd_driver_create(...)`` function that fills\n``anjay_nidd_driver_t`` structure with callback functions used to integrate with\nBG96 modem. The structure with the callback functions containing implementation\nof modem integration layer is returned on success. We call this function before\nfilling ``anjay_configuration_t`` structure and passing the returned pointer to\n``.nidd_driver``.\n\n.. highlight:: c\n.. snippet-source:: examples/commercial-features/CF-NIDD/src/main.c\n   :emphasize-lines: 19\n\n    int main(int argc, char *argv[]) {\n        if (argc != 3) {\n            avs_log(tutorial, ERROR, \"usage: %s ENDPOINT_NAME MODEM_PATH\", argv[0]);\n            return -1;\n        }\n\n        anjay_nidd_driver_t **demo_nidd_driver = demo_nidd_driver_create(argv[2]);\n\n        if (!demo_nidd_driver) {\n            avs_log(tutorial, ERROR, \"Could not create NIDD driver\");\n            return -1;\n        }\n\n        const anjay_configuration_t CONFIG = {\n            .endpoint_name = argv[1],\n            .in_buffer_size = 4000,\n            .out_buffer_size = 4000,\n            .msg_cache_size = 4000,\n            .nidd_driver = *demo_nidd_driver\n        };\n\n        anjay_t *anjay = anjay_new(&CONFIG);\n        if (!anjay) {\n            avs_log(tutorial, ERROR, \"Could not create Anjay object\");\n            return -1;\n        }\n\n        int result = 0;\n        // Setup necessary objects\n        if (setup_security_object(anjay) || setup_server_object(anjay)) {\n            result = -1;\n        }\n\n        if (!result) {\n            result = anjay_event_loop_run(\n                    anjay, avs_time_duration_from_scalar(1, AVS_TIME_S));\n        }\n\n        anjay_delete(anjay);\n        demo_nidd_driver_cleanup(demo_nidd_driver);\n        return result;\n    }\n\n.. important::\n   If ``nidd_driver`` is not ``NULL``, it will enable NIDD transport.\n   If Anjay configuration used does not support Non-IP Data Delivery feature, setting this to\n   a non-NULL will cause an error.\n\nBefore calling ``anjay_event_loop_run`` we need to set up Security and Server objects.\n\n.. highlight:: c\n.. snippet-source:: examples/commercial-features/CF-NIDD/src/main.c\n   :emphasize-lines: 8-9, 40-41\n\n    static int setup_security_object(anjay_t *anjay) {\n        if (anjay_security_object_install(anjay)) {\n            return -1;\n        }\n\n        const anjay_security_instance_t security_instance = {\n            .ssid = 1,\n            .server_uri = \"coap+nidd://\",\n            .security_mode = ANJAY_SECURITY_NOSEC\n        };\n\n        // Anjay will assign Instance ID automatically\n        anjay_iid_t security_instance_id = ANJAY_ID_INVALID;\n        if (anjay_security_object_add_instance(anjay, &security_instance,\n                                               &security_instance_id)) {\n            return -1;\n        }\n\n        return 0;\n    }\n\n    // Installs Server Object and adds an instance of it.\n    // An instance of Server Object provides the data related to a LwM2M Server.\n    static int setup_server_object(anjay_t *anjay) {\n        if (anjay_server_object_install(anjay)) {\n            return -1;\n        }\n\n        const anjay_server_instance_t server_instance = {\n            // Server Short ID\n            .ssid = 1,\n            // Client will send Update message often than every 60 seconds\n            .lifetime = 60,\n            // Disable Default Minimum Period resource\n            .default_min_period = -1,\n            // Disable Default Maximum Period resource\n            .default_max_period = -1,\n            // Disable Disable Timeout resource\n            .disable_timeout = -1,\n            // Sets preferred transport to NIDD\n            .binding = \"N\"\n        };\n\n        // Anjay will assign Instance ID automatically\n        anjay_iid_t server_instance_id = ANJAY_ID_INVALID;\n        if (anjay_server_object_add_instance(anjay, &server_instance,\n                                             &server_instance_id)) {\n            return -1;\n        }\n\n        return 0;\n    }\n\nServer URI should be set to ``coap+nidd://``, security mode should be ``ANJAY_SECURITY_NOSEC``\nand binding in server object should be set to 'N'.\n\n"
  },
  {
    "path": "doc/sphinx/source/CommercialFeatures/CF-OSCORE.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nOSCORE\n======\n\n.. contents:: :local:\n\nGeneral description\n-------------------\n\n**Object Security for Constrained RESTful Environments** (OSCORE, `RFC 8613\n<https://datatracker.ietf.org/doc/html/rfc8613>`_) is a security protocol\nprotecting CoAP requests and responses using CBOR Object Signing and Encryption\n(COSE, `RFC 8152 <https://datatracker.ietf.org/doc/html/rfc8152>`_). It provides a\nhigh standard communication security while remaining lightweight. Unlike DTLS\nand TLS, OSCORE is designed for resource-constrained devices. Encrypting only the\nmessage payload (and parts of the header) on the application layer allows to achieve:\n\n* **Less power consumption** due to less data being processed by time-consuming\n  cryptographic algorithms and shorter messages being transmitted,\n\n* **Smaller memory utilization** by the software protocol stack,\n\n* **Shorter computing time** that enhances device responsiveness,\n\n* **End-to-end** security without data being unencrypted and re-encrypted by\n  the network gateways,\n\n* **Flexibility** in transport protocols selection since OSCORE works with UDP,\n  TCP, :doc:`SMS<CF-SMSBinding>` and :doc:`NIDD<CF-NIDD>`.\n\nOSCORE encryption covers message payload and Request/Response Code. Most CoAP\nheader fields (i.e. the message fields in the fixed 4-byte header) are required\nto be read and/or changed by CoAP proxies, so, in general, they can not be protected\nend-to-end if proxy support is required. Nevertheless, there is no hop-by-hop\ninformation encryption (which takes place in (D)TLS proxies) and only the pre-authorized\nendpoint (either target device or LwM2M Server) is able to decipher the message entirely.\nEven if the network becomes compromised, the data remains secure.\n\nIn solutions where the security is top-priority, OSCORE can be used together with\n(D)TLS to attain double encryption realized on different protocol stack layers.\nOSCORE commercial feature in Anjay comes mostly as an extension to ``avs_coap``\nsubmodule and as LwM2M OSCORE Object implementation (OSCORE module). It gives an\nalternative to the security provided by the Transport Layer Protocols (TLS/DTLS)\nor enhances it by providing additional encryption on the Application Layer and by\ncovering additional message frame fields.\n\nKeying material stored in the OSCORE Object can only be set in the Bootstrap phase,\nthat is, during a Factory Bootstrap, by an LwM2M Bootstrap-Server or by a\n:doc:`CF-SmartCardBootstrap`.\n\n\nTechnical documentation\n-----------------------\n\nEnabling OSCORE support\n^^^^^^^^^^^^^^^^^^^^^^^\n\nIf support for OSCORE is available in your version of Anjay, it can be enabled at\ncompile-time by enabling the ``WITH_AVS_COAP_OSCORE`` macro in the\n``avs_coap_config.h`` file or, if using CMake, by enabling the corresponding\n``WITH_COAP_OSCORE`` CMake option.\n\nThere is also a possibility to use CoAP as defined in `draft-ietf-core-object-security-08\n<https://datatracker.ietf.org/doc/html/draft-ietf-core-object-security-08>`_ (which is\nreferenced in the LwM2M 1.1 Technical Specifications). There is a minor difference\nbetween the implementation of the protocol in this draft and the final version,\nso if you want to be fully compliant with the LwM2M 1.1 standard, you might want to\nuse it. To achieve that, enable the ``WITH_AVS_COAP_OSCORE_DRAFT_8`` macro in the\n``avs_coap_config.h`` file or, if using CMake, enable the corresponding\n``WITH_AVS_COAP_OSCORE_DRAFT_8`` CMake option.\n\nAnjay provides a pre-implemented OSCORE Object module. You can enable it at compile-time\nby enabling ``ANJAY_WITH_MODULE_OSCORE`` macro in the ``anjay_config.h`` file or,\nif using CMake, by enabling the corresponding ``WITH_MODULE_oscore`` CMake option.\nIt is not mandatory to use Anjay's OSCORE Object implementation. However, this article\nand example will focus on using it.\n\n.. note::\n    To provide your own object implementation, you need to prepare handler\n    functions similarly to :doc:`/AdvancedTopics/AT-CustomObjects`.\n\n\nEncryption and decryption backend\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nOSCORE extension in ``avs_coap`` module does not implement any encryption algorithms.\nIt relies on a cryptographic library used as a (D)TLS backend selected via CMake option\nat the compile time.\n\n.. important::\n\n    | The cryptographic algorithms used in the protocol are defined by default values.\n    | The AEAD Algorithm used is AES-CCM-16-64-128.\n    | The HMAC Algorithm used is HKDF SHA-256.\n\n.. important::\n    If a custom (D)TLS backend library is used, make sure it supports AEAD and HMAC\n    Algorithms mentioned above.\n\nThe cryptographic algorithms are used to encrypt the CoAP message payload.\nCoAP message code is protected by writing the original value into an encrypted COSE object\nand setting a valid but not relevant code into the CoAP header.\n\n\nInstalling and configuring OSCORE Object\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nIf OSCORE is enabled and used, OSCORE Object Instance that holds keying parameters\napplied to communication with a specific server has to be linked in the corresponding\nSecurity Object Instance. The object link is realized by a proper setting \n``anjay_security_instance_t.oscore_iid`` field during Security Object Instance creation.\n\n.. highlight:: c\n.. snippet-source:: examples/commercial-features/CF-OSCORE/src/main.c\n   :emphasize-lines: 1-12, 18\n\n    anjay_oscore_instance_t oscore_instance = {\n        .master_secret = \"Ma$T3Rs3CR3t\",\n        .master_salt = \"Ma$T3Rs4LT\",\n        .sender_id = \"15\",\n        .recipient_id = \"25\"\n    };\n\n    anjay_iid_t oscore_instance_id = ANJAY_ID_INVALID;\n    if (anjay_oscore_add_instance(anjay, &oscore_instance,\n                                  &oscore_instance_id)) {\n        return -1;\n    }\n\n    anjay_security_instance_t security_instance = {\n        .ssid = 1,\n        .server_uri = \"coap://eu.iot.avsystem.cloud:5683\",\n        .security_mode = ANJAY_SECURITY_NOSEC,\n        .oscore_iid = &oscore_instance_id\n    };\n\n    anjay_iid_t security_instance_id = ANJAY_ID_INVALID;\n    if (anjay_security_object_add_instance(anjay, &security_instance,\n                                           &security_instance_id)) {\n        return -1;\n    }\n\n\nPersisting OSCORE state\n^^^^^^^^^^^^^^^^^^^^^^^\n\nThe OSCORE state can be persisted and restored similarly to other\nAnjay's pre-implemented objects. Let's reuse and extend\n:doc:`../AdvancedTopics/AT-Persistence` tutorial to provide an example.\n\n.. note::\n   When calling ``anjay_security_object_restore()``, a check is performed\n   if a linked OSCORE Object Instance ID is a valid entry in the Data Model.\n   Therefore the OSCORE Object has to be restored first.\n\n.. highlight:: c\n.. snippet-source:: examples/commercial-features/CF-OSCORE/src/main.c\n    :emphasize-lines: 1-4\n\n    if (avs_is_err(anjay_oscore_object_persist(anjay, file_stream))) {\n        avs_log(tutorial, ERROR, \"Could not persist OSCORE Object\");\n        goto finish;\n    }\n\n    if (avs_is_err(anjay_security_object_persist(anjay, file_stream))) {\n        avs_log(tutorial, ERROR, \"Could not persist Security Object\");\n        goto finish;\n    }\n\n    if (avs_is_err(anjay_server_object_persist(anjay, file_stream))) {\n        avs_log(tutorial, ERROR, \"Could not persist Server Object\");\n        goto finish;\n    }\n\n.. highlight:: c\n.. snippet-source:: examples/commercial-features/CF-OSCORE/src/main.c\n    :emphasize-lines: 1-4\n\n    if (avs_is_err(anjay_oscore_object_restore(anjay, file_stream))) {\n        avs_log(tutorial, ERROR, \"Could not restore OSCORE Object\");\n        goto finish;\n    }\n\n    if (avs_is_err(anjay_security_object_restore(anjay, file_stream))) {\n        avs_log(tutorial, ERROR, \"Could not restore Security Object\");\n        goto finish;\n    }\n\n    if (avs_is_err(anjay_server_object_restore(anjay, file_stream))) {\n        avs_log(tutorial, ERROR, \"Could not restore Server Object\");\n        goto finish;\n    }\n\n.. note::\n\n   The full code for the following example can be found in the\n   ``examples/commercial-features/CF-OSCORE`` directory in Anjay sources. Note that\n   to compile and run it, you need to have access to a commercial version of\n   Anjay that includes the OSCORE feature.\n\n.. important::\n\n    OSCORE support in Coiote DM LwM2M Server is currently a **work in progress**. \n    Provided example based on connection with EU Cloud Coiote DM instance is\n    only a demonstration that **will not yet work** out of the box.\n\n"
  },
  {
    "path": "doc/sphinx/source/CommercialFeatures/CF-SMSBinding.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nSMS Binding\n===========\n\n.. contents:: :local:\n\nGeneral description\n-------------------\n\nOne of LwM2M's advantages is the possibility to choose from numerous underlying\nprotocol stacks. Although most applications use CoAP over UDP,\n`LwM2M TS: Transport Bindings` document specifies how LwM2M messages can be\nconveyed over other protocols, like HTTP, MQTT, TCP, NIDD, or SMS. Such a wide\nchoice increases flexibility of LwM2M, making it the right choice in a number of\ndifferent applications.\n\nAnjay's **SMS Binding** feature incorporates a feature-complete implementation\nof SMS binding as specified in the LwM2M specification. Thanks to that, you can:\n\n* use SMS as a sole transportation method to be able to deploy LwM2M devices in\n  areas where connectivity over IP is unavailable or economically unjustified,\n* integrate Anjay with already existing devices which are not capable of\n  internet communication,\n* use SMS together with UDP, either as an alternative binding to increase the\n  reliability of the connection, or save costs and battery usage by using SMS\n  triggers to wake up the device and then bring on the connection over UDP.\n\nThe implementation is also **interoperable with DTLS** and supports\n**Concatenated SMS (CSMS)**, both for the inbound and outbound messages, in case\nthe modem used doesn't have support for that. With CSMS enabled it's possible to\nsend payloads up to 34170 bytes, without any fragmentation on CoAP level.\n\n**SMS Binding** feature also provides a couple of utilities for developers:\n\n* sample implementation of an SMS driver, which uses the standard AT command set\n  to send, receive and manage SMS messages and communicates over a serial port\n  with **any standards-compliant cellular modem**,\n* utility Python script, which **simulates an AT modem** and acts as a proxy -\n  all messages are conveyed further over UDP to the target server.\n\nTechnical documentation\n-----------------------\n\nEnabling SMS binding support\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nIf **SMS Binding** feature is available in your version of Anjay, the support\nfor SMS transport can be enabled by either defining ``ANJAY_WITH_SMS`` in\n``anjay_config.h`` or, if using CMake, enabling ``WITH_SMS`` option.\n\nYou may also want to enable the ``ANJAY_WITH_SMS_MULTIPART`` macro, which\nenables support for Concatenated SMS, both for incoming and outgoing traffic\n(for outbound traffic it can also be configured in runtime). Using Concatenated\nSMS raises the MTU reported to upper layers, which may solve issues with some\ntypes of CoAP or DTLS messages that can be larger than 140 bytes. This setting\nshould be also disabled for modems, which are capable of handling CSMS on their\nown.\n\nAnjay, similarly to how network sockets are implemented, uses an abstraction\nlayer for SMS connections to remain hardware agnostic. It's the developers'\nresponsibility to implement a driver which handles specific hardware, although\nthe library comes with a basic reference implementation for AT modems, which\ncommunicates with them over a serial port. To compile it in, please define\n``ANJAY_WITH_MODULE_AT_SMS`` or enable ``WITH_MODULE_at_sms`` CMake option.\n\nUsage example\n^^^^^^^^^^^^^\n\n.. important::\n\n   For simplicity, we'll also use the example SMS driver implementation for AT\n   modems through the whole tutorial. You can find more information about\n   features and limitations of that implementation in\n   `driver's documentation <../api/at__sms_8h.html>`_.\n\n   To make those examples work out-of-the-box with the virtual modem/SMS proxy\n   script, client's and server's phone numbers will be set to the default values\n   included in the script.\n\nSimple, unsecured connection\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\n.. note::\n\n   The full code for the following example can be found in the\n   ``examples/commercial-features/CF-SMS`` directory in Anjay sources. Note that\n   to compile and run it, you need to have access to a commercial version of\n   Anjay that includes the SMS binding feature.\n\nAs an example, we'll modify the code from the\n:doc:`../BasicClient/BC-MandatoryObjects` tutorial.\n\nTo connect to the server over SMS, we have to make a couple of little changes\nto the original application.\n\nSince the server is reachable by phone number, not by an internet address, we\nhave to reconfigure the LwM2M Security object - server URI should be changed\nand the security mode for the SMS binding should be specified.\n\n.. highlight:: c\n.. snippet-source:: examples/commercial-features/CF-SMS/src/main.c\n   :emphasize-lines: 8, 10\n\n    static int setup_security_object(anjay_t *anjay) {\n        if (anjay_security_object_install(anjay)) {\n            return -1;\n        }\n\n        const anjay_security_instance_t security_instance = {\n            .ssid = 1,\n            .server_uri = \"tel:+12125550178\",\n            .security_mode = ANJAY_SECURITY_NOSEC,\n            .sms_security_mode = ANJAY_SMS_SECURITY_NOSEC\n        };\n\n        // Anjay will assign Instance ID automatically\n        anjay_iid_t security_instance_id = ANJAY_ID_INVALID;\n        if (anjay_security_object_add_instance(anjay, &security_instance,\n                                               &security_instance_id)) {\n            return -1;\n        }\n\n        return 0;\n    }\n\nNext up, we should update the preferred binding information in the configuration\nof LwM2M Server object.\n\n.. highlight:: c\n.. snippet-source:: examples/commercial-features/CF-SMS/src/main.c\n   :emphasize-lines: 17, 18\n\n    static int setup_server_object(anjay_t *anjay) {\n        if (anjay_server_object_install(anjay)) {\n            return -1;\n        }\n\n        const anjay_server_instance_t server_instance = {\n            // Server Short ID\n            .ssid = 1,\n            // Client will send Update message often than every 60 seconds\n            .lifetime = 60,\n            // Disable Default Minimum Period resource\n            .default_min_period = -1,\n            // Disable Default Maximum Period resource\n            .default_max_period = -1,\n            // Disable Disable Timeout resource\n            .disable_timeout = -1,\n            // Sets preferred transport to SMS\n            .binding = \"S\"\n        };\n\n        // Anjay will assign Instance ID automatically\n        anjay_iid_t server_instance_id = ANJAY_ID_INVALID;\n        if (anjay_server_object_add_instance(anjay, &server_instance,\n                                             &server_instance_id)) {\n            return -1;\n        }\n\n        return 0;\n    }\n\nAs the example SMS driver expects a path to a file representing an AT terminal,\nwe'll accept the path as an application's argument and instantiate the driver.\nClient's phone number is a part of the registration message, thus it must be\nincluded in the config structure.\n\n.. note::\n\n   Technically speaking, the library expects a MSISDN, which is a country\n   code-prefixed phone number without the '+' sign or other prefixes specific\n   to your location.\n\n.. highlight:: c\n.. snippet-source:: examples/commercial-features/CF-SMS/src/main.c\n   :emphasize-lines: 2-4, 13-14\n\n    int main(int argc, char *argv[]) {\n        if (argc != 3) {\n            avs_log(tutorial, ERROR, \"usage: %s ENDPOINT_NAME MODEM_DEVICE\",\n                    argv[0]);\n            return -1;\n        }\n\n        const anjay_configuration_t CONFIG = {\n            .endpoint_name = argv[1],\n            .in_buffer_size = 4000,\n            .out_buffer_size = 4000,\n            .msg_cache_size = 4000,\n            .sms_driver = anjay_at_sms_create(argv[2]),\n            .local_msisdn = \"14155550125\"\n        };\n\n        anjay_t *anjay = anjay_new(&CONFIG);\n        if (!anjay) {\n            avs_log(tutorial, ERROR, \"Could not create Anjay object\");\n            return -1;\n        }\n\n        int result = 0;\n        // Setup necessary objects\n        if (setup_security_object(anjay) || setup_server_object(anjay)) {\n            result = -1;\n        }\n\n        if (!result) {\n            result = anjay_event_loop_run(\n                    anjay, avs_time_duration_from_scalar(1, AVS_TIME_S));\n        }\n\n        anjay_delete(anjay);\n        return result;\n    }\n\nIf you have access to an AT modem with SMS functionality and a LwM2M server\nwith SMS gateway configured, you need to think about a couple of matters:\n\n* the example AT driver expects the device to not echo the commands sent, that\n  is ``ATE0`` should be either sent to the modem first, or configured earlier,\n* the driver communicates with the modem using plain ``read()`` / ``write()``\n  routines and is not aware of the characteristics of the terminal, so it should\n  be configured first. For example, to make this application work with Quectel\n  UG96 modem, on Linux, over 115200-8-N-1 serial connection, following command\n  had to be issued:\n  ``stty -F /dev/ttyACM0 115200 -cs8 -cstopb -parenb -icrnl``.\n\nOtherwise, to run the example, you can use the ``vmodem.py`` script instead,\nwhich acts as a virtual AT modem, translating SMS messages to UDP packets and\nvice-versa. You can find it in ``tests/integration/framework/framework/sms`` directory.\n\nExample run follows:\n\n.. highlight:: none\n\n::\n\n    $ tests/integration/framework/framework/sms/vmodem.py --host eu.iot.avsystem.cloud --port 5683\n    2022-03-03 12:41:49 user root[19173] INFO Modem PTY: /dev/pts/5\n\nThe scripts informs us that it has opened a virtual terminal at ``/dev/pts/5``.\nNow you can build the application by executing\n``make commercial_feature_examples`` and run it with\n``output/bin/examples/anjay-sms endpoint_name /dev/pts/5``.\n\nDTLS over SMS\n\"\"\"\"\"\"\"\"\"\"\"\"\"\n\n.. note::\n\n   The full code for the following example can be found in the\n   ``examples/commercial-features/CF-SMS-PSK`` directory in Anjay sources. Note\n   that to compile and run it, you need to have access to a commercial version\n   of Anjay that includes the SMS binding feature.\n\nTo secure the connection using DTLS over SMS, we'll introduce similar changes\nto those described in :doc:`../BasicClient/BC-Security` tutorial, but targeting\nfields explicitly related to the SMS binding.\n\n.. note::\n\n   LwM2M allows only for PSK mode to be used with DTLS over SMS.\n\n.. highlight:: c\n.. snippet-source:: examples/commercial-features/CF-SMS-PSK/src/main.c\n   :emphasize-lines: 6-7, 13-18\n\n    static int setup_security_object(anjay_t *anjay) {\n        if (anjay_security_object_install(anjay)) {\n            return -1;\n        }\n\n        static const char PSK_IDENTITY[] = \"identity\";\n        static const char PSK_KEY[] = \"P4s$w0rd\";\n\n        const anjay_security_instance_t security_instance = {\n            .ssid = 1,\n            .server_uri = \"tel:+12125550178\",\n            .security_mode = ANJAY_SECURITY_NOSEC,\n            .sms_security_mode = ANJAY_SMS_SECURITY_DTLS_PSK,\n            .sms_key_parameters = (const uint8_t *) PSK_IDENTITY,\n            .sms_key_parameters_size = strlen(PSK_IDENTITY),\n            .sms_secret_key = (const uint8_t *) PSK_KEY,\n            .sms_secret_key_size = strlen(PSK_KEY),\n            .server_name_indication = \"eu.iot.avsystem.cloud\"\n        };\n\n        // Anjay will assign Instance ID automatically\n        anjay_iid_t security_instance_id = ANJAY_ID_INVALID;\n        if (anjay_security_object_add_instance(anjay, &security_instance,\n                                               &security_instance_id)) {\n            return -1;\n        }\n\n        return 0;\n    }\n\nNotice that the ``security_mode`` setting must remain untouched as it's\nunused by the application, but LwM2M Security object specification requires\nits presence.\n\n.. important::\n\n   When using DTLS, Anjay by default sets the Server Name Indication (SNI)\n   extension field to the address extracted from ``server_uri`` field, which\n   in our case is the server's gateway phone number. As in this example we're\n   using the virtual modem script to make the example more accessible, which\n   also means that in fact UDP is used to connect to the server, we have to\n   override the SNI by assigning ``server_name_indication`` field to our target\n   server's address. Otherwise, the server will attempt to recognize the phone\n   number as correct server name.\n\nTo make DTLS over SMS work correctly, we have to ensure that appropriate\nciphersuite is used. The MTU of SMS is just 140 bytes, so some ciphersuites\nhave too much overhead to be conveyed over SMS messages.\n\n.. highlight:: c\n.. snippet-source:: examples/commercial-features/CF-SMS-PSK/src/main.c\n   :emphasize-lines: 8-12\n\n    const anjay_configuration_t CONFIG = {\n        .endpoint_name = argv[1],\n        .in_buffer_size = 4000,\n        .out_buffer_size = 4000,\n        .msg_cache_size = 4000,\n        .sms_driver = anjay_at_sms_create(argv[2]),\n        .local_msisdn = \"14155550125\",\n        .default_tls_ciphersuites = {\n            // TLS_PSK_WITH_AES_128_CCM_8\n            .ids = (uint32_t[]){ 0xC0A8 },\n            .num_ids = 1\n        }\n    };\n\n.. note::\n   ``TLS_PSK_WITH_AES_128_CCM_8`` is one of LwM2M's recommended ciphersuites to\n   be used with SMS bindings. The ID assignments of ciphersuites are maintained\n   by IANA and can be found in `this document\n   <https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-4>`_.\n\nAlternatively, one can use Concatenated SMS messages instead, which have\nlogical MTU large enough to work with all types of ciphersuites, but it means\nthat most of the messages sent to the server, even small ones, will be\nfragmented. To enable multipart SMS messages, set ``prefer_multipart_sms``\nin the ``CONFIG`` structure above to ``true``.\n\n.. _anjay-sms-trigger-mode:\n\nSMS Trigger Mode\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\n.. note::\n\n   The full code for the following example can be found in the\n   ``examples/commercial-features/CF-SMS-UDP`` directory in Anjay sources. Note\n   that to compile and run it, you need to have access to a commercial version\n   of Anjay that includes the SMS binding feature.\n\nLwM2M 1.1 introduces Trigger Mode, which provides the possibility to request a\nLwM2M Update via SMS (by executing Registration Update Trigger resource) and\nreceive the response by currently used transport binding. Similar behavior can\nbe achieved using *US* or *UQS* binding modes available in LwM2M 1.0.\n\nFollowing example will build upon the code from `Simple, unsecured connection`\nsection, use LwM2M 1.1 and UDP as transport binding.\n\nFirstly, let's change the Server URI to use UDP again. Server's phone number\nwill be passed through another field, ``server_sms_number``, in MSISDN form.\n\n.. highlight:: c\n.. snippet-source:: examples/commercial-features/CF-SMS-UDP/src/main.c\n   :emphasize-lines: 3, 6\n\n    const anjay_security_instance_t security_instance = {\n        .ssid = 1,\n        .server_uri = \"coap://eu.iot.avsystem.cloud:5683\",\n        .security_mode = ANJAY_SECURITY_NOSEC,\n        .sms_security_mode = ANJAY_SMS_SECURITY_NOSEC,\n        .server_sms_number = \"12125550178\"\n    };\n\nTo change the binding back to UDP and enable the Trigger resource in Server\nobject which controls Trigger Mode, apply following changes:\n\n.. highlight:: c\n.. snippet-source:: examples/commercial-features/CF-SMS-UDP/src/main.c\n   :emphasize-lines: 12-15\n\n    const anjay_server_instance_t server_instance = {\n        // Server Short ID\n        .ssid = 1,\n        // Client will send Update message often than every 60 seconds\n        .lifetime = 60,\n        // Disable Default Minimum Period resource\n        .default_min_period = -1,\n        // Disable Default Maximum Period resource\n        .default_max_period = -1,\n        // Disable Disable Timeout resource\n        .disable_timeout = -1,\n        // Sets preferred transport to UDP\n        .binding = \"U\",\n        // Enables optional Trigger resource and sets it to true\n        .trigger = &(const bool) { true }\n    };\n\n"
  },
  {
    "path": "doc/sphinx/source/CommercialFeatures/CF-SmartCardBootstrap.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nBootstrapper and SIM bootstrap\n==============================\n\n.. contents:: :local:\n\nGeneral description\n-------------------\n\nThe LwM2M specification defines `Bootstrap from Smartcard\n<http://www.openmobilealliance.org/release/LightweightM2M/V1_1_1-20190617-A/HTML-Version/OMA-TS-LightweightM2M_Core-V1_1_1-20190617-A.html#6-1-3-2-0-6132-Bootstrap-from-Smartcard>`_,\na mode of bootstrapping the device where the initial Bootstrap Information is\nstored on a smart card - typically the SIM card in case of devices that use\ncellular connectivity.\n\nStandard file formats for this bootstrap information and related metadata are\ndefined in `Appendix G\n<http://www.openmobilealliance.org/release/LightweightM2M/V1_1_1-20190617-A/HTML-Version/OMA-TS-LightweightM2M_Core-V1_1_1-20190617-A.html#15-0-Appendix-G-Storage-of-LwM2M-Bootstrap-Information-on-the-Smartcard-Normative>`_,\nof the LwM2M Technical Specification, and specifications for the secure channel\nbetween Smartcard and LwM2M Device Storage in `Appendix H\n<http://www.openmobilealliance.org/release/LightweightM2M/V1_1_1-20190617-A/HTML-Version/OMA-TS-LightweightM2M_Core-V1_1_1-20190617-A.html#16-0-Appendix-H-Secure-channel-between-Smartcard-and-LwM2M-Device-Storage-for-secure-Bootstrap-Data-provisioning-Normative>`_\nthereof.\n\nThe \"bootstrapper\" feature, available as a commercial extension to the Anjay\nlibrary, includes two modules that aid in implementing this part of the\nspecification:\n\n* ``bootstrapper`` implements a parser for the file format described in\n  `section G.3.4 of the Appendix G\n  <http://www.openmobilealliance.org/release/LightweightM2M/V1_1_1-20190617-A/HTML-Version/OMA-TS-LightweightM2M_Core-V1_1_1-20190617-A.html#15-3-4-0-G34-EF-LwM2M_Bootstrap>`_\n  mentioned above\n* ``sim_bootstrap`` implements the flow of `ISO/IEC 7816-4\n  <https://www.iso.org/obp/ui/#iso:std:iso-iec:7816:-4:ed-4:v1:en>`_ commands\n  necessary to retrieve the aforementioned file\n\nWith the above features in place, all that's left to implement is actual\ncommunication with the smart card, typically sending and receiving ``AT+CSIM``\ncommands to a cellular modem.\n\nBootstrapping from smart card has a number of advantages, including:\n\n* Ability to store bootstrap information securely, increasing the device's\n  resilience against tampering\n\n* Possibility to remotely update bootstrap information using cellular\n  infrastructure, without the need for a full firmware upgrade\n\n* For devices controlled by cellular carriers - ability to control the bootstrap\n  information without contacting the device manufacturer\n\nTechnical documentation\n-----------------------\n\n.. _cf-smart-card-bootstrap-enabling:\n\nEnabling the bootstrapper module\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nIf the bootstrapper feature is available in your version of Anjay, it can be\nenabled at compile time by enabling the ``ANJAY_WITH_MODULE_BOOTSTRAPPER`` macro\nin the ``anjay_config.h`` file or, if using CMake, enabling the corresponding\n``WITH_MODULE_bootstrapper`` CMake option.\n\nWhen this feature is enabled, the `anjay_bootstrapper()\n<../api/bootstrapper_8h.html#a9763a2328433e93ae5121f0b218b43a1>`_ function can\nbe used. The user will need to provide an implementation of ``avs_stream_t``\nthat allows the Anjay code to read the file contained on the smartcard. The\n``avs_stream_simple_input_create()`` function from the `avs_stream_simple_io.h\n<https://github.com/AVSystem/avs_commons/blob/master/include_public/avsystem/commons/avs_stream_simple_io.h>`_\nheader is likely to be the easiest way to provide such an implementation, aside\nfrom using the SIM bootstrap module described below.\n\nEnabling and configuring the sim_bootstrap module\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nSimilarly, to enable the sim_bootstrap module, you can enable the\n``ANJAY_WITH_MODULE_SIM_BOOTSTRAP`` macro in the ``anjay_config.h`` file or, if\nusing CMake, enable the corresponding ``WITH_MODULE_sim_bootstrap`` CMake\noption. This requires that the bootstrapper feature is also enabled.\n\nBy default, the module will access the PKCS#15 application directory file and\nsearch it for the EF(DODF-bootstrap) file in a way that is compliant with LwM2M\nTS Appendix G mentioned above.\n\nHowever, you can override the OID of the file to look for, by defining the\n``ANJAY_MODULE_SIM_BOOTSTRAP_DATA_OID_OVERRIDE_HEX`` macro in ``anjay_config.h``\nor setting the corresponding ``MODULE_sim_bootstrap_DATA_OID_OVERRIDE_HEX``\nCMake option. It shall be set to a string containing hexlified DER\nrepresentation of the desired OID. The default, standards-compliant value is\n``\"672b0901\"`` (which corresponds to OID 2.23.43.9.1), but you may need to\nchange it to a different value, for example some cards are known to use a\nmistakenly encoded value of ``\"0604672b0901\"``.\n\nAlternatively, you might define the\n``ANJAY_MODULE_SIM_BOOTSTRAP_HARDCODED_FILE_ID`` macro (or set the\n``MODULE_sim_bootstrap_HARDCODED_FILE_ID`` CMake option) to bypass the directory\nsearch entirely and set a hardcoded file ID, e.g. ``0x6432``.\n\nOnce the module is enabled and configured, you can use the\n`anjay_sim_bootstrap_stream_create()\n<../api/sim__bootstrap_8h.html#a7cd497f30bfc7d36c6f0efb1db1d5a19>`_ function to\ncreate an input stream suitable for passing to ``anjay_bootstrapper()``. In the\nsimplest case, you can also use the `anjay_sim_bootstrap_perform()\n<../api/sim__bootstrap_8h.html#aa94114321f3af6532babde1efd9bdcec>`_ function\nthat combines both calls and automatically closes the stream as well.\n\nBootstrap information generator tool\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nThe ``generator.py`` application, located in the ``bootstrap`` directory of\nAnjay source package, allows generating binary files in the EF LwM2M_Bootstrap\nformat that is supposed to be stored on smart cards, from a human-readable text\nfile format.\n\nThe ``generator.py`` script, by default, processes the standard input and\noutputs to the standard output. However, an input file may be specified using\nthe `-c` option, and the output file may be specified using the ``-o`` option.\n\n.. warning::\n\n    The generator script is **NOT** intended to be safe to use with arbitrary\n    input data. It is only intended for convenience when working with files\n    created locally by trusted parties.\n\n    **The input text files are evaluated as Python code**, and as such, running\n    the generator script with untrusted input may lead to arbitrary operations\n    being performed on the computer.\n\nThe input file shall specify a dictionary according to Python syntax where:\n\n* On the top level, the keys shall be Object IDs, and the values shall be nested\n  dictionaries describing the objects.\n\n* On the Object level, the keys shall be Instance IDs, and the values shall be\n  nested dictionaries describing the instances.\n\n* On the Object Instance level, the keys shall be Resource IDs, and the values\n  shall be either of:\n\n  * Primitive types (numbers, booleans, strings or ``bytes`` objects) for\n    single-instance resources\n\n  * Lists of pairs (tuples of length 2) for Multiple-Instance Resources - in\n    that case the first pair element shall be the Resource Instance ID, and the\n    second one shall be the value of a primitive type\n\nThe constants from the ``OID`` and ``RID`` objects, as defined in the\n`tests/integration/framework/framework/test_utils.py\n<https://github.com/AVSystem/Anjay/blob/master/tests/integration/framework/framework/test_utils.py>`_\nfile, may be used to make the keys more descriptive, as in the example input\nfile (``bootstrap/configs/basic``):\n\n.. highlight:: python\n.. snippet-source:: bootstrap/configs/basic\n    :commercial:\n\n    {\n        OID.Security: {\n            1: {\n                RID.Security.ServerURI          : 'coaps://eu.iot.avsystem.cloud:5684',\n                RID.Security.Bootstrap          : False,\n                RID.Security.Mode               : 0,  # PSK\n                RID.Security.PKOrIdentity       : b'example-psk-identity',\n                RID.Security.SecretKey          : b'3x@mpl3P5K53cr3tK3y',\n                RID.Security.ShortServerID      : 1\n            },\n        },\n\n        OID.Server: {\n            1: {\n                RID.Server.ShortServerID        : 1,\n                RID.Server.Lifetime             : 86400,\n                RID.Server.NotificationStoring  : False,\n                RID.Server.Binding              : 'U'\n            },\n        }\n    }\n\nThe above example is equivalent to the following data written only using\nprimitive values::\n\n    {\n        0: {\n            1: {\n                0: 'coaps://eu.iot.avsystem.cloud:5684',\n                1: False,\n                2: 0,\n                3: b'example-psk-identity',\n                5: b'3x@mpl3P5K53cr3tK3y',\n                10: 1\n            }\n        },\n        1: {\n            1: {\n                0: 1,\n                1: 86400,\n                6: False,\n                7: 'U'\n            }\n        }\n    }\n\n.. highlight:: none\n\nThe following example shell session illustrates the way of generating the\nbinary bootstrap information file::\n\n    ~/projects/anjay/bootstrap$ ./generator.py -c configs/basic -o basic_config.dat\n    ~/projects/anjay/bootstrap$ hexdump -C basic_config.dat\n    00000000  00 02 00 7a 00 00 00 00  5e 08 01 5b c8 00 22 63  |...z....^..[..\"c|\n    00000010  6f 61 70 73 3a 2f 2f 65  75 2e 69 6f 74 2e 61 76  |oaps://eu.iot.av|\n    00000020  73 79 73 74 65 6d 2e 63  6c 6f 75 64 3a 35 36 38  |system.cloud:568|\n    00000030  34 c1 01 00 c1 02 00 c8  03 14 65 78 61 6d 70 6c  |4.........exampl|\n    00000040  65 2d 70 73 6b 2d 69 64  65 6e 74 69 74 79 c8 05  |e-psk-identity..|\n    00000050  13 33 78 40 6d 70 6c 33  50 35 4b 35 33 63 72 33  |.3x@mpl3P5K53cr3|\n    00000060  74 4b 33 79 c1 0a 01 00  01 00 00 12 08 01 0f c1  |tK3y............|\n    00000070  00 01 c4 01 00 01 51 80  c1 06 00 c1 07 55        |......Q......U|\n    0000007e\n\n\nExample code\n^^^^^^^^^^^^\n\n.. note::\n\n   The full code for the following example can be found in the\n   ``examples/commercial-features/CF-SmartCardBootstrap`` directory in Anjay\n   sources. Note that to compile and run it, you need to have access to a\n   commercial version of Anjay that includes the bootstrapper feature.\n\nThe example is loosely based on the :doc:`../BasicClient/BC-MandatoryObjects`\ntutorial, and additionally borrows much of the modem communication code from\n:doc:`CF-NIDD`. Since the bootstrap information will be loaded from a smart\ncard, the ``setup_security_object()`` and ``setup_server_object()`` functions\nare no longer necessary, and the calls to them can be replaced with direct calls\nto `anjay_security_object_install()\n<../api/security_8h.html#a5fffaeedfc5c2933e58ac1446fd0401d>`_ and\n`anjay_server_object_install()\n<../api/server_8h.html#a36a369c0d7d1b2ad42c898ac47b75765>`_:\n\n.. highlight:: c\n.. snippet-source:: examples/commercial-features/CF-SmartCardBootstrap/src/main.c\n    :emphasize-lines: 22-23, 27-29\n\n    int main(int argc, char *argv[]) {\n        if (argc != 3) {\n            avs_log(tutorial, ERROR, \"usage: %s ENDPOINT_NAME MODEM_PATH\", argv[0]);\n            return -1;\n        }\n\n        const anjay_configuration_t CONFIG = {\n            .endpoint_name = argv[1],\n            .in_buffer_size = 4000,\n            .out_buffer_size = 4000,\n            .msg_cache_size = 4000\n        };\n\n        anjay_t *anjay = anjay_new(&CONFIG);\n        if (!anjay) {\n            avs_log(tutorial, ERROR, \"Could not create Anjay object\");\n            return -1;\n        }\n\n        int result = 0;\n        // Setup necessary objects\n        if (anjay_security_object_install(anjay)\n                || anjay_server_object_install(anjay)) {\n            result = -1;\n        }\n\n        if (!result) {\n            result = bootstrap_from_sim(anjay, argv[2]);\n        }\n\n        if (!result) {\n            result = anjay_event_loop_run(\n                    anjay, avs_time_duration_from_scalar(1, AVS_TIME_S));\n        }\n\n        anjay_delete(anjay);\n        return result;\n    }\n\nAs you can see, the command line now expects a second argument with a name of\nthe file containing the bootstrap information.\n\nThis file is loaded using the ``bootstrap_from_sim()`` function, implemented as\nfollows:\n\n.. highlight:: c\n.. snippet-source:: examples/commercial-features/CF-SmartCardBootstrap/src/main.c\n\n    typedef struct {\n        avs_buffer_t *buffer;\n    } fifo_t;\n\n    // ...\n\n    typedef struct {\n        fifo_t fifo;\n        int pts_fd;\n    } modem_ctx_t;\n\n    // ...\n\n    static int sim_perform_command(void *modem_ctx_,\n                                   const void *cmd,\n                                   size_t cmd_length,\n                                   void *out_buf,\n                                   size_t out_buf_size,\n                                   size_t *out_response_size) {\n        modem_ctx_t *modem_ctx = (modem_ctx_t *) modem_ctx_;\n        char req_buf[REQ_BUF_SIZE];\n        char resp_buf[RESP_BUF_SIZE] = \"\";\n\n        char *req_buf_ptr = req_buf;\n        char *const req_buf_end = req_buf + sizeof(req_buf);\n        int result = avs_simple_snprintf(req_buf_ptr,\n                                         (size_t) (req_buf_end - req_buf_ptr),\n                                         \"AT+CSIM=%\" PRIu32 \",\\\"\",\n                                         (uint32_t) (2 * cmd_length));\n        if (result < 0) {\n            return result;\n        }\n        req_buf_ptr += result;\n        if ((size_t) (req_buf_end - req_buf_ptr) < 2 * cmd_length) {\n            return -1;\n        }\n        if ((result = avs_hexlify(req_buf_ptr, (size_t) (req_buf_end - req_buf_ptr),\n                                  NULL, cmd, cmd_length))) {\n            return result;\n        }\n        req_buf_ptr += 2 * cmd_length;\n        if ((result = avs_simple_snprintf(\n                     req_buf_ptr, (size_t) (req_buf_end - req_buf_ptr), \"\\\"\\r\\n\"))\n                < 0) {\n            return result;\n        }\n        req_buf_ptr += result;\n        ssize_t written =\n                write(modem_ctx->pts_fd, req_buf, (size_t) (req_buf_ptr - req_buf));\n        if (written != (ssize_t) (req_buf_ptr - req_buf)) {\n            return -1;\n        }\n        avs_time_monotonic_t deadline = avs_time_monotonic_add(\n                avs_time_monotonic_now(),\n                avs_time_duration_from_scalar(5, AVS_TIME_S));\n        bool csim_resp_received = false;\n        bool ok_received = false;\n        while (!ok_received) {\n            if (modem_getline(modem_ctx, resp_buf, sizeof(resp_buf), deadline)) {\n                return -1;\n            }\n            const char *resp_terminator = memchr(resp_buf, '\\0', sizeof(resp_buf));\n            if (!resp_terminator) {\n                return -1;\n            }\n            if (memcmp(resp_buf, CSIM_RESP, strlen(CSIM_RESP)) == 0) {\n                if (csim_resp_received) {\n                    return -1;\n                }\n                errno = 0;\n                char *endptr = NULL;\n                long long resp_reported_length =\n                        strtoll(resp_buf + strlen(CSIM_RESP), &endptr, 10);\n                if (errno || !endptr || endptr[0] != ',' || endptr[1] != '\"'\n                        || resp_reported_length < 0\n                        || endptr + resp_reported_length + 2 >= resp_terminator\n                        || endptr[resp_reported_length + 2] != '\"'\n                        || avs_unhexlify(out_response_size, (uint8_t *) out_buf,\n                                         out_buf_size, endptr + 2,\n                                         (size_t) resp_reported_length)) {\n                    return -1;\n                }\n                csim_resp_received = true;\n            } else if (strcmp(resp_buf, \"OK\") == 0) {\n                ok_received = true;\n            }\n        }\n        return csim_resp_received ? 0 : -1;\n    }\n\n    static int bootstrap_from_sim(anjay_t *anjay, const char *modem_device) {\n        modem_ctx_t modem_ctx = {\n            .pts_fd = -1\n        };\n        int result = -1;\n\n        avs_log(tutorial, INFO, \"Attempting to bootstrap from SIM card\");\n\n        if (fifo_init(&modem_ctx.fifo)) {\n            avs_log(tutorial, ERROR, \"could not initialize FIFO\");\n            goto finish;\n        }\n        if ((modem_ctx.pts_fd = open(modem_device, O_RDWR)) < 0) {\n            avs_log(tutorial, ERROR, \"could not open modem device %s: %s\",\n                    modem_device, strerror(errno));\n            goto finish;\n        }\n        if (avs_is_err(anjay_sim_bootstrap_perform(anjay, sim_perform_command,\n                                                   &modem_ctx))) {\n            avs_log(tutorial, ERROR, \"Could not bootstrap from SIM card\");\n            goto finish;\n        }\n        result = 0;\n    finish:\n        if (modem_ctx.pts_fd >= 0) {\n            close(modem_ctx.pts_fd);\n        }\n        fifo_destroy(&modem_ctx.fifo);\n        return result;\n    }\n\nThe ``sim_perform_command()`` function is a callback that is passed to the\n``sim_bootstrap`` module logic, and performs the ``AT+CSIM`` command over a\nserial port. The ``modem_getline()`` function it calls is almost identical to\nthe one originally implemented for :doc:`CF-NIDD`.\n\nThe ``bootstrap_from_sim()`` function itself is a wrapper over\n`anjay_sim_bootstrap_perform()\n<../api/sim__bootstrap_8h.html#aa94114321f3af6532babde1efd9bdcec>`_ that\nadditionally initializes and closes the card communication channel.\n\n"
  },
  {
    "path": "doc/sphinx/source/CommercialFeatures.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nCommercial features\n===================\n\n.. image:: avsystem_logo.png\n   :align: center\n   :target: https://avsystem.com/anjay-iot-sdk/\n   :alt: AVSystem logo\n\nEnterprise-grade commercial support for the Anjay library, as well as additional\ncommercially-licensed library features, are provided by AVSystem. For more\ndetails, see\n`AVSystem Anjay website <https://avsystem.com/anjay-iot-sdk/>`_.\n\nIf you want to test your LwM2M client implementation you can use the LwM2M\ninteroperability test module in AVSystem’s `Coiote IoT Device Management\n<https://avsystem.com/coiote-iot-device-management-platform/>`_ platform. Our\nautomated tests and testing scenarios enable you to quickly check how\ninteroperable your device is with LwM2M. The tests include basic actions, such\nas Read, Write, Execute, Discover, Delete, and more as well as advanced actions,\nsuch as Loop or Wait to build more complex test cases. What’s more, you can also\ncreate your own test scenarios or have us prepare custom test cases upon your\nrequest. Visit our `webpage\n<https://avsystem.com/coiote-iot-device-management-platform/lwm2m-interoperability-test/>`_\nto learn more.\n\nCommercial library features can be included separately as options accessible\non top of the basic functionality present in the open source version. Currently\navailable commercial features are:\n\n.. toctree::\n   :glob:\n   :titlesonly:\n\n   CommercialFeatures/CF-*\n"
  },
  {
    "path": "doc/sphinx/source/Compiling_client_applications.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nCompiling client applications\n=============================\n\nCompiling the library\n---------------------\n\n.. important::\n\n    If you cloned Anjay from a Git repository, ensure that you updated\n    submodules by calling ``git submodule update --init`` before continuing.\n\nAnjay uses CMake for project configuration. To compile the library with default\nsettings, call the following command in Anjay root directory:\n\n.. code-block:: bash\n\n    cmake . && make\n\n\nCross-compiling\n---------------\n\n.. note::\n\n    Cross-compilation is necessary only if you are compiling the library to use\n    on a different system, than the one used for compilation. If you, for\n    example, want to use Anjay on Raspberry Pi, then you can perform\n    compilation on Raspberry Pi as described above or cross-compilation on your\n    PC.\n\nARM Cortex-M3-powered STM3220\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nFirst, prepare a CMake toolchain file (see `CMake documentation <https://cmake.org/cmake/help/v3.0/manual/cmake-toolchains.7.html#cross-compiling>`_), then pass :code:`CMAKE_TOOLCHAIN_FILE` when configuring Anjay:\n\n.. code-block:: bash\n\n    cmake -DCMAKE_TOOLCHAIN_FILE=$YOUR_TOOLCHAIN_FILE . && make\n\nAn example CMake toolchain file for an ARM Cortex-M3-powered STM3220 platform may look like so:\n\n.. code-block:: cmake\n\n    set(CMAKE_SYSTEM_NAME Generic)\n    set(CMAKE_SYSTEM_VERSION 1)\n\n    set(CMAKE_C_COMPILER arm-none-eabi-gcc)\n\n    # CMAKE_C_FLAGS set in a toolchain file get overwritten by CMakeCInformation.cmake\n    # unless they are FORCEfully set in the cache\n    # See http://stackoverflow.com/a/30217088/2339636\n    unset(CMAKE_C_FLAGS CACHE)\n    set(CMAKE_C_FLAGS \"-mcpu=cortex-m3 -mthumb -msoft-float -ffunction-sections -fdata-sections -fno-common -fmessage-length=0 -std=gnu99 --specs=nosys.specs\" CACHE STRING \"\" FORCE)\n\n    set(CMAKE_EXE_LINKER_FLAGS \"-Wl,-gc-sections\")\n\n\nAndroid\n~~~~~~~\n\nCompilation on Android platform is rather straightforward. First you have to get `Android NDK\n<https://developer.android.com/ndk/index.html>`_. To configure Anjay you\nhave to pass ``CMAKE_TOOLCHAIN_FILE`` from the NDK (we assume that\n``ANDROID_NDK`` variable contains a path to the folder where Android NDK\nis extracted):\n\n.. code-block:: bash\n\n    cmake -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK/build/cmake/android.toolchain.cmake \\\n          -DDTLS_BACKEND=\"\" \\\n          -DANDROID_ALLOW_UNDEFINED_SYMBOLS=ON \\\n          -DANDROID_PLATFORM=android-18 \\\n          -DANDROID_ABI=armeabi .\n\nAfter that Anjay can be compiled as usual via `make`.\n\n.. note::\n\n    Android platforms older than `android-18` are not supported.\n\n\n.. note::\n\n    ``ANDROID_ALLOW_UNDEFINED_SYMBOLS`` is set, so that unresolved symbols\n    required by the `libanjay.so` are not reported during the linking\n    stage. They shall be resolved by providing dependencies to the final\n    executable as it is illustrated in the next section.\n\nNote that we did not set any ``DTLS_BACKEND`` and therefore Anjay is compiled\nwithout DTLS support. To enable DTLS support you have to provide a value\nto ``DTLS_BACKEND`` (see `README.md <https://github.com/AVSystem/Anjay>`_\nfor more details) along with specific variable indicating where the required\nDTLS libraries are to be found, i.e. one of:\n\n    - ``OPENSSL_ROOT_DIR`` (as `FindOpenSSL.cmake` suggests),\n    - ``MBEDTLS_ROOT_DIR``,\n    - ``TINYDTLS_ROOT_DIR``\n\ndepending on the chosen backend.\n\n.. topic:: Example compilation with mbed TLS backend\n\n    First, we compile mbed TLS on Android:\n\n    .. code-block:: bash\n\n        $ git clone https://github.com/ARMmbed/mbedtls -b mbedtls-2.5.0\n        $ cd mbedtls\n        $ cmake -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK/build/cmake/android.toolchain.cmake \\\n                -DANDROID_PLATFORM=android-18 \\\n                -DANDROID_ABI=armeabi \\\n                -DENABLE_TESTING=OFF \\\n                -DCMAKE_INSTALL_PREFIX=/tmp/mbedtls/install .\n        $ make\n        $ make install\n\n    We then go back to the Anjay source directory, to reconfigure Anjay to use\n    mbed TLS binaries (we strongly suggest to clean all kind of CMake caches\n    before proceeding, as it may not work otherwise):\n\n    .. code-block:: bash\n\n        cmake -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK/build/cmake/android.toolchain.cmake \\\n              -DDTLS_BACKEND=\"mbedtls\" \\\n              -DMBEDTLS_ROOT_DIR=/tmp/mbedtls/install \\\n              -DANDROID_ALLOW_UNDEFINED_SYMBOLS=ON \\\n              -DANDROID_PLATFORM=android-18 \\\n              -DANDROID_ABI=armeabi .\n\n    And finally, we run `make`, finishing the whole procedure.\n\n\nInstalling the library\n----------------------\n\nBuilding with CMake\n~~~~~~~~~~~~~~~~~~~\n\nThe preferred way of building Anjay is to use CMake.\n\nTo install Anjay headers and libraries in :code:`/usr/local`:\n\n.. code-block:: bash\n\n    cmake . && make && sudo make install\n\nA custom installation prefix may be set using :code:`CMAKE_INSTALL_PREFIX`:\n\n.. code-block:: bash\n\n    cmake -DCMAKE_INSTALL_PREFIX=/custom/path . && make && make install\n\n.. _no-cmake:\n\nAlternative build systems\n~~~~~~~~~~~~~~~~~~~~~~~~~\n\nAlternatively, you may use any other build system. You will need to:\n\n* Prepare your ``avs_commons_config.h``, ``avs_coap_config.h`` and ``anjay_config.h`` files.\n\n  * Comments in `avs_commons_config.h.in\n    <https://github.com/AVSystem/avs_commons/blob/master/include_public/avsystem/commons/avs_commons_config.h.in>`_,\n    `avs_coap_config.h.in <https://github.com/AVSystem/Anjay/blob/master/deps/avs_coap/include_public/avsystem/coap/avs_coap_config.h.in>`_\n    and `anjay_config.h.in <https://github.com/AVSystem/Anjay/blob/master/include_public/anjay/anjay_config.h.in>`_\n    will guide you about the meaning of various settings.\n  * You may use one of the directories from `example_configs\n    <https://github.com/AVSystem/Anjay/tree/master/example_configs>`_ as a starting point. See\n    `README.md inside that directory\n    <https://github.com/AVSystem/Anjay/blob/master/example_configs/README.md>`_ for details. You may\n    even set one of the subdirectories there are as an include path directly in your compiler if you\n    do not need any customizations.\n* Configure your build system so that:\n\n  * At least all ``*.c`` and ``*.h`` files from ``src``, ``include_public``, ``deps/avs_coap/src``,\n    ``deps/avs_coap/include_public``, ``deps/avs_commons/src`` and\n    ``deps/avs_commons/include_public`` directories are preserved, with the directory structure\n    intact.\n\n    * It is also safe to merge contents of all ``include_public`` directories into one. Merging\n      ``src`` directories should be safe, too, but is not explicitly supported.\n  * All ``*.c`` files inside ``src``, ``deps/avs_coap/src``, ``deps/avs_commons/src``, or any of\n    their direct or indirect subdirectories are compiled.\n  * ``deps/avs_commons/src`` and ``deps/avs_commons/include_public`` directories are included in the\n    header search path when compiling ``avs_commons``.\n  * ``deps/avs_coap/src``, ``deps/avs_coap/include_public`` and ``deps/avs_commons/include_public``\n    directories are included in the header search path when compiling ``avs_coap``.\n  * ``src``, ``include_public``, ``deps/avs_coap/include_public`` and\n    ``deps/avs_commons/include_public`` directories are included in the header search path when\n    compiling Anjay.\n  * ``include_public``, ``deps/avs_coap/include_public`` and ``deps/avs_commons/include_public``\n    directories, or copies of them (possibly merged into one directory) are included in the header\n    search path when compiling dependent application code.\n\n.. rubric:: Example\n\nBelow is an example of a simplistic build process, that builds all of avs_commons, avs_coap and\nAnjay from a Unix-like shell:\n\n.. code-block:: bash\n\n    # configuration\n    cp -r example_configs/linux_lwm2m10 config\n    # you may want to edit the files in the \"config\" directory before continuing\n\n    # compilation\n    cc -Iconfig -Iinclude_public -Ideps/avs_coap/include_public -Ideps/avs_commons/include_public -Isrc -Ideps/avs_coap/src -Ideps/avs_commons/src -c $(find src deps/avs_coap/src deps/avs_commons/src -name '*.c')\n    ar rcs libanjay.a *.o\n\n    # installation\n    cp libanjay.a /usr/local/lib/\n    cp -r include_public/avsystem /usr/local/include/\n    cp -r deps/avs_coap/include_public/avsystem /usr/local/include/\n    cp -r deps/avs_commons/include_public/avsystem /usr/local/include/\n    cp -r config/* /usr/local/include/\n\n\nIncluding the library in an application\n---------------------------------------\n\nCMake projects\n~~~~~~~~~~~~~~\n\nThe preferred method of using Anjay in custom projects is to use CMake :code:`find_package` command after installing the library:\n\n.. code-block:: cmake\n\n    find_package(anjay)\n    include_directories(${ANJAY_INCLUDE_DIRS})\n    target_link_libraries(my_executable ${ANJAY_LIBRARIES}) # or ANJAY_LIBRARIES_STATIC for a static library\n\n.. note::\n\n    If a custom installation path is used, you need to set :code:`anjay_DIR` CMake variable to :code:`$YOUR_INSTALL_PREFIX/lib/anjay`.\n\n\nAlternative build systems\n~~~~~~~~~~~~~~~~~~~~~~~~~\n\nIf Anjay itself has been compiled using CMake, flags necessary for other build systems can be\nretrieved using :code:`cmake` command:\n\n.. code-block:: bash\n\n    cmake --find-package -DNAME=anjay -DLANGUAGE=C -DCOMPILER_ID=Generic -DMODE=<mode>\n\nWhere :code:`<mode>` is one of:\n\n- :code:`EXIST` - check whether the library can be found,\n- :code:`COMPILE` - print compilation flags,\n- :code:`LINK` - print linking arguments.\n\n.. note::\n\n\tIf a custom installation prefix is used, you need to also pass :code:`-Danjay_DIR=$YOUR_INSTALL_PREFIX/lib/anjay`.\n\n\n\nAnjay compiled without CMake\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nIf Anjay has been compiled without using CMake, you will need to provide necessary flags manually.\n\nSpecific dependencies will vary according to:\n\n* compile-time configuration, including:\n\n  * avs_compat_threading backend\n  * avs_crypto backend, if any\n  * avs_net DTLS backend, if any\n  * ``AVS_COMMONS_HTTP_WITH_ZLIB`` setting, if avs_http is enabled\n* target platform\n* build environment\n\n.. rubric:: Example\n\nFor the following conditions:\n\n* Anjay compiled with all optional features enabled, and:\n\n  * mbed TLS security enabled as avs_net DTLS backend and/or avs_crypto backend\n  * PThread used as avs_compat_threading backend\n  * avs_http enabled with zlib support\n* Target platform being a typical desktop GNU/Linux distribution\n* GCC or Clang used as the compiler\n* Anjay compiled and installed as shown in the example in the :ref:`no-cmake` section\n\nthe flags necessary to link client applications would be:\n\n.. code-block:: bash\n\n    -lanjay -lz -lmbedtls -lmbedcrypto -lmbedx509 -lm -pthread\n"
  },
  {
    "path": "doc/sphinx/source/FirmwareUpdateTutorial/FU-AdvancedFirmwareUpdate/FU-AFU-BasicImplementation.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nImplementation\n==============\n\n.. contents:: :local:\n\n.. note::\n\n    Before approaching this tutorial, it is recommended to get familiar with the\n    :doc:`../FU-BasicImplementation` chapter. Both examples are very similar,\n    except for the additional elements resulting from the Advanced Firmware Update specification.\n    In this document we will not focus on those elements that are also present in basic Firmware Udpates.\n\nProject structure\n^^^^^^^^^^^^^^^^^\n\n.. code::\n\n    examples/tutorial/firmware-update/advanced-firmware-update/\n    ├── CMakeLists.txt\n    └── src\n        ├── advanced_firmware_update.c\n        ├── advanced_firmware_update.h\n        ├── main.c\n        ├── time_object.c\n        └── time_object.h\n\nAdvanced Firmware Update API\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nIn order to install the module, we are going to use:\n\n.. highlight:: c\n.. snippet-source:: include_public/anjay/advanced_fw_update.h\n\n    int anjay_advanced_fw_update_install(\n            anjay_t *anjay, const anjay_advanced_fw_update_global_config_t *config);\n\nWith this call we are passing a ``config`` that will affect all instances of the Advanced Firmware Update object.\n\nTo add an instance of the Advanced Firmware Update object, we are going to use:\n\n.. highlight:: c\n.. snippet-source:: include_public/anjay/advanced_fw_update.h\n\n    int anjay_advanced_fw_update_instance_add(\n            anjay_t *anjay,\n            anjay_iid_t iid,\n            const char *component_name,\n            const anjay_advanced_fw_update_handlers_t *handlers,\n            void *user_arg,\n            const anjay_advanced_fw_update_initial_state_t *initial_state);\n\nThe ``anjay``, ``handlers``, ``user_arg`` and ``initial_state`` arguments are similar\nto their equivalents in ``anjay_fw_update_install`` function (see :ref:`anjay_fw_update_install`).\nThere are two major differences between ``anjay_advanced_fw_update_handlers_t`` and ``anjay_fw_update_handlers_t``:\n\n    - each callback has an additional **iid** argument, which corresponds to the object instance number,\n    - new callback ``get_current_version`` that return the version of current firmware package.\n\nRemember that each instance must have a unique **iid** number. The **component_name** holds value of the /33629/x/14 resource.\n**The string is NOT copied, so it needs to remain valid for the lifetime of the object instance.**\n\nImplementing handlers and installation routine\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nCompared to a regular Firmware Updates, we need to store more information in the global structure.\nFor each AFU instance, we define: a file handle, an array storing the firmware version\n(for the /33629/x/15 resource) and an array storing the instance name (**component_name** argument in\n``anjay_advanced_fw_update_instance_add``).\n\n.. highlight:: c\n.. snippet-source:: examples/tutorial/firmware-update/advanced-firmware-update/src/advanced_firmware_update.c\n\n    #include \"advanced_firmware_update.h\"\n\n    #include <assert.h>\n    #include <errno.h>\n    #include <stdio.h>\n    #include <sys/stat.h>\n    #include <unistd.h>\n\n    #define AFU_VERSION_STR_MAX_LEN 10\n    #define AFU_INSTANCE_NAME_STR_MAX_LEN 10\n    #define AFU_FILE_NAME_STR_MAX_LEN 50\n\n    typedef struct {\n        anjay_t *anjay;\n        char fw_version[AFU_NUMBER_OF_FIRMWARE_INSTANCES]\n                       [AFU_VERSION_STR_MAX_LEN + 1];\n        char instance_name[AFU_NUMBER_OF_FIRMWARE_INSTANCES]\n                          [AFU_INSTANCE_NAME_STR_MAX_LEN + 1];\n        FILE *new_firmware_file[AFU_NUMBER_OF_FIRMWARE_INSTANCES];\n    } advanced_firmware_update_logic_t;\n\n    static advanced_firmware_update_logic_t afu_logic;\n\nNumber of the firmware instances is defined by ``AFU_NUMBER_OF_FIRMWARE_INSTANCES``.\nDefault instance (``AFU_DEFAULT_FIRMWARE_INSTANCE_IID``) is the built image of this software.\nThe other instances correspond to the files created by the ``afu_update_install`` function for\nthe purpose of this tutorial (those are equivalent of such software images as bootloader image,\nmodem image etc. used in embedded systems).\n\n.. highlight:: c\n.. snippet-source:: examples/tutorial/firmware-update/advanced-firmware-update/src/advanced_firmware_update.h\n\n    #ifndef ADVANCED_FIRMWARE_UPDATE_H\n    #define ADVANCED_FIRMWARE_UPDATE_H\n\n    #include <anjay/advanced_fw_update.h>\n    #include <anjay/anjay.h>\n\n    #include <avsystem/commons/avs_log.h>\n\n    #define AFU_DEFAULT_FIRMWARE_VERSION \"1.0.0\"\n    #define AFU_ADD_FILE_DEFAULT_CONTENT \"1.1.1\"\n\n    #define AFU_DEFAULT_FIRMWARE_INSTANCE_IID 0\n    #define AFU_NUMBER_OF_FIRMWARE_INSTANCES 3\n\n    /**\n    * Buffer for the endpoint name that will be used when re-launching the client\n    * after firmware upgrade.\n    */\n    extern const char *ENDPOINT_NAME;\n\n    /**\n    * Installs the advanced firmware update module.\n    *\n    * @returns 0 on success, negative value otherwise.\n    */\n    int afu_update_install(anjay_t *anjay);\n\n    #endif // ADVANCED_FIRMWARE_UPDATE_H\n\nThe implementation of ``fw_stream_open`` and ``fw_update_common_write`` is quite simple.\nFor each iid we open and write to a separate file.\n\n.. highlight:: c\n.. snippet-source:: examples/tutorial/firmware-update/advanced-firmware-update/src/advanced_firmware_update.c\n\n    static void get_firmware_download_name(int iid, char *buff) {\n        if (iid == AFU_DEFAULT_FIRMWARE_INSTANCE_IID) {\n            snprintf(buff, AFU_FILE_NAME_STR_MAX_LEN, \"/tmp/firmware_image.bin\");\n        } else {\n            snprintf(buff, AFU_FILE_NAME_STR_MAX_LEN, \"/tmp/add_image_%d\", iid);\n        }\n    }\n\n.. highlight:: c\n.. snippet-source:: examples/tutorial/firmware-update/advanced-firmware-update/src/advanced_firmware_update.c\n\n    static int fw_stream_open(anjay_iid_t iid, void *user_ptr) {\n        (void) user_ptr;\n\n        char file_name[AFU_FILE_NAME_STR_MAX_LEN] = { 0 };\n        get_firmware_download_name(iid, file_name);\n\n        if (afu_logic.new_firmware_file[iid]) {\n            avs_log(advance_fu, ERROR, \"Already open %s\", file_name);\n            return -1;\n        }\n        afu_logic.new_firmware_file[iid] = fopen(file_name, \"wb\");\n        if (!afu_logic.new_firmware_file[iid]) {\n            avs_log(advance_fu, ERROR, \"Could not open %s\", file_name);\n            return -1;\n        }\n        return 0;\n    }\n\n    static int fw_update_common_write(anjay_iid_t iid,\n                                     void *user_ptr,\n                                     const void *data,\n                                     size_t length) {\n        (void) user_ptr;\n\n        if (!afu_logic.new_firmware_file[iid]) {\n            avs_log(advance_fu, ERROR, \"Stream not open: object %d\", iid);\n            return -1;\n        }\n        if (length\n                && (fwrite(data, length, 1, afu_logic.new_firmware_file[iid]) != 1\n                    || fflush(afu_logic.new_firmware_file[iid]) != 0)) {\n            avs_log(advance_fu, ERROR, \"fwrite or fflush failed: %s\",\n                    strerror(errno));\n            return ANJAY_ADVANCED_FW_UPDATE_ERR_NOT_ENOUGH_SPACE;\n        }\n        return 0;\n    }\n\nIn ``fw_update_common_finish`` after closing the stream, we check the other instances,\nif any is in DOWNLOADED state then we link them with each other. **This is not a requirement,\nthe implementation is free do decide which instances are linked and which are not.**\nWith ``anjay_advanced_fw_update_set_linked_instances`` we set the **Linked Instances** (/33629/x/15)\nresource to inform the server that the upgrade will be performed simultaneously on all linked instances.\n\n.. highlight:: c\n.. snippet-source:: examples/tutorial/firmware-update/advanced-firmware-update/src/advanced_firmware_update.c\n\n    static void add_linked_instance(anjay_iid_t iid, anjay_iid_t target_iid) {\n        const anjay_iid_t *linked_instances;\n        anjay_iid_t linked_target_iids[AFU_NUMBER_OF_FIRMWARE_INSTANCES - 1];\n        size_t linked_iids_count = 0;\n\n        // get linked instances\n        anjay_advanced_fw_update_get_linked_instances(\n                afu_logic.anjay, iid, &linked_instances, &linked_iids_count);\n        // add target_iid to the list\n        for (size_t i = 0; i < linked_iids_count; i++) {\n            linked_target_iids[i] = linked_instances[i];\n        }\n        linked_target_iids[linked_iids_count++] = target_iid;\n        anjay_advanced_fw_update_set_linked_instances(\n                afu_logic.anjay, iid, linked_target_iids, linked_iids_count);\n    }\n\n    static int fw_update_common_finish(anjay_iid_t iid, void *user_ptr) {\n        (void) user_ptr;\n\n        if (!afu_logic.new_firmware_file[iid]) {\n            avs_log(advance_fu, ERROR, \"Stream not open: object %d\", iid);\n            return -1;\n        }\n\n        if (fclose(afu_logic.new_firmware_file[iid])) {\n            avs_log(advance_fu, ERROR, \"Closing firmware image failed: object %d\",\n                    iid);\n            afu_logic.new_firmware_file[iid] = NULL;\n            return -1;\n        }\n        afu_logic.new_firmware_file[iid] = NULL;\n\n        /*\n        If other firmware instances are in downloaded state set linked instances,\n        based on them, the upgrade will be performed simultaneously in the\n        perform_upgrade callback. The reason for setting linked instances may be\n        different and depends on the user's implementation, but always mean\n        that instances will be updated in a batch if the Update resource is executed\n        with no arguments.\n        */\n        for (anjay_iid_t i = 0; i < AFU_NUMBER_OF_FIRMWARE_INSTANCES; i++) {\n            if (i != iid) {\n                anjay_advanced_fw_update_state_t state;\n                anjay_advanced_fw_update_get_state(afu_logic.anjay, i, &state);\n                if (state == ANJAY_ADVANCED_FW_UPDATE_STATE_DOWNLOADED) {\n                    add_linked_instance(iid, i);\n                    add_linked_instance(i, iid);\n                }\n            }\n        }\n\n        return 0;\n    }\n\n\nThree new paramteters are passed with ``fw_update_common_perform_upgrade`` function:\n    - ``iid``,\n    - ``requested_supplemental_iids``,\n    - ``requested_supplemental_iids_count``.\n\nThe ``iid`` points to the instance on which `Update` (/33629/x/2) was called. The ``requested_supplemental_iids`` can\ncontain a list of instances for simultaneous upgrade, passed as an argument in the `Update` (/33629/x/2) command sent by\nthe server. If ``requested_supplemental_iids`` is present (different than `NULL`), specification forces us to upgrade\naccording to it. If this is not possible then we need to return a corresponding error.\n\nFirst, we check which instances are to be updated. To do this, we create ``update_iid`` array and for each\ninstance we set it to `true` if the conditions for upgrade are met. Conditions are checked inside\n``is_update_requested`` function. In our example if ``requested_supplemental_iids == NULL``, we use ``linked_target_iids``\ninstead. Then we retrieve the state of each instance that is involved in the upgrade, if it is other than `DOWNLOADED`\nthen we return a `CONFLICTING_STATE` error. Before leaving the function, we set conflicting instances to tell the server\nwhich instance is causing the problem (``add_conflicting_instance`` function which calls ``anjay_advanced_fw_update_set_conflicting_instances``).\n\nIn next step we check version compatibility. For the purposes of this tutorial, we have assumed that the first character\nin each additional file must have the same value. If we are updating only some of the files, their new versions must\nhave the same value as the old ones. In the case of replacing all files, each new file must match. If a mismatch is detected,\nan error `CONFLICTING_STATE` is returned and the ``add_conflicting_instance`` function is called, so that the server gets\ninformation about the error and the instance that caused it. After that we update the state of each instance from `DOWNLOADED`\nto `UPDATING`.\n\n.. note::\n\n    During upgrade you must remember to change the state of each instance. Anjay will only modify the state of the ``iid``\n    instance. In a typical scenario, the state of each instance must be changed first from `DOWNLOADED` to `UPDATING`\n    and then, if reboot does not occur, to `SUCCESS`.\n\nThe last step is to replace the firmware. We start with additional images, if ``update_iid[i] == true`` then we use the\n``move_file`` function to swap files, if the main image does not change then we create a \"marker\" file for each instance\n(logic carried over from :doc:`../FU-BasicImplementation`) and change its state from `UPDATING` to `SUCCESS`.\nIf the main image is to be replaced then at this point we create the corresponding \"marker\" file and start a new application.\nOtherwise, we call ``refresh_fw_version`` to update the instance's firmware versions and we remove all information about\nall linked and conflicting instances.\n\n.. highlight:: c\n.. snippet-source:: examples/tutorial/firmware-update/advanced-firmware-update/src/advanced_firmware_update.c\n\n    static void get_add_firmware_file_name(int iid, char *buff) {\n        snprintf(buff, AFU_FILE_NAME_STR_MAX_LEN, \"ADD_FILE_%d\", iid);\n    }\n\n    static void get_marker_file_name(int iid, char *buff) {\n        snprintf(buff, AFU_FILE_NAME_STR_MAX_LEN, \"/tmp/fw-updated-marker_%d\", iid);\n    }\n\n    static int move_file(const char *dest, const char *source) {\n        int ret_val = -1;\n        FILE *dest_stream = NULL;\n        FILE *source_stream = fopen(source, \"r\");\n\n        if (!source_stream) {\n            avs_log(advance_fu, ERROR, \"Could not open file: %s\", source);\n            goto cleanup;\n        }\n        dest_stream = fopen(dest, \"w\");\n        if (!dest_stream) {\n            avs_log(advance_fu, ERROR, \"Could not open file: %s\", dest);\n            fclose(source_stream);\n            goto cleanup;\n        }\n\n        while (!feof(source_stream)) {\n            char buff[1024];\n            size_t bytes_read_1 = fread(buff, 1, sizeof(buff), source_stream);\n            if (fwrite(buff, 1, bytes_read_1, dest_stream) != bytes_read_1) {\n                avs_log(advance_fu, ERROR, \"Error during write to file: %s\", dest);\n                goto cleanup;\n            }\n        }\n        ret_val = 0;\n\n    cleanup:\n        if (dest_stream) {\n            if (fclose(dest_stream)) {\n                avs_log(advance_fu, ERROR, \"Could not close file: %s\", dest);\n                ret_val = -1;\n            }\n        }\n        if (source_stream) {\n            if (fclose(source_stream)) {\n                avs_log(advance_fu, ERROR, \"Could not close file: %s\", source);\n                ret_val = -1;\n            }\n        }\n        unlink(source);\n\n        return ret_val;\n    }\n\n    static void add_conflicting_instance(anjay_iid_t iid, anjay_iid_t target_iid) {\n        const anjay_iid_t *conflicting_instances;\n        anjay_iid_t conflicting_target_iids[AFU_NUMBER_OF_FIRMWARE_INSTANCES - 1];\n        size_t conflicting_iids_count = 0;\n\n        // get conflicting instances\n        anjay_advanced_fw_update_get_conflicting_instances(afu_logic.anjay, iid,\n                                                           &conflicting_instances,\n                                                           &conflicting_iids_count);\n        // add target_iid to the list\n        for (size_t i = 0; i < conflicting_iids_count; i++) {\n            conflicting_target_iids[i] = conflicting_instances[i];\n        }\n        conflicting_target_iids[conflicting_iids_count++] = target_iid;\n        anjay_advanced_fw_update_set_conflicting_instances(afu_logic.anjay, iid,\n                                                           conflicting_target_iids,\n                                                           conflicting_iids_count);\n    }\n\n    static bool is_update_requested(anjay_iid_t iid,\n                                    anjay_iid_t target_iid,\n                                    const anjay_iid_t *requested_supplemental_iids,\n                                    size_t requested_supplemental_iids_count,\n                                    const anjay_iid_t *linked_target_iids,\n                                    size_t linked_iids_count) {\n        if (iid == target_iid) {\n            return true;\n        }\n        if (requested_supplemental_iids) {\n            for (size_t i = 0; i < requested_supplemental_iids_count; i++) {\n                if (iid == requested_supplemental_iids[i]) {\n                    return true;\n                }\n            }\n        } else if (linked_target_iids) {\n            for (size_t i = 0; i < linked_iids_count; i++) {\n                if (iid == linked_target_iids[i]) {\n                    return true;\n                }\n            }\n        }\n        return false;\n    }\n\n    static char get_firmware_major_version(anjay_iid_t iid, bool is_upgrade) {\n        char file_name[AFU_FILE_NAME_STR_MAX_LEN] = { 0 };\n\n        if (is_upgrade == false) {\n            return afu_logic.fw_version[iid][0];\n        }\n\n        get_firmware_download_name(iid, file_name);\n\n        // get value from new file\n        char buff;\n        FILE *stream = fopen(file_name, \"r\");\n        if (!stream) {\n            avs_log(advance_fu, ERROR, \"Could not open file: %s\", file_name);\n            return ' ';\n        }\n        if (!fread(&buff, 1, 1, stream)) {\n            avs_log(advance_fu, ERROR, \"Could not read from file file: %s\",\n                    file_name);\n            fclose(stream);\n            return ' ';\n        }\n        if (fclose(stream)) {\n            avs_log(advance_fu, ERROR, \"Could not close file: %s\", file_name);\n        }\n\n        return buff;\n    }\n\n    static int refresh_fw_version() {\n        memcpy(afu_logic.fw_version[AFU_DEFAULT_FIRMWARE_INSTANCE_IID],\n            AFU_DEFAULT_FIRMWARE_VERSION, strlen(AFU_DEFAULT_FIRMWARE_VERSION));\n\n        for (int i = 1; i < AFU_NUMBER_OF_FIRMWARE_INSTANCES; i++) {\n            char buff[AFU_FILE_NAME_STR_MAX_LEN] = { 0 };\n            get_add_firmware_file_name(i, buff);\n            FILE *stream = fopen(buff, \"r\");\n            if (!stream) {\n                avs_log(advance_fu, ERROR, \"Could not open file with iid: %d\", i);\n                return -1;\n            }\n            if (!fread(afu_logic.fw_version[i], 1, AFU_VERSION_STR_MAX_LEN,\n                    stream)) {\n                avs_log(advance_fu, ERROR, \"Could not read file with iid: %d\", i);\n                fclose(stream);\n                return -1;\n            }\n            if (fclose(stream)) {\n                avs_log(advance_fu, ERROR, \"Could not close file with iid: %d\", i);\n                return -1;\n            }\n        }\n\n        for (int i = 0; i < AFU_NUMBER_OF_FIRMWARE_INSTANCES; i++) {\n            avs_log(advance_fu, INFO,\n                    \"Firmware version for object with IID %d is: %s\", i,\n                    afu_logic.fw_version[i]);\n        }\n\n        return 0;\n    }\n\n\n.. highlight:: c\n.. snippet-source:: examples/tutorial/firmware-update/advanced-firmware-update/src/advanced_firmware_update.c\n\n    static int\n    fw_update_common_perform_upgrade(anjay_iid_t iid,\n                                     void *user_ptr,\n                                     const anjay_iid_t *requested_supplemental_iids,\n                                     size_t requested_supplemental_iids_count) {\n        (void) user_ptr;\n\n        const anjay_iid_t *linked_target_iids;\n        bool update_iid[AFU_NUMBER_OF_FIRMWARE_INSTANCES];\n        size_t linked_iids_count = 0;\n\n        // get linked instances\n        anjay_advanced_fw_update_get_linked_instances(\n                afu_logic.anjay, iid, &linked_target_iids, &linked_iids_count);\n\n        /* Prepare list of iid to update. If requested_supplemental_iids is present\n        * use it otherwise use linked_target_iids.*/\n        for (anjay_iid_t i = 0; i < AFU_NUMBER_OF_FIRMWARE_INSTANCES; i++) {\n            if (is_update_requested(i, iid, requested_supplemental_iids,\n                                    requested_supplemental_iids_count,\n                                    linked_target_iids, linked_iids_count)) {\n                update_iid[i] = true;\n                // check if new file is already downloaded\n                anjay_advanced_fw_update_state_t state;\n                anjay_advanced_fw_update_get_state(afu_logic.anjay, i, &state);\n                if ((i != iid)\n                        && (state != ANJAY_ADVANCED_FW_UPDATE_STATE_DOWNLOADED)) {\n                    avs_log(advance_fu, ERROR,\n                            \"Upgrade can't be performed, firmware file with iid %d \"\n                            \"is not ready\",\n                            i);\n                    // set conflicting instance\n                    add_conflicting_instance(iid, i);\n                    return ANJAY_ADVANCED_FW_UPDATE_ERR_CONFLICTING_STATE;\n                }\n            } else {\n                update_iid[i] = false;\n            }\n        }\n\n        /*\n        Check firmware version compatibility.\n        In this example major version number is compare - first character in every\n        additional image must have the same value. If new file is given (DOWNLOADED\n        STATE), get this value from them, otherwise use the old one.\n        */\n        for (anjay_iid_t i = 1; i < AFU_NUMBER_OF_FIRMWARE_INSTANCES; i++) {\n            if (update_iid[i] == true) {\n                for (anjay_iid_t j = i + 1; j < AFU_NUMBER_OF_FIRMWARE_INSTANCES;\n                    j++) {\n                    if (get_firmware_major_version(i, update_iid[i])\n                            != get_firmware_major_version(j, update_iid[j])) {\n                        avs_log(advance_fu, ERROR,\n                                \"Upgrade can't be performed, conflicting firmware \"\n                                \"version between instance %d and %d\",\n                                i, j);\n                        // set conflicting instance due to firmware version\n                        // incompatibility\n                        add_conflicting_instance(i, j);\n                        add_conflicting_instance(j, i);\n                        return ANJAY_ADVANCED_FW_UPDATE_ERR_CONFLICTING_STATE;\n                    }\n                }\n            }\n        }\n\n        /* No errors found, change the status of all requested_supplemental_iids or\n        * linked_target_iids to UPDATING before the actual update process.*/\n        for (anjay_iid_t i = 0; i < AFU_NUMBER_OF_FIRMWARE_INSTANCES; i++) {\n            if (update_iid[i] == true) {\n                if (i != iid) {\n                    anjay_advanced_fw_update_set_state_and_result(\n                            afu_logic.anjay, i,\n                            ANJAY_ADVANCED_FW_UPDATE_STATE_UPDATING,\n                            ANJAY_ADVANCED_FW_UPDATE_RESULT_INITIAL);\n                }\n            }\n        }\n\n        // after firmware versions check, start firmware update, first with\n        // additional images\n        for (anjay_iid_t i = 1; i < AFU_NUMBER_OF_FIRMWARE_INSTANCES; i++) {\n            if (update_iid[i] == true) {\n                avs_log(advance_fu, INFO, \"Perform update on %d instance\", i);\n\n                char new_firm_name[AFU_FILE_NAME_STR_MAX_LEN] = { 0 };\n                char current_firm_name[AFU_FILE_NAME_STR_MAX_LEN] = { 0 };\n                get_firmware_download_name(i, new_firm_name);\n                get_add_firmware_file_name(i, current_firm_name);\n\n                if (move_file(current_firm_name, new_firm_name)) {\n                    avs_log(advance_fu, ERROR,\n                            \"Error during the %d additional image swapping\", i);\n                    return -1;\n                }\n                // if main application is restarted, set update marker\n                if (update_iid[AFU_DEFAULT_FIRMWARE_INSTANCE_IID] == true) {\n                    char marker_name[AFU_FILE_NAME_STR_MAX_LEN] = { 0 };\n                    get_marker_file_name(i, marker_name);\n                    FILE *marker = fopen(marker_name, \"w\");\n                    if (!marker) {\n                        avs_log(advance_fu, ERROR,\n                                \"Marker file could not be created\");\n                        return -1;\n                    }\n                    if (fclose(marker)) {\n                        avs_log(advance_fu, ERROR,\n                                \"Marker file could not be close\");\n                    }\n                } // if main application is not restarted, update state\n                else {\n                    anjay_advanced_fw_update_set_state_and_result(\n                            afu_logic.anjay, i, ANJAY_ADVANCED_FW_UPDATE_STATE_IDLE,\n                            ANJAY_ADVANCED_FW_UPDATE_RESULT_SUCCESS);\n                }\n            }\n        }\n\n        // update application\n        if (update_iid[AFU_DEFAULT_FIRMWARE_INSTANCE_IID] == true) {\n            avs_log(advance_fu, INFO, \"Perform update on default instance\");\n\n            char new_firm_name[AFU_FILE_NAME_STR_MAX_LEN] = { 0 };\n            char marker_name[AFU_FILE_NAME_STR_MAX_LEN] = { 0 };\n            get_firmware_download_name(AFU_DEFAULT_FIRMWARE_INSTANCE_IID,\n                                    new_firm_name);\n            get_marker_file_name(AFU_DEFAULT_FIRMWARE_INSTANCE_IID, marker_name);\n\n            if (chmod(new_firm_name, 0700) == -1) {\n                avs_log(advance_fu, ERROR, \"Could not make firmware executable: %s\",\n                        strerror(errno));\n                return -1;\n            }\n            // Create a marker file, so that the new process knows it is the\n            // \"upgraded\" one\n            FILE *marker = fopen(marker_name, \"w\");\n            if (!marker) {\n                avs_log(advance_fu, ERROR, \"Marker file could not be created\");\n                return -1;\n            }\n            if (fclose(marker)) {\n                avs_log(advance_fu, ERROR, \"Marker file could not be close\");\n            }\n\n            assert(ENDPOINT_NAME);\n\n            // If the call below succeeds, the firmware is considered as \"upgraded\",\n            // and we hope the newly started client registers to the Server.\n            (void) execl(new_firm_name, new_firm_name, ENDPOINT_NAME, NULL);\n            avs_log(advance_fu, ERROR, \"execl() failed: %s\", strerror(errno));\n            // If we are here, it means execl() failed. Marker file MUST now be\n            // removed, as the firmware update failed.\n            unlink(marker_name);\n            return -1;\n        }\n\n        // update firmware version\n        refresh_fw_version();\n\n        // clear conflicting and linked instances in the objects\n        for (anjay_iid_t i = 0; i < AFU_NUMBER_OF_FIRMWARE_INSTANCES; i++) {\n            if (update_iid[i] == true) {\n                anjay_advanced_fw_update_set_conflicting_instances(afu_logic.anjay,\n                                                                i, NULL, 0);\n                anjay_advanced_fw_update_set_linked_instances(afu_logic.anjay, i,\n                                                            NULL, 0);\n                // clear conflicting and linked instances about this object from\n                // other objects\n                for (anjay_iid_t j = 0; j < AFU_NUMBER_OF_FIRMWARE_INSTANCES; j++) {\n                    if (i != j) {\n                        remove_linked_instance(i, j);\n                        remove_conflicting_instance(i, j);\n                    }\n                }\n            }\n        }\n\n        return 0;\n    }\n\nAt the very end we will look at the implementation of ``fw_update_common_reset``. Note that this function will be called\nnot only in case of failure but also after a successful update if there is no reboot. In addition to removing the downloaded\nfiles, we clean `linked` and `conflicting` resources from the instance and using the ``remove_conflicting_instance`` and\n``remove_linked_instance`` functions, we remove the information about given instance from the other instances.\nPlease note that as in the case of ``fw_update_common_perform_upgrade`` we do not check the state of each instance,\nperhaps in your implementation before clearing the `linked` and `conflicting` lists, you will need to include logic\nto check the status of the update.\n\n.. highlight:: c\n.. snippet-source:: examples/tutorial/firmware-update/advanced-firmware-update/src/advanced_firmware_update.c\n\n    static void remove_linked_instance(anjay_iid_t iid, anjay_iid_t target_iid) {\n        const anjay_iid_t *linked_instances;\n        anjay_iid_t linked_target_iids[AFU_NUMBER_OF_FIRMWARE_INSTANCES - 1];\n        size_t linked_iids_count = 0;\n        size_t new_linked_iids_count = 0;\n\n        // get linked instances\n        anjay_advanced_fw_update_get_linked_instances(\n                afu_logic.anjay, iid, &linked_instances, &linked_iids_count);\n        // remove target_iid from the list\n        for (size_t i = 0; i < linked_iids_count; i++) {\n            if (linked_instances[i] != target_iid) {\n                linked_target_iids[new_linked_iids_count++] = linked_instances[i];\n            }\n        }\n        // update linked list\n        anjay_advanced_fw_update_set_linked_instances(\n                afu_logic.anjay, iid, linked_target_iids, new_linked_iids_count);\n    }\n\n    static void remove_conflicting_instance(anjay_iid_t iid,\n                                            anjay_iid_t target_iid) {\n        const anjay_iid_t *conflicting_instances;\n        anjay_iid_t conflicting_target_iids[AFU_NUMBER_OF_FIRMWARE_INSTANCES - 1];\n        size_t conflicting_iids_count = 0;\n        size_t new_conflicting_iids_count = 0;\n\n        // get conflicting instances\n        anjay_advanced_fw_update_get_conflicting_instances(afu_logic.anjay, iid,\n                                                        &conflicting_instances,\n                                                        &conflicting_iids_count);\n        // remove target_iid from the list\n        for (size_t i = 0; i < conflicting_iids_count; i++) {\n            if (conflicting_instances[i] != target_iid) {\n                conflicting_target_iids[new_conflicting_iids_count++] =\n                        conflicting_instances[i];\n            }\n        }\n        // update conflicting list\n        anjay_advanced_fw_update_set_conflicting_instances(\n                afu_logic.anjay, iid, conflicting_target_iids,\n                new_conflicting_iids_count);\n    }\n\n.. highlight:: c\n.. snippet-source:: examples/tutorial/firmware-update/advanced-firmware-update/src/advanced_firmware_update.c\n\n    void fw_update_common_reset(anjay_iid_t iid, void *user_ptr) {\n        (void) user_ptr;\n\n        char new_firm_name[AFU_FILE_NAME_STR_MAX_LEN] = { 0 };\n        get_firmware_download_name(iid, new_firm_name);\n\n        // Reset can be issued even if the download never started\n        if (afu_logic.new_firmware_file[iid]) {\n            // We ignore the result code of fclose(), as fw_reset() can't fail\n            (void) fclose(afu_logic.new_firmware_file[iid]);\n            // and reset our global state to initial value\n            afu_logic.new_firmware_file[iid] = NULL;\n        }\n        // Finally, let's remove any downloaded payload\n        unlink(new_firm_name);\n\n        // clear conflicting and linked instances in the object\n        anjay_advanced_fw_update_set_conflicting_instances(afu_logic.anjay, iid,\n                                                        NULL, 0);\n        anjay_advanced_fw_update_set_linked_instances(afu_logic.anjay, iid, NULL,\n                                                    0);\n        // clear conflicting and linked instances about this object from other\n        // objects\n        for (anjay_iid_t i = 0; i < AFU_NUMBER_OF_FIRMWARE_INSTANCES; i++) {\n            if (i != iid) {\n                remove_linked_instance(i, iid);\n                remove_conflicting_instance(i, iid);\n            }\n        }\n    }\n\nInstalling the Advanced Firmware Update module\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nThe ``afu_update_install`` function is similar to ``fw_update_install`` from :doc:`../FU-BasicImplementation`.\nFirst we call ``anjay_advanced_fw_update_install`` then for each instance we check the existence of a \"marker\" file,\nbased on which we call ``anjay_advanced_fw_update_instance_add`` with the appropriate arguments. For additional files,\nwe create them if they do not exist and fill them with the default content (`AFU_ADD_FILE_DEFAULT_CONTENT`).\nFinally, we call the ``refresh_fw_version`` function so that the ``fw_update_common_get_current_version`` callback\ncan return the correct value.\n\n.. highlight:: c\n.. snippet-source:: examples/tutorial/firmware-update/advanced-firmware-update/src/advanced_firmware_update.c\n\n    static const char *fw_update_common_get_current_version(anjay_iid_t iid,\n                                                            void *user_ptr) {\n        (void) user_ptr;\n\n        return (const char *) afu_logic.fw_version[iid];\n    }\n\n    static const anjay_advanced_fw_update_handlers_t handlers = {\n        .stream_open = fw_stream_open,\n        .stream_write = fw_update_common_write,\n        .stream_finish = fw_update_common_finish,\n        .reset = fw_update_common_reset,\n        .get_current_version = fw_update_common_get_current_version,\n        .perform_upgrade = fw_update_common_perform_upgrade\n    };\n\n    int afu_update_install(anjay_t *anjay) {\n        anjay_advanced_fw_update_initial_state_t state;\n        char marker_name[AFU_FILE_NAME_STR_MAX_LEN] = { 0 };\n        char file_name[AFU_FILE_NAME_STR_MAX_LEN] = { 0 };\n\n        memset(&state, 0, sizeof(state));\n\n        afu_logic.anjay = anjay;\n\n        anjay_advanced_fw_update_global_config_t config = {\n            .prefer_same_socket_downloads = true\n        };\n        int result = anjay_advanced_fw_update_install(anjay, &config);\n        if (result) {\n            avs_log(advance_fu, ERROR,\n                    \"Could not install advanced firmware object: %d\", result);\n            return -1;\n        }\n\n        // check if application was updated\n        get_marker_file_name(AFU_DEFAULT_FIRMWARE_INSTANCE_IID, marker_name);\n        if (access(marker_name, F_OK) != -1) {\n            avs_log(advance_fu, INFO, \"Application update succeded\");\n            state.result = ANJAY_ADVANCED_FW_UPDATE_RESULT_SUCCESS;\n            unlink(marker_name);\n        }\n        result = anjay_advanced_fw_update_instance_add(\n                anjay, AFU_DEFAULT_FIRMWARE_INSTANCE_IID, \"application\", &handlers,\n                NULL, &state);\n        if (result) {\n            avs_log(advance_fu, ERROR,\n                    \"Could not add default application instance: %d\", result);\n            return -1;\n        }\n\n        // check if additional files were updated, if not create it with default\n        // value\n        for (anjay_iid_t i = 1; i < AFU_NUMBER_OF_FIRMWARE_INSTANCES; i++) {\n            memset(marker_name, 0, sizeof(marker_name));\n            get_marker_file_name(i, marker_name);\n            if (access(marker_name, F_OK) != -1) {\n                avs_log(advance_fu, INFO,\n                        \"Additional file with idd: %d update succeded\", i);\n                state.result = ANJAY_ADVANCED_FW_UPDATE_RESULT_SUCCESS;\n                unlink(marker_name);\n            } else {\n                state.result = ANJAY_ADVANCED_FW_UPDATE_RESULT_INITIAL;\n            }\n\n            memset(file_name, 0, sizeof(file_name));\n            get_add_firmware_file_name(i, file_name);\n            // create file only if not exist\n            if (access(file_name, F_OK) != 0) {\n                FILE *stream = fopen(file_name, \"wb\");\n                if (!stream) {\n                    avs_log(advance_fu, ERROR, \"Could not open %s\", file_name);\n                    return -1;\n                }\n                if (fwrite(AFU_ADD_FILE_DEFAULT_CONTENT,\n                        strlen(AFU_ADD_FILE_DEFAULT_CONTENT), 1, stream)\n                        != 1) {\n                    avs_log(advance_fu, ERROR, \"Could not write to %s\", file_name);\n                    fclose(stream);\n                    return -1;\n                }\n                if (fclose(stream)) {\n                    avs_log(advance_fu, ERROR, \"Could not close %s\", file_name);\n                    return -1;\n                }\n            }\n\n            snprintf(afu_logic.instance_name[i], AFU_INSTANCE_NAME_STR_MAX_LEN,\n                    \"add_img_%d\", i);\n            result = anjay_advanced_fw_update_instance_add(\n                    anjay, i, afu_logic.instance_name[i], &handlers, NULL, &state);\n            if (result) {\n                avs_log(advance_fu, ERROR,\n                        \"Could not add the additional image instance: %d\", result);\n                return -1;\n            }\n        }\n\n        if (refresh_fw_version()) {\n            return -1;\n        }\n\n        return 0;\n    }\n"
  },
  {
    "path": "doc/sphinx/source/FirmwareUpdateTutorial/FU-AdvancedFirmwareUpdate/FU-AFU-Examples.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nExamples\n========\n\n.. contents:: :local:\n\nExample client data model - initial state\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nLwm2M Firmware update Object - Application component [/33629/0]\n***************************************************************\n\nThis component governs the package for the main application firmware that\nimplements the core device functionality.\n\n.. flat-table::\n   :header-rows: 1\n   :widths: 50 20 25 20 35\n\n   * - Resource Name\n     - Resource ID\n     - Resource Instance ID\n     - Value\n     - Notes\n   * - Package\n     - 0\n     -\n     -\n     -\n   * - Package URI\n     - 1\n     -\n     -\n     -\n   * - State\n     - 3\n     -\n     - 0\n     - Idle\n   * - Update Result\n     - 5\n     -\n     - 0\n     - Initial value\n   * - :rspan:`3` Firmware Update Protocol Support\n     - :rspan:`3` 8\n     - 0\n     - 0\n     - CoAP\n   * - 1\n     - 1\n     - CoAPs\n   * - 2\n     - 2\n     - HTTP\n   * - 3\n     - 3\n     - HTTPS\n   * - Firmware Update Delivery Method\n     - 9\n     -\n     - 2\n     - Both push and pull\n   * - Component Name\n     - 14\n     -\n     - Application\n     -\n   * - Current version\n     - 15\n     -\n     - 1.0\n     -\n   * - Linked Instances\n     - 16\n     -\n     -\n     -\n   * - Conflicting Instances\n     - 17\n     -\n     -\n     -\n\nLwm2M Firmware update Object - Trusted firmware component [/33629/1]\n********************************************************************\n\nThis component governs the TEE (Trusted Execution Environment) firmware package.\n\n.. flat-table::\n   :header-rows: 1\n   :widths: 50 20 25 20 35\n\n   * - Resource Name\n     - Resource ID\n     - Resource Instance ID\n     - Value\n     - Notes\n   * - Package\n     - 0\n     -\n     -\n     -\n   * - Package URI\n     - 1\n     -\n     -\n     -\n   * - State\n     - 3\n     -\n     - 0\n     - Idle\n   * - Update Result\n     - 5\n     -\n     - 0\n     - Initial value\n   * - :rspan:`3` Firmware Update Protocol Support\n     - :rspan:`3` 8\n     - 0\n     - 0\n     - CoAP\n   * - 1\n     - 1\n     - CoAPs\n   * - 2\n     - 2\n     - HTTP\n   * - 3\n     - 3\n     - HTTPS\n   * - Firmware Update Delivery Method\n     - 9\n     -\n     - 2\n     - Both push and pull\n   * - Component Name\n     - 14\n     -\n     - TEE\n     -\n   * - Current version\n     - 15\n     -\n     - 1.1\n     -\n   * - Linked Instances\n     - 16\n     -\n     -\n     -\n   * - Conflicting Instances\n     - 17\n     -\n     -\n     -\n\nLwm2M Firmware update Object - Bootloader [/33629/2]\n****************************************************\n\nThis component governs the package for the device's bootloader.\n\n.. flat-table::\n   :header-rows: 1\n   :widths: 50 20 25 20 35\n\n   * - Resource Name\n     - Resource ID\n     - Resource Instance ID\n     - Value\n     - Notes\n   * - Package\n     - 0\n     -\n     -\n     -\n   * - Package URI\n     - 1\n     -\n     -\n     -\n   * - State\n     - 3\n     -\n     - 0\n     - Idle\n   * - Update Result\n     - 5\n     -\n     - 0\n     - Initial value\n   * - :rspan:`3` Firmware Update Protocol Support\n     - :rspan:`3` 8\n     - 0\n     - 0\n     - CoAP\n   * - 1\n     - 1\n     - CoAPs\n   * - 2\n     - 2\n     - HTTP\n   * - 3\n     - 3\n     - HTTPS\n   * - Firmware Update Delivery Method\n     - 9\n     -\n     - 2\n     - Both push and pull\n   * - Component Name\n     - 14\n     -\n     - Bootloader\n     -\n   * - Current version\n     - 15\n     -\n     - 2.1\n     -\n   * - Linked Instances\n     - 16\n     -\n     -\n     -\n   * - Conflicting Instances\n     - 17\n     -\n     -\n     -\n\nLwm2M Firmware update Object - Modem [/33629/3]\n***********************************************\n\nThis component governs the firmware for the cellular module that the device\nuses for radio communication. The module may be located on a separate IC\npackage from the main processor or microcontroller.\n\n.. flat-table::\n   :header-rows: 1\n   :widths: 50 20 25 20 35\n\n   * - Resource Name\n     - Resource ID\n     - Resource Instance ID\n     - Value\n     - Notes\n   * - Package\n     - 0\n     -\n     -\n     -\n   * - Package URI\n     - 1\n     -\n     -\n     -\n   * - State\n     - 3\n     -\n     - 0\n     - Idle\n   * - Update Result\n     - 5\n     -\n     - 0\n     - Initial value\n   * - :rspan:`3` Firmware Update Protocol Support\n     - :rspan:`3` 8\n     - 0\n     - 0\n     - CoAP\n   * - 1\n     - 1\n     - CoAPs\n   * - 2\n     - 2\n     - HTTP\n   * - 3\n     - 3\n     - HTTPS\n   * - Firmware Update Delivery Method\n     - 9\n     -\n     - 2\n     - Both push and pull\n   * - Component Name\n     - 14\n     -\n     - Modem\n     -\n   * - Current version\n     - 15\n     -\n     - 22.01\n     -\n   * - Linked Instances\n     - 16\n     -\n     -\n     -\n   * - Conflicting Instances\n     - 17\n     -\n     -\n     -\n\nExample upgrade scenarios\n^^^^^^^^^^^^^^^^^^^^^^^^^\n\nVersion conflict\n****************\n\nLet's assume that the Application firmware version 2.0 requires the TEE\nfirmware to also be updated to version 2.0 or later.\n\n#. The LwM2M Server writes ``http://example.com/app_2.0.bin`` to resource\n   ``/33629/0/1`` (Package URI of the Application component)\n#. The instance enters the Downloading state, downloads the package, and enters\n   the Downloaded state\n#. Resource ``/33629/0/17`` (Conflicting instances) changes the value to: ``[0]\n   = 33629:1``; this informs the server that instance ``/33629/1`` requires\n   attention due to dependency conflict\n#. If the server nevertheless issues Execute on ``/33629/0/2``, the device\n   immediately reverts to the Downloaded state, with Update Result set to 13\n   (Dependency error)\n#. The LwM2M Server then writes ``http://example.com/tee_2.0.bin`` to resource\n   ``/33629/1/1`` (Package URI of the TEE component)\n#. The instance enters the Downloading state, downloads the package, and enters\n   the Downloaded state\n#. Resource ``/33629/0/17`` (Conflicting instances) changes value back to empty.\n#. Resource ``/33629/0/16`` (Linked instances) changes the value to: ``[0] =\n   33629:1``; resource ``/33629/1/16`` (Linked instances) changes the value to:\n   ``[0] = 33629:0``; this signifies that by default, the device will upgrade\n   both when the Update operation is executed on either of the instances\n#. The LwM2M Server issues Execute on ``/33629/0/2``\n#. The device enters Updating state, performs an update of both components,\n   reboots, and reconnects to the server\n#. The State on both ``/33629/0`` and ``/33629/1`` instances read 0 (idle),\n   Update Result on both is 1 (success), and the version numbers have been\n   updated so that ``/33629/0/15`` = 2.0 and ``/33629/0/16`` = 2.0\n\nMulti-component package\n***********************\n\n#. The LwM2M Server writes ``http://example.com/app_tee_2.1.zip`` to\n   ``/33629/0/1`` (Package URI of the Application component)\n#. Instance 0 enters the Downloading state, downloads the package, and the\n   device logic detects that the file contains packages for both the Application\n   and TEE components\n#. Both instance 0 and instance 1 enter the Downloaded state\n\nConflicting downloads\n*********************\n\n#. The LwM2M Server writes ``http://example.com/tee_2.0.bin`` to ``/33629/1/1``\n   (Package URI of the TEE component)\n#. Instance 1 enters the Downloading state, downloads the package, and enters\n   the Downloaded state\n#. The LwM2M Server writes ``http://example.com/app_tee_2.1.zip`` to\n   ``33629/0/1`` (Package URI of the Application component)\n#. Instance 0 enters the Downloading state, downloads at least some part of the\n   package, and then the device logic detects that the file contains packages\n   for both the Application and TEE components\n#. The device aborts the download; instance 0 reverts to the Idle state, with\n   Update Result set to 12 (Conflicting state); instance 1 remains in the\n   Downloaded state, with the 2.0 package in memory.\n#. Resource ``/33629/0/17`` changes the value to: ``[0] = 33629:1``; this serves\n   as the detail for the “Conflicting state” error, informing on which instances\n   were conflicting\n\nImplicit and explicit linked updates\n************************************\n\n#. The LwM2M server writes the following values:\n    #. ``http://example.com/app_2.0.bin`` to resource ``/33629/0/1`` (Package\n       URI of the Application component)\n    #. ``http://example.com/tee_2.0.bin`` to resource ``/33629/1/1`` (Package\n       URI of the TEE component)\n    #. ``http://example.com/boot_2.2.bin`` to resource ``/33629/2/1`` (Package\n       URI of the Bootloader component)\n    #. ``http://example.com/modem_22.02.bin`` to resource ``/33629/3/1``\n       (Package URI of the Modem component)\n#. The device downloads the packages, eventually all the instances enter the\n   Downloaded state\n#. Resource ``/33629/0/16`` (Linked instances) changes the value to: ``[0] =\n   33629:1``; resource ``/33629/1/16`` (Linked instances) changes the value to:\n   ``[0] = 33629:0``; this signifies that by default, the device will upgrade\n   instances 0 and 1 in one go when the Update operation is executed on either\n   of them\n#. Either of the sub-scenarios described below follows\n\nImplicit linked update\n**********************\n\n#. The LwM2M server issues Execute with no arguments on ``/33629/0/2`` or\n   ``/33629/1/2``\n#. The device enters Updating state, performs update of the Application and TEE\n   components, reboots, and reconnects to the server\n#. The State on both ``/33629/0`` and ``/33629/1`` instances read 0 (idle),\n   Update Result on both is 1 (success), and the version numbers have been\n   updated\n#. Instances ``/33629/2`` and ``/33629/3`` remain in the Downloaded state if\n   possible\n\nExplicit linked update\n**********************\n\n#. The LwM2M server issues Execute on ``/33629/0/2`` with argument payload:\n   ``“0='</33629/1>,</33629/2>,</33629/3>'”``\n#. The device enters Updating state, performs an update of all four components,\n   reboots, and reconnects to the server\n#. The State on all instances read 0 (idle), Update Result on all of them is 1\n   (success), and the version numbers have been updated\n\nExplicit single component update\n********************************\n\n#. The LwM2M server issues Execute on ``/33629/1/2`` with argument payload:\n   ``“0”``\n#. Instance 1 enters Updating state, the device performs update of only the TEE\n   component (ignoring the Linked Instances), and reconnects to the server\n#. The State on instance ``/33629/1`` reads 0 (idle), corresponding Update\n   Result is 1 (success), and the version number has been updated\n#. Instances ``/33629/0``, ``/33629/2`` and ``/33629/3`` remain in the\n   Downloaded state if possible\n\nExplicit single component update - unsuccessful\n***********************************************\n\n#. The LwM2M server issues Execute on ``/33629/0/2`` with argument payload:\n   ``“0”``\n#. Instance 0 immediately reverts to the Downloaded state, with Update Result\n   set to 13 (Dependency error), because application version 2.0 requires the\n   TEE component to be updated to version 2.0 first or at the same time\n#. Resource ``/33629/0/17`` changes value to: ``[0] = 33629:1``; this serves as\n   the detail for the “Dependency error” result, informing on which instances\n   were conflicting\n"
  },
  {
    "path": "doc/sphinx/source/FirmwareUpdateTutorial/FU-AdvancedFirmwareUpdate/FU-AFU-ResourceDefinitions.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nResource definitions\n====================\n\n.. contents:: :local:\n\nPackage (/33629/x/0)\n^^^^^^^^^^^^^^^^^^^^\n\n:ID: 0\n:Operations: W\n:Instances: Single\n:Mandatory: Yes\n:Type: Opaque\n:Range or Enumeration: \\-\n:Units: \\-\n:Description:\n  Firmware package\n\nPackage URI (/33629/x/1)\n^^^^^^^^^^^^^^^^^^^^^^^^\n\n:ID: 1\n:Operations: RW\n:Instances: Single\n:Mandatory: Yes\n:Type: String\n:Range or Enumeration: 0..255\n:Units: \\-\n:Description:\n  URI from where the device can download the firmware package by an alternative\n  mechanism. As soon as the device has received the Package URI it performs the\n  download at the next practical opportunity. The URI format is defined in\n  ``RFC 3986``. For example, ``coaps://example.org/firmware`` is a\n  syntactically valid URI. The URI scheme determines the protocol to be used.\n  For CoAP this endpoint MAY be an LwM2M Server but does not necessarily need to\n  be. A CoAP server implementing block-wise transfer is sufficient as a server\n  hosting a firmware repository and the expectation is that this server merely\n  serves as a separate file server making firmware packages available to LwM2M\n  Clients.\n\nUpdate (/33629/x/2)\n^^^^^^^^^^^^^^^^^^^\n\n:ID: 2\n:Operations: E\n:Instances: Single\n:Mandatory: Yes\n:Type: \\-\n:Range or Enumeration: \\-\n:Units: \\-\n:Description:\n  Updates firmware by using the firmware package stored in Package, or, by\n  using the firmware downloaded from the Package URI. This Resource is only\n  executable when the value of the State Resource is Downloaded.\n\n  If multiple instances of the Advanced Firmware Update object are in the\n  Downloaded state, the device MAY update multiple components in one go. In\n  this case, the Linked Instances resource MUST list all other components that\n  will be updated alongside the current one.\n\n  The server MAY override this behavior by including an argument 0 in the\n  Execute operation. If the argument is present with no value, the client MUST\n  attempt to update only the component handled by the current instance. If the\n  argument is present with a value containing a list of Advanced Firmware\n  Update object instances specified as a Core Link Format (so that the argument\n  may read, for example: ``0=</33629/1>,</33629/2>``), the client MUST attempt\n  to update the components handled by the current instance and the instances\n  listed in the argument, and MUST NOT attempt to update any other components.\n  If the client is not able to satisfy such a request, the update process shall\n  fail with the Update Result resource set to 13.\n\n  If the downloaded packages are incompatible with at least one of the packages\n  installed on other components, and compatible updates for them are not\n  downloaded (i.e., the State resource in an instance corresponding to the\n  conflicting component is not Downloaded), the update process shall also fail\n  with the Update Result resource set to 13.\n\n  When multiple components are upgraded as part of a single Update operation,\n  the device SHOULD upgrade them in a transactional fashion (i.e., all are\n  updated successfully, or all are reverted in case of error), and MUST perform\n  the upgrade in a way that ensures that the device will not be rendered\n  unbootable due to partial errors.\n\nState (/33629/x/3)\n^^^^^^^^^^^^^^^^^^\n\n:ID: 3\n:Operations: R\n:Instances: Single\n:Mandatory: Yes\n:Type: Integer\n:Range or Enumeration: 0..3\n:Units: \\-\n:Description:\n  Indicates current state with respect to this firmware update. This value is\n  set by the LwM2M Client.\n\n  | **0:** Idle (before downloading or after successful updating)\n  | **1:** Downloading (The data sequence is on the way)\n  | **2:** Downloaded\n  | **3:** Updating\n\n  If writing the firmware package to Package Resource has completed, or, if the\n  device has downloaded the firmware package from the Package URI the state\n  changes to Downloaded. The device MAY support packages containing code for\n  multiple components in a single file, in which case downloading the package in\n  any instance of the Advanced Firmware Update object that is valid for it,\n  MUST set the State resource to 2 in instances handling all components that\n  are affected by the downloaded package; if the State of any of such instances\n  was different than 0, the package MUST be rejected and the Update Result\n  resource set to 12.\n\n  Writing an empty string to Package URI Resource or setting the Package\n  Resource to **NULL** (``\\0``), resets the Advanced Firmware Update State\n  Machine: the State Resource value is set to Idle and the Update Result\n  Resource value is set to 0. The device should remove the downloaded firmware\n  package when the state is reset to Idle.\n\n  When in Downloaded state, and the executable Resource Update is triggered,\n  the state changes to Updating if the update starts immediately. For devices\n  that support a user interface and the deferred update functionality, the user\n  may be allowed to defer the firmware update to a later time. In this case,\n  the state stays in the Downloaded state and the Update Result is set to 11.\n  Once a user accepts the firmware update, the state changes to Updating. When\n  the user deferred the update, the device will continue operations normally\n  until the user approves the firmware update or an automatic update starts. It\n  will not block any operation on the device.\n\n  If the Update Resource failed, the state may return to either Downloaded or\n  Idle depending on the underlying reason of update failure, e.g. Integrity\n  Check Failure results in the client moving to the Idle state. If performing\n  the Update or Cancel operation was successful, the state changes to Idle. The\n  Advanced Firmware Update state machine is illustrated in the respective LwM2M\n  specification.\n\nUpdate Result (/33629/x/5)\n^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n:ID: 5\n:Operations: R\n:Instances: Single\n:Mandatory: Yes\n:Type: Integer\n:Range or Enumeration: 0..13\n:Units: \\-\n:Description:\n  Contains the result of downloading or updating the firmware.\n\n  | **0**: Initial value. Once the updating process is initiated (Download\n    /Update), this Resource MUST be reset to Initial value.\n  | **1:** Firmware updated successfully.\n  | **2:** Not enough flash memory for the new firmware package.\n  | **3:** Out of RAM during the downloading process.\n  | **4:** Connection lost during the downloading process.\n  | **5:** Integrity check failure for new downloaded package.\n  | **6:** Unsupported package type.\n  | **7:** Invalid URI.\n  | **8:** Firmware update failed.\n  | **9:** Unsupported protocol. An LwM2M client indicates the failure to\n    retrieve the firmware package using the URI provided in the Package URI\n    resource by writing the value 9 to the ``/33629/0/5`` (Update Result\n    resource) when the URI contained a URI scheme unsupported by the client.\n    Consequently, the LwM2M Client is unable to retrieve the firmware package\n    using the URI provided by the LwM2M Server in the Package URI when it\n    refers to an unsupported protocol.\n  | **10:** Firmware update cancelled. A Cancel operation has been executed\n    successfully.\n  | **11:** Firmware update deferred.\n  | **12:** Conflicting state. Multi-component firmware package download is\n    rejected before entering the Downloaded state because it conflicts with an\n    already downloaded package in a different object instance.\n  | **13:** Dependency error. The Update operation failed because the component\n    package requires some other component to be updated first or at the same\n    time.\n\nPkgName (/33629/x/6)\n^^^^^^^^^^^^^^^^^^^^\n\n:ID: 6\n:Operations: R\n:Instances: Single\n:Mandatory: No\n:Type: String\n:Range or Enumeration: \\-\n:Units: \\-\n:Description:\n  Name of the Firmware Package. If this resource is supported, it shall contain\n  the name of the downloaded package when the State is 2 (Downloaded) or 3\n  (Updating); otherwise it MAY be empty.\n\nPkgVersion (/33629/x/7)\n^^^^^^^^^^^^^^^^^^^^^^^\n\n:ID: 7\n:Operations: R\n:Instances: Single\n:Mandatory: No\n:Type: String\n:Range or Enumeration: \\-\n:Units: \\-\n:Description:\n  Version of the Firmware package. If this resource is supported, it shall\n  contain the version of the downloaded package when the State is 2\n  (Downloaded) or 3 (Updating); otherwise it MAY be empty.\n\nFirmware Update Protocol Support (/33629/x/8)\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n:ID: 8\n:Operations: R\n:Instances: Multiple\n:Mandatory: No\n:Type: Integer\n:Range or Enumeration: 0..5\n:Units: \\-\n:Description:\n  This resource indicates what protocols the LwM2M Client implements to\n  retrieve firmware packages. The LwM2M server uses this information to decide\n  what URI to include in the Package URI. An LwM2M Server MUST NOT include a URI\n  in the Package URI object that uses a protocol that is unsupported by the\n  LwM2M client. For example, if an LwM2M client indicates that it supports CoAP\n  and CoAPS then an LwM2M Server must not provide an HTTP URI in the Packet URI.\n  The following values are defined by this version of the specification:\n\n  | **0:** CoAP (as defined in ``RFC 7252``) with the additional support for\n    block-wise transfer. CoAP is the default setting.\n  | **1:** CoAPS (as defined in ``RFC 7252``) with the additional support for\n    block-wise transfer\n  | **2:** HTTP 1.1 (as defined in ``RFC 7230``)\n  | **3:** HTTPS 1.1 (as defined in ``RFC 7230``)\n  | **4:** CoAP over TCP (as defined in ``RFC 8323``)\n  | **5:** CoAP over TLS (as defined in ``RFC 8323``)\n\n  Additional values MAY be defined in the future. Any value not understood by\n  the LwM2M Server MUST be ignored.\n\n  The value of this resource SHOULD be the same for all instances of the\n  Advanced Firmware Update object.\n\nFirmware Update Delivery Method (/33629/x/9)\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n:ID: 9\n:Operations: R\n:Instances: Single\n:Mandatory: Yes\n:Type: Integer\n:Range or Enumeration: 0..2\n:Units: \\-\n:Description:\n  The LwM2M Client uses this resource to indicate its support for transferring\n  firmware packages to the client either via the Package Resource (=push) or via\n  the Package URI Resource (=pull) mechanism.\n\n  | **0:** Pull only\n  | **1:** Push only\n  | **2:** Both. In this case the LwM2M Server MAY choose the preferred\n    mechanism for conveying the firmware package to the LwM2M Client.\n\n  The value of this resource SHOULD be the same for all instances of the\n  Advanced Firmware Update object.\n\nCancel (/33629/x/10)\n^^^^^^^^^^^^^^^^^^^^\n\n:ID: 10\n:Operations: E\n:Instances: Single\n:Mandatory: No\n:Type: \\-\n:Range or Enumeration: \\-\n:Units: \\-\n:Description:\n  Cancels firmware update. Cancel can be executed if the device has not\n  initiated the Update process. If the device is in the process of installing\n  the firmware or has already completed installation it MUST respond with\n  Method Not Allowed error code. Upon successful Cancel operation, Update\n  Result Resource is set to 10 and State is set to 0 by the device.\n\nSeverity (/33629/x/11)\n^^^^^^^^^^^^^^^^^^^^^^\n\n:ID: 11\n:Operations: RW\n:Instances: Single\n:Mandatory: No\n:Type: Integer\n:Range or Enumeration: 0..2\n:Units: \\-\n:Description:\n  Severity of the firmware package.\n\n  | **0:** Critical\n  | **1:** Mandatory\n  | **2:** Optional\n\n  This information is useful when the device provides an option for the\n  deferred update. Default value is 1.\n\nLast State Change Time (/33629/x/12)\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n:ID: 12\n:Operations: R\n:Instances: Single\n:Mandatory: No\n:Type: Time\n:Range or Enumeration: \\-\n:Units: \\-\n:Description:\n  This resource stores the time when the State resource is changed. Device\n  updates this resource before making any change to the State.\n\nMaximum Defer Period (/33629/x/13)\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n:ID: 13\n:Operations: RW\n:Instances: Single\n:Mandatory: No\n:Type: Unsigned Integer\n:Range or Enumeration: \\-\n:Units: s\n:Description:\n  The number of seconds a user can defer the software update. When this time\n  period is over, the device will not prompt the user for update and install it\n  automatically. If the value is 0, a deferred update is not allowed.\n\nComponent Name (/33629/x/14)\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n:ID: 14\n:Operations: R\n:Instances: Single\n:Mandatory: No\n:Type: String\n:Range or Enumeration: \\-\n:Units: \\-\n:Description:\n  Name of the component handled by this instance of the Advanced Firmware\n  Update object.\n\n  This should be a name clearly identifying the component for both humans and\n  machines. The syntax of these names is implementation-specific, but might\n  refer to terms such as “bootloader”, “application”, “modem firmware” etc.\n\nCurrent Version (/33629/x/15)\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n:ID: 15\n:Operations: R\n:Instances: Single\n:Mandatory: Yes\n:Type: String\n:Range or Enumeration: \\-\n:Units: \\-\n:Description:\n  Version number of the package that is currently installed and running for the\n  component handled by this instance of the Advanced Firmware Update object.\n\n  For the main component (the one that contains code for the core part of the\n  device's functionality), this value SHOULD be the same as the Firmware\n  Version resource in the Device object (``/3/0/3``).\n\nLinked Instances (/33629/x/16)\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n:ID: 16\n:Operations: R\n:Instances: Multiple\n:Mandatory: No\n:Type: Objlnk\n:Range or Enumeration: \\-\n:Units: \\-\n:Description:\n  When multiple instances of the Advanced Firmware Update object are in the\n  Downloaded state, this resource shall list all other instances that will be\n  updated in a batch if the Update resource is executed on this instance with\n  no arguments. Each of the instances listed MUST be in the Downloaded state.\n\n  The resource MUST NOT contain references to any objects other than the\n  Advanced Firmware Update object.\n\nConflicting Instances (/33629/x/17)\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n:ID: 17\n:Operations: R\n:Instances: Multiple\n:Mandatory: No\n:Type: Objlnk\n:Range or Enumeration: \\-\n:Units: \\-\n:Description:\n  When the download or update fails and the Update Result resource is set to 12\n  or 13, this resource MUST be present and contain references to the Advanced\n  Firmware Update object instances that caused the conflict.\n\n  In other states, this resource MAY be absent or empty, or it MAY contain\n  references to the Advanced Firmware Update object instances which are in a\n  state conflicting with the possibility of successfully updating this\n  instance.\n\n  The resource MUST NOT contain references to any objects other than the\n  Advanced Firmware Update object.\n"
  },
  {
    "path": "doc/sphinx/source/FirmwareUpdateTutorial/FU-AdvancedFirmwareUpdate/FU-AFU-StateDiagram.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\n\nFirmware Update State Diagram\n=============================\n\n.. figure:: _images/FU-AFU-StateDiagram.svg\n\nThe figure above shows an implementation of the **Advanced Firmware Update**\nmechanism. The state diagram consists of states, drawn as rounded rectangles,\nand transitions, drawn as arrows connecting the states. The syntax of the\ntransition is trigger [guard] / behavior. A trigger is an event that may cause\na transition, a guard is a condition and behavior is an activity that executes\nwhile the transition takes place. The states additionally contain a compartment\nthat includes assertions and variable assignments. For example, the assertion\nin the IDLE state indicates the value of the “Update Result” resource\n(abbreviated as “Res”) must be between 0 and 13. The State resource is set to\nzero (0) when the program is in this IDLE state.\n\nAny operation to the Firmware Update Object that would result in undefined\nbehavior because that specific operation is not identified as a trigger in the\nstate machine (e.g. Write to Package URI while in DOWNLOADING state) SHOULD be\nrejected.\n\nErrors during the Firmware Update process MUST be reported only by the “Update\nResult” resource. For instance, when the LwM2M Server performs a Write\noperation on resource “Package URI”, the LwM2M Client returns a Success or\nFailure for the Write operation. Then if the URI is invalid, it changes its\n“Update Result” resource value to 7.\n"
  },
  {
    "path": "doc/sphinx/source/FirmwareUpdateTutorial/FU-AdvancedFirmwareUpdate/_files/33629.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<!--\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n-->\n\n<LWM2M xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:noNamespaceSchemaLocation=\"http://www.openmobilealliance.org/tech/profiles/LWM2M-v1_1.xsd\">\n\t<Object ObjectType=\"MODefinition\">\n\t\t<Name>Advanced Firmware Update</Name>\n\t\t<Description1><![CDATA[This LwM2M Object enables management of multiple firmware components which are to be updated. This Object includes installing a firmware package, updating firmware, and performing actions after updating firmware. The firmware update MAY require to reboot the device; it will depend on a number of factors, such as the operating system architecture and the extent of the updated software.\nThe envisioned functionality is to allow a LwM2M Client to connect to any LwM2M Server to obtain a firmware package using the object and resource structure defined in this section experiencing communication security protection using TLS/DTLS. There are, however, other design decisions that need to be taken into account to allow a manufacturer of a device to securely install firmware on a device. Examples for such design decisions are how to manage the firmware update repository at the server side (which may include user interface considerations), the techniques to provide additional application layer security protection of the firmware package, how many versions of firmware packages to store on the device, and how to execute the firmware update process considering the hardware specific details of a given IoT hardware product. These aspects are considered to be outside the scope of this version of the specification.\nA LwM2M Server may also instruct a LwM2M Client to fetch a firmware package from a dedicated server (instead of pushing firmware packages to the LwM2M Client). The Package URI resource is contained in the Firmware object and can be used for this purpose.\nA LwM2M Client MUST support block-wise transfer [CoAP_Blockwise] if it implements the Advanced Firmware Update object.\nA LwM2M Server MUST support block-wise transfer. Other protocols, such as HTTP/HTTPs, MAY also be used for downloading firmware updates (via the Package URI resource). For constrained devices it is, however, RECOMMENDED to use CoAP for firmware downloads to avoid the need for additional protocol implementations.\nThis object differs from the Firmware Update object (ID 5) by allowing for multiple instances, with each instance representing a separate “component” of the device’s firmware that can be upgraded independently. The significance of such components is implementation-defined, however the intention is that they might refer to entities such as: bootloaders, application code, cellular modem firmwares, security processor firmwares, etc. Mapping components to lower level entities is also implementation-defined; example implementations include: flash memory partitions, software packages, or virtualized containers.\nIt is expected that firmware components can be upgraded independently in most cases, however the object provides a mechanism for checking version dependencies when a certain order of updates is required, or when multiple components need to be upgraded in tandem.\nInstances of this object SHOULD be static and cannot be managed by the LwM2M Server. The client MUST respond with an error on the transport binding layer signifying a “Method Not Allowed” error upon receiving a Create or Delete request for this object.\nThe main component (i.e., the one that contains code for the core part of the device’s functionality) SHOULD have Instance ID equal to 0.\nA LwM2M implementation may choose to support the Firmare Update object (ID 5), the Advanced Firmware Update object (ID 33629), both, or neither. If both objects are supported, only one of them MUST be used during a firmware update process. LwM2M Servers SHOULD prefer the Advanced Firmware Update object when communicating with LwM2M Clients that support both.]]></Description1>\n\t\t<ObjectID>33629</ObjectID>\n\t\t<ObjectURN>urn:oma:lwm2m:oma:33629</ObjectURN>\n\t\t<LWM2MVersion>1.1</LWM2MVersion>\n\t\t<ObjectVersion>1.0</ObjectVersion>\n\t\t<MultipleInstances>Multiple</MultipleInstances>\n\t\t<Mandatory>Optional</Mandatory>\n\t\t<Resources>\n\t\t\t<Item ID=\"0\">\n\t\t\t\t<Name>Package</Name>\n        <Operations>W</Operations>\n        <MultipleInstances>Single</MultipleInstances>\n\t\t\t\t<Mandatory>Mandatory</Mandatory>\n\t\t\t\t<Type>Opaque</Type>\n\t\t\t\t<RangeEnumeration></RangeEnumeration>\n\t\t\t\t<Units></Units>\n\t\t\t\t<Description><![CDATA[Firmware package]]></Description>\n\t\t\t</Item>\n\t\t\t<Item ID=\"1\">\n\t\t\t\t<Name>Package URI</Name>\n        <Operations>RW</Operations>\n        <MultipleInstances>Single</MultipleInstances>\n\t\t\t\t<Mandatory>Mandatory</Mandatory>\n\t\t\t\t<Type>String</Type>\n\t\t\t\t<RangeEnumeration>0..255</RangeEnumeration>\n\t\t\t\t<Units></Units>\n\t\t\t\t<Description><![CDATA[URI from where the device can download the firmware package by an alternative mechanism. As soon as the device has received the Package URI it performs the download at the next practical opportunity.\nThe URI format is defined in RFC 3986. For example, coaps://example.org/firmware is a syntactically valid URI. The URI scheme determines the protocol to be used. For CoAP this endpoint MAY be a LwM2M Server but does not necessarily need to be. A CoAP server implementing block-wise transfer is sufficient as a server hosting a firmware repository and the expectation is that this server merely serves as a separate file server making firmware packages available to LwM2M Clients.]]></Description>\n\t\t\t</Item>\n\t\t\t<Item ID=\"2\">\n\t\t\t\t<Name>Update</Name>\n        <Operations>E</Operations>\n        <MultipleInstances>Single</MultipleInstances>\n\t\t\t\t<Mandatory>Mandatory</Mandatory>\n\t\t\t\t<Type></Type>\n\t\t\t\t<RangeEnumeration></RangeEnumeration>\n\t\t\t\t<Units></Units>\n\t\t\t\t<Description><![CDATA[Updates firmware by using the firmware package stored in Package, or, by using the firmware downloaded from the Package URI. This Resource is only executable when the value of the State Resource is Downloaded.\nIf multiple instances of the Advanced Firmware Update object are in the Downloaded state, the device MAY update multiple components in one go. In this case, the Linked Instances resource MUST list all other components that will be updated alongside the current one.\nThe server MAY override this behavior by including an argument 0 in the Execute operation. If the argument is present with no value, the client MUST attempt to update only the component handled by the current instance. If the argument is present with a value containing a list of Advanced Firmware Update object instances specified as a Core Link Format (so that the argument may read, for example: 0='</33629/1>,</33629/2>'), the client MUST attempt to update the components handled by the current instance and the instances listed in the argument, and MUST NOT attempt to update any other components. If the client is not able to satisfy such a request, the update process shall fail with the Update Result resource set to 13.\nIf the downloaded packages are incompatible with at least one of the other currently installed components, and compatible updates for them are not downloaded (i.e., the State resource in an instance corresponding to the conflicting component is not Downloaded), the update process shall also fail with the Update Result resource set to 13.\nWhen multiple components are upgraded as part of a single Update operation, the device SHOULD upgrade them in a transactional fashion (i.e., all are updated successfully, or all are reverted in case of error), and MUST perform the upgrade in a way that ensures that the device will not be rendered unbootable due to partial errors.]]></Description>\n\t\t\t</Item>\n\t\t\t<Item ID=\"3\">\n\t\t\t\t<Name>State</Name>\n        <Operations>R</Operations>\n        <MultipleInstances>Single</MultipleInstances>\n\t\t\t\t<Mandatory>Mandatory</Mandatory>\n\t\t\t\t<Type>Integer</Type>\n\t\t\t\t<RangeEnumeration>0..3</RangeEnumeration>\n\t\t\t\t<Units></Units>\n\t\t\t\t<Description><![CDATA[Indicates current state with respect to this firmware update. This value is set by the LwM2M Client.\n0: Idle (before downloading or after successful updating)\n1: Downloading (The data sequence is on the way)\n2: Downloaded\n3: Updating\nIf writing the firmware package to Package Resource has completed, or, if the device has downloaded the firmware package from the Package URI the state changes to Downloaded. The device MAY support packages containing code for multiple components in a single file, in which case downloading the package in any instance of the Advanced Firmware Update object that is valid for it, MUST set the State resource to 2 in instances handling all components that are affected by the downloaded package; if the State of any of such instances was different than 0, the package MUST be rejected and the Update Result resource set to 12.\nWriting an empty string to Package URI Resource or setting the Package Resource to NULL (‘\\0’), resets the Advanced Firmware Update State Machine: the State Resource value is set to Idle and the Update Result Resource value is set to 0. The device should remove the downloaded firmware package when the state is reset to Idle.\nWhen in Downloaded state, and the executable Resource Update is triggered, the state changes to Updating if the update starts immediately. For devices that support a user interface and the deferred update functionality, the user may be allowed to defer the firmware update to a later time. In this case, the state stays in the Downloaded state and the Update Result is set to 11. Once a user accepts the firmware update, the state changes to Updating. When the user deferred the update, the device will continue operations normally until the user approves the firmware update or an automatic update starts. It will not block any operation on the device.\nIf the Update Resource failed, the state may return to either Downloaded or Idle depending on the underlying reason of update failure, e.g. Integrity Check Failure results in the client moving to the Idle state. If performing the Update or Cancel operation was successful, the state changes to Idle. The firmware update state machine is illustrated in the respective LwM2M specification.]]></Description>\n\t\t\t</Item>\n\t\t\t<Item ID=\"5\">\n\t\t\t\t<Name>Update Result</Name>\n        <Operations>R</Operations>\n        <MultipleInstances>Single</MultipleInstances>\n\t\t\t\t<Mandatory>Mandatory</Mandatory>\n\t\t\t\t<Type>Integer</Type>\n\t\t\t\t<RangeEnumeration>0..13</RangeEnumeration>\n\t\t\t\t<Units></Units>\n\t\t\t\t<Description><![CDATA[Contains the result of downloading or updating the firmware\n0: Initial value. Once the updating process is initiated (Download /Update), this Resource MUST be reset to Initial value.\n1: Firmware updated successfully.\n2: Not enough flash memory for the new firmware package.\n3: Out of RAM during the downloading process.\n4: Connection lost during the downloading process.\n5: Integrity check failure for new downloaded package.\n6: Unsupported package type.\n7: Invalid URI.\n8: Firmware update failed.\n9: Unsupported protocol. A LwM2M client indicates the failure to retrieve the firmware package using the URI provided in the Package URI resource by writing the value 9 to the /33629/0/5 (Update Result resource) when the URI contained a URI scheme unsupported by the client. Consequently, the LwM2M Client is unable to retrieve the firmware package using the URI provided by the LwM2M Server in the Package URI when it refers to an unsupported protocol.\n10: Firmware update cancelled. A Cancel operation has been executed successfully.\n11: Firmware update deferred.\n12: Conflicting state. Multi-component firmware package download rejected before entering the Downloaded state because it conflicts with an already downloaded package in a different object instance.\n13: Dependency error. The Update operation failed because the package requires some other component to be updated first or at the same time.]]></Description>\n\t\t\t</Item>\n\t\t\t<Item ID=\"6\">\n\t\t\t\t<Name>PkgName</Name>\n\t\t\t\t<Operations>R</Operations>\n\t\t\t\t<MultipleInstances>Single</MultipleInstances>\n\t\t\t\t<Mandatory>Optional</Mandatory>\n\t\t\t\t<Type>String</Type>\n\t\t\t\t<RangeEnumeration>0..255</RangeEnumeration>\n\t\t\t\t<Units></Units>\n\t\t\t\t<Description><![CDATA[Name of the Firmware Package. If this resource is supported, it shall contain the name of the downloaded package when the State is 2 (Downloaded) or 3 (Updating); otherwise it MAY be empty.]]></Description>\n\t\t\t</Item>\n\t\t\t<Item ID=\"7\">\n\t\t\t\t<Name>PkgVersion</Name>\n\t\t\t\t<Operations>R</Operations>\n\t\t\t\t<MultipleInstances>Single</MultipleInstances>\n\t\t\t\t<Mandatory>Optional</Mandatory>\n\t\t\t\t<Type>String</Type>\n\t\t\t\t<RangeEnumeration>0..255</RangeEnumeration>\n\t\t\t\t<Units></Units>\n\t\t\t\t<Description><![CDATA[Version of the Firmware package. If this resource is supported, it shall contain the version of the downloaded package when the State is 2 (Downloaded) or 3 (Updating); otherwise it MAY be empty.]]></Description>\n\t\t\t</Item>\n\t\t\t<Item ID=\"8\">\n\t\t\t\t<Name>Firmware Update Protocol Support</Name>\n\t\t\t\t<Operations>R</Operations>\n\t\t\t\t<MultipleInstances>Multiple</MultipleInstances>\n\t\t\t\t<Mandatory>Optional</Mandatory>\n\t\t\t\t<Type>Integer</Type>\n\t\t\t\t<RangeEnumeration>0..5</RangeEnumeration>\n\t\t\t\t<Units></Units>\n\t\t\t\t<Description><![CDATA[This resource indicates what protocols the LwM2M Client implements to retrieve firmware packages. The LwM2M server uses this information to decide what URI to include in the Package URI. A LwM2M Server MUST NOT include a URI in the Package URI object that uses a protocol that is unsupported by the LwM2M client. For example, if a LwM2M client indicates that it supports CoAP and CoAPS then a LwM2M Server must not provide an HTTP URI in the Packet URI. The following values are defined by this version of the specification:\n0: CoAP (as defined in RFC 7252) with the additional support for block-wise transfer. CoAP is the default setting.\n1: CoAPS (as defined in RFC 7252) with the additional support for block-wise transfer\n2: HTTP 1.1 (as defined in RFC 7230)\n3: HTTPS 1.1 (as defined in RFC 7230)\n4: CoAP over TCP (as defined in RFC 8323)\n5: CoAP over TLS (as defined in RFC 8323)\nAdditional values MAY be defined in the future. Any value not understood by the LwM2M Server MUST be ignored.\nThe value of this resource SHOULD be the same for all instances of the Advanced Firmware Update object.]]></Description>\n\t\t\t</Item>\n\t\t\t<Item ID=\"9\">\n\t\t\t\t<Name>Firmware Update Delivery Method</Name>\n\t\t\t\t<Operations>R</Operations>\n\t\t\t\t<MultipleInstances>Single</MultipleInstances>\n\t\t\t\t<Mandatory>Mandatory</Mandatory>\n\t\t\t\t<Type>Integer</Type>\n\t\t\t\t<RangeEnumeration>0..2</RangeEnumeration>\n\t\t\t\t<Units></Units>\n\t\t\t\t<Description><![CDATA[The LwM2M Client uses this resource to indicate its support for transferring firmware packages to the client either via the Package Resource (=push) or via the Package URI Resource (=pull) mechanism.\n0: Pull only\n1: Push only\n2: Both. In this case the LwM2M Server MAY choose the preferred mechanism for conveying the firmware package to the LwM2M Client.\nThe value of this resource SHOULD be the same for all instances of the Advanced Firmware Update object.]]></Description>\n\t\t\t</Item>\n\t\t\t<Item ID=\"10\">\n\t\t\t\t<Name>Cancel</Name>\n\t\t\t\t<Operations>E</Operations>\n\t\t\t\t<MultipleInstances>Single</MultipleInstances>\n\t\t\t\t<Mandatory>Optional</Mandatory>\n\t\t\t\t<Type></Type>\n\t\t\t\t<RangeEnumeration></RangeEnumeration>\n\t\t\t\t<Units></Units>\n\t\t\t\t<Description><![CDATA[Cancels firmware update.\nCancel can be executed if the device has not initiated the Update process. If the device is in the process of installing the firmware or has already completed installation it MUST respond with Method Not Allowed error code.\nUpon successful Cancel operation, Update Result Resource is set to 10 and State is set to 0 by the device.]]></Description>\n\t\t\t</Item>\n\t\t\t<Item ID=\"11\">\n\t\t\t\t<Name>Severity</Name>\n\t\t\t\t<Operations>RW</Operations>\n\t\t\t\t<MultipleInstances>Single</MultipleInstances>\n\t\t\t\t<Mandatory>Optional</Mandatory>\n\t\t\t\t<Type>Integer</Type>\n\t\t\t\t<RangeEnumeration>0..2</RangeEnumeration>\n\t\t\t\t<Units></Units>\n\t\t\t\t<Description><![CDATA[Severity of the firmware package.\n0: Critical\n1: Mandatory\n2: Optional\nThis information is useful when the device provides option for the deferred update. Default value is 1.]]></Description>\n\t\t\t</Item>\n\t\t\t<Item ID=\"12\">\n\t\t\t\t<Name>Last State Change Time</Name>\n\t\t\t\t<Operations>R</Operations>\n\t\t\t\t<MultipleInstances>Single</MultipleInstances>\n\t\t\t\t<Mandatory>Optional</Mandatory>\n\t\t\t\t<Type>Time</Type>\n\t\t\t\t<RangeEnumeration></RangeEnumeration>\n\t\t\t\t<Units></Units>\n\t\t\t\t<Description><![CDATA[This resource stores the time when the State resource is changed. Device updates this resource before making any change to the State.]]></Description>\n\t\t\t</Item>\n\t\t\t<Item ID=\"13\">\n\t\t\t\t<Name>Maximum Defer Period</Name>\n\t\t\t\t<Operations>RW</Operations>\n\t\t\t\t<MultipleInstances>Single</MultipleInstances>\n\t\t\t\t<Mandatory>Optional</Mandatory>\n\t\t\t\t<Type>Unsigned Integer</Type>\n\t\t\t\t<RangeEnumeration></RangeEnumeration>\n\t\t\t\t<Units>s</Units>\n\t\t\t\t<Description><![CDATA[The number of seconds a user can defer the software update.\nWhen this time period is over, the device will not prompt the user for update and install it automatically.\nIf the value is 0, a deferred update is not allowed.]]></Description>\n\t\t\t</Item>\n\t\t\t<Item ID=\"14\">\n\t\t\t\t<Name>Component Name</Name>\n\t\t\t\t<Operations>R</Operations>\n\t\t\t\t<MultipleInstances>Single</MultipleInstances>\n\t\t\t\t<Mandatory>Mandatory</Mandatory>\n\t\t\t\t<Type>String</Type>\n\t\t\t\t<RangeEnumeration></RangeEnumeration>\n\t\t\t\t<Units></Units>\n\t\t\t\t<Description><![CDATA[Name of the component handled by this instance of the Advanced Firmware Update object.\nThis should be a name clearly identifying the component for both humans and machines. The syntax of these names is implementation-specific, but might refer to terms such as “bootloader”, “application”, “modem firmware” etc.]]></Description>\n\t\t\t</Item>\n\t\t\t<Item ID=\"15\">\n\t\t\t\t<Name>Current Version</Name>\n\t\t\t\t<Operations>R</Operations>\n\t\t\t\t<MultipleInstances>Single</MultipleInstances>\n\t\t\t\t<Mandatory>Mandatory</Mandatory>\n\t\t\t\t<Type>String</Type>\n\t\t\t\t<RangeEnumeration></RangeEnumeration>\n\t\t\t\t<Units></Units>\n\t\t\t\t<Description><![CDATA[Version number of the package that is currently installed and running for the component handled by this instance of the Advanced Firmware Update object.\nFor the main component (the one that contains code for the core part of the device’s functionality), this value SHOULD be the same as the Firmware Version resource in the Device object (/3/0/3).]]></Description>\n\t\t\t</Item>\n\t\t\t<Item ID=\"16\">\n\t\t\t\t<Name>Linked Instances</Name>\n\t\t\t\t<Operations>R</Operations>\n\t\t\t\t<MultipleInstances>Multiple</MultipleInstances>\n\t\t\t\t<Mandatory>Optional</Mandatory>\n\t\t\t\t<Type>Objlnk</Type>\n\t\t\t\t<RangeEnumeration></RangeEnumeration>\n\t\t\t\t<Units></Units>\n\t\t\t\t<Description><![CDATA[When multiple instances of the Advanced Firmware Update object are in the Downloaded state, this resource shall list all other instances that will be updated in a batch if the Update resource is executed on this instance with no arguments. Each of the instances listed MUST be in the Downloaded state.\nThe resource MUST NOT contain references to any objects other than the Advanced Firmware Update object.]]></Description>\n\t\t\t</Item>\n\t\t\t<Item ID=\"17\">\n\t\t\t\t<Name>Conflicting Instances</Name>\n\t\t\t\t<Operations>R</Operations>\n\t\t\t\t<MultipleInstances>Multiple</MultipleInstances>\n\t\t\t\t<Mandatory>Optional</Mandatory>\n\t\t\t\t<Type>Objlnk</Type>\n\t\t\t\t<RangeEnumeration></RangeEnumeration>\n\t\t\t\t<Units></Units>\n\t\t\t\t<Description><![CDATA[When the download or update fails and the Update Result resource is set to 12 or 13, this resource MUST be present and contain references to the Advanced Firmware Update object instances that caused the conflict.\nIn other states, this resource MAY be absent or empty, or it MAY contain references to the Advanced Firmware Update object instances which are in a state conflicting with the possibility of successfully updating this instance.\nThe resource MUST NOT contain references to any objects other than the Advanced Firmware Update object.]]></Description>\n\t\t\t</Item>\n    </Resources>\n\t\t<Description2><![CDATA[]]></Description2>\n\t</Object>\n</LWM2M>\n"
  },
  {
    "path": "doc/sphinx/source/FirmwareUpdateTutorial/FU-AdvancedFirmwareUpdate.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nAdvanced Firmware Update\n========================\n\n**Advanced Firmware Update** object (``/33629``) is an optional object which\nextends the definition of **Firmware Update** object (``/5``) and allows for\nmultiple instances, with each instance representing a separate “component” of\nthe device's firmware that can be upgraded independently. The significance of\nsuch components is implementation-defined, however, the intention is that they\nmight refer to components such as: bootloaders, application code, cellular\nmodem firmwares, security processor firmwares, etc.\n\nIt is expected that firmware components can be upgraded independently in most\ncases, however, the object provides a mechanism for checking version\ndependencies when a certain order of updates is required, or when multiple\ncomponents need to be upgraded in tandem.\n\n\n:download:`Download: Advanced Firmware Update Object Definition XML <FU-AdvancedFirmwareUpdate/_files/33629.xml>`\n\n.. toctree::\n   :glob:\n   :titlesonly:\n\n   FU-AdvancedFirmwareUpdate/FU-AFU-ResourceDefinitions.rst\n   FU-AdvancedFirmwareUpdate/FU-AFU-StateDiagram.rst\n   FU-AdvancedFirmwareUpdate/FU-AFU-Examples.rst\n   FU-AdvancedFirmwareUpdate/FU-AFU-BasicImplementation.rst\n"
  },
  {
    "path": "doc/sphinx/source/FirmwareUpdateTutorial/FU-BasicImplementation.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nBasic implementation\n====================\n\nProject structure\n^^^^^^^^^^^^^^^^^\n\nWe shall start with the code from :doc:`../BasicClient/BC-Notifications`\nchapter. In the end, our project structure would look as follows:\n\n.. code::\n\n    examples/tutorial/firmware-update/basic-implementation/\n    ├── CMakeLists.txt\n    └── src\n        ├── firmware_update.c\n        ├── firmware_update.h\n        ├── main.c\n        ├── time_object.c\n        └── time_object.h\n\n\nNote the ``firmware_update.c`` and ``firmware_update.h`` are introduced in this\nchapter.\n\n.. _anjay_fw_update_install:\n\nInstalling the Firmware Update module\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nIn order to install the module, we are going to use:\n\n.. highlight:: c\n.. snippet-source:: include_public/anjay/fw_update.h\n\n    int anjay_fw_update_install(\n            anjay_t *anjay,\n            const anjay_fw_update_handlers_t *handlers,\n            void *user_arg,\n            const anjay_fw_update_initial_state_t *initial_state);\n\nThe important arguments for us at this point are ``anjay``, ``handlers``\nand ``user_arg``.\n\nWe already discussed ``handlers`` structure in :ref:`firmware-update-api`, and\nwe will shortly provide simple implementations of required callbacks.\n\n.. note::\n\n    The ``user_arg`` **is a pointer passed to every callback, when Anjay\n    actually calls it**. This pointer can be used by the callback implementation\n    to store any kind of context to operate on. Alternatively, the\n    implementation may set it to `NULL` and rely on the use of global variables.\n\nIn our code, firmware update module installation will be taken care of by\nthe function declared in ``firmware_update.h``:\n\n.. highlight:: c\n.. snippet-source:: examples/tutorial/firmware-update/basic-implementation/src/firmware_update.h\n    :emphasize-lines: 12-17\n\n    #ifndef FIRMWARE_UPDATE_H\n    #define FIRMWARE_UPDATE_H\n    #include <anjay/anjay.h>\n    #include <anjay/fw_update.h>\n\n    /**\n     * Buffer for the endpoint name that will be used when re-launching the client\n     * after firmware upgrade.\n     */\n    extern const char *ENDPOINT_NAME;\n\n    /**\n     * Installs the firmware update module.\n     *\n     * @returns 0 on success, negative value otherwise.\n     */\n    int fw_update_install(anjay_t *anjay);\n\n    #endif // FIRMWARE_UPDATE_H\n\nWe invoke it in ``main.c`` by performing two (highlighted) modifications:\n\n.. highlight:: c\n.. snippet-source:: examples/tutorial/firmware-update/basic-implementation/src/main.c\n    :emphasize-lines: 6, 112\n\n    #include <anjay/anjay.h>\n    #include <anjay/security.h>\n    #include <anjay/server.h>\n    #include <avsystem/commons/avs_log.h>\n\n    #include \"firmware_update.h\"\n    #include \"time_object.h\"\n\n    typedef struct {\n        anjay_t *anjay;\n        const anjay_dm_object_def_t **time_object;\n    } notify_job_args_t;\n\n    // Periodically notifies the library about Resource value changes\n    static void notify_job(avs_sched_t *sched, const void *args_ptr) {\n        const notify_job_args_t *args = (const notify_job_args_t *) args_ptr;\n\n        time_object_notify(args->anjay, args->time_object);\n\n        // Schedule run of the same function after 1 second\n        AVS_SCHED_DELAYED(sched, NULL, avs_time_duration_from_scalar(1, AVS_TIME_S),\n                          notify_job, args, sizeof(*args));\n    }\n\n    // Installs Security Object and adds an instance of it.\n    // An instance of Security Object provides information needed to connect to\n    // LwM2M server.\n    static int setup_security_object(anjay_t *anjay) {\n        if (anjay_security_object_install(anjay)) {\n            return -1;\n        }\n\n        static const char PSK_IDENTITY[] = \"identity\";\n        static const char PSK_KEY[] = \"P4s$w0rd\";\n\n        anjay_security_instance_t security_instance = {\n            .ssid = 1,\n            .server_uri = \"coaps://eu.iot.avsystem.cloud:5684\",\n            .security_mode = ANJAY_SECURITY_PSK,\n            .public_cert_or_psk_identity = (const uint8_t *) PSK_IDENTITY,\n            .public_cert_or_psk_identity_size = strlen(PSK_IDENTITY),\n            .private_cert_or_psk_key = (const uint8_t *) PSK_KEY,\n            .private_cert_or_psk_key_size = strlen(PSK_KEY)\n        };\n\n        // Anjay will assign Instance ID automatically\n        anjay_iid_t security_instance_id = ANJAY_ID_INVALID;\n        if (anjay_security_object_add_instance(anjay, &security_instance,\n                                               &security_instance_id)) {\n            return -1;\n        }\n\n        return 0;\n    }\n\n    // Installs Server Object and adds an instance of it.\n    // An instance of Server Object provides the data related to a LwM2M Server.\n    static int setup_server_object(anjay_t *anjay) {\n        if (anjay_server_object_install(anjay)) {\n            return -1;\n        }\n\n        const anjay_server_instance_t server_instance = {\n            // Server Short ID\n            .ssid = 1,\n            // Client will send Update message often than every 60 seconds\n            .lifetime = 60,\n            // Disable Default Minimum Period resource\n            .default_min_period = -1,\n            // Disable Default Maximum Period resource\n            .default_max_period = -1,\n            // Disable Disable Timeout resource\n            .disable_timeout = -1,\n            // Sets preferred transport to UDP\n            .binding = \"U\"\n        };\n\n        // Anjay will assign Instance ID automatically\n        anjay_iid_t server_instance_id = ANJAY_ID_INVALID;\n        if (anjay_server_object_add_instance(anjay, &server_instance,\n                                             &server_instance_id)) {\n            return -1;\n        }\n\n        return 0;\n    }\n\n    int main(int argc, char *argv[]) {\n        if (argc != 2) {\n            avs_log(tutorial, ERROR, \"usage: %s ENDPOINT_NAME\", argv[0]);\n            return -1;\n        }\n\n        ENDPOINT_NAME = argv[1];\n\n        const anjay_configuration_t CONFIG = {\n            .endpoint_name = ENDPOINT_NAME,\n            .in_buffer_size = 4000,\n            .out_buffer_size = 4000,\n            .msg_cache_size = 4000\n        };\n\n        anjay_t *anjay = anjay_new(&CONFIG);\n        if (!anjay) {\n            avs_log(tutorial, ERROR, \"Could not create Anjay object\");\n            return -1;\n        }\n\n        int result = 0;\n        // Setup necessary objects\n        if (setup_security_object(anjay) || setup_server_object(anjay)\n                || fw_update_install(anjay)) {\n            result = -1;\n        }\n\n        const anjay_dm_object_def_t **time_object = NULL;\n        if (!result) {\n            time_object = time_object_create();\n            if (time_object) {\n                result = anjay_register_object(anjay, time_object);\n            } else {\n                result = -1;\n            }\n        }\n\n        if (!result) {\n            // Run notify_job the first time;\n            // this will schedule periodic calls to itself via the scheduler\n            notify_job(anjay_get_scheduler(anjay), &(const notify_job_args_t) {\n                                                       .anjay = anjay,\n                                                       .time_object = time_object\n                                                   });\n\n            result = anjay_event_loop_run(\n                    anjay, avs_time_duration_from_scalar(1, AVS_TIME_S));\n        }\n\n        anjay_delete(anjay);\n        time_object_release(time_object);\n        return result;\n    }\n\n.. note::\n\n    As you may see, there is also an additional ``ENDPOINT_NAME`` global\n    variable that now stores the command line argument. As we will use the same\n    kind of program binary as the update image, we will need this to properly\n    launch it as part of the upgrade process.\n\n    This is usually not necessary in production code, as the endpoint name is\n    usually either hard-coded, or configured through other means.\n\nImplementing handlers and installation routine\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nFirst, let's think about what would we need to implement I/O operations\nrequired to download the firmware. The approach we could take is to\nopen a ``FILE`` during a call to the ``stream_open`` callback, write to\nit in ``stream_write``, and close it in ``stream_finish``. The only detail\nremaining is: how are we going to share ``FILE *`` pointer between all\nof these?\n\nWe can use a globally allocated structure and pack entire shared state into\nit. In ``firmware_update.c`` it looks like this:\n\n.. highlight:: c\n.. snippet-source:: examples/tutorial/firmware-update/basic-implementation/src/firmware_update.c\n    :emphasize-lines: 9\n\n    #include \"./firmware_update.h\"\n\n    #include <assert.h>\n    #include <errno.h>\n    #include <stdio.h>\n    #include <sys/stat.h>\n    #include <unistd.h>\n\n    static struct fw_state_t { FILE *firmware_file; } FW_STATE;\n\n.. note::\n\n    The numerous headers included will be useful in further stages of the\n    development.\n\n.. _fw-download-io:\n\nHaving the global state structure, we can proceed with implementation of:\n``fw_stream_open``, ``fw_stream_write`` and ``fw_stream_finish``, keeping in mind\nour brief discussion at the beginning of the section:\n\n.. highlight:: c\n.. snippet-source:: examples/tutorial/firmware-update/basic-implementation/src/firmware_update.c\n    :emphasize-lines: 3-\n\n    static struct fw_state_t { FILE *firmware_file; } FW_STATE;\n\n    static const char *FW_IMAGE_DOWNLOAD_NAME = \"/tmp/firmware_image.bin\";\n\n    static int fw_stream_open(void *user_ptr,\n                              const char *package_uri,\n                              const struct anjay_etag *package_etag) {\n        // For a moment, we don't need to care about any of the arguments passed.\n        (void) user_ptr;\n        (void) package_uri;\n        (void) package_etag;\n\n        // It's worth ensuring we start with a NULL firmware_file. In the end\n        // it would be our responsibility to manage this pointer, and we want\n        // to make sure we never leak any memory.\n        assert(FW_STATE.firmware_file == NULL);\n        // We're about to create a firmware file for writing\n        FW_STATE.firmware_file = fopen(FW_IMAGE_DOWNLOAD_NAME, \"wb\");\n        if (!FW_STATE.firmware_file) {\n            fprintf(stderr, \"Could not open %s\\n\", FW_IMAGE_DOWNLOAD_NAME);\n            return -1;\n        }\n        // We've succeeded\n        return 0;\n    }\n\n    static int fw_stream_write(void *user_ptr, const void *data, size_t length) {\n        (void) user_ptr;\n        // We only need to write to file and check if that succeeded\n        if (fwrite(data, length, 1, FW_STATE.firmware_file) != 1) {\n            fprintf(stderr, \"Writing to firmware image failed\\n\");\n            return -1;\n        }\n        return 0;\n    }\n\n    static int fw_stream_finish(void *user_ptr) {\n        (void) user_ptr;\n        assert(FW_STATE.firmware_file != NULL);\n\n        if (fclose(FW_STATE.firmware_file)) {\n            fprintf(stderr, \"Closing firmware image failed\\n\");\n            FW_STATE.firmware_file = NULL;\n            return -1;\n        }\n        FW_STATE.firmware_file = NULL;\n        return 0;\n    }\n\nNext in queue is ``fw_reset``, which is called when something on the Client or\nthe Server side goes wrong, or if the Server decides to not perform firmware\nupdate. We can implement it as follows:\n\n.. highlight:: c\n.. snippet-source:: examples/tutorial/firmware-update/basic-implementation/src/firmware_update.c\n\n    static void fw_reset(void *user_ptr) {\n        // Reset can be issued even if the download never started.\n        if (FW_STATE.firmware_file) {\n            // We ignore the result code of fclose(), as fw_reset() can't fail.\n            (void) fclose(FW_STATE.firmware_file);\n            // and reset our global state to initial value.\n            FW_STATE.firmware_file = NULL;\n        }\n        // Finally, let's remove any downloaded payload\n        unlink(FW_IMAGE_DOWNLOAD_NAME);\n    }\n\nAnd finally, ``fw_perform_upgrade`` as well as ``fw_update_install`` are to\nbe implemented. However, up to this point, we did not specify what would\nthe format of a downloaded image be, nor how would it be applied.\n\nIn our simplified example, we can require from the image to be an executable,\nand then in ``fw_perform_upgrade`` we could be using ``execl()`` to start a\nnew (downloaded) version of our Client.\n\n.. note::\n\n    In a more realistic scenario, one would be doing things such as:\n\n        - firmware verification,\n        - saving it to some persistent storage (e.g. flash), rather than to\n          ``/tmp``,\n        - other platform specific stuff.\n\nThe other important thing to consider is this: how's the newly running\nclient going to know it was upgraded? After all, it would be nice if the\nClient could report this information to the Server for it to know the update\nactually succeeded.\n\nThe simplest solution here is to use a \"marker\" file, indicating the client\nsuccessfully upgraded. Specifically, the idea is as follows:\n\n    - just before performing the upgrade, a \"marker\" file is created,\n    - the logic in the Client can check for the existence of the \"marker\" and\n      conclude, if the upgrade was performed or not,\n    - finally, the \"marker\" gets removed.\n\nThe code is self explanatory:\n\n.. highlight:: c\n.. snippet-source:: examples/tutorial/firmware-update/basic-implementation/src/firmware_update.c\n\n    // A part of a rather simple logic checking if the firmware update was\n    // successfully performed.\n    static const char *FW_UPDATED_MARKER = \"/tmp/fw-updated-marker\";\n\n    static int fw_perform_upgrade(void *user_ptr) {\n        if (chmod(FW_IMAGE_DOWNLOAD_NAME, 0700) == -1) {\n            fprintf(stderr,\n                    \"Could not make firmware executable: %s\\n\",\n                    strerror(errno));\n            return -1;\n        }\n        // Create a marker file, so that the new process knows it is the \"upgraded\"\n        // one\n        FILE *marker = fopen(FW_UPDATED_MARKER, \"w\");\n        if (!marker) {\n            fprintf(stderr, \"Marker file could not be created\\n\");\n            return -1;\n        }\n        fclose(marker);\n\n        assert(ENDPOINT_NAME);\n        // If the call below succeeds, the firmware is considered as \"upgraded\",\n        // and we hope the newly started client registers to the Server.\n        (void) execl(FW_IMAGE_DOWNLOAD_NAME, FW_IMAGE_DOWNLOAD_NAME, ENDPOINT_NAME,\n                     NULL);\n        fprintf(stderr, \"execl() failed: %s\\n\", strerror(errno));\n        // If we are here, it means execl() failed. Marker file MUST now be removed,\n        // as the firmware update failed.\n        unlink(FW_UPDATED_MARKER);\n        return -1;\n    }\n\n    static const anjay_fw_update_handlers_t HANDLERS = {\n        .stream_open = fw_stream_open,\n        .stream_write = fw_stream_write,\n        .stream_finish = fw_stream_finish,\n        .reset = fw_reset,\n        .perform_upgrade = fw_perform_upgrade\n    };\n\n    const char *ENDPOINT_NAME = NULL;\n\n    int fw_update_install(anjay_t *anjay) {\n        anjay_fw_update_initial_state_t state;\n        memset(&state, 0, sizeof(state));\n\n        if (access(FW_UPDATED_MARKER, F_OK) != -1) {\n            // marker file exists, it means firmware update succeded!\n            state.result = ANJAY_FW_UPDATE_INITIAL_SUCCESS;\n            unlink(FW_UPDATED_MARKER);\n        }\n        // install the module, pass handlers that we implemented and initial state\n        // that we discovered upon startup\n        return anjay_fw_update_install(anjay, &HANDLERS, NULL, &state);\n    }\n"
  },
  {
    "path": "doc/sphinx/source/FirmwareUpdateTutorial/FU-DownloadResumption.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nDownload resumption\n===================\n\nIntroduction\n^^^^^^^^^^^^\n\nImagine that due to some bug or, for example, a power loss, the device\nunexpectedly reboots during firmware download. Some portion of the firmware\ncould already have been written to a persistent memory, and it could be\na waste if that data was to be downloaded for the second time.\n\nThis is where the download resumption mechanism comes into play. If\nthis is a **PULL** mode download, the Server supports and uses `ETags\n<https://en.wikipedia.org/wiki/HTTP_ETag>`_ and the firmware resource did\nnot expire (i.e. has the same ETag), there is a good chance the Client will\nbe able to resume a partially finished download.\n\nAnjay and Firmware Update initial state\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nLet's have a look at the ``anjay_fw_update_initial_state_t``:\n\n.. highlight:: c\n.. snippet-source:: include_public/anjay/fw_update.h\n    :emphasize-lines: 9-51\n\n    typedef struct {\n        /**\n         * Controls initialization of the State and Update Result resources. It is\n         * intended to be used after a reboot caused by a firmware update attempt,\n         * to report the update result.\n         */\n        anjay_fw_update_initial_result_t result;\n\n        /**\n         * Value to initialize the Package URI resource with. The passed string is\n         * copied, so the pointer is allowed to become invalid after return from\n         * @ref anjay_fw_update_install .\n         *\n         * Required when <c>result == ANJAY_FW_UPDATE_INITIAL_DOWNLOADING</c>; if it\n         * is not provided (<c>NULL</c>) in such case, @ref anjay_fw_update_reset_t\n         * handler will be called from @ref anjay_fw_update_install to reset the\n         * Firmware Update object into the Idle state.\n         *\n         * Optional when <c>result == ANJAY_FW_UPDATE_INITIAL_DOWNLOADED</c>; in\n         * this case it signals that the firmware was downloaded using the Pull\n         * mechanism.\n         *\n         * In all other cases it is ignored.\n         */\n        const char *persisted_uri;\n\n        /**\n         * Number of bytes that has been already successfully downloaded and are\n         * available at the time of calling @ref anjay_fw_update_install .\n         *\n         * It is ignored unless\n         * <c>result == ANJAY_FW_UPDATE_INITIAL_DOWNLOADING</c>, in which case the\n         * following call to @ref anjay_fw_update_stream_write_t shall append the\n         * passed chunk of data at the offset set here. If resumption from the set\n         * offset is impossible, the library will call @ref anjay_fw_update_reset_t\n         * and @ref anjay_fw_update_stream_open_t to restart the download process.\n         */\n        size_t resume_offset;\n\n        /**\n         * ETag of the download process to resume. The passed value is copied, so\n         * the pointer is allowed to become invalid after return from\n         * @ref anjay_fw_update_install .\n         *\n         * Required when <c>result == ANJAY_FW_UPDATE_INITIAL_DOWNLOADING</c> and\n         * <c>resume_offset > 0</c>; if it is not provided (<c>NULL</c>) in such\n         * case, @ref anjay_fw_update_reset_t handler will be called from\n         * @ref anjay_fw_update_install to reset the Firmware Update object into the\n         * Idle state.\n         */\n        const struct anjay_etag *resume_etag;\n\n        /**\n         * Informs the module to try reusing sockets of existing LwM2M Servers to\n         * download the firmware image if the download URI matches any of the LwM2M\n         * Servers.\n         */\n        bool prefer_same_socket_downloads;\n\n    #ifdef ANJAY_WITH_SEND\n        /**\n         * Enables using LwM2M Send to report State, Update Result and Firmware\n         * Version to the LwM2M Server (if LwM2M Send is enabled) during firmware\n         * update.\n         */\n        bool use_lwm2m_send;\n    #endif // ANJAY_WITH_SEND\n\n    #if defined(ANJAY_WITH_LWM2M11) \\\n            && defined(ANJAY_WITH_MODULE_FW_UPDATE_V11_RESOURCES)\n        /**\n         * Value to initialize the Severity resource with.\n         */\n        anjay_fw_update_severity_t persisted_severity;\n\n        /**\n         * Value to initialize the Last State Change Time resource with.\n         */\n        avs_time_real_t persisted_last_state_change_time;\n\n        /**\n         * Update deadline based on Maximum Defer Period resource value and time of\n         * executing Update resource.\n         */\n        avs_time_real_t persisted_update_deadline;\n    #endif /* defined(ANJAY_WITH_LWM2M11) && \\\n              defined(ANJAY_WITH_MODULE_FW_UPDATE_V11_RESOURCES) */\n    } anjay_fw_update_initial_state_t;\n\n\nThe highlighted fields can be used to arrange a download resumption. Recall\nthat we already passed this structure to ``anjay_fw_update_install`` in\nprevious chapters, but we've always zero-initialized before doing so.\n\n.. note::\n\n    **Quick reminder:** download resumption is supported for **PULL**\n    mode downloads only.\n\nAs you can see from the structure above, we're going to need three\npieces of information:\n\n    - ``persisted_uri`` - the URI from which the download was originally\n      started,\n    - ``resume_offset`` - the number of bytes successfully stored before \n      the device crashed or unexpectedly rebooted,\n    - ``resume_etag`` - ETag that allows to validate whether the Server\n      still has the same firmware file available under given URI.\n\nIn terms of implementation, we will start with introducing a structure that will\nhold the download state as well as utility functions that will store and restore\nthe state from persistent storage:\n\n.. highlight:: c\n.. snippet-source:: examples/tutorial/firmware-update/download-resumption/src/firmware_update.c\n    :emphasize-lines: 1, 10-117, 123-128\n\n    #define _DEFAULT_SOURCE // for fileno()\n    #include \"./firmware_update.h\"\n\n    #include <assert.h>\n    #include <errno.h>\n    #include <stdio.h>\n    #include <sys/stat.h>\n    #include <unistd.h>\n\n    typedef struct {\n        char *persisted_uri;\n        uint32_t resume_offset;\n        anjay_etag_t *resume_etag;\n    } download_state_t;\n\n    static const char *FW_DOWNLOAD_STATE_NAME = \"firmware_dl_state.bin\";\n\n    static int store_etag(FILE *fp, const anjay_etag_t *etag) {\n        assert(etag);\n        if (fwrite(&etag->size, sizeof(etag->size), 1, fp) != 1) {\n            return -1;\n        }\n        if (etag->size > 0 && fwrite(etag->value, etag->size, 1, fp) != 1) {\n            return -1;\n        }\n        return 0;\n    }\n\n    static int store_download_state(const download_state_t *state) {\n        FILE *fp = fopen(FW_DOWNLOAD_STATE_NAME, \"wb\");\n        if (!fp) {\n            fprintf(stderr, \"could not open %s for writing\\n\",\n                    FW_DOWNLOAD_STATE_NAME);\n            return -1;\n        }\n        const uint16_t uri_length = strlen(state->persisted_uri);\n        int result = 0;\n        if (fwrite(&uri_length, sizeof(uri_length), 1, fp) != 1\n                || fwrite(state->persisted_uri, uri_length, 1, fp) != 1\n                || fwrite(&state->resume_offset, sizeof(state->resume_offset), 1,\n                          fp) != 1\n                || store_etag(fp, state->resume_etag)) {\n            fprintf(stderr, \"could not write firmware download state\\n\");\n            result = -1;\n        }\n        fclose(fp);\n        if (result) {\n            unlink(FW_DOWNLOAD_STATE_NAME);\n        }\n        return result;\n    }\n\n    static int restore_etag(FILE *fp, anjay_etag_t **out_etag) {\n        assert(out_etag && !*out_etag); // make sure out_etag is zero-initialized\n        uint8_t size;\n        if (fread(&size, sizeof(size), 1, fp) != 1) {\n            return -1;\n        }\n        anjay_etag_t *etag = anjay_etag_new(size);\n        if (!etag) {\n            return -1;\n        }\n\n        if (size > 0 && fread(etag->value, size, 1, fp) != 1) {\n            avs_free(etag);\n            return -1;\n        }\n        *out_etag = etag;\n        return 0;\n    }\n\n    static int restore_download_state(download_state_t *out_state) {\n        download_state_t data;\n        memset(&data, 0, sizeof(data));\n\n        FILE *fp = fopen(FW_DOWNLOAD_STATE_NAME, \"rb\");\n        if (!fp) {\n            fprintf(stderr, \"could not open %s for reading\\n\",\n                    FW_DOWNLOAD_STATE_NAME);\n            return -1;\n        }\n\n        int result = 0;\n        uint16_t uri_length;\n        if (fread(&uri_length, sizeof(uri_length), 1, fp) != 1 || uri_length == 0) {\n            result = -1;\n        }\n        if (!result) {\n            data.persisted_uri = (char *) avs_calloc(1, uri_length + 1);\n            if (!data.persisted_uri) {\n                result = -1;\n            }\n        }\n        if (!result\n                && (fread(data.persisted_uri, uri_length, 1, fp) != 1\n                    || fread(&data.resume_offset, sizeof(data.resume_offset), 1, fp)\n                               != 1\n                    || restore_etag(fp, &data.resume_etag))) {\n            result = -1;\n        }\n        if (result) {\n            fprintf(stderr, \"could not restore download state from %s\\n\",\n                    FW_DOWNLOAD_STATE_NAME);\n            avs_free(data.persisted_uri);\n        } else {\n            *out_state = data;\n        }\n        fclose(fp);\n        return result;\n    }\n\n    static void reset_download_state(download_state_t *state) {\n        avs_free(state->persisted_uri);\n        avs_free(state->resume_etag);\n        memset(state, 0, sizeof(*state));\n        unlink(FW_DOWNLOAD_STATE_NAME);\n    }\n\n    static struct fw_state_t {\n        FILE *firmware_file;\n        // anjay instance this firmware update singleton is associated with\n        anjay_t *anjay;\n        // Current state of the download. It is updated and persited on each\n        // fw_stream_write() call.\n        download_state_t download_state;\n    } FW_STATE;\n\n    static const char *FW_IMAGE_DOWNLOAD_NAME = \"/tmp/firmware_image.bin\";\n\nIn the next section, we'll discuss when state storing and restoring should\nbe done.\n\nPersisting firmware state\n^^^^^^^^^^^^^^^^^^^^^^^^^\n\nWhen :ref:`we implemented <fw-download-io>` the ``fw_stream_open`` callback,\nwe ignored ``package_uri`` and ``package_etag``, because we didn't need it\nat that time.\n\n.. note::\n\n    Persisting firmware state makes sense only if both ``package_uri``\n    and ``package_etag`` are non-`NULL`. ``package_uri`` indicates it is\n    a **PULL** mode transfer (the only mode supporting resumption), while\n    ``package_etag`` allows the Client to verify that the downloaded file\n    is the exactly the same as before the resumption happened; without it,\n    there will be no resumption.\n\nThis time, however, we will save both of them in ``FW_STATE``. The only\nmissing piece is then the ``resume_offset``, which naturally can be updated\nin ``fw_stream_write`` implementation after writing a chunk of data to the\nstorage. Of course, we also have to remember to reset the download state when\n``fw_reset`` is called, as then the download is either failed or the Server\nexplicitly wants the Client to discard the firmware downloaded so far. These\nideas can be summarized as follows:\n\n    - on a call to ``fw_stream_open`` we'll store ``package_uri`` and\n      ``package_etag`` in ``FW_STATE``,\n    - on a call to ``fw_stream_write`` we'll update the ``resume_offset``\n      state and write the whole state information to persistent storage,\n    - on a call to ``fw_reset`` we'll erase the download state.\n\n.. important::\n\n    The implementation of ``fw_stream_write`` as described above will be\n    awkward on a UNIX-like systems. Complicated operating systems tend to\n    have multiple layers of IO buffering, and it may take some time before\n    the actual writes are made to the physical storage device. This means\n    that we can't just call ``fwrite()`` and blindly update ``resume_offset``\n    with the number of bytes we ordered it to write, even if it returned\n    success (because the data may still reside in some cache, maintained e.g.\n    by the kernel).\n\n    Because of that, rather than updating the download state file on\n    each call to ``fw_stream_write``, it would be wiser to do it once in\n    ``fw_stream_open``, and deduce the ``resume_offset`` from the size of\n    the file.\n\n    In an embedded application though, with no buffering (or without a concept\n    of file), it's more appropriate to update ``resume_offset`` from within\n    ``fw_stream_write`` instead, remembering to do so ONLY after having a\n    high degree of certainty that the chunk of firmware was successfully\n    written to the flash memory.\n\n    Since we want to show the correct way of handling download resumption on\n    embedded hardware while being relatively correct on non-embedded platforms,\n    we'll use inefficient the ``fflush()`` and ``fsync()`` calls after each\n    ``fwrite()`` which should flush the caches and trigger physical writes\n    just to illustrate the point.\n\nKeeping all these things in mind, let's start by refactoring ``fw_stream_open``\naccordingly:\n\n.. highlight:: c\n.. snippet-source:: examples/tutorial/firmware-update/download-resumption/src/firmware_update.c\n\n    static int fw_open_download_file(long seek_offset) {\n        // It's worth ensuring we start with a NULL firmware_file. In the end\n        // it would be our responsibility to manage this pointer, and we want\n        // to make sure we never leak any memory.\n        assert(FW_STATE.firmware_file == NULL);\n        // We're about to create a firmware file for writing\n        FW_STATE.firmware_file = fopen(FW_IMAGE_DOWNLOAD_NAME, \"wb\");\n        if (!FW_STATE.firmware_file) {\n            fprintf(stderr, \"Could not open %s\\n\", FW_IMAGE_DOWNLOAD_NAME);\n            return -1;\n        }\n        if (fseek(FW_STATE.firmware_file, seek_offset, SEEK_SET)) {\n            fprintf(stderr, \"Could not seek to %ld\\n\", seek_offset);\n            fclose(FW_STATE.firmware_file);\n            FW_STATE.firmware_file = NULL;\n            return -1;\n        }\n        // We've succeeded\n        return 0;\n    }\n\n    static int fw_stream_open(void *user_ptr,\n                              const char *package_uri,\n                              const struct anjay_etag *package_etag) {\n        // We don't use user_ptr.\n        (void) user_ptr;\n\n        // We only persist firmware download state if we have both package_uri\n        // and package_etag. Otherwise the download could not be resumed.\n        if (package_uri && package_etag) {\n            FW_STATE.download_state.persisted_uri = avs_strdup(package_uri);\n            int result = 0;\n            if (!FW_STATE.download_state.persisted_uri) {\n                fprintf(stderr, \"Could not duplicate package URI\\n\");\n                result = -1;\n            }\n            anjay_etag_t *etag_copy = NULL;\n            if (!result && package_etag) {\n                etag_copy = anjay_etag_clone(package_etag);\n                if (!etag_copy) {\n                    fprintf(stderr, \"Could not duplicate package ETag\\n\");\n                    result = -1;\n                }\n            }\n            if (!result) {\n                FW_STATE.download_state.resume_etag = etag_copy;\n            } else {\n                reset_download_state(&FW_STATE.download_state);\n                return result;\n            }\n        }\n\n        return fw_open_download_file(0);\n    }\n\n\nThen, we can implement storing the download state logic in ``fw_stream_write``:\n\n.. highlight:: c\n.. snippet-source:: examples/tutorial/firmware-update/download-resumption/src/firmware_update.c\n    :emphasize-lines: 3-7, 9-10, 14-22\n\n    static int fw_stream_write(void *user_ptr, const void *data, size_t length) {\n        (void) user_ptr;\n        // NOTE: fflush() and fsync() are done to be relatively sure that\n        // the data is passed to the hardware and so that we can update\n        // resume_offset in the download state. They are suboptimal on UNIX-like\n        // platforms, and are used just to illustrate when is the right time to\n        // update resume_offset on embedded platforms.\n        if (fwrite(data, length, 1, FW_STATE.firmware_file) != 1\n                || fflush(FW_STATE.firmware_file)\n                || fsync(fileno(FW_STATE.firmware_file))) {\n            fprintf(stderr, \"Writing to firmware image failed\\n\");\n            return -1;\n        }\n        if (FW_STATE.download_state.persisted_uri) {\n            FW_STATE.download_state.resume_offset += length;\n            if (store_download_state(&FW_STATE.download_state)) {\n                // If we returned -1 here, the download would be aborted, so it\n                // is probably better to continue instead.\n                fprintf(stderr,\n                        \"Could not store firmware download state - ignoring\\n\");\n            }\n        }\n        return 0;\n    }\n\nThe next step is to make sure that ``fw_reset`` resets the download state as well:\n\n.. highlight:: c\n.. snippet-source:: examples/tutorial/firmware-update/download-resumption/src/firmware_update.c\n    :emphasize-lines: 11-12\n\n    static void fw_reset(void *user_ptr) {\n        // Reset can be issued even if the download never started.\n        if (FW_STATE.firmware_file) {\n            // We ignore the result code of fclose(), as fw_reset() can't fail.\n            (void) fclose(FW_STATE.firmware_file);\n            // and reset our global state to initial value.\n            FW_STATE.firmware_file = NULL;\n        }\n        // Finally, let's remove any downloaded payload\n        unlink(FW_IMAGE_DOWNLOAD_NAME);\n        // And reset any download state.\n        reset_download_state(&FW_STATE.download_state);\n    }\n\nAnd the last piece of the implementation will be to read the download state (if any)\nat initialization stage, and before installing the firmware update module in Anjay:\n\n.. highlight:: c\n.. snippet-source:: examples/tutorial/firmware-update/download-resumption/src/firmware_update.c\n    :emphasize-lines: 9-23\n\n    int fw_update_install(anjay_t *anjay) {\n        anjay_fw_update_initial_state_t state;\n        memset(&state, 0, sizeof(state));\n\n        if (access(FW_UPDATED_MARKER, F_OK) != -1) {\n            // marker file exists, it means firmware update succeded!\n            state.result = ANJAY_FW_UPDATE_INITIAL_SUCCESS;\n            unlink(FW_UPDATED_MARKER);\n            // we can get rid of any download state if the update succeeded\n            reset_download_state(&FW_STATE.download_state);\n        } else if (!restore_download_state(&FW_STATE.download_state)) {\n            // download state restored, it means we can try using download\n            // resumption\n            if (fw_open_download_file(state.resume_offset)) {\n                // the file cannot be opened or seeking failed\n                reset_download_state(&FW_STATE.download_state);\n            } else {\n                state.persisted_uri = FW_STATE.download_state.persisted_uri;\n                state.resume_offset = FW_STATE.download_state.resume_offset;\n                state.resume_etag = FW_STATE.download_state.resume_etag;\n                state.result = ANJAY_FW_UPDATE_INITIAL_DOWNLOADING;\n            }\n        }\n        // make sure this module is installed for single Anjay instance only\n        assert(FW_STATE.anjay == NULL);\n        FW_STATE.anjay = anjay;\n        // install the module, pass handlers that we implemented and initial state\n        // that we discovered upon startup\n        return anjay_fw_update_install(anjay, &HANDLERS, NULL, &state);\n    }\n"
  },
  {
    "path": "doc/sphinx/source/FirmwareUpdateTutorial/FU-Introduction.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nFirmware Update\n===============\n\nIntroduction\n^^^^^^^^^^^^\n\nOne of the most important applications of the LwM2M protocol is FOTA (**F**\\\nirmware update **O**\\ ver **T**\\ he **A**\\ ir). After all, if you're having\na real deployment, you want to be able to keep it updated.\n\nThe firmware update procedure is well standardized within the LwM2M\nSpecification, and a standard Firmware Update Object (**/5**) can be used\nto perform:\n\n    - download,\n    - verification,\n    - upgrade.\n\nAt the same time, it is flexible enough so that a custom logic may be\nintroduced (e.g. differential updates). There are no restrictions on\nverification method, image formats, and so on, thus they depend entirely on\nthe specific implementation.\n\nFirmware update state machine and a general overview\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nLet's have a quick glance at the Firmware Update State Machine as defined\nby the LwM2M Specification:\n\n.. image:: https://www.openmobilealliance.org/release/LightweightM2M/V1_1-20180710-A/HTML-Version/OMA-TS-LightweightM2M_Core-V1_1-20180710-A_files/firmware_update_mechanisms.svg\n    :alt: LwM2M Firmware Update state machine\n\nThe texts over the transition edges are showing different events that may\nhappen either on the Client side or on the Server side.\n\nYou may notice that the transition diagram uses two separate entities, namely\n**Res** and **State**. These correspond to **Update Result** and **State**\nResources in the Firmware Update Object.\n\n- **Update Result** Resource keeps the result of the download or update attempts,\n- **State** Resource keeps one of the four states as in the big boxes in the diagram above.\n\nIn any case, Anjay maintains these two automatically. In fact, the API of\nFirmware Update module, which is about to be introduced, is simple enough, so\nthat an application developer can focus on implementing the I/O, verification\nand the update process itself and let the library handle the LwM2M side of\nthe whole thing.\n\nMoving forward, generally speaking, the whole **firmware update process\nlooks like this**:\n\n#. Server initiates firmware download.\n#. Client downloads the firmware and reports when it finished to the Server.\n#. Server decides what to do next, and when ready, sends to the Client a request to perform the upgrade.\n#. Client attempts to apply the firmware, and reports the status to the Server.\n\n.. important::\n\n    It shall be emphasized that **it is the LwM2M Server which initiates\n    firmware download and upgrade**.\n\n    `How to download firmware from a server?` seems to be a commonly asked\n    question, but the LwM2M reality is: **one can't trigger this on a\n    Client side in a standard way**. It's the Server which decides when it\n    happens and either provides the client with an URI to perform the download,\n    or directly sends the firmware to the Client.\n\n\n.. _firmware-update-api:\n\nAPI in Anjay\n^^^^^^^^^^^^\n\nAnjay comes with a built-in Firmware Update module, which simplifies FOTA\nimplementation for the user. At its core, Firmware Update module consists\nof user-implemented callbacks of various sort. They are shown below, to\ngive an idea on what the implementation of FOTA would take:\n\n.. highlight:: c\n.. snippet-source:: include_public/anjay/fw_update.h\n\n    typedef struct {\n        /** Opens the stream that will be used to write the firmware package to;\n         * @ref anjay_fw_update_stream_open_t */\n        anjay_fw_update_stream_open_t *stream_open;\n        /** Writes data to the download stream;\n         * @ref anjay_fw_update_stream_write_t */\n        anjay_fw_update_stream_write_t *stream_write;\n        /** Closes the download stream and prepares the firmware package to be\n         * flashed; @ref anjay_fw_update_stream_finish_t */\n        anjay_fw_update_stream_finish_t *stream_finish;\n\n        /** Resets the firmware update state and performs any applicable cleanup of\n         * temporary storage if necessary; @ref anjay_fw_update_reset_t */\n        anjay_fw_update_reset_t *reset;\n\n        /** Returns the name of downloaded firmware package;\n         * @ref anjay_fw_update_get_name_t */\n        anjay_fw_update_get_name_t *get_name;\n        /** Return the version of downloaded firmware package;\n         * @ref anjay_fw_update_get_version_t */\n        anjay_fw_update_get_version_t *get_version;\n\n        /** Performs the actual upgrade with previously downloaded package;\n         * @ref anjay_fw_update_perform_upgrade_t */\n        anjay_fw_update_perform_upgrade_t *perform_upgrade;\n\n        /** Queries security configuration that shall be used for an encrypted\n         * connection; @ref anjay_fw_update_get_security_config_t */\n        anjay_fw_update_get_security_config_t *get_security_config;\n\n        /** Queries CoAP transmission parameters to be used during firmware\n         * update; @ref anjay_fw_update_get_coap_tx_params_t */\n        anjay_fw_update_get_coap_tx_params_t *get_coap_tx_params;\n\n        /** Queries request timeout to be used during firmware update over CoAP+TCP\n         * or HTTP; @ref anjay_fw_update_get_tcp_request_timeout_t */\n        anjay_fw_update_get_tcp_request_timeout_t *get_tcp_request_timeout;\n    } anjay_fw_update_handlers_t;\n\n\nLuckily, not all of them need to be implemented during initial\nexperiments. The mandatory ones are:\n\n    - ``stream_open``,\n    - ``stream_write``,\n    - ``stream_finish``,\n    - ``reset``,\n    - ``perform_upgrade``.\n\nLet's briefly discuss each one of them:\n\n    - ``stream_open`` is called whenever a new firmware download is started\n      by the Server. Its main responsibility is to prepare for receiving\n      firmware chunks - e.g. by opening a file or getting flash storage\n      ready, etc.\n\n    - ``stream_write`` is called whenever there is a next firmware chunk\n      received, ready to be stored. Its responsibility is to append the\n      chunk to the storage.\n\n    - ``stream_finish`` is called whenever the writing process finished and\n      the stored data can now be thought of as a complete firmware image. It may\n      be a good moment here to verify if the entire firmware image is valid.\n\n    - ``reset`` is called whenever there was an error during firmware download,\n      or if the Server decided to not pursue firmware update with downloaded\n      firmware (e.g. because it was notified that firmware verification\n      failed).\n\n    - ``perform_upgrade`` is called whenever the download finished, the\n      firmware is successfully verified on the Client and Server decided to\n      upgrade the device.\n\nIn the next chapter we'll begin implementing all of these from scratch.\n"
  },
  {
    "path": "doc/sphinx/source/FirmwareUpdateTutorial/FU-ModesAndProtocols.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nDownload modes and protocols\n============================\n\n.. _firmware-transfer:\n\nFirmware transfer\n^^^^^^^^^^^^^^^^^\n\nThe LwM2M protocol defines two modes of firmware download:\n\n- **PUSH** - the download is initiated by the Server, performing a Write\n  operation on the /5/0/0 (Package) Resource. This usually means a block-wise\n  transfer, which for Anjay based clients will prevent handling other\n  LwM2M requests until the Write completes, as described in\n  :ref:`single-request-single-function-call`.\n\n- **PULL** - the LwM2M Server indicates where should the client download\n  firmware package from with Write on /5/0/1 (Package URI) Resource. The client\n  then performs the download asynchronously, while still being able to handle\n  LwM2M requests.\n\n.. figure:: _images/anjay-firmware-download-coap-pull.svg\n   :width: 100%\n\n   Anjay generally handles asynchronous CoAP downloads within\n   ``anjay_serve()`` calls. The ``anjay_sched_run()`` may initiate the\n   download or retransmit requests in case of packet loss. Downloaded firmware\n   is passed to the application through callbacks configured using\n   ``fw_update`` field of the ``anjay_configuration_t`` struct passed to\n   ``anjay_new()``. For more details, see API reference.\n\n   Please note that when using ``anjay_event_loop_run()``, all these calls are\n   performed internally and hidden from the user.\n\n\nThe download protocols officially defined in the LwM2M Specification are:\n\n- CoAP(s)/UDP,\n- CoAP(s)/TCP,\n- HTTP(s).\n\nAnjay supports all of them out of the box in a default\nconfiguration. Configuring the specific set of features relevant for the\nuser is a matter of setting a few CMake options:\n\n- ``WITH_DOWNLOADER`` - enables/disables **PULL** downloads in general (`ON`\n  by default),\n- ``WITH_COAP_DOWNLOAD`` - enables/disables **PULL** downloads over CoAP(s)\n  (`ON` by default),\n- ``WITH_HTTP_DOWNLOAD`` - enables/disables **PULL** downloads over HTTP(s)\n  (`ON` by default),\n- ``WITH_AVS_COAP_UDP`` - enables/disables CoAP/UDP (`ON` by default),\n- ``WITH_AVS_COAP_TCP`` - enables/disables CoAP/TCP (`ON` by default).\n\nWhich download mode should you choose\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nAlthough in the end, it is the Server that selects download method,\nwe advise persuading your Server provider to use **PULL** transfers\nexclusively.\n\nThe unfortunate limitation of a **PUSH** download mode in Anjay is that\nit completely blocks the client. In other words, while conducting **PUSH**\ndownload, Anjay is pretty much unable to do anything else. Because of that\nit is **strongly recommended** to use **PULL** mode, which is implemented\nasynchronously in the library.\n\n.. important::\n\n    We recommend using **PULL** download mode due to limitations imposed on\n    other download modes.\n\nWhich download protocol should you choose\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nAgain, it's the Server decision which protocol shall be utilized for the\ndownload (of course out of the list of supported protocols, which you MAY\ncontrol, at least partially, through the list of CMake options mentioned\nin former sections), but downloads using CoAP(s)/UDP tend to be slow due\nto limitation of the maximum CoAP BLOCK size of 1024 bytes. The fact that\neach Block-Wise packet needs an acknowledgment from the other side does\nnot help either.\n\nIf the download speed is important you should stand with either CoAP(s)/TCP or\nHTTP(s) as the download protocol.\n"
  },
  {
    "path": "doc/sphinx/source/FirmwareUpdateTutorial/FU-PoorConnectivity.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nPoor network connectivity\n=========================\n\nIntroduction\n^^^^^^^^^^^^\n\nIt is sometimes the case that the network connectivity is unstable,\nand during the download some packets get lost. Depending on the underlying\ndownload protocol different failures are handled differently.\n\n\nThe definition of \"download failure\"\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nSince the variety of protocols may be used to perform the transfer, we\nneed to understand better what conditions cause the download to fail. Let's\nconsider this case by case:\n\nCoAP(s)/UDP\n\"\"\"\"\"\"\"\"\"\"\"\n\nThe download fails if either the connection could not be established (e.g.\nDTLS handshake failure) or when all request retransmissions were performed\nand no response have been received.\n\nCoAP(s)/TCP\n\"\"\"\"\"\"\"\"\"\"\"\n\nThe download fails if either the connection could not be established (e.g.\nTLS handshake failure, remote host is down) or the TCP stack declares\nthe connection as broken, or when there is no response to the request for\nthe configured time; by default that time is 30 seconds.\n\nHTTP(s)\n\"\"\"\"\"\"\"\n\nThe download fails due to similar reasons as above. Since HTTP(s) operates\nover TCP and the TCP stack maintains retransmissions and other things in a\nway outside of our control. The reponse timeout is configured the same way as\nfor CoAP(s)/TCP, and the default timeout is also 30 seconds.\n\n\nSo what happens when the download fails?\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nWhen the download fails, the firmware update module calls ``reset``\nhandler. Its implementation is required to cleanup any outstanding resources,\nand prepare the Client for a potential new download request.\n\n.. _how-can-we-ensure-higher-success-rate:\n\nHow can we ensure higher success rate?\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nFor CoAP/UDP, you can provide the :ref:`CoAP transmission parameters\n<coap-retransmission-parameters>` by implementing the ``get_coap_tx_params``\nhandler, which is a part of ``anjay_fw_update_handlers_t``. If not provided,\ndefault CoAP transmission parameters (or passed as part of\n``anjay_configuration_t``) will be used.\n\nIn a similar manner, for TCP-based transports (i.e., CoAP(s)/TCP and HTTP(s))\nyou can implement the ``get_tcp_request_timeout`` to set a custom request\ntimeout. This is the time of stream inactivity that will be treated as an error.\n\nFor CoAP(s)/UDP and CoAP(s)/TCP, you can also use configuration parameters:\n``coap_downloader_retry_count`` and ``coap_downloader_retry_delay`` in\n``anjay_configuration_t``. The first one specifies the number of attempts\nto resume downloads, and the second specifies the delay between retries.\nThis is not a mechanism of the CoAP protocol, but an additional layer of\nabstraction that allows resumption of downloads in case of network errors.\nThe download will start from the point where it was interrupted. Using the\nDTLS Connection ID extension makes it possible to resume the connection without\nperforming a handshake. In case of a poor connectivity, a combination of\nConnection ID and ETag provides the optimal solution. The ETag parameter is not\nmandatory, but if it is passed by the server then its value is checked when the\ndownload is resumed. The download is considered unsuccessful and the ``reset``\nhandler is called after the last failed attempt. If ``coap_downloader_retry_count``\nis not set, functionality is disabled.\n"
  },
  {
    "path": "doc/sphinx/source/FirmwareUpdateTutorial/FU-SecureDownloads.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nSecure downloads\n================\n\nIntroduction\n^^^^^^^^^^^^\n\nUp until now, we developed a basic client application that's capable of\ndownloading firmware in **PUSH** and **PULL** modes. If the Server connection\nis secure, then of course any **PUSH** transfer is automatically secured. In\ncase of **PULL** mode, however, there remains a question about the source\nof credentials required to establishing the secure connection.\n\nIn this chapter, we will focus on methods of credentials configuration for\n**PULL** mode transfers.\n\nTwo ways of security configuration\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nWhen a secure firmware transfer is initiated, firmware update module implemented\nin Anjay gets security configuration in one of two following ways.\n\n#. If the user implements a ``get_security_config`` callback located in\n   ``anjay_fw_update_handlers_t``, it is expected to provide the library with\n   security information.\n#. Otherwise, the library looks through Security Object instances, matching\n   the download URI host with configured URI in Security Object Instance. When\n   the matching Instance is found, the security information from that instance\n   is used. When no match is found, the download fails.\n\n.. note::\n\n    It may seem as the described methods are mutually\n    exclusive. However, in ``get_security_config`` callback one can use\n    ``anjay_security_config_from_dm()`` to attempt URI matching with entities\n    already configured in Security Object Instances.\n\nSupported security modes\n^^^^^^^^^^^^^^^^^^^^^^^^\n\nThe download can be secured by either `PSK\n<https://en.wikipedia.org/wiki/Pre-shared_key>`_, or by `Public key\ncertificates <https://en.wikipedia.org/wiki/Public_key_certificate>`_.\n\nSecurity information is configured in Anjay through a structure:\n\n.. highlight:: c\n.. snippet-source:: include_public/anjay/core.h\n\n    typedef struct {\n        /**\n         * DTLS keys or certificates.\n         */\n        avs_net_security_info_t security_info;\n\n        /**\n         * Single DANE TLSA record to use for certificate verification, if\n         * applicable.\n         */\n        const avs_net_socket_dane_tlsa_record_t *dane_tlsa_record;\n\n        /**\n         * TLS ciphersuites to use.\n         *\n         * A value with <c>num_ids == 0</c> (default) will cause defaults configured\n         * through <c>anjay_configuration_t::default_tls_ciphersuites</c>\n         * to be used.\n         */\n        avs_net_socket_tls_ciphersuites_t tls_ciphersuites;\n\n        /*\n         * Server Name Indicator to use for authenticating with the peer during\n         * secure TLS connection. The value is passed to the underlying TLS library\n         * that need to take this variable into account for it make any effect. This\n         * field is optional and can be left zero-initialized. If not set the\n         * integration layer should use the Server URI instead.\n         */\n        const char *server_name_indication;\n    } anjay_security_config_t;\n\nAnd specifically, it's the ``security_info`` field that is of interest to\nus. ``avs_net_security_info_t`` can be configured by:\n\n- ``avs_net_security_info_from_psk()``,\n- ``avs_net_security_info_from_certificates()``.\n\nWe will now have a closer look at both of these methods.\n\nConfiguration of PSK\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nThis is the most straightforward. The structure representing the PSK\nconfiguration is:\n\n.. highlight:: c\n.. snippet-source:: deps/avs_commons/include_public/avsystem/commons/avs_socket.h\n\n    /**\n     * A PSK/identity pair. avs_commons will never attempt to modify these values.\n     */\n    typedef struct {\n        avs_crypto_psk_key_info_t key;\n        avs_crypto_psk_identity_info_t identity;\n    } avs_net_psk_info_t;\n\nThe ``avs_crypto_psk_key_info_t`` and ``avs_crypto_psk_identity_info_t`` are\nsupposed to be populated using the ``avs_crypto_psk_key_info_from_*`` and\n``avs_crypto_psk_identity_info_from_*`` functions.\n\n``avs_crypto_psk_key_info_from_buffer()`` and\n``avs_crypto_psk_identity_info_from_buffer()`` are most commonly used, although\nother variants may be used to utilize PSK information stored on a hardware\nsecurity module.\n\nAfter populating the ``avs_net_psk_info_t`` structure, we may use:\n\n.. highlight:: c\n.. snippet-source:: deps/avs_commons/include_public/avsystem/commons/avs_socket.h\n\n    avs_net_security_info_t\n    avs_net_security_info_from_psk(avs_net_psk_info_t psk);\n\nto convert into ``avs_net_security_info_t``, as in the following example:\n\n.. code-block:: c\n\n    avs_net_psk_info_t psk_info = {\n        .key = avs_crypto_psk_key_info_from_buffer(\n                \"shared-key\", strlen(\"shared-key\")),\n        .identity = avs_crypto_psk_identity_info_from_buffer(\n                \"our-identity\", strlen(\"our-identity\"))\n    };\n    avs_net_security_info_t psk_security =\n            avs_net_security_info_from_psk(psk_info);\n\nConfiguration of Certificates\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nThat's a bit more involving. The structure representing Certificate configuration\nis:\n\n.. highlight:: c\n.. snippet-source:: deps/avs_commons/include_public/avsystem/commons/avs_socket.h\n\n    /**\n     * Configuration for certificate-mode (D)TLS connection.\n     */\n    typedef struct {\n        /**\n         * Enables validation of peer certificate chain. If disabled,\n         * #ignore_system_trust_store and #trusted_certs are ignored.\n         */\n        bool server_cert_validation;\n\n        /**\n         * Setting this flag to true disables the usage of system-wide trust store\n         * (e.g. <c>/etc/ssl/certs</c> on most Unix-like systems).\n         *\n         * NOTE: System-wide trust store is currently supported only by the OpenSSL\n         * backend. This field is ignored by the Mbed TLS backend.\n         */\n        bool ignore_system_trust_store;\n\n        /**\n         * Enable use of DNS-based Authentication of Named Entities (DANE) if\n         * possible.\n         *\n         * If this field is set to true, but #server_cert_validation is disabled,\n         * \"opportunistic DANE\" is used.\n         */\n        bool dane;\n\n        /**\n         * Store of trust anchor certificates. This field is optional and can be\n         * left zero-initialized. If used, it shall be initialized using one of the\n         * <c>avs_crypto_certificate_chain_info_from_*</c> helper functions.\n         */\n        avs_crypto_certificate_chain_info_t trusted_certs;\n\n        /**\n         * Store of certificate revocation lists. This field is optional and can be\n         * left zero-initialized. If used, it shall be initialized using one of the\n         * <c>avs_crypto_cert_revocation_list_info_from_*</c> helper functions.\n         */\n        avs_crypto_cert_revocation_list_info_t cert_revocation_lists;\n\n        /**\n         * Local certificate chain to use for authenticating with the peer. This\n         * field is optional and can be left zero-initialized. If used, it shall be\n         * initialized using one of the\n         * <c>avs_crypto_certificate_chain_info_from_*</c> helper functions.\n         */\n        avs_crypto_certificate_chain_info_t client_cert;\n\n        /**\n         * Private key matching #client_cert to use for authenticating with the\n         * peer. This field is optional and can be left zero-initialized, unless\n         * #client_cert is also specified. If used, it shall be initialized using\n         * one of the <c>avs_crypto_private_key_info_from_*</c> helper functions.\n         */\n        avs_crypto_private_key_info_t client_key;\n\n        /**\n         * Enable rebuilding of client certificate chain based on certificates in\n         * the trust store.\n         *\n         * If this field is set to <c>true</c>, and the last certificate in the\n         * #client_cert chain is not self-signed, the library will attempt to find\n         * its ancestors in #trusted_certs and append them to the chain presented\n         * during handshake.\n         */\n        bool rebuild_client_cert_chain;\n    } avs_net_certificate_info_t;\n\nTo populate it properly, we're gonna need at least two pieces of information\nfrom the following list:\n\n- Trusted Certificates, also known as CA / Root certificates (required only\n  if we intend to verify certificates presented to us by the Server; although\n  it's optional it is **highly recommended**),\n- Client Certificate, which is **required**,\n- Client Private Key, which is also **required**.\n\nEach of them come in variety of formats (text, binary, etc.) that need to\nbe loaded and parsed. In most scenarios however, the API provided by `avs_commons`\nwould suffice to do the necessary work.\n\nFor example, to configure Certificate based security, loading all information\nfrom files, we could do something like this:\n\n.. code-block:: c\n\n    const avs_net_certificate_info_t cert_info = {\n        .server_cert_validation = true,\n        .trusted_certs = avs_crypto_certificate_chain_info_from_file(\"./CA.crt\"),\n        .client_cert = avs_crypto_certificate_chain_info_from_file(\"./client.crt\"),\n        // NOTE: \"password\" may be NULL if no password is required\n        .client_key =\n                avs_crypto_client_key_info_from_file(\"./client.key\", \"password\")\n    };\n    avs_net_security_info_t cert_security =\n            avs_net_security_info_from_certificates(cert_info);\n\nSecurity configuration with ``get_security_config`` callback\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nFirmware update module provided with Anjay, lets the user implement security\nconfiguration per download URI. The relevant API is:\n\n.. highlight:: c\n.. snippet-source:: include_public/anjay/fw_update.h\n\n    typedef int anjay_fw_update_get_security_config_t(\n            void *user_ptr,\n            anjay_security_config_t *out_security_info,\n            const char *download_uri);\n\n\nAnd the corresponding handler in ``anjay_fw_update_handlers_t`` to be implemented\nby the user:\n\n.. highlight:: c\n.. snippet-source:: include_public/anjay/fw_update.h\n    :emphasize-lines: 27-29\n\n    typedef struct {\n        /** Opens the stream that will be used to write the firmware package to;\n         * @ref anjay_fw_update_stream_open_t */\n        anjay_fw_update_stream_open_t *stream_open;\n        /** Writes data to the download stream;\n         * @ref anjay_fw_update_stream_write_t */\n        anjay_fw_update_stream_write_t *stream_write;\n        /** Closes the download stream and prepares the firmware package to be\n         * flashed; @ref anjay_fw_update_stream_finish_t */\n        anjay_fw_update_stream_finish_t *stream_finish;\n\n        /** Resets the firmware update state and performs any applicable cleanup of\n         * temporary storage if necessary; @ref anjay_fw_update_reset_t */\n        anjay_fw_update_reset_t *reset;\n\n        /** Returns the name of downloaded firmware package;\n         * @ref anjay_fw_update_get_name_t */\n        anjay_fw_update_get_name_t *get_name;\n        /** Return the version of downloaded firmware package;\n         * @ref anjay_fw_update_get_version_t */\n        anjay_fw_update_get_version_t *get_version;\n\n        /** Performs the actual upgrade with previously downloaded package;\n         * @ref anjay_fw_update_perform_upgrade_t */\n        anjay_fw_update_perform_upgrade_t *perform_upgrade;\n\n        /** Queries security configuration that shall be used for an encrypted\n         * connection; @ref anjay_fw_update_get_security_config_t */\n        anjay_fw_update_get_security_config_t *get_security_config;\n\n        /** Queries CoAP transmission parameters to be used during firmware\n         * update; @ref anjay_fw_update_get_coap_tx_params_t */\n        anjay_fw_update_get_coap_tx_params_t *get_coap_tx_params;\n\n        /** Queries request timeout to be used during firmware update over CoAP+TCP\n         * or HTTP; @ref anjay_fw_update_get_tcp_request_timeout_t */\n        anjay_fw_update_get_tcp_request_timeout_t *get_tcp_request_timeout;\n    } anjay_fw_update_handlers_t;\n\nNow, the ``anjay_fw_update_get_security_config_t`` job is to fill\n``anjay_security_config_t`` properly. This structure consists of four fields:\n\n.. highlight:: c\n.. snippet-source:: include_public/anjay/core.h\n\n    typedef struct {\n        /**\n         * DTLS keys or certificates.\n         */\n        avs_net_security_info_t security_info;\n\n        /**\n         * Single DANE TLSA record to use for certificate verification, if\n         * applicable.\n         */\n        const avs_net_socket_dane_tlsa_record_t *dane_tlsa_record;\n\n        /**\n         * TLS ciphersuites to use.\n         *\n         * A value with <c>num_ids == 0</c> (default) will cause defaults configured\n         * through <c>anjay_configuration_t::default_tls_ciphersuites</c>\n         * to be used.\n         */\n        avs_net_socket_tls_ciphersuites_t tls_ciphersuites;\n\n        /*\n         * Server Name Indicator to use for authenticating with the peer during\n         * secure TLS connection. The value is passed to the underlying TLS library\n         * that need to take this variable into account for it make any effect. This\n         * field is optional and can be left zero-initialized. If not set the\n         * integration layer should use the Server URI instead.\n         */\n        const char *server_name_indication;\n    } anjay_security_config_t;\n\nWe've already seen in previous sections how to configure\n``security_info``. Also, for now there is no need to worry about\n``dane_tlsa_record``, ``tls_ciphersuites`` and ``server_name_indication`` - they can be\nreset to zero.\n\nImplementation\n^^^^^^^^^^^^^^\n\nOur implementation will use the following strategy:\n\n#. Try loading security info from the data model first (i.e. Security Object).\n#. If that failed, attempt loading certificates from predefined paths.\n\n.. important::\n\n    Before we jump into implementation, there's one more important thing\n    to keep in mind: the lifetime of ``anjay_security_config_t``\n    fields. Failing to satisfy lifetime requirements will be met with\n    undefined behavior.\n\n    The fields of ``anjay_security_config_t`` contain references to file\n    paths, binary security keys, and/or ciphersuite lists. After our\n    ``get_security_config`` is called, they are not immediately stored\n    anywhere, and for that reason we need to ensure their lifetime is as\n    long as necessary. The documentation describes this in more detail,\n    and we recommend to have a glance at it.\n\nOur simplified implementation uses either ``anjay_security_config_from_dm()``\nwhich caches the buffers inside the Anjay object in a way that is compatible\nwith the firmware update object implementation, or when the fallback to\ncertificates is needed, only literal c-strings are used, thus the lifetime of\nsecurity configuration in both cases is just right.\n\nThe implementation is presented below. Changes made since\n:doc:`last time <FU-BasicImplementation>` are highlighted:\n\n.. snippet-source:: examples/tutorial/firmware-update/secure-downloads/src/firmware_update.c\n    :emphasize-lines: 11-12, 106-133, 141, 155-157\n\n    #include \"./firmware_update.h\"\n\n    #include <assert.h>\n    #include <errno.h>\n    #include <stdio.h>\n    #include <sys/stat.h>\n    #include <unistd.h>\n\n    static struct fw_state_t {\n        FILE *firmware_file;\n        // anjay instance this firmware update singleton is associated with\n        anjay_t *anjay;\n    } FW_STATE;\n\n    static const char *FW_IMAGE_DOWNLOAD_NAME = \"/tmp/firmware_image.bin\";\n\n    static int fw_stream_open(void *user_ptr,\n                              const char *package_uri,\n                              const struct anjay_etag *package_etag) {\n        // For a moment, we don't need to care about any of the arguments passed.\n        (void) user_ptr;\n        (void) package_uri;\n        (void) package_etag;\n\n        // It's worth ensuring we start with a NULL firmware_file. In the end\n        // it would be our responsibility to manage this pointer, and we want\n        // to make sure we never leak any memory.\n        assert(FW_STATE.firmware_file == NULL);\n        // We're about to create a firmware file for writing\n        FW_STATE.firmware_file = fopen(FW_IMAGE_DOWNLOAD_NAME, \"wb\");\n        if (!FW_STATE.firmware_file) {\n            fprintf(stderr, \"Could not open %s\\n\", FW_IMAGE_DOWNLOAD_NAME);\n            return -1;\n        }\n        // We've succeeded\n        return 0;\n    }\n\n    static int fw_stream_write(void *user_ptr, const void *data, size_t length) {\n        (void) user_ptr;\n        // We only need to write to file and check if that succeeded\n        if (fwrite(data, length, 1, FW_STATE.firmware_file) != 1) {\n            fprintf(stderr, \"Writing to firmware image failed\\n\");\n            return -1;\n        }\n        return 0;\n    }\n\n    static int fw_stream_finish(void *user_ptr) {\n        (void) user_ptr;\n        assert(FW_STATE.firmware_file != NULL);\n\n        if (fclose(FW_STATE.firmware_file)) {\n            fprintf(stderr, \"Closing firmware image failed\\n\");\n            FW_STATE.firmware_file = NULL;\n            return -1;\n        }\n        FW_STATE.firmware_file = NULL;\n        return 0;\n    }\n\n    static void fw_reset(void *user_ptr) {\n        // Reset can be issued even if the download never started.\n        if (FW_STATE.firmware_file) {\n            // We ignore the result code of fclose(), as fw_reset() can't fail.\n            (void) fclose(FW_STATE.firmware_file);\n            // and reset our global state to initial value.\n            FW_STATE.firmware_file = NULL;\n        }\n        // Finally, let's remove any downloaded payload\n        unlink(FW_IMAGE_DOWNLOAD_NAME);\n    }\n\n    // A part of a rather simple logic checking if the firmware update was\n    // successfully performed.\n    static const char *FW_UPDATED_MARKER = \"/tmp/fw-updated-marker\";\n\n    static int fw_perform_upgrade(void *user_ptr) {\n        if (chmod(FW_IMAGE_DOWNLOAD_NAME, 0700) == -1) {\n            fprintf(stderr,\n                    \"Could not make firmware executable: %s\\n\",\n                    strerror(errno));\n            return -1;\n        }\n        // Create a marker file, so that the new process knows it is the \"upgraded\"\n        // one\n        FILE *marker = fopen(FW_UPDATED_MARKER, \"w\");\n        if (!marker) {\n            fprintf(stderr, \"Marker file could not be created\\n\");\n            return -1;\n        }\n        fclose(marker);\n\n        assert(ENDPOINT_NAME);\n        // If the call below succeeds, the firmware is considered as \"upgraded\",\n        // and we hope the newly started client registers to the Server.\n        (void) execl(FW_IMAGE_DOWNLOAD_NAME, FW_IMAGE_DOWNLOAD_NAME, ENDPOINT_NAME,\n                     NULL);\n        fprintf(stderr, \"execl() failed: %s\\n\", strerror(errno));\n        // If we are here, it means execl() failed. Marker file MUST now be removed,\n        // as the firmware update failed.\n        unlink(FW_UPDATED_MARKER);\n        return -1;\n    }\n\n    static int fw_get_security_config(void *user_ptr,\n                                      anjay_security_config_t *out_security_info,\n                                      const char *download_uri) {\n        (void) user_ptr;\n        if (!anjay_security_config_from_dm(FW_STATE.anjay, out_security_info,\n                                           download_uri)) {\n            // found a match\n            return 0;\n        }\n\n        // no match found, fallback to loading certificates from given paths\n        memset(out_security_info, 0, sizeof(*out_security_info));\n        const avs_net_certificate_info_t cert_info = {\n            .server_cert_validation = true,\n            .trusted_certs =\n                    avs_crypto_certificate_chain_info_from_file(\"./certs/CA.crt\"),\n            .client_cert = avs_crypto_certificate_chain_info_from_file(\n                    \"./certs/client.crt\"),\n            .client_key = avs_crypto_private_key_info_from_file(\n                    \"./certs/client.key\", NULL)\n        };\n        // NOTE: this assignment is safe, because cert_info contains pointers to\n        // string literals only. If the configuration were to load certificate info\n        // from buffers they would have to be stored somewhere - e.g. on the heap.\n        out_security_info->security_info =\n                avs_net_security_info_from_certificates(cert_info);\n        return 0;\n    }\n\n    static const anjay_fw_update_handlers_t HANDLERS = {\n        .stream_open = fw_stream_open,\n        .stream_write = fw_stream_write,\n        .stream_finish = fw_stream_finish,\n        .reset = fw_reset,\n        .perform_upgrade = fw_perform_upgrade,\n        .get_security_config = fw_get_security_config\n    };\n\n    const char *ENDPOINT_NAME = NULL;\n\n    int fw_update_install(anjay_t *anjay) {\n        anjay_fw_update_initial_state_t state;\n        memset(&state, 0, sizeof(state));\n\n        if (access(FW_UPDATED_MARKER, F_OK) != -1) {\n            // marker file exists, it means firmware update succeded!\n            state.result = ANJAY_FW_UPDATE_INITIAL_SUCCESS;\n            unlink(FW_UPDATED_MARKER);\n        }\n        // make sure this module is installed for single Anjay instance only\n        assert(FW_STATE.anjay == NULL);\n        FW_STATE.anjay = anjay;\n        // install the module, pass handlers that we implemented and initial state\n        // that we discovered upon startup\n        return anjay_fw_update_install(anjay, &HANDLERS, NULL, &state);\n    }\n"
  },
  {
    "path": "doc/sphinx/source/FirmwareUpdateTutorial/FU1.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\n:orphan:\n\n.. meta::\n\n    :http-equiv=Refresh: 1; url=FU-Introduction.html\n\n.. title:: Redirection\n\n↳ :doc:`FU-Introduction`\n========================\n"
  },
  {
    "path": "doc/sphinx/source/FirmwareUpdateTutorial/FU2.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\n:orphan:\n\n.. meta::\n\n    :http-equiv=Refresh: 1; url=FU-BasicImplementation.html\n\n.. title:: Redirection\n\n↳ :doc:`FU-BasicImplementation`\n===============================\n"
  },
  {
    "path": "doc/sphinx/source/FirmwareUpdateTutorial/FU3.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\n:orphan:\n\n.. meta::\n\n    :http-equiv=Refresh: 1; url=FU-ModesAndProtocols.html\n\n.. title:: Redirection\n\n↳ :doc:`FU-ModesAndProtocols`\n=============================\n"
  },
  {
    "path": "doc/sphinx/source/FirmwareUpdateTutorial/FU4.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\n:orphan:\n\n.. meta::\n\n    :http-equiv=Refresh: 1; url=FU-SecureDownloads.html\n\n.. title:: Redirection\n\n↳ :doc:`FU-SecureDownloads`\n===========================\n"
  },
  {
    "path": "doc/sphinx/source/FirmwareUpdateTutorial/FU5.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\n:orphan:\n\n.. meta::\n\n    :http-equiv=Refresh: 1; url=FU-PoorConnectivity.html\n\n.. title:: Redirection\n\n↳ :doc:`FU-PoorConnectivity`\n============================\n"
  },
  {
    "path": "doc/sphinx/source/FirmwareUpdateTutorial/FU6.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\n:orphan:\n\n.. meta::\n\n    :http-equiv=Refresh: 1; url=FU-DownloadResumption.html\n\n.. title:: Redirection\n\n↳ :doc:`FU-DownloadResumption`\n==============================\n"
  },
  {
    "path": "doc/sphinx/source/FirmwareUpdateTutorial.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nFirmware Update Tutorial\n========================\n\n.. toctree::\n   :glob:\n   :titlesonly:\n\n   FirmwareUpdateTutorial/FU-Introduction\n   FirmwareUpdateTutorial/FU-BasicImplementation\n   FirmwareUpdateTutorial/FU-ModesAndProtocols\n   FirmwareUpdateTutorial/FU-SecureDownloads\n   FirmwareUpdateTutorial/FU-PoorConnectivity\n   FirmwareUpdateTutorial/FU-DownloadResumption\n   FirmwareUpdateTutorial/FU-AdvancedFirmwareUpdate\n"
  },
  {
    "path": "doc/sphinx/source/Introduction.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nIntroduction\n============\n\n.. attention::\n\n   With release of Anjay 3.10.0, the `library's license terms have changed\n   <https://github.com/AVSystem/Anjay/blob/master/LICENSE>`_. Please make sure\n   that you have reviewed it before updating to the new major release.\n\n**Anjay** is a library that implements the *OMA Lightweight Machine to Machine*\nprotocol, including the necessary subset of CoAP.\n\nThe project has been created and is actively maintained by\n`AVSystem <https://www.avsystem.com>`_.\n\nProtocol support status\n-----------------------\n\nThe basis for this implementation were the following documents:\n\n- *Lightweight Machine to Machine Technical Specification: Core*,\n  document number ``OMA-TS-LightweightM2M_Core-V1_1_1-20190617-A``\n- *Lightweight Machine to Machine Technical Specification: Transport Bindings*,\n  document number ``OMA-TS-LightweightM2M_Transport-V1_1_1-20190617-A``.\n\nIn case of ambiguities, existing implementations were considered as a reference.\n\nThe following features are **supported**:\n\n- Bootstrap - full support\n\n  - Enrollment over Secure Transport and smart card bootstrap are\n    :doc:`available commercially <CommercialFeatures>`\n\n- Client Registration - full support\n- Device Management and Service Enablement - full support\n- Information Reporting - full support\n\n- Data formats\n\n  - Plain Text\n  - Opaque\n  - CBOR\n  - TLV\n  - SenML JSON\n  - SenML CBOR\n  - LwM2M JSON (output only)\n\n- Security\n\n  - DTLS with Certificates, if supported by backend TLS library\n  - DTLS with PSK, if supported by backend TLS library\n  - NoSec mode\n  - Support for Hardware Security Modules (:doc:`available commercially <CommercialFeatures>`)\n\n- Transport\n\n  - Support for UDP Binding\n  - Support for TCP Binding\n  - Support for SMS Binding (:doc:`available commercially <CommercialFeatures>`)\n  - Support for NIDD Binding (:doc:`available commercially <CommercialFeatures>`)\n\nThe following features are **not implemented**:\n\n- RPK DTLS mode\n- LwM2M JSON (input)\n\nTechnical information\n---------------------\n\nAnjay is written in standards-compliant C99. It partly relies on some POSIX\nlibrary extensions, although it can be easily ported to non-POSIX platforms.\n\nSome optional features require C11's ``stdatomic.h`` header to be available\n(``ANJAY_WITH_EVENT_LOOP``, ``AVS_COMMONS_COMPAT_THREADING_WITH_ATOMIC_SPINLOCK``).\n\nIts only external dependency is the open source\n`AVSystem Commons Library <https://github.com/AVSystem/avs_commons>`_. That\nlibrary in turn may additionally depend either on\n`OpenSSL <https://www.openssl.org/>`_ or `Mbed TLS <https://www.trustedfirmware.org/projects/mbed-tls/>`_\nor `TinyDTLS <https://projects.eclipse.org/projects/iot.tinydtls>`_ for DTLS\nsupport.\n\nTo build Anjay from source, `CMake <https://www.cmake.org/>`_ version 3.16 or\nnewer is necessary.\n\nDeprecated and experimental features\n------------------------------------\n\nAnjay's Doxygen documentation contains, among others, **@deprecated** and\n**@experimental** tags:\n\n- features tagged with **@deprecated** are no longer supported and will be\n  deleted in the future versions,\n- features tagged with **@experimental** may contain changes (in the future\n  versions) that will break their backward compatibility.\n"
  },
  {
    "path": "doc/sphinx/source/KnownIssues.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nKnown Issues\n============\n\n.. contents:: :local:\n\nNon valid hostname may appear in SNI extension\n----------------------------------------------\n\nThe DTLS Server Name Indication (SNI) extension is designed to communicate\nthe expected server hostname during a TLS/DTLS handshake, particularly when\nit differs from the connection URI. According to `RFC6066`, the SNI extension\nmust contain a valid hostname, not an IP address.\n\nIn Anjay, when the LwM2M Server URI (``/0/x/0``) is set to a raw IP address, the default\nMbedTLS or OpenSSL integration layer used by Anjay automatically includes that IP address\nin the SNI field. This behavior is non-compliant with `RFC6066 <https://datatracker.ietf.org/doc/html/rfc6066>`_, since both\nMbedTLS and OpenSSL derive the SNI value from the hostname used for\ncertificate validation.\n\n.. note::\n\n   You can resolve this issue by configuring a valid hostname in the\n   SNI Resource (``/0/x/14``) of the Security Object instance used to\n   connect to the server. The value provided in this resource will be\n   sent in the SNI extension and will also be used for certificate\n   verification. Therefore, it must match the Common Name (CN) or\n   Subject Alternative Name (SAN) in the server’s certificate.\n\nIf you prefer to verify the certificate by IP address instead, the SNI\nextension can be disabled. In MbedTLS, this can be done by removing the\n``#define MBEDTLS_SSL_SERVER_NAME_INDICATION`` directive from the MbedTLS\nconfiguration header.\n\n.. warning::\n\n   This issue may lead to DTLS handshake failures without any explicit\n   error message appearing in Anjay logs.\n\nCompatibility issues between OpenSSL and libp11 on some Linux distributions\n---------------------------------------------------------------------------\n\nIn certain Linux distributions, the versions of OpenSSL and the PKCS#11 engine\n(``libp11``, providing ``libengine-pkcs11-openssl``) shipped with the system may\nbe incompatible. This incompatibility can result in runtime failures such as\nsegmentation faults when using PKCS#11-backed cryptography through OpenSSL.\n\nFor example, on Ubuntu 24.04, OpenSSL 3.0.13 combined with libp11 0.4.12 exposes\na bug in the libp11 library that leads to a crash of the application. This issue\nhas been fixed recent version of libp11, but the fix is not yet available in the\ndefault Ubuntu repositories at the time of writing.\n\n.. important::\n\n   If you encounter such issues (e.g., segmentation faults or unexpected\n   handshake failures when using PKCS#11 with OpenSSL), consider upgrading\n   libp11 to a newer version than the one provided by your distribution. The\n   simplest method is to manually build and install libp11 from the latest\n   upstream sources.\n\n   However, installing a self-built version with ``sudo make install`` may\n   lead to incompatibilities or conflicts with system packages managed by\n   apt or other package managers. It is therefore **preferable to rebuild\n   and install the library in a way that remains compatible with the package\n   management system** of your distribution. The example below demonstrates\n   such an approach, which was successfully used on Ubuntu 24.04 to produce a\n   clean ``.deb`` package at the time of writing.\n\nExample procedure (tested on Ubuntu 24.04)\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n.. warning::\n\n   The procedure below is provided as an **example** of how the issue was\n   resolved in our environment. The exact steps required on your system may (and\n   likely will) differ.\n\nThe following steps reproduce the approach used in our internal CI environment\nto rebuilt ``libengine-pkcs11-openssl`` from a newer upstream tag:\n\n.. code-block:: bash\n\n    # Enable fetching package sources\n    sudo sed -i -e 's/Types: deb/Types: deb deb-src/g' /etc/apt/sources.list.d/ubuntu.sources\n    sudo apt-get update\n\n    # Obtain package source and generic dependencies used for building .debs\n    mkdir ~/libp11-pkg-build && cd ~/libp11-pkg-build\n    apt-get source libengine-pkcs11-openssl\n    sudo apt-get install -y devscripts dpkg-dev fakeroot quilt\n\n    # Download newer upstream release of libp11\n    wget https://github.com/OpenSC/libp11/archive/refs/tags/libp11-0.4.16.tar.gz -O libp11_0.4.16.orig.tar.gz\n    tar xf libp11_0.4.16.orig.tar.gz\n    mv libp11-libp11-0.4.16 libp11-0.4.16\n\n    # Reuse debian/ directory from previous package version\n    cp -a libp11-0.4.12/debian libp11-0.4.16/\n    cd libp11-0.4.16\n    rm -rf debian/patches\n\n    # Tag new version\n    dch -v 0.4.16-0ubuntu1+local1 \"Local rebuild of libp11 from upstream tag libp11-0.4.16\"\n\n    # Install build dependencies and build the package\n    sudo apt-get build-dep -y libp11\n    debuild -us -uc -b\n\n    # Install the rebuilt package\n    sudo apt-get install -y ../libengine-pkcs11-openssl_0.4.16-0ubuntu1+local1_amd64.deb\n\nAfter installation, the newer libp11 should resolve the incompatibility and\nenable stable operation of PKCS#11 integration with OpenSSL-based DTLS/TLS\nbackend.\n\nNotification payload may not reflect threshold crossing\n-------------------------------------------------------\n\nIn certain cases, a notification may be triggered when the observed value\ncrosses a threshold (for example, configured using the **gt** attribute),\nbut the actual notification is not sent immediately. If the value goes back\nbelow (or above) that threshold before the notification message is constructed,\nthe server will receive a notification containing the new value that itself would\nnot have caused the notification to be triggered. This applies to all value-based \nattributes: **lt**, **gt**, **st** and **edge**.\n"
  },
  {
    "path": "doc/sphinx/source/LwM2M.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nOMA LwM2M - Brief description\n=============================\n\n.. contents:: :local:\n\n`Lightweight Machine to Machine\n<https://www.omaspecworks.org/what-is-oma-specworks/iot/lightweight-m2m-lwm2m/>`_\nis a protocol developed by `OMA SpecWorks <https://omaspecworks.org/>`_ for\nremote device management in the Internet of Things and other\nMachine-to-Machine applications.\n\nInitially LwM2M was designed to be transported either over UDP, or over SMS\ndirectly in cellular phone networks. The next iteration of the standard, LwM2M\n1.1, added support for another IP transport, TCP, and two more non-IP transports,\n3GPP CIoT and LoRaWAN. All of these transports can be additionally secured with\nuse of (D)TLS.\n\nThe application data is encapsulated using the `Constrained Application Protocol\n<https://tools.ietf.org/html/rfc7252>`_ (CoAP). CoAP is an application layer\nprotocol similar to HTTP in philosophy and general semantics, but designed\nspecifically with being lightweight in mind. CoAP makes it possible to transmit\nmessages with low overhead - the minimal header size is just 4 bytes, which makes\nit feasible to send meaningful content within single, non-fragmented UDP\ndatagrams, even over links with low MTU, such as SMS.\n\nSince LwM2M 1.1, CoAP messages can be additionally secured using `OSCORE\n<https://datatracker.ietf.org/doc/html/rfc8613>`_, an application layer protocol\nwith which it's possible to not only establish a secure communication channel\nbetween LwM2M Clients and Servers, but also between the Client and external\napplications. It's independent of security protocols used on other layers.\n\nAnjay is designed to hide most of the protocol details from application\ndevelopers - however, a rudimentary understanding of the protocol is necessary\nto understand the general philosophy and semantics of the parts that remain to\nbe implemented. You are encouraged to read the `full specification\n<https://www.omaspecworks.org/what-is-oma-specworks/iot/lightweight-m2m-lwm2m/>`_,\nbut this article provides a quick summary of the most important concepts.\n\n.. note::\n  This article targets application developers and covers technical aspects of\n  the protocol that an engineer working with LwM2M should understand. For a\n  more superficial description you might want to read\n  `LwM2M Crash Course <https://avsystem.com/crashcourse/lwm2m/>`_ instead.\n\n\n.. _clients-and-servers:\n\nClients and servers\n-------------------\n\nA network environment using the LwM2M protocol consists of three types of\nentities:\n\n- **LwM2M Clients** are located on end devices. They communicate with server(s),\n  allowing them to manage and monitor the devices' resources, which are exposed\n  via a standardized `data model`_.\n\n  - A LwM2M Client is uniquely identified independently of its network address\n    by an **Endpoint Client Name** - a URN uniquely assigned to a device by its\n    manufacturer. OMA recommends the Endpoint Client Name to be in one of the\n    following formats:\n\n    - ``urn:uuid:########-####-####-############`` (UUID = Universally Unique\n      Identifier)\n    - ``urn:dev:ops:{OUI}-{ProductClass}-{SerialNumber}`` (OUI =\n      Organizationally Unique Identifier - as in e.g. MAC addresses)\n    - ``urn:dev:os:{OUI}-{SerialNumber}``\n    - ``urn:imei:###############`` (IMEI = International Mobile Equipment\n      Identity)\n    - ``urn:esn:########`` (ESN = Electronic Serial Number)\n    - ``urn:meid:##############`` (MEID = Mobile Equipment Identifier)\n    - ``urn:imei-msisdn:###############-###############`` (MSISDN = Mobile\n      Station International Subscriber Directory Number, i.e. a standardized\n      phone number)\n\n- **LwM2M Bootstrap Server** is a specific server that may be contacted by the\n  Client during its first or every boot-up. Its only purpose is to initialize\n  the data model, including connections to regular LwM2M Servers, before first\n  contact to such. The Bootstrap Server communicates with the Client using a\n  different set of commands, so it cannot be considered a \"LwM2M Server\" in the\n  ordinary sense.\n\n- **LwM2M Servers** maintain connections with the clients and have the ability\n  to read from and write to the data model exposed by the clients. Any given\n  client may be concurrently connected to more than one LwM2M Server, and each\n  of them may have access only to a part of the whole data model.\n\nAnjay is a framework for implementing LwM2M Clients. For this reason, the rest\nof this article will be written from the Client perspective.\n\n.. _data-model:\n\nData model\n----------\n\nEach LwM2M Client presents a *data model* - standardized, symbolic\nrepresentation of its configuration and state that is accessible for reading\nand modifying by LwM2M Servers. It can be thought of as a combination of a\nhierarchical configuration file, and a view on statistical information about the\ndevice and its environment.\n\nThe LwM2M data model organized as a tree up to four levels deep. Entities on\neach of those levels are identified with numerical identifiers, in range\n0-65534, inclusive. A reserved value of 65535 (called ``MAX_ID`` in protocol's\nspecification) carries a special meaning in many contexts. Those levels\nare:\n\n- **Object** - each Object represent some different concept of data accessible\n  via the LwM2M Client. For example, separate Objects are defined for managing\n  connections with LwM2M Servers, for managing network connections, for\n  accessing data from various types of sensors, etc.\n\n  Each Object is assigned a unique numerical identifier. OMA manages a\n  `registry of known Object IDs\n  <https://technical.openmobilealliance.org/OMNA/LwM2M/LwM2MRegistry.html>`_.\n  Each Object defines a set of Resources whose meanings are common for each\n  Object Instance.\n\n- **Object Instance** - some Objects are described as \"single-instance\" - such\n  Objects always have exactly one Instance with identifier 0. Examples of such\n  Objects include the Device object which describes the device itself, and the\n  Firmware Update object which is used to perform firmware upgrades.\n\n  Other Objects may have multiple Instances; sometimes the number of Instances\n  may be variable and the Instances themselves may be creatable via LwM2M.\n  Examples of such Objects include the Object that manages connections to LwM2M\n  Servers, Object that represents optional software packages installed on the\n  device, and Objects representing sensors (whose instances are, however, not\n  creatable).\n\n- **Resource** - each Object Instance of a given Object supports the same set\n  of Resources, as defined by the Object definition. Within a given Object,\n  each Resource ID has a well-defined meaning, and represent the same concept.\n  However, some Resources may not be present in some Object Instances, and,\n  obviously, their values and mapping onto real-world entities may be different.\n\n- **Resource Instance** - some Resources may have multiple instances (they're\n  sometimes called *Multiple-Resources*), effectively causing the type of that\n  resource to be an associative array with an integer index. Since LwM2M 1.1,\n  instances of these resources may be addressed individually.\n\nThe numerical identifiers on each of these levels form a path, which is used\nas the path portion of CoAP URLs. For example, a path ``/9/2/17/3`` refers to\nResource Instance ID=3 of Resource ID=17 in Object Instance ID=2 of Object\nID=9. Whole Resources (``/9/2/17``), Object Instances (``/9/2``) or even\nObjects (``/9``) may be referred to using this syntax as well.\n\nObjects\n^^^^^^^\n\nEach Object definition, which may be found in the LwM2M specification, features\nthe following information:\n\n- **Name** - description of the object; it is not used in the actual on-wire\n  protocol.\n\n- **Object ID** - numerical identifier of the Object\n\n- **Instances** - *Single* (always has one Instance with ID=0) or *Multiple*\n  (may have arbitrary number of Instances depending on current configuration)\n\n- **Mandatory** - *Mandatory* (must be supported by all LwM2M Client\n  implementations) or *Optional* (may not be supported)\n\n- **Object URN**\n\n- Resource definitions\n\nThe current set of Mandatory Objects consists of:\n\n- ``/0`` - **LwM2M Security** - contains confidential part of information about\n  connections to the LwM2M Servers configured in the Client. From the on-wire\n  protocol perspective, it is write-only and accessible only via the\n  `Bootstrap Interface`_. Implementation of this object is readily available in\n  Anjay's ``security`` module.\n\n- ``/1`` - **LwM2M Server** - contains non-confidential part of information\n  about connections to the LwM2M Servers configured in the Client.\n  Implementation of this object is readily available in Anjay's ``server``\n  module.\n\n- ``/3`` - **Device** - contains basic information about the device, such as\n  e.g. serial number.\n\nAdditionally, Object ``/2`` (**Access Control**) needs to be supported and\npresent if the Client supports more than one LwM2M Server connection at once.\nImplementation of this object is readily available in Anjay's ``access_control``\nmodule.\n\n.. _lwm2m-resources:\n\nResources\n^^^^^^^^^\n\nEach of the Resource definitions, contained in each Object definition, features\nthe following information:\n\n- **ID** - numerical identifier of the Resource.\n\n- **Name** - short description of the resource; it is not used in the actual\n  on-wire protocol.\n\n- **Operations** - one of:\n\n  - **R** - read-only Resource\n  - **W** - write-only Resource\n  - **RW** - writeable Resource\n  - **E** - executable Resource\n  - *(empty)* - used only in the LwM2M Security Object; signifies a Resource not\n    accessible via the `Device Management and Service Enablement Interface`_\n\n- **Instances** - *Single* or *Multiple*; \"Multiple\" means that the type of data\n  in the resource is actually an \"array\" - called such in the Anjay API, but\n  actually more similar to an associative data structure. It is a list of pairs,\n  each of which containing a unique Resource Instance ID (range 0-65535,\n  inclusive) and instance value, of the type referred in the Resource\n  definition.\n\n- **Mandatory** - *Mandatory* or *Optional*; Mandatory resources need to be\n  present in all Instances on all devices. Optional resources may not be present\n  in all Instances, and may even be not supported at all on some Clients.\n\n- **Type** - data type of the Resource value (or its instances in case of\n  Multiple Resources).\n\n- **Range or Enumeration** - specification of valid values for the Resource,\n  within the given data type.\n\n- **Units** - units in which a numerical value is given.\n\n- **Description** - detailed description of the resource.\n\n.. _lwm2m-attributes:\n\nAttributes\n^^^^^^^^^^\n\nEach entity in the data model (Object, Object Instance, Resource or Resource\nInstance) can have various \"attributes\" attached. Most of these attributes are\nhandled automatically by Anjay. There are two types of attributes currently\ndefined in the LwM2M specification:\n\n- **<PROPERTIES>** Class Attributes are read-only metadata that may be read by\n  Servers without accessing the data itself, possibly allowing it to operate\n  more effectively. These include:\n\n  - **Dimension** (``dim``) - in case of Multiple Resources, it is the number of\n    Resource Instances.\n\n  - **Object Version** (``ver``) - provides a way for versioning Object\n    definitions. This attribute, if not present, implies 1.0 version of the\n    Object, although the user is free to adjust it in ``anjay_dm_object_def_t``\n    structure.\n\n  - additional properties used only in Bootstrap-Discover operation: **Short\n    Server ID** (``ssid``) and **Server URI** (``uri``).\n\n- **<NOTIFICATION>** Class Attributes are writeable by LwM2M Servers and affect\n  the way changes in observed resources are notified over the\n  `Information Reporting Interface`_.\n\n  By default, *Notify* messages are sent each time there is some change to the\n  value of the queried path (which may be a Resource, or all Resources within a\n  given Object Instance or Object, if the Observe request was called on such\n  higher-level path).\n\n  This behavior can be modified using the following available attributes:\n\n  - **Minimum Period** (``pmin``) - if set to a non-zero value, notifications\n    will never be sent more often than once every ``pmin`` seconds.\n\n  - **Maximum Period** (``pmax``) - if set, notifications will *always* be sent\n    at least once every ``pmax`` seconds, even if the value did not change.\n\n  - **Greater Than** (``gt``) and **Less Than** (``lt``) - applicable only to\n    numeric Resources - if set, notifications will only be sent when the value\n    changes from below to above or from above to below the specified threshold.\n    Contrary to what the names of these Attributes might suggest, there is no\n    semantic difference between the two - both behave as equivalent\n    bi-directional thresholds.\n\n  - **Step** (``st``) - applicable only to numeric Resources - if set,\n    notifications will only be sent if the numerical value is different from the\n    previously notified value by at least ``st``.\n\n  - **Minimum Evaluation Period** (``epmin``) - if set, the notification\n    criteria won't be evaluated more often than every ``epmin`` seconds.\n\n  - **Maximum Evaluation Period** (``epmax``) - if set, the notification\n    criteria will be evaluated at least every ``epmax`` seconds (although this\n    attribute is ignored in Anjay, as the automatic evaluation happens when\n    ``pmax`` seconds pass).\n\n  When several Attributes are specified at the same time, the relations between\n  them are as follows:\n\n  - ``pmin`` and ``pmax`` have higher priority - even if the requirements for\n    ``gt``, ``lt`` and ``st`` are not met, a notification will always be sent\n    at least once every ``pmax`` seconds - and conversely, even when the\n    requirements for ``gt``, ``lt`` and ``st`` are met, a notification will\n    never be sent more often than once every ``pmin`` seconds.\n\n  - Requirements for just at least one of ``gt``, ``lt`` or ``st`` need to be\n    met if they are set at the same time. For example, if the new value differs\n    by at least ``st`` from the previously sent one, it does not need to cross\n    either of the ``lt``/``gt`` thresholds - the ``st`` condition alone is\n    enough to trigger sending notification.\n\nInterfaces\n----------\n\nLwM2M currently consists of four interfaces through which the Clients, Servers\nand Bootstrap Servers communicate:\n\n- `Bootstrap Interface`_\n- `Registration Interface`_\n- `Device Management and Service Enablement Interface`_\n- `Information Reporting Interface`_\n\nBootstrap Interface\n^^^^^^^^^^^^^^^^^^^\n\nBootstrap Interface defines the set of commands that the Bootstrap Server may\nuse to provision the initial configuration onto the client. In this interface,\nboth the LwM2M Client and the LwM2M Bootstrap Server act as both a CoAP server\nand a CoAP client. The messages that may be exchanged between those include:\n\n- **POST /bs?ep={Endpoint Client Name}** request sent from the Client to the\n  Bootstrap Server signifies a **Bootstrap Request** command. It informs the\n  Bootstrap Server that a new client has appeared on the network and is\n  requesting bootstrap information. However, the protocol also allows the\n  Bootstrap Server to start issuing Bootstrap commands on its own, without\n  receiving a Bootstrap Request message.\n\n- **GET Accept: application/link-format** request sent from the Bootstrap Server\n  to the Client is interpreted as **Bootstrap Discover**. It allows the\n  Bootstrap Server to get information about the data model supported by and\n  present on the client device.\n\n- **GET** with *Content Format* option sent from the Bootstrap Server to the\n  Client is a **Bootstrap Read**. It can be used to read LwM2M Server and Access\n  Control Objects during the Bootstrap phase to query for already configured\n  Servers on the Client.\n\n- **PUT** requests sent from the Bootstrap Server to the Client are interpreted\n  as **Bootstrap Write** commands. These allow creating and writing to Object\n  Instances and Resources in order to initialize the data model to a state\n  appropriate for communication with regular LwM2M Servers.\n\n- **Bootstrap Delete** command, represented as **DELETE** requests from the\n  Bootstrap Server to the Client, allows the Bootstrap Server to delete existing\n  Object Instances.\n\n- Finally, the Bootstrap Server sends a **Bootstrap Finish** command,\n  represented as a **POST /bs** CoAP request send to the Client. Upon receiving\n  it, the Client validates the data model, and in case of success, connects to\n  regular LwM2M Servers, according to the configured stored within the data\n  model.\n\nAs you can see, the Bootstrap Interface is mostly write-only. The Bootstrap\nServer is not able to do any actual management or monitoring of the Client. It\ncan only prepare it for communication with regular LwM2M Servers. Nevertheless,\nnothing prevents Bootstrap Server and regular Server applications from\ncoexisting on the same host.\n\nThe Bootstrap Server is the only entity that can manage connections to\nLwM2M Servers on a Client via the LwM2M protocol itself. For this reason, an\nassociation with a Bootstrap Server may be maintained indefinitely - however,\nthe protocol also provides an option to permanently disconnect from the\nBootstrap Server after a successful bootstrap.\n\nBootstrap information may also be provided by means other than the Bootstrap\nServer. The protocol also allows the bootstrap information to be pre-provisioned\nat the factory, or read from a smart-card. In those cases, an attempt to contact\na Bootstrap Server may not even be made.\n\n.. _lwm2m-registration-interface:\n\nRegistration Interface\n^^^^^^^^^^^^^^^^^^^^^^\n\nThe Registration Interface defines the protocol the Client uses to inform an\nLwM2M Server about its presence and availability. In this interface, the LwM2M\nServer acts as a CoAP server, and the LwM2M Client is a CoAP client. The\nrequests that may be sent from the Client to the Server include:\n\n- **Register**, represented as a CoAP **POST /rd?...** request, is initially\n  sent by the Client when it goes online. It informs the Server that the Client\n  is available for receiving commands on the\n  `Device Management and Service Enablement Interface`_ and the\n  `Information Reporting Interface`_, and presents it with basic metadata\n  describing its data model. It also gives the server the IP address and port\n  (or phone number, in case of SMS transport) on which the Client is accessible\n  - these are taken directly from the source fields in IP and UDP layer headers.\n\n- **Update**, which is a CoAP **POST** request on an URL previously returned in\n  a response to *Register*. **Update** is sent in following situations:\n\n  - periodically - to ensure the Server that the device is still online,\n\n  - whenever any of the information previously given in a Register message\n    changes - so that the Server always has up-to-date information about the\n    Client's state.\n\n- **De-register** (CoAP **DELETE**) may be sent by the Client if it can\n  determine that it is shutting down. It terminates the association between\n  the Client and Server. Sending it is, however, not required, as the Server\n  will also consider the association terminated if the Client does not report\n  with a Register or Update message for a configured period of time.\n\nDevice Management and Service Enablement Interface\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nThis is the main interface on which the actual device management occurs. In this\ninterface, most of the requests are made by the LwM2M Server, that is, it acts as\na CoAP client, sending requests to the LwM2M Client, which acts as a server on\nthe CoAP layer. The exception is the **Send** request (available since LwM2M\n1.1), which is issued by the LwM2M Client. Please note that the IP addresses and\nport numbers are exactly the same as previously established via the `Registration\nInterface`_ - it means that for given two endpoints, the client/server\nrelationship on the CoAP layer is reversible at any time.\n\nFollowing operations can be issued by the LwM2M Server:\n\n- **Discover** (CoAP **GET Accept: application/link-format**) allows the Server\n  to get a list of all supported and present Objects, Object Instances and\n  Resources, and to read Attributes_ attached to them. Data stored in Resources\n  is not returned.\n\n- **Read** (CoAP **GET** other than the above) reads data - either from a single\n  Resource Instance, entire Resource, Object Instance, or even a whole Object\n  at once.\n\n- **Write** allows the Server to modify the data model. It comes in two\n  flavors:\n\n  - **PUT /{Object ID}/{Instance ID}[/{Resource ID}[/{Resource Instance ID}]]**\n    request signifies the *Replace* method. It can be called on either a single\n    Resource to replace its value, or on a whole Object Instance - in that case\n    all existing contents of that Instance are erased and replaced with the\n    supplied data.\n\n  - **POST /{Object ID}/{Instance ID}** request means *Partial Update*. It can\n    only be called on a whole Object Instance and only replaces the Resources\n    present in the request payload, retaining other previously existing data.\n\n  Both methods require the *Content-Format* option to be included in the\n  request.\n\n  Anjay attempts to abstract away the difference between the two. All such bulk\n  writes are translated to series of writes on single values. However, to\n  properly support the *Replace* semantics, an additional virtual operation\n  called *Reset* is introduced, called before the series of writes during a\n  *Replace* and intended to revert the Object Instance to its initial, empty\n  state.\n\n- While most entities in the data model are designed to be read and written,\n  a given entity may alternatively be specified as supporting the **Execute**\n  operation, represented by a **POST /{Object ID}/{Instance ID}/{Resource ID}**\n  request. Execute operation is introduced in the data model wherever a way is\n  necessary to instruct the device to perform some non-idempotent operation such\n  as a reboot or a firmware upgrade.\n\n- A **PUT** request *without* a *Content-Format* option is interpreted as\n  **Write Attributes**. The Attributes are passed as query string elements in\n  the target URL. These Attributes mostly alter the way the Client behaves in\n  relation to the `Information Reporting Interface`_ and are explained in detail\n  in the Attributes_ section.\n\n- A **POST** request targeting one of the root paths in the data model (called\n  \"Objects\", see `Data model`_) represents the **Create** operation. It creates\n  a new Object Instance, which gives a way to manage configuration entities that\n  might have a variable and configurable number of similar but distinct entries\n  - for example, software packages or APN connections.\n\n- Composite counterparts of **Read** and **Write** operations -\n  **Read-Composite** (CoAP **FETCH**) and **Write-Composite** (CoAP **iPATCH**),\n  which can target many Object Instances and Resources of different Objects.\n  These operations were added in LwM2M 1.1.\n\n- Finally, the **Delete** operation (CoAP **DELETE**) is the reverse of Create,\n  allowing to remove previously created Object Instances.\n\nFollowing operations can be issued by the LwM2M Client:\n\n- A **POST /dp** request represents the **Send** operation. It's used by the\n  Client to send data to the Server without an explicit request, which is in\n  some circumstances a more flexible option compared to the standard Information\n  Reporting Interface described further.\n\nInformation Reporting Interface\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nThis interface can be thought of as an extension to the\n`Device Management and Service Enablement Interface`_, allowing the Server to\nautomatically receive periodic updates about some values in the data model it is\nparticularly interested in. It is based on the\n`OBSERVE extension to CoAP <https://tools.ietf.org/html/rfc7641>`_, applying its\nsemantics mostly unchanged onto the LwM2M mapping of CoAP concepts.\n\n- A *Read* operation (CoAP **GET**), after adding the **Observe option = 0**,\n  becomes **Observe**. Upon receiving such request, in addition to returning the\n  current value, the Client will start sending *Notify* messages when\n  appropriate.\n\n- **Cancel Observation** command can be issued either by performing a *Read*\n  with **Observe option = 1** or by responding to the *Notify* message with a\n  **CoAP RESET**.\n\n- Composite counterparts of **Observe** and **Cancel Observation** operations -\n  **Observe-Composite** (CoAP **FETCH** with **Observe option = 0**) and\n  **Cancel Observation-Composite** (CoAP **FETCH** with **Observe option = 1**),\n  which can target many entities at once.\n\n- **Notify** is an **asynchronous CoAP response** as described in\n  `RFC 7641 <https://tools.ietf.org/html/rfc7641>`_. It is essentially a\n  repeated reply to a *Read*, sent whenever the observed value changes, and/or\n  periodically, according to relevant Attributes_.\n\n  It may be sent as a Non-confirmable or as a Confirmable message at discretion\n  of the Client. Anjay currently sends almost all notifications as\n  Non-confirmable messages; Confirmable notifications are sent once every 24\n  hours, to comply with\n  `RFC 7641 section 4.5 <https://tools.ietf.org/html/rfc7641#page-18>`_.\n\nQueue mode\n----------\n\nThe *Register* command includes a \"Queue Mode\" parameter which indicates if\nthe client device is running in Queue Mode. It's a special mode of operation\nin which the client device is not required to actively listen for incoming\npackets. The client is only required to listen for such packets for a limited\nperiod of time after each exchange of messages with the Server - typically\nafter the *Update* command.\n\nThe specification recommends to use CoAP's ``MAX_TRANSMIT_WAIT`` value (93\nseconds by default) as that aforementioned limited period of time, and this\nrecommendation is respected in Anjay.\n\nAnjay automatically handles the queue mode by hiding connections which are not\nrequired to actively listen from the library user. In particular, if the\n``anjay_get_sockets()`` function returns an empty list, it likely means that all\nactive connections are in queue mode and the listening period has passed. In\nthat case, it is safe to passively sleep for the period returned by\n``anjay_sched_time_to_next()`` (or one of its convenience wrappers).\n\nIn LwM2M 1.0 the use of Queue Mode was handled by Binding parameter, included\nin *Register* and *Update* commands.\n\nTrigger mode\n------------\n\nSMS messages can be used not only just a sole communication method for\nLwM2M-enabled devices, but they can be also used in coordination with any other\nbinding to wake the device up from sleep in Queue Mode and send the Update\nmessage to the Server. This mode of operation is called \"Trigger mode\".\n\nTo wake the device up, the LwM2M Server sends the Execute operation on ``1/x/8``\nResource (*Registration Update Trigger*), expecting a response to that message\non the other communication channel, like TCP or UDP.\n\nYou can find more information about Trigger mode in :ref:`SMS Binding feature\ndescription <anjay-sms-trigger-mode>`.\n\nIn LwM2M 1.0, similar mode of operation could be achieved with use of \"UQS\"\nBinding Mode.\n"
  },
  {
    "path": "doc/sphinx/source/LwM2MGateway/LwM2MGatewayAPI.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nLwM2M Gateway API\n=================\n\n.. contents:: :local:\n\nLwM2M Gateway object\n--------------------\n\nBefore using the LwM2M Gateway object, we need to inform Anjay of its existence.\nAnjay includes a built-in implementation of the LwM2M Gateway object\n`LwM2M Gateway <https://github.com/OpenMobileAlliance/lwm2m-registry/blob/prod/25.xml>`_\n(``/25``), which can be easily utilized.\n\n.. note::\n   Anjay implements version 2.0 of Object ``/25`` containing all three\n   mandatory resources: ``Device ID``, ``Prefix``, and ``IoT Device Object``.\n\nOnce the Anjay object is created, the Gateway object can be installed using\nthe ``anjay_lwm2m_gateway_install()`` function.\n\n.. note::\n   Complete code of this example can be found in\n   ``examples/tutorial/LwM2M-Gateway`` subdirectory of the main\n   Anjay project repository.\n\n.. highlight:: c\n.. snippet-source:: examples/tutorial/LwM2M-Gateway/src/main.c\n    :emphasize-lines: 12\n\n    anjay_t *anjay = anjay_new(&CONFIG);\n    if (!anjay) {\n        avs_log(tutorial, ERROR, \"Could not create Anjay object\");\n        return -1;\n    }\n\n    int result = 0;\n    if (setup_security_object(anjay) || setup_server_object(anjay)) {\n        result = -1;\n    }\n\n    if (!result && anjay_lwm2m_gateway_install(anjay)) {\n        avs_log(tutorial, ERROR, \"Failed to add /25 Gateway Object\");\n        result = -1;\n    }\n\n.. _lwm2m_gateway_register_device:\n\nManaging End Devices\n--------------------\n\nManaging a new End Device involves two key steps:\n\n1. **Establishing communication** between the Gateway and the End Device.\n2. **Registering the device and its objects** in the LwM2M Gateway.\n\nCommunication with End Devices\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nSince different End Devices may have unique requirements, the communication\nmethod varies. The example provided in the next section includes a Python script\n``examples/tutorial/LwM2M-Gateway/end_device.py``. This script\ncommunicates with the Anjay example code using **UNIX sockets**. However, it is\nthe user’s responsibility to implement the appropriate communication mechanism\nfor their specific End Devices.\n\n.. note::\n    The Python script can be started multiple times, and each instance of the\n    script will automatically connect to the Gateway and simulate a\n    single End Device. Stopping the execution of the script will dynamically\n    disconnect the associated End Device from the LwM2M Gateway.\n\nRegistering an End Device\n^^^^^^^^^^^^^^^^^^^^^^^^^\n\nWhen a new End Device sends an **attach request**, the LwM2M Gateway must\nregister it by adding a new instance of the ``/25`` Gateway Object. This is\ndone using the ``anjay_lwm2m_gateway_register_device()`` function.\n\n.. highlight:: c\n.. snippet-source:: examples/tutorial/LwM2M-Gateway/src/gateway_server.c\n    :emphasize-lines: 8-9\n\n    static int setup_end_device(gateway_srv_t *gateway_srv,\n                                end_device_t *end_device,\n                                const char *msg) {\n        anjay_iid_t iid = ANJAY_ID_INVALID;\n        anjay_t *anjay = gateway_srv->anjay;\n\n        strcpy(end_device->end_device_name, msg);\n        if (anjay_lwm2m_gateway_register_device(anjay, end_device->end_device_name,\n                                                &iid)) {\n            avs_log(tutorial, ERROR, \"Failed to add End Device\");\n            return -1;\n        }\n        end_device->iid = iid;\n        end_device->evaluation_period = DEFAULT_MAXIMAL_EVALUATION_PERIOD;\n\nInstance ID (iid) management\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nYou can specify an **Instance ID (iid)** manually or set it to\n``ANJAY_ID_INVALID`` to let Anjay assign the first available ID automatically.  \nAlways check the return code of ``anjay_lwm2m_gateway_register_device()``,\nespecially when assigning ``iid`` manually. If a collision occurs, the function\nwill return a **negative value** without modifying the passed ``iid``.\n\n.. important::\n    The ``device_id`` parameter passed to ``anjay_lwm2m_gateway_register_device()``\n    must remain **valid until the device is deregistered**. The value is not\n    copied internally.\n\nRegistering LwM2M objects for an End Device\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nOnce an End Device is registered, its supported LwM2M objects must also be\nregistered. In the provided example, only one object: **Temperature Object**\n(``/3303``) (`LwM2M Temperature Object Specification  \n<https://github.com/OpenMobileAlliance/lwm2m-registry/blob/prod/3303.xml>`_) is\nregistered.\n\nTo register an object, use the ``anjay_lwm2m_gateway_register_object()``  \nfunction. The object implementation should handle communication with the End  \nDevice, but otherwise follows the same pattern as ``anjay_register_object()``.\n\n.. highlight:: c\n.. snippet-source:: examples/tutorial/LwM2M-Gateway/src/gateway_server.c\n    :emphasize-lines: 9\n\n    const anjay_dm_object_def_t **obj =\n            temperature_object_create(iid, gateway_srv);\n    if (!obj) {\n        avs_log(tutorial, ERROR, \"Failed to create Temperature Object\");\n        return -1;\n    }\n    end_device->temperature_object = obj;\n\n    if (anjay_lwm2m_gateway_register_object(anjay, iid, obj)) {\n        avs_log(tutorial, ERROR, \"Failed to register Temperature Object\");\n        return -1;\n    }\n\n.. note::\n    The Temperature Object implementation  \n    (``examples/tutorial/LwM2M-Gateway/src/temperature_object.c``)\n    interacts with the Python script simulating an End Device to perform actual\n    read and write operations.\n\n.. note::\n    The Anjay LwM2M Gateway **automatically assigns Prefixes** (``/25/*/1``).  \n    However, when using the LwM2M Gateway API, users should rely on the integer\n    **End Device Instance ID** returned by ``anjay_lwm2m_gateway_register_device()``.  \n    It is recommended to **store the Instance ID** in a structure representing\n    the object registered on the End Device. This makes it easier to match\n    the correct End Device when using shared object implementations.\n\nCleaning up\n-----------\n\nWhen a device disconnects, the user should perform two actions:\n\n- Unregister all of the objects of a given End Device using  \n  ``anjay_lwm2m_gateway_unregister_object()``\n- Deregister the End Device with ``anjay_lwm2m_gateway_deregister_device()``\n\n.. highlight:: c\n.. snippet-source:: examples/tutorial/LwM2M-Gateway/src/gateway_server.c\n    :emphasize-lines: 2-3,8\n\n    if (end_device->temperature_object) {\n        if (anjay_lwm2m_gateway_unregister_object(\n                    anjay, end_device->iid, end_device->temperature_object)) {\n            avs_log(tutorial, ERROR, \"Failed to unregister Temperature Object\");\n        }\n        temperature_object_release(end_device->temperature_object);\n    }\n    if (anjay_lwm2m_gateway_deregister_device(anjay, end_device->iid)) {\n        avs_log(tutorial, ERROR, \"Failed to deregister End Device\");\n    }\n"
  },
  {
    "path": "doc/sphinx/source/LwM2MGateway/LwM2MGatewayIntro.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nLwM2M Gateway Introduction\n==========================\n\n.. contents:: :local:\n\nOverview\n--------\n\nWhy the LwM2M gateway was created\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nAs the IoT continues to grow, so does the need for managing a wide range of\nconnected devices. Many of these devices operate in constrained network\nenvironments or have limited processing power. Some IoT devices do not natively\nsupport the LwM2M protocol.\n\nTo address this challenge, the Open Mobile Alliance (OMA) introduced the `(/25)\nLwM2M Gateway Object <https://www.openmobilealliance.org/release/LwM2M_Gateway/\nV1_1_1-20240312-A/OMA-TS-LWM2M_Gateway-V1_1_1-20240312-A.pdf>`_. This extension\nto the LwM2M specification allows an LwM2M Server to manage IoT End Devices\nthrough an intermediary **LwM2M Gateway** in a standardized and LwM2M-compliant\nway.\n\nHow the LwM2M Gateway works\n^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nThe **LwM2M Gateway** acts as a bridge between IoT End Devices and the LwM2M\nServer. It translates the data models of connected End Devices and presents\nthem to the Server, ensuring seamless interoperability. This eliminates the\nneed for native LwM2M support on End Devices while still allowing robust device\nmanagement, monitoring, and control.\n\nThe Gateway allows for communication with IoT End Devices over various\ninterfaces, for example **Bluetooth Low Energy (BLE)**, **Wi-Fi**, or **Serial\nprotocols** such as UART and RS-485.\n\nEach End Device is identified by the LwM2M Server using the **Device Identifier\nResource**, which is stored in separate ``/25`` **LwM2M Gateway Object**\ninstances. The Server accesses these devices using a **prefix in the URI path**.\n\nLwM2M Gateway in Anjay\n^^^^^^^^^^^^^^^^^^^^^^\n\nThe **LwM2M Gateway** in Anjay provides a scalable and efficient solution for\nmanaging End Devices that lack the hardware capabilities for a full LwM2M\ncommunication stack. By leveraging the standardized Gateway Object (``/25``),\nthe Gateway enables communication between an LwM2M Server and connected\ndevices.\n\n.. note::\n    In accordance with the specification, each connected device must be\n    **registered separately** in the Anjay client by the Gateway Device\n    application.\n\nThis design simplifies the integration of cost-effective, resource-constrained\ndevices into IoT ecosystems while maintaining compliance with LwM2M\nspecifications.\n\nPurpose and benefits\n--------------------\n\nThe **LwM2M Gateway** helps overcome key challenges in IoT deployments by\nproviding an efficient, scalable, and secure way to manage connected devices.\n\n**Key Benefits:**\n\n- **Optimized Resource Use and Cost Savings**\n\n  The Gateway offloads internet communication and protocol handling from End\n  Devices, reducing their hardware requirements and enabling the use of\n  simpler, cost-effective devices.\n\n- **Seamless Communication & Interoperability**\n\n  By leveraging the LwM2M protocol, the Gateway ensures standardized\n  interaction with End Devices, simplifying integration across different IoT\n  ecosystems.\n\n- **Scalability for Diverse Applications**\n\n  The Gateway supports various device types and data models, making it\n  suitable for industries such as Smart metering, Asset tracking and\n  Environmental monitoring.\n\n- **Support for Legacy Devices**\n\n  Devices that were not originally designed for LwM2M can still be managed and\n  integrated through the Gateway. This extends their lifecycle and improves\n  operational efficiency without requiring major hardware modifications.\n\n- **Local Control & Edge Computing**\n\n  Applications running on the Gateway can process data locally and manage\n  connected devices, even during network outages, ensuring continuous\n  operation.\n\n- **Enhanced Security**\n\n  - The connection between End Devices and the Gateway (southbound) is often\n    within a trusted local zone.\n  - Many constrained devices lack built-in security features. The Gateway\n    ensures that all northbound communication (to the LwM2M Server over a\n    public network) remains secure.\n\nFeatures and limitations\n------------------------\n\nSupported LwM2M operations\n^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nThe LwM2M Gateway supports the following operations on End Devices:\n\n- **Read**\n- **Write**\n- **Execute**\n- **Discover**\n- **Write-Attributes**\n- **Observe/Notify**\n- **Cancel Observe**\n- **Send**\n\nLwM2M Gateway limitations\n^^^^^^^^^^^^^^^^^^^^^^^^^\n\nThe LwM2M Gateway simplifies the management of non-LwM2M devices but does not\nperform real-time protocol translation. Instead, it abstracts data models and\nprovides a standardized interface for device management.\n\nWhile the Gateway handles communication between End Devices and the LwM2M\nServer, **developers are responsible for implementing specific drivers and\nobject definitions** for their End Devices.\n\nUnsupported features\n^^^^^^^^^^^^^^^^^^^^\n- **Composite operations** targeting End Devices are not supported.\n- ``/25`` **Gateway Object instances** and **End Device Data Models** are not\n  included in the LwM2M Client Bootstrap Information.\n- The **Firmware Update (FOTA) mechanism**, as described in the LwM2M\n  specification and implemented in the ``/5`` **Firmware Object**, is currently\n  not supported.\n\nEnabling LwM2M Gateway support\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nLwM2M Gateway functionality can be enabled at compile-time by enabling the\n``ANJAY_WITH_LWM2M_GATEWAY`` macro in the ``anjay_config.h`` file or, if using\nCMake, by enabling the corresponding ``WITH_LWM2M_GATEWAY`` CMake option.\n"
  },
  {
    "path": "doc/sphinx/source/LwM2MGateway/LwM2MGatewayNotifications.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nObserve and Notify support for LwM2M Gateway EIDs\n=================================================\n\n.. contents:: :local:\n\nOverview\n--------\n\nLwM2M **Observe/Notify** operations allow a server to receive updates on\nresource values whenever they change or meet specific conditions. In **Anjay**,\nit is the user's responsibility to keep resource states up to date for\nobservations and to notify the library about state changes not triggered by\nWrite or Create operations.\n\nTo ensure that the LwM2M client meets the observation attributes configured\nby the server, resource states must be updated frequently enough.\n\n.. note::\n   If you're unfamiliar with the **LwM2M Observe/Notify** mechanism and its\n   use in Anjay for standard LwM2M clients, read the  \n   :doc:`../../BasicClient/BC-Notifications` tutorial first.\n\nHow Observe/Notify works\n^^^^^^^^^^^^^^^^^^^^^^^^\n\nIn most implementations, fulfilling this requirement is straightforward:\n\n- The user **notifies the library** whenever a resource value changes\n  (e.g., on every update or at a high frequency).\n- The library then decides whether to **send a notification** to the server\n  based on the configured observation attributes.\n- The user does **not** need to explicitly handle observations or attributes\n  in their code.\n\nObserve/Notify for LwM2M Gateway EIDs\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nThe observation mechanism works the same way for **LwM2M Gateway EIDs** as it\ndoes for regular LwM2M devices. However, three additional APIs are provided:\n\n- ``anjay_lwm2m_gateway_notify_changed``\n- ``anjay_lwm2m_gateway_notify_instances_changed``\n- ``anjay_lwm2m_gateway_resource_observation_status``\n\nThese functions work similarly to their standard LwM2M client counterparts but\nrequire an additional parameter: **the EID**. The EID is an integer ID assigned\nwhen calling ``anjay_lwm2m_gateway_register_device()``,\n:ref:`as shown here <lwm2m_gateway_register_device>`.\n\n.. note::\n   If an **EID is deregistered**, all active observations on its resources\n   are **automatically canceled**.\n   As per `RFC 7641 (CoAP Observe), section 4.2\n   <https://www.rfc-editor.org/rfc/rfc7641.html#section-4.2>`_, the client\n   will respond with **4.04 Not Found** for any observations on a removed EID.\n\nOptimizing notifications for EIDs\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nNotifying the library for every resource change is impractical for EIDs.  \nContinuously sending updates would cause excessive communication overhead,\nwhich is undesirable for low-power, battery-operated devices.\n\nThe following example outlines an optimized approach for handling observations,\nspecifically for **temperature measurements** using the **Temperature Object**.\n\nThe optimization includes:\n\n- **A caching mechanism** to store recently read resource values, reducing\n  unnecessary communication with EIDs.\n- **Intelligent update logic** that:\n  - Limits communication when resources are not observed.\n  - Adjusts update frequency based on the ``epmax`` (Maximum Evaluation\n  Period) observation attribute.\n\nThis approach ensures efficient resource monitoring while minimizing power\nand communication costs.\n\nExample\n-------\n\n.. note::\n   Complete code of this example can be found in\n   ``examples/tutorial/LwM2M-Gateway`` subdirectory of the\n   main Anjay project repository.\n\nCache mechanism\n^^^^^^^^^^^^^^^\n\nIn Anjay, it is transparent to the user whether a resource read occurs due to:\n\n1. A **Read operation** from the LwM2M Server.\n2. A **notification requirement** triggered by the library.\n\nThe ``resource_read`` callback must return a recent enough value to be useful.  \nA basic approach would be to immediately query the EID for the latest value,\nbut this can cause unnecessary communication overhead.\n\nTo prevent redundant reads, the gateway should periodically read resource values\nand notify Anjay of changes using ``anjay_lwm2m_gateway_notify_changed``.  \nHowever, calling this API causes the library to **read the resource again**,\npotentially leading to **duplicate queries**.\n\n**Optimizing with a cache**\n\nA caching mechanism can be implemented to store recently read resource values,\nreducing unnecessary queries to the EID:\n\n- If a resource read is triggered **twice in a short period** (first to check\n  if the value has changed, then to fetch it), the cache prevents **multiple\n  EID queries**.\n- The cache stores each resource value along with a **timestamp** that\n  determines its validity.\n\nCache data has the following structure:\n\n.. highlight:: c\n.. snippet-source:: examples/tutorial/LwM2M-Gateway/src/temperature_object.c\n    :emphasize-lines: 1-4, 12-14\n\n    typedef struct cached_value_struct {\n        double value;\n        avs_time_monotonic_t timestamp;\n    } cached_value_t;\n\n    typedef struct temperature_instance_struct {\n        anjay_iid_t iid;\n\n        char application_type[10];\n        char application_type_backup[10];\n\n        cached_value_t max_meas_cached_value;\n        cached_value_t min_meas_cached_value;\n        cached_value_t sensor_meas_cached_value;\n    } temperature_instance_t;\n\n**Fetching cached values efficiently**\n\nAll resource accesses are handled through the cache, using the following\nfunction:\n\n.. highlight:: c\n.. snippet-source:: examples/tutorial/LwM2M-Gateway/src/temperature_object.c\n    :emphasize-lines: 16-18, 22-26\n\n    static int get_eid_resource_value(temperature_object_t *obj,\n                                      anjay_rid_t rid,\n                                      cached_value_t *cached_value,\n                                      bool force_update) {\n        avs_time_monotonic_t current_time = avs_time_monotonic_now();\n\n        if (!force_update) {\n            int64_t diff;\n            if (avs_time_duration_to_scalar(\n                        &diff, AVS_TIME_S,\n                        avs_time_monotonic_diff(current_time,\n                                                cached_value->timestamp))) {\n                return ANJAY_ERR_INTERNAL;\n            }\n\n            if (diff < CACHE_VALID_PERIOD_S) {\n                return 0;\n            }\n        }\n        int res;\n        char buffer[VALUE_MESSAGE_MAX_LEN];\n        if ((res = gateway_request(obj->gateway_srv, obj->end_device_iid,\n                                   rid_to_request_type(rid), buffer,\n                                   VALUE_MESSAGE_MAX_LEN))) {\n            return res;\n        }\n\n        cached_value->value = atof(buffer);\n        cached_value->timestamp = current_time;\n        return 0;\n    }\n\nThis function first checks if the cached value is still valid. If so, it\nreturns the cached value. Otherwise, it queries the EID, updates the cache,\nand returns the fresh value. The cache validity period is defined by the\n``CACHE_VALID_PERIOD_S`` constant.\n\n**Using the cache in refresh mechanism**\n\nThis caching mechanism is used in both the ``resource_read`` callback and in\nthe function that periodically refreshes observed resource values:\n\n.. highlight:: c\n.. snippet-source:: examples/tutorial/LwM2M-Gateway/src/temperature_object.c\n    :emphasize-lines: 20\n\n    static int resource_read(anjay_t *anjay,\n                             const anjay_dm_object_def_t *const *obj_ptr,\n                             anjay_iid_t iid,\n                             anjay_rid_t rid,\n                             anjay_riid_t riid,\n                             anjay_output_ctx_t *ctx) {\n        (void) anjay;\n        assert(riid == ANJAY_ID_INVALID);\n\n        temperature_object_t *obj = get_obj(obj_ptr);\n        assert(iid < AVS_ARRAY_SIZE(obj->instances));\n        temperature_instance_t *inst = &obj->instances[iid];\n        int res;\n\n        switch (rid) {\n        case RID_MIN_MEASURED_VALUE:\n        case RID_MAX_MEASURED_VALUE:\n        case RID_SENSOR_VALUE: {\n            cached_value_t *cached_value = rid_to_cached_value(inst, rid);\n            res = get_eid_resource_value(obj, rid, cached_value, false);\n            return res ? ANJAY_ERR_INTERNAL\n                       : anjay_ret_double(ctx, cached_value->value);\n        }\n        case RID_APPLICATION_TYPE:\n            return anjay_ret_string(ctx, inst->application_type);\n\n        default:\n            return ANJAY_ERR_METHOD_NOT_ALLOWED;\n        }\n    }\n\nResource refresh mechanism\n^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nThe function that refreshes resource values and calls\n``anjay_lwm2m_gateway_notify_changed`` follows a structure similar to the\napproach described in the :doc:`../../BasicClient/BC-Notifications` tutorial:\n\n.. highlight:: c\n.. snippet-source:: examples/tutorial/LwM2M-Gateway/src/gateway_server.c\n\n    static void notify_job(avs_sched_t *sched, const void *args_ptr) {\n        const job_args_t *args = (const job_args_t *) args_ptr;\n\n        temperature_object_update_value(args->anjay,\n                                        args->end_device->temperature_object);\n\n        AVS_SCHED_DELAYED(sched, &args->end_device->notify_job_handle,\n                          avs_time_duration_from_scalar(\n                                  args->end_device->evaluation_period, AVS_TIME_S),\n                          notify_job, args, sizeof(*args));\n    }\n\n**Updating observed resource values**\n\nThe ``temperature_object_update_value`` function is responsible for updating the\nresource values and notifying the library about the changes. Notice that this\nfunction doesn't refresh values of resources that are not observed:\n\n.. highlight:: c\n.. snippet-source:: examples/tutorial/LwM2M-Gateway/src/temperature_object.c\n    :emphasize-lines: 5-10\n\n    static void update_resource(anjay_t *anjay,\n                                temperature_object_t *obj,\n                                temperature_instance_t *inst,\n                                anjay_rid_t rid) {\n        anjay_resource_observation_status_t status =\n                anjay_lwm2m_gateway_resource_observation_status(anjay,\n                                                                obj->end_device_iid,\n                                                                OID_TEMPERATURE,\n                                                                inst->iid, rid);\n        if (status.is_observed) {\n            cached_value_t *cached_value = rid_to_cached_value(inst, rid);\n            double prev_value = cached_value->value;\n            get_eid_resource_value(obj, rid, cached_value, true);\n\n            if (prev_value != cached_value->value) {\n                anjay_lwm2m_gateway_notify_changed(anjay, obj->end_device_iid,\n                                                   OID_TEMPERATURE, inst->iid, rid);\n            }\n        }\n    }\n\n    // ...\n\n    void temperature_object_update_value(anjay_t *anjay,\n                                         const anjay_dm_object_def_t **def) {\n        assert(anjay);\n        temperature_object_t *obj = get_obj(def);\n\n        for (size_t iid = 0; iid < AVS_ARRAY_SIZE(obj->instances); iid++) {\n            update_resource(anjay, obj, &obj->instances[iid],\n                            RID_MIN_MEASURED_VALUE);\n            update_resource(anjay, obj, &obj->instances[iid],\n                            RID_MAX_MEASURED_VALUE);\n            update_resource(anjay, obj, &obj->instances[iid], RID_SENSOR_VALUE);\n        }\n    }\n\n**Dynamic adjustment based on epmax**\n\nThe periodic refresh of resource values for observed resources is dynamically\nadjusted based on the ``epmax`` observation attribute. This attribute defines\nthe maximum interval between evaluations that determine whether a notification\nshould be sent.\n\nThe mechanism is implemented through a scheduled job that regularly queries\nAnjay for a list of currently observed resources. If any of the resources in\nthis object is observed, the job responsible for refreshing resource values is\nrescheduled to run at a higher frequency. Specifically, the interval is set to\nthe lowest ``epmax`` value among all observed resources.\n\n.. highlight:: c\n.. snippet-source:: examples/tutorial/LwM2M-Gateway/src/gateway_server.c\n    :emphasize-lines: 15-18\n\n    static void calculate_evaluation_period_job(avs_sched_t *sched,\n                                                const void *args_ptr) {\n        const job_args_t *args = (const job_args_t *) args_ptr;\n\n        // Schedule run of the same function to track the evaluation period\n        // continuously\n        AVS_SCHED_DELAYED(sched, &args->end_device->evaluation_period_job_handle,\n                          avs_time_duration_from_scalar(EVALUATION_CALC_JOB_PERIOD,\n                                                        AVS_TIME_S),\n                          calculate_evaluation_period_job, args, sizeof(*args));\n\n        int32_t prev_evaluation_period = args->end_device->evaluation_period;\n        int32_t new_evaluation_period = DEFAULT_MAXIMAL_EVALUATION_PERIOD;\n\n        temperature_object_evaluation_period_update_value(\n                args->anjay,\n                args->end_device->temperature_object,\n                &new_evaluation_period);\n        if (new_evaluation_period == prev_evaluation_period) {\n            return;\n        }\n        args->end_device->evaluation_period = new_evaluation_period;\n\n        // if evaluation period has changed, notify job should be rescheduled\n        // accordingly to new period\n        avs_time_monotonic_t new_notify_instant = avs_time_monotonic_add(\n                avs_time_monotonic_add(\n                        avs_sched_time(&args->end_device->notify_job_handle),\n                        avs_time_duration_from_scalar(-prev_evaluation_period,\n                                                      AVS_TIME_S)),\n                avs_time_duration_from_scalar(new_evaluation_period, AVS_TIME_S));\n        AVS_RESCHED_AT(&args->end_device->notify_job_handle, new_notify_instant);\n    }\n\nThe ``temperature_object_evaluation_period_update_value`` function is\nresponsible for finding the lowest ``epmax`` value for all observed resources\nthat depend on the temperature measured by an EID:\n\n.. highlight:: c\n.. snippet-source:: examples/tutorial/LwM2M-Gateway/src/temperature_object.c\n\n    static void evaluation_period_update_value(anjay_t *anjay,\n                                               temperature_object_t *obj,\n                                               temperature_instance_t *inst,\n                                               anjay_rid_t rid,\n                                               int32_t *max_evaluation_period) {\n        anjay_resource_observation_status_t status =\n                anjay_lwm2m_gateway_resource_observation_status(anjay,\n                                                                obj->end_device_iid,\n                                                                OID_TEMPERATURE,\n                                                                inst->iid, rid);\n\n        if (status.is_observed && status.max_eval_period != ANJAY_ATTRIB_PERIOD_NONE\n                && *max_evaluation_period > status.max_eval_period) {\n            *max_evaluation_period = status.max_eval_period;\n        }\n    }\n\n    void temperature_object_evaluation_period_update_value(\n            anjay_t *anjay,\n            const anjay_dm_object_def_t **def,\n            int32_t *evaluation_period) {\n        assert(anjay);\n        temperature_object_t *obj = get_obj(def);\n\n        for (size_t iid = 0; iid < AVS_ARRAY_SIZE(obj->instances); iid++) {\n            evaluation_period_update_value(anjay, obj, &obj->instances[iid],\n                                           RID_MIN_MEASURED_VALUE,\n                                           evaluation_period);\n            evaluation_period_update_value(anjay, obj, &obj->instances[iid],\n                                           RID_MAX_MEASURED_VALUE,\n                                           evaluation_period);\n            evaluation_period_update_value(anjay, obj, &obj->instances[iid],\n                                           RID_SENSOR_VALUE, evaluation_period);\n        }\n    }\n\nConclusion\n----------\n\nThis approach shows how the LwM2M Gateway can efficiently manage\n**Observe/Notify** operations for EIDs while keeping communication to a minimum.  \nBy optimizing when and how updates are sent, devices can stay responsive\nwithout unnecessary data transfers.\n\nHowever, the best implementation depends on your specific **use case**.  \nSeveral factors should guide your design:\n\n- **Update Frequency** – How often should resource values be refreshed?\n- **Real-Time Accuracy** – How critical is immediate data reporting?\n- **Power Constraints** – Can the End Device handle frequent updates,\n  or does it need to conserve energy?\n\nDevelopers should **fine-tune** their LwM2M Gateway implementation based on\nthese factors. The goal is to strike the right balance between **data freshness,\npower efficiency, and network optimization**.\n"
  },
  {
    "path": "doc/sphinx/source/LwM2MGateway/LwM2MGatewaySend.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nSend method for LwM2M Gateway EIDs\n==================================\n\n.. contents:: :local:\n\nOverview\n--------\n\nThe LwM2M Gateway specification supports the **LwM2M Send method**, allowing\nefficient data transmission from End IoT Devices (EIDs). Anjay fully supports\nthis feature, and it works similarly to the API used for standard LwM2M devices.\n\nWhy use the LwM2M Send method?\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n- **Batch Data Transmission** – You can send multiple resource values at once\n  without needing Observations.\n- **Multi-Device Support** – A single Send message can include data from\n  multiple EIDs, reducing communication overhead.\n- **Gateway & EID Data Combination** – You can send data from both the LwM2M\n  Gateway (Client) and its connected EIDs in one message.\n\n.. note::\n   If you're new to the **LwM2M Send method** and how it works in Anjay for\n   standard LwM2M clients, read the :doc:`../../BasicClient/BC-Send` tutorial\n   first.\n\nLwM2M Send for EIDs vs. standard Devices\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nThe main difference when using the Send method for EIDs is that the family of\n``anjay_send_batch_[data_]add_*`` functions require an extra parameter—the\n**EID**. This **EID parameter** is an integer ID assigned when calling\n``anjay_lwm2m_gateway_register_device()``,\n:ref:`as shown here <lwm2m_gateway_register_device>`.\n\nSend method API comparison\n^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nBelow is a side-by-side comparison of the original **Send API** and its\ncounterpart for **LwM2M Gateway EIDs**:\n\n.. list-table::\n   :header-rows: 1\n\n   * - Standard Send API\n     - Send API for LwM2M Gateway EIDs\n   * - ``anjay_send_batch_add_int()``\n     - ``anjay_lwm2m_gateway_send_batch_add_int()``\n   * - ``anjay_send_batch_add_uint()``\n     - ``anjay_lwm2m_gateway_send_batch_add_uint()``\n   * - ``anjay_send_batch_add_double()``\n     - ``anjay_lwm2m_gateway_send_batch_add_double()``\n   * - ``anjay_send_batch_add_bool()``\n     - ``anjay_lwm2m_gateway_send_batch_add_bool()``\n   * - ``anjay_send_batch_add_string()``\n     - ``anjay_lwm2m_gateway_send_batch_add_string()``\n   * - ``anjay_send_batch_add_bytes()``\n     - ``anjay_lwm2m_gateway_send_batch_add_bytes()``\n   * - ``anjay_send_batch_add_objlnk()``\n     - ``anjay_lwm2m_gateway_send_batch_add_objlnk()``\n   * - ``anjay_send_batch_data_add_current()``\n     - ``anjay_lwm2m_gateway_send_batch_data_add_current()``\n   * - ``anjay_send_batch_data_add_current_multiple()``\n     - ``anjay_lwm2m_gateway_send_batch_data_add_current_multiple()``\n   * - :small-literal:`anjay_send_batch_data_add_current_multiple_ignore_not_found()`\n     - :small-literal:`anjay_lwm2m_gateway_send_batch_data_add_current_multiple_ignore_not_found()`\n\nThese APIs are meant to be used with other standard APIs for Send method, such as:\n\n- ``anjay_send_batch_builder_new()`` – To build batch messages.\n- ``anjay_send()`` – To transmit data efficiently.\n\nExample\n-------\n\n.. note::\n   Complete code of this example can be found in\n   ``examples/tutorial/LwM2M-Gateway`` subdirectory of the main\n   Anjay project repository.\n\n   For specifics on how this example works, refer to the\n   :doc:`LwM2MGatewayAPI` article.\n\nHow it works\n^^^^^^^^^^^^\n\nThis example periodically sends the temperature measured by all EIDs.\n\nThe function ``temperature_object_send()`` (defined in ``temperature_object.c``):\n\n- **Creates a new batch** for collecting data.\n- **Iterates through the list of EIDs**, fetching their temperature values.\n- **Compiles the batch** and schedules it for transmission.\n\n.. highlight:: c\n.. snippet-source:: examples/tutorial/LwM2M-Gateway/src/temperature_object.c\n    :emphasize-lines: 2, 21-33\n\n    void temperature_object_send(anjay_t *anjay,\n                                 AVS_LIST(end_device_t) end_devices) {\n        if (!anjay) {\n            return;\n        }\n        const anjay_ssid_t server_ssid = 1;\n\n        if (!end_devices) {\n            avs_log(temperature_object, TRACE,\n                    \"No end devices found, skipping sending data\");\n            return;\n        }\n\n        // Allocate new batch builder.\n        anjay_send_batch_builder_t *builder = anjay_send_batch_builder_new();\n        if (!builder) {\n            avs_log(temperature_object, ERROR, \"Failed to allocate batch builder\");\n            return;\n        }\n\n        AVS_LIST(end_device_t) it;\n        AVS_LIST_FOREACH(it, end_devices) {\n            // Add current values of resource from Temperature Object.\n            temperature_object_t *obj = get_obj(it->temperature_object);\n            temperature_instance_t *inst = &obj->instances[0];\n            if (anjay_lwm2m_gateway_send_batch_data_add_current(\n                        builder, anjay, obj->end_device_iid, obj->def->oid,\n                        inst->iid, RID_SENSOR_VALUE)) {\n                anjay_send_batch_builder_cleanup(&builder);\n                avs_log(temperature_object, ERROR, \"Failed to add batch data\");\n                return;\n            }\n        }\n\n        // After adding all values, compile our batch for sending.\n        anjay_send_batch_t *batch = anjay_send_batch_builder_compile(&builder);\n        if (!batch) {\n            anjay_send_batch_builder_cleanup(&builder);\n            avs_log(temperature_object, ERROR, \"Batch compile failed\");\n            return;\n        }\n\n        // Schedule our send to be run on next `anjay_sched_run()` call.\n        int res =\n                anjay_send(anjay, server_ssid, batch, send_finished_handler, NULL);\n        if (res) {\n            avs_log(temperature_object, ERROR, \"Failed to send, result: %d\", res);\n        }\n\n        // After scheduling, we can release our batch.\n        anjay_send_batch_release(&batch);\n    }\n\nDifferences from standard LwM2M Send\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nCompared to the standard **LwM2M Send method** described in the\n:doc:`../../BasicClient/BC-Send` tutorial, this implementation includes:\n\n- **Support for Multiple EIDs** – Instead of sending data from a single device,\n  this function accepts a list of EIDs and retrieves temperature readings from\n  each one.  \n\n- **New API Function with Extra Parameter:** – The function\n  ``anjay_lwm2m_gateway_send_batch_data_add_current()`` is used to support EIDs.\n  The function takes an additional parameter, ``obj->end_device_iid``, which:\n  \n  - Specifies the data source (the correct EID).  \n  - Encodes a proper URI prefix retrieved internally from Gateway module.  \n\nScheduling periodic data transmission\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nTo ensure temperature readings are **sent at regular intervals**, this function\nis called periodically by the scheduler as shown below:\n\n.. highlight:: c\n.. snippet-source:: examples/tutorial/LwM2M-Gateway/src/main.c\n\n    // Periodically issues a Send message with measured values of the temperature\n    static void send_job(avs_sched_t *sched, const void *args_ptr) {\n        gateway_srv_t *gateway_srv = *(gateway_srv_t *const *) args_ptr;\n\n        temperature_object_send(gateway_srv->anjay, gateway_srv->end_devices);\n\n        // Schedule run of the same function after 10 seconds\n        AVS_SCHED_DELAYED(sched, NULL,\n                        avs_time_duration_from_scalar(10, AVS_TIME_S), send_job,\n                        &gateway_srv, sizeof(gateway_srv));\n    }\n\nThis periodic behavior is set up in ``main()``, just before\n``anjay_event_loop_run()`` is called:\n\n.. highlight:: c\n.. snippet-source:: examples/tutorial/LwM2M-Gateway/src/main.c\n    :emphasize-lines: 5-7\n\n    int main(int argc, char *argv[]) {\n        // ...\n\n        if (!result) {\n            // Run send_job the first time;\n            // this will schedule periodic calls to themselves via the scheduler\n            send_job(anjay_get_scheduler(anjay), &gateway_srv_ptr);\n\n            result = anjay_event_loop_run(\n                    anjay, avs_time_duration_from_scalar(1, AVS_TIME_S));\n        }\n\n        // ...\n    }\n"
  },
  {
    "path": "doc/sphinx/source/LwM2MGateway.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nLwM2M Gateway\n=============\n\n.. toctree::\n   :glob:\n   :titlesonly:\n\n   LwM2MGateway/LwM2MGatewayIntro\n   LwM2MGateway/LwM2MGatewayAPI\n   LwM2MGateway/LwM2MGatewayNotifications\n   LwM2MGateway/LwM2MGatewaySend\n"
  },
  {
    "path": "doc/sphinx/source/Migrating/MigratingCustomEntropy.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nMigrating mbed TLS custom entropy initializers\n==============================================\n\n.. highlight:: c\n\nPrevious versions of ``avs_commons`` provided the following mechanism to allow\nadding a custom entropy source at the time of mbed TLS initialization::\n\n    // NOTE: Code compatible with Anjay <=2.2, avs_commons <=4.0\n    // WITH_MBEDTLS_CUSTOM_ENTROPY_INITIALIZER needs to be enabled at compile time\n\n    #include <avsystem/commons/defs.h>\n    #include <mbedtls/entropy.h>\n    #include <mbedtls/entropy_poll.h>\n\n    static int entropy_poll(void *user_arg,\n                            uint8_t *output,\n                            size_t len,\n                            size_t *out_len) {\n        // TODO: Platform-specific entropy collection code\n    }\n\n    // NOTE: For Anjay 1.x / avs_commons 3.x, return type was \"int\"\n    avs_error_t avs_net_mbedtls_entropy_init(mbedtls_entropy_context *entropy) {\n        if (mbedtls_entropy_add_source(entropy, entropy_poll, NULL,\n                                       MBEDTLS_ENTROPY_MIN_PLATFORM,\n                                       MBEDTLS_ENTROPY_SOURCE_STRONG)) {\n            return avs_errno(AVS_UNKNOWN_ERROR);\n        }\n        return AVS_OK;\n    }\n\nThis mechanism has been removed in ``avs_commons`` 4.1, and as such is not\navailable when using Anjay 2.3 or newer.\n\nTo achieve a similar effect in the new version, you can provide your own custom\nPRNG context, for example as follows::\n\n    // Code compatible with Anjay >=2.3, avs_commons >=4.1\n\n    #include <anjay/core.h>\n    #include <avsystem/commons/avs_prng.h>\n    #include <mbedtls/entropy.h>\n    #include <mbedtls/entropy_poll.h>\n\n    static mbedtls_entropy_context g_entropy_context;\n\n    static int entropy_poll(void *user_arg,\n                            uint8_t *output,\n                            size_t len,\n                            size_t *out_len) {\n        // TODO: Platform-specific entropy collection code\n    }\n\n    static int entropy_callback(unsigned char *out_buf,\n                                size_t out_buf_len,\n                                void *dummy_user_arg) {\n        (void) dummy_user_arg;\n        return mbedtls_entropy_func(&g_entropy_context, out_buf, out_buf_len);\n    }\n\n    int main() {\n        // ... before initializing Anjay ...\n        avs_crypto_prng_ctx_t *prng_ctx;\n        mbedtls_entropy_init(&g_entropy_context);\n        if (mbedtls_entropy_add_source(&g_entropy_context, entropy_poll, NULL,\n                                       MBEDTLS_ENTROPY_MIN_PLATFORM,\n                                       MBEDTLS_ENTROPY_SOURCE_STRONG)\n                 || !(prng_ctx = avs_crypto_prng_new(entropy_callback, NULL))) {\n            // TODO: Better error handling\n            return -1;\n        }\n\n        // ... when initializing Anjay ...\n        const anjay_configuration_t anjay_config = {\n            // TODO: Other configuration options\n            .prng_ctx = prng_ctx\n        };\n        anjay_t *anjay = anjay_new(&anjay_config);\n\n        // ... when cleaning up Anjay ...\n        anjay_delete(anjay);\n        avs_crypto_prng_free(&prng_ctx);\n\n        // ...\n        return 0;\n    }\n\nIf you're using ``avs_coap`` contexts and/or raw ``avs_net`` sockets in addition\nto, or instead of Anjay, you will need to pass such custom ``prng_ctx`` object\nwhen initializing those as well. It is generally safe to have multiple objects\nuse the same PRNG context object.\n\n.. note::\n\n    If you don't need the custom mbed TLS entropy logic, it is safe to leave the\n    ``prng_ctx`` field of ``anjay_configuration_t`` as ``NULL``. If you need a\n    PRNG context for other purposes and don't need a custom entropy source, you\n    can also initialize it as ``avs_crypto_prng_new(NULL, NULL)``.\n\n    The mechanism described above is only required or intended for cases when a\n    non-default entropy source is required.\n"
  },
  {
    "path": "doc/sphinx/source/Migrating/MigratingFromAnjay214.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nMigrating from Anjay 2.9.x-2.14.x\n=================================\n\n.. contents:: :local:\n\n.. highlight:: c\n\nIntroduction\n------------\n\nWhile most changes since Anjay 2.14 are minor, some of them (changes to commonly\nused APIs such as Attribute Storage and offline mode control) are breaking.\nThere is a change to the way the ``con`` attribute is handled in the API.\nAdditionally, the upgrade to ``avs_commons`` 5.0 includes refactoring of the\nAPIs related to (D)TLS PSK credentials.\n\nYou may also need to adjust your code if you maintain your own socket\nintegration, or if it accesses the ``avs_net_security_info_t`` structure\ndirectly. The latter is especially likely if you maintain your own\nimplementation of the TLS layer.\n\nChanges in Anjay proper\n-----------------------\n\nRefactor of the Attribute Storage module\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nThe Attribute Storage feature is no longer a standalone module and has been\nmoved to the library core. From the user perspective, this has the following\nconsequences:\n\n* Explicit installation of this module in runtime is no longer necessary. The\n  ``anjay_attr_storage_install()`` method has been removed.\n* The ``ANJAY_WITH_MODULE_ATTR_STORAGE`` configuration macro in\n  ``anjay_config.h`` has been renamed to ``ANJAY_WITH_ATTR_STORAGE``.\n* The ``WITH_MODULE_attr_storage`` CMake option (equivalent to the macro\n  mentioned above) has been renamed to ``WITH_ATTR_STORAGE``.\n\nAdditionally, the behavior of ``anjay_attr_storage_restore()`` has been\nchanged - from now on, this function fails if supplied source stream is\ninvalid and the Attribute Storage remains untouched. This change makes the\nfunction consistent with other ``anjay_*_restore()`` APIs.\n\nRefactor of offline mode control API\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nSince Anjay 2.4, offline mode is configurable independently per every\ntransport. Below is a list of removed functions and counterparts that should\nbe used:\n\n+--------------------------------+------------------------------------------+\n| Removed function               | Counterpart                              |\n+--------------------------------+------------------------------------------+\n| ``anjay_is_offline()``         | ``anjay_transport_is_offline()``         |\n+--------------------------------+------------------------------------------+\n| ``anjay_enter_offline()``      | ``anjay_transport_enter_offline()``      |\n+--------------------------------+------------------------------------------+\n| ``anjay_exit_offline()``       | ``anjay_transport_exit_offline()``       |\n+--------------------------------+------------------------------------------+\n| ``anjay_schedule_reconnect()`` | ``anjay_transport_schedule_reconnect()`` |\n+--------------------------------+------------------------------------------+\n\nNew functions should be called with ``transport_set`` argument set to\n``ANJAY_TRANSPORT_SET_ALL`` to achieve the same behavior.\n\nAddition of the con attribute to public API\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nThe ``con`` attribute, enabled via the ``ANJAY_WITH_CON_ATTR`` compile-time\noption, has been previously supported as a custom extension. Since an identical\nflag has been standardized as part of LwM2M TS 1.2, it has been included in the\npublic API as part of preparations to support the new protocol version.\n\nIf you initialize ``anjay_dm_oi_attributes_t`` or ``anjay_dm_r_attributes_t``\nobjects manually, you may need to initialize the new ``con`` field as well,\nsince the empty ``ANJAY_DM_CON_ATTR_NONE`` value is **NOT** the default\nzero-initialized value.\n\nAs more new attributes may be added in future versions of Anjay, it is\nrecommended to initialize such structures with ``ANJAY_DM_OI_ATTRIBUTES_EMPTY``\nor ``ANJAY_DM_R_ATTRIBUTES_EMPTY`` constants, and then fill in the attributes\nyou actually intend to set.\n\nDefault (D)TLS version\n^^^^^^^^^^^^^^^^^^^^^^\n\nWhen the `anjay_configuration_t::dtls_version\n<../api/structanjay__configuration.html#ab32477e7370a36e02db5b7e7ccbdd89d>`_\nfield is set to ``AVS_NET_SSL_VERSION_DEFAULT`` (which includes the case of\nzero-initialization), Anjay 3.0 and earlier automatically mapped this setting to\n``AVS_NET_SSL_VERSION_TLSv1_2`` to ensure that (D)TLS 1.2 is used as mandated by\nthe LwM2M specification.\n\nThis mapping has been removed in Anjay 3.1, which means that the default version\nconfiguration of the underlying (D)TLS library will be used. This has been done\nto automatically allow the use of newer protocols and deprecate old versions\nwhen the backend library is updated, without the need to update Anjay code.\nHowever, depending on the (D)TLS backend library used, this may lead to (D)TLS\n1.1 or earlier being used if the server does not properly negotiate a higher\nversion. Please explicitly set ``dtls_version`` to\n``AVS_NET_SSL_VERSION_TLSv1_2`` if you want to disallow this.\n\nPlease note that Mbed TLS 3.0 has dropped support for TLS 1.1 and earlier, so\nthis change will not affect behavior with that library.\n\nConditional compilation for structured security credential support\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nThe ``anjay_ret_certificate_chain_info()`` and ``anjay_ret_private_key_info()``\nAPIs, as well as avs_crypto-based fields in ``anjay_security_instance_t``, have\nbeen put under a new conditional compilation flag,\n``ANJAY_WITH_SECURITY_STRUCTURED``.\n\nWhen using CMake, this flag is enabled by default if available. Otherwise, it\nmight need to be enabled by defining ``ANJAY_WITH_SECURITY_STRUCTURED`` in\n``anjay_config.h``.\n\n\n\nAddition of resource_instance_remove handler\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nLwM2M TS 1.2 introduced possibility to delete a Resource Instance, so\none additional data model handler had to be added.\n\nNew ``resource_instance_remove`` handler (and its associated\n``anjay_dm_resource_instance_remove_t`` function type) has been introduced. It\nis analogous to the ``instance_remove`` handler; its job is to remove a specific\nResource Instance from a multiple-instance Resource.\n\nIts implementation is required in objects that include at least one writeable\nmultiple-instance Resource, if the client application aims for compliance with\nLwM2M 1.2.\n\nLimiting the maximum Hold Off Time for Bootstrap\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nA limit has been introduced on the maximum Hold Off Time (LwM2M Security Object,\nResource ID: 11) to avoid excessive delays when initiating the Bootstrap process.\nPreviously, the upper bound was implicitly 120 seconds, but it is now explicitly\nlimited to **20 seconds by default**.\n\nThis behavior can be customized at build time using the ``MAX_HOLDOFF_TIME``\nmacro.\n\nChanges in avs_coap\n-------------------\n\nChanged flow of cancelling observations in case of errors\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nCoAP observations are implicitly cancelled if a notification bearing a 4.xx or\n5.xx error code is delivered.\n\nIn Anjay 3.4.x and earlier, this cancellation (which involves calling the\n``avs_coap_observe_cancel_handler_t`` callback) was performed *before* calling\nthe ``avs_coap_delivery_status_handler_t`` callback for the specific\nnotification. Since Anjay 3.5.0, this order is reversed, so any code that relies\non this logic may break.\n\nThis change is only relevant if you are using ``avs_coap`` APIs directly (e.g.\nwhen communicating over raw CoAP protocol) and in case of notifications intended\nto be delivered as confirmable. The LwM2M Observe/Notify implementation in Anjay\nhas been updated accordingly.\n\nChanges in avs_commons\n----------------------\n\nRenamed configuration macro in avs_commons_config.h\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nThe ``AVS_COMMONS_NET_WITH_PSK`` configuration macro in ``avs_commons_config.h``\nhas been renamed to ``AVS_COMMONS_WITH_AVS_CRYPTO_PSK``.\n\nYou may need to update your configuration files if you are not using CMake, or\nyour preprocessor directives if you check this macro in your code.\n\nIntroduction of new socket option\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\navs_commons 4.10.1 bundled with Anjay 2.15.1 adds a new socket option key:\n``AVS_NET_SOCKET_HAS_BUFFERED_DATA``. This is used to make sure that when\ncontrol is returned to the event loop, the ``poll()`` call will not stall\nwaiting for new data that in reality has been already buffered and could be\nretrieved using the avs_commons APIs.\n\nThis is usually meaningful for (D)TLS connections, but for almost all simple\nunencrypted socket implementations, this should always return ``false``.\n\nThis was previously achieved by always trying to receive more packets with\ntimeout set to zero. However, it has been determined that such logic could lead\nto heavy blocking of the event loop in case communication with the network stack\nis relatively slow, e.g. on devices which implement TCP/IP sockets through modem\nAT commands.\n\nIf you maintain your own socket integration layer or (D)TLS integration layer,\nit is recommended that you add support for this option. This is not, however, a\nbreaking change - if the option is not supported, the library will continue to\nuse the old behavior.\n\nRefactor of PSK credential handling\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n``avs_net_psk_info_t`` structure has been changed to use new types based on\n``avs_crypto_security_info_union_t`` instead of raw buffers. This change also\naffects ``avs_net_security_info_t`` structure which contains the former.\n\n* **Old API:**\n  ::\n\n      /**\n       * A PSK/identity pair with borrowed pointers. avs_commons will never attempt\n       * to modify these values.\n       */\n      typedef struct {\n          const void *psk;\n          size_t psk_size;\n          const void *identity;\n          size_t identity_size;\n      } avs_net_psk_info_t;\n\n      // ...\n\n      typedef struct {\n          avs_net_security_mode_t mode;\n          union {\n              avs_net_psk_info_t psk;\n              avs_net_certificate_info_t cert;\n          } data;\n      } avs_net_security_info_t;\n\n      avs_net_security_info_t avs_net_security_info_from_psk(avs_net_psk_info_t psk);\n\n* **New API:**\n\n  .. snippet-source:: deps/avs_commons/include_public/avsystem/commons/avs_crypto_psk.h\n\n      typedef struct {\n          avs_crypto_security_info_union_t desc;\n      } avs_crypto_psk_identity_info_t;\n\n      // ...\n\n      avs_crypto_psk_identity_info_t\n      avs_crypto_psk_identity_info_from_buffer(const void *buffer,\n                                               size_t buffer_size);\n\n      // ...\n\n      typedef struct {\n          avs_crypto_security_info_union_t desc;\n      } avs_crypto_psk_key_info_t;\n\n      // ...\n\n      avs_crypto_psk_key_info_t\n      avs_crypto_psk_key_info_from_buffer(const void *buffer, size_t buffer_size);\n\n  .. snippet-source:: deps/avs_commons/include_public/avsystem/commons/avs_socket.h\n\n      /**\n       * A PSK/identity pair. avs_commons will never attempt to modify these values.\n       */\n      typedef struct {\n          avs_crypto_psk_key_info_t key;\n          avs_crypto_psk_identity_info_t identity;\n      } avs_net_psk_info_t;\n\n      // ...\n\n      typedef struct {\n          avs_net_security_mode_t mode;\n          union {\n              avs_net_psk_info_t psk;\n              avs_net_certificate_info_t cert;\n          } data;\n      } avs_net_security_info_t;\n\n      avs_net_security_info_t\n      avs_net_security_info_from_psk(avs_net_psk_info_t psk);\n\nThis change is breaking for code that accesses the ``data.psk`` field\nof ``avs_net_security_info_t`` directly.\n\nAdditional function in the hardware security engine API\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nA new API has been added to the hardware security engine API in ``avs_commons``:\n\n.. snippet-source:: deps/avs_commons/include_public/avsystem/commons/avs_crypto_pki.h\n\n    avs_error_t\n    avs_crypto_pki_engine_key_store(const char *query,\n                                    const avs_crypto_private_key_info_t *key_info,\n                                    avs_crypto_prng_ctx_t *prng_ctx);\n\nIf you implement your own hardware security engine backend implementation, you may\nneed to provide an implementation of this function.\n\nThis new API is used by the Security object implementation's features related\nto the ``anjay_security_object_install_with_hsm()``. If you don't use these\nfeatures to store private keys in the hardware security engine, it is OK to\nprovide a dummy implementation such as ``return avs_errno(AVS_ENOTSUP);``.\n\nRefactor of time handling in avs_sched and avs_coap\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nIt is now enforced more strictly that time-based events shall happen when the\nclock reaches *at least* the expected value. Previously, the tasks scheduled via\navs_sched were executed only when the clock reached a value *later* than the\nscheduled job execution time.\n\nThis change will have no impact on your code if your platform has enough clock\nresolution so that two subsequent calls to ``avs_time_real_now()`` or\n``avs_time_monotonic_now()`` will *always* return different values. As a rule of\nthumb, this should be the case if your clock has a resolution no worse than\nabout 1-2 orders of magnitude smaller than the CPU clock. For example, for a\n100 MHz CPU, a clock resolution of around 100-1000 ns (i.e., 1-10 MHz) should be\nsufficient, depending on the specific architecture.\n\nIf your clock has a lower resolution, you may observe the following changes:\n\n* ``anjay_sched_run()`` is now properly guaranteed to execute at least one job\n  if the time reported by ``anjay_sched_time_to_next()`` passed. Previously this\n  could require waiting for another change of the numerical value of the clock,\n  which could cause undesirable active waiting in the event loop. This is the\n  motivating factor in introducing these changes.\n* Jobs scheduled using ``AVS_SCHED_NOW()`` during an execution of\n  ``anjay_sched_run()`` before the numerical value of the clock changes, *will*\n  be executed during the same run. The previous behavior more strictly enforced\n  the policy to not execute such jobs in the same run.\n\nIf you are scheduling custom jobs through the avs_sched module, you may want or\nneed to modify their logic accordingly to accommodate for these changes. In most\ntypical use cases, no changes are expected to be necessary.\n\nRemoval of avs_unit_memstream\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n``avs_unit_memstream`` was a specific implementation of ``avs_stream_t`` within\nthe avs_unit module that implemented a simple FIFO stream in a fixed-size memory\narea.\n\nThis feature has been removed. Instead, you can use an\n``avs_stream_inbuf``/``avs_stream_outbuf`` pair, or an ``avs_stream_membuf``\nobject.\n\nHandling of Trust Store for certain Certificate Usages settings\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\navs_commons 5.5.0 that is used by Anjay 3.11.0 does not pass the configured Trust\nStore (whether manually provided or acquired through the EST process) to Mbed TLS\nwhen the certificate usage is set to DANE-TA or DANE-EE, if the Data Model already\ncontains a Server certificate intended for verifying the Server during a secure\nconnection.\n\nIf the Server certificate is missing from the Data Model, Anjay falls back to\nPKIX verification, provided that a Trust Store is available, even when the\ncertificate usage is set to DANE-TA or DANE-EE.\n\nFor more information on how Anjay manages the Trust Store and the Certificate\nUsage resource, see\n:doc:`Certificate Usage <../AdvancedTopics/AT-CertificateUsage>`.\n\nPython environment isolation\n----------------------------\n\nAll Python-based tools (e.g. integration tests) must be executed within a\nPython virtual environment. See :doc:`/Tools/VirtualEnvironments` for more\ninformation.\n"
  },
  {
    "path": "doc/sphinx/source/Migrating/MigratingFromAnjay215.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nMigrating from Anjay 2.15.x\n===========================\n\n.. contents:: :local:\n\n.. highlight:: c\n\nIntroduction\n------------\n\nWhile most changes since Anjay 2.15 are minor, some of them (changes to commonly\nused APIs such as Attribute Storage and offline mode control) are breaking.\nThere is a change to the way the ``con`` attribute is handled in the API.\nAdditionally, the upgrade to ``avs_commons`` 5.0 includes refactoring of the\nAPIs related to (D)TLS PSK credentials.\n\nChanges in Anjay proper\n-----------------------\n\nRefactor of the Attribute Storage module\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nThe Attribute Storage feature is no longer a standalone module and has been\nmoved to the library core. From the user perspective, this has the following\nconsequences:\n\n* Explicit installation of this module in runtime is no longer necessary. The\n  ``anjay_attr_storage_install()`` method has been removed.\n* The ``ANJAY_WITH_MODULE_ATTR_STORAGE`` configuration macro in\n  ``anjay_config.h`` has been renamed to ``ANJAY_WITH_ATTR_STORAGE``.\n* The ``WITH_MODULE_attr_storage`` CMake option (equivalent to the macro\n  mentioned above) has been renamed to ``WITH_ATTR_STORAGE``.\n\nAdditionally, the behavior of ``anjay_attr_storage_restore()`` has been\nchanged - from now on, this function fails if supplied source stream is\ninvalid and the Attribute Storage remains untouched. This change makes the\nfunction consistent with other ``anjay_*_restore()`` APIs.\n\nRefactor of offline mode control API\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nSince Anjay 2.4, offline mode is configurable independently per every\ntransport. Below is a list of removed functions and counterparts that should\nbe used:\n\n+--------------------------------+------------------------------------------+\n| Removed function               | Counterpart                              |\n+--------------------------------+------------------------------------------+\n| ``anjay_is_offline()``         | ``anjay_transport_is_offline()``         |\n+--------------------------------+------------------------------------------+\n| ``anjay_enter_offline()``      | ``anjay_transport_enter_offline()``      |\n+--------------------------------+------------------------------------------+\n| ``anjay_exit_offline()``       | ``anjay_transport_exit_offline()``       |\n+--------------------------------+------------------------------------------+\n| ``anjay_schedule_reconnect()`` | ``anjay_transport_schedule_reconnect()`` |\n+--------------------------------+------------------------------------------+\n\nNew functions should be called with ``transport_set`` argument set to\n``ANJAY_TRANSPORT_SET_ALL`` to achieve the same behavior.\n\nAddition of the con attribute to public API\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nThe ``con`` attribute, enabled via the ``ANJAY_WITH_CON_ATTR`` compile-time\noption, has been previously supported as a custom extension. Since an identical\nflag has been standardized as part of LwM2M TS 1.2, it has been included in the\npublic API as part of preparations to support the new protocol version.\n\nIf you initialize ``anjay_dm_oi_attributes_t`` or ``anjay_dm_r_attributes_t``\nobjects manually, you may need to initialize the new ``con`` field as well,\nsince the empty ``ANJAY_DM_CON_ATTR_NONE`` value is **NOT** the default\nzero-initialized value.\n\nAs more new attributes may be added in future versions of Anjay, it is\nrecommended to initialize such structures with ``ANJAY_DM_OI_ATTRIBUTES_EMPTY``\nor ``ANJAY_DM_R_ATTRIBUTES_EMPTY`` constants, and then fill in the attributes\nyou actually intend to set.\n\nDefault (D)TLS version\n^^^^^^^^^^^^^^^^^^^^^^\n\nWhen the `anjay_configuration_t::dtls_version\n<../api/structanjay__configuration.html#ab32477e7370a36e02db5b7e7ccbdd89d>`_\nfield is set to ``AVS_NET_SSL_VERSION_DEFAULT`` (which includes the case of\nzero-initialization), Anjay 3.0 and earlier automatically mapped this setting to\n``AVS_NET_SSL_VERSION_TLSv1_2`` to ensure that (D)TLS 1.2 is used as mandated by\nthe LwM2M specification.\n\nThis mapping has been removed in Anjay 3.1, which means that the default version\nconfiguration of the underlying (D)TLS library will be used. This has been done\nto automatically allow the use of newer protocols and deprecate old versions\nwhen the backend library is updated, without the need to update Anjay code.\nHowever, depending on the (D)TLS backend library used, this may lead to (D)TLS\n1.1 or earlier being used if the server does not properly negotiate a higher\nversion. Please explicitly set ``dtls_version`` to\n``AVS_NET_SSL_VERSION_TLSv1_2`` if you want to disallow this.\n\nPlease note that Mbed TLS 3.0 has dropped support for TLS 1.1 and earlier, so\nthis change will not affect behavior with that library.\n\nConditional compilation for structured security credential support\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nThe ``anjay_ret_certificate_chain_info()`` and ``anjay_ret_private_key_info()``\nAPIs, as well as avs_crypto-based fields in ``anjay_security_instance_t``, have\nbeen put under a new conditional compilation flag,\n``ANJAY_WITH_SECURITY_STRUCTURED``.\n\nWhen using CMake, this flag is enabled by default if available. Otherwise, it\nmight need to be enabled by defining ``ANJAY_WITH_SECURITY_STRUCTURED`` in\n``anjay_config.h``.\n\n\nAddition of resource_instance_remove handler\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nLwM2M TS 1.2 introduced possibility to delete a Resource Instance, so\none additional data model handler had to be added.\n\nNew ``resource_instance_remove`` handler (and its associated\n``anjay_dm_resource_instance_remove_t`` function type) has been introduced. It\nis analogous to the ``instance_remove`` handler; its job is to remove a specific\nResource Instance from a multiple-instance Resource.\n\nIts implementation is required in objects that include at least one writeable\nmultiple-instance Resource, if the client application aims for compliance with\nLwM2M 1.2.\n\nLimiting the maximum Hold Off Time for Bootstrap\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nA limit has been introduced on the maximum Hold Off Time (LwM2M Security Object,\nResource ID: 11) to avoid excessive delays when initiating the Bootstrap process.\nPreviously, the upper bound was implicitly 120 seconds, but it is now explicitly\nlimited to **20 seconds by default**.\n\nThis behavior can be customized at build time using the ``MAX_HOLDOFF_TIME``\nmacro.\n\nChanges in avs_coap\n-------------------\n\nChanged flow of cancelling observations in case of errors\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nCoAP observations are implicitly cancelled if a notification bearing a 4.xx or\n5.xx error code is delivered.\n\nIn Anjay 3.4.x and earlier, this cancellation (which involves calling the\n``avs_coap_observe_cancel_handler_t`` callback) was performed *before* calling\nthe ``avs_coap_delivery_status_handler_t`` callback for the specific\nnotification. Since Anjay 3.5.0, this order is reversed, so any code that relies\non this logic may break.\n\nThis change is only relevant if you are using ``avs_coap`` APIs directly (e.g.\nwhen communicating over raw CoAP protocol) and in case of notifications intended\nto be delivered as confirmable. The LwM2M Observe/Notify implementation in Anjay\nhas been updated accordingly.\n\nChanges in avs_commons\n----------------------\n\nIntroduction of new socket option\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\navs_commons 4.10.1 bundled with Anjay 2.15.1 adds a new socket option key:\n``AVS_NET_SOCKET_HAS_BUFFERED_DATA``. This is used to make sure that when\ncontrol is returned to the event loop, the ``poll()`` call will not stall\nwaiting for new data that in reality has been already buffered and could be\nretrieved using the avs_commons APIs.\n\nThis is usually meaningful for (D)TLS connections, but for almost all simple\nunencrypted socket implementations, this should always return ``false``.\n\nThis was previously achieved by always trying to receive more packets with\ntimeout set to zero. However, it has been determined that such logic could lead\nto heavy blocking of the event loop in case communication with the network stack\nis relatively slow, e.g. on devices which implement TCP/IP sockets through modem\nAT commands.\n\nIf you maintain your own socket integration layer or (D)TLS integration layer,\nit is recommended that you add support for this option. This is not, however, a\nbreaking change - if the option is not supported, the library will continue to\nuse the old behavior.\n\nRefactor of PSK credential handling\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nDeprecated ``avs_net_psk_info_t`` structure has been removed. Its successor,\n``avs_net_generic_psk_info_t``, has been renamed to ``avs_net_psk_info_t``.\nThis change also affects ``avs_net_security_info_t`` structure which contains\nthe latter. Implementation of accompanying ``avs_net_security_info_from_psk()``\nfunction has also been replaced with function previously known as\n``avs_net_security_info_from_generic_psk()``.\n\nThese changes are breaking for code that accesses the ``data.psk`` field of\n``avs_net_security_info_t`` directly and for usages of the two changed types.\n\nRefactor of time handling in avs_sched and avs_coap\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nIt is now enforced more strictly that time-based events shall happen when the\nclock reaches *at least* the expected value. Previously, the tasks scheduled via\navs_sched were executed only when the clock reached a value *later* than the\nscheduled job execution time.\n\nThis change will have no impact on your code if your platform has enough clock\nresolution so that two subsequent calls to ``avs_time_real_now()`` or\n``avs_time_monotonic_now()`` will *always* return different values. As a rule of\nthumb, this should be the case if your clock has a resolution no worse than\nabout 1-2 orders of magnitude smaller than the CPU clock. For example, for a\n100 MHz CPU, a clock resolution of around 100-1000 ns (i.e., 1-10 MHz) should be\nsufficient, depending on the specific architecture.\n\nIf your clock has a lower resolution, you may observe the following changes:\n\n* ``anjay_sched_run()`` is now properly guaranteed to execute at least one job\n  if the time reported by ``anjay_sched_time_to_next()`` passed. Previously this\n  could require waiting for another change of the numerical value of the clock,\n  which could cause undesirable active waiting in the event loop. This is the\n  motivating factor in introducing these changes.\n* Jobs scheduled using ``AVS_SCHED_NOW()`` during an execution of\n  ``anjay_sched_run()`` before the numerical value of the clock changes, *will*\n  be executed during the same run. The previous behavior more strictly enforced\n  the policy to not execute such jobs in the same run.\n\nIf you are scheduling custom jobs through the avs_sched module, you may want or\nneed to modify their logic accordingly to accommodate for these changes. In most\ntypical use cases, no changes are expected to be necessary.\n\nRemoval of avs_unit_memstream\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n``avs_unit_memstream`` was a specific implementation of ``avs_stream_t`` within\nthe avs_unit module that implemented a simple FIFO stream in a fixed-size memory\narea.\n\nThis feature has been removed. Instead, you can use an\n``avs_stream_inbuf``/``avs_stream_outbuf`` pair, or an ``avs_stream_membuf``\nobject.\n\nHandling of Trust Store for certain Certificate Usages settings\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\navs_commons 5.5.0 that is used by Anjay 3.11.0 does not pass the configured Trust\nStore (whether manually provided or acquired through the EST process) to Mbed TLS\nwhen the certificate usage is set to DANE-TA or DANE-EE, if the Data Model already\ncontains a Server certificate intended for verifying the Server during a secure\nconnection.\n\nIf the Server certificate is missing from the Data Model, Anjay falls back to\nPKIX verification, provided that a Trust Store is available, even when the\ncertificate usage is set to DANE-TA or DANE-EE.\n\nFor more information on how Anjay manages the Trust Store and the Certificate\nUsage resource, see\n:doc:`Certificate Usage <../AdvancedTopics/AT-CertificateUsage>`.\n\nPython environment isolation\n----------------------------\n\nAll Python-based tools (e.g. integration tests) must be executed within a\nPython virtual environment. See :doc:`/Tools/VirtualEnvironments` for more\ninformation.\n"
  },
  {
    "path": "doc/sphinx/source/Migrating/MigratingFromAnjay225.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nMigrating from Anjay 2.2.5\n==========================\n\n.. contents:: :local:\n\n.. highlight:: c\n\nIntroduction\n------------\n\nMost changes since Anjay 2.2.5 are minor, so no major changes in code flow are\nrequired when porting from Anjay 2.2. However, there has been a significant\nrefactor in project structure, so some changes may be breaking for some users.\n\nChange to minimum CMake version\n-------------------------------\n\nDeclared minimum CMake version necessary for CMake-based compilation, as well as\nfor importing the installed library through ``find_package()``, is now 3.16. If\nyou're using some Linux distribution that only has an older version in its\nrepositories (notably, Ubuntu 16.04), we recommend using one of the following\ninstall methods instead:\n\n* `Kitware APT Repository for Debian and Ubuntu <https://apt.kitware.com/>`_\n* `Snap Store <https://snapcraft.io/cmake>`_ (``snap install cmake``)\n* `Python Package Index <https://pypi.org/project/cmake/>`_\n  (``pip install cmake``)\n\nThis change does not affect users who compile the library using some alternative\napproach, without using the provided CMake scripts.\n\nChanges in Anjay proper\n-----------------------\n\nRemoval of ssize_t usages from Anjay APIs\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nMost changes in Anjay proper relate to the removal of usages of the\nPOSIX-specific ``size_t`` type in Anjay and all related projects. See also\n:ref:`ssize-t-removal-in-commons-225`.\n\nHere is a summary of the relevant API changes:\n\n* **Execute argument value getter**\n\n  - **Old API:**\n    ::\n\n        ssize_t anjay_execute_get_arg_value(anjay_execute_ctx_t *ctx,\n                                            char *out_buf,\n                                            size_t buf_size);\n\n  - **New API:**\n\n    .. snippet-source:: include_public/anjay/io.h\n       :emphasize-lines: 1-2\n\n        int anjay_execute_get_arg_value(anjay_execute_ctx_t *ctx,\n                                        size_t *out_bytes_read,\n                                        char *out_buf,\n                                        size_t buf_size);\n\n  - Return value semantics have been aligned with those of\n    ``anjay_get_string()`` - 0 is returned for success, negative value for\n    error, or ``ANJAY_BUFFER_TOO_SHORT`` (1) if the buffer was too small.\n\n    Length of the extracted data, which is equivalent to the old return value\n    semantics, can be retrieved using the new ``out_bytes_read`` argument, but\n    it can also be ``NULL`` if that is not necessary.\n\n\nRefactor of the Attribute Storage module\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nThe Attribute Storage feature is no longer a standalone module and has been\nmoved to the library core. From the user perspective, this has the following\nconsequences:\n\n* Explicit installation of this module in runtime is no longer necessary. The\n  ``anjay_attr_storage_install()`` method has been removed.\n* The ``ANJAY_WITH_MODULE_ATTR_STORAGE`` configuration macro in\n  ``anjay_config.h`` has been renamed to ``ANJAY_WITH_ATTR_STORAGE``.\n* The ``WITH_MODULE_attr_storage`` CMake option (equivalent to the macro\n  mentioned above) has been renamed to ``WITH_ATTR_STORAGE``.\n\nAdditionally, the behavior of ``anjay_attr_storage_restore()`` has been\nchanged - from now on, this function fails if supplied source stream is\ninvalid and the Attribute Storage remains untouched. This change makes the\nfunction consistent with other ``anjay_*_restore()`` APIs.\n\nRefactor of offline mode control API\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nSince Anjay 2.4, offline mode is configurable independently per every\ntransport. Below is a list of removed functions and counterparts that should\nbe used:\n\n+--------------------------------+------------------------------------------+\n| Removed function               | Counterpart                              |\n+--------------------------------+------------------------------------------+\n| ``anjay_is_offline()``         | ``anjay_transport_is_offline()``         |\n+--------------------------------+------------------------------------------+\n| ``anjay_enter_offline()``      | ``anjay_transport_enter_offline()``      |\n+--------------------------------+------------------------------------------+\n| ``anjay_exit_offline()``       | ``anjay_transport_exit_offline()``       |\n+--------------------------------+------------------------------------------+\n| ``anjay_schedule_reconnect()`` | ``anjay_transport_schedule_reconnect()`` |\n+--------------------------------+------------------------------------------+\n\nNew functions should be called with ``transport_set`` argument set to\n``ANJAY_TRANSPORT_SET_ALL`` to achieve the same behavior.\n\nAddition of the con attribute to public API\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nThe ``con`` attribute, enabled via the ``WITH_CON_ATTR`` CMake option, has been\npreviously supported as a custom extension. Since an identical flag has been\nstandardized as part of LwM2M TS 1.2, it has been included in the public API as\npart of preparations to support the new protocol version.\n\nIf you initialize ``anjay_dm_oi_attributes_t`` or ``anjay_dm_r_attributes_t``\nobjects manually, you may need to initialize the new ``con`` field as well,\nsince the empty ``ANJAY_DM_CON_ATTR_NONE`` value is **NOT** the default\nzero-initialized value.\n\nAs more new attributes may be added in future versions of Anjay, it is\nrecommended to initialize such structures with ``ANJAY_DM_OI_ATTRIBUTES_EMPTY``\nor ``ANJAY_DM_R_ATTRIBUTES_EMPTY`` constants, and then fill in the attributes\nyou actually intend to set.\n\nDefault (D)TLS version\n^^^^^^^^^^^^^^^^^^^^^^\n\nWhen the `anjay_configuration_t::dtls_version\n<../api/structanjay__configuration.html#ab32477e7370a36e02db5b7e7ccbdd89d>`_\nfield is set to ``AVS_NET_SSL_VERSION_DEFAULT`` (which includes the case of\nzero-initialization), Anjay 3.0 and earlier automatically mapped this setting to\n``AVS_NET_SSL_VERSION_TLSv1_2`` to ensure that (D)TLS 1.2 is used as mandated by\nthe LwM2M specification.\n\nThis mapping has been removed in Anjay 3.1, which means that the default version\nconfiguration of the underlying (D)TLS library will be used. This has been done\nto automatically allow the use of newer protocols and deprecate old versions\nwhen the backend library is updated, without the need to update Anjay code.\nHowever, depending on the (D)TLS backend library used, this may lead to (D)TLS\n1.1 or earlier being used if the server does not properly negotiate a higher\nversion. Please explicitly set ``dtls_version`` to\n``AVS_NET_SSL_VERSION_TLSv1_2`` if you want to disallow this.\n\nPlease note that Mbed TLS 3.0 has dropped support for TLS 1.1 and earlier, so\nthis change will not affect behavior with that library.\n\n\nAddition of resource_instance_remove handler\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nLwM2M TS 1.2 introduced possibility to delete a Resource Instance, so\none additional data model handler had to be added.\n\nNew ``resource_instance_remove`` handler (and its associated\n``anjay_dm_resource_instance_remove_t`` function type) has been introduced. It\nis analogous to the ``instance_remove`` handler; its job is to remove a specific\nResource Instance from a multiple-instance Resource.\n\nIts implementation is required in objects that include at least one writeable\nmultiple-instance Resource, if the client application aims for compliance with\nLwM2M 1.2.\n\nLimiting the maximum Hold Off Time for Bootstrap\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nA limit has been introduced on the maximum Hold Off Time (LwM2M Security Object,\nResource ID: 11) to avoid excessive delays when initiating the Bootstrap process.\nPreviously, the upper bound was implicitly 120 seconds, but it is now explicitly\nlimited to **20 seconds by default**.\n\nThis behavior can be customized at build time using the ``MAX_HOLDOFF_TIME``\nmacro.\n\nOther changes\n^^^^^^^^^^^^^\n\n* Declaration of ``anjay_smsdrv_cleanup()`` has been moved from ``anjay/core.h``\n  to ``anjay/sms.h`` in versions that include the SMS commercial feature. It has\n  been removed altogether from versions that do not support SMS.\n* The following compile-time constants have been removed. None of them have been\n  actually used in Anjay 2.x:\n\n  * ``MAX_FLOAT_STRING_SIZE``\n  * ``MAX_OBSERVABLE_RESOURCE_SIZE``\n\n* **Getter function for retrieving security information from data model**\n\n  * **Old API:**\n    ::\n\n        anjay_security_config_t *anjay_security_config_from_dm(anjay_t *anjay,\n                                                               const char *uri);\n\n  * **New API:**\n\n    .. snippet-source:: include_public/anjay/core.h\n\n        int anjay_security_config_from_dm(anjay_t *anjay,\n                                          anjay_security_config_t *out_config,\n                                          const char *uri);\n\n  * The security configuration is now returned through an output argument with\n    any necessary internal buffers cached inside the Anjay object instead of\n    using heap allocation. Please refer to the Doxygen-based documentation of\n    this function for details.\n\n    Due to the change in lifetime requirements, no compatibility variant is\n    provided.\n\n\nChanges in avs_coap\n-------------------\n\nIf you are using ``avs_coap`` APIs directly (e.g. when communicating over raw\nCoAP protocol), please note that following breaking changes in the ``avs_coap``\ncomponent:\n\navs_coap header rename\n^^^^^^^^^^^^^^^^^^^^^^\n\nIn line with Anjay and ``avs_commons``, to improve file name uniqueness, the\n``avsystem/coap/config.h`` file has been renamed to\n``avsystem/coap/avs_coap_config.h``.\n\nContext creation API change\n^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nContext creation functions now take an explicit PRNG context argument:\n\n* **UDP context creation**\n\n  - **Old API:**\n    ::\n\n        avs_coap_ctx_t *\n        avs_coap_udp_ctx_create(avs_sched_t *sched,\n                                const avs_coap_udp_tx_params_t *udp_tx_params,\n                                avs_shared_buffer_t *in_buffer,\n                                avs_shared_buffer_t *out_buffer,\n                                avs_coap_udp_response_cache_t *cache);\n\n  - **New API:**\n\n    .. snippet-source:: deps/avs_coap/include_public/avsystem/coap/udp.h\n      :emphasize-lines: 7\n\n        avs_coap_ctx_t *\n        avs_coap_udp_ctx_create(avs_sched_t *sched,\n                                const avs_coap_udp_tx_params_t *udp_tx_params,\n                                avs_shared_buffer_t *in_buffer,\n                                avs_shared_buffer_t *out_buffer,\n                                avs_coap_udp_response_cache_t *cache,\n                                avs_crypto_prng_ctx_t *prng_ctx);\n\n* **TCP context creation**\n\n  - **Old API:**\n    ::\n\n        avs_coap_ctx_t *avs_coap_tcp_ctx_create(avs_sched_t *sched,\n                                                avs_shared_buffer_t *in_buffer,\n                                                avs_shared_buffer_t *out_buffer,\n                                                size_t max_opts_size,\n                                                avs_time_duration_t request_timeout);\n\n  - **New API:**\n\n    .. snippet-source:: deps/avs_coap/include_public/avsystem/coap/tcp.h\n      :emphasize-lines: 6\n\n        avs_coap_ctx_t *avs_coap_tcp_ctx_create(avs_sched_t *sched,\n                                                avs_shared_buffer_t *in_buffer,\n                                                avs_shared_buffer_t *out_buffer,\n                                                size_t max_opts_size,\n                                                avs_time_duration_t request_timeout,\n                                                avs_crypto_prng_ctx_t *prng_ctx);\n\n.. note ::\n\n    It is now **mandatory** to pass a non-NULL value as the ``prng_ctx``\n    argument to the functions above.\n\nChanged flow of cancelling observations in case of errors\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nCoAP observations are implicitly cancelled if a notification bearing a 4.xx or\n5.xx error code is delivered.\n\nIn Anjay 3.4.x and earlier, this cancellation (which involves calling the\n``avs_coap_observe_cancel_handler_t`` callback) was performed *before* calling\nthe ``avs_coap_delivery_status_handler_t`` callback for the specific\nnotification. Since Anjay 3.5.0, this order is reversed, so any code that relies\non this logic may break.\n\nChanges in avs_commons\n----------------------\n\n``avs_commons`` 4.1 and later contain a number of breaking changes compared to\nversion 4.0 used by Anjay 2.2. If you are using any of the ``avs_commons`` APIs\ndirectly (which is especially likely for e.g. the logging API and querying\nsockets in the event loop), you will need to adjust your code.\n\navs_commons header rename\n^^^^^^^^^^^^^^^^^^^^^^^^^\n\nAll headers of the ``avs_commons`` component have been renamed to make their\nnames more unique. Please adjust your ``#include`` directives accordingly.\n\nThe general rename patterns are:\n\n* ``avsystem/commons/*.h`` → ``avsystem/commons/avs_*.h``\n* ``avsystem/commons/stream/*.h``, ``avsystem/commons/stream/stream_*.h`` →\n  ``avsystem/commons/avs_stream_*.h``\n* ``avsystem/commons/unit/*.h`` → ``avsystem/commons/avs_unit_*.h``\n\nBelow is a detailed list of all renamed files:\n\n+------------------------------------------------+-------------------------------------------------+\n| Old header file                                | New header file                                 |\n+================================================+=================================================+\n| ``avsystem/commons/addrinfo.h``                | ``avsystem/commons/avs_addrinfo.h``             |\n+------------------------------------------------+-------------------------------------------------+\n| ``avsystem/commons/aead.h``                    | ``avsystem/commons/avs_aead.h``                 |\n+------------------------------------------------+-------------------------------------------------+\n| ``avsystem/commons/base64.h``                  | ``avsystem/commons/avs_base64.h``               |\n+------------------------------------------------+-------------------------------------------------+\n| ``avsystem/commons/buffer.h``                  | ``avsystem/commons/avs_buffer.h``               |\n+------------------------------------------------+-------------------------------------------------+\n| ``avsystem/commons/cleanup.h``                 | ``avsystem/commons/avs_cleanup.h``              |\n+------------------------------------------------+-------------------------------------------------+\n| ``avsystem/commons/condvar.h``                 | ``avsystem/commons/avs_condvar.h``              |\n+------------------------------------------------+-------------------------------------------------+\n| ``avsystem/commons/defs.h``                    | ``avsystem/commons/avs_defs.h``                 |\n+------------------------------------------------+-------------------------------------------------+\n| ``avsystem/commons/errno.h``                   | ``avsystem/commons/avs_errno.h``                |\n+------------------------------------------------+-------------------------------------------------+\n| ``avsystem/commons/errno_map.h``               | ``avsystem/commons/avs_errno_map.h``            |\n+------------------------------------------------+-------------------------------------------------+\n| ``avsystem/commons/hkdf.h``                    | ``avsystem/commons/avs_hkdf.h``                 |\n+------------------------------------------------+-------------------------------------------------+\n| ``avsystem/commons/http.h``                    | ``avsystem/commons/avs_http.h``                 |\n+------------------------------------------------+-------------------------------------------------+\n| ``avsystem/commons/init_once.h``               | ``avsystem/commons/avs_init_once.h``            |\n+------------------------------------------------+-------------------------------------------------+\n| ``avsystem/commons/list.h``                    | ``avsystem/commons/avs_list.h``                 |\n+------------------------------------------------+-------------------------------------------------+\n| ``avsystem/commons/log.h``                     | ``avsystem/commons/avs_log.h``                  |\n+------------------------------------------------+-------------------------------------------------+\n| ``avsystem/commons/memory.h``                  | ``avsystem/commons/avs_memory.h``               |\n+------------------------------------------------+-------------------------------------------------+\n| ``avsystem/commons/mutex.h``                   | ``avsystem/commons/avs_mutex.h``                |\n+------------------------------------------------+-------------------------------------------------+\n| ``avsystem/commons/net.h``                     | ``avsystem/commons/avs_net.h``                  |\n+------------------------------------------------+-------------------------------------------------+\n| ``avsystem/commons/persistence.h``             | ``avsystem/commons/avs_persistence.h``          |\n+------------------------------------------------+-------------------------------------------------+\n| ``avsystem/commons/rbtree.h``                  | ``avsystem/commons/avs_rbtree.h``               |\n+------------------------------------------------+-------------------------------------------------+\n| ``avsystem/commons/sched.h``                   | ``avsystem/commons/avs_sched.h``                |\n+------------------------------------------------+-------------------------------------------------+\n| ``avsystem/commons/shared_buffer.h``           | ``avsystem/commons/avs_shared_buffer.h``        |\n+------------------------------------------------+-------------------------------------------------+\n| ``avsystem/commons/socket.h``                  | | ``avsystem/commons/avs_socket.h``             |\n|                                                | | ``avsystem/commons/avs_crypto_pki.h`` [#pki]_ |\n+------------------------------------------------+-------------------------------------------------+\n| ``avsystem/commons/socket_v_table.h``          | ``avsystem/commons/avs_socket_v_table.h``       |\n+------------------------------------------------+-------------------------------------------------+\n| ``avsystem/commons/stream.h``                  | ``avsystem/commons/avs_stream.h``               |\n+------------------------------------------------+-------------------------------------------------+\n| ``avsystem/commons/stream/stream_buffered.h``  | ``avsystem/commons/avs_stream_buffered.h``      |\n+------------------------------------------------+-------------------------------------------------+\n| ``avsystem/commons/stream/stream_file.h``      | ``avsystem/commons/avs_stream_file.h``          |\n+------------------------------------------------+-------------------------------------------------+\n| ``avsystem/commons/stream/stream_inbuf.h``     | ``avsystem/commons/avs_stream_inbuf.h``         |\n+------------------------------------------------+-------------------------------------------------+\n| ``avsystem/commons/stream/md5.h``              | ``avsystem/commons/avs_stream_md5.h``           |\n+------------------------------------------------+-------------------------------------------------+\n| ``avsystem/commons/stream/stream_membuf.h``    | ``avsystem/commons/avs_stream_membuf.h``        |\n+------------------------------------------------+-------------------------------------------------+\n| ``avsystem/commons/stream/stream_net.h``       | ``avsystem/commons/avs_stream_net.h``           |\n+------------------------------------------------+-------------------------------------------------+\n| ``avsystem/commons/stream/netbuf.h``           | ``avsystem/commons/avs_stream_netbuf.h``        |\n+------------------------------------------------+-------------------------------------------------+\n| ``avsystem/commons/stream/stream_outbuf.h``    | ``avsystem/commons/avs_stream_outbuf.h``        |\n+------------------------------------------------+-------------------------------------------------+\n| ``avsystem/commons/stream/stream_simple_io.h`` | ``avsystem/commons/avs_stream_simple_io.h``     |\n+------------------------------------------------+-------------------------------------------------+\n| ``avsystem/commons/stream_v_table.h``          | ``avsystem/commons/avs_stream_v_table.h``       |\n+------------------------------------------------+-------------------------------------------------+\n| ``avsystem/commons/time.h``                    | ``avsystem/commons/avs_time.h``                 |\n+------------------------------------------------+-------------------------------------------------+\n| ``avsystem/commons/unit/memstream.h``          | ``avsystem/commons/avs_unit_memstream.h``       |\n+------------------------------------------------+-------------------------------------------------+\n| ``avsystem/commons/unit/mock_helpers.h``       | ``avsystem/commons/avs_unit_mock_helpers.h``    |\n+------------------------------------------------+-------------------------------------------------+\n| ``avsystem/commons/unit/mocksock.h``           | ``avsystem/commons/avs_unit_mocksock.h``        |\n+------------------------------------------------+-------------------------------------------------+\n| ``avsystem/commons/unit/test.h``               | ``avsystem/commons/avs_unit_test.h``            |\n+------------------------------------------------+-------------------------------------------------+\n| ``avsystem/commons/url.h``                     | ``avsystem/commons/avs_url.h``                  |\n+------------------------------------------------+-------------------------------------------------+\n| ``avsystem/commons/utils.h``                   | ``avsystem/commons/avs_utils.h``                |\n+------------------------------------------------+-------------------------------------------------+\n| ``avsystem/commons/vector.h``                  | ``avsystem/commons/avs_vector.h``               |\n+------------------------------------------------+-------------------------------------------------+\n\n.. [#pki] Some symbols related to public-key cryptography have been refactored\n          by moving from ``avsystem/commons/avs_socket.h`` to\n          ``avsystem/commons/avs_crypto_pki.h``, with additional renames. For\n          details, see :ref:`avs-commons-pki-move-225`.\n\nChanges to avs_net socket API\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nBelow is a reference of changes made to the ``avs_net`` socket API:\n\n.. list-table::\n   :widths: 20 20 40\n   :header-rows: 1\n\n   * - Old identifiers\n     - New identifiers\n     - Notes\n   * - | ``avs_net_socket_create()``\n     - | ``avs_net_udp_socket_create()``\n       | ``avs_net_tcp_socket_create()``\n       | ``avs_net_dtls_socket_create()``\n       | ``avs_net_ssl_socket_create()``\n     - | The ``avs_net_socket_type_t`` enum is no longer used for socket\n         creation. Separate functions are used instead, allowing for type-safe\n         passing of the configuration structures.\n   * - | ``avs_net_socket_decorate_in_place()``\n     - | ``avs_net_dtls_socket_decorate_in_place()``\n       | ``avs_net_ssl_socket_decorate_in_place()``\n     - | This change is analogous to the one above.\n   * - | *implicit*\n     - | ``prng_ctx`` field in ``avs_net_ssl_configuration_t``\n     - | **Note:** It is now **mandatory** to fill this field when instantiating\n         a (D)TLS socket.\n\n.. note::\n\n    With the introduction of the ``prng_ctx`` field in\n    ``avs_net_ssl_configuration_t``, the\n    ``WITH_MBEDTLS_CUSTOM_ENTROPY_INITIALIZER`` compile-time option and the\n    option to use a user-provided ``avs_net_mbedtls_entropy_init()`` function\n    have been **removed**. If you relied on those features in your non-POSIX\n    environment, please replace them with the new PRNG context mechanism.\n    See :doc:`MigratingCustomEntropy` for details.\n\nIntroduction of new socket option\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\navs_commons 4.10.1 bundled with Anjay 2.15.1 adds a new socket option key:\n``AVS_NET_SOCKET_HAS_BUFFERED_DATA``. This is used to make sure that when\ncontrol is returned to the event loop, the ``poll()`` call will not stall\nwaiting for new data that in reality has been already buffered and could be\nretrieved using the avs_commons APIs.\n\nThis is usually meaningful for (D)TLS connections, but for almost all simple\nunencrypted socket implementations, this should always return ``false``.\n\nThis was previously achieved by always trying to receive more packets with\ntimeout set to zero. However, it has been determined that such logic could lead\nto heavy blocking of the event loop in case communication with the network stack\nis relatively slow, e.g. on devices which implement TCP/IP sockets through modem\nAT commands.\n\nIf you maintain your own socket integration layer or (D)TLS integration layer,\nit is recommended that you add support for this option.\n\n.. _ssize-t-removal-in-commons-225:\n\nRemoval of ssize_t usages from avs_commons APIs\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nAll usages of the POSIX-specific ``ssize_t`` type in public APIs have been\nremoved. Instead of replacing it with some other signed integer type, additional\nout-arguments have been introduced to functions that used it.\n\nBelow is a reference of related changes:\n\n* **Base64 decode**\n\n  - **Old APIs:**\n    ::\n\n        ssize_t avs_base64_decode_custom(uint8_t *out,\n                                         size_t out_length,\n                                         const char *input,\n                                         avs_base64_config_t config);\n        // ...\n        static inline ssize_t\n        avs_base64_decode_strict(uint8_t *out, size_t out_length, const char *input) {\n            // ...\n        }\n        // ...\n        static inline ssize_t\n        avs_base64_decode(uint8_t *out, size_t out_length, const char *input) {\n            // ...\n        }\n\n  - **New APIs:**\n\n    .. snippet-source:: deps/avs_commons/include_public/avsystem/commons/avs_base64.h\n       :emphasize-lines: 1,7,14\n\n        int avs_base64_decode_custom(size_t *out_bytes_decoded,\n                                     uint8_t *out,\n                                     size_t out_length,\n                                     const char *input,\n                                     avs_base64_config_t config);\n        // ...\n        static inline int avs_base64_decode_strict(size_t *out_bytes_decoded,\n                                                   uint8_t *out,\n                                                   size_t out_length,\n                                                   const char *input) {\n            // ...\n        }\n        // ...\n        static inline int avs_base64_decode(size_t *out_bytes_decoded,\n                                            uint8_t *out,\n                                            size_t out_length,\n                                            const char *input) {\n            // ...\n        }\n\n* **Hexlify**\n\n  - **Old API:**\n    ::\n\n        ssize_t avs_hexlify(char *out_hex,\n                            size_t out_size,\n                            const void *input,\n                            size_t input_size);\n\n  - **New API:**\n\n    .. snippet-source:: deps/avs_commons/include_public/avsystem/commons/avs_utils.h\n       :emphasize-lines: 1,3\n\n        int avs_hexlify(char *out_hex,\n                        size_t out_size,\n                        size_t *out_bytes_hexlified,\n                        const void *input,\n                        size_t input_size);\n\n* **Unhexlify**\n\n  - **Old API:**\n    ::\n\n        ssize_t avs_unhexlify(uint8_t *output,\n                              size_t out_size,\n                              const char *input,\n                              size_t in_size);\n\n  - **New API:**\n\n    .. snippet-source:: deps/avs_commons/include_public/avsystem/commons/avs_utils.h\n       :emphasize-lines: 1\n\n        int avs_unhexlify(size_t *out_bytes_written,\n                          uint8_t *output,\n                          size_t out_size,\n                          const char *input,\n                          size_t in_size);\n\n.. note::\n\n    The new functions return 0 in all cases in which the old versions returned\n    non-negative values. The value previously returned through the non-negative\n    return value can be retrieved using the additional out-arguments, which have\n    the same semantics. ``NULL`` can be passed to those out-arguments as well if\n    that value is not needed.\n\n    The seemingly irregular placement of the new out-argument in\n    ``avs_hexlify()`` is due to the fact that the semantics of that value is\n    related to the ``input`` argument (hence it directly precedes it), not to\n    the output buffer as is the case with the rest of these functions.\n\n.. _avs-commons-pki-move-225:\n\nMove of public-key cryptography APIs from avs_net to avs_crypto\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nPublic key cryptography APIs, previously defined in\n``avsystem/commons/socket.h``, have been moved into a new header called\n``avsystem/commons/avs_crypto_pki.h``.\n\nAdditionally, client-side and server-side certificate info structures are no\nlonger separate, and both have been merged into a single type.\n\nHere is a summary of renames:\n\n+-----------------------------------------------+-----------------------------------------------------+\n| Old symbol name                               | New symbol name                                     |\n+===============================================+=====================================================+\n| | ``avs_net_trusted_cert_info_t``             | ``avs_crypto_certificate_chain_info_t``             |\n| | ``avs_net_client_cert_info_t``              |                                                     |\n+-----------------------------------------------+-----------------------------------------------------+\n| ``avs_net_client_key_info_t``                 | ``avs_crypto_private_key_info_t``                   |\n+-----------------------------------------------+-----------------------------------------------------+\n| ``avs_net_security_info_union_t``             | ``avs_crypto_security_info_union_t``                |\n+-----------------------------------------------+-----------------------------------------------------+\n| | ``avs_net_trusted_cert_info_from_buffer()`` | ``avs_crypto_certificate_chain_info_from_buffer()`` |\n| | ``avs_net_client_cert_info_from_buffer()``  |                                                     |\n+-----------------------------------------------+-----------------------------------------------------+\n| | ``avs_net_trusted_cert_info_from_file()``   | ``avs_crypto_certificate_chain_info_from_file()``   |\n| | ``avs_net_client_cert_info_from_file()``    |                                                     |\n+-----------------------------------------------+-----------------------------------------------------+\n| ``avs_net_client_key_info_from_buffer()``     | ``avs_crypto_private_key_info_from_buffer()``       |\n+-----------------------------------------------+-----------------------------------------------------+\n| ``avs_net_client_key_info_from_file()``       | ``avs_crypto_private_key_info_from_file()``         |\n+-----------------------------------------------+-----------------------------------------------------+\n| ``avs_net_trusted_cert_info_from_path()``     | ``avs_crypto_certificate_chain_info_from_path()``   |\n+-----------------------------------------------+-----------------------------------------------------+\n\nRefactor of PSK credential handling\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n``avs_net_psk_info_t`` structure has been changed to use new types based on\n``avs_crypto_security_info_union_t`` instead of raw buffers. This change also\naffects ``avs_net_security_info_t`` structure which contains the former.\n\n* **Old API:**\n  ::\n\n      /**\n       * A PSK/identity pair with borrowed pointers. avs_commons will never attempt\n       * to modify these values.\n       */\n      typedef struct {\n          const void *psk;\n          size_t psk_size;\n          const void *identity;\n          size_t identity_size;\n      } avs_net_psk_info_t;\n\n      // ...\n\n      typedef struct {\n          avs_net_security_mode_t mode;\n          union {\n              avs_net_psk_info_t psk;\n              avs_net_certificate_info_t cert;\n          } data;\n      } avs_net_security_info_t;\n\n      avs_net_security_info_t avs_net_security_info_from_psk(avs_net_psk_info_t psk);\n\n* **New API:**\n\n  .. snippet-source:: deps/avs_commons/include_public/avsystem/commons/avs_crypto_psk.h\n\n      typedef struct {\n          avs_crypto_security_info_union_t desc;\n      } avs_crypto_psk_identity_info_t;\n\n      // ...\n\n      avs_crypto_psk_identity_info_t\n      avs_crypto_psk_identity_info_from_buffer(const void *buffer,\n                                               size_t buffer_size);\n\n      // ...\n\n      typedef struct {\n          avs_crypto_security_info_union_t desc;\n      } avs_crypto_psk_key_info_t;\n\n      // ...\n\n      avs_crypto_psk_key_info_t\n      avs_crypto_psk_key_info_from_buffer(const void *buffer, size_t buffer_size);\n\n  .. snippet-source:: deps/avs_commons/include_public/avsystem/commons/avs_socket.h\n\n      /**\n       * A PSK/identity pair. avs_commons will never attempt to modify these values.\n       */\n      typedef struct {\n          avs_crypto_psk_key_info_t key;\n          avs_crypto_psk_identity_info_t identity;\n      } avs_net_psk_info_t;\n\n      // ...\n\n      typedef struct {\n          avs_net_security_mode_t mode;\n          union {\n              avs_net_psk_info_t psk;\n              avs_net_certificate_info_t cert;\n          } data;\n      } avs_net_security_info_t;\n\n      avs_net_security_info_t\n      avs_net_security_info_from_psk(avs_net_psk_info_t psk);\n\nThis change is breaking for code that accesses the ``data.psk`` field\nof ``avs_net_security_info_t`` directly.\n\nChanges to public configuration macros\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n``avs_commons`` 4.1 introduced a new header file,\n``avsystem/commons/avs_commons_config.h``, that encapsulates all its\ncompile-time configuration, allowing compiling the library without the use of\nCMake, among other improvements.\n\nThis file is included by all other ``avs_commons`` headers, so this is not a\nbreaking change in and of itself. However, some configuration macros that were\npreviously ``#define``-d in ``avsystem/commons/defs.h`` have been renamed for\nbetter namespace separation.\n\nIf your code checks for these macros using ``#ifdef`` etc., it will need\nadjustments.\n\n+---------------------------------------------------------+-------------------------------------+\n| Old macro name                                          | New macro name                      |\n+=========================================================+=====================================+\n| ``WITH_IPV4``                                           | ``AVS_COMMONS_NET_WITH_IPV4``       |\n+---------------------------------------------------------+-------------------------------------+\n| ``WITH_IPV6``                                           | ``AVS_COMMONS_NET_WITH_IPV6``       |\n+---------------------------------------------------------+-------------------------------------+\n| ``WITH_X509``                                           | ``AVS_COMMONS_WITH_AVS_CRYPTO_PKI`` |\n+---------------------------------------------------------+-------------------------------------+\n| ``WITH_AVS_MICRO_LOGS``                                 | ``AVS_COMMONS_WITH_MICRO_LOGS``     |\n+---------------------------------------------------------+-------------------------------------+\n| ``HAVE_NET_IF_H``                                       | ``AVS_COMMONS_HAVE_NET_IF_H``       |\n+---------------------------------------------------------+-------------------------------------+\n| ``AVS_SSIZE_T_DEFINED``                                 | *removed completely*                |\n+---------------------------------------------------------+-------------------------------------+\n| ``HAVE_SYS_TYPES_H``                                    | *removed completely*                |\n+---------------------------------------------------------+-------------------------------------+\n| ``AVS_COMMONS_WITH_MBEDTLS_CUSTOM_ENTROPY_INITIALIZER`` | *removed completely*                |\n+---------------------------------------------------------+-------------------------------------+\n\n.. important::\n\n    In the case of ``WITH_X509``, the corresponding CMake variable has also been\n    renamed to ``WITH_PKI``. Attempting to use ``WITH_X509`` will trigger an\n    error.\n\n.. note::\n\n    Aside from the one variable mentioned above, and those removed completely,\n    the CMake variable names have not changed - the renames affect **only** the\n    C preprocessor.\n\nRefactor of avs_net_validate_ip_address() and avs_net_local_address_for_target_host()\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n``avs_net_validate_ip_address()`` is now no longer used by Anjay or\n``avs_commons``. It was previously necessary to implement it as part of the\nsocket implementation. This is no longer required. For compatibility, the\nfunction has been reimplemented as a ``static inline`` function that wraps\n``avs_net_addrinfo_*()`` APIs. Please remove your version of\n``avs_net_validate_ip_address()`` from your socket implementation if you have\none, as having two alternative variants may lead to conflicts.\n\nSince Anjay 2.9 and ``avs_commons`` 4.6,\n``avs_net_local_address_for_target_host()`` underwent a similar refactor. It was\npreviously a function to be optionally implemented as part of the socket\nimplementation, but now it is a ``static inline`` function that wraps\n``avs_net_socket_*()`` APIs. Please remove your version of\n``avs_net_local_address_for_target_host()`` from your socket implementation if\nyou have one, as having two alternative variants may lead to conflicts.\n\nChanges in component dependencies\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n* ``avs_net`` now depends on ``avs_crypto``\n\n  * ``avs_crypto`` itself was previously only used for advanced features, only\n    used by the OSCORE commercial feature.\n  * In the new version, ``avs_crypto`` also contains an abstraction over\n    cryptographically-safe PRNGs.\n  * The functionality that comprised the \"old\" ``avs_crypto`` is now controlled\n    by the ``AVS_COMMONS_WITH_AVS_CRYPTO_ADVANCED_FEATURES`` compile-time\n    option.\n\n* ``avs_vector`` is no longer compiled by default when building Anjay\n\n* URL handling routines, previously a part of ``avs_net``, are now a separate\n  component called ``avs_url``\n\n  * You may need to add ``-lavs_url`` to your link command if you're not using\n    CMake to handle dependencies between your project and Anjay\n\nRemoval of the legacy CoAP component\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nWhile the new ``avs_coap`` has been used as the CoAP implementation in all\nversions of Anjay 2.x, the old CoAP component of ``avs_commons`` remained in the\nrepository in the 4.0 branch of ``avs_commons``.\n\nThis has been removed in ``avs_commons`` 4.1 and Anjay 2.3. If your code used\nthe raw CoAP APIs of that component, you will need to migrate to either the new\n``avs_coap`` library or an entirely different CoAP implementation.\n\n.. note::\n\n    The new ``avs_coap`` library has a higher-level API, designed to abstract\n    away the differences between e.g. UDP and TCP transports. Some of the\n    functionality of the legacy library, especially that related to parsing,\n    serializing, sending and receiving raw, isolated messages (as opposed to\n    proper, conformant CoAP exchanges), is not provided in the public API for\n    this reason.\n\nRefactor of time handling in avs_sched and avs_coap\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nIt is now enforced more strictly that time-based events shall happen when the\nclock reaches *at least* the expected value. Previously, the tasks scheduled via\navs_sched were executed only when the clock reached a value *later* than the\nscheduled job execution time.\n\nThis change will have no impact on your code if your platform has enough clock\nresolution so that two subsequent calls to ``avs_time_real_now()`` or\n``avs_time_monotonic_now()`` will *always* return different values. As a rule of\nthumb, this should be the case if your clock has a resolution no worse than\nabout 1-2 orders of magnitude smaller than the CPU clock. For example, for a\n100 MHz CPU, a clock resolution of around 100-1000 ns (i.e., 1-10 MHz) should be\nsufficient, depending on the specific architecture.\n\nIf your clock has a lower resolution, you may observe the following changes:\n\n* ``anjay_sched_run()`` is now properly guaranteed to execute at least one job\n  if the time reported by ``anjay_sched_time_to_next()`` passed. Previously this\n  could require waiting for another change of the numerical value of the clock,\n  which could cause undesirable active waiting in the event loop. This is the\n  motivating factor in introducing these changes.\n* Jobs scheduled using ``AVS_SCHED_NOW()`` during an execution of\n  ``anjay_sched_run()`` before the numerical value of the clock changes, *will*\n  be executed during the same run. The previous behavior more strictly enforced\n  the policy to not execute such jobs in the same run.\n\nIf you are scheduling custom jobs through the avs_sched module, you may want or\nneed to modify their logic accordingly to accommodate for these changes. In most\ntypical use cases, no changes are expected to be necessary.\n\nRemoval of avs_unit_memstream\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n``avs_unit_memstream`` was a specific implementation of ``avs_stream_t`` within\nthe avs_unit module that implemented a simple FIFO stream in a fixed-size memory\narea.\n\nThis feature has been removed. Instead, you can use an\n``avs_stream_inbuf``/``avs_stream_outbuf`` pair, or an ``avs_stream_membuf``\nobject.\n\nHandling of Trust Store for certain Certificate Usages settings\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\navs_commons 5.5.0 that is used by Anjay 3.11.0 does not pass the configured Trust\nStore (whether manually provided or acquired through the EST process) to Mbed TLS\nwhen the certificate usage is set to DANE-TA or DANE-EE, if the Data Model already\ncontains a Server certificate intended for verifying the Server during a secure\nconnection.\n\nIf the Server certificate is missing from the Data Model, Anjay falls back to\nPKIX verification, provided that a Trust Store is available, even when the\ncertificate usage is set to DANE-TA or DANE-EE.\n\nFor more information on how Anjay manages the Trust Store and the Certificate\nUsage resource, see\n:doc:`Certificate Usage <../AdvancedTopics/AT-CertificateUsage>`.\n\nPython environment isolation\n----------------------------\n\nAll Python-based tools (e.g. integration tests) must be executed within a\nPython virtual environment. See :doc:`/Tools/VirtualEnvironments` for more\ninformation.\n"
  },
  {
    "path": "doc/sphinx/source/Migrating/MigratingFromAnjay24.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nMigrating from Anjay 2.3.x or 2.4.x\n===================================\n\n.. contents:: :local:\n\n.. highlight:: c\n\nIntroduction\n------------\n\nWhile most changes since Anjay 2.4 are minor, some of them (changes to commonly\nused APIs such as Attribute Storage and offline mode control) are breaking.\nAdditionally, ``avs_commons`` cryptography support libraries have undergone a\nsignificant redesign and there are some changes which might prove to be breaking\nif old APIs have been used directly.\n\nAdditional slight updates might be necessary if you are using any alternative\nbuild system instead of CMake to compile your project, of if you maintain your\nown implementation of the socket layer.\n\nChange to minimum CMake version\n-------------------------------\n\nDeclared minimum CMake version necessary for CMake-based compilation, as well as\nfor importing the installed library through ``find_package()``, is now 3.16. If\nyou're using some Linux distribution that only has an older version in its\nrepositories (notably, Ubuntu 16.04), we recommend using one of the following\ninstall methods instead:\n\n* `Kitware APT Repository for Debian and Ubuntu <https://apt.kitware.com/>`_\n* `Snap Store <https://snapcraft.io/cmake>`_ (``snap install cmake``)\n* `Python Package Index <https://pypi.org/project/cmake/>`_\n  (``pip install cmake``)\n\nThis change does not affect users who compile the library using some alternative\napproach, without using the provided CMake scripts.\n\nChanges in Anjay proper\n-----------------------\n\nChange of security configuration lifetime\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n* **Getter function for retrieving security information from data model**\n\n  * **Old API:**\n    ::\n\n        anjay_security_config_t *anjay_security_config_from_dm(anjay_t *anjay,\n                                                               const char *uri);\n  * **New API:**\n\n    .. snippet-source:: include_public/anjay/core.h\n\n        int anjay_security_config_from_dm(anjay_t *anjay,\n                                          anjay_security_config_t *out_config,\n                                          const char *uri);\n\n  * The security configuration is now returned through an output argument with\n    any necessary internal buffers cached inside the Anjay object instead of\n    using heap allocation. Please refer to the Doxygen-based documentation of\n    this function for details.\n\n    Due to the change in lifetime requirements, no compatibility variant is\n    provided.\n\nRefactor of the Attribute Storage module\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nThe Attribute Storage feature is no longer a standalone module and has been\nmoved to the library core. From the user perspective, this has the following\nconsequences:\n\n* Explicit installation of this module in runtime is no longer necessary. The\n  ``anjay_attr_storage_install()`` method has been removed.\n* The ``ANJAY_WITH_MODULE_ATTR_STORAGE`` configuration macro in\n  ``anjay_config.h`` has been renamed to ``ANJAY_WITH_ATTR_STORAGE``.\n* The ``WITH_MODULE_attr_storage`` CMake option (equivalent to the macro\n  mentioned above) has been renamed to ``WITH_ATTR_STORAGE``.\n\nAdditionally, the behavior of ``anjay_attr_storage_restore()`` has been\nchanged - from now on, this function fails if supplied source stream is\ninvalid and the Attribute Storage remains untouched. This change makes the\nfunction consistent with other ``anjay_*_restore()`` APIs.\n\nRefactor of offline mode control API\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nSince Anjay 2.4, offline mode is configurable independently per every\ntransport. Below is a list of removed functions and counterparts that should\nbe used:\n\n+--------------------------------+------------------------------------------+\n| Removed function               | Counterpart                              |\n+--------------------------------+------------------------------------------+\n| ``anjay_is_offline()``         | ``anjay_transport_is_offline()``         |\n+--------------------------------+------------------------------------------+\n| ``anjay_enter_offline()``      | ``anjay_transport_enter_offline()``      |\n+--------------------------------+------------------------------------------+\n| ``anjay_exit_offline()``       | ``anjay_transport_exit_offline()``       |\n+--------------------------------+------------------------------------------+\n| ``anjay_schedule_reconnect()`` | ``anjay_transport_schedule_reconnect()`` |\n+--------------------------------+------------------------------------------+\n\nNew functions should be called with ``transport_set`` argument set to\n``ANJAY_TRANSPORT_SET_ALL`` to achieve the same behavior.\n\nAddition of the con attribute to public API\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nThe ``con`` attribute, enabled via the ``ANJAY_WITH_CON_ATTR`` compile-time\noption, has been previously supported as a custom extension. Since an identical\nflag has been standardized as part of LwM2M TS 1.2, it has been included in the\npublic API as part of preparations to support the new protocol version.\n\nIf you initialize ``anjay_dm_oi_attributes_t`` or ``anjay_dm_r_attributes_t``\nobjects manually, you may need to initialize the new ``con`` field as well,\nsince the empty ``ANJAY_DM_CON_ATTR_NONE`` value is **NOT** the default\nzero-initialized value.\n\nAs more new attributes may be added in future versions of Anjay, it is\nrecommended to initialize such structures with ``ANJAY_DM_OI_ATTRIBUTES_EMPTY``\nor ``ANJAY_DM_R_ATTRIBUTES_EMPTY`` constants, and then fill in the attributes\nyou actually intend to set.\n\nDefault (D)TLS version\n^^^^^^^^^^^^^^^^^^^^^^\n\nWhen the `anjay_configuration_t::dtls_version\n<../api/structanjay__configuration.html#ab32477e7370a36e02db5b7e7ccbdd89d>`_\nfield is set to ``AVS_NET_SSL_VERSION_DEFAULT`` (which includes the case of\nzero-initialization), Anjay 3.0 and earlier automatically mapped this setting to\n``AVS_NET_SSL_VERSION_TLSv1_2`` to ensure that (D)TLS 1.2 is used as mandated by\nthe LwM2M specification.\n\nThis mapping has been removed in Anjay 3.1, which means that the default version\nconfiguration of the underlying (D)TLS library will be used. This has been done\nto automatically allow the use of newer protocols and deprecate old versions\nwhen the backend library is updated, without the need to update Anjay code.\nHowever, depending on the (D)TLS backend library used, this may lead to (D)TLS\n1.1 or earlier being used if the server does not properly negotiate a higher\nversion. Please explicitly set ``dtls_version`` to\n``AVS_NET_SSL_VERSION_TLSv1_2`` if you want to disallow this.\n\nPlease note that Mbed TLS 3.0 has dropped support for TLS 1.1 and earlier, so\nthis change will not affect behavior with that library.\n\n\nAddition of resource_instance_remove handler\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nLwM2M TS 1.2 introduced possibility to delete a Resource Instance, so\none additional data model handler had to be added.\n\nNew ``resource_instance_remove`` handler (and its associated\n``anjay_dm_resource_instance_remove_t`` function type) has been introduced. It\nis analogous to the ``instance_remove`` handler; its job is to remove a specific\nResource Instance from a multiple-instance Resource.\n\nIts implementation is required in objects that include at least one writeable\nmultiple-instance Resource, if the client application aims for compliance with\nLwM2M 1.2.\n\nLimiting the maximum Hold Off Time for Bootstrap\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nA limit has been introduced on the maximum Hold Off Time (LwM2M Security Object,\nResource ID: 11) to avoid excessive delays when initiating the Bootstrap process.\nPreviously, the upper bound was implicitly 120 seconds, but it is now explicitly\nlimited to **20 seconds by default**.\n\nThis behavior can be customized at build time using the ``MAX_HOLDOFF_TIME``\nmacro.\n\nChanges in avs_coap\n-------------------\n\nChanged flow of cancelling observations in case of errors\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nCoAP observations are implicitly cancelled if a notification bearing a 4.xx or\n5.xx error code is delivered.\n\nIn Anjay 3.4.x and earlier, this cancellation (which involves calling the\n``avs_coap_observe_cancel_handler_t`` callback) was performed *before* calling\nthe ``avs_coap_delivery_status_handler_t`` callback for the specific\nnotification. Since Anjay 3.5.0, this order is reversed, so any code that relies\non this logic may break.\n\nThis change is only relevant if you are using ``avs_coap`` APIs directly (e.g.\nwhen communicating over raw CoAP protocol) and in case of notifications intended\nto be delivered as confirmable. The LwM2M Observe/Notify implementation in Anjay\nhas been updated accordingly.\n\nChanges in avs_commons\n----------------------\n\nMove of public-key cryptography APIs from avs_net to avs_crypto\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nPublic key cryptography APIs, previously defined in\n``avsystem/commons/avs_socket.h``, have been moved into a new header called\n``avsystem/commons/avs_crypto_pki.h``.\n\nAdditionally, client-side and server-side certificate info structures are no\nlonger separate, and both have been merged into a single type.\n\nHere is a summary of renames:\n\n+-----------------------------------------------+-----------------------------------------------------+\n| Old symbol name                               | New symbol name                                     |\n+===============================================+=====================================================+\n| | ``avs_net_trusted_cert_info_t``             | ``avs_crypto_certificate_chain_info_t``             |\n| | ``avs_net_client_cert_info_t``              |                                                     |\n+-----------------------------------------------+-----------------------------------------------------+\n| ``avs_net_client_key_info_t``                 | ``avs_crypto_private_key_info_t``                   |\n+-----------------------------------------------+-----------------------------------------------------+\n| ``avs_net_security_info_union_t``             | ``avs_crypto_security_info_union_t``                |\n+-----------------------------------------------+-----------------------------------------------------+\n| | ``avs_net_trusted_cert_info_from_buffer()`` | ``avs_crypto_certificate_chain_info_from_buffer()`` |\n| | ``avs_net_client_cert_info_from_buffer()``  |                                                     |\n+-----------------------------------------------+-----------------------------------------------------+\n| | ``avs_net_trusted_cert_info_from_file()``   | ``avs_crypto_certificate_chain_info_from_file()``   |\n| | ``avs_net_client_cert_info_from_file()``    |                                                     |\n+-----------------------------------------------+-----------------------------------------------------+\n| ``avs_net_client_key_info_from_buffer()``     | ``avs_crypto_private_key_info_from_buffer()``       |\n+-----------------------------------------------+-----------------------------------------------------+\n| ``avs_net_client_key_info_from_file()``       | ``avs_crypto_private_key_info_from_file()``         |\n+-----------------------------------------------+-----------------------------------------------------+\n| ``avs_net_trusted_cert_info_from_path()``     | ``avs_crypto_certificate_chain_info_from_path()``   |\n+-----------------------------------------------+-----------------------------------------------------+\n\nRenamed CMake configuration options\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nThe ``WITH_X509`` CMake configuration option has been removed; the new\nequivalent option is ``WITH_PKI``. Please update CMake invocations in your\nconfiguration scripts.\n\nRenamed configuration macros in avs_commons_config.h\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nThe following configuration macros in ``avs_commons_config.h`` has been renamed.\nYou may need to update your configuration files if you are not using CMake, or\nyour preprocessor directives if you check these macros in your code:\n\n+-----------------------------------+------------------------------------------+\n| Old macro name                    | New macro name                           |\n+===================================+==========================================+\n| ``AVS_COMMONS_NET_WITH_PSK``      | ``AVS_COMMONS_WITH_AVS_CRYPTO_PSK``      |\n+-----------------------------------+------------------------------------------+\n| ``AVS_COMMONS_NET_WITH_X509``     | ``AVS_COMMONS_WITH_AVS_CRYPTO_PKI``      |\n+-----------------------------------+------------------------------------------+\n| ``AVS_COMMONS_NET_WITH_VALGRIND`` | ``AVS_COMMONS_WITH_AVS_CRYPTO_VALGRIND`` |\n+-----------------------------------+------------------------------------------+\n\nIntroduction of new socket option\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\navs_commons 4.10.1 bundled with Anjay 2.15.1 adds a new socket option key:\n``AVS_NET_SOCKET_HAS_BUFFERED_DATA``. This is used to make sure that when\ncontrol is returned to the event loop, the ``poll()`` call will not stall\nwaiting for new data that in reality has been already buffered and could be\nretrieved using the avs_commons APIs.\n\nThis is usually meaningful for (D)TLS connections, but for almost all simple\nunencrypted socket implementations, this should always return ``false``.\n\nThis was previously achieved by always trying to receive more packets with\ntimeout set to zero. However, it has been determined that such logic could lead\nto heavy blocking of the event loop in case communication with the network stack\nis relatively slow, e.g. on devices which implement TCP/IP sockets through modem\nAT commands.\n\nIf you maintain your own socket integration layer or (D)TLS integration layer,\nit is recommended that you add support for this option. This is not, however, a\nbreaking change - if the option is not supported, the library will continue to\nuse the old behavior.\n\nRefactor of PSK credential handling\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n``avs_net_psk_info_t`` structure has been changed to use new types based on\n``avs_crypto_security_info_union_t`` instead of raw buffers. This change also\naffects ``avs_net_security_info_t`` structure which contains the former.\n\n* **Old API:**\n  ::\n\n      /**\n       * A PSK/identity pair with borrowed pointers. avs_commons will never attempt\n       * to modify these values.\n       */\n      typedef struct {\n          const void *psk;\n          size_t psk_size;\n          const void *identity;\n          size_t identity_size;\n      } avs_net_psk_info_t;\n\n      // ...\n\n      typedef struct {\n          avs_net_security_mode_t mode;\n          union {\n              avs_net_psk_info_t psk;\n              avs_net_certificate_info_t cert;\n          } data;\n      } avs_net_security_info_t;\n\n      avs_net_security_info_t avs_net_security_info_from_psk(avs_net_psk_info_t psk);\n\n* **New API:**\n\n  .. snippet-source:: deps/avs_commons/include_public/avsystem/commons/avs_crypto_psk.h\n\n      typedef struct {\n          avs_crypto_security_info_union_t desc;\n      } avs_crypto_psk_identity_info_t;\n\n      // ...\n\n      avs_crypto_psk_identity_info_t\n      avs_crypto_psk_identity_info_from_buffer(const void *buffer,\n                                               size_t buffer_size);\n\n      // ...\n\n      typedef struct {\n          avs_crypto_security_info_union_t desc;\n      } avs_crypto_psk_key_info_t;\n\n      // ...\n\n      avs_crypto_psk_key_info_t\n      avs_crypto_psk_key_info_from_buffer(const void *buffer, size_t buffer_size);\n\n  .. snippet-source:: deps/avs_commons/include_public/avsystem/commons/avs_socket.h\n\n      /**\n       * A PSK/identity pair. avs_commons will never attempt to modify these values.\n       */\n      typedef struct {\n          avs_crypto_psk_key_info_t key;\n          avs_crypto_psk_identity_info_t identity;\n      } avs_net_psk_info_t;\n\n      // ...\n\n      typedef struct {\n          avs_net_security_mode_t mode;\n          union {\n              avs_net_psk_info_t psk;\n              avs_net_certificate_info_t cert;\n          } data;\n      } avs_net_security_info_t;\n\n      avs_net_security_info_t\n      avs_net_security_info_from_psk(avs_net_psk_info_t psk);\n\nThis change is breaking for code that accesses the ``data.psk`` field\nof ``avs_net_security_info_t`` directly.\n\nSeparation of avs_url module\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nURL handling routines, previously a part of ``avs_net``, are now a separate\ncomponent of ``avs_commons``. The specific consequences of that may vary\ndepending on your build process, e.g.:\n\n* You will need to add ``#define AVS_COMMONS_WITH_AVS_URL`` to your\n  ``avs_commons_config.h`` if you specify it manually\n* You may need to add ``-lavs_url`` to your link command if you're using\n  ``avs_commons`` that has been manually compiled separately using CMake\n\nRefactor of avs_net_validate_ip_address() and avs_net_local_address_for_target_host()\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n``avs_net_validate_ip_address()`` is now no longer used by Anjay or\n``avs_commons``. It was previously necessary to implement it as part of the\nsocket implementation. This is no longer required. For compatibility, the\nfunction has been reimplemented as a ``static inline`` function that wraps\n``avs_net_addrinfo_*()`` APIs. Please remove your version of\n``avs_net_validate_ip_address()`` from your socket implementation if you have\none, as having two alternative variants may lead to conflicts.\n\nSince Anjay 2.9 and ``avs_commons`` 4.6,\n``avs_net_local_address_for_target_host()`` underwent a similar refactor. It was\npreviously a function to be optionally implemented as part of the socket\nimplementation, but now it is a ``static inline`` function that wraps\n``avs_net_socket_*()`` APIs. Please remove your version of\n``avs_net_local_address_for_target_host()`` from your socket implementation if\nyou have one, as having two alternative variants may lead to conflicts.\n\nRefactor of time handling in avs_sched and avs_coap\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nIt is now enforced more strictly that time-based events shall happen when the\nclock reaches *at least* the expected value. Previously, the tasks scheduled via\navs_sched were executed only when the clock reached a value *later* than the\nscheduled job execution time.\n\nThis change will have no impact on your code if your platform has enough clock\nresolution so that two subsequent calls to ``avs_time_real_now()`` or\n``avs_time_monotonic_now()`` will *always* return different values. As a rule of\nthumb, this should be the case if your clock has a resolution no worse than\nabout 1-2 orders of magnitude smaller than the CPU clock. For example, for a\n100 MHz CPU, a clock resolution of around 100-1000 ns (i.e., 1-10 MHz) should be\nsufficient, depending on the specific architecture.\n\nIf your clock has a lower resolution, you may observe the following changes:\n\n* ``anjay_sched_run()`` is now properly guaranteed to execute at least one job\n  if the time reported by ``anjay_sched_time_to_next()`` passed. Previously this\n  could require waiting for another change of the numerical value of the clock,\n  which could cause undesirable active waiting in the event loop. This is the\n  motivating factor in introducing these changes.\n* Jobs scheduled using ``AVS_SCHED_NOW()`` during an execution of\n  ``anjay_sched_run()`` before the numerical value of the clock changes, *will*\n  be executed during the same run. The previous behavior more strictly enforced\n  the policy to not execute such jobs in the same run.\n\nIf you are scheduling custom jobs through the avs_sched module, you may want or\nneed to modify their logic accordingly to accommodate for these changes. In most\ntypical use cases, no changes are expected to be necessary.\n\nRemoval of avs_unit_memstream\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n``avs_unit_memstream`` was a specific implementation of ``avs_stream_t`` within\nthe avs_unit module that implemented a simple FIFO stream in a fixed-size memory\narea.\n\nThis feature has been removed. Instead, you can use an\n``avs_stream_inbuf``/``avs_stream_outbuf`` pair, or an ``avs_stream_membuf``\nobject.\n\nHandling of Trust Store for certain Certificate Usages settings\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\navs_commons 5.5.0 that is used by Anjay 3.11.0 does not pass the configured Trust\nStore (whether manually provided or acquired through the EST process) to Mbed TLS\nwhen the certificate usage is set to DANE-TA or DANE-EE, if the Data Model already\ncontains a Server certificate intended for verifying the Server during a secure\nconnection.\n\nIf the Server certificate is missing from the Data Model, Anjay falls back to\nPKIX verification, provided that a Trust Store is available, even when the\ncertificate usage is set to DANE-TA or DANE-EE.\n\nFor more information on how Anjay manages the Trust Store and the Certificate\nUsage resource, see\n:doc:`Certificate Usage <../AdvancedTopics/AT-CertificateUsage>`.\n\nPython environment isolation\n----------------------------\n\nAll Python-based tools (e.g. integration tests) must be executed within a\nPython virtual environment. See :doc:`/Tools/VirtualEnvironments` for more\ninformation.\n"
  },
  {
    "path": "doc/sphinx/source/Migrating/MigratingFromAnjay26.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nMigrating from Anjay 2.5.x or 2.6.x\n===================================\n\n.. contents:: :local:\n\n.. highlight:: c\n\nIntroduction\n------------\n\nWhile most changes since Anjay 2.6 are minor, some of them (changes to commonly\nused APIs such as Attribute Storage and offline mode control) are breaking.\nAdditionally, ``avs_commons`` cryptography support libraries have undergone a\nsignificant redesign and there are some changes which might prove to be breaking\nif old APIs have been used directly.\n\nAdditional slight updates might be necessary if you are using any alternative\nbuild system instead of CMake to compile your project, of if you maintain your\nown implementation of the socket layer.\n\nChange to minimum CMake version\n-------------------------------\n\nDeclared minimum CMake version necessary for CMake-based compilation, as well as\nfor importing the installed library through ``find_package()``, is now 3.16. If\nyou're using some Linux distribution that only has an older version in its\nrepositories (notably, Ubuntu 16.04), we recommend using one of the following\ninstall methods instead:\n\n* `Kitware APT Repository for Debian and Ubuntu <https://apt.kitware.com/>`_\n* `Snap Store <https://snapcraft.io/cmake>`_ (``snap install cmake``)\n* `Python Package Index <https://pypi.org/project/cmake/>`_\n  (``pip install cmake``)\n\nThis change does not affect users who compile the library using some alternative\napproach, without using the provided CMake scripts.\n\nChanges in Anjay proper\n-----------------------\n\nChange of security configuration lifetime\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n* **Getter function for retrieving security information from data model**\n\n  * **Old API:**\n    ::\n\n        anjay_security_config_t *anjay_security_config_from_dm(anjay_t *anjay,\n                                                               const char *uri);\n  * **New API:**\n\n    .. snippet-source:: include_public/anjay/core.h\n\n        int anjay_security_config_from_dm(anjay_t *anjay,\n                                          anjay_security_config_t *out_config,\n                                          const char *uri);\n\n  * The security configuration is now returned through an output argument with\n    any necessary internal buffers cached inside the Anjay object instead of\n    using heap allocation. Please refer to the Doxygen-based documentation of\n    this function for details.\n\n    Due to the change in lifetime requirements, no compatibility variant is\n    provided.\n\nRefactor of the Attribute Storage module\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nThe Attribute Storage feature is no longer a standalone module and has been\nmoved to the library core. From the user perspective, this has the following\nconsequences:\n\n* Explicit installation of this module in runtime is no longer necessary. The\n  ``anjay_attr_storage_install()`` method has been removed.\n* The ``ANJAY_WITH_MODULE_ATTR_STORAGE`` configuration macro in\n  ``anjay_config.h`` has been renamed to ``ANJAY_WITH_ATTR_STORAGE``.\n* The ``WITH_MODULE_attr_storage`` CMake option (equivalent to the macro\n  mentioned above) has been renamed to ``WITH_ATTR_STORAGE``.\n\nAdditionally, the behavior of ``anjay_attr_storage_restore()`` has been\nchanged - from now on, this function fails if supplied source stream is\ninvalid and the Attribute Storage remains untouched. This change makes the\nfunction consistent with other ``anjay_*_restore()`` APIs.\n\nRefactor of offline mode control API\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nSince Anjay 2.4, offline mode is configurable independently per every\ntransport. Below is a list of removed functions and counterparts that should\nbe used:\n\n+--------------------------------+------------------------------------------+\n| Removed function               | Counterpart                              |\n+--------------------------------+------------------------------------------+\n| ``anjay_is_offline()``         | ``anjay_transport_is_offline()``         |\n+--------------------------------+------------------------------------------+\n| ``anjay_enter_offline()``      | ``anjay_transport_enter_offline()``      |\n+--------------------------------+------------------------------------------+\n| ``anjay_exit_offline()``       | ``anjay_transport_exit_offline()``       |\n+--------------------------------+------------------------------------------+\n| ``anjay_schedule_reconnect()`` | ``anjay_transport_schedule_reconnect()`` |\n+--------------------------------+------------------------------------------+\n\nNew functions should be called with ``transport_set`` argument set to\n``ANJAY_TRANSPORT_SET_ALL`` to achieve the same behavior.\n\nAddition of the con attribute to public API\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nThe ``con`` attribute, enabled via the ``ANJAY_WITH_CON_ATTR`` compile-time\noption, has been previously supported as a custom extension. Since an identical\nflag has been standardized as part of LwM2M TS 1.2, it has been included in the\npublic API as part of preparations to support the new protocol version.\n\nIf you initialize ``anjay_dm_oi_attributes_t`` or ``anjay_dm_r_attributes_t``\nobjects manually, you may need to initialize the new ``con`` field as well,\nsince the empty ``ANJAY_DM_CON_ATTR_NONE`` value is **NOT** the default\nzero-initialized value.\n\nAs more new attributes may be added in future versions of Anjay, it is\nrecommended to initialize such structures with ``ANJAY_DM_OI_ATTRIBUTES_EMPTY``\nor ``ANJAY_DM_R_ATTRIBUTES_EMPTY`` constants, and then fill in the attributes\nyou actually intend to set.\n\nDefault (D)TLS version\n^^^^^^^^^^^^^^^^^^^^^^\n\nWhen the `anjay_configuration_t::dtls_version\n<../api/structanjay__configuration.html#ab32477e7370a36e02db5b7e7ccbdd89d>`_\nfield is set to ``AVS_NET_SSL_VERSION_DEFAULT`` (which includes the case of\nzero-initialization), Anjay 3.0 and earlier automatically mapped this setting to\n``AVS_NET_SSL_VERSION_TLSv1_2`` to ensure that (D)TLS 1.2 is used as mandated by\nthe LwM2M specification.\n\nThis mapping has been removed in Anjay 3.1, which means that the default version\nconfiguration of the underlying (D)TLS library will be used. This has been done\nto automatically allow the use of newer protocols and deprecate old versions\nwhen the backend library is updated, without the need to update Anjay code.\nHowever, depending on the (D)TLS backend library used, this may lead to (D)TLS\n1.1 or earlier being used if the server does not properly negotiate a higher\nversion. Please explicitly set ``dtls_version`` to\n``AVS_NET_SSL_VERSION_TLSv1_2`` if you want to disallow this.\n\nPlease note that Mbed TLS 3.0 has dropped support for TLS 1.1 and earlier, so\nthis change will not affect behavior with that library.\n\n\nAddition of resource_instance_remove handler\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nLwM2M TS 1.2 introduced possibility to delete a Resource Instance, so\none additional data model handler had to be added.\n\nNew ``resource_instance_remove`` handler (and its associated\n``anjay_dm_resource_instance_remove_t`` function type) has been introduced. It\nis analogous to the ``instance_remove`` handler; its job is to remove a specific\nResource Instance from a multiple-instance Resource.\n\nIts implementation is required in objects that include at least one writeable\nmultiple-instance Resource, if the client application aims for compliance with\nLwM2M 1.2.\n\nLimiting the maximum Hold Off Time for Bootstrap\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nA limit has been introduced on the maximum Hold Off Time (LwM2M Security Object,\nResource ID: 11) to avoid excessive delays when initiating the Bootstrap process.\nPreviously, the upper bound was implicitly 120 seconds, but it is now explicitly\nlimited to **20 seconds by default**.\n\nThis behavior can be customized at build time using the ``MAX_HOLDOFF_TIME``\nmacro.\n\nChanges in avs_coap\n-------------------\n\nChanged flow of cancelling observations in case of errors\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nCoAP observations are implicitly cancelled if a notification bearing a 4.xx or\n5.xx error code is delivered.\n\nIn Anjay 3.4.x and earlier, this cancellation (which involves calling the\n``avs_coap_observe_cancel_handler_t`` callback) was performed *before* calling\nthe ``avs_coap_delivery_status_handler_t`` callback for the specific\nnotification. Since Anjay 3.5.0, this order is reversed, so any code that relies\non this logic may break.\n\nThis change is only relevant if you are using ``avs_coap`` APIs directly (e.g.\nwhen communicating over raw CoAP protocol) and in case of notifications intended\nto be delivered as confirmable. The LwM2M Observe/Notify implementation in Anjay\nhas been updated accordingly.\n\nChanges in avs_commons\n----------------------\n\nChanges in public-key cryptography APIs\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nClient-side and server-side certificate info structures are no longer separate,\nand both have been merged into a single type. Additionally, the client key info\nstructure have also been renamed for consistency.\n\nHere is a summary of renames:\n\n+--------------------------------------------------+-------------------------------------------------------+\n| Old symbol name                                  | New Symbol name                                       |\n+==================================================+=======================================================+\n| | ``avs_crypto_trusted_cert_info_t``             | ``avs_crypto_certificate_chain_info_t``               |\n| | ``avs_crypto_client_cert_info_t``              |                                                       |\n+--------------------------------------------------+-------------------------------------------------------+\n| ``avs_crypto_client_key_info_t``                 | ``avs_crypto_private_key_info_t``                     |\n+--------------------------------------------------+-------------------------------------------------------+\n| | ``avs_crypto_trusted_cert_info_from_file()``   | ``avs_crypto_certificate_chain_info_from_file()``     |\n| | ``avs_crypto_client_cert_info_from_file()``    |                                                       |\n+--------------------------------------------------+-------------------------------------------------------+\n| ``avs_crypto_trusted_cert_info_from_path()``     | ``avs_crypto_certificate_chain_info_from_path()``     |\n+--------------------------------------------------+-------------------------------------------------------+\n| | ``avs_crypto_trusted_cert_info_from_buffer()`` | ``avs_crypto_certificate_chain_info_from_buffer()``   |\n| | ``avs_crypto_client_cert_info_from_buffer()``  |                                                       |\n+--------------------------------------------------+-------------------------------------------------------+\n| ``avs_crypto_trusted_cert_info_from_array()``    | ``avs_crypto_certificate_chain_info_from_array()``    |\n+--------------------------------------------------+-------------------------------------------------------+\n| ``avs_crypto_trusted_cert_info_copy_as_array()`` | ``avs_crypto_certificate_chain_info_copy_as_array()`` |\n+--------------------------------------------------+-------------------------------------------------------+\n| ``avs_crypto_trusted_cert_info_from_list()``     | ``avs_crypto_certificate_chain_info_from_list()``     |\n+--------------------------------------------------+-------------------------------------------------------+\n| ``avs_crypto_trusted_cert_info_copy_as_list()``  | ``avs_crypto_certificate_chain_info_copy_as_list()``  |\n+--------------------------------------------------+-------------------------------------------------------+\n| ``avs_crypto_client_key_info_from_file()``       | ``avs_crypto_private_key_info_from_file()``           |\n+--------------------------------------------------+-------------------------------------------------------+\n| ``avs_crypto_client_key_info_from_buffer()``     | ``avs_crypto_private_key_info_from_buffer()``         |\n+--------------------------------------------------+-------------------------------------------------------+\n| ``avs_crypto_client_cert_expiration_date()``     | ``avs_crypto_certificate_expiration_date()``          |\n+--------------------------------------------------+-------------------------------------------------------+\n\nRenamed configuration macro in avs_commons_config.h\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nThe ``AVS_COMMONS_NET_WITH_PSK`` configuration macro in ``avs_commons_config.h``\nhas been renamed to ``AVS_COMMONS_WITH_AVS_CRYPTO_PSK``.\n\nYou may need to update your configuration files if you are not using CMake, or\nyour preprocessor directives if you check this macro in your code.\n\nIntroduction of new socket option\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\navs_commons 4.10.1 bundled with Anjay 2.15.1 adds a new socket option key:\n``AVS_NET_SOCKET_HAS_BUFFERED_DATA``. This is used to make sure that when\ncontrol is returned to the event loop, the ``poll()`` call will not stall\nwaiting for new data that in reality has been already buffered and could be\nretrieved using the avs_commons APIs.\n\nThis is usually meaningful for (D)TLS connections, but for almost all simple\nunencrypted socket implementations, this should always return ``false``.\n\nThis was previously achieved by always trying to receive more packets with\ntimeout set to zero. However, it has been determined that such logic could lead\nto heavy blocking of the event loop in case communication with the network stack\nis relatively slow, e.g. on devices which implement TCP/IP sockets through modem\nAT commands.\n\nIf you maintain your own socket integration layer or (D)TLS integration layer,\nit is recommended that you add support for this option. This is not, however, a\nbreaking change - if the option is not supported, the library will continue to\nuse the old behavior.\n\nRefactor of PSK credential handling\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n``avs_net_psk_info_t`` structure has been changed to use new types based on\n``avs_crypto_security_info_union_t`` instead of raw buffers. This change also\naffects ``avs_net_security_info_t`` structure which contains the former.\n\n* **Old API:**\n  ::\n\n      /**\n       * A PSK/identity pair with borrowed pointers. avs_commons will never attempt\n       * to modify these values.\n       */\n      typedef struct {\n          const void *psk;\n          size_t psk_size;\n          const void *identity;\n          size_t identity_size;\n      } avs_net_psk_info_t;\n\n      // ...\n\n      typedef struct {\n          avs_net_security_mode_t mode;\n          union {\n              avs_net_psk_info_t psk;\n              avs_net_certificate_info_t cert;\n          } data;\n      } avs_net_security_info_t;\n\n      avs_net_security_info_t avs_net_security_info_from_psk(avs_net_psk_info_t psk);\n\n* **New API:**\n\n  .. snippet-source:: deps/avs_commons/include_public/avsystem/commons/avs_crypto_psk.h\n\n      typedef struct {\n          avs_crypto_security_info_union_t desc;\n      } avs_crypto_psk_identity_info_t;\n\n      // ...\n\n      avs_crypto_psk_identity_info_t\n      avs_crypto_psk_identity_info_from_buffer(const void *buffer,\n                                               size_t buffer_size);\n\n      // ...\n\n      typedef struct {\n          avs_crypto_security_info_union_t desc;\n      } avs_crypto_psk_key_info_t;\n\n      // ...\n\n      avs_crypto_psk_key_info_t\n      avs_crypto_psk_key_info_from_buffer(const void *buffer, size_t buffer_size);\n\n  .. snippet-source:: deps/avs_commons/include_public/avsystem/commons/avs_socket.h\n\n      /**\n       * A PSK/identity pair. avs_commons will never attempt to modify these values.\n       */\n      typedef struct {\n          avs_crypto_psk_key_info_t key;\n          avs_crypto_psk_identity_info_t identity;\n      } avs_net_psk_info_t;\n\n      // ...\n\n      typedef struct {\n          avs_net_security_mode_t mode;\n          union {\n              avs_net_psk_info_t psk;\n              avs_net_certificate_info_t cert;\n          } data;\n      } avs_net_security_info_t;\n\n      avs_net_security_info_t\n      avs_net_security_info_from_psk(avs_net_psk_info_t psk);\n\nThis change is breaking for code that accesses the ``data.psk`` field\nof ``avs_net_security_info_t`` directly.\n\nSeparation of avs_url module\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nURL handling routines, previously a part of ``avs_net``, are now a separate\ncomponent of ``avs_commons``. The specific consequences of that may vary\ndepending on your build process, e.g.:\n\n* You will need to add ``#define AVS_COMMONS_WITH_AVS_URL`` to your\n  ``avs_commons_config.h`` if you specify it manually\n* You may need to add ``-lavs_url`` to your link command if you're using\n  ``avs_commons`` that has been manually compiled separately using CMake\n\nRefactor of avs_net_validate_ip_address() and avs_net_local_address_for_target_host()\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n``avs_net_validate_ip_address()`` is now no longer used by Anjay or\n``avs_commons``. It was previously necessary to implement it as part of the\nsocket implementation. This is no longer required. For compatibility, the\nfunction has been reimplemented as a ``static inline`` function that wraps\n``avs_net_addrinfo_*()`` APIs. Please remove your version of\n``avs_net_validate_ip_address()`` from your socket implementation if you have\none, as having two alternative variants may lead to conflicts.\n\nSince Anjay 2.9 and ``avs_commons`` 4.6,\n``avs_net_local_address_for_target_host()`` underwent a similar refactor. It was\npreviously a function to be optionally implemented as part of the socket\nimplementation, but now it is a ``static inline`` function that wraps\n``avs_net_socket_*()`` APIs. Please remove your version of\n``avs_net_local_address_for_target_host()`` from your socket implementation if\nyou have one, as having two alternative variants may lead to conflicts.\n\nRefactor of time handling in avs_sched and avs_coap\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nIt is now enforced more strictly that time-based events shall happen when the\nclock reaches *at least* the expected value. Previously, the tasks scheduled via\navs_sched were executed only when the clock reached a value *later* than the\nscheduled job execution time.\n\nThis change will have no impact on your code if your platform has enough clock\nresolution so that two subsequent calls to ``avs_time_real_now()`` or\n``avs_time_monotonic_now()`` will *always* return different values. As a rule of\nthumb, this should be the case if your clock has a resolution no worse than\nabout 1-2 orders of magnitude smaller than the CPU clock. For example, for a\n100 MHz CPU, a clock resolution of around 100-1000 ns (i.e., 1-10 MHz) should be\nsufficient, depending on the specific architecture.\n\nIf your clock has a lower resolution, you may observe the following changes:\n\n* ``anjay_sched_run()`` is now properly guaranteed to execute at least one job\n  if the time reported by ``anjay_sched_time_to_next()`` passed. Previously this\n  could require waiting for another change of the numerical value of the clock,\n  which could cause undesirable active waiting in the event loop. This is the\n  motivating factor in introducing these changes.\n* Jobs scheduled using ``AVS_SCHED_NOW()`` during an execution of\n  ``anjay_sched_run()`` before the numerical value of the clock changes, *will*\n  be executed during the same run. The previous behavior more strictly enforced\n  the policy to not execute such jobs in the same run.\n\nIf you are scheduling custom jobs through the avs_sched module, you may want or\nneed to modify their logic accordingly to accommodate for these changes. In most\ntypical use cases, no changes are expected to be necessary.\n\nRemoval of avs_unit_memstream\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n``avs_unit_memstream`` was a specific implementation of ``avs_stream_t`` within\nthe avs_unit module that implemented a simple FIFO stream in a fixed-size memory\narea.\n\nThis feature has been removed. Instead, you can use an\n``avs_stream_inbuf``/``avs_stream_outbuf`` pair, or an ``avs_stream_membuf``\nobject.\n\nHandling of Trust Store for certain Certificate Usages settings\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\navs_commons 5.5.0 that is used by Anjay 3.11.0 does not pass the configured Trust\nStore (whether manually provided or acquired through the EST process) to Mbed TLS\nwhen the certificate usage is set to DANE-TA or DANE-EE, if the Data Model already\ncontains a Server certificate intended for verifying the Server during a secure\nconnection.\n\nIf the Server certificate is missing from the Data Model, Anjay falls back to\nPKIX verification, provided that a Trust Store is available, even when the\ncertificate usage is set to DANE-TA or DANE-EE.\n\nFor more information on how Anjay manages the Trust Store and the Certificate\nUsage resource, see\n:doc:`Certificate Usage <../AdvancedTopics/AT-CertificateUsage>`.\n\nPython environment isolation\n----------------------------\n\nAll Python-based tools (e.g. integration tests) must be executed within a\nPython virtual environment. See :doc:`/Tools/VirtualEnvironments` for more\ninformation.\n"
  },
  {
    "path": "doc/sphinx/source/Migrating/MigratingFromAnjay27.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nMigrating from Anjay 2.7.x\n==========================\n\n.. contents:: :local:\n\n.. highlight:: c\n\nIntroduction\n------------\n\nWhile most changes since Anjay 2.7 are minor, some of them (changes to commonly\nused APIs such as Attribute Storage and offline mode control) are breaking.\nThere is a change to the way the ``con`` attribute is handled in the API.\nThe advancements in HSM integration in ``avs_commons`` also required some\nbreaking changes in they way compile-time configuration of that library is\nperformed. ``avs_commons`` 5.0 also includes refactoring of the APIs related\nto (D)TLS PSK credentials.\n\nYou may also need to adjust your code if you maintain your own socket\nintegration, or if it accesses the ``avs_net_security_info_t`` structure\ndirectly. The latter is especially likely if you maintain your own\nimplementation of the TLS layer.\n\nIf you are using an alternative build system, not utilizing the included CMake\nscripts, you might additionally need to make adjustments to your configuration\nheaders.\n\nChange to minimum CMake version\n-------------------------------\n\nDeclared minimum CMake version necessary for CMake-based compilation, as well as\nfor importing the installed library through ``find_package()``, is now 3.16. If\nyou're using some Linux distribution that only has an older version in its\nrepositories (notably, Ubuntu 16.04), we recommend using one of the following\ninstall methods instead:\n\n* `Kitware APT Repository for Debian and Ubuntu <https://apt.kitware.com/>`_\n* `Snap Store <https://snapcraft.io/cmake>`_ (``snap install cmake``)\n* `Python Package Index <https://pypi.org/project/cmake/>`_\n  (``pip install cmake``)\n\nThis change does not affect users who compile the library using some alternative\napproach, without using the provided CMake scripts.\n\nChanges in Anjay proper\n-----------------------\n\nRefactor of the Attribute Storage module\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nThe Attribute Storage feature is no longer a standalone module and has been\nmoved to the library core. From the user perspective, this has the following\nconsequences:\n\n* Explicit installation of this module in runtime is no longer necessary. The\n  ``anjay_attr_storage_install()`` method has been removed.\n* The ``ANJAY_WITH_MODULE_ATTR_STORAGE`` configuration macro in\n  ``anjay_config.h`` has been renamed to ``ANJAY_WITH_ATTR_STORAGE``.\n* The ``WITH_MODULE_attr_storage`` CMake option (equivalent to the macro\n  mentioned above) has been renamed to ``WITH_ATTR_STORAGE``.\n\nAdditionally, the behavior of ``anjay_attr_storage_restore()`` has been\nchanged - from now on, this function fails if supplied source stream is\ninvalid and the Attribute Storage remains untouched. This change makes the\nfunction consistent with other ``anjay_*_restore()`` APIs.\n\nRefactor of offline mode control API\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nSince Anjay 2.4, offline mode is configurable independently per every\ntransport. Below is a list of removed functions and counterparts that should\nbe used:\n\n+--------------------------------+------------------------------------------+\n| Removed function               | Counterpart                              |\n+--------------------------------+------------------------------------------+\n| ``anjay_is_offline()``         | ``anjay_transport_is_offline()``         |\n+--------------------------------+------------------------------------------+\n| ``anjay_enter_offline()``      | ``anjay_transport_enter_offline()``      |\n+--------------------------------+------------------------------------------+\n| ``anjay_exit_offline()``       | ``anjay_transport_exit_offline()``       |\n+--------------------------------+------------------------------------------+\n| ``anjay_schedule_reconnect()`` | ``anjay_transport_schedule_reconnect()`` |\n+--------------------------------+------------------------------------------+\n\nNew functions should be called with ``transport_set`` argument set to\n``ANJAY_TRANSPORT_SET_ALL`` to achieve the same behavior.\n\nAddition of the con attribute to public API\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nThe ``con`` attribute, enabled via the ``ANJAY_WITH_CON_ATTR`` compile-time\noption, has been previously supported as a custom extension. Since an identical\nflag has been standardized as part of LwM2M TS 1.2, it has been included in the\npublic API as part of preparations to support the new protocol version.\n\nIf you initialize ``anjay_dm_oi_attributes_t`` or ``anjay_dm_r_attributes_t``\nobjects manually, you may need to initialize the new ``con`` field as well,\nsince the empty ``ANJAY_DM_CON_ATTR_NONE`` value is **NOT** the default\nzero-initialized value.\n\nAs more new attributes may be added in future versions of Anjay, it is\nrecommended to initialize such structures with ``ANJAY_DM_OI_ATTRIBUTES_EMPTY``\nor ``ANJAY_DM_R_ATTRIBUTES_EMPTY`` constants, and then fill in the attributes\nyou actually intend to set.\n\nDefault (D)TLS version\n^^^^^^^^^^^^^^^^^^^^^^\n\nWhen the `anjay_configuration_t::dtls_version\n<../api/structanjay__configuration.html#ab32477e7370a36e02db5b7e7ccbdd89d>`_\nfield is set to ``AVS_NET_SSL_VERSION_DEFAULT`` (which includes the case of\nzero-initialization), Anjay 3.0 and earlier automatically mapped this setting to\n``AVS_NET_SSL_VERSION_TLSv1_2`` to ensure that (D)TLS 1.2 is used as mandated by\nthe LwM2M specification.\n\nThis mapping has been removed in Anjay 3.1, which means that the default version\nconfiguration of the underlying (D)TLS library will be used. This has been done\nto automatically allow the use of newer protocols and deprecate old versions\nwhen the backend library is updated, without the need to update Anjay code.\nHowever, depending on the (D)TLS backend library used, this may lead to (D)TLS\n1.1 or earlier being used if the server does not properly negotiate a higher\nversion. Please explicitly set ``dtls_version`` to\n``AVS_NET_SSL_VERSION_TLSv1_2`` if you want to disallow this.\n\nPlease note that Mbed TLS 3.0 has dropped support for TLS 1.1 and earlier, so\nthis change will not affect behavior with that library.\n\nConditional compilation for structured security credential support\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nThe ``anjay_ret_certificate_chain_info()`` and ``anjay_ret_private_key_info()``\nAPIs, as well as avs_crypto-based fields in ``anjay_security_instance_t``, have\nbeen put under a new conditional compilation flag,\n``ANJAY_WITH_SECURITY_STRUCTURED``.\n\nWhen using CMake, this flag is controlled with the ``WITH_SECURITY_STRUCTURED``\noption and enabled by default if available. Otherwise, it might need to be\nenabled by defining ``ANJAY_WITH_SECURITY_STRUCTURED`` in ``anjay_config.h``.\n\n\nAddition of resource_instance_remove handler\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nLwM2M TS 1.2 introduced possibility to delete a Resource Instance, so\none additional data model handler had to be added.\n\nNew ``resource_instance_remove`` handler (and its associated\n``anjay_dm_resource_instance_remove_t`` function type) has been introduced. It\nis analogous to the ``instance_remove`` handler; its job is to remove a specific\nResource Instance from a multiple-instance Resource.\n\nIts implementation is required in objects that include at least one writeable\nmultiple-instance Resource, if the client application aims for compliance with\nLwM2M 1.2.\n\nLimiting the maximum Hold Off Time for Bootstrap\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nA limit has been introduced on the maximum Hold Off Time (LwM2M Security Object,\nResource ID: 11) to avoid excessive delays when initiating the Bootstrap process.\nPreviously, the upper bound was implicitly 120 seconds, but it is now explicitly\nlimited to **20 seconds by default**.\n\nThis behavior can be customized at build time using the ``MAX_HOLDOFF_TIME``\nmacro.\n\nChanges in avs_coap\n-------------------\n\nChanged flow of cancelling observations in case of errors\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nCoAP observations are implicitly cancelled if a notification bearing a 4.xx or\n5.xx error code is delivered.\n\nIn Anjay 3.4.x and earlier, this cancellation (which involves calling the\n``avs_coap_observe_cancel_handler_t`` callback) was performed *before* calling\nthe ``avs_coap_delivery_status_handler_t`` callback for the specific\nnotification. Since Anjay 3.5.0, this order is reversed, so any code that relies\non this logic may break.\n\nThis change is only relevant if you are using ``avs_coap`` APIs directly (e.g.\nwhen communicating over raw CoAP protocol) and in case of notifications intended\nto be delivered as confirmable. The LwM2M Observe/Notify implementation in Anjay\nhas been updated accordingly.\n\nChanges in avs_commons\n----------------------\n\nSeparation of avs_url module\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nURL handling routines, previously a part of ``avs_net``, are now a separate\ncomponent of ``avs_commons``. The specific consequences of that may vary\ndepending on your build process, e.g.:\n\n* You will need to add ``#define AVS_COMMONS_WITH_AVS_URL`` to your\n  ``avs_commons_config.h`` if you specify it manually\n* You may need to add ``-lavs_url`` to your link command if you're using\n  ``avs_commons`` that has been manually compiled separately using CMake\n\nRenamed configuration macro in avs_commons_config.h\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nThe ``AVS_COMMONS_NET_WITH_PSK`` configuration macro in ``avs_commons_config.h``\nhas been renamed to ``AVS_COMMONS_WITH_AVS_CRYPTO_PSK``.\n\nYou may need to update your configuration files if you are not using CMake, or\nyour preprocessor directives if you check this macro in your code.\n\nIntroduction of new socket option\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\navs_commons 4.10.1 bundled with Anjay 2.15.1 adds a new socket option key:\n``AVS_NET_SOCKET_HAS_BUFFERED_DATA``. This is used to make sure that when\ncontrol is returned to the event loop, the ``poll()`` call will not stall\nwaiting for new data that in reality has been already buffered and could be\nretrieved using the avs_commons APIs.\n\nThis is usually meaningful for (D)TLS connections, but for almost all simple\nunencrypted socket implementations, this should always return ``false``.\n\nThis was previously achieved by always trying to receive more packets with\ntimeout set to zero. However, it has been determined that such logic could lead\nto heavy blocking of the event loop in case communication with the network stack\nis relatively slow, e.g. on devices which implement TCP/IP sockets through modem\nAT commands.\n\nIf you maintain your own socket integration layer or (D)TLS integration layer,\nit is recommended that you add support for this option. This is not, however, a\nbreaking change - if the option is not supported, the library will continue to\nuse the old behavior.\n\nRefactor of PSK credential handling\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n``avs_net_psk_info_t`` structure has been changed to use new types based on\n``avs_crypto_security_info_union_t`` instead of raw buffers. This change also\naffects ``avs_net_security_info_t`` structure which contains the former.\n\n* **Old API:**\n  ::\n\n      /**\n       * A PSK/identity pair with borrowed pointers. avs_commons will never attempt\n       * to modify these values.\n       */\n      typedef struct {\n          const void *psk;\n          size_t psk_size;\n          const void *identity;\n          size_t identity_size;\n      } avs_net_psk_info_t;\n\n      // ...\n\n      typedef struct {\n          avs_net_security_mode_t mode;\n          union {\n              avs_net_psk_info_t psk;\n              avs_net_certificate_info_t cert;\n          } data;\n      } avs_net_security_info_t;\n\n      avs_net_security_info_t avs_net_security_info_from_psk(avs_net_psk_info_t psk);\n\n* **New API:**\n\n  .. snippet-source:: deps/avs_commons/include_public/avsystem/commons/avs_crypto_psk.h\n\n      typedef struct {\n          avs_crypto_security_info_union_t desc;\n      } avs_crypto_psk_identity_info_t;\n\n      // ...\n\n      avs_crypto_psk_identity_info_t\n      avs_crypto_psk_identity_info_from_buffer(const void *buffer,\n                                               size_t buffer_size);\n\n      // ...\n\n      typedef struct {\n          avs_crypto_security_info_union_t desc;\n      } avs_crypto_psk_key_info_t;\n\n      // ...\n\n      avs_crypto_psk_key_info_t\n      avs_crypto_psk_key_info_from_buffer(const void *buffer, size_t buffer_size);\n\n  .. snippet-source:: deps/avs_commons/include_public/avsystem/commons/avs_socket.h\n\n      /**\n       * A PSK/identity pair. avs_commons will never attempt to modify these values.\n       */\n      typedef struct {\n          avs_crypto_psk_key_info_t key;\n          avs_crypto_psk_identity_info_t identity;\n      } avs_net_psk_info_t;\n\n      // ...\n\n      typedef struct {\n          avs_net_security_mode_t mode;\n          union {\n              avs_net_psk_info_t psk;\n              avs_net_certificate_info_t cert;\n          } data;\n      } avs_net_security_info_t;\n\n      avs_net_security_info_t\n      avs_net_security_info_from_psk(avs_net_psk_info_t psk);\n\nThis change is breaking for code that accesses the ``data.psk`` field\nof ``avs_net_security_info_t`` directly.\n\nRefactor of avs_net_validate_ip_address() and avs_net_local_address_for_target_host()\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n``avs_net_validate_ip_address()`` is now no longer used by Anjay or\n``avs_commons``. It was previously necessary to implement it as part of the\nsocket implementation. This is no longer required. For compatibility, the\nfunction has been reimplemented as a ``static inline`` function that wraps\n``avs_net_addrinfo_*()`` APIs. Please remove your version of\n``avs_net_validate_ip_address()`` from your socket implementation if you have\none, as having two alternative variants may lead to conflicts.\n\nSince Anjay 2.9 and ``avs_commons`` 4.6,\n``avs_net_local_address_for_target_host()`` underwent a similar refactor. It was\npreviously a function to be optionally implemented as part of the socket\nimplementation, but now it is a ``static inline`` function that wraps\n``avs_net_socket_*()`` APIs. Please remove your version of\n``avs_net_local_address_for_target_host()`` from your socket implementation if\nyou have one, as having two alternative variants may lead to conflicts.\n\nReorganization of HSM support\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n.. note::\n\n    Low-level HSM support is available in open-source ``avs_commons``, but\n    integration of these features with Anjay is only available as a commercial\n    feature.\n\nCoupling of the Hardware Security Module support in ``avs_commons`` has been\nloosened, making it possible to replace the reference implementation based on\n``libp11`` with a custom one.\n\n* New CMake configuration flag ``WITH_AVS_CRYPTO_PKI_ENGINE``, and its\n  corresponding configuration header macro\n  ``AVS_COMMONS_WITH_AVS_CRYPTO_PKI_ENGINE`` have been added.\n* Enabling the aforementioned flag is now a dependency for enabling\n  ``WITH_OPENSSL_PKCS11_ENGINE`` (CMake) /\n  ``AVS_COMMONS_WITH_OPENSSL_PKCS11_ENGINE`` (header)\n\nRefactor of time handling in avs_sched and avs_coap\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nIt is now enforced more strictly that time-based events shall happen when the\nclock reaches *at least* the expected value. Previously, the tasks scheduled via\navs_sched were executed only when the clock reached a value *later* than the\nscheduled job execution time.\n\nThis change will have no impact on your code if your platform has enough clock\nresolution so that two subsequent calls to ``avs_time_real_now()`` or\n``avs_time_monotonic_now()`` will *always* return different values. As a rule of\nthumb, this should be the case if your clock has a resolution no worse than\nabout 1-2 orders of magnitude smaller than the CPU clock. For example, for a\n100 MHz CPU, a clock resolution of around 100-1000 ns (i.e., 1-10 MHz) should be\nsufficient, depending on the specific architecture.\n\nIf your clock has a lower resolution, you may observe the following changes:\n\n* ``anjay_sched_run()`` is now properly guaranteed to execute at least one job\n  if the time reported by ``anjay_sched_time_to_next()`` passed. Previously this\n  could require waiting for another change of the numerical value of the clock,\n  which could cause undesirable active waiting in the event loop. This is the\n  motivating factor in introducing these changes.\n* Jobs scheduled using ``AVS_SCHED_NOW()`` during an execution of\n  ``anjay_sched_run()`` before the numerical value of the clock changes, *will*\n  be executed during the same run. The previous behavior more strictly enforced\n  the policy to not execute such jobs in the same run.\n\nIf you are scheduling custom jobs through the avs_sched module, you may want or\nneed to modify their logic accordingly to accommodate for these changes. In most\ntypical use cases, no changes are expected to be necessary.\n\nRemoval of avs_unit_memstream\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n``avs_unit_memstream`` was a specific implementation of ``avs_stream_t`` within\nthe avs_unit module that implemented a simple FIFO stream in a fixed-size memory\narea.\n\nThis feature has been removed. Instead, you can use an\n``avs_stream_inbuf``/``avs_stream_outbuf`` pair, or an ``avs_stream_membuf``\nobject.\n\nHandling of Trust Store for certain Certificate Usages settings\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\navs_commons 5.5.0 that is used by Anjay 3.11.0 does not pass the configured Trust\nStore (whether manually provided or acquired through the EST process) to Mbed TLS\nwhen the certificate usage is set to DANE-TA or DANE-EE, if the Data Model already\ncontains a Server certificate intended for verifying the Server during a secure\nconnection.\n\nIf the Server certificate is missing from the Data Model, Anjay falls back to\nPKIX verification, provided that a Trust Store is available, even when the\ncertificate usage is set to DANE-TA or DANE-EE.\n\nFor more information on how Anjay manages the Trust Store and the Certificate\nUsage resource, see\n:doc:`Certificate Usage <../AdvancedTopics/AT-CertificateUsage>`.\n\nPython environment isolation\n----------------------------\n\nAll Python-based tools (e.g. integration tests) must be executed within a\nPython virtual environment. See :doc:`/Tools/VirtualEnvironments` for more\ninformation.\n"
  },
  {
    "path": "doc/sphinx/source/Migrating/MigratingFromAnjay28.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nMigrating from Anjay 2.8.x\n==========================\n\n.. contents:: :local:\n\n.. highlight:: c\n\nIntroduction\n------------\n\nWhile most changes since Anjay 2.8 are minor, some of them (changes to commonly\nused APIs such as Attribute Storage and offline mode control) are breaking.\nThere is a change to the way the ``con`` attribute is handled in the API.\nAdditionally, the upgrade to ``avs_commons`` 5.0 includes refactoring of the\nAPIs related to (D)TLS PSK credentials and further refinements in the network\nintegration layer.\n\nYou may also need to adjust your code if you maintain your own socket\nintegration, or if it accesses the ``avs_net_security_info_t`` structure\ndirectly. The latter is especially likely if you maintain your own\nimplementation of the TLS layer.\n\nChange to minimum CMake version\n-------------------------------\n\nDeclared minimum CMake version necessary for CMake-based compilation, as well as\nfor importing the installed library through ``find_package()``, is now 3.16. If\nyou're using some Linux distribution that only has an older version in its\nrepositories (notably, Ubuntu 16.04), we recommend using one of the following\ninstall methods instead:\n\n* `Kitware APT Repository for Debian and Ubuntu <https://apt.kitware.com/>`_\n* `Snap Store <https://snapcraft.io/cmake>`_ (``snap install cmake``)\n* `Python Package Index <https://pypi.org/project/cmake/>`_\n  (``pip install cmake``)\n\nThis change does not affect users who compile the library using some alternative\napproach, without using the provided CMake scripts.\n\nChanges in Anjay proper\n-----------------------\n\nRefactor of the Attribute Storage module\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nThe Attribute Storage feature is no longer a standalone module and has been\nmoved to the library core. From the user perspective, this has the following\nconsequences:\n\n* Explicit installation of this module in runtime is no longer necessary. The\n  ``anjay_attr_storage_install()`` method has been removed.\n* The ``ANJAY_WITH_MODULE_ATTR_STORAGE`` configuration macro in\n  ``anjay_config.h`` has been renamed to ``ANJAY_WITH_ATTR_STORAGE``.\n* The ``WITH_MODULE_attr_storage`` CMake option (equivalent to the macro\n  mentioned above) has been renamed to ``WITH_ATTR_STORAGE``.\n\nAdditionally, the behavior of ``anjay_attr_storage_restore()`` has been\nchanged - from now on, this function fails if supplied source stream is\ninvalid and the Attribute Storage remains untouched. This change makes the\nfunction consistent with other ``anjay_*_restore()`` APIs.\n\nRefactor of offline mode control API\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nSince Anjay 2.4, offline mode is configurable independently per every\ntransport. Below is a list of removed functions and counterparts that should\nbe used:\n\n+--------------------------------+------------------------------------------+\n| Removed function               | Counterpart                              |\n+--------------------------------+------------------------------------------+\n| ``anjay_is_offline()``         | ``anjay_transport_is_offline()``         |\n+--------------------------------+------------------------------------------+\n| ``anjay_enter_offline()``      | ``anjay_transport_enter_offline()``      |\n+--------------------------------+------------------------------------------+\n| ``anjay_exit_offline()``       | ``anjay_transport_exit_offline()``       |\n+--------------------------------+------------------------------------------+\n| ``anjay_schedule_reconnect()`` | ``anjay_transport_schedule_reconnect()`` |\n+--------------------------------+------------------------------------------+\n\nNew functions should be called with ``transport_set`` argument set to\n``ANJAY_TRANSPORT_SET_ALL`` to achieve the same behavior.\n\nAddition of the con attribute to public API\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nThe ``con`` attribute, enabled via the ``ANJAY_WITH_CON_ATTR`` compile-time\noption, has been previously supported as a custom extension. Since an identical\nflag has been standardized as part of LwM2M TS 1.2, it has been included in the\npublic API as part of preparations to support the new protocol version.\n\nIf you initialize ``anjay_dm_oi_attributes_t`` or ``anjay_dm_r_attributes_t``\nobjects manually, you may need to initialize the new ``con`` field as well,\nsince the empty ``ANJAY_DM_CON_ATTR_NONE`` value is **NOT** the default\nzero-initialized value.\n\nAs more new attributes may be added in future versions of Anjay, it is\nrecommended to initialize such structures with ``ANJAY_DM_OI_ATTRIBUTES_EMPTY``\nor ``ANJAY_DM_R_ATTRIBUTES_EMPTY`` constants, and then fill in the attributes\nyou actually intend to set.\n\nDefault (D)TLS version\n^^^^^^^^^^^^^^^^^^^^^^\n\nWhen the `anjay_configuration_t::dtls_version\n<../api/structanjay__configuration.html#ab32477e7370a36e02db5b7e7ccbdd89d>`_\nfield is set to ``AVS_NET_SSL_VERSION_DEFAULT`` (which includes the case of\nzero-initialization), Anjay 3.0 and earlier automatically mapped this setting to\n``AVS_NET_SSL_VERSION_TLSv1_2`` to ensure that (D)TLS 1.2 is used as mandated by\nthe LwM2M specification.\n\nThis mapping has been removed in Anjay 3.1, which means that the default version\nconfiguration of the underlying (D)TLS library will be used. This has been done\nto automatically allow the use of newer protocols and deprecate old versions\nwhen the backend library is updated, without the need to update Anjay code.\nHowever, depending on the (D)TLS backend library used, this may lead to (D)TLS\n1.1 or earlier being used if the server does not properly negotiate a higher\nversion. Please explicitly set ``dtls_version`` to\n``AVS_NET_SSL_VERSION_TLSv1_2`` if you want to disallow this.\n\nPlease note that Mbed TLS 3.0 has dropped support for TLS 1.1 and earlier, so\nthis change will not affect behavior with that library.\n\nConditional compilation for structured security credential support\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nThe ``anjay_ret_certificate_chain_info()`` and ``anjay_ret_private_key_info()``\nAPIs, as well as avs_crypto-based fields in ``anjay_security_instance_t``, have\nbeen put under a new conditional compilation flag,\n``ANJAY_WITH_SECURITY_STRUCTURED``.\n\nWhen using CMake, this flag is controlled with the ``WITH_SECURITY_STRUCTURED``\noption and enabled by default if available. Otherwise, it might need to be\nenabled by defining ``ANJAY_WITH_SECURITY_STRUCTURED`` in ``anjay_config.h``.\n\n\n\nAddition of resource_instance_remove handler\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nLwM2M TS 1.2 introduced possibility to delete a Resource Instance, so\none additional data model handler had to be added.\n\nNew ``resource_instance_remove`` handler (and its associated\n``anjay_dm_resource_instance_remove_t`` function type) has been introduced. It\nis analogous to the ``instance_remove`` handler; its job is to remove a specific\nResource Instance from a multiple-instance Resource.\n\nIts implementation is required in objects that include at least one writeable\nmultiple-instance Resource, if the client application aims for compliance with\nLwM2M 1.2.\n\nLimiting the maximum Hold Off Time for Bootstrap\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nA limit has been introduced on the maximum Hold Off Time (LwM2M Security Object,\nResource ID: 11) to avoid excessive delays when initiating the Bootstrap process.\nPreviously, the upper bound was implicitly 120 seconds, but it is now explicitly\nlimited to **20 seconds by default**.\n\nThis behavior can be customized at build time using the ``MAX_HOLDOFF_TIME``\nmacro.\n\nChanges in avs_coap\n-------------------\n\nChanged flow of cancelling observations in case of errors\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nCoAP observations are implicitly cancelled if a notification bearing a 4.xx or\n5.xx error code is delivered.\n\nIn Anjay 3.4.x and earlier, this cancellation (which involves calling the\n``avs_coap_observe_cancel_handler_t`` callback) was performed *before* calling\nthe ``avs_coap_delivery_status_handler_t`` callback for the specific\nnotification. Since Anjay 3.5.0, this order is reversed, so any code that relies\non this logic may break.\n\nThis change is only relevant if you are using ``avs_coap`` APIs directly (e.g.\nwhen communicating over raw CoAP protocol) and in case of notifications intended\nto be delivered as confirmable. The LwM2M Observe/Notify implementation in Anjay\nhas been updated accordingly.\n\nChanges in avs_commons\n----------------------\n\nRenamed CMake configuration options\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nThe ``WITH_AVS_CRYPTO_ENGINE`` CMake configuration option has been removed; the\nnew equivalent option is ``WITH_AVS_CRYPTO_PKI_ENGINE``. Please update CMake\ninvocations in your configuration scripts.\n\nRenamed configuration macro in avs_commons_config.h\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nThe following configuration macros in ``avs_commons_config.h`` has been renamed.\nYou may need to update your configuration files if you are not using CMake, or\nyour preprocessor directives if you check these macros in your code:\n\n+----------------------------------------+--------------------------------------------+\n| Old macro name                         | New macro name                             |\n+========================================+============================================+\n| ``AVS_COMMONS_WITH_AVS_CRYPTO_ENGINE`` | ``AVS_COMMONS_WITH_AVS_CRYPTO_PKI_ENGINE`` |\n+----------------------------------------+--------------------------------------------+\n| ``AVS_COMMONS_NET_WITH_PSK``           | ``AVS_COMMONS_WITH_AVS_CRYPTO_PSK``        |\n+----------------------------------------+--------------------------------------------+\n\nIntroduction of new socket option\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\navs_commons 4.10.1 bundled with Anjay 2.15.1 adds a new socket option key:\n``AVS_NET_SOCKET_HAS_BUFFERED_DATA``. This is used to make sure that when\ncontrol is returned to the event loop, the ``poll()`` call will not stall\nwaiting for new data that in reality has been already buffered and could be\nretrieved using the avs_commons APIs.\n\nThis is usually meaningful for (D)TLS connections, but for almost all simple\nunencrypted socket implementations, this should always return ``false``.\n\nThis was previously achieved by always trying to receive more packets with\ntimeout set to zero. However, it has been determined that such logic could lead\nto heavy blocking of the event loop in case communication with the network stack\nis relatively slow, e.g. on devices which implement TCP/IP sockets through modem\nAT commands.\n\nIf you maintain your own socket integration layer or (D)TLS integration layer,\nit is recommended that you add support for this option. This is not, however, a\nbreaking change - if the option is not supported, the library will continue to\nuse the old behavior.\n\nRefactor of PSK credential handling\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n``avs_net_psk_info_t`` structure has been changed to use new types based on\n``avs_crypto_security_info_union_t`` instead of raw buffers. This change also\naffects ``avs_net_security_info_t`` structure which contains the former.\n\n* **Old API:**\n  ::\n\n      /**\n       * A PSK/identity pair with borrowed pointers. avs_commons will never attempt\n       * to modify these values.\n       */\n      typedef struct {\n          const void *psk;\n          size_t psk_size;\n          const void *identity;\n          size_t identity_size;\n      } avs_net_psk_info_t;\n\n      // ...\n\n      typedef struct {\n          avs_net_security_mode_t mode;\n          union {\n              avs_net_psk_info_t psk;\n              avs_net_certificate_info_t cert;\n          } data;\n      } avs_net_security_info_t;\n\n      avs_net_security_info_t avs_net_security_info_from_psk(avs_net_psk_info_t psk);\n\n* **New API:**\n\n  .. snippet-source:: deps/avs_commons/include_public/avsystem/commons/avs_crypto_psk.h\n\n      typedef struct {\n          avs_crypto_security_info_union_t desc;\n      } avs_crypto_psk_identity_info_t;\n\n      // ...\n\n      avs_crypto_psk_identity_info_t\n      avs_crypto_psk_identity_info_from_buffer(const void *buffer,\n                                               size_t buffer_size);\n\n      // ...\n\n      typedef struct {\n          avs_crypto_security_info_union_t desc;\n      } avs_crypto_psk_key_info_t;\n\n      // ...\n\n      avs_crypto_psk_key_info_t\n      avs_crypto_psk_key_info_from_buffer(const void *buffer, size_t buffer_size);\n\n  .. snippet-source:: deps/avs_commons/include_public/avsystem/commons/avs_socket.h\n\n      /**\n       * A PSK/identity pair. avs_commons will never attempt to modify these values.\n       */\n      typedef struct {\n          avs_crypto_psk_key_info_t key;\n          avs_crypto_psk_identity_info_t identity;\n      } avs_net_psk_info_t;\n\n      // ...\n\n      typedef struct {\n          avs_net_security_mode_t mode;\n          union {\n              avs_net_psk_info_t psk;\n              avs_net_certificate_info_t cert;\n          } data;\n      } avs_net_security_info_t;\n\n      avs_net_security_info_t\n      avs_net_security_info_from_psk(avs_net_psk_info_t psk);\n\nThis change is breaking for code that accesses the ``data.psk`` field\nof ``avs_net_security_info_t`` directly.\n\nRefactor of avs_net_local_address_for_target_host()\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n``avs_net_local_address_for_target_host()`` has never been used by Anjay or any\nother part of ``avs_commons``. However, it was previously a function to be\noptionally implemented as part of the socket implementation. It has now been\nreimplemented as a ``static inline`` function that wraps\n``avs_net_socket_*()`` APIs. Please remove your version of\n``avs_net_local_address_for_target_host()`` from your socket implementation if\nyou have one, as having two alternative variants may lead to conflicts.\n\nAdditional function in the hardware security engine API\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nA new API has been added to the hardware security engine API in ``avs_commons``:\n\n.. snippet-source:: deps/avs_commons/include_public/avsystem/commons/avs_crypto_pki.h\n\n    avs_error_t\n    avs_crypto_pki_engine_key_store(const char *query,\n                                    const avs_crypto_private_key_info_t *key_info,\n                                    avs_crypto_prng_ctx_t *prng_ctx);\n\nIf you implement your own hardware security engine backend implementation, you may\nneed to provide an implementation of this function.\n\nThis new API is used by the Security object implementation's features related\nto the ``anjay_security_object_install_with_hsm()``. If you don't use these\nfeatures to store private keys in the hardware security engine, it is OK to\nprovide a dummy implementation such as ``return avs_errno(AVS_ENOTSUP);``.\n\nRefactor of time handling in avs_sched and avs_coap\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nIt is now enforced more strictly that time-based events shall happen when the\nclock reaches *at least* the expected value. Previously, the tasks scheduled via\navs_sched were executed only when the clock reached a value *later* than the\nscheduled job execution time.\n\nThis change will have no impact on your code if your platform has enough clock\nresolution so that two subsequent calls to ``avs_time_real_now()`` or\n``avs_time_monotonic_now()`` will *always* return different values. As a rule of\nthumb, this should be the case if your clock has a resolution no worse than\nabout 1-2 orders of magnitude smaller than the CPU clock. For example, for a\n100 MHz CPU, a clock resolution of around 100-1000 ns (i.e., 1-10 MHz) should be\nsufficient, depending on the specific architecture.\n\nIf your clock has a lower resolution, you may observe the following changes:\n\n* ``anjay_sched_run()`` is now properly guaranteed to execute at least one job\n  if the time reported by ``anjay_sched_time_to_next()`` passed. Previously this\n  could require waiting for another change of the numerical value of the clock,\n  which could cause undesirable active waiting in the event loop. This is the\n  motivating factor in introducing these changes.\n* Jobs scheduled using ``AVS_SCHED_NOW()`` during an execution of\n  ``anjay_sched_run()`` before the numerical value of the clock changes, *will*\n  be executed during the same run. The previous behavior more strictly enforced\n  the policy to not execute such jobs in the same run.\n\nIf you are scheduling custom jobs through the avs_sched module, you may want or\nneed to modify their logic accordingly to accommodate for these changes. In most\ntypical use cases, no changes are expected to be necessary.\n\nRemoval of avs_unit_memstream\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n``avs_unit_memstream`` was a specific implementation of ``avs_stream_t`` within\nthe avs_unit module that implemented a simple FIFO stream in a fixed-size memory\narea.\n\nThis feature has been removed. Instead, you can use an\n``avs_stream_inbuf``/``avs_stream_outbuf`` pair, or an ``avs_stream_membuf``\nobject.\n\nHandling of Trust Store for certain Certificate Usages settings\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\navs_commons 5.5.0 that is used by Anjay 3.11.0 does not pass the configured Trust\nStore (whether manually provided or acquired through the EST process) to Mbed TLS\nwhen the certificate usage is set to DANE-TA or DANE-EE, if the Data Model already\ncontains a Server certificate intended for verifying the Server during a secure\nconnection.\n\nIf the Server certificate is missing from the Data Model, Anjay falls back to\nPKIX verification, provided that a Trust Store is available, even when the\ncertificate usage is set to DANE-TA or DANE-EE.\n\nFor more information on how Anjay manages the Trust Store and the Certificate\nUsage resource, see\n:doc:`Certificate Usage <../AdvancedTopics/AT-CertificateUsage>`.\n\nPython environment isolation\n----------------------------\n\nAll Python-based tools (e.g. integration tests) must be executed within a\nPython virtual environment. See :doc:`/Tools/VirtualEnvironments` for more\ninformation.\n"
  },
  {
    "path": "doc/sphinx/source/Migrating/MigratingFromAnjay30.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nMigrating from Anjay 3.0\n========================\n\n.. contents:: :local:\n\n.. highlight:: c\n\nIntroduction\n------------\n\nSince Anjay 3.0, some minor changes to the API has been done. These change are\nbreaking in the strictest sense, but in practice should not require any changes\nto user code in typical usage.\n\nChanges in Anjay proper\n-----------------------\n\nBehavior of anjay_attr_storage_restore() upon failure\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nDeprecated behavior of ``anjay_attr_storage_restore()`` which did clear the\nAttribute Storage if supplied source stream was invalid has been changed. From\nnow on, if this function fails, the Attribute Storage remains untouched.\nThis change is breaking for code which relied on the old behavior, although\nsuch code is unlikely.\n\nDefault (D)TLS version\n^^^^^^^^^^^^^^^^^^^^^^\n\nWhen the `anjay_configuration_t::dtls_version\n<../api/structanjay__configuration.html#ab32477e7370a36e02db5b7e7ccbdd89d>`_\nfield is set to ``AVS_NET_SSL_VERSION_DEFAULT`` (which includes the case of\nzero-initialization), Anjay 3.0 and earlier automatically mapped this setting to\n``AVS_NET_SSL_VERSION_TLSv1_2`` to ensure that (D)TLS 1.2 is used as mandated by\nthe LwM2M specification.\n\nThis mapping has been removed in Anjay 3.1, which means that the default version\nconfiguration of the underlying (D)TLS library will be used. This has been done\nto automatically allow the use of newer protocols and deprecate old versions\nwhen the backend library is updated, without the need to update Anjay code.\nHowever, depending on the (D)TLS backend library used, this may lead to (D)TLS\n1.1 or earlier being used if the server does not properly negotiate a higher\nversion. Please explicitly set ``dtls_version`` to\n``AVS_NET_SSL_VERSION_TLSv1_2`` if you want to disallow this.\n\nPlease note that Mbed TLS 3.0 has dropped support for TLS 1.1 and earlier, so\nthis change will not affect behavior with that library.\n\n\nAddition of resource_instance_remove handler\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nLwM2M TS 1.2 introduced possibility to delete a Resource Instance, so\none additional data model handler had to be added.\n\nNew ``resource_instance_remove`` handler (and its associated\n``anjay_dm_resource_instance_remove_t`` function type) has been introduced. It\nis analogous to the ``instance_remove`` handler; its job is to remove a specific\nResource Instance from a multiple-instance Resource.\n\nIts implementation is required in objects that include at least one writeable\nmultiple-instance Resource, if the client application aims for compliance with\nLwM2M 1.2.\n\nLimiting the maximum Hold Off Time for Bootstrap\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nA limit has been introduced on the maximum Hold Off Time (LwM2M Security Object,\nResource ID: 11) to avoid excessive delays when initiating the Bootstrap process.\nPreviously, the upper bound was implicitly 120 seconds, but it is now explicitly\nlimited to **20 seconds by default**.\n\nThis behavior can be customized at build time using the ``MAX_HOLDOFF_TIME``\nmacro.\n\nChanges in avs_coap\n-------------------\n\nChanged flow of cancelling observations in case of errors\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nCoAP observations are implicitly cancelled if a notification bearing a 4.xx or\n5.xx error code is delivered.\n\nIn Anjay 3.4.x and earlier, this cancellation (which involves calling the\n``avs_coap_observe_cancel_handler_t`` callback) was performed *before* calling\nthe ``avs_coap_delivery_status_handler_t`` callback for the specific\nnotification. Since Anjay 3.5.0, this order is reversed, so any code that relies\non this logic may break.\n\nThis change is only relevant if you are using ``avs_coap`` APIs directly (e.g.\nwhen communicating over raw CoAP protocol) and in case of notifications intended\nto be delivered as confirmable. The LwM2M Observe/Notify implementation in Anjay\nhas been updated accordingly.\n\nChanges in avs_commons\n----------------------\n\nRefactor of time handling in avs_sched and avs_coap\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nIt is now enforced more strictly that time-based events shall happen when the\nclock reaches *at least* the expected value. Previously, the tasks scheduled via\navs_sched were executed only when the clock reached a value *later* than the\nscheduled job execution time.\n\nThis change will have no impact on your code if your platform has enough clock\nresolution so that two subsequent calls to ``avs_time_real_now()`` or\n``avs_time_monotonic_now()`` will *always* return different values. As a rule of\nthumb, this should be the case if your clock has a resolution no worse than\nabout 1-2 orders of magnitude smaller than the CPU clock. For example, for a\n100 MHz CPU, a clock resolution of around 100-1000 ns (i.e., 1-10 MHz) should be\nsufficient, depending on the specific architecture.\n\nIf your clock has a lower resolution, you may observe the following changes:\n\n* ``anjay_sched_run()`` is now properly guaranteed to execute at least one job\n  if the time reported by ``anjay_sched_time_to_next()`` passed. Previously this\n  could require waiting for another change of the numerical value of the clock,\n  which could cause undesirable active waiting in the event loop. This is the\n  motivating factor in introducing these changes.\n* Jobs scheduled using ``AVS_SCHED_NOW()`` during an execution of\n  ``anjay_sched_run()`` before the numerical value of the clock changes, *will*\n  be executed during the same run. The previous behavior more strictly enforced\n  the policy to not execute such jobs in the same run.\n\nIf you are scheduling custom jobs through the avs_sched module, you may want or\nneed to modify their logic accordingly to accommodate for these changes. In most\ntypical use cases, no changes are expected to be necessary.\n\nRemoval of avs_unit_memstream\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n``avs_unit_memstream`` was a specific implementation of ``avs_stream_t`` within\nthe avs_unit module that implemented a simple FIFO stream in a fixed-size memory\narea.\n\nThis feature has been removed. Instead, you can use an\n``avs_stream_inbuf``/``avs_stream_outbuf`` pair, or an ``avs_stream_membuf``\nobject.\n\nHandling of Trust Store for certain Certificate Usages settings\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\navs_commons 5.5.0 that is used by Anjay 3.11.0 does not pass the configured Trust\nStore (whether manually provided or acquired through the EST process) to Mbed TLS\nwhen the certificate usage is set to DANE-TA or DANE-EE, if the Data Model already\ncontains a Server certificate intended for verifying the Server during a secure\nconnection.\n\nIf the Server certificate is missing from the Data Model, Anjay falls back to\nPKIX verification, provided that a Trust Store is available, even when the\ncertificate usage is set to DANE-TA or DANE-EE.\n\nFor more information on how Anjay manages the Trust Store and the Certificate\nUsage resource, see\n:doc:`Certificate Usage <../AdvancedTopics/AT-CertificateUsage>`.\n\nPython environment isolation\n----------------------------\n\nAll Python-based tools (e.g. integration tests) must be executed within a\nPython virtual environment. See :doc:`/Tools/VirtualEnvironments` for more\ninformation.\n"
  },
  {
    "path": "doc/sphinx/source/Migrating/MigratingFromAnjay310.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nMigrating from Anjay 3.8, 3.9 or 3.10\n=====================================\n\n.. contents:: :local:\n\n.. highlight:: c\n\nIntroduction\n------------\n\nStarting from Anjay 3.11.0, the behavior regarding the maximum Hold Off Time for\nbootstrap connections has changed.\n\nLimiting the maximum Hold Off Time for Bootstrap\n------------------------------------------------\n\nA limit has been introduced on the maximum Hold Off Time (LwM2M Security Object,\nResource ID: 11) to avoid excessive delays when initiating the Bootstrap process.\nPreviously, the upper bound was implicitly 120 seconds, but it is now explicitly\nlimited to **20 seconds by default**.\n\nThis behavior can be customized at build time using the ``MAX_HOLDOFF_TIME``\nmacro.\n\nHandling of Trust Store for certain Certificate Usages settings\n---------------------------------------------------------------\n\navs_commons 5.5.0 that is used by Anjay 3.11.0 does not pass the configured Trust\nStore (whether manually provided or acquired through the EST process) to Mbed TLS\nwhen the certificate usage is set to DANE-TA or DANE-EE, if the Data Model already\ncontains a Server certificate intended for verifying the Server during a secure\nconnection.\n\nIf the Server certificate is missing from the Data Model, Anjay falls back to\nPKIX verification, provided that a Trust Store is available, even when the\ncertificate usage is set to DANE-TA or DANE-EE.\n\nFor more information on how Anjay manages the Trust Store and the Certificate\nUsage resource, see\n:doc:`Certificate Usage <../AdvancedTopics/AT-CertificateUsage>`.\n\nAddition of resource_instance_remove handler\n--------------------------------------------\n\nLwM2M TS 1.2 introduced possibility to delete a Resource Instance, so\none additional data model handler had to be added.\n\nNew ``resource_instance_remove`` handler (and its associated\n``anjay_dm_resource_instance_remove_t`` function type) has been introduced. It\nis analogous to the ``instance_remove`` handler; its job is to remove a specific\nResource Instance from a multiple-instance Resource.\n\nIts implementation is required in objects that include at least one writeable\nmultiple-instance Resource, if the client application aims for compliance with\nLwM2M 1.2.\n\nPython environment isolation\n----------------------------\n\nAll Python-based tools (e.g. integration tests) must be executed within a\nPython virtual environment. See :doc:`/Tools/VirtualEnvironments` for more\ninformation.\n"
  },
  {
    "path": "doc/sphinx/source/Migrating/MigratingFromAnjay312.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nMigrating from Anjay 3.11 or 3.12\n=================================\n\n.. contents:: :local:\n\n.. highlight:: c\n\nAddition of resource_instance_remove handler\n--------------------------------------------\n\nLwM2M TS 1.2 introduced possibility to delete a Resource Instance, so\none additional data model handler had to be added.\n\nNew ``resource_instance_remove`` handler (and its associated\n``anjay_dm_resource_instance_remove_t`` function type) has been introduced. It\nis analogous to the ``instance_remove`` handler; its job is to remove a specific\nResource Instance from a multiple-instance Resource.\n\nIts implementation is required in objects that include at least one writeable\nmultiple-instance Resource, if the client application aims for compliance with\nLwM2M 1.2.\n"
  },
  {
    "path": "doc/sphinx/source/Migrating/MigratingFromAnjay32.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nMigrating from Anjay 3.1 or 3.2\n===============================\n\n.. contents:: :local:\n\n.. highlight:: c\n\nIntroduction\n------------\n\navs_commons 5.2.0 that is used by Anjay 3.3.0, has removed one specific\nfunctionality of the avs_unit module that has not been used by Anjay. If you are\ndeveloping your own unit tests based on avs_unit, you may need to update them.\n\nSince Anjay 3.4.0 and avs_commons 5.4.0, time handling in avs_sched and avs_coap\nhas been refactored, which slightly redefined existing APIs. These changes\nshould not require any changes to user code in typical usage, but may break some\nedge cases, especially on platforms where the system clock has a low resolution\nand you are scheduling custom jobs through the avs_sched module.\n\nRefactor of time handling in avs_sched and avs_coap\n---------------------------------------------------\n\nIt is now enforced more strictly that time-based events shall happen when the\nclock reaches *at least* the expected value. Previously, the tasks scheduled via\navs_sched were executed only when the clock reached a value *later* than the\nscheduled job execution time.\n\nThis change will have no impact on your code if your platform has enough clock\nresolution so that two subsequent calls to ``avs_time_real_now()`` or\n``avs_time_monotonic_now()`` will *always* return different values. As a rule of\nthumb, this should be the case if your clock has a resolution no worse than\nabout 1-2 orders of magnitude smaller than the CPU clock. For example, for a\n100 MHz CPU, a clock resolution of around 100-1000 ns (i.e., 1-10 MHz) should be\nsufficient, depending on the specific architecture.\n\nIf your clock has a lower resolution, you may observe the following changes:\n\n* ``anjay_sched_run()`` is now properly guaranteed to execute at least one job\n  if the time reported by ``anjay_sched_time_to_next()`` passed. Previously this\n  could require waiting for another change of the numerical value of the clock,\n  which could cause undesirable active waiting in the event loop. This is the\n  motivating factor in introducing these changes.\n* Jobs scheduled using ``AVS_SCHED_NOW()`` during an execution of\n  ``anjay_sched_run()`` before the numerical value of the clock changes, *will*\n  be executed during the same run. The previous behavior more strictly enforced\n  the policy to not execute such jobs in the same run.\n\nIf you are scheduling custom jobs through the avs_sched module, you may want or\nneed to modify their logic accordingly to accommodate for these changes. In most\ntypical use cases, no changes are expected to be necessary.\n\nChanged flow of cancelling observations in case of errors\n---------------------------------------------------------\n\nCoAP observations are implicitly cancelled if a notification bearing a 4.xx or\n5.xx error code is delivered.\n\nIn Anjay 3.4.x and earlier, this cancellation (which involves calling the\n``avs_coap_observe_cancel_handler_t`` callback) was performed *before* calling\nthe ``avs_coap_delivery_status_handler_t`` callback for the specific\nnotification. Since Anjay 3.5.0, this order is reversed, so any code that relies\non this logic may break.\n\nThis change is only relevant if you are using ``avs_coap`` APIs directly (e.g.\nwhen communicating over raw CoAP protocol) and in case of notifications intended\nto be delivered as confirmable. The LwM2M Observe/Notify implementation in Anjay\nhas been updated accordingly.\n\n\nAddition of resource_instance_remove handler\n--------------------------------------------\n\nLwM2M TS 1.2 introduced possibility to delete a Resource Instance, so\none additional data model handler had to be added.\n\nNew ``resource_instance_remove`` handler (and its associated\n``anjay_dm_resource_instance_remove_t`` function type) has been introduced. It\nis analogous to the ``instance_remove`` handler; its job is to remove a specific\nResource Instance from a multiple-instance Resource.\n\nIts implementation is required in objects that include at least one writeable\nmultiple-instance Resource, if the client application aims for compliance with\nLwM2M 1.2.\n\nLimiting the maximum Hold Off Time for Bootstrap\n------------------------------------------------\n\nA limit has been introduced on the maximum Hold Off Time (LwM2M Security Object,\nResource ID: 11) to avoid excessive delays when initiating the Bootstrap process.\nPreviously, the upper bound was implicitly 120 seconds, but it is now explicitly\nlimited to **20 seconds by default**.\n\nThis behavior can be customized at build time using the ``MAX_HOLDOFF_TIME``\nmacro.\n\nHandling of Trust Store for certain Certificate Usages settings\n---------------------------------------------------------------\n\navs_commons 5.5.0 that is used by Anjay 3.11.0 does not pass the configured Trust\nStore (whether manually provided or acquired through the EST process) to Mbed TLS\nwhen the certificate usage is set to DANE-TA or DANE-EE, if the Data Model already\ncontains a Server certificate intended for verifying the Server during a secure\nconnection.\n\nIf the Server certificate is missing from the Data Model, Anjay falls back to\nPKIX verification, provided that a Trust Store is available, even when the\ncertificate usage is set to DANE-TA or DANE-EE.\n\nFor more information on how Anjay manages the Trust Store and the Certificate\nUsage resource, see\n:doc:`Certificate Usage <../AdvancedTopics/AT-CertificateUsage>`.\n\nRemoval of avs_unit_memstream\n-----------------------------\n\n``avs_unit_memstream`` was a specific implementation of ``avs_stream_t`` within\nthe avs_unit module that implemented a simple FIFO stream in a fixed-size memory\narea.\n\nThis feature has been removed. Instead, you can use an\n``avs_stream_inbuf``/``avs_stream_outbuf`` pair, or an ``avs_stream_membuf``\nobject.\n\nPython environment isolation\n----------------------------\n\nAll Python-based tools (e.g. integration tests) must be executed within a\nPython virtual environment. See :doc:`/Tools/VirtualEnvironments` for more\ninformation.\n"
  },
  {
    "path": "doc/sphinx/source/Migrating/MigratingFromAnjay33.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nMigrating from Anjay 3.3\n========================\n\n.. contents:: :local:\n\n.. highlight:: c\n\nIntroduction\n------------\n\nSince Anjay 3.4.0 and avs_commons 5.4.0, time handling in avs_sched and avs_coap\nhas been refactored, which slightly redefined existing APIs.\n\nThese changes should not require any changes to user code in typical usage, but\nmay break some edge cases, especially on platforms where the system clock has a\nlow resolution and you are scheduling custom jobs through the avs_sched module.\n\nRefactor of time handling in avs_sched and avs_coap\n---------------------------------------------------\n\nIt is now enforced more strictly that time-based events shall happen when the\nclock reaches *at least* the expected value. Previously, the tasks scheduled via\navs_sched were executed only when the clock reached a value *later* than the\nscheduled job execution time.\n\nThis change will have no impact on your code if your platform has enough clock\nresolution so that two subsequent calls to ``avs_time_real_now()`` or\n``avs_time_monotonic_now()`` will *always* return different values. As a rule of\nthumb, this should be the case if your clock has a resolution no worse than\nabout 1-2 orders of magnitude smaller than the CPU clock. For example, for a\n100 MHz CPU, a clock resolution of around 100-1000 ns (i.e., 1-10 MHz) should be\nsufficient, depending on the specific architecture.\n\nIf your clock has a lower resolution, you may observe the following changes:\n\n* ``anjay_sched_run()`` is now properly guaranteed to execute at least one job\n  if the time reported by ``anjay_sched_time_to_next()`` passed. Previously this\n  could require waiting for another change of the numerical value of the clock,\n  which could cause undesirable active waiting in the event loop. This is the\n  motivating factor in introducing these changes.\n* Jobs scheduled using ``AVS_SCHED_NOW()`` during an execution of\n  ``anjay_sched_run()`` before the numerical value of the clock changes, *will*\n  be executed during the same run. The previous behavior more strictly enforced\n  the policy to not execute such jobs in the same run.\n\nIf you are scheduling custom jobs through the avs_sched module, you may want or\nneed to modify their logic accordingly to accommodate for these changes. In most\ntypical use cases, no changes are expected to be necessary.\n\nChanged flow of cancelling observations in case of errors\n---------------------------------------------------------\n\nCoAP observations are implicitly cancelled if a notification bearing a 4.xx or\n5.xx error code is delivered.\n\nIn Anjay 3.4.x and earlier, this cancellation (which involves calling the\n``avs_coap_observe_cancel_handler_t`` callback) was performed *before* calling\nthe ``avs_coap_delivery_status_handler_t`` callback for the specific\nnotification. Since Anjay 3.5.0, this order is reversed, so any code that relies\non this logic may break.\n\nThis change is only relevant if you are using ``avs_coap`` APIs directly (e.g.\nwhen communicating over raw CoAP protocol) and in case of notifications intended\nto be delivered as confirmable. The LwM2M Observe/Notify implementation in Anjay\nhas been updated accordingly.\n\n\nAddition of resource_instance_remove handler\n--------------------------------------------\n\nLwM2M TS 1.2 introduced possibility to delete a Resource Instance, so\none additional data model handler had to be added.\n\nNew ``resource_instance_remove`` handler (and its associated\n``anjay_dm_resource_instance_remove_t`` function type) has been introduced. It\nis analogous to the ``instance_remove`` handler; its job is to remove a specific\nResource Instance from a multiple-instance Resource.\n\nIts implementation is required in objects that include at least one writeable\nmultiple-instance Resource, if the client application aims for compliance with\nLwM2M 1.2.\n\nLimiting the maximum Hold Off Time for Bootstrap\n------------------------------------------------\n\nA limit has been introduced on the maximum Hold Off Time (LwM2M Security Object,\nResource ID: 11) to avoid excessive delays when initiating the Bootstrap process.\nPreviously, the upper bound was implicitly 120 seconds, but it is now explicitly\nlimited to **20 seconds by default**.\n\nThis behavior can be customized at build time using the ``MAX_HOLDOFF_TIME``\nmacro.\n\nHandling of Trust Store for certain Certificate Usages settings\n---------------------------------------------------------------  \n\navs_commons 5.5.0 that is used by Anjay 3.11.0 does not pass the configured Trust\nStore (whether manually provided or acquired through the EST process) to Mbed TLS\nwhen the certificate usage is set to DANE-TA or DANE-EE, if the Data Model already\ncontains a Server certificate intended for verifying the Server during a secure\nconnection.\n\nIf the Server certificate is missing from the Data Model, Anjay falls back to\nPKIX verification, provided that a Trust Store is available, even when the\ncertificate usage is set to DANE-TA or DANE-EE.\n\nFor more information on how Anjay manages the Trust Store and the Certificate\nUsage resource, see\n:doc:`Certificate Usage <../AdvancedTopics/AT-CertificateUsage>`.\n\nPython environment isolation\n----------------------------\n\nAll Python-based tools (e.g. integration tests) must be executed within a\nPython virtual environment. See :doc:`/Tools/VirtualEnvironments` for more\ninformation.\n"
  },
  {
    "path": "doc/sphinx/source/Migrating/MigratingFromAnjay34.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nMigrating from Anjay 3.4\n========================\n\n.. contents:: :local:\n\n.. highlight:: c\n\nIntroduction\n------------\n\nSince Anjay 3.5.0, the code flow of delivering notifications has been\nrefactored so that user handler callbacks may be called in a different order.\nThis only affects direct users of ``avs_coap`` APIs (e.g. when communicating\nover raw CoAP protocol).\n\nChanged flow of cancelling observations in case of errors\n---------------------------------------------------------\n\nCoAP observations are implicitly cancelled if a notification bearing a 4.xx or\n5.xx error code is delivered. If an attempt to deliver a confirmable\nnotification times out, CoAP observation is not cancelled by default anymore.\nIt can be adjusted by ``WITH_AVS_COAP_OBSERVE_CANCEL_ON_TIMEOUT``.\n\nIn Anjay 3.4.x and earlier, this cancellation (which involves calling the\n``avs_coap_observe_cancel_handler_t`` callback) was performed *before* calling\nthe ``avs_coap_delivery_status_handler_t`` callback for the specific\nnotification. Since Anjay 3.5.0, this order is reversed, so any code that relies\non this logic may break.\n\nThis change is only relevant if you are using ``avs_coap`` APIs directly (e.g.\nwhen communicating over raw CoAP protocol) and in case of notifications intended\nto be delivered as confirmable. The LwM2M Observe/Notify implementation in Anjay\nhas been updated accordingly.\n\nAddition of resource_instance_remove handler\n--------------------------------------------\n\nLwM2M TS 1.2 introduced possibility to delete a Resource Instance, so\none additional data model handler had to be added.\n\nNew ``resource_instance_remove`` handler (and its associated\n``anjay_dm_resource_instance_remove_t`` function type) has been introduced. It\nis analogous to the ``instance_remove`` handler; its job is to remove a specific\nResource Instance from a multiple-instance Resource.\n\nIts implementation is required in objects that include at least one writeable\nmultiple-instance Resource, if the client application aims for compliance with\nLwM2M 1.2.\n\nLimiting the maximum Hold Off Time for Bootstrap\n------------------------------------------------\n\nA limit has been introduced on the maximum Hold Off Time (LwM2M Security Object,\nResource ID: 11) to avoid excessive delays when initiating the Bootstrap process.\nPreviously, the upper bound was implicitly 120 seconds, but it is now explicitly\nlimited to **20 seconds by default**.\n\nThis behavior can be customized at build time using the ``MAX_HOLDOFF_TIME``\nmacro.\n\nHandling of Trust Store for certain Certificate Usages settings\n---------------------------------------------------------------  \n\navs_commons 5.5.0 that is used by Anjay 3.11.0 does not pass the configured Trust\nStore (whether manually provided or acquired through the EST process) to Mbed TLS\nwhen the certificate usage is set to DANE-TA or DANE-EE, if the Data Model already\ncontains a Server certificate intended for verifying the Server during a secure\nconnection.\n\nIf the Server certificate is missing from the Data Model, Anjay falls back to\nPKIX verification, provided that a Trust Store is available, even when the\ncertificate usage is set to DANE-TA or DANE-EE.\n\nFor more information on how Anjay manages the Trust Store and the Certificate\nUsage resource, see\n:doc:`Certificate Usage <../AdvancedTopics/AT-CertificateUsage>`.\n\nPython environment isolation\n----------------------------\n\nAll Python-based tools (e.g. integration tests) must be executed within a\nPython virtual environment. See :doc:`/Tools/VirtualEnvironments` for more\ninformation.\n"
  },
  {
    "path": "doc/sphinx/source/Migrating/MigratingFromAnjay37.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nMigrating from Anjay 3.5, 3.6 or 3.7\n====================================\n\n.. contents:: :local:\n\n.. highlight:: c\n\nIntroduction\n------------\n\nSince Anjay 3.8.0, confirmable notifications are not cancelled anymore in case\nof a timeout.\n\nChanged flow of cancelling observations in case of timeout\n----------------------------------------------------------\n\nIf an attempt to deliver a confirmable notification times out, CoAP observation\nis not cancelled by default anymore. It can be adjusted by\n``WITH_AVS_COAP_OBSERVE_CANCEL_ON_TIMEOUT``.\nThe LwM2M Observe/Notify implementation in Anjay has been updated accordingly.\n\nAddition of resource_instance_remove handler\n--------------------------------------------\n\nLwM2M TS 1.2 introduced possibility to delete a Resource Instance, so\none additional data model handler had to be added.\n\nNew ``resource_instance_remove`` handler (and its associated\n``anjay_dm_resource_instance_remove_t`` function type) has been introduced. It\nis analogous to the ``instance_remove`` handler; its job is to remove a specific\nResource Instance from a multiple-instance Resource.\n\nIts implementation is required in objects that include at least one writeable\nmultiple-instance Resource, if the client application aims for compliance with\nLwM2M 1.2.\n\nLimiting the maximum Hold Off Time for Bootstrap\n------------------------------------------------\n\nA limit has been introduced on the maximum Hold Off Time (LwM2M Security Object,\nResource ID: 11) to avoid excessive delays when initiating the Bootstrap process.\nPreviously, the upper bound was implicitly 120 seconds, but it is now explicitly\nlimited to **20 seconds by default**.\n\nThis behavior can be customized at build time using the ``MAX_HOLDOFF_TIME``\nmacro.\n\nHandling of Trust Store for certain Certificate Usages settings\n---------------------------------------------------------------  \n\navs_commons 5.5.0 that is used by Anjay 3.11.0 does not pass the configured Trust\nStore (whether manually provided or acquired through the EST process) to Mbed TLS\nwhen the certificate usage is set to DANE-TA or DANE-EE, if the Data Model already\ncontains a Server certificate intended for verifying the Server during a secure\nconnection.\n\nIf the Server certificate is missing from the Data Model, Anjay falls back to\nPKIX verification, provided that a Trust Store is available, even when the\ncertificate usage is set to DANE-TA or DANE-EE.\n\nFor more information on how Anjay manages the Trust Store and the Certificate\nUsage resource, see\n:doc:`Certificate Usage <../AdvancedTopics/AT-CertificateUsage>`.\n\nPython environment isolation\n----------------------------\n\nAll Python-based tools (e.g. integration tests) must be executed within a\nPython virtual environment. See :doc:`/Tools/VirtualEnvironments` for more\ninformation.\n"
  },
  {
    "path": "doc/sphinx/source/Migrating.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nMigrating from older versions\n=============================\n\n.. note::\n\n    Each of these documents is intended to be stand-alone and complete guide for\n    migrating from the specified version to the newest one.\n\n    In other words, if you are migrating from e.g. Anjay 2.4, you **only** need\n    to follow :doc:`Migrating/MigratingFromAnjay24`. All the relevant\n    information from :doc:`Migrating/MigratingFromAnjay26` etc. is included\n    there as well.\n\n.. toctree::\n   :glob:\n   :titlesonly:\n\n   Migrating/MigratingFromAnjay225\n   Migrating/MigratingCustomEntropy\n   Migrating/MigratingFromAnjay24\n   Migrating/MigratingFromAnjay26\n   Migrating/MigratingFromAnjay27\n   Migrating/MigratingFromAnjay28\n   Migrating/MigratingFromAnjay214\n   Migrating/MigratingFromAnjay215\n   Migrating/MigratingFromAnjay30\n   Migrating/MigratingFromAnjay32\n   Migrating/MigratingFromAnjay33\n   Migrating/MigratingFromAnjay34\n   Migrating/MigratingFromAnjay37\n   Migrating/MigratingFromAnjay310\n   Migrating/MigratingFromAnjay312\n"
  },
  {
    "path": "doc/sphinx/source/PortingGuideForNonPOSIXPlatforms/CustomTLS/CustomTLS-CertificatesAdvanced.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nAdvanced certificate support\n============================\n\n.. contents:: :local:\n\n.. note::\n\n    Code related to this tutorial can be found under\n    `examples/custom-tls/certificates-advanced\n    <https://github.com/AVSystem/Anjay/tree/master/examples/custom-tls/certificates-advanced>`_\n    in the Anjay source directory.\n\nIntroduction\n------------\n\nThis tutorial builds up on :doc:`the previous one <CustomTLS-CertificatesBasic>`\nand adds:\n\n* Ability to load multiple certificates as the client certificate chain\n* Support for DANE; **this will also add proper support for the Server Public\n  Key LwM2M resource**\n\n.. note::\n\n    In this tutorial, the ``main.c`` file is identical to the one from the\n    :doc:`../../AdvancedTopics/AT-Certificates` tutorial.\n\n    In fact, in the repository, it is a symbolic link to the file from that\n    tutorial.\n\nSupport for multiple client certificates\n----------------------------------------\n\n.. note::\n\n    Loading multiple client certificates is only possible by setting the\n    ``public_cert`` field in ``anjay_security_instance_t``.\n\n\n    If you don't intend to use that field there is no point in making this change.\n\nUsing multiple certificates in the client certificate chain is rarely used, but\nit may be necessary to properly initiate a connection to some LwM2M servers.\n\nTo support this case, the ``AVS_CRYPTO_DATA_SOURCE_ARRAY`` and\n``AVS_CRYPTO_DATA_SOURCE_LIST`` cases need to be supported in the\n``configure_client_cert()`` function, much like is the case for\n``configure_trusted_certs()``.\n\n.. highlight:: c\n.. snippet-source:: examples/custom-tls/certificates-advanced/src/tls_impl.c\n    :emphasize-lines: 2-6, 8-9, 19-24, 31-50\n\n    static avs_error_t\n    configure_client_certs(SSL_CTX *ctx,\n                           const avs_crypto_security_info_union_t *client_certs) {\n        if (!client_certs) {\n            return avs_errno(AVS_EINVAL);\n        }\n        switch (client_certs->source) {\n        case AVS_CRYPTO_DATA_SOURCE_EMPTY:\n            return AVS_OK;\n        case AVS_CRYPTO_DATA_SOURCE_BUFFER: {\n            const unsigned char *ptr =\n                    (const unsigned char *) client_certs->info.buffer.buffer;\n            X509 *cert = d2i_X509(NULL, &ptr,\n                                  (long) client_certs->info.buffer.buffer_size);\n            if (!cert) {\n                return avs_errno(AVS_EPROTO);\n            }\n\n            int result;\n            if (!SSL_CTX_get0_certificate(ctx)) {\n                result = SSL_CTX_use_certificate(ctx, cert);\n            } else {\n                result = SSL_CTX_add1_chain_cert(ctx, cert);\n            }\n            X509_free(cert);\n            if (result != 1) {\n                return avs_errno(AVS_EPROTO);\n            }\n            return AVS_OK;\n        }\n        case AVS_CRYPTO_DATA_SOURCE_ARRAY: {\n            avs_error_t err = AVS_OK;\n            for (size_t i = 0;\n                 avs_is_ok(err) && i < client_certs->info.array.element_count;\n                 ++i) {\n                err = configure_client_certs(\n                        ctx, &client_certs->info.array.array_ptr[i]);\n            }\n            return err;\n        }\n        case AVS_CRYPTO_DATA_SOURCE_LIST: {\n            avs_error_t err = AVS_OK;\n            AVS_LIST(avs_crypto_security_info_union_t) entry;\n            AVS_LIST_FOREACH(entry, client_certs->info.list.list_head) {\n                if (avs_is_err((err = configure_client_certs(ctx, entry)))) {\n                    break;\n                }\n            }\n            return AVS_OK;\n        }\n        default:\n            return avs_errno(AVS_ENOTSUP);\n        }\n    }\n\nThe function has been slightly refactored to take\n``avs_crypto_security_info_union_t`` as an argument to make recursive calls\neasier. ``client_cert`` in the function and argument names has also been\npluralized.\n\nAside from these trivial changes, the ``AVS_CRYPTO_DATA_SOURCE_ARRAY`` and\n``AVS_CRYPTO_DATA_SOURCE_LIST`` have been implemented in essentially the same\nway as in ``configure_trusted_certs()`` and\n``configure_cert_revocation_lists()``, and the ``AVS_CRYPTO_DATA_SOURCE_BUFFER``\ncase has been updated so that ``SSL_CTX_add1_chain_cert()`` is used for the\nsecond and all subsequent certificate entries. This means that the first loaded\ncertificate is always the actual client certificate, with any subsequent ones\nforming the rest of the certification path up towards the root CA certificate.\n\n.. _custom-tls-api-certificates-advanced-dane:\n\nDANE support\n------------\n\nInstead of the standard PKIX rules for certificate verification, from LwM2M 1.1\nonwards, the server certificates are verified using a `custom mechanism\n<https://www.openmobilealliance.org/release/LightweightM2M/V1_1_1-20190617-A/HTML-Version/OMA-TS-LightweightM2M_Transport-V1_1_1-20190617-A.html#5-2-8-7-0-5287-Certificate-Usage-Field>`_\nthat operates on concepts almost identical to those used by `DANE\n<https://en.wikipedia.org/wiki/DNS-based_Authentication_of_Named_Entities>`_.\n\nThe LwM2M 1.0 semantics are mirrored by the default settings used in LwM2M 1.1,\nwhich is to use the \"domain-issued certificate\" mode.\n\nFor the above reasons, server certificate validation in Anjay is largely\nimplemented in terms of DANE, which needs to be provided in the secure socket\nimplementation.\n\n.. note::\n\n    Standard PKIX certificate validation may also be used in conjunction with\n    DANE, particularly when certificate usage mode is set to \"CA constraint\" or\n    \"service certificate constraint\", or when EST is used.\n\nDANE is supported natively since OpenSSL 1.1, which makes it easy to implement\nfor the purpose of this tutorial.\n\n.. important::\n\n    DANE is not widely supported in other TLS backend libraries or hardware\n    implementations.\n\n    Please look at the\n    :ref:`custom-tls-api-certificates-advanced-dane-minimum-subset` section if\n    implementing proper DANE support is impossible or infeasible in your case.\n\nInitialization\n^^^^^^^^^^^^^^\n\nIt is necessary to store some additional state for DANE support, so the\n``tls_socket_impl_t`` structure is extended accordingly:\n\n.. highlight:: c\n.. snippet-source:: examples/custom-tls/certificates-advanced/src/tls_impl.c\n    :emphasize-lines: 12-15\n\n    typedef struct {\n        const avs_net_socket_v_table_t *operations;\n        avs_net_socket_t *backend_socket;\n        SSL_CTX *ctx;\n        SSL *ssl;\n\n        char psk[256];\n        size_t psk_size;\n        char identity[128];\n        size_t identity_size;\n\n        bool dane_enabled;\n        char dane_tlsa_association_data_buf[4096];\n        avs_net_socket_dane_tlsa_record_t dane_tlsa_array[4];\n        size_t dane_tlsa_array_size;\n\n        void *session_resumption_buffer;\n        size_t session_resumption_buffer_size;\n\n        char server_name_indication[256];\n        unsigned int dtls_hs_timeout_min_us;\n        unsigned int dtls_hs_timeout_max_us;\n    } tls_socket_impl_t;\n\n* The ``dane_enabled`` field will store the information about whether DANE shall\n  be used for this connection.\n\n* ``dane_tlsa_array`` will hold the DANE TLSA entries to be used for the\n  connection; maximum of 4 entries is supported in this implementation, while\n  ``dane_tlsa_array_size`` shall be the number of entries actually populated.\n\n* ``dane_tlsa_association_data_buf`` will store the actual certificate data;\n  ``dane_tlsa_array`` entries will contain pointers into this buffer.\n\n.. note::\n\n    In actual LwM2M use, at most 1 DANE TLSA entry is ever used.\n\n    This tutorial provides an implementation that support multiple entries for\n    the sake of completeness, but support for only a single entry is sufficient\n    to cover all the cases used by Anjay.\n\nIn OpenSSL, DANE needs to be enabled both for ``SSL_CTX`` and ``SSL`` objects.\nEnabling it for the ``SSL_CTX`` object needs to be done in the\n``configure_certs()`` function, in accordance to the ``dane`` field in\n``avs_net_certificate_info_t``:\n\n.. highlight:: c\n.. snippet-source:: examples/custom-tls/certificates-advanced/src/tls_impl.c\n    :emphasize-lines: 20-23\n\n    static avs_error_t configure_certs(tls_socket_impl_t *sock,\n                                       const avs_net_certificate_info_t *certs) {\n        if (certs->server_cert_validation) {\n            if (!certs->ignore_system_trust_store) {\n                SSL_CTX_set_default_verify_paths(sock->ctx);\n            }\n            X509_STORE *store = SSL_CTX_get_cert_store(sock->ctx);\n            avs_error_t err;\n            if (avs_is_err((err = configure_trusted_certs(\n                                    store, &certs->trusted_certs.desc)))\n                    || avs_is_err((err = configure_cert_revocation_lists(\n                                           store,\n                                           &certs->cert_revocation_lists.desc)))) {\n                return err;\n            }\n            SSL_CTX_set_verify(sock->ctx, SSL_VERIFY_PEER, NULL);\n        } else {\n            SSL_CTX_set_verify(sock->ctx, SSL_VERIFY_NONE, NULL);\n        }\n        sock->dane_enabled = certs->dane;\n        if (sock->dane_enabled) {\n            SSL_CTX_dane_enable(sock->ctx);\n        }\n        if (certs->client_cert.desc.source != AVS_CRYPTO_DATA_SOURCE_EMPTY) {\n            avs_error_t err;\n            if (avs_is_err((err = configure_client_certs(sock->ctx,\n                                                         &certs->client_cert.desc)))\n                    || avs_is_err(err = configure_client_key(sock->ctx,\n                                                             &certs->client_key))) {\n                return err;\n            }\n        }\n\n        return AVS_OK;\n    }\n\nPopulating the array\n^^^^^^^^^^^^^^^^^^^^\n\nDANE TLSA entries are passed into the socket object through the ``set_opt``\noperation with the ``AVS_NET_SOCKET_OPT_DANE_TLSA_ARRAY`` key.\n\nIn OpenSSL, this information can only be provided after specifying the hostname\nfor the ``SSL`` object, which in our code only happens during the ``connect``\noperation. For this reason, we need to store the DANE TLSA entries in our\ninternal structures first.\n\n.. highlight:: c\n.. snippet-source:: examples/custom-tls/certificates-advanced/src/tls_impl.c\n    :emphasize-lines: 5-44\n\n    static avs_error_t tls_set_opt(avs_net_socket_t *sock_,\n                                   avs_net_socket_opt_key_t option_key,\n                                   avs_net_socket_opt_value_t option_value) {\n        tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n        switch (option_key) {\n        case AVS_NET_SOCKET_OPT_DANE_TLSA_ARRAY: {\n            if (option_value.dane_tlsa_array.array_element_count\n                    > AVS_ARRAY_SIZE(sock->dane_tlsa_array)) {\n                return avs_errno(AVS_EINVAL);\n            }\n            avs_net_socket_dane_tlsa_record_t\n                    copied_array[AVS_ARRAY_SIZE(sock->dane_tlsa_array)];\n            char copied_association_data[sizeof(\n                    sock->dane_tlsa_association_data_buf)];\n            size_t copied_association_data_offset = 0;\n            memcpy(copied_array, option_value.dane_tlsa_array.array_ptr,\n                   option_value.dane_tlsa_array.array_element_count\n                           * sizeof(avs_net_socket_dane_tlsa_record_t));\n            for (size_t i = 0; i < option_value.dane_tlsa_array.array_element_count;\n                 ++i) {\n                if (copied_association_data_offset\n                                + option_value.dane_tlsa_array.array_ptr[i]\n                                          .association_data_size\n                        > sizeof(copied_association_data)) {\n                    return avs_errno(AVS_EINVAL);\n                }\n                memcpy(copied_association_data + copied_association_data_offset,\n                       option_value.dane_tlsa_array.array_ptr[i].association_data,\n                       option_value.dane_tlsa_array.array_ptr[i]\n                               .association_data_size);\n                copied_array[i].association_data =\n                        sock->dane_tlsa_association_data_buf\n                        + copied_association_data_offset;\n                copied_association_data_offset +=\n                        option_value.dane_tlsa_array.array_ptr[i]\n                                .association_data_size;\n            }\n            memcpy(sock->dane_tlsa_association_data_buf, copied_association_data,\n                   sizeof(copied_association_data));\n            memcpy(sock->dane_tlsa_array, copied_array, sizeof(copied_array));\n            sock->dane_tlsa_array_size =\n                    option_value.dane_tlsa_array.array_element_count;\n            return AVS_OK;\n        }\n        default:\n            return avs_net_socket_set_opt(sock->backend_socket, option_key,\n                                          option_value);\n        }\n    }\n\nThe above code essentially makes a deep copy of the data in\n``option_value.dane_tlsa_array``. The buffers pointed to by the\n``association_data`` fields within array entries are copied into the\n``sock->dane_tlsa_association_data_buf`` field and pointers in the copied array\nupdated to point into that buffer as well.\n\n.. important::\n\n    Any pointers passed to the ``set_opt`` function with the\n    ``AVS_NET_SOCKET_OPT_DANE_TLSA_ARRAY`` options shall be treated as data that\n    will be invalidated after returning from the function.\n\n    This means that this data needs to be either immediately loaded into the\n    (D)TLS context, or a deep copy otherwise made.\n\nConfiguring the connection\n^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nNow with all the necessary information, we can configure the ``SSL`` object\nduring the ``connect`` operation.\n\nAll the DANE configuration essentially takes place of the\n``SSL_set_tlsext_host_name()`` call:\n\n.. highlight:: c\n.. snippet-source:: examples/custom-tls/certificates-advanced/src/tls_impl.c\n    :emphasize-lines: 20-50\n\n    static avs_error_t perform_handshake(tls_socket_impl_t *sock,\n                                         const char *host) {\n        union {\n            struct sockaddr addr;\n            struct sockaddr_storage storage;\n        } peername;\n        const void *fd_ptr = avs_net_socket_get_system(sock->backend_socket);\n        if (!fd_ptr\n                || getpeername(*(const int *) fd_ptr, &peername.addr,\n                               &(socklen_t) { sizeof(peername) })) {\n            return avs_errno(AVS_EBADF);\n        }\n\n        sock->ssl = SSL_new(sock->ctx);\n        if (!sock->ssl) {\n            return avs_errno(AVS_ENOMEM);\n        }\n\n        SSL_set_app_data(sock->ssl, sock);\n        if (sock->dane_enabled) {\n            // NOTE: SSL_dane_enable() calls SSL_set_tlsext_host_name() internally\n            SSL_dane_enable(sock->ssl, host);\n            bool have_usable_tlsa_records = false;\n            for (size_t i = 0; i < sock->dane_tlsa_array_size; ++i) {\n                if (SSL_CTX_get_verify_mode(sock->ctx) == SSL_VERIFY_NONE\n                        && (sock->dane_tlsa_array[i].certificate_usage\n                                    == AVS_NET_SOCKET_DANE_CA_CONSTRAINT\n                            || sock->dane_tlsa_array[i].certificate_usage\n                                       == AVS_NET_SOCKET_DANE_SERVICE_CERTIFICATE_CONSTRAINT)) {\n                    // PKIX-TA and PKIX-EE constraints are unusable for\n                    // opportunistic clients\n                    continue;\n                }\n                SSL_dane_tlsa_add(\n                        sock->ssl,\n                        (uint8_t) sock->dane_tlsa_array[i].certificate_usage,\n                        (uint8_t) sock->dane_tlsa_array[i].selector,\n                        (uint8_t) sock->dane_tlsa_array[i].matching_type,\n                        (unsigned const char *) sock->dane_tlsa_array[i]\n                                .association_data,\n                        sock->dane_tlsa_array[i].association_data_size);\n                have_usable_tlsa_records = true;\n            }\n            if (SSL_CTX_get_verify_mode(sock->ctx) == SSL_VERIFY_NONE\n                    && have_usable_tlsa_records) {\n                SSL_set_verify(sock->ssl, SSL_VERIFY_PEER, NULL);\n            }\n        } else {\n            SSL_set_tlsext_host_name(sock->ssl, host);\n        }\n        SSL_set1_host(sock->ssl, host);\n\n        BIO *bio = BIO_new_dgram(*(const int *) fd_ptr, 0);\n        if (!bio) {\n            return avs_errno(AVS_ENOMEM);\n        }\n        BIO_ctrl(bio, BIO_CTRL_DGRAM_SET_CONNECTED, 0, &peername.addr);\n        SSL_set_bio(sock->ssl, bio, bio);\n        DTLS_set_timer_cb(sock->ssl, dtls_timer_cb);\n\n        if (sock->session_resumption_buffer) {\n            const unsigned char *ptr =\n                    (const unsigned char *) sock->session_resumption_buffer;\n            SSL_SESSION *session =\n                    d2i_SSL_SESSION(NULL, &ptr,\n                                    sock->session_resumption_buffer_size);\n            if (session) {\n                SSL_set_session(sock->ssl, session);\n                SSL_SESSION_free(session);\n            }\n        }\n\n        if (SSL_connect(sock->ssl) <= 0) {\n            return avs_errno(AVS_EPROTO);\n        }\n        return AVS_OK;\n    }\n\nNote that `\"opportunistic DANE\"\n<https://datatracker.ietf.org/doc/html/rfc7671#section-4.1>`_ is mentioned and\nsupported in the code above. This means that even if server certificate\nverification is not otherwise enabled, but DANE-TA or DANE-EE entries are\npresent, the client shall verify the server certificate against these entries.\n\n.. note::\n\n    Opportunistic DANE is not used by Anjay. An implementation is provided here\n    for the sake of completeness, but it is not necessary for LwM2M\n    communication.\n\n    If only LwM2M compliance is targeted, it is safe to remove the\n    ``if (SSL_CTX_get_verify_mode(sock->ctx) == SSL_VERIFY_NONE && ...)``\n    clauses and the ``have_usable_tlsa_records`` variable from the code above\n    altogether.\n\n.. _custom-tls-api-certificates-advanced-dane-minimum-subset:\n\nMinimum viable subset\n^^^^^^^^^^^^^^^^^^^^^\n\n.. warning::\n\n    The approach described in this section is not fully compliant with DANE nor\n    any version of LwM2M. It is intended **only** for use if implementing more\n    complete support is not possible.\n\nSupport for DANE is, unfortunately, very limited among (D)TLS implementations.\nIn fact, in the default Mbed TLS integration in avs_commons, it has been\nimplemented from scratch in a custom certificate verification callback, see the\n`verify_cert_cb() function\n<https://github.com/AVSystem/avs_commons/blob/master/src/net/mbedtls/avs_mbedtls_socket.c#L535>`_\nthere. In many cases, this approach might still be infeasible or even\nimpossible, especially if (D)TLS is handled in hardware.\n\nIt is possible to emulate the most common case using standard PKIX concepts,\nwhich will allow LwM2M 1.0 (and 1.1 with typical configuration) to work, at\nleast with some servers.\n\n.. important::\n\n    This implementation will **only work with self-signed server certificates**.\n\n.. note::\n\n    Code modified for this variant can be found under\n    `examples/custom-tls/certificates-advanced-fake-dane\n    <https://github.com/AVSystem/Anjay/tree/master/examples/custom-tls/certificates-advanced-fake-dane>`_\n    in the Anjay source directory.\n\nThis minimum implementation reverts the changed described earlier in the\n:ref:`custom-tls-api-certificates-advanced-dane` section. Instead, the following\nchanges are made:\n\n* The only information about DANE that needs to be kept in the socket state is\n  whether or not it is enabled.\n\n.. highlight:: c\n.. snippet-source:: examples/custom-tls/certificates-advanced-fake-dane/src/tls_impl.c\n    :emphasize-lines: 12\n\n    typedef struct {\n        const avs_net_socket_v_table_t *operations;\n        avs_net_socket_t *backend_socket;\n        SSL_CTX *ctx;\n        SSL *ssl;\n\n        char psk[256];\n        size_t psk_size;\n        char identity[128];\n        size_t identity_size;\n\n        bool dane_enabled;\n\n        void *session_resumption_buffer;\n        size_t session_resumption_buffer_size;\n\n        char server_name_indication[256];\n        unsigned int dtls_hs_timeout_min_us;\n        unsigned int dtls_hs_timeout_max_us;\n    } tls_socket_impl_t;\n\n.. highlight:: c\n.. snippet-source:: examples/custom-tls/certificates-advanced-fake-dane/src/tls_impl.c\n    :emphasize-lines: 20\n\n    static avs_error_t configure_certs(tls_socket_impl_t *sock,\n                                       const avs_net_certificate_info_t *certs) {\n        if (certs->server_cert_validation) {\n            if (!certs->ignore_system_trust_store) {\n                SSL_CTX_set_default_verify_paths(sock->ctx);\n            }\n            X509_STORE *store = SSL_CTX_get_cert_store(sock->ctx);\n            avs_error_t err;\n            if (avs_is_err((err = configure_trusted_certs(\n                                    store, &certs->trusted_certs.desc)))\n                    || avs_is_err((err = configure_cert_revocation_lists(\n                                           store,\n                                           &certs->cert_revocation_lists.desc)))) {\n                return err;\n            }\n            SSL_CTX_set_verify(sock->ctx, SSL_VERIFY_PEER, NULL);\n        } else {\n            SSL_CTX_set_verify(sock->ctx, SSL_VERIFY_NONE, NULL);\n        }\n        sock->dane_enabled = certs->dane;\n        if (certs->client_cert.desc.source != AVS_CRYPTO_DATA_SOURCE_EMPTY) {\n            avs_error_t err;\n            if (avs_is_err((err = configure_client_certs(sock->ctx,\n                                                         &certs->client_cert.desc)))\n                    || avs_is_err(err = configure_client_key(sock->ctx,\n                                                             &certs->client_key))) {\n                return err;\n            }\n        }\n\n        return AVS_OK;\n    }\n\n* The ``tls_set_opt()`` function is updated to put the server certificate into\n  the trust store.\n\n.. highlight:: c\n.. snippet-source:: examples/custom-tls/certificates-advanced-fake-dane/src/tls_impl.c\n    :emphasize-lines: 5-37\n\n    static avs_error_t tls_set_opt(avs_net_socket_t *sock_,\n                                   avs_net_socket_opt_key_t option_key,\n                                   avs_net_socket_opt_value_t option_value) {\n        tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n        switch (option_key) {\n        case AVS_NET_SOCKET_OPT_DANE_TLSA_ARRAY: {\n            if (option_value.dane_tlsa_array.array_element_count > 1) {\n                return avs_errno(AVS_EINVAL);\n            }\n            if (!sock->dane_enabled\n                    || option_value.dane_tlsa_array.array_element_count == 0\n                    || option_value.dane_tlsa_array.array_ptr[0].certificate_usage\n                                   == AVS_NET_SOCKET_DANE_CA_CONSTRAINT\n                    || option_value.dane_tlsa_array.array_ptr[0].certificate_usage\n                                   == AVS_NET_SOCKET_DANE_SERVICE_CERTIFICATE_CONSTRAINT) {\n                return AVS_OK;\n            }\n            X509_STORE *store = SSL_CTX_get_cert_store(sock->ctx);\n            if (option_value.dane_tlsa_array.array_ptr[0].selector\n                            != AVS_NET_SOCKET_DANE_CERTIFICATE\n                    || option_value.dane_tlsa_array.array_ptr[0].matching_type\n                                   != AVS_NET_SOCKET_DANE_MATCH_FULL\n                    || sk_X509_OBJECT_num(X509_STORE_get0_objects(store)) > 0) {\n                return avs_errno(AVS_ENOTSUP);\n            }\n            avs_crypto_certificate_chain_info_t chain =\n                    avs_crypto_certificate_chain_info_from_buffer(\n                            option_value.dane_tlsa_array.array_ptr[0]\n                                    .association_data,\n                            option_value.dane_tlsa_array.array_ptr[0]\n                                    .association_data_size);\n            avs_error_t err = configure_trusted_certs(store, &chain.desc);\n            if (avs_is_ok(err)) {\n                SSL_CTX_set_verify(sock->ctx, SSL_VERIFY_PEER, NULL);\n            }\n            return err;\n        }\n        default:\n            return avs_net_socket_set_opt(sock->backend_socket, option_key,\n                                          option_value);\n        }\n    }\n\n* Due to ``configure_trusted_certs()`` being called in the code above, that\n  function's declaration needs to be moved above ``tls_set_opt()``, with no\n  other changes.\n\nIn the code above, only the DANE-TA and DANE-EE mode (Certificate Usage modes 2\nand 3) entries are taken into account, only full certificate matching is\nsupported, and the trust store needs to be empty at the time of calling this\nfunction. If all those conditions are met, the passed certificate is just added\nto the store - ``configure_trusted_certs()`` is called for that purpose as a\nwrapper to the ``d2i_X509()`` and ``X509_STORE_add_cert()`` functions.\n\nThis is enough for the logic used by Anjay for LwM2M 1.0 (and 1.1 on default\nsettings) to work. However, as mentioned above, only self-signed server\ncertificates are supported. DANE-EE mode will not function properly, as\ncertificate verification will fail due to inability to find the CA certificate.\n"
  },
  {
    "path": "doc/sphinx/source/PortingGuideForNonPOSIXPlatforms/CustomTLS/CustomTLS-CertificatesBasic.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nBasic certificate support\n=========================\n\n.. contents:: :local:\n\n.. note::\n\n    Code related to this tutorial can be found under\n    `examples/custom-tls/certificates-basic\n    <https://github.com/AVSystem/Anjay/tree/master/examples/custom-tls/certificates-basic>`_\n    in the Anjay source directory.\n\nIntroduction\n------------\n\nThis tutorials builds up on :doc:`the previous one <CustomTLS-ConfigFeatures>`\nand adds basic support for the security mode based on certificates and public\nkey infrastructure.\n\n.. note::\n\n    In this tutorial, the ``main.c`` file has been replaced with a slightly\n    modified version of the one from the\n    :doc:`../../AdvancedTopics/AT-Certificates` tutorial.\n\n    This also means that for simplicity, the tutorial project now depends on\n    the ``WITH_EVENT_LOOP`` CMake option enabled in Anjay, and the\n    :doc:`../NetworkingAPI/NetworkingAPI-EventLoopSupport` in the networking\n    layer.\n\n    Compared to the ``main.c`` file from the\n    :doc:`../../AdvancedTopics/AT-Certificates` tutorial, logic related to\n    loading the server certificate has been removed. This means that the\n    security information is now configured as follows:\n\n    .. highlight:: c\n    .. snippet-source:: examples/custom-tls/certificates-basic/src/main.c\n\n            if (load_buffer_from_file(\n                        (uint8_t **) &security_instance.public_cert_or_psk_identity,\n                        &security_instance.public_cert_or_psk_identity_size,\n                        \"client_cert.der\")\n                    || load_buffer_from_file(\n                               (uint8_t **) &security_instance.private_cert_or_psk_key,\n                               &security_instance.private_cert_or_psk_key_size,\n                               \"client_key.der\")) {\n                result = -1;\n                goto cleanup;\n            }\n\n.. warning::\n\n    Verifying the server certificate, as specified in LwM2M, does not work with\n    the set of features implemented in this article. See the\n    :ref:`custom-tls-api-certificates-basic-limitations` section and the next\n    article for details.\n\nAdding support for the certificate mode\n---------------------------------------\n\nIn ``avs_net``, the security mode of the socket - either PSK or certificates -\nis configured by the ``mode`` field in the ``avs_net_security_mode_t`` structure\n(which is a tagged union, with the ``mode`` field acting as the tag), which\nitself is contained in the ``security`` field of\n``avs_net_ssl_configuration_t``.\n\n:ref:`In the minimal implementation tutorial <custom-tls-api-create>`, we have\nwritten a ``switch`` over that field in ``_avs_net_create_dtls_socket()``, even\nthough only the PSK mode was supported there. It is now time to add the second\ncase for the certificate mode:\n\n.. highlight:: c\n.. snippet-source:: examples/custom-tls/certificates-basic/src/tls_impl.c\n    :emphasize-lines: 31-33\n\n    avs_error_t _avs_net_create_dtls_socket(avs_net_socket_t **socket_ptr,\n                                            const void *configuration_) {\n        assert(socket_ptr);\n        assert(!*socket_ptr);\n        assert(configuration_);\n        const avs_net_ssl_configuration_t *configuration =\n                (const avs_net_ssl_configuration_t *) configuration_;\n        tls_socket_impl_t *socket =\n                (tls_socket_impl_t *) avs_calloc(1, sizeof(tls_socket_impl_t));\n        if (!socket) {\n            return avs_errno(AVS_ENOMEM);\n        }\n        *socket_ptr = (avs_net_socket_t *) socket;\n        socket->operations = &TLS_SOCKET_VTABLE;\n\n        avs_error_t err = AVS_OK;\n        if (avs_is_ok((err = avs_net_udp_socket_create(\n                               &socket->backend_socket,\n                               &configuration->backend_configuration)))\n                && !(socket->ctx = SSL_CTX_new(DTLS_method()))) {\n            err = avs_errno(AVS_ENOMEM);\n        }\n        if (avs_is_ok(err)) {\n            err = configure_dtls_version(socket, configuration->version);\n        }\n        if (avs_is_ok(err)) {\n            switch (configuration->security.mode) {\n            case AVS_NET_SECURITY_PSK:\n                err = configure_psk(socket, &configuration->security.data.psk);\n                break;\n            case AVS_NET_SECURITY_CERTIFICATE:\n                err = configure_certs(socket, &configuration->security.data.cert);\n                break;\n            default:\n                err = avs_errno(AVS_ENOTSUP);\n            }\n        }\n        if (avs_is_err(err)\n                || avs_is_err((\n                           err = configure_dtls_handshake_timeouts(\n                                   socket, configuration->dtls_handshake_timeouts)))\n                || avs_is_err((err = configure_ciphersuites(\n                                       socket, &configuration->ciphersuites)))\n                || avs_is_err((err = configure_sni(\n                                       socket,\n                                       configuration->server_name_indication)))) {\n            avs_net_socket_cleanup(socket_ptr);\n            return err;\n        }\n        SSL_CTX_set_mode(socket->ctx, SSL_MODE_AUTO_RETRY);\n        if (configuration->session_resumption_buffer_size > 0) {\n            assert(configuration->session_resumption_buffer);\n            socket->session_resumption_buffer =\n                    configuration->session_resumption_buffer;\n            socket->session_resumption_buffer_size =\n                    configuration->session_resumption_buffer_size;\n            SSL_CTX_set_session_cache_mode(\n                    socket->ctx,\n                    SSL_SESS_CACHE_CLIENT | SSL_SESS_CACHE_NO_INTERNAL_STORE);\n            SSL_CTX_sess_set_new_cb(socket->ctx, new_session_cb);\n        }\n        return AVS_OK;\n    }\n\nThe ``configure_certs()`` function mentioned in the snippet above is an analog\nof ``configure_psk()``, that loads and configures all the necessary security\ncredentials:\n\n.. highlight:: c\n.. snippet-source:: examples/custom-tls/certificates-basic/src/tls_impl.c\n\n    static avs_error_t configure_certs(tls_socket_impl_t *sock,\n                                       const avs_net_certificate_info_t *certs) {\n        if (certs->server_cert_validation) {\n            if (!certs->ignore_system_trust_store) {\n                SSL_CTX_set_default_verify_paths(sock->ctx);\n            }\n            X509_STORE *store = SSL_CTX_get_cert_store(sock->ctx);\n            avs_error_t err;\n            if (avs_is_err((err = configure_trusted_certs(\n                                    store, &certs->trusted_certs.desc)))\n                    || avs_is_err((err = configure_cert_revocation_lists(\n                                           store,\n                                           &certs->cert_revocation_lists.desc)))) {\n                return err;\n            }\n            SSL_CTX_set_verify(sock->ctx, SSL_VERIFY_PEER, NULL);\n        } else {\n            SSL_CTX_set_verify(sock->ctx, SSL_VERIFY_NONE, NULL);\n        }\n\n        if (certs->client_cert.desc.source != AVS_CRYPTO_DATA_SOURCE_EMPTY) {\n            avs_error_t err;\n            if (avs_is_err((err = configure_client_cert(sock->ctx,\n                                                        &certs->client_cert)))\n                    || avs_is_err(err = configure_client_key(sock->ctx,\n                                                             &certs->client_key))) {\n                return err;\n            }\n        }\n\n        return AVS_OK;\n    }\n\nThe ``server_cert_validation`` field acts as a master switch that controls\nwhether the peer certificate shall be verified at all. This controls the\nverification mode set using ``SSL_CTX_set_verify()``, but also all logic related\nto loading the trust store is disabled if it is set to ``false``.\n\nThe ``ignore_system_trust_store`` flag controls whether the default system trust\nstore shall be loaded for this socket. In Anjay, it is usually set to ``true``.\nIt may only be ``false``, if the ``use_system_trust_store`` is enabled in\n``anjay_configuration_t``. If your platform does not have a concept of a system\ntrust store, it is safe to ignore this setting altogether.\n\nThe rest of the code in this function calls auxiliary functions that load all\nthe security credential types: trusted certificates, certificate revocation\nlists, the client certificate and the client private key.\n\nLoading security credentials\n----------------------------\n\nThe security credentials related to the public key infrastructure utilize the\n``avs_crypto_security_info_union_t`` that has been previously described in\n:ref:`custom-tls-security-info-union-type` chapter. You might want to recap the\ninformation contained there before continuing with this tutorial.\n\n.. important::\n\n    The security credential objects passed to the\n    ``_avs_net_create_dtls_socket()`` may be deleted after that call completes.\n    For this reason, the credential data needs to be actually copied.\n\n    Please carefully check whether credentials are passed by value or by\n    reference in the TLS backend you are integrating with.\n\nLoading client certificates\n^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nClient certificates is the simplest case, as we only need to load a single\ncertificate, handling the ``AVS_CRYPTO_DATA_SOURCE_BUFFER`` case:\n\n.. highlight:: c\n.. snippet-source:: examples/custom-tls/certificates-basic/src/tls_impl.c\n\n    static avs_error_t\n    configure_client_cert(SSL_CTX *ctx,\n                          const avs_crypto_certificate_chain_info_t *client_cert) {\n        switch (client_cert->desc.source) {\n        case AVS_CRYPTO_DATA_SOURCE_BUFFER: {\n            const unsigned char *ptr =\n                    (const unsigned char *) client_cert->desc.info.buffer.buffer;\n            X509 *cert = d2i_X509(NULL, &ptr,\n                                  (long) client_cert->desc.info.buffer.buffer_size);\n            if (!cert) {\n                return avs_errno(AVS_EPROTO);\n            }\n\n            int result = SSL_CTX_use_certificate(ctx, cert);\n            X509_free(cert);\n            if (result != 1) {\n                return avs_errno(AVS_EPROTO);\n            }\n            return AVS_OK;\n        }\n        default:\n            return avs_errno(AVS_ENOTSUP);\n        }\n    }\n\n.. note::\n\n    In this tutorial, only DER-encoded credentials are supported. This is most\n    important and enough for compatibility with LwM2M. However, you may want to\n    also support the PEM format. If both formats are supported, they shall be\n    autodetected based on the contents of the file or buffer.\n\nLoading client private keys\n^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nThe code for loading client private keys is very similar, although we want to\nmake sure that the ``password`` field is not used.\n\n.. highlight:: c\n.. snippet-source:: examples/custom-tls/certificates-basic/src/tls_impl.c\n    :emphasize-lines: 6-8\n\n    static avs_error_t\n    configure_client_key(SSL_CTX *ctx,\n                         const avs_crypto_private_key_info_t *client_key) {\n        switch (client_key->desc.source) {\n        case AVS_CRYPTO_DATA_SOURCE_BUFFER: {\n            if (client_key->desc.info.buffer.password) {\n                return avs_errno(AVS_ENOTSUP);\n            }\n            const unsigned char *ptr =\n                    (const unsigned char *) client_key->desc.info.buffer.buffer;\n            EVP_PKEY *key = d2i_AutoPrivateKey(\n                    NULL, &ptr, (long) client_key->desc.info.buffer.buffer_size);\n            if (!key) {\n                return avs_errno(AVS_EPROTO);\n            }\n\n            int result = SSL_CTX_use_PrivateKey(ctx, key);\n            EVP_PKEY_free(key);\n            if (result != 1) {\n                return avs_errno(AVS_EPROTO);\n            }\n            return AVS_OK;\n        }\n        default:\n            return avs_errno(AVS_ENOTSUP);\n        }\n    }\n\nLoading trusted certificates\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nFor the trusted certificates, we need to support the empty and compound sources\nin addition to loading a simple single buffer:\n\n.. highlight:: c\n.. snippet-source:: examples/custom-tls/certificates-basic/src/tls_impl.c\n    :emphasize-lines: 7, 12-13, 27-28, 33-52\n\n    #include <openssl/err.h>\n\n    // ...\n\n    static avs_error_t\n    configure_trusted_certs(X509_STORE *store,\n                            const avs_crypto_security_info_union_t *trusted_certs) {\n        if (!trusted_certs) {\n            return avs_errno(AVS_EINVAL);\n        }\n        switch (trusted_certs->source) {\n        case AVS_CRYPTO_DATA_SOURCE_EMPTY:\n            return AVS_OK;\n        case AVS_CRYPTO_DATA_SOURCE_BUFFER: {\n            const unsigned char *ptr =\n                    (const unsigned char *) trusted_certs->info.buffer.buffer;\n            X509 *cert = d2i_X509(NULL, &ptr,\n                                  (long) trusted_certs->info.buffer.buffer_size);\n            if (!cert) {\n                return avs_errno(AVS_EPROTO);\n            }\n\n            ERR_clear_error();\n            int result = X509_STORE_add_cert(store, cert);\n            X509_free(cert);\n            if (!result\n                    && ERR_GET_REASON(ERR_get_error())\n                                   != X509_R_CERT_ALREADY_IN_HASH_TABLE) {\n                return avs_errno(AVS_EPROTO);\n            }\n            return AVS_OK;\n        }\n        case AVS_CRYPTO_DATA_SOURCE_ARRAY: {\n            avs_error_t err = AVS_OK;\n            for (size_t i = 0;\n                 avs_is_ok(err) && i < trusted_certs->info.array.element_count;\n                 ++i) {\n                err = configure_trusted_certs(\n                        store, &trusted_certs->info.array.array_ptr[i]);\n            }\n            return err;\n        }\n        case AVS_CRYPTO_DATA_SOURCE_LIST: {\n            avs_error_t err = AVS_OK;\n            AVS_LIST(avs_crypto_security_info_union_t) entry;\n            AVS_LIST_FOREACH(entry, trusted_certs->info.list.list_head) {\n                if (avs_is_err((err = configure_trusted_certs(store, entry)))) {\n                    break;\n                }\n            }\n            return AVS_OK;\n        }\n        default:\n            return avs_errno(AVS_ENOTSUP);\n        }\n    }\n\nPlease note the following additional alterations:\n\n* This function takes an argument of type ``avs_crypto_security_info_union_t``\n  instead of the ``avs_crypto_certificate_chain_info_t`` wrapper. This has been\n  done so that it can be more easily called recursively.\n* There is a special case for the ``X509_R_CERT_ALREADY_IN_HASH_TABLE`` error.\n  Loading the same certificate multiple times shall be permitted, in case e.g.\n  an explicitly specified certificate is already present in the system trust\n  store.\n\nLoading certificate revocation lists\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nThe CRL loading function is actually almost identical to the certificate chain\nloading one:\n\n.. highlight:: c\n.. snippet-source:: examples/custom-tls/certificates-basic/src/tls_impl.c\n\n    static avs_error_t configure_cert_revocation_lists(\n            X509_STORE *store,\n            const avs_crypto_security_info_union_t *cert_revocation_lists) {\n        if (!cert_revocation_lists) {\n            return avs_errno(AVS_EINVAL);\n        }\n        switch (cert_revocation_lists->source) {\n        case AVS_CRYPTO_DATA_SOURCE_EMPTY:\n            return AVS_OK;\n        case AVS_CRYPTO_DATA_SOURCE_BUFFER: {\n            const unsigned char *ptr =\n                    (const unsigned char *)\n                            cert_revocation_lists->info.buffer.buffer;\n            X509_CRL *crl = d2i_X509_CRL(\n                    NULL, &ptr,\n                    (long) cert_revocation_lists->info.buffer.buffer_size);\n            if (!crl) {\n                return avs_errno(AVS_EPROTO);\n            }\n\n            ERR_clear_error();\n            int result = X509_STORE_add_crl(store, crl);\n            X509_CRL_free(crl);\n            if (result != 1) {\n                return avs_errno(AVS_EPROTO);\n            }\n            return AVS_OK;\n        }\n        case AVS_CRYPTO_DATA_SOURCE_ARRAY: {\n            avs_error_t err = AVS_OK;\n            for (size_t i = 0;\n                 avs_is_ok(err)\n                 && i < cert_revocation_lists->info.array.element_count;\n                 ++i) {\n                err = configure_cert_revocation_lists(\n                        store, &cert_revocation_lists->info.array.array_ptr[i]);\n            }\n            return err;\n        }\n        case AVS_CRYPTO_DATA_SOURCE_LIST: {\n            avs_error_t err = AVS_OK;\n            AVS_LIST(avs_crypto_security_info_union_t) entry;\n            AVS_LIST_FOREACH(entry, cert_revocation_lists->info.list.list_head) {\n                if (avs_is_err((\n                            err = configure_cert_revocation_lists(store, entry)))) {\n                    break;\n                }\n            }\n            return AVS_OK;\n        }\n        default:\n            return avs_errno(AVS_ENOTSUP);\n        }\n    }\n\nEnabling hostname verification\n------------------------------\n\nNow that all the credentials are properly loaded, the only thing left is to\ninform the TLS library of the hostname, so that the CN or SAN fields of the\nserver certificate can be properly verified. This can be done by calling\n``SSL_set1_host()`` just before the handshake:\n\n.. highlight:: c\n.. snippet-source:: examples/custom-tls/certificates-basic/src/tls_impl.c\n    :emphasize-lines: 21\n\n    static avs_error_t perform_handshake(tls_socket_impl_t *sock,\n                                         const char *host) {\n        union {\n            struct sockaddr addr;\n            struct sockaddr_storage storage;\n        } peername;\n        const void *fd_ptr = avs_net_socket_get_system(sock->backend_socket);\n        if (!fd_ptr\n                || getpeername(*(const int *) fd_ptr, &peername.addr,\n                               &(socklen_t) { sizeof(peername) })) {\n            return avs_errno(AVS_EBADF);\n        }\n\n        sock->ssl = SSL_new(sock->ctx);\n        if (!sock->ssl) {\n            return avs_errno(AVS_ENOMEM);\n        }\n\n        SSL_set_app_data(sock->ssl, sock);\n        SSL_set_tlsext_host_name(sock->ssl, host);\n        SSL_set1_host(sock->ssl, host);\n\n        BIO *bio = BIO_new_dgram(*(const int *) fd_ptr, 0);\n        if (!bio) {\n            return avs_errno(AVS_ENOMEM);\n        }\n        BIO_ctrl(bio, BIO_CTRL_DGRAM_SET_CONNECTED, 0, &peername.addr);\n        SSL_set_bio(sock->ssl, bio, bio);\n        DTLS_set_timer_cb(sock->ssl, dtls_timer_cb);\n\n        if (sock->session_resumption_buffer) {\n            const unsigned char *ptr =\n                    (const unsigned char *) sock->session_resumption_buffer;\n            SSL_SESSION *session =\n                    d2i_SSL_SESSION(NULL, &ptr,\n                                    sock->session_resumption_buffer_size);\n            if (session) {\n                SSL_set_session(sock->ssl, session);\n                SSL_SESSION_free(session);\n            }\n        }\n\n        if (SSL_connect(sock->ssl) <= 0) {\n            return avs_errno(AVS_EPROTO);\n        }\n        return AVS_OK;\n    }\n\n.. _custom-tls-api-certificates-basic-limitations:\n\nLimitations\n-----------\n\nThe implementation above is a complete basic integration with the private key\ninfrastructure, however it lacks a number of features that are supported by the\n``avs_net`` API:\n\n* Lack of DANE support. **This means that the Server Public Key LwM2M resource\n  is not supported, and will cause a failure if used.** This is because LwM2M\n  does not use standard certificate validation logic based on a trust store,\n  using a custom mechanism instead. However, that mechanism is almost identical\n  to the one used by `DANE\n  <https://en.wikipedia.org/wiki/DNS-based_Authentication_of_Named_Entities>`_,\n  so it is implemented in terms of that mechanism in Anjay and ``avs_net``.\n\n  This feature will be discussed in the next tutorial.\n\n* Lack of support for loading chains of more than one certificate as the client\n  certificate chain, although this is rarely used.\n\n* Lack of support for loading credential information from other sources than\n  memory buffers (e.g. files). This may be used e.g. for HTTPS downloads and\n  support of Hardware Security Modules.\n\n* Lack of support for PEM encoding. This is not generally necessary for LwM2M\n  compliance, but may be important for other cases, for example loading\n  credentials from files, as mentioned above.\n\n* Lack of support for the ``rebuild_client_cert_chain`` flag in\n  ``avs_net_certificate_info_t``. When that flag is supported and enabled, the\n  TLS implementation shall find appropriate CA certificates in the trust store,\n  to rebuild the full certification chain of the single certificate specified as\n  the client certificate, and send that complete chain to the server during the\n  handshake.\n\n  This feature may be required for communication with some servers. However, it\n  is complex to implement, usually requiring the use of advanced low-level APIs\n  of the TLS library. For this reason it will not be discussed further in the\n  tutorial.\n"
  },
  {
    "path": "doc/sphinx/source/PortingGuideForNonPOSIXPlatforms/CustomTLS/CustomTLS-ConfigFeatures.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nAdvanced configuration features\n===============================\n\n.. contents:: :local:\n\n.. note::\n\n    Code related to this tutorial can be found under\n    `examples/custom-tls/config-features\n    <https://github.com/AVSystem/Anjay/tree/master/examples/custom-tls/config-features>`_\n    in the Anjay source directory.\n\nIntroduction\n------------\n\nThis tutorial builds up on :doc:`the previous one <CustomTLS-Resumption>` and\nadds support for some of the more advanced features configurable via the\n``avs_net_ssl_configuration_t`` structure.\n\nSpecifically, the following features are implemented:\n\n* Configurable DTLS version\n* Configurable DTLS handshake timers\n* Configurable ciphersuite list\n* Overriding the hostname used for Server Name Identification\n\nThe following features present in ``avs_net_ssl_configuration_t`` are **NOT**\nimplemented here:\n\n* Support for the ``additional_configuration_clb`` field. The semantics of this\n  field are backend-specific, so you are free to handle it in any way you wish\n  if you decide to do so. In the default integrations it is called at the very\n  end of ``_avs_net_create_ssl_socket()``/``_avs_net_create_dtls_socket()`` and\n  passed the ``SSL_CTX *``, ``mbedtls_ssl_config *`` or ``dtls_context_t *``\n  pointer, depending on the backend.\n* Support for DTLS Connection ID extension - there is no support for this in any\n  version of OpenSSL for now. If you wish to implement this, you should just\n  enable support for it if the ``use_connection_id`` field is set to ``true``.\n* Support for the ``prng_ctx`` field. The contract for this field actually\n  requires it to be populated and the intention is that the PRNG provided that\n  way will be used when generating cryptographic material that depends on\n  randomness. However, it is generally OK to ignore this field if entropy source\n  is provided by other means.\n\nIt is generally fine to ignore some or all of the\n``avs_net_ssl_configuration_t`` advanced feature settings - most of these\nfeatures are intended to expose more fine-grained control to the user, and that\ncan just be baked directly into the implementation if it's custom.\n\nHowever, please bear in mind that ciphersuite list and Server Name\nIdentification hostname can be controlled through the LwM2M data model, when\nLwM2M 1.1 is in use.\n\nConfigurable DTLS version\n-------------------------\n\n.. highlight:: c\n.. snippet-source:: examples/custom-tls/config-features/src/tls_impl.c\n\n    static avs_error_t configure_dtls_version(tls_socket_impl_t *sock,\n                                              avs_net_ssl_version_t version) {\n        switch (version) {\n        case AVS_NET_SSL_VERSION_DEFAULT:\n            return AVS_OK;\n        case AVS_NET_SSL_VERSION_TLSv1:\n        case AVS_NET_SSL_VERSION_TLSv1_1:\n            SSL_CTX_set_min_proto_version(sock->ctx, DTLS1_VERSION);\n            return AVS_OK;\n        case AVS_NET_SSL_VERSION_TLSv1_2:\n            SSL_CTX_set_min_proto_version(sock->ctx, DTLS1_2_VERSION);\n            return AVS_OK;\n        default:\n            return avs_errno(AVS_ENOTSUP);\n        }\n    }\n\nThe ``version`` values comes from the ``version`` field of the\n``avs_net_ssl_configuration_t`` structure. This function will be called early on\nduring the socket object initialization, to configure the minimum supported\nversion for the given ``SSL_CTX`` object.\n\nVersion numbers related to the TLS protocol as used over TCP are used for this\nfield. By convention, for DTLS that shall translate to the DTLS version that was\ndirectly based on the specified TLS version, with the exception of TLS 1.0,\nwhich by convention also translates to DTLS 1.0, even though it was based on\nTLS 1.1.\n\nThese are the intended semantics of this field - to configure the minimum\nversion that is allowed for communication, i.e. attempting to connect to a\nserver that only supports versions earlier than the one configured shall fail.\n\nIn some cases, it might only be possible to configure the *only* version\nsupported, i.e. configuring the context for DTLS 1.0 would cause the client to\nspecifically negotiate DTLS 1.0 and disallow any other version, either older or\nnewer. This is also an acceptable behavior for this option, although configuring\nonly the *minimum* version is preferred whenever possible.\n\nConfigurable DTLS handshake timers\n----------------------------------\n\nWhen exchanging application data, DTLS acts as a thin wrapper over the\nunderlying transport protocol (e.g. UDP) and does not implement any reliability\nmechanisms of its own. However, basic reliability is provided during the\nhandshake phase.\n\nHandshake messages are retransmitted if a response is not received for a given\ntimeout period. This period raises exponentially with each attempt.\n\nBy default, the first timeout is 1 second, and it is doubled with each\nfollowing retransmission, with the final timeout being 60 seconds (instead of 64\n- the calculated timeout is clamped to the upper limit), after which the client\ngives up and reports failure. Maximum time between the first message\ntransmission attempt and the failure report is thus 123 seconds (1 + 2 + 3 + 4 +\n8 + 16 + 32 + 60).\n\nAnjay APIs allow customizing these lower and upper limits of 1 and 60 seconds.\nIn OpenSSL, this logic can be implemented using a callback that overrides the\ndefault doubling logic, configured using ``DTLS_set_timer_cb()``.\n\nThese values need to be stored somewhere so that we can read them during the\nhandshake. OpenSSL APIs use microseconds represented as ``unsigned int`` for\nthis purpose, so let's use that in ``tls_socket_impl_t`` as well:\n\n.. highlight:: c\n.. snippet-source:: examples/custom-tls/config-features/src/tls_impl.c\n    :emphasize-lines: 16-17\n\n    typedef struct {\n        const avs_net_socket_v_table_t *operations;\n        avs_net_socket_t *backend_socket;\n        SSL_CTX *ctx;\n        SSL *ssl;\n\n        char psk[256];\n        size_t psk_size;\n        char identity[128];\n        size_t identity_size;\n\n        void *session_resumption_buffer;\n        size_t session_resumption_buffer_size;\n\n        char server_name_indication[256];\n        unsigned int dtls_hs_timeout_min_us;\n        unsigned int dtls_hs_timeout_max_us;\n    } tls_socket_impl_t;\n\nThese values shall be populated based on the ``dtls_handshake_timeouts`` field\nin ``avs_net_ssl_configuration_t``, and default to 1 and 60 seconds if that is\nabsent:\n\n.. highlight:: c\n.. snippet-source:: examples/custom-tls/config-features/src/tls_impl.c\n\n    static avs_error_t configure_dtls_handshake_timeouts(\n            tls_socket_impl_t *sock,\n            const avs_net_dtls_handshake_timeouts_t *dtls_handshake_timeouts) {\n        uint64_t min_us = 1000000, max_us = 60000000;\n        if (dtls_handshake_timeouts) {\n            avs_time_duration_to_scalar(&min_us, AVS_TIME_US,\n                                        dtls_handshake_timeouts->min);\n            avs_time_duration_to_scalar(&max_us, AVS_TIME_US,\n                                        dtls_handshake_timeouts->max);\n        }\n        sock->dtls_hs_timeout_min_us = (unsigned int) min_us;\n        sock->dtls_hs_timeout_max_us = (unsigned int) max_us;\n        return AVS_OK;\n    }\n\nWe can now implement and apply the timer callback function:\n\n.. highlight:: c\n.. snippet-source:: examples/custom-tls/config-features/src/tls_impl.c\n    :emphasize-lines: 1-16, 45\n\n    static unsigned int dtls_timer_cb(SSL *s, unsigned int timer_us) {\n        tls_socket_impl_t *sock = (tls_socket_impl_t *) SSL_get_app_data(s);\n        if (!timer_us) {\n            return sock->dtls_hs_timeout_min_us;\n        } else if (timer_us >= sock->dtls_hs_timeout_max_us) {\n            // maximum number of retransmissions reached, let's give up\n            avs_net_socket_shutdown(sock->backend_socket);\n            return 0;\n        } else {\n            timer_us *= 2;\n            if (timer_us > sock->dtls_hs_timeout_max_us) {\n                timer_us = sock->dtls_hs_timeout_max_us;\n            }\n            return timer_us;\n        }\n    }\n\n    static avs_error_t perform_handshake(tls_socket_impl_t *sock,\n                                         const char *host) {\n        union {\n            struct sockaddr addr;\n            struct sockaddr_storage storage;\n        } peername;\n        const void *fd_ptr = avs_net_socket_get_system(sock->backend_socket);\n        if (!fd_ptr\n                || getpeername(*(const int *) fd_ptr, &peername.addr,\n                               &(socklen_t) { sizeof(peername) })) {\n            return avs_errno(AVS_EBADF);\n        }\n\n        sock->ssl = SSL_new(sock->ctx);\n        if (!sock->ssl) {\n            return avs_errno(AVS_ENOMEM);\n        }\n\n        SSL_set_app_data(sock->ssl, sock);\n        SSL_set_tlsext_host_name(sock->ssl, host);\n\n        BIO *bio = BIO_new_dgram(*(const int *) fd_ptr, 0);\n        if (!bio) {\n            return avs_errno(AVS_ENOMEM);\n        }\n        BIO_ctrl(bio, BIO_CTRL_DGRAM_SET_CONNECTED, 0, &peername.addr);\n        SSL_set_bio(sock->ssl, bio, bio);\n        DTLS_set_timer_cb(sock->ssl, dtls_timer_cb);\n\n        if (sock->session_resumption_buffer) {\n            const unsigned char *ptr =\n                    (const unsigned char *) sock->session_resumption_buffer;\n            SSL_SESSION *session =\n                    d2i_SSL_SESSION(NULL, &ptr,\n                                    sock->session_resumption_buffer_size);\n            if (session) {\n                SSL_set_session(sock->ssl, session);\n                SSL_SESSION_free(session);\n            }\n        }\n\n        if (SSL_connect(sock->ssl) <= 0) {\n            return avs_errno(AVS_EPROTO);\n        }\n        return AVS_OK;\n    }\n\nNote the call to ``avs_net_socket_shutdown(sock->backend_socket)`` - OpenSSL has\na hardcoded number of retransmissions regardless of how the timers are\ncalculated, so to break the process early, if needed, we ensure that the\nunderlying socket will not be able to receive or transmit data. This will cause\n``SSL_connect()`` to fail due to failing ``send()`` or ``recv()``.\n\nIn other TLS implementations, this might not be a problem. In Mbed TLS for\nexample, a simple call to ``mbedtls_ssl_conf_handshake_timeout()`` already\nprovides the expected semantics.\n\nConfigurable ciphersuite list\n-----------------------------\n\nOpenSSL allows configuring the ciphersuite list using a specially prepared\nstring. For example, to configure the use of the two ciphersuites mentioned in\nthe LwM2M specification for the PSK mode (``TLS_PSK_WITH_AES_128_CCM_8`` and\n``TLS_PSK_WITH_AES_128_CBC_SHA256``) and no others, you can configure the\nciphersuite list as ``\"-ALL:PSK-AES128-CCM8:PSK-AES128-CBC-SHA256\"``. The\n``-ALL`` part disables the default ciphersuite list, while the other two parts\nare OpenSSL-specific names for the ciphersuites.\n\nIn ``avs_net``, the ciphersuites are passed as an array of integers with\nciphersuite IDs as transmitted over the wire in TLS, so, for example,\n``TLS_PSK_WITH_AES_128_CCM_8`` is represented as ``0xC0A8`` - see\nhttps://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-4\nfor a full list of known IDs.\n\nWe need to write a function that converts this array into the string format\nexpected by OpenSSL:\n\n.. highlight:: c\n.. snippet-source:: examples/custom-tls/config-features/src/tls_impl.c\n    :emphasize-lines: 11, 22\n\n    static avs_error_t\n    configure_ciphersuites(tls_socket_impl_t *sock,\n                           const avs_net_socket_tls_ciphersuites_t *ciphersuites) {\n        if (!ciphersuites->num_ids) {\n            return AVS_OK;\n        }\n        SSL *dummy_ssl = SSL_new(sock->ctx);\n        if (!dummy_ssl) {\n            return avs_errno(AVS_ENOMEM);\n        }\n        char cipher_list[1024] = \"-ALL\";\n        char *cipher_list_ptr = cipher_list + strlen(cipher_list);\n        const char *const cipher_list_end = cipher_list + sizeof(cipher_list);\n        for (size_t i = 0; i < ciphersuites->num_ids; ++i) {\n            unsigned char id_as_chars[] = {\n                (unsigned char) (ciphersuites->ids[i] >> 8),\n                (unsigned char) (ciphersuites->ids[i] & 0xFF)\n            };\n            const SSL_CIPHER *cipher = SSL_CIPHER_find(dummy_ssl, id_as_chars);\n            if (cipher) {\n                const char *name = SSL_CIPHER_get_name(cipher);\n                if (!!strstr(name, \"PSK\") == !!sock->psk_size\n                        && cipher_list_ptr + 1 + strlen(name) < cipher_list_end) {\n                    *cipher_list_ptr++ = ':';\n                    strcpy(cipher_list_ptr, name);\n                    cipher_list_ptr += strlen(name);\n                }\n            }\n        }\n        SSL_free(dummy_ssl);\n        SSL_CTX_set_cipher_list(sock->ctx, cipher_list);\n        // NOTE: Configuring the set of supported new-style ciphersuites as defined\n        // for TLS 1.3 are not supported by this function.\n        return AVS_OK;\n    }\n\nThe ``-ALL`` at the beginning disables the default configuration, which is not\ndone implicitly in OpenSSL.\n\nNote the other highlighted line, with the\n``!!strstr(name, \"PSK\") == !!sock->psk_size`` condition. This ensures that only\nPSK-compatible ciphersuites are configured when PSK is in use, and that those\nare not used when certificate-based security is in use (certificate support has\nnot been yet discussed in this tutorial, but it will be in subsequent chapters).\nThis is required for proper interoperability with some servers - a ciphersuite\nincompatible with the intended security mode might be selected, preventing the\nhandshake from succeeding. This may especially occur if the server supports both\nPSK and certificate modes on the same port.\n\n.. _custom-tls-tls13-ciphersuites:\n\n.. note::\n\n    TLS 1.3 and DTLS 1.3 have introduced a new kind of ciphersuites, which no\n    longer include the key exchange algorithm and authentication mechanism as\n    part of the suite, with those being negotiated separately. Ciphersuites of\n    this kind can be used for **both** PSK and certificate modes.\n\n    Depending on the underlying (D)TLS implementation, these new-style\n    ciphersuites may need to be handled separately. For example in OpenSSL,\n    these are configured using the new `SSL_CTX_set_ciphersuites()\n    <https://www.openssl.org/docs/man3.0/man3/SSL_CTX_set_cipher_list.html>`_\n    function.\n\n    This is not illustrated in this tutorial due to low level of support for\n    TLS 1.3 and especially DTLS 1.3 in mainstream implementations at the time of\n    writing. Please refer to the `reference implementation\n    <https://github.com/AVSystem/avs_commons/blob/master/src/net/openssl/avs_openssl.c#L649>`_\n    for more information on how this can be implemented.\n\nOverriding the hostname used for SNI\n------------------------------------\n\nWe already have most of the logic related to SNI implemented in the\n``perform_handshake()`` function. However, this is currently locked to the\nhostname provided to the ``connect`` operation. However, it is relatively simple\nto allow overriding this value.\n\nFirst, we need to reserve a place to store the overridden hostname:\n\n.. highlight:: c\n.. snippet-source:: examples/custom-tls/config-features/src/tls_impl.c\n    :emphasize-lines: 15\n\n    typedef struct {\n        const avs_net_socket_v_table_t *operations;\n        avs_net_socket_t *backend_socket;\n        SSL_CTX *ctx;\n        SSL *ssl;\n\n        char psk[256];\n        size_t psk_size;\n        char identity[128];\n        size_t identity_size;\n\n        void *session_resumption_buffer;\n        size_t session_resumption_buffer_size;\n\n        char server_name_indication[256];\n        unsigned int dtls_hs_timeout_min_us;\n        unsigned int dtls_hs_timeout_max_us;\n    } tls_socket_impl_t;\n\nThis value shall be populated based on the ``server_name_indication`` field\nin ``avs_net_ssl_configuration_t``:\n\n.. highlight:: c\n.. snippet-source:: examples/custom-tls/config-features/src/tls_impl.c\n\n    static avs_error_t configure_sni(tls_socket_impl_t *sock,\n                                     const char *server_name_indication) {\n        if (server_name_indication) {\n            if (strlen(server_name_indication)\n                    >= sizeof(sock->server_name_indication)) {\n                return avs_errno(AVS_ENOBUFS);\n            }\n            strcpy(sock->server_name_indication, server_name_indication);\n        }\n        return AVS_OK;\n    }\n\nThis value can now, if present, override the hostname when calling\n``perform_handshake()``:\n\n.. highlight:: c\n.. snippet-source:: examples/custom-tls/config-features/src/tls_impl.c\n    :emphasize-lines: 10-13\n\n    static avs_error_t\n    tls_connect(avs_net_socket_t *sock_, const char *host, const char *port) {\n        tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n        if (sock->ssl) {\n            return avs_errno(AVS_EBADF);\n        }\n        avs_error_t err;\n        if (avs_is_err((\n                    err = avs_net_socket_connect(sock->backend_socket, host, port)))\n                || avs_is_err((err = perform_handshake(\n                                       sock, sock->server_name_indication[0]\n                                                     ? sock->server_name_indication\n                                                     : host)))) {\n            if (sock->ssl) {\n                SSL_free(sock->ssl);\n                sock->ssl = NULL;\n            }\n            avs_net_socket_close(sock->backend_socket);\n        }\n        return err;\n    }\n\nApplying the configuration\n--------------------------\n\nHaving written all the ``configure_*`` functions, they can be called during\nsocket creation in ``_avs_net_create_dtls_socket()``:\n\n.. highlight:: c\n.. snippet-source:: examples/custom-tls/config-features/src/tls_impl.c\n    :emphasize-lines: 23-25, 36-43\n\n    avs_error_t _avs_net_create_dtls_socket(avs_net_socket_t **socket_ptr,\n                                            const void *configuration_) {\n        assert(socket_ptr);\n        assert(!*socket_ptr);\n        assert(configuration_);\n        const avs_net_ssl_configuration_t *configuration =\n                (const avs_net_ssl_configuration_t *) configuration_;\n        tls_socket_impl_t *socket =\n                (tls_socket_impl_t *) avs_calloc(1, sizeof(tls_socket_impl_t));\n        if (!socket) {\n            return avs_errno(AVS_ENOMEM);\n        }\n        *socket_ptr = (avs_net_socket_t *) socket;\n        socket->operations = &TLS_SOCKET_VTABLE;\n\n        avs_error_t err = AVS_OK;\n        if (avs_is_ok((err = avs_net_udp_socket_create(\n                               &socket->backend_socket,\n                               &configuration->backend_configuration)))\n                && !(socket->ctx = SSL_CTX_new(DTLS_method()))) {\n            err = avs_errno(AVS_ENOMEM);\n        }\n        if (avs_is_ok(err)) {\n            err = configure_dtls_version(socket, configuration->version);\n        }\n        if (avs_is_ok(err)) {\n            switch (configuration->security.mode) {\n            case AVS_NET_SECURITY_PSK:\n                err = configure_psk(socket, &configuration->security.data.psk);\n                break;\n            default:\n                err = avs_errno(AVS_ENOTSUP);\n            }\n        }\n        if (avs_is_err(err)\n                || avs_is_err((\n                           err = configure_dtls_handshake_timeouts(\n                                   socket, configuration->dtls_handshake_timeouts)))\n                || avs_is_err((err = configure_ciphersuites(\n                                       socket, &configuration->ciphersuites)))\n                || avs_is_err((err = configure_sni(\n                                       socket,\n                                       configuration->server_name_indication)))) {\n            avs_net_socket_cleanup(socket_ptr);\n            return err;\n        }\n        SSL_CTX_set_mode(socket->ctx, SSL_MODE_AUTO_RETRY);\n        if (configuration->session_resumption_buffer_size > 0) {\n            assert(configuration->session_resumption_buffer);\n            socket->session_resumption_buffer =\n                    configuration->session_resumption_buffer;\n            socket->session_resumption_buffer_size =\n                    configuration->session_resumption_buffer_size;\n            SSL_CTX_set_session_cache_mode(\n                    socket->ctx,\n                    SSL_SESS_CACHE_CLIENT | SSL_SESS_CACHE_NO_INTERNAL_STORE);\n            SSL_CTX_sess_set_new_cb(socket->ctx, new_session_cb);\n        }\n        return AVS_OK;\n    }\n"
  },
  {
    "path": "doc/sphinx/source/PortingGuideForNonPOSIXPlatforms/CustomTLS/CustomTLS-Minimal.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nMinimal DTLS implementation\n===========================\n\n.. contents:: :local:\n\n.. note::\n\n    Code related to this tutorial can be found under\n    `examples/custom-tls/minimal\n    <https://github.com/AVSystem/Anjay/tree/master/examples/custom-tls/minimal>`_\n    in the Anjay source directory.\n\nIntroduction\n------------\n\nThis tutorial builds up on :doc:`the previous one <CustomTLS-Stub>` and adds\nlogic related to actually initializing the SSL context state and performing the\nDTLS handshake.\n\nOnly the bare minimum functionality necessary to use DTLS in PSK mode is\nimplemented for now - but this is enough to register to a LwM2M server in PSK\nmode.\n\nImplementation of the DTLS socket\n---------------------------------\n\n.. _custom-tls-api-create:\n\nInitialization\n^^^^^^^^^^^^^^\n\n.. highlight:: c\n.. snippet-source:: examples/custom-tls/minimal/src/tls_impl.c\n\n    avs_error_t _avs_net_create_dtls_socket(avs_net_socket_t **socket_ptr,\n                                            const void *configuration_) {\n        assert(socket_ptr);\n        assert(!*socket_ptr);\n        assert(configuration_);\n        const avs_net_ssl_configuration_t *configuration =\n                (const avs_net_ssl_configuration_t *) configuration_;\n        tls_socket_impl_t *socket =\n                (tls_socket_impl_t *) avs_calloc(1, sizeof(tls_socket_impl_t));\n        if (!socket) {\n            return avs_errno(AVS_ENOMEM);\n        }\n        *socket_ptr = (avs_net_socket_t *) socket;\n        socket->operations = &TLS_SOCKET_VTABLE;\n\n        avs_error_t err = AVS_OK;\n        if (avs_is_ok((err = avs_net_udp_socket_create(\n                               &socket->backend_socket,\n                               &configuration->backend_configuration)))\n                && !(socket->ctx = SSL_CTX_new(DTLS_method()))) {\n            err = avs_errno(AVS_ENOMEM);\n        }\n        if (avs_is_ok(err)) {\n            switch (configuration->security.mode) {\n            case AVS_NET_SECURITY_PSK:\n                err = configure_psk(socket, &configuration->security.data.psk);\n                break;\n            default:\n                err = avs_errno(AVS_ENOTSUP);\n            }\n        }\n        if (avs_is_err(err)) {\n            avs_net_socket_cleanup(socket_ptr);\n            return err;\n        }\n        SSL_CTX_set_mode(socket->ctx, SSL_MODE_AUTO_RETRY);\n        return AVS_OK;\n    }\n\nThe flow of this function is as follows:\n\n* First, the socket object is allocated and the virtual method table is\n  assigned. This is conceptually identical to the :ref:`initialization of the\n  unencrypted UDP socket <non-posix-networking-api-create>`.\n* Then, the underlying UDP socket and the ``SSL_CTX`` object are created.\n* Initialization related to the security credentials is delegated to a separate\n  function that will be described next. We only support the PSK mode for now,\n  so we check that it is indeed selected, and call ``configure_psk()``.\n* Finally, the auto-retry mode is enabled in OpenSSL. This is the preferred mode\n  that simplifies the implementation when the non-blocking mode is not used. See\n  `SSL_CTX_set_mode() <https://www.openssl.org/docs/man1.1.1/man3/SSL_CTX_set_mode.html>`_\n  for details.\n\n.. _custom-tls-security-info-union-type:\n\nThe ``avs_crypto_security_info_union_t`` type\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nLoading of security credentials in ``avs_net`` and ``avs_crypto`` is centered\naround the ``avs_crypto_security_info_union_t`` type, declared as follows:\n\n.. highlight:: c\n.. snippet-source:: deps/avs_commons/include_public/avsystem/commons/avs_crypto_common.h\n\n    typedef enum {\n        AVS_CRYPTO_SECURITY_INFO_CERTIFICATE_CHAIN,\n        AVS_CRYPTO_SECURITY_INFO_PRIVATE_KEY,\n        AVS_CRYPTO_SECURITY_INFO_CERT_REVOCATION_LIST,\n        AVS_CRYPTO_SECURITY_INFO_PSK_IDENTITY,\n        AVS_CRYPTO_SECURITY_INFO_PSK_KEY\n    } avs_crypto_security_info_tag_t;\n\n    typedef enum {\n        AVS_CRYPTO_DATA_SOURCE_EMPTY,\n        AVS_CRYPTO_DATA_SOURCE_FILE,\n        AVS_CRYPTO_DATA_SOURCE_PATH,\n        AVS_CRYPTO_DATA_SOURCE_BUFFER,\n        AVS_CRYPTO_DATA_SOURCE_ARRAY,\n        AVS_CRYPTO_DATA_SOURCE_LIST,\n    #if defined(AVS_COMMONS_WITH_AVS_CRYPTO_PKI_ENGINE) \\\n            || defined(AVS_COMMONS_WITH_AVS_CRYPTO_PSK_ENGINE)\n        AVS_CRYPTO_DATA_SOURCE_ENGINE\n    #endif /* defined(AVS_COMMONS_WITH_AVS_CRYPTO_PKI_ENGINE) || \\\n            defined(AVS_COMMONS_WITH_AVS_CRYPTO_PSK_ENGINE) */\n        } avs_crypto_data_source_t;\n\n    /**\n     * This struct is for internal use only and should not be filled manually. One\n     * should construct appropriate instances of:\n     * - @ref avs_crypto_certificate_chain_info_t,\n     * - @ref avs_crypto_private_key_info_t\n     * - @ref avs_crypto_cert_revocation_list_info_t\n     * - @ref avs_crypto_psk_identity_info_t\n     * - @ref avs_crypto_psk_key_info_t\n     * using methods declared in @c avs_crypto_pki.h and @c avs_crypto_psk.h.\n     */\n    struct avs_crypto_security_info_union_struct {\n        avs_crypto_security_info_tag_t type;\n        avs_crypto_data_source_t source;\n        union {\n            avs_crypto_security_info_union_internal_file_t file;\n            avs_crypto_security_info_union_internal_path_t path;\n            avs_crypto_security_info_union_internal_buffer_t buffer;\n            avs_crypto_security_info_union_internal_array_t array;\n            avs_crypto_security_info_union_internal_list_t list;\n    #if defined(AVS_COMMONS_WITH_AVS_CRYPTO_PKI_ENGINE) \\\n            || defined(AVS_COMMONS_WITH_AVS_CRYPTO_PSK_ENGINE)\n            avs_crypto_security_info_union_internal_engine_t engine;\n    #endif /* defined(AVS_COMMONS_WITH_AVS_CRYPTO_PKI_ENGINE) || \\\n            defined(AVS_COMMONS_WITH_AVS_CRYPTO_PSK_ENGINE) */\n            } info;\n        };\n\nThe ``source`` fields acts as a tag to the ``info`` union, deciding from which\nsource the credential shall be loaded. There are a number of \"simple\" sources\nsupported:\n\n* ``AVS_CRYPTO_DATA_SOURCE_EMPTY`` - signifies that the object does not\n  represent any valid credential information\n* ``AVS_CRYPTO_DATA_SOURCE_FILE`` - the credential shall be loaded from a file,\n  specified as a file path (``info.file.filename``); in case of private keys,\n  an optional password for encrypted PEM keys can be specified\n  (``info.file.password``)\n* ``AVS_CRYPTO_DATA_SOURCE_PATH`` - the credentials shall be loaded from a\n  directory, specified as a file system path (``info.path.path``); this\n  generally only makes sense for certificate chains\n* ``AVS_CRYPTO_DATA_SOURCE_BUFFER`` - the credentials shall be loaded from a\n  memory buffer (``info.buffer.buffer`` of the size\n  ``info.buffer.buffer_size``); in case of private keys, an optional password\n  for encrypted PEM keys can be specified (``info.buffer.password``); **this is\n  the case that is almost exclusively used in Anjay**\n* ``AVS_CRYPTO_DATA_SOURCE_ENGINE`` - the object refers to a credential stored\n  in a hardware cryptography source, such as a secure element; information on\n  the credential is stored as a \"query string\" at ``info.engine.query``; the\n  format of the query string is platform-specific and may be arbitrary; **this\n  case is supported in the HSM Feature**\n\nIn addition to the \"simple\" sources listed above, two additional \"compound\"\nsources are supported:\n\n* ``AVS_CRYPTO_DATA_SOURCE_ARRAY`` - the object specifies multiple credentials,\n  stored as an array of other ``avs_crypto_security_info_union_t`` objects -\n  ``info.array.element_count`` structures stored at ``info.array.array_ptr``\n* ``AVS_CRYPTO_DATA_SOURCE_LIST`` - the object specifies multiple credentials,\n  stored as an ``AVS_LIST`` whose first element is ``info.list.list_head``; the\n  ``AVS_LIST`` macro is not explicitly used in the declaration of the\n  ``list_head`` field for dependency management reasons, but that field shall\n  still be treated as such\n\n.. note::\n\n    \"Compound\" credential sources are most commonly used for trust store\n    information, i.e. trusted certificates and certificate revocation lists.\n\n    \"Compound\" credential sources are not used for private keys, PSK keys or PSK\n    identities.\n\n    \"Compound\" credential sources MAY be used for client certificates, to\n    signify additional CA certificates that shall be sent to the server during\n    handshake.\n\n    \"Compound\" credential sources, in general, MAY contain other \"compound\"\n    credential sources, forming a tree-like structure. Those SHOULD be loaded\n    recursively. However, the credentials provided by Anjay are expected to not\n    be formed in this way.\n\n.. important::\n\n    Anjay uses both ``AVS_CRYPTO_DATA_SOURCE_ARRAY`` and\n    ``AVS_CRYPTO_DATA_SOURCE_LIST`` for different purposes, so support for both\n    needs to be implemented.\n\nThe ``avs_crypto_security_info_union_t`` structure additionally contains the\n``type`` field, which may be used for validating the credential type (i.e.,\nwhether the object represents a certificate chain, certificate revocation lists,\nor a private key.\n\nIn typical usage, the type is conveyed by composing the\n``avs_crypto_security_info_union_t`` object into one of the wrapper objects:\n\n.. highlight:: c\n.. snippet-source:: deps/avs_commons/include_public/avsystem/commons/avs_crypto_pki.h\n\n    typedef struct avs_crypto_certificate_chain_info_struct {\n        avs_crypto_security_info_union_t desc;\n    } avs_crypto_certificate_chain_info_t;\n\n.. highlight:: c\n.. snippet-source:: deps/avs_commons/include_public/avsystem/commons/avs_crypto_pki.h\n\n    typedef struct {\n        avs_crypto_security_info_union_t desc;\n    } avs_crypto_cert_revocation_list_info_t;\n\n.. highlight:: c\n.. snippet-source:: deps/avs_commons/include_public/avsystem/commons/avs_crypto_pki.h\n\n    typedef struct {\n        avs_crypto_security_info_union_t desc;\n    } avs_crypto_private_key_info_t;\n\n.. highlight:: c\n.. snippet-source:: deps/avs_commons/include_public/avsystem/commons/avs_crypto_psk.h\n\n    typedef struct {\n        avs_crypto_security_info_union_t desc;\n    } avs_crypto_psk_identity_info_t;\n\n.. highlight:: c\n.. snippet-source:: deps/avs_commons/include_public/avsystem/commons/avs_crypto_psk.h\n\n    typedef struct {\n        avs_crypto_security_info_union_t desc;\n    } avs_crypto_psk_key_info_t;\n\nWe will only implement support for the ``AVS_CRYPTO_DATA_SOURCE_BUFFER`` mode\nfor the PSK mode; in later tutorials, where configuration of the certificate\nmode is described, ``AVS_CRYPTO_DATA_SOURCE_ARRAY`` and\n``AVS_CRYPTO_DATA_SOURCE_LIST`` will also be implemented for some cases.\n\nInitialization of PSK credentials\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nIn OpenSSL, credentials for the PSK mode are provided through a callback -\na function is set using `SSL_CTX_set_psk_client_callback()\n<https://www.openssl.org/docs/man1.1.1/man3/SSL_CTX_set_psk_client_callback.html>`_\nand it is called whenever the library needs the PSK credentials - this means\nthat they need to be stored for later access on demand.\n\nThe credentials are passed within the ``avs_net_ssl_configuration_t`` stucture\npassed to ``_avs_net_create_dtls_socket()`` or ``_avs_net_create_ssl_socket()``.\nHowever, the structure passed there shall be treated as ephemeral, so in case of\nthe OpenSSL API, the credentials need to be copied into the socket state.\n\nThis means that the first thing is to add appropriate fields to the\n``tls_socket_impl_t`` structure:\n\n.. highlight:: c\n.. snippet-source:: examples/custom-tls/minimal/src/tls_impl.c\n    :emphasize-lines: 7-10\n\n    typedef struct {\n        const avs_net_socket_v_table_t *operations;\n        avs_net_socket_t *backend_socket;\n        SSL_CTX *ctx;\n        SSL *ssl;\n\n        char psk[256];\n        size_t psk_size;\n        char identity[128];\n        size_t identity_size;\n    } tls_socket_impl_t;\n\n.. note::\n\n    Different TLS libraries have different data lifetime contracts. For example,\n    in contrast to the OpenSSL API, `mbedtls_ssl_conf_psk()\n    <https://mbed-tls.readthedocs.io/projects/api/en/development/api/file/ssl_8h/#ssl_8h_1a1e185199e3ff613bdd1c8231a19e24fc>`_\n    in Mbed TLS copies the data passed as arguments into internal structures and\n    thus it is not necessary to make explicit copies.\n\n    Please carefully check whether credentials are passed by value or by\n    reference in the TLS backend you are integrating with.\n\nWe are now ready to implement the ``configure_psk()`` function, and the\n``psk_client_cb()`` callback that will be passed to\n``SSL_CTX_set_psk_client_callback()``. As mentioned above, only the\n``AVS_CRYPTO_DATA_SOURCE_BUFFER`` source is handled for both the key and\nidentity.\n\n.. highlight:: c\n.. snippet-source:: examples/custom-tls/minimal/src/tls_impl.c\n    :emphasize-lines: 7, 44, 46\n\n    static unsigned int psk_client_cb(SSL *ssl,\n                                      const char *hint,\n                                      char *identity,\n                                      unsigned int max_identity_len,\n                                      unsigned char *psk,\n                                      unsigned int max_psk_len) {\n        tls_socket_impl_t *sock = (tls_socket_impl_t *) SSL_get_app_data(ssl);\n\n        (void) hint;\n\n        if (!sock || max_psk_len < sock->psk_size\n                || max_identity_len < sock->identity_size + 1) {\n            return 0;\n        }\n\n        memcpy(psk, sock->psk, sock->psk_size);\n        memcpy(identity, sock->identity, sock->identity_size);\n        identity[sock->identity_size] = '\\0';\n\n        return (unsigned int) sock->psk_size;\n    }\n\n    static avs_error_t configure_psk(tls_socket_impl_t *sock,\n                                     const avs_net_psk_info_t *psk) {\n        if (psk->key.desc.source != AVS_CRYPTO_DATA_SOURCE_BUFFER\n                || psk->identity.desc.source != AVS_CRYPTO_DATA_SOURCE_BUFFER) {\n            return avs_errno(AVS_EINVAL);\n        }\n\n        const void *key_ptr = psk->key.desc.info.buffer.buffer;\n        size_t key_size = psk->key.desc.info.buffer.buffer_size;\n\n        const void *identity_ptr = psk->identity.desc.info.buffer.buffer;\n        size_t identity_size = psk->identity.desc.info.buffer.buffer_size;\n\n        if (key_size > sizeof(sock->psk)\n                || identity_size > sizeof(sock->identity)) {\n            return avs_errno(AVS_EINVAL);\n        }\n        memcpy(sock->psk, key_ptr, key_size);\n        sock->psk_size = key_size;\n        memcpy(sock->identity, identity_ptr, identity_size);\n        sock->identity_size = identity_size;\n        SSL_CTX_set_cipher_list(sock->ctx, \"PSK\");\n        SSL_CTX_set_psk_client_callback(sock->ctx, psk_client_cb);\n        SSL_CTX_set_verify(sock->ctx, SSL_VERIFY_PEER, NULL);\n        return AVS_OK;\n    }\n\nNote that OpenSSL does not automatically disable ciphersuites and functionality\nrelated to certificates when a PSK callback is provided. For this reason\nadditional settings are changed:\n\n* ``SSL_CTX_set_cipher_list()`` is called to limit the set of allowed\n  ciphersuites to only those that depend on the PSK mode.\n* ``SSL_CTX_set_verify()`` is also set to ``SSL_VERIFY_PEER`` so that a server\n  that attempts to use certificate-based authentication shall be verified -\n  this verification will invariably fail, as there are no trusted certificates\n  configured for this connection.\n\nAlso note that the ``tls_socket_impl_t`` structure is accessed using\n``SSL_get_app_data()``. This will be set while\n:ref:`custom-tls-minimal-handshake`.\n\nCleanup\n^^^^^^^\n\nKnowing what is happening during initialization, we can now reverse this process\nin the cleanup function:\n\n.. highlight:: c\n.. snippet-source:: examples/custom-tls/minimal/src/tls_impl.c\n\n    static avs_error_t tls_cleanup(avs_net_socket_t **sock_ptr) {\n        avs_error_t err = AVS_OK;\n        if (sock_ptr && *sock_ptr) {\n            tls_socket_impl_t *sock = (tls_socket_impl_t *) *sock_ptr;\n            tls_close(*sock_ptr);\n            avs_net_socket_cleanup(&sock->backend_socket);\n            if (sock->ctx) {\n                SSL_CTX_free(sock->ctx);\n            }\n            avs_free(sock);\n            *sock_ptr = NULL;\n        }\n        return err;\n    }\n\n.. _custom-tls-minimal-handshake:\n\nPerforming the handshake\n^^^^^^^^^^^^^^^^^^^^^^^^\n\nThe ``perform_handshake()`` function is now relatively straightforward to\nimplement:\n\n* The new ``SSL`` object is created using ``SSL_new()``\n* The pointer to the socket structure is set as the application data so that it\n  can be retrieved in ``psk_client_cb()``\n* The hostname to which the socket is being connected is set to be used in the\n  Server Name Identification TLS extension\n* A new datagram ``BIO`` object is created, configured and set for use by the\n  ``SSL`` object\n\n  * OpenSSL's datagram BIO object uses ``sendto()`` instead of ``send()``\n    internally, so it needs to be explicitly informed of the address of the\n    peer the socket is connected to. This is performed using ``BIO_ctrl()``,\n    with the raw server address queried using ``getpeername()``.\n* ``SSL_connect()`` is called to perform the actual client-side (D)TLS handshake\n\n.. highlight:: c\n.. snippet-source:: examples/custom-tls/minimal/src/tls_impl.c\n    :emphasize-lines: 38-40\n\n    static avs_error_t perform_handshake(tls_socket_impl_t *sock,\n                                         const char *host) {\n        union {\n            struct sockaddr addr;\n            struct sockaddr_storage storage;\n        } peername;\n        const void *fd_ptr = avs_net_socket_get_system(sock->backend_socket);\n        if (!fd_ptr\n                || getpeername(*(const int *) fd_ptr, &peername.addr,\n                               &(socklen_t) { sizeof(peername) })) {\n            return avs_errno(AVS_EBADF);\n        }\n\n        sock->ssl = SSL_new(sock->ctx);\n        if (!sock->ssl) {\n            return avs_errno(AVS_ENOMEM);\n        }\n\n        SSL_set_app_data(sock->ssl, sock);\n        SSL_set_tlsext_host_name(sock->ssl, host);\n\n        BIO *bio = BIO_new_dgram(*(const int *) fd_ptr, 0);\n        if (!bio) {\n            return avs_errno(AVS_ENOMEM);\n        }\n        BIO_ctrl(bio, BIO_CTRL_DGRAM_SET_CONNECTED, 0, &peername.addr);\n        SSL_set_bio(sock->ssl, bio, bio);\n\n        if (SSL_connect(sock->ssl) <= 0) {\n            return avs_errno(AVS_EPROTO);\n        }\n        return AVS_OK;\n    }\n\n    static avs_error_t\n    tls_connect(avs_net_socket_t *sock_, const char *host, const char *port) {\n        tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n        if (sock->ssl) {\n            return avs_errno(AVS_EBADF);\n        }\n        avs_error_t err;\n        if (avs_is_err((\n                    err = avs_net_socket_connect(sock->backend_socket, host, port)))\n                || avs_is_err((err = perform_handshake(sock, host)))) {\n            if (sock->ssl) {\n                SSL_free(sock->ssl);\n                sock->ssl = NULL;\n            }\n            avs_net_socket_close(sock->backend_socket);\n        }\n        return err;\n    }\n\nAn additional check is also added in ``tls_connect()`` to avoid creating the\n``SSL`` object multiple times.\n\nFixing the socket option values\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nThe ``tls_get_opt()`` function has been previously implemented by simply\nforwarding the call to the underlying unencrypted socket.\n\nThis yields inaccurate results for the ``AVS_NET_SOCKET_OPT_INNER_MTU`` option.\nThe underlying socket will return the maximum number of bytes available on the\nUDP layer, while we need to take the DTLS headers into account.\n\nIt is also desirable to overload the ``AVS_NET_SOCKET_HAS_BUFFERED_DATA``. This\noption is designed to notify the Anjay library whether all data received from\nthe underlying system socket has been processed. This is used to make sure that\nwhen control is returned to the event loop, the ``poll()`` call will not stall\nwaiting for new data, while in reality it is already available, but stuck in the\n(D)TLS layer buffer.\n\nIn this example based on OpenSSL, this condition can be checked by calling the\n`SSL_pending() <https://www.openssl.org/docs/man1.1.1/man3/SSL_pending.html>`_\nfunction.\n\n.. highlight:: c\n.. snippet-source:: examples/custom-tls/minimal/src/tls_impl.c\n\n    static avs_error_t tls_get_opt(avs_net_socket_t *sock_,\n                                   avs_net_socket_opt_key_t option_key,\n                                   avs_net_socket_opt_value_t *out_option_value) {\n        tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n        switch (option_key) {\n        case AVS_NET_SOCKET_OPT_INNER_MTU: {\n            avs_error_t err = avs_net_socket_get_opt(sock->backend_socket,\n                                                     AVS_NET_SOCKET_OPT_INNER_MTU,\n                                                     out_option_value);\n            if (avs_is_ok(err)) {\n                out_option_value->mtu = AVS_MAX(out_option_value->mtu - 64, 0);\n            }\n            return err;\n        }\n        case AVS_NET_SOCKET_HAS_BUFFERED_DATA:\n            out_option_value->flag = (sock->ssl && SSL_pending(sock->ssl) > 0);\n            return AVS_OK;\n        default:\n            return avs_net_socket_get_opt(sock->backend_socket, option_key,\n                                          out_option_value);\n        }\n    }\n\n.. note::\n\n    In this simplistic implementation, the DTLS overhead has been hardcoded to\n    64 bytes, which is generally accepted as the upper limit for this value.\n\n    A more complete implementation could query or calculate the precise\n    overhead for the current session, based on the specific ciphersuite in use.\n\nLimitations\n-----------\n\nThis minimal implementation is enough to communicate with an LwM2M server in PSK\nmode, but a number of functionalities will not work:\n\n* Session resumption is not implemented, which may cause otherwise unnecessary\n  Register requests being sent after reconnecting. Note that a Register request\n  also forces the server to reinitialize all the Observe requests, so this is\n  very undesirable.\n* Certificate mode is not implemented.\n* TLS over TCP is not implemented, which means that e.g. HTTPS will not be\n  supported.\n* DTLS Connection ID extension is not supported.\n* Various additional configuration options are not implemented as well,\n  including:\n\n  * Configurable TLS/DTLS version\n  * Configurable DTLS handshake timers\n  * Configurable ciphersuite list (note that in LwM2M they can be configured\n    through the data model - this will be ignored by the current implementation)\n  * Overriding the hostname used for Server Name Identification - useful for\n    LwM2M 1.1 only\n* TLS alert codes are not forwarded to calling code, and LwM2M 1.1 exposes them\n  through the data model.\n* Support for the SSL error API is not implemented. This API requires\n  the ``connect()`` function to return ``avs_error_t`` objects created using\n  ``avs_net_ssl_lib_error()`` or ``avs_net_ssl_alert()`` defined in\n  ``deps/avs_commons/include_public/avsystem/commons/avs_socket.h``.\n* Socket file descriptor is used directly instead of wrapping ``avs_net`` APIs,\n  and the ``decorate`` function is not implemented - the secure SMS mode will\n  thus not work in versions that include the SMS commercial feature.\n\nWe will expand this implementation to address these limitation in subsequent\nchapters.\n"
  },
  {
    "path": "doc/sphinx/source/PortingGuideForNonPOSIXPlatforms/CustomTLS/CustomTLS-Resumption.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nSession resumption support\n==========================\n\n.. contents:: :local:\n\nIntroduction\n------------\n\nThis tutorial builds up on :doc:`the previous one <CustomTLS-Minimal>` and adds\nsupport for DTLS session resumption mechanism.\n\nDTLS session resumption support is essential for LwM2M device operation,\nespecially if relatively frequent connectivity drops are anticipated and/or if\nnetwork traffic is considered expensive. Each full DTLS handshake creates a new\nCoAP endpoint association, which forces the device to send a new Register\nmessage, in turn forcing the server to re-establish any Observe requests\nrequired.\n\nSession resumption solves this problem - a resumed session is considered a\ncontinuation of the previous CoAP endpoint association, so as long as the\nregistration lifetime has not expired, communication can continue as if nothing\nhappened, even if the device's IP address changed.\n\nTwo approaches to handling session resumption will be considered, one based on\nthe TLS library's native session handling capabilities, and one involving a raw\nbuffer provided by the Anjay library.\n\nSimple session persistence\n--------------------------\n\n.. note::\n\n    Code related to this tutorial can be found under\n    `examples/custom-tls/resumption-simple\n    <https://github.com/AVSystem/Anjay/tree/master/examples/custom-tls/resumption-simple>`_\n    in the Anjay source directory.\n\nOpenSSL provides an object type that represents a (D)TLS session,\n``SSL_SESSION``, and a \"session cache\" mechanism that allows storing the\nsessions and resuming them on demand as needed.\n\nUnfortunately, while the session cache is completely automatic for server-side\nconnections, on the client side the session objects need to be stored and\nrestored manually.\n\nSo we will start by adding a field to the ``tls_socket_impl_t`` structure that\nwill hold the session information even while the socket is disconnected:\n\n.. highlight:: c\n.. snippet-source:: examples/custom-tls/resumption-simple/src/tls_impl.c\n    :emphasize-lines: 12\n\n    typedef struct {\n        const avs_net_socket_v_table_t *operations;\n        avs_net_socket_t *backend_socket;\n        SSL_CTX *ctx;\n        SSL *ssl;\n\n        char psk[256];\n        size_t psk_size;\n        char identity[128];\n        size_t identity_size;\n\n        SSL_SESSION *last_session;\n    } tls_socket_impl_t;\n\nOf course, we need to make sure that this object is cleaned up in\n``tls_cleanup``:\n\n.. highlight:: c\n.. snippet-source:: examples/custom-tls/resumption-simple/src/tls_impl.c\n    :emphasize-lines: 10-12\n\n    static avs_error_t tls_cleanup(avs_net_socket_t **sock_ptr) {\n        avs_error_t err = AVS_OK;\n        if (sock_ptr && *sock_ptr) {\n            tls_socket_impl_t *sock = (tls_socket_impl_t *) *sock_ptr;\n            tls_close(*sock_ptr);\n            avs_net_socket_cleanup(&sock->backend_socket);\n            if (sock->ctx) {\n                SSL_CTX_free(sock->ctx);\n            }\n            if (sock->last_session) {\n                SSL_SESSION_free(sock->last_session);\n            }\n            avs_free(sock);\n            *sock_ptr = NULL;\n        }\n        return err;\n    }\n\n.. note::\n\n    In some TLS implementations, session persistence and resumption may be\n    implemented within the library itself. For example, in the nrfxlib Modem\n    library, session persistence happens automatically if the\n    `NRF_SO_SEC_SESSION_CACHE <https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/nrfxlib/nrf_modem/doc/api.html#c.NRF_SO_SEC_SESSION_CACHE>`_\n    option is enabled.\n\n    With such implementations, it is not necessary to add dedicated fields or\n    save/restore logic, and it is OK to just enable the built-in mechanisms.\n\nSaving the session\n^^^^^^^^^^^^^^^^^^\n\nNow that there is a field to store this information, we may proceed with\nconfiguring the session cache so that each new session is written there:\n\n.. highlight:: c\n.. snippet-source:: examples/custom-tls/resumption-simple/src/tls_impl.c\n    :emphasize-lines: 1-11, 49-52\n\n    static int new_session_cb(SSL *ssl, SSL_SESSION *sess) {\n        tls_socket_impl_t *sock = (tls_socket_impl_t *) SSL_get_app_data(ssl);\n        SSL_SESSION *sess_dup = SSL_SESSION_dup(sess);\n        if (sess_dup) {\n            if (sock->last_session) {\n                SSL_SESSION_free(sock->last_session);\n            }\n            sock->last_session = sess_dup;\n        }\n        return 0;\n    }\n\n    avs_error_t _avs_net_create_dtls_socket(avs_net_socket_t **socket_ptr,\n                                            const void *configuration_) {\n        assert(socket_ptr);\n        assert(!*socket_ptr);\n        assert(configuration_);\n        const avs_net_ssl_configuration_t *configuration =\n                (const avs_net_ssl_configuration_t *) configuration_;\n        tls_socket_impl_t *socket =\n                (tls_socket_impl_t *) avs_calloc(1, sizeof(tls_socket_impl_t));\n        if (!socket) {\n            return avs_errno(AVS_ENOMEM);\n        }\n        *socket_ptr = (avs_net_socket_t *) socket;\n        socket->operations = &TLS_SOCKET_VTABLE;\n\n        avs_error_t err = AVS_OK;\n        if (avs_is_ok((err = avs_net_udp_socket_create(\n                               &socket->backend_socket,\n                               &configuration->backend_configuration)))\n                && !(socket->ctx = SSL_CTX_new(DTLS_method()))) {\n            err = avs_errno(AVS_ENOMEM);\n        }\n        if (avs_is_ok(err)) {\n            switch (configuration->security.mode) {\n            case AVS_NET_SECURITY_PSK:\n                err = configure_psk(socket, &configuration->security.data.psk);\n                break;\n            default:\n                err = avs_errno(AVS_ENOTSUP);\n            }\n        }\n        if (avs_is_err(err)) {\n            avs_net_socket_cleanup(socket_ptr);\n            return err;\n        }\n        SSL_CTX_set_mode(socket->ctx, SSL_MODE_AUTO_RETRY);\n        SSL_CTX_set_session_cache_mode(socket->ctx,\n                                       SSL_SESS_CACHE_CLIENT\n                                               | SSL_SESS_CACHE_NO_INTERNAL_STORE);\n        SSL_CTX_sess_set_new_cb(socket->ctx, new_session_cb);\n        return AVS_OK;\n    }\n\nNote how ``SSL_SESSION_dup()`` is used in the ``new_session_cb`` function - this\nis because the ``SSL_SESSION`` object also contains the transient state of the\nsession that might change later and make it impossible to restore it later, e.g.\nwhen the connection is closed. This is why we want an exact clone of the session\nstate as it was just after the handshake.\n\nRestoring the session\n^^^^^^^^^^^^^^^^^^^^^\n\n.. highlight:: c\n.. snippet-source:: examples/custom-tls/resumption-simple/src/tls_impl.c\n    :emphasize-lines: 29-35\n\n    static avs_error_t perform_handshake(tls_socket_impl_t *sock,\n                                         const char *host) {\n        union {\n            struct sockaddr addr;\n            struct sockaddr_storage storage;\n        } peername;\n        const void *fd_ptr = avs_net_socket_get_system(sock->backend_socket);\n        if (!fd_ptr\n                || getpeername(*(const int *) fd_ptr, &peername.addr,\n                               &(socklen_t) { sizeof(peername) })) {\n            return avs_errno(AVS_EBADF);\n        }\n\n        sock->ssl = SSL_new(sock->ctx);\n        if (!sock->ssl) {\n            return avs_errno(AVS_ENOMEM);\n        }\n\n        SSL_set_app_data(sock->ssl, sock);\n        SSL_set_tlsext_host_name(sock->ssl, host);\n\n        BIO *bio = BIO_new_dgram(*(const int *) fd_ptr, 0);\n        if (!bio) {\n            return avs_errno(AVS_ENOMEM);\n        }\n        BIO_ctrl(bio, BIO_CTRL_DGRAM_SET_CONNECTED, 0, &peername.addr);\n        SSL_set_bio(sock->ssl, bio, bio);\n\n        if (sock->last_session) {\n            SSL_SESSION *session_dup = SSL_SESSION_dup(sock->last_session);\n            if (session_dup) {\n                SSL_set_session(sock->ssl, session_dup);\n                SSL_SESSION_free(session_dup);\n            }\n        }\n\n        if (SSL_connect(sock->ssl) <= 0) {\n            return avs_errno(AVS_EPROTO);\n        }\n        return AVS_OK;\n    }\n\nIf there is a stored session, restoring it just requires calling\n``SSL_set_session()`` right before ``SSL_connect()``. We also use\n``SSL_SESSSION_dup()`` here to avoid modifying the object already stored in\nthe ``last_session`` field.\n\nThe ``AVS_NET_SOCKET_OPT_SESSION_RESUMED`` option\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nThe Anjay library needs to know whether the \"connect\" operation resulted in an\nestablishment of a completely new session, or a resumption of an existing one.\nThis information is used to determine whether sending the Register message is\nnecessary.\n\nThe interface to get this information from the socket is the\n``AVS_NET_SOCKET_OPT_SESSION_RESUMED`` option, used through the \"get_opt\"\noperation.\n\nFor OpenSSL, this can be forwarded to the call to ``SSL_session_reused()``:\n\n.. highlight:: c\n.. snippet-source:: examples/custom-tls/resumption-simple/src/tls_impl.c\n    :emphasize-lines: 18-20\n\n    static avs_error_t tls_get_opt(avs_net_socket_t *sock_,\n                                   avs_net_socket_opt_key_t option_key,\n                                   avs_net_socket_opt_value_t *out_option_value) {\n        tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n        switch (option_key) {\n        case AVS_NET_SOCKET_OPT_INNER_MTU: {\n            avs_error_t err = avs_net_socket_get_opt(sock->backend_socket,\n                                                     AVS_NET_SOCKET_OPT_INNER_MTU,\n                                                     out_option_value);\n            if (avs_is_ok(err)) {\n                out_option_value->mtu = AVS_MAX(out_option_value->mtu - 64, 0);\n            }\n            return err;\n        }\n        case AVS_NET_SOCKET_HAS_BUFFERED_DATA:\n            out_option_value->flag = (sock->ssl && SSL_pending(sock->ssl) > 0);\n            return AVS_OK;\n        case AVS_NET_SOCKET_OPT_SESSION_RESUMED:\n            out_option_value->flag = (sock->ssl && SSL_session_reused(sock->ssl));\n            return AVS_OK;\n        default:\n            return avs_net_socket_get_opt(sock->backend_socket, option_key,\n                                          out_option_value);\n        }\n    }\n\n.. note::\n\n    In many TLS implementations, this information may be very hard or impossible\n    to query. If that is the case, the TLS integration layer may assume one of\n    two possible strategies:\n\n    * **Always assume that a new session has been established**, or indeed **do\n      not support session resumption** at all. This is the safe option, as it\n      ensures proper interoperability and behavior at all times.\n\n      However, this might lead to a lot of network traffic being wasted for the\n      Register and Observe messages after each handshake.\n\n    * **Always assume that a previous session has been resumed.** Note that the\n      session state is not the only factor in deciding whether to send the\n      Register message, so it will still be sent e.g. if lifetime of the\n      previous registration expired, or if it is the first registration with a\n      given server.\n\n      However, this assumption **might be dangerous** in case of false positives\n      - if a full handshake has been performed, but a session resumption is\n      reported by the code, the connection, depending on the specific LwM2M\n      server implementation, may be unusable until the registration lifetime\n      expires, which is expected to eventually trigger the Register message.\n      In such circumstances, non-confirmable Notify messages will be lost,\n      delivery of confirmable Notify messages will fail, but not trigger any\n      additional actions (see\n      :doc:`../../AdvancedTopics/AT-NetworkErrorHandling`), and delivery of Send\n      messages will also fail (with failures reported to the user code) during\n      this time.\n\n      You might nevertheless consider this strategy if you are confident that\n      the session resumption will succeed most of the time in your environment,\n      or if the trade-off of temporary connectivity loss for up to one\n      connection lifetime period is acceptable for your application.\n\n    If the ``AVS_NET_SOCKET_OPT_SESSION_RESUMED`` option is unimplemented or\n    querying it fails, the library will behave the same way as if ``false`` was\n    returned, i.e. a fresh session will be assumed, prioritizing safety over\n    network traffic usage.\n\nLimitations\n^^^^^^^^^^^\n\nAs implemented above, the session is only persisted for as long as the socket\nobject exists. This is fine for most of the cases. However, the persistence\nfeature of Anjay offers the ``anjay_new_from_core_persistence()`` and\n``anjay_delete_with_core_persistence()`` APIs that allow persisting the\ntransient connection state to non-volatile memory. This transient state includes\nthe (D)TLS session information.\n\nFor this reason, the ``avs_net`` socket API includes configuration options that\nallow specifying a dedicated buffer for storing the session information. The\nnext section of this article will showcase an alternate implementation of the\nsession resumption mechanism that implements it.\n\n.. note::\n\n    Even if you implement session resumption without the use of the\n    ``session_resumption_buffer`` APIs, Anjay will allocate memory for such\n    buffers, one for each server connection. You can control the size of those\n    buffers via the ``DTLS_SESSION_BUFFER_SIZE`` CMake option or the\n    ``ANJAY_DTLS_SESSION_BUFFER_SIZE`` macro in ``anjay_config.h``.\n\n    This value will be used in array size declarations, so the value of 0 may\n    not be acceptable for some compilers.\n\nBuffer-based session persistence\n--------------------------------\n\n.. note::\n\n    Code related to this tutorial can be found under\n    `examples/custom-tls/resumption-buffer\n    <https://github.com/AVSystem/Anjay/tree/master/examples/custom-tls/resumption-buffer>`_\n    in the Anjay source directory.\n\n.. note::\n\n    In some implementations, such as the `nrfxlib Modem library\n    <https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/nrfxlib/nrf_modem/README.html>`_,\n    session information may be persisted in non-volatile memory across reboots\n    directly by the underlying implementation. In that case, it is fine to rely\n    on that mechanism and ignore the ``session_resumption_buffer`` field, even\n    if the ``anjay_new_from_core_persistence()`` and\n    ``anjay_delete_with_core_persistence()`` APIs will be utilized.\n\nThis variant is very similar to the previous one, but to address the limitation\nmentioned above, we will serialize the session information into the buffer\nsupplied via socket configuration.\n\nThat means that instead of keeping an ``SSL_SESSION`` object in the socket\nstate, we need to store the information about the buffer:\n\n\n.. highlight:: c\n.. snippet-source:: examples/custom-tls/resumption-buffer/src/tls_impl.c\n    :emphasize-lines: 12-13\n\n    typedef struct {\n        const avs_net_socket_v_table_t *operations;\n        avs_net_socket_t *backend_socket;\n        SSL_CTX *ctx;\n        SSL *ssl;\n\n        char psk[256];\n        size_t psk_size;\n        char identity[128];\n        size_t identity_size;\n\n        void *session_resumption_buffer;\n        size_t session_resumption_buffer_size;\n    } tls_socket_impl_t;\n\nThis buffer is allocated outside the socket object, and not owned by it, so we\nare not putting any deallocation in ``tls_cleanup`` this time.\n\nHowever, we need to copy this pointer and size information from the\nconfiguration structure when initializing the socket:\n\n.. highlight:: c\n.. snippet-source:: examples/custom-tls/resumption-buffer/src/tls_impl.c\n    :emphasize-lines: 37-47\n\n    avs_error_t _avs_net_create_dtls_socket(avs_net_socket_t **socket_ptr,\n                                            const void *configuration_) {\n        assert(socket_ptr);\n        assert(!*socket_ptr);\n        assert(configuration_);\n        const avs_net_ssl_configuration_t *configuration =\n                (const avs_net_ssl_configuration_t *) configuration_;\n        tls_socket_impl_t *socket =\n                (tls_socket_impl_t *) avs_calloc(1, sizeof(tls_socket_impl_t));\n        if (!socket) {\n            return avs_errno(AVS_ENOMEM);\n        }\n        *socket_ptr = (avs_net_socket_t *) socket;\n        socket->operations = &TLS_SOCKET_VTABLE;\n\n        avs_error_t err = AVS_OK;\n        if (avs_is_ok((err = avs_net_udp_socket_create(\n                               &socket->backend_socket,\n                               &configuration->backend_configuration)))\n                && !(socket->ctx = SSL_CTX_new(DTLS_method()))) {\n            err = avs_errno(AVS_ENOMEM);\n        }\n        if (avs_is_ok(err)) {\n            switch (configuration->security.mode) {\n            case AVS_NET_SECURITY_PSK:\n                err = configure_psk(socket, &configuration->security.data.psk);\n                break;\n            default:\n                err = avs_errno(AVS_ENOTSUP);\n            }\n        }\n        if (avs_is_err(err)) {\n            avs_net_socket_cleanup(socket_ptr);\n            return err;\n        }\n        SSL_CTX_set_mode(socket->ctx, SSL_MODE_AUTO_RETRY);\n        if (configuration->session_resumption_buffer_size > 0) {\n            assert(configuration->session_resumption_buffer);\n            socket->session_resumption_buffer =\n                    configuration->session_resumption_buffer;\n            socket->session_resumption_buffer_size =\n                    configuration->session_resumption_buffer_size;\n            SSL_CTX_set_session_cache_mode(\n                    socket->ctx,\n                    SSL_SESS_CACHE_CLIENT | SSL_SESS_CACHE_NO_INTERNAL_STORE);\n            SSL_CTX_sess_set_new_cb(socket->ctx, new_session_cb);\n        }\n        return AVS_OK;\n    }\n\nSaving the session\n^^^^^^^^^^^^^^^^^^\n\nThe snippet above already includes the ``SSL_CTX_set_session_cache_mode()`` and\n``SSL_CTX_sess_set_new_cb()`` calls introduced in the previous version. However\nthis time, the ``new_session_cb`` callback looks very different:\n\n.. highlight:: c\n.. snippet-source:: examples/custom-tls/resumption-buffer/src/tls_impl.c\n\n    static int new_session_cb(SSL *ssl, SSL_SESSION *sess) {\n        tls_socket_impl_t *sock = (tls_socket_impl_t *) SSL_get_app_data(ssl);\n        int serialized_size = i2d_SSL_SESSION(sess, NULL);\n        if (serialized_size > 0\n                && (size_t) serialized_size\n                               <= sock->session_resumption_buffer_size) {\n            unsigned char *ptr = (unsigned char *) sock->session_resumption_buffer;\n            i2d_SSL_SESSION(sess, &ptr);\n        }\n        return 0;\n    }\n\nOpenSSL provides APIs for serializing and deserializing the ``SSL_SESSION``\nobjects, and that is naturally what we use for this purpose.\n\nWhen implementing session serialization in your code, you don't need to adhere\nto any particular data format. However, please bear the following things in\nmind:\n\n* The data is expected to be stored relatively short-term, either within a\n  single execution of the application, or across a single restart (using\n  ``anjay_new_from_core_persistence()`` and\n  ``anjay_delete_with_core_persistence()``).\n\n* A firmware update **MAY** happen during that aforementioned single restart.\n\n* While it is preferred to retain compatibility of the format across firmware\n  versions, it is also acceptable to reject old incompatible data.\n\n  * Full handshake shall be performed in such circumstance, and this fact shall\n    be appropriately reported via the ``AVS_NET_SOCKET_OPT_SESSION_RESUMED``\n    option.\n\n  * The restoring code shall be prepared to handle invalid input. In case of\n    invalid input, it should gracefully fail and revert to performing full\n    handshake.\n\n* The serialized session data is not intended to be moved across hardware units.\n  It is not a problem if the session data is only restorable on the same machine\n  that generated it.\n\nRestoring the session\n^^^^^^^^^^^^^^^^^^^^^\n\nMuch like in the previous version, we need to call ``SSL_set_session()`` with\nthe restored session before calling ``SSL_connect()``.\n\nHowever, in this version, we need to deserialize the session using\n``d2i_SSL_SESSION()`` first:\n\n.. highlight:: c\n.. snippet-source:: examples/custom-tls/resumption-buffer/src/tls_impl.c\n    :emphasize-lines: 29-39\n\n    static avs_error_t perform_handshake(tls_socket_impl_t *sock,\n                                         const char *host) {\n        union {\n            struct sockaddr addr;\n            struct sockaddr_storage storage;\n        } peername;\n        const void *fd_ptr = avs_net_socket_get_system(sock->backend_socket);\n        if (!fd_ptr\n                || getpeername(*(const int *) fd_ptr, &peername.addr,\n                               &(socklen_t) { sizeof(peername) })) {\n            return avs_errno(AVS_EBADF);\n        }\n\n        sock->ssl = SSL_new(sock->ctx);\n        if (!sock->ssl) {\n            return avs_errno(AVS_ENOMEM);\n        }\n\n        SSL_set_app_data(sock->ssl, sock);\n        SSL_set_tlsext_host_name(sock->ssl, host);\n\n        BIO *bio = BIO_new_dgram(*(const int *) fd_ptr, 0);\n        if (!bio) {\n            return avs_errno(AVS_ENOMEM);\n        }\n        BIO_ctrl(bio, BIO_CTRL_DGRAM_SET_CONNECTED, 0, &peername.addr);\n        SSL_set_bio(sock->ssl, bio, bio);\n\n        if (sock->session_resumption_buffer) {\n            const unsigned char *ptr =\n                    (const unsigned char *) sock->session_resumption_buffer;\n            SSL_SESSION *session =\n                    d2i_SSL_SESSION(NULL, &ptr,\n                                    sock->session_resumption_buffer_size);\n            if (session) {\n                SSL_set_session(sock->ssl, session);\n                SSL_SESSION_free(session);\n            }\n        }\n\n        if (SSL_connect(sock->ssl) <= 0) {\n            return avs_errno(AVS_EPROTO);\n        }\n        return AVS_OK;\n    }\n\nOf course, we also need to support the ``AVS_NET_SOCKET_OPT_SESSION_RESUMED``\noption, which in this case looks identical:\n\n.. highlight:: c\n.. snippet-source:: examples/custom-tls/resumption-buffer/src/tls_impl.c\n    :emphasize-lines: 18-20\n\n    static avs_error_t tls_get_opt(avs_net_socket_t *sock_,\n                                   avs_net_socket_opt_key_t option_key,\n                                   avs_net_socket_opt_value_t *out_option_value) {\n        tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n        switch (option_key) {\n        case AVS_NET_SOCKET_OPT_INNER_MTU: {\n            avs_error_t err = avs_net_socket_get_opt(sock->backend_socket,\n                                                     AVS_NET_SOCKET_OPT_INNER_MTU,\n                                                     out_option_value);\n            if (avs_is_ok(err)) {\n                out_option_value->mtu = AVS_MAX(out_option_value->mtu - 64, 0);\n            }\n            return err;\n        }\n        case AVS_NET_SOCKET_HAS_BUFFERED_DATA:\n            out_option_value->flag = (sock->ssl && SSL_pending(sock->ssl) > 0);\n            return AVS_OK;\n        case AVS_NET_SOCKET_OPT_SESSION_RESUMED:\n            out_option_value->flag = (sock->ssl && SSL_session_reused(sock->ssl));\n            return AVS_OK;\n        default:\n            return avs_net_socket_get_opt(sock->backend_socket, option_key,\n                                          out_option_value);\n        }\n    }\n"
  },
  {
    "path": "doc/sphinx/source/PortingGuideForNonPOSIXPlatforms/CustomTLS/CustomTLS-Stub.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nIntroductory stub\n=================\n\n.. contents:: :local:\n\n.. note::\n\n    Code related to this tutorial can be found under\n    `examples/custom-tls/stub\n    <https://github.com/AVSystem/Anjay/tree/master/examples/custom-tls/stub>`_\n    in the Anjay source directory.\n\nIntroduction\n------------\n\nThis article describes a stub with no actual functionality, but contains the\nnecessary boilerplate that all the following tutorials will be based on.\n\nThe example code for this tutorial is an extension of the one built in the\n:doc:`../NetworkingAPI/NetworkingAPI-IpStickiness` article. This has been chosen\nas a base because it contains all the features of a custom networking layer. We\nencourage you to familiarize yourself with the :doc:`../NetworkingAPI` tutorials\nbefore implementing a custom TLS layer, but intricate knowledge of all the\nfeatures of the networking layer is not necessary.\n\nCompared to the networking API tutorials, this one is intended to be used with a\nversion of Anjay that has been compiled without the default TLS layer\nimplementation, i.e. with this additional CMake flag::\n\n    -DDTLS_BACKEND=custom\n\nAdditionally, support for HTTP downloads will be used in later tutorials. When\nthe examples are built as part of Anjay's ``make examples`` target, this feature\nis enabled for all the ``custom-tls`` subprojects with yet another CMake flag::\n\n    -DWITH_HTTP_DOWNLOAD=ON\n\n.. note::\n\n    This new custom network layer implementation will be based on OpenSSL 1.1.1.\n    This is not very useful in the real world, as there is a default\n    implementation provided for the OpenSSL library. However, this tutorial is\n    provided as a reference implementation simpler than the actual default one,\n    to make it easier to base your code on it.\n\nAdjustments to the build system\n-------------------------------\n\nThe `CMakeLists.txt <https://github.com/AVSystem/Anjay/blob/master/examples/custom-tls/stub/CMakeLists.txt>`_\nfile has been modified to accommodate for this custom TLS implementation:\n\n.. highlight:: cmake\n.. snippet-source:: examples/custom-tls/stub/CMakeLists.txt\n    :emphasize-lines: 1, 7, 12-13\n\n    cmake_minimum_required(VERSION 3.16)\n    project(custom-tls-stub C)\n\n    set(CMAKE_C_STANDARD 99)\n\n    find_package(anjay REQUIRED)\n    find_package(OpenSSL REQUIRED)\n\n    add_executable(${PROJECT_NAME}\n                   src/main.c\n                   src/net_impl.c\n                   src/tls_impl.c)\n    target_link_libraries(${PROJECT_NAME} PRIVATE anjay OpenSSL::SSL)\n\nSome minor changes have been made here:\n\n* The project is linked with the OpenSSL library by calling ``find_package``\n  and adding ``OpenSSL::SSL`` to ``target_link_libraries``.\n* Minimum required CMake version has been raised to 3.4 as that is the first\n  version in which the new-style ``OpenSSL::SSL`` target is provided.\n* The `tls_impl.c\n  <https://github.com/AVSystem/Anjay/blob/master/examples/custom-tls/stub/src/tls_impl.c>`_\n  file has been added to the executable target. Just like with the custom\n  network layer, the functions defined there will be called by Anjay or its\n  dependent libraries.\n\n.. note::\n\n    The ``main.c`` and ``net_impl.c`` files are left completely unchanged\n    compared to the :doc:`../NetworkingAPI/NetworkingAPI-IpStickiness` version.\n    In fact, in the repository, they are symbolic links to the files from that\n    tutorial.\n\n    While we encourage you to familiarize yourself with the code in those files,\n    it is not really relevant to this article. It is an example code that\n    establishes a basic connection with a LwM2M server and implements a simple\n    but full-featured networking layer.\n\nGlobal initialization\n---------------------\n\nJust like with the network layer, the APIs that need to be implemented are\nprivate, so we also start with manually including the forward declarations, as\nquoted in the :doc:`previous article <../CustomTLS>`:\n\n.. highlight:: c\n.. snippet-source:: examples/custom-tls/stub/src/tls_impl.c\n\n    avs_error_t _avs_net_initialize_global_ssl_state(void);\n    void _avs_net_cleanup_global_ssl_state(void);\n    avs_error_t _avs_net_create_ssl_socket(avs_net_socket_t **socket,\n                                           const void *socket_configuration);\n    avs_error_t _avs_net_create_dtls_socket(avs_net_socket_t **socket,\n                                            const void *socket_configuration);\n\nThe OpenSSL library needs global initialization, so ``OPENSSL_init_ssl``\nfunction is called in ``_avs_net_initialize_global_ssl_state()``. There is no\nneed for any explicit cleanup, so the ``_avs_net_cleanup_global_ssl_state()``\nfunction can be left empty:\n\n.. highlight:: c\n.. snippet-source:: examples/custom-tls/stub/src/tls_impl.c\n\n    avs_error_t _avs_net_initialize_global_ssl_state(void) {\n        if (!OPENSSL_init_ssl(OPENSSL_INIT_ADD_ALL_CIPHERS\n                                      | OPENSSL_INIT_ADD_ALL_DIGESTS,\n                              NULL)) {\n            return avs_errno(AVS_EPROTO);\n        }\n        return AVS_OK;\n    }\n\n    void _avs_net_cleanup_global_ssl_state(void) {}\n\nIn addition to the custom network layer, we also need to provide an implementation\nof the ``avs_crypto_clear_buffer()`` function. This function should securely\nclear a given buffer and must be written in a way that guarantees the compiler\nwill not optimize it out. For integrating with OpenSSL we can use\n``OPENSSL_cleanse()`` function as an actual implementation but other crypto\nbackends might require a different approach.\n\n.. highlight:: c\n.. snippet-source:: examples/custom-tls/stub/src/tls_impl.c\n\n    void avs_crypto_clear_buffer(void *buf, size_t size) {\n        OPENSSL_cleanse(buf, size);\n    }\n\nTLS socket structure stub\n-------------------------\n\nIn The TLS socket object, the following information will need to be kept:\n\n* The \"backend socket\", the underlying unencrypted socket. We will use the\n  previously implemented ``avs_net`` socket for that purpose, so that as many\n  features as possible can be simply forwarded.\n\n* The ``SSL_CTX`` object that contains the state that OpenSSL intends to be\n  reused between similar connections.\n\n* The ``SSL`` object that contains the per-connection OpenSSL state.\n\nThe way TLS-related APIs are designed in Anjay makes it impossible to share an\n``SSL_CTX`` object between multiple connections, so both of the above need to be\npresent for each connection.\n\n.. highlight:: c\n.. snippet-source:: examples/custom-tls/stub/src/tls_impl.c\n\n    typedef struct {\n        const avs_net_socket_v_table_t *operations;\n        avs_net_socket_t *backend_socket;\n        SSL_CTX *ctx;\n        SSL *ssl;\n    } tls_socket_impl_t;\n\nImplementing socket methods\n---------------------------\n\nForwarded functions\n^^^^^^^^^^^^^^^^^^^\n\nMost of the auxiliary functions not related to actual data transmission or\nhandshakes, can just forward the calls to the underlying backend socket:\n\n.. highlight:: c\n.. snippet-source:: examples/custom-tls/stub/src/tls_impl.c\n    :emphasize-lines: 9-12\n\n    static avs_error_t\n    tls_bind(avs_net_socket_t *sock_, const char *address, const char *port) {\n        tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n        return avs_net_socket_bind(sock->backend_socket, address, port);\n    }\n\n    static avs_error_t tls_close(avs_net_socket_t *sock_) {\n        tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n        if (sock->ssl) {\n            SSL_free(sock->ssl);\n            sock->ssl = NULL;\n        }\n        return avs_net_socket_close(sock->backend_socket);\n    }\n\n    static avs_error_t tls_shutdown(avs_net_socket_t *sock_) {\n        tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n        return avs_net_socket_shutdown(sock->backend_socket);\n    }\n\n    static avs_error_t tls_cleanup(avs_net_socket_t **sock_ptr) {\n        return avs_errno(AVS_ENOTSUP);\n    }\n\n    static const void *tls_system_socket(avs_net_socket_t *sock_) {\n        tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n        return avs_net_socket_get_system(sock->backend_socket);\n    }\n\n    static avs_error_t tls_remote_host(avs_net_socket_t *sock_,\n                                       char *out_buffer,\n                                       size_t out_buffer_size) {\n        tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n        return avs_net_socket_get_remote_host(sock->backend_socket, out_buffer,\n                                              out_buffer_size);\n    }\n\n    static avs_error_t tls_remote_hostname(avs_net_socket_t *sock_,\n                                           char *out_buffer,\n                                           size_t out_buffer_size) {\n        tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n        return avs_net_socket_get_remote_hostname(sock->backend_socket, out_buffer,\n                                                  out_buffer_size);\n    }\n\n    static avs_error_t tls_remote_port(avs_net_socket_t *sock_,\n                                       char *out_buffer,\n                                       size_t out_buffer_size) {\n        tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n        return avs_net_socket_get_remote_port(sock->backend_socket, out_buffer,\n                                              out_buffer_size);\n    }\n\n    static avs_error_t tls_local_port(avs_net_socket_t *sock_,\n                                      char *out_buffer,\n                                      size_t out_buffer_size) {\n        tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n        return avs_net_socket_get_local_port(sock->backend_socket, out_buffer,\n                                             out_buffer_size);\n    }\n\n    static avs_error_t tls_get_opt(avs_net_socket_t *sock_,\n                                   avs_net_socket_opt_key_t option_key,\n                                   avs_net_socket_opt_value_t *out_option_value) {\n        tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n        return avs_net_socket_get_opt(sock->backend_socket, option_key,\n                                      out_option_value);\n    }\n\n    static avs_error_t tls_set_opt(avs_net_socket_t *sock_,\n                                   avs_net_socket_opt_key_t option_key,\n                                   avs_net_socket_opt_value_t option_value) {\n        tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n        return avs_net_socket_set_opt(sock->backend_socket, option_key,\n                                      option_value);\n    }\n\nThe ``tls_close`` function additionally frees the ``SSL`` object, as OpenSSL\ndocumentation recommends creating new one for each connection - see\n`SSL_clear <https://www.openssl.org/docs/man1.1.1/man3/SSL_clear.html>`_ for\ndetails.\n\nConnect method stub\n^^^^^^^^^^^^^^^^^^^\n\nThe ``connect`` method is supposed to perform the TLS handshake, but this is\nbeyond the scope of this boilerplate stub. However, let's extract a separate\nfunction that will be used for this purpose:\n\n.. highlight:: c\n.. snippet-source:: examples/custom-tls/stub/src/tls_impl.c\n\n    static avs_error_t perform_handshake(tls_socket_impl_t *sock,\n                                         const char *host) {\n        return avs_errno(AVS_ENOTSUP);\n    }\n\n    static avs_error_t\n    tls_connect(avs_net_socket_t *sock_, const char *host, const char *port) {\n        tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n        avs_error_t err;\n        if (avs_is_err((\n                    err = avs_net_socket_connect(sock->backend_socket, host, port)))\n                || avs_is_err((err = perform_handshake(sock, host)))) {\n            if (sock->ssl) {\n                SSL_free(sock->ssl);\n                sock->ssl = NULL;\n            }\n            avs_net_socket_close(sock->backend_socket);\n        }\n        return err;\n    }\n\nSend method\n^^^^^^^^^^^\n\nThe ``send`` method is very similar to the one in the unencrypted socket\nimplementation, but using ``SSL_write`` instead of ``send``:\n\n.. highlight:: c\n.. snippet-source:: examples/custom-tls/stub/src/tls_impl.c\n\n    static avs_error_t\n    tls_send(avs_net_socket_t *sock_, const void *buffer, size_t buffer_length) {\n        tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n        int result = SSL_write(sock->ssl, buffer, (int) buffer_length);\n        if (result < 0 || (size_t) result < buffer_length) {\n            return avs_errno(AVS_EPROTO);\n        }\n        return AVS_OK;\n    }\n\nReceive method\n^^^^^^^^^^^^^^\n\nThe ``receive`` method is also very similar to the one in the unencrypted socket\nimplementation. However, as we do not have direct access to the file descriptor\nand the configured receive timeout, we need to extract them from the backend\nsocket before actually calling ``poll()``.\n\nThese additional operations may be unnecessary if we implemented the TLS socket\nso that OpenSSL would actually call the underlying unencrypted socket. However,\nin this tutorial, the default BIO implementations from OpenSSL will be used for\nsimplicity.\n\n.. highlight:: c\n.. snippet-source:: examples/custom-tls/stub/src/tls_impl.c\n    :emphasize-lines: 6-13\n\n    static avs_error_t tls_receive(avs_net_socket_t *sock_,\n                                   size_t *out_bytes_received,\n                                   void *buffer,\n                                   size_t buffer_length) {\n        tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n        const void *fd_ptr = avs_net_socket_get_system(sock->backend_socket);\n        avs_net_socket_opt_value_t timeout;\n        if (!fd_ptr\n                || avs_is_err(avs_net_socket_get_opt(\n                           sock->backend_socket, AVS_NET_SOCKET_OPT_RECV_TIMEOUT,\n                           &timeout))) {\n            return avs_errno(AVS_EBADF);\n        }\n        struct pollfd pfd = {\n            .fd = *(const int *) fd_ptr,\n            .events = POLLIN\n        };\n        int64_t timeout_ms;\n        if (avs_time_duration_to_scalar(&timeout_ms, AVS_TIME_MS,\n                                        timeout.recv_timeout)) {\n            timeout_ms = -1;\n        } else if (timeout_ms < 0) {\n            timeout_ms = 0;\n        }\n        if (poll(&pfd, 1, (int) timeout_ms) == 0) {\n            return avs_errno(AVS_ETIMEDOUT);\n        }\n        int bytes_received = SSL_read(sock->ssl, buffer, (int) buffer_length);\n        if (bytes_received < 0) {\n            return avs_errno(AVS_EPROTO);\n        }\n        *out_bytes_received = (size_t) bytes_received;\n        if (buffer_length > 0 && (size_t) bytes_received == buffer_length) {\n            return avs_errno(AVS_EMSGSIZE);\n        }\n        return AVS_OK;\n    }\n\nVirtual method table and constructor function stubs\n---------------------------------------------------\n\nWith all the methods implemented or stubbed, we are ready to declare the virtual\nmethod table. However, actual socket creation will be described in the next\ntutorial:\n\n.. highlight:: c\n.. snippet-source:: examples/custom-tls/stub/src/tls_impl.c\n\n    static const avs_net_socket_v_table_t TLS_SOCKET_VTABLE = {\n        .connect = tls_connect,\n        .send = tls_send,\n        .receive = tls_receive,\n        .bind = tls_bind,\n        .close = tls_close,\n        .shutdown = tls_shutdown,\n        .cleanup = tls_cleanup,\n        .get_system_socket = tls_system_socket,\n        .get_remote_host = tls_remote_host,\n        .get_remote_hostname = tls_remote_hostname,\n        .get_remote_port = tls_remote_port,\n        .get_local_port = tls_local_port,\n        .get_opt = tls_get_opt,\n        .set_opt = tls_set_opt\n    };\n\n    avs_error_t _avs_net_create_dtls_socket(avs_net_socket_t **socket_ptr,\n                                            const void *configuration) {\n        return avs_errno(AVS_ENOTSUP);\n    }\n\n    avs_error_t _avs_net_create_ssl_socket(avs_net_socket_t **socket_ptr,\n                                           const void *configuration) {\n        return avs_errno(AVS_ENOTSUP);\n    }\n"
  },
  {
    "path": "doc/sphinx/source/PortingGuideForNonPOSIXPlatforms/CustomTLS/CustomTLS-TCPSupport.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nSupport for TLS over TCP\n========================\n\n.. contents:: :local:\n\n.. note::\n\n    Code related to this tutorial can be found under\n    `examples/custom-tls/tcp-support\n    <https://github.com/AVSystem/Anjay/tree/master/examples/custom-tls/tcp-support>`_\n    in the Anjay source directory.\n\nIntroduction\n------------\n\nAll the previous tutorials in this chapter were implementing support for DTLS, a\nvariant of TLS adapted to work over unreliable datagram-based transports such as\nUDP.\n\nWhile that is the most important protocol required for LwM2M communication,\nimplementing support for traditional TLS that works over the TCP transport might\nbe desirable in some cases, such as support for the CoAP+TCP binding introduced\nin LwM2M 1.1, or HTTPS downloads for e.g. firmware update.\n\nThis tutorial combines code from :doc:`the previous one\n<CustomTLS-CertificatesAdvanced>` and from the\n:doc:`../../FirmwareUpdateTutorial/FU-SecureDownloads` tutorial from the\n:doc:`../../FirmwareUpdateTutorial` chapter, and updates the (D)TLS integration\nlayer to support regular TLS, enabling support for HTTPS firmware downloads.\n\n.. note::\n\n    The code for this tutorial contains the following source files:\n\n    * ``main.c``, ``firmware_update.h``, ``time_object.c`` and ``time_object.h``\n      are symbolic links to the ones from\n      :doc:`../../FirmwareUpdateTutorial/FU-SecureDownloads`. Those just\n      implement a simple basic LwM2M client with support for the Time and\n      Firmware Update objects. Intricate knowledge of this code is not required.\n\n    * ``firmware_update.c`` is copied from\n      :doc:`../../FirmwareUpdateTutorial/FU-SecureDownloads` with slight\n      modifications so that certificate and key files are loaded into memory\n      buffers, and only those are passed to the TLS layer. This is necessary\n      because the example TLS integration discussed in this chapter does not\n      support the ``AVS_CRYPTO_DATA_SOURCE_FILE`` data source. These changes are\n      discussed in greater detail below.\n\n    * ``net_impl.c`` is a symbolic link to the file from\n      :doc:`../NetworkingAPI/NetworkingAPI-IpStickiness`, just like in all other\n      tutorials in this chapter.\n\n    * ``tls_impl.c`` is copied from :doc:`the previous tutorial\n      <CustomTLS-CertificatesAdvanced>` with changes to make regular TLS work.\n      These changes are the main topic of this article.\n\nAdapting the application code\n-----------------------------\n\nThe application code for this tutorial is based on the application made in the\n:doc:`../../FirmwareUpdateTutorial`. The security configuration used there is\nsupposed to load the certificates and keys from files, and we haven't\nimplemented support for the ``AVS_CRYPTO_DATA_SOURCE_FILE`` data source in the\nTLS integration layer.\n\nFor this reason, we need to modify the ``firmware_update.c`` file so that the\ncertificates and keys are loaded from files into memory, before passing them\ninto the security configuration. This also means that they now need to be stored\nin the binary DER format.\n\n.. highlight:: c\n.. snippet-source:: examples/custom-tls/tcp-support/src/firmware_update.c\n    :emphasize-lines: 1-28, 41-57, 62-67\n\n    static int\n    load_buffer_from_file(uint8_t **out, size_t *out_size, const char *filename) {\n        FILE *f = fopen(filename, \"rb\");\n        if (!f) {\n            return -1;\n        }\n        int result = -1;\n        if (fseek(f, 0, SEEK_END)) {\n            goto finish;\n        }\n        long size = ftell(f);\n        if (size < 0 || (unsigned long) size > SIZE_MAX || fseek(f, 0, SEEK_SET)) {\n            goto finish;\n        }\n        *out_size = (size_t) size;\n        if (!(*out = (uint8_t *) avs_malloc(*out_size))) {\n            goto finish;\n        }\n        if (fread(*out, *out_size, 1, f) != 1) {\n            avs_free(*out);\n            *out = NULL;\n            goto finish;\n        }\n        result = 0;\n    finish:\n        fclose(f);\n        return result;\n    }\n\n    static int fw_get_security_config(void *user_ptr,\n                                      anjay_security_config_t *out_security_info,\n                                      const char *download_uri) {\n        (void) user_ptr;\n        if (!anjay_security_config_from_dm(FW_STATE.anjay, out_security_info,\n                                           download_uri)) {\n            // found a match\n            return 0;\n        }\n\n        // no match found, fallback to loading certificates from given paths\n        static uint8_t *ca_cert = NULL;\n        static size_t ca_cert_size = 0;\n        static uint8_t *client_cert = NULL;\n        static size_t client_cert_size = 0;\n        static uint8_t *client_key = NULL;\n        static size_t client_key_size = 0;\n        if ((!ca_cert\n             && load_buffer_from_file(&ca_cert, &ca_cert_size,\n                                      \"./certs/CA.crt.der\"))\n                || (!client_cert\n                    && load_buffer_from_file(&client_cert, &client_cert_size,\n                                             \"./certs/client.crt.der\"))\n                || (!client_key\n                    && load_buffer_from_file(&client_key, &client_key_size,\n                                             \"./certs/client.key.der\"))) {\n            return -1;\n        }\n\n        memset(out_security_info, 0, sizeof(*out_security_info));\n        const avs_net_certificate_info_t cert_info = {\n            .server_cert_validation = true,\n            .trusted_certs = avs_crypto_certificate_chain_info_from_buffer(\n                    ca_cert, ca_cert_size),\n            .client_cert = avs_crypto_certificate_chain_info_from_buffer(\n                    client_cert, client_cert_size),\n            .client_key = avs_crypto_private_key_info_from_buffer(\n                    client_key, client_key_size, NULL)\n        };\n        out_security_info->security_info =\n                avs_net_security_info_from_certificates(cert_info);\n        return 0;\n    }\n\n.. note::\n\n    The ``load_buffer_from_file()`` is identical to the one introduced in the\n    :doc:`../../AdvancedTopics/AT-Certificates` tutorial (aside from removed\n    logger calls). The code from that chapter has also already been used in the\n    :doc:`CustomTLS-CertificatesBasic` and :doc:`CustomTLS-CertificatesAdvanced`\n    tutorials.\n\nPrerequisites\n-------------\n\nMost of the implementation of secure sockets is reused between stream-based TLS\nand datagram DTLS sockets. However, the subtle differences are present across\nall stages of communication, so we need to store the socket type for later\nchecks. This means that we need an additional field in the ``tls_socket_impl_t``\nstructure:\n\n.. highlight:: c\n.. snippet-source:: examples/custom-tls/tcp-support/src/tls_impl.c\n    :emphasize-lines: 3\n\n    typedef struct {\n        const avs_net_socket_v_table_t *operations;\n        avs_net_socket_type_t backend_type;\n        avs_net_socket_t *backend_socket;\n        SSL_CTX *ctx;\n        SSL *ssl;\n\n        char psk[256];\n        size_t psk_size;\n        char identity[128];\n        size_t identity_size;\n\n        bool dane_enabled;\n        char dane_tlsa_association_data_buf[4096];\n        avs_net_socket_dane_tlsa_record_t dane_tlsa_array[4];\n        size_t dane_tlsa_array_size;\n\n        void *session_resumption_buffer;\n        size_t session_resumption_buffer_size;\n\n        char server_name_indication[256];\n        unsigned int dtls_hs_timeout_min_us;\n        unsigned int dtls_hs_timeout_max_us;\n    } tls_socket_impl_t;\n\nUpdates to the socket creation\n------------------------------\n\nIn all previous tutorials, all the socket creation code was directly implemented\nin ``_avs_net_create_dtls_socket()``. To support both TLS and DTLS, the logic is\nextracted to a new function called ``create_tls_socket()`` and wrapped in both\n``_avs_net_create_dtls_socket()`` and ``_avs_net_create_ssl_socket()``:\n\n.. highlight:: c\n.. snippet-source:: examples/custom-tls/tcp-support/src/tls_impl.c\n    :emphasize-lines: 1-4, 15, 18-38, 78-90\n\n    static avs_error_t\n    create_tls_socket(avs_net_socket_t **socket_ptr,\n                      avs_net_socket_type_t backend_type,\n                      const avs_net_ssl_configuration_t *configuration) {\n        assert(socket_ptr);\n        assert(!*socket_ptr);\n        assert(configuration);\n        tls_socket_impl_t *socket =\n                (tls_socket_impl_t *) avs_calloc(1, sizeof(tls_socket_impl_t));\n        if (!socket) {\n            return avs_errno(AVS_ENOMEM);\n        }\n        *socket_ptr = (avs_net_socket_t *) socket;\n        socket->operations = &TLS_SOCKET_VTABLE;\n        socket->backend_type = backend_type;\n\n        avs_error_t err = AVS_OK;\n        if (backend_type == AVS_NET_UDP_SOCKET) {\n            if (avs_is_ok((err = avs_net_udp_socket_create(\n                                   &socket->backend_socket,\n                                   &configuration->backend_configuration)))\n                    && !(socket->ctx = SSL_CTX_new(DTLS_method()))) {\n                err = avs_errno(AVS_ENOMEM);\n            }\n            if (avs_is_ok(err)) {\n                err = configure_dtls_version(socket, configuration->version);\n            }\n        } else {\n            if (avs_is_ok((err = avs_net_tcp_socket_create(\n                                   &socket->backend_socket,\n                                   &configuration->backend_configuration)))\n                    && !(socket->ctx = SSL_CTX_new(TLS_method()))) {\n                err = avs_errno(AVS_ENOMEM);\n            }\n            if (avs_is_ok(err)) {\n                err = configure_tls_version(socket, configuration->version);\n            }\n        }\n        if (avs_is_ok(err)) {\n            switch (configuration->security.mode) {\n            case AVS_NET_SECURITY_PSK:\n                err = configure_psk(socket, &configuration->security.data.psk);\n                break;\n            case AVS_NET_SECURITY_CERTIFICATE:\n                err = configure_certs(socket, &configuration->security.data.cert);\n                break;\n            default:\n                err = avs_errno(AVS_ENOTSUP);\n            }\n        }\n        if (avs_is_err(err)\n                || avs_is_err((\n                           err = configure_dtls_handshake_timeouts(\n                                   socket, configuration->dtls_handshake_timeouts)))\n                || avs_is_err((err = configure_ciphersuites(\n                                       socket, &configuration->ciphersuites)))\n                || avs_is_err((err = configure_sni(\n                                       socket,\n                                       configuration->server_name_indication)))) {\n            avs_net_socket_cleanup(socket_ptr);\n            return err;\n        }\n        SSL_CTX_set_mode(socket->ctx, SSL_MODE_AUTO_RETRY);\n        if (configuration->session_resumption_buffer_size > 0) {\n            assert(configuration->session_resumption_buffer);\n            socket->session_resumption_buffer =\n                    configuration->session_resumption_buffer;\n            socket->session_resumption_buffer_size =\n                    configuration->session_resumption_buffer_size;\n            SSL_CTX_set_session_cache_mode(\n                    socket->ctx,\n                    SSL_SESS_CACHE_CLIENT | SSL_SESS_CACHE_NO_INTERNAL_STORE);\n            SSL_CTX_sess_set_new_cb(socket->ctx, new_session_cb);\n        }\n        return AVS_OK;\n    }\n\n    avs_error_t _avs_net_create_dtls_socket(avs_net_socket_t **socket_ptr,\n                                            const void *configuration) {\n        return create_tls_socket(\n                socket_ptr, AVS_NET_UDP_SOCKET,\n                (const avs_net_ssl_configuration_t *) configuration);\n    }\n\n    avs_error_t _avs_net_create_ssl_socket(avs_net_socket_t **socket_ptr,\n                                           const void *configuration) {\n        return create_tls_socket(\n                socket_ptr, AVS_NET_TCP_SOCKET,\n                (const avs_net_ssl_configuration_t *) configuration);\n    }\n\nThe UDP/DTLS and TCP/TLS variants of socket creation differ in the following\nways:\n\n* Either ``avs_net_udp_socket_create()`` or ``avs_net_tcp_socket_create()`` is\n  used to instantiate the backend socket\n\n* Either ``DTLS_method()`` or ``TLS_method()`` is passed to ``SSL_CTX_new()``\n\n* Either ``configure_dtls_version()`` or ``configure_tls_version()`` is used to\n  configure the version of the protocol. ``configure_tls_version()`` itself is a\n  new function, very similar to the DTLS variant, but using different constants\n  for the protocol versions:\n\n.. highlight:: c\n.. snippet-source:: examples/custom-tls/tcp-support/src/tls_impl.c\n\n    static avs_error_t configure_tls_version(tls_socket_impl_t *sock,\n                                             avs_net_ssl_version_t version) {\n        switch (version) {\n        case AVS_NET_SSL_VERSION_DEFAULT:\n        case AVS_NET_SSL_VERSION_SSLv2_OR_3:\n            return AVS_OK;\n        case AVS_NET_SSL_VERSION_SSLv3:\n            SSL_CTX_set_min_proto_version(sock->ctx, SSL3_VERSION);\n            return AVS_OK;\n        case AVS_NET_SSL_VERSION_TLSv1:\n            SSL_CTX_set_min_proto_version(sock->ctx, TLS1_VERSION);\n            return AVS_OK;\n        case AVS_NET_SSL_VERSION_TLSv1_1:\n            SSL_CTX_set_min_proto_version(sock->ctx, TLS1_1_VERSION);\n            return AVS_OK;\n        case AVS_NET_SSL_VERSION_TLSv1_2:\n            SSL_CTX_set_min_proto_version(sock->ctx, TLS1_2_VERSION);\n            return AVS_OK;\n        case AVS_NET_SSL_VERSION_TLSv1_3:\n            SSL_CTX_set_min_proto_version(sock->ctx, TLS1_3_VERSION);\n            return AVS_OK;\n        default:\n            return avs_errno(AVS_ENOTSUP);\n        }\n    }\n\n.. note::\n\n    This implementation provides basic support for TLS 1.3. However, please note\n    that proper TLS 1.3 support may require additional adjustments, such as\n    :ref:`configuring ciphersuites differently <custom-tls-tls13-ciphersuites>`.\n    Depending on the underlying (D)TLS library, session resumption support may\n    also need to be implemented in a different way.\n\n    (D)TLS 1.3 support is not addressed thoroughly in this tutorial due to low\n    level of support for TLS 1.3 and especially DTLS 1.3 in mainstream\n    implementations at the time of writing. Please refer to the reference\n    implementations (`avs_mbedtls_socket.c\n    <https://github.com/AVSystem/avs_commons/blob/master/src/net/mbedtls/avs_mbedtls_socket.c>`__\n    and `avs_openssl.c\n    <https://github.com/AVSystem/avs_commons/blob/master/src/net/openssl/avs_openssl.c>`__)\n    for examples.\n\nUpdates to the handshake process\n--------------------------------\n\nIn OpenSSL, a different type of BIO object is used for TLS and DTLS protocols.\n``perform_handshake()`` function must thus be updated accordingly, so that\n``BIO_new_dgram()`` is used for DTLS and ``BIO_new_socket()`` for TLS:\n\n.. highlight:: c\n.. snippet-source:: examples/custom-tls/tcp-support/src/tls_impl.c\n    :emphasize-lines: 53-64\n\n    static avs_error_t perform_handshake(tls_socket_impl_t *sock,\n                                         const char *host) {\n        union {\n            struct sockaddr addr;\n            struct sockaddr_storage storage;\n        } peername;\n        const void *fd_ptr = avs_net_socket_get_system(sock->backend_socket);\n        if (!fd_ptr\n                || getpeername(*(const int *) fd_ptr, &peername.addr,\n                               &(socklen_t) { sizeof(peername) })) {\n            return avs_errno(AVS_EBADF);\n        }\n\n        sock->ssl = SSL_new(sock->ctx);\n        if (!sock->ssl) {\n            return avs_errno(AVS_ENOMEM);\n        }\n\n        SSL_set_app_data(sock->ssl, sock);\n        if (sock->dane_enabled) {\n            // NOTE: SSL_dane_enable() calls SSL_set_tlsext_host_name() internally\n            SSL_dane_enable(sock->ssl, host);\n            bool have_usable_tlsa_records = false;\n            for (size_t i = 0; i < sock->dane_tlsa_array_size; ++i) {\n                if (SSL_CTX_get_verify_mode(sock->ctx) == SSL_VERIFY_NONE\n                        && (sock->dane_tlsa_array[i].certificate_usage\n                                    == AVS_NET_SOCKET_DANE_CA_CONSTRAINT\n                            || sock->dane_tlsa_array[i].certificate_usage\n                                       == AVS_NET_SOCKET_DANE_SERVICE_CERTIFICATE_CONSTRAINT)) {\n                    // PKIX-TA and PKIX-EE constraints are unusable for\n                    // opportunistic clients\n                    continue;\n                }\n                SSL_dane_tlsa_add(\n                        sock->ssl,\n                        (uint8_t) sock->dane_tlsa_array[i].certificate_usage,\n                        (uint8_t) sock->dane_tlsa_array[i].selector,\n                        (uint8_t) sock->dane_tlsa_array[i].matching_type,\n                        (unsigned const char *) sock->dane_tlsa_array[i]\n                                .association_data,\n                        sock->dane_tlsa_array[i].association_data_size);\n                have_usable_tlsa_records = true;\n            }\n            if (SSL_CTX_get_verify_mode(sock->ctx) == SSL_VERIFY_NONE\n                    && have_usable_tlsa_records) {\n                SSL_set_verify(sock->ssl, SSL_VERIFY_PEER, NULL);\n            }\n        } else {\n            SSL_set_tlsext_host_name(sock->ssl, host);\n        }\n        SSL_set1_host(sock->ssl, host);\n\n        BIO *bio = NULL;\n        if (sock->backend_type == AVS_NET_UDP_SOCKET) {\n            bio = BIO_new_dgram(*(const int *) fd_ptr, 0);\n            BIO_ctrl(bio, BIO_CTRL_DGRAM_SET_CONNECTED, 0, &peername.addr);\n            DTLS_set_timer_cb(sock->ssl, dtls_timer_cb);\n        } else {\n            bio = BIO_new_socket(*(const int *) fd_ptr, 0);\n        }\n        if (!bio) {\n            return avs_errno(AVS_ENOMEM);\n        }\n        SSL_set_bio(sock->ssl, bio, bio);\n\n        if (sock->session_resumption_buffer) {\n            const unsigned char *ptr =\n                    (const unsigned char *) sock->session_resumption_buffer;\n            SSL_SESSION *session =\n                    d2i_SSL_SESSION(NULL, &ptr,\n                                    sock->session_resumption_buffer_size);\n            if (session) {\n                SSL_set_session(sock->ssl, session);\n                SSL_SESSION_free(session);\n            }\n        }\n\n        if (SSL_connect(sock->ssl) <= 0) {\n            return avs_errno(AVS_EPROTO);\n        }\n        return AVS_OK;\n    }\n\nUpdates to the data receiving procedure\n---------------------------------------\n\nStream-oriented and datagram-oriented transport protocols are fundamentally\ndifferent.\n\nIn datagram-oriented communication, the data is transmitted in well-defined\nseparate packets (datagrams), which are atomic in nature. A datagram will never\nbe split into smaller chunks, at least not on the application layer. Datagrams\nmay, however, be lost or reordered, especially in the case of the most common\ndatagram transport protocol, UDP.\n\nStream-oriented communication, on the other hand, treats the connection as a\nstream of bytes. The stream is guaranteed to arrive in its entirety and in\nproper order, and the application is generally free to send and receive bytes at\nits own pace, in arbitrarily small chunks. Boundaries between physical data\npackets transmitted over the network do not need to correlate with how the send\nand receive operations are called by the application.\n\nBoth OpenSSL and avs_commons use the same APIs for interacting with both\nstream-oriented (TCP/TLS) and datagram-oriented (UDP/DTLS) protocols. However,\nthe requirements are different between the two:\n\n* If a datagram is received only in part, due to lack of space in the buffer, it\n  shall be treated as an error; for stream-oriented communication, it is a\n  normal condition, and receiving may continue with the next call.\n\n* We are using the ``poll()`` system call to wait for new data to arrive on the\n  underlying unencrypted socket. This makes no sense if using a stream-oriented\n  socket and there is already data decrypted and buffered by OpenSSL, but not\n  passed down to application code yet (e.g. after a previous partial read\n  operation); ``SSL_pending()`` function can be used to query how many bytes are\n  left in the buffer.\n\nIt is thus necessary to modify the ``tls_receive()`` function to handle these\ndifferences appropriately:\n\n.. highlight:: c\n.. snippet-source:: examples/custom-tls/tcp-support/src/tls_impl.c\n    :emphasize-lines: 6-12, 41\n\n    static avs_error_t tls_receive(avs_net_socket_t *sock_,\n                                   size_t *out_bytes_received,\n                                   void *buffer,\n                                   size_t buffer_length) {\n        tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n        int pending = 0;\n        if (sock->backend_type == AVS_NET_TCP_SOCKET) {\n            pending = SSL_pending(sock->ssl);\n        }\n        if (pending > 0) {\n            buffer_length = AVS_MIN(buffer_length, (size_t) pending);\n        } else {\n            const void *fd_ptr = avs_net_socket_get_system(sock->backend_socket);\n            avs_net_socket_opt_value_t timeout;\n            if (!fd_ptr\n                    || avs_is_err(avs_net_socket_get_opt(\n                               sock->backend_socket,\n                               AVS_NET_SOCKET_OPT_RECV_TIMEOUT, &timeout))) {\n                return avs_errno(AVS_EBADF);\n            }\n            struct pollfd pfd = {\n                .fd = *(const int *) fd_ptr,\n                .events = POLLIN\n            };\n            int64_t timeout_ms;\n            if (avs_time_duration_to_scalar(&timeout_ms, AVS_TIME_MS,\n                                            timeout.recv_timeout)) {\n                timeout_ms = -1;\n            } else if (timeout_ms < 0) {\n                timeout_ms = 0;\n            }\n            if (poll(&pfd, 1, (int) timeout_ms) == 0) {\n                return avs_errno(AVS_ETIMEDOUT);\n            }\n        }\n        int bytes_received = SSL_read(sock->ssl, buffer, (int) buffer_length);\n        if (bytes_received < 0) {\n            return avs_errno(AVS_EPROTO);\n        }\n        *out_bytes_received = (size_t) bytes_received;\n        if (sock->backend_type == AVS_NET_UDP_SOCKET && buffer_length > 0\n                && (size_t) bytes_received == buffer_length) {\n            return avs_errno(AVS_EMSGSIZE);\n        }\n        return AVS_OK;\n    }\n\nConclusion\n----------\n\nThe above changes are enough to make communication using TLS over TCP to work.\nThe example application corresponding to this tutorial is able to both connect\nto a LwM2M server using DTLS transport in PSK mode, and perform firmware\ndownload using HTTPS (HTTP over TLS) with mode traditional certificate-based\nsecurity.\n\nPlease note that the example implementation developed in this chapter still does\nnot implement all the features of avs_commons' TLS integration API.\nSpecifically, the following topics were not covered:\n\n* **Loading certificates and keys from other sources than memory buffers is not\n  supported.** This may be desirable for e.g. firmware downloads, as evident in\n  this article. Please note that when using files as the data source, it is\n  generally expected to support both the PEM and DER file formats, and to\n  automatically detect between the two.\n\n* **DTLS Connection ID extension is not supported.** This is currently not\n  supported in OpenSSL at all, which makes this topic infeasible to cover in\n  this tutorial. Please take a look at the `avs_mbedtls_socket.c\n  <https://github.com/AVSystem/avs_commons/blob/master/src/net/mbedtls/avs_mbedtls_socket.c>`__\n  file in avs_commons to see how it can be implemented using Mbed TLS - the\n  relevant parts of the code can be found by searching for usages of the\n  ``use_connection_id`` field.\n\n* **TLS alert codes are not forwarded to calling code.** LwM2M 1.1 expects TLS\n  alert codes to be exposed through the data model. OpenSSL does not expose\n  these alerts codes to the user, either, so it is also infeasible to cover this\n  topic in this tutorial.\n\n  It is expected that if an alert code is received during the handshake\n  procedure, the alert code shall be wrapped into an ``avs_error_t`` object\n  using `avs_net_ssl_alert()\n  <https://github.com/AVSystem/avs_commons/blob/master/include_public/avsystem/commons/avs_socket.h#L358>`_\n  and returned as a result from the ``connect`` operation. Alert handling may\n  also be added to the ``receive`` operation as well.\n\n  Please see the implementation and usages of the ``return_alert_if_any()``\n  function in the `avs_mbedtls_socket.c\n  <https://github.com/AVSystem/avs_commons/blob/master/src/net/mbedtls/avs_mbedtls_socket.c>`__\n  file in avs_commons to see how it can be implemented using Mbed TLS.\n\n* **Support for the SSL error API is not implemented.** This API requires\n  the ``connect()`` function to return ``avs_error_t`` objects created using\n  ``avs_net_ssl_lib_error()`` or ``avs_net_ssl_alert()`` defined in\n  ``deps/avs_commons/include_public/avsystem/commons/avs_socket.h``.\n\n* **Socket file descriptor is used directly instead of wrapping** ``avs_net``\n  **APIs, and the** ``decorate`` **function is not implemented.** The secure SMS\n  mode will thus not work in versions that include the SMS commercial feature.\n\n* **The** ``rebuild_client_cert_chain`` **flag in**\n  ``avs_net_certificate_info_t`` **is not supported.** The implications of that\n  have been discussed in more detail in the\n  :ref:`custom-tls-api-certificates-basic-limitations` sections of the\n  :doc:`CustomTLS-CertificatesBasic` tutorial. Please take a look at the\n  ``rebuild_client_cert_chain()`` functions in the `avs_mbedtls_socket.c\n  <https://github.com/AVSystem/avs_commons/blob/master/src/net/mbedtls/avs_mbedtls_socket.c#L1380>`__\n  and `avs_openssl.c\n  <https://github.com/AVSystem/avs_commons/blob/master/src/net/openssl/avs_openssl.c#L1148>`__\n  files in avs_commons for examples on how to implement this feature if needed.\n"
  },
  {
    "path": "doc/sphinx/source/PortingGuideForNonPOSIXPlatforms/CustomTLS.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nCustom (D)TLS layers\n====================\n\n.. highlight:: c\n\nIntroduction\n------------\n\n``avs_crypto`` and ``avs_net`` include full-featured, ready-to-use integrations\nwith the TLS and DTLS protocols, using either\n`OpenSSL <https://www.openssl.org/>`_ or `Mbed TLS <https://www.trustedfirmware.org/projects/mbed-tls/>`_,\nas well as a basic implementation that supports the PSK mode only, using\n`tinydtls <https://projects.eclipse.org/projects/iot.tinydtls>`_.\n\nThese integrations use the ``avs_net`` socket APIs underneath, so if the socket\nlayer is implemented properly (either using the default implementation or by the\nuser, as described in the :doc:`previous chapter <NetworkingAPI>`), all the\nnecessary security features will work properly.\n\nHowever, in modern embedded development, it is sometimes desirable to offload\nall TLS processing - for example, a cellular modem may provide integrated TLS\nimplementation, controlled e.g. via AT commands. For this reason, a\nuser-provided implementation of TLS and DTLS \"sockets\" may be provided instead.\n\nThis chapter is a guide for implementing all the features used by Anjay for TLS\ncommunication. The examples recreate an integration with OpenSSL 1.1.1,\nsimplified compared to the default one, but they are also intended to provide\na reference for integrating with any other TLS API.\n\n.. toctree::\n   :glob:\n   :titlesonly:\n\n   CustomTLS/CustomTLS-Stub\n   CustomTLS/CustomTLS-Minimal\n   CustomTLS/CustomTLS-Resumption\n   CustomTLS/CustomTLS-ConfigFeatures\n   CustomTLS/CustomTLS-CertificatesBasic\n   CustomTLS/CustomTLS-CertificatesAdvanced\n   CustomTLS/CustomTLS-TCPSupport\n\nTheory of operation\n-------------------\n\nTLS and DTLS integration in Anjay is based on the same ``avs_net`` socket APIs\nas the basic unencrypted TCP and UDP sockets, with a couple of minor\nadjustments:\n\n* The configuration structure passed when creating the socket is different\n  (``avs_net_ssl_configuration_t`` instead of\n  ``avs_net_socket_configuration_t``), and contains the security configuration,\n  including keys and certificates used for communication.\n\n* The ``connect`` and ``accept`` (if supported) operations are expected to\n  perform the TLS/DTLS handshake.\n\n* Dedicated option keys for the ``get_opt``/``set_opt`` operations control\n  additional TLS/DTLS features:\n\n  * ``AVS_NET_SOCKET_OPT_SESSION_RESUMED`` may be queried to check whether the\n    handshake resulted in a new session, or a resumption of an existing one -\n    this is used by Anjay to check whether a Register operation is necessary\n  * ``AVS_NET_SOCKET_OPT_DANE_TLSA_ARRAY`` may be used to specify additional\n    peer certificate data for validation according to the `DANE\n    <https://en.wikipedia.org/wiki/DNS-based_Authentication_of_Named_Entities>`_\n    mechanism - LwM2M 1.1 specifies an almost identical flow for verifying the\n    server certificate\n\n* The ``AVS_NET_SOCKET_OPT_INNER_MTU`` option shall take the DTLS header\n  overhead into account.\n\n* Additional ``decorate`` operation may be provided to support securing\n  communication over a pre-existing unencrypted socket - this is currently used\n  by the SMS commercial feature of Anjay to provide security for the SMS\n  transport.\n\nList of functions to implement\n------------------------------\n\nSupport for custom TLS layer needs to be enabled in the compile-time\nconfiguration first:\n\n* When using CMake, use ``-DDTLS_BACKEND=custom`` when configuring Anjay.\n\n* When using another build system, enable ``AVS_COMMONS_WITH_CUSTOM_TLS`` and\n  disable ``AVS_COMMONS_WITH_MBEDTLS``, ``AVS_COMMONS_WITH_OPENSSL`` and\n  ``AVS_COMMONS_WITH_TINYDTLS`` in ``avs_commons_config.h``.\n\n* Usually you should also disable\n  ``AVS_COMMONS_WITH_AVS_CRYPTO_ADVANCED_FEATURES`` in ``avs_commons_config.h``.\n  You will most likely want to disable features related to OSCORE and EST if you\n  are using a version of Anjay that includes these commercial features.\n\n  * If you need OSCORE or EST, you will need to implement advanced cryptographic\n    functions related to AEAD, HKDF and processing various crypto-related file\n    formats, that are normally provided by OpenSSL or Mbed TLS. This is not\n    thoroughly supported and not covered by this documentation at the moment.\n\nImplementations of the following functions will need to be provided:\n\n* ``_avs_net_create_dtls_socket`` - a function with the following signature:\n\n  .. snippet-source:: deps/avs_commons/src/net/avs_net_impl.h\n\n      avs_error_t _avs_net_create_dtls_socket(avs_net_socket_t **socket,\n                                              const void *socket_configuration);\n\n  ``socket_configuration`` argument is a pointer to\n  ``const avs_net_ssl_configuration_t`` struct cast to ``void *``.\n\n  Otherwise the function has similar semantics and requirements to the\n  ``_avs_net_create_udp_socket`` function described in :doc:`NetworkingAPI`.\n\n* ``_avs_net_create_ssl_socket`` - only required if the ``fw_update`` module\n  should support HTTPS transfers, or if support for CoAP over TCP is desired.\n  Otherwise, it can be safely implemented as ``return avs_errno(AVS_ENOTSUP);``.\n\n  .. snippet-source:: deps/avs_commons/src/net/avs_net_impl.h\n\n      avs_error_t _avs_net_create_ssl_socket(avs_net_socket_t **socket,\n                                             const void *socket_configuration);\n\n  ``socket_configuration`` argument is a pointer to\n  ``const avs_net_ssl_configuration_t`` struct cast to ``void *``.\n\n  Otherwise the function has similar semantics and requirements to the\n  ``_avs_net_create_tcp_socket`` function described in :doc:`NetworkingAPI`.\n\n* ``_avs_net_initialize_global_ssl_state`` - a function with the following\n  signature:\n\n  .. snippet-source:: deps/avs_commons/src/net/avs_net_global.h\n\n      avs_error_t _avs_net_initialize_global_ssl_state(void);\n\n  The function should return ``AVS_OK`` on success and an error code on error.\n  It should initialize any global state that needs to be kept by the TLS\n  implementation, and initialize external libraries if necessary. If there is no\n  such global state or it is initialized elsewhere, it is safe to implement this\n  function as a no-op (``return AVS_OK;``).\n\n* ``_avs_net_cleanup_global_ssl_state`` - a function with the following\n  signature:\n\n  .. snippet-source:: deps/avs_commons/src/net/avs_net_global.h\n\n      void _avs_net_cleanup_global_ssl_state(void);\n\n  The function should clean up any global state that is kept by the TLS\n  implementation. If there is no such global state or it is managed elsewhere,\n  it is safe to implement this function as a no-op.\n\n* ``avs_crypto_clear_buffer`` – a function with the following signature:\n\n  .. snippet-source:: deps/avs_commons/include_public/avsystem/commons/avs_crypto_common.h\n\n      void avs_crypto_clear_buffer(void *buf, size_t size);\n\n  This function shall securely overwrite the memory region pointed to by\n  ``buf`` with a sequence of bytes (typically zeros) in a way that prevents the\n  compiler from optimizing the memory clearing operation away. It is used to\n  ensure that sensitive data such as cryptographic keys, passwords, or\n  temporary plaintext is not left in memory after it is no longer needed.\n"
  },
  {
    "path": "doc/sphinx/source/PortingGuideForNonPOSIXPlatforms/NetworkingAPI/NetworkingAPI-Bind.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nBind operation\n==============\n\n.. contents:: :local:\n\n.. note::\n\n    Code related to this tutorial can be found under\n    `examples/custom-network/bind\n    <https://github.com/AVSystem/Anjay/tree/master/examples/custom-network/bind>`_\n    in the Anjay source directory.\n\nIntroduction\n------------\n\nThis tutorial builds up on :doc:`the previous one\n<NetworkingAPI-RemoteHostPort>` and adds support for the bind operation.\n\nThis will allow use of the `anjay_configuration_t::udp_listen_port\n<../../api/structanjay__configuration.html#acf74549a99ca3ad5aedb227c4b0258ca>`_\nsetting, which might be useful e.g. for the LwM2M 1.0-style Server-Initiated\nBootstrap.\n\nWe will also add support for the \"get local port\" operation, which will allow\neven ephemeral listening port number to be retained between subsequent\nconnections to the same server.\n\nBind operation itself\n---------------------\n\nImplementation of the bind function is very similar to the previously\nimplemented :ref:`non-posix-networking-api-connect` one. Important changes are\nhighlighted.\n\n.. highlight:: c\n.. snippet-source:: examples/custom-network/bind/src/net_impl.c\n    :emphasize-lines: 5, 20-21, 23, 26-29\n\n    static avs_error_t\n    net_bind(avs_net_socket_t *sock_, const char *address, const char *port) {\n        net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n        struct addrinfo hints = {\n            .ai_flags = AI_PASSIVE,\n            .ai_socktype = sock->socktype\n        };\n        if (sock->fd >= 0) {\n            getsockopt(sock->fd, SOL_SOCKET, SO_DOMAIN, &hints.ai_family,\n                       &(socklen_t) { sizeof(hints.ai_family) });\n        }\n        struct addrinfo *addr = NULL;\n        avs_error_t err = AVS_OK;\n        if (getaddrinfo(address, port, &hints, &addr) || !addr) {\n            err = avs_errno(AVS_EADDRNOTAVAIL);\n        } else if ((sock->fd < 0\n                    && (sock->fd = socket(addr->ai_family, addr->ai_socktype,\n                                          addr->ai_protocol))\n                               < 0)\n                   || setsockopt(sock->fd, SOL_SOCKET, SO_REUSEADDR, &(int) { 1 },\n                                 sizeof(int))) {\n            err = avs_errno(AVS_UNKNOWN_ERROR);\n        } else if (bind(sock->fd, addr->ai_addr, addr->ai_addrlen)) {\n            err = avs_errno(AVS_ECONNREFUSED);\n        }\n        if (avs_is_err(err) && sock->fd >= 0) {\n            close(sock->fd);\n            sock->fd = -1;\n        }\n        freeaddrinfo(addr);\n        return err;\n    }\n\nThis time ``getaddrinfo()`` is called with ``AI_PASSIVE`` flag to allow wildcard\naddresses (e.g. ``0.0.0.0``) and to prevent DNS resolution for such local\naddresses.\n\nOf course, ``bind()`` is called instead of ``connect()``. But before doing so,\n``setsockopt()`` is called to enable the ``SO_REUSEADDR`` flag. This is done\nbecause Anjay may create multiple sockets bound to the same port, one for each\nremote server connection. This shall not result in a conflict, as all those\nsockets will be connected to different remote servers shortly after binding.\n\n.. note::\n\n    More properly, ``SO_REUSEADDR`` should only be used if the ``reuse_addr``\n    flag has been set in the ``avs_net_socket_configuration_t`` structure passed\n    at socket creation time.\n\n    However, Anjay always sets this flag to ``true``, so it is alright to set it\n    unconditionally in such simplistic implementation.\n\nFinally, in case of error, the underlying socket descriptor is closed. This is\nto ensure that upon error, the socket will not end up in the \"bound\" state,\nwhich will be evident in the modifications to ``net_get_opt()`` illustrated\nbelow.\n\nChanges to net_get_opt()\n------------------------\n\nChanges to this function are highlighted:\n\n.. highlight:: c\n.. snippet-source:: examples/custom-network/bind/src/net_impl.c\n    :emphasize-lines: 13-22\n\n    static avs_error_t net_get_opt(avs_net_socket_t *sock_,\n                                   avs_net_socket_opt_key_t option_key,\n                                   avs_net_socket_opt_value_t *out_option_value) {\n        net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n        switch (option_key) {\n        case AVS_NET_SOCKET_OPT_RECV_TIMEOUT:\n            out_option_value->recv_timeout = sock->recv_timeout;\n            return AVS_OK;\n        case AVS_NET_SOCKET_OPT_STATE:\n            if (sock->fd < 0) {\n                out_option_value->state = AVS_NET_SOCKET_STATE_CLOSED;\n            } else {\n                sockaddr_union_t addr;\n                if (!getpeername(sock->fd, &addr.addr,\n                                 &(socklen_t) { sizeof(addr) })\n                        && ((addr.in.sin_family == AF_INET && addr.in.sin_port != 0)\n                            || (addr.in6.sin6_family == AF_INET6\n                                && addr.in6.sin6_port != 0))) {\n                    out_option_value->state = AVS_NET_SOCKET_STATE_CONNECTED;\n                } else {\n                    out_option_value->state = AVS_NET_SOCKET_STATE_BOUND;\n                }\n            }\n            return AVS_OK;\n        case AVS_NET_SOCKET_OPT_INNER_MTU:\n            out_option_value->mtu = 1464;\n            return AVS_OK;\n        case AVS_NET_SOCKET_HAS_BUFFERED_DATA:\n            out_option_value->flag = false;\n            return AVS_OK;\n        default:\n            return avs_errno(AVS_ENOTSUP);\n        }\n    }\n\nThe original variant assumed that if the socket descriptor was present, it is\nconnected. Here, we need to differentiate between the \"connected\" and \"bound\"\nstates - hence we use the ``getpeername()`` function to check if there is a\nvalid remote address.\n\nBecause ``getpeername()`` might return different kind of socket addresses, the\n``sockaddr_union_t`` type :ref:`declared in the previous tutorial\n<non-posix-networking-api-get-remote-host>` is used.\n\nGet local port operation\n------------------------\n\nThe \"get local port\" operation may or may not be implemented. It is not\nnecessary for the bind operation to work, but if implemented, it will allow\nAnjay to keep ephemeral listening port number consistent across subsequent\nconnections to the same server if `anjay_configuration_t::udp_listen_port\n<../../api/structanjay__configuration.html#acf74549a99ca3ad5aedb227c4b0258ca>`_\nis not set.\n\nIts implementation mirrors the :ref:`non-posix-networking-api-get-remote-port`\nfrom the previous tutorial, only with ``getsockname()`` used instead of\n``getpeername()``:\n\n.. highlight:: c\n.. snippet-source:: examples/custom-network/bind/src/net_impl.c\n    :emphasize-lines: 6\n\n    static avs_error_t net_local_port(avs_net_socket_t *sock_,\n                                      char *out_buffer,\n                                      size_t out_buffer_size) {\n        net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n        sockaddr_union_t addr;\n        if (getsockname(sock->fd, &addr.addr, &(socklen_t) { sizeof(addr) })) {\n            return avs_errno(AVS_UNKNOWN_ERROR);\n        }\n        return stringify_sockaddr_port(&addr, out_buffer, out_buffer_size);\n    }\n\nUpdate to vtable\n----------------\n\nOf course the newly implemented functions need to be referenced in the virtual\nmethod table:\n\n.. highlight:: c\n.. snippet-source:: examples/custom-network/bind/src/net_impl.c\n    :emphasize-lines: 5, 11\n\n    static const avs_net_socket_v_table_t NET_SOCKET_VTABLE = {\n        .connect = net_connect,\n        .send = net_send,\n        .receive = net_receive,\n        .bind = net_bind,\n        .close = net_close,\n        .cleanup = net_cleanup,\n        .get_system_socket = net_system_socket,\n        .get_remote_host = net_remote_host,\n        .get_remote_port = net_remote_port,\n        .get_local_port = net_local_port,\n        .get_opt = net_get_opt,\n        .set_opt = net_set_opt\n    };\n"
  },
  {
    "path": "doc/sphinx/source/PortingGuideForNonPOSIXPlatforms/NetworkingAPI/NetworkingAPI-EventLoopSupport.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nEvent loop support\n==================\n\n.. highlight:: c\n\n.. contents:: :local:\n\nIntroduction\n------------\n\nWhen ``WITH_POSIX_AVS_SOCKET`` option is disabled when compiling Anjay,\n``WITH_EVENT_LOOP`` will normally be disabled as well. That means that the\n``anjay_event_loop_run()`` and ``anjay_serve_any()`` functions will not be\navailable, and applications will generally need to implement a\n:doc:`/AdvancedTopics/AT-CustomEventLoop` instead.\n\nHowever, as long as the underlying API provides a function reasonably similar\nto either ``select()`` or ``poll()``, it is possible to enable the event loop\nfunctionality by providing a POSIX compatibility header and manually enabling\n``WITH_EVENT_LOOP``.\n\nDeciding between select() and poll()\n------------------------------------\n\nTwo equivalent implementations of the event loop are provided in Anjay - one\nuses the ``select()`` call, the other uses ``poll()``. ``poll()`` is generally\npreferred due to known limitations of ``select()``. On Unix-like systems, when\nusing CMake to compile the library, one or the other implementation is chosen\nautomatically based on whether ``poll()`` is available in the system.\n\nThe event loop uses these APIs directly because the ``avs_net`` layer does not\nprovide abstraction over the concept of polling multiple sockets. It has been\ndecided that this is a solution simpler than significantly extending the\n``avs_net`` API.\n\nThe implementation based on ``select()`` requires the following APIs, reasonably\nsimilar to the ones defined in Unix-like systems, to be available:\n\n* ``sockfd_t`` type to represent the socket descriptor - normally a ``typedef``\n  to ``int``\n* optional ``INVALID_SOCKET`` macro - automatically defined to ``-1`` if not\n  explicitly provided\n* ``fd_set`` type\n* ``FD_ZERO()``, ``FD_SET()`` and ``FD_ISSET()`` operations, implemented as\n  functions or macros\n* ``FD_SETSIZE`` constant\n* ``struct timeval`` with ``tv_sec`` and ``tv_usec`` fields\n* ``int select(nfds_t nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout)``\n  function, or a macro that can be called as if it had this signature\n\nConversely, the implementation based on ``poll()`` requires the following:\n\n* ``sockfd_t`` type to represent the socket descriptor - normally a ``typedef``\n  to ``int``\n* optional ``INVALID_SOCKET`` macro - automatically defined to ``-1`` if not\n  explicitly provided\n* ``struct pollfd`` with ``fd`` field of type ``sockfd_t``, as well as\n  ``events`` and ``revents`` fields of scalar types\n* ``POLLIN`` constant, compatible with the ``events`` field mentioned above\n* ``int poll(struct pollfds *fds, size_t nfds, int timeout_ms)`` function, or a\n  macro that can be called as if it had this signature\n\n.. note::\n\n    The ``sockfd_t`` type is not standard on Unix-like systems. It has been\n    introduced to allow for socket descriptor types other than ``int``, found\n    on some systems - e.g. ``SOCKET`` in Win32 is equivalent to ``uintptr_t``.\n\nOne or the other implementation is chosen based on state of the\n``AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_POLL`` compile-time definition. When\nusing CMake for compiling, its value is detected; when manually populating the\nconfiguration headers, it can be configured in ``avs_commons_config.h``.\n\nYou can also add ``#define`` or ``#undef`` for this macro in the POSIX\ncompatibility header, explained below.\n\nWriting the POSIX compatibility header\n--------------------------------------\n\nThe POSIX compatibility header mechanism has originally been conceived as a way\nof allowing the use of the default implementations of the networking API (as\nwell as time API) on platforms that have APIs that are close to the Unix\nstandard but have minor incompatible differences - examples include lwIP and\nWindows.\n\nHowever, when the default networking layer is not in use, a variant of this\nheader limited in scope can be used to provide the minimal API subset required\nfor the event loop.\n\nThe POSIX compatibility header can be any custom header file, specified using\nthe ``-DPOSIX_COMPAT_HEADER`` option on CMake command line, or via the\n``AVS_COMMONS_POSIX_COMPAT_HEADER`` macro in ``avs_commons_config.h``. It is\nutilized as ``#include AVS_COMMONS_POSIX_COMPAT_HEADER`` (when using CMake,\nquotes are added around the value provided on the command line), so please keep\nthe include path configuration in mind or use absolute paths if feasible.\n\nThe header shall contain the necessary ``#include`` directives and declarations\nso that the requirements described above are met.\n\nFor example, a POSIX compatibility header for `Zephyr\n<https://zephyrproject.org/>`_ may look like::\n\n    #include <net/socket.h>\n\n    typedef int sockfd_t;\n\n    #ifndef pollfd\n    #    define pollfd zsock_pollfd\n    #endif // pollfd\n\n    #ifndef poll\n    #    define poll zsock_poll\n    #endif // poll\n\n    #ifndef POLLIN\n    #    define POLLIN ZSOCK_POLLIN\n    #endif // POLLIN\n\nNote that neither include guards nor ``#pragma once`` is required in this file,\nalthough it is permitted to include such guards.\n\n.. note::\n\n    The POSIX compatibility header is also included in the file that implements\n    ``avs_time_real_now()`` and ``avs_time_monotonic_now()`` if\n    ``WITH_POSIX_AVS_TIME`` is enabled, so you may need to also add lines such\n    as ``#include <sys/time.h>`` or consider implementing the :doc:`../TimeAPI`\n    yourself.\n"
  },
  {
    "path": "doc/sphinx/source/PortingGuideForNonPOSIXPlatforms/NetworkingAPI/NetworkingAPI-IpStickiness.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nIP address stickiness support\n=============================\n\n.. contents:: :local:\n\n.. note::\n\n    Code related to this tutorial can be found under\n    `examples/custom-network/ip-stickiness\n    <https://github.com/AVSystem/Anjay/tree/master/examples/custom-network/ip-stickiness>`_\n    in the Anjay source directory.\n\nIntroduction\n------------\n\nThis tutorial builds up on :doc:`the previous one <NetworkingAPI-Stats>` and\nadds support for IP address stickiness, i.e. makes it possible for Anjay to\nguarantee that the same IP address will be used for connecting to a server\nconfigured using a DNS hostname each time, regardless of the order of entries in\nDNS response.\n\n.. important::\n\n    This tutorial expects Anjay to be configured differently than the previous\n    ones. ``WITHOUT_IP_STICKINESS`` should be set to ``OFF`` (default) this\n    time. Otherwise the added code will not be used.\n\nFor the IP stickiness feature to work, the `preferred_endpoint\n<https://github.com/AVSystem/avs_commons/blob/master/include_public/avsystem/commons/avs_socket.h#L157>`_\nfield of ``avs_net_socket_configuration_t`` must be supported. Additionally,\n`avs_net_resolved_endpoint_get_host_port()\n<https://github.com/AVSystem/avs_commons/blob/master/include_public/avsystem/commons/avs_addrinfo.h#L172>`_\nalso has to be implemented.\n\nTheory of operation\n-------------------\n\nThe `avs_net_resolved_endpoint_t\n<https://github.com/AVSystem/avs_commons/blob/master/include_public/avsystem/commons/avs_socket.h#L56>`_\ntype has been introduced so that resolved addresses (e.g. the\n``struct sockaddr`` family can be shared outside of ``avs_commons`` without the\nneed to depend on platform-specific types in public API.\n\nAny data up to ``AVS_NET_SOCKET_RAW_RESOLVED_ENDPOINT_MAX_SIZE`` (128 bytes) in\nsize can be stored in that structure. There is also the ``size`` field that can\nbe used to preserve the size information.\n\nIn our example, we will always store an instance of the :ref:`sockaddr_union_t\n<non-posix-networking-api-get-remote-host>`, and we will always store\n``sizeof(sockaddr_union_t)`` in the ``size`` field. However, your implementation\nis free to use these fields in whatever way you feel is appropriate.\n\nThis type is primarily used in the ``avs_net_addrinfo_resolve()`` family of\nfunctions, which is basically a portable version of the ``getaddrinfo()`` API.\nHowever, this API is not used by Anjay. However, the type may also be used for\nstorage of the preferred endpoint by the :ref:`non-posix-networking-api-connect`\nfunction.\n\nThe ``avs_net_resolved_endpoint_get_host_port()`` function is used to convert a\nresolved address into stringified form that is the primary form of passing host\naddresses in ``avs_commons``. In Anjay, it is actually used only to determine\nthe family (IPv4 vs. IPv6) of the stored address.\n\nInitialization\n--------------\n\n.. highlight:: c\n.. snippet-source:: examples/custom-network/ip-stickiness/src/net_impl.c\n    :emphasize-lines: 10, 31\n\n    typedef struct {\n        const avs_net_socket_v_table_t *operations;\n        int socktype;\n        int fd;\n        avs_time_duration_t recv_timeout;\n        char remote_hostname[256];\n        bool shut_down;\n        size_t bytes_sent;\n        size_t bytes_received;\n        avs_net_resolved_endpoint_t *preferred_endpoint;\n    } net_socket_impl_t;\n\n    // ...\n\n    static avs_error_t\n    net_create_socket(avs_net_socket_t **socket_ptr,\n                      const avs_net_socket_configuration_t *configuration,\n                      int socktype) {\n        assert(socket_ptr);\n        assert(!*socket_ptr);\n        (void) configuration;\n        net_socket_impl_t *socket =\n                (net_socket_impl_t *) avs_calloc(1, sizeof(net_socket_impl_t));\n        if (!socket) {\n            return avs_errno(AVS_ENOMEM);\n        }\n        socket->operations = &NET_SOCKET_VTABLE;\n        socket->socktype = socktype;\n        socket->fd = -1;\n        socket->recv_timeout = avs_time_duration_from_scalar(30, AVS_TIME_S);\n        socket->preferred_endpoint = configuration->preferred_endpoint;\n        *socket_ptr = (avs_net_socket_t *) socket;\n        return AVS_OK;\n    }\n\nThe ``preferred_endpoint`` field is intended as a pointer into user-allocated\nstorage, so we just store that pointer at creation time.\n\nChanges to the connect function\n-------------------------------\n\n.. note::\n\n    In addition to the highlighted changes, the original ``addr`` variable has\n    been renamed to ``addrs``. This change has not been highlighted for clarity.\n\n.. highlight:: c\n.. snippet-source:: examples/custom-network/ip-stickiness/src/net_impl.c\n    :emphasize-lines: 21-38, 42-47\n\n    static avs_error_t\n    net_connect(avs_net_socket_t *sock_, const char *host, const char *port) {\n        net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n        struct addrinfo hints = {\n            .ai_socktype = sock->socktype\n        };\n        if (sock->fd >= 0) {\n            getsockopt(sock->fd, SOL_SOCKET, SO_DOMAIN, &hints.ai_family,\n                       &(socklen_t) { sizeof(hints.ai_family) });\n        }\n        struct addrinfo *addrs = NULL;\n        avs_error_t err = AVS_OK;\n        if (getaddrinfo(host, port, &hints, &addrs) || !addrs) {\n            err = avs_errno(AVS_EADDRNOTAVAIL);\n        } else if (sock->fd < 0\n                   && (sock->fd = socket(addrs->ai_family, addrs->ai_socktype,\n                                         addrs->ai_protocol))\n                              < 0) {\n            err = avs_errno(AVS_UNKNOWN_ERROR);\n        } else {\n            const struct addrinfo *addr = addrs;\n            if (sock->preferred_endpoint\n                    && sock->preferred_endpoint->size == sizeof(sockaddr_union_t)) {\n                while (addr) {\n                    if (addr->ai_addrlen <= sizeof(sockaddr_union_t)\n                            && memcmp(addr->ai_addr,\n                                      sock->preferred_endpoint->data.buf,\n                                      addr->ai_addrlen)\n                                           == 0) {\n                        break;\n                    }\n                    addr = addr->ai_next;\n                }\n            }\n            if (!addr) {\n                // Preferred endpoint not found, use the first one\n                addr = addrs;\n            }\n            if (connect(sock->fd, addr->ai_addr, addr->ai_addrlen)) {\n                err = avs_errno(AVS_ECONNREFUSED);\n            }\n            if (sock->preferred_endpoint && avs_is_ok(err)) {\n                assert(addr->ai_addrlen <= sizeof(sockaddr_union_t));\n                memcpy(sock->preferred_endpoint->data.buf, addr->ai_addr,\n                       addr->ai_addrlen);\n                sock->preferred_endpoint->size = sizeof(sockaddr_union_t);\n            }\n        }\n        if (avs_is_ok(err)) {\n            sock->shut_down = false;\n            snprintf(sock->remote_hostname, sizeof(sock->remote_hostname), \"%s\",\n                     host);\n        }\n        freeaddrinfo(addrs);\n        return err;\n    }\n\nIn the code before the ``connect()`` call, if the ``preferred_endpoint`` pointer\nis set and filled with valid data, we iterate over all the entries in the list\nreturned by ``getaddrinfo()``, and check if any of them matches. If so, that\nentry will be passed to the ``connect()`` function. If not, the first entry will\nbe used.\n\nAfter a successful ``connect()`` call, the selected address is stored into the\n``preferred_endpoint`` structure.\n\navs_net_resolved_endpoint_get_host_port()\n-----------------------------------------\n\n.. highlight:: c\n.. snippet-source:: examples/custom-network/ip-stickiness/src/net_impl.c\n\n    avs_error_t\n    avs_net_resolved_endpoint_get_host_port(const avs_net_resolved_endpoint_t *endp,\n                                            char *host,\n                                            size_t hostlen,\n                                            char *serv,\n                                            size_t servlen) {\n        AVS_STATIC_ASSERT(sizeof(endp->data.buf) >= sizeof(sockaddr_union_t),\n                          data_buffer_big_enough);\n        if (endp->size != sizeof(sockaddr_union_t)) {\n            return avs_errno(AVS_EINVAL);\n        }\n        const sockaddr_union_t *addr = (const sockaddr_union_t *) &endp->data.buf;\n        avs_error_t err = AVS_OK;\n        (void) ((host\n                 && avs_is_err(\n                            (err = stringify_sockaddr_host(addr, host, hostlen))))\n                || (serv\n                    && avs_is_err((err = stringify_sockaddr_port(addr, serv,\n                                                                 servlen)))));\n        return err;\n    }\n\nSince in our implementation ``avs_net_resolved_endpoint_t`` is just a wrapper\naround ``sockaddr_union_t``, we can use the :doc:`previously introduced\n<NetworkingAPI-RemoteHostPort>` ``stringify_sockaddr_host()`` and\n``stringify_sockaddr_port()`` functions.\n\nPlease note however, that either of the ``host`` and ``serv`` arguments may be\n``NULL``, in which case this function shall only fill the non-NULL arguments.\n"
  },
  {
    "path": "doc/sphinx/source/PortingGuideForNonPOSIXPlatforms/NetworkingAPI/NetworkingAPI-Minimal.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nMinimal socket implementation\n=============================\n\n.. contents:: :local:\n\n.. note::\n\n    Code related to this tutorial can be found under\n    `examples/custom-network/minimal\n    <https://github.com/AVSystem/Anjay/tree/master/examples/custom-network/minimal>`_\n    in the Anjay source directory.\n\nIntroduction\n------------\n\nThis tutorial builds up on the :doc:`../../BasicClient/BC-Security` tutorial\nwhich contains an implementation of a minimal, but complete LwM2M client.\n\nHowever, this tutorial is intended to be used with a version of Anjay that has\nbeen compiled without the default network layer implementation, i.e. with these\nadditional CMake flags::\n\n    -DWITH_POSIX_AVS_SOCKET=OFF\n    -DWITHOUT_IP_STICKINESS=ON\n\n.. note::\n\n    This new custom network layer implementation will be based on the POSIX\n    socket APIs. This is not very useful in the real world, as the default\n    implementation works fine in such environment. However, this tutorial is\n    provided as a reference implementation simpler than the actual default one,\n    to make it easier to base your code on it.\n\nAdjustments to the build system\n-------------------------------\n\nThe `CMakeLists.txt <https://github.com/AVSystem/Anjay/blob/master/examples/custom-network/minimal/CMakeLists.txt>`_\nfile has been modified to accommodate for this custom network layer:\n\n.. highlight:: cmake\n.. snippet-source:: examples/custom-network/minimal/CMakeLists.txt\n    :emphasize-lines: 4, 10\n\n    cmake_minimum_required(VERSION 3.16)\n    project(minimal-custom-network C)\n\n    set(CMAKE_C_STANDARD 99)\n\n    find_package(anjay REQUIRED)\n\n    add_executable(${PROJECT_NAME}\n                   src/main.c\n                   src/net_impl.c)\n    target_link_libraries(${PROJECT_NAME} PRIVATE anjay)\n\nTwo changes has been made here:\n\n* The ``set(CMAKE_C_EXTENSIONS OFF)`` setting has been removed. This is because\n  we will need to use POSIX APIs, which are considered extensions to the C\n  standard and would not compile with this flag set.\n* The `net_impl.c\n  <https://github.com/AVSystem/Anjay/blob/master/examples/custom-network/minimal/src/net_impl.c>`_\n  file has been added to the executable target. Note that the functions defined\n  there will be called by Anjay or its dependent libraries, so, in a way, in\n  addition to the normal dependency of the application on the library, the\n  opposite is also true - parts of the library depends on the application as\n  well.\n\n.. note::\n\n    The ``main.c`` is left completely unchanged compared to the\n    :doc:`../../BasicClient/BC-Security` version. In fact, in the repository,\n    it is a symbolic link to the file from that tutorial.\n\nGlobal initialization\n---------------------\n\nThe APIs that need to be implemented are private, so there is no public header\nthat can be included to provide forward declarations of them. Hence, we start\nwith manually including the forward declarations, as quoted in the\n:doc:`previous article <../NetworkingAPI>`:\n\n.. highlight:: c\n.. snippet-source:: examples/custom-network/minimal/src/net_impl.c\n\n    avs_error_t _avs_net_initialize_global_compat_state(void);\n    void _avs_net_cleanup_global_compat_state(void);\n    avs_error_t _avs_net_create_tcp_socket(avs_net_socket_t **socket,\n                                           const void *socket_configuration);\n    avs_error_t _avs_net_create_udp_socket(avs_net_socket_t **socket,\n                                           const void *socket_configuration);\n\nWe actually won't need any global state for our implementation, so implementing\nthe ``_avs_net_{initialize,cleanup}_global_compat_state()`` functions is\ntrivial:\n\n.. highlight:: c\n.. snippet-source:: examples/custom-network/minimal/src/net_impl.c\n\n    avs_error_t _avs_net_initialize_global_compat_state(void) {\n        return AVS_OK;\n    }\n\n    void _avs_net_cleanup_global_compat_state(void) {}\n\nGlobal state may be useful on some platforms where using the network requires\nsome global initialization. For example on Windows, this is the right place to\ncall ``WSAStartup()`` and ``WSACleanup()``.\n\nOn embedded platforms, initialization of network interfaces might also go here,\nalthough typically this is done in the main function, before calling any of the\nAnjay APIs and the network layer implementation assumes that the interface has\nalready been initialized.\n\n.. _non-posix-networking-api-create:\n\nSocket creation\n---------------\n\nSome platforms that handle TCP and UDP communication with completely different\nAPIs (`Mbed OS <https://www.mbed.com/en/platform/mbed-os/>`_ being one such\nexample), will require completely separate code to implement TCP and UDP\ncommunication - or you might choose to implement just one of them, and\nimplement the other ``_avs_net_create_*_socket()`` function as a placeholder\nthat always returns an error code.\n\nWith BSD-style socket API, however, it is actually trivial to support both TCP\nand UDP sockets, so we will do just that.\n\n.. highlight:: c\n.. snippet-source:: examples/custom-network/minimal/src/net_impl.c\n\n    typedef struct {\n        const avs_net_socket_v_table_t *operations;\n        int socktype;\n        int fd;\n        avs_time_duration_t recv_timeout;\n    } net_socket_impl_t;\n\n    // ... implementations of NET_SOCKET_VTABLE functions go here\n    // ... they will be discussed separately later\n\n    static const avs_net_socket_v_table_t NET_SOCKET_VTABLE = {\n        .connect = net_connect,\n        .send = net_send,\n        .receive = net_receive,\n        .close = net_close,\n        .cleanup = net_cleanup,\n        .get_system_socket = net_system_socket,\n        .get_opt = net_get_opt,\n        .set_opt = net_set_opt\n    };\n\n    static avs_error_t\n    net_create_socket(avs_net_socket_t **socket_ptr,\n                      const avs_net_socket_configuration_t *configuration,\n                      int socktype) {\n        assert(socket_ptr);\n        assert(!*socket_ptr);\n        (void) configuration;\n        net_socket_impl_t *socket =\n                (net_socket_impl_t *) avs_calloc(1, sizeof(net_socket_impl_t));\n        if (!socket) {\n            return avs_errno(AVS_ENOMEM);\n        }\n        socket->operations = &NET_SOCKET_VTABLE;\n        socket->socktype = socktype;\n        socket->fd = -1;\n        socket->recv_timeout = avs_time_duration_from_scalar(30, AVS_TIME_S);\n        *socket_ptr = (avs_net_socket_t *) socket;\n        return AVS_OK;\n    }\n\n    avs_error_t _avs_net_create_udp_socket(avs_net_socket_t **socket_ptr,\n                                           const void *configuration) {\n        return net_create_socket(\n                socket_ptr, (const avs_net_socket_configuration_t *) configuration,\n                SOCK_DGRAM);\n    }\n\n    avs_error_t _avs_net_create_tcp_socket(avs_net_socket_t **socket_ptr,\n                                           const void *configuration) {\n        return net_create_socket(\n                socket_ptr, (const avs_net_socket_configuration_t *) configuration,\n                SOCK_STREAM);\n    }\n\n``avs_commons`` uses an object-oriented paradigm for its socket layer. Any\nsocket object needs to be created on the heap - it can be any user-defined\nstructure, but its first member MUST be a pointer to the\n``avs_net_socket_v_table_t`` structure. Functions from that structure will be\ncalled as implementations of all the socket operations.\n\nAside from this ``vtable`` pointer, this minimal implementation contains the\nfollowing fields:\n\n* ``socktype`` - either ``SOCK_DGRAM`` or ``SOCK_STREAM``. The actual\n  ``socket()`` call for creating the OS-level socket descriptor will be deferred\n  until the ``connect`` operation. At that point we will need to know whether we\n  need to create a UDP or TCP socket. This will also slightly alter the behavior\n  of the ``receive`` method. Thus, we need to store the value, determined at\n  socket creation time.\n* ``fd`` - the OS-level file descriptor referring to the actual socket.\n* ``recv_timeout`` - timeout for the ``receive`` operation. Anjay uses timed\n  ``receive`` operation extensively, to provide appropriate retransmission and\n  timeout behavior on higher layers, as required by the CoAP and LwM2M\n  protocols. This timeout is controlled by ``get_opt`` and ``set_opt``\n  operations, so it needs to be stored between method calls.\n\nThe actual ``_avs_net_create_udp_socket()`` and ``_avs_net_create_tcp_socket()``\nfunctions are implemented as thin wrappers to the static ``net_create_socket``\nfunction, which allocates the socket object, initializes ``vtable`` and\n``socktype`` fields, as well as sets ``fd`` to ``-1`` (signifying no OS-level\nsocket descriptor initialized yet) and initial ``recv_timeout`` to 30 seconds.\n\nImplementing socket methods\n---------------------------\n\n.. _non-posix-networking-api-connect:\n\nConnect\n^^^^^^^\n\n.. highlight:: c\n.. snippet-source:: examples/custom-network/minimal/src/net_impl.c\n\n    static avs_error_t\n    net_connect(avs_net_socket_t *sock_, const char *host, const char *port) {\n        net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n        struct addrinfo hints = {\n            .ai_socktype = sock->socktype\n        };\n        if (sock->fd >= 0) {\n            getsockopt(sock->fd, SOL_SOCKET, SO_DOMAIN, &hints.ai_family,\n                       &(socklen_t) { sizeof(hints.ai_family) });\n        }\n        struct addrinfo *addr = NULL;\n        avs_error_t err = AVS_OK;\n        if (getaddrinfo(host, port, &hints, &addr) || !addr) {\n            err = avs_errno(AVS_EADDRNOTAVAIL);\n        } else if (sock->fd < 0\n                   && (sock->fd = socket(addr->ai_family, addr->ai_socktype,\n                                         addr->ai_protocol))\n                              < 0) {\n            err = avs_errno(AVS_UNKNOWN_ERROR);\n        } else if (connect(sock->fd, addr->ai_addr, addr->ai_addrlen)) {\n            err = avs_errno(AVS_ECONNREFUSED);\n        }\n        freeaddrinfo(addr);\n        return err;\n    }\n\nIn each of the vtable methods, the first ``avs_net_socket_t *`` argument is the\n\"self\" pointer. It is intended to be cast to the actual type that has been\nallocated for the socket.\n\nTo call the POSIX ``connect()`` function, we need a socket address formatted as\nsome structure from the ``struct sockaddr`` family. ``avs_commons`` use strings\nfor representing TCP/IP endpoint information - ``host`` can be either a\nstringified IP address or a hostname, while ``port`` is a stringified port\nnumber. This is designed to match the API of the POSIX ``getaddrinfo()``\nfunction - as such, it is natural to use it in our implementation.\n\nIn the ``hints`` structure, we fill the ``ai_socktype`` with the type stored at\nsocket creation time - either ``SOCK_DGRAM`` or ``SOCK_STREAM``. If the socket\nfile descriptor has already been created, we also fill ``ai_family`` with the\nsocket family (most likely ``AF_INET`` or ``AF_INET6``).\n\nIf ``getaddrinfo()`` fails, we return the ``avs_errno(AVS_EADDRNOTAVAIL)`` error\ncode.\n\nThen, we create the socket descriptor if needed, and ``connect()`` it -\nreturning the ``avs_errno(AVS_ECONNREFUSED)`` error code if necessary.\n\n.. note::\n\n    For more complete error handling, you can use ``avs_map_errno(errno)``\n    function, declared in ``avs_errno_map.h``, to translate and forward the\n    actual ``errno`` values to the caller. This tutorial uses hardcoded error\n    codes for simplicity.\n\n.. note::\n\n    This simplistic code does not implement some features that might be useful:\n\n    * You might want to try connecting to subsequent addresses from the ``addr``\n      list if the first one fails - especially for TCP. Such issues may happen\n      e.g. when the system has incomplete IPv6 connectivity.\n    * You might want to implement connecting logic in a more sophisticated way,\n      e.g. by putting the socket in non-blocking mode and using ``poll()`` after\n      ``connect()``, to implement better-defined timeout handling when\n      connecting - especially for TCP.\n\nSend\n^^^^\n\nThe ``send()`` implementation is self-explanatory:\n\n.. highlight:: c\n.. snippet-source:: examples/custom-network/minimal/src/net_impl.c\n\n    static avs_error_t\n    net_send(avs_net_socket_t *sock_, const void *buffer, size_t buffer_length) {\n        net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n        ssize_t written = send(sock->fd, buffer, buffer_length, MSG_NOSIGNAL);\n        if (written >= 0 && (size_t) written == buffer_length) {\n            return AVS_OK;\n        }\n        return avs_errno(AVS_EIO);\n    }\n\n.. important::\n\n    This implementation may behave erroneously for TCP. The POSIX API for\n    stream-oriented sockets permits so-called \"short writes\", i.e. the case\n    where ``send()`` writes less data than passed to it is treated as success.\n    The ``avs_commons`` API does not - so a proper implementation of this method\n    for TCP shall call underlying ``send()`` function in a loop until either all\n    data is sent, or an error occurs.\n\n.. note::\n\n    For more completeness, you might want to e.g. call ``poll()`` for the\n    ``POLLOUT`` event, to implement better-defined timeout handling when\n    sending.\n\n.. _non-posix-networking-api-receive:\n\nReceive\n^^^^^^^\n\n.. highlight:: c\n.. snippet-source:: examples/custom-network/minimal/src/net_impl.c\n\n    static avs_error_t net_receive(avs_net_socket_t *sock_,\n                                   size_t *out_bytes_received,\n                                   void *buffer,\n                                   size_t buffer_length) {\n        net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n        struct pollfd pfd = {\n            .fd = sock->fd,\n            .events = POLLIN\n        };\n        int64_t timeout_ms;\n        if (avs_time_duration_to_scalar(&timeout_ms, AVS_TIME_MS,\n                                        sock->recv_timeout)) {\n            timeout_ms = -1;\n        } else if (timeout_ms < 0) {\n            timeout_ms = 0;\n        }\n        if (poll(&pfd, 1, (int) timeout_ms) == 0) {\n            return avs_errno(AVS_ETIMEDOUT);\n        }\n        ssize_t bytes_received = read(sock->fd, buffer, buffer_length);\n        if (bytes_received < 0) {\n            return avs_errno(AVS_EIO);\n        }\n        *out_bytes_received = (size_t) bytes_received;\n        if (buffer_length > 0 && sock->socktype == SOCK_DGRAM\n                && (size_t) bytes_received == buffer_length) {\n            return avs_errno(AVS_EMSGSIZE);\n        }\n        return AVS_OK;\n    }\n\nImplementation of the receive method is a bit more complicated than that of the\nsend method, because proper receive timeout handling is essential for Anjay.\n\nThat's why ``poll()`` with a single socket, waiting for the ``POLLIN`` event is\ncalled before actually calling ``read()``. To call ``poll()``, the configured\nreceive timeout, stored as ``avs_time_duration_t``, needs to be converted to the\nunit expected by ``poll()`` - this is done using\n``avs_time_duration_to_scalar()``, with additional adjustments to ensure\nexpected behavior.\n\nIf a timeout occurs, ``avs_errno(AVS_ETIMEDOUT)`` is returned; if either some\ndata is available or an error occurs, ``read()`` is called - in case of error\nit will return a negative value, which in this implementation is handled by\nreturning ``avs_errno(AVS_EIO)``, but could be more completely handled by\nactually translating the ``errno`` value.\n\nIf some data has been successfully received, ``*out_bytes_received`` shall be\nfilled with the number of bytes received.\n\nFor datagram sockets, it is additionally important to handle the truncated\nmessage case - so that e.g. the CoAP layer can determine whether the received\npayload is complete. Unfortunately, it is non-trivial to do so when using the\n``read()`` function - that's why in this simplistic implementation we\npessimistically assume that if the buffer is fully filled, then the data might\nhave been truncated. Proper handling of this case can be achieved by using the\n``MSG_TRUNC`` flag, which has not been used because it's Linux-specific, or by\nusing the ``recvmsg()`` API, which has not been done here because the more\nconvoluted API of that function would make this example code more difficult to\nfollow.\n\n.. note::\n\n    ``*out_bytes_received`` shall be set for both success and\n    ``avs_errno(AVS_EMSGSIZE)`` cases.\n\nClose\n^^^^^\n\n.. highlight:: c\n.. snippet-source:: examples/custom-network/minimal/src/net_impl.c\n\n    static avs_error_t net_close(avs_net_socket_t *sock_) {\n        net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n        avs_error_t err = AVS_OK;\n        if (sock->fd >= 0) {\n            if (close(sock->fd)) {\n                err = avs_errno(AVS_EIO);\n            }\n            sock->fd = -1;\n        }\n        return err;\n    }\n\nThis function is pretty self-explanatory - but please note that unlike the POSIX\n``close()`` function, the close operation on ``avs_commons`` sockets does\n**not** remove the socket object. This is why the cleanup operation exists.\n\nCleanup\n^^^^^^^\n\n.. highlight:: c\n.. snippet-source:: examples/custom-network/minimal/src/net_impl.c\n\n    static avs_error_t net_cleanup(avs_net_socket_t **sock_ptr) {\n        avs_error_t err = AVS_OK;\n        if (sock_ptr && *sock_ptr) {\n            err = net_close(*sock_ptr);\n            avs_free(*sock_ptr);\n            *sock_ptr = NULL;\n        }\n        return err;\n    }\n\nThe cleanup operation is also self-explanatory, although please note that there\nis no requirement to call the close operation before it - that's why it is\ncalled from inside this function here.\n\nGet system socket\n^^^^^^^^^^^^^^^^^\n\n.. highlight:: c\n.. snippet-source:: examples/custom-network/minimal/src/net_impl.c\n\n    static const void *net_system_socket(avs_net_socket_t *sock_) {\n        net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n        return &sock->fd;\n    }\n\nThis function is only called by Anjay from ``anjay_event_loop_run()`` and\n``anjay_serve_any()`` - but these functions will generally not be available when\nAnjay is configured to use custom socket implementation. However, the \"system\nsocket\" operation is necessary to implement the\n:doc:`../../AdvancedTopics/AT-CustomEventLoop` as well.\n\nOn platforms that use POSIX-style file descriptor numbers, the standard practice\nis to return a pointer to such file descriptor variable. However, the only\nactual requirement is that the usage matches the implementation - so you can\nreturn a pointer to any kind of object that you will be able to use to poll for\nincoming events in the event loop.\n\nGet/set socket options\n^^^^^^^^^^^^^^^^^^^^^^\n\n.. highlight:: c\n.. snippet-source:: examples/custom-network/minimal/src/net_impl.c\n\n    static avs_error_t net_get_opt(avs_net_socket_t *sock_,\n                                   avs_net_socket_opt_key_t option_key,\n                                   avs_net_socket_opt_value_t *out_option_value) {\n        net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n        switch (option_key) {\n        case AVS_NET_SOCKET_OPT_RECV_TIMEOUT:\n            out_option_value->recv_timeout = sock->recv_timeout;\n            return AVS_OK;\n        case AVS_NET_SOCKET_OPT_STATE:\n            if (sock->fd < 0) {\n                out_option_value->state = AVS_NET_SOCKET_STATE_CLOSED;\n            } else {\n                out_option_value->state = AVS_NET_SOCKET_STATE_CONNECTED;\n            }\n            return AVS_OK;\n        case AVS_NET_SOCKET_OPT_INNER_MTU:\n            out_option_value->mtu = 1464;\n            return AVS_OK;\n        case AVS_NET_SOCKET_HAS_BUFFERED_DATA:\n            out_option_value->flag = false;\n            return AVS_OK;\n        default:\n            return avs_errno(AVS_ENOTSUP);\n        }\n    }\n\n    static avs_error_t net_set_opt(avs_net_socket_t *sock_,\n                                   avs_net_socket_opt_key_t option_key,\n                                   avs_net_socket_opt_value_t option_value) {\n        net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n        switch (option_key) {\n        case AVS_NET_SOCKET_OPT_RECV_TIMEOUT:\n            sock->recv_timeout = option_value.recv_timeout;\n            return AVS_OK;\n        default:\n            return avs_errno(AVS_ENOTSUP);\n        }\n    }\n\nThe ``get_opt``/``set_opt`` interface is used for querying and setting various\nstate information about a given socket. The options that can be get or set are\nlisted in the `avs_net_socket_opt_key_t\n<https://github.com/AVSystem/avs_commons/blob/master/include_public/avsystem/commons/avs_socket.h#L502>`_\nenumeration. Option values are passed or returned using the\n`avs_net_socket_opt_value_t\n<https://github.com/AVSystem/avs_commons/blob/master/include_public/avsystem/commons/avs_socket.h#L674>`_\nunion. See the nearby documentation if you need clarification on which field is\nused to pass values for which option.\n\nThree of there options are essential for the operation of Anjay:\n\n* ``AVS_NET_SOCKET_OPT_RECV_TIMEOUT`` - used for getting and setting the current\n  receive timeout, as used by the :ref:`non-posix-networking-api-receive`\n  operation.\n* ``AVS_NET_SOCKET_OPT_STATE`` (get-only) - used to check in which state\n  (closed, shut down, bound, accepted or connected) the socket currently is.\n* ``AVS_NET_SOCKET_OPT_INNER_MTU`` (get-only; only used for UDP) - used to check\n  the number of bytes that can be safely sent and received in a single UDP\n  datagram over the given socket.\n* ``AVS_NET_SOCKET_HAS_BUFFERED_DATA`` (get-only; optional but highly\n  recommended) - used to check whether all data received from the underlying\n  system socket has been processed. This is used to make sure that when control\n  is returned to the event loop, the ``poll()`` call will not stall waiting for\n  new data that in reality has been already buffered and could be retrieved\n  using the avs_commons APIs. This is usually meaningful for (D)TLS connections,\n  but for almost all simple unencrypted socket implementations, this should\n  always return ``false``. If this option is not supported, then the library\n  will always retry receiving data until a timeout condition occurs (timeout is\n  set to zero for subsequent retries), which may lead to stalling of the event\n  loop.\n\n.. note::\n\n    The ``AVS_NET_SOCKET_OPT_INNER_MTU`` option will be used in addition to\n    buffer sizes to e.g. calculate the maximum size of packets for Block-wise\n    CoAP transfers. This is why it is essential to provide this value. If\n    querying this information from the actual connection or network interface is\n    not possible, a hardcoded estimate like the one above should be OK.\n\nLimitations\n-----------\n\nThis minimal implementation is enough to make Anjay run, but a number of\nfunctionalities will not work:\n\n* Attempt to set `anjay_configuration_t::udp_listen_port\n  <../../api/structanjay__configuration.html#acf74549a99ca3ad5aedb227c4b0258ca>`_\n  will result in no connectivity, as the bind operation is not supported.\n* Local port will not be preserved between subsequent connections to the same\n  server.\n* CoAP message cache will not work, regardless of value of the\n  `anjay_configuration_t::msg_cache_size\n  <../../api/structanjay__configuration.html#a3bb16de58b283370b1ab20698dd4849a>`_\n  setting.\n* Suspending CoAP downloads when entering offline mode will not work; downloads\n  will be aborted instead.\n* ``anjay_get_tx_bytes()`` and ``anjay_get_rx_bytes()`` APIs will not work.\n* ``WITHOUT_IP_STICKINESS`` compile-time flag cannot be disabled, which means\n  that when connecting to a server using a domain name, it is not guaranteed\n  that subsequent connections will use the same IP address.\n\nWe will discuss implementing additional methods to address these limitations in\nsubsequent chapters.\n"
  },
  {
    "path": "doc/sphinx/source/PortingGuideForNonPOSIXPlatforms/NetworkingAPI/NetworkingAPI-OtherFeatures.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nOther features\n==============\n\n.. contents:: :local:\n\nIntroduction\n------------\n\nAs you may have observed, the example network layer implementation from the\n:doc:`previous tutorial <NetworkingAPI-IpStickiness>` is just over 400 lines in\nlength, while `the reference implementation\n<https://github.com/AVSystem/avs_commons/tree/master/src/net/compat/posix>`_ is\nover 2000 lines long. You may be wondering what do these thousands of lines\naccount for, if the shorter version already implements all features used by\nAnjay.\n\nThis largely owes to the fact that the network layer in ``avs_commons`` has been\ndesigned not just for Anjay, but for generic use in multiple projects, and can\nalso be used to build third-party applications.\n\nMost of the additional functionality that is not used by Anjay has been\ndeveloped in part or in full due to LibCWMP requirements.\n\nThis article will try to sum up the additional functionality that the reference\nimplementation provides on top of the :doc:`NetworkingAPI-IpStickiness` example.\n\navs_net_addrinfo support\n------------------------\n\nThe reference implementation in ``avs_commons`` provides its own wrapper over\n``getaddrinfo()`` - the ``avs_net_addrinfo_resolve()`` family of functions, that\nis both used internally by the socket implementation, and might be used by user\ncode.\n\nAdditionally, this custom wrapper randomizes the list of addresses returned by\n``getaddrinfo()``, which is a requirement of the CWMP protocol.\n\nAdditional operations\n---------------------\n\nThe following operations, not present in the tutorial implementation, are added:\n\n* ``send_to``\n* ``receive_from``\n* ``accept``, including a non-standard implementation for UDP\n* ``get_interface_name``\n* ``get_local_host``\n\nAdditionally, more options are supported for the ``get_opt`` operation:\n\n* ``AVS_NET_SOCKET_OPT_ADDR_FAMILY``\n* ``AVS_NET_SOCKET_OPT_MTU``\n\nSocket configuration support\n----------------------------\n\nThe reference implementation includes full support for the\n`avs_net_socket_configuration_t\n<https://github.com/AVSystem/avs_commons/blob/master/include_public/avsystem/commons/avs_socket.h#L92>`_\nstructure. No configuration options except ``reuse_addr`` and\n``preferred_endpoint`` are directly used by Anjay, but they can be directly\nspecified by the user through the `socket_config field in anjay_configuration_t\n<../../api/structanjay__configuration.html#a14968e097106889daad258f9e3a066d9>`_.\n\nIn the tutorial implementation all such configuration is ignored.\n\nMore polished implementation\n----------------------------\n\nMethods that exist in the tutorial implementation, are implemented in a more\npolished way in the reference one. Some of such details have been already\nmentioned in notes during the tutorial. These include:\n\n* Proper handling of ``errno`` codes is included.\n* Connect operation falls back to other IP addresses returned by\n  ``getaddrinfo()`` in case of an error.\n* Proper timeout handling is included for connect and send operations.\n* Send operation is implemented using ``recvmsg()`` if possible, resulting in\n  better handling of truncated datagrams.\n* Send operation for TCP implements a loop to handle short writes properly.\n\nAdditional portability\n----------------------\n\nThese tutorials have been written with readability in mind, designed only to\nwork on a typical Linux system. The reference implementation, on the other hand,\nis designed to be highly portable and work not only on any POSIX-compliant\nsystem, but also e.g. on `lwIP <https://www.nongnu.org/lwip/>`_ and\n`Windows Sockets <https://docs.microsoft.com/windows/desktop/WinSock/windows-sockets-start-page-2>`_.\n\nFor this reason, the reference implementation includes multiple alternate\nimplementations for various functions, selected as needed at compile time but\ncontributing to the source code size:\n\n* Support for IPv4 and IPv6 can be separately enabled or disabled at compile\n  time.\n\n  * Additional special handling of IPv4-mapped IPv6 addresses is provided for\n    better interoperability.\n\n* A custom implementation of ``inet_ntop()`` is provided for compatibility with\n  platform that do not provide one.\n\n* Timeout handling in \"receive\" and similar operations may be performed using\n  either ``poll()`` or ``select()``, depending on which one is available.\n\n* ``avs_net_resolved_endpoint_get_host_port()`` may use either ``getnameinfo()``\n  or ``inet_ntop()``, depending on which one is available.\n\n* Receive operation might use either ``recvmsg()`` or ``recvfrom()``, depending\n  on which one is available.\n\n* Network interface name handling might use either ``getifaddrs()`` or\n  ``ioctl(SIOCGIFCONF)``, depending on which one is available.\n"
  },
  {
    "path": "doc/sphinx/source/PortingGuideForNonPOSIXPlatforms/NetworkingAPI/NetworkingAPI-RemoteHostPort.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nGet remote host/port operations\n===============================\n\n.. contents:: :local:\n\n.. note::\n\n    Code related to this tutorial can be found under\n    `examples/custom-network/remote-host-port\n    <https://github.com/AVSystem/Anjay/tree/master/examples/custom-network/remote-host-port>`_\n    in the Anjay source directory.\n\nIntroduction\n------------\n\nThis tutorial builds up on :doc:`the previous one <NetworkingAPI-Minimal>` and\nadds support for the \"get remote host\" and \"get remote port\" operations.\n\nThis will allow CoAP response cache to work and the\n`anjay_configuration_t::msg_cache_size\n<../../api/structanjay__configuration.html#a3bb16de58b283370b1ab20698dd4849a>`_\nconfiguration option to be properly respected.\n\nThis is necessary because the response cache is shared between all the server\nconnections, and remote host/port pairs are used to distinguish between them in\nthe cache storage - and these functions are used to retrieve this information\nfrom sockets.\n\n.. _non-posix-networking-api-get-remote-host:\n\nGet remote host operation\n-------------------------\n\n.. highlight:: c\n.. snippet-source:: examples/custom-network/remote-host-port/src/net_impl.c\n\n    #include <arpa/inet.h>\n\n    // ...\n\n    typedef union {\n        struct sockaddr addr;\n        struct sockaddr_in in;\n        struct sockaddr_in6 in6;\n        struct sockaddr_storage storage;\n    } sockaddr_union_t;\n\n    // ...\n\n    static avs_error_t stringify_sockaddr_host(const sockaddr_union_t *addr,\n                                               char *out_buffer,\n                                               size_t out_buffer_size) {\n        if ((addr->in.sin_family == AF_INET\n             && inet_ntop(AF_INET, &addr->in.sin_addr, out_buffer,\n                          (socklen_t) out_buffer_size))\n                || (addr->in6.sin6_family == AF_INET6\n                    && inet_ntop(AF_INET6, &addr->in6.sin6_addr, out_buffer,\n                                 (socklen_t) out_buffer_size))) {\n            return AVS_OK;\n        }\n        return avs_errno(AVS_UNKNOWN_ERROR);\n    }\n\n    // ...\n\n    static avs_error_t net_remote_host(avs_net_socket_t *sock_,\n                                       char *out_buffer,\n                                       size_t out_buffer_size) {\n        net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n        sockaddr_union_t addr;\n        if (getpeername(sock->fd, &addr.addr, &(socklen_t) { sizeof(addr) })) {\n            return avs_errno(AVS_UNKNOWN_ERROR);\n        }\n        return stringify_sockaddr_host(&addr, out_buffer, out_buffer_size);\n    }\n\nThe ``net_remote_host()`` function essentially wraps the POSIX ``getpeername()``\nfunction. However, that function returns a structure from the ``struct\nsockaddr`` family, while ``avs_commons`` operates on stringified addresses.\n\nBecause several variants of ``struct sockaddr`` may be used, the\n``sockaddr_union_t`` type is declared to accommodate for all supported types.\n\n``stringify_sockaddr_host()`` function converts the IP address stored in\n``struct sockaddr_in`` or ``struct sockaddr_in6`` into stringified form by\ncalling POSIX ``inet_ntop()``.\n\n.. note::\n\n    Out of POSIX APIs, the operations in this tutorial can also be implemented\n    using ``getnameinfo()`` with ``NI_NUMERICSERV`` and ``NI_NUMERICHOST`` flags\n    enabled. ``inet_ntop()`` is used here because of broader compatibility.\n\n.. _non-posix-networking-api-get-remote-port:\n\nGet remote port operation\n-------------------------\n\n.. highlight:: c\n.. snippet-source:: examples/custom-network/remote-host-port/src/net_impl.c\n\n    #include <inttypes.h>\n    // ...\n    #include <avsystem/commons/avs_utils.h>\n\n    // ...\n\n    static avs_error_t stringify_sockaddr_port(const sockaddr_union_t *addr,\n                                               char *out_buffer,\n                                               size_t out_buffer_size) {\n        if ((addr->in.sin_family == AF_INET\n             && avs_simple_snprintf(out_buffer, out_buffer_size, \"%\" PRIu16,\n                                    ntohs(addr->in.sin_port))\n                        >= 0)\n                || (addr->in6.sin6_family == AF_INET6\n                    && avs_simple_snprintf(out_buffer, out_buffer_size, \"%\" PRIu16,\n                                           ntohs(addr->in6.sin6_port))\n                               >= 0)) {\n            return AVS_OK;\n        }\n        return avs_errno(AVS_UNKNOWN_ERROR);\n    }\n\n    // ...\n\n    static avs_error_t net_remote_port(avs_net_socket_t *sock_,\n                                       char *out_buffer,\n                                       size_t out_buffer_size) {\n        net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n        sockaddr_union_t addr;\n        if (getpeername(sock->fd, &addr.addr, &(socklen_t) { sizeof(addr) })) {\n            return avs_errno(AVS_UNKNOWN_ERROR);\n        }\n        return stringify_sockaddr_port(&addr, out_buffer, out_buffer_size);\n    }\n\nSimilar to ``net_remote_host()``, this function also calls ``getpeername()`` -\nbut its companion ``stringify_sockaddr_port()``, instead of examining the IP\naddress stored in the ``sockaddr`` structure, retrieves the port number, and\nstringifies it using ``avs_simple_snprintf()``.\n\nUpdate to vtable\n----------------\n\nOf course the newly implemented function need to be referenced in the virtual\nmethod table:\n\n.. highlight:: c\n.. snippet-source:: examples/custom-network/remote-host-port/src/net_impl.c\n    :emphasize-lines: 8-9\n\n    static const avs_net_socket_v_table_t NET_SOCKET_VTABLE = {\n        .connect = net_connect,\n        .send = net_send,\n        .receive = net_receive,\n        .close = net_close,\n        .cleanup = net_cleanup,\n        .get_system_socket = net_system_socket,\n        .get_remote_host = net_remote_host,\n        .get_remote_port = net_remote_port,\n        .get_opt = net_get_opt,\n        .set_opt = net_set_opt\n    };\n"
  },
  {
    "path": "doc/sphinx/source/PortingGuideForNonPOSIXPlatforms/NetworkingAPI/NetworkingAPI-ShutdownRemoteHostname.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nRemote hostname and shutdown operations\n=======================================\n\n.. contents:: :local:\n\n.. note::\n\n    Code related to this tutorial can be found under\n    `examples/custom-network/shutdown-remote-hostname\n    <https://github.com/AVSystem/Anjay/tree/master/examples/custom-network/shutdown-remote-hostname>`_\n    in the Anjay source directory.\n\nIntroduction\n------------\n\nThis tutorial builds up on :doc:`the previous one <NetworkingAPI-Bind>` and adds\nsupport for the \"get remote hostname\" and \"shutdown\" operations.\n\nThese operations will allow suspending and resuming CoAP downloads when using\nthe \"offline mode\" functionality.\n\nGet remote hostname operation\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n.. highlight:: c\n.. snippet-source:: deps/avs_commons/include_public/avsystem/commons/avs_socket_v_table.h\n\n    typedef avs_error_t (*avs_net_socket_get_remote_hostname_t)(\n            avs_net_socket_t *socket, char *out_buffer, size_t out_buffer_size);\n\nThis operation is similar in concept to the previously introduced\n:ref:`non-posix-networking-api-get-remote-host`. However, \"get remote host\" is\nintended to always return a stringified IP address, while \"get remote hostname\"\nshall return the hostname originally passed to the\n:ref:`non-posix-networking-api-connect` function.\n\nShutdown operation\n^^^^^^^^^^^^^^^^^^\n\n.. highlight:: c\n.. snippet-source:: deps/avs_commons/include_public/avsystem/commons/avs_socket_v_table.h\n\n    typedef avs_error_t (*avs_net_socket_shutdown_t)(avs_net_socket_t *socket);\n\nThis API is intended as a parallel to the POSIX ``shutdown()`` function (called\nwith ``SHUT_RDWR`` mode) - it shall disconnect the socket on the transport\ncontrol layer, but does not close the OS-level socket descriptor.\n\nThis shall put the socket in a state similar to closed, but with the connection\nassociation still in place. This is mostly done to ensure that all \"get\nremote/local host/port\" operations keep returning the same data while the\nconnection is unavailable from the network.\n\nThis operation has additional semantics for (D)TLS sockets - it will shut down\nthe underlying raw socket without gracefully closing the connection on the\n(D)TLS layer. This is however implemented within the (D)TLS backend integration\nand is outside the scope of this implementation.\n\nAdditional socket state\n-----------------------\n\nThe string passed to the :ref:`non-posix-networking-api-connect` function is not\nstored anywhere in our current logic; there is also no standard POSIX API to\ndetermine whether the socket is in the shut down state. That's why we will need\nadditional fields in our socket structure to implement these operations:\n\n.. highlight:: c\n.. snippet-source:: examples/custom-network/shutdown-remote-hostname/src/net_impl.c\n    :emphasize-lines: 6-7\n\n    typedef struct {\n        const avs_net_socket_v_table_t *operations;\n        int socktype;\n        int fd;\n        avs_time_duration_t recv_timeout;\n        char remote_hostname[256];\n        bool shut_down;\n    } net_socket_impl_t;\n\nThe ``remote_hostname`` field will contain the last known hostname to which the\nconnection was successful.\n\nIn our implementation, the ``shut_down`` flag is intended to only be ``true`` if\nthe socket is specifically in the \"shut down\" state - if the socket is either\nbound, connected or closed, it shall be ``false``.\n\nUpdating the socket state\n-------------------------\n\nThe only place where there is direct access to the hostname, is the\n:ref:`non-posix-networking-api-connect` function, so we need to update it\naccordingly to cache this information if the connection is successful:\n\n.. highlight:: c\n.. snippet-source:: examples/custom-network/shutdown-remote-hostname/src/net_impl.c\n    :emphasize-lines: 23-27\n\n    static avs_error_t\n    net_connect(avs_net_socket_t *sock_, const char *host, const char *port) {\n        net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n        struct addrinfo hints = {\n            .ai_socktype = sock->socktype\n        };\n        if (sock->fd >= 0) {\n            getsockopt(sock->fd, SOL_SOCKET, SO_DOMAIN, &hints.ai_family,\n                       &(socklen_t) { sizeof(hints.ai_family) });\n        }\n        struct addrinfo *addr = NULL;\n        avs_error_t err = AVS_OK;\n        if (getaddrinfo(host, port, &hints, &addr) || !addr) {\n            err = avs_errno(AVS_EADDRNOTAVAIL);\n        } else if (sock->fd < 0\n                   && (sock->fd = socket(addr->ai_family, addr->ai_socktype,\n                                         addr->ai_protocol))\n                              < 0) {\n            err = avs_errno(AVS_UNKNOWN_ERROR);\n        } else if (connect(sock->fd, addr->ai_addr, addr->ai_addrlen)) {\n            err = avs_errno(AVS_ECONNREFUSED);\n        }\n        if (avs_is_ok(err)) {\n            sock->shut_down = false;\n            snprintf(sock->remote_hostname, sizeof(sock->remote_hostname), \"%s\",\n                     host);\n        }\n        freeaddrinfo(addr);\n        return err;\n    }\n\nNote that in addition to saving the hostname, we also set the ``shut_down`` flag\nto ``false``. This is because we entered the \"connected\" state, and - as\ndescribed above, the flag is only intended to be ``true`` when the socket is in\nthe \"shut down\" state.\n\nFor this reason, we also need to update this flag in the bind and close\noperations:\n\n.. highlight:: c\n.. snippet-source:: examples/custom-network/shutdown-remote-hostname/src/net_impl.c\n    :emphasize-lines: 25-27, 44\n\n    static avs_error_t\n    net_bind(avs_net_socket_t *sock_, const char *address, const char *port) {\n        net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n        struct addrinfo hints = {\n            .ai_flags = AI_PASSIVE,\n            .ai_socktype = sock->socktype\n        };\n        if (sock->fd >= 0) {\n            getsockopt(sock->fd, SOL_SOCKET, SO_DOMAIN, &hints.ai_family,\n                       &(socklen_t) { sizeof(hints.ai_family) });\n        }\n        struct addrinfo *addr = NULL;\n        avs_error_t err = AVS_OK;\n        if (getaddrinfo(address, port, &hints, &addr) || !addr) {\n            err = avs_errno(AVS_EADDRNOTAVAIL);\n        } else if ((sock->fd < 0\n                    && (sock->fd = socket(addr->ai_family, addr->ai_socktype,\n                                          addr->ai_protocol))\n                               < 0)\n                   || setsockopt(sock->fd, SOL_SOCKET, SO_REUSEADDR, &(int) { 1 },\n                                 sizeof(int))) {\n            err = avs_errno(AVS_UNKNOWN_ERROR);\n        } else if (bind(sock->fd, addr->ai_addr, addr->ai_addrlen)) {\n            err = avs_errno(AVS_ECONNREFUSED);\n        } else {\n            sock->shut_down = false;\n        }\n        if (avs_is_err(err) && sock->fd >= 0) {\n            close(sock->fd);\n            sock->fd = -1;\n        }\n        freeaddrinfo(addr);\n        return err;\n    }\n\n    static avs_error_t net_close(avs_net_socket_t *sock_) {\n        net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n        avs_error_t err = AVS_OK;\n        if (sock->fd >= 0) {\n            if (close(sock->fd)) {\n                err = avs_errno(AVS_EIO);\n            }\n            sock->fd = -1;\n            sock->shut_down = false;\n        }\n        return err;\n    }\n\nUpdate to get_opt implementation\n--------------------------------\n\nWe need to fix implementation of getting the ``AVS_NET_SOCKET_OPT_STATE`` option\nso that the \"shut down\" state is properly reported:\n\n.. highlight:: c\n.. snippet-source:: examples/custom-network/shutdown-remote-hostname/src/net_impl.c\n    :emphasize-lines: 12-14\n\n    static avs_error_t net_get_opt(avs_net_socket_t *sock_,\n                                   avs_net_socket_opt_key_t option_key,\n                                   avs_net_socket_opt_value_t *out_option_value) {\n        net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n        switch (option_key) {\n        case AVS_NET_SOCKET_OPT_RECV_TIMEOUT:\n            out_option_value->recv_timeout = sock->recv_timeout;\n            return AVS_OK;\n        case AVS_NET_SOCKET_OPT_STATE:\n            if (sock->fd < 0) {\n                out_option_value->state = AVS_NET_SOCKET_STATE_CLOSED;\n            } else if (sock->shut_down) {\n                out_option_value->state = AVS_NET_SOCKET_STATE_SHUTDOWN;\n            } else {\n                sockaddr_union_t addr;\n                if (!getpeername(sock->fd, &addr.addr,\n                                 &(socklen_t) { sizeof(addr) })\n                        && ((addr.in.sin_family == AF_INET && addr.in.sin_port != 0)\n                            || (addr.in6.sin6_family == AF_INET6\n                                && addr.in6.sin6_port != 0))) {\n                    out_option_value->state = AVS_NET_SOCKET_STATE_CONNECTED;\n                } else {\n                    out_option_value->state = AVS_NET_SOCKET_STATE_BOUND;\n                }\n            }\n            return AVS_OK;\n        case AVS_NET_SOCKET_OPT_INNER_MTU:\n            out_option_value->mtu = 1464;\n            return AVS_OK;\n        case AVS_NET_SOCKET_HAS_BUFFERED_DATA:\n            out_option_value->flag = false;\n            return AVS_OK;\n        default:\n            return avs_errno(AVS_ENOTSUP);\n        }\n    }\n\n\nNew method implementations\n--------------------------\n\nImplementation of the shutdown operation method is simple and self-explanatory:\n\n.. highlight:: c\n.. snippet-source:: examples/custom-network/shutdown-remote-hostname/src/net_impl.c\n\n    static avs_error_t net_shutdown(avs_net_socket_t *sock_) {\n        net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n        avs_error_t err = avs_errno(AVS_EBADF);\n        if (sock->fd >= 0) {\n            err = shutdown(sock->fd, SHUT_RDWR) ? avs_errno(AVS_EIO) : AVS_OK;\n            sock->shut_down = true;\n        }\n        return err;\n    }\n\nSimilarly, the \"get remote hostname\" method code is just a simple string copy:\n\n.. highlight:: c\n.. snippet-source:: examples/custom-network/shutdown-remote-hostname/src/net_impl.c\n\n    static avs_error_t net_remote_hostname(avs_net_socket_t *sock_,\n                                           char *out_buffer,\n                                           size_t out_buffer_size) {\n        net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n        return avs_simple_snprintf(out_buffer, out_buffer_size, \"%s\",\n                                   sock->remote_hostname)\n                               < 0\n                       ? avs_errno(AVS_UNKNOWN_ERROR)\n                       : AVS_OK;\n    }\n\nOf course the newly implemented functions need to be referenced in the virtual\nmethod table:\n\n.. highlight:: c\n.. snippet-source:: examples/custom-network/shutdown-remote-hostname/src/net_impl.c\n    :emphasize-lines: 7, 11\n\n    static const avs_net_socket_v_table_t NET_SOCKET_VTABLE = {\n        .connect = net_connect,\n        .send = net_send,\n        .receive = net_receive,\n        .bind = net_bind,\n        .close = net_close,\n        .shutdown = net_shutdown,\n        .cleanup = net_cleanup,\n        .get_system_socket = net_system_socket,\n        .get_remote_host = net_remote_host,\n        .get_remote_hostname = net_remote_hostname,\n        .get_remote_port = net_remote_port,\n        .get_local_port = net_local_port,\n        .get_opt = net_get_opt,\n        .set_opt = net_set_opt\n    };\n\n.. note::\n\n    Due to lack of support for IP address stickiness, when resuming CoAP\n    downloads using this code, it might happen that the resumed download will\n    connect to a different node than the original one.\n\n    This limitation will be addressed in a subsequent tutorial.\n"
  },
  {
    "path": "doc/sphinx/source/PortingGuideForNonPOSIXPlatforms/NetworkingAPI/NetworkingAPI-Stats.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nStatistics support\n==================\n\n.. contents:: :local:\n\n.. note::\n\n    Code related to this tutorial can be found under\n    `examples/custom-network/stats\n    <https://github.com/AVSystem/Anjay/tree/master/examples/custom-network/stats>`_\n    in the Anjay source directory.\n\nIntroduction\n------------\n\nThis tutorial builds up on :doc:`the previous one\n<NetworkingAPI-ShutdownRemoteHostname>` and adds support for statistics in the\n\"get options\" operations.\n\nThis will allow the ``anjay_get_tx_bytes()`` and ``anjay_get_rx_bytes()`` APIs\nto work properly.\n\nAdditional socket state\n-----------------------\n\nWe need to store the number of bytes sent and received via the socket, so we add\nappropriate fields to the socket object:\n\n.. highlight:: c\n.. snippet-source:: examples/custom-network/stats/src/net_impl.c\n    :emphasize-lines: 8-9\n\n    typedef struct {\n        const avs_net_socket_v_table_t *operations;\n        int socktype;\n        int fd;\n        avs_time_duration_t recv_timeout;\n        char remote_hostname[256];\n        bool shut_down;\n        size_t bytes_sent;\n        size_t bytes_received;\n    } net_socket_impl_t;\n\nUpdating the socket state\n-------------------------\n\nWe can now update these counters in the send and receive operations:\n\n.. highlight:: c\n.. snippet-source:: examples/custom-network/stats/src/net_impl.c\n    :emphasize-lines: 6, 38\n\n    static avs_error_t\n    net_send(avs_net_socket_t *sock_, const void *buffer, size_t buffer_length) {\n        net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n        ssize_t written = send(sock->fd, buffer, buffer_length, MSG_NOSIGNAL);\n        if (written >= 0) {\n            sock->bytes_sent += (size_t) written;\n            if ((size_t) written == buffer_length) {\n                return AVS_OK;\n            }\n        }\n        return avs_errno(AVS_EIO);\n    }\n\n    static avs_error_t net_receive(avs_net_socket_t *sock_,\n                                   size_t *out_bytes_received,\n                                   void *buffer,\n                                   size_t buffer_length) {\n        net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n        struct pollfd pfd = {\n            .fd = sock->fd,\n            .events = POLLIN\n        };\n        int64_t timeout_ms;\n        if (avs_time_duration_to_scalar(&timeout_ms, AVS_TIME_MS,\n                                        sock->recv_timeout)) {\n            timeout_ms = -1;\n        } else if (timeout_ms < 0) {\n            timeout_ms = 0;\n        }\n        if (poll(&pfd, 1, (int) timeout_ms) == 0) {\n            return avs_errno(AVS_ETIMEDOUT);\n        }\n        ssize_t bytes_received = read(sock->fd, buffer, buffer_length);\n        if (bytes_received < 0) {\n            return avs_errno(AVS_EIO);\n        }\n        *out_bytes_received = (size_t) bytes_received;\n        sock->bytes_received += (size_t) bytes_received;\n        if (buffer_length > 0 && sock->socktype == SOCK_DGRAM\n                && (size_t) bytes_received == buffer_length) {\n            return avs_errno(AVS_EMSGSIZE);\n        }\n        return AVS_OK;\n    }\n\nUpdate to get_opt implementation\n--------------------------------\n\nWe need to add implementations of the ``AVS_NET_SOCKET_OPT_BYTES_SENT`` and\n``AVS_NET_SOCKET_OPT_BYTES_RECEIVED`` options to ``net_get_opt()``:\n\n.. highlight:: c\n.. snippet-source:: examples/custom-network/stats/src/net_impl.c\n    :emphasize-lines: 33-38\n\n    static avs_error_t net_get_opt(avs_net_socket_t *sock_,\n                                   avs_net_socket_opt_key_t option_key,\n                                   avs_net_socket_opt_value_t *out_option_value) {\n        net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n        switch (option_key) {\n        case AVS_NET_SOCKET_OPT_RECV_TIMEOUT:\n            out_option_value->recv_timeout = sock->recv_timeout;\n            return AVS_OK;\n        case AVS_NET_SOCKET_OPT_STATE:\n            if (sock->fd < 0) {\n                out_option_value->state = AVS_NET_SOCKET_STATE_CLOSED;\n            } else if (sock->shut_down) {\n                out_option_value->state = AVS_NET_SOCKET_STATE_SHUTDOWN;\n            } else {\n                sockaddr_union_t addr;\n                if (!getpeername(sock->fd, &addr.addr,\n                                 &(socklen_t) { sizeof(addr) })\n                        && ((addr.in.sin_family == AF_INET && addr.in.sin_port != 0)\n                            || (addr.in6.sin6_family == AF_INET6\n                                && addr.in6.sin6_port != 0))) {\n                    out_option_value->state = AVS_NET_SOCKET_STATE_CONNECTED;\n                } else {\n                    out_option_value->state = AVS_NET_SOCKET_STATE_BOUND;\n                }\n            }\n            return AVS_OK;\n        case AVS_NET_SOCKET_OPT_INNER_MTU:\n            out_option_value->mtu = 1464;\n            return AVS_OK;\n        case AVS_NET_SOCKET_HAS_BUFFERED_DATA:\n            out_option_value->flag = false;\n            return AVS_OK;\n        case AVS_NET_SOCKET_OPT_BYTES_SENT:\n            out_option_value->bytes_sent = sock->bytes_sent;\n            return AVS_OK;\n        case AVS_NET_SOCKET_OPT_BYTES_RECEIVED:\n            out_option_value->bytes_received = sock->bytes_received;\n            return AVS_OK;\n        default:\n            return avs_errno(AVS_ENOTSUP);\n        }\n    }\n"
  },
  {
    "path": "doc/sphinx/source/PortingGuideForNonPOSIXPlatforms/NetworkingAPI/NetworkingAPI1.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\n:orphan:\n\n.. meta::\n\n    :http-equiv=Refresh: 1; url=NetworkingAPI-Minimal.html\n\n.. title:: Redirection\n\n↳ :doc:`NetworkingAPI-Minimal`\n==============================\n"
  },
  {
    "path": "doc/sphinx/source/PortingGuideForNonPOSIXPlatforms/NetworkingAPI/NetworkingAPI2.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\n:orphan:\n\n.. meta::\n\n    :http-equiv=Refresh: 1; url=NetworkingAPI-RemoteHostPort.html\n\n.. title:: Redirection\n\n↳ :doc:`NetworkingAPI-RemoteHostPort`\n=====================================\n"
  },
  {
    "path": "doc/sphinx/source/PortingGuideForNonPOSIXPlatforms/NetworkingAPI/NetworkingAPI3.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\n:orphan:\n\n.. meta::\n\n    :http-equiv=Refresh: 1; url=NetworkingAPI-Bind.html\n\n.. title:: Redirection\n\n↳ :doc:`NetworkingAPI-Bind`\n===========================\n"
  },
  {
    "path": "doc/sphinx/source/PortingGuideForNonPOSIXPlatforms/NetworkingAPI/NetworkingAPI4.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\n:orphan:\n\n.. meta::\n\n    :http-equiv=Refresh: 1; url=NetworkingAPI-ShutdownRemoteHostname.html\n\n.. title:: Redirection\n\n↳ :doc:`NetworkingAPI-ShutdownRemoteHostname`\n=============================================\n"
  },
  {
    "path": "doc/sphinx/source/PortingGuideForNonPOSIXPlatforms/NetworkingAPI/NetworkingAPI5.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\n:orphan:\n\n.. meta::\n\n    :http-equiv=Refresh: 1; url=NetworkingAPI-Stats.html\n\n.. title:: Redirection\n\n↳ :doc:`NetworkingAPI-Stats`\n============================\n"
  },
  {
    "path": "doc/sphinx/source/PortingGuideForNonPOSIXPlatforms/NetworkingAPI/NetworkingAPI6.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\n:orphan:\n\n.. meta::\n\n    :http-equiv=Refresh: 1; url=NetworkingAPI-IpStickiness.html\n\n.. title:: Redirection\n\n↳ :doc:`NetworkingAPI-IpStickiness`\n===================================\n"
  },
  {
    "path": "doc/sphinx/source/PortingGuideForNonPOSIXPlatforms/NetworkingAPI/NetworkingAPI7.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\n:orphan:\n\n.. meta::\n\n    :http-equiv=Refresh: 1; url=NetworkingAPI-OtherFeatures.html\n\n.. title:: Redirection\n\n↳ :doc:`NetworkingAPI-OtherFeatures`\n====================================\n"
  },
  {
    "path": "doc/sphinx/source/PortingGuideForNonPOSIXPlatforms/NetworkingAPI.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nNetworking API\n==============\n\n.. highlight:: c\n\nReference implementations\n-------------------------\n\n``avs_net`` includes a full-featured, complete implementation of its networking\nAPI that is designed to work on systems that implement BSD-style socket API\n(either directly, or some close variant of it, such as lwIP or Winsock). It can\nbe found in the `src/net/compat/posix\n<https://github.com/AVSystem/avs_commons/tree/master/src/net/compat/posix>`_\ndirectory of its repository.\n\nHowever, that implementation is very complex, as it includes a lot of\nfunctionality that is not strictly necessary for Anjay to work (and some that is\nnot used by Anjay at all), alternate variants of code for compatibility with\ndifferent systems, extensive error handling etc.\n\nFor this reason, we also include tutorial code with minimal, compact\nimplementation of the networking API:\n\n.. toctree::\n   :glob:\n   :titlesonly:\n\n   NetworkingAPI/NetworkingAPI-Minimal\n   NetworkingAPI/NetworkingAPI-RemoteHostPort\n   NetworkingAPI/NetworkingAPI-Bind\n   NetworkingAPI/NetworkingAPI-ShutdownRemoteHostname\n   NetworkingAPI/NetworkingAPI-Stats\n   NetworkingAPI/NetworkingAPI-IpStickiness\n   NetworkingAPI/NetworkingAPI-EventLoopSupport\n   NetworkingAPI/NetworkingAPI-OtherFeatures\n\n\nList of functions to implement\n------------------------------\n\n.. note::\n\n    If LwIP 2.0 is used as a network stack, you may set:\n\n     - ``-DWITH_POSIX_AVS_SOCKET=ON``\n     - ``-DWITH_IPV6=OFF``\n     - ``-DPOSIX_COMPAT_HEADER=deps/avs_commons/compat/lwip-posix-compat.h``\n\n    CMake options for an out-of-the-box socket compatibility layer implementation.\n\nIf POSIX socket API is not available:\n\n- Use ``WITH_POSIX_AVS_SOCKET=OFF`` when running CMake on Anjay,\n- Provide an implementation for:\n\n  - ``_avs_net_create_udp_socket`` - a function with following signature:\n\n    .. snippet-source:: deps/avs_commons/src/net/avs_net_impl.h\n\n        avs_error_t _avs_net_create_udp_socket(avs_net_socket_t **socket,\n                                               const void *socket_configuration);\n\n    ``socket_configuration`` argument is a pointer to\n    ``const avs_net_socket_configuration_t`` struct cast to ``void *``.\n\n    The function should return ``AVS_OK`` on success and an error code on error.\n    It should create a socket object, and return its pointer cast to\n    ``avs_net_socket_t *`` through the ``*socket`` argument. The socket object\n    should be a struct, whose first field is ``avs_net_socket_v_table_t *``\n    filled with pointers to method handlers.\n\n    Minimal set of socket methods that have to be implemented:\n\n    - ``cleanup``\n    - ``close``\n    - ``connect``\n    - ``send``\n    - ``receive``\n    - ``get_system_socket``\n    - ``get_opt`` able to read following options:\n\n      - ``AVS_NET_SOCKET_OPT_STATE``\n      - ``AVS_NET_SOCKET_OPT_INNER_MTU``\n      - ``AVS_NET_SOCKET_OPT_RECV_TIMEOUT``\n    - ``set_opt`` able to set the ``AVS_NET_SOCKET_OPT_RECV_TIMEOUT`` option\n\n    Additional functions that are not strictly necessary to run Anjay, but are\n    used by some of the optional functionality\n\n    - ``bind`` - allows binding to a specific statically configured port; also\n      used to keep the bound port stable if possible\n    - ``get_local_port`` - used to keep the bound port stable if possible\n    - ``get_remote_host`` - required for CoAP message cache to work\n    - ``get_remote_port`` - required for CoAP message cache to work\n    - ``shutdown`` - required for ability to suspend CoAP downloads\n\n  - ``_avs_net_create_tcp_socket`` - only required if the ``fw_update`` module\n    should support HTTP/HTTPS transfers, or if support for CoAP over TCP is\n    desired. Otherwise, it can be safely implemented as\n    ``return avs_errno(AVS_ENOTSUP);``.\n\n    Function signature:\n\n    .. snippet-source:: deps/avs_commons/src/net/avs_net_impl.h\n\n        avs_error_t _avs_net_create_tcp_socket(avs_net_socket_t **socket,\n                                               const void *socket_configuration);\n\n    ``socket_configuration`` argument is a pointer to\n    ``const avs_net_socket_configuration_t`` struct cast to ``void *``.\n\n    The function should return ``AVS_OK`` on success and an error code on error.\n    It should create a socket object, and return its pointer cast to\n    ``avs_net_socket_t *`` through the ``*socket`` argument. The socket object\n    should be a struct, whose first field is ``avs_net_socket_v_table_t *``\n    filled with pointers to method handlers.\n\n    The same set of socket methods is required as is the case with UDP.\n\n  - ``_avs_net_initialize_global_compat_state`` - a function with following\n    signature:\n\n    .. snippet-source:: deps/avs_commons/src/net/avs_net_global.h\n\n        avs_error_t _avs_net_initialize_global_compat_state(void);\n\n    The function should return ``AVS_OK`` on success and an error code on error.\n    It should initialize any global state that needs to be kept by the network\n    stack. If there is no such global state or it is initialized elsewhere, it\n    is safe to implement this function as a no-op (``return AVS_OK;``).\n\n  - ``_avs_net_cleanup_global_compat_state`` - a function with following\n    signature:\n\n    .. snippet-source:: deps/avs_commons/src/net/avs_net_global.h\n\n        void _avs_net_cleanup_global_compat_state(void);\n\n    The function should clean up any global state that is kept by the network\n    stack. If there is no such global state or it is managed elsewhere, it is\n    safe to implement this function as a no-op.\n\n  - ``avs_net_resolved_endpoint_get_host_port`` - a function declared in\n    `avs_addrinfo.h <https://github.com/AVSystem/avs_commons/blob/master/include_public/avsystem/commons/avs_addrinfo.h>`_\n    with the following signature:\n\n    .. snippet-source:: deps/avs_commons/include_public/avsystem/commons/avs_addrinfo.h\n\n        avs_error_t\n        avs_net_resolved_endpoint_get_host_port(const avs_net_resolved_endpoint_t *endp,\n                                                char *host,\n                                                size_t hostlen,\n                                                char *serv,\n                                                size_t servlen);\n\n    This function is used by the procedure that keeps the remote IP address\n    stable when the connection URL uses a domain name as the host identifier.\n\n    This functionality can be disabled at compile time by enabling the\n    ``WITHOUT_IP_STICKINESS`` CMake option (``-DWITHOUT_IP_STICKINESS=OFF``),\n    in which case the library will no longer depend on this function.\n\n.. warning::\n    Anjay may attempt to call socket methods other than listed above, even\n    though they are not essential for correct operation of the application.\n    Make sure that all members of ``avs_net_socket_v_table_t`` are not NULL\n    - if required, provide a stub that always fails.\n\n.. note::\n    For signatures and detailed description of listed methods, see\n    `avs_net.h <https://github.com/AVSystem/avs_commons/blob/master/include_public/avsystem/commons/avs_net.h>`_\n"
  },
  {
    "path": "doc/sphinx/source/PortingGuideForNonPOSIXPlatforms/ThreadingAPI.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nThreading API\n=============\n\nOverview\n--------\n\nIf Anjay is compiled with thread safety enabled (``WITH_THREAD_SAFETY`` CMake\noption), it requires some basic threading primitives to be implemented for the\nthread safety features to work properly.\n\nThe same threading primitives are additionally used by the ``avs_net`` and\n``avs_log`` modules regardless of whether thread safety is enabled or not.\n\nThe specific requirements are:\n\n- ``avs_net`` requires ``avs_init_once()``,\n- ``avs_log`` and Anjay thread safety require ``avs_mutex_create()``,\n  ``avs_mutex_cleanup()``, ``avs_mutex_lock()``, ``avs_mutex_unlock()``, and\n  ``avs_init_once()``.\n\nIn addition, ``avs_sched`` optionally depends on ``avs_condvar_create()``,\n``avs_condvar_cleanup()``, ``avs_condvar_notify_all()`` as well as\n``avs_mutex_*`` APIs. The dependency can be controlled with\n``WITH_SCHEDULER_THREAD_SAFE`` CMake option. This option normally has the same\nsetting as ``WITH_THREAD_SAFETY``.\n\nThere are two independent implementations of the threading API for compatibility\nwith most platforms:\n\n- `based on pthreads\n  <https://github.com/AVSystem/avs_commons/tree/master/src/compat/threading/pthread>`_,\n- `based on C11 atomic operations\n  <https://github.com/AVSystem/avs_commons/tree/master/src/compat/threading/atomic_spinlock>`_.\n\n.. note::\n\n    You may use either of the implementations listed above as a reference for\n    writing your own if necessary.\n\nList of functions to implement\n------------------------------\n\nIf, for some reason neither of the default implementations is suitable:\n\n- Use ``WITH_CUSTOM_AVS_THREADING=ON`` when running CMake on Anjay,\n- Provide an implementation of:\n\n  - ``avs_mutex_create()``,\n  - ``avs_mutex_cleanup()``,\n  - ``avs_init_once()``,\n  - ``avs_mutex_lock()``,\n  - ``avs_mutex_unlock()``.\n\n- And if you use thread-safe scheduler, also provide implementation for:\n\n  - ``avs_condvar_create()``,\n  - ``avs_condvar_cleanup()``,\n  - ``avs_condvar_notify_all()``.\n\n.. note::\n    For signatures and detailed description of listed functions, see\n\n    - `avs_mutex.h <https://github.com/AVSystem/avs_commons/blob/master/include_public/avsystem/commons/avs_mutex.h>`_\n    - `avs_init_once.h <https://github.com/AVSystem/avs_commons/blob/master/include_public/avsystem/commons/avs_init_once.h>`_\n    - `avs_condvar.h <https://github.com/AVSystem/avs_commons/blob/master/include_public/avsystem/commons/avs_condvar.h>`_\n\n.. note::\n\n    If you intend to operate the library in a single-threaded fashion, you may\n    provide no-op stubs (returning success) of all mentioned primitives.\n"
  },
  {
    "path": "doc/sphinx/source/PortingGuideForNonPOSIXPlatforms/TimeAPI.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nTime API\n========\n\nList of functions to implement\n------------------------------\n\nIf POSIX ``clock_gettime`` function is not available:\n\n- Use ``WITH_POSIX_AVS_TIME=OFF`` when running CMake on Anjay,\n- Provide an implementation for:\n\n  - ``avs_time_real_now``\n  - ``avs_time_monotonic_now``\n\n.. note::\n    For signatures and detailed description of listed functions, see\n    `avs_time.h <https://github.com/AVSystem/avs_commons/blob/master/include_public/avsystem/commons/avs_time.h>`_\n\nReference implementation\n------------------------\n\nThe default\n`avs_compat_time.c <https://github.com/AVSystem/avs_commons/blob/master/src/utils/compat/posix/avs_compat_time.c>`_\nimplementation that uses POSIX ``clock_gettime()`` API can be used as a\nreference for writing your own integration layer.\n\n.. _timeapi_avs_time_real_now:\n\navs_time_real_now()\n^^^^^^^^^^^^^^^^^^^\n\nThe ``avs_time_real_now()`` function should return the current *real* time,\ni.e. the amount of time that passed since January 1st, 1970, midnight UTC (the\nUnix epoch).\n\nIn the reference POSIX-based implementation, it is a simple wrapper for the\n``CLOCK_REALITME`` clock.\n\n.. highlight:: c\n.. snippet-source:: deps/avs_commons/src/utils/compat/posix/avs_compat_time.c\n\n    avs_time_real_t avs_time_real_now(void) {\n        struct timespec system_value;\n        avs_time_real_t result;\n        clock_gettime(CLOCK_REALTIME, &system_value);\n        result.since_real_epoch.seconds = system_value.tv_sec;\n        result.since_real_epoch.nanoseconds = (int32_t) system_value.tv_nsec;\n        return result;\n    }\n\navs_time_monotonic_now()\n^^^^^^^^^^^^^^^^^^^^^^^^\n\nThe ``avs_time_monotonic_now()`` function should return the current *monotonic*\ntime, i.e. the amount of time that passed since *some epoch* - it might be\nany point in time, but needs to be stable at least throughout the lifetime of\nthe process - different epochs might be used for different launches of the\napplication.\n\nSystem boot time is often used as an epoch for the monotonic clock.\n\nIf the real-time clock is considered stable, and not reset while the application\nis running, it may be also used as the monotonic clock.\n\nThis is used in the reference implementation - it is generally a wrapper for the\n``CLOCK_MONOTONIC`` clock, but on some platforms it is not available -\n``CLOCK_REALTIME`` is used in these cases.\n\n.. highlight:: c\n.. snippet-source:: deps/avs_commons/src/utils/compat/posix/avs_compat_time.c\n\n    avs_time_monotonic_t avs_time_monotonic_now(void) {\n        struct timespec system_value;\n        avs_time_monotonic_t result;\n    #    ifdef CLOCK_MONOTONIC\n        if (clock_gettime(CLOCK_MONOTONIC, &system_value))\n    #    endif\n        {\n            // CLOCK_MONOTONIC is not mandatory in POSIX;\n            // fallback to REALTIME if we don't have it\n            clock_gettime(CLOCK_REALTIME, &system_value);\n        }\n        result.since_monotonic_epoch.seconds = system_value.tv_sec;\n        result.since_monotonic_epoch.nanoseconds = (int32_t) system_value.tv_nsec;\n        return result;\n    }\n"
  },
  {
    "path": "doc/sphinx/source/PortingGuideForNonPOSIXPlatforms.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nPorting guide for non-POSIX platforms\n=====================================\n\nBy default, Anjay makes use of POSIX-specific interfaces for retrieving time\nand handling network traffic. If no such interfaces are provided by the\ntoolchain, the user needs to provide custom implementations.\n\nThe articles below show additional information about the specific functions that\nneed to be implemented.\n\n.. toctree::\n   :titlesonly:\n\n   PortingGuideForNonPOSIXPlatforms/TimeAPI\n   PortingGuideForNonPOSIXPlatforms/ThreadingAPI\n   PortingGuideForNonPOSIXPlatforms/NetworkingAPI\n   PortingGuideForNonPOSIXPlatforms/CustomTLS\n"
  },
  {
    "path": "doc/sphinx/source/Tools/CliLwM2MServer.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nLwM2M testing shell\n-------------------\n\nFor the purpose of early testing of Anjay-based clients, we provide a simple CLI implementation of\nLwM2M. It is written in Python using `powercmd` library. You can find it in\n`tools/test-framework-tools/nsh-lwm2m <https://github.com/AVSystem/Anjay/tree/master/tools/test-framework-tools/nsh-lwm2m>`_\ndirectory in the Anjay repository.\n\n.. note::\n\n    It is recommended to use :doc:`VirtualEnvironments` when running Python\n    scripts.\n\nRunning the server\n~~~~~~~~~~~~~~~~~~\n\nYou can start the server (from the main Anjay directory) by running `./tools/test-framework-tools/nsh-lwm2m/nsh_lwm2m.py`\nwith the following optional arguments:\n\n--help, -h            Show help.\n--ipv6, -6            Use IPv6 by default.\n--listen PORT, -l PORT\n                      Immediately starts listening on specified CoAP port. Default UDP, can be TCP if --tcp is passed. If `PORT` is not specified, default one is used (5683 for CoAP, 5684 for CoAP/(D)TLS)\n--tcp, -t\n                      Listen on TCP port\n--psk-identity IDENTITY, -i IDENTITY\n                      PSK identity to use for DTLS connection (literal string).\n--psk-key KEY, -k KEY\n                      PSK key to use for DTLS connection (literal string).\n--debug               Enable mbed TLS debug output.\n\nSupported commands\n~~~~~~~~~~~~~~~~~~\n\nIn this section we present the commands supported by the shell, with a bunch of examples.\nThe initial setup for most of the provided examples can be obtained by two commands, both runned from the main Anjay directory,\nfirst for setting up the server:\n\n.. code-block:: bash\n\n   ./tools/test-framework-tools/nsh-lwm2m/nsh_lwm2m.py -l 9000\n\nand the second for setting up the client:\n\n.. code-block:: bash\n\n   ./output/bin/demo -e my_endpoint -u coap://127.0.0.1:9000\n\n\nHandling messages\n^^^^^^^^^^^^^^^^^\n\nThe most important group of commands are those for sending messages. Most of them just send the corresponding LwM2M message:\n\nbootstrap_finish ``MSG_ID`` ``TOKEN`` ``OPTIONS`` ``CONTENT``\n   ..\nchanged ``MSG_ID`` ``TOKEN`` ``LOCATION`` ``OPTIONS`` ``CONTENT``\n   ..\ncoap_get ``PATH`` ``ACCEPT`` ``MSG_ID`` ``TOKEN`` ``LOCATION``\n   ..\ncontent  ``MSG_ID`` ``TOKEN`` ``CONTENT`` ``FORMAT`` ``TYPE`` ``OPTIONS``\n   ..\ncontinue ``MSG_ID`` ``TOKEN`` ``TYPE`` ``OPTIONS``\n   ..\ncreate ``PATH`` ``CONTENT`` ``MSG_ID`` ``TOKEN`` ``OPTIONS`` ``FORMAT``\n   ..\ncreated ``MSG_ID`` ``TOKEN`` ``LOCATION`` ``OPTIONS``\n   ..\ndelete ``PATH`` ``MSG_ID`` ``TOKEN`` ``OPTIONS``\n   ..\ndeleted ``MSG_ID`` ``TOKEN`` ``LOCATION`` ``OPTIONS``\n   ..\nderegister ``PATH`` ``MSG_ID`` ``TOKEN`` ``OPTIONS``\n   ..\ndiscover ``PATH`` ``MSG_ID`` ``TOKEN`` ``OPTIONS``\n   ..\nempty\n   ..\nerror_response ``CODE`` ``MSG_ID`` ``TOKEN`` ``OPTIONS``\n   ..\nest_coaps_simple_enroll ``MSG_ID`` ``TOKEN`` ``URI_PATH`` ``URI_QUERY`` ``OPTIONS`` ``CONTENT``\n   ..\nexecute ``PATH`` ``CONTENT`` ``MSG_ID`` ``TOKEN`` ``OPTIONS``\n   ..\nnotify ``TOKEN`` ``CONTENT`` ``FORMAT`` ``CONFIRMABLE`` ``OPTIONS``\n   ..\nobserve ``MSG_ID`` ``TOKEN`` ``OPTIONS``\n   ..\nobserve_composite ``PATHS`` ``OBSERVE`` ``ACCEPT`` ``MSG_ID`` ``TOKEN`` ``OPTIONS``\n   ..\nread ``PATH`` ``ACCEPT`` ``MSG_ID`` ``TOKEN`` ``OPTIONS``\n   ..\nread_composite ``PATHS`` ``ACCEPT`` ``MSG_ID`` ``TOKEN`` ``OPTIONS``\n   ..\nrequest_bootstrap ``ENDPOINT_NAME`` ``PREFERRED_CONTENT_FORMAT`` ``MSG_ID`` ``TOKEN`` ``URI_PATH`` ``URI_QUERY`` ``OPTIONS`` ``CONTENT``\n   ..\nreset ``MSG_ID``\n   ..\nsend ``PATH`` ``MSG_ID`` ``TOKEN`` ``FORMAT`` ``OPTIONS`` ``CONTENT``\n   ..\nwrite ``PATH`` ``CONTENT`` ``FORMAT`` ``UPDATE`` ``MSG_ID`` ``TOKEN`` ``OPTIONS``\n   ..\nwrite_attributes ``PATH`` ``LT`` ``GT`` ``ST`` ``PMIN`` ``PMAX`` ``EPMIN`` ``EPMAX`` ``QUERY`` ``MSG_ID`` ``TOKEN`` ``OPTIONS``\n   ..\nwrite_composite ``CONTENT`` ``FORMAT`` ``MSG_ID`` ``TOKEN`` ``OPTIONS``\n   ..\n\nBecause sometimes it might be necessary to use Write message with a large block of data (e.g. when performing a firmware update)\nit may be handy to load the data from a file instead of writing it by hand.\nNsh supports an additional command for this case:\n\nwrite_file ``FNAME`` ``PATH`` ``FORMAT`` ``CHUNKSIZE`` ``TIMEOUT_S``\n   Opens file ``FNAME`` and attempts to push it using BLOCK1 to the Client.\n\nIt is also possible to send a custom CoAP message/UDP datagram using Nsh:\n\ncoap ``TYPE`` ``CODE`` ``MSG_ID`` ``TOKEN`` ``OPTIONS`` ``CONTENT`` ``RESPOND``\n   Send a custom CoAP message.\nudp ``CONTENT``\n   Send a custom UDP datagram.\n\nAnd finally, there is a command waiting for a message sent by the client:\n\nrecv ``TIMEOUT_S``\n   Waits for a next incoming message. If ``TIMEOUT_S`` is specified, the\n   command will not wait longer than ``TIMEOUT_S`` if no messages are received.\n\nNsh provide also one more feature for handling messages.\nWhen the user enters an empty line while the flags ``AUTO_UPDATE``, ``AUTO_REREGISTER`` or ``AUTO_ACK``\n(see **set** command description)\nare set, the server tries to handle the corresponding messages from the client,\nresponding them in a proper way. For example, when client sends notify\nthe result of entering an empty line on Nsh side should be:\n\n.. code-block:: text\n\n   [Lwm2mCmd] port: 9000, client: 127.0.0.1:47748 $\n   <- Register /rd?lwm2m=1.1&ep=my_endpoint&lt=86400: </1/1>,</2>,</3/0>,</4/0>,<...\n   -> Created /rd/demo\n   [Lwm2mCmd] port: 9000, client: 127.0.0.1:47748 $\n\n.. note::\n\n   Usually there is no need for passing all of the command arguments. To see which are optional\n   you can use **help** for the considered command. In the output they are printed with ``?`` signs.\n\nWorking with payloads\n^^^^^^^^^^^^^^^^^^^^^\n\nIntroduction\n\"\"\"\"\"\"\"\"\"\"\"\"\n\nWhen a binary payload contains a non-printable character, it is\nimpossible to encode it as a plain text. To overcome this inconvenience the shell introduces a special\ntype: ``EscapedBytes``, in which you can hex-encode some of the bytes (in many cases it might be quite handy to\nhex-encode just all of them): after ``\\x`` the following two characters are interpreted as hex digits encoding\none byte. Examples of the binary payloads encoded in such way can be found below, while discussing subshells.\n\nPreparing or reading data in such format may be quite painful so Nsh has tools to make it more comfortable.\nTo build TLV or CBOR payloads (which are binary formats), nsh exposes subshells.\nEach of them has its own set of commands, however, some of them are common.\n**help**, **get_error** and **exit** behave in a similar way to those known from the main shell.\nOther commands common for the subshells are:\n\nserialize\n   Displays the prepared structure as an encoded hex-escaped string (ready to use as EscapedBytes).\nshow\n   Displays current element structure in a human-readable form.\n\nCBOR subshell\n\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nThis subshell is entered by **cbor** command. The only extra command supported is:\n\nadd_resource ``BASENAME`` ``NAME`` ``TYPE`` ``VALUE``\n   Adds the next entry to the existing CBOR data. ``BASENAME`` argument is optional and it can contain the parent path. In ``NAME``\n   a path to some value-containing Resource/Resource Instance is kept.\n\nTLV subshell\n\"\"\"\"\"\"\"\"\"\"\"\"\n\nTLV subshell is entered by **tlv** command. It supports a few commands more:\n\nadd_instance ``ID``\n   Creates an object instance with a given ``ID``. It must be created as a top-level element.\nadd_multiple_resource ``ID``\n   Creates a Multiple Resource under the currently selected Object Instance (as a top-level element, if none is selected).\nadd_resource ``ID`` ``VALUE`` ``TYPE``\n   Creates a Resource with a given ``ID`` under the currently selected Object Instance. If there is none, it is created as a top-level element.\nadd_resource_instance ``ID`` ``VALUE`` ``TYPE``\n   Creates a Resource Instance of the currently selected Multiple Resource.\ndeserialize ``DATA``\n   Loads a TLV-encoded element structure for further processing. It is helpful, when we recieve data from *read* request from the client.\nmake_multires ``(RIID,VALUE),...``\n   Builds Multiple Resource Instances from the list of pairs of ``RIID`` and ``VALUE`` (of type ``EscapedBytes``).\n   The pairs need to be comma separated and no spaces are allowed.\n\n   For example ``(1,\\x04),(5,\\x02)`` represents two object instances, first with ID 1 and value 4 and second with ID 5 and value 2.\nremove ``PATH``\n   Removes an element to which the path points. The path consists of 1 - 3 integers, separated by ``/`` character.\nselect ``PATH``\n   Selects an Object Instance or Multiple Resource that further add_* calls will add elements into.\n\n\nUsing subshells example\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nLet's suppose that we would like to encode some simple data as both CBOR ans TLV, let its structure be:\n\n.. code-block:: text\n\n   /0 (Instance)\n     -> /0 (Multiple Resource)\n       -> 0 = 2 (Resource Instance)\n       -> 1 = 5 (Resource Instance)\n   /1 (Instance)\n     -> /1 = 11 (Resource)\n     -> /3 = 1 (Resource)\n\nTo encode it as TLV, we need to enter the following commands:\n\n.. code-block:: text\n\n   add_instance 0\n   add_multiple_resource 0\n   make_multires (0,\\x02),(1,\\x05)\n   add_instance 1\n   add_resource 1 type=int 11\n   add_resource 3 type=int 1\n\nAfter running these commands, the TLV data are ready, and you can see the result in human-readable form using **show** command:\n\n.. code-block:: text\n\n   [Lwm2mCmd/TLV] port: 9000, client: 127.0.0.1:47748 $ show\n   * exact: show\n     path    value\n   ---------------\n     0       instance (1 resources)\n     0/0       multiple resource (2 instances)\n     0/0/0       resource instance = b'\\x02' (int: 2)\n     0/0/1       resource instance = b'\\x05' (int: 5)\n   * 1       instance (2 resources)\n     1/1       resource = b'\\x0b' (int: 11)\n     1/3       resource = b'\\x01' (int: 1)\n\n\nand when we escape the subshell with **exit** command, we will recieve\nthe created data in form of `EscapedBytes`:\n\n.. code-block:: text\n\n   [Lwm2mCmd/TLV] port: 9000, client: 127.0.0.1:47748 $ exit\n   * exact: exit\n   exiting\n   \\x08\\x00\\x08\\x86\\x00\\x41\\x00\\x02\\x41\\x01\\x05\\x06\\x01\\xc1\\x01\\x0b\\xc1\\x03\\x01\n\nIn CBOR the number of commands will be smaller, as we run them only for leaves:\n\n.. code-block:: text\n\n   add_resource 0/0/0 int 2\n   add_resource 0/0/1 int 5\n   add_resource 1/1 int 11\n   add_resource 1/3 int 1\n\nwhich gives us the following CBOR data:\n\n.. code-block:: text\n\n   [Lwm2mCmd/CBOR] port: 9000, client: 127.0.0.1:47748 $ show\n   * exact: show\n   CBOR (4 elements):\n\n     {<SenmlLabel.NAME: 0>: '0/0/0', <SenmlLabel.VALUE: 2>: 2}\n     {<SenmlLabel.NAME: 0>: '0/0/1', <SenmlLabel.VALUE: 2>: 5}\n     {<SenmlLabel.NAME: 0>: '1/1', <SenmlLabel.VALUE: 2>: 11}\n     {<SenmlLabel.NAME: 0>: '1/3', <SenmlLabel.VALUE: 2>: 1}\n\nand, in the same way as in the case of the TLV subshell, we escape\nthe shell and recieve the encoded data:\n\n.. code-block:: text\n\n   [Lwm2mCmd/CBOR] port: 9000, client: 127.0.0.1:47748 $ exit\n   * exact: exit\n   exiting\n   \\x84\\xa2\\x00\\x65\\x30\\x2f\\x30\\x2f\\x30\\x02\\x02\\xa2\\x00\\x65\\x30\\x2f\\x30\\x2f\\x31\\x02\\x05\\xa2\\x00\\x63\\x31\\x2f\\x31\\x02\\x0b\\xa2\\x00\\x63\\x31\\x2f\\x33\\x02\\x01\n\nDecoding messages\n^^^^^^^^^^^^^^^^^\n\nNsh supports two commands which are connected to both previously discussed topics - tools for decoding CoAP/LwM2M messages:\n\ncoap_decode ``DATA``\n   Decodes a CoAP message and displays it in a human-readable form.\nlwm2m_decode ``DATA``\n   Decodes a LwM2M message and displays it in a human-readable form.\n\nFor example, we can decode an empty coap message (with *EscapedBytes* representation ``\\x60\\x00\\x13\\x38``):\n\n.. code-block:: text\n\n   [Lwm2mCmd] port: 9000, client: 127.0.0.1:47748 $ coap_decode \\x60\\x00\\x13\\x38\n   * exact: coap_decode\n   version: 1\n   type: ACKNOWLEDGEMENT\n   code: 0.00 (EMPTY)\n   msg_id: 4920\n   token:  (length: 0)\n   options:\n\n   content: 0 bytes\n\nInspecting previous messages\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nNsh supports also a bunch of tools for inspecting the results of the previous commands.\n\nMessage history\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nThe first such tool is the *message history* which can be handled using two commands:\n\ndetails ``N``\n   Displays details of a ``N``-th last message, or the last message, if ``N`` is not given.\nreset_history\n   Clears command history.\n\nTo see how they work, let's send a few messages, e.g.:\n\n.. code-block:: text\n\n   read /1/1/3/1\n   empty\n   reset\n\nNow, we can check N-th message, sent or received, by running ``details N``\n(important note: the last message has N=1). For example, in such case running ``details 4`` would return:\n\n.. code-block:: text\n\n   [Lwm2mCmd] port: 9000, client: 127.0.0.1:47748 $ details 4\n   * exact: details\n\n   *** Send ***\n   Read /1/1/3/1\n\n   version: 1\n   type: CONFIRMABLE\n   code: 0.01 (REQ_GET)\n   msg_id: 4920\n   token: NbwK\\x18W\\xc7\\xcb (length: 8)\n   options:\n      option 11 (URI_PATH), content (1 bytes): 1\n      option 11 (URI_PATH), content (1 bytes): 1\n      option 11 (URI_PATH), content (1 bytes): 3\n      option 11 (URI_PATH), content (1 bytes): 1\n   content: 0 bytes\n\n   ascii-ish:\n\nWe can use also run this command without parameters, to see the last message:\n\n.. code-block:: text\n\n   [Lwm2mCmd] port: 9000, client: 127.0.0.1:47748 $ details\n   * exact: details\n\n   *** Send ***\n   Reset, msg_id = 4922\n\n   version: 1\n   type: RESET\n   code: 0.00 (EMPTY)\n   msg_id: 4922\n   token:  (length: 0)\n   options:\n\n   content: 0 bytes\n\n   ascii-ish:\n\nAfter running the **reset_history** command, the history will be cleared and\n**details** (with any parameter) runned after that, returns only a warning ``message not found``.\n\nPayload buffer\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nAnother important tool is **payload buffer**.\nIt stores the contents of the messages received by the server and\ncan be accessed with a set of functions **payload_buffer_\\***:\n\npayload_buffer_clear\n   Clears payload buffer.\npayload_buffer_show\n   Shows the payload buffer content.\npayload_buffer_show_hex\n   Shows the payload buffer content presented as hex.\npayload_buffer_show_tlv\n   Shows the payload buffer content presented as tlv.\n\nLet's see an example. After reading an object instance (with some human readable format, e.g. *JSON*):\n\n.. code-block:: text\n\n   [Lwm2mCmd] port: 9000, client: 127.0.0.1:47748 $ read /1/1 APPLICATION_LWM2M_JSON\n   * exact: read\n   -> Read /1/1: accept APPLICATION_LWM2M_JSON\n   <- Content (11543 (APPLICATION_LWM2M_JSON); 193 bytes)\n\nthe content of the message can be printed data using **payload_buffer_show**. The result should be similar to:\n\n.. code-block:: text\n\n   [Lwm2mCmd] port: 9000, client: 127.0.0.1:47748 $ payload_buffer_show\n   * exact: payload_buffer_show\n   b'{\"bn\":\"/1/1\",\"e\":[{\"n\":\"/0\",\"v\":1},{\"n\":\"/1\",\"v\":86400},{\"n\":\"/6\",\"bv\":true},{\"n\":\"/7\",\"sv\":\"U\"},{\"n\":\"/17\",\"v\":1},\n   {\"n\":\"/18\",\"v\":0},{\"n\":\"/19\",\"v\":1},{\"n\":\"/20\",\"v\":0},{\"n\":\"/23\",\"bv\":false}]}'\n\nSometimes it is quite useful to represent the data as hex-encoded bytes, what can be obtained with **payload_buffer_show_hex**, which for the considered JSON data\nlooks like:\n\n.. code-block:: text\n\n   [Lwm2mCmd] port: 9000, client: 127.0.0.1:47748 $ payload_buffer_show_hex\n   * exact: payload_buffer_show_hex\n   \\x7b\\x22\\x62\\x6e\\x22\\x3a\\x22\\x2f\\x31\\x2f\\x31\\x22\\x2c\\x22\\x65\\x22\\x3a\\x5b\\x7b\\x22\\x6e\\x22\\x3a\\x22\\x2f\\x30\\x22\\x2c\\x22\n   \\x76\\x22\\x3a\\x31\\x7d\\x2c\\x7b\\x22\\x6e\\x22\\x3a\\x22\\x2f\\x31\\x22\\x2c\\x22\\x76\\x22\\x3a\\x38\\x36\\x34\\x30\\x30\\x7d\\x2c\\x7b\\x22\n   \\x6e\\x22\\x3a\\x22\\x2f\\x36\\x22\\x2c\\x22\\x62\\x76\\x22\\x3a\\x74\\x72\\x75\\x65\\x7d\\x2c\\x7b\\x22\\x6e\\x22\\x3a\\x22\\x2f\\x37\\x22\\x2c\n   \\x22\\x73\\x76\\x22\\x3a\\x22\\x55\\x22\\x7d\\x2c\\x7b\\x22\\x6e\\x22\\x3a\\x22\\x2f\\x31\\x37\\x22\\x2c\\x22\\x76\\x22\\x3a\\x31\\x7d\\x2c\\x7b\n   \\x22\\x6e\\x22\\x3a\\x22\\x2f\\x31\\x38\\x22\\x2c\\x22\\x76\\x22\\x3a\\x30\\x7d\\x2c\\x7b\\x22\\x6e\\x22\\x3a\\x22\\x2f\\x31\\x39\\x22\\x2c\\x22\n   \\x76\\x22\\x3a\\x31\\x7d\\x2c\\x7b\\x22\\x6e\\x22\\x3a\\x22\\x2f\\x32\\x30\\x22\\x2c\\x22\\x76\\x22\\x3a\\x30\\x7d\\x2c\\x7b\\x22\\x6e\\x22\\x3a\n   \\x22\\x2f\\x32\\x33\\x22\\x2c\\x22\\x62\\x76\\x22\\x3a\\x66\\x61\\x6c\\x73\\x65\\x7d\\x5d\\x7d\n\nTo use the function **payload_buffer_show_tlv** we need some data in TLV format, so with the current payload it prints only an error:\n\n.. code-block:: text\n\n   [Lwm2mCmd] port: 9000, client: 127.0.0.1:47748 $ payload_buffer_show_tlv\n   * exact: payload_buffer_show_tlv\n   attempted to take 7217722 bytes, but only 187 available (try \"get_error\" for details)\n\nMoreover, after reading the object instance with ``read /1/1 APPLICATION_LWM2M_TLV``, the result will be the same.\nThe reason of such behavior is that there is some data in payload which is not in TLV encoding.\nIn such case **payload_buffer_clear** is needed before:\n\n.. code-block:: text\n\n   payload_buffer_clear\n   read /1/1 APPLICATION_LWM2M_TLV\n   payload_buffer_show_tlv\n\nAnd finally some nice, human-readable TLV representation is printed:\n\n.. code-block:: text\n\n   [Lwm2mCmd] port: 9000, client: 127.0.0.1:47748 $ payload_buffer_show_tlv\n   * exact: payload_buffer_show_tlv\n   TLV (9 elements):\n\n     resource 0 = b'\\x01' (int: 1)\n     resource 1 = b'\\x00\\x01Q\\x80' (int: 86400, float: 0.000000)\n     resource 6 = b'\\x01' (int: 1)\n     resource 7 = b'U' (int: 85)\n     resource 17 = b'\\x01' (int: 1)\n     resource 18 = b'\\x00' (int: 0)\n     resource 19 = b'\\x01' (int: 1)\n     resource 20 = b'\\x00' (int: 0)\n     resource 23 = b'\\x00' (int: 0)\n\nChecking errors\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nWhen something was wrong with your last command Nsh will return an error.\nIt might be helpful to get some more details and for this purpose you can\nuse **get_error** command. To see how it works, let's try the following **read**:\n\n.. code-block:: text\n\n   [Lwm2mCmd] port: 9000, client: 127.0.0.1:47748 $ read 0/3\n   * exact: read\n   could not send Lwm2mRead (not a valid CoAP path: 0/3) (try \"get_error\" for details)\n\n\nSome error was returned, so\n**get_error** command can be used to see some details. A similar trace should be printed:\n\n.. code-block:: text\n\n   [Lwm2mCmd] port: 9000, client: 127.0.0.1:47748 $ get_error\n   * exact: get_error\n   Traceback (most recent call last):\n   File \"./bootstrap/framework/nsh-lwm2m/nsh_lwm2m.py\", line 862, in send_msg\n      self._send(cls(*args, **kwargs))\n   File \"/home/mziobro/anjay/bootstrap/framework/nsh-lwm2m/lwm2m/messages.py\", line 636, in __init__\n      path = Lwm2mNonemptyPath(path)\n   File \"/home/mziobro/anjay/bootstrap/framework/nsh-lwm2m/lwm2m/path.py\", line 62, in __init__\n      super().__init__(text)\n   File \"/home/mziobro/anjay/bootstrap/framework/nsh-lwm2m/lwm2m/path.py\", line 32, in __init__\n      super().__init__(text)\n   File \"/home/mziobro/anjay/bootstrap/framework/nsh-lwm2m/lwm2m/path.py\", line 13, in __init__\n      raise ValueError('not a valid CoAP path: %s' % (text,))\n   ValueError: not a valid CoAP path: 0/3\n\n   During handling of the above exception, another exception occurred:\n\n   Traceback (most recent call last):\n   File \"/home/mziobro/anjay/bootstrap/framework/nsh-lwm2m/powercmd/powercmd/cmd.py\", line 173, in default\n      return invoker.invoke(self, cmdline=CommandLine(cmdline))\n   File \"/home/mziobro/anjay/bootstrap/framework/nsh-lwm2m/powercmd/powercmd/command_invoker.py\", line 208, in invoke\n      return cmd.handler(*args, **typed_args)\n   File \"./bootstrap/framework/nsh-lwm2m/nsh_lwm2m.py\", line 864, in send_msg\n      raise e.__class__('could not send %s (%s)' % (cls.__name__, e))\n   ValueError: could not send Lwm2mRead (not a valid CoAP path: 0/3)\n\n\nAs we can see, the error was raised in line 13 of ``path.py``:\n\n.. code-block:: python\n\n   def __init__(self, text):\n      if not text.startswith('/'):\n         raise ValueError('not a valid CoAP path: %s' % (text,))\n\nNow the issue with the path is clear - it is not started with ``/`` character.\n\nDealing with connections\n^^^^^^^^^^^^^^^^^^^^^^^^\n\nTo this point we always used the same setting of the client and the server, with the server port\ngiven as a command line parameter. This approach is sufficient for most of cases, but Nsh supports\nthree commands for modifying the connection in runtime:\n\nconnect ``HOST`` ``PORT``\n   Connects the socket to given ``HOST:PORT``. Future packets will be sent to this address.\nlisten ``PORT`` ``PSK_IDENTITY`` ``PSK_KEY`` ``CA_PATH`` ``CA_FILE`` ``CRT_FILE`` ``KEY_FILE`` ``IPV6`` ``DEBUG`` ``CONNECTION_ID``\n   Starts listening on given ``PORT``. If any of ``PSK_IDENTITY``, ``PSK_KEY``, ``CA_PATH``, ``CA_FILE``, ``CRT_FILE`` or ``KEY_FILE`` are specified, sets up a DTLS server, otherwise - raw CoAP server.\nunconnect\n   \"Unconnects\" the socket from an already accepted client. The idea is that then the server will be able to receive packets from different (host, port), which may be useful for testing purposes.\n\n\nTesting\n^^^^^^^\n\nWe can use Nsh for running list of commands from a file, working as a kind of a primitive test case.\nThere are two commands which can be especially helpful in such situation:\n\nexpect ``MSG_CODE``\n   Makes the shell compare next received packet against the one configured\n   via this command and print a message if a mismatch is detected.\n\n   ``MSG_CODE`` can be:\n\n   - a string with Python code that evaluates to a correct message,\n\n   - None, if no messages are expected,\n\n   - ANY to disable checking (default).\n\n   Note: after receiving each message the \"expected\" value is set to ANY.\nsleep ``TIMEOUT_S``\n   Blocks for ``TIMEOUT_S`` seconds. Might be helpful when we want to be sure that the client have enough\n   time to make some action.\n\nDifferent kinds of servers\n^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nBesides a casual LwM2M server, Nsh can also serve in two different ways:\n\n 1. as a bootstrap LwM2M server,\n 2. for serving files over CoAP.\n\nThey are implemented with the following commands (respectively):\n\nbootstrap ``URI`` ``SECURITY_MODE`` ``PSK_IDENTITY`` ``PSK_KEY`` ``CLIENT_CERT_PATH`` ``CLIENT_PRIVATE_KEY_PATH`` ``SERVER_CERT_PATH`` ``SSID`` ``IS_BOOTSTRAP`` ``LIFETIME`` ``NOTIFICATION_STORING`` ``BINDING`` ``IID`` ``FINISH`` ``TLS_CIPHERSUITES``\n   Sets up a Security and Server instances for an LwM2M server.\n\n   In case of PreSharedKey security mode, ``PSK_IDENTITY`` and ``PSK_KEY``\n   are literal plain text sequences to be used as DTLS identity and secret key.\n\n   In case of Certificate security mode, ``CLIENT_CERT_PATH`` and\n   ``SERVER_CERT_PATH`` shall be paths to binary DER-encoded X.509\n   certificates, and ``CLIENT_PRIVATE_KEY_PATH`` to binary DER-encoded\n   PKCS#8 file, which MUST NOT be password-protected.\n\n   If ``IS_BOOTSTRAP`` is True, only the Security object instance is\n   configured. ``LIFETIME``, ``NOTIFICATION_STORING`` and ``BINDING`` are ignored\n   in such case. ``SSID`` is still set for the Security instance.\n\n   Both Security and Server object instances are created with given ``IID``.\n\n   If ``FINISH`` is set to True, a *Bootstrap Finish* message will be sent\n   after setting up Security/Server instances.\n\n\nbootstrap_pack ``URI`` ``SECURITY_MODE`` ``PSK_IDENTITY`` ``PSK_KEY`` ``CLIENT_CERT_PATH`` ``CLIENT_PRIVATE_KEY_PATH`` ``SERVER_CERT_PATH`` ``SSID`` ``IS_BOOTSTRAP`` ``LIFETIME`` ``NOTIFICATION_STORING`` ``BINDING`` ``IID`` ``TLS_CIPHERSUITES``\n   Responds to the cached BootstrapPackRequest with BootstrapPack with the content\n   created from the arguments in the same way as the content of bootstrap writes\n   send by the BOOTSTRAP command.\n\n   It can be used only with AUTO_BSPACK_ERROR unset, because without that\n   BootstrapPackRequest is automatically responded with an error message.\n\n   Available as part of LwM2M 1.2 commercial feature.\n\n\nfile_server ``ROOT_DIRECTORY`` ``PORT`` ``PSK_IDENTITY`` ``PSK_KEY`` ``CA_PATH`` ``CA_FILE`` ``CRT_FILE`` ``KEY_FILE`` ``IPV6`` ``DEBUG``\n   Serves files from ``ROOT_DIRECTORY`` over CoAP(s).\n\nAs they are the most complex commands, we provide examples for both of them:\n\nBootstrapping\n\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nTo show how we can use Nsh for bootstrapping, we set up the bootstrap server:\n\n.. code-block:: text\n\n   ./tools/test-framework-tools/nsh-lwm2m/nsh_lwm2m.py -l 9000\n\nand the second one (in some other terminal), this time on a different port and using some id and password\n(for the sake of simplicity the id=`user`and password=`password`):\n\n.. code-block:: text\n\n   ./tools/test-framework-tools/nsh-lwm2m/nsh_lwm2m.py -l 9500 --psk-identity user --psk-key password\n\nThen we run the client (important note: ``--bootstrap`` option is necessary):\n\n.. code-block:: text\n\n   ./output/bin/demo -e my_endpoint -u coap://127.0.0.1:9000 --bootstrap\n\nAt this point the client is connected to the first server and we need to provide it information sufficient\nfor connecting the second server:\n\n.. code-block:: text\n\n   [Lwm2mCmd] port: 9000, client: 127.0.0.1:41266 $ bootstrap finish=True ssid=1 uri=coaps://127.0.0.1:9500 security_m\n   ode=PreSharedKey psk_identity=user psk_key=password\n   * exact: bootstrap\n   -> Write /0: APPLICATION_LWM2M_TLV, 58 bytes\n   <- Changed (no location path)\n   -> Write /1: APPLICATION_LWM2M_TLV, 18 bytes\n   <- Changed (no location path)\n   -> Bootstrap Finish /bs:\n   <- Changed (no location path)\n\nNow the client is connected to the second server. As we can see in the bootstrap server log, it sent 3 messages to the client,\ntwo Writes to set the Server and Security objects and Bootstrap Finish in the end.\n\nServing files over CoAP\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nTo see how we can use Nsh for serving files, first start it without arguments:\n\n.. code-block:: text\n\n   ./tools/test-framework-tools/nsh-lwm2m/nsh_lwm2m.py\n\nand then start serving files from Anjay directory:\n\n.. code-block:: text\n\n   [Lwm2mCmd] $ file_server . 9000\n   * exact: file_server\n   Serving directory /home/mziobro/anjay on port 9000...\n   Press CTRL-C to stop\n\nCurrently we do not have to connect, so we can run the client with any URI starting with `coap://`\n\n.. code-block:: text\n\n   ./output/bin/demo -e my_endpoint -u coap://anything\n\nBecause the URI is invalid, we will recieve a few errors, but the client will run.\nNow, we use **download** command on the client side. Assuming that we are in the same (i.e. Anjay)\ndirectory, it will just copy one of the files (in this case, we download Makefile to Makefile_copy):\n\n.. code-block:: text\n\n   download coap://127.0.0.1:9000/Makefile Makefile_copy\n\nMiscellaneous\n^^^^^^^^^^^^^\n\nThere are a few commands, rather simple, which does not fit in any previous category:\n\nexit\n   Terminates the command loop. Equivalent to ``Ctrl+D``.\nhelp\n   Displays a description of given command or lists all available commands.\nset ``AUTO_UPDATE`` ``AUTO_REREGISTER`` ``AUTO_ACK`` ``AUTO_BSPACK_ERROR``\n   Sets in which situation server sends a message to a client automatically:\n\n   - ``AUTO_UPDATE`` - when LwM2M Update is received from the client,\n   - ``AUTO_REREGISTER`` - when LwM2M Register is received from the client,\n   - ``AUTO_ACK`` - after any confirmable message from the client.\n   - ``AUTO_BSPACK_ERROR`` - after ``BootstrapPackRequest`` is received it\n     automatically responds with ``NOT FOUND``.\n\n   If some of the options are absent, their state remains unchanged.\n\n"
  },
  {
    "path": "doc/sphinx/source/Tools/FactoryProvisioning.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nFactory Provisioning Tool\n=========================\n\nGeneral overview\n^^^^^^^^^^^^^^^^\n\nAnjay comes with a simple Python script that makes factory device\nprovisioning easier. The script supports:\n\n* Generation of SenML CBOR encoded packet with basic object information read by Anjay.\n* Creation of self-signed certificates.\n* Loading configuration to device (currently supports Nordic boards).\n* Automatic device onboarding in the Coiote server.\n\n.. note::\n\n    It is recommended to use :doc:`VirtualEnvironments` when running Python\n    scripts.\n\nProvisioning tool\n^^^^^^^^^^^^^^^^^\n\nThe provisioning tool is a small script that the user will interact with\nduring the factory provisioning process. It uses the Factory Provisioning library\nthat we will talk about in the next section.\n\n.. note::\n   The script can be found in ``tools/provisioning-tool/ptool.py``.\n\nThe script takes many different parameters as arguments to allow some customisation.\nLet's take a closer look:\n\n* `-c`, `--endpoint_cfg` - path to the configuration file with the object information\n  to be loaded to the device. The file is in the format of a Python dictionary that\n  is evaluated by the library and represents LwM2M objects to be loaded to the\n  device. In addition to the standard Python data types (``int``, ``str``, ``bool``,\n  ``bytes``) to represent values of resources in the object, the user can use ``Objlink``\n  class to represent objlnk resource type. The constructor for this class is as\n  following:\n\n  .. highlight:: python\n  .. snippet-source:: tools/test-framework-tools/tools/framework_tools/lwm2m/objlink.py\n\n      class Objlink:\n          def __init__(self, ObjID, ObjInstID):\n              self.ObjID = ObjID\n              self.ObjInstID = ObjInstID\n\n  So for instance ``Objlink(66, 0)`` would represent a object link ``66:0``.\n\n  .. note::\n     Sample configuration can be found in ``tools/provisioning-tool/configs/endpoint_cfg``\n\n  .. warning::\n\n      The provisioning tool is **NOT** intended to be safe to use with\n      arbitrary input data. It is only intended for convenience when working with\n      files created locally by trusted parties.\n\n      **The input text file (\"endpoint_cfg\") is evaluated as Python code**, and as\n      such, running the code from provisioning tool with untrusted input may lead\n      to arbitrary operations being performed on the computer.\n\n* `-e`, `--URN` - This is the name of the device that will be used during registration\n  to the Coiote Server. The name should be unique for the instance of Coiote Server\n  we will register the device to.\n* `-s`, `--server` - This is a JSON file with server information needed for\n  registration process. Those include:\n\n    * `url` - url of the Coiote Server, if missing a default value ``https://eu.iot.avsystem.cloud``\n      is used.\n    * `port` - port number communication with the REST API, if missing a default value\n      ``8087`` is used. Please note that this is not the port number used by the endpoint\n      device for communication with the Coiote server and rather a port number used by the\n      REST API.\n    * `domain` - name of the domain under which to register the device. There is no\n      default value and this needs to be provided by the user if a registration process\n      is performed.\n\n  .. note::\n     Sample configuration can be found in ``tools/provisioning-tool/configs/lwm2m_server.json``\n\n* `-t`, `--token` - Access token for REST authorization to the Coiote server.\n  The generation of this token is explained in the Coiote documentation. In Coiote click\n  on the question mark in the top right corner, then **Documentation -> User**. The\n  description can be found in **Rest API -> REST API authentication** section.\n* `-C`, `--cert` - Path to the JSON file containing information for the generation of\n  a self signed certificate. The provisioning tool supports those JSON entries:\n\n  +------------------------+---------+---------------+----------------------------------------+\n  | Field name             | Type    | Default Value | Description                            |\n  +========================+=========+===============+========================================+\n  | countryName            | String  | N/A           | Holds a 2-character ISO format country |\n  |                        |         |               | code. Represents attribute C in        |\n  |                        |         |               | certificate subject.                   |\n  +------------------------+---------+---------------+----------------------------------------+\n  | stateOrProvinceName    | String  | N/A           | Represents attribute ST in certificate |\n  |                        |         |               | subject.                               |\n  +------------------------+---------+---------------+----------------------------------------+\n  | localityName           | String  | N/A           | Represents attribute L in certificate  |\n  |                        |         |               | subject.                               |\n  +------------------------+---------+---------------+----------------------------------------+\n  | organizationName       | String  | N/A           | Represents attribute O in certificate  |\n  |                        |         |               | subject.                               |\n  +------------------------+---------+---------------+----------------------------------------+\n  | organizationUnitName   | String  | N/A           | Represents attribute OU in certificate |\n  |                        |         |               | subject.                               |\n  +------------------------+---------+---------------+----------------------------------------+\n  | emailAddress           | String  | N/A           | Holds email address.                   |\n  +------------------------+---------+---------------+----------------------------------------+\n  | commonName             | String  | <endpoint     | Represents attribute CN in certificate |\n  |                        |         | name>         | subject.                               |\n  +------------------------+---------+---------------+----------------------------------------+\n  | serialNumber           | Integer | N/A           | Holds serial number attribute in the   |\n  |                        |         |               | certificate subject.                   |\n  +------------------------+---------+---------------+----------------------------------------+\n  | validityOffsetInSeconds| Integer | 220752000     | Represents validity of certificate in  |\n  |                        |         |               | seconds.                               |\n  +------------------------+---------+---------------+----------------------------------------+\n  | ellipticCurve          | String  | \"secp256r1\"   | Elliptic curve on which to base the    |\n  |                        |         |               | key generated during certificate       |\n  |                        |         |               | creation.                              |\n  +------------------------+---------+---------------+----------------------------------------+\n  | RSAKeyLen              | Integer | N/A           | Represents length of the RSA key used  |\n  |                        |         |               | during certificate creation.           |\n  |                        |         |               |                                        |\n  |                        |         |               | Cannot be specified together with      |\n  |                        |         |               | ``ellipticCurve``. EC-based keys are   |\n  |                        |         |               | used by default.                       |\n  +------------------------+---------+---------------+----------------------------------------+\n  | digest                 | String  | \"sha256\"      | Represents a digest algorithm used     |\n  |                        |         |               | during certificate signing.            |\n  +------------------------+---------+---------------+----------------------------------------+\n\n  .. note::\n     Sample configuration can be found in ``tools/provisioning-tool/configs/cert_info.json``\n\n* `-k`, `--pkey` - Path to the endpoint private key in DER format, ignored if CERT\n  parameter is set.\n* `-r`, `--pcert` - Path to the endpoint private cert in DER format, ignored if CERT\n  parameter is set.\n* `-p`, `--scert` - Path to the server public cert in DER format.\n\n.. note::\n    The server public certificate in DER format can be acquired using openssl client:\n    ``echo -n | openssl s_client -connect SERVER:PORT | openssl x509 -outform der > CERTIFICATE.der``\n    or converted from PEM format using:\n    ``openssl x509 -outform der -in CERTIFICATE.pem -out CERTIFICATE.der``.\n\nFactory Provisioning library\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nTo better understand the provisioning process we will look into the implementation\nof the Factory Provisioning library.\n\n.. note::\n   The Python library for factory provisioning can be found in\n   ``tools/provisioning-tool/factory_prov``.\n\nThe main class of the library that the user will interact with is the ``FactoryProvisioning``\nclass. The constructor for this class takes a few different arguments:\n\n.. highlight:: python\n.. snippet-source:: tools/provisioning-tool/factory_prov/factory_prov.py\n\n    class FactoryProvisioning:\n       def __init__(self,\n                    endpoint_cfg,\n                    endpoint_name,\n                    server_info,\n                    token,\n                    cert_info):\n\n\nThe ``endpoint_cfg`` is the path to the file with the device configuration. Corresponds\nto the argument of the same name from the provisioning tool.\n\nThe next parameter is ``endpoint_name``. This is the unique name of the device used\nduring registration. Corresponds to the `URN` argument from the provisioning tool.\n\nThe ``server_info`` is the path to the file with Coiote server information. Corresponds\nto `server` argument from the provisioning tool.\n\nThe ``token`` parameter is a token used to authenticate to the REST API. Corresponds\nto the argument fo the same name from the provisioning tool.\n\nThe ``cert_info`` parameter can be used to pass the path to a file containing information\nused during generation of a self signed certificate. This parameter corresponds to\n`cert` argument from the provisioning tool.\n\n.. note::\n   Parameters ``server_info``, ``token`` and ``endpoint_name``  can be set to ``None`` if\n   automatic registration to the Coiote server won't be done. Also `cert_info`\n   parameter can be ``None`` if the user won't create a self signed certificate using\n   the factory provisioning library or security mode used will be different then\n   Certificate.\n\nThe user can extract the information about used Security Mode set in ``endpoint_cfg``\nusing a class method ``get_sec_mode()``. This returns a string containing one\nof three values: \"psk\", \"cert\", \"nosec\".\n\nIf Certificate is used as a Security Mode in the Security object definition,\nthen before calling ``provision_device()``:\n\n- user should call ``set_server_cert()`` function to pass a path to a DER\n  formatted file containing server's certificate,\n- ``generate_self_signed_cert()`` should be called or pre-generated\n  certificates should be supplied by calling ``set_endpoint_cert_and_key()``\n  with a path to device's private key and public certificate.\n\nTo perform the factory provisioning of the device the user should call ``provision_device()``\nfrom the ``FactoryProvisioning`` class. This function will generate a configuration\nfile in the format os SenML CBOR (the file will be called \"SenMLCBOR\" and writen to disk). This\nconfiguration will be uploaded to the device together with the certificates (either\nself signed client certificates or the cerificate pointed by ``set_endpoint_cert_and_key()``\nand also the server certificate set using ``set_server_cert()``.\n\nThe ``register()`` function can be used to automatically register the device\nto the Coiote Server. Please note that if Certificate was used as a Security Mode\nthen the device public certificate should uploaded by hand in to the Coiote Server.\n\n.. note::\n   The self-signed certificates are generated to the ``cert`` folder.\n\nWe can now take a look at the provisioning tool implementation to see how this API can\nbe used:\n\n.. highlight:: python\n.. snippet-source:: tools/provisioning-tool/ptool.py\n\n\n    try:\n        fcty = fp.FactoryProvisioning(args.endpoint_cfg, args.URN, args.server,\n                                      args.token, args.cert)\n        if fcty.get_sec_mode() == 'cert' or fcty.get_sec_mode() == 'est':\n            if args.scert is not None:\n                fcty.set_server_cert(args.scert)\n\n            if args.cert is not None:\n                fcty.generate_self_signed_cert()\n            elif args.pkey is not None and args.pcert is not None:\n                fcty.set_endpoint_cert_and_key(args.pcert, args.pkey)\n\n        fcty.provision_device()\n\n        if args.server is not None and args.token is not None and args.URN is not None:\n            fcty.register()\n\n        ret_val = 0\n    except ValueError as err:\n        print('Incorrect configuration:', err)\n    except ConnectionError as err:\n        print('Coiote server error:', err)\n    except requests.HTTPError as err:\n        print(err)\n    except OSError as err:\n        print(err)\n    except RuntimeError as err:\n        print(err)\n    except:\n        print('Unexpected error, abort script execution')\n    finally:\n        sys.exit(ret_val)\n\nFirst we create a object of the ``FactoryProvisioning`` class passing the arguments\nprovided to the script. Depending on the Security Mode set in the `endpoint_cfg`\nwe can generate a self signed certificate or pass the paths to the certificate for\nboth the client and server. Next we call ``provision_device()`` that will load the\nconfiguration to the device. Finally we can call ``register()`` to automatically\nregister the device to the Coiote server. At the end of the script we will try\nto catch all exceptions that could show up during script execution. The error\nmessages should give the user a hint what went wrong in case of any trouble.\n"
  },
  {
    "path": "doc/sphinx/source/Tools/PackagesGenerator.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nPackage generator\n=================\n\nThis section describes the ``create_package.py`` script used to generate test packages for\nLwM2M objects implemented in the Anjay demo. These packages simulate firmware and software\nupdates with configurable behaviors and error conditions.\n\n.. note::\n\n    It is recommended to use :doc:`VirtualEnvironments` when running Python\n    scripts.\n\nOverview\n--------\n\nThe Anjay demo implements several LwM2M objects to facilitate firmware and software management.\nThese objects include:\n\n* object 5 — Firmware Update\n* object 9 — Software Management\n* object 33629 — Advanced Firmware Update (AFU)\n\n.. note::\n\n   These objects are implemented primarily for testing purposes and are not production-ready. To\n   generate compatible update packages for testing, use the ``create_package.py`` script located in\n   the ``tests/integration/framework/framework`` directory. \n   If you need help implementing the required callbacks for Objects /5 and /33629, \n   refer to :doc:`Firmware Update tutorial <../FirmwareUpdateTutorial>`.\n\nIn order to use the objects, you need packages that contain the appropriate metadata. To generate\nthese packages, run ``create_package.py`` script located in the \n``tests/integration/framework/framework`` directory.\n\nExample usage\n-------------\n\nTo generate an example package:\n\n1. Run one of the following commands:\n\n.. tabs::\n\n   .. tab:: Package for the object 5 that will be executed during update\n\n      .. code-block:: bash\n\n         python ./tests/integration/framework/framework/create_package.py -o ./package -m ANJAY_FW\n\n      .. note::\n\n         If persistence is enabled, then if the package execution is successful, upon restarting\n         the demo (which may occur during an update if the package includes the demo), the resource\n         /5/0/5 will be set to the value corresponding to the state `Firmware updated successfully`.\n\n   .. tab:: Package for the object 9 that will be executed through the system's shell interpreter during installation\n\n      .. code-block:: bash\n\n         python ./tests/integration/framework/framework/create_package.py -o ./package -m ANJAY_SW\n\n   .. tab:: Package for the object 9 that will fail during validation\n\n      .. code-block:: bash\n\n         python ./tests/integration/framework/framework/create_package.py -o ./package -m ANJAY_SW -c 1234\n\n   .. tab:: Package for the object 9 that will fail during installation\n\n      .. code-block:: bash\n\n         python ./tests/integration/framework/framework/create_package.py -o ./package -m ANJAY_SW -e FailedInstall\n\n   .. tab:: Package for the object 9 that will fail during activation\n\n      .. code-block:: bash\n\n         python ./tests/integration/framework/framework/create_package.py -o ./package -m ANJAY_SW -e FailureInPerformActivate\n\n   .. tab:: Package for the zero instance of object 33629 that will be executed during update\n\n      .. code-block:: bash\n\n            python ./tests/integration/framework/framework/create_package.py -o ./package -m ANJAY_APP\n\n      .. note::\n\n            If persistence is enabled and persistence file is set by ``--afu-marker-path`` argument,\n            then if the package execution is successful, upon restarting the demo (which may occur\n            during an update if the package includes the demo), the resource /33629/0/5 will be set\n            to the value corresponding to the state `Firmware updated successfully`.\n\n2. Provide package content, e.g.:\n\n.. code-block:: bash\n\n    #!/bin/sh\n    echo installed\n\nPress ``Ctrl+D`` (`EOF`) to complete input.\n\n3. After that, package should exists:\n\n.. code-block:: bash\n   \n    cat ./package | xxd -p \n    414e4a41595f5357000200000813e38003312e3023212f62696e2f73680a\n    6563686f20696e7374616c6c65640a\n\nPackage generation arguments\n----------------------------\n\nTo obtain information about each of the available arguments, display the help message by running\nthe following command:\n\n.. code-block:: bash\n\n    python tests/integration/framework/framework/create_package.py --help\n\nManipulating the behavior of objects\n------------------------------------\n\nThe behavior of the upgrade/install process in the Demo application can be manipulated using\nmetadata embedded in the package header. You can trigger specific errors during image generation\nby passing the ``--error`` argument to the ``create_package.py`` script.\nLikewise, the ``--crc`` and ``--magic`` arguments can be used to trigger an error during\npackage validation.\n\nErrors description\n------------------\n\n\nFirmware Update and Advanced Firmware Update errors\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n.. note::\n    In the case of AFU, this description is only valid for the zero instance \n    (magic == AJAY_APP). For other instances, the behavior may differ.\n   \n+----------------------------+---------------------------------------------------------------+\n| Error name                 | Behavior                                                      |\n+============================+===============================================================+\n| NoError                    | The demo will replace the current process with a new one by   |\n|                            | executing the downloaded package during update                |\n|                            | (``perform_upgrade`` callback). It will pass along the        |\n|                            | arguments specified when running the demo itself.             |\n+----------------------------+---------------------------------------------------------------+\n| OutOfMemory                | The firmware update will fail after the package is            |\n|                            | downloaded, during its validation in the ``stream_finish``    |\n|                            | callback, by returning ANJAY_FW_UPDATE_ERR_OUT_OF_MEMORY.     |\n+----------------------------+---------------------------------------------------------------+\n| FailedUpdate               | The firmware update will fail during update                   |\n|                            | (``perform_upgrade`` callback) by returning -1.               |\n+----------------------------+---------------------------------------------------------------+\n| DelayedSuccess             | The demo will replace the current process with a new one by   |\n|                            | executing the downloaded package during update                |\n|                            | (``perform_upgrade`` callback). It will pass along the        |\n|                            | arguments specified when running the demo, plus an argument   |\n|                            | that causes the result to be set to success — provided that   |\n|                            | the package contains a demo capable of interpreting this      |\n|                            | argument accordingly.                                         |\n+----------------------------+---------------------------------------------------------------+\n| DelayedFailedUpdate        | The demo will replace the current process with a new one by   |\n|                            | executing the downloaded package during update                |\n|                            | (``perform_upgrade`` callback). It will pass along the        |\n|                            | arguments specified when running the demo, plus an argument   |\n|                            | that causes the result to be set to `Firmware update failed`  |\n|                            | — provided that the package contains a demo capable of        |\n|                            | interpreting this argument accordingly.                       |\n+----------------------------+---------------------------------------------------------------+\n| SetSuccessInPerformUpgrade | The demo will set the result to success by using the          |\n|                            | ``anjay_fw_update_set_result`` (or                            |\n|                            | ``anjay_advanced_fw_update_set_state_and_result`` in case of  |\n|                            | AFU) function during update (``perform_upgrade`` callback).   |\n+----------------------------+---------------------------------------------------------------+\n| SetFailureInPerformUpgrade | The demo will set the result to `Firmware update failed` by   |\n|                            | using the ``anjay_fw_update_set_result`` (or                  |\n|                            | ``anjay_advanced_fw_update_set_state_and_result`` in case of  |\n|                            | AFU) function during update (``perform_upgrade`` callback).   |\n+----------------------------+---------------------------------------------------------------+\n| DoNothing                  | The demo will return 0 in the ``perform_upgrade`` callback.   |\n+----------------------------+---------------------------------------------------------------+\n| Defer                      | The demo will set the result to deferred by using the         |\n|                            | ``anjay_fw_update_set_result`` function during updating       |\n|                            | (or return ``ANJAY_ADVANCED_FW_UPDATE_ERR_DEFERRED`` in case  |\n|                            | of AFU).                                                      |\n+----------------------------+---------------------------------------------------------------+\n\nSoftware Management errors\n^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n.. note::\n   To enable the Software Management object, define ``ANJAY_WITH_MODULE_SW_MGMT`` e.g. by running\n   CMake or ``./devconfig`` with ``-DANJAY_WITH_MODULE_SW_MGMT=ON`` argument.\n\n+---------------------------------+----------------------------------------------------------+\n| Error name                      | Behavior                                                 |\n+=================================+==========================================================+\n| NoError                         | The demo will create a child process during installation |\n|                                 | (``pkg_install`` callback) by executing the downloaded   |\n|                                 | package through the system's shell interpreter and will  |\n|                                 | wait until the end of its execution. The result will be  |\n|                                 | set to success and activation state to `Disable` by      |\n|                                 | using ``anjay_sw_mgmt_finish_pkg_install`` function.     |\n+---------------------------------+----------------------------------------------------------+\n| FailedInstall                   | The software installation will fail during installation  |\n|                                 | (``pkg_install`` callback) by returning -1.              |\n+---------------------------------+----------------------------------------------------------+\n| DelayedSuccessInstall           | The demo will replace the current process with a new one | \n|                                 | by executing the downloaded package during installation  |\n|                                 | (``pkg_install`` callback). It will pass along the       |\n|                                 | arguments specified when running the demo, plus an       |\n|                                 | argument that causes the result to be set to success —   |\n|                                 | provided that the package contains a demo capable        |\n|                                 | of interpreting this argument accordingly.               |\n+---------------------------------+----------------------------------------------------------+\n| DelayedFailedInstall            | The demo will replace the current process with a new one | \n|                                 | by executing the downloaded package during installation  |\n|                                 | (``pkg_install`` callback). It will pass along the       |\n|                                 | arguments specified when running the demo, plus an       |   \n|                                 | argument that causes the result to be set to `Software   |\n|                                 | installation failure` — provided that the package        |\n|                                 | contains a demo capable of interpreting this argument    |\n|                                 | accordingly.                                             |\n+---------------------------------+----------------------------------------------------------+\n| SuccessInPerformInstall         | The demo will set the result to success by using the     |\n|                                 | ``anjay_sw_mgmt_finish_pkg_install`` function during     |\n|                                 | installation (``pkg_install`` callback).                 |\n+---------------------------------+----------------------------------------------------------+\n| SuccessInPerformInstallActivate | The demo will set the result to success and activation   |\n|                                 | state to `Enable` by using the                           |\n|                                 | ``anjay_sw_mgmt_finish_pkg_install`` function during     |\n|                                 | installation (``pkg_install`` callback).                 |\n+---------------------------------+----------------------------------------------------------+\n| FailureInPerformInstall         | The demo will set the result to `Software installation   |\n|                                 | failure` by using the                                    |\n|                                 | ``anjay_sw_mgmt_finish_pkg_install`` function during     |\n|                                 | installation (``pkg_install`` callback).                 |\n+---------------------------------+----------------------------------------------------------+\n| FailureInPerformUninstall       | The software uninstallation will fail in the             |\n|                                 | ``pkg_uninstall`` callback by returning -1.              |\n+---------------------------------+----------------------------------------------------------+\n| FailureInPerformActivate        | The software activation will fail in the                 |\n|                                 | ``activate`` callback by returning -1.                   |\n+---------------------------------+----------------------------------------------------------+\n| FailureInPerformDeactivate      | The software deactivation will fail in the               |\n|                                 | ``deactivate`` callback by returning -1.                 |\n+---------------------------------+----------------------------------------------------------+\n| FailureInPerformPrepareForUpdate| The software update preparation will fail in the         |\n|                                 | ``prepare_for_update`` callback by returning -1.         |\n+---------------------------------+----------------------------------------------------------+\n| DoNothing                       | The demo will return 0 in the ``pkg_install``            |\n|                                 | callback.                                                |\n+---------------------------------+----------------------------------------------------------+\n\nPackage version\n---------------\n\nYou can set the package version for all objects by using the ``--version`` argument.\nHowever, the version of the Firmware Update and Software Management package must be set to\n`1.0` (this is due to how these objects have been implemented in the demo and is not a\nlimitation of the Anjay library itself). In the case of Advanced Firmware Update, the\nversion can be anything as long as it is shorter than 24 characters.\n\nRemoving persistence files\n--------------------------\n\nEach object can store its state in a persistence file.  To ensure you’re working with an object\nin its initial state, remove the persistence file. By default, these files are located in the\nfollowing paths:\n\n* Firmware Update — ``/tmp/anjay-fw-updated``\n* Software Management — ``/tmp/anjay-sw-mgmt``\n* Advanced Firmware Update — by default there is no persistence file\n\nAdditionally, you can set the persistence file location using the following arguments:\n\n* Firmware Update — ``--fw-updated-marker-path``\n* Software Management — ``--sw-mgmt-persistence-file``\n* Advanced Firmware Update — ``--afu-marker-path``\n"
  },
  {
    "path": "doc/sphinx/source/Tools/StandaloneObjects.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nStandalone LwM2M Object implementations\n=======================================\n\nAs described in :doc:`../BasicClient/BC-MandatoryObjects`, Anjay contains\nimplementations of some LwM2M Core Objects, including the LwM2M Security (0) and\nLwM2M Server (1) objects.\n\nThese implementations should be appropriate for most users, but some users might\nhave specific requirements that deviate from the default Anjay behavior. For\nthis reason, standalone versions of the Security and Server objects are provided\nin the ``standalone`` directory of the repository (or commercial distribution\npackage).\n\n.. note::\n\n    It is recommended to use :doc:`VirtualEnvironments` when running Python\n    scripts.\n\n.. warning::\n\n    Customizing the logic of Core Objects is likely to violate the LwM2M\n    technical specification. Please proceed with care.\n\nUsing the standalone objects\n----------------------------\n\nTo use the standalone objects:\n\n* Copy the ``standalone/security`` and/or ``standalone/server`` directories into\n  your project, and make sure that all the ``*.c`` files are compiled.\n\n* The ``standalone_security.h`` and ``standalone_server.h`` files mirror the\n  public header files of the default implementations. Please include them in\n  your application code to use the object implementations.\n\n* Make sure to account for the following differences between the default and\n  standalone implementations:\n\n  * Prefix for public APIs (including public function and type names) is changed\n    from ``anjay_`` to ``standalone_``\n\n  * The install functions (i.e., ``standalone_security_object_install()``,\n    ``standalone_security_object_install_with_hsm()`` and\n    ``standalone_server_object_install()``), unlike their default versions,\n    return ``const anjay_dm_object_def_t **`` pointers. Please store this value\n    during installation, as it needs to be passed for further API calls.\n    However, you **do not** need to call `anjay_register_object()\n    <../api/dm_8h.html#a1468b47fa9169474920c8c86d533b991>`_ as the install\n    functions already call it.\n\n  * All other public APIs take the aforementioned\n    ``const anjay_dm_object_def_t *const *`` pointer instead of the `anjay_t\n    <../api/core_8h.html#a6c9664a3b0c2d5629c9639dce7b1dbfb>`_ object. Adjust the\n    calls accordingly.\n\n  * Unlike the default implementation, the standalone objects are **not\n    automatically cleaned up** at the time of `anjay_delete()\n    <../api/core_8h.html#a243f18f976bca57b5a7b0714bfb99095>`_. If your code ever\n    cleans up the Anjay object, please make sure to call\n    ``standalone_security_object_cleanup()`` and/or\n    ``standalone_server_object_cleanup()`` afterwards.\n\nThis will replicate the functionality of the default implementations. You can\napply any modifications you need from there.\n\n.. note::\n\n    Even though these implementations are standalone, they still contain\n    conditional compilation directives that refer to Anjay configuration\n    options, including those related to Security and Server objects, e.g.\n    ``ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT``. If you wish to disable the\n    modules completely, please update the code accordingly.\n\nLimitations\n-----------\n\nThe standalone implementations have the same limitations as application code -\nthey cannot access internal or private Anjay APIs. For this reason, the \"OSCORE\nSecurity Mode\" Resource in the Security object (/0/\\*/17) is not validated, as\nthe code does not have access to the OSCORE object implementation.\n\nAlso please note that when upgrading Anjay, you will be responsible for porting\nany fixes and improvements that may be made to the Security and Server object\nimplementations between releases.\n"
  },
  {
    "path": "doc/sphinx/source/Tools/StubGenerator.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\n.. _anjay-object-stub-generator:\n\nAnjay Object stub generator\n---------------------------\n\n.. contents:: :local:\n\nIntroduction\n~~~~~~~~~~~~\n\nFor easy implementation of custom objects, you can use the\n`./tools/anjay_codegen.py` script. It parses a LwM2M Object Definition XML\nand generates a skeleton of the LwM2M object code, requiring the user to only\nfill in actual object logic.\n\n.. note::\n\n    It is recommended to use :doc:`VirtualEnvironments` when running Python\n    scripts.\n\n.. note::\n\n    You can use `./tools/lwm2m_object_registry.py` script to download the\n    Object Definition XML from `OMA LwM2M Object and Resource Registry\n    <https://technical.openmobilealliance.org/OMNA/LwM2M/LwM2MRegistry.html>`_.\n\nCode generation\n~~~~~~~~~~~~~~~\n\nThis section shows how to generate code for an example object defined in an XML\nfile and covers object templates with both static and dynamic instances.\n\nExample object definition\n^^^^^^^^^^^^^^^^^^^^^^^^^\n\nThe example object definition is stored in `some_object.xml` file with the\nfollowing contents:\n\n.. code-block:: xml\n    :caption: some_object.xml\n\n    <?xml version=\"1.0\" encoding=\"utf-8\"?>\n    <LWM2M xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:noNamespaceSchemaLocation=\"http://openmobilealliance.org/tech/profiles/LWM2M.xsd\">\n      <Object ObjectType=\"MODefinition\">\n            <Name>Some Object Name</Name>\n            <Description1><![CDATA[LwM2M Object description.]]></Description1>\n            <ObjectID>9999</ObjectID>\n            <ObjectURN></ObjectURN>\n            <MultipleInstances>Multiple</MultipleInstances>\n            <Mandatory>Optional</Mandatory>\n            <Resources>\n                <Item ID=\"0\">\n                    <Name>Some String Resource</Name>\n                    <Operations>RW</Operations>\n                    <MultipleInstances>Single</MultipleInstances>\n                    <Mandatory>Mandatory</Mandatory>\n                    <Type>String</Type>\n                    <RangeEnumeration></RangeEnumeration>\n                    <Units></Units>\n                    <Description><![CDATA[Some description.]]></Description>\n                </Item>\n                <Item ID=\"1\">\n                    <Name>Some Integer Resource</Name>\n                    <Operations>RW</Operations>\n                    <MultipleInstances>Single</MultipleInstances>\n                    <Mandatory>Mandatory</Mandatory>\n                    <Type>Integer</Type>\n                    <RangeEnumeration></RangeEnumeration>\n                    <Units></Units>\n                    <Description><![CDATA[Some description.]]></Description>\n                </Item>\n                <Item ID=\"2\">\n                    <Name>Some Boolean Multiple Resource</Name>\n                    <Operations>RW</Operations>\n                    <MultipleInstances>Multiple</MultipleInstances>\n                    <Mandatory>Mandatory</Mandatory>\n                    <Type>Boolean</Type>\n                    <RangeEnumeration/>\n                    <Units></Units>\n                    <Description><![CDATA[Some description.]]></Description>\n                </Item>\n            </Resources>\n            <Description2></Description2>\n        </Object>\n    </LWM2M>\n\nWe can see that it is a multiple-instance object. Instances of such object can\nbe allocated dynamically on the heap - this solution provides better flexibility\nbut requires additional code related to memory management. Another way is to use\na container whose size is fixed and known at compilation time. Both of these\napproaches are described in the next subsections.\n\nObject with dynamically-allocated instances\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nTo create a C language object template with dynamically-allocated instances we\nexecute the following command:\n\n.. code-block:: bash\n\n    ./tools/anjay_codegen.py -i some_object.xml -o some_object.c\n\nThe source of the example object looks like this:\n\n.. code-block:: c\n    :caption: some_object.c\n\n    /**\n     * Generated by anjay_codegen.py on 2022-01-24 18:52:32\n     *\n     * LwM2M Object: Some Object Name\n     * ID: 9999, URN: , Optional, Multiple\n     *\n     * LwM2M Object description.\n     */\n    #include <assert.h>\n    #include <stdbool.h>\n\n    #include <anjay/anjay.h>\n    #include <avsystem/commons/avs_defs.h>\n    #include <avsystem/commons/avs_list.h>\n    #include <avsystem/commons/avs_memory.h>\n\n    /**\n     * Some String Resource: RW, Single, Mandatory\n     * type: string, range: N/A, unit: N/A\n     * Some description.\n     */\n    #define RID_SOME_STRING_RESOURCE 0\n\n    /**\n     * Some Integer Resource: RW, Single, Mandatory\n     * type: integer, range: N/A, unit: N/A\n     * Some description.\n     */\n    #define RID_SOME_INTEGER_RESOURCE 1\n\n    /**\n     * Some Boolean Multiple Resource: RW, Multiple, Mandatory\n     * type: boolean, range: N/A, unit: N/A\n     * Some description.\n     */\n    #define RID_SOME_BOOLEAN_MULTIPLE_RESOURCE 2\n\n    typedef struct some_object_name_instance_struct {\n        anjay_iid_t iid;\n\n        // TODO: instance state\n    } some_object_name_instance_t;\n\n    typedef struct some_object_name_object_struct {\n        const anjay_dm_object_def_t *def;\n        AVS_LIST(some_object_name_instance_t) instances;\n\n        // TODO: object state\n    } some_object_name_object_t;\n\n    static inline some_object_name_object_t *\n    get_obj(const anjay_dm_object_def_t *const *obj_ptr) {\n        assert(obj_ptr);\n        return AVS_CONTAINER_OF(obj_ptr, some_object_name_object_t, def);\n    }\n\n    static some_object_name_instance_t *find_instance(const some_object_name_object_t *obj,\n                                                      anjay_iid_t iid) {\n        AVS_LIST(some_object_name_instance_t) it;\n        AVS_LIST_FOREACH(it, obj->instances) {\n            if (it->iid == iid) {\n                return it;\n            } else if (it->iid > iid) {\n                break;\n            }\n        }\n\n        return NULL;\n    }\n\n    static int list_instances(anjay_t *anjay,\n                              const anjay_dm_object_def_t *const *obj_ptr,\n                              anjay_dm_list_ctx_t *ctx) {\n        (void) anjay;\n\n        AVS_LIST(some_object_name_instance_t) it;\n        AVS_LIST_FOREACH(it, get_obj(obj_ptr)->instances) {\n            anjay_dm_emit(ctx, it->iid);\n        }\n\n        return 0;\n    }\n\n    static int init_instance(some_object_name_instance_t *inst, anjay_iid_t iid) {\n        assert(iid != ANJAY_ID_INVALID);\n\n        inst->iid = iid;\n        // TODO: instance init\n\n        // TODO: return 0 on success, negative value on failure\n        return 0;\n    }\n\n    static void release_instance(some_object_name_instance_t *inst) {\n        // TODO: instance cleanup\n        (void) inst;\n    }\n\n    static some_object_name_instance_t *\n    add_instance(some_object_name_object_t *obj, anjay_iid_t iid) {\n        assert(find_instance(obj, iid) == NULL);\n\n        AVS_LIST(some_object_name_instance_t) created =\n                AVS_LIST_NEW_ELEMENT(some_object_name_instance_t);\n        if (!created) {\n            return NULL;\n        }\n\n        int result = init_instance(created, iid);\n        if (result) {\n            AVS_LIST_CLEAR(&created);\n            return NULL;\n        }\n\n        AVS_LIST(some_object_name_instance_t) *ptr;\n        AVS_LIST_FOREACH_PTR(ptr, &obj->instances) {\n            if ((*ptr)->iid > created->iid) {\n                break;\n            }\n        }\n\n        AVS_LIST_INSERT(ptr, created);\n        return created;\n    }\n\n    static int instance_create(anjay_t *anjay,\n                               const anjay_dm_object_def_t *const *obj_ptr,\n                               anjay_iid_t iid) {\n        (void) anjay;\n        some_object_name_object_t *obj = get_obj(obj_ptr);\n\n        return add_instance(obj, iid) ? 0 : ANJAY_ERR_INTERNAL;\n    }\n\n    static int instance_remove(anjay_t *anjay,\n                               const anjay_dm_object_def_t *const *obj_ptr,\n                               anjay_iid_t iid) {\n        (void) anjay;\n        some_object_name_object_t *obj = get_obj(obj_ptr);\n\n        AVS_LIST(some_object_name_instance_t) *it;\n        AVS_LIST_FOREACH_PTR(it, &obj->instances) {\n            if ((*it)->iid == iid) {\n                release_instance(*it);\n                AVS_LIST_DELETE(it);\n                return 0;\n            } else if ((*it)->iid > iid) {\n                break;\n            }\n        }\n\n        assert(0);\n        return ANJAY_ERR_NOT_FOUND;\n    }\n\n    static int instance_reset(anjay_t *anjay,\n                              const anjay_dm_object_def_t *const *obj_ptr,\n                              anjay_iid_t iid) {\n        (void) anjay;\n\n        some_object_name_object_t *obj = get_obj(obj_ptr);\n        some_object_name_instance_t *inst = find_instance(obj, iid);\n        assert(inst);\n\n        // TODO: instance reset\n        return 0;\n    }\n\n    static int list_resources(anjay_t *anjay,\n                              const anjay_dm_object_def_t *const *obj_ptr,\n                              anjay_iid_t iid,\n                              anjay_dm_resource_list_ctx_t *ctx) {\n        (void) anjay;\n        (void) obj_ptr;\n        (void) iid;\n\n        anjay_dm_emit_res(ctx, RID_SOME_STRING_RESOURCE,\n                          ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT);\n        anjay_dm_emit_res(ctx, RID_SOME_INTEGER_RESOURCE,\n                          ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT);\n        anjay_dm_emit_res(ctx, RID_SOME_BOOLEAN_MULTIPLE_RESOURCE,\n                          ANJAY_DM_RES_RWM, ANJAY_DM_RES_PRESENT);\n        return 0;\n    }\n\n    static int resource_read(anjay_t *anjay,\n                             const anjay_dm_object_def_t *const *obj_ptr,\n                             anjay_iid_t iid,\n                             anjay_rid_t rid,\n                             anjay_riid_t riid,\n                             anjay_output_ctx_t *ctx) {\n        (void) anjay;\n\n        some_object_name_object_t *obj = get_obj(obj_ptr);\n        some_object_name_instance_t *inst = find_instance(obj, iid);\n        assert(inst);\n\n        switch (rid) {\n        case RID_SOME_STRING_RESOURCE:\n            assert(riid == ANJAY_ID_INVALID);\n            return anjay_ret_string(ctx, \"\"); // TODO\n\n        case RID_SOME_INTEGER_RESOURCE:\n            assert(riid == ANJAY_ID_INVALID);\n            return anjay_ret_i32(ctx, 0); // TODO\n\n        case RID_SOME_BOOLEAN_MULTIPLE_RESOURCE:\n            // TODO: extract Resource Instance\n            return anjay_ret_bool(ctx, 0); // TODO\n\n        default:\n            return ANJAY_ERR_METHOD_NOT_ALLOWED;\n        }\n    }\n\n    static int resource_write(anjay_t *anjay,\n                              const anjay_dm_object_def_t *const *obj_ptr,\n                              anjay_iid_t iid,\n                              anjay_rid_t rid,\n                              anjay_riid_t riid,\n                              anjay_input_ctx_t *ctx) {\n        (void) anjay;\n\n        some_object_name_object_t *obj = get_obj(obj_ptr);\n        some_object_name_instance_t *inst = find_instance(obj, iid);\n        assert(inst);\n\n        switch (rid) {\n        case RID_SOME_STRING_RESOURCE: {\n            assert(riid == ANJAY_ID_INVALID);\n            char value[256]; // TODO\n            return anjay_get_string(ctx, value, sizeof(value)); // TODO\n        }\n\n        case RID_SOME_INTEGER_RESOURCE: {\n            assert(riid == ANJAY_ID_INVALID);\n            int32_t value; // TODO\n            return anjay_get_i32(ctx, &value); // TODO\n        }\n\n        case RID_SOME_BOOLEAN_MULTIPLE_RESOURCE: {\n            // TODO: extract Resource Instance\n            bool value; // TODO\n            return anjay_get_bool(ctx, &value); // TODO\n        }\n\n        default:\n            return ANJAY_ERR_METHOD_NOT_ALLOWED;\n        }\n    }\n\n    static int resource_reset(anjay_t *anjay,\n                              const anjay_dm_object_def_t *const *obj_ptr,\n                              anjay_iid_t iid,\n                              anjay_rid_t rid) {\n        (void) anjay;\n\n        some_object_name_object_t *obj = get_obj(obj_ptr);\n        some_object_name_instance_t *inst = find_instance(obj, iid);\n        assert(inst);\n\n        switch (rid) {\n        case RID_SOME_BOOLEAN_MULTIPLE_RESOURCE:\n            return ANJAY_ERR_NOT_IMPLEMENTED; // TODO: remove all Resource Instances\n\n        default:\n            return ANJAY_ERR_METHOD_NOT_ALLOWED;\n        }\n    }\n\n    static int resource_instance_remove(anjay_t *anjay,\n                                        const anjay_dm_object_def_t *const *obj_ptr,\n                                        anjay_iid_t iid,\n                                        anjay_rid_t rid,\n                                        anjay_riid_t riid) {\n        // NOTE: This handler can be removed if the client application\n        // does not need to support LwM2M 1.2.\n\n        (void) anjay;\n\n        some_object_name_object_t *obj = get_obj(obj_ptr);\n        some_object_name_instance_t *inst = find_instance(obj, iid);\n        assert(inst);\n\n        switch (rid) {\n        case RID_SOME_BOOLEAN_MULTIPLE_RESOURCE:\n            // TODO: extract and remove Resource Instance\n            return ANJAY_ERR_NOT_IMPLEMENTED;\n\n        default:\n            return ANJAY_ERR_METHOD_NOT_ALLOWED;\n        }\n    }\n\n    static int list_resource_instances(anjay_t *anjay,\n                                       const anjay_dm_object_def_t *const *obj_ptr,\n                                       anjay_iid_t iid,\n                                       anjay_rid_t rid,\n                                       anjay_dm_list_ctx_t *ctx) {\n        (void) anjay;\n\n        some_object_name_object_t *obj = get_obj(obj_ptr);\n        some_object_name_instance_t *inst = find_instance(obj, iid);\n        assert(inst);\n\n        switch (rid) {\n        case RID_SOME_BOOLEAN_MULTIPLE_RESOURCE:\n            // anjay_dm_emit(ctx, ...); // TODO\n            return 0;\n\n        default:\n            return ANJAY_ERR_METHOD_NOT_ALLOWED;\n        }\n    }\n\n    static const anjay_dm_object_def_t OBJ_DEF = {\n        .oid = 9999,\n        .handlers = {\n            .list_instances = list_instances,\n            .instance_create = instance_create,\n            .instance_remove = instance_remove,\n            .instance_reset = instance_reset,\n\n            .list_resources = list_resources,\n            .resource_read = resource_read,\n            .resource_write = resource_write,\n            .resource_reset = resource_reset,\n            .list_resource_instances = list_resource_instances,\n\n            // TODO: implement these if transactional write/create is required\n            .transaction_begin = anjay_dm_transaction_NOOP,\n            .transaction_validate = anjay_dm_transaction_NOOP,\n            .transaction_commit = anjay_dm_transaction_NOOP,\n            .transaction_rollback = anjay_dm_transaction_NOOP,\n\n            .resource_instance_remove = resource_instance_remove\n        }\n    };\n\n    const anjay_dm_object_def_t **some_object_name_object_create(void) {\n        some_object_name_object_t *obj = (some_object_name_object_t *) avs_calloc(1, sizeof(some_object_name_object_t));\n        if (!obj) {\n            return NULL;\n        }\n        obj->def = &OBJ_DEF;\n\n        // TODO: object init\n\n        return &obj->def;\n    }\n\n    void some_object_name_object_release(const anjay_dm_object_def_t **def) {\n        if (def) {\n            some_object_name_object_t *obj = get_obj(def);\n            AVS_LIST_CLEAR(&obj->instances) {\n                release_instance(obj->instances);\n            }\n\n            // TODO: object cleanup\n\n            avs_free(obj);\n        }\n    }\n\n* ``some_object_name_object_t`` object definition contains a member called\n  ``instances`` which represents a list of instances\n  ``AVS_LIST(some_object_instance_t)``.\n\n* Instances are identified by their ID set in ``iid`` field of\n  ``some_object_name_instance_t`` structure.\n\n* To access an instance we have to iterate over all instances and find the one\n  with correct ID.\n\n* Instances can be created dynamically by the server using ``instance_create``\n  handler. ``add_instance`` function allocates memory for a new instance,\n  initializes the instance and appends it to the ``instances`` list.\n\n* Previously allocated instance can be removed by the server by means of\n  ``instance_remove`` handler. ``release_instance`` function cleans up the\n  instance and then the memory is deallocated.\n\n* Each handler (apart from ``instance_create`` and ``instance_remove``) taking\n  ``anjay_iid_t iid`` as an argument utilizes auxiliary ``find_instance``\n  function to get the pointer to the instance.\n\n* All allocated instances are deallocated in ``some_object_name_object_release``\n  function.\n\nObject with statically-allocated instances\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nTo create a C language object template with fixed 10 instances we use the `-n`\nswitch:\n\n.. code-block:: bash\n\n    ./tools/anjay_codegen.py -i some_object.xml -o some_object.c -n 10\n\nThe resulting code is following:\n\n.. code-block:: c\n    :caption: some_object.c\n\n    /**\n     * Generated by anjay_codegen.py on 2021-10-05 16:11:08\n     *\n     * LwM2M Object: Some Object Name\n     * ID: 9999, URN: , Optional, Multiple\n     *\n     * LwM2M Object description.\n     */\n    #include <assert.h>\n    #include <stdbool.h>\n\n    #include <anjay/anjay.h>\n    #include <avsystem/commons/avs_defs.h>\n    #include <avsystem/commons/avs_memory.h>\n\n    /**\n     * Some String Resource: RW, Single, Mandatory\n     * type: string, range: N/A, unit: N/A\n     * Some description.\n     */\n    #define RID_SOME_STRING_RESOURCE 0\n\n    /**\n     * Some Integer Resource: RW, Single, Mandatory\n     * type: integer, range: N/A, unit: N/A\n     * Some description.\n     */\n    #define RID_SOME_INTEGER_RESOURCE 1\n\n    /**\n     * Some Boolean Multiple Resource: RW, Multiple, Mandatory\n     * type: boolean, range: N/A, unit: N/A\n     * Some description.\n     */\n    #define RID_SOME_BOOLEAN_MULTIPLE_RESOURCE 2\n\n    typedef struct some_object_name_instance_struct {\n        // TODO: instance state\n    } some_object_name_instance_t;\n\n    typedef struct some_object_name_object_struct {\n        const anjay_dm_object_def_t *def;\n        some_object_name_instance_t instances[10];\n\n        // TODO: object state\n    } some_object_name_object_t;\n\n    static inline some_object_name_object_t *\n    get_obj(const anjay_dm_object_def_t *const *obj_ptr) {\n        assert(obj_ptr);\n        return AVS_CONTAINER_OF(obj_ptr, some_object_name_object_t, def);\n    }\n\n    static int list_instances(anjay_t *anjay,\n                              const anjay_dm_object_def_t *const *obj_ptr,\n                              anjay_dm_list_ctx_t *ctx) {\n        (void) anjay;\n\n        some_object_name_object_t *obj = get_obj(obj_ptr);\n        for (anjay_iid_t iid = 0; iid < AVS_ARRAY_SIZE(obj->instances); iid++) {\n            anjay_dm_emit(ctx, iid);\n        }\n\n        return 0;\n    }\n\n    static int instance_reset(anjay_t *anjay,\n                              const anjay_dm_object_def_t *const *obj_ptr,\n                              anjay_iid_t iid) {\n        (void) anjay;\n\n        some_object_name_object_t *obj = get_obj(obj_ptr);\n        assert(iid < AVS_ARRAY_SIZE(obj->instances));\n        some_object_name_instance_t *inst = &obj->instances[iid];\n\n        // TODO: instance reset\n\n        // TODO: return 0 on success, negative value on failure\n        return 0;\n    }\n\n    static int list_resources(anjay_t *anjay,\n                              const anjay_dm_object_def_t *const *obj_ptr,\n                              anjay_iid_t iid,\n                              anjay_dm_resource_list_ctx_t *ctx) {\n        (void) anjay;\n        (void) obj_ptr;\n        (void) iid;\n\n        anjay_dm_emit_res(ctx, RID_SOME_STRING_RESOURCE,\n                          ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT);\n        anjay_dm_emit_res(ctx, RID_SOME_INTEGER_RESOURCE,\n                          ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT);\n        anjay_dm_emit_res(ctx, RID_SOME_BOOLEAN_MULTIPLE_RESOURCE,\n                          ANJAY_DM_RES_RWM, ANJAY_DM_RES_PRESENT);\n        return 0;\n    }\n\n    static int resource_read(anjay_t *anjay,\n                             const anjay_dm_object_def_t *const *obj_ptr,\n                             anjay_iid_t iid,\n                             anjay_rid_t rid,\n                             anjay_riid_t riid,\n                             anjay_output_ctx_t *ctx) {\n        (void) anjay;\n\n        some_object_name_object_t *obj = get_obj(obj_ptr);\n        assert(iid < AVS_ARRAY_SIZE(obj->instances));\n        some_object_name_instance_t *inst = &obj->instances[iid];\n\n        switch (rid) {\n        case RID_SOME_STRING_RESOURCE:\n            assert(riid == ANJAY_ID_INVALID);\n            return anjay_ret_string(ctx, \"\"); // TODO\n\n        case RID_SOME_INTEGER_RESOURCE:\n            assert(riid == ANJAY_ID_INVALID);\n            return anjay_ret_i32(ctx, 0); // TODO\n\n        case RID_SOME_BOOLEAN_MULTIPLE_RESOURCE:\n            // TODO: extract Resource Instance\n            return anjay_ret_bool(ctx, 0); // TODO\n\n        default:\n            return ANJAY_ERR_METHOD_NOT_ALLOWED;\n        }\n    }\n\n    static int resource_write(anjay_t *anjay,\n                              const anjay_dm_object_def_t *const *obj_ptr,\n                              anjay_iid_t iid,\n                              anjay_rid_t rid,\n                              anjay_riid_t riid,\n                              anjay_input_ctx_t *ctx) {\n        (void) anjay;\n\n        some_object_name_object_t *obj = get_obj(obj_ptr);\n        assert(iid < AVS_ARRAY_SIZE(obj->instances));\n        some_object_name_instance_t *inst = &obj->instances[iid];\n\n        switch (rid) {\n        case RID_SOME_STRING_RESOURCE: {\n            assert(riid == ANJAY_ID_INVALID);\n            char value[256]; // TODO\n            return anjay_get_string(ctx, value, sizeof(value)); // TODO\n        }\n\n        case RID_SOME_INTEGER_RESOURCE: {\n            assert(riid == ANJAY_ID_INVALID);\n            int32_t value; // TODO\n            return anjay_get_i32(ctx, &value); // TODO\n        }\n\n        case RID_SOME_BOOLEAN_MULTIPLE_RESOURCE: {\n            // TODO: extract Resource Instance\n            bool value; // TODO\n            return anjay_get_bool(ctx, &value); // TODO\n        }\n\n        default:\n            return ANJAY_ERR_METHOD_NOT_ALLOWED;\n        }\n    }\n\n    static int resource_reset(anjay_t *anjay,\n                              const anjay_dm_object_def_t *const *obj_ptr,\n                              anjay_iid_t iid,\n                              anjay_rid_t rid) {\n        (void) anjay;\n\n        some_object_name_object_t *obj = get_obj(obj_ptr);\n        assert(iid < AVS_ARRAY_SIZE(obj->instances));\n        some_object_name_instance_t *inst = &obj->instances[iid];\n\n        switch (rid) {\n        case RID_SOME_BOOLEAN_MULTIPLE_RESOURCE:\n            return ANJAY_ERR_NOT_IMPLEMENTED; // TODO: remove all Resource Instances\n\n        default:\n            return ANJAY_ERR_METHOD_NOT_ALLOWED;\n        }\n    }\n\n    static int list_resource_instances(anjay_t *anjay,\n                                       const anjay_dm_object_def_t *const *obj_ptr,\n                                       anjay_iid_t iid,\n                                       anjay_rid_t rid,\n                                       anjay_dm_list_ctx_t *ctx) {\n        (void) anjay;\n\n        some_object_name_object_t *obj = get_obj(obj_ptr);\n        assert(iid < AVS_ARRAY_SIZE(obj->instances));\n        some_object_name_instance_t *inst = &obj->instances[iid];\n\n        switch (rid) {\n        case RID_SOME_BOOLEAN_MULTIPLE_RESOURCE:\n            // anjay_dm_emit(ctx, ...); // TODO\n            return 0;\n\n        default:\n            return ANJAY_ERR_METHOD_NOT_ALLOWED;\n        }\n    }\n\n    static const anjay_dm_object_def_t OBJ_DEF = {\n        .oid = 9999,\n        .handlers = {\n            .list_instances = list_instances,\n            .instance_reset = instance_reset,\n\n            .list_resources = list_resources,\n            .resource_read = resource_read,\n            .resource_write = resource_write,\n            .resource_reset = resource_reset,\n            .list_resource_instances = list_resource_instances,\n\n            // TODO: implement these if transactional write/create is required\n            .transaction_begin = anjay_dm_transaction_NOOP,\n            .transaction_validate = anjay_dm_transaction_NOOP,\n            .transaction_commit = anjay_dm_transaction_NOOP,\n            .transaction_rollback = anjay_dm_transaction_NOOP\n        }\n    };\n\n    const anjay_dm_object_def_t **some_object_name_object_create(void) {\n        some_object_name_object_t *obj =\n                (some_object_name_object_t *) avs_calloc(1, sizeof(some_object_name_object_t));\n        if (!obj) {\n            return NULL;\n        }\n        obj->def = &OBJ_DEF;\n\n        // TODO: object init\n\n        return &obj->def;\n    }\n\n    void some_object_name_object_release(const anjay_dm_object_def_t **def) {\n        if (def) {\n            some_object_name_object_t *obj = get_obj(def);\n\n            // TODO: object cleanup\n\n            avs_free(obj);\n        }\n    }\n\n* ``some_object_name_object_t`` object definition contains a member called\n  ``instances`` which is an array of 10 ``some_object_instance_t`` elements.\n\n* Instances are identified by ``iid`` used as their index in the ``instances``\n  array, meaning that ``find_instance``-like function is not needed.\n\n* The server cannot create and remove instances, so ``instance_create`` and\n  ``instance_remove`` handlers are not implemented.\n\nC++ object templates\n^^^^^^^^^^^^^^^^^^^^\n\nThe script is capable of generating C++ object templates as well - the `-x`\nswitch is intended to be used in this case. So, in order to create a C++ object\nwith dynamic instances one has to execute the command:\n\n.. code-block:: bash\n\n    ./tools/anjay_codegen.py -i some_object.xml -o some_object.cpp -x\n\n\nTo create a C++ template of the same object with 10 static instances run:\n\n.. code-block:: bash\n\n    ./tools/anjay_codegen.py -i some_object.xml -o some_object.cpp -n 10 -x\n\nThe main difference between the two is that the former approach uses the `C++\nwrapper of AVS_LIST <https://github.com/AVSystem/avs_commons/blob/master/include_public/avsystem/commons/avs_list_cxx.hpp>`_,\nand the latter one takes advantage of\n`std::array <https://en.cppreference.com/w/cpp/container/array>`_ container.\n\nAfter generating the object template\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nNow that the basic object structure is created, one can start thinking about filling in\nmissing parts marked in the code by the `TODO` comments. Then, to make the object present\nin the LwM2M Data Model, one shall instantiate it, and finally :ref:`register <registering-objects>`\nit within Anjay.\n\nAdditional examples\n~~~~~~~~~~~~~~~~~~~\n\n.. code-block:: bash\n\n    # list registered LwM2M objects\n    ./tools/lwm2m_object_registry.py --list\n\n    # download Object Definition XML for object 3 (Device) to device.xml\n    ./tools/lwm2m_object_registry.py --get-xml 3 > device.xml\n\n    # generate object code stub from device.xml\n    ./tools/anjay_codegen.py -i device.xml -o device.c\n\n    # download Object Definition XML for object 3 and generate code stub\n    # without creating an intermediate file\n    ./tools/lwm2m_object_registry.py --get-xml 3 | ./tools/anjay_codegen.py -i - -o device.c\n\n    # download Object Definition XML for object 3303 and generate code stub with\n    # five statically allocated instances without creating an intermediate file\n    ./tools/lwm2m_object_registry.py --get-xml 3303 | ./tools/anjay_codegen.py -i - -o temperature.c -n 5\n"
  },
  {
    "path": "doc/sphinx/source/Tools/VirtualEnvironments.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nVirtual environments\n====================\n\nThere are several Python-based tools in the repository:\n\n* integration tests framework,\n* :doc:`CliLwM2MServer`,\n* :doc:`FactoryProvisioning`,\n* :doc:`PackagesGenerator`,\n* :doc:`StubGenerator`.\n\nTo use them, create, activate and configure a Python vitrual environment by running\n\n.. code-block:: bash\n\n    python3 -m venv venv\n    source venv/bin/activate\n    python -m pip install --upgrade pip\n    python -m pip install -r requirements.txt\n\nor simply run\n\n.. code-block:: bash\n    \n    ./devconfig\n    source venv/bin/activate\n\nin repository root directory.\n"
  },
  {
    "path": "doc/sphinx/source/Tools.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nTools\n=====\n\n.. toctree::\n   :glob:\n   :titlesonly:\n\n   Tools/VirtualEnvironments\n   Tools/StubGenerator\n   Tools/CliLwM2MServer\n   Tools/StandaloneObjects\n   Tools/FactoryProvisioning\n   Tools/PackagesGenerator\n"
  },
  {
    "path": "doc/sphinx/source/_static/theme_overrides.css",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n/**\n * See: http://rackerlabs.github.io/docs-rackspace/tools/rtd-tables.html\n */\n\n/* override table width restrictions */\n@media screen and (min-width: 767px) {\n\n   .wy-table-responsive table td {\n      /* !important prevents the common CSS stylesheets from overriding\n         this as on RTD they are loaded after this stylesheet */\n      white-space: normal !important;\n   }\n\n   .wy-table-responsive {\n      overflow: visible !important;\n   }\n\n   /* https://stackoverflow.com/questions/23211695/modifying-content-width-of-the-sphinx-theme-read-the-docs */\n   .wy-nav-content {\n        max-width: 960px !important;\n    }\n}\n\n/* AVSystem color scheme */\n.wy-side-nav-search>a {\n    color: #000 !important;\n}\n\n.wy-side-nav-search>a:after {\n    content: \"Anjay\";\n}\n\n.wy-side-nav-search>div.version {\n    color: #000;\n}\n\n.wy-side-nav-search input[type=text] {\n    border-radius: 0 !important;\n    border: none !important;\n    box-shadow: none !important;\n}\n\n.wy-menu-vertical a:active {\n    background-color: #ffd500;\n    color: #000;\n}\n\na {\n    color: #000;\n    text-decoration: underline;\n    text-decoration-thickness: 1px;\n}\n\n.wy-nav-content a:visited,\n.wy-nav-content a:hover {\n    color: #8a8a8a;\n}\n\nnav a {\n    text-decoration: none !important;\n}\n\n.highlight {\n    background: #ffffff;\n}\n\n.highlight .hll {\n    background: #f3f6f6;\n}\n\ncode.small-literal {\n    font-size: 65%;\n}\n\n/* --- Doxygen/Breathe API Styling Overrides --- */\n\n/* Use this extremely specific selector to ensure we override \n * the default sphinx_rtd_theme styles regardless of the CSS loading order.\n */\nhtml.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) > dt {\n    \n    /* BACKGROUND: Light yellow instead of the default blue */\n    background: #fffdf0 !important; \n    \n    /* BORDER: Yellow accent line on top */\n    border-top: 3px solid #ffd500 !important; \n    \n    /* TEXT: Dark text for better contrast */\n    color: #333333 !important;\n    \n    /* Optional: cosmetic adjustments */\n    margin-top: 12px;\n    padding: 8px;\n}\n\n/* --- Inner definitions (Parameters, Returns) --- */\n\n/* Reset styles for nested elements (e.g. parameter lists)\n * so they DO NOT inherit the yellow background or top border.\n */\nhtml.writer-html5 .rst-content dl:not(.docutils) dl dt {\n    background-color: transparent !important;\n    border: none !important;\n    color: #333333 !important;\n    padding: 0 !important;\n    margin: 0 !important;\n    font-weight: bold;\n}\n"
  },
  {
    "path": "doc/sphinx/source/_templates/layout.html",
    "content": "{% extends \"sphinx_rtd_theme/layout.html\" %}\n{% block footer %}\n    {{ super() }}\n        <script type=\"text/javascript\" id=\"hs-script-loader\" async defer src=\"//js-eu1.hs-scripts.com/139620700.js\"></script>\n{% endblock %}\n"
  },
  {
    "path": "doc/sphinx/source/_templates/searchbox.html",
    "content": "<div role=\"search\">\n  <form id=\"rtd-search-form\" class=\"wy-form\" action=\"{{ pathto('search') }}\" method=\"get\">\n    <input type=\"text\" name=\"q\" placeholder=\"Search Documentation...\" />\n    <input type=\"hidden\" name=\"check_keywords\" value=\"yes\" />\n    <input type=\"hidden\" name=\"area\" value=\"default\" />\n  </form>\n</div>\n"
  },
  {
    "path": "doc/sphinx/source/conf.py.in",
    "content": "# -*- coding: utf-8 -*-\n#\n# Anjay documentation build configuration file, created by\n# sphinx-quickstart on Mon Aug 31 10:58:06 2015.\n#\n# This file is execfile()d with the current directory set to its\n# containing dir.\n#\n# Note that not all possible configuration values are present in this\n# autogenerated file.\n#\n# All configuration values have a default; values that are commented out\n# serve to show the default.\n\nimport datetime\nimport packaging\nimport packaging.version\nimport sys\nimport os\nimport sphinx_rtd_theme\n\n# If extensions (or modules to document with autodoc) are in another directory,\n# add these directories to sys.path here. If the directory is relative to the\n# documentation root, use os.path.abspath to make it absolute, like shown here.\nsys.path.insert(0, '@ANJAY_SOURCE_DIR@/doc/sphinx/extensions')\n\n# -- General configuration ------------------------------------------------\n\n# If your documentation needs a minimal Sphinx version, state it here.\n#needs_sphinx = '1.0'\n\n# Add any Sphinx extension module names here, as strings. They can be\n# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom\n# ones.\nextensions = ['sphinx_copybutton',\n              'snippet_source',\n              'builders.snippet_source_linter',\n              'builders.snippet_source_list_references',\n              'sphinx_design',\n              'sphinx_rtd_theme',\n              'linuxdoc.rstFlatTable',\n              'small_literal',\n              'sphinx_tabs.tabs',\n              'sphinx_sitemap']\n\n# Add any paths that contain templates here, relative to this directory.\ntemplates_path = ['@CURRENT_TEMPLATES_DIR@']\n\n# The suffix of source filenames.\nsource_suffix = '.rst'\n\n# The encoding of source files.\n#source_encoding = 'utf-8-sig'\n\n# The master toctree document.\nmaster_doc = 'index'\n\n# General information about the project.\nproject = 'Anjay'\ncopyright = f'{datetime.datetime.now().year}, AVSystem'\nauthor = 'AVSystem'\n\n# The version info for the project you're documenting, acts as replacement for\n# |version| and |release|, also used in various other places throughout the\n# built documents.\n#\n# The short X.Y version.\nversion = '@ANJAY_VERSION@'\n# The full version, including alpha/beta/rc tags.\nrelease = version\n\n# The language for content autogenerated by Sphinx. Refer to documentation\n# for a list of supported languages.\n#language = None\n\n# There are two options for replacing |today|: either, you set today to some\n# non-false value, then it is used:\n#today = ''\n# Else, today_fmt is used as the format for a strftime call.\n#today_fmt = '%B %d, %Y'\n\n# List of patterns, relative to source directory, that match files and\n# directories to ignore when looking for source files.\nexclude_patterns = []\n\n# The reST default role (used for this markup: `text`) to use for all\n# documents.\n#default_role = None\n\n# If true, '()' will be appended to :func: etc. cross-reference text.\n#add_function_parentheses = True\n\n# If true, the current module name will be prepended to all description\n# unit titles (such as .. function::).\n#add_module_names = True\n\n# If true, sectionauthor and moduleauthor directives will be shown in the\n# output. They are ignored by default.\n#show_authors = False\n\n# The name of the Pygments (syntax highlighting) style to use.\npygments_style = 'sphinx'\n\n# A list of ignored prefixes for module index sorting.\n#modindex_common_prefix = []\n\n# If true, keep warnings as \"system message\" paragraphs in the built documents.\n#keep_warnings = False\n\n\n# -- Options for HTML output ----------------------------------------------\n\nif packaging.version.parse(sphinx_rtd_theme.__version__) < packaging.version.parse('0.4.3'):\n    raise Exception('Anjay requires sphinx_rtd_theme >=0.4.3 for proper styling')\n\n# The theme to use for HTML and HTML Help pages.  See the documentation for\n# a list of builtin themes.\nhtml_theme = 'sphinx_rtd_theme'\n\n# Theme options are theme-specific and customize the look and feel of a theme\n# further.  For a list of options available for each theme, see the\n# documentation.\nhtml_theme_options = {\n    'logo_only': True,\n    'style_nav_header_background': '#ffd500'\n}\n\n# Add any paths that contain custom themes here, relative to this directory.\nhtml_theme_path = [sphinx_rtd_theme.get_html_theme_path()]\n\n# The name for this set of Sphinx documents.  If None, it defaults to\n# \"<project> v<release> documentation\".\n#html_title = None\n\n# A shorter title for the navigation bar.  Default is the same as html_title.\n#html_short_title = None\n\n# The name of an image file (relative to this directory) to place at the top\n# of the sidebar.\nhtml_logo = '@ANJAY_DOC_SOURCE_MAIN@/avsystem_header.png'\n\n# The name of an image file (within the static path) to use as favicon of the\n# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32\n# pixels large.\n#html_favicon = None\n\n# Add any paths that contain custom static files (such as style sheets) here,\n# relative to this directory. They are copied after the builtin static files,\n# so a file named \"default.css\" will overwrite the builtin \"default.css\".\nhtml_static_path = ['@ANJAY_SOURCE_DIR@/doc/sphinx/source/_static']\n\n# This is necessary to fix issues with tables in \"Read the Docs\" style.\nhtml_css_files = [\n    'theme_overrides.css'\n    ]\n\n# Add any extra paths that contain custom files (such as robots.txt or\n# .htaccess) here, relative to this directory. These files are copied\n# directly to the root of the documentation.\n#html_extra_path = []\n\n# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,\n# using the given strftime format.\n#html_last_updated_fmt = '%b %d, %Y'\n\n# If true, SmartyPants will be used to convert quotes and dashes to\n# typographically correct entities.\n#html_use_smartypants = True\n\n# Custom sidebar templates, maps document names to template names.\n#html_sidebars = {}\n\n# Additional templates that should be rendered to pages, maps page names to\n# template names.\n#html_additional_pages = {}\n\n# If false, no module index is generated.\n#html_domain_indices = True\n\n# If false, no index is generated.\n#html_use_index = True\n\n# If true, the index is split into individual pages for each letter.\n#html_split_index = False\n\n# If true, links to the reST sources are added to the pages.\nhtml_show_sourcelink = False\n\n# If true, \"Created using Sphinx\" is shown in the HTML footer. Default is True.\nhtml_show_sphinx = False\n\n# If true, \"(C) Copyright ...\" is shown in the HTML footer. Default is True.\n#html_show_copyright = True\n\n# If true, an OpenSearch description file will be output, and all pages will\n# contain a <link> tag referring to it.  The value of this option must be the\n# base URL from which the finished HTML is served.\n#html_use_opensearch = ''\n\n# This is the file name suffix for HTML files (e.g. \".xhtml\").\n#html_file_suffix = None\n\n# Output file base name for HTML help builder.\nhtmlhelp_basename = 'Anjaydoc'\n\n\n# -- Options for LaTeX output ---------------------------------------------\n\nlatex_elements = {\n# The paper size ('letterpaper' or 'a4paper').\n#'papersize': 'letterpaper',\n\n# The font size ('10pt', '11pt' or '12pt').\n#'pointsize': '10pt',\n\n# Additional stuff for the LaTeX preamble.\n#'preamble': '',\n}\n\n# Grouping the document tree into LaTeX files. List of tuples\n# (source start file, target name, title,\n#  author, documentclass [howto, manual, or own class]).\nlatex_documents = [\n  ('index', 'Anjay.tex', u'Anjay Documentation',\n   u'AVSystem', 'manual'),\n]\n\n# The name of an image file (relative to this directory) to place at the top of\n# the title page.\n#latex_logo = None\n\n# For \"manual\" documents, if this is true, then toplevel headings are parts,\n# not chapters.\n#latex_use_parts = False\n\n# If true, show page references after internal links.\n#latex_show_pagerefs = False\n\n# If true, show URL addresses after external links.\n#latex_show_urls = False\n\n# Documents to append as an appendix to all manuals.\n#latex_appendices = []\n\n# If false, no module index is generated.\n#latex_domain_indices = True\n\n\n# -- Options for manual page output ---------------------------------------\n\n# One entry per manual page. List of tuples\n# (source start file, name, description, authors, manual section).\nman_pages = [\n    ('index', 'anjay', u'Anjay Documentation',\n     [u'AVSystem'], 1)\n]\n\n# If true, show URL addresses after external links.\n#man_show_urls = False\n\n\n# -- Options for Texinfo output -------------------------------------------\n\n# Grouping the document tree into Texinfo files. List of tuples\n# (source start file, target name, title, author,\n#  dir menu entry, description, category)\ntexinfo_documents = [\n  ('index', 'Anjay', u'Anjay Documentation',\n   u'AVSystem', 'Anjay', 'One line description of project.',\n   'Miscellaneous'),\n]\n\n# Documents to append as an appendix to all manuals.\n#texinfo_appendices = []\n\n# If false, no module index is generated.\n#texinfo_domain_indices = True\n\n# How to display URL addresses: 'footnote', 'no', or 'inline'.\n#texinfo_show_urls = 'footnote'\n\n# If true, do not generate a @detailmenu in the \"Top\" node's menu.\n#texinfo_no_detailmenu = False\n\n# sitemap required url\nhtml_baseurl = 'https://docs.avsystem.com/hubfs/Anjay_Docs/'\nsitemap_url_scheme = \"{link}\"\n"
  },
  {
    "path": "doc/sphinx/source/index.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nWelcome to Anjay LwM2M library documentation!\n=============================================\n\n.. attention::\n\n   With release of Anjay 3.0, the `library's license terms have changed\n   <https://github.com/AVSystem/Anjay/blob/master/LICENSE>`_. Please make sure\n   that you have reviewed it before updating to the new major release. Previous\n   versions of Anjay remain with the old, Apache 2.0 license.\n\nContents:\n\n.. toctree::\n   :numbered:\n   :titlesonly:\n\n   Introduction\n   LwM2M\n   Compiling_client_applications\n   BasicClient\n   AdvancedTopics\n   FirmwareUpdateTutorial\n   LwM2MGateway\n   Tools\n   KnownIssues\n   PortingGuideForNonPOSIXPlatforms\n   Migrating\n   CommercialFeatures\n\n.. toctree::\n   :titlesonly:\n\n   APIReference\n\nLinks\n-----\n\n* `Source repository <https://github.com/AVSystem/Anjay>`_\n\nIndices and tables\n==================\n\n* :ref:`search`\n\n"
  },
  {
    "path": "doc/sphinx/source_api/BackToDocumentation.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nBack to documentation\n=====================\n\n.. meta::\n   :http-equiv=Refresh: 0; url=../index.html\n\n.. raw:: html\n\n   <script type=\"text/javascript\">\n       window.location.href = \"../index.html\";\n   </script>\n\nIf you are not redirected automatically, follow this link to the `Documentation <../index.html>`_.\n"
  },
  {
    "path": "doc/sphinx/source_api/_static/theme_overrides.css",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n/**\n * See: http://rackerlabs.github.io/docs-rackspace/tools/rtd-tables.html\n */\n\n/* override table width restrictions */\n@media screen and (min-width: 767px) {\n\n   .wy-table-responsive table td {\n      /* !important prevents the common CSS stylesheets from overriding\n         this as on RTD they are loaded after this stylesheet */\n      white-space: normal !important;\n   }\n\n   .wy-table-responsive {\n      overflow: visible !important;\n   }\n\n   /* https://stackoverflow.com/questions/23211695/modifying-content-width-of-the-sphinx-theme-read-the-docs */\n   .wy-nav-content {\n        max-width: 960px !important;\n    }\n}\n\n/* AVSystem color scheme */\n.wy-side-nav-search>a {\n    color: #000 !important;\n}\n\n.wy-side-nav-search>a:after {\n    content: \"Anjay\";\n}\n\n.wy-side-nav-search>div.version {\n    color: #000;\n}\n\n.wy-side-nav-search input[type=text] {\n    border-radius: 0 !important;\n    border: none !important;\n    box-shadow: none !important;\n}\n\n.wy-menu-vertical a:active {\n    background-color: #ffd500;\n    color: #000;\n}\n\na {\n    color: #000;\n    text-decoration: underline;\n    text-decoration-thickness: 1px;\n}\n\n.wy-nav-content a:visited,\n.wy-nav-content a:hover {\n    color: #8a8a8a;\n}\n\nnav a {\n    text-decoration: none !important;\n}\n\n.highlight {\n    background: #ffffff;\n}\n\n.highlight .hll {\n    background: #f3f6f6;\n}\n\ncode.small-literal {\n    font-size: 65%;\n}\n\n/* --- Doxygen/Breathe API Styling Overrides --- */\n\n/* Use this extremely specific selector to ensure we override \n * the default sphinx_rtd_theme styles regardless of the CSS loading order.\n */\nhtml.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) > dt {\n\n    /* BACKGROUND: Light yellow instead of the default blue */\n    background: #fffdf0 !important; \n\n    /* BORDER: Yellow accent line on top */\n    border-top: 3px solid #ffd500 !important; \n    \n    /* TEXT: Dark text for better contrast */\n    color: #333333 !important;\n    \n    /* Optional: cosmetic adjustments */\n    margin-top: 12px;\n    padding: 8px;\n}\n\n/* --- Inner definitions (Parameters, Returns) --- */\n\n/* Reset styles for nested elements (e.g. parameter lists)\n * so they DO NOT inherit the yellow background or top border.\n */\nhtml.writer-html5 .rst-content dl:not(.docutils) dl dt {\n    background-color: transparent !important;\n    border: none !important;\n    color: #333333 !important;\n    padding: 0 !important;\n    margin: 0 !important;\n    font-weight: bold;\n}\n"
  },
  {
    "path": "doc/sphinx/source_api/_templates/layout.html",
    "content": "{% extends \"sphinx_rtd_theme/layout.html\" %}\n{% block footer %}\n    {{ super() }}\n        <script type=\"text/javascript\" id=\"hs-script-loader\" async defer src=\"//js-eu1.hs-scripts.com/139620700.js\"></script>\n{% endblock %}\n"
  },
  {
    "path": "doc/sphinx/source_api/_templates/searchbox.html",
    "content": "<div role=\"search\">\n  <form id=\"rtd-search-form\" class=\"wy-form\" action=\"{{ pathto('search') }}\" method=\"get\">\n    <input type=\"text\" name=\"q\" placeholder=\"Search API...\" />\n    <input type=\"hidden\" name=\"check_keywords\" value=\"yes\" />\n    <input type=\"hidden\" name=\"area\" value=\"default\" />\n  </form>\n</div>\n"
  },
  {
    "path": "doc/sphinx/source_api/conf.py.in",
    "content": "# -*- coding: utf-8 -*-\n#\n# Anjay documentation build configuration file, created by\n# sphinx-quickstart on Mon Aug 31 10:58:06 2015.\n#\n# This file is execfile()d with the current directory set to its\n# containing dir.\n#\n# Note that not all possible configuration values are present in this\n# autogenerated file.\n#\n# All configuration values have a default; values that are commented out\n# serve to show the default.\n\nimport datetime\nimport packaging\nimport packaging.version\nimport sys\nimport os\nimport sphinx_rtd_theme\n\n# If extensions (or modules to document with autodoc) are in another directory,\n# add these directories to sys.path here. If the directory is relative to the\n# documentation root, use os.path.abspath to make it absolute, like shown here.\nsys.path.insert(0, '@ANJAY_SOURCE_DIR@/doc/sphinx/extensions')\n\n# -- General configuration ------------------------------------------------\n\n# If your documentation needs a minimal Sphinx version, state it here.\n#needs_sphinx = '1.0'\n\n# Add any Sphinx extension module names here, as strings. They can be\n# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom\n# ones.\nextensions = ['snippet_source',\n              'builders.snippet_source_linter',\n              'builders.snippet_source_list_references',\n              'sphinx_rtd_theme',\n              'linuxdoc.rstFlatTable',\n              'small_literal',\n              'sphinx_tabs.tabs',\n              'breathe',\n              'exhale',\n              'sphinx_sitemap']\n\n# Add any paths that contain templates here, relative to this directory.\ntemplates_path = ['@CURRENT_TEMPLATES_DIR@']\n\n# The suffix of source filenames.\nsource_suffix = '.rst'\n\n# The encoding of source files.\n#source_encoding = 'utf-8-sig'\n\n# The master toctree document.\nmaster_doc = 'index'\n\n# General information about the project.\nproject = 'Anjay'\ncopyright = f'{datetime.datetime.now().year}, AVSystem'\nauthor = 'AVSystem'\n\n# The version info for the project you're documenting, acts as replacement for\n# |version| and |release|, also used in various other places throughout the\n# built documents.\n#\n# The short X.Y version.\nversion = '@ANJAY_VERSION@'\n# The full version, including alpha/beta/rc tags.\nrelease = version\n\n# The language for content autogenerated by Sphinx. Refer to documentation\n# for a list of supported languages.\n#language = None\n\n# There are two options for replacing |today|: either, you set today to some\n# non-false value, then it is used:\n#today = ''\n# Else, today_fmt is used as the format for a strftime call.\n#today_fmt = '%B %d, %Y'\n\n# List of patterns, relative to source directory, that match files and\n# directories to ignore when looking for source files.\nexclude_patterns = []\n\n# The reST default role (used for this markup: `text`) to use for all\n# documents.\n#default_role = None\n\n# If true, '()' will be appended to :func: etc. cross-reference text.\n#add_function_parentheses = True\n\n# If true, the current module name will be prepended to all description\n# unit titles (such as .. function::).\n#add_module_names = True\n\n# If true, sectionauthor and moduleauthor directives will be shown in the\n# output. They are ignored by default.\n#show_authors = False\n\n# The name of the Pygments (syntax highlighting) style to use.\npygments_style = 'sphinx'\n\n# A list of ignored prefixes for module index sorting.\n#modindex_common_prefix = []\n\n# If true, keep warnings as \"system message\" paragraphs in the built documents.\n#keep_warnings = False\n\n\n# -- Options for HTML output ----------------------------------------------\n\nif packaging.version.parse(sphinx_rtd_theme.__version__) < packaging.version.parse('0.4.3'):\n    raise Exception('Anjay requires sphinx_rtd_theme >=0.4.3 for proper styling')\n\n# The theme to use for HTML and HTML Help pages.  See the documentation for\n# a list of builtin themes.\nhtml_theme = 'sphinx_rtd_theme'\n\n# Theme options are theme-specific and customize the look and feel of a theme\n# further.  For a list of options available for each theme, see the\n# documentation.\nhtml_theme_options = {\n    'logo_only': True,\n    'style_nav_header_background': '#ffd500'\n}\n\n# Add any paths that contain custom themes here, relative to this directory.\nhtml_theme_path = [sphinx_rtd_theme.get_html_theme_path()]\n\n# The name for this set of Sphinx documents.  If None, it defaults to\n# \"<project> v<release> documentation\".\n#html_title = None\n\n# A shorter title for the navigation bar.  Default is the same as html_title.\n#html_short_title = None\n\n# The name of an image file (relative to this directory) to place at the top\n# of the sidebar.\nhtml_logo = '@ANJAY_DOC_SOURCE_MAIN@/avsystem_header.png'\n\n# The name of an image file (within the static path) to use as favicon of the\n# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32\n# pixels large.\n#html_favicon = None\n\n# Add any paths that contain custom static files (such as style sheets) here,\n# relative to this directory. They are copied after the builtin static files,\n# so a file named \"default.css\" will overwrite the builtin \"default.css\".\nhtml_static_path = ['@ANJAY_SOURCE_DIR@/doc/sphinx/source/_static']\n\n# This is necessary to fix issues with tables in \"Read the Docs\" style.\nhtml_css_files = [\n    'theme_overrides.css'\n    ]\n\n# Add any extra paths that contain custom files (such as robots.txt or\n# .htaccess) here, relative to this directory. These files are copied\n# directly to the root of the documentation.\n#html_extra_path = []\n\n# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,\n# using the given strftime format.\n#html_last_updated_fmt = '%b %d, %Y'\n\n# If true, SmartyPants will be used to convert quotes and dashes to\n# typographically correct entities.\n#html_use_smartypants = True\n\n# Custom sidebar templates, maps document names to template names.\n#html_sidebars = {}\n\n# Additional templates that should be rendered to pages, maps page names to\n# template names.\n#html_additional_pages = {}\n\n# If false, no module index is generated.\n#html_domain_indices = True\n\n# If false, no index is generated.\n#html_use_index = True\n\n# If true, the index is split into individual pages for each letter.\n#html_split_index = False\n\n# If true, links to the reST sources are added to the pages.\nhtml_show_sourcelink = False\n\n# If true, \"Created using Sphinx\" is shown in the HTML footer. Default is True.\nhtml_show_sphinx = False\n\n# If true, \"(C) Copyright ...\" is shown in the HTML footer. Default is True.\n#html_show_copyright = True\n\n# If true, an OpenSearch description file will be output, and all pages will\n# contain a <link> tag referring to it.  The value of this option must be the\n# base URL from which the finished HTML is served.\n#html_use_opensearch = ''\n\n# This is the file name suffix for HTML files (e.g. \".xhtml\").\n#html_file_suffix = None\n\n# Output file base name for HTML help builder.\nhtmlhelp_basename = 'Anjaydoc'\n\n\n# -- Options for LaTeX output ---------------------------------------------\n\nlatex_elements = {\n# The paper size ('letterpaper' or 'a4paper').\n#'papersize': 'letterpaper',\n\n# The font size ('10pt', '11pt' or '12pt').\n#'pointsize': '10pt',\n\n# Additional stuff for the LaTeX preamble.\n#'preamble': '',\n}\n\n# Grouping the document tree into LaTeX files. List of tuples\n# (source start file, target name, title,\n#  author, documentclass [howto, manual, or own class]).\nlatex_documents = [\n  ('index', 'Anjay.tex', u'Anjay Documentation',\n   u'AVSystem', 'manual'),\n]\n\n# The name of an image file (relative to this directory) to place at the top of\n# the title page.\n#latex_logo = None\n\n# For \"manual\" documents, if this is true, then toplevel headings are parts,\n# not chapters.\n#latex_use_parts = False\n\n# If true, show page references after internal links.\n#latex_show_pagerefs = False\n\n# If true, show URL addresses after external links.\n#latex_show_urls = False\n\n# Documents to append as an appendix to all manuals.\n#latex_appendices = []\n\n# If false, no module index is generated.\n#latex_domain_indices = True\n\n\n# -- Options for manual page output ---------------------------------------\n\n# One entry per manual page. List of tuples\n# (source start file, name, description, authors, manual section).\nman_pages = [\n    ('index', 'anjay', u'Anjay Documentation',\n     [u'AVSystem'], 1)\n]\n\n# If true, show URL addresses after external links.\n#man_show_urls = False\n\n\n# -- Options for Texinfo output -------------------------------------------\n\n# Grouping the document tree into Texinfo files. List of tuples\n# (source start file, target name, title, author,\n#  dir menu entry, description, category)\ntexinfo_documents = [\n  ('index', 'Anjay', u'Anjay Documentation',\n   u'AVSystem', 'Anjay', 'One line description of project.',\n   'Miscellaneous'),\n]\n\n# Documents to append as an appendix to all manuals.\n#texinfo_appendices = []\n\n# If false, no module index is generated.\n#texinfo_domain_indices = True\n\n# How to display URL addresses: 'footnote', 'no', or 'inline'.\n#texinfo_show_urls = 'footnote'\n\n# If true, do not generate a @detailmenu in the \"Top\" node's menu.\n#texinfo_no_detailmenu = False\n\n# -- Options for Breathe --------------------------------------------------\nbreathe_projects = {\n    \"Anjay\": \"@ANJAY_SOURCE_DIR@/output/doc/doxygen/xml\"\n}\nbreathe_default_project = \"Anjay\"\n\n# -- Options for Exhale ----------------------------------------------------\nexhale_args = {\n    # Where Exhale should generate the .rst files (inside source_api)\n    \"containmentFolder\":     \"@ANJAY_DOC_SOURCE_API@/api_generated\",\n    \n    # Name of the main file that serves as the API root\n    \"rootFileName\":          \"library_root.rst\",\n    \n    # The title of the section in the menu\n    \"rootFileTitle\":         \"Anjay API Library\",\n    \n    # Description in the main file (optional)\n    \"afterTitleDescription\":   \"Full API documentation generated from Doxygen.\",\n    \n    # Creates a tree-like menu structure (similar to Zephyr documentation)\n    \"createTreeView\":        True,\n    \n    # Important: Tells Exhale what to \"strip\" from paths to ensure cleaner names\n    \"doxygenStripFromPath\":  \"@DOXYGEN_INPUT_PATHS@/include_public\",\n    \n    # Section layout configuration\n    \"exhaleExecutesDoxygen\": False, # Set to False because CMake handles Doxygen execution\n    #\"listingExclude\":     \"\",    # Do not add prefixes like \"file\" or \"struct\" to titles\n}\n\n# Template fix (sometimes required for Exhale + RTD Theme)\nprimary_domain = 'c'\nhighlight_language = 'c'\n\n# sitemap required url\nhtml_baseurl = 'https://docs.avsystem.com/hubfs/Anjay_Docs/api/'\nsitemap_url_scheme = \"{link}\"\n"
  },
  {
    "path": "doc/sphinx/source_api/index.rst",
    "content": "..\n   Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n   AVSystem Anjay LwM2M SDK\n   All rights reserved.\n\n   Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n   See the attached LICENSE file for details.\n\nWelcome to Anjay API reference!\n===============================\n\nComplete reference documentation for the Anjay C API\n\nContents:\n\n.. toctree::\n   :maxdepth: 2\n\n   api_generated/library_root\n   BackToDocumentation\n\n"
  },
  {
    "path": "example_configs/README.md",
    "content": "# Example configurations for Anjay\n\nEach of the subdirectories here contains a complete configuration that can be used when compiling Anjay without the use of CMake. They are intended as a starting point for custom configurations, but can also be used to compile Anjay without modifications.\n\nYou can use them by copying the contents of one of the directories to some location used as an include path by your compiler (e.g. `-I...` argument on most command-line compilers), or specify it as an include path directly (e.g. `-I$ANJAY_DIR/example_configs/linux_lwm2m11`, provided that Anjay has been downloaded into `$ANJAY_DIR`).\n\n## LwM2M 1.2 configurations\n\n* `linux_lwm2m12` - equivalent to `linux_lwm2m11` configuration, but with LwM2M 1.2-specific features enabled.\n* `embedded_lwm2m12` - equivalent to `embedded_lwm2m11` configuration, but with LwM2M 1.2-specific features enabled.\n\n## LwM2M 1.1 configurations (recommended starting point)\n\n* `linux_lwm2m11` - equivalent to the default configuration of CMake (i.e., running `cmake .`) on a typical modern desktop Linux system.\n  * As-is, it should be usable on most Linux-based systems for 32- and 64-bit little-endian architectures, with GCC as the compiler.\n  * Also likely to work on other Unix-like systems (*BSD, macOS, etc.) using GCC or Clang as the compiler with little to no modifications.\n  * Depends on Mbed TLS for security and PThreads for synchronization primitives.\n* `embedded_lwm2m11` - starting configuration for embedded devices\n  * Configured with any dependency on compiler extensions disabled - it should be compilable with a wide variety of compilers.\n  * Various buffer sizes are reduced from the defaults to reduce resource consumption.\n  * Depends on Mbed TLS for security.\n  * lwIP is used by default as the network stack.\n  * Requires user-provided implementations for `avs_compat_threading`, as well as `avs_time_real_now()` and `avs_time_monotonic_now()` functions.\n\n## LwM2M 1.0 configurations\n\n* `linux_lwm2m10` - equivalent to `linux_lwm2m11` configuration, but with LwM2M 1.1-specific features disabled.\n* `embedded_lwm2m10` - equivalent to `embedded_lwm2m11` configuration, but with LwM2M 1.1-specific features disabled.\n"
  },
  {
    "path": "example_configs/embedded_lwm2m10/anjay/anjay_config.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ANJAY_CONFIG_H\n#define ANJAY_CONFIG_H\n\n/**\n * @file anjay_config.h\n *\n * Anjay library configuration.\n *\n * The preferred way to compile Anjay is to use CMake, in which case this file\n * will be generated automatically by CMake.\n *\n * However, to provide compatibility with various build systems used especially\n * by embedded platforms, it is alternatively supported to compile Anjay by\n * other means, in which case this file will need to be provided manually.\n *\n * <strong>NOTE: To compile this library without using CMake, you need to\n * configure avs_commons and avs_coap first. Please refer to documentation in\n * the <c>avs_commons_config.h</c> and <c>avs_coap_config.h</c> files (provided\n * in the repositories as <c>avs_commons_config.h.in</c> and\n * <c>avs_coap_config.h.in</c>, respectively) for details.</strong>\n *\n * <strong>Anjay requires the following avs_coap options to be enabled:</strong>\n *\n * - @c WITH_AVS_COAP_UDP and/or @c WITH_AVS_COAP_TCP\n * - @c WITH_AVS_COAP_STREAMING_API\n * - @c WITH_AVS_COAP_BLOCK is highly recommended\n * - @c WITH_AVS_COAP_OBSERVE (if @c ANJAY_WITH_OBSERVE is enabled)\n * - @c WITH_AVS_COAP_OSCORE (if @c ANJAY_WITH_COAP_OSCORE is enabled, available\n *   only as a commercial feature)\n *\n * <strong>Anjay requires the following avs_commons components to be\n * enabled:</strong>\n *\n * - All components required by avs_coap, see <c>avs_coap_config.h</c>\n * - @c avs_algorithm\n * - @c avs_stream\n * - @c avs_url\n * - @c avs_persistence is highly recommended\n * - @c avs_http (if @c ANJAY_WITH_HTTP_DOWNLOAD is enabled)\n * - @c avs_rbtree or @c avs_sorted_set (if @c ANJAY_WITH_OBSERVE or\n *   @c ANJAY_WITH_MODULE_ACCESS_CONTROL is enabled)\n *\n * In the repository, this file is provided as <c>anjay_config.h.in</c>,\n * intended to be processed by CMake. If editing this file manually, please copy\n * or rename it to <c>anjay_config.h</c> and for each of the\n * <c>\\#cmakedefine</c> directives, please either replace it with regular\n * <c>\\#define</c> to enable it, or comment it out to disable. You may also need\n * to replace variables wrapped in <c>\\@</c> signs with concrete values. Please\n * refer to the comments above each of the specific definition for details.\n *\n * If you are editing a file previously generated by CMake, these\n * <c>\\#cmakedefine</c>s will be already replaced by either <c>\\#define</c> or\n * commented out <c>\\#undef</c> directives.\n */\n\n/**\n * Enable logging in Anjay.\n *\n * If this flag is disabled, no logging is compiled into the binary at all.\n */\n#define ANJAY_WITH_LOGS\n\n/**\n * Enable TRACE-level logs in Anjay.\n *\n * Only meaningful if <c>ANJAY_WITH_LOGS</c> is enabled.\n */\n/* #undef ANJAY_WITH_TRACE_LOGS */\n\n/**\n * Enable core support for Access Control mechanisms.\n *\n * Requires separate implementation of the Access Control object itself.\n * Either the implementation available as part of\n * <c>ANJAY_WITH_MODULE_ACCESS_CONTROL</c>, or a custom application-provided one\n * may be used.\n */\n/* #undef ANJAY_WITH_ACCESS_CONTROL */\n\n/**\n * Enable automatic attribute storage.\n *\n * Requires <c>AVS_COMMONS_WITH_AVS_PERSISTENCE</c> to be enabled in avs_commons\n * configuration.\n */\n#define ANJAY_WITH_ATTR_STORAGE\n\n/**\n * Enable support for the <c>anjay_download()</c> API.\n */\n#define ANJAY_WITH_DOWNLOADER\n\n/**\n * Enable support for CoAP(S) downloads.\n *\n * Only meaningful if <c>ANJAY_WITH_DOWNLOADER</c> is enabled.\n */\n#define ANJAY_WITH_COAP_DOWNLOAD\n\n/**\n * Enable support for HTTP(S) downloads.\n *\n * Only meaningful if <c>ANJAY_WITH_DOWNLOADER</c> is enabled.\n */\n/* #undef ANJAY_WITH_HTTP_DOWNLOAD */\n\n/**\n * Enable support for the LwM2M Bootstrap Interface.\n */\n#define ANJAY_WITH_BOOTSTRAP\n\n/**\n * Enable support for the LwM2M Bootstrap-Pack operation.\n *\n * Requires <c>ANJAY_WITH_BOOTSTRAP</c> and <c>ANJAY_WITH_LWM2M12</c> to be\n * enabled.\n */\n/* #undef ANJAY_WITH_BOOTSTRAP_PACK */\n\n/**\n * Enable support for the LwM2M Discover operation.\n *\n * Note that it is required for full compliance with the LwM2M protocol.\n */\n#define ANJAY_WITH_DISCOVER\n\n/**\n * Enable support for the LwM2M Information Reporting interface (Observe and\n * Notify operations).\n *\n * Requires <c>WITH_AVS_COAP_OBSERVE</c> to be enabled in avs_coap\n * configuration.\n *\n * Note that it is required for full compliance with the LwM2M protocol.\n */\n#define ANJAY_WITH_OBSERVE\n\n/**\n * Enable support for measuring amount of LwM2M traffic\n * (<c>anjay_get_tx_bytes()</c>, <c>anjay_get_rx_bytes()</c>,\n * <c>anjay_get_num_incoming_retransmissions()</c> and\n * <c>anjay_get_num_outgoing_retransmissions()</c> APIs.\n */\n/* #undef ANJAY_WITH_NET_STATS */\n\n/**\n * Enable support for communication timestamp\n * (<c>anjay_get_server_last_registration_time()</c>\n * <c>anjay_get_server_next_update_time()</c> and\n * <c>anjay_get_server_last_communication_time()</c>) APIs.\n */\n/* #undef ANJAY_WITH_COMMUNICATION_TIMESTAMP_API */\n\n/**\n * Enable support for the <c>anjay_resource_observation_status()</c> API.\n */\n#define ANJAY_WITH_OBSERVATION_STATUS\n\n/**\n * Maximum number of servers observing a given Resource listed by\n * <c>anjay_resource_observation_status()</c> function.\n *\n * Only meaningful if <c>ANJAY_WITH_OBSERVATION_STATUS</c> is enabled.\n */\n#define ANJAY_MAX_OBSERVATION_SERVERS_REPORTED_NUMBER 0\n\n/**\n * Enable guarding of all accesses to <c>anjay_t</c> with a mutex.\n */\n/* #undef ANJAY_WITH_THREAD_SAFETY */\n\n/**\n * Enable standard implementation of an event loop.\n *\n * Requires C11 <c>stdatomic.h</c> header to be available, and either a platform\n * that provides a BSD-compatible socket API, or a compatibility layer file (see\n * <c>AVS_COMMONS_POSIX_COMPAT_HEADER</c> in <c>avs_commons_config.h</c>). This\n * is designed to best work with the defalt implementation of avs_net sockets\n * (see <c>AVS_COMMONS_NET_WITH_POSIX_AVS_SOCKET</c>), but alternative socket\n * implementations can also be used.\n *\n * The event loop is most useful when thread safety features\n * (@ref ANJAY_WITH_THREAD_SAFETY and <c>AVS_COMMONS_SCHED_THREAD_SAFE</c>) are\n * enabled as well, but this is not a hard requirement. See the documentation\n * for <c>anjay_event_loop_run()</c> for details.\n */\n#define ANJAY_WITH_EVENT_LOOP\n\n/**\n * Enable support for features new to LwM2M protocol version 1.1.\n */\n/* #undef ANJAY_WITH_LWM2M11 */\n\n/**\n * Enable support for features new to LwM2M protocol version 1.2.\n *\n * Requires <c>ANJAY_WITH_LWM2M11</c> to be enabled.\n */\n/* #undef ANJAY_WITH_LWM2M12 */\n\n/**\n * Enable support for OSCORE-based security for LwM2M connections.\n *\n * Requires <c>ANJAY_WITH_LWM2M11</c> to be enabled, and\n * <c>WITH_AVS_COAP_OSCORE</c> to be enabled in avs_coap configuration.\n *\n * IMPORTANT: Only available as part of the OSCORE commercial feature. Ignored\n * in the open source version.\n */\n/* #undef ANJAY_WITH_COAP_OSCORE */\n\n/**\n * Enable support for Observation Attributes.\n *\n * Requires <c>ANJAY_WITH_OBSERVE</c> and <c>ANJAY_WITH_LWM2M12</c> to be\n * enabled.\n */\n/* #undef ANJAY_WITH_OBSERVATION_ATTRIBUTES */\n\n/**\n * Enable support for the LwM2M Send operation.\n *\n * Requires <c>ANJAY_WITH_LWM2M11</c> and either <c>ANJAY_WITH_SENML_JSON</c> or\n * <c>ANJAY_WITH_CBOR</c> to be enabled.\n */\n/* #undef ANJAY_WITH_SEND */\n\n/**\n * Enable support for the SMS binding and the SMS trigger mechanism.\n *\n * Requires <c>WITH_AVS_COAP_UDP</c> to be enabled in avs_coap configuration.\n *\n * IMPORTANT: Only available as part of the SMS commercial feature. Ignored in\n * the open source version.\n */\n/* #undef ANJAY_WITH_SMS */\n\n/**\n * Enable support for sending and receiving multipart SMS messages.\n *\n * Requires <c>ANJAY_WITH_SMS</c> to be enabled.\n *\n * IMPORTANT: Only available as part of the SMS commercial feature. Ignored in\n * the open source version.\n */\n/* #undef ANJAY_WITH_SMS_MULTIPART */\n\n/**\n * Enable support for Non-IP Data Delivery.\n *\n * Requires <c>ANJAY_WITH_LWM2M11</c> to be enabled, and\n * <c>WITH_AVS_COAP_UDP</c> to be enabled in avs_coap configuration.\n *\n * IMPORTANT: Only available as a commercial feature. Ignored in the open\n * source version.\n */\n/* #undef ANJAY_WITH_NIDD */\n\n/**\n * Enable support for core state persistence\n * (<c>anjay_new_from_core_persistence()</c> and\n * <c>anjay_delete_with_core_persistence()</c> APIs).\n *\n * Requires <c>ANJAY_WITH_OBSERVE</c> to be enabled,\n * <c>AVS_COMMONS_WITH_AVS_PERSISTENCE</c> to be enabled in avs_commons, and\n * <c>WITH_AVS_COAP_OBSERVE_PERSISTENCE</c> to be enabled in avs_coap\n * configuration.\n *\n * IMPORTANT: Only available as a commercial feature. Ignored in the open\n * source version.\n */\n/* #undef ANJAY_WITH_CORE_PERSISTENCE */\n\n/**\n * Disable automatic closing of server connection sockets after\n * MAX_TRANSMIT_WAIT of inactivity.\n */\n/* #undef ANJAY_WITHOUT_QUEUE_MODE_AUTOCLOSE */\n\n/**\n * Enable support for CoAP Content-Format numerical values 1541-1544 that have\n * been used before final LwM2M TS 1.0.\n */\n/* #undef ANJAY_WITH_LEGACY_CONTENT_FORMAT_SUPPORT */\n\n/**\n * Enable support for JSON format as specified in LwM2M TS 1.0.\n *\n * NOTE: Anjay is only capable of generating this format, there is no parsing\n * support regardless of the state of this option.\n */\n/* #undef ANJAY_WITH_LWM2M_JSON */\n\n/**\n * Disable support for TLV format as specified in LwM2M TS 1.0.\n *\n * NOTE: LwM2M Client using LwM2M 1.0 MUST support this format. It may be\n * disabled if LwM2M 1.1 is used and SenML JSON or SenML CBOR is enabled.\n */\n/* #undef ANJAY_WITHOUT_TLV */\n\n/**\n * Disable support for Plain Text format as specified in LwM2M TS 1.0 and 1.1.\n *\n * NOTE: LwM2M Client SHOULD support this format. It may be disabled to reduce\n * library size if LwM2M Server is configured to not use it in requests.\n */\n/* #undef ANJAY_WITHOUT_PLAINTEXT */\n\n/**\n * Disable use of the Deregister message.\n */\n/* #undef ANJAY_WITHOUT_DEREGISTER */\n\n/**\n * Disable support for \"IP stickiness\", i.e. preference of the same IP address\n * when reconnecting to a server using a domain name.\n */\n/* #undef ANJAY_WITHOUT_IP_STICKINESS */\n\n/**\n * Enable support for SenML JSON format, as specified in LwM2M TS 1.1.\n *\n * NOTE: As opposed to <c>ANJAY_WITH_LWM2M_JSON</c>, both generating and parsing\n * is supported.\n *\n * Requires <c>ANJAY_WITH_LWM2M11</c> to be enabled.\n */\n/* #undef ANJAY_WITH_SENML_JSON */\n\n/**\n * Enable support for CBOR and SenML CBOR formats, as specified in LwM2M TS 1.1.\n *\n * Requires <c>ANJAY_WITH_LWM2M11</c> to be enabled.\n */\n/* #undef ANJAY_WITH_CBOR */\n\n/**\n * Enable support for Enrollment over Secure Transport.\n *\n * Requires <c>ANJAY_WITH_BOOTSTRAP</c> to be enabled.\n *\n * IMPORTANT: Only available as part of the EST commercial feature. Ignored in\n * the open source version.\n */\n/* #undef ANJAY_WITH_EST */\n\n/**\n * Enable support for hardware security engine in the EST subsystem.\n *\n * Requires <c>ANJAY_WITH_EST</c> to be enabled in Anjay configuration and\n * <c>AVS_COMMONS_WITH_AVS_CRYPTO_PKI_ENGINE</c> to be enabled in avs_commons\n * configuration.\n *\n * IMPORTANT: Only available in commercial versions that include both the EST\n * and HSM features. Ignored in versions distributed without these features.\n */\n/* #undef ANJAY_WITH_EST_ENGINE_SUPPORT */\n\n/**\n * Enable support for the Confirmable Notification attribute, as specified in\n * LwM2M TS 1.2.\n *\n * Before TS 1.2, this has been supported in Anjay as a custom extension, and\n * thus it is available independently from TS 1.2 support itself, including in\n * the open source version.\n *\n * Requires <c>ANJAY_WITH_OBSERVE</c> to be enabled.\n */\n/* #undef ANJAY_WITH_CON_ATTR */\n\n/**\n * Enable support for handling security credentials in the data model using\n * structured <c>avs_crypto</c> types.\n *\n * If the <c>security</c> module is also enabled (see @ref\n * ANJAY_WITH_MODULE_SECURITY), it also enables support for passing these\n * credentials through such structured types when adding Security object\n * instances via the @ref anjay_security_instance_t structure.\n */\n#define ANJAY_WITH_SECURITY_STRUCTURED\n\n/**\n * Maximum size in bytes supported for the \"Public Key or Identity\" resource in\n * the LwM2M Security object.\n *\n * If editing this file manually, <c>256</c> shall be\n * replaced with a positive integer literal.\n *\n * The default value defined in CMake build scripts is 2048.\n * Minimal suggested setting for low-resource builds is 256.\n */\n#define ANJAY_MAX_PK_OR_IDENTITY_SIZE 256\n\n/**\n * Maximum size in bytes supported for the \"Secret Key\" resource in the LwM2M\n * Security Object.\n *\n * If editing this file manually, <c>128</c> shall be replaced\n * with a positive integer literal.\n *\n * The default value defined in CMake build scripts is 256.\n * Minimal suggested setting for low-resource builds is 128.\n */\n#define ANJAY_MAX_SECRET_KEY_SIZE 128\n\n/**\n * Maximum length supported for stringified floating-point values.\n *\n * Used when parsing plaintext and SenML JSON content formats - when parsing a\n * floating-point value, any string of length equal or greater than this setting\n * will automatically be considered invalid, even if it could in theory be\n * parsed as a valid number.\n *\n * If editing this file manually, <c>64</c> shall be\n * replaced with a positive integer literal.\n *\n * The default value defined in CMake build scripts is 512.\n * Minimal suggested setting for low-resource builds is 64.\n */\n#define ANJAY_MAX_DOUBLE_STRING_SIZE 64\n\n/**\n * Maximum length supported for a single Uri-Path or Location-Path segment.\n *\n * When handling incoming CoAP messages, any Uri-Path or Location-Path option of\n * length equal or greater than this setting will be considered invalid.\n *\n * If editing this file manually, <c>64</c> shall be\n * replaced with a positive integer literal.\n *\n * The default value defined in CMake build scripts is 256.\n * Minimal suggested setting for low-resource builds is 64.\n */\n#define ANJAY_MAX_URI_SEGMENT_SIZE 64\n\n/**\n * Maximum length supported for a single Uri-Query segment.\n *\n * When handling incoming CoAP messages, any Uri-Query option of length equal or\n * greater than this setting will be considered invalid.\n *\n * If editing this file manually, <c>64</c> shall be\n * replaced with a positive integer literal.\n *\n * The default value defined in CMake build scripts is 256.\n * Minimal suggested setting for low-resource builds is 64.\n */\n#define ANJAY_MAX_URI_QUERY_SEGMENT_SIZE 64\n\n/**\n * Size of buffer allocated for storing DTLS session state when connection is\n * not in use (e.g. during queue mode operation).\n *\n * If editing this file manually, <c>1024</c> shall be\n * replaced with a positive integer literal. The default value defined in CMake\n * build scripts is 1024.\n */\n#define ANJAY_DTLS_SESSION_BUFFER_SIZE 1024\n\n/**\n * Value of Content-Format used in Send messages. Only a few specific values are\n * supported:\n *\n * - @c AVS_COAP_FORMAT_NONE means no default value is used and Anjay will\n *   decide the format based on the what is available.\n * - @c AVS_COAP_FORMAT_OMA_LWM2M_CBOR Anjay will generate a Send message in\n *   LwM2M CBOR format.\n * - @c AVS_COAP_FORMAT_SENML_CBOR Anjay will generate a Send message in SenML\n *   CBOR format.\n * - @c AVS_COAP_FORMAT_SENML_JSON Anjay will generate a Send message in SenML\n *   JSON format.\n *\n * Note that to use a specific format it must be available during compilation.\n *\n * The default value defined in CMake build scripts is\n * <c>AVS_COAP_FORMAT_NONE</c>.\n */\n#define ANJAY_DEFAULT_SEND_FORMAT AVS_COAP_FORMAT_NONE\n\n/**\n * Optional Anjay modules.\n */\n/**@{*/\n/**\n * Enable access_control module (implementation of the Access Control object).\n *\n * Requires <c>ANJAY_WITH_ACCESS_CONTROL</c> to be enabled.\n */\n/* #undef ANJAY_WITH_MODULE_ACCESS_CONTROL */\n\n/**\n * Enable security module (implementation of the LwM2M Security object).\n */\n#define ANJAY_WITH_MODULE_SECURITY\n\n/**\n * Enable support for hardware security engine in the security module.\n *\n * This feature allows security credentials provisioned into the LwM2M Security\n * object to be automatically moved into a hardware security module.\n *\n * Requires <c>ANJAY_WITH_MODULE_SECURITY</c> to be enabled in Anjay\n * configuration, and at least one of\n * <c>AVS_COMMONS_WITH_AVS_CRYPTO_PKI_ENGINE</c> or\n * <c>AVS_COMMONS_WITH_AVS_CRYPTO_PSK_ENGINE</c> to be enabled in avs_commons\n * configuration.\n *\n * IMPORTANT: Only available as part of the HSM support commercial feature.\n * Ignored in versions distributed without this feature.\n */\n/* #undef ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT */\n\n/**\n * Enable server module (implementation of the LwM2M Server object).\n */\n#define ANJAY_WITH_MODULE_SERVER\n\n/**\n * Enable fw_update module (implementation of the Firmware Update object).\n */\n#define ANJAY_WITH_MODULE_FW_UPDATE\n\n/**\n * Enable advanced_fw_update module (implementation of the 33629 custom\n * Advanced Firmware Update object).\n */\n/* #undef ANJAY_WITH_MODULE_ADVANCED_FW_UPDATE */\n\n/**\n * Disable support for PUSH mode Firmware Update.\n *\n * Only meaningful if <c>ANJAY_WITH_MODULE_FW_UPDATE</c> is enabled. Requires\n * <c>ANJAY_WITH_DOWNLOADER</c> to be enabled.\n */\n/* #undef ANJAY_WITHOUT_MODULE_FW_UPDATE_PUSH_MODE */\n\n/**\n * Enable sw_mgmt module (implementation of the Software Management object).\n */\n/* #undef ANJAY_WITH_MODULE_SW_MGMT */\n\n/**\n * Enables ipso_objects module (generic implementation of basic sensor, three\n * axis sensor and Push Button IPSO objects).\n */\n#define ANJAY_WITH_MODULE_IPSO_OBJECTS\n\n/**\n * Enables experimental ipso_objects_v2 module (generic implementation of basic\n * sensor and three axis sensor IPSO objects).\n */\n#define ANJAY_WITH_MODULE_IPSO_OBJECTS_V2\n\n/**\n * Enable at_sms module (implementation of an SMS driver for AT modem devices).\n *\n * Requires <c>ANJAY_WITH_SMS</c> to be enabled and the operating system to\n * support the POSIX <c>poll()</c> function.\n *\n * IMPORTANT: Only available as part of the SMS commercial feature. Ignored in\n * the open source version.\n */\n/* #undef ANJAY_WITH_MODULE_AT_SMS */\n\n/**\n * Enable bg96_nidd module (implementation of NB-IoT-based NIDD driver for\n * Quectel BG96).\n *\n * Requires <c>ANJAY_WITH_NIDD</c> to be enabled.\n *\n * IMPORTANT: Only available as part of the NIDD commercial feature. Ignored\n * in the open source version.\n */\n/* #undef ANJAY_WITH_MODULE_BG96_NIDD */\n\n/**\n * Enable bootstrapper module (loader for bootstrap information formatted as per\n * the \"Storage of LwM2M Bootstrap Information on the Smartcard\" appendix to the\n * LwM2M TS).\n *\n * Requires <c>ANJAY_WITH_BOOTSTRAP</c> to be enabled and\n * <c>AVS_COMMONS_WITH_AVS_PERSISTENCE</c> to be enabled in avs_commons\n * configuration.\n *\n * IMPORTANT: Only available with the bootstrapper feature. Ignored in the open\n * source version.\n */\n/* #undef ANJAY_WITH_MODULE_BOOTSTRAPPER */\n\n/**\n * Enable the SIM bootstrap module, which enables reading the SIM bootstrap\n * information from a smartcard, which can then be passed through to the\n * bootstrapper module.\n *\n * Requires <c>ANJAY_WITH_MODULE_BOOTSTRAPPER</c> to be enabled.\n *\n * IMPORTANT: Only available with the bootstrapper feature. Ignored in the open\n * source version.\n */\n/* #undef ANJAY_WITH_MODULE_SIM_BOOTSTRAP */\n\n/**\n * Forced ID of the file to read the SIM bootstrap information from.\n *\n * If not defined (default), the bootstrap information file will be discovered\n * through the ODF file, as mandated by the specification.\n *\n * Requires <c>ANJAY_WITH_MODULE_BOOTSTRAPPER</c> to be enabled. At most one of\n * <c>ANJAY_MODULE_SIM_BOOTSTRAP_HARDCODED_FILE_ID</c> and\n * <c>ANJAY_MODULE_SIM_BOOTSTRAP_DATA_OID_OVERRIDE_HEX</c> may be defined at the\n * same time.\n *\n * IMPORTANT: Only available with the bootstrapper feature. Ignored in the open\n * source version.\n */\n/* #undef ANJAY_MODULE_SIM_BOOTSTRAP_HARDCODED_FILE_ID */\n\n/**\n * Overridden OID of the SIM bootstrap information to look for in the DODF file,\n * expressed as a hexlified DER representation (without the header).\n *\n * This is the hexlified expected value of the 'id' field within the 'OidDO'\n * sequence in the DODF file (please refer to the PKCS #15 document for more\n * information).\n *\n * If not defined, the default value of <c>\"672b0901\"</c>, which corresponds to\n * OID 2.23.43.9.1 {joint-iso-itu-t(2) international-organizations(23) wap(43)\n * oma-lwm2m(9) lwm2m-bootstrap(1)}, will be used.\n *\n * No other values than the default are valid according to the specification,\n * but some SIM cards are known to use other non-standard values, e.g.\n * <c>\"0604672b0901\"</c> - including a superfluous nested BER-TLV header, as\n * erroneously illustrated in the EF(DODF-bootstrap) file coding example in\n * LwM2M TS 1.2 and earlier (fixed in LwM2M TS 1.2.1) - which is interpreted as\n * OID 0.6.4.103.43.9.1 (note that it is invalid as the 0.6 tree does not exist\n * in the repository as of writing this note).\n *\n * Requires <c>ANJAY_WITH_MODULE_BOOTSTRAPPER</c> to be enabled. At most one of\n * <c>ANJAY_MODULE_SIM_BOOTSTRAP_HARDCODED_FILE_ID</c> and\n * <c>ANJAY_MODULE_SIM_BOOTSTRAP_DATA_OID_OVERRIDE_HEX</c> may be defined at the\n * same time.\n *\n * IMPORTANT: Only available with the bootstrapper feature. Ignored in the open\n * source version.\n */\n/* #undef ANJAY_MODULE_SIM_BOOTSTRAP_DATA_OID_OVERRIDE_HEX */\n\n/**\n * Enable factory provisioning module. Data provided during provisioning uses\n * SenML CBOR format.\n */\n/* #undef ANJAY_WITH_MODULE_FACTORY_PROVISIONING */\n\n/**\n * Enable oscore module (implementation of the OSCORE object).\n *\n * IMPORTANT: Only available as part of the OSCORE commercial feature. Ignored\n * in the open source version.\n */\n/* #undef ANJAY_WITH_MODULE_OSCORE */\n\n/**\n * If enable Anjay doesn't handle composite operation (read, observe and write).\n * Its use makes sense for LWM2M v1.1 upwards.\n *\n * This flag can be used to reduce the size of the resulting code.\n *\n * If active, anjay will respond with message code 5.01 Not Implemented to any\n * composite type request.\n */\n/* #undef ANJAY_WITHOUT_COMPOSITE_OPERATIONS */\n\n/**\n * Enable support for the experimental\n * <c>anjay_get_server_connection_status()</c> API and related\n * <c>anjay_server_connection_status_cb_t</c> callback.\n */\n/* #undef ANJAY_WITH_CONN_STATUS_API */\n\n/**\n * Enable support for the experimental\n * <c>anjay_ssl_error_cb_t</c> callback.\n * Currently only supported for mbedTLS and custom SSL integration builds\n */\n#define ANJAY_WITH_SSL_ERROR_API\n\n/**\n * Enable support for /25 LwM2M Gateway Object.\n *\n * Requires <c>ANJAY_WITH_LWM2M11</c> to be enabled.\n * Requires <c>ANJAY_WITH_CORE_PERSISTENCE</c> (commercial feature) to be\n * disabled.\n */\n/* #undef ANJAY_WITH_LWM2M_GATEWAY */\n\n/**\n * Maximum holdoff time in seconds.\n *\n * This parameter defines an upper limit on the Hold Off Time as specified in\n * the LwM2M Security Object (Object ID: 0, Resource ID: 11). The Hold Off Time\n * is used by the LwM2M Client to delay initiating a Bootstrap process.\n *\n * Limiting the Hold Off Time is important in network environments where\n * prolonged delays may cause NAT bindings to expire, potentially leading to\n * failed connection attempts. This parameter ensures that the configured\n * Hold Off Time does not exceed a safe threshold. If a DTLS binding with\n * Connection ID is used this parameter can be increased safely.\n *\n * If editing this file manually, <c>20</c> shall be\n * replaced with a positive integer literal representing the time in seconds.\n * The default value defined in CMake build scripts is 20.\n */\n#define ANJAY_MAX_HOLDOFF_TIME 20\n\n/**\n * Enable support for optional resources of the Firmware Update object\n * introduced in LwM2M 1.1.\n *\n * This includes the following resources:\n * - Severity\n * - Last State Change Time\n * - Max Defer Period\n *\n * Requires <c>ANJAY_WITH_LWM2M11</c> to be enabled.\n */\n/* #undef ANJAY_WITH_MODULE_FW_UPDATE_V11_RESOURCES */\n\n/**@}*/\n\n#endif // ANJAY_CONFIG_H\n"
  },
  {
    "path": "example_configs/embedded_lwm2m10/avsystem/coap/avs_coap_config.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef AVS_COAP_CONFIG_H\n#define AVS_COAP_CONFIG_H\n\n/**\n * @file avs_coap_config.h\n *\n * avs_coap library configuration.\n *\n * The preferred way to compile avs_coap is to use CMake, in which case this\n * file will be generated automatically by CMake.\n *\n * However, to provide compatibility with various build systems used especially\n * by embedded platforms, it is alternatively supported to compile avs_coap by\n * other means, in which case this file will need to be provided manually.\n *\n * <strong>NOTE: To compile this library without using CMake, you need to\n * configure avs_commons first. Please refer to documentation in the\n * <c>avs_commons_config.h</c> file (provided in the repository as\n * <c>avs_commons_config.h.in</c>) for details.</strong>\n *\n * <strong>avs_coap requires the following avs_commons components to be\n * enabled:</strong>\n *\n * - @c avs_buffer\n * - @c avs_compat_threading\n * - @c avs_list\n * - @c avs_net\n * - @c avs_sched\n * - @c avs_stream\n * - @c avs_utils\n * - @c avs_log (if @c WITH_AVS_COAP_LOGS is enabled)\n * - @c avs_persistence (if @c WITH_AVS_COAP_OBSERVE_PERSISTENCE is enabled)\n * - @c avs_crypto (if @c WITH_AVS_COAP_OSCORE is enabled)\n *\n * In the repository, this file is provided as <c>avs_coap_config.h.in</c>,\n * intended to be processed by CMake. If editing this file manually, please copy\n * or rename it to <c>avs_coap_config.h</c> and for each of the\n * <c>\\#cmakedefine</c> directives, please either replace it with regular\n * <c>\\#define</c> to enable it, or comment it out to disable. You may also need\n * to replace variables wrapped in <c>\\@</c> signs with concrete values. Please\n * refer to the comments above each of the specific definition for details.\n *\n * If you are editing a file previously generated by CMake, these\n * <c>\\#cmakedefine</c>s will be already replaced by either <c>\\#define</c> or\n * commented out <c>\\#undef</c> directives.\n */\n\n/**\n * Enable support for block-wise transfers (RFC 7959).\n *\n * If this flag is disabled, attempting to send a message bigger than the\n * internal buffer will fail; incoming messages may still carry BLOCK1 or BLOCK2\n * options, but they will not be interpreted by the library in any way.\n */\n#define WITH_AVS_COAP_BLOCK\n\n/**\n * Enable support for observations (RFC 7641).\n */\n#define WITH_AVS_COAP_OBSERVE\n\n/**\n * Turn on cancelling observation on a timeout.\n *\n * Only meaningful if <c>WITH_AVS_COAP_OBSERVE</c> is enabled.\n *\n * NOTE: LwM2M specification requires LwM2M server to send Cancel Observation\n * request. Meanwhile CoAP RFC 7641 states that timeout on notification should\n * cancel it. This setting is to enable both of these behaviors with default\n * focused on keeping observations in case of bad connectivity.\n */\n/* #undef WITH_AVS_COAP_OBSERVE_CANCEL_ON_TIMEOUT */\n\n/**\n * Force cancelling observation, even if a confirmable notification yielding\n * an error response is not acknowledged or rejected with RST by the observer.\n *\n * This is a circumvention for some non-compliant servers that respond with an\n * RST message to a confirmable notification yielding an error response. This\n * setting makes the library cancel the observation in such cases, even though\n * the notification is formally rejected. Additionally, it will also make the\n * library cancel the observation if no response is received at all.\n */\n#define WITH_AVS_COAP_OBSERVE_FORCE_CANCEL_ON_UNACKED_ERROR\n\n/**\n * Enable support for observation persistence (<c>avs_coap_observe_persist()</c>\n * and <c>avs_coap_observe_restore()</c> calls).\n *\n * Only meaningful if <c>WITH_AVS_COAP_OBSERVE</c> is enabled.\n */\n/* #undef WITH_AVS_COAP_OBSERVE_PERSISTENCE */\n\n/**\n * Enable support for the streaming API\n */\n#define WITH_AVS_COAP_STREAMING_API\n\n/**\n * Enable support for UDP transport.\n *\n * NOTE: Enabling at least one transport is necessary for the library to be\n * useful.\n */\n#define WITH_AVS_COAP_UDP\n\n/**\n * Enable support for TCP transport (RFC 8323).\n *\n * NOTE: Enabling at least one transport is necessary for the library to be\n * useful.\n */\n/* #undef WITH_AVS_COAP_TCP */\n\n/**\n * Enable support for OSCORE (RFC 8613).\n *\n * IMPORTANT: Only available with the OSCORE feature. Ignored in the open\n * source version.\n */\n/* #undef WITH_AVS_COAP_OSCORE */\n\n/**\n * Use OSCORE version from draft-ietf-core-object-security-08 instead of the\n * final version (RFC 8613).\n *\n * Only meaningful if <c>WITH_AVS_COAP_OSCORE</c> is enabled.\n *\n * IMPORTANT: Only available with the OSCORE feature. Ignored in the open\n * source version.\n */\n/* #undef WITH_AVS_COAP_OSCORE_DRAFT_8 */\n\n/**\n * Maximum size in bytes supported for the Master Secret.\n *\n * If editing this file manually, set it to a positive integer literal.\n *\n * The default value defined in CMake build scripts is 32.\n *\n * IMPORTANT: Only available with the OSCORE feature. Ignored in the open\n * source version.\n */\n/* #undef AVS_COAP_OSCORE_MASTER_SECRET_SIZE */\n\n/**\n * Maximum size in bytes supported for the Master Salt.\n *\n * If editing this file manually, set it to a positive integer literal.\n *\n * The default value defined in CMake build scripts is 16.\n *\n * IMPORTANT: Only available with the OSCORE feature. Ignored in the open\n * source version.\n */\n/* #undef AVS_COAP_OSCORE_MASTER_SALT_SIZE */\n\n/**\n * Maximum number of notification tokens stored to match Reset responses to.\n *\n * Only meaningful if <c>WITH_AVS_COAP_OBSERVE</c> and <c>WITH_AVS_COAP_UDP</c>\n * are enabled.\n *\n * If editing this file manually, <c>4</c> shall be\n * replaced with a positive integer literal. The default value defined in CMake\n * build scripts is 4.\n */\n#define AVS_COAP_UDP_NOTIFY_CACHE_SIZE 4\n\n/**\n * Enable sending diagnostic payload in error responses.\n */\n#define WITH_AVS_COAP_DIAGNOSTIC_MESSAGES\n\n/**\n * Enable logging in avs_coap.\n *\n * If this flag is disabled, no logging is compiled into the binary at all.\n */\n#define WITH_AVS_COAP_LOGS\n\n/**\n * Enable TRACE-level logs in avs_coap.\n *\n * Only meaningful if <c>WITH_AVS_COAP_LOGS</c> is enabled.\n */\n/* #undef WITH_AVS_COAP_TRACE_LOGS */\n\n/**\n * Enable poisoning of unwanted symbols when compiling avs_coap.\n *\n * Requires a compiler that supports <c>\\#pragma GCC poison</c>.\n *\n * This is mostly useful during development, to ensure that avs_commons do not\n * attempt to call functions considered harmful in this library, such as printf.\n * This is not guaranteed to work as intended on every platform.\n */\n/* #undef WITH_AVS_COAP_POISONING */\n\n#endif // AVS_COAP_CONFIG_H\n"
  },
  {
    "path": "example_configs/embedded_lwm2m10/avsystem/commons/avs_commons_config.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AVS_COMMONS_CONFIG_H\n#define AVS_COMMONS_CONFIG_H\n\n/**\n * @file avs_commons_config.h\n *\n * avs_commons library configuration.\n *\n * The preferred way to compile avs_commons is to use CMake, in which case this\n * file will be generated automatically by CMake.\n *\n * However, to provide compatibility with various build systems used especially\n * by embedded platforms, it is alternatively supported to compile avs_commons\n * by other means, in which case this file will need to be provided manually.\n *\n * In the repository, this file is provided as <c>avs_commons_config.h.in</c>,\n * intended to be processed by CMake. If editing this file manually, please copy\n * or rename it to <c>avs_commons_config.h</c> and for each of the\n * <c>#cmakedefine</c> directives, please either replace it with regular\n * <c>#define</c> to enable it, or comment it out to disable. You may also need\n * to replace variables wrapped in <c>@</c> signs with concrete values. Please\n * refer to the comments above each of the specific definition for details.\n *\n * If you are editing a file previously generated by CMake, these\n * <c>#cmakedefine</c>s will be already replaced by either <c>#define</c> or\n * commented out <c>#undef</c> directives.\n */\n\n/**\n * Options that describe capabilities of the build environment.\n *\n * NOTE: If you leave some of these macros undefined, even though the given\n * feature is actually available in the system, avs_commons will attempt to use\n * its own substitutes, which may be incompatible with the definition in the\n * system and lead to undefined behaviour.\n */\n/**@{*/\n/**\n * Is the target platform big-endian?\n *\n * If undefined, little-endian is assumed. Mixed-endian architectures are not\n * supported.\n *\n * Affects <c>avs_convert_be*()</c> and <c>avs_[hn]to[hn]*()</c> calls in\n * avs_utils and, by extension, avs_persistence.\n */\n/* #undef AVS_COMMONS_BIG_ENDIAN */\n\n/**\n * Is GNU __builtin_add_overflow() extension available?\n *\n * Affects time handling functions in avs_utils. If disabled, software overflow\n * checking will be compiled. Note that this software overflow checking code\n * relies on U2 representation of signed integers.\n */\n/* #undef AVS_COMMONS_HAVE_BUILTIN_ADD_OVERFLOW */\n\n/**\n * Is GNU __builtin_mul_overflow() extension available?\n *\n * Affects time handling functions in avs_utils. If disabled, software overflow\n * checking will be compiled. Note that this software overflow checking code\n * relies on U2 representation of signed integers.\n */\n/* #undef AVS_COMMONS_HAVE_BUILTIN_MUL_OVERFLOW */\n\n/**\n * Is net/if.h available in the system?\n *\n * NOTE: If the header is indeed available, but this option is not defined, the\n * <c>IF_NAMESIZE</c> macro will be defined <strong>publicly by avs_commons\n * headers</strong>, which may conflict with system definitions.\n */\n/* #undef AVS_COMMONS_HAVE_NET_IF_H */\n\n/**\n * Are GNU diagnostic pragmas (#pragma GCC diagnostic push/pop/ignored)\n * available?\n *\n * If defined, those pragmas will be used to suppress compiler warnings for some\n * code known to generate them and cannot be improved in a more robust way, e.g.\n * for code that is known to generate warnings from within system headers.\n */\n/* #undef AVS_COMMONS_HAVE_PRAGMA_DIAGNOSTIC */\n\n/**\n * Are GNU visibility pragmas (#pragma GCC visibility push/pop) available?\n *\n * Meaningful mostly if avs_commons will be directly or indirectly linked into\n * a shared library. Causes all symbols except those declared in public headers\n * to be hidden, i.e. not exported outside the shared library. If not defined,\n * default compiler visibility settings will be used, but you still may use\n * compiler flags and linker version scripts to replicate this manually if\n * needed.\n */\n/* #undef AVS_COMMONS_HAVE_VISIBILITY */\n\n/**\n * Specify an optional compatibility header that allows use of POSIX-specific\n * code that is not compliant with POSIX enough to be compiled directly.\n *\n * This header, if specified, will be included only by the following components,\n * which may be enabled or disabled depending on state of the referenced flags:\n * - avs_compat_threading implementation based on POSIX Threads\n *   (@ref AVS_COMMONS_COMPAT_THREADING_WITH_PTHREAD)\n * - default avs_net socket implementation\n *   (@ref AVS_COMMONS_NET_WITH_POSIX_AVS_SOCKET)\n * - avs_unit (@ref AVS_COMMONS_WITH_AVS_UNIT)\n * - default implementation of avs_time routines\n *   (@ref AVS_COMMONS_UTILS_WITH_POSIX_AVS_TIME)\n *\n * Compatibility headers for lwIP and Microsoft Windows are provided with the\n * library (see the <c>compat</c> directory).\n *\n * If this macro is not defined, the afore-mentioned components, if enabled,\n * will use system headers directly, assuming they are POSIX-compliant.\n *\n * If this macro is enabled, the specified file will be included through an\n * <c>#include AVS_COMMONS_POSIX_COMPAT_HEADER</c> statement. Thus, if editing\n * this file manually, <c>avsystem/commons/lwip-posix-compat.h</c> shall be\n * replaced with a path to such file.\n */\n#define AVS_COMMONS_POSIX_COMPAT_HEADER \"avsystem/commons/lwip-posix-compat.h\"\n\n/**\n * Set if printf implementation doesn't support 64-bit format specifiers.\n * If defined, custom implementation of conversion is used in\n * @c AVS_UINT64_AS_STRING instead of using @c snprintf .\n */\n/* #undef AVS_COMMONS_WITHOUT_64BIT_FORMAT_SPECIFIERS */\n\n/**\n * Set if printf implementation doesn't support floating-point numbers.\n * If defined, custom implementation of conversion is used in\n * @c AVS_DOUBLE_AS_STRING instead of using @c snprintf . This might increase\n * compatibility with some embedded libc implementations that do not provide\n * this functionality.\n *\n * NOTE: In order to keep the custom implementation small in code size, it is\n * not intended to be 100% accurate. Rounding errors may occur - according to\n * empirical checks, they show up around the 16th significant decimal digit.\n */\n/* #undef AVS_COMMONS_WITHOUT_FLOAT_FORMAT_SPECIFIERS */\n/**@}*/\n\n/**\n * Enable poisoning of unwanted symbols when compiling avs_commons.\n *\n * Requires a compiler that supports #pragma GCC poison.\n *\n * This is mostly useful during development, to ensure that avs_commons do not\n * attempt to call functions considered harmful in this library, such as printf.\n * This is not guaranteed to work as intended on every platform, e.g. on macOS\n * it is known to generate false positives due to different dependencies between\n * system headers.\n */\n/* #undef AVS_COMMONS_WITH_POISONING */\n\n/**\n * Options that control compilation of avs_commons components.\n *\n * Each of the configuration options below enables, if defined, one of the core\n * components of the avs_commons library.\n *\n * NOTE: Enabling avs_unit will cause an object file with an implementation of\n * main() to be generated.\n */\n/**@{*/\n#define AVS_COMMONS_WITH_AVS_ALGORITHM\n#define AVS_COMMONS_WITH_AVS_BUFFER\n#define AVS_COMMONS_WITH_AVS_COMPAT_THREADING\n#define AVS_COMMONS_WITH_AVS_CRYPTO\n/* #undef AVS_COMMONS_WITH_AVS_HTTP */\n#define AVS_COMMONS_WITH_AVS_LIST\n#define AVS_COMMONS_WITH_AVS_LOG\n#define AVS_COMMONS_WITH_AVS_NET\n#define AVS_COMMONS_WITH_AVS_PERSISTENCE\n/* #undef AVS_COMMONS_WITH_AVS_RBTREE */\n#define AVS_COMMONS_WITH_AVS_SORTED_SET\n#define AVS_COMMONS_WITH_AVS_SCHED\n#define AVS_COMMONS_WITH_AVS_STREAM\n/* #undef AVS_COMMONS_WITH_AVS_UNIT */\n#define AVS_COMMONS_WITH_AVS_URL\n#define AVS_COMMONS_WITH_AVS_UTILS\n/* #undef AVS_COMMONS_WITH_AVS_VECTOR */\n/**@}*/\n\n/**\n * Options that control compilation of avs_compat_threading implementations.\n *\n * If CMake is not used, in the typical scenario at most one of the following\n * implementations may be enabled at the same time. If none is enabled, the\n * relevant symbols will need to be provided by the user, if used.\n *\n * These are meaningful only if <c>AVS_COMMONS_WITH_AVS_COMPAT_THREADING</c> is\n * defined.\n */\n/**@{*/\n/**\n * Enable implementation based on spinlocks.\n *\n * This implementation is usually very inefficient, and requires C11 stdatomic.h\n * header to be available.\n */\n/* #undef AVS_COMMONS_COMPAT_THREADING_WITH_ATOMIC_SPINLOCK */\n\n/**\n * Enable implementation based on the POSIX Threads library.\n *\n * This implementation is preferred over the spinlock-based one, but the POSIX\n * Threads library is normally available only in UNIX-like environments.\n */\n/* #undef AVS_COMMONS_COMPAT_THREADING_WITH_PTHREAD */\n\n/**\n * Is the <c>pthread_condattr_setclock()</c> function available?\n *\n * This flag only makes sense when\n * <c>AVS_COMMONS_COMPAT_THREADING_WITH_PTHREAD</c> is enabled.\n *\n * If this flag is disabled, or if <c>CLOCK_MONOTONIC</c> macro is not\n * available, the <c>avs_condvar_wait()</c> will internally use the real-time\n * clock instead of the monotonic clock. Time values will be converted so that\n * this change does not affect API usage.\n */\n/* #undef AVS_COMMONS_COMPAT_THREADING_PTHREAD_HAVE_PTHREAD_CONDATTR_SETCLOCK */\n/**@}*/\n\n/**\n * Options that control compilation of code depending on TLS backend library.\n *\n * If CMake is not used, in the typical scenario at most one of the following\n * DTLS backends may be enabled at the same time. If none is enabled,\n * functionalities that depends on cryptography will be disabled.\n *\n * Affects avs_crypto, avs_net, and avs_stream (for the MD5 implementation).\n *\n * mbed TLS is the main development backend, and is preferred as such. OpenSSL\n * backend supports most functionality as well, but is not as thoroughly tested.\n * TinyDTLS support is only rudimentary.\n */\n/**@{*/\n#define AVS_COMMONS_WITH_MBEDTLS\n/* #undef AVS_COMMONS_WITH_OPENSSL */\n/* #undef AVS_COMMONS_WITH_TINYDTLS */\n\n/**\n * Enable support for custom TLS socket implementation.\n *\n * If enabled, the user needs to provide their own implementations of\n * <c>_avs_net_create_ssl_socket()</c>, <c>_avs_net_create_dtls_socket()</c>,\n * <c>_avs_net_initialize_global_ssl_state() and\n * <c>_avs_net_cleanup_global_ssl_state()</c>.\n */\n/* #undef AVS_COMMONS_WITH_CUSTOM_TLS */\n/**@}*/\n\n/**\n * Options related to avs_crypto.\n */\n/**@{*/\n/**\n * Enable AEAD and HKDF support in avs_crypto. Requires MbedTLS in version at\n * least 2.14.0, OpenSSL in version at least 1.1.0, or custom implementation in\n * case of <c>AVS_COMMONS_WITH_CUSTOM_TLS</c>.\n */\n/* #undef AVS_COMMONS_WITH_AVS_CRYPTO_ADVANCED_FEATURES */\n\n/**\n * If the TLS backend is either mbed TLS or OpenSSL, enables APIs related to\n * public-key cryptography.\n *\n * Public-key cryptography is not currently supported with TinyDTLS.\n *\n * It also enables support for X.509 certificates in avs_net, if that module is\n * also enabled.\n */\n/* #undef AVS_COMMONS_WITH_AVS_CRYPTO_PKI */\n\n/**\n * If the TLS backend is either mbed TLS, OpenSSL or TinyDTLS, enables support\n * of pre-shared key security.\n *\n * PSK is the only supported security mode for the TinyDTLS backend, so this\n * option MUST be enabled to utilize it.\n *\n * It also enables support for pre-shared key security in avs_net, if that\n * module is also enabled.\n */\n#define AVS_COMMONS_WITH_AVS_CRYPTO_PSK\n\n/**\n * Enables usage of Valgrind API to suppress some of the false positives\n * generated by the OpenSSL backend.\n */\n/* #undef AVS_COMMONS_WITH_AVS_CRYPTO_VALGRIND */\n\n/**\n * Enables high-level support for hardware-based PKI security, i.e. loading,\n * generating and managing key pairs and certificates via external engines.\n *\n * Requires @ref AVS_COMMONS_WITH_AVS_CRYPTO_PKI to be enabled.\n *\n * An actual implementation is required to use this feature. You may provide\n * your own, or use one of the default ones that come with the HSM engine\n * commercial feature (see @ref AVS_COMMONS_WITH_MBEDTLS_PKCS11_ENGINE,\n * @ref AVS_COMMONS_WITH_MBEDTLS_PSA_ENGINE and\n * @ref AVS_COMMONS_WITH_OPENSSL_PKCS11_ENGINE).\n *\n * The functions that need to be provided in case of a custom implementation:\n * - <c>avs_crypto_pki_engine_certificate_rm()</c>\n * - <c>avs_crypto_pki_engine_certificate_store()</c>\n * - <c>avs_crypto_pki_engine_key_gen()</c>\n * - <c>avs_crypto_pki_engine_key_rm()</c>\n * - <c>avs_crypto_pki_engine_key_store()</c>\n * - When targeting the Mbed TLS backend:\n *   - <c>_avs_crypto_mbedtls_engine_initialize_global_state()</c>\n *   - <c>_avs_crypto_mbedtls_engine_cleanup_global_state()</c>\n *   - <c>_avs_crypto_mbedtls_engine_append_cert()</c>\n *   - <c>_avs_crypto_mbedtls_engine_append_crl()</c>\n *   - <c>_avs_crypto_mbedtls_engine_load_private_key()</c>\n * - When targeting the OpenSSL backend:\n *   - <c>_avs_crypto_openssl_engine_initialize_global_state()</c>\n *   - <c>_avs_crypto_openssl_engine_cleanup_global_state()</c>\n *   - <c>_avs_crypto_openssl_engine_load_certs()</c>\n *   - <c>_avs_crypto_openssl_engine_load_crls()</c>\n *   - <c>_avs_crypto_openssl_engine_load_private_key()</c>\n *\n * External PKI engines are NOT supported in the TinyDTLS backend.\n */\n/* #undef AVS_COMMONS_WITH_AVS_CRYPTO_PKI_ENGINE */\n\n/**\n * Enables high-level support for hardware-based PSK security, i.e. loading\n * and managing PSK keys and identities via external engine.\n *\n * Requires @ref AVS_COMMONS_WITH_AVS_CRYPTO_PKI to be enabled.\n *\n * An actual implementation is required to use this feature. You may provide\n * your own, or use the default PSA-based one that comes with the HSM engine\n * commercial feature (see @ref AVS_COMMONS_WITH_MBEDTLS_PSA_ENGINE).\n *\n * The functions that need to be provided in case of a custom implementation:\n * - <c>avs_crypto_psk_engine_identity_store()</c>\n * - <c>avs_crypto_psk_engine_identity_rm()</c>\n * - <c>avs_crypto_psk_engine_key_store()</c>\n * - <c>avs_crypto_psk_engine_key_rm()</c>\n * - When targeting the Mbed TLS backend:\n *   - <c>_avs_crypto_mbedtls_engine_initialize_global_state()</c>\n *   - <c>_avs_crypto_mbedtls_engine_cleanup_global_state()</c>\n *   - <c>_avs_crypto_mbedtls_engine_load_psk_key()</c>\n *\n * External PSK engines are NOT supported in the OpenSSL and TinyDTLS backend.\n */\n/* #undef AVS_COMMONS_WITH_AVS_CRYPTO_PSK_ENGINE */\n\n/**\n * Enables the default implementation of avs_crypto engine, based on Mbed TLS\n * and PKCS#11.\n *\n * Requires @ref AVS_COMMONS_WITH_AVS_CRYPTO_PKI_ENGINE to be enabled.\n *\n * NOTE: Query string format for this engine is a subset of the PKCS#11 URI\n * scheme (see RFC 7512), modelled after the format accepted by libp11 OpenSSL\n * engine.\n *\n * NOTE: The unit tests for this feature depend on SoftHSM and pkcs11-tool.\n * These must be installed for the tests to pass.\n *\n * IMPORTANT: Only available as part of the HSM support commercial feature.\n * Ignored in the open source version.\n */\n/* #undef AVS_COMMONS_WITH_MBEDTLS_PKCS11_ENGINE */\n\n/**\n * Enables the default implementation of avs_crypto engine, based on Mbed TLS\n * and Platform Security Architecture (PSA).\n *\n * Requires @ref AVS_COMMONS_WITH_AVS_CRYPTO_PKI_ENGINE or\n * @ref AVS_COMMONS_WITH_AVS_CRYPTO_PSK_ENGINE to be enabled.\n *\n * NOTE: Query string format for this engine is:\n *\n * <pre>\n * kid=<key_ID>[,lifetime=<lifetime>]|uid=<persistent_storage_UID>\n * </pre>\n *\n * The values are parsed using strtoull() with base=0, so may be in decimal,\n * 0-prefixed octal or 0x-prefixed hexadecimal. On key generation and\n * certificate storage, the specified lifetime will be used, or lifetime 1\n * (default persistent storage) will be used if not. On key or certificate use,\n * the lifetime of the actual key will be verified if present on the query\n * string and the key will be rejected if different.\n *\n * Certificates are stored as PSA_KEY_TYPE_RAW_DATA key entries containing\n * X.509 DER data. Alternatively, the PSA Protected Storage API can be used if\n * @ref AVS_COMMONS_WITH_MBEDTLS_PSA_ENGINE_PROTECTED_STORAGE is enabled, by\n * using the <c>uid=...</c> syntax.\n *\n * IMPORTANT: Only available as part of the HSM support commercial feature.\n * Ignored in the open source version.\n */\n/* #undef AVS_COMMONS_WITH_MBEDTLS_PSA_ENGINE */\n\n/**\n * Enables support for the PSA Protected Storage API in the PSA-based avs_crypto\n * engine.\n *\n * Requires @ref AVS_COMMONS_WITH_MBEDTLS_PSA_ENGINE to be enabled.\n *\n * IMPORTANT: Only available as part of the HSM support commercial feature.\n * Ignored in the open source version.\n */\n/* #undef AVS_COMMONS_WITH_MBEDTLS_PSA_ENGINE_PROTECTED_STORAGE */\n\n/**\n * Enables use of the <c>psa_generate_random()</c> function as the default\n * random number generator when using the Mbed TLS crypto backend, instead of\n * CTR-DRBG seeded by the Mbed TLS entropy pool.\n *\n * It's meaningful only when @ref AVS_COMMONS_WITH_MBEDTLS is enabled. However,\n * it is independent from the above PSA engine settings.\n */\n/* #undef AVS_COMMONS_WITH_MBEDTLS_PSA_RNG */\n\n/**\n * Is the <c>dlsym()</c> function available?\n *\n * This is currently only used if @ref AVS_COMMONS_WITH_MBEDTLS_PKCS11_ENGINE is\n * enabled. If enabled, the PKCS#11 module is loaded dynamically from a library\n * specified by the <c>PKCS11_MODULE_PATH</c> environment variable. If disabled,\n * a function with the following signature, realizing the PKCS#11\n * <c>C_GetFunctionList</c> method, must be provided manually:\n *\n * <pre>\n * CK_RV _avs_crypto_mbedtls_pkcs11_get_function_list(CK_FUNCTION_LIST_PTR_PTR);\n * </pre>\n */\n/* #undef AVS_COMMONS_HAVE_DLSYM */\n\n/**\n * Enables the default implementation of avs_crypto engine, based on OpenSSL and\n * PKCS#11.\n *\n * Requires @ref AVS_COMMONS_WITH_AVS_CRYPTO_PKI_ENGINE to be enabled.\n *\n * NOTE: Query string format for this engine is a subset of the PKCS#11 URI\n * scheme (see RFC 7512), modelled after the format accepted by libp11 OpenSSL\n * engine.\n *\n * NOTE: The unit tests for this feature depend on SoftHSM and pkcs11-tool.\n * These must be installed for the tests to pass.\n *\n * IMPORTANT: Only available as part of the HSM support commercial feature.\n * Ignored in the open source version.\n */\n/* #undef AVS_COMMONS_WITH_OPENSSL_PKCS11_ENGINE */\n/**@}*/\n\n/**\n * Enable support for HTTP content compression in avs_http.\n *\n * Requires linking with zlib.\n */\n/* #undef AVS_COMMONS_HTTP_WITH_ZLIB */\n\n/**\n * Options related to avs_log and logging support within avs_commons.\n */\n/**@{*/\n/* clang-format off */\n/**\n * Size, in bytes, of the avs_log buffer.\n *\n * Log messages that would (including the level, module name and code location)\n * otherwise be longer than this value minus one (for the terminating null\n * character) will be truncated.\n *\n * NOTE: This macro MUST be defined if avs_log is enabled.\n *\n * If editing this file manually, <c>512</c> shall\n * be replaced with a positive integer literal. The default value defined in\n * CMake build scripts is 512.\n */\n#define AVS_COMMONS_LOG_MAX_LINE_LENGTH 512\n/* clang-format on */\n\n/**\n * Configures avs_log to use a synchronized global buffer instead of allocating\n * a buffer on the stack when constructing log messages.\n *\n * Requires avs_compat_threading to be enabled.\n *\n * Enabling this option would reduce the stack space required to use avs_log, at\n * the expense of global storage and the complexity of using a mutex.\n */\n#define AVS_COMMONS_LOG_USE_GLOBAL_BUFFER\n\n/**\n * Provides a default avs_log handler that prints log messages on stderr.\n *\n * Disabling this option will cause logs to be discarded by default, until a\n * custom log handler is set using <c>avs_log_set_handler()</c>.\n */\n/* #undef AVS_COMMONS_LOG_WITH_DEFAULT_HANDLER */\n\n/**\n * Enables the \"micro logs\" feature.\n *\n * Replaces all occurrences of the <c>AVS_DISPOSABLE_LOG()</c> macro with single\n * space strings. This is intended to reduce the size of the compiled code, by\n * stripping it of almost all log string data.\n *\n * Note that this setting will propagate both to avs_commons components\n * themselves (as all its internal logs make use of <c>AVS_DISPOSABLE_LOG()</c>)\n * and the user code that uses it.\n */\n/* #undef AVS_COMMONS_WITH_MICRO_LOGS */\n\n/**\n * Enables logging inside avs_commons.\n *\n * Requires @ref AVS_COMMONS_WITH_AVS_LOG to be enabled.\n *\n * If this macro is not defined at avs_commons compile time, calls to avs_log\n * will not be generated inside avs_commons components.\n */\n#define AVS_COMMONS_WITH_INTERNAL_LOGS\n\n/**\n * Enables TRACE-level logs inside avs_commons.\n *\n * Only meaningful if AVS_COMMONS_WITH_INTERNAL_LOGS is enabled.\n *\n * If this macro is not defined at avs_commons compile time, calls to avs_log\n * with the level set to TRACE will not be generated inside avs_commons\n * components.\n */\n/* #undef AVS_COMMONS_WITH_INTERNAL_TRACE */\n\n/**\n * Enables external implementation of logger subsystem with provided header.\n *\n * Default logger implementation can be found in avs_log_impl.h\n */\n/* #undef AVS_COMMONS_WITH_EXTERNAL_LOGGER_HEADER */\n\n/**\n * If specified, the process of checking if avs_log should be written out\n * takes place in compile time.\n *\n * Specify an optional header with a list of modules for which log level\n * is set. If a log level for specific module is not set, the DEFAULT level\n * will be taken into account. Value of the default logging level is set to\n * DEBUG, but can be overwritten in this header file with AVS_LOG_LEVEL_DEFAULT\n * define. Messages with lower level than the one set will be removed during\n * compile time. Possible values match @ref avs_log_level_t.\n *\n * That file should contain C preprocesor defines in the:\n * - \"#define AVS_LOG_LEVEL_FOR_MODULE_<Module> <Level>\" format,\n *   where <Module> is the module name and <Level> is allowed logging level\n * - \"#define AVS_LOG_LEVEL_DEFAULT <Level>\" format, where <Level> is the\n *   allowed logging level\n *\n * Example file content:\n *\n * <code>\n * #ifndef AVS_COMMONS_EXTERNAL_LOG_LEVELS_H\n * #define AVS_COMMONS_EXTERNAL_LOG_LEVELS_H\n *\n * // global log level value\n * #define AVS_LOG_LEVEL_DEFAULT INFO\n *\n * //for \"coap\" messages only WARNING and ERROR messages will be present\n * #define AVS_LOG_LEVEL_FOR_MODULE_coap WARNING\n *\n * //logs are disable for \"net\" module\n * #define AVS_LOG_LEVEL_FOR_MODULE_net QUIET\n *\n * #endif\n * </code>\n */\n/* #undef AVS_COMMONS_WITH_EXTERNAL_LOG_LEVELS_HEADER */\n\n/**\n * Disable log level check in runtime. Allows to save at least 1.3kB of memory.\n *\n * The macros avs_log_set_level and avs_log_set_default_level\n * will not be available.\n *\n */\n/* #undef AVS_COMMONS_WITHOUT_LOG_CHECK_IN_RUNTIME */\n/**@}*/\n\n/**\n * Options related to avs_net.\n */\n/**@{*/\n/**\n * Enables support for IPv4 connectivity.\n *\n * At least one of AVS_COMMONS_NET_WITH_IPV4 and AVS_COMMONS_NET_WITH_IPV6\n * MUST be defined if avs_net is enabled.\n */\n#define AVS_COMMONS_NET_WITH_IPV4\n\n/**\n * Enables support for IPv6 connectivity.\n *\n * At least one of AVS_COMMONS_NET_WITH_IPV4 and AVS_COMMONS_NET_WITH_IPV6\n * MUST be defined if avs_net is enabled.\n */\n/* #undef AVS_COMMONS_NET_WITH_IPV6 */\n\n/**\n * If the TLS backend is set to OpenSSL, enables support for DTLS.\n *\n * DTLS is always enabled for the mbed TLS and TinyDTLS backends.\n */\n/* #undef AVS_COMMONS_NET_WITH_DTLS */\n\n/**\n * Enables debug logs generated by mbed TLS.\n *\n * An avs_log-backed handler, logging for the \"mbedtls\" module on the TRACE\n * level, is installed using <c>mbedtls_ssl_conf_dbg()</c> for each (D)TLS\n * socket created if this option is enabled.\n */\n/* #undef AVS_COMMONS_NET_WITH_MBEDTLS_LOGS */\n\n/**\n * Enables (Pre-)Master-Secret logs generation.\n *\n * These logs contain TLS session secrets that tools like Wireshark can use\n * to decrypt captured TLS traffic.\n *\n * NOTE: This only works with Mbed TLS starting from v3.0.0. If you’re using\n * Mbed TLS earlier than v3.1.0, you must enable MBEDTLS_SSL_EXPORT_KEYS.\n *\n * NOTE: The user must specify the stream to which the logs will be transferred\n * using the avs_mbedtls_set_sslkeylog_stream function.\n */\n/* #undef AVS_COMMONS_NET_WITH_MBEDTLS_SSLKEYLOG */\n\n/**\n * Enables the default implementation of avs_net TCP and UDP sockets.\n *\n * Requires either a UNIX-like operating environment, or a compatibility layer\n * with a high degree of compatibility with standard BSD sockets with an\n * appropriate compatibility header (see @ref AVS_COMMONS_POSIX_COMPAT_HEADER) -\n * lwIP and Winsock are currently supported for this scenario.\n */\n#define AVS_COMMONS_NET_WITH_POSIX_AVS_SOCKET\n\n/**\n * @deprecated This is deprecated API. This API can be removed in future\n * releases, without any notice.\n *\n * Enables support for logging socket communication to file.\n *\n * If this option is enabled, avs_net_socket_debug() can be used to enable\n * logging all communication to a file called DEBUG.log. If disabled,\n * avs_net_socket_debug() will always return an error.\n */\n/* #undef AVS_COMMONS_NET_WITH_SOCKET_LOG */\n\n/**\n * @experimental This is experimental API. This API can change in the future\n * versions without any notice.\n *\n * Enables support for custom net traffic interceptor.\n *\n * If this option is enabled, the user needs to provide their own implementation\n * of avs_net_create_traffic_interceptor() function which can wrap and extend\n * an underlying socket.\n */\n/* #undef AVS_COMMONS_WITH_TRAFFIC_INTERCEPTOR */\n\n/**\n * If the TLS backend is either mbed TLS or OpenSSL, enables support for (D)TLS\n * session persistence.\n *\n * Session persistence is not currently supported for the TinyDTLS backend.\n */\n#define AVS_COMMONS_NET_WITH_TLS_SESSION_PERSISTENCE\n/**@}*/\n\n/**\n * Options related to avs_net's default implementation of TCP and UDP sockets.\n *\n * These options make sense only when @ref AVS_COMMONS_NET_WITH_POSIX_AVS_SOCKET\n * is enabled. They describe capabilities of the Unix-like environment in which\n * the library is built.\n *\n * Note that if @ref AVS_COMMONS_POSIX_COMPAT_HEADER is defined, it might\n * redefine these flags independently of the settings in this file.\n */\n/**@{*/\n/**\n * Is the <c>gai_strerror()</c> function available?\n *\n * Enabling this flag will provide more detailed log messages in case that\n * <c>getaddrinfo()</c> fails. If this flag is disabled, numeric error codes\n * values will be logged.\n */\n/* #undef AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_GAI_STRERROR */\n\n/**\n * Is the <c>getifaddrs()</c> function available?\n *\n * Disabling this flag will cause <c>avs_net_socket_interface_name()</c> to use\n * a less optimal implementation based on the <c>SIOCGIFCONF</c> ioctl.\n *\n * If <c>SIOCGIFCONF</c> is not defined, either, then\n * <c>avs_net_socket_interface_name()</c> will always return an error.\n */\n/* #undef AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_GETIFADDRS */\n\n/**\n * Is the <c>getnameinfo()</c> function available?\n *\n * Disabling this flag will cause <c>avs_net_socket_receive_from()</c>,\n * <c>avs_net_socket_accept()</c>,\n * <c>avs_net_resolved_endpoint_get_host_port()</c>,\n * <c>avs_net_resolved_endpoint_get_host()</c> and\n * <c>avs_net_resolve_host_simple()</c> to use a custom reimplementation of\n * <c>getnameinfo()</c> based on <c>inet_ntop()</c>.\n */\n/* #undef AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_GETNAMEINFO */\n\n/**\n * Is the <c>IN6_IS_ADDR_V4MAPPED</c> macro available and usable?\n *\n * Disabling this flag will cause a custom code that compares IPv6 addresses\n * with the <c>::ffff:0.0.0.0/32</c> mask to be used instead.\n */\n/* #undef AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_IN6_IS_ADDR_V4MAPPED */\n\n/**\n * Should be defined if IPv4-mapped IPv6 addresses (<c>::ffff:0.0.0.0/32</c>)\n * are <strong>NOT</strong> supported by the underlying platform.\n *\n * Enabling this flag will prevent avs_net from using IPv4-mapped IPv6 addresses\n * and instead re-open and re-bind the socket if a connection to an IPv4 address\n * is requested on a previously created IPv6 socket.\n *\n * This may result in otherwise redundant <c>socket()</c>, <c>bind()</c> and\n * <c>close()</c> system calls to be performed, but may be necessary for\n * interoperability with some platforms.\n */\n/* #undef AVS_COMMONS_NET_POSIX_AVS_SOCKET_WITHOUT_IN6_V4MAPPED_SUPPORT */\n\n/**\n * Is the <c>inet_ntop()</c> function available?\n *\n * Disabling this flag will cause an internal implementation of this function\n * adapted from BIND 4.9.4 to be used instead.\n */\n/* #undef AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_INET_NTOP */\n\n/**\n * Is the <c>poll()</c> function available?\n *\n * Disabling this flag will cause a less robust code based on <c>select()</c> to\n * be used instead.\n */\n/* #undef AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_POLL */\n\n/**\n * Is the <c>recvmsg()</c> function available?\n *\n * Disabling this flag will cause <c>recvfrom()</c> to be used instead. Note\n * that for UDP sockets, this will cause false positives for datagram truncation\n * detection (<c>AVS_EMSGSIZE</c>) to be reported when the received message is\n * exactly the size of the buffer.\n */\n/* #undef AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_RECVMSG */\n/**@}*/\n\n/**\n * Enable thread safety in avs_sched.\n *\n * Makes all scheduler accesses synchronized and thread-safe, at the cost of\n * requiring avs_compat_threading to be enabled, and higher resource usage.\n */\n/* #undef AVS_COMMONS_SCHED_THREAD_SAFE */\n\n/**\n * Enable support for file I/O in avs_stream.\n *\n * Disabling this flag will cause the functions declared in\n * <c>avs_stream_file.h</c> to not be defined.\n */\n/* #undef AVS_COMMONS_STREAM_WITH_FILE */\n\n/**\n * Enable usage of <c>backtrace()</c> and <c>backtrace_symbols()</c> when\n * reporting assertion failures from avs_unit.\n *\n * Requires the afore-mentioned GNU-specific functions to be available.\n *\n * If this flag is disabled, stack traces will not be displayed with assertion\n * failures.\n */\n/* #undef AVS_COMMONS_UNIT_POSIX_HAVE_BACKTRACE */\n\n/**\n * Options related to avs_utils.\n */\n/**@{*/\n/**\n * Enable the default implementation of avs_time_real_now() and\n * avs_time_monotonic_now().\n *\n * Requires an operating environment that supports a clock_gettime() call\n * compatible with POSIX.\n */\n/* #undef AVS_COMMONS_UTILS_WITH_POSIX_AVS_TIME */\n\n/**\n * Enable the default implementation of avs_malloc(), avs_free(), avs_calloc()\n * and avs_realloc() that forwards to system malloc(), free(), calloc() and\n * realloc() calls.\n *\n * You might disable this option if for any reason you need to use a custom\n * allocator.\n */\n#define AVS_COMMONS_UTILS_WITH_STANDARD_ALLOCATOR\n\n/**\n * Enable the alternate implementation of avs_malloc(), avs_free(), avs_calloc()\n * and avs_realloc() that uses system malloc(), free() and realloc() calls, but\n * includes additional fixup code that ensures proper alignment to\n * <c>AVS_ALIGNOF(avs_max_align_t)</c> (usually 8 bytes on common platforms).\n *\n * <c>AVS_COMMONS_UTILS_WITH_STANDARD_ALLOCATOR</c> and\n * <c>AVS_COMMONS_UTILS_WITH_ALIGNFIX_ALLOCATOR</c> cannot be enabled at the\n * same time.\n *\n * NOTE: This implementation is only intended for platforms where the system\n * allocator does not properly conform to the alignment requirements.\n *\n * It comes with an additional runtime costs:\n *\n * - <c>AVS_ALIGNOF(avs_max_align_t)</c> bytes (usually 8) of additional\n *   overhead for each allocated memory block\n * - Additional memmove() for every realloc() that returned a block that is not\n *   properly aligned\n * - avs_calloc() is implemented as avs_malloc() followed by an explicit\n *   memset(); this may be suboptimal on some platforms\n *\n * If these costs are unacceptable for you, you may want to consider fixing,\n * replacing or reconfiguring your system allocator for conformance, or\n * implementing a custom one instead.\n *\n * Please note that some code in avs_commons and dependent projects (e.g. Anjay)\n * may include runtime assertions for proper memory alignment that will be\n * triggered when using a non-conformant standard allocator. Such allocators are\n * relatively common in embedded SDKs. This \"alignfix\" allocator is intended to\n * work around these issues. On some platforms (e.g. x86) those alignment issues\n * may not actually cause any problems - so you may want to consider disabling\n * runtime assertions instead. Please carefully examine your target platform's\n * alignment requirements and behavior of misaligned memory accesses (including\n * 64-bit data types such as <c>int64_t</c> and <c>double</c>) before doing so.\n */\n/* #undef AVS_COMMONS_UTILS_WITH_ALIGNFIX_ALLOCATOR */\n\n/**@}*/\n\n#endif /* AVS_COMMONS_CONFIG_H */\n"
  },
  {
    "path": "example_configs/embedded_lwm2m10/avsystem/commons/lwip-posix-compat.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef COMPAT_H\n#define COMPAT_H\n\n/*\n * Example implementation of a AVS_COMMONS_POSIX_COMPAT_HEADER file required for\n * non-POSIX platforms that use LwIP 1.4.1.\n *\n * Contains all types/macros/symbols not defined in core C that are required\n * to compile avs_commons library.\n */\n\n#include \"lwipopts.h\"\n\n/* Provides lwIP's alternative errno header, used in avs_net_impl.c */\n#include \"lwip/errno.h\"\n\n/* Provides htons/ntohs/htonl/ntohl */\n#include \"lwip/inet.h\"\n\n/* Provides e.g. LWIP_VERSION_* macros used in net_impl.c */\n#include \"lwip/init.h\"\n\n/*\n * Provides:\n * - POSIX-compatible socket API, socklen_t,\n * - fcntl, F_GETFL, F_SETFL, O_NONBLOCK,\n * - select, struct fd_set, FD_SET, FD_CLEAR, FD_ISSET\n */\n#include \"lwip/sockets.h\"\n\n/* Provides getaddrinfo/freeaddrinfo/struct addrinfo */\n#include \"lwip/netdb.h\"\n\n#if LWIP_VERSION_MAJOR >= 2\n#    define AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_INET_NTOP\n#endif // LWIP_VERSION_MAJOR >= 2\n\ntypedef int sockfd_t;\n\n#endif /* COMPAT_H */\n"
  },
  {
    "path": "example_configs/embedded_lwm2m11/anjay/anjay_config.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ANJAY_CONFIG_H\n#define ANJAY_CONFIG_H\n\n/**\n * @file anjay_config.h\n *\n * Anjay library configuration.\n *\n * The preferred way to compile Anjay is to use CMake, in which case this file\n * will be generated automatically by CMake.\n *\n * However, to provide compatibility with various build systems used especially\n * by embedded platforms, it is alternatively supported to compile Anjay by\n * other means, in which case this file will need to be provided manually.\n *\n * <strong>NOTE: To compile this library without using CMake, you need to\n * configure avs_commons and avs_coap first. Please refer to documentation in\n * the <c>avs_commons_config.h</c> and <c>avs_coap_config.h</c> files (provided\n * in the repositories as <c>avs_commons_config.h.in</c> and\n * <c>avs_coap_config.h.in</c>, respectively) for details.</strong>\n *\n * <strong>Anjay requires the following avs_coap options to be enabled:</strong>\n *\n * - @c WITH_AVS_COAP_UDP and/or @c WITH_AVS_COAP_TCP\n * - @c WITH_AVS_COAP_STREAMING_API\n * - @c WITH_AVS_COAP_BLOCK is highly recommended\n * - @c WITH_AVS_COAP_OBSERVE (if @c ANJAY_WITH_OBSERVE is enabled)\n * - @c WITH_AVS_COAP_OSCORE (if @c ANJAY_WITH_COAP_OSCORE is enabled, available\n *   only as a commercial feature)\n *\n * <strong>Anjay requires the following avs_commons components to be\n * enabled:</strong>\n *\n * - All components required by avs_coap, see <c>avs_coap_config.h</c>\n * - @c avs_algorithm\n * - @c avs_stream\n * - @c avs_url\n * - @c avs_persistence is highly recommended\n * - @c avs_http (if @c ANJAY_WITH_HTTP_DOWNLOAD is enabled)\n * - @c avs_rbtree or @c avs_sorted_set (if @c ANJAY_WITH_OBSERVE or\n *   @c ANJAY_WITH_MODULE_ACCESS_CONTROL is enabled)\n *\n * In the repository, this file is provided as <c>anjay_config.h.in</c>,\n * intended to be processed by CMake. If editing this file manually, please copy\n * or rename it to <c>anjay_config.h</c> and for each of the\n * <c>\\#cmakedefine</c> directives, please either replace it with regular\n * <c>\\#define</c> to enable it, or comment it out to disable. You may also need\n * to replace variables wrapped in <c>\\@</c> signs with concrete values. Please\n * refer to the comments above each of the specific definition for details.\n *\n * If you are editing a file previously generated by CMake, these\n * <c>\\#cmakedefine</c>s will be already replaced by either <c>\\#define</c> or\n * commented out <c>\\#undef</c> directives.\n */\n\n/**\n * Enable logging in Anjay.\n *\n * If this flag is disabled, no logging is compiled into the binary at all.\n */\n#define ANJAY_WITH_LOGS\n\n/**\n * Enable TRACE-level logs in Anjay.\n *\n * Only meaningful if <c>ANJAY_WITH_LOGS</c> is enabled.\n */\n/* #undef ANJAY_WITH_TRACE_LOGS */\n\n/**\n * Enable core support for Access Control mechanisms.\n *\n * Requires separate implementation of the Access Control object itself.\n * Either the implementation available as part of\n * <c>ANJAY_WITH_MODULE_ACCESS_CONTROL</c>, or a custom application-provided one\n * may be used.\n */\n/* #undef ANJAY_WITH_ACCESS_CONTROL */\n\n/**\n * Enable automatic attribute storage.\n *\n * Requires <c>AVS_COMMONS_WITH_AVS_PERSISTENCE</c> to be enabled in avs_commons\n * configuration.\n */\n#define ANJAY_WITH_ATTR_STORAGE\n\n/**\n * Enable support for the <c>anjay_download()</c> API.\n */\n#define ANJAY_WITH_DOWNLOADER\n\n/**\n * Enable support for CoAP(S) downloads.\n *\n * Only meaningful if <c>ANJAY_WITH_DOWNLOADER</c> is enabled.\n */\n#define ANJAY_WITH_COAP_DOWNLOAD\n\n/**\n * Enable support for HTTP(S) downloads.\n *\n * Only meaningful if <c>ANJAY_WITH_DOWNLOADER</c> is enabled.\n */\n/* #undef ANJAY_WITH_HTTP_DOWNLOAD */\n\n/**\n * Enable support for the LwM2M Bootstrap Interface.\n */\n#define ANJAY_WITH_BOOTSTRAP\n\n/**\n * Enable support for the LwM2M Bootstrap-Pack operation.\n *\n * Requires <c>ANJAY_WITH_BOOTSTRAP</c> and <c>ANJAY_WITH_LWM2M12</c> to be\n * enabled.\n */\n/* #undef ANJAY_WITH_BOOTSTRAP_PACK */\n\n/**\n * Enable support for the LwM2M Discover operation.\n *\n * Note that it is required for full compliance with the LwM2M protocol.\n */\n#define ANJAY_WITH_DISCOVER\n\n/**\n * Enable support for the LwM2M Information Reporting interface (Observe and\n * Notify operations).\n *\n * Requires <c>WITH_AVS_COAP_OBSERVE</c> to be enabled in avs_coap\n * configuration.\n *\n * Note that it is required for full compliance with the LwM2M protocol.\n */\n#define ANJAY_WITH_OBSERVE\n\n/**\n * Enable support for measuring amount of LwM2M traffic\n * (<c>anjay_get_tx_bytes()</c>, <c>anjay_get_rx_bytes()</c>,\n * <c>anjay_get_num_incoming_retransmissions()</c> and\n * <c>anjay_get_num_outgoing_retransmissions()</c> APIs.\n */\n/* #undef ANJAY_WITH_NET_STATS */\n\n/**\n * Enable support for communication timestamp\n * (<c>anjay_get_server_last_registration_time()</c>\n * <c>anjay_get_server_next_update_time()</c> and\n * <c>anjay_get_server_last_communication_time()</c>) APIs.\n */\n/* #undef ANJAY_WITH_COMMUNICATION_TIMESTAMP_API */\n\n/**\n * Enable support for the <c>anjay_resource_observation_status()</c> API.\n */\n#define ANJAY_WITH_OBSERVATION_STATUS\n\n/**\n * Maximum number of servers observing a given Resource listed by\n * <c>anjay_resource_observation_status()</c> function.\n *\n * Only meaningful if <c>ANJAY_WITH_OBSERVATION_STATUS</c> is enabled.\n */\n#define ANJAY_MAX_OBSERVATION_SERVERS_REPORTED_NUMBER 0\n\n/**\n * Enable guarding of all accesses to <c>anjay_t</c> with a mutex.\n */\n/* #undef ANJAY_WITH_THREAD_SAFETY */\n\n/**\n * Enable standard implementation of an event loop.\n *\n * Requires C11 <c>stdatomic.h</c> header to be available, and either a platform\n * that provides a BSD-compatible socket API, or a compatibility layer file (see\n * <c>AVS_COMMONS_POSIX_COMPAT_HEADER</c> in <c>avs_commons_config.h</c>). This\n * is designed to best work with the defalt implementation of avs_net sockets\n * (see <c>AVS_COMMONS_NET_WITH_POSIX_AVS_SOCKET</c>), but alternative socket\n * implementations can also be used.\n *\n * The event loop is most useful when thread safety features\n * (@ref ANJAY_WITH_THREAD_SAFETY and <c>AVS_COMMONS_SCHED_THREAD_SAFE</c>) are\n * enabled as well, but this is not a hard requirement. See the documentation\n * for <c>anjay_event_loop_run()</c> for details.\n */\n#define ANJAY_WITH_EVENT_LOOP\n\n/**\n * Enable support for features new to LwM2M protocol version 1.1.\n */\n#define ANJAY_WITH_LWM2M11\n\n/**\n * Enable support for features new to LwM2M protocol version 1.2.\n *\n * Requires <c>ANJAY_WITH_LWM2M11</c> to be enabled.\n */\n/* #undef ANJAY_WITH_LWM2M12 */\n\n/**\n * Enable support for OSCORE-based security for LwM2M connections.\n *\n * Requires <c>ANJAY_WITH_LWM2M11</c> to be enabled, and\n * <c>WITH_AVS_COAP_OSCORE</c> to be enabled in avs_coap configuration.\n *\n * IMPORTANT: Only available as part of the OSCORE commercial feature. Ignored\n * in the open source version.\n */\n/* #undef ANJAY_WITH_COAP_OSCORE */\n\n/**\n * Enable support for Observation Attributes.\n *\n * Requires <c>ANJAY_WITH_OBSERVE</c> and <c>ANJAY_WITH_LWM2M12</c> to be\n * enabled.\n */\n/* #undef ANJAY_WITH_OBSERVATION_ATTRIBUTES */\n\n/**\n * Enable support for the LwM2M Send operation.\n *\n * Requires <c>ANJAY_WITH_LWM2M11</c> and either <c>ANJAY_WITH_SENML_JSON</c> or\n * <c>ANJAY_WITH_CBOR</c> to be enabled.\n */\n#define ANJAY_WITH_SEND\n\n/**\n * Enable support for the SMS binding and the SMS trigger mechanism.\n *\n * Requires <c>WITH_AVS_COAP_UDP</c> to be enabled in avs_coap configuration.\n *\n * IMPORTANT: Only available as part of the SMS commercial feature. Ignored in\n * the open source version.\n */\n/* #undef ANJAY_WITH_SMS */\n\n/**\n * Enable support for sending and receiving multipart SMS messages.\n *\n * Requires <c>ANJAY_WITH_SMS</c> to be enabled.\n *\n * IMPORTANT: Only available as part of the SMS commercial feature. Ignored in\n * the open source version.\n */\n/* #undef ANJAY_WITH_SMS_MULTIPART */\n\n/**\n * Enable support for Non-IP Data Delivery.\n *\n * Requires <c>ANJAY_WITH_LWM2M11</c> to be enabled, and\n * <c>WITH_AVS_COAP_UDP</c> to be enabled in avs_coap configuration.\n *\n * IMPORTANT: Only available as a commercial feature. Ignored in the open\n * source version.\n */\n/* #undef ANJAY_WITH_NIDD */\n\n/**\n * Enable support for core state persistence\n * (<c>anjay_new_from_core_persistence()</c> and\n * <c>anjay_delete_with_core_persistence()</c> APIs).\n *\n * Requires <c>ANJAY_WITH_OBSERVE</c> to be enabled,\n * <c>AVS_COMMONS_WITH_AVS_PERSISTENCE</c> to be enabled in avs_commons, and\n * <c>WITH_AVS_COAP_OBSERVE_PERSISTENCE</c> to be enabled in avs_coap\n * configuration.\n *\n * IMPORTANT: Only available as a commercial feature. Ignored in the open\n * source version.\n */\n/* #undef ANJAY_WITH_CORE_PERSISTENCE */\n\n/**\n * Disable automatic closing of server connection sockets after\n * MAX_TRANSMIT_WAIT of inactivity.\n */\n/* #undef ANJAY_WITHOUT_QUEUE_MODE_AUTOCLOSE */\n\n/**\n * Enable support for CoAP Content-Format numerical values 1541-1544 that have\n * been used before final LwM2M TS 1.0.\n */\n/* #undef ANJAY_WITH_LEGACY_CONTENT_FORMAT_SUPPORT */\n\n/**\n * Enable support for JSON format as specified in LwM2M TS 1.0.\n *\n * NOTE: Anjay is only capable of generating this format, there is no parsing\n * support regardless of the state of this option.\n */\n/* #undef ANJAY_WITH_LWM2M_JSON */\n\n/**\n * Disable support for TLV format as specified in LwM2M TS 1.0.\n *\n * NOTE: LwM2M Client using LwM2M 1.0 MUST support this format. It may be\n * disabled if LwM2M 1.1 is used and SenML JSON or SenML CBOR is enabled.\n */\n/* #undef ANJAY_WITHOUT_TLV */\n\n/**\n * Disable support for Plain Text format as specified in LwM2M TS 1.0 and 1.1.\n *\n * NOTE: LwM2M Client SHOULD support this format. It may be disabled to reduce\n * library size if LwM2M Server is configured to not use it in requests.\n */\n/* #undef ANJAY_WITHOUT_PLAINTEXT */\n\n/**\n * Disable use of the Deregister message.\n */\n/* #undef ANJAY_WITHOUT_DEREGISTER */\n\n/**\n * Disable support for \"IP stickiness\", i.e. preference of the same IP address\n * when reconnecting to a server using a domain name.\n */\n/* #undef ANJAY_WITHOUT_IP_STICKINESS */\n\n/**\n * Enable support for SenML JSON format, as specified in LwM2M TS 1.1.\n *\n * NOTE: As opposed to <c>ANJAY_WITH_LWM2M_JSON</c>, both generating and parsing\n * is supported.\n *\n * Requires <c>ANJAY_WITH_LWM2M11</c> to be enabled.\n */\n#define ANJAY_WITH_SENML_JSON\n\n/**\n * Enable support for CBOR and SenML CBOR formats, as specified in LwM2M TS 1.1.\n *\n * Requires <c>ANJAY_WITH_LWM2M11</c> to be enabled.\n */\n#define ANJAY_WITH_CBOR\n\n/**\n * Enable support for Enrollment over Secure Transport.\n *\n * Requires <c>ANJAY_WITH_BOOTSTRAP</c> to be enabled.\n *\n * IMPORTANT: Only available as part of the EST commercial feature. Ignored in\n * the open source version.\n */\n/* #undef ANJAY_WITH_EST */\n\n/**\n * Enable support for hardware security engine in the EST subsystem.\n *\n * Requires <c>ANJAY_WITH_EST</c> to be enabled in Anjay configuration and\n * <c>AVS_COMMONS_WITH_AVS_CRYPTO_PKI_ENGINE</c> to be enabled in avs_commons\n * configuration.\n *\n * IMPORTANT: Only available in commercial versions that include both the EST\n * and HSM features. Ignored in versions distributed without these features.\n */\n/* #undef ANJAY_WITH_EST_ENGINE_SUPPORT */\n\n/**\n * Enable support for the Confirmable Notification attribute, as specified in\n * LwM2M TS 1.2.\n *\n * Before TS 1.2, this has been supported in Anjay as a custom extension, and\n * thus it is available independently from TS 1.2 support itself, including in\n * the open source version.\n *\n * Requires <c>ANJAY_WITH_OBSERVE</c> to be enabled.\n */\n/* #undef ANJAY_WITH_CON_ATTR */\n\n/**\n * Enable support for handling security credentials in the data model using\n * structured <c>avs_crypto</c> types.\n *\n * If the <c>security</c> module is also enabled (see @ref\n * ANJAY_WITH_MODULE_SECURITY), it also enables support for passing these\n * credentials through such structured types when adding Security object\n * instances via the @ref anjay_security_instance_t structure.\n */\n#define ANJAY_WITH_SECURITY_STRUCTURED\n\n/**\n * Maximum size in bytes supported for the \"Public Key or Identity\" resource in\n * the LwM2M Security object.\n *\n * If editing this file manually, <c>256</c> shall be\n * replaced with a positive integer literal.\n *\n * The default value defined in CMake build scripts is 2048.\n * Minimal suggested setting for low-resource builds is 256.\n */\n#define ANJAY_MAX_PK_OR_IDENTITY_SIZE 256\n\n/**\n * Maximum size in bytes supported for the \"Secret Key\" resource in the LwM2M\n * Security Object.\n *\n * If editing this file manually, <c>128</c> shall be replaced\n * with a positive integer literal.\n *\n * The default value defined in CMake build scripts is 256.\n * Minimal suggested setting for low-resource builds is 128.\n */\n#define ANJAY_MAX_SECRET_KEY_SIZE 128\n\n/**\n * Maximum length supported for stringified floating-point values.\n *\n * Used when parsing plaintext and SenML JSON content formats - when parsing a\n * floating-point value, any string of length equal or greater than this setting\n * will automatically be considered invalid, even if it could in theory be\n * parsed as a valid number.\n *\n * If editing this file manually, <c>64</c> shall be\n * replaced with a positive integer literal.\n *\n * The default value defined in CMake build scripts is 512.\n * Minimal suggested setting for low-resource builds is 64.\n */\n#define ANJAY_MAX_DOUBLE_STRING_SIZE 64\n\n/**\n * Maximum length supported for a single Uri-Path or Location-Path segment.\n *\n * When handling incoming CoAP messages, any Uri-Path or Location-Path option of\n * length equal or greater than this setting will be considered invalid.\n *\n * If editing this file manually, <c>64</c> shall be\n * replaced with a positive integer literal.\n *\n * The default value defined in CMake build scripts is 256.\n * Minimal suggested setting for low-resource builds is 64.\n */\n#define ANJAY_MAX_URI_SEGMENT_SIZE 64\n\n/**\n * Maximum length supported for a single Uri-Query segment.\n *\n * When handling incoming CoAP messages, any Uri-Query option of length equal or\n * greater than this setting will be considered invalid.\n *\n * If editing this file manually, <c>64</c> shall be\n * replaced with a positive integer literal.\n *\n * The default value defined in CMake build scripts is 256.\n * Minimal suggested setting for low-resource builds is 64.\n */\n#define ANJAY_MAX_URI_QUERY_SEGMENT_SIZE 64\n\n/**\n * Size of buffer allocated for storing DTLS session state when connection is\n * not in use (e.g. during queue mode operation).\n *\n * If editing this file manually, <c>1024</c> shall be\n * replaced with a positive integer literal. The default value defined in CMake\n * build scripts is 1024.\n */\n#define ANJAY_DTLS_SESSION_BUFFER_SIZE 1024\n\n/**\n * Value of Content-Format used in Send messages. Only a few specific values are\n * supported:\n *\n * - @c AVS_COAP_FORMAT_NONE means no default value is used and Anjay will\n *   decide the format based on the what is available.\n * - @c AVS_COAP_FORMAT_OMA_LWM2M_CBOR Anjay will generate a Send message in\n *   LwM2M CBOR format.\n * - @c AVS_COAP_FORMAT_SENML_CBOR Anjay will generate a Send message in SenML\n *   CBOR format.\n * - @c AVS_COAP_FORMAT_SENML_JSON Anjay will generate a Send message in SenML\n *   JSON format.\n *\n * Note that to use a specific format it must be available during compilation.\n *\n * The default value defined in CMake build scripts is\n * <c>AVS_COAP_FORMAT_NONE</c>.\n */\n#define ANJAY_DEFAULT_SEND_FORMAT AVS_COAP_FORMAT_NONE\n\n/**\n * Optional Anjay modules.\n */\n/**@{*/\n/**\n * Enable access_control module (implementation of the Access Control object).\n *\n * Requires <c>ANJAY_WITH_ACCESS_CONTROL</c> to be enabled.\n */\n/* #undef ANJAY_WITH_MODULE_ACCESS_CONTROL */\n\n/**\n * Enable security module (implementation of the LwM2M Security object).\n */\n#define ANJAY_WITH_MODULE_SECURITY\n\n/**\n * Enable support for hardware security engine in the security module.\n *\n * This feature allows security credentials provisioned into the LwM2M Security\n * object to be automatically moved into a hardware security module.\n *\n * Requires <c>ANJAY_WITH_MODULE_SECURITY</c> to be enabled in Anjay\n * configuration, and at least one of\n * <c>AVS_COMMONS_WITH_AVS_CRYPTO_PKI_ENGINE</c> or\n * <c>AVS_COMMONS_WITH_AVS_CRYPTO_PSK_ENGINE</c> to be enabled in avs_commons\n * configuration.\n *\n * IMPORTANT: Only available as part of the HSM support commercial feature.\n * Ignored in versions distributed without this feature.\n */\n/* #undef ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT */\n\n/**\n * Enable server module (implementation of the LwM2M Server object).\n */\n#define ANJAY_WITH_MODULE_SERVER\n\n/**\n * Enable fw_update module (implementation of the Firmware Update object).\n */\n#define ANJAY_WITH_MODULE_FW_UPDATE\n\n/**\n * Enable advanced_fw_update module (implementation of the 33629 custom\n * Advanced Firmware Update object).\n */\n/* #undef ANJAY_WITH_MODULE_ADVANCED_FW_UPDATE */\n\n/**\n * Disable support for PUSH mode Firmware Update.\n *\n * Only meaningful if <c>ANJAY_WITH_MODULE_FW_UPDATE</c> is enabled. Requires\n * <c>ANJAY_WITH_DOWNLOADER</c> to be enabled.\n */\n/* #undef ANJAY_WITHOUT_MODULE_FW_UPDATE_PUSH_MODE */\n\n/**\n * Enable sw_mgmt module (implementation of the Software Management object).\n */\n/* #undef ANJAY_WITH_MODULE_SW_MGMT */\n\n/**\n * Enables ipso_objects module (generic implementation of basic sensor, three\n * axis sensor and Push Button IPSO objects).\n */\n#define ANJAY_WITH_MODULE_IPSO_OBJECTS\n\n/**\n * Enables experimental ipso_objects_v2 module (generic implementation of basic\n * sensor and three axis sensor IPSO objects).\n */\n#define ANJAY_WITH_MODULE_IPSO_OBJECTS_V2\n\n/**\n * Enable at_sms module (implementation of an SMS driver for AT modem devices).\n *\n * Requires <c>ANJAY_WITH_SMS</c> to be enabled and the operating system to\n * support the POSIX <c>poll()</c> function.\n *\n * IMPORTANT: Only available as part of the SMS commercial feature. Ignored in\n * the open source version.\n */\n/* #undef ANJAY_WITH_MODULE_AT_SMS */\n\n/**\n * Enable bg96_nidd module (implementation of NB-IoT-based NIDD driver for\n * Quectel BG96).\n *\n * Requires <c>ANJAY_WITH_NIDD</c> to be enabled.\n *\n * IMPORTANT: Only available as part of the NIDD commercial feature. Ignored\n * in the open source version.\n */\n/* #undef ANJAY_WITH_MODULE_BG96_NIDD */\n\n/**\n * Enable bootstrapper module (loader for bootstrap information formatted as per\n * the \"Storage of LwM2M Bootstrap Information on the Smartcard\" appendix to the\n * LwM2M TS).\n *\n * Requires <c>ANJAY_WITH_BOOTSTRAP</c> to be enabled and\n * <c>AVS_COMMONS_WITH_AVS_PERSISTENCE</c> to be enabled in avs_commons\n * configuration.\n *\n * IMPORTANT: Only available with the bootstrapper feature. Ignored in the open\n * source version.\n */\n/* #undef ANJAY_WITH_MODULE_BOOTSTRAPPER */\n\n/**\n * Enable the SIM bootstrap module, which enables reading the SIM bootstrap\n * information from a smartcard, which can then be passed through to the\n * bootstrapper module.\n *\n * Requires <c>ANJAY_WITH_MODULE_BOOTSTRAPPER</c> to be enabled.\n *\n * IMPORTANT: Only available with the bootstrapper feature. Ignored in the open\n * source version.\n */\n/* #undef ANJAY_WITH_MODULE_SIM_BOOTSTRAP */\n\n/**\n * Forced ID of the file to read the SIM bootstrap information from.\n *\n * If not defined (default), the bootstrap information file will be discovered\n * through the ODF file, as mandated by the specification.\n *\n * Requires <c>ANJAY_WITH_MODULE_BOOTSTRAPPER</c> to be enabled. At most one of\n * <c>ANJAY_MODULE_SIM_BOOTSTRAP_HARDCODED_FILE_ID</c> and\n * <c>ANJAY_MODULE_SIM_BOOTSTRAP_DATA_OID_OVERRIDE_HEX</c> may be defined at the\n * same time.\n *\n * IMPORTANT: Only available with the bootstrapper feature. Ignored in the open\n * source version.\n */\n/* #undef ANJAY_MODULE_SIM_BOOTSTRAP_HARDCODED_FILE_ID */\n\n/**\n * Overridden OID of the SIM bootstrap information to look for in the DODF file,\n * expressed as a hexlified DER representation (without the header).\n *\n * This is the hexlified expected value of the 'id' field within the 'OidDO'\n * sequence in the DODF file (please refer to the PKCS #15 document for more\n * information).\n *\n * If not defined, the default value of <c>\"672b0901\"</c>, which corresponds to\n * OID 2.23.43.9.1 {joint-iso-itu-t(2) international-organizations(23) wap(43)\n * oma-lwm2m(9) lwm2m-bootstrap(1)}, will be used.\n *\n * No other values than the default are valid according to the specification,\n * but some SIM cards are known to use other non-standard values, e.g.\n * <c>\"0604672b0901\"</c> - including a superfluous nested BER-TLV header, as\n * erroneously illustrated in the EF(DODF-bootstrap) file coding example in\n * LwM2M TS 1.2 and earlier (fixed in LwM2M TS 1.2.1) - which is interpreted as\n * OID 0.6.4.103.43.9.1 (note that it is invalid as the 0.6 tree does not exist\n * in the repository as of writing this note).\n *\n * Requires <c>ANJAY_WITH_MODULE_BOOTSTRAPPER</c> to be enabled. At most one of\n * <c>ANJAY_MODULE_SIM_BOOTSTRAP_HARDCODED_FILE_ID</c> and\n * <c>ANJAY_MODULE_SIM_BOOTSTRAP_DATA_OID_OVERRIDE_HEX</c> may be defined at the\n * same time.\n *\n * IMPORTANT: Only available with the bootstrapper feature. Ignored in the open\n * source version.\n */\n/* #undef ANJAY_MODULE_SIM_BOOTSTRAP_DATA_OID_OVERRIDE_HEX */\n\n/**\n * Enable factory provisioning module. Data provided during provisioning uses\n * SenML CBOR format.\n */\n/* #undef ANJAY_WITH_MODULE_FACTORY_PROVISIONING */\n\n/**\n * Enable oscore module (implementation of the OSCORE object).\n *\n * IMPORTANT: Only available as part of the OSCORE commercial feature. Ignored\n * in the open source version.\n */\n/* #undef ANJAY_WITH_MODULE_OSCORE */\n\n/**\n * If enable Anjay doesn't handle composite operation (read, observe and write).\n * Its use makes sense for LWM2M v1.1 upwards.\n *\n * This flag can be used to reduce the size of the resulting code.\n *\n * If active, anjay will respond with message code 5.01 Not Implemented to any\n * composite type request.\n */\n/* #undef ANJAY_WITHOUT_COMPOSITE_OPERATIONS */\n\n/**\n * Enable support for the experimental\n * <c>anjay_get_server_connection_status()</c> API and related\n * <c>anjay_server_connection_status_cb_t</c> callback.\n */\n/* #undef ANJAY_WITH_CONN_STATUS_API */\n\n/**\n * Enable support for the experimental\n * <c>anjay_ssl_error_cb_t</c> callback.\n * Currently only supported for mbedTLS and custom SSL integration builds\n */\n#define ANJAY_WITH_SSL_ERROR_API\n\n/**\n * Enable support for /25 LwM2M Gateway Object.\n *\n * Requires <c>ANJAY_WITH_LWM2M11</c> to be enabled.\n * Requires <c>ANJAY_WITH_CORE_PERSISTENCE</c> (commercial feature) to be\n * disabled.\n */\n/* #undef ANJAY_WITH_LWM2M_GATEWAY */\n\n/**\n * Maximum holdoff time in seconds.\n *\n * This parameter defines an upper limit on the Hold Off Time as specified in\n * the LwM2M Security Object (Object ID: 0, Resource ID: 11). The Hold Off Time\n * is used by the LwM2M Client to delay initiating a Bootstrap process.\n *\n * Limiting the Hold Off Time is important in network environments where\n * prolonged delays may cause NAT bindings to expire, potentially leading to\n * failed connection attempts. This parameter ensures that the configured\n * Hold Off Time does not exceed a safe threshold. If a DTLS binding with\n * Connection ID is used this parameter can be increased safely.\n *\n * If editing this file manually, <c>20</c> shall be\n * replaced with a positive integer literal representing the time in seconds.\n * The default value defined in CMake build scripts is 20.\n */\n#define ANJAY_MAX_HOLDOFF_TIME 20\n\n/**\n * Enable support for optional resources of the Firmware Update object\n * introduced in LwM2M 1.1.\n *\n * This includes the following resources:\n * - Severity\n * - Last State Change Time\n * - Max Defer Period\n *\n * Requires <c>ANJAY_WITH_LWM2M11</c> to be enabled.\n */\n/* #undef ANJAY_WITH_MODULE_FW_UPDATE_V11_RESOURCES */\n\n/**@}*/\n\n#endif // ANJAY_CONFIG_H\n"
  },
  {
    "path": "example_configs/embedded_lwm2m11/avsystem/coap/avs_coap_config.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef AVS_COAP_CONFIG_H\n#define AVS_COAP_CONFIG_H\n\n/**\n * @file avs_coap_config.h\n *\n * avs_coap library configuration.\n *\n * The preferred way to compile avs_coap is to use CMake, in which case this\n * file will be generated automatically by CMake.\n *\n * However, to provide compatibility with various build systems used especially\n * by embedded platforms, it is alternatively supported to compile avs_coap by\n * other means, in which case this file will need to be provided manually.\n *\n * <strong>NOTE: To compile this library without using CMake, you need to\n * configure avs_commons first. Please refer to documentation in the\n * <c>avs_commons_config.h</c> file (provided in the repository as\n * <c>avs_commons_config.h.in</c>) for details.</strong>\n *\n * <strong>avs_coap requires the following avs_commons components to be\n * enabled:</strong>\n *\n * - @c avs_buffer\n * - @c avs_compat_threading\n * - @c avs_list\n * - @c avs_net\n * - @c avs_sched\n * - @c avs_stream\n * - @c avs_utils\n * - @c avs_log (if @c WITH_AVS_COAP_LOGS is enabled)\n * - @c avs_persistence (if @c WITH_AVS_COAP_OBSERVE_PERSISTENCE is enabled)\n * - @c avs_crypto (if @c WITH_AVS_COAP_OSCORE is enabled)\n *\n * In the repository, this file is provided as <c>avs_coap_config.h.in</c>,\n * intended to be processed by CMake. If editing this file manually, please copy\n * or rename it to <c>avs_coap_config.h</c> and for each of the\n * <c>\\#cmakedefine</c> directives, please either replace it with regular\n * <c>\\#define</c> to enable it, or comment it out to disable. You may also need\n * to replace variables wrapped in <c>\\@</c> signs with concrete values. Please\n * refer to the comments above each of the specific definition for details.\n *\n * If you are editing a file previously generated by CMake, these\n * <c>\\#cmakedefine</c>s will be already replaced by either <c>\\#define</c> or\n * commented out <c>\\#undef</c> directives.\n */\n\n/**\n * Enable support for block-wise transfers (RFC 7959).\n *\n * If this flag is disabled, attempting to send a message bigger than the\n * internal buffer will fail; incoming messages may still carry BLOCK1 or BLOCK2\n * options, but they will not be interpreted by the library in any way.\n */\n#define WITH_AVS_COAP_BLOCK\n\n/**\n * Enable support for observations (RFC 7641).\n */\n#define WITH_AVS_COAP_OBSERVE\n\n/**\n * Turn on cancelling observation on a timeout.\n *\n * Only meaningful if <c>WITH_AVS_COAP_OBSERVE</c> is enabled.\n *\n * NOTE: LwM2M specification requires LwM2M server to send Cancel Observation\n * request. Meanwhile CoAP RFC 7641 states that timeout on notification should\n * cancel it. This setting is to enable both of these behaviors with default\n * focused on keeping observations in case of bad connectivity.\n */\n/* #undef WITH_AVS_COAP_OBSERVE_CANCEL_ON_TIMEOUT */\n\n/**\n * Force cancelling observation, even if a confirmable notification yielding\n * an error response is not acknowledged or rejected with RST by the observer.\n *\n * This is a circumvention for some non-compliant servers that respond with an\n * RST message to a confirmable notification yielding an error response. This\n * setting makes the library cancel the observation in such cases, even though\n * the notification is formally rejected. Additionally, it will also make the\n * library cancel the observation if no response is received at all.\n */\n#define WITH_AVS_COAP_OBSERVE_FORCE_CANCEL_ON_UNACKED_ERROR\n\n/**\n * Enable support for observation persistence (<c>avs_coap_observe_persist()</c>\n * and <c>avs_coap_observe_restore()</c> calls).\n *\n * Only meaningful if <c>WITH_AVS_COAP_OBSERVE</c> is enabled.\n */\n#define WITH_AVS_COAP_OBSERVE_PERSISTENCE\n\n/**\n * Enable support for the streaming API\n */\n#define WITH_AVS_COAP_STREAMING_API\n\n/**\n * Enable support for UDP transport.\n *\n * NOTE: Enabling at least one transport is necessary for the library to be\n * useful.\n */\n#define WITH_AVS_COAP_UDP\n\n/**\n * Enable support for TCP transport (RFC 8323).\n *\n * NOTE: Enabling at least one transport is necessary for the library to be\n * useful.\n */\n#define WITH_AVS_COAP_TCP\n\n/**\n * Enable support for OSCORE (RFC 8613).\n *\n * IMPORTANT: Only available with the OSCORE feature. Ignored in the open\n * source version.\n */\n/* #undef WITH_AVS_COAP_OSCORE */\n\n/**\n * Use OSCORE version from draft-ietf-core-object-security-08 instead of the\n * final version (RFC 8613).\n *\n * Only meaningful if <c>WITH_AVS_COAP_OSCORE</c> is enabled.\n *\n * IMPORTANT: Only available with the OSCORE feature. Ignored in the open\n * source version.\n */\n/* #undef WITH_AVS_COAP_OSCORE_DRAFT_8 */\n\n/**\n * Maximum size in bytes supported for the Master Secret.\n *\n * If editing this file manually, set it to a positive integer literal.\n *\n * The default value defined in CMake build scripts is 32.\n *\n * IMPORTANT: Only available with the OSCORE feature. Ignored in the open\n * source version.\n */\n/* #undef AVS_COAP_OSCORE_MASTER_SECRET_SIZE */\n\n/**\n * Maximum size in bytes supported for the Master Salt.\n *\n * If editing this file manually, set it to a positive integer literal.\n *\n * The default value defined in CMake build scripts is 16.\n *\n * IMPORTANT: Only available with the OSCORE feature. Ignored in the open\n * source version.\n */\n/* #undef AVS_COAP_OSCORE_MASTER_SALT_SIZE */\n\n/**\n * Maximum number of notification tokens stored to match Reset responses to.\n *\n * Only meaningful if <c>WITH_AVS_COAP_OBSERVE</c> and <c>WITH_AVS_COAP_UDP</c>\n * are enabled.\n *\n * If editing this file manually, <c>4</c> shall be\n * replaced with a positive integer literal. The default value defined in CMake\n * build scripts is 4.\n */\n#define AVS_COAP_UDP_NOTIFY_CACHE_SIZE 4\n\n/**\n * Enable sending diagnostic payload in error responses.\n */\n#define WITH_AVS_COAP_DIAGNOSTIC_MESSAGES\n\n/**\n * Enable logging in avs_coap.\n *\n * If this flag is disabled, no logging is compiled into the binary at all.\n */\n#define WITH_AVS_COAP_LOGS\n\n/**\n * Enable TRACE-level logs in avs_coap.\n *\n * Only meaningful if <c>WITH_AVS_COAP_LOGS</c> is enabled.\n */\n/* #undef WITH_AVS_COAP_TRACE_LOGS */\n\n/**\n * Enable poisoning of unwanted symbols when compiling avs_coap.\n *\n * Requires a compiler that supports <c>\\#pragma GCC poison</c>.\n *\n * This is mostly useful during development, to ensure that avs_commons do not\n * attempt to call functions considered harmful in this library, such as printf.\n * This is not guaranteed to work as intended on every platform.\n */\n/* #undef WITH_AVS_COAP_POISONING */\n\n#endif // AVS_COAP_CONFIG_H\n"
  },
  {
    "path": "example_configs/embedded_lwm2m11/avsystem/commons/avs_commons_config.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AVS_COMMONS_CONFIG_H\n#define AVS_COMMONS_CONFIG_H\n\n/**\n * @file avs_commons_config.h\n *\n * avs_commons library configuration.\n *\n * The preferred way to compile avs_commons is to use CMake, in which case this\n * file will be generated automatically by CMake.\n *\n * However, to provide compatibility with various build systems used especially\n * by embedded platforms, it is alternatively supported to compile avs_commons\n * by other means, in which case this file will need to be provided manually.\n *\n * In the repository, this file is provided as <c>avs_commons_config.h.in</c>,\n * intended to be processed by CMake. If editing this file manually, please copy\n * or rename it to <c>avs_commons_config.h</c> and for each of the\n * <c>#cmakedefine</c> directives, please either replace it with regular\n * <c>#define</c> to enable it, or comment it out to disable. You may also need\n * to replace variables wrapped in <c>@</c> signs with concrete values. Please\n * refer to the comments above each of the specific definition for details.\n *\n * If you are editing a file previously generated by CMake, these\n * <c>#cmakedefine</c>s will be already replaced by either <c>#define</c> or\n * commented out <c>#undef</c> directives.\n */\n\n/**\n * Options that describe capabilities of the build environment.\n *\n * NOTE: If you leave some of these macros undefined, even though the given\n * feature is actually available in the system, avs_commons will attempt to use\n * its own substitutes, which may be incompatible with the definition in the\n * system and lead to undefined behaviour.\n */\n/**@{*/\n/**\n * Is the target platform big-endian?\n *\n * If undefined, little-endian is assumed. Mixed-endian architectures are not\n * supported.\n *\n * Affects <c>avs_convert_be*()</c> and <c>avs_[hn]to[hn]*()</c> calls in\n * avs_utils and, by extension, avs_persistence.\n */\n/* #undef AVS_COMMONS_BIG_ENDIAN */\n\n/**\n * Is GNU __builtin_add_overflow() extension available?\n *\n * Affects time handling functions in avs_utils. If disabled, software overflow\n * checking will be compiled. Note that this software overflow checking code\n * relies on U2 representation of signed integers.\n */\n/* #undef AVS_COMMONS_HAVE_BUILTIN_ADD_OVERFLOW */\n\n/**\n * Is GNU __builtin_mul_overflow() extension available?\n *\n * Affects time handling functions in avs_utils. If disabled, software overflow\n * checking will be compiled. Note that this software overflow checking code\n * relies on U2 representation of signed integers.\n */\n/* #undef AVS_COMMONS_HAVE_BUILTIN_MUL_OVERFLOW */\n\n/**\n * Is net/if.h available in the system?\n *\n * NOTE: If the header is indeed available, but this option is not defined, the\n * <c>IF_NAMESIZE</c> macro will be defined <strong>publicly by avs_commons\n * headers</strong>, which may conflict with system definitions.\n */\n/* #undef AVS_COMMONS_HAVE_NET_IF_H */\n\n/**\n * Are GNU diagnostic pragmas (#pragma GCC diagnostic push/pop/ignored)\n * available?\n *\n * If defined, those pragmas will be used to suppress compiler warnings for some\n * code known to generate them and cannot be improved in a more robust way, e.g.\n * for code that is known to generate warnings from within system headers.\n */\n/* #undef AVS_COMMONS_HAVE_PRAGMA_DIAGNOSTIC */\n\n/**\n * Are GNU visibility pragmas (#pragma GCC visibility push/pop) available?\n *\n * Meaningful mostly if avs_commons will be directly or indirectly linked into\n * a shared library. Causes all symbols except those declared in public headers\n * to be hidden, i.e. not exported outside the shared library. If not defined,\n * default compiler visibility settings will be used, but you still may use\n * compiler flags and linker version scripts to replicate this manually if\n * needed.\n */\n/* #undef AVS_COMMONS_HAVE_VISIBILITY */\n\n/**\n * Specify an optional compatibility header that allows use of POSIX-specific\n * code that is not compliant with POSIX enough to be compiled directly.\n *\n * This header, if specified, will be included only by the following components,\n * which may be enabled or disabled depending on state of the referenced flags:\n * - avs_compat_threading implementation based on POSIX Threads\n *   (@ref AVS_COMMONS_COMPAT_THREADING_WITH_PTHREAD)\n * - default avs_net socket implementation\n *   (@ref AVS_COMMONS_NET_WITH_POSIX_AVS_SOCKET)\n * - avs_unit (@ref AVS_COMMONS_WITH_AVS_UNIT)\n * - default implementation of avs_time routines\n *   (@ref AVS_COMMONS_UTILS_WITH_POSIX_AVS_TIME)\n *\n * Compatibility headers for lwIP and Microsoft Windows are provided with the\n * library (see the <c>compat</c> directory).\n *\n * If this macro is not defined, the afore-mentioned components, if enabled,\n * will use system headers directly, assuming they are POSIX-compliant.\n *\n * If this macro is enabled, the specified file will be included through an\n * <c>#include AVS_COMMONS_POSIX_COMPAT_HEADER</c> statement. Thus, if editing\n * this file manually, <c>avsystem/commons/lwip-posix-compat.h</c> shall be\n * replaced with a path to such file.\n */\n#define AVS_COMMONS_POSIX_COMPAT_HEADER \"avsystem/commons/lwip-posix-compat.h\"\n\n/**\n * Set if printf implementation doesn't support 64-bit format specifiers.\n * If defined, custom implementation of conversion is used in\n * @c AVS_UINT64_AS_STRING instead of using @c snprintf .\n */\n/* #undef AVS_COMMONS_WITHOUT_64BIT_FORMAT_SPECIFIERS */\n\n/**\n * Set if printf implementation doesn't support floating-point numbers.\n * If defined, custom implementation of conversion is used in\n * @c AVS_DOUBLE_AS_STRING instead of using @c snprintf . This might increase\n * compatibility with some embedded libc implementations that do not provide\n * this functionality.\n *\n * NOTE: In order to keep the custom implementation small in code size, it is\n * not intended to be 100% accurate. Rounding errors may occur - according to\n * empirical checks, they show up around the 16th significant decimal digit.\n */\n/* #undef AVS_COMMONS_WITHOUT_FLOAT_FORMAT_SPECIFIERS */\n/**@}*/\n\n/**\n * Enable poisoning of unwanted symbols when compiling avs_commons.\n *\n * Requires a compiler that supports #pragma GCC poison.\n *\n * This is mostly useful during development, to ensure that avs_commons do not\n * attempt to call functions considered harmful in this library, such as printf.\n * This is not guaranteed to work as intended on every platform, e.g. on macOS\n * it is known to generate false positives due to different dependencies between\n * system headers.\n */\n/* #undef AVS_COMMONS_WITH_POISONING */\n\n/**\n * Options that control compilation of avs_commons components.\n *\n * Each of the configuration options below enables, if defined, one of the core\n * components of the avs_commons library.\n *\n * NOTE: Enabling avs_unit will cause an object file with an implementation of\n * main() to be generated.\n */\n/**@{*/\n#define AVS_COMMONS_WITH_AVS_ALGORITHM\n#define AVS_COMMONS_WITH_AVS_BUFFER\n#define AVS_COMMONS_WITH_AVS_COMPAT_THREADING\n#define AVS_COMMONS_WITH_AVS_CRYPTO\n/* #undef AVS_COMMONS_WITH_AVS_HTTP */\n#define AVS_COMMONS_WITH_AVS_LIST\n#define AVS_COMMONS_WITH_AVS_LOG\n#define AVS_COMMONS_WITH_AVS_NET\n#define AVS_COMMONS_WITH_AVS_PERSISTENCE\n/* #undef AVS_COMMONS_WITH_AVS_RBTREE */\n#define AVS_COMMONS_WITH_AVS_SORTED_SET\n#define AVS_COMMONS_WITH_AVS_SCHED\n#define AVS_COMMONS_WITH_AVS_STREAM\n/* #undef AVS_COMMONS_WITH_AVS_UNIT */\n#define AVS_COMMONS_WITH_AVS_URL\n#define AVS_COMMONS_WITH_AVS_UTILS\n/* #undef AVS_COMMONS_WITH_AVS_VECTOR */\n/**@}*/\n\n/**\n * Options that control compilation of avs_compat_threading implementations.\n *\n * If CMake is not used, in the typical scenario at most one of the following\n * implementations may be enabled at the same time. If none is enabled, the\n * relevant symbols will need to be provided by the user, if used.\n *\n * These are meaningful only if <c>AVS_COMMONS_WITH_AVS_COMPAT_THREADING</c> is\n * defined.\n */\n/**@{*/\n/**\n * Enable implementation based on spinlocks.\n *\n * This implementation is usually very inefficient, and requires C11 stdatomic.h\n * header to be available.\n */\n/* #undef AVS_COMMONS_COMPAT_THREADING_WITH_ATOMIC_SPINLOCK */\n\n/**\n * Enable implementation based on the POSIX Threads library.\n *\n * This implementation is preferred over the spinlock-based one, but the POSIX\n * Threads library is normally available only in UNIX-like environments.\n */\n/* #undef AVS_COMMONS_COMPAT_THREADING_WITH_PTHREAD */\n\n/**\n * Is the <c>pthread_condattr_setclock()</c> function available?\n *\n * This flag only makes sense when\n * <c>AVS_COMMONS_COMPAT_THREADING_WITH_PTHREAD</c> is enabled.\n *\n * If this flag is disabled, or if <c>CLOCK_MONOTONIC</c> macro is not\n * available, the <c>avs_condvar_wait()</c> will internally use the real-time\n * clock instead of the monotonic clock. Time values will be converted so that\n * this change does not affect API usage.\n */\n/* #undef AVS_COMMONS_COMPAT_THREADING_PTHREAD_HAVE_PTHREAD_CONDATTR_SETCLOCK */\n/**@}*/\n\n/**\n * Options that control compilation of code depending on TLS backend library.\n *\n * If CMake is not used, in the typical scenario at most one of the following\n * DTLS backends may be enabled at the same time. If none is enabled,\n * functionalities that depends on cryptography will be disabled.\n *\n * Affects avs_crypto, avs_net, and avs_stream (for the MD5 implementation).\n *\n * mbed TLS is the main development backend, and is preferred as such. OpenSSL\n * backend supports most functionality as well, but is not as thoroughly tested.\n * TinyDTLS support is only rudimentary.\n */\n/**@{*/\n#define AVS_COMMONS_WITH_MBEDTLS\n/* #undef AVS_COMMONS_WITH_OPENSSL */\n/* #undef AVS_COMMONS_WITH_TINYDTLS */\n\n/**\n * Enable support for custom TLS socket implementation.\n *\n * If enabled, the user needs to provide their own implementations of\n * <c>_avs_net_create_ssl_socket()</c>, <c>_avs_net_create_dtls_socket()</c>,\n * <c>_avs_net_initialize_global_ssl_state() and\n * <c>_avs_net_cleanup_global_ssl_state()</c>.\n */\n/* #undef AVS_COMMONS_WITH_CUSTOM_TLS */\n/**@}*/\n\n/**\n * Options related to avs_crypto.\n */\n/**@{*/\n/**\n * Enable AEAD and HKDF support in avs_crypto. Requires MbedTLS in version at\n * least 2.14.0, OpenSSL in version at least 1.1.0, or custom implementation in\n * case of <c>AVS_COMMONS_WITH_CUSTOM_TLS</c>.\n */\n/* #undef AVS_COMMONS_WITH_AVS_CRYPTO_ADVANCED_FEATURES */\n\n/**\n * If the TLS backend is either mbed TLS or OpenSSL, enables APIs related to\n * public-key cryptography.\n *\n * Public-key cryptography is not currently supported with TinyDTLS.\n *\n * It also enables support for X.509 certificates in avs_net, if that module is\n * also enabled.\n */\n/* #undef AVS_COMMONS_WITH_AVS_CRYPTO_PKI */\n\n/**\n * If the TLS backend is either mbed TLS, OpenSSL or TinyDTLS, enables support\n * of pre-shared key security.\n *\n * PSK is the only supported security mode for the TinyDTLS backend, so this\n * option MUST be enabled to utilize it.\n *\n * It also enables support for pre-shared key security in avs_net, if that\n * module is also enabled.\n */\n#define AVS_COMMONS_WITH_AVS_CRYPTO_PSK\n\n/**\n * Enables usage of Valgrind API to suppress some of the false positives\n * generated by the OpenSSL backend.\n */\n/* #undef AVS_COMMONS_WITH_AVS_CRYPTO_VALGRIND */\n\n/**\n * Enables high-level support for hardware-based PKI security, i.e. loading,\n * generating and managing key pairs and certificates via external engines.\n *\n * Requires @ref AVS_COMMONS_WITH_AVS_CRYPTO_PKI to be enabled.\n *\n * An actual implementation is required to use this feature. You may provide\n * your own, or use one of the default ones that come with the HSM engine\n * commercial feature (see @ref AVS_COMMONS_WITH_MBEDTLS_PKCS11_ENGINE,\n * @ref AVS_COMMONS_WITH_MBEDTLS_PSA_ENGINE and\n * @ref AVS_COMMONS_WITH_OPENSSL_PKCS11_ENGINE).\n *\n * The functions that need to be provided in case of a custom implementation:\n * - <c>avs_crypto_pki_engine_certificate_rm()</c>\n * - <c>avs_crypto_pki_engine_certificate_store()</c>\n * - <c>avs_crypto_pki_engine_key_gen()</c>\n * - <c>avs_crypto_pki_engine_key_rm()</c>\n * - <c>avs_crypto_pki_engine_key_store()</c>\n * - When targeting the Mbed TLS backend:\n *   - <c>_avs_crypto_mbedtls_engine_initialize_global_state()</c>\n *   - <c>_avs_crypto_mbedtls_engine_cleanup_global_state()</c>\n *   - <c>_avs_crypto_mbedtls_engine_append_cert()</c>\n *   - <c>_avs_crypto_mbedtls_engine_append_crl()</c>\n *   - <c>_avs_crypto_mbedtls_engine_load_private_key()</c>\n * - When targeting the OpenSSL backend:\n *   - <c>_avs_crypto_openssl_engine_initialize_global_state()</c>\n *   - <c>_avs_crypto_openssl_engine_cleanup_global_state()</c>\n *   - <c>_avs_crypto_openssl_engine_load_certs()</c>\n *   - <c>_avs_crypto_openssl_engine_load_crls()</c>\n *   - <c>_avs_crypto_openssl_engine_load_private_key()</c>\n *\n * External PKI engines are NOT supported in the TinyDTLS backend.\n */\n/* #undef AVS_COMMONS_WITH_AVS_CRYPTO_PKI_ENGINE */\n\n/**\n * Enables high-level support for hardware-based PSK security, i.e. loading\n * and managing PSK keys and identities via external engine.\n *\n * Requires @ref AVS_COMMONS_WITH_AVS_CRYPTO_PKI to be enabled.\n *\n * An actual implementation is required to use this feature. You may provide\n * your own, or use the default PSA-based one that comes with the HSM engine\n * commercial feature (see @ref AVS_COMMONS_WITH_MBEDTLS_PSA_ENGINE).\n *\n * The functions that need to be provided in case of a custom implementation:\n * - <c>avs_crypto_psk_engine_identity_store()</c>\n * - <c>avs_crypto_psk_engine_identity_rm()</c>\n * - <c>avs_crypto_psk_engine_key_store()</c>\n * - <c>avs_crypto_psk_engine_key_rm()</c>\n * - When targeting the Mbed TLS backend:\n *   - <c>_avs_crypto_mbedtls_engine_initialize_global_state()</c>\n *   - <c>_avs_crypto_mbedtls_engine_cleanup_global_state()</c>\n *   - <c>_avs_crypto_mbedtls_engine_load_psk_key()</c>\n *\n * External PSK engines are NOT supported in the OpenSSL and TinyDTLS backend.\n */\n/* #undef AVS_COMMONS_WITH_AVS_CRYPTO_PSK_ENGINE */\n\n/**\n * Enables the default implementation of avs_crypto engine, based on Mbed TLS\n * and PKCS#11.\n *\n * Requires @ref AVS_COMMONS_WITH_AVS_CRYPTO_PKI_ENGINE to be enabled.\n *\n * NOTE: Query string format for this engine is a subset of the PKCS#11 URI\n * scheme (see RFC 7512), modelled after the format accepted by libp11 OpenSSL\n * engine.\n *\n * NOTE: The unit tests for this feature depend on SoftHSM and pkcs11-tool.\n * These must be installed for the tests to pass.\n *\n * IMPORTANT: Only available as part of the HSM support commercial feature.\n * Ignored in the open source version.\n */\n/* #undef AVS_COMMONS_WITH_MBEDTLS_PKCS11_ENGINE */\n\n/**\n * Enables the default implementation of avs_crypto engine, based on Mbed TLS\n * and Platform Security Architecture (PSA).\n *\n * Requires @ref AVS_COMMONS_WITH_AVS_CRYPTO_PKI_ENGINE or\n * @ref AVS_COMMONS_WITH_AVS_CRYPTO_PSK_ENGINE to be enabled.\n *\n * NOTE: Query string format for this engine is:\n *\n * <pre>\n * kid=<key_ID>[,lifetime=<lifetime>]|uid=<persistent_storage_UID>\n * </pre>\n *\n * The values are parsed using strtoull() with base=0, so may be in decimal,\n * 0-prefixed octal or 0x-prefixed hexadecimal. On key generation and\n * certificate storage, the specified lifetime will be used, or lifetime 1\n * (default persistent storage) will be used if not. On key or certificate use,\n * the lifetime of the actual key will be verified if present on the query\n * string and the key will be rejected if different.\n *\n * Certificates are stored as PSA_KEY_TYPE_RAW_DATA key entries containing\n * X.509 DER data. Alternatively, the PSA Protected Storage API can be used if\n * @ref AVS_COMMONS_WITH_MBEDTLS_PSA_ENGINE_PROTECTED_STORAGE is enabled, by\n * using the <c>uid=...</c> syntax.\n *\n * IMPORTANT: Only available as part of the HSM support commercial feature.\n * Ignored in the open source version.\n */\n/* #undef AVS_COMMONS_WITH_MBEDTLS_PSA_ENGINE */\n\n/**\n * Enables support for the PSA Protected Storage API in the PSA-based avs_crypto\n * engine.\n *\n * Requires @ref AVS_COMMONS_WITH_MBEDTLS_PSA_ENGINE to be enabled.\n *\n * IMPORTANT: Only available as part of the HSM support commercial feature.\n * Ignored in the open source version.\n */\n/* #undef AVS_COMMONS_WITH_MBEDTLS_PSA_ENGINE_PROTECTED_STORAGE */\n\n/**\n * Enables use of the <c>psa_generate_random()</c> function as the default\n * random number generator when using the Mbed TLS crypto backend, instead of\n * CTR-DRBG seeded by the Mbed TLS entropy pool.\n *\n * It's meaningful only when @ref AVS_COMMONS_WITH_MBEDTLS is enabled. However,\n * it is independent from the above PSA engine settings.\n */\n/* #undef AVS_COMMONS_WITH_MBEDTLS_PSA_RNG */\n\n/**\n * Is the <c>dlsym()</c> function available?\n *\n * This is currently only used if @ref AVS_COMMONS_WITH_MBEDTLS_PKCS11_ENGINE is\n * enabled. If enabled, the PKCS#11 module is loaded dynamically from a library\n * specified by the <c>PKCS11_MODULE_PATH</c> environment variable. If disabled,\n * a function with the following signature, realizing the PKCS#11\n * <c>C_GetFunctionList</c> method, must be provided manually:\n *\n * <pre>\n * CK_RV _avs_crypto_mbedtls_pkcs11_get_function_list(CK_FUNCTION_LIST_PTR_PTR);\n * </pre>\n */\n/* #undef AVS_COMMONS_HAVE_DLSYM */\n\n/**\n * Enables the default implementation of avs_crypto engine, based on OpenSSL and\n * PKCS#11.\n *\n * Requires @ref AVS_COMMONS_WITH_AVS_CRYPTO_PKI_ENGINE to be enabled.\n *\n * NOTE: Query string format for this engine is a subset of the PKCS#11 URI\n * scheme (see RFC 7512), modelled after the format accepted by libp11 OpenSSL\n * engine.\n *\n * NOTE: The unit tests for this feature depend on SoftHSM and pkcs11-tool.\n * These must be installed for the tests to pass.\n *\n * IMPORTANT: Only available as part of the HSM support commercial feature.\n * Ignored in the open source version.\n */\n/* #undef AVS_COMMONS_WITH_OPENSSL_PKCS11_ENGINE */\n/**@}*/\n\n/**\n * Enable support for HTTP content compression in avs_http.\n *\n * Requires linking with zlib.\n */\n/* #undef AVS_COMMONS_HTTP_WITH_ZLIB */\n\n/**\n * Options related to avs_log and logging support within avs_commons.\n */\n/**@{*/\n/* clang-format off */\n/**\n * Size, in bytes, of the avs_log buffer.\n *\n * Log messages that would (including the level, module name and code location)\n * otherwise be longer than this value minus one (for the terminating null\n * character) will be truncated.\n *\n * NOTE: This macro MUST be defined if avs_log is enabled.\n *\n * If editing this file manually, <c>512</c> shall\n * be replaced with a positive integer literal. The default value defined in\n * CMake build scripts is 512.\n */\n#define AVS_COMMONS_LOG_MAX_LINE_LENGTH 512\n/* clang-format on */\n\n/**\n * Configures avs_log to use a synchronized global buffer instead of allocating\n * a buffer on the stack when constructing log messages.\n *\n * Requires avs_compat_threading to be enabled.\n *\n * Enabling this option would reduce the stack space required to use avs_log, at\n * the expense of global storage and the complexity of using a mutex.\n */\n#define AVS_COMMONS_LOG_USE_GLOBAL_BUFFER\n\n/**\n * Provides a default avs_log handler that prints log messages on stderr.\n *\n * Disabling this option will cause logs to be discarded by default, until a\n * custom log handler is set using <c>avs_log_set_handler()</c>.\n */\n/* #undef AVS_COMMONS_LOG_WITH_DEFAULT_HANDLER */\n\n/**\n * Enables the \"micro logs\" feature.\n *\n * Replaces all occurrences of the <c>AVS_DISPOSABLE_LOG()</c> macro with single\n * space strings. This is intended to reduce the size of the compiled code, by\n * stripping it of almost all log string data.\n *\n * Note that this setting will propagate both to avs_commons components\n * themselves (as all its internal logs make use of <c>AVS_DISPOSABLE_LOG()</c>)\n * and the user code that uses it.\n */\n/* #undef AVS_COMMONS_WITH_MICRO_LOGS */\n\n/**\n * Enables logging inside avs_commons.\n *\n * Requires @ref AVS_COMMONS_WITH_AVS_LOG to be enabled.\n *\n * If this macro is not defined at avs_commons compile time, calls to avs_log\n * will not be generated inside avs_commons components.\n */\n#define AVS_COMMONS_WITH_INTERNAL_LOGS\n\n/**\n * Enables TRACE-level logs inside avs_commons.\n *\n * Only meaningful if AVS_COMMONS_WITH_INTERNAL_LOGS is enabled.\n *\n * If this macro is not defined at avs_commons compile time, calls to avs_log\n * with the level set to TRACE will not be generated inside avs_commons\n * components.\n */\n/* #undef AVS_COMMONS_WITH_INTERNAL_TRACE */\n\n/**\n * Enables external implementation of logger subsystem with provided header.\n *\n * Default logger implementation can be found in avs_log_impl.h\n */\n/* #undef AVS_COMMONS_WITH_EXTERNAL_LOGGER_HEADER */\n\n/**\n * If specified, the process of checking if avs_log should be written out\n * takes place in compile time.\n *\n * Specify an optional header with a list of modules for which log level\n * is set. If a log level for specific module is not set, the DEFAULT level\n * will be taken into account. Value of the default logging level is set to\n * DEBUG, but can be overwritten in this header file with AVS_LOG_LEVEL_DEFAULT\n * define. Messages with lower level than the one set will be removed during\n * compile time. Possible values match @ref avs_log_level_t.\n *\n * That file should contain C preprocesor defines in the:\n * - \"#define AVS_LOG_LEVEL_FOR_MODULE_<Module> <Level>\" format,\n *   where <Module> is the module name and <Level> is allowed logging level\n * - \"#define AVS_LOG_LEVEL_DEFAULT <Level>\" format, where <Level> is the\n *   allowed logging level\n *\n * Example file content:\n *\n * <code>\n * #ifndef AVS_COMMONS_EXTERNAL_LOG_LEVELS_H\n * #define AVS_COMMONS_EXTERNAL_LOG_LEVELS_H\n *\n * // global log level value\n * #define AVS_LOG_LEVEL_DEFAULT INFO\n *\n * //for \"coap\" messages only WARNING and ERROR messages will be present\n * #define AVS_LOG_LEVEL_FOR_MODULE_coap WARNING\n *\n * //logs are disable for \"net\" module\n * #define AVS_LOG_LEVEL_FOR_MODULE_net QUIET\n *\n * #endif\n * </code>\n */\n/* #undef AVS_COMMONS_WITH_EXTERNAL_LOG_LEVELS_HEADER */\n\n/**\n * Disable log level check in runtime. Allows to save at least 1.3kB of memory.\n *\n * The macros avs_log_set_level and avs_log_set_default_level\n * will not be available.\n *\n */\n/* #undef AVS_COMMONS_WITHOUT_LOG_CHECK_IN_RUNTIME */\n/**@}*/\n\n/**\n * Options related to avs_net.\n */\n/**@{*/\n/**\n * Enables support for IPv4 connectivity.\n *\n * At least one of AVS_COMMONS_NET_WITH_IPV4 and AVS_COMMONS_NET_WITH_IPV6\n * MUST be defined if avs_net is enabled.\n */\n#define AVS_COMMONS_NET_WITH_IPV4\n\n/**\n * Enables support for IPv6 connectivity.\n *\n * At least one of AVS_COMMONS_NET_WITH_IPV4 and AVS_COMMONS_NET_WITH_IPV6\n * MUST be defined if avs_net is enabled.\n */\n/* #undef AVS_COMMONS_NET_WITH_IPV6 */\n\n/**\n * If the TLS backend is set to OpenSSL, enables support for DTLS.\n *\n * DTLS is always enabled for the mbed TLS and TinyDTLS backends.\n */\n/* #undef AVS_COMMONS_NET_WITH_DTLS */\n\n/**\n * Enables debug logs generated by mbed TLS.\n *\n * An avs_log-backed handler, logging for the \"mbedtls\" module on the TRACE\n * level, is installed using <c>mbedtls_ssl_conf_dbg()</c> for each (D)TLS\n * socket created if this option is enabled.\n */\n/* #undef AVS_COMMONS_NET_WITH_MBEDTLS_LOGS */\n\n/**\n * Enables (Pre-)Master-Secret logs generation.\n *\n * These logs contain TLS session secrets that tools like Wireshark can use\n * to decrypt captured TLS traffic.\n *\n * NOTE: This only works with Mbed TLS starting from v3.0.0. If you’re using\n * Mbed TLS earlier than v3.1.0, you must enable MBEDTLS_SSL_EXPORT_KEYS.\n *\n * NOTE: The user must specify the stream to which the logs will be transferred\n * using the avs_mbedtls_set_sslkeylog_stream function.\n */\n/* #undef AVS_COMMONS_NET_WITH_MBEDTLS_SSLKEYLOG */\n\n/**\n * Enables the default implementation of avs_net TCP and UDP sockets.\n *\n * Requires either a UNIX-like operating environment, or a compatibility layer\n * with a high degree of compatibility with standard BSD sockets with an\n * appropriate compatibility header (see @ref AVS_COMMONS_POSIX_COMPAT_HEADER) -\n * lwIP and Winsock are currently supported for this scenario.\n */\n#define AVS_COMMONS_NET_WITH_POSIX_AVS_SOCKET\n\n/**\n * @deprecated This is deprecated API. This API can be removed in future\n * releases, without any notice.\n *\n * Enables support for logging socket communication to file.\n *\n * If this option is enabled, avs_net_socket_debug() can be used to enable\n * logging all communication to a file called DEBUG.log. If disabled,\n * avs_net_socket_debug() will always return an error.\n */\n/* #undef AVS_COMMONS_NET_WITH_SOCKET_LOG */\n\n/**\n * @experimental This is experimental API. This API can change in the future\n * versions without any notice.\n *\n * Enables support for custom net traffic interceptor.\n *\n * If this option is enabled, the user needs to provide their own implementation\n * of avs_net_create_traffic_interceptor() function which can wrap and extend\n * an underlying socket.\n */\n/* #undef AVS_COMMONS_WITH_TRAFFIC_INTERCEPTOR */\n\n/**\n * If the TLS backend is either mbed TLS or OpenSSL, enables support for (D)TLS\n * session persistence.\n *\n * Session persistence is not currently supported for the TinyDTLS backend.\n */\n#define AVS_COMMONS_NET_WITH_TLS_SESSION_PERSISTENCE\n/**@}*/\n\n/**\n * Options related to avs_net's default implementation of TCP and UDP sockets.\n *\n * These options make sense only when @ref AVS_COMMONS_NET_WITH_POSIX_AVS_SOCKET\n * is enabled. They describe capabilities of the Unix-like environment in which\n * the library is built.\n *\n * Note that if @ref AVS_COMMONS_POSIX_COMPAT_HEADER is defined, it might\n * redefine these flags independently of the settings in this file.\n */\n/**@{*/\n/**\n * Is the <c>gai_strerror()</c> function available?\n *\n * Enabling this flag will provide more detailed log messages in case that\n * <c>getaddrinfo()</c> fails. If this flag is disabled, numeric error codes\n * values will be logged.\n */\n/* #undef AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_GAI_STRERROR */\n\n/**\n * Is the <c>getifaddrs()</c> function available?\n *\n * Disabling this flag will cause <c>avs_net_socket_interface_name()</c> to use\n * a less optimal implementation based on the <c>SIOCGIFCONF</c> ioctl.\n *\n * If <c>SIOCGIFCONF</c> is not defined, either, then\n * <c>avs_net_socket_interface_name()</c> will always return an error.\n */\n/* #undef AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_GETIFADDRS */\n\n/**\n * Is the <c>getnameinfo()</c> function available?\n *\n * Disabling this flag will cause <c>avs_net_socket_receive_from()</c>,\n * <c>avs_net_socket_accept()</c>,\n * <c>avs_net_resolved_endpoint_get_host_port()</c>,\n * <c>avs_net_resolved_endpoint_get_host()</c> and\n * <c>avs_net_resolve_host_simple()</c> to use a custom reimplementation of\n * <c>getnameinfo()</c> based on <c>inet_ntop()</c>.\n */\n/* #undef AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_GETNAMEINFO */\n\n/**\n * Is the <c>IN6_IS_ADDR_V4MAPPED</c> macro available and usable?\n *\n * Disabling this flag will cause a custom code that compares IPv6 addresses\n * with the <c>::ffff:0.0.0.0/32</c> mask to be used instead.\n */\n/* #undef AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_IN6_IS_ADDR_V4MAPPED */\n\n/**\n * Should be defined if IPv4-mapped IPv6 addresses (<c>::ffff:0.0.0.0/32</c>)\n * are <strong>NOT</strong> supported by the underlying platform.\n *\n * Enabling this flag will prevent avs_net from using IPv4-mapped IPv6 addresses\n * and instead re-open and re-bind the socket if a connection to an IPv4 address\n * is requested on a previously created IPv6 socket.\n *\n * This may result in otherwise redundant <c>socket()</c>, <c>bind()</c> and\n * <c>close()</c> system calls to be performed, but may be necessary for\n * interoperability with some platforms.\n */\n/* #undef AVS_COMMONS_NET_POSIX_AVS_SOCKET_WITHOUT_IN6_V4MAPPED_SUPPORT */\n\n/**\n * Is the <c>inet_ntop()</c> function available?\n *\n * Disabling this flag will cause an internal implementation of this function\n * adapted from BIND 4.9.4 to be used instead.\n */\n/* #undef AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_INET_NTOP */\n\n/**\n * Is the <c>poll()</c> function available?\n *\n * Disabling this flag will cause a less robust code based on <c>select()</c> to\n * be used instead.\n */\n/* #undef AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_POLL */\n\n/**\n * Is the <c>recvmsg()</c> function available?\n *\n * Disabling this flag will cause <c>recvfrom()</c> to be used instead. Note\n * that for UDP sockets, this will cause false positives for datagram truncation\n * detection (<c>AVS_EMSGSIZE</c>) to be reported when the received message is\n * exactly the size of the buffer.\n */\n/* #undef AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_RECVMSG */\n/**@}*/\n\n/**\n * Enable thread safety in avs_sched.\n *\n * Makes all scheduler accesses synchronized and thread-safe, at the cost of\n * requiring avs_compat_threading to be enabled, and higher resource usage.\n */\n/* #undef AVS_COMMONS_SCHED_THREAD_SAFE */\n\n/**\n * Enable support for file I/O in avs_stream.\n *\n * Disabling this flag will cause the functions declared in\n * <c>avs_stream_file.h</c> to not be defined.\n */\n/* #undef AVS_COMMONS_STREAM_WITH_FILE */\n\n/**\n * Enable usage of <c>backtrace()</c> and <c>backtrace_symbols()</c> when\n * reporting assertion failures from avs_unit.\n *\n * Requires the afore-mentioned GNU-specific functions to be available.\n *\n * If this flag is disabled, stack traces will not be displayed with assertion\n * failures.\n */\n/* #undef AVS_COMMONS_UNIT_POSIX_HAVE_BACKTRACE */\n\n/**\n * Options related to avs_utils.\n */\n/**@{*/\n/**\n * Enable the default implementation of avs_time_real_now() and\n * avs_time_monotonic_now().\n *\n * Requires an operating environment that supports a clock_gettime() call\n * compatible with POSIX.\n */\n/* #undef AVS_COMMONS_UTILS_WITH_POSIX_AVS_TIME */\n\n/**\n * Enable the default implementation of avs_malloc(), avs_free(), avs_calloc()\n * and avs_realloc() that forwards to system malloc(), free(), calloc() and\n * realloc() calls.\n *\n * You might disable this option if for any reason you need to use a custom\n * allocator.\n */\n#define AVS_COMMONS_UTILS_WITH_STANDARD_ALLOCATOR\n\n/**\n * Enable the alternate implementation of avs_malloc(), avs_free(), avs_calloc()\n * and avs_realloc() that uses system malloc(), free() and realloc() calls, but\n * includes additional fixup code that ensures proper alignment to\n * <c>AVS_ALIGNOF(avs_max_align_t)</c> (usually 8 bytes on common platforms).\n *\n * <c>AVS_COMMONS_UTILS_WITH_STANDARD_ALLOCATOR</c> and\n * <c>AVS_COMMONS_UTILS_WITH_ALIGNFIX_ALLOCATOR</c> cannot be enabled at the\n * same time.\n *\n * NOTE: This implementation is only intended for platforms where the system\n * allocator does not properly conform to the alignment requirements.\n *\n * It comes with an additional runtime costs:\n *\n * - <c>AVS_ALIGNOF(avs_max_align_t)</c> bytes (usually 8) of additional\n *   overhead for each allocated memory block\n * - Additional memmove() for every realloc() that returned a block that is not\n *   properly aligned\n * - avs_calloc() is implemented as avs_malloc() followed by an explicit\n *   memset(); this may be suboptimal on some platforms\n *\n * If these costs are unacceptable for you, you may want to consider fixing,\n * replacing or reconfiguring your system allocator for conformance, or\n * implementing a custom one instead.\n *\n * Please note that some code in avs_commons and dependent projects (e.g. Anjay)\n * may include runtime assertions for proper memory alignment that will be\n * triggered when using a non-conformant standard allocator. Such allocators are\n * relatively common in embedded SDKs. This \"alignfix\" allocator is intended to\n * work around these issues. On some platforms (e.g. x86) those alignment issues\n * may not actually cause any problems - so you may want to consider disabling\n * runtime assertions instead. Please carefully examine your target platform's\n * alignment requirements and behavior of misaligned memory accesses (including\n * 64-bit data types such as <c>int64_t</c> and <c>double</c>) before doing so.\n */\n/* #undef AVS_COMMONS_UTILS_WITH_ALIGNFIX_ALLOCATOR */\n\n/**@}*/\n\n#endif /* AVS_COMMONS_CONFIG_H */\n"
  },
  {
    "path": "example_configs/embedded_lwm2m11/avsystem/commons/lwip-posix-compat.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef COMPAT_H\n#define COMPAT_H\n\n/*\n * Example implementation of a AVS_COMMONS_POSIX_COMPAT_HEADER file required for\n * non-POSIX platforms that use LwIP 1.4.1.\n *\n * Contains all types/macros/symbols not defined in core C that are required\n * to compile avs_commons library.\n */\n\n#include \"lwipopts.h\"\n\n/* Provides lwIP's alternative errno header, used in avs_net_impl.c */\n#include \"lwip/errno.h\"\n\n/* Provides htons/ntohs/htonl/ntohl */\n#include \"lwip/inet.h\"\n\n/* Provides e.g. LWIP_VERSION_* macros used in net_impl.c */\n#include \"lwip/init.h\"\n\n/*\n * Provides:\n * - POSIX-compatible socket API, socklen_t,\n * - fcntl, F_GETFL, F_SETFL, O_NONBLOCK,\n * - select, struct fd_set, FD_SET, FD_CLEAR, FD_ISSET\n */\n#include \"lwip/sockets.h\"\n\n/* Provides getaddrinfo/freeaddrinfo/struct addrinfo */\n#include \"lwip/netdb.h\"\n\n#if LWIP_VERSION_MAJOR >= 2\n#    define AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_INET_NTOP\n#endif // LWIP_VERSION_MAJOR >= 2\n\ntypedef int sockfd_t;\n\n#endif /* COMPAT_H */\n"
  },
  {
    "path": "example_configs/embedded_lwm2m12/anjay/anjay_config.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ANJAY_CONFIG_H\n#define ANJAY_CONFIG_H\n\n/**\n * @file anjay_config.h\n *\n * Anjay library configuration.\n *\n * The preferred way to compile Anjay is to use CMake, in which case this file\n * will be generated automatically by CMake.\n *\n * However, to provide compatibility with various build systems used especially\n * by embedded platforms, it is alternatively supported to compile Anjay by\n * other means, in which case this file will need to be provided manually.\n *\n * <strong>NOTE: To compile this library without using CMake, you need to\n * configure avs_commons and avs_coap first. Please refer to documentation in\n * the <c>avs_commons_config.h</c> and <c>avs_coap_config.h</c> files (provided\n * in the repositories as <c>avs_commons_config.h.in</c> and\n * <c>avs_coap_config.h.in</c>, respectively) for details.</strong>\n *\n * <strong>Anjay requires the following avs_coap options to be enabled:</strong>\n *\n * - @c WITH_AVS_COAP_UDP and/or @c WITH_AVS_COAP_TCP\n * - @c WITH_AVS_COAP_STREAMING_API\n * - @c WITH_AVS_COAP_BLOCK is highly recommended\n * - @c WITH_AVS_COAP_OBSERVE (if @c ANJAY_WITH_OBSERVE is enabled)\n * - @c WITH_AVS_COAP_OSCORE (if @c ANJAY_WITH_COAP_OSCORE is enabled, available\n *   only as a commercial feature)\n *\n * <strong>Anjay requires the following avs_commons components to be\n * enabled:</strong>\n *\n * - All components required by avs_coap, see <c>avs_coap_config.h</c>\n * - @c avs_algorithm\n * - @c avs_stream\n * - @c avs_url\n * - @c avs_persistence is highly recommended\n * - @c avs_http (if @c ANJAY_WITH_HTTP_DOWNLOAD is enabled)\n * - @c avs_rbtree or @c avs_sorted_set (if @c ANJAY_WITH_OBSERVE or\n *   @c ANJAY_WITH_MODULE_ACCESS_CONTROL is enabled)\n *\n * In the repository, this file is provided as <c>anjay_config.h.in</c>,\n * intended to be processed by CMake. If editing this file manually, please copy\n * or rename it to <c>anjay_config.h</c> and for each of the\n * <c>\\#cmakedefine</c> directives, please either replace it with regular\n * <c>\\#define</c> to enable it, or comment it out to disable. You may also need\n * to replace variables wrapped in <c>\\@</c> signs with concrete values. Please\n * refer to the comments above each of the specific definition for details.\n *\n * If you are editing a file previously generated by CMake, these\n * <c>\\#cmakedefine</c>s will be already replaced by either <c>\\#define</c> or\n * commented out <c>\\#undef</c> directives.\n */\n\n/**\n * Enable logging in Anjay.\n *\n * If this flag is disabled, no logging is compiled into the binary at all.\n */\n#define ANJAY_WITH_LOGS\n\n/**\n * Enable TRACE-level logs in Anjay.\n *\n * Only meaningful if <c>ANJAY_WITH_LOGS</c> is enabled.\n */\n/* #undef ANJAY_WITH_TRACE_LOGS */\n\n/**\n * Enable core support for Access Control mechanisms.\n *\n * Requires separate implementation of the Access Control object itself.\n * Either the implementation available as part of\n * <c>ANJAY_WITH_MODULE_ACCESS_CONTROL</c>, or a custom application-provided one\n * may be used.\n */\n/* #undef ANJAY_WITH_ACCESS_CONTROL */\n\n/**\n * Enable automatic attribute storage.\n *\n * Requires <c>AVS_COMMONS_WITH_AVS_PERSISTENCE</c> to be enabled in avs_commons\n * configuration.\n */\n#define ANJAY_WITH_ATTR_STORAGE\n\n/**\n * Enable support for the <c>anjay_download()</c> API.\n */\n#define ANJAY_WITH_DOWNLOADER\n\n/**\n * Enable support for CoAP(S) downloads.\n *\n * Only meaningful if <c>ANJAY_WITH_DOWNLOADER</c> is enabled.\n */\n#define ANJAY_WITH_COAP_DOWNLOAD\n\n/**\n * Enable support for HTTP(S) downloads.\n *\n * Only meaningful if <c>ANJAY_WITH_DOWNLOADER</c> is enabled.\n */\n/* #undef ANJAY_WITH_HTTP_DOWNLOAD */\n\n/**\n * Enable support for the LwM2M Bootstrap Interface.\n */\n#define ANJAY_WITH_BOOTSTRAP\n\n/**\n * Enable support for the LwM2M Bootstrap-Pack operation.\n *\n * Requires <c>ANJAY_WITH_BOOTSTRAP</c> and <c>ANJAY_WITH_LWM2M12</c> to be\n * enabled.\n */\n#define ANJAY_WITH_BOOTSTRAP_PACK\n\n/**\n * Enable support for the LwM2M Discover operation.\n *\n * Note that it is required for full compliance with the LwM2M protocol.\n */\n#define ANJAY_WITH_DISCOVER\n\n/**\n * Enable support for the LwM2M Information Reporting interface (Observe and\n * Notify operations).\n *\n * Requires <c>WITH_AVS_COAP_OBSERVE</c> to be enabled in avs_coap\n * configuration.\n *\n * Note that it is required for full compliance with the LwM2M protocol.\n */\n#define ANJAY_WITH_OBSERVE\n\n/**\n * Enable support for measuring amount of LwM2M traffic\n * (<c>anjay_get_tx_bytes()</c>, <c>anjay_get_rx_bytes()</c>,\n * <c>anjay_get_num_incoming_retransmissions()</c> and\n * <c>anjay_get_num_outgoing_retransmissions()</c> APIs.\n */\n/* #undef ANJAY_WITH_NET_STATS */\n\n/**\n * Enable support for communication timestamp\n * (<c>anjay_get_server_last_registration_time()</c>\n * <c>anjay_get_server_next_update_time()</c> and\n * <c>anjay_get_server_last_communication_time()</c>) APIs.\n */\n/* #undef ANJAY_WITH_COMMUNICATION_TIMESTAMP_API */\n\n/**\n * Enable support for the <c>anjay_resource_observation_status()</c> API.\n */\n#define ANJAY_WITH_OBSERVATION_STATUS\n\n/**\n * Maximum number of servers observing a given Resource listed by\n * <c>anjay_resource_observation_status()</c> function.\n *\n * Only meaningful if <c>ANJAY_WITH_OBSERVATION_STATUS</c> is enabled.\n */\n#define ANJAY_MAX_OBSERVATION_SERVERS_REPORTED_NUMBER 0\n\n/**\n * Enable guarding of all accesses to <c>anjay_t</c> with a mutex.\n */\n/* #undef ANJAY_WITH_THREAD_SAFETY */\n\n/**\n * Enable standard implementation of an event loop.\n *\n * Requires C11 <c>stdatomic.h</c> header to be available, and either a platform\n * that provides a BSD-compatible socket API, or a compatibility layer file (see\n * <c>AVS_COMMONS_POSIX_COMPAT_HEADER</c> in <c>avs_commons_config.h</c>). This\n * is designed to best work with the defalt implementation of avs_net sockets\n * (see <c>AVS_COMMONS_NET_WITH_POSIX_AVS_SOCKET</c>), but alternative socket\n * implementations can also be used.\n *\n * The event loop is most useful when thread safety features\n * (@ref ANJAY_WITH_THREAD_SAFETY and <c>AVS_COMMONS_SCHED_THREAD_SAFE</c>) are\n * enabled as well, but this is not a hard requirement. See the documentation\n * for <c>anjay_event_loop_run()</c> for details.\n */\n#define ANJAY_WITH_EVENT_LOOP\n\n/**\n * Enable support for features new to LwM2M protocol version 1.1.\n */\n#define ANJAY_WITH_LWM2M11\n\n/**\n * Enable support for features new to LwM2M protocol version 1.2.\n *\n * Requires <c>ANJAY_WITH_LWM2M11</c> to be enabled.\n */\n#define ANJAY_WITH_LWM2M12\n\n/**\n * Enable support for OSCORE-based security for LwM2M connections.\n *\n * Requires <c>ANJAY_WITH_LWM2M11</c> to be enabled, and\n * <c>WITH_AVS_COAP_OSCORE</c> to be enabled in avs_coap configuration.\n *\n * IMPORTANT: Only available as part of the OSCORE commercial feature. Ignored\n * in the open source version.\n */\n/* #undef ANJAY_WITH_COAP_OSCORE */\n\n/**\n * Enable support for Observation Attributes.\n *\n * Requires <c>ANJAY_WITH_OBSERVE</c> and <c>ANJAY_WITH_LWM2M12</c> to be\n * enabled.\n */\n#define ANJAY_WITH_OBSERVATION_ATTRIBUTES\n\n/**\n * Enable support for the LwM2M Send operation.\n *\n * Requires <c>ANJAY_WITH_LWM2M11</c> and either <c>ANJAY_WITH_SENML_JSON</c> or\n * <c>ANJAY_WITH_CBOR</c> to be enabled.\n */\n#define ANJAY_WITH_SEND\n\n/**\n * Enable support for the SMS binding and the SMS trigger mechanism.\n *\n * Requires <c>WITH_AVS_COAP_UDP</c> to be enabled in avs_coap configuration.\n *\n * IMPORTANT: Only available as part of the SMS commercial feature. Ignored in\n * the open source version.\n */\n/* #undef ANJAY_WITH_SMS */\n\n/**\n * Enable support for sending and receiving multipart SMS messages.\n *\n * Requires <c>ANJAY_WITH_SMS</c> to be enabled.\n *\n * IMPORTANT: Only available as part of the SMS commercial feature. Ignored in\n * the open source version.\n */\n/* #undef ANJAY_WITH_SMS_MULTIPART */\n\n/**\n * Enable support for Non-IP Data Delivery.\n *\n * Requires <c>ANJAY_WITH_LWM2M11</c> to be enabled, and\n * <c>WITH_AVS_COAP_UDP</c> to be enabled in avs_coap configuration.\n *\n * IMPORTANT: Only available as a commercial feature. Ignored in the open\n * source version.\n */\n/* #undef ANJAY_WITH_NIDD */\n\n/**\n * Enable support for core state persistence\n * (<c>anjay_new_from_core_persistence()</c> and\n * <c>anjay_delete_with_core_persistence()</c> APIs).\n *\n * Requires <c>ANJAY_WITH_OBSERVE</c> to be enabled,\n * <c>AVS_COMMONS_WITH_AVS_PERSISTENCE</c> to be enabled in avs_commons, and\n * <c>WITH_AVS_COAP_OBSERVE_PERSISTENCE</c> to be enabled in avs_coap\n * configuration.\n *\n * IMPORTANT: Only available as a commercial feature. Ignored in the open\n * source version.\n */\n/* #undef ANJAY_WITH_CORE_PERSISTENCE */\n\n/**\n * Disable automatic closing of server connection sockets after\n * MAX_TRANSMIT_WAIT of inactivity.\n */\n/* #undef ANJAY_WITHOUT_QUEUE_MODE_AUTOCLOSE */\n\n/**\n * Enable support for CoAP Content-Format numerical values 1541-1544 that have\n * been used before final LwM2M TS 1.0.\n */\n/* #undef ANJAY_WITH_LEGACY_CONTENT_FORMAT_SUPPORT */\n\n/**\n * Enable support for JSON format as specified in LwM2M TS 1.0.\n *\n * NOTE: Anjay is only capable of generating this format, there is no parsing\n * support regardless of the state of this option.\n */\n/* #undef ANJAY_WITH_LWM2M_JSON */\n\n/**\n * Disable support for TLV format as specified in LwM2M TS 1.0.\n *\n * NOTE: LwM2M Client using LwM2M 1.0 MUST support this format. It may be\n * disabled if LwM2M 1.1 is used and SenML JSON or SenML CBOR is enabled.\n */\n/* #undef ANJAY_WITHOUT_TLV */\n\n/**\n * Disable support for Plain Text format as specified in LwM2M TS 1.0 and 1.1.\n *\n * NOTE: LwM2M Client SHOULD support this format. It may be disabled to reduce\n * library size if LwM2M Server is configured to not use it in requests.\n */\n/* #undef ANJAY_WITHOUT_PLAINTEXT */\n\n/**\n * Disable use of the Deregister message.\n */\n/* #undef ANJAY_WITHOUT_DEREGISTER */\n\n/**\n * Disable support for \"IP stickiness\", i.e. preference of the same IP address\n * when reconnecting to a server using a domain name.\n */\n/* #undef ANJAY_WITHOUT_IP_STICKINESS */\n\n/**\n * Enable support for SenML JSON format, as specified in LwM2M TS 1.1.\n *\n * NOTE: As opposed to <c>ANJAY_WITH_LWM2M_JSON</c>, both generating and parsing\n * is supported.\n *\n * Requires <c>ANJAY_WITH_LWM2M11</c> to be enabled.\n */\n#define ANJAY_WITH_SENML_JSON\n\n/**\n * Enable support for CBOR and SenML CBOR formats, as specified in LwM2M TS 1.1.\n *\n * Requires <c>ANJAY_WITH_LWM2M11</c> to be enabled.\n */\n#define ANJAY_WITH_CBOR\n\n/**\n * Enable support for Enrollment over Secure Transport.\n *\n * Requires <c>ANJAY_WITH_BOOTSTRAP</c> to be enabled.\n *\n * IMPORTANT: Only available as part of the EST commercial feature. Ignored in\n * the open source version.\n */\n/* #undef ANJAY_WITH_EST */\n\n/**\n * Enable support for hardware security engine in the EST subsystem.\n *\n * Requires <c>ANJAY_WITH_EST</c> to be enabled in Anjay configuration and\n * <c>AVS_COMMONS_WITH_AVS_CRYPTO_PKI_ENGINE</c> to be enabled in avs_commons\n * configuration.\n *\n * IMPORTANT: Only available in commercial versions that include both the EST\n * and HSM features. Ignored in versions distributed without these features.\n */\n/* #undef ANJAY_WITH_EST_ENGINE_SUPPORT */\n\n/**\n * Enable support for the Confirmable Notification attribute, as specified in\n * LwM2M TS 1.2.\n *\n * Before TS 1.2, this has been supported in Anjay as a custom extension, and\n * thus it is available independently from TS 1.2 support itself, including in\n * the open source version.\n *\n * Requires <c>ANJAY_WITH_OBSERVE</c> to be enabled.\n */\n#define ANJAY_WITH_CON_ATTR\n\n/**\n * Enable support for handling security credentials in the data model using\n * structured <c>avs_crypto</c> types.\n *\n * If the <c>security</c> module is also enabled (see @ref\n * ANJAY_WITH_MODULE_SECURITY), it also enables support for passing these\n * credentials through such structured types when adding Security object\n * instances via the @ref anjay_security_instance_t structure.\n */\n#define ANJAY_WITH_SECURITY_STRUCTURED\n\n/**\n * Maximum size in bytes supported for the \"Public Key or Identity\" resource in\n * the LwM2M Security object.\n *\n * If editing this file manually, <c>256</c> shall be\n * replaced with a positive integer literal.\n *\n * The default value defined in CMake build scripts is 2048.\n * Minimal suggested setting for low-resource builds is 256.\n */\n#define ANJAY_MAX_PK_OR_IDENTITY_SIZE 256\n\n/**\n * Maximum size in bytes supported for the \"Secret Key\" resource in the LwM2M\n * Security Object.\n *\n * If editing this file manually, <c>128</c> shall be replaced\n * with a positive integer literal.\n *\n * The default value defined in CMake build scripts is 256.\n * Minimal suggested setting for low-resource builds is 128.\n */\n#define ANJAY_MAX_SECRET_KEY_SIZE 128\n\n/**\n * Maximum length supported for stringified floating-point values.\n *\n * Used when parsing plaintext and SenML JSON content formats - when parsing a\n * floating-point value, any string of length equal or greater than this setting\n * will automatically be considered invalid, even if it could in theory be\n * parsed as a valid number.\n *\n * If editing this file manually, <c>64</c> shall be\n * replaced with a positive integer literal.\n *\n * The default value defined in CMake build scripts is 512.\n * Minimal suggested setting for low-resource builds is 64.\n */\n#define ANJAY_MAX_DOUBLE_STRING_SIZE 64\n\n/**\n * Maximum length supported for a single Uri-Path or Location-Path segment.\n *\n * When handling incoming CoAP messages, any Uri-Path or Location-Path option of\n * length equal or greater than this setting will be considered invalid.\n *\n * If editing this file manually, <c>64</c> shall be\n * replaced with a positive integer literal.\n *\n * The default value defined in CMake build scripts is 256.\n * Minimal suggested setting for low-resource builds is 64.\n */\n#define ANJAY_MAX_URI_SEGMENT_SIZE 64\n\n/**\n * Maximum length supported for a single Uri-Query segment.\n *\n * When handling incoming CoAP messages, any Uri-Query option of length equal or\n * greater than this setting will be considered invalid.\n *\n * If editing this file manually, <c>64</c> shall be\n * replaced with a positive integer literal.\n *\n * The default value defined in CMake build scripts is 256.\n * Minimal suggested setting for low-resource builds is 64.\n */\n#define ANJAY_MAX_URI_QUERY_SEGMENT_SIZE 64\n\n/**\n * Size of buffer allocated for storing DTLS session state when connection is\n * not in use (e.g. during queue mode operation).\n *\n * If editing this file manually, <c>1024</c> shall be\n * replaced with a positive integer literal. The default value defined in CMake\n * build scripts is 1024.\n */\n#define ANJAY_DTLS_SESSION_BUFFER_SIZE 1024\n\n/**\n * Value of Content-Format used in Send messages. Only a few specific values are\n * supported:\n *\n * - @c AVS_COAP_FORMAT_NONE means no default value is used and Anjay will\n *   decide the format based on the what is available.\n * - @c AVS_COAP_FORMAT_OMA_LWM2M_CBOR Anjay will generate a Send message in\n *   LwM2M CBOR format.\n * - @c AVS_COAP_FORMAT_SENML_CBOR Anjay will generate a Send message in SenML\n *   CBOR format.\n * - @c AVS_COAP_FORMAT_SENML_JSON Anjay will generate a Send message in SenML\n *   JSON format.\n *\n * Note that to use a specific format it must be available during compilation.\n *\n * The default value defined in CMake build scripts is\n * <c>AVS_COAP_FORMAT_NONE</c>.\n */\n#define ANJAY_DEFAULT_SEND_FORMAT AVS_COAP_FORMAT_NONE\n\n/**\n * Optional Anjay modules.\n */\n/**@{*/\n/**\n * Enable access_control module (implementation of the Access Control object).\n *\n * Requires <c>ANJAY_WITH_ACCESS_CONTROL</c> to be enabled.\n */\n/* #undef ANJAY_WITH_MODULE_ACCESS_CONTROL */\n\n/**\n * Enable security module (implementation of the LwM2M Security object).\n */\n#define ANJAY_WITH_MODULE_SECURITY\n\n/**\n * Enable support for hardware security engine in the security module.\n *\n * This feature allows security credentials provisioned into the LwM2M Security\n * object to be automatically moved into a hardware security module.\n *\n * Requires <c>ANJAY_WITH_MODULE_SECURITY</c> to be enabled in Anjay\n * configuration, and at least one of\n * <c>AVS_COMMONS_WITH_AVS_CRYPTO_PKI_ENGINE</c> or\n * <c>AVS_COMMONS_WITH_AVS_CRYPTO_PSK_ENGINE</c> to be enabled in avs_commons\n * configuration.\n *\n * IMPORTANT: Only available as part of the HSM support commercial feature.\n * Ignored in versions distributed without this feature.\n */\n/* #undef ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT */\n\n/**\n * Enable server module (implementation of the LwM2M Server object).\n */\n#define ANJAY_WITH_MODULE_SERVER\n\n/**\n * Enable fw_update module (implementation of the Firmware Update object).\n */\n#define ANJAY_WITH_MODULE_FW_UPDATE\n\n/**\n * Enable advanced_fw_update module (implementation of the 33629 custom\n * Advanced Firmware Update object).\n */\n/* #undef ANJAY_WITH_MODULE_ADVANCED_FW_UPDATE */\n\n/**\n * Disable support for PUSH mode Firmware Update.\n *\n * Only meaningful if <c>ANJAY_WITH_MODULE_FW_UPDATE</c> is enabled. Requires\n * <c>ANJAY_WITH_DOWNLOADER</c> to be enabled.\n */\n/* #undef ANJAY_WITHOUT_MODULE_FW_UPDATE_PUSH_MODE */\n\n/**\n * Enable sw_mgmt module (implementation of the Software Management object).\n */\n/* #undef ANJAY_WITH_MODULE_SW_MGMT */\n\n/**\n * Enables ipso_objects module (generic implementation of basic sensor, three\n * axis sensor and Push Button IPSO objects).\n */\n#define ANJAY_WITH_MODULE_IPSO_OBJECTS\n\n/**\n * Enables experimental ipso_objects_v2 module (generic implementation of basic\n * sensor and three axis sensor IPSO objects).\n */\n#define ANJAY_WITH_MODULE_IPSO_OBJECTS_V2\n\n/**\n * Enable at_sms module (implementation of an SMS driver for AT modem devices).\n *\n * Requires <c>ANJAY_WITH_SMS</c> to be enabled and the operating system to\n * support the POSIX <c>poll()</c> function.\n *\n * IMPORTANT: Only available as part of the SMS commercial feature. Ignored in\n * the open source version.\n */\n/* #undef ANJAY_WITH_MODULE_AT_SMS */\n\n/**\n * Enable bg96_nidd module (implementation of NB-IoT-based NIDD driver for\n * Quectel BG96).\n *\n * Requires <c>ANJAY_WITH_NIDD</c> to be enabled.\n *\n * IMPORTANT: Only available as part of the NIDD commercial feature. Ignored\n * in the open source version.\n */\n/* #undef ANJAY_WITH_MODULE_BG96_NIDD */\n\n/**\n * Enable bootstrapper module (loader for bootstrap information formatted as per\n * the \"Storage of LwM2M Bootstrap Information on the Smartcard\" appendix to the\n * LwM2M TS).\n *\n * Requires <c>ANJAY_WITH_BOOTSTRAP</c> to be enabled and\n * <c>AVS_COMMONS_WITH_AVS_PERSISTENCE</c> to be enabled in avs_commons\n * configuration.\n *\n * IMPORTANT: Only available with the bootstrapper feature. Ignored in the open\n * source version.\n */\n/* #undef ANJAY_WITH_MODULE_BOOTSTRAPPER */\n\n/**\n * Enable the SIM bootstrap module, which enables reading the SIM bootstrap\n * information from a smartcard, which can then be passed through to the\n * bootstrapper module.\n *\n * Requires <c>ANJAY_WITH_MODULE_BOOTSTRAPPER</c> to be enabled.\n *\n * IMPORTANT: Only available with the bootstrapper feature. Ignored in the open\n * source version.\n */\n/* #undef ANJAY_WITH_MODULE_SIM_BOOTSTRAP */\n\n/**\n * Forced ID of the file to read the SIM bootstrap information from.\n *\n * If not defined (default), the bootstrap information file will be discovered\n * through the ODF file, as mandated by the specification.\n *\n * Requires <c>ANJAY_WITH_MODULE_BOOTSTRAPPER</c> to be enabled. At most one of\n * <c>ANJAY_MODULE_SIM_BOOTSTRAP_HARDCODED_FILE_ID</c> and\n * <c>ANJAY_MODULE_SIM_BOOTSTRAP_DATA_OID_OVERRIDE_HEX</c> may be defined at the\n * same time.\n *\n * IMPORTANT: Only available with the bootstrapper feature. Ignored in the open\n * source version.\n */\n/* #undef ANJAY_MODULE_SIM_BOOTSTRAP_HARDCODED_FILE_ID */\n\n/**\n * Overridden OID of the SIM bootstrap information to look for in the DODF file,\n * expressed as a hexlified DER representation (without the header).\n *\n * This is the hexlified expected value of the 'id' field within the 'OidDO'\n * sequence in the DODF file (please refer to the PKCS #15 document for more\n * information).\n *\n * If not defined, the default value of <c>\"672b0901\"</c>, which corresponds to\n * OID 2.23.43.9.1 {joint-iso-itu-t(2) international-organizations(23) wap(43)\n * oma-lwm2m(9) lwm2m-bootstrap(1)}, will be used.\n *\n * No other values than the default are valid according to the specification,\n * but some SIM cards are known to use other non-standard values, e.g.\n * <c>\"0604672b0901\"</c> - including a superfluous nested BER-TLV header, as\n * erroneously illustrated in the EF(DODF-bootstrap) file coding example in\n * LwM2M TS 1.2 and earlier (fixed in LwM2M TS 1.2.1) - which is interpreted as\n * OID 0.6.4.103.43.9.1 (note that it is invalid as the 0.6 tree does not exist\n * in the repository as of writing this note).\n *\n * Requires <c>ANJAY_WITH_MODULE_BOOTSTRAPPER</c> to be enabled. At most one of\n * <c>ANJAY_MODULE_SIM_BOOTSTRAP_HARDCODED_FILE_ID</c> and\n * <c>ANJAY_MODULE_SIM_BOOTSTRAP_DATA_OID_OVERRIDE_HEX</c> may be defined at the\n * same time.\n *\n * IMPORTANT: Only available with the bootstrapper feature. Ignored in the open\n * source version.\n */\n/* #undef ANJAY_MODULE_SIM_BOOTSTRAP_DATA_OID_OVERRIDE_HEX */\n\n/**\n * Enable factory provisioning module. Data provided during provisioning uses\n * SenML CBOR format.\n */\n/* #undef ANJAY_WITH_MODULE_FACTORY_PROVISIONING */\n\n/**\n * Enable oscore module (implementation of the OSCORE object).\n *\n * IMPORTANT: Only available as part of the OSCORE commercial feature. Ignored\n * in the open source version.\n */\n/* #undef ANJAY_WITH_MODULE_OSCORE */\n\n/**\n * If enable Anjay doesn't handle composite operation (read, observe and write).\n * Its use makes sense for LWM2M v1.1 upwards.\n *\n * This flag can be used to reduce the size of the resulting code.\n *\n * If active, anjay will respond with message code 5.01 Not Implemented to any\n * composite type request.\n */\n/* #undef ANJAY_WITHOUT_COMPOSITE_OPERATIONS */\n\n/**\n * Enable support for the experimental\n * <c>anjay_get_server_connection_status()</c> API and related\n * <c>anjay_server_connection_status_cb_t</c> callback.\n */\n/* #undef ANJAY_WITH_CONN_STATUS_API */\n\n/**\n * Enable support for the experimental\n * <c>anjay_ssl_error_cb_t</c> callback.\n * Currently only supported for mbedTLS and custom SSL integration builds\n */\n#define ANJAY_WITH_SSL_ERROR_API\n\n/**\n * Enable support for /25 LwM2M Gateway Object.\n *\n * Requires <c>ANJAY_WITH_LWM2M11</c> to be enabled.\n * Requires <c>ANJAY_WITH_CORE_PERSISTENCE</c> (commercial feature) to be\n * disabled.\n */\n/* #undef ANJAY_WITH_LWM2M_GATEWAY */\n\n/**\n * Maximum holdoff time in seconds.\n *\n * This parameter defines an upper limit on the Hold Off Time as specified in\n * the LwM2M Security Object (Object ID: 0, Resource ID: 11). The Hold Off Time\n * is used by the LwM2M Client to delay initiating a Bootstrap process.\n *\n * Limiting the Hold Off Time is important in network environments where\n * prolonged delays may cause NAT bindings to expire, potentially leading to\n * failed connection attempts. This parameter ensures that the configured\n * Hold Off Time does not exceed a safe threshold. If a DTLS binding with\n * Connection ID is used this parameter can be increased safely.\n *\n * If editing this file manually, <c>20</c> shall be\n * replaced with a positive integer literal representing the time in seconds.\n * The default value defined in CMake build scripts is 20.\n */\n#define ANJAY_MAX_HOLDOFF_TIME 20\n\n/**\n * Enable support for optional resources of the Firmware Update object\n * introduced in LwM2M 1.1.\n *\n * This includes the following resources:\n * - Severity\n * - Last State Change Time\n * - Max Defer Period\n *\n * Requires <c>ANJAY_WITH_LWM2M11</c> to be enabled.\n */\n/* #undef ANJAY_WITH_MODULE_FW_UPDATE_V11_RESOURCES */\n\n/**@}*/\n\n#endif // ANJAY_CONFIG_H\n"
  },
  {
    "path": "example_configs/embedded_lwm2m12/avsystem/coap/avs_coap_config.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef AVS_COAP_CONFIG_H\n#define AVS_COAP_CONFIG_H\n\n/**\n * @file avs_coap_config.h\n *\n * avs_coap library configuration.\n *\n * The preferred way to compile avs_coap is to use CMake, in which case this\n * file will be generated automatically by CMake.\n *\n * However, to provide compatibility with various build systems used especially\n * by embedded platforms, it is alternatively supported to compile avs_coap by\n * other means, in which case this file will need to be provided manually.\n *\n * <strong>NOTE: To compile this library without using CMake, you need to\n * configure avs_commons first. Please refer to documentation in the\n * <c>avs_commons_config.h</c> file (provided in the repository as\n * <c>avs_commons_config.h.in</c>) for details.</strong>\n *\n * <strong>avs_coap requires the following avs_commons components to be\n * enabled:</strong>\n *\n * - @c avs_buffer\n * - @c avs_compat_threading\n * - @c avs_list\n * - @c avs_net\n * - @c avs_sched\n * - @c avs_stream\n * - @c avs_utils\n * - @c avs_log (if @c WITH_AVS_COAP_LOGS is enabled)\n * - @c avs_persistence (if @c WITH_AVS_COAP_OBSERVE_PERSISTENCE is enabled)\n * - @c avs_crypto (if @c WITH_AVS_COAP_OSCORE is enabled)\n *\n * In the repository, this file is provided as <c>avs_coap_config.h.in</c>,\n * intended to be processed by CMake. If editing this file manually, please copy\n * or rename it to <c>avs_coap_config.h</c> and for each of the\n * <c>\\#cmakedefine</c> directives, please either replace it with regular\n * <c>\\#define</c> to enable it, or comment it out to disable. You may also need\n * to replace variables wrapped in <c>\\@</c> signs with concrete values. Please\n * refer to the comments above each of the specific definition for details.\n *\n * If you are editing a file previously generated by CMake, these\n * <c>\\#cmakedefine</c>s will be already replaced by either <c>\\#define</c> or\n * commented out <c>\\#undef</c> directives.\n */\n\n/**\n * Enable support for block-wise transfers (RFC 7959).\n *\n * If this flag is disabled, attempting to send a message bigger than the\n * internal buffer will fail; incoming messages may still carry BLOCK1 or BLOCK2\n * options, but they will not be interpreted by the library in any way.\n */\n#define WITH_AVS_COAP_BLOCK\n\n/**\n * Enable support for observations (RFC 7641).\n */\n#define WITH_AVS_COAP_OBSERVE\n\n/**\n * Turn on cancelling observation on a timeout.\n *\n * Only meaningful if <c>WITH_AVS_COAP_OBSERVE</c> is enabled.\n *\n * NOTE: LwM2M specification requires LwM2M server to send Cancel Observation\n * request. Meanwhile CoAP RFC 7641 states that timeout on notification should\n * cancel it. This setting is to enable both of these behaviors with default\n * focused on keeping observations in case of bad connectivity.\n */\n/* #undef WITH_AVS_COAP_OBSERVE_CANCEL_ON_TIMEOUT */\n\n/**\n * Force cancelling observation, even if a confirmable notification yielding\n * an error response is not acknowledged or rejected with RST by the observer.\n *\n * This is a circumvention for some non-compliant servers that respond with an\n * RST message to a confirmable notification yielding an error response. This\n * setting makes the library cancel the observation in such cases, even though\n * the notification is formally rejected. Additionally, it will also make the\n * library cancel the observation if no response is received at all.\n */\n#define WITH_AVS_COAP_OBSERVE_FORCE_CANCEL_ON_UNACKED_ERROR\n\n/**\n * Enable support for observation persistence (<c>avs_coap_observe_persist()</c>\n * and <c>avs_coap_observe_restore()</c> calls).\n *\n * Only meaningful if <c>WITH_AVS_COAP_OBSERVE</c> is enabled.\n */\n#define WITH_AVS_COAP_OBSERVE_PERSISTENCE\n\n/**\n * Enable support for the streaming API\n */\n#define WITH_AVS_COAP_STREAMING_API\n\n/**\n * Enable support for UDP transport.\n *\n * NOTE: Enabling at least one transport is necessary for the library to be\n * useful.\n */\n#define WITH_AVS_COAP_UDP\n\n/**\n * Enable support for TCP transport (RFC 8323).\n *\n * NOTE: Enabling at least one transport is necessary for the library to be\n * useful.\n */\n#define WITH_AVS_COAP_TCP\n\n/**\n * Enable support for OSCORE (RFC 8613).\n *\n * IMPORTANT: Only available with the OSCORE feature. Ignored in the open\n * source version.\n */\n/* #undef WITH_AVS_COAP_OSCORE */\n\n/**\n * Use OSCORE version from draft-ietf-core-object-security-08 instead of the\n * final version (RFC 8613).\n *\n * Only meaningful if <c>WITH_AVS_COAP_OSCORE</c> is enabled.\n *\n * IMPORTANT: Only available with the OSCORE feature. Ignored in the open\n * source version.\n */\n/* #undef WITH_AVS_COAP_OSCORE_DRAFT_8 */\n\n/**\n * Maximum size in bytes supported for the Master Secret.\n *\n * If editing this file manually, set it to a positive integer literal.\n *\n * The default value defined in CMake build scripts is 32.\n *\n * IMPORTANT: Only available with the OSCORE feature. Ignored in the open\n * source version.\n */\n/* #undef AVS_COAP_OSCORE_MASTER_SECRET_SIZE */\n\n/**\n * Maximum size in bytes supported for the Master Salt.\n *\n * If editing this file manually, set it to a positive integer literal.\n *\n * The default value defined in CMake build scripts is 16.\n *\n * IMPORTANT: Only available with the OSCORE feature. Ignored in the open\n * source version.\n */\n/* #undef AVS_COAP_OSCORE_MASTER_SALT_SIZE */\n\n/**\n * Maximum number of notification tokens stored to match Reset responses to.\n *\n * Only meaningful if <c>WITH_AVS_COAP_OBSERVE</c> and <c>WITH_AVS_COAP_UDP</c>\n * are enabled.\n *\n * If editing this file manually, <c>4</c> shall be\n * replaced with a positive integer literal. The default value defined in CMake\n * build scripts is 4.\n */\n#define AVS_COAP_UDP_NOTIFY_CACHE_SIZE 4\n\n/**\n * Enable sending diagnostic payload in error responses.\n */\n#define WITH_AVS_COAP_DIAGNOSTIC_MESSAGES\n\n/**\n * Enable logging in avs_coap.\n *\n * If this flag is disabled, no logging is compiled into the binary at all.\n */\n#define WITH_AVS_COAP_LOGS\n\n/**\n * Enable TRACE-level logs in avs_coap.\n *\n * Only meaningful if <c>WITH_AVS_COAP_LOGS</c> is enabled.\n */\n/* #undef WITH_AVS_COAP_TRACE_LOGS */\n\n/**\n * Enable poisoning of unwanted symbols when compiling avs_coap.\n *\n * Requires a compiler that supports <c>\\#pragma GCC poison</c>.\n *\n * This is mostly useful during development, to ensure that avs_commons do not\n * attempt to call functions considered harmful in this library, such as printf.\n * This is not guaranteed to work as intended on every platform.\n */\n/* #undef WITH_AVS_COAP_POISONING */\n\n#endif // AVS_COAP_CONFIG_H\n"
  },
  {
    "path": "example_configs/embedded_lwm2m12/avsystem/commons/avs_commons_config.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AVS_COMMONS_CONFIG_H\n#define AVS_COMMONS_CONFIG_H\n\n/**\n * @file avs_commons_config.h\n *\n * avs_commons library configuration.\n *\n * The preferred way to compile avs_commons is to use CMake, in which case this\n * file will be generated automatically by CMake.\n *\n * However, to provide compatibility with various build systems used especially\n * by embedded platforms, it is alternatively supported to compile avs_commons\n * by other means, in which case this file will need to be provided manually.\n *\n * In the repository, this file is provided as <c>avs_commons_config.h.in</c>,\n * intended to be processed by CMake. If editing this file manually, please copy\n * or rename it to <c>avs_commons_config.h</c> and for each of the\n * <c>#cmakedefine</c> directives, please either replace it with regular\n * <c>#define</c> to enable it, or comment it out to disable. You may also need\n * to replace variables wrapped in <c>@</c> signs with concrete values. Please\n * refer to the comments above each of the specific definition for details.\n *\n * If you are editing a file previously generated by CMake, these\n * <c>#cmakedefine</c>s will be already replaced by either <c>#define</c> or\n * commented out <c>#undef</c> directives.\n */\n\n/**\n * Options that describe capabilities of the build environment.\n *\n * NOTE: If you leave some of these macros undefined, even though the given\n * feature is actually available in the system, avs_commons will attempt to use\n * its own substitutes, which may be incompatible with the definition in the\n * system and lead to undefined behaviour.\n */\n/**@{*/\n/**\n * Is the target platform big-endian?\n *\n * If undefined, little-endian is assumed. Mixed-endian architectures are not\n * supported.\n *\n * Affects <c>avs_convert_be*()</c> and <c>avs_[hn]to[hn]*()</c> calls in\n * avs_utils and, by extension, avs_persistence.\n */\n/* #undef AVS_COMMONS_BIG_ENDIAN */\n\n/**\n * Is GNU __builtin_add_overflow() extension available?\n *\n * Affects time handling functions in avs_utils. If disabled, software overflow\n * checking will be compiled. Note that this software overflow checking code\n * relies on U2 representation of signed integers.\n */\n/* #undef AVS_COMMONS_HAVE_BUILTIN_ADD_OVERFLOW */\n\n/**\n * Is GNU __builtin_mul_overflow() extension available?\n *\n * Affects time handling functions in avs_utils. If disabled, software overflow\n * checking will be compiled. Note that this software overflow checking code\n * relies on U2 representation of signed integers.\n */\n/* #undef AVS_COMMONS_HAVE_BUILTIN_MUL_OVERFLOW */\n\n/**\n * Is net/if.h available in the system?\n *\n * NOTE: If the header is indeed available, but this option is not defined, the\n * <c>IF_NAMESIZE</c> macro will be defined <strong>publicly by avs_commons\n * headers</strong>, which may conflict with system definitions.\n */\n/* #undef AVS_COMMONS_HAVE_NET_IF_H */\n\n/**\n * Are GNU diagnostic pragmas (#pragma GCC diagnostic push/pop/ignored)\n * available?\n *\n * If defined, those pragmas will be used to suppress compiler warnings for some\n * code known to generate them and cannot be improved in a more robust way, e.g.\n * for code that is known to generate warnings from within system headers.\n */\n/* #undef AVS_COMMONS_HAVE_PRAGMA_DIAGNOSTIC */\n\n/**\n * Are GNU visibility pragmas (#pragma GCC visibility push/pop) available?\n *\n * Meaningful mostly if avs_commons will be directly or indirectly linked into\n * a shared library. Causes all symbols except those declared in public headers\n * to be hidden, i.e. not exported outside the shared library. If not defined,\n * default compiler visibility settings will be used, but you still may use\n * compiler flags and linker version scripts to replicate this manually if\n * needed.\n */\n/* #undef AVS_COMMONS_HAVE_VISIBILITY */\n\n/**\n * Specify an optional compatibility header that allows use of POSIX-specific\n * code that is not compliant with POSIX enough to be compiled directly.\n *\n * This header, if specified, will be included only by the following components,\n * which may be enabled or disabled depending on state of the referenced flags:\n * - avs_compat_threading implementation based on POSIX Threads\n *   (@ref AVS_COMMONS_COMPAT_THREADING_WITH_PTHREAD)\n * - default avs_net socket implementation\n *   (@ref AVS_COMMONS_NET_WITH_POSIX_AVS_SOCKET)\n * - avs_unit (@ref AVS_COMMONS_WITH_AVS_UNIT)\n * - default implementation of avs_time routines\n *   (@ref AVS_COMMONS_UTILS_WITH_POSIX_AVS_TIME)\n *\n * Compatibility headers for lwIP and Microsoft Windows are provided with the\n * library (see the <c>compat</c> directory).\n *\n * If this macro is not defined, the afore-mentioned components, if enabled,\n * will use system headers directly, assuming they are POSIX-compliant.\n *\n * If this macro is enabled, the specified file will be included through an\n * <c>#include AVS_COMMONS_POSIX_COMPAT_HEADER</c> statement. Thus, if editing\n * this file manually, <c>avsystem/commons/lwip-posix-compat.h</c> shall be\n * replaced with a path to such file.\n */\n#define AVS_COMMONS_POSIX_COMPAT_HEADER \"avsystem/commons/lwip-posix-compat.h\"\n\n/**\n * Set if printf implementation doesn't support 64-bit format specifiers.\n * If defined, custom implementation of conversion is used in\n * @c AVS_UINT64_AS_STRING instead of using @c snprintf .\n */\n/* #undef AVS_COMMONS_WITHOUT_64BIT_FORMAT_SPECIFIERS */\n\n/**\n * Set if printf implementation doesn't support floating-point numbers.\n * If defined, custom implementation of conversion is used in\n * @c AVS_DOUBLE_AS_STRING instead of using @c snprintf . This might increase\n * compatibility with some embedded libc implementations that do not provide\n * this functionality.\n *\n * NOTE: In order to keep the custom implementation small in code size, it is\n * not intended to be 100% accurate. Rounding errors may occur - according to\n * empirical checks, they show up around the 16th significant decimal digit.\n */\n/* #undef AVS_COMMONS_WITHOUT_FLOAT_FORMAT_SPECIFIERS */\n/**@}*/\n\n/**\n * Enable poisoning of unwanted symbols when compiling avs_commons.\n *\n * Requires a compiler that supports #pragma GCC poison.\n *\n * This is mostly useful during development, to ensure that avs_commons do not\n * attempt to call functions considered harmful in this library, such as printf.\n * This is not guaranteed to work as intended on every platform, e.g. on macOS\n * it is known to generate false positives due to different dependencies between\n * system headers.\n */\n/* #undef AVS_COMMONS_WITH_POISONING */\n\n/**\n * Options that control compilation of avs_commons components.\n *\n * Each of the configuration options below enables, if defined, one of the core\n * components of the avs_commons library.\n *\n * NOTE: Enabling avs_unit will cause an object file with an implementation of\n * main() to be generated.\n */\n/**@{*/\n#define AVS_COMMONS_WITH_AVS_ALGORITHM\n#define AVS_COMMONS_WITH_AVS_BUFFER\n#define AVS_COMMONS_WITH_AVS_COMPAT_THREADING\n#define AVS_COMMONS_WITH_AVS_CRYPTO\n/* #undef AVS_COMMONS_WITH_AVS_HTTP */\n#define AVS_COMMONS_WITH_AVS_LIST\n#define AVS_COMMONS_WITH_AVS_LOG\n#define AVS_COMMONS_WITH_AVS_NET\n#define AVS_COMMONS_WITH_AVS_PERSISTENCE\n/* #undef AVS_COMMONS_WITH_AVS_RBTREE */\n#define AVS_COMMONS_WITH_AVS_SORTED_SET\n#define AVS_COMMONS_WITH_AVS_SCHED\n#define AVS_COMMONS_WITH_AVS_STREAM\n/* #undef AVS_COMMONS_WITH_AVS_UNIT */\n#define AVS_COMMONS_WITH_AVS_URL\n#define AVS_COMMONS_WITH_AVS_UTILS\n/* #undef AVS_COMMONS_WITH_AVS_VECTOR */\n/**@}*/\n\n/**\n * Options that control compilation of avs_compat_threading implementations.\n *\n * If CMake is not used, in the typical scenario at most one of the following\n * implementations may be enabled at the same time. If none is enabled, the\n * relevant symbols will need to be provided by the user, if used.\n *\n * These are meaningful only if <c>AVS_COMMONS_WITH_AVS_COMPAT_THREADING</c> is\n * defined.\n */\n/**@{*/\n/**\n * Enable implementation based on spinlocks.\n *\n * This implementation is usually very inefficient, and requires C11 stdatomic.h\n * header to be available.\n */\n/* #undef AVS_COMMONS_COMPAT_THREADING_WITH_ATOMIC_SPINLOCK */\n\n/**\n * Enable implementation based on the POSIX Threads library.\n *\n * This implementation is preferred over the spinlock-based one, but the POSIX\n * Threads library is normally available only in UNIX-like environments.\n */\n/* #undef AVS_COMMONS_COMPAT_THREADING_WITH_PTHREAD */\n\n/**\n * Is the <c>pthread_condattr_setclock()</c> function available?\n *\n * This flag only makes sense when\n * <c>AVS_COMMONS_COMPAT_THREADING_WITH_PTHREAD</c> is enabled.\n *\n * If this flag is disabled, or if <c>CLOCK_MONOTONIC</c> macro is not\n * available, the <c>avs_condvar_wait()</c> will internally use the real-time\n * clock instead of the monotonic clock. Time values will be converted so that\n * this change does not affect API usage.\n */\n/* #undef AVS_COMMONS_COMPAT_THREADING_PTHREAD_HAVE_PTHREAD_CONDATTR_SETCLOCK */\n/**@}*/\n\n/**\n * Options that control compilation of code depending on TLS backend library.\n *\n * If CMake is not used, in the typical scenario at most one of the following\n * DTLS backends may be enabled at the same time. If none is enabled,\n * functionalities that depends on cryptography will be disabled.\n *\n * Affects avs_crypto, avs_net, and avs_stream (for the MD5 implementation).\n *\n * mbed TLS is the main development backend, and is preferred as such. OpenSSL\n * backend supports most functionality as well, but is not as thoroughly tested.\n * TinyDTLS support is only rudimentary.\n */\n/**@{*/\n#define AVS_COMMONS_WITH_MBEDTLS\n/* #undef AVS_COMMONS_WITH_OPENSSL */\n/* #undef AVS_COMMONS_WITH_TINYDTLS */\n\n/**\n * Enable support for custom TLS socket implementation.\n *\n * If enabled, the user needs to provide their own implementations of\n * <c>_avs_net_create_ssl_socket()</c>, <c>_avs_net_create_dtls_socket()</c>,\n * <c>_avs_net_initialize_global_ssl_state() and\n * <c>_avs_net_cleanup_global_ssl_state()</c>.\n */\n/* #undef AVS_COMMONS_WITH_CUSTOM_TLS */\n/**@}*/\n\n/**\n * Options related to avs_crypto.\n */\n/**@{*/\n/**\n * Enable AEAD and HKDF support in avs_crypto. Requires MbedTLS in version at\n * least 2.14.0, OpenSSL in version at least 1.1.0, or custom implementation in\n * case of <c>AVS_COMMONS_WITH_CUSTOM_TLS</c>.\n */\n/* #undef AVS_COMMONS_WITH_AVS_CRYPTO_ADVANCED_FEATURES */\n\n/**\n * If the TLS backend is either mbed TLS or OpenSSL, enables APIs related to\n * public-key cryptography.\n *\n * Public-key cryptography is not currently supported with TinyDTLS.\n *\n * It also enables support for X.509 certificates in avs_net, if that module is\n * also enabled.\n */\n/* #undef AVS_COMMONS_WITH_AVS_CRYPTO_PKI */\n\n/**\n * If the TLS backend is either mbed TLS, OpenSSL or TinyDTLS, enables support\n * of pre-shared key security.\n *\n * PSK is the only supported security mode for the TinyDTLS backend, so this\n * option MUST be enabled to utilize it.\n *\n * It also enables support for pre-shared key security in avs_net, if that\n * module is also enabled.\n */\n#define AVS_COMMONS_WITH_AVS_CRYPTO_PSK\n\n/**\n * Enables usage of Valgrind API to suppress some of the false positives\n * generated by the OpenSSL backend.\n */\n/* #undef AVS_COMMONS_WITH_AVS_CRYPTO_VALGRIND */\n\n/**\n * Enables high-level support for hardware-based PKI security, i.e. loading,\n * generating and managing key pairs and certificates via external engines.\n *\n * Requires @ref AVS_COMMONS_WITH_AVS_CRYPTO_PKI to be enabled.\n *\n * An actual implementation is required to use this feature. You may provide\n * your own, or use one of the default ones that come with the HSM engine\n * commercial feature (see @ref AVS_COMMONS_WITH_MBEDTLS_PKCS11_ENGINE,\n * @ref AVS_COMMONS_WITH_MBEDTLS_PSA_ENGINE and\n * @ref AVS_COMMONS_WITH_OPENSSL_PKCS11_ENGINE).\n *\n * The functions that need to be provided in case of a custom implementation:\n * - <c>avs_crypto_pki_engine_certificate_rm()</c>\n * - <c>avs_crypto_pki_engine_certificate_store()</c>\n * - <c>avs_crypto_pki_engine_key_gen()</c>\n * - <c>avs_crypto_pki_engine_key_rm()</c>\n * - <c>avs_crypto_pki_engine_key_store()</c>\n * - When targeting the Mbed TLS backend:\n *   - <c>_avs_crypto_mbedtls_engine_initialize_global_state()</c>\n *   - <c>_avs_crypto_mbedtls_engine_cleanup_global_state()</c>\n *   - <c>_avs_crypto_mbedtls_engine_append_cert()</c>\n *   - <c>_avs_crypto_mbedtls_engine_append_crl()</c>\n *   - <c>_avs_crypto_mbedtls_engine_load_private_key()</c>\n * - When targeting the OpenSSL backend:\n *   - <c>_avs_crypto_openssl_engine_initialize_global_state()</c>\n *   - <c>_avs_crypto_openssl_engine_cleanup_global_state()</c>\n *   - <c>_avs_crypto_openssl_engine_load_certs()</c>\n *   - <c>_avs_crypto_openssl_engine_load_crls()</c>\n *   - <c>_avs_crypto_openssl_engine_load_private_key()</c>\n *\n * External PKI engines are NOT supported in the TinyDTLS backend.\n */\n/* #undef AVS_COMMONS_WITH_AVS_CRYPTO_PKI_ENGINE */\n\n/**\n * Enables high-level support for hardware-based PSK security, i.e. loading\n * and managing PSK keys and identities via external engine.\n *\n * Requires @ref AVS_COMMONS_WITH_AVS_CRYPTO_PKI to be enabled.\n *\n * An actual implementation is required to use this feature. You may provide\n * your own, or use the default PSA-based one that comes with the HSM engine\n * commercial feature (see @ref AVS_COMMONS_WITH_MBEDTLS_PSA_ENGINE).\n *\n * The functions that need to be provided in case of a custom implementation:\n * - <c>avs_crypto_psk_engine_identity_store()</c>\n * - <c>avs_crypto_psk_engine_identity_rm()</c>\n * - <c>avs_crypto_psk_engine_key_store()</c>\n * - <c>avs_crypto_psk_engine_key_rm()</c>\n * - When targeting the Mbed TLS backend:\n *   - <c>_avs_crypto_mbedtls_engine_initialize_global_state()</c>\n *   - <c>_avs_crypto_mbedtls_engine_cleanup_global_state()</c>\n *   - <c>_avs_crypto_mbedtls_engine_load_psk_key()</c>\n *\n * External PSK engines are NOT supported in the OpenSSL and TinyDTLS backend.\n */\n/* #undef AVS_COMMONS_WITH_AVS_CRYPTO_PSK_ENGINE */\n\n/**\n * Enables the default implementation of avs_crypto engine, based on Mbed TLS\n * and PKCS#11.\n *\n * Requires @ref AVS_COMMONS_WITH_AVS_CRYPTO_PKI_ENGINE to be enabled.\n *\n * NOTE: Query string format for this engine is a subset of the PKCS#11 URI\n * scheme (see RFC 7512), modelled after the format accepted by libp11 OpenSSL\n * engine.\n *\n * NOTE: The unit tests for this feature depend on SoftHSM and pkcs11-tool.\n * These must be installed for the tests to pass.\n *\n * IMPORTANT: Only available as part of the HSM support commercial feature.\n * Ignored in the open source version.\n */\n/* #undef AVS_COMMONS_WITH_MBEDTLS_PKCS11_ENGINE */\n\n/**\n * Enables the default implementation of avs_crypto engine, based on Mbed TLS\n * and Platform Security Architecture (PSA).\n *\n * Requires @ref AVS_COMMONS_WITH_AVS_CRYPTO_PKI_ENGINE or\n * @ref AVS_COMMONS_WITH_AVS_CRYPTO_PSK_ENGINE to be enabled.\n *\n * NOTE: Query string format for this engine is:\n *\n * <pre>\n * kid=<key_ID>[,lifetime=<lifetime>]|uid=<persistent_storage_UID>\n * </pre>\n *\n * The values are parsed using strtoull() with base=0, so may be in decimal,\n * 0-prefixed octal or 0x-prefixed hexadecimal. On key generation and\n * certificate storage, the specified lifetime will be used, or lifetime 1\n * (default persistent storage) will be used if not. On key or certificate use,\n * the lifetime of the actual key will be verified if present on the query\n * string and the key will be rejected if different.\n *\n * Certificates are stored as PSA_KEY_TYPE_RAW_DATA key entries containing\n * X.509 DER data. Alternatively, the PSA Protected Storage API can be used if\n * @ref AVS_COMMONS_WITH_MBEDTLS_PSA_ENGINE_PROTECTED_STORAGE is enabled, by\n * using the <c>uid=...</c> syntax.\n *\n * IMPORTANT: Only available as part of the HSM support commercial feature.\n * Ignored in the open source version.\n */\n/* #undef AVS_COMMONS_WITH_MBEDTLS_PSA_ENGINE */\n\n/**\n * Enables support for the PSA Protected Storage API in the PSA-based avs_crypto\n * engine.\n *\n * Requires @ref AVS_COMMONS_WITH_MBEDTLS_PSA_ENGINE to be enabled.\n *\n * IMPORTANT: Only available as part of the HSM support commercial feature.\n * Ignored in the open source version.\n */\n/* #undef AVS_COMMONS_WITH_MBEDTLS_PSA_ENGINE_PROTECTED_STORAGE */\n\n/**\n * Enables use of the <c>psa_generate_random()</c> function as the default\n * random number generator when using the Mbed TLS crypto backend, instead of\n * CTR-DRBG seeded by the Mbed TLS entropy pool.\n *\n * It's meaningful only when @ref AVS_COMMONS_WITH_MBEDTLS is enabled. However,\n * it is independent from the above PSA engine settings.\n */\n/* #undef AVS_COMMONS_WITH_MBEDTLS_PSA_RNG */\n\n/**\n * Is the <c>dlsym()</c> function available?\n *\n * This is currently only used if @ref AVS_COMMONS_WITH_MBEDTLS_PKCS11_ENGINE is\n * enabled. If enabled, the PKCS#11 module is loaded dynamically from a library\n * specified by the <c>PKCS11_MODULE_PATH</c> environment variable. If disabled,\n * a function with the following signature, realizing the PKCS#11\n * <c>C_GetFunctionList</c> method, must be provided manually:\n *\n * <pre>\n * CK_RV _avs_crypto_mbedtls_pkcs11_get_function_list(CK_FUNCTION_LIST_PTR_PTR);\n * </pre>\n */\n/* #undef AVS_COMMONS_HAVE_DLSYM */\n\n/**\n * Enables the default implementation of avs_crypto engine, based on OpenSSL and\n * PKCS#11.\n *\n * Requires @ref AVS_COMMONS_WITH_AVS_CRYPTO_PKI_ENGINE to be enabled.\n *\n * NOTE: Query string format for this engine is a subset of the PKCS#11 URI\n * scheme (see RFC 7512), modelled after the format accepted by libp11 OpenSSL\n * engine.\n *\n * NOTE: The unit tests for this feature depend on SoftHSM and pkcs11-tool.\n * These must be installed for the tests to pass.\n *\n * IMPORTANT: Only available as part of the HSM support commercial feature.\n * Ignored in the open source version.\n */\n/* #undef AVS_COMMONS_WITH_OPENSSL_PKCS11_ENGINE */\n/**@}*/\n\n/**\n * Enable support for HTTP content compression in avs_http.\n *\n * Requires linking with zlib.\n */\n/* #undef AVS_COMMONS_HTTP_WITH_ZLIB */\n\n/**\n * Options related to avs_log and logging support within avs_commons.\n */\n/**@{*/\n/* clang-format off */\n/**\n * Size, in bytes, of the avs_log buffer.\n *\n * Log messages that would (including the level, module name and code location)\n * otherwise be longer than this value minus one (for the terminating null\n * character) will be truncated.\n *\n * NOTE: This macro MUST be defined if avs_log is enabled.\n *\n * If editing this file manually, <c>512</c> shall\n * be replaced with a positive integer literal. The default value defined in\n * CMake build scripts is 512.\n */\n#define AVS_COMMONS_LOG_MAX_LINE_LENGTH 512\n/* clang-format on */\n\n/**\n * Configures avs_log to use a synchronized global buffer instead of allocating\n * a buffer on the stack when constructing log messages.\n *\n * Requires avs_compat_threading to be enabled.\n *\n * Enabling this option would reduce the stack space required to use avs_log, at\n * the expense of global storage and the complexity of using a mutex.\n */\n#define AVS_COMMONS_LOG_USE_GLOBAL_BUFFER\n\n/**\n * Provides a default avs_log handler that prints log messages on stderr.\n *\n * Disabling this option will cause logs to be discarded by default, until a\n * custom log handler is set using <c>avs_log_set_handler()</c>.\n */\n/* #undef AVS_COMMONS_LOG_WITH_DEFAULT_HANDLER */\n\n/**\n * Enables the \"micro logs\" feature.\n *\n * Replaces all occurrences of the <c>AVS_DISPOSABLE_LOG()</c> macro with single\n * space strings. This is intended to reduce the size of the compiled code, by\n * stripping it of almost all log string data.\n *\n * Note that this setting will propagate both to avs_commons components\n * themselves (as all its internal logs make use of <c>AVS_DISPOSABLE_LOG()</c>)\n * and the user code that uses it.\n */\n/* #undef AVS_COMMONS_WITH_MICRO_LOGS */\n\n/**\n * Enables logging inside avs_commons.\n *\n * Requires @ref AVS_COMMONS_WITH_AVS_LOG to be enabled.\n *\n * If this macro is not defined at avs_commons compile time, calls to avs_log\n * will not be generated inside avs_commons components.\n */\n#define AVS_COMMONS_WITH_INTERNAL_LOGS\n\n/**\n * Enables TRACE-level logs inside avs_commons.\n *\n * Only meaningful if AVS_COMMONS_WITH_INTERNAL_LOGS is enabled.\n *\n * If this macro is not defined at avs_commons compile time, calls to avs_log\n * with the level set to TRACE will not be generated inside avs_commons\n * components.\n */\n/* #undef AVS_COMMONS_WITH_INTERNAL_TRACE */\n\n/**\n * Enables external implementation of logger subsystem with provided header.\n *\n * Default logger implementation can be found in avs_log_impl.h\n */\n/* #undef AVS_COMMONS_WITH_EXTERNAL_LOGGER_HEADER */\n\n/**\n * If specified, the process of checking if avs_log should be written out\n * takes place in compile time.\n *\n * Specify an optional header with a list of modules for which log level\n * is set. If a log level for specific module is not set, the DEFAULT level\n * will be taken into account. Value of the default logging level is set to\n * DEBUG, but can be overwritten in this header file with AVS_LOG_LEVEL_DEFAULT\n * define. Messages with lower level than the one set will be removed during\n * compile time. Possible values match @ref avs_log_level_t.\n *\n * That file should contain C preprocesor defines in the:\n * - \"#define AVS_LOG_LEVEL_FOR_MODULE_<Module> <Level>\" format,\n *   where <Module> is the module name and <Level> is allowed logging level\n * - \"#define AVS_LOG_LEVEL_DEFAULT <Level>\" format, where <Level> is the\n *   allowed logging level\n *\n * Example file content:\n *\n * <code>\n * #ifndef AVS_COMMONS_EXTERNAL_LOG_LEVELS_H\n * #define AVS_COMMONS_EXTERNAL_LOG_LEVELS_H\n *\n * // global log level value\n * #define AVS_LOG_LEVEL_DEFAULT INFO\n *\n * //for \"coap\" messages only WARNING and ERROR messages will be present\n * #define AVS_LOG_LEVEL_FOR_MODULE_coap WARNING\n *\n * //logs are disable for \"net\" module\n * #define AVS_LOG_LEVEL_FOR_MODULE_net QUIET\n *\n * #endif\n * </code>\n */\n/* #undef AVS_COMMONS_WITH_EXTERNAL_LOG_LEVELS_HEADER */\n\n/**\n * Disable log level check in runtime. Allows to save at least 1.3kB of memory.\n *\n * The macros avs_log_set_level and avs_log_set_default_level\n * will not be available.\n *\n */\n/* #undef AVS_COMMONS_WITHOUT_LOG_CHECK_IN_RUNTIME */\n/**@}*/\n\n/**\n * Options related to avs_net.\n */\n/**@{*/\n/**\n * Enables support for IPv4 connectivity.\n *\n * At least one of AVS_COMMONS_NET_WITH_IPV4 and AVS_COMMONS_NET_WITH_IPV6\n * MUST be defined if avs_net is enabled.\n */\n#define AVS_COMMONS_NET_WITH_IPV4\n\n/**\n * Enables support for IPv6 connectivity.\n *\n * At least one of AVS_COMMONS_NET_WITH_IPV4 and AVS_COMMONS_NET_WITH_IPV6\n * MUST be defined if avs_net is enabled.\n */\n/* #undef AVS_COMMONS_NET_WITH_IPV6 */\n\n/**\n * If the TLS backend is set to OpenSSL, enables support for DTLS.\n *\n * DTLS is always enabled for the mbed TLS and TinyDTLS backends.\n */\n/* #undef AVS_COMMONS_NET_WITH_DTLS */\n\n/**\n * Enables debug logs generated by mbed TLS.\n *\n * An avs_log-backed handler, logging for the \"mbedtls\" module on the TRACE\n * level, is installed using <c>mbedtls_ssl_conf_dbg()</c> for each (D)TLS\n * socket created if this option is enabled.\n */\n/* #undef AVS_COMMONS_NET_WITH_MBEDTLS_LOGS */\n\n/**\n * Enables (Pre-)Master-Secret logs generation.\n *\n * These logs contain TLS session secrets that tools like Wireshark can use\n * to decrypt captured TLS traffic.\n *\n * NOTE: This only works with Mbed TLS starting from v3.0.0. If you’re using\n * Mbed TLS earlier than v3.1.0, you must enable MBEDTLS_SSL_EXPORT_KEYS.\n *\n * NOTE: The user must specify the stream to which the logs will be transferred\n * using the avs_mbedtls_set_sslkeylog_stream function.\n */\n/* #undef AVS_COMMONS_NET_WITH_MBEDTLS_SSLKEYLOG */\n\n/**\n * Enables the default implementation of avs_net TCP and UDP sockets.\n *\n * Requires either a UNIX-like operating environment, or a compatibility layer\n * with a high degree of compatibility with standard BSD sockets with an\n * appropriate compatibility header (see @ref AVS_COMMONS_POSIX_COMPAT_HEADER) -\n * lwIP and Winsock are currently supported for this scenario.\n */\n#define AVS_COMMONS_NET_WITH_POSIX_AVS_SOCKET\n\n/**\n * @deprecated This is deprecated API. This API can be removed in future\n * releases, without any notice.\n *\n * Enables support for logging socket communication to file.\n *\n * If this option is enabled, avs_net_socket_debug() can be used to enable\n * logging all communication to a file called DEBUG.log. If disabled,\n * avs_net_socket_debug() will always return an error.\n */\n/* #undef AVS_COMMONS_NET_WITH_SOCKET_LOG */\n\n/**\n * @experimental This is experimental API. This API can change in the future\n * versions without any notice.\n *\n * Enables support for custom net traffic interceptor.\n *\n * If this option is enabled, the user needs to provide their own implementation\n * of avs_net_create_traffic_interceptor() function which can wrap and extend\n * an underlying socket.\n */\n/* #undef AVS_COMMONS_WITH_TRAFFIC_INTERCEPTOR */\n\n/**\n * If the TLS backend is either mbed TLS or OpenSSL, enables support for (D)TLS\n * session persistence.\n *\n * Session persistence is not currently supported for the TinyDTLS backend.\n */\n#define AVS_COMMONS_NET_WITH_TLS_SESSION_PERSISTENCE\n/**@}*/\n\n/**\n * Options related to avs_net's default implementation of TCP and UDP sockets.\n *\n * These options make sense only when @ref AVS_COMMONS_NET_WITH_POSIX_AVS_SOCKET\n * is enabled. They describe capabilities of the Unix-like environment in which\n * the library is built.\n *\n * Note that if @ref AVS_COMMONS_POSIX_COMPAT_HEADER is defined, it might\n * redefine these flags independently of the settings in this file.\n */\n/**@{*/\n/**\n * Is the <c>gai_strerror()</c> function available?\n *\n * Enabling this flag will provide more detailed log messages in case that\n * <c>getaddrinfo()</c> fails. If this flag is disabled, numeric error codes\n * values will be logged.\n */\n/* #undef AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_GAI_STRERROR */\n\n/**\n * Is the <c>getifaddrs()</c> function available?\n *\n * Disabling this flag will cause <c>avs_net_socket_interface_name()</c> to use\n * a less optimal implementation based on the <c>SIOCGIFCONF</c> ioctl.\n *\n * If <c>SIOCGIFCONF</c> is not defined, either, then\n * <c>avs_net_socket_interface_name()</c> will always return an error.\n */\n/* #undef AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_GETIFADDRS */\n\n/**\n * Is the <c>getnameinfo()</c> function available?\n *\n * Disabling this flag will cause <c>avs_net_socket_receive_from()</c>,\n * <c>avs_net_socket_accept()</c>,\n * <c>avs_net_resolved_endpoint_get_host_port()</c>,\n * <c>avs_net_resolved_endpoint_get_host()</c> and\n * <c>avs_net_resolve_host_simple()</c> to use a custom reimplementation of\n * <c>getnameinfo()</c> based on <c>inet_ntop()</c>.\n */\n/* #undef AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_GETNAMEINFO */\n\n/**\n * Is the <c>IN6_IS_ADDR_V4MAPPED</c> macro available and usable?\n *\n * Disabling this flag will cause a custom code that compares IPv6 addresses\n * with the <c>::ffff:0.0.0.0/32</c> mask to be used instead.\n */\n/* #undef AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_IN6_IS_ADDR_V4MAPPED */\n\n/**\n * Should be defined if IPv4-mapped IPv6 addresses (<c>::ffff:0.0.0.0/32</c>)\n * are <strong>NOT</strong> supported by the underlying platform.\n *\n * Enabling this flag will prevent avs_net from using IPv4-mapped IPv6 addresses\n * and instead re-open and re-bind the socket if a connection to an IPv4 address\n * is requested on a previously created IPv6 socket.\n *\n * This may result in otherwise redundant <c>socket()</c>, <c>bind()</c> and\n * <c>close()</c> system calls to be performed, but may be necessary for\n * interoperability with some platforms.\n */\n/* #undef AVS_COMMONS_NET_POSIX_AVS_SOCKET_WITHOUT_IN6_V4MAPPED_SUPPORT */\n\n/**\n * Is the <c>inet_ntop()</c> function available?\n *\n * Disabling this flag will cause an internal implementation of this function\n * adapted from BIND 4.9.4 to be used instead.\n */\n/* #undef AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_INET_NTOP */\n\n/**\n * Is the <c>poll()</c> function available?\n *\n * Disabling this flag will cause a less robust code based on <c>select()</c> to\n * be used instead.\n */\n/* #undef AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_POLL */\n\n/**\n * Is the <c>recvmsg()</c> function available?\n *\n * Disabling this flag will cause <c>recvfrom()</c> to be used instead. Note\n * that for UDP sockets, this will cause false positives for datagram truncation\n * detection (<c>AVS_EMSGSIZE</c>) to be reported when the received message is\n * exactly the size of the buffer.\n */\n/* #undef AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_RECVMSG */\n/**@}*/\n\n/**\n * Enable thread safety in avs_sched.\n *\n * Makes all scheduler accesses synchronized and thread-safe, at the cost of\n * requiring avs_compat_threading to be enabled, and higher resource usage.\n */\n/* #undef AVS_COMMONS_SCHED_THREAD_SAFE */\n\n/**\n * Enable support for file I/O in avs_stream.\n *\n * Disabling this flag will cause the functions declared in\n * <c>avs_stream_file.h</c> to not be defined.\n */\n/* #undef AVS_COMMONS_STREAM_WITH_FILE */\n\n/**\n * Enable usage of <c>backtrace()</c> and <c>backtrace_symbols()</c> when\n * reporting assertion failures from avs_unit.\n *\n * Requires the afore-mentioned GNU-specific functions to be available.\n *\n * If this flag is disabled, stack traces will not be displayed with assertion\n * failures.\n */\n/* #undef AVS_COMMONS_UNIT_POSIX_HAVE_BACKTRACE */\n\n/**\n * Options related to avs_utils.\n */\n/**@{*/\n/**\n * Enable the default implementation of avs_time_real_now() and\n * avs_time_monotonic_now().\n *\n * Requires an operating environment that supports a clock_gettime() call\n * compatible with POSIX.\n */\n/* #undef AVS_COMMONS_UTILS_WITH_POSIX_AVS_TIME */\n\n/**\n * Enable the default implementation of avs_malloc(), avs_free(), avs_calloc()\n * and avs_realloc() that forwards to system malloc(), free(), calloc() and\n * realloc() calls.\n *\n * You might disable this option if for any reason you need to use a custom\n * allocator.\n */\n#define AVS_COMMONS_UTILS_WITH_STANDARD_ALLOCATOR\n\n/**\n * Enable the alternate implementation of avs_malloc(), avs_free(), avs_calloc()\n * and avs_realloc() that uses system malloc(), free() and realloc() calls, but\n * includes additional fixup code that ensures proper alignment to\n * <c>AVS_ALIGNOF(avs_max_align_t)</c> (usually 8 bytes on common platforms).\n *\n * <c>AVS_COMMONS_UTILS_WITH_STANDARD_ALLOCATOR</c> and\n * <c>AVS_COMMONS_UTILS_WITH_ALIGNFIX_ALLOCATOR</c> cannot be enabled at the\n * same time.\n *\n * NOTE: This implementation is only intended for platforms where the system\n * allocator does not properly conform to the alignment requirements.\n *\n * It comes with an additional runtime costs:\n *\n * - <c>AVS_ALIGNOF(avs_max_align_t)</c> bytes (usually 8) of additional\n *   overhead for each allocated memory block\n * - Additional memmove() for every realloc() that returned a block that is not\n *   properly aligned\n * - avs_calloc() is implemented as avs_malloc() followed by an explicit\n *   memset(); this may be suboptimal on some platforms\n *\n * If these costs are unacceptable for you, you may want to consider fixing,\n * replacing or reconfiguring your system allocator for conformance, or\n * implementing a custom one instead.\n *\n * Please note that some code in avs_commons and dependent projects (e.g. Anjay)\n * may include runtime assertions for proper memory alignment that will be\n * triggered when using a non-conformant standard allocator. Such allocators are\n * relatively common in embedded SDKs. This \"alignfix\" allocator is intended to\n * work around these issues. On some platforms (e.g. x86) those alignment issues\n * may not actually cause any problems - so you may want to consider disabling\n * runtime assertions instead. Please carefully examine your target platform's\n * alignment requirements and behavior of misaligned memory accesses (including\n * 64-bit data types such as <c>int64_t</c> and <c>double</c>) before doing so.\n */\n/* #undef AVS_COMMONS_UTILS_WITH_ALIGNFIX_ALLOCATOR */\n\n/**@}*/\n\n#endif /* AVS_COMMONS_CONFIG_H */\n"
  },
  {
    "path": "example_configs/embedded_lwm2m12/avsystem/commons/lwip-posix-compat.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef COMPAT_H\n#define COMPAT_H\n\n/*\n * Example implementation of a AVS_COMMONS_POSIX_COMPAT_HEADER file required for\n * non-POSIX platforms that use LwIP 1.4.1.\n *\n * Contains all types/macros/symbols not defined in core C that are required\n * to compile avs_commons library.\n */\n\n#include \"lwipopts.h\"\n\n/* Provides lwIP's alternative errno header, used in avs_net_impl.c */\n#include \"lwip/errno.h\"\n\n/* Provides htons/ntohs/htonl/ntohl */\n#include \"lwip/inet.h\"\n\n/* Provides e.g. LWIP_VERSION_* macros used in net_impl.c */\n#include \"lwip/init.h\"\n\n/*\n * Provides:\n * - POSIX-compatible socket API, socklen_t,\n * - fcntl, F_GETFL, F_SETFL, O_NONBLOCK,\n * - select, struct fd_set, FD_SET, FD_CLEAR, FD_ISSET\n */\n#include \"lwip/sockets.h\"\n\n/* Provides getaddrinfo/freeaddrinfo/struct addrinfo */\n#include \"lwip/netdb.h\"\n\n#if LWIP_VERSION_MAJOR >= 2\n#    define AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_INET_NTOP\n#endif // LWIP_VERSION_MAJOR >= 2\n\ntypedef int sockfd_t;\n\n#endif /* COMPAT_H */\n"
  },
  {
    "path": "example_configs/linux_lwm2m10/anjay/anjay_config.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ANJAY_CONFIG_H\n#define ANJAY_CONFIG_H\n\n/**\n * @file anjay_config.h\n *\n * Anjay library configuration.\n *\n * The preferred way to compile Anjay is to use CMake, in which case this file\n * will be generated automatically by CMake.\n *\n * However, to provide compatibility with various build systems used especially\n * by embedded platforms, it is alternatively supported to compile Anjay by\n * other means, in which case this file will need to be provided manually.\n *\n * <strong>NOTE: To compile this library without using CMake, you need to\n * configure avs_commons and avs_coap first. Please refer to documentation in\n * the <c>avs_commons_config.h</c> and <c>avs_coap_config.h</c> files (provided\n * in the repositories as <c>avs_commons_config.h.in</c> and\n * <c>avs_coap_config.h.in</c>, respectively) for details.</strong>\n *\n * <strong>Anjay requires the following avs_coap options to be enabled:</strong>\n *\n * - @c WITH_AVS_COAP_UDP and/or @c WITH_AVS_COAP_TCP\n * - @c WITH_AVS_COAP_STREAMING_API\n * - @c WITH_AVS_COAP_BLOCK is highly recommended\n * - @c WITH_AVS_COAP_OBSERVE (if @c ANJAY_WITH_OBSERVE is enabled)\n * - @c WITH_AVS_COAP_OSCORE (if @c ANJAY_WITH_COAP_OSCORE is enabled, available\n *   only as a commercial feature)\n *\n * <strong>Anjay requires the following avs_commons components to be\n * enabled:</strong>\n *\n * - All components required by avs_coap, see <c>avs_coap_config.h</c>\n * - @c avs_algorithm\n * - @c avs_stream\n * - @c avs_url\n * - @c avs_persistence is highly recommended\n * - @c avs_http (if @c ANJAY_WITH_HTTP_DOWNLOAD is enabled)\n * - @c avs_rbtree or @c avs_sorted_set (if @c ANJAY_WITH_OBSERVE or\n *   @c ANJAY_WITH_MODULE_ACCESS_CONTROL is enabled)\n *\n * In the repository, this file is provided as <c>anjay_config.h.in</c>,\n * intended to be processed by CMake. If editing this file manually, please copy\n * or rename it to <c>anjay_config.h</c> and for each of the\n * <c>\\#cmakedefine</c> directives, please either replace it with regular\n * <c>\\#define</c> to enable it, or comment it out to disable. You may also need\n * to replace variables wrapped in <c>\\@</c> signs with concrete values. Please\n * refer to the comments above each of the specific definition for details.\n *\n * If you are editing a file previously generated by CMake, these\n * <c>\\#cmakedefine</c>s will be already replaced by either <c>\\#define</c> or\n * commented out <c>\\#undef</c> directives.\n */\n\n/**\n * Enable logging in Anjay.\n *\n * If this flag is disabled, no logging is compiled into the binary at all.\n */\n#define ANJAY_WITH_LOGS\n\n/**\n * Enable TRACE-level logs in Anjay.\n *\n * Only meaningful if <c>ANJAY_WITH_LOGS</c> is enabled.\n */\n#define ANJAY_WITH_TRACE_LOGS\n\n/**\n * Enable core support for Access Control mechanisms.\n *\n * Requires separate implementation of the Access Control object itself.\n * Either the implementation available as part of\n * <c>ANJAY_WITH_MODULE_ACCESS_CONTROL</c>, or a custom application-provided one\n * may be used.\n */\n#define ANJAY_WITH_ACCESS_CONTROL\n\n/**\n * Enable automatic attribute storage.\n *\n * Requires <c>AVS_COMMONS_WITH_AVS_PERSISTENCE</c> to be enabled in avs_commons\n * configuration.\n */\n#define ANJAY_WITH_ATTR_STORAGE\n\n/**\n * Enable support for the <c>anjay_download()</c> API.\n */\n#define ANJAY_WITH_DOWNLOADER\n\n/**\n * Enable support for CoAP(S) downloads.\n *\n * Only meaningful if <c>ANJAY_WITH_DOWNLOADER</c> is enabled.\n */\n#define ANJAY_WITH_COAP_DOWNLOAD\n\n/**\n * Enable support for HTTP(S) downloads.\n *\n * Only meaningful if <c>ANJAY_WITH_DOWNLOADER</c> is enabled.\n */\n/* #undef ANJAY_WITH_HTTP_DOWNLOAD */\n\n/**\n * Enable support for the LwM2M Bootstrap Interface.\n */\n#define ANJAY_WITH_BOOTSTRAP\n\n/**\n * Enable support for the LwM2M Bootstrap-Pack operation.\n *\n * Requires <c>ANJAY_WITH_BOOTSTRAP</c> and <c>ANJAY_WITH_LWM2M12</c> to be\n * enabled.\n */\n/* #undef ANJAY_WITH_BOOTSTRAP_PACK */\n\n/**\n * Enable support for the LwM2M Discover operation.\n *\n * Note that it is required for full compliance with the LwM2M protocol.\n */\n#define ANJAY_WITH_DISCOVER\n\n/**\n * Enable support for the LwM2M Information Reporting interface (Observe and\n * Notify operations).\n *\n * Requires <c>WITH_AVS_COAP_OBSERVE</c> to be enabled in avs_coap\n * configuration.\n *\n * Note that it is required for full compliance with the LwM2M protocol.\n */\n#define ANJAY_WITH_OBSERVE\n\n/**\n * Enable support for measuring amount of LwM2M traffic\n * (<c>anjay_get_tx_bytes()</c>, <c>anjay_get_rx_bytes()</c>,\n * <c>anjay_get_num_incoming_retransmissions()</c> and\n * <c>anjay_get_num_outgoing_retransmissions()</c> APIs.\n */\n#define ANJAY_WITH_NET_STATS\n\n/**\n * Enable support for communication timestamp\n * (<c>anjay_get_server_last_registration_time()</c>\n * <c>anjay_get_server_next_update_time()</c> and\n * <c>anjay_get_server_last_communication_time()</c>) APIs.\n */\n#define ANJAY_WITH_COMMUNICATION_TIMESTAMP_API\n\n/**\n * Enable support for the <c>anjay_resource_observation_status()</c> API.\n */\n#define ANJAY_WITH_OBSERVATION_STATUS\n\n/**\n * Maximum number of servers observing a given Resource listed by\n * <c>anjay_resource_observation_status()</c> function.\n *\n * Only meaningful if <c>ANJAY_WITH_OBSERVATION_STATUS</c> is enabled.\n */\n#define ANJAY_MAX_OBSERVATION_SERVERS_REPORTED_NUMBER 0\n\n/**\n * Enable guarding of all accesses to <c>anjay_t</c> with a mutex.\n */\n#define ANJAY_WITH_THREAD_SAFETY\n\n/**\n * Enable standard implementation of an event loop.\n *\n * Requires C11 <c>stdatomic.h</c> header to be available, and either a platform\n * that provides a BSD-compatible socket API, or a compatibility layer file (see\n * <c>AVS_COMMONS_POSIX_COMPAT_HEADER</c> in <c>avs_commons_config.h</c>). This\n * is designed to best work with the defalt implementation of avs_net sockets\n * (see <c>AVS_COMMONS_NET_WITH_POSIX_AVS_SOCKET</c>), but alternative socket\n * implementations can also be used.\n *\n * The event loop is most useful when thread safety features\n * (@ref ANJAY_WITH_THREAD_SAFETY and <c>AVS_COMMONS_SCHED_THREAD_SAFE</c>) are\n * enabled as well, but this is not a hard requirement. See the documentation\n * for <c>anjay_event_loop_run()</c> for details.\n */\n#define ANJAY_WITH_EVENT_LOOP\n\n/**\n * Enable support for features new to LwM2M protocol version 1.1.\n */\n/* #undef ANJAY_WITH_LWM2M11 */\n\n/**\n * Enable support for features new to LwM2M protocol version 1.2.\n *\n * Requires <c>ANJAY_WITH_LWM2M11</c> to be enabled.\n */\n/* #undef ANJAY_WITH_LWM2M12 */\n\n/**\n * Enable support for OSCORE-based security for LwM2M connections.\n *\n * Requires <c>ANJAY_WITH_LWM2M11</c> to be enabled, and\n * <c>WITH_AVS_COAP_OSCORE</c> to be enabled in avs_coap configuration.\n *\n * IMPORTANT: Only available as part of the OSCORE commercial feature. Ignored\n * in the open source version.\n */\n/* #undef ANJAY_WITH_COAP_OSCORE */\n\n/**\n * Enable support for Observation Attributes.\n *\n * Requires <c>ANJAY_WITH_OBSERVE</c> and <c>ANJAY_WITH_LWM2M12</c> to be\n * enabled.\n */\n/* #undef ANJAY_WITH_OBSERVATION_ATTRIBUTES */\n\n/**\n * Enable support for the LwM2M Send operation.\n *\n * Requires <c>ANJAY_WITH_LWM2M11</c> and either <c>ANJAY_WITH_SENML_JSON</c> or\n * <c>ANJAY_WITH_CBOR</c> to be enabled.\n */\n/* #undef ANJAY_WITH_SEND */\n\n/**\n * Enable support for the SMS binding and the SMS trigger mechanism.\n *\n * Requires <c>WITH_AVS_COAP_UDP</c> to be enabled in avs_coap configuration.\n *\n * IMPORTANT: Only available as part of the SMS commercial feature. Ignored in\n * the open source version.\n */\n/* #undef ANJAY_WITH_SMS */\n\n/**\n * Enable support for sending and receiving multipart SMS messages.\n *\n * Requires <c>ANJAY_WITH_SMS</c> to be enabled.\n *\n * IMPORTANT: Only available as part of the SMS commercial feature. Ignored in\n * the open source version.\n */\n/* #undef ANJAY_WITH_SMS_MULTIPART */\n\n/**\n * Enable support for Non-IP Data Delivery.\n *\n * Requires <c>ANJAY_WITH_LWM2M11</c> to be enabled, and\n * <c>WITH_AVS_COAP_UDP</c> to be enabled in avs_coap configuration.\n *\n * IMPORTANT: Only available as a commercial feature. Ignored in the open\n * source version.\n */\n/* #undef ANJAY_WITH_NIDD */\n\n/**\n * Enable support for core state persistence\n * (<c>anjay_new_from_core_persistence()</c> and\n * <c>anjay_delete_with_core_persistence()</c> APIs).\n *\n * Requires <c>ANJAY_WITH_OBSERVE</c> to be enabled,\n * <c>AVS_COMMONS_WITH_AVS_PERSISTENCE</c> to be enabled in avs_commons, and\n * <c>WITH_AVS_COAP_OBSERVE_PERSISTENCE</c> to be enabled in avs_coap\n * configuration.\n *\n * IMPORTANT: Only available as a commercial feature. Ignored in the open\n * source version.\n */\n/* #undef ANJAY_WITH_CORE_PERSISTENCE */\n\n/**\n * Disable automatic closing of server connection sockets after\n * MAX_TRANSMIT_WAIT of inactivity.\n */\n/* #undef ANJAY_WITHOUT_QUEUE_MODE_AUTOCLOSE */\n\n/**\n * Enable support for CoAP Content-Format numerical values 1541-1544 that have\n * been used before final LwM2M TS 1.0.\n */\n/* #undef ANJAY_WITH_LEGACY_CONTENT_FORMAT_SUPPORT */\n\n/**\n * Enable support for JSON format as specified in LwM2M TS 1.0.\n *\n * NOTE: Anjay is only capable of generating this format, there is no parsing\n * support regardless of the state of this option.\n */\n#define ANJAY_WITH_LWM2M_JSON\n\n/**\n * Disable support for TLV format as specified in LwM2M TS 1.0.\n *\n * NOTE: LwM2M Client using LwM2M 1.0 MUST support this format. It may be\n * disabled if LwM2M 1.1 is used and SenML JSON or SenML CBOR is enabled.\n */\n/* #undef ANJAY_WITHOUT_TLV */\n\n/**\n * Disable support for Plain Text format as specified in LwM2M TS 1.0 and 1.1.\n *\n * NOTE: LwM2M Client SHOULD support this format. It may be disabled to reduce\n * library size if LwM2M Server is configured to not use it in requests.\n */\n/* #undef ANJAY_WITHOUT_PLAINTEXT */\n\n/**\n * Disable use of the Deregister message.\n */\n/* #undef ANJAY_WITHOUT_DEREGISTER */\n\n/**\n * Disable support for \"IP stickiness\", i.e. preference of the same IP address\n * when reconnecting to a server using a domain name.\n */\n/* #undef ANJAY_WITHOUT_IP_STICKINESS */\n\n/**\n * Enable support for SenML JSON format, as specified in LwM2M TS 1.1.\n *\n * NOTE: As opposed to <c>ANJAY_WITH_LWM2M_JSON</c>, both generating and parsing\n * is supported.\n *\n * Requires <c>ANJAY_WITH_LWM2M11</c> to be enabled.\n */\n/* #undef ANJAY_WITH_SENML_JSON */\n\n/**\n * Enable support for CBOR and SenML CBOR formats, as specified in LwM2M TS 1.1.\n *\n * Requires <c>ANJAY_WITH_LWM2M11</c> to be enabled.\n */\n/* #undef ANJAY_WITH_CBOR */\n\n/**\n * Enable support for Enrollment over Secure Transport.\n *\n * Requires <c>ANJAY_WITH_BOOTSTRAP</c> to be enabled.\n *\n * IMPORTANT: Only available as part of the EST commercial feature. Ignored in\n * the open source version.\n */\n/* #undef ANJAY_WITH_EST */\n\n/**\n * Enable support for hardware security engine in the EST subsystem.\n *\n * Requires <c>ANJAY_WITH_EST</c> to be enabled in Anjay configuration and\n * <c>AVS_COMMONS_WITH_AVS_CRYPTO_PKI_ENGINE</c> to be enabled in avs_commons\n * configuration.\n *\n * IMPORTANT: Only available in commercial versions that include both the EST\n * and HSM features. Ignored in versions distributed without these features.\n */\n/* #undef ANJAY_WITH_EST_ENGINE_SUPPORT */\n\n/**\n * Enable support for the Confirmable Notification attribute, as specified in\n * LwM2M TS 1.2.\n *\n * Before TS 1.2, this has been supported in Anjay as a custom extension, and\n * thus it is available independently from TS 1.2 support itself, including in\n * the open source version.\n *\n * Requires <c>ANJAY_WITH_OBSERVE</c> to be enabled.\n */\n/* #undef ANJAY_WITH_CON_ATTR */\n\n/**\n * Enable support for handling security credentials in the data model using\n * structured <c>avs_crypto</c> types.\n *\n * If the <c>security</c> module is also enabled (see @ref\n * ANJAY_WITH_MODULE_SECURITY), it also enables support for passing these\n * credentials through such structured types when adding Security object\n * instances via the @ref anjay_security_instance_t structure.\n */\n#define ANJAY_WITH_SECURITY_STRUCTURED\n\n/**\n * Maximum size in bytes supported for the \"Public Key or Identity\" resource in\n * the LwM2M Security object.\n *\n * If editing this file manually, <c>2048</c> shall be\n * replaced with a positive integer literal.\n *\n * The default value defined in CMake build scripts is 2048.\n * Minimal suggested setting for low-resource builds is 256.\n */\n#define ANJAY_MAX_PK_OR_IDENTITY_SIZE 2048\n\n/**\n * Maximum size in bytes supported for the \"Secret Key\" resource in the LwM2M\n * Security Object.\n *\n * If editing this file manually, <c>256</c> shall be replaced\n * with a positive integer literal.\n *\n * The default value defined in CMake build scripts is 256.\n * Minimal suggested setting for low-resource builds is 128.\n */\n#define ANJAY_MAX_SECRET_KEY_SIZE 256\n\n/**\n * Maximum length supported for stringified floating-point values.\n *\n * Used when parsing plaintext and SenML JSON content formats - when parsing a\n * floating-point value, any string of length equal or greater than this setting\n * will automatically be considered invalid, even if it could in theory be\n * parsed as a valid number.\n *\n * If editing this file manually, <c>512</c> shall be\n * replaced with a positive integer literal.\n *\n * The default value defined in CMake build scripts is 512.\n * Minimal suggested setting for low-resource builds is 64.\n */\n#define ANJAY_MAX_DOUBLE_STRING_SIZE 512\n\n/**\n * Maximum length supported for a single Uri-Path or Location-Path segment.\n *\n * When handling incoming CoAP messages, any Uri-Path or Location-Path option of\n * length equal or greater than this setting will be considered invalid.\n *\n * If editing this file manually, <c>256</c> shall be\n * replaced with a positive integer literal.\n *\n * The default value defined in CMake build scripts is 256.\n * Minimal suggested setting for low-resource builds is 64.\n */\n#define ANJAY_MAX_URI_SEGMENT_SIZE 256\n\n/**\n * Maximum length supported for a single Uri-Query segment.\n *\n * When handling incoming CoAP messages, any Uri-Query option of length equal or\n * greater than this setting will be considered invalid.\n *\n * If editing this file manually, <c>256</c> shall be\n * replaced with a positive integer literal.\n *\n * The default value defined in CMake build scripts is 256.\n * Minimal suggested setting for low-resource builds is 64.\n */\n#define ANJAY_MAX_URI_QUERY_SEGMENT_SIZE 256\n\n/**\n * Size of buffer allocated for storing DTLS session state when connection is\n * not in use (e.g. during queue mode operation).\n *\n * If editing this file manually, <c>1024</c> shall be\n * replaced with a positive integer literal. The default value defined in CMake\n * build scripts is 1024.\n */\n#define ANJAY_DTLS_SESSION_BUFFER_SIZE 1024\n\n/**\n * Value of Content-Format used in Send messages. Only a few specific values are\n * supported:\n *\n * - @c AVS_COAP_FORMAT_NONE means no default value is used and Anjay will\n *   decide the format based on the what is available.\n * - @c AVS_COAP_FORMAT_OMA_LWM2M_CBOR Anjay will generate a Send message in\n *   LwM2M CBOR format.\n * - @c AVS_COAP_FORMAT_SENML_CBOR Anjay will generate a Send message in SenML\n *   CBOR format.\n * - @c AVS_COAP_FORMAT_SENML_JSON Anjay will generate a Send message in SenML\n *   JSON format.\n *\n * Note that to use a specific format it must be available during compilation.\n *\n * The default value defined in CMake build scripts is\n * <c>AVS_COAP_FORMAT_NONE</c>.\n */\n#define ANJAY_DEFAULT_SEND_FORMAT AVS_COAP_FORMAT_NONE\n\n/**\n * Optional Anjay modules.\n */\n/**@{*/\n/**\n * Enable access_control module (implementation of the Access Control object).\n *\n * Requires <c>ANJAY_WITH_ACCESS_CONTROL</c> to be enabled.\n */\n#define ANJAY_WITH_MODULE_ACCESS_CONTROL\n\n/**\n * Enable security module (implementation of the LwM2M Security object).\n */\n#define ANJAY_WITH_MODULE_SECURITY\n\n/**\n * Enable support for hardware security engine in the security module.\n *\n * This feature allows security credentials provisioned into the LwM2M Security\n * object to be automatically moved into a hardware security module.\n *\n * Requires <c>ANJAY_WITH_MODULE_SECURITY</c> to be enabled in Anjay\n * configuration, and at least one of\n * <c>AVS_COMMONS_WITH_AVS_CRYPTO_PKI_ENGINE</c> or\n * <c>AVS_COMMONS_WITH_AVS_CRYPTO_PSK_ENGINE</c> to be enabled in avs_commons\n * configuration.\n *\n * IMPORTANT: Only available as part of the HSM support commercial feature.\n * Ignored in versions distributed without this feature.\n */\n/* #undef ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT */\n\n/**\n * Enable server module (implementation of the LwM2M Server object).\n */\n#define ANJAY_WITH_MODULE_SERVER\n\n/**\n * Enable fw_update module (implementation of the Firmware Update object).\n */\n#define ANJAY_WITH_MODULE_FW_UPDATE\n\n/**\n * Enable advanced_fw_update module (implementation of the 33629 custom\n * Advanced Firmware Update object).\n */\n/* #undef ANJAY_WITH_MODULE_ADVANCED_FW_UPDATE */\n\n/**\n * Disable support for PUSH mode Firmware Update.\n *\n * Only meaningful if <c>ANJAY_WITH_MODULE_FW_UPDATE</c> is enabled. Requires\n * <c>ANJAY_WITH_DOWNLOADER</c> to be enabled.\n */\n/* #undef ANJAY_WITHOUT_MODULE_FW_UPDATE_PUSH_MODE */\n\n/**\n * Enable sw_mgmt module (implementation of the Software Management object).\n */\n/* #undef ANJAY_WITH_MODULE_SW_MGMT */\n\n/**\n * Enables ipso_objects module (generic implementation of basic sensor, three\n * axis sensor and Push Button IPSO objects).\n */\n#define ANJAY_WITH_MODULE_IPSO_OBJECTS\n\n/**\n * Enables experimental ipso_objects_v2 module (generic implementation of basic\n * sensor and three axis sensor IPSO objects).\n */\n#define ANJAY_WITH_MODULE_IPSO_OBJECTS_V2\n\n/**\n * Enable at_sms module (implementation of an SMS driver for AT modem devices).\n *\n * Requires <c>ANJAY_WITH_SMS</c> to be enabled and the operating system to\n * support the POSIX <c>poll()</c> function.\n *\n * IMPORTANT: Only available as part of the SMS commercial feature. Ignored in\n * the open source version.\n */\n/* #undef ANJAY_WITH_MODULE_AT_SMS */\n\n/**\n * Enable bg96_nidd module (implementation of NB-IoT-based NIDD driver for\n * Quectel BG96).\n *\n * Requires <c>ANJAY_WITH_NIDD</c> to be enabled.\n *\n * IMPORTANT: Only available as part of the NIDD commercial feature. Ignored\n * in the open source version.\n */\n/* #undef ANJAY_WITH_MODULE_BG96_NIDD */\n\n/**\n * Enable bootstrapper module (loader for bootstrap information formatted as per\n * the \"Storage of LwM2M Bootstrap Information on the Smartcard\" appendix to the\n * LwM2M TS).\n *\n * Requires <c>ANJAY_WITH_BOOTSTRAP</c> to be enabled and\n * <c>AVS_COMMONS_WITH_AVS_PERSISTENCE</c> to be enabled in avs_commons\n * configuration.\n *\n * IMPORTANT: Only available with the bootstrapper feature. Ignored in the open\n * source version.\n */\n/* #undef ANJAY_WITH_MODULE_BOOTSTRAPPER */\n\n/**\n * Enable the SIM bootstrap module, which enables reading the SIM bootstrap\n * information from a smartcard, which can then be passed through to the\n * bootstrapper module.\n *\n * Requires <c>ANJAY_WITH_MODULE_BOOTSTRAPPER</c> to be enabled.\n *\n * IMPORTANT: Only available with the bootstrapper feature. Ignored in the open\n * source version.\n */\n/* #undef ANJAY_WITH_MODULE_SIM_BOOTSTRAP */\n\n/**\n * Forced ID of the file to read the SIM bootstrap information from.\n *\n * If not defined (default), the bootstrap information file will be discovered\n * through the ODF file, as mandated by the specification.\n *\n * Requires <c>ANJAY_WITH_MODULE_BOOTSTRAPPER</c> to be enabled. At most one of\n * <c>ANJAY_MODULE_SIM_BOOTSTRAP_HARDCODED_FILE_ID</c> and\n * <c>ANJAY_MODULE_SIM_BOOTSTRAP_DATA_OID_OVERRIDE_HEX</c> may be defined at the\n * same time.\n *\n * IMPORTANT: Only available with the bootstrapper feature. Ignored in the open\n * source version.\n */\n/* #undef ANJAY_MODULE_SIM_BOOTSTRAP_HARDCODED_FILE_ID */\n\n/**\n * Overridden OID of the SIM bootstrap information to look for in the DODF file,\n * expressed as a hexlified DER representation (without the header).\n *\n * This is the hexlified expected value of the 'id' field within the 'OidDO'\n * sequence in the DODF file (please refer to the PKCS #15 document for more\n * information).\n *\n * If not defined, the default value of <c>\"672b0901\"</c>, which corresponds to\n * OID 2.23.43.9.1 {joint-iso-itu-t(2) international-organizations(23) wap(43)\n * oma-lwm2m(9) lwm2m-bootstrap(1)}, will be used.\n *\n * No other values than the default are valid according to the specification,\n * but some SIM cards are known to use other non-standard values, e.g.\n * <c>\"0604672b0901\"</c> - including a superfluous nested BER-TLV header, as\n * erroneously illustrated in the EF(DODF-bootstrap) file coding example in\n * LwM2M TS 1.2 and earlier (fixed in LwM2M TS 1.2.1) - which is interpreted as\n * OID 0.6.4.103.43.9.1 (note that it is invalid as the 0.6 tree does not exist\n * in the repository as of writing this note).\n *\n * Requires <c>ANJAY_WITH_MODULE_BOOTSTRAPPER</c> to be enabled. At most one of\n * <c>ANJAY_MODULE_SIM_BOOTSTRAP_HARDCODED_FILE_ID</c> and\n * <c>ANJAY_MODULE_SIM_BOOTSTRAP_DATA_OID_OVERRIDE_HEX</c> may be defined at the\n * same time.\n *\n * IMPORTANT: Only available with the bootstrapper feature. Ignored in the open\n * source version.\n */\n/* #undef ANJAY_MODULE_SIM_BOOTSTRAP_DATA_OID_OVERRIDE_HEX */\n\n/**\n * Enable factory provisioning module. Data provided during provisioning uses\n * SenML CBOR format.\n */\n/* #undef ANJAY_WITH_MODULE_FACTORY_PROVISIONING */\n\n/**\n * Enable oscore module (implementation of the OSCORE object).\n *\n * IMPORTANT: Only available as part of the OSCORE commercial feature. Ignored\n * in the open source version.\n */\n/* #undef ANJAY_WITH_MODULE_OSCORE */\n\n/**\n * If enable Anjay doesn't handle composite operation (read, observe and write).\n * Its use makes sense for LWM2M v1.1 upwards.\n *\n * This flag can be used to reduce the size of the resulting code.\n *\n * If active, anjay will respond with message code 5.01 Not Implemented to any\n * composite type request.\n */\n/* #undef ANJAY_WITHOUT_COMPOSITE_OPERATIONS */\n\n/**\n * Enable support for the experimental\n * <c>anjay_get_server_connection_status()</c> API and related\n * <c>anjay_server_connection_status_cb_t</c> callback.\n */\n#define ANJAY_WITH_CONN_STATUS_API\n\n/**\n * Enable support for the experimental\n * <c>anjay_ssl_error_cb_t</c> callback.\n * Currently only supported for mbedTLS and custom SSL integration builds\n */\n#define ANJAY_WITH_SSL_ERROR_API\n\n/**\n * Enable support for /25 LwM2M Gateway Object.\n *\n * Requires <c>ANJAY_WITH_LWM2M11</c> to be enabled.\n * Requires <c>ANJAY_WITH_CORE_PERSISTENCE</c> (commercial feature) to be\n * disabled.\n */\n/* #undef ANJAY_WITH_LWM2M_GATEWAY */\n\n/**\n * Maximum holdoff time in seconds.\n *\n * This parameter defines an upper limit on the Hold Off Time as specified in\n * the LwM2M Security Object (Object ID: 0, Resource ID: 11). The Hold Off Time\n * is used by the LwM2M Client to delay initiating a Bootstrap process.\n *\n * Limiting the Hold Off Time is important in network environments where\n * prolonged delays may cause NAT bindings to expire, potentially leading to\n * failed connection attempts. This parameter ensures that the configured\n * Hold Off Time does not exceed a safe threshold. If a DTLS binding with\n * Connection ID is used this parameter can be increased safely.\n *\n * If editing this file manually, <c>20</c> shall be\n * replaced with a positive integer literal representing the time in seconds.\n * The default value defined in CMake build scripts is 20.\n */\n#define ANJAY_MAX_HOLDOFF_TIME 20\n\n/**\n * Enable support for optional resources of the Firmware Update object\n * introduced in LwM2M 1.1.\n *\n * This includes the following resources:\n * - Severity\n * - Last State Change Time\n * - Max Defer Period\n *\n * Requires <c>ANJAY_WITH_LWM2M11</c> to be enabled.\n */\n/* #undef ANJAY_WITH_MODULE_FW_UPDATE_V11_RESOURCES */\n\n/**@}*/\n\n#endif // ANJAY_CONFIG_H\n"
  },
  {
    "path": "example_configs/linux_lwm2m10/avsystem/coap/avs_coap_config.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef AVS_COAP_CONFIG_H\n#define AVS_COAP_CONFIG_H\n\n/**\n * @file avs_coap_config.h\n *\n * avs_coap library configuration.\n *\n * The preferred way to compile avs_coap is to use CMake, in which case this\n * file will be generated automatically by CMake.\n *\n * However, to provide compatibility with various build systems used especially\n * by embedded platforms, it is alternatively supported to compile avs_coap by\n * other means, in which case this file will need to be provided manually.\n *\n * <strong>NOTE: To compile this library without using CMake, you need to\n * configure avs_commons first. Please refer to documentation in the\n * <c>avs_commons_config.h</c> file (provided in the repository as\n * <c>avs_commons_config.h.in</c>) for details.</strong>\n *\n * <strong>avs_coap requires the following avs_commons components to be\n * enabled:</strong>\n *\n * - @c avs_buffer\n * - @c avs_compat_threading\n * - @c avs_list\n * - @c avs_net\n * - @c avs_sched\n * - @c avs_stream\n * - @c avs_utils\n * - @c avs_log (if @c WITH_AVS_COAP_LOGS is enabled)\n * - @c avs_persistence (if @c WITH_AVS_COAP_OBSERVE_PERSISTENCE is enabled)\n * - @c avs_crypto (if @c WITH_AVS_COAP_OSCORE is enabled)\n *\n * In the repository, this file is provided as <c>avs_coap_config.h.in</c>,\n * intended to be processed by CMake. If editing this file manually, please copy\n * or rename it to <c>avs_coap_config.h</c> and for each of the\n * <c>\\#cmakedefine</c> directives, please either replace it with regular\n * <c>\\#define</c> to enable it, or comment it out to disable. You may also need\n * to replace variables wrapped in <c>\\@</c> signs with concrete values. Please\n * refer to the comments above each of the specific definition for details.\n *\n * If you are editing a file previously generated by CMake, these\n * <c>\\#cmakedefine</c>s will be already replaced by either <c>\\#define</c> or\n * commented out <c>\\#undef</c> directives.\n */\n\n/**\n * Enable support for block-wise transfers (RFC 7959).\n *\n * If this flag is disabled, attempting to send a message bigger than the\n * internal buffer will fail; incoming messages may still carry BLOCK1 or BLOCK2\n * options, but they will not be interpreted by the library in any way.\n */\n#define WITH_AVS_COAP_BLOCK\n\n/**\n * Enable support for observations (RFC 7641).\n */\n#define WITH_AVS_COAP_OBSERVE\n\n/**\n * Turn on cancelling observation on a timeout.\n *\n * Only meaningful if <c>WITH_AVS_COAP_OBSERVE</c> is enabled.\n *\n * NOTE: LwM2M specification requires LwM2M server to send Cancel Observation\n * request. Meanwhile CoAP RFC 7641 states that timeout on notification should\n * cancel it. This setting is to enable both of these behaviors with default\n * focused on keeping observations in case of bad connectivity.\n */\n/* #undef WITH_AVS_COAP_OBSERVE_CANCEL_ON_TIMEOUT */\n\n/**\n * Force cancelling observation, even if a confirmable notification yielding\n * an error response is not acknowledged or rejected with RST by the observer.\n *\n * This is a circumvention for some non-compliant servers that respond with an\n * RST message to a confirmable notification yielding an error response. This\n * setting makes the library cancel the observation in such cases, even though\n * the notification is formally rejected. Additionally, it will also make the\n * library cancel the observation if no response is received at all.\n */\n#define WITH_AVS_COAP_OBSERVE_FORCE_CANCEL_ON_UNACKED_ERROR\n\n/**\n * Enable support for observation persistence (<c>avs_coap_observe_persist()</c>\n * and <c>avs_coap_observe_restore()</c> calls).\n *\n * Only meaningful if <c>WITH_AVS_COAP_OBSERVE</c> is enabled.\n */\n/* #undef WITH_AVS_COAP_OBSERVE_PERSISTENCE */\n\n/**\n * Enable support for the streaming API\n */\n#define WITH_AVS_COAP_STREAMING_API\n\n/**\n * Enable support for UDP transport.\n *\n * NOTE: Enabling at least one transport is necessary for the library to be\n * useful.\n */\n#define WITH_AVS_COAP_UDP\n\n/**\n * Enable support for TCP transport (RFC 8323).\n *\n * NOTE: Enabling at least one transport is necessary for the library to be\n * useful.\n */\n/* #undef WITH_AVS_COAP_TCP */\n\n/**\n * Enable support for OSCORE (RFC 8613).\n *\n * IMPORTANT: Only available with the OSCORE feature. Ignored in the open\n * source version.\n */\n/* #undef WITH_AVS_COAP_OSCORE */\n\n/**\n * Use OSCORE version from draft-ietf-core-object-security-08 instead of the\n * final version (RFC 8613).\n *\n * Only meaningful if <c>WITH_AVS_COAP_OSCORE</c> is enabled.\n *\n * IMPORTANT: Only available with the OSCORE feature. Ignored in the open\n * source version.\n */\n/* #undef WITH_AVS_COAP_OSCORE_DRAFT_8 */\n\n/**\n * Maximum size in bytes supported for the Master Secret.\n *\n * If editing this file manually, set it to a positive integer literal.\n *\n * The default value defined in CMake build scripts is 32.\n *\n * IMPORTANT: Only available with the OSCORE feature. Ignored in the open\n * source version.\n */\n/* #undef AVS_COAP_OSCORE_MASTER_SECRET_SIZE */\n\n/**\n * Maximum size in bytes supported for the Master Salt.\n *\n * If editing this file manually, set it to a positive integer literal.\n *\n * The default value defined in CMake build scripts is 16.\n *\n * IMPORTANT: Only available with the OSCORE feature. Ignored in the open\n * source version.\n */\n/* #undef AVS_COAP_OSCORE_MASTER_SALT_SIZE */\n\n/**\n * Maximum number of notification tokens stored to match Reset responses to.\n *\n * Only meaningful if <c>WITH_AVS_COAP_OBSERVE</c> and <c>WITH_AVS_COAP_UDP</c>\n * are enabled.\n *\n * If editing this file manually, <c>4</c> shall be\n * replaced with a positive integer literal. The default value defined in CMake\n * build scripts is 4.\n */\n#define AVS_COAP_UDP_NOTIFY_CACHE_SIZE 4\n\n/**\n * Enable sending diagnostic payload in error responses.\n */\n#define WITH_AVS_COAP_DIAGNOSTIC_MESSAGES\n\n/**\n * Enable logging in avs_coap.\n *\n * If this flag is disabled, no logging is compiled into the binary at all.\n */\n#define WITH_AVS_COAP_LOGS\n\n/**\n * Enable TRACE-level logs in avs_coap.\n *\n * Only meaningful if <c>WITH_AVS_COAP_LOGS</c> is enabled.\n */\n#define WITH_AVS_COAP_TRACE_LOGS\n\n/**\n * Enable poisoning of unwanted symbols when compiling avs_coap.\n *\n * Requires a compiler that supports <c>\\#pragma GCC poison</c>.\n *\n * This is mostly useful during development, to ensure that avs_commons do not\n * attempt to call functions considered harmful in this library, such as printf.\n * This is not guaranteed to work as intended on every platform.\n */\n/* #undef WITH_AVS_COAP_POISONING */\n\n#endif // AVS_COAP_CONFIG_H\n"
  },
  {
    "path": "example_configs/linux_lwm2m10/avsystem/commons/avs_commons_config.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AVS_COMMONS_CONFIG_H\n#define AVS_COMMONS_CONFIG_H\n\n/**\n * @file avs_commons_config.h\n *\n * avs_commons library configuration.\n *\n * The preferred way to compile avs_commons is to use CMake, in which case this\n * file will be generated automatically by CMake.\n *\n * However, to provide compatibility with various build systems used especially\n * by embedded platforms, it is alternatively supported to compile avs_commons\n * by other means, in which case this file will need to be provided manually.\n *\n * In the repository, this file is provided as <c>avs_commons_config.h.in</c>,\n * intended to be processed by CMake. If editing this file manually, please copy\n * or rename it to <c>avs_commons_config.h</c> and for each of the\n * <c>#cmakedefine</c> directives, please either replace it with regular\n * <c>#define</c> to enable it, or comment it out to disable. You may also need\n * to replace variables wrapped in <c>@</c> signs with concrete values. Please\n * refer to the comments above each of the specific definition for details.\n *\n * If you are editing a file previously generated by CMake, these\n * <c>#cmakedefine</c>s will be already replaced by either <c>#define</c> or\n * commented out <c>#undef</c> directives.\n */\n\n/**\n * Options that describe capabilities of the build environment.\n *\n * NOTE: If you leave some of these macros undefined, even though the given\n * feature is actually available in the system, avs_commons will attempt to use\n * its own substitutes, which may be incompatible with the definition in the\n * system and lead to undefined behaviour.\n */\n/**@{*/\n/**\n * Is the target platform big-endian?\n *\n * If undefined, little-endian is assumed. Mixed-endian architectures are not\n * supported.\n *\n * Affects <c>avs_convert_be*()</c> and <c>avs_[hn]to[hn]*()</c> calls in\n * avs_utils and, by extension, avs_persistence.\n */\n/* #undef AVS_COMMONS_BIG_ENDIAN */\n\n/**\n * Is GNU __builtin_add_overflow() extension available?\n *\n * Affects time handling functions in avs_utils. If disabled, software overflow\n * checking will be compiled. Note that this software overflow checking code\n * relies on U2 representation of signed integers.\n */\n#define AVS_COMMONS_HAVE_BUILTIN_ADD_OVERFLOW\n\n/**\n * Is GNU __builtin_mul_overflow() extension available?\n *\n * Affects time handling functions in avs_utils. If disabled, software overflow\n * checking will be compiled. Note that this software overflow checking code\n * relies on U2 representation of signed integers.\n */\n#define AVS_COMMONS_HAVE_BUILTIN_MUL_OVERFLOW\n\n/**\n * Is net/if.h available in the system?\n *\n * NOTE: If the header is indeed available, but this option is not defined, the\n * <c>IF_NAMESIZE</c> macro will be defined <strong>publicly by avs_commons\n * headers</strong>, which may conflict with system definitions.\n */\n#define AVS_COMMONS_HAVE_NET_IF_H\n\n/**\n * Are GNU diagnostic pragmas (#pragma GCC diagnostic push/pop/ignored)\n * available?\n *\n * If defined, those pragmas will be used to suppress compiler warnings for some\n * code known to generate them and cannot be improved in a more robust way, e.g.\n * for code that is known to generate warnings from within system headers.\n */\n#define AVS_COMMONS_HAVE_PRAGMA_DIAGNOSTIC\n\n/**\n * Are GNU visibility pragmas (#pragma GCC visibility push/pop) available?\n *\n * Meaningful mostly if avs_commons will be directly or indirectly linked into\n * a shared library. Causes all symbols except those declared in public headers\n * to be hidden, i.e. not exported outside the shared library. If not defined,\n * default compiler visibility settings will be used, but you still may use\n * compiler flags and linker version scripts to replicate this manually if\n * needed.\n */\n#define AVS_COMMONS_HAVE_VISIBILITY\n\n/**\n * Specify an optional compatibility header that allows use of POSIX-specific\n * code that is not compliant with POSIX enough to be compiled directly.\n *\n * This header, if specified, will be included only by the following components,\n * which may be enabled or disabled depending on state of the referenced flags:\n * - avs_compat_threading implementation based on POSIX Threads\n *   (@ref AVS_COMMONS_COMPAT_THREADING_WITH_PTHREAD)\n * - default avs_net socket implementation\n *   (@ref AVS_COMMONS_NET_WITH_POSIX_AVS_SOCKET)\n * - avs_unit (@ref AVS_COMMONS_WITH_AVS_UNIT)\n * - default implementation of avs_time routines\n *   (@ref AVS_COMMONS_UTILS_WITH_POSIX_AVS_TIME)\n *\n * Compatibility headers for lwIP and Microsoft Windows are provided with the\n * library (see the <c>compat</c> directory).\n *\n * If this macro is not defined, the afore-mentioned components, if enabled,\n * will use system headers directly, assuming they are POSIX-compliant.\n *\n * If this macro is enabled, the specified file will be included through an\n * <c>#include AVS_COMMONS_POSIX_COMPAT_HEADER</c> statement. Thus, if editing\n * this file manually, <c></c> shall be\n * replaced with a path to such file.\n */\n/* #undef AVS_COMMONS_POSIX_COMPAT_HEADER */\n\n/**\n * Set if printf implementation doesn't support 64-bit format specifiers.\n * If defined, custom implementation of conversion is used in\n * @c AVS_UINT64_AS_STRING instead of using @c snprintf .\n */\n/* #undef AVS_COMMONS_WITHOUT_64BIT_FORMAT_SPECIFIERS */\n\n/**\n * Set if printf implementation doesn't support floating-point numbers.\n * If defined, custom implementation of conversion is used in\n * @c AVS_DOUBLE_AS_STRING instead of using @c snprintf . This might increase\n * compatibility with some embedded libc implementations that do not provide\n * this functionality.\n *\n * NOTE: In order to keep the custom implementation small in code size, it is\n * not intended to be 100% accurate. Rounding errors may occur - according to\n * empirical checks, they show up around the 16th significant decimal digit.\n */\n/* #undef AVS_COMMONS_WITHOUT_FLOAT_FORMAT_SPECIFIERS */\n/**@}*/\n\n/**\n * Enable poisoning of unwanted symbols when compiling avs_commons.\n *\n * Requires a compiler that supports #pragma GCC poison.\n *\n * This is mostly useful during development, to ensure that avs_commons do not\n * attempt to call functions considered harmful in this library, such as printf.\n * This is not guaranteed to work as intended on every platform, e.g. on macOS\n * it is known to generate false positives due to different dependencies between\n * system headers.\n */\n/* #undef AVS_COMMONS_WITH_POISONING */\n\n/**\n * Options that control compilation of avs_commons components.\n *\n * Each of the configuration options below enables, if defined, one of the core\n * components of the avs_commons library.\n *\n * NOTE: Enabling avs_unit will cause an object file with an implementation of\n * main() to be generated.\n */\n/**@{*/\n#define AVS_COMMONS_WITH_AVS_ALGORITHM\n#define AVS_COMMONS_WITH_AVS_BUFFER\n#define AVS_COMMONS_WITH_AVS_COMPAT_THREADING\n#define AVS_COMMONS_WITH_AVS_CRYPTO\n/* #undef AVS_COMMONS_WITH_AVS_HTTP */\n#define AVS_COMMONS_WITH_AVS_LIST\n#define AVS_COMMONS_WITH_AVS_LOG\n#define AVS_COMMONS_WITH_AVS_NET\n#define AVS_COMMONS_WITH_AVS_PERSISTENCE\n#define AVS_COMMONS_WITH_AVS_RBTREE\n/* #undef AVS_COMMONS_WITH_AVS_SORTED_SET */\n#define AVS_COMMONS_WITH_AVS_SCHED\n#define AVS_COMMONS_WITH_AVS_STREAM\n/* #undef AVS_COMMONS_WITH_AVS_UNIT */\n#define AVS_COMMONS_WITH_AVS_URL\n#define AVS_COMMONS_WITH_AVS_UTILS\n/* #undef AVS_COMMONS_WITH_AVS_VECTOR */\n/**@}*/\n\n/**\n * Options that control compilation of avs_compat_threading implementations.\n *\n * If CMake is not used, in the typical scenario at most one of the following\n * implementations may be enabled at the same time. If none is enabled, the\n * relevant symbols will need to be provided by the user, if used.\n *\n * These are meaningful only if <c>AVS_COMMONS_WITH_AVS_COMPAT_THREADING</c> is\n * defined.\n */\n/**@{*/\n/**\n * Enable implementation based on spinlocks.\n *\n * This implementation is usually very inefficient, and requires C11 stdatomic.h\n * header to be available.\n */\n/* #undef AVS_COMMONS_COMPAT_THREADING_WITH_ATOMIC_SPINLOCK */\n\n/**\n * Enable implementation based on the POSIX Threads library.\n *\n * This implementation is preferred over the spinlock-based one, but the POSIX\n * Threads library is normally available only in UNIX-like environments.\n */\n#define AVS_COMMONS_COMPAT_THREADING_WITH_PTHREAD\n\n/**\n * Is the <c>pthread_condattr_setclock()</c> function available?\n *\n * This flag only makes sense when\n * <c>AVS_COMMONS_COMPAT_THREADING_WITH_PTHREAD</c> is enabled.\n *\n * If this flag is disabled, or if <c>CLOCK_MONOTONIC</c> macro is not\n * available, the <c>avs_condvar_wait()</c> will internally use the real-time\n * clock instead of the monotonic clock. Time values will be converted so that\n * this change does not affect API usage.\n */\n#define AVS_COMMONS_COMPAT_THREADING_PTHREAD_HAVE_PTHREAD_CONDATTR_SETCLOCK\n/**@}*/\n\n/**\n * Options that control compilation of code depending on TLS backend library.\n *\n * If CMake is not used, in the typical scenario at most one of the following\n * DTLS backends may be enabled at the same time. If none is enabled,\n * functionalities that depends on cryptography will be disabled.\n *\n * Affects avs_crypto, avs_net, and avs_stream (for the MD5 implementation).\n *\n * mbed TLS is the main development backend, and is preferred as such. OpenSSL\n * backend supports most functionality as well, but is not as thoroughly tested.\n * TinyDTLS support is only rudimentary.\n */\n/**@{*/\n#define AVS_COMMONS_WITH_MBEDTLS\n/* #undef AVS_COMMONS_WITH_OPENSSL */\n/* #undef AVS_COMMONS_WITH_TINYDTLS */\n\n/**\n * Enable support for custom TLS socket implementation.\n *\n * If enabled, the user needs to provide their own implementations of\n * <c>_avs_net_create_ssl_socket()</c>, <c>_avs_net_create_dtls_socket()</c>,\n * <c>_avs_net_initialize_global_ssl_state() and\n * <c>_avs_net_cleanup_global_ssl_state()</c>.\n */\n/* #undef AVS_COMMONS_WITH_CUSTOM_TLS */\n/**@}*/\n\n/**\n * Options related to avs_crypto.\n */\n/**@{*/\n/**\n * Enable AEAD and HKDF support in avs_crypto. Requires MbedTLS in version at\n * least 2.14.0, OpenSSL in version at least 1.1.0, or custom implementation in\n * case of <c>AVS_COMMONS_WITH_CUSTOM_TLS</c>.\n */\n/* #undef AVS_COMMONS_WITH_AVS_CRYPTO_ADVANCED_FEATURES */\n\n/**\n * If the TLS backend is either mbed TLS or OpenSSL, enables APIs related to\n * public-key cryptography.\n *\n * Public-key cryptography is not currently supported with TinyDTLS.\n *\n * It also enables support for X.509 certificates in avs_net, if that module is\n * also enabled.\n */\n#define AVS_COMMONS_WITH_AVS_CRYPTO_PKI\n\n/**\n * If the TLS backend is either mbed TLS, OpenSSL or TinyDTLS, enables support\n * of pre-shared key security.\n *\n * PSK is the only supported security mode for the TinyDTLS backend, so this\n * option MUST be enabled to utilize it.\n *\n * It also enables support for pre-shared key security in avs_net, if that\n * module is also enabled.\n */\n#define AVS_COMMONS_WITH_AVS_CRYPTO_PSK\n\n/**\n * Enables usage of Valgrind API to suppress some of the false positives\n * generated by the OpenSSL backend.\n */\n/* #undef AVS_COMMONS_WITH_AVS_CRYPTO_VALGRIND */\n\n/**\n * Enables high-level support for hardware-based PKI security, i.e. loading,\n * generating and managing key pairs and certificates via external engines.\n *\n * Requires @ref AVS_COMMONS_WITH_AVS_CRYPTO_PKI to be enabled.\n *\n * An actual implementation is required to use this feature. You may provide\n * your own, or use one of the default ones that come with the HSM engine\n * commercial feature (see @ref AVS_COMMONS_WITH_MBEDTLS_PKCS11_ENGINE,\n * @ref AVS_COMMONS_WITH_MBEDTLS_PSA_ENGINE and\n * @ref AVS_COMMONS_WITH_OPENSSL_PKCS11_ENGINE).\n *\n * The functions that need to be provided in case of a custom implementation:\n * - <c>avs_crypto_pki_engine_certificate_rm()</c>\n * - <c>avs_crypto_pki_engine_certificate_store()</c>\n * - <c>avs_crypto_pki_engine_key_gen()</c>\n * - <c>avs_crypto_pki_engine_key_rm()</c>\n * - <c>avs_crypto_pki_engine_key_store()</c>\n * - When targeting the Mbed TLS backend:\n *   - <c>_avs_crypto_mbedtls_engine_initialize_global_state()</c>\n *   - <c>_avs_crypto_mbedtls_engine_cleanup_global_state()</c>\n *   - <c>_avs_crypto_mbedtls_engine_append_cert()</c>\n *   - <c>_avs_crypto_mbedtls_engine_append_crl()</c>\n *   - <c>_avs_crypto_mbedtls_engine_load_private_key()</c>\n * - When targeting the OpenSSL backend:\n *   - <c>_avs_crypto_openssl_engine_initialize_global_state()</c>\n *   - <c>_avs_crypto_openssl_engine_cleanup_global_state()</c>\n *   - <c>_avs_crypto_openssl_engine_load_certs()</c>\n *   - <c>_avs_crypto_openssl_engine_load_crls()</c>\n *   - <c>_avs_crypto_openssl_engine_load_private_key()</c>\n *\n * External PKI engines are NOT supported in the TinyDTLS backend.\n */\n/* #undef AVS_COMMONS_WITH_AVS_CRYPTO_PKI_ENGINE */\n\n/**\n * Enables high-level support for hardware-based PSK security, i.e. loading\n * and managing PSK keys and identities via external engine.\n *\n * Requires @ref AVS_COMMONS_WITH_AVS_CRYPTO_PKI to be enabled.\n *\n * An actual implementation is required to use this feature. You may provide\n * your own, or use the default PSA-based one that comes with the HSM engine\n * commercial feature (see @ref AVS_COMMONS_WITH_MBEDTLS_PSA_ENGINE).\n *\n * The functions that need to be provided in case of a custom implementation:\n * - <c>avs_crypto_psk_engine_identity_store()</c>\n * - <c>avs_crypto_psk_engine_identity_rm()</c>\n * - <c>avs_crypto_psk_engine_key_store()</c>\n * - <c>avs_crypto_psk_engine_key_rm()</c>\n * - When targeting the Mbed TLS backend:\n *   - <c>_avs_crypto_mbedtls_engine_initialize_global_state()</c>\n *   - <c>_avs_crypto_mbedtls_engine_cleanup_global_state()</c>\n *   - <c>_avs_crypto_mbedtls_engine_load_psk_key()</c>\n *\n * External PSK engines are NOT supported in the OpenSSL and TinyDTLS backend.\n */\n/* #undef AVS_COMMONS_WITH_AVS_CRYPTO_PSK_ENGINE */\n\n/**\n * Enables the default implementation of avs_crypto engine, based on Mbed TLS\n * and PKCS#11.\n *\n * Requires @ref AVS_COMMONS_WITH_AVS_CRYPTO_PKI_ENGINE to be enabled.\n *\n * NOTE: Query string format for this engine is a subset of the PKCS#11 URI\n * scheme (see RFC 7512), modelled after the format accepted by libp11 OpenSSL\n * engine.\n *\n * NOTE: The unit tests for this feature depend on SoftHSM and pkcs11-tool.\n * These must be installed for the tests to pass.\n *\n * IMPORTANT: Only available as part of the HSM support commercial feature.\n * Ignored in the open source version.\n */\n/* #undef AVS_COMMONS_WITH_MBEDTLS_PKCS11_ENGINE */\n\n/**\n * Enables the default implementation of avs_crypto engine, based on Mbed TLS\n * and Platform Security Architecture (PSA).\n *\n * Requires @ref AVS_COMMONS_WITH_AVS_CRYPTO_PKI_ENGINE or\n * @ref AVS_COMMONS_WITH_AVS_CRYPTO_PSK_ENGINE to be enabled.\n *\n * NOTE: Query string format for this engine is:\n *\n * <pre>\n * kid=<key_ID>[,lifetime=<lifetime>]|uid=<persistent_storage_UID>\n * </pre>\n *\n * The values are parsed using strtoull() with base=0, so may be in decimal,\n * 0-prefixed octal or 0x-prefixed hexadecimal. On key generation and\n * certificate storage, the specified lifetime will be used, or lifetime 1\n * (default persistent storage) will be used if not. On key or certificate use,\n * the lifetime of the actual key will be verified if present on the query\n * string and the key will be rejected if different.\n *\n * Certificates are stored as PSA_KEY_TYPE_RAW_DATA key entries containing\n * X.509 DER data. Alternatively, the PSA Protected Storage API can be used if\n * @ref AVS_COMMONS_WITH_MBEDTLS_PSA_ENGINE_PROTECTED_STORAGE is enabled, by\n * using the <c>uid=...</c> syntax.\n *\n * IMPORTANT: Only available as part of the HSM support commercial feature.\n * Ignored in the open source version.\n */\n/* #undef AVS_COMMONS_WITH_MBEDTLS_PSA_ENGINE */\n\n/**\n * Enables support for the PSA Protected Storage API in the PSA-based avs_crypto\n * engine.\n *\n * Requires @ref AVS_COMMONS_WITH_MBEDTLS_PSA_ENGINE to be enabled.\n *\n * IMPORTANT: Only available as part of the HSM support commercial feature.\n * Ignored in the open source version.\n */\n/* #undef AVS_COMMONS_WITH_MBEDTLS_PSA_ENGINE_PROTECTED_STORAGE */\n\n/**\n * Enables use of the <c>psa_generate_random()</c> function as the default\n * random number generator when using the Mbed TLS crypto backend, instead of\n * CTR-DRBG seeded by the Mbed TLS entropy pool.\n *\n * It's meaningful only when @ref AVS_COMMONS_WITH_MBEDTLS is enabled. However,\n * it is independent from the above PSA engine settings.\n */\n/* #undef AVS_COMMONS_WITH_MBEDTLS_PSA_RNG */\n\n/**\n * Is the <c>dlsym()</c> function available?\n *\n * This is currently only used if @ref AVS_COMMONS_WITH_MBEDTLS_PKCS11_ENGINE is\n * enabled. If enabled, the PKCS#11 module is loaded dynamically from a library\n * specified by the <c>PKCS11_MODULE_PATH</c> environment variable. If disabled,\n * a function with the following signature, realizing the PKCS#11\n * <c>C_GetFunctionList</c> method, must be provided manually:\n *\n * <pre>\n * CK_RV _avs_crypto_mbedtls_pkcs11_get_function_list(CK_FUNCTION_LIST_PTR_PTR);\n * </pre>\n */\n#define AVS_COMMONS_HAVE_DLSYM\n\n/**\n * Enables the default implementation of avs_crypto engine, based on OpenSSL and\n * PKCS#11.\n *\n * Requires @ref AVS_COMMONS_WITH_AVS_CRYPTO_PKI_ENGINE to be enabled.\n *\n * NOTE: Query string format for this engine is a subset of the PKCS#11 URI\n * scheme (see RFC 7512), modelled after the format accepted by libp11 OpenSSL\n * engine.\n *\n * NOTE: The unit tests for this feature depend on SoftHSM and pkcs11-tool.\n * These must be installed for the tests to pass.\n *\n * IMPORTANT: Only available as part of the HSM support commercial feature.\n * Ignored in the open source version.\n */\n/* #undef AVS_COMMONS_WITH_OPENSSL_PKCS11_ENGINE */\n/**@}*/\n\n/**\n * Enable support for HTTP content compression in avs_http.\n *\n * Requires linking with zlib.\n */\n/* #undef AVS_COMMONS_HTTP_WITH_ZLIB */\n\n/**\n * Options related to avs_log and logging support within avs_commons.\n */\n/**@{*/\n/* clang-format off */\n/**\n * Size, in bytes, of the avs_log buffer.\n *\n * Log messages that would (including the level, module name and code location)\n * otherwise be longer than this value minus one (for the terminating null\n * character) will be truncated.\n *\n * NOTE: This macro MUST be defined if avs_log is enabled.\n *\n * If editing this file manually, <c>512</c> shall\n * be replaced with a positive integer literal. The default value defined in\n * CMake build scripts is 512.\n */\n#define AVS_COMMONS_LOG_MAX_LINE_LENGTH 512\n/* clang-format on */\n\n/**\n * Configures avs_log to use a synchronized global buffer instead of allocating\n * a buffer on the stack when constructing log messages.\n *\n * Requires avs_compat_threading to be enabled.\n *\n * Enabling this option would reduce the stack space required to use avs_log, at\n * the expense of global storage and the complexity of using a mutex.\n */\n/* #undef AVS_COMMONS_LOG_USE_GLOBAL_BUFFER */\n\n/**\n * Provides a default avs_log handler that prints log messages on stderr.\n *\n * Disabling this option will cause logs to be discarded by default, until a\n * custom log handler is set using <c>avs_log_set_handler()</c>.\n */\n#define AVS_COMMONS_LOG_WITH_DEFAULT_HANDLER\n\n/**\n * Enables the \"micro logs\" feature.\n *\n * Replaces all occurrences of the <c>AVS_DISPOSABLE_LOG()</c> macro with single\n * space strings. This is intended to reduce the size of the compiled code, by\n * stripping it of almost all log string data.\n *\n * Note that this setting will propagate both to avs_commons components\n * themselves (as all its internal logs make use of <c>AVS_DISPOSABLE_LOG()</c>)\n * and the user code that uses it.\n */\n/* #undef AVS_COMMONS_WITH_MICRO_LOGS */\n\n/**\n * Enables logging inside avs_commons.\n *\n * Requires @ref AVS_COMMONS_WITH_AVS_LOG to be enabled.\n *\n * If this macro is not defined at avs_commons compile time, calls to avs_log\n * will not be generated inside avs_commons components.\n */\n#define AVS_COMMONS_WITH_INTERNAL_LOGS\n\n/**\n * Enables TRACE-level logs inside avs_commons.\n *\n * Only meaningful if AVS_COMMONS_WITH_INTERNAL_LOGS is enabled.\n *\n * If this macro is not defined at avs_commons compile time, calls to avs_log\n * with the level set to TRACE will not be generated inside avs_commons\n * components.\n */\n/* #undef AVS_COMMONS_WITH_INTERNAL_TRACE */\n\n/**\n * Enables external implementation of logger subsystem with provided header.\n *\n * Default logger implementation can be found in avs_log_impl.h\n */\n/* #undef AVS_COMMONS_WITH_EXTERNAL_LOGGER_HEADER */\n\n/**\n * If specified, the process of checking if avs_log should be written out\n * takes place in compile time.\n *\n * Specify an optional header with a list of modules for which log level\n * is set. If a log level for specific module is not set, the DEFAULT level\n * will be taken into account. Value of the default logging level is set to\n * DEBUG, but can be overwritten in this header file with AVS_LOG_LEVEL_DEFAULT\n * define. Messages with lower level than the one set will be removed during\n * compile time. Possible values match @ref avs_log_level_t.\n *\n * That file should contain C preprocesor defines in the:\n * - \"#define AVS_LOG_LEVEL_FOR_MODULE_<Module> <Level>\" format,\n *   where <Module> is the module name and <Level> is allowed logging level\n * - \"#define AVS_LOG_LEVEL_DEFAULT <Level>\" format, where <Level> is the\n *   allowed logging level\n *\n * Example file content:\n *\n * <code>\n * #ifndef AVS_COMMONS_EXTERNAL_LOG_LEVELS_H\n * #define AVS_COMMONS_EXTERNAL_LOG_LEVELS_H\n *\n * // global log level value\n * #define AVS_LOG_LEVEL_DEFAULT INFO\n *\n * //for \"coap\" messages only WARNING and ERROR messages will be present\n * #define AVS_LOG_LEVEL_FOR_MODULE_coap WARNING\n *\n * //logs are disable for \"net\" module\n * #define AVS_LOG_LEVEL_FOR_MODULE_net QUIET\n *\n * #endif\n * </code>\n */\n/* #undef AVS_COMMONS_WITH_EXTERNAL_LOG_LEVELS_HEADER */\n\n/**\n * Disable log level check in runtime. Allows to save at least 1.3kB of memory.\n *\n * The macros avs_log_set_level and avs_log_set_default_level\n * will not be available.\n *\n */\n/* #undef AVS_COMMONS_WITHOUT_LOG_CHECK_IN_RUNTIME */\n/**@}*/\n\n/**\n * Options related to avs_net.\n */\n/**@{*/\n/**\n * Enables support for IPv4 connectivity.\n *\n * At least one of AVS_COMMONS_NET_WITH_IPV4 and AVS_COMMONS_NET_WITH_IPV6\n * MUST be defined if avs_net is enabled.\n */\n#define AVS_COMMONS_NET_WITH_IPV4\n\n/**\n * Enables support for IPv6 connectivity.\n *\n * At least one of AVS_COMMONS_NET_WITH_IPV4 and AVS_COMMONS_NET_WITH_IPV6\n * MUST be defined if avs_net is enabled.\n */\n#define AVS_COMMONS_NET_WITH_IPV6\n\n/**\n * If the TLS backend is set to OpenSSL, enables support for DTLS.\n *\n * DTLS is always enabled for the mbed TLS and TinyDTLS backends.\n */\n/* #undef AVS_COMMONS_NET_WITH_DTLS */\n\n/**\n * Enables debug logs generated by mbed TLS.\n *\n * An avs_log-backed handler, logging for the \"mbedtls\" module on the TRACE\n * level, is installed using <c>mbedtls_ssl_conf_dbg()</c> for each (D)TLS\n * socket created if this option is enabled.\n */\n/* #undef AVS_COMMONS_NET_WITH_MBEDTLS_LOGS */\n\n/**\n * Enables (Pre-)Master-Secret logs generation.\n *\n * These logs contain TLS session secrets that tools like Wireshark can use\n * to decrypt captured TLS traffic.\n *\n * NOTE: This only works with Mbed TLS starting from v3.0.0. If you’re using\n * Mbed TLS earlier than v3.1.0, you must enable MBEDTLS_SSL_EXPORT_KEYS.\n *\n * NOTE: The user must specify the stream to which the logs will be transferred\n * using the avs_mbedtls_set_sslkeylog_stream function.\n */\n/* #undef AVS_COMMONS_NET_WITH_MBEDTLS_SSLKEYLOG */\n\n/**\n * Enables the default implementation of avs_net TCP and UDP sockets.\n *\n * Requires either a UNIX-like operating environment, or a compatibility layer\n * with a high degree of compatibility with standard BSD sockets with an\n * appropriate compatibility header (see @ref AVS_COMMONS_POSIX_COMPAT_HEADER) -\n * lwIP and Winsock are currently supported for this scenario.\n */\n#define AVS_COMMONS_NET_WITH_POSIX_AVS_SOCKET\n\n/**\n * @deprecated This is deprecated API. This API can be removed in future\n * releases, without any notice.\n *\n * Enables support for logging socket communication to file.\n *\n * If this option is enabled, avs_net_socket_debug() can be used to enable\n * logging all communication to a file called DEBUG.log. If disabled,\n * avs_net_socket_debug() will always return an error.\n */\n/* #undef AVS_COMMONS_NET_WITH_SOCKET_LOG */\n\n/**\n * @experimental This is experimental API. This API can change in the future\n * versions without any notice.\n *\n * Enables support for custom net traffic interceptor.\n *\n * If this option is enabled, the user needs to provide their own implementation\n * of avs_net_create_traffic_interceptor() function which can wrap and extend\n * an underlying socket.\n */\n/* #undef AVS_COMMONS_WITH_TRAFFIC_INTERCEPTOR */\n\n/**\n * If the TLS backend is either mbed TLS or OpenSSL, enables support for (D)TLS\n * session persistence.\n *\n * Session persistence is not currently supported for the TinyDTLS backend.\n */\n#define AVS_COMMONS_NET_WITH_TLS_SESSION_PERSISTENCE\n/**@}*/\n\n/**\n * Options related to avs_net's default implementation of TCP and UDP sockets.\n *\n * These options make sense only when @ref AVS_COMMONS_NET_WITH_POSIX_AVS_SOCKET\n * is enabled. They describe capabilities of the Unix-like environment in which\n * the library is built.\n *\n * Note that if @ref AVS_COMMONS_POSIX_COMPAT_HEADER is defined, it might\n * redefine these flags independently of the settings in this file.\n */\n/**@{*/\n/**\n * Is the <c>gai_strerror()</c> function available?\n *\n * Enabling this flag will provide more detailed log messages in case that\n * <c>getaddrinfo()</c> fails. If this flag is disabled, numeric error codes\n * values will be logged.\n */\n#define AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_GAI_STRERROR\n\n/**\n * Is the <c>getifaddrs()</c> function available?\n *\n * Disabling this flag will cause <c>avs_net_socket_interface_name()</c> to use\n * a less optimal implementation based on the <c>SIOCGIFCONF</c> ioctl.\n *\n * If <c>SIOCGIFCONF</c> is not defined, either, then\n * <c>avs_net_socket_interface_name()</c> will always return an error.\n */\n#define AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_GETIFADDRS\n\n/**\n * Is the <c>getnameinfo()</c> function available?\n *\n * Disabling this flag will cause <c>avs_net_socket_receive_from()</c>,\n * <c>avs_net_socket_accept()</c>,\n * <c>avs_net_resolved_endpoint_get_host_port()</c>,\n * <c>avs_net_resolved_endpoint_get_host()</c> and\n * <c>avs_net_resolve_host_simple()</c> to use a custom reimplementation of\n * <c>getnameinfo()</c> based on <c>inet_ntop()</c>.\n */\n#define AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_GETNAMEINFO\n\n/**\n * Is the <c>IN6_IS_ADDR_V4MAPPED</c> macro available and usable?\n *\n * Disabling this flag will cause a custom code that compares IPv6 addresses\n * with the <c>::ffff:0.0.0.0/32</c> mask to be used instead.\n */\n#define AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_IN6_IS_ADDR_V4MAPPED\n\n/**\n * Should be defined if IPv4-mapped IPv6 addresses (<c>::ffff:0.0.0.0/32</c>)\n * are <strong>NOT</strong> supported by the underlying platform.\n *\n * Enabling this flag will prevent avs_net from using IPv4-mapped IPv6 addresses\n * and instead re-open and re-bind the socket if a connection to an IPv4 address\n * is requested on a previously created IPv6 socket.\n *\n * This may result in otherwise redundant <c>socket()</c>, <c>bind()</c> and\n * <c>close()</c> system calls to be performed, but may be necessary for\n * interoperability with some platforms.\n */\n/* #undef AVS_COMMONS_NET_POSIX_AVS_SOCKET_WITHOUT_IN6_V4MAPPED_SUPPORT */\n\n/**\n * Is the <c>inet_ntop()</c> function available?\n *\n * Disabling this flag will cause an internal implementation of this function\n * adapted from BIND 4.9.4 to be used instead.\n */\n#define AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_INET_NTOP\n\n/**\n * Is the <c>poll()</c> function available?\n *\n * Disabling this flag will cause a less robust code based on <c>select()</c> to\n * be used instead.\n */\n#define AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_POLL\n\n/**\n * Is the <c>recvmsg()</c> function available?\n *\n * Disabling this flag will cause <c>recvfrom()</c> to be used instead. Note\n * that for UDP sockets, this will cause false positives for datagram truncation\n * detection (<c>AVS_EMSGSIZE</c>) to be reported when the received message is\n * exactly the size of the buffer.\n */\n#define AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_RECVMSG\n/**@}*/\n\n/**\n * Enable thread safety in avs_sched.\n *\n * Makes all scheduler accesses synchronized and thread-safe, at the cost of\n * requiring avs_compat_threading to be enabled, and higher resource usage.\n */\n#define AVS_COMMONS_SCHED_THREAD_SAFE\n\n/**\n * Enable support for file I/O in avs_stream.\n *\n * Disabling this flag will cause the functions declared in\n * <c>avs_stream_file.h</c> to not be defined.\n */\n#define AVS_COMMONS_STREAM_WITH_FILE\n\n/**\n * Enable usage of <c>backtrace()</c> and <c>backtrace_symbols()</c> when\n * reporting assertion failures from avs_unit.\n *\n * Requires the afore-mentioned GNU-specific functions to be available.\n *\n * If this flag is disabled, stack traces will not be displayed with assertion\n * failures.\n */\n#define AVS_COMMONS_UNIT_POSIX_HAVE_BACKTRACE\n\n/**\n * Options related to avs_utils.\n */\n/**@{*/\n/**\n * Enable the default implementation of avs_time_real_now() and\n * avs_time_monotonic_now().\n *\n * Requires an operating environment that supports a clock_gettime() call\n * compatible with POSIX.\n */\n#define AVS_COMMONS_UTILS_WITH_POSIX_AVS_TIME\n\n/**\n * Enable the default implementation of avs_malloc(), avs_free(), avs_calloc()\n * and avs_realloc() that forwards to system malloc(), free(), calloc() and\n * realloc() calls.\n *\n * You might disable this option if for any reason you need to use a custom\n * allocator.\n */\n#define AVS_COMMONS_UTILS_WITH_STANDARD_ALLOCATOR\n\n/**\n * Enable the alternate implementation of avs_malloc(), avs_free(), avs_calloc()\n * and avs_realloc() that uses system malloc(), free() and realloc() calls, but\n * includes additional fixup code that ensures proper alignment to\n * <c>AVS_ALIGNOF(avs_max_align_t)</c> (usually 8 bytes on common platforms).\n *\n * <c>AVS_COMMONS_UTILS_WITH_STANDARD_ALLOCATOR</c> and\n * <c>AVS_COMMONS_UTILS_WITH_ALIGNFIX_ALLOCATOR</c> cannot be enabled at the\n * same time.\n *\n * NOTE: This implementation is only intended for platforms where the system\n * allocator does not properly conform to the alignment requirements.\n *\n * It comes with an additional runtime costs:\n *\n * - <c>AVS_ALIGNOF(avs_max_align_t)</c> bytes (usually 8) of additional\n *   overhead for each allocated memory block\n * - Additional memmove() for every realloc() that returned a block that is not\n *   properly aligned\n * - avs_calloc() is implemented as avs_malloc() followed by an explicit\n *   memset(); this may be suboptimal on some platforms\n *\n * If these costs are unacceptable for you, you may want to consider fixing,\n * replacing or reconfiguring your system allocator for conformance, or\n * implementing a custom one instead.\n *\n * Please note that some code in avs_commons and dependent projects (e.g. Anjay)\n * may include runtime assertions for proper memory alignment that will be\n * triggered when using a non-conformant standard allocator. Such allocators are\n * relatively common in embedded SDKs. This \"alignfix\" allocator is intended to\n * work around these issues. On some platforms (e.g. x86) those alignment issues\n * may not actually cause any problems - so you may want to consider disabling\n * runtime assertions instead. Please carefully examine your target platform's\n * alignment requirements and behavior of misaligned memory accesses (including\n * 64-bit data types such as <c>int64_t</c> and <c>double</c>) before doing so.\n */\n/* #undef AVS_COMMONS_UTILS_WITH_ALIGNFIX_ALLOCATOR */\n\n/**@}*/\n\n#endif /* AVS_COMMONS_CONFIG_H */\n"
  },
  {
    "path": "example_configs/linux_lwm2m11/anjay/anjay_config.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ANJAY_CONFIG_H\n#define ANJAY_CONFIG_H\n\n/**\n * @file anjay_config.h\n *\n * Anjay library configuration.\n *\n * The preferred way to compile Anjay is to use CMake, in which case this file\n * will be generated automatically by CMake.\n *\n * However, to provide compatibility with various build systems used especially\n * by embedded platforms, it is alternatively supported to compile Anjay by\n * other means, in which case this file will need to be provided manually.\n *\n * <strong>NOTE: To compile this library without using CMake, you need to\n * configure avs_commons and avs_coap first. Please refer to documentation in\n * the <c>avs_commons_config.h</c> and <c>avs_coap_config.h</c> files (provided\n * in the repositories as <c>avs_commons_config.h.in</c> and\n * <c>avs_coap_config.h.in</c>, respectively) for details.</strong>\n *\n * <strong>Anjay requires the following avs_coap options to be enabled:</strong>\n *\n * - @c WITH_AVS_COAP_UDP and/or @c WITH_AVS_COAP_TCP\n * - @c WITH_AVS_COAP_STREAMING_API\n * - @c WITH_AVS_COAP_BLOCK is highly recommended\n * - @c WITH_AVS_COAP_OBSERVE (if @c ANJAY_WITH_OBSERVE is enabled)\n * - @c WITH_AVS_COAP_OSCORE (if @c ANJAY_WITH_COAP_OSCORE is enabled, available\n *   only as a commercial feature)\n *\n * <strong>Anjay requires the following avs_commons components to be\n * enabled:</strong>\n *\n * - All components required by avs_coap, see <c>avs_coap_config.h</c>\n * - @c avs_algorithm\n * - @c avs_stream\n * - @c avs_url\n * - @c avs_persistence is highly recommended\n * - @c avs_http (if @c ANJAY_WITH_HTTP_DOWNLOAD is enabled)\n * - @c avs_rbtree or @c avs_sorted_set (if @c ANJAY_WITH_OBSERVE or\n *   @c ANJAY_WITH_MODULE_ACCESS_CONTROL is enabled)\n *\n * In the repository, this file is provided as <c>anjay_config.h.in</c>,\n * intended to be processed by CMake. If editing this file manually, please copy\n * or rename it to <c>anjay_config.h</c> and for each of the\n * <c>\\#cmakedefine</c> directives, please either replace it with regular\n * <c>\\#define</c> to enable it, or comment it out to disable. You may also need\n * to replace variables wrapped in <c>\\@</c> signs with concrete values. Please\n * refer to the comments above each of the specific definition for details.\n *\n * If you are editing a file previously generated by CMake, these\n * <c>\\#cmakedefine</c>s will be already replaced by either <c>\\#define</c> or\n * commented out <c>\\#undef</c> directives.\n */\n\n/**\n * Enable logging in Anjay.\n *\n * If this flag is disabled, no logging is compiled into the binary at all.\n */\n#define ANJAY_WITH_LOGS\n\n/**\n * Enable TRACE-level logs in Anjay.\n *\n * Only meaningful if <c>ANJAY_WITH_LOGS</c> is enabled.\n */\n#define ANJAY_WITH_TRACE_LOGS\n\n/**\n * Enable core support for Access Control mechanisms.\n *\n * Requires separate implementation of the Access Control object itself.\n * Either the implementation available as part of\n * <c>ANJAY_WITH_MODULE_ACCESS_CONTROL</c>, or a custom application-provided one\n * may be used.\n */\n#define ANJAY_WITH_ACCESS_CONTROL\n\n/**\n * Enable automatic attribute storage.\n *\n * Requires <c>AVS_COMMONS_WITH_AVS_PERSISTENCE</c> to be enabled in avs_commons\n * configuration.\n */\n#define ANJAY_WITH_ATTR_STORAGE\n\n/**\n * Enable support for the <c>anjay_download()</c> API.\n */\n#define ANJAY_WITH_DOWNLOADER\n\n/**\n * Enable support for CoAP(S) downloads.\n *\n * Only meaningful if <c>ANJAY_WITH_DOWNLOADER</c> is enabled.\n */\n#define ANJAY_WITH_COAP_DOWNLOAD\n\n/**\n * Enable support for HTTP(S) downloads.\n *\n * Only meaningful if <c>ANJAY_WITH_DOWNLOADER</c> is enabled.\n */\n/* #undef ANJAY_WITH_HTTP_DOWNLOAD */\n\n/**\n * Enable support for the LwM2M Bootstrap Interface.\n */\n#define ANJAY_WITH_BOOTSTRAP\n\n/**\n * Enable support for the LwM2M Bootstrap-Pack operation.\n *\n * Requires <c>ANJAY_WITH_BOOTSTRAP</c> and <c>ANJAY_WITH_LWM2M12</c> to be\n * enabled.\n */\n/* #undef ANJAY_WITH_BOOTSTRAP_PACK */\n\n/**\n * Enable support for the LwM2M Discover operation.\n *\n * Note that it is required for full compliance with the LwM2M protocol.\n */\n#define ANJAY_WITH_DISCOVER\n\n/**\n * Enable support for the LwM2M Information Reporting interface (Observe and\n * Notify operations).\n *\n * Requires <c>WITH_AVS_COAP_OBSERVE</c> to be enabled in avs_coap\n * configuration.\n *\n * Note that it is required for full compliance with the LwM2M protocol.\n */\n#define ANJAY_WITH_OBSERVE\n\n/**\n * Enable support for measuring amount of LwM2M traffic\n * (<c>anjay_get_tx_bytes()</c>, <c>anjay_get_rx_bytes()</c>,\n * <c>anjay_get_num_incoming_retransmissions()</c> and\n * <c>anjay_get_num_outgoing_retransmissions()</c> APIs.\n */\n#define ANJAY_WITH_NET_STATS\n\n/**\n * Enable support for communication timestamp\n * (<c>anjay_get_server_last_registration_time()</c>\n * <c>anjay_get_server_next_update_time()</c> and\n * <c>anjay_get_server_last_communication_time()</c>) APIs.\n */\n#define ANJAY_WITH_COMMUNICATION_TIMESTAMP_API\n\n/**\n * Enable support for the <c>anjay_resource_observation_status()</c> API.\n */\n#define ANJAY_WITH_OBSERVATION_STATUS\n\n/**\n * Maximum number of servers observing a given Resource listed by\n * <c>anjay_resource_observation_status()</c> function.\n *\n * Only meaningful if <c>ANJAY_WITH_OBSERVATION_STATUS</c> is enabled.\n */\n#define ANJAY_MAX_OBSERVATION_SERVERS_REPORTED_NUMBER 0\n\n/**\n * Enable guarding of all accesses to <c>anjay_t</c> with a mutex.\n */\n#define ANJAY_WITH_THREAD_SAFETY\n\n/**\n * Enable standard implementation of an event loop.\n *\n * Requires C11 <c>stdatomic.h</c> header to be available, and either a platform\n * that provides a BSD-compatible socket API, or a compatibility layer file (see\n * <c>AVS_COMMONS_POSIX_COMPAT_HEADER</c> in <c>avs_commons_config.h</c>). This\n * is designed to best work with the defalt implementation of avs_net sockets\n * (see <c>AVS_COMMONS_NET_WITH_POSIX_AVS_SOCKET</c>), but alternative socket\n * implementations can also be used.\n *\n * The event loop is most useful when thread safety features\n * (@ref ANJAY_WITH_THREAD_SAFETY and <c>AVS_COMMONS_SCHED_THREAD_SAFE</c>) are\n * enabled as well, but this is not a hard requirement. See the documentation\n * for <c>anjay_event_loop_run()</c> for details.\n */\n#define ANJAY_WITH_EVENT_LOOP\n\n/**\n * Enable support for features new to LwM2M protocol version 1.1.\n */\n#define ANJAY_WITH_LWM2M11\n\n/**\n * Enable support for features new to LwM2M protocol version 1.2.\n *\n * Requires <c>ANJAY_WITH_LWM2M11</c> to be enabled.\n */\n/* #undef ANJAY_WITH_LWM2M12 */\n\n/**\n * Enable support for OSCORE-based security for LwM2M connections.\n *\n * Requires <c>ANJAY_WITH_LWM2M11</c> to be enabled, and\n * <c>WITH_AVS_COAP_OSCORE</c> to be enabled in avs_coap configuration.\n *\n * IMPORTANT: Only available as part of the OSCORE commercial feature. Ignored\n * in the open source version.\n */\n/* #undef ANJAY_WITH_COAP_OSCORE */\n\n/**\n * Enable support for Observation Attributes.\n *\n * Requires <c>ANJAY_WITH_OBSERVE</c> and <c>ANJAY_WITH_LWM2M12</c> to be\n * enabled.\n */\n/* #undef ANJAY_WITH_OBSERVATION_ATTRIBUTES */\n\n/**\n * Enable support for the LwM2M Send operation.\n *\n * Requires <c>ANJAY_WITH_LWM2M11</c> and either <c>ANJAY_WITH_SENML_JSON</c> or\n * <c>ANJAY_WITH_CBOR</c> to be enabled.\n */\n#define ANJAY_WITH_SEND\n\n/**\n * Enable support for the SMS binding and the SMS trigger mechanism.\n *\n * Requires <c>WITH_AVS_COAP_UDP</c> to be enabled in avs_coap configuration.\n *\n * IMPORTANT: Only available as part of the SMS commercial feature. Ignored in\n * the open source version.\n */\n/* #undef ANJAY_WITH_SMS */\n\n/**\n * Enable support for sending and receiving multipart SMS messages.\n *\n * Requires <c>ANJAY_WITH_SMS</c> to be enabled.\n *\n * IMPORTANT: Only available as part of the SMS commercial feature. Ignored in\n * the open source version.\n */\n/* #undef ANJAY_WITH_SMS_MULTIPART */\n\n/**\n * Enable support for Non-IP Data Delivery.\n *\n * Requires <c>ANJAY_WITH_LWM2M11</c> to be enabled, and\n * <c>WITH_AVS_COAP_UDP</c> to be enabled in avs_coap configuration.\n *\n * IMPORTANT: Only available as a commercial feature. Ignored in the open\n * source version.\n */\n/* #undef ANJAY_WITH_NIDD */\n\n/**\n * Enable support for core state persistence\n * (<c>anjay_new_from_core_persistence()</c> and\n * <c>anjay_delete_with_core_persistence()</c> APIs).\n *\n * Requires <c>ANJAY_WITH_OBSERVE</c> to be enabled,\n * <c>AVS_COMMONS_WITH_AVS_PERSISTENCE</c> to be enabled in avs_commons, and\n * <c>WITH_AVS_COAP_OBSERVE_PERSISTENCE</c> to be enabled in avs_coap\n * configuration.\n *\n * IMPORTANT: Only available as a commercial feature. Ignored in the open\n * source version.\n */\n/* #undef ANJAY_WITH_CORE_PERSISTENCE */\n\n/**\n * Disable automatic closing of server connection sockets after\n * MAX_TRANSMIT_WAIT of inactivity.\n */\n/* #undef ANJAY_WITHOUT_QUEUE_MODE_AUTOCLOSE */\n\n/**\n * Enable support for CoAP Content-Format numerical values 1541-1544 that have\n * been used before final LwM2M TS 1.0.\n */\n/* #undef ANJAY_WITH_LEGACY_CONTENT_FORMAT_SUPPORT */\n\n/**\n * Enable support for JSON format as specified in LwM2M TS 1.0.\n *\n * NOTE: Anjay is only capable of generating this format, there is no parsing\n * support regardless of the state of this option.\n */\n#define ANJAY_WITH_LWM2M_JSON\n\n/**\n * Disable support for TLV format as specified in LwM2M TS 1.0.\n *\n * NOTE: LwM2M Client using LwM2M 1.0 MUST support this format. It may be\n * disabled if LwM2M 1.1 is used and SenML JSON or SenML CBOR is enabled.\n */\n/* #undef ANJAY_WITHOUT_TLV */\n\n/**\n * Disable support for Plain Text format as specified in LwM2M TS 1.0 and 1.1.\n *\n * NOTE: LwM2M Client SHOULD support this format. It may be disabled to reduce\n * library size if LwM2M Server is configured to not use it in requests.\n */\n/* #undef ANJAY_WITHOUT_PLAINTEXT */\n\n/**\n * Disable use of the Deregister message.\n */\n/* #undef ANJAY_WITHOUT_DEREGISTER */\n\n/**\n * Disable support for \"IP stickiness\", i.e. preference of the same IP address\n * when reconnecting to a server using a domain name.\n */\n/* #undef ANJAY_WITHOUT_IP_STICKINESS */\n\n/**\n * Enable support for SenML JSON format, as specified in LwM2M TS 1.1.\n *\n * NOTE: As opposed to <c>ANJAY_WITH_LWM2M_JSON</c>, both generating and parsing\n * is supported.\n *\n * Requires <c>ANJAY_WITH_LWM2M11</c> to be enabled.\n */\n#define ANJAY_WITH_SENML_JSON\n\n/**\n * Enable support for CBOR and SenML CBOR formats, as specified in LwM2M TS 1.1.\n *\n * Requires <c>ANJAY_WITH_LWM2M11</c> to be enabled.\n */\n#define ANJAY_WITH_CBOR\n\n/**\n * Enable support for Enrollment over Secure Transport.\n *\n * Requires <c>ANJAY_WITH_BOOTSTRAP</c> to be enabled.\n *\n * IMPORTANT: Only available as part of the EST commercial feature. Ignored in\n * the open source version.\n */\n/* #undef ANJAY_WITH_EST */\n\n/**\n * Enable support for hardware security engine in the EST subsystem.\n *\n * Requires <c>ANJAY_WITH_EST</c> to be enabled in Anjay configuration and\n * <c>AVS_COMMONS_WITH_AVS_CRYPTO_PKI_ENGINE</c> to be enabled in avs_commons\n * configuration.\n *\n * IMPORTANT: Only available in commercial versions that include both the EST\n * and HSM features. Ignored in versions distributed without these features.\n */\n/* #undef ANJAY_WITH_EST_ENGINE_SUPPORT */\n\n/**\n * Enable support for the Confirmable Notification attribute, as specified in\n * LwM2M TS 1.2.\n *\n * Before TS 1.2, this has been supported in Anjay as a custom extension, and\n * thus it is available independently from TS 1.2 support itself, including in\n * the open source version.\n *\n * Requires <c>ANJAY_WITH_OBSERVE</c> to be enabled.\n */\n/* #undef ANJAY_WITH_CON_ATTR */\n\n/**\n * Enable support for handling security credentials in the data model using\n * structured <c>avs_crypto</c> types.\n *\n * If the <c>security</c> module is also enabled (see @ref\n * ANJAY_WITH_MODULE_SECURITY), it also enables support for passing these\n * credentials through such structured types when adding Security object\n * instances via the @ref anjay_security_instance_t structure.\n */\n#define ANJAY_WITH_SECURITY_STRUCTURED\n\n/**\n * Maximum size in bytes supported for the \"Public Key or Identity\" resource in\n * the LwM2M Security object.\n *\n * If editing this file manually, <c>2048</c> shall be\n * replaced with a positive integer literal.\n *\n * The default value defined in CMake build scripts is 2048.\n * Minimal suggested setting for low-resource builds is 256.\n */\n#define ANJAY_MAX_PK_OR_IDENTITY_SIZE 2048\n\n/**\n * Maximum size in bytes supported for the \"Secret Key\" resource in the LwM2M\n * Security Object.\n *\n * If editing this file manually, <c>256</c> shall be replaced\n * with a positive integer literal.\n *\n * The default value defined in CMake build scripts is 256.\n * Minimal suggested setting for low-resource builds is 128.\n */\n#define ANJAY_MAX_SECRET_KEY_SIZE 256\n\n/**\n * Maximum length supported for stringified floating-point values.\n *\n * Used when parsing plaintext and SenML JSON content formats - when parsing a\n * floating-point value, any string of length equal or greater than this setting\n * will automatically be considered invalid, even if it could in theory be\n * parsed as a valid number.\n *\n * If editing this file manually, <c>512</c> shall be\n * replaced with a positive integer literal.\n *\n * The default value defined in CMake build scripts is 512.\n * Minimal suggested setting for low-resource builds is 64.\n */\n#define ANJAY_MAX_DOUBLE_STRING_SIZE 512\n\n/**\n * Maximum length supported for a single Uri-Path or Location-Path segment.\n *\n * When handling incoming CoAP messages, any Uri-Path or Location-Path option of\n * length equal or greater than this setting will be considered invalid.\n *\n * If editing this file manually, <c>256</c> shall be\n * replaced with a positive integer literal.\n *\n * The default value defined in CMake build scripts is 256.\n * Minimal suggested setting for low-resource builds is 64.\n */\n#define ANJAY_MAX_URI_SEGMENT_SIZE 256\n\n/**\n * Maximum length supported for a single Uri-Query segment.\n *\n * When handling incoming CoAP messages, any Uri-Query option of length equal or\n * greater than this setting will be considered invalid.\n *\n * If editing this file manually, <c>256</c> shall be\n * replaced with a positive integer literal.\n *\n * The default value defined in CMake build scripts is 256.\n * Minimal suggested setting for low-resource builds is 64.\n */\n#define ANJAY_MAX_URI_QUERY_SEGMENT_SIZE 256\n\n/**\n * Size of buffer allocated for storing DTLS session state when connection is\n * not in use (e.g. during queue mode operation).\n *\n * If editing this file manually, <c>1024</c> shall be\n * replaced with a positive integer literal. The default value defined in CMake\n * build scripts is 1024.\n */\n#define ANJAY_DTLS_SESSION_BUFFER_SIZE 1024\n\n/**\n * Value of Content-Format used in Send messages. Only a few specific values are\n * supported:\n *\n * - @c AVS_COAP_FORMAT_NONE means no default value is used and Anjay will\n *   decide the format based on the what is available.\n * - @c AVS_COAP_FORMAT_OMA_LWM2M_CBOR Anjay will generate a Send message in\n *   LwM2M CBOR format.\n * - @c AVS_COAP_FORMAT_SENML_CBOR Anjay will generate a Send message in SenML\n *   CBOR format.\n * - @c AVS_COAP_FORMAT_SENML_JSON Anjay will generate a Send message in SenML\n *   JSON format.\n *\n * Note that to use a specific format it must be available during compilation.\n *\n * The default value defined in CMake build scripts is\n * <c>AVS_COAP_FORMAT_NONE</c>.\n */\n#define ANJAY_DEFAULT_SEND_FORMAT AVS_COAP_FORMAT_NONE\n\n/**\n * Optional Anjay modules.\n */\n/**@{*/\n/**\n * Enable access_control module (implementation of the Access Control object).\n *\n * Requires <c>ANJAY_WITH_ACCESS_CONTROL</c> to be enabled.\n */\n#define ANJAY_WITH_MODULE_ACCESS_CONTROL\n\n/**\n * Enable security module (implementation of the LwM2M Security object).\n */\n#define ANJAY_WITH_MODULE_SECURITY\n\n/**\n * Enable support for hardware security engine in the security module.\n *\n * This feature allows security credentials provisioned into the LwM2M Security\n * object to be automatically moved into a hardware security module.\n *\n * Requires <c>ANJAY_WITH_MODULE_SECURITY</c> to be enabled in Anjay\n * configuration, and at least one of\n * <c>AVS_COMMONS_WITH_AVS_CRYPTO_PKI_ENGINE</c> or\n * <c>AVS_COMMONS_WITH_AVS_CRYPTO_PSK_ENGINE</c> to be enabled in avs_commons\n * configuration.\n *\n * IMPORTANT: Only available as part of the HSM support commercial feature.\n * Ignored in versions distributed without this feature.\n */\n/* #undef ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT */\n\n/**\n * Enable server module (implementation of the LwM2M Server object).\n */\n#define ANJAY_WITH_MODULE_SERVER\n\n/**\n * Enable fw_update module (implementation of the Firmware Update object).\n */\n#define ANJAY_WITH_MODULE_FW_UPDATE\n\n/**\n * Enable advanced_fw_update module (implementation of the 33629 custom\n * Advanced Firmware Update object).\n */\n/* #undef ANJAY_WITH_MODULE_ADVANCED_FW_UPDATE */\n\n/**\n * Disable support for PUSH mode Firmware Update.\n *\n * Only meaningful if <c>ANJAY_WITH_MODULE_FW_UPDATE</c> is enabled. Requires\n * <c>ANJAY_WITH_DOWNLOADER</c> to be enabled.\n */\n/* #undef ANJAY_WITHOUT_MODULE_FW_UPDATE_PUSH_MODE */\n\n/**\n * Enable sw_mgmt module (implementation of the Software Management object).\n */\n/* #undef ANJAY_WITH_MODULE_SW_MGMT */\n\n/**\n * Enables ipso_objects module (generic implementation of basic sensor, three\n * axis sensor and Push Button IPSO objects).\n */\n#define ANJAY_WITH_MODULE_IPSO_OBJECTS\n\n/**\n * Enables experimental ipso_objects_v2 module (generic implementation of basic\n * sensor and three axis sensor IPSO objects).\n */\n#define ANJAY_WITH_MODULE_IPSO_OBJECTS_V2\n\n/**\n * Enable at_sms module (implementation of an SMS driver for AT modem devices).\n *\n * Requires <c>ANJAY_WITH_SMS</c> to be enabled and the operating system to\n * support the POSIX <c>poll()</c> function.\n *\n * IMPORTANT: Only available as part of the SMS commercial feature. Ignored in\n * the open source version.\n */\n/* #undef ANJAY_WITH_MODULE_AT_SMS */\n\n/**\n * Enable bg96_nidd module (implementation of NB-IoT-based NIDD driver for\n * Quectel BG96).\n *\n * Requires <c>ANJAY_WITH_NIDD</c> to be enabled.\n *\n * IMPORTANT: Only available as part of the NIDD commercial feature. Ignored\n * in the open source version.\n */\n/* #undef ANJAY_WITH_MODULE_BG96_NIDD */\n\n/**\n * Enable bootstrapper module (loader for bootstrap information formatted as per\n * the \"Storage of LwM2M Bootstrap Information on the Smartcard\" appendix to the\n * LwM2M TS).\n *\n * Requires <c>ANJAY_WITH_BOOTSTRAP</c> to be enabled and\n * <c>AVS_COMMONS_WITH_AVS_PERSISTENCE</c> to be enabled in avs_commons\n * configuration.\n *\n * IMPORTANT: Only available with the bootstrapper feature. Ignored in the open\n * source version.\n */\n/* #undef ANJAY_WITH_MODULE_BOOTSTRAPPER */\n\n/**\n * Enable the SIM bootstrap module, which enables reading the SIM bootstrap\n * information from a smartcard, which can then be passed through to the\n * bootstrapper module.\n *\n * Requires <c>ANJAY_WITH_MODULE_BOOTSTRAPPER</c> to be enabled.\n *\n * IMPORTANT: Only available with the bootstrapper feature. Ignored in the open\n * source version.\n */\n/* #undef ANJAY_WITH_MODULE_SIM_BOOTSTRAP */\n\n/**\n * Forced ID of the file to read the SIM bootstrap information from.\n *\n * If not defined (default), the bootstrap information file will be discovered\n * through the ODF file, as mandated by the specification.\n *\n * Requires <c>ANJAY_WITH_MODULE_BOOTSTRAPPER</c> to be enabled. At most one of\n * <c>ANJAY_MODULE_SIM_BOOTSTRAP_HARDCODED_FILE_ID</c> and\n * <c>ANJAY_MODULE_SIM_BOOTSTRAP_DATA_OID_OVERRIDE_HEX</c> may be defined at the\n * same time.\n *\n * IMPORTANT: Only available with the bootstrapper feature. Ignored in the open\n * source version.\n */\n/* #undef ANJAY_MODULE_SIM_BOOTSTRAP_HARDCODED_FILE_ID */\n\n/**\n * Overridden OID of the SIM bootstrap information to look for in the DODF file,\n * expressed as a hexlified DER representation (without the header).\n *\n * This is the hexlified expected value of the 'id' field within the 'OidDO'\n * sequence in the DODF file (please refer to the PKCS #15 document for more\n * information).\n *\n * If not defined, the default value of <c>\"672b0901\"</c>, which corresponds to\n * OID 2.23.43.9.1 {joint-iso-itu-t(2) international-organizations(23) wap(43)\n * oma-lwm2m(9) lwm2m-bootstrap(1)}, will be used.\n *\n * No other values than the default are valid according to the specification,\n * but some SIM cards are known to use other non-standard values, e.g.\n * <c>\"0604672b0901\"</c> - including a superfluous nested BER-TLV header, as\n * erroneously illustrated in the EF(DODF-bootstrap) file coding example in\n * LwM2M TS 1.2 and earlier (fixed in LwM2M TS 1.2.1) - which is interpreted as\n * OID 0.6.4.103.43.9.1 (note that it is invalid as the 0.6 tree does not exist\n * in the repository as of writing this note).\n *\n * Requires <c>ANJAY_WITH_MODULE_BOOTSTRAPPER</c> to be enabled. At most one of\n * <c>ANJAY_MODULE_SIM_BOOTSTRAP_HARDCODED_FILE_ID</c> and\n * <c>ANJAY_MODULE_SIM_BOOTSTRAP_DATA_OID_OVERRIDE_HEX</c> may be defined at the\n * same time.\n *\n * IMPORTANT: Only available with the bootstrapper feature. Ignored in the open\n * source version.\n */\n/* #undef ANJAY_MODULE_SIM_BOOTSTRAP_DATA_OID_OVERRIDE_HEX */\n\n/**\n * Enable factory provisioning module. Data provided during provisioning uses\n * SenML CBOR format.\n */\n#define ANJAY_WITH_MODULE_FACTORY_PROVISIONING\n\n/**\n * Enable oscore module (implementation of the OSCORE object).\n *\n * IMPORTANT: Only available as part of the OSCORE commercial feature. Ignored\n * in the open source version.\n */\n/* #undef ANJAY_WITH_MODULE_OSCORE */\n\n/**\n * If enable Anjay doesn't handle composite operation (read, observe and write).\n * Its use makes sense for LWM2M v1.1 upwards.\n *\n * This flag can be used to reduce the size of the resulting code.\n *\n * If active, anjay will respond with message code 5.01 Not Implemented to any\n * composite type request.\n */\n/* #undef ANJAY_WITHOUT_COMPOSITE_OPERATIONS */\n\n/**\n * Enable support for the experimental\n * <c>anjay_get_server_connection_status()</c> API and related\n * <c>anjay_server_connection_status_cb_t</c> callback.\n */\n#define ANJAY_WITH_CONN_STATUS_API\n\n/**\n * Enable support for the experimental\n * <c>anjay_ssl_error_cb_t</c> callback.\n * Currently only supported for mbedTLS and custom SSL integration builds\n */\n#define ANJAY_WITH_SSL_ERROR_API\n\n/**\n * Enable support for /25 LwM2M Gateway Object.\n *\n * Requires <c>ANJAY_WITH_LWM2M11</c> to be enabled.\n * Requires <c>ANJAY_WITH_CORE_PERSISTENCE</c> (commercial feature) to be\n * disabled.\n */\n/* #undef ANJAY_WITH_LWM2M_GATEWAY */\n\n/**\n * Maximum holdoff time in seconds.\n *\n * This parameter defines an upper limit on the Hold Off Time as specified in\n * the LwM2M Security Object (Object ID: 0, Resource ID: 11). The Hold Off Time\n * is used by the LwM2M Client to delay initiating a Bootstrap process.\n *\n * Limiting the Hold Off Time is important in network environments where\n * prolonged delays may cause NAT bindings to expire, potentially leading to\n * failed connection attempts. This parameter ensures that the configured\n * Hold Off Time does not exceed a safe threshold. If a DTLS binding with\n * Connection ID is used this parameter can be increased safely.\n *\n * If editing this file manually, <c>20</c> shall be\n * replaced with a positive integer literal representing the time in seconds.\n * The default value defined in CMake build scripts is 20.\n */\n#define ANJAY_MAX_HOLDOFF_TIME 20\n\n/**\n * Enable support for optional resources of the Firmware Update object\n * introduced in LwM2M 1.1.\n *\n * This includes the following resources:\n * - Severity\n * - Last State Change Time\n * - Max Defer Period\n *\n * Requires <c>ANJAY_WITH_LWM2M11</c> to be enabled.\n */\n/* #undef ANJAY_WITH_MODULE_FW_UPDATE_V11_RESOURCES */\n\n/**@}*/\n\n#endif // ANJAY_CONFIG_H\n"
  },
  {
    "path": "example_configs/linux_lwm2m11/avsystem/coap/avs_coap_config.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef AVS_COAP_CONFIG_H\n#define AVS_COAP_CONFIG_H\n\n/**\n * @file avs_coap_config.h\n *\n * avs_coap library configuration.\n *\n * The preferred way to compile avs_coap is to use CMake, in which case this\n * file will be generated automatically by CMake.\n *\n * However, to provide compatibility with various build systems used especially\n * by embedded platforms, it is alternatively supported to compile avs_coap by\n * other means, in which case this file will need to be provided manually.\n *\n * <strong>NOTE: To compile this library without using CMake, you need to\n * configure avs_commons first. Please refer to documentation in the\n * <c>avs_commons_config.h</c> file (provided in the repository as\n * <c>avs_commons_config.h.in</c>) for details.</strong>\n *\n * <strong>avs_coap requires the following avs_commons components to be\n * enabled:</strong>\n *\n * - @c avs_buffer\n * - @c avs_compat_threading\n * - @c avs_list\n * - @c avs_net\n * - @c avs_sched\n * - @c avs_stream\n * - @c avs_utils\n * - @c avs_log (if @c WITH_AVS_COAP_LOGS is enabled)\n * - @c avs_persistence (if @c WITH_AVS_COAP_OBSERVE_PERSISTENCE is enabled)\n * - @c avs_crypto (if @c WITH_AVS_COAP_OSCORE is enabled)\n *\n * In the repository, this file is provided as <c>avs_coap_config.h.in</c>,\n * intended to be processed by CMake. If editing this file manually, please copy\n * or rename it to <c>avs_coap_config.h</c> and for each of the\n * <c>\\#cmakedefine</c> directives, please either replace it with regular\n * <c>\\#define</c> to enable it, or comment it out to disable. You may also need\n * to replace variables wrapped in <c>\\@</c> signs with concrete values. Please\n * refer to the comments above each of the specific definition for details.\n *\n * If you are editing a file previously generated by CMake, these\n * <c>\\#cmakedefine</c>s will be already replaced by either <c>\\#define</c> or\n * commented out <c>\\#undef</c> directives.\n */\n\n/**\n * Enable support for block-wise transfers (RFC 7959).\n *\n * If this flag is disabled, attempting to send a message bigger than the\n * internal buffer will fail; incoming messages may still carry BLOCK1 or BLOCK2\n * options, but they will not be interpreted by the library in any way.\n */\n#define WITH_AVS_COAP_BLOCK\n\n/**\n * Enable support for observations (RFC 7641).\n */\n#define WITH_AVS_COAP_OBSERVE\n\n/**\n * Turn on cancelling observation on a timeout.\n *\n * Only meaningful if <c>WITH_AVS_COAP_OBSERVE</c> is enabled.\n *\n * NOTE: LwM2M specification requires LwM2M server to send Cancel Observation\n * request. Meanwhile CoAP RFC 7641 states that timeout on notification should\n * cancel it. This setting is to enable both of these behaviors with default\n * focused on keeping observations in case of bad connectivity.\n */\n/* #undef WITH_AVS_COAP_OBSERVE_CANCEL_ON_TIMEOUT */\n\n/**\n * Force cancelling observation, even if a confirmable notification yielding\n * an error response is not acknowledged or rejected with RST by the observer.\n *\n * This is a circumvention for some non-compliant servers that respond with an\n * RST message to a confirmable notification yielding an error response. This\n * setting makes the library cancel the observation in such cases, even though\n * the notification is formally rejected. Additionally, it will also make the\n * library cancel the observation if no response is received at all.\n */\n#define WITH_AVS_COAP_OBSERVE_FORCE_CANCEL_ON_UNACKED_ERROR\n\n/**\n * Enable support for observation persistence (<c>avs_coap_observe_persist()</c>\n * and <c>avs_coap_observe_restore()</c> calls).\n *\n * Only meaningful if <c>WITH_AVS_COAP_OBSERVE</c> is enabled.\n */\n#define WITH_AVS_COAP_OBSERVE_PERSISTENCE\n\n/**\n * Enable support for the streaming API\n */\n#define WITH_AVS_COAP_STREAMING_API\n\n/**\n * Enable support for UDP transport.\n *\n * NOTE: Enabling at least one transport is necessary for the library to be\n * useful.\n */\n#define WITH_AVS_COAP_UDP\n\n/**\n * Enable support for TCP transport (RFC 8323).\n *\n * NOTE: Enabling at least one transport is necessary for the library to be\n * useful.\n */\n#define WITH_AVS_COAP_TCP\n\n/**\n * Enable support for OSCORE (RFC 8613).\n *\n * IMPORTANT: Only available with the OSCORE feature. Ignored in the open\n * source version.\n */\n/* #undef WITH_AVS_COAP_OSCORE */\n\n/**\n * Use OSCORE version from draft-ietf-core-object-security-08 instead of the\n * final version (RFC 8613).\n *\n * Only meaningful if <c>WITH_AVS_COAP_OSCORE</c> is enabled.\n *\n * IMPORTANT: Only available with the OSCORE feature. Ignored in the open\n * source version.\n */\n/* #undef WITH_AVS_COAP_OSCORE_DRAFT_8 */\n\n/**\n * Maximum size in bytes supported for the Master Secret.\n *\n * If editing this file manually, set it to a positive integer literal.\n *\n * The default value defined in CMake build scripts is 32.\n *\n * IMPORTANT: Only available with the OSCORE feature. Ignored in the open\n * source version.\n */\n/* #undef AVS_COAP_OSCORE_MASTER_SECRET_SIZE */\n\n/**\n * Maximum size in bytes supported for the Master Salt.\n *\n * If editing this file manually, set it to a positive integer literal.\n *\n * The default value defined in CMake build scripts is 16.\n *\n * IMPORTANT: Only available with the OSCORE feature. Ignored in the open\n * source version.\n */\n/* #undef AVS_COAP_OSCORE_MASTER_SALT_SIZE */\n\n/**\n * Maximum number of notification tokens stored to match Reset responses to.\n *\n * Only meaningful if <c>WITH_AVS_COAP_OBSERVE</c> and <c>WITH_AVS_COAP_UDP</c>\n * are enabled.\n *\n * If editing this file manually, <c>4</c> shall be\n * replaced with a positive integer literal. The default value defined in CMake\n * build scripts is 4.\n */\n#define AVS_COAP_UDP_NOTIFY_CACHE_SIZE 4\n\n/**\n * Enable sending diagnostic payload in error responses.\n */\n#define WITH_AVS_COAP_DIAGNOSTIC_MESSAGES\n\n/**\n * Enable logging in avs_coap.\n *\n * If this flag is disabled, no logging is compiled into the binary at all.\n */\n#define WITH_AVS_COAP_LOGS\n\n/**\n * Enable TRACE-level logs in avs_coap.\n *\n * Only meaningful if <c>WITH_AVS_COAP_LOGS</c> is enabled.\n */\n#define WITH_AVS_COAP_TRACE_LOGS\n\n/**\n * Enable poisoning of unwanted symbols when compiling avs_coap.\n *\n * Requires a compiler that supports <c>\\#pragma GCC poison</c>.\n *\n * This is mostly useful during development, to ensure that avs_commons do not\n * attempt to call functions considered harmful in this library, such as printf.\n * This is not guaranteed to work as intended on every platform.\n */\n/* #undef WITH_AVS_COAP_POISONING */\n\n#endif // AVS_COAP_CONFIG_H\n"
  },
  {
    "path": "example_configs/linux_lwm2m11/avsystem/commons/avs_commons_config.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AVS_COMMONS_CONFIG_H\n#define AVS_COMMONS_CONFIG_H\n\n/**\n * @file avs_commons_config.h\n *\n * avs_commons library configuration.\n *\n * The preferred way to compile avs_commons is to use CMake, in which case this\n * file will be generated automatically by CMake.\n *\n * However, to provide compatibility with various build systems used especially\n * by embedded platforms, it is alternatively supported to compile avs_commons\n * by other means, in which case this file will need to be provided manually.\n *\n * In the repository, this file is provided as <c>avs_commons_config.h.in</c>,\n * intended to be processed by CMake. If editing this file manually, please copy\n * or rename it to <c>avs_commons_config.h</c> and for each of the\n * <c>#cmakedefine</c> directives, please either replace it with regular\n * <c>#define</c> to enable it, or comment it out to disable. You may also need\n * to replace variables wrapped in <c>@</c> signs with concrete values. Please\n * refer to the comments above each of the specific definition for details.\n *\n * If you are editing a file previously generated by CMake, these\n * <c>#cmakedefine</c>s will be already replaced by either <c>#define</c> or\n * commented out <c>#undef</c> directives.\n */\n\n/**\n * Options that describe capabilities of the build environment.\n *\n * NOTE: If you leave some of these macros undefined, even though the given\n * feature is actually available in the system, avs_commons will attempt to use\n * its own substitutes, which may be incompatible with the definition in the\n * system and lead to undefined behaviour.\n */\n/**@{*/\n/**\n * Is the target platform big-endian?\n *\n * If undefined, little-endian is assumed. Mixed-endian architectures are not\n * supported.\n *\n * Affects <c>avs_convert_be*()</c> and <c>avs_[hn]to[hn]*()</c> calls in\n * avs_utils and, by extension, avs_persistence.\n */\n/* #undef AVS_COMMONS_BIG_ENDIAN */\n\n/**\n * Is GNU __builtin_add_overflow() extension available?\n *\n * Affects time handling functions in avs_utils. If disabled, software overflow\n * checking will be compiled. Note that this software overflow checking code\n * relies on U2 representation of signed integers.\n */\n#define AVS_COMMONS_HAVE_BUILTIN_ADD_OVERFLOW\n\n/**\n * Is GNU __builtin_mul_overflow() extension available?\n *\n * Affects time handling functions in avs_utils. If disabled, software overflow\n * checking will be compiled. Note that this software overflow checking code\n * relies on U2 representation of signed integers.\n */\n#define AVS_COMMONS_HAVE_BUILTIN_MUL_OVERFLOW\n\n/**\n * Is net/if.h available in the system?\n *\n * NOTE: If the header is indeed available, but this option is not defined, the\n * <c>IF_NAMESIZE</c> macro will be defined <strong>publicly by avs_commons\n * headers</strong>, which may conflict with system definitions.\n */\n#define AVS_COMMONS_HAVE_NET_IF_H\n\n/**\n * Are GNU diagnostic pragmas (#pragma GCC diagnostic push/pop/ignored)\n * available?\n *\n * If defined, those pragmas will be used to suppress compiler warnings for some\n * code known to generate them and cannot be improved in a more robust way, e.g.\n * for code that is known to generate warnings from within system headers.\n */\n#define AVS_COMMONS_HAVE_PRAGMA_DIAGNOSTIC\n\n/**\n * Are GNU visibility pragmas (#pragma GCC visibility push/pop) available?\n *\n * Meaningful mostly if avs_commons will be directly or indirectly linked into\n * a shared library. Causes all symbols except those declared in public headers\n * to be hidden, i.e. not exported outside the shared library. If not defined,\n * default compiler visibility settings will be used, but you still may use\n * compiler flags and linker version scripts to replicate this manually if\n * needed.\n */\n#define AVS_COMMONS_HAVE_VISIBILITY\n\n/**\n * Specify an optional compatibility header that allows use of POSIX-specific\n * code that is not compliant with POSIX enough to be compiled directly.\n *\n * This header, if specified, will be included only by the following components,\n * which may be enabled or disabled depending on state of the referenced flags:\n * - avs_compat_threading implementation based on POSIX Threads\n *   (@ref AVS_COMMONS_COMPAT_THREADING_WITH_PTHREAD)\n * - default avs_net socket implementation\n *   (@ref AVS_COMMONS_NET_WITH_POSIX_AVS_SOCKET)\n * - avs_unit (@ref AVS_COMMONS_WITH_AVS_UNIT)\n * - default implementation of avs_time routines\n *   (@ref AVS_COMMONS_UTILS_WITH_POSIX_AVS_TIME)\n *\n * Compatibility headers for lwIP and Microsoft Windows are provided with the\n * library (see the <c>compat</c> directory).\n *\n * If this macro is not defined, the afore-mentioned components, if enabled,\n * will use system headers directly, assuming they are POSIX-compliant.\n *\n * If this macro is enabled, the specified file will be included through an\n * <c>#include AVS_COMMONS_POSIX_COMPAT_HEADER</c> statement. Thus, if editing\n * this file manually, <c></c> shall be\n * replaced with a path to such file.\n */\n/* #undef AVS_COMMONS_POSIX_COMPAT_HEADER */\n\n/**\n * Set if printf implementation doesn't support 64-bit format specifiers.\n * If defined, custom implementation of conversion is used in\n * @c AVS_UINT64_AS_STRING instead of using @c snprintf .\n */\n/* #undef AVS_COMMONS_WITHOUT_64BIT_FORMAT_SPECIFIERS */\n\n/**\n * Set if printf implementation doesn't support floating-point numbers.\n * If defined, custom implementation of conversion is used in\n * @c AVS_DOUBLE_AS_STRING instead of using @c snprintf . This might increase\n * compatibility with some embedded libc implementations that do not provide\n * this functionality.\n *\n * NOTE: In order to keep the custom implementation small in code size, it is\n * not intended to be 100% accurate. Rounding errors may occur - according to\n * empirical checks, they show up around the 16th significant decimal digit.\n */\n/* #undef AVS_COMMONS_WITHOUT_FLOAT_FORMAT_SPECIFIERS */\n/**@}*/\n\n/**\n * Enable poisoning of unwanted symbols when compiling avs_commons.\n *\n * Requires a compiler that supports #pragma GCC poison.\n *\n * This is mostly useful during development, to ensure that avs_commons do not\n * attempt to call functions considered harmful in this library, such as printf.\n * This is not guaranteed to work as intended on every platform, e.g. on macOS\n * it is known to generate false positives due to different dependencies between\n * system headers.\n */\n/* #undef AVS_COMMONS_WITH_POISONING */\n\n/**\n * Options that control compilation of avs_commons components.\n *\n * Each of the configuration options below enables, if defined, one of the core\n * components of the avs_commons library.\n *\n * NOTE: Enabling avs_unit will cause an object file with an implementation of\n * main() to be generated.\n */\n/**@{*/\n#define AVS_COMMONS_WITH_AVS_ALGORITHM\n#define AVS_COMMONS_WITH_AVS_BUFFER\n#define AVS_COMMONS_WITH_AVS_COMPAT_THREADING\n#define AVS_COMMONS_WITH_AVS_CRYPTO\n/* #undef AVS_COMMONS_WITH_AVS_HTTP */\n#define AVS_COMMONS_WITH_AVS_LIST\n#define AVS_COMMONS_WITH_AVS_LOG\n#define AVS_COMMONS_WITH_AVS_NET\n#define AVS_COMMONS_WITH_AVS_PERSISTENCE\n#define AVS_COMMONS_WITH_AVS_RBTREE\n/* #undef AVS_COMMONS_WITH_AVS_SORTED_SET */\n#define AVS_COMMONS_WITH_AVS_SCHED\n#define AVS_COMMONS_WITH_AVS_STREAM\n/* #undef AVS_COMMONS_WITH_AVS_UNIT */\n#define AVS_COMMONS_WITH_AVS_URL\n#define AVS_COMMONS_WITH_AVS_UTILS\n/* #undef AVS_COMMONS_WITH_AVS_VECTOR */\n/**@}*/\n\n/**\n * Options that control compilation of avs_compat_threading implementations.\n *\n * If CMake is not used, in the typical scenario at most one of the following\n * implementations may be enabled at the same time. If none is enabled, the\n * relevant symbols will need to be provided by the user, if used.\n *\n * These are meaningful only if <c>AVS_COMMONS_WITH_AVS_COMPAT_THREADING</c> is\n * defined.\n */\n/**@{*/\n/**\n * Enable implementation based on spinlocks.\n *\n * This implementation is usually very inefficient, and requires C11 stdatomic.h\n * header to be available.\n */\n/* #undef AVS_COMMONS_COMPAT_THREADING_WITH_ATOMIC_SPINLOCK */\n\n/**\n * Enable implementation based on the POSIX Threads library.\n *\n * This implementation is preferred over the spinlock-based one, but the POSIX\n * Threads library is normally available only in UNIX-like environments.\n */\n#define AVS_COMMONS_COMPAT_THREADING_WITH_PTHREAD\n\n/**\n * Is the <c>pthread_condattr_setclock()</c> function available?\n *\n * This flag only makes sense when\n * <c>AVS_COMMONS_COMPAT_THREADING_WITH_PTHREAD</c> is enabled.\n *\n * If this flag is disabled, or if <c>CLOCK_MONOTONIC</c> macro is not\n * available, the <c>avs_condvar_wait()</c> will internally use the real-time\n * clock instead of the monotonic clock. Time values will be converted so that\n * this change does not affect API usage.\n */\n#define AVS_COMMONS_COMPAT_THREADING_PTHREAD_HAVE_PTHREAD_CONDATTR_SETCLOCK\n/**@}*/\n\n/**\n * Options that control compilation of code depending on TLS backend library.\n *\n * If CMake is not used, in the typical scenario at most one of the following\n * DTLS backends may be enabled at the same time. If none is enabled,\n * functionalities that depends on cryptography will be disabled.\n *\n * Affects avs_crypto, avs_net, and avs_stream (for the MD5 implementation).\n *\n * mbed TLS is the main development backend, and is preferred as such. OpenSSL\n * backend supports most functionality as well, but is not as thoroughly tested.\n * TinyDTLS support is only rudimentary.\n */\n/**@{*/\n#define AVS_COMMONS_WITH_MBEDTLS\n/* #undef AVS_COMMONS_WITH_OPENSSL */\n/* #undef AVS_COMMONS_WITH_TINYDTLS */\n\n/**\n * Enable support for custom TLS socket implementation.\n *\n * If enabled, the user needs to provide their own implementations of\n * <c>_avs_net_create_ssl_socket()</c>, <c>_avs_net_create_dtls_socket()</c>,\n * <c>_avs_net_initialize_global_ssl_state() and\n * <c>_avs_net_cleanup_global_ssl_state()</c>.\n */\n/* #undef AVS_COMMONS_WITH_CUSTOM_TLS */\n/**@}*/\n\n/**\n * Options related to avs_crypto.\n */\n/**@{*/\n/**\n * Enable AEAD and HKDF support in avs_crypto. Requires MbedTLS in version at\n * least 2.14.0, OpenSSL in version at least 1.1.0, or custom implementation in\n * case of <c>AVS_COMMONS_WITH_CUSTOM_TLS</c>.\n */\n/* #undef AVS_COMMONS_WITH_AVS_CRYPTO_ADVANCED_FEATURES */\n\n/**\n * If the TLS backend is either mbed TLS or OpenSSL, enables APIs related to\n * public-key cryptography.\n *\n * Public-key cryptography is not currently supported with TinyDTLS.\n *\n * It also enables support for X.509 certificates in avs_net, if that module is\n * also enabled.\n */\n#define AVS_COMMONS_WITH_AVS_CRYPTO_PKI\n\n/**\n * If the TLS backend is either mbed TLS, OpenSSL or TinyDTLS, enables support\n * of pre-shared key security.\n *\n * PSK is the only supported security mode for the TinyDTLS backend, so this\n * option MUST be enabled to utilize it.\n *\n * It also enables support for pre-shared key security in avs_net, if that\n * module is also enabled.\n */\n#define AVS_COMMONS_WITH_AVS_CRYPTO_PSK\n\n/**\n * Enables usage of Valgrind API to suppress some of the false positives\n * generated by the OpenSSL backend.\n */\n/* #undef AVS_COMMONS_WITH_AVS_CRYPTO_VALGRIND */\n\n/**\n * Enables high-level support for hardware-based PKI security, i.e. loading,\n * generating and managing key pairs and certificates via external engines.\n *\n * Requires @ref AVS_COMMONS_WITH_AVS_CRYPTO_PKI to be enabled.\n *\n * An actual implementation is required to use this feature. You may provide\n * your own, or use one of the default ones that come with the HSM engine\n * commercial feature (see @ref AVS_COMMONS_WITH_MBEDTLS_PKCS11_ENGINE,\n * @ref AVS_COMMONS_WITH_MBEDTLS_PSA_ENGINE and\n * @ref AVS_COMMONS_WITH_OPENSSL_PKCS11_ENGINE).\n *\n * The functions that need to be provided in case of a custom implementation:\n * - <c>avs_crypto_pki_engine_certificate_rm()</c>\n * - <c>avs_crypto_pki_engine_certificate_store()</c>\n * - <c>avs_crypto_pki_engine_key_gen()</c>\n * - <c>avs_crypto_pki_engine_key_rm()</c>\n * - <c>avs_crypto_pki_engine_key_store()</c>\n * - When targeting the Mbed TLS backend:\n *   - <c>_avs_crypto_mbedtls_engine_initialize_global_state()</c>\n *   - <c>_avs_crypto_mbedtls_engine_cleanup_global_state()</c>\n *   - <c>_avs_crypto_mbedtls_engine_append_cert()</c>\n *   - <c>_avs_crypto_mbedtls_engine_append_crl()</c>\n *   - <c>_avs_crypto_mbedtls_engine_load_private_key()</c>\n * - When targeting the OpenSSL backend:\n *   - <c>_avs_crypto_openssl_engine_initialize_global_state()</c>\n *   - <c>_avs_crypto_openssl_engine_cleanup_global_state()</c>\n *   - <c>_avs_crypto_openssl_engine_load_certs()</c>\n *   - <c>_avs_crypto_openssl_engine_load_crls()</c>\n *   - <c>_avs_crypto_openssl_engine_load_private_key()</c>\n *\n * External PKI engines are NOT supported in the TinyDTLS backend.\n */\n/* #undef AVS_COMMONS_WITH_AVS_CRYPTO_PKI_ENGINE */\n\n/**\n * Enables high-level support for hardware-based PSK security, i.e. loading\n * and managing PSK keys and identities via external engine.\n *\n * Requires @ref AVS_COMMONS_WITH_AVS_CRYPTO_PKI to be enabled.\n *\n * An actual implementation is required to use this feature. You may provide\n * your own, or use the default PSA-based one that comes with the HSM engine\n * commercial feature (see @ref AVS_COMMONS_WITH_MBEDTLS_PSA_ENGINE).\n *\n * The functions that need to be provided in case of a custom implementation:\n * - <c>avs_crypto_psk_engine_identity_store()</c>\n * - <c>avs_crypto_psk_engine_identity_rm()</c>\n * - <c>avs_crypto_psk_engine_key_store()</c>\n * - <c>avs_crypto_psk_engine_key_rm()</c>\n * - When targeting the Mbed TLS backend:\n *   - <c>_avs_crypto_mbedtls_engine_initialize_global_state()</c>\n *   - <c>_avs_crypto_mbedtls_engine_cleanup_global_state()</c>\n *   - <c>_avs_crypto_mbedtls_engine_load_psk_key()</c>\n *\n * External PSK engines are NOT supported in the OpenSSL and TinyDTLS backend.\n */\n/* #undef AVS_COMMONS_WITH_AVS_CRYPTO_PSK_ENGINE */\n\n/**\n * Enables the default implementation of avs_crypto engine, based on Mbed TLS\n * and PKCS#11.\n *\n * Requires @ref AVS_COMMONS_WITH_AVS_CRYPTO_PKI_ENGINE to be enabled.\n *\n * NOTE: Query string format for this engine is a subset of the PKCS#11 URI\n * scheme (see RFC 7512), modelled after the format accepted by libp11 OpenSSL\n * engine.\n *\n * NOTE: The unit tests for this feature depend on SoftHSM and pkcs11-tool.\n * These must be installed for the tests to pass.\n *\n * IMPORTANT: Only available as part of the HSM support commercial feature.\n * Ignored in the open source version.\n */\n/* #undef AVS_COMMONS_WITH_MBEDTLS_PKCS11_ENGINE */\n\n/**\n * Enables the default implementation of avs_crypto engine, based on Mbed TLS\n * and Platform Security Architecture (PSA).\n *\n * Requires @ref AVS_COMMONS_WITH_AVS_CRYPTO_PKI_ENGINE or\n * @ref AVS_COMMONS_WITH_AVS_CRYPTO_PSK_ENGINE to be enabled.\n *\n * NOTE: Query string format for this engine is:\n *\n * <pre>\n * kid=<key_ID>[,lifetime=<lifetime>]|uid=<persistent_storage_UID>\n * </pre>\n *\n * The values are parsed using strtoull() with base=0, so may be in decimal,\n * 0-prefixed octal or 0x-prefixed hexadecimal. On key generation and\n * certificate storage, the specified lifetime will be used, or lifetime 1\n * (default persistent storage) will be used if not. On key or certificate use,\n * the lifetime of the actual key will be verified if present on the query\n * string and the key will be rejected if different.\n *\n * Certificates are stored as PSA_KEY_TYPE_RAW_DATA key entries containing\n * X.509 DER data. Alternatively, the PSA Protected Storage API can be used if\n * @ref AVS_COMMONS_WITH_MBEDTLS_PSA_ENGINE_PROTECTED_STORAGE is enabled, by\n * using the <c>uid=...</c> syntax.\n *\n * IMPORTANT: Only available as part of the HSM support commercial feature.\n * Ignored in the open source version.\n */\n/* #undef AVS_COMMONS_WITH_MBEDTLS_PSA_ENGINE */\n\n/**\n * Enables support for the PSA Protected Storage API in the PSA-based avs_crypto\n * engine.\n *\n * Requires @ref AVS_COMMONS_WITH_MBEDTLS_PSA_ENGINE to be enabled.\n *\n * IMPORTANT: Only available as part of the HSM support commercial feature.\n * Ignored in the open source version.\n */\n/* #undef AVS_COMMONS_WITH_MBEDTLS_PSA_ENGINE_PROTECTED_STORAGE */\n\n/**\n * Enables use of the <c>psa_generate_random()</c> function as the default\n * random number generator when using the Mbed TLS crypto backend, instead of\n * CTR-DRBG seeded by the Mbed TLS entropy pool.\n *\n * It's meaningful only when @ref AVS_COMMONS_WITH_MBEDTLS is enabled. However,\n * it is independent from the above PSA engine settings.\n */\n/* #undef AVS_COMMONS_WITH_MBEDTLS_PSA_RNG */\n\n/**\n * Is the <c>dlsym()</c> function available?\n *\n * This is currently only used if @ref AVS_COMMONS_WITH_MBEDTLS_PKCS11_ENGINE is\n * enabled. If enabled, the PKCS#11 module is loaded dynamically from a library\n * specified by the <c>PKCS11_MODULE_PATH</c> environment variable. If disabled,\n * a function with the following signature, realizing the PKCS#11\n * <c>C_GetFunctionList</c> method, must be provided manually:\n *\n * <pre>\n * CK_RV _avs_crypto_mbedtls_pkcs11_get_function_list(CK_FUNCTION_LIST_PTR_PTR);\n * </pre>\n */\n#define AVS_COMMONS_HAVE_DLSYM\n\n/**\n * Enables the default implementation of avs_crypto engine, based on OpenSSL and\n * PKCS#11.\n *\n * Requires @ref AVS_COMMONS_WITH_AVS_CRYPTO_PKI_ENGINE to be enabled.\n *\n * NOTE: Query string format for this engine is a subset of the PKCS#11 URI\n * scheme (see RFC 7512), modelled after the format accepted by libp11 OpenSSL\n * engine.\n *\n * NOTE: The unit tests for this feature depend on SoftHSM and pkcs11-tool.\n * These must be installed for the tests to pass.\n *\n * IMPORTANT: Only available as part of the HSM support commercial feature.\n * Ignored in the open source version.\n */\n/* #undef AVS_COMMONS_WITH_OPENSSL_PKCS11_ENGINE */\n/**@}*/\n\n/**\n * Enable support for HTTP content compression in avs_http.\n *\n * Requires linking with zlib.\n */\n/* #undef AVS_COMMONS_HTTP_WITH_ZLIB */\n\n/**\n * Options related to avs_log and logging support within avs_commons.\n */\n/**@{*/\n/* clang-format off */\n/**\n * Size, in bytes, of the avs_log buffer.\n *\n * Log messages that would (including the level, module name and code location)\n * otherwise be longer than this value minus one (for the terminating null\n * character) will be truncated.\n *\n * NOTE: This macro MUST be defined if avs_log is enabled.\n *\n * If editing this file manually, <c>512</c> shall\n * be replaced with a positive integer literal. The default value defined in\n * CMake build scripts is 512.\n */\n#define AVS_COMMONS_LOG_MAX_LINE_LENGTH 512\n/* clang-format on */\n\n/**\n * Configures avs_log to use a synchronized global buffer instead of allocating\n * a buffer on the stack when constructing log messages.\n *\n * Requires avs_compat_threading to be enabled.\n *\n * Enabling this option would reduce the stack space required to use avs_log, at\n * the expense of global storage and the complexity of using a mutex.\n */\n/* #undef AVS_COMMONS_LOG_USE_GLOBAL_BUFFER */\n\n/**\n * Provides a default avs_log handler that prints log messages on stderr.\n *\n * Disabling this option will cause logs to be discarded by default, until a\n * custom log handler is set using <c>avs_log_set_handler()</c>.\n */\n#define AVS_COMMONS_LOG_WITH_DEFAULT_HANDLER\n\n/**\n * Enables the \"micro logs\" feature.\n *\n * Replaces all occurrences of the <c>AVS_DISPOSABLE_LOG()</c> macro with single\n * space strings. This is intended to reduce the size of the compiled code, by\n * stripping it of almost all log string data.\n *\n * Note that this setting will propagate both to avs_commons components\n * themselves (as all its internal logs make use of <c>AVS_DISPOSABLE_LOG()</c>)\n * and the user code that uses it.\n */\n/* #undef AVS_COMMONS_WITH_MICRO_LOGS */\n\n/**\n * Enables logging inside avs_commons.\n *\n * Requires @ref AVS_COMMONS_WITH_AVS_LOG to be enabled.\n *\n * If this macro is not defined at avs_commons compile time, calls to avs_log\n * will not be generated inside avs_commons components.\n */\n#define AVS_COMMONS_WITH_INTERNAL_LOGS\n\n/**\n * Enables TRACE-level logs inside avs_commons.\n *\n * Only meaningful if AVS_COMMONS_WITH_INTERNAL_LOGS is enabled.\n *\n * If this macro is not defined at avs_commons compile time, calls to avs_log\n * with the level set to TRACE will not be generated inside avs_commons\n * components.\n */\n/* #undef AVS_COMMONS_WITH_INTERNAL_TRACE */\n\n/**\n * Enables external implementation of logger subsystem with provided header.\n *\n * Default logger implementation can be found in avs_log_impl.h\n */\n/* #undef AVS_COMMONS_WITH_EXTERNAL_LOGGER_HEADER */\n\n/**\n * If specified, the process of checking if avs_log should be written out\n * takes place in compile time.\n *\n * Specify an optional header with a list of modules for which log level\n * is set. If a log level for specific module is not set, the DEFAULT level\n * will be taken into account. Value of the default logging level is set to\n * DEBUG, but can be overwritten in this header file with AVS_LOG_LEVEL_DEFAULT\n * define. Messages with lower level than the one set will be removed during\n * compile time. Possible values match @ref avs_log_level_t.\n *\n * That file should contain C preprocesor defines in the:\n * - \"#define AVS_LOG_LEVEL_FOR_MODULE_<Module> <Level>\" format,\n *   where <Module> is the module name and <Level> is allowed logging level\n * - \"#define AVS_LOG_LEVEL_DEFAULT <Level>\" format, where <Level> is the\n *   allowed logging level\n *\n * Example file content:\n *\n * <code>\n * #ifndef AVS_COMMONS_EXTERNAL_LOG_LEVELS_H\n * #define AVS_COMMONS_EXTERNAL_LOG_LEVELS_H\n *\n * // global log level value\n * #define AVS_LOG_LEVEL_DEFAULT INFO\n *\n * //for \"coap\" messages only WARNING and ERROR messages will be present\n * #define AVS_LOG_LEVEL_FOR_MODULE_coap WARNING\n *\n * //logs are disable for \"net\" module\n * #define AVS_LOG_LEVEL_FOR_MODULE_net QUIET\n *\n * #endif\n * </code>\n */\n/* #undef AVS_COMMONS_WITH_EXTERNAL_LOG_LEVELS_HEADER */\n\n/**\n * Disable log level check in runtime. Allows to save at least 1.3kB of memory.\n *\n * The macros avs_log_set_level and avs_log_set_default_level\n * will not be available.\n *\n */\n/* #undef AVS_COMMONS_WITHOUT_LOG_CHECK_IN_RUNTIME */\n/**@}*/\n\n/**\n * Options related to avs_net.\n */\n/**@{*/\n/**\n * Enables support for IPv4 connectivity.\n *\n * At least one of AVS_COMMONS_NET_WITH_IPV4 and AVS_COMMONS_NET_WITH_IPV6\n * MUST be defined if avs_net is enabled.\n */\n#define AVS_COMMONS_NET_WITH_IPV4\n\n/**\n * Enables support for IPv6 connectivity.\n *\n * At least one of AVS_COMMONS_NET_WITH_IPV4 and AVS_COMMONS_NET_WITH_IPV6\n * MUST be defined if avs_net is enabled.\n */\n#define AVS_COMMONS_NET_WITH_IPV6\n\n/**\n * If the TLS backend is set to OpenSSL, enables support for DTLS.\n *\n * DTLS is always enabled for the mbed TLS and TinyDTLS backends.\n */\n/* #undef AVS_COMMONS_NET_WITH_DTLS */\n\n/**\n * Enables debug logs generated by mbed TLS.\n *\n * An avs_log-backed handler, logging for the \"mbedtls\" module on the TRACE\n * level, is installed using <c>mbedtls_ssl_conf_dbg()</c> for each (D)TLS\n * socket created if this option is enabled.\n */\n/* #undef AVS_COMMONS_NET_WITH_MBEDTLS_LOGS */\n\n/**\n * Enables (Pre-)Master-Secret logs generation.\n *\n * These logs contain TLS session secrets that tools like Wireshark can use\n * to decrypt captured TLS traffic.\n *\n * NOTE: This only works with Mbed TLS starting from v3.0.0. If you’re using\n * Mbed TLS earlier than v3.1.0, you must enable MBEDTLS_SSL_EXPORT_KEYS.\n *\n * NOTE: The user must specify the stream to which the logs will be transferred\n * using the avs_mbedtls_set_sslkeylog_stream function.\n */\n/* #undef AVS_COMMONS_NET_WITH_MBEDTLS_SSLKEYLOG */\n\n/**\n * Enables the default implementation of avs_net TCP and UDP sockets.\n *\n * Requires either a UNIX-like operating environment, or a compatibility layer\n * with a high degree of compatibility with standard BSD sockets with an\n * appropriate compatibility header (see @ref AVS_COMMONS_POSIX_COMPAT_HEADER) -\n * lwIP and Winsock are currently supported for this scenario.\n */\n#define AVS_COMMONS_NET_WITH_POSIX_AVS_SOCKET\n\n/**\n * @deprecated This is deprecated API. This API can be removed in future\n * releases, without any notice.\n *\n * Enables support for logging socket communication to file.\n *\n * If this option is enabled, avs_net_socket_debug() can be used to enable\n * logging all communication to a file called DEBUG.log. If disabled,\n * avs_net_socket_debug() will always return an error.\n */\n/* #undef AVS_COMMONS_NET_WITH_SOCKET_LOG */\n\n/**\n * @experimental This is experimental API. This API can change in the future\n * versions without any notice.\n *\n * Enables support for custom net traffic interceptor.\n *\n * If this option is enabled, the user needs to provide their own implementation\n * of avs_net_create_traffic_interceptor() function which can wrap and extend\n * an underlying socket.\n */\n/* #undef AVS_COMMONS_WITH_TRAFFIC_INTERCEPTOR */\n\n/**\n * If the TLS backend is either mbed TLS or OpenSSL, enables support for (D)TLS\n * session persistence.\n *\n * Session persistence is not currently supported for the TinyDTLS backend.\n */\n#define AVS_COMMONS_NET_WITH_TLS_SESSION_PERSISTENCE\n/**@}*/\n\n/**\n * Options related to avs_net's default implementation of TCP and UDP sockets.\n *\n * These options make sense only when @ref AVS_COMMONS_NET_WITH_POSIX_AVS_SOCKET\n * is enabled. They describe capabilities of the Unix-like environment in which\n * the library is built.\n *\n * Note that if @ref AVS_COMMONS_POSIX_COMPAT_HEADER is defined, it might\n * redefine these flags independently of the settings in this file.\n */\n/**@{*/\n/**\n * Is the <c>gai_strerror()</c> function available?\n *\n * Enabling this flag will provide more detailed log messages in case that\n * <c>getaddrinfo()</c> fails. If this flag is disabled, numeric error codes\n * values will be logged.\n */\n#define AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_GAI_STRERROR\n\n/**\n * Is the <c>getifaddrs()</c> function available?\n *\n * Disabling this flag will cause <c>avs_net_socket_interface_name()</c> to use\n * a less optimal implementation based on the <c>SIOCGIFCONF</c> ioctl.\n *\n * If <c>SIOCGIFCONF</c> is not defined, either, then\n * <c>avs_net_socket_interface_name()</c> will always return an error.\n */\n#define AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_GETIFADDRS\n\n/**\n * Is the <c>getnameinfo()</c> function available?\n *\n * Disabling this flag will cause <c>avs_net_socket_receive_from()</c>,\n * <c>avs_net_socket_accept()</c>,\n * <c>avs_net_resolved_endpoint_get_host_port()</c>,\n * <c>avs_net_resolved_endpoint_get_host()</c> and\n * <c>avs_net_resolve_host_simple()</c> to use a custom reimplementation of\n * <c>getnameinfo()</c> based on <c>inet_ntop()</c>.\n */\n#define AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_GETNAMEINFO\n\n/**\n * Is the <c>IN6_IS_ADDR_V4MAPPED</c> macro available and usable?\n *\n * Disabling this flag will cause a custom code that compares IPv6 addresses\n * with the <c>::ffff:0.0.0.0/32</c> mask to be used instead.\n */\n#define AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_IN6_IS_ADDR_V4MAPPED\n\n/**\n * Should be defined if IPv4-mapped IPv6 addresses (<c>::ffff:0.0.0.0/32</c>)\n * are <strong>NOT</strong> supported by the underlying platform.\n *\n * Enabling this flag will prevent avs_net from using IPv4-mapped IPv6 addresses\n * and instead re-open and re-bind the socket if a connection to an IPv4 address\n * is requested on a previously created IPv6 socket.\n *\n * This may result in otherwise redundant <c>socket()</c>, <c>bind()</c> and\n * <c>close()</c> system calls to be performed, but may be necessary for\n * interoperability with some platforms.\n */\n/* #undef AVS_COMMONS_NET_POSIX_AVS_SOCKET_WITHOUT_IN6_V4MAPPED_SUPPORT */\n\n/**\n * Is the <c>inet_ntop()</c> function available?\n *\n * Disabling this flag will cause an internal implementation of this function\n * adapted from BIND 4.9.4 to be used instead.\n */\n#define AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_INET_NTOP\n\n/**\n * Is the <c>poll()</c> function available?\n *\n * Disabling this flag will cause a less robust code based on <c>select()</c> to\n * be used instead.\n */\n#define AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_POLL\n\n/**\n * Is the <c>recvmsg()</c> function available?\n *\n * Disabling this flag will cause <c>recvfrom()</c> to be used instead. Note\n * that for UDP sockets, this will cause false positives for datagram truncation\n * detection (<c>AVS_EMSGSIZE</c>) to be reported when the received message is\n * exactly the size of the buffer.\n */\n#define AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_RECVMSG\n/**@}*/\n\n/**\n * Enable thread safety in avs_sched.\n *\n * Makes all scheduler accesses synchronized and thread-safe, at the cost of\n * requiring avs_compat_threading to be enabled, and higher resource usage.\n */\n#define AVS_COMMONS_SCHED_THREAD_SAFE\n\n/**\n * Enable support for file I/O in avs_stream.\n *\n * Disabling this flag will cause the functions declared in\n * <c>avs_stream_file.h</c> to not be defined.\n */\n#define AVS_COMMONS_STREAM_WITH_FILE\n\n/**\n * Enable usage of <c>backtrace()</c> and <c>backtrace_symbols()</c> when\n * reporting assertion failures from avs_unit.\n *\n * Requires the afore-mentioned GNU-specific functions to be available.\n *\n * If this flag is disabled, stack traces will not be displayed with assertion\n * failures.\n */\n#define AVS_COMMONS_UNIT_POSIX_HAVE_BACKTRACE\n\n/**\n * Options related to avs_utils.\n */\n/**@{*/\n/**\n * Enable the default implementation of avs_time_real_now() and\n * avs_time_monotonic_now().\n *\n * Requires an operating environment that supports a clock_gettime() call\n * compatible with POSIX.\n */\n#define AVS_COMMONS_UTILS_WITH_POSIX_AVS_TIME\n\n/**\n * Enable the default implementation of avs_malloc(), avs_free(), avs_calloc()\n * and avs_realloc() that forwards to system malloc(), free(), calloc() and\n * realloc() calls.\n *\n * You might disable this option if for any reason you need to use a custom\n * allocator.\n */\n#define AVS_COMMONS_UTILS_WITH_STANDARD_ALLOCATOR\n\n/**\n * Enable the alternate implementation of avs_malloc(), avs_free(), avs_calloc()\n * and avs_realloc() that uses system malloc(), free() and realloc() calls, but\n * includes additional fixup code that ensures proper alignment to\n * <c>AVS_ALIGNOF(avs_max_align_t)</c> (usually 8 bytes on common platforms).\n *\n * <c>AVS_COMMONS_UTILS_WITH_STANDARD_ALLOCATOR</c> and\n * <c>AVS_COMMONS_UTILS_WITH_ALIGNFIX_ALLOCATOR</c> cannot be enabled at the\n * same time.\n *\n * NOTE: This implementation is only intended for platforms where the system\n * allocator does not properly conform to the alignment requirements.\n *\n * It comes with an additional runtime costs:\n *\n * - <c>AVS_ALIGNOF(avs_max_align_t)</c> bytes (usually 8) of additional\n *   overhead for each allocated memory block\n * - Additional memmove() for every realloc() that returned a block that is not\n *   properly aligned\n * - avs_calloc() is implemented as avs_malloc() followed by an explicit\n *   memset(); this may be suboptimal on some platforms\n *\n * If these costs are unacceptable for you, you may want to consider fixing,\n * replacing or reconfiguring your system allocator for conformance, or\n * implementing a custom one instead.\n *\n * Please note that some code in avs_commons and dependent projects (e.g. Anjay)\n * may include runtime assertions for proper memory alignment that will be\n * triggered when using a non-conformant standard allocator. Such allocators are\n * relatively common in embedded SDKs. This \"alignfix\" allocator is intended to\n * work around these issues. On some platforms (e.g. x86) those alignment issues\n * may not actually cause any problems - so you may want to consider disabling\n * runtime assertions instead. Please carefully examine your target platform's\n * alignment requirements and behavior of misaligned memory accesses (including\n * 64-bit data types such as <c>int64_t</c> and <c>double</c>) before doing so.\n */\n/* #undef AVS_COMMONS_UTILS_WITH_ALIGNFIX_ALLOCATOR */\n\n/**@}*/\n\n#endif /* AVS_COMMONS_CONFIG_H */\n"
  },
  {
    "path": "example_configs/linux_lwm2m12/anjay/anjay_config.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ANJAY_CONFIG_H\n#define ANJAY_CONFIG_H\n\n/**\n * @file anjay_config.h\n *\n * Anjay library configuration.\n *\n * The preferred way to compile Anjay is to use CMake, in which case this file\n * will be generated automatically by CMake.\n *\n * However, to provide compatibility with various build systems used especially\n * by embedded platforms, it is alternatively supported to compile Anjay by\n * other means, in which case this file will need to be provided manually.\n *\n * <strong>NOTE: To compile this library without using CMake, you need to\n * configure avs_commons and avs_coap first. Please refer to documentation in\n * the <c>avs_commons_config.h</c> and <c>avs_coap_config.h</c> files (provided\n * in the repositories as <c>avs_commons_config.h.in</c> and\n * <c>avs_coap_config.h.in</c>, respectively) for details.</strong>\n *\n * <strong>Anjay requires the following avs_coap options to be enabled:</strong>\n *\n * - @c WITH_AVS_COAP_UDP and/or @c WITH_AVS_COAP_TCP\n * - @c WITH_AVS_COAP_STREAMING_API\n * - @c WITH_AVS_COAP_BLOCK is highly recommended\n * - @c WITH_AVS_COAP_OBSERVE (if @c ANJAY_WITH_OBSERVE is enabled)\n * - @c WITH_AVS_COAP_OSCORE (if @c ANJAY_WITH_COAP_OSCORE is enabled, available\n *   only as a commercial feature)\n *\n * <strong>Anjay requires the following avs_commons components to be\n * enabled:</strong>\n *\n * - All components required by avs_coap, see <c>avs_coap_config.h</c>\n * - @c avs_algorithm\n * - @c avs_stream\n * - @c avs_url\n * - @c avs_persistence is highly recommended\n * - @c avs_http (if @c ANJAY_WITH_HTTP_DOWNLOAD is enabled)\n * - @c avs_rbtree or @c avs_sorted_set (if @c ANJAY_WITH_OBSERVE or\n *   @c ANJAY_WITH_MODULE_ACCESS_CONTROL is enabled)\n *\n * In the repository, this file is provided as <c>anjay_config.h.in</c>,\n * intended to be processed by CMake. If editing this file manually, please copy\n * or rename it to <c>anjay_config.h</c> and for each of the\n * <c>\\#cmakedefine</c> directives, please either replace it with regular\n * <c>\\#define</c> to enable it, or comment it out to disable. You may also need\n * to replace variables wrapped in <c>\\@</c> signs with concrete values. Please\n * refer to the comments above each of the specific definition for details.\n *\n * If you are editing a file previously generated by CMake, these\n * <c>\\#cmakedefine</c>s will be already replaced by either <c>\\#define</c> or\n * commented out <c>\\#undef</c> directives.\n */\n\n/**\n * Enable logging in Anjay.\n *\n * If this flag is disabled, no logging is compiled into the binary at all.\n */\n#define ANJAY_WITH_LOGS\n\n/**\n * Enable TRACE-level logs in Anjay.\n *\n * Only meaningful if <c>ANJAY_WITH_LOGS</c> is enabled.\n */\n#define ANJAY_WITH_TRACE_LOGS\n\n/**\n * Enable core support for Access Control mechanisms.\n *\n * Requires separate implementation of the Access Control object itself.\n * Either the implementation available as part of\n * <c>ANJAY_WITH_MODULE_ACCESS_CONTROL</c>, or a custom application-provided one\n * may be used.\n */\n#define ANJAY_WITH_ACCESS_CONTROL\n\n/**\n * Enable automatic attribute storage.\n *\n * Requires <c>AVS_COMMONS_WITH_AVS_PERSISTENCE</c> to be enabled in avs_commons\n * configuration.\n */\n#define ANJAY_WITH_ATTR_STORAGE\n\n/**\n * Enable support for the <c>anjay_download()</c> API.\n */\n#define ANJAY_WITH_DOWNLOADER\n\n/**\n * Enable support for CoAP(S) downloads.\n *\n * Only meaningful if <c>ANJAY_WITH_DOWNLOADER</c> is enabled.\n */\n#define ANJAY_WITH_COAP_DOWNLOAD\n\n/**\n * Enable support for HTTP(S) downloads.\n *\n * Only meaningful if <c>ANJAY_WITH_DOWNLOADER</c> is enabled.\n */\n/* #undef ANJAY_WITH_HTTP_DOWNLOAD */\n\n/**\n * Enable support for the LwM2M Bootstrap Interface.\n */\n#define ANJAY_WITH_BOOTSTRAP\n\n/**\n * Enable support for the LwM2M Bootstrap-Pack operation.\n *\n * Requires <c>ANJAY_WITH_BOOTSTRAP</c> and <c>ANJAY_WITH_LWM2M12</c> to be\n * enabled.\n */\n#define ANJAY_WITH_BOOTSTRAP_PACK\n\n/**\n * Enable support for the LwM2M Discover operation.\n *\n * Note that it is required for full compliance with the LwM2M protocol.\n */\n#define ANJAY_WITH_DISCOVER\n\n/**\n * Enable support for the LwM2M Information Reporting interface (Observe and\n * Notify operations).\n *\n * Requires <c>WITH_AVS_COAP_OBSERVE</c> to be enabled in avs_coap\n * configuration.\n *\n * Note that it is required for full compliance with the LwM2M protocol.\n */\n#define ANJAY_WITH_OBSERVE\n\n/**\n * Enable support for measuring amount of LwM2M traffic\n * (<c>anjay_get_tx_bytes()</c>, <c>anjay_get_rx_bytes()</c>,\n * <c>anjay_get_num_incoming_retransmissions()</c> and\n * <c>anjay_get_num_outgoing_retransmissions()</c> APIs.\n */\n#define ANJAY_WITH_NET_STATS\n\n/**\n * Enable support for communication timestamp\n * (<c>anjay_get_server_last_registration_time()</c>\n * <c>anjay_get_server_next_update_time()</c> and\n * <c>anjay_get_server_last_communication_time()</c>) APIs.\n */\n#define ANJAY_WITH_COMMUNICATION_TIMESTAMP_API\n\n/**\n * Enable support for the <c>anjay_resource_observation_status()</c> API.\n */\n#define ANJAY_WITH_OBSERVATION_STATUS\n\n/**\n * Maximum number of servers observing a given Resource listed by\n * <c>anjay_resource_observation_status()</c> function.\n *\n * Only meaningful if <c>ANJAY_WITH_OBSERVATION_STATUS</c> is enabled.\n */\n#define ANJAY_MAX_OBSERVATION_SERVERS_REPORTED_NUMBER 0\n\n/**\n * Enable guarding of all accesses to <c>anjay_t</c> with a mutex.\n */\n#define ANJAY_WITH_THREAD_SAFETY\n\n/**\n * Enable standard implementation of an event loop.\n *\n * Requires C11 <c>stdatomic.h</c> header to be available, and either a platform\n * that provides a BSD-compatible socket API, or a compatibility layer file (see\n * <c>AVS_COMMONS_POSIX_COMPAT_HEADER</c> in <c>avs_commons_config.h</c>). This\n * is designed to best work with the defalt implementation of avs_net sockets\n * (see <c>AVS_COMMONS_NET_WITH_POSIX_AVS_SOCKET</c>), but alternative socket\n * implementations can also be used.\n *\n * The event loop is most useful when thread safety features\n * (@ref ANJAY_WITH_THREAD_SAFETY and <c>AVS_COMMONS_SCHED_THREAD_SAFE</c>) are\n * enabled as well, but this is not a hard requirement. See the documentation\n * for <c>anjay_event_loop_run()</c> for details.\n */\n#define ANJAY_WITH_EVENT_LOOP\n\n/**\n * Enable support for features new to LwM2M protocol version 1.1.\n */\n#define ANJAY_WITH_LWM2M11\n\n/**\n * Enable support for features new to LwM2M protocol version 1.2.\n *\n * Requires <c>ANJAY_WITH_LWM2M11</c> to be enabled.\n */\n#define ANJAY_WITH_LWM2M12\n\n/**\n * Enable support for OSCORE-based security for LwM2M connections.\n *\n * Requires <c>ANJAY_WITH_LWM2M11</c> to be enabled, and\n * <c>WITH_AVS_COAP_OSCORE</c> to be enabled in avs_coap configuration.\n *\n * IMPORTANT: Only available as part of the OSCORE commercial feature. Ignored\n * in the open source version.\n */\n/* #undef ANJAY_WITH_COAP_OSCORE */\n\n/**\n * Enable support for Observation Attributes.\n *\n * Requires <c>ANJAY_WITH_OBSERVE</c> and <c>ANJAY_WITH_LWM2M12</c> to be\n * enabled.\n */\n#define ANJAY_WITH_OBSERVATION_ATTRIBUTES\n\n/**\n * Enable support for the LwM2M Send operation.\n *\n * Requires <c>ANJAY_WITH_LWM2M11</c> and either <c>ANJAY_WITH_SENML_JSON</c> or\n * <c>ANJAY_WITH_CBOR</c> to be enabled.\n */\n#define ANJAY_WITH_SEND\n\n/**\n * Enable support for the SMS binding and the SMS trigger mechanism.\n *\n * Requires <c>WITH_AVS_COAP_UDP</c> to be enabled in avs_coap configuration.\n *\n * IMPORTANT: Only available as part of the SMS commercial feature. Ignored in\n * the open source version.\n */\n/* #undef ANJAY_WITH_SMS */\n\n/**\n * Enable support for sending and receiving multipart SMS messages.\n *\n * Requires <c>ANJAY_WITH_SMS</c> to be enabled.\n *\n * IMPORTANT: Only available as part of the SMS commercial feature. Ignored in\n * the open source version.\n */\n/* #undef ANJAY_WITH_SMS_MULTIPART */\n\n/**\n * Enable support for Non-IP Data Delivery.\n *\n * Requires <c>ANJAY_WITH_LWM2M11</c> to be enabled, and\n * <c>WITH_AVS_COAP_UDP</c> to be enabled in avs_coap configuration.\n *\n * IMPORTANT: Only available as a commercial feature. Ignored in the open\n * source version.\n */\n/* #undef ANJAY_WITH_NIDD */\n\n/**\n * Enable support for core state persistence\n * (<c>anjay_new_from_core_persistence()</c> and\n * <c>anjay_delete_with_core_persistence()</c> APIs).\n *\n * Requires <c>ANJAY_WITH_OBSERVE</c> to be enabled,\n * <c>AVS_COMMONS_WITH_AVS_PERSISTENCE</c> to be enabled in avs_commons, and\n * <c>WITH_AVS_COAP_OBSERVE_PERSISTENCE</c> to be enabled in avs_coap\n * configuration.\n *\n * IMPORTANT: Only available as a commercial feature. Ignored in the open\n * source version.\n */\n/* #undef ANJAY_WITH_CORE_PERSISTENCE */\n\n/**\n * Disable automatic closing of server connection sockets after\n * MAX_TRANSMIT_WAIT of inactivity.\n */\n/* #undef ANJAY_WITHOUT_QUEUE_MODE_AUTOCLOSE */\n\n/**\n * Enable support for CoAP Content-Format numerical values 1541-1544 that have\n * been used before final LwM2M TS 1.0.\n */\n/* #undef ANJAY_WITH_LEGACY_CONTENT_FORMAT_SUPPORT */\n\n/**\n * Enable support for JSON format as specified in LwM2M TS 1.0.\n *\n * NOTE: Anjay is only capable of generating this format, there is no parsing\n * support regardless of the state of this option.\n */\n#define ANJAY_WITH_LWM2M_JSON\n\n/**\n * Disable support for TLV format as specified in LwM2M TS 1.0.\n *\n * NOTE: LwM2M Client using LwM2M 1.0 MUST support this format. It may be\n * disabled if LwM2M 1.1 is used and SenML JSON or SenML CBOR is enabled.\n */\n/* #undef ANJAY_WITHOUT_TLV */\n\n/**\n * Disable support for Plain Text format as specified in LwM2M TS 1.0 and 1.1.\n *\n * NOTE: LwM2M Client SHOULD support this format. It may be disabled to reduce\n * library size if LwM2M Server is configured to not use it in requests.\n */\n/* #undef ANJAY_WITHOUT_PLAINTEXT */\n\n/**\n * Disable use of the Deregister message.\n */\n/* #undef ANJAY_WITHOUT_DEREGISTER */\n\n/**\n * Disable support for \"IP stickiness\", i.e. preference of the same IP address\n * when reconnecting to a server using a domain name.\n */\n/* #undef ANJAY_WITHOUT_IP_STICKINESS */\n\n/**\n * Enable support for SenML JSON format, as specified in LwM2M TS 1.1.\n *\n * NOTE: As opposed to <c>ANJAY_WITH_LWM2M_JSON</c>, both generating and parsing\n * is supported.\n *\n * Requires <c>ANJAY_WITH_LWM2M11</c> to be enabled.\n */\n#define ANJAY_WITH_SENML_JSON\n\n/**\n * Enable support for CBOR and SenML CBOR formats, as specified in LwM2M TS 1.1.\n *\n * Requires <c>ANJAY_WITH_LWM2M11</c> to be enabled.\n */\n#define ANJAY_WITH_CBOR\n\n/**\n * Enable support for Enrollment over Secure Transport.\n *\n * Requires <c>ANJAY_WITH_BOOTSTRAP</c> to be enabled.\n *\n * IMPORTANT: Only available as part of the EST commercial feature. Ignored in\n * the open source version.\n */\n/* #undef ANJAY_WITH_EST */\n\n/**\n * Enable support for hardware security engine in the EST subsystem.\n *\n * Requires <c>ANJAY_WITH_EST</c> to be enabled in Anjay configuration and\n * <c>AVS_COMMONS_WITH_AVS_CRYPTO_PKI_ENGINE</c> to be enabled in avs_commons\n * configuration.\n *\n * IMPORTANT: Only available in commercial versions that include both the EST\n * and HSM features. Ignored in versions distributed without these features.\n */\n/* #undef ANJAY_WITH_EST_ENGINE_SUPPORT */\n\n/**\n * Enable support for the Confirmable Notification attribute, as specified in\n * LwM2M TS 1.2.\n *\n * Before TS 1.2, this has been supported in Anjay as a custom extension, and\n * thus it is available independently from TS 1.2 support itself, including in\n * the open source version.\n *\n * Requires <c>ANJAY_WITH_OBSERVE</c> to be enabled.\n */\n#define ANJAY_WITH_CON_ATTR\n\n/**\n * Enable support for handling security credentials in the data model using\n * structured <c>avs_crypto</c> types.\n *\n * If the <c>security</c> module is also enabled (see @ref\n * ANJAY_WITH_MODULE_SECURITY), it also enables support for passing these\n * credentials through such structured types when adding Security object\n * instances via the @ref anjay_security_instance_t structure.\n */\n#define ANJAY_WITH_SECURITY_STRUCTURED\n\n/**\n * Maximum size in bytes supported for the \"Public Key or Identity\" resource in\n * the LwM2M Security object.\n *\n * If editing this file manually, <c>2048</c> shall be\n * replaced with a positive integer literal.\n *\n * The default value defined in CMake build scripts is 2048.\n * Minimal suggested setting for low-resource builds is 256.\n */\n#define ANJAY_MAX_PK_OR_IDENTITY_SIZE 2048\n\n/**\n * Maximum size in bytes supported for the \"Secret Key\" resource in the LwM2M\n * Security Object.\n *\n * If editing this file manually, <c>256</c> shall be replaced\n * with a positive integer literal.\n *\n * The default value defined in CMake build scripts is 256.\n * Minimal suggested setting for low-resource builds is 128.\n */\n#define ANJAY_MAX_SECRET_KEY_SIZE 256\n\n/**\n * Maximum length supported for stringified floating-point values.\n *\n * Used when parsing plaintext and SenML JSON content formats - when parsing a\n * floating-point value, any string of length equal or greater than this setting\n * will automatically be considered invalid, even if it could in theory be\n * parsed as a valid number.\n *\n * If editing this file manually, <c>512</c> shall be\n * replaced with a positive integer literal.\n *\n * The default value defined in CMake build scripts is 512.\n * Minimal suggested setting for low-resource builds is 64.\n */\n#define ANJAY_MAX_DOUBLE_STRING_SIZE 512\n\n/**\n * Maximum length supported for a single Uri-Path or Location-Path segment.\n *\n * When handling incoming CoAP messages, any Uri-Path or Location-Path option of\n * length equal or greater than this setting will be considered invalid.\n *\n * If editing this file manually, <c>256</c> shall be\n * replaced with a positive integer literal.\n *\n * The default value defined in CMake build scripts is 256.\n * Minimal suggested setting for low-resource builds is 64.\n */\n#define ANJAY_MAX_URI_SEGMENT_SIZE 256\n\n/**\n * Maximum length supported for a single Uri-Query segment.\n *\n * When handling incoming CoAP messages, any Uri-Query option of length equal or\n * greater than this setting will be considered invalid.\n *\n * If editing this file manually, <c>256</c> shall be\n * replaced with a positive integer literal.\n *\n * The default value defined in CMake build scripts is 256.\n * Minimal suggested setting for low-resource builds is 64.\n */\n#define ANJAY_MAX_URI_QUERY_SEGMENT_SIZE 256\n\n/**\n * Size of buffer allocated for storing DTLS session state when connection is\n * not in use (e.g. during queue mode operation).\n *\n * If editing this file manually, <c>1024</c> shall be\n * replaced with a positive integer literal. The default value defined in CMake\n * build scripts is 1024.\n */\n#define ANJAY_DTLS_SESSION_BUFFER_SIZE 1024\n\n/**\n * Value of Content-Format used in Send messages. Only a few specific values are\n * supported:\n *\n * - @c AVS_COAP_FORMAT_NONE means no default value is used and Anjay will\n *   decide the format based on the what is available.\n * - @c AVS_COAP_FORMAT_OMA_LWM2M_CBOR Anjay will generate a Send message in\n *   LwM2M CBOR format.\n * - @c AVS_COAP_FORMAT_SENML_CBOR Anjay will generate a Send message in SenML\n *   CBOR format.\n * - @c AVS_COAP_FORMAT_SENML_JSON Anjay will generate a Send message in SenML\n *   JSON format.\n *\n * Note that to use a specific format it must be available during compilation.\n *\n * The default value defined in CMake build scripts is\n * <c>AVS_COAP_FORMAT_NONE</c>.\n */\n#define ANJAY_DEFAULT_SEND_FORMAT AVS_COAP_FORMAT_NONE\n\n/**\n * Optional Anjay modules.\n */\n/**@{*/\n/**\n * Enable access_control module (implementation of the Access Control object).\n *\n * Requires <c>ANJAY_WITH_ACCESS_CONTROL</c> to be enabled.\n */\n#define ANJAY_WITH_MODULE_ACCESS_CONTROL\n\n/**\n * Enable security module (implementation of the LwM2M Security object).\n */\n#define ANJAY_WITH_MODULE_SECURITY\n\n/**\n * Enable support for hardware security engine in the security module.\n *\n * This feature allows security credentials provisioned into the LwM2M Security\n * object to be automatically moved into a hardware security module.\n *\n * Requires <c>ANJAY_WITH_MODULE_SECURITY</c> to be enabled in Anjay\n * configuration, and at least one of\n * <c>AVS_COMMONS_WITH_AVS_CRYPTO_PKI_ENGINE</c> or\n * <c>AVS_COMMONS_WITH_AVS_CRYPTO_PSK_ENGINE</c> to be enabled in avs_commons\n * configuration.\n *\n * IMPORTANT: Only available as part of the HSM support commercial feature.\n * Ignored in versions distributed without this feature.\n */\n/* #undef ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT */\n\n/**\n * Enable server module (implementation of the LwM2M Server object).\n */\n#define ANJAY_WITH_MODULE_SERVER\n\n/**\n * Enable fw_update module (implementation of the Firmware Update object).\n */\n#define ANJAY_WITH_MODULE_FW_UPDATE\n\n/**\n * Enable advanced_fw_update module (implementation of the 33629 custom\n * Advanced Firmware Update object).\n */\n/* #undef ANJAY_WITH_MODULE_ADVANCED_FW_UPDATE */\n\n/**\n * Disable support for PUSH mode Firmware Update.\n *\n * Only meaningful if <c>ANJAY_WITH_MODULE_FW_UPDATE</c> is enabled. Requires\n * <c>ANJAY_WITH_DOWNLOADER</c> to be enabled.\n */\n/* #undef ANJAY_WITHOUT_MODULE_FW_UPDATE_PUSH_MODE */\n\n/**\n * Enable sw_mgmt module (implementation of the Software Management object).\n */\n/* #undef ANJAY_WITH_MODULE_SW_MGMT */\n\n/**\n * Enables ipso_objects module (generic implementation of basic sensor, three\n * axis sensor and Push Button IPSO objects).\n */\n#define ANJAY_WITH_MODULE_IPSO_OBJECTS\n\n/**\n * Enables experimental ipso_objects_v2 module (generic implementation of basic\n * sensor and three axis sensor IPSO objects).\n */\n#define ANJAY_WITH_MODULE_IPSO_OBJECTS_V2\n\n/**\n * Enable at_sms module (implementation of an SMS driver for AT modem devices).\n *\n * Requires <c>ANJAY_WITH_SMS</c> to be enabled and the operating system to\n * support the POSIX <c>poll()</c> function.\n *\n * IMPORTANT: Only available as part of the SMS commercial feature. Ignored in\n * the open source version.\n */\n/* #undef ANJAY_WITH_MODULE_AT_SMS */\n\n/**\n * Enable bg96_nidd module (implementation of NB-IoT-based NIDD driver for\n * Quectel BG96).\n *\n * Requires <c>ANJAY_WITH_NIDD</c> to be enabled.\n *\n * IMPORTANT: Only available as part of the NIDD commercial feature. Ignored\n * in the open source version.\n */\n/* #undef ANJAY_WITH_MODULE_BG96_NIDD */\n\n/**\n * Enable bootstrapper module (loader for bootstrap information formatted as per\n * the \"Storage of LwM2M Bootstrap Information on the Smartcard\" appendix to the\n * LwM2M TS).\n *\n * Requires <c>ANJAY_WITH_BOOTSTRAP</c> to be enabled and\n * <c>AVS_COMMONS_WITH_AVS_PERSISTENCE</c> to be enabled in avs_commons\n * configuration.\n *\n * IMPORTANT: Only available with the bootstrapper feature. Ignored in the open\n * source version.\n */\n/* #undef ANJAY_WITH_MODULE_BOOTSTRAPPER */\n\n/**\n * Enable the SIM bootstrap module, which enables reading the SIM bootstrap\n * information from a smartcard, which can then be passed through to the\n * bootstrapper module.\n *\n * Requires <c>ANJAY_WITH_MODULE_BOOTSTRAPPER</c> to be enabled.\n *\n * IMPORTANT: Only available with the bootstrapper feature. Ignored in the open\n * source version.\n */\n/* #undef ANJAY_WITH_MODULE_SIM_BOOTSTRAP */\n\n/**\n * Forced ID of the file to read the SIM bootstrap information from.\n *\n * If not defined (default), the bootstrap information file will be discovered\n * through the ODF file, as mandated by the specification.\n *\n * Requires <c>ANJAY_WITH_MODULE_BOOTSTRAPPER</c> to be enabled. At most one of\n * <c>ANJAY_MODULE_SIM_BOOTSTRAP_HARDCODED_FILE_ID</c> and\n * <c>ANJAY_MODULE_SIM_BOOTSTRAP_DATA_OID_OVERRIDE_HEX</c> may be defined at the\n * same time.\n *\n * IMPORTANT: Only available with the bootstrapper feature. Ignored in the open\n * source version.\n */\n/* #undef ANJAY_MODULE_SIM_BOOTSTRAP_HARDCODED_FILE_ID */\n\n/**\n * Overridden OID of the SIM bootstrap information to look for in the DODF file,\n * expressed as a hexlified DER representation (without the header).\n *\n * This is the hexlified expected value of the 'id' field within the 'OidDO'\n * sequence in the DODF file (please refer to the PKCS #15 document for more\n * information).\n *\n * If not defined, the default value of <c>\"672b0901\"</c>, which corresponds to\n * OID 2.23.43.9.1 {joint-iso-itu-t(2) international-organizations(23) wap(43)\n * oma-lwm2m(9) lwm2m-bootstrap(1)}, will be used.\n *\n * No other values than the default are valid according to the specification,\n * but some SIM cards are known to use other non-standard values, e.g.\n * <c>\"0604672b0901\"</c> - including a superfluous nested BER-TLV header, as\n * erroneously illustrated in the EF(DODF-bootstrap) file coding example in\n * LwM2M TS 1.2 and earlier (fixed in LwM2M TS 1.2.1) - which is interpreted as\n * OID 0.6.4.103.43.9.1 (note that it is invalid as the 0.6 tree does not exist\n * in the repository as of writing this note).\n *\n * Requires <c>ANJAY_WITH_MODULE_BOOTSTRAPPER</c> to be enabled. At most one of\n * <c>ANJAY_MODULE_SIM_BOOTSTRAP_HARDCODED_FILE_ID</c> and\n * <c>ANJAY_MODULE_SIM_BOOTSTRAP_DATA_OID_OVERRIDE_HEX</c> may be defined at the\n * same time.\n *\n * IMPORTANT: Only available with the bootstrapper feature. Ignored in the open\n * source version.\n */\n/* #undef ANJAY_MODULE_SIM_BOOTSTRAP_DATA_OID_OVERRIDE_HEX */\n\n/**\n * Enable factory provisioning module. Data provided during provisioning uses\n * SenML CBOR format.\n */\n#define ANJAY_WITH_MODULE_FACTORY_PROVISIONING\n\n/**\n * Enable oscore module (implementation of the OSCORE object).\n *\n * IMPORTANT: Only available as part of the OSCORE commercial feature. Ignored\n * in the open source version.\n */\n/* #undef ANJAY_WITH_MODULE_OSCORE */\n\n/**\n * If enable Anjay doesn't handle composite operation (read, observe and write).\n * Its use makes sense for LWM2M v1.1 upwards.\n *\n * This flag can be used to reduce the size of the resulting code.\n *\n * If active, anjay will respond with message code 5.01 Not Implemented to any\n * composite type request.\n */\n/* #undef ANJAY_WITHOUT_COMPOSITE_OPERATIONS */\n\n/**\n * Enable support for the experimental\n * <c>anjay_get_server_connection_status()</c> API and related\n * <c>anjay_server_connection_status_cb_t</c> callback.\n */\n#define ANJAY_WITH_CONN_STATUS_API\n\n/**\n * Enable support for the experimental\n * <c>anjay_ssl_error_cb_t</c> callback.\n * Currently only supported for mbedTLS and custom SSL integration builds\n */\n#define ANJAY_WITH_SSL_ERROR_API\n\n/**\n * Enable support for /25 LwM2M Gateway Object.\n *\n * Requires <c>ANJAY_WITH_LWM2M11</c> to be enabled.\n * Requires <c>ANJAY_WITH_CORE_PERSISTENCE</c> (commercial feature) to be\n * disabled.\n */\n/* #undef ANJAY_WITH_LWM2M_GATEWAY */\n\n/**\n * Maximum holdoff time in seconds.\n *\n * This parameter defines an upper limit on the Hold Off Time as specified in\n * the LwM2M Security Object (Object ID: 0, Resource ID: 11). The Hold Off Time\n * is used by the LwM2M Client to delay initiating a Bootstrap process.\n *\n * Limiting the Hold Off Time is important in network environments where\n * prolonged delays may cause NAT bindings to expire, potentially leading to\n * failed connection attempts. This parameter ensures that the configured\n * Hold Off Time does not exceed a safe threshold. If a DTLS binding with\n * Connection ID is used this parameter can be increased safely.\n *\n * If editing this file manually, <c>20</c> shall be\n * replaced with a positive integer literal representing the time in seconds.\n * The default value defined in CMake build scripts is 20.\n */\n#define ANJAY_MAX_HOLDOFF_TIME 20\n\n/**\n * Enable support for optional resources of the Firmware Update object\n * introduced in LwM2M 1.1.\n *\n * This includes the following resources:\n * - Severity\n * - Last State Change Time\n * - Max Defer Period\n *\n * Requires <c>ANJAY_WITH_LWM2M11</c> to be enabled.\n */\n/* #undef ANJAY_WITH_MODULE_FW_UPDATE_V11_RESOURCES */\n\n/**@}*/\n\n#endif // ANJAY_CONFIG_H\n"
  },
  {
    "path": "example_configs/linux_lwm2m12/avsystem/coap/avs_coap_config.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem CoAP library\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef AVS_COAP_CONFIG_H\n#define AVS_COAP_CONFIG_H\n\n/**\n * @file avs_coap_config.h\n *\n * avs_coap library configuration.\n *\n * The preferred way to compile avs_coap is to use CMake, in which case this\n * file will be generated automatically by CMake.\n *\n * However, to provide compatibility with various build systems used especially\n * by embedded platforms, it is alternatively supported to compile avs_coap by\n * other means, in which case this file will need to be provided manually.\n *\n * <strong>NOTE: To compile this library without using CMake, you need to\n * configure avs_commons first. Please refer to documentation in the\n * <c>avs_commons_config.h</c> file (provided in the repository as\n * <c>avs_commons_config.h.in</c>) for details.</strong>\n *\n * <strong>avs_coap requires the following avs_commons components to be\n * enabled:</strong>\n *\n * - @c avs_buffer\n * - @c avs_compat_threading\n * - @c avs_list\n * - @c avs_net\n * - @c avs_sched\n * - @c avs_stream\n * - @c avs_utils\n * - @c avs_log (if @c WITH_AVS_COAP_LOGS is enabled)\n * - @c avs_persistence (if @c WITH_AVS_COAP_OBSERVE_PERSISTENCE is enabled)\n * - @c avs_crypto (if @c WITH_AVS_COAP_OSCORE is enabled)\n *\n * In the repository, this file is provided as <c>avs_coap_config.h.in</c>,\n * intended to be processed by CMake. If editing this file manually, please copy\n * or rename it to <c>avs_coap_config.h</c> and for each of the\n * <c>\\#cmakedefine</c> directives, please either replace it with regular\n * <c>\\#define</c> to enable it, or comment it out to disable. You may also need\n * to replace variables wrapped in <c>\\@</c> signs with concrete values. Please\n * refer to the comments above each of the specific definition for details.\n *\n * If you are editing a file previously generated by CMake, these\n * <c>\\#cmakedefine</c>s will be already replaced by either <c>\\#define</c> or\n * commented out <c>\\#undef</c> directives.\n */\n\n/**\n * Enable support for block-wise transfers (RFC 7959).\n *\n * If this flag is disabled, attempting to send a message bigger than the\n * internal buffer will fail; incoming messages may still carry BLOCK1 or BLOCK2\n * options, but they will not be interpreted by the library in any way.\n */\n#define WITH_AVS_COAP_BLOCK\n\n/**\n * Enable support for observations (RFC 7641).\n */\n#define WITH_AVS_COAP_OBSERVE\n\n/**\n * Turn on cancelling observation on a timeout.\n *\n * Only meaningful if <c>WITH_AVS_COAP_OBSERVE</c> is enabled.\n *\n * NOTE: LwM2M specification requires LwM2M server to send Cancel Observation\n * request. Meanwhile CoAP RFC 7641 states that timeout on notification should\n * cancel it. This setting is to enable both of these behaviors with default\n * focused on keeping observations in case of bad connectivity.\n */\n/* #undef WITH_AVS_COAP_OBSERVE_CANCEL_ON_TIMEOUT */\n\n/**\n * Force cancelling observation, even if a confirmable notification yielding\n * an error response is not acknowledged or rejected with RST by the observer.\n *\n * This is a circumvention for some non-compliant servers that respond with an\n * RST message to a confirmable notification yielding an error response. This\n * setting makes the library cancel the observation in such cases, even though\n * the notification is formally rejected. Additionally, it will also make the\n * library cancel the observation if no response is received at all.\n */\n#define WITH_AVS_COAP_OBSERVE_FORCE_CANCEL_ON_UNACKED_ERROR\n\n/**\n * Enable support for observation persistence (<c>avs_coap_observe_persist()</c>\n * and <c>avs_coap_observe_restore()</c> calls).\n *\n * Only meaningful if <c>WITH_AVS_COAP_OBSERVE</c> is enabled.\n */\n#define WITH_AVS_COAP_OBSERVE_PERSISTENCE\n\n/**\n * Enable support for the streaming API\n */\n#define WITH_AVS_COAP_STREAMING_API\n\n/**\n * Enable support for UDP transport.\n *\n * NOTE: Enabling at least one transport is necessary for the library to be\n * useful.\n */\n#define WITH_AVS_COAP_UDP\n\n/**\n * Enable support for TCP transport (RFC 8323).\n *\n * NOTE: Enabling at least one transport is necessary for the library to be\n * useful.\n */\n#define WITH_AVS_COAP_TCP\n\n/**\n * Enable support for OSCORE (RFC 8613).\n *\n * IMPORTANT: Only available with the OSCORE feature. Ignored in the open\n * source version.\n */\n/* #undef WITH_AVS_COAP_OSCORE */\n\n/**\n * Use OSCORE version from draft-ietf-core-object-security-08 instead of the\n * final version (RFC 8613).\n *\n * Only meaningful if <c>WITH_AVS_COAP_OSCORE</c> is enabled.\n *\n * IMPORTANT: Only available with the OSCORE feature. Ignored in the open\n * source version.\n */\n/* #undef WITH_AVS_COAP_OSCORE_DRAFT_8 */\n\n/**\n * Maximum size in bytes supported for the Master Secret.\n *\n * If editing this file manually, set it to a positive integer literal.\n *\n * The default value defined in CMake build scripts is 32.\n *\n * IMPORTANT: Only available with the OSCORE feature. Ignored in the open\n * source version.\n */\n/* #undef AVS_COAP_OSCORE_MASTER_SECRET_SIZE */\n\n/**\n * Maximum size in bytes supported for the Master Salt.\n *\n * If editing this file manually, set it to a positive integer literal.\n *\n * The default value defined in CMake build scripts is 16.\n *\n * IMPORTANT: Only available with the OSCORE feature. Ignored in the open\n * source version.\n */\n/* #undef AVS_COAP_OSCORE_MASTER_SALT_SIZE */\n\n/**\n * Maximum number of notification tokens stored to match Reset responses to.\n *\n * Only meaningful if <c>WITH_AVS_COAP_OBSERVE</c> and <c>WITH_AVS_COAP_UDP</c>\n * are enabled.\n *\n * If editing this file manually, <c>4</c> shall be\n * replaced with a positive integer literal. The default value defined in CMake\n * build scripts is 4.\n */\n#define AVS_COAP_UDP_NOTIFY_CACHE_SIZE 4\n\n/**\n * Enable sending diagnostic payload in error responses.\n */\n#define WITH_AVS_COAP_DIAGNOSTIC_MESSAGES\n\n/**\n * Enable logging in avs_coap.\n *\n * If this flag is disabled, no logging is compiled into the binary at all.\n */\n#define WITH_AVS_COAP_LOGS\n\n/**\n * Enable TRACE-level logs in avs_coap.\n *\n * Only meaningful if <c>WITH_AVS_COAP_LOGS</c> is enabled.\n */\n#define WITH_AVS_COAP_TRACE_LOGS\n\n/**\n * Enable poisoning of unwanted symbols when compiling avs_coap.\n *\n * Requires a compiler that supports <c>\\#pragma GCC poison</c>.\n *\n * This is mostly useful during development, to ensure that avs_commons do not\n * attempt to call functions considered harmful in this library, such as printf.\n * This is not guaranteed to work as intended on every platform.\n */\n/* #undef WITH_AVS_COAP_POISONING */\n\n#endif // AVS_COAP_CONFIG_H\n"
  },
  {
    "path": "example_configs/linux_lwm2m12/avsystem/commons/avs_commons_config.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AVS_COMMONS_CONFIG_H\n#define AVS_COMMONS_CONFIG_H\n\n/**\n * @file avs_commons_config.h\n *\n * avs_commons library configuration.\n *\n * The preferred way to compile avs_commons is to use CMake, in which case this\n * file will be generated automatically by CMake.\n *\n * However, to provide compatibility with various build systems used especially\n * by embedded platforms, it is alternatively supported to compile avs_commons\n * by other means, in which case this file will need to be provided manually.\n *\n * In the repository, this file is provided as <c>avs_commons_config.h.in</c>,\n * intended to be processed by CMake. If editing this file manually, please copy\n * or rename it to <c>avs_commons_config.h</c> and for each of the\n * <c>#cmakedefine</c> directives, please either replace it with regular\n * <c>#define</c> to enable it, or comment it out to disable. You may also need\n * to replace variables wrapped in <c>@</c> signs with concrete values. Please\n * refer to the comments above each of the specific definition for details.\n *\n * If you are editing a file previously generated by CMake, these\n * <c>#cmakedefine</c>s will be already replaced by either <c>#define</c> or\n * commented out <c>#undef</c> directives.\n */\n\n/**\n * Options that describe capabilities of the build environment.\n *\n * NOTE: If you leave some of these macros undefined, even though the given\n * feature is actually available in the system, avs_commons will attempt to use\n * its own substitutes, which may be incompatible with the definition in the\n * system and lead to undefined behaviour.\n */\n/**@{*/\n/**\n * Is the target platform big-endian?\n *\n * If undefined, little-endian is assumed. Mixed-endian architectures are not\n * supported.\n *\n * Affects <c>avs_convert_be*()</c> and <c>avs_[hn]to[hn]*()</c> calls in\n * avs_utils and, by extension, avs_persistence.\n */\n/* #undef AVS_COMMONS_BIG_ENDIAN */\n\n/**\n * Is GNU __builtin_add_overflow() extension available?\n *\n * Affects time handling functions in avs_utils. If disabled, software overflow\n * checking will be compiled. Note that this software overflow checking code\n * relies on U2 representation of signed integers.\n */\n#define AVS_COMMONS_HAVE_BUILTIN_ADD_OVERFLOW\n\n/**\n * Is GNU __builtin_mul_overflow() extension available?\n *\n * Affects time handling functions in avs_utils. If disabled, software overflow\n * checking will be compiled. Note that this software overflow checking code\n * relies on U2 representation of signed integers.\n */\n#define AVS_COMMONS_HAVE_BUILTIN_MUL_OVERFLOW\n\n/**\n * Is net/if.h available in the system?\n *\n * NOTE: If the header is indeed available, but this option is not defined, the\n * <c>IF_NAMESIZE</c> macro will be defined <strong>publicly by avs_commons\n * headers</strong>, which may conflict with system definitions.\n */\n#define AVS_COMMONS_HAVE_NET_IF_H\n\n/**\n * Are GNU diagnostic pragmas (#pragma GCC diagnostic push/pop/ignored)\n * available?\n *\n * If defined, those pragmas will be used to suppress compiler warnings for some\n * code known to generate them and cannot be improved in a more robust way, e.g.\n * for code that is known to generate warnings from within system headers.\n */\n#define AVS_COMMONS_HAVE_PRAGMA_DIAGNOSTIC\n\n/**\n * Are GNU visibility pragmas (#pragma GCC visibility push/pop) available?\n *\n * Meaningful mostly if avs_commons will be directly or indirectly linked into\n * a shared library. Causes all symbols except those declared in public headers\n * to be hidden, i.e. not exported outside the shared library. If not defined,\n * default compiler visibility settings will be used, but you still may use\n * compiler flags and linker version scripts to replicate this manually if\n * needed.\n */\n#define AVS_COMMONS_HAVE_VISIBILITY\n\n/**\n * Specify an optional compatibility header that allows use of POSIX-specific\n * code that is not compliant with POSIX enough to be compiled directly.\n *\n * This header, if specified, will be included only by the following components,\n * which may be enabled or disabled depending on state of the referenced flags:\n * - avs_compat_threading implementation based on POSIX Threads\n *   (@ref AVS_COMMONS_COMPAT_THREADING_WITH_PTHREAD)\n * - default avs_net socket implementation\n *   (@ref AVS_COMMONS_NET_WITH_POSIX_AVS_SOCKET)\n * - avs_unit (@ref AVS_COMMONS_WITH_AVS_UNIT)\n * - default implementation of avs_time routines\n *   (@ref AVS_COMMONS_UTILS_WITH_POSIX_AVS_TIME)\n *\n * Compatibility headers for lwIP and Microsoft Windows are provided with the\n * library (see the <c>compat</c> directory).\n *\n * If this macro is not defined, the afore-mentioned components, if enabled,\n * will use system headers directly, assuming they are POSIX-compliant.\n *\n * If this macro is enabled, the specified file will be included through an\n * <c>#include AVS_COMMONS_POSIX_COMPAT_HEADER</c> statement. Thus, if editing\n * this file manually, <c></c> shall be\n * replaced with a path to such file.\n */\n/* #undef AVS_COMMONS_POSIX_COMPAT_HEADER */\n\n/**\n * Set if printf implementation doesn't support 64-bit format specifiers.\n * If defined, custom implementation of conversion is used in\n * @c AVS_UINT64_AS_STRING instead of using @c snprintf .\n */\n/* #undef AVS_COMMONS_WITHOUT_64BIT_FORMAT_SPECIFIERS */\n\n/**\n * Set if printf implementation doesn't support floating-point numbers.\n * If defined, custom implementation of conversion is used in\n * @c AVS_DOUBLE_AS_STRING instead of using @c snprintf . This might increase\n * compatibility with some embedded libc implementations that do not provide\n * this functionality.\n *\n * NOTE: In order to keep the custom implementation small in code size, it is\n * not intended to be 100% accurate. Rounding errors may occur - according to\n * empirical checks, they show up around the 16th significant decimal digit.\n */\n/* #undef AVS_COMMONS_WITHOUT_FLOAT_FORMAT_SPECIFIERS */\n/**@}*/\n\n/**\n * Enable poisoning of unwanted symbols when compiling avs_commons.\n *\n * Requires a compiler that supports #pragma GCC poison.\n *\n * This is mostly useful during development, to ensure that avs_commons do not\n * attempt to call functions considered harmful in this library, such as printf.\n * This is not guaranteed to work as intended on every platform, e.g. on macOS\n * it is known to generate false positives due to different dependencies between\n * system headers.\n */\n/* #undef AVS_COMMONS_WITH_POISONING */\n\n/**\n * Options that control compilation of avs_commons components.\n *\n * Each of the configuration options below enables, if defined, one of the core\n * components of the avs_commons library.\n *\n * NOTE: Enabling avs_unit will cause an object file with an implementation of\n * main() to be generated.\n */\n/**@{*/\n#define AVS_COMMONS_WITH_AVS_ALGORITHM\n#define AVS_COMMONS_WITH_AVS_BUFFER\n#define AVS_COMMONS_WITH_AVS_COMPAT_THREADING\n#define AVS_COMMONS_WITH_AVS_CRYPTO\n/* #undef AVS_COMMONS_WITH_AVS_HTTP */\n#define AVS_COMMONS_WITH_AVS_LIST\n#define AVS_COMMONS_WITH_AVS_LOG\n#define AVS_COMMONS_WITH_AVS_NET\n#define AVS_COMMONS_WITH_AVS_PERSISTENCE\n#define AVS_COMMONS_WITH_AVS_RBTREE\n/* #undef AVS_COMMONS_WITH_AVS_SORTED_SET */\n#define AVS_COMMONS_WITH_AVS_SCHED\n#define AVS_COMMONS_WITH_AVS_STREAM\n/* #undef AVS_COMMONS_WITH_AVS_UNIT */\n#define AVS_COMMONS_WITH_AVS_URL\n#define AVS_COMMONS_WITH_AVS_UTILS\n/* #undef AVS_COMMONS_WITH_AVS_VECTOR */\n/**@}*/\n\n/**\n * Options that control compilation of avs_compat_threading implementations.\n *\n * If CMake is not used, in the typical scenario at most one of the following\n * implementations may be enabled at the same time. If none is enabled, the\n * relevant symbols will need to be provided by the user, if used.\n *\n * These are meaningful only if <c>AVS_COMMONS_WITH_AVS_COMPAT_THREADING</c> is\n * defined.\n */\n/**@{*/\n/**\n * Enable implementation based on spinlocks.\n *\n * This implementation is usually very inefficient, and requires C11 stdatomic.h\n * header to be available.\n */\n/* #undef AVS_COMMONS_COMPAT_THREADING_WITH_ATOMIC_SPINLOCK */\n\n/**\n * Enable implementation based on the POSIX Threads library.\n *\n * This implementation is preferred over the spinlock-based one, but the POSIX\n * Threads library is normally available only in UNIX-like environments.\n */\n#define AVS_COMMONS_COMPAT_THREADING_WITH_PTHREAD\n\n/**\n * Is the <c>pthread_condattr_setclock()</c> function available?\n *\n * This flag only makes sense when\n * <c>AVS_COMMONS_COMPAT_THREADING_WITH_PTHREAD</c> is enabled.\n *\n * If this flag is disabled, or if <c>CLOCK_MONOTONIC</c> macro is not\n * available, the <c>avs_condvar_wait()</c> will internally use the real-time\n * clock instead of the monotonic clock. Time values will be converted so that\n * this change does not affect API usage.\n */\n#define AVS_COMMONS_COMPAT_THREADING_PTHREAD_HAVE_PTHREAD_CONDATTR_SETCLOCK\n/**@}*/\n\n/**\n * Options that control compilation of code depending on TLS backend library.\n *\n * If CMake is not used, in the typical scenario at most one of the following\n * DTLS backends may be enabled at the same time. If none is enabled,\n * functionalities that depends on cryptography will be disabled.\n *\n * Affects avs_crypto, avs_net, and avs_stream (for the MD5 implementation).\n *\n * mbed TLS is the main development backend, and is preferred as such. OpenSSL\n * backend supports most functionality as well, but is not as thoroughly tested.\n * TinyDTLS support is only rudimentary.\n */\n/**@{*/\n#define AVS_COMMONS_WITH_MBEDTLS\n/* #undef AVS_COMMONS_WITH_OPENSSL */\n/* #undef AVS_COMMONS_WITH_TINYDTLS */\n\n/**\n * Enable support for custom TLS socket implementation.\n *\n * If enabled, the user needs to provide their own implementations of\n * <c>_avs_net_create_ssl_socket()</c>, <c>_avs_net_create_dtls_socket()</c>,\n * <c>_avs_net_initialize_global_ssl_state() and\n * <c>_avs_net_cleanup_global_ssl_state()</c>.\n */\n/* #undef AVS_COMMONS_WITH_CUSTOM_TLS */\n/**@}*/\n\n/**\n * Options related to avs_crypto.\n */\n/**@{*/\n/**\n * Enable AEAD and HKDF support in avs_crypto. Requires MbedTLS in version at\n * least 2.14.0, OpenSSL in version at least 1.1.0, or custom implementation in\n * case of <c>AVS_COMMONS_WITH_CUSTOM_TLS</c>.\n */\n/* #undef AVS_COMMONS_WITH_AVS_CRYPTO_ADVANCED_FEATURES */\n\n/**\n * If the TLS backend is either mbed TLS or OpenSSL, enables APIs related to\n * public-key cryptography.\n *\n * Public-key cryptography is not currently supported with TinyDTLS.\n *\n * It also enables support for X.509 certificates in avs_net, if that module is\n * also enabled.\n */\n#define AVS_COMMONS_WITH_AVS_CRYPTO_PKI\n\n/**\n * If the TLS backend is either mbed TLS, OpenSSL or TinyDTLS, enables support\n * of pre-shared key security.\n *\n * PSK is the only supported security mode for the TinyDTLS backend, so this\n * option MUST be enabled to utilize it.\n *\n * It also enables support for pre-shared key security in avs_net, if that\n * module is also enabled.\n */\n#define AVS_COMMONS_WITH_AVS_CRYPTO_PSK\n\n/**\n * Enables usage of Valgrind API to suppress some of the false positives\n * generated by the OpenSSL backend.\n */\n/* #undef AVS_COMMONS_WITH_AVS_CRYPTO_VALGRIND */\n\n/**\n * Enables high-level support for hardware-based PKI security, i.e. loading,\n * generating and managing key pairs and certificates via external engines.\n *\n * Requires @ref AVS_COMMONS_WITH_AVS_CRYPTO_PKI to be enabled.\n *\n * An actual implementation is required to use this feature. You may provide\n * your own, or use one of the default ones that come with the HSM engine\n * commercial feature (see @ref AVS_COMMONS_WITH_MBEDTLS_PKCS11_ENGINE,\n * @ref AVS_COMMONS_WITH_MBEDTLS_PSA_ENGINE and\n * @ref AVS_COMMONS_WITH_OPENSSL_PKCS11_ENGINE).\n *\n * The functions that need to be provided in case of a custom implementation:\n * - <c>avs_crypto_pki_engine_certificate_rm()</c>\n * - <c>avs_crypto_pki_engine_certificate_store()</c>\n * - <c>avs_crypto_pki_engine_key_gen()</c>\n * - <c>avs_crypto_pki_engine_key_rm()</c>\n * - <c>avs_crypto_pki_engine_key_store()</c>\n * - When targeting the Mbed TLS backend:\n *   - <c>_avs_crypto_mbedtls_engine_initialize_global_state()</c>\n *   - <c>_avs_crypto_mbedtls_engine_cleanup_global_state()</c>\n *   - <c>_avs_crypto_mbedtls_engine_append_cert()</c>\n *   - <c>_avs_crypto_mbedtls_engine_append_crl()</c>\n *   - <c>_avs_crypto_mbedtls_engine_load_private_key()</c>\n * - When targeting the OpenSSL backend:\n *   - <c>_avs_crypto_openssl_engine_initialize_global_state()</c>\n *   - <c>_avs_crypto_openssl_engine_cleanup_global_state()</c>\n *   - <c>_avs_crypto_openssl_engine_load_certs()</c>\n *   - <c>_avs_crypto_openssl_engine_load_crls()</c>\n *   - <c>_avs_crypto_openssl_engine_load_private_key()</c>\n *\n * External PKI engines are NOT supported in the TinyDTLS backend.\n */\n/* #undef AVS_COMMONS_WITH_AVS_CRYPTO_PKI_ENGINE */\n\n/**\n * Enables high-level support for hardware-based PSK security, i.e. loading\n * and managing PSK keys and identities via external engine.\n *\n * Requires @ref AVS_COMMONS_WITH_AVS_CRYPTO_PKI to be enabled.\n *\n * An actual implementation is required to use this feature. You may provide\n * your own, or use the default PSA-based one that comes with the HSM engine\n * commercial feature (see @ref AVS_COMMONS_WITH_MBEDTLS_PSA_ENGINE).\n *\n * The functions that need to be provided in case of a custom implementation:\n * - <c>avs_crypto_psk_engine_identity_store()</c>\n * - <c>avs_crypto_psk_engine_identity_rm()</c>\n * - <c>avs_crypto_psk_engine_key_store()</c>\n * - <c>avs_crypto_psk_engine_key_rm()</c>\n * - When targeting the Mbed TLS backend:\n *   - <c>_avs_crypto_mbedtls_engine_initialize_global_state()</c>\n *   - <c>_avs_crypto_mbedtls_engine_cleanup_global_state()</c>\n *   - <c>_avs_crypto_mbedtls_engine_load_psk_key()</c>\n *\n * External PSK engines are NOT supported in the OpenSSL and TinyDTLS backend.\n */\n/* #undef AVS_COMMONS_WITH_AVS_CRYPTO_PSK_ENGINE */\n\n/**\n * Enables the default implementation of avs_crypto engine, based on Mbed TLS\n * and PKCS#11.\n *\n * Requires @ref AVS_COMMONS_WITH_AVS_CRYPTO_PKI_ENGINE to be enabled.\n *\n * NOTE: Query string format for this engine is a subset of the PKCS#11 URI\n * scheme (see RFC 7512), modelled after the format accepted by libp11 OpenSSL\n * engine.\n *\n * NOTE: The unit tests for this feature depend on SoftHSM and pkcs11-tool.\n * These must be installed for the tests to pass.\n *\n * IMPORTANT: Only available as part of the HSM support commercial feature.\n * Ignored in the open source version.\n */\n/* #undef AVS_COMMONS_WITH_MBEDTLS_PKCS11_ENGINE */\n\n/**\n * Enables the default implementation of avs_crypto engine, based on Mbed TLS\n * and Platform Security Architecture (PSA).\n *\n * Requires @ref AVS_COMMONS_WITH_AVS_CRYPTO_PKI_ENGINE or\n * @ref AVS_COMMONS_WITH_AVS_CRYPTO_PSK_ENGINE to be enabled.\n *\n * NOTE: Query string format for this engine is:\n *\n * <pre>\n * kid=<key_ID>[,lifetime=<lifetime>]|uid=<persistent_storage_UID>\n * </pre>\n *\n * The values are parsed using strtoull() with base=0, so may be in decimal,\n * 0-prefixed octal or 0x-prefixed hexadecimal. On key generation and\n * certificate storage, the specified lifetime will be used, or lifetime 1\n * (default persistent storage) will be used if not. On key or certificate use,\n * the lifetime of the actual key will be verified if present on the query\n * string and the key will be rejected if different.\n *\n * Certificates are stored as PSA_KEY_TYPE_RAW_DATA key entries containing\n * X.509 DER data. Alternatively, the PSA Protected Storage API can be used if\n * @ref AVS_COMMONS_WITH_MBEDTLS_PSA_ENGINE_PROTECTED_STORAGE is enabled, by\n * using the <c>uid=...</c> syntax.\n *\n * IMPORTANT: Only available as part of the HSM support commercial feature.\n * Ignored in the open source version.\n */\n/* #undef AVS_COMMONS_WITH_MBEDTLS_PSA_ENGINE */\n\n/**\n * Enables support for the PSA Protected Storage API in the PSA-based avs_crypto\n * engine.\n *\n * Requires @ref AVS_COMMONS_WITH_MBEDTLS_PSA_ENGINE to be enabled.\n *\n * IMPORTANT: Only available as part of the HSM support commercial feature.\n * Ignored in the open source version.\n */\n/* #undef AVS_COMMONS_WITH_MBEDTLS_PSA_ENGINE_PROTECTED_STORAGE */\n\n/**\n * Enables use of the <c>psa_generate_random()</c> function as the default\n * random number generator when using the Mbed TLS crypto backend, instead of\n * CTR-DRBG seeded by the Mbed TLS entropy pool.\n *\n * It's meaningful only when @ref AVS_COMMONS_WITH_MBEDTLS is enabled. However,\n * it is independent from the above PSA engine settings.\n */\n/* #undef AVS_COMMONS_WITH_MBEDTLS_PSA_RNG */\n\n/**\n * Is the <c>dlsym()</c> function available?\n *\n * This is currently only used if @ref AVS_COMMONS_WITH_MBEDTLS_PKCS11_ENGINE is\n * enabled. If enabled, the PKCS#11 module is loaded dynamically from a library\n * specified by the <c>PKCS11_MODULE_PATH</c> environment variable. If disabled,\n * a function with the following signature, realizing the PKCS#11\n * <c>C_GetFunctionList</c> method, must be provided manually:\n *\n * <pre>\n * CK_RV _avs_crypto_mbedtls_pkcs11_get_function_list(CK_FUNCTION_LIST_PTR_PTR);\n * </pre>\n */\n#define AVS_COMMONS_HAVE_DLSYM\n\n/**\n * Enables the default implementation of avs_crypto engine, based on OpenSSL and\n * PKCS#11.\n *\n * Requires @ref AVS_COMMONS_WITH_AVS_CRYPTO_PKI_ENGINE to be enabled.\n *\n * NOTE: Query string format for this engine is a subset of the PKCS#11 URI\n * scheme (see RFC 7512), modelled after the format accepted by libp11 OpenSSL\n * engine.\n *\n * NOTE: The unit tests for this feature depend on SoftHSM and pkcs11-tool.\n * These must be installed for the tests to pass.\n *\n * IMPORTANT: Only available as part of the HSM support commercial feature.\n * Ignored in the open source version.\n */\n/* #undef AVS_COMMONS_WITH_OPENSSL_PKCS11_ENGINE */\n/**@}*/\n\n/**\n * Enable support for HTTP content compression in avs_http.\n *\n * Requires linking with zlib.\n */\n/* #undef AVS_COMMONS_HTTP_WITH_ZLIB */\n\n/**\n * Options related to avs_log and logging support within avs_commons.\n */\n/**@{*/\n/* clang-format off */\n/**\n * Size, in bytes, of the avs_log buffer.\n *\n * Log messages that would (including the level, module name and code location)\n * otherwise be longer than this value minus one (for the terminating null\n * character) will be truncated.\n *\n * NOTE: This macro MUST be defined if avs_log is enabled.\n *\n * If editing this file manually, <c>512</c> shall\n * be replaced with a positive integer literal. The default value defined in\n * CMake build scripts is 512.\n */\n#define AVS_COMMONS_LOG_MAX_LINE_LENGTH 512\n/* clang-format on */\n\n/**\n * Configures avs_log to use a synchronized global buffer instead of allocating\n * a buffer on the stack when constructing log messages.\n *\n * Requires avs_compat_threading to be enabled.\n *\n * Enabling this option would reduce the stack space required to use avs_log, at\n * the expense of global storage and the complexity of using a mutex.\n */\n/* #undef AVS_COMMONS_LOG_USE_GLOBAL_BUFFER */\n\n/**\n * Provides a default avs_log handler that prints log messages on stderr.\n *\n * Disabling this option will cause logs to be discarded by default, until a\n * custom log handler is set using <c>avs_log_set_handler()</c>.\n */\n#define AVS_COMMONS_LOG_WITH_DEFAULT_HANDLER\n\n/**\n * Enables the \"micro logs\" feature.\n *\n * Replaces all occurrences of the <c>AVS_DISPOSABLE_LOG()</c> macro with single\n * space strings. This is intended to reduce the size of the compiled code, by\n * stripping it of almost all log string data.\n *\n * Note that this setting will propagate both to avs_commons components\n * themselves (as all its internal logs make use of <c>AVS_DISPOSABLE_LOG()</c>)\n * and the user code that uses it.\n */\n/* #undef AVS_COMMONS_WITH_MICRO_LOGS */\n\n/**\n * Enables logging inside avs_commons.\n *\n * Requires @ref AVS_COMMONS_WITH_AVS_LOG to be enabled.\n *\n * If this macro is not defined at avs_commons compile time, calls to avs_log\n * will not be generated inside avs_commons components.\n */\n#define AVS_COMMONS_WITH_INTERNAL_LOGS\n\n/**\n * Enables TRACE-level logs inside avs_commons.\n *\n * Only meaningful if AVS_COMMONS_WITH_INTERNAL_LOGS is enabled.\n *\n * If this macro is not defined at avs_commons compile time, calls to avs_log\n * with the level set to TRACE will not be generated inside avs_commons\n * components.\n */\n/* #undef AVS_COMMONS_WITH_INTERNAL_TRACE */\n\n/**\n * Enables external implementation of logger subsystem with provided header.\n *\n * Default logger implementation can be found in avs_log_impl.h\n */\n/* #undef AVS_COMMONS_WITH_EXTERNAL_LOGGER_HEADER */\n\n/**\n * If specified, the process of checking if avs_log should be written out\n * takes place in compile time.\n *\n * Specify an optional header with a list of modules for which log level\n * is set. If a log level for specific module is not set, the DEFAULT level\n * will be taken into account. Value of the default logging level is set to\n * DEBUG, but can be overwritten in this header file with AVS_LOG_LEVEL_DEFAULT\n * define. Messages with lower level than the one set will be removed during\n * compile time. Possible values match @ref avs_log_level_t.\n *\n * That file should contain C preprocesor defines in the:\n * - \"#define AVS_LOG_LEVEL_FOR_MODULE_<Module> <Level>\" format,\n *   where <Module> is the module name and <Level> is allowed logging level\n * - \"#define AVS_LOG_LEVEL_DEFAULT <Level>\" format, where <Level> is the\n *   allowed logging level\n *\n * Example file content:\n *\n * <code>\n * #ifndef AVS_COMMONS_EXTERNAL_LOG_LEVELS_H\n * #define AVS_COMMONS_EXTERNAL_LOG_LEVELS_H\n *\n * // global log level value\n * #define AVS_LOG_LEVEL_DEFAULT INFO\n *\n * //for \"coap\" messages only WARNING and ERROR messages will be present\n * #define AVS_LOG_LEVEL_FOR_MODULE_coap WARNING\n *\n * //logs are disable for \"net\" module\n * #define AVS_LOG_LEVEL_FOR_MODULE_net QUIET\n *\n * #endif\n * </code>\n */\n/* #undef AVS_COMMONS_WITH_EXTERNAL_LOG_LEVELS_HEADER */\n\n/**\n * Disable log level check in runtime. Allows to save at least 1.3kB of memory.\n *\n * The macros avs_log_set_level and avs_log_set_default_level\n * will not be available.\n *\n */\n/* #undef AVS_COMMONS_WITHOUT_LOG_CHECK_IN_RUNTIME */\n/**@}*/\n\n/**\n * Options related to avs_net.\n */\n/**@{*/\n/**\n * Enables support for IPv4 connectivity.\n *\n * At least one of AVS_COMMONS_NET_WITH_IPV4 and AVS_COMMONS_NET_WITH_IPV6\n * MUST be defined if avs_net is enabled.\n */\n#define AVS_COMMONS_NET_WITH_IPV4\n\n/**\n * Enables support for IPv6 connectivity.\n *\n * At least one of AVS_COMMONS_NET_WITH_IPV4 and AVS_COMMONS_NET_WITH_IPV6\n * MUST be defined if avs_net is enabled.\n */\n#define AVS_COMMONS_NET_WITH_IPV6\n\n/**\n * If the TLS backend is set to OpenSSL, enables support for DTLS.\n *\n * DTLS is always enabled for the mbed TLS and TinyDTLS backends.\n */\n/* #undef AVS_COMMONS_NET_WITH_DTLS */\n\n/**\n * Enables debug logs generated by mbed TLS.\n *\n * An avs_log-backed handler, logging for the \"mbedtls\" module on the TRACE\n * level, is installed using <c>mbedtls_ssl_conf_dbg()</c> for each (D)TLS\n * socket created if this option is enabled.\n */\n/* #undef AVS_COMMONS_NET_WITH_MBEDTLS_LOGS */\n\n/**\n * Enables (Pre-)Master-Secret logs generation.\n *\n * These logs contain TLS session secrets that tools like Wireshark can use\n * to decrypt captured TLS traffic.\n *\n * NOTE: This only works with Mbed TLS starting from v3.0.0. If you’re using\n * Mbed TLS earlier than v3.1.0, you must enable MBEDTLS_SSL_EXPORT_KEYS.\n *\n * NOTE: The user must specify the stream to which the logs will be transferred\n * using the avs_mbedtls_set_sslkeylog_stream function.\n */\n/* #undef AVS_COMMONS_NET_WITH_MBEDTLS_SSLKEYLOG */\n\n/**\n * Enables the default implementation of avs_net TCP and UDP sockets.\n *\n * Requires either a UNIX-like operating environment, or a compatibility layer\n * with a high degree of compatibility with standard BSD sockets with an\n * appropriate compatibility header (see @ref AVS_COMMONS_POSIX_COMPAT_HEADER) -\n * lwIP and Winsock are currently supported for this scenario.\n */\n#define AVS_COMMONS_NET_WITH_POSIX_AVS_SOCKET\n\n/**\n * @deprecated This is deprecated API. This API can be removed in future\n * releases, without any notice.\n *\n * Enables support for logging socket communication to file.\n *\n * If this option is enabled, avs_net_socket_debug() can be used to enable\n * logging all communication to a file called DEBUG.log. If disabled,\n * avs_net_socket_debug() will always return an error.\n */\n/* #undef AVS_COMMONS_NET_WITH_SOCKET_LOG */\n\n/**\n * @experimental This is experimental API. This API can change in the future\n * versions without any notice.\n *\n * Enables support for custom net traffic interceptor.\n *\n * If this option is enabled, the user needs to provide their own implementation\n * of avs_net_create_traffic_interceptor() function which can wrap and extend\n * an underlying socket.\n */\n/* #undef AVS_COMMONS_WITH_TRAFFIC_INTERCEPTOR */\n\n/**\n * If the TLS backend is either mbed TLS or OpenSSL, enables support for (D)TLS\n * session persistence.\n *\n * Session persistence is not currently supported for the TinyDTLS backend.\n */\n#define AVS_COMMONS_NET_WITH_TLS_SESSION_PERSISTENCE\n/**@}*/\n\n/**\n * Options related to avs_net's default implementation of TCP and UDP sockets.\n *\n * These options make sense only when @ref AVS_COMMONS_NET_WITH_POSIX_AVS_SOCKET\n * is enabled. They describe capabilities of the Unix-like environment in which\n * the library is built.\n *\n * Note that if @ref AVS_COMMONS_POSIX_COMPAT_HEADER is defined, it might\n * redefine these flags independently of the settings in this file.\n */\n/**@{*/\n/**\n * Is the <c>gai_strerror()</c> function available?\n *\n * Enabling this flag will provide more detailed log messages in case that\n * <c>getaddrinfo()</c> fails. If this flag is disabled, numeric error codes\n * values will be logged.\n */\n#define AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_GAI_STRERROR\n\n/**\n * Is the <c>getifaddrs()</c> function available?\n *\n * Disabling this flag will cause <c>avs_net_socket_interface_name()</c> to use\n * a less optimal implementation based on the <c>SIOCGIFCONF</c> ioctl.\n *\n * If <c>SIOCGIFCONF</c> is not defined, either, then\n * <c>avs_net_socket_interface_name()</c> will always return an error.\n */\n#define AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_GETIFADDRS\n\n/**\n * Is the <c>getnameinfo()</c> function available?\n *\n * Disabling this flag will cause <c>avs_net_socket_receive_from()</c>,\n * <c>avs_net_socket_accept()</c>,\n * <c>avs_net_resolved_endpoint_get_host_port()</c>,\n * <c>avs_net_resolved_endpoint_get_host()</c> and\n * <c>avs_net_resolve_host_simple()</c> to use a custom reimplementation of\n * <c>getnameinfo()</c> based on <c>inet_ntop()</c>.\n */\n#define AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_GETNAMEINFO\n\n/**\n * Is the <c>IN6_IS_ADDR_V4MAPPED</c> macro available and usable?\n *\n * Disabling this flag will cause a custom code that compares IPv6 addresses\n * with the <c>::ffff:0.0.0.0/32</c> mask to be used instead.\n */\n#define AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_IN6_IS_ADDR_V4MAPPED\n\n/**\n * Should be defined if IPv4-mapped IPv6 addresses (<c>::ffff:0.0.0.0/32</c>)\n * are <strong>NOT</strong> supported by the underlying platform.\n *\n * Enabling this flag will prevent avs_net from using IPv4-mapped IPv6 addresses\n * and instead re-open and re-bind the socket if a connection to an IPv4 address\n * is requested on a previously created IPv6 socket.\n *\n * This may result in otherwise redundant <c>socket()</c>, <c>bind()</c> and\n * <c>close()</c> system calls to be performed, but may be necessary for\n * interoperability with some platforms.\n */\n/* #undef AVS_COMMONS_NET_POSIX_AVS_SOCKET_WITHOUT_IN6_V4MAPPED_SUPPORT */\n\n/**\n * Is the <c>inet_ntop()</c> function available?\n *\n * Disabling this flag will cause an internal implementation of this function\n * adapted from BIND 4.9.4 to be used instead.\n */\n#define AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_INET_NTOP\n\n/**\n * Is the <c>poll()</c> function available?\n *\n * Disabling this flag will cause a less robust code based on <c>select()</c> to\n * be used instead.\n */\n#define AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_POLL\n\n/**\n * Is the <c>recvmsg()</c> function available?\n *\n * Disabling this flag will cause <c>recvfrom()</c> to be used instead. Note\n * that for UDP sockets, this will cause false positives for datagram truncation\n * detection (<c>AVS_EMSGSIZE</c>) to be reported when the received message is\n * exactly the size of the buffer.\n */\n#define AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_RECVMSG\n/**@}*/\n\n/**\n * Enable thread safety in avs_sched.\n *\n * Makes all scheduler accesses synchronized and thread-safe, at the cost of\n * requiring avs_compat_threading to be enabled, and higher resource usage.\n */\n#define AVS_COMMONS_SCHED_THREAD_SAFE\n\n/**\n * Enable support for file I/O in avs_stream.\n *\n * Disabling this flag will cause the functions declared in\n * <c>avs_stream_file.h</c> to not be defined.\n */\n#define AVS_COMMONS_STREAM_WITH_FILE\n\n/**\n * Enable usage of <c>backtrace()</c> and <c>backtrace_symbols()</c> when\n * reporting assertion failures from avs_unit.\n *\n * Requires the afore-mentioned GNU-specific functions to be available.\n *\n * If this flag is disabled, stack traces will not be displayed with assertion\n * failures.\n */\n#define AVS_COMMONS_UNIT_POSIX_HAVE_BACKTRACE\n\n/**\n * Options related to avs_utils.\n */\n/**@{*/\n/**\n * Enable the default implementation of avs_time_real_now() and\n * avs_time_monotonic_now().\n *\n * Requires an operating environment that supports a clock_gettime() call\n * compatible with POSIX.\n */\n#define AVS_COMMONS_UTILS_WITH_POSIX_AVS_TIME\n\n/**\n * Enable the default implementation of avs_malloc(), avs_free(), avs_calloc()\n * and avs_realloc() that forwards to system malloc(), free(), calloc() and\n * realloc() calls.\n *\n * You might disable this option if for any reason you need to use a custom\n * allocator.\n */\n#define AVS_COMMONS_UTILS_WITH_STANDARD_ALLOCATOR\n\n/**\n * Enable the alternate implementation of avs_malloc(), avs_free(), avs_calloc()\n * and avs_realloc() that uses system malloc(), free() and realloc() calls, but\n * includes additional fixup code that ensures proper alignment to\n * <c>AVS_ALIGNOF(avs_max_align_t)</c> (usually 8 bytes on common platforms).\n *\n * <c>AVS_COMMONS_UTILS_WITH_STANDARD_ALLOCATOR</c> and\n * <c>AVS_COMMONS_UTILS_WITH_ALIGNFIX_ALLOCATOR</c> cannot be enabled at the\n * same time.\n *\n * NOTE: This implementation is only intended for platforms where the system\n * allocator does not properly conform to the alignment requirements.\n *\n * It comes with an additional runtime costs:\n *\n * - <c>AVS_ALIGNOF(avs_max_align_t)</c> bytes (usually 8) of additional\n *   overhead for each allocated memory block\n * - Additional memmove() for every realloc() that returned a block that is not\n *   properly aligned\n * - avs_calloc() is implemented as avs_malloc() followed by an explicit\n *   memset(); this may be suboptimal on some platforms\n *\n * If these costs are unacceptable for you, you may want to consider fixing,\n * replacing or reconfiguring your system allocator for conformance, or\n * implementing a custom one instead.\n *\n * Please note that some code in avs_commons and dependent projects (e.g. Anjay)\n * may include runtime assertions for proper memory alignment that will be\n * triggered when using a non-conformant standard allocator. Such allocators are\n * relatively common in embedded SDKs. This \"alignfix\" allocator is intended to\n * work around these issues. On some platforms (e.g. x86) those alignment issues\n * may not actually cause any problems - so you may want to consider disabling\n * runtime assertions instead. Please carefully examine your target platform's\n * alignment requirements and behavior of misaligned memory accesses (including\n * 64-bit data types such as <c>int64_t</c> and <c>double</c>) before doing so.\n */\n/* #undef AVS_COMMONS_UTILS_WITH_ALIGNFIX_ALLOCATOR */\n\n/**@}*/\n\n#endif /* AVS_COMMONS_CONFIG_H */\n"
  },
  {
    "path": "examples/CMakeLists.txt",
    "content": "# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\n\nadd_custom_target(examples)\n\n# TUTORIALS\n\nset(ANJAY_TUTORIALS_BUILD_DIR \"${CMAKE_CURRENT_BINARY_DIR}/tutorial-build\")\nset(ANJAY_TUTORIALS_ANJAY_BUILD_DIR \"${ANJAY_TUTORIALS_BUILD_DIR}/anjay-build\")\nset(ANJAY_TUTORIALS_ANJAY_INSTALL_DIR \"${ANJAY_TUTORIALS_BUILD_DIR}/anjay\")\n\nadd_custom_target(tutorial_examples)\nadd_custom_command(TARGET tutorial_examples\n                   COMMAND ${CMAKE_COMMAND} -E make_directory \"${ANJAY_TUTORIALS_BUILD_DIR}\"\n                   COMMAND ${CMAKE_COMMAND} -E make_directory \"${ANJAY_TUTORIALS_ANJAY_INSTALL_DIR}\"\n                   COMMAND ${CMAKE_COMMAND} -E make_directory \"${ANJAY_TUTORIALS_ANJAY_BUILD_DIR}\")\n\nadd_custom_command(TARGET tutorial_examples\n                   COMMAND ${CMAKE_COMMAND}\n                        -H${ANJAY_SOURCE_DIR}\n                        -B.\n                        -DCMAKE_INSTALL_PREFIX=\"${ANJAY_TUTORIALS_ANJAY_INSTALL_DIR}\"\n                        -DWITH_LIBRARY_SHARED=OFF\n                        -DWITH_DEMO=OFF\n                        -DWITH_MODULE_advanced_fw_update=ON\n                        -DWITH_LWM2M_GATEWAY=ON\n                        -DCMAKE_BUILD_TYPE=Debug\n                   COMMAND ${CMAKE_COMMAND} --build . --target install -- -j${NPROC}\n                   WORKING_DIRECTORY ${ANJAY_TUTORIALS_ANJAY_BUILD_DIR})\n\nadd_custom_command(TARGET tutorial_examples\n                   COMMAND ${CMAKE_COMMAND}\n                        -H${CMAKE_CURRENT_SOURCE_DIR}/tutorial/\n                        -B.\n                        -DCMAKE_PREFIX_PATH=\"${ANJAY_TUTORIALS_ANJAY_INSTALL_DIR}\"\n                        -DCMAKE_RUNTIME_OUTPUT_DIRECTORY=\"${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/examples\"\n                        -DCMAKE_BUILD_TYPE=Debug\n                   COMMAND ${CMAKE_COMMAND} --build . -- -j${NPROC}\n                   WORKING_DIRECTORY \"${ANJAY_TUTORIALS_BUILD_DIR}\")\n\nadd_dependencies(examples tutorial_examples)\n\n\n# CUSTOM NETWORK LAYER TUTORIALS\n\nset(ANJAY_CUSTOM_NETWORK_BUILD_DIR \"${CMAKE_CURRENT_BINARY_DIR}/custom-network-build\")\nset(ANJAY_CUSTOM_NETWORK_ANJAY_BUILD_DIR \"${ANJAY_CUSTOM_NETWORK_BUILD_DIR}/anjay-build\")\nset(ANJAY_CUSTOM_NETWORK_ANJAY_INSTALL_DIR \"${ANJAY_CUSTOM_NETWORK_BUILD_DIR}/anjay\")\n\nadd_custom_target(custom_network_examples)\nadd_custom_command(TARGET custom_network_examples\n                   COMMAND ${CMAKE_COMMAND} -E make_directory \"${ANJAY_CUSTOM_NETWORK_BUILD_DIR}\"\n                   COMMAND ${CMAKE_COMMAND} -E make_directory \"${ANJAY_CUSTOM_NETWORK_ANJAY_INSTALL_DIR}\"\n                   COMMAND ${CMAKE_COMMAND} -E make_directory \"${ANJAY_CUSTOM_NETWORK_ANJAY_BUILD_DIR}\")\n\nadd_custom_command(TARGET custom_network_examples\n                   COMMAND ${CMAKE_COMMAND}\n                        -H${ANJAY_SOURCE_DIR}\n                        -B.\n                        -DCMAKE_INSTALL_PREFIX=\"${ANJAY_CUSTOM_NETWORK_ANJAY_INSTALL_DIR}\"\n                        -DWITH_LIBRARY_SHARED=OFF\n                        -DWITH_DEMO=OFF\n                        -DWITH_POSIX_AVS_SOCKET=OFF\n                        -DWITHOUT_IP_STICKINESS=ON\n                        -DCMAKE_BUILD_TYPE=Debug\n                   COMMAND ${CMAKE_COMMAND} --build . --target install -- -j${NPROC}\n                   WORKING_DIRECTORY ${ANJAY_CUSTOM_NETWORK_ANJAY_BUILD_DIR})\n\nadd_custom_command(TARGET custom_network_examples\n                   COMMAND ${CMAKE_COMMAND}\n                        -H${CMAKE_CURRENT_SOURCE_DIR}/custom-network/\n                        -B.\n                        -DCMAKE_PREFIX_PATH=\"${ANJAY_CUSTOM_NETWORK_ANJAY_INSTALL_DIR}\"\n                        -DCMAKE_RUNTIME_OUTPUT_DIRECTORY=\"${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/examples\"\n                        -DCMAKE_BUILD_TYPE=Debug\n                   COMMAND ${CMAKE_COMMAND} --build . -- -j${NPROC}\n                   WORKING_DIRECTORY \"${ANJAY_CUSTOM_NETWORK_BUILD_DIR}\")\n\nadd_dependencies(examples custom_network_examples)\n\n# ip-stickiness example\n\nset(ANJAY_CUSTOM_NETWORK_IP_STICKINESS_BUILD_DIR \"${ANJAY_CUSTOM_NETWORK_BUILD_DIR}/ip-stickiness\")\nset(ANJAY_CUSTOM_NETWORK_IP_STICKINESS_ANJAY_BUILD_DIR \"${ANJAY_CUSTOM_NETWORK_BUILD_DIR}/anjay-with-ip-stickiness-build\")\nset(ANJAY_CUSTOM_NETWORK_IP_STICKINESS_ANJAY_INSTALL_DIR \"${ANJAY_CUSTOM_NETWORK_BUILD_DIR}/anjay-with-ip-stickiness\")\n\nadd_custom_target(custom_network_ip_stickiness_example)\nadd_custom_command(TARGET custom_network_ip_stickiness_example\n                   COMMAND ${CMAKE_COMMAND} -E make_directory \"${ANJAY_CUSTOM_NETWORK_IP_STICKINESS_BUILD_DIR}\"\n                   COMMAND ${CMAKE_COMMAND} -E make_directory \"${ANJAY_CUSTOM_NETWORK_IP_STICKINESS_ANJAY_BUILD_DIR}\"\n                   COMMAND ${CMAKE_COMMAND} -E make_directory \"${ANJAY_CUSTOM_NETWORK_IP_STICKINESS_ANJAY_INSTALL_DIR}\")\n\nadd_custom_command(TARGET custom_network_ip_stickiness_example\n                   COMMAND ${CMAKE_COMMAND}\n                        -H${ANJAY_SOURCE_DIR}\n                        -B.\n                        -DCMAKE_INSTALL_PREFIX=\"${ANJAY_CUSTOM_NETWORK_IP_STICKINESS_ANJAY_INSTALL_DIR}\"\n                        -DWITH_LIBRARY_SHARED=OFF\n                        -DWITH_DEMO=OFF\n                        -DWITH_POSIX_AVS_SOCKET=OFF\n                        -DCMAKE_BUILD_TYPE=Debug\n                   COMMAND ${CMAKE_COMMAND} --build . --target install -- -j${NPROC}\n                   WORKING_DIRECTORY ${ANJAY_CUSTOM_NETWORK_IP_STICKINESS_ANJAY_BUILD_DIR})\n\nadd_custom_command(TARGET custom_network_ip_stickiness_example\n                   COMMAND ${CMAKE_COMMAND}\n                        -H${CMAKE_CURRENT_SOURCE_DIR}/custom-network/ip-stickiness\n                        -B.\n                        -DCMAKE_PREFIX_PATH=\"${ANJAY_CUSTOM_NETWORK_IP_STICKINESS_ANJAY_INSTALL_DIR}\"\n                        -DCMAKE_RUNTIME_OUTPUT_DIRECTORY=\"${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/examples\"\n                        -DCMAKE_BUILD_TYPE=Debug\n                   COMMAND ${CMAKE_COMMAND} --build . -- -j${NPROC}\n                   WORKING_DIRECTORY \"${ANJAY_CUSTOM_NETWORK_IP_STICKINESS_BUILD_DIR}\")\n\nadd_dependencies(custom_network_examples custom_network_ip_stickiness_example)\n\n# CUSTOM TLS LAYER TUTORIAL\n\nfind_package(OpenSSL)\nif(OPENSSL_FOUND AND NOT OPENSSL_VERSION VERSION_LESS 1.1.0)\n    set(ANJAY_CUSTOM_TLS_BUILD_DIR \"${CMAKE_CURRENT_BINARY_DIR}/custom-tls-build\")\n    set(ANJAY_CUSTOM_TLS_ANJAY_BUILD_DIR \"${ANJAY_CUSTOM_TLS_BUILD_DIR}/anjay-build\")\n    set(ANJAY_CUSTOM_TLS_ANJAY_INSTALL_DIR \"${ANJAY_CUSTOM_TLS_BUILD_DIR}/anjay\")\n\n    add_custom_target(custom_tls_examples)\n    add_custom_command(TARGET custom_tls_examples\n                       COMMAND ${CMAKE_COMMAND} -E make_directory \"${ANJAY_CUSTOM_TLS_BUILD_DIR}\"\n                       COMMAND ${CMAKE_COMMAND} -E make_directory \"${ANJAY_CUSTOM_TLS_ANJAY_INSTALL_DIR}\"\n                       COMMAND ${CMAKE_COMMAND} -E make_directory \"${ANJAY_CUSTOM_TLS_ANJAY_BUILD_DIR}\")\n\n    add_custom_command(TARGET custom_tls_examples\n                       COMMAND ${CMAKE_COMMAND}\n                            -H${ANJAY_SOURCE_DIR}\n                            -B.\n                            -DCMAKE_INSTALL_PREFIX=\"${ANJAY_CUSTOM_TLS_ANJAY_INSTALL_DIR}\"\n                            -DDTLS_BACKEND=custom\n                            -DWITH_LIBRARY_SHARED=OFF\n                            -DWITH_DEMO=OFF\n                            -DWITH_POSIX_AVS_SOCKET=OFF\n                            -DWITH_EVENT_LOOP=ON\n                            -DWITH_HTTP_DOWNLOAD=ON\n                            -DCMAKE_BUILD_TYPE=Debug\n                       COMMAND ${CMAKE_COMMAND} --build . --target install -- -j${NPROC}\n                       WORKING_DIRECTORY ${ANJAY_CUSTOM_TLS_ANJAY_BUILD_DIR})\n\n    add_custom_command(TARGET custom_tls_examples\n                       COMMAND ${CMAKE_COMMAND}\n                            -H${CMAKE_CURRENT_SOURCE_DIR}/custom-tls/\n                            -B.\n                            -DCMAKE_PREFIX_PATH=\"${ANJAY_CUSTOM_TLS_ANJAY_INSTALL_DIR}\"\n                            -DCMAKE_RUNTIME_OUTPUT_DIRECTORY=\"${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/examples\"\n                            -DCMAKE_BUILD_TYPE=Debug\n                       COMMAND ${CMAKE_COMMAND} --build . -- -j${NPROC}\n                       WORKING_DIRECTORY \"${ANJAY_CUSTOM_TLS_BUILD_DIR}\")\n\n    add_dependencies(examples custom_tls_examples)\nendif()\n\nif(TARGET check)\n    add_dependencies(check examples)\nendif()\n"
  },
  {
    "path": "examples/commercial-features/CF-CorePersistence/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.16)\nproject(anjay-core-persistence C)\n\nset(CMAKE_C_STANDARD 99)\nset(CMAKE_C_EXTENSIONS OFF)\n\nfind_package(anjay REQUIRED)\n\nadd_executable(${PROJECT_NAME} src/main.c)\ntarget_link_libraries(${PROJECT_NAME} PRIVATE anjay)\n"
  },
  {
    "path": "examples/commercial-features/CF-CorePersistence/src/main.c",
    "content": "#include <anjay/anjay.h>\n#include <anjay/attr_storage.h>\n#include <anjay/security.h>\n#include <anjay/server.h>\n#include <avsystem/commons/avs_log.h>\n#include <avsystem/commons/avs_stream_file.h>\n\n#include <errno.h>\n#include <signal.h>\n#include <unistd.h>\n\nstatic anjay_t *volatile g_anjay;\n\nvoid signal_handler(int signum) {\n    if (signum == SIGINT && g_anjay) {\n        anjay_event_loop_interrupt(g_anjay);\n    }\n}\n\n#define OBJECT_PERSISTENCE_FILENAME \"cf-object-persistence.dat\"\n#define CORE_PERSISTENCE_FILENAME \"cf-core-persistence.dat\"\n\nint persist_objects(anjay_t *anjay) {\n    avs_log(tutorial, INFO,\n            \"Persisting objects to \" OBJECT_PERSISTENCE_FILENAME);\n\n    avs_stream_t *file_stream =\n            avs_stream_file_create(OBJECT_PERSISTENCE_FILENAME,\n                                   AVS_STREAM_FILE_WRITE);\n\n    if (!file_stream) {\n        avs_log(tutorial, ERROR, \"Could not open file for writing\");\n        return -1;\n    }\n\n    int result = -1;\n\n    if (avs_is_err(anjay_security_object_persist(anjay, file_stream))) {\n        avs_log(tutorial, ERROR, \"Could not persist Security Object\");\n        goto finish;\n    }\n\n    if (avs_is_err(anjay_server_object_persist(anjay, file_stream))) {\n        avs_log(tutorial, ERROR, \"Could not persist Server Object\");\n        goto finish;\n    }\n\n    if (avs_is_err(anjay_attr_storage_persist(anjay, file_stream))) {\n        avs_log(tutorial, ERROR, \"Could not persist LwM2M attribute storage\");\n        goto finish;\n    }\n\n    result = 0;\nfinish:\n    avs_stream_cleanup(&file_stream);\n    return result;\n}\n\nint restore_objects_if_possible(anjay_t *anjay) {\n    avs_log(tutorial, INFO, \"Attempting to restore objects from persistence\");\n    int result;\n\n    errno = 0;\n    if ((result = access(OBJECT_PERSISTENCE_FILENAME, F_OK))) {\n        switch (errno) {\n        case ENOENT:\n        case ENOTDIR:\n            // no persistence file means there is nothing to restore\n            return 1;\n        default:\n            // some other unpredicted error\n            return result;\n        }\n    } else if ((result = access(OBJECT_PERSISTENCE_FILENAME, R_OK))) {\n        // most likely file is just not readable\n        return result;\n    }\n\n    avs_stream_t *file_stream =\n            avs_stream_file_create(OBJECT_PERSISTENCE_FILENAME,\n                                   AVS_STREAM_FILE_READ);\n\n    if (!file_stream) {\n        return -1;\n    }\n\n    result = -1;\n\n    if (avs_is_err(anjay_security_object_restore(anjay, file_stream))) {\n        avs_log(tutorial, ERROR, \"Could not restore Security Object\");\n        goto finish;\n    }\n\n    if (avs_is_err(anjay_server_object_restore(anjay, file_stream))) {\n        avs_log(tutorial, ERROR, \"Could not restore Server Object\");\n        goto finish;\n    }\n\n    if (avs_is_err(anjay_attr_storage_restore(anjay, file_stream))) {\n        avs_log(tutorial, ERROR, \"Could not restore LwM2M attribute storage\");\n        goto finish;\n    }\n\n    result = 0;\nfinish:\n    avs_stream_cleanup(&file_stream);\n    return result;\n}\n\nanjay_t *\nanjay_new_try_from_core_persistence(const anjay_configuration_t *config) {\n    avs_log(tutorial, INFO,\n            \"Attempting to initialize Anjay from core persistence\");\n\n    avs_stream_t *file_stream =\n            avs_stream_file_create(CORE_PERSISTENCE_FILENAME,\n                                   AVS_STREAM_FILE_READ);\n\n    anjay_t *result;\n    if (!file_stream\n            || !(result = anjay_new_from_core_persistence(config,\n                                                          file_stream))) {\n        result = anjay_new(config);\n    }\n\n    avs_stream_cleanup(&file_stream);\n    // remove persistence file to prevent client from reading\n    // outdated state in case it doesn't shut down gracefully\n    unlink(CORE_PERSISTENCE_FILENAME);\n    return result;\n}\n\nint anjay_delete_try_with_core_persistence(anjay_t *anjay) {\n    avs_log(tutorial, INFO,\n            \"Attempting to shut down Anjay and persist its state\");\n\n    avs_stream_t *file_stream =\n            avs_stream_file_create(CORE_PERSISTENCE_FILENAME,\n                                   AVS_STREAM_FILE_WRITE);\n    if (file_stream) {\n        int result = anjay_delete_with_core_persistence(anjay, file_stream);\n        avs_stream_cleanup(&file_stream);\n        if (result) {\n            unlink(CORE_PERSISTENCE_FILENAME);\n        }\n        return result;\n    } else {\n        anjay_delete(anjay);\n        return -1;\n    }\n}\n\nvoid initialize_objects_with_default_settings(anjay_t *anjay) {\n    static const char PSK_IDENTITY[] = \"identity\";\n    static const char PSK_KEY[] = \"P4s$w0rd\";\n\n    const anjay_security_instance_t security_instance = {\n        .ssid = 1,\n        .server_uri = \"coaps://eu.iot.avsystem.cloud:5684\",\n        .security_mode = ANJAY_SECURITY_PSK,\n        .public_cert_or_psk_identity = (const uint8_t *) PSK_IDENTITY,\n        .public_cert_or_psk_identity_size = strlen(PSK_IDENTITY),\n        .private_cert_or_psk_key = (const uint8_t *) PSK_KEY,\n        .private_cert_or_psk_key_size = strlen(PSK_KEY)\n    };\n\n    const anjay_server_instance_t server_instance = {\n        .ssid = 1,\n        .lifetime = 60,\n        .default_min_period = -1,\n        .default_max_period = -1,\n        .disable_timeout = -1,\n        .binding = \"U\"\n    };\n\n    anjay_iid_t security_instance_id = ANJAY_ID_INVALID;\n    anjay_iid_t server_instance_id = ANJAY_ID_INVALID;\n    anjay_security_object_add_instance(anjay, &security_instance,\n                                       &security_instance_id);\n    anjay_server_object_add_instance(anjay, &server_instance,\n                                     &server_instance_id);\n}\n\nint main(int argc, char *argv[]) {\n    if (argc != 2) {\n        avs_log(tutorial, ERROR, \"usage: %s ENDPOINT_NAME\", argv[0]);\n        return -1;\n    }\n\n    signal(SIGINT, signal_handler);\n\n    const anjay_configuration_t CONFIG = {\n        .endpoint_name = argv[1],\n        .in_buffer_size = 4000,\n        .out_buffer_size = 4000\n    };\n\n    g_anjay = anjay_new_try_from_core_persistence(&CONFIG);\n    if (!g_anjay) {\n        avs_log(tutorial, ERROR, \"Could not create Anjay object\");\n        return -1;\n    }\n\n    int result = -1;\n\n    // Setup necessary objects\n    if (anjay_security_object_install(g_anjay)\n            || anjay_server_object_install(g_anjay)) {\n        goto cleanup;\n    }\n\n    int restore_retval = restore_objects_if_possible(g_anjay);\n    if (restore_retval < 0) {\n        goto cleanup;\n    } else if (restore_retval > 0) {\n        initialize_objects_with_default_settings(g_anjay);\n    }\n\n    result = anjay_event_loop_run(g_anjay,\n                                  avs_time_duration_from_scalar(1, AVS_TIME_S));\n\n    int persist_result = persist_objects(g_anjay);\n    if (!result) {\n        result = persist_result;\n    }\n\ncleanup:\n    if (result) {\n        anjay_delete(g_anjay);\n    } else {\n        result = anjay_delete_try_with_core_persistence(g_anjay);\n    }\n    return result;\n}\n"
  },
  {
    "path": "examples/commercial-features/CF-EST/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.16)\nproject(anjay-est C)\n\nset(CMAKE_C_STANDARD 99)\nset(CMAKE_C_EXTENSIONS OFF)\n\nfind_package(anjay REQUIRED)\n\nadd_executable(${PROJECT_NAME} src/main.c)\ntarget_link_libraries(${PROJECT_NAME} PRIVATE anjay)\n"
  },
  {
    "path": "examples/commercial-features/CF-EST/src/main.c",
    "content": "#include <anjay/anjay.h>\n#include <anjay/attr_storage.h>\n#include <anjay/security.h>\n#include <anjay/server.h>\n#include <avsystem/commons/avs_log.h>\n#include <avsystem/commons/avs_stream_file.h>\n\n#include <errno.h>\n#include <signal.h>\n#include <string.h>\n#include <unistd.h>\n\nstatic anjay_t *volatile g_anjay;\n\nvoid signal_handler(int signum) {\n    if (signum == SIGINT && g_anjay) {\n        anjay_event_loop_interrupt(g_anjay);\n    }\n}\n\n#define PERSISTENCE_FILENAME \"anjay-est-persistence.dat\"\n\nint persist_objects_if_necessary(anjay_t *anjay) {\n    if ((!anjay_security_object_is_modified(anjay)\n         && !anjay_server_object_is_modified(anjay)\n         && !anjay_attr_storage_is_modified(anjay))\n            || !anjay_est_state_is_ready_for_persistence(anjay)) {\n        avs_log(tutorial, INFO,\n                \"Persistence not necessary - NOT persisting objects\");\n        return 0;\n    }\n\n    avs_log(tutorial, INFO, \"Persisting objects to %s\", PERSISTENCE_FILENAME);\n\n    avs_stream_t *file_stream =\n            avs_stream_file_create(PERSISTENCE_FILENAME, AVS_STREAM_FILE_WRITE);\n\n    if (!file_stream) {\n        avs_log(tutorial, ERROR, \"Could not open file for writing\");\n        return -1;\n    }\n\n    int result = -1;\n\n    if (avs_is_err(anjay_security_object_persist(anjay, file_stream))) {\n        avs_log(tutorial, ERROR, \"Could not persist Security Object\");\n        goto finish;\n    }\n\n    if (avs_is_err(anjay_server_object_persist(anjay, file_stream))) {\n        avs_log(tutorial, ERROR, \"Could not persist Server Object\");\n        goto finish;\n    }\n\n    if (avs_is_err(anjay_attr_storage_persist(anjay, file_stream))) {\n        avs_log(tutorial, ERROR, \"Could not persist LwM2M attribute storage\");\n        goto finish;\n    }\n\n    if (avs_is_err(anjay_est_state_persist(anjay, file_stream))) {\n        avs_log(tutorial, ERROR, \"Could not persist EST state\");\n        goto finish;\n    }\n\n    result = 0;\nfinish:\n    avs_stream_cleanup(&file_stream);\n    return result;\n}\n\nint restore_objects_if_possible(anjay_t *anjay) {\n    avs_log(tutorial, INFO, \"Attempting to restore objects from persistence\");\n    int result;\n\n    errno = 0;\n    if ((result = access(PERSISTENCE_FILENAME, F_OK))) {\n        switch (errno) {\n        case ENOENT:\n        case ENOTDIR:\n            // no persistence file means there is nothing to restore\n            return 1;\n        default:\n            // some other unpredicted error\n            return result;\n        }\n    } else if ((result = access(PERSISTENCE_FILENAME, R_OK))) {\n        // most likely file is just not readable\n        return result;\n    }\n\n    avs_stream_t *file_stream =\n            avs_stream_file_create(PERSISTENCE_FILENAME, AVS_STREAM_FILE_READ);\n\n    if (!file_stream) {\n        return -1;\n    }\n\n    result = -1;\n\n    if (avs_is_err(anjay_security_object_restore(anjay, file_stream))) {\n        avs_log(tutorial, ERROR, \"Could not restore Security Object\");\n        goto finish;\n    }\n\n    if (avs_is_err(anjay_server_object_restore(anjay, file_stream))) {\n        avs_log(tutorial, ERROR, \"Could not restore Server Object\");\n        goto finish;\n    }\n\n    if (avs_is_err(anjay_attr_storage_restore(anjay, file_stream))) {\n        avs_log(tutorial, ERROR, \"Could not restore LwM2M attribute storage\");\n        goto finish;\n    }\n\n    if (avs_is_err(anjay_est_state_restore(anjay, file_stream))) {\n        avs_log(tutorial, ERROR, \"Could not restore EST state\");\n        goto finish;\n    }\n\n    result = 0;\nfinish:\n    avs_stream_cleanup(&file_stream);\n    return result;\n}\n\nstatic int\nload_buffer_from_file(uint8_t **out, size_t *out_size, const char *filename) {\n    FILE *f = fopen(filename, \"rb\");\n    if (!f) {\n        avs_log(tutorial, ERROR, \"could not open %s\", filename);\n        return -1;\n    }\n    int result = -1;\n    if (fseek(f, 0, SEEK_END)) {\n        goto finish;\n    }\n    long size = ftell(f);\n    if (size < 0 || (unsigned long) size > SIZE_MAX || fseek(f, 0, SEEK_SET)) {\n        goto finish;\n    }\n    *out_size = (size_t) size;\n    if (!(*out = (uint8_t *) avs_malloc(*out_size))) {\n        goto finish;\n    }\n    if (fread(*out, *out_size, 1, f) != 1) {\n        avs_free(*out);\n        *out = NULL;\n        goto finish;\n    }\n    result = 0;\nfinish:\n    fclose(f);\n    if (result) {\n        avs_log(tutorial, ERROR, \"could not read %s\", filename);\n    }\n    return result;\n}\n\nstatic int initialize_objects_with_default_settings(anjay_t *anjay) {\n    anjay_security_instance_t security_instance = {\n        .bootstrap_server = true,\n        .server_uri = \"coaps://eu.iot.avsystem.cloud:5694\",\n        .security_mode = ANJAY_SECURITY_EST\n    };\n\n    int result = 0;\n\n    if (load_buffer_from_file(\n                (uint8_t **) &security_instance.public_cert_or_psk_identity,\n                &security_instance.public_cert_or_psk_identity_size,\n                \"client_cert.der\")\n            || load_buffer_from_file(\n                       (uint8_t **) &security_instance.private_cert_or_psk_key,\n                       &security_instance.private_cert_or_psk_key_size,\n                       \"client_key.der\")\n            || load_buffer_from_file(\n                       (uint8_t **) &security_instance.server_public_key,\n                       &security_instance.server_public_key_size,\n                       \"server_cert.der\")) {\n        result = -1;\n        goto cleanup;\n    }\n\n    // Anjay will assign Instance ID automatically\n    anjay_iid_t security_instance_id = ANJAY_ID_INVALID;\n    if (anjay_security_object_add_instance(anjay, &security_instance,\n                                           &security_instance_id)) {\n        result = -1;\n    }\n\ncleanup:\n    avs_free((uint8_t *) security_instance.public_cert_or_psk_identity);\n    avs_free((uint8_t *) security_instance.private_cert_or_psk_key);\n    avs_free((uint8_t *) security_instance.server_public_key);\n\n    return result;\n}\n\nint main(int argc, char *argv[]) {\n    if (argc != 2) {\n        avs_log(tutorial, ERROR, \"usage: %s ENDPOINT_NAME\", argv[0]);\n        return -1;\n    }\n\n    signal(SIGINT, signal_handler);\n\n    const anjay_configuration_t CONFIG = {\n        .endpoint_name = argv[1],\n        .in_buffer_size = 4000,\n        .out_buffer_size = 4000,\n        .msg_cache_size = 4000,\n\n        .trust_store_certs = avs_crypto_certificate_chain_info_from_file(\n                \"/etc/ssl/certs/ca-certificates.crt\"),\n        .est_reenroll_config = &(const anjay_est_reenroll_config_t) {\n            .enable = true,\n            .nominal_usage = 0.8,\n            .max_margin = avs_time_duration_from_scalar(7, AVS_TIME_DAY)\n        },\n        .est_cacerts_policy = ANJAY_EST_CACERTS_FOR_EST_SECURITY\n    };\n\n    g_anjay = anjay_new(&CONFIG);\n    if (!g_anjay) {\n        avs_log(tutorial, ERROR, \"Could not create Anjay object\");\n        return -1;\n    }\n\n    int result = 0;\n    // Setup necessary objects\n    if (anjay_security_object_install(g_anjay)\n            || anjay_server_object_install(g_anjay)) {\n        result = -1;\n        goto cleanup;\n    }\n\n    int restore_retval = restore_objects_if_possible(g_anjay);\n    if (restore_retval < 0\n            || (restore_retval > 0\n                && initialize_objects_with_default_settings(g_anjay))) {\n        result = -1;\n        goto cleanup;\n    }\n\n    result = anjay_event_loop_run(g_anjay,\n                                  avs_time_duration_from_scalar(1, AVS_TIME_S));\n\n    int persist_result = persist_objects_if_necessary(g_anjay);\n    if (!result) {\n        result = persist_result;\n    }\n\ncleanup:\n    anjay_delete(g_anjay);\n    return result;\n}\n"
  },
  {
    "path": "examples/commercial-features/CF-EST-PKCS11/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.16)\nproject(anjay-est-pkcs11 C)\n\nset(CMAKE_C_STANDARD 99)\nset(CMAKE_C_EXTENSIONS OFF)\n\nfind_package(anjay REQUIRED)\n\n# check if AVS_COMMONS_WITH_AVS_CRYPTO_PKI_ENGINE is enabled\nfile(WRITE ${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/CMakeTmp/avs_commons_check.c\n     \"#include <avsystem/commons/avs_commons_config.h>\\nint main() {\\n#ifndef AVS_COMMONS_WITH_AVS_CRYPTO_PKI_ENGINE\\nint error[-1];\\n#endif\\nreturn 0; }\\n\")\nget_target_property(INCLUDE_DIRS anjay INTERFACE_INCLUDE_DIRECTORIES)\ntry_compile(WITH_AVS_CRYPTO_PKI_ENGINE\n            ${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/CMakeTmp\n            ${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/CMakeTmp/avs_commons_check.c\n            CMAKE_FLAGS \"-DINCLUDE_DIRECTORIES=${INCLUDE_DIRS}\")\n\nif(WITH_AVS_CRYPTO_PKI_ENGINE)\n    add_executable(${PROJECT_NAME} src/main.c)\n    target_link_libraries(${PROJECT_NAME} PRIVATE anjay)\nendif()\n"
  },
  {
    "path": "examples/commercial-features/CF-EST-PKCS11/src/main.c",
    "content": "#include <anjay/anjay.h>\n#include <anjay/attr_storage.h>\n#include <anjay/security.h>\n#include <anjay/server.h>\n#include <avsystem/commons/avs_log.h>\n#include <avsystem/commons/avs_stream_file.h>\n\n#include <errno.h>\n#include <signal.h>\n#include <string.h>\n#include <time.h>\n#include <unistd.h>\n\nstatic anjay_t *volatile g_anjay;\n\nvoid signal_handler(int signum) {\n    if (signum == SIGINT && g_anjay) {\n        anjay_event_loop_interrupt(g_anjay);\n    }\n}\n\n#define PERSISTENCE_FILENAME \"anjay-est-pkcs11-persistence.dat\"\n\nint persist_objects_if_necessary(anjay_t *anjay) {\n    if ((!anjay_security_object_is_modified(anjay)\n         && !anjay_server_object_is_modified(anjay)\n         && !anjay_attr_storage_is_modified(anjay))\n            || !anjay_est_state_is_ready_for_persistence(anjay)) {\n        avs_log(tutorial, INFO,\n                \"Persistence not necessary - NOT persisting objects\");\n        return 0;\n    }\n\n    avs_log(tutorial, INFO, \"Persisting objects to %s\", PERSISTENCE_FILENAME);\n\n    avs_stream_t *file_stream =\n            avs_stream_file_create(PERSISTENCE_FILENAME, AVS_STREAM_FILE_WRITE);\n\n    if (!file_stream) {\n        avs_log(tutorial, ERROR, \"Could not open file for writing\");\n        return -1;\n    }\n\n    int result = -1;\n\n    if (avs_is_err(anjay_security_object_persist(anjay, file_stream))) {\n        avs_log(tutorial, ERROR, \"Could not persist Security Object\");\n        goto finish;\n    }\n\n    if (avs_is_err(anjay_server_object_persist(anjay, file_stream))) {\n        avs_log(tutorial, ERROR, \"Could not persist Server Object\");\n        goto finish;\n    }\n\n    if (avs_is_err(anjay_attr_storage_persist(anjay, file_stream))) {\n        avs_log(tutorial, ERROR, \"Could not persist LwM2M attribute storage\");\n        goto finish;\n    }\n\n    if (avs_is_err(anjay_est_state_persist(anjay, file_stream))) {\n        avs_log(tutorial, ERROR, \"Could not persist EST state\");\n        goto finish;\n    }\n\n    result = 0;\nfinish:\n    avs_stream_cleanup(&file_stream);\n    return result;\n}\n\nint restore_objects_if_possible(anjay_t *anjay) {\n    avs_log(tutorial, INFO, \"Attempting to restore objects from persistence\");\n    int result;\n\n    errno = 0;\n    if ((result = access(PERSISTENCE_FILENAME, F_OK))) {\n        switch (errno) {\n        case ENOENT:\n        case ENOTDIR:\n            // no persistence file means there is nothing to restore\n            return 1;\n        default:\n            // some other unpredicted error\n            return result;\n        }\n    } else if ((result = access(PERSISTENCE_FILENAME, R_OK))) {\n        // most likely file is just not readable\n        return result;\n    }\n\n    avs_stream_t *file_stream =\n            avs_stream_file_create(PERSISTENCE_FILENAME, AVS_STREAM_FILE_READ);\n\n    if (!file_stream) {\n        return -1;\n    }\n\n    result = -1;\n\n    if (avs_is_err(anjay_security_object_restore(anjay, file_stream))) {\n        avs_log(tutorial, ERROR, \"Could not restore Security Object\");\n        goto finish;\n    }\n\n    if (avs_is_err(anjay_server_object_restore(anjay, file_stream))) {\n        avs_log(tutorial, ERROR, \"Could not restore Server Object\");\n        goto finish;\n    }\n\n    if (avs_is_err(anjay_attr_storage_restore(anjay, file_stream))) {\n        avs_log(tutorial, ERROR, \"Could not restore LwM2M attribute storage\");\n        goto finish;\n    }\n\n    if (avs_is_err(anjay_est_state_restore(anjay, file_stream))) {\n        avs_log(tutorial, ERROR, \"Could not restore EST state\");\n        goto finish;\n    }\n\n    result = 0;\nfinish:\n    avs_stream_cleanup(&file_stream);\n    return result;\n}\n\nstatic int\nload_buffer_from_file(uint8_t **out, size_t *out_size, const char *filename) {\n    FILE *f = fopen(filename, \"rb\");\n    if (!f) {\n        avs_log(tutorial, ERROR, \"could not open %s\", filename);\n        return -1;\n    }\n    int result = -1;\n    if (fseek(f, 0, SEEK_END)) {\n        goto finish;\n    }\n    long size = ftell(f);\n    if (size < 0 || (unsigned long) size > SIZE_MAX || fseek(f, 0, SEEK_SET)) {\n        goto finish;\n    }\n    *out_size = (size_t) size;\n    if (!(*out = (uint8_t *) avs_malloc(*out_size))) {\n        goto finish;\n    }\n    if (fread(*out, *out_size, 1, f) != 1) {\n        avs_free(*out);\n        *out = NULL;\n        goto finish;\n    }\n    result = 0;\nfinish:\n    fclose(f);\n    if (result) {\n        avs_log(tutorial, ERROR, \"could not read %s\", filename);\n    }\n    return result;\n}\n\nstatic int initialize_objects_with_default_settings(anjay_t *anjay) {\n    anjay_security_instance_t security_instance = {\n        .bootstrap_server = true,\n        .server_uri = \"coaps://eu.iot.avsystem.cloud:5694\",\n        .security_mode = ANJAY_SECURITY_EST,\n        .public_cert = avs_crypto_certificate_chain_info_from_engine(\n                \"pkcs11:token=MyToken;object=InitialClientCert;pin-value=1234\"),\n        .private_key = avs_crypto_private_key_info_from_engine(\n                \"pkcs11:token=MyToken;object=InitialClientKey;pin-value=1234\")\n    };\n\n    int result = 0;\n\n    if (load_buffer_from_file((uint8_t **) &security_instance.server_public_key,\n                              &security_instance.server_public_key_size,\n                              \"server_cert.der\")) {\n        result = -1;\n        goto cleanup;\n    }\n\n    // Anjay will assign Instance ID automatically\n    anjay_iid_t security_instance_id = ANJAY_ID_INVALID;\n    if (anjay_security_object_add_instance(anjay, &security_instance,\n                                           &security_instance_id)) {\n        result = -1;\n    }\n\ncleanup:\n    avs_free((uint8_t *) security_instance.server_public_key);\n\n    return result;\n}\n\nstatic const char *est_crts_address_gen(void *arg,\n                                        const void *x509_der_data,\n                                        size_t x509_der_data_size) {\n    (void) x509_der_data;\n    (void) x509_der_data_size;\n\n    char *buf = (char *) arg;\n    sprintf(buf, \"pkcs11:token=MyToken;object=CaCert%d;pin-value=1234\", rand());\n    return buf;\n}\n\nint main(int argc, char *argv[]) {\n    if (argc != 2) {\n        avs_log(tutorial, ERROR, \"usage: %s ENDPOINT_NAME\", argv[0]);\n        return -1;\n    }\n\n    signal(SIGINT, signal_handler);\n\n    char EST_CACERTS_ADDRESS_BUF[256];\n\n    srand(time(NULL));\n\n    const anjay_configuration_t CONFIG = {\n        .endpoint_name = argv[1],\n        .in_buffer_size = 4000,\n        .out_buffer_size = 4000,\n        .msg_cache_size = 4000,\n\n        .trust_store_certs = avs_crypto_certificate_chain_info_from_file(\n                \"/etc/ssl/certs/ca-certificates.crt\"),\n        .est_reenroll_config = &(const anjay_est_reenroll_config_t) {\n            .enable = true,\n            .nominal_usage = 0.8,\n            .max_margin = avs_time_duration_from_scalar(7, AVS_TIME_DAY)\n        },\n        .est_cacerts_policy = ANJAY_EST_CACERTS_FOR_EST_SECURITY,\n\n        .est_engine_key_address =\n                \"pkcs11:token=MyToken;object=EstClientKey;pin-value=1234\",\n        .est_engine_cert_address =\n                \"pkcs11:token=MyToken;object=EstClientCert;pin-value=1234\",\n        .est_engine_cacerts_address_gen_cb = est_crts_address_gen,\n        .est_engine_cacerts_address_gen_cb_arg = EST_CACERTS_ADDRESS_BUF\n    };\n\n    g_anjay = anjay_new(&CONFIG);\n    if (!g_anjay) {\n        avs_log(tutorial, ERROR, \"Could not create Anjay object\");\n        return -1;\n    }\n\n    int result = 0;\n    // Setup necessary objects\n    if (anjay_security_object_install(g_anjay)\n            || anjay_server_object_install(g_anjay)) {\n        result = -1;\n        goto cleanup;\n    }\n\n    int restore_retval = restore_objects_if_possible(g_anjay);\n    if (restore_retval < 0\n            || (restore_retval > 0\n                && initialize_objects_with_default_settings(g_anjay))) {\n        result = -1;\n        goto cleanup;\n    }\n\n    result = anjay_event_loop_run(g_anjay,\n                                  avs_time_duration_from_scalar(1, AVS_TIME_S));\n\n    int persist_result = persist_objects_if_necessary(g_anjay);\n    if (!result) {\n        result = persist_result;\n    }\n\ncleanup:\n    anjay_delete(g_anjay);\n    return result;\n}\n"
  },
  {
    "path": "examples/commercial-features/CF-NIDD/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.16)\nproject(anjay-nidd C)\n\nset(CMAKE_C_STANDARD 99)\nset(CMAKE_C_EXTENSIONS OFF)\n\nfind_package(anjay REQUIRED)\n\nadd_executable(${PROJECT_NAME}\n                src/main.c\n                src/nidd_demo_driver.h\n                src/nidd_demo_driver.c)\ntarget_link_libraries(${PROJECT_NAME} PRIVATE anjay)\n"
  },
  {
    "path": "examples/commercial-features/CF-NIDD/src/main.c",
    "content": "#include <anjay/anjay.h>\n#include <anjay/security.h>\n#include <anjay/server.h>\n#include <avsystem/commons/avs_log.h>\n\n#include \"nidd_demo_driver.h\"\n\n// Installs Security Object and adds an instance of it.\n// An instance of Security Object provides information needed to connect to\n// LwM2M server.\nstatic int setup_security_object(anjay_t *anjay) {\n    if (anjay_security_object_install(anjay)) {\n        return -1;\n    }\n\n    const anjay_security_instance_t security_instance = {\n        .ssid = 1,\n        .server_uri = \"coap+nidd://\",\n        .security_mode = ANJAY_SECURITY_NOSEC\n    };\n\n    // Anjay will assign Instance ID automatically\n    anjay_iid_t security_instance_id = ANJAY_ID_INVALID;\n    if (anjay_security_object_add_instance(anjay, &security_instance,\n                                           &security_instance_id)) {\n        return -1;\n    }\n\n    return 0;\n}\n\n// Installs Server Object and adds an instance of it.\n// An instance of Server Object provides the data related to a LwM2M Server.\nstatic int setup_server_object(anjay_t *anjay) {\n    if (anjay_server_object_install(anjay)) {\n        return -1;\n    }\n\n    const anjay_server_instance_t server_instance = {\n        // Server Short ID\n        .ssid = 1,\n        // Client will send Update message often than every 60 seconds\n        .lifetime = 60,\n        // Disable Default Minimum Period resource\n        .default_min_period = -1,\n        // Disable Default Maximum Period resource\n        .default_max_period = -1,\n        // Disable Disable Timeout resource\n        .disable_timeout = -1,\n        // Sets preferred transport to NIDD\n        .binding = \"N\"\n    };\n\n    // Anjay will assign Instance ID automatically\n    anjay_iid_t server_instance_id = ANJAY_ID_INVALID;\n    if (anjay_server_object_add_instance(anjay, &server_instance,\n                                         &server_instance_id)) {\n        return -1;\n    }\n\n    return 0;\n}\n\nint main(int argc, char *argv[]) {\n    if (argc != 3) {\n        avs_log(tutorial, ERROR, \"usage: %s ENDPOINT_NAME MODEM_PATH\", argv[0]);\n        return -1;\n    }\n\n    anjay_nidd_driver_t **demo_nidd_driver = demo_nidd_driver_create(argv[2]);\n\n    if (!demo_nidd_driver) {\n        avs_log(tutorial, ERROR, \"Could not create NIDD driver\");\n        return -1;\n    }\n\n    const anjay_configuration_t CONFIG = {\n        .endpoint_name = argv[1],\n        .in_buffer_size = 4000,\n        .out_buffer_size = 4000,\n        .msg_cache_size = 4000,\n        .nidd_driver = *demo_nidd_driver\n    };\n\n    anjay_t *anjay = anjay_new(&CONFIG);\n    if (!anjay) {\n        avs_log(tutorial, ERROR, \"Could not create Anjay object\");\n        return -1;\n    }\n\n    int result = 0;\n    // Setup necessary objects\n    if (setup_security_object(anjay) || setup_server_object(anjay)) {\n        result = -1;\n    }\n\n    if (!result) {\n        result = anjay_event_loop_run(\n                anjay, avs_time_duration_from_scalar(1, AVS_TIME_S));\n    }\n\n    anjay_delete(anjay);\n    demo_nidd_driver_cleanup(demo_nidd_driver);\n    return result;\n}\n"
  },
  {
    "path": "examples/commercial-features/CF-NIDD/src/nidd_demo_driver.c",
    "content": "#include <anjay/anjay_config.h>\n#include <avsystem/commons/avs_log.h>\n\n#include <avsystem/commons/avs_buffer.h>\n#include <avsystem/commons/avs_errno.h>\n\n#include \"nidd_demo_driver.h\"\n\n#include <anjay/bg96_nidd.h>\n\n#include <ctype.h>\n#include <errno.h>\n#include <fcntl.h>\n#include <poll.h>\n#include <unistd.h>\n\ntypedef struct {\n    avs_buffer_t *buffer;\n} fifo_t;\n\nstatic int fifo_init(fifo_t *fifo) {\n    if (fifo->buffer) {\n        return -1;\n    }\n    return avs_buffer_create(&fifo->buffer, 4096);\n}\n\nstatic void fifo_destroy(fifo_t *fifo) {\n    avs_buffer_free(&fifo->buffer);\n}\n\nstatic int fifo_find_off(fifo_t *fifo, char ch, size_t *out_off) {\n    const char *start = avs_buffer_data(fifo->buffer);\n    char *ptr = (char *) memchr(start, ch, avs_buffer_data_size(fifo->buffer));\n    if (ptr) {\n        *out_off = (size_t) (ptr - start);\n        return 0;\n    }\n    return -1;\n}\n\nstatic void fifo_pop_n(fifo_t *fifo, char *out_buffer, size_t size, size_t n) {\n    assert(n <= avs_buffer_data_size(fifo->buffer));\n    assert(n <= size);\n    (void) size;\n\n    memcpy(out_buffer, avs_buffer_data(fifo->buffer), n);\n    avs_buffer_consume_bytes(fifo->buffer, n);\n}\n\nstatic void fifo_discard_n(fifo_t *fifo, size_t n) {\n    assert(n <= avs_buffer_data_size(fifo->buffer));\n    avs_buffer_consume_bytes(fifo->buffer, n);\n}\n\n// NOTE: If out_line is too small to hold the entire line,\n// excess characters will be discarded\nstatic int fifo_pop_line(fifo_t *fifo, char *out_line, size_t out_size) {\n    assert(out_size > 0);\n\n    size_t line_size;\n    if (!fifo_find_off(fifo, '\\n', &line_size)\n            || !fifo_find_off(fifo, '\\r', &line_size)) {\n        ++line_size;\n    } else if (avs_buffer_space_left(fifo->buffer) == 0) {\n        avs_log(tutorial, WARNING,\n                \"FIFO buffer full, treating received data as a line\");\n        line_size = avs_buffer_data_size(fifo->buffer);\n    } else {\n        line_size = 0;\n    }\n\n    size_t bytes_to_pop = AVS_MIN(out_size - 1, line_size);\n    fifo_pop_n(fifo, out_line, out_size - 1, bytes_to_pop);\n    out_line[bytes_to_pop] = '\\0';\n\n    // Discard excess bytes, if any\n    if (line_size != bytes_to_pop) {\n        fifo_discard_n(fifo, line_size - bytes_to_pop);\n        avs_log(tutorial, WARNING, \"buffer size too small to hold the line\");\n        return 1;\n    }\n    return 0;\n}\n\nstatic void fifo_strip_nullbytes(fifo_t *fifo) {\n    size_t buffer_size = avs_buffer_data_size(fifo->buffer);\n    char *buffer = avs_buffer_raw_insert_ptr(fifo->buffer) - buffer_size;\n    ssize_t block_end_offset = (ssize_t) buffer_size;\n    assert(block_end_offset >= 0);\n    ssize_t moved_by = 0;\n    while (block_end_offset > 0) {\n        ssize_t first_null_offset = block_end_offset;\n        while (first_null_offset > 0 && buffer[first_null_offset - 1] == '\\0') {\n            --first_null_offset;\n        }\n        ssize_t first_nonnull_offset = first_null_offset;\n        while (first_nonnull_offset > 0\n               && buffer[first_nonnull_offset - 1] != '\\0') {\n            --first_nonnull_offset;\n        }\n        if (first_null_offset != block_end_offset) {\n            assert(first_null_offset < block_end_offset);\n            moved_by += block_end_offset - first_null_offset;\n            if (first_nonnull_offset != first_null_offset) {\n                assert(first_nonnull_offset < first_null_offset);\n                memmove(buffer + first_nonnull_offset + moved_by,\n                        buffer + first_nonnull_offset,\n                        (size_t) (first_null_offset - first_nonnull_offset));\n            }\n        }\n        block_end_offset = first_nonnull_offset;\n    }\n    assert(moved_by >= 0);\n    if (moved_by > 0) {\n        avs_buffer_consume_bytes(fifo->buffer, (size_t) moved_by);\n    }\n}\n\nstatic avs_error_t fifo_push_read(fifo_t *fifo, int fd) {\n    // This shall be handled in fifo_pop_line()\n    assert(avs_buffer_space_left(fifo->buffer) > 0);\n    ssize_t bytes_read = read(fd, avs_buffer_raw_insert_ptr(fifo->buffer), 1);\n    if (bytes_read < 0) {\n        return avs_errno(AVS_EIO);\n    } else if (bytes_read == 0) {\n        return AVS_EOF;\n    } else {\n        assert(bytes_read == 1);\n        avs_buffer_advance_ptr(fifo->buffer, 1);\n        fifo_strip_nullbytes(fifo);\n        return AVS_OK;\n    }\n}\n\ntypedef struct {\n    anjay_nidd_driver_t *bg96_nidd;\n    int pts_fd;\n    fifo_t fifo;\n} demo_nidd_driver_t;\n\nstatic void trim_inplace(char *buffer) {\n    assert(buffer);\n\n    size_t len = strlen(buffer);\n    for (char *ch = buffer + len - 1;\n         ch >= buffer && isspace((unsigned char) *ch);\n         --ch) {\n        *ch = '\\0';\n    }\n    char *first_nonblank = buffer;\n    for (char *ch = buffer; *ch; ++ch) {\n        if (!isspace((unsigned char) *ch)) {\n            first_nonblank = ch;\n            break;\n        }\n    }\n    memmove(buffer, first_nonblank, strlen(first_nonblank) + 1);\n}\n\nstatic int modem_getline(void *user_context,\n                         char *out_line_buffer,\n                         size_t buffer_size,\n                         avs_time_monotonic_t deadline) {\n    demo_nidd_driver_t *driver = (demo_nidd_driver_t *) user_context;\n\n    struct pollfd fd;\n    fd.fd = driver->pts_fd;\n    fd.events = POLLIN;\n\n    int64_t timeout_ms;\n    int result;\n\n    // Note: this loop is not signal-safe.\n    do {\n        if (avs_time_duration_to_scalar(\n                    &timeout_ms, AVS_TIME_MS,\n                    avs_time_monotonic_diff(deadline,\n                                            avs_time_monotonic_now()))) {\n            timeout_ms = -1;\n        } else if (timeout_ms < 0) {\n            timeout_ms = 0;\n        }\n\n        while (true) {\n            result = fifo_pop_line(&driver->fifo, out_line_buffer, buffer_size);\n            if (*out_line_buffer == '\\0') {\n                break;\n            }\n            trim_inplace(out_line_buffer);\n            if (*out_line_buffer != '\\0') {\n                avs_log(tutorial, DEBUG, \"[MODEM] recv: %s\", out_line_buffer);\n                return result;\n            }\n        }\n\n        assert(timeout_ms <= INT_MAX);\n        if ((result = poll(&fd, 1, (int) timeout_ms)) == 1) {\n            avs_error_t err = fifo_push_read(&driver->fifo, driver->pts_fd);\n            if (avs_is_eof(err)) {\n                avs_log(tutorial, DEBUG, \"[MODEM] recv: EOF\");\n                return -1;\n            } else if (avs_is_err(err)) {\n                return -1;\n            }\n        } else if (result < 0) {\n            return -1;\n        }\n        // read up until timeout becomes 0, or if it is 0, read up until there's\n        // something to read.\n    } while (timeout_ms != 0 || result == 1);\n\n    avs_log(tutorial, DEBUG, \"[MODEM] recv: timeout\");\n    assert(buffer_size > 0);\n    out_line_buffer[0] = '\\0';\n    return 0;\n}\n\nstatic inline bool is_blank(const char *buffer) {\n    for (const char *ch = buffer; *ch; ++ch) {\n        if (!isspace(*ch)) {\n            return false;\n        }\n    }\n    return true;\n}\n\nstatic int modem_write(void *user_context, const char *buffer) {\n    demo_nidd_driver_t *driver = (demo_nidd_driver_t *) user_context;\n    // Note: not signal-safe.\n    ssize_t written = write(driver->pts_fd, buffer, strlen(buffer));\n    if (written != (ssize_t) strlen(buffer)) {\n        return -1;\n    }\n    if (!is_blank(buffer)) {\n        avs_log(tutorial, DEBUG, \"[MODEM] sent: %s\", buffer);\n    }\n    return 0;\n}\n\nstatic int modem_get_parameter(void *user_context,\n                               anjay_bg96_nidd_parameter_t parameter,\n                               char *out_value,\n                               size_t size) {\n    (void) user_context;\n    static const char APN[] = \"test\";\n    if (parameter == ANJAY_BG96_NIDD_APN) {\n        if (size < sizeof(APN)) {\n            return -1;\n        }\n        memcpy(out_value, APN, sizeof(APN));\n        return 0;\n    }\n    *out_value = '\\0';\n    return 0;\n}\n\nstatic void driver_cleanup(demo_nidd_driver_t *driver) {\n    if (driver->pts_fd >= 0) {\n        close(driver->pts_fd);\n    }\n    anjay_nidd_driver_cleanup(&driver->bg96_nidd);\n    fifo_destroy(&driver->fifo);\n    avs_free(driver);\n}\n\nanjay_nidd_driver_t **demo_nidd_driver_create(const char *modem_device) {\n    demo_nidd_driver_t *driver =\n            (demo_nidd_driver_t *) avs_calloc(1, sizeof(*driver));\n    if (!driver) {\n        return NULL;\n    }\n\n    const anjay_bg96_nidd_config_t config = {\n        .system_descriptor = &driver->pts_fd,\n        .user_context = driver,\n        .modem_getline = modem_getline,\n        .modem_write = modem_write,\n        .modem_get_parameter = modem_get_parameter\n    };\n    if (fifo_init(&driver->fifo)) {\n        avs_log(tutorial, ERROR, \"could not initialize FIFO\");\n        goto fail;\n    }\n    if ((driver->pts_fd = open(modem_device, O_RDWR)) < 0) {\n        avs_log(tutorial, ERROR, \"could not open modem device %s: %s\",\n                modem_device, strerror(errno));\n        goto fail;\n    }\n\n    if (!(driver->bg96_nidd = anjay_bg96_nidd_driver_create(&config))) {\n        avs_log(tutorial, ERROR, \"could not create AT NIDD driver\");\n        goto fail;\n    }\n    return &driver->bg96_nidd;\n\nfail:\n    driver_cleanup(driver);\n    return NULL;\n}\n\nvoid demo_nidd_driver_cleanup(anjay_nidd_driver_t **driver) {\n    if (!driver || !*driver) {\n        return;\n    }\n    demo_nidd_driver_t *demo_driver =\n            AVS_CONTAINER_OF(driver, demo_nidd_driver_t, bg96_nidd);\n    driver_cleanup(demo_driver);\n}\n"
  },
  {
    "path": "examples/commercial-features/CF-NIDD/src/nidd_demo_driver.h",
    "content": "#ifndef DEMO_NIDD_DEMO_DRIVER_H\n#define DEMO_NIDD_DEMO_DRIVER_H\n\n#include <anjay/core.h>\n#include <anjay/nidd.h>\n\n/**\n * Simple NIDD driver that connects to the PTY of a modem device, responsible\n * for NIDD connectivity.\n *\n * @param modem_device  Path to the modem pseudo-terminal device, e.g.\n *                      \"/dev/pts/1\".\n *\n * @returns pointer to a newly created NIDD driver.\n */\nanjay_nidd_driver_t **demo_nidd_driver_create(const char *modem_device);\n\nvoid demo_nidd_driver_cleanup(anjay_nidd_driver_t **driver);\n\n#endif /* DEMO_NIDD_DEMO_DRIVER_H */\n"
  },
  {
    "path": "examples/commercial-features/CF-OSCORE/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.16)\nproject(anjay-oscore C)\n\nset(CMAKE_C_STANDARD 99)\nset(CMAKE_C_EXTENSIONS OFF)\n\nfind_package(anjay REQUIRED)\n\nadd_executable(${PROJECT_NAME} src/main.c)\ntarget_link_libraries(${PROJECT_NAME} PRIVATE anjay)\n"
  },
  {
    "path": "examples/commercial-features/CF-OSCORE/src/main.c",
    "content": "#include <anjay/anjay.h>\n#include <anjay/attr_storage.h>\n#include <anjay/oscore.h>\n#include <anjay/security.h>\n#include <anjay/server.h>\n#include <avsystem/commons/avs_log.h>\n#include <avsystem/commons/avs_stream_file.h>\n\n#include <errno.h>\n#include <signal.h>\n#include <string.h>\n#include <unistd.h>\n\nstatic anjay_t *volatile g_anjay;\n\nvoid signal_handler(int signum) {\n    if (signum == SIGINT && g_anjay) {\n        anjay_event_loop_interrupt(g_anjay);\n    }\n}\n\n#define PERSISTENCE_FILENAME \"anjay-oscore-persistence.dat\"\n\nint persist_objects(anjay_t *anjay) {\n    avs_log(tutorial, INFO, \"Persisting objects to %s\", PERSISTENCE_FILENAME);\n\n    avs_stream_t *file_stream =\n            avs_stream_file_create(PERSISTENCE_FILENAME, AVS_STREAM_FILE_WRITE);\n\n    if (!file_stream) {\n        avs_log(tutorial, ERROR, \"Could not open file for writing\");\n        return -1;\n    }\n\n    int result = -1;\n\n    if (avs_is_err(anjay_oscore_object_persist(anjay, file_stream))) {\n        avs_log(tutorial, ERROR, \"Could not persist OSCORE Object\");\n        goto finish;\n    }\n\n    if (avs_is_err(anjay_security_object_persist(anjay, file_stream))) {\n        avs_log(tutorial, ERROR, \"Could not persist Security Object\");\n        goto finish;\n    }\n\n    if (avs_is_err(anjay_server_object_persist(anjay, file_stream))) {\n        avs_log(tutorial, ERROR, \"Could not persist Server Object\");\n        goto finish;\n    }\n\n    if (avs_is_err(anjay_attr_storage_persist(anjay, file_stream))) {\n        avs_log(tutorial, ERROR, \"Could not persist LwM2M attribute storage\");\n        goto finish;\n    }\n\n    result = 0;\nfinish:\n    avs_stream_cleanup(&file_stream);\n    return result;\n}\n\nint restore_objects_if_possible(anjay_t *anjay) {\n    avs_log(tutorial, INFO, \"Attempting to restore objects from persistence\");\n    int result;\n\n    errno = 0;\n    if ((result = access(PERSISTENCE_FILENAME, F_OK))) {\n        switch (errno) {\n        case ENOENT:\n        case ENOTDIR:\n            // no persistence file means there is nothing to restore\n            return 1;\n        default:\n            // some other unpredicted error\n            return result;\n        }\n    } else if ((result = access(PERSISTENCE_FILENAME, R_OK))) {\n        // most likely file is just not readable\n        return result;\n    }\n\n    avs_stream_t *file_stream =\n            avs_stream_file_create(PERSISTENCE_FILENAME, AVS_STREAM_FILE_READ);\n\n    if (!file_stream) {\n        return -1;\n    }\n\n    result = -1;\n\n    if (avs_is_err(anjay_oscore_object_restore(anjay, file_stream))) {\n        avs_log(tutorial, ERROR, \"Could not restore OSCORE Object\");\n        goto finish;\n    }\n\n    if (avs_is_err(anjay_security_object_restore(anjay, file_stream))) {\n        avs_log(tutorial, ERROR, \"Could not restore Security Object\");\n        goto finish;\n    }\n\n    if (avs_is_err(anjay_server_object_restore(anjay, file_stream))) {\n        avs_log(tutorial, ERROR, \"Could not restore Server Object\");\n        goto finish;\n    }\n\n    if (avs_is_err(anjay_attr_storage_restore(anjay, file_stream))) {\n        avs_log(tutorial, ERROR, \"Could not restore LwM2M attribute storage\");\n        goto finish;\n    }\n\n    result = 0;\nfinish:\n    avs_stream_cleanup(&file_stream);\n    return result;\n}\n\nstatic int initialize_objects_with_default_settings(anjay_t *anjay) {\n    const anjay_server_instance_t server_instance = {\n        .ssid = 1,\n        .lifetime = 60,\n        .default_min_period = -1,\n        .default_max_period = -1,\n        .disable_timeout = -1,\n        .binding = \"U\"\n    };\n\n    anjay_iid_t server_instance_id = ANJAY_ID_INVALID;\n    if (anjay_server_object_add_instance(anjay, &server_instance,\n                                         &server_instance_id)) {\n        return -1;\n    }\n\n    anjay_oscore_instance_t oscore_instance = {\n        .master_secret = \"Ma$T3Rs3CR3t\",\n        .master_salt = \"Ma$T3Rs4LT\",\n        .sender_id = \"15\",\n        .recipient_id = \"25\"\n    };\n\n    anjay_iid_t oscore_instance_id = ANJAY_ID_INVALID;\n    if (anjay_oscore_add_instance(anjay, &oscore_instance,\n                                  &oscore_instance_id)) {\n        return -1;\n    }\n\n    anjay_security_instance_t security_instance = {\n        .ssid = 1,\n        .server_uri = \"coap://eu.iot.avsystem.cloud:5683\",\n        .security_mode = ANJAY_SECURITY_NOSEC,\n        .oscore_iid = &oscore_instance_id\n    };\n\n    anjay_iid_t security_instance_id = ANJAY_ID_INVALID;\n    if (anjay_security_object_add_instance(anjay, &security_instance,\n                                           &security_instance_id)) {\n        return -1;\n    }\n\n    return 0;\n}\n\nint main(int argc, char *argv[]) {\n    if (argc != 2) {\n        avs_log(tutorial, ERROR, \"usage: %s ENDPOINT_NAME\", argv[0]);\n        return -1;\n    }\n\n    signal(SIGINT, signal_handler);\n\n    const anjay_configuration_t CONFIG = {\n        .endpoint_name = argv[1],\n        .in_buffer_size = 4000,\n        .out_buffer_size = 4000,\n        .msg_cache_size = 4000,\n    };\n\n    g_anjay = anjay_new(&CONFIG);\n    if (!g_anjay) {\n        avs_log(tutorial, ERROR, \"Could not create Anjay object\");\n        return -1;\n    }\n\n    int result = 0;\n    // Setup necessary objects\n    if (anjay_security_object_install(g_anjay)\n            || anjay_server_object_install(g_anjay)\n            || anjay_oscore_object_install(g_anjay)) {\n        result = -1;\n        goto cleanup;\n    }\n\n    int restore_retval = restore_objects_if_possible(g_anjay);\n    if (restore_retval < 0\n            || (restore_retval > 0\n                && initialize_objects_with_default_settings(g_anjay))) {\n        result = -1;\n        goto cleanup;\n    }\n\n    result = anjay_event_loop_run(g_anjay,\n                                  avs_time_duration_from_scalar(1, AVS_TIME_S));\n\n    int persist_result = persist_objects(g_anjay);\n    if (!result) {\n        result = persist_result;\n    }\n\ncleanup:\n    anjay_delete(g_anjay);\n    return result;\n}\n"
  },
  {
    "path": "examples/commercial-features/CF-PKCS11/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.16)\nproject(anjay-pkcs11 C)\n\nset(CMAKE_C_STANDARD 99)\nset(CMAKE_C_EXTENSIONS OFF)\n\nfind_package(anjay REQUIRED)\n\n# check if AVS_COMMONS_WITH_AVS_CRYPTO_PKI_ENGINE is enabled\nfile(WRITE ${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/CMakeTmp/avs_commons_check.c\n     \"#include <avsystem/commons/avs_commons_config.h>\\nint main() {\\n#ifndef AVS_COMMONS_WITH_AVS_CRYPTO_PKI_ENGINE\\nint error[-1];\\n#endif\\nreturn 0; }\\n\")\nget_target_property(INCLUDE_DIRS anjay INTERFACE_INCLUDE_DIRECTORIES)\ntry_compile(WITH_AVS_CRYPTO_PKI_ENGINE\n            ${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/CMakeTmp\n            ${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/CMakeTmp/avs_commons_check.c\n            CMAKE_FLAGS \"-DINCLUDE_DIRECTORIES=${INCLUDE_DIRS}\")\n\nif(WITH_AVS_CRYPTO_PKI_ENGINE)\n    add_executable(${PROJECT_NAME} src/main.c)\n    target_link_libraries(${PROJECT_NAME} PRIVATE anjay)\nendif()\n"
  },
  {
    "path": "examples/commercial-features/CF-PKCS11/src/main.c",
    "content": "#include <anjay/anjay.h>\n#include <anjay/security.h>\n#include <anjay/server.h>\n#include <avsystem/commons/avs_log.h>\n\n#define KEY_QUERY \"pkcs11:token=MyToken;object=ClientKey;pin-value=1234\"\n#define CERTIFICATE_QUERY \\\n    \"pkcs11:token=MyToken;object=ClientCert;pin-value=1234\"\n\n// Installs Security Object and adds an instance of it.\n// An instance of Security Object provides information needed to connect to\n// LwM2M server.\nstatic int setup_security_object(anjay_t *anjay) {\n    if (anjay_security_object_install(anjay)) {\n        return -1;\n    }\n\n    const anjay_security_instance_t security_instance = {\n        .ssid = 1,\n        .server_uri = \"coaps://eu.iot.avsystem.cloud:5684\",\n        .security_mode = ANJAY_SECURITY_CERTIFICATE,\n        .public_cert = avs_crypto_certificate_chain_info_from_engine(\n                CERTIFICATE_QUERY),\n        .private_key = avs_crypto_private_key_info_from_engine(KEY_QUERY)\n    };\n\n    // Anjay will assign Instance ID automatically\n    anjay_iid_t security_instance_id = ANJAY_ID_INVALID;\n    if (anjay_security_object_add_instance(anjay, &security_instance,\n                                           &security_instance_id)) {\n        return -1;\n    }\n\n    return 0;\n}\n\n// Installs Server Object and adds an instance of it.\n// An instance of Server Object provides the data related to a LwM2M Server.\nstatic int setup_server_object(anjay_t *anjay) {\n    if (anjay_server_object_install(anjay)) {\n        return -1;\n    }\n\n    const anjay_server_instance_t server_instance = {\n        // Server Short ID\n        .ssid = 1,\n        // Client will send Update message often than every 60 seconds\n        .lifetime = 60,\n        // Disable Default Minimum Period resource\n        .default_min_period = -1,\n        // Disable Default Maximum Period resource\n        .default_max_period = -1,\n        // Disable Disable Timeout resource\n        .disable_timeout = -1,\n        // Sets preferred transport to UDP\n        .binding = \"U\"\n    };\n\n    // Anjay will assign Instance ID automatically\n    anjay_iid_t server_instance_id = ANJAY_ID_INVALID;\n    if (anjay_server_object_add_instance(anjay, &server_instance,\n                                         &server_instance_id)) {\n        return -1;\n    }\n\n    return 0;\n}\n\nint main(int argc, char *argv[]) {\n    if (argc != 2) {\n        avs_log(tutorial, ERROR, \"usage: %s ENDPOINT_NAME\", argv[0]);\n        return -1;\n    }\n\n    const anjay_configuration_t CONFIG = {\n        .endpoint_name = argv[1],\n        .in_buffer_size = 4000,\n        .out_buffer_size = 4000,\n        .msg_cache_size = 4000\n    };\n\n    anjay_t *anjay = anjay_new(&CONFIG);\n    if (!anjay) {\n        avs_log(tutorial, ERROR, \"Could not create Anjay object\");\n        return -1;\n    }\n\n    int result = 0;\n    // Setup necessary objects\n    if (setup_security_object(anjay) || setup_server_object(anjay)) {\n        result = -1;\n    }\n\n    if (!result) {\n        result = anjay_event_loop_run(\n                anjay, avs_time_duration_from_scalar(1, AVS_TIME_S));\n    }\n\n    anjay_delete(anjay);\n    return result;\n}\n"
  },
  {
    "path": "examples/commercial-features/CF-PSA-PKI/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.16)\nproject(anjay-psa-pki C)\n\nset(CMAKE_C_STANDARD 99)\nset(CMAKE_C_EXTENSIONS OFF)\n\nfind_package(anjay REQUIRED)\n\nadd_executable(${PROJECT_NAME} src/main.c)\ntarget_link_libraries(${PROJECT_NAME} PRIVATE anjay)\n"
  },
  {
    "path": "examples/commercial-features/CF-PSA-PKI/src/main.c",
    "content": "#include <anjay/anjay.h>\n#include <anjay/security.h>\n#include <anjay/server.h>\n#include <avsystem/commons/avs_crypto_pki.h>\n#include <avsystem/commons/avs_log.h>\n\n#include <signal.h>\n\n#define KEY_QUERY \"kid=0x00000001\"\n#define CERTIFICATE_QUERY \"kid=0x00000002\"\n\nstatic anjay_t *volatile g_anjay;\n\nvoid signal_handler(int signum) {\n    if (signum == SIGINT && g_anjay) {\n        anjay_event_loop_interrupt(g_anjay);\n    }\n}\n\n// Installs Security Object and adds an instance of it.\n// An instance of Security Object provides information needed to connect to\n// LwM2M server.\nstatic int setup_security_object(anjay_t *anjay) {\n    if (anjay_security_object_install(anjay)) {\n        return -1;\n    }\n\n    const anjay_security_instance_t security_instance = {\n        .ssid = 1,\n        .server_uri = \"coaps://eu.iot.avsystem.cloud:5684\",\n        .security_mode = ANJAY_SECURITY_CERTIFICATE,\n        .public_cert = avs_crypto_certificate_chain_info_from_engine(\n                CERTIFICATE_QUERY),\n        .private_key = avs_crypto_private_key_info_from_engine(KEY_QUERY),\n    };\n\n    // Anjay will assign Instance ID automatically\n    anjay_iid_t security_instance_id = ANJAY_ID_INVALID;\n    if (anjay_security_object_add_instance(anjay, &security_instance,\n                                           &security_instance_id)) {\n        return -1;\n    }\n\n    return 0;\n}\n\n// Installs Server Object and adds an instance of it.\n// An instance of Server Object provides the data related to a LwM2M Server.\nstatic int setup_server_object(anjay_t *anjay) {\n    if (anjay_server_object_install(anjay)) {\n        return -1;\n    }\n\n    const anjay_server_instance_t server_instance = {\n        // Server Short ID\n        .ssid = 1,\n        // Client will send Update message often than every 60 seconds\n        .lifetime = 60,\n        // Disable Default Minimum Period resource\n        .default_min_period = -1,\n        // Disable Default Maximum Period resource\n        .default_max_period = -1,\n        // Disable Disable Timeout resource\n        .disable_timeout = -1,\n        // Sets preferred transport to UDP\n        .binding = \"U\"\n    };\n\n    // Anjay will assign Instance ID automatically\n    anjay_iid_t server_instance_id = ANJAY_ID_INVALID;\n    if (anjay_server_object_add_instance(anjay, &server_instance,\n                                         &server_instance_id)) {\n        return -1;\n    }\n\n    return 0;\n}\n\nint main(int argc, char *argv[]) {\n    int result = 0;\n\n    if (argc != 2) {\n        avs_log(tutorial, ERROR, \"usage: %s ENDPOINT_NAME\", argv[0]);\n        return -1;\n    }\n\n    signal(SIGINT, signal_handler);\n\n    const anjay_configuration_t CONFIG = {\n        .endpoint_name = argv[1],\n        .in_buffer_size = 4000,\n        .out_buffer_size = 4000,\n        .msg_cache_size = 4000\n    };\n\n    g_anjay = anjay_new(&CONFIG);\n    if (!g_anjay) {\n        avs_log(tutorial, ERROR, \"Could not create Anjay object\");\n        return -1;\n    }\n\n    // Setup necessary objects\n    if (setup_security_object(g_anjay) || setup_server_object(g_anjay)) {\n        result = -1;\n    }\n\n    if (!result) {\n        result = anjay_event_loop_run(\n                g_anjay, avs_time_duration_from_scalar(1, AVS_TIME_S));\n    }\n\n    anjay_delete(g_anjay);\n\n    return result;\n}\n"
  },
  {
    "path": "examples/commercial-features/CF-PSA-PSK/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.16)\nproject(anjay-psa-psk C)\n\nset(CMAKE_C_STANDARD 99)\nset(CMAKE_C_EXTENSIONS OFF)\n\nfind_package(anjay REQUIRED)\n\nadd_executable(${PROJECT_NAME} src/main.c)\ntarget_link_libraries(${PROJECT_NAME} PRIVATE anjay)\n"
  },
  {
    "path": "examples/commercial-features/CF-PSA-PSK/src/main.c",
    "content": "#include <anjay/anjay.h>\n#include <anjay/security.h>\n#include <anjay/server.h>\n#include <avsystem/commons/avs_crypto_psk.h>\n#include <avsystem/commons/avs_log.h>\n\n#include <signal.h>\n\n#define IDENTITY_QUERY \"kid=0x00000001\"\n#define KEY_QUERY \"kid=0x00000002\"\n\nstatic anjay_t *volatile g_anjay;\n\nvoid signal_handler(int signum) {\n    if (signum == SIGINT && g_anjay) {\n        anjay_event_loop_interrupt(g_anjay);\n    }\n}\n\n// Installs Security Object and adds an instance of it.\n// An instance of Security Object provides information needed to connect to\n// LwM2M server.\nstatic int setup_security_object(anjay_t *anjay) {\n    if (anjay_security_object_install(anjay)) {\n        return -1;\n    }\n\n    const anjay_security_instance_t security_instance = {\n        .ssid = 1,\n        .server_uri = \"coaps://eu.iot.avsystem.cloud:5684\",\n        .security_mode = ANJAY_SECURITY_PSK,\n        .psk_identity =\n                avs_crypto_psk_identity_info_from_engine(IDENTITY_QUERY),\n        .psk_key = avs_crypto_psk_key_info_from_engine(KEY_QUERY),\n    };\n\n    // Anjay will assign Instance ID automatically\n    anjay_iid_t security_instance_id = ANJAY_ID_INVALID;\n    if (anjay_security_object_add_instance(anjay, &security_instance,\n                                           &security_instance_id)) {\n        return -1;\n    }\n\n    return 0;\n}\n\n// Installs Server Object and adds an instance of it.\n// An instance of Server Object provides the data related to a LwM2M Server.\nstatic int setup_server_object(anjay_t *anjay) {\n    if (anjay_server_object_install(anjay)) {\n        return -1;\n    }\n\n    const anjay_server_instance_t server_instance = {\n        // Server Short ID\n        .ssid = 1,\n        // Client will send Update message often than every 60 seconds\n        .lifetime = 60,\n        // Disable Default Minimum Period resource\n        .default_min_period = -1,\n        // Disable Default Maximum Period resource\n        .default_max_period = -1,\n        // Disable Disable Timeout resource\n        .disable_timeout = -1,\n        // Sets preferred transport to UDP\n        .binding = \"U\"\n    };\n\n    // Anjay will assign Instance ID automatically\n    anjay_iid_t server_instance_id = ANJAY_ID_INVALID;\n    if (anjay_server_object_add_instance(anjay, &server_instance,\n                                         &server_instance_id)) {\n        return -1;\n    }\n\n    return 0;\n}\n\nint main(int argc, char *argv[]) {\n    int result = 0;\n\n    if (argc != 2) {\n        avs_log(tutorial, ERROR, \"usage: %s ENDPOINT_NAME\", argv[0]);\n        return -1;\n    }\n\n    signal(SIGINT, signal_handler);\n\n    const anjay_configuration_t CONFIG = {\n        .endpoint_name = argv[1],\n        .in_buffer_size = 4000,\n        .out_buffer_size = 4000,\n        .msg_cache_size = 4000\n    };\n\n    g_anjay = anjay_new(&CONFIG);\n    if (!g_anjay) {\n        avs_log(tutorial, ERROR, \"Could not create Anjay object\");\n        return -1;\n    }\n\n    // Setup necessary objects\n    if (setup_security_object(g_anjay) || setup_server_object(g_anjay)) {\n        result = -1;\n    }\n\n    if (!result) {\n        result = anjay_event_loop_run(\n                g_anjay, avs_time_duration_from_scalar(1, AVS_TIME_S));\n    }\n\n    anjay_delete(g_anjay);\n\n    return result;\n}\n"
  },
  {
    "path": "examples/commercial-features/CF-PSA-bootstrap/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.16)\nproject(anjay-psa-bootstrap C)\n\nset(CMAKE_C_STANDARD 99)\nset(CMAKE_C_EXTENSIONS OFF)\n\nfind_package(anjay REQUIRED)\n\nadd_executable(${PROJECT_NAME} src/main.c)\ntarget_link_libraries(${PROJECT_NAME} PRIVATE anjay)\n"
  },
  {
    "path": "examples/commercial-features/CF-PSA-bootstrap/src/main.c",
    "content": "#include <anjay/anjay.h>\n#include <anjay/security.h>\n#include <anjay/server.h>\n#include <avsystem/commons/avs_log.h>\n\n#include <signal.h>\n#include <time.h>\n\nstatic anjay_t *volatile g_anjay;\n\nvoid signal_handler(int signum) {\n    if (signum == SIGINT && g_anjay) {\n        anjay_event_loop_interrupt(g_anjay);\n    }\n}\n\nconst char HSM_ALPHABET[] = \"0123456789abcdef\";\nconst char HSM_TEMPLATE[] = \"kid=0x0000....\";\n\nstatic const char *generate_hsm_address(anjay_iid_t iid,\n                                        anjay_ssid_t ssid,\n                                        const void *data,\n                                        size_t data_size,\n                                        void *arg) {\n    (void) iid;\n    (void) ssid;\n    (void) data;\n    (void) data_size;\n    (void) arg;\n\n    static size_t offset = 0ul;\n    static char buffer[1024];\n\n    if (offset + sizeof(HSM_TEMPLATE) > sizeof(buffer)) {\n        avs_log(tutorial, ERROR, \"Wrong HSM address\");\n        return NULL;\n    }\n\n    static avs_rand_seed_t SEED;\n    if (!SEED) {\n        SEED = (avs_rand_seed_t) time(NULL);\n    }\n\n    char *result = buffer + offset;\n    offset += sizeof(HSM_TEMPLATE);\n    strcpy(result, HSM_TEMPLATE);\n\n    for (int i = 0; result[i]; i++) {\n        if (result[i] == '.') {\n            result[i] = HSM_ALPHABET[(size_t) avs_rand_r(&SEED)\n                                     % (sizeof(HSM_ALPHABET) - 1)];\n        }\n    }\n\n    return result;\n}\n\nstatic const anjay_security_hsm_configuration_t HSM_CONFIG = {\n    .psk_identity_cb = generate_hsm_address,\n    .psk_key_cb = generate_hsm_address\n};\n\n// Installs Security Object and adds its instance for the bootstrap server.\nstatic int\nsetup_security_object(anjay_t *anjay, const char *identity, const char *key) {\n    if (anjay_security_object_install_with_hsm(anjay, &HSM_CONFIG)) {\n        return -1;\n    }\n\n    const anjay_security_instance_t security_instance = {\n        .ssid = 1,\n        .server_uri = \"coaps://eu.iot.avsystem.cloud:5694\",\n        .bootstrap_server = true,\n        .security_mode = ANJAY_SECURITY_PSK,\n        .public_cert_or_psk_identity = identity,\n        .public_cert_or_psk_identity_size = strlen(identity),\n        .private_cert_or_psk_key = key,\n        .private_cert_or_psk_key_size = strlen(key)\n    };\n\n    // Anjay will assign Instance ID automatically\n    anjay_iid_t security_instance_id = ANJAY_ID_INVALID;\n    if (anjay_security_object_add_instance(anjay, &security_instance,\n                                           &security_instance_id)) {\n        return -1;\n    }\n\n    return 0;\n}\n\nint main(int argc, char *argv[]) {\n    int result = 0;\n\n    if (argc != 2) {\n        avs_log(tutorial, ERROR, \"usage: %s PSK_IDENTITY PSK_KEY\", argv[0]);\n        avs_log(tutorial, INFO,\n                \"note: PSK_IDENTITY is used also as an endpoint name\");\n        return -1;\n    }\n\n    signal(SIGINT, signal_handler);\n\n    const anjay_configuration_t config = {\n        .endpoint_name = argv[1],\n        .in_buffer_size = 4000,\n        .out_buffer_size = 4000,\n        .msg_cache_size = 4000\n    };\n\n    g_anjay = anjay_new(&config);\n    if (!g_anjay) {\n        avs_log(tutorial, ERROR, \"Could not create Anjay object\");\n        return -1;\n    }\n\n    if (setup_security_object(g_anjay, argv[1], argv[2])\n            || anjay_server_object_install(g_anjay)) {\n        result = -1;\n    }\n\n    if (!result) {\n        result = anjay_event_loop_run(\n                g_anjay, avs_time_duration_from_scalar(1, AVS_TIME_S));\n    }\n\n    anjay_delete(g_anjay);\n\n    return result;\n}\n"
  },
  {
    "path": "examples/commercial-features/CF-PSA-management/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.16)\nproject(anjay-psa-management C)\n\nset(CMAKE_C_STANDARD 99)\nset(CMAKE_C_EXTENSIONS OFF)\n\nfind_package(avs_commons COMPONENTS crypto REQUIRED)\n\nadd_executable(${PROJECT_NAME} src/main.c)\ntarget_link_libraries(${PROJECT_NAME} PRIVATE avs_crypto)\n"
  },
  {
    "path": "examples/commercial-features/CF-PSA-management/src/main.c",
    "content": "#include <avsystem/commons/avs_crypto_pki.h>\n#include <avsystem/commons/avs_crypto_psk.h>\n#include <avsystem/commons/avs_log.h>\n\nconst char *USAGE_STR =\n        \"\\nusage: %s COMMAND TYPE ID [PATH || DATA]\\n\"\n        \"\\tCOMMAND:\\tstore|remove\\n\"\n        \"\\tTYPE:\\t\\tpkey|certificate|psk_key|psk_identity\\n\"\n        \"\\tID:\\t\\tPSA ID of the considered credential\\n\"\n        \"\\tPATH:\\t\\tpath to the credential to be stored (4th argument is \"\n        \"to be a path when storing PKI credentials)\\n\"\n        \"\\tDATA:\\t\\tcredential to be stored (4th argument is considered to be \"\n        \"credential itself when storing PSK credential)\";\n\nint main(int argc, char *argv[]) {\n    int result = 0;\n\n    if (argc < 4 || argc > 5\n            || ((strcmp(argv[1], \"remove\") || argc != 4)\n                && (strcmp(argv[1], \"store\") || argc != 5))\n            || (strcmp(argv[2], \"pkey\") && strcmp(argv[2], \"certificate\")\n                && strcmp(argv[2], \"psk_key\")\n                && strcmp(argv[2], \"psk_identity\"))) {\n        avs_log(tutorial, INFO, USAGE_STR, argv[0]);\n        return -1;\n    }\n\n    char query[sizeof(\"kid=0x\") + 9];\n    sprintf(query, \"kid=%#010x\", atoi(argv[3]));\n\n    if (!strcmp(argv[1], \"remove\")) {\n        if (!strcmp(argv[2], \"pkey\")) {\n            if (avs_is_err(avs_crypto_pki_engine_key_rm(query))) {\n                avs_log(tutorial, ERROR, \"Private key removal failed\");\n                return -1;\n            }\n        } else if (!strcmp(argv[2], \"certificate\")) {\n            if (avs_is_err(avs_crypto_pki_engine_certificate_rm(query))) {\n                avs_log(tutorial, ERROR, \"Certificate removal failed\");\n                return -1;\n            }\n        } else if (!strcmp(argv[2], \"psk_key\")) {\n            if (avs_is_err(avs_crypto_psk_engine_key_rm(query))) {\n                avs_log(tutorial, ERROR, \"PSK key removal failed\");\n                return -1;\n            }\n        } else {\n            if (avs_is_err(avs_crypto_psk_engine_identity_rm(query))) {\n                avs_log(tutorial, ERROR, \"PSK identity removal failed\");\n                return -1;\n            }\n        }\n    } else {\n        if (!strcmp(argv[2], \"pkey\")) {\n            avs_crypto_private_key_info_t key_info =\n                    avs_crypto_private_key_info_from_file(argv[4], NULL);\n            if (avs_is_err(avs_crypto_pki_engine_key_store(\n                        query, &key_info, NULL))) {\n                avs_log(tutorial, ERROR, \"Storing private key failed\");\n                return -1;\n            }\n        } else if (!strcmp(argv[2], \"certificate\")) {\n            avs_crypto_certificate_chain_info_t cert_info =\n                    avs_crypto_certificate_chain_info_from_file(argv[4]);\n            if (avs_is_err(avs_crypto_pki_engine_certificate_store(\n                        query, &cert_info))) {\n                avs_log(tutorial, ERROR, \"Storing certificate failed\");\n                return -1;\n            }\n        } else if (!strcmp(argv[2], \"psk_key\")) {\n            avs_crypto_psk_key_info_t psk_key_info =\n                    avs_crypto_psk_key_info_from_buffer(argv[4],\n                                                        strlen(argv[4]));\n            if (avs_is_err(avs_crypto_psk_engine_key_store(query,\n                                                           &psk_key_info))) {\n                avs_log(tutorial, ERROR, \"Storing PSK key failed\");\n                return -1;\n            }\n        } else {\n            avs_crypto_psk_identity_info_t identity_info =\n                    avs_crypto_psk_identity_info_from_buffer(argv[4],\n                                                             strlen(argv[4]));\n            if (avs_is_err(avs_crypto_psk_engine_identity_store(\n                        query, &identity_info))) {\n                avs_log(tutorial, ERROR, \"Storing PSK identity failed\");\n                return -1;\n            }\n        }\n    }\n\n    return 0;\n}\n"
  },
  {
    "path": "examples/commercial-features/CF-SMS/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.16)\nproject(anjay-sms C)\n\nset(CMAKE_C_STANDARD 99)\nset(CMAKE_C_EXTENSIONS OFF)\n\nadd_compile_options(-Wall -Wextra)\n\nfind_package(anjay REQUIRED)\n\nadd_executable(${PROJECT_NAME}\n               src/main.c)\ntarget_link_libraries(${PROJECT_NAME} PRIVATE anjay)\n"
  },
  {
    "path": "examples/commercial-features/CF-SMS/src/main.c",
    "content": "#include <anjay/anjay.h>\n#include <anjay/at_sms.h>\n#include <anjay/security.h>\n#include <anjay/server.h>\n#include <avsystem/commons/avs_log.h>\n\n// Installs Security Object and adds an instance of it.\n// An instance of Security Object provides information needed to connect to\n// LwM2M server.\nstatic int setup_security_object(anjay_t *anjay) {\n    if (anjay_security_object_install(anjay)) {\n        return -1;\n    }\n\n    const anjay_security_instance_t security_instance = {\n        .ssid = 1,\n        .server_uri = \"tel:+12125550178\",\n        .security_mode = ANJAY_SECURITY_NOSEC,\n        .sms_security_mode = ANJAY_SMS_SECURITY_NOSEC\n    };\n\n    // Anjay will assign Instance ID automatically\n    anjay_iid_t security_instance_id = ANJAY_ID_INVALID;\n    if (anjay_security_object_add_instance(anjay, &security_instance,\n                                           &security_instance_id)) {\n        return -1;\n    }\n\n    return 0;\n}\n\n// Installs Server Object and adds an instance of it.\n// An instance of Server Object provides the data related to a LwM2M Server.\nstatic int setup_server_object(anjay_t *anjay) {\n    if (anjay_server_object_install(anjay)) {\n        return -1;\n    }\n\n    const anjay_server_instance_t server_instance = {\n        // Server Short ID\n        .ssid = 1,\n        // Client will send Update message often than every 60 seconds\n        .lifetime = 60,\n        // Disable Default Minimum Period resource\n        .default_min_period = -1,\n        // Disable Default Maximum Period resource\n        .default_max_period = -1,\n        // Disable Disable Timeout resource\n        .disable_timeout = -1,\n        // Sets preferred transport to SMS\n        .binding = \"S\"\n    };\n\n    // Anjay will assign Instance ID automatically\n    anjay_iid_t server_instance_id = ANJAY_ID_INVALID;\n    if (anjay_server_object_add_instance(anjay, &server_instance,\n                                         &server_instance_id)) {\n        return -1;\n    }\n\n    return 0;\n}\n\nint main(int argc, char *argv[]) {\n    if (argc != 3) {\n        avs_log(tutorial, ERROR, \"usage: %s ENDPOINT_NAME MODEM_DEVICE\",\n                argv[0]);\n        return -1;\n    }\n\n    const anjay_configuration_t CONFIG = {\n        .endpoint_name = argv[1],\n        .in_buffer_size = 4000,\n        .out_buffer_size = 4000,\n        .msg_cache_size = 4000,\n        .sms_driver = anjay_at_sms_create(argv[2]),\n        .local_msisdn = \"14155550125\"\n    };\n\n    anjay_t *anjay = anjay_new(&CONFIG);\n    if (!anjay) {\n        avs_log(tutorial, ERROR, \"Could not create Anjay object\");\n        return -1;\n    }\n\n    int result = 0;\n    // Setup necessary objects\n    if (setup_security_object(anjay) || setup_server_object(anjay)) {\n        result = -1;\n    }\n\n    if (!result) {\n        result = anjay_event_loop_run(\n                anjay, avs_time_duration_from_scalar(1, AVS_TIME_S));\n    }\n\n    anjay_delete(anjay);\n    return result;\n}\n"
  },
  {
    "path": "examples/commercial-features/CF-SMS-PSK/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.16)\nproject(anjay-sms-psk C)\n\nset(CMAKE_C_STANDARD 99)\nset(CMAKE_C_EXTENSIONS OFF)\n\nadd_compile_options(-Wall -Wextra)\n\nfind_package(anjay REQUIRED)\n\nadd_executable(${PROJECT_NAME}\n               src/main.c)\ntarget_link_libraries(${PROJECT_NAME} PRIVATE anjay)\n"
  },
  {
    "path": "examples/commercial-features/CF-SMS-PSK/src/main.c",
    "content": "#include <anjay/anjay.h>\n#include <anjay/at_sms.h>\n#include <anjay/security.h>\n#include <anjay/server.h>\n#include <avsystem/commons/avs_log.h>\n\n// Installs Security Object and adds an instance of it.\n// An instance of Security Object provides information needed to connect to\n// LwM2M server.\nstatic int setup_security_object(anjay_t *anjay) {\n    if (anjay_security_object_install(anjay)) {\n        return -1;\n    }\n\n    static const char PSK_IDENTITY[] = \"identity\";\n    static const char PSK_KEY[] = \"P4s$w0rd\";\n\n    const anjay_security_instance_t security_instance = {\n        .ssid = 1,\n        .server_uri = \"tel:+12125550178\",\n        .security_mode = ANJAY_SECURITY_NOSEC,\n        .sms_security_mode = ANJAY_SMS_SECURITY_DTLS_PSK,\n        .sms_key_parameters = (const uint8_t *) PSK_IDENTITY,\n        .sms_key_parameters_size = strlen(PSK_IDENTITY),\n        .sms_secret_key = (const uint8_t *) PSK_KEY,\n        .sms_secret_key_size = strlen(PSK_KEY),\n        .server_name_indication = \"eu.iot.avsystem.cloud\"\n    };\n\n    // Anjay will assign Instance ID automatically\n    anjay_iid_t security_instance_id = ANJAY_ID_INVALID;\n    if (anjay_security_object_add_instance(anjay, &security_instance,\n                                           &security_instance_id)) {\n        return -1;\n    }\n\n    return 0;\n}\n\n// Installs Server Object and adds an instance of it.\n// An instance of Server Object provides the data related to a LwM2M Server.\nstatic int setup_server_object(anjay_t *anjay) {\n    if (anjay_server_object_install(anjay)) {\n        return -1;\n    }\n\n    const anjay_server_instance_t server_instance = {\n        // Server Short ID\n        .ssid = 1,\n        // Client will send Update message often than every 60 seconds\n        .lifetime = 60,\n        // Disable Default Minimum Period resource\n        .default_min_period = -1,\n        // Disable Default Maximum Period resource\n        .default_max_period = -1,\n        // Disable Disable Timeout resource\n        .disable_timeout = -1,\n        // Sets preferred transport to SMS\n        .binding = \"S\"\n    };\n\n    // Anjay will assign Instance ID automatically\n    anjay_iid_t server_instance_id = ANJAY_ID_INVALID;\n    if (anjay_server_object_add_instance(anjay, &server_instance,\n                                         &server_instance_id)) {\n        return -1;\n    }\n\n    return 0;\n}\n\nint main(int argc, char *argv[]) {\n    if (argc != 3) {\n        avs_log(tutorial, ERROR, \"usage: %s ENDPOINT_NAME MODEM_DEVICE\",\n                argv[0]);\n        return -1;\n    }\n\n    const anjay_configuration_t CONFIG = {\n        .endpoint_name = argv[1],\n        .in_buffer_size = 4000,\n        .out_buffer_size = 4000,\n        .msg_cache_size = 4000,\n        .sms_driver = anjay_at_sms_create(argv[2]),\n        .local_msisdn = \"14155550125\",\n        .default_tls_ciphersuites = {\n            // TLS_PSK_WITH_AES_128_CCM_8\n            .ids = (uint32_t[]){ 0xC0A8 },\n            .num_ids = 1\n        }\n    };\n\n    anjay_t *anjay = anjay_new(&CONFIG);\n    if (!anjay) {\n        avs_log(tutorial, ERROR, \"Could not create Anjay object\");\n        return -1;\n    }\n\n    int result = 0;\n    // Setup necessary objects\n    if (setup_security_object(anjay) || setup_server_object(anjay)) {\n        result = -1;\n    }\n\n    if (!result) {\n        result = anjay_event_loop_run(\n                anjay, avs_time_duration_from_scalar(1, AVS_TIME_S));\n    }\n\n    anjay_delete(anjay);\n    return result;\n}\n"
  },
  {
    "path": "examples/commercial-features/CF-SMS-UDP/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.16)\nproject(anjay-sms-udp C)\n\nset(CMAKE_C_STANDARD 99)\nset(CMAKE_C_EXTENSIONS OFF)\n\nadd_compile_options(-Wall -Wextra)\n\nfind_package(anjay REQUIRED)\n\nadd_executable(${PROJECT_NAME}\n               src/main.c)\ntarget_link_libraries(${PROJECT_NAME} PRIVATE anjay)\n"
  },
  {
    "path": "examples/commercial-features/CF-SMS-UDP/src/main.c",
    "content": "#include <anjay/anjay.h>\n#include <anjay/at_sms.h>\n#include <anjay/security.h>\n#include <anjay/server.h>\n#include <avsystem/commons/avs_log.h>\n\n// Installs Security Object and adds an instance of it.\n// An instance of Security Object provides information needed to connect to\n// LwM2M server.\nstatic int setup_security_object(anjay_t *anjay) {\n    if (anjay_security_object_install(anjay)) {\n        return -1;\n    }\n\n    const anjay_security_instance_t security_instance = {\n        .ssid = 1,\n        .server_uri = \"coap://eu.iot.avsystem.cloud:5683\",\n        .security_mode = ANJAY_SECURITY_NOSEC,\n        .sms_security_mode = ANJAY_SMS_SECURITY_NOSEC,\n        .server_sms_number = \"12125550178\"\n    };\n\n    // Anjay will assign Instance ID automatically\n    anjay_iid_t security_instance_id = ANJAY_ID_INVALID;\n    if (anjay_security_object_add_instance(anjay, &security_instance,\n                                           &security_instance_id)) {\n        return -1;\n    }\n\n    return 0;\n}\n\n// Installs Server Object and adds an instance of it.\n// An instance of Server Object provides the data related to a LwM2M Server.\nstatic int setup_server_object(anjay_t *anjay) {\n    if (anjay_server_object_install(anjay)) {\n        return -1;\n    }\n\n    const anjay_server_instance_t server_instance = {\n        // Server Short ID\n        .ssid = 1,\n        // Client will send Update message often than every 60 seconds\n        .lifetime = 60,\n        // Disable Default Minimum Period resource\n        .default_min_period = -1,\n        // Disable Default Maximum Period resource\n        .default_max_period = -1,\n        // Disable Disable Timeout resource\n        .disable_timeout = -1,\n        // Sets preferred transport to UDP\n        .binding = \"U\",\n        // Enables optional Trigger resource and sets it to true\n        .trigger = &(const bool) { true }\n    };\n\n    // Anjay will assign Instance ID automatically\n    anjay_iid_t server_instance_id = ANJAY_ID_INVALID;\n    if (anjay_server_object_add_instance(anjay, &server_instance,\n                                         &server_instance_id)) {\n        return -1;\n    }\n\n    return 0;\n}\n\nint main(int argc, char *argv[]) {\n    if (argc != 3) {\n        avs_log(tutorial, ERROR, \"usage: %s ENDPOINT_NAME MODEM_DEVICE\",\n                argv[0]);\n        return -1;\n    }\n\n    const anjay_configuration_t CONFIG = {\n        .endpoint_name = argv[1],\n        .in_buffer_size = 4000,\n        .out_buffer_size = 4000,\n        .msg_cache_size = 4000,\n        .sms_driver = anjay_at_sms_create(argv[2]),\n        .local_msisdn = \"14155550125\"\n    };\n\n    anjay_t *anjay = anjay_new(&CONFIG);\n    if (!anjay) {\n        avs_log(tutorial, ERROR, \"Could not create Anjay object\");\n        return -1;\n    }\n\n    int result = 0;\n    // Setup necessary objects\n    if (setup_security_object(anjay) || setup_server_object(anjay)) {\n        result = -1;\n    }\n\n    if (!result) {\n        result = anjay_event_loop_run(\n                anjay, avs_time_duration_from_scalar(1, AVS_TIME_S));\n    }\n\n    anjay_delete(anjay);\n    return result;\n}\n"
  },
  {
    "path": "examples/commercial-features/CF-SmartCardBootstrap/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.16)\nproject(anjay-smart-card-bootstrap C)\n\nset(CMAKE_C_STANDARD 99)\nset(CMAKE_C_EXTENSIONS OFF)\n\nadd_compile_options(-Wall -Wextra)\n\nfind_package(anjay REQUIRED)\n\nadd_executable(${PROJECT_NAME} src/main.c)\ntarget_link_libraries(${PROJECT_NAME} PRIVATE anjay)\n"
  },
  {
    "path": "examples/commercial-features/CF-SmartCardBootstrap/src/main.c",
    "content": "#include <anjay/anjay.h>\n#include <anjay/security.h>\n#include <anjay/server.h>\n#include <anjay/sim_bootstrap.h>\n#include <avsystem/commons/avs_buffer.h>\n#include <avsystem/commons/avs_log.h>\n#include <avsystem/commons/avs_stream_file.h>\n\n#include <ctype.h>\n#include <errno.h>\n#include <inttypes.h>\n\n#include <fcntl.h>\n#include <poll.h>\n#include <unistd.h>\n\n#include <sys/stat.h>\n#include <sys/types.h>\n\n#define SIM_COMMAND_MAX_BINARY_SIZE 258\n\n#define CSIM_RESP \"+CSIM: \"\n\n#define REQ_BUF_SIZE \\\n    (sizeof(\"AT+CSIM=999,\\\"\\\"\\r\\n\") + 2 * SIM_COMMAND_MAX_BINARY_SIZE)\n#define RESP_BUF_SIZE \\\n    (sizeof(CSIM_RESP \"999,\\\"\\\"\") + 2 * SIM_COMMAND_MAX_BINARY_SIZE)\n\ntypedef struct {\n    avs_buffer_t *buffer;\n} fifo_t;\n\nstatic int fifo_init(fifo_t *fifo) {\n    if (fifo->buffer) {\n        return -1;\n    }\n    return avs_buffer_create(&fifo->buffer, 4096);\n}\n\nstatic void fifo_destroy(fifo_t *fifo) {\n    avs_buffer_free(&fifo->buffer);\n}\n\nstatic int fifo_find_off(fifo_t *fifo, char ch, size_t *out_off) {\n    const char *start = avs_buffer_data(fifo->buffer);\n    char *ptr = (char *) memchr(start, ch, avs_buffer_data_size(fifo->buffer));\n    if (ptr) {\n        *out_off = (size_t) (ptr - start);\n        return 0;\n    }\n    return -1;\n}\n\nstatic void fifo_pop_n(fifo_t *fifo, char *out_buffer, size_t size, size_t n) {\n    assert(n <= avs_buffer_data_size(fifo->buffer));\n    assert(n <= size);\n    (void) size;\n\n    memcpy(out_buffer, avs_buffer_data(fifo->buffer), n);\n    avs_buffer_consume_bytes(fifo->buffer, n);\n}\n\nstatic void fifo_discard_n(fifo_t *fifo, size_t n) {\n    assert(n <= avs_buffer_data_size(fifo->buffer));\n    avs_buffer_consume_bytes(fifo->buffer, n);\n}\n\n// NOTE: If out_line is too small to hold the entire line,\n// excess characters will be discarded\nstatic int fifo_pop_line(fifo_t *fifo, char *out_line, size_t out_size) {\n    assert(out_size > 0);\n\n    size_t line_size;\n    if (!fifo_find_off(fifo, '\\n', &line_size)\n            || !fifo_find_off(fifo, '\\r', &line_size)) {\n        ++line_size;\n    } else if (avs_buffer_space_left(fifo->buffer) == 0) {\n        avs_log(tutorial, WARNING,\n                \"FIFO buffer full, treating received data as a line\");\n        line_size = avs_buffer_data_size(fifo->buffer);\n    } else {\n        line_size = 0;\n    }\n\n    size_t bytes_to_pop = AVS_MIN(out_size - 1, line_size);\n    fifo_pop_n(fifo, out_line, out_size - 1, bytes_to_pop);\n    out_line[bytes_to_pop] = '\\0';\n\n    // Discard excess bytes, if any\n    if (line_size != bytes_to_pop) {\n        fifo_discard_n(fifo, line_size - bytes_to_pop);\n        avs_log(tutorial, WARNING, \"buffer size too small to hold the line\");\n        return 1;\n    }\n    return 0;\n}\n\nstatic void fifo_strip_nullbytes(fifo_t *fifo) {\n    size_t buffer_size = avs_buffer_data_size(fifo->buffer);\n    char *buffer = avs_buffer_raw_insert_ptr(fifo->buffer) - buffer_size;\n    ssize_t block_end_offset = (ssize_t) buffer_size;\n    assert(block_end_offset >= 0);\n    ssize_t moved_by = 0;\n    while (block_end_offset > 0) {\n        ssize_t first_null_offset = block_end_offset;\n        while (first_null_offset > 0 && buffer[first_null_offset - 1] == '\\0') {\n            --first_null_offset;\n        }\n        ssize_t first_nonnull_offset = first_null_offset;\n        while (first_nonnull_offset > 0\n               && buffer[first_nonnull_offset - 1] != '\\0') {\n            --first_nonnull_offset;\n        }\n        if (first_null_offset != block_end_offset) {\n            assert(first_null_offset < block_end_offset);\n            moved_by += block_end_offset - first_null_offset;\n            if (first_nonnull_offset != first_null_offset) {\n                assert(first_nonnull_offset < first_null_offset);\n                memmove(buffer + first_nonnull_offset + moved_by,\n                        buffer + first_nonnull_offset,\n                        (size_t) (first_null_offset - first_nonnull_offset));\n            }\n        }\n        block_end_offset = first_nonnull_offset;\n    }\n    assert(moved_by >= 0);\n    if (moved_by > 0) {\n        avs_buffer_consume_bytes(fifo->buffer, (size_t) moved_by);\n    }\n}\n\nstatic avs_error_t fifo_push_read(fifo_t *fifo, int fd) {\n    // This shall be handled in fifo_pop_line()\n    assert(avs_buffer_space_left(fifo->buffer) > 0);\n    ssize_t bytes_read = read(fd, avs_buffer_raw_insert_ptr(fifo->buffer), 1);\n    if (bytes_read < 0) {\n        return avs_errno(AVS_EIO);\n    } else if (bytes_read == 0) {\n        return AVS_EOF;\n    } else {\n        assert(bytes_read == 1);\n        avs_buffer_advance_ptr(fifo->buffer, 1);\n        fifo_strip_nullbytes(fifo);\n        return AVS_OK;\n    }\n}\n\ntypedef struct {\n    fifo_t fifo;\n    int pts_fd;\n} modem_ctx_t;\n\nstatic void trim_inplace(char *buffer) {\n    assert(buffer);\n\n    size_t len = strlen(buffer);\n    for (char *ch = buffer + len - 1;\n         ch >= buffer && isspace((unsigned char) *ch);\n         --ch) {\n        *ch = '\\0';\n    }\n    char *first_nonblank = buffer;\n    for (char *ch = buffer; *ch; ++ch) {\n        if (!isspace((unsigned char) *ch)) {\n            first_nonblank = ch;\n            break;\n        }\n    }\n    memmove(buffer, first_nonblank, strlen(first_nonblank) + 1);\n}\n\nstatic int modem_getline(modem_ctx_t *modem_ctx,\n                         char *out_line_buffer,\n                         size_t buffer_size,\n                         avs_time_monotonic_t deadline) {\n    struct pollfd fd;\n    fd.fd = modem_ctx->pts_fd;\n    fd.events = POLLIN;\n\n    int64_t timeout_ms;\n    int result;\n\n    // Note: this loop is not signal-safe.\n    do {\n        if (avs_time_duration_to_scalar(\n                    &timeout_ms, AVS_TIME_MS,\n                    avs_time_monotonic_diff(deadline,\n                                            avs_time_monotonic_now()))) {\n            timeout_ms = -1;\n        } else if (timeout_ms < 0) {\n            timeout_ms = 0;\n        }\n\n        while (true) {\n            result = fifo_pop_line(&modem_ctx->fifo, out_line_buffer,\n                                   buffer_size);\n            if (*out_line_buffer == '\\0') {\n                break;\n            }\n            trim_inplace(out_line_buffer);\n            if (*out_line_buffer != '\\0') {\n                avs_log(tutorial, DEBUG, \"[MODEM] recv: %s\", out_line_buffer);\n                return result;\n            }\n        }\n\n        assert(timeout_ms <= INT_MAX);\n        if ((result = poll(&fd, 1, (int) timeout_ms)) == 1) {\n            avs_error_t err =\n                    fifo_push_read(&modem_ctx->fifo, modem_ctx->pts_fd);\n            if (avs_is_eof(err)) {\n                avs_log(tutorial, DEBUG, \"[MODEM] recv: EOF\");\n                return -1;\n            } else if (avs_is_err(err)) {\n                return -1;\n            }\n        } else if (result < 0) {\n            return -1;\n        }\n        // read up until timeout becomes 0, or if it is 0, read up until there's\n        // something to read.\n    } while (timeout_ms != 0 || result == 1);\n\n    avs_log(tutorial, DEBUG, \"[MODEM] recv: timeout\");\n    assert(buffer_size > 0);\n    out_line_buffer[0] = '\\0';\n    return 0;\n}\n\nstatic int sim_perform_command(void *modem_ctx_,\n                               const void *cmd,\n                               size_t cmd_length,\n                               void *out_buf,\n                               size_t out_buf_size,\n                               size_t *out_response_size) {\n    modem_ctx_t *modem_ctx = (modem_ctx_t *) modem_ctx_;\n    char req_buf[REQ_BUF_SIZE];\n    char resp_buf[RESP_BUF_SIZE] = \"\";\n\n    char *req_buf_ptr = req_buf;\n    char *const req_buf_end = req_buf + sizeof(req_buf);\n    int result = avs_simple_snprintf(req_buf_ptr,\n                                     (size_t) (req_buf_end - req_buf_ptr),\n                                     \"AT+CSIM=%\" PRIu32 \",\\\"\",\n                                     (uint32_t) (2 * cmd_length));\n    if (result < 0) {\n        return result;\n    }\n    req_buf_ptr += result;\n    if ((size_t) (req_buf_end - req_buf_ptr) < 2 * cmd_length) {\n        return -1;\n    }\n    if ((result = avs_hexlify(req_buf_ptr, (size_t) (req_buf_end - req_buf_ptr),\n                              NULL, cmd, cmd_length))) {\n        return result;\n    }\n    req_buf_ptr += 2 * cmd_length;\n    if ((result = avs_simple_snprintf(\n                 req_buf_ptr, (size_t) (req_buf_end - req_buf_ptr), \"\\\"\\r\\n\"))\n            < 0) {\n        return result;\n    }\n    req_buf_ptr += result;\n    ssize_t written =\n            write(modem_ctx->pts_fd, req_buf, (size_t) (req_buf_ptr - req_buf));\n    if (written != (ssize_t) (req_buf_ptr - req_buf)) {\n        return -1;\n    }\n    avs_time_monotonic_t deadline = avs_time_monotonic_add(\n            avs_time_monotonic_now(),\n            avs_time_duration_from_scalar(5, AVS_TIME_S));\n    bool csim_resp_received = false;\n    bool ok_received = false;\n    while (!ok_received) {\n        if (modem_getline(modem_ctx, resp_buf, sizeof(resp_buf), deadline)) {\n            return -1;\n        }\n        const char *resp_terminator = memchr(resp_buf, '\\0', sizeof(resp_buf));\n        if (!resp_terminator) {\n            return -1;\n        }\n        if (memcmp(resp_buf, CSIM_RESP, strlen(CSIM_RESP)) == 0) {\n            if (csim_resp_received) {\n                return -1;\n            }\n            errno = 0;\n            char *endptr = NULL;\n            long long resp_reported_length =\n                    strtoll(resp_buf + strlen(CSIM_RESP), &endptr, 10);\n            if (errno || !endptr || endptr[0] != ',' || endptr[1] != '\"'\n                    || resp_reported_length < 0\n                    || endptr + resp_reported_length + 2 >= resp_terminator\n                    || endptr[resp_reported_length + 2] != '\"'\n                    || avs_unhexlify(out_response_size, (uint8_t *) out_buf,\n                                     out_buf_size, endptr + 2,\n                                     (size_t) resp_reported_length)) {\n                return -1;\n            }\n            csim_resp_received = true;\n        } else if (strcmp(resp_buf, \"OK\") == 0) {\n            ok_received = true;\n        }\n    }\n    return csim_resp_received ? 0 : -1;\n}\n\nstatic int bootstrap_from_sim(anjay_t *anjay, const char *modem_device) {\n    modem_ctx_t modem_ctx = {\n        .pts_fd = -1\n    };\n    int result = -1;\n\n    avs_log(tutorial, INFO, \"Attempting to bootstrap from SIM card\");\n\n    if (fifo_init(&modem_ctx.fifo)) {\n        avs_log(tutorial, ERROR, \"could not initialize FIFO\");\n        goto finish;\n    }\n    if ((modem_ctx.pts_fd = open(modem_device, O_RDWR)) < 0) {\n        avs_log(tutorial, ERROR, \"could not open modem device %s: %s\",\n                modem_device, strerror(errno));\n        goto finish;\n    }\n    if (avs_is_err(anjay_sim_bootstrap_perform(anjay, sim_perform_command,\n                                               &modem_ctx))) {\n        avs_log(tutorial, ERROR, \"Could not bootstrap from SIM card\");\n        goto finish;\n    }\n    result = 0;\nfinish:\n    if (modem_ctx.pts_fd >= 0) {\n        close(modem_ctx.pts_fd);\n    }\n    fifo_destroy(&modem_ctx.fifo);\n    return result;\n}\n\nint main(int argc, char *argv[]) {\n    if (argc != 3) {\n        avs_log(tutorial, ERROR, \"usage: %s ENDPOINT_NAME MODEM_PATH\", argv[0]);\n        return -1;\n    }\n\n    const anjay_configuration_t CONFIG = {\n        .endpoint_name = argv[1],\n        .in_buffer_size = 4000,\n        .out_buffer_size = 4000,\n        .msg_cache_size = 4000\n    };\n\n    anjay_t *anjay = anjay_new(&CONFIG);\n    if (!anjay) {\n        avs_log(tutorial, ERROR, \"Could not create Anjay object\");\n        return -1;\n    }\n\n    int result = 0;\n    // Setup necessary objects\n    if (anjay_security_object_install(anjay)\n            || anjay_server_object_install(anjay)) {\n        result = -1;\n    }\n\n    if (!result) {\n        result = bootstrap_from_sim(anjay, argv[2]);\n    }\n\n    if (!result) {\n        result = anjay_event_loop_run(\n                anjay, avs_time_duration_from_scalar(1, AVS_TIME_S));\n    }\n\n    anjay_delete(anjay);\n    return result;\n}\n"
  },
  {
    "path": "examples/commercial-features/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.16)\n\n\n\n\n\n"
  },
  {
    "path": "examples/custom-network/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.16)\nadd_subdirectory(minimal)\nadd_subdirectory(remote-host-port)\nadd_subdirectory(bind)\nadd_subdirectory(shutdown-remote-hostname)\nadd_subdirectory(stats)\n\n# NOTE: ip-stickiness requires -DWITHOUT_IP_STICKINESS=OFF\n# so it's not included here\n"
  },
  {
    "path": "examples/custom-network/bind/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.16)\nproject(custom-network-bind C)\n\nset(CMAKE_C_STANDARD 99)\n\nfind_package(anjay REQUIRED)\n\nadd_executable(${PROJECT_NAME}\n               src/main.c\n               src/net_impl.c)\ntarget_link_libraries(${PROJECT_NAME} PRIVATE anjay)\n"
  },
  {
    "path": "examples/custom-network/bind/src/main.c",
    "content": "#include <anjay/anjay.h>\n#include <anjay/security.h>\n#include <anjay/server.h>\n#include <avsystem/commons/avs_log.h>\n\n#include <poll.h>\n\nint main_loop(anjay_t *anjay) {\n    while (true) {\n        // Obtain all network data sources\n        AVS_LIST(avs_net_socket_t *const) sockets = anjay_get_sockets(anjay);\n\n        // Prepare to poll() on them\n        size_t numsocks = AVS_LIST_SIZE(sockets);\n        struct pollfd pollfds[numsocks];\n        size_t i = 0;\n        AVS_LIST(avs_net_socket_t *const) sock;\n        AVS_LIST_FOREACH(sock, sockets) {\n            pollfds[i].fd = *(const int *) avs_net_socket_get_system(*sock);\n            pollfds[i].events = POLLIN;\n            pollfds[i].revents = 0;\n            ++i;\n        }\n\n        const int max_wait_time_ms = 1000;\n        // Determine the expected time to the next job in milliseconds.\n        // If there is no job we will wait till something arrives for\n        // at most 1 second (i.e. max_wait_time_ms).\n        int wait_ms =\n                anjay_sched_calculate_wait_time_ms(anjay, max_wait_time_ms);\n\n        // Wait for the events if necessary, and handle them.\n        if (poll(pollfds, numsocks, wait_ms) > 0) {\n            int socket_id = 0;\n            AVS_LIST(avs_net_socket_t *const) socket = NULL;\n            AVS_LIST_FOREACH(socket, sockets) {\n                if (pollfds[socket_id].revents) {\n                    if (anjay_serve(anjay, *socket)) {\n                        avs_log(tutorial, ERROR, \"anjay_serve failed\");\n                    }\n                }\n                ++socket_id;\n            }\n        }\n\n        // Finally run the scheduler\n        anjay_sched_run(anjay);\n    }\n    return 0;\n}\n\n// Installs Security Object and adds an instance of it.\n// An instance of Security Object provides information needed to connect to\n// LwM2M server.\nstatic int setup_security_object(anjay_t *anjay) {\n    if (anjay_security_object_install(anjay)) {\n        return -1;\n    }\n\n    static const char PSK_IDENTITY[] = \"identity\";\n    static const char PSK_KEY[] = \"P4s$w0rd\";\n\n    anjay_security_instance_t security_instance = {\n        .ssid = 1,\n        .server_uri = \"coaps://eu.iot.avsystem.cloud:5684\",\n        .security_mode = ANJAY_SECURITY_PSK,\n        .public_cert_or_psk_identity = (const uint8_t *) PSK_IDENTITY,\n        .public_cert_or_psk_identity_size = strlen(PSK_IDENTITY),\n        .private_cert_or_psk_key = (const uint8_t *) PSK_KEY,\n        .private_cert_or_psk_key_size = strlen(PSK_KEY)\n    };\n\n    // Anjay will assign Instance ID automatically\n    anjay_iid_t security_instance_id = ANJAY_ID_INVALID;\n    if (anjay_security_object_add_instance(anjay, &security_instance,\n                                           &security_instance_id)) {\n        return -1;\n    }\n\n    return 0;\n}\n\n// Installs Server Object and adds an instance of it.\n// An instance of Server Object provides the data related to a LwM2M Server.\nstatic int setup_server_object(anjay_t *anjay) {\n    if (anjay_server_object_install(anjay)) {\n        return -1;\n    }\n\n    const anjay_server_instance_t server_instance = {\n        // Server Short ID\n        .ssid = 1,\n        // Client will send Update message often than every 60 seconds\n        .lifetime = 60,\n        // Disable Default Minimum Period resource\n        .default_min_period = -1,\n        // Disable Default Maximum Period resource\n        .default_max_period = -1,\n        // Disable Disable Timeout resource\n        .disable_timeout = -1,\n        // Sets preferred transport to UDP\n        .binding = \"U\"\n    };\n\n    // Anjay will assign Instance ID automatically\n    anjay_iid_t server_instance_id = ANJAY_ID_INVALID;\n    if (anjay_server_object_add_instance(anjay, &server_instance,\n                                         &server_instance_id)) {\n        return -1;\n    }\n\n    return 0;\n}\n\nint main(int argc, char *argv[]) {\n    if (argc != 2) {\n        avs_log(tutorial, ERROR, \"usage: %s ENDPOINT_NAME\", argv[0]);\n        return -1;\n    }\n\n    const anjay_configuration_t CONFIG = {\n        .endpoint_name = argv[1],\n        .in_buffer_size = 4000,\n        .out_buffer_size = 4000,\n        .msg_cache_size = 4000\n    };\n\n    anjay_t *anjay = anjay_new(&CONFIG);\n    if (!anjay) {\n        avs_log(tutorial, ERROR, \"Could not create Anjay object\");\n        return -1;\n    }\n\n    int result = 0;\n    // Setup necessary objects\n    if (setup_security_object(anjay) || setup_server_object(anjay)) {\n        result = -1;\n    }\n\n    if (!result) {\n        result = main_loop(anjay);\n    }\n\n    anjay_delete(anjay);\n    return result;\n}\n"
  },
  {
    "path": "examples/custom-network/bind/src/net_impl.c",
    "content": "#include <inttypes.h>\n\n#include <netdb.h>\n#include <poll.h>\n#include <unistd.h>\n\n#include <arpa/inet.h>\n\n#include <sys/socket.h>\n#include <sys/types.h>\n\n#include <avsystem/commons/avs_socket_v_table.h>\n#include <avsystem/commons/avs_utils.h>\n\n#ifdef AVS_COMMONS_NET_WITH_POSIX_AVS_SOCKET\n#    error \"Custom implementation of the network layer conflicts with AVS_COMMONS_NET_WITH_POSIX_AVS_SOCKET\"\n#endif // AVS_COMMONS_NET_WITH_POSIX_AVS_SOCKET\n\navs_error_t _avs_net_initialize_global_compat_state(void);\nvoid _avs_net_cleanup_global_compat_state(void);\navs_error_t _avs_net_create_tcp_socket(avs_net_socket_t **socket,\n                                       const void *socket_configuration);\navs_error_t _avs_net_create_udp_socket(avs_net_socket_t **socket,\n                                       const void *socket_configuration);\n\navs_error_t _avs_net_initialize_global_compat_state(void) {\n    return AVS_OK;\n}\n\nvoid _avs_net_cleanup_global_compat_state(void) {}\n\ntypedef struct {\n    const avs_net_socket_v_table_t *operations;\n    int socktype;\n    int fd;\n    avs_time_duration_t recv_timeout;\n} net_socket_impl_t;\n\ntypedef union {\n    struct sockaddr addr;\n    struct sockaddr_in in;\n    struct sockaddr_in6 in6;\n    struct sockaddr_storage storage;\n} sockaddr_union_t;\n\nstatic avs_error_t\nnet_connect(avs_net_socket_t *sock_, const char *host, const char *port) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    struct addrinfo hints = {\n        .ai_socktype = sock->socktype\n    };\n    if (sock->fd >= 0) {\n        getsockopt(sock->fd, SOL_SOCKET, SO_DOMAIN, &hints.ai_family,\n                   &(socklen_t) { sizeof(hints.ai_family) });\n    }\n    struct addrinfo *addr = NULL;\n    avs_error_t err = AVS_OK;\n    if (getaddrinfo(host, port, &hints, &addr) || !addr) {\n        err = avs_errno(AVS_EADDRNOTAVAIL);\n    } else if (sock->fd < 0\n               && (sock->fd = socket(addr->ai_family, addr->ai_socktype,\n                                     addr->ai_protocol))\n                          < 0) {\n        err = avs_errno(AVS_UNKNOWN_ERROR);\n    } else if (connect(sock->fd, addr->ai_addr, addr->ai_addrlen)) {\n        err = avs_errno(AVS_ECONNREFUSED);\n    }\n    freeaddrinfo(addr);\n    return err;\n}\n\nstatic avs_error_t\nnet_send(avs_net_socket_t *sock_, const void *buffer, size_t buffer_length) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    ssize_t written = send(sock->fd, buffer, buffer_length, MSG_NOSIGNAL);\n    if (written >= 0 && (size_t) written == buffer_length) {\n        return AVS_OK;\n    }\n    return avs_errno(AVS_EIO);\n}\n\nstatic avs_error_t net_receive(avs_net_socket_t *sock_,\n                               size_t *out_bytes_received,\n                               void *buffer,\n                               size_t buffer_length) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    struct pollfd pfd = {\n        .fd = sock->fd,\n        .events = POLLIN\n    };\n    int64_t timeout_ms;\n    if (avs_time_duration_to_scalar(&timeout_ms, AVS_TIME_MS,\n                                    sock->recv_timeout)) {\n        timeout_ms = -1;\n    } else if (timeout_ms < 0) {\n        timeout_ms = 0;\n    }\n    if (poll(&pfd, 1, (int) timeout_ms) == 0) {\n        return avs_errno(AVS_ETIMEDOUT);\n    }\n    ssize_t bytes_received = read(sock->fd, buffer, buffer_length);\n    if (bytes_received < 0) {\n        return avs_errno(AVS_EIO);\n    }\n    *out_bytes_received = (size_t) bytes_received;\n    if (buffer_length > 0 && sock->socktype == SOCK_DGRAM\n            && (size_t) bytes_received == buffer_length) {\n        return avs_errno(AVS_EMSGSIZE);\n    }\n    return AVS_OK;\n}\n\nstatic avs_error_t\nnet_bind(avs_net_socket_t *sock_, const char *address, const char *port) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    struct addrinfo hints = {\n        .ai_flags = AI_PASSIVE,\n        .ai_socktype = sock->socktype\n    };\n    if (sock->fd >= 0) {\n        getsockopt(sock->fd, SOL_SOCKET, SO_DOMAIN, &hints.ai_family,\n                   &(socklen_t) { sizeof(hints.ai_family) });\n    }\n    struct addrinfo *addr = NULL;\n    avs_error_t err = AVS_OK;\n    if (getaddrinfo(address, port, &hints, &addr) || !addr) {\n        err = avs_errno(AVS_EADDRNOTAVAIL);\n    } else if ((sock->fd < 0\n                && (sock->fd = socket(addr->ai_family, addr->ai_socktype,\n                                      addr->ai_protocol))\n                           < 0)\n               || setsockopt(sock->fd, SOL_SOCKET, SO_REUSEADDR, &(int) { 1 },\n                             sizeof(int))) {\n        err = avs_errno(AVS_UNKNOWN_ERROR);\n    } else if (bind(sock->fd, addr->ai_addr, addr->ai_addrlen)) {\n        err = avs_errno(AVS_ECONNREFUSED);\n    }\n    if (avs_is_err(err) && sock->fd >= 0) {\n        close(sock->fd);\n        sock->fd = -1;\n    }\n    freeaddrinfo(addr);\n    return err;\n}\n\nstatic avs_error_t net_close(avs_net_socket_t *sock_) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    avs_error_t err = AVS_OK;\n    if (sock->fd >= 0) {\n        if (close(sock->fd)) {\n            err = avs_errno(AVS_EIO);\n        }\n        sock->fd = -1;\n    }\n    return err;\n}\n\nstatic avs_error_t net_cleanup(avs_net_socket_t **sock_ptr) {\n    avs_error_t err = AVS_OK;\n    if (sock_ptr && *sock_ptr) {\n        err = net_close(*sock_ptr);\n        avs_free(*sock_ptr);\n        *sock_ptr = NULL;\n    }\n    return err;\n}\n\nstatic const void *net_system_socket(avs_net_socket_t *sock_) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    return &sock->fd;\n}\n\nstatic avs_error_t stringify_sockaddr_host(const sockaddr_union_t *addr,\n                                           char *out_buffer,\n                                           size_t out_buffer_size) {\n    if ((addr->in.sin_family == AF_INET\n         && inet_ntop(AF_INET, &addr->in.sin_addr, out_buffer,\n                      (socklen_t) out_buffer_size))\n            || (addr->in6.sin6_family == AF_INET6\n                && inet_ntop(AF_INET6, &addr->in6.sin6_addr, out_buffer,\n                             (socklen_t) out_buffer_size))) {\n        return AVS_OK;\n    }\n    return avs_errno(AVS_UNKNOWN_ERROR);\n}\n\nstatic avs_error_t stringify_sockaddr_port(const sockaddr_union_t *addr,\n                                           char *out_buffer,\n                                           size_t out_buffer_size) {\n    if ((addr->in.sin_family == AF_INET\n         && avs_simple_snprintf(out_buffer, out_buffer_size, \"%\" PRIu16,\n                                ntohs(addr->in.sin_port))\n                    >= 0)\n            || (addr->in6.sin6_family == AF_INET6\n                && avs_simple_snprintf(out_buffer, out_buffer_size, \"%\" PRIu16,\n                                       ntohs(addr->in6.sin6_port))\n                           >= 0)) {\n        return AVS_OK;\n    }\n    return avs_errno(AVS_UNKNOWN_ERROR);\n}\n\nstatic avs_error_t net_remote_host(avs_net_socket_t *sock_,\n                                   char *out_buffer,\n                                   size_t out_buffer_size) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    sockaddr_union_t addr;\n    if (getpeername(sock->fd, &addr.addr, &(socklen_t) { sizeof(addr) })) {\n        return avs_errno(AVS_UNKNOWN_ERROR);\n    }\n    return stringify_sockaddr_host(&addr, out_buffer, out_buffer_size);\n}\n\nstatic avs_error_t net_remote_port(avs_net_socket_t *sock_,\n                                   char *out_buffer,\n                                   size_t out_buffer_size) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    sockaddr_union_t addr;\n    if (getpeername(sock->fd, &addr.addr, &(socklen_t) { sizeof(addr) })) {\n        return avs_errno(AVS_UNKNOWN_ERROR);\n    }\n    return stringify_sockaddr_port(&addr, out_buffer, out_buffer_size);\n}\n\nstatic avs_error_t net_local_port(avs_net_socket_t *sock_,\n                                  char *out_buffer,\n                                  size_t out_buffer_size) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    sockaddr_union_t addr;\n    if (getsockname(sock->fd, &addr.addr, &(socklen_t) { sizeof(addr) })) {\n        return avs_errno(AVS_UNKNOWN_ERROR);\n    }\n    return stringify_sockaddr_port(&addr, out_buffer, out_buffer_size);\n}\n\nstatic avs_error_t net_get_opt(avs_net_socket_t *sock_,\n                               avs_net_socket_opt_key_t option_key,\n                               avs_net_socket_opt_value_t *out_option_value) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    switch (option_key) {\n    case AVS_NET_SOCKET_OPT_RECV_TIMEOUT:\n        out_option_value->recv_timeout = sock->recv_timeout;\n        return AVS_OK;\n    case AVS_NET_SOCKET_OPT_STATE:\n        if (sock->fd < 0) {\n            out_option_value->state = AVS_NET_SOCKET_STATE_CLOSED;\n        } else {\n            sockaddr_union_t addr;\n            if (!getpeername(sock->fd, &addr.addr,\n                             &(socklen_t) { sizeof(addr) })\n                    && ((addr.in.sin_family == AF_INET && addr.in.sin_port != 0)\n                        || (addr.in6.sin6_family == AF_INET6\n                            && addr.in6.sin6_port != 0))) {\n                out_option_value->state = AVS_NET_SOCKET_STATE_CONNECTED;\n            } else {\n                out_option_value->state = AVS_NET_SOCKET_STATE_BOUND;\n            }\n        }\n        return AVS_OK;\n    case AVS_NET_SOCKET_OPT_INNER_MTU:\n        out_option_value->mtu = 1464;\n        return AVS_OK;\n    case AVS_NET_SOCKET_HAS_BUFFERED_DATA:\n        out_option_value->flag = false;\n        return AVS_OK;\n    default:\n        return avs_errno(AVS_ENOTSUP);\n    }\n}\n\nstatic avs_error_t net_set_opt(avs_net_socket_t *sock_,\n                               avs_net_socket_opt_key_t option_key,\n                               avs_net_socket_opt_value_t option_value) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    switch (option_key) {\n    case AVS_NET_SOCKET_OPT_RECV_TIMEOUT:\n        sock->recv_timeout = option_value.recv_timeout;\n        return AVS_OK;\n    default:\n        return avs_errno(AVS_ENOTSUP);\n    }\n}\n\nstatic const avs_net_socket_v_table_t NET_SOCKET_VTABLE = {\n    .connect = net_connect,\n    .send = net_send,\n    .receive = net_receive,\n    .bind = net_bind,\n    .close = net_close,\n    .cleanup = net_cleanup,\n    .get_system_socket = net_system_socket,\n    .get_remote_host = net_remote_host,\n    .get_remote_port = net_remote_port,\n    .get_local_port = net_local_port,\n    .get_opt = net_get_opt,\n    .set_opt = net_set_opt\n};\n\nstatic avs_error_t\nnet_create_socket(avs_net_socket_t **socket_ptr,\n                  const avs_net_socket_configuration_t *configuration,\n                  int socktype) {\n    assert(socket_ptr);\n    assert(!*socket_ptr);\n    (void) configuration;\n    net_socket_impl_t *socket =\n            (net_socket_impl_t *) avs_calloc(1, sizeof(net_socket_impl_t));\n    if (!socket) {\n        return avs_errno(AVS_ENOMEM);\n    }\n    socket->operations = &NET_SOCKET_VTABLE;\n    socket->socktype = socktype;\n    socket->fd = -1;\n    socket->recv_timeout = avs_time_duration_from_scalar(30, AVS_TIME_S);\n    *socket_ptr = (avs_net_socket_t *) socket;\n    return AVS_OK;\n}\n\navs_error_t _avs_net_create_udp_socket(avs_net_socket_t **socket_ptr,\n                                       const void *configuration) {\n    return net_create_socket(\n            socket_ptr, (const avs_net_socket_configuration_t *) configuration,\n            SOCK_DGRAM);\n}\n\navs_error_t _avs_net_create_tcp_socket(avs_net_socket_t **socket_ptr,\n                                       const void *configuration) {\n    return net_create_socket(\n            socket_ptr, (const avs_net_socket_configuration_t *) configuration,\n            SOCK_STREAM);\n}\n"
  },
  {
    "path": "examples/custom-network/ip-stickiness/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.16)\nproject(custom-network-ip-stickiness C)\n\nset(CMAKE_C_STANDARD 99)\n\nfind_package(anjay REQUIRED)\n\nadd_executable(${PROJECT_NAME}\n               src/main.c\n               src/net_impl.c)\ntarget_link_libraries(${PROJECT_NAME} PRIVATE anjay)\n"
  },
  {
    "path": "examples/custom-network/ip-stickiness/src/main.c",
    "content": "#include <anjay/anjay.h>\n#include <anjay/security.h>\n#include <anjay/server.h>\n#include <avsystem/commons/avs_log.h>\n\n#include <poll.h>\n\nint main_loop(anjay_t *anjay) {\n    while (true) {\n        // Obtain all network data sources\n        AVS_LIST(avs_net_socket_t *const) sockets = anjay_get_sockets(anjay);\n\n        // Prepare to poll() on them\n        size_t numsocks = AVS_LIST_SIZE(sockets);\n        struct pollfd pollfds[numsocks];\n        size_t i = 0;\n        AVS_LIST(avs_net_socket_t *const) sock;\n        AVS_LIST_FOREACH(sock, sockets) {\n            pollfds[i].fd = *(const int *) avs_net_socket_get_system(*sock);\n            pollfds[i].events = POLLIN;\n            pollfds[i].revents = 0;\n            ++i;\n        }\n\n        const int max_wait_time_ms = 1000;\n        // Determine the expected time to the next job in milliseconds.\n        // If there is no job we will wait till something arrives for\n        // at most 1 second (i.e. max_wait_time_ms).\n        int wait_ms =\n                anjay_sched_calculate_wait_time_ms(anjay, max_wait_time_ms);\n\n        // Wait for the events if necessary, and handle them.\n        if (poll(pollfds, numsocks, wait_ms) > 0) {\n            int socket_id = 0;\n            AVS_LIST(avs_net_socket_t *const) socket = NULL;\n            AVS_LIST_FOREACH(socket, sockets) {\n                if (pollfds[socket_id].revents) {\n                    if (anjay_serve(anjay, *socket)) {\n                        avs_log(tutorial, ERROR, \"anjay_serve failed\");\n                    }\n                }\n                ++socket_id;\n            }\n        }\n\n        // Finally run the scheduler\n        anjay_sched_run(anjay);\n    }\n    return 0;\n}\n\n// Installs Security Object and adds an instance of it.\n// An instance of Security Object provides information needed to connect to\n// LwM2M server.\nstatic int setup_security_object(anjay_t *anjay) {\n    if (anjay_security_object_install(anjay)) {\n        return -1;\n    }\n\n    static const char PSK_IDENTITY[] = \"identity\";\n    static const char PSK_KEY[] = \"P4s$w0rd\";\n\n    anjay_security_instance_t security_instance = {\n        .ssid = 1,\n        .server_uri = \"coaps://eu.iot.avsystem.cloud:5684\",\n        .security_mode = ANJAY_SECURITY_PSK,\n        .public_cert_or_psk_identity = (const uint8_t *) PSK_IDENTITY,\n        .public_cert_or_psk_identity_size = strlen(PSK_IDENTITY),\n        .private_cert_or_psk_key = (const uint8_t *) PSK_KEY,\n        .private_cert_or_psk_key_size = strlen(PSK_KEY)\n    };\n\n    // Anjay will assign Instance ID automatically\n    anjay_iid_t security_instance_id = ANJAY_ID_INVALID;\n    if (anjay_security_object_add_instance(anjay, &security_instance,\n                                           &security_instance_id)) {\n        return -1;\n    }\n\n    return 0;\n}\n\n// Installs Server Object and adds an instance of it.\n// An instance of Server Object provides the data related to a LwM2M Server.\nstatic int setup_server_object(anjay_t *anjay) {\n    if (anjay_server_object_install(anjay)) {\n        return -1;\n    }\n\n    const anjay_server_instance_t server_instance = {\n        // Server Short ID\n        .ssid = 1,\n        // Client will send Update message often than every 60 seconds\n        .lifetime = 60,\n        // Disable Default Minimum Period resource\n        .default_min_period = -1,\n        // Disable Default Maximum Period resource\n        .default_max_period = -1,\n        // Disable Disable Timeout resource\n        .disable_timeout = -1,\n        // Sets preferred transport to UDP\n        .binding = \"U\"\n    };\n\n    // Anjay will assign Instance ID automatically\n    anjay_iid_t server_instance_id = ANJAY_ID_INVALID;\n    if (anjay_server_object_add_instance(anjay, &server_instance,\n                                         &server_instance_id)) {\n        return -1;\n    }\n\n    return 0;\n}\n\nint main(int argc, char *argv[]) {\n    if (argc != 2) {\n        avs_log(tutorial, ERROR, \"usage: %s ENDPOINT_NAME\", argv[0]);\n        return -1;\n    }\n\n    const anjay_configuration_t CONFIG = {\n        .endpoint_name = argv[1],\n        .in_buffer_size = 4000,\n        .out_buffer_size = 4000,\n        .msg_cache_size = 4000\n    };\n\n    anjay_t *anjay = anjay_new(&CONFIG);\n    if (!anjay) {\n        avs_log(tutorial, ERROR, \"Could not create Anjay object\");\n        return -1;\n    }\n\n    int result = 0;\n    // Setup necessary objects\n    if (setup_security_object(anjay) || setup_server_object(anjay)) {\n        result = -1;\n    }\n\n    if (!result) {\n        result = main_loop(anjay);\n    }\n\n    anjay_delete(anjay);\n    return result;\n}\n"
  },
  {
    "path": "examples/custom-network/ip-stickiness/src/net_impl.c",
    "content": "#include <inttypes.h>\n\n#include <netdb.h>\n#include <poll.h>\n#include <unistd.h>\n\n#include <arpa/inet.h>\n\n#include <sys/socket.h>\n#include <sys/types.h>\n\n#include <avsystem/commons/avs_socket_v_table.h>\n#include <avsystem/commons/avs_utils.h>\n\n#ifdef AVS_COMMONS_NET_WITH_POSIX_AVS_SOCKET\n#    error \"Custom implementation of the network layer conflicts with AVS_COMMONS_NET_WITH_POSIX_AVS_SOCKET\"\n#endif // AVS_COMMONS_NET_WITH_POSIX_AVS_SOCKET\n\navs_error_t _avs_net_initialize_global_compat_state(void);\nvoid _avs_net_cleanup_global_compat_state(void);\navs_error_t _avs_net_create_tcp_socket(avs_net_socket_t **socket,\n                                       const void *socket_configuration);\navs_error_t _avs_net_create_udp_socket(avs_net_socket_t **socket,\n                                       const void *socket_configuration);\n\navs_error_t _avs_net_initialize_global_compat_state(void) {\n    return AVS_OK;\n}\n\nvoid _avs_net_cleanup_global_compat_state(void) {}\n\ntypedef struct {\n    const avs_net_socket_v_table_t *operations;\n    int socktype;\n    int fd;\n    avs_time_duration_t recv_timeout;\n    char remote_hostname[256];\n    bool shut_down;\n    size_t bytes_sent;\n    size_t bytes_received;\n    avs_net_resolved_endpoint_t *preferred_endpoint;\n} net_socket_impl_t;\n\ntypedef union {\n    struct sockaddr addr;\n    struct sockaddr_in in;\n    struct sockaddr_in6 in6;\n    struct sockaddr_storage storage;\n} sockaddr_union_t;\n\nstatic avs_error_t\nnet_connect(avs_net_socket_t *sock_, const char *host, const char *port) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    struct addrinfo hints = {\n        .ai_socktype = sock->socktype\n    };\n    if (sock->fd >= 0) {\n        getsockopt(sock->fd, SOL_SOCKET, SO_DOMAIN, &hints.ai_family,\n                   &(socklen_t) { sizeof(hints.ai_family) });\n    }\n    struct addrinfo *addrs = NULL;\n    avs_error_t err = AVS_OK;\n    if (getaddrinfo(host, port, &hints, &addrs) || !addrs) {\n        err = avs_errno(AVS_EADDRNOTAVAIL);\n    } else if (sock->fd < 0\n               && (sock->fd = socket(addrs->ai_family, addrs->ai_socktype,\n                                     addrs->ai_protocol))\n                          < 0) {\n        err = avs_errno(AVS_UNKNOWN_ERROR);\n    } else {\n        const struct addrinfo *addr = addrs;\n        if (sock->preferred_endpoint\n                && sock->preferred_endpoint->size == sizeof(sockaddr_union_t)) {\n            while (addr) {\n                if (addr->ai_addrlen <= sizeof(sockaddr_union_t)\n                        && memcmp(addr->ai_addr,\n                                  sock->preferred_endpoint->data.buf,\n                                  addr->ai_addrlen)\n                                       == 0) {\n                    break;\n                }\n                addr = addr->ai_next;\n            }\n        }\n        if (!addr) {\n            // Preferred endpoint not found, use the first one\n            addr = addrs;\n        }\n        if (connect(sock->fd, addr->ai_addr, addr->ai_addrlen)) {\n            err = avs_errno(AVS_ECONNREFUSED);\n        }\n        if (sock->preferred_endpoint && avs_is_ok(err)) {\n            assert(addr->ai_addrlen <= sizeof(sockaddr_union_t));\n            memcpy(sock->preferred_endpoint->data.buf, addr->ai_addr,\n                   addr->ai_addrlen);\n            sock->preferred_endpoint->size = sizeof(sockaddr_union_t);\n        }\n    }\n    if (avs_is_ok(err)) {\n        sock->shut_down = false;\n        snprintf(sock->remote_hostname, sizeof(sock->remote_hostname), \"%s\",\n                 host);\n    }\n    freeaddrinfo(addrs);\n    return err;\n}\n\nstatic avs_error_t\nnet_send(avs_net_socket_t *sock_, const void *buffer, size_t buffer_length) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    ssize_t written = send(sock->fd, buffer, buffer_length, MSG_NOSIGNAL);\n    if (written >= 0) {\n        sock->bytes_sent += (size_t) written;\n        if ((size_t) written == buffer_length) {\n            return AVS_OK;\n        }\n    }\n    return avs_errno(AVS_EIO);\n}\n\nstatic avs_error_t net_receive(avs_net_socket_t *sock_,\n                               size_t *out_bytes_received,\n                               void *buffer,\n                               size_t buffer_length) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    struct pollfd pfd = {\n        .fd = sock->fd,\n        .events = POLLIN\n    };\n    int64_t timeout_ms;\n    if (avs_time_duration_to_scalar(&timeout_ms, AVS_TIME_MS,\n                                    sock->recv_timeout)) {\n        timeout_ms = -1;\n    } else if (timeout_ms < 0) {\n        timeout_ms = 0;\n    }\n    if (poll(&pfd, 1, (int) timeout_ms) == 0) {\n        return avs_errno(AVS_ETIMEDOUT);\n    }\n    ssize_t bytes_received = read(sock->fd, buffer, buffer_length);\n    if (bytes_received < 0) {\n        return avs_errno(AVS_EIO);\n    }\n    *out_bytes_received = (size_t) bytes_received;\n    sock->bytes_received += (size_t) bytes_received;\n    if (buffer_length > 0 && sock->socktype == SOCK_DGRAM\n            && (size_t) bytes_received == buffer_length) {\n        return avs_errno(AVS_EMSGSIZE);\n    }\n    return AVS_OK;\n}\n\nstatic avs_error_t\nnet_bind(avs_net_socket_t *sock_, const char *address, const char *port) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    struct addrinfo hints = {\n        .ai_flags = AI_PASSIVE,\n        .ai_socktype = sock->socktype\n    };\n    if (sock->fd >= 0) {\n        getsockopt(sock->fd, SOL_SOCKET, SO_DOMAIN, &hints.ai_family,\n                   &(socklen_t) { sizeof(hints.ai_family) });\n    }\n    struct addrinfo *addr = NULL;\n    avs_error_t err = AVS_OK;\n    if (getaddrinfo(address, port, &hints, &addr) || !addr) {\n        err = avs_errno(AVS_EADDRNOTAVAIL);\n    } else if ((sock->fd < 0\n                && (sock->fd = socket(addr->ai_family, addr->ai_socktype,\n                                      addr->ai_protocol))\n                           < 0)\n               || setsockopt(sock->fd, SOL_SOCKET, SO_REUSEADDR, &(int) { 1 },\n                             sizeof(int))) {\n        err = avs_errno(AVS_UNKNOWN_ERROR);\n    } else if (bind(sock->fd, addr->ai_addr, addr->ai_addrlen)) {\n        err = avs_errno(AVS_ECONNREFUSED);\n    } else {\n        sock->shut_down = false;\n    }\n    if (avs_is_err(err) && sock->fd >= 0) {\n        close(sock->fd);\n        sock->fd = -1;\n    }\n    freeaddrinfo(addr);\n    return err;\n}\n\nstatic avs_error_t net_close(avs_net_socket_t *sock_) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    avs_error_t err = AVS_OK;\n    if (sock->fd >= 0) {\n        if (close(sock->fd)) {\n            err = avs_errno(AVS_EIO);\n        }\n        sock->fd = -1;\n        sock->shut_down = false;\n    }\n    return err;\n}\n\nstatic avs_error_t net_shutdown(avs_net_socket_t *sock_) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    avs_error_t err = avs_errno(AVS_EBADF);\n    if (sock->fd >= 0) {\n        err = shutdown(sock->fd, SHUT_RDWR) ? avs_errno(AVS_EIO) : AVS_OK;\n        sock->shut_down = true;\n    }\n    return err;\n}\n\nstatic avs_error_t net_cleanup(avs_net_socket_t **sock_ptr) {\n    avs_error_t err = AVS_OK;\n    if (sock_ptr && *sock_ptr) {\n        err = net_close(*sock_ptr);\n        avs_free(*sock_ptr);\n        *sock_ptr = NULL;\n    }\n    return err;\n}\n\nstatic const void *net_system_socket(avs_net_socket_t *sock_) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    return &sock->fd;\n}\n\nstatic avs_error_t stringify_sockaddr_host(const sockaddr_union_t *addr,\n                                           char *out_buffer,\n                                           size_t out_buffer_size) {\n    if ((addr->in.sin_family == AF_INET\n         && inet_ntop(AF_INET, &addr->in.sin_addr, out_buffer,\n                      (socklen_t) out_buffer_size))\n            || (addr->in6.sin6_family == AF_INET6\n                && inet_ntop(AF_INET6, &addr->in6.sin6_addr, out_buffer,\n                             (socklen_t) out_buffer_size))) {\n        return AVS_OK;\n    }\n    return avs_errno(AVS_UNKNOWN_ERROR);\n}\n\nstatic avs_error_t stringify_sockaddr_port(const sockaddr_union_t *addr,\n                                           char *out_buffer,\n                                           size_t out_buffer_size) {\n    if ((addr->in.sin_family == AF_INET\n         && avs_simple_snprintf(out_buffer, out_buffer_size, \"%\" PRIu16,\n                                ntohs(addr->in.sin_port))\n                    >= 0)\n            || (addr->in6.sin6_family == AF_INET6\n                && avs_simple_snprintf(out_buffer, out_buffer_size, \"%\" PRIu16,\n                                       ntohs(addr->in6.sin6_port))\n                           >= 0)) {\n        return AVS_OK;\n    }\n    return avs_errno(AVS_UNKNOWN_ERROR);\n}\n\nstatic avs_error_t net_remote_host(avs_net_socket_t *sock_,\n                                   char *out_buffer,\n                                   size_t out_buffer_size) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    sockaddr_union_t addr;\n    if (getpeername(sock->fd, &addr.addr, &(socklen_t) { sizeof(addr) })) {\n        return avs_errno(AVS_UNKNOWN_ERROR);\n    }\n    return stringify_sockaddr_host(&addr, out_buffer, out_buffer_size);\n}\n\nstatic avs_error_t net_remote_hostname(avs_net_socket_t *sock_,\n                                       char *out_buffer,\n                                       size_t out_buffer_size) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    return avs_simple_snprintf(out_buffer, out_buffer_size, \"%s\",\n                               sock->remote_hostname)\n                           < 0\n                   ? avs_errno(AVS_UNKNOWN_ERROR)\n                   : AVS_OK;\n}\n\nstatic avs_error_t net_remote_port(avs_net_socket_t *sock_,\n                                   char *out_buffer,\n                                   size_t out_buffer_size) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    sockaddr_union_t addr;\n    if (getpeername(sock->fd, &addr.addr, &(socklen_t) { sizeof(addr) })) {\n        return avs_errno(AVS_UNKNOWN_ERROR);\n    }\n    return stringify_sockaddr_port(&addr, out_buffer, out_buffer_size);\n}\n\nstatic avs_error_t net_local_port(avs_net_socket_t *sock_,\n                                  char *out_buffer,\n                                  size_t out_buffer_size) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    sockaddr_union_t addr;\n    if (getsockname(sock->fd, &addr.addr, &(socklen_t) { sizeof(addr) })) {\n        return avs_errno(AVS_UNKNOWN_ERROR);\n    }\n    return stringify_sockaddr_port(&addr, out_buffer, out_buffer_size);\n}\n\nstatic avs_error_t net_get_opt(avs_net_socket_t *sock_,\n                               avs_net_socket_opt_key_t option_key,\n                               avs_net_socket_opt_value_t *out_option_value) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    switch (option_key) {\n    case AVS_NET_SOCKET_OPT_RECV_TIMEOUT:\n        out_option_value->recv_timeout = sock->recv_timeout;\n        return AVS_OK;\n    case AVS_NET_SOCKET_OPT_STATE:\n        if (sock->fd < 0) {\n            out_option_value->state = AVS_NET_SOCKET_STATE_CLOSED;\n        } else if (sock->shut_down) {\n            out_option_value->state = AVS_NET_SOCKET_STATE_SHUTDOWN;\n        } else {\n            sockaddr_union_t addr;\n            if (!getpeername(sock->fd, &addr.addr,\n                             &(socklen_t) { sizeof(addr) })\n                    && ((addr.in.sin_family == AF_INET && addr.in.sin_port != 0)\n                        || (addr.in6.sin6_family == AF_INET6\n                            && addr.in6.sin6_port != 0))) {\n                out_option_value->state = AVS_NET_SOCKET_STATE_CONNECTED;\n            } else {\n                out_option_value->state = AVS_NET_SOCKET_STATE_BOUND;\n            }\n        }\n        return AVS_OK;\n    case AVS_NET_SOCKET_OPT_INNER_MTU:\n        out_option_value->mtu = 1464;\n        return AVS_OK;\n    case AVS_NET_SOCKET_HAS_BUFFERED_DATA:\n        out_option_value->flag = false;\n        return AVS_OK;\n    case AVS_NET_SOCKET_OPT_BYTES_SENT:\n        out_option_value->bytes_sent = sock->bytes_sent;\n        return AVS_OK;\n    case AVS_NET_SOCKET_OPT_BYTES_RECEIVED:\n        out_option_value->bytes_received = sock->bytes_received;\n        return AVS_OK;\n    default:\n        return avs_errno(AVS_ENOTSUP);\n    }\n}\n\nstatic avs_error_t net_set_opt(avs_net_socket_t *sock_,\n                               avs_net_socket_opt_key_t option_key,\n                               avs_net_socket_opt_value_t option_value) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    switch (option_key) {\n    case AVS_NET_SOCKET_OPT_RECV_TIMEOUT:\n        sock->recv_timeout = option_value.recv_timeout;\n        return AVS_OK;\n    default:\n        return avs_errno(AVS_ENOTSUP);\n    }\n}\n\nstatic const avs_net_socket_v_table_t NET_SOCKET_VTABLE = {\n    .connect = net_connect,\n    .send = net_send,\n    .receive = net_receive,\n    .bind = net_bind,\n    .close = net_close,\n    .shutdown = net_shutdown,\n    .cleanup = net_cleanup,\n    .get_system_socket = net_system_socket,\n    .get_remote_host = net_remote_host,\n    .get_remote_hostname = net_remote_hostname,\n    .get_remote_port = net_remote_port,\n    .get_local_port = net_local_port,\n    .get_opt = net_get_opt,\n    .set_opt = net_set_opt\n};\n\nstatic avs_error_t\nnet_create_socket(avs_net_socket_t **socket_ptr,\n                  const avs_net_socket_configuration_t *configuration,\n                  int socktype) {\n    assert(socket_ptr);\n    assert(!*socket_ptr);\n    (void) configuration;\n    net_socket_impl_t *socket =\n            (net_socket_impl_t *) avs_calloc(1, sizeof(net_socket_impl_t));\n    if (!socket) {\n        return avs_errno(AVS_ENOMEM);\n    }\n    socket->operations = &NET_SOCKET_VTABLE;\n    socket->socktype = socktype;\n    socket->fd = -1;\n    socket->recv_timeout = avs_time_duration_from_scalar(30, AVS_TIME_S);\n    socket->preferred_endpoint = configuration->preferred_endpoint;\n    *socket_ptr = (avs_net_socket_t *) socket;\n    return AVS_OK;\n}\n\navs_error_t _avs_net_create_udp_socket(avs_net_socket_t **socket_ptr,\n                                       const void *configuration) {\n    return net_create_socket(\n            socket_ptr, (const avs_net_socket_configuration_t *) configuration,\n            SOCK_DGRAM);\n}\n\navs_error_t _avs_net_create_tcp_socket(avs_net_socket_t **socket_ptr,\n                                       const void *configuration) {\n    return net_create_socket(\n            socket_ptr, (const avs_net_socket_configuration_t *) configuration,\n            SOCK_STREAM);\n}\n\navs_error_t\navs_net_resolved_endpoint_get_host_port(const avs_net_resolved_endpoint_t *endp,\n                                        char *host,\n                                        size_t hostlen,\n                                        char *serv,\n                                        size_t servlen) {\n    AVS_STATIC_ASSERT(sizeof(endp->data.buf) >= sizeof(sockaddr_union_t),\n                      data_buffer_big_enough);\n    if (endp->size != sizeof(sockaddr_union_t)) {\n        return avs_errno(AVS_EINVAL);\n    }\n    const sockaddr_union_t *addr = (const sockaddr_union_t *) &endp->data.buf;\n    avs_error_t err = AVS_OK;\n    (void) ((host\n             && avs_is_err(\n                        (err = stringify_sockaddr_host(addr, host, hostlen))))\n            || (serv\n                && avs_is_err((err = stringify_sockaddr_port(addr, serv,\n                                                             servlen)))));\n    return err;\n}\n"
  },
  {
    "path": "examples/custom-network/minimal/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.16)\nproject(minimal-custom-network C)\n\nset(CMAKE_C_STANDARD 99)\n\nfind_package(anjay REQUIRED)\n\nadd_executable(${PROJECT_NAME}\n               src/main.c\n               src/net_impl.c)\ntarget_link_libraries(${PROJECT_NAME} PRIVATE anjay)\n"
  },
  {
    "path": "examples/custom-network/minimal/src/main.c",
    "content": "#include <anjay/anjay.h>\n#include <anjay/security.h>\n#include <anjay/server.h>\n#include <avsystem/commons/avs_log.h>\n\n#include <poll.h>\n\nint main_loop(anjay_t *anjay) {\n    while (true) {\n        // Obtain all network data sources\n        AVS_LIST(avs_net_socket_t *const) sockets = anjay_get_sockets(anjay);\n\n        // Prepare to poll() on them\n        size_t numsocks = AVS_LIST_SIZE(sockets);\n        struct pollfd pollfds[numsocks];\n        size_t i = 0;\n        AVS_LIST(avs_net_socket_t *const) sock;\n        AVS_LIST_FOREACH(sock, sockets) {\n            pollfds[i].fd = *(const int *) avs_net_socket_get_system(*sock);\n            pollfds[i].events = POLLIN;\n            pollfds[i].revents = 0;\n            ++i;\n        }\n\n        const int max_wait_time_ms = 1000;\n        // Determine the expected time to the next job in milliseconds.\n        // If there is no job we will wait till something arrives for\n        // at most 1 second (i.e. max_wait_time_ms).\n        int wait_ms =\n                anjay_sched_calculate_wait_time_ms(anjay, max_wait_time_ms);\n\n        // Wait for the events if necessary, and handle them.\n        if (poll(pollfds, numsocks, wait_ms) > 0) {\n            int socket_id = 0;\n            AVS_LIST(avs_net_socket_t *const) socket = NULL;\n            AVS_LIST_FOREACH(socket, sockets) {\n                if (pollfds[socket_id].revents) {\n                    if (anjay_serve(anjay, *socket)) {\n                        avs_log(tutorial, ERROR, \"anjay_serve failed\");\n                    }\n                }\n                ++socket_id;\n            }\n        }\n\n        // Finally run the scheduler\n        anjay_sched_run(anjay);\n    }\n    return 0;\n}\n\n// Installs Security Object and adds an instance of it.\n// An instance of Security Object provides information needed to connect to\n// LwM2M server.\nstatic int setup_security_object(anjay_t *anjay) {\n    if (anjay_security_object_install(anjay)) {\n        return -1;\n    }\n\n    static const char PSK_IDENTITY[] = \"identity\";\n    static const char PSK_KEY[] = \"P4s$w0rd\";\n\n    anjay_security_instance_t security_instance = {\n        .ssid = 1,\n        .server_uri = \"coaps://eu.iot.avsystem.cloud:5684\",\n        .security_mode = ANJAY_SECURITY_PSK,\n        .public_cert_or_psk_identity = (const uint8_t *) PSK_IDENTITY,\n        .public_cert_or_psk_identity_size = strlen(PSK_IDENTITY),\n        .private_cert_or_psk_key = (const uint8_t *) PSK_KEY,\n        .private_cert_or_psk_key_size = strlen(PSK_KEY)\n    };\n\n    // Anjay will assign Instance ID automatically\n    anjay_iid_t security_instance_id = ANJAY_ID_INVALID;\n    if (anjay_security_object_add_instance(anjay, &security_instance,\n                                           &security_instance_id)) {\n        return -1;\n    }\n\n    return 0;\n}\n\n// Installs Server Object and adds an instance of it.\n// An instance of Server Object provides the data related to a LwM2M Server.\nstatic int setup_server_object(anjay_t *anjay) {\n    if (anjay_server_object_install(anjay)) {\n        return -1;\n    }\n\n    const anjay_server_instance_t server_instance = {\n        // Server Short ID\n        .ssid = 1,\n        // Client will send Update message often than every 60 seconds\n        .lifetime = 60,\n        // Disable Default Minimum Period resource\n        .default_min_period = -1,\n        // Disable Default Maximum Period resource\n        .default_max_period = -1,\n        // Disable Disable Timeout resource\n        .disable_timeout = -1,\n        // Sets preferred transport to UDP\n        .binding = \"U\"\n    };\n\n    // Anjay will assign Instance ID automatically\n    anjay_iid_t server_instance_id = ANJAY_ID_INVALID;\n    if (anjay_server_object_add_instance(anjay, &server_instance,\n                                         &server_instance_id)) {\n        return -1;\n    }\n\n    return 0;\n}\n\nint main(int argc, char *argv[]) {\n    if (argc != 2) {\n        avs_log(tutorial, ERROR, \"usage: %s ENDPOINT_NAME\", argv[0]);\n        return -1;\n    }\n\n    const anjay_configuration_t CONFIG = {\n        .endpoint_name = argv[1],\n        .in_buffer_size = 4000,\n        .out_buffer_size = 4000,\n        .msg_cache_size = 4000\n    };\n\n    anjay_t *anjay = anjay_new(&CONFIG);\n    if (!anjay) {\n        avs_log(tutorial, ERROR, \"Could not create Anjay object\");\n        return -1;\n    }\n\n    int result = 0;\n    // Setup necessary objects\n    if (setup_security_object(anjay) || setup_server_object(anjay)) {\n        result = -1;\n    }\n\n    if (!result) {\n        result = main_loop(anjay);\n    }\n\n    anjay_delete(anjay);\n    return result;\n}\n"
  },
  {
    "path": "examples/custom-network/minimal/src/net_impl.c",
    "content": "#include <netdb.h>\n#include <poll.h>\n#include <unistd.h>\n\n#include <sys/socket.h>\n#include <sys/types.h>\n\n#include <avsystem/commons/avs_socket_v_table.h>\n\n#ifdef AVS_COMMONS_NET_WITH_POSIX_AVS_SOCKET\n#    error \"Custom implementation of the network layer conflicts with AVS_COMMONS_NET_WITH_POSIX_AVS_SOCKET\"\n#endif // AVS_COMMONS_NET_WITH_POSIX_AVS_SOCKET\n\navs_error_t _avs_net_initialize_global_compat_state(void);\nvoid _avs_net_cleanup_global_compat_state(void);\navs_error_t _avs_net_create_tcp_socket(avs_net_socket_t **socket,\n                                       const void *socket_configuration);\navs_error_t _avs_net_create_udp_socket(avs_net_socket_t **socket,\n                                       const void *socket_configuration);\n\navs_error_t _avs_net_initialize_global_compat_state(void) {\n    return AVS_OK;\n}\n\nvoid _avs_net_cleanup_global_compat_state(void) {}\n\ntypedef struct {\n    const avs_net_socket_v_table_t *operations;\n    int socktype;\n    int fd;\n    avs_time_duration_t recv_timeout;\n} net_socket_impl_t;\n\nstatic avs_error_t\nnet_connect(avs_net_socket_t *sock_, const char *host, const char *port) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    struct addrinfo hints = {\n        .ai_socktype = sock->socktype\n    };\n    if (sock->fd >= 0) {\n        getsockopt(sock->fd, SOL_SOCKET, SO_DOMAIN, &hints.ai_family,\n                   &(socklen_t) { sizeof(hints.ai_family) });\n    }\n    struct addrinfo *addr = NULL;\n    avs_error_t err = AVS_OK;\n    if (getaddrinfo(host, port, &hints, &addr) || !addr) {\n        err = avs_errno(AVS_EADDRNOTAVAIL);\n    } else if (sock->fd < 0\n               && (sock->fd = socket(addr->ai_family, addr->ai_socktype,\n                                     addr->ai_protocol))\n                          < 0) {\n        err = avs_errno(AVS_UNKNOWN_ERROR);\n    } else if (connect(sock->fd, addr->ai_addr, addr->ai_addrlen)) {\n        err = avs_errno(AVS_ECONNREFUSED);\n    }\n    freeaddrinfo(addr);\n    return err;\n}\n\nstatic avs_error_t\nnet_send(avs_net_socket_t *sock_, const void *buffer, size_t buffer_length) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    ssize_t written = send(sock->fd, buffer, buffer_length, MSG_NOSIGNAL);\n    if (written >= 0 && (size_t) written == buffer_length) {\n        return AVS_OK;\n    }\n    return avs_errno(AVS_EIO);\n}\n\nstatic avs_error_t net_receive(avs_net_socket_t *sock_,\n                               size_t *out_bytes_received,\n                               void *buffer,\n                               size_t buffer_length) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    struct pollfd pfd = {\n        .fd = sock->fd,\n        .events = POLLIN\n    };\n    int64_t timeout_ms;\n    if (avs_time_duration_to_scalar(&timeout_ms, AVS_TIME_MS,\n                                    sock->recv_timeout)) {\n        timeout_ms = -1;\n    } else if (timeout_ms < 0) {\n        timeout_ms = 0;\n    }\n    if (poll(&pfd, 1, (int) timeout_ms) == 0) {\n        return avs_errno(AVS_ETIMEDOUT);\n    }\n    ssize_t bytes_received = read(sock->fd, buffer, buffer_length);\n    if (bytes_received < 0) {\n        return avs_errno(AVS_EIO);\n    }\n    *out_bytes_received = (size_t) bytes_received;\n    if (buffer_length > 0 && sock->socktype == SOCK_DGRAM\n            && (size_t) bytes_received == buffer_length) {\n        return avs_errno(AVS_EMSGSIZE);\n    }\n    return AVS_OK;\n}\n\nstatic avs_error_t net_close(avs_net_socket_t *sock_) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    avs_error_t err = AVS_OK;\n    if (sock->fd >= 0) {\n        if (close(sock->fd)) {\n            err = avs_errno(AVS_EIO);\n        }\n        sock->fd = -1;\n    }\n    return err;\n}\n\nstatic avs_error_t net_cleanup(avs_net_socket_t **sock_ptr) {\n    avs_error_t err = AVS_OK;\n    if (sock_ptr && *sock_ptr) {\n        err = net_close(*sock_ptr);\n        avs_free(*sock_ptr);\n        *sock_ptr = NULL;\n    }\n    return err;\n}\n\nstatic const void *net_system_socket(avs_net_socket_t *sock_) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    return &sock->fd;\n}\n\nstatic avs_error_t net_get_opt(avs_net_socket_t *sock_,\n                               avs_net_socket_opt_key_t option_key,\n                               avs_net_socket_opt_value_t *out_option_value) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    switch (option_key) {\n    case AVS_NET_SOCKET_OPT_RECV_TIMEOUT:\n        out_option_value->recv_timeout = sock->recv_timeout;\n        return AVS_OK;\n    case AVS_NET_SOCKET_OPT_STATE:\n        if (sock->fd < 0) {\n            out_option_value->state = AVS_NET_SOCKET_STATE_CLOSED;\n        } else {\n            out_option_value->state = AVS_NET_SOCKET_STATE_CONNECTED;\n        }\n        return AVS_OK;\n    case AVS_NET_SOCKET_OPT_INNER_MTU:\n        out_option_value->mtu = 1464;\n        return AVS_OK;\n    case AVS_NET_SOCKET_HAS_BUFFERED_DATA:\n        out_option_value->flag = false;\n        return AVS_OK;\n    default:\n        return avs_errno(AVS_ENOTSUP);\n    }\n}\n\nstatic avs_error_t net_set_opt(avs_net_socket_t *sock_,\n                               avs_net_socket_opt_key_t option_key,\n                               avs_net_socket_opt_value_t option_value) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    switch (option_key) {\n    case AVS_NET_SOCKET_OPT_RECV_TIMEOUT:\n        sock->recv_timeout = option_value.recv_timeout;\n        return AVS_OK;\n    default:\n        return avs_errno(AVS_ENOTSUP);\n    }\n}\n\nstatic const avs_net_socket_v_table_t NET_SOCKET_VTABLE = {\n    .connect = net_connect,\n    .send = net_send,\n    .receive = net_receive,\n    .close = net_close,\n    .cleanup = net_cleanup,\n    .get_system_socket = net_system_socket,\n    .get_opt = net_get_opt,\n    .set_opt = net_set_opt\n};\n\nstatic avs_error_t\nnet_create_socket(avs_net_socket_t **socket_ptr,\n                  const avs_net_socket_configuration_t *configuration,\n                  int socktype) {\n    assert(socket_ptr);\n    assert(!*socket_ptr);\n    (void) configuration;\n    net_socket_impl_t *socket =\n            (net_socket_impl_t *) avs_calloc(1, sizeof(net_socket_impl_t));\n    if (!socket) {\n        return avs_errno(AVS_ENOMEM);\n    }\n    socket->operations = &NET_SOCKET_VTABLE;\n    socket->socktype = socktype;\n    socket->fd = -1;\n    socket->recv_timeout = avs_time_duration_from_scalar(30, AVS_TIME_S);\n    *socket_ptr = (avs_net_socket_t *) socket;\n    return AVS_OK;\n}\n\navs_error_t _avs_net_create_udp_socket(avs_net_socket_t **socket_ptr,\n                                       const void *configuration) {\n    return net_create_socket(\n            socket_ptr, (const avs_net_socket_configuration_t *) configuration,\n            SOCK_DGRAM);\n}\n\navs_error_t _avs_net_create_tcp_socket(avs_net_socket_t **socket_ptr,\n                                       const void *configuration) {\n    return net_create_socket(\n            socket_ptr, (const avs_net_socket_configuration_t *) configuration,\n            SOCK_STREAM);\n}\n"
  },
  {
    "path": "examples/custom-network/remote-host-port/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.16)\nproject(custom-network-remote-host-port C)\n\nset(CMAKE_C_STANDARD 99)\n\nfind_package(anjay REQUIRED)\n\nadd_executable(${PROJECT_NAME}\n               src/main.c\n               src/net_impl.c)\ntarget_link_libraries(${PROJECT_NAME} PRIVATE anjay)\n"
  },
  {
    "path": "examples/custom-network/remote-host-port/src/main.c",
    "content": "#include <anjay/anjay.h>\n#include <anjay/security.h>\n#include <anjay/server.h>\n#include <avsystem/commons/avs_log.h>\n\n#include <poll.h>\n\nint main_loop(anjay_t *anjay) {\n    while (true) {\n        // Obtain all network data sources\n        AVS_LIST(avs_net_socket_t *const) sockets = anjay_get_sockets(anjay);\n\n        // Prepare to poll() on them\n        size_t numsocks = AVS_LIST_SIZE(sockets);\n        struct pollfd pollfds[numsocks];\n        size_t i = 0;\n        AVS_LIST(avs_net_socket_t *const) sock;\n        AVS_LIST_FOREACH(sock, sockets) {\n            pollfds[i].fd = *(const int *) avs_net_socket_get_system(*sock);\n            pollfds[i].events = POLLIN;\n            pollfds[i].revents = 0;\n            ++i;\n        }\n\n        const int max_wait_time_ms = 1000;\n        // Determine the expected time to the next job in milliseconds.\n        // If there is no job we will wait till something arrives for\n        // at most 1 second (i.e. max_wait_time_ms).\n        int wait_ms =\n                anjay_sched_calculate_wait_time_ms(anjay, max_wait_time_ms);\n\n        // Wait for the events if necessary, and handle them.\n        if (poll(pollfds, numsocks, wait_ms) > 0) {\n            int socket_id = 0;\n            AVS_LIST(avs_net_socket_t *const) socket = NULL;\n            AVS_LIST_FOREACH(socket, sockets) {\n                if (pollfds[socket_id].revents) {\n                    if (anjay_serve(anjay, *socket)) {\n                        avs_log(tutorial, ERROR, \"anjay_serve failed\");\n                    }\n                }\n                ++socket_id;\n            }\n        }\n\n        // Finally run the scheduler\n        anjay_sched_run(anjay);\n    }\n    return 0;\n}\n\n// Installs Security Object and adds an instance of it.\n// An instance of Security Object provides information needed to connect to\n// LwM2M server.\nstatic int setup_security_object(anjay_t *anjay) {\n    if (anjay_security_object_install(anjay)) {\n        return -1;\n    }\n\n    static const char PSK_IDENTITY[] = \"identity\";\n    static const char PSK_KEY[] = \"P4s$w0rd\";\n\n    anjay_security_instance_t security_instance = {\n        .ssid = 1,\n        .server_uri = \"coaps://eu.iot.avsystem.cloud:5684\",\n        .security_mode = ANJAY_SECURITY_PSK,\n        .public_cert_or_psk_identity = (const uint8_t *) PSK_IDENTITY,\n        .public_cert_or_psk_identity_size = strlen(PSK_IDENTITY),\n        .private_cert_or_psk_key = (const uint8_t *) PSK_KEY,\n        .private_cert_or_psk_key_size = strlen(PSK_KEY)\n    };\n\n    // Anjay will assign Instance ID automatically\n    anjay_iid_t security_instance_id = ANJAY_ID_INVALID;\n    if (anjay_security_object_add_instance(anjay, &security_instance,\n                                           &security_instance_id)) {\n        return -1;\n    }\n\n    return 0;\n}\n\n// Installs Server Object and adds an instance of it.\n// An instance of Server Object provides the data related to a LwM2M Server.\nstatic int setup_server_object(anjay_t *anjay) {\n    if (anjay_server_object_install(anjay)) {\n        return -1;\n    }\n\n    const anjay_server_instance_t server_instance = {\n        // Server Short ID\n        .ssid = 1,\n        // Client will send Update message often than every 60 seconds\n        .lifetime = 60,\n        // Disable Default Minimum Period resource\n        .default_min_period = -1,\n        // Disable Default Maximum Period resource\n        .default_max_period = -1,\n        // Disable Disable Timeout resource\n        .disable_timeout = -1,\n        // Sets preferred transport to UDP\n        .binding = \"U\"\n    };\n\n    // Anjay will assign Instance ID automatically\n    anjay_iid_t server_instance_id = ANJAY_ID_INVALID;\n    if (anjay_server_object_add_instance(anjay, &server_instance,\n                                         &server_instance_id)) {\n        return -1;\n    }\n\n    return 0;\n}\n\nint main(int argc, char *argv[]) {\n    if (argc != 2) {\n        avs_log(tutorial, ERROR, \"usage: %s ENDPOINT_NAME\", argv[0]);\n        return -1;\n    }\n\n    const anjay_configuration_t CONFIG = {\n        .endpoint_name = argv[1],\n        .in_buffer_size = 4000,\n        .out_buffer_size = 4000,\n        .msg_cache_size = 4000\n    };\n\n    anjay_t *anjay = anjay_new(&CONFIG);\n    if (!anjay) {\n        avs_log(tutorial, ERROR, \"Could not create Anjay object\");\n        return -1;\n    }\n\n    int result = 0;\n    // Setup necessary objects\n    if (setup_security_object(anjay) || setup_server_object(anjay)) {\n        result = -1;\n    }\n\n    if (!result) {\n        result = main_loop(anjay);\n    }\n\n    anjay_delete(anjay);\n    return result;\n}\n"
  },
  {
    "path": "examples/custom-network/remote-host-port/src/net_impl.c",
    "content": "#include <inttypes.h>\n\n#include <netdb.h>\n#include <poll.h>\n#include <unistd.h>\n\n#include <arpa/inet.h>\n\n#include <sys/socket.h>\n#include <sys/types.h>\n\n#include <avsystem/commons/avs_socket_v_table.h>\n#include <avsystem/commons/avs_utils.h>\n\n#ifdef AVS_COMMONS_NET_WITH_POSIX_AVS_SOCKET\n#    error \"Custom implementation of the network layer conflicts with AVS_COMMONS_NET_WITH_POSIX_AVS_SOCKET\"\n#endif // AVS_COMMONS_NET_WITH_POSIX_AVS_SOCKET\n\navs_error_t _avs_net_initialize_global_compat_state(void);\nvoid _avs_net_cleanup_global_compat_state(void);\navs_error_t _avs_net_create_tcp_socket(avs_net_socket_t **socket,\n                                       const void *socket_configuration);\navs_error_t _avs_net_create_udp_socket(avs_net_socket_t **socket,\n                                       const void *socket_configuration);\n\navs_error_t _avs_net_initialize_global_compat_state(void) {\n    return AVS_OK;\n}\n\nvoid _avs_net_cleanup_global_compat_state(void) {}\n\ntypedef struct {\n    const avs_net_socket_v_table_t *operations;\n    int socktype;\n    int fd;\n    avs_time_duration_t recv_timeout;\n} net_socket_impl_t;\n\ntypedef union {\n    struct sockaddr addr;\n    struct sockaddr_in in;\n    struct sockaddr_in6 in6;\n    struct sockaddr_storage storage;\n} sockaddr_union_t;\n\nstatic avs_error_t\nnet_connect(avs_net_socket_t *sock_, const char *host, const char *port) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    struct addrinfo hints = {\n        .ai_socktype = sock->socktype\n    };\n    if (sock->fd >= 0) {\n        getsockopt(sock->fd, SOL_SOCKET, SO_DOMAIN, &hints.ai_family,\n                   &(socklen_t) { sizeof(hints.ai_family) });\n    }\n    struct addrinfo *addr = NULL;\n    avs_error_t err = AVS_OK;\n    if (getaddrinfo(host, port, &hints, &addr) || !addr) {\n        err = avs_errno(AVS_EADDRNOTAVAIL);\n    } else if (sock->fd < 0\n               && (sock->fd = socket(addr->ai_family, addr->ai_socktype,\n                                     addr->ai_protocol))\n                          < 0) {\n        err = avs_errno(AVS_UNKNOWN_ERROR);\n    } else if (connect(sock->fd, addr->ai_addr, addr->ai_addrlen)) {\n        err = avs_errno(AVS_ECONNREFUSED);\n    }\n    freeaddrinfo(addr);\n    return err;\n}\n\nstatic avs_error_t\nnet_send(avs_net_socket_t *sock_, const void *buffer, size_t buffer_length) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    ssize_t written = send(sock->fd, buffer, buffer_length, MSG_NOSIGNAL);\n    if (written >= 0 && (size_t) written == buffer_length) {\n        return AVS_OK;\n    }\n    return avs_errno(AVS_EIO);\n}\n\nstatic avs_error_t net_receive(avs_net_socket_t *sock_,\n                               size_t *out_bytes_received,\n                               void *buffer,\n                               size_t buffer_length) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    struct pollfd pfd = {\n        .fd = sock->fd,\n        .events = POLLIN\n    };\n    int64_t timeout_ms;\n    if (avs_time_duration_to_scalar(&timeout_ms, AVS_TIME_MS,\n                                    sock->recv_timeout)) {\n        timeout_ms = -1;\n    } else if (timeout_ms < 0) {\n        timeout_ms = 0;\n    }\n    if (poll(&pfd, 1, (int) timeout_ms) == 0) {\n        return avs_errno(AVS_ETIMEDOUT);\n    }\n    ssize_t bytes_received = read(sock->fd, buffer, buffer_length);\n    if (bytes_received < 0) {\n        return avs_errno(AVS_EIO);\n    }\n    *out_bytes_received = (size_t) bytes_received;\n    if (buffer_length > 0 && sock->socktype == SOCK_DGRAM\n            && (size_t) bytes_received == buffer_length) {\n        return avs_errno(AVS_EMSGSIZE);\n    }\n    return AVS_OK;\n}\n\nstatic avs_error_t net_close(avs_net_socket_t *sock_) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    avs_error_t err = AVS_OK;\n    if (sock->fd >= 0) {\n        if (close(sock->fd)) {\n            err = avs_errno(AVS_EIO);\n        }\n        sock->fd = -1;\n    }\n    return err;\n}\n\nstatic avs_error_t net_cleanup(avs_net_socket_t **sock_ptr) {\n    avs_error_t err = AVS_OK;\n    if (sock_ptr && *sock_ptr) {\n        err = net_close(*sock_ptr);\n        avs_free(*sock_ptr);\n        *sock_ptr = NULL;\n    }\n    return err;\n}\n\nstatic const void *net_system_socket(avs_net_socket_t *sock_) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    return &sock->fd;\n}\n\nstatic avs_error_t stringify_sockaddr_host(const sockaddr_union_t *addr,\n                                           char *out_buffer,\n                                           size_t out_buffer_size) {\n    if ((addr->in.sin_family == AF_INET\n         && inet_ntop(AF_INET, &addr->in.sin_addr, out_buffer,\n                      (socklen_t) out_buffer_size))\n            || (addr->in6.sin6_family == AF_INET6\n                && inet_ntop(AF_INET6, &addr->in6.sin6_addr, out_buffer,\n                             (socklen_t) out_buffer_size))) {\n        return AVS_OK;\n    }\n    return avs_errno(AVS_UNKNOWN_ERROR);\n}\n\nstatic avs_error_t stringify_sockaddr_port(const sockaddr_union_t *addr,\n                                           char *out_buffer,\n                                           size_t out_buffer_size) {\n    if ((addr->in.sin_family == AF_INET\n         && avs_simple_snprintf(out_buffer, out_buffer_size, \"%\" PRIu16,\n                                ntohs(addr->in.sin_port))\n                    >= 0)\n            || (addr->in6.sin6_family == AF_INET6\n                && avs_simple_snprintf(out_buffer, out_buffer_size, \"%\" PRIu16,\n                                       ntohs(addr->in6.sin6_port))\n                           >= 0)) {\n        return AVS_OK;\n    }\n    return avs_errno(AVS_UNKNOWN_ERROR);\n}\n\nstatic avs_error_t net_remote_host(avs_net_socket_t *sock_,\n                                   char *out_buffer,\n                                   size_t out_buffer_size) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    sockaddr_union_t addr;\n    if (getpeername(sock->fd, &addr.addr, &(socklen_t) { sizeof(addr) })) {\n        return avs_errno(AVS_UNKNOWN_ERROR);\n    }\n    return stringify_sockaddr_host(&addr, out_buffer, out_buffer_size);\n}\n\nstatic avs_error_t net_remote_port(avs_net_socket_t *sock_,\n                                   char *out_buffer,\n                                   size_t out_buffer_size) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    sockaddr_union_t addr;\n    if (getpeername(sock->fd, &addr.addr, &(socklen_t) { sizeof(addr) })) {\n        return avs_errno(AVS_UNKNOWN_ERROR);\n    }\n    return stringify_sockaddr_port(&addr, out_buffer, out_buffer_size);\n}\n\nstatic avs_error_t net_get_opt(avs_net_socket_t *sock_,\n                               avs_net_socket_opt_key_t option_key,\n                               avs_net_socket_opt_value_t *out_option_value) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    switch (option_key) {\n    case AVS_NET_SOCKET_OPT_RECV_TIMEOUT:\n        out_option_value->recv_timeout = sock->recv_timeout;\n        return AVS_OK;\n    case AVS_NET_SOCKET_OPT_STATE:\n        if (sock->fd < 0) {\n            out_option_value->state = AVS_NET_SOCKET_STATE_CLOSED;\n        } else {\n            out_option_value->state = AVS_NET_SOCKET_STATE_CONNECTED;\n        }\n        return AVS_OK;\n    case AVS_NET_SOCKET_OPT_INNER_MTU:\n        out_option_value->mtu = 1464;\n        return AVS_OK;\n    case AVS_NET_SOCKET_HAS_BUFFERED_DATA:\n        out_option_value->flag = false;\n        return AVS_OK;\n    default:\n        return avs_errno(AVS_ENOTSUP);\n    }\n}\n\nstatic avs_error_t net_set_opt(avs_net_socket_t *sock_,\n                               avs_net_socket_opt_key_t option_key,\n                               avs_net_socket_opt_value_t option_value) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    switch (option_key) {\n    case AVS_NET_SOCKET_OPT_RECV_TIMEOUT:\n        sock->recv_timeout = option_value.recv_timeout;\n        return AVS_OK;\n    default:\n        return avs_errno(AVS_ENOTSUP);\n    }\n}\n\nstatic const avs_net_socket_v_table_t NET_SOCKET_VTABLE = {\n    .connect = net_connect,\n    .send = net_send,\n    .receive = net_receive,\n    .close = net_close,\n    .cleanup = net_cleanup,\n    .get_system_socket = net_system_socket,\n    .get_remote_host = net_remote_host,\n    .get_remote_port = net_remote_port,\n    .get_opt = net_get_opt,\n    .set_opt = net_set_opt\n};\n\nstatic avs_error_t\nnet_create_socket(avs_net_socket_t **socket_ptr,\n                  const avs_net_socket_configuration_t *configuration,\n                  int socktype) {\n    assert(socket_ptr);\n    assert(!*socket_ptr);\n    (void) configuration;\n    net_socket_impl_t *socket =\n            (net_socket_impl_t *) avs_calloc(1, sizeof(net_socket_impl_t));\n    if (!socket) {\n        return avs_errno(AVS_ENOMEM);\n    }\n    socket->operations = &NET_SOCKET_VTABLE;\n    socket->socktype = socktype;\n    socket->fd = -1;\n    socket->recv_timeout = avs_time_duration_from_scalar(30, AVS_TIME_S);\n    *socket_ptr = (avs_net_socket_t *) socket;\n    return AVS_OK;\n}\n\navs_error_t _avs_net_create_udp_socket(avs_net_socket_t **socket_ptr,\n                                       const void *configuration) {\n    return net_create_socket(\n            socket_ptr, (const avs_net_socket_configuration_t *) configuration,\n            SOCK_DGRAM);\n}\n\navs_error_t _avs_net_create_tcp_socket(avs_net_socket_t **socket_ptr,\n                                       const void *configuration) {\n    return net_create_socket(\n            socket_ptr, (const avs_net_socket_configuration_t *) configuration,\n            SOCK_STREAM);\n}\n"
  },
  {
    "path": "examples/custom-network/shutdown-remote-hostname/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.16)\nproject(custom-network-shutdown-remote-hostname C)\n\nset(CMAKE_C_STANDARD 99)\n\nfind_package(anjay REQUIRED)\n\nadd_executable(${PROJECT_NAME}\n               src/main.c\n               src/net_impl.c)\ntarget_link_libraries(${PROJECT_NAME} PRIVATE anjay)\n"
  },
  {
    "path": "examples/custom-network/shutdown-remote-hostname/src/main.c",
    "content": "#include <anjay/anjay.h>\n#include <anjay/security.h>\n#include <anjay/server.h>\n#include <avsystem/commons/avs_log.h>\n\n#include <poll.h>\n\nint main_loop(anjay_t *anjay) {\n    while (true) {\n        // Obtain all network data sources\n        AVS_LIST(avs_net_socket_t *const) sockets = anjay_get_sockets(anjay);\n\n        // Prepare to poll() on them\n        size_t numsocks = AVS_LIST_SIZE(sockets);\n        struct pollfd pollfds[numsocks];\n        size_t i = 0;\n        AVS_LIST(avs_net_socket_t *const) sock;\n        AVS_LIST_FOREACH(sock, sockets) {\n            pollfds[i].fd = *(const int *) avs_net_socket_get_system(*sock);\n            pollfds[i].events = POLLIN;\n            pollfds[i].revents = 0;\n            ++i;\n        }\n\n        const int max_wait_time_ms = 1000;\n        // Determine the expected time to the next job in milliseconds.\n        // If there is no job we will wait till something arrives for\n        // at most 1 second (i.e. max_wait_time_ms).\n        int wait_ms =\n                anjay_sched_calculate_wait_time_ms(anjay, max_wait_time_ms);\n\n        // Wait for the events if necessary, and handle them.\n        if (poll(pollfds, numsocks, wait_ms) > 0) {\n            int socket_id = 0;\n            AVS_LIST(avs_net_socket_t *const) socket = NULL;\n            AVS_LIST_FOREACH(socket, sockets) {\n                if (pollfds[socket_id].revents) {\n                    if (anjay_serve(anjay, *socket)) {\n                        avs_log(tutorial, ERROR, \"anjay_serve failed\");\n                    }\n                }\n                ++socket_id;\n            }\n        }\n\n        // Finally run the scheduler\n        anjay_sched_run(anjay);\n    }\n    return 0;\n}\n\n// Installs Security Object and adds an instance of it.\n// An instance of Security Object provides information needed to connect to\n// LwM2M server.\nstatic int setup_security_object(anjay_t *anjay) {\n    if (anjay_security_object_install(anjay)) {\n        return -1;\n    }\n\n    static const char PSK_IDENTITY[] = \"identity\";\n    static const char PSK_KEY[] = \"P4s$w0rd\";\n\n    anjay_security_instance_t security_instance = {\n        .ssid = 1,\n        .server_uri = \"coaps://eu.iot.avsystem.cloud:5684\",\n        .security_mode = ANJAY_SECURITY_PSK,\n        .public_cert_or_psk_identity = (const uint8_t *) PSK_IDENTITY,\n        .public_cert_or_psk_identity_size = strlen(PSK_IDENTITY),\n        .private_cert_or_psk_key = (const uint8_t *) PSK_KEY,\n        .private_cert_or_psk_key_size = strlen(PSK_KEY)\n    };\n\n    // Anjay will assign Instance ID automatically\n    anjay_iid_t security_instance_id = ANJAY_ID_INVALID;\n    if (anjay_security_object_add_instance(anjay, &security_instance,\n                                           &security_instance_id)) {\n        return -1;\n    }\n\n    return 0;\n}\n\n// Installs Server Object and adds an instance of it.\n// An instance of Server Object provides the data related to a LwM2M Server.\nstatic int setup_server_object(anjay_t *anjay) {\n    if (anjay_server_object_install(anjay)) {\n        return -1;\n    }\n\n    const anjay_server_instance_t server_instance = {\n        // Server Short ID\n        .ssid = 1,\n        // Client will send Update message often than every 60 seconds\n        .lifetime = 60,\n        // Disable Default Minimum Period resource\n        .default_min_period = -1,\n        // Disable Default Maximum Period resource\n        .default_max_period = -1,\n        // Disable Disable Timeout resource\n        .disable_timeout = -1,\n        // Sets preferred transport to UDP\n        .binding = \"U\"\n    };\n\n    // Anjay will assign Instance ID automatically\n    anjay_iid_t server_instance_id = ANJAY_ID_INVALID;\n    if (anjay_server_object_add_instance(anjay, &server_instance,\n                                         &server_instance_id)) {\n        return -1;\n    }\n\n    return 0;\n}\n\nint main(int argc, char *argv[]) {\n    if (argc != 2) {\n        avs_log(tutorial, ERROR, \"usage: %s ENDPOINT_NAME\", argv[0]);\n        return -1;\n    }\n\n    const anjay_configuration_t CONFIG = {\n        .endpoint_name = argv[1],\n        .in_buffer_size = 4000,\n        .out_buffer_size = 4000,\n        .msg_cache_size = 4000\n    };\n\n    anjay_t *anjay = anjay_new(&CONFIG);\n    if (!anjay) {\n        avs_log(tutorial, ERROR, \"Could not create Anjay object\");\n        return -1;\n    }\n\n    int result = 0;\n    // Setup necessary objects\n    if (setup_security_object(anjay) || setup_server_object(anjay)) {\n        result = -1;\n    }\n\n    if (!result) {\n        result = main_loop(anjay);\n    }\n\n    anjay_delete(anjay);\n    return result;\n}\n"
  },
  {
    "path": "examples/custom-network/shutdown-remote-hostname/src/net_impl.c",
    "content": "#include <inttypes.h>\n\n#include <netdb.h>\n#include <poll.h>\n#include <unistd.h>\n\n#include <arpa/inet.h>\n\n#include <sys/socket.h>\n#include <sys/types.h>\n\n#include <avsystem/commons/avs_socket_v_table.h>\n#include <avsystem/commons/avs_utils.h>\n\n#ifdef AVS_COMMONS_NET_WITH_POSIX_AVS_SOCKET\n#    error \"Custom implementation of the network layer conflicts with AVS_COMMONS_NET_WITH_POSIX_AVS_SOCKET\"\n#endif // AVS_COMMONS_NET_WITH_POSIX_AVS_SOCKET\n\navs_error_t _avs_net_initialize_global_compat_state(void);\nvoid _avs_net_cleanup_global_compat_state(void);\navs_error_t _avs_net_create_tcp_socket(avs_net_socket_t **socket,\n                                       const void *socket_configuration);\navs_error_t _avs_net_create_udp_socket(avs_net_socket_t **socket,\n                                       const void *socket_configuration);\n\navs_error_t _avs_net_initialize_global_compat_state(void) {\n    return AVS_OK;\n}\n\nvoid _avs_net_cleanup_global_compat_state(void) {}\n\ntypedef struct {\n    const avs_net_socket_v_table_t *operations;\n    int socktype;\n    int fd;\n    avs_time_duration_t recv_timeout;\n    char remote_hostname[256];\n    bool shut_down;\n} net_socket_impl_t;\n\ntypedef union {\n    struct sockaddr addr;\n    struct sockaddr_in in;\n    struct sockaddr_in6 in6;\n    struct sockaddr_storage storage;\n} sockaddr_union_t;\n\nstatic avs_error_t\nnet_connect(avs_net_socket_t *sock_, const char *host, const char *port) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    struct addrinfo hints = {\n        .ai_socktype = sock->socktype\n    };\n    if (sock->fd >= 0) {\n        getsockopt(sock->fd, SOL_SOCKET, SO_DOMAIN, &hints.ai_family,\n                   &(socklen_t) { sizeof(hints.ai_family) });\n    }\n    struct addrinfo *addr = NULL;\n    avs_error_t err = AVS_OK;\n    if (getaddrinfo(host, port, &hints, &addr) || !addr) {\n        err = avs_errno(AVS_EADDRNOTAVAIL);\n    } else if (sock->fd < 0\n               && (sock->fd = socket(addr->ai_family, addr->ai_socktype,\n                                     addr->ai_protocol))\n                          < 0) {\n        err = avs_errno(AVS_UNKNOWN_ERROR);\n    } else if (connect(sock->fd, addr->ai_addr, addr->ai_addrlen)) {\n        err = avs_errno(AVS_ECONNREFUSED);\n    }\n    if (avs_is_ok(err)) {\n        sock->shut_down = false;\n        snprintf(sock->remote_hostname, sizeof(sock->remote_hostname), \"%s\",\n                 host);\n    }\n    freeaddrinfo(addr);\n    return err;\n}\n\nstatic avs_error_t\nnet_send(avs_net_socket_t *sock_, const void *buffer, size_t buffer_length) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    ssize_t written = send(sock->fd, buffer, buffer_length, MSG_NOSIGNAL);\n    if (written >= 0 && (size_t) written == buffer_length) {\n        return AVS_OK;\n    }\n    return avs_errno(AVS_EIO);\n}\n\nstatic avs_error_t net_receive(avs_net_socket_t *sock_,\n                               size_t *out_bytes_received,\n                               void *buffer,\n                               size_t buffer_length) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    struct pollfd pfd = {\n        .fd = sock->fd,\n        .events = POLLIN\n    };\n    int64_t timeout_ms;\n    if (avs_time_duration_to_scalar(&timeout_ms, AVS_TIME_MS,\n                                    sock->recv_timeout)) {\n        timeout_ms = -1;\n    } else if (timeout_ms < 0) {\n        timeout_ms = 0;\n    }\n    if (poll(&pfd, 1, (int) timeout_ms) == 0) {\n        return avs_errno(AVS_ETIMEDOUT);\n    }\n    ssize_t bytes_received = read(sock->fd, buffer, buffer_length);\n    if (bytes_received < 0) {\n        return avs_errno(AVS_EIO);\n    }\n    *out_bytes_received = (size_t) bytes_received;\n    if (buffer_length > 0 && sock->socktype == SOCK_DGRAM\n            && (size_t) bytes_received == buffer_length) {\n        return avs_errno(AVS_EMSGSIZE);\n    }\n    return AVS_OK;\n}\n\nstatic avs_error_t\nnet_bind(avs_net_socket_t *sock_, const char *address, const char *port) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    struct addrinfo hints = {\n        .ai_flags = AI_PASSIVE,\n        .ai_socktype = sock->socktype\n    };\n    if (sock->fd >= 0) {\n        getsockopt(sock->fd, SOL_SOCKET, SO_DOMAIN, &hints.ai_family,\n                   &(socklen_t) { sizeof(hints.ai_family) });\n    }\n    struct addrinfo *addr = NULL;\n    avs_error_t err = AVS_OK;\n    if (getaddrinfo(address, port, &hints, &addr) || !addr) {\n        err = avs_errno(AVS_EADDRNOTAVAIL);\n    } else if ((sock->fd < 0\n                && (sock->fd = socket(addr->ai_family, addr->ai_socktype,\n                                      addr->ai_protocol))\n                           < 0)\n               || setsockopt(sock->fd, SOL_SOCKET, SO_REUSEADDR, &(int) { 1 },\n                             sizeof(int))) {\n        err = avs_errno(AVS_UNKNOWN_ERROR);\n    } else if (bind(sock->fd, addr->ai_addr, addr->ai_addrlen)) {\n        err = avs_errno(AVS_ECONNREFUSED);\n    } else {\n        sock->shut_down = false;\n    }\n    if (avs_is_err(err) && sock->fd >= 0) {\n        close(sock->fd);\n        sock->fd = -1;\n    }\n    freeaddrinfo(addr);\n    return err;\n}\n\nstatic avs_error_t net_close(avs_net_socket_t *sock_) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    avs_error_t err = AVS_OK;\n    if (sock->fd >= 0) {\n        if (close(sock->fd)) {\n            err = avs_errno(AVS_EIO);\n        }\n        sock->fd = -1;\n        sock->shut_down = false;\n    }\n    return err;\n}\n\nstatic avs_error_t net_shutdown(avs_net_socket_t *sock_) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    avs_error_t err = avs_errno(AVS_EBADF);\n    if (sock->fd >= 0) {\n        err = shutdown(sock->fd, SHUT_RDWR) ? avs_errno(AVS_EIO) : AVS_OK;\n        sock->shut_down = true;\n    }\n    return err;\n}\n\nstatic avs_error_t net_cleanup(avs_net_socket_t **sock_ptr) {\n    avs_error_t err = AVS_OK;\n    if (sock_ptr && *sock_ptr) {\n        err = net_close(*sock_ptr);\n        avs_free(*sock_ptr);\n        *sock_ptr = NULL;\n    }\n    return err;\n}\n\nstatic const void *net_system_socket(avs_net_socket_t *sock_) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    return &sock->fd;\n}\n\nstatic avs_error_t stringify_sockaddr_host(const sockaddr_union_t *addr,\n                                           char *out_buffer,\n                                           size_t out_buffer_size) {\n    if ((addr->in.sin_family == AF_INET\n         && inet_ntop(AF_INET, &addr->in.sin_addr, out_buffer,\n                      (socklen_t) out_buffer_size))\n            || (addr->in6.sin6_family == AF_INET6\n                && inet_ntop(AF_INET6, &addr->in6.sin6_addr, out_buffer,\n                             (socklen_t) out_buffer_size))) {\n        return AVS_OK;\n    }\n    return avs_errno(AVS_UNKNOWN_ERROR);\n}\n\nstatic avs_error_t stringify_sockaddr_port(const sockaddr_union_t *addr,\n                                           char *out_buffer,\n                                           size_t out_buffer_size) {\n    if ((addr->in.sin_family == AF_INET\n         && avs_simple_snprintf(out_buffer, out_buffer_size, \"%\" PRIu16,\n                                ntohs(addr->in.sin_port))\n                    >= 0)\n            || (addr->in6.sin6_family == AF_INET6\n                && avs_simple_snprintf(out_buffer, out_buffer_size, \"%\" PRIu16,\n                                       ntohs(addr->in6.sin6_port))\n                           >= 0)) {\n        return AVS_OK;\n    }\n    return avs_errno(AVS_UNKNOWN_ERROR);\n}\n\nstatic avs_error_t net_remote_host(avs_net_socket_t *sock_,\n                                   char *out_buffer,\n                                   size_t out_buffer_size) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    sockaddr_union_t addr;\n    if (getpeername(sock->fd, &addr.addr, &(socklen_t) { sizeof(addr) })) {\n        return avs_errno(AVS_UNKNOWN_ERROR);\n    }\n    return stringify_sockaddr_host(&addr, out_buffer, out_buffer_size);\n}\n\nstatic avs_error_t net_remote_hostname(avs_net_socket_t *sock_,\n                                       char *out_buffer,\n                                       size_t out_buffer_size) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    return avs_simple_snprintf(out_buffer, out_buffer_size, \"%s\",\n                               sock->remote_hostname)\n                           < 0\n                   ? avs_errno(AVS_UNKNOWN_ERROR)\n                   : AVS_OK;\n}\n\nstatic avs_error_t net_remote_port(avs_net_socket_t *sock_,\n                                   char *out_buffer,\n                                   size_t out_buffer_size) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    sockaddr_union_t addr;\n    if (getpeername(sock->fd, &addr.addr, &(socklen_t) { sizeof(addr) })) {\n        return avs_errno(AVS_UNKNOWN_ERROR);\n    }\n    return stringify_sockaddr_port(&addr, out_buffer, out_buffer_size);\n}\n\nstatic avs_error_t net_local_port(avs_net_socket_t *sock_,\n                                  char *out_buffer,\n                                  size_t out_buffer_size) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    sockaddr_union_t addr;\n    if (getsockname(sock->fd, &addr.addr, &(socklen_t) { sizeof(addr) })) {\n        return avs_errno(AVS_UNKNOWN_ERROR);\n    }\n    return stringify_sockaddr_port(&addr, out_buffer, out_buffer_size);\n}\n\nstatic avs_error_t net_get_opt(avs_net_socket_t *sock_,\n                               avs_net_socket_opt_key_t option_key,\n                               avs_net_socket_opt_value_t *out_option_value) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    switch (option_key) {\n    case AVS_NET_SOCKET_OPT_RECV_TIMEOUT:\n        out_option_value->recv_timeout = sock->recv_timeout;\n        return AVS_OK;\n    case AVS_NET_SOCKET_OPT_STATE:\n        if (sock->fd < 0) {\n            out_option_value->state = AVS_NET_SOCKET_STATE_CLOSED;\n        } else if (sock->shut_down) {\n            out_option_value->state = AVS_NET_SOCKET_STATE_SHUTDOWN;\n        } else {\n            sockaddr_union_t addr;\n            if (!getpeername(sock->fd, &addr.addr,\n                             &(socklen_t) { sizeof(addr) })\n                    && ((addr.in.sin_family == AF_INET && addr.in.sin_port != 0)\n                        || (addr.in6.sin6_family == AF_INET6\n                            && addr.in6.sin6_port != 0))) {\n                out_option_value->state = AVS_NET_SOCKET_STATE_CONNECTED;\n            } else {\n                out_option_value->state = AVS_NET_SOCKET_STATE_BOUND;\n            }\n        }\n        return AVS_OK;\n    case AVS_NET_SOCKET_OPT_INNER_MTU:\n        out_option_value->mtu = 1464;\n        return AVS_OK;\n    case AVS_NET_SOCKET_HAS_BUFFERED_DATA:\n        out_option_value->flag = false;\n        return AVS_OK;\n    default:\n        return avs_errno(AVS_ENOTSUP);\n    }\n}\n\nstatic avs_error_t net_set_opt(avs_net_socket_t *sock_,\n                               avs_net_socket_opt_key_t option_key,\n                               avs_net_socket_opt_value_t option_value) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    switch (option_key) {\n    case AVS_NET_SOCKET_OPT_RECV_TIMEOUT:\n        sock->recv_timeout = option_value.recv_timeout;\n        return AVS_OK;\n    default:\n        return avs_errno(AVS_ENOTSUP);\n    }\n}\n\nstatic const avs_net_socket_v_table_t NET_SOCKET_VTABLE = {\n    .connect = net_connect,\n    .send = net_send,\n    .receive = net_receive,\n    .bind = net_bind,\n    .close = net_close,\n    .shutdown = net_shutdown,\n    .cleanup = net_cleanup,\n    .get_system_socket = net_system_socket,\n    .get_remote_host = net_remote_host,\n    .get_remote_hostname = net_remote_hostname,\n    .get_remote_port = net_remote_port,\n    .get_local_port = net_local_port,\n    .get_opt = net_get_opt,\n    .set_opt = net_set_opt\n};\n\nstatic avs_error_t\nnet_create_socket(avs_net_socket_t **socket_ptr,\n                  const avs_net_socket_configuration_t *configuration,\n                  int socktype) {\n    assert(socket_ptr);\n    assert(!*socket_ptr);\n    (void) configuration;\n    net_socket_impl_t *socket =\n            (net_socket_impl_t *) avs_calloc(1, sizeof(net_socket_impl_t));\n    if (!socket) {\n        return avs_errno(AVS_ENOMEM);\n    }\n    socket->operations = &NET_SOCKET_VTABLE;\n    socket->socktype = socktype;\n    socket->fd = -1;\n    socket->recv_timeout = avs_time_duration_from_scalar(30, AVS_TIME_S);\n    *socket_ptr = (avs_net_socket_t *) socket;\n    return AVS_OK;\n}\n\navs_error_t _avs_net_create_udp_socket(avs_net_socket_t **socket_ptr,\n                                       const void *configuration) {\n    return net_create_socket(\n            socket_ptr, (const avs_net_socket_configuration_t *) configuration,\n            SOCK_DGRAM);\n}\n\navs_error_t _avs_net_create_tcp_socket(avs_net_socket_t **socket_ptr,\n                                       const void *configuration) {\n    return net_create_socket(\n            socket_ptr, (const avs_net_socket_configuration_t *) configuration,\n            SOCK_STREAM);\n}\n"
  },
  {
    "path": "examples/custom-network/stats/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.16)\nproject(custom-network-stats C)\n\nset(CMAKE_C_STANDARD 99)\n\nfind_package(anjay REQUIRED)\n\nadd_executable(${PROJECT_NAME}\n               src/main.c\n               src/net_impl.c)\ntarget_link_libraries(${PROJECT_NAME} PRIVATE anjay)\n"
  },
  {
    "path": "examples/custom-network/stats/src/main.c",
    "content": "#include <anjay/anjay.h>\n#include <anjay/security.h>\n#include <anjay/server.h>\n#include <avsystem/commons/avs_log.h>\n\n#include <poll.h>\n\nint main_loop(anjay_t *anjay) {\n    while (true) {\n        // Obtain all network data sources\n        AVS_LIST(avs_net_socket_t *const) sockets = anjay_get_sockets(anjay);\n\n        // Prepare to poll() on them\n        size_t numsocks = AVS_LIST_SIZE(sockets);\n        struct pollfd pollfds[numsocks];\n        size_t i = 0;\n        AVS_LIST(avs_net_socket_t *const) sock;\n        AVS_LIST_FOREACH(sock, sockets) {\n            pollfds[i].fd = *(const int *) avs_net_socket_get_system(*sock);\n            pollfds[i].events = POLLIN;\n            pollfds[i].revents = 0;\n            ++i;\n        }\n\n        const int max_wait_time_ms = 1000;\n        // Determine the expected time to the next job in milliseconds.\n        // If there is no job we will wait till something arrives for\n        // at most 1 second (i.e. max_wait_time_ms).\n        int wait_ms =\n                anjay_sched_calculate_wait_time_ms(anjay, max_wait_time_ms);\n\n        // Wait for the events if necessary, and handle them.\n        if (poll(pollfds, numsocks, wait_ms) > 0) {\n            int socket_id = 0;\n            AVS_LIST(avs_net_socket_t *const) socket = NULL;\n            AVS_LIST_FOREACH(socket, sockets) {\n                if (pollfds[socket_id].revents) {\n                    if (anjay_serve(anjay, *socket)) {\n                        avs_log(tutorial, ERROR, \"anjay_serve failed\");\n                    }\n                }\n                ++socket_id;\n            }\n        }\n\n        // Finally run the scheduler\n        anjay_sched_run(anjay);\n    }\n    return 0;\n}\n\n// Installs Security Object and adds an instance of it.\n// An instance of Security Object provides information needed to connect to\n// LwM2M server.\nstatic int setup_security_object(anjay_t *anjay) {\n    if (anjay_security_object_install(anjay)) {\n        return -1;\n    }\n\n    static const char PSK_IDENTITY[] = \"identity\";\n    static const char PSK_KEY[] = \"P4s$w0rd\";\n\n    anjay_security_instance_t security_instance = {\n        .ssid = 1,\n        .server_uri = \"coaps://eu.iot.avsystem.cloud:5684\",\n        .security_mode = ANJAY_SECURITY_PSK,\n        .public_cert_or_psk_identity = (const uint8_t *) PSK_IDENTITY,\n        .public_cert_or_psk_identity_size = strlen(PSK_IDENTITY),\n        .private_cert_or_psk_key = (const uint8_t *) PSK_KEY,\n        .private_cert_or_psk_key_size = strlen(PSK_KEY)\n    };\n\n    // Anjay will assign Instance ID automatically\n    anjay_iid_t security_instance_id = ANJAY_ID_INVALID;\n    if (anjay_security_object_add_instance(anjay, &security_instance,\n                                           &security_instance_id)) {\n        return -1;\n    }\n\n    return 0;\n}\n\n// Installs Server Object and adds an instance of it.\n// An instance of Server Object provides the data related to a LwM2M Server.\nstatic int setup_server_object(anjay_t *anjay) {\n    if (anjay_server_object_install(anjay)) {\n        return -1;\n    }\n\n    const anjay_server_instance_t server_instance = {\n        // Server Short ID\n        .ssid = 1,\n        // Client will send Update message often than every 60 seconds\n        .lifetime = 60,\n        // Disable Default Minimum Period resource\n        .default_min_period = -1,\n        // Disable Default Maximum Period resource\n        .default_max_period = -1,\n        // Disable Disable Timeout resource\n        .disable_timeout = -1,\n        // Sets preferred transport to UDP\n        .binding = \"U\"\n    };\n\n    // Anjay will assign Instance ID automatically\n    anjay_iid_t server_instance_id = ANJAY_ID_INVALID;\n    if (anjay_server_object_add_instance(anjay, &server_instance,\n                                         &server_instance_id)) {\n        return -1;\n    }\n\n    return 0;\n}\n\nint main(int argc, char *argv[]) {\n    if (argc != 2) {\n        avs_log(tutorial, ERROR, \"usage: %s ENDPOINT_NAME\", argv[0]);\n        return -1;\n    }\n\n    const anjay_configuration_t CONFIG = {\n        .endpoint_name = argv[1],\n        .in_buffer_size = 4000,\n        .out_buffer_size = 4000,\n        .msg_cache_size = 4000\n    };\n\n    anjay_t *anjay = anjay_new(&CONFIG);\n    if (!anjay) {\n        avs_log(tutorial, ERROR, \"Could not create Anjay object\");\n        return -1;\n    }\n\n    int result = 0;\n    // Setup necessary objects\n    if (setup_security_object(anjay) || setup_server_object(anjay)) {\n        result = -1;\n    }\n\n    if (!result) {\n        result = main_loop(anjay);\n    }\n\n    anjay_delete(anjay);\n    return result;\n}\n"
  },
  {
    "path": "examples/custom-network/stats/src/net_impl.c",
    "content": "#include <inttypes.h>\n\n#include <netdb.h>\n#include <poll.h>\n#include <unistd.h>\n\n#include <arpa/inet.h>\n\n#include <sys/socket.h>\n#include <sys/types.h>\n\n#include <avsystem/commons/avs_socket_v_table.h>\n#include <avsystem/commons/avs_utils.h>\n\n#ifdef AVS_COMMONS_NET_WITH_POSIX_AVS_SOCKET\n#    error \"Custom implementation of the network layer conflicts with AVS_COMMONS_NET_WITH_POSIX_AVS_SOCKET\"\n#endif // AVS_COMMONS_NET_WITH_POSIX_AVS_SOCKET\n\navs_error_t _avs_net_initialize_global_compat_state(void);\nvoid _avs_net_cleanup_global_compat_state(void);\navs_error_t _avs_net_create_tcp_socket(avs_net_socket_t **socket,\n                                       const void *socket_configuration);\navs_error_t _avs_net_create_udp_socket(avs_net_socket_t **socket,\n                                       const void *socket_configuration);\n\navs_error_t _avs_net_initialize_global_compat_state(void) {\n    return AVS_OK;\n}\n\nvoid _avs_net_cleanup_global_compat_state(void) {}\n\ntypedef struct {\n    const avs_net_socket_v_table_t *operations;\n    int socktype;\n    int fd;\n    avs_time_duration_t recv_timeout;\n    char remote_hostname[256];\n    bool shut_down;\n    size_t bytes_sent;\n    size_t bytes_received;\n} net_socket_impl_t;\n\ntypedef union {\n    struct sockaddr addr;\n    struct sockaddr_in in;\n    struct sockaddr_in6 in6;\n    struct sockaddr_storage storage;\n} sockaddr_union_t;\n\nstatic avs_error_t\nnet_connect(avs_net_socket_t *sock_, const char *host, const char *port) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    struct addrinfo hints = {\n        .ai_socktype = sock->socktype\n    };\n    if (sock->fd >= 0) {\n        getsockopt(sock->fd, SOL_SOCKET, SO_DOMAIN, &hints.ai_family,\n                   &(socklen_t) { sizeof(hints.ai_family) });\n    }\n    struct addrinfo *addr = NULL;\n    avs_error_t err = AVS_OK;\n    if (getaddrinfo(host, port, &hints, &addr) || !addr) {\n        err = avs_errno(AVS_EADDRNOTAVAIL);\n    } else if (sock->fd < 0\n               && (sock->fd = socket(addr->ai_family, addr->ai_socktype,\n                                     addr->ai_protocol))\n                          < 0) {\n        err = avs_errno(AVS_UNKNOWN_ERROR);\n    } else if (connect(sock->fd, addr->ai_addr, addr->ai_addrlen)) {\n        err = avs_errno(AVS_ECONNREFUSED);\n    }\n    if (avs_is_ok(err)) {\n        sock->shut_down = false;\n        snprintf(sock->remote_hostname, sizeof(sock->remote_hostname), \"%s\",\n                 host);\n    }\n    freeaddrinfo(addr);\n    return err;\n}\n\nstatic avs_error_t\nnet_send(avs_net_socket_t *sock_, const void *buffer, size_t buffer_length) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    ssize_t written = send(sock->fd, buffer, buffer_length, MSG_NOSIGNAL);\n    if (written >= 0) {\n        sock->bytes_sent += (size_t) written;\n        if ((size_t) written == buffer_length) {\n            return AVS_OK;\n        }\n    }\n    return avs_errno(AVS_EIO);\n}\n\nstatic avs_error_t net_receive(avs_net_socket_t *sock_,\n                               size_t *out_bytes_received,\n                               void *buffer,\n                               size_t buffer_length) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    struct pollfd pfd = {\n        .fd = sock->fd,\n        .events = POLLIN\n    };\n    int64_t timeout_ms;\n    if (avs_time_duration_to_scalar(&timeout_ms, AVS_TIME_MS,\n                                    sock->recv_timeout)) {\n        timeout_ms = -1;\n    } else if (timeout_ms < 0) {\n        timeout_ms = 0;\n    }\n    if (poll(&pfd, 1, (int) timeout_ms) == 0) {\n        return avs_errno(AVS_ETIMEDOUT);\n    }\n    ssize_t bytes_received = read(sock->fd, buffer, buffer_length);\n    if (bytes_received < 0) {\n        return avs_errno(AVS_EIO);\n    }\n    *out_bytes_received = (size_t) bytes_received;\n    sock->bytes_received += (size_t) bytes_received;\n    if (buffer_length > 0 && sock->socktype == SOCK_DGRAM\n            && (size_t) bytes_received == buffer_length) {\n        return avs_errno(AVS_EMSGSIZE);\n    }\n    return AVS_OK;\n}\n\nstatic avs_error_t\nnet_bind(avs_net_socket_t *sock_, const char *address, const char *port) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    struct addrinfo hints = {\n        .ai_flags = AI_PASSIVE,\n        .ai_socktype = sock->socktype\n    };\n    if (sock->fd >= 0) {\n        getsockopt(sock->fd, SOL_SOCKET, SO_DOMAIN, &hints.ai_family,\n                   &(socklen_t) { sizeof(hints.ai_family) });\n    }\n    struct addrinfo *addr = NULL;\n    avs_error_t err = AVS_OK;\n    if (getaddrinfo(address, port, &hints, &addr) || !addr) {\n        err = avs_errno(AVS_EADDRNOTAVAIL);\n    } else if ((sock->fd < 0\n                && (sock->fd = socket(addr->ai_family, addr->ai_socktype,\n                                      addr->ai_protocol))\n                           < 0)\n               || setsockopt(sock->fd, SOL_SOCKET, SO_REUSEADDR, &(int) { 1 },\n                             sizeof(int))) {\n        err = avs_errno(AVS_UNKNOWN_ERROR);\n    } else if (bind(sock->fd, addr->ai_addr, addr->ai_addrlen)) {\n        err = avs_errno(AVS_ECONNREFUSED);\n    } else {\n        sock->shut_down = false;\n    }\n    if (avs_is_err(err) && sock->fd >= 0) {\n        close(sock->fd);\n        sock->fd = -1;\n    }\n    freeaddrinfo(addr);\n    return err;\n}\n\nstatic avs_error_t net_close(avs_net_socket_t *sock_) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    avs_error_t err = AVS_OK;\n    if (sock->fd >= 0) {\n        if (close(sock->fd)) {\n            err = avs_errno(AVS_EIO);\n        }\n        sock->fd = -1;\n        sock->shut_down = false;\n    }\n    return err;\n}\n\nstatic avs_error_t net_shutdown(avs_net_socket_t *sock_) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    avs_error_t err = avs_errno(AVS_EBADF);\n    if (sock->fd >= 0) {\n        err = shutdown(sock->fd, SHUT_RDWR) ? avs_errno(AVS_EIO) : AVS_OK;\n        sock->shut_down = true;\n    }\n    return err;\n}\n\nstatic avs_error_t net_cleanup(avs_net_socket_t **sock_ptr) {\n    avs_error_t err = AVS_OK;\n    if (sock_ptr && *sock_ptr) {\n        err = net_close(*sock_ptr);\n        avs_free(*sock_ptr);\n        *sock_ptr = NULL;\n    }\n    return err;\n}\n\nstatic const void *net_system_socket(avs_net_socket_t *sock_) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    return &sock->fd;\n}\n\nstatic avs_error_t stringify_sockaddr_host(const sockaddr_union_t *addr,\n                                           char *out_buffer,\n                                           size_t out_buffer_size) {\n    if ((addr->in.sin_family == AF_INET\n         && inet_ntop(AF_INET, &addr->in.sin_addr, out_buffer,\n                      (socklen_t) out_buffer_size))\n            || (addr->in6.sin6_family == AF_INET6\n                && inet_ntop(AF_INET6, &addr->in6.sin6_addr, out_buffer,\n                             (socklen_t) out_buffer_size))) {\n        return AVS_OK;\n    }\n    return avs_errno(AVS_UNKNOWN_ERROR);\n}\n\nstatic avs_error_t stringify_sockaddr_port(const sockaddr_union_t *addr,\n                                           char *out_buffer,\n                                           size_t out_buffer_size) {\n    if ((addr->in.sin_family == AF_INET\n         && avs_simple_snprintf(out_buffer, out_buffer_size, \"%\" PRIu16,\n                                ntohs(addr->in.sin_port))\n                    >= 0)\n            || (addr->in6.sin6_family == AF_INET6\n                && avs_simple_snprintf(out_buffer, out_buffer_size, \"%\" PRIu16,\n                                       ntohs(addr->in6.sin6_port))\n                           >= 0)) {\n        return AVS_OK;\n    }\n    return avs_errno(AVS_UNKNOWN_ERROR);\n}\n\nstatic avs_error_t net_remote_host(avs_net_socket_t *sock_,\n                                   char *out_buffer,\n                                   size_t out_buffer_size) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    sockaddr_union_t addr;\n    if (getpeername(sock->fd, &addr.addr, &(socklen_t) { sizeof(addr) })) {\n        return avs_errno(AVS_UNKNOWN_ERROR);\n    }\n    return stringify_sockaddr_host(&addr, out_buffer, out_buffer_size);\n}\n\nstatic avs_error_t net_remote_hostname(avs_net_socket_t *sock_,\n                                       char *out_buffer,\n                                       size_t out_buffer_size) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    return avs_simple_snprintf(out_buffer, out_buffer_size, \"%s\",\n                               sock->remote_hostname)\n                           < 0\n                   ? avs_errno(AVS_UNKNOWN_ERROR)\n                   : AVS_OK;\n}\n\nstatic avs_error_t net_remote_port(avs_net_socket_t *sock_,\n                                   char *out_buffer,\n                                   size_t out_buffer_size) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    sockaddr_union_t addr;\n    if (getpeername(sock->fd, &addr.addr, &(socklen_t) { sizeof(addr) })) {\n        return avs_errno(AVS_UNKNOWN_ERROR);\n    }\n    return stringify_sockaddr_port(&addr, out_buffer, out_buffer_size);\n}\n\nstatic avs_error_t net_local_port(avs_net_socket_t *sock_,\n                                  char *out_buffer,\n                                  size_t out_buffer_size) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    sockaddr_union_t addr;\n    if (getsockname(sock->fd, &addr.addr, &(socklen_t) { sizeof(addr) })) {\n        return avs_errno(AVS_UNKNOWN_ERROR);\n    }\n    return stringify_sockaddr_port(&addr, out_buffer, out_buffer_size);\n}\n\nstatic avs_error_t net_get_opt(avs_net_socket_t *sock_,\n                               avs_net_socket_opt_key_t option_key,\n                               avs_net_socket_opt_value_t *out_option_value) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    switch (option_key) {\n    case AVS_NET_SOCKET_OPT_RECV_TIMEOUT:\n        out_option_value->recv_timeout = sock->recv_timeout;\n        return AVS_OK;\n    case AVS_NET_SOCKET_OPT_STATE:\n        if (sock->fd < 0) {\n            out_option_value->state = AVS_NET_SOCKET_STATE_CLOSED;\n        } else if (sock->shut_down) {\n            out_option_value->state = AVS_NET_SOCKET_STATE_SHUTDOWN;\n        } else {\n            sockaddr_union_t addr;\n            if (!getpeername(sock->fd, &addr.addr,\n                             &(socklen_t) { sizeof(addr) })\n                    && ((addr.in.sin_family == AF_INET && addr.in.sin_port != 0)\n                        || (addr.in6.sin6_family == AF_INET6\n                            && addr.in6.sin6_port != 0))) {\n                out_option_value->state = AVS_NET_SOCKET_STATE_CONNECTED;\n            } else {\n                out_option_value->state = AVS_NET_SOCKET_STATE_BOUND;\n            }\n        }\n        return AVS_OK;\n    case AVS_NET_SOCKET_OPT_INNER_MTU:\n        out_option_value->mtu = 1464;\n        return AVS_OK;\n    case AVS_NET_SOCKET_HAS_BUFFERED_DATA:\n        out_option_value->flag = false;\n        return AVS_OK;\n    case AVS_NET_SOCKET_OPT_BYTES_SENT:\n        out_option_value->bytes_sent = sock->bytes_sent;\n        return AVS_OK;\n    case AVS_NET_SOCKET_OPT_BYTES_RECEIVED:\n        out_option_value->bytes_received = sock->bytes_received;\n        return AVS_OK;\n    default:\n        return avs_errno(AVS_ENOTSUP);\n    }\n}\n\nstatic avs_error_t net_set_opt(avs_net_socket_t *sock_,\n                               avs_net_socket_opt_key_t option_key,\n                               avs_net_socket_opt_value_t option_value) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    switch (option_key) {\n    case AVS_NET_SOCKET_OPT_RECV_TIMEOUT:\n        sock->recv_timeout = option_value.recv_timeout;\n        return AVS_OK;\n    default:\n        return avs_errno(AVS_ENOTSUP);\n    }\n}\n\nstatic const avs_net_socket_v_table_t NET_SOCKET_VTABLE = {\n    .connect = net_connect,\n    .send = net_send,\n    .receive = net_receive,\n    .bind = net_bind,\n    .close = net_close,\n    .shutdown = net_shutdown,\n    .cleanup = net_cleanup,\n    .get_system_socket = net_system_socket,\n    .get_remote_host = net_remote_host,\n    .get_remote_hostname = net_remote_hostname,\n    .get_remote_port = net_remote_port,\n    .get_local_port = net_local_port,\n    .get_opt = net_get_opt,\n    .set_opt = net_set_opt\n};\n\nstatic avs_error_t\nnet_create_socket(avs_net_socket_t **socket_ptr,\n                  const avs_net_socket_configuration_t *configuration,\n                  int socktype) {\n    assert(socket_ptr);\n    assert(!*socket_ptr);\n    (void) configuration;\n    net_socket_impl_t *socket =\n            (net_socket_impl_t *) avs_calloc(1, sizeof(net_socket_impl_t));\n    if (!socket) {\n        return avs_errno(AVS_ENOMEM);\n    }\n    socket->operations = &NET_SOCKET_VTABLE;\n    socket->socktype = socktype;\n    socket->fd = -1;\n    socket->recv_timeout = avs_time_duration_from_scalar(30, AVS_TIME_S);\n    *socket_ptr = (avs_net_socket_t *) socket;\n    return AVS_OK;\n}\n\navs_error_t _avs_net_create_udp_socket(avs_net_socket_t **socket_ptr,\n                                       const void *configuration) {\n    return net_create_socket(\n            socket_ptr, (const avs_net_socket_configuration_t *) configuration,\n            SOCK_DGRAM);\n}\n\navs_error_t _avs_net_create_tcp_socket(avs_net_socket_t **socket_ptr,\n                                       const void *configuration) {\n    return net_create_socket(\n            socket_ptr, (const avs_net_socket_configuration_t *) configuration,\n            SOCK_STREAM);\n}\n"
  },
  {
    "path": "examples/custom-tls/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.16)\nadd_subdirectory(stub)\nadd_subdirectory(minimal)\nadd_subdirectory(resumption-simple)\nadd_subdirectory(resumption-buffer)\nadd_subdirectory(config-features)\nadd_subdirectory(certificates-basic)\nadd_subdirectory(certificates-advanced)\nadd_subdirectory(certificates-advanced-fake-dane)\nadd_subdirectory(tcp-support)\n"
  },
  {
    "path": "examples/custom-tls/certificates-advanced/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.16)\nproject(custom-tls-certificates-advanced C)\n\nset(CMAKE_C_STANDARD 99)\n\nfind_package(anjay REQUIRED)\nfind_package(OpenSSL REQUIRED)\n\nadd_executable(${PROJECT_NAME}\n               src/main.c\n               src/net_impl.c\n               src/tls_impl.c)\ntarget_link_libraries(${PROJECT_NAME} PRIVATE anjay OpenSSL::SSL)\n"
  },
  {
    "path": "examples/custom-tls/certificates-advanced/src/main.c",
    "content": "#include <anjay/anjay.h>\n#include <anjay/security.h>\n#include <anjay/server.h>\n#include <avsystem/commons/avs_log.h>\n\n#include <string.h>\n\nstatic int\nload_buffer_from_file(uint8_t **out, size_t *out_size, const char *filename) {\n    FILE *f = fopen(filename, \"rb\");\n    if (!f) {\n        avs_log(tutorial, ERROR, \"could not open %s\", filename);\n        return -1;\n    }\n    int result = -1;\n    if (fseek(f, 0, SEEK_END)) {\n        goto finish;\n    }\n    long size = ftell(f);\n    if (size < 0 || (unsigned long) size > SIZE_MAX || fseek(f, 0, SEEK_SET)) {\n        goto finish;\n    }\n    *out_size = (size_t) size;\n    if (!(*out = (uint8_t *) avs_malloc(*out_size))) {\n        goto finish;\n    }\n    if (fread(*out, *out_size, 1, f) != 1) {\n        avs_free(*out);\n        *out = NULL;\n        goto finish;\n    }\n    result = 0;\nfinish:\n    fclose(f);\n    if (result) {\n        avs_log(tutorial, ERROR, \"could not read %s\", filename);\n    }\n    return result;\n}\n\n// Installs Security Object and adds an instance of it.\n// An instance of Security Object provides information needed to connect to\n// LwM2M server.\nstatic int setup_security_object(anjay_t *anjay) {\n    if (anjay_security_object_install(anjay)) {\n        return -1;\n    }\n\n    anjay_security_instance_t security_instance = {\n        .ssid = 1,\n        .server_uri = \"coaps://eu.iot.avsystem.cloud:5684\",\n        .security_mode = ANJAY_SECURITY_CERTIFICATE\n    };\n\n    int result = 0;\n\n    if (load_buffer_from_file(\n                (uint8_t **) &security_instance.public_cert_or_psk_identity,\n                &security_instance.public_cert_or_psk_identity_size,\n                \"client_cert.der\")\n            || load_buffer_from_file(\n                       (uint8_t **) &security_instance.private_cert_or_psk_key,\n                       &security_instance.private_cert_or_psk_key_size,\n                       \"client_key.der\")\n            || load_buffer_from_file(\n                       (uint8_t **) &security_instance.server_public_key,\n                       &security_instance.server_public_key_size,\n                       \"server_cert.der\")) {\n        result = -1;\n        goto cleanup;\n    }\n\n    // Anjay will assign Instance ID automatically\n    anjay_iid_t security_instance_id = ANJAY_ID_INVALID;\n    if (anjay_security_object_add_instance(anjay, &security_instance,\n                                           &security_instance_id)) {\n        result = -1;\n    }\n\ncleanup:\n    avs_free((uint8_t *) security_instance.public_cert_or_psk_identity);\n    avs_free((uint8_t *) security_instance.private_cert_or_psk_key);\n    avs_free((uint8_t *) security_instance.server_public_key);\n\n    return result;\n}\n\n// Installs Server Object and adds an instance of it.\n// An instance of Server Object provides the data related to a LwM2M Server.\nstatic int setup_server_object(anjay_t *anjay) {\n    if (anjay_server_object_install(anjay)) {\n        return -1;\n    }\n\n    const anjay_server_instance_t server_instance = {\n        // Server Short ID\n        .ssid = 1,\n        // Client will send Update message often than every 60 seconds\n        .lifetime = 60,\n        // Disable Default Minimum Period resource\n        .default_min_period = -1,\n        // Disable Default Maximum Period resource\n        .default_max_period = -1,\n        // Disable Disable Timeout resource\n        .disable_timeout = -1,\n        // Sets preferred transport to UDP\n        .binding = \"U\"\n    };\n\n    // Anjay will assign Instance ID automatically\n    anjay_iid_t server_instance_id = ANJAY_ID_INVALID;\n    if (anjay_server_object_add_instance(anjay, &server_instance,\n                                         &server_instance_id)) {\n        return -1;\n    }\n\n    return 0;\n}\n\nint main(int argc, char *argv[]) {\n    if (argc != 2) {\n        avs_log(tutorial, ERROR, \"usage: %s ENDPOINT_NAME\", argv[0]);\n        return -1;\n    }\n\n    const anjay_configuration_t CONFIG = {\n        .endpoint_name = argv[1],\n        .in_buffer_size = 4000,\n        .out_buffer_size = 4000,\n        .msg_cache_size = 4000\n    };\n\n    anjay_t *anjay = anjay_new(&CONFIG);\n    if (!anjay) {\n        avs_log(tutorial, ERROR, \"Could not create Anjay object\");\n        return -1;\n    }\n\n    int result = 0;\n    // Setup necessary objects\n    if (setup_security_object(anjay) || setup_server_object(anjay)) {\n        result = -1;\n    }\n\n    if (!result) {\n        result = anjay_event_loop_run(\n                anjay, avs_time_duration_from_scalar(1, AVS_TIME_S));\n    }\n\n    anjay_delete(anjay);\n    return result;\n}\n"
  },
  {
    "path": "examples/custom-tls/certificates-advanced/src/net_impl.c",
    "content": "#include <inttypes.h>\n\n#include <netdb.h>\n#include <poll.h>\n#include <unistd.h>\n\n#include <arpa/inet.h>\n\n#include <sys/socket.h>\n#include <sys/types.h>\n\n#include <avsystem/commons/avs_socket_v_table.h>\n#include <avsystem/commons/avs_utils.h>\n\n#ifdef AVS_COMMONS_NET_WITH_POSIX_AVS_SOCKET\n#    error \"Custom implementation of the network layer conflicts with AVS_COMMONS_NET_WITH_POSIX_AVS_SOCKET\"\n#endif // AVS_COMMONS_NET_WITH_POSIX_AVS_SOCKET\n\navs_error_t _avs_net_initialize_global_compat_state(void);\nvoid _avs_net_cleanup_global_compat_state(void);\navs_error_t _avs_net_create_tcp_socket(avs_net_socket_t **socket,\n                                       const void *socket_configuration);\navs_error_t _avs_net_create_udp_socket(avs_net_socket_t **socket,\n                                       const void *socket_configuration);\n\navs_error_t _avs_net_initialize_global_compat_state(void) {\n    return AVS_OK;\n}\n\nvoid _avs_net_cleanup_global_compat_state(void) {}\n\ntypedef struct {\n    const avs_net_socket_v_table_t *operations;\n    int socktype;\n    int fd;\n    avs_time_duration_t recv_timeout;\n    char remote_hostname[256];\n    bool shut_down;\n    size_t bytes_sent;\n    size_t bytes_received;\n    avs_net_resolved_endpoint_t *preferred_endpoint;\n} net_socket_impl_t;\n\ntypedef union {\n    struct sockaddr addr;\n    struct sockaddr_in in;\n    struct sockaddr_in6 in6;\n    struct sockaddr_storage storage;\n} sockaddr_union_t;\n\nstatic avs_error_t\nnet_connect(avs_net_socket_t *sock_, const char *host, const char *port) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    struct addrinfo hints = {\n        .ai_socktype = sock->socktype\n    };\n    if (sock->fd >= 0) {\n        getsockopt(sock->fd, SOL_SOCKET, SO_DOMAIN, &hints.ai_family,\n                   &(socklen_t) { sizeof(hints.ai_family) });\n    }\n    struct addrinfo *addrs = NULL;\n    avs_error_t err = AVS_OK;\n    if (getaddrinfo(host, port, &hints, &addrs) || !addrs) {\n        err = avs_errno(AVS_EADDRNOTAVAIL);\n    } else if (sock->fd < 0\n               && (sock->fd = socket(addrs->ai_family, addrs->ai_socktype,\n                                     addrs->ai_protocol))\n                          < 0) {\n        err = avs_errno(AVS_UNKNOWN_ERROR);\n    } else {\n        const struct addrinfo *addr = addrs;\n        if (sock->preferred_endpoint\n                && sock->preferred_endpoint->size == sizeof(sockaddr_union_t)) {\n            while (addr) {\n                if (addr->ai_addrlen <= sizeof(sockaddr_union_t)\n                        && memcmp(addr->ai_addr,\n                                  sock->preferred_endpoint->data.buf,\n                                  addr->ai_addrlen)\n                                       == 0) {\n                    break;\n                }\n                addr = addr->ai_next;\n            }\n        }\n        if (!addr) {\n            // Preferred endpoint not found, use the first one\n            addr = addrs;\n        }\n        if (connect(sock->fd, addr->ai_addr, addr->ai_addrlen)) {\n            err = avs_errno(AVS_ECONNREFUSED);\n        }\n        if (sock->preferred_endpoint && avs_is_ok(err)) {\n            assert(addr->ai_addrlen <= sizeof(sockaddr_union_t));\n            memcpy(sock->preferred_endpoint->data.buf, addr->ai_addr,\n                   addr->ai_addrlen);\n            sock->preferred_endpoint->size = sizeof(sockaddr_union_t);\n        }\n    }\n    if (avs_is_ok(err)) {\n        sock->shut_down = false;\n        snprintf(sock->remote_hostname, sizeof(sock->remote_hostname), \"%s\",\n                 host);\n    }\n    freeaddrinfo(addrs);\n    return err;\n}\n\nstatic avs_error_t\nnet_send(avs_net_socket_t *sock_, const void *buffer, size_t buffer_length) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    ssize_t written = send(sock->fd, buffer, buffer_length, MSG_NOSIGNAL);\n    if (written >= 0) {\n        sock->bytes_sent += (size_t) written;\n        if ((size_t) written == buffer_length) {\n            return AVS_OK;\n        }\n    }\n    return avs_errno(AVS_EIO);\n}\n\nstatic avs_error_t net_receive(avs_net_socket_t *sock_,\n                               size_t *out_bytes_received,\n                               void *buffer,\n                               size_t buffer_length) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    struct pollfd pfd = {\n        .fd = sock->fd,\n        .events = POLLIN\n    };\n    int64_t timeout_ms;\n    if (avs_time_duration_to_scalar(&timeout_ms, AVS_TIME_MS,\n                                    sock->recv_timeout)) {\n        timeout_ms = -1;\n    } else if (timeout_ms < 0) {\n        timeout_ms = 0;\n    }\n    if (poll(&pfd, 1, (int) timeout_ms) == 0) {\n        return avs_errno(AVS_ETIMEDOUT);\n    }\n    ssize_t bytes_received = read(sock->fd, buffer, buffer_length);\n    if (bytes_received < 0) {\n        return avs_errno(AVS_EIO);\n    }\n    *out_bytes_received = (size_t) bytes_received;\n    sock->bytes_received += (size_t) bytes_received;\n    if (buffer_length > 0 && sock->socktype == SOCK_DGRAM\n            && (size_t) bytes_received == buffer_length) {\n        return avs_errno(AVS_EMSGSIZE);\n    }\n    return AVS_OK;\n}\n\nstatic avs_error_t\nnet_bind(avs_net_socket_t *sock_, const char *address, const char *port) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    struct addrinfo hints = {\n        .ai_flags = AI_PASSIVE,\n        .ai_socktype = sock->socktype\n    };\n    if (sock->fd >= 0) {\n        getsockopt(sock->fd, SOL_SOCKET, SO_DOMAIN, &hints.ai_family,\n                   &(socklen_t) { sizeof(hints.ai_family) });\n    }\n    struct addrinfo *addr = NULL;\n    avs_error_t err = AVS_OK;\n    if (getaddrinfo(address, port, &hints, &addr) || !addr) {\n        err = avs_errno(AVS_EADDRNOTAVAIL);\n    } else if ((sock->fd < 0\n                && (sock->fd = socket(addr->ai_family, addr->ai_socktype,\n                                      addr->ai_protocol))\n                           < 0)\n               || setsockopt(sock->fd, SOL_SOCKET, SO_REUSEADDR, &(int) { 1 },\n                             sizeof(int))) {\n        err = avs_errno(AVS_UNKNOWN_ERROR);\n    } else if (bind(sock->fd, addr->ai_addr, addr->ai_addrlen)) {\n        err = avs_errno(AVS_ECONNREFUSED);\n    } else {\n        sock->shut_down = false;\n    }\n    if (avs_is_err(err) && sock->fd >= 0) {\n        close(sock->fd);\n        sock->fd = -1;\n    }\n    freeaddrinfo(addr);\n    return err;\n}\n\nstatic avs_error_t net_close(avs_net_socket_t *sock_) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    avs_error_t err = AVS_OK;\n    if (sock->fd >= 0) {\n        if (close(sock->fd)) {\n            err = avs_errno(AVS_EIO);\n        }\n        sock->fd = -1;\n        sock->shut_down = false;\n    }\n    return err;\n}\n\nstatic avs_error_t net_shutdown(avs_net_socket_t *sock_) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    avs_error_t err = avs_errno(AVS_EBADF);\n    if (sock->fd >= 0) {\n        err = shutdown(sock->fd, SHUT_RDWR) ? avs_errno(AVS_EIO) : AVS_OK;\n        sock->shut_down = true;\n    }\n    return err;\n}\n\nstatic avs_error_t net_cleanup(avs_net_socket_t **sock_ptr) {\n    avs_error_t err = AVS_OK;\n    if (sock_ptr && *sock_ptr) {\n        err = net_close(*sock_ptr);\n        avs_free(*sock_ptr);\n        *sock_ptr = NULL;\n    }\n    return err;\n}\n\nstatic const void *net_system_socket(avs_net_socket_t *sock_) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    return &sock->fd;\n}\n\nstatic avs_error_t stringify_sockaddr_host(const sockaddr_union_t *addr,\n                                           char *out_buffer,\n                                           size_t out_buffer_size) {\n    if ((addr->in.sin_family == AF_INET\n         && inet_ntop(AF_INET, &addr->in.sin_addr, out_buffer,\n                      (socklen_t) out_buffer_size))\n            || (addr->in6.sin6_family == AF_INET6\n                && inet_ntop(AF_INET6, &addr->in6.sin6_addr, out_buffer,\n                             (socklen_t) out_buffer_size))) {\n        return AVS_OK;\n    }\n    return avs_errno(AVS_UNKNOWN_ERROR);\n}\n\nstatic avs_error_t stringify_sockaddr_port(const sockaddr_union_t *addr,\n                                           char *out_buffer,\n                                           size_t out_buffer_size) {\n    if ((addr->in.sin_family == AF_INET\n         && avs_simple_snprintf(out_buffer, out_buffer_size, \"%\" PRIu16,\n                                ntohs(addr->in.sin_port))\n                    >= 0)\n            || (addr->in6.sin6_family == AF_INET6\n                && avs_simple_snprintf(out_buffer, out_buffer_size, \"%\" PRIu16,\n                                       ntohs(addr->in6.sin6_port))\n                           >= 0)) {\n        return AVS_OK;\n    }\n    return avs_errno(AVS_UNKNOWN_ERROR);\n}\n\nstatic avs_error_t net_remote_host(avs_net_socket_t *sock_,\n                                   char *out_buffer,\n                                   size_t out_buffer_size) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    sockaddr_union_t addr;\n    if (getpeername(sock->fd, &addr.addr, &(socklen_t) { sizeof(addr) })) {\n        return avs_errno(AVS_UNKNOWN_ERROR);\n    }\n    return stringify_sockaddr_host(&addr, out_buffer, out_buffer_size);\n}\n\nstatic avs_error_t net_remote_hostname(avs_net_socket_t *sock_,\n                                       char *out_buffer,\n                                       size_t out_buffer_size) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    return avs_simple_snprintf(out_buffer, out_buffer_size, \"%s\",\n                               sock->remote_hostname)\n                           < 0\n                   ? avs_errno(AVS_UNKNOWN_ERROR)\n                   : AVS_OK;\n}\n\nstatic avs_error_t net_remote_port(avs_net_socket_t *sock_,\n                                   char *out_buffer,\n                                   size_t out_buffer_size) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    sockaddr_union_t addr;\n    if (getpeername(sock->fd, &addr.addr, &(socklen_t) { sizeof(addr) })) {\n        return avs_errno(AVS_UNKNOWN_ERROR);\n    }\n    return stringify_sockaddr_port(&addr, out_buffer, out_buffer_size);\n}\n\nstatic avs_error_t net_local_port(avs_net_socket_t *sock_,\n                                  char *out_buffer,\n                                  size_t out_buffer_size) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    sockaddr_union_t addr;\n    if (getsockname(sock->fd, &addr.addr, &(socklen_t) { sizeof(addr) })) {\n        return avs_errno(AVS_UNKNOWN_ERROR);\n    }\n    return stringify_sockaddr_port(&addr, out_buffer, out_buffer_size);\n}\n\nstatic avs_error_t net_get_opt(avs_net_socket_t *sock_,\n                               avs_net_socket_opt_key_t option_key,\n                               avs_net_socket_opt_value_t *out_option_value) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    switch (option_key) {\n    case AVS_NET_SOCKET_OPT_RECV_TIMEOUT:\n        out_option_value->recv_timeout = sock->recv_timeout;\n        return AVS_OK;\n    case AVS_NET_SOCKET_OPT_STATE:\n        if (sock->fd < 0) {\n            out_option_value->state = AVS_NET_SOCKET_STATE_CLOSED;\n        } else if (sock->shut_down) {\n            out_option_value->state = AVS_NET_SOCKET_STATE_SHUTDOWN;\n        } else {\n            sockaddr_union_t addr;\n            if (!getpeername(sock->fd, &addr.addr,\n                             &(socklen_t) { sizeof(addr) })\n                    && ((addr.in.sin_family == AF_INET && addr.in.sin_port != 0)\n                        || (addr.in6.sin6_family == AF_INET6\n                            && addr.in6.sin6_port != 0))) {\n                out_option_value->state = AVS_NET_SOCKET_STATE_CONNECTED;\n            } else {\n                out_option_value->state = AVS_NET_SOCKET_STATE_BOUND;\n            }\n        }\n        return AVS_OK;\n    case AVS_NET_SOCKET_OPT_INNER_MTU:\n        out_option_value->mtu = 1464;\n        return AVS_OK;\n    case AVS_NET_SOCKET_HAS_BUFFERED_DATA:\n        out_option_value->flag = false;\n        return AVS_OK;\n    case AVS_NET_SOCKET_OPT_BYTES_SENT:\n        out_option_value->bytes_sent = sock->bytes_sent;\n        return AVS_OK;\n    case AVS_NET_SOCKET_OPT_BYTES_RECEIVED:\n        out_option_value->bytes_received = sock->bytes_received;\n        return AVS_OK;\n    default:\n        return avs_errno(AVS_ENOTSUP);\n    }\n}\n\nstatic avs_error_t net_set_opt(avs_net_socket_t *sock_,\n                               avs_net_socket_opt_key_t option_key,\n                               avs_net_socket_opt_value_t option_value) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    switch (option_key) {\n    case AVS_NET_SOCKET_OPT_RECV_TIMEOUT:\n        sock->recv_timeout = option_value.recv_timeout;\n        return AVS_OK;\n    default:\n        return avs_errno(AVS_ENOTSUP);\n    }\n}\n\nstatic const avs_net_socket_v_table_t NET_SOCKET_VTABLE = {\n    .connect = net_connect,\n    .send = net_send,\n    .receive = net_receive,\n    .bind = net_bind,\n    .close = net_close,\n    .shutdown = net_shutdown,\n    .cleanup = net_cleanup,\n    .get_system_socket = net_system_socket,\n    .get_remote_host = net_remote_host,\n    .get_remote_hostname = net_remote_hostname,\n    .get_remote_port = net_remote_port,\n    .get_local_port = net_local_port,\n    .get_opt = net_get_opt,\n    .set_opt = net_set_opt\n};\n\nstatic avs_error_t\nnet_create_socket(avs_net_socket_t **socket_ptr,\n                  const avs_net_socket_configuration_t *configuration,\n                  int socktype) {\n    assert(socket_ptr);\n    assert(!*socket_ptr);\n    (void) configuration;\n    net_socket_impl_t *socket =\n            (net_socket_impl_t *) avs_calloc(1, sizeof(net_socket_impl_t));\n    if (!socket) {\n        return avs_errno(AVS_ENOMEM);\n    }\n    socket->operations = &NET_SOCKET_VTABLE;\n    socket->socktype = socktype;\n    socket->fd = -1;\n    socket->recv_timeout = avs_time_duration_from_scalar(30, AVS_TIME_S);\n    socket->preferred_endpoint = configuration->preferred_endpoint;\n    *socket_ptr = (avs_net_socket_t *) socket;\n    return AVS_OK;\n}\n\navs_error_t _avs_net_create_udp_socket(avs_net_socket_t **socket_ptr,\n                                       const void *configuration) {\n    return net_create_socket(\n            socket_ptr, (const avs_net_socket_configuration_t *) configuration,\n            SOCK_DGRAM);\n}\n\navs_error_t _avs_net_create_tcp_socket(avs_net_socket_t **socket_ptr,\n                                       const void *configuration) {\n    return net_create_socket(\n            socket_ptr, (const avs_net_socket_configuration_t *) configuration,\n            SOCK_STREAM);\n}\n\navs_error_t\navs_net_resolved_endpoint_get_host_port(const avs_net_resolved_endpoint_t *endp,\n                                        char *host,\n                                        size_t hostlen,\n                                        char *serv,\n                                        size_t servlen) {\n    AVS_STATIC_ASSERT(sizeof(endp->data.buf) >= sizeof(sockaddr_union_t),\n                      data_buffer_big_enough);\n    if (endp->size != sizeof(sockaddr_union_t)) {\n        return avs_errno(AVS_EINVAL);\n    }\n    const sockaddr_union_t *addr = (const sockaddr_union_t *) &endp->data.buf;\n    avs_error_t err = AVS_OK;\n    (void) ((host\n             && avs_is_err(\n                        (err = stringify_sockaddr_host(addr, host, hostlen))))\n            || (serv\n                && avs_is_err((err = stringify_sockaddr_port(addr, serv,\n                                                             servlen)))));\n    return err;\n}\n"
  },
  {
    "path": "examples/custom-tls/certificates-advanced/src/tls_impl.c",
    "content": "#include <poll.h>\n\n#include <openssl/err.h>\n#include <openssl/ssl.h>\n\n#include <avsystem/commons/avs_crypto_common.h>\n#include <avsystem/commons/avs_socket_v_table.h>\n#include <avsystem/commons/avs_utils.h>\n\n#ifndef AVS_COMMONS_WITH_CUSTOM_TLS\n#    error \"Custom implementation of the TLS layer requires AVS_COMMONS_WITH_CUSTOM_TLS\"\n#endif // AVS_COMMONS_WITH_CUSTOM_TLS\n\navs_error_t _avs_net_initialize_global_ssl_state(void);\nvoid _avs_net_cleanup_global_ssl_state(void);\navs_error_t _avs_net_create_ssl_socket(avs_net_socket_t **socket,\n                                       const void *socket_configuration);\navs_error_t _avs_net_create_dtls_socket(avs_net_socket_t **socket,\n                                        const void *socket_configuration);\n\navs_error_t _avs_net_initialize_global_ssl_state(void) {\n    if (!OPENSSL_init_ssl(OPENSSL_INIT_ADD_ALL_CIPHERS\n                                  | OPENSSL_INIT_ADD_ALL_DIGESTS,\n                          NULL)) {\n        return avs_errno(AVS_EPROTO);\n    }\n    return AVS_OK;\n}\n\nvoid _avs_net_cleanup_global_ssl_state(void) {}\n\nvoid avs_crypto_clear_buffer(void *buf, size_t size) {\n    OPENSSL_cleanse(buf, size);\n}\n\ntypedef struct {\n    const avs_net_socket_v_table_t *operations;\n    avs_net_socket_t *backend_socket;\n    SSL_CTX *ctx;\n    SSL *ssl;\n\n    char psk[256];\n    size_t psk_size;\n    char identity[128];\n    size_t identity_size;\n\n    bool dane_enabled;\n    char dane_tlsa_association_data_buf[4096];\n    avs_net_socket_dane_tlsa_record_t dane_tlsa_array[4];\n    size_t dane_tlsa_array_size;\n\n    void *session_resumption_buffer;\n    size_t session_resumption_buffer_size;\n\n    char server_name_indication[256];\n    unsigned int dtls_hs_timeout_min_us;\n    unsigned int dtls_hs_timeout_max_us;\n} tls_socket_impl_t;\n\nstatic unsigned int dtls_timer_cb(SSL *s, unsigned int timer_us) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) SSL_get_app_data(s);\n    if (!timer_us) {\n        return sock->dtls_hs_timeout_min_us;\n    } else if (timer_us >= sock->dtls_hs_timeout_max_us) {\n        // maximum number of retransmissions reached, let's give up\n        avs_net_socket_shutdown(sock->backend_socket);\n        return 0;\n    } else {\n        timer_us *= 2;\n        if (timer_us > sock->dtls_hs_timeout_max_us) {\n            timer_us = sock->dtls_hs_timeout_max_us;\n        }\n        return timer_us;\n    }\n}\n\nstatic avs_error_t perform_handshake(tls_socket_impl_t *sock,\n                                     const char *host) {\n    union {\n        struct sockaddr addr;\n        struct sockaddr_storage storage;\n    } peername;\n    const void *fd_ptr = avs_net_socket_get_system(sock->backend_socket);\n    if (!fd_ptr\n            || getpeername(*(const int *) fd_ptr, &peername.addr,\n                           &(socklen_t) { sizeof(peername) })) {\n        return avs_errno(AVS_EBADF);\n    }\n\n    sock->ssl = SSL_new(sock->ctx);\n    if (!sock->ssl) {\n        return avs_errno(AVS_ENOMEM);\n    }\n\n    SSL_set_app_data(sock->ssl, sock);\n    if (sock->dane_enabled) {\n        // NOTE: SSL_dane_enable() calls SSL_set_tlsext_host_name() internally\n        SSL_dane_enable(sock->ssl, host);\n        bool have_usable_tlsa_records = false;\n        for (size_t i = 0; i < sock->dane_tlsa_array_size; ++i) {\n            if (SSL_CTX_get_verify_mode(sock->ctx) == SSL_VERIFY_NONE\n                    && (sock->dane_tlsa_array[i].certificate_usage\n                                == AVS_NET_SOCKET_DANE_CA_CONSTRAINT\n                        || sock->dane_tlsa_array[i].certificate_usage\n                                   == AVS_NET_SOCKET_DANE_SERVICE_CERTIFICATE_CONSTRAINT)) {\n                // PKIX-TA and PKIX-EE constraints are unusable for\n                // opportunistic clients\n                continue;\n            }\n            SSL_dane_tlsa_add(\n                    sock->ssl,\n                    (uint8_t) sock->dane_tlsa_array[i].certificate_usage,\n                    (uint8_t) sock->dane_tlsa_array[i].selector,\n                    (uint8_t) sock->dane_tlsa_array[i].matching_type,\n                    (unsigned const char *) sock->dane_tlsa_array[i]\n                            .association_data,\n                    sock->dane_tlsa_array[i].association_data_size);\n            have_usable_tlsa_records = true;\n        }\n        if (SSL_CTX_get_verify_mode(sock->ctx) == SSL_VERIFY_NONE\n                && have_usable_tlsa_records) {\n            SSL_set_verify(sock->ssl, SSL_VERIFY_PEER, NULL);\n        }\n    } else {\n        SSL_set_tlsext_host_name(sock->ssl, host);\n    }\n    SSL_set1_host(sock->ssl, host);\n\n    BIO *bio = BIO_new_dgram(*(const int *) fd_ptr, 0);\n    if (!bio) {\n        return avs_errno(AVS_ENOMEM);\n    }\n    BIO_ctrl(bio, BIO_CTRL_DGRAM_SET_CONNECTED, 0, &peername.addr);\n    SSL_set_bio(sock->ssl, bio, bio);\n    DTLS_set_timer_cb(sock->ssl, dtls_timer_cb);\n\n    if (sock->session_resumption_buffer) {\n        const unsigned char *ptr =\n                (const unsigned char *) sock->session_resumption_buffer;\n        SSL_SESSION *session =\n                d2i_SSL_SESSION(NULL, &ptr,\n                                sock->session_resumption_buffer_size);\n        if (session) {\n            SSL_set_session(sock->ssl, session);\n            SSL_SESSION_free(session);\n        }\n    }\n\n    if (SSL_connect(sock->ssl) <= 0) {\n        return avs_errno(AVS_EPROTO);\n    }\n    return AVS_OK;\n}\n\nstatic avs_error_t\ntls_connect(avs_net_socket_t *sock_, const char *host, const char *port) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    if (sock->ssl) {\n        return avs_errno(AVS_EBADF);\n    }\n    avs_error_t err;\n    if (avs_is_err((\n                err = avs_net_socket_connect(sock->backend_socket, host, port)))\n            || avs_is_err((err = perform_handshake(\n                                   sock, sock->server_name_indication[0]\n                                                 ? sock->server_name_indication\n                                                 : host)))) {\n        if (sock->ssl) {\n            SSL_free(sock->ssl);\n            sock->ssl = NULL;\n        }\n        avs_net_socket_close(sock->backend_socket);\n    }\n    return err;\n}\n\nstatic avs_error_t\ntls_send(avs_net_socket_t *sock_, const void *buffer, size_t buffer_length) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    int result = SSL_write(sock->ssl, buffer, (int) buffer_length);\n    if (result < 0 || (size_t) result < buffer_length) {\n        return avs_errno(AVS_EPROTO);\n    }\n    return AVS_OK;\n}\n\nstatic avs_error_t tls_receive(avs_net_socket_t *sock_,\n                               size_t *out_bytes_received,\n                               void *buffer,\n                               size_t buffer_length) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    const void *fd_ptr = avs_net_socket_get_system(sock->backend_socket);\n    avs_net_socket_opt_value_t timeout;\n    if (!fd_ptr\n            || avs_is_err(avs_net_socket_get_opt(\n                       sock->backend_socket, AVS_NET_SOCKET_OPT_RECV_TIMEOUT,\n                       &timeout))) {\n        return avs_errno(AVS_EBADF);\n    }\n    struct pollfd pfd = {\n        .fd = *(const int *) fd_ptr,\n        .events = POLLIN\n    };\n    int64_t timeout_ms;\n    if (avs_time_duration_to_scalar(&timeout_ms, AVS_TIME_MS,\n                                    timeout.recv_timeout)) {\n        timeout_ms = -1;\n    } else if (timeout_ms < 0) {\n        timeout_ms = 0;\n    }\n    if (poll(&pfd, 1, (int) timeout_ms) == 0) {\n        return avs_errno(AVS_ETIMEDOUT);\n    }\n    int bytes_received = SSL_read(sock->ssl, buffer, (int) buffer_length);\n    if (bytes_received < 0) {\n        return avs_errno(AVS_EPROTO);\n    }\n    *out_bytes_received = (size_t) bytes_received;\n    if (buffer_length > 0 && (size_t) bytes_received == buffer_length) {\n        return avs_errno(AVS_EMSGSIZE);\n    }\n    return AVS_OK;\n}\n\nstatic avs_error_t\ntls_bind(avs_net_socket_t *sock_, const char *address, const char *port) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    return avs_net_socket_bind(sock->backend_socket, address, port);\n}\n\nstatic avs_error_t tls_close(avs_net_socket_t *sock_) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    if (sock->ssl) {\n        SSL_free(sock->ssl);\n        sock->ssl = NULL;\n    }\n    return avs_net_socket_close(sock->backend_socket);\n}\n\nstatic avs_error_t tls_shutdown(avs_net_socket_t *sock_) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    return avs_net_socket_shutdown(sock->backend_socket);\n}\n\nstatic avs_error_t tls_cleanup(avs_net_socket_t **sock_ptr) {\n    avs_error_t err = AVS_OK;\n    if (sock_ptr && *sock_ptr) {\n        tls_socket_impl_t *sock = (tls_socket_impl_t *) *sock_ptr;\n        tls_close(*sock_ptr);\n        avs_net_socket_cleanup(&sock->backend_socket);\n        if (sock->ctx) {\n            SSL_CTX_free(sock->ctx);\n        }\n        avs_free(sock);\n        *sock_ptr = NULL;\n    }\n    return err;\n}\n\nstatic const void *tls_system_socket(avs_net_socket_t *sock_) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    return avs_net_socket_get_system(sock->backend_socket);\n}\n\nstatic avs_error_t tls_remote_host(avs_net_socket_t *sock_,\n                                   char *out_buffer,\n                                   size_t out_buffer_size) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    return avs_net_socket_get_remote_host(sock->backend_socket, out_buffer,\n                                          out_buffer_size);\n}\n\nstatic avs_error_t tls_remote_hostname(avs_net_socket_t *sock_,\n                                       char *out_buffer,\n                                       size_t out_buffer_size) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    return avs_net_socket_get_remote_hostname(sock->backend_socket, out_buffer,\n                                              out_buffer_size);\n}\n\nstatic avs_error_t tls_remote_port(avs_net_socket_t *sock_,\n                                   char *out_buffer,\n                                   size_t out_buffer_size) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    return avs_net_socket_get_remote_port(sock->backend_socket, out_buffer,\n                                          out_buffer_size);\n}\n\nstatic avs_error_t tls_local_port(avs_net_socket_t *sock_,\n                                  char *out_buffer,\n                                  size_t out_buffer_size) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    return avs_net_socket_get_local_port(sock->backend_socket, out_buffer,\n                                         out_buffer_size);\n}\n\nstatic avs_error_t tls_get_opt(avs_net_socket_t *sock_,\n                               avs_net_socket_opt_key_t option_key,\n                               avs_net_socket_opt_value_t *out_option_value) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    switch (option_key) {\n    case AVS_NET_SOCKET_OPT_INNER_MTU: {\n        avs_error_t err = avs_net_socket_get_opt(sock->backend_socket,\n                                                 AVS_NET_SOCKET_OPT_INNER_MTU,\n                                                 out_option_value);\n        if (avs_is_ok(err)) {\n            out_option_value->mtu = AVS_MAX(out_option_value->mtu - 64, 0);\n        }\n        return err;\n    }\n    case AVS_NET_SOCKET_HAS_BUFFERED_DATA:\n        out_option_value->flag = (sock->ssl && SSL_pending(sock->ssl) > 0);\n        return AVS_OK;\n    case AVS_NET_SOCKET_OPT_SESSION_RESUMED:\n        out_option_value->flag = (sock->ssl && SSL_session_reused(sock->ssl));\n        return AVS_OK;\n    default:\n        return avs_net_socket_get_opt(sock->backend_socket, option_key,\n                                      out_option_value);\n    }\n}\n\nstatic avs_error_t tls_set_opt(avs_net_socket_t *sock_,\n                               avs_net_socket_opt_key_t option_key,\n                               avs_net_socket_opt_value_t option_value) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    switch (option_key) {\n    case AVS_NET_SOCKET_OPT_DANE_TLSA_ARRAY: {\n        if (option_value.dane_tlsa_array.array_element_count\n                > AVS_ARRAY_SIZE(sock->dane_tlsa_array)) {\n            return avs_errno(AVS_EINVAL);\n        }\n        avs_net_socket_dane_tlsa_record_t\n                copied_array[AVS_ARRAY_SIZE(sock->dane_tlsa_array)];\n        char copied_association_data[sizeof(\n                sock->dane_tlsa_association_data_buf)];\n        size_t copied_association_data_offset = 0;\n        memcpy(copied_array, option_value.dane_tlsa_array.array_ptr,\n               option_value.dane_tlsa_array.array_element_count\n                       * sizeof(avs_net_socket_dane_tlsa_record_t));\n        for (size_t i = 0; i < option_value.dane_tlsa_array.array_element_count;\n             ++i) {\n            if (copied_association_data_offset\n                            + option_value.dane_tlsa_array.array_ptr[i]\n                                      .association_data_size\n                    > sizeof(copied_association_data)) {\n                return avs_errno(AVS_EINVAL);\n            }\n            memcpy(copied_association_data + copied_association_data_offset,\n                   option_value.dane_tlsa_array.array_ptr[i].association_data,\n                   option_value.dane_tlsa_array.array_ptr[i]\n                           .association_data_size);\n            copied_array[i].association_data =\n                    sock->dane_tlsa_association_data_buf\n                    + copied_association_data_offset;\n            copied_association_data_offset +=\n                    option_value.dane_tlsa_array.array_ptr[i]\n                            .association_data_size;\n        }\n        memcpy(sock->dane_tlsa_association_data_buf, copied_association_data,\n               sizeof(copied_association_data));\n        memcpy(sock->dane_tlsa_array, copied_array, sizeof(copied_array));\n        sock->dane_tlsa_array_size =\n                option_value.dane_tlsa_array.array_element_count;\n        return AVS_OK;\n    }\n    default:\n        return avs_net_socket_set_opt(sock->backend_socket, option_key,\n                                      option_value);\n    }\n}\n\nstatic const avs_net_socket_v_table_t TLS_SOCKET_VTABLE = {\n    .connect = tls_connect,\n    .send = tls_send,\n    .receive = tls_receive,\n    .bind = tls_bind,\n    .close = tls_close,\n    .shutdown = tls_shutdown,\n    .cleanup = tls_cleanup,\n    .get_system_socket = tls_system_socket,\n    .get_remote_host = tls_remote_host,\n    .get_remote_hostname = tls_remote_hostname,\n    .get_remote_port = tls_remote_port,\n    .get_local_port = tls_local_port,\n    .get_opt = tls_get_opt,\n    .set_opt = tls_set_opt\n};\n\nstatic avs_error_t configure_dtls_version(tls_socket_impl_t *sock,\n                                          avs_net_ssl_version_t version) {\n    switch (version) {\n    case AVS_NET_SSL_VERSION_DEFAULT:\n        return AVS_OK;\n    case AVS_NET_SSL_VERSION_TLSv1:\n    case AVS_NET_SSL_VERSION_TLSv1_1:\n        SSL_CTX_set_min_proto_version(sock->ctx, DTLS1_VERSION);\n        return AVS_OK;\n    case AVS_NET_SSL_VERSION_TLSv1_2:\n        SSL_CTX_set_min_proto_version(sock->ctx, DTLS1_2_VERSION);\n        return AVS_OK;\n    default:\n        return avs_errno(AVS_ENOTSUP);\n    }\n}\n\nstatic unsigned int psk_client_cb(SSL *ssl,\n                                  const char *hint,\n                                  char *identity,\n                                  unsigned int max_identity_len,\n                                  unsigned char *psk,\n                                  unsigned int max_psk_len) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) SSL_get_app_data(ssl);\n\n    (void) hint;\n\n    if (!sock || max_psk_len < sock->psk_size\n            || max_identity_len < sock->identity_size + 1) {\n        return 0;\n    }\n\n    memcpy(psk, sock->psk, sock->psk_size);\n    memcpy(identity, sock->identity, sock->identity_size);\n    identity[sock->identity_size] = '\\0';\n\n    return (unsigned int) sock->psk_size;\n}\n\nstatic avs_error_t configure_psk(tls_socket_impl_t *sock,\n                                 const avs_net_psk_info_t *psk) {\n    if (psk->key.desc.source != AVS_CRYPTO_DATA_SOURCE_BUFFER\n            || psk->identity.desc.source != AVS_CRYPTO_DATA_SOURCE_BUFFER) {\n        return avs_errno(AVS_EINVAL);\n    }\n\n    const void *key_ptr = psk->key.desc.info.buffer.buffer;\n    size_t key_size = psk->key.desc.info.buffer.buffer_size;\n\n    const void *identity_ptr = psk->identity.desc.info.buffer.buffer;\n    size_t identity_size = psk->identity.desc.info.buffer.buffer_size;\n\n    if (key_size > sizeof(sock->psk)\n            || identity_size > sizeof(sock->identity)) {\n        return avs_errno(AVS_EINVAL);\n    }\n    memcpy(sock->psk, key_ptr, key_size);\n    sock->psk_size = key_size;\n    memcpy(sock->identity, identity_ptr, identity_size);\n    sock->identity_size = identity_size;\n    SSL_CTX_set_cipher_list(sock->ctx, \"PSK\");\n    SSL_CTX_set_psk_client_callback(sock->ctx, psk_client_cb);\n    SSL_CTX_set_verify(sock->ctx, SSL_VERIFY_PEER, NULL);\n    return AVS_OK;\n}\n\nstatic avs_error_t\nconfigure_trusted_certs(X509_STORE *store,\n                        const avs_crypto_security_info_union_t *trusted_certs) {\n    if (!trusted_certs) {\n        return avs_errno(AVS_EINVAL);\n    }\n    switch (trusted_certs->source) {\n    case AVS_CRYPTO_DATA_SOURCE_EMPTY:\n        return AVS_OK;\n    case AVS_CRYPTO_DATA_SOURCE_BUFFER: {\n        const unsigned char *ptr =\n                (const unsigned char *) trusted_certs->info.buffer.buffer;\n        X509 *cert = d2i_X509(NULL, &ptr,\n                              (long) trusted_certs->info.buffer.buffer_size);\n        if (!cert) {\n            return avs_errno(AVS_EPROTO);\n        }\n\n        ERR_clear_error();\n        int result = X509_STORE_add_cert(store, cert);\n        X509_free(cert);\n        if (!result\n                && ERR_GET_REASON(ERR_get_error())\n                               != X509_R_CERT_ALREADY_IN_HASH_TABLE) {\n            return avs_errno(AVS_EPROTO);\n        }\n        return AVS_OK;\n    }\n    case AVS_CRYPTO_DATA_SOURCE_ARRAY: {\n        avs_error_t err = AVS_OK;\n        for (size_t i = 0;\n             avs_is_ok(err) && i < trusted_certs->info.array.element_count;\n             ++i) {\n            err = configure_trusted_certs(\n                    store, &trusted_certs->info.array.array_ptr[i]);\n        }\n        return err;\n    }\n    case AVS_CRYPTO_DATA_SOURCE_LIST: {\n        avs_error_t err = AVS_OK;\n        AVS_LIST(avs_crypto_security_info_union_t) entry;\n        AVS_LIST_FOREACH(entry, trusted_certs->info.list.list_head) {\n            if (avs_is_err((err = configure_trusted_certs(store, entry)))) {\n                break;\n            }\n        }\n        return AVS_OK;\n    }\n    default:\n        return avs_errno(AVS_ENOTSUP);\n    }\n}\n\nstatic avs_error_t configure_cert_revocation_lists(\n        X509_STORE *store,\n        const avs_crypto_security_info_union_t *cert_revocation_lists) {\n    if (!cert_revocation_lists) {\n        return avs_errno(AVS_EINVAL);\n    }\n    switch (cert_revocation_lists->source) {\n    case AVS_CRYPTO_DATA_SOURCE_EMPTY:\n        return AVS_OK;\n    case AVS_CRYPTO_DATA_SOURCE_BUFFER: {\n        const unsigned char *ptr =\n                (const unsigned char *)\n                        cert_revocation_lists->info.buffer.buffer;\n        X509_CRL *crl = d2i_X509_CRL(\n                NULL, &ptr,\n                (long) cert_revocation_lists->info.buffer.buffer_size);\n        if (!crl) {\n            return avs_errno(AVS_EPROTO);\n        }\n\n        ERR_clear_error();\n        int result = X509_STORE_add_crl(store, crl);\n        X509_CRL_free(crl);\n        if (result != 1) {\n            return avs_errno(AVS_EPROTO);\n        }\n        return AVS_OK;\n    }\n    case AVS_CRYPTO_DATA_SOURCE_ARRAY: {\n        avs_error_t err = AVS_OK;\n        for (size_t i = 0;\n             avs_is_ok(err)\n             && i < cert_revocation_lists->info.array.element_count;\n             ++i) {\n            err = configure_cert_revocation_lists(\n                    store, &cert_revocation_lists->info.array.array_ptr[i]);\n        }\n        return err;\n    }\n    case AVS_CRYPTO_DATA_SOURCE_LIST: {\n        avs_error_t err = AVS_OK;\n        AVS_LIST(avs_crypto_security_info_union_t) entry;\n        AVS_LIST_FOREACH(entry, cert_revocation_lists->info.list.list_head) {\n            if (avs_is_err((\n                        err = configure_cert_revocation_lists(store, entry)))) {\n                break;\n            }\n        }\n        return AVS_OK;\n    }\n    default:\n        return avs_errno(AVS_ENOTSUP);\n    }\n}\n\nstatic avs_error_t\nconfigure_client_certs(SSL_CTX *ctx,\n                       const avs_crypto_security_info_union_t *client_certs) {\n    if (!client_certs) {\n        return avs_errno(AVS_EINVAL);\n    }\n    switch (client_certs->source) {\n    case AVS_CRYPTO_DATA_SOURCE_EMPTY:\n        return AVS_OK;\n    case AVS_CRYPTO_DATA_SOURCE_BUFFER: {\n        const unsigned char *ptr =\n                (const unsigned char *) client_certs->info.buffer.buffer;\n        X509 *cert = d2i_X509(NULL, &ptr,\n                              (long) client_certs->info.buffer.buffer_size);\n        if (!cert) {\n            return avs_errno(AVS_EPROTO);\n        }\n\n        int result;\n        if (!SSL_CTX_get0_certificate(ctx)) {\n            result = SSL_CTX_use_certificate(ctx, cert);\n        } else {\n            result = SSL_CTX_add1_chain_cert(ctx, cert);\n        }\n        X509_free(cert);\n        if (result != 1) {\n            return avs_errno(AVS_EPROTO);\n        }\n        return AVS_OK;\n    }\n    case AVS_CRYPTO_DATA_SOURCE_ARRAY: {\n        avs_error_t err = AVS_OK;\n        for (size_t i = 0;\n             avs_is_ok(err) && i < client_certs->info.array.element_count;\n             ++i) {\n            err = configure_client_certs(\n                    ctx, &client_certs->info.array.array_ptr[i]);\n        }\n        return err;\n    }\n    case AVS_CRYPTO_DATA_SOURCE_LIST: {\n        avs_error_t err = AVS_OK;\n        AVS_LIST(avs_crypto_security_info_union_t) entry;\n        AVS_LIST_FOREACH(entry, client_certs->info.list.list_head) {\n            if (avs_is_err((err = configure_client_certs(ctx, entry)))) {\n                break;\n            }\n        }\n        return AVS_OK;\n    }\n    default:\n        return avs_errno(AVS_ENOTSUP);\n    }\n}\n\nstatic avs_error_t\nconfigure_client_key(SSL_CTX *ctx,\n                     const avs_crypto_private_key_info_t *client_key) {\n    switch (client_key->desc.source) {\n    case AVS_CRYPTO_DATA_SOURCE_BUFFER: {\n        if (client_key->desc.info.buffer.password) {\n            return avs_errno(AVS_ENOTSUP);\n        }\n        const unsigned char *ptr =\n                (const unsigned char *) client_key->desc.info.buffer.buffer;\n        EVP_PKEY *key = d2i_AutoPrivateKey(\n                NULL, &ptr, (long) client_key->desc.info.buffer.buffer_size);\n        if (!key) {\n            return avs_errno(AVS_EPROTO);\n        }\n\n        int result = SSL_CTX_use_PrivateKey(ctx, key);\n        EVP_PKEY_free(key);\n        if (result != 1) {\n            return avs_errno(AVS_EPROTO);\n        }\n        return AVS_OK;\n    }\n    default:\n        return avs_errno(AVS_ENOTSUP);\n    }\n}\n\nstatic avs_error_t configure_certs(tls_socket_impl_t *sock,\n                                   const avs_net_certificate_info_t *certs) {\n    if (certs->server_cert_validation) {\n        if (!certs->ignore_system_trust_store) {\n            SSL_CTX_set_default_verify_paths(sock->ctx);\n        }\n        X509_STORE *store = SSL_CTX_get_cert_store(sock->ctx);\n        avs_error_t err;\n        if (avs_is_err((err = configure_trusted_certs(\n                                store, &certs->trusted_certs.desc)))\n                || avs_is_err((err = configure_cert_revocation_lists(\n                                       store,\n                                       &certs->cert_revocation_lists.desc)))) {\n            return err;\n        }\n        SSL_CTX_set_verify(sock->ctx, SSL_VERIFY_PEER, NULL);\n    } else {\n        SSL_CTX_set_verify(sock->ctx, SSL_VERIFY_NONE, NULL);\n    }\n    sock->dane_enabled = certs->dane;\n    if (sock->dane_enabled) {\n        SSL_CTX_dane_enable(sock->ctx);\n    }\n    if (certs->client_cert.desc.source != AVS_CRYPTO_DATA_SOURCE_EMPTY) {\n        avs_error_t err;\n        if (avs_is_err((err = configure_client_certs(sock->ctx,\n                                                     &certs->client_cert.desc)))\n                || avs_is_err(err = configure_client_key(sock->ctx,\n                                                         &certs->client_key))) {\n            return err;\n        }\n    }\n\n    return AVS_OK;\n}\n\nstatic avs_error_t configure_dtls_handshake_timeouts(\n        tls_socket_impl_t *sock,\n        const avs_net_dtls_handshake_timeouts_t *dtls_handshake_timeouts) {\n    uint64_t min_us = 1000000, max_us = 60000000;\n    if (dtls_handshake_timeouts) {\n        avs_time_duration_to_scalar(&min_us, AVS_TIME_US,\n                                    dtls_handshake_timeouts->min);\n        avs_time_duration_to_scalar(&max_us, AVS_TIME_US,\n                                    dtls_handshake_timeouts->max);\n    }\n    sock->dtls_hs_timeout_min_us = (unsigned int) min_us;\n    sock->dtls_hs_timeout_max_us = (unsigned int) max_us;\n    return AVS_OK;\n}\n\nstatic avs_error_t\nconfigure_ciphersuites(tls_socket_impl_t *sock,\n                       const avs_net_socket_tls_ciphersuites_t *ciphersuites) {\n    if (!ciphersuites->num_ids) {\n        return AVS_OK;\n    }\n    SSL *dummy_ssl = SSL_new(sock->ctx);\n    if (!dummy_ssl) {\n        return avs_errno(AVS_ENOMEM);\n    }\n    char cipher_list[1024] = \"-ALL\";\n    char *cipher_list_ptr = cipher_list + strlen(cipher_list);\n    const char *const cipher_list_end = cipher_list + sizeof(cipher_list);\n    for (size_t i = 0; i < ciphersuites->num_ids; ++i) {\n        unsigned char id_as_chars[] = {\n            (unsigned char) (ciphersuites->ids[i] >> 8),\n            (unsigned char) (ciphersuites->ids[i] & 0xFF)\n        };\n        const SSL_CIPHER *cipher = SSL_CIPHER_find(dummy_ssl, id_as_chars);\n        if (cipher) {\n            const char *name = SSL_CIPHER_get_name(cipher);\n            if (!!strstr(name, \"PSK\") == !!sock->psk_size\n                    && cipher_list_ptr + 1 + strlen(name) < cipher_list_end) {\n                *cipher_list_ptr++ = ':';\n                strcpy(cipher_list_ptr, name);\n                cipher_list_ptr += strlen(name);\n            }\n        }\n    }\n    SSL_free(dummy_ssl);\n    SSL_CTX_set_cipher_list(sock->ctx, cipher_list);\n    // NOTE: Configuring the set of supported new-style ciphersuites as defined\n    // for TLS 1.3 are not supported by this function.\n    return AVS_OK;\n}\n\nstatic avs_error_t configure_sni(tls_socket_impl_t *sock,\n                                 const char *server_name_indication) {\n    if (server_name_indication) {\n        if (strlen(server_name_indication)\n                >= sizeof(sock->server_name_indication)) {\n            return avs_errno(AVS_ENOBUFS);\n        }\n        strcpy(sock->server_name_indication, server_name_indication);\n    }\n    return AVS_OK;\n}\n\nstatic int new_session_cb(SSL *ssl, SSL_SESSION *sess) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) SSL_get_app_data(ssl);\n    int serialized_size = i2d_SSL_SESSION(sess, NULL);\n    if (serialized_size > 0\n            && (size_t) serialized_size\n                           <= sock->session_resumption_buffer_size) {\n        unsigned char *ptr = (unsigned char *) sock->session_resumption_buffer;\n        i2d_SSL_SESSION(sess, &ptr);\n    }\n    return 0;\n}\n\navs_error_t _avs_net_create_dtls_socket(avs_net_socket_t **socket_ptr,\n                                        const void *configuration_) {\n    assert(socket_ptr);\n    assert(!*socket_ptr);\n    assert(configuration_);\n    const avs_net_ssl_configuration_t *configuration =\n            (const avs_net_ssl_configuration_t *) configuration_;\n    tls_socket_impl_t *socket =\n            (tls_socket_impl_t *) avs_calloc(1, sizeof(tls_socket_impl_t));\n    if (!socket) {\n        return avs_errno(AVS_ENOMEM);\n    }\n    *socket_ptr = (avs_net_socket_t *) socket;\n    socket->operations = &TLS_SOCKET_VTABLE;\n\n    avs_error_t err = AVS_OK;\n    if (avs_is_ok((err = avs_net_udp_socket_create(\n                           &socket->backend_socket,\n                           &configuration->backend_configuration)))\n            && !(socket->ctx = SSL_CTX_new(DTLS_method()))) {\n        err = avs_errno(AVS_ENOMEM);\n    }\n    if (avs_is_ok(err)) {\n        err = configure_dtls_version(socket, configuration->version);\n    }\n    if (avs_is_ok(err)) {\n        switch (configuration->security.mode) {\n        case AVS_NET_SECURITY_PSK:\n            err = configure_psk(socket, &configuration->security.data.psk);\n            break;\n        case AVS_NET_SECURITY_CERTIFICATE:\n            err = configure_certs(socket, &configuration->security.data.cert);\n            break;\n        default:\n            err = avs_errno(AVS_ENOTSUP);\n        }\n    }\n    if (avs_is_err(err)\n            || avs_is_err((\n                       err = configure_dtls_handshake_timeouts(\n                               socket, configuration->dtls_handshake_timeouts)))\n            || avs_is_err((err = configure_ciphersuites(\n                                   socket, &configuration->ciphersuites)))\n            || avs_is_err((err = configure_sni(\n                                   socket,\n                                   configuration->server_name_indication)))) {\n        avs_net_socket_cleanup(socket_ptr);\n        return err;\n    }\n    SSL_CTX_set_mode(socket->ctx, SSL_MODE_AUTO_RETRY);\n    if (configuration->session_resumption_buffer_size > 0) {\n        assert(configuration->session_resumption_buffer);\n        socket->session_resumption_buffer =\n                configuration->session_resumption_buffer;\n        socket->session_resumption_buffer_size =\n                configuration->session_resumption_buffer_size;\n        SSL_CTX_set_session_cache_mode(\n                socket->ctx,\n                SSL_SESS_CACHE_CLIENT | SSL_SESS_CACHE_NO_INTERNAL_STORE);\n        SSL_CTX_sess_set_new_cb(socket->ctx, new_session_cb);\n    }\n    return AVS_OK;\n}\n\navs_error_t _avs_net_create_ssl_socket(avs_net_socket_t **socket_ptr,\n                                       const void *configuration) {\n    return avs_errno(AVS_ENOTSUP);\n}\n"
  },
  {
    "path": "examples/custom-tls/certificates-advanced-fake-dane/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.16)\nproject(custom-tls-certificates-advanced-fake-dane C)\n\nset(CMAKE_C_STANDARD 99)\n\nfind_package(anjay REQUIRED)\nfind_package(OpenSSL REQUIRED)\n\nadd_executable(${PROJECT_NAME}\n               src/main.c\n               src/net_impl.c\n               src/tls_impl.c)\ntarget_link_libraries(${PROJECT_NAME} PRIVATE anjay OpenSSL::SSL)\n"
  },
  {
    "path": "examples/custom-tls/certificates-advanced-fake-dane/src/main.c",
    "content": "#include <anjay/anjay.h>\n#include <anjay/security.h>\n#include <anjay/server.h>\n#include <avsystem/commons/avs_log.h>\n\n#include <string.h>\n\nstatic int\nload_buffer_from_file(uint8_t **out, size_t *out_size, const char *filename) {\n    FILE *f = fopen(filename, \"rb\");\n    if (!f) {\n        avs_log(tutorial, ERROR, \"could not open %s\", filename);\n        return -1;\n    }\n    int result = -1;\n    if (fseek(f, 0, SEEK_END)) {\n        goto finish;\n    }\n    long size = ftell(f);\n    if (size < 0 || (unsigned long) size > SIZE_MAX || fseek(f, 0, SEEK_SET)) {\n        goto finish;\n    }\n    *out_size = (size_t) size;\n    if (!(*out = (uint8_t *) avs_malloc(*out_size))) {\n        goto finish;\n    }\n    if (fread(*out, *out_size, 1, f) != 1) {\n        avs_free(*out);\n        *out = NULL;\n        goto finish;\n    }\n    result = 0;\nfinish:\n    fclose(f);\n    if (result) {\n        avs_log(tutorial, ERROR, \"could not read %s\", filename);\n    }\n    return result;\n}\n\n// Installs Security Object and adds an instance of it.\n// An instance of Security Object provides information needed to connect to\n// LwM2M server.\nstatic int setup_security_object(anjay_t *anjay) {\n    if (anjay_security_object_install(anjay)) {\n        return -1;\n    }\n\n    anjay_security_instance_t security_instance = {\n        .ssid = 1,\n        .server_uri = \"coaps://eu.iot.avsystem.cloud:5684\",\n        .security_mode = ANJAY_SECURITY_CERTIFICATE\n    };\n\n    int result = 0;\n\n    if (load_buffer_from_file(\n                (uint8_t **) &security_instance.public_cert_or_psk_identity,\n                &security_instance.public_cert_or_psk_identity_size,\n                \"client_cert.der\")\n            || load_buffer_from_file(\n                       (uint8_t **) &security_instance.private_cert_or_psk_key,\n                       &security_instance.private_cert_or_psk_key_size,\n                       \"client_key.der\")\n            || load_buffer_from_file(\n                       (uint8_t **) &security_instance.server_public_key,\n                       &security_instance.server_public_key_size,\n                       \"server_cert.der\")) {\n        result = -1;\n        goto cleanup;\n    }\n\n    // Anjay will assign Instance ID automatically\n    anjay_iid_t security_instance_id = ANJAY_ID_INVALID;\n    if (anjay_security_object_add_instance(anjay, &security_instance,\n                                           &security_instance_id)) {\n        result = -1;\n    }\n\ncleanup:\n    avs_free((uint8_t *) security_instance.public_cert_or_psk_identity);\n    avs_free((uint8_t *) security_instance.private_cert_or_psk_key);\n    avs_free((uint8_t *) security_instance.server_public_key);\n\n    return result;\n}\n\n// Installs Server Object and adds an instance of it.\n// An instance of Server Object provides the data related to a LwM2M Server.\nstatic int setup_server_object(anjay_t *anjay) {\n    if (anjay_server_object_install(anjay)) {\n        return -1;\n    }\n\n    const anjay_server_instance_t server_instance = {\n        // Server Short ID\n        .ssid = 1,\n        // Client will send Update message often than every 60 seconds\n        .lifetime = 60,\n        // Disable Default Minimum Period resource\n        .default_min_period = -1,\n        // Disable Default Maximum Period resource\n        .default_max_period = -1,\n        // Disable Disable Timeout resource\n        .disable_timeout = -1,\n        // Sets preferred transport to UDP\n        .binding = \"U\"\n    };\n\n    // Anjay will assign Instance ID automatically\n    anjay_iid_t server_instance_id = ANJAY_ID_INVALID;\n    if (anjay_server_object_add_instance(anjay, &server_instance,\n                                         &server_instance_id)) {\n        return -1;\n    }\n\n    return 0;\n}\n\nint main(int argc, char *argv[]) {\n    if (argc != 2) {\n        avs_log(tutorial, ERROR, \"usage: %s ENDPOINT_NAME\", argv[0]);\n        return -1;\n    }\n\n    const anjay_configuration_t CONFIG = {\n        .endpoint_name = argv[1],\n        .in_buffer_size = 4000,\n        .out_buffer_size = 4000,\n        .msg_cache_size = 4000\n    };\n\n    anjay_t *anjay = anjay_new(&CONFIG);\n    if (!anjay) {\n        avs_log(tutorial, ERROR, \"Could not create Anjay object\");\n        return -1;\n    }\n\n    int result = 0;\n    // Setup necessary objects\n    if (setup_security_object(anjay) || setup_server_object(anjay)) {\n        result = -1;\n    }\n\n    if (!result) {\n        result = anjay_event_loop_run(\n                anjay, avs_time_duration_from_scalar(1, AVS_TIME_S));\n    }\n\n    anjay_delete(anjay);\n    return result;\n}\n"
  },
  {
    "path": "examples/custom-tls/certificates-advanced-fake-dane/src/net_impl.c",
    "content": "#include <inttypes.h>\n\n#include <netdb.h>\n#include <poll.h>\n#include <unistd.h>\n\n#include <arpa/inet.h>\n\n#include <sys/socket.h>\n#include <sys/types.h>\n\n#include <avsystem/commons/avs_socket_v_table.h>\n#include <avsystem/commons/avs_utils.h>\n\n#ifdef AVS_COMMONS_NET_WITH_POSIX_AVS_SOCKET\n#    error \"Custom implementation of the network layer conflicts with AVS_COMMONS_NET_WITH_POSIX_AVS_SOCKET\"\n#endif // AVS_COMMONS_NET_WITH_POSIX_AVS_SOCKET\n\navs_error_t _avs_net_initialize_global_compat_state(void);\nvoid _avs_net_cleanup_global_compat_state(void);\navs_error_t _avs_net_create_tcp_socket(avs_net_socket_t **socket,\n                                       const void *socket_configuration);\navs_error_t _avs_net_create_udp_socket(avs_net_socket_t **socket,\n                                       const void *socket_configuration);\n\navs_error_t _avs_net_initialize_global_compat_state(void) {\n    return AVS_OK;\n}\n\nvoid _avs_net_cleanup_global_compat_state(void) {}\n\ntypedef struct {\n    const avs_net_socket_v_table_t *operations;\n    int socktype;\n    int fd;\n    avs_time_duration_t recv_timeout;\n    char remote_hostname[256];\n    bool shut_down;\n    size_t bytes_sent;\n    size_t bytes_received;\n    avs_net_resolved_endpoint_t *preferred_endpoint;\n} net_socket_impl_t;\n\ntypedef union {\n    struct sockaddr addr;\n    struct sockaddr_in in;\n    struct sockaddr_in6 in6;\n    struct sockaddr_storage storage;\n} sockaddr_union_t;\n\nstatic avs_error_t\nnet_connect(avs_net_socket_t *sock_, const char *host, const char *port) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    struct addrinfo hints = {\n        .ai_socktype = sock->socktype\n    };\n    if (sock->fd >= 0) {\n        getsockopt(sock->fd, SOL_SOCKET, SO_DOMAIN, &hints.ai_family,\n                   &(socklen_t) { sizeof(hints.ai_family) });\n    }\n    struct addrinfo *addrs = NULL;\n    avs_error_t err = AVS_OK;\n    if (getaddrinfo(host, port, &hints, &addrs) || !addrs) {\n        err = avs_errno(AVS_EADDRNOTAVAIL);\n    } else if (sock->fd < 0\n               && (sock->fd = socket(addrs->ai_family, addrs->ai_socktype,\n                                     addrs->ai_protocol))\n                          < 0) {\n        err = avs_errno(AVS_UNKNOWN_ERROR);\n    } else {\n        const struct addrinfo *addr = addrs;\n        if (sock->preferred_endpoint\n                && sock->preferred_endpoint->size == sizeof(sockaddr_union_t)) {\n            while (addr) {\n                if (addr->ai_addrlen <= sizeof(sockaddr_union_t)\n                        && memcmp(addr->ai_addr,\n                                  sock->preferred_endpoint->data.buf,\n                                  addr->ai_addrlen)\n                                       == 0) {\n                    break;\n                }\n                addr = addr->ai_next;\n            }\n        }\n        if (!addr) {\n            // Preferred endpoint not found, use the first one\n            addr = addrs;\n        }\n        if (connect(sock->fd, addr->ai_addr, addr->ai_addrlen)) {\n            err = avs_errno(AVS_ECONNREFUSED);\n        }\n        if (sock->preferred_endpoint && avs_is_ok(err)) {\n            assert(addr->ai_addrlen <= sizeof(sockaddr_union_t));\n            memcpy(sock->preferred_endpoint->data.buf, addr->ai_addr,\n                   addr->ai_addrlen);\n            sock->preferred_endpoint->size = sizeof(sockaddr_union_t);\n        }\n    }\n    if (avs_is_ok(err)) {\n        sock->shut_down = false;\n        snprintf(sock->remote_hostname, sizeof(sock->remote_hostname), \"%s\",\n                 host);\n    }\n    freeaddrinfo(addrs);\n    return err;\n}\n\nstatic avs_error_t\nnet_send(avs_net_socket_t *sock_, const void *buffer, size_t buffer_length) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    ssize_t written = send(sock->fd, buffer, buffer_length, MSG_NOSIGNAL);\n    if (written >= 0) {\n        sock->bytes_sent += (size_t) written;\n        if ((size_t) written == buffer_length) {\n            return AVS_OK;\n        }\n    }\n    return avs_errno(AVS_EIO);\n}\n\nstatic avs_error_t net_receive(avs_net_socket_t *sock_,\n                               size_t *out_bytes_received,\n                               void *buffer,\n                               size_t buffer_length) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    struct pollfd pfd = {\n        .fd = sock->fd,\n        .events = POLLIN\n    };\n    int64_t timeout_ms;\n    if (avs_time_duration_to_scalar(&timeout_ms, AVS_TIME_MS,\n                                    sock->recv_timeout)) {\n        timeout_ms = -1;\n    } else if (timeout_ms < 0) {\n        timeout_ms = 0;\n    }\n    if (poll(&pfd, 1, (int) timeout_ms) == 0) {\n        return avs_errno(AVS_ETIMEDOUT);\n    }\n    ssize_t bytes_received = read(sock->fd, buffer, buffer_length);\n    if (bytes_received < 0) {\n        return avs_errno(AVS_EIO);\n    }\n    *out_bytes_received = (size_t) bytes_received;\n    sock->bytes_received += (size_t) bytes_received;\n    if (buffer_length > 0 && sock->socktype == SOCK_DGRAM\n            && (size_t) bytes_received == buffer_length) {\n        return avs_errno(AVS_EMSGSIZE);\n    }\n    return AVS_OK;\n}\n\nstatic avs_error_t\nnet_bind(avs_net_socket_t *sock_, const char *address, const char *port) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    struct addrinfo hints = {\n        .ai_flags = AI_PASSIVE,\n        .ai_socktype = sock->socktype\n    };\n    if (sock->fd >= 0) {\n        getsockopt(sock->fd, SOL_SOCKET, SO_DOMAIN, &hints.ai_family,\n                   &(socklen_t) { sizeof(hints.ai_family) });\n    }\n    struct addrinfo *addr = NULL;\n    avs_error_t err = AVS_OK;\n    if (getaddrinfo(address, port, &hints, &addr) || !addr) {\n        err = avs_errno(AVS_EADDRNOTAVAIL);\n    } else if ((sock->fd < 0\n                && (sock->fd = socket(addr->ai_family, addr->ai_socktype,\n                                      addr->ai_protocol))\n                           < 0)\n               || setsockopt(sock->fd, SOL_SOCKET, SO_REUSEADDR, &(int) { 1 },\n                             sizeof(int))) {\n        err = avs_errno(AVS_UNKNOWN_ERROR);\n    } else if (bind(sock->fd, addr->ai_addr, addr->ai_addrlen)) {\n        err = avs_errno(AVS_ECONNREFUSED);\n    } else {\n        sock->shut_down = false;\n    }\n    if (avs_is_err(err) && sock->fd >= 0) {\n        close(sock->fd);\n        sock->fd = -1;\n    }\n    freeaddrinfo(addr);\n    return err;\n}\n\nstatic avs_error_t net_close(avs_net_socket_t *sock_) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    avs_error_t err = AVS_OK;\n    if (sock->fd >= 0) {\n        if (close(sock->fd)) {\n            err = avs_errno(AVS_EIO);\n        }\n        sock->fd = -1;\n        sock->shut_down = false;\n    }\n    return err;\n}\n\nstatic avs_error_t net_shutdown(avs_net_socket_t *sock_) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    avs_error_t err = avs_errno(AVS_EBADF);\n    if (sock->fd >= 0) {\n        err = shutdown(sock->fd, SHUT_RDWR) ? avs_errno(AVS_EIO) : AVS_OK;\n        sock->shut_down = true;\n    }\n    return err;\n}\n\nstatic avs_error_t net_cleanup(avs_net_socket_t **sock_ptr) {\n    avs_error_t err = AVS_OK;\n    if (sock_ptr && *sock_ptr) {\n        err = net_close(*sock_ptr);\n        avs_free(*sock_ptr);\n        *sock_ptr = NULL;\n    }\n    return err;\n}\n\nstatic const void *net_system_socket(avs_net_socket_t *sock_) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    return &sock->fd;\n}\n\nstatic avs_error_t stringify_sockaddr_host(const sockaddr_union_t *addr,\n                                           char *out_buffer,\n                                           size_t out_buffer_size) {\n    if ((addr->in.sin_family == AF_INET\n         && inet_ntop(AF_INET, &addr->in.sin_addr, out_buffer,\n                      (socklen_t) out_buffer_size))\n            || (addr->in6.sin6_family == AF_INET6\n                && inet_ntop(AF_INET6, &addr->in6.sin6_addr, out_buffer,\n                             (socklen_t) out_buffer_size))) {\n        return AVS_OK;\n    }\n    return avs_errno(AVS_UNKNOWN_ERROR);\n}\n\nstatic avs_error_t stringify_sockaddr_port(const sockaddr_union_t *addr,\n                                           char *out_buffer,\n                                           size_t out_buffer_size) {\n    if ((addr->in.sin_family == AF_INET\n         && avs_simple_snprintf(out_buffer, out_buffer_size, \"%\" PRIu16,\n                                ntohs(addr->in.sin_port))\n                    >= 0)\n            || (addr->in6.sin6_family == AF_INET6\n                && avs_simple_snprintf(out_buffer, out_buffer_size, \"%\" PRIu16,\n                                       ntohs(addr->in6.sin6_port))\n                           >= 0)) {\n        return AVS_OK;\n    }\n    return avs_errno(AVS_UNKNOWN_ERROR);\n}\n\nstatic avs_error_t net_remote_host(avs_net_socket_t *sock_,\n                                   char *out_buffer,\n                                   size_t out_buffer_size) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    sockaddr_union_t addr;\n    if (getpeername(sock->fd, &addr.addr, &(socklen_t) { sizeof(addr) })) {\n        return avs_errno(AVS_UNKNOWN_ERROR);\n    }\n    return stringify_sockaddr_host(&addr, out_buffer, out_buffer_size);\n}\n\nstatic avs_error_t net_remote_hostname(avs_net_socket_t *sock_,\n                                       char *out_buffer,\n                                       size_t out_buffer_size) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    return avs_simple_snprintf(out_buffer, out_buffer_size, \"%s\",\n                               sock->remote_hostname)\n                           < 0\n                   ? avs_errno(AVS_UNKNOWN_ERROR)\n                   : AVS_OK;\n}\n\nstatic avs_error_t net_remote_port(avs_net_socket_t *sock_,\n                                   char *out_buffer,\n                                   size_t out_buffer_size) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    sockaddr_union_t addr;\n    if (getpeername(sock->fd, &addr.addr, &(socklen_t) { sizeof(addr) })) {\n        return avs_errno(AVS_UNKNOWN_ERROR);\n    }\n    return stringify_sockaddr_port(&addr, out_buffer, out_buffer_size);\n}\n\nstatic avs_error_t net_local_port(avs_net_socket_t *sock_,\n                                  char *out_buffer,\n                                  size_t out_buffer_size) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    sockaddr_union_t addr;\n    if (getsockname(sock->fd, &addr.addr, &(socklen_t) { sizeof(addr) })) {\n        return avs_errno(AVS_UNKNOWN_ERROR);\n    }\n    return stringify_sockaddr_port(&addr, out_buffer, out_buffer_size);\n}\n\nstatic avs_error_t net_get_opt(avs_net_socket_t *sock_,\n                               avs_net_socket_opt_key_t option_key,\n                               avs_net_socket_opt_value_t *out_option_value) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    switch (option_key) {\n    case AVS_NET_SOCKET_OPT_RECV_TIMEOUT:\n        out_option_value->recv_timeout = sock->recv_timeout;\n        return AVS_OK;\n    case AVS_NET_SOCKET_OPT_STATE:\n        if (sock->fd < 0) {\n            out_option_value->state = AVS_NET_SOCKET_STATE_CLOSED;\n        } else if (sock->shut_down) {\n            out_option_value->state = AVS_NET_SOCKET_STATE_SHUTDOWN;\n        } else {\n            sockaddr_union_t addr;\n            if (!getpeername(sock->fd, &addr.addr,\n                             &(socklen_t) { sizeof(addr) })\n                    && ((addr.in.sin_family == AF_INET && addr.in.sin_port != 0)\n                        || (addr.in6.sin6_family == AF_INET6\n                            && addr.in6.sin6_port != 0))) {\n                out_option_value->state = AVS_NET_SOCKET_STATE_CONNECTED;\n            } else {\n                out_option_value->state = AVS_NET_SOCKET_STATE_BOUND;\n            }\n        }\n        return AVS_OK;\n    case AVS_NET_SOCKET_OPT_INNER_MTU:\n        out_option_value->mtu = 1464;\n        return AVS_OK;\n    case AVS_NET_SOCKET_HAS_BUFFERED_DATA:\n        out_option_value->flag = false;\n        return AVS_OK;\n    case AVS_NET_SOCKET_OPT_BYTES_SENT:\n        out_option_value->bytes_sent = sock->bytes_sent;\n        return AVS_OK;\n    case AVS_NET_SOCKET_OPT_BYTES_RECEIVED:\n        out_option_value->bytes_received = sock->bytes_received;\n        return AVS_OK;\n    default:\n        return avs_errno(AVS_ENOTSUP);\n    }\n}\n\nstatic avs_error_t net_set_opt(avs_net_socket_t *sock_,\n                               avs_net_socket_opt_key_t option_key,\n                               avs_net_socket_opt_value_t option_value) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    switch (option_key) {\n    case AVS_NET_SOCKET_OPT_RECV_TIMEOUT:\n        sock->recv_timeout = option_value.recv_timeout;\n        return AVS_OK;\n    default:\n        return avs_errno(AVS_ENOTSUP);\n    }\n}\n\nstatic const avs_net_socket_v_table_t NET_SOCKET_VTABLE = {\n    .connect = net_connect,\n    .send = net_send,\n    .receive = net_receive,\n    .bind = net_bind,\n    .close = net_close,\n    .shutdown = net_shutdown,\n    .cleanup = net_cleanup,\n    .get_system_socket = net_system_socket,\n    .get_remote_host = net_remote_host,\n    .get_remote_hostname = net_remote_hostname,\n    .get_remote_port = net_remote_port,\n    .get_local_port = net_local_port,\n    .get_opt = net_get_opt,\n    .set_opt = net_set_opt\n};\n\nstatic avs_error_t\nnet_create_socket(avs_net_socket_t **socket_ptr,\n                  const avs_net_socket_configuration_t *configuration,\n                  int socktype) {\n    assert(socket_ptr);\n    assert(!*socket_ptr);\n    (void) configuration;\n    net_socket_impl_t *socket =\n            (net_socket_impl_t *) avs_calloc(1, sizeof(net_socket_impl_t));\n    if (!socket) {\n        return avs_errno(AVS_ENOMEM);\n    }\n    socket->operations = &NET_SOCKET_VTABLE;\n    socket->socktype = socktype;\n    socket->fd = -1;\n    socket->recv_timeout = avs_time_duration_from_scalar(30, AVS_TIME_S);\n    socket->preferred_endpoint = configuration->preferred_endpoint;\n    *socket_ptr = (avs_net_socket_t *) socket;\n    return AVS_OK;\n}\n\navs_error_t _avs_net_create_udp_socket(avs_net_socket_t **socket_ptr,\n                                       const void *configuration) {\n    return net_create_socket(\n            socket_ptr, (const avs_net_socket_configuration_t *) configuration,\n            SOCK_DGRAM);\n}\n\navs_error_t _avs_net_create_tcp_socket(avs_net_socket_t **socket_ptr,\n                                       const void *configuration) {\n    return net_create_socket(\n            socket_ptr, (const avs_net_socket_configuration_t *) configuration,\n            SOCK_STREAM);\n}\n\navs_error_t\navs_net_resolved_endpoint_get_host_port(const avs_net_resolved_endpoint_t *endp,\n                                        char *host,\n                                        size_t hostlen,\n                                        char *serv,\n                                        size_t servlen) {\n    AVS_STATIC_ASSERT(sizeof(endp->data.buf) >= sizeof(sockaddr_union_t),\n                      data_buffer_big_enough);\n    if (endp->size != sizeof(sockaddr_union_t)) {\n        return avs_errno(AVS_EINVAL);\n    }\n    const sockaddr_union_t *addr = (const sockaddr_union_t *) &endp->data.buf;\n    avs_error_t err = AVS_OK;\n    (void) ((host\n             && avs_is_err(\n                        (err = stringify_sockaddr_host(addr, host, hostlen))))\n            || (serv\n                && avs_is_err((err = stringify_sockaddr_port(addr, serv,\n                                                             servlen)))));\n    return err;\n}\n"
  },
  {
    "path": "examples/custom-tls/certificates-advanced-fake-dane/src/tls_impl.c",
    "content": "#include <poll.h>\n\n#include <openssl/err.h>\n#include <openssl/ssl.h>\n\n#include <avsystem/commons/avs_crypto_common.h>\n#include <avsystem/commons/avs_socket_v_table.h>\n#include <avsystem/commons/avs_utils.h>\n\n#ifndef AVS_COMMONS_WITH_CUSTOM_TLS\n#    error \"Custom implementation of the TLS layer requires AVS_COMMONS_WITH_CUSTOM_TLS\"\n#endif // AVS_COMMONS_WITH_CUSTOM_TLS\n\navs_error_t _avs_net_initialize_global_ssl_state(void);\nvoid _avs_net_cleanup_global_ssl_state(void);\navs_error_t _avs_net_create_ssl_socket(avs_net_socket_t **socket,\n                                       const void *socket_configuration);\navs_error_t _avs_net_create_dtls_socket(avs_net_socket_t **socket,\n                                        const void *socket_configuration);\n\navs_error_t _avs_net_initialize_global_ssl_state(void) {\n    if (!OPENSSL_init_ssl(OPENSSL_INIT_ADD_ALL_CIPHERS\n                                  | OPENSSL_INIT_ADD_ALL_DIGESTS,\n                          NULL)) {\n        return avs_errno(AVS_EPROTO);\n    }\n    return AVS_OK;\n}\n\nvoid _avs_net_cleanup_global_ssl_state(void) {}\n\nvoid avs_crypto_clear_buffer(void *buf, size_t size) {\n    OPENSSL_cleanse(buf, size);\n}\n\ntypedef struct {\n    const avs_net_socket_v_table_t *operations;\n    avs_net_socket_t *backend_socket;\n    SSL_CTX *ctx;\n    SSL *ssl;\n\n    char psk[256];\n    size_t psk_size;\n    char identity[128];\n    size_t identity_size;\n\n    bool dane_enabled;\n\n    void *session_resumption_buffer;\n    size_t session_resumption_buffer_size;\n\n    char server_name_indication[256];\n    unsigned int dtls_hs_timeout_min_us;\n    unsigned int dtls_hs_timeout_max_us;\n} tls_socket_impl_t;\n\nstatic unsigned int dtls_timer_cb(SSL *s, unsigned int timer_us) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) SSL_get_app_data(s);\n    if (!timer_us) {\n        return sock->dtls_hs_timeout_min_us;\n    } else if (timer_us >= sock->dtls_hs_timeout_max_us) {\n        // maximum number of retransmissions reached, let's give up\n        avs_net_socket_shutdown(sock->backend_socket);\n        return 0;\n    } else {\n        timer_us *= 2;\n        if (timer_us > sock->dtls_hs_timeout_max_us) {\n            timer_us = sock->dtls_hs_timeout_max_us;\n        }\n        return timer_us;\n    }\n}\n\nstatic avs_error_t perform_handshake(tls_socket_impl_t *sock,\n                                     const char *host) {\n    union {\n        struct sockaddr addr;\n        struct sockaddr_storage storage;\n    } peername;\n    const void *fd_ptr = avs_net_socket_get_system(sock->backend_socket);\n    if (!fd_ptr\n            || getpeername(*(const int *) fd_ptr, &peername.addr,\n                           &(socklen_t) { sizeof(peername) })) {\n        return avs_errno(AVS_EBADF);\n    }\n\n    sock->ssl = SSL_new(sock->ctx);\n    if (!sock->ssl) {\n        return avs_errno(AVS_ENOMEM);\n    }\n\n    SSL_set_app_data(sock->ssl, sock);\n    SSL_set_tlsext_host_name(sock->ssl, host);\n    SSL_set1_host(sock->ssl, host);\n\n    BIO *bio = BIO_new_dgram(*(const int *) fd_ptr, 0);\n    if (!bio) {\n        return avs_errno(AVS_ENOMEM);\n    }\n    BIO_ctrl(bio, BIO_CTRL_DGRAM_SET_CONNECTED, 0, &peername.addr);\n    SSL_set_bio(sock->ssl, bio, bio);\n    DTLS_set_timer_cb(sock->ssl, dtls_timer_cb);\n\n    if (sock->session_resumption_buffer) {\n        const unsigned char *ptr =\n                (const unsigned char *) sock->session_resumption_buffer;\n        SSL_SESSION *session =\n                d2i_SSL_SESSION(NULL, &ptr,\n                                sock->session_resumption_buffer_size);\n        if (session) {\n            SSL_set_session(sock->ssl, session);\n            SSL_SESSION_free(session);\n        }\n    }\n\n    if (SSL_connect(sock->ssl) <= 0) {\n        return avs_errno(AVS_EPROTO);\n    }\n    return AVS_OK;\n}\n\nstatic avs_error_t\ntls_connect(avs_net_socket_t *sock_, const char *host, const char *port) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    if (sock->ssl) {\n        return avs_errno(AVS_EBADF);\n    }\n    avs_error_t err;\n    if (avs_is_err((\n                err = avs_net_socket_connect(sock->backend_socket, host, port)))\n            || avs_is_err((err = perform_handshake(\n                                   sock, sock->server_name_indication[0]\n                                                 ? sock->server_name_indication\n                                                 : host)))) {\n        if (sock->ssl) {\n            SSL_free(sock->ssl);\n            sock->ssl = NULL;\n        }\n        avs_net_socket_close(sock->backend_socket);\n    }\n    return err;\n}\n\nstatic avs_error_t\ntls_send(avs_net_socket_t *sock_, const void *buffer, size_t buffer_length) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    int result = SSL_write(sock->ssl, buffer, (int) buffer_length);\n    if (result < 0 || (size_t) result < buffer_length) {\n        return avs_errno(AVS_EPROTO);\n    }\n    return AVS_OK;\n}\n\nstatic avs_error_t tls_receive(avs_net_socket_t *sock_,\n                               size_t *out_bytes_received,\n                               void *buffer,\n                               size_t buffer_length) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    const void *fd_ptr = avs_net_socket_get_system(sock->backend_socket);\n    avs_net_socket_opt_value_t timeout;\n    if (!fd_ptr\n            || avs_is_err(avs_net_socket_get_opt(\n                       sock->backend_socket, AVS_NET_SOCKET_OPT_RECV_TIMEOUT,\n                       &timeout))) {\n        return avs_errno(AVS_EBADF);\n    }\n    struct pollfd pfd = {\n        .fd = *(const int *) fd_ptr,\n        .events = POLLIN\n    };\n    int64_t timeout_ms;\n    if (avs_time_duration_to_scalar(&timeout_ms, AVS_TIME_MS,\n                                    timeout.recv_timeout)) {\n        timeout_ms = -1;\n    } else if (timeout_ms < 0) {\n        timeout_ms = 0;\n    }\n    if (poll(&pfd, 1, (int) timeout_ms) == 0) {\n        return avs_errno(AVS_ETIMEDOUT);\n    }\n    int bytes_received = SSL_read(sock->ssl, buffer, (int) buffer_length);\n    if (bytes_received < 0) {\n        return avs_errno(AVS_EPROTO);\n    }\n    *out_bytes_received = (size_t) bytes_received;\n    if (buffer_length > 0 && (size_t) bytes_received == buffer_length) {\n        return avs_errno(AVS_EMSGSIZE);\n    }\n    return AVS_OK;\n}\n\nstatic avs_error_t\ntls_bind(avs_net_socket_t *sock_, const char *address, const char *port) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    return avs_net_socket_bind(sock->backend_socket, address, port);\n}\n\nstatic avs_error_t tls_close(avs_net_socket_t *sock_) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    if (sock->ssl) {\n        SSL_free(sock->ssl);\n        sock->ssl = NULL;\n    }\n    return avs_net_socket_close(sock->backend_socket);\n}\n\nstatic avs_error_t tls_shutdown(avs_net_socket_t *sock_) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    return avs_net_socket_shutdown(sock->backend_socket);\n}\n\nstatic avs_error_t tls_cleanup(avs_net_socket_t **sock_ptr) {\n    avs_error_t err = AVS_OK;\n    if (sock_ptr && *sock_ptr) {\n        tls_socket_impl_t *sock = (tls_socket_impl_t *) *sock_ptr;\n        tls_close(*sock_ptr);\n        avs_net_socket_cleanup(&sock->backend_socket);\n        if (sock->ctx) {\n            SSL_CTX_free(sock->ctx);\n        }\n        avs_free(sock);\n        *sock_ptr = NULL;\n    }\n    return err;\n}\n\nstatic const void *tls_system_socket(avs_net_socket_t *sock_) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    return avs_net_socket_get_system(sock->backend_socket);\n}\n\nstatic avs_error_t tls_remote_host(avs_net_socket_t *sock_,\n                                   char *out_buffer,\n                                   size_t out_buffer_size) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    return avs_net_socket_get_remote_host(sock->backend_socket, out_buffer,\n                                          out_buffer_size);\n}\n\nstatic avs_error_t tls_remote_hostname(avs_net_socket_t *sock_,\n                                       char *out_buffer,\n                                       size_t out_buffer_size) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    return avs_net_socket_get_remote_hostname(sock->backend_socket, out_buffer,\n                                              out_buffer_size);\n}\n\nstatic avs_error_t tls_remote_port(avs_net_socket_t *sock_,\n                                   char *out_buffer,\n                                   size_t out_buffer_size) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    return avs_net_socket_get_remote_port(sock->backend_socket, out_buffer,\n                                          out_buffer_size);\n}\n\nstatic avs_error_t tls_local_port(avs_net_socket_t *sock_,\n                                  char *out_buffer,\n                                  size_t out_buffer_size) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    return avs_net_socket_get_local_port(sock->backend_socket, out_buffer,\n                                         out_buffer_size);\n}\n\nstatic avs_error_t tls_get_opt(avs_net_socket_t *sock_,\n                               avs_net_socket_opt_key_t option_key,\n                               avs_net_socket_opt_value_t *out_option_value) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    switch (option_key) {\n    case AVS_NET_SOCKET_OPT_INNER_MTU: {\n        avs_error_t err = avs_net_socket_get_opt(sock->backend_socket,\n                                                 AVS_NET_SOCKET_OPT_INNER_MTU,\n                                                 out_option_value);\n        if (avs_is_ok(err)) {\n            out_option_value->mtu = AVS_MAX(out_option_value->mtu - 64, 0);\n        }\n        return err;\n    }\n    case AVS_NET_SOCKET_HAS_BUFFERED_DATA:\n        out_option_value->flag = (sock->ssl && SSL_pending(sock->ssl) > 0);\n        return AVS_OK;\n    case AVS_NET_SOCKET_OPT_SESSION_RESUMED:\n        out_option_value->flag = (sock->ssl && SSL_session_reused(sock->ssl));\n        return AVS_OK;\n    default:\n        return avs_net_socket_get_opt(sock->backend_socket, option_key,\n                                      out_option_value);\n    }\n}\n\nstatic avs_error_t\nconfigure_trusted_certs(X509_STORE *store,\n                        const avs_crypto_security_info_union_t *trusted_certs) {\n    if (!trusted_certs) {\n        return avs_errno(AVS_EINVAL);\n    }\n    switch (trusted_certs->source) {\n    case AVS_CRYPTO_DATA_SOURCE_EMPTY:\n        return AVS_OK;\n    case AVS_CRYPTO_DATA_SOURCE_BUFFER: {\n        const unsigned char *ptr =\n                (const unsigned char *) trusted_certs->info.buffer.buffer;\n        X509 *cert = d2i_X509(NULL, &ptr,\n                              (long) trusted_certs->info.buffer.buffer_size);\n        if (!cert) {\n            return avs_errno(AVS_EPROTO);\n        }\n\n        ERR_clear_error();\n        int result = X509_STORE_add_cert(store, cert);\n        X509_free(cert);\n        if (!result\n                && ERR_GET_REASON(ERR_get_error())\n                               != X509_R_CERT_ALREADY_IN_HASH_TABLE) {\n            return avs_errno(AVS_EPROTO);\n        }\n        return AVS_OK;\n    }\n    case AVS_CRYPTO_DATA_SOURCE_ARRAY: {\n        avs_error_t err = AVS_OK;\n        for (size_t i = 0;\n             avs_is_ok(err) && i < trusted_certs->info.array.element_count;\n             ++i) {\n            err = configure_trusted_certs(\n                    store, &trusted_certs->info.array.array_ptr[i]);\n        }\n        return err;\n    }\n    case AVS_CRYPTO_DATA_SOURCE_LIST: {\n        avs_error_t err = AVS_OK;\n        AVS_LIST(avs_crypto_security_info_union_t) entry;\n        AVS_LIST_FOREACH(entry, trusted_certs->info.list.list_head) {\n            if (avs_is_err((err = configure_trusted_certs(store, entry)))) {\n                break;\n            }\n        }\n        return AVS_OK;\n    }\n    default:\n        return avs_errno(AVS_ENOTSUP);\n    }\n}\n\nstatic avs_error_t tls_set_opt(avs_net_socket_t *sock_,\n                               avs_net_socket_opt_key_t option_key,\n                               avs_net_socket_opt_value_t option_value) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    switch (option_key) {\n    case AVS_NET_SOCKET_OPT_DANE_TLSA_ARRAY: {\n        if (option_value.dane_tlsa_array.array_element_count > 1) {\n            return avs_errno(AVS_EINVAL);\n        }\n        if (!sock->dane_enabled\n                || option_value.dane_tlsa_array.array_element_count == 0\n                || option_value.dane_tlsa_array.array_ptr[0].certificate_usage\n                               == AVS_NET_SOCKET_DANE_CA_CONSTRAINT\n                || option_value.dane_tlsa_array.array_ptr[0].certificate_usage\n                               == AVS_NET_SOCKET_DANE_SERVICE_CERTIFICATE_CONSTRAINT) {\n            return AVS_OK;\n        }\n        X509_STORE *store = SSL_CTX_get_cert_store(sock->ctx);\n        if (option_value.dane_tlsa_array.array_ptr[0].selector\n                        != AVS_NET_SOCKET_DANE_CERTIFICATE\n                || option_value.dane_tlsa_array.array_ptr[0].matching_type\n                               != AVS_NET_SOCKET_DANE_MATCH_FULL\n                || sk_X509_OBJECT_num(X509_STORE_get0_objects(store)) > 0) {\n            return avs_errno(AVS_ENOTSUP);\n        }\n        avs_crypto_certificate_chain_info_t chain =\n                avs_crypto_certificate_chain_info_from_buffer(\n                        option_value.dane_tlsa_array.array_ptr[0]\n                                .association_data,\n                        option_value.dane_tlsa_array.array_ptr[0]\n                                .association_data_size);\n        avs_error_t err = configure_trusted_certs(store, &chain.desc);\n        if (avs_is_ok(err)) {\n            SSL_CTX_set_verify(sock->ctx, SSL_VERIFY_PEER, NULL);\n        }\n        return err;\n    }\n    default:\n        return avs_net_socket_set_opt(sock->backend_socket, option_key,\n                                      option_value);\n    }\n}\n\nstatic const avs_net_socket_v_table_t TLS_SOCKET_VTABLE = {\n    .connect = tls_connect,\n    .send = tls_send,\n    .receive = tls_receive,\n    .bind = tls_bind,\n    .close = tls_close,\n    .shutdown = tls_shutdown,\n    .cleanup = tls_cleanup,\n    .get_system_socket = tls_system_socket,\n    .get_remote_host = tls_remote_host,\n    .get_remote_hostname = tls_remote_hostname,\n    .get_remote_port = tls_remote_port,\n    .get_local_port = tls_local_port,\n    .get_opt = tls_get_opt,\n    .set_opt = tls_set_opt\n};\n\nstatic avs_error_t configure_dtls_version(tls_socket_impl_t *sock,\n                                          avs_net_ssl_version_t version) {\n    switch (version) {\n    case AVS_NET_SSL_VERSION_DEFAULT:\n        return AVS_OK;\n    case AVS_NET_SSL_VERSION_TLSv1:\n    case AVS_NET_SSL_VERSION_TLSv1_1:\n        SSL_CTX_set_min_proto_version(sock->ctx, DTLS1_VERSION);\n        return AVS_OK;\n    case AVS_NET_SSL_VERSION_TLSv1_2:\n        SSL_CTX_set_min_proto_version(sock->ctx, DTLS1_2_VERSION);\n        return AVS_OK;\n    default:\n        return avs_errno(AVS_ENOTSUP);\n    }\n}\n\nstatic unsigned int psk_client_cb(SSL *ssl,\n                                  const char *hint,\n                                  char *identity,\n                                  unsigned int max_identity_len,\n                                  unsigned char *psk,\n                                  unsigned int max_psk_len) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) SSL_get_app_data(ssl);\n\n    (void) hint;\n\n    if (!sock || max_psk_len < sock->psk_size\n            || max_identity_len < sock->identity_size + 1) {\n        return 0;\n    }\n\n    memcpy(psk, sock->psk, sock->psk_size);\n    memcpy(identity, sock->identity, sock->identity_size);\n    identity[sock->identity_size] = '\\0';\n\n    return (unsigned int) sock->psk_size;\n}\n\nstatic avs_error_t configure_psk(tls_socket_impl_t *sock,\n                                 const avs_net_psk_info_t *psk) {\n    if (psk->key.desc.source != AVS_CRYPTO_DATA_SOURCE_BUFFER\n            || psk->identity.desc.source != AVS_CRYPTO_DATA_SOURCE_BUFFER) {\n        return avs_errno(AVS_EINVAL);\n    }\n\n    const void *key_ptr = psk->key.desc.info.buffer.buffer;\n    size_t key_size = psk->key.desc.info.buffer.buffer_size;\n\n    const void *identity_ptr = psk->identity.desc.info.buffer.buffer;\n    size_t identity_size = psk->identity.desc.info.buffer.buffer_size;\n\n    if (key_size > sizeof(sock->psk)\n            || identity_size > sizeof(sock->identity)) {\n        return avs_errno(AVS_EINVAL);\n    }\n    memcpy(sock->psk, key_ptr, key_size);\n    sock->psk_size = key_size;\n    memcpy(sock->identity, identity_ptr, identity_size);\n    sock->identity_size = identity_size;\n    SSL_CTX_set_cipher_list(sock->ctx, \"PSK\");\n    SSL_CTX_set_psk_client_callback(sock->ctx, psk_client_cb);\n    SSL_CTX_set_verify(sock->ctx, SSL_VERIFY_PEER, NULL);\n    return AVS_OK;\n}\n\nstatic avs_error_t configure_cert_revocation_lists(\n        X509_STORE *store,\n        const avs_crypto_security_info_union_t *cert_revocation_lists) {\n    if (!cert_revocation_lists) {\n        return avs_errno(AVS_EINVAL);\n    }\n    switch (cert_revocation_lists->source) {\n    case AVS_CRYPTO_DATA_SOURCE_EMPTY:\n        return AVS_OK;\n    case AVS_CRYPTO_DATA_SOURCE_BUFFER: {\n        const unsigned char *ptr =\n                (const unsigned char *)\n                        cert_revocation_lists->info.buffer.buffer;\n        X509_CRL *crl = d2i_X509_CRL(\n                NULL, &ptr,\n                (long) cert_revocation_lists->info.buffer.buffer_size);\n        if (!crl) {\n            return avs_errno(AVS_EPROTO);\n        }\n\n        ERR_clear_error();\n        int result = X509_STORE_add_crl(store, crl);\n        X509_CRL_free(crl);\n        if (result != 1) {\n            return avs_errno(AVS_EPROTO);\n        }\n        return AVS_OK;\n    }\n    case AVS_CRYPTO_DATA_SOURCE_ARRAY: {\n        avs_error_t err = AVS_OK;\n        for (size_t i = 0;\n             avs_is_ok(err)\n             && i < cert_revocation_lists->info.array.element_count;\n             ++i) {\n            err = configure_cert_revocation_lists(\n                    store, &cert_revocation_lists->info.array.array_ptr[i]);\n        }\n        return err;\n    }\n    case AVS_CRYPTO_DATA_SOURCE_LIST: {\n        avs_error_t err = AVS_OK;\n        AVS_LIST(avs_crypto_security_info_union_t) entry;\n        AVS_LIST_FOREACH(entry, cert_revocation_lists->info.list.list_head) {\n            if (avs_is_err((\n                        err = configure_cert_revocation_lists(store, entry)))) {\n                break;\n            }\n        }\n        return AVS_OK;\n    }\n    default:\n        return avs_errno(AVS_ENOTSUP);\n    }\n}\n\nstatic avs_error_t\nconfigure_client_certs(SSL_CTX *ctx,\n                       const avs_crypto_security_info_union_t *client_certs) {\n    if (!client_certs) {\n        return avs_errno(AVS_EINVAL);\n    }\n    switch (client_certs->source) {\n    case AVS_CRYPTO_DATA_SOURCE_EMPTY:\n        return AVS_OK;\n    case AVS_CRYPTO_DATA_SOURCE_BUFFER: {\n        const unsigned char *ptr =\n                (const unsigned char *) client_certs->info.buffer.buffer;\n        X509 *cert = d2i_X509(NULL, &ptr,\n                              (long) client_certs->info.buffer.buffer_size);\n        if (!cert) {\n            return avs_errno(AVS_EPROTO);\n        }\n\n        int result;\n        if (!SSL_CTX_get0_certificate(ctx)) {\n            result = SSL_CTX_use_certificate(ctx, cert);\n        } else {\n            result = SSL_CTX_add1_chain_cert(ctx, cert);\n        }\n        X509_free(cert);\n        if (result != 1) {\n            return avs_errno(AVS_EPROTO);\n        }\n        return AVS_OK;\n    }\n    case AVS_CRYPTO_DATA_SOURCE_ARRAY: {\n        avs_error_t err = AVS_OK;\n        for (size_t i = 0;\n             avs_is_ok(err) && i < client_certs->info.array.element_count;\n             ++i) {\n            err = configure_client_certs(\n                    ctx, &client_certs->info.array.array_ptr[i]);\n        }\n        return err;\n    }\n    case AVS_CRYPTO_DATA_SOURCE_LIST: {\n        avs_error_t err = AVS_OK;\n        AVS_LIST(avs_crypto_security_info_union_t) entry;\n        AVS_LIST_FOREACH(entry, client_certs->info.list.list_head) {\n            if (avs_is_err((err = configure_client_certs(ctx, entry)))) {\n                break;\n            }\n        }\n        return AVS_OK;\n    }\n    default:\n        return avs_errno(AVS_ENOTSUP);\n    }\n}\n\nstatic avs_error_t\nconfigure_client_key(SSL_CTX *ctx,\n                     const avs_crypto_private_key_info_t *client_key) {\n    switch (client_key->desc.source) {\n    case AVS_CRYPTO_DATA_SOURCE_BUFFER: {\n        if (client_key->desc.info.buffer.password) {\n            return avs_errno(AVS_ENOTSUP);\n        }\n        const unsigned char *ptr =\n                (const unsigned char *) client_key->desc.info.buffer.buffer;\n        EVP_PKEY *key = d2i_AutoPrivateKey(\n                NULL, &ptr, (long) client_key->desc.info.buffer.buffer_size);\n        if (!key) {\n            return avs_errno(AVS_EPROTO);\n        }\n\n        int result = SSL_CTX_use_PrivateKey(ctx, key);\n        EVP_PKEY_free(key);\n        if (result != 1) {\n            return avs_errno(AVS_EPROTO);\n        }\n        return AVS_OK;\n    }\n    default:\n        return avs_errno(AVS_ENOTSUP);\n    }\n}\n\nstatic avs_error_t configure_certs(tls_socket_impl_t *sock,\n                                   const avs_net_certificate_info_t *certs) {\n    if (certs->server_cert_validation) {\n        if (!certs->ignore_system_trust_store) {\n            SSL_CTX_set_default_verify_paths(sock->ctx);\n        }\n        X509_STORE *store = SSL_CTX_get_cert_store(sock->ctx);\n        avs_error_t err;\n        if (avs_is_err((err = configure_trusted_certs(\n                                store, &certs->trusted_certs.desc)))\n                || avs_is_err((err = configure_cert_revocation_lists(\n                                       store,\n                                       &certs->cert_revocation_lists.desc)))) {\n            return err;\n        }\n        SSL_CTX_set_verify(sock->ctx, SSL_VERIFY_PEER, NULL);\n    } else {\n        SSL_CTX_set_verify(sock->ctx, SSL_VERIFY_NONE, NULL);\n    }\n    sock->dane_enabled = certs->dane;\n    if (certs->client_cert.desc.source != AVS_CRYPTO_DATA_SOURCE_EMPTY) {\n        avs_error_t err;\n        if (avs_is_err((err = configure_client_certs(sock->ctx,\n                                                     &certs->client_cert.desc)))\n                || avs_is_err(err = configure_client_key(sock->ctx,\n                                                         &certs->client_key))) {\n            return err;\n        }\n    }\n\n    return AVS_OK;\n}\n\nstatic avs_error_t configure_dtls_handshake_timeouts(\n        tls_socket_impl_t *sock,\n        const avs_net_dtls_handshake_timeouts_t *dtls_handshake_timeouts) {\n    uint64_t min_us = 1000000, max_us = 60000000;\n    if (dtls_handshake_timeouts) {\n        avs_time_duration_to_scalar(&min_us, AVS_TIME_US,\n                                    dtls_handshake_timeouts->min);\n        avs_time_duration_to_scalar(&max_us, AVS_TIME_US,\n                                    dtls_handshake_timeouts->max);\n    }\n    sock->dtls_hs_timeout_min_us = (unsigned int) min_us;\n    sock->dtls_hs_timeout_max_us = (unsigned int) max_us;\n    return AVS_OK;\n}\n\nstatic avs_error_t\nconfigure_ciphersuites(tls_socket_impl_t *sock,\n                       const avs_net_socket_tls_ciphersuites_t *ciphersuites) {\n    if (!ciphersuites->num_ids) {\n        return AVS_OK;\n    }\n    SSL *dummy_ssl = SSL_new(sock->ctx);\n    if (!dummy_ssl) {\n        return avs_errno(AVS_ENOMEM);\n    }\n    char cipher_list[1024] = \"-ALL\";\n    char *cipher_list_ptr = cipher_list + strlen(cipher_list);\n    const char *const cipher_list_end = cipher_list + sizeof(cipher_list);\n    for (size_t i = 0; i < ciphersuites->num_ids; ++i) {\n        unsigned char id_as_chars[] = {\n            (unsigned char) (ciphersuites->ids[i] >> 8),\n            (unsigned char) (ciphersuites->ids[i] & 0xFF)\n        };\n        const SSL_CIPHER *cipher = SSL_CIPHER_find(dummy_ssl, id_as_chars);\n        if (cipher) {\n            const char *name = SSL_CIPHER_get_name(cipher);\n            if (!!strstr(name, \"PSK\") == !!sock->psk_size\n                    && cipher_list_ptr + 1 + strlen(name) < cipher_list_end) {\n                *cipher_list_ptr++ = ':';\n                strcpy(cipher_list_ptr, name);\n                cipher_list_ptr += strlen(name);\n            }\n        }\n    }\n    SSL_free(dummy_ssl);\n    SSL_CTX_set_cipher_list(sock->ctx, cipher_list);\n    // NOTE: Configuring the set of supported new-style ciphersuites as defined\n    // for TLS 1.3 are not supported by this function.\n    return AVS_OK;\n}\n\nstatic avs_error_t configure_sni(tls_socket_impl_t *sock,\n                                 const char *server_name_indication) {\n    if (server_name_indication) {\n        if (strlen(server_name_indication)\n                >= sizeof(sock->server_name_indication)) {\n            return avs_errno(AVS_ENOBUFS);\n        }\n        strcpy(sock->server_name_indication, server_name_indication);\n    }\n    return AVS_OK;\n}\n\nstatic int new_session_cb(SSL *ssl, SSL_SESSION *sess) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) SSL_get_app_data(ssl);\n    int serialized_size = i2d_SSL_SESSION(sess, NULL);\n    if (serialized_size > 0\n            && (size_t) serialized_size\n                           <= sock->session_resumption_buffer_size) {\n        unsigned char *ptr = (unsigned char *) sock->session_resumption_buffer;\n        i2d_SSL_SESSION(sess, &ptr);\n    }\n    return 0;\n}\n\navs_error_t _avs_net_create_dtls_socket(avs_net_socket_t **socket_ptr,\n                                        const void *configuration_) {\n    assert(socket_ptr);\n    assert(!*socket_ptr);\n    assert(configuration_);\n    const avs_net_ssl_configuration_t *configuration =\n            (const avs_net_ssl_configuration_t *) configuration_;\n    tls_socket_impl_t *socket =\n            (tls_socket_impl_t *) avs_calloc(1, sizeof(tls_socket_impl_t));\n    if (!socket) {\n        return avs_errno(AVS_ENOMEM);\n    }\n    *socket_ptr = (avs_net_socket_t *) socket;\n    socket->operations = &TLS_SOCKET_VTABLE;\n\n    avs_error_t err = AVS_OK;\n    if (avs_is_ok((err = avs_net_udp_socket_create(\n                           &socket->backend_socket,\n                           &configuration->backend_configuration)))\n            && !(socket->ctx = SSL_CTX_new(DTLS_method()))) {\n        err = avs_errno(AVS_ENOMEM);\n    }\n    if (avs_is_ok(err)) {\n        err = configure_dtls_version(socket, configuration->version);\n    }\n    if (avs_is_ok(err)) {\n        switch (configuration->security.mode) {\n        case AVS_NET_SECURITY_PSK:\n            err = configure_psk(socket, &configuration->security.data.psk);\n            break;\n        case AVS_NET_SECURITY_CERTIFICATE:\n            err = configure_certs(socket, &configuration->security.data.cert);\n            break;\n        default:\n            err = avs_errno(AVS_ENOTSUP);\n        }\n    }\n    if (avs_is_err(err)\n            || avs_is_err((\n                       err = configure_dtls_handshake_timeouts(\n                               socket, configuration->dtls_handshake_timeouts)))\n            || avs_is_err((err = configure_ciphersuites(\n                                   socket, &configuration->ciphersuites)))\n            || avs_is_err((err = configure_sni(\n                                   socket,\n                                   configuration->server_name_indication)))) {\n        avs_net_socket_cleanup(socket_ptr);\n        return err;\n    }\n    SSL_CTX_set_mode(socket->ctx, SSL_MODE_AUTO_RETRY);\n    if (configuration->session_resumption_buffer_size > 0) {\n        assert(configuration->session_resumption_buffer);\n        socket->session_resumption_buffer =\n                configuration->session_resumption_buffer;\n        socket->session_resumption_buffer_size =\n                configuration->session_resumption_buffer_size;\n        SSL_CTX_set_session_cache_mode(\n                socket->ctx,\n                SSL_SESS_CACHE_CLIENT | SSL_SESS_CACHE_NO_INTERNAL_STORE);\n        SSL_CTX_sess_set_new_cb(socket->ctx, new_session_cb);\n    }\n    return AVS_OK;\n}\n\navs_error_t _avs_net_create_ssl_socket(avs_net_socket_t **socket_ptr,\n                                       const void *configuration) {\n    return avs_errno(AVS_ENOTSUP);\n}\n"
  },
  {
    "path": "examples/custom-tls/certificates-basic/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.16)\nproject(custom-tls-certificates-basic C)\n\nset(CMAKE_C_STANDARD 99)\n\nfind_package(anjay REQUIRED)\nfind_package(OpenSSL REQUIRED)\n\nadd_executable(${PROJECT_NAME}\n               src/main.c\n               src/net_impl.c\n               src/tls_impl.c)\ntarget_link_libraries(${PROJECT_NAME} PRIVATE anjay OpenSSL::SSL)\n"
  },
  {
    "path": "examples/custom-tls/certificates-basic/src/main.c",
    "content": "#include <anjay/anjay.h>\n#include <anjay/security.h>\n#include <anjay/server.h>\n#include <avsystem/commons/avs_log.h>\n\n#include <string.h>\n\nstatic int\nload_buffer_from_file(uint8_t **out, size_t *out_size, const char *filename) {\n    FILE *f = fopen(filename, \"rb\");\n    if (!f) {\n        avs_log(tutorial, ERROR, \"could not open %s\", filename);\n        return -1;\n    }\n    int result = -1;\n    if (fseek(f, 0, SEEK_END)) {\n        goto finish;\n    }\n    long size = ftell(f);\n    if (size < 0 || (unsigned long) size > SIZE_MAX || fseek(f, 0, SEEK_SET)) {\n        goto finish;\n    }\n    *out_size = (size_t) size;\n    if (!(*out = (uint8_t *) avs_malloc(*out_size))) {\n        goto finish;\n    }\n    if (fread(*out, *out_size, 1, f) != 1) {\n        avs_free(*out);\n        *out = NULL;\n        goto finish;\n    }\n    result = 0;\nfinish:\n    fclose(f);\n    if (result) {\n        avs_log(tutorial, ERROR, \"could not read %s\", filename);\n    }\n    return result;\n}\n\n// Installs Security Object and adds an instance of it.\n// An instance of Security Object provides information needed to connect to\n// LwM2M server.\nstatic int setup_security_object(anjay_t *anjay) {\n    if (anjay_security_object_install(anjay)) {\n        return -1;\n    }\n\n    anjay_security_instance_t security_instance = {\n        .ssid = 1,\n        .server_uri = \"coaps://eu.iot.avsystem.cloud:5684\",\n        .security_mode = ANJAY_SECURITY_CERTIFICATE\n    };\n\n    int result = 0;\n\n    if (load_buffer_from_file(\n                (uint8_t **) &security_instance.public_cert_or_psk_identity,\n                &security_instance.public_cert_or_psk_identity_size,\n                \"client_cert.der\")\n            || load_buffer_from_file(\n                       (uint8_t **) &security_instance.private_cert_or_psk_key,\n                       &security_instance.private_cert_or_psk_key_size,\n                       \"client_key.der\")) {\n        result = -1;\n        goto cleanup;\n    }\n\n    // Anjay will assign Instance ID automatically\n    anjay_iid_t security_instance_id = ANJAY_ID_INVALID;\n    if (anjay_security_object_add_instance(anjay, &security_instance,\n                                           &security_instance_id)) {\n        result = -1;\n    }\n\ncleanup:\n    avs_free((uint8_t *) security_instance.public_cert_or_psk_identity);\n    avs_free((uint8_t *) security_instance.private_cert_or_psk_key);\n\n    return result;\n}\n\n// Installs Server Object and adds an instance of it.\n// An instance of Server Object provides the data related to a LwM2M Server.\nstatic int setup_server_object(anjay_t *anjay) {\n    if (anjay_server_object_install(anjay)) {\n        return -1;\n    }\n\n    const anjay_server_instance_t server_instance = {\n        // Server Short ID\n        .ssid = 1,\n        // Client will send Update message often than every 60 seconds\n        .lifetime = 60,\n        // Disable Default Minimum Period resource\n        .default_min_period = -1,\n        // Disable Default Maximum Period resource\n        .default_max_period = -1,\n        // Disable Disable Timeout resource\n        .disable_timeout = -1,\n        // Sets preferred transport to UDP\n        .binding = \"U\"\n    };\n\n    // Anjay will assign Instance ID automatically\n    anjay_iid_t server_instance_id = ANJAY_ID_INVALID;\n    if (anjay_server_object_add_instance(anjay, &server_instance,\n                                         &server_instance_id)) {\n        return -1;\n    }\n\n    return 0;\n}\n\nint main(int argc, char *argv[]) {\n    if (argc != 2) {\n        avs_log(tutorial, ERROR, \"usage: %s ENDPOINT_NAME\", argv[0]);\n        return -1;\n    }\n\n    const anjay_configuration_t CONFIG = {\n        .endpoint_name = argv[1],\n        .in_buffer_size = 4000,\n        .out_buffer_size = 4000,\n        .msg_cache_size = 4000\n    };\n\n    anjay_t *anjay = anjay_new(&CONFIG);\n    if (!anjay) {\n        avs_log(tutorial, ERROR, \"Could not create Anjay object\");\n        return -1;\n    }\n\n    int result = 0;\n    // Setup necessary objects\n    if (setup_security_object(anjay) || setup_server_object(anjay)) {\n        result = -1;\n    }\n\n    if (!result) {\n        result = anjay_event_loop_run(\n                anjay, avs_time_duration_from_scalar(1, AVS_TIME_S));\n    }\n\n    anjay_delete(anjay);\n    return result;\n}\n"
  },
  {
    "path": "examples/custom-tls/certificates-basic/src/net_impl.c",
    "content": "#include <inttypes.h>\n\n#include <netdb.h>\n#include <poll.h>\n#include <unistd.h>\n\n#include <arpa/inet.h>\n\n#include <sys/socket.h>\n#include <sys/types.h>\n\n#include <avsystem/commons/avs_socket_v_table.h>\n#include <avsystem/commons/avs_utils.h>\n\n#ifdef AVS_COMMONS_NET_WITH_POSIX_AVS_SOCKET\n#    error \"Custom implementation of the network layer conflicts with AVS_COMMONS_NET_WITH_POSIX_AVS_SOCKET\"\n#endif // AVS_COMMONS_NET_WITH_POSIX_AVS_SOCKET\n\navs_error_t _avs_net_initialize_global_compat_state(void);\nvoid _avs_net_cleanup_global_compat_state(void);\navs_error_t _avs_net_create_tcp_socket(avs_net_socket_t **socket,\n                                       const void *socket_configuration);\navs_error_t _avs_net_create_udp_socket(avs_net_socket_t **socket,\n                                       const void *socket_configuration);\n\navs_error_t _avs_net_initialize_global_compat_state(void) {\n    return AVS_OK;\n}\n\nvoid _avs_net_cleanup_global_compat_state(void) {}\n\ntypedef struct {\n    const avs_net_socket_v_table_t *operations;\n    int socktype;\n    int fd;\n    avs_time_duration_t recv_timeout;\n    char remote_hostname[256];\n    bool shut_down;\n    size_t bytes_sent;\n    size_t bytes_received;\n    avs_net_resolved_endpoint_t *preferred_endpoint;\n} net_socket_impl_t;\n\ntypedef union {\n    struct sockaddr addr;\n    struct sockaddr_in in;\n    struct sockaddr_in6 in6;\n    struct sockaddr_storage storage;\n} sockaddr_union_t;\n\nstatic avs_error_t\nnet_connect(avs_net_socket_t *sock_, const char *host, const char *port) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    struct addrinfo hints = {\n        .ai_socktype = sock->socktype\n    };\n    if (sock->fd >= 0) {\n        getsockopt(sock->fd, SOL_SOCKET, SO_DOMAIN, &hints.ai_family,\n                   &(socklen_t) { sizeof(hints.ai_family) });\n    }\n    struct addrinfo *addrs = NULL;\n    avs_error_t err = AVS_OK;\n    if (getaddrinfo(host, port, &hints, &addrs) || !addrs) {\n        err = avs_errno(AVS_EADDRNOTAVAIL);\n    } else if (sock->fd < 0\n               && (sock->fd = socket(addrs->ai_family, addrs->ai_socktype,\n                                     addrs->ai_protocol))\n                          < 0) {\n        err = avs_errno(AVS_UNKNOWN_ERROR);\n    } else {\n        const struct addrinfo *addr = addrs;\n        if (sock->preferred_endpoint\n                && sock->preferred_endpoint->size == sizeof(sockaddr_union_t)) {\n            while (addr) {\n                if (addr->ai_addrlen <= sizeof(sockaddr_union_t)\n                        && memcmp(addr->ai_addr,\n                                  sock->preferred_endpoint->data.buf,\n                                  addr->ai_addrlen)\n                                       == 0) {\n                    break;\n                }\n                addr = addr->ai_next;\n            }\n        }\n        if (!addr) {\n            // Preferred endpoint not found, use the first one\n            addr = addrs;\n        }\n        if (connect(sock->fd, addr->ai_addr, addr->ai_addrlen)) {\n            err = avs_errno(AVS_ECONNREFUSED);\n        }\n        if (sock->preferred_endpoint && avs_is_ok(err)) {\n            assert(addr->ai_addrlen <= sizeof(sockaddr_union_t));\n            memcpy(sock->preferred_endpoint->data.buf, addr->ai_addr,\n                   addr->ai_addrlen);\n            sock->preferred_endpoint->size = sizeof(sockaddr_union_t);\n        }\n    }\n    if (avs_is_ok(err)) {\n        sock->shut_down = false;\n        snprintf(sock->remote_hostname, sizeof(sock->remote_hostname), \"%s\",\n                 host);\n    }\n    freeaddrinfo(addrs);\n    return err;\n}\n\nstatic avs_error_t\nnet_send(avs_net_socket_t *sock_, const void *buffer, size_t buffer_length) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    ssize_t written = send(sock->fd, buffer, buffer_length, MSG_NOSIGNAL);\n    if (written >= 0) {\n        sock->bytes_sent += (size_t) written;\n        if ((size_t) written == buffer_length) {\n            return AVS_OK;\n        }\n    }\n    return avs_errno(AVS_EIO);\n}\n\nstatic avs_error_t net_receive(avs_net_socket_t *sock_,\n                               size_t *out_bytes_received,\n                               void *buffer,\n                               size_t buffer_length) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    struct pollfd pfd = {\n        .fd = sock->fd,\n        .events = POLLIN\n    };\n    int64_t timeout_ms;\n    if (avs_time_duration_to_scalar(&timeout_ms, AVS_TIME_MS,\n                                    sock->recv_timeout)) {\n        timeout_ms = -1;\n    } else if (timeout_ms < 0) {\n        timeout_ms = 0;\n    }\n    if (poll(&pfd, 1, (int) timeout_ms) == 0) {\n        return avs_errno(AVS_ETIMEDOUT);\n    }\n    ssize_t bytes_received = read(sock->fd, buffer, buffer_length);\n    if (bytes_received < 0) {\n        return avs_errno(AVS_EIO);\n    }\n    *out_bytes_received = (size_t) bytes_received;\n    sock->bytes_received += (size_t) bytes_received;\n    if (buffer_length > 0 && sock->socktype == SOCK_DGRAM\n            && (size_t) bytes_received == buffer_length) {\n        return avs_errno(AVS_EMSGSIZE);\n    }\n    return AVS_OK;\n}\n\nstatic avs_error_t\nnet_bind(avs_net_socket_t *sock_, const char *address, const char *port) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    struct addrinfo hints = {\n        .ai_flags = AI_PASSIVE,\n        .ai_socktype = sock->socktype\n    };\n    if (sock->fd >= 0) {\n        getsockopt(sock->fd, SOL_SOCKET, SO_DOMAIN, &hints.ai_family,\n                   &(socklen_t) { sizeof(hints.ai_family) });\n    }\n    struct addrinfo *addr = NULL;\n    avs_error_t err = AVS_OK;\n    if (getaddrinfo(address, port, &hints, &addr) || !addr) {\n        err = avs_errno(AVS_EADDRNOTAVAIL);\n    } else if ((sock->fd < 0\n                && (sock->fd = socket(addr->ai_family, addr->ai_socktype,\n                                      addr->ai_protocol))\n                           < 0)\n               || setsockopt(sock->fd, SOL_SOCKET, SO_REUSEADDR, &(int) { 1 },\n                             sizeof(int))) {\n        err = avs_errno(AVS_UNKNOWN_ERROR);\n    } else if (bind(sock->fd, addr->ai_addr, addr->ai_addrlen)) {\n        err = avs_errno(AVS_ECONNREFUSED);\n    } else {\n        sock->shut_down = false;\n    }\n    if (avs_is_err(err) && sock->fd >= 0) {\n        close(sock->fd);\n        sock->fd = -1;\n    }\n    freeaddrinfo(addr);\n    return err;\n}\n\nstatic avs_error_t net_close(avs_net_socket_t *sock_) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    avs_error_t err = AVS_OK;\n    if (sock->fd >= 0) {\n        if (close(sock->fd)) {\n            err = avs_errno(AVS_EIO);\n        }\n        sock->fd = -1;\n        sock->shut_down = false;\n    }\n    return err;\n}\n\nstatic avs_error_t net_shutdown(avs_net_socket_t *sock_) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    avs_error_t err = avs_errno(AVS_EBADF);\n    if (sock->fd >= 0) {\n        err = shutdown(sock->fd, SHUT_RDWR) ? avs_errno(AVS_EIO) : AVS_OK;\n        sock->shut_down = true;\n    }\n    return err;\n}\n\nstatic avs_error_t net_cleanup(avs_net_socket_t **sock_ptr) {\n    avs_error_t err = AVS_OK;\n    if (sock_ptr && *sock_ptr) {\n        err = net_close(*sock_ptr);\n        avs_free(*sock_ptr);\n        *sock_ptr = NULL;\n    }\n    return err;\n}\n\nstatic const void *net_system_socket(avs_net_socket_t *sock_) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    return &sock->fd;\n}\n\nstatic avs_error_t stringify_sockaddr_host(const sockaddr_union_t *addr,\n                                           char *out_buffer,\n                                           size_t out_buffer_size) {\n    if ((addr->in.sin_family == AF_INET\n         && inet_ntop(AF_INET, &addr->in.sin_addr, out_buffer,\n                      (socklen_t) out_buffer_size))\n            || (addr->in6.sin6_family == AF_INET6\n                && inet_ntop(AF_INET6, &addr->in6.sin6_addr, out_buffer,\n                             (socklen_t) out_buffer_size))) {\n        return AVS_OK;\n    }\n    return avs_errno(AVS_UNKNOWN_ERROR);\n}\n\nstatic avs_error_t stringify_sockaddr_port(const sockaddr_union_t *addr,\n                                           char *out_buffer,\n                                           size_t out_buffer_size) {\n    if ((addr->in.sin_family == AF_INET\n         && avs_simple_snprintf(out_buffer, out_buffer_size, \"%\" PRIu16,\n                                ntohs(addr->in.sin_port))\n                    >= 0)\n            || (addr->in6.sin6_family == AF_INET6\n                && avs_simple_snprintf(out_buffer, out_buffer_size, \"%\" PRIu16,\n                                       ntohs(addr->in6.sin6_port))\n                           >= 0)) {\n        return AVS_OK;\n    }\n    return avs_errno(AVS_UNKNOWN_ERROR);\n}\n\nstatic avs_error_t net_remote_host(avs_net_socket_t *sock_,\n                                   char *out_buffer,\n                                   size_t out_buffer_size) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    sockaddr_union_t addr;\n    if (getpeername(sock->fd, &addr.addr, &(socklen_t) { sizeof(addr) })) {\n        return avs_errno(AVS_UNKNOWN_ERROR);\n    }\n    return stringify_sockaddr_host(&addr, out_buffer, out_buffer_size);\n}\n\nstatic avs_error_t net_remote_hostname(avs_net_socket_t *sock_,\n                                       char *out_buffer,\n                                       size_t out_buffer_size) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    return avs_simple_snprintf(out_buffer, out_buffer_size, \"%s\",\n                               sock->remote_hostname)\n                           < 0\n                   ? avs_errno(AVS_UNKNOWN_ERROR)\n                   : AVS_OK;\n}\n\nstatic avs_error_t net_remote_port(avs_net_socket_t *sock_,\n                                   char *out_buffer,\n                                   size_t out_buffer_size) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    sockaddr_union_t addr;\n    if (getpeername(sock->fd, &addr.addr, &(socklen_t) { sizeof(addr) })) {\n        return avs_errno(AVS_UNKNOWN_ERROR);\n    }\n    return stringify_sockaddr_port(&addr, out_buffer, out_buffer_size);\n}\n\nstatic avs_error_t net_local_port(avs_net_socket_t *sock_,\n                                  char *out_buffer,\n                                  size_t out_buffer_size) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    sockaddr_union_t addr;\n    if (getsockname(sock->fd, &addr.addr, &(socklen_t) { sizeof(addr) })) {\n        return avs_errno(AVS_UNKNOWN_ERROR);\n    }\n    return stringify_sockaddr_port(&addr, out_buffer, out_buffer_size);\n}\n\nstatic avs_error_t net_get_opt(avs_net_socket_t *sock_,\n                               avs_net_socket_opt_key_t option_key,\n                               avs_net_socket_opt_value_t *out_option_value) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    switch (option_key) {\n    case AVS_NET_SOCKET_OPT_RECV_TIMEOUT:\n        out_option_value->recv_timeout = sock->recv_timeout;\n        return AVS_OK;\n    case AVS_NET_SOCKET_OPT_STATE:\n        if (sock->fd < 0) {\n            out_option_value->state = AVS_NET_SOCKET_STATE_CLOSED;\n        } else if (sock->shut_down) {\n            out_option_value->state = AVS_NET_SOCKET_STATE_SHUTDOWN;\n        } else {\n            sockaddr_union_t addr;\n            if (!getpeername(sock->fd, &addr.addr,\n                             &(socklen_t) { sizeof(addr) })\n                    && ((addr.in.sin_family == AF_INET && addr.in.sin_port != 0)\n                        || (addr.in6.sin6_family == AF_INET6\n                            && addr.in6.sin6_port != 0))) {\n                out_option_value->state = AVS_NET_SOCKET_STATE_CONNECTED;\n            } else {\n                out_option_value->state = AVS_NET_SOCKET_STATE_BOUND;\n            }\n        }\n        return AVS_OK;\n    case AVS_NET_SOCKET_OPT_INNER_MTU:\n        out_option_value->mtu = 1464;\n        return AVS_OK;\n    case AVS_NET_SOCKET_HAS_BUFFERED_DATA:\n        out_option_value->flag = false;\n        return AVS_OK;\n    case AVS_NET_SOCKET_OPT_BYTES_SENT:\n        out_option_value->bytes_sent = sock->bytes_sent;\n        return AVS_OK;\n    case AVS_NET_SOCKET_OPT_BYTES_RECEIVED:\n        out_option_value->bytes_received = sock->bytes_received;\n        return AVS_OK;\n    default:\n        return avs_errno(AVS_ENOTSUP);\n    }\n}\n\nstatic avs_error_t net_set_opt(avs_net_socket_t *sock_,\n                               avs_net_socket_opt_key_t option_key,\n                               avs_net_socket_opt_value_t option_value) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    switch (option_key) {\n    case AVS_NET_SOCKET_OPT_RECV_TIMEOUT:\n        sock->recv_timeout = option_value.recv_timeout;\n        return AVS_OK;\n    default:\n        return avs_errno(AVS_ENOTSUP);\n    }\n}\n\nstatic const avs_net_socket_v_table_t NET_SOCKET_VTABLE = {\n    .connect = net_connect,\n    .send = net_send,\n    .receive = net_receive,\n    .bind = net_bind,\n    .close = net_close,\n    .shutdown = net_shutdown,\n    .cleanup = net_cleanup,\n    .get_system_socket = net_system_socket,\n    .get_remote_host = net_remote_host,\n    .get_remote_hostname = net_remote_hostname,\n    .get_remote_port = net_remote_port,\n    .get_local_port = net_local_port,\n    .get_opt = net_get_opt,\n    .set_opt = net_set_opt\n};\n\nstatic avs_error_t\nnet_create_socket(avs_net_socket_t **socket_ptr,\n                  const avs_net_socket_configuration_t *configuration,\n                  int socktype) {\n    assert(socket_ptr);\n    assert(!*socket_ptr);\n    (void) configuration;\n    net_socket_impl_t *socket =\n            (net_socket_impl_t *) avs_calloc(1, sizeof(net_socket_impl_t));\n    if (!socket) {\n        return avs_errno(AVS_ENOMEM);\n    }\n    socket->operations = &NET_SOCKET_VTABLE;\n    socket->socktype = socktype;\n    socket->fd = -1;\n    socket->recv_timeout = avs_time_duration_from_scalar(30, AVS_TIME_S);\n    socket->preferred_endpoint = configuration->preferred_endpoint;\n    *socket_ptr = (avs_net_socket_t *) socket;\n    return AVS_OK;\n}\n\navs_error_t _avs_net_create_udp_socket(avs_net_socket_t **socket_ptr,\n                                       const void *configuration) {\n    return net_create_socket(\n            socket_ptr, (const avs_net_socket_configuration_t *) configuration,\n            SOCK_DGRAM);\n}\n\navs_error_t _avs_net_create_tcp_socket(avs_net_socket_t **socket_ptr,\n                                       const void *configuration) {\n    return net_create_socket(\n            socket_ptr, (const avs_net_socket_configuration_t *) configuration,\n            SOCK_STREAM);\n}\n\navs_error_t\navs_net_resolved_endpoint_get_host_port(const avs_net_resolved_endpoint_t *endp,\n                                        char *host,\n                                        size_t hostlen,\n                                        char *serv,\n                                        size_t servlen) {\n    AVS_STATIC_ASSERT(sizeof(endp->data.buf) >= sizeof(sockaddr_union_t),\n                      data_buffer_big_enough);\n    if (endp->size != sizeof(sockaddr_union_t)) {\n        return avs_errno(AVS_EINVAL);\n    }\n    const sockaddr_union_t *addr = (const sockaddr_union_t *) &endp->data.buf;\n    avs_error_t err = AVS_OK;\n    (void) ((host\n             && avs_is_err(\n                        (err = stringify_sockaddr_host(addr, host, hostlen))))\n            || (serv\n                && avs_is_err((err = stringify_sockaddr_port(addr, serv,\n                                                             servlen)))));\n    return err;\n}\n"
  },
  {
    "path": "examples/custom-tls/certificates-basic/src/tls_impl.c",
    "content": "#include <poll.h>\n\n#include <openssl/err.h>\n#include <openssl/ssl.h>\n\n#include <avsystem/commons/avs_crypto_common.h>\n#include <avsystem/commons/avs_socket_v_table.h>\n#include <avsystem/commons/avs_utils.h>\n\n#ifndef AVS_COMMONS_WITH_CUSTOM_TLS\n#    error \"Custom implementation of the TLS layer requires AVS_COMMONS_WITH_CUSTOM_TLS\"\n#endif // AVS_COMMONS_WITH_CUSTOM_TLS\n\navs_error_t _avs_net_initialize_global_ssl_state(void);\nvoid _avs_net_cleanup_global_ssl_state(void);\navs_error_t _avs_net_create_ssl_socket(avs_net_socket_t **socket,\n                                       const void *socket_configuration);\navs_error_t _avs_net_create_dtls_socket(avs_net_socket_t **socket,\n                                        const void *socket_configuration);\n\navs_error_t _avs_net_initialize_global_ssl_state(void) {\n    if (!OPENSSL_init_ssl(OPENSSL_INIT_ADD_ALL_CIPHERS\n                                  | OPENSSL_INIT_ADD_ALL_DIGESTS,\n                          NULL)) {\n        return avs_errno(AVS_EPROTO);\n    }\n    return AVS_OK;\n}\n\nvoid _avs_net_cleanup_global_ssl_state(void) {}\n\nvoid avs_crypto_clear_buffer(void *buf, size_t size) {\n    OPENSSL_cleanse(buf, size);\n}\n\ntypedef struct {\n    const avs_net_socket_v_table_t *operations;\n    avs_net_socket_t *backend_socket;\n    SSL_CTX *ctx;\n    SSL *ssl;\n\n    char psk[256];\n    size_t psk_size;\n    char identity[128];\n    size_t identity_size;\n\n    void *session_resumption_buffer;\n    size_t session_resumption_buffer_size;\n\n    char server_name_indication[256];\n    unsigned int dtls_hs_timeout_min_us;\n    unsigned int dtls_hs_timeout_max_us;\n} tls_socket_impl_t;\n\nstatic unsigned int dtls_timer_cb(SSL *s, unsigned int timer_us) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) SSL_get_app_data(s);\n    if (!timer_us) {\n        return sock->dtls_hs_timeout_min_us;\n    } else if (timer_us >= sock->dtls_hs_timeout_max_us) {\n        // maximum number of retransmissions reached, let's give up\n        avs_net_socket_shutdown(sock->backend_socket);\n        return 0;\n    } else {\n        timer_us *= 2;\n        if (timer_us > sock->dtls_hs_timeout_max_us) {\n            timer_us = sock->dtls_hs_timeout_max_us;\n        }\n        return timer_us;\n    }\n}\n\nstatic avs_error_t perform_handshake(tls_socket_impl_t *sock,\n                                     const char *host) {\n    union {\n        struct sockaddr addr;\n        struct sockaddr_storage storage;\n    } peername;\n    const void *fd_ptr = avs_net_socket_get_system(sock->backend_socket);\n    if (!fd_ptr\n            || getpeername(*(const int *) fd_ptr, &peername.addr,\n                           &(socklen_t) { sizeof(peername) })) {\n        return avs_errno(AVS_EBADF);\n    }\n\n    sock->ssl = SSL_new(sock->ctx);\n    if (!sock->ssl) {\n        return avs_errno(AVS_ENOMEM);\n    }\n\n    SSL_set_app_data(sock->ssl, sock);\n    SSL_set_tlsext_host_name(sock->ssl, host);\n    SSL_set1_host(sock->ssl, host);\n\n    BIO *bio = BIO_new_dgram(*(const int *) fd_ptr, 0);\n    if (!bio) {\n        return avs_errno(AVS_ENOMEM);\n    }\n    BIO_ctrl(bio, BIO_CTRL_DGRAM_SET_CONNECTED, 0, &peername.addr);\n    SSL_set_bio(sock->ssl, bio, bio);\n    DTLS_set_timer_cb(sock->ssl, dtls_timer_cb);\n\n    if (sock->session_resumption_buffer) {\n        const unsigned char *ptr =\n                (const unsigned char *) sock->session_resumption_buffer;\n        SSL_SESSION *session =\n                d2i_SSL_SESSION(NULL, &ptr,\n                                sock->session_resumption_buffer_size);\n        if (session) {\n            SSL_set_session(sock->ssl, session);\n            SSL_SESSION_free(session);\n        }\n    }\n\n    if (SSL_connect(sock->ssl) <= 0) {\n        return avs_errno(AVS_EPROTO);\n    }\n    return AVS_OK;\n}\n\nstatic avs_error_t\ntls_connect(avs_net_socket_t *sock_, const char *host, const char *port) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    if (sock->ssl) {\n        return avs_errno(AVS_EBADF);\n    }\n    avs_error_t err;\n    if (avs_is_err((\n                err = avs_net_socket_connect(sock->backend_socket, host, port)))\n            || avs_is_err((err = perform_handshake(\n                                   sock, sock->server_name_indication[0]\n                                                 ? sock->server_name_indication\n                                                 : host)))) {\n        if (sock->ssl) {\n            SSL_free(sock->ssl);\n            sock->ssl = NULL;\n        }\n        avs_net_socket_close(sock->backend_socket);\n    }\n    return err;\n}\n\nstatic avs_error_t\ntls_send(avs_net_socket_t *sock_, const void *buffer, size_t buffer_length) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    int result = SSL_write(sock->ssl, buffer, (int) buffer_length);\n    if (result < 0 || (size_t) result < buffer_length) {\n        return avs_errno(AVS_EPROTO);\n    }\n    return AVS_OK;\n}\n\nstatic avs_error_t tls_receive(avs_net_socket_t *sock_,\n                               size_t *out_bytes_received,\n                               void *buffer,\n                               size_t buffer_length) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    const void *fd_ptr = avs_net_socket_get_system(sock->backend_socket);\n    avs_net_socket_opt_value_t timeout;\n    if (!fd_ptr\n            || avs_is_err(avs_net_socket_get_opt(\n                       sock->backend_socket, AVS_NET_SOCKET_OPT_RECV_TIMEOUT,\n                       &timeout))) {\n        return avs_errno(AVS_EBADF);\n    }\n    struct pollfd pfd = {\n        .fd = *(const int *) fd_ptr,\n        .events = POLLIN\n    };\n    int64_t timeout_ms;\n    if (avs_time_duration_to_scalar(&timeout_ms, AVS_TIME_MS,\n                                    timeout.recv_timeout)) {\n        timeout_ms = -1;\n    } else if (timeout_ms < 0) {\n        timeout_ms = 0;\n    }\n    if (poll(&pfd, 1, (int) timeout_ms) == 0) {\n        return avs_errno(AVS_ETIMEDOUT);\n    }\n    int bytes_received = SSL_read(sock->ssl, buffer, (int) buffer_length);\n    if (bytes_received < 0) {\n        return avs_errno(AVS_EPROTO);\n    }\n    *out_bytes_received = (size_t) bytes_received;\n    if (buffer_length > 0 && (size_t) bytes_received == buffer_length) {\n        return avs_errno(AVS_EMSGSIZE);\n    }\n    return AVS_OK;\n}\n\nstatic avs_error_t\ntls_bind(avs_net_socket_t *sock_, const char *address, const char *port) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    return avs_net_socket_bind(sock->backend_socket, address, port);\n}\n\nstatic avs_error_t tls_close(avs_net_socket_t *sock_) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    if (sock->ssl) {\n        SSL_free(sock->ssl);\n        sock->ssl = NULL;\n    }\n    return avs_net_socket_close(sock->backend_socket);\n}\n\nstatic avs_error_t tls_shutdown(avs_net_socket_t *sock_) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    return avs_net_socket_shutdown(sock->backend_socket);\n}\n\nstatic avs_error_t tls_cleanup(avs_net_socket_t **sock_ptr) {\n    avs_error_t err = AVS_OK;\n    if (sock_ptr && *sock_ptr) {\n        tls_socket_impl_t *sock = (tls_socket_impl_t *) *sock_ptr;\n        tls_close(*sock_ptr);\n        avs_net_socket_cleanup(&sock->backend_socket);\n        if (sock->ctx) {\n            SSL_CTX_free(sock->ctx);\n        }\n        avs_free(sock);\n        *sock_ptr = NULL;\n    }\n    return err;\n}\n\nstatic const void *tls_system_socket(avs_net_socket_t *sock_) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    return avs_net_socket_get_system(sock->backend_socket);\n}\n\nstatic avs_error_t tls_remote_host(avs_net_socket_t *sock_,\n                                   char *out_buffer,\n                                   size_t out_buffer_size) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    return avs_net_socket_get_remote_host(sock->backend_socket, out_buffer,\n                                          out_buffer_size);\n}\n\nstatic avs_error_t tls_remote_hostname(avs_net_socket_t *sock_,\n                                       char *out_buffer,\n                                       size_t out_buffer_size) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    return avs_net_socket_get_remote_hostname(sock->backend_socket, out_buffer,\n                                              out_buffer_size);\n}\n\nstatic avs_error_t tls_remote_port(avs_net_socket_t *sock_,\n                                   char *out_buffer,\n                                   size_t out_buffer_size) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    return avs_net_socket_get_remote_port(sock->backend_socket, out_buffer,\n                                          out_buffer_size);\n}\n\nstatic avs_error_t tls_local_port(avs_net_socket_t *sock_,\n                                  char *out_buffer,\n                                  size_t out_buffer_size) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    return avs_net_socket_get_local_port(sock->backend_socket, out_buffer,\n                                         out_buffer_size);\n}\n\nstatic avs_error_t tls_get_opt(avs_net_socket_t *sock_,\n                               avs_net_socket_opt_key_t option_key,\n                               avs_net_socket_opt_value_t *out_option_value) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    switch (option_key) {\n    case AVS_NET_SOCKET_OPT_INNER_MTU: {\n        avs_error_t err = avs_net_socket_get_opt(sock->backend_socket,\n                                                 AVS_NET_SOCKET_OPT_INNER_MTU,\n                                                 out_option_value);\n        if (avs_is_ok(err)) {\n            out_option_value->mtu = AVS_MAX(out_option_value->mtu - 64, 0);\n        }\n        return err;\n    }\n    case AVS_NET_SOCKET_HAS_BUFFERED_DATA:\n        out_option_value->flag = (sock->ssl && SSL_pending(sock->ssl) > 0);\n        return AVS_OK;\n    case AVS_NET_SOCKET_OPT_SESSION_RESUMED:\n        out_option_value->flag = (sock->ssl && SSL_session_reused(sock->ssl));\n        return AVS_OK;\n    default:\n        return avs_net_socket_get_opt(sock->backend_socket, option_key,\n                                      out_option_value);\n    }\n}\n\nstatic avs_error_t tls_set_opt(avs_net_socket_t *sock_,\n                               avs_net_socket_opt_key_t option_key,\n                               avs_net_socket_opt_value_t option_value) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    return avs_net_socket_set_opt(sock->backend_socket, option_key,\n                                  option_value);\n}\n\nstatic const avs_net_socket_v_table_t TLS_SOCKET_VTABLE = {\n    .connect = tls_connect,\n    .send = tls_send,\n    .receive = tls_receive,\n    .bind = tls_bind,\n    .close = tls_close,\n    .shutdown = tls_shutdown,\n    .cleanup = tls_cleanup,\n    .get_system_socket = tls_system_socket,\n    .get_remote_host = tls_remote_host,\n    .get_remote_hostname = tls_remote_hostname,\n    .get_remote_port = tls_remote_port,\n    .get_local_port = tls_local_port,\n    .get_opt = tls_get_opt,\n    .set_opt = tls_set_opt\n};\n\nstatic avs_error_t configure_dtls_version(tls_socket_impl_t *sock,\n                                          avs_net_ssl_version_t version) {\n    switch (version) {\n    case AVS_NET_SSL_VERSION_DEFAULT:\n        return AVS_OK;\n    case AVS_NET_SSL_VERSION_TLSv1:\n    case AVS_NET_SSL_VERSION_TLSv1_1:\n        SSL_CTX_set_min_proto_version(sock->ctx, DTLS1_VERSION);\n        return AVS_OK;\n    case AVS_NET_SSL_VERSION_TLSv1_2:\n        SSL_CTX_set_min_proto_version(sock->ctx, DTLS1_2_VERSION);\n        return AVS_OK;\n    default:\n        return avs_errno(AVS_ENOTSUP);\n    }\n}\n\nstatic unsigned int psk_client_cb(SSL *ssl,\n                                  const char *hint,\n                                  char *identity,\n                                  unsigned int max_identity_len,\n                                  unsigned char *psk,\n                                  unsigned int max_psk_len) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) SSL_get_app_data(ssl);\n\n    (void) hint;\n\n    if (!sock || max_psk_len < sock->psk_size\n            || max_identity_len < sock->identity_size + 1) {\n        return 0;\n    }\n\n    memcpy(psk, sock->psk, sock->psk_size);\n    memcpy(identity, sock->identity, sock->identity_size);\n    identity[sock->identity_size] = '\\0';\n\n    return (unsigned int) sock->psk_size;\n}\n\nstatic avs_error_t configure_psk(tls_socket_impl_t *sock,\n                                 const avs_net_psk_info_t *psk) {\n    if (psk->key.desc.source != AVS_CRYPTO_DATA_SOURCE_BUFFER\n            || psk->identity.desc.source != AVS_CRYPTO_DATA_SOURCE_BUFFER) {\n        return avs_errno(AVS_EINVAL);\n    }\n\n    const void *key_ptr = psk->key.desc.info.buffer.buffer;\n    size_t key_size = psk->key.desc.info.buffer.buffer_size;\n\n    const void *identity_ptr = psk->identity.desc.info.buffer.buffer;\n    size_t identity_size = psk->identity.desc.info.buffer.buffer_size;\n\n    if (key_size > sizeof(sock->psk)\n            || identity_size > sizeof(sock->identity)) {\n        return avs_errno(AVS_EINVAL);\n    }\n    memcpy(sock->psk, key_ptr, key_size);\n    sock->psk_size = key_size;\n    memcpy(sock->identity, identity_ptr, identity_size);\n    sock->identity_size = identity_size;\n    SSL_CTX_set_cipher_list(sock->ctx, \"PSK\");\n    SSL_CTX_set_psk_client_callback(sock->ctx, psk_client_cb);\n    SSL_CTX_set_verify(sock->ctx, SSL_VERIFY_PEER, NULL);\n    return AVS_OK;\n}\n\nstatic avs_error_t\nconfigure_trusted_certs(X509_STORE *store,\n                        const avs_crypto_security_info_union_t *trusted_certs) {\n    if (!trusted_certs) {\n        return avs_errno(AVS_EINVAL);\n    }\n    switch (trusted_certs->source) {\n    case AVS_CRYPTO_DATA_SOURCE_EMPTY:\n        return AVS_OK;\n    case AVS_CRYPTO_DATA_SOURCE_BUFFER: {\n        const unsigned char *ptr =\n                (const unsigned char *) trusted_certs->info.buffer.buffer;\n        X509 *cert = d2i_X509(NULL, &ptr,\n                              (long) trusted_certs->info.buffer.buffer_size);\n        if (!cert) {\n            return avs_errno(AVS_EPROTO);\n        }\n\n        ERR_clear_error();\n        int result = X509_STORE_add_cert(store, cert);\n        X509_free(cert);\n        if (!result\n                && ERR_GET_REASON(ERR_get_error())\n                               != X509_R_CERT_ALREADY_IN_HASH_TABLE) {\n            return avs_errno(AVS_EPROTO);\n        }\n        return AVS_OK;\n    }\n    case AVS_CRYPTO_DATA_SOURCE_ARRAY: {\n        avs_error_t err = AVS_OK;\n        for (size_t i = 0;\n             avs_is_ok(err) && i < trusted_certs->info.array.element_count;\n             ++i) {\n            err = configure_trusted_certs(\n                    store, &trusted_certs->info.array.array_ptr[i]);\n        }\n        return err;\n    }\n    case AVS_CRYPTO_DATA_SOURCE_LIST: {\n        avs_error_t err = AVS_OK;\n        AVS_LIST(avs_crypto_security_info_union_t) entry;\n        AVS_LIST_FOREACH(entry, trusted_certs->info.list.list_head) {\n            if (avs_is_err((err = configure_trusted_certs(store, entry)))) {\n                break;\n            }\n        }\n        return AVS_OK;\n    }\n    default:\n        return avs_errno(AVS_ENOTSUP);\n    }\n}\n\nstatic avs_error_t configure_cert_revocation_lists(\n        X509_STORE *store,\n        const avs_crypto_security_info_union_t *cert_revocation_lists) {\n    if (!cert_revocation_lists) {\n        return avs_errno(AVS_EINVAL);\n    }\n    switch (cert_revocation_lists->source) {\n    case AVS_CRYPTO_DATA_SOURCE_EMPTY:\n        return AVS_OK;\n    case AVS_CRYPTO_DATA_SOURCE_BUFFER: {\n        const unsigned char *ptr =\n                (const unsigned char *)\n                        cert_revocation_lists->info.buffer.buffer;\n        X509_CRL *crl = d2i_X509_CRL(\n                NULL, &ptr,\n                (long) cert_revocation_lists->info.buffer.buffer_size);\n        if (!crl) {\n            return avs_errno(AVS_EPROTO);\n        }\n\n        ERR_clear_error();\n        int result = X509_STORE_add_crl(store, crl);\n        X509_CRL_free(crl);\n        if (result != 1) {\n            return avs_errno(AVS_EPROTO);\n        }\n        return AVS_OK;\n    }\n    case AVS_CRYPTO_DATA_SOURCE_ARRAY: {\n        avs_error_t err = AVS_OK;\n        for (size_t i = 0;\n             avs_is_ok(err)\n             && i < cert_revocation_lists->info.array.element_count;\n             ++i) {\n            err = configure_cert_revocation_lists(\n                    store, &cert_revocation_lists->info.array.array_ptr[i]);\n        }\n        return err;\n    }\n    case AVS_CRYPTO_DATA_SOURCE_LIST: {\n        avs_error_t err = AVS_OK;\n        AVS_LIST(avs_crypto_security_info_union_t) entry;\n        AVS_LIST_FOREACH(entry, cert_revocation_lists->info.list.list_head) {\n            if (avs_is_err((\n                        err = configure_cert_revocation_lists(store, entry)))) {\n                break;\n            }\n        }\n        return AVS_OK;\n    }\n    default:\n        return avs_errno(AVS_ENOTSUP);\n    }\n}\n\nstatic avs_error_t\nconfigure_client_cert(SSL_CTX *ctx,\n                      const avs_crypto_certificate_chain_info_t *client_cert) {\n    switch (client_cert->desc.source) {\n    case AVS_CRYPTO_DATA_SOURCE_BUFFER: {\n        const unsigned char *ptr =\n                (const unsigned char *) client_cert->desc.info.buffer.buffer;\n        X509 *cert = d2i_X509(NULL, &ptr,\n                              (long) client_cert->desc.info.buffer.buffer_size);\n        if (!cert) {\n            return avs_errno(AVS_EPROTO);\n        }\n\n        int result = SSL_CTX_use_certificate(ctx, cert);\n        X509_free(cert);\n        if (result != 1) {\n            return avs_errno(AVS_EPROTO);\n        }\n        return AVS_OK;\n    }\n    default:\n        return avs_errno(AVS_ENOTSUP);\n    }\n}\n\nstatic avs_error_t\nconfigure_client_key(SSL_CTX *ctx,\n                     const avs_crypto_private_key_info_t *client_key) {\n    switch (client_key->desc.source) {\n    case AVS_CRYPTO_DATA_SOURCE_BUFFER: {\n        if (client_key->desc.info.buffer.password) {\n            return avs_errno(AVS_ENOTSUP);\n        }\n        const unsigned char *ptr =\n                (const unsigned char *) client_key->desc.info.buffer.buffer;\n        EVP_PKEY *key = d2i_AutoPrivateKey(\n                NULL, &ptr, (long) client_key->desc.info.buffer.buffer_size);\n        if (!key) {\n            return avs_errno(AVS_EPROTO);\n        }\n\n        int result = SSL_CTX_use_PrivateKey(ctx, key);\n        EVP_PKEY_free(key);\n        if (result != 1) {\n            return avs_errno(AVS_EPROTO);\n        }\n        return AVS_OK;\n    }\n    default:\n        return avs_errno(AVS_ENOTSUP);\n    }\n}\n\nstatic avs_error_t configure_certs(tls_socket_impl_t *sock,\n                                   const avs_net_certificate_info_t *certs) {\n    if (certs->server_cert_validation) {\n        if (!certs->ignore_system_trust_store) {\n            SSL_CTX_set_default_verify_paths(sock->ctx);\n        }\n        X509_STORE *store = SSL_CTX_get_cert_store(sock->ctx);\n        avs_error_t err;\n        if (avs_is_err((err = configure_trusted_certs(\n                                store, &certs->trusted_certs.desc)))\n                || avs_is_err((err = configure_cert_revocation_lists(\n                                       store,\n                                       &certs->cert_revocation_lists.desc)))) {\n            return err;\n        }\n        SSL_CTX_set_verify(sock->ctx, SSL_VERIFY_PEER, NULL);\n    } else {\n        SSL_CTX_set_verify(sock->ctx, SSL_VERIFY_NONE, NULL);\n    }\n\n    if (certs->client_cert.desc.source != AVS_CRYPTO_DATA_SOURCE_EMPTY) {\n        avs_error_t err;\n        if (avs_is_err((err = configure_client_cert(sock->ctx,\n                                                    &certs->client_cert)))\n                || avs_is_err(err = configure_client_key(sock->ctx,\n                                                         &certs->client_key))) {\n            return err;\n        }\n    }\n\n    return AVS_OK;\n}\n\nstatic avs_error_t configure_dtls_handshake_timeouts(\n        tls_socket_impl_t *sock,\n        const avs_net_dtls_handshake_timeouts_t *dtls_handshake_timeouts) {\n    uint64_t min_us = 1000000, max_us = 60000000;\n    if (dtls_handshake_timeouts) {\n        avs_time_duration_to_scalar(&min_us, AVS_TIME_US,\n                                    dtls_handshake_timeouts->min);\n        avs_time_duration_to_scalar(&max_us, AVS_TIME_US,\n                                    dtls_handshake_timeouts->max);\n    }\n    sock->dtls_hs_timeout_min_us = (unsigned int) min_us;\n    sock->dtls_hs_timeout_max_us = (unsigned int) max_us;\n    return AVS_OK;\n}\n\nstatic avs_error_t\nconfigure_ciphersuites(tls_socket_impl_t *sock,\n                       const avs_net_socket_tls_ciphersuites_t *ciphersuites) {\n    if (!ciphersuites->num_ids) {\n        return AVS_OK;\n    }\n    SSL *dummy_ssl = SSL_new(sock->ctx);\n    if (!dummy_ssl) {\n        return avs_errno(AVS_ENOMEM);\n    }\n    char cipher_list[1024] = \"-ALL\";\n    char *cipher_list_ptr = cipher_list + strlen(cipher_list);\n    const char *const cipher_list_end = cipher_list + sizeof(cipher_list);\n    for (size_t i = 0; i < ciphersuites->num_ids; ++i) {\n        unsigned char id_as_chars[] = {\n            (unsigned char) (ciphersuites->ids[i] >> 8),\n            (unsigned char) (ciphersuites->ids[i] & 0xFF)\n        };\n        const SSL_CIPHER *cipher = SSL_CIPHER_find(dummy_ssl, id_as_chars);\n        if (cipher) {\n            const char *name = SSL_CIPHER_get_name(cipher);\n            if (!!strstr(name, \"PSK\") == !!sock->psk_size\n                    && cipher_list_ptr + 1 + strlen(name) < cipher_list_end) {\n                *cipher_list_ptr++ = ':';\n                strcpy(cipher_list_ptr, name);\n                cipher_list_ptr += strlen(name);\n            }\n        }\n    }\n    SSL_free(dummy_ssl);\n    SSL_CTX_set_cipher_list(sock->ctx, cipher_list);\n    // NOTE: Configuring the set of supported new-style ciphersuites as defined\n    // for TLS 1.3 are not supported by this function.\n    return AVS_OK;\n}\n\nstatic avs_error_t configure_sni(tls_socket_impl_t *sock,\n                                 const char *server_name_indication) {\n    if (server_name_indication) {\n        if (strlen(server_name_indication)\n                >= sizeof(sock->server_name_indication)) {\n            return avs_errno(AVS_ENOBUFS);\n        }\n        strcpy(sock->server_name_indication, server_name_indication);\n    }\n    return AVS_OK;\n}\n\nstatic int new_session_cb(SSL *ssl, SSL_SESSION *sess) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) SSL_get_app_data(ssl);\n    int serialized_size = i2d_SSL_SESSION(sess, NULL);\n    if (serialized_size > 0\n            && (size_t) serialized_size\n                           <= sock->session_resumption_buffer_size) {\n        unsigned char *ptr = (unsigned char *) sock->session_resumption_buffer;\n        i2d_SSL_SESSION(sess, &ptr);\n    }\n    return 0;\n}\n\navs_error_t _avs_net_create_dtls_socket(avs_net_socket_t **socket_ptr,\n                                        const void *configuration_) {\n    assert(socket_ptr);\n    assert(!*socket_ptr);\n    assert(configuration_);\n    const avs_net_ssl_configuration_t *configuration =\n            (const avs_net_ssl_configuration_t *) configuration_;\n    tls_socket_impl_t *socket =\n            (tls_socket_impl_t *) avs_calloc(1, sizeof(tls_socket_impl_t));\n    if (!socket) {\n        return avs_errno(AVS_ENOMEM);\n    }\n    *socket_ptr = (avs_net_socket_t *) socket;\n    socket->operations = &TLS_SOCKET_VTABLE;\n\n    avs_error_t err = AVS_OK;\n    if (avs_is_ok((err = avs_net_udp_socket_create(\n                           &socket->backend_socket,\n                           &configuration->backend_configuration)))\n            && !(socket->ctx = SSL_CTX_new(DTLS_method()))) {\n        err = avs_errno(AVS_ENOMEM);\n    }\n    if (avs_is_ok(err)) {\n        err = configure_dtls_version(socket, configuration->version);\n    }\n    if (avs_is_ok(err)) {\n        switch (configuration->security.mode) {\n        case AVS_NET_SECURITY_PSK:\n            err = configure_psk(socket, &configuration->security.data.psk);\n            break;\n        case AVS_NET_SECURITY_CERTIFICATE:\n            err = configure_certs(socket, &configuration->security.data.cert);\n            break;\n        default:\n            err = avs_errno(AVS_ENOTSUP);\n        }\n    }\n    if (avs_is_err(err)\n            || avs_is_err((\n                       err = configure_dtls_handshake_timeouts(\n                               socket, configuration->dtls_handshake_timeouts)))\n            || avs_is_err((err = configure_ciphersuites(\n                                   socket, &configuration->ciphersuites)))\n            || avs_is_err((err = configure_sni(\n                                   socket,\n                                   configuration->server_name_indication)))) {\n        avs_net_socket_cleanup(socket_ptr);\n        return err;\n    }\n    SSL_CTX_set_mode(socket->ctx, SSL_MODE_AUTO_RETRY);\n    if (configuration->session_resumption_buffer_size > 0) {\n        assert(configuration->session_resumption_buffer);\n        socket->session_resumption_buffer =\n                configuration->session_resumption_buffer;\n        socket->session_resumption_buffer_size =\n                configuration->session_resumption_buffer_size;\n        SSL_CTX_set_session_cache_mode(\n                socket->ctx,\n                SSL_SESS_CACHE_CLIENT | SSL_SESS_CACHE_NO_INTERNAL_STORE);\n        SSL_CTX_sess_set_new_cb(socket->ctx, new_session_cb);\n    }\n    return AVS_OK;\n}\n\navs_error_t _avs_net_create_ssl_socket(avs_net_socket_t **socket_ptr,\n                                       const void *configuration) {\n    return avs_errno(AVS_ENOTSUP);\n}\n"
  },
  {
    "path": "examples/custom-tls/config-features/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.16)\nproject(custom-tls-config-features C)\n\nset(CMAKE_C_STANDARD 99)\n\nfind_package(anjay REQUIRED)\nfind_package(OpenSSL REQUIRED)\n\nadd_executable(${PROJECT_NAME}\n               src/main.c\n               src/net_impl.c\n               src/tls_impl.c)\ntarget_link_libraries(${PROJECT_NAME} PRIVATE anjay OpenSSL::SSL)\n"
  },
  {
    "path": "examples/custom-tls/config-features/src/main.c",
    "content": "#include <anjay/anjay.h>\n#include <anjay/security.h>\n#include <anjay/server.h>\n#include <avsystem/commons/avs_log.h>\n\n#include <poll.h>\n\nint main_loop(anjay_t *anjay) {\n    while (true) {\n        // Obtain all network data sources\n        AVS_LIST(avs_net_socket_t *const) sockets = anjay_get_sockets(anjay);\n\n        // Prepare to poll() on them\n        size_t numsocks = AVS_LIST_SIZE(sockets);\n        struct pollfd pollfds[numsocks];\n        size_t i = 0;\n        AVS_LIST(avs_net_socket_t *const) sock;\n        AVS_LIST_FOREACH(sock, sockets) {\n            pollfds[i].fd = *(const int *) avs_net_socket_get_system(*sock);\n            pollfds[i].events = POLLIN;\n            pollfds[i].revents = 0;\n            ++i;\n        }\n\n        const int max_wait_time_ms = 1000;\n        // Determine the expected time to the next job in milliseconds.\n        // If there is no job we will wait till something arrives for\n        // at most 1 second (i.e. max_wait_time_ms).\n        int wait_ms =\n                anjay_sched_calculate_wait_time_ms(anjay, max_wait_time_ms);\n\n        // Wait for the events if necessary, and handle them.\n        if (poll(pollfds, numsocks, wait_ms) > 0) {\n            int socket_id = 0;\n            AVS_LIST(avs_net_socket_t *const) socket = NULL;\n            AVS_LIST_FOREACH(socket, sockets) {\n                if (pollfds[socket_id].revents) {\n                    if (anjay_serve(anjay, *socket)) {\n                        avs_log(tutorial, ERROR, \"anjay_serve failed\");\n                    }\n                }\n                ++socket_id;\n            }\n        }\n\n        // Finally run the scheduler\n        anjay_sched_run(anjay);\n    }\n    return 0;\n}\n\n// Installs Security Object and adds an instance of it.\n// An instance of Security Object provides information needed to connect to\n// LwM2M server.\nstatic int setup_security_object(anjay_t *anjay) {\n    if (anjay_security_object_install(anjay)) {\n        return -1;\n    }\n\n    static const char PSK_IDENTITY[] = \"identity\";\n    static const char PSK_KEY[] = \"P4s$w0rd\";\n\n    anjay_security_instance_t security_instance = {\n        .ssid = 1,\n        .server_uri = \"coaps://eu.iot.avsystem.cloud:5684\",\n        .security_mode = ANJAY_SECURITY_PSK,\n        .public_cert_or_psk_identity = (const uint8_t *) PSK_IDENTITY,\n        .public_cert_or_psk_identity_size = strlen(PSK_IDENTITY),\n        .private_cert_or_psk_key = (const uint8_t *) PSK_KEY,\n        .private_cert_or_psk_key_size = strlen(PSK_KEY)\n    };\n\n    // Anjay will assign Instance ID automatically\n    anjay_iid_t security_instance_id = ANJAY_ID_INVALID;\n    if (anjay_security_object_add_instance(anjay, &security_instance,\n                                           &security_instance_id)) {\n        return -1;\n    }\n\n    return 0;\n}\n\n// Installs Server Object and adds an instance of it.\n// An instance of Server Object provides the data related to a LwM2M Server.\nstatic int setup_server_object(anjay_t *anjay) {\n    if (anjay_server_object_install(anjay)) {\n        return -1;\n    }\n\n    const anjay_server_instance_t server_instance = {\n        // Server Short ID\n        .ssid = 1,\n        // Client will send Update message often than every 60 seconds\n        .lifetime = 60,\n        // Disable Default Minimum Period resource\n        .default_min_period = -1,\n        // Disable Default Maximum Period resource\n        .default_max_period = -1,\n        // Disable Disable Timeout resource\n        .disable_timeout = -1,\n        // Sets preferred transport to UDP\n        .binding = \"U\"\n    };\n\n    // Anjay will assign Instance ID automatically\n    anjay_iid_t server_instance_id = ANJAY_ID_INVALID;\n    if (anjay_server_object_add_instance(anjay, &server_instance,\n                                         &server_instance_id)) {\n        return -1;\n    }\n\n    return 0;\n}\n\nint main(int argc, char *argv[]) {\n    if (argc != 2) {\n        avs_log(tutorial, ERROR, \"usage: %s ENDPOINT_NAME\", argv[0]);\n        return -1;\n    }\n\n    const anjay_configuration_t CONFIG = {\n        .endpoint_name = argv[1],\n        .in_buffer_size = 4000,\n        .out_buffer_size = 4000,\n        .msg_cache_size = 4000\n    };\n\n    anjay_t *anjay = anjay_new(&CONFIG);\n    if (!anjay) {\n        avs_log(tutorial, ERROR, \"Could not create Anjay object\");\n        return -1;\n    }\n\n    int result = 0;\n    // Setup necessary objects\n    if (setup_security_object(anjay) || setup_server_object(anjay)) {\n        result = -1;\n    }\n\n    if (!result) {\n        result = main_loop(anjay);\n    }\n\n    anjay_delete(anjay);\n    return result;\n}\n"
  },
  {
    "path": "examples/custom-tls/config-features/src/net_impl.c",
    "content": "#include <inttypes.h>\n\n#include <netdb.h>\n#include <poll.h>\n#include <unistd.h>\n\n#include <arpa/inet.h>\n\n#include <sys/socket.h>\n#include <sys/types.h>\n\n#include <avsystem/commons/avs_socket_v_table.h>\n#include <avsystem/commons/avs_utils.h>\n\n#ifdef AVS_COMMONS_NET_WITH_POSIX_AVS_SOCKET\n#    error \"Custom implementation of the network layer conflicts with AVS_COMMONS_NET_WITH_POSIX_AVS_SOCKET\"\n#endif // AVS_COMMONS_NET_WITH_POSIX_AVS_SOCKET\n\navs_error_t _avs_net_initialize_global_compat_state(void);\nvoid _avs_net_cleanup_global_compat_state(void);\navs_error_t _avs_net_create_tcp_socket(avs_net_socket_t **socket,\n                                       const void *socket_configuration);\navs_error_t _avs_net_create_udp_socket(avs_net_socket_t **socket,\n                                       const void *socket_configuration);\n\navs_error_t _avs_net_initialize_global_compat_state(void) {\n    return AVS_OK;\n}\n\nvoid _avs_net_cleanup_global_compat_state(void) {}\n\ntypedef struct {\n    const avs_net_socket_v_table_t *operations;\n    int socktype;\n    int fd;\n    avs_time_duration_t recv_timeout;\n    char remote_hostname[256];\n    bool shut_down;\n    size_t bytes_sent;\n    size_t bytes_received;\n    avs_net_resolved_endpoint_t *preferred_endpoint;\n} net_socket_impl_t;\n\ntypedef union {\n    struct sockaddr addr;\n    struct sockaddr_in in;\n    struct sockaddr_in6 in6;\n    struct sockaddr_storage storage;\n} sockaddr_union_t;\n\nstatic avs_error_t\nnet_connect(avs_net_socket_t *sock_, const char *host, const char *port) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    struct addrinfo hints = {\n        .ai_socktype = sock->socktype\n    };\n    if (sock->fd >= 0) {\n        getsockopt(sock->fd, SOL_SOCKET, SO_DOMAIN, &hints.ai_family,\n                   &(socklen_t) { sizeof(hints.ai_family) });\n    }\n    struct addrinfo *addrs = NULL;\n    avs_error_t err = AVS_OK;\n    if (getaddrinfo(host, port, &hints, &addrs) || !addrs) {\n        err = avs_errno(AVS_EADDRNOTAVAIL);\n    } else if (sock->fd < 0\n               && (sock->fd = socket(addrs->ai_family, addrs->ai_socktype,\n                                     addrs->ai_protocol))\n                          < 0) {\n        err = avs_errno(AVS_UNKNOWN_ERROR);\n    } else {\n        const struct addrinfo *addr = addrs;\n        if (sock->preferred_endpoint\n                && sock->preferred_endpoint->size == sizeof(sockaddr_union_t)) {\n            while (addr) {\n                if (addr->ai_addrlen <= sizeof(sockaddr_union_t)\n                        && memcmp(addr->ai_addr,\n                                  sock->preferred_endpoint->data.buf,\n                                  addr->ai_addrlen)\n                                       == 0) {\n                    break;\n                }\n                addr = addr->ai_next;\n            }\n        }\n        if (!addr) {\n            // Preferred endpoint not found, use the first one\n            addr = addrs;\n        }\n        if (connect(sock->fd, addr->ai_addr, addr->ai_addrlen)) {\n            err = avs_errno(AVS_ECONNREFUSED);\n        }\n        if (sock->preferred_endpoint && avs_is_ok(err)) {\n            assert(addr->ai_addrlen <= sizeof(sockaddr_union_t));\n            memcpy(sock->preferred_endpoint->data.buf, addr->ai_addr,\n                   addr->ai_addrlen);\n            sock->preferred_endpoint->size = sizeof(sockaddr_union_t);\n        }\n    }\n    if (avs_is_ok(err)) {\n        sock->shut_down = false;\n        snprintf(sock->remote_hostname, sizeof(sock->remote_hostname), \"%s\",\n                 host);\n    }\n    freeaddrinfo(addrs);\n    return err;\n}\n\nstatic avs_error_t\nnet_send(avs_net_socket_t *sock_, const void *buffer, size_t buffer_length) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    ssize_t written = send(sock->fd, buffer, buffer_length, MSG_NOSIGNAL);\n    if (written >= 0) {\n        sock->bytes_sent += (size_t) written;\n        if ((size_t) written == buffer_length) {\n            return AVS_OK;\n        }\n    }\n    return avs_errno(AVS_EIO);\n}\n\nstatic avs_error_t net_receive(avs_net_socket_t *sock_,\n                               size_t *out_bytes_received,\n                               void *buffer,\n                               size_t buffer_length) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    struct pollfd pfd = {\n        .fd = sock->fd,\n        .events = POLLIN\n    };\n    int64_t timeout_ms;\n    if (avs_time_duration_to_scalar(&timeout_ms, AVS_TIME_MS,\n                                    sock->recv_timeout)) {\n        timeout_ms = -1;\n    } else if (timeout_ms < 0) {\n        timeout_ms = 0;\n    }\n    if (poll(&pfd, 1, (int) timeout_ms) == 0) {\n        return avs_errno(AVS_ETIMEDOUT);\n    }\n    ssize_t bytes_received = read(sock->fd, buffer, buffer_length);\n    if (bytes_received < 0) {\n        return avs_errno(AVS_EIO);\n    }\n    *out_bytes_received = (size_t) bytes_received;\n    sock->bytes_received += (size_t) bytes_received;\n    if (buffer_length > 0 && sock->socktype == SOCK_DGRAM\n            && (size_t) bytes_received == buffer_length) {\n        return avs_errno(AVS_EMSGSIZE);\n    }\n    return AVS_OK;\n}\n\nstatic avs_error_t\nnet_bind(avs_net_socket_t *sock_, const char *address, const char *port) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    struct addrinfo hints = {\n        .ai_flags = AI_PASSIVE,\n        .ai_socktype = sock->socktype\n    };\n    if (sock->fd >= 0) {\n        getsockopt(sock->fd, SOL_SOCKET, SO_DOMAIN, &hints.ai_family,\n                   &(socklen_t) { sizeof(hints.ai_family) });\n    }\n    struct addrinfo *addr = NULL;\n    avs_error_t err = AVS_OK;\n    if (getaddrinfo(address, port, &hints, &addr) || !addr) {\n        err = avs_errno(AVS_EADDRNOTAVAIL);\n    } else if ((sock->fd < 0\n                && (sock->fd = socket(addr->ai_family, addr->ai_socktype,\n                                      addr->ai_protocol))\n                           < 0)\n               || setsockopt(sock->fd, SOL_SOCKET, SO_REUSEADDR, &(int) { 1 },\n                             sizeof(int))) {\n        err = avs_errno(AVS_UNKNOWN_ERROR);\n    } else if (bind(sock->fd, addr->ai_addr, addr->ai_addrlen)) {\n        err = avs_errno(AVS_ECONNREFUSED);\n    } else {\n        sock->shut_down = false;\n    }\n    if (avs_is_err(err) && sock->fd >= 0) {\n        close(sock->fd);\n        sock->fd = -1;\n    }\n    freeaddrinfo(addr);\n    return err;\n}\n\nstatic avs_error_t net_close(avs_net_socket_t *sock_) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    avs_error_t err = AVS_OK;\n    if (sock->fd >= 0) {\n        if (close(sock->fd)) {\n            err = avs_errno(AVS_EIO);\n        }\n        sock->fd = -1;\n        sock->shut_down = false;\n    }\n    return err;\n}\n\nstatic avs_error_t net_shutdown(avs_net_socket_t *sock_) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    avs_error_t err = avs_errno(AVS_EBADF);\n    if (sock->fd >= 0) {\n        err = shutdown(sock->fd, SHUT_RDWR) ? avs_errno(AVS_EIO) : AVS_OK;\n        sock->shut_down = true;\n    }\n    return err;\n}\n\nstatic avs_error_t net_cleanup(avs_net_socket_t **sock_ptr) {\n    avs_error_t err = AVS_OK;\n    if (sock_ptr && *sock_ptr) {\n        err = net_close(*sock_ptr);\n        avs_free(*sock_ptr);\n        *sock_ptr = NULL;\n    }\n    return err;\n}\n\nstatic const void *net_system_socket(avs_net_socket_t *sock_) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    return &sock->fd;\n}\n\nstatic avs_error_t stringify_sockaddr_host(const sockaddr_union_t *addr,\n                                           char *out_buffer,\n                                           size_t out_buffer_size) {\n    if ((addr->in.sin_family == AF_INET\n         && inet_ntop(AF_INET, &addr->in.sin_addr, out_buffer,\n                      (socklen_t) out_buffer_size))\n            || (addr->in6.sin6_family == AF_INET6\n                && inet_ntop(AF_INET6, &addr->in6.sin6_addr, out_buffer,\n                             (socklen_t) out_buffer_size))) {\n        return AVS_OK;\n    }\n    return avs_errno(AVS_UNKNOWN_ERROR);\n}\n\nstatic avs_error_t stringify_sockaddr_port(const sockaddr_union_t *addr,\n                                           char *out_buffer,\n                                           size_t out_buffer_size) {\n    if ((addr->in.sin_family == AF_INET\n         && avs_simple_snprintf(out_buffer, out_buffer_size, \"%\" PRIu16,\n                                ntohs(addr->in.sin_port))\n                    >= 0)\n            || (addr->in6.sin6_family == AF_INET6\n                && avs_simple_snprintf(out_buffer, out_buffer_size, \"%\" PRIu16,\n                                       ntohs(addr->in6.sin6_port))\n                           >= 0)) {\n        return AVS_OK;\n    }\n    return avs_errno(AVS_UNKNOWN_ERROR);\n}\n\nstatic avs_error_t net_remote_host(avs_net_socket_t *sock_,\n                                   char *out_buffer,\n                                   size_t out_buffer_size) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    sockaddr_union_t addr;\n    if (getpeername(sock->fd, &addr.addr, &(socklen_t) { sizeof(addr) })) {\n        return avs_errno(AVS_UNKNOWN_ERROR);\n    }\n    return stringify_sockaddr_host(&addr, out_buffer, out_buffer_size);\n}\n\nstatic avs_error_t net_remote_hostname(avs_net_socket_t *sock_,\n                                       char *out_buffer,\n                                       size_t out_buffer_size) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    return avs_simple_snprintf(out_buffer, out_buffer_size, \"%s\",\n                               sock->remote_hostname)\n                           < 0\n                   ? avs_errno(AVS_UNKNOWN_ERROR)\n                   : AVS_OK;\n}\n\nstatic avs_error_t net_remote_port(avs_net_socket_t *sock_,\n                                   char *out_buffer,\n                                   size_t out_buffer_size) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    sockaddr_union_t addr;\n    if (getpeername(sock->fd, &addr.addr, &(socklen_t) { sizeof(addr) })) {\n        return avs_errno(AVS_UNKNOWN_ERROR);\n    }\n    return stringify_sockaddr_port(&addr, out_buffer, out_buffer_size);\n}\n\nstatic avs_error_t net_local_port(avs_net_socket_t *sock_,\n                                  char *out_buffer,\n                                  size_t out_buffer_size) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    sockaddr_union_t addr;\n    if (getsockname(sock->fd, &addr.addr, &(socklen_t) { sizeof(addr) })) {\n        return avs_errno(AVS_UNKNOWN_ERROR);\n    }\n    return stringify_sockaddr_port(&addr, out_buffer, out_buffer_size);\n}\n\nstatic avs_error_t net_get_opt(avs_net_socket_t *sock_,\n                               avs_net_socket_opt_key_t option_key,\n                               avs_net_socket_opt_value_t *out_option_value) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    switch (option_key) {\n    case AVS_NET_SOCKET_OPT_RECV_TIMEOUT:\n        out_option_value->recv_timeout = sock->recv_timeout;\n        return AVS_OK;\n    case AVS_NET_SOCKET_OPT_STATE:\n        if (sock->fd < 0) {\n            out_option_value->state = AVS_NET_SOCKET_STATE_CLOSED;\n        } else if (sock->shut_down) {\n            out_option_value->state = AVS_NET_SOCKET_STATE_SHUTDOWN;\n        } else {\n            sockaddr_union_t addr;\n            if (!getpeername(sock->fd, &addr.addr,\n                             &(socklen_t) { sizeof(addr) })\n                    && ((addr.in.sin_family == AF_INET && addr.in.sin_port != 0)\n                        || (addr.in6.sin6_family == AF_INET6\n                            && addr.in6.sin6_port != 0))) {\n                out_option_value->state = AVS_NET_SOCKET_STATE_CONNECTED;\n            } else {\n                out_option_value->state = AVS_NET_SOCKET_STATE_BOUND;\n            }\n        }\n        return AVS_OK;\n    case AVS_NET_SOCKET_OPT_INNER_MTU:\n        out_option_value->mtu = 1464;\n        return AVS_OK;\n    case AVS_NET_SOCKET_HAS_BUFFERED_DATA:\n        out_option_value->flag = false;\n        return AVS_OK;\n    case AVS_NET_SOCKET_OPT_BYTES_SENT:\n        out_option_value->bytes_sent = sock->bytes_sent;\n        return AVS_OK;\n    case AVS_NET_SOCKET_OPT_BYTES_RECEIVED:\n        out_option_value->bytes_received = sock->bytes_received;\n        return AVS_OK;\n    default:\n        return avs_errno(AVS_ENOTSUP);\n    }\n}\n\nstatic avs_error_t net_set_opt(avs_net_socket_t *sock_,\n                               avs_net_socket_opt_key_t option_key,\n                               avs_net_socket_opt_value_t option_value) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    switch (option_key) {\n    case AVS_NET_SOCKET_OPT_RECV_TIMEOUT:\n        sock->recv_timeout = option_value.recv_timeout;\n        return AVS_OK;\n    default:\n        return avs_errno(AVS_ENOTSUP);\n    }\n}\n\nstatic const avs_net_socket_v_table_t NET_SOCKET_VTABLE = {\n    .connect = net_connect,\n    .send = net_send,\n    .receive = net_receive,\n    .bind = net_bind,\n    .close = net_close,\n    .shutdown = net_shutdown,\n    .cleanup = net_cleanup,\n    .get_system_socket = net_system_socket,\n    .get_remote_host = net_remote_host,\n    .get_remote_hostname = net_remote_hostname,\n    .get_remote_port = net_remote_port,\n    .get_local_port = net_local_port,\n    .get_opt = net_get_opt,\n    .set_opt = net_set_opt\n};\n\nstatic avs_error_t\nnet_create_socket(avs_net_socket_t **socket_ptr,\n                  const avs_net_socket_configuration_t *configuration,\n                  int socktype) {\n    assert(socket_ptr);\n    assert(!*socket_ptr);\n    (void) configuration;\n    net_socket_impl_t *socket =\n            (net_socket_impl_t *) avs_calloc(1, sizeof(net_socket_impl_t));\n    if (!socket) {\n        return avs_errno(AVS_ENOMEM);\n    }\n    socket->operations = &NET_SOCKET_VTABLE;\n    socket->socktype = socktype;\n    socket->fd = -1;\n    socket->recv_timeout = avs_time_duration_from_scalar(30, AVS_TIME_S);\n    socket->preferred_endpoint = configuration->preferred_endpoint;\n    *socket_ptr = (avs_net_socket_t *) socket;\n    return AVS_OK;\n}\n\navs_error_t _avs_net_create_udp_socket(avs_net_socket_t **socket_ptr,\n                                       const void *configuration) {\n    return net_create_socket(\n            socket_ptr, (const avs_net_socket_configuration_t *) configuration,\n            SOCK_DGRAM);\n}\n\navs_error_t _avs_net_create_tcp_socket(avs_net_socket_t **socket_ptr,\n                                       const void *configuration) {\n    return net_create_socket(\n            socket_ptr, (const avs_net_socket_configuration_t *) configuration,\n            SOCK_STREAM);\n}\n\navs_error_t\navs_net_resolved_endpoint_get_host_port(const avs_net_resolved_endpoint_t *endp,\n                                        char *host,\n                                        size_t hostlen,\n                                        char *serv,\n                                        size_t servlen) {\n    AVS_STATIC_ASSERT(sizeof(endp->data.buf) >= sizeof(sockaddr_union_t),\n                      data_buffer_big_enough);\n    if (endp->size != sizeof(sockaddr_union_t)) {\n        return avs_errno(AVS_EINVAL);\n    }\n    const sockaddr_union_t *addr = (const sockaddr_union_t *) &endp->data.buf;\n    avs_error_t err = AVS_OK;\n    (void) ((host\n             && avs_is_err(\n                        (err = stringify_sockaddr_host(addr, host, hostlen))))\n            || (serv\n                && avs_is_err((err = stringify_sockaddr_port(addr, serv,\n                                                             servlen)))));\n    return err;\n}\n"
  },
  {
    "path": "examples/custom-tls/config-features/src/tls_impl.c",
    "content": "#include <poll.h>\n\n#include <openssl/ssl.h>\n\n#include <avsystem/commons/avs_crypto_common.h>\n#include <avsystem/commons/avs_socket_v_table.h>\n#include <avsystem/commons/avs_utils.h>\n\n#ifndef AVS_COMMONS_WITH_CUSTOM_TLS\n#    error \"Custom implementation of the TLS layer requires AVS_COMMONS_WITH_CUSTOM_TLS\"\n#endif // AVS_COMMONS_WITH_CUSTOM_TLS\n\navs_error_t _avs_net_initialize_global_ssl_state(void);\nvoid _avs_net_cleanup_global_ssl_state(void);\navs_error_t _avs_net_create_ssl_socket(avs_net_socket_t **socket,\n                                       const void *socket_configuration);\navs_error_t _avs_net_create_dtls_socket(avs_net_socket_t **socket,\n                                        const void *socket_configuration);\n\navs_error_t _avs_net_initialize_global_ssl_state(void) {\n    if (!OPENSSL_init_ssl(OPENSSL_INIT_ADD_ALL_CIPHERS\n                                  | OPENSSL_INIT_ADD_ALL_DIGESTS,\n                          NULL)) {\n        return avs_errno(AVS_EPROTO);\n    }\n    return AVS_OK;\n}\n\nvoid _avs_net_cleanup_global_ssl_state(void) {}\n\nvoid avs_crypto_clear_buffer(void *buf, size_t size) {\n    OPENSSL_cleanse(buf, size);\n}\n\ntypedef struct {\n    const avs_net_socket_v_table_t *operations;\n    avs_net_socket_t *backend_socket;\n    SSL_CTX *ctx;\n    SSL *ssl;\n\n    char psk[256];\n    size_t psk_size;\n    char identity[128];\n    size_t identity_size;\n\n    void *session_resumption_buffer;\n    size_t session_resumption_buffer_size;\n\n    char server_name_indication[256];\n    unsigned int dtls_hs_timeout_min_us;\n    unsigned int dtls_hs_timeout_max_us;\n} tls_socket_impl_t;\n\nstatic unsigned int dtls_timer_cb(SSL *s, unsigned int timer_us) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) SSL_get_app_data(s);\n    if (!timer_us) {\n        return sock->dtls_hs_timeout_min_us;\n    } else if (timer_us >= sock->dtls_hs_timeout_max_us) {\n        // maximum number of retransmissions reached, let's give up\n        avs_net_socket_shutdown(sock->backend_socket);\n        return 0;\n    } else {\n        timer_us *= 2;\n        if (timer_us > sock->dtls_hs_timeout_max_us) {\n            timer_us = sock->dtls_hs_timeout_max_us;\n        }\n        return timer_us;\n    }\n}\n\nstatic avs_error_t perform_handshake(tls_socket_impl_t *sock,\n                                     const char *host) {\n    union {\n        struct sockaddr addr;\n        struct sockaddr_storage storage;\n    } peername;\n    const void *fd_ptr = avs_net_socket_get_system(sock->backend_socket);\n    if (!fd_ptr\n            || getpeername(*(const int *) fd_ptr, &peername.addr,\n                           &(socklen_t) { sizeof(peername) })) {\n        return avs_errno(AVS_EBADF);\n    }\n\n    sock->ssl = SSL_new(sock->ctx);\n    if (!sock->ssl) {\n        return avs_errno(AVS_ENOMEM);\n    }\n\n    SSL_set_app_data(sock->ssl, sock);\n    SSL_set_tlsext_host_name(sock->ssl, host);\n\n    BIO *bio = BIO_new_dgram(*(const int *) fd_ptr, 0);\n    if (!bio) {\n        return avs_errno(AVS_ENOMEM);\n    }\n    BIO_ctrl(bio, BIO_CTRL_DGRAM_SET_CONNECTED, 0, &peername.addr);\n    SSL_set_bio(sock->ssl, bio, bio);\n    DTLS_set_timer_cb(sock->ssl, dtls_timer_cb);\n\n    if (sock->session_resumption_buffer) {\n        const unsigned char *ptr =\n                (const unsigned char *) sock->session_resumption_buffer;\n        SSL_SESSION *session =\n                d2i_SSL_SESSION(NULL, &ptr,\n                                sock->session_resumption_buffer_size);\n        if (session) {\n            SSL_set_session(sock->ssl, session);\n            SSL_SESSION_free(session);\n        }\n    }\n\n    if (SSL_connect(sock->ssl) <= 0) {\n        return avs_errno(AVS_EPROTO);\n    }\n    return AVS_OK;\n}\n\nstatic avs_error_t\ntls_connect(avs_net_socket_t *sock_, const char *host, const char *port) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    if (sock->ssl) {\n        return avs_errno(AVS_EBADF);\n    }\n    avs_error_t err;\n    if (avs_is_err((\n                err = avs_net_socket_connect(sock->backend_socket, host, port)))\n            || avs_is_err((err = perform_handshake(\n                                   sock, sock->server_name_indication[0]\n                                                 ? sock->server_name_indication\n                                                 : host)))) {\n        if (sock->ssl) {\n            SSL_free(sock->ssl);\n            sock->ssl = NULL;\n        }\n        avs_net_socket_close(sock->backend_socket);\n    }\n    return err;\n}\n\nstatic avs_error_t\ntls_send(avs_net_socket_t *sock_, const void *buffer, size_t buffer_length) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    int result = SSL_write(sock->ssl, buffer, (int) buffer_length);\n    if (result < 0 || (size_t) result < buffer_length) {\n        return avs_errno(AVS_EPROTO);\n    }\n    return AVS_OK;\n}\n\nstatic avs_error_t tls_receive(avs_net_socket_t *sock_,\n                               size_t *out_bytes_received,\n                               void *buffer,\n                               size_t buffer_length) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    const void *fd_ptr = avs_net_socket_get_system(sock->backend_socket);\n    avs_net_socket_opt_value_t timeout;\n    if (!fd_ptr\n            || avs_is_err(avs_net_socket_get_opt(\n                       sock->backend_socket, AVS_NET_SOCKET_OPT_RECV_TIMEOUT,\n                       &timeout))) {\n        return avs_errno(AVS_EBADF);\n    }\n    struct pollfd pfd = {\n        .fd = *(const int *) fd_ptr,\n        .events = POLLIN\n    };\n    int64_t timeout_ms;\n    if (avs_time_duration_to_scalar(&timeout_ms, AVS_TIME_MS,\n                                    timeout.recv_timeout)) {\n        timeout_ms = -1;\n    } else if (timeout_ms < 0) {\n        timeout_ms = 0;\n    }\n    if (poll(&pfd, 1, (int) timeout_ms) == 0) {\n        return avs_errno(AVS_ETIMEDOUT);\n    }\n    int bytes_received = SSL_read(sock->ssl, buffer, (int) buffer_length);\n    if (bytes_received < 0) {\n        return avs_errno(AVS_EPROTO);\n    }\n    *out_bytes_received = (size_t) bytes_received;\n    if (buffer_length > 0 && (size_t) bytes_received == buffer_length) {\n        return avs_errno(AVS_EMSGSIZE);\n    }\n    return AVS_OK;\n}\n\nstatic avs_error_t\ntls_bind(avs_net_socket_t *sock_, const char *address, const char *port) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    return avs_net_socket_bind(sock->backend_socket, address, port);\n}\n\nstatic avs_error_t tls_close(avs_net_socket_t *sock_) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    if (sock->ssl) {\n        SSL_free(sock->ssl);\n        sock->ssl = NULL;\n    }\n    return avs_net_socket_close(sock->backend_socket);\n}\n\nstatic avs_error_t tls_shutdown(avs_net_socket_t *sock_) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    return avs_net_socket_shutdown(sock->backend_socket);\n}\n\nstatic avs_error_t tls_cleanup(avs_net_socket_t **sock_ptr) {\n    avs_error_t err = AVS_OK;\n    if (sock_ptr && *sock_ptr) {\n        tls_socket_impl_t *sock = (tls_socket_impl_t *) *sock_ptr;\n        tls_close(*sock_ptr);\n        avs_net_socket_cleanup(&sock->backend_socket);\n        if (sock->ctx) {\n            SSL_CTX_free(sock->ctx);\n        }\n        avs_free(sock);\n        *sock_ptr = NULL;\n    }\n    return err;\n}\n\nstatic const void *tls_system_socket(avs_net_socket_t *sock_) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    return avs_net_socket_get_system(sock->backend_socket);\n}\n\nstatic avs_error_t tls_remote_host(avs_net_socket_t *sock_,\n                                   char *out_buffer,\n                                   size_t out_buffer_size) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    return avs_net_socket_get_remote_host(sock->backend_socket, out_buffer,\n                                          out_buffer_size);\n}\n\nstatic avs_error_t tls_remote_hostname(avs_net_socket_t *sock_,\n                                       char *out_buffer,\n                                       size_t out_buffer_size) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    return avs_net_socket_get_remote_hostname(sock->backend_socket, out_buffer,\n                                              out_buffer_size);\n}\n\nstatic avs_error_t tls_remote_port(avs_net_socket_t *sock_,\n                                   char *out_buffer,\n                                   size_t out_buffer_size) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    return avs_net_socket_get_remote_port(sock->backend_socket, out_buffer,\n                                          out_buffer_size);\n}\n\nstatic avs_error_t tls_local_port(avs_net_socket_t *sock_,\n                                  char *out_buffer,\n                                  size_t out_buffer_size) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    return avs_net_socket_get_local_port(sock->backend_socket, out_buffer,\n                                         out_buffer_size);\n}\n\nstatic avs_error_t tls_get_opt(avs_net_socket_t *sock_,\n                               avs_net_socket_opt_key_t option_key,\n                               avs_net_socket_opt_value_t *out_option_value) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    switch (option_key) {\n    case AVS_NET_SOCKET_OPT_INNER_MTU: {\n        avs_error_t err = avs_net_socket_get_opt(sock->backend_socket,\n                                                 AVS_NET_SOCKET_OPT_INNER_MTU,\n                                                 out_option_value);\n        if (avs_is_ok(err)) {\n            out_option_value->mtu = AVS_MAX(out_option_value->mtu - 64, 0);\n        }\n        return err;\n    }\n    case AVS_NET_SOCKET_HAS_BUFFERED_DATA:\n        out_option_value->flag = (sock->ssl && SSL_pending(sock->ssl) > 0);\n        return AVS_OK;\n    case AVS_NET_SOCKET_OPT_SESSION_RESUMED:\n        out_option_value->flag = (sock->ssl && SSL_session_reused(sock->ssl));\n        return AVS_OK;\n    default:\n        return avs_net_socket_get_opt(sock->backend_socket, option_key,\n                                      out_option_value);\n    }\n}\n\nstatic avs_error_t tls_set_opt(avs_net_socket_t *sock_,\n                               avs_net_socket_opt_key_t option_key,\n                               avs_net_socket_opt_value_t option_value) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    return avs_net_socket_set_opt(sock->backend_socket, option_key,\n                                  option_value);\n}\n\nstatic const avs_net_socket_v_table_t TLS_SOCKET_VTABLE = {\n    .connect = tls_connect,\n    .send = tls_send,\n    .receive = tls_receive,\n    .bind = tls_bind,\n    .close = tls_close,\n    .shutdown = tls_shutdown,\n    .cleanup = tls_cleanup,\n    .get_system_socket = tls_system_socket,\n    .get_remote_host = tls_remote_host,\n    .get_remote_hostname = tls_remote_hostname,\n    .get_remote_port = tls_remote_port,\n    .get_local_port = tls_local_port,\n    .get_opt = tls_get_opt,\n    .set_opt = tls_set_opt\n};\n\nstatic avs_error_t configure_dtls_version(tls_socket_impl_t *sock,\n                                          avs_net_ssl_version_t version) {\n    switch (version) {\n    case AVS_NET_SSL_VERSION_DEFAULT:\n        return AVS_OK;\n    case AVS_NET_SSL_VERSION_TLSv1:\n    case AVS_NET_SSL_VERSION_TLSv1_1:\n        SSL_CTX_set_min_proto_version(sock->ctx, DTLS1_VERSION);\n        return AVS_OK;\n    case AVS_NET_SSL_VERSION_TLSv1_2:\n        SSL_CTX_set_min_proto_version(sock->ctx, DTLS1_2_VERSION);\n        return AVS_OK;\n    default:\n        return avs_errno(AVS_ENOTSUP);\n    }\n}\n\nstatic unsigned int psk_client_cb(SSL *ssl,\n                                  const char *hint,\n                                  char *identity,\n                                  unsigned int max_identity_len,\n                                  unsigned char *psk,\n                                  unsigned int max_psk_len) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) SSL_get_app_data(ssl);\n\n    (void) hint;\n\n    if (!sock || max_psk_len < sock->psk_size\n            || max_identity_len < sock->identity_size + 1) {\n        return 0;\n    }\n\n    memcpy(psk, sock->psk, sock->psk_size);\n    memcpy(identity, sock->identity, sock->identity_size);\n    identity[sock->identity_size] = '\\0';\n\n    return (unsigned int) sock->psk_size;\n}\n\nstatic avs_error_t configure_psk(tls_socket_impl_t *sock,\n                                 const avs_net_psk_info_t *psk) {\n    if (psk->key.desc.source != AVS_CRYPTO_DATA_SOURCE_BUFFER\n            || psk->identity.desc.source != AVS_CRYPTO_DATA_SOURCE_BUFFER) {\n        return avs_errno(AVS_EINVAL);\n    }\n\n    const void *key_ptr = psk->key.desc.info.buffer.buffer;\n    size_t key_size = psk->key.desc.info.buffer.buffer_size;\n\n    const void *identity_ptr = psk->identity.desc.info.buffer.buffer;\n    size_t identity_size = psk->identity.desc.info.buffer.buffer_size;\n\n    if (key_size > sizeof(sock->psk)\n            || identity_size > sizeof(sock->identity)) {\n        return avs_errno(AVS_EINVAL);\n    }\n    memcpy(sock->psk, key_ptr, key_size);\n    sock->psk_size = key_size;\n    memcpy(sock->identity, identity_ptr, identity_size);\n    sock->identity_size = identity_size;\n    SSL_CTX_set_cipher_list(sock->ctx, \"PSK\");\n    SSL_CTX_set_psk_client_callback(sock->ctx, psk_client_cb);\n    SSL_CTX_set_verify(sock->ctx, SSL_VERIFY_PEER, NULL);\n    return AVS_OK;\n}\n\nstatic avs_error_t configure_dtls_handshake_timeouts(\n        tls_socket_impl_t *sock,\n        const avs_net_dtls_handshake_timeouts_t *dtls_handshake_timeouts) {\n    uint64_t min_us = 1000000, max_us = 60000000;\n    if (dtls_handshake_timeouts) {\n        avs_time_duration_to_scalar(&min_us, AVS_TIME_US,\n                                    dtls_handshake_timeouts->min);\n        avs_time_duration_to_scalar(&max_us, AVS_TIME_US,\n                                    dtls_handshake_timeouts->max);\n    }\n    sock->dtls_hs_timeout_min_us = (unsigned int) min_us;\n    sock->dtls_hs_timeout_max_us = (unsigned int) max_us;\n    return AVS_OK;\n}\n\nstatic avs_error_t\nconfigure_ciphersuites(tls_socket_impl_t *sock,\n                       const avs_net_socket_tls_ciphersuites_t *ciphersuites) {\n    if (!ciphersuites->num_ids) {\n        return AVS_OK;\n    }\n    SSL *dummy_ssl = SSL_new(sock->ctx);\n    if (!dummy_ssl) {\n        return avs_errno(AVS_ENOMEM);\n    }\n    char cipher_list[1024] = \"-ALL\";\n    char *cipher_list_ptr = cipher_list + strlen(cipher_list);\n    const char *const cipher_list_end = cipher_list + sizeof(cipher_list);\n    for (size_t i = 0; i < ciphersuites->num_ids; ++i) {\n        unsigned char id_as_chars[] = {\n            (unsigned char) (ciphersuites->ids[i] >> 8),\n            (unsigned char) (ciphersuites->ids[i] & 0xFF)\n        };\n        const SSL_CIPHER *cipher = SSL_CIPHER_find(dummy_ssl, id_as_chars);\n        if (cipher) {\n            const char *name = SSL_CIPHER_get_name(cipher);\n            if (!!strstr(name, \"PSK\") == !!sock->psk_size\n                    && cipher_list_ptr + 1 + strlen(name) < cipher_list_end) {\n                *cipher_list_ptr++ = ':';\n                strcpy(cipher_list_ptr, name);\n                cipher_list_ptr += strlen(name);\n            }\n        }\n    }\n    SSL_free(dummy_ssl);\n    SSL_CTX_set_cipher_list(sock->ctx, cipher_list);\n    // NOTE: Configuring the set of supported new-style ciphersuites as defined\n    // for TLS 1.3 are not supported by this function.\n    return AVS_OK;\n}\n\nstatic avs_error_t configure_sni(tls_socket_impl_t *sock,\n                                 const char *server_name_indication) {\n    if (server_name_indication) {\n        if (strlen(server_name_indication)\n                >= sizeof(sock->server_name_indication)) {\n            return avs_errno(AVS_ENOBUFS);\n        }\n        strcpy(sock->server_name_indication, server_name_indication);\n    }\n    return AVS_OK;\n}\n\nstatic int new_session_cb(SSL *ssl, SSL_SESSION *sess) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) SSL_get_app_data(ssl);\n    int serialized_size = i2d_SSL_SESSION(sess, NULL);\n    if (serialized_size > 0\n            && (size_t) serialized_size\n                           <= sock->session_resumption_buffer_size) {\n        unsigned char *ptr = (unsigned char *) sock->session_resumption_buffer;\n        i2d_SSL_SESSION(sess, &ptr);\n    }\n    return 0;\n}\n\navs_error_t _avs_net_create_dtls_socket(avs_net_socket_t **socket_ptr,\n                                        const void *configuration_) {\n    assert(socket_ptr);\n    assert(!*socket_ptr);\n    assert(configuration_);\n    const avs_net_ssl_configuration_t *configuration =\n            (const avs_net_ssl_configuration_t *) configuration_;\n    tls_socket_impl_t *socket =\n            (tls_socket_impl_t *) avs_calloc(1, sizeof(tls_socket_impl_t));\n    if (!socket) {\n        return avs_errno(AVS_ENOMEM);\n    }\n    *socket_ptr = (avs_net_socket_t *) socket;\n    socket->operations = &TLS_SOCKET_VTABLE;\n\n    avs_error_t err = AVS_OK;\n    if (avs_is_ok((err = avs_net_udp_socket_create(\n                           &socket->backend_socket,\n                           &configuration->backend_configuration)))\n            && !(socket->ctx = SSL_CTX_new(DTLS_method()))) {\n        err = avs_errno(AVS_ENOMEM);\n    }\n    if (avs_is_ok(err)) {\n        err = configure_dtls_version(socket, configuration->version);\n    }\n    if (avs_is_ok(err)) {\n        switch (configuration->security.mode) {\n        case AVS_NET_SECURITY_PSK:\n            err = configure_psk(socket, &configuration->security.data.psk);\n            break;\n        default:\n            err = avs_errno(AVS_ENOTSUP);\n        }\n    }\n    if (avs_is_err(err)\n            || avs_is_err((\n                       err = configure_dtls_handshake_timeouts(\n                               socket, configuration->dtls_handshake_timeouts)))\n            || avs_is_err((err = configure_ciphersuites(\n                                   socket, &configuration->ciphersuites)))\n            || avs_is_err((err = configure_sni(\n                                   socket,\n                                   configuration->server_name_indication)))) {\n        avs_net_socket_cleanup(socket_ptr);\n        return err;\n    }\n    SSL_CTX_set_mode(socket->ctx, SSL_MODE_AUTO_RETRY);\n    if (configuration->session_resumption_buffer_size > 0) {\n        assert(configuration->session_resumption_buffer);\n        socket->session_resumption_buffer =\n                configuration->session_resumption_buffer;\n        socket->session_resumption_buffer_size =\n                configuration->session_resumption_buffer_size;\n        SSL_CTX_set_session_cache_mode(\n                socket->ctx,\n                SSL_SESS_CACHE_CLIENT | SSL_SESS_CACHE_NO_INTERNAL_STORE);\n        SSL_CTX_sess_set_new_cb(socket->ctx, new_session_cb);\n    }\n    return AVS_OK;\n}\n\navs_error_t _avs_net_create_ssl_socket(avs_net_socket_t **socket_ptr,\n                                       const void *configuration) {\n    return avs_errno(AVS_ENOTSUP);\n}\n"
  },
  {
    "path": "examples/custom-tls/minimal/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.16)\nproject(custom-tls-minimal C)\n\nset(CMAKE_C_STANDARD 99)\n\nfind_package(anjay REQUIRED)\nfind_package(OpenSSL REQUIRED)\n\nadd_executable(${PROJECT_NAME}\n               src/main.c\n               src/net_impl.c\n               src/tls_impl.c)\ntarget_link_libraries(${PROJECT_NAME} PRIVATE anjay OpenSSL::SSL)\n"
  },
  {
    "path": "examples/custom-tls/minimal/src/main.c",
    "content": "#include <anjay/anjay.h>\n#include <anjay/security.h>\n#include <anjay/server.h>\n#include <avsystem/commons/avs_log.h>\n\n#include <poll.h>\n\nint main_loop(anjay_t *anjay) {\n    while (true) {\n        // Obtain all network data sources\n        AVS_LIST(avs_net_socket_t *const) sockets = anjay_get_sockets(anjay);\n\n        // Prepare to poll() on them\n        size_t numsocks = AVS_LIST_SIZE(sockets);\n        struct pollfd pollfds[numsocks];\n        size_t i = 0;\n        AVS_LIST(avs_net_socket_t *const) sock;\n        AVS_LIST_FOREACH(sock, sockets) {\n            pollfds[i].fd = *(const int *) avs_net_socket_get_system(*sock);\n            pollfds[i].events = POLLIN;\n            pollfds[i].revents = 0;\n            ++i;\n        }\n\n        const int max_wait_time_ms = 1000;\n        // Determine the expected time to the next job in milliseconds.\n        // If there is no job we will wait till something arrives for\n        // at most 1 second (i.e. max_wait_time_ms).\n        int wait_ms =\n                anjay_sched_calculate_wait_time_ms(anjay, max_wait_time_ms);\n\n        // Wait for the events if necessary, and handle them.\n        if (poll(pollfds, numsocks, wait_ms) > 0) {\n            int socket_id = 0;\n            AVS_LIST(avs_net_socket_t *const) socket = NULL;\n            AVS_LIST_FOREACH(socket, sockets) {\n                if (pollfds[socket_id].revents) {\n                    if (anjay_serve(anjay, *socket)) {\n                        avs_log(tutorial, ERROR, \"anjay_serve failed\");\n                    }\n                }\n                ++socket_id;\n            }\n        }\n\n        // Finally run the scheduler\n        anjay_sched_run(anjay);\n    }\n    return 0;\n}\n\n// Installs Security Object and adds an instance of it.\n// An instance of Security Object provides information needed to connect to\n// LwM2M server.\nstatic int setup_security_object(anjay_t *anjay) {\n    if (anjay_security_object_install(anjay)) {\n        return -1;\n    }\n\n    static const char PSK_IDENTITY[] = \"identity\";\n    static const char PSK_KEY[] = \"P4s$w0rd\";\n\n    anjay_security_instance_t security_instance = {\n        .ssid = 1,\n        .server_uri = \"coaps://eu.iot.avsystem.cloud:5684\",\n        .security_mode = ANJAY_SECURITY_PSK,\n        .public_cert_or_psk_identity = (const uint8_t *) PSK_IDENTITY,\n        .public_cert_or_psk_identity_size = strlen(PSK_IDENTITY),\n        .private_cert_or_psk_key = (const uint8_t *) PSK_KEY,\n        .private_cert_or_psk_key_size = strlen(PSK_KEY)\n    };\n\n    // Anjay will assign Instance ID automatically\n    anjay_iid_t security_instance_id = ANJAY_ID_INVALID;\n    if (anjay_security_object_add_instance(anjay, &security_instance,\n                                           &security_instance_id)) {\n        return -1;\n    }\n\n    return 0;\n}\n\n// Installs Server Object and adds an instance of it.\n// An instance of Server Object provides the data related to a LwM2M Server.\nstatic int setup_server_object(anjay_t *anjay) {\n    if (anjay_server_object_install(anjay)) {\n        return -1;\n    }\n\n    const anjay_server_instance_t server_instance = {\n        // Server Short ID\n        .ssid = 1,\n        // Client will send Update message often than every 60 seconds\n        .lifetime = 60,\n        // Disable Default Minimum Period resource\n        .default_min_period = -1,\n        // Disable Default Maximum Period resource\n        .default_max_period = -1,\n        // Disable Disable Timeout resource\n        .disable_timeout = -1,\n        // Sets preferred transport to UDP\n        .binding = \"U\"\n    };\n\n    // Anjay will assign Instance ID automatically\n    anjay_iid_t server_instance_id = ANJAY_ID_INVALID;\n    if (anjay_server_object_add_instance(anjay, &server_instance,\n                                         &server_instance_id)) {\n        return -1;\n    }\n\n    return 0;\n}\n\nint main(int argc, char *argv[]) {\n    if (argc != 2) {\n        avs_log(tutorial, ERROR, \"usage: %s ENDPOINT_NAME\", argv[0]);\n        return -1;\n    }\n\n    const anjay_configuration_t CONFIG = {\n        .endpoint_name = argv[1],\n        .in_buffer_size = 4000,\n        .out_buffer_size = 4000,\n        .msg_cache_size = 4000\n    };\n\n    anjay_t *anjay = anjay_new(&CONFIG);\n    if (!anjay) {\n        avs_log(tutorial, ERROR, \"Could not create Anjay object\");\n        return -1;\n    }\n\n    int result = 0;\n    // Setup necessary objects\n    if (setup_security_object(anjay) || setup_server_object(anjay)) {\n        result = -1;\n    }\n\n    if (!result) {\n        result = main_loop(anjay);\n    }\n\n    anjay_delete(anjay);\n    return result;\n}\n"
  },
  {
    "path": "examples/custom-tls/minimal/src/net_impl.c",
    "content": "#include <inttypes.h>\n\n#include <netdb.h>\n#include <poll.h>\n#include <unistd.h>\n\n#include <arpa/inet.h>\n\n#include <sys/socket.h>\n#include <sys/types.h>\n\n#include <avsystem/commons/avs_socket_v_table.h>\n#include <avsystem/commons/avs_utils.h>\n\n#ifdef AVS_COMMONS_NET_WITH_POSIX_AVS_SOCKET\n#    error \"Custom implementation of the network layer conflicts with AVS_COMMONS_NET_WITH_POSIX_AVS_SOCKET\"\n#endif // AVS_COMMONS_NET_WITH_POSIX_AVS_SOCKET\n\navs_error_t _avs_net_initialize_global_compat_state(void);\nvoid _avs_net_cleanup_global_compat_state(void);\navs_error_t _avs_net_create_tcp_socket(avs_net_socket_t **socket,\n                                       const void *socket_configuration);\navs_error_t _avs_net_create_udp_socket(avs_net_socket_t **socket,\n                                       const void *socket_configuration);\n\navs_error_t _avs_net_initialize_global_compat_state(void) {\n    return AVS_OK;\n}\n\nvoid _avs_net_cleanup_global_compat_state(void) {}\n\ntypedef struct {\n    const avs_net_socket_v_table_t *operations;\n    int socktype;\n    int fd;\n    avs_time_duration_t recv_timeout;\n    char remote_hostname[256];\n    bool shut_down;\n    size_t bytes_sent;\n    size_t bytes_received;\n    avs_net_resolved_endpoint_t *preferred_endpoint;\n} net_socket_impl_t;\n\ntypedef union {\n    struct sockaddr addr;\n    struct sockaddr_in in;\n    struct sockaddr_in6 in6;\n    struct sockaddr_storage storage;\n} sockaddr_union_t;\n\nstatic avs_error_t\nnet_connect(avs_net_socket_t *sock_, const char *host, const char *port) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    struct addrinfo hints = {\n        .ai_socktype = sock->socktype\n    };\n    if (sock->fd >= 0) {\n        getsockopt(sock->fd, SOL_SOCKET, SO_DOMAIN, &hints.ai_family,\n                   &(socklen_t) { sizeof(hints.ai_family) });\n    }\n    struct addrinfo *addrs = NULL;\n    avs_error_t err = AVS_OK;\n    if (getaddrinfo(host, port, &hints, &addrs) || !addrs) {\n        err = avs_errno(AVS_EADDRNOTAVAIL);\n    } else if (sock->fd < 0\n               && (sock->fd = socket(addrs->ai_family, addrs->ai_socktype,\n                                     addrs->ai_protocol))\n                          < 0) {\n        err = avs_errno(AVS_UNKNOWN_ERROR);\n    } else {\n        const struct addrinfo *addr = addrs;\n        if (sock->preferred_endpoint\n                && sock->preferred_endpoint->size == sizeof(sockaddr_union_t)) {\n            while (addr) {\n                if (addr->ai_addrlen <= sizeof(sockaddr_union_t)\n                        && memcmp(addr->ai_addr,\n                                  sock->preferred_endpoint->data.buf,\n                                  addr->ai_addrlen)\n                                       == 0) {\n                    break;\n                }\n                addr = addr->ai_next;\n            }\n        }\n        if (!addr) {\n            // Preferred endpoint not found, use the first one\n            addr = addrs;\n        }\n        if (connect(sock->fd, addr->ai_addr, addr->ai_addrlen)) {\n            err = avs_errno(AVS_ECONNREFUSED);\n        }\n        if (sock->preferred_endpoint && avs_is_ok(err)) {\n            assert(addr->ai_addrlen <= sizeof(sockaddr_union_t));\n            memcpy(sock->preferred_endpoint->data.buf, addr->ai_addr,\n                   addr->ai_addrlen);\n            sock->preferred_endpoint->size = sizeof(sockaddr_union_t);\n        }\n    }\n    if (avs_is_ok(err)) {\n        sock->shut_down = false;\n        snprintf(sock->remote_hostname, sizeof(sock->remote_hostname), \"%s\",\n                 host);\n    }\n    freeaddrinfo(addrs);\n    return err;\n}\n\nstatic avs_error_t\nnet_send(avs_net_socket_t *sock_, const void *buffer, size_t buffer_length) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    ssize_t written = send(sock->fd, buffer, buffer_length, MSG_NOSIGNAL);\n    if (written >= 0) {\n        sock->bytes_sent += (size_t) written;\n        if ((size_t) written == buffer_length) {\n            return AVS_OK;\n        }\n    }\n    return avs_errno(AVS_EIO);\n}\n\nstatic avs_error_t net_receive(avs_net_socket_t *sock_,\n                               size_t *out_bytes_received,\n                               void *buffer,\n                               size_t buffer_length) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    struct pollfd pfd = {\n        .fd = sock->fd,\n        .events = POLLIN\n    };\n    int64_t timeout_ms;\n    if (avs_time_duration_to_scalar(&timeout_ms, AVS_TIME_MS,\n                                    sock->recv_timeout)) {\n        timeout_ms = -1;\n    } else if (timeout_ms < 0) {\n        timeout_ms = 0;\n    }\n    if (poll(&pfd, 1, (int) timeout_ms) == 0) {\n        return avs_errno(AVS_ETIMEDOUT);\n    }\n    ssize_t bytes_received = read(sock->fd, buffer, buffer_length);\n    if (bytes_received < 0) {\n        return avs_errno(AVS_EIO);\n    }\n    *out_bytes_received = (size_t) bytes_received;\n    sock->bytes_received += (size_t) bytes_received;\n    if (buffer_length > 0 && sock->socktype == SOCK_DGRAM\n            && (size_t) bytes_received == buffer_length) {\n        return avs_errno(AVS_EMSGSIZE);\n    }\n    return AVS_OK;\n}\n\nstatic avs_error_t\nnet_bind(avs_net_socket_t *sock_, const char *address, const char *port) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    struct addrinfo hints = {\n        .ai_flags = AI_PASSIVE,\n        .ai_socktype = sock->socktype\n    };\n    if (sock->fd >= 0) {\n        getsockopt(sock->fd, SOL_SOCKET, SO_DOMAIN, &hints.ai_family,\n                   &(socklen_t) { sizeof(hints.ai_family) });\n    }\n    struct addrinfo *addr = NULL;\n    avs_error_t err = AVS_OK;\n    if (getaddrinfo(address, port, &hints, &addr) || !addr) {\n        err = avs_errno(AVS_EADDRNOTAVAIL);\n    } else if ((sock->fd < 0\n                && (sock->fd = socket(addr->ai_family, addr->ai_socktype,\n                                      addr->ai_protocol))\n                           < 0)\n               || setsockopt(sock->fd, SOL_SOCKET, SO_REUSEADDR, &(int) { 1 },\n                             sizeof(int))) {\n        err = avs_errno(AVS_UNKNOWN_ERROR);\n    } else if (bind(sock->fd, addr->ai_addr, addr->ai_addrlen)) {\n        err = avs_errno(AVS_ECONNREFUSED);\n    } else {\n        sock->shut_down = false;\n    }\n    if (avs_is_err(err) && sock->fd >= 0) {\n        close(sock->fd);\n        sock->fd = -1;\n    }\n    freeaddrinfo(addr);\n    return err;\n}\n\nstatic avs_error_t net_close(avs_net_socket_t *sock_) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    avs_error_t err = AVS_OK;\n    if (sock->fd >= 0) {\n        if (close(sock->fd)) {\n            err = avs_errno(AVS_EIO);\n        }\n        sock->fd = -1;\n        sock->shut_down = false;\n    }\n    return err;\n}\n\nstatic avs_error_t net_shutdown(avs_net_socket_t *sock_) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    avs_error_t err = avs_errno(AVS_EBADF);\n    if (sock->fd >= 0) {\n        err = shutdown(sock->fd, SHUT_RDWR) ? avs_errno(AVS_EIO) : AVS_OK;\n        sock->shut_down = true;\n    }\n    return err;\n}\n\nstatic avs_error_t net_cleanup(avs_net_socket_t **sock_ptr) {\n    avs_error_t err = AVS_OK;\n    if (sock_ptr && *sock_ptr) {\n        err = net_close(*sock_ptr);\n        avs_free(*sock_ptr);\n        *sock_ptr = NULL;\n    }\n    return err;\n}\n\nstatic const void *net_system_socket(avs_net_socket_t *sock_) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    return &sock->fd;\n}\n\nstatic avs_error_t stringify_sockaddr_host(const sockaddr_union_t *addr,\n                                           char *out_buffer,\n                                           size_t out_buffer_size) {\n    if ((addr->in.sin_family == AF_INET\n         && inet_ntop(AF_INET, &addr->in.sin_addr, out_buffer,\n                      (socklen_t) out_buffer_size))\n            || (addr->in6.sin6_family == AF_INET6\n                && inet_ntop(AF_INET6, &addr->in6.sin6_addr, out_buffer,\n                             (socklen_t) out_buffer_size))) {\n        return AVS_OK;\n    }\n    return avs_errno(AVS_UNKNOWN_ERROR);\n}\n\nstatic avs_error_t stringify_sockaddr_port(const sockaddr_union_t *addr,\n                                           char *out_buffer,\n                                           size_t out_buffer_size) {\n    if ((addr->in.sin_family == AF_INET\n         && avs_simple_snprintf(out_buffer, out_buffer_size, \"%\" PRIu16,\n                                ntohs(addr->in.sin_port))\n                    >= 0)\n            || (addr->in6.sin6_family == AF_INET6\n                && avs_simple_snprintf(out_buffer, out_buffer_size, \"%\" PRIu16,\n                                       ntohs(addr->in6.sin6_port))\n                           >= 0)) {\n        return AVS_OK;\n    }\n    return avs_errno(AVS_UNKNOWN_ERROR);\n}\n\nstatic avs_error_t net_remote_host(avs_net_socket_t *sock_,\n                                   char *out_buffer,\n                                   size_t out_buffer_size) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    sockaddr_union_t addr;\n    if (getpeername(sock->fd, &addr.addr, &(socklen_t) { sizeof(addr) })) {\n        return avs_errno(AVS_UNKNOWN_ERROR);\n    }\n    return stringify_sockaddr_host(&addr, out_buffer, out_buffer_size);\n}\n\nstatic avs_error_t net_remote_hostname(avs_net_socket_t *sock_,\n                                       char *out_buffer,\n                                       size_t out_buffer_size) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    return avs_simple_snprintf(out_buffer, out_buffer_size, \"%s\",\n                               sock->remote_hostname)\n                           < 0\n                   ? avs_errno(AVS_UNKNOWN_ERROR)\n                   : AVS_OK;\n}\n\nstatic avs_error_t net_remote_port(avs_net_socket_t *sock_,\n                                   char *out_buffer,\n                                   size_t out_buffer_size) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    sockaddr_union_t addr;\n    if (getpeername(sock->fd, &addr.addr, &(socklen_t) { sizeof(addr) })) {\n        return avs_errno(AVS_UNKNOWN_ERROR);\n    }\n    return stringify_sockaddr_port(&addr, out_buffer, out_buffer_size);\n}\n\nstatic avs_error_t net_local_port(avs_net_socket_t *sock_,\n                                  char *out_buffer,\n                                  size_t out_buffer_size) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    sockaddr_union_t addr;\n    if (getsockname(sock->fd, &addr.addr, &(socklen_t) { sizeof(addr) })) {\n        return avs_errno(AVS_UNKNOWN_ERROR);\n    }\n    return stringify_sockaddr_port(&addr, out_buffer, out_buffer_size);\n}\n\nstatic avs_error_t net_get_opt(avs_net_socket_t *sock_,\n                               avs_net_socket_opt_key_t option_key,\n                               avs_net_socket_opt_value_t *out_option_value) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    switch (option_key) {\n    case AVS_NET_SOCKET_OPT_RECV_TIMEOUT:\n        out_option_value->recv_timeout = sock->recv_timeout;\n        return AVS_OK;\n    case AVS_NET_SOCKET_OPT_STATE:\n        if (sock->fd < 0) {\n            out_option_value->state = AVS_NET_SOCKET_STATE_CLOSED;\n        } else if (sock->shut_down) {\n            out_option_value->state = AVS_NET_SOCKET_STATE_SHUTDOWN;\n        } else {\n            sockaddr_union_t addr;\n            if (!getpeername(sock->fd, &addr.addr,\n                             &(socklen_t) { sizeof(addr) })\n                    && ((addr.in.sin_family == AF_INET && addr.in.sin_port != 0)\n                        || (addr.in6.sin6_family == AF_INET6\n                            && addr.in6.sin6_port != 0))) {\n                out_option_value->state = AVS_NET_SOCKET_STATE_CONNECTED;\n            } else {\n                out_option_value->state = AVS_NET_SOCKET_STATE_BOUND;\n            }\n        }\n        return AVS_OK;\n    case AVS_NET_SOCKET_OPT_INNER_MTU:\n        out_option_value->mtu = 1464;\n        return AVS_OK;\n    case AVS_NET_SOCKET_HAS_BUFFERED_DATA:\n        out_option_value->flag = false;\n        return AVS_OK;\n    case AVS_NET_SOCKET_OPT_BYTES_SENT:\n        out_option_value->bytes_sent = sock->bytes_sent;\n        return AVS_OK;\n    case AVS_NET_SOCKET_OPT_BYTES_RECEIVED:\n        out_option_value->bytes_received = sock->bytes_received;\n        return AVS_OK;\n    default:\n        return avs_errno(AVS_ENOTSUP);\n    }\n}\n\nstatic avs_error_t net_set_opt(avs_net_socket_t *sock_,\n                               avs_net_socket_opt_key_t option_key,\n                               avs_net_socket_opt_value_t option_value) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    switch (option_key) {\n    case AVS_NET_SOCKET_OPT_RECV_TIMEOUT:\n        sock->recv_timeout = option_value.recv_timeout;\n        return AVS_OK;\n    default:\n        return avs_errno(AVS_ENOTSUP);\n    }\n}\n\nstatic const avs_net_socket_v_table_t NET_SOCKET_VTABLE = {\n    .connect = net_connect,\n    .send = net_send,\n    .receive = net_receive,\n    .bind = net_bind,\n    .close = net_close,\n    .shutdown = net_shutdown,\n    .cleanup = net_cleanup,\n    .get_system_socket = net_system_socket,\n    .get_remote_host = net_remote_host,\n    .get_remote_hostname = net_remote_hostname,\n    .get_remote_port = net_remote_port,\n    .get_local_port = net_local_port,\n    .get_opt = net_get_opt,\n    .set_opt = net_set_opt\n};\n\nstatic avs_error_t\nnet_create_socket(avs_net_socket_t **socket_ptr,\n                  const avs_net_socket_configuration_t *configuration,\n                  int socktype) {\n    assert(socket_ptr);\n    assert(!*socket_ptr);\n    (void) configuration;\n    net_socket_impl_t *socket =\n            (net_socket_impl_t *) avs_calloc(1, sizeof(net_socket_impl_t));\n    if (!socket) {\n        return avs_errno(AVS_ENOMEM);\n    }\n    socket->operations = &NET_SOCKET_VTABLE;\n    socket->socktype = socktype;\n    socket->fd = -1;\n    socket->recv_timeout = avs_time_duration_from_scalar(30, AVS_TIME_S);\n    socket->preferred_endpoint = configuration->preferred_endpoint;\n    *socket_ptr = (avs_net_socket_t *) socket;\n    return AVS_OK;\n}\n\navs_error_t _avs_net_create_udp_socket(avs_net_socket_t **socket_ptr,\n                                       const void *configuration) {\n    return net_create_socket(\n            socket_ptr, (const avs_net_socket_configuration_t *) configuration,\n            SOCK_DGRAM);\n}\n\navs_error_t _avs_net_create_tcp_socket(avs_net_socket_t **socket_ptr,\n                                       const void *configuration) {\n    return net_create_socket(\n            socket_ptr, (const avs_net_socket_configuration_t *) configuration,\n            SOCK_STREAM);\n}\n\navs_error_t\navs_net_resolved_endpoint_get_host_port(const avs_net_resolved_endpoint_t *endp,\n                                        char *host,\n                                        size_t hostlen,\n                                        char *serv,\n                                        size_t servlen) {\n    AVS_STATIC_ASSERT(sizeof(endp->data.buf) >= sizeof(sockaddr_union_t),\n                      data_buffer_big_enough);\n    if (endp->size != sizeof(sockaddr_union_t)) {\n        return avs_errno(AVS_EINVAL);\n    }\n    const sockaddr_union_t *addr = (const sockaddr_union_t *) &endp->data.buf;\n    avs_error_t err = AVS_OK;\n    (void) ((host\n             && avs_is_err(\n                        (err = stringify_sockaddr_host(addr, host, hostlen))))\n            || (serv\n                && avs_is_err((err = stringify_sockaddr_port(addr, serv,\n                                                             servlen)))));\n    return err;\n}\n"
  },
  {
    "path": "examples/custom-tls/minimal/src/tls_impl.c",
    "content": "#include <poll.h>\n\n#include <openssl/ssl.h>\n\n#include <avsystem/commons/avs_crypto_common.h>\n#include <avsystem/commons/avs_socket_v_table.h>\n#include <avsystem/commons/avs_utils.h>\n\n#ifndef AVS_COMMONS_WITH_CUSTOM_TLS\n#    error \"Custom implementation of the TLS layer requires AVS_COMMONS_WITH_CUSTOM_TLS\"\n#endif // AVS_COMMONS_WITH_CUSTOM_TLS\n\navs_error_t _avs_net_initialize_global_ssl_state(void);\nvoid _avs_net_cleanup_global_ssl_state(void);\navs_error_t _avs_net_create_ssl_socket(avs_net_socket_t **socket,\n                                       const void *socket_configuration);\navs_error_t _avs_net_create_dtls_socket(avs_net_socket_t **socket,\n                                        const void *socket_configuration);\n\navs_error_t _avs_net_initialize_global_ssl_state(void) {\n    if (!OPENSSL_init_ssl(OPENSSL_INIT_ADD_ALL_CIPHERS\n                                  | OPENSSL_INIT_ADD_ALL_DIGESTS,\n                          NULL)) {\n        return avs_errno(AVS_EPROTO);\n    }\n    return AVS_OK;\n}\n\nvoid _avs_net_cleanup_global_ssl_state(void) {}\n\nvoid avs_crypto_clear_buffer(void *buf, size_t size) {\n    OPENSSL_cleanse(buf, size);\n}\n\ntypedef struct {\n    const avs_net_socket_v_table_t *operations;\n    avs_net_socket_t *backend_socket;\n    SSL_CTX *ctx;\n    SSL *ssl;\n\n    char psk[256];\n    size_t psk_size;\n    char identity[128];\n    size_t identity_size;\n} tls_socket_impl_t;\n\nstatic avs_error_t perform_handshake(tls_socket_impl_t *sock,\n                                     const char *host) {\n    union {\n        struct sockaddr addr;\n        struct sockaddr_storage storage;\n    } peername;\n    const void *fd_ptr = avs_net_socket_get_system(sock->backend_socket);\n    if (!fd_ptr\n            || getpeername(*(const int *) fd_ptr, &peername.addr,\n                           &(socklen_t) { sizeof(peername) })) {\n        return avs_errno(AVS_EBADF);\n    }\n\n    sock->ssl = SSL_new(sock->ctx);\n    if (!sock->ssl) {\n        return avs_errno(AVS_ENOMEM);\n    }\n\n    SSL_set_app_data(sock->ssl, sock);\n    SSL_set_tlsext_host_name(sock->ssl, host);\n\n    BIO *bio = BIO_new_dgram(*(const int *) fd_ptr, 0);\n    if (!bio) {\n        return avs_errno(AVS_ENOMEM);\n    }\n    BIO_ctrl(bio, BIO_CTRL_DGRAM_SET_CONNECTED, 0, &peername.addr);\n    SSL_set_bio(sock->ssl, bio, bio);\n\n    if (SSL_connect(sock->ssl) <= 0) {\n        return avs_errno(AVS_EPROTO);\n    }\n    return AVS_OK;\n}\n\nstatic avs_error_t\ntls_connect(avs_net_socket_t *sock_, const char *host, const char *port) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    if (sock->ssl) {\n        return avs_errno(AVS_EBADF);\n    }\n    avs_error_t err;\n    if (avs_is_err((\n                err = avs_net_socket_connect(sock->backend_socket, host, port)))\n            || avs_is_err((err = perform_handshake(sock, host)))) {\n        if (sock->ssl) {\n            SSL_free(sock->ssl);\n            sock->ssl = NULL;\n        }\n        avs_net_socket_close(sock->backend_socket);\n    }\n    return err;\n}\n\nstatic avs_error_t\ntls_send(avs_net_socket_t *sock_, const void *buffer, size_t buffer_length) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    int result = SSL_write(sock->ssl, buffer, (int) buffer_length);\n    if (result < 0 || (size_t) result < buffer_length) {\n        return avs_errno(AVS_EPROTO);\n    }\n    return AVS_OK;\n}\n\nstatic avs_error_t tls_receive(avs_net_socket_t *sock_,\n                               size_t *out_bytes_received,\n                               void *buffer,\n                               size_t buffer_length) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    const void *fd_ptr = avs_net_socket_get_system(sock->backend_socket);\n    avs_net_socket_opt_value_t timeout;\n    if (!fd_ptr\n            || avs_is_err(avs_net_socket_get_opt(\n                       sock->backend_socket, AVS_NET_SOCKET_OPT_RECV_TIMEOUT,\n                       &timeout))) {\n        return avs_errno(AVS_EBADF);\n    }\n    struct pollfd pfd = {\n        .fd = *(const int *) fd_ptr,\n        .events = POLLIN\n    };\n    int64_t timeout_ms;\n    if (avs_time_duration_to_scalar(&timeout_ms, AVS_TIME_MS,\n                                    timeout.recv_timeout)) {\n        timeout_ms = -1;\n    } else if (timeout_ms < 0) {\n        timeout_ms = 0;\n    }\n    if (poll(&pfd, 1, (int) timeout_ms) == 0) {\n        return avs_errno(AVS_ETIMEDOUT);\n    }\n    int bytes_received = SSL_read(sock->ssl, buffer, (int) buffer_length);\n    if (bytes_received < 0) {\n        return avs_errno(AVS_EPROTO);\n    }\n    *out_bytes_received = (size_t) bytes_received;\n    if (buffer_length > 0 && (size_t) bytes_received == buffer_length) {\n        return avs_errno(AVS_EMSGSIZE);\n    }\n    return AVS_OK;\n}\n\nstatic avs_error_t\ntls_bind(avs_net_socket_t *sock_, const char *address, const char *port) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    return avs_net_socket_bind(sock->backend_socket, address, port);\n}\n\nstatic avs_error_t tls_close(avs_net_socket_t *sock_) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    if (sock->ssl) {\n        SSL_free(sock->ssl);\n        sock->ssl = NULL;\n    }\n    return avs_net_socket_close(sock->backend_socket);\n}\n\nstatic avs_error_t tls_shutdown(avs_net_socket_t *sock_) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    return avs_net_socket_shutdown(sock->backend_socket);\n}\n\nstatic avs_error_t tls_cleanup(avs_net_socket_t **sock_ptr) {\n    avs_error_t err = AVS_OK;\n    if (sock_ptr && *sock_ptr) {\n        tls_socket_impl_t *sock = (tls_socket_impl_t *) *sock_ptr;\n        tls_close(*sock_ptr);\n        avs_net_socket_cleanup(&sock->backend_socket);\n        if (sock->ctx) {\n            SSL_CTX_free(sock->ctx);\n        }\n        avs_free(sock);\n        *sock_ptr = NULL;\n    }\n    return err;\n}\n\nstatic const void *tls_system_socket(avs_net_socket_t *sock_) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    return avs_net_socket_get_system(sock->backend_socket);\n}\n\nstatic avs_error_t tls_remote_host(avs_net_socket_t *sock_,\n                                   char *out_buffer,\n                                   size_t out_buffer_size) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    return avs_net_socket_get_remote_host(sock->backend_socket, out_buffer,\n                                          out_buffer_size);\n}\n\nstatic avs_error_t tls_remote_hostname(avs_net_socket_t *sock_,\n                                       char *out_buffer,\n                                       size_t out_buffer_size) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    return avs_net_socket_get_remote_hostname(sock->backend_socket, out_buffer,\n                                              out_buffer_size);\n}\n\nstatic avs_error_t tls_remote_port(avs_net_socket_t *sock_,\n                                   char *out_buffer,\n                                   size_t out_buffer_size) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    return avs_net_socket_get_remote_port(sock->backend_socket, out_buffer,\n                                          out_buffer_size);\n}\n\nstatic avs_error_t tls_local_port(avs_net_socket_t *sock_,\n                                  char *out_buffer,\n                                  size_t out_buffer_size) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    return avs_net_socket_get_local_port(sock->backend_socket, out_buffer,\n                                         out_buffer_size);\n}\n\nstatic avs_error_t tls_get_opt(avs_net_socket_t *sock_,\n                               avs_net_socket_opt_key_t option_key,\n                               avs_net_socket_opt_value_t *out_option_value) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    switch (option_key) {\n    case AVS_NET_SOCKET_OPT_INNER_MTU: {\n        avs_error_t err = avs_net_socket_get_opt(sock->backend_socket,\n                                                 AVS_NET_SOCKET_OPT_INNER_MTU,\n                                                 out_option_value);\n        if (avs_is_ok(err)) {\n            out_option_value->mtu = AVS_MAX(out_option_value->mtu - 64, 0);\n        }\n        return err;\n    }\n    case AVS_NET_SOCKET_HAS_BUFFERED_DATA:\n        out_option_value->flag = (sock->ssl && SSL_pending(sock->ssl) > 0);\n        return AVS_OK;\n    default:\n        return avs_net_socket_get_opt(sock->backend_socket, option_key,\n                                      out_option_value);\n    }\n}\n\nstatic avs_error_t tls_set_opt(avs_net_socket_t *sock_,\n                               avs_net_socket_opt_key_t option_key,\n                               avs_net_socket_opt_value_t option_value) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    return avs_net_socket_set_opt(sock->backend_socket, option_key,\n                                  option_value);\n}\n\nstatic const avs_net_socket_v_table_t TLS_SOCKET_VTABLE = {\n    .connect = tls_connect,\n    .send = tls_send,\n    .receive = tls_receive,\n    .bind = tls_bind,\n    .close = tls_close,\n    .shutdown = tls_shutdown,\n    .cleanup = tls_cleanup,\n    .get_system_socket = tls_system_socket,\n    .get_remote_host = tls_remote_host,\n    .get_remote_hostname = tls_remote_hostname,\n    .get_remote_port = tls_remote_port,\n    .get_local_port = tls_local_port,\n    .get_opt = tls_get_opt,\n    .set_opt = tls_set_opt\n};\n\nstatic unsigned int psk_client_cb(SSL *ssl,\n                                  const char *hint,\n                                  char *identity,\n                                  unsigned int max_identity_len,\n                                  unsigned char *psk,\n                                  unsigned int max_psk_len) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) SSL_get_app_data(ssl);\n\n    (void) hint;\n\n    if (!sock || max_psk_len < sock->psk_size\n            || max_identity_len < sock->identity_size + 1) {\n        return 0;\n    }\n\n    memcpy(psk, sock->psk, sock->psk_size);\n    memcpy(identity, sock->identity, sock->identity_size);\n    identity[sock->identity_size] = '\\0';\n\n    return (unsigned int) sock->psk_size;\n}\n\nstatic avs_error_t configure_psk(tls_socket_impl_t *sock,\n                                 const avs_net_psk_info_t *psk) {\n    if (psk->key.desc.source != AVS_CRYPTO_DATA_SOURCE_BUFFER\n            || psk->identity.desc.source != AVS_CRYPTO_DATA_SOURCE_BUFFER) {\n        return avs_errno(AVS_EINVAL);\n    }\n\n    const void *key_ptr = psk->key.desc.info.buffer.buffer;\n    size_t key_size = psk->key.desc.info.buffer.buffer_size;\n\n    const void *identity_ptr = psk->identity.desc.info.buffer.buffer;\n    size_t identity_size = psk->identity.desc.info.buffer.buffer_size;\n\n    if (key_size > sizeof(sock->psk)\n            || identity_size > sizeof(sock->identity)) {\n        return avs_errno(AVS_EINVAL);\n    }\n    memcpy(sock->psk, key_ptr, key_size);\n    sock->psk_size = key_size;\n    memcpy(sock->identity, identity_ptr, identity_size);\n    sock->identity_size = identity_size;\n    SSL_CTX_set_cipher_list(sock->ctx, \"PSK\");\n    SSL_CTX_set_psk_client_callback(sock->ctx, psk_client_cb);\n    SSL_CTX_set_verify(sock->ctx, SSL_VERIFY_PEER, NULL);\n    return AVS_OK;\n}\n\navs_error_t _avs_net_create_dtls_socket(avs_net_socket_t **socket_ptr,\n                                        const void *configuration_) {\n    assert(socket_ptr);\n    assert(!*socket_ptr);\n    assert(configuration_);\n    const avs_net_ssl_configuration_t *configuration =\n            (const avs_net_ssl_configuration_t *) configuration_;\n    tls_socket_impl_t *socket =\n            (tls_socket_impl_t *) avs_calloc(1, sizeof(tls_socket_impl_t));\n    if (!socket) {\n        return avs_errno(AVS_ENOMEM);\n    }\n    *socket_ptr = (avs_net_socket_t *) socket;\n    socket->operations = &TLS_SOCKET_VTABLE;\n\n    avs_error_t err = AVS_OK;\n    if (avs_is_ok((err = avs_net_udp_socket_create(\n                           &socket->backend_socket,\n                           &configuration->backend_configuration)))\n            && !(socket->ctx = SSL_CTX_new(DTLS_method()))) {\n        err = avs_errno(AVS_ENOMEM);\n    }\n    if (avs_is_ok(err)) {\n        switch (configuration->security.mode) {\n        case AVS_NET_SECURITY_PSK:\n            err = configure_psk(socket, &configuration->security.data.psk);\n            break;\n        default:\n            err = avs_errno(AVS_ENOTSUP);\n        }\n    }\n    if (avs_is_err(err)) {\n        avs_net_socket_cleanup(socket_ptr);\n        return err;\n    }\n    SSL_CTX_set_mode(socket->ctx, SSL_MODE_AUTO_RETRY);\n    return AVS_OK;\n}\n\navs_error_t _avs_net_create_ssl_socket(avs_net_socket_t **socket_ptr,\n                                       const void *configuration) {\n    return avs_errno(AVS_ENOTSUP);\n}\n"
  },
  {
    "path": "examples/custom-tls/resumption-buffer/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.16)\nproject(custom-tls-resumption-buffer C)\n\nset(CMAKE_C_STANDARD 99)\n\nfind_package(anjay REQUIRED)\nfind_package(OpenSSL REQUIRED)\n\nadd_executable(${PROJECT_NAME}\n               src/main.c\n               src/net_impl.c\n               src/tls_impl.c)\ntarget_link_libraries(${PROJECT_NAME} PRIVATE anjay OpenSSL::SSL)\n"
  },
  {
    "path": "examples/custom-tls/resumption-buffer/src/main.c",
    "content": "#include <anjay/anjay.h>\n#include <anjay/security.h>\n#include <anjay/server.h>\n#include <avsystem/commons/avs_log.h>\n\n#include <poll.h>\n\nint main_loop(anjay_t *anjay) {\n    while (true) {\n        // Obtain all network data sources\n        AVS_LIST(avs_net_socket_t *const) sockets = anjay_get_sockets(anjay);\n\n        // Prepare to poll() on them\n        size_t numsocks = AVS_LIST_SIZE(sockets);\n        struct pollfd pollfds[numsocks];\n        size_t i = 0;\n        AVS_LIST(avs_net_socket_t *const) sock;\n        AVS_LIST_FOREACH(sock, sockets) {\n            pollfds[i].fd = *(const int *) avs_net_socket_get_system(*sock);\n            pollfds[i].events = POLLIN;\n            pollfds[i].revents = 0;\n            ++i;\n        }\n\n        const int max_wait_time_ms = 1000;\n        // Determine the expected time to the next job in milliseconds.\n        // If there is no job we will wait till something arrives for\n        // at most 1 second (i.e. max_wait_time_ms).\n        int wait_ms =\n                anjay_sched_calculate_wait_time_ms(anjay, max_wait_time_ms);\n\n        // Wait for the events if necessary, and handle them.\n        if (poll(pollfds, numsocks, wait_ms) > 0) {\n            int socket_id = 0;\n            AVS_LIST(avs_net_socket_t *const) socket = NULL;\n            AVS_LIST_FOREACH(socket, sockets) {\n                if (pollfds[socket_id].revents) {\n                    if (anjay_serve(anjay, *socket)) {\n                        avs_log(tutorial, ERROR, \"anjay_serve failed\");\n                    }\n                }\n                ++socket_id;\n            }\n        }\n\n        // Finally run the scheduler\n        anjay_sched_run(anjay);\n    }\n    return 0;\n}\n\n// Installs Security Object and adds an instance of it.\n// An instance of Security Object provides information needed to connect to\n// LwM2M server.\nstatic int setup_security_object(anjay_t *anjay) {\n    if (anjay_security_object_install(anjay)) {\n        return -1;\n    }\n\n    static const char PSK_IDENTITY[] = \"identity\";\n    static const char PSK_KEY[] = \"P4s$w0rd\";\n\n    anjay_security_instance_t security_instance = {\n        .ssid = 1,\n        .server_uri = \"coaps://eu.iot.avsystem.cloud:5684\",\n        .security_mode = ANJAY_SECURITY_PSK,\n        .public_cert_or_psk_identity = (const uint8_t *) PSK_IDENTITY,\n        .public_cert_or_psk_identity_size = strlen(PSK_IDENTITY),\n        .private_cert_or_psk_key = (const uint8_t *) PSK_KEY,\n        .private_cert_or_psk_key_size = strlen(PSK_KEY)\n    };\n\n    // Anjay will assign Instance ID automatically\n    anjay_iid_t security_instance_id = ANJAY_ID_INVALID;\n    if (anjay_security_object_add_instance(anjay, &security_instance,\n                                           &security_instance_id)) {\n        return -1;\n    }\n\n    return 0;\n}\n\n// Installs Server Object and adds an instance of it.\n// An instance of Server Object provides the data related to a LwM2M Server.\nstatic int setup_server_object(anjay_t *anjay) {\n    if (anjay_server_object_install(anjay)) {\n        return -1;\n    }\n\n    const anjay_server_instance_t server_instance = {\n        // Server Short ID\n        .ssid = 1,\n        // Client will send Update message often than every 60 seconds\n        .lifetime = 60,\n        // Disable Default Minimum Period resource\n        .default_min_period = -1,\n        // Disable Default Maximum Period resource\n        .default_max_period = -1,\n        // Disable Disable Timeout resource\n        .disable_timeout = -1,\n        // Sets preferred transport to UDP\n        .binding = \"U\"\n    };\n\n    // Anjay will assign Instance ID automatically\n    anjay_iid_t server_instance_id = ANJAY_ID_INVALID;\n    if (anjay_server_object_add_instance(anjay, &server_instance,\n                                         &server_instance_id)) {\n        return -1;\n    }\n\n    return 0;\n}\n\nint main(int argc, char *argv[]) {\n    if (argc != 2) {\n        avs_log(tutorial, ERROR, \"usage: %s ENDPOINT_NAME\", argv[0]);\n        return -1;\n    }\n\n    const anjay_configuration_t CONFIG = {\n        .endpoint_name = argv[1],\n        .in_buffer_size = 4000,\n        .out_buffer_size = 4000,\n        .msg_cache_size = 4000\n    };\n\n    anjay_t *anjay = anjay_new(&CONFIG);\n    if (!anjay) {\n        avs_log(tutorial, ERROR, \"Could not create Anjay object\");\n        return -1;\n    }\n\n    int result = 0;\n    // Setup necessary objects\n    if (setup_security_object(anjay) || setup_server_object(anjay)) {\n        result = -1;\n    }\n\n    if (!result) {\n        result = main_loop(anjay);\n    }\n\n    anjay_delete(anjay);\n    return result;\n}\n"
  },
  {
    "path": "examples/custom-tls/resumption-buffer/src/net_impl.c",
    "content": "#include <inttypes.h>\n\n#include <netdb.h>\n#include <poll.h>\n#include <unistd.h>\n\n#include <arpa/inet.h>\n\n#include <sys/socket.h>\n#include <sys/types.h>\n\n#include <avsystem/commons/avs_socket_v_table.h>\n#include <avsystem/commons/avs_utils.h>\n\n#ifdef AVS_COMMONS_NET_WITH_POSIX_AVS_SOCKET\n#    error \"Custom implementation of the network layer conflicts with AVS_COMMONS_NET_WITH_POSIX_AVS_SOCKET\"\n#endif // AVS_COMMONS_NET_WITH_POSIX_AVS_SOCKET\n\navs_error_t _avs_net_initialize_global_compat_state(void);\nvoid _avs_net_cleanup_global_compat_state(void);\navs_error_t _avs_net_create_tcp_socket(avs_net_socket_t **socket,\n                                       const void *socket_configuration);\navs_error_t _avs_net_create_udp_socket(avs_net_socket_t **socket,\n                                       const void *socket_configuration);\n\navs_error_t _avs_net_initialize_global_compat_state(void) {\n    return AVS_OK;\n}\n\nvoid _avs_net_cleanup_global_compat_state(void) {}\n\ntypedef struct {\n    const avs_net_socket_v_table_t *operations;\n    int socktype;\n    int fd;\n    avs_time_duration_t recv_timeout;\n    char remote_hostname[256];\n    bool shut_down;\n    size_t bytes_sent;\n    size_t bytes_received;\n    avs_net_resolved_endpoint_t *preferred_endpoint;\n} net_socket_impl_t;\n\ntypedef union {\n    struct sockaddr addr;\n    struct sockaddr_in in;\n    struct sockaddr_in6 in6;\n    struct sockaddr_storage storage;\n} sockaddr_union_t;\n\nstatic avs_error_t\nnet_connect(avs_net_socket_t *sock_, const char *host, const char *port) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    struct addrinfo hints = {\n        .ai_socktype = sock->socktype\n    };\n    if (sock->fd >= 0) {\n        getsockopt(sock->fd, SOL_SOCKET, SO_DOMAIN, &hints.ai_family,\n                   &(socklen_t) { sizeof(hints.ai_family) });\n    }\n    struct addrinfo *addrs = NULL;\n    avs_error_t err = AVS_OK;\n    if (getaddrinfo(host, port, &hints, &addrs) || !addrs) {\n        err = avs_errno(AVS_EADDRNOTAVAIL);\n    } else if (sock->fd < 0\n               && (sock->fd = socket(addrs->ai_family, addrs->ai_socktype,\n                                     addrs->ai_protocol))\n                          < 0) {\n        err = avs_errno(AVS_UNKNOWN_ERROR);\n    } else {\n        const struct addrinfo *addr = addrs;\n        if (sock->preferred_endpoint\n                && sock->preferred_endpoint->size == sizeof(sockaddr_union_t)) {\n            while (addr) {\n                if (addr->ai_addrlen <= sizeof(sockaddr_union_t)\n                        && memcmp(addr->ai_addr,\n                                  sock->preferred_endpoint->data.buf,\n                                  addr->ai_addrlen)\n                                       == 0) {\n                    break;\n                }\n                addr = addr->ai_next;\n            }\n        }\n        if (!addr) {\n            // Preferred endpoint not found, use the first one\n            addr = addrs;\n        }\n        if (connect(sock->fd, addr->ai_addr, addr->ai_addrlen)) {\n            err = avs_errno(AVS_ECONNREFUSED);\n        }\n        if (sock->preferred_endpoint && avs_is_ok(err)) {\n            assert(addr->ai_addrlen <= sizeof(sockaddr_union_t));\n            memcpy(sock->preferred_endpoint->data.buf, addr->ai_addr,\n                   addr->ai_addrlen);\n            sock->preferred_endpoint->size = sizeof(sockaddr_union_t);\n        }\n    }\n    if (avs_is_ok(err)) {\n        sock->shut_down = false;\n        snprintf(sock->remote_hostname, sizeof(sock->remote_hostname), \"%s\",\n                 host);\n    }\n    freeaddrinfo(addrs);\n    return err;\n}\n\nstatic avs_error_t\nnet_send(avs_net_socket_t *sock_, const void *buffer, size_t buffer_length) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    ssize_t written = send(sock->fd, buffer, buffer_length, MSG_NOSIGNAL);\n    if (written >= 0) {\n        sock->bytes_sent += (size_t) written;\n        if ((size_t) written == buffer_length) {\n            return AVS_OK;\n        }\n    }\n    return avs_errno(AVS_EIO);\n}\n\nstatic avs_error_t net_receive(avs_net_socket_t *sock_,\n                               size_t *out_bytes_received,\n                               void *buffer,\n                               size_t buffer_length) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    struct pollfd pfd = {\n        .fd = sock->fd,\n        .events = POLLIN\n    };\n    int64_t timeout_ms;\n    if (avs_time_duration_to_scalar(&timeout_ms, AVS_TIME_MS,\n                                    sock->recv_timeout)) {\n        timeout_ms = -1;\n    } else if (timeout_ms < 0) {\n        timeout_ms = 0;\n    }\n    if (poll(&pfd, 1, (int) timeout_ms) == 0) {\n        return avs_errno(AVS_ETIMEDOUT);\n    }\n    ssize_t bytes_received = read(sock->fd, buffer, buffer_length);\n    if (bytes_received < 0) {\n        return avs_errno(AVS_EIO);\n    }\n    *out_bytes_received = (size_t) bytes_received;\n    sock->bytes_received += (size_t) bytes_received;\n    if (buffer_length > 0 && sock->socktype == SOCK_DGRAM\n            && (size_t) bytes_received == buffer_length) {\n        return avs_errno(AVS_EMSGSIZE);\n    }\n    return AVS_OK;\n}\n\nstatic avs_error_t\nnet_bind(avs_net_socket_t *sock_, const char *address, const char *port) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    struct addrinfo hints = {\n        .ai_flags = AI_PASSIVE,\n        .ai_socktype = sock->socktype\n    };\n    if (sock->fd >= 0) {\n        getsockopt(sock->fd, SOL_SOCKET, SO_DOMAIN, &hints.ai_family,\n                   &(socklen_t) { sizeof(hints.ai_family) });\n    }\n    struct addrinfo *addr = NULL;\n    avs_error_t err = AVS_OK;\n    if (getaddrinfo(address, port, &hints, &addr) || !addr) {\n        err = avs_errno(AVS_EADDRNOTAVAIL);\n    } else if ((sock->fd < 0\n                && (sock->fd = socket(addr->ai_family, addr->ai_socktype,\n                                      addr->ai_protocol))\n                           < 0)\n               || setsockopt(sock->fd, SOL_SOCKET, SO_REUSEADDR, &(int) { 1 },\n                             sizeof(int))) {\n        err = avs_errno(AVS_UNKNOWN_ERROR);\n    } else if (bind(sock->fd, addr->ai_addr, addr->ai_addrlen)) {\n        err = avs_errno(AVS_ECONNREFUSED);\n    } else {\n        sock->shut_down = false;\n    }\n    if (avs_is_err(err) && sock->fd >= 0) {\n        close(sock->fd);\n        sock->fd = -1;\n    }\n    freeaddrinfo(addr);\n    return err;\n}\n\nstatic avs_error_t net_close(avs_net_socket_t *sock_) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    avs_error_t err = AVS_OK;\n    if (sock->fd >= 0) {\n        if (close(sock->fd)) {\n            err = avs_errno(AVS_EIO);\n        }\n        sock->fd = -1;\n        sock->shut_down = false;\n    }\n    return err;\n}\n\nstatic avs_error_t net_shutdown(avs_net_socket_t *sock_) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    avs_error_t err = avs_errno(AVS_EBADF);\n    if (sock->fd >= 0) {\n        err = shutdown(sock->fd, SHUT_RDWR) ? avs_errno(AVS_EIO) : AVS_OK;\n        sock->shut_down = true;\n    }\n    return err;\n}\n\nstatic avs_error_t net_cleanup(avs_net_socket_t **sock_ptr) {\n    avs_error_t err = AVS_OK;\n    if (sock_ptr && *sock_ptr) {\n        err = net_close(*sock_ptr);\n        avs_free(*sock_ptr);\n        *sock_ptr = NULL;\n    }\n    return err;\n}\n\nstatic const void *net_system_socket(avs_net_socket_t *sock_) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    return &sock->fd;\n}\n\nstatic avs_error_t stringify_sockaddr_host(const sockaddr_union_t *addr,\n                                           char *out_buffer,\n                                           size_t out_buffer_size) {\n    if ((addr->in.sin_family == AF_INET\n         && inet_ntop(AF_INET, &addr->in.sin_addr, out_buffer,\n                      (socklen_t) out_buffer_size))\n            || (addr->in6.sin6_family == AF_INET6\n                && inet_ntop(AF_INET6, &addr->in6.sin6_addr, out_buffer,\n                             (socklen_t) out_buffer_size))) {\n        return AVS_OK;\n    }\n    return avs_errno(AVS_UNKNOWN_ERROR);\n}\n\nstatic avs_error_t stringify_sockaddr_port(const sockaddr_union_t *addr,\n                                           char *out_buffer,\n                                           size_t out_buffer_size) {\n    if ((addr->in.sin_family == AF_INET\n         && avs_simple_snprintf(out_buffer, out_buffer_size, \"%\" PRIu16,\n                                ntohs(addr->in.sin_port))\n                    >= 0)\n            || (addr->in6.sin6_family == AF_INET6\n                && avs_simple_snprintf(out_buffer, out_buffer_size, \"%\" PRIu16,\n                                       ntohs(addr->in6.sin6_port))\n                           >= 0)) {\n        return AVS_OK;\n    }\n    return avs_errno(AVS_UNKNOWN_ERROR);\n}\n\nstatic avs_error_t net_remote_host(avs_net_socket_t *sock_,\n                                   char *out_buffer,\n                                   size_t out_buffer_size) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    sockaddr_union_t addr;\n    if (getpeername(sock->fd, &addr.addr, &(socklen_t) { sizeof(addr) })) {\n        return avs_errno(AVS_UNKNOWN_ERROR);\n    }\n    return stringify_sockaddr_host(&addr, out_buffer, out_buffer_size);\n}\n\nstatic avs_error_t net_remote_hostname(avs_net_socket_t *sock_,\n                                       char *out_buffer,\n                                       size_t out_buffer_size) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    return avs_simple_snprintf(out_buffer, out_buffer_size, \"%s\",\n                               sock->remote_hostname)\n                           < 0\n                   ? avs_errno(AVS_UNKNOWN_ERROR)\n                   : AVS_OK;\n}\n\nstatic avs_error_t net_remote_port(avs_net_socket_t *sock_,\n                                   char *out_buffer,\n                                   size_t out_buffer_size) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    sockaddr_union_t addr;\n    if (getpeername(sock->fd, &addr.addr, &(socklen_t) { sizeof(addr) })) {\n        return avs_errno(AVS_UNKNOWN_ERROR);\n    }\n    return stringify_sockaddr_port(&addr, out_buffer, out_buffer_size);\n}\n\nstatic avs_error_t net_local_port(avs_net_socket_t *sock_,\n                                  char *out_buffer,\n                                  size_t out_buffer_size) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    sockaddr_union_t addr;\n    if (getsockname(sock->fd, &addr.addr, &(socklen_t) { sizeof(addr) })) {\n        return avs_errno(AVS_UNKNOWN_ERROR);\n    }\n    return stringify_sockaddr_port(&addr, out_buffer, out_buffer_size);\n}\n\nstatic avs_error_t net_get_opt(avs_net_socket_t *sock_,\n                               avs_net_socket_opt_key_t option_key,\n                               avs_net_socket_opt_value_t *out_option_value) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    switch (option_key) {\n    case AVS_NET_SOCKET_OPT_RECV_TIMEOUT:\n        out_option_value->recv_timeout = sock->recv_timeout;\n        return AVS_OK;\n    case AVS_NET_SOCKET_OPT_STATE:\n        if (sock->fd < 0) {\n            out_option_value->state = AVS_NET_SOCKET_STATE_CLOSED;\n        } else if (sock->shut_down) {\n            out_option_value->state = AVS_NET_SOCKET_STATE_SHUTDOWN;\n        } else {\n            sockaddr_union_t addr;\n            if (!getpeername(sock->fd, &addr.addr,\n                             &(socklen_t) { sizeof(addr) })\n                    && ((addr.in.sin_family == AF_INET && addr.in.sin_port != 0)\n                        || (addr.in6.sin6_family == AF_INET6\n                            && addr.in6.sin6_port != 0))) {\n                out_option_value->state = AVS_NET_SOCKET_STATE_CONNECTED;\n            } else {\n                out_option_value->state = AVS_NET_SOCKET_STATE_BOUND;\n            }\n        }\n        return AVS_OK;\n    case AVS_NET_SOCKET_OPT_INNER_MTU:\n        out_option_value->mtu = 1464;\n        return AVS_OK;\n    case AVS_NET_SOCKET_HAS_BUFFERED_DATA:\n        out_option_value->flag = false;\n        return AVS_OK;\n    case AVS_NET_SOCKET_OPT_BYTES_SENT:\n        out_option_value->bytes_sent = sock->bytes_sent;\n        return AVS_OK;\n    case AVS_NET_SOCKET_OPT_BYTES_RECEIVED:\n        out_option_value->bytes_received = sock->bytes_received;\n        return AVS_OK;\n    default:\n        return avs_errno(AVS_ENOTSUP);\n    }\n}\n\nstatic avs_error_t net_set_opt(avs_net_socket_t *sock_,\n                               avs_net_socket_opt_key_t option_key,\n                               avs_net_socket_opt_value_t option_value) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    switch (option_key) {\n    case AVS_NET_SOCKET_OPT_RECV_TIMEOUT:\n        sock->recv_timeout = option_value.recv_timeout;\n        return AVS_OK;\n    default:\n        return avs_errno(AVS_ENOTSUP);\n    }\n}\n\nstatic const avs_net_socket_v_table_t NET_SOCKET_VTABLE = {\n    .connect = net_connect,\n    .send = net_send,\n    .receive = net_receive,\n    .bind = net_bind,\n    .close = net_close,\n    .shutdown = net_shutdown,\n    .cleanup = net_cleanup,\n    .get_system_socket = net_system_socket,\n    .get_remote_host = net_remote_host,\n    .get_remote_hostname = net_remote_hostname,\n    .get_remote_port = net_remote_port,\n    .get_local_port = net_local_port,\n    .get_opt = net_get_opt,\n    .set_opt = net_set_opt\n};\n\nstatic avs_error_t\nnet_create_socket(avs_net_socket_t **socket_ptr,\n                  const avs_net_socket_configuration_t *configuration,\n                  int socktype) {\n    assert(socket_ptr);\n    assert(!*socket_ptr);\n    (void) configuration;\n    net_socket_impl_t *socket =\n            (net_socket_impl_t *) avs_calloc(1, sizeof(net_socket_impl_t));\n    if (!socket) {\n        return avs_errno(AVS_ENOMEM);\n    }\n    socket->operations = &NET_SOCKET_VTABLE;\n    socket->socktype = socktype;\n    socket->fd = -1;\n    socket->recv_timeout = avs_time_duration_from_scalar(30, AVS_TIME_S);\n    socket->preferred_endpoint = configuration->preferred_endpoint;\n    *socket_ptr = (avs_net_socket_t *) socket;\n    return AVS_OK;\n}\n\navs_error_t _avs_net_create_udp_socket(avs_net_socket_t **socket_ptr,\n                                       const void *configuration) {\n    return net_create_socket(\n            socket_ptr, (const avs_net_socket_configuration_t *) configuration,\n            SOCK_DGRAM);\n}\n\navs_error_t _avs_net_create_tcp_socket(avs_net_socket_t **socket_ptr,\n                                       const void *configuration) {\n    return net_create_socket(\n            socket_ptr, (const avs_net_socket_configuration_t *) configuration,\n            SOCK_STREAM);\n}\n\navs_error_t\navs_net_resolved_endpoint_get_host_port(const avs_net_resolved_endpoint_t *endp,\n                                        char *host,\n                                        size_t hostlen,\n                                        char *serv,\n                                        size_t servlen) {\n    AVS_STATIC_ASSERT(sizeof(endp->data.buf) >= sizeof(sockaddr_union_t),\n                      data_buffer_big_enough);\n    if (endp->size != sizeof(sockaddr_union_t)) {\n        return avs_errno(AVS_EINVAL);\n    }\n    const sockaddr_union_t *addr = (const sockaddr_union_t *) &endp->data.buf;\n    avs_error_t err = AVS_OK;\n    (void) ((host\n             && avs_is_err(\n                        (err = stringify_sockaddr_host(addr, host, hostlen))))\n            || (serv\n                && avs_is_err((err = stringify_sockaddr_port(addr, serv,\n                                                             servlen)))));\n    return err;\n}\n"
  },
  {
    "path": "examples/custom-tls/resumption-buffer/src/tls_impl.c",
    "content": "#include <poll.h>\n\n#include <openssl/ssl.h>\n\n#include <avsystem/commons/avs_crypto_common.h>\n#include <avsystem/commons/avs_socket_v_table.h>\n#include <avsystem/commons/avs_utils.h>\n\n#ifndef AVS_COMMONS_WITH_CUSTOM_TLS\n#    error \"Custom implementation of the TLS layer requires AVS_COMMONS_WITH_CUSTOM_TLS\"\n#endif // AVS_COMMONS_WITH_CUSTOM_TLS\n\navs_error_t _avs_net_initialize_global_ssl_state(void);\nvoid _avs_net_cleanup_global_ssl_state(void);\navs_error_t _avs_net_create_ssl_socket(avs_net_socket_t **socket,\n                                       const void *socket_configuration);\navs_error_t _avs_net_create_dtls_socket(avs_net_socket_t **socket,\n                                        const void *socket_configuration);\n\navs_error_t _avs_net_initialize_global_ssl_state(void) {\n    if (!OPENSSL_init_ssl(OPENSSL_INIT_ADD_ALL_CIPHERS\n                                  | OPENSSL_INIT_ADD_ALL_DIGESTS,\n                          NULL)) {\n        return avs_errno(AVS_EPROTO);\n    }\n    return AVS_OK;\n}\n\nvoid _avs_net_cleanup_global_ssl_state(void) {}\n\nvoid avs_crypto_clear_buffer(void *buf, size_t size) {\n    OPENSSL_cleanse(buf, size);\n}\n\ntypedef struct {\n    const avs_net_socket_v_table_t *operations;\n    avs_net_socket_t *backend_socket;\n    SSL_CTX *ctx;\n    SSL *ssl;\n\n    char psk[256];\n    size_t psk_size;\n    char identity[128];\n    size_t identity_size;\n\n    void *session_resumption_buffer;\n    size_t session_resumption_buffer_size;\n} tls_socket_impl_t;\n\nstatic avs_error_t perform_handshake(tls_socket_impl_t *sock,\n                                     const char *host) {\n    union {\n        struct sockaddr addr;\n        struct sockaddr_storage storage;\n    } peername;\n    const void *fd_ptr = avs_net_socket_get_system(sock->backend_socket);\n    if (!fd_ptr\n            || getpeername(*(const int *) fd_ptr, &peername.addr,\n                           &(socklen_t) { sizeof(peername) })) {\n        return avs_errno(AVS_EBADF);\n    }\n\n    sock->ssl = SSL_new(sock->ctx);\n    if (!sock->ssl) {\n        return avs_errno(AVS_ENOMEM);\n    }\n\n    SSL_set_app_data(sock->ssl, sock);\n    SSL_set_tlsext_host_name(sock->ssl, host);\n\n    BIO *bio = BIO_new_dgram(*(const int *) fd_ptr, 0);\n    if (!bio) {\n        return avs_errno(AVS_ENOMEM);\n    }\n    BIO_ctrl(bio, BIO_CTRL_DGRAM_SET_CONNECTED, 0, &peername.addr);\n    SSL_set_bio(sock->ssl, bio, bio);\n\n    if (sock->session_resumption_buffer) {\n        const unsigned char *ptr =\n                (const unsigned char *) sock->session_resumption_buffer;\n        SSL_SESSION *session =\n                d2i_SSL_SESSION(NULL, &ptr,\n                                sock->session_resumption_buffer_size);\n        if (session) {\n            SSL_set_session(sock->ssl, session);\n            SSL_SESSION_free(session);\n        }\n    }\n\n    if (SSL_connect(sock->ssl) <= 0) {\n        return avs_errno(AVS_EPROTO);\n    }\n    return AVS_OK;\n}\n\nstatic avs_error_t\ntls_connect(avs_net_socket_t *sock_, const char *host, const char *port) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    if (sock->ssl) {\n        return avs_errno(AVS_EBADF);\n    }\n    avs_error_t err;\n    if (avs_is_err((\n                err = avs_net_socket_connect(sock->backend_socket, host, port)))\n            || avs_is_err((err = perform_handshake(sock, host)))) {\n        if (sock->ssl) {\n            SSL_free(sock->ssl);\n            sock->ssl = NULL;\n        }\n        avs_net_socket_close(sock->backend_socket);\n    }\n    return err;\n}\n\nstatic avs_error_t\ntls_send(avs_net_socket_t *sock_, const void *buffer, size_t buffer_length) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    int result = SSL_write(sock->ssl, buffer, (int) buffer_length);\n    if (result < 0 || (size_t) result < buffer_length) {\n        return avs_errno(AVS_EPROTO);\n    }\n    return AVS_OK;\n}\n\nstatic avs_error_t tls_receive(avs_net_socket_t *sock_,\n                               size_t *out_bytes_received,\n                               void *buffer,\n                               size_t buffer_length) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    const void *fd_ptr = avs_net_socket_get_system(sock->backend_socket);\n    avs_net_socket_opt_value_t timeout;\n    if (!fd_ptr\n            || avs_is_err(avs_net_socket_get_opt(\n                       sock->backend_socket, AVS_NET_SOCKET_OPT_RECV_TIMEOUT,\n                       &timeout))) {\n        return avs_errno(AVS_EBADF);\n    }\n    struct pollfd pfd = {\n        .fd = *(const int *) fd_ptr,\n        .events = POLLIN\n    };\n    int64_t timeout_ms;\n    if (avs_time_duration_to_scalar(&timeout_ms, AVS_TIME_MS,\n                                    timeout.recv_timeout)) {\n        timeout_ms = -1;\n    } else if (timeout_ms < 0) {\n        timeout_ms = 0;\n    }\n    if (poll(&pfd, 1, (int) timeout_ms) == 0) {\n        return avs_errno(AVS_ETIMEDOUT);\n    }\n    int bytes_received = SSL_read(sock->ssl, buffer, (int) buffer_length);\n    if (bytes_received < 0) {\n        return avs_errno(AVS_EPROTO);\n    }\n    *out_bytes_received = (size_t) bytes_received;\n    if (buffer_length > 0 && (size_t) bytes_received == buffer_length) {\n        return avs_errno(AVS_EMSGSIZE);\n    }\n    return AVS_OK;\n}\n\nstatic avs_error_t\ntls_bind(avs_net_socket_t *sock_, const char *address, const char *port) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    return avs_net_socket_bind(sock->backend_socket, address, port);\n}\n\nstatic avs_error_t tls_close(avs_net_socket_t *sock_) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    if (sock->ssl) {\n        SSL_free(sock->ssl);\n        sock->ssl = NULL;\n    }\n    return avs_net_socket_close(sock->backend_socket);\n}\n\nstatic avs_error_t tls_shutdown(avs_net_socket_t *sock_) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    return avs_net_socket_shutdown(sock->backend_socket);\n}\n\nstatic avs_error_t tls_cleanup(avs_net_socket_t **sock_ptr) {\n    avs_error_t err = AVS_OK;\n    if (sock_ptr && *sock_ptr) {\n        tls_socket_impl_t *sock = (tls_socket_impl_t *) *sock_ptr;\n        tls_close(*sock_ptr);\n        avs_net_socket_cleanup(&sock->backend_socket);\n        if (sock->ctx) {\n            SSL_CTX_free(sock->ctx);\n        }\n        avs_free(sock);\n        *sock_ptr = NULL;\n    }\n    return err;\n}\n\nstatic const void *tls_system_socket(avs_net_socket_t *sock_) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    return avs_net_socket_get_system(sock->backend_socket);\n}\n\nstatic avs_error_t tls_remote_host(avs_net_socket_t *sock_,\n                                   char *out_buffer,\n                                   size_t out_buffer_size) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    return avs_net_socket_get_remote_host(sock->backend_socket, out_buffer,\n                                          out_buffer_size);\n}\n\nstatic avs_error_t tls_remote_hostname(avs_net_socket_t *sock_,\n                                       char *out_buffer,\n                                       size_t out_buffer_size) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    return avs_net_socket_get_remote_hostname(sock->backend_socket, out_buffer,\n                                              out_buffer_size);\n}\n\nstatic avs_error_t tls_remote_port(avs_net_socket_t *sock_,\n                                   char *out_buffer,\n                                   size_t out_buffer_size) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    return avs_net_socket_get_remote_port(sock->backend_socket, out_buffer,\n                                          out_buffer_size);\n}\n\nstatic avs_error_t tls_local_port(avs_net_socket_t *sock_,\n                                  char *out_buffer,\n                                  size_t out_buffer_size) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    return avs_net_socket_get_local_port(sock->backend_socket, out_buffer,\n                                         out_buffer_size);\n}\n\nstatic avs_error_t tls_get_opt(avs_net_socket_t *sock_,\n                               avs_net_socket_opt_key_t option_key,\n                               avs_net_socket_opt_value_t *out_option_value) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    switch (option_key) {\n    case AVS_NET_SOCKET_OPT_INNER_MTU: {\n        avs_error_t err = avs_net_socket_get_opt(sock->backend_socket,\n                                                 AVS_NET_SOCKET_OPT_INNER_MTU,\n                                                 out_option_value);\n        if (avs_is_ok(err)) {\n            out_option_value->mtu = AVS_MAX(out_option_value->mtu - 64, 0);\n        }\n        return err;\n    }\n    case AVS_NET_SOCKET_HAS_BUFFERED_DATA:\n        out_option_value->flag = (sock->ssl && SSL_pending(sock->ssl) > 0);\n        return AVS_OK;\n    case AVS_NET_SOCKET_OPT_SESSION_RESUMED:\n        out_option_value->flag = (sock->ssl && SSL_session_reused(sock->ssl));\n        return AVS_OK;\n    default:\n        return avs_net_socket_get_opt(sock->backend_socket, option_key,\n                                      out_option_value);\n    }\n}\n\nstatic avs_error_t tls_set_opt(avs_net_socket_t *sock_,\n                               avs_net_socket_opt_key_t option_key,\n                               avs_net_socket_opt_value_t option_value) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    return avs_net_socket_set_opt(sock->backend_socket, option_key,\n                                  option_value);\n}\n\nstatic const avs_net_socket_v_table_t TLS_SOCKET_VTABLE = {\n    .connect = tls_connect,\n    .send = tls_send,\n    .receive = tls_receive,\n    .bind = tls_bind,\n    .close = tls_close,\n    .shutdown = tls_shutdown,\n    .cleanup = tls_cleanup,\n    .get_system_socket = tls_system_socket,\n    .get_remote_host = tls_remote_host,\n    .get_remote_hostname = tls_remote_hostname,\n    .get_remote_port = tls_remote_port,\n    .get_local_port = tls_local_port,\n    .get_opt = tls_get_opt,\n    .set_opt = tls_set_opt\n};\n\nstatic unsigned int psk_client_cb(SSL *ssl,\n                                  const char *hint,\n                                  char *identity,\n                                  unsigned int max_identity_len,\n                                  unsigned char *psk,\n                                  unsigned int max_psk_len) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) SSL_get_app_data(ssl);\n\n    (void) hint;\n\n    if (!sock || max_psk_len < sock->psk_size\n            || max_identity_len < sock->identity_size + 1) {\n        return 0;\n    }\n\n    memcpy(psk, sock->psk, sock->psk_size);\n    memcpy(identity, sock->identity, sock->identity_size);\n    identity[sock->identity_size] = '\\0';\n\n    return (unsigned int) sock->psk_size;\n}\n\nstatic avs_error_t configure_psk(tls_socket_impl_t *sock,\n                                 const avs_net_psk_info_t *psk) {\n    if (psk->key.desc.source != AVS_CRYPTO_DATA_SOURCE_BUFFER\n            || psk->identity.desc.source != AVS_CRYPTO_DATA_SOURCE_BUFFER) {\n        return avs_errno(AVS_EINVAL);\n    }\n\n    const void *key_ptr = psk->key.desc.info.buffer.buffer;\n    size_t key_size = psk->key.desc.info.buffer.buffer_size;\n\n    const void *identity_ptr = psk->identity.desc.info.buffer.buffer;\n    size_t identity_size = psk->identity.desc.info.buffer.buffer_size;\n\n    if (key_size > sizeof(sock->psk)\n            || identity_size > sizeof(sock->identity)) {\n        return avs_errno(AVS_EINVAL);\n    }\n    memcpy(sock->psk, key_ptr, key_size);\n    sock->psk_size = key_size;\n    memcpy(sock->identity, identity_ptr, identity_size);\n    sock->identity_size = identity_size;\n    SSL_CTX_set_cipher_list(sock->ctx, \"PSK\");\n    SSL_CTX_set_psk_client_callback(sock->ctx, psk_client_cb);\n    SSL_CTX_set_verify(sock->ctx, SSL_VERIFY_PEER, NULL);\n    return AVS_OK;\n}\n\nstatic int new_session_cb(SSL *ssl, SSL_SESSION *sess) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) SSL_get_app_data(ssl);\n    int serialized_size = i2d_SSL_SESSION(sess, NULL);\n    if (serialized_size > 0\n            && (size_t) serialized_size\n                           <= sock->session_resumption_buffer_size) {\n        unsigned char *ptr = (unsigned char *) sock->session_resumption_buffer;\n        i2d_SSL_SESSION(sess, &ptr);\n    }\n    return 0;\n}\n\navs_error_t _avs_net_create_dtls_socket(avs_net_socket_t **socket_ptr,\n                                        const void *configuration_) {\n    assert(socket_ptr);\n    assert(!*socket_ptr);\n    assert(configuration_);\n    const avs_net_ssl_configuration_t *configuration =\n            (const avs_net_ssl_configuration_t *) configuration_;\n    tls_socket_impl_t *socket =\n            (tls_socket_impl_t *) avs_calloc(1, sizeof(tls_socket_impl_t));\n    if (!socket) {\n        return avs_errno(AVS_ENOMEM);\n    }\n    *socket_ptr = (avs_net_socket_t *) socket;\n    socket->operations = &TLS_SOCKET_VTABLE;\n\n    avs_error_t err = AVS_OK;\n    if (avs_is_ok((err = avs_net_udp_socket_create(\n                           &socket->backend_socket,\n                           &configuration->backend_configuration)))\n            && !(socket->ctx = SSL_CTX_new(DTLS_method()))) {\n        err = avs_errno(AVS_ENOMEM);\n    }\n    if (avs_is_ok(err)) {\n        switch (configuration->security.mode) {\n        case AVS_NET_SECURITY_PSK:\n            err = configure_psk(socket, &configuration->security.data.psk);\n            break;\n        default:\n            err = avs_errno(AVS_ENOTSUP);\n        }\n    }\n    if (avs_is_err(err)) {\n        avs_net_socket_cleanup(socket_ptr);\n        return err;\n    }\n    SSL_CTX_set_mode(socket->ctx, SSL_MODE_AUTO_RETRY);\n    if (configuration->session_resumption_buffer_size > 0) {\n        assert(configuration->session_resumption_buffer);\n        socket->session_resumption_buffer =\n                configuration->session_resumption_buffer;\n        socket->session_resumption_buffer_size =\n                configuration->session_resumption_buffer_size;\n        SSL_CTX_set_session_cache_mode(\n                socket->ctx,\n                SSL_SESS_CACHE_CLIENT | SSL_SESS_CACHE_NO_INTERNAL_STORE);\n        SSL_CTX_sess_set_new_cb(socket->ctx, new_session_cb);\n    }\n    return AVS_OK;\n}\n\navs_error_t _avs_net_create_ssl_socket(avs_net_socket_t **socket_ptr,\n                                       const void *configuration) {\n    return avs_errno(AVS_ENOTSUP);\n}\n"
  },
  {
    "path": "examples/custom-tls/resumption-simple/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.16)\nproject(custom-tls-resumption-simple C)\n\nset(CMAKE_C_STANDARD 99)\n\nfind_package(anjay REQUIRED)\nfind_package(OpenSSL REQUIRED)\n\nadd_executable(${PROJECT_NAME}\n               src/main.c\n               src/net_impl.c\n               src/tls_impl.c)\ntarget_link_libraries(${PROJECT_NAME} PRIVATE anjay OpenSSL::SSL)\n"
  },
  {
    "path": "examples/custom-tls/resumption-simple/src/main.c",
    "content": "#include <anjay/anjay.h>\n#include <anjay/security.h>\n#include <anjay/server.h>\n#include <avsystem/commons/avs_log.h>\n\n#include <poll.h>\n\nint main_loop(anjay_t *anjay) {\n    while (true) {\n        // Obtain all network data sources\n        AVS_LIST(avs_net_socket_t *const) sockets = anjay_get_sockets(anjay);\n\n        // Prepare to poll() on them\n        size_t numsocks = AVS_LIST_SIZE(sockets);\n        struct pollfd pollfds[numsocks];\n        size_t i = 0;\n        AVS_LIST(avs_net_socket_t *const) sock;\n        AVS_LIST_FOREACH(sock, sockets) {\n            pollfds[i].fd = *(const int *) avs_net_socket_get_system(*sock);\n            pollfds[i].events = POLLIN;\n            pollfds[i].revents = 0;\n            ++i;\n        }\n\n        const int max_wait_time_ms = 1000;\n        // Determine the expected time to the next job in milliseconds.\n        // If there is no job we will wait till something arrives for\n        // at most 1 second (i.e. max_wait_time_ms).\n        int wait_ms =\n                anjay_sched_calculate_wait_time_ms(anjay, max_wait_time_ms);\n\n        // Wait for the events if necessary, and handle them.\n        if (poll(pollfds, numsocks, wait_ms) > 0) {\n            int socket_id = 0;\n            AVS_LIST(avs_net_socket_t *const) socket = NULL;\n            AVS_LIST_FOREACH(socket, sockets) {\n                if (pollfds[socket_id].revents) {\n                    if (anjay_serve(anjay, *socket)) {\n                        avs_log(tutorial, ERROR, \"anjay_serve failed\");\n                    }\n                }\n                ++socket_id;\n            }\n        }\n\n        // Finally run the scheduler\n        anjay_sched_run(anjay);\n    }\n    return 0;\n}\n\n// Installs Security Object and adds an instance of it.\n// An instance of Security Object provides information needed to connect to\n// LwM2M server.\nstatic int setup_security_object(anjay_t *anjay) {\n    if (anjay_security_object_install(anjay)) {\n        return -1;\n    }\n\n    static const char PSK_IDENTITY[] = \"identity\";\n    static const char PSK_KEY[] = \"P4s$w0rd\";\n\n    anjay_security_instance_t security_instance = {\n        .ssid = 1,\n        .server_uri = \"coaps://eu.iot.avsystem.cloud:5684\",\n        .security_mode = ANJAY_SECURITY_PSK,\n        .public_cert_or_psk_identity = (const uint8_t *) PSK_IDENTITY,\n        .public_cert_or_psk_identity_size = strlen(PSK_IDENTITY),\n        .private_cert_or_psk_key = (const uint8_t *) PSK_KEY,\n        .private_cert_or_psk_key_size = strlen(PSK_KEY)\n    };\n\n    // Anjay will assign Instance ID automatically\n    anjay_iid_t security_instance_id = ANJAY_ID_INVALID;\n    if (anjay_security_object_add_instance(anjay, &security_instance,\n                                           &security_instance_id)) {\n        return -1;\n    }\n\n    return 0;\n}\n\n// Installs Server Object and adds an instance of it.\n// An instance of Server Object provides the data related to a LwM2M Server.\nstatic int setup_server_object(anjay_t *anjay) {\n    if (anjay_server_object_install(anjay)) {\n        return -1;\n    }\n\n    const anjay_server_instance_t server_instance = {\n        // Server Short ID\n        .ssid = 1,\n        // Client will send Update message often than every 60 seconds\n        .lifetime = 60,\n        // Disable Default Minimum Period resource\n        .default_min_period = -1,\n        // Disable Default Maximum Period resource\n        .default_max_period = -1,\n        // Disable Disable Timeout resource\n        .disable_timeout = -1,\n        // Sets preferred transport to UDP\n        .binding = \"U\"\n    };\n\n    // Anjay will assign Instance ID automatically\n    anjay_iid_t server_instance_id = ANJAY_ID_INVALID;\n    if (anjay_server_object_add_instance(anjay, &server_instance,\n                                         &server_instance_id)) {\n        return -1;\n    }\n\n    return 0;\n}\n\nint main(int argc, char *argv[]) {\n    if (argc != 2) {\n        avs_log(tutorial, ERROR, \"usage: %s ENDPOINT_NAME\", argv[0]);\n        return -1;\n    }\n\n    const anjay_configuration_t CONFIG = {\n        .endpoint_name = argv[1],\n        .in_buffer_size = 4000,\n        .out_buffer_size = 4000,\n        .msg_cache_size = 4000\n    };\n\n    anjay_t *anjay = anjay_new(&CONFIG);\n    if (!anjay) {\n        avs_log(tutorial, ERROR, \"Could not create Anjay object\");\n        return -1;\n    }\n\n    int result = 0;\n    // Setup necessary objects\n    if (setup_security_object(anjay) || setup_server_object(anjay)) {\n        result = -1;\n    }\n\n    if (!result) {\n        result = main_loop(anjay);\n    }\n\n    anjay_delete(anjay);\n    return result;\n}\n"
  },
  {
    "path": "examples/custom-tls/resumption-simple/src/net_impl.c",
    "content": "#include <inttypes.h>\n\n#include <netdb.h>\n#include <poll.h>\n#include <unistd.h>\n\n#include <arpa/inet.h>\n\n#include <sys/socket.h>\n#include <sys/types.h>\n\n#include <avsystem/commons/avs_socket_v_table.h>\n#include <avsystem/commons/avs_utils.h>\n\n#ifdef AVS_COMMONS_NET_WITH_POSIX_AVS_SOCKET\n#    error \"Custom implementation of the network layer conflicts with AVS_COMMONS_NET_WITH_POSIX_AVS_SOCKET\"\n#endif // AVS_COMMONS_NET_WITH_POSIX_AVS_SOCKET\n\navs_error_t _avs_net_initialize_global_compat_state(void);\nvoid _avs_net_cleanup_global_compat_state(void);\navs_error_t _avs_net_create_tcp_socket(avs_net_socket_t **socket,\n                                       const void *socket_configuration);\navs_error_t _avs_net_create_udp_socket(avs_net_socket_t **socket,\n                                       const void *socket_configuration);\n\navs_error_t _avs_net_initialize_global_compat_state(void) {\n    return AVS_OK;\n}\n\nvoid _avs_net_cleanup_global_compat_state(void) {}\n\ntypedef struct {\n    const avs_net_socket_v_table_t *operations;\n    int socktype;\n    int fd;\n    avs_time_duration_t recv_timeout;\n    char remote_hostname[256];\n    bool shut_down;\n    size_t bytes_sent;\n    size_t bytes_received;\n    avs_net_resolved_endpoint_t *preferred_endpoint;\n} net_socket_impl_t;\n\ntypedef union {\n    struct sockaddr addr;\n    struct sockaddr_in in;\n    struct sockaddr_in6 in6;\n    struct sockaddr_storage storage;\n} sockaddr_union_t;\n\nstatic avs_error_t\nnet_connect(avs_net_socket_t *sock_, const char *host, const char *port) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    struct addrinfo hints = {\n        .ai_socktype = sock->socktype\n    };\n    if (sock->fd >= 0) {\n        getsockopt(sock->fd, SOL_SOCKET, SO_DOMAIN, &hints.ai_family,\n                   &(socklen_t) { sizeof(hints.ai_family) });\n    }\n    struct addrinfo *addrs = NULL;\n    avs_error_t err = AVS_OK;\n    if (getaddrinfo(host, port, &hints, &addrs) || !addrs) {\n        err = avs_errno(AVS_EADDRNOTAVAIL);\n    } else if (sock->fd < 0\n               && (sock->fd = socket(addrs->ai_family, addrs->ai_socktype,\n                                     addrs->ai_protocol))\n                          < 0) {\n        err = avs_errno(AVS_UNKNOWN_ERROR);\n    } else {\n        const struct addrinfo *addr = addrs;\n        if (sock->preferred_endpoint\n                && sock->preferred_endpoint->size == sizeof(sockaddr_union_t)) {\n            while (addr) {\n                if (addr->ai_addrlen <= sizeof(sockaddr_union_t)\n                        && memcmp(addr->ai_addr,\n                                  sock->preferred_endpoint->data.buf,\n                                  addr->ai_addrlen)\n                                       == 0) {\n                    break;\n                }\n                addr = addr->ai_next;\n            }\n        }\n        if (!addr) {\n            // Preferred endpoint not found, use the first one\n            addr = addrs;\n        }\n        if (connect(sock->fd, addr->ai_addr, addr->ai_addrlen)) {\n            err = avs_errno(AVS_ECONNREFUSED);\n        }\n        if (sock->preferred_endpoint && avs_is_ok(err)) {\n            assert(addr->ai_addrlen <= sizeof(sockaddr_union_t));\n            memcpy(sock->preferred_endpoint->data.buf, addr->ai_addr,\n                   addr->ai_addrlen);\n            sock->preferred_endpoint->size = sizeof(sockaddr_union_t);\n        }\n    }\n    if (avs_is_ok(err)) {\n        sock->shut_down = false;\n        snprintf(sock->remote_hostname, sizeof(sock->remote_hostname), \"%s\",\n                 host);\n    }\n    freeaddrinfo(addrs);\n    return err;\n}\n\nstatic avs_error_t\nnet_send(avs_net_socket_t *sock_, const void *buffer, size_t buffer_length) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    ssize_t written = send(sock->fd, buffer, buffer_length, MSG_NOSIGNAL);\n    if (written >= 0) {\n        sock->bytes_sent += (size_t) written;\n        if ((size_t) written == buffer_length) {\n            return AVS_OK;\n        }\n    }\n    return avs_errno(AVS_EIO);\n}\n\nstatic avs_error_t net_receive(avs_net_socket_t *sock_,\n                               size_t *out_bytes_received,\n                               void *buffer,\n                               size_t buffer_length) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    struct pollfd pfd = {\n        .fd = sock->fd,\n        .events = POLLIN\n    };\n    int64_t timeout_ms;\n    if (avs_time_duration_to_scalar(&timeout_ms, AVS_TIME_MS,\n                                    sock->recv_timeout)) {\n        timeout_ms = -1;\n    } else if (timeout_ms < 0) {\n        timeout_ms = 0;\n    }\n    if (poll(&pfd, 1, (int) timeout_ms) == 0) {\n        return avs_errno(AVS_ETIMEDOUT);\n    }\n    ssize_t bytes_received = read(sock->fd, buffer, buffer_length);\n    if (bytes_received < 0) {\n        return avs_errno(AVS_EIO);\n    }\n    *out_bytes_received = (size_t) bytes_received;\n    sock->bytes_received += (size_t) bytes_received;\n    if (buffer_length > 0 && sock->socktype == SOCK_DGRAM\n            && (size_t) bytes_received == buffer_length) {\n        return avs_errno(AVS_EMSGSIZE);\n    }\n    return AVS_OK;\n}\n\nstatic avs_error_t\nnet_bind(avs_net_socket_t *sock_, const char *address, const char *port) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    struct addrinfo hints = {\n        .ai_flags = AI_PASSIVE,\n        .ai_socktype = sock->socktype\n    };\n    if (sock->fd >= 0) {\n        getsockopt(sock->fd, SOL_SOCKET, SO_DOMAIN, &hints.ai_family,\n                   &(socklen_t) { sizeof(hints.ai_family) });\n    }\n    struct addrinfo *addr = NULL;\n    avs_error_t err = AVS_OK;\n    if (getaddrinfo(address, port, &hints, &addr) || !addr) {\n        err = avs_errno(AVS_EADDRNOTAVAIL);\n    } else if ((sock->fd < 0\n                && (sock->fd = socket(addr->ai_family, addr->ai_socktype,\n                                      addr->ai_protocol))\n                           < 0)\n               || setsockopt(sock->fd, SOL_SOCKET, SO_REUSEADDR, &(int) { 1 },\n                             sizeof(int))) {\n        err = avs_errno(AVS_UNKNOWN_ERROR);\n    } else if (bind(sock->fd, addr->ai_addr, addr->ai_addrlen)) {\n        err = avs_errno(AVS_ECONNREFUSED);\n    } else {\n        sock->shut_down = false;\n    }\n    if (avs_is_err(err) && sock->fd >= 0) {\n        close(sock->fd);\n        sock->fd = -1;\n    }\n    freeaddrinfo(addr);\n    return err;\n}\n\nstatic avs_error_t net_close(avs_net_socket_t *sock_) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    avs_error_t err = AVS_OK;\n    if (sock->fd >= 0) {\n        if (close(sock->fd)) {\n            err = avs_errno(AVS_EIO);\n        }\n        sock->fd = -1;\n        sock->shut_down = false;\n    }\n    return err;\n}\n\nstatic avs_error_t net_shutdown(avs_net_socket_t *sock_) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    avs_error_t err = avs_errno(AVS_EBADF);\n    if (sock->fd >= 0) {\n        err = shutdown(sock->fd, SHUT_RDWR) ? avs_errno(AVS_EIO) : AVS_OK;\n        sock->shut_down = true;\n    }\n    return err;\n}\n\nstatic avs_error_t net_cleanup(avs_net_socket_t **sock_ptr) {\n    avs_error_t err = AVS_OK;\n    if (sock_ptr && *sock_ptr) {\n        err = net_close(*sock_ptr);\n        avs_free(*sock_ptr);\n        *sock_ptr = NULL;\n    }\n    return err;\n}\n\nstatic const void *net_system_socket(avs_net_socket_t *sock_) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    return &sock->fd;\n}\n\nstatic avs_error_t stringify_sockaddr_host(const sockaddr_union_t *addr,\n                                           char *out_buffer,\n                                           size_t out_buffer_size) {\n    if ((addr->in.sin_family == AF_INET\n         && inet_ntop(AF_INET, &addr->in.sin_addr, out_buffer,\n                      (socklen_t) out_buffer_size))\n            || (addr->in6.sin6_family == AF_INET6\n                && inet_ntop(AF_INET6, &addr->in6.sin6_addr, out_buffer,\n                             (socklen_t) out_buffer_size))) {\n        return AVS_OK;\n    }\n    return avs_errno(AVS_UNKNOWN_ERROR);\n}\n\nstatic avs_error_t stringify_sockaddr_port(const sockaddr_union_t *addr,\n                                           char *out_buffer,\n                                           size_t out_buffer_size) {\n    if ((addr->in.sin_family == AF_INET\n         && avs_simple_snprintf(out_buffer, out_buffer_size, \"%\" PRIu16,\n                                ntohs(addr->in.sin_port))\n                    >= 0)\n            || (addr->in6.sin6_family == AF_INET6\n                && avs_simple_snprintf(out_buffer, out_buffer_size, \"%\" PRIu16,\n                                       ntohs(addr->in6.sin6_port))\n                           >= 0)) {\n        return AVS_OK;\n    }\n    return avs_errno(AVS_UNKNOWN_ERROR);\n}\n\nstatic avs_error_t net_remote_host(avs_net_socket_t *sock_,\n                                   char *out_buffer,\n                                   size_t out_buffer_size) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    sockaddr_union_t addr;\n    if (getpeername(sock->fd, &addr.addr, &(socklen_t) { sizeof(addr) })) {\n        return avs_errno(AVS_UNKNOWN_ERROR);\n    }\n    return stringify_sockaddr_host(&addr, out_buffer, out_buffer_size);\n}\n\nstatic avs_error_t net_remote_hostname(avs_net_socket_t *sock_,\n                                       char *out_buffer,\n                                       size_t out_buffer_size) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    return avs_simple_snprintf(out_buffer, out_buffer_size, \"%s\",\n                               sock->remote_hostname)\n                           < 0\n                   ? avs_errno(AVS_UNKNOWN_ERROR)\n                   : AVS_OK;\n}\n\nstatic avs_error_t net_remote_port(avs_net_socket_t *sock_,\n                                   char *out_buffer,\n                                   size_t out_buffer_size) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    sockaddr_union_t addr;\n    if (getpeername(sock->fd, &addr.addr, &(socklen_t) { sizeof(addr) })) {\n        return avs_errno(AVS_UNKNOWN_ERROR);\n    }\n    return stringify_sockaddr_port(&addr, out_buffer, out_buffer_size);\n}\n\nstatic avs_error_t net_local_port(avs_net_socket_t *sock_,\n                                  char *out_buffer,\n                                  size_t out_buffer_size) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    sockaddr_union_t addr;\n    if (getsockname(sock->fd, &addr.addr, &(socklen_t) { sizeof(addr) })) {\n        return avs_errno(AVS_UNKNOWN_ERROR);\n    }\n    return stringify_sockaddr_port(&addr, out_buffer, out_buffer_size);\n}\n\nstatic avs_error_t net_get_opt(avs_net_socket_t *sock_,\n                               avs_net_socket_opt_key_t option_key,\n                               avs_net_socket_opt_value_t *out_option_value) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    switch (option_key) {\n    case AVS_NET_SOCKET_OPT_RECV_TIMEOUT:\n        out_option_value->recv_timeout = sock->recv_timeout;\n        return AVS_OK;\n    case AVS_NET_SOCKET_OPT_STATE:\n        if (sock->fd < 0) {\n            out_option_value->state = AVS_NET_SOCKET_STATE_CLOSED;\n        } else if (sock->shut_down) {\n            out_option_value->state = AVS_NET_SOCKET_STATE_SHUTDOWN;\n        } else {\n            sockaddr_union_t addr;\n            if (!getpeername(sock->fd, &addr.addr,\n                             &(socklen_t) { sizeof(addr) })\n                    && ((addr.in.sin_family == AF_INET && addr.in.sin_port != 0)\n                        || (addr.in6.sin6_family == AF_INET6\n                            && addr.in6.sin6_port != 0))) {\n                out_option_value->state = AVS_NET_SOCKET_STATE_CONNECTED;\n            } else {\n                out_option_value->state = AVS_NET_SOCKET_STATE_BOUND;\n            }\n        }\n        return AVS_OK;\n    case AVS_NET_SOCKET_OPT_INNER_MTU:\n        out_option_value->mtu = 1464;\n        return AVS_OK;\n    case AVS_NET_SOCKET_HAS_BUFFERED_DATA:\n        out_option_value->flag = false;\n        return AVS_OK;\n    case AVS_NET_SOCKET_OPT_BYTES_SENT:\n        out_option_value->bytes_sent = sock->bytes_sent;\n        return AVS_OK;\n    case AVS_NET_SOCKET_OPT_BYTES_RECEIVED:\n        out_option_value->bytes_received = sock->bytes_received;\n        return AVS_OK;\n    default:\n        return avs_errno(AVS_ENOTSUP);\n    }\n}\n\nstatic avs_error_t net_set_opt(avs_net_socket_t *sock_,\n                               avs_net_socket_opt_key_t option_key,\n                               avs_net_socket_opt_value_t option_value) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    switch (option_key) {\n    case AVS_NET_SOCKET_OPT_RECV_TIMEOUT:\n        sock->recv_timeout = option_value.recv_timeout;\n        return AVS_OK;\n    default:\n        return avs_errno(AVS_ENOTSUP);\n    }\n}\n\nstatic const avs_net_socket_v_table_t NET_SOCKET_VTABLE = {\n    .connect = net_connect,\n    .send = net_send,\n    .receive = net_receive,\n    .bind = net_bind,\n    .close = net_close,\n    .shutdown = net_shutdown,\n    .cleanup = net_cleanup,\n    .get_system_socket = net_system_socket,\n    .get_remote_host = net_remote_host,\n    .get_remote_hostname = net_remote_hostname,\n    .get_remote_port = net_remote_port,\n    .get_local_port = net_local_port,\n    .get_opt = net_get_opt,\n    .set_opt = net_set_opt\n};\n\nstatic avs_error_t\nnet_create_socket(avs_net_socket_t **socket_ptr,\n                  const avs_net_socket_configuration_t *configuration,\n                  int socktype) {\n    assert(socket_ptr);\n    assert(!*socket_ptr);\n    (void) configuration;\n    net_socket_impl_t *socket =\n            (net_socket_impl_t *) avs_calloc(1, sizeof(net_socket_impl_t));\n    if (!socket) {\n        return avs_errno(AVS_ENOMEM);\n    }\n    socket->operations = &NET_SOCKET_VTABLE;\n    socket->socktype = socktype;\n    socket->fd = -1;\n    socket->recv_timeout = avs_time_duration_from_scalar(30, AVS_TIME_S);\n    socket->preferred_endpoint = configuration->preferred_endpoint;\n    *socket_ptr = (avs_net_socket_t *) socket;\n    return AVS_OK;\n}\n\navs_error_t _avs_net_create_udp_socket(avs_net_socket_t **socket_ptr,\n                                       const void *configuration) {\n    return net_create_socket(\n            socket_ptr, (const avs_net_socket_configuration_t *) configuration,\n            SOCK_DGRAM);\n}\n\navs_error_t _avs_net_create_tcp_socket(avs_net_socket_t **socket_ptr,\n                                       const void *configuration) {\n    return net_create_socket(\n            socket_ptr, (const avs_net_socket_configuration_t *) configuration,\n            SOCK_STREAM);\n}\n\navs_error_t\navs_net_resolved_endpoint_get_host_port(const avs_net_resolved_endpoint_t *endp,\n                                        char *host,\n                                        size_t hostlen,\n                                        char *serv,\n                                        size_t servlen) {\n    AVS_STATIC_ASSERT(sizeof(endp->data.buf) >= sizeof(sockaddr_union_t),\n                      data_buffer_big_enough);\n    if (endp->size != sizeof(sockaddr_union_t)) {\n        return avs_errno(AVS_EINVAL);\n    }\n    const sockaddr_union_t *addr = (const sockaddr_union_t *) &endp->data.buf;\n    avs_error_t err = AVS_OK;\n    (void) ((host\n             && avs_is_err(\n                        (err = stringify_sockaddr_host(addr, host, hostlen))))\n            || (serv\n                && avs_is_err((err = stringify_sockaddr_port(addr, serv,\n                                                             servlen)))));\n    return err;\n}\n"
  },
  {
    "path": "examples/custom-tls/resumption-simple/src/tls_impl.c",
    "content": "#include <poll.h>\n\n#include <openssl/ssl.h>\n\n#include <avsystem/commons/avs_crypto_common.h>\n#include <avsystem/commons/avs_socket_v_table.h>\n#include <avsystem/commons/avs_utils.h>\n\n#ifndef AVS_COMMONS_WITH_CUSTOM_TLS\n#    error \"Custom implementation of the TLS layer requires AVS_COMMONS_WITH_CUSTOM_TLS\"\n#endif // AVS_COMMONS_WITH_CUSTOM_TLS\n\navs_error_t _avs_net_initialize_global_ssl_state(void);\nvoid _avs_net_cleanup_global_ssl_state(void);\navs_error_t _avs_net_create_ssl_socket(avs_net_socket_t **socket,\n                                       const void *socket_configuration);\navs_error_t _avs_net_create_dtls_socket(avs_net_socket_t **socket,\n                                        const void *socket_configuration);\n\navs_error_t _avs_net_initialize_global_ssl_state(void) {\n    if (!OPENSSL_init_ssl(OPENSSL_INIT_ADD_ALL_CIPHERS\n                                  | OPENSSL_INIT_ADD_ALL_DIGESTS,\n                          NULL)) {\n        return avs_errno(AVS_EPROTO);\n    }\n    return AVS_OK;\n}\n\nvoid _avs_net_cleanup_global_ssl_state(void) {}\n\nvoid avs_crypto_clear_buffer(void *buf, size_t size) {\n    OPENSSL_cleanse(buf, size);\n}\n\ntypedef struct {\n    const avs_net_socket_v_table_t *operations;\n    avs_net_socket_t *backend_socket;\n    SSL_CTX *ctx;\n    SSL *ssl;\n\n    char psk[256];\n    size_t psk_size;\n    char identity[128];\n    size_t identity_size;\n\n    SSL_SESSION *last_session;\n} tls_socket_impl_t;\n\nstatic avs_error_t perform_handshake(tls_socket_impl_t *sock,\n                                     const char *host) {\n    union {\n        struct sockaddr addr;\n        struct sockaddr_storage storage;\n    } peername;\n    const void *fd_ptr = avs_net_socket_get_system(sock->backend_socket);\n    if (!fd_ptr\n            || getpeername(*(const int *) fd_ptr, &peername.addr,\n                           &(socklen_t) { sizeof(peername) })) {\n        return avs_errno(AVS_EBADF);\n    }\n\n    sock->ssl = SSL_new(sock->ctx);\n    if (!sock->ssl) {\n        return avs_errno(AVS_ENOMEM);\n    }\n\n    SSL_set_app_data(sock->ssl, sock);\n    SSL_set_tlsext_host_name(sock->ssl, host);\n\n    BIO *bio = BIO_new_dgram(*(const int *) fd_ptr, 0);\n    if (!bio) {\n        return avs_errno(AVS_ENOMEM);\n    }\n    BIO_ctrl(bio, BIO_CTRL_DGRAM_SET_CONNECTED, 0, &peername.addr);\n    SSL_set_bio(sock->ssl, bio, bio);\n\n    if (sock->last_session) {\n        SSL_SESSION *session_dup = SSL_SESSION_dup(sock->last_session);\n        if (session_dup) {\n            SSL_set_session(sock->ssl, session_dup);\n            SSL_SESSION_free(session_dup);\n        }\n    }\n\n    if (SSL_connect(sock->ssl) <= 0) {\n        return avs_errno(AVS_EPROTO);\n    }\n    return AVS_OK;\n}\n\nstatic avs_error_t\ntls_connect(avs_net_socket_t *sock_, const char *host, const char *port) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    if (sock->ssl) {\n        return avs_errno(AVS_EBADF);\n    }\n    avs_error_t err;\n    if (avs_is_err((\n                err = avs_net_socket_connect(sock->backend_socket, host, port)))\n            || avs_is_err((err = perform_handshake(sock, host)))) {\n        if (sock->ssl) {\n            SSL_free(sock->ssl);\n            sock->ssl = NULL;\n        }\n        avs_net_socket_close(sock->backend_socket);\n    }\n    return err;\n}\n\nstatic avs_error_t\ntls_send(avs_net_socket_t *sock_, const void *buffer, size_t buffer_length) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    int result = SSL_write(sock->ssl, buffer, (int) buffer_length);\n    if (result < 0 || (size_t) result < buffer_length) {\n        return avs_errno(AVS_EPROTO);\n    }\n    return AVS_OK;\n}\n\nstatic avs_error_t tls_receive(avs_net_socket_t *sock_,\n                               size_t *out_bytes_received,\n                               void *buffer,\n                               size_t buffer_length) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    const void *fd_ptr = avs_net_socket_get_system(sock->backend_socket);\n    avs_net_socket_opt_value_t timeout;\n    if (!fd_ptr\n            || avs_is_err(avs_net_socket_get_opt(\n                       sock->backend_socket, AVS_NET_SOCKET_OPT_RECV_TIMEOUT,\n                       &timeout))) {\n        return avs_errno(AVS_EBADF);\n    }\n    struct pollfd pfd = {\n        .fd = *(const int *) fd_ptr,\n        .events = POLLIN\n    };\n    int64_t timeout_ms;\n    if (avs_time_duration_to_scalar(&timeout_ms, AVS_TIME_MS,\n                                    timeout.recv_timeout)) {\n        timeout_ms = -1;\n    } else if (timeout_ms < 0) {\n        timeout_ms = 0;\n    }\n    if (poll(&pfd, 1, (int) timeout_ms) == 0) {\n        return avs_errno(AVS_ETIMEDOUT);\n    }\n    int bytes_received = SSL_read(sock->ssl, buffer, (int) buffer_length);\n    if (bytes_received < 0) {\n        return avs_errno(AVS_EPROTO);\n    }\n    *out_bytes_received = (size_t) bytes_received;\n    if (buffer_length > 0 && (size_t) bytes_received == buffer_length) {\n        return avs_errno(AVS_EMSGSIZE);\n    }\n    return AVS_OK;\n}\n\nstatic avs_error_t\ntls_bind(avs_net_socket_t *sock_, const char *address, const char *port) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    return avs_net_socket_bind(sock->backend_socket, address, port);\n}\n\nstatic avs_error_t tls_close(avs_net_socket_t *sock_) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    if (sock->ssl) {\n        SSL_free(sock->ssl);\n        sock->ssl = NULL;\n    }\n    return avs_net_socket_close(sock->backend_socket);\n}\n\nstatic avs_error_t tls_shutdown(avs_net_socket_t *sock_) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    return avs_net_socket_shutdown(sock->backend_socket);\n}\n\nstatic avs_error_t tls_cleanup(avs_net_socket_t **sock_ptr) {\n    avs_error_t err = AVS_OK;\n    if (sock_ptr && *sock_ptr) {\n        tls_socket_impl_t *sock = (tls_socket_impl_t *) *sock_ptr;\n        tls_close(*sock_ptr);\n        avs_net_socket_cleanup(&sock->backend_socket);\n        if (sock->ctx) {\n            SSL_CTX_free(sock->ctx);\n        }\n        if (sock->last_session) {\n            SSL_SESSION_free(sock->last_session);\n        }\n        avs_free(sock);\n        *sock_ptr = NULL;\n    }\n    return err;\n}\n\nstatic const void *tls_system_socket(avs_net_socket_t *sock_) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    return avs_net_socket_get_system(sock->backend_socket);\n}\n\nstatic avs_error_t tls_remote_host(avs_net_socket_t *sock_,\n                                   char *out_buffer,\n                                   size_t out_buffer_size) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    return avs_net_socket_get_remote_host(sock->backend_socket, out_buffer,\n                                          out_buffer_size);\n}\n\nstatic avs_error_t tls_remote_hostname(avs_net_socket_t *sock_,\n                                       char *out_buffer,\n                                       size_t out_buffer_size) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    return avs_net_socket_get_remote_hostname(sock->backend_socket, out_buffer,\n                                              out_buffer_size);\n}\n\nstatic avs_error_t tls_remote_port(avs_net_socket_t *sock_,\n                                   char *out_buffer,\n                                   size_t out_buffer_size) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    return avs_net_socket_get_remote_port(sock->backend_socket, out_buffer,\n                                          out_buffer_size);\n}\n\nstatic avs_error_t tls_local_port(avs_net_socket_t *sock_,\n                                  char *out_buffer,\n                                  size_t out_buffer_size) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    return avs_net_socket_get_local_port(sock->backend_socket, out_buffer,\n                                         out_buffer_size);\n}\n\nstatic avs_error_t tls_get_opt(avs_net_socket_t *sock_,\n                               avs_net_socket_opt_key_t option_key,\n                               avs_net_socket_opt_value_t *out_option_value) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    switch (option_key) {\n    case AVS_NET_SOCKET_OPT_INNER_MTU: {\n        avs_error_t err = avs_net_socket_get_opt(sock->backend_socket,\n                                                 AVS_NET_SOCKET_OPT_INNER_MTU,\n                                                 out_option_value);\n        if (avs_is_ok(err)) {\n            out_option_value->mtu = AVS_MAX(out_option_value->mtu - 64, 0);\n        }\n        return err;\n    }\n    case AVS_NET_SOCKET_HAS_BUFFERED_DATA:\n        out_option_value->flag = (sock->ssl && SSL_pending(sock->ssl) > 0);\n        return AVS_OK;\n    case AVS_NET_SOCKET_OPT_SESSION_RESUMED:\n        out_option_value->flag = (sock->ssl && SSL_session_reused(sock->ssl));\n        return AVS_OK;\n    default:\n        return avs_net_socket_get_opt(sock->backend_socket, option_key,\n                                      out_option_value);\n    }\n}\n\nstatic avs_error_t tls_set_opt(avs_net_socket_t *sock_,\n                               avs_net_socket_opt_key_t option_key,\n                               avs_net_socket_opt_value_t option_value) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    return avs_net_socket_set_opt(sock->backend_socket, option_key,\n                                  option_value);\n}\n\nstatic const avs_net_socket_v_table_t TLS_SOCKET_VTABLE = {\n    .connect = tls_connect,\n    .send = tls_send,\n    .receive = tls_receive,\n    .bind = tls_bind,\n    .close = tls_close,\n    .shutdown = tls_shutdown,\n    .cleanup = tls_cleanup,\n    .get_system_socket = tls_system_socket,\n    .get_remote_host = tls_remote_host,\n    .get_remote_hostname = tls_remote_hostname,\n    .get_remote_port = tls_remote_port,\n    .get_local_port = tls_local_port,\n    .get_opt = tls_get_opt,\n    .set_opt = tls_set_opt\n};\n\nstatic unsigned int psk_client_cb(SSL *ssl,\n                                  const char *hint,\n                                  char *identity,\n                                  unsigned int max_identity_len,\n                                  unsigned char *psk,\n                                  unsigned int max_psk_len) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) SSL_get_app_data(ssl);\n\n    (void) hint;\n\n    if (!sock || max_psk_len < sock->psk_size\n            || max_identity_len < sock->identity_size + 1) {\n        return 0;\n    }\n\n    memcpy(psk, sock->psk, sock->psk_size);\n    memcpy(identity, sock->identity, sock->identity_size);\n    identity[sock->identity_size] = '\\0';\n\n    return (unsigned int) sock->psk_size;\n}\n\nstatic avs_error_t configure_psk(tls_socket_impl_t *sock,\n                                 const avs_net_psk_info_t *psk) {\n    if (psk->key.desc.source != AVS_CRYPTO_DATA_SOURCE_BUFFER\n            || psk->identity.desc.source != AVS_CRYPTO_DATA_SOURCE_BUFFER) {\n        return avs_errno(AVS_EINVAL);\n    }\n\n    const void *key_ptr = psk->key.desc.info.buffer.buffer;\n    size_t key_size = psk->key.desc.info.buffer.buffer_size;\n\n    const void *identity_ptr = psk->identity.desc.info.buffer.buffer;\n    size_t identity_size = psk->identity.desc.info.buffer.buffer_size;\n\n    if (key_size > sizeof(sock->psk)\n            || identity_size > sizeof(sock->identity)) {\n        return avs_errno(AVS_EINVAL);\n    }\n    memcpy(sock->psk, key_ptr, key_size);\n    sock->psk_size = key_size;\n    memcpy(sock->identity, identity_ptr, identity_size);\n    sock->identity_size = identity_size;\n    SSL_CTX_set_cipher_list(sock->ctx, \"PSK\");\n    SSL_CTX_set_psk_client_callback(sock->ctx, psk_client_cb);\n    SSL_CTX_set_verify(sock->ctx, SSL_VERIFY_PEER, NULL);\n    return AVS_OK;\n}\n\nstatic int new_session_cb(SSL *ssl, SSL_SESSION *sess) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) SSL_get_app_data(ssl);\n    SSL_SESSION *sess_dup = SSL_SESSION_dup(sess);\n    if (sess_dup) {\n        if (sock->last_session) {\n            SSL_SESSION_free(sock->last_session);\n        }\n        sock->last_session = sess_dup;\n    }\n    return 0;\n}\n\navs_error_t _avs_net_create_dtls_socket(avs_net_socket_t **socket_ptr,\n                                        const void *configuration_) {\n    assert(socket_ptr);\n    assert(!*socket_ptr);\n    assert(configuration_);\n    const avs_net_ssl_configuration_t *configuration =\n            (const avs_net_ssl_configuration_t *) configuration_;\n    tls_socket_impl_t *socket =\n            (tls_socket_impl_t *) avs_calloc(1, sizeof(tls_socket_impl_t));\n    if (!socket) {\n        return avs_errno(AVS_ENOMEM);\n    }\n    *socket_ptr = (avs_net_socket_t *) socket;\n    socket->operations = &TLS_SOCKET_VTABLE;\n\n    avs_error_t err = AVS_OK;\n    if (avs_is_ok((err = avs_net_udp_socket_create(\n                           &socket->backend_socket,\n                           &configuration->backend_configuration)))\n            && !(socket->ctx = SSL_CTX_new(DTLS_method()))) {\n        err = avs_errno(AVS_ENOMEM);\n    }\n    if (avs_is_ok(err)) {\n        switch (configuration->security.mode) {\n        case AVS_NET_SECURITY_PSK:\n            err = configure_psk(socket, &configuration->security.data.psk);\n            break;\n        default:\n            err = avs_errno(AVS_ENOTSUP);\n        }\n    }\n    if (avs_is_err(err)) {\n        avs_net_socket_cleanup(socket_ptr);\n        return err;\n    }\n    SSL_CTX_set_mode(socket->ctx, SSL_MODE_AUTO_RETRY);\n    SSL_CTX_set_session_cache_mode(socket->ctx,\n                                   SSL_SESS_CACHE_CLIENT\n                                           | SSL_SESS_CACHE_NO_INTERNAL_STORE);\n    SSL_CTX_sess_set_new_cb(socket->ctx, new_session_cb);\n    return AVS_OK;\n}\n\navs_error_t _avs_net_create_ssl_socket(avs_net_socket_t **socket_ptr,\n                                       const void *configuration) {\n    return avs_errno(AVS_ENOTSUP);\n}\n"
  },
  {
    "path": "examples/custom-tls/stub/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.16)\nproject(custom-tls-stub C)\n\nset(CMAKE_C_STANDARD 99)\n\nfind_package(anjay REQUIRED)\nfind_package(OpenSSL REQUIRED)\n\nadd_executable(${PROJECT_NAME}\n               src/main.c\n               src/net_impl.c\n               src/tls_impl.c)\ntarget_link_libraries(${PROJECT_NAME} PRIVATE anjay OpenSSL::SSL)\n"
  },
  {
    "path": "examples/custom-tls/stub/src/main.c",
    "content": "#include <anjay/anjay.h>\n#include <anjay/security.h>\n#include <anjay/server.h>\n#include <avsystem/commons/avs_log.h>\n\n#include <poll.h>\n\nint main_loop(anjay_t *anjay) {\n    while (true) {\n        // Obtain all network data sources\n        AVS_LIST(avs_net_socket_t *const) sockets = anjay_get_sockets(anjay);\n\n        // Prepare to poll() on them\n        size_t numsocks = AVS_LIST_SIZE(sockets);\n        struct pollfd pollfds[numsocks];\n        size_t i = 0;\n        AVS_LIST(avs_net_socket_t *const) sock;\n        AVS_LIST_FOREACH(sock, sockets) {\n            pollfds[i].fd = *(const int *) avs_net_socket_get_system(*sock);\n            pollfds[i].events = POLLIN;\n            pollfds[i].revents = 0;\n            ++i;\n        }\n\n        const int max_wait_time_ms = 1000;\n        // Determine the expected time to the next job in milliseconds.\n        // If there is no job we will wait till something arrives for\n        // at most 1 second (i.e. max_wait_time_ms).\n        int wait_ms =\n                anjay_sched_calculate_wait_time_ms(anjay, max_wait_time_ms);\n\n        // Wait for the events if necessary, and handle them.\n        if (poll(pollfds, numsocks, wait_ms) > 0) {\n            int socket_id = 0;\n            AVS_LIST(avs_net_socket_t *const) socket = NULL;\n            AVS_LIST_FOREACH(socket, sockets) {\n                if (pollfds[socket_id].revents) {\n                    if (anjay_serve(anjay, *socket)) {\n                        avs_log(tutorial, ERROR, \"anjay_serve failed\");\n                    }\n                }\n                ++socket_id;\n            }\n        }\n\n        // Finally run the scheduler\n        anjay_sched_run(anjay);\n    }\n    return 0;\n}\n\n// Installs Security Object and adds an instance of it.\n// An instance of Security Object provides information needed to connect to\n// LwM2M server.\nstatic int setup_security_object(anjay_t *anjay) {\n    if (anjay_security_object_install(anjay)) {\n        return -1;\n    }\n\n    static const char PSK_IDENTITY[] = \"identity\";\n    static const char PSK_KEY[] = \"P4s$w0rd\";\n\n    anjay_security_instance_t security_instance = {\n        .ssid = 1,\n        .server_uri = \"coaps://eu.iot.avsystem.cloud:5684\",\n        .security_mode = ANJAY_SECURITY_PSK,\n        .public_cert_or_psk_identity = (const uint8_t *) PSK_IDENTITY,\n        .public_cert_or_psk_identity_size = strlen(PSK_IDENTITY),\n        .private_cert_or_psk_key = (const uint8_t *) PSK_KEY,\n        .private_cert_or_psk_key_size = strlen(PSK_KEY)\n    };\n\n    // Anjay will assign Instance ID automatically\n    anjay_iid_t security_instance_id = ANJAY_ID_INVALID;\n    if (anjay_security_object_add_instance(anjay, &security_instance,\n                                           &security_instance_id)) {\n        return -1;\n    }\n\n    return 0;\n}\n\n// Installs Server Object and adds an instance of it.\n// An instance of Server Object provides the data related to a LwM2M Server.\nstatic int setup_server_object(anjay_t *anjay) {\n    if (anjay_server_object_install(anjay)) {\n        return -1;\n    }\n\n    const anjay_server_instance_t server_instance = {\n        // Server Short ID\n        .ssid = 1,\n        // Client will send Update message often than every 60 seconds\n        .lifetime = 60,\n        // Disable Default Minimum Period resource\n        .default_min_period = -1,\n        // Disable Default Maximum Period resource\n        .default_max_period = -1,\n        // Disable Disable Timeout resource\n        .disable_timeout = -1,\n        // Sets preferred transport to UDP\n        .binding = \"U\"\n    };\n\n    // Anjay will assign Instance ID automatically\n    anjay_iid_t server_instance_id = ANJAY_ID_INVALID;\n    if (anjay_server_object_add_instance(anjay, &server_instance,\n                                         &server_instance_id)) {\n        return -1;\n    }\n\n    return 0;\n}\n\nint main(int argc, char *argv[]) {\n    if (argc != 2) {\n        avs_log(tutorial, ERROR, \"usage: %s ENDPOINT_NAME\", argv[0]);\n        return -1;\n    }\n\n    const anjay_configuration_t CONFIG = {\n        .endpoint_name = argv[1],\n        .in_buffer_size = 4000,\n        .out_buffer_size = 4000,\n        .msg_cache_size = 4000\n    };\n\n    anjay_t *anjay = anjay_new(&CONFIG);\n    if (!anjay) {\n        avs_log(tutorial, ERROR, \"Could not create Anjay object\");\n        return -1;\n    }\n\n    int result = 0;\n    // Setup necessary objects\n    if (setup_security_object(anjay) || setup_server_object(anjay)) {\n        result = -1;\n    }\n\n    if (!result) {\n        result = main_loop(anjay);\n    }\n\n    anjay_delete(anjay);\n    return result;\n}\n"
  },
  {
    "path": "examples/custom-tls/stub/src/net_impl.c",
    "content": "#include <inttypes.h>\n\n#include <netdb.h>\n#include <poll.h>\n#include <unistd.h>\n\n#include <arpa/inet.h>\n\n#include <sys/socket.h>\n#include <sys/types.h>\n\n#include <avsystem/commons/avs_socket_v_table.h>\n#include <avsystem/commons/avs_utils.h>\n\n#ifdef AVS_COMMONS_NET_WITH_POSIX_AVS_SOCKET\n#    error \"Custom implementation of the network layer conflicts with AVS_COMMONS_NET_WITH_POSIX_AVS_SOCKET\"\n#endif // AVS_COMMONS_NET_WITH_POSIX_AVS_SOCKET\n\navs_error_t _avs_net_initialize_global_compat_state(void);\nvoid _avs_net_cleanup_global_compat_state(void);\navs_error_t _avs_net_create_tcp_socket(avs_net_socket_t **socket,\n                                       const void *socket_configuration);\navs_error_t _avs_net_create_udp_socket(avs_net_socket_t **socket,\n                                       const void *socket_configuration);\n\navs_error_t _avs_net_initialize_global_compat_state(void) {\n    return AVS_OK;\n}\n\nvoid _avs_net_cleanup_global_compat_state(void) {}\n\ntypedef struct {\n    const avs_net_socket_v_table_t *operations;\n    int socktype;\n    int fd;\n    avs_time_duration_t recv_timeout;\n    char remote_hostname[256];\n    bool shut_down;\n    size_t bytes_sent;\n    size_t bytes_received;\n    avs_net_resolved_endpoint_t *preferred_endpoint;\n} net_socket_impl_t;\n\ntypedef union {\n    struct sockaddr addr;\n    struct sockaddr_in in;\n    struct sockaddr_in6 in6;\n    struct sockaddr_storage storage;\n} sockaddr_union_t;\n\nstatic avs_error_t\nnet_connect(avs_net_socket_t *sock_, const char *host, const char *port) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    struct addrinfo hints = {\n        .ai_socktype = sock->socktype\n    };\n    if (sock->fd >= 0) {\n        getsockopt(sock->fd, SOL_SOCKET, SO_DOMAIN, &hints.ai_family,\n                   &(socklen_t) { sizeof(hints.ai_family) });\n    }\n    struct addrinfo *addrs = NULL;\n    avs_error_t err = AVS_OK;\n    if (getaddrinfo(host, port, &hints, &addrs) || !addrs) {\n        err = avs_errno(AVS_EADDRNOTAVAIL);\n    } else if (sock->fd < 0\n               && (sock->fd = socket(addrs->ai_family, addrs->ai_socktype,\n                                     addrs->ai_protocol))\n                          < 0) {\n        err = avs_errno(AVS_UNKNOWN_ERROR);\n    } else {\n        const struct addrinfo *addr = addrs;\n        if (sock->preferred_endpoint\n                && sock->preferred_endpoint->size == sizeof(sockaddr_union_t)) {\n            while (addr) {\n                if (addr->ai_addrlen <= sizeof(sockaddr_union_t)\n                        && memcmp(addr->ai_addr,\n                                  sock->preferred_endpoint->data.buf,\n                                  addr->ai_addrlen)\n                                       == 0) {\n                    break;\n                }\n                addr = addr->ai_next;\n            }\n        }\n        if (!addr) {\n            // Preferred endpoint not found, use the first one\n            addr = addrs;\n        }\n        if (connect(sock->fd, addr->ai_addr, addr->ai_addrlen)) {\n            err = avs_errno(AVS_ECONNREFUSED);\n        }\n        if (sock->preferred_endpoint && avs_is_ok(err)) {\n            assert(addr->ai_addrlen <= sizeof(sockaddr_union_t));\n            memcpy(sock->preferred_endpoint->data.buf, addr->ai_addr,\n                   addr->ai_addrlen);\n            sock->preferred_endpoint->size = sizeof(sockaddr_union_t);\n        }\n    }\n    if (avs_is_ok(err)) {\n        sock->shut_down = false;\n        snprintf(sock->remote_hostname, sizeof(sock->remote_hostname), \"%s\",\n                 host);\n    }\n    freeaddrinfo(addrs);\n    return err;\n}\n\nstatic avs_error_t\nnet_send(avs_net_socket_t *sock_, const void *buffer, size_t buffer_length) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    ssize_t written = send(sock->fd, buffer, buffer_length, MSG_NOSIGNAL);\n    if (written >= 0) {\n        sock->bytes_sent += (size_t) written;\n        if ((size_t) written == buffer_length) {\n            return AVS_OK;\n        }\n    }\n    return avs_errno(AVS_EIO);\n}\n\nstatic avs_error_t net_receive(avs_net_socket_t *sock_,\n                               size_t *out_bytes_received,\n                               void *buffer,\n                               size_t buffer_length) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    struct pollfd pfd = {\n        .fd = sock->fd,\n        .events = POLLIN\n    };\n    int64_t timeout_ms;\n    if (avs_time_duration_to_scalar(&timeout_ms, AVS_TIME_MS,\n                                    sock->recv_timeout)) {\n        timeout_ms = -1;\n    } else if (timeout_ms < 0) {\n        timeout_ms = 0;\n    }\n    if (poll(&pfd, 1, (int) timeout_ms) == 0) {\n        return avs_errno(AVS_ETIMEDOUT);\n    }\n    ssize_t bytes_received = read(sock->fd, buffer, buffer_length);\n    if (bytes_received < 0) {\n        return avs_errno(AVS_EIO);\n    }\n    *out_bytes_received = (size_t) bytes_received;\n    sock->bytes_received += (size_t) bytes_received;\n    if (buffer_length > 0 && sock->socktype == SOCK_DGRAM\n            && (size_t) bytes_received == buffer_length) {\n        return avs_errno(AVS_EMSGSIZE);\n    }\n    return AVS_OK;\n}\n\nstatic avs_error_t\nnet_bind(avs_net_socket_t *sock_, const char *address, const char *port) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    struct addrinfo hints = {\n        .ai_flags = AI_PASSIVE,\n        .ai_socktype = sock->socktype\n    };\n    if (sock->fd >= 0) {\n        getsockopt(sock->fd, SOL_SOCKET, SO_DOMAIN, &hints.ai_family,\n                   &(socklen_t) { sizeof(hints.ai_family) });\n    }\n    struct addrinfo *addr = NULL;\n    avs_error_t err = AVS_OK;\n    if (getaddrinfo(address, port, &hints, &addr) || !addr) {\n        err = avs_errno(AVS_EADDRNOTAVAIL);\n    } else if ((sock->fd < 0\n                && (sock->fd = socket(addr->ai_family, addr->ai_socktype,\n                                      addr->ai_protocol))\n                           < 0)\n               || setsockopt(sock->fd, SOL_SOCKET, SO_REUSEADDR, &(int) { 1 },\n                             sizeof(int))) {\n        err = avs_errno(AVS_UNKNOWN_ERROR);\n    } else if (bind(sock->fd, addr->ai_addr, addr->ai_addrlen)) {\n        err = avs_errno(AVS_ECONNREFUSED);\n    } else {\n        sock->shut_down = false;\n    }\n    if (avs_is_err(err) && sock->fd >= 0) {\n        close(sock->fd);\n        sock->fd = -1;\n    }\n    freeaddrinfo(addr);\n    return err;\n}\n\nstatic avs_error_t net_close(avs_net_socket_t *sock_) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    avs_error_t err = AVS_OK;\n    if (sock->fd >= 0) {\n        if (close(sock->fd)) {\n            err = avs_errno(AVS_EIO);\n        }\n        sock->fd = -1;\n        sock->shut_down = false;\n    }\n    return err;\n}\n\nstatic avs_error_t net_shutdown(avs_net_socket_t *sock_) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    avs_error_t err = avs_errno(AVS_EBADF);\n    if (sock->fd >= 0) {\n        err = shutdown(sock->fd, SHUT_RDWR) ? avs_errno(AVS_EIO) : AVS_OK;\n        sock->shut_down = true;\n    }\n    return err;\n}\n\nstatic avs_error_t net_cleanup(avs_net_socket_t **sock_ptr) {\n    avs_error_t err = AVS_OK;\n    if (sock_ptr && *sock_ptr) {\n        err = net_close(*sock_ptr);\n        avs_free(*sock_ptr);\n        *sock_ptr = NULL;\n    }\n    return err;\n}\n\nstatic const void *net_system_socket(avs_net_socket_t *sock_) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    return &sock->fd;\n}\n\nstatic avs_error_t stringify_sockaddr_host(const sockaddr_union_t *addr,\n                                           char *out_buffer,\n                                           size_t out_buffer_size) {\n    if ((addr->in.sin_family == AF_INET\n         && inet_ntop(AF_INET, &addr->in.sin_addr, out_buffer,\n                      (socklen_t) out_buffer_size))\n            || (addr->in6.sin6_family == AF_INET6\n                && inet_ntop(AF_INET6, &addr->in6.sin6_addr, out_buffer,\n                             (socklen_t) out_buffer_size))) {\n        return AVS_OK;\n    }\n    return avs_errno(AVS_UNKNOWN_ERROR);\n}\n\nstatic avs_error_t stringify_sockaddr_port(const sockaddr_union_t *addr,\n                                           char *out_buffer,\n                                           size_t out_buffer_size) {\n    if ((addr->in.sin_family == AF_INET\n         && avs_simple_snprintf(out_buffer, out_buffer_size, \"%\" PRIu16,\n                                ntohs(addr->in.sin_port))\n                    >= 0)\n            || (addr->in6.sin6_family == AF_INET6\n                && avs_simple_snprintf(out_buffer, out_buffer_size, \"%\" PRIu16,\n                                       ntohs(addr->in6.sin6_port))\n                           >= 0)) {\n        return AVS_OK;\n    }\n    return avs_errno(AVS_UNKNOWN_ERROR);\n}\n\nstatic avs_error_t net_remote_host(avs_net_socket_t *sock_,\n                                   char *out_buffer,\n                                   size_t out_buffer_size) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    sockaddr_union_t addr;\n    if (getpeername(sock->fd, &addr.addr, &(socklen_t) { sizeof(addr) })) {\n        return avs_errno(AVS_UNKNOWN_ERROR);\n    }\n    return stringify_sockaddr_host(&addr, out_buffer, out_buffer_size);\n}\n\nstatic avs_error_t net_remote_hostname(avs_net_socket_t *sock_,\n                                       char *out_buffer,\n                                       size_t out_buffer_size) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    return avs_simple_snprintf(out_buffer, out_buffer_size, \"%s\",\n                               sock->remote_hostname)\n                           < 0\n                   ? avs_errno(AVS_UNKNOWN_ERROR)\n                   : AVS_OK;\n}\n\nstatic avs_error_t net_remote_port(avs_net_socket_t *sock_,\n                                   char *out_buffer,\n                                   size_t out_buffer_size) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    sockaddr_union_t addr;\n    if (getpeername(sock->fd, &addr.addr, &(socklen_t) { sizeof(addr) })) {\n        return avs_errno(AVS_UNKNOWN_ERROR);\n    }\n    return stringify_sockaddr_port(&addr, out_buffer, out_buffer_size);\n}\n\nstatic avs_error_t net_local_port(avs_net_socket_t *sock_,\n                                  char *out_buffer,\n                                  size_t out_buffer_size) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    sockaddr_union_t addr;\n    if (getsockname(sock->fd, &addr.addr, &(socklen_t) { sizeof(addr) })) {\n        return avs_errno(AVS_UNKNOWN_ERROR);\n    }\n    return stringify_sockaddr_port(&addr, out_buffer, out_buffer_size);\n}\n\nstatic avs_error_t net_get_opt(avs_net_socket_t *sock_,\n                               avs_net_socket_opt_key_t option_key,\n                               avs_net_socket_opt_value_t *out_option_value) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    switch (option_key) {\n    case AVS_NET_SOCKET_OPT_RECV_TIMEOUT:\n        out_option_value->recv_timeout = sock->recv_timeout;\n        return AVS_OK;\n    case AVS_NET_SOCKET_OPT_STATE:\n        if (sock->fd < 0) {\n            out_option_value->state = AVS_NET_SOCKET_STATE_CLOSED;\n        } else if (sock->shut_down) {\n            out_option_value->state = AVS_NET_SOCKET_STATE_SHUTDOWN;\n        } else {\n            sockaddr_union_t addr;\n            if (!getpeername(sock->fd, &addr.addr,\n                             &(socklen_t) { sizeof(addr) })\n                    && ((addr.in.sin_family == AF_INET && addr.in.sin_port != 0)\n                        || (addr.in6.sin6_family == AF_INET6\n                            && addr.in6.sin6_port != 0))) {\n                out_option_value->state = AVS_NET_SOCKET_STATE_CONNECTED;\n            } else {\n                out_option_value->state = AVS_NET_SOCKET_STATE_BOUND;\n            }\n        }\n        return AVS_OK;\n    case AVS_NET_SOCKET_OPT_INNER_MTU:\n        out_option_value->mtu = 1464;\n        return AVS_OK;\n    case AVS_NET_SOCKET_HAS_BUFFERED_DATA:\n        out_option_value->flag = false;\n        return AVS_OK;\n    case AVS_NET_SOCKET_OPT_BYTES_SENT:\n        out_option_value->bytes_sent = sock->bytes_sent;\n        return AVS_OK;\n    case AVS_NET_SOCKET_OPT_BYTES_RECEIVED:\n        out_option_value->bytes_received = sock->bytes_received;\n        return AVS_OK;\n    default:\n        return avs_errno(AVS_ENOTSUP);\n    }\n}\n\nstatic avs_error_t net_set_opt(avs_net_socket_t *sock_,\n                               avs_net_socket_opt_key_t option_key,\n                               avs_net_socket_opt_value_t option_value) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    switch (option_key) {\n    case AVS_NET_SOCKET_OPT_RECV_TIMEOUT:\n        sock->recv_timeout = option_value.recv_timeout;\n        return AVS_OK;\n    default:\n        return avs_errno(AVS_ENOTSUP);\n    }\n}\n\nstatic const avs_net_socket_v_table_t NET_SOCKET_VTABLE = {\n    .connect = net_connect,\n    .send = net_send,\n    .receive = net_receive,\n    .bind = net_bind,\n    .close = net_close,\n    .shutdown = net_shutdown,\n    .cleanup = net_cleanup,\n    .get_system_socket = net_system_socket,\n    .get_remote_host = net_remote_host,\n    .get_remote_hostname = net_remote_hostname,\n    .get_remote_port = net_remote_port,\n    .get_local_port = net_local_port,\n    .get_opt = net_get_opt,\n    .set_opt = net_set_opt\n};\n\nstatic avs_error_t\nnet_create_socket(avs_net_socket_t **socket_ptr,\n                  const avs_net_socket_configuration_t *configuration,\n                  int socktype) {\n    assert(socket_ptr);\n    assert(!*socket_ptr);\n    (void) configuration;\n    net_socket_impl_t *socket =\n            (net_socket_impl_t *) avs_calloc(1, sizeof(net_socket_impl_t));\n    if (!socket) {\n        return avs_errno(AVS_ENOMEM);\n    }\n    socket->operations = &NET_SOCKET_VTABLE;\n    socket->socktype = socktype;\n    socket->fd = -1;\n    socket->recv_timeout = avs_time_duration_from_scalar(30, AVS_TIME_S);\n    socket->preferred_endpoint = configuration->preferred_endpoint;\n    *socket_ptr = (avs_net_socket_t *) socket;\n    return AVS_OK;\n}\n\navs_error_t _avs_net_create_udp_socket(avs_net_socket_t **socket_ptr,\n                                       const void *configuration) {\n    return net_create_socket(\n            socket_ptr, (const avs_net_socket_configuration_t *) configuration,\n            SOCK_DGRAM);\n}\n\navs_error_t _avs_net_create_tcp_socket(avs_net_socket_t **socket_ptr,\n                                       const void *configuration) {\n    return net_create_socket(\n            socket_ptr, (const avs_net_socket_configuration_t *) configuration,\n            SOCK_STREAM);\n}\n\navs_error_t\navs_net_resolved_endpoint_get_host_port(const avs_net_resolved_endpoint_t *endp,\n                                        char *host,\n                                        size_t hostlen,\n                                        char *serv,\n                                        size_t servlen) {\n    AVS_STATIC_ASSERT(sizeof(endp->data.buf) >= sizeof(sockaddr_union_t),\n                      data_buffer_big_enough);\n    if (endp->size != sizeof(sockaddr_union_t)) {\n        return avs_errno(AVS_EINVAL);\n    }\n    const sockaddr_union_t *addr = (const sockaddr_union_t *) &endp->data.buf;\n    avs_error_t err = AVS_OK;\n    (void) ((host\n             && avs_is_err(\n                        (err = stringify_sockaddr_host(addr, host, hostlen))))\n            || (serv\n                && avs_is_err((err = stringify_sockaddr_port(addr, serv,\n                                                             servlen)))));\n    return err;\n}\n"
  },
  {
    "path": "examples/custom-tls/stub/src/tls_impl.c",
    "content": "#include <poll.h>\n\n#include <openssl/ssl.h>\n\n#include <avsystem/commons/avs_crypto_common.h>\n#include <avsystem/commons/avs_socket_v_table.h>\n#include <avsystem/commons/avs_utils.h>\n\n#ifndef AVS_COMMONS_WITH_CUSTOM_TLS\n#    error \"Custom implementation of the TLS layer requires AVS_COMMONS_WITH_CUSTOM_TLS\"\n#endif // AVS_COMMONS_WITH_CUSTOM_TLS\n\navs_error_t _avs_net_initialize_global_ssl_state(void);\nvoid _avs_net_cleanup_global_ssl_state(void);\navs_error_t _avs_net_create_ssl_socket(avs_net_socket_t **socket,\n                                       const void *socket_configuration);\navs_error_t _avs_net_create_dtls_socket(avs_net_socket_t **socket,\n                                        const void *socket_configuration);\n\navs_error_t _avs_net_initialize_global_ssl_state(void) {\n    if (!OPENSSL_init_ssl(OPENSSL_INIT_ADD_ALL_CIPHERS\n                                  | OPENSSL_INIT_ADD_ALL_DIGESTS,\n                          NULL)) {\n        return avs_errno(AVS_EPROTO);\n    }\n    return AVS_OK;\n}\n\nvoid _avs_net_cleanup_global_ssl_state(void) {}\n\nvoid avs_crypto_clear_buffer(void *buf, size_t size) {\n    OPENSSL_cleanse(buf, size);\n}\n\ntypedef struct {\n    const avs_net_socket_v_table_t *operations;\n    avs_net_socket_t *backend_socket;\n    SSL_CTX *ctx;\n    SSL *ssl;\n} tls_socket_impl_t;\n\nstatic avs_error_t perform_handshake(tls_socket_impl_t *sock,\n                                     const char *host) {\n    return avs_errno(AVS_ENOTSUP);\n}\n\nstatic avs_error_t\ntls_connect(avs_net_socket_t *sock_, const char *host, const char *port) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    avs_error_t err;\n    if (avs_is_err((\n                err = avs_net_socket_connect(sock->backend_socket, host, port)))\n            || avs_is_err((err = perform_handshake(sock, host)))) {\n        if (sock->ssl) {\n            SSL_free(sock->ssl);\n            sock->ssl = NULL;\n        }\n        avs_net_socket_close(sock->backend_socket);\n    }\n    return err;\n}\n\nstatic avs_error_t\ntls_send(avs_net_socket_t *sock_, const void *buffer, size_t buffer_length) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    int result = SSL_write(sock->ssl, buffer, (int) buffer_length);\n    if (result < 0 || (size_t) result < buffer_length) {\n        return avs_errno(AVS_EPROTO);\n    }\n    return AVS_OK;\n}\n\nstatic avs_error_t tls_receive(avs_net_socket_t *sock_,\n                               size_t *out_bytes_received,\n                               void *buffer,\n                               size_t buffer_length) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    const void *fd_ptr = avs_net_socket_get_system(sock->backend_socket);\n    avs_net_socket_opt_value_t timeout;\n    if (!fd_ptr\n            || avs_is_err(avs_net_socket_get_opt(\n                       sock->backend_socket, AVS_NET_SOCKET_OPT_RECV_TIMEOUT,\n                       &timeout))) {\n        return avs_errno(AVS_EBADF);\n    }\n    struct pollfd pfd = {\n        .fd = *(const int *) fd_ptr,\n        .events = POLLIN\n    };\n    int64_t timeout_ms;\n    if (avs_time_duration_to_scalar(&timeout_ms, AVS_TIME_MS,\n                                    timeout.recv_timeout)) {\n        timeout_ms = -1;\n    } else if (timeout_ms < 0) {\n        timeout_ms = 0;\n    }\n    if (poll(&pfd, 1, (int) timeout_ms) == 0) {\n        return avs_errno(AVS_ETIMEDOUT);\n    }\n    int bytes_received = SSL_read(sock->ssl, buffer, (int) buffer_length);\n    if (bytes_received < 0) {\n        return avs_errno(AVS_EPROTO);\n    }\n    *out_bytes_received = (size_t) bytes_received;\n    if (buffer_length > 0 && (size_t) bytes_received == buffer_length) {\n        return avs_errno(AVS_EMSGSIZE);\n    }\n    return AVS_OK;\n}\n\nstatic avs_error_t\ntls_bind(avs_net_socket_t *sock_, const char *address, const char *port) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    return avs_net_socket_bind(sock->backend_socket, address, port);\n}\n\nstatic avs_error_t tls_close(avs_net_socket_t *sock_) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    if (sock->ssl) {\n        SSL_free(sock->ssl);\n        sock->ssl = NULL;\n    }\n    return avs_net_socket_close(sock->backend_socket);\n}\n\nstatic avs_error_t tls_shutdown(avs_net_socket_t *sock_) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    return avs_net_socket_shutdown(sock->backend_socket);\n}\n\nstatic avs_error_t tls_cleanup(avs_net_socket_t **sock_ptr) {\n    return avs_errno(AVS_ENOTSUP);\n}\n\nstatic const void *tls_system_socket(avs_net_socket_t *sock_) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    return avs_net_socket_get_system(sock->backend_socket);\n}\n\nstatic avs_error_t tls_remote_host(avs_net_socket_t *sock_,\n                                   char *out_buffer,\n                                   size_t out_buffer_size) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    return avs_net_socket_get_remote_host(sock->backend_socket, out_buffer,\n                                          out_buffer_size);\n}\n\nstatic avs_error_t tls_remote_hostname(avs_net_socket_t *sock_,\n                                       char *out_buffer,\n                                       size_t out_buffer_size) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    return avs_net_socket_get_remote_hostname(sock->backend_socket, out_buffer,\n                                              out_buffer_size);\n}\n\nstatic avs_error_t tls_remote_port(avs_net_socket_t *sock_,\n                                   char *out_buffer,\n                                   size_t out_buffer_size) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    return avs_net_socket_get_remote_port(sock->backend_socket, out_buffer,\n                                          out_buffer_size);\n}\n\nstatic avs_error_t tls_local_port(avs_net_socket_t *sock_,\n                                  char *out_buffer,\n                                  size_t out_buffer_size) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    return avs_net_socket_get_local_port(sock->backend_socket, out_buffer,\n                                         out_buffer_size);\n}\n\nstatic avs_error_t tls_get_opt(avs_net_socket_t *sock_,\n                               avs_net_socket_opt_key_t option_key,\n                               avs_net_socket_opt_value_t *out_option_value) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    return avs_net_socket_get_opt(sock->backend_socket, option_key,\n                                  out_option_value);\n}\n\nstatic avs_error_t tls_set_opt(avs_net_socket_t *sock_,\n                               avs_net_socket_opt_key_t option_key,\n                               avs_net_socket_opt_value_t option_value) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    return avs_net_socket_set_opt(sock->backend_socket, option_key,\n                                  option_value);\n}\n\nstatic const avs_net_socket_v_table_t TLS_SOCKET_VTABLE = {\n    .connect = tls_connect,\n    .send = tls_send,\n    .receive = tls_receive,\n    .bind = tls_bind,\n    .close = tls_close,\n    .shutdown = tls_shutdown,\n    .cleanup = tls_cleanup,\n    .get_system_socket = tls_system_socket,\n    .get_remote_host = tls_remote_host,\n    .get_remote_hostname = tls_remote_hostname,\n    .get_remote_port = tls_remote_port,\n    .get_local_port = tls_local_port,\n    .get_opt = tls_get_opt,\n    .set_opt = tls_set_opt\n};\n\navs_error_t _avs_net_create_dtls_socket(avs_net_socket_t **socket_ptr,\n                                        const void *configuration) {\n    return avs_errno(AVS_ENOTSUP);\n}\n\navs_error_t _avs_net_create_ssl_socket(avs_net_socket_t **socket_ptr,\n                                       const void *configuration) {\n    return avs_errno(AVS_ENOTSUP);\n}\n"
  },
  {
    "path": "examples/custom-tls/tcp-support/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.16)\nproject(custom-tls-tcp-support C)\n\nset(CMAKE_C_STANDARD 99)\n\nfind_package(anjay REQUIRED)\nfind_package(OpenSSL REQUIRED)\n\nadd_executable(${PROJECT_NAME}\n               src/main.c\n               src/firmware_update.c\n               src/firmware_update.h\n               src/net_impl.c\n               src/time_object.c\n               src/time_object.h\n               src/tls_impl.c)\ntarget_link_libraries(${PROJECT_NAME} PRIVATE anjay OpenSSL::SSL)\n"
  },
  {
    "path": "examples/custom-tls/tcp-support/src/firmware_update.c",
    "content": "#include \"./firmware_update.h\"\n\n#include <assert.h>\n#include <errno.h>\n#include <stdio.h>\n#include <sys/stat.h>\n#include <unistd.h>\n\nstatic struct fw_state_t {\n    FILE *firmware_file;\n    // anjay instance this firmware update singleton is associated with\n    anjay_t *anjay;\n} FW_STATE;\n\nstatic const char *FW_IMAGE_DOWNLOAD_NAME = \"/tmp/firmware_image.bin\";\n\nstatic int fw_stream_open(void *user_ptr,\n                          const char *package_uri,\n                          const struct anjay_etag *package_etag) {\n    // For a moment, we don't need to care about any of the arguments passed.\n    (void) user_ptr;\n    (void) package_uri;\n    (void) package_etag;\n\n    // It's worth ensuring we start with a NULL firmware_file. In the end\n    // it would be our responsibility to manage this pointer, and we want\n    // to make sure we never leak any memory.\n    assert(FW_STATE.firmware_file == NULL);\n    // We're about to create a firmware file for writing\n    FW_STATE.firmware_file = fopen(FW_IMAGE_DOWNLOAD_NAME, \"wb\");\n    if (!FW_STATE.firmware_file) {\n        fprintf(stderr, \"Could not open %s\\n\", FW_IMAGE_DOWNLOAD_NAME);\n        return -1;\n    }\n    // We've succeeded\n    return 0;\n}\n\nstatic int fw_stream_write(void *user_ptr, const void *data, size_t length) {\n    (void) user_ptr;\n    // We only need to write to file and check if that succeeded\n    if (fwrite(data, length, 1, FW_STATE.firmware_file) != 1) {\n        fprintf(stderr, \"Writing to firmware image failed\\n\");\n        return -1;\n    }\n    return 0;\n}\n\nstatic int fw_stream_finish(void *user_ptr) {\n    (void) user_ptr;\n    assert(FW_STATE.firmware_file != NULL);\n\n    if (fclose(FW_STATE.firmware_file)) {\n        fprintf(stderr, \"Closing firmware image failed\\n\");\n        FW_STATE.firmware_file = NULL;\n        return -1;\n    }\n    FW_STATE.firmware_file = NULL;\n    return 0;\n}\n\nstatic void fw_reset(void *user_ptr) {\n    // Reset can be issued even if the download never started.\n    if (FW_STATE.firmware_file) {\n        // We ignore the result code of fclose(), as fw_reset() can't fail.\n        (void) fclose(FW_STATE.firmware_file);\n        // and reset our global state to initial value.\n        FW_STATE.firmware_file = NULL;\n    }\n    // Finally, let's remove any downloaded payload\n    unlink(FW_IMAGE_DOWNLOAD_NAME);\n}\n\n// A part of a rather simple logic checking if the firmware update was\n// successfully performed.\nstatic const char *FW_UPDATED_MARKER = \"/tmp/fw-updated-marker\";\n\nstatic int fw_perform_upgrade(void *user_ptr) {\n    if (chmod(FW_IMAGE_DOWNLOAD_NAME, 0700) == -1) {\n        fprintf(stderr,\n                \"Could not make firmware executable: %s\\n\",\n                strerror(errno));\n        return -1;\n    }\n    // Create a marker file, so that the new process knows it is the \"upgraded\"\n    // one\n    FILE *marker = fopen(FW_UPDATED_MARKER, \"w\");\n    if (!marker) {\n        fprintf(stderr, \"Marker file could not be created\\n\");\n        return -1;\n    }\n    fclose(marker);\n\n    assert(ENDPOINT_NAME);\n    // If the call below succeeds, the firmware is considered as \"upgraded\",\n    // and we hope the newly started client registers to the Server.\n    (void) execl(FW_IMAGE_DOWNLOAD_NAME, FW_IMAGE_DOWNLOAD_NAME, ENDPOINT_NAME,\n                 NULL);\n    fprintf(stderr, \"execl() failed: %s\\n\", strerror(errno));\n    // If we are here, it means execl() failed. Marker file MUST now be removed,\n    // as the firmware update failed.\n    unlink(FW_UPDATED_MARKER);\n    return -1;\n}\n\nstatic int\nload_buffer_from_file(uint8_t **out, size_t *out_size, const char *filename) {\n    FILE *f = fopen(filename, \"rb\");\n    if (!f) {\n        return -1;\n    }\n    int result = -1;\n    if (fseek(f, 0, SEEK_END)) {\n        goto finish;\n    }\n    long size = ftell(f);\n    if (size < 0 || (unsigned long) size > SIZE_MAX || fseek(f, 0, SEEK_SET)) {\n        goto finish;\n    }\n    *out_size = (size_t) size;\n    if (!(*out = (uint8_t *) avs_malloc(*out_size))) {\n        goto finish;\n    }\n    if (fread(*out, *out_size, 1, f) != 1) {\n        avs_free(*out);\n        *out = NULL;\n        goto finish;\n    }\n    result = 0;\nfinish:\n    fclose(f);\n    return result;\n}\n\nstatic int fw_get_security_config(void *user_ptr,\n                                  anjay_security_config_t *out_security_info,\n                                  const char *download_uri) {\n    (void) user_ptr;\n    if (!anjay_security_config_from_dm(FW_STATE.anjay, out_security_info,\n                                       download_uri)) {\n        // found a match\n        return 0;\n    }\n\n    // no match found, fallback to loading certificates from given paths\n    static uint8_t *ca_cert = NULL;\n    static size_t ca_cert_size = 0;\n    static uint8_t *client_cert = NULL;\n    static size_t client_cert_size = 0;\n    static uint8_t *client_key = NULL;\n    static size_t client_key_size = 0;\n    if ((!ca_cert\n         && load_buffer_from_file(&ca_cert, &ca_cert_size,\n                                  \"./certs/CA.crt.der\"))\n            || (!client_cert\n                && load_buffer_from_file(&client_cert, &client_cert_size,\n                                         \"./certs/client.crt.der\"))\n            || (!client_key\n                && load_buffer_from_file(&client_key, &client_key_size,\n                                         \"./certs/client.key.der\"))) {\n        return -1;\n    }\n\n    memset(out_security_info, 0, sizeof(*out_security_info));\n    const avs_net_certificate_info_t cert_info = {\n        .server_cert_validation = true,\n        .trusted_certs = avs_crypto_certificate_chain_info_from_buffer(\n                ca_cert, ca_cert_size),\n        .client_cert = avs_crypto_certificate_chain_info_from_buffer(\n                client_cert, client_cert_size),\n        .client_key = avs_crypto_private_key_info_from_buffer(\n                client_key, client_key_size, NULL)\n    };\n    out_security_info->security_info =\n            avs_net_security_info_from_certificates(cert_info);\n    return 0;\n}\n\nstatic const anjay_fw_update_handlers_t HANDLERS = {\n    .stream_open = fw_stream_open,\n    .stream_write = fw_stream_write,\n    .stream_finish = fw_stream_finish,\n    .reset = fw_reset,\n    .perform_upgrade = fw_perform_upgrade,\n    .get_security_config = fw_get_security_config\n};\n\nconst char *ENDPOINT_NAME = NULL;\n\nint fw_update_install(anjay_t *anjay) {\n    anjay_fw_update_initial_state_t state;\n    memset(&state, 0, sizeof(state));\n\n    if (access(FW_UPDATED_MARKER, F_OK) != -1) {\n        // marker file exists, it means firmware update succeded!\n        state.result = ANJAY_FW_UPDATE_INITIAL_SUCCESS;\n        unlink(FW_UPDATED_MARKER);\n    }\n    // make sure this module is installed for single Anjay instance only\n    assert(FW_STATE.anjay == NULL);\n    FW_STATE.anjay = anjay;\n    // install the module, pass handlers that we implemented and initial state\n    // that we discovered upon startup\n    return anjay_fw_update_install(anjay, &HANDLERS, NULL, &state);\n}\n"
  },
  {
    "path": "examples/custom-tls/tcp-support/src/firmware_update.h",
    "content": "#ifndef FIRMWARE_UPDATE_H\n#define FIRMWARE_UPDATE_H\n#include <anjay/anjay.h>\n#include <anjay/fw_update.h>\n\n/**\n * Buffer for the endpoint name that will be used when re-launching the client\n * after firmware upgrade.\n */\nextern const char *ENDPOINT_NAME;\n\n/**\n * Installs the firmware update module.\n *\n * @returns 0 on success, negative value otherwise.\n */\nint fw_update_install(anjay_t *anjay);\n\n#endif // FIRMWARE_UPDATE_H\n"
  },
  {
    "path": "examples/custom-tls/tcp-support/src/main.c",
    "content": "#include <anjay/anjay.h>\n#include <anjay/security.h>\n#include <anjay/server.h>\n#include <avsystem/commons/avs_log.h>\n\n#include \"firmware_update.h\"\n#include \"time_object.h\"\n\ntypedef struct {\n    anjay_t *anjay;\n    const anjay_dm_object_def_t **time_object;\n} notify_job_args_t;\n\n// Periodically notifies the library about Resource value changes\nstatic void notify_job(avs_sched_t *sched, const void *args_ptr) {\n    const notify_job_args_t *args = (const notify_job_args_t *) args_ptr;\n\n    time_object_notify(args->anjay, args->time_object);\n\n    // Schedule run of the same function after 1 second\n    AVS_SCHED_DELAYED(sched, NULL, avs_time_duration_from_scalar(1, AVS_TIME_S),\n                      notify_job, args, sizeof(*args));\n}\n\n// Installs Security Object and adds an instance of it.\n// An instance of Security Object provides information needed to connect to\n// LwM2M server.\nstatic int setup_security_object(anjay_t *anjay) {\n    if (anjay_security_object_install(anjay)) {\n        return -1;\n    }\n\n    static const char PSK_IDENTITY[] = \"identity\";\n    static const char PSK_KEY[] = \"P4s$w0rd\";\n\n    anjay_security_instance_t security_instance = {\n        .ssid = 1,\n        .server_uri = \"coaps://eu.iot.avsystem.cloud:5684\",\n        .security_mode = ANJAY_SECURITY_PSK,\n        .public_cert_or_psk_identity = (const uint8_t *) PSK_IDENTITY,\n        .public_cert_or_psk_identity_size = strlen(PSK_IDENTITY),\n        .private_cert_or_psk_key = (const uint8_t *) PSK_KEY,\n        .private_cert_or_psk_key_size = strlen(PSK_KEY)\n    };\n\n    // Anjay will assign Instance ID automatically\n    anjay_iid_t security_instance_id = ANJAY_ID_INVALID;\n    if (anjay_security_object_add_instance(anjay, &security_instance,\n                                           &security_instance_id)) {\n        return -1;\n    }\n\n    return 0;\n}\n\n// Installs Server Object and adds an instance of it.\n// An instance of Server Object provides the data related to a LwM2M Server.\nstatic int setup_server_object(anjay_t *anjay) {\n    if (anjay_server_object_install(anjay)) {\n        return -1;\n    }\n\n    const anjay_server_instance_t server_instance = {\n        // Server Short ID\n        .ssid = 1,\n        // Client will send Update message often than every 60 seconds\n        .lifetime = 60,\n        // Disable Default Minimum Period resource\n        .default_min_period = -1,\n        // Disable Default Maximum Period resource\n        .default_max_period = -1,\n        // Disable Disable Timeout resource\n        .disable_timeout = -1,\n        // Sets preferred transport to UDP\n        .binding = \"U\"\n    };\n\n    // Anjay will assign Instance ID automatically\n    anjay_iid_t server_instance_id = ANJAY_ID_INVALID;\n    if (anjay_server_object_add_instance(anjay, &server_instance,\n                                         &server_instance_id)) {\n        return -1;\n    }\n\n    return 0;\n}\n\nint main(int argc, char *argv[]) {\n    if (argc != 2) {\n        avs_log(tutorial, ERROR, \"usage: %s ENDPOINT_NAME\", argv[0]);\n        return -1;\n    }\n\n    ENDPOINT_NAME = argv[1];\n\n    const anjay_configuration_t CONFIG = {\n        .endpoint_name = ENDPOINT_NAME,\n        .in_buffer_size = 4000,\n        .out_buffer_size = 4000,\n        .msg_cache_size = 4000\n    };\n\n    anjay_t *anjay = anjay_new(&CONFIG);\n    if (!anjay) {\n        avs_log(tutorial, ERROR, \"Could not create Anjay object\");\n        return -1;\n    }\n\n    int result = 0;\n    // Setup necessary objects\n    if (setup_security_object(anjay) || setup_server_object(anjay)\n            || fw_update_install(anjay)) {\n        result = -1;\n    }\n\n    const anjay_dm_object_def_t **time_object = NULL;\n    if (!result) {\n        time_object = time_object_create();\n        if (time_object) {\n            result = anjay_register_object(anjay, time_object);\n        } else {\n            result = -1;\n        }\n    }\n\n    if (!result) {\n        // Run notify_job the first time;\n        // this will schedule periodic calls to itself via the scheduler\n        notify_job(anjay_get_scheduler(anjay), &(const notify_job_args_t) {\n                                                   .anjay = anjay,\n                                                   .time_object = time_object\n                                               });\n\n        result = anjay_event_loop_run(\n                anjay, avs_time_duration_from_scalar(1, AVS_TIME_S));\n    }\n\n    anjay_delete(anjay);\n    time_object_release(time_object);\n    return result;\n}\n"
  },
  {
    "path": "examples/custom-tls/tcp-support/src/net_impl.c",
    "content": "#include <inttypes.h>\n\n#include <netdb.h>\n#include <poll.h>\n#include <unistd.h>\n\n#include <arpa/inet.h>\n\n#include <sys/socket.h>\n#include <sys/types.h>\n\n#include <avsystem/commons/avs_socket_v_table.h>\n#include <avsystem/commons/avs_utils.h>\n\n#ifdef AVS_COMMONS_NET_WITH_POSIX_AVS_SOCKET\n#    error \"Custom implementation of the network layer conflicts with AVS_COMMONS_NET_WITH_POSIX_AVS_SOCKET\"\n#endif // AVS_COMMONS_NET_WITH_POSIX_AVS_SOCKET\n\navs_error_t _avs_net_initialize_global_compat_state(void);\nvoid _avs_net_cleanup_global_compat_state(void);\navs_error_t _avs_net_create_tcp_socket(avs_net_socket_t **socket,\n                                       const void *socket_configuration);\navs_error_t _avs_net_create_udp_socket(avs_net_socket_t **socket,\n                                       const void *socket_configuration);\n\navs_error_t _avs_net_initialize_global_compat_state(void) {\n    return AVS_OK;\n}\n\nvoid _avs_net_cleanup_global_compat_state(void) {}\n\ntypedef struct {\n    const avs_net_socket_v_table_t *operations;\n    int socktype;\n    int fd;\n    avs_time_duration_t recv_timeout;\n    char remote_hostname[256];\n    bool shut_down;\n    size_t bytes_sent;\n    size_t bytes_received;\n    avs_net_resolved_endpoint_t *preferred_endpoint;\n} net_socket_impl_t;\n\ntypedef union {\n    struct sockaddr addr;\n    struct sockaddr_in in;\n    struct sockaddr_in6 in6;\n    struct sockaddr_storage storage;\n} sockaddr_union_t;\n\nstatic avs_error_t\nnet_connect(avs_net_socket_t *sock_, const char *host, const char *port) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    struct addrinfo hints = {\n        .ai_socktype = sock->socktype\n    };\n    if (sock->fd >= 0) {\n        getsockopt(sock->fd, SOL_SOCKET, SO_DOMAIN, &hints.ai_family,\n                   &(socklen_t) { sizeof(hints.ai_family) });\n    }\n    struct addrinfo *addrs = NULL;\n    avs_error_t err = AVS_OK;\n    if (getaddrinfo(host, port, &hints, &addrs) || !addrs) {\n        err = avs_errno(AVS_EADDRNOTAVAIL);\n    } else if (sock->fd < 0\n               && (sock->fd = socket(addrs->ai_family, addrs->ai_socktype,\n                                     addrs->ai_protocol))\n                          < 0) {\n        err = avs_errno(AVS_UNKNOWN_ERROR);\n    } else {\n        const struct addrinfo *addr = addrs;\n        if (sock->preferred_endpoint\n                && sock->preferred_endpoint->size == sizeof(sockaddr_union_t)) {\n            while (addr) {\n                if (addr->ai_addrlen <= sizeof(sockaddr_union_t)\n                        && memcmp(addr->ai_addr,\n                                  sock->preferred_endpoint->data.buf,\n                                  addr->ai_addrlen)\n                                       == 0) {\n                    break;\n                }\n                addr = addr->ai_next;\n            }\n        }\n        if (!addr) {\n            // Preferred endpoint not found, use the first one\n            addr = addrs;\n        }\n        if (connect(sock->fd, addr->ai_addr, addr->ai_addrlen)) {\n            err = avs_errno(AVS_ECONNREFUSED);\n        }\n        if (sock->preferred_endpoint && avs_is_ok(err)) {\n            assert(addr->ai_addrlen <= sizeof(sockaddr_union_t));\n            memcpy(sock->preferred_endpoint->data.buf, addr->ai_addr,\n                   addr->ai_addrlen);\n            sock->preferred_endpoint->size = sizeof(sockaddr_union_t);\n        }\n    }\n    if (avs_is_ok(err)) {\n        sock->shut_down = false;\n        snprintf(sock->remote_hostname, sizeof(sock->remote_hostname), \"%s\",\n                 host);\n    }\n    freeaddrinfo(addrs);\n    return err;\n}\n\nstatic avs_error_t\nnet_send(avs_net_socket_t *sock_, const void *buffer, size_t buffer_length) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    ssize_t written = send(sock->fd, buffer, buffer_length, MSG_NOSIGNAL);\n    if (written >= 0) {\n        sock->bytes_sent += (size_t) written;\n        if ((size_t) written == buffer_length) {\n            return AVS_OK;\n        }\n    }\n    return avs_errno(AVS_EIO);\n}\n\nstatic avs_error_t net_receive(avs_net_socket_t *sock_,\n                               size_t *out_bytes_received,\n                               void *buffer,\n                               size_t buffer_length) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    struct pollfd pfd = {\n        .fd = sock->fd,\n        .events = POLLIN\n    };\n    int64_t timeout_ms;\n    if (avs_time_duration_to_scalar(&timeout_ms, AVS_TIME_MS,\n                                    sock->recv_timeout)) {\n        timeout_ms = -1;\n    } else if (timeout_ms < 0) {\n        timeout_ms = 0;\n    }\n    if (poll(&pfd, 1, (int) timeout_ms) == 0) {\n        return avs_errno(AVS_ETIMEDOUT);\n    }\n    ssize_t bytes_received = read(sock->fd, buffer, buffer_length);\n    if (bytes_received < 0) {\n        return avs_errno(AVS_EIO);\n    }\n    *out_bytes_received = (size_t) bytes_received;\n    sock->bytes_received += (size_t) bytes_received;\n    if (buffer_length > 0 && sock->socktype == SOCK_DGRAM\n            && (size_t) bytes_received == buffer_length) {\n        return avs_errno(AVS_EMSGSIZE);\n    }\n    return AVS_OK;\n}\n\nstatic avs_error_t\nnet_bind(avs_net_socket_t *sock_, const char *address, const char *port) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    struct addrinfo hints = {\n        .ai_flags = AI_PASSIVE,\n        .ai_socktype = sock->socktype\n    };\n    if (sock->fd >= 0) {\n        getsockopt(sock->fd, SOL_SOCKET, SO_DOMAIN, &hints.ai_family,\n                   &(socklen_t) { sizeof(hints.ai_family) });\n    }\n    struct addrinfo *addr = NULL;\n    avs_error_t err = AVS_OK;\n    if (getaddrinfo(address, port, &hints, &addr) || !addr) {\n        err = avs_errno(AVS_EADDRNOTAVAIL);\n    } else if ((sock->fd < 0\n                && (sock->fd = socket(addr->ai_family, addr->ai_socktype,\n                                      addr->ai_protocol))\n                           < 0)\n               || setsockopt(sock->fd, SOL_SOCKET, SO_REUSEADDR, &(int) { 1 },\n                             sizeof(int))) {\n        err = avs_errno(AVS_UNKNOWN_ERROR);\n    } else if (bind(sock->fd, addr->ai_addr, addr->ai_addrlen)) {\n        err = avs_errno(AVS_ECONNREFUSED);\n    } else {\n        sock->shut_down = false;\n    }\n    if (avs_is_err(err) && sock->fd >= 0) {\n        close(sock->fd);\n        sock->fd = -1;\n    }\n    freeaddrinfo(addr);\n    return err;\n}\n\nstatic avs_error_t net_close(avs_net_socket_t *sock_) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    avs_error_t err = AVS_OK;\n    if (sock->fd >= 0) {\n        if (close(sock->fd)) {\n            err = avs_errno(AVS_EIO);\n        }\n        sock->fd = -1;\n        sock->shut_down = false;\n    }\n    return err;\n}\n\nstatic avs_error_t net_shutdown(avs_net_socket_t *sock_) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    avs_error_t err = avs_errno(AVS_EBADF);\n    if (sock->fd >= 0) {\n        err = shutdown(sock->fd, SHUT_RDWR) ? avs_errno(AVS_EIO) : AVS_OK;\n        sock->shut_down = true;\n    }\n    return err;\n}\n\nstatic avs_error_t net_cleanup(avs_net_socket_t **sock_ptr) {\n    avs_error_t err = AVS_OK;\n    if (sock_ptr && *sock_ptr) {\n        err = net_close(*sock_ptr);\n        avs_free(*sock_ptr);\n        *sock_ptr = NULL;\n    }\n    return err;\n}\n\nstatic const void *net_system_socket(avs_net_socket_t *sock_) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    return &sock->fd;\n}\n\nstatic avs_error_t stringify_sockaddr_host(const sockaddr_union_t *addr,\n                                           char *out_buffer,\n                                           size_t out_buffer_size) {\n    if ((addr->in.sin_family == AF_INET\n         && inet_ntop(AF_INET, &addr->in.sin_addr, out_buffer,\n                      (socklen_t) out_buffer_size))\n            || (addr->in6.sin6_family == AF_INET6\n                && inet_ntop(AF_INET6, &addr->in6.sin6_addr, out_buffer,\n                             (socklen_t) out_buffer_size))) {\n        return AVS_OK;\n    }\n    return avs_errno(AVS_UNKNOWN_ERROR);\n}\n\nstatic avs_error_t stringify_sockaddr_port(const sockaddr_union_t *addr,\n                                           char *out_buffer,\n                                           size_t out_buffer_size) {\n    if ((addr->in.sin_family == AF_INET\n         && avs_simple_snprintf(out_buffer, out_buffer_size, \"%\" PRIu16,\n                                ntohs(addr->in.sin_port))\n                    >= 0)\n            || (addr->in6.sin6_family == AF_INET6\n                && avs_simple_snprintf(out_buffer, out_buffer_size, \"%\" PRIu16,\n                                       ntohs(addr->in6.sin6_port))\n                           >= 0)) {\n        return AVS_OK;\n    }\n    return avs_errno(AVS_UNKNOWN_ERROR);\n}\n\nstatic avs_error_t net_remote_host(avs_net_socket_t *sock_,\n                                   char *out_buffer,\n                                   size_t out_buffer_size) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    sockaddr_union_t addr;\n    if (getpeername(sock->fd, &addr.addr, &(socklen_t) { sizeof(addr) })) {\n        return avs_errno(AVS_UNKNOWN_ERROR);\n    }\n    return stringify_sockaddr_host(&addr, out_buffer, out_buffer_size);\n}\n\nstatic avs_error_t net_remote_hostname(avs_net_socket_t *sock_,\n                                       char *out_buffer,\n                                       size_t out_buffer_size) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    return avs_simple_snprintf(out_buffer, out_buffer_size, \"%s\",\n                               sock->remote_hostname)\n                           < 0\n                   ? avs_errno(AVS_UNKNOWN_ERROR)\n                   : AVS_OK;\n}\n\nstatic avs_error_t net_remote_port(avs_net_socket_t *sock_,\n                                   char *out_buffer,\n                                   size_t out_buffer_size) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    sockaddr_union_t addr;\n    if (getpeername(sock->fd, &addr.addr, &(socklen_t) { sizeof(addr) })) {\n        return avs_errno(AVS_UNKNOWN_ERROR);\n    }\n    return stringify_sockaddr_port(&addr, out_buffer, out_buffer_size);\n}\n\nstatic avs_error_t net_local_port(avs_net_socket_t *sock_,\n                                  char *out_buffer,\n                                  size_t out_buffer_size) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    sockaddr_union_t addr;\n    if (getsockname(sock->fd, &addr.addr, &(socklen_t) { sizeof(addr) })) {\n        return avs_errno(AVS_UNKNOWN_ERROR);\n    }\n    return stringify_sockaddr_port(&addr, out_buffer, out_buffer_size);\n}\n\nstatic avs_error_t net_get_opt(avs_net_socket_t *sock_,\n                               avs_net_socket_opt_key_t option_key,\n                               avs_net_socket_opt_value_t *out_option_value) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    switch (option_key) {\n    case AVS_NET_SOCKET_OPT_RECV_TIMEOUT:\n        out_option_value->recv_timeout = sock->recv_timeout;\n        return AVS_OK;\n    case AVS_NET_SOCKET_OPT_STATE:\n        if (sock->fd < 0) {\n            out_option_value->state = AVS_NET_SOCKET_STATE_CLOSED;\n        } else if (sock->shut_down) {\n            out_option_value->state = AVS_NET_SOCKET_STATE_SHUTDOWN;\n        } else {\n            sockaddr_union_t addr;\n            if (!getpeername(sock->fd, &addr.addr,\n                             &(socklen_t) { sizeof(addr) })\n                    && ((addr.in.sin_family == AF_INET && addr.in.sin_port != 0)\n                        || (addr.in6.sin6_family == AF_INET6\n                            && addr.in6.sin6_port != 0))) {\n                out_option_value->state = AVS_NET_SOCKET_STATE_CONNECTED;\n            } else {\n                out_option_value->state = AVS_NET_SOCKET_STATE_BOUND;\n            }\n        }\n        return AVS_OK;\n    case AVS_NET_SOCKET_OPT_INNER_MTU:\n        out_option_value->mtu = 1464;\n        return AVS_OK;\n    case AVS_NET_SOCKET_HAS_BUFFERED_DATA:\n        out_option_value->flag = false;\n        return AVS_OK;\n    case AVS_NET_SOCKET_OPT_BYTES_SENT:\n        out_option_value->bytes_sent = sock->bytes_sent;\n        return AVS_OK;\n    case AVS_NET_SOCKET_OPT_BYTES_RECEIVED:\n        out_option_value->bytes_received = sock->bytes_received;\n        return AVS_OK;\n    default:\n        return avs_errno(AVS_ENOTSUP);\n    }\n}\n\nstatic avs_error_t net_set_opt(avs_net_socket_t *sock_,\n                               avs_net_socket_opt_key_t option_key,\n                               avs_net_socket_opt_value_t option_value) {\n    net_socket_impl_t *sock = (net_socket_impl_t *) sock_;\n    switch (option_key) {\n    case AVS_NET_SOCKET_OPT_RECV_TIMEOUT:\n        sock->recv_timeout = option_value.recv_timeout;\n        return AVS_OK;\n    default:\n        return avs_errno(AVS_ENOTSUP);\n    }\n}\n\nstatic const avs_net_socket_v_table_t NET_SOCKET_VTABLE = {\n    .connect = net_connect,\n    .send = net_send,\n    .receive = net_receive,\n    .bind = net_bind,\n    .close = net_close,\n    .shutdown = net_shutdown,\n    .cleanup = net_cleanup,\n    .get_system_socket = net_system_socket,\n    .get_remote_host = net_remote_host,\n    .get_remote_hostname = net_remote_hostname,\n    .get_remote_port = net_remote_port,\n    .get_local_port = net_local_port,\n    .get_opt = net_get_opt,\n    .set_opt = net_set_opt\n};\n\nstatic avs_error_t\nnet_create_socket(avs_net_socket_t **socket_ptr,\n                  const avs_net_socket_configuration_t *configuration,\n                  int socktype) {\n    assert(socket_ptr);\n    assert(!*socket_ptr);\n    (void) configuration;\n    net_socket_impl_t *socket =\n            (net_socket_impl_t *) avs_calloc(1, sizeof(net_socket_impl_t));\n    if (!socket) {\n        return avs_errno(AVS_ENOMEM);\n    }\n    socket->operations = &NET_SOCKET_VTABLE;\n    socket->socktype = socktype;\n    socket->fd = -1;\n    socket->recv_timeout = avs_time_duration_from_scalar(30, AVS_TIME_S);\n    socket->preferred_endpoint = configuration->preferred_endpoint;\n    *socket_ptr = (avs_net_socket_t *) socket;\n    return AVS_OK;\n}\n\navs_error_t _avs_net_create_udp_socket(avs_net_socket_t **socket_ptr,\n                                       const void *configuration) {\n    return net_create_socket(\n            socket_ptr, (const avs_net_socket_configuration_t *) configuration,\n            SOCK_DGRAM);\n}\n\navs_error_t _avs_net_create_tcp_socket(avs_net_socket_t **socket_ptr,\n                                       const void *configuration) {\n    return net_create_socket(\n            socket_ptr, (const avs_net_socket_configuration_t *) configuration,\n            SOCK_STREAM);\n}\n\navs_error_t\navs_net_resolved_endpoint_get_host_port(const avs_net_resolved_endpoint_t *endp,\n                                        char *host,\n                                        size_t hostlen,\n                                        char *serv,\n                                        size_t servlen) {\n    AVS_STATIC_ASSERT(sizeof(endp->data.buf) >= sizeof(sockaddr_union_t),\n                      data_buffer_big_enough);\n    if (endp->size != sizeof(sockaddr_union_t)) {\n        return avs_errno(AVS_EINVAL);\n    }\n    const sockaddr_union_t *addr = (const sockaddr_union_t *) &endp->data.buf;\n    avs_error_t err = AVS_OK;\n    (void) ((host\n             && avs_is_err(\n                        (err = stringify_sockaddr_host(addr, host, hostlen))))\n            || (serv\n                && avs_is_err((err = stringify_sockaddr_port(addr, serv,\n                                                             servlen)))));\n    return err;\n}\n"
  },
  {
    "path": "examples/custom-tls/tcp-support/src/time_object.c",
    "content": "/**\n * Generated by anjay_codegen.py on 2020-03-19 14:13:34\n *\n * LwM2M Object: Time\n * ID: 3333, URN: urn:oma:lwm2m:ext:3333, Optional, Multiple\n *\n * This IPSO object is used to report the current time in seconds since\n * January 1, 1970 UTC. There is also a fractional time counter that has\n * a range of less than one second.\n */\n\n#include <assert.h>\n#include <stdbool.h>\n\n#include <anjay/anjay.h>\n#include <avsystem/commons/avs_defs.h>\n#include <avsystem/commons/avs_list.h>\n#include <avsystem/commons/avs_memory.h>\n\n#include \"time_object.h\"\n\n/**\n * Current Time: RW, Single, Mandatory\n * type: time, range: N/A, unit: N/A\n * Unix Time. A signed integer representing the number of seconds since\n * Jan 1st, 1970 in the UTC time zone.\n */\n#define RID_CURRENT_TIME 5506\n\n/**\n * Fractional Time: RW, Single, Optional\n * type: float, range: 0..1, unit: s\n * Fractional part of the time when sub-second precision is used (e.g.,\n * 0.23 for 230 ms).\n */\n#define RID_FRACTIONAL_TIME 5507\n\n/**\n * Application Type: RW, Single, Optional\n * type: string, range: N/A, unit: N/A\n * The application type of the sensor or actuator as a string depending\n * on the use case.\n */\n#define RID_APPLICATION_TYPE 5750\n\ntypedef struct time_instance_struct {\n    anjay_iid_t iid;\n    char application_type[64];\n    char application_type_backup[64];\n    int64_t last_notify_timestamp;\n} time_instance_t;\n\ntypedef struct time_object_struct {\n    const anjay_dm_object_def_t *def;\n    AVS_LIST(time_instance_t) instances;\n} time_object_t;\n\nstatic inline time_object_t *\nget_obj(const anjay_dm_object_def_t *const *obj_ptr) {\n    assert(obj_ptr);\n    return AVS_CONTAINER_OF(obj_ptr, time_object_t, def);\n}\n\nstatic time_instance_t *find_instance(const time_object_t *obj,\n                                      anjay_iid_t iid) {\n    AVS_LIST(time_instance_t) it;\n    AVS_LIST_FOREACH(it, obj->instances) {\n        if (it->iid == iid) {\n            return it;\n        } else if (it->iid > iid) {\n            break;\n        }\n    }\n\n    return NULL;\n}\n\nstatic int list_instances(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr,\n                          anjay_dm_list_ctx_t *ctx) {\n    (void) anjay;\n\n    AVS_LIST(time_instance_t) it;\n    AVS_LIST_FOREACH(it, get_obj(obj_ptr)->instances) {\n        anjay_dm_emit(ctx, it->iid);\n    }\n\n    return 0;\n}\n\nstatic int init_instance(time_instance_t *inst, anjay_iid_t iid) {\n    assert(iid != ANJAY_ID_INVALID);\n\n    inst->iid = iid;\n    inst->application_type[0] = '\\0';\n\n    return 0;\n}\n\nstatic void release_instance(time_instance_t *inst) {\n    (void) inst;\n}\n\nstatic time_instance_t *add_instance(time_object_t *obj, anjay_iid_t iid) {\n    assert(find_instance(obj, iid) == NULL);\n\n    AVS_LIST(time_instance_t) created = AVS_LIST_NEW_ELEMENT(time_instance_t);\n    if (!created) {\n        return NULL;\n    }\n\n    int result = init_instance(created, iid);\n    if (result) {\n        AVS_LIST_CLEAR(&created);\n        return NULL;\n    }\n\n    AVS_LIST(time_instance_t) *ptr;\n    AVS_LIST_FOREACH_PTR(ptr, &obj->instances) {\n        if ((*ptr)->iid > created->iid) {\n            break;\n        }\n    }\n\n    AVS_LIST_INSERT(ptr, created);\n    return created;\n}\n\nstatic int instance_create(anjay_t *anjay,\n                           const anjay_dm_object_def_t *const *obj_ptr,\n                           anjay_iid_t iid) {\n    (void) anjay;\n    time_object_t *obj = get_obj(obj_ptr);\n\n    return add_instance(obj, iid) ? 0 : ANJAY_ERR_INTERNAL;\n}\n\nstatic int instance_remove(anjay_t *anjay,\n                           const anjay_dm_object_def_t *const *obj_ptr,\n                           anjay_iid_t iid) {\n    (void) anjay;\n    time_object_t *obj = get_obj(obj_ptr);\n\n    AVS_LIST(time_instance_t) *it;\n    AVS_LIST_FOREACH_PTR(it, &obj->instances) {\n        if ((*it)->iid == iid) {\n            release_instance(*it);\n            AVS_LIST_DELETE(it);\n            return 0;\n        } else if ((*it)->iid > iid) {\n            break;\n        }\n    }\n\n    assert(0);\n    return ANJAY_ERR_NOT_FOUND;\n}\n\nstatic int instance_reset(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr,\n                          anjay_iid_t iid) {\n    (void) anjay;\n\n    time_object_t *obj = get_obj(obj_ptr);\n    time_instance_t *inst = find_instance(obj, iid);\n    assert(inst);\n\n    inst->application_type[0] = '\\0';\n\n    return 0;\n}\n\nstatic int list_resources(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr,\n                          anjay_iid_t iid,\n                          anjay_dm_resource_list_ctx_t *ctx) {\n    (void) anjay;\n    (void) obj_ptr;\n    (void) iid;\n\n    anjay_dm_emit_res(ctx, RID_CURRENT_TIME, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, RID_FRACTIONAL_TIME, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT);\n    anjay_dm_emit_res(ctx, RID_APPLICATION_TYPE, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_PRESENT);\n    return 0;\n}\n\nstatic int resource_read(anjay_t *anjay,\n                         const anjay_dm_object_def_t *const *obj_ptr,\n                         anjay_iid_t iid,\n                         anjay_rid_t rid,\n                         anjay_riid_t riid,\n                         anjay_output_ctx_t *ctx) {\n    (void) anjay;\n\n    time_object_t *obj = get_obj(obj_ptr);\n    time_instance_t *inst = find_instance(obj, iid);\n    assert(inst);\n\n    switch (rid) {\n    case RID_CURRENT_TIME: {\n        assert(riid == ANJAY_ID_INVALID);\n        int64_t timestamp;\n        if (avs_time_real_to_scalar(&timestamp, AVS_TIME_S,\n                                    avs_time_real_now())) {\n            return -1;\n        }\n        return anjay_ret_i64(ctx, timestamp);\n    }\n\n    case RID_APPLICATION_TYPE:\n        assert(riid == ANJAY_ID_INVALID);\n        return anjay_ret_string(ctx, inst->application_type);\n\n    default:\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\nstatic int resource_write(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr,\n                          anjay_iid_t iid,\n                          anjay_rid_t rid,\n                          anjay_riid_t riid,\n                          anjay_input_ctx_t *ctx) {\n    (void) anjay;\n\n    time_object_t *obj = get_obj(obj_ptr);\n    time_instance_t *inst = find_instance(obj, iid);\n    assert(inst);\n\n    switch (rid) {\n    case RID_APPLICATION_TYPE:\n        assert(riid == ANJAY_ID_INVALID);\n        return anjay_get_string(ctx, inst->application_type,\n                                sizeof(inst->application_type));\n\n    default:\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\nint transaction_begin(anjay_t *anjay,\n                      const anjay_dm_object_def_t *const *obj_ptr) {\n    (void) anjay;\n\n    time_object_t *obj = get_obj(obj_ptr);\n\n    time_instance_t *element;\n    AVS_LIST_FOREACH(element, obj->instances) {\n        strcpy(element->application_type_backup, element->application_type);\n    }\n    return 0;\n}\n\nint transaction_rollback(anjay_t *anjay,\n                         const anjay_dm_object_def_t *const *obj_ptr) {\n    (void) anjay;\n\n    time_object_t *obj = get_obj(obj_ptr);\n\n    time_instance_t *element;\n    AVS_LIST_FOREACH(element, obj->instances) {\n        strcpy(element->application_type, element->application_type_backup);\n    }\n    return 0;\n}\n\nstatic const anjay_dm_object_def_t OBJ_DEF = {\n    .oid = 3333,\n    .handlers = {\n        .list_instances = list_instances,\n        .instance_create = instance_create,\n        .instance_remove = instance_remove,\n        .instance_reset = instance_reset,\n\n        .list_resources = list_resources,\n        .resource_read = resource_read,\n        .resource_write = resource_write,\n\n        .transaction_begin = transaction_begin,\n        .transaction_validate = anjay_dm_transaction_NOOP,\n        .transaction_commit = anjay_dm_transaction_NOOP,\n        .transaction_rollback = transaction_rollback\n    }\n};\n\nconst anjay_dm_object_def_t **time_object_create(void) {\n    time_object_t *obj = (time_object_t *) avs_calloc(1, sizeof(time_object_t));\n    if (!obj) {\n        return NULL;\n    }\n    obj->def = &OBJ_DEF;\n\n    time_instance_t *inst = add_instance(obj, 0);\n    if (inst) {\n        strcpy(inst->application_type, \"Clock 0\");\n    } else {\n        avs_free(obj);\n        return NULL;\n    }\n\n    return &obj->def;\n}\n\nvoid time_object_release(const anjay_dm_object_def_t **def) {\n    if (def) {\n        time_object_t *obj = get_obj(def);\n        AVS_LIST_CLEAR(&obj->instances) {\n            release_instance(obj->instances);\n        }\n\n        avs_free(obj);\n    }\n}\n\nvoid time_object_notify(anjay_t *anjay, const anjay_dm_object_def_t **def) {\n    if (!anjay || !def) {\n        return;\n    }\n    time_object_t *obj = get_obj(def);\n\n    int64_t current_timestamp;\n    if (avs_time_real_to_scalar(&current_timestamp, AVS_TIME_S,\n                                avs_time_real_now())) {\n        return;\n    }\n\n    AVS_LIST(time_instance_t) it;\n    AVS_LIST_FOREACH(it, obj->instances) {\n        if (it->last_notify_timestamp != current_timestamp) {\n            if (!anjay_notify_changed(anjay, 3333, it->iid, RID_CURRENT_TIME)) {\n                it->last_notify_timestamp = current_timestamp;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "examples/custom-tls/tcp-support/src/time_object.h",
    "content": "#ifndef TIME_OBJECT_H\n#define TIME_OBJECT_H\n\n#include <anjay/dm.h>\n\nconst anjay_dm_object_def_t **time_object_create(void);\nvoid time_object_release(const anjay_dm_object_def_t **def);\nvoid time_object_notify(anjay_t *anjay, const anjay_dm_object_def_t **def);\n\n#endif // TIME_OBJECT_H\n"
  },
  {
    "path": "examples/custom-tls/tcp-support/src/tls_impl.c",
    "content": "#include <poll.h>\n\n#include <openssl/err.h>\n#include <openssl/ssl.h>\n\n#include <avsystem/commons/avs_crypto_common.h>\n#include <avsystem/commons/avs_socket_v_table.h>\n#include <avsystem/commons/avs_utils.h>\n\n#ifndef AVS_COMMONS_WITH_CUSTOM_TLS\n#    error \"Custom implementation of the TLS layer requires AVS_COMMONS_WITH_CUSTOM_TLS\"\n#endif // AVS_COMMONS_WITH_CUSTOM_TLS\n\navs_error_t _avs_net_initialize_global_ssl_state(void);\nvoid _avs_net_cleanup_global_ssl_state(void);\navs_error_t _avs_net_create_ssl_socket(avs_net_socket_t **socket,\n                                       const void *socket_configuration);\navs_error_t _avs_net_create_dtls_socket(avs_net_socket_t **socket,\n                                        const void *socket_configuration);\n\navs_error_t _avs_net_initialize_global_ssl_state(void) {\n    if (!OPENSSL_init_ssl(OPENSSL_INIT_ADD_ALL_CIPHERS\n                                  | OPENSSL_INIT_ADD_ALL_DIGESTS,\n                          NULL)) {\n        return avs_errno(AVS_EPROTO);\n    }\n    return AVS_OK;\n}\n\nvoid _avs_net_cleanup_global_ssl_state(void) {}\n\nvoid avs_crypto_clear_buffer(void *buf, size_t size) {\n    OPENSSL_cleanse(buf, size);\n}\n\ntypedef struct {\n    const avs_net_socket_v_table_t *operations;\n    avs_net_socket_type_t backend_type;\n    avs_net_socket_t *backend_socket;\n    SSL_CTX *ctx;\n    SSL *ssl;\n\n    char psk[256];\n    size_t psk_size;\n    char identity[128];\n    size_t identity_size;\n\n    bool dane_enabled;\n    char dane_tlsa_association_data_buf[4096];\n    avs_net_socket_dane_tlsa_record_t dane_tlsa_array[4];\n    size_t dane_tlsa_array_size;\n\n    void *session_resumption_buffer;\n    size_t session_resumption_buffer_size;\n\n    char server_name_indication[256];\n    unsigned int dtls_hs_timeout_min_us;\n    unsigned int dtls_hs_timeout_max_us;\n} tls_socket_impl_t;\n\nstatic unsigned int dtls_timer_cb(SSL *s, unsigned int timer_us) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) SSL_get_app_data(s);\n    if (!timer_us) {\n        return sock->dtls_hs_timeout_min_us;\n    } else if (timer_us >= sock->dtls_hs_timeout_max_us) {\n        // maximum number of retransmissions reached, let's give up\n        avs_net_socket_shutdown(sock->backend_socket);\n        return 0;\n    } else {\n        timer_us *= 2;\n        if (timer_us > sock->dtls_hs_timeout_max_us) {\n            timer_us = sock->dtls_hs_timeout_max_us;\n        }\n        return timer_us;\n    }\n}\n\nstatic avs_error_t perform_handshake(tls_socket_impl_t *sock,\n                                     const char *host) {\n    union {\n        struct sockaddr addr;\n        struct sockaddr_storage storage;\n    } peername;\n    const void *fd_ptr = avs_net_socket_get_system(sock->backend_socket);\n    if (!fd_ptr\n            || getpeername(*(const int *) fd_ptr, &peername.addr,\n                           &(socklen_t) { sizeof(peername) })) {\n        return avs_errno(AVS_EBADF);\n    }\n\n    sock->ssl = SSL_new(sock->ctx);\n    if (!sock->ssl) {\n        return avs_errno(AVS_ENOMEM);\n    }\n\n    SSL_set_app_data(sock->ssl, sock);\n    if (sock->dane_enabled) {\n        // NOTE: SSL_dane_enable() calls SSL_set_tlsext_host_name() internally\n        SSL_dane_enable(sock->ssl, host);\n        bool have_usable_tlsa_records = false;\n        for (size_t i = 0; i < sock->dane_tlsa_array_size; ++i) {\n            if (SSL_CTX_get_verify_mode(sock->ctx) == SSL_VERIFY_NONE\n                    && (sock->dane_tlsa_array[i].certificate_usage\n                                == AVS_NET_SOCKET_DANE_CA_CONSTRAINT\n                        || sock->dane_tlsa_array[i].certificate_usage\n                                   == AVS_NET_SOCKET_DANE_SERVICE_CERTIFICATE_CONSTRAINT)) {\n                // PKIX-TA and PKIX-EE constraints are unusable for\n                // opportunistic clients\n                continue;\n            }\n            SSL_dane_tlsa_add(\n                    sock->ssl,\n                    (uint8_t) sock->dane_tlsa_array[i].certificate_usage,\n                    (uint8_t) sock->dane_tlsa_array[i].selector,\n                    (uint8_t) sock->dane_tlsa_array[i].matching_type,\n                    (unsigned const char *) sock->dane_tlsa_array[i]\n                            .association_data,\n                    sock->dane_tlsa_array[i].association_data_size);\n            have_usable_tlsa_records = true;\n        }\n        if (SSL_CTX_get_verify_mode(sock->ctx) == SSL_VERIFY_NONE\n                && have_usable_tlsa_records) {\n            SSL_set_verify(sock->ssl, SSL_VERIFY_PEER, NULL);\n        }\n    } else {\n        SSL_set_tlsext_host_name(sock->ssl, host);\n    }\n    SSL_set1_host(sock->ssl, host);\n\n    BIO *bio = NULL;\n    if (sock->backend_type == AVS_NET_UDP_SOCKET) {\n        bio = BIO_new_dgram(*(const int *) fd_ptr, 0);\n        BIO_ctrl(bio, BIO_CTRL_DGRAM_SET_CONNECTED, 0, &peername.addr);\n        DTLS_set_timer_cb(sock->ssl, dtls_timer_cb);\n    } else {\n        bio = BIO_new_socket(*(const int *) fd_ptr, 0);\n    }\n    if (!bio) {\n        return avs_errno(AVS_ENOMEM);\n    }\n    SSL_set_bio(sock->ssl, bio, bio);\n\n    if (sock->session_resumption_buffer) {\n        const unsigned char *ptr =\n                (const unsigned char *) sock->session_resumption_buffer;\n        SSL_SESSION *session =\n                d2i_SSL_SESSION(NULL, &ptr,\n                                sock->session_resumption_buffer_size);\n        if (session) {\n            SSL_set_session(sock->ssl, session);\n            SSL_SESSION_free(session);\n        }\n    }\n\n    if (SSL_connect(sock->ssl) <= 0) {\n        return avs_errno(AVS_EPROTO);\n    }\n    return AVS_OK;\n}\n\nstatic avs_error_t\ntls_connect(avs_net_socket_t *sock_, const char *host, const char *port) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    if (sock->ssl) {\n        return avs_errno(AVS_EBADF);\n    }\n    avs_error_t err;\n    if (avs_is_err((\n                err = avs_net_socket_connect(sock->backend_socket, host, port)))\n            || avs_is_err((err = perform_handshake(\n                                   sock, sock->server_name_indication[0]\n                                                 ? sock->server_name_indication\n                                                 : host)))) {\n        if (sock->ssl) {\n            SSL_free(sock->ssl);\n            sock->ssl = NULL;\n        }\n        avs_net_socket_close(sock->backend_socket);\n    }\n    return err;\n}\n\nstatic avs_error_t\ntls_send(avs_net_socket_t *sock_, const void *buffer, size_t buffer_length) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    int result = SSL_write(sock->ssl, buffer, (int) buffer_length);\n    if (result < 0 || (size_t) result < buffer_length) {\n        return avs_errno(AVS_EPROTO);\n    }\n    return AVS_OK;\n}\n\nstatic avs_error_t tls_receive(avs_net_socket_t *sock_,\n                               size_t *out_bytes_received,\n                               void *buffer,\n                               size_t buffer_length) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    int pending = 0;\n    if (sock->backend_type == AVS_NET_TCP_SOCKET) {\n        pending = SSL_pending(sock->ssl);\n    }\n    if (pending > 0) {\n        buffer_length = AVS_MIN(buffer_length, (size_t) pending);\n    } else {\n        const void *fd_ptr = avs_net_socket_get_system(sock->backend_socket);\n        avs_net_socket_opt_value_t timeout;\n        if (!fd_ptr\n                || avs_is_err(avs_net_socket_get_opt(\n                           sock->backend_socket,\n                           AVS_NET_SOCKET_OPT_RECV_TIMEOUT, &timeout))) {\n            return avs_errno(AVS_EBADF);\n        }\n        struct pollfd pfd = {\n            .fd = *(const int *) fd_ptr,\n            .events = POLLIN\n        };\n        int64_t timeout_ms;\n        if (avs_time_duration_to_scalar(&timeout_ms, AVS_TIME_MS,\n                                        timeout.recv_timeout)) {\n            timeout_ms = -1;\n        } else if (timeout_ms < 0) {\n            timeout_ms = 0;\n        }\n        if (poll(&pfd, 1, (int) timeout_ms) == 0) {\n            return avs_errno(AVS_ETIMEDOUT);\n        }\n    }\n    int bytes_received = SSL_read(sock->ssl, buffer, (int) buffer_length);\n    if (bytes_received < 0) {\n        return avs_errno(AVS_EPROTO);\n    }\n    *out_bytes_received = (size_t) bytes_received;\n    if (sock->backend_type == AVS_NET_UDP_SOCKET && buffer_length > 0\n            && (size_t) bytes_received == buffer_length) {\n        return avs_errno(AVS_EMSGSIZE);\n    }\n    return AVS_OK;\n}\n\nstatic avs_error_t\ntls_bind(avs_net_socket_t *sock_, const char *address, const char *port) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    return avs_net_socket_bind(sock->backend_socket, address, port);\n}\n\nstatic avs_error_t tls_close(avs_net_socket_t *sock_) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    if (sock->ssl) {\n        SSL_free(sock->ssl);\n        sock->ssl = NULL;\n    }\n    return avs_net_socket_close(sock->backend_socket);\n}\n\nstatic avs_error_t tls_shutdown(avs_net_socket_t *sock_) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    return avs_net_socket_shutdown(sock->backend_socket);\n}\n\nstatic avs_error_t tls_cleanup(avs_net_socket_t **sock_ptr) {\n    avs_error_t err = AVS_OK;\n    if (sock_ptr && *sock_ptr) {\n        tls_socket_impl_t *sock = (tls_socket_impl_t *) *sock_ptr;\n        tls_close(*sock_ptr);\n        avs_net_socket_cleanup(&sock->backend_socket);\n        if (sock->ctx) {\n            SSL_CTX_free(sock->ctx);\n        }\n        avs_free(sock);\n        *sock_ptr = NULL;\n    }\n    return err;\n}\n\nstatic const void *tls_system_socket(avs_net_socket_t *sock_) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    return avs_net_socket_get_system(sock->backend_socket);\n}\n\nstatic avs_error_t tls_remote_host(avs_net_socket_t *sock_,\n                                   char *out_buffer,\n                                   size_t out_buffer_size) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    return avs_net_socket_get_remote_host(sock->backend_socket, out_buffer,\n                                          out_buffer_size);\n}\n\nstatic avs_error_t tls_remote_hostname(avs_net_socket_t *sock_,\n                                       char *out_buffer,\n                                       size_t out_buffer_size) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    return avs_net_socket_get_remote_hostname(sock->backend_socket, out_buffer,\n                                              out_buffer_size);\n}\n\nstatic avs_error_t tls_remote_port(avs_net_socket_t *sock_,\n                                   char *out_buffer,\n                                   size_t out_buffer_size) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    return avs_net_socket_get_remote_port(sock->backend_socket, out_buffer,\n                                          out_buffer_size);\n}\n\nstatic avs_error_t tls_local_port(avs_net_socket_t *sock_,\n                                  char *out_buffer,\n                                  size_t out_buffer_size) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    return avs_net_socket_get_local_port(sock->backend_socket, out_buffer,\n                                         out_buffer_size);\n}\n\nstatic avs_error_t tls_get_opt(avs_net_socket_t *sock_,\n                               avs_net_socket_opt_key_t option_key,\n                               avs_net_socket_opt_value_t *out_option_value) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    switch (option_key) {\n    case AVS_NET_SOCKET_OPT_INNER_MTU: {\n        avs_error_t err = avs_net_socket_get_opt(sock->backend_socket,\n                                                 AVS_NET_SOCKET_OPT_INNER_MTU,\n                                                 out_option_value);\n        if (avs_is_ok(err)) {\n            out_option_value->mtu = AVS_MAX(out_option_value->mtu - 64, 0);\n        }\n        return err;\n    }\n    case AVS_NET_SOCKET_HAS_BUFFERED_DATA:\n        out_option_value->flag = (sock->ssl && SSL_pending(sock->ssl) > 0);\n        return AVS_OK;\n    case AVS_NET_SOCKET_OPT_SESSION_RESUMED:\n        out_option_value->flag = (sock->ssl && SSL_session_reused(sock->ssl));\n        return AVS_OK;\n    default:\n        return avs_net_socket_get_opt(sock->backend_socket, option_key,\n                                      out_option_value);\n    }\n}\n\nstatic avs_error_t tls_set_opt(avs_net_socket_t *sock_,\n                               avs_net_socket_opt_key_t option_key,\n                               avs_net_socket_opt_value_t option_value) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) sock_;\n    switch (option_key) {\n    case AVS_NET_SOCKET_OPT_DANE_TLSA_ARRAY: {\n        if (option_value.dane_tlsa_array.array_element_count\n                > AVS_ARRAY_SIZE(sock->dane_tlsa_array)) {\n            return avs_errno(AVS_EINVAL);\n        }\n        avs_net_socket_dane_tlsa_record_t\n                copied_array[AVS_ARRAY_SIZE(sock->dane_tlsa_array)];\n        char copied_association_data[sizeof(\n                sock->dane_tlsa_association_data_buf)];\n        size_t copied_association_data_offset = 0;\n        memcpy(copied_array, option_value.dane_tlsa_array.array_ptr,\n               option_value.dane_tlsa_array.array_element_count\n                       * sizeof(avs_net_socket_dane_tlsa_record_t));\n        for (size_t i = 0; i < option_value.dane_tlsa_array.array_element_count;\n             ++i) {\n            if (copied_association_data_offset\n                            + option_value.dane_tlsa_array.array_ptr[i]\n                                      .association_data_size\n                    > sizeof(copied_association_data)) {\n                return avs_errno(AVS_EINVAL);\n            }\n            memcpy(copied_association_data + copied_association_data_offset,\n                   option_value.dane_tlsa_array.array_ptr[i].association_data,\n                   option_value.dane_tlsa_array.array_ptr[i]\n                           .association_data_size);\n            copied_array[i].association_data =\n                    sock->dane_tlsa_association_data_buf\n                    + copied_association_data_offset;\n            copied_association_data_offset +=\n                    option_value.dane_tlsa_array.array_ptr[i]\n                            .association_data_size;\n        }\n        memcpy(sock->dane_tlsa_association_data_buf, copied_association_data,\n               sizeof(copied_association_data));\n        memcpy(sock->dane_tlsa_array, copied_array, sizeof(copied_array));\n        sock->dane_tlsa_array_size =\n                option_value.dane_tlsa_array.array_element_count;\n        return AVS_OK;\n    }\n    default:\n        return avs_net_socket_set_opt(sock->backend_socket, option_key,\n                                      option_value);\n    }\n}\n\nstatic const avs_net_socket_v_table_t TLS_SOCKET_VTABLE = {\n    .connect = tls_connect,\n    .send = tls_send,\n    .receive = tls_receive,\n    .bind = tls_bind,\n    .close = tls_close,\n    .shutdown = tls_shutdown,\n    .cleanup = tls_cleanup,\n    .get_system_socket = tls_system_socket,\n    .get_remote_host = tls_remote_host,\n    .get_remote_hostname = tls_remote_hostname,\n    .get_remote_port = tls_remote_port,\n    .get_local_port = tls_local_port,\n    .get_opt = tls_get_opt,\n    .set_opt = tls_set_opt\n};\n\nstatic avs_error_t configure_dtls_version(tls_socket_impl_t *sock,\n                                          avs_net_ssl_version_t version) {\n    switch (version) {\n    case AVS_NET_SSL_VERSION_DEFAULT:\n        return AVS_OK;\n    case AVS_NET_SSL_VERSION_TLSv1:\n    case AVS_NET_SSL_VERSION_TLSv1_1:\n        SSL_CTX_set_min_proto_version(sock->ctx, DTLS1_VERSION);\n        return AVS_OK;\n    case AVS_NET_SSL_VERSION_TLSv1_2:\n        SSL_CTX_set_min_proto_version(sock->ctx, DTLS1_2_VERSION);\n        return AVS_OK;\n    default:\n        return avs_errno(AVS_ENOTSUP);\n    }\n}\n\nstatic avs_error_t configure_tls_version(tls_socket_impl_t *sock,\n                                         avs_net_ssl_version_t version) {\n    switch (version) {\n    case AVS_NET_SSL_VERSION_DEFAULT:\n    case AVS_NET_SSL_VERSION_SSLv2_OR_3:\n        return AVS_OK;\n    case AVS_NET_SSL_VERSION_SSLv3:\n        SSL_CTX_set_min_proto_version(sock->ctx, SSL3_VERSION);\n        return AVS_OK;\n    case AVS_NET_SSL_VERSION_TLSv1:\n        SSL_CTX_set_min_proto_version(sock->ctx, TLS1_VERSION);\n        return AVS_OK;\n    case AVS_NET_SSL_VERSION_TLSv1_1:\n        SSL_CTX_set_min_proto_version(sock->ctx, TLS1_1_VERSION);\n        return AVS_OK;\n    case AVS_NET_SSL_VERSION_TLSv1_2:\n        SSL_CTX_set_min_proto_version(sock->ctx, TLS1_2_VERSION);\n        return AVS_OK;\n    case AVS_NET_SSL_VERSION_TLSv1_3:\n        SSL_CTX_set_min_proto_version(sock->ctx, TLS1_3_VERSION);\n        return AVS_OK;\n    default:\n        return avs_errno(AVS_ENOTSUP);\n    }\n}\n\nstatic unsigned int psk_client_cb(SSL *ssl,\n                                  const char *hint,\n                                  char *identity,\n                                  unsigned int max_identity_len,\n                                  unsigned char *psk,\n                                  unsigned int max_psk_len) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) SSL_get_app_data(ssl);\n\n    (void) hint;\n\n    if (!sock || max_psk_len < sock->psk_size\n            || max_identity_len < sock->identity_size + 1) {\n        return 0;\n    }\n\n    memcpy(psk, sock->psk, sock->psk_size);\n    memcpy(identity, sock->identity, sock->identity_size);\n    identity[sock->identity_size] = '\\0';\n\n    return (unsigned int) sock->psk_size;\n}\n\nstatic avs_error_t configure_psk(tls_socket_impl_t *sock,\n                                 const avs_net_psk_info_t *psk) {\n    if (psk->key.desc.source != AVS_CRYPTO_DATA_SOURCE_BUFFER\n            || psk->identity.desc.source != AVS_CRYPTO_DATA_SOURCE_BUFFER) {\n        return avs_errno(AVS_EINVAL);\n    }\n\n    const void *key_ptr = psk->key.desc.info.buffer.buffer;\n    size_t key_size = psk->key.desc.info.buffer.buffer_size;\n\n    const void *identity_ptr = psk->identity.desc.info.buffer.buffer;\n    size_t identity_size = psk->identity.desc.info.buffer.buffer_size;\n\n    if (key_size > sizeof(sock->psk)\n            || identity_size > sizeof(sock->identity)) {\n        return avs_errno(AVS_EINVAL);\n    }\n    memcpy(sock->psk, key_ptr, key_size);\n    sock->psk_size = key_size;\n    memcpy(sock->identity, identity_ptr, identity_size);\n    sock->identity_size = identity_size;\n    SSL_CTX_set_cipher_list(sock->ctx, \"PSK\");\n    SSL_CTX_set_psk_client_callback(sock->ctx, psk_client_cb);\n    SSL_CTX_set_verify(sock->ctx, SSL_VERIFY_PEER, NULL);\n    return AVS_OK;\n}\n\nstatic avs_error_t\nconfigure_trusted_certs(X509_STORE *store,\n                        const avs_crypto_security_info_union_t *trusted_certs) {\n    if (!trusted_certs) {\n        return avs_errno(AVS_EINVAL);\n    }\n    switch (trusted_certs->source) {\n    case AVS_CRYPTO_DATA_SOURCE_EMPTY:\n        return AVS_OK;\n    case AVS_CRYPTO_DATA_SOURCE_BUFFER: {\n        const unsigned char *ptr =\n                (const unsigned char *) trusted_certs->info.buffer.buffer;\n        X509 *cert = d2i_X509(NULL, &ptr,\n                              (long) trusted_certs->info.buffer.buffer_size);\n        if (!cert) {\n            return avs_errno(AVS_EPROTO);\n        }\n\n        ERR_clear_error();\n        int result = X509_STORE_add_cert(store, cert);\n        X509_free(cert);\n        if (!result\n                && ERR_GET_REASON(ERR_get_error())\n                               != X509_R_CERT_ALREADY_IN_HASH_TABLE) {\n            return avs_errno(AVS_EPROTO);\n        }\n        return AVS_OK;\n    }\n    case AVS_CRYPTO_DATA_SOURCE_ARRAY: {\n        avs_error_t err = AVS_OK;\n        for (size_t i = 0;\n             avs_is_ok(err) && i < trusted_certs->info.array.element_count;\n             ++i) {\n            err = configure_trusted_certs(\n                    store, &trusted_certs->info.array.array_ptr[i]);\n        }\n        return err;\n    }\n    case AVS_CRYPTO_DATA_SOURCE_LIST: {\n        avs_error_t err = AVS_OK;\n        AVS_LIST(avs_crypto_security_info_union_t) entry;\n        AVS_LIST_FOREACH(entry, trusted_certs->info.list.list_head) {\n            if (avs_is_err((err = configure_trusted_certs(store, entry)))) {\n                break;\n            }\n        }\n        return AVS_OK;\n    }\n    default:\n        return avs_errno(AVS_ENOTSUP);\n    }\n}\n\nstatic avs_error_t configure_cert_revocation_lists(\n        X509_STORE *store,\n        const avs_crypto_security_info_union_t *cert_revocation_lists) {\n    if (!cert_revocation_lists) {\n        return avs_errno(AVS_EINVAL);\n    }\n    switch (cert_revocation_lists->source) {\n    case AVS_CRYPTO_DATA_SOURCE_EMPTY:\n        return AVS_OK;\n    case AVS_CRYPTO_DATA_SOURCE_BUFFER: {\n        const unsigned char *ptr =\n                (const unsigned char *)\n                        cert_revocation_lists->info.buffer.buffer;\n        X509_CRL *crl = d2i_X509_CRL(\n                NULL, &ptr,\n                (long) cert_revocation_lists->info.buffer.buffer_size);\n        if (!crl) {\n            return avs_errno(AVS_EPROTO);\n        }\n\n        ERR_clear_error();\n        int result = X509_STORE_add_crl(store, crl);\n        X509_CRL_free(crl);\n        if (result != 1) {\n            return avs_errno(AVS_EPROTO);\n        }\n        return AVS_OK;\n    }\n    case AVS_CRYPTO_DATA_SOURCE_ARRAY: {\n        avs_error_t err = AVS_OK;\n        for (size_t i = 0;\n             avs_is_ok(err)\n             && i < cert_revocation_lists->info.array.element_count;\n             ++i) {\n            err = configure_cert_revocation_lists(\n                    store, &cert_revocation_lists->info.array.array_ptr[i]);\n        }\n        return err;\n    }\n    case AVS_CRYPTO_DATA_SOURCE_LIST: {\n        avs_error_t err = AVS_OK;\n        AVS_LIST(avs_crypto_security_info_union_t) entry;\n        AVS_LIST_FOREACH(entry, cert_revocation_lists->info.list.list_head) {\n            if (avs_is_err((\n                        err = configure_cert_revocation_lists(store, entry)))) {\n                break;\n            }\n        }\n        return AVS_OK;\n    }\n    default:\n        return avs_errno(AVS_ENOTSUP);\n    }\n}\n\nstatic avs_error_t\nconfigure_client_certs(SSL_CTX *ctx,\n                       const avs_crypto_security_info_union_t *client_certs) {\n    if (!client_certs) {\n        return avs_errno(AVS_EINVAL);\n    }\n    switch (client_certs->source) {\n    case AVS_CRYPTO_DATA_SOURCE_EMPTY:\n        return AVS_OK;\n    case AVS_CRYPTO_DATA_SOURCE_BUFFER: {\n        const unsigned char *ptr =\n                (const unsigned char *) client_certs->info.buffer.buffer;\n        X509 *cert = d2i_X509(NULL, &ptr,\n                              (long) client_certs->info.buffer.buffer_size);\n        if (!cert) {\n            return avs_errno(AVS_EPROTO);\n        }\n\n        int result;\n        if (!SSL_CTX_get0_certificate(ctx)) {\n            result = SSL_CTX_use_certificate(ctx, cert);\n        } else {\n            result = SSL_CTX_add1_chain_cert(ctx, cert);\n        }\n        X509_free(cert);\n        if (result != 1) {\n            return avs_errno(AVS_EPROTO);\n        }\n        return AVS_OK;\n    }\n    case AVS_CRYPTO_DATA_SOURCE_ARRAY: {\n        avs_error_t err = AVS_OK;\n        for (size_t i = 0;\n             avs_is_ok(err) && i < client_certs->info.array.element_count;\n             ++i) {\n            err = configure_client_certs(\n                    ctx, &client_certs->info.array.array_ptr[i]);\n        }\n        return err;\n    }\n    case AVS_CRYPTO_DATA_SOURCE_LIST: {\n        avs_error_t err = AVS_OK;\n        AVS_LIST(avs_crypto_security_info_union_t) entry;\n        AVS_LIST_FOREACH(entry, client_certs->info.list.list_head) {\n            if (avs_is_err((err = configure_client_certs(ctx, entry)))) {\n                break;\n            }\n        }\n        return AVS_OK;\n    }\n    default:\n        return avs_errno(AVS_ENOTSUP);\n    }\n}\n\nstatic avs_error_t\nconfigure_client_key(SSL_CTX *ctx,\n                     const avs_crypto_private_key_info_t *client_key) {\n    switch (client_key->desc.source) {\n    case AVS_CRYPTO_DATA_SOURCE_BUFFER: {\n        if (client_key->desc.info.buffer.password) {\n            return avs_errno(AVS_ENOTSUP);\n        }\n        const unsigned char *ptr =\n                (const unsigned char *) client_key->desc.info.buffer.buffer;\n        EVP_PKEY *key = d2i_AutoPrivateKey(\n                NULL, &ptr, (long) client_key->desc.info.buffer.buffer_size);\n        if (!key) {\n            return avs_errno(AVS_EPROTO);\n        }\n\n        int result = SSL_CTX_use_PrivateKey(ctx, key);\n        EVP_PKEY_free(key);\n        if (result != 1) {\n            return avs_errno(AVS_EPROTO);\n        }\n        return AVS_OK;\n    }\n    default:\n        return avs_errno(AVS_ENOTSUP);\n    }\n}\n\nstatic avs_error_t configure_certs(tls_socket_impl_t *sock,\n                                   const avs_net_certificate_info_t *certs) {\n    if (certs->server_cert_validation) {\n        if (!certs->ignore_system_trust_store) {\n            SSL_CTX_set_default_verify_paths(sock->ctx);\n        }\n        X509_STORE *store = SSL_CTX_get_cert_store(sock->ctx);\n        avs_error_t err;\n        if (avs_is_err((err = configure_trusted_certs(\n                                store, &certs->trusted_certs.desc)))\n                || avs_is_err((err = configure_cert_revocation_lists(\n                                       store,\n                                       &certs->cert_revocation_lists.desc)))) {\n            return err;\n        }\n        SSL_CTX_set_verify(sock->ctx, SSL_VERIFY_PEER, NULL);\n    } else {\n        SSL_CTX_set_verify(sock->ctx, SSL_VERIFY_NONE, NULL);\n    }\n    sock->dane_enabled = certs->dane;\n    if (sock->dane_enabled) {\n        SSL_CTX_dane_enable(sock->ctx);\n    }\n    if (certs->client_cert.desc.source != AVS_CRYPTO_DATA_SOURCE_EMPTY) {\n        avs_error_t err;\n        if (avs_is_err((err = configure_client_certs(sock->ctx,\n                                                     &certs->client_cert.desc)))\n                || avs_is_err(err = configure_client_key(sock->ctx,\n                                                         &certs->client_key))) {\n            return err;\n        }\n    }\n\n    return AVS_OK;\n}\n\nstatic avs_error_t configure_dtls_handshake_timeouts(\n        tls_socket_impl_t *sock,\n        const avs_net_dtls_handshake_timeouts_t *dtls_handshake_timeouts) {\n    uint64_t min_us = 1000000, max_us = 60000000;\n    if (dtls_handshake_timeouts) {\n        avs_time_duration_to_scalar(&min_us, AVS_TIME_US,\n                                    dtls_handshake_timeouts->min);\n        avs_time_duration_to_scalar(&max_us, AVS_TIME_US,\n                                    dtls_handshake_timeouts->max);\n    }\n    sock->dtls_hs_timeout_min_us = (unsigned int) min_us;\n    sock->dtls_hs_timeout_max_us = (unsigned int) max_us;\n    return AVS_OK;\n}\n\nstatic avs_error_t\nconfigure_ciphersuites(tls_socket_impl_t *sock,\n                       const avs_net_socket_tls_ciphersuites_t *ciphersuites) {\n    if (!ciphersuites->num_ids) {\n        return AVS_OK;\n    }\n    SSL *dummy_ssl = SSL_new(sock->ctx);\n    if (!dummy_ssl) {\n        return avs_errno(AVS_ENOMEM);\n    }\n    char cipher_list[1024] = \"-ALL\";\n    char *cipher_list_ptr = cipher_list + strlen(cipher_list);\n    const char *const cipher_list_end = cipher_list + sizeof(cipher_list);\n    for (size_t i = 0; i < ciphersuites->num_ids; ++i) {\n        unsigned char id_as_chars[] = {\n            (unsigned char) (ciphersuites->ids[i] >> 8),\n            (unsigned char) (ciphersuites->ids[i] & 0xFF)\n        };\n        const SSL_CIPHER *cipher = SSL_CIPHER_find(dummy_ssl, id_as_chars);\n        if (cipher) {\n            const char *name = SSL_CIPHER_get_name(cipher);\n            if (!!strstr(name, \"PSK\") == !!sock->psk_size\n                    && cipher_list_ptr + 1 + strlen(name) < cipher_list_end) {\n                *cipher_list_ptr++ = ':';\n                strcpy(cipher_list_ptr, name);\n                cipher_list_ptr += strlen(name);\n            }\n        }\n    }\n    SSL_free(dummy_ssl);\n    SSL_CTX_set_cipher_list(sock->ctx, cipher_list);\n    // NOTE: Configuring the set of supported new-style ciphersuites as defined\n    // for TLS 1.3 are not supported by this function.\n    return AVS_OK;\n}\n\nstatic avs_error_t configure_sni(tls_socket_impl_t *sock,\n                                 const char *server_name_indication) {\n    if (server_name_indication) {\n        if (strlen(server_name_indication)\n                >= sizeof(sock->server_name_indication)) {\n            return avs_errno(AVS_ENOBUFS);\n        }\n        strcpy(sock->server_name_indication, server_name_indication);\n    }\n    return AVS_OK;\n}\n\nstatic int new_session_cb(SSL *ssl, SSL_SESSION *sess) {\n    tls_socket_impl_t *sock = (tls_socket_impl_t *) SSL_get_app_data(ssl);\n    int serialized_size = i2d_SSL_SESSION(sess, NULL);\n    if (serialized_size > 0\n            && (size_t) serialized_size\n                           <= sock->session_resumption_buffer_size) {\n        unsigned char *ptr = (unsigned char *) sock->session_resumption_buffer;\n        i2d_SSL_SESSION(sess, &ptr);\n    }\n    return 0;\n}\n\nstatic avs_error_t\ncreate_tls_socket(avs_net_socket_t **socket_ptr,\n                  avs_net_socket_type_t backend_type,\n                  const avs_net_ssl_configuration_t *configuration) {\n    assert(socket_ptr);\n    assert(!*socket_ptr);\n    assert(configuration);\n    tls_socket_impl_t *socket =\n            (tls_socket_impl_t *) avs_calloc(1, sizeof(tls_socket_impl_t));\n    if (!socket) {\n        return avs_errno(AVS_ENOMEM);\n    }\n    *socket_ptr = (avs_net_socket_t *) socket;\n    socket->operations = &TLS_SOCKET_VTABLE;\n    socket->backend_type = backend_type;\n\n    avs_error_t err = AVS_OK;\n    if (backend_type == AVS_NET_UDP_SOCKET) {\n        if (avs_is_ok((err = avs_net_udp_socket_create(\n                               &socket->backend_socket,\n                               &configuration->backend_configuration)))\n                && !(socket->ctx = SSL_CTX_new(DTLS_method()))) {\n            err = avs_errno(AVS_ENOMEM);\n        }\n        if (avs_is_ok(err)) {\n            err = configure_dtls_version(socket, configuration->version);\n        }\n    } else {\n        if (avs_is_ok((err = avs_net_tcp_socket_create(\n                               &socket->backend_socket,\n                               &configuration->backend_configuration)))\n                && !(socket->ctx = SSL_CTX_new(TLS_method()))) {\n            err = avs_errno(AVS_ENOMEM);\n        }\n        if (avs_is_ok(err)) {\n            err = configure_tls_version(socket, configuration->version);\n        }\n    }\n    if (avs_is_ok(err)) {\n        switch (configuration->security.mode) {\n        case AVS_NET_SECURITY_PSK:\n            err = configure_psk(socket, &configuration->security.data.psk);\n            break;\n        case AVS_NET_SECURITY_CERTIFICATE:\n            err = configure_certs(socket, &configuration->security.data.cert);\n            break;\n        default:\n            err = avs_errno(AVS_ENOTSUP);\n        }\n    }\n    if (avs_is_err(err)\n            || avs_is_err((\n                       err = configure_dtls_handshake_timeouts(\n                               socket, configuration->dtls_handshake_timeouts)))\n            || avs_is_err((err = configure_ciphersuites(\n                                   socket, &configuration->ciphersuites)))\n            || avs_is_err((err = configure_sni(\n                                   socket,\n                                   configuration->server_name_indication)))) {\n        avs_net_socket_cleanup(socket_ptr);\n        return err;\n    }\n    SSL_CTX_set_mode(socket->ctx, SSL_MODE_AUTO_RETRY);\n    if (configuration->session_resumption_buffer_size > 0) {\n        assert(configuration->session_resumption_buffer);\n        socket->session_resumption_buffer =\n                configuration->session_resumption_buffer;\n        socket->session_resumption_buffer_size =\n                configuration->session_resumption_buffer_size;\n        SSL_CTX_set_session_cache_mode(\n                socket->ctx,\n                SSL_SESS_CACHE_CLIENT | SSL_SESS_CACHE_NO_INTERNAL_STORE);\n        SSL_CTX_sess_set_new_cb(socket->ctx, new_session_cb);\n    }\n    return AVS_OK;\n}\n\navs_error_t _avs_net_create_dtls_socket(avs_net_socket_t **socket_ptr,\n                                        const void *configuration) {\n    return create_tls_socket(\n            socket_ptr, AVS_NET_UDP_SOCKET,\n            (const avs_net_ssl_configuration_t *) configuration);\n}\n\navs_error_t _avs_net_create_ssl_socket(avs_net_socket_t **socket_ptr,\n                                       const void *configuration) {\n    return create_tls_socket(\n            socket_ptr, AVS_NET_TCP_SOCKET,\n            (const avs_net_ssl_configuration_t *) configuration);\n}\n"
  },
  {
    "path": "examples/tutorial/AT-AccessControl/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.16)\nproject(access-control C)\n\nset(CMAKE_C_STANDARD 99)\nset(CMAKE_C_EXTENSIONS OFF)\n\nfind_package(anjay REQUIRED)\n\nadd_executable(${PROJECT_NAME}\n               src/main.c\n               src/test_object.c\n               src/test_object.h)\ntarget_link_libraries(${PROJECT_NAME} PRIVATE anjay)\n"
  },
  {
    "path": "examples/tutorial/AT-AccessControl/src/main.c",
    "content": "#include <anjay/access_control.h>\n#include <anjay/anjay.h>\n#include <anjay/security.h>\n#include <anjay/server.h>\n#include <avsystem/commons/avs_log.h>\n\n#include \"test_object.h\"\n\nint main(int argc, char *argv[]) {\n    if (argc != 2) {\n        avs_log(tutorial, ERROR, \"usage: %s ENDPOINT_NAME\", argv[0]);\n        return -1;\n    }\n\n    const anjay_configuration_t CONFIG = {\n        .endpoint_name = argv[1],\n        .in_buffer_size = 4000,\n        .out_buffer_size = 4000\n    };\n\n    anjay_t *anjay = anjay_new(&CONFIG);\n    if (!anjay) {\n        avs_log(tutorial, ERROR, \"Could not create Anjay object\");\n        return -1;\n    }\n\n    int result = 0;\n    if (anjay_access_control_install(anjay)\n            || anjay_security_object_install(anjay)\n            || anjay_server_object_install(anjay)) {\n        result = -1;\n    }\n\n    // Instantiate test object\n    const anjay_dm_object_def_t **test_obj = create_test_object();\n\n    // For some reason we were unable to instantiate or install\n    if (!test_obj || result) {\n        result = -1;\n        goto cleanup;\n    }\n\n    // Register test object within Anjay\n    if (anjay_register_object(anjay, test_obj)) {\n        result = -1;\n        goto cleanup;\n    }\n\n    // LwM2M Server account with SSID = 1\n    const anjay_security_instance_t security_instance1 = {\n        .ssid = 1,\n        .server_uri = \"coap://eu.iot.avsystem.cloud:5683\",\n        .security_mode = ANJAY_SECURITY_NOSEC\n    };\n\n    const anjay_server_instance_t server_instance1 = {\n        .ssid = 1,\n        .lifetime = 86400,\n        .default_min_period = -1,\n        .default_max_period = -1,\n        .disable_timeout = -1,\n        .binding = \"U\"\n    };\n\n    // LwM2M Server account with SSID = 2\n    const anjay_security_instance_t security_instance2 = {\n        .ssid = 2,\n        .server_uri = \"coap://127.0.0.1:5683\",\n        .security_mode = ANJAY_SECURITY_NOSEC\n    };\n\n    const anjay_server_instance_t server_instance2 = {\n        .ssid = 2,\n        .lifetime = 86400,\n        .default_min_period = -1,\n        .default_max_period = -1,\n        .disable_timeout = -1,\n        .binding = \"U\"\n    };\n\n    // Setup first LwM2M Server\n    anjay_iid_t server_instance_iid1 = ANJAY_ID_INVALID;\n    anjay_security_object_add_instance(anjay, &security_instance1,\n                                       &(anjay_iid_t) { ANJAY_ID_INVALID });\n    anjay_server_object_add_instance(anjay, &server_instance1,\n                                     &server_instance_iid1);\n\n    // Setup second LwM2M Server\n    anjay_iid_t server_instance_iid2 = ANJAY_ID_INVALID;\n    anjay_security_object_add_instance(anjay, &security_instance2,\n                                       &(anjay_iid_t) { ANJAY_ID_INVALID });\n    anjay_server_object_add_instance(anjay, &server_instance2,\n                                     &server_instance_iid2);\n\n    // Make SSID = 1 the owner of the Test object\n    anjay_access_control_set_owner(anjay, 1234, ANJAY_ID_INVALID,\n                                   server_instance1.ssid, NULL);\n\n    // Set LwM2M Create permission rights for it as well\n    anjay_access_control_set_acl(anjay, 1234, ANJAY_ID_INVALID,\n                                 server_instance1.ssid,\n                                 ANJAY_ACCESS_MASK_CREATE);\n\n    // Allow both LwM2M Servers to read their Server Instances\n    anjay_access_control_set_acl(anjay, 1, server_instance_iid1,\n                                 server_instance1.ssid, ANJAY_ACCESS_MASK_READ);\n    anjay_access_control_set_acl(anjay, 1, server_instance_iid2,\n                                 server_instance2.ssid, ANJAY_ACCESS_MASK_READ);\n\n    result = anjay_event_loop_run(anjay,\n                                  avs_time_duration_from_scalar(1, AVS_TIME_S));\n\ncleanup:\n    anjay_delete(anjay);\n    delete_test_object(test_obj);\n    return result;\n}\n"
  },
  {
    "path": "examples/tutorial/AT-AccessControl/src/test_object.c",
    "content": "#include \"test_object.h\"\n\n#include <assert.h>\n#include <stdlib.h>\n#include <string.h>\n\n#include <avsystem/commons/avs_list.h>\n\ntypedef struct test_instance {\n    anjay_iid_t iid;\n\n    bool has_label;\n    char label[32];\n\n    bool has_value;\n    int32_t value;\n} test_instance_t;\n\ntypedef struct test_object {\n    // handlers\n    const anjay_dm_object_def_t *obj_def;\n\n    // object state\n    AVS_LIST(test_instance_t) instances;\n\n    AVS_LIST(test_instance_t) backup_instances;\n} test_object_t;\n\nstatic test_object_t *get_test_object(const anjay_dm_object_def_t *const *obj) {\n    assert(obj);\n\n    // use the container_of pattern to retrieve test_object_t pointer\n    // AVS_CONTAINER_OF macro provided by avsystem/commons/defs.h\n    return AVS_CONTAINER_OF(obj, test_object_t, obj_def);\n}\n\nstatic AVS_LIST(test_instance_t) get_instance(test_object_t *repr,\n                                              anjay_iid_t iid) {\n    AVS_LIST(test_instance_t) it;\n    AVS_LIST_FOREACH(it, repr->instances) {\n        if (it->iid == iid) {\n            return it;\n        } else if (it->iid > iid) {\n            // Since list of instances is sorted by Instance ID,\n            // Instance with given iid does not exist on that list\n            break;\n        }\n    }\n    // Instance was not found.\n    return NULL;\n}\n\nstatic int test_list_instances(anjay_t *anjay,\n                               const anjay_dm_object_def_t *const *obj_ptr,\n                               anjay_dm_list_ctx_t *ctx) {\n    (void) anjay; // unused\n\n    // iterate over all instances and return their IDs\n    AVS_LIST(test_instance_t) it;\n    AVS_LIST_FOREACH(it, get_test_object(obj_ptr)->instances) {\n        anjay_dm_emit(ctx, it->iid);\n    }\n\n    return 0;\n}\n\nstatic int test_instance_create(anjay_t *anjay,\n                                const anjay_dm_object_def_t *const *obj_ptr,\n                                anjay_iid_t iid) {\n    (void) anjay; // unused\n\n    test_object_t *repr = get_test_object(obj_ptr);\n\n    AVS_LIST(test_instance_t) new_instance =\n            AVS_LIST_NEW_ELEMENT(test_instance_t);\n\n    if (!new_instance) {\n        // out of memory\n        return ANJAY_ERR_INTERNAL;\n    }\n\n    new_instance->iid = iid;\n\n    // find a place where instance should be inserted,\n    // insert it and claim a victory\n    AVS_LIST(test_instance_t) *insert_ptr;\n    AVS_LIST_FOREACH_PTR(insert_ptr, &repr->instances) {\n        if ((*insert_ptr)->iid > new_instance->iid) {\n            break;\n        }\n    }\n    AVS_LIST_INSERT(insert_ptr, new_instance);\n    return 0;\n}\n\nstatic int test_instance_remove(anjay_t *anjay,\n                                const anjay_dm_object_def_t *const *obj_ptr,\n                                anjay_iid_t iid) {\n    (void) anjay; // unused\n    test_object_t *repr = get_test_object(obj_ptr);\n\n    AVS_LIST(test_instance_t) *it;\n    AVS_LIST_FOREACH_PTR(it, &repr->instances) {\n        if ((*it)->iid == iid) {\n            AVS_LIST_DELETE(it);\n            return 0;\n        }\n    }\n    // should never happen as Anjay checks whether instance is present\n    // prior to issuing instance_remove\n    return ANJAY_ERR_INTERNAL;\n}\n\nstatic int test_instance_reset(anjay_t *anjay,\n                               const anjay_dm_object_def_t *const *obj_ptr,\n                               anjay_iid_t iid) {\n    (void) anjay; // unused\n\n    test_instance_t *instance = get_instance(get_test_object(obj_ptr), iid);\n    // mark all Resource values for Object Instance `iid` as unset\n    instance->has_label = false;\n    instance->has_value = false;\n    return 0;\n}\n\nstatic int test_list_resources(anjay_t *anjay,\n                               const anjay_dm_object_def_t *const *obj_ptr,\n                               anjay_iid_t iid,\n                               anjay_dm_resource_list_ctx_t *ctx) {\n    (void) anjay;   // unused\n    (void) obj_ptr; // unused\n    (void) iid;     // unused\n\n    anjay_dm_emit_res(ctx, 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT);\n    return 0;\n}\n\nstatic int test_resource_read(anjay_t *anjay,\n                              const anjay_dm_object_def_t *const *obj_ptr,\n                              anjay_iid_t iid,\n                              anjay_rid_t rid,\n                              anjay_riid_t riid,\n                              anjay_output_ctx_t *ctx) {\n    (void) anjay; // unused\n\n    const test_instance_t *current_instance =\n            (const test_instance_t *) get_instance(get_test_object(obj_ptr),\n                                                   iid);\n\n    assert(riid == ANJAY_ID_INVALID);\n\n    switch (rid) {\n    case 0:\n        return anjay_ret_string(ctx, current_instance->label);\n    case 1:\n        return anjay_ret_i32(ctx, current_instance->value);\n    default:\n        // control will never reach this part due to test_list_resources\n        return ANJAY_ERR_INTERNAL;\n    }\n}\n\nstatic int test_resource_write(anjay_t *anjay,\n                               const anjay_dm_object_def_t *const *obj_ptr,\n                               anjay_iid_t iid,\n                               anjay_rid_t rid,\n                               anjay_riid_t riid,\n                               anjay_input_ctx_t *ctx) {\n    (void) anjay; // unused\n\n    test_instance_t *current_instance =\n            (test_instance_t *) get_instance(get_test_object(obj_ptr), iid);\n\n    assert(riid == ANJAY_ID_INVALID);\n\n    switch (rid) {\n    case 0: {\n        // `anjay_get_string` may return a chunk of data instead of the\n        // whole value - we need to make sure the client is able to hold\n        // the entire value\n        char buffer[sizeof(current_instance->label)];\n        int result = anjay_get_string(ctx, buffer, sizeof(buffer));\n\n        if (result == 0) {\n            // value OK - save it\n            memcpy(current_instance->label, buffer, sizeof(buffer));\n            current_instance->has_label = true;\n        } else if (result == ANJAY_BUFFER_TOO_SHORT) {\n            // the value is too long to store in the buffer\n            result = ANJAY_ERR_BAD_REQUEST;\n        }\n\n        return result;\n    }\n\n    case 1: {\n        // reading primitive values can be done directly - the value will only\n        // be written to the output variable if everything went fine\n        int result = anjay_get_i32(ctx, &current_instance->value);\n        if (result == 0) {\n            current_instance->has_value = true;\n        }\n        return result;\n    }\n\n    default:\n        // control will never reach this part due to test_list_resources\n        return ANJAY_ERR_INTERNAL;\n    }\n}\n\nstatic int test_transaction_begin(anjay_t *anjay,\n                                  const anjay_dm_object_def_t *const *obj_ptr) {\n    (void) anjay; // unused\n\n    test_object_t *test = get_test_object(obj_ptr);\n\n    // store a snapshot of object state\n    test->backup_instances = AVS_LIST_SIMPLE_CLONE(test->instances);\n\n    if (test->instances && !test->backup_instances) {\n        return ANJAY_ERR_INTERNAL;\n    }\n    return 0;\n}\n\nstatic int\ntest_transaction_validate(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr) {\n    (void) anjay; // unused\n\n    test_object_t *test = get_test_object(obj_ptr);\n\n    AVS_LIST(test_instance_t) it;\n\n    // ensure all Object Instances contain all Mandatory Resources\n    AVS_LIST_FOREACH(it, test->instances) {\n        if (!it->has_label || !it->has_value) {\n            // validation failed: Object state invalid, rollback required\n            return ANJAY_ERR_BAD_REQUEST;\n        }\n    }\n\n    // validation successful, can commit\n    return 0;\n}\n\nstatic int\ntest_transaction_commit(anjay_t *anjay,\n                        const anjay_dm_object_def_t *const *obj_ptr) {\n    (void) anjay;   // unused\n    (void) obj_ptr; // unused\n    test_object_t *test = get_test_object(obj_ptr);\n\n    // we free backup instances now, as current instance set is valid\n    AVS_LIST_CLEAR(&test->backup_instances);\n    return 0;\n}\n\nstatic int\ntest_transaction_rollback(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr) {\n    (void) anjay; // unused\n\n    test_object_t *test = get_test_object(obj_ptr);\n\n    // restore saved object state\n    AVS_LIST_CLEAR(&test->instances);\n    test->instances = test->backup_instances;\n    test->backup_instances = NULL;\n    return 0;\n}\n\nstatic const anjay_dm_object_def_t OBJECT_DEF = {\n    // Object ID\n    .oid = 1234,\n\n    .handlers = {\n        .list_instances = test_list_instances,\n        .instance_create = test_instance_create,\n        .instance_remove = test_instance_remove,\n        .instance_reset = test_instance_reset,\n\n        .list_resources = test_list_resources,\n        .resource_read = test_resource_read,\n        .resource_write = test_resource_write,\n\n        .transaction_begin = test_transaction_begin,\n        .transaction_validate = test_transaction_validate,\n        .transaction_commit = test_transaction_commit,\n        .transaction_rollback = test_transaction_rollback\n    }\n};\n\nconst anjay_dm_object_def_t **create_test_object(void) {\n    test_object_t *repr =\n            (test_object_t *) avs_calloc(1, sizeof(test_object_t));\n    if (repr) {\n        repr->obj_def = &OBJECT_DEF;\n        return &repr->obj_def;\n    }\n    return NULL;\n}\n\nvoid delete_test_object(const anjay_dm_object_def_t **obj) {\n    if (!obj) {\n        return;\n    }\n    test_object_t *repr = get_test_object(obj);\n    AVS_LIST_CLEAR(&repr->instances);\n    AVS_LIST_CLEAR(&repr->backup_instances);\n    avs_free(repr);\n}\n"
  },
  {
    "path": "examples/tutorial/AT-AccessControl/src/test_object.h",
    "content": "#ifndef TEST_OBJECT_H\n#define TEST_OBJECT_H\n\n#include <anjay/anjay.h>\n\nconst anjay_dm_object_def_t **create_test_object(void);\nvoid delete_test_object(const anjay_dm_object_def_t **obj);\n\n#endif /* TEST_OBJECT_H */\n"
  },
  {
    "path": "examples/tutorial/AT-Bootstrap/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.16)\nproject(anjay-at-bootstrap C)\n\nset(CMAKE_C_STANDARD 99)\nset(CMAKE_C_EXTENSIONS OFF)\n\nadd_compile_options(-Wall -Wextra)\n\nfind_package(anjay REQUIRED)\n\nadd_executable(${PROJECT_NAME} src/main.c)\ntarget_link_libraries(${PROJECT_NAME} PRIVATE anjay)\n"
  },
  {
    "path": "examples/tutorial/AT-Bootstrap/src/main.c",
    "content": "#include <anjay/anjay.h>\n#include <anjay/security.h>\n#include <anjay/server.h>\n#include <avsystem/commons/avs_log.h>\n\n// Installs Security Object and adds an instance of it.\n// An instance of Security Object provides information needed to connect to\n// LwM2M Bootstrap server.\nstatic int setup_security_object(anjay_t *anjay) {\n    if (anjay_security_object_install(anjay)) {\n        return -1;\n    }\n\n    const anjay_security_instance_t security_instance = {\n        .ssid = 1,\n        .bootstrap_server = true,\n        .server_uri = \"coap://eu.iot.avsystem.cloud:5693\",\n        .security_mode = ANJAY_SECURITY_NOSEC,\n    };\n\n    // Anjay will assign Instance ID automatically\n    anjay_iid_t security_instance_id = ANJAY_ID_INVALID;\n    if (anjay_security_object_add_instance(anjay, &security_instance,\n                                           &security_instance_id)) {\n        return -1;\n    }\n\n    return 0;\n}\n\n// Installs Server Object but does not add any instances of it. This is\n// necessary to allow LwM2M Bootstrap Server to create Server Object instances.\nstatic int setup_server_object(anjay_t *anjay) {\n    if (anjay_server_object_install(anjay)) {\n        return -1;\n    }\n\n    return 0;\n}\n\nint main(int argc, char *argv[]) {\n    if (argc != 2) {\n        avs_log(tutorial, ERROR, \"usage: %s ENDPOINT_NAME\", argv[0]);\n        return -1;\n    }\n\n    const anjay_configuration_t CONFIG = {\n        .endpoint_name = argv[1],\n        .in_buffer_size = 4000,\n        .out_buffer_size = 4000,\n        .msg_cache_size = 4000\n    };\n\n    anjay_t *anjay = anjay_new(&CONFIG);\n    if (!anjay) {\n        avs_log(tutorial, ERROR, \"Could not create Anjay object\");\n        return -1;\n    }\n\n    int result = 0;\n    // Setup necessary objects\n    if (setup_security_object(anjay) || setup_server_object(anjay)) {\n        result = -1;\n    }\n\n    if (!result) {\n        result = anjay_event_loop_run(\n                anjay, avs_time_duration_from_scalar(1, AVS_TIME_S));\n    }\n\n    anjay_delete(anjay);\n    return result;\n}\n"
  },
  {
    "path": "examples/tutorial/AT-Certificates/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.16)\nproject(anjay-cert C)\n\nset(CMAKE_C_STANDARD 99)\nset(CMAKE_C_EXTENSIONS OFF)\n\nfind_package(anjay REQUIRED)\n\nadd_executable(${PROJECT_NAME} src/main.c)\ntarget_link_libraries(${PROJECT_NAME} PRIVATE anjay)\n"
  },
  {
    "path": "examples/tutorial/AT-Certificates/src/main.c",
    "content": "#include <anjay/anjay.h>\n#include <anjay/security.h>\n#include <anjay/server.h>\n#include <avsystem/commons/avs_log.h>\n\n#include <string.h>\n\nstatic int\nload_buffer_from_file(uint8_t **out, size_t *out_size, const char *filename) {\n    FILE *f = fopen(filename, \"rb\");\n    if (!f) {\n        avs_log(tutorial, ERROR, \"could not open %s\", filename);\n        return -1;\n    }\n    int result = -1;\n    if (fseek(f, 0, SEEK_END)) {\n        goto finish;\n    }\n    long size = ftell(f);\n    if (size < 0 || (unsigned long) size > SIZE_MAX || fseek(f, 0, SEEK_SET)) {\n        goto finish;\n    }\n    *out_size = (size_t) size;\n    if (!(*out = (uint8_t *) avs_malloc(*out_size))) {\n        goto finish;\n    }\n    if (fread(*out, *out_size, 1, f) != 1) {\n        avs_free(*out);\n        *out = NULL;\n        goto finish;\n    }\n    result = 0;\nfinish:\n    fclose(f);\n    if (result) {\n        avs_log(tutorial, ERROR, \"could not read %s\", filename);\n    }\n    return result;\n}\n\n// Installs Security Object and adds an instance of it.\n// An instance of Security Object provides information needed to connect to\n// LwM2M server.\nstatic int setup_security_object(anjay_t *anjay) {\n    if (anjay_security_object_install(anjay)) {\n        return -1;\n    }\n\n    anjay_security_instance_t security_instance = {\n        .ssid = 1,\n        .server_uri = \"coaps://eu.iot.avsystem.cloud:5684\",\n        .security_mode = ANJAY_SECURITY_CERTIFICATE\n    };\n\n    int result = 0;\n\n    if (load_buffer_from_file(\n                (uint8_t **) &security_instance.public_cert_or_psk_identity,\n                &security_instance.public_cert_or_psk_identity_size,\n                \"client_cert.der\")\n            || load_buffer_from_file(\n                       (uint8_t **) &security_instance.private_cert_or_psk_key,\n                       &security_instance.private_cert_or_psk_key_size,\n                       \"client_key.der\")\n            || load_buffer_from_file(\n                       (uint8_t **) &security_instance.server_public_key,\n                       &security_instance.server_public_key_size,\n                       \"server_cert.der\")) {\n        result = -1;\n        goto cleanup;\n    }\n\n    // Anjay will assign Instance ID automatically\n    anjay_iid_t security_instance_id = ANJAY_ID_INVALID;\n    if (anjay_security_object_add_instance(anjay, &security_instance,\n                                           &security_instance_id)) {\n        result = -1;\n    }\n\ncleanup:\n    avs_free((uint8_t *) security_instance.public_cert_or_psk_identity);\n    avs_free((uint8_t *) security_instance.private_cert_or_psk_key);\n    avs_free((uint8_t *) security_instance.server_public_key);\n\n    return result;\n}\n\n// Installs Server Object and adds an instance of it.\n// An instance of Server Object provides the data related to a LwM2M Server.\nstatic int setup_server_object(anjay_t *anjay) {\n    if (anjay_server_object_install(anjay)) {\n        return -1;\n    }\n\n    const anjay_server_instance_t server_instance = {\n        // Server Short ID\n        .ssid = 1,\n        // Client will send Update message often than every 60 seconds\n        .lifetime = 60,\n        // Disable Default Minimum Period resource\n        .default_min_period = -1,\n        // Disable Default Maximum Period resource\n        .default_max_period = -1,\n        // Disable Disable Timeout resource\n        .disable_timeout = -1,\n        // Sets preferred transport to UDP\n        .binding = \"U\"\n    };\n\n    // Anjay will assign Instance ID automatically\n    anjay_iid_t server_instance_id = ANJAY_ID_INVALID;\n    if (anjay_server_object_add_instance(anjay, &server_instance,\n                                         &server_instance_id)) {\n        return -1;\n    }\n\n    return 0;\n}\n\nint main(int argc, char *argv[]) {\n    if (argc != 2) {\n        avs_log(tutorial, ERROR, \"usage: %s ENDPOINT_NAME\", argv[0]);\n        return -1;\n    }\n\n    const anjay_configuration_t CONFIG = {\n        .endpoint_name = argv[1],\n        .in_buffer_size = 4000,\n        .out_buffer_size = 4000,\n        .msg_cache_size = 4000\n    };\n\n    anjay_t *anjay = anjay_new(&CONFIG);\n    if (!anjay) {\n        avs_log(tutorial, ERROR, \"Could not create Anjay object\");\n        return -1;\n    }\n\n    int result = 0;\n    // Setup necessary objects\n    if (setup_security_object(anjay) || setup_server_object(anjay)) {\n        result = -1;\n    }\n\n    if (!result) {\n        result = anjay_event_loop_run(\n                anjay, avs_time_duration_from_scalar(1, AVS_TIME_S));\n    }\n\n    anjay_delete(anjay);\n    return result;\n}\n"
  },
  {
    "path": "examples/tutorial/AT-CustomEventLoop/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.16)\nproject(custom-event-loop C)\n\nset(CMAKE_C_STANDARD 99)\nset(CMAKE_C_EXTENSIONS OFF)\n\nadd_compile_options(-Wall -Wextra)\n\nfind_package(anjay REQUIRED)\n\nadd_executable(${PROJECT_NAME} src/main.c)\ntarget_link_libraries(${PROJECT_NAME} PRIVATE anjay)\n"
  },
  {
    "path": "examples/tutorial/AT-CustomEventLoop/src/main.c",
    "content": "#include <anjay/anjay.h>\n#include <anjay/security.h>\n#include <anjay/server.h>\n#include <avsystem/commons/avs_log.h>\n\n#include <poll.h>\n\nint main_loop(anjay_t *anjay) {\n    while (true) {\n        // Obtain all network data sources\n        AVS_LIST(avs_net_socket_t *const) sockets = anjay_get_sockets(anjay);\n\n        // Prepare to poll() on them\n        size_t numsocks = AVS_LIST_SIZE(sockets);\n        struct pollfd pollfds[numsocks];\n        size_t i = 0;\n        AVS_LIST(avs_net_socket_t *const) sock;\n        AVS_LIST_FOREACH(sock, sockets) {\n            pollfds[i].fd = *(const int *) avs_net_socket_get_system(*sock);\n            pollfds[i].events = POLLIN;\n            pollfds[i].revents = 0;\n            ++i;\n        }\n\n        const int max_wait_time_ms = 1000;\n        // Determine the expected time to the next job in milliseconds.\n        // If there is no job we will wait till something arrives for\n        // at most 1 second (i.e. max_wait_time_ms).\n        int wait_ms =\n                anjay_sched_calculate_wait_time_ms(anjay, max_wait_time_ms);\n\n        // Wait for the events if necessary, and handle them.\n        if (poll(pollfds, numsocks, wait_ms) > 0) {\n            int socket_id = 0;\n            AVS_LIST(avs_net_socket_t *const) socket = NULL;\n            AVS_LIST_FOREACH(socket, sockets) {\n                if (pollfds[socket_id].revents) {\n                    if (anjay_serve(anjay, *socket)) {\n                        avs_log(tutorial, ERROR, \"anjay_serve failed\");\n                    }\n                }\n                ++socket_id;\n            }\n        }\n\n        // Finally run the scheduler\n        anjay_sched_run(anjay);\n    }\n    return 0;\n}\n\n// Installs Security Object and adds an instance of it.\n// An instance of Security Object provides information needed to connect to\n// LwM2M server.\nstatic int setup_security_object(anjay_t *anjay) {\n    if (anjay_security_object_install(anjay)) {\n        return -1;\n    }\n\n    static const char PSK_IDENTITY[] = \"identity\";\n    static const char PSK_KEY[] = \"P4s$w0rd\";\n\n    anjay_security_instance_t security_instance = {\n        .ssid = 1,\n        .server_uri = \"coaps://eu.iot.avsystem.cloud:5684\",\n        .security_mode = ANJAY_SECURITY_PSK,\n        .public_cert_or_psk_identity = (const uint8_t *) PSK_IDENTITY,\n        .public_cert_or_psk_identity_size = strlen(PSK_IDENTITY),\n        .private_cert_or_psk_key = (const uint8_t *) PSK_KEY,\n        .private_cert_or_psk_key_size = strlen(PSK_KEY)\n    };\n\n    // Anjay will assign Instance ID automatically\n    anjay_iid_t security_instance_id = ANJAY_ID_INVALID;\n    if (anjay_security_object_add_instance(anjay, &security_instance,\n                                           &security_instance_id)) {\n        return -1;\n    }\n\n    return 0;\n}\n\n// Installs Server Object and adds an instance of it.\n// An instance of Server Object provides the data related to a LwM2M Server.\nstatic int setup_server_object(anjay_t *anjay) {\n    if (anjay_server_object_install(anjay)) {\n        return -1;\n    }\n\n    const anjay_server_instance_t server_instance = {\n        // Server Short ID\n        .ssid = 1,\n        // Client will send Update message often than every 60 seconds\n        .lifetime = 60,\n        // Disable Default Minimum Period resource\n        .default_min_period = -1,\n        // Disable Default Maximum Period resource\n        .default_max_period = -1,\n        // Disable Disable Timeout resource\n        .disable_timeout = -1,\n        // Sets preferred transport to UDP\n        .binding = \"U\"\n    };\n\n    // Anjay will assign Instance ID automatically\n    anjay_iid_t server_instance_id = ANJAY_ID_INVALID;\n    if (anjay_server_object_add_instance(anjay, &server_instance,\n                                         &server_instance_id)) {\n        return -1;\n    }\n\n    return 0;\n}\n\nint main(int argc, char *argv[]) {\n    if (argc != 2) {\n        avs_log(tutorial, ERROR, \"usage: %s ENDPOINT_NAME\", argv[0]);\n        return -1;\n    }\n\n    const anjay_configuration_t CONFIG = {\n        .endpoint_name = argv[1],\n        .in_buffer_size = 4000,\n        .out_buffer_size = 4000,\n        .msg_cache_size = 4000\n    };\n\n    anjay_t *anjay = anjay_new(&CONFIG);\n    if (!anjay) {\n        avs_log(tutorial, ERROR, \"Could not create Anjay object\");\n        return -1;\n    }\n\n    int result = 0;\n    // Setup necessary objects\n    if (setup_security_object(anjay) || setup_server_object(anjay)) {\n        result = -1;\n    }\n\n    if (!result) {\n        result = main_loop(anjay);\n    }\n\n    anjay_delete(anjay);\n    return result;\n}\n"
  },
  {
    "path": "examples/tutorial/AT-CustomObjects/CMakeLists.txt",
    "content": "add_subdirectory(bootstrap-awareness)\nadd_subdirectory(multi-instance-dynamic)\nadd_subdirectory(multi-instance-resources-dynamic)\nadd_subdirectory(read-only)\nadd_subdirectory(read-only-with-executable)\nadd_subdirectory(read-only-multiple-fixed)\nadd_subdirectory(writable-multiple-fixed)\nadd_subdirectory(writable-multiple-fixed-transactional)\n"
  },
  {
    "path": "examples/tutorial/AT-CustomObjects/bootstrap-awareness/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.16)\nproject(bootstrap_awareness C)\n\nset(CMAKE_C_STANDARD 99)\nset(CMAKE_C_EXTENSIONS OFF)\n\nfind_package(anjay REQUIRED)\n\nadd_executable(${PROJECT_NAME} src/main.c)\ntarget_link_libraries(${PROJECT_NAME} PRIVATE anjay)\n"
  },
  {
    "path": "examples/tutorial/AT-CustomObjects/bootstrap-awareness/src/main.c",
    "content": "#include <assert.h>\n#include <string.h>\n\n#include <anjay/anjay.h>\n#include <anjay/security.h>\n#include <anjay/server.h>\n\n#include <avsystem/commons/avs_log.h>\n\ntypedef struct test_instance {\n    char label[32];\n    int32_t value;\n} test_instance_t;\n\nstatic const test_instance_t DEFAULT_INSTANCE_VALUES[] = { { \"First\", 1 },\n                                                           { \"Second\", 2 } };\n#define NUM_INSTANCES \\\n    (sizeof(DEFAULT_INSTANCE_VALUES) / sizeof(DEFAULT_INSTANCE_VALUES[0]))\n\ntypedef struct test_object {\n    // handlers\n    const anjay_dm_object_def_t *const obj_def;\n\n    // object state\n    test_instance_t instances[NUM_INSTANCES];\n} test_object_t;\n\nstatic test_object_t *get_test_object(const anjay_dm_object_def_t *const *obj) {\n    assert(obj);\n\n    // use the container_of pattern to retrieve test_object_t pointer\n    // AVS_CONTAINER_OF macro provided by avsystem/commons/defs.h\n    return AVS_CONTAINER_OF(obj, test_object_t, obj_def);\n}\n\nstatic int test_list_instances(anjay_t *anjay,\n                               const anjay_dm_object_def_t *const *obj_ptr,\n                               anjay_dm_list_ctx_t *ctx) {\n    (void) anjay;   // unused\n    (void) obj_ptr; // unused\n\n    for (anjay_iid_t iid = 0; iid < NUM_INSTANCES; ++iid) {\n        anjay_dm_emit(ctx, iid);\n    }\n\n    return 0;\n}\n\nstatic int test_list_resources(anjay_t *anjay,\n                               const anjay_dm_object_def_t *const *obj_ptr,\n                               anjay_iid_t iid,\n                               anjay_dm_resource_list_ctx_t *ctx) {\n    (void) anjay;   // unused\n    (void) obj_ptr; // unused\n    (void) iid;     // unused\n\n    // only allow reading Resource 0 by LwM2M Servers\n    // this will be ignored for LwM2M Bootstrap Server\n    anjay_dm_emit_res(ctx, 0, ANJAY_DM_RES_R, ANJAY_DM_RES_PRESENT);\n    // Value Resource can be read/written by LwM2M Servers\n    anjay_dm_emit_res(ctx, 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT);\n    return 0;\n}\n\nstatic int test_resource_read(anjay_t *anjay,\n                              const anjay_dm_object_def_t *const *obj_ptr,\n                              anjay_iid_t iid,\n                              anjay_rid_t rid,\n                              anjay_riid_t riid,\n                              anjay_output_ctx_t *ctx) {\n    (void) anjay; // unused\n\n    test_object_t *test = get_test_object(obj_ptr);\n\n    // IID validity was checked by the `anjay_dm_list_instances_t` handler.\n    // If the Object Instance set does not change, or can only be modified\n    // via LwM2M Create/Delete requests, it is safe to assume IID is correct.\n    assert((size_t) iid < NUM_INSTANCES);\n\n    // We have no Multiple-Instance Resources, so it is safe to assume\n    // that RIID is never set.\n    assert(riid == ANJAY_ID_INVALID);\n\n    const struct test_instance *current_instance = &test->instances[iid];\n\n    switch (rid) {\n    case 0:\n        return anjay_ret_string(ctx, current_instance->label);\n    case 1:\n        return anjay_ret_i32(ctx, current_instance->value);\n    default:\n        // control will never reach this part due to test_list_resources\n        return ANJAY_ERR_INTERNAL;\n    }\n}\n\nstatic int test_resource_write(anjay_t *anjay,\n                               const anjay_dm_object_def_t *const *obj_ptr,\n                               anjay_iid_t iid,\n                               anjay_rid_t rid,\n                               anjay_riid_t riid,\n                               anjay_input_ctx_t *ctx) {\n    (void) anjay; // unused\n\n    test_object_t *test = get_test_object(obj_ptr);\n\n    // IID validity was checked by the `anjay_dm_list_instances_t` handler.\n    // If the Object Instance set does not change, or can only be modified\n    // via LwM2M Create/Delete requests, it is safe to assume IID is correct.\n    assert((size_t) iid < NUM_INSTANCES);\n    struct test_instance *current_instance = &test->instances[iid];\n\n    // We have no Multiple-Instance Resources, so it is safe to assume\n    // that RIID is never set.\n    assert(riid == ANJAY_ID_INVALID);\n\n    switch (rid) {\n    case 0: {\n        // `anjay_get_string` may return a chunk of data instead of the\n        // whole value - we need to make sure the client is able to hold\n        // the entire value\n        char buffer[sizeof(current_instance->label)];\n        int result = anjay_get_string(ctx, buffer, sizeof(buffer));\n\n        if (result == 0) {\n            // value OK - save it\n            memcpy(current_instance->label, buffer, sizeof(buffer));\n        } else if (result == ANJAY_BUFFER_TOO_SHORT) {\n            // the value is too long to store in the buffer\n            result = ANJAY_ERR_BAD_REQUEST;\n        }\n\n        return result;\n    }\n\n    case 1:\n        // reading primitive values can be done directly - the value will only\n        // be written to the output variable if everything went fine\n        return anjay_get_i32(ctx, &current_instance->value);\n\n    default:\n        // control will never reach this part due to test_list_resources\n        return ANJAY_ERR_INTERNAL;\n    }\n}\n\nstatic const anjay_dm_object_def_t OBJECT_DEF = {\n    // Object ID\n    .oid = 1234,\n\n    .handlers = {\n        .list_instances = test_list_instances,\n\n        .list_resources = test_list_resources,\n        .resource_read = test_resource_read,\n        .resource_write = test_resource_write,\n\n        .transaction_begin = anjay_dm_transaction_NOOP,\n        .transaction_validate = anjay_dm_transaction_NOOP,\n        .transaction_commit = anjay_dm_transaction_NOOP,\n        .transaction_rollback = anjay_dm_transaction_NOOP\n    }\n};\n\nstatic int setup_security_object(anjay_t *anjay) {\n    const anjay_security_instance_t security_instance = {\n        .ssid = 1,\n        .bootstrap_server = true,\n        .server_uri = \"coap://eu.iot.avsystem.cloud:5693\",\n        .security_mode = ANJAY_SECURITY_NOSEC\n    };\n\n    if (anjay_security_object_install(anjay)) {\n        return -1;\n    }\n\n    // let Anjay assign an Object Instance ID\n    anjay_iid_t security_instance_id = ANJAY_ID_INVALID;\n    if (anjay_security_object_add_instance(anjay, &security_instance,\n                                           &security_instance_id)) {\n        return -1;\n    }\n\n    return 0;\n}\n\nstatic int setup_server_object(anjay_t *anjay) {\n    // Bootstrap Server does not need a Server Object instance\n\n    return anjay_server_object_install(anjay);\n}\n\nint main(int argc, char *argv[]) {\n    if (argc != 2) {\n        avs_log(tutorial, ERROR, \"usage: %s ENDPOINT_NAME\", argv[0]);\n        return -1;\n    }\n\n    const anjay_configuration_t CONFIG = {\n        .endpoint_name = argv[1],\n        .in_buffer_size = 4000,\n        .out_buffer_size = 4000\n    };\n\n    anjay_t *anjay = anjay_new(&CONFIG);\n    if (!anjay) {\n        return -1;\n    }\n\n    int result = 0;\n\n    if (setup_security_object(anjay) || setup_server_object(anjay)) {\n        result = -1;\n        goto cleanup;\n    }\n\n    // initialize and register the test object\n    test_object_t test_object = {\n        .obj_def = &OBJECT_DEF\n    };\n    for (size_t i = 0; i < NUM_INSTANCES; ++i) {\n        test_object.instances[i] = DEFAULT_INSTANCE_VALUES[i];\n    }\n\n    anjay_register_object(anjay, &test_object.obj_def);\n\n    result = anjay_event_loop_run(anjay,\n                                  avs_time_duration_from_scalar(1, AVS_TIME_S));\n\ncleanup:\n    anjay_delete(anjay);\n\n    // test object does not need cleanup\n    return result;\n}\n"
  },
  {
    "path": "examples/tutorial/AT-CustomObjects/multi-instance-dynamic/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.16)\nproject(multi-instance-dynamic C)\n\nset(CMAKE_C_STANDARD 99)\nset(CMAKE_C_EXTENSIONS OFF)\n\nfind_package(anjay REQUIRED)\n\nadd_executable(${PROJECT_NAME}\n               src/main.c\n               src/test_object.c\n               src/test_object.h)\ntarget_link_libraries(${PROJECT_NAME} PRIVATE anjay)\n"
  },
  {
    "path": "examples/tutorial/AT-CustomObjects/multi-instance-dynamic/src/main.c",
    "content": "#include <anjay/anjay.h>\n#include <anjay/security.h>\n#include <anjay/server.h>\n\n#include <avsystem/commons/avs_log.h>\n\n#include \"test_object.h\"\n\nstatic int setup_security_object(anjay_t *anjay) {\n    const anjay_security_instance_t security_instance = {\n        .ssid = 1,\n        .server_uri = \"coap://eu.iot.avsystem.cloud:5683\",\n        .security_mode = ANJAY_SECURITY_NOSEC\n    };\n\n    if (anjay_security_object_install(anjay)) {\n        return -1;\n    }\n\n    // let Anjay assign an Object Instance ID\n    anjay_iid_t security_instance_id = ANJAY_ID_INVALID;\n    if (anjay_security_object_add_instance(anjay, &security_instance,\n                                           &security_instance_id)) {\n        return -1;\n    }\n\n    return 0;\n}\n\nstatic int setup_server_object(anjay_t *anjay) {\n    const anjay_server_instance_t server_instance = {\n        .ssid = 1,\n        .lifetime = 86400,\n        .default_min_period = -1,\n        .default_max_period = -1,\n        .disable_timeout = -1,\n        .binding = \"U\"\n    };\n\n    if (anjay_server_object_install(anjay)) {\n        return -1;\n    }\n\n    anjay_iid_t server_instance_id = ANJAY_ID_INVALID;\n    if (anjay_server_object_add_instance(anjay, &server_instance,\n                                         &server_instance_id)) {\n        return -1;\n    }\n\n    return 0;\n}\n\nint main(int argc, char *argv[]) {\n    if (argc != 2) {\n        avs_log(tutorial, ERROR, \"usage: %s ENDPOINT_NAME\", argv[0]);\n        return -1;\n    }\n\n    const anjay_configuration_t CONFIG = {\n        .endpoint_name = argv[1],\n        .in_buffer_size = 4000,\n        .out_buffer_size = 4000\n    };\n\n    anjay_t *anjay = anjay_new(&CONFIG);\n    if (!anjay) {\n        return -1;\n    }\n\n    const anjay_dm_object_def_t **test_object = create_test_object();\n\n    int result = 0;\n\n    if (setup_security_object(anjay) || setup_server_object(anjay)\n            || anjay_register_object(anjay, test_object)) {\n        result = -1;\n        goto cleanup;\n    }\n\n    result = anjay_event_loop_run(anjay,\n                                  avs_time_duration_from_scalar(1, AVS_TIME_S));\n\ncleanup:\n    anjay_delete(anjay);\n    delete_test_object(test_object);\n\n    return result;\n}\n"
  },
  {
    "path": "examples/tutorial/AT-CustomObjects/multi-instance-dynamic/src/test_object.c",
    "content": "#include \"test_object.h\"\n\n#include <assert.h>\n#include <stdlib.h>\n#include <string.h>\n\n#include <avsystem/commons/avs_list.h>\n\ntypedef struct test_instance {\n    anjay_iid_t iid;\n\n    bool has_label;\n    char label[32];\n\n    bool has_value;\n    int32_t value;\n} test_instance_t;\n\ntypedef struct test_object {\n    // handlers\n    const anjay_dm_object_def_t *obj_def;\n\n    // object state\n    AVS_LIST(test_instance_t) instances;\n\n    AVS_LIST(test_instance_t) backup_instances;\n} test_object_t;\n\nstatic test_object_t *get_test_object(const anjay_dm_object_def_t *const *obj) {\n    assert(obj);\n\n    // use the container_of pattern to retrieve test_object_t pointer\n    // AVS_CONTAINER_OF macro provided by avsystem/commons/defs.h\n    return AVS_CONTAINER_OF(obj, test_object_t, obj_def);\n}\n\nstatic AVS_LIST(test_instance_t) get_instance(test_object_t *repr,\n                                              anjay_iid_t iid) {\n    AVS_LIST(test_instance_t) it;\n    AVS_LIST_FOREACH(it, repr->instances) {\n        if (it->iid == iid) {\n            return it;\n        } else if (it->iid > iid) {\n            // Since list of instances is sorted by Instance ID,\n            // Instance with given iid does not exist on that list\n            break;\n        }\n    }\n    // Instance was not found.\n    return NULL;\n}\n\nstatic int test_list_instances(anjay_t *anjay,\n                               const anjay_dm_object_def_t *const *obj_ptr,\n                               anjay_dm_list_ctx_t *ctx) {\n    (void) anjay; // unused\n\n    // iterate over all instances and return their IDs\n    AVS_LIST(test_instance_t) it;\n    AVS_LIST_FOREACH(it, get_test_object(obj_ptr)->instances) {\n        anjay_dm_emit(ctx, it->iid);\n    }\n\n    return 0;\n}\n\nstatic int test_instance_create(anjay_t *anjay,\n                                const anjay_dm_object_def_t *const *obj_ptr,\n                                anjay_iid_t iid) {\n    (void) anjay; // unused\n\n    test_object_t *repr = get_test_object(obj_ptr);\n\n    AVS_LIST(test_instance_t) new_instance =\n            AVS_LIST_NEW_ELEMENT(test_instance_t);\n\n    if (!new_instance) {\n        // out of memory\n        return ANJAY_ERR_INTERNAL;\n    }\n\n    new_instance->iid = iid;\n\n    // find a place where instance should be inserted,\n    // insert it and claim a victory\n    AVS_LIST(test_instance_t) *insert_ptr;\n    AVS_LIST_FOREACH_PTR(insert_ptr, &repr->instances) {\n        if ((*insert_ptr)->iid > new_instance->iid) {\n            break;\n        }\n    }\n    AVS_LIST_INSERT(insert_ptr, new_instance);\n    return 0;\n}\n\nstatic int test_instance_remove(anjay_t *anjay,\n                                const anjay_dm_object_def_t *const *obj_ptr,\n                                anjay_iid_t iid) {\n    (void) anjay; // unused\n    test_object_t *repr = get_test_object(obj_ptr);\n\n    AVS_LIST(test_instance_t) *it;\n    AVS_LIST_FOREACH_PTR(it, &repr->instances) {\n        if ((*it)->iid == iid) {\n            AVS_LIST_DELETE(it);\n            return 0;\n        }\n    }\n    // should never happen as Anjay checks whether instance is present\n    // prior to issuing instance_remove\n    return ANJAY_ERR_INTERNAL;\n}\n\nstatic int test_instance_reset(anjay_t *anjay,\n                               const anjay_dm_object_def_t *const *obj_ptr,\n                               anjay_iid_t iid) {\n    (void) anjay; // unused\n\n    test_instance_t *instance = get_instance(get_test_object(obj_ptr), iid);\n    // mark all Resource values for Object Instance `iid` as unset\n    instance->has_label = false;\n    instance->has_value = false;\n    return 0;\n}\n\nstatic int test_list_resources(anjay_t *anjay,\n                               const anjay_dm_object_def_t *const *obj_ptr,\n                               anjay_iid_t iid,\n                               anjay_dm_resource_list_ctx_t *ctx) {\n    (void) anjay;   // unused\n    (void) obj_ptr; // unused\n    (void) iid;     // unused\n\n    anjay_dm_emit_res(ctx, 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT);\n    return 0;\n}\n\nstatic int test_resource_read(anjay_t *anjay,\n                              const anjay_dm_object_def_t *const *obj_ptr,\n                              anjay_iid_t iid,\n                              anjay_rid_t rid,\n                              anjay_riid_t riid,\n                              anjay_output_ctx_t *ctx) {\n    (void) anjay; // unused\n\n    const test_instance_t *current_instance =\n            (const test_instance_t *) get_instance(get_test_object(obj_ptr),\n                                                   iid);\n\n    assert(riid == ANJAY_ID_INVALID);\n\n    switch (rid) {\n    case 0:\n        return anjay_ret_string(ctx, current_instance->label);\n    case 1:\n        return anjay_ret_i32(ctx, current_instance->value);\n    default:\n        // control will never reach this part due to test_list_resources\n        return ANJAY_ERR_INTERNAL;\n    }\n}\n\nstatic int test_resource_write(anjay_t *anjay,\n                               const anjay_dm_object_def_t *const *obj_ptr,\n                               anjay_iid_t iid,\n                               anjay_rid_t rid,\n                               anjay_riid_t riid,\n                               anjay_input_ctx_t *ctx) {\n    (void) anjay; // unused\n\n    test_instance_t *current_instance =\n            (test_instance_t *) get_instance(get_test_object(obj_ptr), iid);\n\n    assert(riid == ANJAY_ID_INVALID);\n\n    switch (rid) {\n    case 0: {\n        // `anjay_get_string` may return a chunk of data instead of the\n        // whole value - we need to make sure the client is able to hold\n        // the entire value\n        char buffer[sizeof(current_instance->label)];\n        int result = anjay_get_string(ctx, buffer, sizeof(buffer));\n\n        if (result == 0) {\n            // value OK - save it\n            memcpy(current_instance->label, buffer, sizeof(buffer));\n            current_instance->has_label = true;\n        } else if (result == ANJAY_BUFFER_TOO_SHORT) {\n            // the value is too long to store in the buffer\n            result = ANJAY_ERR_BAD_REQUEST;\n        }\n\n        return result;\n    }\n\n    case 1: {\n        // reading primitive values can be done directly - the value will only\n        // be written to the output variable if everything went fine\n        int result = anjay_get_i32(ctx, &current_instance->value);\n        if (result == 0) {\n            current_instance->has_value = true;\n        }\n        return result;\n    }\n\n    default:\n        // control will never reach this part due to test_list_resources\n        return ANJAY_ERR_INTERNAL;\n    }\n}\n\nstatic int test_transaction_begin(anjay_t *anjay,\n                                  const anjay_dm_object_def_t *const *obj_ptr) {\n    (void) anjay; // unused\n\n    test_object_t *test = get_test_object(obj_ptr);\n\n    // store a snapshot of object state\n    test->backup_instances = AVS_LIST_SIMPLE_CLONE(test->instances);\n\n    if (test->instances && !test->backup_instances) {\n        return ANJAY_ERR_INTERNAL;\n    }\n    return 0;\n}\n\nstatic int\ntest_transaction_validate(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr) {\n    (void) anjay; // unused\n\n    test_object_t *test = get_test_object(obj_ptr);\n\n    AVS_LIST(test_instance_t) it;\n\n    // ensure all Object Instances contain all Mandatory Resources\n    AVS_LIST_FOREACH(it, test->instances) {\n        if (!it->has_label || !it->has_value) {\n            // validation failed: Object state invalid, rollback required\n            return ANJAY_ERR_BAD_REQUEST;\n        }\n    }\n\n    // validation successful, can commit\n    return 0;\n}\n\nstatic int\ntest_transaction_commit(anjay_t *anjay,\n                        const anjay_dm_object_def_t *const *obj_ptr) {\n    (void) anjay;   // unused\n    (void) obj_ptr; // unused\n    test_object_t *test = get_test_object(obj_ptr);\n\n    // we free backup instances now, as current instance set is valid\n    AVS_LIST_CLEAR(&test->backup_instances);\n    return 0;\n}\n\nstatic int\ntest_transaction_rollback(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr) {\n    (void) anjay; // unused\n\n    test_object_t *test = get_test_object(obj_ptr);\n\n    // restore saved object state\n    AVS_LIST_CLEAR(&test->instances);\n    test->instances = test->backup_instances;\n    test->backup_instances = NULL;\n    return 0;\n}\n\nstatic const anjay_dm_object_def_t OBJECT_DEF = {\n    // Object ID\n    .oid = 1234,\n\n    .handlers = {\n        .list_instances = test_list_instances,\n        .instance_create = test_instance_create,\n        .instance_remove = test_instance_remove,\n        .instance_reset = test_instance_reset,\n\n        .list_resources = test_list_resources,\n        .resource_read = test_resource_read,\n        .resource_write = test_resource_write,\n\n        .transaction_begin = test_transaction_begin,\n        .transaction_validate = test_transaction_validate,\n        .transaction_commit = test_transaction_commit,\n        .transaction_rollback = test_transaction_rollback\n    }\n};\n\nconst anjay_dm_object_def_t **create_test_object(void) {\n    test_object_t *repr =\n            (test_object_t *) avs_calloc(1, sizeof(test_object_t));\n    if (repr) {\n        repr->obj_def = &OBJECT_DEF;\n        return &repr->obj_def;\n    }\n    return NULL;\n}\n\nvoid delete_test_object(const anjay_dm_object_def_t **obj) {\n    if (!obj) {\n        return;\n    }\n    test_object_t *repr = get_test_object(obj);\n    AVS_LIST_CLEAR(&repr->instances);\n    AVS_LIST_CLEAR(&repr->backup_instances);\n    avs_free(repr);\n}\n"
  },
  {
    "path": "examples/tutorial/AT-CustomObjects/multi-instance-dynamic/src/test_object.h",
    "content": "#ifndef TEST_OBJECT_H\n#define TEST_OBJECT_H\n\n#include <anjay/anjay.h>\n\nconst anjay_dm_object_def_t **create_test_object(void);\nvoid delete_test_object(const anjay_dm_object_def_t **obj);\n\n#endif /* TEST_OBJECT_H */\n"
  },
  {
    "path": "examples/tutorial/AT-CustomObjects/multi-instance-resources-dynamic/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.16)\nproject(multi-instance-resources-dynamic C)\n\nset(CMAKE_C_STANDARD 99)\nset(CMAKE_C_EXTENSIONS OFF)\n\nfind_package(anjay REQUIRED)\n\nadd_executable(${PROJECT_NAME}\n               src/main.c\n               src/test_object.c\n               src/test_object.h)\ntarget_link_libraries(${PROJECT_NAME} PRIVATE anjay)\n"
  },
  {
    "path": "examples/tutorial/AT-CustomObjects/multi-instance-resources-dynamic/src/main.c",
    "content": "#include <anjay/anjay.h>\n#include <anjay/security.h>\n#include <anjay/server.h>\n\n#include <avsystem/commons/avs_log.h>\n\n#include \"test_object.h\"\n\nstatic int setup_security_object(anjay_t *anjay) {\n    const anjay_security_instance_t security_instance = {\n        .ssid = 1,\n        .server_uri = \"coap://eu.iot.avsystem.cloud:5683\",\n        .security_mode = ANJAY_SECURITY_NOSEC\n    };\n\n    if (anjay_security_object_install(anjay)) {\n        return -1;\n    }\n\n    // let Anjay assign an Object Instance ID\n    anjay_iid_t security_instance_id = ANJAY_ID_INVALID;\n    if (anjay_security_object_add_instance(anjay, &security_instance,\n                                           &security_instance_id)) {\n        return -1;\n    }\n\n    return 0;\n}\n\nstatic int setup_server_object(anjay_t *anjay) {\n    const anjay_server_instance_t server_instance = {\n        .ssid = 1,\n        .lifetime = 86400,\n        .default_min_period = -1,\n        .default_max_period = -1,\n        .disable_timeout = -1,\n        .binding = \"U\"\n    };\n\n    if (anjay_server_object_install(anjay)) {\n        return -1;\n    }\n\n    anjay_iid_t server_instance_id = ANJAY_ID_INVALID;\n    if (anjay_server_object_add_instance(anjay, &server_instance,\n                                         &server_instance_id)) {\n        return -1;\n    }\n\n    return 0;\n}\n\nint main(int argc, char *argv[]) {\n    if (argc != 2) {\n        avs_log(tutorial, ERROR, \"usage: %s ENDPOINT_NAME\", argv[0]);\n        return -1;\n    }\n\n    const anjay_configuration_t CONFIG = {\n        .endpoint_name = argv[1],\n        .in_buffer_size = 4000,\n        .out_buffer_size = 4000\n    };\n\n    anjay_t *anjay = anjay_new(&CONFIG);\n    if (!anjay) {\n        return -1;\n    }\n\n    const anjay_dm_object_def_t **test_object = create_test_object();\n\n    int result = 0;\n\n    if (setup_security_object(anjay) || setup_server_object(anjay)\n            || anjay_register_object(anjay, test_object)) {\n        result = -1;\n        goto cleanup;\n    }\n\n    result = anjay_event_loop_run(anjay,\n                                  avs_time_duration_from_scalar(1, AVS_TIME_S));\n\ncleanup:\n    anjay_delete(anjay);\n    delete_test_object(test_object);\n\n    return result;\n}\n"
  },
  {
    "path": "examples/tutorial/AT-CustomObjects/multi-instance-resources-dynamic/src/test_object.c",
    "content": "#include \"test_object.h\"\n\n#include <assert.h>\n#include <stdlib.h>\n#include <string.h>\n\n#include <avsystem/commons/avs_list.h>\n\ntypedef struct test_value_instance {\n    anjay_riid_t index;\n    int32_t value;\n} test_value_instance_t;\n\ntypedef struct test_instance {\n    anjay_iid_t iid;\n\n    bool has_label;\n    char label[32];\n\n    bool has_values;\n    AVS_LIST(test_value_instance_t) values;\n} test_instance_t;\n\ntypedef struct test_object {\n    // handlers\n    const anjay_dm_object_def_t *obj_def;\n\n    // object state\n    AVS_LIST(test_instance_t) instances;\n\n    AVS_LIST(test_instance_t) backup_instances;\n} test_object_t;\n\nstatic test_object_t *get_test_object(const anjay_dm_object_def_t *const *obj) {\n    assert(obj);\n\n    // use the container_of pattern to retrieve test_object_t pointer\n    // AVS_CONTAINER_OF macro provided by avsystem/commons/defs.h\n    return AVS_CONTAINER_OF(obj, test_object_t, obj_def);\n}\n\nstatic AVS_LIST(test_instance_t) get_instance(test_object_t *repr,\n                                              anjay_iid_t iid) {\n    AVS_LIST(test_instance_t) it;\n    AVS_LIST_FOREACH(it, repr->instances) {\n        if (it->iid == iid) {\n            return it;\n        } else if (it->iid > iid) {\n            // Since list of instances is sorted by Instance Id\n            // there is no way to find Instance with given iid.\n            break;\n        }\n    }\n    // Instance was not found.\n    return NULL;\n}\n\nstatic int test_list_instances(anjay_t *anjay,\n                               const anjay_dm_object_def_t *const *obj_ptr,\n                               anjay_dm_list_ctx_t *ctx) {\n    (void) anjay; // unused\n\n    // iterate over all instances and return their IDs\n    AVS_LIST(test_instance_t) it;\n    AVS_LIST_FOREACH(it, get_test_object(obj_ptr)->instances) {\n        anjay_dm_emit(ctx, it->iid);\n    }\n\n    return 0;\n}\n\nstatic int test_instance_create(anjay_t *anjay,\n                                const anjay_dm_object_def_t *const *obj_ptr,\n                                anjay_iid_t iid) {\n    (void) anjay; // unused\n\n    test_object_t *repr = get_test_object(obj_ptr);\n\n    AVS_LIST(test_instance_t) new_instance =\n            AVS_LIST_NEW_ELEMENT(test_instance_t);\n\n    if (!new_instance) {\n        // out of memory\n        return ANJAY_ERR_INTERNAL;\n    }\n\n    new_instance->iid = iid;\n\n    // find a place where instance should be inserted,\n    // insert it and claim a victory\n    AVS_LIST(test_instance_t) *insert_ptr;\n    AVS_LIST_FOREACH_PTR(insert_ptr, &repr->instances) {\n        if ((*insert_ptr)->iid > new_instance->iid) {\n            break;\n        }\n    }\n    AVS_LIST_INSERT(insert_ptr, new_instance);\n    return 0;\n}\n\nstatic int test_instance_remove(anjay_t *anjay,\n                                const anjay_dm_object_def_t *const *obj_ptr,\n                                anjay_iid_t iid) {\n    (void) anjay; // unused\n    test_object_t *repr = get_test_object(obj_ptr);\n\n    AVS_LIST(test_instance_t) *it;\n    AVS_LIST_FOREACH_PTR(it, &repr->instances) {\n        if ((*it)->iid == iid) {\n            AVS_LIST_CLEAR(&(*it)->values);\n            AVS_LIST_DELETE(it);\n            return 0;\n        }\n    }\n    // should never happen as Anjay checks whether instance is present\n    // prior to issuing instance_remove\n    return ANJAY_ERR_INTERNAL;\n}\n\nstatic int test_instance_reset(anjay_t *anjay,\n                               const anjay_dm_object_def_t *const *obj_ptr,\n                               anjay_iid_t iid) {\n    (void) anjay; // unused\n\n    test_instance_t *instance = get_instance(get_test_object(obj_ptr), iid);\n    // mark all Resource values for Object Instance `iid` as unset\n    instance->has_label = false;\n    instance->has_values = false;\n    AVS_LIST_CLEAR(&instance->values);\n    return 0;\n}\n\nstatic int test_list_resources(anjay_t *anjay,\n                               const anjay_dm_object_def_t *const *obj_ptr,\n                               anjay_iid_t iid,\n                               anjay_dm_resource_list_ctx_t *ctx) {\n    (void) anjay;   // unused\n    (void) obj_ptr; // unused\n    (void) iid;     // unused\n\n    anjay_dm_emit_res(ctx, 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, 1, ANJAY_DM_RES_RWM, ANJAY_DM_RES_PRESENT);\n    return 0;\n}\n\nstatic int test_resource_read(anjay_t *anjay,\n                              const anjay_dm_object_def_t *const *obj_ptr,\n                              anjay_iid_t iid,\n                              anjay_rid_t rid,\n                              anjay_riid_t riid,\n                              anjay_output_ctx_t *ctx) {\n    (void) anjay; // unused\n\n    const test_instance_t *current_instance =\n            (const test_instance_t *) get_instance(get_test_object(obj_ptr),\n                                                   iid);\n\n    switch (rid) {\n    case 0:\n        assert(riid == ANJAY_ID_INVALID);\n        return anjay_ret_string(ctx, current_instance->label);\n    case 1: {\n        AVS_LIST(const test_value_instance_t) it;\n        AVS_LIST_FOREACH(it, current_instance->values) {\n            if (it->index == riid) {\n                return anjay_ret_i32(ctx, it->value);\n            }\n        }\n        // Resource Instance not found\n        return ANJAY_ERR_NOT_FOUND;\n    }\n    default:\n        // control will never reach this part due to test_list_resources\n        return ANJAY_ERR_INTERNAL;\n    }\n}\n\nstatic int test_array_write(AVS_LIST(test_value_instance_t) *out_instances,\n                            anjay_riid_t index,\n                            anjay_input_ctx_t *input_ctx) {\n    test_value_instance_t instance = {\n        .index = index\n    };\n\n    if (anjay_get_i32(input_ctx, &instance.value)) {\n        // An error occurred during the read.\n        return ANJAY_ERR_INTERNAL;\n    }\n\n    AVS_LIST(test_value_instance_t) *insert_it;\n\n    // Searching for the place to insert;\n    // note that it makes the whole function O(n).\n    AVS_LIST_FOREACH_PTR(insert_it, out_instances) {\n        if ((*insert_it)->index >= instance.index) {\n            break;\n        }\n    }\n\n    if ((*insert_it)->index != instance.index) {\n        AVS_LIST(test_value_instance_t) new_element =\n                AVS_LIST_NEW_ELEMENT(test_value_instance_t);\n\n        if (!new_element) {\n            // out of memory\n            return ANJAY_ERR_INTERNAL;\n        }\n\n        AVS_LIST_INSERT(insert_it, new_element);\n    }\n\n    assert((*insert_it)->index == instance.index);\n    **insert_it = instance;\n\n    return 0;\n}\n\nstatic int test_resource_write(anjay_t *anjay,\n                               const anjay_dm_object_def_t *const *obj_ptr,\n                               anjay_iid_t iid,\n                               anjay_rid_t rid,\n                               anjay_riid_t riid,\n                               anjay_input_ctx_t *ctx) {\n    (void) anjay; // unused\n\n    test_instance_t *current_instance =\n            (test_instance_t *) get_instance(get_test_object(obj_ptr), iid);\n\n    switch (rid) {\n    case 0: {\n        assert(riid == ANJAY_ID_INVALID);\n\n        // `anjay_get_string` may return a chunk of data instead of the\n        // whole value - we need to make sure the client is able to hold\n        // the entire value\n        char buffer[sizeof(current_instance->label)];\n        int result = anjay_get_string(ctx, buffer, sizeof(buffer));\n\n        if (result == 0) {\n            // value OK - save it\n            memcpy(current_instance->label, buffer, sizeof(buffer));\n            current_instance->has_label = true;\n        } else if (result == ANJAY_BUFFER_TOO_SHORT) {\n            // the value is too long to store in the buffer\n            result = ANJAY_ERR_BAD_REQUEST;\n        }\n\n        return result;\n    }\n\n    case 1: {\n        int result = test_array_write(&current_instance->values, riid, ctx);\n        if (!result) {\n            current_instance->has_values = true;\n        }\n\n        // either test_array_write succeeded and result is 0, or not\n        // in which case result contains appropriate error code.\n        return result;\n    }\n\n    default:\n        // control will never reach this part due to test_list_resources\n        return ANJAY_ERR_INTERNAL;\n    }\n}\n\nstatic int test_resource_reset(anjay_t *anjay,\n                               const anjay_dm_object_def_t *const *obj_ptr,\n                               anjay_iid_t iid,\n                               anjay_rid_t rid) {\n    (void) anjay; // unused\n\n    test_instance_t *current_instance =\n            (test_instance_t *) get_instance(get_test_object(obj_ptr), iid);\n\n    // this handler can only be called for Multiple-Instance Resources\n    assert(rid == 1);\n\n    // free memory associated with old values\n    AVS_LIST_CLEAR(&current_instance->values);\n    current_instance->has_values = true;\n    return 0;\n}\n\nstatic int\ntest_resource_instance_remove(anjay_t *anjay,\n                              const anjay_dm_object_def_t *const *obj_ptr,\n                              anjay_iid_t iid,\n                              anjay_rid_t rid,\n                              anjay_riid_t riid) {\n    (void) anjay; // unused\n\n    test_instance_t *current_instance =\n            (test_instance_t *) get_instance(get_test_object(obj_ptr), iid);\n\n    // this handler can only be called for Multiple-Instance Resources\n    assert(rid == 1);\n\n    // find the Resource Instance entry\n    AVS_LIST(test_value_instance_t) *it;\n    AVS_LIST_FOREACH_PTR(it, &current_instance->values) {\n        if ((*it)->index == riid) {\n            break;\n        }\n    }\n\n    // this handler can only be called for existing Resource Instances\n    assert(it && *it && (*it)->index == riid);\n\n    // free memory associated with the instance\n    AVS_LIST_DELETE(it);\n    current_instance->has_values = true;\n    return 0;\n}\n\nstatic int\ntest_list_resource_instances(anjay_t *anjay,\n                             const anjay_dm_object_def_t *const *obj_ptr,\n                             anjay_iid_t iid,\n                             anjay_rid_t rid,\n                             anjay_dm_list_ctx_t *ctx) {\n    (void) anjay; // unused\n    test_instance_t *current_instance =\n            (test_instance_t *) get_instance(get_test_object(obj_ptr), iid);\n\n    // this handler can only be called for Multiple-Instance Resources\n    assert(rid == 1);\n\n    AVS_LIST(test_value_instance_t) it;\n    AVS_LIST_FOREACH(it, current_instance->values) {\n        anjay_dm_emit(ctx, it->index);\n    }\n    return 0;\n}\n\nstatic void clear_instances(AVS_LIST(test_instance_t) *instances) {\n    AVS_LIST_CLEAR(instances) {\n        AVS_LIST_CLEAR(&(*instances)->values);\n    }\n}\n\nstatic int clone_instances(AVS_LIST(test_instance_t) *cloned_instances,\n                           AVS_LIST(const test_instance_t) instances) {\n    assert(*cloned_instances == NULL);\n\n    AVS_LIST(test_instance_t) *end_ptr = cloned_instances;\n    AVS_LIST(const test_instance_t) it;\n\n    AVS_LIST_FOREACH(it, instances) {\n        AVS_LIST(test_instance_t) cloned_instance =\n                AVS_LIST_NEW_ELEMENT(test_instance_t);\n        if (!cloned_instance) {\n            goto failure;\n        }\n\n        // copy all fields\n        cloned_instance->iid = it->iid;\n        cloned_instance->has_label = it->has_label;\n        memcpy(cloned_instance->label, it->label, sizeof(it->label));\n        cloned_instance->has_values = it->has_values;\n        cloned_instance->values = AVS_LIST_SIMPLE_CLONE(it->values);\n\n        if (it->values && !cloned_instance->values) {\n            // cloning failed, probably due to out of memory\n            goto failure;\n        }\n\n        // everything went just right, append\n        AVS_LIST_INSERT(end_ptr, cloned_instance);\n\n        // align the end_ptr to point at the next feasible\n        // insertion point\n        end_ptr = AVS_LIST_NEXT_PTR(end_ptr);\n    }\n    return 0;\n\nfailure:\n    clear_instances(cloned_instances);\n    return -1;\n}\n\nstatic int test_transaction_begin(anjay_t *anjay,\n                                  const anjay_dm_object_def_t *const *obj_ptr) {\n    (void) anjay; // unused\n\n    test_object_t *test = get_test_object(obj_ptr);\n\n    assert(test->backup_instances == NULL);\n    // store a snapshot of object state\n    if (clone_instances(&test->backup_instances, test->instances)) {\n        return ANJAY_ERR_INTERNAL;\n    }\n    return 0;\n}\n\nstatic int\ntest_transaction_validate(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr) {\n    (void) anjay; // unused\n\n    test_object_t *test = get_test_object(obj_ptr);\n\n    AVS_LIST(test_instance_t) it;\n\n    // ensure all Object Instances contain all Mandatory Resources\n    AVS_LIST_FOREACH(it, test->instances) {\n        if (!it->has_label || !it->has_values) {\n            // validation failed: Object state invalid, rollback required\n            return ANJAY_ERR_BAD_REQUEST;\n        }\n    }\n\n    // validation successful, can commit\n    return 0;\n}\n\nstatic int\ntest_transaction_commit(anjay_t *anjay,\n                        const anjay_dm_object_def_t *const *obj_ptr) {\n    (void) anjay;   // unused\n    (void) obj_ptr; // unused\n    test_object_t *test = get_test_object(obj_ptr);\n\n    // we free backup instances now, as current instance set is valid\n    clear_instances(&test->backup_instances);\n    return 0;\n}\n\nstatic int\ntest_transaction_rollback(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr) {\n    (void) anjay; // unused\n\n    test_object_t *test = get_test_object(obj_ptr);\n\n    // restore saved object state\n    clear_instances(&test->instances);\n    test->instances = test->backup_instances;\n    test->backup_instances = NULL;\n    return 0;\n}\n\nstatic const anjay_dm_object_def_t OBJECT_DEF = {\n    // Object ID\n    .oid = 1234,\n\n    .handlers = {\n        .list_instances = test_list_instances,\n        .instance_create = test_instance_create,\n        .instance_remove = test_instance_remove,\n        .instance_reset = test_instance_reset,\n\n        .list_resources = test_list_resources,\n        .resource_read = test_resource_read,\n        .resource_write = test_resource_write,\n        .resource_reset = test_resource_reset,\n\n        .list_resource_instances = test_list_resource_instances,\n\n        .transaction_begin = test_transaction_begin,\n        .transaction_validate = test_transaction_validate,\n        .transaction_commit = test_transaction_commit,\n        .transaction_rollback = test_transaction_rollback,\n    }\n};\n\nconst anjay_dm_object_def_t **create_test_object(void) {\n    test_object_t *repr =\n            (test_object_t *) avs_calloc(1, sizeof(test_object_t));\n    if (repr) {\n        repr->obj_def = &OBJECT_DEF;\n        return &repr->obj_def;\n    }\n    return NULL;\n}\n\nvoid delete_test_object(const anjay_dm_object_def_t **obj) {\n    test_object_t *repr = get_test_object(obj);\n    clear_instances(&repr->instances);\n    clear_instances(&repr->backup_instances);\n    avs_free(repr);\n}\n"
  },
  {
    "path": "examples/tutorial/AT-CustomObjects/multi-instance-resources-dynamic/src/test_object.h",
    "content": "#ifndef TEST_OBJECT_H\n#define TEST_OBJECT_H\n\n#include <anjay/anjay.h>\n\nconst anjay_dm_object_def_t **create_test_object(void);\nvoid delete_test_object(const anjay_dm_object_def_t **obj);\n\n#endif /* TEST_OBJECT_H */\n"
  },
  {
    "path": "examples/tutorial/AT-CustomObjects/read-only/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.16)\nproject(single-instance-read-only C)\n\nset(CMAKE_C_STANDARD 99)\nset(CMAKE_C_EXTENSIONS OFF)\n\nfind_package(anjay REQUIRED)\n\nadd_executable(${PROJECT_NAME} src/main.c)\ntarget_link_libraries(${PROJECT_NAME} PRIVATE anjay)\n"
  },
  {
    "path": "examples/tutorial/AT-CustomObjects/read-only/src/main.c",
    "content": "#include <anjay/anjay.h>\n#include <anjay/security.h>\n#include <anjay/server.h>\n\n#include <avsystem/commons/avs_log.h>\n#include <avsystem/commons/avs_time.h>\n\nstatic int test_list_resources(anjay_t *anjay,\n                               const anjay_dm_object_def_t *const *obj_ptr,\n                               anjay_iid_t iid,\n                               anjay_dm_resource_list_ctx_t *ctx) {\n    (void) anjay;   // unused\n    (void) obj_ptr; // unused\n    (void) iid;     // unused\n\n    anjay_dm_emit_res(ctx, 0, ANJAY_DM_RES_R, ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, 1, ANJAY_DM_RES_R, ANJAY_DM_RES_PRESENT);\n    return 0;\n}\n\nstatic int test_resource_read(anjay_t *anjay,\n                              const anjay_dm_object_def_t *const *obj_ptr,\n                              anjay_iid_t iid,\n                              anjay_rid_t rid,\n                              anjay_riid_t riid,\n                              anjay_output_ctx_t *ctx) {\n    // These arguments may seem superfluous now, but they will come in handy\n    // while defining more complex objects\n    (void) anjay;   // unused\n    (void) obj_ptr; // unused: the object holds no state\n    (void) iid;     // unused: will always be 0 for single-instance Objects\n    (void) riid;    // unused: will always be ANJAY_ID_INVALID\n\n    switch (rid) {\n    case 0:\n        return anjay_ret_string(ctx, \"Test object\");\n    case 1:\n        return anjay_ret_i64(ctx, avs_time_real_now().since_real_epoch.seconds);\n    default:\n        // control will never reach this part due to test_list_resources\n        return 0;\n    }\n}\n\nstatic const anjay_dm_object_def_t OBJECT_DEF = {\n    // Object ID\n    .oid = 1234,\n\n    .handlers = {\n        // single-instance Objects can use this pre-implemented handler:\n        .list_instances = anjay_dm_list_instances_SINGLE,\n\n        .list_resources = test_list_resources,\n        .resource_read = test_resource_read\n\n        // all other handlers can be left NULL if only Read operation is\n        // required\n    }\n};\n\nstatic int setup_security_object(anjay_t *anjay) {\n    const anjay_security_instance_t security_instance = {\n        .ssid = 1,\n        .server_uri = \"coap://eu.iot.avsystem.cloud:5683\",\n        .security_mode = ANJAY_SECURITY_NOSEC\n    };\n\n    if (anjay_security_object_install(anjay)) {\n        return -1;\n    }\n\n    // let Anjay assign an Object Instance ID\n    anjay_iid_t security_instance_id = ANJAY_ID_INVALID;\n    if (anjay_security_object_add_instance(anjay, &security_instance,\n                                           &security_instance_id)) {\n        return -1;\n    }\n\n    return 0;\n}\n\nstatic int setup_server_object(anjay_t *anjay) {\n    const anjay_server_instance_t server_instance = {\n        .ssid = 1,\n        .lifetime = 86400,\n        .default_min_period = -1,\n        .default_max_period = -1,\n        .disable_timeout = -1,\n        .binding = \"U\"\n    };\n\n    if (anjay_server_object_install(anjay)) {\n        return -1;\n    }\n\n    anjay_iid_t server_instance_id = ANJAY_ID_INVALID;\n    if (anjay_server_object_add_instance(anjay, &server_instance,\n                                         &server_instance_id)) {\n        return -1;\n    }\n\n    return 0;\n}\n\nint main(int argc, char *argv[]) {\n    if (argc != 2) {\n        avs_log(tutorial, ERROR, \"usage: %s ENDPOINT_NAME\", argv[0]);\n        return -1;\n    }\n\n    const anjay_configuration_t CONFIG = {\n        .endpoint_name = argv[1],\n        .in_buffer_size = 4000,\n        .out_buffer_size = 4000\n    };\n\n    anjay_t *anjay = anjay_new(&CONFIG);\n    if (!anjay) {\n        return -1;\n    }\n\n    int result = 0;\n\n    if (setup_security_object(anjay) || setup_server_object(anjay)) {\n        result = -1;\n        goto cleanup;\n    }\n\n    // initialize and register the test object\n\n    // note: in this simple case the object does not have any state,\n    // so it's fine to use a plain double pointer to its definition struct\n    const anjay_dm_object_def_t *test_object_def_ptr = &OBJECT_DEF;\n\n    anjay_register_object(anjay, &test_object_def_ptr);\n\n    result = anjay_event_loop_run(anjay,\n                                  avs_time_duration_from_scalar(1, AVS_TIME_S));\n\ncleanup:\n    anjay_delete(anjay);\n\n    // test object does not need cleanup\n    return result;\n}\n"
  },
  {
    "path": "examples/tutorial/AT-CustomObjects/read-only-multiple-fixed/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.16)\nproject(multi-instance-read-only-fixed C)\n\nset(CMAKE_C_STANDARD 99)\nset(CMAKE_C_EXTENSIONS OFF)\n\nfind_package(anjay REQUIRED)\n\nadd_executable(${PROJECT_NAME} src/main.c)\ntarget_link_libraries(${PROJECT_NAME} PRIVATE anjay)\n"
  },
  {
    "path": "examples/tutorial/AT-CustomObjects/read-only-multiple-fixed/src/main.c",
    "content": "#include <assert.h>\n\n#include <anjay/anjay.h>\n#include <anjay/security.h>\n#include <anjay/server.h>\n\n#include <avsystem/commons/avs_log.h>\n\ntypedef struct test_instance {\n    const char *label;\n    int32_t value;\n} test_instance_t;\n\ntypedef struct test_object {\n    // handlers\n    const anjay_dm_object_def_t *obj_def;\n\n    // object state\n    test_instance_t instances[2];\n} test_object_t;\n\nstatic test_object_t *get_test_object(const anjay_dm_object_def_t *const *obj) {\n    assert(obj);\n\n    // use the container_of pattern to retrieve test_object_t pointer\n    // AVS_CONTAINER_OF macro provided by avsystem/commons/defs.h\n    return AVS_CONTAINER_OF(obj, test_object_t, obj_def);\n}\n\nstatic int test_list_instances(anjay_t *anjay,\n                               const anjay_dm_object_def_t *const *obj_ptr,\n                               anjay_dm_list_ctx_t *ctx) {\n    (void) anjay; // unused\n\n    test_object_t *test = get_test_object(obj_ptr);\n\n    for (anjay_iid_t iid = 0;\n         (size_t) iid < sizeof(test->instances) / sizeof(test->instances[0]);\n         ++iid) {\n        anjay_dm_emit(ctx, iid);\n    }\n\n    return 0;\n}\n\nstatic int test_list_resources(anjay_t *anjay,\n                               const anjay_dm_object_def_t *const *obj_ptr,\n                               anjay_iid_t iid,\n                               anjay_dm_resource_list_ctx_t *ctx) {\n    (void) anjay;   // unused\n    (void) obj_ptr; // unused\n    (void) iid;     // unused\n\n    anjay_dm_emit_res(ctx, 0, ANJAY_DM_RES_R, ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, 1, ANJAY_DM_RES_R, ANJAY_DM_RES_PRESENT);\n    return 0;\n}\n\nstatic int test_resource_read(anjay_t *anjay,\n                              const anjay_dm_object_def_t *const *obj_ptr,\n                              anjay_iid_t iid,\n                              anjay_rid_t rid,\n                              anjay_riid_t riid,\n                              anjay_output_ctx_t *ctx) {\n    (void) anjay; // unused\n\n    test_object_t *test = get_test_object(obj_ptr);\n\n    // IID validity was checked by the `anjay_dm_list_instances_t` handler.\n    // If the Object Instance set does not change, or can only be modified\n    // via LwM2M Create/Delete requests, it is safe to assume IID is correct.\n    assert((size_t) iid < sizeof(test->instances) / sizeof(test->instances[0]));\n    const struct test_instance *current_instance = &test->instances[iid];\n\n    // We have no Multiple-Instance Resources, so it is safe to assume\n    // that RIID is never set.\n    assert(riid == ANJAY_ID_INVALID);\n\n    switch (rid) {\n    case 0:\n        return anjay_ret_string(ctx, current_instance->label);\n    case 1:\n        return anjay_ret_i32(ctx, current_instance->value);\n    default:\n        // control will never reach this part due to test_list_resources\n        return ANJAY_ERR_INTERNAL;\n    }\n}\n\nstatic const anjay_dm_object_def_t OBJECT_DEF = {\n    // Object ID\n    .oid = 1234,\n\n    .handlers = {\n        .list_instances = test_list_instances,\n\n        .list_resources = test_list_resources,\n        .resource_read = test_resource_read\n\n        // all other handlers can be left NULL if only Read operation is\n        // required\n    }\n};\n\nstatic int setup_security_object(anjay_t *anjay) {\n    const anjay_security_instance_t security_instance = {\n        .ssid = 1,\n        .server_uri = \"coap://eu.iot.avsystem.cloud:5683\",\n        .security_mode = ANJAY_SECURITY_NOSEC\n    };\n\n    if (anjay_security_object_install(anjay)) {\n        return -1;\n    }\n\n    // let Anjay assign an Object Instance ID\n    anjay_iid_t security_instance_id = ANJAY_ID_INVALID;\n    if (anjay_security_object_add_instance(anjay, &security_instance,\n                                           &security_instance_id)) {\n        return -1;\n    }\n\n    return 0;\n}\n\nstatic int setup_server_object(anjay_t *anjay) {\n    const anjay_server_instance_t server_instance = {\n        .ssid = 1,\n        .lifetime = 86400,\n        .default_min_period = -1,\n        .default_max_period = -1,\n        .disable_timeout = -1,\n        .binding = \"U\"\n    };\n\n    if (anjay_server_object_install(anjay)) {\n        return -1;\n    }\n\n    anjay_iid_t server_instance_id = ANJAY_ID_INVALID;\n    if (anjay_server_object_add_instance(anjay, &server_instance,\n                                         &server_instance_id)) {\n        return -1;\n    }\n\n    return 0;\n}\n\nint main(int argc, char *argv[]) {\n    if (argc != 2) {\n        avs_log(tutorial, ERROR, \"usage: %s ENDPOINT_NAME\", argv[0]);\n        return -1;\n    }\n\n    const anjay_configuration_t CONFIG = {\n        .endpoint_name = argv[1],\n        .in_buffer_size = 4000,\n        .out_buffer_size = 4000\n    };\n\n    anjay_t *anjay = anjay_new(&CONFIG);\n    if (!anjay) {\n        return -1;\n    }\n\n    int result = 0;\n\n    if (setup_security_object(anjay) || setup_server_object(anjay)) {\n        result = -1;\n        goto cleanup;\n    }\n\n    // initialize and register the test object\n    const test_object_t test_object = {\n        .obj_def = &OBJECT_DEF,\n        .instances = { { \"First\", 1 }, { \"Second\", 2 } }\n    };\n\n    anjay_register_object(anjay, &test_object.obj_def);\n\n    result = anjay_event_loop_run(anjay,\n                                  avs_time_duration_from_scalar(1, AVS_TIME_S));\n\ncleanup:\n    anjay_delete(anjay);\n\n    // test object does not need cleanup\n    return result;\n}\n"
  },
  {
    "path": "examples/tutorial/AT-CustomObjects/read-only-with-executable/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.16)\nproject(single-instance-read-only-with-executable C)\n\nset(CMAKE_C_STANDARD 99)\nset(CMAKE_C_EXTENSIONS OFF)\n\nfind_package(anjay REQUIRED)\n\nadd_executable(${PROJECT_NAME} src/main.c)\ntarget_link_libraries(${PROJECT_NAME} PRIVATE anjay)\n"
  },
  {
    "path": "examples/tutorial/AT-CustomObjects/read-only-with-executable/src/main.c",
    "content": "#include <anjay/anjay.h>\n#include <anjay/security.h>\n#include <anjay/server.h>\n\n#include <avsystem/commons/avs_log.h>\n\nstatic long addition_result;\n\nstatic int test_list_resources(anjay_t *anjay,\n                               const anjay_dm_object_def_t *const *obj_ptr,\n                               anjay_iid_t iid,\n                               anjay_dm_resource_list_ctx_t *ctx) {\n    (void) anjay;   // unused\n    (void) obj_ptr; // unused\n    (void) iid;     // unused\n\n    anjay_dm_emit_res(ctx, 0, ANJAY_DM_RES_R, ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, 1, ANJAY_DM_RES_R, ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, 2, ANJAY_DM_RES_E, ANJAY_DM_RES_PRESENT);\n    return 0;\n}\n\nstatic int test_resource_read(anjay_t *anjay,\n                              const anjay_dm_object_def_t *const *obj_ptr,\n                              anjay_iid_t iid,\n                              anjay_rid_t rid,\n                              anjay_riid_t riid,\n                              anjay_output_ctx_t *ctx) {\n    // These arguments may seem superfluous now, but they will come in handy\n    // while defining more complex objects\n    (void) anjay;   // unused\n    (void) obj_ptr; // unused: the object holds no state\n    (void) iid;     // unused: will always be 0 for single-instance Objects\n    (void) riid;    // unused: will always be ANJAY_ID_INVALID\n\n    switch (rid) {\n    case 0:\n        return anjay_ret_string(ctx, \"Test object\");\n    case 1:\n        return anjay_ret_i64(ctx, addition_result);\n    case 2:\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    default:\n        // control will never reach this part due to test_list_resources\n        return 0;\n    }\n}\n\nstatic int get_arg_value(anjay_execute_ctx_t *ctx, int *out_value) {\n    // we expect arguments of form <0-9>='<integer>'\n    int arg_number;\n    bool has_value;\n    int result = anjay_execute_get_next_arg(ctx, &arg_number, &has_value);\n    // note that we do not check against duplicated argument ids\n    (void) arg_number;\n\n    if (result < 0 || result == ANJAY_EXECUTE_GET_ARG_END) {\n        // an error occured or there is just nothing more to read\n        return result;\n    }\n    if (!has_value) {\n        // we expect arguments with values only\n        return ANJAY_ERR_BAD_REQUEST;\n    }\n\n    char value_buffer[10];\n    if (anjay_execute_get_arg_value(ctx, NULL, value_buffer,\n                                    sizeof(value_buffer))\n            != 0) {\n        // the value must have been malformed or it is too long - either way, we\n        // don't like it\n        return ANJAY_ERR_BAD_REQUEST;\n    }\n    char *endptr = NULL;\n    long value = strtol(value_buffer, &endptr, 10);\n    if (!endptr || *endptr != '\\0' || value < INT_MIN || value > INT_MAX) {\n        // either not an integer or the number is too small / too big\n        return ANJAY_ERR_BAD_REQUEST;\n    }\n    *out_value = (int) value;\n    return 0;\n}\n\nstatic int test_resource_execute(anjay_t *anjay,\n                                 const anjay_dm_object_def_t *const *obj_ptr,\n                                 anjay_iid_t iid,\n                                 anjay_rid_t rid,\n                                 anjay_execute_ctx_t *ctx) {\n    switch (rid) {\n    case 2: {\n        long sum = 0;\n        int result;\n        do {\n            int arg_value = 0;\n            if ((result = get_arg_value(ctx, &arg_value)) == 0) {\n                sum += arg_value;\n            }\n        } while (!result);\n\n        if (result != ANJAY_EXECUTE_GET_ARG_END) {\n            return result;\n        }\n        addition_result = sum;\n        return 0;\n    }\n    default:\n        // no other resource is executable\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\nstatic const anjay_dm_object_def_t OBJECT_DEF = {\n    // Object ID\n    .oid = 1234,\n\n    .handlers = {\n        // single-instance Objects can use this pre-implemented handler:\n        .list_instances = anjay_dm_list_instances_SINGLE,\n\n        .list_resources = test_list_resources,\n        .resource_read = test_resource_read,\n        .resource_execute = test_resource_execute\n\n        // all other handlers can be left NULL if only Read and Execute\n        // operations are required\n    }\n};\n\nstatic int setup_security_object(anjay_t *anjay) {\n    const anjay_security_instance_t security_instance = {\n        .ssid = 1,\n        .server_uri = \"coap://eu.iot.avsystem.cloud:5683\",\n        .security_mode = ANJAY_SECURITY_NOSEC\n    };\n\n    if (anjay_security_object_install(anjay)) {\n        return -1;\n    }\n\n    // let Anjay assign an Object Instance ID\n    anjay_iid_t security_instance_id = ANJAY_ID_INVALID;\n    if (anjay_security_object_add_instance(anjay, &security_instance,\n                                           &security_instance_id)) {\n        return -1;\n    }\n\n    return 0;\n}\n\nstatic int setup_server_object(anjay_t *anjay) {\n    const anjay_server_instance_t server_instance = {\n        .ssid = 1,\n        .lifetime = 86400,\n        .default_min_period = -1,\n        .default_max_period = -1,\n        .disable_timeout = -1,\n        .binding = \"U\"\n    };\n\n    if (anjay_server_object_install(anjay)) {\n        return -1;\n    }\n\n    anjay_iid_t server_instance_id = ANJAY_ID_INVALID;\n    if (anjay_server_object_add_instance(anjay, &server_instance,\n                                         &server_instance_id)) {\n        return -1;\n    }\n\n    return 0;\n}\n\nint main(int argc, char *argv[]) {\n    if (argc != 2) {\n        avs_log(tutorial, ERROR, \"usage: %s ENDPOINT_NAME\", argv[0]);\n        return -1;\n    }\n\n    const anjay_configuration_t CONFIG = {\n        .endpoint_name = argv[1],\n        .in_buffer_size = 4000,\n        .out_buffer_size = 4000\n    };\n\n    anjay_t *anjay = anjay_new(&CONFIG);\n    if (!anjay) {\n        return -1;\n    }\n\n    int result = 0;\n\n    if (setup_security_object(anjay) || setup_server_object(anjay)) {\n        result = -1;\n        goto cleanup;\n    }\n\n    // initialize and register the test object\n\n    // note: in this simple case the object does not have any state,\n    // so it's fine to use a plain double pointer to its definition struct\n    const anjay_dm_object_def_t *test_object_def_ptr = &OBJECT_DEF;\n\n    anjay_register_object(anjay, &test_object_def_ptr);\n\n    result = anjay_event_loop_run(anjay,\n                                  avs_time_duration_from_scalar(1, AVS_TIME_S));\n\ncleanup:\n    anjay_delete(anjay);\n\n    // test object does not need cleanup\n    return result;\n}\n"
  },
  {
    "path": "examples/tutorial/AT-CustomObjects/writable-multiple-fixed/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.16)\nproject(multi-instance-writable-fixed C)\n\nset(CMAKE_C_STANDARD 99)\nset(CMAKE_C_EXTENSIONS OFF)\n\nfind_package(anjay REQUIRED)\n\nadd_executable(${PROJECT_NAME} src/main.c)\ntarget_link_libraries(${PROJECT_NAME} PRIVATE anjay)\n"
  },
  {
    "path": "examples/tutorial/AT-CustomObjects/writable-multiple-fixed/src/main.c",
    "content": "#include <assert.h>\n#include <string.h>\n\n#include <anjay/anjay.h>\n#include <anjay/security.h>\n#include <anjay/server.h>\n\n#include <avsystem/commons/avs_log.h>\n\ntypedef struct test_instance {\n    char label[32];\n    int32_t value;\n} test_instance_t;\n\nstatic const test_instance_t DEFAULT_INSTANCE_VALUES[] = { { \"First\", 1 },\n                                                           { \"Second\", 2 } };\n#define NUM_INSTANCES \\\n    (sizeof(DEFAULT_INSTANCE_VALUES) / sizeof(DEFAULT_INSTANCE_VALUES[0]))\n\ntypedef struct test_object {\n    // handlers\n    const anjay_dm_object_def_t *const obj_def;\n\n    // object state\n    test_instance_t instances[NUM_INSTANCES];\n} test_object_t;\n\nstatic test_object_t *get_test_object(const anjay_dm_object_def_t *const *obj) {\n    assert(obj);\n\n    // use the container_of pattern to retrieve test_object_t pointer\n    // AVS_CONTAINER_OF macro provided by avsystem/commons/defs.h\n    return AVS_CONTAINER_OF(obj, test_object_t, obj_def);\n}\n\nstatic int test_list_instances(anjay_t *anjay,\n                               const anjay_dm_object_def_t *const *obj_ptr,\n                               anjay_dm_list_ctx_t *ctx) {\n    (void) anjay;   // unused\n    (void) obj_ptr; // unused\n\n    for (anjay_iid_t iid = 0; iid < NUM_INSTANCES; ++iid) {\n        anjay_dm_emit(ctx, iid);\n    }\n\n    return 0;\n}\n\nstatic int test_list_resources(anjay_t *anjay,\n                               const anjay_dm_object_def_t *const *obj_ptr,\n                               anjay_iid_t iid,\n                               anjay_dm_resource_list_ctx_t *ctx) {\n    (void) anjay;   // unused\n    (void) obj_ptr; // unused\n    (void) iid;     // unused\n\n    anjay_dm_emit_res(ctx, 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT);\n    return 0;\n}\n\nstatic int test_resource_read(anjay_t *anjay,\n                              const anjay_dm_object_def_t *const *obj_ptr,\n                              anjay_iid_t iid,\n                              anjay_rid_t rid,\n                              anjay_riid_t riid,\n                              anjay_output_ctx_t *ctx) {\n    (void) anjay; // unused\n\n    test_object_t *test = get_test_object(obj_ptr);\n\n    // IID validity was checked by the `anjay_dm_list_instances_t` handler.\n    // If the Object Instance set does not change, or can only be modified\n    // via LwM2M Create/Delete requests, it is safe to assume IID is correct.\n    assert((size_t) iid < NUM_INSTANCES);\n    const struct test_instance *current_instance = &test->instances[iid];\n\n    // We have no Multiple-Instance Resources, so it is safe to assume\n    // that RIID is never set.\n    assert(riid == ANJAY_ID_INVALID);\n\n    switch (rid) {\n    case 0:\n        return anjay_ret_string(ctx, current_instance->label);\n    case 1:\n        return anjay_ret_i32(ctx, current_instance->value);\n    default:\n        // control will never reach this part due to test_list_resources\n        return ANJAY_ERR_INTERNAL;\n    }\n}\n\nstatic int test_resource_write(anjay_t *anjay,\n                               const anjay_dm_object_def_t *const *obj_ptr,\n                               anjay_iid_t iid,\n                               anjay_rid_t rid,\n                               anjay_riid_t riid,\n                               anjay_input_ctx_t *ctx) {\n    (void) anjay; // unused\n\n    test_object_t *test = get_test_object(obj_ptr);\n\n    // IID validity was checked by the `anjay_dm_list_instances_t` handler.\n    // If the Object Instance set does not change, or can only be modified\n    // via LwM2M Create/Delete requests, it is safe to assume IID is correct.\n    assert((size_t) iid < NUM_INSTANCES);\n    struct test_instance *current_instance = &test->instances[iid];\n\n    // We have no Multiple-Instance Resources, so it is safe to assume\n    // that RIID is never set.\n    assert(riid == ANJAY_ID_INVALID);\n\n    switch (rid) {\n    case 0: {\n        // `anjay_get_string` may return a chunk of data instead of the\n        // whole value - we need to make sure the client is able to hold\n        // the entire value\n        char buffer[sizeof(current_instance->label)];\n        int result = anjay_get_string(ctx, buffer, sizeof(buffer));\n\n        if (result == 0) {\n            // value OK - save it\n            memcpy(current_instance->label, buffer, sizeof(buffer));\n        } else if (result == ANJAY_BUFFER_TOO_SHORT) {\n            // the value is too long to store in the buffer\n            result = ANJAY_ERR_BAD_REQUEST;\n        }\n\n        return result;\n    }\n\n    case 1:\n        // reading primitive values can be done directly - the value will only\n        // be written to the output variable if everything went fine\n        return anjay_get_i32(ctx, &current_instance->value);\n\n    default:\n        // control will never reach this part due to test_list_resources\n        return ANJAY_ERR_INTERNAL;\n    }\n}\n\nstatic const anjay_dm_object_def_t OBJECT_DEF = {\n    // Object ID\n    .oid = 1234,\n\n    .handlers = {\n        .list_instances = test_list_instances,\n\n        .list_resources = test_list_resources,\n        .resource_read = test_resource_read,\n        .resource_write = test_resource_write,\n\n        .transaction_begin = anjay_dm_transaction_NOOP,\n        .transaction_validate = anjay_dm_transaction_NOOP,\n        .transaction_commit = anjay_dm_transaction_NOOP,\n        .transaction_rollback = anjay_dm_transaction_NOOP\n    }\n};\n\nstatic int setup_security_object(anjay_t *anjay) {\n    const anjay_security_instance_t security_instance = {\n        .ssid = 1,\n        .server_uri = \"coap://eu.iot.avsystem.cloud:5683\",\n        .security_mode = ANJAY_SECURITY_NOSEC\n    };\n\n    if (anjay_security_object_install(anjay)) {\n        return -1;\n    }\n\n    // let Anjay assign an Object Instance ID\n    anjay_iid_t security_instance_id = ANJAY_ID_INVALID;\n    if (anjay_security_object_add_instance(anjay, &security_instance,\n                                           &security_instance_id)) {\n        return -1;\n    }\n\n    return 0;\n}\n\nstatic int setup_server_object(anjay_t *anjay) {\n    const anjay_server_instance_t server_instance = {\n        .ssid = 1,\n        .lifetime = 86400,\n        .default_min_period = -1,\n        .default_max_period = -1,\n        .disable_timeout = -1,\n        .binding = \"U\"\n    };\n\n    if (anjay_server_object_install(anjay)) {\n        return -1;\n    }\n\n    anjay_iid_t server_instance_id = ANJAY_ID_INVALID;\n    if (anjay_server_object_add_instance(anjay, &server_instance,\n                                         &server_instance_id)) {\n        return -1;\n    }\n\n    return 0;\n}\n\nint main(int argc, char *argv[]) {\n    if (argc != 2) {\n        avs_log(tutorial, ERROR, \"usage: %s ENDPOINT_NAME\", argv[0]);\n        return -1;\n    }\n\n    const anjay_configuration_t CONFIG = {\n        .endpoint_name = argv[1],\n        .in_buffer_size = 4000,\n        .out_buffer_size = 4000\n    };\n\n    anjay_t *anjay = anjay_new(&CONFIG);\n    if (!anjay) {\n        return -1;\n    }\n\n    int result = 0;\n\n    if (setup_security_object(anjay) || setup_server_object(anjay)) {\n        result = -1;\n        goto cleanup;\n    }\n\n    // initialize and register the test object\n    test_object_t test_object = {\n        .obj_def = &OBJECT_DEF\n    };\n    for (size_t i = 0; i < NUM_INSTANCES; ++i) {\n        test_object.instances[i] = DEFAULT_INSTANCE_VALUES[i];\n    }\n\n    anjay_register_object(anjay, &test_object.obj_def);\n\n    result = anjay_event_loop_run(anjay,\n                                  avs_time_duration_from_scalar(1, AVS_TIME_S));\n\ncleanup:\n    anjay_delete(anjay);\n\n    // test object does not need cleanup\n    return result;\n}\n"
  },
  {
    "path": "examples/tutorial/AT-CustomObjects/writable-multiple-fixed-transactional/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.16)\nproject(multi-instance-writable-fixed-transactional C)\n\nset(CMAKE_C_STANDARD 99)\nset(CMAKE_C_EXTENSIONS OFF)\n\nfind_package(anjay REQUIRED)\n\nadd_executable(${PROJECT_NAME} src/main.c)\ntarget_link_libraries(${PROJECT_NAME} PRIVATE anjay)\n"
  },
  {
    "path": "examples/tutorial/AT-CustomObjects/writable-multiple-fixed-transactional/src/main.c",
    "content": "#include <assert.h>\n#include <string.h>\n\n#include <anjay/anjay.h>\n#include <anjay/security.h>\n#include <anjay/server.h>\n\n#include <avsystem/commons/avs_log.h>\n\ntypedef struct test_instance {\n    bool has_label;\n    char label[32];\n\n    bool has_value;\n    int32_t value;\n} test_instance_t;\n\nstatic const test_instance_t DEFAULT_INSTANCE_VALUES[] = {\n    { true, \"First\", true, 1 }, { true, \"Second\", true, 2 }\n};\n#define NUM_INSTANCES \\\n    (sizeof(DEFAULT_INSTANCE_VALUES) / sizeof(DEFAULT_INSTANCE_VALUES[0]))\n\ntypedef struct test_object {\n    // handlers\n    const anjay_dm_object_def_t *const obj_def;\n\n    // object state\n    test_instance_t instances[NUM_INSTANCES];\n\n    test_instance_t backup_instances[NUM_INSTANCES];\n} test_object_t;\n\nstatic test_object_t *get_test_object(const anjay_dm_object_def_t *const *obj) {\n    assert(obj);\n\n    // use the container_of pattern to retrieve test_object_t pointer\n    // AVS_CONTAINER_OF macro provided by avsystem/commons/defs.h\n    return AVS_CONTAINER_OF(obj, test_object_t, obj_def);\n}\n\nstatic int test_list_instances(anjay_t *anjay,\n                               const anjay_dm_object_def_t *const *obj_ptr,\n                               anjay_dm_list_ctx_t *ctx) {\n    (void) anjay;   // unused\n    (void) obj_ptr; // unused\n\n    for (anjay_iid_t iid = 0; iid < NUM_INSTANCES; ++iid) {\n        anjay_dm_emit(ctx, iid);\n    }\n\n    return 0;\n}\n\nstatic int test_instance_reset(anjay_t *anjay,\n                               const anjay_dm_object_def_t *const *obj_ptr,\n                               anjay_iid_t iid) {\n    (void) anjay; // unused\n\n    test_object_t *test = get_test_object(obj_ptr);\n\n    // IID validity was checked by the `anjay_dm_list_instances_t` handler.\n    // If the Object Instance set does not change, or can only be modified\n    // via LwM2M Create/Delete requests, it is safe to assume IID is correct.\n    assert((size_t) iid < NUM_INSTANCES);\n\n    // mark all Resource values for Object Instance `iid` as unset\n    test->instances[iid].has_label = false;\n    test->instances[iid].has_value = false;\n    return 0;\n}\n\nstatic int test_list_resources(anjay_t *anjay,\n                               const anjay_dm_object_def_t *const *obj_ptr,\n                               anjay_iid_t iid,\n                               anjay_dm_resource_list_ctx_t *ctx) {\n    (void) anjay;   // unused\n    (void) obj_ptr; // unused\n    (void) iid;     // unused\n\n    anjay_dm_emit_res(ctx, 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT);\n    return 0;\n}\n\nstatic int test_resource_read(anjay_t *anjay,\n                              const anjay_dm_object_def_t *const *obj_ptr,\n                              anjay_iid_t iid,\n                              anjay_rid_t rid,\n                              anjay_riid_t riid,\n                              anjay_output_ctx_t *ctx) {\n    (void) anjay; // unused\n\n    test_object_t *test = get_test_object(obj_ptr);\n\n    // IID validity was checked by the `anjay_dm_list_instances_t` handler.\n    // If the Object Instance set does not change, or can only be modified\n    // via LwM2M Create/Delete requests, it is safe to assume IID is correct.\n    assert((size_t) iid < NUM_INSTANCES);\n    const struct test_instance *current_instance = &test->instances[iid];\n\n    // We have no Multiple-Instance Resources, so it is safe to assume\n    // that RIID is never set.\n    assert(riid == ANJAY_ID_INVALID);\n\n    switch (rid) {\n    case 0:\n        return anjay_ret_string(ctx, current_instance->label);\n    case 1:\n        return anjay_ret_i32(ctx, current_instance->value);\n    default:\n        // control will never reach this part due to test_list_resources\n        return ANJAY_ERR_INTERNAL;\n    }\n}\n\nstatic int test_resource_write(anjay_t *anjay,\n                               const anjay_dm_object_def_t *const *obj_ptr,\n                               anjay_iid_t iid,\n                               anjay_rid_t rid,\n                               anjay_riid_t riid,\n                               anjay_input_ctx_t *ctx) {\n    (void) anjay; // unused\n\n    test_object_t *test = get_test_object(obj_ptr);\n\n    // IID validity was checked by the `anjay_dm_list_instances_t` handler.\n    // If the Object Instance set does not change, or can only be modified\n    // via LwM2M Create/Delete requests, it is safe to assume IID is correct.\n    assert((size_t) iid < NUM_INSTANCES);\n    struct test_instance *current_instance = &test->instances[iid];\n\n    // We have no Multiple-Instance Resources, so it is safe to assume\n    // that RIID is never set.\n    assert(riid == ANJAY_ID_INVALID);\n\n    switch (rid) {\n    case 0: {\n        // `anjay_get_string` may return a chunk of data instead of the\n        // whole value - we need to make sure the client is able to hold\n        // the entire value\n        char buffer[sizeof(current_instance->label)];\n        int result = anjay_get_string(ctx, buffer, sizeof(buffer));\n\n        if (result == 0) {\n            // value OK - save it\n            memcpy(current_instance->label, buffer, sizeof(buffer));\n            current_instance->has_label = true;\n        } else if (result == ANJAY_BUFFER_TOO_SHORT) {\n            // the value is too long to store in the buffer\n            result = ANJAY_ERR_BAD_REQUEST;\n        }\n\n        return result;\n    }\n\n    case 1: {\n        // reading primitive values can be done directly - the value will only\n        // be written to the output variable if everything went fine\n        int result = anjay_get_i32(ctx, &current_instance->value);\n        if (result == 0) {\n            current_instance->has_value = true;\n        }\n        return result;\n    }\n\n    default:\n        // control will never reach this part due to test_list_resources\n        return ANJAY_ERR_INTERNAL;\n    }\n}\n\nstatic int test_transaction_begin(anjay_t *anjay,\n                                  const anjay_dm_object_def_t *const *obj_ptr) {\n    (void) anjay; // unused\n\n    test_object_t *test = get_test_object(obj_ptr);\n\n    // store a snapshot of object state\n    memcpy(test->backup_instances, test->instances, sizeof(test->instances));\n    return 0;\n}\n\nstatic int\ntest_transaction_validate(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr) {\n    (void) anjay; // unused\n\n    test_object_t *test = get_test_object(obj_ptr);\n\n    // ensure all Object Instances contain all Mandatory Resources\n    for (size_t i = 0; i < NUM_INSTANCES; ++i) {\n        if (!test->instances[i].has_label || !test->instances[i].has_value) {\n            // validation failed: Object state invalid, rollback required\n            return ANJAY_ERR_BAD_REQUEST;\n        }\n    }\n\n    // validation successful, can commit\n    return 0;\n}\n\nstatic int\ntest_transaction_commit(anjay_t *anjay,\n                        const anjay_dm_object_def_t *const *obj_ptr) {\n    (void) anjay;   // unused\n    (void) obj_ptr; // unused\n\n    // no action required in this implementation; if object state snapshot was\n    // dynamically allocated, this would be the place for releasing it\n    return 0;\n}\n\nstatic int\ntest_transaction_rollback(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr) {\n    (void) anjay; // unused\n\n    test_object_t *test = get_test_object(obj_ptr);\n\n    // restore saved object state\n    memcpy(test->instances, test->backup_instances, sizeof(test->instances));\n    return 0;\n}\n\nstatic const anjay_dm_object_def_t OBJECT_DEF = {\n    // Object ID\n    .oid = 1234,\n\n    .handlers = {\n        .list_instances = test_list_instances,\n        .instance_reset = test_instance_reset,\n\n        .list_resources = test_list_resources,\n        .resource_read = test_resource_read,\n        .resource_write = test_resource_write,\n\n        .transaction_begin = test_transaction_begin,\n        .transaction_validate = test_transaction_validate,\n        .transaction_commit = test_transaction_commit,\n        .transaction_rollback = test_transaction_rollback\n    }\n};\n\nstatic int setup_security_object(anjay_t *anjay) {\n    const anjay_security_instance_t security_instance = {\n        .ssid = 1,\n        .server_uri = \"coap://eu.iot.avsystem.cloud:5683\",\n        .security_mode = ANJAY_SECURITY_NOSEC\n    };\n\n    if (anjay_security_object_install(anjay)) {\n        return -1;\n    }\n\n    // let Anjay assign an Object Instance ID\n    anjay_iid_t security_instance_id = ANJAY_ID_INVALID;\n    if (anjay_security_object_add_instance(anjay, &security_instance,\n                                           &security_instance_id)) {\n        return -1;\n    }\n\n    return 0;\n}\n\nstatic int setup_server_object(anjay_t *anjay) {\n    const anjay_server_instance_t server_instance = {\n        .ssid = 1,\n        .lifetime = 86400,\n        .default_min_period = -1,\n        .default_max_period = -1,\n        .disable_timeout = -1,\n        .binding = \"U\"\n    };\n\n    if (anjay_server_object_install(anjay)) {\n        return -1;\n    }\n\n    anjay_iid_t server_instance_id = ANJAY_ID_INVALID;\n    if (anjay_server_object_add_instance(anjay, &server_instance,\n                                         &server_instance_id)) {\n        return -1;\n    }\n\n    return 0;\n}\n\nint main(int argc, char *argv[]) {\n    if (argc != 2) {\n        avs_log(tutorial, ERROR, \"usage: %s ENDPOINT_NAME\", argv[0]);\n        return -1;\n    }\n\n    const anjay_configuration_t CONFIG = {\n        .endpoint_name = argv[1],\n        .in_buffer_size = 4000,\n        .out_buffer_size = 4000\n    };\n\n    anjay_t *anjay = anjay_new(&CONFIG);\n    if (!anjay) {\n        return -1;\n    }\n\n    int result = 0;\n\n    if (setup_security_object(anjay) || setup_server_object(anjay)) {\n        result = -1;\n        goto cleanup;\n    }\n\n    // initialize and register the test object\n    test_object_t test_object = {\n        .obj_def = &OBJECT_DEF\n    };\n    for (size_t i = 0; i < NUM_INSTANCES; ++i) {\n        test_object.instances[i] = DEFAULT_INSTANCE_VALUES[i];\n    }\n\n    anjay_register_object(anjay, &test_object.obj_def);\n\n    result = anjay_event_loop_run(anjay,\n                                  avs_time_duration_from_scalar(1, AVS_TIME_S));\n\ncleanup:\n    anjay_delete(anjay);\n\n    // test object does not need cleanup\n    return result;\n}\n"
  },
  {
    "path": "examples/tutorial/AT-Downloader/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.16)\nproject(downloader C)\n\nset(CMAKE_C_STANDARD 99)\nset(CMAKE_C_EXTENSIONS OFF)\n\nfind_package(anjay REQUIRED)\n\nadd_executable(${PROJECT_NAME} src/main.c)\ntarget_link_libraries(${PROJECT_NAME} anjay)\n"
  },
  {
    "path": "examples/tutorial/AT-Downloader/src/main.c",
    "content": "#include <anjay/anjay.h>\n#include <anjay/download.h>\n#include <avsystem/commons/avs_log.h>\n\n#include <stdio.h>\n#include <string.h>\n\n// This example uses hard-coded file path for simplicity.\nstatic const char DOWNLOAD_TARGET[] = \"/tmp/coap-download\";\n\nstatic avs_error_t coap_write_block(anjay_t *anjay,\n                                    const uint8_t *data,\n                                    size_t data_size,\n                                    const anjay_etag_t *etag,\n                                    void *file_) {\n    (void) anjay;\n    // ETag value can be saved to allow resuming the download later, in case\n    // where it could be interrupted at any point.\n    //\n    // To resume the download, one can pass `etag` and `start_offset` values\n    // in @ref anjay_download_config_t . If the downloaded file is still\n    // available, and its ETag did not change, the download will proceed as if\n    // no interruption happened.\n    //\n    // This example ignores ETag value for simplicity.\n    (void) etag;\n\n    FILE *file = (FILE *) file_;\n    if (fwrite(data, data_size, 1, file) != 1) {\n        avs_log(tutorial, ERROR, \"could not write file\");\n        return avs_errno(AVS_EIO);\n    }\n\n    return AVS_OK;\n}\n\nstatic void coap_download_finished(anjay_t *anjay,\n                                   anjay_download_status_t status,\n                                   void *file_) {\n    (void) anjay;\n\n    FILE *file = (FILE *) file_;\n    fclose(file);\n\n    if (status.result == ANJAY_DOWNLOAD_FINISHED) {\n        avs_log(tutorial, INFO, \"download complete: %s\", DOWNLOAD_TARGET);\n    } else {\n        avs_log(tutorial, ERROR, \"download failed: result = %d\",\n                (int) status.result);\n        remove(DOWNLOAD_TARGET);\n    }\n}\n\nstatic int request_coap_download(anjay_t *anjay,\n                                 const char *url,\n                                 const char *psk_identity,\n                                 const char *psk_key) {\n    FILE *file = fopen(DOWNLOAD_TARGET, \"wb\");\n    if (!file) {\n        avs_log(tutorial, ERROR, \"could not open file %s for writing\",\n                DOWNLOAD_TARGET);\n        return -1;\n    }\n\n    avs_net_psk_info_t psk = {\n        .key = avs_crypto_psk_key_info_from_buffer(psk_key, strlen(psk_key)),\n        .identity = avs_crypto_psk_identity_info_from_buffer(\n                psk_identity, strlen(psk_identity))\n    };\n\n    anjay_download_config_t cfg = {\n        .url = url,\n        .on_next_block = coap_write_block,\n        .on_download_finished = coap_download_finished,\n        .user_data = file,\n        .security_config = {\n            .security_info = avs_net_security_info_from_psk(psk)\n        }\n    };\n\n    anjay_download_handle_t handle;\n    if (avs_is_err(anjay_download(anjay, &cfg, &handle))) {\n        avs_log(tutorial, ERROR, \"could not schedule download: %s\", url);\n        // In case of anjay_download failure, cfg.user_data needs to be freed\n        fclose(file);\n        return -1;\n    }\n\n    // After anjay_download succeeds, cfg.on_download_finished is guaranteed\n    // to be called at some point. If cfg.user_data requires some cleanup or\n    // deallocation, it should be done in on_download_finished handler.\n    return 0;\n}\n\nint main(int argc, char *argv[]) {\n    if (argc != 2) {\n        avs_log(tutorial, ERROR, \"usage: %s ENDPOINT_NAME\", argv[0]);\n        return -1;\n    }\n\n    const anjay_configuration_t CONFIG = {\n        .endpoint_name = argv[1],\n        .in_buffer_size = 4000,\n        .out_buffer_size = 4000\n    };\n\n    anjay_t *anjay = anjay_new(&CONFIG);\n    if (!anjay) {\n        avs_log(tutorial, ERROR, \"Could not create Anjay object\");\n        return -1;\n    }\n    int result = 0;\n\n    // For simplicity, no LwM2M objects are installed. This application is\n    // unable to handle any LwM2M traffic.\n\n    if (request_coap_download(anjay, \"coaps://eu.iot.avsystem.cloud:5684/file\",\n                              \"psk_identity\", \"psk_key\")) {\n        result = -1;\n        goto cleanup;\n    }\n\n    result = anjay_event_loop_run(anjay,\n                                  avs_time_duration_from_scalar(1, AVS_TIME_S));\n\ncleanup:\n    anjay_delete(anjay);\n    return result;\n}\n"
  },
  {
    "path": "examples/tutorial/AT-IpsoObjects/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.16)\nproject(ipso-objects C)\n\nset(CMAKE_C_STANDARD 99)\nset(CMAKE_C_EXTENSIONS OFF)\n\nfind_package(anjay REQUIRED)\n\nadd_executable(${PROJECT_NAME} src/main.c)\ntarget_link_libraries(${PROJECT_NAME} PRIVATE anjay)\n"
  },
  {
    "path": "examples/tutorial/AT-IpsoObjects/src/main.c",
    "content": "#include <stdlib.h>\n\n#include <anjay/anjay.h>\n#include <anjay/ipso_objects.h>\n#include <anjay/ipso_objects_v2.h>\n#include <anjay/security.h>\n#include <anjay/server.h>\n\n#include <avsystem/commons/avs_log.h>\n#include <avsystem/commons/avs_sched.h>\n\n#define TEMPERATURE_OBJ_OID 3303\n#define ACCELEROMETER_OBJ_OID 3313\n\n#define THERMOMETER_COUNT 3\n#define ACCELEROMETER_COUNT 2\n#define BUTTON_COUNT 4\n\nstatic const anjay_ipso_v2_basic_sensor_meta_t thermometer_meta = {\n    .unit = \"Cel\",\n    .min_max_measured_value_present = true,\n    .min_range_value = -20.0,\n    .max_range_value = 120.0\n};\n\nstatic const anjay_ipso_v2_3d_sensor_meta_t accelerometer_meta = {\n    .unit = \"m/s2\",\n    .min_range_value = -20.0,\n    .max_range_value = 20.0,\n    .y_axis_present = true,\n    .z_axis_present = true\n};\n\nstatic double get_random_in_range(double min, double max) {\n    return min + (max - min) * rand() / RAND_MAX;\n}\n\nstatic double get_thermometer_value(void) {\n    return get_random_in_range(thermometer_meta.min_range_value,\n                               thermometer_meta.max_range_value);\n}\n\nstatic anjay_ipso_v2_3d_sensor_value_t get_accelerometer_value(void) {\n    return (anjay_ipso_v2_3d_sensor_value_t) {\n        .x = get_random_in_range(accelerometer_meta.min_range_value,\n                                 accelerometer_meta.max_range_value),\n        .y = get_random_in_range(accelerometer_meta.min_range_value,\n                                 accelerometer_meta.max_range_value),\n        .z = get_random_in_range(accelerometer_meta.min_range_value,\n                                 accelerometer_meta.max_range_value)\n    };\n}\n\nstatic bool get_button_state(void) {\n    return rand() % 2 == 0;\n}\n\nstatic int setup_temperature_object(anjay_t *anjay) {\n    if (anjay_ipso_v2_basic_sensor_install(anjay, TEMPERATURE_OBJ_OID, NULL,\n                                           THERMOMETER_COUNT)) {\n        return -1;\n    }\n\n    for (anjay_iid_t iid = 0; iid < THERMOMETER_COUNT; iid++) {\n        if (anjay_ipso_v2_basic_sensor_instance_add(\n                    anjay, TEMPERATURE_OBJ_OID, iid, 20.0, &thermometer_meta)) {\n            return -1;\n        }\n    }\n\n    return 0;\n}\n\nstatic int setup_accelerometer_object(anjay_t *anjay) {\n    if (anjay_ipso_v2_3d_sensor_install(anjay, ACCELEROMETER_OBJ_OID, NULL,\n                                        ACCELEROMETER_COUNT)) {\n        return -1;\n    }\n\n    for (anjay_iid_t iid = 0; iid < ACCELEROMETER_COUNT; iid++) {\n        anjay_ipso_v2_3d_sensor_value_t initial_value = {\n            .x = 0.0,\n            .y = 0.0,\n            .z = 0.0\n        };\n\n        if (anjay_ipso_v2_3d_sensor_instance_add(anjay, ACCELEROMETER_OBJ_OID,\n                                                 iid, &initial_value,\n                                                 &accelerometer_meta)) {\n            return -1;\n        }\n    }\n\n    return 0;\n}\n\nstatic int setup_button_object(anjay_t *anjay) {\n    if (anjay_ipso_button_install(anjay, BUTTON_COUNT)) {\n        return -1;\n    }\n\n    for (anjay_iid_t iid = 0; iid < BUTTON_COUNT; iid++) {\n        if (anjay_ipso_button_instance_add(anjay, iid, \"\")) {\n            return -1;\n        }\n    }\n\n    return 0;\n}\n\nstatic int setup_security_object(anjay_t *anjay) {\n    const anjay_security_instance_t security_instance = {\n        .ssid = 1,\n        .server_uri = \"coap://eu.iot.avsystem.cloud:5683\",\n        .security_mode = ANJAY_SECURITY_NOSEC\n    };\n\n    if (anjay_security_object_install(anjay)) {\n        return -1;\n    }\n\n    // let Anjay assign an Object Instance ID\n    anjay_iid_t security_instance_id = ANJAY_ID_INVALID;\n    if (anjay_security_object_add_instance(anjay, &security_instance,\n                                           &security_instance_id)) {\n        return -1;\n    }\n\n    return 0;\n}\n\nstatic int setup_server_object(anjay_t *anjay) {\n    const anjay_server_instance_t server_instance = {\n        .ssid = 1,\n        .lifetime = 60,\n        .default_min_period = -1,\n        .default_max_period = -1,\n        .disable_timeout = -1,\n        .binding = \"U\"\n    };\n\n    if (anjay_server_object_install(anjay)) {\n        return -1;\n    }\n\n    anjay_iid_t server_instance_id = ANJAY_ID_INVALID;\n    if (anjay_server_object_add_instance(anjay, &server_instance,\n                                         &server_instance_id)) {\n        return -1;\n    }\n\n    return 0;\n}\n\nstatic void update_sensor_values(avs_sched_t *sched, const void *anjay_ptr) {\n    anjay_t *anjay = *(anjay_t *const *) anjay_ptr;\n\n    for (anjay_iid_t iid = 0; iid < THERMOMETER_COUNT; iid++) {\n        (void) anjay_ipso_v2_basic_sensor_value_update(\n                anjay, TEMPERATURE_OBJ_OID, iid, get_thermometer_value());\n    }\n\n    for (anjay_iid_t iid = 0; iid < ACCELEROMETER_COUNT; iid++) {\n        anjay_ipso_v2_3d_sensor_value_t value = get_accelerometer_value();\n\n        (void) anjay_ipso_v2_3d_sensor_value_update(\n                anjay, ACCELEROMETER_OBJ_OID, iid, &value);\n    }\n\n    for (anjay_iid_t iid = 0; iid < BUTTON_COUNT; iid++) {\n        (void) anjay_ipso_button_update(anjay, iid, get_button_state());\n    }\n\n    AVS_SCHED_DELAYED(sched, NULL, avs_time_duration_from_scalar(1, AVS_TIME_S),\n                      update_sensor_values, &anjay, sizeof(anjay));\n}\n\nint main(int argc, char *argv[]) {\n    if (argc != 2) {\n        avs_log(tutorial, ERROR, \"usage: %s ENDPOINT_NAME\", argv[0]);\n        return -1;\n    }\n\n    const anjay_configuration_t CONFIG = {\n        .endpoint_name = argv[1],\n        .in_buffer_size = 4000,\n        .out_buffer_size = 4000\n    };\n\n    anjay_t *anjay = anjay_new(&CONFIG);\n    if (!anjay) {\n        return -1;\n    }\n\n    int result = 0;\n\n    if (setup_security_object(anjay) || setup_server_object(anjay)\n            || setup_temperature_object(anjay)\n            || setup_accelerometer_object(anjay)\n            || setup_button_object(anjay)) {\n        result = -1;\n    }\n\n    if (!result) {\n        update_sensor_values(anjay_get_scheduler(anjay), &anjay);\n        result = anjay_event_loop_run(\n                anjay, avs_time_duration_from_scalar(1, AVS_TIME_S));\n    }\n\n    anjay_delete(anjay);\n    return result;\n}\n"
  },
  {
    "path": "examples/tutorial/AT-Persistence/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.16)\nproject(persistence C)\n\nset(CMAKE_C_STANDARD 99)\nset(CMAKE_C_EXTENSIONS OFF)\n\nfind_package(anjay REQUIRED)\n\nadd_executable(${PROJECT_NAME} src/main.c)\ntarget_link_libraries(${PROJECT_NAME} PRIVATE anjay)\n"
  },
  {
    "path": "examples/tutorial/AT-Persistence/src/main.c",
    "content": "#include <anjay/anjay.h>\n#include <anjay/attr_storage.h>\n#include <anjay/security.h>\n#include <anjay/server.h>\n#include <avsystem/commons/avs_log.h>\n#include <avsystem/commons/avs_stream_file.h>\n\n#include <errno.h>\n#include <signal.h>\n#include <unistd.h>\n\nstatic anjay_t *volatile g_anjay;\n\nvoid signal_handler(int signum) {\n    if (signum == SIGINT && g_anjay) {\n        anjay_event_loop_interrupt(g_anjay);\n    }\n}\n\n#define PERSISTENCE_FILENAME \"at2-persistence.dat\"\n\nint persist_objects(anjay_t *anjay) {\n    avs_log(tutorial, INFO, \"Persisting objects to %s\", PERSISTENCE_FILENAME);\n\n    avs_stream_t *file_stream =\n            avs_stream_file_create(PERSISTENCE_FILENAME, AVS_STREAM_FILE_WRITE);\n\n    if (!file_stream) {\n        avs_log(tutorial, ERROR, \"Could not open file for writing\");\n        return -1;\n    }\n\n    int result = -1;\n\n    if (avs_is_err(anjay_security_object_persist(anjay, file_stream))) {\n        avs_log(tutorial, ERROR, \"Could not persist Security Object\");\n        goto finish;\n    }\n\n    if (avs_is_err(anjay_server_object_persist(anjay, file_stream))) {\n        avs_log(tutorial, ERROR, \"Could not persist Server Object\");\n        goto finish;\n    }\n\n    if (avs_is_err(anjay_attr_storage_persist(anjay, file_stream))) {\n        avs_log(tutorial, ERROR, \"Could not persist LwM2M attribute storage\");\n        goto finish;\n    }\n\n    result = 0;\nfinish:\n    avs_stream_cleanup(&file_stream);\n    return result;\n}\n\nint restore_objects_if_possible(anjay_t *anjay) {\n    avs_log(tutorial, INFO, \"Attempting to restore objects from persistence\");\n    int result;\n\n    errno = 0;\n    if ((result = access(PERSISTENCE_FILENAME, F_OK))) {\n        switch (errno) {\n        case ENOENT:\n        case ENOTDIR:\n            // no persistence file means there is nothing to restore\n            return 1;\n        default:\n            // some other unpredicted error\n            return result;\n        }\n    } else if ((result = access(PERSISTENCE_FILENAME, R_OK))) {\n        // most likely file is just not readable\n        return result;\n    }\n\n    avs_stream_t *file_stream =\n            avs_stream_file_create(PERSISTENCE_FILENAME, AVS_STREAM_FILE_READ);\n\n    if (!file_stream) {\n        return -1;\n    }\n\n    result = -1;\n\n    if (avs_is_err(anjay_security_object_restore(anjay, file_stream))) {\n        avs_log(tutorial, ERROR, \"Could not restore Security Object\");\n        goto finish;\n    }\n\n    if (avs_is_err(anjay_server_object_restore(anjay, file_stream))) {\n        avs_log(tutorial, ERROR, \"Could not restore Server Object\");\n        goto finish;\n    }\n\n    if (avs_is_err(anjay_attr_storage_restore(anjay, file_stream))) {\n        avs_log(tutorial, ERROR, \"Could not restore LwM2M attribute storage\");\n        goto finish;\n    }\n\n    result = 0;\nfinish:\n    avs_stream_cleanup(&file_stream);\n    return result;\n}\n\nvoid initialize_objects_with_default_settings(anjay_t *anjay) {\n    const anjay_security_instance_t security_instance = {\n        .ssid = 1,\n        .server_uri = \"coap://eu.iot.avsystem.cloud:5683\",\n        .security_mode = ANJAY_SECURITY_NOSEC\n    };\n\n    const anjay_server_instance_t server_instance = {\n        .ssid = 1,\n        .lifetime = 86400,\n        .default_min_period = -1,\n        .default_max_period = -1,\n        .disable_timeout = -1,\n        .binding = \"U\"\n    };\n\n    anjay_iid_t security_instance_id = ANJAY_ID_INVALID;\n    anjay_iid_t server_instance_id = ANJAY_ID_INVALID;\n    anjay_security_object_add_instance(anjay, &security_instance,\n                                       &security_instance_id);\n    anjay_server_object_add_instance(anjay, &server_instance,\n                                     &server_instance_id);\n}\n\nint main(int argc, char *argv[]) {\n    if (argc != 2) {\n        avs_log(tutorial, ERROR, \"usage: %s ENDPOINT_NAME\", argv[0]);\n        return -1;\n    }\n\n    signal(SIGINT, signal_handler);\n\n    const anjay_configuration_t CONFIG = {\n        .endpoint_name = argv[1],\n        .in_buffer_size = 4000,\n        .out_buffer_size = 4000\n    };\n\n    g_anjay = anjay_new(&CONFIG);\n    if (!g_anjay) {\n        avs_log(tutorial, ERROR, \"Could not create Anjay object\");\n        return -1;\n    }\n\n    int result = 0;\n\n    // Setup necessary objects\n    if (anjay_security_object_install(g_anjay)\n            || anjay_server_object_install(g_anjay)) {\n        result = -1;\n        goto cleanup;\n    }\n\n    int restore_retval = restore_objects_if_possible(g_anjay);\n    if (restore_retval < 0) {\n        result = -1;\n        goto cleanup;\n    } else if (restore_retval > 0) {\n        initialize_objects_with_default_settings(g_anjay);\n    }\n\n    result = anjay_event_loop_run(g_anjay,\n                                  avs_time_duration_from_scalar(1, AVS_TIME_S));\n\n    int persist_result = persist_objects(g_anjay);\n    if (!result) {\n        result = persist_result;\n    }\n\ncleanup:\n    anjay_delete(g_anjay);\n    return result;\n}\n"
  },
  {
    "path": "examples/tutorial/BC-Initialization/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.16)\nproject(anjay-bc-initialization C)\n\nset(CMAKE_C_STANDARD 99)\nset(CMAKE_C_EXTENSIONS OFF)\n\nadd_compile_options(-Wall -Wextra)\n\nfind_package(anjay REQUIRED)\n\nadd_executable(${PROJECT_NAME} src/main.c)\ntarget_link_libraries(${PROJECT_NAME} PRIVATE anjay)\n"
  },
  {
    "path": "examples/tutorial/BC-Initialization/src/main.c",
    "content": "#include <anjay/anjay.h>\n#include <avsystem/commons/avs_log.h>\n\nint main(int argc, char *argv[]) {\n    if (argc != 2) {\n        avs_log(tutorial, ERROR, \"usage: %s ENDPOINT_NAME\", argv[0]);\n        return -1;\n    }\n\n    const anjay_configuration_t CONFIG = {\n        .endpoint_name = argv[1],\n        .in_buffer_size = 4000,\n        .out_buffer_size = 4000,\n        .msg_cache_size = 4000\n    };\n\n    anjay_t *anjay = anjay_new(&CONFIG);\n    if (!anjay) {\n        avs_log(tutorial, ERROR, \"Could not create Anjay object\");\n        return -1;\n    }\n\n    anjay_event_loop_run(anjay, avs_time_duration_from_scalar(1, AVS_TIME_S));\n\n    anjay_delete(anjay);\n    return 0;\n}\n"
  },
  {
    "path": "examples/tutorial/BC-MandatoryObjects/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.16)\nproject(anjay-bc-mandatory-objects C)\n\nset(CMAKE_C_STANDARD 99)\nset(CMAKE_C_EXTENSIONS OFF)\n\nadd_compile_options(-Wall -Wextra)\n\nfind_package(anjay REQUIRED)\n\nadd_executable(${PROJECT_NAME} src/main.c)\ntarget_link_libraries(${PROJECT_NAME} PRIVATE anjay)\n"
  },
  {
    "path": "examples/tutorial/BC-MandatoryObjects/src/main.c",
    "content": "#include <anjay/anjay.h>\n#include <anjay/security.h>\n#include <anjay/server.h>\n#include <avsystem/commons/avs_log.h>\n\n// Installs Security Object and adds an instance of it.\n// An instance of Security Object provides information needed to connect to\n// LwM2M server.\nstatic int setup_security_object(anjay_t *anjay) {\n    if (anjay_security_object_install(anjay)) {\n        return -1;\n    }\n\n    const anjay_security_instance_t security_instance = {\n        .ssid = 1,\n        .server_uri = \"coap://eu.iot.avsystem.cloud:5683\",\n        .security_mode = ANJAY_SECURITY_NOSEC\n    };\n\n    // Anjay will assign Instance ID automatically\n    anjay_iid_t security_instance_id = ANJAY_ID_INVALID;\n    if (anjay_security_object_add_instance(anjay, &security_instance,\n                                           &security_instance_id)) {\n        return -1;\n    }\n\n    return 0;\n}\n\n// Installs Server Object and adds an instance of it.\n// An instance of Server Object provides the data related to a LwM2M Server.\nstatic int setup_server_object(anjay_t *anjay) {\n    if (anjay_server_object_install(anjay)) {\n        return -1;\n    }\n\n    const anjay_server_instance_t server_instance = {\n        // Server Short ID\n        .ssid = 1,\n        // Client will send Update message often than every 60 seconds\n        .lifetime = 60,\n        // Disable Default Minimum Period resource\n        .default_min_period = -1,\n        // Disable Default Maximum Period resource\n        .default_max_period = -1,\n        // Disable Disable Timeout resource\n        .disable_timeout = -1,\n        // Sets preferred transport to UDP\n        .binding = \"U\"\n    };\n\n    // Anjay will assign Instance ID automatically\n    anjay_iid_t server_instance_id = ANJAY_ID_INVALID;\n    if (anjay_server_object_add_instance(anjay, &server_instance,\n                                         &server_instance_id)) {\n        return -1;\n    }\n\n    return 0;\n}\n\nint main(int argc, char *argv[]) {\n    if (argc != 2) {\n        avs_log(tutorial, ERROR, \"usage: %s ENDPOINT_NAME\", argv[0]);\n        return -1;\n    }\n\n    const anjay_configuration_t CONFIG = {\n        .endpoint_name = argv[1],\n        .in_buffer_size = 4000,\n        .out_buffer_size = 4000,\n        .msg_cache_size = 4000\n    };\n\n    anjay_t *anjay = anjay_new(&CONFIG);\n    if (!anjay) {\n        avs_log(tutorial, ERROR, \"Could not create Anjay object\");\n        return -1;\n    }\n\n    int result = 0;\n    // Setup necessary objects\n    if (setup_security_object(anjay) || setup_server_object(anjay)) {\n        result = -1;\n    }\n\n    if (!result) {\n        result = anjay_event_loop_run(\n                anjay, avs_time_duration_from_scalar(1, AVS_TIME_S));\n    }\n\n    anjay_delete(anjay);\n    return result;\n}\n"
  },
  {
    "path": "examples/tutorial/BC-Notifications/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.16)\nproject(anjay-bc-notifications C)\n\nset(CMAKE_C_STANDARD 99)\nset(CMAKE_C_EXTENSIONS OFF)\n\nadd_compile_options(-Wall -Wextra)\n\nfind_package(anjay REQUIRED)\n\nadd_executable(${PROJECT_NAME}\n               src/main.c\n               src/time_object.h\n               src/time_object.c)\ntarget_link_libraries(${PROJECT_NAME} PRIVATE anjay)\n"
  },
  {
    "path": "examples/tutorial/BC-Notifications/src/main.c",
    "content": "#include <anjay/anjay.h>\n#include <anjay/security.h>\n#include <anjay/server.h>\n#include <avsystem/commons/avs_log.h>\n\n#include \"time_object.h\"\n\ntypedef struct {\n    anjay_t *anjay;\n    const anjay_dm_object_def_t **time_object;\n} notify_job_args_t;\n\n// Periodically notifies the library about Resource value changes\nstatic void notify_job(avs_sched_t *sched, const void *args_ptr) {\n    const notify_job_args_t *args = (const notify_job_args_t *) args_ptr;\n\n    time_object_notify(args->anjay, args->time_object);\n\n    // Schedule run of the same function after 1 second\n    AVS_SCHED_DELAYED(sched, NULL, avs_time_duration_from_scalar(1, AVS_TIME_S),\n                      notify_job, args, sizeof(*args));\n}\n\n// Installs Security Object and adds an instance of it.\n// An instance of Security Object provides information needed to connect to\n// LwM2M server.\nstatic int setup_security_object(anjay_t *anjay) {\n    if (anjay_security_object_install(anjay)) {\n        return -1;\n    }\n\n    static const char PSK_IDENTITY[] = \"identity\";\n    static const char PSK_KEY[] = \"P4s$w0rd\";\n\n    anjay_security_instance_t security_instance = {\n        .ssid = 1,\n        .server_uri = \"coaps://eu.iot.avsystem.cloud:5684\",\n        .security_mode = ANJAY_SECURITY_PSK,\n        .public_cert_or_psk_identity = (const uint8_t *) PSK_IDENTITY,\n        .public_cert_or_psk_identity_size = strlen(PSK_IDENTITY),\n        .private_cert_or_psk_key = (const uint8_t *) PSK_KEY,\n        .private_cert_or_psk_key_size = strlen(PSK_KEY)\n    };\n\n    // Anjay will assign Instance ID automatically\n    anjay_iid_t security_instance_id = ANJAY_ID_INVALID;\n    if (anjay_security_object_add_instance(anjay, &security_instance,\n                                           &security_instance_id)) {\n        return -1;\n    }\n\n    return 0;\n}\n\n// Installs Server Object and adds an instance of it.\n// An instance of Server Object provides the data related to a LwM2M Server.\nstatic int setup_server_object(anjay_t *anjay) {\n    if (anjay_server_object_install(anjay)) {\n        return -1;\n    }\n\n    const anjay_server_instance_t server_instance = {\n        // Server Short ID\n        .ssid = 1,\n        // Client will send Update message often than every 60 seconds\n        .lifetime = 60,\n        // Disable Default Minimum Period resource\n        .default_min_period = -1,\n        // Disable Default Maximum Period resource\n        .default_max_period = -1,\n        // Disable Disable Timeout resource\n        .disable_timeout = -1,\n        // Sets preferred transport to UDP\n        .binding = \"U\"\n    };\n\n    // Anjay will assign Instance ID automatically\n    anjay_iid_t server_instance_id = ANJAY_ID_INVALID;\n    if (anjay_server_object_add_instance(anjay, &server_instance,\n                                         &server_instance_id)) {\n        return -1;\n    }\n\n    return 0;\n}\n\nint main(int argc, char *argv[]) {\n    if (argc != 2) {\n        avs_log(tutorial, ERROR, \"usage: %s ENDPOINT_NAME\", argv[0]);\n        return -1;\n    }\n\n    const anjay_configuration_t CONFIG = {\n        .endpoint_name = argv[1],\n        .in_buffer_size = 4000,\n        .out_buffer_size = 4000,\n        .msg_cache_size = 4000\n    };\n\n    anjay_t *anjay = anjay_new(&CONFIG);\n    if (!anjay) {\n        avs_log(tutorial, ERROR, \"Could not create Anjay object\");\n        return -1;\n    }\n\n    int result = 0;\n    // Setup necessary objects\n    if (setup_security_object(anjay) || setup_server_object(anjay)) {\n        result = -1;\n    }\n\n    const anjay_dm_object_def_t **time_object = NULL;\n    if (!result) {\n        time_object = time_object_create();\n        if (time_object) {\n            result = anjay_register_object(anjay, time_object);\n        } else {\n            result = -1;\n        }\n    }\n\n    if (!result) {\n        // Run notify_job the first time;\n        // this will schedule periodic calls to itself via the scheduler\n        notify_job(anjay_get_scheduler(anjay), &(const notify_job_args_t) {\n                                                   .anjay = anjay,\n                                                   .time_object = time_object\n                                               });\n\n        result = anjay_event_loop_run(\n                anjay, avs_time_duration_from_scalar(1, AVS_TIME_S));\n    }\n\n    anjay_delete(anjay);\n    time_object_release(time_object);\n    return result;\n}\n"
  },
  {
    "path": "examples/tutorial/BC-Notifications/src/time_object.c",
    "content": "/**\n * Generated by anjay_codegen.py on 2020-03-19 14:13:34\n *\n * LwM2M Object: Time\n * ID: 3333, URN: urn:oma:lwm2m:ext:3333, Optional, Multiple\n *\n * This IPSO object is used to report the current time in seconds since\n * January 1, 1970 UTC. There is also a fractional time counter that has\n * a range of less than one second.\n */\n\n#include <assert.h>\n#include <stdbool.h>\n\n#include <anjay/anjay.h>\n#include <avsystem/commons/avs_defs.h>\n#include <avsystem/commons/avs_list.h>\n#include <avsystem/commons/avs_memory.h>\n\n#include \"time_object.h\"\n\n/**\n * Current Time: RW, Single, Mandatory\n * type: time, range: N/A, unit: N/A\n * Unix Time. A signed integer representing the number of seconds since\n * Jan 1st, 1970 in the UTC time zone.\n */\n#define RID_CURRENT_TIME 5506\n\n/**\n * Fractional Time: RW, Single, Optional\n * type: float, range: 0..1, unit: s\n * Fractional part of the time when sub-second precision is used (e.g.,\n * 0.23 for 230 ms).\n */\n#define RID_FRACTIONAL_TIME 5507\n\n/**\n * Application Type: RW, Single, Optional\n * type: string, range: N/A, unit: N/A\n * The application type of the sensor or actuator as a string depending\n * on the use case.\n */\n#define RID_APPLICATION_TYPE 5750\n\ntypedef struct time_instance_struct {\n    anjay_iid_t iid;\n    char application_type[64];\n    char application_type_backup[64];\n    int64_t last_notify_timestamp;\n} time_instance_t;\n\ntypedef struct time_object_struct {\n    const anjay_dm_object_def_t *def;\n    AVS_LIST(time_instance_t) instances;\n} time_object_t;\n\nstatic inline time_object_t *\nget_obj(const anjay_dm_object_def_t *const *obj_ptr) {\n    assert(obj_ptr);\n    return AVS_CONTAINER_OF(obj_ptr, time_object_t, def);\n}\n\nstatic time_instance_t *find_instance(const time_object_t *obj,\n                                      anjay_iid_t iid) {\n    AVS_LIST(time_instance_t) it;\n    AVS_LIST_FOREACH(it, obj->instances) {\n        if (it->iid == iid) {\n            return it;\n        } else if (it->iid > iid) {\n            break;\n        }\n    }\n\n    return NULL;\n}\n\nstatic int list_instances(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr,\n                          anjay_dm_list_ctx_t *ctx) {\n    (void) anjay;\n\n    AVS_LIST(time_instance_t) it;\n    AVS_LIST_FOREACH(it, get_obj(obj_ptr)->instances) {\n        anjay_dm_emit(ctx, it->iid);\n    }\n\n    return 0;\n}\n\nstatic int init_instance(time_instance_t *inst, anjay_iid_t iid) {\n    assert(iid != ANJAY_ID_INVALID);\n\n    inst->iid = iid;\n    inst->application_type[0] = '\\0';\n\n    return 0;\n}\n\nstatic void release_instance(time_instance_t *inst) {\n    (void) inst;\n}\n\nstatic time_instance_t *add_instance(time_object_t *obj, anjay_iid_t iid) {\n    assert(find_instance(obj, iid) == NULL);\n\n    AVS_LIST(time_instance_t) created = AVS_LIST_NEW_ELEMENT(time_instance_t);\n    if (!created) {\n        return NULL;\n    }\n\n    int result = init_instance(created, iid);\n    if (result) {\n        AVS_LIST_CLEAR(&created);\n        return NULL;\n    }\n\n    AVS_LIST(time_instance_t) *ptr;\n    AVS_LIST_FOREACH_PTR(ptr, &obj->instances) {\n        if ((*ptr)->iid > created->iid) {\n            break;\n        }\n    }\n\n    AVS_LIST_INSERT(ptr, created);\n    return created;\n}\n\nstatic int instance_create(anjay_t *anjay,\n                           const anjay_dm_object_def_t *const *obj_ptr,\n                           anjay_iid_t iid) {\n    (void) anjay;\n    time_object_t *obj = get_obj(obj_ptr);\n\n    return add_instance(obj, iid) ? 0 : ANJAY_ERR_INTERNAL;\n}\n\nstatic int instance_remove(anjay_t *anjay,\n                           const anjay_dm_object_def_t *const *obj_ptr,\n                           anjay_iid_t iid) {\n    (void) anjay;\n    time_object_t *obj = get_obj(obj_ptr);\n\n    AVS_LIST(time_instance_t) *it;\n    AVS_LIST_FOREACH_PTR(it, &obj->instances) {\n        if ((*it)->iid == iid) {\n            release_instance(*it);\n            AVS_LIST_DELETE(it);\n            return 0;\n        } else if ((*it)->iid > iid) {\n            break;\n        }\n    }\n\n    assert(0);\n    return ANJAY_ERR_NOT_FOUND;\n}\n\nstatic int instance_reset(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr,\n                          anjay_iid_t iid) {\n    (void) anjay;\n\n    time_object_t *obj = get_obj(obj_ptr);\n    time_instance_t *inst = find_instance(obj, iid);\n    assert(inst);\n\n    inst->application_type[0] = '\\0';\n\n    return 0;\n}\n\nstatic int list_resources(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr,\n                          anjay_iid_t iid,\n                          anjay_dm_resource_list_ctx_t *ctx) {\n    (void) anjay;\n    (void) obj_ptr;\n    (void) iid;\n\n    anjay_dm_emit_res(ctx, RID_CURRENT_TIME, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, RID_FRACTIONAL_TIME, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT);\n    anjay_dm_emit_res(ctx, RID_APPLICATION_TYPE, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_PRESENT);\n    return 0;\n}\n\nstatic int resource_read(anjay_t *anjay,\n                         const anjay_dm_object_def_t *const *obj_ptr,\n                         anjay_iid_t iid,\n                         anjay_rid_t rid,\n                         anjay_riid_t riid,\n                         anjay_output_ctx_t *ctx) {\n    (void) anjay;\n\n    time_object_t *obj = get_obj(obj_ptr);\n    time_instance_t *inst = find_instance(obj, iid);\n    assert(inst);\n\n    switch (rid) {\n    case RID_CURRENT_TIME: {\n        assert(riid == ANJAY_ID_INVALID);\n        int64_t timestamp;\n        if (avs_time_real_to_scalar(&timestamp, AVS_TIME_S,\n                                    avs_time_real_now())) {\n            return -1;\n        }\n        return anjay_ret_i64(ctx, timestamp);\n    }\n\n    case RID_APPLICATION_TYPE:\n        assert(riid == ANJAY_ID_INVALID);\n        return anjay_ret_string(ctx, inst->application_type);\n\n    default:\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\nstatic int resource_write(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr,\n                          anjay_iid_t iid,\n                          anjay_rid_t rid,\n                          anjay_riid_t riid,\n                          anjay_input_ctx_t *ctx) {\n    (void) anjay;\n\n    time_object_t *obj = get_obj(obj_ptr);\n    time_instance_t *inst = find_instance(obj, iid);\n    assert(inst);\n\n    switch (rid) {\n    case RID_APPLICATION_TYPE:\n        assert(riid == ANJAY_ID_INVALID);\n        return anjay_get_string(ctx, inst->application_type,\n                                sizeof(inst->application_type));\n\n    default:\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\nint transaction_begin(anjay_t *anjay,\n                      const anjay_dm_object_def_t *const *obj_ptr) {\n    (void) anjay;\n\n    time_object_t *obj = get_obj(obj_ptr);\n\n    time_instance_t *element;\n    AVS_LIST_FOREACH(element, obj->instances) {\n        strcpy(element->application_type_backup, element->application_type);\n    }\n    return 0;\n}\n\nint transaction_rollback(anjay_t *anjay,\n                         const anjay_dm_object_def_t *const *obj_ptr) {\n    (void) anjay;\n\n    time_object_t *obj = get_obj(obj_ptr);\n\n    time_instance_t *element;\n    AVS_LIST_FOREACH(element, obj->instances) {\n        strcpy(element->application_type, element->application_type_backup);\n    }\n    return 0;\n}\n\nstatic const anjay_dm_object_def_t OBJ_DEF = {\n    .oid = 3333,\n    .handlers = {\n        .list_instances = list_instances,\n        .instance_create = instance_create,\n        .instance_remove = instance_remove,\n        .instance_reset = instance_reset,\n\n        .list_resources = list_resources,\n        .resource_read = resource_read,\n        .resource_write = resource_write,\n\n        .transaction_begin = transaction_begin,\n        .transaction_validate = anjay_dm_transaction_NOOP,\n        .transaction_commit = anjay_dm_transaction_NOOP,\n        .transaction_rollback = transaction_rollback\n    }\n};\n\nconst anjay_dm_object_def_t **time_object_create(void) {\n    time_object_t *obj = (time_object_t *) avs_calloc(1, sizeof(time_object_t));\n    if (!obj) {\n        return NULL;\n    }\n    obj->def = &OBJ_DEF;\n\n    time_instance_t *inst = add_instance(obj, 0);\n    if (inst) {\n        strcpy(inst->application_type, \"Clock 0\");\n    } else {\n        avs_free(obj);\n        return NULL;\n    }\n\n    return &obj->def;\n}\n\nvoid time_object_release(const anjay_dm_object_def_t **def) {\n    if (def) {\n        time_object_t *obj = get_obj(def);\n        AVS_LIST_CLEAR(&obj->instances) {\n            release_instance(obj->instances);\n        }\n\n        avs_free(obj);\n    }\n}\n\nvoid time_object_notify(anjay_t *anjay, const anjay_dm_object_def_t **def) {\n    if (!anjay || !def) {\n        return;\n    }\n    time_object_t *obj = get_obj(def);\n\n    int64_t current_timestamp;\n    if (avs_time_real_to_scalar(&current_timestamp, AVS_TIME_S,\n                                avs_time_real_now())) {\n        return;\n    }\n\n    AVS_LIST(time_instance_t) it;\n    AVS_LIST_FOREACH(it, obj->instances) {\n        if (it->last_notify_timestamp != current_timestamp) {\n            if (!anjay_notify_changed(anjay, 3333, it->iid, RID_CURRENT_TIME)) {\n                it->last_notify_timestamp = current_timestamp;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "examples/tutorial/BC-Notifications/src/time_object.h",
    "content": "#ifndef TIME_OBJECT_H\n#define TIME_OBJECT_H\n\n#include <anjay/dm.h>\n\nconst anjay_dm_object_def_t **time_object_create(void);\nvoid time_object_release(const anjay_dm_object_def_t **def);\nvoid time_object_notify(anjay_t *anjay, const anjay_dm_object_def_t **def);\n\n#endif // TIME_OBJECT_H\n"
  },
  {
    "path": "examples/tutorial/BC-ObjectImplementation/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.16)\nproject(anjay-bc-object-implementation C)\n\nset(CMAKE_C_STANDARD 99)\nset(CMAKE_C_EXTENSIONS OFF)\n\nadd_compile_options(-Wall -Wextra)\n\nfind_package(anjay REQUIRED)\n\nadd_executable(${PROJECT_NAME}\n               src/main.c\n               src/time_object.h\n               src/time_object.c)\ntarget_link_libraries(${PROJECT_NAME} PRIVATE anjay)\n"
  },
  {
    "path": "examples/tutorial/BC-ObjectImplementation/src/main.c",
    "content": "#include <anjay/anjay.h>\n#include <anjay/security.h>\n#include <anjay/server.h>\n#include <avsystem/commons/avs_log.h>\n\n#include \"time_object.h\"\n\n// Installs Security Object and adds an instance of it.\n// An instance of Security Object provides information needed to connect to\n// LwM2M server.\nstatic int setup_security_object(anjay_t *anjay) {\n    if (anjay_security_object_install(anjay)) {\n        return -1;\n    }\n\n    static const char PSK_IDENTITY[] = \"identity\";\n    static const char PSK_KEY[] = \"P4s$w0rd\";\n\n    anjay_security_instance_t security_instance = {\n        .ssid = 1,\n        .server_uri = \"coaps://eu.iot.avsystem.cloud:5684\",\n        .security_mode = ANJAY_SECURITY_PSK,\n        .public_cert_or_psk_identity = (const uint8_t *) PSK_IDENTITY,\n        .public_cert_or_psk_identity_size = strlen(PSK_IDENTITY),\n        .private_cert_or_psk_key = (const uint8_t *) PSK_KEY,\n        .private_cert_or_psk_key_size = strlen(PSK_KEY)\n    };\n\n    // Anjay will assign Instance ID automatically\n    anjay_iid_t security_instance_id = ANJAY_ID_INVALID;\n    if (anjay_security_object_add_instance(anjay, &security_instance,\n                                           &security_instance_id)) {\n        return -1;\n    }\n\n    return 0;\n}\n\n// Installs Server Object and adds an instance of it.\n// An instance of Server Object provides the data related to a LwM2M Server.\nstatic int setup_server_object(anjay_t *anjay) {\n    if (anjay_server_object_install(anjay)) {\n        return -1;\n    }\n\n    const anjay_server_instance_t server_instance = {\n        // Server Short ID\n        .ssid = 1,\n        // Client will send Update message often than every 60 seconds\n        .lifetime = 60,\n        // Disable Default Minimum Period resource\n        .default_min_period = -1,\n        // Disable Default Maximum Period resource\n        .default_max_period = -1,\n        // Disable Disable Timeout resource\n        .disable_timeout = -1,\n        // Sets preferred transport to UDP\n        .binding = \"U\"\n    };\n\n    // Anjay will assign Instance ID automatically\n    anjay_iid_t server_instance_id = ANJAY_ID_INVALID;\n    if (anjay_server_object_add_instance(anjay, &server_instance,\n                                         &server_instance_id)) {\n        return -1;\n    }\n\n    return 0;\n}\n\nint main(int argc, char *argv[]) {\n    if (argc != 2) {\n        avs_log(tutorial, ERROR, \"usage: %s ENDPOINT_NAME\", argv[0]);\n        return -1;\n    }\n\n    const anjay_configuration_t CONFIG = {\n        .endpoint_name = argv[1],\n        .in_buffer_size = 4000,\n        .out_buffer_size = 4000,\n        .msg_cache_size = 4000\n    };\n\n    anjay_t *anjay = anjay_new(&CONFIG);\n    if (!anjay) {\n        avs_log(tutorial, ERROR, \"Could not create Anjay object\");\n        return -1;\n    }\n\n    int result = 0;\n    // Setup necessary objects\n    if (setup_security_object(anjay) || setup_server_object(anjay)) {\n        result = -1;\n    }\n\n    const anjay_dm_object_def_t **time_object = NULL;\n    if (!result) {\n        time_object = time_object_create();\n        if (time_object) {\n            result = anjay_register_object(anjay, time_object);\n        } else {\n            result = -1;\n        }\n    }\n\n    if (!result) {\n        result = anjay_event_loop_run(\n                anjay, avs_time_duration_from_scalar(1, AVS_TIME_S));\n    }\n\n    anjay_delete(anjay);\n    time_object_release(time_object);\n    return result;\n}\n"
  },
  {
    "path": "examples/tutorial/BC-ObjectImplementation/src/time_object.c",
    "content": "/**\n * Generated by anjay_codegen.py on 2020-03-19 14:13:34\n *\n * LwM2M Object: Time\n * ID: 3333, URN: urn:oma:lwm2m:ext:3333, Optional, Multiple\n *\n * This IPSO object is used to report the current time in seconds since\n * January 1, 1970 UTC. There is also a fractional time counter that has\n * a range of less than one second.\n */\n\n#include <assert.h>\n#include <stdbool.h>\n\n#include <anjay/anjay.h>\n#include <avsystem/commons/avs_defs.h>\n#include <avsystem/commons/avs_list.h>\n#include <avsystem/commons/avs_memory.h>\n\n#include \"time_object.h\"\n\n/**\n * Current Time: RW, Single, Mandatory\n * type: time, range: N/A, unit: N/A\n * Unix Time. A signed integer representing the number of seconds since\n * Jan 1st, 1970 in the UTC time zone.\n */\n#define RID_CURRENT_TIME 5506\n\n/**\n * Fractional Time: RW, Single, Optional\n * type: float, range: 0..1, unit: s\n * Fractional part of the time when sub-second precision is used (e.g.,\n * 0.23 for 230 ms).\n */\n#define RID_FRACTIONAL_TIME 5507\n\n/**\n * Application Type: RW, Single, Optional\n * type: string, range: N/A, unit: N/A\n * The application type of the sensor or actuator as a string depending\n * on the use case.\n */\n#define RID_APPLICATION_TYPE 5750\n\ntypedef struct time_instance_struct {\n    anjay_iid_t iid;\n    char application_type[64];\n    char application_type_backup[64];\n} time_instance_t;\n\ntypedef struct time_object_struct {\n    const anjay_dm_object_def_t *def;\n    AVS_LIST(time_instance_t) instances;\n} time_object_t;\n\nstatic inline time_object_t *\nget_obj(const anjay_dm_object_def_t *const *obj_ptr) {\n    assert(obj_ptr);\n    return AVS_CONTAINER_OF(obj_ptr, time_object_t, def);\n}\n\nstatic time_instance_t *find_instance(const time_object_t *obj,\n                                      anjay_iid_t iid) {\n    AVS_LIST(time_instance_t) it;\n    AVS_LIST_FOREACH(it, obj->instances) {\n        if (it->iid == iid) {\n            return it;\n        } else if (it->iid > iid) {\n            break;\n        }\n    }\n\n    return NULL;\n}\n\nstatic int list_instances(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr,\n                          anjay_dm_list_ctx_t *ctx) {\n    (void) anjay;\n\n    AVS_LIST(time_instance_t) it;\n    AVS_LIST_FOREACH(it, get_obj(obj_ptr)->instances) {\n        anjay_dm_emit(ctx, it->iid);\n    }\n\n    return 0;\n}\n\nstatic int init_instance(time_instance_t *inst, anjay_iid_t iid) {\n    assert(iid != ANJAY_ID_INVALID);\n\n    inst->iid = iid;\n    inst->application_type[0] = '\\0';\n\n    return 0;\n}\n\nstatic void release_instance(time_instance_t *inst) {\n    (void) inst;\n}\n\nstatic time_instance_t *add_instance(time_object_t *obj, anjay_iid_t iid) {\n    assert(find_instance(obj, iid) == NULL);\n\n    AVS_LIST(time_instance_t) created = AVS_LIST_NEW_ELEMENT(time_instance_t);\n    if (!created) {\n        return NULL;\n    }\n\n    int result = init_instance(created, iid);\n    if (result) {\n        AVS_LIST_CLEAR(&created);\n        return NULL;\n    }\n\n    AVS_LIST(time_instance_t) *ptr;\n    AVS_LIST_FOREACH_PTR(ptr, &obj->instances) {\n        if ((*ptr)->iid > created->iid) {\n            break;\n        }\n    }\n\n    AVS_LIST_INSERT(ptr, created);\n    return created;\n}\n\nstatic int instance_create(anjay_t *anjay,\n                           const anjay_dm_object_def_t *const *obj_ptr,\n                           anjay_iid_t iid) {\n    (void) anjay;\n    time_object_t *obj = get_obj(obj_ptr);\n\n    return add_instance(obj, iid) ? 0 : ANJAY_ERR_INTERNAL;\n}\n\nstatic int instance_remove(anjay_t *anjay,\n                           const anjay_dm_object_def_t *const *obj_ptr,\n                           anjay_iid_t iid) {\n    (void) anjay;\n    time_object_t *obj = get_obj(obj_ptr);\n\n    AVS_LIST(time_instance_t) *it;\n    AVS_LIST_FOREACH_PTR(it, &obj->instances) {\n        if ((*it)->iid == iid) {\n            release_instance(*it);\n            AVS_LIST_DELETE(it);\n            return 0;\n        } else if ((*it)->iid > iid) {\n            break;\n        }\n    }\n\n    assert(0);\n    return ANJAY_ERR_NOT_FOUND;\n}\n\nstatic int instance_reset(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr,\n                          anjay_iid_t iid) {\n    (void) anjay;\n\n    time_object_t *obj = get_obj(obj_ptr);\n    time_instance_t *inst = find_instance(obj, iid);\n    assert(inst);\n\n    inst->application_type[0] = '\\0';\n\n    return 0;\n}\n\nstatic int list_resources(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr,\n                          anjay_iid_t iid,\n                          anjay_dm_resource_list_ctx_t *ctx) {\n    (void) anjay;\n    (void) obj_ptr;\n    (void) iid;\n\n    anjay_dm_emit_res(ctx, RID_CURRENT_TIME, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, RID_FRACTIONAL_TIME, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT);\n    anjay_dm_emit_res(ctx, RID_APPLICATION_TYPE, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_PRESENT);\n    return 0;\n}\n\nstatic int resource_read(anjay_t *anjay,\n                         const anjay_dm_object_def_t *const *obj_ptr,\n                         anjay_iid_t iid,\n                         anjay_rid_t rid,\n                         anjay_riid_t riid,\n                         anjay_output_ctx_t *ctx) {\n    (void) anjay;\n\n    time_object_t *obj = get_obj(obj_ptr);\n    time_instance_t *inst = find_instance(obj, iid);\n    assert(inst);\n\n    switch (rid) {\n    case RID_CURRENT_TIME: {\n        assert(riid == ANJAY_ID_INVALID);\n        int64_t timestamp;\n        if (avs_time_real_to_scalar(&timestamp, AVS_TIME_S,\n                                    avs_time_real_now())) {\n            return -1;\n        }\n        return anjay_ret_i64(ctx, timestamp);\n    }\n\n    case RID_APPLICATION_TYPE:\n        assert(riid == ANJAY_ID_INVALID);\n        return anjay_ret_string(ctx, inst->application_type);\n\n    default:\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\nstatic int resource_write(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr,\n                          anjay_iid_t iid,\n                          anjay_rid_t rid,\n                          anjay_riid_t riid,\n                          anjay_input_ctx_t *ctx) {\n    (void) anjay;\n\n    time_object_t *obj = get_obj(obj_ptr);\n    time_instance_t *inst = find_instance(obj, iid);\n    assert(inst);\n\n    switch (rid) {\n    case RID_APPLICATION_TYPE:\n        assert(riid == ANJAY_ID_INVALID);\n        return anjay_get_string(ctx, inst->application_type,\n                                sizeof(inst->application_type));\n\n    default:\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\nint transaction_begin(anjay_t *anjay,\n                      const anjay_dm_object_def_t *const *obj_ptr) {\n    (void) anjay;\n\n    time_object_t *obj = get_obj(obj_ptr);\n\n    time_instance_t *element;\n    AVS_LIST_FOREACH(element, obj->instances) {\n        strcpy(element->application_type_backup, element->application_type);\n    }\n    return 0;\n}\n\nint transaction_rollback(anjay_t *anjay,\n                         const anjay_dm_object_def_t *const *obj_ptr) {\n    (void) anjay;\n\n    time_object_t *obj = get_obj(obj_ptr);\n\n    time_instance_t *element;\n    AVS_LIST_FOREACH(element, obj->instances) {\n        strcpy(element->application_type, element->application_type_backup);\n    }\n    return 0;\n}\n\nstatic const anjay_dm_object_def_t OBJ_DEF = {\n    .oid = 3333,\n    .handlers = {\n        .list_instances = list_instances,\n        .instance_create = instance_create,\n        .instance_remove = instance_remove,\n        .instance_reset = instance_reset,\n\n        .list_resources = list_resources,\n        .resource_read = resource_read,\n        .resource_write = resource_write,\n\n        .transaction_begin = transaction_begin,\n        .transaction_validate = anjay_dm_transaction_NOOP,\n        .transaction_commit = anjay_dm_transaction_NOOP,\n        .transaction_rollback = transaction_rollback\n    }\n};\n\nconst anjay_dm_object_def_t **time_object_create(void) {\n    time_object_t *obj = (time_object_t *) avs_calloc(1, sizeof(time_object_t));\n    if (!obj) {\n        return NULL;\n    }\n    obj->def = &OBJ_DEF;\n\n    time_instance_t *inst = add_instance(obj, 0);\n    if (inst) {\n        strcpy(inst->application_type, \"Clock 0\");\n    } else {\n        avs_free(obj);\n        return NULL;\n    }\n\n    return &obj->def;\n}\n\nvoid time_object_release(const anjay_dm_object_def_t **def) {\n    if (def) {\n        time_object_t *obj = get_obj(def);\n        AVS_LIST_CLEAR(&obj->instances) {\n            release_instance(obj->instances);\n        }\n\n        avs_free(obj);\n    }\n}\n"
  },
  {
    "path": "examples/tutorial/BC-ObjectImplementation/src/time_object.h",
    "content": "#ifndef TIME_OBJECT_H\n#define TIME_OBJECT_H\n\n#include <anjay/dm.h>\n\nconst anjay_dm_object_def_t **time_object_create(void);\nvoid time_object_release(const anjay_dm_object_def_t **def);\n\n#endif // TIME_OBJECT_H\n"
  },
  {
    "path": "examples/tutorial/BC-Security/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.16)\nproject(anjay-bc-security C)\n\nset(CMAKE_C_STANDARD 99)\nset(CMAKE_C_EXTENSIONS OFF)\n\nadd_compile_options(-Wall -Wextra)\n\nfind_package(anjay REQUIRED)\n\nadd_executable(${PROJECT_NAME} src/main.c)\ntarget_link_libraries(${PROJECT_NAME} PRIVATE anjay)\n"
  },
  {
    "path": "examples/tutorial/BC-Security/src/main.c",
    "content": "#include <anjay/anjay.h>\n#include <anjay/security.h>\n#include <anjay/server.h>\n#include <avsystem/commons/avs_log.h>\n\n// Installs Security Object and adds an instance of it.\n// An instance of Security Object provides information needed to connect to\n// LwM2M server.\nstatic int setup_security_object(anjay_t *anjay) {\n    if (anjay_security_object_install(anjay)) {\n        return -1;\n    }\n\n    static const char PSK_IDENTITY[] = \"identity\";\n    static const char PSK_KEY[] = \"P4s$w0rd\";\n\n    anjay_security_instance_t security_instance = {\n        .ssid = 1,\n        .server_uri = \"coaps://eu.iot.avsystem.cloud:5684\",\n        .security_mode = ANJAY_SECURITY_PSK,\n        .public_cert_or_psk_identity = (const uint8_t *) PSK_IDENTITY,\n        .public_cert_or_psk_identity_size = strlen(PSK_IDENTITY),\n        .private_cert_or_psk_key = (const uint8_t *) PSK_KEY,\n        .private_cert_or_psk_key_size = strlen(PSK_KEY)\n    };\n\n    // Anjay will assign Instance ID automatically\n    anjay_iid_t security_instance_id = ANJAY_ID_INVALID;\n    if (anjay_security_object_add_instance(anjay, &security_instance,\n                                           &security_instance_id)) {\n        return -1;\n    }\n\n    return 0;\n}\n\n// Installs Server Object and adds an instance of it.\n// An instance of Server Object provides the data related to a LwM2M Server.\nstatic int setup_server_object(anjay_t *anjay) {\n    if (anjay_server_object_install(anjay)) {\n        return -1;\n    }\n\n    const anjay_server_instance_t server_instance = {\n        // Server Short ID\n        .ssid = 1,\n        // Client will send Update message often than every 60 seconds\n        .lifetime = 60,\n        // Disable Default Minimum Period resource\n        .default_min_period = -1,\n        // Disable Default Maximum Period resource\n        .default_max_period = -1,\n        // Disable Disable Timeout resource\n        .disable_timeout = -1,\n        // Sets preferred transport to UDP\n        .binding = \"U\"\n    };\n\n    // Anjay will assign Instance ID automatically\n    anjay_iid_t server_instance_id = ANJAY_ID_INVALID;\n    if (anjay_server_object_add_instance(anjay, &server_instance,\n                                         &server_instance_id)) {\n        return -1;\n    }\n\n    return 0;\n}\n\nint main(int argc, char *argv[]) {\n    if (argc != 2) {\n        avs_log(tutorial, ERROR, \"usage: %s ENDPOINT_NAME\", argv[0]);\n        return -1;\n    }\n\n    const anjay_configuration_t CONFIG = {\n        .endpoint_name = argv[1],\n        .in_buffer_size = 4000,\n        .out_buffer_size = 4000,\n        .msg_cache_size = 4000\n    };\n\n    anjay_t *anjay = anjay_new(&CONFIG);\n    if (!anjay) {\n        avs_log(tutorial, ERROR, \"Could not create Anjay object\");\n        return -1;\n    }\n\n    int result = 0;\n    // Setup necessary objects\n    if (setup_security_object(anjay) || setup_server_object(anjay)) {\n        result = -1;\n    }\n\n    if (!result) {\n        result = anjay_event_loop_run(\n                anjay, avs_time_duration_from_scalar(1, AVS_TIME_S));\n    }\n\n    anjay_delete(anjay);\n    return result;\n}\n"
  },
  {
    "path": "examples/tutorial/BC-Send/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.16)\nproject(anjay-bc-send C)\n\nset(CMAKE_C_STANDARD 99)\nset(CMAKE_C_EXTENSIONS OFF)\n\nfind_package(anjay REQUIRED)\n\nadd_compile_options(-Wall -Wextra)\n\nadd_executable(${PROJECT_NAME}\n               src/main.c\n               src/time_object.h\n               src/time_object.c)\ntarget_link_libraries(${PROJECT_NAME} PRIVATE anjay)\n"
  },
  {
    "path": "examples/tutorial/BC-Send/src/main.c",
    "content": "#include <anjay/anjay.h>\n#include <anjay/security.h>\n#include <anjay/server.h>\n#include <avsystem/commons/avs_log.h>\n\n#include \"time_object.h\"\n\ntypedef struct {\n    anjay_t *anjay;\n    const anjay_dm_object_def_t **time_object;\n} time_object_job_args_t;\n\n// Periodically notifies the library about Resource value changes\nstatic void notify_job(avs_sched_t *sched, const void *args_ptr) {\n    const time_object_job_args_t *args =\n            (const time_object_job_args_t *) args_ptr;\n\n    time_object_notify(args->anjay, args->time_object);\n\n    // Schedule run of the same function after 1 second\n    AVS_SCHED_DELAYED(sched, NULL, avs_time_duration_from_scalar(1, AVS_TIME_S),\n                      notify_job, args, sizeof(*args));\n}\n\n// Periodically issues a Send message with application type and current time\nstatic void send_job(avs_sched_t *sched, const void *args_ptr) {\n    const time_object_job_args_t *args =\n            (const time_object_job_args_t *) args_ptr;\n\n    time_object_send(args->anjay, args->time_object);\n\n    // Schedule run of the same function after 10 seconds\n    AVS_SCHED_DELAYED(sched, NULL,\n                      avs_time_duration_from_scalar(10, AVS_TIME_S), send_job,\n                      args, sizeof(*args));\n}\n\n// Installs Security Object and adds an instance of it.\n// An instance of Security Object provides information needed to connect to\n// LwM2M server.\nstatic int setup_security_object(anjay_t *anjay) {\n    if (anjay_security_object_install(anjay)) {\n        return -1;\n    }\n\n    static const char PSK_IDENTITY[] = \"identity\";\n    static const char PSK_KEY[] = \"P4s$w0rd\";\n\n    anjay_security_instance_t security_instance = {\n        .ssid = 1,\n        .server_uri = \"coaps://eu.iot.avsystem.cloud:5684\",\n        .security_mode = ANJAY_SECURITY_PSK,\n        .public_cert_or_psk_identity = (const uint8_t *) PSK_IDENTITY,\n        .public_cert_or_psk_identity_size = strlen(PSK_IDENTITY),\n        .private_cert_or_psk_key = (const uint8_t *) PSK_KEY,\n        .private_cert_or_psk_key_size = strlen(PSK_KEY)\n    };\n\n    // Anjay will assign Instance ID automatically\n    anjay_iid_t security_instance_id = ANJAY_ID_INVALID;\n    if (anjay_security_object_add_instance(anjay, &security_instance,\n                                           &security_instance_id)) {\n        return -1;\n    }\n\n    return 0;\n}\n\n// Installs Server Object and adds an instance of it.\n// An instance of Server Object provides the data related to a LwM2M Server.\nstatic int setup_server_object(anjay_t *anjay) {\n    if (anjay_server_object_install(anjay)) {\n        return -1;\n    }\n\n    const anjay_server_instance_t server_instance = {\n        // Server Short ID\n        .ssid = 1,\n        // Client will send Update message often than every 60 seconds\n        .lifetime = 60,\n        // Disable Default Minimum Period resource\n        .default_min_period = -1,\n        // Disable Default Maximum Period resource\n        .default_max_period = -1,\n        // Disable Disable Timeout resource\n        .disable_timeout = -1,\n        // Sets preferred transport to UDP\n        .binding = \"U\"\n    };\n\n    // Anjay will assign Instance ID automatically\n    anjay_iid_t server_instance_id = ANJAY_ID_INVALID;\n    if (anjay_server_object_add_instance(anjay, &server_instance,\n                                         &server_instance_id)) {\n        return -1;\n    }\n\n    return 0;\n}\n\nint main(int argc, char *argv[]) {\n    if (argc != 2) {\n        avs_log(tutorial, ERROR, \"usage: %s ENDPOINT_NAME\", argv[0]);\n        return -1;\n    }\n\n    const anjay_configuration_t CONFIG = {\n        .endpoint_name = argv[1],\n        .in_buffer_size = 4000,\n        .out_buffer_size = 4000,\n        .msg_cache_size = 4000\n    };\n\n    anjay_t *anjay = anjay_new(&CONFIG);\n    if (!anjay) {\n        avs_log(tutorial, ERROR, \"Could not create Anjay object\");\n        return -1;\n    }\n\n    int result = 0;\n    // Setup necessary objects\n    if (setup_security_object(anjay) || setup_server_object(anjay)) {\n        result = -1;\n    }\n\n    const anjay_dm_object_def_t **time_object = NULL;\n    if (!result) {\n        time_object = time_object_create();\n        if (time_object) {\n            result = anjay_register_object(anjay, time_object);\n        } else {\n            result = -1;\n        }\n    }\n\n    if (!result) {\n        // Run notify_job and send_job the first time;\n        // this will schedule periodic calls to themselves via the scheduler\n        notify_job(anjay_get_scheduler(anjay), &(const time_object_job_args_t) {\n                                                   .anjay = anjay,\n                                                   .time_object = time_object\n                                               });\n        send_job(anjay_get_scheduler(anjay), &(const time_object_job_args_t) {\n                                                 .anjay = anjay,\n                                                 .time_object = time_object\n                                             });\n\n        result = anjay_event_loop_run(\n                anjay, avs_time_duration_from_scalar(1, AVS_TIME_S));\n    }\n\n    anjay_delete(anjay);\n    time_object_release(time_object);\n    return result;\n}\n"
  },
  {
    "path": "examples/tutorial/BC-Send/src/time_object.c",
    "content": "/**\n * Generated by anjay_codegen.py on 2020-03-19 14:13:34\n *\n * LwM2M Object: Time\n * ID: 3333, URN: urn:oma:lwm2m:ext:3333, Optional, Multiple\n *\n * This IPSO object is used to report the current time in seconds since\n * January 1, 1970 UTC. There is also a fractional time counter that has\n * a range of less than one second.\n */\n\n#include <assert.h>\n#include <stdbool.h>\n\n#include <anjay/anjay.h>\n#include <anjay/lwm2m_send.h>\n#include <avsystem/commons/avs_defs.h>\n#include <avsystem/commons/avs_list.h>\n#include <avsystem/commons/avs_log.h>\n#include <avsystem/commons/avs_memory.h>\n\n#include \"time_object.h\"\n\n/**\n * Current Time: RW, Single, Mandatory\n * type: time, range: N/A, unit: N/A\n * Unix Time. A signed integer representing the number of seconds since\n * Jan 1st, 1970 in the UTC time zone.\n */\n#define RID_CURRENT_TIME 5506\n\n/**\n * Fractional Time: RW, Single, Optional\n * type: float, range: 0..1, unit: s\n * Fractional part of the time when sub-second precision is used (e.g.,\n * 0.23 for 230 ms).\n */\n#define RID_FRACTIONAL_TIME 5507\n\n/**\n * Application Type: RW, Single, Optional\n * type: string, range: N/A, unit: N/A\n * The application type of the sensor or actuator as a string depending\n * on the use case.\n */\n#define RID_APPLICATION_TYPE 5750\n\ntypedef struct time_instance_struct {\n    anjay_iid_t iid;\n    char application_type[64];\n    char application_type_backup[64];\n    int64_t last_notify_timestamp;\n} time_instance_t;\n\ntypedef struct time_object_struct {\n    const anjay_dm_object_def_t *def;\n    AVS_LIST(time_instance_t) instances;\n} time_object_t;\n\nstatic inline time_object_t *\nget_obj(const anjay_dm_object_def_t *const *obj_ptr) {\n    assert(obj_ptr);\n    return AVS_CONTAINER_OF(obj_ptr, time_object_t, def);\n}\n\nstatic time_instance_t *find_instance(const time_object_t *obj,\n                                      anjay_iid_t iid) {\n    AVS_LIST(time_instance_t) it;\n    AVS_LIST_FOREACH(it, obj->instances) {\n        if (it->iid == iid) {\n            return it;\n        } else if (it->iid > iid) {\n            break;\n        }\n    }\n\n    return NULL;\n}\n\nstatic int list_instances(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr,\n                          anjay_dm_list_ctx_t *ctx) {\n    (void) anjay;\n\n    AVS_LIST(time_instance_t) it;\n    AVS_LIST_FOREACH(it, get_obj(obj_ptr)->instances) {\n        anjay_dm_emit(ctx, it->iid);\n    }\n\n    return 0;\n}\n\nstatic int init_instance(time_instance_t *inst, anjay_iid_t iid) {\n    assert(iid != ANJAY_ID_INVALID);\n\n    inst->iid = iid;\n    inst->application_type[0] = '\\0';\n\n    return 0;\n}\n\nstatic void release_instance(time_instance_t *inst) {\n    (void) inst;\n}\n\nstatic time_instance_t *add_instance(time_object_t *obj, anjay_iid_t iid) {\n    assert(find_instance(obj, iid) == NULL);\n\n    AVS_LIST(time_instance_t) created = AVS_LIST_NEW_ELEMENT(time_instance_t);\n    if (!created) {\n        return NULL;\n    }\n\n    int result = init_instance(created, iid);\n    if (result) {\n        AVS_LIST_CLEAR(&created);\n        return NULL;\n    }\n\n    AVS_LIST(time_instance_t) *ptr;\n    AVS_LIST_FOREACH_PTR(ptr, &obj->instances) {\n        if ((*ptr)->iid > created->iid) {\n            break;\n        }\n    }\n\n    AVS_LIST_INSERT(ptr, created);\n    return created;\n}\n\nstatic int instance_create(anjay_t *anjay,\n                           const anjay_dm_object_def_t *const *obj_ptr,\n                           anjay_iid_t iid) {\n    (void) anjay;\n    time_object_t *obj = get_obj(obj_ptr);\n\n    return add_instance(obj, iid) ? 0 : ANJAY_ERR_INTERNAL;\n}\n\nstatic int instance_remove(anjay_t *anjay,\n                           const anjay_dm_object_def_t *const *obj_ptr,\n                           anjay_iid_t iid) {\n    (void) anjay;\n    time_object_t *obj = get_obj(obj_ptr);\n\n    AVS_LIST(time_instance_t) *it;\n    AVS_LIST_FOREACH_PTR(it, &obj->instances) {\n        if ((*it)->iid == iid) {\n            release_instance(*it);\n            AVS_LIST_DELETE(it);\n            return 0;\n        } else if ((*it)->iid > iid) {\n            break;\n        }\n    }\n\n    assert(0);\n    return ANJAY_ERR_NOT_FOUND;\n}\n\nstatic int instance_reset(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr,\n                          anjay_iid_t iid) {\n    (void) anjay;\n\n    time_object_t *obj = get_obj(obj_ptr);\n    time_instance_t *inst = find_instance(obj, iid);\n    assert(inst);\n\n    inst->application_type[0] = '\\0';\n\n    return 0;\n}\n\nstatic int list_resources(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr,\n                          anjay_iid_t iid,\n                          anjay_dm_resource_list_ctx_t *ctx) {\n    (void) anjay;\n    (void) obj_ptr;\n    (void) iid;\n\n    anjay_dm_emit_res(ctx, RID_CURRENT_TIME, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, RID_FRACTIONAL_TIME, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT);\n    anjay_dm_emit_res(ctx, RID_APPLICATION_TYPE, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_PRESENT);\n    return 0;\n}\n\nstatic int resource_read(anjay_t *anjay,\n                         const anjay_dm_object_def_t *const *obj_ptr,\n                         anjay_iid_t iid,\n                         anjay_rid_t rid,\n                         anjay_riid_t riid,\n                         anjay_output_ctx_t *ctx) {\n    (void) anjay;\n\n    time_object_t *obj = get_obj(obj_ptr);\n    time_instance_t *inst = find_instance(obj, iid);\n    assert(inst);\n\n    switch (rid) {\n    case RID_CURRENT_TIME: {\n        assert(riid == ANJAY_ID_INVALID);\n        int64_t timestamp;\n        if (avs_time_real_to_scalar(&timestamp, AVS_TIME_S,\n                                    avs_time_real_now())) {\n            return -1;\n        }\n        return anjay_ret_i64(ctx, timestamp);\n    }\n\n    case RID_APPLICATION_TYPE:\n        assert(riid == ANJAY_ID_INVALID);\n        return anjay_ret_string(ctx, inst->application_type);\n\n    default:\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\nstatic int resource_write(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr,\n                          anjay_iid_t iid,\n                          anjay_rid_t rid,\n                          anjay_riid_t riid,\n                          anjay_input_ctx_t *ctx) {\n    (void) anjay;\n\n    time_object_t *obj = get_obj(obj_ptr);\n    time_instance_t *inst = find_instance(obj, iid);\n    assert(inst);\n\n    switch (rid) {\n    case RID_APPLICATION_TYPE:\n        assert(riid == ANJAY_ID_INVALID);\n        return anjay_get_string(ctx, inst->application_type,\n                                sizeof(inst->application_type));\n\n    default:\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\nint transaction_begin(anjay_t *anjay,\n                      const anjay_dm_object_def_t *const *obj_ptr) {\n    (void) anjay;\n\n    time_object_t *obj = get_obj(obj_ptr);\n\n    time_instance_t *element;\n    AVS_LIST_FOREACH(element, obj->instances) {\n        strcpy(element->application_type_backup, element->application_type);\n    }\n    return 0;\n}\n\nint transaction_rollback(anjay_t *anjay,\n                         const anjay_dm_object_def_t *const *obj_ptr) {\n    (void) anjay;\n\n    time_object_t *obj = get_obj(obj_ptr);\n\n    time_instance_t *element;\n    AVS_LIST_FOREACH(element, obj->instances) {\n        strcpy(element->application_type, element->application_type_backup);\n    }\n    return 0;\n}\n\nstatic const anjay_dm_object_def_t OBJ_DEF = {\n    .oid = 3333,\n    .handlers = {\n        .list_instances = list_instances,\n        .instance_create = instance_create,\n        .instance_remove = instance_remove,\n        .instance_reset = instance_reset,\n\n        .list_resources = list_resources,\n        .resource_read = resource_read,\n        .resource_write = resource_write,\n\n        .transaction_begin = transaction_begin,\n        .transaction_validate = anjay_dm_transaction_NOOP,\n        .transaction_commit = anjay_dm_transaction_NOOP,\n        .transaction_rollback = transaction_rollback\n    }\n};\n\nconst anjay_dm_object_def_t **time_object_create(void) {\n    time_object_t *obj = (time_object_t *) avs_calloc(1, sizeof(time_object_t));\n    if (!obj) {\n        return NULL;\n    }\n    obj->def = &OBJ_DEF;\n\n    time_instance_t *inst = add_instance(obj, 0);\n    if (inst) {\n        strcpy(inst->application_type, \"Clock 0\");\n    } else {\n        avs_free(obj);\n        return NULL;\n    }\n\n    return &obj->def;\n}\n\nvoid time_object_release(const anjay_dm_object_def_t **def) {\n    if (def) {\n        time_object_t *obj = get_obj(def);\n        AVS_LIST_CLEAR(&obj->instances) {\n            release_instance(obj->instances);\n        }\n\n        avs_free(obj);\n    }\n}\n\nvoid time_object_notify(anjay_t *anjay, const anjay_dm_object_def_t **def) {\n    if (!anjay || !def) {\n        return;\n    }\n    time_object_t *obj = get_obj(def);\n\n    int64_t current_timestamp;\n    if (avs_time_real_to_scalar(&current_timestamp, AVS_TIME_S,\n                                avs_time_real_now())) {\n        return;\n    }\n\n    AVS_LIST(time_instance_t) it;\n    AVS_LIST_FOREACH(it, obj->instances) {\n        if (it->last_notify_timestamp != current_timestamp) {\n            if (!anjay_notify_changed(anjay, 3333, it->iid, RID_CURRENT_TIME)) {\n                it->last_notify_timestamp = current_timestamp;\n            }\n        }\n    }\n}\n\nstatic void send_finished_handler(anjay_t *anjay,\n                                  anjay_ssid_t ssid,\n                                  const anjay_send_batch_t *batch,\n                                  int result,\n                                  void *data) {\n    (void) anjay;\n    (void) ssid;\n    (void) batch;\n    (void) data;\n\n    if (result != ANJAY_SEND_SUCCESS) {\n        avs_log(time_object, ERROR, \"Send failed, result: %d\", result);\n    } else {\n        avs_log(time_object, TRACE, \"Send successful\");\n    }\n}\n\nvoid time_object_send(anjay_t *anjay, const anjay_dm_object_def_t **def) {\n    if (!anjay || !def) {\n        return;\n    }\n    time_object_t *obj = get_obj(def);\n    const anjay_ssid_t server_ssid = 1;\n\n    // Allocate new batch builder.\n    anjay_send_batch_builder_t *builder = anjay_send_batch_builder_new();\n\n    if (!builder) {\n        avs_log(time_object, ERROR, \"Failed to allocate batch builder\");\n        return;\n    }\n\n    int res = 0;\n\n    AVS_LIST(time_instance_t) it;\n    AVS_LIST_FOREACH(it, obj->instances) {\n        // Add current values of resources from Time Object.\n        if (anjay_send_batch_data_add_current(builder, anjay, obj->def->oid,\n                                              it->iid, RID_CURRENT_TIME)\n                || anjay_send_batch_data_add_current(builder, anjay,\n                                                     obj->def->oid, it->iid,\n                                                     RID_APPLICATION_TYPE)) {\n            anjay_send_batch_builder_cleanup(&builder);\n            avs_log(time_object, ERROR, \"Failed to add batch data, result: %d\",\n                    res);\n            return;\n        }\n    }\n    // After adding all values, compile our batch for sending.\n    anjay_send_batch_t *batch = anjay_send_batch_builder_compile(&builder);\n\n    if (!batch) {\n        anjay_send_batch_builder_cleanup(&builder);\n        avs_log(time_object, ERROR, \"Batch compile failed\");\n        return;\n    }\n\n    // Schedule our send to be run on next `anjay_sched_run()` call.\n    res = anjay_send(anjay, server_ssid, batch, send_finished_handler, NULL);\n\n    if (res) {\n        avs_log(time_object, ERROR, \"Failed to send, result: %d\", res);\n    }\n\n    // After scheduling, we can release our batch.\n    anjay_send_batch_release(&batch);\n}\n"
  },
  {
    "path": "examples/tutorial/BC-Send/src/time_object.h",
    "content": "#ifndef TIME_OBJECT_H\n#define TIME_OBJECT_H\n\n#include <anjay/dm.h>\n\nconst anjay_dm_object_def_t **time_object_create(void);\nvoid time_object_release(const anjay_dm_object_def_t **def);\nvoid time_object_notify(anjay_t *anjay, const anjay_dm_object_def_t **def);\nvoid time_object_send(anjay_t *anjay, const anjay_dm_object_def_t **def);\n\n#endif // TIME_OBJECT_H\n"
  },
  {
    "path": "examples/tutorial/BC-ThreadSafety/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.16)\nproject(anjay-bc-thread-safety C)\n\nset(CMAKE_C_STANDARD 99)\nset(CMAKE_C_EXTENSIONS ON)\n\nadd_compile_options(-Wall -Wextra)\n\nfind_package(anjay REQUIRED)\nfind_package(Threads REQUIRED)\n\nadd_executable(${PROJECT_NAME}\n               src/main.c\n               src/time_object.h\n               src/time_object.c)\ntarget_link_libraries(${PROJECT_NAME} PRIVATE anjay ${CMAKE_THREAD_LIBS_INIT})\n"
  },
  {
    "path": "examples/tutorial/BC-ThreadSafety/src/main.c",
    "content": "#include <pthread.h>\n#include <unistd.h>\n\n#include <anjay/anjay.h>\n#include <anjay/security.h>\n#include <anjay/server.h>\n#include <avsystem/commons/avs_log.h>\n\n#include \"time_object.h\"\n\n#if !defined(ANJAY_WITH_THREAD_SAFETY) \\\n        || !defined(AVS_COMMONS_SCHED_THREAD_SAFE)\n#    error \"This example requires Anjay compiled with thread safety enabled\"\n#endif // !defined(ANJAY_WITH_THREAD_SAFETY) ||\n       // !defined(AVS_COMMONS_SCHED_THREAD_SAFE)\n\n// Installs Security Object and adds an instance of it.\n// An instance of Security Object provides information needed to connect to\n// LwM2M server.\nstatic int setup_security_object(anjay_t *anjay) {\n    if (anjay_security_object_install(anjay)) {\n        return -1;\n    }\n\n    static const char PSK_IDENTITY[] = \"identity\";\n    static const char PSK_KEY[] = \"P4s$w0rd\";\n\n    anjay_security_instance_t security_instance = {\n        .ssid = 1,\n        .server_uri = \"coaps://eu.iot.avsystem.cloud:5684\",\n        .security_mode = ANJAY_SECURITY_PSK,\n        .public_cert_or_psk_identity = (const uint8_t *) PSK_IDENTITY,\n        .public_cert_or_psk_identity_size = strlen(PSK_IDENTITY),\n        .private_cert_or_psk_key = (const uint8_t *) PSK_KEY,\n        .private_cert_or_psk_key_size = strlen(PSK_KEY)\n    };\n\n    // Anjay will assign Instance ID automatically\n    anjay_iid_t security_instance_id = ANJAY_ID_INVALID;\n    if (anjay_security_object_add_instance(anjay, &security_instance,\n                                           &security_instance_id)) {\n        return -1;\n    }\n\n    return 0;\n}\n\n// Installs Server Object and adds an instance of it.\n// An instance of Server Object provides the data related to a LwM2M Server.\nstatic int setup_server_object(anjay_t *anjay) {\n    if (anjay_server_object_install(anjay)) {\n        return -1;\n    }\n\n    const anjay_server_instance_t server_instance = {\n        // Server Short ID\n        .ssid = 1,\n        // Client will send Update message often than every 60 seconds\n        .lifetime = 60,\n        // Disable Default Minimum Period resource\n        .default_min_period = -1,\n        // Disable Default Maximum Period resource\n        .default_max_period = -1,\n        // Disable Disable Timeout resource\n        .disable_timeout = -1,\n        // Sets preferred transport to UDP\n        .binding = \"U\"\n    };\n\n    // Anjay will assign Instance ID automatically\n    anjay_iid_t server_instance_id = ANJAY_ID_INVALID;\n    if (anjay_server_object_add_instance(anjay, &server_instance,\n                                         &server_instance_id)) {\n        return -1;\n    }\n\n    return 0;\n}\n\nstatic void *event_loop_func(void *anjay) {\n    intptr_t result = anjay_event_loop_run(\n            (anjay_t *) anjay, avs_time_duration_from_scalar(100, AVS_TIME_MS));\n    return (void *) result;\n}\n\nint main(int argc, char *argv[]) {\n    if (argc != 2) {\n        avs_log(tutorial, ERROR, \"usage: %s ENDPOINT_NAME\", argv[0]);\n        return -1;\n    }\n\n    const anjay_configuration_t CONFIG = {\n        .endpoint_name = argv[1],\n        .in_buffer_size = 4000,\n        .out_buffer_size = 4000,\n        .msg_cache_size = 4000\n    };\n\n    anjay_t *anjay = anjay_new(&CONFIG);\n    if (!anjay) {\n        avs_log(tutorial, ERROR, \"Could not create Anjay object\");\n        return -1;\n    }\n\n    int result = 0;\n    // Setup necessary objects\n    if (setup_security_object(anjay) || setup_server_object(anjay)) {\n        result = -1;\n    }\n\n    const anjay_dm_object_def_t **time_object = NULL;\n    if (!result) {\n        time_object = time_object_create();\n        if (time_object) {\n            result = anjay_register_object(anjay, time_object);\n        } else {\n            result = -1;\n        }\n    }\n\n    pthread_t event_loop_thread;\n    if (!result) {\n        result = pthread_create(&event_loop_thread, NULL, event_loop_func,\n                                anjay);\n    }\n\n    if (!result) {\n        // Periodically notify the library about Resource value changes\n        while (true) {\n            sleep(1);\n            time_object_notify(anjay, time_object);\n        }\n    }\n\n    anjay_delete(anjay);\n    time_object_release(time_object);\n    return result;\n}\n"
  },
  {
    "path": "examples/tutorial/BC-ThreadSafety/src/time_object.c",
    "content": "/**\n * Generated by anjay_codegen.py on 2020-03-19 14:13:34\n *\n * LwM2M Object: Time\n * ID: 3333, URN: urn:oma:lwm2m:ext:3333, Optional, Multiple\n *\n * This IPSO object is used to report the current time in seconds since\n * January 1, 1970 UTC. There is also a fractional time counter that has\n * a range of less than one second.\n */\n\n#include <assert.h>\n#include <stdbool.h>\n\n#include <pthread.h>\n\n#include <anjay/anjay.h>\n#include <avsystem/commons/avs_defs.h>\n#include <avsystem/commons/avs_list.h>\n#include <avsystem/commons/avs_memory.h>\n\n#include \"time_object.h\"\n\n/**\n * Current Time: RW, Single, Mandatory\n * type: time, range: N/A, unit: N/A\n * Unix Time. A signed integer representing the number of seconds since\n * Jan 1st, 1970 in the UTC time zone.\n */\n#define RID_CURRENT_TIME 5506\n\n/**\n * Fractional Time: RW, Single, Optional\n * type: float, range: 0..1, unit: s\n * Fractional part of the time when sub-second precision is used (e.g.,\n * 0.23 for 230 ms).\n */\n#define RID_FRACTIONAL_TIME 5507\n\n/**\n * Application Type: RW, Single, Optional\n * type: string, range: N/A, unit: N/A\n * The application type of the sensor or actuator as a string depending\n * on the use case.\n */\n#define RID_APPLICATION_TYPE 5750\n\ntypedef struct time_instance_struct {\n    anjay_iid_t iid;\n    char application_type[64];\n    char application_type_backup[64];\n    int64_t last_notify_timestamp;\n} time_instance_t;\n\ntypedef struct time_object_struct {\n    const anjay_dm_object_def_t *def;\n    pthread_mutex_t mutex;\n    AVS_LIST(time_instance_t) instances;\n} time_object_t;\n\nstatic inline time_object_t *\nget_obj(const anjay_dm_object_def_t *const *obj_ptr) {\n    assert(obj_ptr);\n    return AVS_CONTAINER_OF(obj_ptr, time_object_t, def);\n}\n\nstatic time_instance_t *find_instance(const time_object_t *obj,\n                                      anjay_iid_t iid) {\n    AVS_LIST(time_instance_t) it;\n    AVS_LIST_FOREACH(it, obj->instances) {\n        if (it->iid == iid) {\n            return it;\n        } else if (it->iid > iid) {\n            break;\n        }\n    }\n\n    return NULL;\n}\n\nstatic int list_instances(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr,\n                          anjay_dm_list_ctx_t *ctx) {\n    (void) anjay;\n    time_object_t *obj = get_obj(obj_ptr);\n\n    pthread_mutex_lock(&obj->mutex);\n    AVS_LIST(time_instance_t) it;\n    AVS_LIST_FOREACH(it, obj->instances) {\n        anjay_dm_emit(ctx, it->iid);\n    }\n    pthread_mutex_unlock(&obj->mutex);\n    return 0;\n}\n\nstatic int init_instance(time_instance_t *inst, anjay_iid_t iid) {\n    assert(iid != ANJAY_ID_INVALID);\n\n    inst->iid = iid;\n    inst->application_type[0] = '\\0';\n\n    return 0;\n}\n\nstatic void release_instance(time_instance_t *inst) {\n    (void) inst;\n}\n\nstatic time_instance_t *add_instance(time_object_t *obj, anjay_iid_t iid) {\n    assert(find_instance(obj, iid) == NULL);\n\n    AVS_LIST(time_instance_t) created = AVS_LIST_NEW_ELEMENT(time_instance_t);\n    if (!created) {\n        return NULL;\n    }\n\n    int result = init_instance(created, iid);\n    if (result) {\n        AVS_LIST_CLEAR(&created);\n        return NULL;\n    }\n\n    AVS_LIST(time_instance_t) *ptr;\n    AVS_LIST_FOREACH_PTR(ptr, &obj->instances) {\n        if ((*ptr)->iid > created->iid) {\n            break;\n        }\n    }\n\n    AVS_LIST_INSERT(ptr, created);\n    return created;\n}\n\nstatic int instance_create(anjay_t *anjay,\n                           const anjay_dm_object_def_t *const *obj_ptr,\n                           anjay_iid_t iid) {\n    (void) anjay;\n    time_object_t *obj = get_obj(obj_ptr);\n\n    pthread_mutex_lock(&obj->mutex);\n    int result = 0;\n    if (add_instance(obj, iid)) {\n        result = ANJAY_ERR_INTERNAL;\n    }\n    pthread_mutex_unlock(&obj->mutex);\n    return result;\n}\n\nstatic int instance_remove(anjay_t *anjay,\n                           const anjay_dm_object_def_t *const *obj_ptr,\n                           anjay_iid_t iid) {\n    (void) anjay;\n    time_object_t *obj = get_obj(obj_ptr);\n\n    pthread_mutex_lock(&obj->mutex);\n    int result = ANJAY_ERR_NOT_FOUND;\n    AVS_LIST(time_instance_t) *it;\n    AVS_LIST_FOREACH_PTR(it, &obj->instances) {\n        if ((*it)->iid == iid) {\n            release_instance(*it);\n            AVS_LIST_DELETE(it);\n            result = 0;\n            break;\n        } else if ((*it)->iid > iid) {\n            break;\n        }\n    }\n    assert(!result);\n    pthread_mutex_unlock(&obj->mutex);\n    return result;\n}\n\nstatic int instance_reset(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr,\n                          anjay_iid_t iid) {\n    (void) anjay;\n    time_object_t *obj = get_obj(obj_ptr);\n\n    pthread_mutex_lock(&obj->mutex);\n    time_instance_t *inst = find_instance(obj, iid);\n    assert(inst);\n    inst->application_type[0] = '\\0';\n    pthread_mutex_unlock(&obj->mutex);\n    return 0;\n}\n\nstatic int list_resources(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr,\n                          anjay_iid_t iid,\n                          anjay_dm_resource_list_ctx_t *ctx) {\n    (void) anjay;\n    (void) obj_ptr;\n    (void) iid;\n\n    anjay_dm_emit_res(ctx, RID_CURRENT_TIME, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, RID_FRACTIONAL_TIME, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT);\n    anjay_dm_emit_res(ctx, RID_APPLICATION_TYPE, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_PRESENT);\n    return 0;\n}\n\nstatic int resource_read(anjay_t *anjay,\n                         const anjay_dm_object_def_t *const *obj_ptr,\n                         anjay_iid_t iid,\n                         anjay_rid_t rid,\n                         anjay_riid_t riid,\n                         anjay_output_ctx_t *ctx) {\n    (void) anjay;\n    time_object_t *obj = get_obj(obj_ptr);\n\n    pthread_mutex_lock(&obj->mutex);\n    time_instance_t *inst = find_instance(obj, iid);\n    assert(inst);\n    int result;\n    switch (rid) {\n    case RID_CURRENT_TIME: {\n        assert(riid == ANJAY_ID_INVALID);\n        int64_t timestamp;\n        if (avs_time_real_to_scalar(&timestamp, AVS_TIME_S,\n                                    avs_time_real_now())) {\n            result = -1;\n        } else {\n            result = anjay_ret_i64(ctx, timestamp);\n        }\n        break;\n    }\n\n    case RID_APPLICATION_TYPE:\n        assert(riid == ANJAY_ID_INVALID);\n        result = anjay_ret_string(ctx, inst->application_type);\n        break;\n\n    default:\n        result = ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n    pthread_mutex_unlock(&obj->mutex);\n    return result;\n}\n\nstatic int resource_write(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr,\n                          anjay_iid_t iid,\n                          anjay_rid_t rid,\n                          anjay_riid_t riid,\n                          anjay_input_ctx_t *ctx) {\n    (void) anjay;\n    time_object_t *obj = get_obj(obj_ptr);\n\n    pthread_mutex_lock(&obj->mutex);\n    time_instance_t *inst = find_instance(obj, iid);\n    assert(inst);\n    int result;\n    switch (rid) {\n    case RID_APPLICATION_TYPE:\n        assert(riid == ANJAY_ID_INVALID);\n        result = anjay_get_string(ctx, inst->application_type,\n                                  sizeof(inst->application_type));\n        break;\n\n    default:\n        result = ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n    pthread_mutex_unlock(&obj->mutex);\n    return result;\n}\n\nint transaction_begin(anjay_t *anjay,\n                      const anjay_dm_object_def_t *const *obj_ptr) {\n    (void) anjay;\n    time_object_t *obj = get_obj(obj_ptr);\n\n    pthread_mutex_lock(&obj->mutex);\n    time_instance_t *element;\n    AVS_LIST_FOREACH(element, obj->instances) {\n        strcpy(element->application_type_backup, element->application_type);\n    }\n    pthread_mutex_unlock(&obj->mutex);\n    return 0;\n}\n\nint transaction_rollback(anjay_t *anjay,\n                         const anjay_dm_object_def_t *const *obj_ptr) {\n    (void) anjay;\n    time_object_t *obj = get_obj(obj_ptr);\n\n    pthread_mutex_lock(&obj->mutex);\n    time_instance_t *element;\n    AVS_LIST_FOREACH(element, obj->instances) {\n        strcpy(element->application_type, element->application_type_backup);\n    }\n    pthread_mutex_unlock(&obj->mutex);\n    return 0;\n}\n\nstatic const anjay_dm_object_def_t OBJ_DEF = {\n    .oid = 3333,\n    .handlers = {\n        .list_instances = list_instances,\n        .instance_create = instance_create,\n        .instance_remove = instance_remove,\n        .instance_reset = instance_reset,\n\n        .list_resources = list_resources,\n        .resource_read = resource_read,\n        .resource_write = resource_write,\n\n        .transaction_begin = transaction_begin,\n        .transaction_validate = anjay_dm_transaction_NOOP,\n        .transaction_commit = anjay_dm_transaction_NOOP,\n        .transaction_rollback = transaction_rollback\n    }\n};\n\nconst anjay_dm_object_def_t **time_object_create(void) {\n    pthread_mutexattr_t attr;\n    if (pthread_mutexattr_init(&attr)) {\n        return NULL;\n    }\n    // anjay_dm_emit() and anjay_dm_emit_res() may call other handlers,\n    // so we need a recursive mutex\n    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);\n\n    time_object_t *obj = (time_object_t *) avs_calloc(1, sizeof(time_object_t));\n    if (!obj) {\n        return NULL;\n    }\n    obj->def = &OBJ_DEF;\n\n    if (pthread_mutex_init(&obj->mutex, &attr)) {\n        pthread_mutexattr_destroy(&attr);\n        avs_free(obj);\n        return NULL;\n    }\n\n    pthread_mutexattr_destroy(&attr);\n    pthread_mutex_lock(&obj->mutex);\n    time_instance_t *inst = add_instance(obj, 0);\n    if (inst) {\n        strcpy(inst->application_type, \"Clock 0\");\n    }\n    pthread_mutex_unlock(&obj->mutex);\n\n    if (!inst) {\n        pthread_mutex_destroy(&obj->mutex);\n        avs_free(obj);\n        return NULL;\n    }\n\n    return &obj->def;\n}\n\nvoid time_object_release(const anjay_dm_object_def_t **def) {\n    if (def) {\n        time_object_t *obj = get_obj(def);\n        pthread_mutex_lock(&obj->mutex);\n        AVS_LIST_CLEAR(&obj->instances) {\n            release_instance(obj->instances);\n        }\n        pthread_mutex_unlock(&obj->mutex);\n        pthread_mutex_destroy(&obj->mutex);\n        avs_free(obj);\n    }\n}\n\nvoid time_object_notify(anjay_t *anjay, const anjay_dm_object_def_t **def) {\n    if (!anjay || !def) {\n        return;\n    }\n    time_object_t *obj = get_obj(def);\n    pthread_mutex_lock(&obj->mutex);\n    int64_t current_timestamp;\n    if (!avs_time_real_to_scalar(&current_timestamp, AVS_TIME_S,\n                                 avs_time_real_now())) {\n        AVS_LIST(time_instance_t) it;\n        AVS_LIST_FOREACH(it, obj->instances) {\n            if (it->last_notify_timestamp != current_timestamp) {\n                if (!anjay_notify_changed(anjay, 3333, it->iid,\n                                          RID_CURRENT_TIME)) {\n                    it->last_notify_timestamp = current_timestamp;\n                }\n            }\n        }\n    }\n    pthread_mutex_unlock(&obj->mutex);\n}\n"
  },
  {
    "path": "examples/tutorial/BC-ThreadSafety/src/time_object.h",
    "content": "#ifndef TIME_OBJECT_H\n#define TIME_OBJECT_H\n\n#include <anjay/dm.h>\n\nconst anjay_dm_object_def_t **time_object_create(void);\nvoid time_object_release(const anjay_dm_object_def_t **def);\nvoid time_object_notify(anjay_t *anjay, const anjay_dm_object_def_t **def);\n\n#endif // TIME_OBJECT_H\n"
  },
  {
    "path": "examples/tutorial/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.16)\nadd_subdirectory(BC-Initialization)\nadd_subdirectory(BC-MandatoryObjects)\nadd_subdirectory(BC-Security)\nadd_subdirectory(BC-ObjectImplementation)\nadd_subdirectory(BC-Notifications)\nadd_subdirectory(BC-Send)\nadd_subdirectory(BC-ThreadSafety)\nadd_subdirectory(firmware-update)\nadd_subdirectory(AT-AccessControl)\nadd_subdirectory(AT-Certificates)\nadd_subdirectory(AT-CustomObjects)\nadd_subdirectory(AT-Downloader)\nadd_subdirectory(AT-Persistence)\nadd_subdirectory(AT-CustomEventLoop)\nadd_subdirectory(AT-IpsoObjects)\nadd_subdirectory(AT-Bootstrap)\nadd_subdirectory(LwM2M-Gateway)\n"
  },
  {
    "path": "examples/tutorial/LwM2M-Gateway/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.16)\nproject(anjay-lwm2m-gateway C)\n\nset(CMAKE_C_STANDARD 99)\nset(CMAKE_C_EXTENSIONS OFF)\n\nadd_compile_options(-Wall -Wextra)\n\nfind_package(anjay REQUIRED)\n\nadd_executable(${PROJECT_NAME}\n               src/main.c\n               src/gateway_server.h\n               src/gateway_server.c\n               src/temperature_object.h\n               src/temperature_object.c)\ntarget_link_libraries(${PROJECT_NAME} PRIVATE anjay)\n"
  },
  {
    "path": "examples/tutorial/LwM2M-Gateway/end_device.py",
    "content": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nimport socket\nimport random\nimport math\nfrom argparse import ArgumentParser\n\nSOCKET_PATH = \"/tmp/lwm2m-gateway.sock\"\n\n\ndef run_end_device_simulator():\n    # Argument parser setup\n    parser = ArgumentParser(\n        description=\"Simulate an end device comunnicating with the LwM2M Gateway\")\n    parser.add_argument(\n        '-d',\n        '--device-id',\n        help=\"Specify the device ID (e.g., urn:dev:12345). If not provided, a random one will be generated.\",\n        default=None)\n\n    args = parser.parse_args()\n\n    device_id = \"\"\n    if args.device_id:\n        device_id = args.device_id\n        print(\"End device ID provided: \", device_id)\n        max_length = len(\"urn:dev:XXXXX\")\n        if len(device_id) > max_length:\n            print(f\"End device ID is too long - the maximum length is {max_length} characters\")\n            return\n    else:\n        random_int = random.randint(1, 65534)\n        device_id = f\"urn:dev:{random_int:05}\"\n        print(\"New random generated end device ID: \", device_id)\n\n    rand_temp = lambda: round(random.uniform(0, 100), 2)\n    temperature = rand_temp()\n    max_temperature = temperature\n    min_temperature = temperature\n\n    try:\n        client_socket = socket.socket(socket.AF_UNIX, socket.SOCK_SEQPACKET)\n        client_socket.connect(SOCKET_PATH)\n        print(\"Connected to the Gateway\")\n\n        while True:\n            # Wait for a message from the server\n            server_message = client_socket.recv(65535)\n            if server_message == b\"get temperature\":\n                print(\"Sending temperature\")\n                temperature = rand_temp()\n                if temperature > max_temperature:\n                    max_temperature = temperature\n                if temperature < min_temperature:\n                    min_temperature = temperature\n                client_socket.sendall(str(temperature).encode())\n            elif  server_message == b\"get id\":\n                # Message format: \"urn:dev:XXXXX\"\n                print(\"Sending device ID\")\n                client_socket.sendall(device_id.encode())\n            elif server_message == b\"get max\":\n                print(\"Sending max temperature\")\n                client_socket.sendall(str(round(max_temperature, 2)).encode())\n            elif server_message == b\"get min\":\n                print(\"Sending min temperature\")\n                client_socket.sendall(str(round(min_temperature, 2)).encode())\n            elif server_message == b\"reset\":\n                print(\"Resetting max and min temperature\")\n                max_temperature = temperature\n                min_temperature = temperature\n                client_socket.sendall(b\"OK\")\n            else:\n                raise Exception(\"Unexpected message from server\")\n\n    except KeyboardInterrupt:\n        print(\"End device simulator stopped\")\n    except Exception as e:\n        print(f\"Error: {e}\")\n    finally:\n        client_socket.close()\n\n\nif __name__ == \"__main__\":\n    run_end_device_simulator()\n"
  },
  {
    "path": "examples/tutorial/LwM2M-Gateway/src/gateway_server.c",
    "content": "#include <anjay/anjay.h>\n#include <anjay/lwm2m_gateway.h>\n\n#include <avsystem/commons/avs_list.h>\n#include <avsystem/commons/avs_log.h>\n#include <avsystem/commons/avs_sched.h>\n\n#include <errno.h>\n#include <poll.h>\n#include <stdio.h>\n#include <string.h>\n#include <sys/socket.h>\n#include <sys/un.h>\n#include <unistd.h>\n\n#include \"gateway_server.h\"\n#include \"temperature_object.h\"\n\nstatic avs_sched_handle_t serve_gateway_job_handle;\n\nstatic void cleanup_end_device(anjay_t *anjay, end_device_t *end_device) {\n    close(end_device->cl_poll_fd.fd);\n\n    if (end_device->iid == ANJAY_ID_INVALID) {\n        return;\n    }\n    avs_sched_del(&end_device->notify_job_handle);\n    avs_sched_del(&end_device->evaluation_period_job_handle);\n    if (end_device->temperature_object) {\n        if (anjay_lwm2m_gateway_unregister_object(\n                    anjay, end_device->iid, end_device->temperature_object)) {\n            avs_log(tutorial, ERROR, \"Failed to unregister Temperature Object\");\n        }\n        temperature_object_release(end_device->temperature_object);\n    }\n    if (anjay_lwm2m_gateway_deregister_device(anjay, end_device->iid)) {\n        avs_log(tutorial, ERROR, \"Failed to deregister End Device\");\n    }\n}\n\ntypedef struct {\n    anjay_t *anjay;\n    end_device_t *end_device;\n} job_args_t;\n\nstatic void calculate_evaluation_period_job(avs_sched_t *sched,\n                                            const void *args_ptr) {\n    const job_args_t *args = (const job_args_t *) args_ptr;\n\n    // Schedule run of the same function to track the evaluation period\n    // continuously\n    AVS_SCHED_DELAYED(sched, &args->end_device->evaluation_period_job_handle,\n                      avs_time_duration_from_scalar(EVALUATION_CALC_JOB_PERIOD,\n                                                    AVS_TIME_S),\n                      calculate_evaluation_period_job, args, sizeof(*args));\n\n    int32_t prev_evaluation_period = args->end_device->evaluation_period;\n    int32_t new_evaluation_period = DEFAULT_MAXIMAL_EVALUATION_PERIOD;\n\n    temperature_object_evaluation_period_update_value(\n            args->anjay,\n            args->end_device->temperature_object,\n            &new_evaluation_period);\n    if (new_evaluation_period == prev_evaluation_period) {\n        return;\n    }\n    args->end_device->evaluation_period = new_evaluation_period;\n\n    // if evaluation period has changed, notify job should be rescheduled\n    // accordingly to new period\n    avs_time_monotonic_t new_notify_instant = avs_time_monotonic_add(\n            avs_time_monotonic_add(\n                    avs_sched_time(&args->end_device->notify_job_handle),\n                    avs_time_duration_from_scalar(-prev_evaluation_period,\n                                                  AVS_TIME_S)),\n            avs_time_duration_from_scalar(new_evaluation_period, AVS_TIME_S));\n    AVS_RESCHED_AT(&args->end_device->notify_job_handle, new_notify_instant);\n}\n\nstatic void notify_job(avs_sched_t *sched, const void *args_ptr) {\n    const job_args_t *args = (const job_args_t *) args_ptr;\n\n    temperature_object_update_value(args->anjay,\n                                    args->end_device->temperature_object);\n\n    AVS_SCHED_DELAYED(sched, &args->end_device->notify_job_handle,\n                      avs_time_duration_from_scalar(\n                              args->end_device->evaluation_period, AVS_TIME_S),\n                      notify_job, args, sizeof(*args));\n}\n\nstatic int setup_end_device(gateway_srv_t *gateway_srv,\n                            end_device_t *end_device,\n                            const char *msg) {\n    anjay_iid_t iid = ANJAY_ID_INVALID;\n    anjay_t *anjay = gateway_srv->anjay;\n\n    strcpy(end_device->end_device_name, msg);\n    if (anjay_lwm2m_gateway_register_device(anjay, end_device->end_device_name,\n                                            &iid)) {\n        avs_log(tutorial, ERROR, \"Failed to add End Device\");\n        return -1;\n    }\n    end_device->iid = iid;\n    end_device->evaluation_period = DEFAULT_MAXIMAL_EVALUATION_PERIOD;\n\n    const anjay_dm_object_def_t **obj =\n            temperature_object_create(iid, gateway_srv);\n    if (!obj) {\n        avs_log(tutorial, ERROR, \"Failed to create Temperature Object\");\n        return -1;\n    }\n    end_device->temperature_object = obj;\n\n    if (anjay_lwm2m_gateway_register_object(anjay, iid, obj)) {\n        avs_log(tutorial, ERROR, \"Failed to register Temperature Object\");\n        return -1;\n    }\n\n    calculate_evaluation_period_job(anjay_get_scheduler(anjay),\n                                    &(const job_args_t) {\n                                        .anjay = anjay,\n                                        .end_device = end_device\n                                    });\n\n    notify_job(anjay_get_scheduler(anjay),\n               &(const job_args_t) {\n                   .anjay = anjay,\n                   .end_device = end_device\n               });\n\n    avs_log(tutorial, INFO, \"End Device %s added\", end_device->end_device_name);\n    return 0;\n}\n\nstatic int request_process(end_device_t *end_device,\n                           gateway_request_type_t request_type,\n                           char *out_buffer,\n                           size_t out_buffer_size) {\n    static const char *const request_value = \"get temperature\";\n    static const char *const request_id = \"get id\";\n    static const char *const request_max_measured_value = \"get max\";\n    static const char *const request_min_measured_value = \"get min\";\n    static const char *const request_reset_min_max = \"reset\";\n\n    struct pollfd *cl_poll_fd = &end_device->cl_poll_fd;\n    const char *request;\n    switch (request_type) {\n    case GATEWAY_REQUEST_TYPE_GET_ID:\n        request = request_id;\n        break;\n    case GATEWAY_REQUEST_TYPE_GET_TEMPERATURE:\n        request = request_value;\n        break;\n    case GATEWAY_REQUEST_TYPE_GET_MAX_MEASURED_VALUE:\n        request = request_max_measured_value;\n        break;\n    case GATEWAY_REQUEST_TYPE_GET_MIN_MEASURED_VALUE:\n        request = request_min_measured_value;\n        break;\n    case GATEWAY_REQUEST_TYPE_RESET_MIN_AND_MAX_MEASURED_VALUES:\n        request = request_reset_min_max;\n        break;\n    default:\n        return -1;\n    }\n\n    if (write(cl_poll_fd->fd, request, strlen(request)) == -1) {\n        avs_log(tutorial, ERROR, \"Failed to send request to client %d\",\n                cl_poll_fd->fd);\n        return -1;\n    }\n    // timeout set to 1 second\n    if ((poll(cl_poll_fd, 1, 1000) > 0) && (cl_poll_fd->revents & POLLIN)) {\n        // leave one byte for null terminator\n        int bytes_read = read(cl_poll_fd->fd, out_buffer, out_buffer_size - 1);\n        if (bytes_read <= 0) {\n            avs_log(tutorial, INFO, \"Connection closed by client %d\",\n                    cl_poll_fd->fd);\n            return -1;\n        }\n        out_buffer[bytes_read] = '\\0';\n        avs_log(tutorial, INFO, \"Received message: %s\", out_buffer);\n    } else {\n        avs_log(tutorial, WARNING, \"No response from client\");\n        return -1;\n    }\n    return 0;\n}\n\nint gateway_request(gateway_srv_t *gateway_srv,\n                    anjay_iid_t end_device_iid,\n                    gateway_request_type_t request_type,\n                    char *out_buffer,\n                    size_t out_buffer_size) {\n    AVS_LIST(end_device_t) end_device;\n    AVS_LIST_FOREACH(end_device, gateway_srv->end_devices) {\n        if (end_device->iid == end_device_iid) {\n            return request_process(end_device, request_type, out_buffer,\n                                   out_buffer_size);\n        }\n    }\n    avs_log(tutorial, ERROR, \"End Device not found\");\n    return -1;\n}\n\nstatic void serve_gateway_job(avs_sched_t *sched, const void *args_ptr) {\n    gateway_srv_t *gateway_srv = *(gateway_srv_t *const *) args_ptr;\n\n    // Discover incoming connections\n    struct pollfd srv_poll_fd = {\n        .fd = gateway_srv->srv_socket,\n        .events = POLLIN\n    };\n    if ((poll(&srv_poll_fd, 1, 0) > 0) && srv_poll_fd.revents & POLLIN) {\n        int client_socket = accept(gateway_srv->srv_socket, NULL, NULL);\n        if (client_socket == -1) {\n            avs_log(tutorial, ERROR, \"Failed to accept a new connection %s\",\n                    strerror(errno));\n        } else {\n            avs_log(tutorial, INFO, \"New connection accepted %d\",\n                    client_socket);\n\n            AVS_LIST(end_device_t) new_end_device =\n                    AVS_LIST_NEW_ELEMENT(end_device_t);\n            assert(new_end_device);\n            new_end_device->cl_poll_fd.fd = client_socket;\n            new_end_device->cl_poll_fd.events = POLLIN;\n            new_end_device->temperature_object = NULL;\n            new_end_device->iid = ANJAY_ID_INVALID;\n\n            // register new end device\n            char buffer[END_DEVICE_NAME_LEN];\n            if (request_process(new_end_device, GATEWAY_REQUEST_TYPE_GET_ID,\n                                buffer, END_DEVICE_NAME_LEN)\n                    || setup_end_device(gateway_srv, new_end_device, buffer)) {\n                cleanup_end_device(gateway_srv->anjay, new_end_device);\n                AVS_LIST_DELETE(&new_end_device);\n                avs_log(tutorial, ERROR, \"Failed to add new end device\");\n            } else {\n                AVS_LIST_INSERT(&gateway_srv->end_devices, new_end_device);\n            }\n        }\n    }\n\n    AVS_LIST(end_device_t) *ptr;\n    AVS_LIST(end_device_t) helper;\n    AVS_LIST_DELETABLE_FOREACH_PTR(ptr, helper, &gateway_srv->end_devices) {\n        end_device_t *end_device = *ptr;\n        int ret = poll(&end_device->cl_poll_fd, 1, 0);\n        if (ret == -1\n                || (end_device->cl_poll_fd.revents\n                    & (POLLERR | POLLHUP | POLLNVAL))) {\n            cleanup_end_device(gateway_srv->anjay, end_device);\n            AVS_LIST_DELETE(ptr);\n            avs_log(tutorial, INFO, \"End Device removed\");\n        }\n    }\n    // Schedule run of the same function after 1 second\n    AVS_SCHED_DELAYED(sched, &serve_gateway_job_handle,\n                      avs_time_duration_from_scalar(1, AVS_TIME_S),\n                      serve_gateway_job, &gateway_srv, sizeof(gateway_srv));\n}\n\nint gateway_setup_server(gateway_srv_t *gateway_srv) {\n    struct sockaddr_un server_addr;\n\n    if ((gateway_srv->srv_socket = socket(AF_UNIX, SOCK_SEQPACKET, 0)) == -1) {\n        avs_log(tutorial, ERROR, \"Failed to create a socket %s\",\n                strerror(errno));\n        return -1;\n    }\n\n    memset(&server_addr, 0, sizeof(server_addr));\n    server_addr.sun_family = AF_UNIX;\n    avs_simple_snprintf(server_addr.sun_path, sizeof(server_addr.sun_path),\n                        \"%s\", SOCKET_PATH);\n\n    // remove the socket file if it already exists\n    unlink(SOCKET_PATH);\n\n    if (bind(gateway_srv->srv_socket, (struct sockaddr *) &server_addr,\n             sizeof(server_addr))\n            == -1) {\n        avs_log(tutorial, ERROR, \"Failed to bind a socket %s\", strerror(errno));\n        gateway_cleanup_server(gateway_srv);\n        return -1;\n    }\n\n    if (listen(gateway_srv->srv_socket, 1) == -1) {\n        avs_log(tutorial, ERROR, \"Failed to listen on a socket %s\",\n                strerror(errno));\n        gateway_cleanup_server(gateway_srv);\n        return -1;\n    }\n    gateway_srv->end_devices = NULL;\n\n    avs_log(tutorial, INFO, \"Local server is listening on %s\", SOCKET_PATH);\n\n    serve_gateway_job(anjay_get_scheduler(gateway_srv->anjay), &gateway_srv);\n    return 0;\n}\n\nvoid gateway_cleanup_server(gateway_srv_t *gateway_srv) {\n    close(gateway_srv->srv_socket);\n    unlink(SOCKET_PATH);\n    AVS_LIST(end_device_t) list = gateway_srv->end_devices;\n    AVS_LIST_CLEAR(&list) {\n        cleanup_end_device(gateway_srv->anjay, list);\n    }\n\n    avs_sched_del(&serve_gateway_job_handle);\n}\n"
  },
  {
    "path": "examples/tutorial/LwM2M-Gateway/src/gateway_server.h",
    "content": "#ifndef GATEWAY_SERVER_H\n#define GATEWAY_SERVER_H\n\n#include <poll.h>\n\n#include <avsystem/commons/avs_list.h>\n\n#include <anjay/dm.h>\n\n#define SOCKET_PATH \"/tmp/lwm2m-gateway.sock\"\n\n#define END_DEVICE_NAME_LEN sizeof(\"urn:dev:00000\")\n#define VALUE_MESSAGE_MAX_LEN sizeof(\"xx.yy\")\n#define EXECUTE_MSG_RESPONSE_LEN sizeof(\"OK\")\n\n#define DEFAULT_MAXIMAL_EVALUATION_PERIOD 60\n#define EVALUATION_CALC_JOB_PERIOD 1\n\ntypedef struct {\n    struct pollfd cl_poll_fd;\n    const anjay_dm_object_def_t **temperature_object;\n    anjay_iid_t iid;\n    char end_device_name[END_DEVICE_NAME_LEN];\n    int32_t evaluation_period;\n    avs_sched_handle_t notify_job_handle;\n    avs_sched_handle_t evaluation_period_job_handle;\n} end_device_t;\n\ntypedef struct {\n    anjay_t *anjay;\n    int srv_socket;\n    AVS_LIST(end_device_t) end_devices;\n} gateway_srv_t;\n\ntypedef enum {\n    GATEWAY_REQUEST_TYPE_GET_ID,\n    GATEWAY_REQUEST_TYPE_GET_TEMPERATURE,\n    GATEWAY_REQUEST_TYPE_GET_MAX_MEASURED_VALUE,\n    GATEWAY_REQUEST_TYPE_GET_MIN_MEASURED_VALUE,\n    GATEWAY_REQUEST_TYPE_RESET_MIN_AND_MAX_MEASURED_VALUES\n} gateway_request_type_t;\n\nint gateway_setup_server(gateway_srv_t *gateway_srv);\nvoid gateway_cleanup_server(gateway_srv_t *gateway_srv);\nint gateway_request(gateway_srv_t *gateway_srv,\n                    anjay_iid_t end_device_iid,\n                    gateway_request_type_t request_type,\n                    char *out_buffer,\n                    size_t out_buffer_size);\n\n#endif // GATEWAY_SERVER_H\n"
  },
  {
    "path": "examples/tutorial/LwM2M-Gateway/src/main.c",
    "content": "#include <anjay/anjay.h>\n#include <anjay/download.h>\n#include <anjay/lwm2m_gateway.h>\n#include <anjay/security.h>\n#include <anjay/server.h>\n\n#include <avsystem/commons/avs_log.h>\n#include <avsystem/commons/avs_sched.h>\n\n#include <stdio.h>\n#include <string.h>\n\n#include \"gateway_server.h\"\n#include \"temperature_object.h\"\n\n// Installs Security Object and adds an instance of it.\n// An instance of Security Object provides information needed to connect to\n// LwM2M server.\nstatic int setup_security_object(anjay_t *anjay) {\n    if (anjay_security_object_install(anjay)) {\n        return -1;\n    }\n\n    static const char PSK_IDENTITY[] = \"identity\";\n    static const char PSK_KEY[] = \"P4s$w0rd\";\n\n    anjay_security_instance_t security_instance = {\n        .ssid = 1,\n        .server_uri = \"coaps://eu.iot.avsystem.cloud:5684\",\n        .security_mode = ANJAY_SECURITY_PSK,\n        .public_cert_or_psk_identity = (const uint8_t *) PSK_IDENTITY,\n        .public_cert_or_psk_identity_size = strlen(PSK_IDENTITY),\n        .private_cert_or_psk_key = (const uint8_t *) PSK_KEY,\n        .private_cert_or_psk_key_size = strlen(PSK_KEY)\n    };\n\n    // Anjay will assign Instance ID automatically\n    anjay_iid_t security_instance_id = ANJAY_ID_INVALID;\n    if (anjay_security_object_add_instance(anjay, &security_instance,\n                                           &security_instance_id)) {\n        return -1;\n    }\n\n    return 0;\n}\n\n// Installs Server Object and adds an instance of it.\n// An instance of Server Object provides the data related to a LwM2M Server.\nstatic int setup_server_object(anjay_t *anjay) {\n    if (anjay_server_object_install(anjay)) {\n        return -1;\n    }\n\n    const anjay_server_instance_t server_instance = {\n        // Server Short ID\n        .ssid = 1,\n        // Client will send Update message often than every 60 seconds\n        .lifetime = 60,\n        // Disable Default Minimum Period resource\n        .default_min_period = -1,\n        // Disable Default Maximum Period resource\n        .default_max_period = -1,\n        // Disable Disable Timeout resource\n        .disable_timeout = -1,\n        // Sets preferred transport to UDP\n        .binding = \"U\"\n    };\n\n    // Anjay will assign Instance ID automatically\n    anjay_iid_t server_instance_id = ANJAY_ID_INVALID;\n    if (anjay_server_object_add_instance(anjay, &server_instance,\n                                         &server_instance_id)) {\n        return -1;\n    }\n\n    return 0;\n}\n\n// Periodically issues a Send message with measured values of the temperature\nstatic void send_job(avs_sched_t *sched, const void *args_ptr) {\n    gateway_srv_t *gateway_srv = *(gateway_srv_t *const *) args_ptr;\n\n    temperature_object_send(gateway_srv->anjay, gateway_srv->end_devices);\n\n    // Schedule run of the same function after 10 seconds\n    AVS_SCHED_DELAYED(sched, NULL,\n                      avs_time_duration_from_scalar(10, AVS_TIME_S), send_job,\n                      &gateway_srv, sizeof(gateway_srv));\n}\n\nint main(int argc, char *argv[]) {\n    if (argc != 2) {\n        avs_log(tutorial, ERROR, \"usage: %s ENDPOINT_NAME\", argv[0]);\n        return -1;\n    }\n\n    const anjay_configuration_t CONFIG = {\n        .endpoint_name = argv[1],\n        .in_buffer_size = 4000,\n        .out_buffer_size = 4000\n    };\n\n    anjay_t *anjay = anjay_new(&CONFIG);\n    if (!anjay) {\n        avs_log(tutorial, ERROR, \"Could not create Anjay object\");\n        return -1;\n    }\n\n    int result = 0;\n    if (setup_security_object(anjay) || setup_server_object(anjay)) {\n        result = -1;\n    }\n\n    if (!result && anjay_lwm2m_gateway_install(anjay)) {\n        avs_log(tutorial, ERROR, \"Failed to add /25 Gateway Object\");\n        result = -1;\n    }\n\n    gateway_srv_t gateway_srv = { 0 };\n    gateway_srv_t *gateway_srv_ptr = &gateway_srv;\n    gateway_srv.anjay = anjay;\n    if (!result && gateway_setup_server(&gateway_srv)) {\n        avs_log(tutorial, ERROR, \"Failed to setup local server\");\n        result = -1;\n    }\n\n    if (!result) {\n        // Run send_job the first time;\n        // this will schedule periodic calls to themselves via the scheduler\n        send_job(anjay_get_scheduler(anjay), &gateway_srv_ptr);\n\n        result = anjay_event_loop_run(\n                anjay, avs_time_duration_from_scalar(1, AVS_TIME_S));\n    }\n\n    gateway_cleanup_server(&gateway_srv);\n    anjay_delete(anjay);\n    return result;\n}\n"
  },
  {
    "path": "examples/tutorial/LwM2M-Gateway/src/temperature_object.c",
    "content": "/**\n * Generated by anjay_codegen.py on 2024-10-03 12:40:55\n *\n * LwM2M Object: Temperature\n * ID: 3303, URN: urn:oma:lwm2m:ext:3303:1.1, Optional, Multiple\n *\n * This IPSO object should be used with a temperature sensor to report a\n * temperature measurement.  It also provides resources for\n * minimum/maximum measured values and the minimum/maximum range that can\n * be measured by the temperature sensor. An example measurement unit is\n * degrees Celsius.\n */\n#include <assert.h>\n#include <stdbool.h>\n\n#include <avsystem/commons/avs_defs.h>\n#include <avsystem/commons/avs_list.h>\n#include <avsystem/commons/avs_log.h>\n#include <avsystem/commons/avs_memory.h>\n\n#include <anjay/anjay.h>\n#include <anjay/dm.h>\n#include <anjay/lwm2m_gateway.h>\n#include <anjay/lwm2m_send.h>\n\n#include \"gateway_server.h\"\n#include \"temperature_object.h\"\n\n#define CACHE_VALID_PERIOD_S 3\n\n#define OID_TEMPERATURE 3303\n\n/**\n * Min Measured Value: R, Single, Optional\n * type: float, range: N/A, unit: N/A\n * The minimum value measured by the sensor since power ON or reset.\n */\n#define RID_MIN_MEASURED_VALUE 5601\n\n/**\n * Max Measured Value: R, Single, Optional\n * type: float, range: N/A, unit: N/A\n * The maximum value measured by the sensor since power ON or reset.\n */\n#define RID_MAX_MEASURED_VALUE 5602\n\n/**\n * Reset Min and Max Measured Values: E, Single, Optional\n * type: N/A, range: N/A, unit: N/A\n * Reset the Min and Max Measured Values to Current Value.\n */\n#define RID_RESET_MIN_AND_MAX_MEASURED_VALUES 5605\n\n/**\n * Sensor Value: R, Single, Mandatory\n * type: float, range: N/A, unit: N/A\n * Last or Current Measured Value from the Sensor.\n */\n#define RID_SENSOR_VALUE 5700\n\n/**\n * Application Type: RW, Single, Optional\n * type: string, range: N/A, unit: N/A\n * The application type of the sensor or actuator as a string depending\n * on the use case.\n */\n#define RID_APPLICATION_TYPE 5750\n\ntypedef struct cached_value_struct {\n    double value;\n    avs_time_monotonic_t timestamp;\n} cached_value_t;\n\ntypedef struct temperature_instance_struct {\n    anjay_iid_t iid;\n\n    char application_type[10];\n    char application_type_backup[10];\n\n    cached_value_t max_meas_cached_value;\n    cached_value_t min_meas_cached_value;\n    cached_value_t sensor_meas_cached_value;\n} temperature_instance_t;\n\ntypedef struct temperature_object_struct {\n    const anjay_dm_object_def_t *def;\n    temperature_instance_t instances[1];\n    gateway_srv_t *gateway_srv;\n    anjay_iid_t end_device_iid;\n} temperature_object_t;\n\nstatic gateway_request_type_t rid_to_request_type(anjay_rid_t rid) {\n    switch (rid) {\n    case RID_MIN_MEASURED_VALUE:\n        return GATEWAY_REQUEST_TYPE_GET_MIN_MEASURED_VALUE;\n    case RID_MAX_MEASURED_VALUE:\n        return GATEWAY_REQUEST_TYPE_GET_MAX_MEASURED_VALUE;\n    case RID_SENSOR_VALUE:\n        return GATEWAY_REQUEST_TYPE_GET_TEMPERATURE;\n    default:\n        return -1;\n    }\n}\n\nstatic cached_value_t *rid_to_cached_value(temperature_instance_t *inst,\n                                           anjay_rid_t rid) {\n    switch (rid) {\n    case RID_MIN_MEASURED_VALUE:\n        return &inst->min_meas_cached_value;\n    case RID_MAX_MEASURED_VALUE:\n        return &inst->max_meas_cached_value;\n    case RID_SENSOR_VALUE:\n        return &inst->sensor_meas_cached_value;\n    default:\n        return NULL;\n    }\n}\n\nstatic inline temperature_object_t *\nget_obj(const anjay_dm_object_def_t *const *obj_ptr) {\n    assert(obj_ptr);\n    return AVS_CONTAINER_OF(obj_ptr, temperature_object_t, def);\n}\n\nstatic int list_instances(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr,\n                          anjay_dm_list_ctx_t *ctx) {\n    (void) anjay;\n\n    temperature_object_t *obj = get_obj(obj_ptr);\n    for (anjay_iid_t iid = 0; iid < AVS_ARRAY_SIZE(obj->instances); iid++) {\n        anjay_dm_emit(ctx, iid);\n    }\n\n    return 0;\n}\n\nstatic int instance_reset(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr,\n                          anjay_iid_t iid) {\n    (void) anjay;\n\n    temperature_object_t *obj = get_obj(obj_ptr);\n    assert(iid < AVS_ARRAY_SIZE(obj->instances));\n    temperature_instance_t *inst = &obj->instances[iid];\n\n    inst->application_type[0] = '\\0';\n    return 0;\n}\n\nstatic int list_resources(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr,\n                          anjay_iid_t iid,\n                          anjay_dm_resource_list_ctx_t *ctx) {\n    (void) anjay;\n    (void) obj_ptr;\n    (void) iid;\n\n    anjay_dm_emit_res(ctx, RID_MIN_MEASURED_VALUE, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, RID_MAX_MEASURED_VALUE, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, RID_RESET_MIN_AND_MAX_MEASURED_VALUES,\n                      ANJAY_DM_RES_E, ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, RID_SENSOR_VALUE, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, RID_APPLICATION_TYPE, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_PRESENT);\n    return 0;\n}\n\nstatic int get_eid_resource_value(temperature_object_t *obj,\n                                  anjay_rid_t rid,\n                                  cached_value_t *cached_value,\n                                  bool force_update) {\n    avs_time_monotonic_t current_time = avs_time_monotonic_now();\n\n    if (!force_update) {\n        int64_t diff;\n        if (avs_time_duration_to_scalar(\n                    &diff, AVS_TIME_S,\n                    avs_time_monotonic_diff(current_time,\n                                            cached_value->timestamp))) {\n            return ANJAY_ERR_INTERNAL;\n        }\n\n        if (diff < CACHE_VALID_PERIOD_S) {\n            return 0;\n        }\n    }\n    int res;\n    char buffer[VALUE_MESSAGE_MAX_LEN];\n    if ((res = gateway_request(obj->gateway_srv, obj->end_device_iid,\n                               rid_to_request_type(rid), buffer,\n                               VALUE_MESSAGE_MAX_LEN))) {\n        return res;\n    }\n\n    cached_value->value = atof(buffer);\n    cached_value->timestamp = current_time;\n    return 0;\n}\n\nstatic void update_resource(anjay_t *anjay,\n                            temperature_object_t *obj,\n                            temperature_instance_t *inst,\n                            anjay_rid_t rid) {\n    anjay_resource_observation_status_t status =\n            anjay_lwm2m_gateway_resource_observation_status(anjay,\n                                                            obj->end_device_iid,\n                                                            OID_TEMPERATURE,\n                                                            inst->iid, rid);\n    if (status.is_observed) {\n        cached_value_t *cached_value = rid_to_cached_value(inst, rid);\n        double prev_value = cached_value->value;\n        get_eid_resource_value(obj, rid, cached_value, true);\n\n        if (prev_value != cached_value->value) {\n            anjay_lwm2m_gateway_notify_changed(anjay, obj->end_device_iid,\n                                               OID_TEMPERATURE, inst->iid, rid);\n        }\n    }\n}\n\nstatic void evaluation_period_update_value(anjay_t *anjay,\n                                           temperature_object_t *obj,\n                                           temperature_instance_t *inst,\n                                           anjay_rid_t rid,\n                                           int32_t *max_evaluation_period) {\n    anjay_resource_observation_status_t status =\n            anjay_lwm2m_gateway_resource_observation_status(anjay,\n                                                            obj->end_device_iid,\n                                                            OID_TEMPERATURE,\n                                                            inst->iid, rid);\n\n    if (status.is_observed && status.max_eval_period != ANJAY_ATTRIB_PERIOD_NONE\n            && *max_evaluation_period > status.max_eval_period) {\n        *max_evaluation_period = status.max_eval_period;\n    }\n}\n\nvoid temperature_object_evaluation_period_update_value(\n        anjay_t *anjay,\n        const anjay_dm_object_def_t **def,\n        int32_t *evaluation_period) {\n    assert(anjay);\n    temperature_object_t *obj = get_obj(def);\n\n    for (size_t iid = 0; iid < AVS_ARRAY_SIZE(obj->instances); iid++) {\n        evaluation_period_update_value(anjay, obj, &obj->instances[iid],\n                                       RID_MIN_MEASURED_VALUE,\n                                       evaluation_period);\n        evaluation_period_update_value(anjay, obj, &obj->instances[iid],\n                                       RID_MAX_MEASURED_VALUE,\n                                       evaluation_period);\n        evaluation_period_update_value(anjay, obj, &obj->instances[iid],\n                                       RID_SENSOR_VALUE, evaluation_period);\n    }\n}\n\nvoid temperature_object_update_value(anjay_t *anjay,\n                                     const anjay_dm_object_def_t **def) {\n    assert(anjay);\n    temperature_object_t *obj = get_obj(def);\n\n    for (size_t iid = 0; iid < AVS_ARRAY_SIZE(obj->instances); iid++) {\n        update_resource(anjay, obj, &obj->instances[iid],\n                        RID_MIN_MEASURED_VALUE);\n        update_resource(anjay, obj, &obj->instances[iid],\n                        RID_MAX_MEASURED_VALUE);\n        update_resource(anjay, obj, &obj->instances[iid], RID_SENSOR_VALUE);\n    }\n}\n\nstatic int resource_read(anjay_t *anjay,\n                         const anjay_dm_object_def_t *const *obj_ptr,\n                         anjay_iid_t iid,\n                         anjay_rid_t rid,\n                         anjay_riid_t riid,\n                         anjay_output_ctx_t *ctx) {\n    (void) anjay;\n    assert(riid == ANJAY_ID_INVALID);\n\n    temperature_object_t *obj = get_obj(obj_ptr);\n    assert(iid < AVS_ARRAY_SIZE(obj->instances));\n    temperature_instance_t *inst = &obj->instances[iid];\n    int res;\n\n    switch (rid) {\n    case RID_MIN_MEASURED_VALUE:\n    case RID_MAX_MEASURED_VALUE:\n    case RID_SENSOR_VALUE: {\n        cached_value_t *cached_value = rid_to_cached_value(inst, rid);\n        res = get_eid_resource_value(obj, rid, cached_value, false);\n        return res ? ANJAY_ERR_INTERNAL\n                   : anjay_ret_double(ctx, cached_value->value);\n    }\n    case RID_APPLICATION_TYPE:\n        return anjay_ret_string(ctx, inst->application_type);\n\n    default:\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\nstatic int resource_write(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr,\n                          anjay_iid_t iid,\n                          anjay_rid_t rid,\n                          anjay_riid_t riid,\n                          anjay_input_ctx_t *ctx) {\n    (void) anjay;\n\n    temperature_object_t *obj = get_obj(obj_ptr);\n    assert(iid < AVS_ARRAY_SIZE(obj->instances));\n    temperature_instance_t *inst = &obj->instances[iid];\n\n    switch (rid) {\n    case RID_APPLICATION_TYPE: {\n        assert(riid == ANJAY_ID_INVALID);\n        return anjay_get_string(ctx, inst->application_type,\n                                sizeof(inst->application_type));\n    }\n\n    default:\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\nstatic int resource_execute(anjay_t *anjay,\n                            const anjay_dm_object_def_t *const *obj_ptr,\n                            anjay_iid_t iid,\n                            anjay_rid_t rid,\n                            anjay_execute_ctx_t *arg_ctx) {\n    (void) anjay;\n    (void) arg_ctx;\n\n    temperature_object_t *obj = get_obj(obj_ptr);\n    assert(iid < AVS_ARRAY_SIZE(obj->instances));\n    char buffer[EXECUTE_MSG_RESPONSE_LEN];\n\n    switch (rid) {\n    case RID_RESET_MIN_AND_MAX_MEASURED_VALUES:\n        return gateway_request(\n                obj->gateway_srv, obj->end_device_iid,\n                GATEWAY_REQUEST_TYPE_RESET_MIN_AND_MAX_MEASURED_VALUES, buffer,\n                EXECUTE_MSG_RESPONSE_LEN);\n\n    default:\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\nstatic int transaction_begin(anjay_t *anjay,\n                             const anjay_dm_object_def_t *const *obj_ptr) {\n    (void) anjay;\n\n    temperature_object_t *obj = get_obj(obj_ptr);\n    temperature_instance_t *inst = &obj->instances[0];\n    strcpy(inst->application_type_backup, inst->application_type);\n\n    return 0;\n}\n\nstatic int transaction_rollback(anjay_t *anjay,\n                                const anjay_dm_object_def_t *const *obj_ptr) {\n    (void) anjay;\n\n    temperature_object_t *obj = get_obj(obj_ptr);\n    temperature_instance_t *inst = &obj->instances[0];\n    strcpy(inst->application_type, inst->application_type_backup);\n\n    return 0;\n}\n\nstatic const anjay_dm_object_def_t OBJ_DEF = {\n    .oid = OID_TEMPERATURE,\n    .version = \"1.1\",\n    .handlers = {\n        .list_instances = list_instances,\n        .instance_reset = instance_reset,\n\n        .list_resources = list_resources,\n        .resource_read = resource_read,\n        .resource_write = resource_write,\n        .resource_execute = resource_execute,\n\n        .transaction_begin = transaction_begin,\n        .transaction_validate = anjay_dm_transaction_NOOP,\n        .transaction_commit = anjay_dm_transaction_NOOP,\n        .transaction_rollback = transaction_rollback\n    }\n};\n\nconst anjay_dm_object_def_t **\ntemperature_object_create(int id, gateway_srv_t *gateway_srv) {\n    temperature_object_t *obj =\n            (temperature_object_t *) avs_calloc(1,\n                                                sizeof(temperature_object_t));\n    if (!obj) {\n        return NULL;\n    }\n    obj->def = &OBJ_DEF;\n\n    sprintf(obj->instances[0].application_type, \"Sensor %d\", id);\n    obj->end_device_iid = id;\n    obj->gateway_srv = gateway_srv;\n\n    return &obj->def;\n}\n\nvoid temperature_object_release(const anjay_dm_object_def_t **def) {\n    if (def) {\n        temperature_object_t *obj = get_obj(def);\n        avs_free(obj);\n    }\n}\n\nstatic void send_finished_handler(anjay_t *anjay,\n                                  anjay_ssid_t ssid,\n                                  const anjay_send_batch_t *batch,\n                                  int result,\n                                  void *data) {\n    (void) anjay;\n    (void) ssid;\n    (void) batch;\n    (void) data;\n    if (result != ANJAY_SEND_SUCCESS) {\n        avs_log(temperature_object, ERROR, \"Send failed, result: %d\", result);\n    } else {\n        avs_log(temperature_object, INFO, \"Send successful\");\n    }\n}\n\nvoid temperature_object_send(anjay_t *anjay,\n                             AVS_LIST(end_device_t) end_devices) {\n    if (!anjay) {\n        return;\n    }\n    const anjay_ssid_t server_ssid = 1;\n\n    if (!end_devices) {\n        avs_log(temperature_object, TRACE,\n                \"No end devices found, skipping sending data\");\n        return;\n    }\n\n    // Allocate new batch builder.\n    anjay_send_batch_builder_t *builder = anjay_send_batch_builder_new();\n    if (!builder) {\n        avs_log(temperature_object, ERROR, \"Failed to allocate batch builder\");\n        return;\n    }\n\n    AVS_LIST(end_device_t) it;\n    AVS_LIST_FOREACH(it, end_devices) {\n        // Add current values of resource from Temperature Object.\n        temperature_object_t *obj = get_obj(it->temperature_object);\n        temperature_instance_t *inst = &obj->instances[0];\n        if (anjay_lwm2m_gateway_send_batch_data_add_current(\n                    builder, anjay, obj->end_device_iid, obj->def->oid,\n                    inst->iid, RID_SENSOR_VALUE)) {\n            anjay_send_batch_builder_cleanup(&builder);\n            avs_log(temperature_object, ERROR, \"Failed to add batch data\");\n            return;\n        }\n    }\n\n    // After adding all values, compile our batch for sending.\n    anjay_send_batch_t *batch = anjay_send_batch_builder_compile(&builder);\n    if (!batch) {\n        anjay_send_batch_builder_cleanup(&builder);\n        avs_log(temperature_object, ERROR, \"Batch compile failed\");\n        return;\n    }\n\n    // Schedule our send to be run on next `anjay_sched_run()` call.\n    int res =\n            anjay_send(anjay, server_ssid, batch, send_finished_handler, NULL);\n    if (res) {\n        avs_log(temperature_object, ERROR, \"Failed to send, result: %d\", res);\n    }\n\n    // After scheduling, we can release our batch.\n    anjay_send_batch_release(&batch);\n}\n"
  },
  {
    "path": "examples/tutorial/LwM2M-Gateway/src/temperature_object.h",
    "content": "#ifndef TEMPERATURE_OBJECT_H\n#define TEMPERATURE_OBJECT_H\n\n#include <anjay/dm.h>\n\n#include \"gateway_server.h\"\n\nconst anjay_dm_object_def_t **\ntemperature_object_create(int id, gateway_srv_t *gateway_srv);\nvoid temperature_object_release(const anjay_dm_object_def_t **def);\nvoid temperature_object_send(anjay_t *anjay, AVS_LIST(end_device_t) end_device);\nvoid temperature_object_update_value(anjay_t *anjay,\n                                     const anjay_dm_object_def_t **def);\nvoid temperature_object_evaluation_period_update_value(\n        anjay_t *anjay,\n        const anjay_dm_object_def_t **def,\n        int32_t *evaluation_period);\n#endif // TEMPERATURE_OBJECT_H\n"
  },
  {
    "path": "examples/tutorial/firmware-update/CMakeLists.txt",
    "content": "add_subdirectory(basic-implementation)\nadd_subdirectory(secure-downloads)\nadd_subdirectory(download-resumption)\nadd_subdirectory(advanced-firmware-update)\n"
  },
  {
    "path": "examples/tutorial/firmware-update/advanced-firmware-update/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.16)\nproject(advanced-firmware-update C)\n\nset(CMAKE_C_STANDARD 99)\nset(CMAKE_C_EXTENSIONS OFF)\n\nfind_package(anjay REQUIRED)\n\nadd_executable(${PROJECT_NAME}\n               src/main.c\n               src/advanced_firmware_update.c\n               src/advanced_firmware_update.h\n               src/time_object.c\n               src/time_object.h)\ntarget_link_libraries(${PROJECT_NAME} PRIVATE anjay)\n"
  },
  {
    "path": "examples/tutorial/firmware-update/advanced-firmware-update/src/advanced_firmware_update.c",
    "content": "#include \"advanced_firmware_update.h\"\n\n#include <assert.h>\n#include <errno.h>\n#include <stdio.h>\n#include <sys/stat.h>\n#include <unistd.h>\n\n#define AFU_VERSION_STR_MAX_LEN 10\n#define AFU_INSTANCE_NAME_STR_MAX_LEN 10\n#define AFU_FILE_NAME_STR_MAX_LEN 50\n\ntypedef struct {\n    anjay_t *anjay;\n    char fw_version[AFU_NUMBER_OF_FIRMWARE_INSTANCES]\n                   [AFU_VERSION_STR_MAX_LEN + 1];\n    char instance_name[AFU_NUMBER_OF_FIRMWARE_INSTANCES]\n                      [AFU_INSTANCE_NAME_STR_MAX_LEN + 1];\n    FILE *new_firmware_file[AFU_NUMBER_OF_FIRMWARE_INSTANCES];\n} advanced_firmware_update_logic_t;\n\nstatic advanced_firmware_update_logic_t afu_logic;\n\nconst char *ENDPOINT_NAME;\n\nstatic void get_firmware_download_name(int iid, char *buff) {\n    if (iid == AFU_DEFAULT_FIRMWARE_INSTANCE_IID) {\n        snprintf(buff, AFU_FILE_NAME_STR_MAX_LEN, \"/tmp/firmware_image.bin\");\n    } else {\n        snprintf(buff, AFU_FILE_NAME_STR_MAX_LEN, \"/tmp/add_image_%d\", iid);\n    }\n}\n\nstatic void get_add_firmware_file_name(int iid, char *buff) {\n    snprintf(buff, AFU_FILE_NAME_STR_MAX_LEN, \"ADD_FILE_%d\", iid);\n}\n\nstatic void get_marker_file_name(int iid, char *buff) {\n    snprintf(buff, AFU_FILE_NAME_STR_MAX_LEN, \"/tmp/fw-updated-marker_%d\", iid);\n}\n\nstatic int move_file(const char *dest, const char *source) {\n    int ret_val = -1;\n    FILE *dest_stream = NULL;\n    FILE *source_stream = fopen(source, \"r\");\n\n    if (!source_stream) {\n        avs_log(advance_fu, ERROR, \"Could not open file: %s\", source);\n        goto cleanup;\n    }\n    dest_stream = fopen(dest, \"w\");\n    if (!dest_stream) {\n        avs_log(advance_fu, ERROR, \"Could not open file: %s\", dest);\n        fclose(source_stream);\n        goto cleanup;\n    }\n\n    while (!feof(source_stream)) {\n        char buff[1024];\n        size_t bytes_read_1 = fread(buff, 1, sizeof(buff), source_stream);\n        if (fwrite(buff, 1, bytes_read_1, dest_stream) != bytes_read_1) {\n            avs_log(advance_fu, ERROR, \"Error during write to file: %s\", dest);\n            goto cleanup;\n        }\n    }\n    ret_val = 0;\n\ncleanup:\n    if (dest_stream) {\n        if (fclose(dest_stream)) {\n            avs_log(advance_fu, ERROR, \"Could not close file: %s\", dest);\n            ret_val = -1;\n        }\n    }\n    if (source_stream) {\n        if (fclose(source_stream)) {\n            avs_log(advance_fu, ERROR, \"Could not close file: %s\", source);\n            ret_val = -1;\n        }\n    }\n    unlink(source);\n\n    return ret_val;\n}\n\nstatic void add_conflicting_instance(anjay_iid_t iid, anjay_iid_t target_iid) {\n    const anjay_iid_t *conflicting_instances;\n    anjay_iid_t conflicting_target_iids[AFU_NUMBER_OF_FIRMWARE_INSTANCES - 1];\n    size_t conflicting_iids_count = 0;\n\n    // get conflicting instances\n    anjay_advanced_fw_update_get_conflicting_instances(afu_logic.anjay, iid,\n                                                       &conflicting_instances,\n                                                       &conflicting_iids_count);\n    // add target_iid to the list\n    for (size_t i = 0; i < conflicting_iids_count; i++) {\n        conflicting_target_iids[i] = conflicting_instances[i];\n    }\n    conflicting_target_iids[conflicting_iids_count++] = target_iid;\n    anjay_advanced_fw_update_set_conflicting_instances(afu_logic.anjay, iid,\n                                                       conflicting_target_iids,\n                                                       conflicting_iids_count);\n}\n\nstatic bool is_update_requested(anjay_iid_t iid,\n                                anjay_iid_t target_iid,\n                                const anjay_iid_t *requested_supplemental_iids,\n                                size_t requested_supplemental_iids_count,\n                                const anjay_iid_t *linked_target_iids,\n                                size_t linked_iids_count) {\n    if (iid == target_iid) {\n        return true;\n    }\n    if (requested_supplemental_iids) {\n        for (size_t i = 0; i < requested_supplemental_iids_count; i++) {\n            if (iid == requested_supplemental_iids[i]) {\n                return true;\n            }\n        }\n    } else if (linked_target_iids) {\n        for (size_t i = 0; i < linked_iids_count; i++) {\n            if (iid == linked_target_iids[i]) {\n                return true;\n            }\n        }\n    }\n    return false;\n}\n\nstatic char get_firmware_major_version(anjay_iid_t iid, bool is_upgrade) {\n    char file_name[AFU_FILE_NAME_STR_MAX_LEN] = { 0 };\n\n    if (is_upgrade == false) {\n        return afu_logic.fw_version[iid][0];\n    }\n\n    get_firmware_download_name(iid, file_name);\n\n    // get value from new file\n    char buff;\n    FILE *stream = fopen(file_name, \"r\");\n    if (!stream) {\n        avs_log(advance_fu, ERROR, \"Could not open file: %s\", file_name);\n        return ' ';\n    }\n    if (!fread(&buff, 1, 1, stream)) {\n        avs_log(advance_fu, ERROR, \"Could not read from file file: %s\",\n                file_name);\n        fclose(stream);\n        return ' ';\n    }\n    if (fclose(stream)) {\n        avs_log(advance_fu, ERROR, \"Could not close file: %s\", file_name);\n    }\n\n    return buff;\n}\n\nstatic int refresh_fw_version() {\n    memcpy(afu_logic.fw_version[AFU_DEFAULT_FIRMWARE_INSTANCE_IID],\n           AFU_DEFAULT_FIRMWARE_VERSION, strlen(AFU_DEFAULT_FIRMWARE_VERSION));\n\n    for (int i = 1; i < AFU_NUMBER_OF_FIRMWARE_INSTANCES; i++) {\n        char buff[AFU_FILE_NAME_STR_MAX_LEN] = { 0 };\n        get_add_firmware_file_name(i, buff);\n        FILE *stream = fopen(buff, \"r\");\n        if (!stream) {\n            avs_log(advance_fu, ERROR, \"Could not open file with iid: %d\", i);\n            return -1;\n        }\n        if (!fread(afu_logic.fw_version[i], 1, AFU_VERSION_STR_MAX_LEN,\n                   stream)) {\n            avs_log(advance_fu, ERROR, \"Could not read file with iid: %d\", i);\n            fclose(stream);\n            return -1;\n        }\n        if (fclose(stream)) {\n            avs_log(advance_fu, ERROR, \"Could not close file with iid: %d\", i);\n            return -1;\n        }\n    }\n\n    for (int i = 0; i < AFU_NUMBER_OF_FIRMWARE_INSTANCES; i++) {\n        avs_log(advance_fu, INFO,\n                \"Firmware version for object with IID %d is: %s\", i,\n                afu_logic.fw_version[i]);\n    }\n\n    return 0;\n}\n\nstatic int fw_stream_open(anjay_iid_t iid, void *user_ptr) {\n    (void) user_ptr;\n\n    char file_name[AFU_FILE_NAME_STR_MAX_LEN] = { 0 };\n    get_firmware_download_name(iid, file_name);\n\n    if (afu_logic.new_firmware_file[iid]) {\n        avs_log(advance_fu, ERROR, \"Already open %s\", file_name);\n        return -1;\n    }\n    afu_logic.new_firmware_file[iid] = fopen(file_name, \"wb\");\n    if (!afu_logic.new_firmware_file[iid]) {\n        avs_log(advance_fu, ERROR, \"Could not open %s\", file_name);\n        return -1;\n    }\n    return 0;\n}\n\nstatic int fw_update_common_write(anjay_iid_t iid,\n                                  void *user_ptr,\n                                  const void *data,\n                                  size_t length) {\n    (void) user_ptr;\n\n    if (!afu_logic.new_firmware_file[iid]) {\n        avs_log(advance_fu, ERROR, \"Stream not open: object %d\", iid);\n        return -1;\n    }\n    if (length\n            && (fwrite(data, length, 1, afu_logic.new_firmware_file[iid]) != 1\n                || fflush(afu_logic.new_firmware_file[iid]) != 0)) {\n        avs_log(advance_fu, ERROR, \"fwrite or fflush failed: %s\",\n                strerror(errno));\n        return ANJAY_ADVANCED_FW_UPDATE_ERR_NOT_ENOUGH_SPACE;\n    }\n    return 0;\n}\n\nstatic void remove_linked_instance(anjay_iid_t iid, anjay_iid_t target_iid) {\n    const anjay_iid_t *linked_instances;\n    anjay_iid_t linked_target_iids[AFU_NUMBER_OF_FIRMWARE_INSTANCES - 1];\n    size_t linked_iids_count = 0;\n    size_t new_linked_iids_count = 0;\n\n    // get linked instances\n    anjay_advanced_fw_update_get_linked_instances(\n            afu_logic.anjay, iid, &linked_instances, &linked_iids_count);\n    // remove target_iid from the list\n    for (size_t i = 0; i < linked_iids_count; i++) {\n        if (linked_instances[i] != target_iid) {\n            linked_target_iids[new_linked_iids_count++] = linked_instances[i];\n        }\n    }\n    // update linked list\n    anjay_advanced_fw_update_set_linked_instances(\n            afu_logic.anjay, iid, linked_target_iids, new_linked_iids_count);\n}\n\nstatic void remove_conflicting_instance(anjay_iid_t iid,\n                                        anjay_iid_t target_iid) {\n    const anjay_iid_t *conflicting_instances;\n    anjay_iid_t conflicting_target_iids[AFU_NUMBER_OF_FIRMWARE_INSTANCES - 1];\n    size_t conflicting_iids_count = 0;\n    size_t new_conflicting_iids_count = 0;\n\n    // get conflicting instances\n    anjay_advanced_fw_update_get_conflicting_instances(afu_logic.anjay, iid,\n                                                       &conflicting_instances,\n                                                       &conflicting_iids_count);\n    // remove target_iid from the list\n    for (size_t i = 0; i < conflicting_iids_count; i++) {\n        if (conflicting_instances[i] != target_iid) {\n            conflicting_target_iids[new_conflicting_iids_count++] =\n                    conflicting_instances[i];\n        }\n    }\n    // update conflicting list\n    anjay_advanced_fw_update_set_conflicting_instances(\n            afu_logic.anjay, iid, conflicting_target_iids,\n            new_conflicting_iids_count);\n}\n\nstatic void add_linked_instance(anjay_iid_t iid, anjay_iid_t target_iid) {\n    const anjay_iid_t *linked_instances;\n    anjay_iid_t linked_target_iids[AFU_NUMBER_OF_FIRMWARE_INSTANCES - 1];\n    size_t linked_iids_count = 0;\n\n    // get linked instances\n    anjay_advanced_fw_update_get_linked_instances(\n            afu_logic.anjay, iid, &linked_instances, &linked_iids_count);\n    // add target_iid to the list\n    for (size_t i = 0; i < linked_iids_count; i++) {\n        linked_target_iids[i] = linked_instances[i];\n    }\n    linked_target_iids[linked_iids_count++] = target_iid;\n    anjay_advanced_fw_update_set_linked_instances(\n            afu_logic.anjay, iid, linked_target_iids, linked_iids_count);\n}\n\nstatic int fw_update_common_finish(anjay_iid_t iid, void *user_ptr) {\n    (void) user_ptr;\n\n    if (!afu_logic.new_firmware_file[iid]) {\n        avs_log(advance_fu, ERROR, \"Stream not open: object %d\", iid);\n        return -1;\n    }\n\n    if (fclose(afu_logic.new_firmware_file[iid])) {\n        avs_log(advance_fu, ERROR, \"Closing firmware image failed: object %d\",\n                iid);\n        afu_logic.new_firmware_file[iid] = NULL;\n        return -1;\n    }\n    afu_logic.new_firmware_file[iid] = NULL;\n\n    /*\n    If other firmware instances are in downloaded state set linked instances,\n    based on them, the upgrade will be performed simultaneously in the\n    perform_upgrade callback. The reason for setting linked instances may be\n    different and depends on the user's implementation, but always mean\n    that instances will be updated in a batch if the Update resource is executed\n    with no arguments.\n    */\n    for (anjay_iid_t i = 0; i < AFU_NUMBER_OF_FIRMWARE_INSTANCES; i++) {\n        if (i != iid) {\n            anjay_advanced_fw_update_state_t state;\n            anjay_advanced_fw_update_get_state(afu_logic.anjay, i, &state);\n            if (state == ANJAY_ADVANCED_FW_UPDATE_STATE_DOWNLOADED) {\n                add_linked_instance(iid, i);\n                add_linked_instance(i, iid);\n            }\n        }\n    }\n\n    return 0;\n}\n\nstatic int\nfw_update_common_perform_upgrade(anjay_iid_t iid,\n                                 void *user_ptr,\n                                 const anjay_iid_t *requested_supplemental_iids,\n                                 size_t requested_supplemental_iids_count) {\n    (void) user_ptr;\n\n    const anjay_iid_t *linked_target_iids;\n    bool update_iid[AFU_NUMBER_OF_FIRMWARE_INSTANCES];\n    size_t linked_iids_count = 0;\n\n    // get linked instances\n    anjay_advanced_fw_update_get_linked_instances(\n            afu_logic.anjay, iid, &linked_target_iids, &linked_iids_count);\n\n    /* Prepare list of iid to update. If requested_supplemental_iids is present\n     * use it otherwise use linked_target_iids.*/\n    for (anjay_iid_t i = 0; i < AFU_NUMBER_OF_FIRMWARE_INSTANCES; i++) {\n        if (is_update_requested(i, iid, requested_supplemental_iids,\n                                requested_supplemental_iids_count,\n                                linked_target_iids, linked_iids_count)) {\n            update_iid[i] = true;\n            // check if new file is already downloaded\n            anjay_advanced_fw_update_state_t state;\n            anjay_advanced_fw_update_get_state(afu_logic.anjay, i, &state);\n            if ((i != iid)\n                    && (state != ANJAY_ADVANCED_FW_UPDATE_STATE_DOWNLOADED)) {\n                avs_log(advance_fu, ERROR,\n                        \"Upgrade can't be performed, firmware file with iid %d \"\n                        \"is not ready\",\n                        i);\n                // set conflicting instance\n                add_conflicting_instance(iid, i);\n                return ANJAY_ADVANCED_FW_UPDATE_ERR_CONFLICTING_STATE;\n            }\n        } else {\n            update_iid[i] = false;\n        }\n    }\n\n    /*\n    Check firmware version compatibility.\n    In this example major version number is compare - first character in every\n    additional image must have the same value. If new file is given (DOWNLOADED\n    STATE), get this value from them, otherwise use the old one.\n    */\n    for (anjay_iid_t i = 1; i < AFU_NUMBER_OF_FIRMWARE_INSTANCES; i++) {\n        if (update_iid[i] == true) {\n            for (anjay_iid_t j = i + 1; j < AFU_NUMBER_OF_FIRMWARE_INSTANCES;\n                 j++) {\n                if (get_firmware_major_version(i, update_iid[i])\n                        != get_firmware_major_version(j, update_iid[j])) {\n                    avs_log(advance_fu, ERROR,\n                            \"Upgrade can't be performed, conflicting firmware \"\n                            \"version between instance %d and %d\",\n                            i, j);\n                    // set conflicting instance due to firmware version\n                    // incompatibility\n                    add_conflicting_instance(i, j);\n                    add_conflicting_instance(j, i);\n                    return ANJAY_ADVANCED_FW_UPDATE_ERR_CONFLICTING_STATE;\n                }\n            }\n        }\n    }\n\n    /* No errors found, change the status of all requested_supplemental_iids or\n     * linked_target_iids to UPDATING before the actual update process.*/\n    for (anjay_iid_t i = 0; i < AFU_NUMBER_OF_FIRMWARE_INSTANCES; i++) {\n        if (update_iid[i] == true) {\n            if (i != iid) {\n                anjay_advanced_fw_update_set_state_and_result(\n                        afu_logic.anjay, i,\n                        ANJAY_ADVANCED_FW_UPDATE_STATE_UPDATING,\n                        ANJAY_ADVANCED_FW_UPDATE_RESULT_INITIAL);\n            }\n        }\n    }\n\n    // after firmware versions check, start firmware update, first with\n    // additional images\n    for (anjay_iid_t i = 1; i < AFU_NUMBER_OF_FIRMWARE_INSTANCES; i++) {\n        if (update_iid[i] == true) {\n            avs_log(advance_fu, INFO, \"Perform update on %d instance\", i);\n\n            char new_firm_name[AFU_FILE_NAME_STR_MAX_LEN] = { 0 };\n            char current_firm_name[AFU_FILE_NAME_STR_MAX_LEN] = { 0 };\n            get_firmware_download_name(i, new_firm_name);\n            get_add_firmware_file_name(i, current_firm_name);\n\n            if (move_file(current_firm_name, new_firm_name)) {\n                avs_log(advance_fu, ERROR,\n                        \"Error during the %d additional image swapping\", i);\n                return -1;\n            }\n            // if main application is restarted, set update marker\n            if (update_iid[AFU_DEFAULT_FIRMWARE_INSTANCE_IID] == true) {\n                char marker_name[AFU_FILE_NAME_STR_MAX_LEN] = { 0 };\n                get_marker_file_name(i, marker_name);\n                FILE *marker = fopen(marker_name, \"w\");\n                if (!marker) {\n                    avs_log(advance_fu, ERROR,\n                            \"Marker file could not be created\");\n                    return -1;\n                }\n                if (fclose(marker)) {\n                    avs_log(advance_fu, ERROR,\n                            \"Marker file could not be close\");\n                }\n            } // if main application is not restarted, update state\n            else {\n                anjay_advanced_fw_update_set_state_and_result(\n                        afu_logic.anjay, i, ANJAY_ADVANCED_FW_UPDATE_STATE_IDLE,\n                        ANJAY_ADVANCED_FW_UPDATE_RESULT_SUCCESS);\n            }\n        }\n    }\n\n    // update application\n    if (update_iid[AFU_DEFAULT_FIRMWARE_INSTANCE_IID] == true) {\n        avs_log(advance_fu, INFO, \"Perform update on default instance\");\n\n        char new_firm_name[AFU_FILE_NAME_STR_MAX_LEN] = { 0 };\n        char marker_name[AFU_FILE_NAME_STR_MAX_LEN] = { 0 };\n        get_firmware_download_name(AFU_DEFAULT_FIRMWARE_INSTANCE_IID,\n                                   new_firm_name);\n        get_marker_file_name(AFU_DEFAULT_FIRMWARE_INSTANCE_IID, marker_name);\n\n        if (chmod(new_firm_name, 0700) == -1) {\n            avs_log(advance_fu, ERROR, \"Could not make firmware executable: %s\",\n                    strerror(errno));\n            return -1;\n        }\n        // Create a marker file, so that the new process knows it is the\n        // \"upgraded\" one\n        FILE *marker = fopen(marker_name, \"w\");\n        if (!marker) {\n            avs_log(advance_fu, ERROR, \"Marker file could not be created\");\n            return -1;\n        }\n        if (fclose(marker)) {\n            avs_log(advance_fu, ERROR, \"Marker file could not be close\");\n        }\n\n        assert(ENDPOINT_NAME);\n\n        // If the call below succeeds, the firmware is considered as \"upgraded\",\n        // and we hope the newly started client registers to the Server.\n        (void) execl(new_firm_name, new_firm_name, ENDPOINT_NAME, NULL);\n        avs_log(advance_fu, ERROR, \"execl() failed: %s\", strerror(errno));\n        // If we are here, it means execl() failed. Marker file MUST now be\n        // removed, as the firmware update failed.\n        unlink(marker_name);\n        return -1;\n    }\n\n    // update firmware version\n    refresh_fw_version();\n\n    // clear conflicting and linked instances in the objects\n    for (anjay_iid_t i = 0; i < AFU_NUMBER_OF_FIRMWARE_INSTANCES; i++) {\n        if (update_iid[i] == true) {\n            anjay_advanced_fw_update_set_conflicting_instances(afu_logic.anjay,\n                                                               i, NULL, 0);\n            anjay_advanced_fw_update_set_linked_instances(afu_logic.anjay, i,\n                                                          NULL, 0);\n            // clear conflicting and linked instances about this object from\n            // other objects\n            for (anjay_iid_t j = 0; j < AFU_NUMBER_OF_FIRMWARE_INSTANCES; j++) {\n                if (i != j) {\n                    remove_linked_instance(i, j);\n                    remove_conflicting_instance(i, j);\n                }\n            }\n        }\n    }\n\n    return 0;\n}\n\nvoid fw_update_common_reset(anjay_iid_t iid, void *user_ptr) {\n    (void) user_ptr;\n\n    char new_firm_name[AFU_FILE_NAME_STR_MAX_LEN] = { 0 };\n    get_firmware_download_name(iid, new_firm_name);\n\n    // Reset can be issued even if the download never started\n    if (afu_logic.new_firmware_file[iid]) {\n        // We ignore the result code of fclose(), as fw_reset() can't fail\n        (void) fclose(afu_logic.new_firmware_file[iid]);\n        // and reset our global state to initial value\n        afu_logic.new_firmware_file[iid] = NULL;\n    }\n    // Finally, let's remove any downloaded payload\n    unlink(new_firm_name);\n\n    // clear conflicting and linked instances in the object\n    anjay_advanced_fw_update_set_conflicting_instances(afu_logic.anjay, iid,\n                                                       NULL, 0);\n    anjay_advanced_fw_update_set_linked_instances(afu_logic.anjay, iid, NULL,\n                                                  0);\n    // clear conflicting and linked instances about this object from other\n    // objects\n    for (anjay_iid_t i = 0; i < AFU_NUMBER_OF_FIRMWARE_INSTANCES; i++) {\n        if (i != iid) {\n            remove_linked_instance(i, iid);\n            remove_conflicting_instance(i, iid);\n        }\n    }\n}\n\nstatic const char *fw_update_common_get_current_version(anjay_iid_t iid,\n                                                        void *user_ptr) {\n    (void) user_ptr;\n\n    return (const char *) afu_logic.fw_version[iid];\n}\n\nstatic const anjay_advanced_fw_update_handlers_t handlers = {\n    .stream_open = fw_stream_open,\n    .stream_write = fw_update_common_write,\n    .stream_finish = fw_update_common_finish,\n    .reset = fw_update_common_reset,\n    .get_current_version = fw_update_common_get_current_version,\n    .perform_upgrade = fw_update_common_perform_upgrade\n};\n\nint afu_update_install(anjay_t *anjay) {\n    anjay_advanced_fw_update_initial_state_t state;\n    char marker_name[AFU_FILE_NAME_STR_MAX_LEN] = { 0 };\n    char file_name[AFU_FILE_NAME_STR_MAX_LEN] = { 0 };\n\n    memset(&state, 0, sizeof(state));\n\n    afu_logic.anjay = anjay;\n\n    anjay_advanced_fw_update_global_config_t config = {\n        .prefer_same_socket_downloads = true\n    };\n    int result = anjay_advanced_fw_update_install(anjay, &config);\n    if (result) {\n        avs_log(advance_fu, ERROR,\n                \"Could not install advanced firmware object: %d\", result);\n        return -1;\n    }\n\n    // check if application was updated\n    get_marker_file_name(AFU_DEFAULT_FIRMWARE_INSTANCE_IID, marker_name);\n    if (access(marker_name, F_OK) != -1) {\n        avs_log(advance_fu, INFO, \"Application update succeded\");\n        state.result = ANJAY_ADVANCED_FW_UPDATE_RESULT_SUCCESS;\n        unlink(marker_name);\n    }\n    result = anjay_advanced_fw_update_instance_add(\n            anjay, AFU_DEFAULT_FIRMWARE_INSTANCE_IID, \"application\", &handlers,\n            NULL, &state);\n    if (result) {\n        avs_log(advance_fu, ERROR,\n                \"Could not add default application instance: %d\", result);\n        return -1;\n    }\n\n    // check if additional files were updated, if not create it with default\n    // value\n    for (anjay_iid_t i = 1; i < AFU_NUMBER_OF_FIRMWARE_INSTANCES; i++) {\n        memset(marker_name, 0, sizeof(marker_name));\n        get_marker_file_name(i, marker_name);\n        if (access(marker_name, F_OK) != -1) {\n            avs_log(advance_fu, INFO,\n                    \"Additional file with idd: %d update succeded\", i);\n            state.result = ANJAY_ADVANCED_FW_UPDATE_RESULT_SUCCESS;\n            unlink(marker_name);\n        } else {\n            state.result = ANJAY_ADVANCED_FW_UPDATE_RESULT_INITIAL;\n        }\n\n        memset(file_name, 0, sizeof(file_name));\n        get_add_firmware_file_name(i, file_name);\n        // create file only if not exist\n        if (access(file_name, F_OK) != 0) {\n            FILE *stream = fopen(file_name, \"wb\");\n            if (!stream) {\n                avs_log(advance_fu, ERROR, \"Could not open %s\", file_name);\n                return -1;\n            }\n            if (fwrite(AFU_ADD_FILE_DEFAULT_CONTENT,\n                       strlen(AFU_ADD_FILE_DEFAULT_CONTENT), 1, stream)\n                    != 1) {\n                avs_log(advance_fu, ERROR, \"Could not write to %s\", file_name);\n                fclose(stream);\n                return -1;\n            }\n            if (fclose(stream)) {\n                avs_log(advance_fu, ERROR, \"Could not close %s\", file_name);\n                return -1;\n            }\n        }\n\n        snprintf(afu_logic.instance_name[i], AFU_INSTANCE_NAME_STR_MAX_LEN,\n                 \"add_img_%d\", i);\n        result = anjay_advanced_fw_update_instance_add(\n                anjay, i, afu_logic.instance_name[i], &handlers, NULL, &state);\n        if (result) {\n            avs_log(advance_fu, ERROR,\n                    \"Could not add the additional image instance: %d\", result);\n            return -1;\n        }\n    }\n\n    if (refresh_fw_version()) {\n        return -1;\n    }\n\n    return 0;\n}\n"
  },
  {
    "path": "examples/tutorial/firmware-update/advanced-firmware-update/src/advanced_firmware_update.h",
    "content": "#ifndef ADVANCED_FIRMWARE_UPDATE_H\n#define ADVANCED_FIRMWARE_UPDATE_H\n\n#include <anjay/advanced_fw_update.h>\n#include <anjay/anjay.h>\n\n#include <avsystem/commons/avs_log.h>\n\n#define AFU_DEFAULT_FIRMWARE_VERSION \"1.0.0\"\n#define AFU_ADD_FILE_DEFAULT_CONTENT \"1.1.1\"\n\n#define AFU_DEFAULT_FIRMWARE_INSTANCE_IID 0\n#define AFU_NUMBER_OF_FIRMWARE_INSTANCES 3\n\n/**\n * Buffer for the endpoint name that will be used when re-launching the client\n * after firmware upgrade.\n */\nextern const char *ENDPOINT_NAME;\n\n/**\n * Installs the advanced firmware update module.\n *\n * @returns 0 on success, negative value otherwise.\n */\nint afu_update_install(anjay_t *anjay);\n\n#endif // ADVANCED_FIRMWARE_UPDATE_H\n"
  },
  {
    "path": "examples/tutorial/firmware-update/advanced-firmware-update/src/main.c",
    "content": "#include <anjay/anjay.h>\n#include <anjay/security.h>\n#include <anjay/server.h>\n#include <avsystem/commons/avs_log.h>\n\n#include \"advanced_firmware_update.h\"\n#include \"time_object.h\"\n\ntypedef struct {\n    anjay_t *anjay;\n    const anjay_dm_object_def_t **time_object;\n} notify_job_args_t;\n\n// Periodically notifies the library about Resource value changes\nstatic void notify_job(avs_sched_t *sched, const void *args_ptr) {\n    const notify_job_args_t *args = (const notify_job_args_t *) args_ptr;\n\n    time_object_notify(args->anjay, args->time_object);\n\n    // Schedule run of the same function after 1 second\n    AVS_SCHED_DELAYED(sched, NULL, avs_time_duration_from_scalar(1, AVS_TIME_S),\n                      notify_job, args, sizeof(*args));\n}\n\n// Installs Security Object and adds an instance of it.\n// An instance of Security Object provides information needed to connect to\n// LwM2M server.\nstatic int setup_security_object(anjay_t *anjay) {\n    if (anjay_security_object_install(anjay)) {\n        return -1;\n    }\n\n    static const char PSK_IDENTITY[] = \"IDENTITY\";\n    static const char PSK_KEY[] = \"KEY\";\n\n    anjay_security_instance_t security_instance = {\n        .ssid = 1,\n        .server_uri = \"coaps://eu.iot.avsystem.cloud:5684\",\n        .security_mode = ANJAY_SECURITY_PSK,\n        .public_cert_or_psk_identity = (const uint8_t *) PSK_IDENTITY,\n        .public_cert_or_psk_identity_size = strlen(PSK_IDENTITY),\n        .private_cert_or_psk_key = (const uint8_t *) PSK_KEY,\n        .private_cert_or_psk_key_size = strlen(PSK_KEY)\n    };\n\n    // Anjay will assign Instance ID automatically\n    anjay_iid_t security_instance_id = ANJAY_ID_INVALID;\n    if (anjay_security_object_add_instance(anjay, &security_instance,\n                                           &security_instance_id)) {\n        return -1;\n    }\n\n    return 0;\n}\n\n// Installs Server Object and adds an instance of it.\n// An instance of Server Object provides the data related to a LwM2M Server.\nstatic int setup_server_object(anjay_t *anjay) {\n    if (anjay_server_object_install(anjay)) {\n        return -1;\n    }\n\n    const anjay_server_instance_t server_instance = {\n        // Server Short ID\n        .ssid = 1,\n        // Client will send Update message often than every 60 seconds\n        .lifetime = 60,\n        // Disable Default Minimum Period resource\n        .default_min_period = -1,\n        // Disable Default Maximum Period resource\n        .default_max_period = -1,\n        // Disable Disable Timeout resource\n        .disable_timeout = -1,\n        // Sets preferred transport to UDP\n        .binding = \"U\"\n    };\n\n    // Anjay will assign Instance ID automatically\n    anjay_iid_t server_instance_id = ANJAY_ID_INVALID;\n    if (anjay_server_object_add_instance(anjay, &server_instance,\n                                         &server_instance_id)) {\n        return -1;\n    }\n\n    return 0;\n}\n\nint main(int argc, char *argv[]) {\n    if (argc != 2) {\n        avs_log(tutorial, ERROR, \"usage: %s ENDPOINT_NAME\", argv[0]);\n        return -1;\n    }\n\n    ENDPOINT_NAME = argv[1];\n\n    const anjay_configuration_t CONFIG = {\n        .endpoint_name = ENDPOINT_NAME,\n        .in_buffer_size = 4000,\n        .out_buffer_size = 4000,\n        .msg_cache_size = 4000\n    };\n\n    anjay_t *anjay = anjay_new(&CONFIG);\n    if (!anjay) {\n        avs_log(tutorial, ERROR, \"Could not create Anjay object\");\n        return -1;\n    }\n\n    int result = 0;\n    // Setup necessary objects\n    if (setup_security_object(anjay) || setup_server_object(anjay)\n            || afu_update_install(anjay)) {\n        result = -1;\n    }\n\n    const anjay_dm_object_def_t **time_object = NULL;\n    if (!result) {\n        time_object = time_object_create();\n        if (time_object) {\n            result = anjay_register_object(anjay, time_object);\n        } else {\n            result = -1;\n        }\n    }\n\n    if (!result) {\n        // Run notify_job the first time;\n        // this will schedule periodic calls to itself via the scheduler\n        notify_job(anjay_get_scheduler(anjay), &(const notify_job_args_t) {\n                                                   .anjay = anjay,\n                                                   .time_object = time_object\n                                               });\n\n        result = anjay_event_loop_run(\n                anjay, avs_time_duration_from_scalar(1, AVS_TIME_S));\n    }\n\n    anjay_delete(anjay);\n    time_object_release(time_object);\n    return result;\n}\n"
  },
  {
    "path": "examples/tutorial/firmware-update/advanced-firmware-update/src/time_object.c",
    "content": "/**\n * Generated by anjay_codegen.py on 2020-03-19 14:13:34\n *\n * LwM2M Object: Time\n * ID: 3333, URN: urn:oma:lwm2m:ext:3333, Optional, Multiple\n *\n * This IPSO object is used to report the current time in seconds since\n * January 1, 1970 UTC. There is also a fractional time counter that has\n * a range of less than one second.\n */\n\n#include <assert.h>\n#include <stdbool.h>\n\n#include <anjay/anjay.h>\n#include <avsystem/commons/avs_defs.h>\n#include <avsystem/commons/avs_list.h>\n#include <avsystem/commons/avs_memory.h>\n\n#include \"time_object.h\"\n\n/**\n * Current Time: RW, Single, Mandatory\n * type: time, range: N/A, unit: N/A\n * Unix Time. A signed integer representing the number of seconds since\n * Jan 1st, 1970 in the UTC time zone.\n */\n#define RID_CURRENT_TIME 5506\n\n/**\n * Fractional Time: RW, Single, Optional\n * type: float, range: 0..1, unit: s\n * Fractional part of the time when sub-second precision is used (e.g.,\n * 0.23 for 230 ms).\n */\n#define RID_FRACTIONAL_TIME 5507\n\n/**\n * Application Type: RW, Single, Optional\n * type: string, range: N/A, unit: N/A\n * The application type of the sensor or actuator as a string depending\n * on the use case.\n */\n#define RID_APPLICATION_TYPE 5750\n\ntypedef struct time_instance_struct {\n    anjay_iid_t iid;\n    char application_type[64];\n    char application_type_backup[64];\n    int64_t last_notify_timestamp;\n} time_instance_t;\n\ntypedef struct time_object_struct {\n    const anjay_dm_object_def_t *def;\n    AVS_LIST(time_instance_t) instances;\n} time_object_t;\n\nstatic inline time_object_t *\nget_obj(const anjay_dm_object_def_t *const *obj_ptr) {\n    assert(obj_ptr);\n    return AVS_CONTAINER_OF(obj_ptr, time_object_t, def);\n}\n\nstatic time_instance_t *find_instance(const time_object_t *obj,\n                                      anjay_iid_t iid) {\n    AVS_LIST(time_instance_t) it;\n    AVS_LIST_FOREACH(it, obj->instances) {\n        if (it->iid == iid) {\n            return it;\n        } else if (it->iid > iid) {\n            break;\n        }\n    }\n\n    return NULL;\n}\n\nstatic int list_instances(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr,\n                          anjay_dm_list_ctx_t *ctx) {\n    (void) anjay;\n\n    AVS_LIST(time_instance_t) it;\n    AVS_LIST_FOREACH(it, get_obj(obj_ptr)->instances) {\n        anjay_dm_emit(ctx, it->iid);\n    }\n\n    return 0;\n}\n\nstatic int init_instance(time_instance_t *inst, anjay_iid_t iid) {\n    assert(iid != ANJAY_ID_INVALID);\n\n    inst->iid = iid;\n    inst->application_type[0] = '\\0';\n\n    return 0;\n}\n\nstatic void release_instance(time_instance_t *inst) {\n    (void) inst;\n}\n\nstatic time_instance_t *add_instance(time_object_t *obj, anjay_iid_t iid) {\n    assert(find_instance(obj, iid) == NULL);\n\n    AVS_LIST(time_instance_t) created = AVS_LIST_NEW_ELEMENT(time_instance_t);\n    if (!created) {\n        return NULL;\n    }\n\n    int result = init_instance(created, iid);\n    if (result) {\n        AVS_LIST_CLEAR(&created);\n        return NULL;\n    }\n\n    AVS_LIST(time_instance_t) *ptr;\n    AVS_LIST_FOREACH_PTR(ptr, &obj->instances) {\n        if ((*ptr)->iid > created->iid) {\n            break;\n        }\n    }\n\n    AVS_LIST_INSERT(ptr, created);\n    return created;\n}\n\nstatic int instance_create(anjay_t *anjay,\n                           const anjay_dm_object_def_t *const *obj_ptr,\n                           anjay_iid_t iid) {\n    (void) anjay;\n    time_object_t *obj = get_obj(obj_ptr);\n\n    return add_instance(obj, iid) ? 0 : ANJAY_ERR_INTERNAL;\n}\n\nstatic int instance_remove(anjay_t *anjay,\n                           const anjay_dm_object_def_t *const *obj_ptr,\n                           anjay_iid_t iid) {\n    (void) anjay;\n    time_object_t *obj = get_obj(obj_ptr);\n\n    AVS_LIST(time_instance_t) *it;\n    AVS_LIST_FOREACH_PTR(it, &obj->instances) {\n        if ((*it)->iid == iid) {\n            release_instance(*it);\n            AVS_LIST_DELETE(it);\n            return 0;\n        } else if ((*it)->iid > iid) {\n            break;\n        }\n    }\n\n    assert(0);\n    return ANJAY_ERR_NOT_FOUND;\n}\n\nstatic int instance_reset(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr,\n                          anjay_iid_t iid) {\n    (void) anjay;\n\n    time_object_t *obj = get_obj(obj_ptr);\n    time_instance_t *inst = find_instance(obj, iid);\n    assert(inst);\n\n    inst->application_type[0] = '\\0';\n\n    return 0;\n}\n\nstatic int list_resources(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr,\n                          anjay_iid_t iid,\n                          anjay_dm_resource_list_ctx_t *ctx) {\n    (void) anjay;\n    (void) obj_ptr;\n    (void) iid;\n\n    anjay_dm_emit_res(ctx, RID_CURRENT_TIME, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, RID_FRACTIONAL_TIME, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT);\n    anjay_dm_emit_res(ctx, RID_APPLICATION_TYPE, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_PRESENT);\n    return 0;\n}\n\nstatic int resource_read(anjay_t *anjay,\n                         const anjay_dm_object_def_t *const *obj_ptr,\n                         anjay_iid_t iid,\n                         anjay_rid_t rid,\n                         anjay_riid_t riid,\n                         anjay_output_ctx_t *ctx) {\n    (void) anjay;\n\n    time_object_t *obj = get_obj(obj_ptr);\n    time_instance_t *inst = find_instance(obj, iid);\n    assert(inst);\n\n    switch (rid) {\n    case RID_CURRENT_TIME: {\n        assert(riid == ANJAY_ID_INVALID);\n        int64_t timestamp;\n        if (avs_time_real_to_scalar(&timestamp, AVS_TIME_S,\n                                    avs_time_real_now())) {\n            return -1;\n        }\n        return anjay_ret_i64(ctx, timestamp);\n    }\n\n    case RID_APPLICATION_TYPE:\n        assert(riid == ANJAY_ID_INVALID);\n        return anjay_ret_string(ctx, inst->application_type);\n\n    default:\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\nstatic int resource_write(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr,\n                          anjay_iid_t iid,\n                          anjay_rid_t rid,\n                          anjay_riid_t riid,\n                          anjay_input_ctx_t *ctx) {\n    (void) anjay;\n\n    time_object_t *obj = get_obj(obj_ptr);\n    time_instance_t *inst = find_instance(obj, iid);\n    assert(inst);\n\n    switch (rid) {\n    case RID_APPLICATION_TYPE:\n        assert(riid == ANJAY_ID_INVALID);\n        return anjay_get_string(ctx, inst->application_type,\n                                sizeof(inst->application_type));\n\n    default:\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\nint transaction_begin(anjay_t *anjay,\n                      const anjay_dm_object_def_t *const *obj_ptr) {\n    (void) anjay;\n\n    time_object_t *obj = get_obj(obj_ptr);\n\n    time_instance_t *element;\n    AVS_LIST_FOREACH(element, obj->instances) {\n        strcpy(element->application_type_backup, element->application_type);\n    }\n    return 0;\n}\n\nint transaction_rollback(anjay_t *anjay,\n                         const anjay_dm_object_def_t *const *obj_ptr) {\n    (void) anjay;\n\n    time_object_t *obj = get_obj(obj_ptr);\n\n    time_instance_t *element;\n    AVS_LIST_FOREACH(element, obj->instances) {\n        strcpy(element->application_type, element->application_type_backup);\n    }\n    return 0;\n}\n\nstatic const anjay_dm_object_def_t OBJ_DEF = {\n    .oid = 3333,\n    .handlers = {\n        .list_instances = list_instances,\n        .instance_create = instance_create,\n        .instance_remove = instance_remove,\n        .instance_reset = instance_reset,\n\n        .list_resources = list_resources,\n        .resource_read = resource_read,\n        .resource_write = resource_write,\n\n        .transaction_begin = transaction_begin,\n        .transaction_validate = anjay_dm_transaction_NOOP,\n        .transaction_commit = anjay_dm_transaction_NOOP,\n        .transaction_rollback = transaction_rollback\n    }\n};\n\nconst anjay_dm_object_def_t **time_object_create(void) {\n    time_object_t *obj = (time_object_t *) avs_calloc(1, sizeof(time_object_t));\n    if (!obj) {\n        return NULL;\n    }\n    obj->def = &OBJ_DEF;\n\n    time_instance_t *inst = add_instance(obj, 0);\n    if (inst) {\n        strcpy(inst->application_type, \"Clock 0\");\n    } else {\n        avs_free(obj);\n        return NULL;\n    }\n\n    return &obj->def;\n}\n\nvoid time_object_release(const anjay_dm_object_def_t **def) {\n    if (def) {\n        time_object_t *obj = get_obj(def);\n        AVS_LIST_CLEAR(&obj->instances) {\n            release_instance(obj->instances);\n        }\n\n        avs_free(obj);\n    }\n}\n\nvoid time_object_notify(anjay_t *anjay, const anjay_dm_object_def_t **def) {\n    if (!anjay || !def) {\n        return;\n    }\n    time_object_t *obj = get_obj(def);\n\n    int64_t current_timestamp;\n    if (avs_time_real_to_scalar(&current_timestamp, AVS_TIME_S,\n                                avs_time_real_now())) {\n        return;\n    }\n\n    AVS_LIST(time_instance_t) it;\n    AVS_LIST_FOREACH(it, obj->instances) {\n        if (it->last_notify_timestamp != current_timestamp) {\n            if (!anjay_notify_changed(anjay, 3333, it->iid, RID_CURRENT_TIME)) {\n                it->last_notify_timestamp = current_timestamp;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "examples/tutorial/firmware-update/advanced-firmware-update/src/time_object.h",
    "content": "#ifndef TIME_OBJECT_H\n#define TIME_OBJECT_H\n\n#include <anjay/dm.h>\n\nconst anjay_dm_object_def_t **time_object_create(void);\nvoid time_object_release(const anjay_dm_object_def_t **def);\nvoid time_object_notify(anjay_t *anjay, const anjay_dm_object_def_t **def);\n\n#endif // TIME_OBJECT_H\n"
  },
  {
    "path": "examples/tutorial/firmware-update/basic-implementation/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.16)\nproject(basic-firmware-update C)\n\nset(CMAKE_C_STANDARD 99)\nset(CMAKE_C_EXTENSIONS OFF)\n\nfind_package(anjay REQUIRED)\n\nadd_executable(${PROJECT_NAME}\n               src/main.c\n               src/firmware_update.c\n               src/firmware_update.h\n               src/time_object.c\n               src/time_object.h)\ntarget_link_libraries(${PROJECT_NAME} PRIVATE anjay)\n"
  },
  {
    "path": "examples/tutorial/firmware-update/basic-implementation/src/firmware_update.c",
    "content": "#include \"./firmware_update.h\"\n\n#include <assert.h>\n#include <errno.h>\n#include <stdio.h>\n#include <sys/stat.h>\n#include <unistd.h>\n\nstatic struct fw_state_t { FILE *firmware_file; } FW_STATE;\n\nstatic const char *FW_IMAGE_DOWNLOAD_NAME = \"/tmp/firmware_image.bin\";\n\nstatic int fw_stream_open(void *user_ptr,\n                          const char *package_uri,\n                          const struct anjay_etag *package_etag) {\n    // For a moment, we don't need to care about any of the arguments passed.\n    (void) user_ptr;\n    (void) package_uri;\n    (void) package_etag;\n\n    // It's worth ensuring we start with a NULL firmware_file. In the end\n    // it would be our responsibility to manage this pointer, and we want\n    // to make sure we never leak any memory.\n    assert(FW_STATE.firmware_file == NULL);\n    // We're about to create a firmware file for writing\n    FW_STATE.firmware_file = fopen(FW_IMAGE_DOWNLOAD_NAME, \"wb\");\n    if (!FW_STATE.firmware_file) {\n        fprintf(stderr, \"Could not open %s\\n\", FW_IMAGE_DOWNLOAD_NAME);\n        return -1;\n    }\n    // We've succeeded\n    return 0;\n}\n\nstatic int fw_stream_write(void *user_ptr, const void *data, size_t length) {\n    (void) user_ptr;\n    // We only need to write to file and check if that succeeded\n    if (fwrite(data, length, 1, FW_STATE.firmware_file) != 1) {\n        fprintf(stderr, \"Writing to firmware image failed\\n\");\n        return -1;\n    }\n    return 0;\n}\n\nstatic int fw_stream_finish(void *user_ptr) {\n    (void) user_ptr;\n    assert(FW_STATE.firmware_file != NULL);\n\n    if (fclose(FW_STATE.firmware_file)) {\n        fprintf(stderr, \"Closing firmware image failed\\n\");\n        FW_STATE.firmware_file = NULL;\n        return -1;\n    }\n    FW_STATE.firmware_file = NULL;\n    return 0;\n}\n\nstatic void fw_reset(void *user_ptr) {\n    // Reset can be issued even if the download never started.\n    if (FW_STATE.firmware_file) {\n        // We ignore the result code of fclose(), as fw_reset() can't fail.\n        (void) fclose(FW_STATE.firmware_file);\n        // and reset our global state to initial value.\n        FW_STATE.firmware_file = NULL;\n    }\n    // Finally, let's remove any downloaded payload\n    unlink(FW_IMAGE_DOWNLOAD_NAME);\n}\n\n// A part of a rather simple logic checking if the firmware update was\n// successfully performed.\nstatic const char *FW_UPDATED_MARKER = \"/tmp/fw-updated-marker\";\n\nstatic int fw_perform_upgrade(void *user_ptr) {\n    if (chmod(FW_IMAGE_DOWNLOAD_NAME, 0700) == -1) {\n        fprintf(stderr,\n                \"Could not make firmware executable: %s\\n\",\n                strerror(errno));\n        return -1;\n    }\n    // Create a marker file, so that the new process knows it is the \"upgraded\"\n    // one\n    FILE *marker = fopen(FW_UPDATED_MARKER, \"w\");\n    if (!marker) {\n        fprintf(stderr, \"Marker file could not be created\\n\");\n        return -1;\n    }\n    fclose(marker);\n\n    assert(ENDPOINT_NAME);\n    // If the call below succeeds, the firmware is considered as \"upgraded\",\n    // and we hope the newly started client registers to the Server.\n    (void) execl(FW_IMAGE_DOWNLOAD_NAME, FW_IMAGE_DOWNLOAD_NAME, ENDPOINT_NAME,\n                 NULL);\n    fprintf(stderr, \"execl() failed: %s\\n\", strerror(errno));\n    // If we are here, it means execl() failed. Marker file MUST now be removed,\n    // as the firmware update failed.\n    unlink(FW_UPDATED_MARKER);\n    return -1;\n}\n\nstatic const anjay_fw_update_handlers_t HANDLERS = {\n    .stream_open = fw_stream_open,\n    .stream_write = fw_stream_write,\n    .stream_finish = fw_stream_finish,\n    .reset = fw_reset,\n    .perform_upgrade = fw_perform_upgrade\n};\n\nconst char *ENDPOINT_NAME = NULL;\n\nint fw_update_install(anjay_t *anjay) {\n    anjay_fw_update_initial_state_t state;\n    memset(&state, 0, sizeof(state));\n\n    if (access(FW_UPDATED_MARKER, F_OK) != -1) {\n        // marker file exists, it means firmware update succeded!\n        state.result = ANJAY_FW_UPDATE_INITIAL_SUCCESS;\n        unlink(FW_UPDATED_MARKER);\n    }\n    // install the module, pass handlers that we implemented and initial state\n    // that we discovered upon startup\n    return anjay_fw_update_install(anjay, &HANDLERS, NULL, &state);\n}\n"
  },
  {
    "path": "examples/tutorial/firmware-update/basic-implementation/src/firmware_update.h",
    "content": "#ifndef FIRMWARE_UPDATE_H\n#define FIRMWARE_UPDATE_H\n#include <anjay/anjay.h>\n#include <anjay/fw_update.h>\n\n/**\n * Buffer for the endpoint name that will be used when re-launching the client\n * after firmware upgrade.\n */\nextern const char *ENDPOINT_NAME;\n\n/**\n * Installs the firmware update module.\n *\n * @returns 0 on success, negative value otherwise.\n */\nint fw_update_install(anjay_t *anjay);\n\n#endif // FIRMWARE_UPDATE_H\n"
  },
  {
    "path": "examples/tutorial/firmware-update/basic-implementation/src/main.c",
    "content": "#include <anjay/anjay.h>\n#include <anjay/security.h>\n#include <anjay/server.h>\n#include <avsystem/commons/avs_log.h>\n\n#include \"firmware_update.h\"\n#include \"time_object.h\"\n\ntypedef struct {\n    anjay_t *anjay;\n    const anjay_dm_object_def_t **time_object;\n} notify_job_args_t;\n\n// Periodically notifies the library about Resource value changes\nstatic void notify_job(avs_sched_t *sched, const void *args_ptr) {\n    const notify_job_args_t *args = (const notify_job_args_t *) args_ptr;\n\n    time_object_notify(args->anjay, args->time_object);\n\n    // Schedule run of the same function after 1 second\n    AVS_SCHED_DELAYED(sched, NULL, avs_time_duration_from_scalar(1, AVS_TIME_S),\n                      notify_job, args, sizeof(*args));\n}\n\n// Installs Security Object and adds an instance of it.\n// An instance of Security Object provides information needed to connect to\n// LwM2M server.\nstatic int setup_security_object(anjay_t *anjay) {\n    if (anjay_security_object_install(anjay)) {\n        return -1;\n    }\n\n    static const char PSK_IDENTITY[] = \"identity\";\n    static const char PSK_KEY[] = \"P4s$w0rd\";\n\n    anjay_security_instance_t security_instance = {\n        .ssid = 1,\n        .server_uri = \"coaps://eu.iot.avsystem.cloud:5684\",\n        .security_mode = ANJAY_SECURITY_PSK,\n        .public_cert_or_psk_identity = (const uint8_t *) PSK_IDENTITY,\n        .public_cert_or_psk_identity_size = strlen(PSK_IDENTITY),\n        .private_cert_or_psk_key = (const uint8_t *) PSK_KEY,\n        .private_cert_or_psk_key_size = strlen(PSK_KEY)\n    };\n\n    // Anjay will assign Instance ID automatically\n    anjay_iid_t security_instance_id = ANJAY_ID_INVALID;\n    if (anjay_security_object_add_instance(anjay, &security_instance,\n                                           &security_instance_id)) {\n        return -1;\n    }\n\n    return 0;\n}\n\n// Installs Server Object and adds an instance of it.\n// An instance of Server Object provides the data related to a LwM2M Server.\nstatic int setup_server_object(anjay_t *anjay) {\n    if (anjay_server_object_install(anjay)) {\n        return -1;\n    }\n\n    const anjay_server_instance_t server_instance = {\n        // Server Short ID\n        .ssid = 1,\n        // Client will send Update message often than every 60 seconds\n        .lifetime = 60,\n        // Disable Default Minimum Period resource\n        .default_min_period = -1,\n        // Disable Default Maximum Period resource\n        .default_max_period = -1,\n        // Disable Disable Timeout resource\n        .disable_timeout = -1,\n        // Sets preferred transport to UDP\n        .binding = \"U\"\n    };\n\n    // Anjay will assign Instance ID automatically\n    anjay_iid_t server_instance_id = ANJAY_ID_INVALID;\n    if (anjay_server_object_add_instance(anjay, &server_instance,\n                                         &server_instance_id)) {\n        return -1;\n    }\n\n    return 0;\n}\n\nint main(int argc, char *argv[]) {\n    if (argc != 2) {\n        avs_log(tutorial, ERROR, \"usage: %s ENDPOINT_NAME\", argv[0]);\n        return -1;\n    }\n\n    ENDPOINT_NAME = argv[1];\n\n    const anjay_configuration_t CONFIG = {\n        .endpoint_name = ENDPOINT_NAME,\n        .in_buffer_size = 4000,\n        .out_buffer_size = 4000,\n        .msg_cache_size = 4000\n    };\n\n    anjay_t *anjay = anjay_new(&CONFIG);\n    if (!anjay) {\n        avs_log(tutorial, ERROR, \"Could not create Anjay object\");\n        return -1;\n    }\n\n    int result = 0;\n    // Setup necessary objects\n    if (setup_security_object(anjay) || setup_server_object(anjay)\n            || fw_update_install(anjay)) {\n        result = -1;\n    }\n\n    const anjay_dm_object_def_t **time_object = NULL;\n    if (!result) {\n        time_object = time_object_create();\n        if (time_object) {\n            result = anjay_register_object(anjay, time_object);\n        } else {\n            result = -1;\n        }\n    }\n\n    if (!result) {\n        // Run notify_job the first time;\n        // this will schedule periodic calls to itself via the scheduler\n        notify_job(anjay_get_scheduler(anjay), &(const notify_job_args_t) {\n                                                   .anjay = anjay,\n                                                   .time_object = time_object\n                                               });\n\n        result = anjay_event_loop_run(\n                anjay, avs_time_duration_from_scalar(1, AVS_TIME_S));\n    }\n\n    anjay_delete(anjay);\n    time_object_release(time_object);\n    return result;\n}\n"
  },
  {
    "path": "examples/tutorial/firmware-update/basic-implementation/src/time_object.c",
    "content": "/**\n * Generated by anjay_codegen.py on 2020-03-19 14:13:34\n *\n * LwM2M Object: Time\n * ID: 3333, URN: urn:oma:lwm2m:ext:3333, Optional, Multiple\n *\n * This IPSO object is used to report the current time in seconds since\n * January 1, 1970 UTC. There is also a fractional time counter that has\n * a range of less than one second.\n */\n\n#include <assert.h>\n#include <stdbool.h>\n\n#include <anjay/anjay.h>\n#include <avsystem/commons/avs_defs.h>\n#include <avsystem/commons/avs_list.h>\n#include <avsystem/commons/avs_memory.h>\n\n#include \"time_object.h\"\n\n/**\n * Current Time: RW, Single, Mandatory\n * type: time, range: N/A, unit: N/A\n * Unix Time. A signed integer representing the number of seconds since\n * Jan 1st, 1970 in the UTC time zone.\n */\n#define RID_CURRENT_TIME 5506\n\n/**\n * Fractional Time: RW, Single, Optional\n * type: float, range: 0..1, unit: s\n * Fractional part of the time when sub-second precision is used (e.g.,\n * 0.23 for 230 ms).\n */\n#define RID_FRACTIONAL_TIME 5507\n\n/**\n * Application Type: RW, Single, Optional\n * type: string, range: N/A, unit: N/A\n * The application type of the sensor or actuator as a string depending\n * on the use case.\n */\n#define RID_APPLICATION_TYPE 5750\n\ntypedef struct time_instance_struct {\n    anjay_iid_t iid;\n    char application_type[64];\n    char application_type_backup[64];\n    int64_t last_notify_timestamp;\n} time_instance_t;\n\ntypedef struct time_object_struct {\n    const anjay_dm_object_def_t *def;\n    AVS_LIST(time_instance_t) instances;\n} time_object_t;\n\nstatic inline time_object_t *\nget_obj(const anjay_dm_object_def_t *const *obj_ptr) {\n    assert(obj_ptr);\n    return AVS_CONTAINER_OF(obj_ptr, time_object_t, def);\n}\n\nstatic time_instance_t *find_instance(const time_object_t *obj,\n                                      anjay_iid_t iid) {\n    AVS_LIST(time_instance_t) it;\n    AVS_LIST_FOREACH(it, obj->instances) {\n        if (it->iid == iid) {\n            return it;\n        } else if (it->iid > iid) {\n            break;\n        }\n    }\n\n    return NULL;\n}\n\nstatic int list_instances(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr,\n                          anjay_dm_list_ctx_t *ctx) {\n    (void) anjay;\n\n    AVS_LIST(time_instance_t) it;\n    AVS_LIST_FOREACH(it, get_obj(obj_ptr)->instances) {\n        anjay_dm_emit(ctx, it->iid);\n    }\n\n    return 0;\n}\n\nstatic int init_instance(time_instance_t *inst, anjay_iid_t iid) {\n    assert(iid != ANJAY_ID_INVALID);\n\n    inst->iid = iid;\n    inst->application_type[0] = '\\0';\n\n    return 0;\n}\n\nstatic void release_instance(time_instance_t *inst) {\n    (void) inst;\n}\n\nstatic time_instance_t *add_instance(time_object_t *obj, anjay_iid_t iid) {\n    assert(find_instance(obj, iid) == NULL);\n\n    AVS_LIST(time_instance_t) created = AVS_LIST_NEW_ELEMENT(time_instance_t);\n    if (!created) {\n        return NULL;\n    }\n\n    int result = init_instance(created, iid);\n    if (result) {\n        AVS_LIST_CLEAR(&created);\n        return NULL;\n    }\n\n    AVS_LIST(time_instance_t) *ptr;\n    AVS_LIST_FOREACH_PTR(ptr, &obj->instances) {\n        if ((*ptr)->iid > created->iid) {\n            break;\n        }\n    }\n\n    AVS_LIST_INSERT(ptr, created);\n    return created;\n}\n\nstatic int instance_create(anjay_t *anjay,\n                           const anjay_dm_object_def_t *const *obj_ptr,\n                           anjay_iid_t iid) {\n    (void) anjay;\n    time_object_t *obj = get_obj(obj_ptr);\n\n    return add_instance(obj, iid) ? 0 : ANJAY_ERR_INTERNAL;\n}\n\nstatic int instance_remove(anjay_t *anjay,\n                           const anjay_dm_object_def_t *const *obj_ptr,\n                           anjay_iid_t iid) {\n    (void) anjay;\n    time_object_t *obj = get_obj(obj_ptr);\n\n    AVS_LIST(time_instance_t) *it;\n    AVS_LIST_FOREACH_PTR(it, &obj->instances) {\n        if ((*it)->iid == iid) {\n            release_instance(*it);\n            AVS_LIST_DELETE(it);\n            return 0;\n        } else if ((*it)->iid > iid) {\n            break;\n        }\n    }\n\n    assert(0);\n    return ANJAY_ERR_NOT_FOUND;\n}\n\nstatic int instance_reset(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr,\n                          anjay_iid_t iid) {\n    (void) anjay;\n\n    time_object_t *obj = get_obj(obj_ptr);\n    time_instance_t *inst = find_instance(obj, iid);\n    assert(inst);\n\n    inst->application_type[0] = '\\0';\n\n    return 0;\n}\n\nstatic int list_resources(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr,\n                          anjay_iid_t iid,\n                          anjay_dm_resource_list_ctx_t *ctx) {\n    (void) anjay;\n    (void) obj_ptr;\n    (void) iid;\n\n    anjay_dm_emit_res(ctx, RID_CURRENT_TIME, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, RID_FRACTIONAL_TIME, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT);\n    anjay_dm_emit_res(ctx, RID_APPLICATION_TYPE, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_PRESENT);\n    return 0;\n}\n\nstatic int resource_read(anjay_t *anjay,\n                         const anjay_dm_object_def_t *const *obj_ptr,\n                         anjay_iid_t iid,\n                         anjay_rid_t rid,\n                         anjay_riid_t riid,\n                         anjay_output_ctx_t *ctx) {\n    (void) anjay;\n\n    time_object_t *obj = get_obj(obj_ptr);\n    time_instance_t *inst = find_instance(obj, iid);\n    assert(inst);\n\n    switch (rid) {\n    case RID_CURRENT_TIME: {\n        assert(riid == ANJAY_ID_INVALID);\n        int64_t timestamp;\n        if (avs_time_real_to_scalar(&timestamp, AVS_TIME_S,\n                                    avs_time_real_now())) {\n            return -1;\n        }\n        return anjay_ret_i64(ctx, timestamp);\n    }\n\n    case RID_APPLICATION_TYPE:\n        assert(riid == ANJAY_ID_INVALID);\n        return anjay_ret_string(ctx, inst->application_type);\n\n    default:\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\nstatic int resource_write(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr,\n                          anjay_iid_t iid,\n                          anjay_rid_t rid,\n                          anjay_riid_t riid,\n                          anjay_input_ctx_t *ctx) {\n    (void) anjay;\n\n    time_object_t *obj = get_obj(obj_ptr);\n    time_instance_t *inst = find_instance(obj, iid);\n    assert(inst);\n\n    switch (rid) {\n    case RID_APPLICATION_TYPE:\n        assert(riid == ANJAY_ID_INVALID);\n        return anjay_get_string(ctx, inst->application_type,\n                                sizeof(inst->application_type));\n\n    default:\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\nint transaction_begin(anjay_t *anjay,\n                      const anjay_dm_object_def_t *const *obj_ptr) {\n    (void) anjay;\n\n    time_object_t *obj = get_obj(obj_ptr);\n\n    time_instance_t *element;\n    AVS_LIST_FOREACH(element, obj->instances) {\n        strcpy(element->application_type_backup, element->application_type);\n    }\n    return 0;\n}\n\nint transaction_rollback(anjay_t *anjay,\n                         const anjay_dm_object_def_t *const *obj_ptr) {\n    (void) anjay;\n\n    time_object_t *obj = get_obj(obj_ptr);\n\n    time_instance_t *element;\n    AVS_LIST_FOREACH(element, obj->instances) {\n        strcpy(element->application_type, element->application_type_backup);\n    }\n    return 0;\n}\n\nstatic const anjay_dm_object_def_t OBJ_DEF = {\n    .oid = 3333,\n    .handlers = {\n        .list_instances = list_instances,\n        .instance_create = instance_create,\n        .instance_remove = instance_remove,\n        .instance_reset = instance_reset,\n\n        .list_resources = list_resources,\n        .resource_read = resource_read,\n        .resource_write = resource_write,\n\n        .transaction_begin = transaction_begin,\n        .transaction_validate = anjay_dm_transaction_NOOP,\n        .transaction_commit = anjay_dm_transaction_NOOP,\n        .transaction_rollback = transaction_rollback\n    }\n};\n\nconst anjay_dm_object_def_t **time_object_create(void) {\n    time_object_t *obj = (time_object_t *) avs_calloc(1, sizeof(time_object_t));\n    if (!obj) {\n        return NULL;\n    }\n    obj->def = &OBJ_DEF;\n\n    time_instance_t *inst = add_instance(obj, 0);\n    if (inst) {\n        strcpy(inst->application_type, \"Clock 0\");\n    } else {\n        avs_free(obj);\n        return NULL;\n    }\n\n    return &obj->def;\n}\n\nvoid time_object_release(const anjay_dm_object_def_t **def) {\n    if (def) {\n        time_object_t *obj = get_obj(def);\n        AVS_LIST_CLEAR(&obj->instances) {\n            release_instance(obj->instances);\n        }\n\n        avs_free(obj);\n    }\n}\n\nvoid time_object_notify(anjay_t *anjay, const anjay_dm_object_def_t **def) {\n    if (!anjay || !def) {\n        return;\n    }\n    time_object_t *obj = get_obj(def);\n\n    int64_t current_timestamp;\n    if (avs_time_real_to_scalar(&current_timestamp, AVS_TIME_S,\n                                avs_time_real_now())) {\n        return;\n    }\n\n    AVS_LIST(time_instance_t) it;\n    AVS_LIST_FOREACH(it, obj->instances) {\n        if (it->last_notify_timestamp != current_timestamp) {\n            if (!anjay_notify_changed(anjay, 3333, it->iid, RID_CURRENT_TIME)) {\n                it->last_notify_timestamp = current_timestamp;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "examples/tutorial/firmware-update/basic-implementation/src/time_object.h",
    "content": "#ifndef TIME_OBJECT_H\n#define TIME_OBJECT_H\n\n#include <anjay/dm.h>\n\nconst anjay_dm_object_def_t **time_object_create(void);\nvoid time_object_release(const anjay_dm_object_def_t **def);\nvoid time_object_notify(anjay_t *anjay, const anjay_dm_object_def_t **def);\n\n#endif // TIME_OBJECT_H\n"
  },
  {
    "path": "examples/tutorial/firmware-update/download-resumption/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.16)\nproject(download-resumption C)\n\nset(CMAKE_C_STANDARD 99)\nset(CMAKE_C_EXTENSIONS OFF)\n\nfind_package(anjay REQUIRED)\n\nadd_executable(${PROJECT_NAME}\n               src/main.c\n               src/firmware_update.c\n               src/firmware_update.h\n               src/time_object.c\n               src/time_object.h)\ntarget_link_libraries(${PROJECT_NAME} PRIVATE anjay)\n"
  },
  {
    "path": "examples/tutorial/firmware-update/download-resumption/src/firmware_update.c",
    "content": "#define _DEFAULT_SOURCE // for fileno()\n#include \"./firmware_update.h\"\n\n#include <assert.h>\n#include <errno.h>\n#include <stdio.h>\n#include <sys/stat.h>\n#include <unistd.h>\n\ntypedef struct {\n    char *persisted_uri;\n    uint32_t resume_offset;\n    anjay_etag_t *resume_etag;\n} download_state_t;\n\nstatic const char *FW_DOWNLOAD_STATE_NAME = \"firmware_dl_state.bin\";\n\nstatic int store_etag(FILE *fp, const anjay_etag_t *etag) {\n    assert(etag);\n    if (fwrite(&etag->size, sizeof(etag->size), 1, fp) != 1) {\n        return -1;\n    }\n    if (etag->size > 0 && fwrite(etag->value, etag->size, 1, fp) != 1) {\n        return -1;\n    }\n    return 0;\n}\n\nstatic int store_download_state(const download_state_t *state) {\n    FILE *fp = fopen(FW_DOWNLOAD_STATE_NAME, \"wb\");\n    if (!fp) {\n        fprintf(stderr, \"could not open %s for writing\\n\",\n                FW_DOWNLOAD_STATE_NAME);\n        return -1;\n    }\n    const uint16_t uri_length = strlen(state->persisted_uri);\n    int result = 0;\n    if (fwrite(&uri_length, sizeof(uri_length), 1, fp) != 1\n            || fwrite(state->persisted_uri, uri_length, 1, fp) != 1\n            || fwrite(&state->resume_offset, sizeof(state->resume_offset), 1,\n                      fp) != 1\n            || store_etag(fp, state->resume_etag)) {\n        fprintf(stderr, \"could not write firmware download state\\n\");\n        result = -1;\n    }\n    fclose(fp);\n    if (result) {\n        unlink(FW_DOWNLOAD_STATE_NAME);\n    }\n    return result;\n}\n\nstatic int restore_etag(FILE *fp, anjay_etag_t **out_etag) {\n    assert(out_etag && !*out_etag); // make sure out_etag is zero-initialized\n    uint8_t size;\n    if (fread(&size, sizeof(size), 1, fp) != 1) {\n        return -1;\n    }\n    anjay_etag_t *etag = anjay_etag_new(size);\n    if (!etag) {\n        return -1;\n    }\n\n    if (size > 0 && fread(etag->value, size, 1, fp) != 1) {\n        avs_free(etag);\n        return -1;\n    }\n    *out_etag = etag;\n    return 0;\n}\n\nstatic int restore_download_state(download_state_t *out_state) {\n    download_state_t data;\n    memset(&data, 0, sizeof(data));\n\n    FILE *fp = fopen(FW_DOWNLOAD_STATE_NAME, \"rb\");\n    if (!fp) {\n        fprintf(stderr, \"could not open %s for reading\\n\",\n                FW_DOWNLOAD_STATE_NAME);\n        return -1;\n    }\n\n    int result = 0;\n    uint16_t uri_length;\n    if (fread(&uri_length, sizeof(uri_length), 1, fp) != 1 || uri_length == 0) {\n        result = -1;\n    }\n    if (!result) {\n        data.persisted_uri = (char *) avs_calloc(1, uri_length + 1);\n        if (!data.persisted_uri) {\n            result = -1;\n        }\n    }\n    if (!result\n            && (fread(data.persisted_uri, uri_length, 1, fp) != 1\n                || fread(&data.resume_offset, sizeof(data.resume_offset), 1, fp)\n                           != 1\n                || restore_etag(fp, &data.resume_etag))) {\n        result = -1;\n    }\n    if (result) {\n        fprintf(stderr, \"could not restore download state from %s\\n\",\n                FW_DOWNLOAD_STATE_NAME);\n        avs_free(data.persisted_uri);\n    } else {\n        *out_state = data;\n    }\n    fclose(fp);\n    return result;\n}\n\nstatic void reset_download_state(download_state_t *state) {\n    avs_free(state->persisted_uri);\n    avs_free(state->resume_etag);\n    memset(state, 0, sizeof(*state));\n    unlink(FW_DOWNLOAD_STATE_NAME);\n}\n\nstatic struct fw_state_t {\n    FILE *firmware_file;\n    // anjay instance this firmware update singleton is associated with\n    anjay_t *anjay;\n    // Current state of the download. It is updated and persited on each\n    // fw_stream_write() call.\n    download_state_t download_state;\n} FW_STATE;\n\nstatic const char *FW_IMAGE_DOWNLOAD_NAME = \"/tmp/firmware_image.bin\";\n\nstatic int fw_open_download_file(long seek_offset) {\n    // It's worth ensuring we start with a NULL firmware_file. In the end\n    // it would be our responsibility to manage this pointer, and we want\n    // to make sure we never leak any memory.\n    assert(FW_STATE.firmware_file == NULL);\n    // We're about to create a firmware file for writing\n    FW_STATE.firmware_file = fopen(FW_IMAGE_DOWNLOAD_NAME, \"wb\");\n    if (!FW_STATE.firmware_file) {\n        fprintf(stderr, \"Could not open %s\\n\", FW_IMAGE_DOWNLOAD_NAME);\n        return -1;\n    }\n    if (fseek(FW_STATE.firmware_file, seek_offset, SEEK_SET)) {\n        fprintf(stderr, \"Could not seek to %ld\\n\", seek_offset);\n        fclose(FW_STATE.firmware_file);\n        FW_STATE.firmware_file = NULL;\n        return -1;\n    }\n    // We've succeeded\n    return 0;\n}\n\nstatic int fw_stream_open(void *user_ptr,\n                          const char *package_uri,\n                          const struct anjay_etag *package_etag) {\n    // We don't use user_ptr.\n    (void) user_ptr;\n\n    // We only persist firmware download state if we have both package_uri\n    // and package_etag. Otherwise the download could not be resumed.\n    if (package_uri && package_etag) {\n        FW_STATE.download_state.persisted_uri = avs_strdup(package_uri);\n        int result = 0;\n        if (!FW_STATE.download_state.persisted_uri) {\n            fprintf(stderr, \"Could not duplicate package URI\\n\");\n            result = -1;\n        }\n        anjay_etag_t *etag_copy = NULL;\n        if (!result && package_etag) {\n            etag_copy = anjay_etag_clone(package_etag);\n            if (!etag_copy) {\n                fprintf(stderr, \"Could not duplicate package ETag\\n\");\n                result = -1;\n            }\n        }\n        if (!result) {\n            FW_STATE.download_state.resume_etag = etag_copy;\n        } else {\n            reset_download_state(&FW_STATE.download_state);\n            return result;\n        }\n    }\n\n    return fw_open_download_file(0);\n}\n\nstatic int fw_stream_write(void *user_ptr, const void *data, size_t length) {\n    (void) user_ptr;\n    // NOTE: fflush() and fsync() are done to be relatively sure that\n    // the data is passed to the hardware and so that we can update\n    // resume_offset in the download state. They are suboptimal on UNIX-like\n    // platforms, and are used just to illustrate when is the right time to\n    // update resume_offset on embedded platforms.\n    if (fwrite(data, length, 1, FW_STATE.firmware_file) != 1\n            || fflush(FW_STATE.firmware_file)\n            || fsync(fileno(FW_STATE.firmware_file))) {\n        fprintf(stderr, \"Writing to firmware image failed\\n\");\n        return -1;\n    }\n    if (FW_STATE.download_state.persisted_uri) {\n        FW_STATE.download_state.resume_offset += length;\n        if (store_download_state(&FW_STATE.download_state)) {\n            // If we returned -1 here, the download would be aborted, so it\n            // is probably better to continue instead.\n            fprintf(stderr,\n                    \"Could not store firmware download state - ignoring\\n\");\n        }\n    }\n    return 0;\n}\n\nstatic int fw_stream_finish(void *user_ptr) {\n    (void) user_ptr;\n    assert(FW_STATE.firmware_file != NULL);\n\n    if (fclose(FW_STATE.firmware_file)) {\n        fprintf(stderr, \"Closing firmware image failed\\n\");\n        FW_STATE.firmware_file = NULL;\n        return -1;\n    }\n    FW_STATE.firmware_file = NULL;\n    return 0;\n}\n\nstatic void fw_reset(void *user_ptr) {\n    // Reset can be issued even if the download never started.\n    if (FW_STATE.firmware_file) {\n        // We ignore the result code of fclose(), as fw_reset() can't fail.\n        (void) fclose(FW_STATE.firmware_file);\n        // and reset our global state to initial value.\n        FW_STATE.firmware_file = NULL;\n    }\n    // Finally, let's remove any downloaded payload\n    unlink(FW_IMAGE_DOWNLOAD_NAME);\n    // And reset any download state.\n    reset_download_state(&FW_STATE.download_state);\n}\n\n// A part of a rather simple logic checking if the firmware update was\n// successfully performed.\nstatic const char *FW_UPDATED_MARKER = \"/tmp/fw-updated-marker\";\n\nstatic int fw_perform_upgrade(void *user_ptr) {\n    if (chmod(FW_IMAGE_DOWNLOAD_NAME, 0700) == -1) {\n        fprintf(stderr,\n                \"Could not make firmware executable: %s\\n\",\n                strerror(errno));\n        return -1;\n    }\n    // Create a marker file, so that the new process knows it is the \"upgraded\"\n    // one\n    FILE *marker = fopen(FW_UPDATED_MARKER, \"w\");\n    if (!marker) {\n        fprintf(stderr, \"Marker file could not be created\\n\");\n        return -1;\n    }\n    fclose(marker);\n\n    assert(ENDPOINT_NAME);\n    // If the call below succeeds, the firmware is considered as \"upgraded\",\n    // and we hope the newly started client registers to the Server.\n    (void) execl(FW_IMAGE_DOWNLOAD_NAME, FW_IMAGE_DOWNLOAD_NAME, ENDPOINT_NAME,\n                 NULL);\n    fprintf(stderr, \"execl() failed: %s\\n\", strerror(errno));\n    // If we are here, it means execl() failed. Marker file MUST now be removed,\n    // as the firmware update failed.\n    unlink(FW_UPDATED_MARKER);\n    return -1;\n}\n\nstatic int fw_get_security_config(void *user_ptr,\n                                  anjay_security_config_t *out_security_info,\n                                  const char *download_uri) {\n    (void) user_ptr;\n    if (!anjay_security_config_from_dm(FW_STATE.anjay, out_security_info,\n                                       download_uri)) {\n        // found a match\n        return 0;\n    }\n\n    // no match found, fallback to loading certificates from given paths\n    memset(out_security_info, 0, sizeof(*out_security_info));\n    const avs_net_certificate_info_t cert_info = {\n        .server_cert_validation = true,\n        .trusted_certs =\n                avs_crypto_certificate_chain_info_from_file(\"./certs/CA.crt\"),\n        .client_cert = avs_crypto_certificate_chain_info_from_file(\n                \"./certs/client.crt\"),\n        .client_key = avs_crypto_private_key_info_from_file(\n                \"./certs/client.key\", NULL)\n    };\n    // NOTE: this assignment is safe, because cert_info contains pointers to\n    // string literals only. If the configuration were to load certificate info\n    // from buffers they would have to be stored somewhere - e.g. on the heap.\n    out_security_info->security_info =\n            avs_net_security_info_from_certificates(cert_info);\n    return 0;\n}\n\nstatic const anjay_fw_update_handlers_t HANDLERS = {\n    .stream_open = fw_stream_open,\n    .stream_write = fw_stream_write,\n    .stream_finish = fw_stream_finish,\n    .reset = fw_reset,\n    .perform_upgrade = fw_perform_upgrade,\n    .get_security_config = fw_get_security_config\n};\n\nconst char *ENDPOINT_NAME = NULL;\n\nint fw_update_install(anjay_t *anjay) {\n    anjay_fw_update_initial_state_t state;\n    memset(&state, 0, sizeof(state));\n\n    if (access(FW_UPDATED_MARKER, F_OK) != -1) {\n        // marker file exists, it means firmware update succeded!\n        state.result = ANJAY_FW_UPDATE_INITIAL_SUCCESS;\n        unlink(FW_UPDATED_MARKER);\n        // we can get rid of any download state if the update succeeded\n        reset_download_state(&FW_STATE.download_state);\n    } else if (!restore_download_state(&FW_STATE.download_state)) {\n        // download state restored, it means we can try using download\n        // resumption\n        if (fw_open_download_file(state.resume_offset)) {\n            // the file cannot be opened or seeking failed\n            reset_download_state(&FW_STATE.download_state);\n        } else {\n            state.persisted_uri = FW_STATE.download_state.persisted_uri;\n            state.resume_offset = FW_STATE.download_state.resume_offset;\n            state.resume_etag = FW_STATE.download_state.resume_etag;\n            state.result = ANJAY_FW_UPDATE_INITIAL_DOWNLOADING;\n        }\n    }\n    // make sure this module is installed for single Anjay instance only\n    assert(FW_STATE.anjay == NULL);\n    FW_STATE.anjay = anjay;\n    // install the module, pass handlers that we implemented and initial state\n    // that we discovered upon startup\n    return anjay_fw_update_install(anjay, &HANDLERS, NULL, &state);\n}\n"
  },
  {
    "path": "examples/tutorial/firmware-update/download-resumption/src/firmware_update.h",
    "content": "#ifndef FIRMWARE_UPDATE_H\n#define FIRMWARE_UPDATE_H\n#include <anjay/anjay.h>\n#include <anjay/fw_update.h>\n\n/**\n * Buffer for the endpoint name that will be used when re-launching the client\n * after firmware upgrade.\n */\nextern const char *ENDPOINT_NAME;\n\n/**\n * Installs the firmware update module.\n *\n * @returns 0 on success, negative value otherwise.\n */\nint fw_update_install(anjay_t *anjay);\n\n#endif // FIRMWARE_UPDATE_H\n"
  },
  {
    "path": "examples/tutorial/firmware-update/download-resumption/src/main.c",
    "content": "#include <anjay/anjay.h>\n#include <anjay/security.h>\n#include <anjay/server.h>\n#include <avsystem/commons/avs_log.h>\n\n#include \"firmware_update.h\"\n#include \"time_object.h\"\n\ntypedef struct {\n    anjay_t *anjay;\n    const anjay_dm_object_def_t **time_object;\n} notify_job_args_t;\n\n// Periodically notifies the library about Resource value changes\nstatic void notify_job(avs_sched_t *sched, const void *args_ptr) {\n    const notify_job_args_t *args = (const notify_job_args_t *) args_ptr;\n\n    time_object_notify(args->anjay, args->time_object);\n\n    // Schedule run of the same function after 1 second\n    AVS_SCHED_DELAYED(sched, NULL, avs_time_duration_from_scalar(1, AVS_TIME_S),\n                      notify_job, args, sizeof(*args));\n}\n\n// Installs Security Object and adds an instance of it.\n// An instance of Security Object provides information needed to connect to\n// LwM2M server.\nstatic int setup_security_object(anjay_t *anjay) {\n    if (anjay_security_object_install(anjay)) {\n        return -1;\n    }\n\n    static const char PSK_IDENTITY[] = \"identity\";\n    static const char PSK_KEY[] = \"P4s$w0rd\";\n\n    anjay_security_instance_t security_instance = {\n        .ssid = 1,\n        .server_uri = \"coaps://eu.iot.avsystem.cloud:5684\",\n        .security_mode = ANJAY_SECURITY_PSK,\n        .public_cert_or_psk_identity = (const uint8_t *) PSK_IDENTITY,\n        .public_cert_or_psk_identity_size = strlen(PSK_IDENTITY),\n        .private_cert_or_psk_key = (const uint8_t *) PSK_KEY,\n        .private_cert_or_psk_key_size = strlen(PSK_KEY)\n    };\n\n    // Anjay will assign Instance ID automatically\n    anjay_iid_t security_instance_id = ANJAY_ID_INVALID;\n    if (anjay_security_object_add_instance(anjay, &security_instance,\n                                           &security_instance_id)) {\n        return -1;\n    }\n\n    return 0;\n}\n\n// Installs Server Object and adds an instance of it.\n// An instance of Server Object provides the data related to a LwM2M Server.\nstatic int setup_server_object(anjay_t *anjay) {\n    if (anjay_server_object_install(anjay)) {\n        return -1;\n    }\n\n    const anjay_server_instance_t server_instance = {\n        // Server Short ID\n        .ssid = 1,\n        // Client will send Update message often than every 60 seconds\n        .lifetime = 60,\n        // Disable Default Minimum Period resource\n        .default_min_period = -1,\n        // Disable Default Maximum Period resource\n        .default_max_period = -1,\n        // Disable Disable Timeout resource\n        .disable_timeout = -1,\n        // Sets preferred transport to UDP\n        .binding = \"U\"\n    };\n\n    // Anjay will assign Instance ID automatically\n    anjay_iid_t server_instance_id = ANJAY_ID_INVALID;\n    if (anjay_server_object_add_instance(anjay, &server_instance,\n                                         &server_instance_id)) {\n        return -1;\n    }\n\n    return 0;\n}\n\nint main(int argc, char *argv[]) {\n    if (argc != 2) {\n        avs_log(tutorial, ERROR, \"usage: %s ENDPOINT_NAME\", argv[0]);\n        return -1;\n    }\n\n    ENDPOINT_NAME = argv[1];\n\n    const anjay_configuration_t CONFIG = {\n        .endpoint_name = ENDPOINT_NAME,\n        .in_buffer_size = 4000,\n        .out_buffer_size = 4000,\n        .msg_cache_size = 4000\n    };\n\n    anjay_t *anjay = anjay_new(&CONFIG);\n    if (!anjay) {\n        avs_log(tutorial, ERROR, \"Could not create Anjay object\");\n        return -1;\n    }\n\n    int result = 0;\n    // Setup necessary objects\n    if (setup_security_object(anjay) || setup_server_object(anjay)\n            || fw_update_install(anjay)) {\n        result = -1;\n    }\n\n    const anjay_dm_object_def_t **time_object = NULL;\n    if (!result) {\n        time_object = time_object_create();\n        if (time_object) {\n            result = anjay_register_object(anjay, time_object);\n        } else {\n            result = -1;\n        }\n    }\n\n    if (!result) {\n        // Run notify_job the first time;\n        // this will schedule periodic calls to itself via the scheduler\n        notify_job(anjay_get_scheduler(anjay), &(const notify_job_args_t) {\n                                                   .anjay = anjay,\n                                                   .time_object = time_object\n                                               });\n\n        result = anjay_event_loop_run(\n                anjay, avs_time_duration_from_scalar(1, AVS_TIME_S));\n    }\n\n    anjay_delete(anjay);\n    time_object_release(time_object);\n    return result;\n}\n"
  },
  {
    "path": "examples/tutorial/firmware-update/download-resumption/src/time_object.c",
    "content": "/**\n * Generated by anjay_codegen.py on 2020-03-19 14:13:34\n *\n * LwM2M Object: Time\n * ID: 3333, URN: urn:oma:lwm2m:ext:3333, Optional, Multiple\n *\n * This IPSO object is used to report the current time in seconds since\n * January 1, 1970 UTC. There is also a fractional time counter that has\n * a range of less than one second.\n */\n\n#include <assert.h>\n#include <stdbool.h>\n\n#include <anjay/anjay.h>\n#include <avsystem/commons/avs_defs.h>\n#include <avsystem/commons/avs_list.h>\n#include <avsystem/commons/avs_memory.h>\n\n#include \"time_object.h\"\n\n/**\n * Current Time: RW, Single, Mandatory\n * type: time, range: N/A, unit: N/A\n * Unix Time. A signed integer representing the number of seconds since\n * Jan 1st, 1970 in the UTC time zone.\n */\n#define RID_CURRENT_TIME 5506\n\n/**\n * Fractional Time: RW, Single, Optional\n * type: float, range: 0..1, unit: s\n * Fractional part of the time when sub-second precision is used (e.g.,\n * 0.23 for 230 ms).\n */\n#define RID_FRACTIONAL_TIME 5507\n\n/**\n * Application Type: RW, Single, Optional\n * type: string, range: N/A, unit: N/A\n * The application type of the sensor or actuator as a string depending\n * on the use case.\n */\n#define RID_APPLICATION_TYPE 5750\n\ntypedef struct time_instance_struct {\n    anjay_iid_t iid;\n    char application_type[64];\n    char application_type_backup[64];\n    int64_t last_notify_timestamp;\n} time_instance_t;\n\ntypedef struct time_object_struct {\n    const anjay_dm_object_def_t *def;\n    AVS_LIST(time_instance_t) instances;\n} time_object_t;\n\nstatic inline time_object_t *\nget_obj(const anjay_dm_object_def_t *const *obj_ptr) {\n    assert(obj_ptr);\n    return AVS_CONTAINER_OF(obj_ptr, time_object_t, def);\n}\n\nstatic time_instance_t *find_instance(const time_object_t *obj,\n                                      anjay_iid_t iid) {\n    AVS_LIST(time_instance_t) it;\n    AVS_LIST_FOREACH(it, obj->instances) {\n        if (it->iid == iid) {\n            return it;\n        } else if (it->iid > iid) {\n            break;\n        }\n    }\n\n    return NULL;\n}\n\nstatic int list_instances(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr,\n                          anjay_dm_list_ctx_t *ctx) {\n    (void) anjay;\n\n    AVS_LIST(time_instance_t) it;\n    AVS_LIST_FOREACH(it, get_obj(obj_ptr)->instances) {\n        anjay_dm_emit(ctx, it->iid);\n    }\n\n    return 0;\n}\n\nstatic int init_instance(time_instance_t *inst, anjay_iid_t iid) {\n    assert(iid != ANJAY_ID_INVALID);\n\n    inst->iid = iid;\n    inst->application_type[0] = '\\0';\n\n    return 0;\n}\n\nstatic void release_instance(time_instance_t *inst) {\n    (void) inst;\n}\n\nstatic time_instance_t *add_instance(time_object_t *obj, anjay_iid_t iid) {\n    assert(find_instance(obj, iid) == NULL);\n\n    AVS_LIST(time_instance_t) created = AVS_LIST_NEW_ELEMENT(time_instance_t);\n    if (!created) {\n        return NULL;\n    }\n\n    int result = init_instance(created, iid);\n    if (result) {\n        AVS_LIST_CLEAR(&created);\n        return NULL;\n    }\n\n    AVS_LIST(time_instance_t) *ptr;\n    AVS_LIST_FOREACH_PTR(ptr, &obj->instances) {\n        if ((*ptr)->iid > created->iid) {\n            break;\n        }\n    }\n\n    AVS_LIST_INSERT(ptr, created);\n    return created;\n}\n\nstatic int instance_create(anjay_t *anjay,\n                           const anjay_dm_object_def_t *const *obj_ptr,\n                           anjay_iid_t iid) {\n    (void) anjay;\n    time_object_t *obj = get_obj(obj_ptr);\n\n    return add_instance(obj, iid) ? 0 : ANJAY_ERR_INTERNAL;\n}\n\nstatic int instance_remove(anjay_t *anjay,\n                           const anjay_dm_object_def_t *const *obj_ptr,\n                           anjay_iid_t iid) {\n    (void) anjay;\n    time_object_t *obj = get_obj(obj_ptr);\n\n    AVS_LIST(time_instance_t) *it;\n    AVS_LIST_FOREACH_PTR(it, &obj->instances) {\n        if ((*it)->iid == iid) {\n            release_instance(*it);\n            AVS_LIST_DELETE(it);\n            return 0;\n        } else if ((*it)->iid > iid) {\n            break;\n        }\n    }\n\n    assert(0);\n    return ANJAY_ERR_NOT_FOUND;\n}\n\nstatic int instance_reset(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr,\n                          anjay_iid_t iid) {\n    (void) anjay;\n\n    time_object_t *obj = get_obj(obj_ptr);\n    time_instance_t *inst = find_instance(obj, iid);\n    assert(inst);\n\n    inst->application_type[0] = '\\0';\n\n    return 0;\n}\n\nstatic int list_resources(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr,\n                          anjay_iid_t iid,\n                          anjay_dm_resource_list_ctx_t *ctx) {\n    (void) anjay;\n    (void) obj_ptr;\n    (void) iid;\n\n    anjay_dm_emit_res(ctx, RID_CURRENT_TIME, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, RID_FRACTIONAL_TIME, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT);\n    anjay_dm_emit_res(ctx, RID_APPLICATION_TYPE, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_PRESENT);\n    return 0;\n}\n\nstatic int resource_read(anjay_t *anjay,\n                         const anjay_dm_object_def_t *const *obj_ptr,\n                         anjay_iid_t iid,\n                         anjay_rid_t rid,\n                         anjay_riid_t riid,\n                         anjay_output_ctx_t *ctx) {\n    (void) anjay;\n\n    time_object_t *obj = get_obj(obj_ptr);\n    time_instance_t *inst = find_instance(obj, iid);\n    assert(inst);\n\n    switch (rid) {\n    case RID_CURRENT_TIME: {\n        assert(riid == ANJAY_ID_INVALID);\n        int64_t timestamp;\n        if (avs_time_real_to_scalar(&timestamp, AVS_TIME_S,\n                                    avs_time_real_now())) {\n            return -1;\n        }\n        return anjay_ret_i64(ctx, timestamp);\n    }\n\n    case RID_APPLICATION_TYPE:\n        assert(riid == ANJAY_ID_INVALID);\n        return anjay_ret_string(ctx, inst->application_type);\n\n    default:\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\nstatic int resource_write(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr,\n                          anjay_iid_t iid,\n                          anjay_rid_t rid,\n                          anjay_riid_t riid,\n                          anjay_input_ctx_t *ctx) {\n    (void) anjay;\n\n    time_object_t *obj = get_obj(obj_ptr);\n    time_instance_t *inst = find_instance(obj, iid);\n    assert(inst);\n\n    switch (rid) {\n    case RID_APPLICATION_TYPE:\n        assert(riid == ANJAY_ID_INVALID);\n        return anjay_get_string(ctx, inst->application_type,\n                                sizeof(inst->application_type));\n\n    default:\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\nint transaction_begin(anjay_t *anjay,\n                      const anjay_dm_object_def_t *const *obj_ptr) {\n    (void) anjay;\n\n    time_object_t *obj = get_obj(obj_ptr);\n\n    time_instance_t *element;\n    AVS_LIST_FOREACH(element, obj->instances) {\n        strcpy(element->application_type_backup, element->application_type);\n    }\n    return 0;\n}\n\nint transaction_rollback(anjay_t *anjay,\n                         const anjay_dm_object_def_t *const *obj_ptr) {\n    (void) anjay;\n\n    time_object_t *obj = get_obj(obj_ptr);\n\n    time_instance_t *element;\n    AVS_LIST_FOREACH(element, obj->instances) {\n        strcpy(element->application_type, element->application_type_backup);\n    }\n    return 0;\n}\n\nstatic const anjay_dm_object_def_t OBJ_DEF = {\n    .oid = 3333,\n    .handlers = {\n        .list_instances = list_instances,\n        .instance_create = instance_create,\n        .instance_remove = instance_remove,\n        .instance_reset = instance_reset,\n\n        .list_resources = list_resources,\n        .resource_read = resource_read,\n        .resource_write = resource_write,\n\n        .transaction_begin = transaction_begin,\n        .transaction_validate = anjay_dm_transaction_NOOP,\n        .transaction_commit = anjay_dm_transaction_NOOP,\n        .transaction_rollback = transaction_rollback\n    }\n};\n\nconst anjay_dm_object_def_t **time_object_create(void) {\n    time_object_t *obj = (time_object_t *) avs_calloc(1, sizeof(time_object_t));\n    if (!obj) {\n        return NULL;\n    }\n    obj->def = &OBJ_DEF;\n\n    time_instance_t *inst = add_instance(obj, 0);\n    if (inst) {\n        strcpy(inst->application_type, \"Clock 0\");\n    } else {\n        avs_free(obj);\n        return NULL;\n    }\n\n    return &obj->def;\n}\n\nvoid time_object_release(const anjay_dm_object_def_t **def) {\n    if (def) {\n        time_object_t *obj = get_obj(def);\n        AVS_LIST_CLEAR(&obj->instances) {\n            release_instance(obj->instances);\n        }\n\n        avs_free(obj);\n    }\n}\n\nvoid time_object_notify(anjay_t *anjay, const anjay_dm_object_def_t **def) {\n    if (!anjay || !def) {\n        return;\n    }\n    time_object_t *obj = get_obj(def);\n\n    int64_t current_timestamp;\n    if (avs_time_real_to_scalar(&current_timestamp, AVS_TIME_S,\n                                avs_time_real_now())) {\n        return;\n    }\n\n    AVS_LIST(time_instance_t) it;\n    AVS_LIST_FOREACH(it, obj->instances) {\n        if (it->last_notify_timestamp != current_timestamp) {\n            if (!anjay_notify_changed(anjay, 3333, it->iid, RID_CURRENT_TIME)) {\n                it->last_notify_timestamp = current_timestamp;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "examples/tutorial/firmware-update/download-resumption/src/time_object.h",
    "content": "#ifndef TIME_OBJECT_H\n#define TIME_OBJECT_H\n\n#include <anjay/dm.h>\n\nconst anjay_dm_object_def_t **time_object_create(void);\nvoid time_object_release(const anjay_dm_object_def_t **def);\nvoid time_object_notify(anjay_t *anjay, const anjay_dm_object_def_t **def);\n\n#endif // TIME_OBJECT_H\n"
  },
  {
    "path": "examples/tutorial/firmware-update/secure-downloads/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.16)\nproject(secure-downloads C)\n\nset(CMAKE_C_STANDARD 99)\nset(CMAKE_C_EXTENSIONS OFF)\n\nfind_package(anjay REQUIRED)\n\nadd_executable(${PROJECT_NAME}\n               src/main.c\n               src/firmware_update.c\n               src/firmware_update.h\n               src/time_object.c\n               src/time_object.h)\ntarget_link_libraries(${PROJECT_NAME} PRIVATE anjay)\n"
  },
  {
    "path": "examples/tutorial/firmware-update/secure-downloads/src/firmware_update.c",
    "content": "#include \"./firmware_update.h\"\n\n#include <assert.h>\n#include <errno.h>\n#include <stdio.h>\n#include <sys/stat.h>\n#include <unistd.h>\n\nstatic struct fw_state_t {\n    FILE *firmware_file;\n    // anjay instance this firmware update singleton is associated with\n    anjay_t *anjay;\n} FW_STATE;\n\nstatic const char *FW_IMAGE_DOWNLOAD_NAME = \"/tmp/firmware_image.bin\";\n\nstatic int fw_stream_open(void *user_ptr,\n                          const char *package_uri,\n                          const struct anjay_etag *package_etag) {\n    // For a moment, we don't need to care about any of the arguments passed.\n    (void) user_ptr;\n    (void) package_uri;\n    (void) package_etag;\n\n    // It's worth ensuring we start with a NULL firmware_file. In the end\n    // it would be our responsibility to manage this pointer, and we want\n    // to make sure we never leak any memory.\n    assert(FW_STATE.firmware_file == NULL);\n    // We're about to create a firmware file for writing\n    FW_STATE.firmware_file = fopen(FW_IMAGE_DOWNLOAD_NAME, \"wb\");\n    if (!FW_STATE.firmware_file) {\n        fprintf(stderr, \"Could not open %s\\n\", FW_IMAGE_DOWNLOAD_NAME);\n        return -1;\n    }\n    // We've succeeded\n    return 0;\n}\n\nstatic int fw_stream_write(void *user_ptr, const void *data, size_t length) {\n    (void) user_ptr;\n    // We only need to write to file and check if that succeeded\n    if (fwrite(data, length, 1, FW_STATE.firmware_file) != 1) {\n        fprintf(stderr, \"Writing to firmware image failed\\n\");\n        return -1;\n    }\n    return 0;\n}\n\nstatic int fw_stream_finish(void *user_ptr) {\n    (void) user_ptr;\n    assert(FW_STATE.firmware_file != NULL);\n\n    if (fclose(FW_STATE.firmware_file)) {\n        fprintf(stderr, \"Closing firmware image failed\\n\");\n        FW_STATE.firmware_file = NULL;\n        return -1;\n    }\n    FW_STATE.firmware_file = NULL;\n    return 0;\n}\n\nstatic void fw_reset(void *user_ptr) {\n    // Reset can be issued even if the download never started.\n    if (FW_STATE.firmware_file) {\n        // We ignore the result code of fclose(), as fw_reset() can't fail.\n        (void) fclose(FW_STATE.firmware_file);\n        // and reset our global state to initial value.\n        FW_STATE.firmware_file = NULL;\n    }\n    // Finally, let's remove any downloaded payload\n    unlink(FW_IMAGE_DOWNLOAD_NAME);\n}\n\n// A part of a rather simple logic checking if the firmware update was\n// successfully performed.\nstatic const char *FW_UPDATED_MARKER = \"/tmp/fw-updated-marker\";\n\nstatic int fw_perform_upgrade(void *user_ptr) {\n    if (chmod(FW_IMAGE_DOWNLOAD_NAME, 0700) == -1) {\n        fprintf(stderr,\n                \"Could not make firmware executable: %s\\n\",\n                strerror(errno));\n        return -1;\n    }\n    // Create a marker file, so that the new process knows it is the \"upgraded\"\n    // one\n    FILE *marker = fopen(FW_UPDATED_MARKER, \"w\");\n    if (!marker) {\n        fprintf(stderr, \"Marker file could not be created\\n\");\n        return -1;\n    }\n    fclose(marker);\n\n    assert(ENDPOINT_NAME);\n    // If the call below succeeds, the firmware is considered as \"upgraded\",\n    // and we hope the newly started client registers to the Server.\n    (void) execl(FW_IMAGE_DOWNLOAD_NAME, FW_IMAGE_DOWNLOAD_NAME, ENDPOINT_NAME,\n                 NULL);\n    fprintf(stderr, \"execl() failed: %s\\n\", strerror(errno));\n    // If we are here, it means execl() failed. Marker file MUST now be removed,\n    // as the firmware update failed.\n    unlink(FW_UPDATED_MARKER);\n    return -1;\n}\n\nstatic int fw_get_security_config(void *user_ptr,\n                                  anjay_security_config_t *out_security_info,\n                                  const char *download_uri) {\n    (void) user_ptr;\n    if (!anjay_security_config_from_dm(FW_STATE.anjay, out_security_info,\n                                       download_uri)) {\n        // found a match\n        return 0;\n    }\n\n    // no match found, fallback to loading certificates from given paths\n    memset(out_security_info, 0, sizeof(*out_security_info));\n    const avs_net_certificate_info_t cert_info = {\n        .server_cert_validation = true,\n        .trusted_certs =\n                avs_crypto_certificate_chain_info_from_file(\"./certs/CA.crt\"),\n        .client_cert = avs_crypto_certificate_chain_info_from_file(\n                \"./certs/client.crt\"),\n        .client_key = avs_crypto_private_key_info_from_file(\n                \"./certs/client.key\", NULL)\n    };\n    // NOTE: this assignment is safe, because cert_info contains pointers to\n    // string literals only. If the configuration were to load certificate info\n    // from buffers they would have to be stored somewhere - e.g. on the heap.\n    out_security_info->security_info =\n            avs_net_security_info_from_certificates(cert_info);\n    return 0;\n}\n\nstatic const anjay_fw_update_handlers_t HANDLERS = {\n    .stream_open = fw_stream_open,\n    .stream_write = fw_stream_write,\n    .stream_finish = fw_stream_finish,\n    .reset = fw_reset,\n    .perform_upgrade = fw_perform_upgrade,\n    .get_security_config = fw_get_security_config\n};\n\nconst char *ENDPOINT_NAME = NULL;\n\nint fw_update_install(anjay_t *anjay) {\n    anjay_fw_update_initial_state_t state;\n    memset(&state, 0, sizeof(state));\n\n    if (access(FW_UPDATED_MARKER, F_OK) != -1) {\n        // marker file exists, it means firmware update succeded!\n        state.result = ANJAY_FW_UPDATE_INITIAL_SUCCESS;\n        unlink(FW_UPDATED_MARKER);\n    }\n    // make sure this module is installed for single Anjay instance only\n    assert(FW_STATE.anjay == NULL);\n    FW_STATE.anjay = anjay;\n    // install the module, pass handlers that we implemented and initial state\n    // that we discovered upon startup\n    return anjay_fw_update_install(anjay, &HANDLERS, NULL, &state);\n}\n"
  },
  {
    "path": "examples/tutorial/firmware-update/secure-downloads/src/firmware_update.h",
    "content": "#ifndef FIRMWARE_UPDATE_H\n#define FIRMWARE_UPDATE_H\n#include <anjay/anjay.h>\n#include <anjay/fw_update.h>\n\n/**\n * Buffer for the endpoint name that will be used when re-launching the client\n * after firmware upgrade.\n */\nextern const char *ENDPOINT_NAME;\n\n/**\n * Installs the firmware update module.\n *\n * @returns 0 on success, negative value otherwise.\n */\nint fw_update_install(anjay_t *anjay);\n\n#endif // FIRMWARE_UPDATE_H\n"
  },
  {
    "path": "examples/tutorial/firmware-update/secure-downloads/src/main.c",
    "content": "#include <anjay/anjay.h>\n#include <anjay/security.h>\n#include <anjay/server.h>\n#include <avsystem/commons/avs_log.h>\n\n#include \"firmware_update.h\"\n#include \"time_object.h\"\n\ntypedef struct {\n    anjay_t *anjay;\n    const anjay_dm_object_def_t **time_object;\n} notify_job_args_t;\n\n// Periodically notifies the library about Resource value changes\nstatic void notify_job(avs_sched_t *sched, const void *args_ptr) {\n    const notify_job_args_t *args = (const notify_job_args_t *) args_ptr;\n\n    time_object_notify(args->anjay, args->time_object);\n\n    // Schedule run of the same function after 1 second\n    AVS_SCHED_DELAYED(sched, NULL, avs_time_duration_from_scalar(1, AVS_TIME_S),\n                      notify_job, args, sizeof(*args));\n}\n\n// Installs Security Object and adds an instance of it.\n// An instance of Security Object provides information needed to connect to\n// LwM2M server.\nstatic int setup_security_object(anjay_t *anjay) {\n    if (anjay_security_object_install(anjay)) {\n        return -1;\n    }\n\n    static const char PSK_IDENTITY[] = \"identity\";\n    static const char PSK_KEY[] = \"P4s$w0rd\";\n\n    anjay_security_instance_t security_instance = {\n        .ssid = 1,\n        .server_uri = \"coaps://eu.iot.avsystem.cloud:5684\",\n        .security_mode = ANJAY_SECURITY_PSK,\n        .public_cert_or_psk_identity = (const uint8_t *) PSK_IDENTITY,\n        .public_cert_or_psk_identity_size = strlen(PSK_IDENTITY),\n        .private_cert_or_psk_key = (const uint8_t *) PSK_KEY,\n        .private_cert_or_psk_key_size = strlen(PSK_KEY)\n    };\n\n    // Anjay will assign Instance ID automatically\n    anjay_iid_t security_instance_id = ANJAY_ID_INVALID;\n    if (anjay_security_object_add_instance(anjay, &security_instance,\n                                           &security_instance_id)) {\n        return -1;\n    }\n\n    return 0;\n}\n\n// Installs Server Object and adds an instance of it.\n// An instance of Server Object provides the data related to a LwM2M Server.\nstatic int setup_server_object(anjay_t *anjay) {\n    if (anjay_server_object_install(anjay)) {\n        return -1;\n    }\n\n    const anjay_server_instance_t server_instance = {\n        // Server Short ID\n        .ssid = 1,\n        // Client will send Update message often than every 60 seconds\n        .lifetime = 60,\n        // Disable Default Minimum Period resource\n        .default_min_period = -1,\n        // Disable Default Maximum Period resource\n        .default_max_period = -1,\n        // Disable Disable Timeout resource\n        .disable_timeout = -1,\n        // Sets preferred transport to UDP\n        .binding = \"U\"\n    };\n\n    // Anjay will assign Instance ID automatically\n    anjay_iid_t server_instance_id = ANJAY_ID_INVALID;\n    if (anjay_server_object_add_instance(anjay, &server_instance,\n                                         &server_instance_id)) {\n        return -1;\n    }\n\n    return 0;\n}\n\nint main(int argc, char *argv[]) {\n    if (argc != 2) {\n        avs_log(tutorial, ERROR, \"usage: %s ENDPOINT_NAME\", argv[0]);\n        return -1;\n    }\n\n    ENDPOINT_NAME = argv[1];\n\n    const anjay_configuration_t CONFIG = {\n        .endpoint_name = ENDPOINT_NAME,\n        .in_buffer_size = 4000,\n        .out_buffer_size = 4000,\n        .msg_cache_size = 4000\n    };\n\n    anjay_t *anjay = anjay_new(&CONFIG);\n    if (!anjay) {\n        avs_log(tutorial, ERROR, \"Could not create Anjay object\");\n        return -1;\n    }\n\n    int result = 0;\n    // Setup necessary objects\n    if (setup_security_object(anjay) || setup_server_object(anjay)\n            || fw_update_install(anjay)) {\n        result = -1;\n    }\n\n    const anjay_dm_object_def_t **time_object = NULL;\n    if (!result) {\n        time_object = time_object_create();\n        if (time_object) {\n            result = anjay_register_object(anjay, time_object);\n        } else {\n            result = -1;\n        }\n    }\n\n    if (!result) {\n        // Run notify_job the first time;\n        // this will schedule periodic calls to itself via the scheduler\n        notify_job(anjay_get_scheduler(anjay), &(const notify_job_args_t) {\n                                                   .anjay = anjay,\n                                                   .time_object = time_object\n                                               });\n\n        result = anjay_event_loop_run(\n                anjay, avs_time_duration_from_scalar(1, AVS_TIME_S));\n    }\n\n    anjay_delete(anjay);\n    time_object_release(time_object);\n    return result;\n}\n"
  },
  {
    "path": "examples/tutorial/firmware-update/secure-downloads/src/time_object.c",
    "content": "/**\n * Generated by anjay_codegen.py on 2020-03-19 14:13:34\n *\n * LwM2M Object: Time\n * ID: 3333, URN: urn:oma:lwm2m:ext:3333, Optional, Multiple\n *\n * This IPSO object is used to report the current time in seconds since\n * January 1, 1970 UTC. There is also a fractional time counter that has\n * a range of less than one second.\n */\n\n#include <assert.h>\n#include <stdbool.h>\n\n#include <anjay/anjay.h>\n#include <avsystem/commons/avs_defs.h>\n#include <avsystem/commons/avs_list.h>\n#include <avsystem/commons/avs_memory.h>\n\n#include \"time_object.h\"\n\n/**\n * Current Time: RW, Single, Mandatory\n * type: time, range: N/A, unit: N/A\n * Unix Time. A signed integer representing the number of seconds since\n * Jan 1st, 1970 in the UTC time zone.\n */\n#define RID_CURRENT_TIME 5506\n\n/**\n * Fractional Time: RW, Single, Optional\n * type: float, range: 0..1, unit: s\n * Fractional part of the time when sub-second precision is used (e.g.,\n * 0.23 for 230 ms).\n */\n#define RID_FRACTIONAL_TIME 5507\n\n/**\n * Application Type: RW, Single, Optional\n * type: string, range: N/A, unit: N/A\n * The application type of the sensor or actuator as a string depending\n * on the use case.\n */\n#define RID_APPLICATION_TYPE 5750\n\ntypedef struct time_instance_struct {\n    anjay_iid_t iid;\n    char application_type[64];\n    char application_type_backup[64];\n    int64_t last_notify_timestamp;\n} time_instance_t;\n\ntypedef struct time_object_struct {\n    const anjay_dm_object_def_t *def;\n    AVS_LIST(time_instance_t) instances;\n} time_object_t;\n\nstatic inline time_object_t *\nget_obj(const anjay_dm_object_def_t *const *obj_ptr) {\n    assert(obj_ptr);\n    return AVS_CONTAINER_OF(obj_ptr, time_object_t, def);\n}\n\nstatic time_instance_t *find_instance(const time_object_t *obj,\n                                      anjay_iid_t iid) {\n    AVS_LIST(time_instance_t) it;\n    AVS_LIST_FOREACH(it, obj->instances) {\n        if (it->iid == iid) {\n            return it;\n        } else if (it->iid > iid) {\n            break;\n        }\n    }\n\n    return NULL;\n}\n\nstatic int list_instances(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr,\n                          anjay_dm_list_ctx_t *ctx) {\n    (void) anjay;\n\n    AVS_LIST(time_instance_t) it;\n    AVS_LIST_FOREACH(it, get_obj(obj_ptr)->instances) {\n        anjay_dm_emit(ctx, it->iid);\n    }\n\n    return 0;\n}\n\nstatic int init_instance(time_instance_t *inst, anjay_iid_t iid) {\n    assert(iid != ANJAY_ID_INVALID);\n\n    inst->iid = iid;\n    inst->application_type[0] = '\\0';\n\n    return 0;\n}\n\nstatic void release_instance(time_instance_t *inst) {\n    (void) inst;\n}\n\nstatic time_instance_t *add_instance(time_object_t *obj, anjay_iid_t iid) {\n    assert(find_instance(obj, iid) == NULL);\n\n    AVS_LIST(time_instance_t) created = AVS_LIST_NEW_ELEMENT(time_instance_t);\n    if (!created) {\n        return NULL;\n    }\n\n    int result = init_instance(created, iid);\n    if (result) {\n        AVS_LIST_CLEAR(&created);\n        return NULL;\n    }\n\n    AVS_LIST(time_instance_t) *ptr;\n    AVS_LIST_FOREACH_PTR(ptr, &obj->instances) {\n        if ((*ptr)->iid > created->iid) {\n            break;\n        }\n    }\n\n    AVS_LIST_INSERT(ptr, created);\n    return created;\n}\n\nstatic int instance_create(anjay_t *anjay,\n                           const anjay_dm_object_def_t *const *obj_ptr,\n                           anjay_iid_t iid) {\n    (void) anjay;\n    time_object_t *obj = get_obj(obj_ptr);\n\n    return add_instance(obj, iid) ? 0 : ANJAY_ERR_INTERNAL;\n}\n\nstatic int instance_remove(anjay_t *anjay,\n                           const anjay_dm_object_def_t *const *obj_ptr,\n                           anjay_iid_t iid) {\n    (void) anjay;\n    time_object_t *obj = get_obj(obj_ptr);\n\n    AVS_LIST(time_instance_t) *it;\n    AVS_LIST_FOREACH_PTR(it, &obj->instances) {\n        if ((*it)->iid == iid) {\n            release_instance(*it);\n            AVS_LIST_DELETE(it);\n            return 0;\n        } else if ((*it)->iid > iid) {\n            break;\n        }\n    }\n\n    assert(0);\n    return ANJAY_ERR_NOT_FOUND;\n}\n\nstatic int instance_reset(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr,\n                          anjay_iid_t iid) {\n    (void) anjay;\n\n    time_object_t *obj = get_obj(obj_ptr);\n    time_instance_t *inst = find_instance(obj, iid);\n    assert(inst);\n\n    inst->application_type[0] = '\\0';\n\n    return 0;\n}\n\nstatic int list_resources(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr,\n                          anjay_iid_t iid,\n                          anjay_dm_resource_list_ctx_t *ctx) {\n    (void) anjay;\n    (void) obj_ptr;\n    (void) iid;\n\n    anjay_dm_emit_res(ctx, RID_CURRENT_TIME, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, RID_FRACTIONAL_TIME, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT);\n    anjay_dm_emit_res(ctx, RID_APPLICATION_TYPE, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_PRESENT);\n    return 0;\n}\n\nstatic int resource_read(anjay_t *anjay,\n                         const anjay_dm_object_def_t *const *obj_ptr,\n                         anjay_iid_t iid,\n                         anjay_rid_t rid,\n                         anjay_riid_t riid,\n                         anjay_output_ctx_t *ctx) {\n    (void) anjay;\n\n    time_object_t *obj = get_obj(obj_ptr);\n    time_instance_t *inst = find_instance(obj, iid);\n    assert(inst);\n\n    switch (rid) {\n    case RID_CURRENT_TIME: {\n        assert(riid == ANJAY_ID_INVALID);\n        int64_t timestamp;\n        if (avs_time_real_to_scalar(&timestamp, AVS_TIME_S,\n                                    avs_time_real_now())) {\n            return -1;\n        }\n        return anjay_ret_i64(ctx, timestamp);\n    }\n\n    case RID_APPLICATION_TYPE:\n        assert(riid == ANJAY_ID_INVALID);\n        return anjay_ret_string(ctx, inst->application_type);\n\n    default:\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\nstatic int resource_write(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr,\n                          anjay_iid_t iid,\n                          anjay_rid_t rid,\n                          anjay_riid_t riid,\n                          anjay_input_ctx_t *ctx) {\n    (void) anjay;\n\n    time_object_t *obj = get_obj(obj_ptr);\n    time_instance_t *inst = find_instance(obj, iid);\n    assert(inst);\n\n    switch (rid) {\n    case RID_APPLICATION_TYPE:\n        assert(riid == ANJAY_ID_INVALID);\n        return anjay_get_string(ctx, inst->application_type,\n                                sizeof(inst->application_type));\n\n    default:\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\nint transaction_begin(anjay_t *anjay,\n                      const anjay_dm_object_def_t *const *obj_ptr) {\n    (void) anjay;\n\n    time_object_t *obj = get_obj(obj_ptr);\n\n    time_instance_t *element;\n    AVS_LIST_FOREACH(element, obj->instances) {\n        strcpy(element->application_type_backup, element->application_type);\n    }\n    return 0;\n}\n\nint transaction_rollback(anjay_t *anjay,\n                         const anjay_dm_object_def_t *const *obj_ptr) {\n    (void) anjay;\n\n    time_object_t *obj = get_obj(obj_ptr);\n\n    time_instance_t *element;\n    AVS_LIST_FOREACH(element, obj->instances) {\n        strcpy(element->application_type, element->application_type_backup);\n    }\n    return 0;\n}\n\nstatic const anjay_dm_object_def_t OBJ_DEF = {\n    .oid = 3333,\n    .handlers = {\n        .list_instances = list_instances,\n        .instance_create = instance_create,\n        .instance_remove = instance_remove,\n        .instance_reset = instance_reset,\n\n        .list_resources = list_resources,\n        .resource_read = resource_read,\n        .resource_write = resource_write,\n\n        .transaction_begin = transaction_begin,\n        .transaction_validate = anjay_dm_transaction_NOOP,\n        .transaction_commit = anjay_dm_transaction_NOOP,\n        .transaction_rollback = transaction_rollback\n    }\n};\n\nconst anjay_dm_object_def_t **time_object_create(void) {\n    time_object_t *obj = (time_object_t *) avs_calloc(1, sizeof(time_object_t));\n    if (!obj) {\n        return NULL;\n    }\n    obj->def = &OBJ_DEF;\n\n    time_instance_t *inst = add_instance(obj, 0);\n    if (inst) {\n        strcpy(inst->application_type, \"Clock 0\");\n    } else {\n        avs_free(obj);\n        return NULL;\n    }\n\n    return &obj->def;\n}\n\nvoid time_object_release(const anjay_dm_object_def_t **def) {\n    if (def) {\n        time_object_t *obj = get_obj(def);\n        AVS_LIST_CLEAR(&obj->instances) {\n            release_instance(obj->instances);\n        }\n\n        avs_free(obj);\n    }\n}\n\nvoid time_object_notify(anjay_t *anjay, const anjay_dm_object_def_t **def) {\n    if (!anjay || !def) {\n        return;\n    }\n    time_object_t *obj = get_obj(def);\n\n    int64_t current_timestamp;\n    if (avs_time_real_to_scalar(&current_timestamp, AVS_TIME_S,\n                                avs_time_real_now())) {\n        return;\n    }\n\n    AVS_LIST(time_instance_t) it;\n    AVS_LIST_FOREACH(it, obj->instances) {\n        if (it->last_notify_timestamp != current_timestamp) {\n            if (!anjay_notify_changed(anjay, 3333, it->iid, RID_CURRENT_TIME)) {\n                it->last_notify_timestamp = current_timestamp;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "examples/tutorial/firmware-update/secure-downloads/src/time_object.h",
    "content": "#ifndef TIME_OBJECT_H\n#define TIME_OBJECT_H\n\n#include <anjay/dm.h>\n\nconst anjay_dm_object_def_t **time_object_create(void);\nvoid time_object_release(const anjay_dm_object_def_t **def);\nvoid time_object_notify(anjay_t *anjay, const anjay_dm_object_def_t **def);\n\n#endif // TIME_OBJECT_H\n"
  },
  {
    "path": "include_public/anjay/access_control.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ANJAY_INCLUDE_ANJAY_MODULES_ACCESS_CONTROL_H\n#define ANJAY_INCLUDE_ANJAY_MODULES_ACCESS_CONTROL_H\n\n#include <avsystem/commons/avs_stream.h>\n\n#include <anjay/dm.h>\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n/**\n * Installs the Access Control Object in an Anjay object.\n *\n * The Access Control module does not require explicit cleanup; all resources\n * will be automatically freed up during the call to @ref anjay_delete.\n *\n * WARNING: After any modification of Security, Server or Access Control Object\n * by means other than LwM2M one has to execute\n * @ref anjay_notify_instances_changed in order to trigger necessary\n * revalidation routines of Access Control Object instances.\n *\n * @param anjay ANJAY object for which the Access Control Object is installed.\n *\n * @returns 0 on success, or a negative value in case of error.\n */\nint anjay_access_control_install(anjay_t *anjay);\n\n/**\n * Removes all instances of Access Control Object, leaving it in an empty state.\n *\n * @param anjay ANJAY object with the Access Control module installed\n */\nvoid anjay_access_control_purge(anjay_t *anjay);\n\n/**\n * Dumps Access Control Object Instances to the @p out_stream.\n *\n * @param anjay         ANJAY object with the Access Control module installed\n * @param out_stream    stream to write to\n * @returns AVS_OK in case of success, or an error code.\n */\navs_error_t anjay_access_control_persist(anjay_t *anjay,\n                                         avs_stream_t *out_stream);\n\n/**\n * Tries to restore Access Control Object Instances from given @p in_stream.\n *\n * @param anjay         ANJAY object with the Access Control module installed\n * @param in_stream     stream used for reading Access Control Object Instances\n * @returns AVS_OK in case of success, or an error code.\n */\navs_error_t anjay_access_control_restore(anjay_t *anjay,\n                                         avs_stream_t *in_stream);\n\n/**\n * Checks whether the Access Control Object from Anjay instance has been\n * modified since last successful call to @ref anjay_access_control_persist or\n * @ref anjay_access_control_restore.\n */\nbool anjay_access_control_is_modified(anjay_t *anjay);\n\n/**\n * Assign permissions for Instance /OID/IID to a particular server.\n *\n * @param anjay       ANJAY object with the Access Control module installed\n * @param oid         Object ID of the target Instance.\n * @param iid         Target Object Instance ID, or <c>ANJAY_ID_INVALID</c>\n *                    (i.e., MAX_ID==65535) to set an ACL referring to new\n *                    instance creation.\n * @param ssid        SSID of the server to grant permissions to.\n *                    @ref ANJAY_SSID_ANY may be used to set default permissions\n *                    for all servers with no explicit ACL entry.\n *                    Must not be equal to MAX_ID (65535).\n * @param access_mask ACL value to set for given Instance.\n *                    NOTE: Create permission makes no sense for an Instance,\n *                    and other permissions make no sense for new instance\n *                    creation.\n * @return 0 in case of success, negative value in case of an error (including\n *         the case where target Object Instance does not exist).\n */\nint anjay_access_control_set_acl(anjay_t *anjay,\n                                 anjay_oid_t oid,\n                                 anjay_iid_t iid,\n                                 anjay_ssid_t ssid,\n                                 anjay_access_mask_t access_mask);\n\n/**\n * Set the Access Control Owner for a given Object Instance.\n *\n * @param anjay         ANJAY object with the Access Control module installed\n *\n * @param target_oid    Object ID of the target Instance.\n *\n * @param target_iid    Target Object Instance ID, or <c>ANJAY_ID_INVALID</c>\n *                      (i.e., MAX_ID==65535) to set an ACL referring to new\n *                      instance creation.\n *\n * @param owner_ssid    SSID of the server which should become the Access\n *                      Control Owner for the given Object Instance.\n *                      <c>ANJAY_SSID_BOOTSTRAP</c> can be specified to signify\n *                      that the ACL shall not be editable by any regular LwM2M\n *                      Server.\n *\n * @param inout_acl_iid Setting related to the Instance ID of the Access Control\n *                      Object Instance that governs the given target.\n *                      @li If <c>NULL</c>, any existing instance governing the\n *                          given target will be used if present, or a new\n *                          instance with a first free Instance ID will be\n *                          created.\n *                      @li If non-<c>NULL</c> and <c>*inout_acl_iid ==\n *                          ANJAY_ID_INVALID</c>, any existing instance\n *                          governing the given target will be used if present,\n *                          or a new instance with a first free Instance ID will\n *                          be created, and <c>*inout_acl_iid</c> will be set to\n *                          the Instance ID of the affected Access Control\n *                          Object Instance upon a successful return from this\n *                          function.\n *                      @li If non-<c>NULL</c> and <c>*inout_acl_iid !=\n *                          ANJAY_ID_INVALID</c>, a new instance with that ID\n *                          will be created; an existing instance may also be\n *                          used, but only if the instance governing the given\n *                          target has the ID specified. If an instance\n *                          governing the given target already exists and has a\n *                          different Instance ID, or if an instance with the\n *                          given ID, but governs a different target,\n *                          <c>*inout_acl_iid</c> will be set to the ID of the\n *                          conflicting instance and this function will return\n *                          an error.\n *\n * @return 0 in case of success, negative value in case of an error (including\n *         the case where target Object Instance does not exist).\n */\nint anjay_access_control_set_owner(anjay_t *anjay,\n                                   anjay_oid_t target_oid,\n                                   anjay_iid_t target_iid,\n                                   anjay_ssid_t owner_ssid,\n                                   anjay_iid_t *inout_acl_iid);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif /* ANJAY_INCLUDE_ANJAY_MODULES_ACCESS_CONTROL_H */\n"
  },
  {
    "path": "include_public/anjay/advanced_fw_update.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ANJAY_INCLUDE_ANJAY_ADVANCED_FW_UPDATE_H\n#define ANJAY_INCLUDE_ANJAY_ADVANCED_FW_UPDATE_H\n\n#include <anjay/core.h>\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n#define ANJAY_ADVANCED_FW_UPDATE_OID 33629\n\n/**\n * Numeric values of the Advanced Firmware Update State resource.\n * See AVSystem specification of Advanced Firmware Update for details.\n *\n * Note: they SHOULD only be used with\n * @ref anjay_advanced_fw_update_set_state_and_result .\n */\ntypedef enum {\n    ANJAY_ADVANCED_FW_UPDATE_STATE_IDLE = 0,\n    ANJAY_ADVANCED_FW_UPDATE_STATE_DOWNLOADING,\n    ANJAY_ADVANCED_FW_UPDATE_STATE_DOWNLOADED,\n    ANJAY_ADVANCED_FW_UPDATE_STATE_UPDATING\n} anjay_advanced_fw_update_state_t;\n\n/**\n * Numeric values of the Advanced Firmware Update Result resource.\n * See AVSystem specification of Advanced Firmware Update for details.\n *\n * Note: they SHOULD only be used with\n * @ref anjay_advanced_fw_update_set_state_and_result .\n */\ntypedef enum {\n    ANJAY_ADVANCED_FW_UPDATE_RESULT_INITIAL = 0,\n    ANJAY_ADVANCED_FW_UPDATE_RESULT_SUCCESS = 1,\n    ANJAY_ADVANCED_FW_UPDATE_RESULT_NOT_ENOUGH_SPACE = 2,\n    ANJAY_ADVANCED_FW_UPDATE_RESULT_OUT_OF_MEMORY = 3,\n    ANJAY_ADVANCED_FW_UPDATE_RESULT_CONNECTION_LOST = 4,\n    ANJAY_ADVANCED_FW_UPDATE_RESULT_INTEGRITY_FAILURE = 5,\n    ANJAY_ADVANCED_FW_UPDATE_RESULT_UNSUPPORTED_PACKAGE_TYPE = 6,\n    ANJAY_ADVANCED_FW_UPDATE_RESULT_INVALID_URI = 7,\n    ANJAY_ADVANCED_FW_UPDATE_RESULT_FAILED = 8,\n    ANJAY_ADVANCED_FW_UPDATE_RESULT_UNSUPPORTED_PROTOCOL = 9,\n    ANJAY_ADVANCED_FW_UPDATE_RESULT_UPDATE_CANCELLED = 10,\n    ANJAY_ADVANCED_FW_UPDATE_RESULT_DEFERRED = 11,\n    ANJAY_ADVANCED_FW_UPDATE_RESULT_CONFLICTING_STATE = 12,\n    ANJAY_ADVANCED_FW_UPDATE_RESULT_DEPENDENCY_ERROR = 13,\n} anjay_advanced_fw_update_result_t;\n\n/** @name Advanced Firmware Update result codes\n * @{\n * The following result codes may be returned from\n * @ref anjay_advanced_fw_update_stream_write_t,\n * @ref anjay_advanced_fw_update_stream_finish_t or\n * @ref anjay_advanced_fw_update_perform_upgrade_t to control the value of the\n * Update Result Resource after the failure.\n *\n * Their values correspond to negated numeric values of that resource. However,\n * attempting to use other negated value will be checked and cause a fall-back\n * to a value default for a given handler.\n */\n#define ANJAY_ADVANCED_FW_UPDATE_ERR_NOT_ENOUGH_SPACE \\\n    (-(int) ANJAY_ADVANCED_FW_UPDATE_RESULT_NOT_ENOUGH_SPACE)\n#define ANJAY_ADVANCED_FW_UPDATE_ERR_OUT_OF_MEMORY \\\n    (-(int) ANJAY_ADVANCED_FW_UPDATE_RESULT_OUT_OF_MEMORY)\n#define ANJAY_ADVANCED_FW_UPDATE_ERR_INTEGRITY_FAILURE \\\n    (-(int) ANJAY_ADVANCED_FW_UPDATE_RESULT_INTEGRITY_FAILURE)\n#define ANJAY_ADVANCED_FW_UPDATE_ERR_UNSUPPORTED_PACKAGE_TYPE \\\n    (-(int) ANJAY_ADVANCED_FW_UPDATE_RESULT_UNSUPPORTED_PACKAGE_TYPE)\n#define ANJAY_ADVANCED_FW_UPDATE_ERR_DEFERRED \\\n    (-(int) ANJAY_ADVANCED_FW_UPDATE_RESULT_DEFERRED)\n#define ANJAY_ADVANCED_FW_UPDATE_ERR_CONFLICTING_STATE \\\n    (-(int) ANJAY_ADVANCED_FW_UPDATE_RESULT_CONFLICTING_STATE)\n#define ANJAY_ADVANCED_FW_UPDATE_ERR_DEPENDENCY_ERROR \\\n    (-(int) ANJAY_ADVANCED_FW_UPDATE_RESULT_DEPENDENCY_ERROR)\n/** @} */\n\n/**\n * Numeric values of the Advanced Firmware Update Severity resource.\n * See AVSystem specification of Advanced Firmware Update for details.\n */\ntypedef enum {\n    ANJAY_ADVANCED_FW_UPDATE_SEVERITY_CRITICAL = 0,\n    ANJAY_ADVANCED_FW_UPDATE_SEVERITY_MANDATORY,\n    ANJAY_ADVANCED_FW_UPDATE_SEVERITY_OPTIONAL\n} anjay_advanced_fw_update_severity_t;\n\n/**\n * Bool values of the Advanced Firmware Update object configuration.\n * This Advanced Firmware Update object configuration affects all instances.\n */\ntypedef struct {\n    /**\n     * Informs the module to try reusing sockets of existing LwM2M Servers to\n     * download the firmware image if the download URI matches any of the LwM2M\n     * Servers.\n     */\n    bool prefer_same_socket_downloads;\n#ifdef ANJAY_WITH_SEND\n    /**\n     * Enables using LwM2M Send to report State, Update Result and Firmware\n     * Version to the LwM2M Server (if LwM2M Send is enabled) during firmware\n     * update.\n     */\n    bool use_lwm2m_send;\n#endif // ANJAY_WITH_SEND\n} anjay_advanced_fw_update_global_config_t;\n\n/**\n * Information about the state to initialize the instances of Advanced\n * Firmware Update objects in.\n */\ntypedef struct {\n    /**\n     * Information about the state of update of particular instance of\n     * Advance Firmware Update object, at the moment of initialization.\n     */\n    anjay_advanced_fw_update_state_t state;\n\n    /**\n     * Information about the result of update of particular instance of\n     * Advance Firmware Update object, at the moment of initialization.\n     */\n    anjay_advanced_fw_update_result_t result;\n\n    /**\n     * Value to initialize the Severity resource with.\n     */\n    anjay_advanced_fw_update_severity_t persisted_severity;\n\n    /**\n     * Value to initialize the Last State Change Time resource with.\n     */\n    avs_time_real_t persisted_last_state_change_time;\n\n    /**\n     * Update deadline based on Maximum Defer Period resource value and time of\n     * executing Update resource.\n     */\n    avs_time_real_t persisted_update_deadline;\n} anjay_advanced_fw_update_initial_state_t;\n\n/**\n * Opens the stream that will be used to write the firmware package to.\n *\n * The intended way of implementing this handler is to open a temporary file\n * using <c>fopen()</c> or allocate some memory buffer that may then be used to\n * store the downloaded data in. The library will not attempt to call\n * @ref anjay_advanced_fw_update_stream_write_t without having previously called\n * @ref anjay_advanced_fw_update_stream_open_t . Please see\n * @ref anjay_advanced_fw_update_handlers_t for more information about state\n * transitions.\n *\n * Note that this handler will NOT be called after initializing the object with\n * the <c>ANJAY_ADVANCED_FW_UPDATE_STATE_DOWNLOADING</c> option, so any\n * necessary resources shall be already open before calling\n * @ref anjay_advanced_fw_update_instance_add .\n *\n * @param iid          Instance ID of an Advanced Firmware Object which tries to\n *                     open a stream.\n *\n * @param user_ptr     Opaque pointer to user data, as passed to\n *                     @ref anjay_advanced_fw_update_instance_add\n *\n * @returns The callback shall return 0 if successful or a negative value in\n *          case of error. Error codes are <strong>NOT</strong> handled here, so\n *          attempting to return <c>ANJAY_ADVANCED_FW_UPDATE_ERR_*</c> values\n *          will <strong>NOT</strong> cause any effect different than any other\n *          negative value.\n */\ntypedef int anjay_advanced_fw_update_stream_open_t(anjay_iid_t iid,\n                                                   void *user_ptr);\n/**\n * Writes data to the download stream.\n *\n * May be called multipled times after\n * @ref anjay_advanced_fw_update_stream_open_t, once for each consecutive chunk\n * of downloaded data.\n *\n * @param iid      Instance ID of an Advanced Firmware Object which tries to\n *                 write to a stream.\n *\n * @param user_ptr Opaque pointer to user data, as passed to\n *                 @ref anjay_advanced_fw_update_instance_add\n *\n * @param data     Pointer to a chunk of the firmware package being downloaded.\n *                 Guaranteed to be non-<c>NULL</c>.\n *\n * @param length   Number of bytes in the chunk pointed to by <c>data</c>.\n *                 Guaranteed to be greater than zero.\n *\n * @returns The callback shall return 0 if successful or a negative value in\n *          case of error. If one of the <c>ANJAY_ADVANCED_FW_UPDATE_ERR_*</c>\n *          value is returned, an equivalent value will be set in the Update\n *          Result Resource.\n */\ntypedef int anjay_advanced_fw_update_stream_write_t(anjay_iid_t iid,\n                                                    void *user_ptr,\n                                                    const void *data,\n                                                    size_t length);\n\n/**\n * Closes the download stream and prepares the firmware package to be flashed.\n *\n * Will be called after a series of @ref anjay_advanced_fw_update_stream_write_t\n * calls, after the whole package is downloaded.\n *\n * The intended way of implementing this handler is to e.g. call <c>fclose()</c>\n * and perform integrity check on the downloaded file. It might also be\n * uncompressed or decrypted as necessary, so that it is ready to be flashed.\n * The exact split of responsibility between\n * @ref anjay_advanced_fw_update_stream_finish_t and\n * @ref anjay_advanced_fw_update_perform_upgrade_t is not clearly defined and up\n * to the implementor.\n *\n * Note that regardless of the return value, the stream is considered to be\n * closed. That is, upon successful return, the Advanced Firmware Update object\n * is considered to be in the <em>Downloaded</em> state, and upon returning an\n * error - in the <em>Idle</em> state.\n *\n * @param iid      Instance ID of an Advanced Firmware Object which tries to\n *                 finish a stream.\n *\n * @param user_ptr Opaque pointer to user data, as passed to\n *                 @ref anjay_advanced_fw_update_instance_add\n *\n * @returns The callback shall return 0 if successful or a negative value in\n *          case of error. If one of the <c>ANJAY_ADVANCED_FW_UPDATE_ERR_*</c>\n *          value is returned, an equivalent value will be set in the Update\n *          Result Resource.\n */\ntypedef int anjay_advanced_fw_update_stream_finish_t(anjay_iid_t iid,\n                                                     void *user_ptr);\n\n/**\n * Resets the firmware update state and performs any applicable cleanup of\n * temporary storage if necessary.\n *\n * Will be called at request of the server, or after a failed download. Note\n * that it may be called without previously calling\n * @ref anjay_advanced_fw_update_stream_finish_t, so it shall also close the\n * currently open download stream, if any.\n *\n * @note If reset of particular instance is done while it is in\n *       <c>ANJAY_ADVANCED_FW_UPDATE_STATE_DOWNLOADED</c> state, it is likely\n *       possible that it is listed as linked instance of another instance.\n *       If that is the case, it should be marked as Conflicting instance\n *       in every instance that it is linked with.\n *\n * @param iid      Instance ID of an Advanced Firmware Object which performs\n *                 reset.\n *\n * @param user_ptr Opaque pointer to user data, as passed to\n *                 @ref anjay_advanced_fw_update_instance_add\n */\ntypedef void anjay_advanced_fw_update_reset_t(anjay_iid_t iid, void *user_ptr);\n\n/**\n * Returns the name of downloaded firmware package.\n *\n * The name will be exposed in the data model as the PkgName Resource. If this\n * callback returns <c>NULL</c> or is not implemented at all (with the\n * corresponding field set to <c>NULL</c>), that Resource will not be present in\n * the data model.\n *\n * It only makes sense for this handler to return non-<c>NULL</c> values if\n * there is a valid package already downloaded. The library will not call this\n * handler in any state other than <em>Downloaded</em>.\n *\n * The library will not attempt to deallocate the returned pointer. User code\n * must assure that the pointer will remain valid at least until return from\n * @ref anjay_serve or @ref anjay_sched_run .\n *\n * @param iid      Instance ID of an Advanced Firmware Object which tries to get\n *                 related package name.\n *\n * @param user_ptr Opaque pointer to user data, as passed to\n *                 @ref anjay_advanced_fw_update_instance_add\n *\n * @returns The callback shall return a pointer to a null-terminated string\n *          containing the package name, or <c>NULL</c> if it is not currently\n *          available.\n */\ntypedef const char *anjay_advanced_fw_update_get_pkg_name_t(anjay_iid_t iid,\n                                                            void *user_ptr);\n\n/**\n * Returns the version of downloaded firmware package.\n *\n * The version will be exposed in the data model as the PkgVersion Resource. If\n * this callback returns <c>NULL</c> or is not implemented at all (with the\n * corresponding field set to <c>NULL</c>), that Resource will not be present in\n * the data model.\n *\n * It only makes sense for this handler to return non-<c>NULL</c> values if\n * there is a valid package already downloaded. The library will not call this\n * handler in any state other than <em>Downloaded</em>.\n *\n * The library will not attempt to deallocate the returned pointer. User code\n * must assure that the pointer will remain valid at least until return from\n * @ref anjay_serve or @ref anjay_sched_run .\n *\n * @param iid      Instance ID of an Advanced Firmware Object which tries to get\n *                 related package version.\n *\n * @param user_ptr Opaque pointer to user data, as passed to\n *                 @ref anjay_advanced_fw_update_instance_add\n *\n * @returns The callback shall return a pointer to a null-terminated string\n *          containing the package version, or <c>NULL</c> if it is not\n *          currently available.\n */\ntypedef const char *anjay_advanced_fw_update_get_pkg_version_t(anjay_iid_t iid,\n                                                               void *user_ptr);\n\n/**\n * Returns the current version of firmware represented by Advanced Firmware\n * Update object instance.\n *\n * The version will be exposed in the data model as the Current Version\n * Resource. If this callback returns <c>NULL</c> or is not implemented at all\n * (with the corresponding field set to <c>NULL</c>), that Resource will not be\n * present in the data model.\n *\n * @param iid      Instance ID of an Advanced Firmware Object which tries to get\n *                 related current version.\n *\n * @param user_ptr Opaque pointer to user data, as passed to\n *                 @ref anjay_advanced_fw_update_instance_add\n *\n * @returns The callback shall return a pointer to a null-terminated string\n *          containing the package version, or <c>NULL</c> if it is not\n *          currently available.\n */\ntypedef const char *\nanjay_advanced_fw_update_get_current_version_t(anjay_iid_t iid, void *user_ptr);\n\n/**\n * Performs the actual upgrade with previously downloaded package.\n *\n * Will be called at request of the server, after a package has been downloaded.\n *\n * Most users will want to implement firmware update in a way that involves a\n * reboot. In such case, it is expected that this callback will do either one of\n * the following:\n *\n * - perform firmware upgrade, terminate outermost event loop and return,\n *   call reboot after @ref anjay_event_loop_run\n * - perform the firmware upgrade internally and then reboot, it means that\n *   the return will never happen (although the library won't be able to send\n *   the acknowledgement to execution of Update resource)\n *\n * After rebooting, the result of the upgrade process may be passed to the\n * library during initialization via the <c>initial_result</c> argument to\n * @ref anjay_advanced_fw_update_instance_add .\n *\n * Alternatively, if the update can be performed without reinitializing Anjay,\n * you can use @ref anjay_advanced_fw_update_set_state_and_result (either from\n * within the handler or some time after returning from it) to pass the update\n * result.\n *\n * @param iid                               Instance ID of an Advanced Firmware\n *                                          Object which tries to perform\n *                                          upgrade.\n *\n * @param user_ptr                          Opaque pointer to user data, as\n *                                          passed to\n *                                    @ref anjay_advanced_fw_update_instance_add\n *\n * @param requested_supplemental_iids       Pointer to list of Advanced Firmware\n *                                          Object instances that server request\n *                                          to upgrade along with instance that\n *                                          this callback belongs to.\n *\n * @param requested_supplemental_iids_count Count of requested supplemental iids\n *\n * @returns The callback shall return a negative value if it can be determined\n *          without a reboot, that the firmware upgrade cannot be successfully\n *          performed.\n *\n *          If one of the <c>ANJAY_ADVANCED_FW_UPDATE_ERR_*</c> values is\n *          returned, an equivalent value will be set in the Update Result\n *          Resource. Otherwise, if a non-zero value is returned, the Update\n *          Result Resource is set to generic \"Firmware update failed\" code.\n *\n */\ntypedef int anjay_advanced_fw_update_perform_upgrade_t(\n        anjay_iid_t iid,\n        void *user_ptr,\n        const anjay_iid_t *requested_supplemental_iids,\n        size_t requested_supplemental_iids_count);\n\n/**\n * Queries security information that shall be used for an encrypted connection\n * with a PULL-mode download server.\n *\n * May be called before @ref anjay_advanced_fw_update_stream_open_t if the\n * download is to be performed in PULL mode and the connection needs to use TLS\n * or DTLS encryption.\n *\n * Note that the @ref anjay_security_config_t contains references to file paths,\n * binary security keys, and/or ciphersuite lists. It is the user's\n * responsibility to appropriately allocate them and ensure proper lifetime of\n * the returned pointers. The returned security information may only be\n * invalidated in a call to @ref anjay_advanced_fw_update_reset_t or after a\n * call to @ref anjay_delete .\n *\n * If this handler is not implemented at all (with the corresponding field set\n * to <c>NULL</c>), @ref anjay_security_config_from_dm will be used as a default\n * way to get security information.\n *\n * <strong>WARNING:</strong> If the aforementioned @ref\n * anjay_security_config_from_dm function won't find any server\n * connection that matches the <c>download_uri</c> by protocol,\n * hostname and port triple, it'll attempt to match a configuration just by the\n * hostname. This may cause Anjay to use wrong security configuration, e.g. in\n * case when both CoAPS LwM2M server and HTTPS firmware package server have the\n * same hostname, but require different security configs.\n *\n * If no user-defined handler is provided and the call to\n * @ref anjay_security_config_from_dm fails (including case when no matching\n * LwM2M Security Object instance is found, even just by the hostname),\n * @ref anjay_security_config_pkix will be used as an additional fallback\n * if <c>ANJAY_WITH_LWM2M11</c> is enabled and a valid trust store is available\n * (either specified through <c>use_system_trust_store</c>,\n * <c>trust_store_certs</c> or <c>trust_store_crls</c> fields in\n * <c>anjay_configuration_t</c>, or obtained via <c>/est/crts</c> request if\n * <c>est_cacerts_policy</c> is set to\n * <c>ANJAY_EST_CACERTS_IF_EST_CONFIGURED</c> or\n * <c>ANJAY_EST_CACERTS_ALWAYS</c>).\n *\n * You may also use those aforementioned functions\n * (@ref anjay_security_config_from_dm, @ref anjay_security_config_pkix) in\n * your callback, for example as a fallback mechanism.\n *\n * @param iid                 Instance ID of an Advanced Firmware Object which\n *                            tries to get security config.\n *\n * @param user_ptr            Opaque pointer to user data, as passed to\n *                            @ref anjay_advanced_fw_update_instance_add\n *\n * @param out_security_info   Pointer in which the handler shall fill in\n *                            security configuration to use for download. Note\n *                            that leaving this value as empty without filling\n *                            it in will result in a configuration that is\n *                            <strong>valid, but very insecure</strong>: it will\n *                            cause any server certificate to be accepted\n *                            without validation. Any pointers used within the\n *                            supplied structure shall remain valid until either\n *                            a call to @ref anjay_advanced_fw_update_reset_t,\n *                            or exit to the event loop (from either\n *                            @ref anjay_serve, @ref anjay_sched_run or\n *                            @ref anjay_advanced_fw_update_instance_add),\n *                            whichever happens first. Anjay will\n *                            <strong>not</strong> attempt to deallocate\n *                            anything automatically.\n *\n * @param download_uri        Target firmware URI.\n *\n * @returns The callback shall return 0 if successful or a negative value in\n *          case of error. If one of the <c>ANJAY_ADVANCED_FW_UPDATE_ERR_*</c>\n *          value is returned, an equivalent value will be set in the Update\n *          Result Resource.\n */\ntypedef int anjay_advanced_fw_update_get_security_config_t(\n        anjay_iid_t iid,\n        void *user_ptr,\n        anjay_security_config_t *out_security_info,\n        const char *download_uri);\n\n/**\n * Returns tx_params used to override default ones.\n *\n * If this handler is not implemented at all (with the corresponding field set\n * to <c>NULL</c>), <c>udp_tx_params</c> from <c>anjay_t</c> object are used.\n *\n * <strong>NOTE:</strong> This callback is called even for non-CoAP downloads,\n * but the returned transmission parameters are ignored in that case.\n *\n * @param iid           Instance ID of an Advanced Firmware Object which query\n *                      tx_params.\n *\n * @param user_ptr      Opaque pointer to user data, as passed to\n *                      @ref anjay_advanced_fw_update_instance_add .\n *\n * @param download_uri  Target firmware URI.\n *\n * @returns Object with CoAP transmission parameters.\n */\ntypedef avs_coap_udp_tx_params_t anjay_advanced_fw_update_get_coap_tx_params_t(\n        anjay_iid_t iid, void *user_ptr, const char *download_uri);\n\n/**\n * Returns request timeout to be used during firmware update over CoAP+TCP or\n * HTTP.\n *\n * If this handler is not implemented at all (with the corresponding field set\n * to <c>NULL</c>), <c>coap_tcp_request_timeout</c> from <c>anjay_t</c> object\n * will be used for CoAP+TCP, and <c>AVS_NET_SOCKET_DEFAULT_RECV_TIMEOUT</c>\n * (i.e., 30 seconds) will be used for HTTP.\n *\n * <strong>NOTE:</strong> This callback is called even for non-TCP downloads,\n * but the returned transmission parameters are ignored in that case.\n *\n * @param iid           Instance ID of an Advanced Firmware Object which query\n *                      tx_params.\n *\n * @param user_ptr      Opaque pointer to user data, as passed to\n *                      @ref anjay_advanced_fw_update_instance_add .\n *\n * @param download_uri  Target firmware URI.\n *\n * @returns The desired request timeout. If the value returned is non-positive\n *          (including zero and invalid value), the default will be used.\n */\ntypedef avs_time_duration_t anjay_advanced_fw_update_get_tcp_request_timeout_t(\n        anjay_iid_t iid, void *user_ptr, const char *download_uri);\n\n/**\n * Handler callbacks that shall implement the platform-specific part of firmware\n * update process.\n *\n * The Firmware Update object logic may be in one of the following states:\n *\n * - <strong>Idle</strong>. This is the state in which the object is just after\n *   creation (unless initialized with either\n *   <c>ANJAY_ADVANCED_FW_UPDATE_STATE_DOWNLOADED</c> or\n *   <c>ANJAY_ADVANCED_FW_UPDATE_STATE_DOWNLOADING</c>). The following handlers\n * may be called in this state:\n *   - <c>stream_open</c> - shall open the download stream; moves the object\n *     into the <em>Downloading</em> state\n *   - <c>get_security_config</c> - shall fill in security info that shall be\n *     used for a given URL\n *   - <c>reset</c> - shall free data allocated by <c>get_security_config</c>,\n *     if it was called and there is any\n * - <strong>Downloading</strong>. The object might be initialized directly into\n *   this state by using <c>ANJAY_ADVANCED_FW_UPDATE_STATE_DOWNLOADING</c>. In\n * this state, the download stream is open and data may be transferred. The\n *   following handlers may be called in this state:\n *   - <c>stream_write</c> - shall write a chunk of data into the download\n *     stream; it normally does not change state - however, if it fails, it will\n *     be immediately followed by a call to <c>reset</c>\n *   - <c>stream_finish</c> - shall close the download stream and perform\n *     integrity check on the downloaded image; if successful, this moves the\n *     object into the <em>Downloaded</em> state. If failed - into the\n *     <em>Idle</em> state; note that <c>reset</c> will NOT be called in that\n *     case\n *   - <c>reset</c> - shall remove all downloaded data; moves the object into\n *     the <em>Idle</em> state\n * - <strong>Downloaded</strong>. The object might be initialized directly into\n *   this state by using <c>ANJAY_ADVANCED_FW_UPDATE_STATE_DOWNLOADED</c>. In\n * this state, the firmware package has been downloaded and checked and is ready\n * to be flashed. The following handlers may be called in this state:\n *   - <c>reset</c> - shall reset all downloaded data; moves the object into the\n *     <em>Idle</em> state\n *   - <c>get_name</c> - shall return the package name, if available\n *   - <c>get_version</c> - shall return the package version, if available\n *   - <c>perform_upgrade</c> - shall perform the actual upgrade; if it fails,\n *     it does not cause a state change and may be called again; upon success,\n *     it may be treated as a transition to a \"terminal\" state, after which the\n *     device is expected to reboot\n */\ntypedef struct {\n    /** Opens the stream that will be used to write the firmware package to;\n     * @ref anjay_advanced_fw_update_stream_open_t */\n    anjay_advanced_fw_update_stream_open_t *stream_open;\n    /** Writes data to the download stream;\n     * @ref anjay_advanced_fw_update_stream_write_t */\n    anjay_advanced_fw_update_stream_write_t *stream_write;\n    /** Closes the download stream and prepares the firmware package to be\n     * flashed; @ref anjay_advanced_fw_update_stream_finish_t */\n    anjay_advanced_fw_update_stream_finish_t *stream_finish;\n\n    /** Resets the firmware update state and performs any applicable cleanup of\n     * temporary storage if necessary; @ref anjay_advanced_fw_update_reset_t */\n    anjay_advanced_fw_update_reset_t *reset;\n\n    /** Returns the name of downloaded firmware package;\n     * @ref anjay_advanced_fw_update_get_pkg_name_t */\n    anjay_advanced_fw_update_get_pkg_name_t *get_pkg_name;\n    /** Return the version of downloaded firmware package;\n     * @ref anjay_advanced_fw_update_get_pkg_version_t */\n    anjay_advanced_fw_update_get_pkg_version_t *get_pkg_version;\n    /** Return the version of current firmware package;\n     * @ref anjay_advanced_fw_update_get_current_version_t */\n    anjay_advanced_fw_update_get_current_version_t *get_current_version;\n\n    /** Performs the actual upgrade with previously downloaded package;\n     * @ref anjay_advanced_fw_update_perform_upgrade_t */\n    anjay_advanced_fw_update_perform_upgrade_t *perform_upgrade;\n\n    /** Queries security configuration that shall be used for an encrypted\n     * connection; @ref anjay_advanced_fw_update_get_security_config_t */\n    anjay_advanced_fw_update_get_security_config_t *get_security_config;\n\n    /** Queries CoAP transmission parameters to be used during firmware\n     * update; @ref anjay_advanced_fw_update_get_coap_tx_params_t */\n    anjay_advanced_fw_update_get_coap_tx_params_t *get_coap_tx_params;\n\n    /** Queries request timeout to be used during firmware update over CoAP+TCP\n     * or HTTP; @ref anjay_advanced_fw_update_get_tcp_request_timeout_t */\n    anjay_advanced_fw_update_get_tcp_request_timeout_t *get_tcp_request_timeout;\n} anjay_advanced_fw_update_handlers_t;\n\n/**\n * Installs the Advanced Firmware Update object in an Anjay object.\n *\n * The Advanced Firmware Update module does not require explicit cleanup; all\n * resources  will be automatically freed up during the call to\n * @ref anjay_delete.\n *\n * @param anjay         Anjay object for which the Advanced Firmware Update\n *                      Object is installed.\n *\n * @param config        Provides configuration of preferred socked downloads and\n *                      lwm2m send usage;\n *                      @ref anjay_advanced_fw_update_global_config_t\n *\n * @returns 0 on success, or a negative value in case of error.\n */\nint anjay_advanced_fw_update_install(\n        anjay_t *anjay, const anjay_advanced_fw_update_global_config_t *config);\n\n/**\n * Adds the Advanced Firmware Update object instance in an Advanced Firmware\n * Update object.\n *\n * The Advanced Firmware Update module does not require explicit cleanup; all\n * resources  will be automatically freed up during the call to\n * @ref anjay_delete.\n *\n * @param anjay          Anjay object for which the Advanced Firmware Update\n *                       Object is installed.\n *\n * @param iid            Instance ID of an Advanced Firmware Object.\n *\n * @param component_name Pointer to null-terminated component name string.\n *                       Note: String is NOT copied, so it needs to remain valid\n *                       for the lifetime of the object instance.\n *\n * @param handlers       Pointer to a set of handler functions that handle the\n *                       platform-specific part of firmware update process.\n *                       Note: Contents of the structure are NOT copied, so it\n *                       needs to remain valid for the lifetime of the object\n *                       instance.\n *\n * @param user_arg      Opaque user pointer that will be passed as the first\n *                      argument to handler functions.\n *\n * @param initial_state Information about the state to initialize the Advanced\n *                      Firmware Update object instance in. It is intended to be\n *                      used after either an orderly reboot caused by a firmware\n *                      update attempt to report the update result, or by an\n *                      unexpected reboot in the middle of the download process.\n *                      If the object shall be initialized in a neutral initial\n *                      state, <c>NULL</c> might be passed.\n *\n * @returns 0 on success, or a negative value in case of error.\n */\nint anjay_advanced_fw_update_instance_add(\n        anjay_t *anjay,\n        anjay_iid_t iid,\n        const char *component_name,\n        const anjay_advanced_fw_update_handlers_t *handlers,\n        void *user_arg,\n        const anjay_advanced_fw_update_initial_state_t *initial_state);\n\n/**\n * Sets the Advanced Firmware Update object instance State to @p state and\n * Result to @p result , interrupting the update process.\n *\n * If the function fails, neither Update State nor Update Result are changed.\n *\n * Some state transitions are disallowed and cause this function to fail:\n *\n * - @ref ANJAY_ADVANCED_FW_UPDATE_RESULT_INITIAL and\n *   @ref ANJAY_ADVANCED_FW_UPDATE_RESULT_UPDATE_CANCELLED are never allowed and\n *   cause this function to fail.\n *\n * - @ref ANJAY_ADVANCED_FW_UPDATE_RESULT_SUCCESS is only allowed if the\n *   firmware application process was started by the server (an Execute\n *   operation was already performed on the Update resource of the Firmware\n *   Update object or @ref ANJAY_ADVANCED_FW_UPDATE_STATE_UPDATING was used in a\n *   call to @ref anjay_advanced_fw_update_instance_add). Otherwise, the\n *   function fails.\n *\n * - Other values of @p result (various error codes) are only allowed if\n *   Advanced Firmware Update State is not Idle (0), i.e. firmware is being\n *   downloaded, was already downloaded or is being applied.\n *\n * WARNING: calling this in @ref anjay_advanced_fw_update_perform_upgrade_t\n * handler is supported, but the result of using it from within any other of\n * @ref anjay_advanced_fw_update_handlers_t handlers is undefined.\n *\n * @param anjay  Anjay object to operate on.\n *\n * @param iid    Instance ID of an Advanced Firmware Object.\n *\n * @param state  Value of the State resource to set.\n *\n * @param result Value of the Update Result resource to set.\n *\n * @returns 0 on success, or a negative value in case of error.\n */\nint anjay_advanced_fw_update_set_state_and_result(\n        anjay_t *anjay,\n        anjay_iid_t iid,\n        anjay_advanced_fw_update_state_t state,\n        anjay_advanced_fw_update_result_t result);\n\n/**\n * Gets the Advanced Firmware Update object instance State.\n *\n * @param anjay     Anjay object to operate on.\n *\n * @param iid       Instance ID of an Advanced Firmware Object.\n *\n * @param out_state Pointer to where write output state.\n *\n * @returns 0 on success, or a negative value in case of error.\n */\nint anjay_advanced_fw_update_get_state(\n        anjay_t *anjay,\n        anjay_iid_t iid,\n        anjay_advanced_fw_update_state_t *out_state);\n\n/**\n * Gets the Advanced Firmware Update object instance Result.\n *\n * @param anjay     Anjay object to operate on.\n *\n * @param iid       Instance ID of an Advanced Firmware Object.\n *\n * @param out_result Pointer to where write output result.\n *\n * @returns 0 on success, or a negative value in case of error.\n */\nint anjay_advanced_fw_update_get_result(\n        anjay_t *anjay,\n        anjay_iid_t iid,\n        anjay_advanced_fw_update_result_t *out_result);\n\n/**\n * Sets linked instances resource of Advance Firmware Update object instance.\n *\n * Linked instances mark instances that will be updated in a batch together when\n * performing upgrade of a @p iid instance. See AVSystem specification of\n * Advanced Firmware Update for details.\n *\n * @param anjay             Anjay object to operate on.\n *\n * @param iid               Instance ID of an Advanced Firmware Object.\n *\n * @param target_iids       Points to array iids of linked instances in relation\n *                          to Advanced Firmware Update object instance @p iid.\n *                          NOTE: Only already added instances only of Advanced\n *                          Firmware Update object are allowed.\n *\n * @param target_iids_count Count of target iids in an array.\n *\n * @returns 0 on success, or a negative value in case of error.\n */\nint anjay_advanced_fw_update_set_linked_instances(\n        anjay_t *anjay,\n        anjay_iid_t iid,\n        const anjay_iid_t *target_iids,\n        size_t target_iids_count);\n\n/**\n * Gets linked instances resource of Advance Firmware Update object instance.\n *\n * Linked instances mark instances that will be updated in a batch together when\n * performing upgrade of a @p iid instance. See AVSystem specification of\n * Advanced Firmware Update for details.\n *\n * <strong>NOTE:</strong> The returned array points directly into the internal\n * structures of Anjay; however, it may only be modified by a call to\n * @ref anjay_advanced_fw_update_set_linked_instances . Nevertheless, if your\n * code calls the \"get\" and \"set\" functions from different threads, the calls\n * need to be additionally synchronized to achieve thread safety.\n *\n * @param anjay                 Anjay object to operate on.\n *\n * @param iid                   Instance ID of an Advanced Firmware Object.\n *\n * @param out_target_iids       Points to memory where to write array of iids of\n *                              linked instances.\n *\n * @param out_target_iids_count Point to memory where to write count of target\n *                              iids in an array.\n *\n * @returns 0 on success, or a negative value in case of error.\n */\nint anjay_advanced_fw_update_get_linked_instances(\n        anjay_t *anjay,\n        anjay_iid_t iid,\n        const anjay_iid_t **out_target_iids,\n        size_t *out_target_iids_count);\n\n/**\n * Sets conflicting instances resource of Advance Firmware Update object\n * instance.\n *\n * When the download or update fails and the Update Result resource is set to\n * @ref ANJAY_ADVANCED_FW_UPDATE_RESULT_CONFLICTING_STATE or\n * @ref ANJAY_ADVANCED_FW_UPDATE_RESULT_DEPENDENCY_ERROR\n * this resource MUST be present and contain references to the Advanced\n * Firmware Update object instances that caused the conflict. See LwM2M\n * specification for details.\n *\n * @param anjay             Anjay object to operate on.\n *\n * @param iid               Instance ID of an Advanced Firmware Object.\n *\n * @param target_iids       Points to array iids of conflicting instances in\n *                          relation to Advanced Firmware Update object instance\n *                          @p iid.\n *                          NOTE: Only already added instances only of Advanced\n *                          Firmware Update object are allowed.\n *\n * @param target_iids_count Count of target iids in an array.\n *\n * @returns 0 on success, or a negative value in case of error.\n */\nint anjay_advanced_fw_update_set_conflicting_instances(\n        anjay_t *anjay,\n        anjay_iid_t iid,\n        const anjay_iid_t *target_iids,\n        size_t target_iids_count);\n\n/**\n * Gets conflicting instances resource of Advance Firmware Update object\n * instance.\n *\n * When the download or update fails and the Update Result resource is set to\n * @ref ANJAY_ADVANCED_FW_UPDATE_RESULT_CONFLICTING_STATE or\n * @ref ANJAY_ADVANCED_FW_UPDATE_RESULT_DEPENDENCY_ERROR\n * this resource MUST be present and contain references to the Advanced\n * Firmware Update object instances that caused the conflict. See LwM2M\n * specification for details.\n *\n * <strong>NOTE:</strong> The returned array points directly into the internal\n * structures of Anjay; however, it may only be modified by a call to\n * @ref anjay_advanced_fw_update_set_conflicting_instances . Nevertheless, if\n * your code calls the \"get\" and \"set\" functions from different threads, the\n * calls need to be additionally synchronized to achieve thread safety.\n *\n * @param anjay                 Anjay object to operate on.\n *\n * @param iid                   Instance ID of an Advanced Firmware Object.\n *\n * @param out_target_iids       Points to memory where to write array of iids of\n *                              conflicting instances.\n *\n * @param out_target_iids_count Point to memory where to write count of target\n *                              iids in an array.\n *\n * @returns 0 on success, or a negative value in case of error.\n */\nint anjay_advanced_fw_update_get_conflicting_instances(\n        anjay_t *anjay,\n        anjay_iid_t iid,\n        const anjay_iid_t **out_target_iids,\n        size_t *out_target_iids_count);\n\n/**\n * Gets the update deadline based on Maximum Defer Period resource value and\n * time of downloading full firmware.\n *\n * @param anjay Anjay object to operate on.\n *\n * @param iid   Instance ID of an Advanced Firmware Object.\n *\n * @returns Real time of the update deadline. In case of not deferring\n * update returns @c AVS_TIME_REAL_INVALID.\n */\navs_time_real_t anjay_advanced_fw_update_get_deadline(anjay_t *anjay,\n                                                      anjay_iid_t iid);\n\n/**\n * Gets the update severity.\n *\n * @param anjay Anjay object to operate on.\n *\n * @param iid   Instance ID of an Advanced Firmware Object.\n *\n * @returns Severity resource value present in Firmware Update object on\n * success, or @ref ANJAY_ADVANCED_FW_UPDATE_SEVERITY_MANDATORY on error.\n */\nanjay_advanced_fw_update_severity_t\nanjay_advanced_fw_update_get_severity(anjay_t *anjay, anjay_iid_t iid);\n\n/**\n * Gets the value of Last State Change Time resource.\n *\n * @param anjay Anjay object to operate on.\n *\n * @param iid   Instance ID of an Advanced Firmware Object.\n *\n * @returns Real time of last State resource change, or @ref\n * AVS_TIME_REAL_INVALID on error.\n */\navs_time_real_t\nanjay_advanced_fw_update_get_last_state_change_time(anjay_t *anjay,\n                                                    anjay_iid_t iid);\n\n#ifdef ANJAY_WITH_DOWNLOADER\n/**\n * Suspends the operation of PULL-mode downloads in the Advanced Firmware Update\n * module.\n *\n * This will have the effect of suspending any ongoing downloads (see\n * @ref anjay_download_suspend for details), as well as preventing new downloads\n * from being started.\n *\n * When PULL-mode downloads are suspended,\n * @ref anjay_advanced_fw_update_stream_open_t will <strong>NOT</strong> be\n * called when a download request is issued. However,\n * @ref anjay_advanced_fw_update_get_security_config_t,\n * @ref anjay_advanced_fw_update_get_coap_tx_params_t and\n * @ref anjay_advanced_fw_update_get_tcp_request_timeout_t will be called. You\n * may call @ref anjay_advanced_fw_update_pull_reconnect from one of these\n * functions if you decide to accept the download immediately after all.\n *\n * @param anjay Anjay object to operate on.\n */\nvoid anjay_advanced_fw_update_pull_suspend(anjay_t *anjay);\n\n/**\n * Reconnects any ongoing PULL-mode downloads in the Advanced Firmware Update\n * module. Additionally, if PULL-mode downloads are suspended (see\n * @ref anjay_advanced_fw_update_pull_suspend), resumes normal operation.\n *\n * If an ongoing PULL-mode download exists, this will call\n * @ref anjay_download_reconnect internally, so you may want to reference the\n * documentation of that function for details.\n *\n * @param anjay Anjay object to operate on.\n *\n * @returns 0 for success; -1 if @p anjay does not have the Firmware Update\n *          object installed or if the call to @ref anjay_download_reconnect\n *          fails.\n */\nint anjay_advanced_fw_update_pull_reconnect(anjay_t *anjay);\n#endif // ANJAY_WITH_DOWNLOADER\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif /* ANJAY_INCLUDE_ANJAY_ADVANCED_FW_UPDATE_H */\n"
  },
  {
    "path": "include_public/anjay/anjay.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ANJAY_INCLUDE_ANJAY_ANJAY_H\n#define ANJAY_INCLUDE_ANJAY_ANJAY_H\n\n#include <anjay/core.h>\n#include <anjay/dm.h>\n#include <anjay/download.h>\n#include <anjay/io.h>\n\n#endif /*ANJAY_INCLUDE_ANJAY_ANJAY_H*/\n"
  },
  {
    "path": "include_public/anjay/anjay_config.h.in",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ANJAY_CONFIG_H\n#define ANJAY_CONFIG_H\n\n/**\n * @file anjay_config.h\n *\n * Anjay library configuration.\n *\n * The preferred way to compile Anjay is to use CMake, in which case this file\n * will be generated automatically by CMake.\n *\n * However, to provide compatibility with various build systems used especially\n * by embedded platforms, it is alternatively supported to compile Anjay by\n * other means, in which case this file will need to be provided manually.\n *\n * <strong>NOTE: To compile this library without using CMake, you need to\n * configure avs_commons and avs_coap first. Please refer to documentation in\n * the <c>avs_commons_config.h</c> and <c>avs_coap_config.h</c> files (provided\n * in the repositories as <c>avs_commons_config.h.in</c> and\n * <c>avs_coap_config.h.in</c>, respectively) for details.</strong>\n *\n * <strong>Anjay requires the following avs_coap options to be enabled:</strong>\n *\n * - @c WITH_AVS_COAP_UDP and/or @c WITH_AVS_COAP_TCP\n * - @c WITH_AVS_COAP_STREAMING_API\n * - @c WITH_AVS_COAP_BLOCK is highly recommended\n * - @c WITH_AVS_COAP_OBSERVE (if @c ANJAY_WITH_OBSERVE is enabled)\n * - @c WITH_AVS_COAP_OSCORE (if @c ANJAY_WITH_COAP_OSCORE is enabled, available\n *   only as a commercial feature)\n *\n * <strong>Anjay requires the following avs_commons components to be\n * enabled:</strong>\n *\n * - All components required by avs_coap, see <c>avs_coap_config.h</c>\n * - @c avs_algorithm\n * - @c avs_stream\n * - @c avs_url\n * - @c avs_persistence is highly recommended\n * - @c avs_http (if @c ANJAY_WITH_HTTP_DOWNLOAD is enabled)\n * - @c avs_rbtree or @c avs_sorted_set (if @c ANJAY_WITH_OBSERVE or\n *   @c ANJAY_WITH_MODULE_ACCESS_CONTROL is enabled)\n *\n * In the repository, this file is provided as <c>anjay_config.h.in</c>,\n * intended to be processed by CMake. If editing this file manually, please copy\n * or rename it to <c>anjay_config.h</c> and for each of the\n * <c>\\#cmakedefine</c> directives, please either replace it with regular\n * <c>\\#define</c> to enable it, or comment it out to disable. You may also need\n * to replace variables wrapped in <c>\\@</c> signs with concrete values. Please\n * refer to the comments above each of the specific definition for details.\n *\n * If you are editing a file previously generated by CMake, these\n * <c>\\#cmakedefine</c>s will be already replaced by either <c>\\#define</c> or\n * commented out <c>\\#undef</c> directives.\n */\n\n/**\n * Enable logging in Anjay.\n *\n * If this flag is disabled, no logging is compiled into the binary at all.\n */\n#cmakedefine ANJAY_WITH_LOGS\n\n/**\n * Enable TRACE-level logs in Anjay.\n *\n * Only meaningful if <c>ANJAY_WITH_LOGS</c> is enabled.\n */\n#cmakedefine ANJAY_WITH_TRACE_LOGS\n\n/**\n * Enable core support for Access Control mechanisms.\n *\n * Requires separate implementation of the Access Control object itself.\n * Either the implementation available as part of\n * <c>ANJAY_WITH_MODULE_ACCESS_CONTROL</c>, or a custom application-provided one\n * may be used.\n */\n#cmakedefine ANJAY_WITH_ACCESS_CONTROL\n\n/**\n * Enable automatic attribute storage.\n *\n * Requires <c>AVS_COMMONS_WITH_AVS_PERSISTENCE</c> to be enabled in avs_commons\n * configuration.\n */\n#cmakedefine ANJAY_WITH_ATTR_STORAGE\n\n/**\n * Enable support for the <c>anjay_download()</c> API.\n */\n#cmakedefine ANJAY_WITH_DOWNLOADER\n\n/**\n * Enable support for CoAP(S) downloads.\n *\n * Only meaningful if <c>ANJAY_WITH_DOWNLOADER</c> is enabled.\n */\n#cmakedefine ANJAY_WITH_COAP_DOWNLOAD\n\n/**\n * Enable support for HTTP(S) downloads.\n *\n * Only meaningful if <c>ANJAY_WITH_DOWNLOADER</c> is enabled.\n */\n#cmakedefine ANJAY_WITH_HTTP_DOWNLOAD\n\n/**\n * Enable support for the LwM2M Bootstrap Interface.\n */\n#cmakedefine ANJAY_WITH_BOOTSTRAP\n\n/**\n * Enable support for the LwM2M Bootstrap-Pack operation.\n *\n * Requires <c>ANJAY_WITH_BOOTSTRAP</c> and <c>ANJAY_WITH_LWM2M12</c> to be\n * enabled.\n */\n#cmakedefine ANJAY_WITH_BOOTSTRAP_PACK\n\n/**\n * Enable support for the LwM2M Discover operation.\n *\n * Note that it is required for full compliance with the LwM2M protocol.\n */\n#cmakedefine ANJAY_WITH_DISCOVER\n\n/**\n * Enable support for the LwM2M Information Reporting interface (Observe and\n * Notify operations).\n *\n * Requires <c>WITH_AVS_COAP_OBSERVE</c> to be enabled in avs_coap\n * configuration.\n *\n * Note that it is required for full compliance with the LwM2M protocol.\n */\n#cmakedefine ANJAY_WITH_OBSERVE\n\n/**\n * Enable support for measuring amount of LwM2M traffic\n * (<c>anjay_get_tx_bytes()</c>, <c>anjay_get_rx_bytes()</c>,\n * <c>anjay_get_num_incoming_retransmissions()</c> and\n * <c>anjay_get_num_outgoing_retransmissions()</c> APIs.\n */\n#cmakedefine ANJAY_WITH_NET_STATS\n\n/**\n * Enable support for communication timestamp\n * (<c>anjay_get_server_last_registration_time()</c>\n * <c>anjay_get_server_next_update_time()</c> and\n * <c>anjay_get_server_last_communication_time()</c>) APIs.\n */\n#cmakedefine ANJAY_WITH_COMMUNICATION_TIMESTAMP_API\n\n/**\n * Enable support for the <c>anjay_resource_observation_status()</c> API.\n */\n#cmakedefine ANJAY_WITH_OBSERVATION_STATUS\n\n/**\n * Maximum number of servers observing a given Resource listed by\n * <c>anjay_resource_observation_status()</c> function.\n *\n * Only meaningful if <c>ANJAY_WITH_OBSERVATION_STATUS</c> is enabled.\n */\n#define ANJAY_MAX_OBSERVATION_SERVERS_REPORTED_NUMBER @MAX_OBSERVATION_SERVERS_REPORTED_NUMBER@\n\n/**\n * Enable guarding of all accesses to <c>anjay_t</c> with a mutex.\n */\n#cmakedefine ANJAY_WITH_THREAD_SAFETY\n\n/**\n * Enable standard implementation of an event loop.\n *\n * Requires C11 <c>stdatomic.h</c> header to be available, and either a platform\n * that provides a BSD-compatible socket API, or a compatibility layer file (see\n * <c>AVS_COMMONS_POSIX_COMPAT_HEADER</c> in <c>avs_commons_config.h</c>). This\n * is designed to best work with the defalt implementation of avs_net sockets\n * (see <c>AVS_COMMONS_NET_WITH_POSIX_AVS_SOCKET</c>), but alternative socket\n * implementations can also be used.\n *\n * The event loop is most useful when thread safety features\n * (@ref ANJAY_WITH_THREAD_SAFETY and <c>AVS_COMMONS_SCHED_THREAD_SAFE</c>) are\n * enabled as well, but this is not a hard requirement. See the documentation\n * for <c>anjay_event_loop_run()</c> for details.\n */\n#cmakedefine ANJAY_WITH_EVENT_LOOP\n\n/**\n * Enable support for features new to LwM2M protocol version 1.1.\n */\n#cmakedefine ANJAY_WITH_LWM2M11\n\n/**\n * Enable support for features new to LwM2M protocol version 1.2.\n *\n * Requires <c>ANJAY_WITH_LWM2M11</c> to be enabled.\n */\n#cmakedefine ANJAY_WITH_LWM2M12\n\n/**\n * Enable support for OSCORE-based security for LwM2M connections.\n *\n * Requires <c>ANJAY_WITH_LWM2M11</c> to be enabled, and\n * <c>WITH_AVS_COAP_OSCORE</c> to be enabled in avs_coap configuration.\n *\n * IMPORTANT: Only available as part of the OSCORE commercial feature. Ignored\n * in the open source version.\n */\n#cmakedefine ANJAY_WITH_COAP_OSCORE\n\n/**\n * Enable support for Observation Attributes.\n *\n * Requires <c>ANJAY_WITH_OBSERVE</c> and <c>ANJAY_WITH_LWM2M12</c> to be\n * enabled.\n */\n#cmakedefine ANJAY_WITH_OBSERVATION_ATTRIBUTES\n\n/**\n * Enable support for the LwM2M Send operation.\n *\n * Requires <c>ANJAY_WITH_LWM2M11</c> and either <c>ANJAY_WITH_SENML_JSON</c> or\n * <c>ANJAY_WITH_CBOR</c> to be enabled.\n */\n#cmakedefine ANJAY_WITH_SEND\n\n/**\n * Enable support for the SMS binding and the SMS trigger mechanism.\n *\n * Requires <c>WITH_AVS_COAP_UDP</c> to be enabled in avs_coap configuration.\n *\n * IMPORTANT: Only available as part of the SMS commercial feature. Ignored in\n * the open source version.\n */\n#cmakedefine ANJAY_WITH_SMS\n\n/**\n * Enable support for sending and receiving multipart SMS messages.\n *\n * Requires <c>ANJAY_WITH_SMS</c> to be enabled.\n *\n * IMPORTANT: Only available as part of the SMS commercial feature. Ignored in\n * the open source version.\n */\n#cmakedefine ANJAY_WITH_SMS_MULTIPART\n\n/**\n * Enable support for Non-IP Data Delivery.\n *\n * Requires <c>ANJAY_WITH_LWM2M11</c> to be enabled, and\n * <c>WITH_AVS_COAP_UDP</c> to be enabled in avs_coap configuration.\n *\n * IMPORTANT: Only available as a commercial feature. Ignored in the open\n * source version.\n */\n#cmakedefine ANJAY_WITH_NIDD\n\n/**\n * Enable support for core state persistence\n * (<c>anjay_new_from_core_persistence()</c> and\n * <c>anjay_delete_with_core_persistence()</c> APIs).\n *\n * Requires <c>ANJAY_WITH_OBSERVE</c> to be enabled,\n * <c>AVS_COMMONS_WITH_AVS_PERSISTENCE</c> to be enabled in avs_commons, and\n * <c>WITH_AVS_COAP_OBSERVE_PERSISTENCE</c> to be enabled in avs_coap\n * configuration.\n *\n * IMPORTANT: Only available as a commercial feature. Ignored in the open\n * source version.\n */\n#cmakedefine ANJAY_WITH_CORE_PERSISTENCE\n\n/**\n * Disable automatic closing of server connection sockets after\n * MAX_TRANSMIT_WAIT of inactivity.\n */\n#cmakedefine ANJAY_WITHOUT_QUEUE_MODE_AUTOCLOSE\n\n/**\n * Enable support for CoAP Content-Format numerical values 1541-1544 that have\n * been used before final LwM2M TS 1.0.\n */\n#cmakedefine ANJAY_WITH_LEGACY_CONTENT_FORMAT_SUPPORT\n\n/**\n * Enable support for JSON format as specified in LwM2M TS 1.0.\n *\n * NOTE: Anjay is only capable of generating this format, there is no parsing\n * support regardless of the state of this option.\n */\n#cmakedefine ANJAY_WITH_LWM2M_JSON\n\n/**\n * Disable support for TLV format as specified in LwM2M TS 1.0.\n *\n * NOTE: LwM2M Client using LwM2M 1.0 MUST support this format. It may be\n * disabled if LwM2M 1.1 is used and SenML JSON or SenML CBOR is enabled.\n */\n#cmakedefine ANJAY_WITHOUT_TLV\n\n/**\n * Disable support for Plain Text format as specified in LwM2M TS 1.0 and 1.1.\n *\n * NOTE: LwM2M Client SHOULD support this format. It may be disabled to reduce\n * library size if LwM2M Server is configured to not use it in requests.\n */\n#cmakedefine ANJAY_WITHOUT_PLAINTEXT\n\n/**\n * Disable use of the Deregister message.\n */\n#cmakedefine ANJAY_WITHOUT_DEREGISTER\n\n/**\n * Disable support for \"IP stickiness\", i.e. preference of the same IP address\n * when reconnecting to a server using a domain name.\n */\n#cmakedefine ANJAY_WITHOUT_IP_STICKINESS\n\n/**\n * Enable support for SenML JSON format, as specified in LwM2M TS 1.1.\n *\n * NOTE: As opposed to <c>ANJAY_WITH_LWM2M_JSON</c>, both generating and parsing\n * is supported.\n *\n * Requires <c>ANJAY_WITH_LWM2M11</c> to be enabled.\n */\n#cmakedefine ANJAY_WITH_SENML_JSON\n\n/**\n * Enable support for CBOR and SenML CBOR formats, as specified in LwM2M TS 1.1.\n *\n * Requires <c>ANJAY_WITH_LWM2M11</c> to be enabled.\n */\n#cmakedefine ANJAY_WITH_CBOR\n\n/**\n * Enable support for Enrollment over Secure Transport.\n *\n * Requires <c>ANJAY_WITH_BOOTSTRAP</c> to be enabled.\n *\n * IMPORTANT: Only available as part of the EST commercial feature. Ignored in\n * the open source version.\n */\n#cmakedefine ANJAY_WITH_EST\n\n/**\n * Enable support for hardware security engine in the EST subsystem.\n *\n * Requires <c>ANJAY_WITH_EST</c> to be enabled in Anjay configuration and\n * <c>AVS_COMMONS_WITH_AVS_CRYPTO_PKI_ENGINE</c> to be enabled in avs_commons\n * configuration.\n *\n * IMPORTANT: Only available in commercial versions that include both the EST\n * and HSM features. Ignored in versions distributed without these features.\n */\n#cmakedefine ANJAY_WITH_EST_ENGINE_SUPPORT\n\n/**\n * Enable support for the Confirmable Notification attribute, as specified in\n * LwM2M TS 1.2.\n *\n * Before TS 1.2, this has been supported in Anjay as a custom extension, and\n * thus it is available independently from TS 1.2 support itself, including in\n * the open source version.\n *\n * Requires <c>ANJAY_WITH_OBSERVE</c> to be enabled.\n */\n#cmakedefine ANJAY_WITH_CON_ATTR\n\n/**\n * Enable support for handling security credentials in the data model using\n * structured <c>avs_crypto</c> types.\n *\n * If the <c>security</c> module is also enabled (see @ref\n * ANJAY_WITH_MODULE_SECURITY), it also enables support for passing these\n * credentials through such structured types when adding Security object\n * instances via the @ref anjay_security_instance_t structure.\n */\n#cmakedefine ANJAY_WITH_SECURITY_STRUCTURED\n\n/**\n * Maximum size in bytes supported for the \"Public Key or Identity\" resource in\n * the LwM2M Security object.\n *\n * If editing this file manually, <c>@MAX_PK_OR_IDENTITY_SIZE@</c> shall be\n * replaced with a positive integer literal.\n *\n * The default value defined in CMake build scripts is 2048.\n * Minimal suggested setting for low-resource builds is 256.\n */\n#define ANJAY_MAX_PK_OR_IDENTITY_SIZE @MAX_PK_OR_IDENTITY_SIZE@\n\n/**\n * Maximum size in bytes supported for the \"Secret Key\" resource in the LwM2M\n * Security Object.\n *\n * If editing this file manually, <c>@MAX_SECRET_KEY_SIZE@</c> shall be replaced\n * with a positive integer literal.\n *\n * The default value defined in CMake build scripts is 256.\n * Minimal suggested setting for low-resource builds is 128.\n */\n#define ANJAY_MAX_SECRET_KEY_SIZE @MAX_SECRET_KEY_SIZE@\n\n/**\n * Maximum length supported for stringified floating-point values.\n *\n * Used when parsing plaintext and SenML JSON content formats - when parsing a\n * floating-point value, any string of length equal or greater than this setting\n * will automatically be considered invalid, even if it could in theory be\n * parsed as a valid number.\n *\n * If editing this file manually, <c>@MAX_DOUBLE_STRING_SIZE@</c> shall be\n * replaced with a positive integer literal.\n *\n * The default value defined in CMake build scripts is 512.\n * Minimal suggested setting for low-resource builds is 64.\n */\n#define ANJAY_MAX_DOUBLE_STRING_SIZE @MAX_DOUBLE_STRING_SIZE@\n\n/**\n * Maximum length supported for a single Uri-Path or Location-Path segment.\n *\n * When handling incoming CoAP messages, any Uri-Path or Location-Path option of\n * length equal or greater than this setting will be considered invalid.\n *\n * If editing this file manually, <c>@MAX_URI_SEGMENT_SIZE@</c> shall be\n * replaced with a positive integer literal.\n *\n * The default value defined in CMake build scripts is 256.\n * Minimal suggested setting for low-resource builds is 64.\n */\n#define ANJAY_MAX_URI_SEGMENT_SIZE @MAX_URI_SEGMENT_SIZE@\n\n/**\n * Maximum length supported for a single Uri-Query segment.\n *\n * When handling incoming CoAP messages, any Uri-Query option of length equal or\n * greater than this setting will be considered invalid.\n *\n * If editing this file manually, <c>@MAX_URI_QUERY_SEGMENT_SIZE@</c> shall be\n * replaced with a positive integer literal.\n *\n * The default value defined in CMake build scripts is 256.\n * Minimal suggested setting for low-resource builds is 64.\n */\n#define ANJAY_MAX_URI_QUERY_SEGMENT_SIZE @MAX_URI_QUERY_SEGMENT_SIZE@\n\n/**\n * Size of buffer allocated for storing DTLS session state when connection is\n * not in use (e.g. during queue mode operation).\n *\n * If editing this file manually, <c>@DTLS_SESSION_BUFFER_SIZE@</c> shall be\n * replaced with a positive integer literal. The default value defined in CMake\n * build scripts is 1024.\n */\n#define ANJAY_DTLS_SESSION_BUFFER_SIZE @DTLS_SESSION_BUFFER_SIZE@\n\n/**\n * Value of Content-Format used in Send messages. Only a few specific values are\n * supported:\n *\n * - @c AVS_COAP_FORMAT_NONE means no default value is used and Anjay will\n *   decide the format based on the what is available.\n * - @c AVS_COAP_FORMAT_OMA_LWM2M_CBOR Anjay will generate a Send message in\n *   LwM2M CBOR format.\n * - @c AVS_COAP_FORMAT_SENML_CBOR Anjay will generate a Send message in SenML\n *   CBOR format.\n * - @c AVS_COAP_FORMAT_SENML_JSON Anjay will generate a Send message in SenML\n *   JSON format.\n *\n * Note that to use a specific format it must be available during compilation.\n *\n * The default value defined in CMake build scripts is\n * <c>AVS_COAP_FORMAT_NONE</c>.\n */\n#define ANJAY_DEFAULT_SEND_FORMAT @ANJAY_DEFAULT_SEND_FORMAT@\n\n/**\n * Optional Anjay modules.\n */\n/**@{*/\n/**\n * Enable access_control module (implementation of the Access Control object).\n *\n * Requires <c>ANJAY_WITH_ACCESS_CONTROL</c> to be enabled.\n */\n#cmakedefine ANJAY_WITH_MODULE_ACCESS_CONTROL\n\n/**\n * Enable security module (implementation of the LwM2M Security object).\n */\n#cmakedefine ANJAY_WITH_MODULE_SECURITY\n\n/**\n * Enable support for hardware security engine in the security module.\n *\n * This feature allows security credentials provisioned into the LwM2M Security\n * object to be automatically moved into a hardware security module.\n *\n * Requires <c>ANJAY_WITH_MODULE_SECURITY</c> to be enabled in Anjay\n * configuration, and at least one of\n * <c>AVS_COMMONS_WITH_AVS_CRYPTO_PKI_ENGINE</c> or\n * <c>AVS_COMMONS_WITH_AVS_CRYPTO_PSK_ENGINE</c> to be enabled in avs_commons\n * configuration.\n *\n * IMPORTANT: Only available as part of the HSM support commercial feature.\n * Ignored in versions distributed without this feature.\n */\n#cmakedefine ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT\n\n/**\n * Enable server module (implementation of the LwM2M Server object).\n */\n#cmakedefine ANJAY_WITH_MODULE_SERVER\n\n/**\n * Enable fw_update module (implementation of the Firmware Update object).\n */\n#cmakedefine ANJAY_WITH_MODULE_FW_UPDATE\n\n/**\n * Enable advanced_fw_update module (implementation of the 33629 custom\n * Advanced Firmware Update object).\n */\n#cmakedefine ANJAY_WITH_MODULE_ADVANCED_FW_UPDATE\n\n/**\n * Disable support for PUSH mode Firmware Update.\n *\n * Only meaningful if <c>ANJAY_WITH_MODULE_FW_UPDATE</c> is enabled. Requires\n * <c>ANJAY_WITH_DOWNLOADER</c> to be enabled.\n */\n#cmakedefine ANJAY_WITHOUT_MODULE_FW_UPDATE_PUSH_MODE\n\n/**\n * Enable sw_mgmt module (implementation of the Software Management object).\n */\n#cmakedefine ANJAY_WITH_MODULE_SW_MGMT\n\n/**\n * Enables ipso_objects module (generic implementation of basic sensor, three\n * axis sensor and Push Button IPSO objects).\n */\n#cmakedefine ANJAY_WITH_MODULE_IPSO_OBJECTS\n\n/**\n * Enables experimental ipso_objects_v2 module (generic implementation of basic\n * sensor and three axis sensor IPSO objects).\n */\n#cmakedefine ANJAY_WITH_MODULE_IPSO_OBJECTS_V2\n\n/**\n * Enable at_sms module (implementation of an SMS driver for AT modem devices).\n *\n * Requires <c>ANJAY_WITH_SMS</c> to be enabled and the operating system to\n * support the POSIX <c>poll()</c> function.\n *\n * IMPORTANT: Only available as part of the SMS commercial feature. Ignored in\n * the open source version.\n */\n#cmakedefine ANJAY_WITH_MODULE_AT_SMS\n\n/**\n * Enable bg96_nidd module (implementation of NB-IoT-based NIDD driver for\n * Quectel BG96).\n *\n * Requires <c>ANJAY_WITH_NIDD</c> to be enabled.\n *\n * IMPORTANT: Only available as part of the NIDD commercial feature. Ignored\n * in the open source version.\n */\n#cmakedefine ANJAY_WITH_MODULE_BG96_NIDD\n\n/**\n * Enable bootstrapper module (loader for bootstrap information formatted as per\n * the \"Storage of LwM2M Bootstrap Information on the Smartcard\" appendix to the\n * LwM2M TS).\n *\n * Requires <c>ANJAY_WITH_BOOTSTRAP</c> to be enabled and\n * <c>AVS_COMMONS_WITH_AVS_PERSISTENCE</c> to be enabled in avs_commons\n * configuration.\n *\n * IMPORTANT: Only available with the bootstrapper feature. Ignored in the open\n * source version.\n */\n#cmakedefine ANJAY_WITH_MODULE_BOOTSTRAPPER\n\n/**\n * Enable the SIM bootstrap module, which enables reading the SIM bootstrap\n * information from a smartcard, which can then be passed through to the\n * bootstrapper module.\n *\n * Requires <c>ANJAY_WITH_MODULE_BOOTSTRAPPER</c> to be enabled.\n *\n * IMPORTANT: Only available with the bootstrapper feature. Ignored in the open\n * source version.\n */\n#cmakedefine ANJAY_WITH_MODULE_SIM_BOOTSTRAP\n\n/**\n * Forced ID of the file to read the SIM bootstrap information from.\n *\n * If not defined (default), the bootstrap information file will be discovered\n * through the ODF file, as mandated by the specification.\n *\n * Requires <c>ANJAY_WITH_MODULE_BOOTSTRAPPER</c> to be enabled. At most one of\n * <c>ANJAY_MODULE_SIM_BOOTSTRAP_HARDCODED_FILE_ID</c> and\n * <c>ANJAY_MODULE_SIM_BOOTSTRAP_DATA_OID_OVERRIDE_HEX</c> may be defined at the\n * same time.\n *\n * IMPORTANT: Only available with the bootstrapper feature. Ignored in the open\n * source version.\n */\n#cmakedefine ANJAY_MODULE_SIM_BOOTSTRAP_HARDCODED_FILE_ID @ANJAY_MODULE_SIM_BOOTSTRAP_HARDCODED_FILE_ID@\n\n/**\n * Overridden OID of the SIM bootstrap information to look for in the DODF file,\n * expressed as a hexlified DER representation (without the header).\n *\n * This is the hexlified expected value of the 'id' field within the 'OidDO'\n * sequence in the DODF file (please refer to the PKCS #15 document for more\n * information).\n *\n * If not defined, the default value of <c>\"672b0901\"</c>, which corresponds to\n * OID 2.23.43.9.1 {joint-iso-itu-t(2) international-organizations(23) wap(43)\n * oma-lwm2m(9) lwm2m-bootstrap(1)}, will be used.\n *\n * No other values than the default are valid according to the specification,\n * but some SIM cards are known to use other non-standard values, e.g.\n * <c>\"0604672b0901\"</c> - including a superfluous nested BER-TLV header, as\n * erroneously illustrated in the EF(DODF-bootstrap) file coding example in\n * LwM2M TS 1.2 and earlier (fixed in LwM2M TS 1.2.1) - which is interpreted as\n * OID 0.6.4.103.43.9.1 (note that it is invalid as the 0.6 tree does not exist\n * in the repository as of writing this note).\n *\n * Requires <c>ANJAY_WITH_MODULE_BOOTSTRAPPER</c> to be enabled. At most one of\n * <c>ANJAY_MODULE_SIM_BOOTSTRAP_HARDCODED_FILE_ID</c> and\n * <c>ANJAY_MODULE_SIM_BOOTSTRAP_DATA_OID_OVERRIDE_HEX</c> may be defined at the\n * same time.\n *\n * IMPORTANT: Only available with the bootstrapper feature. Ignored in the open\n * source version.\n */\n#cmakedefine ANJAY_MODULE_SIM_BOOTSTRAP_DATA_OID_OVERRIDE_HEX \"@ANJAY_MODULE_SIM_BOOTSTRAP_DATA_OID_OVERRIDE_HEX@\"\n\n/**\n * Enable factory provisioning module. Data provided during provisioning uses\n * SenML CBOR format.\n */\n#cmakedefine ANJAY_WITH_MODULE_FACTORY_PROVISIONING\n\n/**\n * Enable oscore module (implementation of the OSCORE object).\n *\n * IMPORTANT: Only available as part of the OSCORE commercial feature. Ignored\n * in the open source version.\n */\n#cmakedefine ANJAY_WITH_MODULE_OSCORE\n\n/**\n * If enable Anjay doesn't handle composite operation (read, observe and write).\n * Its use makes sense for LWM2M v1.1 upwards.\n *\n * This flag can be used to reduce the size of the resulting code.\n *\n * If active, anjay will respond with message code 5.01 Not Implemented to any\n * composite type request.\n */\n#cmakedefine ANJAY_WITHOUT_COMPOSITE_OPERATIONS\n\n/**\n * Enable support for the experimental\n * <c>anjay_get_server_connection_status()</c> API and related\n * <c>anjay_server_connection_status_cb_t</c> callback.\n */\n#cmakedefine ANJAY_WITH_CONN_STATUS_API\n\n/**\n * Enable support for the experimental\n * <c>anjay_ssl_error_cb_t</c> callback.\n * Currently only supported for mbedTLS and custom SSL integration builds\n */\n#cmakedefine ANJAY_WITH_SSL_ERROR_API\n\n/**\n * Enable support for /25 LwM2M Gateway Object.\n *\n * Requires <c>ANJAY_WITH_LWM2M11</c> to be enabled.\n * Requires <c>ANJAY_WITH_CORE_PERSISTENCE</c> (commercial feature) to be\n * disabled.\n */\n#cmakedefine ANJAY_WITH_LWM2M_GATEWAY\n\n/**\n * Maximum holdoff time in seconds.\n *\n * This parameter defines an upper limit on the Hold Off Time as specified in\n * the LwM2M Security Object (Object ID: 0, Resource ID: 11). The Hold Off Time\n * is used by the LwM2M Client to delay initiating a Bootstrap process.\n *\n * Limiting the Hold Off Time is important in network environments where\n * prolonged delays may cause NAT bindings to expire, potentially leading to\n * failed connection attempts. This parameter ensures that the configured\n * Hold Off Time does not exceed a safe threshold. If a DTLS binding with\n * Connection ID is used this parameter can be increased safely.\n *\n * If editing this file manually, <c>@MAX_HOLDOFF_TIME@</c> shall be\n * replaced with a positive integer literal representing the time in seconds.\n * The default value defined in CMake build scripts is 20.\n */\n#define ANJAY_MAX_HOLDOFF_TIME @MAX_HOLDOFF_TIME@\n\n/**\n * Enable support for optional resources of the Firmware Update object\n * introduced in LwM2M 1.1.\n *\n * This includes the following resources:\n * - Severity\n * - Last State Change Time\n * - Max Defer Period\n *\n * Requires <c>ANJAY_WITH_LWM2M11</c> to be enabled.\n */\n#cmakedefine ANJAY_WITH_MODULE_FW_UPDATE_V11_RESOURCES\n\n/**@}*/\n\n#endif // ANJAY_CONFIG_H\n"
  },
  {
    "path": "include_public/anjay/attr_storage.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ANJAY_INCLUDE_ANJAY_ATTR_STORAGE_H\n#define ANJAY_INCLUDE_ANJAY_ATTR_STORAGE_H\n\n#include <avsystem/commons/avs_stream.h>\n\n#include <anjay/dm.h>\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n/**\n * @file attr_storage.h\n *\n * Automatic Attribute Storage module.\n *\n * This feature is enabled using the <c>ANJAY_WITH_ATTR_STORAGE</c> compile-time\n * feature macro. It makes it possible to automatically manage attributes for\n * LwM2M Objects, their instances and resources.\n *\n * In accordance to the LwM2M specification, there are three levels on which\n * attributes may be stored:\n *\n * - Resource level (@ref anjay_dm_resource_read_attrs_t,\n *   @ref anjay_dm_resource_write_attrs_t)\n * - Instance level (@ref anjay_dm_instance_read_default_attrs_t,\n *   @ref anjay_dm_instance_write_default_attrs_t)\n * - Object level (@ref anjay_dm_object_read_default_attrs_t,\n *   @ref anjay_dm_object_write_default_attrs_t)\n *\n * If at least one of either read or write handlers is provided in a given\n * object for a given level, attribute handling on that level will not be\n * altered, but instead any calls will be directly forwarded to the original\n * handlers.\n *\n * If both read and write handlers are left as NULL in a given object for a\n * given level, attribute storage will be handled by the Attribute Storage\n * module instead, implementing both handlers.\n */\n\n/**\n * Checks whether the attribute storage has been modified since last successful\n * call to @ref anjay_attr_storage_persist or @ref anjay_attr_storage_restore.\n */\nbool anjay_attr_storage_is_modified(anjay_t *anjay);\n\n/**\n * Removes all attributes from all entities, leaving the Attribute Storage in an\n * empty state.\n *\n * @param anjay Anjay instance.\n */\nvoid anjay_attr_storage_purge(anjay_t *anjay);\n\n/**\n * Dumps all set attributes to the @p out_stream.\n *\n * @param anjay         Anjay instance.\n * @param out_stream    Stream to write to.\n * @returns AVS_OK in case of success, or an error code.\n */\navs_error_t anjay_attr_storage_persist(anjay_t *anjay,\n                                       avs_stream_t *out_stream);\n\n/**\n * Attempts to restore attribute storage from specified @p in_stream.\n *\n * Note: before attempting restoration, the Attribute Storage is cleared, so no\n * previously set attributes will be retained. In particular, if restore fails,\n * then the Attribute Storage will be completely cleared and\n * @ref anjay_attr_storage_is_modified will return <c>true</c>.\n *\n * @param anjay     Anjay instance.\n * @param in_stream Stream to read from.\n * @returns AVS_OK in case of success, or an error code.\n *\n * NOTE: if restorations fails, then the Attribute Storage will be untouched.\n */\navs_error_t anjay_attr_storage_restore(anjay_t *anjay, avs_stream_t *in_stream);\n\n/**\n * Sets Object level attributes for the specified @p ssid.\n *\n * @param anjay Anjay object to operate on.\n * @param ssid  SSID for which given Attributes shall be set (must be a valid\n *              SSID corresponding to one of the non-Bootstrap LwM2M Servers).\n * @param oid   Object ID for which given Attributes shall be set.\n * @param attrs Attributes to be set (MUST NOT be NULL).\n *\n * NOTE: This function will fail if the object has object_read_default_attrs or\n * object_write_default_attrs handler implemented.\n *\n * @returns 0 on success, negative value in case of an error.\n */\nint anjay_attr_storage_set_object_attrs(anjay_t *anjay,\n                                        anjay_ssid_t ssid,\n                                        anjay_oid_t oid,\n                                        const anjay_dm_oi_attributes_t *attrs);\n/**\n * Sets Instance level attributes for the specified @p ssid.\n *\n * @param anjay Anjay object to operate on.\n * @param ssid  SSID for which given Attributes shall be set (must be a valid\n *              SSID corresponding to one of the non-Bootstrap LwM2M Servers).\n * @param oid   Object ID for which given Attributes shall be set.\n * @param iid   Instance ID for which given Attributes shall be set.\n * @param attrs Attributes to be set (MUST NOT be NULL).\n *\n * NOTE: This function will fail if the object has instance_read_default_attrs\n * or instance_write_default_attrs handler implemented.\n *\n * @returns 0 on success, negative value in case of an error.\n */\nint anjay_attr_storage_set_instance_attrs(\n        anjay_t *anjay,\n        anjay_ssid_t ssid,\n        anjay_oid_t oid,\n        anjay_iid_t iid,\n        const anjay_dm_oi_attributes_t *attrs);\n\n/**\n * Sets Resource level attributes for the specified @p ssid.\n *\n * @param anjay Anjay object to operate on.\n * @param ssid  SSID for which given Attributes shall be set (must be a valid\n *              SSID corresponding to one of the non-Bootstrap LwM2M Servers).\n * @param oid   Object ID owning the specified Instance.\n * @param iid   Instance ID owning the specified Resource.\n * @param rid   Resource ID for which given Attributes shall be set.\n * @param attrs Attributes to be set (MUST NOT be NULL).\n *\n * NOTE: This function will fail if the object has resource_read_attrs\n * or resource_write_attrs handler implemented.\n *\n * @returns 0 on success, negative value in case of an error.\n */\nint anjay_attr_storage_set_resource_attrs(anjay_t *anjay,\n                                          anjay_ssid_t ssid,\n                                          anjay_oid_t oid,\n                                          anjay_iid_t iid,\n                                          anjay_rid_t rid,\n                                          const anjay_dm_r_attributes_t *attrs);\n\n#ifdef ANJAY_WITH_LWM2M11\n/**\n * Sets Resource Instance level attributes for the specified @p ssid.\n *\n * @param anjay Anjay object to operate on.\n * @param ssid  SSID for which given Attributes shall be set (must be a valid\n *              SSID corresponding to one of the non-Bootstrap LwM2M Servers).\n * @param oid   Object ID owning the specified Instance.\n * @param iid   Instance ID owning the specified Resource.\n * @param rid   Resource ID owning the specified Resource Instance.\n * @param riid  Resource Instance ID for which given Attributes shall be set.\n * @param attrs Attributes to be set (MUST NOT be NULL).\n *\n * NOTE: This function will fail if the object has resource_read_attrs\n * or resource_write_attrs handler implemented.\n *\n * @returns 0 on success, negative value in case of an error.\n */\nint anjay_attr_storage_set_resource_instance_attrs(\n        anjay_t *anjay,\n        anjay_ssid_t ssid,\n        anjay_oid_t oid,\n        anjay_iid_t iid,\n        anjay_rid_t rid,\n        anjay_riid_t riid,\n        const anjay_dm_r_attributes_t *attrs);\n#endif // ANJAY_WITH_LWM2M11\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif /* ANJAY_INCLUDE_ANJAY_ATTR_STORAGE_H */\n"
  },
  {
    "path": "include_public/anjay/core.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ANJAY_INCLUDE_ANJAY_CORE_H\n#define ANJAY_INCLUDE_ANJAY_CORE_H\n\n#include <stdint.h>\n\n#include <avsystem/coap/udp.h>\n\n#include <avsystem/commons/avs_list.h>\n#include <avsystem/commons/avs_net.h>\n#include <avsystem/commons/avs_prng.h>\n#include <avsystem/commons/avs_sched.h>\n#include <avsystem/commons/avs_stream.h>\n#include <avsystem/commons/avs_time.h>\n\n#include <anjay/anjay_config.h>\n\n#ifdef ANJAY_WITH_LWM2M_GATEWAY\n#    define ANJAY_GATEWAY_MAX_PREFIX_LEN sizeof(\"dev65535\")\n#endif // ANJAY_WITH_LWM2M_GATEWAY\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n/** Short Server ID type. */\ntypedef uint16_t anjay_ssid_t;\n\n/** A constant that may be used in @ref anjay_schedule_registration_update\n * call instead of Short Server ID to send Update messages to all connected\n * servers. */\n#define ANJAY_SSID_ANY 0\n\n/** An SSID value reserved by LwM2M to refer to the Bootstrap Server.\n * NOTE: The value of a \"Short Server ID\" Resource in the Security Object\n * Instance referring to the Bootstrap Server is irrelevant and cannot be used\n * to identify the Bootstrap Server. */\n#define ANJAY_SSID_BOOTSTRAP UINT16_MAX\n\n/** Anjay object containing all information required for LwM2M communication. */\ntypedef struct anjay_struct anjay_t;\n\n/**\n * Default transmission params recommended by the CoAP specification (RFC 7252).\n */\n// clang-format off\n#define ANJAY_COAP_DEFAULT_UDP_TX_PARAMS \\\n    {                                    \\\n        /* .ack_timeout = */ { 2, 0 },   \\\n        /* .ack_random_factor = */ 1.5,  \\\n        /* .max_retransmit = */ 4,       \\\n        /* .nstart = */ 1                \\\n    }\n// clang-format on\n\n/**\n * Default handshake retransmission params recommended by the DTLS specification\n * (RFC 6347), i.e: 1s for the initial response, growing exponentially (with\n * each retransmission) up to maximum of 60s.\n */\n// clang-format off\n#define ANJAY_DTLS_DEFAULT_UDP_HS_TX_PARAMS \\\n    {                                       \\\n        /* .min = */ { 1, 0 },              \\\n        /* .max = */ { 60, 0 }              \\\n    }\n// clang-format on\n\n#ifdef ANJAY_WITH_LWM2M11\ntypedef enum {\n    /**\n     * Lightweight Machine to Machine Technical Specification, Approved Version\n     * 1.0.2 - 2018-02-09 (OMA-TS-LightweightM2M-V1_0_2-20180209-A).\n     */\n    ANJAY_LWM2M_VERSION_1_0,\n    /**\n     * Lightweight Machine to Machine Technical Specification, Approved Version\n     * 1.1.1 - 2019-06-17; Core (OMA-TS-LightweightM2M_Core-V1_1_1-20190617-A)\n     * and Transport Bindings\n     * (OMA-TS-LightweightM2M_Transport-V1_1_1-20190617-A).\n     */\n    ANJAY_LWM2M_VERSION_1_1\n#    ifdef ANJAY_WITH_LWM2M12\n    ,\n    /**\n     * Lightweight Machine to Machine Technical Specification, Approved Version\n     * 1.2 - 2020-11-10; Core (OMA-TS-LightweightM2M_Core-V1_2-20201110-A) and\n     * Transport Bindings (OMA-TS-LightweightM2M_Transport-V1_2-20201110-A).\n     */\n    ANJAY_LWM2M_VERSION_1_2\n#    endif // ANJAY_WITH_LWM2M12\n} anjay_lwm2m_version_t;\n\ntypedef struct {\n    /**\n     * The lowest version to attempt using when registering to LwM2M Servers.\n     */\n    anjay_lwm2m_version_t minimum_version;\n\n    /**\n     * The highest version to attempt using when registering to LwM2M Servers.\n     * This is also the version number sent in reponse to Bootstrap Discover.\n     */\n    anjay_lwm2m_version_t maximum_version;\n} anjay_lwm2m_version_config_t;\n#endif // ANJAY_WITH_LWM2M11\n\n// NOTE: A lot of code depends on numerical values of these constants.\n// Please be careful when refactoring.\ntypedef enum {\n    ANJAY_ID_OID,\n    ANJAY_ID_IID,\n    ANJAY_ID_RID,\n    ANJAY_ID_RIID,\n    _ANJAY_URI_PATH_MAX_LENGTH\n} anjay_id_type_t;\n\n/**\n * A data type that represents a data model path.\n *\n * It may represent a root path, an Object path, an Object Instance path, a\n * Resource path, or a Resource Instance path.\n *\n * The path is terminated either by an @ref ANJAY_ID_INVALID value, or\n * end-of-array (in case of Resource Instance paths). In case of root, Object\n * and Object Instance paths, the array elements past the terminating invalid ID\n * value are undefined and shall not be used. They are NOT required to be set to\n * @ref ANJAY_ID_INVALID. Paths object that numerically differ only in values\n * past the terminating invalid ID shall be treated as equal (and this is how\n * _anjay_uri_path_equal is implemented).\n *\n * The <c>ids</c> array is designed to be safely and meaningfully indexed by\n * @ref anjay_id_type_t values.\n */\ntypedef struct {\n    uint16_t ids[_ANJAY_URI_PATH_MAX_LENGTH];\n\n#ifdef ANJAY_WITH_LWM2M_GATEWAY\n    // NULL-terminated string\n    char prefix[ANJAY_GATEWAY_MAX_PREFIX_LEN];\n#endif // ANJAY_WITH_LWM2M_GATEWAY\n} anjay_uri_path_t;\n\n/**\n * A handler called if acknowledgement for confirmable notification is received\n * from the Server or some error has occurred.\n *\n * We consider a notification sent by a confirmable transport to be a\n * confirmable notification. This means that the callback will be triggered both\n * for notifications sent via a Confirmable CoAP message (which is sent over\n * UDP) and for notifications sent via CoAP over TCP.\n *\n * Please note that even if you have not set the\n * <c>anjay_configuration_t::confirmable_notifications</c> option, the\n * Confirmable Notification attribute is not configured on the specified path,\n * and you are not using TCP, this callback can still be triggered in two\n * situations:\n *\n * 1. Once every 24 hours, Anjay must send a confirmable notification (see RFC\n *    7641, Section 4.5).\n * 2. If an error occurs while preparing the notification-then the notification\n *    containing the error code (e.g., 4.04 Not Found) is sent as a confirmable\n *    message (if the server acknowledges that notification, the @p err is set\n *    to <c>AVS_OK</c>).\n *\n * @param anjay             Anjay object to operate on.\n * @param ssid              LwM2M Short Server ID.\n * @param observation_paths Array containing observation paths used during\n *                          notification process.\n * @param paths_count       Number of paths used during notification process.\n * @param err               Notification error status in category/code format.\n *                          It includes in particular:\n *                            - Reset\n *                              .category = AVS_COAP_ERR_CATEGORY\n *                              .code = AVS_COAP_ERR_UDP_RESET_RECEIVED\n *                            - Timeout/missing\n *                              .category = AVS_COAP_ERR_CATEGORY\n *                              .code = AVS_COAP_ERR_TIMEOUT\n *                            - Delivered\n *                              .code = 0 regardless of .category\n *                          For more information, please refer to @ref\n *                          deps/avs_coap/include_public/avsystem/coap/ctx.h.\n */\ntypedef void anjay_confirmable_notification_status_cb_t(\n        anjay_t *anjay,\n        anjay_ssid_t ssid,\n        const anjay_uri_path_t *observation_paths,\n        const size_t paths_count,\n        avs_error_t err);\n\n#ifdef ANJAY_WITH_CONN_STATUS_API\n/**\n * This enum represents the possible states of a server connection.\n */\ntypedef enum anjay_server_conn_status {\n    /**\n     * Invalid state. It is only returned if a given server connection object\n     * does not exist, or there was an error in determining its state.\n     */\n    ANJAY_SERV_CONN_STATUS_INVALID,\n    /**\n     * Generic state for any errors for which more specific states are not\n     * defined. For a regular LwM2M Server, this means that the primary\n     * connection is invalid or de-register/update operation has failed. For a\n     * LwM2M Bootstrap Server, it means that the bootstrap process failed.\n     */\n    ANJAY_SERV_CONN_STATUS_ERROR,\n    /**\n     * This status means that the server connection object has been just created\n     * as a result of the Security and Server object instances having been\n     * previously added to the data model. A connection attempt is scheduled to\n     * be made during subsequent calls to @ref anjay_sched_run.\n     */\n    ANJAY_SERV_CONN_STATUS_INITIAL,\n    /**\n     * This status is reported just before attempting the connection procedure,\n     * which may include binding the socket to a local port, performing DNS\n     * resolution, connecting the socket, and performing a (D)TLS handshake.\n     */\n    ANJAY_SERV_CONN_STATUS_CONNECTING,\n    /**\n     * This status means that the bootstrap process is in progress. It only\n     * applies to LwM2M Bootstrap Servers.\n     */\n    ANJAY_SERV_CONN_STATUS_BOOTSTRAPPING,\n    /**\n     * This status means that the bootstrap process has completed successfully.\n     * It only applies to LwM2M Bootstrap Servers.\n     */\n    ANJAY_SERV_CONN_STATUS_BOOTSTRAPPED,\n    /**\n     * This status means that the register operation is in progress. It only\n     * applies to regular LwM2M Servers.\n     */\n    ANJAY_SERV_CONN_STATUS_REGISTERING,\n    /**\n     * This status means that the Anjay is registered to the LwM2M Server. It\n     * only applies to regular LwM2M Servers.\n     */\n    ANJAY_SERV_CONN_STATUS_REGISTERED,\n    /**\n     * This status means that the last register operation has failed due to no\n     * response, a non-success CoAP response was received, or network problems.\n     * It only applies to regular LwM2M Servers.\n     */\n    ANJAY_SERV_CONN_STATUS_REG_FAILURE,\n    /**\n     * This status means that the de-register operation is in progress. It\n     * only applies to regular LwM2M Servers.\n     */\n    ANJAY_SERV_CONN_STATUS_DEREGISTERING,\n    /**\n     * This status means that the the library has de-registered with the LwM2M\n     * Server, either explicitly by successfully performing the De-register\n     * operation, or implicitly by discarding the registration state (which\n     * happens when the server is disabled while offline). It only applies to\n     * regular LwM2M Servers.\n     */\n    ANJAY_SERV_CONN_STATUS_DEREGISTERED,\n    /**\n     * This status means that the /1/x/4 resource has been executed and Anjay\n     * will try to suspend the LwM2M Server. It only applies to regular LwM2M\n     * Servers.\n     */\n    ANJAY_SERV_CONN_STATUS_SUSPENDING,\n    /**\n     * This status means that the suspend process has completed successfully. It\n     * only applies to regular LwM2M Servers.\n     */\n    ANJAY_SERV_CONN_STATUS_SUSPENDED,\n    /**\n     * This status means that the last update operation has failed due to either\n     * no response or a non-success CoAP response was received. It only\n     * applies to regular LwM2M Servers.\n     */\n    ANJAY_SERV_CONN_STATUS_REREGISTERING,\n    /**\n     * This status means that the update operation is in progress. It only\n     * applies to regular LwM2M Servers.\n     */\n    ANJAY_SERV_CONN_STATUS_UPDATING\n} anjay_server_conn_status_t;\n\n/**\n * Callback called each time there is a transition of a server connection status\n * (as listed in @ref anjay_server_conn_status_t ).\n *\n * @param arg    Opaque argument as set through the @c\n *               server_connection_status_cb_arg field in @ref\n *               anjay_configuration_t\n *\n * @param anjay  Anjay object that calls this callback\n *\n * @param ssid   Short Server ID of the server for which the status is reported\n *\n * @param status New status of the server connection\n */\ntypedef void\nanjay_server_connection_status_cb_t(void *arg,\n                                    anjay_t *anjay,\n                                    anjay_ssid_t ssid,\n                                    anjay_server_conn_status_t status);\n\n/**\n * This function returns the server connection status. Possible statuses are\n * given in the @ref anjay_server_conn_status_t. Statuses for a LwM2M Bootstrap\n * Server are different from the statuses for a regular LwM2M Server.\n *\n * @param anjay Anjay object to operate on.\n * @param ssid Short Server ID of the server which status will be returned. @ref\n * ANJAY_SSID_ANY is not a valid argument.\n *\n * @returns Server status on success, in case of error\n * ANJAY_SERV_CONN_STATUS_UNKNOWN.\n */\nanjay_server_conn_status_t\nanjay_get_server_connection_status(anjay_t *anjay, anjay_ssid_t ssid);\n#endif // ANJAY_WITH_CONN_STATUS_API\n\n#ifdef ANJAY_WITH_SSL_ERROR_API\n/**\n * @experimental This is experimental SSL error callback API. This API\n *               can change in future versions without any notice.\n *\n * Callback called each time a (D)TLS error is reported.\n *\n * @param arg    Opaque argument as set through the\n *               @ref anjay_configuration_t::ssl_error_cb_arg\n *\n * @param anjay  Anjay object that calls this callback\n *\n * @param ssid   Short Server ID of the server for which the error is reported\n *\n * @param error  SSL error encoded as `avs_error_t`. It includes in particular:\n *                 - SSL alert received from the peer:\n *                   - `.category` is `AVS_NET_SSL_ALERT_CATEGORY`\n *                   - `.code` is SSL alert code as defined in TLS specification\n *                 - SSL error from the crypto library\n *                   - `.category` is `AVS_SSL_LIB_ERR_CATEGORY`\n *                   - `.code` is SSL error code as defined in the library\n *                      documentation mapped to `uint16_t`. Example MbedTLS\n *                      mapping: `map_mbedtls_error_to_uint16()` in\n *                      `deps/avs_commons/src/net/mbedtls/avs_mbedtls_socket.c`\n */\ntypedef void anjay_ssl_error_cb_t(void *arg,\n                                  anjay_t *anjay,\n                                  anjay_ssid_t ssid,\n                                  avs_error_t error);\n\n#endif // ANJAY_WITH_SSL_ERROR_API\n\ntypedef struct anjay_configuration {\n    /**\n     * Endpoint name as presented to the LwM2M server. Must be non-NULL, or\n     * otherwise @ref anjay_new() will fail.\n     *\n     * NOTE: Endpoint name is copied during @ref anjay_new() and cannot be\n     * modified later on.\n     */\n    const char *endpoint_name;\n\n    /**\n     * UDP port number that all listening sockets will be bound to. It may be\n     * left at 0 - in that case, connection with each server will use a freshly\n     * generated ephemeral port number.\n     */\n    uint16_t udp_listen_port;\n\n    /**\n     * DTLS version to use for communication.\n     */\n    avs_net_ssl_version_t dtls_version;\n\n    /**\n     * Maximum size of a single incoming CoAP message. Decreasing this value\n     * reduces memory usage, but packets bigger than this value will\n     * be dropped.\n     */\n    size_t in_buffer_size;\n\n    /**\n     * Maximum size of a single outgoing CoAP message. If the message exceeds\n     * this size, the library performs the block-wise CoAP transfer\n     * ( https://tools.ietf.org/html/rfc7959 ).\n     * NOTE: in case of block-wise transfers, this value limits the payload size\n     * for a single block, not the size of a whole packet.\n     */\n    size_t out_buffer_size;\n\n    /**\n     * Number of bytes reserved for caching CoAP responses. If not 0,\n     * the library looks up recently generated responses and reuses them\n     * to handle retransmitted packets (ones with identical CoAP message ID).\n     *\n     * NOTE: while a single cache is used for all LwM2M servers, cached\n     * responses are tied to a particular server and not reused for other ones.\n     */\n    size_t msg_cache_size;\n\n    /**\n     * Socket configuration to use when creating TCP/UDP sockets.\n     *\n     * Note that:\n     * - <c>reuse_addr</c> will be forced to true.\n     * - Value pointed to by the <c>preferred_endpoint</c> will be ignored.\n     */\n    avs_net_socket_configuration_t socket_config;\n\n    /**\n     * Configuration of the CoAP transmission params for UDP connection, as per\n     * RFC 7252.\n     *\n     * If NULL, the default configuration @ref ANJAY_COAP_DEFAULT_UDP_TX_PARAMS\n     * will be selected.\n     *\n     * NOTE: Parameters are copied during @ref anjay_new() and cannot be\n     * modified later on.\n     */\n    const avs_coap_udp_tx_params_t *udp_tx_params;\n\n    /**\n     * Configuration of the DTLS handshake retransmission timeouts for UDP\n     * connection.\n     *\n     * If NULL, the default configuration\n     * @ref ANJAY_DTLS_DEFAULT_UDP_HS_TX_PARAMS will be selected.\n     *\n     * NOTE: Parameters are copied during @ref anjay_new() and cannot be\n     * modified later on.\n     *\n     * IMPORTANT: In case of a need to adjust DTLS retransmission params to\n     * match the CoAP retransmission params, the @ref udp_dtls_hs_tx_params\n     * shall be initialized as `dtls_hs_params` is in the following code\n     * snippet:\n     * @code\n     *  const avs_coap_udp_tx_params_t coap_tx_params = {\n     *      // ... some initialization\n     *  };\n     *\n     *  // Without ACK_RANDOM_FACTOR = 1.0, it is impossible to create a DTLS HS\n     *  // configuration that matches CoAP retransmission configuration\n     *  // perfectly.\n     *  assert(coap_tx_params.ack_random_factor == 1.0);\n     *\n     *  const avs_net_dtls_handshake_timeouts_t dtls_hs_tx_params = {\n     *      .min = avs_time_duration_fmul(coap_tx_params.ack_timeout,\n     *                                    coap_tx_params.ack_random_factor),\n     *      .max = avs_time_duration_fmul(\n     *              coap_tx_params.ack_timeout,\n     *              (1 << coap_tx_params.max_retransmit)\n     *                  * coap_tx_params.ack_random_factor)\n     *  };\n     * @endcode\n     */\n    const avs_net_dtls_handshake_timeouts_t *udp_dtls_hs_tx_params;\n\n    /**\n     * Controls whether Notify operations are conveyed using Confirmable CoAP\n     * messages by default.\n     */\n    bool confirmable_notifications;\n\n    /**\n     * If set to true, connection to the Bootstrap Server will be closed\n     * immediately after making a successful connection to any regular LwM2M\n     * Server and only opened again if (re)connection to a regular server is\n     * rejected.\n     *\n     * If set to false, legacy Server-Initiated Bootstrap is possible, i.e. the\n     * Bootstrap Server can reach the client at any time to re-initiate the\n     * bootstrap sequence.\n     *\n     * NOTE: This parameter controls a legacy Server-Initiated Bootstrap\n     * mechanism based on an interpretation of LwM2M 1.0 TS that is not\n     * universally accepted. Server-Initiated Bootstrap as specified in LwM2M\n     * 1.1 TS is always supported, regardless of this setting.\n     */\n    bool disable_legacy_server_initiated_bootstrap;\n\n    /**\n     * If \"Notification Storing When Disabled or Offline\" resource is set to\n     * true and either the client is in offline mode, or uses Queue Mode,\n     * Notify messages are enqueued and sent whenever the client is online\n     * again. This value allows one to limit the size of said notification\n     * queue. The limit applies to notifications queued for all servers.\n     *\n     * If set to 0, size of the stored notification queue is only limited by\n     * the amount of available RAM.\n     *\n     * If set to a positive value, that much *most recent* notifications are\n     * stored. Attempting to add a notification to the queue while it is\n     * already full drops the oldest one to make room for new one.\n     */\n    size_t stored_notification_limit;\n\n    /**\n     * Sets the preference of the library for Content-Format used when\n     * responding to a request without Accept option.\n     *\n     * If set to true, the formats used would be:\n     *  - for LwM2M 1.0: TLV,\n     *  - for LwM2M 1.1: SenML CBOR, or if not compiled in, SenML JSON, or if\n     *    not compiled in TLV.\n     */\n    bool prefer_hierarchical_formats;\n\n    /**\n     * Enables support for DTLS connection_id extension for all DTLS\n     * connections.\n     */\n    bool use_connection_id;\n\n    /**\n     * Send the Update message immediately when Object Instances are created or\n     * deleted.\n     *\n     * NOTE: In case of Create and Delete operations, the Update message will be\n     * immediately sent to <strong>all</strong> the servers, including the one\n     * that initiated the operation.\n     *\n     * By default, such data model changes are reported in the next scheduled\n     * update message (or the message can be requested using\n     * @ref anjay_schedule_registration_update), but the Update is not triggered\n     * automatically.\n     */\n    bool update_immediately_on_dm_change;\n\n    /**\n     * Send the Notify messages as a result of a server action (e.g. Write) even\n     * to the initiating server.\n     *\n     * By default, notifications resulting from server actions are only sent to\n     * the servers other than the one which initiated the action.\n     */\n    bool enable_self_notify;\n\n    /**\n     * Treat failures of the \"connect\" socket operation (e.g. (D)TLS handshake\n     * failures) as a failed LwM2M Register operation. This enables automatic\n     * retrying of them as described in the \"Bootstrap and LwM2M Server\n     * Registration Mechanisms\" of LwM2M Core TS 1.1.\n     *\n     * When disabled, such failures are treated as fatal errors and cause the\n     * entire registration sequence for that server to be aborted (which will\n     * trigger a fallback to Bootstrap if applicable).\n     */\n    bool connection_error_is_registration_failure;\n\n    /**\n     * (D)TLS ciphersuites to use if the \"DTLS/TLS Ciphersuite\" Resource\n     * (/0/x/16) is not available or empty.\n     *\n     * Passing a value with <c>num_ids == 0</c> (default) will cause defaults of\n     * the TLS backend library to be used.\n     *\n     * Contents of the <c>ids</c> array are copied, so it is safe to free the\n     * passed array after the call to @ref anjay_new.\n     */\n    avs_net_socket_tls_ciphersuites_t default_tls_ciphersuites;\n\n    /**\n     * Custom PRNG context to use. If @c NULL , a default one is used, with\n     * entropy source specific to selected cryptograpic backend. If default\n     * entropy source isn't available, creation of Anjay object will fail.\n     *\n     * Used for establishing TLS and DTLS connections, generation of tokens and\n     * by OSCORE module, if it's available.\n     *\n     * If not @c NULL , then MUST outlive created Anjay object.\n     */\n    avs_crypto_prng_ctx_t *prng_ctx;\n\n    /**\n     * Callback that will be executed when initializing TLS and DTLS\n     * connections, that can be used for additional configuration of the TLS\n     * backend.\n     */\n    avs_ssl_additional_configuration_clb_t *additional_tls_config_clb;\n\n#if defined(WITH_AVS_COAP_TCP) \\\n        && (defined(ANJAY_WITH_LWM2M11) || defined(ANJAY_WITH_COAP_DOWNLOAD))\n    /**\n     * Maximum expected TCP options size. CoAP messages with options longer\n     * than this value will be rejected.\n     *\n     * If set to 0, a hard-coded default value (128) will be used.\n     */\n    size_t coap_tcp_max_options_size;\n\n    /**\n     * Time to wait for incoming response after sending a request. After this\n     * time request is considered unsuccessful.\n     *\n     * If zero-initialized or set to @c AVS_TIME_DURATION_ZERO, a default\n     * value of 30s is used.\n     */\n    avs_time_duration_t coap_tcp_request_timeout;\n#endif // defined(WITH_AVS_COAP_TCP) && (defined(ANJAY_WITH_LWM2M11) ||\n       // defined(ANJAY_WITH_COAP_DOWNLOAD))\n\n#ifdef ANJAY_WITH_LWM2M11\n    /**\n     * Configuration of LwM2M protocol versions to use when attempting to\n     * register to LwM2M servers.\n     *\n     * If NULL, the default configuration, that allows all supported versions to\n     * be used, will be selected.\n     *\n     * Notes:\n     * - Configuration is copied during @ref anjay_new() and cannot be\n     *   modified later on.\n     * - Restricting the set of supported versions may speed up the Register\n     *   operation, as less versions will be attempted for registration.\n     * - If <c>minimum_version</c> is set to a higher value than\n     *   <c>maximum_version</c>, @ref anjay_new will fail.\n     * - If <c>minimum_version</c> is set to a version higher than LwM2M 1.0,\n     *   <c>disable_legacy_server_initiated_bootstrap</c> will be effectively\n     *   implied even if that field is set to <c>false</c>.\n     */\n    const anjay_lwm2m_version_config_t *lwm2m_version_config;\n\n    /**\n     * Enable usage of system-wide trust store (e.g. <c>/etc/ssl/certs</c> on\n     * most Unix-like systems) for PKIX certificate verification in addition to\n     * those specified via <c>trust_store_certs</c> and <c>trust_store_crls</c>.\n     *\n     * NOTE: System-wide trust store is currently supported only by the OpenSSL\n     * backend. This field will not have the intended effect with the Mbed TLS\n     * backend.\n     *\n     * NOTE: PKIX certificate verification is only used in certain \"Certificate\n     * Usage\" modes configured in the Security object of the data model. It is\n     * also not automatically propagated to downloads, although is passed\n     * through by @ref anjay_security_config_from_dm.\n     *\n     * NOTE: System-wide trust store will be disabled for connections using the\n     * trust store updated through the <c>/est/crts</c> request, regardless of\n     * the value of this flag.\n     */\n    bool use_system_trust_store;\n\n    /**\n     * Store of trust anchor certificates to use for PKIX certificate\n     * verification. This field is optional and can be left zero-initialized. If\n     * used, it shall be initialized using one of the\n     * <c>avs_crypto_trusted_cert_info_from_*</c> helper functions.\n     *\n     * Any data passed is copied immediately, so it is safe to free any\n     * associated buffers after calling @ref anjay_new.\n     *\n     * NOTE: PKIX certificate verification is only used in certain \"Certificate\n     * Usage\" modes configured in the Security object of the data model. It is\n     * also not automatically propagated to downloads, although is passed\n     * through by @ref anjay_security_config_from_dm.\n     */\n    avs_crypto_certificate_chain_info_t trust_store_certs;\n\n    /**\n     * Store of certificate revocation lists to use for PKIX certificate\n     * verification. This field is optional and can be left zero-initialized. If\n     * used, it shall be initialized using one of the\n     * <c>avs_crypto_cert_revocation_list_info_from_*</c> helper functions.\n     *\n     * Any data passed is copied immediately, so it is safe to free any\n     * associated buffers after calling @ref anjay_new.\n     *\n     * NOTE: PKIX certificate verification is only used in certain \"Certificate\n     * Usage\" modes configured in the Security object of the data model. It is\n     * also not automatically propagated to downloads, although is passed\n     * through by @ref anjay_security_config_from_dm.\n     */\n    avs_crypto_cert_revocation_list_info_t trust_store_crls;\n\n    /**\n     * Enable rebuilding of client certificate chain based on certificates in\n     * the trust store.\n     *\n     * If this field is set to <c>true</c>, when performing a (D)TLS handshake,\n     * if the client certificate configured in the data model (or the last\n     * certificate in a chain) is not self-signed, Anjay will attempt to find\n     * its ancestors in the appropriate trust store (which may be\n     * <c>trust_store_certs</c> or the one provisioned by <c>/est/crts</c>\n     * operation) and append them to the chain presented during handshake.\n     */\n    bool rebuild_client_cert_chain;\n#endif // ANJAY_WITH_LWM2M11\n\n    /**\n     * A handler called if acknowledgement for confirmable notification is\n     * received from the Server or some error has occurred.\n     */\n    anjay_confirmable_notification_status_cb_t\n            *confirmable_notification_status_cb;\n\n#ifdef ANJAY_WITH_CONN_STATUS_API\n    /**\n     * Function called each time there is a transition of a server connection\n     * status (as listed in @ref anjay_server_conn_status_t ).\n     */\n    anjay_server_connection_status_cb_t *server_connection_status_cb;\n\n    /**\n     * Opaque argument that will be passed to the function configured in the\n     * <c>server_connection_status_cb</c> field.\n     *\n     * If <c>server_connection_status_cb</c> is NULL, this field is ignored.\n     */\n    void *server_connection_status_cb_arg;\n#endif // ANJAY_WITH_CONN_STATUS_API\n#ifdef ANJAY_WITH_SSL_ERROR_API\n    /**\n     * @experimental This is experimental SSL error callback API. This\n     *               API can change in future versions without any notice.\n     *\n     * Function called each time a (D)TLS error is reported.\n     */\n    anjay_ssl_error_cb_t *ssl_error_cb;\n\n    /**\n     * @experimental This is experimental SSL error callback API. This\n     *               API can change in future versions without any notice.\n     *\n     * Opaque argument that will be passed to the function configured in the\n     * <c>ssl_error_cb</c> field.\n     *\n     * If <c>ssl_error_cb</c> is NULL, this field is ignored.\n     */\n    void *ssl_error_cb_arg;\n#endif // ANJAY_WITH_SSL_ERROR_API\n#ifdef ANJAY_WITH_COAP_DOWNLOAD\n    /**\n     * If set, defines the number of additional CoAP download attempts that will\n     * be made in case of failure. Can be useful for large files (Firmware\n     * Update) and poor network quality. If the resumption of the transfer\n     * was successful then the retry counter is reset.\n     *\n     * Retries are performed only in case of network problems - @ref\n     * AVS_ERRNO_CATEGORY or exchange timeout, in case of internal problems or\n     * error server responses the download is stopped.\n     *\n     * Each try will establish new connections and start a new exchange but\n     * preserve the download status. This logic does not comply with CoAP, but\n     * it can prevent multiple downloads of the same file.\n     *\n     * NOTE: Keep in mind that the behavior of this feature changes when the\n     * same socket is used for both file downloads and other CoAP operations\n     * (which may happen if the\n     * @ref anjay_download_config_t::prefer_same_socket_downloads flag is set).\n     * A specific difference is how network errors (e.g., ICMP) are handled: if\n     * a separate socket is used for downloads, Anjay will retry downloading;\n     * however, if the same socket is used for other CoAP operations, Anjay will\n     * abort the download. In the event of a timeout, Anjay will retry\n     * downloading in both cases.\n     */\n    size_t coap_downloader_retry_count;\n\n    /**\n     * If set, defines the delay between CoAP download attempts in case of\n     * failure. If not set, next attempt will be made immediately. Related to\n     * @ref coap_downloader_retry_count.\n     */\n    avs_time_duration_t coap_downloader_retry_delay;\n#endif // ANJAY_WITH_COAP_DOWNLOAD\n} anjay_configuration_t;\n\n/**\n * @returns pointer to the string representing current version of the library.\n */\nconst char *anjay_get_version(void);\n\n/**\n * Creates a new Anjay object.\n *\n * @param config Initial configuration. For details, see\n *               @ref anjay_configuration_t .\n *\n * @returns Created Anjay object on success, NULL in case of error.\n */\nanjay_t *anjay_new(const anjay_configuration_t *config);\n\n/**\n * Cleans up all resources and releases the Anjay object.\n *\n * <strong>NOTE:</strong> It shall be called <strong>before</strong> freeing\n * LwM2M Objects registered within the <c>anjay</c> object.\n *\n * <strong>NOTE:</strong> The <c>anjay</c> pointer is invalidated during the\n * call to this function. If Anjay is compiled with thread safety enabled, all\n * the intermediary cleanup code is properly synchronized, but you should still\n * make sure that no other thread is able to access the <c>anjay</c> object\n * before calling this function, to avoid its usage after <em>free</em>.\n *\n * @param anjay Anjay object to delete. MUST NOT be @c NULL .\n */\nvoid anjay_delete(anjay_t *anjay);\n\n/**\n * Retrieves a list of sockets used for communication with LwM2M servers.\n * Returned list must not be freed nor modified.\n *\n * Example usage: poll()-based application loop\n *\n * @code\n * struct pollfd poll_fd = { .events = POLLIN, .fd = -1 };\n *\n * while (true) {\n *     AVS_LIST(avs_net_socket_t*) sockets = anjay_get_sockets(anjay);\n *     if (sockets) {\n *         // assuming there is only one socket\n *         poll_fd.fd = *(const int*)avs_net_socket_get_system(*sockets);\n *     } else {\n *         // sockets not initialized yet\n *         poll_fd.fd = -1;\n *     }\n *     if (poll(&poll_fd, 1, 1000) > 0) {\n *          if (poll_fd.revents & POLLIN) {\n *              if (anjay_serve(anjay, *sockets)) {\n *                  log(\"anjay_serve failed\");\n *              }\n *          }\n *     }\n * }\n * @endcode\n *\n * <strong>NOTE:</strong> The returned list will be invalidated by any\n * subsequent call to <c>anjay_get_sockets()</c> or\n * @ref anjay_get_socket_entries . If you need to call these functions from\n * multiple threads, you need to implement additional synchronization to achieve\n * thread safety.\n *\n * The socket object pointers themselves may additionally be invalidated by a\n * call to @ref anjay_sched_run, @ref anjay_serve, @ref anjay_serve_any or\n * during the execution of @ref anjay_event_loop_run or\n * @ref anjay_event_loop_run_with_error_handling . For this reason, it is\n * recommended to only call this function from callback functions called from\n * within Anjay, in scheduler jobs, or as part of a custom event loop.\n *\n * @param anjay Anjay object to operate on.\n *\n * @returns A list of valid server sockets on success,\n *          NULL when the device is not connected to any server.\n */\nAVS_LIST(avs_net_socket_t *const) anjay_get_sockets(anjay_t *anjay);\n\ntypedef enum {\n    ANJAY_SOCKET_TRANSPORT_INVALID = -1,\n    ANJAY_SOCKET_TRANSPORT_UDP = 0,\n    ANJAY_SOCKET_TRANSPORT_TCP,\n    ANJAY_SOCKET_TRANSPORT_SMS,\n    ANJAY_SOCKET_TRANSPORT_NIDD\n} anjay_socket_transport_t;\n\n/**\n * A structure that describes an open socket used by Anjay. Returned by\n * @ref anjay_get_socket_entries.\n */\ntypedef struct {\n    /**\n     * The socket described by this structure. It is intended to be used\n     * directly only for checking whether there is data ready, using mechanisms\n     * such as <c>select()</c> or <c>poll()</c>.\n     */\n    avs_net_socket_t *socket;\n\n    /**\n     * Transport layer used by <c>socket</c>.\n     *\n     * Guaranteed to not be @ref ANJAY_SOCKET_TRANSPORT_INVALID, that value is\n     * only used internally.\n     */\n    anjay_socket_transport_t transport;\n\n    /**\n     * SSID of the server to which the socket is related. May be:\n     * - <c>ANJAY_SSID_ANY</c> if the socket is not directly and unambiguously\n     *   related to any server, which includes:\n     *   - download sockets\n     *   - SMS communication socket (common for all servers; only in versions of\n     *     Anjay that include the SMS commercial feature)\n     * - <c>ANJAY_SSID_BOOTSTRAP</c> for the Bootstrap Server socket\n     * - any other value for sockets related to regular LwM2M servers\n     */\n    anjay_ssid_t ssid;\n\n    /**\n     * Flag that is true in the following cases:\n     * - it is a UDP communication socket for a regular LwM2M server that is\n     *   configured to use the \"queue mode\", or\n     * - it is an SMS communication socket and all LwM2M servers that use this\n     *   transport use the \"queue mode\" (only relevant to versions of Anjay that\n     *   include the SMS commercial feature)\n     *\n     * In either case, a queue mode socket will stop being returned from\n     * @ref anjay_get_sockets and @ref anjay_get_socket_entries after period\n     * defined by CoAP <c>MAX_TRANSMIT_WAIT</c> since last communication.\n     */\n    bool queue_mode;\n} anjay_socket_entry_t;\n\n/**\n * Retrieves a list of structures that describe sockets used for communication\n * with LwM2M servers. Returned list must not be freed nor modified.\n *\n * The returned data is equivalent to the one that can be retrieved using\n * @ref anjay_get_sockets - but includes additional data that describes the\n * socket in addition to the socket itself. See @ref anjay_socket_entry_t for\n * details.\n *\n * <strong>NOTE:</strong> The returned list will be invalidated by any\n * subsequent call to @ref anjay_get_sockets or <c>anjay_get_socket_entries</c>.\n * If you need to call these functions from multiple threads, you need to\n * implement additional synchronization to achieve thread safety.\n *\n * The socket object pointers themselves may additionally be invalidated by a\n * call to @ref anjay_sched_run, @ref anjay_serve, @ref anjay_serve_any or\n * during the execution of @ref anjay_event_loop_run or\n * @ref anjay_event_loop_run_with_error_handling . For this reason, it is\n * recommended to only call this function from callback functions called from\n * within Anjay, in scheduler jobs, or as part of a custom event loop.\n *\n * @param anjay Anjay object to operate on.\n *\n * @returns A list of valid server socket entries on success,\n *          NULL when the device is not connected to any server.\n */\nAVS_LIST(const anjay_socket_entry_t) anjay_get_socket_entries(anjay_t *anjay);\n\n/**\n * Reads a message from given @p ready_socket and handles it appropriately.\n *\n * Initially, the receive method on the underlying socket is called with receive\n * timeout set to zero. Subsequent receive requests may block with non-zero\n * timeout values when e.g. waiting for retransmissions or subsequent BLOCK\n * chunks - this is necessary to hide this complexity from the user callbacks in\n * streaming mode.\n *\n * This function may handle more than one request at once. Upon successful\n * return, it is guaranteed that there is no more data to be received on the\n * socket at the moment.\n *\n * @param anjay        Anjay object to operate on.\n * @param ready_socket A socket to read the message from.\n *\n * @returns 0 on success, a negative value in case of error. Note that it\n *          includes non-fatal errors, such as receiving a malformed packet.\n */\nint anjay_serve(anjay_t *anjay, avs_net_socket_t *ready_socket);\n\n/** Object ID */\ntypedef uint16_t anjay_oid_t;\n\n/** Object Instance ID */\ntypedef uint16_t anjay_iid_t;\n\n/** Resource ID */\ntypedef uint16_t anjay_rid_t;\n\n/** Resource Instance ID */\ntypedef uint16_t anjay_riid_t;\n\n/**\n * Value reserved by the LwM2M spec for all kinds of IDs (Object IDs, Object\n * Instance IDs, Resource IDs, Resource Instance IDs, Short Server IDs).\n */\n#define ANJAY_ID_INVALID UINT16_MAX\n\n/** Helper macro used to define ANJAY_ERR_ constants.\n * Generated values are valid CoAP Status Codes encoded as a single byte. */\n#define ANJAY_COAP_STATUS(Maj, Min) ((uint8_t) ((Maj << 5) | (Min & 0x1F)))\n\n/** Error values that may be returned from data model handlers. @{ */\n/**\n * Request sent by the LwM2M Server was malformed or contained an invalid\n * value.\n */\n#define ANJAY_ERR_BAD_REQUEST (-(int) ANJAY_COAP_STATUS(4, 0))\n/**\n * LwM2M Server is not allowed to perform the operation due to lack of\n * necessary access rights.\n */\n#define ANJAY_ERR_UNAUTHORIZED (-(int) ANJAY_COAP_STATUS(4, 1))\n/**\n * Low-level CoAP error code; used internally by Anjay when CoAP option values\n * were invalid.\n */\n#define ANJAY_ERR_BAD_OPTION (-(int) ANJAY_COAP_STATUS(4, 2))\n#define ANJAY_ERR_FORBIDDEN (-(int) ANJAY_COAP_STATUS(4, 3))\n/** Target of the operation (Object/Instance/Resource) does not exist. */\n#define ANJAY_ERR_NOT_FOUND (-(int) ANJAY_COAP_STATUS(4, 4))\n/**\n * Operation is not allowed in current device state or the attempted operation\n * is invalid for this target (Object/Instance/Resource)\n */\n#define ANJAY_ERR_METHOD_NOT_ALLOWED (-(int) ANJAY_COAP_STATUS(4, 5))\n/**\n * Low-level CoAP error code; used internally by Anjay when the client is\n * unable to encode response in requested content format.\n */\n#define ANJAY_ERR_NOT_ACCEPTABLE (-(int) ANJAY_COAP_STATUS(4, 6))\n/**\n * Low-level CoAP error code; used internally by Anjay in case of unrecoverable\n * problems during block-wise transfer.\n */\n#define ANJAY_ERR_REQUEST_ENTITY_INCOMPLETE (-(int) ANJAY_COAP_STATUS(4, 8))\n/**\n * The server requested operation has a Content Format option that is\n * unsupported by Anjay.\n */\n#define ANJAY_ERR_UNSUPPORTED_CONTENT_FORMAT (-(int) ANJAY_COAP_STATUS(4, 15))\n/** Unspecified error, no other error code was suitable. */\n#define ANJAY_ERR_INTERNAL (-(int) ANJAY_COAP_STATUS(5, 0))\n/** Operation is not implemented by the LwM2M Client. */\n#define ANJAY_ERR_NOT_IMPLEMENTED (-(int) ANJAY_COAP_STATUS(5, 1))\n/**\n * LwM2M Client is busy processing some other request; LwM2M Server may retry\n * sending the same request after some delay.\n */\n#define ANJAY_ERR_SERVICE_UNAVAILABLE (-(int) ANJAY_COAP_STATUS(5, 3))\n/** @} */\n\n/**\n * Extracts the scheduler used by Anjay allowing the user to schedule his own\n * tasks.\n *\n * See docs of [avs_commons library](https://github.com/AVSystem/avs_commons/\n * blob/master/include_public/avsystem/commons/sched.h) for API of\n * @c avs_sched_t object.\n *\n * **Must not** use @c avs_sched_cleanup on the returned scheduler. Anjay will\n * cleanup it itself.\n *\n * @param anjay Anjay object to operate on.\n *\n * @returns non-null scheduler object used by Anjay.\n */\navs_sched_t *anjay_get_scheduler(anjay_t *anjay);\n\n/**\n * Determines time of next scheduled task.\n *\n * May be used to determine how long the device may wait before calling\n * @ref anjay_sched_run .\n *\n * @param      anjay     Anjay object to operate on.\n * @param[out] out_delay Relative time from now of next scheduled task.\n *\n * @returns 0 on success, or a negative value if no tasks are scheduled.\n */\nint anjay_sched_time_to_next(anjay_t *anjay, avs_time_duration_t *out_delay);\n\n/**\n * Determines time of next scheduled task in milliseconds.\n *\n * This function is equivalent to @ref anjay_sched_time_to_next but, as a\n * convenience for users of system calls such as <c>poll()</c>, the result is\n * returned as a single integer number of milliseconds.\n *\n * @param      anjay        Anjay object to operate on.\n * @param[out] out_delay_ms Relative time from now of next scheduled task, in\n *                          milliseconds.\n *\n * @returns 0 on success, or a negative value if no tasks are scheduled.\n */\nint anjay_sched_time_to_next_ms(anjay_t *anjay, int *out_delay_ms);\n\n/**\n * Calculates time in milliseconds the client code may wait for incoming events\n * before the need to call @ref anjay_sched_run .\n *\n * This function combines @ref anjay_sched_time_to_next_ms with a user-provided\n * limit, so that a conclusive value will always be returned. It is provided as\n * a convenience for users of system calls such as <c>poll()</c>.\n *\n * @param anjay    Anjay object to operate on.\n * @param limit_ms The longest amount of time the function shall return.\n *\n * @returns Relative time from now of next scheduled task, in milliseconds, if\n *          such task exists and it's scheduled to run earlier than\n *          <c>limit_ms</c> seconds from now, or <c>limit_ms</c> otherwise.\n */\nint anjay_sched_calculate_wait_time_ms(anjay_t *anjay, int limit_ms);\n\n/**\n * Runs all scheduled events which need to be invoked at or before the time of\n * this function invocation.\n *\n * @param anjay Anjay object to operate on.\n */\nvoid anjay_sched_run(anjay_t *anjay);\n\n#ifdef ANJAY_WITH_EVENT_LOOP\n/**\n * Runs Anjay's main event loop that executes @ref anjay_serve and\n * @ref anjay_sched_run as appropriate.\n *\n * This function will only return after either:\n *\n * - @ref anjay_event_loop_interrupt is called (from a different thread or from\n *   a user callback), or\n * - a fatal error (e.g. out-of-memory) occurs in the loop itself (errors from\n *   @ref anjay_serve are <em>not</em> considered fatal)\n *\n * <strong>IMPORTANT:</strong> The preimplemented event loop is primarily\n * intended to be run in a dedicated thread, while other threads may handle\n * tasks not related to LwM2M. It is safe to control the same instance of Anjay\n * concurrently from a different thread <strong>only if</strong> the thread\n * safety features have been enabled at Anjay's compile time:\n *\n * - <c>AVS_COMMONS_SCHED_THREAD_SAFE</c> (<c>WITH_SCHEDULER_THREAD_SAFE</c> in\n *   CMake) is required to safely use Anjay's internal scheduler (see\n *   @ref anjay_get_scheduler)\n *   - Please note that only managing scheduler tasks from another thread is\n *     safe. Attempting to call @ref anjay_sched_run (or\n *     <c>avs_sched_run(anjay_get_scheduler(anjay))</c> while the event loop is\n *     running <strong>may lead to undefined behavior in the loop</strong>\n * - <c>ANJAY_WITH_THREAD_SAFETY</c> (<c>WITH_THREAD_SAFETY</c> in CMake) is\n *   required to call any other functions, aside from the cleanup functions\n *   (which are only safe to call after the event loop has finished) and\n *   @ref anjay_event_loop_interrupt (which is always safe to call)\n *\n * <strong>CAUTION:</strong> The preimplemented event loop will only work if all\n * the sockets that may be created by Anjay will return file descriptors\n * compatible with <c>poll()</c> or <c>select()</c> through\n * <c>avs_net_socket_get_system()</c>.\n *\n * In particular, please be cautious when using SMS or NIDD transports (in\n * versions of Anjay that include the relevant commercial features) - your\n * <c>anjay_smsdrv_system_socket_t</c> and\n * <c>anjay_nidd_driver_system_descriptor_t</c> functions need to populate the\n * output argument with a valid file descriptor (e.g.\n * <c>int fd = ...; *out = &fd;</c>), and that file descriptor must be valid to\n * use with the <c>poll()</c> or <c>select()</c> functions (which may not true\n * if the socket API is separate from other IO APIs, as is the case e.g. on\n * Windows). Otherwise attempting to use this event loop implementation will not\n * handle NIDD and SMS transports properly, and may even lead to undefined\n * behavior.\n *\n * @param anjay         Anjay object to operate on.\n * @param max_wait_time Maximum time to spend in each single call to\n *                      <c>poll()</c> or <c>select()</c>. Larger times will\n *                      prevent the event loop thread from waking up too\n *                      frequently. However, during this wait time, the call to\n *                      @ref anjay_event_loop_interrupt may not be handled\n *                      immediately, and scheduler jobs requested from other\n *                      threads (see @ref anjay_get_scheduler) will not be\n *                      executed, so shortening this time will make the\n *                      scheduler more responsive.\n *\n * @returns 0 after having been successfully interrupted by\n *          @ref anjay_event_loop_interrupt, or a negative value in case of a\n *          fatal error.\n */\nint anjay_event_loop_run(anjay_t *anjay, avs_time_duration_t max_wait_time);\n\n/**\n * Act same as @ref anjay_event_loop_run, but when none of the configured\n * servers could be reached, try to reconnect using function @ref\n * anjay_transport_schedule_reconnect.\n *\n * @param anjay         Anjay object to operate on.\n * @param max_wait_time Maximum time to spend in each single call to\n *                      <c>poll()</c> or <c>select()</c>. Larger times will\n *                      prevent the event loop thread from waking up too\n *                      frequently. However, during this wait time, the call to\n *                      @ref anjay_event_loop_interrupt may not be handled\n *                      immediately, and scheduler jobs requested from other\n *                      threads (see @ref anjay_get_scheduler) will not be\n *                      executed, so shortening this time will make the\n *                      scheduler more responsive.\n *\n * @returns 0 after having been successfully interrupted by\n *          @ref anjay_event_loop_interrupt, or a negative value in case of a\n *          fatal error.\n */\nint anjay_event_loop_run_with_error_handling(anjay_t *anjay,\n                                             avs_time_duration_t max_wait_time);\n\n/**\n * Interrupts an ongoing execution of @ref anjay_event_loop_run.\n *\n * This function may be called from another thread, or from a callback running\n * within the event loop itself.\n *\n * After the call to this function, the event loop will finish as soon as\n * possible while gracefully executing any outstanding tasks.\n *\n * Please note that the event loop may not be interrupted immediately and\n * instead wait until either of the following:\n *\n * - any ongoing scheduler tasks finish\n * - any incoming operation involving a blockwise transfer finishes\n * - the <c>poll</c> or <c>select()</c> operation finishes (see the\n *   <c>max_wait_time</c> argument to @ref anjay_event_loop_run)\n *\n * Interrupting the two latter cases may be possible by performing a\n * system-specific call that would interrupt ongoing socket operations (e.g.\n * raising a signal with an appropriately crafted signal handler on Unix-like\n * systems) <em>immediately after</em> having called\n * <c>anjay_event_loop_interrupt()</c>.\n *\n * @param anjay Anjay object to operate on.\n *\n * @returns 0 if the interrupt has been successfully raised, or a negative value\n *          if the event loop is either not running or already in the process of\n *          finishing due to a previous interrupt.\n *\n * Please note that this function always returns immediately, without waiting\n * for the event loop to actually finish. Please use an appropriate system API\n * (e.g. <c>pthread_join()</c> on Unix-like systems) if you need to ensure that\n * the event loop has finished.\n */\nint anjay_event_loop_interrupt(anjay_t *anjay);\n\n/**\n * Executes <c>select()</c> or <c>poll()</c> on all sockets currently in use and\n * calls @ref anjay_serve if appropriate.\n *\n * This is intended as a building block for custom event loops. In particular,\n * this code:\n *\n * <code>\n * while (true) {\n *     anjay_serve_any(anjay, max_wait_time);\n *     anjay_sched_run(anjay);\n * }\n * </code>\n *\n * is equivalent to <c>anjay_event_loop_run(anjay, max_wait_time)</c>, as long\n * as @ref anjay_event_loop_interrupt is never called.\n *\n * <strong>CAUTION:</strong> Most of the caveats described in the documentation\n * for @ref anjay_event_loop_run also apply to this function. Please refer there\n * for more information.\n *\n * @param anjay         Anjay object to operate on.\n * @param max_wait_time Maximum time to spend in <c>poll()</c> or\n *                      <c>select()</c>.\n *\n * @returns 0 for success, or a negative value in case of a fatal error. Please\n *          note that errors from @ref anjay_serve are <em>not</em> considered\n *          fatal.\n */\nint anjay_serve_any(anjay_t *anjay, avs_time_duration_t max_wait_time);\n#endif // ANJAY_WITH_EVENT_LOOP\n\n/**\n * Schedules sending a Register message to the server identified by given\n * Short Server ID.\n *\n * For currently connected servers, the Register message will be sent during the\n * next @ref anjay_sched_run call, without reconnecting (and thus without a new\n * DTLS handshake, if applicable). Please additionally call\n * @ref anjay_server_schedule_reconnect before or after this function (without\n * running @ref anjay_sched_run in between - so in multi-threaded applications\n * you may need to do that from within an intermediary scheduler job) if you\n * want to force a reconnect.\n *\n * For servers that are disabled or in a failure state, this function will\n * invalidate the registration state, so that a new Register message will\n * definitely be sent once the server is re-enabled (even if the DTLS session\n * is successfully resumed), but will not re-enable it itself. Please\n * additionally call @ref anjay_enable_server before or after this funciton if\n * you want the server reactivated immediately.\n *\n * Note: This function will not change the offline state of the server's\n * transport.\n *\n * @param anjay Anjay object to operate on.\n * @param ssid  Short Server ID of the server to send Register to or\n *              @ref ANJAY_SSID_ANY to send Register to all known servers.\n *              NOTE: Since Register is not useful for the Bootstrap Server,\n *              this function does not send one for @ref ANJAY_SSID_BOOTSTRAP\n *              @p ssid .\n *\n * @returns 0 on success, a negative value in case of error.\n */\nint anjay_schedule_register(anjay_t *anjay, anjay_ssid_t ssid);\n\n/**\n * Schedules sending an Update message to the server identified by given\n * Short Server ID.\n *\n * The Update will be sent during the next @ref anjay_sched_run call.\n *\n * Note: This function will not schedule registration update if Anjay is in\n * offline mode.\n *\n * @param anjay Anjay object to operate on.\n * @param ssid  Short Server ID of the server to send Update to or\n *              @ref ANJAY_SSID_ANY to send Updates to all connected servers.\n *              NOTE: Since Updates are not useful for the Bootstrap Server,\n *              this function does not send one for @ref ANJAY_SSID_BOOTSTRAP\n *              @p ssid .\n *\n * @returns 0 on success, a negative value in case of error.\n */\nint anjay_schedule_registration_update(anjay_t *anjay, anjay_ssid_t ssid);\n\n#ifdef ANJAY_WITH_LWM2M11\n/**\n * Schedules sending a Bootstrap-Request message to the LwM2M Bootstrap Server.\n *\n * The Bootstrap-Request will be sent during one of subsequent\n * @ref anjay_sched_run calls.\n *\n * @param anjay Anjay object to operate on.\n *\n * @returns 0 on success, or a negative value in case of error (including the\n *          case where there might not be a Bootstrap Server available or\n *          Bootstrap support might not be enabled at compile time).\n */\nint anjay_schedule_bootstrap_request(anjay_t *anjay);\n#endif // ANJAY_WITH_LWM2M11\n\n/**\n * This function shall be called when an LwM2M Server Object shall be disabled.\n * The standard case for this is when Execute is performed on the Disable\n * resource (/1/x/4).\n *\n * The server will be disabled for the period of time determined by the value\n * of the Disable Timeout resource (/1/x/5). The resource is read soon after\n * the invocation of this function (during next @ref anjay_sched_run) and is\n * <strong>not</strong> updated upon any subsequent Writes to that resource.\n *\n * If the server is already disabled, its re-enable action will be re-scheduled\n * according to the value of the Disable Timeout resource added to current time.\n *\n *\n * @param anjay Anjay object to operate on.\n * @param ssid  Short Server ID of the server to put in a disabled state.\n *              NOTE: disabling a server requires a Server Object Instance\n *              to be present for given @p ssid . Because the Bootstrap Server\n *              does not have one, this function does nothing when called with\n *              @ref ANJAY_SSID_BOOTSTRAP .\n *\n * @returns 0 on success, a negative value in case of error.\n */\nint anjay_disable_server(anjay_t *anjay, anjay_ssid_t ssid);\n\n/**\n * This function shall be called when an LwM2M Server Object shall be disabled.\n * The standard case for this is when Execute is performed on the Disable\n * resource (/1/x/4). It may also be used to prevent reconnections if the\n * server becomes unreachable.\n *\n * The server will become disabled during next @ref anjay_sched_run call.\n *\n * If the server is already disabled, its re-enable action will be re-scheduled\n * or cancelled, according to the @p timeout argument.\n *\n * NOTE: disabling a server with dual binding (e.g. UDP+SMS trigger) closes both\n * communication channels. Shutting down only one of them requires changing\n * the Binding Resource in Server object.\n *\n *\n * @param anjay   Anjay object to operate on.\n * @param ssid    Short Server ID of the server to put in a disabled state.\n * @param timeout Disable timeout. If set to @c AVS_TIME_DURATION_INVALID,\n *                the server will remain disabled until explicit call to\n *                @ref anjay_enable_server . Otherwise, the server will get\n *                enabled automatically after @p timeout .\n *\n * @returns 0 on success, a negative value in case of error.\n */\nint anjay_disable_server_with_timeout(anjay_t *anjay,\n                                      anjay_ssid_t ssid,\n                                      avs_time_duration_t timeout);\n\n/**\n * Schedules a job for re-enabling a previously disabled (with a call to\n * @ref anjay_disable_server_with_timeout ) server. The server will be enabled\n * during next @ref anjay_sched_run call.\n *\n * @param anjay Anjay object to operate on.\n * @param ssid  Short Server ID of the server to enable.\n *\n * @returns 0 on success, a negative value in case of error.\n */\nint anjay_enable_server(anjay_t *anjay, anjay_ssid_t ssid);\n\n/**\n * Reconnects sockets associated with a specific LwM2M Server.\n *\n * The reconnection will be performed during the next @ref anjay_sched_run call\n * and will trigger sending any messages necessary to maintain valid\n * registration (DTLS session resumption and/or Register or Update operations).\n *\n * If the server connection is disconnected due to queue mode, and there are no\n * outstanding messages (Register, Update, Notify or Send), the socket will not\n * be reconnected.\n *\n * If the server is in a disabled state, an error will be returned. Use\n * @ref anjay_enable_server if you want to re-enable such a server.\n *\n * @param anjay Anjay object to operate on.\n * @param ssid  Short Server ID of the server to reconnect.\n *\n * @returns 0 on success, a negative value in case of error.\n */\nint anjay_server_schedule_reconnect(anjay_t *anjay, anjay_ssid_t ssid);\n\n/**\n * Structure defining the set of transports that\n * @ref anjay_transport_is_offline, @ref anjay_transport_enter_offline,\n * @ref anjay_transport_exit_offline, @ref anjay_transport_set_online and\n * @ref anjay_transport_schedule_reconnect operate on.\n */\ntypedef struct {\n    bool udp : 1;\n    bool tcp : 1;\n} anjay_transport_set_t;\n\n/**\n * @ref anjay_transport_set_t constant with all fields set to <c>true</c>.\n */\nextern const anjay_transport_set_t ANJAY_TRANSPORT_SET_ALL;\n\n/**\n * @ref anjay_transport_set_t constant with <c>udp</c> and <c>tcp</c> fields set\n * to <c>true</c>.\n *\n * NOTE: In the open-source version, @ref ANJAY_TRANSPORT_SET_ALL and\n * @ref ANJAY_TRANSPORT_SET_IP are equivalent.\n */\nextern const anjay_transport_set_t ANJAY_TRANSPORT_SET_IP;\n\n/**\n * @ref anjay_transport_set_t constant with just the <c>udp</c> field set to\n * <c>true</c>.\n */\nextern const anjay_transport_set_t ANJAY_TRANSPORT_SET_UDP;\n\n/**\n * @ref anjay_transport_set_t constant with just the <c>tcp</c> field set to\n * <c>true</c>.\n */\nextern const anjay_transport_set_t ANJAY_TRANSPORT_SET_TCP;\n\n/**\n * Checks whether all the specified transports are in offline mode.\n *\n * @param anjay         Anjay object to operate on.\n * @param transport_set Set of transports to check.\n *\n * @returns true if all of the transports speicifed by @p transport_set are in\n *          offline mode, false otherwise\n */\nbool anjay_transport_is_offline(anjay_t *anjay,\n                                anjay_transport_set_t transport_set);\n\n/**\n * Puts all the transports specified by @p transport_set into offline mode.\n * This should be done when the connectivity for these transports is deemed\n * unavailable or lost.\n *\n * During subsequent calls to @ref anjay_sched_run, Anjay will close all of the\n * sockets corresponding to the specified transport and stop attempting to make\n * any contact with remote hosts over it, until a call to\n * @ref anjay_transport_exit_offline for any of the corresponding transports.\n *\n * Note that offline mode also affects downloads. E.g., putting the TCP\n * transport into offline mode will pause all ongoing downloads over TCP and\n * prevent new such download requests from being performed.\n *\n * User code shall still interface normally with the library, even if all the\n * transports are in the offline state. This include regular calls to\n * @ref anjay_sched_run. Notifications (as reported using\n * @ref anjay_notify_changed and @ref anjay_notify_instances_changed) continue\n * to be tracked, and may be sent after reconnecting, depending on values of the\n * \"Notification Storing When Disabled or Offline\" resource.\n *\n * @param anjay         Anjay object to operate on.\n * @param transport_set Set of transports to put into offline mode.\n *\n * @returns 0 on success, a negative value in case of error.\n */\nint anjay_transport_enter_offline(anjay_t *anjay,\n                                  anjay_transport_set_t transport_set);\n\n/**\n * Puts all the transports specified by @p transport_set back into online\n * mode, if any of them were previously put into offline mode using\n * @ref anjay_transport_enter_offline.\n *\n * Transports that are unavailable due to compile-time or runtime configuration\n * are ignored.\n *\n * During subsequent calls to @ref anjay_sched_run, new connections to all\n * LwM2M servers disconnected due to offline mode will be attempted, and\n * Register or Registration Update messages will be sent as appropriate.\n * Downloads paused due to offline mode will be resumed as well.\n *\n * @param anjay         Anjay object to operate on.\n * @param transport_set Set of transports to put into online mode.\n *\n * @returns 0 on success, a negative value in case of error.\n */\nint anjay_transport_exit_offline(anjay_t *anjay,\n                                 anjay_transport_set_t transport_set);\n\n/**\n * Puts all the transports that are enabled through the compile-time and\n * runtime configuration, and specified in @p transport_set, into online mode.\n * At the same time, puts all the other transports into offline mode.\n *\n * This function combines the functionality of @p anjay_transport_enter_offline\n * and @p anjay_transport_exit_offline into a single function. See their\n * documentation for details about the semantics of online and offline modes.\n *\n * @param anjay         Anjay object to operate on.\n * @param transport_set Set of transports to put into online mode.\n *\n * @returns 0 on success, a negative value in case of error.\n */\nint anjay_transport_set_online(anjay_t *anjay,\n                               anjay_transport_set_t transport_set);\n\n/**\n * Reconnects sockets associated with all servers and ongoing downloads over the\n * specified transports. Should be called if something related to the\n * connectivity over those transports changes.\n *\n * The reconnection will be performed during the next @ref anjay_sched_run call\n * and will trigger sending any messages necessary to maintain valid\n * registration (DTLS session resumption and/or Register or Update operations).\n *\n * In case of ongoing downloads (started via @ref anjay_download or the\n * <c>fw_update</c> module), if the reconnection fails, the download will be\n * aborted with an error.\n *\n * Note: This function puts all the transports in @p transport_set into online\n * mode.\n *\n * @param anjay         Anjay object to operate on.\n * @param transport_set Set of transports whose sockets shall be reconnected.\n *\n * @returns 0 on success, a negative value in case of error.\n */\nint anjay_transport_schedule_reconnect(anjay_t *anjay,\n                                       anjay_transport_set_t transport_set);\n\n/**\n * Tests if Anjay gave up on any further server connection attempts. It will\n * happen if none of the configured servers could be reached.\n *\n * If this function returns <c>true</c>, it means that Anjay is in an\n * essentially non-operational state. @ref anjay_transport_schedule_reconnect\n * may be called to reset the failure state and retry connecting to all\n * configured servers. @ref anjay_transport_schedule_reconnect will do the same,\n * but only for the specified transports. Alternatively,\n * @ref anjay_enable_server may be used to retry connection only to a specific\n * server.\n *\n * @param anjay Anjay object to operate on.\n *\n * @returns 0 on success, a negative value in case of error.\n */\nbool anjay_all_connections_failed(anjay_t *anjay);\n\n#ifdef ANJAY_WITH_LWM2M11\ntypedef enum {\n    /**\n     * Force Queue Mode: Anjay will always register with LwM2M Servers with\n     * Queue Mode enabled, even if LwM2M 1.0 is used and server-configured\n     * Binding Mode does not contain the \"Q\" letter.\n     *\n     * Use of this setting breaks strict LwM2M 1.0 compliance, but makes sure\n     * that all idle connections will be suspended.\n     */\n    ANJAY_FORCE_QUEUE_MODE,\n\n    /**\n     * Prefer Queue Mode: Anjay will register with LwM2M Servers with Queue Mode\n     * enabled if LwM2M 1.1 is used. For LwM2M 1.0 registrations, the\n     * server-configured Binding Mode will be respected.\n     */\n    ANJAY_PREFER_QUEUE_MODE,\n\n    /**\n     * Prefer Online Mode: Anjay will use Queue Mode only when the\n     * server-configured Binding Mode contains the \"Q\" letter - either in\n     * compliance with LwM2M 1.0, or as a custom extension to LwM2M 1.1.\n     *\n     * This is the default setting.\n     */\n    ANJAY_PREFER_ONLINE_MODE,\n\n    /**\n     * Force Online Mode: Anjay will always register with LwM2M Server with\n     * Queue Mode disabled, even if LwM2M 1.0 is used and server-configured\n     * Binding Mode contains the \"Q\" letter.\n     *\n     * Use of this setting breaks strict LwM2M 1.0 compliance, but makes sure\n     * that all server connections are kept in connected state even when idle.\n     */\n    ANJAY_FORCE_ONLINE_MODE\n} anjay_queue_mode_preference_t;\n\n/**\n * Configures the client's usage of the LwM2M Queue Mode.\n *\n * Please see the documentation of @ref anjay_queue_mode_preference_t for the\n * description of available configurations. The default value is\n * <c>ANJAY_PREFER_ONLINE_MODE</c>.\n *\n * NOTE: Changing the Queue Mode status of an existing registration is not\n * possible without sending the Register (for LwM2M 1.1) or Update (for LwM2M\n * 1.0) message again. For this reason, this call does <strong>NOT</strong>\n * immediately take effect for connections that already have active\n * registrations at the time of a call to this function. The changes will be\n * applied at the time when next Register or Update message is scheduled to be\n * sent. If you wish to apply the queue mode state change immediately, you can\n * call either of @ref anjay_schedule_registration_update,\n * @ref anjay_transport_schedule_reconnect, @ref anjay_transport_exit_offline or\n * @ref anjay_transport_set_online .\n *\n *\n * @param anjay      Anjay object to operate on.\n *\n * @param preference Queue Mode preference to use.\n *\n * @returns 0 for success, or a negative value if @p preference is not any of\n *          the supported values.\n */\nint anjay_set_queue_mode_preference(anjay_t *anjay,\n                                    anjay_queue_mode_preference_t preference);\n#endif // ANJAY_WITH_LWM2M11\n\ntypedef struct {\n    /**\n     * DTLS keys or certificates.\n     */\n    avs_net_security_info_t security_info;\n\n    /**\n     * Single DANE TLSA record to use for certificate verification, if\n     * applicable.\n     */\n    const avs_net_socket_dane_tlsa_record_t *dane_tlsa_record;\n\n    /**\n     * TLS ciphersuites to use.\n     *\n     * A value with <c>num_ids == 0</c> (default) will cause defaults configured\n     * through <c>anjay_configuration_t::default_tls_ciphersuites</c>\n     * to be used.\n     */\n    avs_net_socket_tls_ciphersuites_t tls_ciphersuites;\n\n    /*\n     * Server Name Indicator to use for authenticating with the peer during\n     * secure TLS connection. The value is passed to the underlying TLS library\n     * that need to take this variable into account for it make any effect. This\n     * field is optional and can be left zero-initialized. If not set the\n     * integration layer should use the Server URI instead.\n     */\n    const char *server_name_indication;\n} anjay_security_config_t;\n\n/**\n * Queries security configuration appropriate for a specified URI.\n *\n * Given a URI, the Security object is scanned for instances with Server URI\n * resource matching it in the following way:\n * - if there is at least one instance with matching hostname, protocol and port\n *   number, and valid secure connection configuration, the first such instance\n *   (in the order as returned via @ref anjay_dm_list_instances_t) is used\n * - otherwise, if there is at least one instance with matching hostname and\n *   valid secure connection configuration, the first such instance (in the\n *   order as returned via @ref anjay_dm_list_instances_t) is used\n *\n * The returned security information is exactly the same configuration that is\n * used for LwM2M connection with the server chosen with the rules described\n * above.\n *\n * @param anjay      Anjay object whose data model shall be queried.\n *\n * @param out_config Pointer to an @ref anjay_security_config_t structure that\n *                   will be filled with the appropriate information, if found.\n *\n * @param uri        URI for which to find security configuration.\n *\n * @returns 0 for success, or a negative value in case of error, including if\n *          no suitable LwM2M Security Object instance could be found.\n *\n * <strong>NOTE:</strong> The returned structure will contain pointers to\n * buffers allocated within the @p anjay object. They will only be valid until\n * next call to <c>anjay_security_config_from_dm()</c> or @ref anjay_serve.\n * Note that this is enough for direct use in\n * @ref anjay_fw_update_get_security_config_t implementations. If you need this\n * information for a longer period, you will need to manually create a deep\n * copy.\n *\n * In particular, you may need to implement additional synchronization to\n * achieve thread safety if calling this function from multiple threads.\n * Instead, it is recommended to only call it from callback functions called\n * from within Anjay, or in scheduler jobs.\n */\nint anjay_security_config_from_dm(anjay_t *anjay,\n                                  anjay_security_config_t *out_config,\n                                  const char *uri);\n\n/**\n * Returns the security configuration that Anjay is configured to use for X.509\n * certificate-based security, if no server-specific certificate is known, but\n * PKIX certificate validation is requested.\n *\n * The returned security information is determined by the\n * <c>default_tls_ciphersuites</c>, <c>use_system_trust_store</c>,\n * <c>trust_store_certs</c> and <c>trust_store_crls</c> fields of\n * @ref anjay_configuration_t, which may be overridden by an <c>/est/crts</c>\n * request if <c>est_cacerts_policy</c> is set to\n * <c>ANJAY_EST_CACERTS_IF_EST_CONFIGURED</c> or\n * <c>ANJAY_EST_CACERTS_ALWAYS</c>.\n *\n * @returns Security configuration for the global trust store. The structure\n *          contains no dynamically allocated data.\n *\n * NOTE: Pointers in the returned structure will point to internal Anjay\n * structures. Attempting to modify or deallocate them will result in undefined\n * behavior.\n *\n * NOTE: If no valid trust store is available, an unsafe \"trust everything\"\n * configuration is returned\n * (<c>security_info.data.cert.server_cert_validation</c> is set to false).\n */\nanjay_security_config_t anjay_security_config_pkix(anjay_t *anjay);\n\n/**\n * Returns @c false if registration to all LwM2M Servers either succeeded or\n * failed with no more retries pending and @c true if a registration or\n * bootstrap is in progress.\n */\nbool anjay_ongoing_registration_exists(anjay_t *anjay);\n\ntypedef enum {\n    /**\n     * Anjay registration is valid, expiration time got from\n     * @ref anjay_registration_expiration_time_with_status informs when the\n     * registration expires.\n     */\n    ANJAY_REGISTRATION_EXPIRATION_STATUS_VALID,\n    /**\n     * Anjay registration expired, expiration time is set to\n     * <c>AVS_TIME_REAL_INVALID</c>.\n     */\n    ANJAY_REGISTRATION_EXPIRATION_STATUS_EXPIRED,\n    /**\n     * Anjay lifetime is set to infinity and expiration time is set to\n     * <c>AVS_TIME_REAL_INVALID</c>.\n     */\n    ANJAY_REGISTRATION_EXPIRATION_STATUS_INFINITE_LIFETIME,\n} anjay_registration_expiration_status_t;\n\n/**\n * Returns the time at which the lifetime of a registration to a given LwM2M\n * Server expires. Returns the registration status through an inout parameter\n * <c>out_status</c>.\n *\n * Note that this is <strong>not</strong> the time at which the Update message\n * will be sent. Update messages are planned for <c>MAX_TRANSMIT_WAIT</c> before\n * the expiration time (or at halfway between last registration update and the\n * expiration time if that is less than <c>MAX_TRANSMIT_WAIT</c>) to allow some\n * leeway to make sure that the registration is renewed strictly <em>before</em>\n * it expires.\n *\n * If you want to retrieve the information about the next scheduled Update\n * operation, please use @ref anjay_next_planned_lifecycle_operation.\n *\n * @param anjay Anjay object to operate on.\n *\n * @param ssid  Short Server ID of a non-bootstrap LwM2M Server for which to get\n *              the information.\n * @param out_status Status of expiration.\n *\n * @returns Point in time according to the real-time clock at which the\n *          registration expires, or <c>AVS_TIME_REAL_INVALID</c> if there is\n *          no active registration for a given server or no such server\n *          connection exists. <c>AVS_TIME_REAL_INVALID</c> is returned also in\n *          case of inifinite lifetime\n */\navs_time_real_t anjay_registration_expiration_time_with_status(\n        anjay_t *anjay,\n        anjay_ssid_t ssid,\n        anjay_registration_expiration_status_t *out_status);\n\n/**\n * Function replaced with anjay_registration_expiration_time_with_status(),\n * which support LwM2M 1.2.\n *\n * Returns the time at which the lifetime of a registration to a given LwM2M\n * Server expires.\n *\n * Note that this is <strong>not</strong> the time at which the Update message\n * will be sent. Update messages are planned for <c>MAX_TRANSMIT_WAIT</c> before\n * the expiration time (or at halfway between last registration update and the\n * expiration time if that is less than <c>MAX_TRANSMIT_WAIT</c>) to allow some\n * leeway to make sure that the registration is renewed strictly <em>before</em>\n * it expires.\n *\n * If you want to retrieve the information about the next scheduled Update\n * operation, please use @ref anjay_next_planned_lifecycle_operation.\n *\n * @param anjay Anjay object to operate on.\n *\n * @param ssid  Short Server ID of a non-bootstrap LwM2M Server for which to get\n *              the information.\n *\n * @returns Point in time according to the real-time clock at which the\n *          registration expires, or <c>AVS_TIME_REAL_INVALID</c> if there is\n *          no active registration for a given server or no such server\n *          connection exists.\n */\nstatic inline avs_time_real_t\nanjay_registration_expiration_time(anjay_t *anjay, anjay_ssid_t ssid) {\n    return anjay_registration_expiration_time_with_status(anjay, ssid, NULL);\n}\n\n/**\n * Returns the time at which next outgoing lifecycle message is planned to be\n * sent if no outside intervention that might reschedule it (e.g. change of\n * Lifetime - either by user code or from a server) happens until that time.\n *\n * Lifecycle operations may include:\n * - DTLS handshake\n * - for non-bootstrap LwM2M Server:\n *   - Register\n *   - Registration Update\n * - for the Bootstrap Server:\n *   - Request Bootstrap\n *   - EST Simple Re-enroll\n *\n * <strong>NOTE:</strong> This function may internally perform conversion\n * between time values attached to different clocks (real-time clock vs.\n * monotonic clock), which depends on immediate readings of those clocks. For\n * this reason, the calculated value may slightly change from call to call, even\n * if no action was performed in between. This accuracy depends on the accuracy\n * of the underlying clocks as well as CPU performance. It is generally expected\n * to be accurate within single-digit milliseconds, but it is recommended to\n * avoid code that would perform direct comparisons on those values.\n *\n * @param anjay Anjay object to operate on.\n *\n * @param ssid  Either one of:\n *              - Short Server ID of a single regular LwM2M Server for which to\n *                get the information\n *              - @ref ANJAY_SSID_BOOTSTRAP if information about the Bootstrap\n *                Server is requested\n *              - @ref ANJAY_SSID_ANY to get time of the nearest operation\n *                scheduled for any known server connection\n *\n * @returns Point in time according to the real-time clock at which some of the\n *          operations mentioned above is scheduled, or\n *          <c>AVS_TIME_REAL_INVALID</c> if there are none.\n */\navs_time_real_t anjay_next_planned_lifecycle_operation(anjay_t *anjay,\n                                                       anjay_ssid_t ssid);\n\n/**\n * Returns the time at which next outgoing lifecycle message is planned to be\n * sent via any of the given transports if no outside intervention that might\n * reschedule it (e.g. change of Lifetime - either by user code or from a\n * server) happens until that time.\n *\n * <c>anjay_transport_next_planned_lifecycle_operation(anjay,\n * ANJAY_TRANSPORT_SET_ALL)</c> is mostly equivalent to\n * <c>anjay_next_planned_lifecycle_operation(anjay, ANJAY_SSID_ANY)</c> (see\n * below for more details). Different transport sets may be used to filter the\n * set of server connections queried to only those for which the last known\n * transport matches the provided set.\n *\n * Server connection entries which have not yet been matched to any transport\n * are ignored. Such entries may exist for a short time between refreshing the\n * list of known servers (triggered e.g. by adding a new instance to the\n * Security or Server object) and the actual attempt to connect to that server,\n * as those two actions are performed in separate runs of @ref anjay_sched_run.\n * During that brief period when such entries exist,\n * <c>anjay_next_planned_lifecycle_operation(anjay, ANJAY_SSID_ANY)</c> will\n * return the current time, while\n * <c>anjay_transport_next_planned_lifecycle_operation(anjay,\n * ANJAY_TRANSPORT_SET_ALL)</c> may return time of some later planned operation\n * or <c>AVS_TIME_REAL_INVALID</c>.\n *\n * @param anjay         Anjay object to operate on.\n *\n * @param transport_set Set of transports to include in calculation.\n *\n * @returns Point in time according to the real-time clock at which some\n *          lifecycle operation is scheduled, or <c>AVS_TIME_REAL_INVALID</c>\n *          if there are none.\n */\navs_time_real_t anjay_transport_next_planned_lifecycle_operation(\n        anjay_t *anjay, anjay_transport_set_t transport_set);\n\n/**\n * Returns the time at which the earliest notification trigger is scheduled if\n * no outside intervention that might reschedule it (e.g. @ref\n * anjay_notify_changed or a Write Attributes request from a server) happens\n * until that time.\n *\n * Notification trigger checks value of one or more resources and might send a\n * notification (if the resource value actually changed and other criteria\n * specified by the &lt;NOTIFICATION&gt; Class attributes are met), but it might\n * also not cause any action. In this sense, the time point returned by this\n * function is the earliest point in time at which a notification MAY be\n * generated, i.e. the lower bound estimate for time of the nearest\n * notification.\n *\n * Notification triggers may be scheduled:\n * - by @ref anjay_notify_changed (it might not be scheduled for immediate\n *   execution if the Minimum Period attribute is in effect)\n * - by certain requests from a server, including Write, Write Composite, Write\n *   Attributes and Bootstrap Finish\n * - after generating every new notification, if Maximum Period is in effect\n *   (see @ref anjay_next_planned_pmax_notify_trigger that only reports this\n *   case)\n *\n * The point in time is calculated for one specific server, or for all known\n * servers at once. In versions that include the SMS commercial feature, only\n * the primary connections are included (including the case where SMS transport\n * is the only one in use for a given Server Account), secondary SMS Trigger\n * connections are not.\n *\n * @param anjay Anjay object to operate on.\n *\n * @param ssid  A Short Server ID of a single regular LwM2M Server for which to\n *              get the information, or @ref ANJAY_SSID_ANY to get time of the\n *              nearest notification trigger scheduled for any known server\n *              connection.\n *\n * @returns Point in time according to the real-time clock at which the earliest\n *          notification trigger is scheduled, or <c>AVS_TIME_REAL_INVALID</c>\n *          if there are none.\n */\navs_time_real_t anjay_next_planned_notify_trigger(anjay_t *anjay,\n                                                  anjay_ssid_t ssid);\n\n/**\n * Returns the time at which the earliest notification trigger based on the\n * Maximum Period attribute is scheduled if no outside intervention that might\n * reschedule it (e.g. @ref anjay_notify_changed or a Write Attributes request\n * from a server) happens until that time.\n *\n * This type of notification triggers are scheduled after sending each\n * notification, for after a period of time specified by the Maximum Period\n * attribute. In this sense, the time point returned by this function is the\n * earliest point in time at which a notification is REQUIRED to be generated,\n * i.e. the upper bound estimate for time of a the nearest notification.\n *\n * The point in time is calculated for one specific server, or for all known\n * servers at once. In versions that include the SMS commercial feature, only\n * the primary connections are included (including the case where SMS transport\n * is the only one in use for a given Server Account), secondary SMS Trigger\n * connections are not.\n *\n * @param anjay Anjay object to operate on.\n *\n * @param ssid  A Short Server ID of a single regular LwM2M Server for which to\n *              get the information, or @ref ANJAY_SSID_ANY to get time of the\n *              nearest notification trigger based on the Maximum Period\n *              attribute scheduled for any known server connection.\n *\n * @returns Point in time according to the real-time clock at which the earliest\n *          notification trigger based on the Maximum Period attribute is\n *          scheduled, or <c>AVS_TIME_REAL_INVALID</c> if there are none.\n */\navs_time_real_t anjay_next_planned_pmax_notify_trigger(anjay_t *anjay,\n                                                       anjay_ssid_t ssid);\n\n/**\n * Returns the time at which the earliest notification trigger is scheduled for\n * any of the given transports if no outside intervention that might reschedule\n * it (e.g. @ref anjay_notify_changed or a Write Attributes request from a\n * server) happens until that time.\n *\n * In versions that do not include the SMS commercial feature, or if no SMS\n * trigger connections exist,\n * <c>anjay_transport_next_planned_notify_trigger(anjay,\n * ANJAY_TRANSPORT_SET_ALL)</c> is equivalent to\n * <c>anjay_next_planned_notify_trigger(anjay, ANJAY_SSID_ANY)</c>. In contrast\n * to that function, both primary and secondary (SMS trigger) connections are\n * queried, if present. Different transport sets may be used to filter the set\n * of server connections queried to only those for which the connection\n * transport matches the provided set.\n *\n * @param anjay         Anjay object to operate on.\n *\n * @param transport_set Set of transports to include in calculation.\n *\n * @returns Point in time according to the real-time clock at which the earliest\n *          notification trigger is scheduled, or <c>AVS_TIME_REAL_INVALID</c>\n *          if there are none.\n */\navs_time_real_t anjay_transport_next_planned_notify_trigger(\n        anjay_t *anjay, anjay_transport_set_t transport_set);\n\n/**\n * Returns the time at which the earliest notification trigger based on the\n * Maximum Period attribute is scheduled for any given transports if no outside\n * intervention that might reschedule it (e.g. @ref anjay_notify_changed or a\n * Write Attributes request from a server) happens until that time.\n *\n * In versions that do not include the SMS commercial feature, or if no SMS\n * trigger connections exist,\n * <c>anjay_transport_next_planned_pmax_notify_trigger(anjay,\n * ANJAY_TRANSPORT_SET_ALL)</c> is equivalent to\n * <c>anjay_next_planned_pmax_notify_trigger(anjay, ANJAY_SSID_ANY)</c>. In\n * contrast to that function, both primary and secondary (SMS trigger)\n * connections are queried, if present. Different transport sets may be used to\n * filter the set of server connections queried to only those for which the\n * connection transport matches the provided set.\n *\n * @param anjay         Anjay object to operate on.\n *\n * @param transport_set Set of transports to include in calculation.\n *\n * @returns Point in time according to the real-time clock at which the earliest\n *          notification trigger based on the Maximum Period attribute is\n *          scheduled, or <c>AVS_TIME_REAL_INVALID</c> if there are none.\n */\navs_time_real_t anjay_transport_next_planned_pmax_notify_trigger(\n        anjay_t *anjay, anjay_transport_set_t transport_set);\n\n/**\n * Returns whether there are some notifications which have been postponed to be\n * sent later, either due to the connection being offline or a previous failure\n * to send some notification.\n *\n * The check is performed for one specific server, or for all known servers at\n * once. In versions that do not include the SMS commercial feature, only the\n * primary connections are included (including the case where SMS transport is\n * the only one in use for a given Server Account), secondary SMS Trigger\n * connections are not.\n *\n * @param anjay Anjay object to operate on.\n *\n * @param ssid  A Short Server ID of a single regular LwM2M Server for which to\n *              get the information, or @ref ANJAY_SSID_ANY to check whether\n *              unsent notifications exist for any known server connection.\n *\n * @returns False if all generated notifications for given server(s) have been\n *          either successfully sent, discarded or are being sent at the moment.\n *          True otherwise, i.e. if unsent, postponed notifications exist in\n *          memory.\n */\nbool anjay_has_unsent_notifications(anjay_t *anjay, anjay_ssid_t ssid);\n\n/**\n * Returns whether there are some notifications which have been postponed to be\n * sent later on any of the given transports, either due to the connection being\n * offline or a previous failure to send some notification.\n *\n * In versions that do not include the SMS commercial feature, or if no SMS\n * trigger connections exist,\n * <c>anjay_transport_has_unsent_notifications(anjay,\n * ANJAY_TRANSPORT_SET_ALL)</c> is equivalent to\n * <c>anjay_has_unsent_notifications(anjay, ANJAY_SSID_ANY)</c>. In contrast to\n * that function, both primary and secondary (SMS trigger) connections are\n * queried, if present. Different transport sets may be used to filter the set\n * of server connections queried to only those for which the connection\n * transport matches the provided set.\n *\n * @param anjay         Anjay object to operate on.\n *\n * @param transport_set Set of transports to include in the check.\n *\n * @returns False if all generated notifications for given transports(s) have\n *          been either successfully sent, discarded or are being sent at the\n *          moment. True otherwise, i.e. if unsent, postponed notifications\n *          exist in memory.\n */\nbool anjay_transport_has_unsent_notifications(\n        anjay_t *anjay, anjay_transport_set_t transport_set);\n\n/**\n * Changes transmission parameters for given transports.\n *\n * @param anjay         Anjay object to operate on.\n *\n * @param transport_set Set of transports for which parameters should be\n *                      changed.\n *\n * @param tx_params     New transmission parameters.\n *\n * @returns AVS_OK in case of success (parameters have been changed for at least\n *          one transport), or an error code.\n */\navs_error_t\nanjay_update_transport_tx_params(anjay_t *anjay,\n                                 anjay_transport_set_t transport_set,\n                                 const avs_coap_udp_tx_params_t *tx_params);\n\n/**\n * Changes the CoAP exchange update timeout for given transports.\n *\n * @param anjay              Anjay object to operate on.\n *\n * @param transport_set      Set of transport for which the exchange update\n *                           timeout should be changed.\n *\n * @param exchange_timeout   New exchange update timeout.\n *\n * @returns AVS_OK in case of success (update timeout has been changed for at\n *          least one transport), or an error code.\n */\navs_error_t\nanjay_update_coap_exchange_timeout(anjay_t *anjay,\n                                   anjay_transport_set_t transport_set,\n                                   avs_time_duration_t exchange_timeout);\n\n/**\n * Changes handshake timeouts for sockets that communicate using DTLS over UDP.\n *\n * @param anjay                   Anjay object to operate on.\n *\n * @param dtls_handshake_timeouts New DTLS handshake timeouts.\n *\n * @returns AVS_OK in case of success, or an error code.\n */\navs_error_t anjay_update_dtls_handshake_timeouts(\n        anjay_t *anjay,\n        avs_net_dtls_handshake_timeouts_t dtls_handshake_timeouts);\n\n#ifdef ANJAY_WITH_COMMUNICATION_TIMESTAMP_API\n/**\n * Gets the time at which the client has registered successfully to a given\n * LwM2M server for the last time.\n *\n * @param anjay         Anjay object to operate on.\n *\n * @param ssid          A Short Server ID of a single regular LwM2M Server for\n *                      which to get the information, or @ref ANJAY_SSID_ANY to\n *                      get the time of the last successful registration to any\n *                      known server.\n *\n * @param[out] out_time Non-NULL pointer to an @ref avs_time_real_t structure.\n *                      The structure will be filled with information about a\n *                      point in time according to the real-time clock at which\n *                      the last registration happened, or\n *                      <c>AVS_TIME_REAL_INVALID</c> if there was no successful\n *                      registration to a given server.\n *\n * @returns AVS_OK on success, or an error code.\n */\navs_error_t anjay_get_server_last_registration_time(anjay_t *anjay,\n                                                    anjay_ssid_t ssid,\n                                                    avs_time_real_t *out_time);\n\n/**\n * Gets the time at which next registration update operation with a given\n * LwM2M Server is scheduled.\n *\n * <strong>NOTE:</strong> This function may internally perform conversion\n * between time values attached to different clocks (real-time clock vs.\n * monotonic clock), which depends on immediate readings of those clocks. For\n * this reason, the calculated value may slightly change from call to call, even\n * if no action was performed in between. This accuracy depends on the accuracy\n * of the underlying clocks as well as CPU performance. It is generally expected\n * to be accurate within single-digit milliseconds, but it is recommended to\n * avoid code that would perform direct comparisons on those values.\n *\n * @param anjay         Anjay object to operate on.\n *\n * @param ssid          A Short Server ID of a single regular LwM2M Server for\n *                      which to get the information, or @ref ANJAY_SSID_ANY to\n *                      get the time of the closes registration update operation\n *                      to any known server.\n *\n * @param[out] out_time Non-NULL pointer to an @ref avs_time_real_t structure.\n *                      The structure will be filled with information about a\n *                      point in time according to the real-time clock at which\n *                      next registration update operation is scheduled, or\n *                      <c>AVS_TIME_REAL_INVALID</c> if a registration update\n *                      operation isn't scheduled for a given SSID.\n *\n * @returns AVS_OK on success, or an error code.\n */\navs_error_t anjay_get_server_next_update_time(anjay_t *anjay,\n                                              anjay_ssid_t ssid,\n                                              avs_time_real_t *out_time);\n/**\n * Gets the time at which the client has communicated with a given LwM2M Server\n * for the last time.\n *\n * @param anjay         Anjay object to operate on.\n *\n * @param ssid          A Short Server ID of a single regular LwM2M Server for\n *                      which to get the information, or @ref ANJAY_SSID_ANY to\n *                      get the time of last communication attempt to any known\n *                      server.\n *\n * @param[out] out_time Non-NULL pointer to an @ref avs_time_real_t structure.\n *                      The structure will be filled with information about a\n *                      point in time according to the real-time clock at which\n *                      the last communication happened, or\n *                      <c>AVS_TIME_REAL_INVALID</c> if there was no\n *                      communication attempt with a given server.\n *\n * @returns AVS_OK on success, or an error code.\n */\navs_error_t anjay_get_server_last_communication_time(anjay_t *anjay,\n                                                     anjay_ssid_t ssid,\n                                                     avs_time_real_t *out_time);\n#endif // ANJAY_WITH_COMMUNICATION_TIMESTAMP_API\n\n#ifdef __cplusplus\n} /* extern \"C\" */\n#endif\n\n#endif /*ANJAY_INCLUDE_ANJAY_CORE_H*/\n"
  },
  {
    "path": "include_public/anjay/dm.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ANJAY_INCLUDE_ANJAY_DM_H\n#define ANJAY_INCLUDE_ANJAY_DM_H\n\n#include <math.h>\n#include <stdint.h>\n\n#include <anjay/io.h>\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n#define ANJAY_DM_OID_SECURITY 0\n#define ANJAY_DM_OID_SERVER 1\n#define ANJAY_DM_OID_ACCESS_CONTROL 2\n#define ANJAY_DM_OID_DEVICE 3\n#define ANJAY_DM_OID_FIRMWARE_UPDATE 5\n#ifdef ANJAY_WITH_LWM2M_GATEWAY\n#    define ANJAY_DM_OID_LWM2M_GATEWAY 25\n#endif // ANJAY_WITH_LWM2M_GATEWAY\n\ntypedef struct anjay_dm_object_def_struct anjay_dm_object_def_t;\n\n/** Values for the con attribute. */\ntypedef enum {\n    ANJAY_DM_CON_ATTR_NONE = -1,\n    ANJAY_DM_CON_ATTR_NON = 0,\n    ANJAY_DM_CON_ATTR_CON = 1\n} anjay_dm_con_attr_t;\n\n#ifdef ANJAY_WITH_LWM2M12\n/** Values for the edge attribute. */\ntypedef enum {\n    ANJAY_DM_EDGE_ATTR_NONE = -1,\n    ANJAY_DM_EDGE_ATTR_FALLING = 0,\n    ANJAY_DM_EDGE_ATTR_RISING = 1\n} anjay_dm_edge_attr_t;\n#endif // ANJAY_WITH_LWM2M12\n\n/** Object/Object Instance Attributes */\ntypedef struct {\n    /** Minimum Period as defined by LwM2M spec */\n    int32_t min_period;\n    /** Maximum Period as defined by LwM2M spec */\n    int32_t max_period;\n    /** Minimum Evaluation Period as defined by LwM2M spec */\n    int32_t min_eval_period;\n    /** Maximum Evaluation Period as defined by LwM2M spec */\n    int32_t max_eval_period;\n#ifdef ANJAY_WITH_CON_ATTR\n    /** Confirmable Notification as defined by LwM2M spec */\n    anjay_dm_con_attr_t con;\n#endif // ANJAY_WITH_CON_ATTR\n#ifdef ANJAY_WITH_LWM2M12\n    /** Maximum Historical Queue as defined by LwM2M spec */\n    int32_t hqmax;\n#endif // ANJAY_WITH_LWM2M12\n} anjay_dm_oi_attributes_t;\n\n/** Resource attributes. */\ntypedef struct {\n    /** Attributes shared with Objects/Object Instances */\n    anjay_dm_oi_attributes_t common;\n    /** Greater Than attribute as defined by LwM2M spec */\n    double greater_than;\n    /** Less Than attribute as defined by LwM2M spec */\n    double less_than;\n    /** Step attribute as defined by LwM2M spec */\n    double step;\n#ifdef ANJAY_WITH_LWM2M12\n    /** Edge attribute as defined by LwM2M spec */\n    anjay_dm_edge_attr_t edge;\n#endif // ANJAY_WITH_LWM2M12\n} anjay_dm_r_attributes_t;\n\n/** A value indicating that the Min/Max Period or Maximum Historical Queue\n * attribute is not set */\n#define ANJAY_ATTRIB_INTEGER_NONE (-1)\n\n/** An alias for @ref ANJAY_ATTRIB_INTEGER_NONE */\n#define ANJAY_ATTRIB_PERIOD_NONE ANJAY_ATTRIB_INTEGER_NONE\n\n/** A value indicating that the Less Than/Greater Than/Step attribute\n * is not set */\n#define ANJAY_ATTRIB_DOUBLE_NONE (NAN)\n\n/** An alias for @ref ANJAY_ATTRIB_DOUBLE_NONE */\n#define ANJAY_ATTRIB_VALUE_NONE ANJAY_ATTRIB_DOUBLE_NONE\n\n/** Convenience Object/Object Instance attributes constant, filled with\n * \"attribute not set\" values */\nextern const anjay_dm_oi_attributes_t ANJAY_DM_OI_ATTRIBUTES_EMPTY;\n\n/** Convenience Resource attributes constant, filled with\n * \"attribute not set\" values */\nextern const anjay_dm_r_attributes_t ANJAY_DM_R_ATTRIBUTES_EMPTY;\n\n/**\n * A handler that returns default attribute values set for the Object.\n *\n * @param      anjay   Anjay object to operate on.\n * @param      obj_ptr Object definition pointer, as passed to\n *                     @ref anjay_register_object .\n * @param      ssid    Short Server ID of the server requesting the operation.\n * @param[out] out     Attributes struct to be filled by the handler.\n *\n * @returns This handler should return:\n * - 0 on success,\n * - a negative value in case of error. If it returns one of ANJAY_ERR_\n *   constants, the response message will have an appropriate CoAP response\n *   code. Otherwise, the device will respond with an unspecified (but valid)\n *   error code.\n */\ntypedef int anjay_dm_object_read_default_attrs_t(\n        anjay_t *anjay,\n        const anjay_dm_object_def_t *const *obj_ptr,\n        anjay_ssid_t ssid,\n        anjay_dm_oi_attributes_t *out);\n\n/**\n * A handler that sets default attribute values for the Object.\n *\n * @param anjay   Anjay object to operate on.\n * @param obj_ptr Object definition pointer, as passed to\n *                @ref anjay_register_object .\n * @param ssid    Short Server ID of the server requesting the operation.\n * @param attrs   Attributes struct to be set for the Object.\n *\n * @returns This handler should return:\n * - 0 on success,\n * - a negative value in case of error. If it returns one of ANJAY_ERR_\n *   constants, the response message will have an appropriate CoAP response\n *   code. Otherwise, the device will respond with an unspecified (but valid)\n *   error code.\n */\ntypedef int anjay_dm_object_write_default_attrs_t(\n        anjay_t *anjay,\n        const anjay_dm_object_def_t *const *obj_ptr,\n        anjay_ssid_t ssid,\n        const anjay_dm_oi_attributes_t *attrs);\n\n/**\n * A handler that enumerates all Object Instances for the Object.\n *\n * The library will not attempt to call @ref anjay_dm_instance_remove_t or\n * @ref anjay_dm_instance_create_t handlers inside the @ref anjay_dm_emit calls\n * performed from this handler, so the implementation is free to use iteration\n * state that would be invalidated by such calls.\n *\n * CAUTION: Aside from the note above, the library MAY call other data model\n * handlers for the same Object from within the @ref anjay_dm_emit call. Please\n * make sure that your code is able to handle this - e.g. avoid calling\n * @ref anjay_dm_emit with a non-recursive object-scope mutex locked.\n *\n * @param anjay   Anjay object to operate on.\n * @param obj_ptr Object definition pointer, as passed to\n *                @ref anjay_register_object .\n * @param ctx     Context through which the Instance IDs shall be returned, see\n *                @ref anjay_dm_emit .\n *\n * Instance listing handlers MUST always return Instance IDs in a strictly\n * ascending, sorted order. Failure to do so will result in an error being sent\n * to the LwM2M server or passed down to internal routines that called this\n * handler.\n *\n * @returns This handler should return:\n * - 0 on success,\n * - a negative value in case of error. If it returns one of ANJAY_ERR_\n *   constants, the response message will have an appropriate CoAP response\n *   code. Otherwise, the device will respond with an unspecified (but valid)\n *   error code.\n */\ntypedef int\nanjay_dm_list_instances_t(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr,\n                          anjay_dm_list_ctx_t *ctx);\n\n/**\n * Convenience function to use as the list_instances handler in Single Instance\n * objects.\n *\n * Implements a valid iteration that returns a single Instance ID: 0.\n */\nint anjay_dm_list_instances_SINGLE(anjay_t *anjay,\n                                   const anjay_dm_object_def_t *const *obj_ptr,\n                                   anjay_dm_list_ctx_t *ctx);\n\n/**\n * A handler that shall reset Object Instance to its default (after creational)\n * state.\n *\n * Note: if this handler is not implemented, then non-partial write on the\n * Object Instance (@p iid) will not succeed.\n *\n * @param anjay     Anjay Object to operate on.\n * @param obj_ptr   Object definition pointer, as passed to\n *                  @ref anjay_register_object .\n * @param iid       Instance ID to reset.\n *\n * @return This handler should return:\n * - 0 on success,\n * - a negative value in case of error. If it returns one of ANJAY_ERR_\n *   constants, the response message will have an appropriate CoAP response\n *   code. Otherwise, the device will respond with an unspecified (but valid)\n *   error code.\n */\ntypedef int\nanjay_dm_instance_reset_t(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr,\n                          anjay_iid_t iid);\n\n/**\n * A handler that removes an Object Instance with given Instance ID.\n *\n * @param anjay   Anjay object to operate on.\n * @param obj_ptr Object definition pointer, as passed to\n *                @ref anjay_register_object .\n * @param iid     Checked Object Instance ID.\n *\n * @returns This handler should return:\n * - 0 on success,\n * - a negative value in case of error. If it returns one of ANJAY_ERR_\n *   constants, the response message will have an appropriate CoAP response\n *   code. Otherwise, the device will respond with an unspecified (but valid)\n *   error code.\n */\ntypedef int\nanjay_dm_instance_remove_t(anjay_t *anjay,\n                           const anjay_dm_object_def_t *const *obj_ptr,\n                           anjay_iid_t iid);\n\n/**\n * A handler that creates an Object Instance.\n *\n * @param anjay   Anjay object to operate on.\n * @param obj_ptr Object definition pointer, as passed to\n *                @ref anjay_register_object .\n * @param iid     Instance ID to create, chosen either by the server or the\n *                library. An ID that has been previously checked (using\n *                @ref anjay_dm_list_instances_t) to not be PRESENT is\n *                guaranteed to be passed.\n *\n * @returns This handler should return:\n * - 0 on success,\n * - a negative value in case of error. If it returns one of ANJAY_ERR_\n *   constants, the response message will have an appropriate CoAP response\n *   code. Otherwise, the device will respond with an unspecified (but valid)\n *   error code.\n */\ntypedef int\nanjay_dm_instance_create_t(anjay_t *anjay,\n                           const anjay_dm_object_def_t *const *obj_ptr,\n                           anjay_iid_t iid);\n\n/**\n * A handler that returns default attributes set for the Object Instance.\n *\n * @param      anjay   Anjay object to operate on.\n * @param      obj_ptr Object definition pointer, as passed to\n *                     @ref anjay_register_object .\n * @param      iid     Checked Object Instance ID.\n * @param      ssid    Short Server ID of the server requesting the operation.\n * @param[out] out     Returned attributes.\n *\n * @returns This handler should return:\n * - 0 on success,\n * - a negative value in case of error. If it returns one of ANJAY_ERR_\n *   constants, the response message will have an appropriate CoAP response\n *   code. Otherwise, the device will respond with an unspecified (but valid)\n *   error code.\n */\ntypedef int anjay_dm_instance_read_default_attrs_t(\n        anjay_t *anjay,\n        const anjay_dm_object_def_t *const *obj_ptr,\n        anjay_iid_t iid,\n        anjay_ssid_t ssid,\n        anjay_dm_oi_attributes_t *out);\n\n/**\n * A handler that sets default attributes for the Object Instance.\n *\n * @param anjay   Anjay object to operate on.\n * @param obj_ptr Object definition pointer, as passed to\n *                @ref anjay_register_object .\n * @param iid     Checked Object Instance ID.\n * @param ssid    Short Server ID of the server requesting the operation.\n * @param attrs   Attributes to set for the Object Instance.\n *\n * @returns This handler should return:\n * - 0 on success,\n * - a negative value in case of error. If it returns one of ANJAY_ERR_\n *   constants, the response message will have an appropriate CoAP response\n *   code. Otherwise, the device will respond with an unspecified (but valid)\n *   error code.\n */\ntypedef int anjay_dm_instance_write_default_attrs_t(\n        anjay_t *anjay,\n        const anjay_dm_object_def_t *const *obj_ptr,\n        anjay_iid_t iid,\n        anjay_ssid_t ssid,\n        const anjay_dm_oi_attributes_t *attrs);\n\n/**\n * A handler that enumerates SUPPORTED Resources for an Object Instance, called\n * only if the Object Instance is PRESENT (has recently been returned via\n * @ref anjay_dm_list_instances_t).\n *\n * CAUTION: The library MAY call other data model handlers for the same Object\n * from within the @ref anjay_dm_emit_res call. Please make sure that your code\n * is able to handle this - e.g. avoid calling @ref anjay_dm_emit_res with\n * a non-recursive object-scope mutex locked.\n *\n * @param anjay   Anjay object to operate on.\n * @param obj_ptr Object definition pointer, as passed to\n *                @ref anjay_register_object .\n * @param iid     Object Instance ID.\n * @param ctx     Context through which the Resource IDs shall be returned, see\n *                @ref anjay_dm_emit_res .\n *\n * Resource listing handlers MUST always return Resource IDs in a strictly\n * ascending, sorted order. Failure to do so will result in an error being sent\n * to the LwM2M server or passed down to internal routines that called this\n * handler.\n *\n * @returns This handler should return:\n * - 0 on success,\n * - a negative value in case of error. If it returns one of ANJAY_ERR_\n *   constants, the response message will have an appropriate CoAP response\n *   code. Otherwise, the device will respond with an unspecified (but valid)\n *   error code.\n */\ntypedef int\nanjay_dm_list_resources_t(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr,\n                          anjay_iid_t iid,\n                          anjay_dm_resource_list_ctx_t *ctx);\n\n/**\n * A handler that reads the Resource or Resource Instance value, called only if\n * the Resource is PRESENT and is one of the @ref ANJAY_DM_RES_R,\n * @ref ANJAY_DM_RES_RW, @ref ANJAY_DM_RES_RM or @ref ANJAY_DM_RES_RWM kinds (as\n * returned by @ref anjay_dm_list_resources_t).\n *\n * @param anjay   Anjay object to operate on.\n * @param obj_ptr Object definition pointer, as passed to\n *                @ref anjay_register_object .\n * @param iid     Object Instance ID.\n * @param rid     Resource ID.\n * @param riid    Resource Instance ID, or @ref ANJAY_ID_INVALID in case of a\n *                Single Resource.\n * @param ctx     Output context to write the resource value to using the\n *                <c>anjay_ret_*</c> function family.\n *\n * NOTE: One of the <c>anjay_ret_*</c> functions <strong>MUST</strong> be called\n * in this handler before returning successfully. Failure to do so will result\n * in 5.00 Internal Server Error being sent to the server.\n *\n * NOTE: This handler will only be called with @p riid set to a valid value if\n * the Resource Instance is PRESENT (has recently been returned via\n * @ref anjay_dm_list_resource_instances_t).\n *\n * @returns This handler should return:\n * - 0 on success,\n * - a negative value in case of error. If it returns one of ANJAY_ERR_\n *   constants, it will be used as a hint for the CoAP response code to use. The\n *   library may decide to override the returned value in case of a more\n *   specific internal error (e.g. 4.06 Not Acceptable in response to an invalid\n *   Accept option).\n *\n *   Note that the CoAP response sent by the library will always be valid.\n *   If the value returned is a negative number that is not any of the\n *   ANJAY_ERR_ constant, the normal fallback response is\n *   5.00 Internal Server Error.\n */\ntypedef int\nanjay_dm_resource_read_t(anjay_t *anjay,\n                         const anjay_dm_object_def_t *const *obj_ptr,\n                         anjay_iid_t iid,\n                         anjay_rid_t rid,\n                         anjay_riid_t riid,\n                         anjay_output_ctx_t *ctx);\n\n/**\n * A handler that writes the Resource value, called only if the Resource is\n * SUPPORTED and not of the @ref ANJAY_DM_RES_E kind (as returned by\n * @ref anjay_dm_list_resources_t). Note that it may be called on nominally\n * read-only Resources if the write is performed by the Bootstrap Server.\n *\n * @param anjay   Anjay object to operate on.\n * @param obj_ptr Object definition pointer, as passed to\n *                @ref anjay_register_object .\n * @param iid     Object Instance ID.\n * @param rid     Resource ID.\n * @param riid    Resource Instance ID, or @ref ANJAY_ID_INVALID in case of a\n *                Single Resource.\n * @param ctx     Input context to read the resource value from using the\n *                anjay_get_* function family.\n *\n * NOTE: This handler will only be called with @p riid set to a valid value if\n * the Resource has been verified to be a Multiple Resource (as returned by\n * @ref anjay_dm_list_resources_t).\n *\n * @returns This handler should return:\n * - 0 on success,\n * - a negative value in case of error. If it returns one of ANJAY_ERR_\n *   constants, the response message will have an appropriate CoAP response\n *   code. Otherwise, the device will respond with an unspecified (but valid)\n *   error code.\n */\ntypedef int\nanjay_dm_resource_write_t(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr,\n                          anjay_iid_t iid,\n                          anjay_rid_t rid,\n                          anjay_riid_t riid,\n                          anjay_input_ctx_t *ctx);\n\n/**\n * A handler that performs the Execute action on given Resource, called only if\n * the Resource is PRESENT and of the @ref ANJAY_DM_RES_E kind (as returned by\n * @ref anjay_dm_list_resources_t).\n *\n * @param anjay   Anjay object to operate on.\n * @param obj_ptr Object definition pointer, as passed to\n *                @ref anjay_register_object .\n * @param iid     Object Instance ID.\n * @param rid     Resource ID.\n * @param ctx     Execute context to read the execution arguments from, using\n * the anjay_execute_get_* function family.\n *\n * @returns This handler should return:\n * - 0 on success,\n * - a negative value in case of error. If it returns one of ANJAY_ERR_\n *   constants, the response message will have an appropriate CoAP response\n *   code. Otherwise, the device will respond with an unspecified (but valid)\n *   error code.\n */\ntypedef int\nanjay_dm_resource_execute_t(anjay_t *anjay,\n                            const anjay_dm_object_def_t *const *obj_ptr,\n                            anjay_iid_t iid,\n                            anjay_rid_t rid,\n                            anjay_execute_ctx_t *ctx);\n\n/**\n * A handler that shall reset a Resource to its default (after creational)\n * state. In particular, for any writeable optional resource, it shall remove\n * it; for any writeable mandatory Multiple Resource, it shall remove all its\n * instances.\n *\n * NOTE: If this handler is not implemented for a Multiple Resource, then\n * non-partial write on it will not succeed.\n *\n * NOTE: In the current version of Anjay, this handler is only ever called on\n * Multiple Resources. It is REQUIRED so that after calling this handler, any\n * Multiple Resource is either not PRESENT, or PRESENT, but contain zero\n * Resource Instances.\n *\n * @param anjay     Anjay Object to operate on.\n * @param obj_ptr   Object definition pointer, as passed to\n *                  @ref anjay_register_object .\n * @param iid       Object Instance ID.\n * @param rid       ID of the Resource to reset.\n *\n * @return This handler should return:\n * - 0 on success,\n * - a negative value in case of error. If it returns one of ANJAY_ERR_\n *   constants, the response message will have an appropriate CoAP response\n *   code. Otherwise, the device will respond with an unspecified (but valid)\n *   error code.\n */\ntypedef int\nanjay_dm_resource_reset_t(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr,\n                          anjay_iid_t iid,\n                          anjay_rid_t rid);\n\n/**\n * A handler that enumerates all Resource Instances of a Multiple Resource,\n * called only if the Resource is PRESENT and is of either @ref ANJAY_DM_RES_RM,\n * @ref ANJAY_DM_RES_WM or @ref ANJAY_DM_RES_RWM kind (as returned by\n * @ref anjay_dm_list_resources_t).\n *\n * The library will not attempt to call @ref anjay_dm_resource_write_t or\n * @ref anjay_dm_resource_reset_t handlers inside the @ref anjay_dm_emit calls\n * performed from this handler, so the implementation is free to use iteration\n * state that would be invalidated by such calls.\n *\n * CAUTION: Aside from the note above, the library MAY call other data model\n * handlers for the same Object from within the @ref anjay_dm_emit call. Please\n * make sure that your code is able to handle this - e.g. avoid calling\n * @ref anjay_dm_emit with a non-recursive object-scope mutex locked.\n *\n * @param anjay   Anjay object to operate on.\n * @param obj_ptr Object definition pointer, as passed to\n *                @ref anjay_register_object .\n * @param iid     Object Instance ID.\n * @param rid     Resource ID.\n * @param ctx     Context through which the Resource Instance IDs shall be\n *                returned, see @ref anjay_dm_emit .\n *\n * Resource instance listing handlers MUST always return Resource Instance IDs\n * in a strictly ascending, sorted order. Failure to do so will result in an\n * error being sent to the LwM2M server or passed down to internal routines that\n * called this handler.\n *\n * @returns This handler should return:\n * - 0 on success,\n * - a negative value in case of error. If it returns one of ANJAY_ERR_\n *   constants, the response message will have an appropriate CoAP response\n *   code. Otherwise, the device will respond with an unspecified (but valid)\n *   error code.\n */\ntypedef int\nanjay_dm_list_resource_instances_t(anjay_t *anjay,\n                                   const anjay_dm_object_def_t *const *obj_ptr,\n                                   anjay_iid_t iid,\n                                   anjay_rid_t rid,\n                                   anjay_dm_list_ctx_t *ctx);\n\n/**\n * A handler that returns Resource attributes.\n *\n * @param      anjay   Anjay object to operate on.\n * @param      obj_ptr Object definition pointer, as passed to\n *                     @ref anjay_register_object .\n * @param      iid     Object Instance ID.\n * @param      rid     Resource ID.\n * @param      ssid    Short Server ID of the LwM2M Server issuing the request.\n * @param[out] out     Returned Resource attributes.\n *\n * @returns This handler should return:\n * - 0 on success,\n * - a negative value in case of error. If it returns one of ANJAY_ERR_\n *   constants, the response message will have an appropriate CoAP response\n *   code. Otherwise, the device will respond with an unspecified (but valid)\n *   error code.\n */\ntypedef int\nanjay_dm_resource_read_attrs_t(anjay_t *anjay,\n                               const anjay_dm_object_def_t *const *obj_ptr,\n                               anjay_iid_t iid,\n                               anjay_rid_t rid,\n                               anjay_ssid_t ssid,\n                               anjay_dm_r_attributes_t *out);\n\n/**\n * A handler that sets attributes for given Resource.\n *\n * @param anjay   Anjay object to operate on.\n * @param obj_ptr Object definition pointer, as passed to\n *                @ref anjay_register_object .\n * @param iid     Object Instance ID.\n * @param rid     Resource ID.\n * @param ssid    Short Server ID of the LwM2M Server issuing the request.\n * @param attrs   Attributes to set for this Resource.\n *\n * @returns This handler should return:\n * - 0 on success,\n * - a negative value in case of error. If it returns one of ANJAY_ERR_\n *   constants, the response message will have an appropriate CoAP response\n *   code. Otherwise, the device will respond with an unspecified (but valid)\n *   error code.\n */\ntypedef int\nanjay_dm_resource_write_attrs_t(anjay_t *anjay,\n                                const anjay_dm_object_def_t *const *obj_ptr,\n                                anjay_iid_t iid,\n                                anjay_rid_t rid,\n                                anjay_ssid_t ssid,\n                                const anjay_dm_r_attributes_t *attrs);\n\n/**\n * A handler that returns Resource Instance attributes.\n *\n * @param      anjay   Anjay object to operate on.\n * @param      obj_ptr Object definition pointer, as passed to\n *                     @ref anjay_register_object .\n * @param      iid     Object Instance ID.\n * @param      rid     Resource ID.\n * @param      riid    Resource Instance ID.\n * @param      ssid    Short Server ID of the LwM2M Server issuing the request.\n * @param[out] out     Returned Resource attributes.\n *\n * @returns This handler should return:\n * - 0 on success,\n * - a negative value in case of error. If it returns one of ANJAY_ERR_\n *   constants, the response message will have an appropriate CoAP response\n *   code. Otherwise, the device will respond with an unspecified (but valid)\n *   error code.\n */\ntypedef int anjay_dm_resource_instance_read_attrs_t(\n        anjay_t *anjay,\n        const anjay_dm_object_def_t *const *obj_ptr,\n        anjay_iid_t iid,\n        anjay_rid_t rid,\n        anjay_riid_t riid,\n        anjay_ssid_t ssid,\n        anjay_dm_r_attributes_t *out);\n\n/**\n * A handler that sets attributes for given Resource Instance.\n *\n * @param anjay   Anjay object to operate on.\n * @param obj_ptr Object definition pointer, as passed to\n *                @ref anjay_register_object .\n * @param iid     Object Instance ID.\n * @param rid     Resource ID.\n * @param riid    Resource Instance ID.\n * @param ssid    Short Server ID of the LwM2M Server issuing the request.\n * @param attrs   Attributes to set for this Resource.\n *\n * @returns This handler should return:\n * - 0 on success,\n * - a negative value in case of error. If it returns one of ANJAY_ERR_\n *   constants, the response message will have an appropriate CoAP response\n *   code. Otherwise, the device will respond with an unspecified (but valid)\n *   error code.\n */\ntypedef int anjay_dm_resource_instance_write_attrs_t(\n        anjay_t *anjay,\n        const anjay_dm_object_def_t *const *obj_ptr,\n        anjay_iid_t iid,\n        anjay_rid_t rid,\n        anjay_riid_t riid,\n        anjay_ssid_t ssid,\n        const anjay_dm_r_attributes_t *attrs);\n\n/**\n * A handler that removes a Resource Instance from a Multiple Resource.\n *\n * This handler will only be called if the Resource is PRESENT, is of the\n * @ref ANJAY_DM_RES_WM or @ref ANJAY_DM_RES_RWM kinds (as returned by\n * @ref anjay_dm_list_resources_t), and the Resource Instance is PRESENT as well\n * (has recently been returned via @ref anjay_dm_list_resource_instances_t).\n *\n * Note: if this handler is not implemented, then the Delete operation on the\n * Resource Instance (@p riid) will not succeed.\n *\n * NOTE: This operation is new to the LwM2M 1.2 standard. It will never be\n * called when communicating using protocol version 1.0 or 1.1. If you do\n * not aim for LwM2M 1.2 compliance, this handler can be left unimplemented.\n *\n * @param anjay   Anjay object to operate on.\n * @param obj_ptr Object definition pointer, as passed to\n *                @ref anjay_register_object .\n * @param iid     Object Instance ID.\n * @param rid     Resource ID.\n * @param riid    Resource Instance ID to remove.\n *\n * @returns This handler should return:\n * - 0 on success,\n * - a negative value in case of error. If it returns one of ANJAY_ERR_\n *   constants, the response message will have an appropriate CoAP response\n *   code. Otherwise, the device will respond with an unspecified (but valid)\n *   error code.\n */\ntypedef int\nanjay_dm_resource_instance_remove_t(anjay_t *anjay,\n                                    const anjay_dm_object_def_t *const *obj_ptr,\n                                    anjay_iid_t iid,\n                                    anjay_rid_t rid,\n                                    anjay_riid_t riid);\n\n/**\n * A handler that is called when there is a request that might modify an Object\n * and fail. Such situation often requires to rollback changes, and this handler\n * shall implement logic that prepares for possible failure in the future.\n *\n * Handlers listed below are NOT called without beginning transaction in the\n * first place (note that if an Object does not implement transaction handlers,\n * then it will not be possible to perform operations listed below):\n *  - @ref anjay_dm_instance_create_t\n *  - @ref anjay_dm_instance_remove_t\n *  - @ref anjay_dm_instance_reset_t\n *  - @ref anjay_dm_resource_write_t\n *  - @ref anjay_dm_resource_reset_t\n *  - @ref anjay_dm_transaction_commit_t\n *  - @ref anjay_dm_transaction_rollback_t\n *\n * Note: if an error occurs during a transaction (i.e. after successful call of\n * this function) then the rollback handler @ref anjay_dm_transaction_rollback_t\n * will be executed by the library.\n *\n * @param anjay     Anjay object to operate on.\n * @param obj_ptr   Object definition pointer, as passed to\n *                  @ref anjay_register_object .\n *\n * @return\n * - 0 on success\n * - a negative value in case of error\n */\ntypedef int\nanjay_dm_transaction_begin_t(anjay_t *anjay,\n                             const anjay_dm_object_def_t *const *obj_ptr);\n\n/**\n * A handler that is called after transaction is finished, but before\n * @ref anjay_dm_transaction_commit_t is called. It is used to check whether the\n * commit operation may be successfully performed.\n *\n * Any validation of the object's state shall be performed in this function,\n * rather than in the commit handler. If there is a need to commit changes to\n * multiple objects at once, this handler is called on all modified objects\n * first, to avoid potential inconsistencies that may arise from a failing\n * commit operation.\n *\n * Returning success from this handler means that the corresponding commit\n * function shall subsequently execute successfully. The commit handler may\n * nevertheless fail, but if and only if a fatal, unpredictable and\n * irrecoverable error (e.g. physical write error) occurs.\n *\n * @param anjay     Anjay Object to operate on.\n * @param obj_ptr   Object definition pointer, as passed to\n *                  @ref anjay_register_object .\n * @return\n * - 0 on success\n * - a negative value in case of error. If it returns one of ANJAY_ERR_\n *   constants, the response message will have an appropriate CoAP response\n *   code. Otherwise, the device will respond with an unspecified (but valid)\n *   error code.\n */\ntypedef int\nanjay_dm_transaction_validate_t(anjay_t *anjay,\n                                const anjay_dm_object_def_t *const *obj_ptr);\n\n/**\n * A handler that is called after transaction is finished. If it fails then\n * @ref anjay_dm_transaction_rollback_t handler must be called by the user\n * code if it is necessary.\n *\n * NOTE: If this function fails, the data model will be left in an inconsistent\n * state. For this reason, it may return an error value if and only if a fatal,\n * unpredictable and irrecoverable error (e.g. physical write error) occurs.\n * All other errors (such as invalid object state) shall be reported via\n * @ref anjay_dm_transaction_validate_t .\n *\n * @param anjay     Anjay Object to operate on.\n * @param obj_ptr   Object definition pointer, as passed to\n *                  @ref anjay_register_object .\n * @return\n * - 0 on success\n * - a negative value in case of error. If it returns one of ANJAY_ERR_\n *   constants, the response message will have an appropriate CoAP response\n *   code. Otherwise, the device will respond with an unspecified (but valid)\n *   error code.\n */\ntypedef int\nanjay_dm_transaction_commit_t(anjay_t *anjay,\n                              const anjay_dm_object_def_t *const *obj_ptr);\n\n/**\n * Stub handler that can be substituted for any transaction operation. Does\n * nothing. It is <strong>NOT</strong> recommended for production usage.\n *\n * @return always 0\n */\nint anjay_dm_transaction_NOOP(anjay_t *anjay,\n                              const anjay_dm_object_def_t *const *obj_ptr);\n\n/**\n * A handler that is called whenever there is a need to restore previous\n * Object state during a transaction or during committing a transaction.\n *\n * @param anjay     Anjay Object to operate on.\n * @param obj_ptr   Object definition pointer, as passed to\n *                  @ref anjay_register_object .\n * @return\n * - 0 on success\n * - a negative value in case of error.\n */\ntypedef int\nanjay_dm_transaction_rollback_t(anjay_t *anjay,\n                                const anjay_dm_object_def_t *const *obj_ptr);\n\n/** A struct containing pointers to Object handlers. */\ntypedef struct {\n    /**\n     * Get default Object attributes, @ref anjay_dm_object_read_default_attrs_t\n     *\n     * Required for handling *LwM2M Discover* and *LwM2M Observe* operations.\n     *\n     * Can be NULL if the *Attribute Storage* feature is enabled. Non-NULL\n     * handler overrides *Attribute Storage* logic.\n     */\n    anjay_dm_object_read_default_attrs_t *object_read_default_attrs;\n\n    /**\n     * Set default Object attributes,\n     * @ref anjay_dm_object_write_default_attrs_t\n     *\n     * Required for handling *LwM2M Write-Attributes* operation.\n     *\n     * Can be NULL when *Attribute Storage* feature is enabled. Non-NULL handler\n     * overrides *Attribute Storage* logic.\n     */\n    anjay_dm_object_write_default_attrs_t *object_write_default_attrs;\n\n    /**\n     * Enumerate available Object Instances, @ref anjay_dm_list_instances_t\n     *\n     * Required for every LwM2M operation.\n     *\n     * **Must not be NULL.** @ref anjay_dm_list_instances_SINGLE can be used\n     * here.\n     */\n    anjay_dm_list_instances_t *list_instances;\n\n    /**\n     * Resets an Object Instance, @ref anjay_dm_instance_reset_t\n     *\n     * Required for handling *LwM2M Write* operation in *replace mode*.\n     *\n     * Can be NULL if the object does not contain writable resources.\n     */\n    anjay_dm_instance_reset_t *instance_reset;\n\n    /**\n     * Create an Object Instance, @ref anjay_dm_instance_create_t\n     *\n     * Required for handling *LwM2M Create* operation.\n     *\n     * Can be NULL for single instance objects.\n     */\n    anjay_dm_instance_create_t *instance_create;\n\n    /**\n     * Delete an Object Instance, @ref anjay_dm_instance_remove_t\n     *\n     * Required for handling *LwM2M Delete* operation performed on Object\n     * Instances.\n     *\n     * Can be NULL for single instance objects.\n     */\n    anjay_dm_instance_remove_t *instance_remove;\n\n    /**\n     * Get default Object Instance attributes,\n     * @ref anjay_dm_instance_read_default_attrs_t\n     *\n     * Required for handling *LwM2M Discover* and *LwM2M Observe* operations.\n     *\n     * Can be NULL when *Attribute Storage* feature is enabled. Non-NULL handler\n     * overrides *Attribute Storage* logic.\n     */\n    anjay_dm_instance_read_default_attrs_t *instance_read_default_attrs;\n\n    /**\n     * Set default Object Instance attributes,\n     * @ref anjay_dm_instance_write_default_attrs_t\n     *\n     * Required for handling *LwM2M Write-Attributes* operation.\n     *\n     * Can be NULL when *Attribute Storage* feature is enabled. Non-NULL handler\n     * overrides *Attribute Storage* logic.\n     */\n    anjay_dm_instance_write_default_attrs_t *instance_write_default_attrs;\n\n    /**\n     * Enumerate PRESENT Resources in a given Object Instance,\n     * @ref anjay_dm_list_resources_t\n     *\n     * Required for every LwM2M operation.\n     *\n     * **Must not be NULL.**\n     */\n    anjay_dm_list_resources_t *list_resources;\n\n    /**\n     * Get Resource value, @ref anjay_dm_resource_read_t\n     *\n     * Required for *LwM2M Read* operation.\n     *\n     * Can be NULL if the object does not contain readable resources.\n     */\n    anjay_dm_resource_read_t *resource_read;\n\n    /**\n     * Set Resource value, @ref anjay_dm_resource_write_t\n     *\n     * Required for *LwM2M Write* operation.\n     *\n     * Can be NULL if the object does not contain writable resources.\n     */\n    anjay_dm_resource_write_t *resource_write;\n\n    /**\n     * Perform Execute action on a Resource, @ref anjay_dm_resource_execute_t\n     *\n     * Required for *LwM2M Execute* operation.\n     *\n     * Can be NULL if the object does not contain executable resources.\n     */\n    anjay_dm_resource_execute_t *resource_execute;\n\n    /**\n     * Remove all Resource Instances from a Multiple Resource,\n     * @ref anjay_dm_resource_reset_t\n     *\n     * Required for *LwM2M Write* operation performed on multiple-instance\n     * resources.\n     *\n     * Can be NULL if the object does not contain multiple writable resources.\n     */\n    anjay_dm_resource_reset_t *resource_reset;\n\n    /**\n     * Enumerate available Resource Instances,\n     * @ref anjay_dm_list_resource_instances_t\n     *\n     * Required for *LwM2M Read*, *LwM2M Write* and *LwM2M Discover* operations\n     * performed on multiple-instance resources..\n     *\n     * Can be NULL if the object does not contain multiple resources.\n     */\n    anjay_dm_list_resource_instances_t *list_resource_instances;\n\n    /**\n     * Get Resource attributes, @ref anjay_dm_resource_read_attrs_t\n     *\n     * Required for handling *LwM2M Discover* and *LwM2M Observe* operations.\n     *\n     * Can be NULL when *Attribute Storage* feature is enabled. Non-NULL handler\n     * overrides *Attribute Storage* logic.\n     */\n    anjay_dm_resource_read_attrs_t *resource_read_attrs;\n\n    /**\n     * Set Resource attributes, @ref anjay_dm_resource_write_attrs_t\n     *\n     * Required for handling *LwM2M Write-Attributes* operation.\n     *\n     * Can be NULL when *Attribute Storage* feature is enabled. Non-NULL handler\n     * overrides *Attribute Storage* logic.\n     */\n    anjay_dm_resource_write_attrs_t *resource_write_attrs;\n\n    /**\n     * Begin a transaction on this Object, @ref anjay_dm_transaction_begin_t\n     *\n     * Required for handling modifying operation: *LwM2M Write*, *LwM2M Create*\n     * or *LwM2M Delete*.\n     *\n     * Can be NULL for read-only objects. @ref anjay_dm_transaction_NOOP can be\n     * used here.\n     */\n    anjay_dm_transaction_begin_t *transaction_begin;\n\n    /**\n     * Validate whether a transaction on this Object can be cleanly committed.\n     * See @ref anjay_dm_transaction_validate_t\n     *\n     * Required for handling modifying operation: *LwM2M Write*, *LwM2M Create*\n     * or *LwM2M Delete*.\n     *\n     * Can be NULL for read-only objects. @ref anjay_dm_transaction_NOOP can be\n     * used here.\n     */\n    anjay_dm_transaction_validate_t *transaction_validate;\n\n    /**\n     * Commit changes made in a transaction, @ref anjay_dm_transaction_commit_t\n     *\n     * Required for handling modifying operation: *LwM2M Write*, *LwM2M Create*\n     * or *LwM2M Delete*.\n     *\n     * Can be NULL for read-only objects. @ref anjay_dm_transaction_NOOP can be\n     * used here.\n     */\n    anjay_dm_transaction_commit_t *transaction_commit;\n\n    /**\n     * Rollback changes made in a transaction,\n     * @ref anjay_dm_transaction_rollback_t\n     *\n     * Required for handling modifying operation: *LwM2M Write*, *LwM2M Create*\n     * or *LwM2M Delete*.\n     *\n     * Can be NULL for read-only objects. @ref anjay_dm_transaction_NOOP can be\n     * used here.\n     */\n    anjay_dm_transaction_rollback_t *transaction_rollback;\n\n    /**\n     * Get Resource Instance attributes, @ref\n     * anjay_dm_resource_instance_read_attrs_t\n     *\n     * Required for handling *LwM2M Discover* and *LwM2M Observe* operations.\n     *\n     * Can be NULL if the object does not contain multiple resources, when\n     * *Attribute Storage* feature is enabled, or when the application only\n     * targets compliance with LwM2M TS 1.0. Non-NULL handler overrides\n     * *Attribute Storage* logic.\n     */\n    anjay_dm_resource_instance_read_attrs_t *resource_instance_read_attrs;\n\n    /**\n     * Set Resource Instance attributes, @ref\n     * anjay_dm_resource_instance_write_attrs_t\n     *\n     * Required for handling *LwM2M Write-Attributes* operation.\n     *\n     * Can be NULL if the object does not contain multiple resources, when\n     * *Attribute Storage* feature is enabled, or when the application only\n     * targets compliance with LwM2M TS 1.0. Non-NULL handler overrides\n     * *Attribute Storage* logic.\n     */\n    anjay_dm_resource_instance_write_attrs_t *resource_instance_write_attrs;\n\n#ifdef ANJAY_WITH_LWM2M12\n    /**\n     * Delete a Resource Instance from a Multiple Resource,\n     * @ref anjay_dm_resource_instance_remove_t\n     *\n     * Required for handling *LwM2M Delete* operation performed on Resource\n     * Instances.\n     *\n     * Can be NULL if the object does not contain multiple writable resources.\n     *\n     * NOTE: This operation is new to the LwM2M 1.2 standard. It will never be\n     * called when communicating using protocol version 1.0 or 1.1. If you do\n     * not aim for LwM2M 1.2 compliance, it can also be NULL.\n     */\n    anjay_dm_resource_instance_remove_t *resource_instance_remove;\n#endif // ANJAY_WITH_LWM2M12\n} anjay_dm_handlers_t;\n\n/** A struct defining a LwM2M Object. */\nstruct anjay_dm_object_def_struct {\n    /** Object ID; MUST not be <c>ANJAY_ID_INVALID</c> (65535) */\n    anjay_oid_t oid;\n\n    /**\n     * Object version: a string with static lifetime, containing two digits\n     * separated by a dot (for example: \"1.1\").\n     * If left NULL, client will not include the \"ver=\" attribute in Register\n     * and Discover messages. This implies:\n     * 1. Version 1.0 for Non-Core Objects.\n     * 2. The version corresponding to the version in the LwM2M Enabler for Core\n     * Objects.\n     */\n    const char *version;\n\n    /** Handler callbacks for this object. */\n    anjay_dm_handlers_t handlers;\n};\n\n/**\n * Notifies the library that the value of given Resource changed. Calling this\n * function does not send the notification immediately, but schedules a job to\n * be run on the next event loop iteration. This job may trigger a LwM2M Notify\n * message, update server connections and perform other tasks, as required for\n * the specified Resource.\n *\n * This function may be called before or after the value of a given Resource\n * changed in the DM, as long as the Resource is updated before the scheduled\n * notification job is executed. This ensures that the notification reflects the\n * correct value. Please see documentation of @ref AVS_SCHED_AT and\n * @ref anjay_sched_run for more information.\n *\n * Note that it should not be called after a Write performed by the LwM2M\n * server.\n *\n * @param anjay Anjay object to operate on.\n * @param oid   Object ID of the changed Resource.\n * @param iid   Object Instance ID of the changed Resource.\n * @param rid   Resource ID of the changed Resource.\n *\n * @returns 0 on success, a negative value in case of error.\n */\nint anjay_notify_changed(anjay_t *anjay,\n                         anjay_oid_t oid,\n                         anjay_iid_t iid,\n                         anjay_rid_t rid);\n\n/**\n * Notifies the library that the set of Instances existing in a given Object\n * changed. It may trigger a LwM2M Notify message, update server connections\n * and perform other tasks, as required for the specified Object ID.\n *\n * Needs to be called for each Object, after an Instance is created or removed\n * by means other than LwM2M.\n *\n * Note that it should not be called after a Create or Delete performed by the\n * LwM2M server.\n *\n * @param anjay Anjay object to operate on.\n * @param oid   Object ID of the changed Object.\n *\n * @returns 0 on success, a negative value in case of error.\n */\nint anjay_notify_instances_changed(anjay_t *anjay, anjay_oid_t oid);\n\n#ifdef ANJAY_WITH_OBSERVATION_STATUS\n/**\n * Maximum number of servers observing a Resource reported in\n * @ref anjay_resource_observation_status_t structure.\n */\n#    ifndef ANJAY_MAX_OBSERVATION_SERVERS_REPORTED_NUMBER\n#        define ANJAY_MAX_OBSERVATION_SERVERS_REPORTED_NUMBER 0\n#    endif // ANJAY_MAX_OBSERVATION_SERVERS_REPORTED_NUMBER\n\n/**\n * Structure representing an observation state of a Resource.\n */\ntypedef struct {\n    /**\n     * Informs whether a given Resource is observed (by any server) or not.\n     */\n    bool is_observed;\n    /**\n     * The minimum effective value (in seconds) of the <c>pmin</c> attribute for\n     * a given Resource. The value of this field equals 0 if <c>pmin</c> wasn't\n     * set for any server or <c>is_observed</c> is false.\n     */\n    int32_t min_period;\n    /**\n     * The minimum effective value (in seconds) of the <c>epmax</c> attribute\n     * for a given Resource. The value of this field equals @ref\n     * ANJAY_ATTRIB_INTEGER_NONE if <c>epmax</c> wasn't set for any server or\n     * <c>is_observed</c> is false.\n     */\n    int32_t max_eval_period;\n#    if (ANJAY_MAX_OBSERVATION_SERVERS_REPORTED_NUMBER > 0)\n    /**\n     * Number of servers that observe the Resource.\n     */\n    uint16_t servers_number;\n    /**\n     * SSIDs of servers that observe the Resource.\n     */\n    anjay_ssid_t servers[ANJAY_MAX_OBSERVATION_SERVERS_REPORTED_NUMBER];\n#    endif //(ANJAY_MAX_OBSERVATION_SERVERS_REPORTED_NUMBER > 0)\n} anjay_resource_observation_status_t;\n\n/**\n * Gets information whether and how a given Resource is observed. See\n * @ref anjay_resource_observation_status_t for details.\n *\n * NOTE: This API is a companion to @ref anjay_notify_changed. There is no\n * analogous API that would be a companion to\n * @ref anjay_notify_instances_changed. Any changes to set of instances of any\n * LwM2M Object MUST be considered observed at all times and notified as soon as\n * possible.\n *\n * @param anjay Anjay object to operate on.\n * @param oid   Object ID of the Resource to check.\n * @param iid   Object Instance ID of the Resource to check.\n * @param rid   Resource ID of the Resource to check.\n *\n * @returns Observation status of a given Resource. If the arguments do not\n *          specify a valid Resource path, data equivalent to a non-observed\n *          Resource will be returned.\n *\n * NOTE: This function may be used to implement notifications for Resources that\n * require active polling by the client application. A naive implementation\n * could look more or less like this (pseudocode):\n *\n * <code>\n * status = anjay_resource_observation_status(anjay, oid, iid, rid);\n * if (status.is_observed\n *         && current_time >= last_check_time + status.min_period) {\n *     new_value = read_resource_value();\n *     if (new_value != old_value) {\n *         anjay_notify_changed(anjay, oid, iid, rid);\n *     }\n *     last_check_time = current_time;\n * }\n * </code>\n *\n * However, please note that such implementation may not be strictly conformant\n * to the LwM2M specification. For example, in the following case:\n *\n * [time] --|--------|-*------|-->     | - intervals between resource reads\n *          |<------>|                 * - point in time when underlying state\n *          min_period                     actually changes\n *\n * the specification would require the notification to be sent exactly at the\n * time of the (*) event, but with this naive implementation, will be delayed\n * until the next (|).\n */\nanjay_resource_observation_status_t anjay_resource_observation_status(\n        anjay_t *anjay, anjay_oid_t oid, anjay_iid_t iid, anjay_rid_t rid);\n\n#endif // ANJAY_WITH_OBSERVATION_STATUS\n\n/**\n * Registers the Object in the data model, making it available for access by the\n * LwM2M Servers.\n *\n * NOTE: <c>def_ptr</c> MUST stay valid up to and including the corresponding\n * @ref anjay_delete or @ref anjay_unregister_object call.\n *\n * @param anjay   Anjay object to operate on.\n * @param def_ptr Pointer to the Object definition struct. The exact value\n *                passed to this function will be forwarded to all data model\n *                handler calls.\n *\n * @returns 0 on success, a negative value in case of error.\n */\nint anjay_register_object(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *def_ptr);\n\n/**\n * Unregisters an Object in the data model, so that it is no longer available\n * for access by the LwM2M Servers.\n *\n * <c>def_ptr</c> MUST be a pointer previously passed to\n * @ref anjay_register_object for the same <c>anjay</c> object.\n *\n * After a successful unregister, any resources used by the actual object may be\n * safely freed up.\n *\n * NOTE: This function MUST NOT be called from within any data model handler\n * callback function (i.e. any of the @ref anjay_dm_handlers_t members). Doing\n * so is undefined behavior.\n *\n * @param anjay   Anjay object to operate on.\n * @param def_ptr Pointer to the Object definition struct.\n *\n * @returns 0 on success, a negative value if <c>def_ptr</c> does not correspond\n *          to any known registered object.\n */\nint anjay_unregister_object(anjay_t *anjay,\n                            const anjay_dm_object_def_t *const *def_ptr);\n\n/**\n * Checks whether the passed string is a valid LwM2M Binding Mode.\n *\n * @return true for <c>\"U\"</c>, <c>\"S\"</c>, <c>\"US\"</c>, <c>\"UQ\"</c>,\n *         <c>\"SQ\"</c>, <c>\"UQS\"</c>, false in any other case.\n */\nbool anjay_binding_mode_valid(const char *binding_mode);\n\n/**\n * Possible values of the Security Mode Resource, as described in the Security\n * Object definition.\n */\ntypedef enum {\n    ANJAY_SECURITY_PSK = 0,         //< Pre-Shared Key mode\n    ANJAY_SECURITY_RPK = 1,         //< Raw Public Key mode\n    ANJAY_SECURITY_CERTIFICATE = 2, //< Certificate mode\n    ANJAY_SECURITY_NOSEC = 3,       //< NoSec mode\n    ANJAY_SECURITY_EST = 4          //< Certificate mode with EST\n} anjay_security_mode_t;\n\n#define ANJAY_ACCESS_MASK_READ (1U << 0)\n#define ANJAY_ACCESS_MASK_WRITE (1U << 1)\n#define ANJAY_ACCESS_MASK_EXECUTE (1U << 2)\n#define ANJAY_ACCESS_MASK_DELETE (1U << 3)\n#define ANJAY_ACCESS_MASK_CREATE (1U << 4)\n// clang-format off\n#define ANJAY_ACCESS_MASK_FULL   \\\n    (ANJAY_ACCESS_MASK_READ      \\\n     | ANJAY_ACCESS_MASK_WRITE   \\\n     | ANJAY_ACCESS_MASK_DELETE  \\\n     | ANJAY_ACCESS_MASK_EXECUTE \\\n     | ANJAY_ACCESS_MASK_CREATE)\n// clang-format on\n#define ANJAY_ACCESS_MASK_NONE 0\n#define ANJAY_ACCESS_LIST_OWNER_BOOTSTRAP UINT16_MAX\n\ntypedef uint16_t anjay_access_mask_t;\n\n#ifdef __cplusplus\n} /* extern \"C\" */\n#endif\n\n#endif /*ANJAY_INCLUDE_ANJAY_DM_H*/\n"
  },
  {
    "path": "include_public/anjay/download.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ANJAY_INCLUDE_ANJAY_DOWNLOAD_H\n#define ANJAY_INCLUDE_ANJAY_DOWNLOAD_H\n\n#include <stddef.h>\n#include <stdint.h>\n\n#include <avsystem/commons/avs_net.h>\n\n#include <anjay/anjay_config.h>\n#include <anjay/core.h>\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n/** CoAP Entity Tag. */\ntypedef struct anjay_etag {\n    uint8_t size;\n    uint8_t value[1]; // actually a flexible array member\n} anjay_etag_t;\n\n/**\n * Allocates ETag with a given size.\n * The new ETag can be freed using @c avs_free.\n *\n * @param etag_size The number of bytes to be available in the returned\n *                  anjay_etag_t::value array.\n *\n * @return Pointer to created ETag, NULL on failure\n */\nanjay_etag_t *anjay_etag_new(uint8_t etag_size);\n\n/**\n * Given one ETag, creates a new one, with the same size and value.\n * The new ETag can be freed using @c avs_free.\n *\n * @param old_etag Pointer to old ETag copy\n *\n * @return Pointer to created ETag copy, NULL on failure\n */\nanjay_etag_t *anjay_etag_clone(const anjay_etag_t *old_etag);\n\n/**\n * Called each time a chunk of data is received from remote host.\n * It is guaranteed to be called with consecutive chunks of data, starting\n * from @ref anjay_download_config_t#start_offset.\n *\n * @param anjay     Anjay object managing the download process.\n * @param data      Received data.\n * @param data_size Number of bytes available in @p data .\n * @param etag      ETag option sent by the server. Should be saved if the\n *                  client may need to resume the transfer after it gets\n *                  interrupted.\n * @param user_data Value of @ref anjay_download_config_t#user_data passed\n *                  to @ref anjay_download .\n *\n * @return Should return:\n *         @li <c>AVS_OK</c> on success,\n *         @li an error value if an error occurred, in which case the download\n *             will be terminated with @ref ANJAY_DOWNLOAD_ERR_FAILED result.\n */\ntypedef avs_error_t\nanjay_download_next_block_handler_t(anjay_t *anjay,\n                                    const uint8_t *data,\n                                    size_t data_size,\n                                    const anjay_etag_t *etag,\n                                    void *user_data);\n\ntypedef enum anjay_download_result {\n    /** Download finished successfully. */\n    ANJAY_DOWNLOAD_FINISHED,\n    /** Download failed due to a local failure or a network error. */\n    ANJAY_DOWNLOAD_ERR_FAILED,\n    /** The remote server responded in a way that is permitted by the protocol,\n     * but does not indicate a success (e.g. a 4xx or 5xx HTTP status). */\n    ANJAY_DOWNLOAD_ERR_INVALID_RESPONSE,\n    /** Downloaded resource changed while transfer was in progress. */\n    ANJAY_DOWNLOAD_ERR_EXPIRED,\n    /** Download was aborted by calling @ref anjay_download_abort . */\n    ANJAY_DOWNLOAD_ERR_ABORTED\n} anjay_download_result_t;\n\ntypedef struct {\n    anjay_download_result_t result;\n\n    union {\n        /**\n         * Error code. Only valid if result is ANJAY_DOWNLOAD_ERR_FAILED.\n         *\n         * Possible values include (but are not limited to):\n         *\n         * - <c>avs_errno(AVS_EADDRNOTAVAIL)</c> - DNS resolution failed\n         * - <c>avs_errno(AVS_ECONNABORTED)</c> - remote resource is no longer\n         *   valid\n         * - <c>avs_errno(AVS_ECONNREFUSED)</c> - server responded with a reset\n         *   message on the application layer (e.g. CoAP Reset)\n         * - <c>avs_errno(AVS_ECONNRESET)</c> - connection lost or reset\n         * - <c>avs_errno(AVS_EINVAL)</c> - could not parse response from the\n         *   server\n         * - <c>avs_errno(AVS_EIO)</c> - internal error in the transfer code\n         * - <c>avs_errno(AVS_EMSGSIZE)</c> - could not send or receive datagram\n         *   because it was too large\n         * - <c>avs_errno(AVS_ENOMEM)</c> - out of memory\n         * - <c>avs_errno(AVS_ETIMEDOUT)</c> - could not receive data from\n         *   server in time\n         */\n        avs_error_t error;\n\n        /**\n         * Protocol-specific status code. Only valid if result is\n         * ANJAY_DOWNLOAD_ERR_INVALID_RESPONSE.\n         *\n         * Currently it may be a HTTP status code (e.g. 404 or 501), or a CoAP\n         * code (e.g. 132 or 161 - these examples are canonically interpreted as\n         * 4.04 and 5.01, respectively). If any user log is to depend on status\n         * codes, it is expected that it will be interpreted in line with the\n         * URL originally passed to @ref anjay_download for the same download.\n         */\n        int status_code;\n    } details;\n} anjay_download_status_t;\n\n/**\n * Called whenever the download finishes, successfully or not.\n *\n * @param anjay     Anjay object managing the download process.\n * @param status    Status of the download, with additional error information if\n *                  applicable.\n * @param user_data Value of @ref anjay_download_config_t#user_data passed\n *                  to @ref anjay_download .\n */\ntypedef void anjay_download_finished_handler_t(anjay_t *anjay,\n                                               anjay_download_status_t status,\n                                               void *user_data);\n\ntypedef struct anjay_download_config {\n    /** Required. %coap://, %coaps://, %http:// or %https:// URL */\n    const char *url;\n\n    /**\n     * If the download gets interrupted for some reason, and the client\n     * is aware of how much data it managed to successfully download,\n     * it can resume the transfer from a specific offset.\n     */\n    size_t start_offset;\n\n    /**\n     * If start_offset is not 0, etag should be set to a value returned\n     * by the server during the transfer before it got interrupted.\n     */\n    const anjay_etag_t *etag;\n\n    /** Required. Called after receiving a chunk of data from remote server. */\n    anjay_download_next_block_handler_t *on_next_block;\n\n    /** Required. Called after the download is finished or aborted. */\n    anjay_download_finished_handler_t *on_download_finished;\n\n    /** Opaque pointer passed to download handlers. */\n    void *user_data;\n\n    /**\n     * DTLS security configuration. Required if coaps:// is used,\n     * ignored for coap:// transfers.\n     *\n     * Contents of any data aggregated as pointers within is copied as needed,\n     * so it is safe to free all related resources array after the call to\n     * @ref anjay_download.\n     */\n    anjay_security_config_t security_config;\n\n    /**\n     * Pointer to CoAP transmission parameters object. If NULL, downloader will\n     * inherit parameters from Anjay.\n     */\n    avs_coap_udp_tx_params_t *coap_tx_params;\n\n    /**\n     * Time of inactivity that will cause the download to time out when using\n     * TCP-based transports (i.e., CoAP+TCP or HTTP).\n     *\n     * If uninitialized or otherwise non-positive (including zero and invalid\n     * value), the value passed as\n     * <c>anjay_configuration_t::coap_tcp_request_timeout</c> (or its default,\n     * which is 30 seconds) will be used for CoAP+TCP, and\n     * <c>AVS_NET_SOCKET_DEFAULT_RECV_TIMEOUT</c> (i.e., 30 seconds) will be\n     * used for HTTP.\n     */\n    avs_time_duration_t tcp_request_timeout;\n\n    /**\n     * If set to true, the downloader module will attempt performing downloads\n     * over the same sockets as existing LwM2M Servers (if the download URI is\n     * found to match the URI of some LwM2M Server). Moreover, if set to true,\n     * @p security_config as well as @p coap_tx_params MAY or MAY NOT be used\n     * depending on whether the new socket is created or an existing one can\n     * be reused.\n     */\n    bool prefer_same_socket_downloads;\n} anjay_download_config_t;\n\ntypedef void *anjay_download_handle_t;\n\n/**\n * Requests asynchronous download of an external resource.\n *\n * Download will create a new socket that will be later included in the list\n * returned by @ref anjay_get_sockets . Calling @ref anjay_serve on such socket\n * may cause calling @ref anjay_download_config_t#on_next_block if received\n * packet is the next expected chunk of downloaded data and\n * @ref anjay_download_finished_handler_t if the transfer completes or fails.\n * Request packet retransmissions are managed by Anjay scheduler, and sent by\n * @ref anjay_sched_run whenever required.\n *\n * No network activity is performed immediately during the call to\n * <c>anjay_download()</c>. Instead, the TCP and/or (D)TLS handshakes and the\n * first request packet, will be sent during subsequent calls to\n * @ref anjay_sched_run. This also means that you can create a postponed\n * download by calling @ref anjay_download_suspend immediately afterwards.\n *\n * The behavior of the CoAP downloader can be also affected by the\n * @ref anjay_configuration_t::coap_downloader_retry_count and\n * @ref anjay_configuration_t::coap_downloader_retry_delay.\n *\n * @param anjay      Anjay object that will manage the download process.\n * @param config     Download configuration.\n * @param out_handle Pointer to a variable that will be set to a download\n *                   handle, that may be used for aborting the download.\n *                   MUST NOT be NULL.\n *\n * @returns @li <c>AVS_OK</c> on success, in which case <c>*out_handle</c> is\n *              set to a handle to the created download,\n *          @li Code of the error that happened, in which case\n *              <c>*out_handle</c> is not modified, and\n *              @ref anjay_download_config_t#on_download_finished handler is NOT\n *              called.\n */\navs_error_t anjay_download(anjay_t *anjay,\n                           const anjay_download_config_t *config,\n                           anjay_download_handle_t *out_handle);\n\n/**\n * Changes the offset of the remote resource that the user wants to receive the\n * next response data block from.\n *\n * This function is only intended to be called from within an implementation of\n * @ref anjay_download_next_block_handler_t.\n *\n * The offset can only be moved forward relative to the last known starting\n * offset. Attempting to set it to an offset of byte that was already received\n * in a previously finished call to @ref anjay_download_next_block_handler_t, or\n * is smaller than an offset already passed to this function, will result in an\n * error.\n *\n * When called from within @ref anjay_download_next_block_handler_t,\n * @p next_block_offset may be set to a position that lies after or within the\n * <c>data</c> buffer passed to it (but further than the current offset). If a\n * position within the buffer is passed, the block handler will be called again\n * with a portion of the same buffer, starting at the desired offset.\n *\n * If this function is never called during a call to\n * @ref anjay_download_next_block_handler_t, the file pointer is implicitly\n * moved by the whole size of the buffer passed to it.\n *\n * It is guaranteed that if there will be a next call to\n * @ref anjay_download_next_block_handler_t for the given download, it will be\n * passed data from the specified offset.\n *\n * NOTE: Actual efficient skipping of already downloaded data is currently only\n * supported for CoAP. Using this function with HTTP downloads will only\n * suppress passing the skipped data; full file will still be transmitted over\n * the network.\n *\n * @param anjay             Anjay object managing the download process.\n * @param dl_handle         Download handle previously returned by\n *                          @ref anjay_download.\n * @param next_block_offset Block offset to set.\n *\n * @returns\n *  - @ref AVS_OK for success\n *  - <c>avs_errno(AVS_ENOENT)</c> if @p dl_handle does not refer to an existing\n *    download process\n *  - <c>avs_errno(AVS_EINVAL)</c> if @p next_block_offset is smaller than the\n *    currently recognized value\n *  - <c>avs_errno(AVS_ENOTSUP)</c> if Anjay has been compiled without support\n *    for downloads\n */\navs_error_t\nanjay_download_set_next_block_offset(anjay_t *anjay,\n                                     anjay_download_handle_t dl_handle,\n                                     size_t next_block_offset);\n\n/**\n * Aborts a download identified by @p dl_handle. Does nothing if @p dl_handle\n * does not represent a valid download handle.\n *\n * @param anjay     Anjay object managing the download process.\n * @param dl_handle Download handle previously returned by\n *                  @ref anjay_download.\n */\nvoid anjay_download_abort(anjay_t *anjay, anjay_download_handle_t dl_handle);\n\n/**\n * Suspends a download identified by @p dl_handle. Does nothing if @p dl_handle\n * does not represent a valid download handle.\n *\n * The suspend operation is performed immediately and synchronously. The socket\n * is disconnected, but the rest of the download context is kept intact. The\n * download can be resumed by calling @p anjay_download_reconnect.\n *\n * If the download is already suspended due to the transport being offline (see\n * @ref anjay_transport_set_online), no immediate action is performed, but the\n * download is marked in such a way that it will not be automatically resumed\n * until an explicit call to @ref anjay_download_reconnect.\n *\n * @param anjay     Anjay object managing the download process.\n * @param dl_handle Download handle previously returned by\n *                  @ref anjay_download.\n */\nvoid anjay_download_suspend(anjay_t *anjay, anjay_download_handle_t dl_handle);\n\n/**\n * Reconnects a download identified by @p dl_handle while retaining the download\n * progress.\n *\n * If the download has been previously suspended using\n * @ref anjay_download_suspend, it will be resumed. If the download is suspended\n * due to the transport being offline (see @ref anjay_transport_set_online), no\n * immediate action is performed, but the suspended state as per @ref\n * anjay_download_suspend will be cleared.\n *\n * This function only schedules the actual reconnect operation. The socket will\n * be actually reconnected during subsequent calls to @ref anjay_sched_run.\n *\n * @param anjay     Anjay object managing the download process.\n * @param dl_handle Download handle previously returned by\n *                  @ref anjay_download.\n *\n * @returns 0 for success; -1 if @p dl_handle does not represent a valid\n *          download handle, or if the reconnect job could not be scheduled\n *          (e.g. due to an out-of-memory condition).\n */\nint anjay_download_reconnect(anjay_t *anjay, anjay_download_handle_t dl_handle);\n\n#ifdef __cplusplus\n} /* extern \"C\" */\n#endif\n\n#endif /*ANJAY_INCLUDE_ANJAY_DOWNLOAD_H*/\n"
  },
  {
    "path": "include_public/anjay/factory_provisioning.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n#ifndef ANJAY_INCLUDE_ANJAY_FACTORY_PROVISIONING_H\n#define ANJAY_INCLUDE_ANJAY_FACTORY_PROVISIONING_H\n\n#include <avsystem/commons/avs_errno.h>\n#include <avsystem/commons/avs_stream.h>\n\n#include <anjay/core.h>\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n/**\n * Reads Bootstrap Information from the stream (@p data_stream) and initializes\n * Anjay's data model. Expected format of the stream data is SenML CBOR, as used\n * for a Write-Composite operation.\n\n *\n * @param anjay         Anjay Object to operate on.\n * @param data_stream   Bootstrap Information data stream.\n * @returns\n *  - @ref AVS_OK for success\n *  - <c>avs_errno(AVS_EBADMSG)</c> if Anjay failed to apply bootstrap\n information\n *  - <c>avs_errno(AVS_ENOMEM)</c> if Anjay failed to allocate memory\n *  - <c>avs_errno(AVS_EAGAIN)</c> if connection with Bootstrap Server is in\n progress\n *  - <c>avs_errno(AVS_EPROTO)</c> in case of other internal errors\n */\navs_error_t anjay_factory_provision(anjay_t *anjay, avs_stream_t *data_stream);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif /* ANJAY_INCLUDE_ANJAY_FACTORY_PROVISIONING_H */\n"
  },
  {
    "path": "include_public/anjay/fw_update.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ANJAY_INCLUDE_ANJAY_FW_UPDATE_H\n#define ANJAY_INCLUDE_ANJAY_FW_UPDATE_H\n\n#include <anjay/anjay_config.h>\n#include <anjay/dm.h>\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n/**\n * Numeric values of the Firmware Update Result resource. See LwM2M\n * specification for details.\n *\n * Note: they SHOULD only be used with @ref anjay_fw_update_set_result .\n */\ntypedef enum {\n    ANJAY_FW_UPDATE_RESULT_INITIAL = 0,\n    ANJAY_FW_UPDATE_RESULT_SUCCESS = 1,\n    ANJAY_FW_UPDATE_RESULT_NOT_ENOUGH_SPACE = 2,\n    ANJAY_FW_UPDATE_RESULT_OUT_OF_MEMORY = 3,\n    ANJAY_FW_UPDATE_RESULT_CONNECTION_LOST = 4,\n    ANJAY_FW_UPDATE_RESULT_INTEGRITY_FAILURE = 5,\n    ANJAY_FW_UPDATE_RESULT_UNSUPPORTED_PACKAGE_TYPE = 6,\n    ANJAY_FW_UPDATE_RESULT_INVALID_URI = 7,\n    ANJAY_FW_UPDATE_RESULT_FAILED = 8,\n    ANJAY_FW_UPDATE_RESULT_UNSUPPORTED_PROTOCOL = 9,\n    ANJAY_FW_UPDATE_RESULT_UPDATE_CANCELLED = 10,\n    ANJAY_FW_UPDATE_RESULT_DEFERRED = 11,\n} anjay_fw_update_result_t;\n\n/** @name Firmware update result codes\n * @{\n * The following result codes may be returned from\n * @ref anjay_fw_update_stream_write_t, @ref anjay_fw_update_stream_finish_t or\n * @ref anjay_fw_update_perform_upgrade_t to control the value of the Update\n * Result Resource after the failure.\n *\n * Their values correspond to negated numeric values of that resource. However,\n * attempting to use other negated value will be checked and cause a fall-back\n * to a value default for a given handler.\n */\n#define ANJAY_FW_UPDATE_ERR_NOT_ENOUGH_SPACE \\\n    (-(int) ANJAY_FW_UPDATE_RESULT_NOT_ENOUGH_SPACE)\n#define ANJAY_FW_UPDATE_ERR_OUT_OF_MEMORY \\\n    (-(int) ANJAY_FW_UPDATE_RESULT_OUT_OF_MEMORY)\n#define ANJAY_FW_UPDATE_ERR_INTEGRITY_FAILURE \\\n    (-(int) ANJAY_FW_UPDATE_RESULT_INTEGRITY_FAILURE)\n#define ANJAY_FW_UPDATE_ERR_UNSUPPORTED_PACKAGE_TYPE \\\n    (-(int) ANJAY_FW_UPDATE_RESULT_UNSUPPORTED_PACKAGE_TYPE)\n#ifdef ANJAY_WITH_LWM2M11\n#    define ANJAY_FW_UPDATE_ERR_DEFERRED \\\n        (-(int) ANJAY_FW_UPDATE_RESULT_DEFERRED)\n#endif // ANJAY_WITH_LWM2M11\n\n/** @} */\n\n/**\n * Possible values that control the State and Update Result resources at the\n * time of initialization of the Firmware Update object.\n */\ntypedef enum {\n    /**\n     * Corresponds to the \"Updating\" State and \"Initial\" Result. Shall be used\n     * when the device rebooted as part of the update process, but the firmware\n     * image is not fully applied yet. The application MUST use\n     * @ref anjay_fw_update_set_result to set the result to success or failure\n     * after the update process is complete.\n     */\n    ANJAY_FW_UPDATE_INITIAL_UPDATING = -3,\n    /**\n     * Corresponds to the \"Downloaded\" State and \"Initial\" Result. Shall be used\n     * when the device unexpectedly rebooted when the firmware image has already\n     * been downloaded into some non-volatile memory.\n     */\n    ANJAY_FW_UPDATE_INITIAL_DOWNLOADED = -2,\n\n    /**\n     * Corresponds to the \"Downloading\" State and \"Initial\" Result. Shall be\n     * used when the device can determine that it unexpectedly rebooted during\n     * the download of the firmware image, and it has all the information\n     * necessary to resume the download. Such information shall then be passed\n     * via other fields in the @ref anjay_fw_update_initial_state_t structure.\n     */\n    ANJAY_FW_UPDATE_INITIAL_DOWNLOADING = -1,\n\n    /**\n     * Corresponds to the \"Idle\" State and \"Initial\" Result. Shall be used when\n     * the library is initializing normally, not after a firmware update\n     * attempt.\n     */\n    ANJAY_FW_UPDATE_INITIAL_NEUTRAL = 0,\n\n    /**\n     * Corresponds to the \"Idle\" State and \"Firmware updated successfully\"\n     * Result. Shall be used when the device has just rebooted after\n     * successfully updating the firmware.\n     */\n    ANJAY_FW_UPDATE_INITIAL_SUCCESS = 1,\n\n    /**\n     * Corresponds to the \"Idle\" State and \"Integrity check failure\" Result.\n     * Shall be used when the device has just rebooted after an unsuccessful\n     * firmware update attempt that failed due to failed integrity check of the\n     * firmware package.\n     */\n    ANJAY_FW_UPDATE_INITIAL_INTEGRITY_FAILURE = 5,\n\n    /**\n     * Corresponds to the \"Idle\" State \"Firmware update failed\" Result. Shall be\n     * used when the device has just rebooted after a firmware upgrade attempt\n     * that was unsuccessful for reason any other than integrity check.\n     */\n    ANJAY_FW_UPDATE_INITIAL_FAILED = 8\n} anjay_fw_update_initial_result_t;\n\n/**\n * Numeric values of the Firmware Update Severity resource. See LwM2M\n * specification for details.\n */\ntypedef enum {\n    ANJAY_FW_UPDATE_SEVERITY_CRITICAL = 0,\n    ANJAY_FW_UPDATE_SEVERITY_MANDATORY,\n    ANJAY_FW_UPDATE_SEVERITY_OPTIONAL\n} anjay_fw_update_severity_t;\n\n/**\n * Information about the state to initialize the Firmware Update object in.\n */\ntypedef struct {\n    /**\n     * Controls initialization of the State and Update Result resources. It is\n     * intended to be used after a reboot caused by a firmware update attempt,\n     * to report the update result.\n     */\n    anjay_fw_update_initial_result_t result;\n\n    /**\n     * Value to initialize the Package URI resource with. The passed string is\n     * copied, so the pointer is allowed to become invalid after return from\n     * @ref anjay_fw_update_install .\n     *\n     * Required when <c>result == ANJAY_FW_UPDATE_INITIAL_DOWNLOADING</c>; if it\n     * is not provided (<c>NULL</c>) in such case, @ref anjay_fw_update_reset_t\n     * handler will be called from @ref anjay_fw_update_install to reset the\n     * Firmware Update object into the Idle state.\n     *\n     * Optional when <c>result == ANJAY_FW_UPDATE_INITIAL_DOWNLOADED</c>; in\n     * this case it signals that the firmware was downloaded using the Pull\n     * mechanism.\n     *\n     * In all other cases it is ignored.\n     */\n    const char *persisted_uri;\n\n    /**\n     * Number of bytes that has been already successfully downloaded and are\n     * available at the time of calling @ref anjay_fw_update_install .\n     *\n     * It is ignored unless\n     * <c>result == ANJAY_FW_UPDATE_INITIAL_DOWNLOADING</c>, in which case the\n     * following call to @ref anjay_fw_update_stream_write_t shall append the\n     * passed chunk of data at the offset set here. If resumption from the set\n     * offset is impossible, the library will call @ref anjay_fw_update_reset_t\n     * and @ref anjay_fw_update_stream_open_t to restart the download process.\n     */\n    size_t resume_offset;\n\n    /**\n     * ETag of the download process to resume. The passed value is copied, so\n     * the pointer is allowed to become invalid after return from\n     * @ref anjay_fw_update_install .\n     *\n     * Required when <c>result == ANJAY_FW_UPDATE_INITIAL_DOWNLOADING</c> and\n     * <c>resume_offset > 0</c>; if it is not provided (<c>NULL</c>) in such\n     * case, @ref anjay_fw_update_reset_t handler will be called from\n     * @ref anjay_fw_update_install to reset the Firmware Update object into the\n     * Idle state.\n     */\n    const struct anjay_etag *resume_etag;\n\n    /**\n     * Informs the module to try reusing sockets of existing LwM2M Servers to\n     * download the firmware image if the download URI matches any of the LwM2M\n     * Servers.\n     */\n    bool prefer_same_socket_downloads;\n\n#ifdef ANJAY_WITH_SEND\n    /**\n     * Enables using LwM2M Send to report State, Update Result and Firmware\n     * Version to the LwM2M Server (if LwM2M Send is enabled) during firmware\n     * update.\n     */\n    bool use_lwm2m_send;\n#endif // ANJAY_WITH_SEND\n\n#if defined(ANJAY_WITH_LWM2M11) \\\n        && defined(ANJAY_WITH_MODULE_FW_UPDATE_V11_RESOURCES)\n    /**\n     * Value to initialize the Severity resource with.\n     */\n    anjay_fw_update_severity_t persisted_severity;\n\n    /**\n     * Value to initialize the Last State Change Time resource with.\n     */\n    avs_time_real_t persisted_last_state_change_time;\n\n    /**\n     * Update deadline based on Maximum Defer Period resource value and time of\n     * executing Update resource.\n     */\n    avs_time_real_t persisted_update_deadline;\n#endif /* defined(ANJAY_WITH_LWM2M11) && \\\n          defined(ANJAY_WITH_MODULE_FW_UPDATE_V11_RESOURCES) */\n} anjay_fw_update_initial_state_t;\n\n/**\n * Opens the stream that will be used to write the firmware package to.\n *\n * The intended way of implementing this handler is to open a temporary file\n * using <c>fopen()</c> or allocate some memory buffer that may then be used to\n * store the downloaded data in. The library will not attempt to call\n * @ref anjay_fw_update_stream_write_t without having previously called\n * @ref anjay_fw_update_stream_open_t . Please see\n * @ref anjay_fw_update_handlers_t for more information about state transitions.\n *\n * Note that this handler will NOT be called after initializing the object with\n * the <c>ANJAY_FW_UPDATE_INITIAL_DOWNLOADING</c> option, so any necessary\n * resources shall be already open before calling @ref anjay_fw_update_install .\n *\n * @param user_ptr     Opaque pointer to user data, as passed to\n *                     @ref anjay_fw_update_install\n *\n * @param package_uri  URI of the package from which a Pull-mode download is\n *                     performed, or <c>NULL</c> if it is a Push-mode download.\n *                     This argument may either be ignored, or persisted in\n *                     non-volatile storage if the client supports download\n *                     resumption after an unexpected reboot (see\n *                     @ref anjay_fw_update_initial_state_t and its fields).\n *\n * @param package_etag ETag of the data being downloaded in Pull mode, or\n *                     <c>NULL</c> if it is a Push-mode download or ETags are\n *                     not supported by the remote server. This argument may\n *                     either be ignored, or persisted in non-volatile storage\n *                     if the client supports download resumption after an\n *                     unexpected reboot (see\n *                     @ref anjay_fw_update_initial_state_t and its fields).\n *\n * @returns The callback shall return 0 if successful or a negative value in\n *          case of error. Error codes are <strong>NOT</strong> handled here, so\n *          attempting to return <c>ANJAY_FW_UPDATE_ERR_*</c> values will\n *          <strong>NOT</strong> cause any effect different than any other\n *          negative value.\n */\ntypedef int\nanjay_fw_update_stream_open_t(void *user_ptr,\n                              const char *package_uri,\n                              const struct anjay_etag *package_etag);\n\n/**\n * Writes data to the download stream.\n *\n * May be called multipled times after @ref anjay_fw_update_stream_open_t, once\n * for each consecutive chunk of downloaded data.\n *\n * @param user_ptr Opaque pointer to user data, as passed to\n *                 @ref anjay_fw_update_install\n *\n * @param data     Pointer to a chunk of the firmware package being downloaded.\n *                 Guaranteed to be non-<c>NULL</c>.\n *\n * @param length   Number of bytes in the chunk pointed to by <c>data</c>.\n *                 Guaranteed to be greater than zero.\n *\n * @returns The callback shall return 0 if successful or a negative value in\n *          case of error. If one of the <c>ANJAY_FW_UPDATE_ERR_*</c> value is\n *          returned, an equivalent value will be set in the Update Result\n *          Resource.\n */\ntypedef int\nanjay_fw_update_stream_write_t(void *user_ptr, const void *data, size_t length);\n\n/**\n * Closes the download stream and prepares the firmware package to be flashed.\n *\n * Will be called after a series of @ref anjay_fw_update_stream_write_t calls\n * after the whole package is downloaded.\n *\n * The intended way of implementing this handler is to e.g. call <c>fclose()</c>\n * and perform integrity check on the downloaded file. It might also be\n * uncompressed or decrypted as necessary, so that it is ready to be flashed.\n * The exact split of responsibility between\n * @ref anjay_fw_update_stream_finish_t and\n * @ref anjay_fw_update_perform_upgrade_t is not clearly defined and up to the\n * implementor.\n *\n * Note that regardless of the return value, the stream is considered to be\n * closed. That is, upon successful return, the Firmware Update object is\n * considered to be in the <em>Downloaded</em> state, and upon returning an\n * error - in the <em>Idle</em> state.\n *\n * @param user_ptr Opaque pointer to user data, as passed to\n *                 @ref anjay_fw_update_install\n *\n * @returns The callback shall return 0 if successful or a negative value in\n *          case of error. If one of the <c>ANJAY_FW_UPDATE_ERR_*</c> value is\n *          returned, an equivalent value will be set in the Update Result\n *          Resource.\n */\ntypedef int anjay_fw_update_stream_finish_t(void *user_ptr);\n\n/**\n * Resets the firmware update state and performs any applicable cleanup of\n * temporary storage if necessary.\n *\n * Will be called at request of the server, or after a failed download. Note\n * that it may be called without previously calling\n * @ref anjay_fw_update_stream_finish_t, so it shall also close the currently\n * open download stream, if any.\n *\n * @param user_ptr Opaque pointer to user data, as passed to\n *                 @ref anjay_fw_update_install\n */\ntypedef void anjay_fw_update_reset_t(void *user_ptr);\n\n/**\n * Returns the name of downloaded firmware package.\n *\n * The name will be exposed in the data model as the PkgName Resource. If this\n * callback returns <c>NULL</c> or is not implemented at all (with the\n * corresponding field set to <c>NULL</c>), that Resource will not be present in\n * the data model.\n *\n * It only makes sense for this handler to return non-<c>NULL</c> values if\n * there is a valid package already downloaded. The library will not call this\n * handler in any state other than <em>Downloaded</em>.\n *\n * The library will not attempt to deallocate the returned pointer. User code\n * must assure that the pointer will remain valid at least until return from\n * @ref anjay_serve or @ref anjay_sched_run .\n *\n * @param user_ptr Opaque pointer to user data, as passed to\n *                 @ref anjay_fw_update_install\n *\n * @returns The callback shall return a pointer to a null-terminated string\n *          containing the package name, or <c>NULL</c> if it is not currently\n *          available.\n */\ntypedef const char *anjay_fw_update_get_name_t(void *user_ptr);\n\n/**\n * Returns the version of downloaded firmware package.\n *\n * The version will be exposed in the data model as the PkgVersion Resource. If\n * this callback returns <c>NULL</c> or is not implemented at all (with the\n * corresponding field set to <c>NULL</c>), that Resource will not be present in\n * the data model.\n *\n * It only makes sense for this handler to return non-<c>NULL</c> values if\n * there is a valid package already downloaded. The library will not call this\n * handler in any state other than <em>Downloaded</em>.\n *\n * The library will not attempt to deallocate the returned pointer. User code\n * must assure that the pointer will remain valid at least until return from\n * @ref anjay_serve or @ref anjay_sched_run .\n *\n * @param user_ptr Opaque pointer to user data, as passed to\n *                 @ref anjay_fw_update_install\n *\n * @returns The callback shall return a pointer to a null-terminated string\n *          containing the package version, or <c>NULL</c> if it is not\n *          currently available.\n */\ntypedef const char *anjay_fw_update_get_version_t(void *user_ptr);\n\n/**\n * Performs the actual upgrade with previously downloaded package.\n *\n * Will be called at request of the server, after a package has been downloaded.\n *\n * Most users will want to implement firmware update in a way that involves a\n * reboot. In such case, it is expected that this callback will do either one of\n * the following:\n *\n * - perform firmware upgrade, terminate outermost event loop and return,\n *   call reboot after @ref anjay_event_loop_run()\n * - perform the firmware upgrade internally and then reboot, it means that\n *   the return will never happen (although the library won't be able to send\n *   the acknowledgement to execution of Update resource)\n *\n * After rebooting, the result of the upgrade process may be passed to the\n * library during initialization via the <c>initial_result</c> argument to\n * @ref anjay_fw_update_install .\n *\n * Alternatively, if the update can be performed without reinitializing Anjay,\n * you can use @ref anjay_fw_update_set_result (either from within the handler\n * or some time after returning from it) to pass the update result.\n *\n * @param user_ptr Opaque pointer to user data, as passed to\n *                 @ref anjay_fw_update_install\n *\n * @returns The callback shall return a negative value if it can be determined\n *          without a reboot that the firmware upgrade cannot be successfully\n *          performed.\n *\n *          If one of the <c>ANJAY_FW_UPDATE_ERR_*</c> values is returned, an\n *          equivalent value will be set in the Update Result Resource.\n *          Otherwise, if a non-zero value is returned, the Update Result\n *          Resource is set to generic \"Firmware update failed\" code.\n *\n */\ntypedef int anjay_fw_update_perform_upgrade_t(void *user_ptr);\n\n/**\n * Queries security information that shall be used for an encrypted connection\n * with a PULL-mode download server.\n *\n * May be called before @ref anjay_fw_update_stream_open_t if the download is to\n * be performed in PULL mode and the connection needs to use TLS or DTLS\n * encryption.\n *\n * Note that the @ref anjay_security_config_t contains references to file paths,\n * binary security keys, and/or ciphersuite lists. It is the user's\n * responsibility to appropriately allocate them and ensure proper lifetime of\n * the returned pointers. The returned security information may only be\n * invalidated in a call to @ref anjay_fw_update_reset_t or after a call to\n * @ref anjay_delete .\n *\n * If this handler is not implemented at all (with the corresponding field set\n * to <c>NULL</c>), @ref anjay_security_config_from_dm will be used as a default\n * way to get security information.\n *\n * <strong>WARNING:</strong> If the aforementioned @ref\n * anjay_security_config_from_dm function won't find any server\n * connection that matches the <c>download_uri</c> by protocol,\n * hostname and port triple, it'll attempt to match a configuration just by the\n * hostname. This may cause Anjay to use wrong security configuration, e.g. in\n * case when both CoAPS LwM2M server and HTTPS firmware package server have the\n * same hostname, but require different security configs.\n *\n * If no user-defined handler is provided and the call to\n * @ref anjay_security_config_from_dm fails (including case when no matching\n * LwM2M Security Object instance is found, even just by the hostname),\n * @ref anjay_security_config_pkix will be used as an additional fallback\n * if <c>ANJAY_WITH_LWM2M11</c> is enabled and a valid trust store is available\n * (either specified through <c>use_system_trust_store</c>,\n * <c>trust_store_certs</c> or <c>trust_store_crls</c> fields in\n * <c>anjay_configuration_t</c>, or obtained via <c>/est/crts</c> request if\n * <c>est_cacerts_policy</c> is set to\n * <c>ANJAY_EST_CACERTS_IF_EST_CONFIGURED</c> or\n * <c>ANJAY_EST_CACERTS_ALWAYS</c>).\n *\n * You may also use those aforementioned functions\n * (@ref anjay_security_config_from_dm, @ref anjay_security_config_pkix) in\n * your callback, for example as a fallback mechanism.\n *\n * @param user_ptr          Opaque pointer to user data, as passed to\n *                          @ref anjay_fw_update_install\n *\n * @param out_security_info Pointer in which the handler shall fill in\n *                          security configuration to use for download. Note\n *                          that leaving this value as empty without filling\n *                          it in will result in a configuration that is\n *                          <strong>valid, but very insecure</strong>: it will\n *                          cause any server certificate to be accepted\n *                          without validation. Any pointers used within the\n *                          supplied structure shall remain valid until either\n *                          a call to @ref anjay_fw_update_reset_t, or exit to\n *                          the event loop (from either @ref anjay_serve,\n *                          @ref anjay_sched_run or\n *                          @ref anjay_fw_update_install), whichever happens\n *                          first. Anjay will <strong>not</strong> attempt to\n *                          deallocate anything automatically.\n *\n * @param download_uri      Target firmware URI.\n *\n * @returns The callback shall return 0 if successful or a negative value in\n *          case of error. If one of the <c>ANJAY_FW_UPDATE_ERR_*</c> value is\n *          returned, an equivalent value will be set in the Update Result\n *          Resource.\n */\ntypedef int anjay_fw_update_get_security_config_t(\n        void *user_ptr,\n        anjay_security_config_t *out_security_info,\n        const char *download_uri);\n\n/**\n * Returns tx_params used to override default ones.\n *\n * If this handler is not implemented at all (with the corresponding field set\n * to <c>NULL</c>), <c>udp_tx_params</c> from <c>anjay_t</c> object are used.\n *\n * <strong>NOTE:</strong> This callback is called even for non-CoAP downloads,\n * but the returned transmission parameters are ignored in that case.\n *\n * @param user_ptr      Opaque pointer to user data, as passed to\n *                      @ref anjay_fw_update_install .\n *\n * @param download_uri  Target firmware URI.\n *\n * @returns Object with CoAP transmission parameters.\n */\ntypedef avs_coap_udp_tx_params_t\nanjay_fw_update_get_coap_tx_params_t(void *user_ptr, const char *download_uri);\n\n/**\n * Returns request timeout to be used during firmware update over CoAP+TCP or\n * HTTP.\n *\n * If this handler is not implemented at all (with the corresponding field set\n * to <c>NULL</c>), <c>coap_tcp_request_timeout</c> from <c>anjay_t</c> object\n * will be used for CoAP+TCP, and <c>AVS_NET_SOCKET_DEFAULT_RECV_TIMEOUT</c>\n * (i.e., 30 seconds) will be used for HTTP.\n *\n * <strong>NOTE:</strong> This callback is called even for non-TCP downloads,\n * but the returned transmission parameters are ignored in that case.\n *\n * @param user_ptr      Opaque pointer to user data, as passed to\n *                      @ref anjay_fw_update_install .\n *\n * @param download_uri  Target firmware URI.\n *\n * @returns The desired request timeout. If the value returned is non-positive\n *          (including zero and invalid value), the default will be used.\n */\ntypedef avs_time_duration_t\nanjay_fw_update_get_tcp_request_timeout_t(void *user_ptr,\n                                          const char *download_uri);\n\n/**\n * Handler callbacks that shall implement the platform-specific part of firmware\n * update process.\n *\n * The Firmware Update object logic may be in one of the following states:\n *\n * - <strong>Idle</strong>. This is the state in which the object is just after\n *   creation (unless initialized with either\n *   <c>ANJAY_FW_UPDATE_INITIAL_DOWNLOADED</c> or\n *   <c>ANJAY_FW_UPDATE_INITIAL_DOWNLOADING</c>). The following handlers may be\n *   called in this state:\n *   - <c>stream_open</c> - shall open the download stream; moves the object\n *     into the <em>Downloading</em> state\n *   - <c>get_security_config</c> - shall fill in security info that shall be\n *     used for a given URL\n *   - <c>reset</c> - shall free data allocated by <c>get_security_config</c>,\n *     if it was called and there is any\n * - <strong>Downloading</strong>. The object might be initialized directly into\n *   this state by using <c>ANJAY_FW_UPDATE_INITIAL_DOWNLOADING</c>. In this\n *   state, the download stream is open and data may be transferred. The\n *   following handlers may be called in this state:\n *   - <c>stream_write</c> - shall write a chunk of data into the download\n *     stream; it normally does not change state - however, if it fails, it will\n *     be immediately followed by a call to <c>reset</c>\n *   - <c>stream_finish</c> - shall close the download stream and perform\n *     integrity check on the downloaded image; if successful, this moves the\n *     object into the <em>Downloaded</em> state. If failed - into the\n *     <em>Idle</em> state; note that <c>reset</c> will NOT be called in that\n *     case\n *   - <c>reset</c> - shall remove all downloaded data; moves the object into\n *     the <em>Idle</em> state\n * - <strong>Downloaded</strong>. The object might be initialized directly into\n *   this state by using <c>ANJAY_FW_UPDATE_INITIAL_DOWNLOADED</c>. In this\n *   state, the firmware package has been downloaded and checked and is ready to\n *   be flashed. The following handlers may be called in this state:\n *   - <c>reset</c> - shall reset all downloaded data; moves the object into the\n *     <em>Idle</em> state\n *   - <c>get_name</c> - shall return the package name, if available\n *   - <c>get_version</c> - shall return the package version, if available\n *   - <c>perform_upgrade</c> - shall perform the actual upgrade; if it fails,\n *     it does not cause a state change and may be called again; upon success,\n *     it may be treated as a transition to a \"terminal\" state, after which the\n *     device is expected to reboot\n */\ntypedef struct {\n    /** Opens the stream that will be used to write the firmware package to;\n     * @ref anjay_fw_update_stream_open_t */\n    anjay_fw_update_stream_open_t *stream_open;\n    /** Writes data to the download stream;\n     * @ref anjay_fw_update_stream_write_t */\n    anjay_fw_update_stream_write_t *stream_write;\n    /** Closes the download stream and prepares the firmware package to be\n     * flashed; @ref anjay_fw_update_stream_finish_t */\n    anjay_fw_update_stream_finish_t *stream_finish;\n\n    /** Resets the firmware update state and performs any applicable cleanup of\n     * temporary storage if necessary; @ref anjay_fw_update_reset_t */\n    anjay_fw_update_reset_t *reset;\n\n    /** Returns the name of downloaded firmware package;\n     * @ref anjay_fw_update_get_name_t */\n    anjay_fw_update_get_name_t *get_name;\n    /** Return the version of downloaded firmware package;\n     * @ref anjay_fw_update_get_version_t */\n    anjay_fw_update_get_version_t *get_version;\n\n    /** Performs the actual upgrade with previously downloaded package;\n     * @ref anjay_fw_update_perform_upgrade_t */\n    anjay_fw_update_perform_upgrade_t *perform_upgrade;\n\n    /** Queries security configuration that shall be used for an encrypted\n     * connection; @ref anjay_fw_update_get_security_config_t */\n    anjay_fw_update_get_security_config_t *get_security_config;\n\n    /** Queries CoAP transmission parameters to be used during firmware\n     * update; @ref anjay_fw_update_get_coap_tx_params_t */\n    anjay_fw_update_get_coap_tx_params_t *get_coap_tx_params;\n\n    /** Queries request timeout to be used during firmware update over CoAP+TCP\n     * or HTTP; @ref anjay_fw_update_get_tcp_request_timeout_t */\n    anjay_fw_update_get_tcp_request_timeout_t *get_tcp_request_timeout;\n} anjay_fw_update_handlers_t;\n\n/**\n * Installs the Firmware Update object in an Anjay object.\n *\n * The Firmware Update module does not require explicit cleanup; all resources\n * will be automatically freed up during the call to @ref anjay_delete.\n *\n * @param anjay         Anjay object for which the Firmware Update Object is\n *                      installed.\n *\n * @param handlers      Pointer to a set of handler functions that handle the\n *                      platform-specific part of firmware update process.\n *                      Note: Contents of the structure are NOT copied, so it\n *                      needs to remain valid for the lifetime of the object.\n *\n * @param user_arg      Opaque user pointer that will be passed as the first\n *                      argument to handler functions.\n *\n * @param initial_state Information about the state to initialize the Firmware\n *                      Update object in. It is intended to be used after either\n *                      an orderly reboot caused by a firmware update attempt to\n *                      report the update result, or by an unexpected reboot in\n *                      the middle of the download process. If the object shall\n *                      be initialized in a neutral initial state, <c>NULL</c>\n *                      might be passed.\n *\n * @returns 0 on success, or a negative value in case of error.\n */\nint anjay_fw_update_install(\n        anjay_t *anjay,\n        const anjay_fw_update_handlers_t *handlers,\n        void *user_arg,\n        const anjay_fw_update_initial_state_t *initial_state);\n\n/**\n * Sets the Firmware Update Result to @p result, interrupting the update\n * process.\n *\n * A successful call to this function always sets Update State to Idle (0).\n * If the function fails, neither Update State nor Update Result are changed.\n *\n * Some state transitions are disallowed and cause this function to fail:\n *\n * - @ref ANJAY_FW_UPDATE_RESULT_INITIAL and @ref\n *   ANJAY_FW_UPDATE_RESULT_UPDATE_CANCELLED are never allowed and cause this\n *   function to fail.\n *\n * - @ref ANJAY_FW_UPDATE_RESULT_SUCCESS is only allowed if the firmware\n *   application process was started by the server (an Execute operation was\n *   already performed on the Update resource of the Firmware Update object or\n *   @ref ANJAY_FW_UPDATE_INITIAL_UPDATING was used in a call to @ref\n *   anjay_fw_update_install). Otherwise, the function fails.\n *\n * - Other values of @p result (various error codes) are only allowed if\n *   Firmware Update State is not Idle (0), i.e. firmware is being downloaded,\n *   was already downloaded or is being applied.\n *\n * WARNING: calling this in @ref anjay_fw_update_perform_upgrade_t handler is\n * supported, but the result of using it from within any other of\n * @ref anjay_fw_update_handlers_t handlers is undefined.\n *\n * @param anjay  Anjay object to operate on.\n *\n * @param result Value of the Update Result resource to set.\n *\n * @returns 0 on success, or a negative value in case of error.\n */\nint anjay_fw_update_set_result(anjay_t *anjay, anjay_fw_update_result_t result);\n\n#ifdef ANJAY_WITH_DOWNLOADER\n/**\n * Suspends the operation of PULL-mode downloads in the Firmware Update module.\n *\n * This will have the effect of suspending any ongoing downloads (see\n * @ref anjay_download_suspend for details), as well as preventing new downloads\n * from being started.\n *\n * When PULL-mode downloads are suspended, @ref anjay_fw_update_stream_open_t\n * will <strong>NOT</strong> be called when a download request is issued.\n * However, @ref anjay_fw_update_get_security_config_t,\n * @ref anjay_fw_update_get_coap_tx_params_t and\n * @ref anjay_fw_update_get_tcp_request_timeout_t will be called. You may call\n * @ref anjay_fw_update_pull_reconnect from one of these functions if you decide\n * to accept the download immediately after all.\n *\n * @param anjay Anjay object to operate on.\n */\nvoid anjay_fw_update_pull_suspend(anjay_t *anjay);\n\n/**\n * Reconnects any ongoing PULL-mode downloads in the Firmware Update module.\n * Which could be disconnected due to connection loss or deliberate suspend.\n * In the latter case, when PULL-mode downloads are suspended (see\n * @ref anjay_fw_update_pull_suspend), resumes normal operation.\n *\n * If an ongoing PULL-mode download exists, this will call\n * @ref anjay_download_reconnect internally, so you may want to reference the\n * documentation of that function for details.\n *\n * @param anjay Anjay object to operate on.\n *\n * @returns 0 for success; -1 if @p anjay does not have the Firmware Update\n *          object installed or if the call to @ref anjay_download_reconnect\n *          fails.\n */\nint anjay_fw_update_pull_reconnect(anjay_t *anjay);\n#endif // ANJAY_WITH_DOWNLOADER\n\n#if defined(ANJAY_WITH_LWM2M11) \\\n        && defined(ANJAY_WITH_MODULE_FW_UPDATE_V11_RESOURCES)\n/**\n * Gets the update deadline based on Maximum Defer Period resource value and\n * time of downloading full firmware.\n *\n * @param anjay Anjay object to operate on.\n *\n * @returns Real time of the update deadline. In case of not deferring\n * update returns @c AVS_TIME_REAL_INVALID.\n */\navs_time_real_t anjay_fw_update_get_deadline(anjay_t *anjay);\n\n/**\n * Gets the update severity.\n *\n * @param anjay Anjay object to operate on.\n *\n * @returns Severity resource value present in Firmware Update object on\n * success, or @ref ANJAY_FW_UPDATE_SEVERITY_MANDATORY on error.\n */\nanjay_fw_update_severity_t anjay_fw_update_get_severity(anjay_t *anjay);\n\n/**\n * Gets the value of Last State Change Time resource.\n *\n * @param anjay Anjay object to operate on.\n *\n * @returns Real time of last State resource change, or @ref\n * AVS_TIME_REAL_INVALID on error.\n */\navs_time_real_t anjay_fw_update_get_last_state_change_time(anjay_t *anjay);\n#endif /* defined(ANJAY_WITH_LWM2M11) && \\\n          defined(ANJAY_WITH_MODULE_FW_UPDATE_V11_RESOURCES) */\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif /* ANJAY_INCLUDE_ANJAY_FW_UPDATE_H */\n"
  },
  {
    "path": "include_public/anjay/io.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ANJAY_INCLUDE_ANJAY_IO_H\n#define ANJAY_INCLUDE_ANJAY_IO_H\n\n#include <anjay/core.h>\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n/**\n * Type used to return Object Instance or Resource Instance lists.\n */\ntypedef struct anjay_dm_list_ctx_struct anjay_dm_list_ctx_t;\n\n/**\n * Used to return entries from @ref anjay_dm_list_instances_t or\n * @ref anjay_dm_list_resource_instances_t .\n *\n * @param ctx Context passed to the iteration handler.\n * @param id  ID of the returned Object Instance or Resource Instance. MUST NOT\n *            be <c>ANJAY_ID_INVALID</c> / <c>ANJAY_ID_INVALID</c> (65535).\n *\n * This function returns no value. Any errors that may occur are handled\n * internally by the library after the calling handler returns.\n */\nvoid anjay_dm_emit(anjay_dm_list_ctx_t *ctx, uint16_t id);\n\n/**\n * Type used to return Resource lists.\n */\ntypedef struct anjay_dm_resource_list_ctx_struct anjay_dm_resource_list_ctx_t;\n\n/**\n * Kind of a Resource.\n */\ntypedef enum {\n    /**\n     * Read-only Single-Instance Resource. Bootstrap Server might attempt to\n     * write to it anyway.\n     */\n    ANJAY_DM_RES_R,\n\n    /**\n     * Write-only Single-Instance Resource.\n     */\n    ANJAY_DM_RES_W,\n\n    /**\n     * Read/Write Single-Instance Resource.\n     */\n    ANJAY_DM_RES_RW,\n\n    /**\n     * Read-only Multiple Instance Resource. Bootstrap Server might attempt to\n     * write to it anyway.\n     */\n    ANJAY_DM_RES_RM,\n\n    /**\n     * Write-only Multiple Instance Resource.\n     */\n    ANJAY_DM_RES_WM,\n\n    /**\n     * Read/Write Multiple Instance Resource.\n     */\n    ANJAY_DM_RES_RWM,\n\n    /**\n     * Executable Resource.\n     */\n    ANJAY_DM_RES_E,\n\n    /**\n     * Resource that can be read/written only by Bootstrap server.\n     */\n    ANJAY_DM_RES_BS_RW\n} anjay_dm_resource_kind_t;\n\n/**\n * Resource presentness flag.\n */\ntypedef enum {\n    /**\n     * Resource that is absent (not yet instantiable, but might be instantiated\n     * e.g. using a Write operation).\n     */\n    ANJAY_DM_RES_ABSENT = 0,\n\n    /**\n     * Resource that is present.\n     */\n    ANJAY_DM_RES_PRESENT = 1\n} anjay_dm_resource_presence_t;\n\n/**\n * Used to return Resource entries from @ref anjay_dm_list_resources_t .\n *\n * @param ctx      Context passed to the iteration handler.\n * @param rid      ID of the returned Resource. MUST NOT be\n *                 <c>ANJAY_ID_INVALID</c> (65535).\n * @param kind     Kind of the returned Resource.\n * @param presence Flag that indicates whether the Resource is PRESENT.\n *\n * This function returns no value. Any errors that may occur are handled\n * internally by the library after the calling handler returns.\n */\nvoid anjay_dm_emit_res(anjay_dm_resource_list_ctx_t *ctx,\n                       anjay_rid_t rid,\n                       anjay_dm_resource_kind_t kind,\n                       anjay_dm_resource_presence_t presence);\n\n/** Type used to return some content in response to a request from server. */\ntypedef struct anjay_output_ctx_struct anjay_output_ctx_t;\n\n/** Type used to return a chunked blob of data in response to a request from\n * server. Useful in cases where the application needs to send more data than it\n * can fit in the memory. */\ntypedef struct anjay_ret_bytes_ctx_struct anjay_ret_bytes_ctx_t;\n\n/**\n * Marks the beginning of raw data returned from the data model handler. Used\n * in conjunction with @ref anjay_ret_bytes_append to return a large blob of\n * data in multiple chunks.\n *\n * Example: file content in the response.\n *\n * @code\n * FILE *file;\n * size_t filesize;\n * // initialize file and filesize\n *\n * anjay_ret_bytes_ctx_t *bytes_ctx = anjay_ret_bytes_begin(ctx, filesize);\n * if (!bytes_ctx) {\n *     // handle error\n * }\n *\n * size_t bytes_read;\n * char buffer[1024];\n * while ((bytes_read = fread(buffer, 1, sizeof(buffer), file)) > 0) {\n *     if (anjay_ret_bytes_append(bytes_ctx, buffer, bytes_read)) {\n *         // handle error\n *     }\n * }\n *\n * @endcode\n *\n * If a zero-length value is to be returned, it is safe both not to call\n * @ref anjay_ret_bytes_append at all, or to call it any number of times with\n * a <c>length</c> argument equal to zero.\n *\n * @param ctx    Output context to write data into.\n * @param length Size of the data to be written.\n *\n * @returns Output context used to return the data or NULL in case of error.\n */\nanjay_ret_bytes_ctx_t *anjay_ret_bytes_begin(anjay_output_ctx_t *ctx,\n                                             size_t length);\n\n/**\n * Appends a chunk of the data blob to the response message.\n *\n * Note: total number of bytes returned by multiple consecutive successful calls\n * to this function must be equal to the value passed as the length parameter to\n * @ref anjay_ret_bytes_begin that initialized the @p ctx, otherwise the\n * behavior is undefined.\n *\n * @param ctx    Context to operate on.\n * @param data   Data buffer.\n * @param length Number of bytes available in the @p data buffer.\n *\n * @returns 0 on success, a negative value in case of error.\n */\nint anjay_ret_bytes_append(anjay_ret_bytes_ctx_t *ctx,\n                           const void *data,\n                           size_t length);\n\n/**\n * Returns a blob of data from the data model handler.\n *\n * Note: this should be used only for small, self-contained chunks of data.\n * See @ref anjay_ret_bytes_begin documentation for a recommended method of\n * returning large data blobs.\n *\n * @param ctx    Context to operate on.\n * @param data   Data buffer.\n * @param length Number of bytes available in the @p data buffer.\n *\n * @returns 0 on success, a negative value in case of error.\n */\nint anjay_ret_bytes(anjay_output_ctx_t *ctx, const void *data, size_t length);\n\n/**\n * Returns a null-terminated string from the data model handler.\n *\n * @param ctx   Output context to operate on.\n * @param value Null-terminated string to return.\n *\n * @returns 0 on success, a negative value in case of error.\n */\nint anjay_ret_string(anjay_output_ctx_t *ctx, const char *value);\n\n/**\n * Returns a 64-bit signed integer from the data model handler.\n *\n * Note: the only difference between @p anjay_ret_i32 and @p anjay_ret_i64 is\n * the size of the @p value parameter. Actual number of bytes sent on the wire\n * depends on the @p value.\n *\n * @param ctx   Output context to operate on.\n * @param value The value to return.\n *\n * @returns 0 on success, a negative value in case of error.\n */\nint anjay_ret_i64(anjay_output_ctx_t *ctx, int64_t value);\n\n/**\n * Returns a 32-bit signed integer from the data model handler.\n *\n * Note: the only difference between @p anjay_ret_i32 and @p anjay_ret_i64 is\n * the size of the @p value parameter. Actual number of bytes sent on the wire\n * depends on the @p value.\n *\n * @param ctx   Output context to operate on.\n * @param value The value to return.\n *\n * @returns 0 on success, a negative value in case of error.\n */\nstatic inline int anjay_ret_i32(anjay_output_ctx_t *ctx, int32_t value) {\n    return anjay_ret_i64(ctx, value);\n}\n\n#ifdef ANJAY_WITH_LWM2M11\n/**\n * Returns a 64-bit unsigned integer from the data model handler.\n *\n * Note: the only difference between @p anjay_ret_u32 and @p anjay_ret_u64 is\n * the size of the @p value parameter. Actual number of bytes sent on the wire\n * depends on the @p value.\n *\n * @param ctx   Output context to operate on.\n * @param value The value to return.\n *\n * @returns 0 on success, a negative value in case of error.\n */\nint anjay_ret_u64(anjay_output_ctx_t *ctx, uint64_t value);\n\n/**\n * Returns a 32-bit unsigned integer from the data model handler.\n *\n * Note: the only difference between @p anjay_ret_u32 and @p anjay_ret_u64 is\n * the size of the @p value parameter. Actual number of bytes sent on the wire\n * depends on the @p value.\n *\n * @param ctx   Output context to operate on.\n * @param value The value to return.\n *\n * @returns 0 on success, a negative value in case of error.\n */\nstatic inline int anjay_ret_u32(anjay_output_ctx_t *ctx, uint32_t value) {\n    return anjay_ret_u64(ctx, value);\n}\n#endif // ANJAY_WITH_LWM2M11\n\n/**\n * Returns a 64-bit floating-point value from the data model handler.\n *\n * Note: the @p value will be sent as a 32-bit floating-point value if it is\n * exactly representable as such.\n *\n * @param ctx   Output context to operate on.\n * @param value The value to return.\n *\n * @returns 0 on success, a negative value in case of error.\n */\nint anjay_ret_double(anjay_output_ctx_t *ctx, double value);\n\n/**\n * Returns a 32-bit floating-point value from the data model handler.\n *\n * @param ctx   Output context to operate on.\n * @param value The value to return.\n *\n * @returns 0 on success, a negative value in case of error.\n */\nstatic inline int anjay_ret_float(anjay_output_ctx_t *ctx, float value) {\n    return anjay_ret_double(ctx, value);\n}\n\n/**\n * Returns a boolean value from the data model handler.\n *\n * @param ctx   Output context to operate on.\n * @param value The value to return.\n *\n * @returns 0 on success, a negative value in case of error.\n */\nint anjay_ret_bool(anjay_output_ctx_t *ctx, bool value);\n\n/**\n * Returns a object link (Object ID/Instance ID pair) from the\n * data model handler.\n *\n * @param ctx Output context to operate on.\n * @param oid Object ID part of the link.\n * @param iid Object Instance ID part of the link.\n *\n * @returns 0 on success, a negative value in case of error.\n */\nint anjay_ret_objlnk(anjay_output_ctx_t *ctx, anjay_oid_t oid, anjay_iid_t iid);\n\n#ifdef ANJAY_WITH_SECURITY_STRUCTURED\n/**\n * Returns information about a certificate chain from the data model handler.\n *\n * NOTE: This function is <strong>ONLY</strong> intended to be used when\n * handling the \"Public Key or Identity\" resource in custom implementations of\n * the LwM2M Security object (i.e., when not using\n * @ref anjay_security_object_install). In this context, it may be used to pass\n * client certificate configuration that is not representable through standard\n * LwM2M format. In all other cases, @ref anjay_ret_bytes family of functions\n * SHOULD be used.\n *\n * @param ctx                    Output context to operate on.\n *\n * @param certificate_chain_info Certificate chain information to return. A deep\n *                               copy will immediately be created, so it is safe\n *                               to invalidate any referenced buffers just after\n *                               this call.\n *\n * @returns 0 on success, a negative value in case of error.\n */\nint anjay_ret_certificate_chain_info(\n        anjay_output_ctx_t *ctx,\n        avs_crypto_certificate_chain_info_t certificate_chain_info);\n\n/**\n * Returns information about a private key from the data model handler.\n *\n * NOTE: This function is <strong>ONLY</strong> intended to be used when\n * handling the \"Secret Key\" resource in custom implementations of the LwM2M\n * Security object (i.e., when not using @ref anjay_security_object_install).\n * In this context, it may be used to pass client certificate configuration that\n * is not representable through standard LwM2M format. In all other cases,\n * @ref anjay_ret_bytes family of functions SHOULD be used.\n *\n * @param ctx              Output context to operate on.\n *\n * @param private_key_info Private key information to return. A deep copy will\n *                         immediately be created, so it is safe to invalidate\n *                         any referenced buffers just after this call.\n *\n * @returns 0 on success, a negative value in case of error.\n */\nint anjay_ret_private_key_info(anjay_output_ctx_t *ctx,\n                               avs_crypto_private_key_info_t private_key_info);\n\n/**\n * Returns information about a PSK identity from the data model handler.\n *\n * NOTE: This function is <strong>ONLY</strong> intended to be used when\n * handling the \"Public Key Or Identity\" or \"SMS Binding Key Parameters\"\n * resource in custom implementations of the LwM2M Security object (i.e., when\n * not using @ref anjay_security_object_install). In this context, it may be\n * used to pass key configuration that is not representable through standard\n * LwM2M format. In all other cases, @ref anjay_ret_bytes family of functions\n * SHOULD be used.\n *\n * @param ctx               Output context to operate on.\n *\n * @param psk_identity_info PSK identity information to return. A deep copy will\n *                          immediately be created, so it is safe to invalidate\n *                          any referenced buffers just after this call.\n *\n * @returns 0 on success, a negative value in case of error.\n */\nint anjay_ret_psk_identity_info(\n        anjay_output_ctx_t *ctx,\n        avs_crypto_psk_identity_info_t psk_identity_info);\n\n/**\n * Returns information about a PSK key from the data model handler.\n *\n * NOTE: This function is <strong>ONLY</strong> intended to be used when\n * handling the \"Secret Key\" or \"SMS Binding Secret Key(s)\" resource in custom\n * implementations of the LwM2M Security object (i.e., when not using @ref\n * anjay_security_object_install). In this context, it may be used to pass\n * key configuration that is not representable through standard LwM2M format. In\n * all other cases, @ref anjay_ret_bytes family of functions SHOULD be used.\n *\n * @param ctx          Output context to operate on.\n *\n * @param psk_key_info PSK key information to return. A deep copy will\n *                     immediately be created, so it is safe to invalidate any\n *                     referenced buffers just after this call.\n *\n * @returns 0 on success, a negative value in case of error.\n */\nint anjay_ret_psk_key_info(anjay_output_ctx_t *ctx,\n                           avs_crypto_psk_key_info_t psk_key_info);\n#endif // ANJAY_WITH_SECURITY_STRUCTURED\n\n/** Type used to retrieve request content. */\ntypedef struct anjay_input_ctx_struct anjay_input_ctx_t;\n\n#define ANJAY_EXECUTE_GET_ARG_END 1\n/** Type used to retrieve execute command. */\ntypedef struct anjay_execute_ctx_struct anjay_execute_ctx_t;\n\n/**\n * Reads next argument from execute request content.\n *\n * Returns @ref ANJAY_ERR_BAD_REQUEST to indicate the message is malformed and\n * user should forward this code as the return value of @ref\n * anjay_dm_resource_execute_t . Arguments are parsed sequentially so not\n * necessarily the first call of this function will return an error. In case of\n * an error all data read up to the point when an error occurs should be\n * considered invalid.\n *\n * User not interested in argument value (or interested in ignoring the value\n * after reading some part of it), can safely call this function to skip tail of\n * the value and get next argument or an EOF information.\n *\n * @param ctx           Execute context\n * @param out_arg       Obtained argument id\n * @param out_has_value true if argument has a value, false otherwise\n *\n * @returns 0 on success, @ref ANJAY_ERR_BAD_REQUEST in case of malformed\n *          message, @ref ANJAY_EXECUTE_GET_ARG_END in case of end of message\n *          (in which case @p out_arg is set to -1, and @p out_has_value to\n *          @c false)\n */\nint anjay_execute_get_next_arg(anjay_execute_ctx_t *ctx,\n                               int *out_arg,\n                               bool *out_has_value);\n\n/**\n * Attempts to read currently processed argument's value (or part of it).\n * Read data is written as null-terminated string into @p out_buf.\n *\n * Returns @ref ANJAY_ERR_BAD_REQUEST to indicate the message is malformed and\n * user should forward this code as the return value of @ref\n * anjay_dm_resource_execute_t .\n *\n * Function might return 0 when there is nothing more to read or because\n * argument does not have associated value with it, or because the value has\n * already been read / skipped entirely.\n *\n * When the output buffer is not big enough to contain whole message content +\n * terminating nullbyte, ANJAY_BUFFER_TOO_SHORT is returned, after which further\n * calls can be made, to retrieve more data.\n *\n * In case of an error following values are returned:\n * - -1 if buf_size < 2 or out_buf is NULL\n * - @ref ANJAY_ERR_BAD_REQUEST in case of malformed message\n *\n * In such cases all data read up to this point should be considered invalid.\n *\n * @param ctx            Execute context\n * @param out_bytes_read Pointer to a variable that, on successful exit, will be\n *                       set to the number of bytes read (not counting the\n *                       terminating null-byte). May be NULL if not needed.\n * @param out_buf        Buffer where read bytes will be stored\n * @param buf_size       Size of the buffer\n *\n * @returns 0 on success, a negative value in case of error,\n *          ANJAY_BUFFER_TOO_SHORT if the buffer is not big enough to contain\n *          whole message content + terminating nullbyte.\n */\nint anjay_execute_get_arg_value(anjay_execute_ctx_t *ctx,\n                                size_t *out_bytes_read,\n                                char *out_buf,\n                                size_t buf_size);\n\n/**\n * Reads a chunk of data blob from the request message.\n *\n * Consecutive calls to this function will return successive chunks of\n * the data blob. Reaching end of the data is signaled by setting the\n * @p out_message_finished flag.\n *\n * A call to this function will always attempt to read as much data as possible.\n *\n * Example: writing a large data blob to file.\n *\n * @code\n * FILE *file;\n * // initialize file\n *\n * bool finished;\n * size_t bytes_read;\n * char buf[1024];\n *\n * do {\n *     if (anjay_get_bytes(ctx, &bytes_read, &finished, buf, sizeof(buf))\n *             || fwrite(buf, 1, bytes_read, file) < bytes_read) {\n *         // handle error\n *     }\n * } while (!finished);\n *\n * @endcode\n *\n * @param      ctx                  Input context to operate on.\n * @param[out] out_bytes_read       Number of bytes read.\n * @param[out] out_message_finished Set to true if there is no more data\n *                                  to read.\n * @param[out] out_buf              Buffer to read data into.\n * @param      buf_size             Number of bytes available in @p out_buf .\n *\n * @returns 0 on success, a negative value in case of error.\n */\nint anjay_get_bytes(anjay_input_ctx_t *ctx,\n                    size_t *out_bytes_read,\n                    bool *out_message_finished,\n                    void *out_buf,\n                    size_t buf_size);\n\n#define ANJAY_BUFFER_TOO_SHORT 1\n/**\n * Reads a null-terminated string from the request content. On success or even\n * when @ref ANJAY_BUFFER_TOO_SHORT is returned, the content inside @p out_buf\n * is always null-terminated. On failure, the contents of @p out_buf are\n * undefined.\n *\n * When the input buffer is not big enough to contain whole message content +\n * terminating nullbyte, ANJAY_BUFFER_TOO_SHORT is returned, after which further\n * calls can be made, to retrieve more data.\n *\n * @param      ctx                  Input context to operate on.\n * @param[out] out_buf              Buffer to read data into.\n * @param      buf_size             Number of bytes available in @p out_buf .\n *                                  Must be at least 1.\n *\n * @returns 0 on success, a negative value in case of error,\n *          @ref ANJAY_BUFFER_TOO_SHORT if the buffer is not big enough to\n *          contain whole message content + terminating nullbyte.\n */\nint anjay_get_string(anjay_input_ctx_t *ctx, char *out_buf, size_t buf_size);\n\n/**\n * Reads an integer as a 32-bit signed value from the request content.\n *\n * @param      ctx Input context to operate on.\n * @param[out] out Returned value. If the call is not successful, it is\n *                 guaranteed to be left untouched.\n *\n * @returns 0 on success, a negative value in case of error.\n */\nint anjay_get_i32(anjay_input_ctx_t *ctx, int32_t *out);\n\n/**\n * Reads an integer as a 64-bit signed value from the request content.\n *\n * @param      ctx Input context to operate on.\n * @param[out] out Returned value. If the call is not successful, it is\n *                 guaranteed to be left untouched.\n *\n * @returns 0 on success, a negative value in case of error.\n */\nint anjay_get_i64(anjay_input_ctx_t *ctx, int64_t *out);\n\n#ifdef ANJAY_WITH_LWM2M11\n/**\n * Reads an unsigned integer as a 32-bit unsigned value from the request\n * content.\n *\n * @param      ctx Input context to operate on.\n * @param[out] out Returned value. If the call is not successful, it is\n *                 guaranteed to be left untouched.\n *\n * @returns 0 on success, a negative value in case of error.\n */\nint anjay_get_u32(anjay_input_ctx_t *ctx, uint32_t *out);\n\n/**\n * Reads an unsigned integer as a 64-bit unsigned value from the request\n * content.\n *\n * @param      ctx Input context to operate on.\n * @param[out] out Returned value. If the call is not successful, it is\n *                 guaranteed to be left untouched.\n *\n * @returns 0 on success, a negative value in case of error.\n */\nint anjay_get_u64(anjay_input_ctx_t *ctx, uint64_t *out);\n#endif // ANJAY_WITH_LWM2M11\n\n/**\n * Reads a floating-point value as a float from the request content.\n *\n * @param      ctx Input context to operate on.\n * @param[out] out Returned value. If the call is not successful, it is\n *                 guaranteed to be left untouched.\n *\n * @returns 0 on success, a negative value in case of error.\n */\nint anjay_get_float(anjay_input_ctx_t *ctx, float *out);\n\n/**\n * Reads a floating-point value as a double from the request content.\n *\n * @param      ctx Input context to operate on.\n * @param[out] out Returned value. If the call is not successful, it is\n *                 guaranteed to be left untouched.\n *\n * @returns 0 on success, a negative value in case of error.\n */\nint anjay_get_double(anjay_input_ctx_t *ctx, double *out);\n\n/**\n * Reads a boolean value from the request content.\n *\n * @param      ctx Input context to operate on.\n * @param[out] out Returned value. If the call is not successful, it is\n *                 guaranteed to be left untouched.\n *\n * @returns 0 on success, a negative value in case of error.\n */\nint anjay_get_bool(anjay_input_ctx_t *ctx, bool *out);\n\n/**\n * Reads an object link (Object ID/Object Instance ID pair) from the request\n * content.\n *\n * @param      ctx     Input context to operate on.\n * @param[out] out_oid Object ID part of the returned value.\n * @param[out] out_iid Object Instance ID part of the returned value.\n *\n * @returns 0 on success, a negative value in case of error.\n *\n * In case of error, <c>out_oid</c> and <c>out_iid</c> are guaranteed to be left\n * untouched.\n */\nint anjay_get_objlnk(anjay_input_ctx_t *ctx,\n                     anjay_oid_t *out_oid,\n                     anjay_iid_t *out_iid);\n\n#ifdef __cplusplus\n} /* extern \"C\" */\n#endif\n\n#endif /*ANJAY_INCLUDE_ANJAY_IO_H*/\n"
  },
  {
    "path": "include_public/anjay/ipso_objects.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n#ifndef ANJAY_IPSO_OBJECTS_H\n#define ANJAY_IPSO_OBJECTS_H\n\n#include <anjay/dm.h>\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n/**\n * Type of the user provided callbacks to read a basic sensor value.\n *\n * @param iid   IID of the instance for which the value will be read.\n * @param ctx   User provided context.\n * @param value Output.\n *\n * @returns 0 on success, or a negative value in case of error.\n */\ntypedef int anjay_ipso_basic_sensor_value_reader_t(anjay_iid_t iid,\n                                                   void *ctx,\n                                                   double *value);\n\ntypedef struct anjay_ipso_basic_sensor_impl_struct {\n    /**\n     * Unit of the measured values.\n     *\n     * The pointed string won't be copied, so user code must assure that the\n     * pointer will remain valid for the lifetime of the object.\n     */\n    const char *unit;\n\n    /**\n     * User context which will be passed to @ref get_value callback.\n     */\n    void *user_context;\n\n    /**\n     * The minimum value that can be measured by the sensor.\n     *\n     * If the value is NaN the resource won't be created.\n     */\n    double min_range_value;\n\n    /**\n     * The maximum value that can be measured by the sensor.\n     *\n     * If the value is NaN the resource won't be created.\n     */\n    double max_range_value;\n\n    /**\n     * User provided callback for reading the sensor value.\n     */\n    anjay_ipso_basic_sensor_value_reader_t *get_value;\n} anjay_ipso_basic_sensor_impl_t;\n\n/**\n * Installs a basic sensor object in an Anjay object.\n *\n * @param anjay         Anjay object for which the sensor object is installed.\n * @param oid           OID of the installed object.\n * @param num_instances Maximum number of the instances which will be created\n *                      for the installed object.\n *\n * @returns 0 on success, or a negative value in case of error.\n */\nint anjay_ipso_basic_sensor_install(anjay_t *anjay,\n                                    anjay_oid_t oid,\n                                    size_t num_instances);\n\n/**\n * Adds an instance of a sensor object installed in an Anjay object.\n *\n * @param anjay Anjay object with the installed the sensor object.\n * @param oid   OID of the installed object.\n * @param iid   IID of the added instance. Should be lower than\n *              the number of instances passed to the corresponding\n *              @ref anjay_ipso_basic_sensor_install\n * @param impl  Parameters and callbacks needed to initialize an instance.\n *\n * @returns 0 on success, or a negative value in case of error.\n */\nint anjay_ipso_basic_sensor_instance_add(\n        anjay_t *anjay,\n        anjay_oid_t oid,\n        anjay_iid_t iid,\n        const anjay_ipso_basic_sensor_impl_t impl);\n\n/**\n * Removes an instance of a sensor object installed in an Anjay object.\n *\n * @param anjay Anjay object with the installed the sensor object.\n * @param oid   OID of the installed object.\n * @param iid   IID of the removed instance. Should be lower than\n *              the number of instances passed to the corresponding\n *              @ref anjay_ipso_basic_sensor_install\n *\n * @returns 0 on success, or a negative value in case of error.\n */\nint anjay_ipso_basic_sensor_instance_remove(anjay_t *anjay,\n                                            anjay_oid_t oid,\n                                            anjay_iid_t iid);\n\n/**\n * Updates a basic sensor object installed in an Anjay object.\n *\n * @param anjay Anjay object with the installed the sensor object.\n * @param oid   OID of the installed object.\n * @param iid   IID of the updated instance.\n */\nint anjay_ipso_basic_sensor_update(anjay_t *anjay,\n                                   anjay_oid_t oid,\n                                   anjay_iid_t iid);\n\n/**\n * Type of the user provided callbacks to read the three-axis sensor value.\n *\n * @param iid     IID of the instance reading the value.\n * @param ctx     User provided context.\n * @param x_value X axis output.\n * @param y_value Y axis output.\n * @param z_value Z axis output.\n *\n * @returns 0 on success, or a negative value in case of error.\n */\ntypedef int anjay_ipso_3d_sensor_value_reader_t(anjay_iid_t iid,\n                                                void *ctx,\n                                                double *x_value,\n                                                double *y_value,\n                                                double *z_value);\n\ntypedef struct anjay_ipso_3d_sensor_impl_struct {\n    /**\n     * Unit of the measured values.\n     *\n     * The pointed string won't be copied, so user code must assure that the\n     * pointer will remain valid for the lifetime of the object.\n     */\n    const char *unit;\n    /**\n     * Enables usage of the optional Y axis.\n     */\n    bool use_y_value;\n    /**\n     * Enables usage of the optional Z axis.\n     */\n    bool use_z_value;\n\n    /**\n     * User context which will be passed to @ref get_values callback.\n     */\n    void *user_context;\n\n    /**\n     * The minimum value that can be measured by the sensor.\n     *\n     * If the value is NaN the resource won't be created.\n     */\n    double min_range_value;\n\n    /**\n     * The maximum value that can be measured by the sensor.\n     *\n     * If the value is NaN the resource won't be created.\n     */\n    double max_range_value;\n\n    /**\n     * User provided callback for reading the sensor value.\n     */\n    anjay_ipso_3d_sensor_value_reader_t *get_values;\n} anjay_ipso_3d_sensor_impl_t;\n\n/**\n * Installs a three-axis sensor object in an Anjay object.\n *\n * @param anjay         Anjay object for which the sensor object is installed.\n * @param oid           OID of the installed object.\n * @param num_instances Maximum number of the instances which will be created\n *                      for the installed object.\n *\n * @returns 0 on success, or a negative value in case of error.\n */\nint anjay_ipso_3d_sensor_install(anjay_t *anjay,\n                                 anjay_oid_t oid,\n                                 size_t num_instances);\n\n/**\n * Adds an instance of a three-axis sensor object installed in an Anjay object.\n *\n * @param anjay Anjay object with the installed the sensor object.\n * @param oid   OID of the installed object.\n * @param iid   IID of the added instance. Should be lower than\n *              the number of instances passed to the corresponding\n *              @ref anjay_ipso_3d_sensor_install\n * @param impl  Parameters and callbacks needed to initialize an instance.\n *\n * @returns 0 on success, or a negative value in case of error.\n */\nint anjay_ipso_3d_sensor_instance_add(anjay_t *anjay,\n                                      anjay_oid_t oid,\n                                      anjay_iid_t iid,\n                                      const anjay_ipso_3d_sensor_impl_t impl);\n\n/**\n * Removes an instance of a three-axis sensor object installed in an Anjay\n * object.\n *\n * @param anjay Anjay object with the installed the sensor object.\n * @param oid   OID of the installed object.\n * @param iid   IID of the removed instance.\n *\n * @returns 0 on success, or a negative value in case of error.\n */\nint anjay_ipso_3d_sensor_instance_remove(anjay_t *anjay,\n                                         anjay_oid_t oid,\n                                         anjay_iid_t iid);\n\n/**\n * Updates a three-axis sensor object installed in an Anjay object.\n *\n * @param anjay Anjay object with the installed the sensor object.\n * @param oid   OID of the installed object.\n * @param iid   IID of the updated instance.\n */\nint anjay_ipso_3d_sensor_update(anjay_t *anjay,\n                                anjay_oid_t oid,\n                                anjay_iid_t iid);\n\n/**\n * Installs button object in the given Anjay object.\n *\n * @param anjay         Anjay object for the Push Button Object installation.\n * @param num_instances Maximum number of the instances of the installed object.\n *\n * @returns 0 on success, or a negative value in case of error.\n */\nint anjay_ipso_button_install(anjay_t *anjay, size_t num_instances);\n\n/**\n * Add an instance of the Push Button Object installed in an Anjay\n * object.\n *\n * @param anjay            Anjay object with the Push Button Object installed.\n * @param iid              IID of the added instance.\n * @param application_type \"Application type\" string for the button instance,\n *                         is copied during the instance initialization, should\n *                         not be longer than 40 characters.\n *\n * @returns 0 on success, or a negative value in case of error.\n */\nint anjay_ipso_button_instance_add(anjay_t *anjay,\n                                   anjay_iid_t iid,\n                                   const char *application_type);\n\n/**\n * Remove an instance of the Push Button Object installed in an Anjay\n * object.\n *\n * @param anjay Anjay object with the Push Button Object installed.\n * @param iid   IID of the removed instance.\n *\n * @returns 0 on success, or a negative value in case of error.\n */\nint anjay_ipso_button_instance_remove(anjay_t *anjay, anjay_iid_t iid);\n\n/**\n * Updates Push Button Object installed in an Anjay object.\n *\n * @param anjay   Anjay object with the Push Button Object installed.\n * @param iid     IID of the removed instance.\n * @param pressed New state of the button (true if pressed).\n *\n * @returns 0 on success, or a negative value in case of error.\n */\nint anjay_ipso_button_update(anjay_t *anjay, anjay_iid_t iid, bool pressed);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif // ANJAY_IPSO_OBJECTS_H\n"
  },
  {
    "path": "include_public/anjay/ipso_objects_v2.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n#ifndef ANJAY_IPSO_OBJECTS_V2_H\n#define ANJAY_IPSO_OBJECTS_V2_H\n\n#include <anjay/dm.h>\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n/**\n * IPSO basic sensor object instance metadata.\n */\ntypedef struct anjay_ipso_v2_basic_sensor_meta_struct {\n    /**\n     * Unit of the measured values.\n     *\n     * This value is optional; \"Sensor Unit\" resource will not be created if\n     * this value is set to <c>NULL</c>.\n     *\n     * The pointed string won't be copied, so user code must assure that the\n     * pointer will remain valid for the lifetime of the object.\n     */\n    const char *unit;\n\n    /**\n     * Set to <c>true</c> to enable \"Min Measured Value\", \"Max Measured Value\",\n     * and \"Reset Min and Max Measured Values\" resources.\n     */\n    bool min_max_measured_value_present;\n\n    /**\n     * The minimum value that can be measured by the sensor.\n     *\n     * This value is optional; \"Min Range Value\" resource will not be created if\n     * this value is set to NaN.\n     */\n    double min_range_value;\n\n    /**\n     * The maximum value that can be measured by the sensor.\n     *\n     * This value is optional; \"Min Range Value\" resource will not be created if\n     * this value is set to NaN.\n     */\n    double max_range_value;\n} anjay_ipso_v2_basic_sensor_meta_t;\n\n/**\n * IPSO three-axis sensor object instance metadata.\n */\ntypedef struct anjay_ipso_v2_3d_sensor_meta_struct {\n    /**\n     * Unit of the measured values.\n     *\n     * This value is optional; \"Sensor Unit\" resource will not be created if\n     * this value is set to <c>NULL</c>.\n     *\n     * The pointed string won't be copied, so user code must assure that the\n     * pointer will remain valid for the lifetime of the object.\n     */\n    const char *unit;\n\n    /**\n     * Set to <c>true</c> to enable \"Y Value\" resource.\n     */\n    bool y_axis_present;\n\n    /**\n     * Set to <c>true</c> to enable \"Z Value\" resource.\n     */\n    bool z_axis_present;\n\n    /**\n     * Set to <c>true</c> to enable:\n     * - \"Min X Value\", \"Max X Value\",\n     * - \"Min Y Value\", \"Max Y Value\" (if <c>y_axis_present</c>),\n     * - \"Min Z Value\", \"Max Z Value\" (if <c>z_axis_present</c>),\n     * - \"Reset Min and Max Measured Values\"\n     *\n     * resources.\n     */\n    bool min_max_measured_value_present;\n\n    /**\n     * The minimum value that can be measured by the sensor.\n     *\n     * If the value is NaN the resource won't be created.\n     */\n    double min_range_value;\n\n    /**\n     * The maximum value that can be measured by the sensor.\n     *\n     * If the value is NaN the resource won't be created.\n     */\n    double max_range_value;\n} anjay_ipso_v2_3d_sensor_meta_t;\n\n/**\n * Value of IPSO three-axis sensor object.\n */\ntypedef struct anjay_ipso_v2_3d_sensor_value_struct {\n    /**\n     * Value of X axis. Must always be set.\n     */\n    double x;\n\n    /**\n     * Value of Y axis. Must be set only if Y axis is present.\n     */\n    double y;\n\n    /**\n     * Value of Z axis. Must be set only if Z axis is present.\n     */\n    double z;\n} anjay_ipso_v2_3d_sensor_value_t;\n\n/**\n * Installs a basic IPSO object.\n *\n * @param anjay          Anjay object for which the object is installed.\n * @param oid            Object ID of installed object.\n * @param version        Object version. This value is optional; version will\n *                       not be reported if this value is set to <c>NULL</c>.\n *                       The pointed string is not copied, so user code must\n *                       assure that the pointer will remain valid for the\n *                       lifetime of the object.\n * @param instance_count Maximum count of instances of installed object.\n *\n * @returns 0 on success, or a negative value in case of error.\n */\nint anjay_ipso_v2_basic_sensor_install(anjay_t *anjay,\n                                       anjay_oid_t oid,\n                                       const char *version,\n                                       size_t instance_count);\n\n/**\n * Adds an instance of basic IPSO object. Requires the object to be installed\n * first with @ref anjay_ipso_v2_basic_sensor_install .\n *\n * @param anjay         Anjay object for which the instance is added.\n * @param oid           Object ID of added object instance.\n * @param iid           Instance ID of added object instance. Must be lower\n *                      than number of <c>instance_count</c> parameter passed to\n *                      @ref anjay_ipso_v2_basic_sensor_install\n * @param initial_value Initial sensor value.\n * @param meta          Metadata about added object instance.\n *\n * @returns 0 on success, or a negative value in case of error.\n */\nint anjay_ipso_v2_basic_sensor_instance_add(\n        anjay_t *anjay,\n        anjay_oid_t oid,\n        anjay_iid_t iid,\n        double initial_value,\n        const anjay_ipso_v2_basic_sensor_meta_t *meta);\n\n/**\n * Updates sensor value of basic IPSO object, and also minimum and maximum\n * measured values.\n *\n * This method should be called frequently if user needs LwM2M observations to\n * behave responsively.\n *\n * CAUTION: Do not call this method from interrupts.\n *\n * @param anjay Anjay object with an installed basic IPSO object.\n * @param oid   Object ID of object instance of which the sensor value is\n *              updated.\n * @param iid   Instance ID of object instance of which the sensor value is\n *              updated.\n * @param value New sensor value.\n *\n * @returns 0 on success, or a negative value in case of error.\n */\nint anjay_ipso_v2_basic_sensor_value_update(anjay_t *anjay,\n                                            anjay_oid_t oid,\n                                            anjay_iid_t iid,\n                                            double value);\n\n/**\n * Removes an instance of basic IPSO object.\n *\n * @param anjay Anjay object with an installed basic IPSO object.\n * @param oid   Object ID of object instance to remove.\n * @param iid   Instance ID of object instance to remove.\n *\n * @returns 0 on success, or a negative value in case of error.\n */\nint anjay_ipso_v2_basic_sensor_instance_remove(anjay_t *anjay,\n                                               anjay_oid_t oid,\n                                               anjay_iid_t iid);\n\n/**\n * Installs a three-axis IPSO object.\n *\n * @param anjay          Anjay object for which the object is installed.\n * @param oid            Object ID of installed object.\n * @param version        Object version. This value is optional; version will\n *                       not be reported if this value is set to <c>NULL</c>.\n *                       The pointed string is not copied, so user code must\n *                       assure that the pointer will remain valid for the\n *                       lifetime of the object.\n * @param instance_count Maximum count of instances of installed object.\n *\n * @returns 0 on success, or a negative value in case of error.\n */\nint anjay_ipso_v2_3d_sensor_install(anjay_t *anjay,\n                                    anjay_oid_t oid,\n                                    const char *version,\n                                    size_t instance_count);\n\n/**\n * Adds an instance of three-axis IPSO object. Requires the object to be\n * installed first with @ref anjay_ipso_v2_3d_sensor_install .\n *\n * @param anjay         Anjay object for which the instance is added.\n * @param oid           Object ID of added object instance.\n * @param iid           Instance ID of added object instance. Must be lower\n *                      than number of <c>instance_count</c> parameter passed to\n *                      @ref anjay_ipso_v2_3d_sensor_install\n * @param initial_value Initial sensor value.\n * @param meta          Metadata about added object instance.\n *\n * @returns 0 on success, or a negative value in case of error.\n */\nint anjay_ipso_v2_3d_sensor_instance_add(\n        anjay_t *anjay,\n        anjay_oid_t oid,\n        anjay_iid_t iid,\n        const anjay_ipso_v2_3d_sensor_value_t *initial_value,\n        const anjay_ipso_v2_3d_sensor_meta_t *meta);\n\n/**\n * Updates sensor value of three-axis IPSO object.\n *\n * This method should be called frequently if user needs LwM2M observations to\n * behave responsively.\n *\n * CAUTION: Do not call this method from interrupts.\n *\n * @param anjay Anjay object with an installed three-axis IPSO object.\n * @param oid   Object ID of object instance of which the sensor value is\n *              updated.\n * @param iid   Instance ID of object instance of which the sensor value is\n *              updated.\n * @param value New sensor value.\n *\n * @returns 0 on success, or a negative value in case of error.\n */\nint anjay_ipso_v2_3d_sensor_value_update(\n        anjay_t *anjay,\n        anjay_oid_t oid,\n        anjay_iid_t iid,\n        const anjay_ipso_v2_3d_sensor_value_t *value);\n\n/**\n * Removes an instance of three-axis IPSO object.\n *\n * @param anjay Anjay object with an installed three-axis IPSO object.\n * @param oid   Object ID of object instance to remove.\n * @param iid   Instance ID of object instance to remove.\n *\n * @returns 0 on success, or a negative value in case of error.\n */\nint anjay_ipso_v2_3d_sensor_instance_remove(anjay_t *anjay,\n                                            anjay_oid_t oid,\n                                            anjay_iid_t iid);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif // ANJAY_IPSO_OBJECTS_V2_H\n"
  },
  {
    "path": "include_public/anjay/lwm2m_gateway.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ANJAY_INCLUDE_LWM2M_GATEWAY_H\n#define ANJAY_INCLUDE_LWM2M_GATEWAY_H\n\n#include <anjay/anjay.h>\n#include <anjay/anjay_config.h>\n#include <anjay/lwm2m_send.h>\n\n#ifdef ANJAY_WITH_LWM2M_GATEWAY\n\n#    ifdef __cplusplus\nextern \"C\" {\n#    endif\n\n/**\n * Registers LwM2M Gateway Object and initializes the Gateway Module.\n *\n * @param anjay   Anjay object to operate on\n *\n * @returns 0 on success, or a negative value in case of error.\n */\nint anjay_lwm2m_gateway_install(anjay_t *anjay);\n\n/**\n * Register an End Device in LwM2M Gateway and assign the necessary Resources.\n * /0 Device ID Resource is set as @p device_id parameter.\n * /1 Prefix Resource is assigned automatically as \"dev<x>\" where <x> is the\n * Device ID returned with @p inout_iid parameter.\n * /3 IoT Device Object Resource is generated as Corelnk format upon Read\n * Request according to the Data Model set with\n * @ref anjay_lwm2m_gateway_register_object calls\n *\n * Note: if @p inout_iid is set to @ref ANJAY_ID_INVALID then the Instance id\n * is generated automatically, otherwise value of @p inout_iid is used as a\n * new Gateway Instance ID.\n *\n * @param anjay      Anjay object to operate on\n * @param device_id  Globally Unique Device ID (/0 Resource) as a\n *                   NULL-terminated string. It's value is not copied, so\n *                   the pointer must remain valid\n * @param inout_iid  Gateway Instance ID to use or @ref ANJAY_ID_INVALID .\n *                   Treated also as End IoT Device ID which shall be used with\n *                   further API calls to specify the End Device entity in\n *                   Gateway\n *\n * @returns 0 on success, or a negative value in case of error.\n */\nint anjay_lwm2m_gateway_register_device(anjay_t *anjay,\n                                        const char *device_id,\n                                        anjay_iid_t *inout_iid);\n\n/**\n * Deregister an End Device in LwM2M Gateway.\n *\n * @param anjay  Anjay object to operate on\n * @param iid    End Device Instance ID to be deregistered\n *\n * @returns 0 on success, or a negative value in case of error.\n */\nint anjay_lwm2m_gateway_deregister_device(anjay_t *anjay, anjay_iid_t iid);\n\n/**\n * Register an Object in LwM2M Gateway End Device Data Model.\n *\n * @param anjay   Anjay object to operate on\n * @param iid     End Device Instance ID\n * @param def_ptr Pointer to the Object definition struct. The exact value\n *                passed to this function will be forwarded to all data model\n *                handler calls.\n *\n * @returns 0 on success, or a negative value in case of error.\n */\nint anjay_lwm2m_gateway_register_object(\n        anjay_t *anjay,\n        anjay_iid_t iid,\n        const anjay_dm_object_def_t *const *def_ptr);\n\n/**\n * Unregister an Object in LwM2M Gateway End Device Data Model.\n *\n * @param anjay   Anjay object to operate on\n * @param iid     End Device Instance ID\n * @param def_ptr Pointer to the Object definition struct.\n *\n * @returns 0 on success, or a negative value in case of error.\n */\nint anjay_lwm2m_gateway_unregister_object(\n        anjay_t *anjay,\n        anjay_iid_t iid,\n        const anjay_dm_object_def_t *const *def_ptr);\n\n#    ifdef ANJAY_WITH_SEND\n\n/**\n * Adds a value to batch builder. This function is intended to be used with\n * LwM2M Gateway End Device objects.\n *\n * IMPORTANT NOTE:\n * If @p timestamp is earlier than 1978-07-04 21:24:16 UTC (2**28 seconds since\n * Unix epoch), then it's assumed to be relative to some arbitrary point in\n * time, and will be encoded as relative to \"now\". Otherwise, the time is\n * assumed to be an Unix timestamp, and encoded as time since Unix epoch. See\n * also: RFC 8428, \"Requirements and Design Goals\"\n *\n * @param builder     Pointer to batch builder\n * @param gateway_iid End Device Instance ID, MUST NOT be @c UINT16_MAX\n * @param oid         Object ID, MUST NOT be @c UINT16_MAX\n * @param iid         Instance ID, MUST NOT be @c UINT16_MAX\n * @param rid         Resource ID, MUST NOT be @c UINT16_MAX\n * @param riid        Resource Instance ID, @c UINT16_MAX for no RIID\n * @param timestamp   Time related to value being sent (e.g. when the\n *                    measurement corresponding to the passed value was made)\n * @param value       Value to add to the batch.\n *\n * @returns 0 on success, negative value otherwise. In case of failure, the\n *          @p builder is left unchanged.\n */\nint anjay_lwm2m_gateway_send_batch_add_int(anjay_send_batch_builder_t *builder,\n                                           anjay_iid_t gateway_iid,\n                                           anjay_oid_t oid,\n                                           anjay_iid_t iid,\n                                           anjay_rid_t rid,\n                                           anjay_riid_t riid,\n                                           avs_time_real_t timestamp,\n                                           int64_t value);\n\n/**\n * @copydoc anjay_lwm2m_gateway_send_batch_add_int()\n */\nint anjay_lwm2m_gateway_send_batch_add_uint(anjay_send_batch_builder_t *builder,\n                                            anjay_iid_t gateway_iid,\n                                            anjay_oid_t oid,\n                                            anjay_iid_t iid,\n                                            anjay_rid_t rid,\n                                            anjay_riid_t riid,\n                                            avs_time_real_t timestamp,\n                                            uint64_t value);\n\n/**\n * @copydoc anjay_lwm2m_gateway_send_batch_add_int()\n */\nint anjay_lwm2m_gateway_send_batch_add_double(\n        anjay_send_batch_builder_t *builder,\n        anjay_iid_t gateway_iid,\n        anjay_oid_t oid,\n        anjay_iid_t iid,\n        anjay_rid_t rid,\n        anjay_riid_t riid,\n        avs_time_real_t timestamp,\n        double value);\n\n/**\n * @copydoc anjay_lwm2m_gateway_send_batch_add_int()\n */\nint anjay_lwm2m_gateway_send_batch_add_bool(anjay_send_batch_builder_t *builder,\n                                            anjay_iid_t gateway_iid,\n                                            anjay_oid_t oid,\n                                            anjay_iid_t iid,\n                                            anjay_rid_t rid,\n                                            anjay_riid_t riid,\n                                            avs_time_real_t timestamp,\n                                            bool value);\n\n/**\n * Adds a string to batch builder. This function is intended to be used with\n * LwM2M Gateway End Device objects.\n *\n * IMPORTANT NOTE:\n * If @p timestamp is earlier than 1978-07-04 21:24:16 UTC (2**28 seconds since\n * Unix epoch), then it's assumed to be relative to some arbitrary point in\n * time, and will be encoded as relative to \"now\". Otherwise, the time is\n * assumed to be an Unix timestamp, and encoded as time since Unix epoch. See\n * also: RFC 8428, \"Requirements and Design Goals\"\n *\n * @param builder     Pointer to batch builder\n * @param gateway_iid End Device Instance ID, MUST NOT be @c UINT16_MAX\n * @param oid         Object ID, MUST NOT be @c UINT16_MAX\n * @param iid         Instance ID, MUST NOT be @c UINT16_MAX\n * @param rid         Resource ID, MUST NOT be @c UINT16_MAX\n * @param riid        Resource Instance ID, @c UINT16_MAX for no RIID\n * @param timestamp   Time related to string being send (e.g. when the\n *                    measurement corresponding to the passed string was made)\n * @param str         Pointer to a NULL-terminated string. Must not be NULL.\n *                    No longer required by batch builder after call to this\n *                    function, because internal copy is made.\n *\n * @returns 0 on success, negative value otherwise. In case of failure, the\n *          @p builder is left unchanged.\n */\nint anjay_lwm2m_gateway_send_batch_add_string(\n        anjay_send_batch_builder_t *builder,\n        anjay_iid_t gateway_iid,\n        anjay_oid_t oid,\n        anjay_iid_t iid,\n        anjay_rid_t rid,\n        anjay_riid_t riid,\n        avs_time_real_t timestamp,\n        const char *str);\n\n/**\n * Adds bytes to batch builder. This function is intended to be used with LwM2M\n * Gateway End Device objects.\n *\n * IMPORTANT NOTE:\n * If @p timestamp is earlier than 1978-07-04 21:24:16 UTC (2**28 seconds since\n * Unix epoch), then it's assumed to be relative to some arbitrary point in\n * time, and will be encoded as relative to \"now\". Otherwise, the time is\n * assumed to be an Unix timestamp, and encoded as time since Unix epoch. See\n * also: RFC 8428, \"Requirements and Design Goals\"\n *\n * @param builder     Pointer to batch builder\n * @param gateway_iid End Device Instance ID, MUST NOT be @c UINT16_MAX\n * @param oid         Object ID, MUST NOT be @c UINT16_MAX\n * @param iid         Instance ID, MUST NOT be @c UINT16_MAX\n * @param rid         Resource ID, MUST NOT be @c UINT16_MAX\n * @param riid        Resource Instance ID, @c UINT16_MAX for no RIID\n * @param timestamp   Time related to bytes being send (e.g. when the\n *                    measurement corresponding to the passed bytes was made)\n * @param data        Pointer to data. No longer required by batch builder after\n *                    call to this function, because internal copy is made. Can\n *                    be NULL only if @p length is 0.\n * @param length      Length of data in bytes.\n *\n * @returns 0 on success, negative value otherwise. In case of failure, the\n *          @p builder is left unchanged.\n */\nint anjay_lwm2m_gateway_send_batch_add_bytes(\n        anjay_send_batch_builder_t *builder,\n        anjay_iid_t gateway_iid,\n        anjay_oid_t oid,\n        anjay_iid_t iid,\n        anjay_rid_t rid,\n        anjay_riid_t riid,\n        avs_time_real_t timestamp,\n        const void *data,\n        size_t length);\n\n/**\n * Adds an Object Link to batch builder. This function is intended to be used\n * with LwM2M Gateway End Device objects.\n *\n * IMPORTANT NOTE:\n * If @p timestamp is earlier than 1978-07-04 21:24:16 UTC (2**28 seconds since\n * Unix epoch), then it's assumed to be relative to some arbitrary point in\n * time, and will be encoded as relative to \"now\". Otherwise, the time is\n * assumed to be an Unix timestamp, and encoded as time since Unix epoch. See\n * also: RFC 8428, \"Requirements and Design Goals\"\n *\n * @param builder     Pointer to batch builder\n * @param gateway_iid End Device Instance ID, MUST NOT be @c UINT16_MAX\n * @param oid         Object ID, MUST NOT be @c UINT16_MAX\n * @param iid         Instance ID, MUST NOT be @c UINT16_MAX\n * @param rid         Resource ID, MUST NOT be @c UINT16_MAX\n * @param riid        Resource Instance ID, @c UINT16_MAX for no RIID\n * @param timestamp   Time related to Object Link being send (e.g. when the\n *                    measurement corresponding to the passed Object Link was\n *                    made)\n * @param objlnk_oid  OID of Object Link\n * @param objlnk_iid  IID of Object Link\n *\n * @returns 0 on success, negative value otherwise. In case of failure, the\n *          @p builder is left unchanged.\n */\nint anjay_lwm2m_gateway_send_batch_add_objlnk(\n        anjay_send_batch_builder_t *builder,\n        anjay_iid_t gateway_iid,\n        anjay_oid_t oid,\n        anjay_iid_t iid,\n        anjay_rid_t rid,\n        anjay_riid_t riid,\n        avs_time_real_t timestamp,\n        anjay_oid_t objlnk_oid,\n        anjay_iid_t objlnk_iid);\n\n/**\n * Reads value from data model of the End Device (without checking access\n * privileges) and adds it to the builder with timestamp set to @c\n * avs_time_real_now().\n *\n * May possibly add multiple entries if /prefix/oid/iid/rid is a Multiple\n * Resource.\n *\n * @param builder     Pointer to batch builder, MUST NOT be @c NULL\n * @param anjay       Pointer to Anjay object, MUST NOT be @c NULL\n * @param gateway_iid End Device Instance ID, MUST NOT be @c UINT16_MAX\n * @param oid         Object ID, MUST NOT be @c UINT16_MAX , @c 0 (Security\n *                    object ID) or @c 21 (OSCORE object ID).\n * @param iid         Instance ID, MUST NOT be @c UINT16_MAX\n * @param rid         Resource ID, MUST NOT be @c UINT16_MAX\n *\n * @returns 0 on success, negative value otherwise. In case of failure, the\n *          @p builder is left unchanged.\n */\nint anjay_lwm2m_gateway_send_batch_data_add_current(\n        anjay_send_batch_builder_t *builder,\n        anjay_t *anjay,\n        anjay_iid_t gateway_iid,\n        anjay_oid_t oid,\n        anjay_iid_t iid,\n        anjay_rid_t rid);\n\n/**\n * Reads value from data model of the End Device (without checking access\n * privileges) and adds them to the builder with the same timestamp for every\n * value. Timestamp is set to @c avs_time_real_now().\n *\n * IMPORTANT: All @p paths must point to the objects of the same End Device.\n *\n * @param builder      Pointer to batch builder, MUST NOT be @c NULL\n * @param anjay        Pointer to Anjay object, MUST NOT be @c NULL\n * @param gateway_iid  End Device Instance ID, MUST NOT be @c UINT16_MAX\n * @param paths        Pointer to array of @ref anjay_send_resource_path_t .\n * @param paths_length Length of @p paths array.\n *\n * @returns 0 on success, negative value otherwise. In case of failure, the\n *          @p builder is left unchanged.\n */\nint anjay_lwm2m_gateway_send_batch_data_add_current_multiple(\n        anjay_send_batch_builder_t *builder,\n        anjay_t *anjay,\n        anjay_iid_t gateway_iid,\n        const anjay_send_resource_path_t *paths,\n        size_t paths_length);\n\n/**\n * Reads value from data model of the End Device (without checking access\n * privileges) and adds them to the builder with the same timestamp for every\n * value. Timestamp is set to @c avs_time_real_now().\n *\n * IMPORTANT: All @p paths must point to the objects of the same End Device.\n *\n * If a resource is not found, it's ignored, the error isn't returned and the\n * function adds next resources from the @p paths. Hoverer, if the End Device is\n * not present, the error is returned.\n *\n * @param builder      Pointer to batch builder, MUST NOT be @c NULL\n * @param anjay        Pointer to Anjay object, MUST NOT be @c NULL\n * @param gateway_iid  End Device Instance ID, MUST NOT be @c UINT16_MAX\n * @param paths        Pointer to array of @ref anjay_send_resource_path_t .\n * @param paths_length Length of @p paths array.\n *\n * @returns 0 on success, negative value otherwise. In case of failure, the\n *          @p builder is left unchanged.\n */\nint anjay_lwm2m_gateway_send_batch_data_add_current_multiple_ignore_not_found(\n        anjay_send_batch_builder_t *builder,\n        anjay_t *anjay,\n        anjay_iid_t gateway_iid,\n        const anjay_send_resource_path_t *paths,\n        size_t paths_length);\n\n#    endif // ANJAY_WITH_SEND\n\n/**\n * Notifies the library that the value of given Resource changed. It may trigger\n * a LwM2M Notify message, update server connections and perform other tasks,\n * as required for the specified Resource.\n *\n * Needs to be called for any Resource after its value is changed by means other\n * than LwM2M.\n *\n * Note that it should not be called after a Write performed by the LwM2M\n * server.\n *\n * @param anjay   Anjay object to operate on.\n * @param end_dev End Device Instance ID.\n * @param oid     Object ID of the changed Resource.\n * @param iid     Object Instance ID of the changed Resource.\n * @param rid     Resource ID of the changed Resource.\n *\n * @returns 0 on success, a negative value in case of error.\n */\nint anjay_lwm2m_gateway_notify_changed(anjay_t *anjay,\n                                       anjay_iid_t end_dev,\n                                       anjay_oid_t oid,\n                                       anjay_iid_t iid,\n                                       anjay_rid_t rid);\n\n/**\n * Notifies the library that the set of Instances existing in a given Object\n * changed. It may trigger a LwM2M Notify message, update server connections\n * and perform other tasks, as required for the specified Object ID.\n *\n * Needs to be called for each Object, after an Instance is created or removed\n * by means other than LwM2M.\n *\n * Note that it should not be called after a Create or Delete performed by the\n * LwM2M server.\n *\n * @param anjay   Anjay object to operate on.\n * @param end_dev End Device Instance ID.\n * @param oid     Object ID of the changed Object.\n *\n * @returns 0 on success, a negative value in case of error.\n */\nint anjay_lwm2m_gateway_notify_instances_changed(anjay_t *anjay,\n                                                 anjay_iid_t end_dev,\n                                                 anjay_oid_t oid);\n#    ifdef ANJAY_WITH_OBSERVATION_STATUS\n/**\n * Gets information whether and how a given Resource is observed. See\n * @ref anjay_resource_observation_status_t for details.\n *\n * NOTE: This API is a companion to @ref anjay_notify_changed. There is no\n * analogous API that would be a companion to\n * @ref anjay_notify_instances_changed. Any changes to set of instances of any\n * LwM2M Object MUST be considered observed at all times and notified as soon as\n * possible.\n *\n * @param anjay Anjay object to operate on.\n * @param end_dev End Device Instance ID.\n * @param oid   Object ID of the Resource to check.\n * @param iid   Object Instance ID of the Resource to check.\n * @param rid   Resource ID of the Resource to check.\n *\n * @returns Observation status of a given Resource. If the arguments do not\n *          specify a valid Resource path, data equivalent to a non-observed\n *          Resource will be returned.\n *\n * NOTE: This function may be used to implement notifications for Resources that\n * require active polling by the client application. A naive implementation\n * could look more or less like this (pseudocode):\n *\n * <code>\n * status = anjay_resource_observation_status(anjay, oid, iid, rid);\n * if (status.is_observed\n *         && current_time >= last_check_time + status.min_period) {\n *     new_value = read_resource_value();\n *     if (new_value != old_value) {\n *         anjay_notify_changed(anjay, oid, iid, rid);\n *     }\n *     last_check_time = current_time;\n * }\n * </code>\n *\n * However, please note that such implementation may not be strictly conformant\n * to the LwM2M specification. For example, in the following case:\n *\n * [time] --|--------|-*------|-->     | - intervals between resource reads\n *          |<------>|                 * - point in time when underlying state\n *          min_period                     actually changes\n *\n * the specification would require the notification to be sent exactly at the\n * time of the (*) event, but with this naive implementation, will be delayed\n * until the next (|).\n */\nanjay_resource_observation_status_t\nanjay_lwm2m_gateway_resource_observation_status(anjay_t *anjay,\n                                                anjay_iid_t end_dev,\n                                                anjay_oid_t oid,\n                                                anjay_iid_t iid,\n                                                anjay_rid_t rid);\n#    endif // ANJAY_WITH_OBSERVATION_STATUS\n#    ifdef __cplusplus\n}\n#    endif\n\n#endif // ANJAY_WITH_LWM2M_GATEWAY\n#endif // ANJAY_INCLUDE_LWM2M_GATEWAY_H\n"
  },
  {
    "path": "include_public/anjay/lwm2m_send.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ANJAY_INCLUDE_ANJAY_LWM2M_SEND_H\n#define ANJAY_INCLUDE_ANJAY_LWM2M_SEND_H\n\n#include <anjay/anjay.h>\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n#ifdef ANJAY_WITH_SEND\n\ntypedef struct anjay_send_batch_builder_struct anjay_send_batch_builder_t;\n\ntypedef struct anjay_send_batch_struct anjay_send_batch_t;\n\n/**\n * Struct used to build array of resources to be added to batch by using\n * @ref anjay_send_batch_data_add_current_multiple() .\n *\n * None of the fields MUST NOT be equal to @c UINT16_MAX .\n */\ntypedef struct {\n    anjay_oid_t oid;\n    anjay_iid_t iid;\n    anjay_rid_t rid;\n} anjay_send_resource_path_t;\n\n/**\n * Send request has previously been deferred, the factors that caused it to be\n * deferred are no longer valid, but it could not be initiated for other\n * reasons.\n *\n * EXAMPLE: @ref anjay_send_deferrable may have been called when the server was\n * offline. The server is now online, but the Send request have been rejected\n * due to the registration having been performed with the LwM2M TS 1.0 protocol\n * version.\n *\n * NOTE: Any of the errors defined in #anjay_send_result_t may be mapped onto\n * this error code. There is currently no way to determine more detailed reason.\n */\n#    define ANJAY_SEND_DEFERRED_ERROR (-3)\n\n/**\n * Result passed to #anjay_send_finished_handler_t: No response from Server was\n * received and further retransmissions are aborted due to library cleanup or\n * because the socket used to communicate with the server is being disconnected\n * (e.g. when entering offline mode).\n */\n#    define ANJAY_SEND_ABORT (-2)\n\n/**\n * Result passed to #anjay_send_finished_handler_t: No response from Server was\n * received in expected time, or connection with the server has been lost.\n * Retransmissions will not continue - you may try to send the same batch again\n * using @ref anjay_send .\n */\n#    define ANJAY_SEND_TIMEOUT (-1)\n\n/**\n * Result passed to #anjay_send_finished_handler_t: Server confirmed successful\n * message delivery.\n */\n#    define ANJAY_SEND_SUCCESS 0\n\n/**\n * A handler called if acknowledgement for LwM2M Send operation is received from\n * the Server or all retransmissions of LwM2M Send have failed.\n *\n * @param anjay  Anjay object for which the Send operation was attempted.\n * @param ssid   Short Server ID of the server to which the batch was being\n *               sent.\n * @param batch  Pointer to a batch that was being sent. This pointer may be\n *               passed to @ref anjay_send for sending again; if you wish to\n *               store it for later usage, @ref anjay_send_batch_acquire MUST be\n *               used.\n * @param result Result of the Send message delivery attempt. May be one of:\n *               - @ref ANJAY_SEND_SUCCESS (0) - Server confirmed successful\n *                 message delivery.\n *               - A negative value if any kind of error occured:\n *                 - One of <c>ANJAY_SEND_*</c> constants for conditions\n *                   described by\n *                 - A negated @ref ANJAY_COAP_STATUS (i.e., one of\n *                   <c>ANJAY_ERR_*</c> constants) if there was an unexpected\n *                   (non-success) CoAP response from the server.\n * @param data   Data defined by user passed into the handler.\n */\ntypedef void anjay_send_finished_handler_t(anjay_t *anjay,\n                                           anjay_ssid_t ssid,\n                                           const anjay_send_batch_t *batch,\n                                           int result,\n                                           void *data);\n\n/**\n * Creates a batch builder that may be used to build a payload with the data to\n * be sent to the LwM2M Server by means of LwM2M Send operation.\n *\n * Intended use of the batch builder may be divided into four steps, as follows:\n * 1. Create a batch builder.\n * 2. Fill in the builder with data, by calling anjay_send_batch_add_* functions\n * (possibly multiple times).\n * 3. Convert the builder into the final, immutable batch by @ref\n * anjay_send_batch_builder_compile function call.\n * 4. Pass the resulting batch to @ref anjay_send .\n *\n * IMPORTANT NOTE:\n * LwM2M SEND API can be also used with LwM2M Gateway End Device objects \\ref\n * lwm2m_gateway.h . In such case, use anjay_lwm2m_gateway_send_batch_add_*\n * functions to add values from Gateway End Device objects to the batch. In one\n * batch builder you can mix data from different End Devices and Gateway\n * objects. Do not use @ref anjay_lwm2m_gateway_deregister_device or @ref\n * anjay_lwm2m_gateway_register_device when building data batch because the\n * records in batch may refer to the wrong End Device.\n *\n * Example use (error checking omitted for brevity):\n * @code\n * // Creates a builder for a batch.\n * anjay_send_batch_builder_t *builder = anjay_send_batch_builder_new();\n *\n * // Adds signed integer value to batch builder, without checking if such\n * // resource (oid=1, iid=2, rid=3) exists in datamodel.\n * anjay_send_batch_add_int(\n *         builder, 1, 2, 3, UINT16_MAX, avs_time_real_now(), 123);\n *\n * // Adds value from datamodel (oid=4, iid=5, rid=6) to batch builder if it\n * // exists.\n * anjay_send_batch_data_add_current(builder, anjay, 4, 5, 6);\n *\n * // Creates immutable data batch and releases builder.\n * anjay_send_batch_t *batch = anjay_send_batch_builder_compile(builder);\n *\n * // Puts LwM2M Send request on the scheduler queue. During next call to\n * // anjay_sched_run content of the batch will be sent to server with SSID=1\n * anjay_send(anjay, 1, batch, NULL, NULL);\n *\n * // Releases the batch if it's not used by some send operation.\n * anjay_send_batch_release(&batch);\n * @endcode\n *\n * @returns Pointer to dynamically allocated batch builder, which is freed\n *          implicitly in @ref anjay_send_batch_builder_compile() or has to be\n *          freed manually by calling @ref anjay_send_batch_builder_cleanup() .\n *          NULL in case of allocation failure.\n */\nanjay_send_batch_builder_t *anjay_send_batch_builder_new(void);\n\n/**\n * Releases batch builder and discards all data. It has no effect if builder was\n * previously compiled.\n *\n * @param builder Pointer to pointer to data builder. Set to NULL after cleanup.\n */\nvoid anjay_send_batch_builder_cleanup(anjay_send_batch_builder_t **builder);\n\n/**\n * Adds a value to batch builder.\n *\n * IMPORTANT NOTE:\n * If @p timestamp is earlier than 1978-07-04 21:24:16 UTC (2**28 seconds since\n * Unix epoch), then it's assumed to be relative to some arbitrary point in\n * time, and will be encoded as relative to \"now\". Otherwise, the time is\n * assumed to be an Unix timestamp, and encoded as time since Unix epoch. See\n * also: RFC 8428, \"Requirements and Design Goals\"\n *\n * @param builder   Pointer to batch builder\n * @param oid       Object ID, MUST NOT be @c UINT16_MAX\n * @param iid       Instance ID, MUST NOT be @c UINT16_MAX\n * @param rid       Resource ID, MUST NOT be @c UINT16_MAX\n * @param riid      Resource Instance ID, @c UINT16_MAX for no RIID\n * @param timestamp Time related to value being sent (e.g. when the measurement\n *                  corresponding to the passed value was made)\n * @param value     Value to add to the batch.\n *\n * @returns 0 on success, negative value otherwise. In case of failure, the\n *          @p builder is left unchanged.\n */\nint anjay_send_batch_add_int(anjay_send_batch_builder_t *builder,\n                             anjay_oid_t oid,\n                             anjay_iid_t iid,\n                             anjay_rid_t rid,\n                             anjay_riid_t riid,\n                             avs_time_real_t timestamp,\n                             int64_t value);\n\n/**\n * @copydoc anjay_send_batch_add_int()\n */\nint anjay_send_batch_add_uint(anjay_send_batch_builder_t *builder,\n                              anjay_oid_t oid,\n                              anjay_iid_t iid,\n                              anjay_rid_t rid,\n                              anjay_riid_t riid,\n                              avs_time_real_t timestamp,\n                              uint64_t value);\n\n/**\n * @copydoc anjay_send_batch_add_int()\n */\nint anjay_send_batch_add_double(anjay_send_batch_builder_t *builder,\n                                anjay_oid_t oid,\n                                anjay_iid_t iid,\n                                anjay_rid_t rid,\n                                anjay_riid_t riid,\n                                avs_time_real_t timestamp,\n                                double value);\n\n/**\n * @copydoc anjay_send_batch_add_int()\n */\nint anjay_send_batch_add_bool(anjay_send_batch_builder_t *builder,\n                              anjay_oid_t oid,\n                              anjay_iid_t iid,\n                              anjay_rid_t rid,\n                              anjay_riid_t riid,\n                              avs_time_real_t timestamp,\n                              bool value);\n\n/**\n * Adds a string to batch builder.\n *\n * IMPORTANT NOTE:\n * If @p timestamp is earlier than 1978-07-04 21:24:16 UTC (2**28 seconds since\n * Unix epoch), then it's assumed to be relative to some arbitrary point in\n * time, and will be encoded as relative to \"now\". Otherwise, the time is\n * assumed to be an Unix timestamp, and encoded as time since Unix epoch. See\n * also: RFC 8428, \"Requirements and Design Goals\"\n *\n * @param builder   Pointer to batch builder\n * @param oid       Object ID, MUST NOT be @c UINT16_MAX\n * @param iid       Instance ID, MUST NOT be @c UINT16_MAX\n * @param rid       Resource ID, MUST NOT be @c UINT16_MAX\n * @param riid      Resource Instance ID, @c UINT16_MAX for no RIID\n * @param timestamp Time related to string being send (e.g. when the measurement\n *                  corresponding to the passed string was made)\n * @param str       Pointer to a NULL-terminated string. Must not be NULL.\n *                  No longer required by batch builder after call to this\n *                  function, because internal copy is made.\n *\n * @returns 0 on success, negative value otherwise. In case of failure, the\n *          @p builder is left unchanged.\n */\nint anjay_send_batch_add_string(anjay_send_batch_builder_t *builder,\n                                anjay_oid_t oid,\n                                anjay_iid_t iid,\n                                anjay_rid_t rid,\n                                anjay_riid_t riid,\n                                avs_time_real_t timestamp,\n                                const char *str);\n\n/**\n * Adds bytes to batch builder.\n *\n * IMPORTANT NOTE:\n * If @p timestamp is earlier than 1978-07-04 21:24:16 UTC (2**28 seconds since\n * Unix epoch), then it's assumed to be relative to some arbitrary point in\n * time, and will be encoded as relative to \"now\". Otherwise, the time is\n * assumed to be an Unix timestamp, and encoded as time since Unix epoch. See\n * also: RFC 8428, \"Requirements and Design Goals\"\n *\n * @param builder   Pointer to batch builder\n * @param oid       Object ID, MUST NOT be @c UINT16_MAX\n * @param iid       Instance ID, MUST NOT be @c UINT16_MAX\n * @param rid       Resource ID, MUST NOT be @c UINT16_MAX\n * @param riid      Resource Instance ID, @c UINT16_MAX for no RIID\n * @param timestamp Time related to bytes being send (e.g. when the measurement\n *                  corresponding to the passed bytes was made)\n * @param data      Pointer to data. No longer required by batch builder after\n *                  call to this function, because internal copy is made. Can be\n *                  NULL only if @p length is 0.\n * @param length    Length of data in bytes.\n *\n * @returns 0 on success, negative value otherwise. In case of failure, the\n *          @p builder is left unchanged.\n */\nint anjay_send_batch_add_bytes(anjay_send_batch_builder_t *builder,\n                               anjay_oid_t oid,\n                               anjay_iid_t iid,\n                               anjay_rid_t rid,\n                               anjay_riid_t riid,\n                               avs_time_real_t timestamp,\n                               const void *data,\n                               size_t length);\n\n/**\n * Adds an Object Link to batch builder.\n *\n * IMPORTANT NOTE:\n * If @p timestamp is earlier than 1978-07-04 21:24:16 UTC (2**28 seconds since\n * Unix epoch), then it's assumed to be relative to some arbitrary point in\n * time, and will be encoded as relative to \"now\". Otherwise, the time is\n * assumed to be an Unix timestamp, and encoded as time since Unix epoch. See\n * also: RFC 8428, \"Requirements and Design Goals\"\n *\n * @param builder    Pointer to batch builder\n * @param oid        Object ID, MUST NOT be @c UINT16_MAX\n * @param iid        Instance ID, MUST NOT be @c UINT16_MAX\n * @param rid        Resource ID, MUST NOT be @c UINT16_MAX\n * @param riid       Resource Instance ID, @c UINT16_MAX for no RIID\n * @param timestamp  Time related to Object Link being send (e.g. when the\n *                   measurement corresponding to the passed Object Link was\n *                   made)\n * @param objlnk_oid OID of Object Link\n * @param objlnk_iid IID of Object Link\n *\n * @returns 0 on success, negative value otherwise. In case of failure, the\n *          @p builder is left unchanged.\n */\nint anjay_send_batch_add_objlnk(anjay_send_batch_builder_t *builder,\n                                anjay_oid_t oid,\n                                anjay_iid_t iid,\n                                anjay_rid_t rid,\n                                anjay_riid_t riid,\n                                avs_time_real_t timestamp,\n                                anjay_oid_t objlnk_oid,\n                                anjay_iid_t objlnk_iid);\n\n/**\n * Reads value from data model of object @p anjay (without checking access\n * privileges) and adds it to the builder with timestamp set to\n * @c avs_time_real_now().\n *\n * May possibly add multiple entries if /oid/iid/rid is a Multiple Resource.\n *\n * @param builder Pointer to batch builder, MUST NOT be @c NULL\n * @param anjay   Pointer to Anjay object, MUST NOT be @c NULL\n * @param oid     Object ID, MUST NOT be @c UINT16_MAX , @c 0 (Security object\n *                ID) or @c 21 (OSCORE object ID).\n * @param iid     Instance ID, MUST NOT be @c UINT16_MAX\n * @param rid     Resource ID, MUST NOT be @c UINT16_MAX\n *\n * @returns 0 on success, negative value otherwise. In case of failure, the\n *          @p builder is left unchanged.\n */\nint anjay_send_batch_data_add_current(anjay_send_batch_builder_t *builder,\n                                      anjay_t *anjay,\n                                      anjay_oid_t oid,\n                                      anjay_iid_t iid,\n                                      anjay_rid_t rid);\n\n/**\n * Reads values from data model of object @p anjay (without checking access\n * privileges) and adds them to the builder with the same timestamp for every\n * value. Timestamp is set to @c avs_time_real_now().\n *\n * @param builder      Pointer to batch builder, MUST NOT be @c NULL\n * @param anjay        Pointer to Anjay object, MUST NOT be @c NULL\n * @param paths        Pointer to array of @ref anjay_send_resource_path_t .\n * @param paths_length Length of @p paths array.\n *\n * @returns 0 on success, negative value otherwise. In case of failure, the\n *          @p builder is left unchanged.\n */\nint anjay_send_batch_data_add_current_multiple(\n        anjay_send_batch_builder_t *builder,\n        anjay_t *anjay,\n        const anjay_send_resource_path_t *paths,\n        size_t paths_length);\n\n/**\n * Reads values from data model of object @p anjay (without checking access\n * privileges) and adds them to the builder with the same timestamp for every\n * value. Timestamp is set to @c avs_time_real_now().\n *\n * If a resource is not found, it's ignored, the error isn't returned and the\n * function adds next resources from the @p paths.\n *\n * @param builder      Pointer to batch builder, MUST NOT be @c NULL\n * @param anjay        Pointer to Anjay object, MUST NOT be @c NULL\n * @param paths        Pointer to array of @ref anjay_send_resource_path_t .\n * @param paths_length Length of @p paths array.\n *\n * @returns 0 on success, negative value otherwise. In case of failure, the\n *          @p builder is left unchanged.\n */\nint anjay_send_batch_data_add_current_multiple_ignore_not_found(\n        anjay_send_batch_builder_t *builder,\n        anjay_t *anjay,\n        const anjay_send_resource_path_t *paths,\n        size_t paths_length);\n\n/**\n * Makes a dynamically-allocated, reference-counted immutable data batch using\n * data from batch builder. Created batch can be used for multiple calls of\n * @c anjay_send().\n *\n * @param builder Pointer to pointer to batch builder. Set to NULL after\n *                successful return.\n *\n * @returns Pointer to compiled batch in case of success, NULL otherwise.\n *          If this function fails, batch builder is not modified and must be\n *          freed manually with @ref anjay_send_batch_builder_cleanup() if it's\n *          not to be used anymore.\n */\nanjay_send_batch_t *\nanjay_send_batch_builder_compile(anjay_send_batch_builder_t **builder);\n\n/**\n * Increments the refcount for a *batch. Must always be used if batch would be\n * referenced out of current scope, especially when the pointer would be saved\n * to an object that is dynamically allocated. Each call of this function must\n * have complementary @ref anjay_send_batch_release call at some point.\n *\n * @param batch Non-null batch which refcount will be incremented\n *\n * @returns @p batch\n */\nanjay_send_batch_t *anjay_send_batch_acquire(const anjay_send_batch_t *batch);\n\n/**\n * Decreases the refcount for a *batch, sets it to NULL, and frees it if the\n * refcount has reached zero.\n *\n * @param *batch Pointer to compiled data batch.\n */\nvoid anjay_send_batch_release(anjay_send_batch_t **batch);\n\n/**\n * All possible error codes that will be returned by the @ref anjay_send()\n */\ntypedef enum {\n    ANJAY_SEND_OK = 0,\n\n    /**\n     * This version of Anjay does not support LwM2M Send operation.\n     */\n    ANJAY_SEND_ERR_UNSUPPORTED,\n\n    /**\n     * LwM2M Send cannot be performed because of \"Mute Send\" Resource is set to\n     * true.\n     *\n     * NOTE: The value of \"Mute Send\" Resource is controlled by the LwM2M Server\n     * itself.\n     */\n    ANJAY_SEND_ERR_MUTED,\n\n    /**\n     * Passed Short Server ID refers to a server, connection to which is\n     * currently offline. The LwM2M Send operation may be retried after making\n     * the connection back online.\n     */\n    ANJAY_SEND_ERR_OFFLINE,\n\n    /**\n     * Anjay is in process of a Bootstrap. The LwM2M Send operation may be\n     * retried after finishing the Bootstrap stage.\n     */\n    ANJAY_SEND_ERR_BOOTSTRAP,\n\n    /**\n     * Passed Short Server ID does not correspond to any existing / connected,\n     * non-Bootstrap Server. Especially passing @ref ANJAY_SSID_ANY or @ref\n     * ANJAY_SSID_BOOTSTRAP causes this error to be returned.\n     */\n    ANJAY_SEND_ERR_SSID,\n\n    /**\n     * The LwM2M protocol version used to connect to an LwM2M Server does not\n     * support the LwM2M Send operation.\n     */\n    ANJAY_SEND_ERR_PROTOCOL,\n\n    /**\n     * Internal error. Very likely caused by the out-of-memory condition. The\n     * LwM2M Send operation may be retried after freeing some memory.\n     */\n    ANJAY_SEND_ERR_INTERNAL\n} anjay_send_result_t;\n\n/**\n * Sends data to the LwM2M Server without explicit request by that Server.\n *\n * During the next call to @ref anjay_sched_run @p data will be sent\n * asynchronously to the Server with specified @p ssid but only if Mute Send\n * resource of the server instance associated with @p ssid is set to false.\n * Otherwise nothing is sent and @ref ANJAY_SEND_ERR_MUTED is returned.\n *\n * @p data must not be NULL but it can be everything successfully returned from\n * @ref anjay_send_batch_builder_compile . Even empty batch is acceptable:\n * @code\n * anjay_send_batch_builder_t *builder = anjay_send_batch_builder_new();\n * anjay_send_batch_t *empty_batch = anjay_send_batch_builder_compile(&builder);\n * anjay_send(anjay, ssid, empty_batch, NULL, NULL);\n * @endcode\n * Before sending content of @p data is filtered according to Access Control\n * permissions of a particular server. The server will get only those entries of\n * @p data which paths were configured by @ref anjay_access_control_set_acl with\n * enabled @c ANJAY_ACCESS_MASK_READ .\n *\n * If @p finished_handler is not NULL it will always be called at some\n * point - after receiving acknowledgement from the Server or if no response was\n * received in expected time.\n *\n * Success of this function means only that the data has been sent, not\n * necessarily delivered. Data is delivered if and only if\n * @p finished_handler with status @c ANJAY_SEND_SUCCESS is called.\n *\n * @param anjay                 Anjay object to operate on.\n * @param ssid                  Short Server ID of target LwM2M Server. Cannot\n *                              be ANJAY_SSID_ANY or ANJAY_SSID_BOOTSTRAP.\n * @param data                  Content of the message compiled previously with\n *                              @ref anjay_send_batch_builder_compile .\n * @param finished_handler      Handler called if the server confirmed message\n *                              delivery or if no response was received in\n *                              expected time (handler can be NULL).\n * @param finished_handler_data Data for the handler.\n *\n * @returns one of the @ref anjay_send_result_t enum values.\n */\nanjay_send_result_t anjay_send(anjay_t *anjay,\n                               anjay_ssid_t ssid,\n                               const anjay_send_batch_t *data,\n                               anjay_send_finished_handler_t *finished_handler,\n                               void *finished_handler_data);\n\n/**\n * Sends data to the LwM2M server, either immediately, or deferring it until\n * such operation will be possible.\n *\n * This function is equivalent to @ref anjay_send, but in cases when the former\n * would return @ref ANJAY_SEND_ERR_OFFLINE or @ref ANJAY_SEND_ERR_BOOTSTRAP,\n * this variant returns success and postpones the actual Send operation until\n * the server connection identified by @p ssid is online.\n *\n * If at that time, the server in question will be removed from the data model,\n * registered using a LwM2M version that does not support the Send operation\n * (i.e., LwM2M 1.0), or the Mute Send resource changes while the Send is\n * deferred, the operation is cancelled and @p finished_handler is called with\n * the @c result argument set to @ref ANJAY_SEND_DEFERRED_ERROR.\n *\n * @param anjay                 Anjay object to operate on.\n * @param ssid                  Short Server ID of target LwM2M Server. Cannot\n *                              be ANJAY_SSID_ANY or ANJAY_SSID_BOOTSTRAP.\n * @param data                  Content of the message compiled previously with\n *                              @ref anjay_send_batch_builder_compile .\n * @param finished_handler      Handler called if the server confirmed message\n *                              delivery or if no response was received in\n *                              expected time (handler can be NULL).\n * @param finished_handler_data Data for the handler.\n *\n * @returns one of the @ref anjay_send_result_t enum values.\n */\nanjay_send_result_t\nanjay_send_deferrable(anjay_t *anjay,\n                      anjay_ssid_t ssid,\n                      const anjay_send_batch_t *data,\n                      anjay_send_finished_handler_t *finished_handler,\n                      void *finished_handler_data);\n\n#endif // ANJAY_WITH_SEND\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif // ANJAY_INCLUDE_ANJAY_LWM2M_SEND_H\n"
  },
  {
    "path": "include_public/anjay/security.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ANJAY_INCLUDE_ANJAY_SECURITY_H\n#define ANJAY_INCLUDE_ANJAY_SECURITY_H\n\n#include <anjay/dm.h>\n\n#include <avsystem/commons/avs_stream.h>\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\ntypedef struct {\n    /** Resource: Short Server ID */\n    anjay_ssid_t ssid;\n    /** Resource: LwM2M Server URI */\n    const char *server_uri;\n    /** Resource: Bootstrap Server */\n    bool bootstrap_server;\n    /** Resource: Security Mode */\n    anjay_security_mode_t security_mode;\n    /** Resource: Client Hold Off Time */\n    int32_t client_holdoff_s;\n    /** Resource: Bootstrap Server Account Timeout */\n    int32_t bootstrap_timeout_s;\n    /** Resource: Public Key Or Identity */\n    const uint8_t *public_cert_or_psk_identity;\n    size_t public_cert_or_psk_identity_size;\n    /** Resource: Secret Key */\n    const uint8_t *private_cert_or_psk_key;\n    size_t private_cert_or_psk_key_size;\n    /** Resource: Server Public Key */\n    const uint8_t *server_public_key;\n    size_t server_public_key_size;\n#ifdef ANJAY_WITH_LWM2M11\n    /** Resource: Matching Type (NULL for not present) */\n    const uint8_t *matching_type;\n    /** Resource: SNI */\n    const char *server_name_indication;\n    /** Resource: Certificate Usage (NULL for not present) */\n    const uint8_t *certificate_usage;\n    /** Resource: DTLS/TLS Ciphersuite;\n     * Note: Passing a value with <c>num_ids == 0</c> (default) will cause the\n     * resource to be absent, resulting in a fallback to defaults. */\n    avs_net_socket_tls_ciphersuites_t ciphersuites;\n#endif // ANJAY_WITH_LWM2M11\n#ifdef ANJAY_WITH_SECURITY_STRUCTURED\n    /** Resource: Public Key Or Identity;\n     * This is an alternative to the @p public_cert_or_psk_identity and\n     * @p psk_identity fields that may be used only if @p security_mode is\n     * either @ref ANJAY_SECURITY_CERTIFICATE or @ref ANJAY_SECURITY_EST; it is\n     * also an error to specify non-empty values for more than one of these\n     * fields at the same time. */\n    avs_crypto_certificate_chain_info_t public_cert;\n    /** Resource: Secret Key;\n     * This is an alternative to the @p private_cert_or_psk_key and @ref psk_key\n     * fields that may be used only if @p security_mode is either\n     * @ref ANJAY_SECURITY_CERTIFICATE or @ref ANJAY_SECURITY_EST; it is also an\n     * error to specify non-empty values for more than one of these fields at\n     * the same time. */\n    avs_crypto_private_key_info_t private_key;\n    /** Resource: Public Key Or Identity;\n     * This is an alternative to the @p public_cert_or_psk_identity and\n     * @ref public_cert fields that may be used only if @p security_mode is\n     * @ref ANJAY_SECURITY_PSK; it is also an error to specify non-empty values\n     * for more than one of these fields at the same time. */\n    avs_crypto_psk_identity_info_t psk_identity;\n    /** Resource: Secret Key;\n     * This is an alternative to the @p private_cert_or_psk_key and\n     * @ref private_key fields that may be used only if @p security_mode is\n     * @ref ANJAY_SECURITY_PSK; it is also an error to specify non-empty values\n     * for more than one of these fields at the same time. */\n    avs_crypto_psk_key_info_t psk_key;\n#endif // ANJAY_WITH_SECURITY_STRUCTURED\n} anjay_security_instance_t;\n\n/**\n * Adds new Instance of Security Object and returns newly created Instance id\n * via @p inout_iid .\n *\n * Note: if @p *inout_iid is set to @ref ANJAY_ID_INVALID then the Instance id\n * is generated automatically, otherwise value of @p *inout_iid is used as a\n * new Security Instance Id.\n *\n * Note: @p instance may be safely freed by the user code after this function\n * finishes (internally a deep copy of @ref anjay_security_instance_t is\n * performed).\n *\n * Warning: calling this function during active communication with Bootstrap\n * Server may yield undefined behavior and unexpected failures may occur.\n *\n * @param anjay     Anjay instance with Security Object installed to operate on.\n * @param instance  Security Instance to insert.\n * @param inout_iid Security Instance id to use or @ref ANJAY_ID_INVALID .\n *\n * @return 0 on success, negative value in case of an error or if the instance\n * of specified id already exists.\n */\nint anjay_security_object_add_instance(\n        anjay_t *anjay,\n        const anjay_security_instance_t *instance,\n        anjay_iid_t *inout_iid);\n\n/**\n * Purges instances of Security Object leaving it in an empty state.\n *\n * @param anjay Anjay instance with Security Object installed to purge.\n */\nvoid anjay_security_object_purge(anjay_t *anjay);\n\n/**\n * Dumps Security Object Instances to the @p out_stream.\n *\n * @param anjay         Anjay instance with Security Object installed.\n * @param out_stream    Stream to write to.\n * @returns AVS_OK in case of success, or an error code.\n */\navs_error_t anjay_security_object_persist(anjay_t *anjay,\n                                          avs_stream_t *out_stream);\n\n/**\n * Attempts to restore Security Object Instances from specified @p in_stream.\n *\n * Note: if restore fails, then Security Object will be left untouched, on\n * success though all Instances stored within the Object will be purged.\n *\n * @param anjay     Anjay instance with Security Object installed.\n * @param in_stream Stream to read from.\n * @returns AVS_OK in case of success, or an error code.\n */\navs_error_t anjay_security_object_restore(anjay_t *anjay,\n                                          avs_stream_t *in_stream);\n\n/**\n * Checks whether the Security Object in Anjay instance has been modified since\n * last successful call to @ref anjay_security_object_persist or @ref\n * anjay_security_object_restore.\n */\nbool anjay_security_object_is_modified(anjay_t *anjay);\n\n/**\n * Installs the Security Object in an Anjay instance.\n *\n * The Security module does not require explicit cleanup; all resources will be\n * automatically freed up during the call to @ref anjay_delete.\n *\n * @param anjay Anjay instance for which the Security Object is installed.\n *\n * @returns 0 on success, or a negative value in case of error.\n */\nint anjay_security_object_install(anjay_t *anjay);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif /* ANJAY_INCLUDE_ANJAY_SECURITY_H */\n"
  },
  {
    "path": "include_public/anjay/server.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ANJAY_INCLUDE_ANJAY_SERVER_H\n#define ANJAY_INCLUDE_ANJAY_SERVER_H\n\n#include <anjay/anjay_config.h>\n#include <anjay/dm.h>\n\n#include <avsystem/commons/avs_stream.h>\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\ntypedef struct {\n    /** Resource: Short Server ID */\n    anjay_ssid_t ssid;\n    /** Resource: Lifetime */\n    int32_t lifetime;\n    /** Resource: Default Minimum Period - or a negative value to disable\n     * presence */\n    int32_t default_min_period;\n    /** Resource: Default Maximum Period - or a negative value to disable\n     * presence */\n    int32_t default_max_period;\n    /** Resource: Disable Timeout - or a negative value to disable presence */\n    int32_t disable_timeout;\n    /** Resource: Binding */\n    const char *binding;\n    /** Resource: Notification Storing When Disabled or Offline */\n    bool notification_storing;\n#ifdef ANJAY_WITH_LWM2M11\n    /** Resource: Bootstrap on Registration Failure. True if not set. */\n    const bool *bootstrap_on_registration_failure;\n    /** Resource: Preferred Transport */\n    char preferred_transport;\n    /** Resource: Mute Send */\n    bool mute_send;\n    /** Resource: Communication Retry Count. NULL if not set. */\n    const uint32_t *communication_retry_count;\n    /** Resource: Communication Retry Timer. NULL if not set. */\n    const uint32_t *communication_retry_timer;\n    /** Resource: Communication Sequence Retry Count. NULL if not set. */\n    const uint32_t *communication_sequence_retry_count;\n    /** Resource: Communication Sequence Delay Timer (in seconds). NULL if not\n     * set. */\n    const uint32_t *communication_sequence_delay_timer;\n#endif // ANJAY_WITH_LWM2M11\n} anjay_server_instance_t;\n\n/**\n * Adds new Instance of Server Object and returns newly created Instance id\n * via @p inout_iid .\n *\n * Note: if @p *inout_iid is set to @ref ANJAY_ID_INVALID then the Instance id\n * is generated automatically, otherwise value of @p *inout_iid is used as a\n * new Server Instance Id.\n *\n * Note: @p instance may be safely freed by the user code after this function\n * finishes (internally a deep copy of @ref anjay_server_instance_t is\n * performed).\n *\n * @param anjay     Anjay instance with Server Object installed to operate on.\n * @param instance  Server Instance to insert.\n * @param inout_iid Server Instance id to use or @ref ANJAY_ID_INVALID .\n *\n * @return 0 on success, negative value in case of an error or if the instance\n * of specified id already exists.\n */\nint anjay_server_object_add_instance(anjay_t *anjay,\n                                     const anjay_server_instance_t *instance,\n                                     anjay_iid_t *inout_iid);\n\n/**\n * Removes all instances of Server Object leaving it in an empty state.\n *\n * @param anjay Anjay instance with Server Object installed to purge.\n */\nvoid anjay_server_object_purge(anjay_t *anjay);\n\n/**\n * Retrieves a list of SSIDs currently present in the Server object. The SSIDs\n * are NOT guaranteed to be returned in any particular order. Returned list may\n * not be freed nor modified.\n *\n * Attempting to call this function if @ref anjay_server_object_install has not\n * been previously successfully called on the same Anjay instance yields\n * undefined behavior.\n *\n * The returned list pointer shall be considered invalidated by any call to @ref\n * anjay_sched_run, @ref anjay_serve, @ref anjay_server_object_add_instance,\n * @ref anjay_server_object_purge, @ref anjay_server_object_restore, or, if\n * called from within some callback handler, on return from that handler.\n *\n * If a transaction on the Server object is currently ongoing (e.g., during\n * Bootstrap), last known state from before the transaction will be returned.\n *\n * <strong>NOTE:</strong> If Anjay is compiled with thread safety enabled, the\n * list that is returned is normally accessed with the Anjay mutex locked. You\n * will need to ensure thread safety yourself if using this function. It is\n * recommended to only call it from callback functions called from within Anjay,\n * or in scheduler jobs.\n *\n * @param anjay Anjay instance with Server Object installed.\n *\n * @returns A list of known SSIDs on success, NULL when the object is empty.\n */\nAVS_LIST(const anjay_ssid_t) anjay_server_get_ssids(anjay_t *anjay);\n\n/**\n * Dumps Server Object Instances into the @p out_stream .\n *\n * @param anjay         Anjay instance with Server Object installed.\n * @param out_stream    Stream to write to.\n * @return AVS_OK in case of success, or an error code.\n */\navs_error_t anjay_server_object_persist(anjay_t *anjay,\n                                        avs_stream_t *out_stream);\n\n/**\n * Attempts to restore Server Object Instances from specified @p in_stream .\n *\n * Note: if restore fails, then Server Object will be left untouched, on\n * success though all Instances stored within the Object will be purged.\n *\n * @param anjay     Anjay instance with Server Object installed.\n * @param in_stream Stream to read from.\n * @return AVS_OK in case of success, or an error code.\n */\navs_error_t anjay_server_object_restore(anjay_t *anjay,\n                                        avs_stream_t *in_stream);\n\n/**\n * Checks whether the Server Object from Anjay instance has been modified since\n * last successful call to @ref anjay_server_object_persist or @ref\n * anjay_server_object_restore.\n */\nbool anjay_server_object_is_modified(anjay_t *anjay);\n\n/**\n * Installs the Server Object in an Anjay instance.\n *\n * The Server module does not require explicit cleanup; all resources\n * will be automatically freed up during the call to @ref anjay_delete.\n *\n * @param anjay Anjay instance for which the Server Object is installed.\n *\n * @returns 0 on success, or a negative value in case of error.\n */\nint anjay_server_object_install(anjay_t *anjay);\n\n/**\n * Sets the Lifetime value for the specified Server Instance ID.\n *\n * NOTE: Calling this function MAY trigger sending LwM2M Update message to\n * an associated LwM2M Server.\n *\n * @param anjay     Anjay instance for which the Server Object is installed.\n * @param iid       Server Object Instance for which the Lifetime shall be\n *                  altered.\n * @param lifetime  New value of the Lifetime Resource. MUST BE strictly\n *                  positive.\n *\n * @returns 0 on success, negative value in case of an error. If an error\n * is returned, the Lifetime value remains unchanged.\n */\nint anjay_server_object_set_lifetime(anjay_t *anjay,\n                                     anjay_iid_t iid,\n                                     int32_t lifetime);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif /* ANJAY_INCLUDE_ANJAY_SERVER_H */\n"
  },
  {
    "path": "include_public/anjay/stats.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n#ifndef ANJAY_INCLUDE_ANJAY_STATS_H\n#define ANJAY_INCLUDE_ANJAY_STATS_H\n\n#include <anjay/core.h>\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n/**\n * @returns the total amount of bytes transmitted by the client.\n *\n * NOTE: When ANJAY_WITH_NET_STATS is disabled this function always returns 0.\n */\nuint64_t anjay_get_tx_bytes(anjay_t *anjay);\n\n/**\n * @returns the amount of bytes received by the client.\n *\n * NOTE: When ANJAY_WITH_NET_STATS is disabled this function always returns 0.\n */\nuint64_t anjay_get_rx_bytes(anjay_t *anjay);\n\n/**\n * @returns the number of packets received by the client to which cached\n *          responses were found.\n *\n * NOTE: When ANJAY_WITH_NET_STATS is disabled this function always returns 0.\n */\nuint64_t anjay_get_num_incoming_retransmissions(anjay_t *anjay);\n\n/**\n * @returns the number of packets sent by the client that were already\n *          cached as well as requests which the client did not get any\n *          response to.\n *\n * NOTE: When ANJAY_WITH_NET_STATS is disabled this function always returns 0.\n */\nuint64_t anjay_get_num_outgoing_retransmissions(anjay_t *anjay);\n\n#ifdef __cplusplus\n} /* extern \"C\" */\n#endif\n\n#endif /* ANJAY_INCLUDE_ANJAY_STATS_H */\n"
  },
  {
    "path": "include_public/anjay/sw_mgmt.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ANJAY_INCLUDE_ANJAY_SW_MGMT_H\n#define ANJAY_INCLUDE_ANJAY_SW_MGMT_H\n\n#include <anjay/anjay_config.h>\n#include <anjay/dm.h>\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n/**\n * Numeric values of the Update Result resource. See LwM2M\n * specification related to object 9 for details.\n */\ntypedef enum {\n    ANJAY_SW_MGMT_UPDATE_RESULT_INITIAL = 0,\n    ANJAY_SW_MGMT_UPDATE_RESULT_DOWNLOADING = 1,\n    ANJAY_SW_MGMT_UPDATE_RESULT_INSTALLED = 2,\n    ANJAY_SW_MGMT_UPDATE_RESULT_DOWNLOADED_VERIFIED = 3,\n    ANJAY_SW_MGMT_UPDATE_RESULT_NOT_ENOUGH_SPACE = 50,\n    ANJAY_SW_MGMT_UPDATE_RESULT_OUT_OF_MEMORY = 51,\n    ANJAY_SW_MGMT_UPDATE_RESULT_CONNECTION_LOST = 52,\n    ANJAY_SW_MGMT_UPDATE_RESULT_INTEGRITY_FAILURE = 53,\n    ANJAY_SW_MGMT_UPDATE_RESULT_UNSUPPORTED_PACKAGE_TYPE = 54,\n    ANJAY_SW_MGMT_UPDATE_RESULT_INVALID_URI = 56,\n    ANJAY_SW_MGMT_UPDATE_RESULT_UPDATE_ERROR = 57,\n    ANJAY_SW_MGMT_UPDATE_RESULT_INSTALLATION_FAILURE = 58,\n    ANJAY_SW_MGMT_UPDATE_RESULT_UNINSTALLATION_FAILURE = 59\n} anjay_sw_mgmt_update_result_t;\n\n/** @name Software update result codes\n * @{\n * The following result codes may be returned from\n * @ref anjay_sw_mgmt_stream_write_t or @ref anjay_sw_mgmt_stream_finish_t to\n * control the value of Update Result resource in case of an error.\n *\n * Their values correspond to negated numeric values of that resource. However,\n * attempting to use other negated value will be checked and cause a fall-back\n * to a value default for a given handler.\n */\n#define ANJAY_SW_MGMT_ERR_NOT_ENOUGH_SPACE \\\n    (-(int) ANJAY_SW_MGMT_UPDATE_RESULT_NOT_ENOUGH_SPACE)\n#define ANJAY_SW_MGMT_ERR_OUT_OF_MEMORY \\\n    (-(int) ANJAY_SW_MGMT_UPDATE_RESULT_OUT_OF_MEMORY)\n#define ANJAY_SW_MGMT_ERR_INTEGRITY_FAILURE \\\n    (-(int) ANJAY_SW_MGMT_UPDATE_RESULT_INTEGRITY_FAILURE)\n#define ANJAY_SW_MGMT_ERR_UNSUPPORTED_PACKAGE_TYPE \\\n    (-(int) ANJAY_SW_MGMT_UPDATE_RESULT_UNSUPPORTED_PACKAGE_TYPE)\n/** @} */\n\n/**\n * Possible values that control Update State, Update Result and Activation State\n * resources at the time of initialization of the Software Management object.\n */\ntypedef enum {\n    /**\n     * Corresponds to the \"Initial\" Update State and \"Initial\" Update Result.\n     * Shall be used for software instances which are not yet downloaded.\n     *\n     * Note: \"Initial\" Update State and \"Initial\" Update Result can be also\n     * caused by preparing installed software for update process (by executing\n     * Uninstall resource with \"1\" argument), although, in case of a reboot, it\n     * is recommended to revert back to \"Installed\" Update State by initializing\n     * the object instance with @ref\n     * ANJAY_SW_MGMT_INITIAL_STATE_INSTALLED_DEACTIVATED or @ref\n     * ANJAY_SW_MGMT_INITIAL_STATE_INSTALLED_ACTIVATED . Software Management\n     * Object in its current state is not able to differentiate these two\n     * situations.\n     */\n    ANJAY_SW_MGMT_INITIAL_STATE_IDLE,\n\n    /**\n     * Corresponds to the \"Downloaded\" Update State and \"Initial\" Update Result.\n     * Shall be used when the device unexpectedly rebooted when the software\n     * package has already been downloaded into some non-volatile memory and\n     * integrity check wasn't performed yet.\n     */\n    ANJAY_SW_MGMT_INITIAL_STATE_DOWNLOADED,\n\n    /**\n     * Corresponds to the \"Delivered\" Update State and \"Initial\" Update Result.\n     * Shall be used when the device unexpectedly rebooted when the software\n     * package has already been downloaded into some non-volatile memory and\n     * integrity check was performed.\n     */\n    ANJAY_SW_MGMT_INITIAL_STATE_DELIVERED,\n\n    /**\n     * Corresponds to the \"Delivered\" Update State and \"Initial\" Update Result.\n     * Shall be used when the device has rebooted as a part of installation\n     * process, which hasn't completed yet. The application should call @ref\n     * anjay_sw_mgmt_finish_pkg_install to set the result to success or failure\n     * after the installation process is complete.\n     */\n    ANJAY_SW_MGMT_INITIAL_STATE_INSTALLING,\n\n    /**\n     * Corresponds to the \"Installed\" Update State, \"Installed\"\n     * Update Result and Activation State set to false. Shall be used when given\n     * software instance is installed, but deactivated.\n     */\n    ANJAY_SW_MGMT_INITIAL_STATE_INSTALLED_DEACTIVATED,\n\n    /**\n     * Corresponds to the \"Installed\" Update State, \"Installed\"\n     * Update Result and Activation State set to true. Shall be used when given\n     * software instance is installed and activated.\n     */\n    ANJAY_SW_MGMT_INITIAL_STATE_INSTALLED_ACTIVATED\n} anjay_sw_mgmt_initial_state_t;\n\n/**\n * Information about the state to initialize the Software Management object\n * instance.\n */\ntypedef struct {\n    /**\n     * Controls initialization of Update State, Update Result and Activation\n     * State resources.\n     */\n    anjay_sw_mgmt_initial_state_t initial_state;\n\n    /**\n     * Software Management object instance ID. As the server may expect\n     * the instance ID's to be unchanged, they must be set explicitly\n     * by the user.\n     */\n    anjay_iid_t iid;\n\n    /**\n     * Opaque pointer to instance-specific user data.\n     */\n    void *inst_ctx;\n} anjay_sw_mgmt_instance_initializer_t;\n\n/**\n * Opens the stream that will be used to write the software package to.\n *\n * The intended way of implementing this handler is to open a temporary file\n * using <c>fopen()</c> or allocate some memory buffer that may then be used to\n * store the downloaded data in. The library will not attempt to call\n * @ref anjay_sw_mgmt_stream_write_t without having previously called\n * @ref anjay_sw_mgmt_stream_open_t . Please see\n * @ref anjay_sw_mgmt_handlers_t for more information about state transitions.\n *\n * @param obj_ctx  Opaque pointer to object-wide user data, as passed to\n *                 @ref anjay_sw_mgmt_settings_t .\n *\n * @param iid      ID of Software Management object instance.\n *\n * @param inst_ctx Opaque pointer to instance-specific user data, as passed\n *                 to @ref anjay_sw_mgmt_instance_initializer_t or\n *                 <c>out_inst_ctx</c> parameter of\n *                 @ref anjay_sw_mgmt_add_handler_t .\n *\n * @returns The callback shall return 0 if successful or a negative value in\n *          case of error. Error codes are <strong>NOT</strong> handled here, so\n *          attempting to return <c>ANJAY_SW_MGMT_ERR_*</c> values will\n *          <strong>NOT</strong> cause any effect different than any other\n *          negative value.\n */\ntypedef int\nanjay_sw_mgmt_stream_open_t(void *obj_ctx, anjay_iid_t iid, void *inst_ctx);\n\n/**\n * Writes data to the download stream.\n *\n * May be called multiple times after @ref anjay_sw_mgmt_stream_open_t, once\n * for each consecutive chunk of downloaded data.\n *\n * @param obj_ctx  Opaque pointer to object-wide user data, as passed to\n *                 @ref anjay_sw_mgmt_settings_t .\n *\n * @param iid      ID of Software Management object instance.\n *\n * @param inst_ctx Opaque pointer to instance-specific user data, as passed to\n *                 @ref anjay_sw_mgmt_instance_initializer_t or\n *                 <c>out_inst_ctx</c> parameter of\n *                 @ref anjay_sw_mgmt_add_handler_t .\n *\n * @param data     Pointer to a chunk of the software package being downloaded.\n *                 Guaranteed to be non-<c>NULL</c>.\n *\n * @param length   Number of bytes in the chunk pointed to by <c>data</c>.\n *                 Guaranteed to be greater than zero.\n *\n * @returns The callback shall return 0 if successful or a negative value in\n *          case of error. If one of the <c>ANJAY_SW_MGMT_ERR_*</c> value is\n *          returned, an equivalent value will be set in the Update Result\n *          Resource.\n */\ntypedef int anjay_sw_mgmt_stream_write_t(void *obj_ctx,\n                                         anjay_iid_t iid,\n                                         void *inst_ctx,\n                                         const void *data,\n                                         size_t length);\n\n/**\n * Closes the download stream.\n *\n * Will be called after a series of @ref anjay_sw_mgmt_stream_write_t calls\n * after the whole package is downloaded.\n *\n * The intended way of implementing this handler is to e.g. call <c>fclose()</c>\n * on the downloaded file. After that, package might also be uncompressed,\n * decrypted and checked for integrity in @ref anjay_sw_mgmt_check_integrity_t .\n * The exact split of responsibility between these two methods is not clearly\n * defined and up to implementor.\n *\n * Note that regardless of the return value, the stream is considered to be\n * closed. That is, upon successful return, the Update State resource is\n * considered to be either in the <em>Downloaded</em> state, and upon\n * returning an error - in the <em>Initial</em> state, with appropriate\n * Update Result set.\n *\n * @param obj_ctx  Opaque pointer to object-wide user data, as passed to\n *                 @ref anjay_sw_mgmt_settings_t .\n *\n * @param iid      ID of Software Management object instance.\n *\n * @param inst_ctx Opaque pointer to instance-specific user data, as passed to\n *                 @ref anjay_sw_mgmt_instance_initializer_t or\n *                 <c>out_inst_ctx</c> parameter of\n *                 @ref anjay_sw_mgmt_add_handler_t .\n *\n * @returns The callback shall return 0 if successful or a negative value in\n *          case of error. If one of the <c>ANJAY_SW_MGMT_ERR_*</c> value is\n *          returned, an equivalent value will be set in the Update Result\n *          Resource.\n */\ntypedef int\nanjay_sw_mgmt_stream_finish_t(void *obj_ctx, anjay_iid_t iid, void *inst_ctx);\n\n/**\n * Conducts integrity check of downloaded package.\n *\n * If this handler is not implemented at all (with the corresponding field set\n * to <c>NULL</c>), integrity check will be entirely skipped, and the Update\n * State resource upon finished download will change the state directly from\n * <em>Download started</em> to <em>Delivered</em>.\n *\n * @param obj_ctx  Opaque pointer to object-wide user data, as passed to\n *                 @ref anjay_sw_mgmt_settings_t .\n *\n * @param iid      ID of Software Management object instance.\n *\n * @param inst_ctx Opaque pointer to instance-specific user data, as passed to\n *                 @ref anjay_sw_mgmt_instance_initializer_t or\n *                 <c>out_inst_ctx</c> parameter of\n *                 @ref anjay_sw_mgmt_add_handler_t .\n *\n * @returns The callback shall return 0 if successful or a negative value in\n *          case of error. If one of the <c>ANJAY_SW_MGMT_ERR_*</c> value is\n *          returned, an equivalent value will be set in the Update Result\n *          Resource.\n */\ntypedef int\nanjay_sw_mgmt_check_integrity_t(void *obj_ctx, anjay_iid_t iid, void *inst_ctx);\n\n/**\n * Resets the software installation state and performs any applicable cleanup of\n * temporary storage if necessary.\n *\n * Will be called at request of the server (upon execution of Uninstall resource\n * in <em>Delivered</em> state in purpose of removing downloaded, but not yet\n * installed software package) or after a failed download. Note that it may be\n * called without previously calling @ref anjay_sw_mgmt_stream_finish_t, so it\n * shall also close the currently open download stream, if any.\n *\n * @param obj_ctx  Opaque pointer to object-wide user data, as passed to\n *                 @ref anjay_sw_mgmt_settings_t .\n *\n * @param iid      ID of Software Management object instance.\n *\n * @param inst_ctx Opaque pointer to instance-specific user data, as passed to\n *                 @ref anjay_sw_mgmt_instance_initializer_t or\n *                 <c>out_inst_ctx</c> parameter of\n *                 @ref anjay_sw_mgmt_add_handler_t .\n */\ntypedef void\nanjay_sw_mgmt_reset_t(void *obj_ctx, anjay_iid_t iid, void *inst_ctx);\n\n/**\n * Returns the name of downloaded software package.\n *\n * The name will be exposed in the data model as the PkgName Resource. If this\n * callback returns <c>NULL</c> or is not implemented at all (with the\n * corresponding field set to <c>NULL</c>), that Resource will have its value\n * set to empty string.\n *\n * It only makes sense for this handler to return non-<c>NULL</c> values if\n * there is a valid package already downloaded. The library will call this\n * handler in <em>Delivered</em> and <em>Installed</em> states.\n *\n * The library will not attempt to deallocate the returned pointer. User code\n * must assure that the pointer will remain valid at least until return from\n * @ref anjay_serve or @ref anjay_sched_run .\n *\n * @param obj_ctx  Opaque pointer to object-wide user data, as passed to\n *                 @ref anjay_sw_mgmt_settings_t .\n *\n * @param iid      ID of Software Management object instance.\n *\n * @param inst_ctx Opaque pointer to instance-specific user data, as passed to\n *                 @ref anjay_sw_mgmt_instance_initializer_t or\n *                 <c>out_inst_ctx</c> parameter of\n *                 @ref anjay_sw_mgmt_add_handler_t .\n *\n * @returns The callback shall return a pointer to a null-terminated string\n *          containing the package name, or <c>NULL</c> if it is not currently\n *          available.\n */\ntypedef const char *\nanjay_sw_mgmt_get_name_t(void *obj_ctx, anjay_iid_t iid, void *inst_ctx);\n\n/**\n * Returns the version of downloaded software package.\n *\n * The version will be exposed in the data model as the PkgVersion Resource. If\n * this callback returns <c>NULL</c> or is not implemented at all (with the\n * corresponding field set to <c>NULL</c>), that Resource will have its value\n * set to empty string.\n *\n * It only makes sense for this handler to return non-<c>NULL</c> values if\n * there is a valid package already downloaded. The library will call this\n * handler in <em>Delivered</em> and <em>Installed</em> states.\n *\n * The library will not attempt to deallocate the returned pointer. User code\n * must assure that the pointer will remain valid at least until return from\n * @ref anjay_serve or @ref anjay_sched_run .\n *\n * @param obj_ctx  Opaque pointer to object-wide user data, as passed to\n *                 @ref anjay_sw_mgmt_settings_t .\n *\n * @param iid      ID of Software Management object instance.\n *\n * @param inst_ctx Opaque pointer to instance-specific user data, as passed to\n *                 @ref anjay_sw_mgmt_instance_initializer_t or\n *                 <c>out_inst_ctx</c> parameter of\n *                 @ref anjay_sw_mgmt_add_handler_t .\n *\n * @returns The callback shall return a pointer to a null-terminated string\n *          containing the package name, or <c>NULL</c> if it is not currently\n *          available.\n */\ntypedef const char *\nanjay_sw_mgmt_get_version_t(void *obj_ctx, anjay_iid_t iid, void *inst_ctx);\n\n/**\n * Performs the actual installation of previously downloaded software package.\n *\n * Will be called at the request of the server, after a package has been\n * downloaded and its integrity has been checked.\n *\n * Some users will want to implement software installation in a way that\n * involves a reboot. In such case, it is expected that this callback will do\n * either one of the following:\n *\n * - software package installation, terminate outermost event loop and return,\n *   call reboot after @ref anjay_event_loop_run()\n * - perform the software package installation internally and then reboot, it\n *   means that the return will never happen (although the library won't be able\n *   to send the acknowledgement of execution of Install resource)\n *\n * After rebooting, the result of the installation process may be passed to the\n * library during initialization via the <c>initial_state</c> field of\n * @ref anjay_sw_mgmt_instance_initializer_t .\n *\n * Alternatively, if the installation can be performed without reinitializing\n * Anjay, you can use @ref anjay_sw_mgmt_finish_pkg_install (either from within\n * the handler or some time after returning from it) to pass the installation\n * result.\n *\n * @param obj_ctx  Opaque pointer to object-wide user data, as passed to\n *                 @ref anjay_sw_mgmt_settings_t .\n *\n * @param iid      ID of Software Management object instance.\n *\n * @param inst_ctx Opaque pointer to instance-specific user data, as passed to\n *                 @ref anjay_sw_mgmt_instance_initializer_t or\n *                 <c>out_inst_ctx</c> parameter of\n *                 @ref anjay_sw_mgmt_add_handler_t .\n *\n * @returns The callback shall return a negative value if it can be determined\n *          without a reboot that the package installation cannot be\n *          successfully performed. Error codes are <strong>NOT</strong> handled\n *          here, so attempting to return <c>ANJAY_SW_MGMT_ERR_*</c> values will\n *          <strong>NOT</strong> cause any effect different than any other\n *          negative value.\n *\n */\ntypedef int\nanjay_sw_mgmt_pkg_install_t(void *obj_ctx, anjay_iid_t iid, void *inst_ctx);\n\n/**\n * Uninstalls software package.\n *\n * This callback will be called only in <em>Installed</em> state, if the\n * Uninstall resource was executed with no argument or argument \"0\".\n *\n * If this callback is not implemented at all (with the corresponding field set\n * to <c>NULL</c>), uninstalling software will not be possible.\n *\n * Note: in case the server requests to remove the software package\n * which has been delivered, but not yet installed (<em>Delivered</em> state),\n * anjay_sw_mgmt_reset_t callback will be used.\n *\n * @param obj_ctx  Opaque pointer to object-wide user data, as passed to\n *                 @ref anjay_sw_mgmt_settings_t .\n *\n * @param iid      ID of Software Management object instance.\n *\n * @param inst_ctx Opaque pointer to instance-specific user data, as passed to\n *                 @ref anjay_sw_mgmt_instance_initializer_t or\n *                 <c>out_inst_ctx</c> parameter of\n *                 @ref anjay_sw_mgmt_add_handler_t .\n *\n * @returns The callback shall return 0 if successful or a negative value in\n *          case of error. Error codes are <strong>NOT</strong> handled here, so\n *          attempting to return <c>ANJAY_SW_MGMT_ERR_*</c> values will\n *          <strong>NOT</strong> cause any effect different than any other\n *          negative value.\n */\ntypedef int\nanjay_sw_mgmt_pkg_uninstall_t(void *obj_ctx, anjay_iid_t iid, void *inst_ctx);\n\n/**\n * Prepares software package for update.\n *\n * This callback will be called only in <em>Installed</em> state, if the\n * Uninstall resource was executed with argument \"1\".\n *\n * If this callback is not implemented at all (with the corresponding field set\n * to <c>NULL</c>), updating software will not be possible.\n *\n * Most users will want to implement this callback as a no-op.\n *\n * @param obj_ctx  Opaque pointer to object-wide user data, as passed to\n *                 @ref anjay_sw_mgmt_settings_t .\n *\n * @param iid      ID of Software Management object instance.\n *\n * @param inst_ctx Opaque pointer to instance-specific user data, as passed to\n *                 @ref anjay_sw_mgmt_instance_initializer_t or\n *                 <c>out_inst_ctx</c> parameter of\n *                 @ref anjay_sw_mgmt_add_handler_t .\n *\n * @returns The callback shall return 0 if successful or a negative value in\n *          case of error. Error codes are <strong>NOT</strong> handled here, so\n *          attempting to return <c>ANJAY_SW_MGMT_ERR_*</c> values will\n *          <strong>NOT</strong> cause any effect different than any other\n *          negative value.\n */\ntypedef int anjay_sw_mgmt_prepare_for_update_t(void *obj_ctx,\n                                               anjay_iid_t iid,\n                                               void *inst_ctx);\n\n/**\n * Activates software package.\n *\n * This callback will be called only in <em>Installed</em> state. The activation\n * state does not affect the execution of this callback. If the user wants to\n * block the execution when the package is already active, this must be done on\n * user side. The @ref anjay_sw_mgmt_get_activation_state function may be\n * useful.\n *\n * Some of the users will want to opt-out from ability to handle the activation\n * state - if this callback is not implemented at all (with the corresponding\n * field set to <c>NULL</c>), executing Activate resource will always succeed.\n * If this callback is not implemented, @ref anjay_sw_mgmt_deactivate_t MUST\n * NOT be implemented too.\n *\n * @param obj_ctx  Opaque pointer to object-wide user data, as passed to\n *                 @ref anjay_sw_mgmt_settings_t .\n *\n * @param iid      ID of Software Management object instance.\n *\n * @param inst_ctx Opaque pointer to instance-specific user data, as passed to\n *                 @ref anjay_sw_mgmt_instance_initializer_t or\n *                 <c>out_inst_ctx</c> parameter of\n *                 @ref anjay_sw_mgmt_add_handler_t .\n *\n * @returns The callback shall return 0 if successful or a negative value in\n *          case of error or when user do not want to execute this callback.\n *          Error codes are <strong>NOT</strong> handled here, so attempting to\n *          return <c>ANJAY_SW_MGMT_ERR_*</c> values will <strong>NOT</strong>\n *          cause any effect different than any other negative value.\n */\ntypedef int\nanjay_sw_mgmt_activate_t(void *obj_ctx, anjay_iid_t iid, void *inst_ctx);\n\n/**\n * Deactivates software package.\n *\n * This callback will be called only in <em>Installed</em> state. The activation\n * state does not affect the execution of this callback. If the user wants to\n * block the execution when the package is already deactivate, this must be done\n * on user side. The @ref anjay_sw_mgmt_get_activation_state function may be\n * useful.\n *\n * Some of the users will want to opt-out from ability to handle the activation\n * state - if this callback is not implemented at all (with the corresponding\n * field set to <c>NULL</c>), executing Deactivate resource will always succeed.\n * If this callback is not implemented, @ref anjay_sw_mgmt_activate_t MUST\n * NOT be implemented too.\n *\n * @param obj_ctx  Opaque pointer to object-wide user data, as passed to\n *                 @ref anjay_sw_mgmt_settings_t .\n *\n * @param iid      ID of Software Management object instance.\n *\n * @param inst_ctx Opaque pointer to instance-specific user data, as passed to\n *                 @ref anjay_sw_mgmt_instance_initializer_t or\n *                 <c>out_inst_ctx</c> parameter of\n *                 @ref anjay_sw_mgmt_add_handler_t .\n *\n * @returns The callback shall return 0 if successful or a negative value in\n *          case of error or when user do not want to execute this callback.\n *          Error codes are <strong>NOT</strong> handled here, so attempting to\n *          return <c>ANJAY_SW_MGMT_ERR_*</c> values will <strong>NOT</strong>\n *          cause any effect different than any other negative value.\n */\ntypedef int\nanjay_sw_mgmt_deactivate_t(void *obj_ctx, anjay_iid_t iid, void *inst_ctx);\n\n#ifdef ANJAY_WITH_DOWNLOADER\n\n/**\n * Queries security information that shall be used for an encrypted connection\n * with a PULL-mode download server.\n *\n * May be called before @ref anjay_sw_mgmt_stream_open_t if the download is to\n * be performed in PULL mode and the connection needs to use TLS or DTLS\n * encryption.\n *\n * Note that the @ref anjay_security_config_t contains references to file paths,\n * binary security keys, and/or ciphersuite lists. It is the user's\n * responsibility to appropriately allocate them and ensure proper lifetime of\n * the returned pointers. The returned security information may only be\n * invalidated in a call to @ref anjay_sw_mgmt_reset_t or after a call to\n * @ref anjay_delete .\n *\n * If this handler is not implemented at all (with the corresponding field set\n * to <c>NULL</c>), @ref anjay_security_config_from_dm will be used as a default\n * way to get security information.\n *\n * <strong>WARNING:</strong> If the aforementioned @ref\n * anjay_security_config_from_dm function won't find any server\n * connection that matches the <c>download_uri</c> by protocol,\n * hostname and port triple, it'll attempt to match a configuration just by the\n * hostname. This may cause Anjay to use wrong security configuration, e.g. in\n * case when both CoAPS LwM2M server and HTTPS software package server have the\n * same hostname, but require different security configs.\n *\n * If no user-defined handler is provided and the call to\n * @ref anjay_security_config_from_dm fails (including case when no matching\n * LwM2M Security Object instance is found, even just by the hostname),\n * @ref anjay_security_config_pkix will be used as an additional fallback\n * if <c>ANJAY_WITH_LWM2M11</c> is enabled and a valid trust store is available\n * (either specified through <c>use_system_trust_store</c>,\n * <c>trust_store_certs</c> or <c>trust_store_crls</c> fields in\n * <c>anjay_configuration_t</c>, or obtained via <c>/est/crts</c> request if\n * <c>est_cacerts_policy</c> is set to\n * <c>ANJAY_EST_CACERTS_IF_EST_CONFIGURED</c> or\n * <c>ANJAY_EST_CACERTS_ALWAYS</c>).\n *\n * You may also use these functions yourself, for example as a fallback\n * mechanism.\n *\n * @param obj_ctx             Opaque pointer to object-wide user data, as\n *                            passed to @ref anjay_sw_mgmt_settings_t .\n *\n * @param iid                 ID of Software Management object instance.\n *\n * @param inst_ctx            Opaque pointer to instance-specific user data,\n *                            as passed to\n *                            @ref anjay_sw_mgmt_instance_initializer_t or\n *                            <c>out_inst_ctx</c> parameter of\n *                            @ref anjay_sw_mgmt_add_handler_t .\n *\n * @param download_uri        URI of the package from which a Pull-mode download\n *                            is performed.\n *\n * @param out_security_info   Pointer in which the handler shall fill in\n *                            security configuration to use for download. Note\n *                            that leaving this value as empty without filling\n *                            it in will result in a configuration that is\n *                            <strong>valid, but very insecure</strong>: it will\n *                            cause any server certificate to be accepted\n *                            without validation. Any pointers used within the\n *                            supplied structure shall remain valid until either\n *                            a call to @ref anjay_sw_mgmt_reset_t, or exit to\n *                            the event loop (from either @ref anjay_serve,\n *                            @ref anjay_sched_run or\n *                            @ref anjay_sw_mgmt_add_instance), whichever\n *                            happens first. Anjay will <strong>not</strong>\n *                            attempt to deallocate anything automatically.\n *\n * @returns The callback shall return 0 if successful or a negative value in\n *          case of error. Error codes are <strong>NOT</strong> handled here, so\n *          attempting to return <c>ANJAY_SW_MGMT_ERR_*</c> values will\n *          <strong>NOT</strong> cause any effect different than any other\n *          negative value.\n */\ntypedef int\nanjay_sw_mgmt_get_security_config_t(void *obj_ctx,\n                                    anjay_iid_t iid,\n                                    void *inst_ctx,\n                                    const char *download_uri,\n                                    anjay_security_config_t *out_security_info);\n\n/**\n * Returns tx_params used to override default ones.\n *\n * If this handler is not implemented at all (with the corresponding field set\n * to <c>NULL</c>), <c>udp_tx_params</c> from <c>anjay_t</c> object are used.\n *\n * @param obj_ctx      Opaque pointer to object-wide user data, as passed to\n *                     @ref anjay_sw_mgmt_settings_t .\n *\n * @param iid          ID of Software Management object instance.\n *\n * @param inst_ctx     Opaque pointer to instance-specific user data, as passed\n *                     to @ref anjay_sw_mgmt_instance_initializer_t or\n *                     <c>out_inst_ctx</c> parameter of\n *                     @ref anjay_sw_mgmt_add_handler_t .\n *\n * @param download_uri Target software URI.\n *\n * @returns Object with CoAP transmission parameters.\n */\ntypedef avs_coap_udp_tx_params_t\nanjay_sw_mgmt_get_coap_tx_params_t(void *obj_ctx,\n                                   anjay_iid_t iid,\n                                   void *inst_ctx,\n                                   const char *download_uri);\n\n/**\n * Returns request timeout to be used during software download over CoAP+TCP or\n * HTTP.\n *\n * If this handler is not implemented at all (with the corresponding field set\n * to <c>NULL</c>), <c>coap_tcp_request_timeout</c> from <c>anjay_t</c> object\n * will be used for CoAP+TCP, and <c>AVS_NET_SOCKET_DEFAULT_RECV_TIMEOUT</c>\n * (i.e., 30 seconds) will be used for HTTP.\n *\n * @param obj_ctx      Opaque pointer to object-wide user data, as passed to\n *                     @ref anjay_sw_mgmt_settings_t .\n *\n * @param iid          ID of Software Management object instance.\n *\n * @param inst_ctx     Opaque pointer to instance-specific user data, as passed\n *                     to @ref anjay_sw_mgmt_instance_initializer_t or\n *                     <c>out_inst_ctx</c> parameter of\n *                     @ref anjay_sw_mgmt_add_handler_t .\n *\n * @param download_uri  Target software URI.\n *\n * @returns The desired request timeout. If the value returned is non-positive\n *          (including zero and invalid value), the default will be used.\n */\ntypedef avs_time_duration_t\nanjay_sw_mgmt_get_tcp_request_timeout_t(void *obj_ctx,\n                                        anjay_iid_t iid,\n                                        void *inst_ctx,\n                                        const char *download_uri);\n\n/**\n * Suspends the operation of PULL-mode downloads in the Software Management\n * module.\n *\n * This will have the effect of suspending any ongoing downloads (see\n * @ref anjay_download_suspend for details), as well as preventing new downloads\n * from being started.\n *\n * When PULL-mode downloads are suspended, @ref anjay_sw_mgmt_stream_open_t\n * will <strong>NOT</strong> be called when a download request is issued.\n * However, @ref anjay_sw_mgmt_get_security_config_t,\n * @ref anjay_sw_mgmt_get_coap_tx_params_t and\n * @ref anjay_sw_mgmt_get_tcp_request_timeout_t will be called. You may call\n * @ref anjay_sw_mgmt_pull_reconnect from one of these functions if you decide\n * to accept the download immediately after all.\n *\n * @param anjay         Anjay object to operate on.\n */\nvoid anjay_sw_mgmt_pull_suspend(anjay_t *anjay);\n\n/**\n * Reconnects any ongoing PULL-mode downloads in the Software Management module.\n * Which could be disconnected due to connection loss or deliberate suspend.\n * In the latter case, when PULL-mode downloads are suspended (see\n * @ref anjay_sw_mgmt_pull_suspend), resumes normal operation.\n *\n * If an ongoing PULL-mode download exists, this will call\n * @ref anjay_download_reconnect internally, so you may want to reference the\n * documentation of that function for details.\n *\n * @param anjay         Anjay object to operate on.\n *\n * @returns 0 on success; -1 if @p anjay does not have the Software Management\n *          object installed or the latest non-zero error code returned by @ref\n *          anjay_download_reconnect .\n */\nint anjay_sw_mgmt_pull_reconnect(anjay_t *anjay);\n\n#endif // ANJAY_WITH_DOWNLOADER\n\n/**\n * Handles server's request to create new instance of Software Management\n * object.\n *\n * This callback allows the user to set up user-specific data or to reject\n * server's attempt to create a new object instance.\n *\n * If this handler is not implemented at all (with the corresponding field set\n * to <c>NULL</c>), library won't allow creating new instances of the object.\n *\n * This callback won't be called if the application adds a new instance of\n * the object on its own.\n *\n * @param obj_ctx      Opaque pointer to object-wide user data, as passed to\n *                     @ref anjay_sw_mgmt_settings_t .\n *\n * @param iid          ID of Software Management object instance.\n *\n * @param out_inst_ctx Pointer in which the handler shall fill in\n *                     opaque pointer to instance-specific user data.\n *\n * @returns The callback shall return 0 if successful or a negative value in\n *          case of error. Error codes are <strong>NOT</strong> handled here, so\n *          attempting to return <c>ANJAY_SW_MGMT_ERR_*</c> values will\n *          <strong>NOT</strong> cause any effect different than any other\n *          negative value.\n */\ntypedef int anjay_sw_mgmt_add_handler_t(void *obj_ctx,\n                                        anjay_iid_t iid,\n                                        void **out_inst_ctx);\n\n/**\n * Handles server's request to remove an instance of Software Management object.\n *\n * This callback allows the user to clean up user-specific data or to reject\n * server's attempt to remove an object instance.\n *\n * If this handler is not implemented at all (with the corresponding field set\n * to <c>NULL</c>), library won't allow deleting instances of the object.\n *\n * @param obj_ctx  Opaque pointer to object-wide user data, as passed to\n *                 @ref anjay_sw_mgmt_settings_t .\n *\n * @param iid      ID of Software Management object instance.\n *\n * @param inst_ctx Opaque pointer to instance-specific user data, as passed to\n *                 @ref anjay_sw_mgmt_instance_initializer_t or\n *                 <c>out_inst_ctx</c> parameter of\n *                 @ref anjay_sw_mgmt_add_handler_t .\n *\n * @returns The callback shall return 0 if successful or a negative value in\n *          case of error. Error codes are <strong>NOT</strong> handled here, so\n *          attempting to return <c>ANJAY_SW_MGMT_ERR_*</c> values will\n *          <strong>NOT</strong> cause any effect different than any other\n *          negative value.\n */\ntypedef int\nanjay_sw_mgmt_remove_handler_t(void *obj_ctx, anjay_iid_t iid, void *inst_ctx);\n\ntypedef struct {\n    /** Opens the stream that will be used to write the software package to;\n     * @ref anjay_sw_mgmt_stream_open_t */\n    anjay_sw_mgmt_stream_open_t *stream_open;\n\n    /** Writes data to the download stream;\n     * @ref anjay_sw_mgmt_stream_write_t */\n    anjay_sw_mgmt_stream_write_t *stream_write;\n\n    /** Closes the download stream; @ref anjay_sw_mgmt_stream_finish_t */\n    anjay_sw_mgmt_stream_finish_t *stream_finish;\n\n    /** Prepares the software package to be installed and checks\n     * its integrity; @ref anjay_sw_mgmt_check_integrity_t */\n    anjay_sw_mgmt_check_integrity_t *check_integrity;\n\n    /** Resets the software installation state and performs any applicable\n     * cleanup of temporary storage if necessary; @ref anjay_sw_mgmt_reset_t */\n    anjay_sw_mgmt_reset_t *reset;\n\n    /** Returns the name of downloaded software package;\n     * @ref anjay_sw_mgmt_get_name_t */\n    anjay_sw_mgmt_get_name_t *get_name;\n\n    /** Returns the version of downloaded software package;\n     * @ref anjay_sw_mgmt_get_version_t */\n    anjay_sw_mgmt_get_version_t *get_version;\n\n    /** Installs downloaded software package; @ref anjay_sw_mgmt_pkg_install_t\n     */\n    anjay_sw_mgmt_pkg_install_t *pkg_install;\n\n    /** Uninstalls software package; @ref anjay_sw_mgmt_pkg_uninstall_t */\n    anjay_sw_mgmt_pkg_uninstall_t *pkg_uninstall;\n\n    /** Prepares software package for update process; @ref\n     * anjay_sw_mgmt_prepare_for_update_t */\n    anjay_sw_mgmt_prepare_for_update_t *prepare_for_update;\n\n    /** Activates software package; @ref anjay_sw_mgmt_activate_t */\n    anjay_sw_mgmt_activate_t *activate;\n\n    /** Deactivates software package; @ref anjay_sw_mgmt_deactivate_t */\n    anjay_sw_mgmt_deactivate_t *deactivate;\n\n#ifdef ANJAY_WITH_DOWNLOADER\n    /** Queries security configuration that shall be used for an encrypted\n     * connection; @ref anjay_sw_mgmt_get_security_config_t */\n    anjay_sw_mgmt_get_security_config_t *get_security_config;\n\n    /** Queries request timeout to be used during software download over\n     * CoAP+TCP or HTTP; @ref anjay_sw_mgmt_get_tcp_request_timeout_t */\n    anjay_sw_mgmt_get_tcp_request_timeout_t *get_tcp_request_timeout;\n\n#    ifdef ANJAY_WITH_COAP_DOWNLOAD\n    /** Queries CoAP transmission parameters to be used during download\n     * process; @ref anjay_sw_mgmt_get_coap_tx_params_t */\n    anjay_sw_mgmt_get_coap_tx_params_t *get_coap_tx_params;\n#    endif // ANJAY_WITH_COAP_DOWNLOAD\n#endif     // ANJAY_WITH_DOWNLOADER\n\n    /** Accepts or rejects server's request to create a new instance\n     * of Software Management object; @ref anjay_sw_mgmt_add_handler_t */\n    anjay_sw_mgmt_add_handler_t *add_handler;\n\n    /** Accepts or rejects server's request to remove an instance\n     * of Software Management object; @ref anjay_sw_mgmt_remove_handler_t */\n    anjay_sw_mgmt_remove_handler_t *remove_handler;\n} anjay_sw_mgmt_handlers_t;\n\n/**\n * Settings of the Software Management module, global for all instances\n * installed.\n */\ntypedef struct {\n    /**\n     * Pointer to a set of handler functions that handle the\n     * platform-specific part of software management.\n     * Note: Contents of the structure are NOT copied, so it\n     * needs to remain valid for the lifetime of the object.\n     */\n    const anjay_sw_mgmt_handlers_t *handlers;\n\n    /**\n     * Opaque pointer to object-wide user data that will be\n     * passed as the first argument to handler functions.\n     */\n    void *obj_ctx;\n\n#if defined(ANJAY_WITH_DOWNLOADER)\n    /**\n     * Informs the module to try reusing sockets of existing LwM2M Servers to\n     * download the software package if the download URI matches any of the\n     * LwM2M Servers.\n     */\n    bool prefer_same_socket_downloads;\n#endif // defined(ANJAY_WITH_DOWNLOADER)\n} anjay_sw_mgmt_settings_t;\n\n/**\n * Installs the Software Management object in an Anjay object.\n *\n * The Software Management module does not require explicit cleanup; all\n * resources will be automatically freed up during the call to @ref\n * anjay_delete.\n *\n * Specific instances of Software Management object shall be created using\n * @ref anjay_sw_mgmt_add_instance . It is desirable to create all instances\n * expected by the server before the first call to @ref anjay_event_loop_run ,\n * @ref anjay_serve or @ref anjay_sched_run , to make sure that they are present\n * from the beginning of the device registration.\n *\n * @param anjay    Anjay object for which the Software Management Object is\n *                 installed.\n *\n * @param settings Configuration of Software Management module, see @ref\n *                 anjay_sw_mgmt_settings_t for details.\n *\n * @returns 0 on success, or a negative value in case of error.\n */\nint anjay_sw_mgmt_install(anjay_t *anjay,\n                          const anjay_sw_mgmt_settings_t *settings);\n\n/**\n * Checks if the instance state is <em>Installed</em> and return the activation\n * state via \\p out_state argument. Can be used in @ref anjay_sw_mgmt_activate_t\n * or @ref anjay_sw_mgmt_deactivate_t to check if we want to proceed with\n * current activation state with the code responsible for\n * activation/deactivation the package.\n *\n * @param anjay          Anjay object for which the Software Management Object\n *                       is installed.\n *\n * @param iid            ID of Software Management object instance.\n *\n * @param out_state      Activation state of Software Management object\n *                       instance.\n *\n * @returns 0 on success, -1 if there is no such instance or instance state is\n *          different than <em>Installed</em>\n */\nint anjay_sw_mgmt_get_activation_state(anjay_t *anjay,\n                                       anjay_iid_t iid,\n                                       bool *out_state);\n\n/**\n * Possible values that control package state after installation.\n */\ntypedef enum {\n    /**\n     * Corresponds to the \"Installed\" Update State, \"Installed\" Update Result\n     * and Activation State set to false.\n     */\n    ANJAY_SW_MGMT_FINISH_PKG_INSTALL_SUCCESS_INACTIVE,\n    /**\n     * Corresponds to the \"Installed\" Update State, \"Installed\" Update Result\n     * and Activation State set to true.\n     *\n     * WARNING: Setting the Activation State to true via @ref\n     * anjay_sw_mgmt_finish_pkg_install breaks the specifications. Activation\n     * should be done on the server side. However, there are known cases in\n     * which such behavior is required.\n     */\n    ANJAY_SW_MGMT_FINISH_PKG_INSTALL_SUCCESS_ACTIVE,\n    /**\n     * Corresponds to the \"Delivered\" Update State, \"Installation failure\"\n     * Update Result and Activation State set to false.\n     */\n    ANJAY_SW_MGMT_FINISH_PKG_INSTALL_FAILURE\n} anjay_sw_mgmt_finish_pkg_install_result_t;\n\n/**\n * Marks delivered software package as installed and optionally activated,\n * making transition to <em>Installed</em> state or reports installation error.\n *\n * WARNING: Calling this function is only valid in <em>Delivered</em> state,\n * directly in the @ref anjay_sw_mgmt_pkg_install_t handler, or in some later\n * point of time, possibly after a reboot, as explained in\n * @ref anjay_sw_mgmt_pkg_install_t .\n *\n * NOTE: Setting activation state with this function does <strong>NOT</strong>\n * mean that activation ( @ref anjay_sw_mgmt_activate_t ) or deactivation ( @ref\n * anjay_sw_mgmt_deactivate_t ) software package handler will be called. Setting\n * activation state to true after installation breaks the specifications, but\n * there are known cases when this behavior is required.\n *\n * NOTE: If this function is called inside @ref anjay_sw_mgmt_pkg_install_t\n * handler with \\p pkg_install_result set to @ref\n * ANJAY_SW_MGMT_FINISH_PKG_INSTALL_SUCCESS_INACTIVE or @ref\n * ANJAY_SW_MGMT_FINISH_PKG_INSTALL_SUCCESS_ACTIVE , the handler is expected to\n * return 0. Otherwise, returning nonzero value will cause the result set by\n * this function being overwritten.\n *\n * @param anjay                 Anjay object for which the Software Management\n *                              Object is installed.\n *\n * @param iid                   ID of Software Management object instance.\n *\n * @param pkg_install_result    Result of the installation process.\n *\n * @returns 0 on success, or a negative value in case of error.\n */\nint anjay_sw_mgmt_finish_pkg_install(\n        anjay_t *anjay,\n        anjay_iid_t iid,\n        anjay_sw_mgmt_finish_pkg_install_result_t pkg_install_result);\n\n/**\n * Adds an instance of Software Management object.\n *\n * This method will not cause @ref anjay_sw_mgmt_add_handler_t to be called,\n * as this method creates a new instance of the object on application's request.\n *\n * @param anjay                Anjay object for which the Software Management\n *                             Object is installed.\n *\n * @param instance_initializer Information about the state to initialize the\n *                             Software Management object instance in.\n *\n * @returns 0 on success, or a negative value in case of error.\n */\nint anjay_sw_mgmt_add_instance(\n        anjay_t *anjay,\n        const anjay_sw_mgmt_instance_initializer_t *instance_initializer);\n\n/**\n * Remove an instance of Software Management object.\n *\n * This method will not cause @ref anjay_sw_mgmt_remove_handler_t to be called,\n * as this method deletes a instance of the object on application's request.\n *\n * <strong>CAUTION:</strong> Calling this function inside any Software\n * Management module handler with the same \\p iid as passed to the\n * handler, will result in an error code with value 1. This function shouldn't\n * be called from any module handler.\n * In multi-threaded scenarios, it should be expected that this function can\n * also return an error code with value 1, in case one thread calls this\n * function when another thread is executing one of the module's handler\n * associated with the instance with the same \\p iid as the one passed to this\n * function. In this case, the user should wait a while and call this function\n * again.\n *\n * @param anjay Anjay object for which the Software Management Object is\n *              installed.\n *\n * @param iid   ID of Software Management object instance.\n *\n * @returns 0 on success;\n *          1 if a handler associated with an instance with the same \\p iid as\n *          the one passed to this function is currently being executed;\n *          a negative value in case of error\n */\nint anjay_sw_mgmt_remove_instance(anjay_t *anjay, anjay_iid_t iid);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif /* ANJAY_INCLUDE_ANJAY_SW_MGMT_H */\n"
  },
  {
    "path": "ltoconfig",
    "content": "#!/usr/bin/env bash\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\n./devconfig \\\n        --c-flags '-Os -DNDEBUG -flto' \\\n        --without-dtls \\\n        -D CMAKE_EXE_LINKER_FLAGS='-flto' \\\n        -D CMAKE_SHARED_LINKED_FLAGS='-flto' \\\n        -D CMAKE_AR=\"$(which gcc-ar)\" \\\n        -D CMAKE_RANLIB=\"$(which gcc-ranlib)\" \\\n        -D WITH_LIBRARY_SHARED=OFF \\\n        -D AVS_LOG_WITH_TRACE=OFF \\\n        \"$@\"\n"
  },
  {
    "path": "requirements.txt",
    "content": "# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\naiocoap>=0.4\ncbor2>=5.4.2\ncryptography>=40.0.2\nGitPython>=3.1.18\nlinuxdoc==20230827\nopenpyxl>=3.1.2\npackaging>=21.3\npowercmd>=0.3.6\npyparsing>=3.1.1\npyyaml\ndpkt\nSphinx==7.3.7\nsphinx-copybutton==0.5.2\nsphinx-design\nsphinx-rtd-theme==1.3.0\nsphinx-tabs==3.4.7\nbreathe==4.36.0\nexhale==0.3.7\nsphinx-sitemap==2.9.0\n"
  },
  {
    "path": "src/anjay_config_log.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ANJAY_CONFIG_LOG_H\n#define ANJAY_CONFIG_LOG_H\n\n#include <anjay_modules/anjay_utils_core.h>\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\n// clang-format off\nstatic inline void _anjay_log_feature_list(void) {\n    _anjay_log(anjay, TRACE, \"ANJAY_DEFAULT_SEND_FORMAT = \" AVS_QUOTE_MACRO(ANJAY_DEFAULT_SEND_FORMAT));\n    _anjay_log(anjay, TRACE, \"ANJAY_DTLS_SESSION_BUFFER_SIZE = \" AVS_QUOTE_MACRO(ANJAY_DTLS_SESSION_BUFFER_SIZE));\n    _anjay_log(anjay, TRACE, \"ANJAY_MAX_DOUBLE_STRING_SIZE = \" AVS_QUOTE_MACRO(ANJAY_MAX_DOUBLE_STRING_SIZE));\n    _anjay_log(anjay, TRACE, \"ANJAY_MAX_HOLDOFF_TIME = \" AVS_QUOTE_MACRO(ANJAY_MAX_HOLDOFF_TIME));\n    _anjay_log(anjay, TRACE, \"ANJAY_MAX_OBSERVATION_SERVERS_REPORTED_NUMBER = \" AVS_QUOTE_MACRO(ANJAY_MAX_OBSERVATION_SERVERS_REPORTED_NUMBER));\n    _anjay_log(anjay, TRACE, \"ANJAY_MAX_PK_OR_IDENTITY_SIZE = \" AVS_QUOTE_MACRO(ANJAY_MAX_PK_OR_IDENTITY_SIZE));\n    _anjay_log(anjay, TRACE, \"ANJAY_MAX_SECRET_KEY_SIZE = \" AVS_QUOTE_MACRO(ANJAY_MAX_SECRET_KEY_SIZE));\n    _anjay_log(anjay, TRACE, \"ANJAY_MAX_URI_QUERY_SEGMENT_SIZE = \" AVS_QUOTE_MACRO(ANJAY_MAX_URI_QUERY_SEGMENT_SIZE));\n    _anjay_log(anjay, TRACE, \"ANJAY_MAX_URI_SEGMENT_SIZE = \" AVS_QUOTE_MACRO(ANJAY_MAX_URI_SEGMENT_SIZE));\n#ifdef ANJAY_MODULE_SIM_BOOTSTRAP_DATA_OID_OVERRIDE_HEX\n    _anjay_log(anjay, TRACE, \"ANJAY_MODULE_SIM_BOOTSTRAP_DATA_OID_OVERRIDE_HEX = \" AVS_QUOTE_MACRO(ANJAY_MODULE_SIM_BOOTSTRAP_DATA_OID_OVERRIDE_HEX));\n#else // ANJAY_MODULE_SIM_BOOTSTRAP_DATA_OID_OVERRIDE_HEX\n    _anjay_log(anjay, TRACE, \"ANJAY_MODULE_SIM_BOOTSTRAP_DATA_OID_OVERRIDE_HEX = OFF\");\n#endif // ANJAY_MODULE_SIM_BOOTSTRAP_DATA_OID_OVERRIDE_HEX\n#ifdef ANJAY_MODULE_SIM_BOOTSTRAP_HARDCODED_FILE_ID\n    _anjay_log(anjay, TRACE, \"ANJAY_MODULE_SIM_BOOTSTRAP_HARDCODED_FILE_ID = \" AVS_QUOTE_MACRO(ANJAY_MODULE_SIM_BOOTSTRAP_HARDCODED_FILE_ID));\n#else // ANJAY_MODULE_SIM_BOOTSTRAP_HARDCODED_FILE_ID\n    _anjay_log(anjay, TRACE, \"ANJAY_MODULE_SIM_BOOTSTRAP_HARDCODED_FILE_ID = OFF\");\n#endif // ANJAY_MODULE_SIM_BOOTSTRAP_HARDCODED_FILE_ID\n#ifdef ANJAY_WITHOUT_COMPOSITE_OPERATIONS\n    _anjay_log(anjay, TRACE, \"ANJAY_WITHOUT_COMPOSITE_OPERATIONS = ON\");\n#else // ANJAY_WITHOUT_COMPOSITE_OPERATIONS\n    _anjay_log(anjay, TRACE, \"ANJAY_WITHOUT_COMPOSITE_OPERATIONS = OFF\");\n#endif // ANJAY_WITHOUT_COMPOSITE_OPERATIONS\n#ifdef ANJAY_WITHOUT_DEREGISTER\n    _anjay_log(anjay, TRACE, \"ANJAY_WITHOUT_DEREGISTER = ON\");\n#else // ANJAY_WITHOUT_DEREGISTER\n    _anjay_log(anjay, TRACE, \"ANJAY_WITHOUT_DEREGISTER = OFF\");\n#endif // ANJAY_WITHOUT_DEREGISTER\n#ifdef ANJAY_WITHOUT_IP_STICKINESS\n    _anjay_log(anjay, TRACE, \"ANJAY_WITHOUT_IP_STICKINESS = ON\");\n#else // ANJAY_WITHOUT_IP_STICKINESS\n    _anjay_log(anjay, TRACE, \"ANJAY_WITHOUT_IP_STICKINESS = OFF\");\n#endif // ANJAY_WITHOUT_IP_STICKINESS\n#ifdef ANJAY_WITHOUT_MODULE_FW_UPDATE_PUSH_MODE\n    _anjay_log(anjay, TRACE, \"ANJAY_WITHOUT_MODULE_FW_UPDATE_PUSH_MODE = ON\");\n#else // ANJAY_WITHOUT_MODULE_FW_UPDATE_PUSH_MODE\n    _anjay_log(anjay, TRACE, \"ANJAY_WITHOUT_MODULE_FW_UPDATE_PUSH_MODE = OFF\");\n#endif // ANJAY_WITHOUT_MODULE_FW_UPDATE_PUSH_MODE\n#ifdef ANJAY_WITHOUT_PLAINTEXT\n    _anjay_log(anjay, TRACE, \"ANJAY_WITHOUT_PLAINTEXT = ON\");\n#else // ANJAY_WITHOUT_PLAINTEXT\n    _anjay_log(anjay, TRACE, \"ANJAY_WITHOUT_PLAINTEXT = OFF\");\n#endif // ANJAY_WITHOUT_PLAINTEXT\n#ifdef ANJAY_WITHOUT_QUEUE_MODE_AUTOCLOSE\n    _anjay_log(anjay, TRACE, \"ANJAY_WITHOUT_QUEUE_MODE_AUTOCLOSE = ON\");\n#else // ANJAY_WITHOUT_QUEUE_MODE_AUTOCLOSE\n    _anjay_log(anjay, TRACE, \"ANJAY_WITHOUT_QUEUE_MODE_AUTOCLOSE = OFF\");\n#endif // ANJAY_WITHOUT_QUEUE_MODE_AUTOCLOSE\n#ifdef ANJAY_WITHOUT_TLV\n    _anjay_log(anjay, TRACE, \"ANJAY_WITHOUT_TLV = ON\");\n#else // ANJAY_WITHOUT_TLV\n    _anjay_log(anjay, TRACE, \"ANJAY_WITHOUT_TLV = OFF\");\n#endif // ANJAY_WITHOUT_TLV\n#ifdef ANJAY_WITH_ACCESS_CONTROL\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_ACCESS_CONTROL = ON\");\n#else // ANJAY_WITH_ACCESS_CONTROL\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_ACCESS_CONTROL = OFF\");\n#endif // ANJAY_WITH_ACCESS_CONTROL\n#ifdef ANJAY_WITH_ATTR_STORAGE\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_ATTR_STORAGE = ON\");\n#else // ANJAY_WITH_ATTR_STORAGE\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_ATTR_STORAGE = OFF\");\n#endif // ANJAY_WITH_ATTR_STORAGE\n#ifdef ANJAY_WITH_BOOTSTRAP\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_BOOTSTRAP = ON\");\n#else // ANJAY_WITH_BOOTSTRAP\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_BOOTSTRAP = OFF\");\n#endif // ANJAY_WITH_BOOTSTRAP\n#ifdef ANJAY_WITH_BOOTSTRAP_PACK\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_BOOTSTRAP_PACK = ON\");\n#else // ANJAY_WITH_BOOTSTRAP_PACK\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_BOOTSTRAP_PACK = OFF\");\n#endif // ANJAY_WITH_BOOTSTRAP_PACK\n#ifdef ANJAY_WITH_CBOR\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_CBOR = ON\");\n#else // ANJAY_WITH_CBOR\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_CBOR = OFF\");\n#endif // ANJAY_WITH_CBOR\n#ifdef ANJAY_WITH_COAP_DOWNLOAD\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_COAP_DOWNLOAD = ON\");\n#else // ANJAY_WITH_COAP_DOWNLOAD\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_COAP_DOWNLOAD = OFF\");\n#endif // ANJAY_WITH_COAP_DOWNLOAD\n#ifdef ANJAY_WITH_COAP_OSCORE\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_COAP_OSCORE = ON\");\n#else // ANJAY_WITH_COAP_OSCORE\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_COAP_OSCORE = OFF\");\n#endif // ANJAY_WITH_COAP_OSCORE\n#ifdef ANJAY_WITH_COMMUNICATION_TIMESTAMP_API\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_COMMUNICATION_TIMESTAMP_API = ON\");\n#else // ANJAY_WITH_COMMUNICATION_TIMESTAMP_API\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_COMMUNICATION_TIMESTAMP_API = OFF\");\n#endif // ANJAY_WITH_COMMUNICATION_TIMESTAMP_API\n#ifdef ANJAY_WITH_CONN_STATUS_API\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_CONN_STATUS_API = ON\");\n#else // ANJAY_WITH_CONN_STATUS_API\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_CONN_STATUS_API = OFF\");\n#endif // ANJAY_WITH_CONN_STATUS_API\n#ifdef ANJAY_WITH_CON_ATTR\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_CON_ATTR = ON\");\n#else // ANJAY_WITH_CON_ATTR\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_CON_ATTR = OFF\");\n#endif // ANJAY_WITH_CON_ATTR\n#ifdef ANJAY_WITH_CORE_PERSISTENCE\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_CORE_PERSISTENCE = ON\");\n#else // ANJAY_WITH_CORE_PERSISTENCE\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_CORE_PERSISTENCE = OFF\");\n#endif // ANJAY_WITH_CORE_PERSISTENCE\n#ifdef ANJAY_WITH_DISCOVER\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_DISCOVER = ON\");\n#else // ANJAY_WITH_DISCOVER\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_DISCOVER = OFF\");\n#endif // ANJAY_WITH_DISCOVER\n#ifdef ANJAY_WITH_DOWNLOADER\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_DOWNLOADER = ON\");\n#else // ANJAY_WITH_DOWNLOADER\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_DOWNLOADER = OFF\");\n#endif // ANJAY_WITH_DOWNLOADER\n#ifdef ANJAY_WITH_EST\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_EST = ON\");\n#else // ANJAY_WITH_EST\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_EST = OFF\");\n#endif // ANJAY_WITH_EST\n#ifdef ANJAY_WITH_EST_ENGINE_SUPPORT\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_EST_ENGINE_SUPPORT = ON\");\n#else // ANJAY_WITH_EST_ENGINE_SUPPORT\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_EST_ENGINE_SUPPORT = OFF\");\n#endif // ANJAY_WITH_EST_ENGINE_SUPPORT\n#ifdef ANJAY_WITH_EVENT_LOOP\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_EVENT_LOOP = ON\");\n#else // ANJAY_WITH_EVENT_LOOP\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_EVENT_LOOP = OFF\");\n#endif // ANJAY_WITH_EVENT_LOOP\n#ifdef ANJAY_WITH_HTTP_DOWNLOAD\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_HTTP_DOWNLOAD = ON\");\n#else // ANJAY_WITH_HTTP_DOWNLOAD\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_HTTP_DOWNLOAD = OFF\");\n#endif // ANJAY_WITH_HTTP_DOWNLOAD\n#ifdef ANJAY_WITH_LEGACY_CONTENT_FORMAT_SUPPORT\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_LEGACY_CONTENT_FORMAT_SUPPORT = ON\");\n#else // ANJAY_WITH_LEGACY_CONTENT_FORMAT_SUPPORT\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_LEGACY_CONTENT_FORMAT_SUPPORT = OFF\");\n#endif // ANJAY_WITH_LEGACY_CONTENT_FORMAT_SUPPORT\n#ifdef ANJAY_WITH_LOGS\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_LOGS = ON\");\n#else // ANJAY_WITH_LOGS\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_LOGS = OFF\");\n#endif // ANJAY_WITH_LOGS\n#ifdef ANJAY_WITH_LWM2M11\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_LWM2M11 = ON\");\n#else // ANJAY_WITH_LWM2M11\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_LWM2M11 = OFF\");\n#endif // ANJAY_WITH_LWM2M11\n#ifdef ANJAY_WITH_LWM2M12\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_LWM2M12 = ON\");\n#else // ANJAY_WITH_LWM2M12\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_LWM2M12 = OFF\");\n#endif // ANJAY_WITH_LWM2M12\n#ifdef ANJAY_WITH_LWM2M_GATEWAY\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_LWM2M_GATEWAY = ON\");\n#else // ANJAY_WITH_LWM2M_GATEWAY\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_LWM2M_GATEWAY = OFF\");\n#endif // ANJAY_WITH_LWM2M_GATEWAY\n#ifdef ANJAY_WITH_LWM2M_JSON\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_LWM2M_JSON = ON\");\n#else // ANJAY_WITH_LWM2M_JSON\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_LWM2M_JSON = OFF\");\n#endif // ANJAY_WITH_LWM2M_JSON\n#ifdef ANJAY_WITH_MODULE_ACCESS_CONTROL\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_MODULE_ACCESS_CONTROL = ON\");\n#else // ANJAY_WITH_MODULE_ACCESS_CONTROL\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_MODULE_ACCESS_CONTROL = OFF\");\n#endif // ANJAY_WITH_MODULE_ACCESS_CONTROL\n#ifdef ANJAY_WITH_MODULE_ADVANCED_FW_UPDATE\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_MODULE_ADVANCED_FW_UPDATE = ON\");\n#else // ANJAY_WITH_MODULE_ADVANCED_FW_UPDATE\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_MODULE_ADVANCED_FW_UPDATE = OFF\");\n#endif // ANJAY_WITH_MODULE_ADVANCED_FW_UPDATE\n#ifdef ANJAY_WITH_MODULE_AT_SMS\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_MODULE_AT_SMS = ON\");\n#else // ANJAY_WITH_MODULE_AT_SMS\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_MODULE_AT_SMS = OFF\");\n#endif // ANJAY_WITH_MODULE_AT_SMS\n#ifdef ANJAY_WITH_MODULE_BG96_NIDD\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_MODULE_BG96_NIDD = ON\");\n#else // ANJAY_WITH_MODULE_BG96_NIDD\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_MODULE_BG96_NIDD = OFF\");\n#endif // ANJAY_WITH_MODULE_BG96_NIDD\n#ifdef ANJAY_WITH_MODULE_BOOTSTRAPPER\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_MODULE_BOOTSTRAPPER = ON\");\n#else // ANJAY_WITH_MODULE_BOOTSTRAPPER\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_MODULE_BOOTSTRAPPER = OFF\");\n#endif // ANJAY_WITH_MODULE_BOOTSTRAPPER\n#ifdef ANJAY_WITH_MODULE_FACTORY_PROVISIONING\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_MODULE_FACTORY_PROVISIONING = ON\");\n#else // ANJAY_WITH_MODULE_FACTORY_PROVISIONING\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_MODULE_FACTORY_PROVISIONING = OFF\");\n#endif // ANJAY_WITH_MODULE_FACTORY_PROVISIONING\n#ifdef ANJAY_WITH_MODULE_FW_UPDATE\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_MODULE_FW_UPDATE = ON\");\n#else // ANJAY_WITH_MODULE_FW_UPDATE\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_MODULE_FW_UPDATE = OFF\");\n#endif // ANJAY_WITH_MODULE_FW_UPDATE\n#ifdef ANJAY_WITH_MODULE_FW_UPDATE_V11_RESOURCES\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_MODULE_FW_UPDATE_V11_RESOURCES = ON\");\n#else // ANJAY_WITH_MODULE_FW_UPDATE_V11_RESOURCES\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_MODULE_FW_UPDATE_V11_RESOURCES = OFF\");\n#endif // ANJAY_WITH_MODULE_FW_UPDATE_V11_RESOURCES\n#ifdef ANJAY_WITH_MODULE_IPSO_OBJECTS\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_MODULE_IPSO_OBJECTS = ON\");\n#else // ANJAY_WITH_MODULE_IPSO_OBJECTS\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_MODULE_IPSO_OBJECTS = OFF\");\n#endif // ANJAY_WITH_MODULE_IPSO_OBJECTS\n#ifdef ANJAY_WITH_MODULE_IPSO_OBJECTS_V2\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_MODULE_IPSO_OBJECTS_V2 = ON\");\n#else // ANJAY_WITH_MODULE_IPSO_OBJECTS_V2\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_MODULE_IPSO_OBJECTS_V2 = OFF\");\n#endif // ANJAY_WITH_MODULE_IPSO_OBJECTS_V2\n#ifdef ANJAY_WITH_MODULE_OSCORE\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_MODULE_OSCORE = ON\");\n#else // ANJAY_WITH_MODULE_OSCORE\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_MODULE_OSCORE = OFF\");\n#endif // ANJAY_WITH_MODULE_OSCORE\n#ifdef ANJAY_WITH_MODULE_SECURITY\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_MODULE_SECURITY = ON\");\n#else // ANJAY_WITH_MODULE_SECURITY\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_MODULE_SECURITY = OFF\");\n#endif // ANJAY_WITH_MODULE_SECURITY\n#ifdef ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT = ON\");\n#else // ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT = OFF\");\n#endif // ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT\n#ifdef ANJAY_WITH_MODULE_SERVER\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_MODULE_SERVER = ON\");\n#else // ANJAY_WITH_MODULE_SERVER\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_MODULE_SERVER = OFF\");\n#endif // ANJAY_WITH_MODULE_SERVER\n#ifdef ANJAY_WITH_MODULE_SIM_BOOTSTRAP\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_MODULE_SIM_BOOTSTRAP = ON\");\n#else // ANJAY_WITH_MODULE_SIM_BOOTSTRAP\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_MODULE_SIM_BOOTSTRAP = OFF\");\n#endif // ANJAY_WITH_MODULE_SIM_BOOTSTRAP\n#ifdef ANJAY_WITH_MODULE_SW_MGMT\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_MODULE_SW_MGMT = ON\");\n#else // ANJAY_WITH_MODULE_SW_MGMT\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_MODULE_SW_MGMT = OFF\");\n#endif // ANJAY_WITH_MODULE_SW_MGMT\n#ifdef ANJAY_WITH_NET_STATS\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_NET_STATS = ON\");\n#else // ANJAY_WITH_NET_STATS\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_NET_STATS = OFF\");\n#endif // ANJAY_WITH_NET_STATS\n#ifdef ANJAY_WITH_NIDD\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_NIDD = ON\");\n#else // ANJAY_WITH_NIDD\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_NIDD = OFF\");\n#endif // ANJAY_WITH_NIDD\n#ifdef ANJAY_WITH_OBSERVATION_ATTRIBUTES\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_OBSERVATION_ATTRIBUTES = ON\");\n#else // ANJAY_WITH_OBSERVATION_ATTRIBUTES\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_OBSERVATION_ATTRIBUTES = OFF\");\n#endif // ANJAY_WITH_OBSERVATION_ATTRIBUTES\n#ifdef ANJAY_WITH_OBSERVATION_STATUS\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_OBSERVATION_STATUS = ON\");\n#else // ANJAY_WITH_OBSERVATION_STATUS\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_OBSERVATION_STATUS = OFF\");\n#endif // ANJAY_WITH_OBSERVATION_STATUS\n#ifdef ANJAY_WITH_OBSERVE\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_OBSERVE = ON\");\n#else // ANJAY_WITH_OBSERVE\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_OBSERVE = OFF\");\n#endif // ANJAY_WITH_OBSERVE\n#ifdef ANJAY_WITH_SECURITY_STRUCTURED\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_SECURITY_STRUCTURED = ON\");\n#else // ANJAY_WITH_SECURITY_STRUCTURED\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_SECURITY_STRUCTURED = OFF\");\n#endif // ANJAY_WITH_SECURITY_STRUCTURED\n#ifdef ANJAY_WITH_SEND\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_SEND = ON\");\n#else // ANJAY_WITH_SEND\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_SEND = OFF\");\n#endif // ANJAY_WITH_SEND\n#ifdef ANJAY_WITH_SENML_JSON\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_SENML_JSON = ON\");\n#else // ANJAY_WITH_SENML_JSON\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_SENML_JSON = OFF\");\n#endif // ANJAY_WITH_SENML_JSON\n#ifdef ANJAY_WITH_SMS\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_SMS = ON\");\n#else // ANJAY_WITH_SMS\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_SMS = OFF\");\n#endif // ANJAY_WITH_SMS\n#ifdef ANJAY_WITH_SMS_MULTIPART\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_SMS_MULTIPART = ON\");\n#else // ANJAY_WITH_SMS_MULTIPART\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_SMS_MULTIPART = OFF\");\n#endif // ANJAY_WITH_SMS_MULTIPART\n#ifdef ANJAY_WITH_SSL_ERROR_API\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_SSL_ERROR_API = ON\");\n#else // ANJAY_WITH_SSL_ERROR_API\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_SSL_ERROR_API = OFF\");\n#endif // ANJAY_WITH_SSL_ERROR_API\n#ifdef ANJAY_WITH_THREAD_SAFETY\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_THREAD_SAFETY = ON\");\n#else // ANJAY_WITH_THREAD_SAFETY\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_THREAD_SAFETY = OFF\");\n#endif // ANJAY_WITH_THREAD_SAFETY\n#ifdef ANJAY_WITH_TRACE_LOGS\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_TRACE_LOGS = ON\");\n#else // ANJAY_WITH_TRACE_LOGS\n    _anjay_log(anjay, TRACE, \"ANJAY_WITH_TRACE_LOGS = OFF\");\n#endif // ANJAY_WITH_TRACE_LOGS\n#ifdef AVS_COAP_OSCORE_MASTER_SALT_SIZE\n    _anjay_log(anjay, TRACE, \"AVS_COAP_OSCORE_MASTER_SALT_SIZE = \" AVS_QUOTE_MACRO(AVS_COAP_OSCORE_MASTER_SALT_SIZE));\n#else // AVS_COAP_OSCORE_MASTER_SALT_SIZE\n    _anjay_log(anjay, TRACE, \"AVS_COAP_OSCORE_MASTER_SALT_SIZE = OFF\");\n#endif // AVS_COAP_OSCORE_MASTER_SALT_SIZE\n#ifdef AVS_COAP_OSCORE_MASTER_SECRET_SIZE\n    _anjay_log(anjay, TRACE, \"AVS_COAP_OSCORE_MASTER_SECRET_SIZE = \" AVS_QUOTE_MACRO(AVS_COAP_OSCORE_MASTER_SECRET_SIZE));\n#else // AVS_COAP_OSCORE_MASTER_SECRET_SIZE\n    _anjay_log(anjay, TRACE, \"AVS_COAP_OSCORE_MASTER_SECRET_SIZE = OFF\");\n#endif // AVS_COAP_OSCORE_MASTER_SECRET_SIZE\n    _anjay_log(anjay, TRACE, \"AVS_COAP_UDP_NOTIFY_CACHE_SIZE = \" AVS_QUOTE_MACRO(AVS_COAP_UDP_NOTIFY_CACHE_SIZE));\n#ifdef AVS_COMMONS_BIG_ENDIAN\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_BIG_ENDIAN = ON\");\n#else // AVS_COMMONS_BIG_ENDIAN\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_BIG_ENDIAN = OFF\");\n#endif // AVS_COMMONS_BIG_ENDIAN\n#ifdef AVS_COMMONS_COMPAT_THREADING_PTHREAD_HAVE_PTHREAD_CONDATTR_SETCLOCK\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_COMPAT_THREADING_PTHREAD_HAVE_PTHREAD_CONDATTR_SETCLOCK = ON\");\n#else // AVS_COMMONS_COMPAT_THREADING_PTHREAD_HAVE_PTHREAD_CONDATTR_SETCLOCK\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_COMPAT_THREADING_PTHREAD_HAVE_PTHREAD_CONDATTR_SETCLOCK = OFF\");\n#endif // AVS_COMMONS_COMPAT_THREADING_PTHREAD_HAVE_PTHREAD_CONDATTR_SETCLOCK\n#ifdef AVS_COMMONS_COMPAT_THREADING_WITH_ATOMIC_SPINLOCK\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_COMPAT_THREADING_WITH_ATOMIC_SPINLOCK = ON\");\n#else // AVS_COMMONS_COMPAT_THREADING_WITH_ATOMIC_SPINLOCK\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_COMPAT_THREADING_WITH_ATOMIC_SPINLOCK = OFF\");\n#endif // AVS_COMMONS_COMPAT_THREADING_WITH_ATOMIC_SPINLOCK\n#ifdef AVS_COMMONS_COMPAT_THREADING_WITH_PTHREAD\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_COMPAT_THREADING_WITH_PTHREAD = ON\");\n#else // AVS_COMMONS_COMPAT_THREADING_WITH_PTHREAD\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_COMPAT_THREADING_WITH_PTHREAD = OFF\");\n#endif // AVS_COMMONS_COMPAT_THREADING_WITH_PTHREAD\n#ifdef AVS_COMMONS_HAVE_BUILTIN_ADD_OVERFLOW\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_HAVE_BUILTIN_ADD_OVERFLOW = ON\");\n#else // AVS_COMMONS_HAVE_BUILTIN_ADD_OVERFLOW\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_HAVE_BUILTIN_ADD_OVERFLOW = OFF\");\n#endif // AVS_COMMONS_HAVE_BUILTIN_ADD_OVERFLOW\n#ifdef AVS_COMMONS_HAVE_BUILTIN_MUL_OVERFLOW\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_HAVE_BUILTIN_MUL_OVERFLOW = ON\");\n#else // AVS_COMMONS_HAVE_BUILTIN_MUL_OVERFLOW\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_HAVE_BUILTIN_MUL_OVERFLOW = OFF\");\n#endif // AVS_COMMONS_HAVE_BUILTIN_MUL_OVERFLOW\n#ifdef AVS_COMMONS_HAVE_DLSYM\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_HAVE_DLSYM = ON\");\n#else // AVS_COMMONS_HAVE_DLSYM\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_HAVE_DLSYM = OFF\");\n#endif // AVS_COMMONS_HAVE_DLSYM\n#ifdef AVS_COMMONS_HAVE_NET_IF_H\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_HAVE_NET_IF_H = ON\");\n#else // AVS_COMMONS_HAVE_NET_IF_H\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_HAVE_NET_IF_H = OFF\");\n#endif // AVS_COMMONS_HAVE_NET_IF_H\n#ifdef AVS_COMMONS_HAVE_PRAGMA_DIAGNOSTIC\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_HAVE_PRAGMA_DIAGNOSTIC = ON\");\n#else // AVS_COMMONS_HAVE_PRAGMA_DIAGNOSTIC\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_HAVE_PRAGMA_DIAGNOSTIC = OFF\");\n#endif // AVS_COMMONS_HAVE_PRAGMA_DIAGNOSTIC\n#ifdef AVS_COMMONS_HAVE_VISIBILITY\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_HAVE_VISIBILITY = ON\");\n#else // AVS_COMMONS_HAVE_VISIBILITY\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_HAVE_VISIBILITY = OFF\");\n#endif // AVS_COMMONS_HAVE_VISIBILITY\n#ifdef AVS_COMMONS_HTTP_WITH_ZLIB\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_HTTP_WITH_ZLIB = ON\");\n#else // AVS_COMMONS_HTTP_WITH_ZLIB\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_HTTP_WITH_ZLIB = OFF\");\n#endif // AVS_COMMONS_HTTP_WITH_ZLIB\n#ifdef AVS_COMMONS_LOG_MAX_LINE_LENGTH\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_LOG_MAX_LINE_LENGTH = \" AVS_QUOTE_MACRO(AVS_COMMONS_LOG_MAX_LINE_LENGTH));\n#else // AVS_COMMONS_LOG_MAX_LINE_LENGTH\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_LOG_MAX_LINE_LENGTH = OFF\");\n#endif // AVS_COMMONS_LOG_MAX_LINE_LENGTH\n#ifdef AVS_COMMONS_LOG_USE_GLOBAL_BUFFER\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_LOG_USE_GLOBAL_BUFFER = ON\");\n#else // AVS_COMMONS_LOG_USE_GLOBAL_BUFFER\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_LOG_USE_GLOBAL_BUFFER = OFF\");\n#endif // AVS_COMMONS_LOG_USE_GLOBAL_BUFFER\n#ifdef AVS_COMMONS_LOG_WITH_DEFAULT_HANDLER\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_LOG_WITH_DEFAULT_HANDLER = ON\");\n#else // AVS_COMMONS_LOG_WITH_DEFAULT_HANDLER\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_LOG_WITH_DEFAULT_HANDLER = OFF\");\n#endif // AVS_COMMONS_LOG_WITH_DEFAULT_HANDLER\n#ifdef AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_GAI_STRERROR\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_GAI_STRERROR = ON\");\n#else // AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_GAI_STRERROR\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_GAI_STRERROR = OFF\");\n#endif // AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_GAI_STRERROR\n#ifdef AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_GETIFADDRS\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_GETIFADDRS = ON\");\n#else // AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_GETIFADDRS\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_GETIFADDRS = OFF\");\n#endif // AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_GETIFADDRS\n#ifdef AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_GETNAMEINFO\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_GETNAMEINFO = ON\");\n#else // AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_GETNAMEINFO\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_GETNAMEINFO = OFF\");\n#endif // AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_GETNAMEINFO\n#ifdef AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_IN6_IS_ADDR_V4MAPPED\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_IN6_IS_ADDR_V4MAPPED = ON\");\n#else // AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_IN6_IS_ADDR_V4MAPPED\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_IN6_IS_ADDR_V4MAPPED = OFF\");\n#endif // AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_IN6_IS_ADDR_V4MAPPED\n#ifdef AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_INET_NTOP\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_INET_NTOP = ON\");\n#else // AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_INET_NTOP\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_INET_NTOP = OFF\");\n#endif // AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_INET_NTOP\n#ifdef AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_POLL\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_POLL = ON\");\n#else // AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_POLL\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_POLL = OFF\");\n#endif // AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_POLL\n#ifdef AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_RECVMSG\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_RECVMSG = ON\");\n#else // AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_RECVMSG\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_RECVMSG = OFF\");\n#endif // AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_RECVMSG\n#ifdef AVS_COMMONS_NET_POSIX_AVS_SOCKET_WITHOUT_IN6_V4MAPPED_SUPPORT\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_NET_POSIX_AVS_SOCKET_WITHOUT_IN6_V4MAPPED_SUPPORT = ON\");\n#else // AVS_COMMONS_NET_POSIX_AVS_SOCKET_WITHOUT_IN6_V4MAPPED_SUPPORT\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_NET_POSIX_AVS_SOCKET_WITHOUT_IN6_V4MAPPED_SUPPORT = OFF\");\n#endif // AVS_COMMONS_NET_POSIX_AVS_SOCKET_WITHOUT_IN6_V4MAPPED_SUPPORT\n#ifdef AVS_COMMONS_NET_WITH_DTLS\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_NET_WITH_DTLS = ON\");\n#else // AVS_COMMONS_NET_WITH_DTLS\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_NET_WITH_DTLS = OFF\");\n#endif // AVS_COMMONS_NET_WITH_DTLS\n#ifdef AVS_COMMONS_NET_WITH_IPV4\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_NET_WITH_IPV4 = ON\");\n#else // AVS_COMMONS_NET_WITH_IPV4\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_NET_WITH_IPV4 = OFF\");\n#endif // AVS_COMMONS_NET_WITH_IPV4\n#ifdef AVS_COMMONS_NET_WITH_IPV6\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_NET_WITH_IPV6 = ON\");\n#else // AVS_COMMONS_NET_WITH_IPV6\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_NET_WITH_IPV6 = OFF\");\n#endif // AVS_COMMONS_NET_WITH_IPV6\n#ifdef AVS_COMMONS_NET_WITH_MBEDTLS_LOGS\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_NET_WITH_MBEDTLS_LOGS = ON\");\n#else // AVS_COMMONS_NET_WITH_MBEDTLS_LOGS\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_NET_WITH_MBEDTLS_LOGS = OFF\");\n#endif // AVS_COMMONS_NET_WITH_MBEDTLS_LOGS\n#ifdef AVS_COMMONS_NET_WITH_MBEDTLS_SSLKEYLOG\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_NET_WITH_MBEDTLS_SSLKEYLOG = ON\");\n#else // AVS_COMMONS_NET_WITH_MBEDTLS_SSLKEYLOG\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_NET_WITH_MBEDTLS_SSLKEYLOG = OFF\");\n#endif // AVS_COMMONS_NET_WITH_MBEDTLS_SSLKEYLOG\n#ifdef AVS_COMMONS_NET_WITH_POSIX_AVS_SOCKET\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_NET_WITH_POSIX_AVS_SOCKET = ON\");\n#else // AVS_COMMONS_NET_WITH_POSIX_AVS_SOCKET\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_NET_WITH_POSIX_AVS_SOCKET = OFF\");\n#endif // AVS_COMMONS_NET_WITH_POSIX_AVS_SOCKET\n#ifdef AVS_COMMONS_NET_WITH_SOCKET_LOG\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_NET_WITH_SOCKET_LOG = ON\");\n#else // AVS_COMMONS_NET_WITH_SOCKET_LOG\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_NET_WITH_SOCKET_LOG = OFF\");\n#endif // AVS_COMMONS_NET_WITH_SOCKET_LOG\n#ifdef AVS_COMMONS_NET_WITH_TLS_SESSION_PERSISTENCE\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_NET_WITH_TLS_SESSION_PERSISTENCE = ON\");\n#else // AVS_COMMONS_NET_WITH_TLS_SESSION_PERSISTENCE\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_NET_WITH_TLS_SESSION_PERSISTENCE = OFF\");\n#endif // AVS_COMMONS_NET_WITH_TLS_SESSION_PERSISTENCE\n#ifdef AVS_COMMONS_POSIX_COMPAT_HEADER\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_POSIX_COMPAT_HEADER = \" AVS_QUOTE_MACRO(AVS_COMMONS_POSIX_COMPAT_HEADER));\n#else // AVS_COMMONS_POSIX_COMPAT_HEADER\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_POSIX_COMPAT_HEADER = OFF\");\n#endif // AVS_COMMONS_POSIX_COMPAT_HEADER\n#ifdef AVS_COMMONS_SCHED_THREAD_SAFE\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_SCHED_THREAD_SAFE = ON\");\n#else // AVS_COMMONS_SCHED_THREAD_SAFE\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_SCHED_THREAD_SAFE = OFF\");\n#endif // AVS_COMMONS_SCHED_THREAD_SAFE\n#ifdef AVS_COMMONS_STREAM_WITH_FILE\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_STREAM_WITH_FILE = ON\");\n#else // AVS_COMMONS_STREAM_WITH_FILE\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_STREAM_WITH_FILE = OFF\");\n#endif // AVS_COMMONS_STREAM_WITH_FILE\n#ifdef AVS_COMMONS_UNIT_POSIX_HAVE_BACKTRACE\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_UNIT_POSIX_HAVE_BACKTRACE = ON\");\n#else // AVS_COMMONS_UNIT_POSIX_HAVE_BACKTRACE\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_UNIT_POSIX_HAVE_BACKTRACE = OFF\");\n#endif // AVS_COMMONS_UNIT_POSIX_HAVE_BACKTRACE\n#ifdef AVS_COMMONS_UTILS_WITH_ALIGNFIX_ALLOCATOR\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_UTILS_WITH_ALIGNFIX_ALLOCATOR = ON\");\n#else // AVS_COMMONS_UTILS_WITH_ALIGNFIX_ALLOCATOR\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_UTILS_WITH_ALIGNFIX_ALLOCATOR = OFF\");\n#endif // AVS_COMMONS_UTILS_WITH_ALIGNFIX_ALLOCATOR\n#ifdef AVS_COMMONS_UTILS_WITH_POSIX_AVS_TIME\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_UTILS_WITH_POSIX_AVS_TIME = ON\");\n#else // AVS_COMMONS_UTILS_WITH_POSIX_AVS_TIME\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_UTILS_WITH_POSIX_AVS_TIME = OFF\");\n#endif // AVS_COMMONS_UTILS_WITH_POSIX_AVS_TIME\n#ifdef AVS_COMMONS_UTILS_WITH_STANDARD_ALLOCATOR\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_UTILS_WITH_STANDARD_ALLOCATOR = ON\");\n#else // AVS_COMMONS_UTILS_WITH_STANDARD_ALLOCATOR\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_UTILS_WITH_STANDARD_ALLOCATOR = OFF\");\n#endif // AVS_COMMONS_UTILS_WITH_STANDARD_ALLOCATOR\n#ifdef AVS_COMMONS_WITHOUT_64BIT_FORMAT_SPECIFIERS\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_WITHOUT_64BIT_FORMAT_SPECIFIERS = ON\");\n#else // AVS_COMMONS_WITHOUT_64BIT_FORMAT_SPECIFIERS\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_WITHOUT_64BIT_FORMAT_SPECIFIERS = OFF\");\n#endif // AVS_COMMONS_WITHOUT_64BIT_FORMAT_SPECIFIERS\n#ifdef AVS_COMMONS_WITHOUT_FLOAT_FORMAT_SPECIFIERS\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_WITHOUT_FLOAT_FORMAT_SPECIFIERS = ON\");\n#else // AVS_COMMONS_WITHOUT_FLOAT_FORMAT_SPECIFIERS\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_WITHOUT_FLOAT_FORMAT_SPECIFIERS = OFF\");\n#endif // AVS_COMMONS_WITHOUT_FLOAT_FORMAT_SPECIFIERS\n#ifdef AVS_COMMONS_WITHOUT_LOG_CHECK_IN_RUNTIME\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_WITHOUT_LOG_CHECK_IN_RUNTIME = ON\");\n#else // AVS_COMMONS_WITHOUT_LOG_CHECK_IN_RUNTIME\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_WITHOUT_LOG_CHECK_IN_RUNTIME = OFF\");\n#endif // AVS_COMMONS_WITHOUT_LOG_CHECK_IN_RUNTIME\n#ifdef AVS_COMMONS_WITH_AVS_ALGORITHM\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_WITH_AVS_ALGORITHM = ON\");\n#else // AVS_COMMONS_WITH_AVS_ALGORITHM\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_WITH_AVS_ALGORITHM = OFF\");\n#endif // AVS_COMMONS_WITH_AVS_ALGORITHM\n#ifdef AVS_COMMONS_WITH_AVS_BUFFER\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_WITH_AVS_BUFFER = ON\");\n#else // AVS_COMMONS_WITH_AVS_BUFFER\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_WITH_AVS_BUFFER = OFF\");\n#endif // AVS_COMMONS_WITH_AVS_BUFFER\n#ifdef AVS_COMMONS_WITH_AVS_COMPAT_THREADING\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_WITH_AVS_COMPAT_THREADING = ON\");\n#else // AVS_COMMONS_WITH_AVS_COMPAT_THREADING\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_WITH_AVS_COMPAT_THREADING = OFF\");\n#endif // AVS_COMMONS_WITH_AVS_COMPAT_THREADING\n#ifdef AVS_COMMONS_WITH_AVS_CRYPTO\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_WITH_AVS_CRYPTO = ON\");\n#else // AVS_COMMONS_WITH_AVS_CRYPTO\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_WITH_AVS_CRYPTO = OFF\");\n#endif // AVS_COMMONS_WITH_AVS_CRYPTO\n#ifdef AVS_COMMONS_WITH_AVS_CRYPTO_ADVANCED_FEATURES\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_WITH_AVS_CRYPTO_ADVANCED_FEATURES = ON\");\n#else // AVS_COMMONS_WITH_AVS_CRYPTO_ADVANCED_FEATURES\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_WITH_AVS_CRYPTO_ADVANCED_FEATURES = OFF\");\n#endif // AVS_COMMONS_WITH_AVS_CRYPTO_ADVANCED_FEATURES\n#ifdef AVS_COMMONS_WITH_AVS_CRYPTO_PKI\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_WITH_AVS_CRYPTO_PKI = ON\");\n#else // AVS_COMMONS_WITH_AVS_CRYPTO_PKI\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_WITH_AVS_CRYPTO_PKI = OFF\");\n#endif // AVS_COMMONS_WITH_AVS_CRYPTO_PKI\n#ifdef AVS_COMMONS_WITH_AVS_CRYPTO_PKI_ENGINE\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_WITH_AVS_CRYPTO_PKI_ENGINE = ON\");\n#else // AVS_COMMONS_WITH_AVS_CRYPTO_PKI_ENGINE\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_WITH_AVS_CRYPTO_PKI_ENGINE = OFF\");\n#endif // AVS_COMMONS_WITH_AVS_CRYPTO_PKI_ENGINE\n#ifdef AVS_COMMONS_WITH_AVS_CRYPTO_PSK\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_WITH_AVS_CRYPTO_PSK = ON\");\n#else // AVS_COMMONS_WITH_AVS_CRYPTO_PSK\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_WITH_AVS_CRYPTO_PSK = OFF\");\n#endif // AVS_COMMONS_WITH_AVS_CRYPTO_PSK\n#ifdef AVS_COMMONS_WITH_AVS_CRYPTO_PSK_ENGINE\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_WITH_AVS_CRYPTO_PSK_ENGINE = ON\");\n#else // AVS_COMMONS_WITH_AVS_CRYPTO_PSK_ENGINE\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_WITH_AVS_CRYPTO_PSK_ENGINE = OFF\");\n#endif // AVS_COMMONS_WITH_AVS_CRYPTO_PSK_ENGINE\n#ifdef AVS_COMMONS_WITH_AVS_CRYPTO_VALGRIND\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_WITH_AVS_CRYPTO_VALGRIND = ON\");\n#else // AVS_COMMONS_WITH_AVS_CRYPTO_VALGRIND\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_WITH_AVS_CRYPTO_VALGRIND = OFF\");\n#endif // AVS_COMMONS_WITH_AVS_CRYPTO_VALGRIND\n#ifdef AVS_COMMONS_WITH_AVS_HTTP\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_WITH_AVS_HTTP = ON\");\n#else // AVS_COMMONS_WITH_AVS_HTTP\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_WITH_AVS_HTTP = OFF\");\n#endif // AVS_COMMONS_WITH_AVS_HTTP\n#ifdef AVS_COMMONS_WITH_AVS_LIST\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_WITH_AVS_LIST = ON\");\n#else // AVS_COMMONS_WITH_AVS_LIST\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_WITH_AVS_LIST = OFF\");\n#endif // AVS_COMMONS_WITH_AVS_LIST\n#ifdef AVS_COMMONS_WITH_AVS_LOG\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_WITH_AVS_LOG = ON\");\n#else // AVS_COMMONS_WITH_AVS_LOG\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_WITH_AVS_LOG = OFF\");\n#endif // AVS_COMMONS_WITH_AVS_LOG\n#ifdef AVS_COMMONS_WITH_AVS_NET\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_WITH_AVS_NET = ON\");\n#else // AVS_COMMONS_WITH_AVS_NET\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_WITH_AVS_NET = OFF\");\n#endif // AVS_COMMONS_WITH_AVS_NET\n#ifdef AVS_COMMONS_WITH_AVS_PERSISTENCE\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_WITH_AVS_PERSISTENCE = ON\");\n#else // AVS_COMMONS_WITH_AVS_PERSISTENCE\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_WITH_AVS_PERSISTENCE = OFF\");\n#endif // AVS_COMMONS_WITH_AVS_PERSISTENCE\n#ifdef AVS_COMMONS_WITH_AVS_RBTREE\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_WITH_AVS_RBTREE = ON\");\n#else // AVS_COMMONS_WITH_AVS_RBTREE\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_WITH_AVS_RBTREE = OFF\");\n#endif // AVS_COMMONS_WITH_AVS_RBTREE\n#ifdef AVS_COMMONS_WITH_AVS_SCHED\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_WITH_AVS_SCHED = ON\");\n#else // AVS_COMMONS_WITH_AVS_SCHED\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_WITH_AVS_SCHED = OFF\");\n#endif // AVS_COMMONS_WITH_AVS_SCHED\n#ifdef AVS_COMMONS_WITH_AVS_SORTED_SET\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_WITH_AVS_SORTED_SET = ON\");\n#else // AVS_COMMONS_WITH_AVS_SORTED_SET\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_WITH_AVS_SORTED_SET = OFF\");\n#endif // AVS_COMMONS_WITH_AVS_SORTED_SET\n#ifdef AVS_COMMONS_WITH_AVS_STREAM\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_WITH_AVS_STREAM = ON\");\n#else // AVS_COMMONS_WITH_AVS_STREAM\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_WITH_AVS_STREAM = OFF\");\n#endif // AVS_COMMONS_WITH_AVS_STREAM\n#ifdef AVS_COMMONS_WITH_AVS_UNIT\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_WITH_AVS_UNIT = ON\");\n#else // AVS_COMMONS_WITH_AVS_UNIT\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_WITH_AVS_UNIT = OFF\");\n#endif // AVS_COMMONS_WITH_AVS_UNIT\n#ifdef AVS_COMMONS_WITH_AVS_URL\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_WITH_AVS_URL = ON\");\n#else // AVS_COMMONS_WITH_AVS_URL\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_WITH_AVS_URL = OFF\");\n#endif // AVS_COMMONS_WITH_AVS_URL\n#ifdef AVS_COMMONS_WITH_AVS_UTILS\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_WITH_AVS_UTILS = ON\");\n#else // AVS_COMMONS_WITH_AVS_UTILS\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_WITH_AVS_UTILS = OFF\");\n#endif // AVS_COMMONS_WITH_AVS_UTILS\n#ifdef AVS_COMMONS_WITH_AVS_VECTOR\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_WITH_AVS_VECTOR = ON\");\n#else // AVS_COMMONS_WITH_AVS_VECTOR\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_WITH_AVS_VECTOR = OFF\");\n#endif // AVS_COMMONS_WITH_AVS_VECTOR\n#ifdef AVS_COMMONS_WITH_CUSTOM_TLS\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_WITH_CUSTOM_TLS = ON\");\n#else // AVS_COMMONS_WITH_CUSTOM_TLS\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_WITH_CUSTOM_TLS = OFF\");\n#endif // AVS_COMMONS_WITH_CUSTOM_TLS\n#ifdef AVS_COMMONS_WITH_EXTERNAL_LOGGER_HEADER\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_WITH_EXTERNAL_LOGGER_HEADER = \" AVS_QUOTE_MACRO(AVS_COMMONS_WITH_EXTERNAL_LOGGER_HEADER));\n#else // AVS_COMMONS_WITH_EXTERNAL_LOGGER_HEADER\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_WITH_EXTERNAL_LOGGER_HEADER = OFF\");\n#endif // AVS_COMMONS_WITH_EXTERNAL_LOGGER_HEADER\n#ifdef AVS_COMMONS_WITH_EXTERNAL_LOG_LEVELS_HEADER\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_WITH_EXTERNAL_LOG_LEVELS_HEADER = \" AVS_QUOTE_MACRO(AVS_COMMONS_WITH_EXTERNAL_LOG_LEVELS_HEADER));\n#else // AVS_COMMONS_WITH_EXTERNAL_LOG_LEVELS_HEADER\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_WITH_EXTERNAL_LOG_LEVELS_HEADER = OFF\");\n#endif // AVS_COMMONS_WITH_EXTERNAL_LOG_LEVELS_HEADER\n#ifdef AVS_COMMONS_WITH_INTERNAL_LOGS\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_WITH_INTERNAL_LOGS = ON\");\n#else // AVS_COMMONS_WITH_INTERNAL_LOGS\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_WITH_INTERNAL_LOGS = OFF\");\n#endif // AVS_COMMONS_WITH_INTERNAL_LOGS\n#ifdef AVS_COMMONS_WITH_INTERNAL_TRACE\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_WITH_INTERNAL_TRACE = ON\");\n#else // AVS_COMMONS_WITH_INTERNAL_TRACE\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_WITH_INTERNAL_TRACE = OFF\");\n#endif // AVS_COMMONS_WITH_INTERNAL_TRACE\n#ifdef AVS_COMMONS_WITH_MBEDTLS\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_WITH_MBEDTLS = ON\");\n#else // AVS_COMMONS_WITH_MBEDTLS\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_WITH_MBEDTLS = OFF\");\n#endif // AVS_COMMONS_WITH_MBEDTLS\n#ifdef AVS_COMMONS_WITH_MBEDTLS_PKCS11_ENGINE\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_WITH_MBEDTLS_PKCS11_ENGINE = ON\");\n#else // AVS_COMMONS_WITH_MBEDTLS_PKCS11_ENGINE\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_WITH_MBEDTLS_PKCS11_ENGINE = OFF\");\n#endif // AVS_COMMONS_WITH_MBEDTLS_PKCS11_ENGINE\n#ifdef AVS_COMMONS_WITH_MBEDTLS_PSA_ENGINE\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_WITH_MBEDTLS_PSA_ENGINE = ON\");\n#else // AVS_COMMONS_WITH_MBEDTLS_PSA_ENGINE\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_WITH_MBEDTLS_PSA_ENGINE = OFF\");\n#endif // AVS_COMMONS_WITH_MBEDTLS_PSA_ENGINE\n#ifdef AVS_COMMONS_WITH_MBEDTLS_PSA_ENGINE_PROTECTED_STORAGE\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_WITH_MBEDTLS_PSA_ENGINE_PROTECTED_STORAGE = ON\");\n#else // AVS_COMMONS_WITH_MBEDTLS_PSA_ENGINE_PROTECTED_STORAGE\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_WITH_MBEDTLS_PSA_ENGINE_PROTECTED_STORAGE = OFF\");\n#endif // AVS_COMMONS_WITH_MBEDTLS_PSA_ENGINE_PROTECTED_STORAGE\n#ifdef AVS_COMMONS_WITH_MBEDTLS_PSA_RNG\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_WITH_MBEDTLS_PSA_RNG = ON\");\n#else // AVS_COMMONS_WITH_MBEDTLS_PSA_RNG\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_WITH_MBEDTLS_PSA_RNG = OFF\");\n#endif // AVS_COMMONS_WITH_MBEDTLS_PSA_RNG\n#ifdef AVS_COMMONS_WITH_MICRO_LOGS\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_WITH_MICRO_LOGS = ON\");\n#else // AVS_COMMONS_WITH_MICRO_LOGS\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_WITH_MICRO_LOGS = OFF\");\n#endif // AVS_COMMONS_WITH_MICRO_LOGS\n#ifdef AVS_COMMONS_WITH_OPENSSL\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_WITH_OPENSSL = ON\");\n#else // AVS_COMMONS_WITH_OPENSSL\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_WITH_OPENSSL = OFF\");\n#endif // AVS_COMMONS_WITH_OPENSSL\n#ifdef AVS_COMMONS_WITH_OPENSSL_PKCS11_ENGINE\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_WITH_OPENSSL_PKCS11_ENGINE = ON\");\n#else // AVS_COMMONS_WITH_OPENSSL_PKCS11_ENGINE\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_WITH_OPENSSL_PKCS11_ENGINE = OFF\");\n#endif // AVS_COMMONS_WITH_OPENSSL_PKCS11_ENGINE\n#ifdef AVS_COMMONS_WITH_POISONING\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_WITH_POISONING = ON\");\n#else // AVS_COMMONS_WITH_POISONING\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_WITH_POISONING = OFF\");\n#endif // AVS_COMMONS_WITH_POISONING\n#ifdef AVS_COMMONS_WITH_TINYDTLS\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_WITH_TINYDTLS = ON\");\n#else // AVS_COMMONS_WITH_TINYDTLS\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_WITH_TINYDTLS = OFF\");\n#endif // AVS_COMMONS_WITH_TINYDTLS\n#ifdef AVS_COMMONS_WITH_TRAFFIC_INTERCEPTOR\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_WITH_TRAFFIC_INTERCEPTOR = ON\");\n#else // AVS_COMMONS_WITH_TRAFFIC_INTERCEPTOR\n    _anjay_log(anjay, TRACE, \"AVS_COMMONS_WITH_TRAFFIC_INTERCEPTOR = OFF\");\n#endif // AVS_COMMONS_WITH_TRAFFIC_INTERCEPTOR\n#ifdef WITH_AVS_COAP_BLOCK\n    _anjay_log(anjay, TRACE, \"WITH_AVS_COAP_BLOCK = ON\");\n#else // WITH_AVS_COAP_BLOCK\n    _anjay_log(anjay, TRACE, \"WITH_AVS_COAP_BLOCK = OFF\");\n#endif // WITH_AVS_COAP_BLOCK\n#ifdef WITH_AVS_COAP_DIAGNOSTIC_MESSAGES\n    _anjay_log(anjay, TRACE, \"WITH_AVS_COAP_DIAGNOSTIC_MESSAGES = ON\");\n#else // WITH_AVS_COAP_DIAGNOSTIC_MESSAGES\n    _anjay_log(anjay, TRACE, \"WITH_AVS_COAP_DIAGNOSTIC_MESSAGES = OFF\");\n#endif // WITH_AVS_COAP_DIAGNOSTIC_MESSAGES\n#ifdef WITH_AVS_COAP_LOGS\n    _anjay_log(anjay, TRACE, \"WITH_AVS_COAP_LOGS = ON\");\n#else // WITH_AVS_COAP_LOGS\n    _anjay_log(anjay, TRACE, \"WITH_AVS_COAP_LOGS = OFF\");\n#endif // WITH_AVS_COAP_LOGS\n#ifdef WITH_AVS_COAP_OBSERVE\n    _anjay_log(anjay, TRACE, \"WITH_AVS_COAP_OBSERVE = ON\");\n#else // WITH_AVS_COAP_OBSERVE\n    _anjay_log(anjay, TRACE, \"WITH_AVS_COAP_OBSERVE = OFF\");\n#endif // WITH_AVS_COAP_OBSERVE\n#ifdef WITH_AVS_COAP_OBSERVE_CANCEL_ON_TIMEOUT\n    _anjay_log(anjay, TRACE, \"WITH_AVS_COAP_OBSERVE_CANCEL_ON_TIMEOUT = ON\");\n#else // WITH_AVS_COAP_OBSERVE_CANCEL_ON_TIMEOUT\n    _anjay_log(anjay, TRACE, \"WITH_AVS_COAP_OBSERVE_CANCEL_ON_TIMEOUT = OFF\");\n#endif // WITH_AVS_COAP_OBSERVE_CANCEL_ON_TIMEOUT\n#ifdef WITH_AVS_COAP_OBSERVE_FORCE_CANCEL_ON_UNACKED_ERROR\n    _anjay_log(anjay, TRACE, \"WITH_AVS_COAP_OBSERVE_FORCE_CANCEL_ON_UNACKED_ERROR = ON\");\n#else // WITH_AVS_COAP_OBSERVE_FORCE_CANCEL_ON_UNACKED_ERROR\n    _anjay_log(anjay, TRACE, \"WITH_AVS_COAP_OBSERVE_FORCE_CANCEL_ON_UNACKED_ERROR = OFF\");\n#endif // WITH_AVS_COAP_OBSERVE_FORCE_CANCEL_ON_UNACKED_ERROR\n#ifdef WITH_AVS_COAP_OBSERVE_PERSISTENCE\n    _anjay_log(anjay, TRACE, \"WITH_AVS_COAP_OBSERVE_PERSISTENCE = ON\");\n#else // WITH_AVS_COAP_OBSERVE_PERSISTENCE\n    _anjay_log(anjay, TRACE, \"WITH_AVS_COAP_OBSERVE_PERSISTENCE = OFF\");\n#endif // WITH_AVS_COAP_OBSERVE_PERSISTENCE\n#ifdef WITH_AVS_COAP_OSCORE\n    _anjay_log(anjay, TRACE, \"WITH_AVS_COAP_OSCORE = ON\");\n#else // WITH_AVS_COAP_OSCORE\n    _anjay_log(anjay, TRACE, \"WITH_AVS_COAP_OSCORE = OFF\");\n#endif // WITH_AVS_COAP_OSCORE\n#ifdef WITH_AVS_COAP_OSCORE_DRAFT_8\n    _anjay_log(anjay, TRACE, \"WITH_AVS_COAP_OSCORE_DRAFT_8 = ON\");\n#else // WITH_AVS_COAP_OSCORE_DRAFT_8\n    _anjay_log(anjay, TRACE, \"WITH_AVS_COAP_OSCORE_DRAFT_8 = OFF\");\n#endif // WITH_AVS_COAP_OSCORE_DRAFT_8\n#ifdef WITH_AVS_COAP_POISONING\n    _anjay_log(anjay, TRACE, \"WITH_AVS_COAP_POISONING = ON\");\n#else // WITH_AVS_COAP_POISONING\n    _anjay_log(anjay, TRACE, \"WITH_AVS_COAP_POISONING = OFF\");\n#endif // WITH_AVS_COAP_POISONING\n#ifdef WITH_AVS_COAP_STREAMING_API\n    _anjay_log(anjay, TRACE, \"WITH_AVS_COAP_STREAMING_API = ON\");\n#else // WITH_AVS_COAP_STREAMING_API\n    _anjay_log(anjay, TRACE, \"WITH_AVS_COAP_STREAMING_API = OFF\");\n#endif // WITH_AVS_COAP_STREAMING_API\n#ifdef WITH_AVS_COAP_TCP\n    _anjay_log(anjay, TRACE, \"WITH_AVS_COAP_TCP = ON\");\n#else // WITH_AVS_COAP_TCP\n    _anjay_log(anjay, TRACE, \"WITH_AVS_COAP_TCP = OFF\");\n#endif // WITH_AVS_COAP_TCP\n#ifdef WITH_AVS_COAP_TRACE_LOGS\n    _anjay_log(anjay, TRACE, \"WITH_AVS_COAP_TRACE_LOGS = ON\");\n#else // WITH_AVS_COAP_TRACE_LOGS\n    _anjay_log(anjay, TRACE, \"WITH_AVS_COAP_TRACE_LOGS = OFF\");\n#endif // WITH_AVS_COAP_TRACE_LOGS\n#ifdef WITH_AVS_COAP_UDP\n    _anjay_log(anjay, TRACE, \"WITH_AVS_COAP_UDP = ON\");\n#else // WITH_AVS_COAP_UDP\n    _anjay_log(anjay, TRACE, \"WITH_AVS_COAP_UDP = OFF\");\n#endif // WITH_AVS_COAP_UDP\n}\n// clang-format on\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif /* ANJAY_CONFIG_LOG_H */\n"
  },
  {
    "path": "src/anjay_init.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay/anjay_config.h>\n#include <avsystem/coap/avs_coap_config.h>\n#include <avsystem/commons/avs_commons_config.h>\n\n// Checks for usage of removed configuration macros\n#ifdef ANJAY_WITH_MODULE_ATTR_STORAGE\n#    error \"ANJAY_WITH_MODULE_ATTR_STORAGE has been removed since Anjay 3.0. Please update your anjay_config.h to use ANJAY_WITH_ATTR_STORAGE instead.\"\n#endif // ANJAY_WITH_MODULE_ATTR_STORAGE\n\n#ifdef ANJAY_WITH_LWM2M_GATEWAY\n#    ifndef ANJAY_WITH_LWM2M11\n#        error \"LwM2M Gateway functionality requires LwM2M 1.1 support.\"\n#    endif // ANJAY_WITH_LWM2M11\n#endif     // ANJAY_WITH_LWM2M_GATEWAY\n\n#if defined(ANJAY_WITH_SSL_ERROR_API) && defined(AVS_COMMONS_WITH_OPENSSL)\n#    error \"ANJAY_WITH_SSL_ERROR_API is not implemented for the OpenSSL backend\"\n#endif // defined(ANJAY_WITH_SSL_ERROR_API) && defined(AVS_COMMONS_WITH_OPENSSL)\n\n#if defined(ANJAY_WITH_SSL_ERROR_API) && defined(AVS_COMMONS_WITH_TINYDTLS)\n#    error \"ANJAY_WITH_SSL_ERROR_API is not implemented for the TinyDTLS backend\"\n#endif // defined(ANJAY_WITH_SSL_ERROR_API) &&\n       // defined(AVS_COMMONS_WITH_TINYDTLS)\n\n#if defined(AVS_COMMONS_HAVE_VISIBILITY) && !defined(ANJAY_TEST)\n/* set default visibility for external symbols */\n#    pragma GCC visibility push(default)\n#    define VISIBILITY_SOURCE_BEGIN _Pragma(\"GCC visibility push(hidden)\")\n#    define VISIBILITY_PRIVATE_HEADER_BEGIN \\\n        _Pragma(\"GCC visibility push(hidden)\")\n#    define VISIBILITY_PRIVATE_HEADER_END _Pragma(\"GCC visibility pop\")\n#else\n#    define VISIBILITY_SOURCE_BEGIN\n#    define VISIBILITY_PRIVATE_HEADER_BEGIN\n#    define VISIBILITY_PRIVATE_HEADER_END\n#endif\n\n#ifdef ANJAY_WITH_TRACE_LOGS\n#    define AVS_LOG_WITH_TRACE\n#endif\n\n#ifdef AVS_COMMONS_WITH_AVS_LOG\n#    include <avsystem/commons/avs_log.h>\n#    define _(Arg) AVS_DISPOSABLE_LOG(Arg)\n#else // AVS_COMMONS_WITH_AVS_LOG\n#    define _(Arg) Arg\n#endif // AVS_COMMONS_WITH_AVS_LOG\n"
  },
  {
    "path": "src/anjay_modules/anjay_access_utils.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ANJAY_INCLUDE_ANJAY_MODULES_ACCESS_UTILS_H\n#define ANJAY_INCLUDE_ANJAY_MODULES_ACCESS_UTILS_H\n\n#include <anjay_modules/anjay_utils_core.h>\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\ntypedef struct {\n    AVS_LIST(struct anjay_acl_ref_validation_object_info_struct) object_infos;\n} anjay_acl_ref_validation_ctx_t;\n\n/**\n * Creates a new validation context for use with\n * @ref _anjay_acl_ref_validate_inst_ref. This is necessary because it caches\n * the IID list to limit the number of calls to list_instances handler, and for\n * the purpose of duplicate checking.\n */\nstatic inline anjay_acl_ref_validation_ctx_t\n_anjay_acl_ref_validation_ctx_new(void) {\n    return (anjay_acl_ref_validation_ctx_t) { NULL };\n}\n\nvoid _anjay_acl_ref_validation_ctx_cleanup(anjay_acl_ref_validation_ctx_t *ctx);\n\n/**\n * Validates whether the target instance reference inside an Access Control\n * object. The validation fails on one of the following conditions:\n *\n * - Object with OID == target_oid does not exist in the data model\n * - target_iid is not 65535, and instance with IID == target_iid does not exist\n *   in the object\n * - Validation of the same (target_oid, target_iid) pair is attempted more than\n *   once for the same ctx\n */\nint _anjay_acl_ref_validate_inst_ref(anjay_unlocked_t *anjay,\n                                     anjay_acl_ref_validation_ctx_t *ctx,\n                                     anjay_oid_t target_oid,\n                                     anjay_iid_t target_iid);\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif /* ANJAY_INCLUDE_ANJAY_MODULES_ACCESS_UTILS_H */\n"
  },
  {
    "path": "src/anjay_modules/anjay_attr_storage_utils.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ANJAY_INCLUDE_ANJAY_MODULES_AS_H\n#define ANJAY_INCLUDE_ANJAY_MODULES_AS_H\n\ntypedef struct anjay_attr_storage_struct anjay_attr_storage_t;\n\n#endif // ANJAY_INCLUDE_ANJAY_MODULES_AS_H\n"
  },
  {
    "path": "src/anjay_modules/anjay_bootstrap.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ANJAY_INCLUDE_ANJAY_MODULES_BOOTSTRAP_H\n#define ANJAY_INCLUDE_ANJAY_MODULES_BOOTSTRAP_H\n\n#include <stdbool.h>\n\n#include <anjay_modules/anjay_io_utils.h>\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\n#ifdef ANJAY_WITH_BOOTSTRAP\n\nbool _anjay_bootstrap_in_progress(anjay_unlocked_t *anjay);\n\nvoid _anjay_bootstrap_cleanup(anjay_unlocked_t *anjay);\n\n#    if defined(ANJAY_WITH_MODULE_FACTORY_PROVISIONING)\navs_error_t _anjay_bootstrap_delete_everything(anjay_unlocked_t *anjay);\n\nint _anjay_bootstrap_finish(anjay_unlocked_t *anjay);\n#    endif /* defined(ANJAY_WITH_MODULE_BOOTSTRAPPER) || \\\n              defined(ANJAY_WITH_MODULE_FACTORY_PROVISIONING) */\n\n#    ifdef ANJAY_WITH_MODULE_FACTORY_PROVISIONING\nint _anjay_bootstrap_write_composite(anjay_unlocked_t *anjay,\n                                     anjay_unlocked_input_ctx_t *in_ctx);\n#    endif // ANJAY_WITH_MODULE_FACTORY_PROVISIONING\n\n#    ifdef ANJAY_WITH_LWM2M11\nint _anjay_schedule_bootstrap_request_unlocked(anjay_unlocked_t *anjay);\n#    endif // ANJAY_WITH_LWM2M11\n\n#else\n\n#    define _anjay_bootstrap_in_progress(anjay) ((void) (anjay), false)\n\n#    define _anjay_bootstrap_cleanup(anjay) ((void) 0)\n\n#endif\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif /* ANJAY_INCLUDE_ANJAY_MODULES_BOOTSTRAP_H */\n"
  },
  {
    "path": "src/anjay_modules/anjay_dm_utils.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ANJAY_INCLUDE_ANJAY_MODULES_DM_H\n#define ANJAY_INCLUDE_ANJAY_MODULES_DM_H\n\n#ifdef ANJAY_WITH_SEND\n#    include <anjay/lwm2m_send.h>\n#endif // ANJAY_WITH_SEND\n\n#ifdef ANJAY_WITH_LWM2M_GATEWAY\n#    include <inttypes.h>\n#    include <string.h>\n\n#    include <anjay/core.h>\n#endif // ANJAY_WITH_LWM2M_GATEWAY\n\n#include <anjay_modules/dm/anjay_modules.h>\n\n#include <assert.h>\n#include <limits.h>\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n#ifdef ANJAY_WITH_LWM2M_GATEWAY\n// ANJAY_GATEWAY_MAX_PREFIX_LEN contains space for null-terminator, but since\n// sizeof(...) already includes null-terminator, we use that one additional byte\n// for the new leading '/' in path\n#    define MAX_PATH_STRING_SIZE \\\n        sizeof(\"/65535/65535/65535/65535\") + ANJAY_GATEWAY_MAX_PREFIX_LEN\n#else // ANJAY_WITH_LWM2M_GATEWAY\n#    define MAX_PATH_STRING_SIZE sizeof(\"/65535/65535/65535/65535\")\n#endif // ANJAY_WITH_LWM2M_GATEWAY\n\nstatic inline size_t _anjay_uri_path_length(const anjay_uri_path_t *path) {\n    size_t result;\n    for (result = 0; result < AVS_ARRAY_SIZE(path->ids); ++result) {\n        if (path->ids[result] == ANJAY_ID_INVALID) {\n            break;\n        }\n    }\n    return result;\n}\n\nstatic inline bool _anjay_uri_path_has(const anjay_uri_path_t *path,\n                                       anjay_id_type_t id_type) {\n    return _anjay_uri_path_length(path) > id_type;\n}\n\nstatic inline bool _anjay_uri_path_leaf_is(const anjay_uri_path_t *path,\n                                           anjay_id_type_t id_type) {\n    return _anjay_uri_path_length(path) == (size_t) id_type + 1u;\n}\n\n#ifdef ANJAY_WITH_LWM2M_GATEWAY\nstatic inline bool _anjay_uri_path_prefix_is(const anjay_uri_path_t *path,\n                                             const char *prefix) {\n    return strcmp(path->prefix, prefix) == 0;\n}\n\nstatic inline bool _anjay_uri_path_prefix_equal(const anjay_uri_path_t *left,\n                                                const anjay_uri_path_t *right) {\n    return strcmp(left->prefix, right->prefix) == 0;\n}\n\nstatic inline bool _anjay_uri_path_has_prefix(const anjay_uri_path_t *path) {\n    return path->prefix[0] != '\\0';\n}\n\nstatic inline void\n_anjay_uri_path_with_prefix_from_end_device_iid(anjay_uri_path_t *path,\n                                                anjay_iid_t end_device_iid,\n                                                anjay_oid_t oid,\n                                                anjay_iid_t iid,\n                                                anjay_rid_t rid,\n                                                anjay_riid_t riid) {\n    *path = (anjay_uri_path_t) {\n        .ids = {\n            [ANJAY_ID_OID] = oid,\n            [ANJAY_ID_IID] = iid,\n            [ANJAY_ID_RID] = rid,\n            [ANJAY_ID_RIID] = riid\n        }\n    };\n    avs_simple_snprintf(path->prefix, ANJAY_GATEWAY_MAX_PREFIX_LEN,\n                        \"dev%\" PRIu16, end_device_iid);\n}\n\n#endif // ANJAY_WITH_LWM2M_GATEWAY\n\nstatic inline int _anjay_uri_path_compare(const anjay_uri_path_t *left,\n                                          const anjay_uri_path_t *right) {\n#ifdef ANJAY_WITH_LWM2M_GATEWAY\n    int prefix_cmp = strcmp(left->prefix, right->prefix);\n    if (prefix_cmp) {\n        return prefix_cmp;\n    }\n#endif // ANJAY_WITH_LWM2M_GATEWAY\n    for (size_t i = 0; i < AVS_ARRAY_SIZE(left->ids); ++i) {\n        if (left->ids[i] < right->ids[i]) {\n            return -1;\n        } else if (left->ids[i] > right->ids[i]) {\n            return 1;\n        } else if (left->ids[i] == ANJAY_ID_INVALID) {\n            break;\n        }\n    }\n    return 0;\n}\n\nstatic inline bool _anjay_uri_path_equal(const anjay_uri_path_t *left,\n                                         const anjay_uri_path_t *right) {\n    return _anjay_uri_path_compare(left, right) == 0;\n}\n\nstatic inline bool _anjay_uri_path_outside_base(const anjay_uri_path_t *path,\n                                                const anjay_uri_path_t *base) {\n    for (size_t i = 0; i < AVS_ARRAY_SIZE(base->ids); ++i) {\n        if (base->ids[i] == ANJAY_ID_INVALID) {\n            // base is no longer than path, previous IDs validated\n            return false;\n        } else if (path->ids[i] != base->ids[i]) {\n            // path is shorter than base (path->ids[i] == ANJAY_ID_INVALID)\n            // or IDs differ\n            return true;\n        }\n    }\n    return false;\n}\n\n/**\n * Returns true if array of ids can be splitted into two consistent parts:\n * - valid ids from the beginning\n * - ANJAY_INVALID_ID from the end\n */\nstatic inline bool _anjay_uri_path_normalized(const anjay_uri_path_t *path) {\n    for (size_t i = _anjay_uri_path_length(path) + 1;\n         i < AVS_ARRAY_SIZE(path->ids);\n         ++i) {\n        if (path->ids[i] != ANJAY_ID_INVALID) {\n            return false;\n        }\n    }\n    return true;\n}\n\n#ifdef ANJAY_WITH_LWM2M11\nvoid _anjay_uri_path_update_common_prefix(const anjay_uri_path_t **prefix_ptr,\n                                          anjay_uri_path_t *prefix_buf,\n                                          const anjay_uri_path_t *path);\n#endif // ANJAY_WITH_LWM2M11\n\nconst char *_anjay_debug_make_path__(char *buffer,\n                                     size_t buffer_size,\n                                     const anjay_uri_path_t *uri);\n\n#define ANJAY_DEBUG_MAKE_PATH(path)                                  \\\n    (_anjay_debug_make_path__(&(char[MAX_PATH_STRING_SIZE]){ 0 }[0], \\\n                              MAX_PATH_STRING_SIZE, (path)))\n\n#define URI_PATH_INITIALIZER(Oid, Iid, Rid, Riid) \\\n    {                                             \\\n        .ids = {                                  \\\n            [ANJAY_ID_OID] = (Oid),               \\\n            [ANJAY_ID_IID] = (Iid),               \\\n            [ANJAY_ID_RID] = (Rid),               \\\n            [ANJAY_ID_RIID] = (Riid)              \\\n        }                                         \\\n    }\n\n#define RESOURCE_INSTANCE_PATH_INITIALIZER(Oid, Iid, Rid, Riid) \\\n    URI_PATH_INITIALIZER(Oid, Iid, Rid, Riid)\n\n#define RESOURCE_PATH_INITIALIZER(Oid, Iid, Rid) \\\n    URI_PATH_INITIALIZER(Oid, Iid, Rid, ANJAY_ID_INVALID)\n\n#define INSTANCE_PATH_INITIALIZER(Oid, Iid) \\\n    URI_PATH_INITIALIZER(Oid, Iid, ANJAY_ID_INVALID, ANJAY_ID_INVALID)\n\n#define OBJECT_PATH_INITIALIZER(Oid)                              \\\n    URI_PATH_INITIALIZER(Oid, ANJAY_ID_INVALID, ANJAY_ID_INVALID, \\\n                         ANJAY_ID_INVALID)\n\n#define ROOT_PATH_INITIALIZER()                                                \\\n    URI_PATH_INITIALIZER(ANJAY_ID_INVALID, ANJAY_ID_INVALID, ANJAY_ID_INVALID, \\\n                         ANJAY_ID_INVALID)\n\n#define MAKE_URI_PATH(...) \\\n    ((anjay_uri_path_t) URI_PATH_INITIALIZER(__VA_ARGS__))\n\n#define MAKE_RESOURCE_INSTANCE_PATH(Oid, Iid, Rid, Riid) \\\n    MAKE_URI_PATH(Oid, Iid, Rid, Riid)\n\n#define MAKE_RESOURCE_PATH(Oid, Iid, Rid) \\\n    MAKE_URI_PATH(Oid, Iid, Rid, ANJAY_ID_INVALID)\n\n#define MAKE_INSTANCE_PATH(Oid, Iid) \\\n    MAKE_URI_PATH(Oid, Iid, ANJAY_ID_INVALID, ANJAY_ID_INVALID)\n\n#define MAKE_OBJECT_PATH(Oid) \\\n    MAKE_URI_PATH(Oid, ANJAY_ID_INVALID, ANJAY_ID_INVALID, ANJAY_ID_INVALID)\n\n#define MAKE_ROOT_PATH()                                                \\\n    MAKE_URI_PATH(ANJAY_ID_INVALID, ANJAY_ID_INVALID, ANJAY_ID_INVALID, \\\n                  ANJAY_ID_INVALID)\n\n#ifdef ANJAY_WITH_LWM2M_GATEWAY\n#    define URI_PATH_INITIALIZER_WITH_PREFIX(Prefix, Oid, Iid, Rid, Riid) \\\n        {                                                                 \\\n            .ids = {                                                      \\\n                [ANJAY_ID_OID] = (Oid),                                   \\\n                [ANJAY_ID_IID] = (Iid),                                   \\\n                [ANJAY_ID_RID] = (Rid),                                   \\\n                [ANJAY_ID_RIID] = (Riid)                                  \\\n            },                                                            \\\n            .prefix = Prefix                                              \\\n        }\n\n#    define RESOURCE_INSTANCE_PATH_INITIALIZER_WITH_PREFIX(Prefix, Oid, Iid, \\\n                                                           Rid, Riid)        \\\n        URI_PATH_INITIALIZER_WITH_PREFIX(Prefix, Oid, Iid, Rid, Riid)\n\n#    define RESOURCE_PATH_INITIALIZER_WITH_PREFIX(Prefix, Oid, Iid, Rid) \\\n        URI_PATH_INITIALIZER_WITH_PREFIX(Prefix, Oid, Iid, Rid,          \\\n                                         ANJAY_ID_INVALID)\n\n#    define INSTANCE_PATH_INITIALIZER_WITH_PREFIX(Prefix, Oid, Iid)          \\\n        URI_PATH_INITIALIZER_WITH_PREFIX(Prefix, Oid, Iid, ANJAY_ID_INVALID, \\\n                                         ANJAY_ID_INVALID)\n\n#    define OBJECT_PATH_INITIALIZER_WITH_PREFIX(Prefix, Oid)            \\\n        URI_PATH_INITIALIZER_WITH_PREFIX(Prefix, Oid, ANJAY_ID_INVALID, \\\n                                         ANJAY_ID_INVALID, ANJAY_ID_INVALID)\n\n#    define ROOT_PATH_INITIALIZER_WITH_PREFIX(Prefix)                        \\\n        URI_PATH_INITIALIZER_WITH_PREFIX(Prefix, ANJAY_ID_INVALID,           \\\n                                         ANJAY_ID_INVALID, ANJAY_ID_INVALID, \\\n                                         ANJAY_ID_INVALID)\n\n#    define MAKE_URI_PATH_WITH_PREFIX(...) \\\n        ((anjay_uri_path_t) URI_PATH_INITIALIZER_WITH_PREFIX(__VA_ARGS__))\n\n#    define MAKE_RESOURCE_INSTANCE_PATH_WITH_PREFIX(Prefix, Oid, Iid, Rid, \\\n                                                    Riid)                  \\\n        MAKE_URI_PATH_WITH_PREFIX(Prefix, Oid, Iid, Rid, Riid)\n\n#    define MAKE_RESOURCE_PATH_WITH_PREFIX(Prefix, Oid, Iid, Rid) \\\n        MAKE_URI_PATH_WITH_PREFIX(Prefix, Oid, Iid, Rid, ANJAY_ID_INVALID)\n\n#    define MAKE_INSTANCE_PATH_WITH_PREFIX(Prefix, Oid, Iid)          \\\n        MAKE_URI_PATH_WITH_PREFIX(Prefix, Oid, Iid, ANJAY_ID_INVALID, \\\n                                  ANJAY_ID_INVALID)\n\n#    define MAKE_OBJECT_PATH_WITH_PREFIX(Prefix, Oid)            \\\n        MAKE_URI_PATH_WITH_PREFIX(Prefix, Oid, ANJAY_ID_INVALID, \\\n                                  ANJAY_ID_INVALID, ANJAY_ID_INVALID)\n\n#    define MAKE_ROOT_PATH_WITH_PREFIX(Prefix)                                \\\n        MAKE_URI_PATH_WITH_PREFIX(Prefix, ANJAY_ID_INVALID, ANJAY_ID_INVALID, \\\n                                  ANJAY_ID_INVALID, ANJAY_ID_INVALID)\n#endif // ANJAY_WITH_LWM2M_GATEWAY\n\ntypedef enum anjay_request_action {\n    ANJAY_ACTION_READ,\n#ifdef ANJAY_WITH_LWM2M11\n    ANJAY_ACTION_READ_COMPOSITE,\n#endif // ANJAY_WITH_LWM2M11\n    ANJAY_ACTION_DISCOVER,\n    ANJAY_ACTION_WRITE,\n#ifdef ANJAY_WITH_LWM2M11\n    ANJAY_ACTION_WRITE_COMPOSITE,\n#endif // ANJAY_WITH_LWM2M11\n    ANJAY_ACTION_WRITE_UPDATE,\n    ANJAY_ACTION_WRITE_ATTRIBUTES,\n    ANJAY_ACTION_EXECUTE,\n    ANJAY_ACTION_CREATE,\n    ANJAY_ACTION_DELETE,\n    ANJAY_ACTION_BOOTSTRAP_FINISH\n} anjay_request_action_t;\n\ntypedef enum {\n    ANJAY_DM_WRITE_TYPE_INVALID = -1,\n    ANJAY_DM_WRITE_TYPE_UPDATE,\n    ANJAY_DM_WRITE_TYPE_REPLACE\n} anjay_dm_write_type_t;\n\nstatic inline anjay_dm_write_type_t _anjay_dm_write_type_from_request_action(\n        anjay_request_action_t request_action) {\n    switch (request_action) {\n    case ANJAY_ACTION_WRITE:\n        return ANJAY_DM_WRITE_TYPE_REPLACE;\n    case ANJAY_ACTION_WRITE_UPDATE:\n    case ANJAY_ACTION_CREATE:\n#ifdef ANJAY_WITH_LWM2M11\n    case ANJAY_ACTION_WRITE_COMPOSITE:\n#endif // ANJAY_WITH_LWM2M11\n        return ANJAY_DM_WRITE_TYPE_UPDATE;\n    default:\n        AVS_UNREACHABLE(\"Unexpected request action\");\n        return ANJAY_DM_WRITE_TYPE_INVALID;\n    }\n}\n\n#ifdef ANJAY_WITH_THREAD_SAFETY\n\nstruct anjay_unlocked_dm_object_def_struct {\n    anjay_oid_t oid;\n    const char *version;\n    anjay_unlocked_dm_handlers_t handlers;\n};\n\n#endif // ANJAY_WITH_THREAD_SAFETY\n\nint _anjay_dm_read_resource_into_ctx(anjay_unlocked_t *anjay,\n                                     const anjay_uri_path_t *path,\n                                     anjay_unlocked_output_ctx_t *ctx);\n\nint _anjay_dm_read_resource_into_stream(anjay_unlocked_t *anjay,\n                                        const anjay_uri_path_t *path,\n                                        avs_stream_t *stream);\n\nint _anjay_dm_read_resource_into_buffer(anjay_unlocked_t *anjay,\n                                        const anjay_uri_path_t *path,\n                                        char *buffer,\n                                        size_t buffer_size,\n                                        size_t *out_bytes_read);\n\nstatic inline int _anjay_dm_read_resource_string(anjay_unlocked_t *anjay,\n                                                 const anjay_uri_path_t *path,\n                                                 char *buffer,\n                                                 size_t buffer_size) {\n    assert(buffer && buffer_size > 0);\n    size_t bytes_read;\n    int result =\n            _anjay_dm_read_resource_into_buffer(anjay, path, buffer,\n                                                buffer_size - 1, &bytes_read);\n    if (!result) {\n        buffer[bytes_read] = '\\0';\n    }\n    return result;\n}\n\nstatic inline int _anjay_dm_read_resource_i64(anjay_unlocked_t *anjay,\n                                              const anjay_uri_path_t *path,\n                                              int64_t *out_value) {\n    size_t bytes_read;\n    int result = _anjay_dm_read_resource_into_buffer(\n            anjay, path, (char *) out_value, sizeof(*out_value), &bytes_read);\n    if (result) {\n        return result;\n    }\n    return bytes_read != sizeof(*out_value);\n}\n\n#ifdef ANJAY_WITH_LWM2M11\nstatic inline int _anjay_dm_read_resource_u64(anjay_unlocked_t *anjay,\n                                              const anjay_uri_path_t *path,\n                                              uint64_t *out_value) {\n    size_t bytes_read;\n    int result = _anjay_dm_read_resource_into_buffer(\n            anjay, path, (char *) out_value, sizeof(*out_value), &bytes_read);\n    if (result) {\n        return result;\n    }\n    return bytes_read != sizeof(*out_value);\n}\n#endif // ANJAY_WITH_LWM2M11\n\nstatic inline int _anjay_dm_read_resource_u16(anjay_unlocked_t *anjay,\n                                              const anjay_uri_path_t *path,\n                                              uint16_t *out_value) {\n    int64_t value;\n    int result = _anjay_dm_read_resource_i64(anjay, path, &value);\n    if (result) {\n        return result;\n    }\n    if (value < 0 || value >= UINT16_MAX) {\n        return -1;\n    }\n    *out_value = (uint16_t) value;\n    return 0;\n}\n\nstatic inline int _anjay_dm_read_resource_bool(anjay_unlocked_t *anjay,\n                                               const anjay_uri_path_t *path,\n                                               bool *out_value) {\n    size_t bytes_read;\n    int result = _anjay_dm_read_resource_into_buffer(\n            anjay, path, (char *) out_value, sizeof(*out_value), &bytes_read);\n    if (result) {\n        return result;\n    }\n    return bytes_read != sizeof(*out_value);\n}\n\nstatic inline int _anjay_dm_read_resource_objlnk(anjay_unlocked_t *anjay,\n                                                 const anjay_uri_path_t *path,\n                                                 anjay_oid_t *out_oid,\n                                                 anjay_iid_t *out_iid) {\n    size_t bytes_read;\n    uint32_t objlnk_encoded;\n    int result = _anjay_dm_read_resource_into_buffer(anjay, path,\n                                                     (char *) &objlnk_encoded,\n                                                     sizeof(objlnk_encoded),\n                                                     &bytes_read);\n    if (result) {\n        return result;\n    }\n    if (bytes_read != sizeof(objlnk_encoded)) {\n        return -1;\n    }\n    *out_iid = (anjay_iid_t) (objlnk_encoded & 0xFFFF);\n    *out_oid = (anjay_oid_t) (objlnk_encoded >> 16);\n    return 0;\n}\n\n#ifdef ANJAY_WITH_LWM2M11\n/**\n * Reads an array of uint32_t values from the data model and returns them via an\n * array allocated using @ref avs_malloc .\n *\n * @param      anjay                   Anjay object to operate on.\n * @param      path                    Resource path to pull data from.\n * @param[out] out_array               Pointer to a variable that on success\n *                                     will be set to a pointer to array of read\n *                                     values. Must not be NULL.\n * @param[out] out_array_size_elements Pointer to a variable that on success\n *                                     will be set to the number of uint32_t\n *                                     values read from data model and available\n *                                     in @p out_array . Must not be NULL.\n *\n * @returns 0 on success, a negative value (one of ANJAY_ERR_* constants) on\n *          error.\n *\n * Notes:\n * - on error, out_array and out_array_size_elements are not modified.\n * - in case 0 elements are read successfully, @p out_array may or may not be\n *   set to a non-NULL value. It is always necessary to call @ref avs_free on\n *   @p out_array whenever this function succeeds.\n */\nint _anjay_dm_read_resource_u32_array(anjay_unlocked_t *anjay,\n                                      const anjay_uri_path_t *path,\n                                      uint32_t **out_array,\n                                      size_t *out_array_size_elements);\n#endif // ANJAY_WITH_LWM2M11\n\ntypedef struct anjay_dm anjay_dm_t;\n\ntypedef int\nanjay_dm_foreach_object_handler_t(anjay_unlocked_t *anjay,\n                                  const anjay_dm_installed_object_t *obj,\n                                  void *data);\n\nint _anjay_dm_foreach_object(anjay_unlocked_t *anjay,\n                             anjay_dm_t *dm,\n                             anjay_dm_foreach_object_handler_t *handler,\n                             void *data);\n\ntypedef int\nanjay_dm_foreach_instance_handler_t(anjay_unlocked_t *anjay,\n                                    const anjay_dm_installed_object_t *obj,\n                                    anjay_iid_t iid,\n                                    void *data);\n\nint _anjay_dm_foreach_instance(anjay_unlocked_t *anjay,\n                               const anjay_dm_installed_object_t *obj,\n                               anjay_dm_foreach_instance_handler_t *handler,\n                               void *data);\n\nint _anjay_dm_get_sorted_instance_list(anjay_unlocked_t *anjay,\n                                       const anjay_dm_installed_object_t *obj,\n                                       AVS_LIST(anjay_iid_t) *out);\n\nint _anjay_dm_instance_present(anjay_unlocked_t *anjay,\n                               const anjay_dm_installed_object_t *obj_ptr,\n                               anjay_iid_t iid);\n\ntypedef int\nanjay_dm_foreach_resource_handler_t(anjay_unlocked_t *anjay,\n                                    const anjay_dm_installed_object_t *obj,\n                                    anjay_iid_t iid,\n                                    anjay_rid_t rid,\n                                    anjay_dm_resource_kind_t kind,\n                                    anjay_dm_resource_presence_t presence,\n                                    void *data);\n\nint _anjay_dm_foreach_resource(anjay_unlocked_t *anjay,\n                               const anjay_dm_installed_object_t *obj,\n                               anjay_iid_t iid,\n                               anjay_dm_foreach_resource_handler_t *handler,\n                               void *data);\n\n/**\n * Checks if the specific resource is supported and present, and what is its\n * kind. This function internally calls @ref _anjay_dm_foreach_resource, so it\n * is not optimal to use for multiple resources within the same Object Instance.\n *\n * NOTE: It is REQUIRED that the presence of the Object and Object Instance is\n * checked beforehand, this function does not perform such checks.\n *\n * @param anjay        Anjay object to operate on\n * @param obj_ptr      Definition of an Object in which the queried resource is\n * @param iid          ID of Object Instance in which the queried resource is\n * @param rid          ID of the queried Resource\n * @param out_kind     Pointer to a variable in which the kind of the resource\n *                     will be stored. May be NULL, in which case only the\n *                     presence is checked.\n * @param out_presence Pointer to a variable in which the presence information\n *                     about the resource will be stored. May be NULL, in which\n *                     case only the kind is checked.\n *\n * @returns 0 for success, or a non-zero error code in case of error.\n *\n * NOTE: Two scenarios are possible if the resource is not currently present in\n * the object:\n * - If the resource is not Supported (i.e., it has not been enumerated by the\n *   list_resources handler at all), the function fails, returning\n *   ANJAY_ERR_NOT_FOUND\n * - If the resource is Supported, but not Present (i.e., it has been enumerated\n *   by the list_resources handler with presence set to ANJAY_DM_RES_ABSENT),\n *   the function succeeds (returns 0), but *out_presence is set to\n *   ANJAY_DM_RES_ABSENT\n */\nint _anjay_dm_resource_kind_and_presence(\n        anjay_unlocked_t *anjay,\n        const anjay_dm_installed_object_t *obj_ptr,\n        anjay_iid_t iid,\n        anjay_rid_t rid,\n        anjay_dm_resource_kind_t *out_kind,\n        anjay_dm_resource_presence_t *out_presence);\n\ntypedef int anjay_dm_foreach_resource_instance_handler_t(\n        anjay_unlocked_t *anjay,\n        const anjay_dm_installed_object_t *obj,\n        anjay_iid_t iid,\n        anjay_rid_t rid,\n        anjay_riid_t riid,\n        void *data);\n\nint _anjay_dm_foreach_resource_instance(\n        anjay_unlocked_t *anjay,\n        const anjay_dm_installed_object_t *obj,\n        anjay_iid_t iid,\n        anjay_rid_t rid,\n        anjay_dm_foreach_resource_instance_handler_t *handler,\n        void *data);\n\nstatic inline bool _anjay_dm_res_kind_valid(anjay_dm_resource_kind_t kind) {\n    return kind == ANJAY_DM_RES_R || kind == ANJAY_DM_RES_W\n           || kind == ANJAY_DM_RES_RW || kind == ANJAY_DM_RES_RM\n           || kind == ANJAY_DM_RES_WM || kind == ANJAY_DM_RES_RWM\n           || kind == ANJAY_DM_RES_E || kind == ANJAY_DM_RES_BS_RW;\n}\n\nstatic inline bool\n_anjay_dm_res_kind_single_readable(anjay_dm_resource_kind_t kind) {\n    return kind == ANJAY_DM_RES_R || kind == ANJAY_DM_RES_RW;\n}\n\nstatic inline bool _anjay_dm_res_kind_readable(anjay_dm_resource_kind_t kind) {\n    return kind == ANJAY_DM_RES_R || kind == ANJAY_DM_RES_RW\n           || kind == ANJAY_DM_RES_RM || kind == ANJAY_DM_RES_RWM;\n}\n\nstatic inline bool _anjay_dm_res_kind_writable(anjay_dm_resource_kind_t kind) {\n    return kind == ANJAY_DM_RES_W || kind == ANJAY_DM_RES_RW\n           || kind == ANJAY_DM_RES_WM || kind == ANJAY_DM_RES_RWM;\n}\n\nstatic inline bool\n_anjay_dm_res_kind_executable(anjay_dm_resource_kind_t kind) {\n    return kind == ANJAY_DM_RES_E;\n}\n\nstatic inline bool _anjay_dm_res_kind_multiple(anjay_dm_resource_kind_t kind) {\n    return kind == ANJAY_DM_RES_RM || kind == ANJAY_DM_RES_WM\n           || kind == ANJAY_DM_RES_RWM;\n}\n\nstatic inline bool\n_anjay_dm_res_kind_bootstrappable(anjay_dm_resource_kind_t kind) {\n    return kind == ANJAY_DM_RES_BS_RW;\n}\n\n/**\n * Writes to a resource whose location is determined by the path extracted\n * from Input Context (@p in_ctx). Note that it does NOT check whether the\n * resource is writable - it is enough that it represents a value (i.e. is not\n * an executable resource).\n */\nint _anjay_dm_write_resource_and_move_to_next_entry(\n        anjay_unlocked_t *anjay,\n        const anjay_dm_installed_object_t *obj,\n        anjay_unlocked_input_ctx_t *in_ctx,\n        anjay_notify_queue_t *notify_queue);\n\n#ifdef ANJAY_WITH_LWM2M11\n/**\n * Works as @ref _anjay_dm_write_resource , but takes value of type @c int64_t\n * instead of input context.\n */\nint _anjay_dm_write_resource_i64(anjay_unlocked_t *anjay,\n                                 anjay_uri_path_t path,\n                                 int64_t value,\n                                 anjay_notify_queue_t *notify_queue);\n\n/**\n * Works as @ref _anjay_dm_write_resource , but takes value of type @c uint64_t\n * instead of input context.\n */\nint _anjay_dm_write_resource_u64(anjay_unlocked_t *anjay,\n                                 anjay_uri_path_t path,\n                                 uint64_t value,\n                                 anjay_notify_queue_t *notify_queue);\n#endif // ANJAY_WITH_LWM2M11\n\ntypedef enum {\n    ANJAY_DM_HANDLER_object_read_default_attrs,\n    ANJAY_DM_HANDLER_object_write_default_attrs,\n    ANJAY_DM_HANDLER_list_instances,\n    ANJAY_DM_HANDLER_instance_reset,\n    ANJAY_DM_HANDLER_instance_create,\n    ANJAY_DM_HANDLER_instance_remove,\n    ANJAY_DM_HANDLER_instance_read_default_attrs,\n    ANJAY_DM_HANDLER_instance_write_default_attrs,\n    ANJAY_DM_HANDLER_list_resources,\n    ANJAY_DM_HANDLER_resource_read,\n    ANJAY_DM_HANDLER_resource_write,\n    ANJAY_DM_HANDLER_resource_execute,\n    ANJAY_DM_HANDLER_resource_reset,\n    ANJAY_DM_HANDLER_list_resource_instances,\n    ANJAY_DM_HANDLER_resource_read_attrs,\n    ANJAY_DM_HANDLER_resource_write_attrs,\n    ANJAY_DM_HANDLER_transaction_begin,\n    ANJAY_DM_HANDLER_transaction_validate,\n    ANJAY_DM_HANDLER_transaction_commit,\n    ANJAY_DM_HANDLER_transaction_rollback,\n#ifdef ANJAY_WITH_LWM2M11\n    ANJAY_DM_HANDLER_resource_instance_read_attrs,\n    ANJAY_DM_HANDLER_resource_instance_write_attrs,\n#endif // ANJAY_WITH_LWM2M11\n#ifdef ANJAY_WITH_LWM2M12\n    ANJAY_DM_HANDLER_resource_instance_remove,\n#endif // ANJAY_WITH_LWM2M12\n} anjay_dm_handler_t;\n\n/**\n * Checks whether a specific data model handler is implemented for a given\n * Object, with respect to the Attribute Storage subsystem.\n *\n * The basic idea is that if this function returns <c>true</c> for a given\n * handler, it means that the corresponding <c>_anjay_dm_*</c> function called\n * with the same <c>anjay</c> and <c>obj_ptr</c> arguments will forward to some\n * actually implemented code (rather than defaulting to\n * <c>ANJAY_ERR_METHOD_NOT_ALLOWED</c>).\n *\n * @param anjay          Anjay object to operate on\n *\n * @param obj_ptr        Handle to an object to check handlers in\n *\n * @param handler_type   Type of handler to check\n *\n * @return Boolean value determining whether an applicable handler\n *         implementation is available\n */\nbool _anjay_dm_handler_implemented(const anjay_dm_installed_object_t *obj_ptr,\n                                   anjay_dm_handler_t handler_type);\n\nint _anjay_dm_call_object_read_default_attrs(\n        anjay_unlocked_t *anjay,\n        const anjay_dm_installed_object_t *obj_ptr,\n        anjay_ssid_t ssid,\n        anjay_dm_oi_attributes_t *out);\nint _anjay_dm_call_object_write_default_attrs(\n        anjay_unlocked_t *anjay,\n        const anjay_dm_installed_object_t *obj_ptr,\n        anjay_ssid_t ssid,\n        const anjay_dm_oi_attributes_t *attrs);\nint _anjay_dm_call_list_instances(anjay_unlocked_t *anjay,\n                                  const anjay_dm_installed_object_t *obj_ptr,\n                                  anjay_unlocked_dm_list_ctx_t *ctx);\nint _anjay_dm_call_instance_reset(anjay_unlocked_t *anjay,\n                                  const anjay_dm_installed_object_t *obj_ptr,\n                                  anjay_iid_t iid);\nint _anjay_dm_call_instance_create(anjay_unlocked_t *anjay,\n                                   const anjay_dm_installed_object_t *obj_ptr,\n                                   anjay_iid_t iid);\nint _anjay_dm_call_instance_remove(anjay_unlocked_t *anjay,\n                                   const anjay_dm_installed_object_t *obj_ptr,\n                                   anjay_iid_t iid);\nint _anjay_dm_call_instance_read_default_attrs(\n        anjay_unlocked_t *anjay,\n        const anjay_dm_installed_object_t *obj_ptr,\n        anjay_iid_t iid,\n        anjay_ssid_t ssid,\n        anjay_dm_oi_attributes_t *out);\nint _anjay_dm_call_instance_write_default_attrs(\n        anjay_unlocked_t *anjay,\n        const anjay_dm_installed_object_t *obj_ptr,\n        anjay_iid_t iid,\n        anjay_ssid_t ssid,\n        const anjay_dm_oi_attributes_t *attrs);\nint _anjay_dm_call_list_resources(anjay_unlocked_t *anjay,\n                                  const anjay_dm_installed_object_t *obj_ptr,\n                                  anjay_iid_t iid,\n                                  anjay_unlocked_dm_resource_list_ctx_t *ctx);\n\nint _anjay_dm_call_resource_read(anjay_unlocked_t *anjay,\n                                 const anjay_dm_installed_object_t *obj_ptr,\n                                 anjay_iid_t iid,\n                                 anjay_rid_t rid,\n                                 anjay_riid_t riid,\n                                 anjay_unlocked_output_ctx_t *ctx);\nint _anjay_dm_call_resource_write(anjay_unlocked_t *anjay,\n                                  const anjay_dm_installed_object_t *obj_ptr,\n                                  anjay_iid_t iid,\n                                  anjay_rid_t rid,\n                                  anjay_riid_t riid,\n                                  anjay_unlocked_input_ctx_t *ctx);\nint _anjay_dm_call_resource_execute(anjay_unlocked_t *anjay,\n                                    const anjay_dm_installed_object_t *obj_ptr,\n                                    anjay_iid_t iid,\n                                    anjay_rid_t rid,\n                                    anjay_unlocked_execute_ctx_t *execute_ctx);\nint _anjay_dm_call_resource_reset(anjay_unlocked_t *anjay,\n                                  const anjay_dm_installed_object_t *obj_ptr,\n                                  anjay_iid_t iid,\n                                  anjay_rid_t rid);\nint _anjay_dm_call_list_resource_instances(\n        anjay_unlocked_t *anjay,\n        const anjay_dm_installed_object_t *obj_ptr,\n        anjay_iid_t iid,\n        anjay_rid_t rid,\n        anjay_unlocked_dm_list_ctx_t *ctx);\nint _anjay_dm_call_resource_read_attrs(\n        anjay_unlocked_t *anjay,\n        const anjay_dm_installed_object_t *obj_ptr,\n        anjay_iid_t iid,\n        anjay_rid_t rid,\n        anjay_ssid_t ssid,\n        anjay_dm_r_attributes_t *out);\nint _anjay_dm_call_resource_write_attrs(\n        anjay_unlocked_t *anjay,\n        const anjay_dm_installed_object_t *obj_ptr,\n        anjay_iid_t iid,\n        anjay_rid_t rid,\n        anjay_ssid_t ssid,\n        const anjay_dm_r_attributes_t *attrs);\n#ifdef ANJAY_WITH_LWM2M11\nint _anjay_dm_call_resource_instance_read_attrs(\n        anjay_unlocked_t *anjay,\n        const anjay_dm_installed_object_t *obj_ptr,\n        anjay_iid_t iid,\n        anjay_rid_t rid,\n        anjay_riid_t riid,\n        anjay_ssid_t ssid,\n        anjay_dm_r_attributes_t *out);\nint _anjay_dm_call_resource_instance_write_attrs(\n        anjay_unlocked_t *anjay,\n        const anjay_dm_installed_object_t *obj_ptr,\n        anjay_iid_t iid,\n        anjay_rid_t rid,\n        anjay_riid_t riid,\n        anjay_ssid_t ssid,\n        const anjay_dm_r_attributes_t *attrs);\n#    ifdef ANJAY_WITH_LWM2M12\nint _anjay_dm_call_resource_instance_remove(\n        anjay_unlocked_t *anjay,\n        const anjay_dm_installed_object_t *obj_ptr,\n        anjay_iid_t iid,\n        anjay_rid_t rid,\n        anjay_riid_t riid);\n#    endif // ANJAY_WITH_LWM2M12\n#endif     // ANJAY_WITH_LWM2M11\n\nint _anjay_dm_call_transaction_begin(\n        anjay_unlocked_t *anjay, const anjay_dm_installed_object_t *obj_ptr);\nint _anjay_dm_call_transaction_validate(\n        anjay_unlocked_t *anjay, const anjay_dm_installed_object_t *obj_ptr);\nint _anjay_dm_call_transaction_commit(\n        anjay_unlocked_t *anjay, const anjay_dm_installed_object_t *obj_ptr);\nint _anjay_dm_call_transaction_rollback(\n        anjay_unlocked_t *anjay, const anjay_dm_installed_object_t *obj_ptr);\n\n/**\n * Starts a transaction on the data model. If a transaction is already in\n * progress, it has nesting semantics.\n *\n * @param anjay ANJAY object to operate on.\n */\navs_error_t _anjay_dm_transaction_begin(anjay_unlocked_t *anjay);\n\n/**\n * Includes a given object in transaction, calling its <c>transaction_begin</c>\n * handler if not already called during the current global transaction.\n *\n * During the outermost call to @ref _anjay_dm_transaction_finish, the\n * <c>transaction_commit</c> (preceded by <c>transaction_validate</c>) or\n * <c>transaction_rollback</c> handler will be called on all objects included\n * in this way.\n *\n * This function is automatically called by @ref _anjay_dm_call_instance_reset,\n * @ref _anjay_dm_call_instance_create, @ref _anjay_dm_call_instance_remove and\n * @ref _anjay_dm_read_resource .\n *\n * NOTE: Attempting to call this function without a global transaction in place\n * will cause an assertion fail.\n *\n * @param anjay   ANJAY object to operate on.\n * @param obj_ptr Handle to an object to include in transaction.\n *\n * @return 0 for success, or an error code.\n */\nint _anjay_dm_transaction_include_object(\n        anjay_unlocked_t *anjay, const anjay_dm_installed_object_t *obj_ptr);\n\n/**\n * After having been called a number of times corresponding to number of\n * preceding calls to @ref _anjay_dm_transaction_begin, finishes the transaction\n * by performing either a commit or a rollback, depending on the value of the\n * <c>result</c> parameter.\n *\n * @param anjay  ANJAY object to operate on.\n * @param result Result code from the operations performed during the\n *               transaction. 0 denotes a success and causes the transaction to\n *               be committed. Non-zero value is treated as an error code and\n *               causes the transaction to be rolled back.\n *\n * @return Final result code of the transaction. If an error occurred during the\n *         transaction handling routines (e.g. the transaction did not\n *         validate), a nonzero error code from those routines is returned.\n *         Otherwise, <c>result</c> is propagated. Note that it means that\n *         <c>0</c> is returned only after a successful commit following a\n *         successful transaction (denoted by <c>result == 0</c>).\n */\nint _anjay_dm_transaction_finish(anjay_unlocked_t *anjay, int result);\n\nbool _anjay_dm_transaction_object_included(\n        anjay_unlocked_t *anjay, const anjay_dm_installed_object_t *obj_ptr);\n\nconst anjay_dm_installed_object_t *\n_anjay_dm_find_object_by_oid(const anjay_dm_t *dm, anjay_oid_t oid);\n\nbool _anjay_dm_ssid_exists(anjay_unlocked_t *anjay, anjay_ssid_t ssid);\n\nint _anjay_ssid_from_security_iid(anjay_unlocked_t *anjay,\n                                  anjay_iid_t security_iid,\n                                  uint16_t *out_ssid);\n\n#ifdef ANJAY_WITH_LWM2M11\nint _anjay_server_uri_from_security_iid(anjay_unlocked_t *anjay,\n                                        anjay_iid_t security_iid,\n                                        char *out_uri,\n                                        size_t out_size);\n#endif // ANJAY_WITH_LWM2M11\n\nbool _anjay_dm_attributes_empty(const anjay_dm_oi_attributes_t *attrs);\nbool _anjay_dm_resource_attributes_empty(const anjay_dm_r_attributes_t *attrs);\n\nbool _anjay_dm_attributes_full(const anjay_dm_oi_attributes_t *attrs);\nbool _anjay_dm_resource_attributes_full(const anjay_dm_r_attributes_t *attrs);\n\nint _anjay_dm_verify_resource_present(anjay_unlocked_t *anjay,\n                                      const anjay_dm_installed_object_t *obj,\n                                      anjay_iid_t iid,\n                                      anjay_rid_t rid,\n                                      anjay_dm_resource_kind_t *out_kind);\n\nint _anjay_dm_verify_resource_instance_present(\n        anjay_unlocked_t *anjay,\n        const anjay_dm_installed_object_t *obj,\n        anjay_iid_t iid,\n        anjay_rid_t rid,\n        anjay_riid_t riid);\n\nint _anjay_dm_verify_instance_present(\n        anjay_unlocked_t *anjay,\n        const anjay_dm_installed_object_t *obj_ptr,\n        anjay_iid_t iid);\n\n#define ANJAY_DM_RID_SECURITY_SERVER_URI 0\n#define ANJAY_DM_RID_SECURITY_BOOTSTRAP 1\n#define ANJAY_DM_RID_SECURITY_MODE 2\n#define ANJAY_DM_RID_SECURITY_PK_OR_IDENTITY 3\n#define ANJAY_DM_RID_SECURITY_SERVER_PK_OR_IDENTITY 4\n#define ANJAY_DM_RID_SECURITY_SECRET_KEY 5\n#define ANJAY_DM_RID_SECURITY_SSID 10\n#define ANJAY_DM_RID_SECURITY_CLIENT_HOLD_OFF_TIME 11\n#define ANJAY_DM_RID_SECURITY_BOOTSTRAP_TIMEOUT 12\n#ifdef ANJAY_WITH_LWM2M11\n#    define ANJAY_DM_RID_SECURITY_MATCHING_TYPE 13\n#    define ANJAY_DM_RID_SECURITY_SNI 14\n#    define ANJAY_DM_RID_SECURITY_CERTIFICATE_USAGE 15\n#    define ANJAY_DM_RID_SECURITY_DTLS_TLS_CIPHERSUITE 16\n#endif // ANJAY_WITH_LWM2M11\n\n#define ANJAY_DM_RID_SERVER_SSID 0\n#define ANJAY_DM_RID_SERVER_LIFETIME 1\n#define ANJAY_DM_RID_SERVER_DEFAULT_PMIN 2\n#define ANJAY_DM_RID_SERVER_DEFAULT_PMAX 3\n#define ANJAY_DM_RID_SERVER_DISABLE 4\n#define ANJAY_DM_RID_SERVER_DISABLE_TIMEOUT 5\n#define ANJAY_DM_RID_SERVER_NOTIFICATION_STORING 6\n#define ANJAY_DM_RID_SERVER_BINDING 7\n#ifdef ANJAY_WITH_LWM2M11\n#    define ANJAY_DM_RID_SERVER_TLS_DTLS_ALERT_CODE 11\n#    define ANJAY_DM_RID_SERVER_LAST_BOOTSTRAPPED 12\n#    define ANJAY_DM_RID_SERVER_BOOTSTRAP_ON_REGISTRATION_FAILURE 16\n#    define ANJAY_DM_RID_SERVER_COMMUNICATION_RETRY_COUNT 17\n#    define ANJAY_DM_RID_SERVER_COMMUNICATION_RETRY_TIMER 18\n#    define ANJAY_DM_RID_SERVER_COMMUNICATION_SEQUENCE_DELAY_TIMER 19\n#    define ANJAY_DM_RID_SERVER_COMMUNICATION_SEQUENCE_RETRY_COUNT 20\n#    define ANJAY_DM_RID_SERVER_PREFERRED_TRANSPORT 22\n#    define ANJAY_DM_RID_SERVER_MUTE_SEND 23\n#endif // ANJAY_WITH_LWM2M11\n\n#define ANJAY_DM_RID_ACCESS_CONTROL_OID 0\n#define ANJAY_DM_RID_ACCESS_CONTROL_OIID 1\n#define ANJAY_DM_RID_ACCESS_CONTROL_ACL 2\n#define ANJAY_DM_RID_ACCESS_CONTROL_OWNER 3\n\n#define ANJAY_DM_RID_DEVICE_FIRMWARE_VERSION 3\n#define ANJAY_DM_RID_DEVICE_SOFTWARE_VERSION 19\n\n#ifdef ANJAY_WITH_THREAD_SAFETY\n\nanjay_oid_t\n_anjay_dm_installed_object_oid(const anjay_dm_installed_object_t *obj);\n\nconst char *\n_anjay_dm_installed_object_version(const anjay_dm_installed_object_t *obj);\n\n#else // ANJAY_WITH_THREAD_SAFETY\n\nstatic inline anjay_oid_t\n_anjay_dm_installed_object_oid(const anjay_dm_installed_object_t *obj) {\n    assert(obj);\n    assert(*obj);\n    assert(**obj);\n    return (**obj)->oid;\n}\n\nstatic inline const char *\n_anjay_dm_installed_object_version(const anjay_dm_installed_object_t *obj) {\n    assert(obj);\n    assert(*obj);\n    assert(**obj);\n    return (**obj)->version;\n}\n\n#endif // ANJAY_WITH_THREAD_SAFETY\n\nAVS_LIST(anjay_dm_installed_object_t) *\n_anjay_find_and_verify_object_to_unregister(\n        anjay_dm_t *dm, const anjay_dm_object_def_t *const *def_ptr);\n\nvoid _anjay_unregister_object_handle_transaction_state(\n        anjay_unlocked_t *anjay, const anjay_dm_installed_object_t *def_ptr);\n\nvoid _anjay_unregister_object_handle_notify_queue(\n        anjay_unlocked_t *anjay, const anjay_dm_installed_object_t *def_ptr);\n\nAVS_LIST(anjay_dm_installed_object_t) _anjay_prepare_user_provided_object(\n        const anjay_dm_object_def_t *const *def_ptr);\n\nint _anjay_dm_register_object(\n        anjay_dm_t *dm, AVS_LIST(anjay_dm_installed_object_t) *elem_ptr_move);\n\n#ifdef ANJAY_WITH_LWM2M12\nvoid _anjay_dm_check_implemented_handlers(\n        anjay_unlocked_t *anjay, const anjay_dm_installed_object_t *obj);\n#endif // ANJAY_WITH_LWM2M12\n\nint _anjay_register_object_unlocked(\n        anjay_unlocked_t *anjay,\n        AVS_LIST(anjay_dm_installed_object_t) *elem_ptr_move);\n\n#ifdef ANJAY_WITH_SEND\n\nint _anjay_send_batch_data_add_current_multiple_unlocked(\n        anjay_send_batch_builder_t *builder,\n        anjay_unlocked_t *anjay,\n        anjay_iid_t gateway_iid,\n        const anjay_send_resource_path_t *paths,\n        size_t paths_length,\n        bool ignore_not_found);\n\nanjay_send_result_t\n_anjay_send_deferrable_unlocked(anjay_unlocked_t *anjay,\n                                anjay_ssid_t ssid,\n                                const anjay_send_batch_t *data,\n                                anjay_send_finished_handler_t *finished_handler,\n                                void *finished_handler_data);\n#endif // ANJAY_WITH_SEND\n\nvoid _anjay_dm_emit_unlocked(anjay_unlocked_dm_list_ctx_t *ctx, uint16_t id);\n\nvoid _anjay_dm_emit_res_unlocked(anjay_unlocked_dm_resource_list_ctx_t *ctx,\n                                 anjay_rid_t rid,\n                                 anjay_dm_resource_kind_t kind,\n                                 anjay_dm_resource_presence_t presence);\n\nanjay_unlocked_ret_bytes_ctx_t *\n_anjay_ret_bytes_begin_unlocked(anjay_unlocked_output_ctx_t *ctx,\n                                size_t length);\n\nint _anjay_ret_bytes_append_unlocked(anjay_unlocked_ret_bytes_ctx_t *ctx,\n                                     const void *data,\n                                     size_t length);\n\nint _anjay_ret_bytes_unlocked(anjay_unlocked_output_ctx_t *ctx,\n                              const void *data,\n                              size_t length);\n\nint _anjay_ret_string_unlocked(anjay_unlocked_output_ctx_t *ctx,\n                               const char *value);\n\nint _anjay_ret_i64_unlocked(anjay_unlocked_output_ctx_t *ctx, int64_t value);\n\nint _anjay_ret_double_unlocked(anjay_unlocked_output_ctx_t *ctx, double value);\n\nint _anjay_ret_bool_unlocked(anjay_unlocked_output_ctx_t *ctx, bool value);\n\nint _anjay_ret_objlnk_unlocked(anjay_unlocked_output_ctx_t *ctx,\n                               anjay_oid_t oid,\n                               anjay_iid_t iid);\n\n#ifdef ANJAY_WITH_LWM2M11\nint _anjay_ret_u64_unlocked(anjay_unlocked_output_ctx_t *ctx, uint64_t value);\n#endif // ANJAY_WITH_LWM2M11\n\n#if defined(ANJAY_WITH_SECURITY_STRUCTURED)\nint _anjay_ret_security_info_unlocked(\n        anjay_unlocked_output_ctx_t *ctx,\n        const avs_crypto_security_info_union_t *desc);\n#endif /* defined(ANJAY_WITH_SECURITY_STRUCTURED) || \\\n          defined(ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT) */\n\nint _anjay_get_bytes_unlocked(anjay_unlocked_input_ctx_t *ctx,\n                              size_t *out_bytes_read,\n                              bool *out_message_finished,\n                              void *out_buf,\n                              size_t buf_size);\n\nint _anjay_get_string_unlocked(anjay_unlocked_input_ctx_t *ctx,\n                               char *out_buf,\n                               size_t buf_size);\n\nint _anjay_get_i32_unlocked(anjay_unlocked_input_ctx_t *ctx, int32_t *out);\n\nint _anjay_get_i64_unlocked(anjay_unlocked_input_ctx_t *ctx, int64_t *out);\n\n#ifdef ANJAY_WITH_LWM2M11\nint _anjay_get_u32_unlocked(anjay_unlocked_input_ctx_t *ctx, uint32_t *out);\n\nint _anjay_get_u64_unlocked(anjay_unlocked_input_ctx_t *ctx, uint64_t *out);\n#endif // ANJAY_WITH_LWM2M11\n\nint _anjay_get_double_unlocked(anjay_unlocked_input_ctx_t *ctx, double *out);\n\nint _anjay_get_bool_unlocked(anjay_unlocked_input_ctx_t *ctx, bool *out);\n\nint _anjay_get_objlnk_unlocked(anjay_unlocked_input_ctx_t *ctx,\n                               anjay_oid_t *out_oid,\n                               anjay_iid_t *out_iid);\n\nint _anjay_execute_get_next_arg_unlocked(anjay_unlocked_execute_ctx_t *ctx,\n                                         int *out_arg,\n                                         bool *out_has_value);\n\nint _anjay_execute_get_arg_value_unlocked(anjay_unlocked_execute_ctx_t *ctx,\n                                          size_t *out_bytes_read,\n                                          char *out_buf,\n                                          size_t buf_size);\n\nanjay_dm_t *_anjay_get_dm(anjay_unlocked_t *anjay);\n\n#ifdef ANJAY_WITH_LWM2M_GATEWAY\n#    define DM_LOG_PREFIX \"%s%s\"\n#    define DM_LOG_PREFIX_ARG(Prefix)               \\\n        ((Prefix && Prefix[0] != '\\0') ? \"/\" : \"\"), \\\n                ((Prefix && Prefix[0] != '\\0') ? Prefix : \"\"),\n#    define DM_LOG_PREFIX_OBJ_ARG(Obj)                                       \\\n        (((Obj)->prefix && (Obj)->prefix[0] != '\\0') ? \"/\" : \"\"),            \\\n                (((Obj)->prefix && (Obj)->prefix[0] != '\\0') ? (Obj)->prefix \\\n                                                             : \"\"),\n#else\n#    define DM_LOG_PREFIX\n#    define DM_LOG_PREFIX_ARG(Prefix)\n#    define DM_LOG_PREFIX_OBJ_ARG(Obj)\n#endif\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif /* ANJAY_INCLUDE_ANJAY_MODULES_DM_H */\n"
  },
  {
    "path": "src/anjay_modules/anjay_io_utils.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ANJAY_INCLUDE_ANJAY_MODULES_IO_UTILS_H\n#define ANJAY_INCLUDE_ANJAY_MODULES_IO_UTILS_H\n\n#include <stdint.h>\n\n#include <avsystem/commons/avs_stream.h>\n\n#include <anjay/io.h>\n\n#include <anjay_modules/anjay_dm_utils.h>\n#include <anjay_modules/anjay_raw_buffer.h>\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\ntypedef int anjay_input_ctx_constructor_t(anjay_unlocked_input_ctx_t **out,\n                                          avs_stream_t *stream_ptr,\n                                          const anjay_uri_path_t *request_uri);\n\nanjay_input_ctx_constructor_t _anjay_input_opaque_create;\n\n#ifndef ANJAY_WITHOUT_PLAINTEXT\nanjay_input_ctx_constructor_t _anjay_input_text_create;\n#endif // ANJAY_WITHOUT_PLAINTEXT\n\n#ifndef ANJAY_WITHOUT_TLV\nanjay_input_ctx_constructor_t _anjay_input_tlv_create;\n#endif // ANJAY_WITHOUT_TLV\n\n#ifdef ANJAY_WITH_CBOR\nanjay_input_ctx_constructor_t _anjay_input_cbor_create;\nanjay_input_ctx_constructor_t _anjay_input_senml_cbor_create;\nanjay_input_ctx_constructor_t _anjay_input_senml_cbor_composite_read_create;\n\n#    ifdef ANJAY_WITH_LWM2M12\nanjay_input_ctx_constructor_t _anjay_input_lwm2m_cbor_create;\n#    endif // ANJAY_WITH_LWM2M12\n#endif     // ANJAY_WITH_CBOR\n\n#ifdef ANJAY_WITH_SENML_JSON\nanjay_input_ctx_constructor_t _anjay_input_json_create;\nanjay_input_ctx_constructor_t _anjay_input_json_composite_read_create;\n#endif // ANJAY_WITH_SENML_JSON\n\nint _anjay_input_ctx_destroy(anjay_unlocked_input_ctx_t **ctx_ptr);\n\n/**\n * Fetches bytes from @p ctx. On success it frees underlying @p buffer storage\n * via @p _anjay_sec_raw_buffer_clear and reinitializes @p buffer properly with\n * obtained data.\n */\nint _anjay_io_fetch_bytes(anjay_unlocked_input_ctx_t *ctx,\n                          anjay_raw_buffer_t *buffer);\n\n/**\n * Fetches string from @p ctx. It calls avs_free() on @p *out and, on success,\n * reinitializes @p *out properly with a pointer to (heap allocated) obtained\n * data.\n */\nint _anjay_io_fetch_string(anjay_unlocked_input_ctx_t *ctx, char **out);\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif /* ANJAY_INCLUDE_ANJAY_MODULES_IO_UTILS_H */\n"
  },
  {
    "path": "src/anjay_modules/anjay_lwm2m_gateway.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ANJAY_INCLUDE_ANJAY_MODULES_LWM2M_GATEWAY_H\n#define ANJAY_INCLUDE_ANJAY_MODULES_LWM2M_GATEWAY_H\n\n#include \"anjay_init.h\"\n\n#include <anjay_modules/anjay_attr_storage_utils.h>\n#include <anjay_modules/anjay_dm_utils.h>\n#include <anjay_modules/anjay_utils_core.h>\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\n#ifdef ANJAY_WITH_LWM2M_GATEWAY\n\n/**\n * Maps URI prefix to target End Device Data Model\n *\n * @param      anjay   Anjay object to operate on\n * @param      prefix  DM prefix from the LwM2M request\n * @param[out] dm      End Device DM. If prefix is found in the Gateway scope,\n *                     this pointer is set to a proper DM, set to NULL otherwise\n *\n * @returns 0 on success, or a negative value in case of error.\n */\nint _anjay_lwm2m_gateway_prefix_to_dm(anjay_unlocked_t *anjay,\n                                      const char *prefix,\n                                      const anjay_dm_t **dm);\n\n#    ifdef ANJAY_WITH_ATTR_STORAGE\n/**\n * Maps URI prefix to target End Device Attribute Storage\n *\n * @param      anjay   Anjay object to operate on\n * @param      prefix  DM prefix from the LwM2M request\n * @param[out] as      End Device Attribute Storage. If prefix is found in the\n *                     Gateway scope, this pointer is set to a proper AS,\n *                     set to NULL otherwise\n *\n * @returns 0 on success, or a negative value in case of error.\n */\nint _anjay_lwm2m_gateway_prefix_to_as(anjay_unlocked_t *anjay,\n                                      const char *prefix,\n                                      anjay_attr_storage_t **as);\n#    endif // ANJAY_WITH_ATTR_STORAGE\n\n/**\n * Maps Instance ID to target End Device Data Model\n *\n * @param      anjay  Anjay object to operate on\n * @param      iid    End Device Instance ID\n * @param[out] dm     End Device DM. If prefix is found in the Gateway scope,\n *                    this pointer is set to a proper DM, set to NULL otherwise\n */\nvoid _anjay_lwm2m_gateway_iid_to_dm(anjay_unlocked_t *anjay,\n                                    anjay_iid_t iid,\n                                    const anjay_dm_t **dm);\n\n#endif // ANJAY_WITH_LWM2M_GATEWAY\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif // ANJAY_INCLUDE_ANJAY_MODULES_LWM2M_GATEWAY_H\n"
  },
  {
    "path": "src/anjay_modules/anjay_notify.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ANJAY_INCLUDE_ANJAY_NOTIFY_IO_H\n#define ANJAY_INCLUDE_ANJAY_NOTIFY_IO_H\n\n#include <stdbool.h>\n\n#include <anjay_modules/anjay_utils_core.h>\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\ntypedef struct {\n    bool instance_set_changed;\n    // NOTE: known_added_iids list may not be exhaustive\n    AVS_LIST(anjay_iid_t) known_added_iids;\n} anjay_notify_queue_instance_entry_t;\n\ntypedef struct {\n    anjay_iid_t iid;\n    anjay_rid_t rid;\n} anjay_notify_queue_resource_entry_t;\n\ntypedef struct {\n#ifdef ANJAY_WITH_LWM2M_GATEWAY\n    char prefix[ANJAY_GATEWAY_MAX_PREFIX_LEN];\n#endif // ANJAY_WITH_LWM2M_GATEWAY\n    anjay_oid_t oid;\n    anjay_notify_queue_instance_entry_t instance_set_changes;\n    AVS_LIST(anjay_notify_queue_resource_entry_t) resources_changed;\n} anjay_notify_queue_object_entry_t;\n\ntypedef AVS_LIST(anjay_notify_queue_object_entry_t) anjay_notify_queue_t;\n\n/**\n * Performs all the actions necessary due to all the changes in the data model\n * specified by the <c>queue_ptr</c> parameter.\n */\nint _anjay_notify_perform(anjay_unlocked_t *anjay,\n                          anjay_ssid_t origin_ssid,\n                          anjay_notify_queue_t *queue_ptr);\n\n/**\n * Works like @ref _anjay_notify_perform but doesn't call\n * server_modified_notify().\n */\nint _anjay_notify_perform_without_servers(anjay_unlocked_t *anjay,\n                                          anjay_ssid_t origin_ssid,\n                                          anjay_notify_queue_t *queue_ptr);\n\n/**\n * Calls @ref _anjay_notify_perform and @ref _anjay_notify_clear_queue\n * afterwards (regardless of success or failure).\n */\nint _anjay_notify_flush(anjay_unlocked_t *anjay,\n                        anjay_ssid_t origin_ssid,\n                        anjay_notify_queue_t *queue_ptr);\n\nint _anjay_notify_queue_instance_created(anjay_notify_queue_t *out_queue,\n                                         const anjay_uri_path_t *path);\n\nint _anjay_notify_queue_instance_removed(anjay_notify_queue_t *out_queue,\n                                         const anjay_uri_path_t *path);\n\nint _anjay_notify_queue_instance_set_unknown_change(\n        anjay_notify_queue_t *out_queue, const anjay_uri_path_t *path);\n\n/**\n * Adds a notification about the change of value of the data model resource\n * specified by <c>path</c>.\n */\nint _anjay_notify_queue_resource_change(anjay_notify_queue_t *out_queue,\n                                        const anjay_uri_path_t *path);\n\nvoid _anjay_notify_clear_queue(anjay_notify_queue_t *out_queue);\n\nint _anjay_notify_instance_created(anjay_unlocked_t *anjay,\n                                   anjay_oid_t oid,\n                                   anjay_iid_t iid);\n\nint _anjay_notify_changed_unlocked(anjay_unlocked_t *anjay,\n                                   anjay_oid_t oid,\n                                   anjay_iid_t iid,\n                                   anjay_rid_t rid);\n\n#ifdef ANJAY_WITH_LWM2M_GATEWAY\nint _anjay_notify_changed_gw_unlocked(anjay_unlocked_t *anjay,\n                                      const char *prefix,\n                                      anjay_oid_t oid,\n                                      anjay_iid_t iid,\n                                      anjay_rid_t rid);\n\nint _anjay_notify_instances_changed_gw_unlocked(anjay_unlocked_t *anjay,\n                                                const char *prefix,\n                                                anjay_oid_t oid);\n#endif // ANJAY_WITH_LWM2M_GATEWAY\n\nint _anjay_notify_instances_changed_unlocked(anjay_unlocked_t *anjay,\n                                             anjay_oid_t oid);\n\ntypedef int anjay_notify_callback_t(anjay_unlocked_t *anjay,\n                                    anjay_notify_queue_t queue,\n                                    void *data);\n\n#ifdef ANJAY_WITH_OBSERVATION_STATUS\nvoid _anjay_notify_observation_status_impl_unlocked(\n        anjay_unlocked_t *anjay,\n        anjay_resource_observation_status_t *status,\n        const char *prefix,\n        anjay_oid_t oid,\n        anjay_iid_t iid,\n        anjay_rid_t rid);\n#endif // ANJAY_WITH_OBSERVATION_STATUS\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif /* ANJAY_INCLUDE_ANJAY_NOTIFY_IO_H */\n"
  },
  {
    "path": "src/anjay_modules/anjay_raw_buffer.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ANJAY_INCLUDE_ANJAY_MODULES_RAW_BUFFER_H\n#define ANJAY_INCLUDE_ANJAY_MODULES_RAW_BUFFER_H\n\n#include <anjay_init.h>\n\n#include <stdio.h>\n\n#include <avsystem/commons/avs_defs.h>\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\ntypedef struct {\n    void *data;\n    /** Amount of bytes currently stored in the buffer. */\n    size_t size;\n    /** Amount of bytes that might be stored in the buffer. */\n    size_t capacity;\n} anjay_raw_buffer_t;\n\n#define ANJAY_RAW_BUFFER_ON_STACK(Capacity)   \\\n    (anjay_raw_buffer_t) {                    \\\n        .data = &(uint8_t[Capacity]){ 0 }[0], \\\n        .size = 0,                            \\\n        .capacity = Capacity                  \\\n    }\n\n#define ANJAY_RAW_BUFFER_EMPTY \\\n    (anjay_raw_buffer_t) {     \\\n        .data = NULL,          \\\n        .size = 0,             \\\n        .capacity = 0          \\\n    }\n\n/**\n * Calls avs_free() on buffer->data and resets its state by setting buffer->data\n * to NULL and buffer->size to 0 and buffer->capacity to 0.\n *\n * WARNING: do not call this function if @ref anjay_raw_buffer_t was created\n * on the stack via @ref ANJAY_RAW_BUFFER_ON_STACK macro.\n */\nvoid _anjay_raw_buffer_clear(anjay_raw_buffer_t *buffer);\n\n/**\n * Copies data from src->data to dst->data and updates size and capacity\n * accordingly.\n *\n * @returns 0 on success, negative value if no memory is available\n */\nint _anjay_raw_buffer_clone(anjay_raw_buffer_t *dst,\n                            const anjay_raw_buffer_t *src);\n\n/**\n * Creates heap raw buffer.\n * @returns 0 on success, negative value if no memory is available\n */\nint _anjay_raw_buffer_alloc(anjay_raw_buffer_t *dst, size_t capacity);\n\n/**\n * Creates heap raw buffer by copying data pointed by @p data.\n * @returns 0 on success, negative value if no memory is available\n */\nint _anjay_raw_buffer_from_data(anjay_raw_buffer_t *dst,\n                                const void *src,\n                                size_t size);\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif /* ANJAY_INCLUDE_ANJAY_MODULES_RAW_BUFFER_H */\n"
  },
  {
    "path": "src/anjay_modules/anjay_sched.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ANJAY_INCLUDE_ANJAY_MODULES_SCHED_H\n#define ANJAY_INCLUDE_ANJAY_MODULES_SCHED_H\n\n#include <avsystem/commons/avs_sched.h>\n\n#include <anjay_modules/anjay_utils_core.h>\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\nstatic inline anjay_t *_anjay_get_from_sched(avs_sched_t *sched) {\n    return (anjay_t *) avs_sched_data(sched);\n}\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif /* ANJAY_INCLUDE_ANJAY_MODULES_SCHED_H */\n"
  },
  {
    "path": "src/anjay_modules/anjay_servers.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ANJAY_INCLUDE_ANJAY_MODULES_SERVERS_H\n#define ANJAY_INCLUDE_ANJAY_MODULES_SERVERS_H\n\n#include <anjay/core.h>\n\n#include <anjay_modules/anjay_utils_core.h>\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\ntypedef enum {\n    ANJAY_CONNECTION_UNSET = -1,\n    ANJAY_CONNECTION_PRIMARY = 0,\n    ANJAY_CONNECTION_LIMIT_\n} anjay_connection_type_t;\n\n#define ANJAY_CONNECTION_TYPE_FOREACH(Var)                                     \\\n    for ((Var) = (anjay_connection_type_t) 0; (Var) < ANJAY_CONNECTION_LIMIT_; \\\n         (Var) = (anjay_connection_type_t) ((Var) + 1))\n\n// inactive servers include administratively disabled ones\n// as well as those which were unreachable at connect attempt\nstruct anjay_server_info_struct;\ntypedef struct anjay_server_info_struct anjay_server_info_t;\n\ntypedef struct {\n    anjay_server_info_t *server;\n    anjay_connection_type_t conn_type;\n} anjay_connection_ref_t;\n\navs_coap_ctx_t *_anjay_connection_get_coap(anjay_connection_ref_t ref);\n\n/**\n * Reads security information (security mode, keys etc.) for a given Security\n * object instance. This is part of the servers subsystem because it reuses some\n * private code that is also used when refreshing server connections - namely,\n * connection_type_definition_t instances that query the data model for security\n * information, abstracting away the fact that UDP/TCP and SMS security\n * information is stored in different resources.\n *\n * It's currently only used in the Firmware Update module, to allow deriving the\n * security information from the data model when it's not explicitly specified.\n */\navs_error_t _anjay_get_security_config(anjay_unlocked_t *anjay,\n                                       anjay_security_config_t *out_config,\n                                       anjay_security_config_cache_t *cache,\n                                       anjay_ssid_t ssid,\n                                       anjay_iid_t security_iid);\n\n#if defined(ANJAY_WITH_LWM2M11)\nbool _anjay_bootstrap_server_exists(anjay_unlocked_t *anjay);\n#endif // defined(ANJAY_WITH_LWM2M11) || defined(ANJAY_WITH_EST)\n\n/**\n * Returns an active server object associated with given @p socket .\n */\nanjay_server_info_t *\n_anjay_servers_find_by_primary_socket(anjay_unlocked_t *anjay,\n                                      avs_net_socket_t *socket);\n\n/**\n * Reschedules Update for a specified server or all servers. In the very end,\n * it calls schedule_update(), which basically speeds up the scheduled Update\n * operation (it is normally scheduled for \"just before the lifetime expires\",\n * this function reschedules it to now. The scheduled job is\n * server_next_action_job() with the action set to\n * ANJAY_SERVER_NEXT_ACTION_REFRESH action, and it is also used for regular\n * Updates.\n *\n * Aside from being a public API, this is also called in:\n *\n * - anjay_register_object() and anjay_unregister_object(), to force an Update\n *   when the set of available Objects changed\n * - serv_execute(), as a default implementation of Registration Update Trigger\n * - server_modified_notify(), to force an Update whenever Lifetime or Binding\n *   change\n */\nint _anjay_schedule_registration_update_unlocked(anjay_unlocked_t *anjay,\n                                                 anjay_ssid_t ssid);\n\n/**\n * Basically the same as anjay_disable_server(), but with explicit timeout value\n * instead of reading it from the data model.\n *\n * Aside from being a public API, it is called from:\n *\n * - bootstrap_finish_impl(), to deactivate the Bootstrap Server connection if\n *   legacy Server-Initiated Bootstrap is disabled\n * - serv_execute(), as a reference implementation of the Disable resource\n * - _anjay_schedule_socket_update(), to force reconnection of all sockets\n */\nint _anjay_schedule_disable_server_with_explicit_timeout_unlocked(\n        anjay_unlocked_t *anjay,\n        anjay_ssid_t ssid,\n        avs_time_duration_t timeout);\n\n/**\n * Schedules server activation immediately, after some sanity checks.\n *\n * The activation request is rejected if someone tries to enable the Bootstrap\n * Server, Client-Initiated Bootstrap is not supposed to be performed, and\n * legacy Server-Initiated Bootstrap is administratively disabled.\n */\nint _anjay_enable_server_unlocked(anjay_unlocked_t *anjay, anjay_ssid_t ssid);\n\n#ifdef ANJAY_WITH_CONN_STATUS_API\n/**\n * Set suspending flag for the server specified by the ssid argument.\n */\nvoid _anjay_set_server_suspending_flag(anjay_unlocked_t *anjay,\n                                       anjay_ssid_t ssid,\n                                       bool val);\n#endif // ANJAY_WITH_CONN_STATUS_API\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif /* ANJAY_INCLUDE_ANJAY_MODULES_SERVERS_H */\n"
  },
  {
    "path": "src/anjay_modules/anjay_time_defs.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ANJAY_INCLUDE_ANJAY_MODULES_TIME_H\n#define ANJAY_INCLUDE_ANJAY_MODULES_TIME_H\n\n#include <assert.h>\n#include <stdint.h>\n\n#include <avsystem/commons/avs_time.h>\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\n#define NUM_NANOSECONDS_IN_A_SECOND (1L * 1000L * 1000L * 1000L)\n#define NUM_SECONDS_IN_A_DAY 86400\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif /* ANJAY_INCLUDE_ANJAY_MODULES_TIME_H */\n"
  },
  {
    "path": "src/anjay_modules/anjay_utils_core.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ANJAY_INCLUDE_ANJAY_MODULES_UTILS_CORE_H\n#define ANJAY_INCLUDE_ANJAY_MODULES_UTILS_CORE_H\n\n#ifdef ANJAY_WITH_EVENT_LOOP\n#    include <stdatomic.h>\n#endif // ANJAY_WITH_EVENT_LOOP\n\n#include <avsystem/commons/avs_list.h>\n#include <avsystem/commons/avs_url.h>\n\n#ifdef ANJAY_WITH_THREAD_SAFETY\n#    include <avsystem/commons/avs_mutex.h>\n#endif // ANJAY_WITH_THREAD_SAFETY\n\n#ifdef ANJAY_WITH_LOGS\n#    ifndef AVS_COMMONS_WITH_AVS_LOG\n#        error \"ANJAY_WITH_LOGS requires avs_log to be enabled\"\n#    endif\n// these macros interfere with avs_log() macro implementation\n#    ifdef TRACE\n#        undef TRACE\n#    endif\n#    ifdef DEBUG\n#        undef DEBUG\n#    endif\n#    ifdef INFO\n#        undef INFO\n#    endif\n#    ifdef WARNING\n#        undef WARNING\n#    endif\n#    ifdef ERROR\n#        undef ERROR\n#    endif\n#    include <avsystem/commons/avs_log.h>\n#    define _anjay_log(...) avs_log(__VA_ARGS__)\n#else\n#    include <stdio.h>\n#    define _anjay_log(Module, Level, ...) ((void) sizeof(printf(__VA_ARGS__)))\n#endif\n\n#include <anjay/core.h>\n#include <anjay/dm.h>\n\n#ifdef ANJAY_WITH_DOWNLOADER\n#    include <anjay/download.h>\n#endif // ANJAY_WITH_DOWNLOADER\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\n#ifdef ANJAY_WITH_EVENT_LOOP\ntypedef enum {\n    ANJAY_EVENT_LOOP_IDLE,\n    ANJAY_EVENT_LOOP_RUNNING,\n    ANJAY_EVENT_LOOP_INTERRUPT\n} anjay_event_loop_status_t;\n#endif // ANJAY_WITH_EVENT_LOOP\n\n// Please update this condition if anjay_atomic_fields_t ever gets more fields\n#if defined(ANJAY_WITH_EVENT_LOOP)\n#    define ANJAY_ATOMIC_FIELDS_DEFINED\n#endif // defined(ANJAY_WITH_EVENT_LOOP)\n\n#ifdef ANJAY_ATOMIC_FIELDS_DEFINED\ntypedef struct {\n#    ifdef ANJAY_WITH_EVENT_LOOP\n    volatile atomic_int event_loop_status;\n#    endif // ANJAY_WITH_EVENT_LOOP\n} anjay_atomic_fields_t;\n#endif // ANJAY_ATOMIC_FIELDS_DEFINED\n\n#ifdef ANJAY_WITH_THREAD_SAFETY\n\ntypedef struct anjay_unlocked_struct anjay_unlocked_t;\n\nstruct anjay_struct {\n    avs_mutex_t *mutex;\n#    ifdef ANJAY_ATOMIC_FIELDS_DEFINED\n    anjay_atomic_fields_t atomic_fields;\n#    endif // ANJAY_ATOMIC_FIELDS_DEFINED\n    avs_max_align_t anjay_unlocked_placeholder;\n};\n\nvoid _anjay_reschedule_coap_sched_job(anjay_unlocked_t *anjay);\n\n#    ifdef ANJAY_WITH_NESTED_FUNCTION_MUTEX_LOCKS\n\n// We are compiling on a reasonably recent version of GCC in Debug mode.\n// We are using GCC's nested function extension to make sure that there are\n// no \"return\" statements between the lock and unlock statements.\n// If the programmer attempts to use \"return\" there, they'll get at least\n// a warning saying that the return type is wrong, as the nested functions\n// return anjay_gcc_nested_function_retval_placeholder_t.\n\n#        pragma GCC diagnostic push\n#        pragma GCC diagnostic ignored \"-Wc++-compat\"\n#        pragma GCC diagnostic ignored \"-Wpedantic\"\ntypedef struct {\n} anjay_gcc_nested_function_retval_placeholder_t;\n#        pragma GCC diagnostic pop\n\n#        define ANJAY_MUTEX_LOCK(AnjayUnlockedVar, AnjayLockedVar)    \\\n            {                                                         \\\n                AVS_PRAGMA(GCC diagnostic push)                       \\\n                AVS_PRAGMA(GCC diagnostic ignored \"-Wpedantic\")       \\\n                AVS_PRAGMA(GCC diagnostic ignored \"-Wshadow\")         \\\n                inline anjay_gcc_nested_function_retval_placeholder_t \\\n                mutex_lock_nested_function(                           \\\n                        anjay_unlocked_t *AnjayUnlockedVar) {         \\\n                    AVS_PRAGMA(GCC diagnostic pop)                    \\\n                    (void) AnjayUnlockedVar\n\n#        define ANJAY_MUTEX_UNLOCK(AnjayLockedVar)                      \\\n            AVS_PRAGMA(GCC diagnostic push)                             \\\n            AVS_PRAGMA(GCC diagnostic ignored \"-Wpedantic\")             \\\n            return (anjay_gcc_nested_function_retval_placeholder_t) {}; \\\n            AVS_PRAGMA(GCC diagnostic pop)                              \\\n            }                                                           \\\n            if (!(AnjayLockedVar)                                       \\\n                    || avs_mutex_lock((AnjayLockedVar)->mutex)) {       \\\n                _anjay_log(anjay, ERROR, _(\"Could not lock mutex\"));    \\\n            } else {                                                    \\\n                mutex_lock_nested_function(                             \\\n                        (anjay_unlocked_t *) &(AnjayLockedVar)          \\\n                                ->anjay_unlocked_placeholder);          \\\n                _anjay_reschedule_coap_sched_job(                       \\\n                        (anjay_unlocked_t *) &(AnjayLockedVar)          \\\n                                ->anjay_unlocked_placeholder);          \\\n                avs_mutex_unlock((AnjayLockedVar)->mutex);              \\\n            }                                                           \\\n            }                                                           \\\n            (void) 0\n\n#        define ANJAY_MUTEX_UNLOCK_FOR_CALLBACK(AnjayLockedVar,            \\\n                                                AnjayUnlockedVar)          \\\n            {                                                              \\\n                AVS_PRAGMA(GCC diagnostic push)                            \\\n                AVS_PRAGMA(GCC diagnostic ignored \"-Wpedantic\")            \\\n                auto inline anjay_gcc_nested_function_retval_placeholder_t \\\n                mutex_unlock_for_callback_nested_function(anjay_t *);      \\\n                mutex_unlock_for_callback_nested_function(                 \\\n                        AVS_CONTAINER_OF(AnjayUnlockedVar,                 \\\n                                         anjay_t,                          \\\n                                         anjay_unlocked_placeholder));     \\\n                                                                           \\\n                inline anjay_gcc_nested_function_retval_placeholder_t      \\\n                mutex_unlock_for_callback_nested_function(                 \\\n                        anjay_t *AnjayLockedVar) {                         \\\n                    AVS_PRAGMA(GCC diagnostic pop)                         \\\n                    avs_mutex_unlock((AnjayLockedVar)->mutex)\n\n#        define ANJAY_MUTEX_LOCK_AFTER_CALLBACK(AnjayLockedVar)         \\\n            if (avs_mutex_lock(AnjayLockedVar->mutex)) {                \\\n                _anjay_log(anjay, ERROR, _(\"Could not lock mutex\"));    \\\n            }                                                           \\\n            AVS_PRAGMA(GCC diagnostic push)                             \\\n            AVS_PRAGMA(GCC diagnostic ignored \"-Wpedantic\")             \\\n            return (anjay_gcc_nested_function_retval_placeholder_t) {}; \\\n            AVS_PRAGMA(GCC diagnostic pop)                              \\\n            }                                                           \\\n            }                                                           \\\n            (void) 0\n\n#    else // ANJAY_WITH_NESTED_FUNCTION_MUTEX_LOCKS\n\n// We are either not compiling on GCC, or we are compiling in Release mode.\n// Use more conventional code - the lock and unlock clauses are still contain\n// stray { and } to make sure that they're paired properly.\n\n#        define ANJAY_MUTEX_LOCK(AnjayUnlockedVar, AnjayLockedVar)   \\\n            if (!(AnjayLockedVar)                                    \\\n                    || avs_mutex_lock((AnjayLockedVar)->mutex)) {    \\\n                _anjay_log(anjay, ERROR, _(\"Could not lock mutex\")); \\\n            } else {                                                 \\\n                anjay_unlocked_t *AnjayUnlockedVar =                 \\\n                        (anjay_unlocked_t *) &(AnjayLockedVar)       \\\n                                ->anjay_unlocked_placeholder;        \\\n                (void) AnjayUnlockedVar\n\n#        define ANJAY_MUTEX_UNLOCK(AnjayLockedVar)         \\\n            _anjay_reschedule_coap_sched_job(              \\\n                    (anjay_unlocked_t *) &(AnjayLockedVar) \\\n                            ->anjay_unlocked_placeholder); \\\n            avs_mutex_unlock((AnjayLockedVar)->mutex);     \\\n            }                                              \\\n            (void) 0\n\n#        define ANJAY_MUTEX_UNLOCK_FOR_CALLBACK(AnjayLockedVar,       \\\n                                                AnjayUnlockedVar)     \\\n            {                                                         \\\n                anjay_t *AnjayLockedVar =                             \\\n                        AVS_CONTAINER_OF(AnjayUnlockedVar,            \\\n                                         anjay_t,                     \\\n                                         anjay_unlocked_placeholder); \\\n                avs_mutex_unlock(AnjayLockedVar->mutex)\n\n#        define ANJAY_MUTEX_LOCK_AFTER_CALLBACK(AnjayLockedVar)      \\\n            if (avs_mutex_lock(AnjayLockedVar->mutex)) {             \\\n                _anjay_log(anjay, ERROR, _(\"Could not lock mutex\")); \\\n            }                                                        \\\n            }                                                        \\\n            (void) 0\n\n#    endif // ANJAY_WITH_NESTED_FUNCTION_MUTEX_LOCKS\n\n#else // ANJAY_WITH_THREAD_SAFETY\n\n// Thread safety is disabled - use basically no-op blocks, although still\n// containing stray { and } to make sure they're paired properly.\n\ntypedef anjay_t anjay_unlocked_t;\n\n#    define ANJAY_MUTEX_LOCK(AnjayUnlockedVar, AnjayLockedVar)     \\\n        if (!(AnjayLockedVar)) {                                   \\\n            _anjay_log(anjay, ERROR, _(\"Anjay pointer is NULL\"));  \\\n        } else {                                                   \\\n            anjay_unlocked_t *AnjayUnlockedVar = (AnjayLockedVar); \\\n            (void) AnjayUnlockedVar\n\n#    define ANJAY_MUTEX_UNLOCK(AnjayLockedVar) \\\n        }                                      \\\n        (void) 0\n\n#    define ANJAY_MUTEX_UNLOCK_FOR_CALLBACK(AnjayLockedVar, AnjayUnlockedVar) \\\n        {                                                                     \\\n            anjay_t *AnjayLockedVar = (AnjayUnlockedVar);                     \\\n            (void) AnjayLockedVar\n\n#    define ANJAY_MUTEX_LOCK_AFTER_CALLBACK(AnjayLockedVar) \\\n        }                                                   \\\n        (void) 0\n\n#endif // ANJAY_WITH_THREAD_SAFETY\n\ntypedef enum {\n    /**\n     * Given URI scheme does not imply any security configuration.\n     */\n    ANJAY_TRANSPORT_SECURITY_UNDEFINED,\n\n    /**\n     * Given URI scheme implies unencrypted communication (e.g. \"coap\", \"http\").\n     */\n    ANJAY_TRANSPORT_NOSEC,\n\n    /**\n     * Given URI scheme implies encrypted communication (e.g. \"coaps\", \"https\").\n     */\n    ANJAY_TRANSPORT_ENCRYPTED\n} anjay_transport_security_t;\n\n/** Set of properties of a transport-specific variant of CoAP. */\ntypedef struct anjay_transport_info {\n    /**\n     * CoAP URI scheme part, e.g. \"coap\"/\"coaps\"/\"coap+tcp\"/\"coaps+tcp\"\n     */\n    const char *uri_scheme;\n\n    /**\n     * Port to use for URIs that do not include one, usually 5683 or 5684\n     */\n    const char *default_port;\n\n    /**\n     * Underlying socket type, e.g. UDP/TCP\n     */\n    anjay_socket_transport_t transport;\n\n    /**\n     * Required avs_commons socket type, e.g. UDP/DTLS/TCP/SSL. NULL if a custom\n     * socket type (not creatable using\n     * avs_net_(tcp|udp|ssl|dtls)_socket_create()) is required.\n     */\n    const avs_net_socket_type_t *socket_type;\n\n    /**\n     * Security requirements related to uri_scheme.\n     */\n    anjay_transport_security_t security;\n} anjay_transport_info_t;\n\ntypedef struct anjay_string {\n    char c_str[1]; // actually a FAM, but a struct must not consist of FAM only\n} anjay_string_t;\n\n#define ANJAY_MAX_URL_RAW_LENGTH 256\n#define ANJAY_MAX_URL_HOSTNAME_SIZE \\\n    (ANJAY_MAX_URL_RAW_LENGTH       \\\n     - sizeof(\"coaps://\"            \\\n              \":0\"))\n#define ANJAY_MAX_URL_PORT_SIZE sizeof(\"65535\")\n\ntypedef struct anjay_url {\n    char host[ANJAY_MAX_URL_HOSTNAME_SIZE];\n    char port[ANJAY_MAX_URL_PORT_SIZE];\n    AVS_LIST(const anjay_string_t) uri_path;\n    AVS_LIST(const anjay_string_t) uri_query;\n} anjay_url_t;\n\n#define ANJAY_URL_EMPTY   \\\n    (anjay_url_t) {       \\\n        .uri_path = NULL, \\\n        .uri_query = NULL \\\n    }\n\n#define ANJAY_FOREACH_BREAK INT_MIN\n#define ANJAY_FOREACH_CONTINUE 0\n\nint _anjay_url_parse_path_and_query(const char *path,\n                                    AVS_LIST(const anjay_string_t) *out_path,\n                                    AVS_LIST(const anjay_string_t) *out_query);\n\nint _anjay_url_from_avs_url(const avs_url_t *avs_url,\n                            anjay_url_t *out_parsed_url);\n\n/**\n * Parses endpoint name into hostname, path and port number. Additionally\n * extracts Uri-Path and Uri-Query options as (unsecaped) strings.\n *\n * NOTE: @p out_parsed_url MUST be initialized with ANJAY_URL_EMPTY or otherwise\n * the behavior is undefined.\n */\nint _anjay_url_parse(const char *raw_url, anjay_url_t *out_parsed_url);\n\n/**\n * Frees any allocated memory by @ref _anjay_url_parse\n */\nvoid _anjay_url_cleanup(anjay_url_t *url);\n\ntypedef struct {\n    char data[8];\n} anjay_binding_mode_t;\n\nstatic inline void _anjay_update_ret(int *var, int new_retval) {\n    if (!*var) {\n        *var = new_retval;\n    }\n}\n\ntypedef struct anjay_binding_info {\n    char letter;\n    anjay_socket_transport_t transport;\n} anjay_binding_info_t;\n\nint _anjay_security_config_from_dm_unlocked(anjay_unlocked_t *anjay,\n                                            anjay_security_config_t *out_config,\n                                            const char *raw_url);\n\n#ifdef ANJAY_WITH_LWM2M11\ntypedef struct {\n    bool use_system_wide;\n    AVS_LIST(avs_crypto_certificate_chain_info_t) certs;\n    AVS_LIST(avs_crypto_cert_revocation_list_info_t) crls;\n} anjay_trust_store_t;\n\nconst anjay_trust_store_t *\n_anjay_get_trust_store(anjay_unlocked_t *anjay,\n                       anjay_ssid_t for_ssid,\n                       anjay_security_mode_t security_mode);\n\nanjay_security_config_t\n_anjay_security_config_pkix_unlocked(anjay_unlocked_t *anjay);\n#endif // ANJAY_WITH_LWM2M11\n\n#if defined(ANJAY_WITH_LWM2M11)\nconst anjay_binding_info_t *_anjay_binding_info_by_letter(char letter);\n#endif // defined(ANJAY_WITH_LWM2M11) || defined(ANJAY_WITH_CORE_PERSISTENCE)\n\nconst anjay_binding_info_t *\n_anjay_binding_info_by_transport(anjay_socket_transport_t transport);\n\nconst anjay_transport_info_t *\n_anjay_transport_info_by_uri_scheme(const char *uri_or_scheme);\n\nconst char *_anjay_default_port_by_url(const anjay_url_t *url);\n\nvoid _anjay_find_matching_coap_context_and_socket(\n        anjay_unlocked_t *anjay,\n        const char *raw_url,\n        avs_coap_ctx_t **out_coap,\n        avs_net_socket_t **out_socket);\n\ntypedef struct {\n    avs_crypto_psk_key_info_t *psk_key;\n    avs_crypto_psk_identity_info_t *psk_identity;\n    avs_crypto_certificate_chain_info_t *trusted_certs_array;\n    avs_crypto_cert_revocation_list_info_t *cert_revocation_lists_array;\n    avs_crypto_certificate_chain_info_t *client_cert_array;\n    avs_crypto_private_key_info_t *client_key;\n    avs_net_socket_dane_tlsa_record_t *dane_tlsa_record;\n    avs_net_socket_tls_ciphersuites_t ciphersuites;\n#ifdef ANJAY_WITH_LWM2M11\n    char server_name_indication[256];\n#endif\n} anjay_security_config_cache_t;\n\nvoid _anjay_security_config_cache_cleanup(anjay_security_config_cache_t *cache);\n\n#ifdef ANJAY_WITH_DOWNLOADER\navs_error_t _anjay_download_unlocked(anjay_unlocked_t *anjay,\n                                     const anjay_download_config_t *config,\n                                     anjay_download_handle_t *out_handle);\n\nvoid _anjay_download_abort_unlocked(anjay_unlocked_t *anjay,\n                                    anjay_download_handle_t handle);\n\nvoid _anjay_download_suspend_unlocked(anjay_unlocked_t *anjay,\n                                      anjay_download_handle_t dl_handle);\n\nint _anjay_download_reconnect_unlocked(anjay_unlocked_t *anjay,\n                                       anjay_download_handle_t dl_handle);\n#endif // ANJAY_WITH_DOWNLOADER\n\nbool _anjay_ongoing_registration_exists_unlocked(anjay_unlocked_t *anjay);\n\navs_sched_t *_anjay_get_scheduler_unlocked(anjay_unlocked_t *anjay);\n\nvoid _anjay_log_oom(void);\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif /* ANJAY_INCLUDE_ANJAY_MODULES_UTILS_CORE_H */\n"
  },
  {
    "path": "src/anjay_modules/dm/anjay_execute.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ANJAY_INCLUDE_ANJAY_MODULES_DM_EXECUTE_H\n#define ANJAY_INCLUDE_ANJAY_MODULES_DM_EXECUTE_H\n\n#include <anjay/io.h>\n\n#include <anjay_modules/dm/anjay_modules.h>\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\nanjay_unlocked_execute_ctx_t *\n_anjay_execute_ctx_create(avs_stream_t *payload_stream);\nvoid _anjay_execute_ctx_destroy(anjay_unlocked_execute_ctx_t **ctx);\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif /* ANJAY_INCLUDE_ANJAY_MODULES_DM_EXECUTE_H */\n"
  },
  {
    "path": "src/anjay_modules/dm/anjay_modules.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ANJAY_INCLUDE_ANJAY_MODULES_DM_MODULES_H\n#define ANJAY_INCLUDE_ANJAY_MODULES_DM_MODULES_H\n\n#include <avsystem/commons/avs_defs.h>\n\n#include <anjay/dm.h>\n\n#include <anjay_modules/anjay_notify.h>\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\n#ifdef ANJAY_WITH_THREAD_SAFETY\n\ntypedef struct anjay_unlocked_dm_object_def_struct\n        anjay_unlocked_dm_object_def_t;\n\ntypedef struct anjay_unlocked_dm_list_ctx_struct anjay_unlocked_dm_list_ctx_t;\ntypedef struct anjay_unlocked_dm_resource_list_ctx_struct\n        anjay_unlocked_dm_resource_list_ctx_t;\ntypedef struct anjay_unlocked_output_ctx_struct anjay_unlocked_output_ctx_t;\ntypedef struct anjay_unlocked_ret_bytes_ctx_struct\n        anjay_unlocked_ret_bytes_ctx_t;\ntypedef struct anjay_unlocked_input_ctx_struct anjay_unlocked_input_ctx_t;\ntypedef struct anjay_unlocked_execute_ctx_struct anjay_unlocked_execute_ctx_t;\n\ntypedef enum {\n    ANJAY_DM_OBJECT_USER_PROVIDED,\n    ANJAY_DM_OBJECT_UNLOCKED\n} anjay_dm_installed_object_type_t;\n\ntypedef struct {\n    anjay_dm_installed_object_type_t type;\n    union {\n        const anjay_dm_object_def_t *const *user_provided;\n        const anjay_unlocked_dm_object_def_t *const *unlocked;\n    } impl;\n#    ifdef ANJAY_WITH_LWM2M_GATEWAY\n    /*\n     * Prefix that helps to identify which DM is the object installed in.\n     * It is set to NULL by default in _anjay_register_object_unlocked()\n     * and set to a specific prefix only in lwm2m_gateway implementation,\n     * anjay_lwm2m_gateway_register_object() function. The link is necessary\n     * for uri_path_outside_base() checkers. Link to parent DM might make more\n     * sense, but ultimately we just need to compare paths that include prefix.\n     *\n     * Also it's not currently possible to include anjay_dm_t * reference here.\n     */\n    const char *prefix;\n#    endif // ANJAY_WITH_LWM2M_GATEWAY\n} anjay_dm_installed_object_t;\n\nstatic inline void _anjay_dm_installed_object_init_unlocked(\n        anjay_dm_installed_object_t *obj_ptr,\n        const anjay_unlocked_dm_object_def_t *const *def_ptr) {\n    assert(obj_ptr);\n    assert(!obj_ptr->impl.unlocked);\n    obj_ptr->type = ANJAY_DM_OBJECT_UNLOCKED;\n    obj_ptr->impl.unlocked = def_ptr;\n}\n\nstatic inline const anjay_unlocked_dm_object_def_t *const *\n_anjay_dm_installed_object_get_unlocked(\n        const anjay_dm_installed_object_t *obj_ptr) {\n    assert(obj_ptr);\n    assert(obj_ptr->type == ANJAY_DM_OBJECT_UNLOCKED);\n    assert(obj_ptr->impl.unlocked);\n    assert(*obj_ptr->impl.unlocked);\n    return obj_ptr->impl.unlocked;\n}\n\nstatic inline bool _anjay_dm_installed_object_is_valid_unlocked(\n        const anjay_dm_installed_object_t *obj_ptr) {\n    return obj_ptr && (obj_ptr->type == ANJAY_DM_OBJECT_UNLOCKED)\n           && obj_ptr->impl.unlocked && (*obj_ptr->impl.unlocked);\n}\n\n#else // ANJAY_WITH_THREAD_SAFETY\n\ntypedef anjay_dm_object_def_t anjay_unlocked_dm_object_def_t;\ntypedef const anjay_dm_object_def_t *const *anjay_dm_installed_object_t;\n\nstatic inline void _anjay_dm_installed_object_init_unlocked(\n        anjay_dm_installed_object_t *obj_ptr,\n        const anjay_unlocked_dm_object_def_t *const *def_ptr) {\n    assert(obj_ptr);\n    assert(!*obj_ptr);\n    *obj_ptr = def_ptr;\n}\n\nstatic inline const anjay_unlocked_dm_object_def_t *const *\n_anjay_dm_installed_object_get_unlocked(\n        const anjay_dm_installed_object_t *obj_ptr) {\n    assert(obj_ptr);\n    assert(*obj_ptr);\n    assert(**obj_ptr);\n    return *obj_ptr;\n}\n\nstatic inline bool _anjay_dm_installed_object_is_valid_unlocked(\n        const anjay_dm_installed_object_t *obj_ptr) {\n    return obj_ptr && (*obj_ptr) && (**obj_ptr);\n}\n\ntypedef anjay_dm_list_ctx_t anjay_unlocked_dm_list_ctx_t;\ntypedef anjay_dm_resource_list_ctx_t anjay_unlocked_dm_resource_list_ctx_t;\ntypedef anjay_output_ctx_t anjay_unlocked_output_ctx_t;\ntypedef anjay_ret_bytes_ctx_t anjay_unlocked_ret_bytes_ctx_t;\ntypedef anjay_input_ctx_t anjay_unlocked_input_ctx_t;\ntypedef anjay_execute_ctx_t anjay_unlocked_execute_ctx_t;\n\ntypedef anjay_dm_handlers_t anjay_unlocked_dm_handlers_t;\n\n#endif // ANJAY_WITH_THREAD_SAFETY\n\n/**\n * AVS_LIST(anjay_dm_installed_object_t) of installed objects is de facto a list\n * of objects of different types, because unlocked objects often maintain their\n * state in a surrounding container struct. To make this work,\n * @ref anjay_dm_installed_object_t must come as the first field, otherwise\n * additional state that's stored in memory (pointer to next element) before\n * theseactual elements of the list would collide with other fields of\n * surrounding struct.\n */\n#define _ANJAY_ASSERT_INSTALLED_OBJECT_IS_FIRST_FIELD(                         \\\n        ObjectReprType, InstalledObjectFieldName)                              \\\n    AVS_STATIC_ASSERT(offsetof(ObjectReprType, InstalledObjectFieldName) == 0, \\\n                      installed_object_field_in_object_repr_must_be_first)\n\ntypedef int anjay_unlocked_dm_object_read_default_attrs_t(\n        anjay_unlocked_t *anjay,\n        const anjay_dm_installed_object_t obj,\n        anjay_ssid_t ssid,\n        anjay_dm_oi_attributes_t *out);\ntypedef int anjay_unlocked_dm_object_write_default_attrs_t(\n        anjay_unlocked_t *anjay,\n        const anjay_dm_installed_object_t obj,\n        anjay_ssid_t ssid,\n        const anjay_dm_oi_attributes_t *attrs);\ntypedef int\nanjay_unlocked_dm_list_instances_t(anjay_unlocked_t *anjay,\n                                   const anjay_dm_installed_object_t obj,\n                                   anjay_unlocked_dm_list_ctx_t *ctx);\ntypedef int\nanjay_unlocked_dm_instance_reset_t(anjay_unlocked_t *anjay,\n                                   const anjay_dm_installed_object_t obj,\n                                   anjay_iid_t iid);\ntypedef int\nanjay_unlocked_dm_instance_remove_t(anjay_unlocked_t *anjay,\n                                    const anjay_dm_installed_object_t obj,\n                                    anjay_iid_t iid);\ntypedef int\nanjay_unlocked_dm_instance_create_t(anjay_unlocked_t *anjay,\n                                    const anjay_dm_installed_object_t obj,\n                                    anjay_iid_t iid);\ntypedef int anjay_unlocked_dm_instance_read_default_attrs_t(\n        anjay_unlocked_t *anjay,\n        const anjay_dm_installed_object_t obj,\n        anjay_iid_t iid,\n        anjay_ssid_t ssid,\n        anjay_dm_oi_attributes_t *out);\ntypedef int anjay_unlocked_dm_instance_write_default_attrs_t(\n        anjay_unlocked_t *anjay,\n        const anjay_dm_installed_object_t obj,\n        anjay_iid_t iid,\n        anjay_ssid_t ssid,\n        const anjay_dm_oi_attributes_t *attrs);\ntypedef int\nanjay_unlocked_dm_list_resources_t(anjay_unlocked_t *anjay,\n                                   const anjay_dm_installed_object_t obj,\n                                   anjay_iid_t iid,\n                                   anjay_unlocked_dm_resource_list_ctx_t *ctx);\ntypedef int\nanjay_unlocked_dm_resource_read_t(anjay_unlocked_t *anjay,\n                                  const anjay_dm_installed_object_t obj,\n                                  anjay_iid_t iid,\n                                  anjay_rid_t rid,\n                                  anjay_riid_t riid,\n                                  anjay_unlocked_output_ctx_t *ctx);\ntypedef int\nanjay_unlocked_dm_resource_write_t(anjay_unlocked_t *anjay,\n                                   const anjay_dm_installed_object_t obj,\n                                   anjay_iid_t iid,\n                                   anjay_rid_t rid,\n                                   anjay_riid_t riid,\n                                   anjay_unlocked_input_ctx_t *ctx);\ntypedef int\nanjay_unlocked_dm_resource_execute_t(anjay_unlocked_t *anjay,\n                                     const anjay_dm_installed_object_t obj,\n                                     anjay_iid_t iid,\n                                     anjay_rid_t rid,\n                                     anjay_unlocked_execute_ctx_t *ctx);\ntypedef int\nanjay_unlocked_dm_resource_reset_t(anjay_unlocked_t *anjay,\n                                   const anjay_dm_installed_object_t obj,\n                                   anjay_iid_t iid,\n                                   anjay_rid_t rid);\ntypedef int anjay_unlocked_dm_list_resource_instances_t(\n        anjay_unlocked_t *anjay,\n        const anjay_dm_installed_object_t obj,\n        anjay_iid_t iid,\n        anjay_rid_t rid,\n        anjay_unlocked_dm_list_ctx_t *ctx);\ntypedef int\nanjay_unlocked_dm_resource_read_attrs_t(anjay_unlocked_t *anjay,\n                                        const anjay_dm_installed_object_t obj,\n                                        anjay_iid_t iid,\n                                        anjay_rid_t rid,\n                                        anjay_ssid_t ssid,\n                                        anjay_dm_r_attributes_t *out);\ntypedef int\nanjay_unlocked_dm_resource_write_attrs_t(anjay_unlocked_t *anjay,\n                                         const anjay_dm_installed_object_t obj,\n                                         anjay_iid_t iid,\n                                         anjay_rid_t rid,\n                                         anjay_ssid_t ssid,\n                                         const anjay_dm_r_attributes_t *attrs);\n#ifdef ANJAY_WITH_LWM2M11\ntypedef int anjay_unlocked_dm_resource_instance_read_attrs_t(\n        anjay_unlocked_t *anjay,\n        const anjay_dm_installed_object_t obj,\n        anjay_iid_t iid,\n        anjay_rid_t rid,\n        anjay_riid_t riid,\n        anjay_ssid_t ssid,\n        anjay_dm_r_attributes_t *out);\ntypedef int anjay_unlocked_dm_resource_instance_write_attrs_t(\n        anjay_unlocked_t *anjay,\n        const anjay_dm_installed_object_t obj,\n        anjay_iid_t iid,\n        anjay_rid_t rid,\n        anjay_riid_t riid,\n        anjay_ssid_t ssid,\n        const anjay_dm_r_attributes_t *attrs);\n#endif // ANJAY_WITH_LWM2M11\n#ifdef ANJAY_WITH_LWM2M12\ntypedef int anjay_unlocked_dm_resource_instance_remove_t(\n        anjay_unlocked_t *anjay,\n        const anjay_dm_installed_object_t obj,\n        anjay_iid_t iid,\n        anjay_rid_t rid,\n        anjay_riid_t riid);\n#endif // ANJAY_WITH_LWM2M12\ntypedef int\nanjay_unlocked_dm_transaction_begin_t(anjay_unlocked_t *anjay,\n                                      const anjay_dm_installed_object_t obj);\ntypedef int\nanjay_unlocked_dm_transaction_validate_t(anjay_unlocked_t *anjay,\n                                         const anjay_dm_installed_object_t obj);\ntypedef int\nanjay_unlocked_dm_transaction_commit_t(anjay_unlocked_t *anjay,\n                                       const anjay_dm_installed_object_t obj);\ntypedef int\nanjay_unlocked_dm_transaction_rollback_t(anjay_unlocked_t *anjay,\n                                         const anjay_dm_installed_object_t obj);\n\n#ifdef ANJAY_WITH_THREAD_SAFETY\ntypedef struct {\n    anjay_unlocked_dm_object_read_default_attrs_t *object_read_default_attrs;\n    anjay_unlocked_dm_object_write_default_attrs_t *object_write_default_attrs;\n    anjay_unlocked_dm_list_instances_t *list_instances;\n    anjay_unlocked_dm_instance_reset_t *instance_reset;\n    anjay_unlocked_dm_instance_create_t *instance_create;\n    anjay_unlocked_dm_instance_remove_t *instance_remove;\n    anjay_unlocked_dm_instance_read_default_attrs_t\n            *instance_read_default_attrs;\n    anjay_unlocked_dm_instance_write_default_attrs_t\n            *instance_write_default_attrs;\n    anjay_unlocked_dm_list_resources_t *list_resources;\n    anjay_unlocked_dm_resource_read_t *resource_read;\n    anjay_unlocked_dm_resource_write_t *resource_write;\n    anjay_unlocked_dm_resource_execute_t *resource_execute;\n    anjay_unlocked_dm_resource_reset_t *resource_reset;\n    anjay_unlocked_dm_list_resource_instances_t *list_resource_instances;\n    anjay_unlocked_dm_resource_read_attrs_t *resource_read_attrs;\n    anjay_unlocked_dm_resource_write_attrs_t *resource_write_attrs;\n    anjay_unlocked_dm_transaction_begin_t *transaction_begin;\n    anjay_unlocked_dm_transaction_validate_t *transaction_validate;\n    anjay_unlocked_dm_transaction_commit_t *transaction_commit;\n    anjay_unlocked_dm_transaction_rollback_t *transaction_rollback;\n#    ifdef ANJAY_WITH_LWM2M11\n    anjay_unlocked_dm_resource_instance_read_attrs_t\n            *resource_instance_read_attrs;\n    anjay_unlocked_dm_resource_instance_write_attrs_t\n            *resource_instance_write_attrs;\n#    endif // ANJAY_WITH_LWM2M11\n#    ifdef ANJAY_WITH_LWM2M12\n    anjay_unlocked_dm_resource_instance_remove_t *resource_instance_remove;\n#    endif // ANJAY_WITH_LWM2M12\n} anjay_unlocked_dm_handlers_t;\n#endif // ANJAY_WITH_THREAD_SAFETY\n\n/**\n * A function to be called when the module is uninstalled, that will clean up\n * any resources used by it.\n */\ntypedef void anjay_dm_module_deleter_t(void *arg);\n\n/**\n * Installs an Anjay module. In practice this just means registering an\n * arbitrary function to be called during <c>anjay_delete()</c>.\n *\n * @param anjay  Anjay object to operate on\n *\n * @param module_deleter Pointer to a module deleter function; this pointer is\n *                       also used as a module identifier\n *\n * @param arg    Opaque pointer that can be later retrieved using\n *               @ref _anjay_dm_module_get_arg and will also be passed to the\n *               <c>notify_callback</c> and <c>deleter</c> functions.\n *\n * @returns 0 for success, or a negative value in case of error.\n */\nint _anjay_dm_module_install(anjay_unlocked_t *anjay,\n                             anjay_dm_module_deleter_t *module_deleter,\n                             void *arg);\n\nint _anjay_dm_module_uninstall(anjay_unlocked_t *anjay,\n                               anjay_dm_module_deleter_t *module_deleter);\n\n/**\n * Returns the <c>arg</c> previously passed to @ref _anjay_dm_module_install\n */\nvoid *_anjay_dm_module_get_arg(anjay_unlocked_t *anjay,\n                               anjay_dm_module_deleter_t *module_deleter);\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif /* ANJAY_INCLUDE_ANJAY_MODULES_DM_MODULES_H */\n"
  },
  {
    "path": "src/core/anjay_access_utils.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#include <inttypes.h>\n\n#include <anjay_modules/anjay_access_utils.h>\n#include <anjay_modules/anjay_dm_utils.h>\n#include <anjay_modules/anjay_raw_buffer.h>\n\n#include \"anjay_access_utils_private.h\"\n#include \"anjay_dm_core.h\"\n#include \"anjay_io_core.h\"\n#include \"anjay_servers_utils.h\"\n\n#include \"io/anjay_vtable.h\"\n\nVISIBILITY_SOURCE_BEGIN\n\n#ifdef ANJAY_WITH_ACCESS_CONTROL\n\nstatic inline const anjay_dm_installed_object_t *\nget_access_control(anjay_unlocked_t *anjay) {\n    return _anjay_dm_find_object_by_oid(&anjay->dm,\n                                        ANJAY_DM_OID_ACCESS_CONTROL);\n}\n\nstatic int read_u16(anjay_unlocked_t *anjay,\n                    anjay_iid_t iid,\n                    anjay_rid_t rid,\n                    uint16_t *out) {\n    int64_t ret;\n    const anjay_uri_path_t uri =\n            MAKE_RESOURCE_PATH(ANJAY_DM_OID_ACCESS_CONTROL, iid, rid);\n    int result = _anjay_dm_read_resource_i64(anjay, &uri, &ret);\n    if (result) {\n        return result;\n    } else if (ret != (uint16_t) ret) {\n        anjay_log(WARNING,\n                  _(\"cannot read \") \"%s\" _(\" = \") \"%s\" _(\n                          \" as uint16: value overflow\"),\n                  ANJAY_DEBUG_MAKE_PATH(&uri), AVS_INT64_AS_STRING(ret));\n        return -1;\n    }\n    *out = (uint16_t) ret;\n    return 0;\n}\n\ntypedef struct {\n    const anjay_input_ctx_vtable_t *vtable;\n    uint16_t value;\n} u16_writer_ctx_t;\n\nstatic int u16_writer_integer(anjay_unlocked_input_ctx_t *ctx, int64_t *out) {\n    *out = ((u16_writer_ctx_t *) ctx)->value;\n    return 0;\n}\n\nstatic int write_u16(anjay_unlocked_t *anjay,\n                     const anjay_dm_installed_object_t *ac_obj_ptr,\n                     anjay_iid_t iid,\n                     anjay_rid_t rid,\n                     anjay_riid_t riid,\n                     uint16_t value) {\n    static const anjay_input_ctx_vtable_t VTABLE = {\n        .integer = u16_writer_integer\n    };\n    u16_writer_ctx_t ctx = {\n        .vtable = &VTABLE,\n        .value = value\n    };\n    return _anjay_dm_call_resource_write(anjay, ac_obj_ptr, iid, rid, riid,\n                                         (anjay_unlocked_input_ctx_t *) &ctx);\n}\n\nstatic int read_ids_from_ac_instance(anjay_unlocked_t *anjay,\n                                     anjay_iid_t access_control_iid,\n                                     anjay_oid_t *out_oid,\n                                     anjay_iid_t *out_oiid,\n                                     anjay_ssid_t *out_owner) {\n    int ret = 0;\n    if (!ret && out_oid) {\n        ret = read_u16(anjay, access_control_iid,\n                       ANJAY_DM_RID_ACCESS_CONTROL_OID, out_oid);\n    }\n    if (!ret && out_oiid) {\n        ret = read_u16(anjay, access_control_iid,\n                       ANJAY_DM_RID_ACCESS_CONTROL_OIID, out_oiid);\n    }\n    if (!ret && out_owner) {\n        ret = read_u16(anjay, access_control_iid,\n                       ANJAY_DM_RID_ACCESS_CONTROL_OWNER, out_owner);\n    }\n    return ret;\n}\n\nstatic int read_mask(anjay_unlocked_t *anjay,\n                     const anjay_dm_installed_object_t *obj,\n                     anjay_iid_t iid,\n                     anjay_rid_t rid,\n                     anjay_riid_t riid,\n                     anjay_access_mask_t *out) {\n    int64_t mask;\n\n    avs_stream_outbuf_t stream = AVS_STREAM_OUTBUF_STATIC_INITIALIZER;\n    avs_stream_outbuf_set_buffer(&stream, &mask, sizeof(mask));\n    anjay_output_buf_ctx_t ctx =\n            _anjay_output_buf_ctx_init((avs_stream_t *) &stream);\n    int result =\n            _anjay_dm_call_resource_read(anjay, obj, iid, rid, riid,\n                                         (anjay_unlocked_output_ctx_t *) &ctx);\n    if (!result) {\n        if (avs_stream_outbuf_offset(&stream) != sizeof(mask)) {\n            return -1;\n        }\n        *out = (anjay_access_mask_t) mask;\n    }\n    return result;\n}\n\ntypedef struct {\n    bool acl_empty;\n    anjay_ssid_t ssid_lookup;\n    anjay_ssid_t found_ssid;\n    anjay_access_mask_t mask;\n} get_mask_instance_clb_args_t;\n\nstatic int get_mask_instance_clb(anjay_unlocked_t *anjay,\n                                 const anjay_dm_installed_object_t *obj,\n                                 anjay_iid_t iid,\n                                 anjay_rid_t rid,\n                                 anjay_riid_t riid,\n                                 void *args_) {\n    get_mask_instance_clb_args_t *args = (get_mask_instance_clb_args_t *) args_;\n\n    args->acl_empty = false;\n    if (riid == args->ssid_lookup || riid == 0) {\n        // Found an entry for the given ssid or the default ACL entry\n        anjay_access_mask_t mask;\n        int result = read_mask(anjay, obj, iid, rid, riid, &mask);\n        if (result) {\n            return result;\n        }\n\n        args->found_ssid = riid;\n        args->mask = mask;\n        if (riid) {\n            // not the default\n            return ANJAY_FOREACH_BREAK;\n        }\n    }\n    return 0;\n}\n\nstatic int foreach_acl(anjay_unlocked_t *anjay,\n                       const anjay_dm_installed_object_t *ac_obj,\n                       anjay_iid_t ac_iid,\n                       anjay_dm_foreach_resource_instance_handler_t *handler,\n                       void *data) {\n    anjay_dm_resource_kind_t kind;\n    anjay_dm_resource_presence_t presence;\n    int result = _anjay_dm_resource_kind_and_presence(\n            anjay, ac_obj, ac_iid, ANJAY_DM_RID_ACCESS_CONTROL_ACL, &kind,\n            &presence);\n    if (!result && presence != ANJAY_DM_RES_ABSENT) {\n        // Ensure that the ACL resource is sane: readable and multi-instance;\n        // it might not be true if Access Control object is user-implemented\n        // in an insane way, and without checking this, calling\n        // list_resource_instances or later resource_read\n        // would break handler contracts\n        if (!_anjay_dm_res_kind_readable(kind)\n                || !_anjay_dm_res_kind_multiple(kind)) {\n            return -1;\n        }\n        result = _anjay_dm_foreach_resource_instance(\n                anjay, ac_obj, ac_iid, ANJAY_DM_RID_ACCESS_CONTROL_ACL, handler,\n                data);\n    }\n    return result;\n}\n\ntypedef struct {\n    anjay_iid_t ac_iid;\n    anjay_oid_t target_oid;\n    anjay_iid_t target_iid;\n} find_ac_instance_args_t;\n\nstatic int find_ac_instance_clb(anjay_unlocked_t *anjay,\n                                const anjay_dm_installed_object_t *ac_obj,\n                                anjay_iid_t ac_iid,\n                                void *args_) {\n    (void) ac_obj;\n    find_ac_instance_args_t *args = (find_ac_instance_args_t *) args_;\n    anjay_oid_t res_oid;\n    anjay_iid_t res_oiid;\n    int result =\n            read_ids_from_ac_instance(anjay, ac_iid, &res_oid, &res_oiid, NULL);\n    if (result) {\n        return result;\n    }\n    if (res_oid == args->target_oid && res_oiid == args->target_iid) {\n        assert(args->ac_iid == ANJAY_ID_INVALID);\n        args->ac_iid = ac_iid;\n        return ANJAY_FOREACH_BREAK;\n    }\n    return ANJAY_FOREACH_CONTINUE;\n}\n\nstatic int find_ac_instance_by_target(anjay_unlocked_t *anjay,\n                                      const anjay_dm_installed_object_t *ac_obj,\n                                      anjay_iid_t *out_ac_iid,\n                                      anjay_oid_t target_oid,\n                                      anjay_iid_t target_iid) {\n    find_ac_instance_args_t args = {\n        .ac_iid = ANJAY_ID_INVALID,\n        .target_oid = target_oid,\n        .target_iid = target_iid\n    };\n    int result = _anjay_dm_foreach_instance(anjay, ac_obj, find_ac_instance_clb,\n                                            &args);\n    if (!result) {\n        if (args.ac_iid == ANJAY_ID_INVALID) {\n            return ANJAY_ERR_NOT_FOUND;\n        }\n        if (out_ac_iid) {\n            *out_ac_iid = args.ac_iid;\n        }\n    }\n    return result;\n}\n\nstatic int get_mask(anjay_unlocked_t *anjay,\n                    const anjay_dm_installed_object_t *ac_obj,\n                    anjay_iid_t ac_iid,\n                    anjay_ssid_t *inout_ssid,\n                    anjay_access_mask_t *out_mask) {\n    get_mask_instance_clb_args_t args = {\n        .acl_empty = true,\n        .ssid_lookup = *inout_ssid,\n        .found_ssid = 0,\n        .mask = ANJAY_ACCESS_MASK_NONE\n    };\n    int result =\n            foreach_acl(anjay, ac_obj, ac_iid, get_mask_instance_clb, &args);\n    if (result) {\n        return result;\n    }\n    if (args.acl_empty) {\n        *inout_ssid = ANJAY_SSID_BOOTSTRAP;\n    } else {\n        *inout_ssid = args.found_ssid;\n    }\n    *out_mask = args.mask;\n    return 0;\n}\n\nstatic anjay_access_mask_t access_control_mask(anjay_unlocked_t *anjay,\n                                               anjay_oid_t oid,\n                                               anjay_iid_t iid,\n                                               anjay_ssid_t ssid) {\n    const anjay_dm_installed_object_t *ac_obj =\n            _anjay_dm_find_object_by_oid(&anjay->dm,\n                                         ANJAY_DM_OID_ACCESS_CONTROL);\n    anjay_iid_t ac_iid;\n    if (!ac_obj\n            || find_ac_instance_by_target(anjay, ac_obj, &ac_iid, oid, iid)) {\n        return ANJAY_ACCESS_MASK_NONE;\n    }\n\n    anjay_ssid_t found_ssid = ssid;\n    anjay_access_mask_t mask;\n    if (get_mask(anjay, ac_obj, (anjay_iid_t) ac_iid, &found_ssid, &mask)) {\n        anjay_log(WARNING, _(\"failed to read ACL!\"));\n        return ANJAY_ACCESS_MASK_NONE;\n    }\n\n    if (found_ssid == ssid) {\n        // Found the ACL\n        return mask;\n    } else if (found_ssid == ANJAY_SSID_BOOTSTRAP) {\n        anjay_ssid_t owner;\n        if (!read_ids_from_ac_instance(anjay, ac_iid, NULL, NULL, &owner)\n                && owner == ssid) {\n            // Empty ACL, and given ssid is an owner of the instance\n            return ANJAY_ACCESS_MASK_FULL & ~ANJAY_ACCESS_MASK_CREATE;\n        }\n    } else if (!found_ssid) {\n        // Default ACL\n        return mask;\n    }\n    return ANJAY_ACCESS_MASK_NONE;\n}\n\nstatic bool can_instantiate(anjay_unlocked_t *anjay,\n                            const anjay_action_info_t *info) {\n    return access_control_mask(anjay, info->oid, ANJAY_ID_INVALID, info->ssid)\n           & ANJAY_ACCESS_MASK_CREATE;\n}\n\ntypedef struct {\n    anjay_oid_t oid;\n    anjay_iid_t iid;\n    anjay_ssid_t owner;\n} get_owner_data_t;\n\nstatic int count_non_bootstrap_clb(anjay_unlocked_t *anjay,\n                                   anjay_ssid_t ssid,\n                                   void *counter_ptr) {\n    (void) anjay;\n    if (ssid != ANJAY_SSID_BOOTSTRAP) {\n        ++*((size_t *) counter_ptr);\n    }\n    return ANJAY_FOREACH_CONTINUE;\n}\n\nstatic bool is_single_ssid_environment(anjay_unlocked_t *anjay) {\n    size_t non_bootstrap_count = 0;\n    if (_anjay_servers_foreach_ssid(anjay, count_non_bootstrap_clb,\n                                    &non_bootstrap_count)) {\n        return false;\n    }\n    return non_bootstrap_count == 1;\n}\n\n#endif // ANJAY_WITH_ACCESS_CONTROL\n\nanjay_instance_action_allowed_stateless_result_t\n_anjay_instance_action_allowed_stateless(anjay_unlocked_t *anjay,\n                                         const anjay_action_info_t *info) {\n    if (info->oid == ANJAY_DM_OID_SECURITY) {\n        return ANJAY_INSTANCE_ACTION_DISALLOWED;\n    }\n    assert(info->iid != ANJAY_ID_INVALID\n           || info->action == ANJAY_ACTION_CREATE);\n#ifndef ANJAY_WITH_ACCESS_CONTROL\n    (void) anjay;\n    return ANJAY_INSTANCE_ACTION_ALLOWED;\n#else\n\n#    ifdef ANJAY_WITH_LWM2M_GATEWAY\n    if (info->end_device) {\n        return ANJAY_INSTANCE_ACTION_ALLOWED;\n    }\n#    endif // ANJAY_WITH_LWM2M_GATEWAY\n\n    if (info->ssid == ANJAY_SSID_BOOTSTRAP) {\n        // Access Control is not applicable to Bootstrap Server\n        return ANJAY_INSTANCE_ACTION_ALLOWED;\n    }\n\n    if (info->action == ANJAY_ACTION_DISCOVER) {\n        return ANJAY_INSTANCE_ACTION_ALLOWED;\n    }\n\n    if (!get_access_control(anjay) || is_single_ssid_environment(anjay)) {\n        return ANJAY_INSTANCE_ACTION_ALLOWED;\n    }\n\n    if (info->oid == ANJAY_DM_OID_ACCESS_CONTROL) {\n        if (info->action == ANJAY_ACTION_READ\n                || info->action == ANJAY_ACTION_WRITE_ATTRIBUTES) {\n            return ANJAY_INSTANCE_ACTION_ALLOWED;\n        } else if (info->action == ANJAY_ACTION_CREATE\n                   || info->action == ANJAY_ACTION_DELETE) {\n            return ANJAY_INSTANCE_ACTION_DISALLOWED;\n        }\n    }\n\n    return ANJAY_INSTANCE_ACTION_NEEDS_ACL_CHECK;\n#endif     // ANJAY_WITH_ACCESS_CONTROL\n}\n\n#ifdef ANJAY_WITH_ACCESS_CONTROL\nbool _anjay_instance_action_allowed_by_acl(anjay_unlocked_t *anjay,\n                                           const anjay_action_info_t *info) {\n    assert(info->oid != ANJAY_DM_OID_SECURITY);\n    assert(info->iid != ANJAY_ID_INVALID\n           || info->action == ANJAY_ACTION_CREATE);\n    assert(info->ssid != ANJAY_SSID_BOOTSTRAP);\n    assert(info->action != ANJAY_ACTION_DISCOVER);\n    assert(get_access_control(anjay));\n    assert(!is_single_ssid_environment(anjay));\n\n    if (info->oid == ANJAY_DM_OID_ACCESS_CONTROL) {\n        assert(info->action != ANJAY_ACTION_READ);\n        assert(info->action != ANJAY_ACTION_WRITE_ATTRIBUTES);\n        assert(info->action != ANJAY_ACTION_CREATE);\n        assert(info->action != ANJAY_ACTION_DELETE);\n\n        anjay_ssid_t owner;\n        if (read_u16(anjay, info->iid, ANJAY_DM_RID_ACCESS_CONTROL_OWNER,\n                     &owner)) {\n            return false;\n        }\n        return owner == info->ssid;\n    }\n\n    if (info->action == ANJAY_ACTION_CREATE) {\n        return can_instantiate(anjay, info);\n    }\n\n    anjay_access_mask_t mask =\n            access_control_mask(anjay, info->oid, info->iid, info->ssid);\n    switch (info->action) {\n    case ANJAY_ACTION_READ:\n    case ANJAY_ACTION_WRITE_ATTRIBUTES:\n        return !!(mask & ANJAY_ACCESS_MASK_READ);\n    case ANJAY_ACTION_WRITE:\n    case ANJAY_ACTION_WRITE_UPDATE:\n        return !!(mask & ANJAY_ACCESS_MASK_WRITE);\n    case ANJAY_ACTION_EXECUTE:\n        return !!(mask & ANJAY_ACCESS_MASK_EXECUTE);\n    case ANJAY_ACTION_DELETE:\n        return !!(mask & ANJAY_ACCESS_MASK_DELETE);\n    default:\n        AVS_UNREACHABLE(\"invalid enum value\");\n        return false;\n    }\n}\n#endif // ANJAY_WITH_ACCESS_CONTROL\n\nbool _anjay_instance_action_allowed(anjay_unlocked_t *anjay,\n                                    const anjay_action_info_t *info) {\n    switch (_anjay_instance_action_allowed_stateless(anjay, info)) {\n    case ANJAY_INSTANCE_ACTION_DISALLOWED:\n        return false;\n    case ANJAY_INSTANCE_ACTION_ALLOWED:\n        return true;\n#ifdef ANJAY_WITH_ACCESS_CONTROL\n    case ANJAY_INSTANCE_ACTION_NEEDS_ACL_CHECK:\n        return _anjay_instance_action_allowed_by_acl(anjay, info);\n#endif // ANJAY_WITH_ACCESS_CONTROL\n    }\n    AVS_UNREACHABLE(\"invalid enum value\");\n    return false;\n}\n\n#ifdef ANJAY_WITH_ACCESS_CONTROL\n\nstatic void what_changed(anjay_ssid_t origin_ssid,\n                         anjay_notify_queue_t notifications_already_queued,\n                         bool *out_might_caused_orphaned_ac_instances,\n                         bool *out_have_adds,\n                         bool *out_might_have_removes) {\n    *out_might_caused_orphaned_ac_instances = false;\n    *out_have_adds = false;\n    *out_might_have_removes = false;\n    AVS_LIST(anjay_notify_queue_object_entry_t) it;\n    AVS_LIST_FOREACH(it, notifications_already_queued) {\n#    ifdef ANJAY_WITH_LWM2M_GATEWAY\n        // EID's DMs are not guarded by AC\n        if (it->prefix[0] != '\\0') {\n            continue;\n        }\n#    endif // ANJAY_WITH_LWM2M_GATEWAY\n        if (!it->instance_set_changes.instance_set_changed) {\n            continue;\n        }\n        if (it->oid == ANJAY_DM_OID_SECURITY || it->oid == ANJAY_DM_OID_SERVER\n                || it->oid == ANJAY_DM_OID_ACCESS_CONTROL) {\n            // If the instance set changed for Security or Server, the set of\n            // valid SSIDs might have changed, so some AC instances might now\n            // be orphaned (have no valid owner).\n            // Also, if the set of Access Control instances changed, it might\n            // mean that an instance with invalid owner (so, technically,\n            // already orphaned) have been created.\n            *out_might_caused_orphaned_ac_instances = true;\n        }\n        if (it->oid != ANJAY_DM_OID_SECURITY\n                && it->oid != ANJAY_DM_OID_ACCESS_CONTROL) {\n            *out_might_have_removes = true;\n            if (it->instance_set_changes.known_added_iids\n                    && origin_ssid != ANJAY_SSID_BOOTSTRAP) {\n                // Technically, even if this condition is not met, there might\n                // be \"undocumented\" adds (not listed in known_added_iids), but\n                // we don't care about them.\n                *out_have_adds = true;\n            }\n        }\n        if (*out_might_caused_orphaned_ac_instances && *out_might_have_removes\n                && (*out_have_adds || origin_ssid == ANJAY_SSID_BOOTSTRAP)) {\n            // all flags possible to set are already set\n            // they can't be any more true, so we break out from this loop\n            break;\n        }\n    }\n}\n\nstatic int\nenumerate_valid_ssids_clb(anjay_unlocked_t *anjay,\n                          const anjay_dm_installed_object_t *security_obj,\n                          anjay_iid_t iid,\n                          void *ssid_list_ptr_) {\n    (void) security_obj;\n    AVS_LIST(anjay_ssid_t) *insert_ptr =\n            (AVS_LIST(anjay_ssid_t) *) ssid_list_ptr_;\n    anjay_ssid_t ssid;\n    if (_anjay_ssid_from_security_iid(anjay, iid, &ssid)) {\n        return -1;\n    }\n    while (*insert_ptr && **insert_ptr < ssid) {\n        AVS_LIST_ADVANCE_PTR(&insert_ptr);\n    }\n    if (*insert_ptr && **insert_ptr == ssid) {\n        return 0;\n    }\n    if (!AVS_LIST_INSERT_NEW(anjay_ssid_t, insert_ptr)) {\n        _anjay_log_oom();\n        return -1;\n    }\n    **insert_ptr = ssid;\n    return 0;\n}\n\ntypedef struct {\n    anjay_ssid_t ssid;\n    anjay_access_mask_t mask;\n} acl_entry_t;\n\nstatic int read_acl_clb(anjay_unlocked_t *anjay,\n                        const anjay_dm_installed_object_t *obj,\n                        anjay_iid_t iid,\n                        anjay_rid_t rid,\n                        anjay_riid_t riid,\n                        void *endptr_ptr_) {\n    AVS_LIST(acl_entry_t) **endptr_ptr = (AVS_LIST(acl_entry_t) **) endptr_ptr_;\n    assert(!**endptr_ptr);\n    if (!(**endptr_ptr = AVS_LIST_NEW_ELEMENT(acl_entry_t))) {\n        return -1;\n    }\n    (**endptr_ptr)->ssid = riid;\n    int result = read_mask(anjay, obj, iid, rid, riid, &(**endptr_ptr)->mask);\n    if (result) {\n        AVS_LIST_DELETE(*endptr_ptr);\n    } else {\n        AVS_LIST_ADVANCE_PTR(endptr_ptr);\n    }\n    return result;\n}\n\nstatic int read_acl(anjay_unlocked_t *anjay,\n                    const anjay_dm_installed_object_t *ac_obj,\n                    anjay_iid_t ac_iid,\n                    AVS_LIST(acl_entry_t) *out_acl) {\n    assert(out_acl);\n    assert(!*out_acl);\n    AVS_LIST(acl_entry_t) *endptr = out_acl;\n    int result = foreach_acl(anjay, ac_obj, ac_iid, read_acl_clb, &endptr);\n    if (result) {\n        AVS_LIST_CLEAR(out_acl);\n    }\n    return result;\n}\n\n/**\n * Finds the server that will become the new owner of the given ACL.\n * Servers with both Write and Delete rights are ranked with value 2, those with\n * one of these are ranked with 1, others with 0. The first entry with the\n * highest rank is elected the new owner.\n */\nstatic anjay_ssid_t elect_instance_owner(AVS_LIST(acl_entry_t) acl) {\n    static const size_t write_weight = 1;\n    static const size_t delete_weight = 1;\n\n    /* Clearly we cannot perform election otherwise. */\n    assert(AVS_LIST_SIZE(acl) > 0);\n\n    anjay_ssid_t new_owner = ANJAY_ACCESS_LIST_OWNER_BOOTSTRAP;\n    size_t highest_sum = 0;\n\n    AVS_LIST(acl_entry_t) entry;\n    AVS_LIST_FOREACH(entry, acl) {\n        size_t sum = (entry->mask & ANJAY_ACCESS_MASK_WRITE) * write_weight\n                     + (entry->mask & ANJAY_ACCESS_MASK_DELETE) * delete_weight;\n        if (sum >= highest_sum) {\n            highest_sum = sum;\n            new_owner = entry->ssid;\n        }\n    }\n    return new_owner;\n}\n\ntypedef struct {\n    anjay_iid_t ac_iid;\n    anjay_oid_t target_oid;\n    anjay_iid_t target_iid;\n} orphaned_instance_info_t;\n\ntypedef struct {\n    AVS_LIST(anjay_ssid_t) valid_ssids;\n    AVS_LIST(orphaned_instance_info_t) *orphaned_instance_list_append_ptr;\n    anjay_notify_queue_t *out_dm_changes;\n} process_orphaned_instances_args_t;\n\nstatic int\nprocess_orphaned_instances_clb(anjay_unlocked_t *anjay,\n                               const anjay_dm_installed_object_t *obj,\n                               anjay_iid_t iid,\n                               void *args_) {\n    process_orphaned_instances_args_t *args =\n            (process_orphaned_instances_args_t *) args_;\n\n    anjay_oid_t target_oid;\n    anjay_iid_t target_iid;\n    anjay_ssid_t owner;\n    AVS_LIST(acl_entry_t) acl = NULL;\n    bool acl_modified = false;\n    bool owner_valid = true;\n    AVS_LIST(acl_entry_t) *entry;\n    AVS_LIST(acl_entry_t) acl_helper;\n    int result = 0;\n\n    // Read all resources in the Access Control instance\n    if ((result = read_ids_from_ac_instance(anjay, iid, &target_oid,\n                                            &target_iid, &owner))\n            || (result = read_acl(anjay, obj, iid, &acl))) {\n        goto finish;\n    }\n\n    // Remove invalid SSID from our temporary copy of the ACL\n    AVS_LIST_DELETABLE_FOREACH_PTR(entry, acl_helper, &acl) {\n        if ((*entry)->ssid != ANJAY_ACCESS_LIST_OWNER_BOOTSTRAP\n                && (*entry)->ssid != ANJAY_SSID_ANY\n                && !AVS_LIST_FIND_BY_VALUE_PTR(&args->valid_ssids,\n                                               &(*entry)->ssid, memcmp)) {\n            acl_modified = true;\n            if ((*entry)->ssid == owner) {\n                owner_valid = false;\n            }\n            AVS_LIST_DELETE(entry);\n        }\n    }\n    if (!acl_modified) {\n        goto finish;\n    }\n    if (!acl) {\n        // No valid ACL entries, the entire instance needs to be removed;\n        // we can't do it now because of handler contracts, so add it to the\n        // list of orphaned instances for later removal by\n        // remove_orphaned_instances().\n        assert(!*args->orphaned_instance_list_append_ptr);\n        if (!(*args->orphaned_instance_list_append_ptr =\n                      AVS_LIST_NEW_ELEMENT(orphaned_instance_info_t))) {\n            _anjay_log_oom();\n            result = -1;\n            goto finish;\n        }\n        (*args->orphaned_instance_list_append_ptr)->ac_iid = iid;\n        (*args->orphaned_instance_list_append_ptr)->target_oid = target_oid;\n        (*args->orphaned_instance_list_append_ptr)->target_iid = target_iid;\n        AVS_LIST_ADVANCE_PTR(&args->orphaned_instance_list_append_ptr);\n    } else {\n        if (!owner_valid) {\n            (void) ((result = write_u16(\n                             anjay, obj, iid, ANJAY_DM_RID_ACCESS_CONTROL_OWNER,\n                             ANJAY_ID_INVALID, elect_instance_owner(acl)))\n                    || (result = _anjay_notify_queue_resource_change(\n                                args->out_dm_changes,\n                                &MAKE_RESOURCE_PATH(\n                                        ANJAY_DM_OID_ACCESS_CONTROL, iid,\n                                        ANJAY_DM_RID_ACCESS_CONTROL_OWNER))));\n        }\n        // Rewrite the modified ACL to the data model\n        if (!result) {\n            result = _anjay_dm_call_resource_reset(\n                    anjay, obj, iid, ANJAY_DM_RID_ACCESS_CONTROL_ACL);\n        }\n        AVS_LIST_CLEAR(&acl) {\n            if (!result) {\n                result = write_u16(anjay, obj, iid,\n                                   ANJAY_DM_RID_ACCESS_CONTROL_ACL, acl->ssid,\n                                   acl->mask);\n            }\n        }\n        if (!result) {\n            result = _anjay_notify_queue_resource_change(\n                    args->out_dm_changes,\n                    &MAKE_RESOURCE_PATH(ANJAY_DM_OID_ACCESS_CONTROL, iid,\n                                        ANJAY_DM_RID_ACCESS_CONTROL_ACL));\n        }\n    }\nfinish:\n    AVS_LIST_CLEAR(&acl);\n    return result;\n}\n\nstatic int remove_referred_instance(anjay_unlocked_t *anjay,\n                                    const orphaned_instance_info_t *it,\n                                    anjay_notify_queue_t *out_dm_changes) {\n    // We do not fail if either of the following is true:\n    // - the target Object does not exist\n    // - the target Instance is not set\n    // - the target Instance does not exist\n    int result = 0;\n    const anjay_dm_installed_object_t *obj =\n            _anjay_dm_find_object_by_oid(&anjay->dm, it->target_oid);\n    if (obj\n            && _anjay_dm_instance_present(anjay, obj,\n                                          (anjay_iid_t) it->target_iid)\n                           > 0\n            && !(result = _anjay_dm_call_instance_remove(\n                         anjay, obj, (anjay_iid_t) it->target_iid))) {\n        result = _anjay_notify_queue_instance_removed(\n                out_dm_changes,\n                &MAKE_INSTANCE_PATH(it->target_oid, it->target_iid));\n    }\n    if (result) {\n        anjay_log(ERROR,\n                  _(\"cannot remove assigned Object Instance /\") \"%\" PRIu16 _(\n                          \"/\") \"%\" PRIu16,\n                  it->target_oid, it->target_iid);\n    }\n    return result;\n}\n\n/**\n * Removes ACL entries (ACL Resource Instances) that refer to SSIDs that do not\n * correspond with any Security object instance.\n *\n * Also, if any Access Control object's owner is set to an SSID that is no\n * longer, valid:\n * - changes the owner of that ACL to some other server listed in the ACL if\n *   possible,\n * - if not, removes the Access Control instance, and the object instance\n *   referred to by it (see LwM2M TS 1.0.2, E.1.3 Unbootstrapping).\n *\n * This function does not check LwM2M Gateway related prefix in\n * new_notifications_queue because it only removes existing AC entries and adds\n * them to the queue, and End IoT Devices DMs are not added to AC in the first\n * place, so they won't be removed as well.\n */\nstatic int\nremove_orphaned_instances(anjay_unlocked_t *anjay,\n                          const anjay_dm_installed_object_t *ac_obj,\n                          anjay_notify_queue_t *new_notifications_queue) {\n    int result = 0;\n    AVS_LIST(anjay_ssid_t) ssid_list = NULL;\n    AVS_LIST(orphaned_instance_info_t) instances_to_remove = NULL;\n    const anjay_dm_installed_object_t *security_obj =\n            _anjay_dm_find_object_by_oid(&anjay->dm, ANJAY_DM_OID_SECURITY);\n    if (security_obj) {\n        result = _anjay_dm_foreach_instance(\n                anjay, security_obj, enumerate_valid_ssids_clb, &ssid_list);\n    }\n    if (!result) {\n        result = _anjay_dm_foreach_instance(\n                anjay, ac_obj, process_orphaned_instances_clb,\n                &(process_orphaned_instances_args_t) {\n                    .valid_ssids = ssid_list,\n                    .orphaned_instance_list_append_ptr = &instances_to_remove,\n                    .out_dm_changes = new_notifications_queue\n                });\n    }\n    // Actually remove the instances marked by process_orphaned_instances_clb\n    // as necessary for removal, and the Object Instances referred to by them.\n    AVS_LIST_CLEAR(&instances_to_remove) {\n        (void) (result\n                || (result =\n                            remove_referred_instance(anjay, instances_to_remove,\n                                                     new_notifications_queue))\n                || (result = _anjay_dm_call_instance_remove(\n                            anjay, ac_obj, instances_to_remove->ac_iid))\n                || (result = _anjay_notify_queue_instance_removed(\n                            new_notifications_queue,\n                            &MAKE_INSTANCE_PATH(ANJAY_DM_OID_ACCESS_CONTROL,\n                                                instances_to_remove->ac_iid))));\n    }\n    AVS_LIST_CLEAR(&ssid_list);\n    return result;\n}\n\ntypedef struct anjay_acl_ref_validation_object_info_struct {\n    anjay_oid_t oid;\n    AVS_LIST(anjay_iid_t) allowed_iids;\n} acl_ref_validation_object_info_t;\n\nstatic AVS_LIST(anjay_iid_t)\ncreate_allowed_iids_set(anjay_unlocked_t *anjay,\n                        const anjay_dm_installed_object_t *obj) {\n    AVS_LIST(anjay_iid_t) result = NULL;\n    if (_anjay_dm_get_sorted_instance_list(anjay, obj, &result)) {\n        return NULL;\n    }\n    // ANJAY_ID_INVALID is also allowed for AC target\n    AVS_LIST(anjay_iid_t) iid = AVS_LIST_NEW_ELEMENT(anjay_iid_t);\n    if (!iid) {\n        AVS_LIST_CLEAR(&result);\n        return NULL;\n    }\n    *iid = ANJAY_ID_INVALID;\n    AVS_LIST_APPEND(&result, iid);\n    return result;\n}\n\nstatic acl_ref_validation_object_info_t *\nget_or_create_validation_object_info(anjay_unlocked_t *anjay,\n                                     const anjay_dm_installed_object_t *obj,\n                                     anjay_acl_ref_validation_ctx_t *ctx) {\n    AVS_LIST(acl_ref_validation_object_info_t) *it;\n    AVS_LIST_FOREACH_PTR(it, &ctx->object_infos) {\n        if ((*it)->oid == _anjay_dm_installed_object_oid(obj)) {\n            return *it;\n        } else if ((*it)->oid > _anjay_dm_installed_object_oid(obj)) {\n            break;\n        }\n    }\n    if (!AVS_LIST_INSERT_NEW(acl_ref_validation_object_info_t, it)) {\n        return NULL;\n    }\n    (*it)->oid = _anjay_dm_installed_object_oid(obj);\n    if (!((*it)->allowed_iids = create_allowed_iids_set(anjay, obj))) {\n        AVS_LIST_DELETE(it);\n        return NULL;\n    }\n    return *it;\n}\n\nvoid _anjay_acl_ref_validation_ctx_cleanup(\n        anjay_acl_ref_validation_ctx_t *ctx) {\n    AVS_LIST_CLEAR(&ctx->object_infos) {\n        AVS_LIST_CLEAR(&ctx->object_infos->allowed_iids);\n    }\n}\n\nint _anjay_acl_ref_validate_inst_ref(anjay_unlocked_t *anjay,\n                                     anjay_acl_ref_validation_ctx_t *ctx,\n                                     anjay_oid_t target_oid,\n                                     anjay_iid_t target_iid) {\n    const anjay_dm_installed_object_t *obj =\n            _anjay_dm_find_object_by_oid(&anjay->dm, target_oid);\n    if (!obj) {\n        return -1;\n    }\n    acl_ref_validation_object_info_t *object_info =\n            get_or_create_validation_object_info(anjay, obj, ctx);\n    if (!object_info) {\n        _anjay_log_oom();\n        return -1;\n    }\n    AVS_LIST(anjay_iid_t) *allowed_iid_ptr = &object_info->allowed_iids;\n    while (*allowed_iid_ptr && **allowed_iid_ptr < target_iid) {\n        AVS_LIST_ADVANCE_PTR(&allowed_iid_ptr);\n    }\n    if (!*allowed_iid_ptr || **allowed_iid_ptr != target_iid) {\n        return -1;\n    }\n    AVS_LIST_DELETE(allowed_iid_ptr);\n    return 0;\n}\n\ntypedef struct {\n    anjay_acl_ref_validation_ctx_t validation_ctx;\n    AVS_LIST(anjay_iid_t) iids_to_remove;\n} enumerate_instances_to_remove_args_t;\n\n/**\n * Puts IIDs for Accces Control instances that do not refer to any valid object\n * instance onto the args->iids_to_remove list.\n */\nstatic int\nenumerate_instances_to_remove_clb(anjay_unlocked_t *anjay,\n                                  const anjay_dm_installed_object_t *obj,\n                                  anjay_iid_t iid,\n                                  void *args_) {\n    (void) obj;\n    enumerate_instances_to_remove_args_t *args =\n            (enumerate_instances_to_remove_args_t *) args_;\n    anjay_oid_t target_oid;\n    anjay_iid_t target_iid;\n    int result = read_ids_from_ac_instance(anjay, iid, &target_oid, &target_iid,\n                                           NULL);\n    if (!result\n            && _anjay_acl_ref_validate_inst_ref(anjay, &args->validation_ctx,\n                                                target_oid, target_iid)) {\n        if (!AVS_LIST_INSERT_NEW(anjay_iid_t, &args->iids_to_remove)) {\n            _anjay_log_oom();\n            return -1;\n        }\n        *args->iids_to_remove = iid;\n    }\n    return result;\n}\n\n/**\n * Removes Access Control instances that do not refer to any valid object\n * instance.\n *\n * This function does not check LwM2M Gateway related prefix in\n * new_notifications_queue because it only removes existing AC entries and adds\n * them to the queue, and End IoT Devices DMs are not added to AC in the first\n * place, so they won't be removed as well.\n */\nstatic int perform_removes(anjay_unlocked_t *anjay,\n                           const anjay_dm_installed_object_t *ac_obj,\n                           anjay_notify_queue_t *new_notifications_queue) {\n    enumerate_instances_to_remove_args_t args = {\n        .validation_ctx = _anjay_acl_ref_validation_ctx_new(),\n        .iids_to_remove = NULL\n    };\n\n    int result = _anjay_dm_foreach_instance(\n            anjay, ac_obj, enumerate_instances_to_remove_clb, &args);\n    _anjay_acl_ref_validation_ctx_cleanup(&args.validation_ctx);\n    AVS_LIST_CLEAR(&args.iids_to_remove) {\n        (void) (result\n                || (result = _anjay_dm_call_instance_remove(\n                            anjay, ac_obj, *args.iids_to_remove))\n                || (result = _anjay_notify_queue_instance_removed(\n                            new_notifications_queue,\n                            &MAKE_INSTANCE_PATH(ANJAY_DM_OID_ACCESS_CONTROL,\n                                                *args.iids_to_remove))));\n    }\n    return result;\n}\n\ntypedef struct {\n    bool oid_found;\n    bool oiid_found;\n    bool acl_found;\n    bool owner_found;\n} validate_resources_to_write_clb_args_t;\n\nstatic int\nvalidate_resources_to_write_clb(anjay_unlocked_t *anjay,\n                                const anjay_dm_installed_object_t *obj,\n                                anjay_iid_t iid,\n                                anjay_rid_t rid,\n                                anjay_dm_resource_kind_t kind,\n                                anjay_dm_resource_presence_t presence,\n                                void *args_) {\n    (void) anjay;\n    (void) obj;\n    (void) iid;\n    (void) presence;\n    validate_resources_to_write_clb_args_t *args =\n            (validate_resources_to_write_clb_args_t *) args_;\n    // we act as part of the bootstrap process,\n    // so we don't check the writable flag\n    switch (rid) {\n    case ANJAY_DM_RID_ACCESS_CONTROL_OID:\n        if (!args->oid_found && _anjay_dm_res_kind_single_readable(kind)) {\n            args->oid_found = true;\n            return 0;\n        }\n        return -1;\n    case ANJAY_DM_RID_ACCESS_CONTROL_OIID:\n        if (!args->oiid_found && _anjay_dm_res_kind_single_readable(kind)) {\n            args->oiid_found = true;\n            return 0;\n        }\n        return -1;\n    case ANJAY_DM_RID_ACCESS_CONTROL_ACL:\n        if (!args->acl_found && _anjay_dm_res_kind_multiple(kind)) {\n            args->acl_found = true;\n            return 0;\n        }\n        return -1;\n    case ANJAY_DM_RID_ACCESS_CONTROL_OWNER:\n        if (!args->owner_found && _anjay_dm_res_kind_single_readable(kind)) {\n            args->owner_found = true;\n            return 0;\n        }\n        return -1;\n    default:\n        return 0;\n    }\n}\n\nstatic int\nvalidate_resources_to_write(anjay_unlocked_t *anjay,\n                            const anjay_dm_installed_object_t *ac_obj,\n                            anjay_iid_t ac_iid) {\n    validate_resources_to_write_clb_args_t args = {\n        .oid_found = false,\n        .oiid_found = false,\n        .acl_found = false,\n        .owner_found = false\n    };\n    int result =\n            _anjay_dm_foreach_resource(anjay, ac_obj, ac_iid,\n                                       validate_resources_to_write_clb, &args);\n    if (!result\n            && !(args.oid_found && args.oiid_found && args.acl_found\n                 && args.owner_found)) {\n        result = -1;\n    }\n    return result;\n}\n\n/**\n * Creates Access Control object instances for objects instances listed in\n * known_added_iids entries inside incoming_queue.\n */\nstatic int perform_adds(anjay_unlocked_t *anjay,\n                        const anjay_dm_installed_object_t *ac_obj,\n                        anjay_ssid_t origin_ssid,\n                        anjay_notify_queue_t *notifications_queue) {\n    AVS_LIST(anjay_notify_queue_object_entry_t) it;\n    AVS_LIST_FOREACH(it, *notifications_queue) {\n#    ifdef ANJAY_WITH_LWM2M_GATEWAY\n        // EID's DMs are not guarded by AC\n        if (it->prefix[0] != '\\0') {\n            continue;\n        }\n#    endif // ANJAY_WITH_LWM2M_GATEWAY\n        if (it->oid == ANJAY_DM_OID_SECURITY\n                || it->oid == ANJAY_DM_OID_ACCESS_CONTROL) {\n            continue;\n        }\n\n        // create Access Control object instances for created instances\n        AVS_LIST(anjay_iid_t) iid_it;\n        AVS_LIST_FOREACH(iid_it, it->instance_set_changes.known_added_iids) {\n            int result = find_ac_instance_by_target(anjay, ac_obj, NULL,\n                                                    it->oid, *iid_it);\n            if (!result) {\n                // AC instance already exists, skip\n                continue;\n            }\n            anjay_iid_t ac_iid;\n            if (result != ANJAY_ERR_NOT_FOUND\n                    || (result = _anjay_dm_select_free_iid(anjay, ac_obj,\n                                                           &ac_iid))\n                    || (result = _anjay_dm_call_instance_create(anjay, ac_obj,\n                                                                ac_iid))\n                    || (result = validate_resources_to_write(anjay, ac_obj,\n                                                             ac_iid))\n                    || (result = write_u16(anjay, ac_obj, ac_iid,\n                                           ANJAY_DM_RID_ACCESS_CONTROL_OID,\n                                           ANJAY_ID_INVALID, it->oid))\n                    || (result = write_u16(anjay, ac_obj, ac_iid,\n                                           ANJAY_DM_RID_ACCESS_CONTROL_OIID,\n                                           ANJAY_ID_INVALID, *iid_it))\n                    || (result = write_u16(anjay, ac_obj, ac_iid,\n                                           ANJAY_DM_RID_ACCESS_CONTROL_ACL,\n                                           origin_ssid,\n                                           ANJAY_ACCESS_MASK_FULL\n                                                   & ~ANJAY_ACCESS_MASK_CREATE))\n                    || (result = write_u16(anjay, ac_obj, ac_iid,\n                                           ANJAY_DM_RID_ACCESS_CONTROL_OWNER,\n                                           ANJAY_ID_INVALID, origin_ssid))\n                    || (result = _anjay_notify_queue_instance_created(\n                                notifications_queue,\n                                &MAKE_INSTANCE_PATH(ANJAY_DM_OID_ACCESS_CONTROL,\n                                                    ac_iid)))) {\n                return result;\n            }\n        }\n    }\n    return 0;\n}\n\nstatic const anjay_notify_queue_object_entry_t *\nget_ac_notif_entry(anjay_notify_queue_t queue) {\n    AVS_LIST(anjay_notify_queue_object_entry_t) it;\n    AVS_LIST_FOREACH(it, queue) {\n        // Queue entries are sorted by OID, compare with\n        // find_or_create_object_entry() in notify.c\n        if (it->oid >= ANJAY_DM_OID_ACCESS_CONTROL) {\n            break;\n        }\n    }\n    if (it && it->oid == ANJAY_DM_OID_ACCESS_CONTROL) {\n        return it;\n    }\n    return NULL;\n}\n\n/**\n * This function does not check LwM2M Gateway related prefix in\n * new_notifications_queue because it only processes entries with\n * OID==Access Control Object, and there are no Access Control Object Instances\n * related to LwM2M Gateway.\n */\nstatic int generate_apparent_instance_set_change_notifications(\n        anjay_unlocked_t *anjay, anjay_notify_queue_t *notifications_queue) {\n    const anjay_notify_queue_object_entry_t *ac_notif =\n            get_ac_notif_entry(*notifications_queue);\n    if (!ac_notif) {\n        return 0;\n    }\n\n    anjay_iid_t last_iid = ANJAY_ID_INVALID;\n    AVS_LIST(anjay_notify_queue_resource_entry_t) it;\n    AVS_LIST_FOREACH(it, ac_notif->resources_changed) {\n        // Resource entries are sorted lexicographically over (IID, RID) pairs,\n        // compare with _anjay_notify_queue_resource_change() in notify.c\n        if (it->iid == last_iid) {\n            continue;\n        }\n\n        last_iid = it->iid;\n        anjay_oid_t target_oid;\n        int result;\n        if ((result = read_ids_from_ac_instance(anjay, it->iid, &target_oid,\n                                                NULL, NULL))\n                || (result = _anjay_notify_queue_instance_set_unknown_change(\n                            notifications_queue,\n                            &MAKE_OBJECT_PATH(target_oid)))) {\n            return result;\n        }\n    }\n    return 0;\n}\n\n#endif // ANJAY_WITH_ACCESS_CONTROL\n\nint _anjay_sync_access_control(anjay_unlocked_t *anjay,\n                               anjay_ssid_t origin_ssid,\n                               anjay_notify_queue_t *notifications_queue) {\n#ifndef ANJAY_WITH_ACCESS_CONTROL\n    (void) anjay;\n    (void) origin_ssid;\n    (void) notifications_queue;\n    return 0;\n#else  // ANJAY_WITH_ACCESS_CONTROL\n    const anjay_dm_installed_object_t *ac_obj = get_access_control(anjay);\n    if (!ac_obj) {\n        return 0;\n    }\n    bool might_caused_orphaned_ac_instances;\n    bool have_adds;\n    bool might_have_removes;\n    what_changed(origin_ssid, *notifications_queue,\n                 &might_caused_orphaned_ac_instances, &have_adds,\n                 &might_have_removes);\n    if (avs_is_err(_anjay_dm_transaction_begin(anjay))) {\n        return ANJAY_ERR_INTERNAL;\n    }\n    int result = 0;\n    if (might_have_removes) {\n        result = perform_removes(anjay, ac_obj, notifications_queue);\n    }\n    if (!result && might_caused_orphaned_ac_instances) {\n        result = remove_orphaned_instances(anjay, ac_obj, notifications_queue);\n    }\n    if (!result && have_adds) {\n        result = perform_adds(anjay, ac_obj, origin_ssid, notifications_queue);\n    }\n    if (!result) {\n        result = generate_apparent_instance_set_change_notifications(\n                anjay, notifications_queue);\n    }\n    return _anjay_dm_transaction_finish(anjay, result);\n#endif // ANJAY_WITH_ACCESS_CONTROL\n}\n\n#ifdef ANJAY_TEST\n#    include \"tests/core/access_utils.c\"\n#endif // ANJAY_TEST\n"
  },
  {
    "path": "src/core/anjay_access_utils_private.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ANJAY_ACCESS_UTILS_PRIVATE_H\n#define ANJAY_ACCESS_UTILS_PRIVATE_H\n\n#include \"anjay_core.h\"\n#include \"anjay_dm_core.h\"\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\ntypedef struct {\n    anjay_oid_t oid;\n    anjay_iid_t iid;\n    anjay_ssid_t ssid;\n    anjay_request_action_t action;\n#ifdef ANJAY_WITH_LWM2M_GATEWAY\n    bool end_device;\n#endif // ANJAY_WITH_LWM2M_GATEWAY\n} anjay_action_info_t;\n\ntypedef enum {\n    ANJAY_INSTANCE_ACTION_DISALLOWED,\n    ANJAY_INSTANCE_ACTION_ALLOWED,\n#ifdef ANJAY_WITH_ACCESS_CONTROL\n    ANJAY_INSTANCE_ACTION_NEEDS_ACL_CHECK\n#endif // ANJAY_WITH_ACCESS_CONTROL\n} anjay_instance_action_allowed_stateless_result_t;\n\n/**\n * Checks whether an operation described by the @p info on a non-restricted\n * Object is allowed. Security checks for restricted objects shall be performed\n * elsewhere.\n *\n * Restricted Objects in LwM2M 1.0 are:\n *  - Security Object (/0)\n *\n * NOTE: The instance ID may be @ref ANJAY_ID_INVALID only if the operation is\n * Create.\n */\nbool _anjay_instance_action_allowed(anjay_unlocked_t *anjay,\n                                    const anjay_action_info_t *info);\n\n/**\n * Checks whether an operation described by the @p info on a non-restricted\n * Object is allowed, but only if that can be determined without accessing the\n * data model.\n *\n * Returns @ref ANJAY_INSTANCE_ACTION_NEEDS_ACL_CHECK if it is not possible.\n */\nanjay_instance_action_allowed_stateless_result_t\n_anjay_instance_action_allowed_stateless(anjay_unlocked_t *anjay,\n                                         const anjay_action_info_t *info);\n\n#ifdef ANJAY_WITH_ACCESS_CONTROL\n/**\n * Accesses the data model to check whether an operation described by @p info\n * is allowed.\n *\n * May only be called on an @p info object previously checked using @ref\n * _anjay_instance_action_allowed_stateless which returned @ref\n * ANJAY_INSTANCE_ACTION_NEEDS_ACL_CHECK result. The behavior is undefined\n * otherwise.\n *\n * @ref _anjay_instance_action_allowed_stateless and @ref\n * _anjay_instance_action_allowed_by_acl together shall have identical semantics\n * to @ref _anjay_instance_action_allowed.\n */\nbool _anjay_instance_action_allowed_by_acl(anjay_unlocked_t *anjay,\n                                           const anjay_action_info_t *info);\n#endif // ANJAY_WITH_ACCESS_CONTROL\n\n/**\n * Performs implicit creations and deletions of Access Control object instances\n * according to data model changes.\n *\n * Specifically, it performs three steps:\n *\n * 1. Removes all Access Control object instances that refer to Object Instances\n *    that have been removed from the data model.\n * 2. If there were changes to the Security object, removes all ACL entries\n *    (i.e., ACL Resource Instances) that refer to SSIDs of Servers who are no\n *    longer represented in the data model. This may cause changing the owner of\n *    those Access Control object instances which have multiple ACL entries, or\n *    removal of instances for which the ACL would be empty. In the latter case,\n *    the referred Object Instances are removed as well (see LwM2M TS 1.0.2,\n *    E.1.3 Unbootstrapping).\n * 3. Creates new Access Control object instances that refer to all newly\n *    created Object Instances. These will have the owner and the default ACL\n *    referring to SSID == <c>origin_ssid</c> parameter.\n *\n * Please refer to comments inside the implementation for details.\n */\nint _anjay_sync_access_control(anjay_unlocked_t *anjay,\n                               anjay_ssid_t origin_ssid,\n                               anjay_notify_queue_t *notifications_queue);\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif /* ANJAY_ACCESS_UTILS_PRIVATE_H */\n"
  },
  {
    "path": "src/core/anjay_bootstrap_core.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#include <inttypes.h>\n\n#include <avsystem/commons/avs_errno.h>\n#include <avsystem/commons/avs_stream_membuf.h>\n\n#include <avsystem/coap/async_client.h>\n#include <avsystem/coap/code.h>\n#include <avsystem/coap/streaming.h>\n\n#include <anjay_modules/anjay_bootstrap.h>\n#include <anjay_modules/anjay_notify.h>\n#include <anjay_modules/anjay_time_defs.h>\n\n#include \"anjay_bootstrap_core.h\"\n#include \"anjay_core.h\"\n#include \"anjay_io_core.h\"\n#include \"anjay_servers_utils.h\"\n#include \"coap/anjay_content_format.h\"\n#include \"dm/anjay_discover.h\"\n#include \"dm/anjay_dm_read.h\"\n#include \"dm/anjay_query.h\"\n\n#ifdef ANJAY_TEST\n#    include \"tests/core/bootstrap_mock.h\"\n#endif // ANJAY_TEST\n\nVISIBILITY_SOURCE_BEGIN\n\n#ifdef ANJAY_WITH_BOOTSTRAP\n\n#    ifdef ANJAY_WITH_BOOTSTRAP_PACK\n#        ifdef ANJAY_WITH_CBOR\n#            define BOOTSTRAP_PACK_ACCEPT_FORMAT AVS_COAP_FORMAT_SENML_CBOR\n#        else // ANJAY_WITH_CBOR\n#            define BOOTSTRAP_PACK_ACCEPT_FORMAT AVS_COAP_FORMAT_SENML_JSON\n#        endif // ANJAY_WITH_CBOR\n#    endif     // ANJAY_WITH_BOOTSTRAP_PACK\n\nstatic void cancel_client_initiated_bootstrap(anjay_unlocked_t *anjay) {\n    avs_sched_del(&anjay->bootstrap.client_initiated_bootstrap_handle);\n}\n\nstatic void cancel_est_sren(anjay_unlocked_t *anjay) {\n    (void) anjay;\n}\n\nstatic int suspend_nonbootstrap_server(anjay_unlocked_t *anjay,\n                                       anjay_server_info_t *server,\n                                       void *data) {\n    (void) anjay;\n    (void) data;\n    if (_anjay_server_ssid(server) != ANJAY_SSID_BOOTSTRAP) {\n        anjay_connection_type_t conn_type;\n        ANJAY_CONNECTION_TYPE_FOREACH(conn_type) {\n            _anjay_connection_suspend((anjay_connection_ref_t) {\n                .server = server,\n                .conn_type = conn_type\n            });\n        }\n    }\n    return 0;\n}\n\nstatic avs_error_t start_bootstrap_if_not_already_started(\n        anjay_unlocked_t *anjay,\n        anjay_connection_ref_t bootstrap_connection,\n        bool cancel_ongoing_request) {\n    if (!anjay->bootstrap.in_progress) {\n        avs_error_t err = _anjay_dm_transaction_begin(anjay);\n        if (avs_is_err(err)) {\n            return err;\n        }\n    }\n    if (bootstrap_connection.server) {\n        anjay->bootstrap.bootstrap_session_token =\n                _anjay_server_primary_session_token(\n                        bootstrap_connection.server);\n        if (cancel_ongoing_request\n                && avs_coap_exchange_id_valid(\n                           anjay->bootstrap.outgoing_request_exchange_id)) {\n            avs_coap_exchange_cancel(\n                    _anjay_connection_get_coap(bootstrap_connection),\n                    anjay->bootstrap.outgoing_request_exchange_id);\n        }\n    }\n    if (!anjay->bootstrap.in_progress) {\n        // clear inactive servers so that they won't attempt to retry; they will\n        // be recreated during _anjay_schedule_reload_servers() after bootstrap\n        // procedure is finished\n        _anjay_servers_cleanup_inactive_nonbootstrap(anjay);\n        // suspend active connections\n        _anjay_servers_foreach_active(anjay, suspend_nonbootstrap_server, NULL);\n\n        avs_sched_del(&anjay->bootstrap.purge_bootstrap_handle);\n    }\n    anjay->bootstrap.in_progress = true;\n#    ifdef ANJAY_WITH_CONN_STATUS_API\n    if (bootstrap_connection.server) {\n        _anjay_set_server_connection_status(\n                bootstrap_connection.server,\n                ANJAY_SERV_CONN_STATUS_BOOTSTRAPPING);\n    }\n#    endif // ANJAY_WITH_CONN_STATUS_API\n    return AVS_OK;\n}\n\nstatic void abort_bootstrap(anjay_unlocked_t *anjay) {\n    if (anjay->bootstrap.in_progress) {\n        _anjay_dm_transaction_rollback(anjay);\n        anjay->bootstrap.in_progress = false;\n        _anjay_conn_session_token_reset(\n                &anjay->bootstrap.bootstrap_session_token,\n                &anjay->session_token_counter);\n        _anjay_schedule_reload_servers(anjay);\n    }\n}\n\nstatic void bootstrap_remove_notify_changed(anjay_bootstrap_t *bootstrap,\n                                            anjay_oid_t oid,\n                                            anjay_iid_t iid) {\n    AVS_LIST(anjay_notify_queue_object_entry_t) *obj_it;\n    AVS_LIST_FOREACH_PTR(obj_it, &bootstrap->notification_queue) {\n        if ((*obj_it)->oid > oid) {\n            return;\n        } else if ((*obj_it)->oid == oid) {\n            break;\n        }\n    }\n    if (!*obj_it) {\n        return;\n    }\n    AVS_LIST(anjay_notify_queue_resource_entry_t) *res_it;\n    AVS_LIST_FOREACH_PTR(res_it, &(*obj_it)->resources_changed) {\n        if ((*res_it)->iid >= iid) {\n            break;\n        }\n    }\n    while (*res_it && (*res_it)->iid == iid) {\n        AVS_LIST_DELETE(res_it);\n    }\n}\n\nstatic uint8_t make_success_response_code(anjay_request_action_t action) {\n    switch (action) {\n    case ANJAY_ACTION_READ:\n        return AVS_COAP_CODE_CONTENT;\n    case ANJAY_ACTION_WRITE:\n        return AVS_COAP_CODE_CHANGED;\n    case ANJAY_ACTION_DELETE:\n        return AVS_COAP_CODE_DELETED;\n    case ANJAY_ACTION_DISCOVER:\n        return AVS_COAP_CODE_CONTENT;\n    case ANJAY_ACTION_BOOTSTRAP_FINISH:\n        return AVS_COAP_CODE_CHANGED;\n    default:\n        break;\n    }\n    return (uint8_t) (-ANJAY_ERR_INTERNAL);\n}\n\ntypedef int with_instance_on_demand_cb_t(anjay_unlocked_t *anjay,\n                                         const anjay_dm_installed_object_t *obj,\n                                         anjay_iid_t iid,\n                                         anjay_unlocked_input_ctx_t *in_ctx);\n\nstatic int\nwrite_resource_and_move_to_next_entry(anjay_unlocked_t *anjay,\n                                      const anjay_dm_installed_object_t *obj,\n                                      anjay_iid_t iid,\n                                      anjay_unlocked_input_ctx_t *in_ctx) {\n    (void) iid;\n    return _anjay_dm_write_resource_and_move_to_next_entry(\n            anjay, obj, in_ctx, &anjay->bootstrap.notification_queue);\n}\n\nstatic int write_instance_and_move_to_next_entry_inner(\n        anjay_unlocked_t *anjay,\n        const anjay_dm_installed_object_t *obj,\n        anjay_iid_t iid,\n        anjay_unlocked_input_ctx_t *in_ctx) {\n    int retval;\n    anjay_uri_path_t path;\n    while (!(retval = _anjay_input_get_path(in_ctx, &path, NULL))) {\n        if (path.ids[ANJAY_ID_IID] != iid\n                || path.ids[ANJAY_ID_OID]\n                               != _anjay_dm_installed_object_oid(obj)) {\n            /* another instance or object */\n            break;\n        }\n        if (_anjay_uri_path_has(&path, ANJAY_ID_RID)) {\n            /* non-empty instance */\n            retval = write_resource_and_move_to_next_entry(anjay, obj, iid,\n                                                           in_ctx);\n            if (retval == ANJAY_ERR_NOT_FOUND\n                    || retval == ANJAY_ERR_NOT_IMPLEMENTED) {\n                // LwM2M spec, 5.2.7.1 BOOTSTRAP WRITE:\n                // \"When the 'Write' operation targets an Object or an Object\n                // Instance, the LwM2M Client MUST ignore optional resources it\n                // does not support in the payload.\" - so, continue on these\n                // errors.\n                anjay_log(WARNING,\n                          _(\"Ignoring error during BOOTSTRAP WRITE to \") \"%s\" _(\n                                  \": \") \"%d\",\n                          ANJAY_DEBUG_MAKE_PATH(&path), retval);\n                retval = 0;\n            }\n        } else {\n            retval = _anjay_input_next_entry(in_ctx);\n        }\n        if (retval) {\n            return retval;\n        }\n    }\n    return (retval == ANJAY_GET_PATH_END) ? 0 : retval;\n}\n\nstatic int with_instance_on_demand(anjay_unlocked_t *anjay,\n                                   const anjay_dm_installed_object_t *obj,\n                                   anjay_iid_t iid,\n                                   anjay_unlocked_input_ctx_t *in_ctx,\n                                   with_instance_on_demand_cb_t callback) {\n    int result = 0;\n    int ipresent = _anjay_dm_instance_present(anjay, obj, iid);\n    anjay_oid_t oid = _anjay_dm_installed_object_oid(obj);\n    if (ipresent < 0) {\n        return ipresent;\n    } else if (ipresent == 0\n               && (result = _anjay_dm_call_instance_create(anjay, obj, iid))) {\n        anjay_log(DEBUG,\n                  _(\"Instance Create handler for object \") \"%\" PRIu16 _(\n                          \" failed\"),\n                  oid);\n        return result;\n    }\n\n    if (!result) {\n        result = callback(anjay, obj, iid, in_ctx);\n    }\n    if (ipresent == 0 && !result) {\n        result = _anjay_notify_queue_instance_created(\n                &anjay->bootstrap.notification_queue,\n                &MAKE_INSTANCE_PATH(oid, iid));\n    }\n    return result;\n}\n\nstatic int\nwrite_instance_and_move_to_next_entry(anjay_unlocked_t *anjay,\n                                      const anjay_dm_installed_object_t *obj,\n                                      anjay_iid_t iid,\n                                      anjay_unlocked_input_ctx_t *in_ctx) {\n    return with_instance_on_demand(anjay, obj, iid, in_ctx,\n                                   write_instance_and_move_to_next_entry_inner);\n}\n\nstatic int\nwrite_object_and_move_to_next_entry(anjay_unlocked_t *anjay,\n                                    const anjay_dm_installed_object_t *obj,\n                                    anjay_unlocked_input_ctx_t *in_ctx) {\n    // should it remove existing instances?\n    int retval;\n    do {\n        anjay_uri_path_t path;\n        if ((retval = _anjay_input_get_path(in_ctx, &path, NULL))) {\n            if (retval == ANJAY_GET_PATH_END) {\n                retval = 0;\n            }\n            break;\n        }\n        if (path.ids[ANJAY_ID_IID] == ANJAY_ID_INVALID) {\n            retval = ANJAY_ERR_BAD_REQUEST;\n            break;\n        }\n        if (path.ids[ANJAY_ID_OID] != _anjay_dm_installed_object_oid(obj)) {\n            /* another object */\n            break;\n        }\n        retval = write_instance_and_move_to_next_entry(\n                anjay, obj, path.ids[ANJAY_ID_IID], in_ctx);\n    } while (!retval);\n    return retval;\n}\n\nstatic int security_object_valid_handler(anjay_unlocked_t *anjay,\n                                         const anjay_dm_installed_object_t *obj,\n                                         anjay_iid_t iid,\n                                         void *bootstrap_instances) {\n    (void) obj;\n    if (!_anjay_is_bootstrap_security_instance(anjay, iid)) {\n        return 0;\n    }\n    if (++*((uintptr_t *) bootstrap_instances) > 1) {\n        return ANJAY_FOREACH_BREAK;\n    }\n    return 0;\n}\n\nstatic bool has_multiple_bootstrap_security_instances(anjay_unlocked_t *anjay) {\n    uintptr_t bootstrap_instances = 0;\n    const anjay_dm_installed_object_t *obj =\n            _anjay_dm_find_object_by_oid(&anjay->dm, ANJAY_DM_OID_SECURITY);\n    if (_anjay_dm_foreach_instance(anjay, obj, security_object_valid_handler,\n                                   &bootstrap_instances)\n            || bootstrap_instances > 1) {\n        return true;\n    }\n    return false;\n}\n\n#    ifdef ANJAY_WITH_LWM2M11\nstatic int update_last_bootstrapped_time(anjay_unlocked_t *anjay,\n                                         const anjay_dm_installed_object_t *obj,\n                                         anjay_iid_t iid) {\n    anjay_iid_t server_iid;\n    uint16_t ssid;\n    assert(obj);\n    if (_anjay_dm_installed_object_oid(obj) == ANJAY_DM_OID_SECURITY) {\n        if (!_anjay_dm_find_object_by_oid(&anjay->dm, ANJAY_DM_OID_SERVER)\n                || _anjay_ssid_from_security_iid(anjay, iid, &ssid)\n                || _anjay_find_server_iid(anjay, ssid, &server_iid)) {\n            // It isn't an error if Server Object instance doesn't exist, or if\n            // SSID is not yet set for a Security Object instance - all that\n            // might be set later.\n            return 0;\n        }\n    } else {\n        assert(_anjay_dm_installed_object_oid(obj) == ANJAY_DM_OID_SERVER);\n        server_iid = iid;\n    }\n\n    int64_t timestamp;\n    int retval = avs_time_real_to_scalar(&timestamp, AVS_TIME_S,\n                                         avs_time_real_now());\n    if (retval) {\n        return retval;\n    }\n\n    anjay_uri_path_t path =\n            MAKE_RESOURCE_PATH(ANJAY_DM_OID_SERVER, server_iid,\n                               ANJAY_DM_RID_SERVER_LAST_BOOTSTRAPPED);\n\n    return _anjay_dm_write_resource_i64(anjay, path, timestamp,\n                                        &anjay->bootstrap.notification_queue);\n}\n#    endif // ANJAY_WITH_LWM2M11\n\nstatic int bootstrap_write_impl(anjay_unlocked_t *anjay,\n                                anjay_connection_ref_t bootstrap_connection,\n                                const anjay_uri_path_t *uri,\n                                anjay_unlocked_input_ctx_t *in_ctx) {\n    anjay_log(LAZY_DEBUG, _(\"Bootstrap Write \") \"%s\",\n              ANJAY_DEBUG_MAKE_PATH(uri));\n    if (!_anjay_uri_path_has(uri, ANJAY_ID_OID)\n            || _anjay_uri_path_has(uri, ANJAY_ID_RIID)) {\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n    cancel_client_initiated_bootstrap(anjay);\n    cancel_est_sren(anjay);\n    if (avs_is_err(start_bootstrap_if_not_already_started(\n                anjay, bootstrap_connection, true))) {\n        return ANJAY_ERR_INTERNAL;\n    }\n    const anjay_dm_installed_object_t *obj =\n            _anjay_dm_find_object_by_oid(&anjay->dm, uri->ids[ANJAY_ID_OID]);\n    if (!obj) {\n        anjay_log(DEBUG, _(\"Object not found: \") \"%u\", uri->ids[ANJAY_ID_OID]);\n        return ANJAY_ERR_NOT_FOUND;\n    }\n\n    int retval = -1;\n    if (_anjay_uri_path_leaf_is(uri, ANJAY_ID_OID)) {\n        retval = write_object_and_move_to_next_entry(anjay, obj, in_ctx);\n    } else if (_anjay_uri_path_leaf_is(uri, ANJAY_ID_IID)) {\n        retval = write_instance_and_move_to_next_entry(\n                anjay, obj, uri->ids[ANJAY_ID_IID], in_ctx);\n    } else if (_anjay_uri_path_leaf_is(uri, ANJAY_ID_RID)) {\n        retval = with_instance_on_demand(anjay, obj, uri->ids[ANJAY_ID_IID],\n                                         in_ctx,\n                                         write_resource_and_move_to_next_entry);\n    }\n    if (!retval && uri->ids[ANJAY_ID_OID] == ANJAY_DM_OID_SECURITY) {\n        if (has_multiple_bootstrap_security_instances(anjay)) {\n            anjay_log(DEBUG, _(\"Multiple Security Object instances configured \"\n                               \"for the Bootstrap Server Account\"));\n            retval = ANJAY_ERR_BAD_REQUEST;\n        }\n    }\n\n#    ifdef ANJAY_WITH_LWM2M11\n    if (retval\n            || (uri->ids[ANJAY_ID_OID] != ANJAY_DM_OID_SECURITY\n                && uri->ids[ANJAY_ID_OID] != ANJAY_DM_OID_SERVER)\n            // If Write on entire object is performed, this function will be\n            // called again with Instance ID passed to it.\n            || uri->ids[ANJAY_ID_IID] == ANJAY_ID_INVALID) {\n        return retval;\n    } else {\n        return update_last_bootstrapped_time(anjay, obj,\n                                             uri->ids[ANJAY_ID_IID]);\n    }\n#    else  // ANJAY_WITH_LWM2M11\n    return retval;\n#    endif // ANJAY_WITH_LWM2M11\n}\n\n#    ifdef ANJAY_WITH_MODULE_FACTORY_PROVISIONING\nint _anjay_bootstrap_write_composite(anjay_unlocked_t *anjay,\n                                     anjay_unlocked_input_ctx_t *in_ctx) {\n    anjay_log(DEBUG, _(\"Bootstrap Write from CBOR context\"));\n    cancel_client_initiated_bootstrap(anjay);\n    cancel_est_sren(anjay);\n    start_bootstrap_if_not_already_started(\n            anjay, (anjay_connection_ref_t) { NULL }, true);\n\n    int retval;\n    anjay_uri_path_t path;\n    while (!(retval = _anjay_input_get_path(in_ctx, &path, NULL))) {\n        if (!_anjay_uri_path_has(&path, ANJAY_ID_RID)) {\n            return ANJAY_ERR_BAD_REQUEST;\n        }\n\n        const anjay_dm_installed_object_t *obj =\n                _anjay_dm_find_object_by_oid(&anjay->dm,\n                                             path.ids[ANJAY_ID_OID]);\n        if (!obj) {\n            anjay_log(DEBUG, _(\"Object not found: \") \"%u\",\n                      path.ids[ANJAY_ID_OID]);\n            return ANJAY_ERR_NOT_FOUND;\n        }\n\n        int ipresent =\n                _anjay_dm_instance_present(anjay, obj, path.ids[ANJAY_ID_IID]);\n        if (ipresent < 0) {\n            return ANJAY_ERR_BAD_REQUEST;\n        } else if (ipresent == 0\n                   && (retval = _anjay_dm_call_instance_create(\n                               anjay, obj, path.ids[ANJAY_ID_IID]))) {\n            return retval;\n        }\n\n        if ((retval = _anjay_dm_call_resource_write(\n                     anjay, obj, path.ids[ANJAY_ID_IID], path.ids[ANJAY_ID_RID],\n                     path.ids[ANJAY_ID_RIID], in_ctx))) {\n            return retval;\n        }\n\n        if ((retval = _anjay_notify_queue_resource_change(\n                     &anjay->bootstrap.notification_queue, &path))) {\n            return retval;\n        }\n\n        if (path.ids[ANJAY_ID_OID] == ANJAY_DM_OID_SERVER\n                || path.ids[ANJAY_ID_OID] == ANJAY_DM_OID_SECURITY) {\n            if ((retval = update_last_bootstrapped_time(\n                         anjay, obj, path.ids[ANJAY_ID_IID]))) {\n                return retval;\n            }\n        }\n\n        if ((retval = _anjay_input_next_entry(in_ctx))) {\n            break;\n        }\n    }\n\n    return (retval == ANJAY_GET_PATH_END) ? 0 : retval;\n}\n#    endif // ANJAY_WITH_MODULE_FACTORY_PROVISIONING\n\nstatic int delete_instance(anjay_unlocked_t *anjay,\n                           const anjay_dm_installed_object_t *obj,\n                           anjay_iid_t iid) {\n    anjay_oid_t oid = _anjay_dm_installed_object_oid(obj);\n    int retval = _anjay_dm_call_instance_remove(anjay, obj, iid);\n    if (retval) {\n        anjay_log(WARNING,\n                  _(\"delete_instance: cannot delete \") \"/%d/%d\" _(\": \") \"%d\",\n                  oid, iid, retval);\n    } else {\n        bootstrap_remove_notify_changed(&anjay->bootstrap, oid, iid);\n        retval = _anjay_notify_queue_instance_removed(\n                &anjay->bootstrap.notification_queue,\n                &MAKE_INSTANCE_PATH(oid, iid));\n    }\n    return retval;\n}\n\ntypedef struct {\n    bool skip_bootstrap;\n    int retval;\n} delete_object_arg_t;\n\nstatic int delete_object(anjay_unlocked_t *anjay,\n                         const anjay_dm_installed_object_t *obj,\n                         void *arg_) {\n    // the contract forbids deleting instances from within\n    // _anjay_dm_list_instances(), so we use a temporary list\n    AVS_LIST(anjay_iid_t) iids = NULL;\n    delete_object_arg_t *arg = (delete_object_arg_t *) arg_;\n    int retval = _anjay_dm_get_sorted_instance_list(anjay, obj, &iids);\n    if (!retval) {\n        AVS_LIST(anjay_iid_t) iid;\n        AVS_LIST_FOREACH(iid, iids) {\n            if (arg->skip_bootstrap\n                    && _anjay_dm_installed_object_oid(obj)\n                                   == ANJAY_DM_OID_SECURITY\n                    && _anjay_is_bootstrap_security_instance(anjay, *iid)) {\n                continue; // don't remove self\n            }\n            if ((retval = delete_instance(anjay, obj, *iid))) {\n                if (retval == ANJAY_ERR_METHOD_NOT_ALLOWED) {\n                    // ignore 4.05 Method Not Allowed\n                    // it most likely means that the Object is non-modifiable\n                    // (transaction or Delete handlers not implemented)\n                    // so we just leave it as it is\n                    retval = 0;\n                } else {\n                    break;\n                }\n            }\n        }\n    }\n    AVS_LIST_CLEAR(&iids);\n    if (!arg->retval) {\n        arg->retval = retval;\n    }\n    return 0;\n}\n\nstatic int bootstrap_delete(anjay_connection_ref_t bootstrap_connection,\n                            const anjay_request_t *request) {\n    anjay_unlocked_t *anjay = _anjay_from_server(bootstrap_connection.server);\n    anjay_log(LAZY_DEBUG, _(\"Bootstrap Delete \") \"%s\",\n              ANJAY_DEBUG_MAKE_PATH(&request->uri));\n    cancel_client_initiated_bootstrap(anjay);\n    cancel_est_sren(anjay);\n    if (avs_is_err(start_bootstrap_if_not_already_started(\n                anjay, bootstrap_connection, true))) {\n        return ANJAY_ERR_INTERNAL;\n    }\n\n    if (request->is_bs_uri\n            || _anjay_uri_path_has(&request->uri, ANJAY_ID_RID)) {\n        return ANJAY_ERR_BAD_REQUEST;\n    }\n\n    int retval = 0;\n    delete_object_arg_t delete_arg = {\n        .skip_bootstrap = true,\n        .retval = 0\n    };\n    if (_anjay_uri_path_has(&request->uri, ANJAY_ID_OID)) {\n        const anjay_dm_installed_object_t *obj =\n                _anjay_dm_find_object_by_oid(&anjay->dm,\n                                             request->uri.ids[ANJAY_ID_OID]);\n        if (!obj) {\n            anjay_log(WARNING, _(\"Object not found: \") \"%u\",\n                      request->uri.ids[ANJAY_ID_OID]);\n            return 0;\n        }\n\n        if (_anjay_uri_path_leaf_is(&request->uri, ANJAY_ID_IID)) {\n            int present =\n                    _anjay_dm_instance_present(anjay, obj,\n                                               request->uri.ids[ANJAY_ID_IID]);\n            if (present > 0) {\n                return delete_instance(anjay, obj,\n                                       request->uri.ids[ANJAY_ID_IID]);\n            } else {\n                return present;\n            }\n        } else {\n            retval = delete_object(anjay, obj, &delete_arg);\n        }\n    } else {\n        retval = _anjay_dm_foreach_object(anjay, &anjay->dm, delete_object,\n                                          &delete_arg);\n    }\n    if (delete_arg.retval) {\n        return delete_arg.retval;\n    } else {\n        return retval;\n    }\n}\n\nstatic int bootstrap_discover(anjay_connection_ref_t bootstrap_connection,\n                              const anjay_request_t *request) {\n#    ifdef ANJAY_WITH_DISCOVER\n    if (\n#        ifdef ANJAY_WITH_LWM2M12\n                request->depth >= 0 ||\n#        endif // ANJAY_WITH_LWM2M12\n                    _anjay_uri_path_has(&request->uri, ANJAY_ID_IID)) {\n        return ANJAY_ERR_BAD_REQUEST;\n    }\n\n    anjay_msg_details_t msg_details = {\n        .msg_code = make_success_response_code(request->action),\n        .format = AVS_COAP_FORMAT_LINK_FORMAT\n    };\n    avs_stream_t *response_stream =\n            _anjay_coap_setup_response_stream(request->ctx, &msg_details);\n    if (!response_stream) {\n        return -1;\n    }\n\n    return _anjay_bootstrap_discover(\n            _anjay_from_server(bootstrap_connection.server), response_stream,\n            request->uri.ids[ANJAY_ID_OID],\n            _anjay_server_registration_info(bootstrap_connection.server)\n                    ->lwm2m_version);\n#    else  // ANJAY_WITH_DISCOVER\n    (void) bootstrap_connection;\n    (void) request;\n    anjay_log(ERROR, _(\"Not supported: Bootstrap Discover \") \"%s\",\n              ANJAY_DEBUG_MAKE_PATH(&request->uri));\n    return ANJAY_ERR_NOT_IMPLEMENTED;\n#    endif // ANJAY_WITH_DISCOVER\n}\n\nstatic void purge_bootstrap(avs_sched_t *sched, const void *dummy) {\n    (void) dummy;\n    anjay_t *anjay_locked = _anjay_get_from_sched(sched);\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    anjay_iid_t iid;\n    int retval = 0;\n    anjay_notify_queue_t notification = NULL;\n    const anjay_dm_installed_object_t *obj =\n            _anjay_dm_find_object_by_oid(&anjay->dm, ANJAY_DM_OID_SECURITY);\n    if (!obj\n            || (iid = _anjay_find_bootstrap_security_iid(anjay))\n                           == ANJAY_ID_INVALID) {\n        anjay_log(WARNING,\n                  _(\"Could not find Bootstrap Server Account to purge\"));\n    } else if (avs_is_err(_anjay_dm_transaction_begin(anjay))) {\n        retval = -1;\n    } else {\n        (void) (retval\n                || (retval = _anjay_dm_call_instance_remove(anjay, obj, iid))\n                || (retval = _anjay_notify_queue_instance_removed(\n                            &notification,\n                            &MAKE_INSTANCE_PATH(\n                                    _anjay_dm_installed_object_oid(obj), iid)))\n                || (retval = _anjay_notify_flush(anjay, ANJAY_SSID_BOOTSTRAP,\n                                                 &notification)));\n        retval = _anjay_dm_transaction_finish(anjay, retval);\n    }\n    if (retval) {\n        anjay_log(WARNING,\n                  _(\"Could not purge Bootstrap Server Account \") \"%\" PRIu16,\n                  iid);\n    }\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n}\n\nstatic int schedule_bootstrap_timeout(anjay_unlocked_t *anjay) {\n    anjay_iid_t iid;\n    const anjay_dm_installed_object_t *obj =\n            _anjay_dm_find_object_by_oid(&anjay->dm, ANJAY_DM_OID_SECURITY);\n    if (!obj\n            || (iid = _anjay_find_bootstrap_security_iid(anjay))\n                           == ANJAY_ID_INVALID) {\n        anjay_log(DEBUG, _(\"Could not find Bootstrap Server Account to purge\"));\n        return 0;\n    }\n\n    const anjay_uri_path_t res_path =\n            MAKE_RESOURCE_PATH(ANJAY_DM_OID_SECURITY, iid,\n                               ANJAY_DM_RID_SECURITY_BOOTSTRAP_TIMEOUT);\n\n    int64_t timeout;\n    if (!_anjay_dm_read_resource_i64(anjay, &res_path, &timeout)\n            && timeout > 0) {\n        /* This function is called on each Bootstrap Finish -- i.e. we might\n         * have already scheduled a purge. For this reason, we need to release\n         * the purge job handle first. */\n        if (AVS_SCHED_DELAYED(\n                    anjay->sched, &anjay->bootstrap.purge_bootstrap_handle,\n                    avs_time_duration_from_scalar(timeout, AVS_TIME_S),\n                    purge_bootstrap, NULL, 0)) {\n            anjay_log(ERROR,\n                      _(\"Could not schedule purge of Bootstrap Server \"\n                        \"Account \") \"%\" PRIu16,\n                      iid);\n            return -1;\n        }\n    }\n    return 0;\n}\n\nstatic int\nvalidate_bootstrap_configuration(anjay_unlocked_t *anjay,\n                                 anjay_connection_ref_t bootstrap_connection) {\n    cancel_client_initiated_bootstrap(anjay);\n    if (avs_is_err(start_bootstrap_if_not_already_started(\n                anjay, bootstrap_connection, true))) {\n        return ANJAY_ERR_INTERNAL;\n    } else if (_anjay_dm_transaction_validate(anjay)) {\n        anjay_log(WARNING, _(\"Bootstrap configuration is invalid, rejecting\"));\n        return ANJAY_ERR_NOT_ACCEPTABLE;\n    }\n    return 0;\n}\n\n#    define BOOTSTRAP_FINISH_PERFORM_TIMEOUT (1 << 0)\n#    define BOOTSTRAP_FINISH_DISABLE_SERVER (1 << 1)\n\nstatic int bootstrap_finish_impl(anjay_unlocked_t *anjay,\n                                 anjay_connection_ref_t bootstrap_connection,\n                                 int flags) {\n    anjay_log(INFO, _(\"Bootstrap Sequence finished\"));\n    anjay->bootstrap.in_progress = false;\n    _anjay_conn_session_token_reset(&anjay->bootstrap.bootstrap_session_token,\n                                    &anjay->session_token_counter);\n    int retval = _anjay_dm_transaction_finish_without_validation(anjay, 0);\n    if (retval) {\n        anjay_log(\n                WARNING,\n                _(\"Bootstrap configuration could not be committed, rejecting\"));\n#    ifdef ANJAY_WITH_CONN_STATUS_API\n        if (bootstrap_connection.server) {\n            _anjay_set_server_connection_status(bootstrap_connection.server,\n                                                ANJAY_SERV_CONN_STATUS_ERROR);\n        }\n#    endif // ANJAY_WITH_CONN_STATUS_API\n        return retval;\n    }\n    if ((retval = _anjay_notify_perform_without_servers(\n                 anjay, ANJAY_SSID_BOOTSTRAP,\n                 &anjay->bootstrap.notification_queue))) {\n        anjay_log(WARNING,\n                  _(\"Could not post-process data model after bootstrap\"));\n    } else {\n        _anjay_notify_clear_queue(&anjay->bootstrap.notification_queue);\n        if (flags & BOOTSTRAP_FINISH_PERFORM_TIMEOUT) {\n            retval = schedule_bootstrap_timeout(anjay);\n        }\n    }\n    if (!retval && !anjay->bootstrap.allow_legacy_server_initiated_bootstrap\n            && (flags & BOOTSTRAP_FINISH_DISABLE_SERVER)) {\n        retval = _anjay_schedule_disable_server_with_explicit_timeout_unlocked(\n                anjay, ANJAY_SSID_BOOTSTRAP, AVS_TIME_DURATION_INVALID);\n    }\n    // Server might have been invalidated during the calls above\n    bool server_still_active =\n            (bootstrap_connection.server\n             && !!_anjay_servers_find_active(anjay, ANJAY_SSID_BOOTSTRAP));\n    if (retval) {\n        anjay_log(WARNING,\n                  _(\"Bootstrap Finish failed, re-entering bootstrap phase\"));\n        avs_error_t err = start_bootstrap_if_not_already_started(\n                anjay, bootstrap_connection, true);\n        if (avs_is_err(err) && server_still_active) {\n            _anjay_server_on_server_communication_error(\n                    bootstrap_connection.server, err);\n        }\n#    ifdef ANJAY_WITH_CONN_STATUS_API\n        if (avs_is_err(err) && bootstrap_connection.server) {\n            _anjay_set_server_connection_status(bootstrap_connection.server,\n                                                ANJAY_SERV_CONN_STATUS_ERROR);\n        }\n#    endif // ANJAY_WITH_CONN_STATUS_API\n    } else {\n#    ifdef ANJAY_WITH_CONN_STATUS_API\n        if (bootstrap_connection.server) {\n            _anjay_set_server_connection_status(\n                    bootstrap_connection.server,\n                    ANJAY_SERV_CONN_STATUS_BOOTSTRAPPED);\n        }\n#    endif // ANJAY_WITH_CONN_STATUS_API\n        _anjay_schedule_reload_servers(anjay);\n    }\n    return retval;\n}\n\nstatic int bootstrap_finish(anjay_connection_ref_t bootstrap_connection) {\n    anjay_unlocked_t *anjay = _anjay_from_server(bootstrap_connection.server);\n    int result = validate_bootstrap_configuration(anjay, bootstrap_connection);\n    if (result) {\n        return result;\n    }\n    return bootstrap_finish_impl(anjay, bootstrap_connection,\n                                 BOOTSTRAP_FINISH_PERFORM_TIMEOUT\n                                         | BOOTSTRAP_FINISH_DISABLE_SERVER);\n}\n\nstatic void\nreset_client_initiated_bootstrap_backoff(anjay_bootstrap_t *bootstrap) {\n    bootstrap->client_initiated_bootstrap_last_attempt =\n            AVS_TIME_MONOTONIC_INVALID;\n    bootstrap->client_initiated_bootstrap_holdoff = AVS_TIME_DURATION_INVALID;\n}\n\nint _anjay_bootstrap_notify_regular_connection_available(\n        anjay_unlocked_t *anjay) {\n    if (avs_coap_exchange_id_valid(\n                anjay->bootstrap.outgoing_request_exchange_id)) {\n        // Let the bootstrap request finish. When a response comes, bootstrap\n        // procedure will be started, which will suspend all non-bootstrap\n        // connections, including the one whose readiness is being notified\n        // with this function.\n        return 0;\n    }\n    int result = 0;\n    anjay_connection_ref_t bootstrap_connection = {\n        .server = _anjay_servers_find_active(anjay, ANJAY_SSID_BOOTSTRAP),\n        .conn_type = ANJAY_CONNECTION_PRIMARY\n    };\n    if (anjay->bootstrap.in_progress) {\n        (void) ((result = validate_bootstrap_configuration(\n                         anjay, bootstrap_connection))\n                || (result = bootstrap_finish_impl(\n                            anjay, bootstrap_connection,\n                            BOOTSTRAP_FINISH_DISABLE_SERVER)));\n    } else {\n        cancel_client_initiated_bootstrap(anjay);\n#    ifdef ANJAY_WITH_CONN_STATUS_API\n        if (bootstrap_connection.server) {\n            _anjay_set_server_connection_status(\n                    bootstrap_connection.server,\n                    ANJAY_SERV_CONN_STATUS_BOOTSTRAPPED);\n        }\n#    endif // ANJAY_WITH_CONN_STATUS_API\n    }\n    if (!result) {\n        reset_client_initiated_bootstrap_backoff(&anjay->bootstrap);\n    }\n    return result;\n}\n\nbool _anjay_bootstrap_legacy_server_initiated_allowed(anjay_unlocked_t *anjay) {\n    return anjay->bootstrap.allow_legacy_server_initiated_bootstrap;\n}\n\nbool _anjay_bootstrap_scheduled(anjay_unlocked_t *anjay) {\n    return anjay->bootstrap.bootstrap_trigger\n           || avs_coap_exchange_id_valid(\n                      anjay->bootstrap.outgoing_request_exchange_id)\n           || anjay->bootstrap.client_initiated_bootstrap_handle;\n}\n\nbool _anjay_bootstrap_in_progress(anjay_unlocked_t *anjay) {\n    return anjay->bootstrap.in_progress;\n}\n\n#    if defined(ANJAY_WITH_MODULE_FACTORY_PROVISIONING)\navs_error_t _anjay_bootstrap_delete_everything(anjay_unlocked_t *anjay) {\n    cancel_client_initiated_bootstrap(anjay);\n    delete_object_arg_t delete_arg = {\n        .skip_bootstrap = false,\n        .retval = 0\n    };\n    avs_error_t err = start_bootstrap_if_not_already_started(\n            anjay, (anjay_connection_ref_t) { NULL }, true);\n    if (avs_is_err(err)) {\n        return err;\n    }\n    if (_anjay_dm_foreach_object(anjay, &anjay->dm, delete_object, &delete_arg)\n            || delete_arg.retval) {\n        return avs_errno(AVS_EPROTO);\n    } else {\n        return AVS_OK;\n    }\n}\n\nint _anjay_bootstrap_finish(anjay_unlocked_t *anjay) {\n    int result = 0;\n    if (anjay->bootstrap.in_progress) {\n        (void) ((result = validate_bootstrap_configuration(\n                         anjay, (anjay_connection_ref_t) { NULL }))\n                || (result = bootstrap_finish_impl(\n                            anjay, (anjay_connection_ref_t) { NULL }, 0)));\n    }\n    return result;\n}\n#    endif /* defined(ANJAY_WITH_MODULE_BOOTSTRAPPER) || \\\n              defined(ANJAY_WITH_MODULE_FACTORY_PROVISIONING) */\n\n#    ifdef ANJAY_WITH_LWM2M11\nstatic int bootstrap_read(anjay_connection_ref_t bootstrap_connection,\n                          const anjay_request_t *request) {\n    assert(bootstrap_connection.server);\n    anjay_unlocked_t *anjay = _anjay_from_server(bootstrap_connection.server);\n    anjay_log(DEBUG, _(\"Bootstrap Read \") \"%s\",\n              ANJAY_DEBUG_MAKE_PATH(&request->uri));\n    if (avs_is_err(start_bootstrap_if_not_already_started(\n                anjay, bootstrap_connection, true))) {\n        return ANJAY_ERR_INTERNAL;\n    }\n\n    if ((!_anjay_uri_path_leaf_is(&request->uri, ANJAY_ID_OID)\n         && !_anjay_uri_path_leaf_is(&request->uri, ANJAY_ID_IID))\n            || (request->uri.ids[ANJAY_ID_OID] != ANJAY_DM_OID_SERVER\n                && request->uri.ids[ANJAY_ID_OID]\n                           != ANJAY_DM_OID_ACCESS_CONTROL)) {\n        anjay_log(DEBUG,\n                  _(\"the only acceptable targets of Bootstrap Read are LwM2M \"\n                    \"Server Object and Access Control Object or their \"\n                    \"instances\"));\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n    const anjay_dm_installed_object_t *obj =\n            _anjay_dm_find_object_by_oid(&anjay->dm,\n                                         request->uri.ids[ANJAY_ID_OID]);\n    if (!obj) {\n        anjay_log(DEBUG, _(\"Object not found: \") \"%u\",\n                  request->uri.ids[ANJAY_ID_OID]);\n        return ANJAY_ERR_NOT_FOUND;\n    }\n\n    anjay_dm_path_info_t path_info;\n    int result = _anjay_dm_path_info(anjay, obj, &request->uri, &path_info);\n    if (result) {\n        return result;\n    }\n\n    const anjay_msg_details_t details = _anjay_dm_response_details_for_read(\n            anjay, request, path_info.is_hierarchical,\n            _anjay_server_registration_info(bootstrap_connection.server)\n                    ->lwm2m_version);\n\n    avs_stream_t *response_stream =\n            _anjay_coap_setup_response_stream(request->ctx, &details);\n    if (!response_stream) {\n        return ANJAY_ERR_INTERNAL;\n    }\n\n    anjay_unlocked_output_ctx_t *out_ctx = NULL;\n    (void) ((result = _anjay_output_dynamic_construct(\n                     &out_ctx, response_stream, &request->uri, details.format,\n                     NULL, ANJAY_ACTION_READ))\n            || (result = _anjay_dm_read_and_destroy_ctx(anjay, obj, &path_info,\n                                                        ANJAY_SSID_BOOTSTRAP,\n                                                        &out_ctx)));\n    return result;\n}\n#    endif // ANJAY_WITH_LWM2M11\n\nstatic int bootstrap_write(anjay_connection_ref_t bootstrap_connection,\n                           const anjay_request_t *request) {\n    anjay_unlocked_input_ctx_t *in_ctx;\n    int result;\n    if ((result = _anjay_input_dynamic_construct(\n                 &in_ctx, request->payload_stream, request))) {\n        anjay_log(ERROR, _(\"could not create input context\"));\n        return result;\n    }\n\n    if (!result) {\n        result = bootstrap_write_impl(\n                _anjay_from_server(bootstrap_connection.server),\n                bootstrap_connection, &request->uri, in_ctx);\n    }\n    if (_anjay_input_ctx_destroy(&in_ctx)) {\n        anjay_log(ERROR, _(\"input ctx cleanup failed\"));\n    }\n    return result;\n}\n\nstatic void timeout_bootstrap_finish(avs_sched_t *sched, const void *dummy) {\n    (void) dummy;\n    anjay_t *anjay_locked = _anjay_get_from_sched(sched);\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    anjay_log(WARNING, _(\"Bootstrap Finish not received in time - aborting\"));\n    // Abort client-initiated-bootstrap entirely. After that,\n    // anjay_all_connections_failed() starts returning true (if the\n    // bootstrap was the only server), which gives the user an\n    // opportunity to react accordingly.\n    anjay_server_info_t *server =\n            _anjay_servers_find_active(anjay, ANJAY_SSID_BOOTSTRAP);\n    if (server) {\n        _anjay_server_on_failure(server, \"not reachable\");\n    }\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n}\n\nstatic avs_error_t schedule_finish_timeout(anjay_unlocked_t *anjay,\n                                           anjay_connection_ref_t connection) {\n    if (AVS_SCHED_DELAYED(anjay->sched, &anjay->bootstrap.finish_timeout_handle,\n                          _anjay_exchange_lifetime_for_transport(\n                                  anjay,\n                                  _anjay_connection_transport(connection)),\n                          timeout_bootstrap_finish, NULL, 0)) {\n        anjay_log(ERROR, _(\"could not schedule finish timeout\"));\n        return avs_errno(AVS_ENOMEM);\n    }\n    return AVS_OK;\n}\n\nstatic int invoke_action(anjay_connection_ref_t bootstrap_connection,\n                         const anjay_request_t *request) {\n    anjay_unlocked_t *anjay = _anjay_from_server(bootstrap_connection.server);\n    // Cancel the job explicitly, because it may happen that Bootstrap Finish\n    // succeeds, but schedule_finish_timeout() fails, leaving the job on the\n    // scheduler.\n    avs_sched_del(&anjay->bootstrap.finish_timeout_handle);\n\n    int result;\n    switch (request->action) {\n#    ifdef ANJAY_WITH_LWM2M11\n    case ANJAY_ACTION_READ:\n        result = bootstrap_read(bootstrap_connection, request);\n        break;\n#    endif // ANJAY_WITH_LWM2M11\n    case ANJAY_ACTION_WRITE:\n        result = bootstrap_write(bootstrap_connection, request);\n        break;\n    case ANJAY_ACTION_DELETE:\n        result = bootstrap_delete(bootstrap_connection, request);\n        break;\n    case ANJAY_ACTION_DISCOVER:\n        result = bootstrap_discover(bootstrap_connection, request);\n        break;\n    case ANJAY_ACTION_BOOTSTRAP_FINISH:\n        result = bootstrap_finish(bootstrap_connection);\n        break;\n    default:\n        anjay_log(DEBUG, _(\"Invalid action for Bootstrap Interface\"));\n        result = ANJAY_ERR_METHOD_NOT_ALLOWED;\n        break;\n    }\n    if (request->action == ANJAY_ACTION_BOOTSTRAP_FINISH) {\n        if (!result) {\n            // Don't reschedule finish timeout\n            bootstrap_connection.server = NULL;\n        } else {\n            // The server might have been invalidated, re-find it\n            bootstrap_connection.server =\n                    _anjay_servers_find_active(anjay, ANJAY_SSID_BOOTSTRAP);\n        }\n    }\n    if (bootstrap_connection.server\n            && avs_is_err(\n                       schedule_finish_timeout(anjay, bootstrap_connection))) {\n        result = -1;\n    }\n    return result;\n}\n\nint _anjay_bootstrap_perform_action(anjay_connection_ref_t bootstrap_connection,\n                                    const anjay_request_t *request) {\n    anjay_msg_details_t msg_details = {\n        .msg_code = make_success_response_code(request->action),\n        .format = AVS_COAP_FORMAT_NONE\n    };\n\n#    ifdef ANJAY_WITH_LWM2M_GATEWAY\n    if (_anjay_uri_path_has_prefix(&request->uri)) {\n        anjay_log(DEBUG, _(\"Bootstrap Interface can't operate on LwM2M Gateway \"\n                           \"End Devices\"));\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n#    endif // ANJAY_WITH_LWM2M_GATEWAY\n\n    if (!_anjay_coap_setup_response_stream(request->ctx, &msg_details)) {\n        return -1;\n    }\n\n    return invoke_action(bootstrap_connection, request);\n}\n\nstatic void send_request_bootstrap(anjay_unlocked_t *anjay,\n                                   anjay_connection_ref_t connection\n#    ifdef ANJAY_WITH_BOOTSTRAP_PACK\n                                   ,\n                                   bool bootstrap_pack\n#    endif // ANJAY_WITH_BOOTSTRAP_PACK\n);\n\nstatic void bootstrap_request_response_handler(\n        avs_coap_ctx_t *ctx,\n        avs_coap_exchange_id_t exchange_id,\n        avs_coap_client_request_state_t result,\n        const avs_coap_client_async_response_t *response,\n        avs_error_t err,\n        void *anjay_) {\n    anjay_unlocked_t *anjay = (anjay_unlocked_t *) anjay_;\n    if (result != AVS_COAP_CLIENT_REQUEST_PARTIAL_CONTENT) {\n        anjay->bootstrap.outgoing_request_exchange_id =\n                AVS_COAP_EXCHANGE_ID_INVALID;\n    }\n    if (result != AVS_COAP_CLIENT_REQUEST_CANCEL) {\n        anjay->bootstrap.bootstrap_trigger = false;\n    }\n\n    const anjay_connection_ref_t connection =\n            _anjay_servers_find_active_primary_connection(anjay,\n                                                          ANJAY_SSID_BOOTSTRAP);\n    assert(connection.server || result == AVS_COAP_CLIENT_REQUEST_CANCEL);\n    switch (result) {\n    case AVS_COAP_CLIENT_REQUEST_PARTIAL_CONTENT:\n        // Note: this will recursively call this function with\n        // AVS_COAP_CLIENT_REQUEST_CANCEL.\n        avs_coap_exchange_cancel(ctx, exchange_id);\n        // fall-through\n\n    case AVS_COAP_CLIENT_REQUEST_OK:\n        assert(connection.conn_type != ANJAY_CONNECTION_UNSET);\n        if (response->header.code != AVS_COAP_CODE_CHANGED) {\n#    ifdef ANJAY_WITH_LWM2M11\n            // See comment in request_bootstrap_job() for more information about\n            // why are we using \"registration info\".\n            anjay_lwm2m_version_t attempted_version =\n                    _anjay_server_registration_info(connection.server)\n                            ->lwm2m_version;\n            if (avs_coap_code_is_client_error(response->header.code)\n                    && attempted_version >= ANJAY_LWM2M_VERSION_1_1\n                    && anjay->lwm2m_version_config.minimum_version\n                                   <= ANJAY_LWM2M_VERSION_1_0) {\n                anjay_log(WARNING,\n                          _(\"attempting to fall back to LwM2M version 1.0\"));\n                _anjay_server_update_registration_info(connection.server, NULL,\n                                                       ANJAY_LWM2M_VERSION_1_0,\n                                                       false, NULL);\n                send_request_bootstrap(anjay, connection\n#        ifdef ANJAY_WITH_BOOTSTRAP_PACK\n                                       ,\n                                       false\n#        endif // ANJAY_WITH_BOOTSTRAP_PACK\n                );\n                return;\n            }\n#    endif // ANJAY_WITH_LWM2M11\n            anjay_log(WARNING,\n                      _(\"server responded with \") \"%s\" _(\" (expected \") \"%s\" _(\n                              \")\"),\n                      AVS_COAP_CODE_STRING(response->header.code),\n                      AVS_COAP_CODE_STRING(AVS_COAP_CODE_CHANGED));\n            _anjay_server_on_server_communication_error(connection.server,\n                                                        avs_errno(AVS_EPROTO));\n        } else {\n            anjay_log(INFO,\n                      _(\"Client-initiated Bootstrap successfully started\"));\n            if (avs_is_err((err = start_bootstrap_if_not_already_started(\n                                    anjay, connection, true)))\n                    || avs_is_err((err = schedule_finish_timeout(\n                                           anjay, connection)))) {\n                _anjay_server_on_server_communication_error(connection.server,\n                                                            err);\n            }\n        }\n        break;\n\n    case AVS_COAP_CLIENT_REQUEST_FAIL: {\n        if (avs_is_err(err)) {\n            if (err.category == AVS_COAP_ERR_CATEGORY\n                    && err.code == AVS_COAP_ERR_TIMEOUT) {\n                anjay_log(WARNING, _(\"could not request bootstrap: timeout\"));\n                _anjay_server_on_server_communication_timeout(\n                        connection.server);\n            } else {\n                anjay_log(WARNING, _(\"could not send Request Bootstrap: \") \"%s\",\n                          AVS_COAP_STRERROR(err));\n                _anjay_server_on_server_communication_error(connection.server,\n                                                            err);\n            }\n        }\n        break;\n    }\n\n    case AVS_COAP_CLIENT_REQUEST_CANCEL:\n        break;\n    }\n}\n\n#    ifdef ANJAY_WITH_BOOTSTRAP_PACK\nstatic int write_bootstrap_pack(anjay_unlocked_t *anjay,\n                                anjay_unlocked_input_ctx_t *in_ctx) {\n    int retval;\n    delete_object_arg_t delete_args = {\n        .skip_bootstrap = true,\n        .retval = 0\n    };\n\n    do {\n        anjay_uri_path_t path;\n        if ((retval = _anjay_input_get_path(in_ctx, &path, NULL))) {\n            if (retval == ANJAY_GET_PATH_END) {\n                retval = 0;\n            }\n            break;\n        }\n        if (path.ids[ANJAY_ID_OID] == ANJAY_ID_INVALID) {\n            retval = ANJAY_ERR_BAD_REQUEST;\n            break;\n        }\n\n        const anjay_dm_installed_object_t *obj =\n                _anjay_dm_find_object_by_oid(&anjay->dm,\n                                             path.ids[ANJAY_ID_OID]);\n        if (!_anjay_dm_transaction_object_included(anjay, obj)) {\n            delete_object(anjay, obj, &delete_args);\n        }\n        retval = write_object_and_move_to_next_entry(anjay, obj, in_ctx);\n    } while (!retval);\n\n    return retval;\n}\n\nstatic void bootstrap_pack_request_response_handler(\n        avs_coap_ctx_t *ctx,\n        avs_coap_exchange_id_t exchange_id,\n        avs_coap_client_request_state_t result,\n        const avs_coap_client_async_response_t *response,\n        avs_error_t err,\n        void *anjay_) {\n    (void) ctx;\n    (void) exchange_id;\n    anjay_unlocked_t *anjay = (anjay_unlocked_t *) anjay_;\n    if (result != AVS_COAP_CLIENT_REQUEST_PARTIAL_CONTENT) {\n        anjay->bootstrap.outgoing_request_exchange_id =\n                AVS_COAP_EXCHANGE_ID_INVALID;\n    }\n    if (result != AVS_COAP_CLIENT_REQUEST_CANCEL) {\n        anjay->bootstrap.bootstrap_trigger = false;\n    }\n\n    const anjay_connection_ref_t connection =\n            _anjay_servers_find_active_primary_connection(anjay,\n                                                          ANJAY_SSID_BOOTSTRAP);\n    assert(connection.server || result == AVS_COAP_CLIENT_REQUEST_CANCEL);\n    bool destroy_membuf = true;\n    switch (result) {\n    case AVS_COAP_CLIENT_REQUEST_PARTIAL_CONTENT:\n    case AVS_COAP_CLIENT_REQUEST_OK:\n        assert(connection.conn_type != ANJAY_CONNECTION_UNSET);\n        if (response->header.code != AVS_COAP_CODE_CONTENT) {\n            anjay_log(WARNING,\n                      _(\"server responded with \") \"%s\" _(\" (expected \") \"%s\" _(\n                              \")\"),\n                      AVS_COAP_CODE_STRING(response->header.code),\n                      AVS_COAP_CODE_STRING(AVS_COAP_CODE_CONTENT));\n            anjay_log(WARNING, _(\"Bootstrap-pack failed - attempting to fall \"\n                                 \"back to casual bootstrap\"));\n            send_request_bootstrap(anjay, connection, false);\n        } else {\n            if (avs_is_err((err = start_bootstrap_if_not_already_started(\n                                    anjay, connection, false)))) {\n                _anjay_server_on_server_communication_error(connection.server,\n                                                            err);\n                break;\n            }\n\n            uint16_t content_format;\n            if (avs_coap_options_get_content_format(&response->header.options,\n                                                    &content_format)) {\n                anjay_log(WARNING,\n                          _(\"Could not read Bootstrap-Pack Content format, \"\n                            \"falling back to casual bootstrap\"));\n                send_request_bootstrap(anjay, connection, false);\n                break;\n            }\n\n            avs_off_t expected_offset = 0;\n            if (!anjay->bootstrap.pack_membuf\n                    || avs_is_err(avs_stream_offset(\n                               anjay->bootstrap.pack_membuf, &expected_offset))\n                    || expected_offset < 0) {\n                expected_offset = 0;\n            }\n            if (response->payload_offset != (size_t) expected_offset) {\n                anjay_log(ERROR, _(\"bootstrap_pack_request_response_handler() \"\n                                   \"called in invalid state\"));\n                send_request_bootstrap(anjay, connection, false);\n                break;\n            }\n            if (expected_offset == 0) {\n                anjay_log(INFO, _(\"Bootstrap-pack successfully received\"));\n            }\n            if ((!anjay->bootstrap.pack_membuf\n                 && !(anjay->bootstrap.pack_membuf =\n                              avs_stream_membuf_create()))\n                    || avs_is_err(avs_stream_write(anjay->bootstrap.pack_membuf,\n                                                   response->payload,\n                                                   response->payload_size))) {\n                _anjay_log_oom();\n                send_request_bootstrap(anjay, connection, false);\n            } else if (result == AVS_COAP_CLIENT_REQUEST_PARTIAL_CONTENT) {\n                destroy_membuf = false;\n            } else {\n                anjay_unlocked_input_ctx_t *input_ctx;\n                anjay_uri_path_t uri = MAKE_ROOT_PATH();\n                if (_anjay_input_dynamic_construct_raw(\n                            &input_ctx, anjay->bootstrap.pack_membuf,\n                            content_format, ANJAY_ACTION_WRITE_COMPOSITE,\n                            &uri)) {\n                    anjay_log(WARNING,\n                              _(\"Creating Bootstrap-Pack input context failed, \"\n                                \"falling back to casual bootstrap\"));\n                    send_request_bootstrap(anjay, connection, false);\n                    break;\n                }\n\n                if (write_bootstrap_pack(anjay, input_ctx)) {\n                    anjay_log(WARNING,\n                              _(\"Reading Bootstrap-Pack Content failed, \"\n                                \"falling back to casual bootstrap\"));\n                    send_request_bootstrap(anjay, connection, false);\n                } else {\n                    anjay_log(INFO, _(\"Bootstrap-Pack successfuly read\"));\n                    if (bootstrap_finish(connection)) {\n                        anjay_log(INFO, _(\"Cannot connect to the server using \"\n                                          \"Bootstrap-Pack data, falling back \"\n                                          \"to casual bootstrap\"));\n                        send_request_bootstrap(anjay, connection, false);\n                    }\n                }\n\n                (void) _anjay_input_ctx_destroy(&input_ctx);\n            }\n        }\n        break;\n    case AVS_COAP_CLIENT_REQUEST_FAIL: {\n        if (avs_is_err(err)) {\n            if (err.category == AVS_COAP_ERR_CATEGORY\n                    && err.code == AVS_COAP_ERR_TIMEOUT) {\n                anjay_log(WARNING,\n                          _(\"could not request Bootstrap-Pack: timeout\"));\n                _anjay_server_on_server_communication_timeout(\n                        connection.server);\n            } else {\n                anjay_log(WARNING,\n                          _(\"could not send Bootstrap-Pack Request: \") \"%s\",\n                          AVS_COAP_STRERROR(err));\n                _anjay_server_on_server_communication_error(connection.server,\n                                                            err);\n            }\n        }\n        break;\n    }\n\n    case AVS_COAP_CLIENT_REQUEST_CANCEL:\n        break;\n    }\n\n    if (destroy_membuf) {\n        avs_stream_cleanup(&anjay->bootstrap.pack_membuf);\n    }\n}\n#    endif // ANJAY_WITH_BOOTSTRAP_PACK\n\n#    ifdef ANJAY_WITH_LWM2M11\nstatic inline avs_error_t\nadd_pct_option_if_required(avs_coap_options_t *options,\n                           anjay_connection_ref_t connection) {\n    // See comment in request_bootstrap_job() for more\n    // information about why are we using \"registration info\".\n    if (_anjay_server_registration_info(connection.server)->lwm2m_version\n            < ANJAY_LWM2M_VERSION_1_1) {\n        return AVS_OK;\n    }\n    return avs_coap_options_add_string_f(\n            options,\n            AVS_COAP_OPTION_URI_QUERY,\n            \"pct=%d\",\n            _anjay_default_hierarchical_format(\n                    _anjay_server_registration_info(connection.server)\n                            ->lwm2m_version));\n}\n#    endif // ANJAY_WITH_LWM2M11\n\nstatic void send_request_bootstrap(anjay_unlocked_t *anjay,\n                                   anjay_connection_ref_t connection\n#    ifdef ANJAY_WITH_BOOTSTRAP_PACK\n                                   ,\n                                   bool bootstrap_pack\n#    endif // ANJAY_WITH_BOOTSTRAP_PACK\n) {\n    const anjay_url_t *const connection_uri = _anjay_connection_uri(connection);\n    avs_coap_request_header_t request = {\n        .code = AVS_COAP_CODE_POST\n    };\n\n    const char *prefix = \"bs\";\n\n#    ifdef ANJAY_WITH_BOOTSTRAP_PACK\n    if (bootstrap_pack) {\n        request.code = AVS_COAP_CODE_GET;\n        prefix = \"bspack\";\n    }\n#    endif // ANJAY_WITH_BOOTSTRAP_PACK\n\n    avs_coap_ctx_t *coap = _anjay_connection_get_coap(connection);\n    assert(coap);\n\n    avs_error_t err;\n    if (avs_is_err((err = avs_coap_options_dynamic_init(&request.options)))\n            || avs_is_err((err = _anjay_coap_add_string_options(\n                                   &request.options,\n                                   connection_uri->uri_path,\n                                   AVS_COAP_OPTION_URI_PATH)))\n            || avs_is_err((err = avs_coap_options_add_string(\n                                   &request.options, AVS_COAP_OPTION_URI_PATH,\n                                   prefix)))\n            || avs_is_err((err = _anjay_coap_add_string_options(\n                                   &request.options,\n                                   connection_uri->uri_query,\n                                   AVS_COAP_OPTION_URI_QUERY)))\n            || avs_is_err((err = _anjay_coap_add_query_options(\n                                   &request.options, NULL, anjay->endpoint_name,\n                                   NULL, NULL, false, NULL)))\n#    ifdef ANJAY_WITH_LWM2M11\n            || (\n#        ifdef ANJAY_WITH_BOOTSTRAP_PACK\n                       bootstrap_pack\n                       && (avs_is_err((err = avs_coap_options_add_u16(\n                                               &request.options,\n                                               AVS_COAP_OPTION_ACCEPT,\n                                               BOOTSTRAP_PACK_ACCEPT_FORMAT)))))\n            || (!bootstrap_pack &&\n#        endif // ANJAY_WITH_BOOTSTRAP_PACK\n                avs_is_err(add_pct_option_if_required(&request.options,\n                                                      connection)))\n#    endif // ANJAY_WITH_LWM2M11\n    ) {\n        anjay_log(ERROR, _(\"could not initialize request headers\"));\n        anjay->bootstrap.bootstrap_trigger = false;\n        _anjay_server_on_server_communication_error(connection.server, err);\n    } else {\n        assert(!avs_coap_exchange_id_valid(\n                anjay->bootstrap.outgoing_request_exchange_id));\n        const char *msg_name = \"Bootstrap Request:\";\n        avs_coap_client_async_response_handler_t *response_handler =\n                bootstrap_request_response_handler;\n\n#    ifdef ANJAY_WITH_BOOTSTRAP_PACK\n        if (bootstrap_pack) {\n            msg_name = \"Bootstrap-Pack Request:\";\n            response_handler = bootstrap_pack_request_response_handler;\n        }\n#    endif // ANJAY_WITH_BOOTSTRAP_PACK\n\n        if (avs_is_err(\n                    (err = avs_coap_client_send_async_request(\n                             coap,\n                             &anjay->bootstrap.outgoing_request_exchange_id,\n                             &request, NULL, NULL, response_handler, anjay)))) {\n            anjay_log(WARNING, _(\"could not send \") \"%s %s\", msg_name,\n                      AVS_COAP_STRERROR(err));\n\n            anjay->bootstrap.bootstrap_trigger = false;\n            _anjay_server_on_server_communication_error(connection.server, err);\n        }\n    }\n    avs_coap_options_cleanup(&request.options);\n}\n\nstatic void request_bootstrap_job(avs_sched_t *sched, const void *dummy);\n\nstatic int schedule_request_bootstrap(anjay_unlocked_t *anjay) {\n    avs_time_monotonic_t now = avs_time_monotonic_now();\n    if (!avs_time_monotonic_valid(\n                anjay->bootstrap.client_initiated_bootstrap_last_attempt)) {\n        anjay->bootstrap.client_initiated_bootstrap_last_attempt = now;\n    }\n    if (!avs_time_duration_valid(\n                anjay->bootstrap.client_initiated_bootstrap_holdoff)) {\n        anjay->bootstrap.client_initiated_bootstrap_holdoff =\n                AVS_TIME_DURATION_ZERO;\n    }\n\n    const avs_time_duration_t MIN_HOLDOFF =\n            avs_time_duration_from_scalar(3, AVS_TIME_S);\n    const avs_time_duration_t MAX_HOLDOFF =\n            avs_time_duration_from_scalar(ANJAY_MAX_HOLDOFF_TIME, AVS_TIME_S);\n\n    if (avs_time_duration_less(\n                MAX_HOLDOFF,\n                anjay->bootstrap.client_initiated_bootstrap_holdoff)) {\n        anjay->bootstrap.client_initiated_bootstrap_holdoff = MAX_HOLDOFF;\n        anjay_log(WARNING,\n                  _(\"Holdoff time is bigger then max allowed value, setting \"\n                    \"to \") \"%s\" _(\" seconds\"),\n                  AVS_TIME_DURATION_AS_STRING(\n                          anjay->bootstrap.client_initiated_bootstrap_holdoff));\n    }\n\n    avs_time_monotonic_t attempt_instant = avs_time_monotonic_add(\n            anjay->bootstrap.client_initiated_bootstrap_last_attempt,\n            anjay->bootstrap.client_initiated_bootstrap_holdoff);\n    anjay_log(DEBUG, _(\"Scheduling bootstrap in \") \"%s\" _(\" seconds\"),\n              AVS_TIME_DURATION_AS_STRING(\n                      anjay->bootstrap.client_initiated_bootstrap_holdoff));\n    if (AVS_SCHED_DELAYED(anjay->sched,\n                          &anjay->bootstrap.client_initiated_bootstrap_handle,\n                          avs_time_monotonic_diff(attempt_instant, now),\n                          request_bootstrap_job, NULL, 0)) {\n        anjay_log(WARNING, _(\"Could not schedule Client Initiated Bootstrap\"));\n        return -1;\n    }\n\n    anjay->bootstrap.client_initiated_bootstrap_last_attempt = attempt_instant;\n    anjay->bootstrap.client_initiated_bootstrap_holdoff = avs_time_duration_mul(\n            anjay->bootstrap.client_initiated_bootstrap_holdoff, 2);\n    if (avs_time_duration_less(\n                anjay->bootstrap.client_initiated_bootstrap_holdoff,\n                MIN_HOLDOFF)) {\n        anjay->bootstrap.client_initiated_bootstrap_holdoff = MIN_HOLDOFF;\n    } else if (avs_time_duration_less(\n                       MAX_HOLDOFF,\n                       anjay->bootstrap.client_initiated_bootstrap_holdoff)) {\n        anjay->bootstrap.client_initiated_bootstrap_holdoff = MAX_HOLDOFF;\n    }\n    return 0;\n}\n\nstatic void request_bootstrap_job(avs_sched_t *sched, const void *dummy) {\n    anjay_t *anjay_locked = _anjay_get_from_sched(sched);\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n\n    (void) dummy;\n\n    const anjay_connection_ref_t connection =\n            _anjay_servers_find_active_primary_connection(anjay,\n                                                          ANJAY_SSID_BOOTSTRAP);\n    if (!connection.server) {\n        anjay_log(DEBUG, _(\"Bootstrap server connection not available to send \"\n                           \"Request Bootstrap through\"));\n        anjay->bootstrap.bootstrap_trigger = false;\n        goto finish;\n    }\n    if (_anjay_conn_session_tokens_equal(\n                anjay->bootstrap.bootstrap_session_token,\n                _anjay_server_primary_session_token(connection.server))) {\n        anjay_log(DEBUG, _(\"Bootstrap already started on the same connection\"));\n        goto error;\n    }\n    if (!_anjay_connection_get_online_socket(connection)) {\n        anjay_log(DEBUG, _(\"Bootstrap server connection is not online\"));\n        goto error;\n    }\n    // Bootstrap Server has no concept of \"registration\", but we're reusing the\n    // registration_info field in the server structure to store which LwM2M\n    // version was used for Request Bootstrap. This is used to determine whether\n    // Preferred Content Type is sent in the Request Bootstrap message.\n    _anjay_server_update_registration_info(\n            connection.server, NULL,\n#    ifdef ANJAY_WITH_LWM2M12\n            AVS_MIN(anjay->lwm2m_version_config.maximum_version,\n                    ANJAY_LWM2M_VERSION_1_2),\n#    elif defined(ANJAY_WITH_LWM2M11)\n            AVS_MIN(anjay->lwm2m_version_config.maximum_version,\n                    ANJAY_LWM2M_VERSION_1_1),\n#    else  // ANJAY_WITH_LWM2M11\n            ANJAY_LWM2M_VERSION_1_0,\n#    endif // ANJAY_WITH_LWM2M11\n            false, NULL);\n\n    send_request_bootstrap(anjay, connection\n#    ifdef ANJAY_WITH_BOOTSTRAP_PACK\n                           ,\n                           /* bootstrap-pack */ _anjay_server_registration_info(\n                                   connection.server)\n                                           ->lwm2m_version\n                                   >= ANJAY_LWM2M_VERSION_1_2\n#    endif // ANJAY_WITH_BOOTSTRAP_PACK\n    );\n    goto finish;\nerror:\n    anjay->bootstrap.bootstrap_trigger = false;\n    _anjay_server_on_server_communication_error(connection.server,\n                                                avs_errno(AVS_EPROTO));\nfinish:;\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n}\n\nstatic int64_t client_hold_off_time_s(anjay_unlocked_t *anjay) {\n    anjay_iid_t security_iid = _anjay_find_bootstrap_security_iid(anjay);\n    if (security_iid == ANJAY_ID_INVALID) {\n        anjay_log(WARNING,\n                  _(\"could not find server Security IID of the Bootstrap \"\n                    \"Server\"));\n        return -1;\n    }\n\n    const anjay_uri_path_t path =\n            MAKE_RESOURCE_PATH(ANJAY_DM_OID_SECURITY, security_iid,\n                               ANJAY_DM_RID_SECURITY_CLIENT_HOLD_OFF_TIME);\n    int64_t holdoff_s;\n    if (_anjay_dm_read_resource_i64(anjay, &path, &holdoff_s)\n            || holdoff_s < 0) {\n        return -1;\n    }\n    return holdoff_s;\n}\n\nint _anjay_perform_bootstrap_action_if_appropriate(\n        anjay_unlocked_t *anjay,\n        anjay_server_info_t *bootstrap_server,\n        anjay_bootstrap_action_t action) {\n    if (!bootstrap_server && action != ANJAY_BOOTSTRAP_ACTION_NONE) {\n        return _anjay_enable_server_unlocked(anjay, ANJAY_SSID_BOOTSTRAP);\n    }\n\n    switch (action) {\n    case ANJAY_BOOTSTRAP_ACTION_NONE:\n        return 0;\n    case ANJAY_BOOTSTRAP_ACTION_REQUEST: {\n        // schedule Client Initiated Bootstrap if not attempted already;\n        // if bootstrap is already in progress, schedule_request_bootstrap()\n        // will check if the endpoint changed and re-request if so\n        if (!avs_time_monotonic_valid(\n                    anjay->bootstrap.client_initiated_bootstrap_last_attempt)) {\n            int64_t holdoff_s = client_hold_off_time_s(anjay);\n            if (holdoff_s < 0) {\n                anjay_log(INFO,\n                          _(\"Client Hold Off Time not set or invalid, not \"\n                            \"scheduling Client Initiated Bootstrap\"));\n                return 0;\n            }\n            anjay_log(DEBUG, _(\"Scheduling Client Initiated Bootstrap\"));\n            anjay->bootstrap.client_initiated_bootstrap_holdoff =\n                    avs_time_duration_from_scalar(holdoff_s, AVS_TIME_S);\n        }\n        int result = schedule_request_bootstrap(anjay);\n        if (!result) {\n            cancel_est_sren(anjay);\n        }\n        return result;\n    }\n    }\n    AVS_UNREACHABLE(\"invalid action\");\n    return -1;\n}\n\nvoid _anjay_bootstrap_init(anjay_unlocked_t *anjay,\n                           bool allow_legacy_server_initiated_bootstrap) {\n    anjay->bootstrap.allow_legacy_server_initiated_bootstrap =\n            allow_legacy_server_initiated_bootstrap;\n    _anjay_conn_session_token_reset(&anjay->bootstrap.bootstrap_session_token,\n                                    &anjay->session_token_counter);\n    reset_client_initiated_bootstrap_backoff(&anjay->bootstrap);\n}\n\nvoid _anjay_bootstrap_cleanup(anjay_unlocked_t *anjay) {\n    assert(!avs_coap_exchange_id_valid(\n            anjay->bootstrap.outgoing_request_exchange_id));\n#    ifdef ANJAY_WITH_BOOTSTRAP_PACK\n    assert(!anjay->bootstrap.pack_membuf);\n#    endif // ANJAY_WITH_BOOTSTRAP_PACK\n    cancel_client_initiated_bootstrap(anjay);\n    cancel_est_sren(anjay);\n    reset_client_initiated_bootstrap_backoff(&anjay->bootstrap);\n    abort_bootstrap(anjay);\n    avs_sched_del(&anjay->bootstrap.purge_bootstrap_handle);\n    avs_sched_del(&anjay->bootstrap.finish_timeout_handle);\n    _anjay_notify_clear_queue(&anjay->bootstrap.notification_queue);\n}\n\n#    ifdef ANJAY_WITH_LWM2M11\nint _anjay_schedule_bootstrap_request_unlocked(anjay_unlocked_t *anjay) {\n    if (avs_coap_exchange_id_valid(\n                anjay->bootstrap.outgoing_request_exchange_id)) {\n        anjay_log(DEBUG,\n                  _(\"Bootstrap already requested, not requesting again\"));\n        return 0;\n    }\n\n    if (!_anjay_bootstrap_server_exists(anjay)) {\n        anjay_log(WARNING,\n                  _(\"Bootstrap Server Account does not exist, cannot \"\n                    \"schedule Bootstrap Request\"));\n        return -1;\n    }\n\n    cancel_client_initiated_bootstrap(anjay);\n    cancel_est_sren(anjay);\n    anjay->bootstrap.bootstrap_trigger = true;\n    anjay->bootstrap.client_initiated_bootstrap_last_attempt =\n            avs_time_monotonic_now();\n    anjay->bootstrap.client_initiated_bootstrap_holdoff =\n            AVS_TIME_DURATION_ZERO;\n    if (_anjay_servers_find_active(anjay, ANJAY_SSID_BOOTSTRAP)) {\n        return schedule_request_bootstrap(anjay);\n    } else {\n        return _anjay_enable_server_unlocked(anjay, ANJAY_SSID_BOOTSTRAP);\n    }\n}\n\nint anjay_schedule_bootstrap_request(anjay_t *anjay_locked) {\n    int result = -1;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    result = _anjay_schedule_bootstrap_request_unlocked(anjay);\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return result;\n}\n#    endif // ANJAY_WITH_LWM2M11\n\n#    ifdef ANJAY_TEST\n#        include \"tests/core/bootstrap.c\"\n#    endif // ANJAY_TEST\n\n#else // ANJAY_WITH_BOOTSTRAP\n\nint anjay_schedule_bootstrap_request(anjay_t *anjay) {\n    (void) anjay;\n    anjay_log(ERROR,\n              _(\"Anjay is compiled without Bootstrap support, cannot \"\n                \"schedule Bootstrap Request\"));\n    return -1;\n}\n\n#endif // ANJAY_WITH_BOOTSTRAP\n"
  },
  {
    "path": "src/core/anjay_bootstrap_core.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ANJAY_BOOTSTRAP_CORE_H\n#define ANJAY_BOOTSTRAP_CORE_H\n\n#include <anjay/core.h>\n\n#include <anjay_modules/anjay_bootstrap.h>\n\n#include <avsystem/commons/avs_stream.h>\n#include <avsystem/commons/avs_stream_outbuf.h>\n\n#include \"anjay_dm_core.h\"\n#include \"anjay_servers_private.h\"\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\ntypedef enum {\n    ANJAY_BOOTSTRAP_ACTION_NONE = 0,\n    ANJAY_BOOTSTRAP_ACTION_REQUEST,\n} anjay_bootstrap_action_t;\n\n#if defined(ANJAY_WITH_BOOTSTRAP_PACK)   \\\n        && (!defined(ANJAY_WITH_LWM2M12) \\\n            || (!defined(ANJAY_WITH_SENML_JSON) && !defined(ANJAY_WITH_CBOR)))\n#    error \"ANJAY_WITH_LWM2M12 and either ANJAY_WITH_SENML_JSON or ANJAY_WITH_CBOR are required for ANJAY_WITH_BOOTSTRAP_PACK\"\n#endif\n\n#ifdef ANJAY_WITH_BOOTSTRAP\n\ntypedef struct {\n    bool allow_legacy_server_initiated_bootstrap;\n    bool bootstrap_trigger;\n    avs_coap_exchange_id_t outgoing_request_exchange_id;\n    bool in_progress;\n    anjay_conn_session_token_t bootstrap_session_token;\n    anjay_notify_queue_t notification_queue;\n    avs_sched_handle_t purge_bootstrap_handle;\n    avs_sched_handle_t client_initiated_bootstrap_handle;\n    avs_sched_handle_t finish_timeout_handle;\n    avs_time_monotonic_t client_initiated_bootstrap_last_attempt;\n    avs_time_duration_t client_initiated_bootstrap_holdoff;\n#    ifdef ANJAY_WITH_BOOTSTRAP_PACK\n    avs_stream_t *pack_membuf;\n#    endif // ANJAY_WITH_BOOTSTRAP_PACK\n} anjay_bootstrap_t;\n\nint _anjay_bootstrap_notify_regular_connection_available(\n        anjay_unlocked_t *anjay);\n\nbool _anjay_bootstrap_legacy_server_initiated_allowed(anjay_unlocked_t *anjay);\n\nbool _anjay_bootstrap_scheduled(anjay_unlocked_t *anjay);\n\nint _anjay_bootstrap_perform_action(anjay_connection_ref_t bootstrap_connection,\n                                    const anjay_request_t *request);\n\nint _anjay_perform_bootstrap_action_if_appropriate(\n        anjay_unlocked_t *anjay,\n        anjay_server_info_t *bootstrap_server,\n        anjay_bootstrap_action_t action);\n\nvoid _anjay_bootstrap_init(anjay_unlocked_t *anjay,\n                           bool allow_legacy_server_initiated_bootstrap);\n\n#else\n\n#    define _anjay_bootstrap_notify_regular_connection_available(anjay) \\\n        ((void) 0)\n\n#    define _anjay_bootstrap_legacy_server_initiated_allowed(...) (false)\n\n#    define _anjay_bootstrap_scheduled(anjay) ((void) (anjay), false)\n\n#    define _anjay_bootstrap_perform_action(...) (-1)\n\n#    define _anjay_perform_bootstrap_action_if_appropriate(...) (-1)\n\n#endif\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif /* ANJAY_BOOTSTRAP_CORE_H */\n"
  },
  {
    "path": "src/core/anjay_core.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#include <assert.h>\n#include <ctype.h>\n#include <inttypes.h>\n#include <math.h>\n#include <stddef.h>\n#include <stdio.h>\n#include <stdlib.h>\n\n#include <avsystem/commons/avs_errno.h>\n#include <avsystem/commons/avs_memory.h>\n#include <avsystem/commons/avs_stream_net.h>\n#include <avsystem/commons/avs_stream_v_table.h>\n\n#include <avsystem/coap/coap.h>\n\n#include <anjay/core.h>\n#include <anjay/stats.h>\n\n#include <anjay_modules/anjay_time_defs.h>\n\n#include <anjay_config_log.h>\n\n#include \"anjay_core.h\"\n#include \"coap/anjay_content_format.h\"\n#include \"coap/anjay_msg_details.h\"\n\n#include \"anjay_bootstrap_core.h\"\n#include \"anjay_dm_core.h\"\n#include \"anjay_downloader.h\"\n#include \"anjay_io_core.h\"\n#include \"anjay_servers_utils.h\"\n#include \"anjay_utils_private.h\"\n#ifdef ANJAY_WITH_LWM2M_GATEWAY\n#    include <string.h>\n#endif // ANJAY_WITH_LWM2M_GATEWAY\n\n#include \"dm/anjay_dm_write_attrs.h\"\n\nVISIBILITY_SOURCE_BEGIN\n\n#ifndef ANJAY_VERSION\n#    define ANJAY_VERSION \"23f4cd115\"\n#endif // ANJAY_VERSION\n\n#ifdef ANJAY_WITH_LWM2M11\nstatic anjay_lwm2m_version_config_t ALL_VERSIONS = {\n    .minimum_version = ANJAY_LWM2M_VERSION_1_0,\n    .maximum_version =\n#    ifdef ANJAY_WITH_LWM2M12\n            ANJAY_LWM2M_VERSION_1_2\n#    else  // ANJAY_WITH_LWM2M12\n            ANJAY_LWM2M_VERSION_1_1\n#    endif // ANJAY_WITH_LWM2M12\n};\n#endif // ANJAY_WITH_LWM2M11\n\nstatic int init_anjay(anjay_unlocked_t *anjay,\n                      const anjay_configuration_t *config) {\n#ifdef ANJAY_WITH_THREAD_SAFETY\n    if (!(anjay->coap_sched = avs_sched_new(\"Anjay CoAP\", NULL))) {\n        _anjay_log_oom();\n        return -1;\n    }\n#endif // ANJAY_WITH_THREAD_SAFETY\n\n#ifdef ANJAY_WITH_LWM2M11\n    if (config->lwm2m_version_config) {\n        if (config->lwm2m_version_config->maximum_version\n                < config->lwm2m_version_config->minimum_version) {\n            anjay_log(ERROR,\n                      _(\"lwm2m_version_config->maximum_version must not be \"\n                        \"less than lwm2m_version_config->minimum_version\"));\n            return -1;\n        } else if (config->lwm2m_version_config->minimum_version\n                           < ALL_VERSIONS.minimum_version\n                   || config->lwm2m_version_config->maximum_version\n                              > ALL_VERSIONS.maximum_version) {\n            anjay_log(ERROR, _(\"invalid values in lwm2m_version_config\"));\n            return -1;\n        }\n        anjay->lwm2m_version_config = *config->lwm2m_version_config;\n    } else {\n        anjay->lwm2m_version_config = ALL_VERSIONS;\n    }\n\n    anjay->initial_trust_store.use_system_wide = config->use_system_trust_store;\n    anjay->rebuild_client_cert_chain = config->rebuild_client_cert_chain;\n\n    avs_error_t err;\n    if (avs_is_err((err = avs_crypto_certificate_chain_info_copy_as_list(\n                            &anjay->initial_trust_store.certs,\n                            config->trust_store_certs)))\n            || avs_is_err(\n                       (err = avs_crypto_cert_revocation_list_info_copy_as_list(\n                                &anjay->initial_trust_store.crls,\n                                config->trust_store_crls)))) {\n        anjay_log(ERROR, _(\"Could not copy trust store: \") \"%s\",\n                  AVS_COAP_STRERROR(err));\n        return -1;\n    }\n#endif // ANJAY_WITH_LWM2M11\n\n#ifdef ANJAY_WITH_BOOTSTRAP\n    bool legacy_server_initiated_bootstrap =\n            !config->disable_legacy_server_initiated_bootstrap;\n#    ifdef ANJAY_WITH_LWM2M11\n    legacy_server_initiated_bootstrap =\n            (legacy_server_initiated_bootstrap\n             && anjay->lwm2m_version_config.minimum_version\n                        == ANJAY_LWM2M_VERSION_1_0);\n#    endif // ANJAY_WITH_LWM2M11\n    _anjay_bootstrap_init(anjay, legacy_server_initiated_bootstrap);\n#endif // ANJAY_WITH_BOOTSTRAP\n\n    anjay->dtls_version = config->dtls_version;\n\n    if (!config->endpoint_name) {\n        anjay_log(ERROR, _(\"endpoint name must not be null\"));\n        return -1;\n    }\n    anjay->endpoint_name = avs_strdup(config->endpoint_name);\n    if (!anjay->endpoint_name) {\n        anjay_log(ERROR, _(\"endpoint name could not be copied\"));\n        return -1;\n    }\n\n    anjay->confirmable_notification_status_cb =\n            config->confirmable_notification_status_cb;\n\n#ifdef ANJAY_WITH_CONN_STATUS_API\n    anjay->server_connection_status_cb = config->server_connection_status_cb;\n\n    if (anjay->server_connection_status_cb) {\n        anjay->server_connection_status_cb_arg =\n                config->server_connection_status_cb_arg;\n    }\n#endif // ANJAY_WITH_CONN_STATUS_API\n#ifdef ANJAY_WITH_SSL_ERROR_API\n    anjay->ssl_error_cb = config->ssl_error_cb;\n\n    if (anjay->ssl_error_cb) {\n        anjay->ssl_error_cb_arg = config->ssl_error_cb_arg;\n    }\n#endif // ANJAY_WITH_SSL_ERROR_API\n\n    anjay->socket_config = config->socket_config;\n    anjay->udp_listen_port = config->udp_listen_port;\n\n    const char *error_msg;\n#ifdef WITH_AVS_COAP_UDP\n    if (config->udp_tx_params) {\n        if (!avs_coap_udp_tx_params_valid(config->udp_tx_params, &error_msg)) {\n            anjay_log(ERROR,\n                      _(\"UDP CoAP transmission parameters are invalid: \") \"%s\",\n                      error_msg);\n            return -1;\n        }\n        anjay->udp_tx_params = *config->udp_tx_params;\n    } else {\n        anjay->udp_tx_params =\n                (avs_coap_udp_tx_params_t) ANJAY_COAP_DEFAULT_UDP_TX_PARAMS;\n    }\n    anjay->udp_exchange_timeout = AVS_COAP_DEFAULT_EXCHANGE_MAX_TIME;\n    if (config->msg_cache_size) {\n        anjay->udp_response_cache =\n                avs_coap_udp_response_cache_create(config->msg_cache_size);\n        if (!anjay->udp_response_cache) {\n            _anjay_log_oom();\n            return -1;\n        }\n    }\n#endif // WITH_AVS_COAP_UDP\n\n    if (config->udp_dtls_hs_tx_params) {\n        if (!avs_time_duration_less(config->udp_dtls_hs_tx_params->min,\n                                    config->udp_dtls_hs_tx_params->max)) {\n            anjay_log(ERROR,\n                      _(\"UDP DTLS Handshake transmission parameters are \"\n                        \"invalid: min >= max\"));\n            return -1;\n        }\n        anjay->udp_dtls_hs_tx_params = *config->udp_dtls_hs_tx_params;\n    } else {\n        anjay->udp_dtls_hs_tx_params = (avs_net_dtls_handshake_timeouts_t)\n                ANJAY_DTLS_DEFAULT_UDP_HS_TX_PARAMS;\n    }\n\n    if (_anjay_copy_tls_ciphersuites(&anjay->default_tls_ciphersuites,\n                                     &config->default_tls_ciphersuites)) {\n        return -1;\n    }\n\n#ifdef ANJAY_WITH_LWM2M11\n    anjay->queue_mode_preference = ANJAY_PREFER_ONLINE_MODE;\n#endif // ANJAY_WITH_LWM2M11\n\n#ifdef WITH_AVS_COAP_TCP\n#    if defined(ANJAY_WITH_LWM2M11) || defined(ANJAY_WITH_COAP_DOWNLOAD)\n    // completely arbitrary default value\n    static const size_t ANJAY_DEFAULT_COAP_TCP_MAX_OPTIONS_SIZE = 128;\n    if (config->coap_tcp_max_options_size == 0) {\n        anjay->coap_tcp_max_options_size =\n                ANJAY_DEFAULT_COAP_TCP_MAX_OPTIONS_SIZE;\n    } else {\n        anjay->coap_tcp_max_options_size = config->coap_tcp_max_options_size;\n    }\n\n    static const avs_time_duration_t ANJAY_DEFAULT_COAP_TCP_REQUEST_TIMEOUT = {\n        .seconds = 30\n    };\n    if (avs_time_duration_valid(config->coap_tcp_request_timeout)\n            && !avs_time_duration_equal(config->coap_tcp_request_timeout,\n                                        AVS_TIME_DURATION_ZERO)) {\n        anjay->coap_tcp_request_timeout = config->coap_tcp_request_timeout;\n    } else {\n        anjay->coap_tcp_request_timeout =\n                ANJAY_DEFAULT_COAP_TCP_REQUEST_TIMEOUT;\n    }\n    anjay->tcp_exchange_timeout = AVS_COAP_DEFAULT_EXCHANGE_MAX_TIME;\n#    endif // defined(ANJAY_WITH_LWM2M11) || defined(ANJAY_WITH_COAP_DOWNLOAD)\n#endif     // WITH_AVS_COAP_TCP\n\n    anjay->in_shared_buffer = avs_shared_buffer_new(config->in_buffer_size);\n    if (!anjay->in_shared_buffer) {\n        _anjay_log_oom();\n        return -1;\n    }\n    anjay->out_shared_buffer = avs_shared_buffer_new(config->out_buffer_size);\n    if (!anjay->out_shared_buffer) {\n        _anjay_log_oom();\n        return -1;\n    }\n\n    _anjay_observe_init(&anjay->observe,\n                        config->confirmable_notifications,\n                        config->stored_notification_limit);\n\n    anjay->online_transports =\n            _anjay_transport_set_remove_unavailable(anjay,\n                                                    ANJAY_TRANSPORT_SET_ALL);\n\n#ifdef ANJAY_WITH_DOWNLOADER\n    if (_anjay_downloader_init(&anjay->downloader, anjay)) {\n        return -1;\n    }\n#endif // ANJAY_WITH_DOWNLOADER\n\n    anjay->prefer_hierarchical_formats = config->prefer_hierarchical_formats;\n    anjay->update_immediately_on_dm_change =\n            config->update_immediately_on_dm_change;\n    anjay->connection_error_is_registration_failure =\n            config->connection_error_is_registration_failure;\n    anjay->enable_self_notify = config->enable_self_notify;\n    anjay->use_connection_id = config->use_connection_id;\n    anjay->additional_tls_config_clb = config->additional_tls_config_clb;\n\n#ifdef ANJAY_WITH_COAP_DOWNLOAD\n    anjay->coap_downloader_retry_count = config->coap_downloader_retry_count;\n    anjay->coap_downloader_retry_delay = config->coap_downloader_retry_delay;\n#endif // ANJAY_WITH_COAP_DOWNLOAD\n\n    if (config->prng_ctx) {\n        anjay->prng_ctx.allocated_by_user = true;\n        anjay->prng_ctx.ctx = config->prng_ctx;\n    } else {\n        anjay->prng_ctx.ctx = avs_crypto_prng_new(NULL, NULL);\n        if (!anjay->prng_ctx.ctx) {\n            anjay_log(ERROR, _(\"failed to create PRNG context\"));\n            return -1;\n        }\n    }\n\n#ifdef ANJAY_WITH_ATTR_STORAGE\n    if (_anjay_attr_storage_init(&anjay->attr_storage, &anjay->dm)) {\n        return -1;\n    }\n#endif // ANJAY_WITH_ATTR_STORAGE\n\n    return 0;\n}\n\n#ifdef ANJAY_WITH_THREAD_SAFETY\nstatic void coap_sched_job(avs_sched_t *sched, const void *dummy) {\n    (void) dummy;\n    anjay_t *anjay_locked = _anjay_get_from_sched(sched);\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    if (anjay->coap_sched) {\n        avs_sched_run(anjay->coap_sched);\n    }\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n}\n\nvoid _anjay_reschedule_coap_sched_job(anjay_unlocked_t *anjay) {\n    // NOTE: This function is implicitly called at every ANJAY_MUTEX_UNLOCK()\n    // This is necessary because the CoAP jobs need to be run with the Anjay\n    // mutex locked, and the main scheduler is run without that lock\n    if (anjay->coap_sched) {\n        avs_time_monotonic_t next_job_time =\n                avs_sched_time_of_next(anjay->coap_sched);\n        if (avs_time_monotonic_valid(next_job_time)) {\n            if ((!anjay->coap_sched_job_handle\n                 || AVS_RESCHED_AT(&anjay->coap_sched_job_handle,\n                                   next_job_time))\n                    && AVS_SCHED_AT(anjay->sched, &anjay->coap_sched_job_handle,\n                                    next_job_time, coap_sched_job, NULL, 0)) {\n                anjay_log(ERROR, _(\"Could not reschedule coap_sched_job\"));\n            }\n        } else {\n            avs_sched_del(&anjay->coap_sched_job_handle);\n        }\n    }\n}\n#endif // ANJAY_WITH_THREAD_SAFETY\n\nconst char *anjay_get_version(void) {\n    return ANJAY_VERSION;\n}\n\nstatic anjay_t *alloc_anjay(void) {\n    anjay_log(INFO, _(\"Initializing Anjay \") ANJAY_VERSION);\n    _anjay_log_feature_list();\n    size_t alloc_size = sizeof(anjay_unlocked_t);\n#ifdef ANJAY_WITH_THREAD_SAFETY\n    alloc_size += offsetof(anjay_t, anjay_unlocked_placeholder);\n#endif // ANJAY_WITH_THREAD_SAFETY\n    anjay_t *out = (anjay_t *) avs_calloc(1, alloc_size);\n    if (!out) {\n        _anjay_log_oom();\n        return NULL;\n    }\n#ifdef ANJAY_WITH_THREAD_SAFETY\n    if (avs_mutex_create(&out->mutex)) {\n        anjay_log(ERROR, _(\"Could not create mutex\"));\n        avs_free(out);\n        return NULL;\n    }\n    anjay_unlocked_t *anjay =\n            (anjay_unlocked_t *) &out->anjay_unlocked_placeholder;\n#else  // ANJAY_WITH_THREAD_SAFETY\n    anjay_unlocked_t *anjay = out;\n#endif // ANJAY_WITH_THREAD_SAFETY\n    if (!(anjay->sched = avs_sched_new(\"Anjay\", out))) {\n        _anjay_log_oom();\n#ifdef ANJAY_WITH_THREAD_SAFETY\n        avs_mutex_cleanup(&out->mutex);\n#endif // ANJAY_WITH_THREAD_SAFETY\n        avs_free(out);\n        return NULL;\n    }\n    return out;\n}\n\n#ifdef ANJAY_WITH_LWM2M11\nvoid _anjay_trust_store_cleanup(anjay_trust_store_t *trust_store) {\n    AVS_LIST_CLEAR(&trust_store->certs);\n    AVS_LIST_CLEAR(&trust_store->crls);\n}\n#endif // ANJAY_WITH_LWM2M11\n\nstatic void anjay_cleanup_impl(anjay_unlocked_t *anjay, bool deregister) {\n    anjay_log(TRACE, _(\"deleting anjay object\"));\n\n#ifdef ANJAY_WITH_DOWNLOADER\n    _anjay_downloader_cleanup(&anjay->downloader);\n#endif // ANJAY_WITH_DOWNLOADER\n\n    if (deregister) {\n        _anjay_servers_deregister(anjay);\n    }\n\n    // Make sure to deregister from all servers *before* cleaning up the\n    // scheduler. That prevents us from updating a registration even though\n    // we're about to deregister anyway.\n    _anjay_servers_cleanup(anjay);\n\n    _anjay_bootstrap_cleanup(anjay);\n\n    // we want to clear this now so that notifications won't be sent during\n    // avs_sched_cleanup()\n    _anjay_observe_cleanup(&anjay->observe);\n\n#ifdef ANJAY_WITH_ATTR_STORAGE\n    _anjay_attr_storage_cleanup(&anjay->attr_storage);\n#endif // ANJAY_WITH_ATTR_STORAGE\n    _anjay_dm_cleanup(&anjay->dm);\n    _anjay_notify_clear_queue(&anjay->scheduled_notify.queue);\n\n#ifdef ANJAY_WITH_SEND\n    _anjay_send_cleanup(&anjay->sender);\n#endif // ANJAY_WITH_SEND\n\n    avs_free(anjay->default_tls_ciphersuites.ids);\n    avs_free(anjay->endpoint_name);\n\n#ifdef WITH_AVS_COAP_UDP\n    avs_coap_udp_response_cache_release(&anjay->udp_response_cache);\n#endif // WITH_AVS_COAP_UDP\n\n    avs_sched_del(&anjay->reload_servers_sched_job_handle);\n    avs_sched_del(&anjay->scheduled_notify.handle);\n\n    ANJAY_MUTEX_UNLOCK_FOR_CALLBACK(anjay_locked, anjay);\n    avs_sched_cleanup(&anjay->sched);\n    ANJAY_MUTEX_LOCK_AFTER_CALLBACK(anjay_locked);\n#ifdef ANJAY_WITH_THREAD_SAFETY\n    avs_sched_cleanup(&anjay->coap_sched);\n#endif // ANJAY_WITH_THREAD_SAFETY\n\n    if (!anjay->prng_ctx.allocated_by_user) {\n        avs_crypto_prng_free(&anjay->prng_ctx.ctx);\n    }\n\n    avs_free(anjay->in_shared_buffer);\n    avs_free(anjay->out_shared_buffer);\n    _anjay_security_config_cache_cleanup(&anjay->security_config_from_dm_cache);\n\n#ifdef ANJAY_WITH_LWM2M11\n    _anjay_trust_store_cleanup(&anjay->initial_trust_store);\n#endif // ANJAY_WITH_LWM2M11\n}\n\nanjay_t *anjay_new(const anjay_configuration_t *config) {\n    anjay_t *out = alloc_anjay();\n    if (!out) {\n        return NULL;\n    }\n\n    int result = -1;\n    ANJAY_MUTEX_LOCK(anjay, out);\n    result = init_anjay(anjay, config);\n    if (result) {\n        anjay_cleanup_impl(anjay, true);\n    }\n    ANJAY_MUTEX_UNLOCK(out);\n\n    if (result) {\n#ifdef ANJAY_WITH_THREAD_SAFETY\n        avs_mutex_cleanup(&out->mutex);\n        anjay_unlocked_t *anjay_unlocked =\n                (anjay_unlocked_t *) &out->anjay_unlocked_placeholder;\n        avs_sched_t **sched_ptr = &anjay_unlocked->sched;\n        if (*sched_ptr) {\n            avs_sched_cleanup(sched_ptr);\n        }\n#endif // ANJAY_WITH_THREAD_SAFETY\n        avs_free(out);\n        out = NULL;\n    }\n    return out;\n}\n\nvoid anjay_delete(anjay_t *anjay) {\n#ifdef ANJAY_WITH_THREAD_SAFETY\n    int lock_result = avs_mutex_lock(anjay->mutex);\n    if (lock_result) {\n        anjay_log(WARNING, _(\"Could not lock mutex\"));\n    }\n    anjay_cleanup_impl((anjay_unlocked_t *) &anjay->anjay_unlocked_placeholder,\n                       true);\n    if (!lock_result) {\n        avs_mutex_unlock(anjay->mutex);\n    }\n    avs_mutex_cleanup(&anjay->mutex);\n#else  // ANJAY_WITH_THREAD_SAFETY\n    anjay_cleanup_impl(anjay, true);\n#endif // ANJAY_WITH_THREAD_SAFETY\n    avs_free(anjay);\n}\n\nstatic void\nsplit_query_string(char *query, const char **out_key, const char **out_value) {\n    char *eq = strchr(query, '=');\n\n    *out_key = query;\n\n    if (eq) {\n        *eq = '\\0';\n        *out_value = eq + 1;\n    } else {\n        *out_value = NULL;\n    }\n}\n\nstatic int parse_nullable_integer(const char *key_str,\n                                  const char *integer_str,\n                                  bool *out_present,\n                                  int32_t *out_value) {\n    long long num;\n    if (*out_present) {\n        anjay_log(WARNING, _(\"Duplicated attribute in query string: \") \"%s\",\n                  key_str);\n        return -1;\n    } else if (!integer_str) {\n        *out_present = true;\n        *out_value = ANJAY_ATTRIB_INTEGER_NONE;\n        return 0;\n    } else if (_anjay_safe_strtoll(integer_str, &num) || num < 0) {\n        return -1;\n    } else {\n        *out_present = true;\n        *out_value = (int32_t) num;\n        return 0;\n    }\n}\n\nstatic int parse_nullable_double(const char *key_str,\n                                 const char *double_str,\n                                 bool *out_present,\n                                 double *out_value) {\n    if (*out_present) {\n        anjay_log(WARNING, _(\"Duplicated attribute in query string: \") \"%s\",\n                  key_str);\n        return -1;\n    } else if (!double_str) {\n        *out_present = true;\n        *out_value = ANJAY_ATTRIB_DOUBLE_NONE;\n        return 0;\n    } else if (_anjay_safe_strtod(double_str, out_value) || isnan(*out_value)) {\n        return -1;\n    } else {\n        *out_present = true;\n        return 0;\n    }\n}\n\n#ifdef ANJAY_WITH_CON_ATTR\nstatic int parse_con(const char *value,\n                     bool *out_present,\n                     anjay_dm_con_attr_t *out_value) {\n    if (*out_present) {\n        anjay_log(WARNING, _(\"Duplicated attribute in query string: con\"));\n        return -1;\n    } else if (!value) {\n        *out_present = true;\n        *out_value = ANJAY_DM_CON_ATTR_NONE;\n        return 0;\n    } else if (strcmp(value, \"0\") == 0) {\n        *out_present = true;\n        *out_value = ANJAY_DM_CON_ATTR_NON;\n        return 0;\n    } else if (strcmp(value, \"1\") == 0) {\n        *out_present = true;\n        *out_value = ANJAY_DM_CON_ATTR_CON;\n        return 0;\n    } else {\n        anjay_log(WARNING, _(\"Invalid con attribute value: \") \"%s\", value);\n        return -1;\n    }\n}\n#endif // ANJAY_WITH_CON_ATTR\n\n#ifdef ANJAY_WITH_LWM2M12\nstatic int parse_edge(const char *value,\n                      bool *out_present,\n                      anjay_dm_edge_attr_t *out_value) {\n    if (*out_present) {\n        anjay_log(WARNING, _(\"Duplicated attribute in query string: edge\"));\n        return -1;\n    } else if (!value) {\n        *out_present = true;\n        *out_value = ANJAY_DM_EDGE_ATTR_NONE;\n        return 0;\n    } else if (strcmp(value, \"0\") == 0) {\n        *out_present = true;\n        *out_value = ANJAY_DM_EDGE_ATTR_FALLING;\n        return 0;\n    } else if (strcmp(value, \"1\") == 0) {\n        *out_present = true;\n        *out_value = ANJAY_DM_EDGE_ATTR_RISING;\n        return 0;\n    } else {\n        anjay_log(WARNING, _(\"Invalid edge attribute value: \") \"%s\", value);\n        return -1;\n    }\n}\n\nstatic int parse_depth(const char *value, int8_t *out_depth) {\n    long long llvalue;\n    if (*out_depth >= 0) {\n        anjay_log(WARNING, _(\"Duplicated query string entry: depth\"));\n        return -1;\n    } else if (_anjay_safe_strtoll(value, &llvalue) || llvalue < 0\n               || llvalue > 3) {\n        return -1;\n    } else {\n        *out_depth = (int8_t) llvalue;\n        return 0;\n    }\n}\n#endif // ANJAY_WITH_LWM2M12\n\nstatic int parse_query(anjay_request_attributes_t *out_attrs,\n#ifdef ANJAY_WITH_LWM2M12\n                       int8_t *out_depth,\n#endif // ANJAY_WITH_LWM2M12\n                       const char *key,\n                       const char *value) {\n    if (!strcmp(key, ANJAY_ATTR_PMIN)) {\n        return parse_nullable_integer(key, value, &out_attrs->has_min_period,\n                                      &out_attrs->values.common.min_period);\n    } else if (!strcmp(key, ANJAY_ATTR_PMAX)) {\n        return parse_nullable_integer(key, value, &out_attrs->has_max_period,\n                                      &out_attrs->values.common.max_period);\n    } else if (!strcmp(key, ANJAY_ATTR_EPMIN)) {\n        return parse_nullable_integer(\n                key, value, &out_attrs->has_min_eval_period,\n                &out_attrs->values.common.min_eval_period);\n    } else if (!strcmp(key, ANJAY_ATTR_EPMAX)) {\n        return parse_nullable_integer(\n                key, value, &out_attrs->has_max_eval_period,\n                &out_attrs->values.common.max_eval_period);\n#ifdef ANJAY_WITH_LWM2M12\n    } else if (!strcmp(key, ANJAY_ATTR_HQMAX)) {\n        return parse_nullable_integer(key, value, &out_attrs->has_hqmax,\n                                      &out_attrs->values.common.hqmax);\n#endif // ANJAY_WITH_LWM2M12\n    } else if (!strcmp(key, ANJAY_ATTR_GT)) {\n        return parse_nullable_double(key, value, &out_attrs->has_greater_than,\n                                     &out_attrs->values.greater_than);\n    } else if (!strcmp(key, ANJAY_ATTR_LT)) {\n        return parse_nullable_double(key, value, &out_attrs->has_less_than,\n                                     &out_attrs->values.less_than);\n    } else if (!strcmp(key, ANJAY_ATTR_ST)) {\n        return parse_nullable_double(key, value, &out_attrs->has_step,\n                                     &out_attrs->values.step);\n#ifdef ANJAY_WITH_LWM2M12\n    } else if (!strcmp(key, ANJAY_ATTR_EDGE)) {\n        return parse_edge(value, &out_attrs->has_edge, &out_attrs->values.edge);\n#endif // ANJAY_WITH_LWM2M12\n#ifdef ANJAY_WITH_CON_ATTR\n    } else if (!strcmp(key, ANJAY_CUSTOM_ATTR_CON)) {\n        return parse_con(value, &out_attrs->has_con,\n                         &out_attrs->values.common.con);\n#endif // ANJAY_WITH_CON_ATTR\n#ifdef ANJAY_WITH_LWM2M12\n    } else if (!strcmp(key, \"depth\")) {\n        return parse_depth(value, out_depth);\n#endif // ANJAY_WITH_LWM2M12\n    } else {\n        anjay_log(DEBUG, _(\"unrecognized query string: \") \"%s\" _(\" = \") \"%s\",\n                  key, value ? value : \"(null)\");\n        return -1;\n    }\n}\n\nstatic int parse_queries(const avs_coap_request_header_t *hdr,\n                         anjay_request_attributes_t *out_attrs\n#ifdef ANJAY_WITH_LWM2M12\n                         ,\n                         int8_t *out_depth\n#endif // ANJAY_WITH_LWM2M12\n) {\n    memset(out_attrs, 0, sizeof(*out_attrs));\n    out_attrs->values = ANJAY_DM_R_ATTRIBUTES_EMPTY;\n\n#ifdef ANJAY_WITH_LWM2M12\n    *out_depth = -1;\n#endif // ANJAY_WITH_LWM2M12\n\n    char buffer[ANJAY_MAX_URI_QUERY_SEGMENT_SIZE];\n    size_t attr_size;\n\n    int result;\n    avs_coap_option_iterator_t it = AVS_COAP_OPTION_ITERATOR_EMPTY;\n    while ((result = avs_coap_options_get_string_it(\n                    &hdr->options, AVS_COAP_OPTION_URI_QUERY, &it, &attr_size,\n                    buffer, sizeof(buffer) - 1))\n           == 0) {\n        const char *key;\n        const char *value;\n\n        buffer[attr_size] = '\\0';\n        split_query_string(buffer, &key, &value);\n        assert(key != NULL);\n\n        if (parse_query(out_attrs,\n#ifdef ANJAY_WITH_LWM2M12\n                        out_depth,\n#endif // ANJAY_WITH_LWM2M12\n                        key, value)) {\n            anjay_log(DEBUG, _(\"invalid query string: \") \"%s\" _(\" = \") \"%s\",\n                      key, value ? value : \"(null)\");\n            return -1;\n        }\n    }\n\n    if (result < 0) {\n        anjay_log(WARNING, _(\"could not read Request-Query\"));\n        return -1;\n    }\n\n    return 0;\n}\n\nstatic const char *action_to_string(anjay_request_action_t action) {\n    switch (action) {\n    case ANJAY_ACTION_READ:\n        return \"Read\";\n#ifdef ANJAY_WITH_LWM2M11\n    case ANJAY_ACTION_READ_COMPOSITE:\n        return \"Read Composite\";\n#endif // ANJAY_WITH_LWM2M11\n    case ANJAY_ACTION_DISCOVER:\n        return \"Discover\";\n    case ANJAY_ACTION_WRITE:\n        return \"Write\";\n    case ANJAY_ACTION_WRITE_UPDATE:\n        return \"Write (Update)\";\n    case ANJAY_ACTION_WRITE_ATTRIBUTES:\n        return \"Write Attributes\";\n#ifdef ANJAY_WITH_LWM2M11\n    case ANJAY_ACTION_WRITE_COMPOSITE:\n        return \"Write Composite\";\n#endif // ANJAY_WITH_LWM2M11\n    case ANJAY_ACTION_EXECUTE:\n        return \"Execute\";\n    case ANJAY_ACTION_CREATE:\n        return \"Create\";\n    case ANJAY_ACTION_DELETE:\n        return \"Delete\";\n    case ANJAY_ACTION_BOOTSTRAP_FINISH:\n        return \"Bootstrap Finish\";\n    }\n    AVS_UNREACHABLE(\"invalid enum value\");\n    return \"<invalid action>\";\n}\n\nstatic int code_to_action(uint8_t code,\n                          uint16_t requested_format,\n                          bool is_bs_uri,\n                          const anjay_uri_path_t *path,\n                          bool has_content_format,\n                          anjay_request_action_t *out_action) {\n    switch (code) {\n    case AVS_COAP_CODE_GET:\n        if (requested_format == AVS_COAP_FORMAT_LINK_FORMAT) {\n            *out_action = ANJAY_ACTION_DISCOVER;\n        } else {\n            *out_action = ANJAY_ACTION_READ;\n        }\n        return 0;\n    case AVS_COAP_CODE_POST:\n        if (is_bs_uri) {\n            *out_action = ANJAY_ACTION_BOOTSTRAP_FINISH;\n        } else if (_anjay_uri_path_leaf_is(path, ANJAY_ID_IID)) {\n            *out_action = ANJAY_ACTION_WRITE_UPDATE;\n        } else if (_anjay_uri_path_leaf_is(path, ANJAY_ID_RID)) {\n            *out_action = ANJAY_ACTION_EXECUTE;\n        } else if (_anjay_uri_path_leaf_is(path, ANJAY_ID_RIID)) {\n            *out_action = ANJAY_ACTION_WRITE;\n        } else {\n            // root or object path\n            *out_action = ANJAY_ACTION_CREATE;\n        }\n        return 0;\n    case AVS_COAP_CODE_PUT:\n        if (has_content_format) {\n            *out_action = ANJAY_ACTION_WRITE;\n        } else {\n            *out_action = ANJAY_ACTION_WRITE_ATTRIBUTES;\n        }\n        return 0;\n    case AVS_COAP_CODE_DELETE:\n        *out_action = ANJAY_ACTION_DELETE;\n        return 0;\n#ifdef ANJAY_WITH_LWM2M11\n    case AVS_COAP_CODE_FETCH:\n        *out_action = ANJAY_ACTION_READ_COMPOSITE;\n        return 0;\n    case AVS_COAP_CODE_IPATCH:\n        *out_action = ANJAY_ACTION_WRITE_COMPOSITE;\n        return 0;\n#endif // ANJAY_WITH_LWM2M11\n    default:\n        anjay_log(DEBUG, _(\"unrecognized CoAP method: \") \"%s\",\n                  AVS_COAP_CODE_STRING(code));\n        return -1;\n    }\n}\n\nstatic int parse_action(const avs_coap_request_header_t *hdr,\n                        anjay_request_t *inout_request) {\n    if (avs_coap_options_get_u16(&hdr->options, AVS_COAP_OPTION_ACCEPT,\n                                 &inout_request->requested_format)) {\n        inout_request->requested_format = AVS_COAP_FORMAT_NONE;\n    }\n\n    bool has_content_format =\n            (inout_request->content_format != AVS_COAP_FORMAT_NONE);\n    int result = code_to_action(inout_request->request_code,\n                                inout_request->requested_format,\n                                inout_request->is_bs_uri, &inout_request->uri,\n                                has_content_format, &inout_request->action);\n    if (!result) {\n        anjay_log(DEBUG, _(\"LwM2M action: \") \"%s\",\n                  action_to_string(inout_request->action));\n    }\n    return result;\n}\n\nstatic int parse_request_uri_segment(const char *uri, uint16_t *out_id) {\n    long long num;\n    if (_anjay_safe_strtoll(uri, &num) || num < 0 || num >= UINT16_MAX) {\n        anjay_log(DEBUG, _(\"invalid Uri-Path segment: \") \"%s\", uri);\n        return -1;\n    }\n\n    *out_id = (uint16_t) num;\n    return 0;\n}\n\nstatic int parse_bs_uri(const avs_coap_request_header_t *hdr, bool *out_is_bs) {\n    char uri[ANJAY_MAX_URI_SEGMENT_SIZE] = \"\";\n    size_t uri_size;\n\n    *out_is_bs = false;\n\n    avs_coap_option_iterator_t it = AVS_COAP_OPTION_ITERATOR_EMPTY;\n    int result =\n            avs_coap_options_get_string_it(&hdr->options,\n                                           AVS_COAP_OPTION_URI_PATH, &it,\n                                           &uri_size, uri, sizeof(uri) - 1);\n\n    if (result) {\n        return (result == AVS_COAP_OPTION_MISSING) ? 0 : result;\n    }\n\n    if (strcmp(uri, \"bs\") == 0) {\n        result =\n                avs_coap_options_get_string_it(&hdr->options,\n                                               AVS_COAP_OPTION_URI_PATH, &it,\n                                               &uri_size, uri, sizeof(uri) - 1);\n        if (result == AVS_COAP_OPTION_MISSING) {\n            *out_is_bs = true;\n            return 0;\n        }\n    }\n\n    return result;\n}\n\nstatic int parse_dm_uri(const avs_coap_request_header_t *hdr,\n                        anjay_uri_path_t *out_uri) {\n    char uri[ANJAY_MAX_URI_SEGMENT_SIZE] = \"\";\n    size_t uri_size;\n\n    *out_uri = MAKE_ROOT_PATH();\n\n    avs_coap_option_iterator_t it = AVS_COAP_OPTION_ITERATOR_EMPTY;\n\n    size_t segment_index = 0;\n    bool expect_no_more_options = false;\n    int result = 0;\n#ifdef ANJAY_WITH_LWM2M_GATEWAY\n    bool prefix_found = false;\n#endif // ANJAY_WITH_LWM2M_GATEWAY\n\n    while (!(result = avs_coap_options_get_string_it(\n                     &hdr->options, AVS_COAP_OPTION_URI_PATH, &it, &uri_size,\n                     uri, sizeof(uri) - 1))) {\n        if (segment_index == 0 && uri[0] == '\\0') {\n            // Empty URI segment is only allowed as the first and only segment\n            // as an alternative representation of an empty path.\n            expect_no_more_options = true;\n        } else if (expect_no_more_options || uri[0] == '\\0') {\n            anjay_log(WARNING, _(\"superfluous empty Uri-Path segment\"));\n            return -1;\n#ifdef ANJAY_WITH_LWM2M_GATEWAY\n        } else if (segment_index\n                   >= _ANJAY_URI_PATH_MAX_LENGTH + (prefix_found ? 1 : 0)) {\n            // too many segments\n            anjay_log(WARNING, _(\"too many Uri-Path options\"));\n            return -1;\n        } else if (segment_index == 0 && isalpha(uri[0])) {\n            // first segment starts with a letter\n            if (uri_size > ANJAY_GATEWAY_MAX_PREFIX_LEN) {\n                anjay_log(WARNING, _(\"prefix too long\"));\n                return -1;\n            }\n            strcpy(out_uri->prefix, uri);\n            prefix_found = true;\n        } else {\n            size_t path_depth =\n                    prefix_found ? segment_index - 1 : segment_index;\n            if (parse_request_uri_segment(uri, &out_uri->ids[path_depth])) {\n                return -1;\n            }\n        }\n#else  // ANJAY_WITH_LWM2M_GATEWAY\n        } else if (segment_index >= _ANJAY_URI_PATH_MAX_LENGTH) {\n            // 5 or more segments...\n            anjay_log(WARNING, _(\"prefixed Uri-Path are not supported\"));\n            return -1;\n        } else if (parse_request_uri_segment(uri,\n                                             &out_uri->ids[segment_index])) {\n            return -1;\n        }\n#endif // ANJAY_WITH_LWM2M_GATEWAY\n        ++segment_index;\n    }\n\n    return result == AVS_COAP_OPTION_MISSING ? 0 : result;\n}\n\nstatic int parse_request_uri(const avs_coap_request_header_t *hdr,\n                             bool *out_is_bs,\n                             anjay_uri_path_t *out_uri) {\n    int result = parse_bs_uri(hdr, out_is_bs);\n    if (result) {\n        return result;\n    }\n    if (*out_is_bs) {\n        *out_uri = MAKE_ROOT_PATH();\n        return 0;\n    } else {\n        return parse_dm_uri(hdr, out_uri);\n    }\n}\n\nstatic int parse_request(const avs_coap_request_header_t *hdr,\n                         anjay_request_t *out_request,\n                         const avs_coap_observe_id_t *observe_id) {\n    memset(out_request, 0, sizeof(*out_request));\n    out_request->request_code = hdr->code;\n    if (parse_request_uri(hdr, &out_request->is_bs_uri, &out_request->uri)\n            || parse_queries(hdr, &out_request->attributes\n#ifdef ANJAY_WITH_LWM2M12\n                             ,\n                             &out_request->depth\n#endif // ANJAY_WITH_LWM2M12\n                             )\n            || avs_coap_options_get_content_format(&hdr->options,\n                                                   &out_request->content_format)\n            || parse_action(hdr, out_request)) {\n        return -1;\n    }\n    if (out_request->action != ANJAY_ACTION_WRITE_ATTRIBUTES\n#ifdef ANJAY_WITH_OBSERVATION_ATTRIBUTES\n            && !(out_request->action == ANJAY_ACTION_READ && observe_id != NULL)\n            && !(out_request->action == ANJAY_ACTION_READ_COMPOSITE\n                 && observe_id != NULL)\n#endif // ANJAY_WITH_OBSERVATION_ATTRIBUTES\n            && !_anjay_dm_request_attrs_empty(&out_request->attributes)) {\n#ifdef ANJAY_WITH_OBSERVATION_ATTRIBUTES\n        anjay_log(WARNING,\n                  _(\"NOTIFICATION-class attributes present in request \"\n                    \"other than Write-Attributes, Observe or \"\n                    \"Observe-Composite\"));\n#else  // ANJAY_WITH_OBSERVATION_ATTRIBUTES\n        (void) observe_id;\n        anjay_log(WARNING,\n                  _(\"NOTIFICATION-class attributes present in request \"\n                    \"other than Write-Attributes\"));\n#endif // ANJAY_WITH_OBSERVATION_ATTRIBUTES\n        return -1;\n    }\n#ifdef ANJAY_WITH_LWM2M12\n    if (out_request->action != ANJAY_ACTION_DISCOVER\n            && out_request->depth >= 0) {\n        anjay_log(\n                WARNING,\n                _(\"depth query string present in request other than Discover\"));\n        return -1;\n    }\n#endif // ANJAY_WITH_LWM2M12\n    return 0;\n}\n\nuint8_t _anjay_make_error_response_code(int handler_result) {\n    uint8_t handler_code = (uint8_t) (-handler_result);\n    int cls = avs_coap_code_get_class(handler_code);\n    if (cls == 4 || cls == 5) {\n        return handler_code;\n    } else {\n        switch (handler_result) {\n        case ANJAY_OUTCTXERR_FORMAT_MISMATCH:\n        case ANJAY_OUTCTXERR_METHOD_NOT_IMPLEMENTED:\n            return AVS_COAP_CODE_NOT_ACCEPTABLE;\n        default:\n            return -ANJAY_ERR_INTERNAL;\n        }\n    }\n}\n\nstatic bool critical_option_validator(uint8_t msg_code, uint32_t optnum) {\n    if (optnum == AVS_COAP_OPTION_ACCEPT) {\n        return true;\n    }\n    /* Note: BLOCK Options are handled inside stream.c */\n    switch (msg_code) {\n    case AVS_COAP_CODE_GET:\n    case AVS_COAP_CODE_PUT:\n    case AVS_COAP_CODE_POST:\n        return optnum == AVS_COAP_OPTION_URI_PATH\n               || optnum == AVS_COAP_OPTION_URI_QUERY;\n    case AVS_COAP_CODE_DELETE:\n        return optnum == AVS_COAP_OPTION_URI_PATH;\n    default:\n        return false;\n    }\n}\n\nstatic int handle_request(anjay_connection_ref_t connection,\n                          const anjay_request_t *request) {\n    int result = -1;\n\n    if (_anjay_server_ssid(connection.server) == ANJAY_SSID_BOOTSTRAP) {\n        result = _anjay_bootstrap_perform_action(connection, request);\n    } else {\n        result = _anjay_dm_perform_action(connection, request);\n        (void) _anjay_observe_sched_flush(connection);\n    }\n    return result;\n}\n\ntypedef struct {\n    anjay_connection_ref_t connection;\n    int serve_result;\n} handle_incoming_message_args_t;\n\nstatic int\nhandle_incoming_message(avs_coap_streaming_request_ctx_t *ctx,\n                        const avs_coap_request_header_t *request_header,\n                        avs_stream_t *payload_stream,\n                        const avs_coap_observe_id_t *observe_id,\n                        void *args_) {\n    handle_incoming_message_args_t *args =\n            (handle_incoming_message_args_t *) args_;\n\n    if (_anjay_server_ssid(args->connection.server) == ANJAY_SSID_BOOTSTRAP) {\n        anjay_log(DEBUG, _(\"bootstrap server\"));\n    } else {\n        anjay_log(DEBUG, _(\"server ID = \") \"%u\",\n                  _anjay_server_ssid(args->connection.server));\n    }\n\n    anjay_request_t request;\n    if (avs_coap_options_validate_critical(request_header,\n                                           critical_option_validator)\n            || parse_request(request_header, &request, observe_id)) {\n        return AVS_COAP_CODE_BAD_OPTION;\n    }\n    request.ctx = ctx;\n    request.payload_stream = payload_stream;\n    request.observe = observe_id;\n\n    int result = handle_request(args->connection, &request);\n    if (result) {\n        const uint8_t error_code = _anjay_make_error_response_code(result);\n        if (error_code != -result) {\n            anjay_log(WARNING, _(\"invalid error code: \") \"%d\", result);\n        }\n\n        if (avs_coap_code_is_client_error(error_code)) {\n            // the request was invalid; that's not really an error on our side,\n            anjay_log(TRACE, _(\"invalid request: \") \"%s\",\n                      AVS_COAP_CODE_STRING(request_header->code));\n            args->serve_result = 0;\n        } else {\n            anjay_log(DEBUG, _(\"could not handle request: \") \"%s\",\n                      AVS_COAP_CODE_STRING(request_header->code));\n            args->serve_result = result;\n        }\n        return error_code;\n    }\n    return 0;\n}\n\navs_time_duration_t\n_anjay_max_transmit_wait_for_transport(anjay_unlocked_t *anjay,\n                                       anjay_socket_transport_t transport) {\n    switch (transport) {\n    case ANJAY_SOCKET_TRANSPORT_INVALID:\n        return AVS_TIME_DURATION_INVALID;\n#ifdef WITH_AVS_COAP_UDP\n    case ANJAY_SOCKET_TRANSPORT_UDP:\n        return avs_coap_udp_max_transmit_wait(&anjay->udp_tx_params);\n#endif // WITH_AVS_COAP_UDP\n#if defined(ANJAY_WITH_LWM2M11) && defined(WITH_AVS_COAP_TCP)\n    case ANJAY_SOCKET_TRANSPORT_TCP:\n        return anjay->coap_tcp_request_timeout;\n#endif // defined(ANJAY_WITH_LWM2M11) && defined(WITH_AVS_COAP_TCP)\n    default:\n        AVS_UNREACHABLE(\"Should never happen\");\n        return AVS_TIME_DURATION_INVALID;\n    }\n}\n\navs_time_duration_t\n_anjay_exchange_lifetime_for_transport(anjay_unlocked_t *anjay,\n                                       anjay_socket_transport_t transport) {\n    switch (transport) {\n#ifdef WITH_AVS_COAP_UDP\n    case ANJAY_SOCKET_TRANSPORT_UDP:\n        return avs_coap_udp_exchange_lifetime(&anjay->udp_tx_params);\n#endif // WITH_AVS_COAP_UDP\n#if defined(ANJAY_WITH_LWM2M11) && defined(WITH_AVS_COAP_TCP)\n    case ANJAY_SOCKET_TRANSPORT_TCP:\n        // By transforming the formulas from RFC 7252, we can get:\n        //\n        //   EXCHANGE_LIFETIME =\n        //     (MAX_TRANSMIT_WAIT + ACK_TIMEOUT * (2 - ACK_RANDOM_FACTOR)) / 2\n        //     + 2 * MAX_LATENCY\n        //\n        // Which, when using default values of ACK_TIMEOUT, ACK_RANDOM_FACTOR\n        // and MAX_LATENCY, degenerates to:\n        //\n        //   EXCHANGE_LIFETIME = (MAX_TRANSMIT_WAIT + 1) / 2 + 200\n        //\n        // ...and this is exactly what we're calculating here.\n        return avs_time_duration_div(\n                avs_time_duration_add(\n                        anjay->coap_tcp_request_timeout,\n                        avs_time_duration_from_scalar(401, AVS_TIME_S)),\n                2);\n#endif // defined(ANJAY_WITH_LWM2M11) && defined(WITH_AVS_COAP_TCP)\n    case ANJAY_SOCKET_TRANSPORT_INVALID:\n    default:\n        AVS_UNREACHABLE(\"Should never happen\");\n        return AVS_TIME_DURATION_INVALID;\n    }\n}\n\nstatic int serve_connection(anjay_connection_ref_t connection) {\n    if (!_anjay_connection_get_online_socket(connection)) {\n        anjay_log(ERROR, _(\"server connection is not online\"));\n        return -1;\n    }\n\n    avs_coap_ctx_t *coap = _anjay_connection_get_coap(connection);\n    assert(coap);\n\n    handle_incoming_message_args_t args = {\n        .connection = connection,\n        .serve_result = 0\n    };\n    avs_error_t err = avs_coap_streaming_handle_incoming_packet(\n            coap, handle_incoming_message, &args);\n    _anjay_connection_schedule_queue_mode_close(connection);\n\n    avs_coap_error_recovery_action_t recovery_action =\n            avs_coap_error_recovery_action(err);\n    if (recovery_action == AVS_COAP_ERR_RECOVERY_RECREATE_CONTEXT) {\n        _anjay_server_on_fatal_coap_error(connection, err);\n    } else if (err.category == AVS_ERRNO_CATEGORY && err.code == AVS_ENODEV) {\n        anjay_log(WARNING,\n                  _(\"ENODEV returned from the networking layer, ignoring\"));\n    } else if (recovery_action == AVS_COAP_ERR_RECOVERY_UNKNOWN\n               && (err.category != AVS_COAP_ERR_CATEGORY\n                   || avs_coap_error_class(err) != AVS_COAP_ERR_CLASS_OTHER)) {\n        _anjay_server_on_server_communication_error(connection.server, err);\n    }\n\n#ifdef ANJAY_WITH_COMMUNICATION_TIMESTAMP_API\n    if (avs_is_ok(err) && !args.serve_result) {\n        _anjay_server_set_last_communication_time(connection.server);\n    }\n#endif // ANJAY_WITH_COMMUNICATION_TIMESTAMP_API\n\n    return avs_is_ok(err) ? args.serve_result : -1;\n}\n\nint _anjay_serve_unlocked(anjay_unlocked_t *anjay,\n                          avs_net_socket_t *ready_socket) {\n    _anjay_security_config_cache_cleanup(&anjay->security_config_from_dm_cache);\n\n#ifdef ANJAY_WITH_DOWNLOADER\n    if (!_anjay_downloader_handle_packet(&anjay->downloader, ready_socket)) {\n        return 0;\n    }\n#endif // ANJAY_WITH_DOWNLOADER\n\n    anjay_connection_ref_t connection = {\n        .server = _anjay_servers_find_by_primary_socket(anjay, ready_socket),\n        .conn_type = ANJAY_CONNECTION_PRIMARY\n    };\n    if (!connection.server) {\n        return -1;\n    }\n    return serve_connection(connection);\n}\n\nint anjay_serve(anjay_t *anjay_locked, avs_net_socket_t *ready_socket) {\n    int result = -1;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    result = _anjay_serve_unlocked(anjay, ready_socket);\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return result;\n}\n\navs_sched_t *_anjay_get_scheduler_unlocked(anjay_unlocked_t *anjay) {\n    return anjay->sched;\n}\n\navs_sched_t *anjay_get_scheduler(anjay_t *anjay) {\n    if (!anjay) {\n        return NULL;\n    }\n#ifdef ANJAY_WITH_THREAD_SAFETY\n    anjay_unlocked_t *anjay_unlocked =\n            (anjay_unlocked_t *) &anjay->anjay_unlocked_placeholder;\n    return anjay_unlocked->sched;\n#else  // ANJAY_WITH_THREAD_SAFETY\n    return anjay->sched;\n#endif // ANJAY_WITH_THREAD_SAFETY\n}\n\nint anjay_sched_time_to_next(anjay_t *anjay, avs_time_duration_t *out_delay) {\n    *out_delay = AVS_TIME_DURATION_INVALID;\n    avs_sched_t *sched = anjay_get_scheduler(anjay);\n    if (sched) {\n        *out_delay = avs_sched_time_to_next(sched);\n    }\n    return avs_time_duration_valid(*out_delay) ? 0 : -1;\n}\n\nint anjay_sched_time_to_next_ms(anjay_t *anjay, int *out_delay_ms) {\n    avs_time_duration_t delay;\n    int result = anjay_sched_time_to_next(anjay, &delay);\n    if (!result) {\n        int64_t delay_ms;\n        result = avs_time_duration_to_scalar(&delay_ms, AVS_TIME_MS, delay);\n        if (!result) {\n            assert(delay_ms >= 0); // guaranteed by _anjay_sched_time_to_next()\n            *out_delay_ms = (int) AVS_MIN(delay_ms, INT_MAX);\n        }\n    }\n    return result;\n}\n\nint anjay_sched_calculate_wait_time_ms(anjay_t *anjay, int limit_ms) {\n    int time_to_next_ms;\n    if (!anjay_sched_time_to_next_ms(anjay, &time_to_next_ms)\n            && time_to_next_ms < limit_ms) {\n        return time_to_next_ms;\n    }\n    return limit_ms;\n}\n\nvoid anjay_sched_run(anjay_t *anjay) {\n    avs_sched_t *sched = anjay_get_scheduler(anjay);\n    if (sched) {\n        avs_sched_run(sched);\n    }\n}\n\nanjay_etag_t *anjay_etag_new(uint8_t etag_size) {\n    anjay_etag_t *result = (anjay_etag_t *) avs_calloc(\n            offsetof(anjay_etag_t, value) + etag_size, sizeof(char));\n\n    if (result) {\n        result->size = (uint8_t) etag_size;\n    }\n\n    return result;\n}\n\nanjay_etag_t *anjay_etag_clone(const anjay_etag_t *old_etag) {\n    if (old_etag == NULL) {\n        return NULL;\n    }\n\n    anjay_etag_t *result = anjay_etag_new(old_etag->size);\n\n    if (result) {\n        memcpy(result->value, old_etag->value, result->size);\n    }\n\n    return result;\n}\n\n#ifdef ANJAY_WITH_DOWNLOADER\navs_error_t _anjay_download_unlocked(anjay_unlocked_t *anjay,\n                                     const anjay_download_config_t *config,\n                                     anjay_download_handle_t *out_handle) {\n    avs_coap_ctx_t *forced_coap_ctx = NULL;\n    avs_net_socket_t *forced_coap_socket = NULL;\n    if (config->prefer_same_socket_downloads) {\n        _anjay_find_matching_coap_context_and_socket(\n                anjay, config->url, &forced_coap_ctx, &forced_coap_socket);\n    }\n    return _anjay_downloader_download(&anjay->downloader, out_handle, config,\n                                      forced_coap_ctx, forced_coap_socket);\n}\n#endif // ANJAY_WITH_DOWNLOADER\n\navs_error_t anjay_download(anjay_t *anjay_locked,\n                           const anjay_download_config_t *config,\n                           anjay_download_handle_t *out_handle) {\n#ifdef ANJAY_WITH_DOWNLOADER\n    avs_error_t err = avs_errno(AVS_EINVAL);\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    err = _anjay_download_unlocked(anjay, config, out_handle);\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return err;\n#else  // ANJAY_WITH_DOWNLOADER\n    (void) anjay_locked;\n    (void) config;\n    (void) out_handle;\n    anjay_log(ERROR, _(\"Download support disabled\"));\n    return avs_errno(AVS_ENOTSUP);\n#endif // ANJAY_WITH_DOWNLOADER\n}\n\navs_error_t\nanjay_download_set_next_block_offset(anjay_t *anjay_locked,\n                                     anjay_download_handle_t dl_handle,\n                                     size_t next_block_offset) {\n#ifdef ANJAY_WITH_DOWNLOADER\n    avs_error_t err = avs_errno(AVS_EINVAL);\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    err = _anjay_downloader_set_next_block_offset(&anjay->downloader, dl_handle,\n                                                  next_block_offset);\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return err;\n#else  // ANJAY_WITH_DOWNLOADER\n    (void) anjay_locked;\n    (void) dl_handle;\n    (void) next_block_offset;\n    anjay_log(ERROR, _(\"Download support disabled\"));\n    return avs_errno(AVS_ENOTSUP);\n#endif // ANJAY_WITH_DOWNLOADER\n}\n\n#ifdef ANJAY_WITH_DOWNLOADER\nvoid _anjay_download_abort_unlocked(anjay_unlocked_t *anjay,\n                                    anjay_download_handle_t handle) {\n    _anjay_downloader_abort(&anjay->downloader, handle);\n}\n\nvoid _anjay_download_suspend_unlocked(anjay_unlocked_t *anjay,\n                                      anjay_download_handle_t handle) {\n    _anjay_downloader_suspend(&anjay->downloader, handle);\n}\n\nint _anjay_download_reconnect_unlocked(anjay_unlocked_t *anjay,\n                                       anjay_download_handle_t handle) {\n    return _anjay_downloader_sched_reconnect_by_handle(&anjay->downloader,\n                                                       handle);\n}\n#endif // ANJAY_WITH_DOWNLOADER\n\nvoid anjay_download_abort(anjay_t *anjay_locked,\n                          anjay_download_handle_t handle) {\n#ifdef ANJAY_WITH_DOWNLOADER\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    _anjay_downloader_abort(&anjay->downloader, handle);\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n#else  // ANJAY_WITH_DOWNLOADER\n    (void) anjay_locked;\n    (void) handle;\n    anjay_log(ERROR, _(\"Download support disabled\"));\n#endif // ANJAY_WITH_DOWNLOADER\n}\n\nvoid anjay_download_suspend(anjay_t *anjay_locked,\n                            anjay_download_handle_t handle) {\n#ifdef ANJAY_WITH_DOWNLOADER\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    _anjay_downloader_suspend(&anjay->downloader, handle);\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n#else  // ANJAY_WITH_DOWNLOADER\n    (void) anjay_locked;\n    (void) handle;\n    anjay_log(ERROR, _(\"Download support disabled\"));\n#endif // ANJAY_WITH_DOWNLOADER\n}\n\nint anjay_download_reconnect(anjay_t *anjay_locked,\n                             anjay_download_handle_t handle) {\n    int result = -1;\n#ifdef ANJAY_WITH_DOWNLOADER\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    result = _anjay_downloader_sched_reconnect_by_handle(&anjay->downloader,\n                                                         handle);\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n#else  // ANJAY_WITH_DOWNLOADER\n    (void) anjay_locked;\n    (void) handle;\n    anjay_log(ERROR, _(\"Download support disabled\"));\n#endif // ANJAY_WITH_DOWNLOADER\n    return result;\n}\n\n#ifdef ANJAY_WITH_LWM2M11\nint anjay_set_queue_mode_preference(anjay_t *anjay_locked,\n                                    anjay_queue_mode_preference_t preference) {\n    int result = -1;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    switch (preference) {\n    case ANJAY_FORCE_QUEUE_MODE:\n    case ANJAY_PREFER_QUEUE_MODE:\n    case ANJAY_PREFER_ONLINE_MODE:\n    case ANJAY_FORCE_ONLINE_MODE:\n        anjay->queue_mode_preference = preference;\n        result = 0;\n    }\n    if (result) {\n        anjay_log(WARNING, _(\"Invalid anjay_queue_mode_preference_t value\"));\n    }\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return result;\n}\n#endif // ANJAY_WITH_LWM2M11\n\n#if defined(ANJAY_WITH_ATTR_STORAGE)\nstatic avs_error_t persistence_dm_oi_attributes(avs_persistence_context_t *ctx,\n                                                anjay_dm_oi_attributes_t *attrs,\n                                                int32_t bitmask) {\n    avs_error_t err;\n    if (avs_is_err((err = avs_persistence_u32(ctx,\n                                              (uint32_t *) &attrs->min_period)))\n            || avs_is_err((err = avs_persistence_u32(\n                                   ctx, (uint32_t *) &attrs->max_period)))) {\n        return err;\n    }\n    if (bitmask & ANJAY_PERSIST_EVAL_PERIODS_ATTR) {\n        (void) (avs_is_err((err = avs_persistence_u32(\n                                    ctx, (uint32_t *) &attrs->min_eval_period)))\n                || avs_is_err((err = avs_persistence_u32(\n                                       ctx,\n                                       (uint32_t *) &attrs->max_eval_period))));\n    } else if (avs_persistence_direction(ctx) == AVS_PERSISTENCE_RESTORE) {\n        attrs->min_eval_period = ANJAY_ATTRIB_INTEGER_NONE;\n        attrs->max_eval_period = ANJAY_ATTRIB_INTEGER_NONE;\n    }\n#    ifdef ANJAY_WITH_LWM2M12\n    if (bitmask & ANJAY_PERSIST_HQMAX_ATTR) {\n        err = avs_persistence_i32(ctx, &attrs->hqmax);\n    } else if (avs_persistence_direction(ctx) == AVS_PERSISTENCE_RESTORE) {\n        attrs->hqmax = ANJAY_ATTRIB_INTEGER_NONE;\n    }\n#    else  // ANJAY_WITH_LWM2M12\n    if (bitmask & ANJAY_PERSIST_HQMAX_ATTR) {\n        err = avs_persistence_i32(ctx, &(int32_t) { -1 });\n    }\n#    endif // ANJAY_WITH_LWM2M12\n    return err;\n}\n\nstatic avs_error_t persistence_dm_r_attributes(avs_persistence_context_t *ctx,\n                                               anjay_dm_r_attributes_t *attrs,\n                                               int32_t bitmask) {\n    avs_error_t err;\n    if (avs_is_err((err = persistence_dm_oi_attributes(ctx, &attrs->common,\n                                                       bitmask)))\n            || avs_is_err((\n                       err = avs_persistence_double(ctx, &attrs->greater_than)))\n            || avs_is_err(\n                       (err = avs_persistence_double(ctx, &attrs->less_than)))\n            || avs_is_err((err = avs_persistence_double(ctx, &attrs->step)))) {\n        return err;\n    }\n#    ifdef ANJAY_WITH_LWM2M12\n    if (bitmask & ANJAY_PERSIST_EDGE_ATTR) {\n        int8_t edge = (int8_t) attrs->edge;\n        err = avs_persistence_bytes(ctx, (uint8_t *) &edge, 1);\n        attrs->edge = (anjay_dm_edge_attr_t) edge;\n    } else if (avs_persistence_direction(ctx) == AVS_PERSISTENCE_RESTORE) {\n        attrs->edge = ANJAY_DM_EDGE_ATTR_NONE;\n    }\n#    else  // ANJAY_WITH_LWM2M12\n    if (bitmask & ANJAY_PERSIST_EDGE_ATTR) {\n        err = avs_persistence_bytes(ctx, &(int8_t) { -1 }, 1);\n    }\n#    endif // ANJAY_WITH_LWM2M12\n    return err;\n}\n\nstatic avs_error_t persistence_con_attr(avs_persistence_context_t *ctx,\n                                        anjay_dm_oi_attributes_t *attrs,\n                                        int32_t bitmask) {\n    avs_error_t err = AVS_OK;\n    int8_t con = ANJAY_DM_CON_ATTR_NONE;\n    if (bitmask & ANJAY_PERSIST_CON_ATTR) {\n        (void) attrs;\n#    ifdef ANJAY_WITH_CON_ATTR\n        con = (int8_t) attrs->con;\n#    endif // ANJAY_WITH_CON_ATTR\n        err = avs_persistence_bytes(ctx, (uint8_t *) &con, 1);\n    }\n#    ifdef ANJAY_WITH_CON_ATTR\n    if (avs_is_ok(err)) {\n        switch (con) {\n        case ANJAY_DM_CON_ATTR_NONE:\n        case ANJAY_DM_CON_ATTR_NON:\n        case ANJAY_DM_CON_ATTR_CON:\n            attrs->con = (anjay_dm_con_attr_t) con;\n            break;\n        default:\n            err = avs_errno(AVS_EBADMSG);\n        }\n    }\n#    endif // ANJAY_WITH_CON_ATTR\n    return err;\n}\n\navs_error_t _anjay_persistence_dm_oi_attributes(avs_persistence_context_t *ctx,\n                                                anjay_dm_oi_attributes_t *attrs,\n                                                int32_t bitmask) {\n    avs_error_t err;\n    (void) (avs_is_err(\n                    (err = persistence_dm_oi_attributes(ctx, attrs, bitmask)))\n            || avs_is_err((err = persistence_con_attr(ctx, attrs, bitmask))));\n    return err;\n}\n\navs_error_t _anjay_persistence_dm_r_attributes(avs_persistence_context_t *ctx,\n                                               anjay_dm_r_attributes_t *attrs,\n                                               int32_t bitmask) {\n    avs_error_t err;\n    (void) (avs_is_err((err = persistence_dm_r_attributes(ctx, attrs, bitmask)))\n            || avs_is_err((err = persistence_con_attr(ctx, &attrs->common,\n                                                      bitmask))));\n    return err;\n}\n#endif /* (defined(ANJAY_WITH_CORE_PERSISTENCE) &&       \\\n          defined(ANJAY_WITH_OBSERVATION_ATTRIBUTES)) || \\\n          defined(ANJAY_WITH_ATTR_STORAGE) */\n\navs_error_t anjay_update_dtls_handshake_timeouts(\n        anjay_t *anjay_locked,\n        avs_net_dtls_handshake_timeouts_t dtls_handshake_timeouts) {\n    avs_error_t err = avs_errno(AVS_EINVAL);\n\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n\n    anjay->udp_dtls_hs_tx_params = dtls_handshake_timeouts;\n\n    AVS_LIST(const anjay_socket_entry_t) socket_entries =\n            _anjay_collect_socket_entries(anjay, /* include_offline = */ true);\n\n    avs_net_socket_opt_value_t value;\n    value.dtls_handshake_timeouts = dtls_handshake_timeouts;\n\n    AVS_LIST_CLEAR(&socket_entries) {\n        if (socket_entries->transport == ANJAY_SOCKET_TRANSPORT_UDP) {\n            avs_net_socket_set_opt(socket_entries->socket,\n                                   AVS_NET_SOCKET_OPT_DTLS_HANDSHAKE_TIMEOUTS,\n                                   value);\n        }\n    }\n\n    err = AVS_OK;\n\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n\n    return err;\n}\n\n#ifdef ANJAY_TEST\n#    include \"tests/core/anjay.c\"\n#endif // ANJAY_TEST\n"
  },
  {
    "path": "src/core/anjay_core.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ANJAY_CORE_H\n#define ANJAY_CORE_H\n\n#include <avsystem/commons/avs_errno.h>\n#include <avsystem/commons/avs_list.h>\n#include <avsystem/commons/avs_net.h>\n#include <avsystem/commons/avs_prng.h>\n#include <avsystem/commons/avs_shared_buffer.h>\n#include <avsystem/commons/avs_stream.h>\n\n#include <avsystem/coap/udp.h>\n\n#include \"anjay_dm_core.h\"\n#include \"observe/anjay_observe_core.h\"\n\n#include \"anjay_bootstrap_core.h\"\n#include \"anjay_downloader.h\"\n#include \"anjay_servers_private.h\"\n#include \"anjay_stats.h\"\n#include \"anjay_utils_private.h\"\n\n#ifdef ANJAY_WITH_ATTR_STORAGE\n#    include \"attr_storage/anjay_attr_storage.h\"\n#endif // ANJAY_WITH_ATTR_STORAGE\n#ifdef ANJAY_WITH_SEND\n#    include \"anjay_lwm2m_send.h\"\n#endif // ANJAY_WITH_SEND\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\ntypedef struct {\n    anjay_notify_queue_t queue;\n    avs_sched_handle_t handle;\n} anjay_scheduled_notify_t;\n\ntypedef struct {\n    unsigned depth;\n    AVS_LIST(const anjay_dm_installed_object_t *) objs_in_transaction;\n} anjay_transaction_state_t;\n\ntypedef struct {\n    bool allocated_by_user;\n    avs_crypto_prng_ctx_t *ctx;\n} anjay_prng_ctx_t;\n\n#ifdef ANJAY_WITH_LWM2M11\nstatic inline bool\n_anjay_trust_store_valid(const anjay_trust_store_t *trust_store) {\n    return trust_store->use_system_wide || trust_store->certs\n           || trust_store->crls;\n}\n\nvoid _anjay_trust_store_cleanup(anjay_trust_store_t *trust_store);\n#endif // ANJAY_WITH_LWM2M11\n\nstruct\n#ifdef ANJAY_WITH_THREAD_SAFETY\n        anjay_unlocked_struct\n#else  // ANJAY_WITH_THREAD_SAFETY\n        anjay_struct\n#endif // ANJAY_WITH_THREAD_SAFETY\n{\n    anjay_transport_set_t online_transports;\n\n#ifdef ANJAY_WITH_LWM2M11\n    anjay_lwm2m_version_config_t lwm2m_version_config;\n    anjay_queue_mode_preference_t queue_mode_preference;\n    anjay_trust_store_t initial_trust_store;\n    bool rebuild_client_cert_chain;\n#endif // ANJAY_WITH_LWM2M11\n    avs_net_ssl_version_t dtls_version;\n    avs_net_socket_configuration_t socket_config;\n    avs_sched_t *sched;\n#ifdef ANJAY_WITH_THREAD_SAFETY\n    avs_sched_t *coap_sched;\n    avs_sched_handle_t coap_sched_job_handle;\n#endif // ANJAY_WITH_THREAD_SAFETY\n    anjay_dm_t dm;\n    anjay_security_config_cache_t security_config_from_dm_cache;\n    uint16_t udp_listen_port;\n    uint64_t session_token_counter;\n\n    /**\n     * List of known LwM2M servers we may want to be connected to. This is\n     * semantically a map, keyed (and ordered) by SSID.\n     */\n    AVS_LIST(anjay_server_info_t) servers;\n\n    /**\n     * Cache of anjay_socket_entry_t objects, returned by\n     * anjay_get_socket_entries(). These entries are never used for anything\n     * inside the library, it's just to allow returning a list from a function\n     * without requiring the user to clean it up.\n     */\n    AVS_LIST(const anjay_socket_entry_t) cached_public_sockets;\n\n    avs_sched_handle_t reload_servers_sched_job_handle;\n#ifdef ANJAY_WITH_OBSERVE\n    anjay_observe_state_t observe;\n#endif\n#ifdef ANJAY_WITH_BOOTSTRAP\n    anjay_bootstrap_t bootstrap;\n#endif\n#ifdef WITH_AVS_COAP_UDP\n    avs_coap_udp_response_cache_t *udp_response_cache;\n    avs_coap_udp_tx_params_t udp_tx_params;\n    avs_time_duration_t udp_exchange_timeout;\n#endif\n    avs_net_dtls_handshake_timeouts_t udp_dtls_hs_tx_params;\n    avs_net_socket_tls_ciphersuites_t default_tls_ciphersuites;\n\n#ifdef WITH_AVS_COAP_TCP\n#    if defined(ANJAY_WITH_LWM2M11) || defined(ANJAY_WITH_COAP_DOWNLOAD)\n    size_t coap_tcp_max_options_size;\n    avs_time_duration_t coap_tcp_request_timeout;\n    avs_time_duration_t tcp_exchange_timeout;\n#    endif // defined(ANJAY_WITH_LWM2M11) || defined(ANJAY_WITH_COAP_DOWNLOAD)\n#endif     // WITH_AVS_COAP_TCP\n\n    anjay_scheduled_notify_t scheduled_notify;\n\n    char *endpoint_name;\n    anjay_transaction_state_t transaction_state;\n\n    anjay_confirmable_notification_status_cb_t\n            *confirmable_notification_status_cb;\n\n#ifdef ANJAY_WITH_CONN_STATUS_API\n    anjay_server_connection_status_cb_t *server_connection_status_cb;\n    void *server_connection_status_cb_arg;\n#endif // ANJAY_WITH_CONN_STATUS_API\n#ifdef ANJAY_WITH_SSL_ERROR_API\n    anjay_ssl_error_cb_t *ssl_error_cb;\n    void *ssl_error_cb_arg;\n#endif // ANJAY_WITH_SSL_ERROR_API\n\n#ifdef ANJAY_WITH_SEND\n    anjay_sender_t sender;\n#endif // ANJAY_WITH_SEND\n\n    avs_shared_buffer_t *in_shared_buffer;\n    avs_shared_buffer_t *out_shared_buffer;\n\n#ifdef ANJAY_WITH_DOWNLOADER\n    anjay_downloader_t downloader;\n#endif // ANJAY_WITH_DOWNLOADER\n    bool prefer_hierarchical_formats;\n    bool update_immediately_on_dm_change;\n    bool enable_self_notify;\n    bool connection_error_is_registration_failure;\n#ifdef ANJAY_WITH_NET_STATS\n    closed_connections_stats_t closed_connections_stats;\n#endif // ANJAY_WITH_NET_STATS\n    bool use_connection_id;\n    avs_ssl_additional_configuration_clb_t *additional_tls_config_clb;\n\n#ifdef ANJAY_WITH_ATTR_STORAGE\n    anjay_attr_storage_t attr_storage;\n#endif // ANJAY_WITH_ATTR_STORAGE\n\n    anjay_prng_ctx_t prng_ctx;\n#if !defined(ANJAY_WITH_THREAD_SAFETY) && defined(ANJAY_ATOMIC_FIELDS_DEFINED)\n    anjay_atomic_fields_t atomic_fields;\n#endif // !defined(ANJAY_WITH_THREAD_SAFETY) &&\n       // defined(ANJAY_ATOMIC_FIELDS_DEFINED)\n#ifdef ANJAY_WITH_COAP_DOWNLOAD\n    size_t coap_downloader_retry_count;\n    avs_time_duration_t coap_downloader_retry_delay;\n#endif // ANJAY_WITH_COAP_DOWNLOAD\n};\n\n#define ANJAY_DM_DEFAULT_PMIN_VALUE 0\n\nuint8_t _anjay_make_error_response_code(int handler_result);\n\navs_time_duration_t\n_anjay_max_transmit_wait_for_transport(anjay_unlocked_t *anjay,\n                                       anjay_socket_transport_t transport);\n\navs_time_duration_t\n_anjay_exchange_lifetime_for_transport(anjay_unlocked_t *anjay,\n                                       anjay_socket_transport_t transport);\n\nint _anjay_serve_unlocked(anjay_unlocked_t *anjay,\n                          avs_net_socket_t *ready_socket);\n\nstatic inline avs_sched_t *_anjay_get_coap_sched(anjay_unlocked_t *anjay) {\n#ifdef ANJAY_WITH_THREAD_SAFETY\n    return anjay->coap_sched;\n#else  // ANJAY_WITH_THREAD_SAFETY\n    return anjay->sched;\n#endif // ANJAY_WITH_THREAD_SAFETY\n}\n\n#if defined(ANJAY_WITH_ATTR_STORAGE)\n\n// clang-format off\n#    define ANJAY_PERSIST_EVAL_PERIODS_ATTR (1 << 0)\n#    define ANJAY_PERSIST_CON_ATTR          (1 << 1)\n#    define ANJAY_PERSIST_HQMAX_ATTR        (1 << 2)\n#    define ANJAY_PERSIST_EDGE_ATTR         (1 << 3)\n// clang-format on\n\n#    define ANJAY_PERSIST_ALL_ATTR                                \\\n        (ANJAY_PERSIST_EVAL_PERIODS_ATTR | ANJAY_PERSIST_CON_ATTR \\\n         | ANJAY_PERSIST_HQMAX_ATTR | ANJAY_PERSIST_EDGE_ATTR)\n\navs_error_t _anjay_persistence_dm_r_attributes(avs_persistence_context_t *ctx,\n                                               anjay_dm_r_attributes_t *attrs,\n                                               int32_t bitmask);\navs_error_t _anjay_persistence_dm_oi_attributes(avs_persistence_context_t *ctx,\n                                                anjay_dm_oi_attributes_t *attrs,\n                                                int32_t bitmask);\n\n#endif /* (defined(ANJAY_WITH_CORE_PERSISTENCE) &&       \\\n          defined(ANJAY_WITH_OBSERVATION_ATTRIBUTES)) || \\\n          defined(ANJAY_WITH_ATTR_STORAGE) */\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif /* ANJAY_CORE_H */\n"
  },
  {
    "path": "src/core/anjay_dm_core.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#include <assert.h>\n#include <inttypes.h>\n#include <math.h>\n#include <stdio.h>\n#include <string.h>\n\n#include <anjay/core.h>\n#include <avsystem/commons/avs_stream.h>\n#include <avsystem/commons/avs_stream_membuf.h>\n#include <avsystem/commons/avs_stream_v_table.h>\n#include <avsystem/commons/avs_utils.h>\n\n#include <anjay_modules/anjay_notify.h>\n#ifdef ANJAY_WITH_LWM2M_GATEWAY\n#    include <anjay_modules/anjay_lwm2m_gateway.h>\n#endif // ANJAY_WITH_LWM2M_GATEWAY\n\n#include <avsystem/coap/code.h>\n\n#include \"coap/anjay_content_format.h\"\n\n#include \"anjay_access_utils_private.h\"\n#include \"anjay_core.h\"\n#include \"anjay_dm_core.h\"\n#include \"anjay_io_core.h\"\n#include \"anjay_utils_private.h\"\n#include \"dm/anjay_discover.h\"\n#include \"dm/anjay_dm_create.h\"\n#include \"dm/anjay_dm_execute.h\"\n#include \"dm/anjay_dm_read.h\"\n#include \"dm/anjay_dm_write.h\"\n#include \"dm/anjay_dm_write_attrs.h\"\n#include \"dm/anjay_query.h\"\n#include \"io/anjay_vtable.h\"\n#include \"observe/anjay_observe_core.h\"\n\nVISIBILITY_SOURCE_BEGIN\n\n#ifdef ANJAY_WITH_THREAD_SAFETY\n\nanjay_oid_t\n_anjay_dm_installed_object_oid(const anjay_dm_installed_object_t *obj) {\n    assert(obj);\n    switch (obj->type) {\n    case ANJAY_DM_OBJECT_USER_PROVIDED:\n        assert(obj->impl.user_provided);\n        assert(*obj->impl.user_provided);\n        return (*obj->impl.user_provided)->oid;\n\n    case ANJAY_DM_OBJECT_UNLOCKED:\n        assert(obj->impl.unlocked);\n        assert(*obj->impl.unlocked);\n        return (*obj->impl.unlocked)->oid;\n    }\n    AVS_UNREACHABLE(\"Invalid installed object type\");\n    return ANJAY_ID_INVALID;\n}\n\nconst char *\n_anjay_dm_installed_object_version(const anjay_dm_installed_object_t *obj) {\n    assert(obj);\n    switch (obj->type) {\n    case ANJAY_DM_OBJECT_USER_PROVIDED:\n        assert(obj->impl.user_provided);\n        assert(*obj->impl.user_provided);\n        return (*obj->impl.user_provided)->version;\n\n    case ANJAY_DM_OBJECT_UNLOCKED:\n        assert(obj->impl.unlocked);\n        assert(*obj->impl.unlocked);\n        return (*obj->impl.unlocked)->version;\n    }\n    AVS_UNREACHABLE(\"Invalid installed object type\");\n    return NULL;\n}\n\n#endif // ANJAY_WITH_THREAD_SAFETY\n\nstatic int validate_version(const anjay_dm_installed_object_t *obj) {\n    const char *version = _anjay_dm_installed_object_version(obj);\n    if (!version) {\n        // missing version is equivalent to 1.0\n        return 0;\n    }\n\n    unsigned major, minor;\n    char dummy;\n    if (sscanf(version, \"%u.%u%c\", &major, &minor, &dummy) != 2) {\n        dm_log(ERROR,\n               _(\"invalid Object \") \"/%u\" _(\n                       \" version format (expected X.Y, where X and Y are \"\n                       \"unsigned integers): \") \"%s\",\n               (unsigned) _anjay_dm_installed_object_oid(obj), version);\n        return -1;\n    }\n\n    return 0;\n}\n\nint _anjay_dm_register_object(\n        anjay_dm_t *dm, AVS_LIST(anjay_dm_installed_object_t) *elem_ptr_move) {\n    assert(elem_ptr_move);\n    assert(*elem_ptr_move);\n    assert(_anjay_dm_installed_object_oid(*elem_ptr_move) != ANJAY_ID_INVALID);\n    assert(!AVS_LIST_NEXT(*elem_ptr_move));\n\n    if (validate_version(*elem_ptr_move)) {\n        return -1;\n    }\n\n    AVS_LIST(anjay_dm_installed_object_t) *obj_iter;\n\n    AVS_LIST_FOREACH_PTR(obj_iter, &dm->objects) {\n        assert(*obj_iter);\n\n        if (_anjay_dm_installed_object_oid(*obj_iter)\n                >= _anjay_dm_installed_object_oid(*elem_ptr_move)) {\n            break;\n        }\n    }\n\n    if (*obj_iter\n            && _anjay_dm_installed_object_oid(*obj_iter)\n                           == _anjay_dm_installed_object_oid(*elem_ptr_move)) {\n        dm_log(ERROR,\n               _(\"data model object /\") \"%\" PRIu16 _(\" already registered\"),\n               _anjay_dm_installed_object_oid(*elem_ptr_move));\n        return -1;\n    }\n\n    AVS_LIST_INSERT(obj_iter, *elem_ptr_move);\n\n    return 0;\n}\n\n#ifdef ANJAY_WITH_LWM2M12\nvoid _anjay_dm_check_implemented_handlers(\n        anjay_unlocked_t *anjay, const anjay_dm_installed_object_t *obj) {\n    if (anjay->lwm2m_version_config.maximum_version >= ANJAY_LWM2M_VERSION_1_2\n            && _anjay_dm_handler_implemented(obj,\n                                             ANJAY_DM_HANDLER_resource_reset)\n            && _anjay_dm_handler_implemented(\n                       obj, ANJAY_DM_HANDLER_list_resource_instances)\n            && !_anjay_dm_handler_implemented(\n                       obj, ANJAY_DM_HANDLER_resource_instance_remove)) {\n        dm_log(WARNING,\n               _(\"object \") \"/%u\" _(\" implements resource_reset but not \"\n                                    \"resource_instance_remove, and LwM2M 1.2 \"\n                                    \"support is enabled; DELETE on Resource \"\n                                    \"Instances may not work properly\"),\n               _anjay_dm_installed_object_oid(obj));\n    }\n}\n#endif // ANJAY_WITH_LWM2M12\n\nint _anjay_register_object_unlocked(\n        anjay_unlocked_t *anjay,\n        AVS_LIST(anjay_dm_installed_object_t) *elem_ptr_move) {\n    if (_anjay_dm_register_object(&anjay->dm, elem_ptr_move)) {\n        return -1;\n    }\n\n#ifdef ANJAY_WITH_LWM2M12\n    _anjay_dm_check_implemented_handlers(anjay, *elem_ptr_move);\n#endif // ANJAY_WITH_LWM2M12\n\n#ifdef ANJAY_WITH_LWM2M_GATEWAY\n    (*elem_ptr_move)->prefix = NULL;\n#endif // ANJAY_WITH_LWM2M_GATEWAY\n\n    dm_log(INFO, _(\"successfully registered object \") \"/%u\",\n           _anjay_dm_installed_object_oid(*elem_ptr_move));\n    if (_anjay_notify_instances_changed_unlocked(\n                anjay, _anjay_dm_installed_object_oid(*elem_ptr_move))) {\n        dm_log(WARNING, _(\"anjay_notify_instances_changed() failed on \") \"/%u\",\n               _anjay_dm_installed_object_oid(*elem_ptr_move));\n    }\n    if (_anjay_schedule_registration_update_unlocked(anjay, ANJAY_SSID_ANY)) {\n        dm_log(WARNING, _(\"anjay_schedule_registration_update() failed\"));\n    }\n    *elem_ptr_move = NULL;\n    return 0;\n}\n\nAVS_LIST(anjay_dm_installed_object_t) _anjay_prepare_user_provided_object(\n        const anjay_dm_object_def_t *const *def_ptr) {\n    if (!def_ptr || !*def_ptr) {\n        dm_log(ERROR, _(\"invalid object pointer\"));\n        return NULL;\n    }\n\n    if ((*def_ptr)->oid == ANJAY_ID_INVALID) {\n        anjay_log(ERROR,\n                  _(\"Object ID \") \"%u\" _(\n                          \" is forbidden by the LwM2M 1.1 specification\"),\n                  ANJAY_ID_INVALID);\n        return NULL;\n    }\n\n    AVS_LIST(anjay_dm_installed_object_t) new_elem =\n            AVS_LIST_NEW_ELEMENT(anjay_dm_installed_object_t);\n    if (!new_elem) {\n        _anjay_log_oom();\n        return NULL;\n    }\n\n#ifdef ANJAY_WITH_THREAD_SAFETY\n    new_elem->type = ANJAY_DM_OBJECT_USER_PROVIDED;\n    new_elem->impl.user_provided = def_ptr;\n#else  // ANJAY_WITH_THREAD_SAFETY\n    *new_elem = def_ptr;\n#endif // ANJAY_WITH_THREAD_SAFETY\n    return new_elem;\n}\n\nint anjay_register_object(anjay_t *anjay_locked,\n                          const anjay_dm_object_def_t *const *def_ptr) {\n    AVS_LIST(anjay_dm_installed_object_t) new_elem =\n            _anjay_prepare_user_provided_object(def_ptr);\n    if (!new_elem) {\n        return -1;\n    }\n\n    int result = -1;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    result = _anjay_register_object_unlocked(anjay, &new_elem);\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    AVS_LIST_CLEAR(&new_elem);\n    return result;\n}\n\nstatic void remove_oid_from_notify_queue(anjay_notify_queue_t *out_queue,\n                                         anjay_oid_t oid) {\n    AVS_LIST(anjay_notify_queue_object_entry_t) *it;\n    AVS_LIST_FOREACH_PTR(it, out_queue) {\n        if ((*it)->oid >= oid) {\n            break;\n        }\n    }\n    if (*it && (*it)->oid == oid) {\n        AVS_LIST(anjay_notify_queue_object_entry_t) entry = AVS_LIST_DETACH(it);\n        _anjay_notify_clear_queue(&entry);\n    }\n}\n\nvoid _anjay_unregister_object_handle_transaction_state(\n        anjay_unlocked_t *anjay, const anjay_dm_installed_object_t *def_ptr) {\n    AVS_LIST(const anjay_dm_installed_object_t *) *obj_in_transaction_iter;\n    AVS_LIST_FOREACH_PTR(obj_in_transaction_iter,\n                         &anjay->transaction_state.objs_in_transaction) {\n        if (**obj_in_transaction_iter >= def_ptr) {\n            if (**obj_in_transaction_iter == def_ptr) {\n                assert(anjay->transaction_state.depth);\n                if (_anjay_dm_call_transaction_rollback(\n                            anjay, **obj_in_transaction_iter)) {\n                    dm_log(ERROR,\n                           _(\"cannot rollback transaction on \") \"/%u\" _(\n                                   \", object may be left in undefined state\"),\n                           _anjay_dm_installed_object_oid(def_ptr));\n                }\n                AVS_LIST_DELETE(obj_in_transaction_iter);\n            }\n            break;\n        }\n    }\n}\n\nvoid _anjay_unregister_object_handle_notify_queue(\n        anjay_unlocked_t *anjay, const anjay_dm_installed_object_t *def_ptr) {\n    anjay_notify_queue_t notify = NULL;\n    anjay_oid_t oid = _anjay_dm_installed_object_oid(def_ptr);\n    anjay_uri_path_t path = MAKE_OBJECT_PATH(oid);\n#ifdef ANJAY_WITH_LWM2M_GATEWAY\n    if (def_ptr->prefix != NULL) {\n        strcpy(path.prefix, def_ptr->prefix);\n    }\n#endif // ANJAY_WITH_LWM2M_GATEWAY\n    if (_anjay_notify_queue_instance_set_unknown_change(&notify, &path)\n            || _anjay_notify_flush(anjay, ANJAY_SSID_BOOTSTRAP, &notify)) {\n        dm_log(WARNING,\n               _(\"could not perform notifications about removed object \") \"%\" PRIu16,\n               oid);\n    }\n\n    remove_oid_from_notify_queue(&anjay->scheduled_notify.queue, oid);\n#ifdef ANJAY_WITH_BOOTSTRAP\n    remove_oid_from_notify_queue(&anjay->bootstrap.notification_queue, oid);\n#endif // ANJAY_WITH_BOOTSTRAP\n}\n\nstatic int\nunregister_object_unlocked(anjay_unlocked_t *anjay,\n                           AVS_LIST(anjay_dm_installed_object_t) *def_ptr) {\n    assert(def_ptr && *def_ptr);\n\n    assert(AVS_LIST_FIND_PTR(&anjay->dm.objects, *def_ptr));\n    AVS_LIST(anjay_dm_installed_object_t) detached = AVS_LIST_DETACH(def_ptr);\n\n    _anjay_unregister_object_handle_transaction_state(anjay, detached);\n    _anjay_unregister_object_handle_notify_queue(anjay, detached);\n\n    dm_log(INFO, _(\"successfully unregistered object /\") \"%\" PRIu16,\n           _anjay_dm_installed_object_oid(detached));\n    AVS_LIST_DELETE(&detached);\n    if (_anjay_schedule_registration_update_unlocked(anjay, ANJAY_SSID_ANY)) {\n        dm_log(WARNING, _(\"anjay_schedule_registration_update() failed\"));\n    }\n    return 0;\n}\n\nAVS_LIST(anjay_dm_installed_object_t) *\n_anjay_find_and_verify_object_to_unregister(\n        anjay_dm_t *dm, const anjay_dm_object_def_t *const *def_ptr) {\n    if (!def_ptr || !*def_ptr) {\n        dm_log(ERROR, _(\"invalid object pointer\"));\n        return NULL;\n    }\n\n    AVS_LIST(anjay_dm_installed_object_t) *obj_iter;\n    AVS_LIST_FOREACH_PTR(obj_iter, &dm->objects) {\n        assert(*obj_iter);\n        if (_anjay_dm_installed_object_oid(*obj_iter) >= (*def_ptr)->oid) {\n            break;\n        }\n    }\n\n    if (!*obj_iter\n            || _anjay_dm_installed_object_oid(*obj_iter) != (*def_ptr)->oid) {\n        dm_log(ERROR,\n               _(\"object \") \"%\" PRIu16 _(\" is not currently registered\"),\n               (*def_ptr)->oid);\n        return NULL;\n    } else if (\n#ifdef ANJAY_WITH_THREAD_SAFETY\n            (*obj_iter)->type != ANJAY_DM_OBJECT_USER_PROVIDED\n            || (*obj_iter)->impl.user_provided != def_ptr\n#else  // ANJAY_WITH_THREAD_SAFETY\n            **obj_iter != def_ptr\n#endif // ANJAY_WITH_THREAD_SAFETY\n    ) {\n        dm_log(ERROR,\n               _(\"object \") \"%\" PRIu16 _(\n                       \" that is registered is not the same as the object \"\n                       \"passed for unregister\"),\n               (*def_ptr)->oid);\n        return NULL;\n    }\n\n    return obj_iter;\n}\n\nint anjay_unregister_object(anjay_t *anjay_locked,\n                            const anjay_dm_object_def_t *const *def_ptr) {\n    int result = -1;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    AVS_LIST(anjay_dm_installed_object_t) *obj =\n            _anjay_find_and_verify_object_to_unregister(&anjay->dm, def_ptr);\n    if (obj) {\n        result = unregister_object_unlocked(anjay, obj);\n    }\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return result;\n}\n\nvoid _anjay_dm_cleanup(anjay_dm_t *dm) {\n    AVS_LIST_CLEAR(&dm->modules) {\n        assert(dm->modules->deleter);\n        dm->modules->deleter(dm->modules->arg);\n    }\n\n    AVS_LIST_CLEAR(&dm->objects);\n}\n\nconst anjay_dm_installed_object_t *\n_anjay_dm_find_object_by_oid(const anjay_dm_t *dm, anjay_oid_t oid) {\n    AVS_LIST(anjay_dm_installed_object_t) obj;\n    AVS_LIST_FOREACH(obj, dm->objects) {\n        if (_anjay_dm_installed_object_oid(obj) == oid) {\n            return obj;\n        }\n    }\n\n    return NULL;\n}\n\nuint8_t _anjay_dm_make_success_response_code(anjay_request_action_t action) {\n    switch (action) {\n    case ANJAY_ACTION_READ:\n#ifdef ANJAY_WITH_LWM2M11\n    case ANJAY_ACTION_READ_COMPOSITE:\n#endif // ANJAY_WITH_LWM2M11\n    case ANJAY_ACTION_DISCOVER:\n        return AVS_COAP_CODE_CONTENT;\n    case ANJAY_ACTION_WRITE:\n    case ANJAY_ACTION_WRITE_UPDATE:\n    case ANJAY_ACTION_WRITE_ATTRIBUTES:\n#ifdef ANJAY_WITH_LWM2M11\n    case ANJAY_ACTION_WRITE_COMPOSITE:\n#endif // ANJAY_WITH_LWM2M11\n    case ANJAY_ACTION_EXECUTE:\n        return AVS_COAP_CODE_CHANGED;\n    case ANJAY_ACTION_CREATE:\n        return AVS_COAP_CODE_CREATED;\n    case ANJAY_ACTION_DELETE:\n        return AVS_COAP_CODE_DELETED;\n    default:\n        break;\n    }\n    return (uint8_t) (-ANJAY_ERR_INTERNAL);\n}\n\nstatic int prepare_input_context(avs_stream_t *stream,\n                                 const anjay_request_t *request,\n                                 anjay_unlocked_input_ctx_t **out_in_ctx) {\n    *out_in_ctx = NULL;\n\n    int result = _anjay_input_dynamic_construct(out_in_ctx, stream, request);\n    if (result) {\n        dm_log(ERROR, _(\"could not create input context\"));\n    }\n\n    return result;\n}\n\n#ifdef ANJAY_WITH_LWM2M11\nvoid _anjay_uri_path_update_common_prefix(const anjay_uri_path_t **prefix_ptr,\n                                          anjay_uri_path_t *prefix_buf,\n                                          const anjay_uri_path_t *path) {\n    assert(prefix_ptr);\n    if (!*prefix_ptr) {\n        *prefix_buf = *path;\n        *prefix_ptr = prefix_buf;\n    } else {\n        assert(*prefix_ptr == prefix_buf);\n        size_t index = 0;\n        anjay_uri_path_t new_prefix = MAKE_ROOT_PATH();\n#    ifdef ANJAY_WITH_LWM2M_GATEWAY\n        if (strcmp(prefix_buf->prefix, path->prefix)) {\n            *prefix_buf = new_prefix;\n            return;\n        }\n#    endif // ANJAY_WITH_LWM2M_GATEWAY\n        while (index < AVS_ARRAY_SIZE(prefix_buf->ids)\n               && prefix_buf->ids[index] != ANJAY_ID_INVALID\n               && prefix_buf->ids[index] == path->ids[index]) {\n            new_prefix.ids[index] = prefix_buf->ids[index];\n            index++;\n        }\n#    ifdef ANJAY_WITH_LWM2M_GATEWAY\n        memcpy(prefix_buf->ids, new_prefix.ids, sizeof(new_prefix.ids));\n#    else\n        *prefix_buf = new_prefix;\n#    endif // ANJAY_WITH_LWM2M_GATEWAY\n    }\n}\n#endif // ANJAY_WITH_LWM2M11\n\nconst char *_anjay_debug_make_path__(char *buffer,\n                                     size_t buffer_size,\n                                     const anjay_uri_path_t *uri) {\n    assert(uri);\n    int result = 0;\n    char *ptr = buffer;\n    char *buffer_end = buffer + buffer_size;\n    size_t length = _anjay_uri_path_length(uri);\n#ifdef ANJAY_WITH_LWM2M_GATEWAY\n    if (_anjay_uri_path_has_prefix(uri)) {\n        result = avs_simple_snprintf(buffer, buffer_size, \"/%s\", uri->prefix);\n        ptr += result;\n    }\n    if (result >= 0)\n#endif // ANJAY_WITH_LWM2M_GATEWAY\n    {\n        if (!length) {\n            result = avs_simple_snprintf(ptr, (size_t) (buffer_end - ptr), \"/\");\n        } else {\n            for (size_t i = 0; result >= 0 && i < length; ++i) {\n                result = avs_simple_snprintf(ptr, (size_t) (buffer_end - ptr),\n                                             \"/%u\", (unsigned) uri->ids[i]);\n                ptr += result;\n            }\n        }\n    }\n    if (result < 0) {\n        AVS_UNREACHABLE(\"should never happen\");\n        return \"<error>\";\n    }\n    return buffer;\n}\n\nint _anjay_dm_verify_instance_present(\n        anjay_unlocked_t *anjay,\n        const anjay_dm_installed_object_t *obj_ptr,\n        anjay_iid_t iid) {\n    return _anjay_dm_map_present_result(\n            _anjay_dm_instance_present(anjay, obj_ptr, iid));\n}\n\nint _anjay_dm_verify_resource_present(anjay_unlocked_t *anjay,\n                                      const anjay_dm_installed_object_t *obj,\n                                      anjay_iid_t iid,\n                                      anjay_rid_t rid,\n                                      anjay_dm_resource_kind_t *out_kind) {\n    anjay_dm_resource_presence_t presence;\n    int retval = _anjay_dm_resource_kind_and_presence(anjay, obj, iid, rid,\n                                                      out_kind, &presence);\n    if (retval) {\n        return retval;\n    }\n    if (presence == ANJAY_DM_RES_ABSENT) {\n        return ANJAY_ERR_NOT_FOUND;\n    }\n    return 0;\n}\n\ntypedef struct {\n    anjay_riid_t riid_to_find;\n    bool found;\n} resource_instance_present_args_t;\n\nstatic int\ndm_resource_instance_present_clb(anjay_unlocked_t *anjay,\n                                 const anjay_dm_installed_object_t *obj,\n                                 anjay_iid_t iid,\n                                 anjay_rid_t rid,\n                                 anjay_riid_t riid,\n                                 void *args_) {\n    (void) anjay;\n    (void) obj;\n    (void) iid;\n    (void) rid;\n    resource_instance_present_args_t *args =\n            (resource_instance_present_args_t *) args_;\n    if (riid == args->riid_to_find) {\n        args->found = true;\n        return ANJAY_FOREACH_BREAK;\n    }\n    return ANJAY_FOREACH_CONTINUE;\n}\n\nstatic int dm_resource_instance_present(anjay_unlocked_t *anjay,\n                                        const anjay_dm_installed_object_t *obj,\n                                        anjay_iid_t iid,\n                                        anjay_rid_t rid,\n                                        anjay_riid_t riid) {\n    resource_instance_present_args_t args = {\n        .riid_to_find = riid,\n        .found = false\n    };\n    int result = _anjay_dm_foreach_resource_instance(\n            anjay, obj, iid, rid, dm_resource_instance_present_clb, &args);\n    if (result < 0) {\n        return result;\n    }\n    return args.found ? 1 : 0;\n}\n\nint _anjay_dm_verify_resource_instance_present(\n        anjay_unlocked_t *anjay,\n        const anjay_dm_installed_object_t *obj,\n        anjay_iid_t iid,\n        anjay_rid_t rid,\n        anjay_riid_t riid) {\n    return _anjay_dm_map_present_result(\n            dm_resource_instance_present(anjay, obj, iid, rid, riid));\n}\n\nstatic int dm_discover(anjay_connection_ref_t connection,\n                       const anjay_dm_installed_object_t *obj,\n                       const anjay_request_t *request) {\n#ifdef ANJAY_WITH_DISCOVER\n    dm_log(LAZY_DEBUG, _(\"Discover \") \"%s\",\n           ANJAY_DEBUG_MAKE_PATH(&request->uri));\n    if (_anjay_uri_path_has(&request->uri, ANJAY_ID_RIID)) {\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n    avs_stream_t *response_stream = _anjay_coap_setup_response_stream(\n            request->ctx,\n            &(anjay_msg_details_t) {\n                .msg_code = _anjay_dm_make_success_response_code(\n                        ANJAY_ACTION_DISCOVER),\n                .format = AVS_COAP_FORMAT_LINK_FORMAT\n            });\n\n    if (!response_stream) {\n        dm_log(ERROR, _(\"could not setup message\"));\n        return -1;\n    }\n\n    uint8_t depth = 1;\n#    ifdef ANJAY_WITH_LWM2M12\n    if (request->depth >= 0\n            && _anjay_server_registration_info(connection.server)->lwm2m_version\n                           >= ANJAY_LWM2M_VERSION_1_2) {\n        // According to OMA-TS-LightweightM2M_Core-V1_2-20201110-A:\n        // > If the LwM2M Client does not support the \"Depth\" parameter, it MUST\n        // > ignore it and use the default value from Table: 6.3.2.-2 Depth\n        // > Modifier.\n        // That's why we treat the \"depth\" parameter as non-fatal for LwM2M 1.1\n        // and below, but ignore it. We could just support it, but attribute\n        // reporting semantics are different for 1.1 and 1.2, so let's better\n        // not mix things up.\n        depth = (uint8_t) request->depth;\n    } else\n#    endif // ANJAY_WITH_LWM2M12\n            if (_anjay_uri_path_leaf_is(&request->uri, ANJAY_ID_OID)) {\n        depth = 2;\n    }\n\n    int result = _anjay_discover(\n            _anjay_from_server(connection.server), response_stream, obj,\n            request->uri.ids[ANJAY_ID_IID], request->uri.ids[ANJAY_ID_RID],\n            depth, _anjay_server_ssid(connection.server),\n            _anjay_server_registration_info(connection.server)->lwm2m_version);\n    if (result) {\n        dm_log(WARNING, _(\"Discover \") \"%s\" _(\" failed!\"),\n               ANJAY_DEBUG_MAKE_PATH(&request->uri));\n    }\n    return result;\n#else  // ANJAY_WITH_DISCOVER\n    (void) connection;\n    (void) obj;\n    (void) request;\n    dm_log(ERROR, _(\"Not supported: Discover \") \"%s\",\n           ANJAY_DEBUG_MAKE_PATH(&request->uri));\n    return ANJAY_ERR_NOT_IMPLEMENTED;\n#endif // ANJAY_WITH_DISCOVER\n}\n\nstatic int dm_execute(anjay_unlocked_t *anjay,\n                      const anjay_dm_installed_object_t *obj,\n                      const anjay_request_t *request,\n                      anjay_ssid_t ssid) {\n    // Treat not specified format as implicit Plain Text\n    if (request->content_format != AVS_COAP_FORMAT_PLAINTEXT\n            && request->content_format != AVS_COAP_FORMAT_NONE) {\n        return AVS_COAP_CODE_UNSUPPORTED_CONTENT_FORMAT;\n    }\n    dm_log(LAZY_DEBUG, _(\"Execute \") \"%s\",\n           ANJAY_DEBUG_MAKE_PATH(&request->uri));\n    if (!_anjay_uri_path_leaf_is(&request->uri, ANJAY_ID_RID)) {\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n\n    int retval =\n            _anjay_dm_verify_instance_present(anjay, obj,\n                                              request->uri.ids[ANJAY_ID_IID]);\n    if (!retval) {\n        if (!_anjay_instance_action_allowed(\n                    anjay, &REQUEST_TO_ACTION_INFO(request, ssid))) {\n            return ANJAY_ERR_UNAUTHORIZED;\n        }\n        anjay_dm_resource_kind_t kind;\n        if (!(retval = _anjay_dm_verify_resource_present(\n                      anjay, obj, request->uri.ids[ANJAY_ID_IID],\n                      request->uri.ids[ANJAY_ID_RID], &kind))\n                && !_anjay_dm_res_kind_executable(kind)) {\n            dm_log(LAZY_DEBUG, \"%s\" _(\" is not executable\"),\n                   ANJAY_DEBUG_MAKE_PATH(&request->uri));\n            return ANJAY_ERR_METHOD_NOT_ALLOWED;\n        }\n    }\n    if (!retval) {\n        anjay_unlocked_execute_ctx_t *execute_ctx =\n                _anjay_execute_ctx_create(request->payload_stream);\n        retval = _anjay_dm_call_resource_execute(anjay, obj,\n                                                 request->uri.ids[ANJAY_ID_IID],\n                                                 request->uri.ids[ANJAY_ID_RID],\n                                                 execute_ctx);\n#ifdef ANJAY_WITH_CONN_STATUS_API\n        /* RID == 4 -> Disable resource */\n        if (!retval && request->uri.ids[ANJAY_ID_OID] == ANJAY_DM_OID_SERVER\n                && request->uri.ids[ANJAY_ID_RID]\n                               == ANJAY_DM_RID_SERVER_DISABLE) {\n            anjay_ssid_t target_ssid;\n            if (!_anjay_ssid_from_server_iid(\n                        anjay, request->uri.ids[ANJAY_ID_IID], &target_ssid)) {\n                _anjay_set_server_suspending_flag(anjay, target_ssid, true);\n            }\n        }\n#endif // ANJAY_WITH_CONN_STATUS_API\n        _anjay_execute_ctx_destroy(&execute_ctx);\n    }\n    return retval;\n}\n\nstatic int dm_delete_object_instance(anjay_unlocked_t *anjay,\n                                     const anjay_dm_installed_object_t *obj,\n                                     const anjay_request_t *request,\n                                     anjay_ssid_t ssid) {\n    assert(_anjay_uri_path_leaf_is(&request->uri, ANJAY_ID_IID));\n    int retval =\n            _anjay_dm_verify_instance_present(anjay, obj,\n                                              request->uri.ids[ANJAY_ID_IID]);\n    if (retval) {\n        return retval;\n    }\n    if (!_anjay_instance_action_allowed(anjay, &REQUEST_TO_ACTION_INFO(request,\n                                                                       ssid))) {\n        return ANJAY_ERR_UNAUTHORIZED;\n    }\n\n    anjay_notify_queue_t notify_queue = NULL;\n    (void) ((retval = _anjay_dm_call_instance_remove(\n                     anjay, obj, request->uri.ids[ANJAY_ID_IID]))\n            || (retval = _anjay_notify_queue_instance_removed(&notify_queue,\n                                                              &request->uri))\n            || (retval = _anjay_notify_flush(anjay, ssid, &notify_queue)));\n    return retval;\n}\n\n#ifdef ANJAY_WITH_LWM2M12\nstatic int dm_delete_resource_instance(anjay_unlocked_t *anjay,\n                                       const anjay_dm_installed_object_t *obj,\n                                       const anjay_request_t *request,\n                                       anjay_ssid_t ssid) {\n    assert(_anjay_uri_path_leaf_is(&request->uri, ANJAY_ID_RIID));\n    anjay_dm_path_info_t path_info;\n    int retval = _anjay_dm_path_info(anjay, obj, &request->uri, &path_info);\n    if (retval) {\n        return retval;\n    }\n    if (!path_info.is_present) {\n        return ANJAY_ERR_NOT_FOUND;\n    }\n    if (!_anjay_instance_action_allowed(anjay, &REQUEST_TO_ACTION_INFO(request,\n                                                                       ssid))) {\n        return ANJAY_ERR_UNAUTHORIZED;\n    }\n    if (!_anjay_dm_res_kind_writable(path_info.kind)) {\n        dm_log(DEBUG, \"/%s\" _(\" is not writable\"),\n               ANJAY_DEBUG_MAKE_PATH(&request->uri));\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n    anjay_notify_queue_t notify_queue = NULL;\n    (void) ((retval = _anjay_dm_call_resource_instance_remove(\n                     anjay, obj, request->uri.ids[ANJAY_ID_IID],\n                     request->uri.ids[ANJAY_ID_RID],\n                     request->uri.ids[ANJAY_ID_RIID]))\n            || (retval = _anjay_notify_queue_resource_change(&notify_queue,\n                                                             &request->uri))\n            || (retval = _anjay_notify_flush(anjay, ssid, &notify_queue)));\n    return retval;\n}\n#endif // ANJAY_WITH_LWM2M12\n\nstatic int dm_delete(anjay_unlocked_t *anjay,\n                     const anjay_dm_installed_object_t *obj,\n                     const anjay_request_t *request,\n                     anjay_ssid_t ssid) {\n    dm_log(LAZY_DEBUG, _(\"Delete \") \"%s\", ANJAY_DEBUG_MAKE_PATH(&request->uri));\n#ifdef ANJAY_WITH_LWM2M12\n    if (_anjay_uri_path_leaf_is(&request->uri, ANJAY_ID_RIID)) {\n        return dm_delete_resource_instance(anjay, obj, request, ssid);\n    } else\n#endif // ANJAY_WITH_LWM2M12\n            if (_anjay_uri_path_leaf_is(&request->uri, ANJAY_ID_IID)) {\n        return dm_delete_object_instance(anjay, obj, request, ssid);\n    } else {\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\nstatic int invoke_transactional_action(anjay_unlocked_t *anjay,\n                                       const anjay_dm_installed_object_t *obj,\n                                       const anjay_request_t *request,\n                                       anjay_ssid_t ssid,\n                                       anjay_unlocked_input_ctx_t *in_ctx) {\n    if (avs_is_err(_anjay_dm_transaction_begin(anjay))) {\n        return ANJAY_ERR_INTERNAL;\n    }\n    int retval = 0;\n    switch (request->action) {\n    case ANJAY_ACTION_WRITE:\n    case ANJAY_ACTION_WRITE_UPDATE:\n        assert(in_ctx);\n        retval = _anjay_dm_write(anjay, obj, request, ssid, in_ctx);\n        break;\n#ifdef ANJAY_WITH_LWM2M11\n    case ANJAY_ACTION_WRITE_COMPOSITE:\n#    ifdef ANJAY_WITHOUT_COMPOSITE_OPERATIONS\n        retval = ANJAY_ERR_NOT_IMPLEMENTED;\n#    else  // ANJAY_WITHOUT_COMPOSITE_OPERATIONS\n        assert(in_ctx);\n        retval = _anjay_dm_write_composite(anjay, request, ssid, in_ctx);\n#    endif // ANJAY_WITHOUT_COMPOSITE_OPERATIONS\n        break;\n#endif // ANJAY_WITH_LWM2M11\n    case ANJAY_ACTION_CREATE:\n        assert(in_ctx);\n        retval = _anjay_dm_create(anjay, obj, request, ssid, in_ctx);\n        break;\n    case ANJAY_ACTION_DELETE:\n        retval = dm_delete(anjay, obj, request, ssid);\n        break;\n    default:\n        dm_log(ERROR, _(\"invalid transactional action\"));\n        retval = ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n\n    return _anjay_dm_transaction_finish(anjay, retval);\n}\n\nstatic int invoke_action(anjay_connection_ref_t connection,\n                         const anjay_dm_installed_object_t *obj,\n                         const anjay_request_t *request,\n                         anjay_unlocked_input_ctx_t *in_ctx) {\n    anjay_unlocked_t *anjay = _anjay_from_server(connection.server);\n    switch (request->action) {\n    case ANJAY_ACTION_READ:\n        return _anjay_dm_read_or_observe(connection, obj, request);\n#ifdef ANJAY_WITH_LWM2M11\n    case ANJAY_ACTION_READ_COMPOSITE:\n#    ifdef ANJAY_WITHOUT_COMPOSITE_OPERATIONS\n        return ANJAY_ERR_NOT_IMPLEMENTED;\n#    else  // ANJAY_WITHOUT_COMPOSITE_OPERATIONS\n        return _anjay_dm_read_or_observe_composite(connection, request, in_ctx);\n#    endif // ANJAY_WITHOUT_COMPOSITE_OPERATIONS\n#endif     // ANJAY_WITH_LWM2M11\n    case ANJAY_ACTION_DISCOVER:\n        return dm_discover(connection, obj, request);\n    case ANJAY_ACTION_WRITE:\n    case ANJAY_ACTION_WRITE_UPDATE:\n#ifdef ANJAY_WITH_LWM2M11\n    case ANJAY_ACTION_WRITE_COMPOSITE:\n#endif // ANJAY_WITH_LWM2M11\n    case ANJAY_ACTION_CREATE:\n    case ANJAY_ACTION_DELETE:\n        return invoke_transactional_action(\n                anjay, obj, request, _anjay_server_ssid(connection.server),\n                in_ctx);\n    case ANJAY_ACTION_WRITE_ATTRIBUTES:\n        return _anjay_dm_write_attributes(\n                anjay, obj, request, _anjay_server_ssid(connection.server));\n    case ANJAY_ACTION_EXECUTE:\n        AVS_ASSERT(!in_ctx, \"in_ctx should be NULL for Execute\");\n        return dm_execute(anjay, obj, request,\n                          _anjay_server_ssid(connection.server));\n    default:\n        dm_log(ERROR, _(\"Invalid action for Management Interface\"));\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\nint _anjay_dm_perform_action(anjay_connection_ref_t connection,\n                             const anjay_request_t *request) {\n    const anjay_dm_installed_object_t *obj = NULL;\n\n    if (_anjay_uri_path_has(&request->uri, ANJAY_ID_OID)) {\n        const anjay_dm_t *dm;\n#ifdef ANJAY_WITH_LWM2M_GATEWAY\n        if (strlen(request->uri.prefix) != 0) {\n            if (_anjay_lwm2m_gateway_prefix_to_dm(_anjay_from_server(\n                                                          connection.server),\n                                                  request->uri.prefix, &dm)) {\n                dm_log(ERROR, _(\"No End Device with specified prefix found\"));\n                return ANJAY_ERR_NOT_FOUND;\n            }\n        } else\n#endif // ANJAY_WITH_LWM2M_GATEWAY\n        {\n            dm = &_anjay_from_server(connection.server)->dm;\n        }\n\n        if (!(obj = _anjay_dm_find_object_by_oid(\n                      dm, request->uri.ids[ANJAY_ID_OID]))) {\n            dm_log(DEBUG, _(\"Object not found: \") DM_LOG_PREFIX \"/%u\",\n                   DM_LOG_PREFIX_ARG(request->uri.prefix)\n                           request->uri.ids[ANJAY_ID_OID]);\n            return ANJAY_ERR_NOT_FOUND;\n        }\n    } else\n#ifdef ANJAY_WITH_LWM2M11\n            if (request->action != ANJAY_ACTION_READ_COMPOSITE\n                && request->action != ANJAY_ACTION_WRITE_COMPOSITE)\n#endif // ANJAY_WITH_LWM2M11\n    {\n        dm_log(DEBUG, _(\"at least Object ID must be present in Uri-Path\"));\n        return ANJAY_ERR_BAD_REQUEST;\n    }\n\n    /**\n     * NOTE: Some operations do not require payload in response, and a simple\n     * empty response initialized just below will be sufficient. Other\n     * operations may setup response once again themselves if necessary.\n     */\n    anjay_msg_details_t msg_details = {\n        .msg_code = _anjay_dm_make_success_response_code(request->action),\n        .format = AVS_COAP_FORMAT_NONE\n    };\n\n    if (!_anjay_coap_setup_response_stream(request->ctx, &msg_details)) {\n        return ANJAY_ERR_INTERNAL;\n    }\n\n    if (_anjay_uri_path_has(&request->uri, ANJAY_ID_OID)\n            && (request->uri.ids[ANJAY_ID_OID] == ANJAY_DM_OID_SECURITY)) {\n        /**\n         * According to the LwM2M 1.1 specification:\n         * > The LwM2M Client MUST reject with an \"4.01 Unauthorized\" response\n         * > code any LwM2M Server operation on the Security Object (ID: 0).\n         * >\n         * > The LwM2M Client MUST reject with an \"4.01 Unauthorized\" response\n         * > code any LwM2M Server operation on an OSCORE Object (ID: 21).\n         *\n         * Note that other, per-instance security checks are performed via\n         * _anjay_instance_action_allowed().\n         */\n        return ANJAY_ERR_UNAUTHORIZED;\n    }\n\n    anjay_unlocked_input_ctx_t *in_ctx = NULL;\n    int result =\n            prepare_input_context(request->payload_stream, request, &in_ctx);\n    if (result) {\n        return result;\n    }\n    result = invoke_action(connection, obj, request, in_ctx);\n\n    int destroy_result = _anjay_input_ctx_destroy(&in_ctx);\n    return result ? result : destroy_result;\n}\n\nint _anjay_dm_foreach_object(anjay_unlocked_t *anjay,\n                             anjay_dm_t *dm,\n                             anjay_dm_foreach_object_handler_t *handler,\n                             void *data) {\n    AVS_LIST(anjay_dm_installed_object_t) obj;\n    AVS_LIST_FOREACH(obj, dm->objects) {\n        int result = handler(anjay, obj, data);\n        if (result == ANJAY_FOREACH_BREAK) {\n            dm_log(TRACE, _(\"foreach_object: break on \") DM_LOG_PREFIX \"/%u\",\n                   DM_LOG_PREFIX_OBJ_ARG(obj)\n                           _anjay_dm_installed_object_oid(obj));\n            return 0;\n        } else if (result) {\n            dm_log(DEBUG,\n                   _(\"foreach_object_handler failed for \") DM_LOG_PREFIX\n                   \"/%u\" _(\" (\") \"%d\" _(\")\"),\n                   DM_LOG_PREFIX_OBJ_ARG(obj)\n                           _anjay_dm_installed_object_oid(obj),\n                   result);\n            return result;\n        }\n    }\n\n    return 0;\n}\n\ntypedef struct {\n    const anjay_dm_list_ctx_vtable_t *vtable;\n    anjay_unlocked_t *anjay;\n    const anjay_dm_installed_object_t *obj;\n    int32_t last_iid;\n    anjay_dm_foreach_instance_handler_t *handler;\n    void *handler_data;\n    int result;\n} anjay_dm_foreach_instance_ctx_t;\n\nstatic void foreach_instance_emit(anjay_unlocked_dm_list_ctx_t *ctx_,\n                                  uint16_t iid) {\n    anjay_dm_foreach_instance_ctx_t *ctx =\n            (anjay_dm_foreach_instance_ctx_t *) ctx_;\n    if (!ctx->result) {\n        if (iid == ANJAY_ID_INVALID) {\n            dm_log(ERROR, \"%\" PRIu16 _(\" is not a valid Instance ID\"), iid);\n            ctx->result = ANJAY_ERR_INTERNAL;\n            return;\n        }\n        if (iid <= ctx->last_iid) {\n            dm_log(ERROR,\n                   _(\"list_instances MUST return Instance IDs in strictly \"\n                     \"ascending order; \") \"%\" PRIu16\n                           _(\" returned after \") \"%\" PRId32,\n                   iid, ctx->last_iid);\n            ctx->result = ANJAY_ERR_INTERNAL;\n            return;\n        }\n        ctx->last_iid = iid;\n        ctx->result =\n                ctx->handler(ctx->anjay, ctx->obj, iid, ctx->handler_data);\n        if (ctx->result == ANJAY_FOREACH_BREAK) {\n            dm_log(TRACE,\n                   _(\"foreach_instance: break on \") DM_LOG_PREFIX \"/%u/%u\",\n                   DM_LOG_PREFIX_OBJ_ARG(ctx->obj)\n                           _anjay_dm_installed_object_oid(ctx->obj),\n                   iid);\n        } else if (ctx->result) {\n            dm_log(DEBUG,\n                   _(\"foreach_instance_handler failed for \") DM_LOG_PREFIX\n                   \"/%u/%u\" _(\" (\") \"%d\" _(\")\"),\n                   DM_LOG_PREFIX_OBJ_ARG(ctx->obj)\n                           _anjay_dm_installed_object_oid(ctx->obj),\n                   iid, ctx->result);\n        }\n    }\n}\n\nint _anjay_dm_foreach_instance(anjay_unlocked_t *anjay,\n                               const anjay_dm_installed_object_t *obj,\n                               anjay_dm_foreach_instance_handler_t *handler,\n                               void *data) {\n    if (!obj) {\n        dm_log(ERROR, _(\"attempt to iterate through NULL Object\"));\n        return -1;\n    }\n\n    static const anjay_dm_list_ctx_vtable_t VTABLE = {\n        .emit = foreach_instance_emit\n    };\n    anjay_dm_foreach_instance_ctx_t ctx = {\n        .vtable = &VTABLE,\n        .anjay = anjay,\n        .obj = obj,\n        .last_iid = -1,\n        .handler = handler,\n        .handler_data = data,\n        .result = 0\n    };\n    int result = _anjay_dm_call_list_instances(\n            anjay, obj, (anjay_unlocked_dm_list_ctx_t *) &ctx);\n    if (result < 0) {\n        dm_log(WARNING,\n               _(\"list_instances handler for \") DM_LOG_PREFIX\n               \"/%u\" _(\" failed (\") \"%d\" _(\")\"),\n               DM_LOG_PREFIX_OBJ_ARG(obj) _anjay_dm_installed_object_oid(obj),\n               result);\n        return result;\n    }\n    return ctx.result == ANJAY_FOREACH_BREAK ? 0 : ctx.result;\n}\n\ntypedef struct {\n    anjay_iid_t iid_to_find;\n    bool found;\n} instance_present_args_t;\n\nstatic int query_dm_instance(anjay_unlocked_t *anjay,\n                             const anjay_dm_installed_object_t *obj,\n                             anjay_iid_t iid,\n                             void *instance_insert_ptr_) {\n    (void) anjay;\n    (void) obj;\n    AVS_LIST(anjay_iid_t) **instance_insert_ptr =\n            (AVS_LIST(anjay_iid_t) **) instance_insert_ptr_;\n\n    AVS_LIST(anjay_iid_t) new_instance = AVS_LIST_NEW_ELEMENT(anjay_iid_t);\n    if (!new_instance) {\n        _anjay_log_oom();\n        return -1;\n    }\n    AVS_LIST_INSERT(*instance_insert_ptr, new_instance);\n    AVS_LIST_ADVANCE_PTR(instance_insert_ptr);\n\n    *new_instance = iid;\n    return 0;\n}\n\nint _anjay_dm_get_sorted_instance_list(anjay_unlocked_t *anjay,\n                                       const anjay_dm_installed_object_t *obj,\n                                       AVS_LIST(anjay_iid_t) *out) {\n    assert(!*out);\n    AVS_LIST(anjay_iid_t) *instance_insert_ptr = out;\n    int retval = _anjay_dm_foreach_instance(anjay, obj, query_dm_instance,\n                                            &instance_insert_ptr);\n    if (retval) {\n        AVS_LIST_CLEAR(out);\n    }\n    return retval;\n}\n\nstatic int instance_present_clb(anjay_unlocked_t *anjay,\n                                const anjay_dm_installed_object_t *obj,\n                                anjay_iid_t iid,\n                                void *args_) {\n    (void) anjay;\n    (void) obj;\n    instance_present_args_t *args = (instance_present_args_t *) args_;\n    if (iid >= args->iid_to_find) {\n        args->found = (iid == args->iid_to_find);\n        return ANJAY_FOREACH_BREAK;\n    }\n    return ANJAY_FOREACH_CONTINUE;\n}\n\nint _anjay_dm_instance_present(anjay_unlocked_t *anjay,\n                               const anjay_dm_installed_object_t *obj_ptr,\n                               anjay_iid_t iid) {\n    instance_present_args_t args = {\n        .iid_to_find = iid,\n        .found = false\n    };\n    int retval = _anjay_dm_foreach_instance(anjay, obj_ptr,\n                                            instance_present_clb, &args);\n    if (retval < 0) {\n        return retval;\n    }\n    return args.found ? 1 : 0;\n}\n\nstruct anjay_unlocked_dm_resource_list_ctx_struct {\n    anjay_unlocked_t *anjay;\n    const anjay_dm_installed_object_t *obj;\n    anjay_iid_t iid;\n    int32_t last_rid;\n    anjay_dm_foreach_resource_handler_t *handler;\n    void *handler_data;\n    int result;\n};\n\nstatic bool presence_valid(anjay_dm_resource_presence_t presence) {\n    return presence == ANJAY_DM_RES_ABSENT || presence == ANJAY_DM_RES_PRESENT;\n}\n\nvoid _anjay_dm_emit_res_unlocked(anjay_unlocked_dm_resource_list_ctx_t *ctx,\n                                 anjay_rid_t rid,\n                                 anjay_dm_resource_kind_t kind,\n                                 anjay_dm_resource_presence_t presence) {\n    if (!ctx->result) {\n        if (rid == ANJAY_ID_INVALID) {\n            dm_log(ERROR, \"%\" PRIu16 _(\" is not a valid Resource ID\"), rid);\n            ctx->result = ANJAY_ERR_INTERNAL;\n            return;\n        }\n        if (rid <= ctx->last_rid) {\n            dm_log(ERROR,\n                   _(\"list_resources MUST return Resource IDs in strictly \"\n                     \"ascending order; \") \"%\" PRIu16\n                           _(\" returned after \") \"%\" PRId32,\n                   rid, ctx->last_rid);\n            ctx->result = ANJAY_ERR_INTERNAL;\n            return;\n        }\n        ctx->last_rid = rid;\n        if (!_anjay_dm_res_kind_valid(kind)) {\n            dm_log(ERROR, \"%d\" _(\" is not valid anjay_dm_resource_kind_t\"),\n                   (int) kind);\n            ctx->result = ANJAY_ERR_INTERNAL;\n            return;\n        }\n        if (!presence_valid(presence)) {\n            dm_log(ERROR, \"%d\" _(\" is not valid anjay_dm_resource_presence_t\"),\n                   (int) presence);\n            ctx->result = ANJAY_ERR_INTERNAL;\n            return;\n        }\n        ctx->result = ctx->handler(ctx->anjay, ctx->obj, ctx->iid, rid, kind,\n                                   presence, ctx->handler_data);\n        if (ctx->result == ANJAY_FOREACH_BREAK) {\n            dm_log(TRACE,\n                   _(\"foreach_resource: break on \") DM_LOG_PREFIX \"/%u/%u/%u\",\n                   DM_LOG_PREFIX_OBJ_ARG(ctx->obj)\n                           _anjay_dm_installed_object_oid(ctx->obj),\n                   ctx->iid, rid);\n        } else if (ctx->result) {\n            dm_log(DEBUG,\n                   _(\"foreach_resource_handler failed for \") DM_LOG_PREFIX\n                   \"/%u/%u/%u\" _(\" (\") \"%d\" _(\")\"),\n                   DM_LOG_PREFIX_OBJ_ARG(ctx->obj)\n                           _anjay_dm_installed_object_oid(ctx->obj),\n                   ctx->iid, rid, ctx->result);\n        }\n    }\n}\n\nvoid anjay_dm_emit_res(anjay_dm_resource_list_ctx_t *ctx,\n                       anjay_rid_t rid,\n                       anjay_dm_resource_kind_t kind,\n                       anjay_dm_resource_presence_t presence) {\n#ifdef ANJAY_WITH_THREAD_SAFETY\n    anjay_t *anjay_locked =\n            AVS_CONTAINER_OF(_anjay_dm_resource_list_get_unlocked(ctx)->anjay,\n                             anjay_t, anjay_unlocked_placeholder);\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n#endif // ANJAY_WITH_THREAD_SAFETY\n    _anjay_dm_emit_res_unlocked(_anjay_dm_resource_list_get_unlocked(ctx), rid,\n                                kind, presence);\n#ifdef ANJAY_WITH_THREAD_SAFETY\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n#endif // ANJAY_WITH_THREAD_SAFETY\n}\n\nint _anjay_dm_foreach_resource(anjay_unlocked_t *anjay,\n                               const anjay_dm_installed_object_t *obj,\n                               anjay_iid_t iid,\n                               anjay_dm_foreach_resource_handler_t *handler,\n                               void *data) {\n    if (!obj) {\n        dm_log(ERROR, _(\"attempt to iterate through NULL Object\"));\n        return -1;\n    }\n\n    anjay_unlocked_dm_resource_list_ctx_t ctx = {\n        .anjay = anjay,\n        .obj = obj,\n        .iid = iid,\n        .last_rid = -1,\n        .handler = handler,\n        .handler_data = data,\n        .result = 0\n    };\n    int result = _anjay_dm_call_list_resources(anjay, obj, iid, &ctx);\n    if (result < 0) {\n        dm_log(ERROR,\n               _(\"list_resources handler for \") DM_LOG_PREFIX\n               \"/%u/%u\" _(\" failed (\") \"%d\" _(\")\"),\n               DM_LOG_PREFIX_OBJ_ARG(obj) _anjay_dm_installed_object_oid(obj),\n               iid, result);\n        return result;\n    }\n    return ctx.result == ANJAY_FOREACH_BREAK ? 0 : ctx.result;\n}\n\ntypedef struct {\n    anjay_rid_t rid_to_find;\n    anjay_dm_resource_kind_t kind;\n    anjay_dm_resource_presence_t presence;\n} resource_present_args_t;\n\nstatic int kind_and_presence_clb(anjay_unlocked_t *anjay,\n                                 const anjay_dm_installed_object_t *obj,\n                                 anjay_iid_t iid,\n                                 anjay_rid_t rid,\n                                 anjay_dm_resource_kind_t kind,\n                                 anjay_dm_resource_presence_t presence,\n                                 void *args_) {\n    (void) anjay;\n    (void) obj;\n    (void) iid;\n    resource_present_args_t *args = (resource_present_args_t *) args_;\n    if (rid >= args->rid_to_find) {\n        if (rid == args->rid_to_find) {\n            args->kind = kind;\n            args->presence = presence;\n        }\n        return ANJAY_FOREACH_BREAK;\n    }\n    return ANJAY_FOREACH_CONTINUE;\n}\n\nint _anjay_dm_resource_kind_and_presence(\n        anjay_unlocked_t *anjay,\n        const anjay_dm_installed_object_t *obj_ptr,\n        anjay_iid_t iid,\n        anjay_rid_t rid,\n        anjay_dm_resource_kind_t *out_kind,\n        anjay_dm_resource_presence_t *out_presence) {\n    resource_present_args_t args = {\n        .rid_to_find = rid,\n        .kind = (anjay_dm_resource_kind_t) -1,\n        .presence = ANJAY_DM_RES_ABSENT\n    };\n    assert(!_anjay_dm_res_kind_valid(args.kind));\n    int retval = _anjay_dm_foreach_resource(anjay, obj_ptr, iid,\n                                            kind_and_presence_clb, &args);\n    if (retval) {\n        return retval;\n    }\n    if (!_anjay_dm_res_kind_valid(args.kind)) {\n        return ANJAY_ERR_NOT_FOUND;\n    }\n    if (out_kind) {\n        *out_kind = args.kind;\n    }\n    if (out_presence) {\n        *out_presence = args.presence;\n    }\n    return 0;\n}\n\nint _anjay_dm_path_info(anjay_unlocked_t *anjay,\n                        const anjay_dm_installed_object_t *obj,\n                        const anjay_uri_path_t *path,\n                        anjay_dm_path_info_t *out_info) {\n    memset(out_info, 0, sizeof(*out_info));\n    out_info->uri = *path;\n    out_info->is_present = true;\n    out_info->is_hierarchical = true;\n\n    if (_anjay_uri_path_length(path) == 0) {\n        return 0;\n    }\n    if (!obj) {\n        out_info->is_present = false;\n        return 0;\n    }\n\n    int result = 0;\n    if (_anjay_uri_path_has(path, ANJAY_ID_IID)) {\n        result = _anjay_dm_verify_instance_present(anjay, obj,\n                                                   path->ids[ANJAY_ID_IID]);\n    }\n    if (!result && _anjay_uri_path_has(path, ANJAY_ID_RID)) {\n        anjay_dm_resource_kind_t kind;\n        anjay_dm_resource_presence_t presence;\n        result = _anjay_dm_resource_kind_and_presence(anjay, obj,\n                                                      path->ids[ANJAY_ID_IID],\n                                                      path->ids[ANJAY_ID_RID],\n                                                      &kind, &presence);\n        if (!result) {\n            out_info->is_present = (presence == ANJAY_DM_RES_PRESENT);\n            if (out_info->is_present) {\n                out_info->has_resource = true;\n                out_info->kind = kind;\n                out_info->is_hierarchical = _anjay_dm_res_kind_multiple(kind);\n            }\n        }\n    }\n    if (!result && _anjay_uri_path_has(path, ANJAY_ID_RIID)) {\n        int is_present = (out_info->is_present && out_info->is_hierarchical);\n        if (is_present) {\n            is_present = dm_resource_instance_present(anjay, obj,\n                                                      path->ids[ANJAY_ID_IID],\n                                                      path->ids[ANJAY_ID_RID],\n                                                      path->ids[ANJAY_ID_RIID]);\n        }\n        result = _anjay_dm_map_present_result(is_present);\n        out_info->is_hierarchical = false;\n    }\n    if (result == ANJAY_ERR_NOT_FOUND) {\n        out_info->is_present = false;\n        return 0;\n    } else {\n        return result;\n    }\n}\n\ntypedef struct {\n    const anjay_dm_list_ctx_vtable_t *vtable;\n    anjay_unlocked_t *anjay;\n    const anjay_dm_installed_object_t *obj;\n    anjay_iid_t iid;\n    anjay_rid_t rid;\n    int32_t last_riid;\n    anjay_dm_foreach_resource_instance_handler_t *handler;\n    void *handler_data;\n    int result;\n} anjay_dm_foreach_resource_instance_ctx_t;\n\nstatic void foreach_resource_instance_emit(anjay_unlocked_dm_list_ctx_t *ctx_,\n                                           uint16_t riid) {\n    anjay_dm_foreach_resource_instance_ctx_t *ctx =\n            (anjay_dm_foreach_resource_instance_ctx_t *) ctx_;\n    if (!ctx->result) {\n        if (riid == ANJAY_ID_INVALID) {\n            dm_log(ERROR, \"%\" PRIu16 _(\" is not a valid Resource Instance ID\"),\n                   riid);\n            ctx->result = ANJAY_ERR_INTERNAL;\n            return;\n        }\n        if (riid <= ctx->last_riid) {\n            dm_log(ERROR,\n                   _(\"list_resource_instances MUST return Resource Instance \"\n                     \"IDs in strictly ascending order; \") \"%\" PRIu16\n                           _(\" returned after \") \"%\" PRId32,\n                   riid, ctx->last_riid);\n            ctx->result = ANJAY_ERR_INTERNAL;\n            return;\n        }\n        ctx->last_riid = riid;\n        ctx->result = ctx->handler(ctx->anjay, ctx->obj, ctx->iid, ctx->rid,\n                                   riid, ctx->handler_data);\n        if (ctx->result == ANJAY_FOREACH_BREAK) {\n            dm_log(TRACE,\n                   _(\"foreach_resource_instance: break on \") DM_LOG_PREFIX\n                   \"/%u/%u/%u/%u\",\n                   DM_LOG_PREFIX_OBJ_ARG(ctx->obj)\n                           _anjay_dm_installed_object_oid(ctx->obj),\n                   ctx->iid, ctx->rid, riid);\n        } else if (ctx->result) {\n            dm_log(DEBUG,\n                   _(\"foreach_resource_handler failed for \") DM_LOG_PREFIX\n                   \"/%u/%u/%u/%u\" _(\" (\") \"%d\" _(\")\"),\n                   DM_LOG_PREFIX_OBJ_ARG(ctx->obj)\n                           _anjay_dm_installed_object_oid(ctx->obj),\n                   ctx->iid, ctx->rid, riid, ctx->result);\n        }\n    }\n}\n\nint _anjay_dm_foreach_resource_instance(\n        anjay_unlocked_t *anjay,\n        const anjay_dm_installed_object_t *obj,\n        anjay_iid_t iid,\n        anjay_rid_t rid,\n        anjay_dm_foreach_resource_instance_handler_t *handler,\n        void *data) {\n    if (!obj) {\n        dm_log(ERROR, _(\"attempt to iterate through NULL Object\"));\n        return -1;\n    }\n\n    static const anjay_dm_list_ctx_vtable_t VTABLE = {\n        .emit = foreach_resource_instance_emit\n    };\n    anjay_dm_foreach_resource_instance_ctx_t ctx = {\n        .vtable = &VTABLE,\n        .anjay = anjay,\n        .obj = obj,\n        .iid = iid,\n        .rid = rid,\n        .last_riid = -1,\n        .handler = handler,\n        .handler_data = data,\n        .result = 0\n    };\n    int result = _anjay_dm_call_list_resource_instances(\n            anjay, obj, iid, rid, (anjay_unlocked_dm_list_ctx_t *) &ctx);\n    if (result < 0) {\n        dm_log(ERROR,\n               _(\"list_resource_instances handler for \") DM_LOG_PREFIX\n               \"/%u/%u/%u\" _(\" failed (\") \"%d\" _(\")\"),\n               DM_LOG_PREFIX_OBJ_ARG(obj) _anjay_dm_installed_object_oid(obj),\n               iid, rid, result);\n        return result;\n    }\n    return ctx.result == ANJAY_FOREACH_BREAK ? 0 : ctx.result;\n}\n\n#ifdef ANJAY_TEST\n#    include \"tests/core/dm.c\"\n#endif // ANJAY_TEST\n"
  },
  {
    "path": "src/core/anjay_dm_core.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ANJAY_DM_H\n#define ANJAY_DM_H\n\n#include <limits.h>\n\n#include <avsystem/commons/avs_list.h>\n#include <avsystem/commons/avs_stream.h>\n\n#include <anjay_modules/anjay_dm_utils.h>\n#include <anjay_modules/anjay_servers.h>\n\n#include <avsystem/coap/streaming.h>\n\n#include \"coap/anjay_msg_details.h\"\n#include \"dm/anjay_dm_attributes.h\"\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\ntypedef struct {\n    anjay_dm_module_deleter_t *deleter;\n    void *arg;\n} anjay_dm_installed_module_t;\n\nstruct anjay_dm {\n    AVS_LIST(anjay_dm_installed_object_t) objects;\n    AVS_LIST(anjay_dm_installed_module_t) modules;\n};\n\nvoid _anjay_dm_cleanup(anjay_dm_t *dm);\n\ntypedef struct {\n    bool has_min_period;\n    bool has_max_period;\n    bool has_greater_than;\n    bool has_less_than;\n    bool has_step;\n    bool has_min_eval_period;\n    bool has_max_eval_period;\n#ifdef ANJAY_WITH_CON_ATTR\n    bool has_con;\n#endif // ANJAY_WITH_CON_ATTR\n#ifdef ANJAY_WITH_LWM2M12\n    bool has_edge;\n    bool has_hqmax;\n#endif // ANJAY_WITH_LWM2M12\n    anjay_dm_r_attributes_t values;\n} anjay_request_attributes_t;\n\ntypedef struct {\n    avs_coap_streaming_request_ctx_t *ctx;\n    avs_stream_t *payload_stream;\n\n    uint8_t request_code;\n\n    bool is_bs_uri;\n\n    anjay_uri_path_t uri;\n\n    anjay_request_action_t action;\n    uint16_t content_format;\n    uint16_t requested_format;\n    const avs_coap_observe_id_t *observe;\n\n    anjay_request_attributes_t attributes;\n#ifdef ANJAY_WITH_LWM2M12\n    int8_t depth;\n#endif // ANJAY_WITH_LWM2M12\n} anjay_request_t;\n\n#ifdef ANJAY_WITH_LWM2M_GATEWAY\n#    define REQUEST_TO_ACTION_INFO(Request, Ssid)                      \\\n        (const anjay_action_info_t) {                                  \\\n            .end_device = _anjay_uri_path_has_prefix(&(Request)->uri), \\\n            .oid = (Request)->uri.ids[ANJAY_ID_OID],                   \\\n            .iid = (Request)->uri.ids[ANJAY_ID_IID],                   \\\n            .ssid = (Ssid),                                            \\\n            .action = (Request)->action                                \\\n        }\n#else // ANJAY_WITH_LWM2M_GATEWAY\n#    define REQUEST_TO_ACTION_INFO(Request, Ssid)    \\\n        (const anjay_action_info_t) {                \\\n            .oid = (Request)->uri.ids[ANJAY_ID_OID], \\\n            .iid = (Request)->uri.ids[ANJAY_ID_IID], \\\n            .ssid = (Ssid),                          \\\n            .action = (Request)->action              \\\n        }\n#endif // ANJAY_WITH_LWM2M_GATEWAY\n\nint _anjay_dm_transaction_validate(anjay_unlocked_t *anjay);\nint _anjay_dm_transaction_finish_without_validation(anjay_unlocked_t *anjay,\n                                                    int result);\n\nstatic inline int _anjay_dm_transaction_rollback(anjay_unlocked_t *anjay) {\n    int result = _anjay_dm_transaction_finish(anjay, INT_MIN);\n    return (result == INT_MIN) ? 0 : result;\n}\n\nint _anjay_dm_perform_action(anjay_connection_ref_t connection,\n                             const anjay_request_t *request);\n\nstatic inline int _anjay_dm_map_present_result(int result) {\n    if (!result) {\n        return ANJAY_ERR_NOT_FOUND;\n    } else if (result > 0) {\n        return 0;\n    } else {\n        return result;\n    }\n}\n\nAVS_LIST(anjay_dm_installed_module_t) *\n_anjay_dm_module_find_ptr(anjay_unlocked_t *anjay,\n                          anjay_dm_module_deleter_t *module_deleter);\n\nint _anjay_dm_select_free_iid(anjay_unlocked_t *anjay,\n                              const anjay_dm_installed_object_t *obj,\n                              anjay_iid_t *new_iid_ptr);\n\ntypedef struct {\n    anjay_uri_path_t uri;\n\n    // True if the entire path queried by @ref _anjay_dm_path_info() is present.\n    bool is_present;\n\n    // True if a leaf of the queried path is not a simple value.\n    bool is_hierarchical;\n    // True if the path points to a present resource or multiple resource.\n    bool has_resource;\n    // Only valid if has_resource == true.\n    anjay_dm_resource_kind_t kind;\n} anjay_dm_path_info_t;\n\nint _anjay_dm_path_info(anjay_unlocked_t *anjay,\n                        const anjay_dm_installed_object_t *obj,\n                        const anjay_uri_path_t *path,\n                        anjay_dm_path_info_t *out_info);\n\nuint8_t _anjay_dm_make_success_response_code(anjay_request_action_t action);\n\n#define dm_log(...) _anjay_log(anjay_dm, __VA_ARGS__)\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif // ANJAY_DM_H\n"
  },
  {
    "path": "src/core/anjay_downloader.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ANJAY_DOWNLOADER_H\n#define ANJAY_DOWNLOADER_H\n\n#include <anjay/download.h>\n#include <avsystem/commons/avs_net.h>\n#include <avsystem/commons/avs_sched.h>\n#include <avsystem/commons/avs_stream.h>\n\n#include \"anjay_utils_private.h\"\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\ntypedef struct anjay_download_ctx anjay_download_ctx_t;\n\ntypedef struct {\n    uintptr_t next_id;\n    AVS_LIST(anjay_download_ctx_t) downloads;\n} anjay_downloader_t;\n\n/**\n * Initializes a downloader object.\n *\n * @param dl                A downloader instance to initialize.\n * @param anjay             Anjay instance which owns the downloader.\n *                          Must outlive the downloader object.\n *                          @ref anjay_t#coap_id_source MUST be initialized.\n *                          @ref anjay_t#sched and @ref anjay_t#coap_socket\n *                          fields of @p anjay MUST be initialized, and MUST\n *                          not change during the downloader object lifetime.\n *\n * @returns 0 on success, negative value in case of an error.\n */\nint _anjay_downloader_init(anjay_downloader_t *dl, anjay_unlocked_t *anjay);\n\n/**\n * Frees any resources associated with the downloader object. Aborts all\n * unfinished downloads, calling their @ref anjay_download_finished_handler_t\n * handlers beforehand. All scheduled retransmission jobs are canceled.\n *\n * @param dl    Pointer to the downloader object to cleanup.\n */\nvoid _anjay_downloader_cleanup(anjay_downloader_t *dl);\n\n/**\n * @returns currently supported values are:\n *\n * - <c>AVS_EINVAL</c> - invalid argument, i.e. unparsable URL or unset handlers\n * - <c>AVS_ENOMEM</c> - out of memory\n * - <c>AVS_EPROTO</c> - unknown error on the socket layer, including (D)TLS\n *   encryption errors\n * - <c>AVS_EPROTONOSUPPORT</c> - unsupported protocol (URL schema)\n * - <c>AVS_ETIMEDOUT</c> - attempt to connect to the remote host timed out\n * - any <c>avs_errno_t</c> value that might be set by the underlying socket\n */\navs_error_t _anjay_downloader_download(anjay_downloader_t *dl,\n                                       anjay_download_handle_t *out_handle,\n                                       const anjay_download_config_t *config,\n                                       avs_coap_ctx_t *forced_coap_context,\n                                       avs_net_socket_t *forced_coap_socket);\n\n/**\n * Retrieves all sockets used for downloads managed by @p dl and prepends them\n * to @p out_socks.\n */\nint _anjay_downloader_get_sockets(anjay_downloader_t *dl,\n                                  AVS_LIST(anjay_socket_entry_t) *out_socks,\n                                  bool include_offline);\n\n/**\n * @returns @li 0 if @p socket was a downloaded socket and the incoming packet\n *              does not require further processing,\n *          @li a negative value if @p socket was not a download socket.\n *\n */\nint _anjay_downloader_handle_packet(anjay_downloader_t *dl,\n                                    avs_net_socket_t *socket);\n\navs_error_t\n_anjay_downloader_set_next_block_offset(anjay_downloader_t *dl,\n                                        anjay_download_handle_t handle,\n                                        size_t next_block_offset);\n\nvoid _anjay_downloader_abort(anjay_downloader_t *dl,\n                             anjay_download_handle_t handle);\n\nvoid _anjay_downloader_suspend(anjay_downloader_t *dl,\n                               anjay_download_handle_t handle);\n\nint _anjay_downloader_sched_reconnect_by_handle(anjay_downloader_t *dl,\n                                                anjay_download_handle_t handle);\n\nbool _anjay_downloader_same_socket_transfer_ongoing(anjay_downloader_t *dl,\n                                                    avs_net_socket_t *socket);\n\nvoid _anjay_downloader_suspend_same_socket(anjay_downloader_t *dl,\n                                           avs_net_socket_t *socket);\n\nvoid _anjay_downloader_abort_same_socket(anjay_downloader_t *dl,\n                                         avs_net_socket_t *socket);\n\nint _anjay_downloader_sched_reconnect_by_transports(\n        anjay_downloader_t *dl, anjay_transport_set_t transport_set);\n\nint _anjay_downloader_sync_online_transports(anjay_downloader_t *dl);\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif /* ANJAY_DOWNLOADER_H */\n"
  },
  {
    "path": "src/core/anjay_event_loop.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n// NOTE: Some compat headers need to be included before avs_log.h,\n// so we can't use anjay_init.h here.\n#include <anjay/anjay_config.h>\n#include <avsystem/commons/avs_commons_config.h>\n\n#ifdef ANJAY_WITH_EVENT_LOOP\n\n#    ifdef AVS_COMMONS_POSIX_COMPAT_HEADER\n#        include AVS_COMMONS_POSIX_COMPAT_HEADER\n#    else // AVS_COMMONS_POSIX_COMPAT_HEADER\n#        ifdef AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_POLL\n#            include <poll.h>\n#        else // AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_POLL\n#            include <sys/select.h>\n#        endif // AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_POLL\n#    endif     // AVS_COMMONS_POSIX_COMPAT_HEADER\n\n#    include <anjay_init.h>\n\n#    include \"anjay_core.h\"\n\nVISIBILITY_SOURCE_BEGIN\n\n#    ifndef AVS_COMMONS_POSIX_COMPAT_HEADER\ntypedef int sockfd_t;\n#    endif // AVS_COMMONS_POSIX_COMPAT_HEADER\n\n#    ifndef INVALID_SOCKET\n#        define INVALID_SOCKET (-1)\n#    endif\n\nstatic bool should_event_loop_still_run(anjay_t *anjay) {\n    int status = ANJAY_EVENT_LOOP_INTERRUPT;\n    if (atomic_compare_exchange_strong(&anjay->atomic_fields.event_loop_status,\n                                       &status,\n                                       ANJAY_EVENT_LOOP_IDLE)) {\n        // interrupt has been just handled\n        return false;\n    } else {\n        // expected now contains the value of event_loop_status\n        return status == ANJAY_EVENT_LOOP_RUNNING;\n    }\n}\n\ntypedef struct {\n    anjay_t *const anjay_locked;\n    const avs_time_duration_t max_wait_time;\n    const bool allow_interrupt;\n#    ifdef AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_POLL\n    struct pollfd *pollfds;\n    size_t pollfds_size;\n#    endif // AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_POLL\n} event_loop_state_t;\n\nstatic void event_loop_state_cleanup(event_loop_state_t *state) {\n    (void) state;\n#    ifdef AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_POLL\n    avs_free(state->pollfds);\n    state->pollfds = NULL;\n    state->pollfds_size = 0;\n#    endif // AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_POLL\n}\n\ntypedef enum {\n    HANDLE_SOCKETS_ERROR = -1,\n    HANDLE_SOCKETS_CONTINUE = 0,\n    HANDLE_SOCKETS_BREAK = 1\n} handle_sockets_result_t;\n\nstatic handle_sockets_result_t handle_sockets(event_loop_state_t *state) {\n    assert(state->anjay_locked);\n    assert(avs_time_duration_valid(state->max_wait_time)\n           && !avs_time_duration_less(state->max_wait_time,\n                                      AVS_TIME_DURATION_ZERO));\n#    ifdef AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_POLL\n    size_t numsocks = 0;\n    size_t i = 0;\n#    else  // AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_POLL\n    fd_set infds;\n    fd_set outfds;\n    fd_set errfds;\n    sockfd_t nfds = 0;\n#    endif // AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_POLL\n    handle_sockets_result_t result = HANDLE_SOCKETS_CONTINUE;\n    AVS_LIST(const anjay_socket_entry_t) entries = NULL;\n    AVS_LIST(const anjay_socket_entry_t) *entry_ptr = NULL;\n    AVS_LIST(const anjay_socket_entry_t) entry = NULL;\n    bool sockets_ready = false;\n\n    ANJAY_MUTEX_LOCK(anjay, state->anjay_locked);\n    entries =\n            _anjay_collect_socket_entries(anjay, /* include_offline = */ false);\n\n#    ifdef AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_POLL\n    numsocks = AVS_LIST_SIZE(entries);\n    if (numsocks != state->pollfds_size) {\n        struct pollfd *pollfds_new = NULL;\n        if (!numsocks) {\n            avs_free(state->pollfds);\n        } else {\n            pollfds_new = (struct pollfd *) avs_realloc(\n                    state->pollfds, numsocks * sizeof(*state->pollfds));\n        }\n        if (pollfds_new || !numsocks) {\n            state->pollfds = pollfds_new;\n            state->pollfds_size = numsocks;\n        } else if (numsocks > state->pollfds_size) {\n            _anjay_log_oom();\n            result = HANDLE_SOCKETS_ERROR;\n        }\n    }\n\n    if (result == HANDLE_SOCKETS_CONTINUE) {\n        AVS_LIST_DELETABLE_FOREACH_PTR(entry_ptr, entry, &entries) {\n            assert(i < numsocks);\n            state->pollfds[i].events = POLLIN;\n            state->pollfds[i].revents = 0;\n            state->pollfds[i].fd = INVALID_SOCKET;\n            const void *fd_ptr =\n                    avs_net_socket_get_system((*entry_ptr)->socket);\n            if (fd_ptr) {\n                state->pollfds[i].fd = *(const sockfd_t *) fd_ptr;\n            }\n            if (state->pollfds[i].fd == INVALID_SOCKET) {\n                AVS_LIST_DELETE(entry_ptr);\n            } else {\n                ++i;\n            }\n        }\n    }\n#    else  // AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_POLL\n    FD_ZERO(&infds);\n    FD_ZERO(&outfds);\n    FD_ZERO(&errfds);\n    nfds = 0;\n\n    AVS_LIST_DELETABLE_FOREACH_PTR(entry_ptr, entry, &entries) {\n        sockfd_t fd = INVALID_SOCKET;\n        const void *fd_ptr = avs_net_socket_get_system((*entry_ptr)->socket);\n        if (fd_ptr) {\n            fd = *(const sockfd_t *) fd_ptr;\n        }\n        if (fd == INVALID_SOCKET || fd >= FD_SETSIZE) {\n            AVS_LIST_DELETE(entry_ptr);\n        } else {\n            FD_SET(fd, &infds);\n            FD_SET(fd, &errfds);\n            nfds = AVS_MAX(nfds, fd + 1);\n        }\n    }\n#    endif // AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_POLL\n    ANJAY_MUTEX_UNLOCK(state->anjay_locked);\n\n    avs_time_duration_t wait_time;\n    if (anjay_sched_time_to_next(state->anjay_locked, &wait_time)\n            || !avs_time_duration_less(wait_time, state->max_wait_time)) {\n        wait_time = state->max_wait_time;\n    }\n    assert(avs_time_duration_valid(wait_time)\n           && !avs_time_duration_less(wait_time, AVS_TIME_DURATION_ZERO));\n\n    // Wait for the events if necessary, and handle them.\n#    ifdef AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_POLL\n    int64_t wait_ms;\n    if (result == HANDLE_SOCKETS_CONTINUE) {\n        if (avs_time_duration_to_scalar(&wait_ms, AVS_TIME_MS, wait_time)\n                || wait_ms > INT_MAX) {\n            wait_ms = (int64_t) INT_MAX;\n        }\n        sockets_ready = (poll(state->pollfds, i, (int) wait_ms) > 0);\n    }\n    i = 0;\n#    else  // AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_POLL\n           // NOTE: This assumes that time_t is a signed integer type\n    static const time_t AVS_TIME_MAX =\n            (time_t) ((UINT64_C(1) << (8 * sizeof(time_t) - 1)) - 1);\n    struct timeval wait_timeval = {\n        .tv_sec = AVS_TIME_MAX\n    };\n    if (wait_time.seconds <= AVS_TIME_MAX) {\n        wait_timeval.tv_sec = (time_t) wait_time.seconds;\n        wait_timeval.tv_usec = (int32_t) (wait_time.nanoseconds / 1000);\n    }\n    sockets_ready = (select(nfds, &infds, &outfds, &errfds, &wait_timeval) > 0);\n#    endif // AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_POLL\n\n    if (sockets_ready) {\n        AVS_LIST_FOREACH(entry, entries) {\n            if (state->allow_interrupt\n                    && !should_event_loop_still_run(state->anjay_locked)) {\n                result = HANDLE_SOCKETS_BREAK;\n                break;\n            }\n#    ifdef AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_POLL\n            assert(i < numsocks);\n            if (!state->pollfds[i++].revents) {\n                continue;\n            }\n#    else  // AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_POLL\n            sockfd_t fd = INVALID_SOCKET;\n            const void *fd_ptr = avs_net_socket_get_system(entry->socket);\n            if (fd_ptr) {\n                fd = *(const sockfd_t *) fd_ptr;\n            }\n            if (fd == INVALID_SOCKET\n                    || !(FD_ISSET(fd, &infds) || FD_ISSET(fd, &errfds))) {\n                continue;\n            }\n#    endif // AVS_COMMONS_NET_POSIX_AVS_SOCKET_HAVE_POLL\n            if (anjay_serve(state->anjay_locked, entry->socket)) {\n                anjay_log(WARNING, \"anjay_serve failed\");\n            }\n        }\n    }\n\n    AVS_LIST_CLEAR(&entries);\n    return result;\n}\n\nstatic int event_loop_run_with_error_handling(anjay_t *anjay_locked,\n                                              avs_time_duration_t max_wait_time,\n                                              bool enable_error_handling) {\n    if (!avs_time_duration_valid(max_wait_time)\n            || avs_time_duration_less(max_wait_time, AVS_TIME_DURATION_ZERO)) {\n        anjay_log(ERROR, \"max_wait_time needs to be valid and non-negative\");\n        return -1;\n    }\n    if (!atomic_compare_exchange_strong(\n                &anjay_locked->atomic_fields.event_loop_status,\n                &(int) { ANJAY_EVENT_LOOP_IDLE },\n                ANJAY_EVENT_LOOP_RUNNING)) {\n        anjay_log(ERROR, \"Event loop is already running\");\n        return -1;\n    }\n    handle_sockets_result_t handle_sockets_result = HANDLE_SOCKETS_CONTINUE;\n    event_loop_state_t state = {\n        .anjay_locked = anjay_locked,\n        .max_wait_time = max_wait_time,\n        .allow_interrupt = true\n    };\n    bool running = should_event_loop_still_run(anjay_locked);\n    while (running) {\n        handle_sockets_result = handle_sockets(&state);\n        switch (handle_sockets_result) {\n        case HANDLE_SOCKETS_ERROR:\n            atomic_store(&anjay_locked->atomic_fields.event_loop_status,\n                         ANJAY_EVENT_LOOP_IDLE);\n            // fall through\n        case HANDLE_SOCKETS_BREAK:\n            running = false;\n            break;\n        case HANDLE_SOCKETS_CONTINUE:\n            anjay_sched_run(anjay_locked);\n            running = should_event_loop_still_run(anjay_locked);\n\n            if (enable_error_handling) {\n                if (anjay_all_connections_failed(anjay_locked)) {\n                    anjay_transport_schedule_reconnect(anjay_locked,\n                                                       ANJAY_TRANSPORT_SET_ALL);\n                }\n            }\n        }\n    }\n    event_loop_state_cleanup(&state);\n    return handle_sockets_result == HANDLE_SOCKETS_ERROR ? -1 : 0;\n}\n\nint anjay_event_loop_run(anjay_t *anjay_locked,\n                         avs_time_duration_t max_wait_time) {\n    return event_loop_run_with_error_handling(anjay_locked, max_wait_time,\n                                              false);\n}\n\nint anjay_event_loop_run_with_error_handling(\n        anjay_t *anjay_locked, avs_time_duration_t max_wait_time) {\n    return event_loop_run_with_error_handling(anjay_locked, max_wait_time,\n                                              true);\n}\n\nint anjay_event_loop_interrupt(anjay_t *anjay) {\n    return atomic_compare_exchange_strong(\n                   &anjay->atomic_fields.event_loop_status,\n                   &(int) { ANJAY_EVENT_LOOP_RUNNING },\n                   ANJAY_EVENT_LOOP_INTERRUPT)\n                   ? 0\n                   : -1;\n}\n\nint anjay_serve_any(anjay_t *anjay_locked, avs_time_duration_t max_wait_time) {\n    if (!avs_time_duration_valid(max_wait_time)\n            || avs_time_duration_less(max_wait_time, AVS_TIME_DURATION_ZERO)) {\n        anjay_log(ERROR, \"max_wait_time needs to be valid and non-negative\");\n        return -1;\n    }\n    event_loop_state_t state = {\n        .anjay_locked = anjay_locked,\n        .max_wait_time = max_wait_time,\n        .allow_interrupt = false\n    };\n    handle_sockets_result_t handle_sockets_result = handle_sockets(&state);\n    event_loop_state_cleanup(&state);\n    return handle_sockets_result == HANDLE_SOCKETS_ERROR ? -1 : 0;\n}\n\n#endif // ANJAY_WITH_EVENT_LOOP\n"
  },
  {
    "path": "src/core/anjay_io_core.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#include <inttypes.h>\n#include <stdlib.h>\n\n#include <avsystem/commons/avs_memory.h>\n#include <avsystem/commons/avs_stream_v_table.h>\n#include <avsystem/commons/avs_utils.h>\n\n#include <anjay/core.h>\n\n#include \"coap/anjay_content_format.h\"\n\n#include \"anjay_io_core.h\"\n#include \"io/anjay_vtable.h\"\n\nVISIBILITY_SOURCE_BEGIN\n\nstruct anjay_unlocked_dm_list_ctx_struct {\n    const anjay_dm_list_ctx_vtable_t *vtable;\n};\n\nvoid _anjay_dm_emit_unlocked(anjay_unlocked_dm_list_ctx_t *ctx, uint16_t id) {\n    assert(ctx->vtable && ctx->vtable->emit);\n    ctx->vtable->emit(ctx, id);\n}\n\nvoid anjay_dm_emit(anjay_dm_list_ctx_t *ctx, uint16_t id) {\n#ifdef ANJAY_WITH_THREAD_SAFETY\n    ANJAY_MUTEX_LOCK(anjay, ctx->anjay_locked);\n#endif // ANJAY_WITH_THREAD_SAFETY\n    _anjay_dm_emit_unlocked(_anjay_dm_list_get_unlocked(ctx), id);\n#ifdef ANJAY_WITH_THREAD_SAFETY\n    ANJAY_MUTEX_UNLOCK(ctx->anjay_locked);\n#endif // ANJAY_WITH_THREAD_SAFETY\n}\n\nstruct anjay_unlocked_ret_bytes_ctx_struct {\n    const anjay_ret_bytes_ctx_vtable_t *vtable;\n};\n\n#ifdef ANJAY_WITH_LEGACY_CONTENT_FORMAT_SUPPORT\n\nuint16_t _anjay_translate_legacy_content_format(uint16_t format) {\n    static const char MSG_FMT[] =\n            \"legacy application/vnd.oma.lwm2m+%s Content-Format value: %d\";\n\n    switch (format) {\n    case ANJAY_COAP_FORMAT_LEGACY_PLAINTEXT:\n        anjay_log(DEBUG, MSG_FMT, _(\"text\"),\n                  ANJAY_COAP_FORMAT_LEGACY_PLAINTEXT);\n        return AVS_COAP_FORMAT_PLAINTEXT;\n\n    case ANJAY_COAP_FORMAT_LEGACY_TLV:\n        anjay_log(DEBUG, MSG_FMT, _(\"tlv\"), ANJAY_COAP_FORMAT_LEGACY_TLV);\n        return AVS_COAP_FORMAT_OMA_LWM2M_TLV;\n\n    case ANJAY_COAP_FORMAT_LEGACY_JSON:\n        anjay_log(DEBUG, MSG_FMT, _(\"json\"), ANJAY_COAP_FORMAT_LEGACY_JSON);\n        return AVS_COAP_FORMAT_OMA_LWM2M_JSON;\n\n    case ANJAY_COAP_FORMAT_LEGACY_OPAQUE:\n        anjay_log(DEBUG, MSG_FMT, _(\"opaque\"), ANJAY_COAP_FORMAT_LEGACY_OPAQUE);\n        return AVS_COAP_FORMAT_OCTET_STREAM;\n\n    default:\n        return format;\n    }\n}\n\n#endif // ANJAY_WITH_LEGACY_CONTENT_FORMAT_SUPPORT\n\nanjay_unlocked_ret_bytes_ctx_t *\n_anjay_ret_bytes_begin_unlocked(anjay_unlocked_output_ctx_t *ctx,\n                                size_t length) {\n    anjay_unlocked_ret_bytes_ctx_t *bytes_ctx = NULL;\n    int result = _anjay_output_bytes_begin(ctx, length, &bytes_ctx);\n    assert(!result == !!bytes_ctx);\n    (void) result;\n    return bytes_ctx;\n}\n\nanjay_ret_bytes_ctx_t *anjay_ret_bytes_begin(anjay_output_ctx_t *ctx,\n                                             size_t length) {\n#ifdef ANJAY_WITH_THREAD_SAFETY\n    anjay_ret_bytes_ctx_t *retval = NULL;\n    ANJAY_MUTEX_LOCK(anjay, ctx->anjay_locked);\n#endif // ANJAY_WITH_THREAD_SAFETY\n    anjay_unlocked_ret_bytes_ctx_t *bytes_ctx =\n            _anjay_ret_bytes_begin_unlocked(_anjay_output_get_unlocked(ctx),\n                                            length);\n#ifdef ANJAY_WITH_THREAD_SAFETY\n    if (bytes_ctx) {\n        ctx->bytes_ctx.unlocked_ctx = bytes_ctx;\n        retval = &ctx->bytes_ctx;\n    }\n    ANJAY_MUTEX_UNLOCK(ctx->anjay_locked);\n    return retval;\n#else  // ANJAY_WITH_THREAD_SAFETY\n    return (anjay_ret_bytes_ctx_t *) bytes_ctx;\n#endif // ANJAY_WITH_THREAD_SAFETY\n}\n\nint _anjay_ret_bytes_append_unlocked(anjay_unlocked_ret_bytes_ctx_t *ctx,\n                                     const void *data,\n                                     size_t length) {\n    assert(ctx && ctx->vtable && ctx->vtable->append);\n    return ctx->vtable->append(ctx, data, length);\n}\n\nint anjay_ret_bytes_append(anjay_ret_bytes_ctx_t *ctx,\n                           const void *data,\n                           size_t length) {\n    assert(ctx);\n    int result = -1;\n#ifdef ANJAY_WITH_THREAD_SAFETY\n    anjay_t *anjay_locked =\n            AVS_CONTAINER_OF(ctx, anjay_output_ctx_t, bytes_ctx)->anjay_locked;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n#endif // ANJAY_WITH_THREAD_SAFETY\n    result =\n            _anjay_ret_bytes_append_unlocked(_anjay_ret_bytes_get_unlocked(ctx),\n                                             data, length);\n#ifdef ANJAY_WITH_THREAD_SAFETY\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n#endif // ANJAY_WITH_THREAD_SAFETY\n    return result;\n}\n\nint _anjay_ret_bytes_unlocked(anjay_unlocked_output_ctx_t *ctx,\n                              const void *data,\n                              size_t length) {\n    anjay_unlocked_ret_bytes_ctx_t *bytes =\n            _anjay_ret_bytes_begin_unlocked(ctx, length);\n    if (!bytes) {\n        return -1;\n    } else {\n        return _anjay_ret_bytes_append_unlocked(bytes, data, length);\n    }\n}\n\nint anjay_ret_bytes(anjay_output_ctx_t *ctx, const void *data, size_t length) {\n    int result = -1;\n#ifdef ANJAY_WITH_THREAD_SAFETY\n    ANJAY_MUTEX_LOCK(anjay, ctx->anjay_locked);\n#endif // ANJAY_WITH_THREAD_SAFETY\n    result = _anjay_ret_bytes_unlocked(_anjay_output_get_unlocked(ctx), data,\n                                       length);\n#ifdef ANJAY_WITH_THREAD_SAFETY\n    ANJAY_MUTEX_UNLOCK(ctx->anjay_locked);\n#endif // ANJAY_WITH_THREAD_SAFETY\n    return result;\n}\n\nint _anjay_ret_string_unlocked(anjay_unlocked_output_ctx_t *ctx,\n                               const char *value) {\n    int result = ANJAY_OUTCTXERR_METHOD_NOT_IMPLEMENTED;\n    if (ctx->vtable->string) {\n        result = ctx->vtable->string(ctx, value);\n    }\n    _anjay_update_ret(&ctx->error, result);\n    return result;\n}\n\nint anjay_ret_string(anjay_output_ctx_t *ctx, const char *value) {\n    int result = -1;\n#ifdef ANJAY_WITH_THREAD_SAFETY\n    ANJAY_MUTEX_LOCK(anjay, ctx->anjay_locked);\n#endif // ANJAY_WITH_THREAD_SAFETY\n    result = _anjay_ret_string_unlocked(_anjay_output_get_unlocked(ctx), value);\n#ifdef ANJAY_WITH_THREAD_SAFETY\n    ANJAY_MUTEX_UNLOCK(ctx->anjay_locked);\n#endif // ANJAY_WITH_THREAD_SAFETY\n    return result;\n}\n\nint _anjay_ret_i64_unlocked(anjay_unlocked_output_ctx_t *ctx, int64_t value) {\n    int result = ANJAY_OUTCTXERR_METHOD_NOT_IMPLEMENTED;\n    if (ctx->vtable->integer) {\n        result = ctx->vtable->integer(ctx, value);\n    }\n    _anjay_update_ret(&ctx->error, result);\n    return result;\n}\n\nint anjay_ret_i64(anjay_output_ctx_t *ctx, int64_t value) {\n    int result = -1;\n#ifdef ANJAY_WITH_THREAD_SAFETY\n    ANJAY_MUTEX_LOCK(anjay, ctx->anjay_locked);\n#endif // ANJAY_WITH_THREAD_SAFETY\n    result = _anjay_ret_i64_unlocked(_anjay_output_get_unlocked(ctx), value);\n#ifdef ANJAY_WITH_THREAD_SAFETY\n    ANJAY_MUTEX_UNLOCK(ctx->anjay_locked);\n#endif // ANJAY_WITH_THREAD_SAFETY\n    return result;\n}\n\n#ifdef ANJAY_WITH_LWM2M11\nint _anjay_ret_u64_unlocked(anjay_unlocked_output_ctx_t *ctx, uint64_t value) {\n    int result = ANJAY_OUTCTXERR_METHOD_NOT_IMPLEMENTED;\n    if (ctx->vtable->uint) {\n        result = ctx->vtable->uint(ctx, value);\n    }\n    _anjay_update_ret(&ctx->error, result);\n    return result;\n}\n\nint anjay_ret_u64(anjay_output_ctx_t *ctx, uint64_t value) {\n    int result = -1;\n#    ifdef ANJAY_WITH_THREAD_SAFETY\n    ANJAY_MUTEX_LOCK(anjay, ctx->anjay_locked);\n#    endif // ANJAY_WITH_THREAD_SAFETY\n    result = _anjay_ret_u64_unlocked(_anjay_output_get_unlocked(ctx), value);\n#    ifdef ANJAY_WITH_THREAD_SAFETY\n    ANJAY_MUTEX_UNLOCK(ctx->anjay_locked);\n#    endif // ANJAY_WITH_THREAD_SAFETY\n    return result;\n}\n#endif // ANJAY_WITH_LWM2M11\n\nint _anjay_ret_double_unlocked(anjay_unlocked_output_ctx_t *ctx, double value) {\n    int result = ANJAY_OUTCTXERR_METHOD_NOT_IMPLEMENTED;\n    if (ctx->vtable->floating) {\n        result = ctx->vtable->floating(ctx, value);\n    }\n    _anjay_update_ret(&ctx->error, result);\n    return result;\n}\n\nint anjay_ret_double(anjay_output_ctx_t *ctx, double value) {\n    int result = -1;\n#ifdef ANJAY_WITH_THREAD_SAFETY\n    ANJAY_MUTEX_LOCK(anjay, ctx->anjay_locked);\n#endif // ANJAY_WITH_THREAD_SAFETY\n    result = _anjay_ret_double_unlocked(_anjay_output_get_unlocked(ctx), value);\n#ifdef ANJAY_WITH_THREAD_SAFETY\n    ANJAY_MUTEX_UNLOCK(ctx->anjay_locked);\n#endif // ANJAY_WITH_THREAD_SAFETY\n    return result;\n}\n\nint _anjay_ret_bool_unlocked(anjay_unlocked_output_ctx_t *ctx, bool value) {\n    int result = ANJAY_OUTCTXERR_METHOD_NOT_IMPLEMENTED;\n    if (ctx->vtable->boolean) {\n        result = ctx->vtable->boolean(ctx, value);\n    }\n    _anjay_update_ret(&ctx->error, result);\n    return result;\n}\n\nint anjay_ret_bool(anjay_output_ctx_t *ctx, bool value) {\n    int result = -1;\n#ifdef ANJAY_WITH_THREAD_SAFETY\n    ANJAY_MUTEX_LOCK(anjay, ctx->anjay_locked);\n#endif // ANJAY_WITH_THREAD_SAFETY\n    result = _anjay_ret_bool_unlocked(_anjay_output_get_unlocked(ctx), value);\n#ifdef ANJAY_WITH_THREAD_SAFETY\n    ANJAY_MUTEX_UNLOCK(ctx->anjay_locked);\n#endif // ANJAY_WITH_THREAD_SAFETY\n    return result;\n}\n\nint _anjay_ret_objlnk_unlocked(anjay_unlocked_output_ctx_t *ctx,\n                               anjay_oid_t oid,\n                               anjay_iid_t iid) {\n    int result = ANJAY_OUTCTXERR_METHOD_NOT_IMPLEMENTED;\n    if (ctx->vtable->objlnk) {\n        result = ctx->vtable->objlnk(ctx, oid, iid);\n    }\n    _anjay_update_ret(&ctx->error, result);\n    return result;\n}\n\nint anjay_ret_objlnk(anjay_output_ctx_t *ctx,\n                     anjay_oid_t oid,\n                     anjay_iid_t iid) {\n    int result = -1;\n#ifdef ANJAY_WITH_THREAD_SAFETY\n    ANJAY_MUTEX_LOCK(anjay, ctx->anjay_locked);\n#endif // ANJAY_WITH_THREAD_SAFETY\n    result = _anjay_ret_objlnk_unlocked(_anjay_output_get_unlocked(ctx), oid,\n                                        iid);\n#ifdef ANJAY_WITH_THREAD_SAFETY\n    ANJAY_MUTEX_UNLOCK(ctx->anjay_locked);\n#endif // ANJAY_WITH_THREAD_SAFETY\n    return result;\n}\n\n#if defined(ANJAY_WITH_SECURITY_STRUCTURED)\nint _anjay_ret_security_info_unlocked(\n        anjay_unlocked_output_ctx_t *ctx,\n        const avs_crypto_security_info_union_t *desc) {\n    int result = ANJAY_OUTCTXERR_METHOD_NOT_IMPLEMENTED;\n    if (ctx->vtable->security_info) {\n        result = ctx->vtable->security_info(ctx, desc);\n    }\n    _anjay_update_ret(&ctx->error, result);\n    return result;\n}\n#endif /* defined(ANJAY_WITH_SECURITY_STRUCTURED) || \\\n          defined(ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT) */\n\n#ifdef ANJAY_WITH_SECURITY_STRUCTURED\nint anjay_ret_certificate_chain_info(\n        anjay_output_ctx_t *ctx,\n        avs_crypto_certificate_chain_info_t certificate_chain_info) {\n    assert(certificate_chain_info.desc.type\n           == AVS_CRYPTO_SECURITY_INFO_CERTIFICATE_CHAIN);\n    int result = -1;\n#    ifdef ANJAY_WITH_THREAD_SAFETY\n    ANJAY_MUTEX_LOCK(anjay, ctx->anjay_locked);\n#    endif // ANJAY_WITH_THREAD_SAFETY\n    result = _anjay_ret_security_info_unlocked(_anjay_output_get_unlocked(ctx),\n                                               &certificate_chain_info.desc);\n#    ifdef ANJAY_WITH_THREAD_SAFETY\n    ANJAY_MUTEX_UNLOCK(ctx->anjay_locked);\n#    endif // ANJAY_WITH_THREAD_SAFETY\n    return result;\n}\n\nint anjay_ret_private_key_info(anjay_output_ctx_t *ctx,\n                               avs_crypto_private_key_info_t private_key_info) {\n    int result = -1;\n#    ifdef ANJAY_WITH_THREAD_SAFETY\n    ANJAY_MUTEX_LOCK(anjay, ctx->anjay_locked);\n#    endif // ANJAY_WITH_THREAD_SAFETY\n    result = _anjay_ret_security_info_unlocked(_anjay_output_get_unlocked(ctx),\n                                               &private_key_info.desc);\n#    ifdef ANJAY_WITH_THREAD_SAFETY\n    ANJAY_MUTEX_UNLOCK(ctx->anjay_locked);\n#    endif // ANJAY_WITH_THREAD_SAFETY\n    return result;\n}\n\nint anjay_ret_psk_identity_info(\n        anjay_output_ctx_t *ctx,\n        avs_crypto_psk_identity_info_t psk_identity_info) {\n    int result = -1;\n#    ifdef ANJAY_WITH_THREAD_SAFETY\n    ANJAY_MUTEX_LOCK(anjay, ctx->anjay_locked);\n#    endif // ANJAY_WITH_THREAD_SAFETY\n    result = _anjay_ret_security_info_unlocked(_anjay_output_get_unlocked(ctx),\n                                               &psk_identity_info.desc);\n#    ifdef ANJAY_WITH_THREAD_SAFETY\n    ANJAY_MUTEX_UNLOCK(ctx->anjay_locked);\n#    endif // ANJAY_WITH_THREAD_SAFETY\n    return result;\n}\n\nint anjay_ret_psk_key_info(anjay_output_ctx_t *ctx,\n                           avs_crypto_psk_key_info_t psk_key_info) {\n    int result = -1;\n#    ifdef ANJAY_WITH_THREAD_SAFETY\n    ANJAY_MUTEX_LOCK(anjay, ctx->anjay_locked);\n#    endif // ANJAY_WITH_THREAD_SAFETY\n    result = _anjay_ret_security_info_unlocked(_anjay_output_get_unlocked(ctx),\n                                               &psk_key_info.desc);\n#    ifdef ANJAY_WITH_THREAD_SAFETY\n    ANJAY_MUTEX_UNLOCK(ctx->anjay_locked);\n#    endif // ANJAY_WITH_THREAD_SAFETY\n    return result;\n}\n#endif // ANJAY_WITH_SECURITY_STRUCTURED\n\nint _anjay_output_bytes_begin(anjay_unlocked_output_ctx_t *ctx,\n                              size_t length,\n                              anjay_unlocked_ret_bytes_ctx_t **out_bytes_ctx) {\n    assert(out_bytes_ctx);\n    assert(!*out_bytes_ctx);\n    int result = ANJAY_OUTCTXERR_METHOD_NOT_IMPLEMENTED;\n    if (ctx->vtable->bytes_begin) {\n        result = ctx->vtable->bytes_begin(ctx, length, out_bytes_ctx);\n    }\n    _anjay_update_ret(&ctx->error, result);\n    return result;\n}\n\nint _anjay_output_start_aggregate(anjay_unlocked_output_ctx_t *ctx) {\n    int result = ANJAY_OUTCTXERR_METHOD_NOT_IMPLEMENTED;\n    if (ctx->vtable->start_aggregate) {\n        result = ctx->vtable->start_aggregate(ctx);\n    }\n    _anjay_update_ret(&ctx->error, result);\n    return result;\n}\n\nint _anjay_output_set_path(anjay_unlocked_output_ctx_t *ctx,\n                           const anjay_uri_path_t *path) {\n    // NOTE: We explicitly consider NULL set_path() to be always successful,\n    // to simplify implementation of outbuf_ctx and the like.\n    int result = 0;\n    if (ctx->vtable->set_path) {\n        result = ctx->vtable->set_path(ctx, path);\n    }\n    _anjay_update_ret(&ctx->error, result);\n    return result;\n}\n\nint _anjay_output_clear_path(anjay_unlocked_output_ctx_t *ctx) {\n    int result = ANJAY_OUTCTXERR_METHOD_NOT_IMPLEMENTED;\n    if (ctx->vtable->clear_path) {\n        result = ctx->vtable->clear_path(ctx);\n    }\n    _anjay_update_ret(&ctx->error, result);\n    return result;\n}\n\nint _anjay_output_set_time(anjay_unlocked_output_ctx_t *ctx, double value) {\n    if (!ctx->vtable->set_time) {\n        // Deliberately ignore set_time fails - non-SenML formats will just omit\n        // the timestamps, this is fine.\n        return 0;\n    }\n    int result = ctx->vtable->set_time(ctx, value);\n    _anjay_update_ret(&ctx->error, result);\n    return result;\n}\n\nint _anjay_output_ctx_destroy(anjay_unlocked_output_ctx_t **ctx_ptr) {\n    anjay_unlocked_output_ctx_t *ctx = *ctx_ptr;\n    int result = 0;\n    if (ctx) {\n        result = ctx->error;\n        if (ctx->vtable->close) {\n            _anjay_update_ret(&result, ctx->vtable->close(ctx));\n        }\n        avs_free(ctx);\n        *ctx_ptr = NULL;\n    }\n    return result;\n}\n\nint _anjay_output_ctx_destroy_and_process_result(\n        anjay_unlocked_output_ctx_t **out_ctx_ptr, int result) {\n    int destroy_result = _anjay_output_ctx_destroy(out_ctx_ptr);\n    if (destroy_result != ANJAY_OUTCTXERR_ANJAY_RET_NOT_CALLED) {\n        return destroy_result ? destroy_result : result;\n    } else if (result) {\n        return result;\n    } else {\n        anjay_log(ERROR,\n                  _(\"unable to determine resource type: anjay_ret_* not called \"\n                    \"during successful resource_read handler call\"));\n        return ANJAY_ERR_INTERNAL;\n    }\n}\n\nstatic int get_some_bytes(anjay_unlocked_input_ctx_t *ctx,\n                          size_t *out_bytes_read,\n                          bool *out_message_finished,\n                          void *out_buf,\n                          size_t buf_size) {\n    if (!ctx->vtable->some_bytes) {\n        return -1;\n    }\n    return ctx->vtable->some_bytes(ctx, out_bytes_read, out_message_finished,\n                                   out_buf, buf_size);\n}\n\nint _anjay_get_bytes_unlocked(anjay_unlocked_input_ctx_t *ctx,\n                              size_t *out_bytes_read,\n                              bool *out_message_finished,\n                              void *out_buf,\n                              size_t buf_size) {\n    char *buf_ptr = (char *) out_buf;\n    size_t buf_left = buf_size;\n    while (true) {\n        size_t tmp_bytes_read = 0;\n        int retval = get_some_bytes(ctx, &tmp_bytes_read, out_message_finished,\n                                    buf_ptr, buf_left);\n        buf_ptr += tmp_bytes_read;\n        buf_left -= tmp_bytes_read;\n        if (retval || *out_message_finished || !buf_left) {\n            *out_bytes_read = buf_size - buf_left;\n            return retval;\n        }\n    }\n}\n\nint anjay_get_bytes(anjay_input_ctx_t *ctx,\n                    size_t *out_bytes_read,\n                    bool *out_message_finished,\n                    void *out_buf,\n                    size_t buf_size) {\n    int retval = -1;\n#ifdef ANJAY_WITH_THREAD_SAFETY\n    ANJAY_MUTEX_LOCK(anjay, ctx->anjay_locked);\n#endif // ANJAY_WITH_THREAD_SAFETY\n    retval = _anjay_get_bytes_unlocked(_anjay_input_get_unlocked(ctx),\n                                       out_bytes_read, out_message_finished,\n                                       out_buf, buf_size);\n#ifdef ANJAY_WITH_THREAD_SAFETY\n    ANJAY_MUTEX_UNLOCK(ctx->anjay_locked);\n#endif // ANJAY_WITH_THREAD_SAFETY\n    return retval;\n}\n\nint _anjay_get_string_unlocked(anjay_unlocked_input_ctx_t *ctx,\n                               char *out_buf,\n                               size_t buf_size) {\n    if (!ctx->vtable->string) {\n        return -1;\n    }\n    if (buf_size == 0) {\n        // At least terminating nullbyte must fit into the buffer!\n        return ANJAY_BUFFER_TOO_SHORT;\n    }\n    return ctx->vtable->string(ctx, out_buf, buf_size);\n}\n\nint anjay_get_string(anjay_input_ctx_t *ctx, char *out_buf, size_t buf_size) {\n    int result = -1;\n#ifdef ANJAY_WITH_THREAD_SAFETY\n    ANJAY_MUTEX_LOCK(anjay, ctx->anjay_locked);\n#endif // ANJAY_WITH_THREAD_SAFETY\n    result = _anjay_get_string_unlocked(_anjay_input_get_unlocked(ctx), out_buf,\n                                        buf_size);\n#ifdef ANJAY_WITH_THREAD_SAFETY\n    ANJAY_MUTEX_UNLOCK(ctx->anjay_locked);\n#endif // ANJAY_WITH_THREAD_SAFETY\n    return result;\n}\n\nint _anjay_get_i32_unlocked(anjay_unlocked_input_ctx_t *ctx, int32_t *out) {\n    int64_t tmp;\n    int result = _anjay_get_i64_unlocked(ctx, &tmp);\n    if (!result) {\n        if (tmp < INT32_MIN || tmp > INT32_MAX) {\n            result = ANJAY_ERR_BAD_REQUEST;\n        } else {\n            *out = (int32_t) tmp;\n        }\n    }\n    return result;\n}\n\nint anjay_get_i32(anjay_input_ctx_t *ctx, int32_t *out) {\n    int result = -1;\n#ifdef ANJAY_WITH_THREAD_SAFETY\n    ANJAY_MUTEX_LOCK(anjay, ctx->anjay_locked);\n#endif // ANJAY_WITH_THREAD_SAFETY\n    result = _anjay_get_i32_unlocked(_anjay_input_get_unlocked(ctx), out);\n#ifdef ANJAY_WITH_THREAD_SAFETY\n    ANJAY_MUTEX_UNLOCK(ctx->anjay_locked);\n#endif // ANJAY_WITH_THREAD_SAFETY\n    return result;\n}\n\nint _anjay_get_i64_unlocked(anjay_unlocked_input_ctx_t *ctx, int64_t *out) {\n    if (!ctx->vtable->integer) {\n        return -1;\n    }\n    return ctx->vtable->integer(ctx, out);\n}\n\nint anjay_get_i64(anjay_input_ctx_t *ctx, int64_t *out) {\n    int result = -1;\n#ifdef ANJAY_WITH_THREAD_SAFETY\n    ANJAY_MUTEX_LOCK(anjay, ctx->anjay_locked);\n#endif // ANJAY_WITH_THREAD_SAFETY\n    result = _anjay_get_i64_unlocked(_anjay_input_get_unlocked(ctx), out);\n#ifdef ANJAY_WITH_THREAD_SAFETY\n    ANJAY_MUTEX_UNLOCK(ctx->anjay_locked);\n#endif // ANJAY_WITH_THREAD_SAFETY\n    return result;\n}\n\n#ifdef ANJAY_WITH_LWM2M11\nint _anjay_get_u32_unlocked(anjay_unlocked_input_ctx_t *ctx, uint32_t *out) {\n    uint64_t tmp;\n    int result = _anjay_get_u64_unlocked(ctx, &tmp);\n    if (!result) {\n        if (tmp > UINT32_MAX) {\n            result = ANJAY_ERR_BAD_REQUEST;\n        } else {\n            *out = (uint32_t) tmp;\n        }\n    }\n    return result;\n}\n\nint anjay_get_u32(anjay_input_ctx_t *ctx, uint32_t *out) {\n    int result = -1;\n#    ifdef ANJAY_WITH_THREAD_SAFETY\n    ANJAY_MUTEX_LOCK(anjay, ctx->anjay_locked);\n#    endif // ANJAY_WITH_THREAD_SAFETY\n    result = _anjay_get_u32_unlocked(_anjay_input_get_unlocked(ctx), out);\n#    ifdef ANJAY_WITH_THREAD_SAFETY\n    ANJAY_MUTEX_UNLOCK(ctx->anjay_locked);\n#    endif // ANJAY_WITH_THREAD_SAFETY\n    return result;\n}\n\nint _anjay_get_u64_unlocked(anjay_unlocked_input_ctx_t *ctx, uint64_t *out) {\n    if (!ctx->vtable->uint) {\n        return -1;\n    }\n    return ctx->vtable->uint(ctx, out);\n}\n\nint anjay_get_u64(anjay_input_ctx_t *ctx, uint64_t *out) {\n    int result = -1;\n#    ifdef ANJAY_WITH_THREAD_SAFETY\n    ANJAY_MUTEX_LOCK(anjay, ctx->anjay_locked);\n#    endif // ANJAY_WITH_THREAD_SAFETY\n    result = _anjay_get_u64_unlocked(_anjay_input_get_unlocked(ctx), out);\n#    ifdef ANJAY_WITH_THREAD_SAFETY\n    ANJAY_MUTEX_UNLOCK(ctx->anjay_locked);\n#    endif // ANJAY_WITH_THREAD_SAFETY\n    return result;\n}\n#endif // ANJAY_WITH_LWM2M11\n\nint anjay_get_float(anjay_input_ctx_t *ctx, float *out) {\n    double tmp;\n    int result = anjay_get_double(ctx, &tmp);\n    if (!result) {\n        *out = (float) tmp;\n    }\n    return result;\n}\n\nint _anjay_get_double_unlocked(anjay_unlocked_input_ctx_t *ctx, double *out) {\n    if (!ctx->vtable->floating) {\n        return -1;\n    }\n    return ctx->vtable->floating(ctx, out);\n}\n\nint anjay_get_double(anjay_input_ctx_t *ctx, double *out) {\n    int result = -1;\n#ifdef ANJAY_WITH_THREAD_SAFETY\n    ANJAY_MUTEX_LOCK(anjay, ctx->anjay_locked);\n#endif // ANJAY_WITH_THREAD_SAFETY\n    result = _anjay_get_double_unlocked(_anjay_input_get_unlocked(ctx), out);\n#ifdef ANJAY_WITH_THREAD_SAFETY\n    ANJAY_MUTEX_UNLOCK(ctx->anjay_locked);\n#endif // ANJAY_WITH_THREAD_SAFETY\n    return result;\n}\n\nint _anjay_get_bool_unlocked(anjay_unlocked_input_ctx_t *ctx, bool *out) {\n    if (!ctx->vtable->boolean) {\n        return -1;\n    }\n    return ctx->vtable->boolean(ctx, out);\n}\n\nint anjay_get_bool(anjay_input_ctx_t *ctx, bool *out) {\n    int result = -1;\n#ifdef ANJAY_WITH_THREAD_SAFETY\n    ANJAY_MUTEX_LOCK(anjay, ctx->anjay_locked);\n#endif // ANJAY_WITH_THREAD_SAFETY\n    result = _anjay_get_bool_unlocked(_anjay_input_get_unlocked(ctx), out);\n#ifdef ANJAY_WITH_THREAD_SAFETY\n    ANJAY_MUTEX_UNLOCK(ctx->anjay_locked);\n#endif // ANJAY_WITH_THREAD_SAFETY\n    return result;\n}\n\nint _anjay_get_objlnk_unlocked(anjay_unlocked_input_ctx_t *ctx,\n                               anjay_oid_t *out_oid,\n                               anjay_iid_t *out_iid) {\n    if (!ctx->vtable->objlnk) {\n        return -1;\n    }\n    return ctx->vtable->objlnk(ctx, out_oid, out_iid);\n}\n\nint anjay_get_objlnk(anjay_input_ctx_t *ctx,\n                     anjay_oid_t *out_oid,\n                     anjay_iid_t *out_iid) {\n    int result = -1;\n#ifdef ANJAY_WITH_THREAD_SAFETY\n    ANJAY_MUTEX_LOCK(anjay, ctx->anjay_locked);\n#endif // ANJAY_WITH_THREAD_SAFETY\n    result = _anjay_get_objlnk_unlocked(_anjay_input_get_unlocked(ctx), out_oid,\n                                        out_iid);\n#ifdef ANJAY_WITH_THREAD_SAFETY\n    ANJAY_MUTEX_UNLOCK(ctx->anjay_locked);\n#endif // ANJAY_WITH_THREAD_SAFETY\n    return result;\n}\n\n#ifdef ANJAY_WITH_LWM2M12\nint _anjay_input_get_null(anjay_unlocked_input_ctx_t *ctx) {\n    if (!ctx->vtable->null) {\n        return -1;\n    }\n    return ctx->vtable->null(ctx);\n}\n#endif // ANJAY_WITH_LWM2M12\n\nint _anjay_input_get_path(anjay_unlocked_input_ctx_t *ctx,\n                          anjay_uri_path_t *out_path,\n                          bool *out_is_array) {\n    if (!ctx->vtable->get_path) {\n        return ANJAY_ERR_BAD_REQUEST;\n    }\n    bool ignored_is_array;\n    if (!out_is_array) {\n        out_is_array = &ignored_is_array;\n    }\n    anjay_uri_path_t ignored_path;\n    if (!out_path) {\n        out_path = &ignored_path;\n    }\n    (void) ignored_is_array;\n    (void) ignored_path;\n    return ctx->vtable->get_path(ctx, out_path, out_is_array);\n}\n\nint _anjay_input_update_root_path(anjay_unlocked_input_ctx_t *ctx,\n                                  const anjay_uri_path_t *root_path) {\n    if (!ctx->vtable->update_root_path) {\n        return ANJAY_ERR_BAD_REQUEST;\n    }\n    return ctx->vtable->update_root_path(ctx, root_path);\n}\n\nint _anjay_input_next_entry(anjay_unlocked_input_ctx_t *ctx) {\n    if (!ctx->vtable->next_entry) {\n        return -1;\n    }\n    return ctx->vtable->next_entry(ctx);\n}\n\nint _anjay_input_ctx_destroy(anjay_unlocked_input_ctx_t **ctx_ptr) {\n    int retval = 0;\n    anjay_unlocked_input_ctx_t *ctx = *ctx_ptr;\n    if (ctx) {\n        if (ctx->vtable->close) {\n            retval = ctx->vtable->close(*ctx_ptr);\n        }\n        avs_free(ctx);\n        *ctx_ptr = NULL;\n    }\n    return retval;\n}\n\n#ifdef ANJAY_TEST\n#    include \"tests/core/io.c\"\n#endif\n"
  },
  {
    "path": "src/core/anjay_io_core.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ANJAY_IO_CORE_H\n#define ANJAY_IO_CORE_H\n\n#include <avsystem/commons/avs_stream.h>\n#include <avsystem/commons/avs_stream_outbuf.h>\n\n#include <anjay/core.h>\n\n#include <anjay_modules/anjay_dm_utils.h>\n#include <anjay_modules/anjay_io_utils.h>\n\n#include \"coap/anjay_msg_details.h\"\n\n#include \"anjay_dm_core.h\"\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\nint _anjay_input_dynamic_construct(anjay_unlocked_input_ctx_t **out,\n                                   avs_stream_t *stream,\n                                   const anjay_request_t *request);\n\nint _anjay_input_dynamic_construct_raw(anjay_unlocked_input_ctx_t **out,\n                                       avs_stream_t *stream,\n                                       uint16_t format,\n                                       anjay_request_action_t action,\n                                       const anjay_uri_path_t *uri);\n\nint _anjay_output_dynamic_construct(anjay_unlocked_output_ctx_t **out_ctx,\n                                    avs_stream_t *stream,\n                                    const anjay_uri_path_t *uri,\n                                    uint16_t format,\n                                    const size_t *items_count,\n                                    anjay_request_action_t action);\n\n#ifdef ANJAY_WITH_LEGACY_CONTENT_FORMAT_SUPPORT\nuint16_t _anjay_translate_legacy_content_format(uint16_t format);\n#else\n#    define _anjay_translate_legacy_content_format(fmt) (fmt)\n#endif\n\n#define ANJAY_OUTCTXERR_FORMAT_MISMATCH (-0xCE0)\n#define ANJAY_OUTCTXERR_METHOD_NOT_IMPLEMENTED (-0xCE1)\n/* returned from _anjay_output_ctx_destroy if no anjay_ret_* function was\n * called, making it impossible to determine actual resource format */\n#define ANJAY_OUTCTXERR_ANJAY_RET_NOT_CALLED (-0xCE2)\n\n/** A value returned from @ref anjay_input_ctx_get_path_t to indicate end of the\n * path listing. */\n#define ANJAY_GET_PATH_END 1\n\ntypedef struct anjay_output_ctx_vtable_struct anjay_output_ctx_vtable_t;\n\n#ifndef ANJAY_WITH_THREAD_SAFETY\n\n#    define anjay_unlocked_dm_list_ctx_struct anjay_dm_list_ctx_struct\n#    define anjay_unlocked_dm_resource_list_ctx_struct \\\n        anjay_dm_resource_list_ctx_struct\n#    define anjay_unlocked_output_ctx_struct anjay_output_ctx_struct\n#    define anjay_unlocked_ret_bytes_ctx_struct anjay_ret_bytes_ctx_struct\n#    define anjay_unlocked_input_ctx_struct anjay_input_ctx_struct\n#    define anjay_unlocked_execute_ctx_struct anjay_execute_ctx_struct\n\n#endif // ANJAY_WITH_THREAD_SAFETY\n\nstruct anjay_unlocked_output_ctx_struct {\n    const anjay_output_ctx_vtable_t *vtable;\n    int error;\n};\n\ntypedef struct anjay_input_ctx_vtable_struct anjay_input_ctx_vtable_t;\n\nstruct anjay_unlocked_input_ctx_struct {\n    const anjay_input_ctx_vtable_t *vtable;\n};\n\n#ifdef ANJAY_WITH_THREAD_SAFETY\nstruct anjay_ret_bytes_ctx_struct {\n    anjay_unlocked_ret_bytes_ctx_t *unlocked_ctx;\n};\n\nstatic inline anjay_unlocked_ret_bytes_ctx_t *\n_anjay_ret_bytes_get_unlocked(anjay_ret_bytes_ctx_t *ctx) {\n    return ctx->unlocked_ctx;\n}\n\nstruct anjay_output_ctx_struct {\n    anjay_t *anjay_locked;\n    anjay_unlocked_output_ctx_t *unlocked_ctx;\n    anjay_ret_bytes_ctx_t bytes_ctx;\n};\n\nstatic inline anjay_unlocked_output_ctx_t *\n_anjay_output_get_unlocked(anjay_output_ctx_t *ctx) {\n    return ctx->unlocked_ctx;\n}\n\nstruct anjay_input_ctx_struct {\n    anjay_t *anjay_locked;\n    anjay_unlocked_input_ctx_t *unlocked_ctx;\n};\n\nstatic inline anjay_unlocked_input_ctx_t *\n_anjay_input_get_unlocked(anjay_input_ctx_t *ctx) {\n    return ctx->unlocked_ctx;\n}\n\nstruct anjay_execute_ctx_struct {\n    anjay_t *anjay_locked;\n    anjay_unlocked_execute_ctx_t *unlocked_ctx;\n};\n\nstatic inline anjay_unlocked_execute_ctx_t *\n_anjay_execute_get_unlocked(anjay_execute_ctx_t *ctx) {\n    return ctx->unlocked_ctx;\n}\n\nstruct anjay_dm_list_ctx_struct {\n    anjay_t *anjay_locked;\n    anjay_unlocked_dm_list_ctx_t *unlocked_ctx;\n};\n\nstatic inline anjay_unlocked_dm_list_ctx_t *\n_anjay_dm_list_get_unlocked(anjay_dm_list_ctx_t *ctx) {\n    return ctx->unlocked_ctx;\n}\n\nstruct anjay_dm_resource_list_ctx_struct {\n    anjay_unlocked_dm_resource_list_ctx_t *unlocked_ctx;\n};\n\nstatic inline anjay_unlocked_dm_resource_list_ctx_t *\n_anjay_dm_resource_list_get_unlocked(anjay_dm_resource_list_ctx_t *ctx) {\n    return ctx->unlocked_ctx;\n}\n\n#else // ANJAY_WITH_THREAD_SAFETY\n\nstatic inline anjay_unlocked_ret_bytes_ctx_t *\n_anjay_ret_bytes_get_unlocked(anjay_ret_bytes_ctx_t *ctx) {\n    return ctx;\n}\n\nstatic inline anjay_unlocked_output_ctx_t *\n_anjay_output_get_unlocked(anjay_output_ctx_t *ctx) {\n    return ctx;\n}\n\nstatic inline anjay_unlocked_input_ctx_t *\n_anjay_input_get_unlocked(anjay_input_ctx_t *ctx) {\n    return ctx;\n}\n\nstatic inline anjay_unlocked_execute_ctx_t *\n_anjay_execute_get_unlocked(anjay_execute_ctx_t *ctx) {\n    return ctx;\n}\n\nstatic inline anjay_unlocked_dm_list_ctx_t *\n_anjay_dm_list_get_unlocked(anjay_dm_list_ctx_t *ctx) {\n    return ctx;\n}\n\nstatic inline anjay_unlocked_dm_resource_list_ctx_t *\n_anjay_dm_resource_list_get_unlocked(anjay_dm_resource_list_ctx_t *ctx) {\n    return ctx;\n}\n\n#endif // ANJAY_WITH_THREAD_SAFETY\n\nanjay_unlocked_output_ctx_t *_anjay_output_opaque_create(avs_stream_t *stream);\n\n#ifndef ANJAY_WITHOUT_PLAINTEXT\nanjay_unlocked_output_ctx_t *_anjay_output_text_create(avs_stream_t *stream);\n#endif // ANJAY_WITHOUT_PLAINTEXT\n\n#ifdef ANJAY_WITH_CBOR\nanjay_unlocked_output_ctx_t *_anjay_output_cbor_create(avs_stream_t *stream);\n#endif // ANJAY_WITH_CBOR\n\n#ifndef ANJAY_WITHOUT_TLV\nanjay_unlocked_output_ctx_t *\n_anjay_output_tlv_create(avs_stream_t *stream, const anjay_uri_path_t *uri);\n#endif // ANJAY_WITHOUT_TLV\n\n#if defined(ANJAY_WITH_LWM2M_JSON) || defined(ANJAY_WITH_SENML_JSON) \\\n        || defined(ANJAY_WITH_CBOR)\nanjay_unlocked_output_ctx_t *\n_anjay_output_senml_like_create(avs_stream_t *stream,\n                                const anjay_uri_path_t *uri,\n                                uint16_t format,\n                                const size_t *items_count);\n#endif\n\n#if defined(ANJAY_WITH_CBOR) && defined(ANJAY_WITH_LWM2M12)\nanjay_unlocked_output_ctx_t *\n_anjay_output_lwm2m_cbor_create(avs_stream_t *stream,\n                                const anjay_uri_path_t *uri);\n#endif // defined(ANJAY_WITH_CBOR) && defined(ANJAY_WITH_LWM2M12)\n\nint _anjay_output_bytes_begin(anjay_unlocked_output_ctx_t *ctx,\n                              size_t length,\n                              anjay_unlocked_ret_bytes_ctx_t **out_bytes_ctx);\n\n/**\n * Starts an aggregate object, which may be an Object Instance (aggregate of\n * Resources) or Multi-Instance Resource (aggregate of Resource Instances).\n *\n * Normally, this operation is implicit, as it's enough to call\n * @ref _anjay_output_set_path to inform the output context of the appropriate\n * nesting level of the data to serialize. However, there is a need to handle\n * empty aggregates specially - e.g. when a Read was issued on a Multi-Instance\n * Resource that exists, but has zero instances.\n *\n * Currently such empty aggregates are only representable in TLV format, so this\n * method is implemented as a no-op in the SenML context.\n *\n * Note that it wouldn't be enough to specially handle\n * @ref _anjay_output_set_path when not followed by data, because TLV requires\n * different serialization format for Single and Multiple Instance Resources.\n * Call to this function after having called @ref _anjay_output_set_path with a\n * Resource path also doubles as an information for the context that it's\n * dealing with a Multiple Resource.\n */\nint _anjay_output_start_aggregate(anjay_unlocked_output_ctx_t *ctx);\n\nint _anjay_output_set_path(anjay_unlocked_output_ctx_t *ctx,\n                           const anjay_uri_path_t *path);\n\nint _anjay_output_clear_path(anjay_unlocked_output_ctx_t *ctx);\n\nint _anjay_output_set_time(anjay_unlocked_output_ctx_t *ctx, double value);\n\n/**\n * @returns Code of the FIRST known error encountered on this output context,\n *          in the following precedence order:\n *          1. First known error code of any method call on this context\n *          2. Error code of the destroy operation\n */\nint _anjay_output_ctx_destroy(anjay_unlocked_output_ctx_t **ctx_ptr);\n\nint _anjay_output_ctx_destroy_and_process_result(\n        anjay_unlocked_output_ctx_t **out_ctx_ptr, int result);\n\n#ifdef ANJAY_WITH_LWM2M12\nint _anjay_input_get_null(anjay_unlocked_input_ctx_t *ctx);\n#endif // ANJAY_WITH_LWM2M12\n\nint _anjay_input_next_entry(anjay_unlocked_input_ctx_t *ctx);\nint _anjay_input_get_path(anjay_unlocked_input_ctx_t *ctx,\n                          anjay_uri_path_t *uri_path,\n                          bool *out_is_array);\nint _anjay_input_update_root_path(anjay_unlocked_input_ctx_t *ctx,\n                                  const anjay_uri_path_t *root_path);\n\ntypedef struct anjay_output_buf_ctx {\n    anjay_unlocked_output_ctx_t base;\n    const void *ret_bytes_vtable;\n    avs_stream_t *stream;\n} anjay_output_buf_ctx_t;\n\nanjay_output_buf_ctx_t _anjay_output_buf_ctx_init(avs_stream_t *stream);\n\ntypedef struct anjay_input_buf_ctx {\n    anjay_unlocked_input_ctx_t base;\n    avs_stream_t *stream;\n    bool msg_finished;\n    anjay_uri_path_t path;\n} anjay_input_buf_ctx_t;\n\n#ifdef ANJAY_WITH_LWM2M11\nanjay_input_buf_ctx_t _anjay_input_buf_ctx_init(avs_stream_t *stream,\n                                                anjay_uri_path_t *path);\n#endif // ANJAY_WITH_LWM2M11\n\nbool _anjay_is_supported_hierarchical_format(uint16_t content_format);\n\nuint16_t _anjay_default_hierarchical_format(anjay_lwm2m_version_t version);\n\nuint16_t _anjay_default_simple_format(anjay_unlocked_t *anjay,\n                                      anjay_lwm2m_version_t version);\n\n#ifdef ANJAY_WITH_SEND\nint _anjay_output_dynamic_send_construct(anjay_unlocked_output_ctx_t **out_ctx,\n                                         avs_stream_t *stream,\n                                         const anjay_uri_path_t *uri,\n                                         uint16_t format,\n                                         const size_t *items_count);\n#endif // ANJAY_WITH_SEND\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif /* ANJAY_IO_CORE_H */\n"
  },
  {
    "path": "src/core/anjay_io_utils.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#include <string.h>\n\n#include <anjay_modules/anjay_io_utils.h>\n\n#include \"anjay_io_core.h\"\n\nVISIBILITY_SOURCE_BEGIN\n\ntypedef int chunk_getter_t(anjay_unlocked_input_ctx_t *ctx,\n                           char *out,\n                           size_t out_size,\n                           bool *out_finished,\n                           size_t *out_bytes_read);\n\nstatic int bytes_getter(anjay_unlocked_input_ctx_t *ctx,\n                        char *out,\n                        size_t size,\n                        bool *out_finished,\n                        size_t *out_bytes_read) {\n    return _anjay_get_bytes_unlocked(ctx, out_bytes_read, out_finished, out,\n                                     size);\n}\n\nstatic int string_getter(anjay_unlocked_input_ctx_t *ctx,\n                         char *out,\n                         size_t size,\n                         bool *out_finished,\n                         size_t *out_bytes_read) {\n    int result = _anjay_get_string_unlocked(ctx, out, size);\n    if (result < 0) {\n        return result;\n    }\n    *out_finished = true;\n    *out_bytes_read = strlen(out) + 1;\n    if (result == ANJAY_BUFFER_TOO_SHORT) {\n        *out_finished = false;\n        /**\n         * We don't want null terminator, because we're still in the phase of\n         * string chunk concatenation (and null terminators in the middle of\n         * the string are rather bad).\n         */\n        --*out_bytes_read;\n    }\n    return 0;\n}\n\nstatic int generic_getter(anjay_unlocked_input_ctx_t *ctx,\n                          char **out,\n                          size_t *out_bytes_read,\n                          chunk_getter_t *getter) {\n    char tmp[128];\n    bool finished = false;\n    char *buffer = NULL;\n    size_t buffer_size = 0;\n    int result;\n    do {\n        size_t chunk_bytes_read = 0;\n        if ((result = getter(ctx, tmp, sizeof(tmp), &finished,\n                             &chunk_bytes_read))) {\n            goto error;\n        }\n        if (chunk_bytes_read > 0) {\n            char *bigger_buffer =\n                    (char *) avs_realloc(buffer,\n                                         buffer_size + chunk_bytes_read);\n            if (!bigger_buffer) {\n                result = ANJAY_ERR_INTERNAL;\n                goto error;\n            }\n            memcpy(bigger_buffer + buffer_size, tmp, chunk_bytes_read);\n            buffer = bigger_buffer;\n            buffer_size += chunk_bytes_read;\n        }\n    } while (!finished);\n    *out = buffer;\n    *out_bytes_read = buffer_size;\n    return 0;\nerror:\n    avs_free(buffer);\n    return result;\n}\n\nint _anjay_io_fetch_bytes(anjay_unlocked_input_ctx_t *ctx,\n                          anjay_raw_buffer_t *buffer) {\n    _anjay_raw_buffer_clear(buffer);\n    int retval = generic_getter(ctx, (char **) &buffer->data, &buffer->size,\n                                bytes_getter);\n    buffer->capacity = buffer->size;\n    return retval;\n}\n\nint _anjay_io_fetch_string(anjay_unlocked_input_ctx_t *ctx, char **out) {\n    avs_free(*out);\n    *out = NULL;\n    size_t bytes_read = 0;\n    return generic_getter(ctx, out, &bytes_read, string_getter);\n}\n"
  },
  {
    "path": "src/core/anjay_lwm2m_send.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#ifdef ANJAY_WITH_LWM2M11\n\n#    include <inttypes.h>\n\n#    include <avsystem/commons/avs_stream_membuf.h>\n#    include <avsystem/commons/avs_utils.h>\n\n#    include <avsystem/coap/async_client.h>\n#    include <avsystem/coap/code.h>\n\n#    include <anjay/lwm2m_send.h>\n\n#    ifdef ANJAY_WITH_LWM2M_GATEWAY\n#        include <anjay/lwm2m_gateway.h>\n\n#        include \"../anjay_modules/anjay_lwm2m_gateway.h\"\n#    endif // ANJAY_WITH_LWM2M_GATEWAY\n\n#    include \"anjay_access_utils_private.h\"\n#    include \"anjay_core.h\"\n#    include \"anjay_io_core.h\"\n#    include \"anjay_lwm2m_send.h\"\n#    include \"anjay_servers_utils.h\"\n#    include \"coap/anjay_content_format.h\"\n#    include \"dm/anjay_query.h\"\n#    include \"io/anjay_batch_builder.h\"\n\n#    define ANJAY_LWM2M_SEND_SOURCE\n\n#    include \"anjay_servers_reload.h\"\n\nVISIBILITY_SOURCE_BEGIN\n\n#    define send_log(...) _anjay_log(anjay_send, __VA_ARGS__)\n\n#    ifdef ANJAY_WITH_SEND\n\n// Path for LwM2M Send requests defined by spec\n#        define ANJAY_SEND_URI_PATH \"dp\"\n\nstatic inline anjay_batch_builder_t *\ncast_to_builder(anjay_send_batch_builder_t *builder) {\n    return (anjay_batch_builder_t *) builder;\n}\n\nstatic inline anjay_send_batch_builder_t *\ncast_to_send_builder(anjay_batch_builder_t *builder) {\n    return (anjay_send_batch_builder_t *) builder;\n}\n\nstatic inline anjay_batch_t *cast_to_batch(anjay_send_batch_t *batch) {\n    return (anjay_batch_t *) batch;\n}\n\nstatic inline anjay_send_batch_t *cast_to_send_batch(anjay_batch_t *batch) {\n    return (anjay_send_batch_t *) batch;\n}\n\nstatic inline const anjay_batch_t *\ncast_to_const_batch(const anjay_send_batch_t *batch) {\n    return (const anjay_batch_t *) batch;\n}\n\nstatic inline const anjay_send_batch_t *\ncast_to_const_send_batch(const anjay_batch_t *batch) {\n    return (const anjay_send_batch_t *) batch;\n}\n\ntypedef struct {\n    avs_coap_exchange_id_t id;\n    avs_stream_t *memstream;\n    anjay_unlocked_output_ctx_t *out_ctx;\n    size_t expected_offset;\n    avs_time_real_t serialization_time;\n    const anjay_batch_data_output_state_t *output_state;\n} exchange_status_t;\n\nstruct anjay_send_entry {\n    anjay_unlocked_t *anjay;\n    anjay_send_finished_handler_t *finished_handler;\n    void *finished_handler_data;\n    anjay_ssid_t target_ssid;\n    bool deferrable;\n    anjay_batch_t *payload_batch;\n    exchange_status_t exchange_status;\n};\n\nstatic void clear_exchange_status(exchange_status_t *status) {\n    assert(!avs_coap_exchange_id_valid(status->id));\n    _anjay_output_ctx_destroy(&status->out_ctx);\n    avs_stream_cleanup(&status->memstream);\n    status->output_state = NULL;\n}\n\nstatic void delete_send_entry(AVS_LIST(anjay_send_entry_t) *entry) {\n    _anjay_batch_release(&(*entry)->payload_batch);\n    clear_exchange_status(&(*entry)->exchange_status);\n    AVS_LIST_DELETE(entry);\n}\n\nstatic avs_error_t setup_send_options(avs_coap_options_t *options,\n                                      const anjay_url_t *server_uri,\n                                      uint16_t content_format) {\n    avs_error_t err;\n    (void) (avs_is_err((err = _anjay_coap_add_string_options(\n                                options, server_uri->uri_path,\n                                AVS_COAP_OPTION_URI_PATH)))\n            || avs_is_err((err = avs_coap_options_add_string(\n                                   options, AVS_COAP_OPTION_URI_PATH,\n                                   ANJAY_SEND_URI_PATH)))\n            || avs_is_err((err = avs_coap_options_set_content_format(\n                                   options, content_format)))\n            || avs_is_err((err = _anjay_coap_add_string_options(\n                                   options, server_uri->uri_query,\n                                   AVS_COAP_OPTION_URI_QUERY))));\n    return err;\n}\n\nstatic int request_payload_writer(size_t payload_offset,\n                                  void *payload_buf,\n                                  size_t payload_buf_size,\n                                  size_t *out_payload_chunk_size,\n                                  void *entry_) {\n    anjay_send_entry_t *entry = (anjay_send_entry_t *) entry_;\n    if (payload_offset != entry->exchange_status.expected_offset) {\n        send_log(DEBUG,\n                 _(\"Server requested unexpected chunk of payload (expected \"\n                   \"offset \") \"%u\" _(\", got \") \"%u\" _(\")\"),\n                 (unsigned) entry->exchange_status.expected_offset,\n                 (unsigned) payload_offset);\n        return -1;\n    }\n\n    char *write_ptr = (char *) payload_buf;\n    const char *end_ptr = write_ptr + payload_buf_size;\n    while (true) {\n        size_t bytes_read;\n        if (avs_is_err(avs_stream_read(entry->exchange_status.memstream,\n                                       &bytes_read, NULL, write_ptr,\n                                       (size_t) (end_ptr - write_ptr)))) {\n            return -1;\n        }\n        write_ptr += bytes_read;\n\n        // NOTE: (output_state == NULL && out_ctx != NULL) means start of\n        // iteration; out_ctx is cleaned up at the end of iteration, so\n        // (output_state == NULL && out_ctx == NULL) means end of iteration\n        if (write_ptr >= end_ptr || !entry->exchange_status.out_ctx) {\n            break;\n        }\n        int result = _anjay_batch_data_output_entry(\n                entry->anjay, entry->payload_batch, entry->target_ssid,\n                entry->exchange_status.serialization_time,\n                &entry->exchange_status.output_state,\n                entry->exchange_status.out_ctx);\n        if (!result && !entry->exchange_status.output_state) {\n            result = _anjay_output_ctx_destroy_and_process_result(\n                    &entry->exchange_status.out_ctx, result);\n        }\n        if (result) {\n            return result;\n        }\n    }\n    *out_payload_chunk_size = (size_t) (write_ptr - (char *) payload_buf);\n    entry->exchange_status.expected_offset += *out_payload_chunk_size;\n    return 0;\n}\n\nstatic void call_finished_handler(anjay_send_entry_t *entry, int result) {\n    if (!entry->finished_handler) {\n        return;\n    }\n    anjay_send_finished_handler_t *handler = entry->finished_handler;\n    void *handler_data = entry->finished_handler_data;\n    anjay_unlocked_t *anjay = entry->anjay;\n    anjay_ssid_t target_ssid = entry->target_ssid;\n    const anjay_send_batch_t *batch =\n            cast_to_const_send_batch(entry->payload_batch);\n    // Prevent finished_handler from being called again\n    entry->finished_handler = NULL;\n\n    ANJAY_MUTEX_UNLOCK_FOR_CALLBACK(anjay_locked, anjay);\n    handler(anjay_locked, target_ssid, batch, result, handler_data);\n    ANJAY_MUTEX_LOCK_AFTER_CALLBACK(anjay_locked);\n}\n\nstatic void response_handler(avs_coap_ctx_t *ctx,\n                             avs_coap_exchange_id_t exchange_id,\n                             avs_coap_client_request_state_t state,\n                             const avs_coap_client_async_response_t *response,\n                             avs_error_t err,\n                             void *entry_) {\n    anjay_send_entry_t *entry = (anjay_send_entry_t *) entry_;\n    assert(entry);\n    assert(avs_coap_exchange_id_equal(exchange_id, entry->exchange_status.id));\n    if (avs_is_ok(err)) {\n        anjay_server_info_t *server =\n                _anjay_servers_find_active(entry->anjay, entry->target_ssid);\n        if (server) {\n            _anjay_connection_mark_stable((anjay_connection_ref_t) {\n                .server = server,\n                .conn_type = ANJAY_CONNECTION_PRIMARY\n            });\n        }\n    }\n    if (entry->finished_handler) {\n        static const int STATE_TO_RESULT[] = {\n            [AVS_COAP_CLIENT_REQUEST_OK] = ANJAY_SEND_SUCCESS,\n            [AVS_COAP_CLIENT_REQUEST_PARTIAL_CONTENT] = ANJAY_SEND_SUCCESS,\n            [AVS_COAP_CLIENT_REQUEST_FAIL] = ANJAY_SEND_TIMEOUT,\n            [AVS_COAP_CLIENT_REQUEST_CANCEL] = ANJAY_SEND_ABORT\n        };\n        assert(state >= 0 && state < AVS_ARRAY_SIZE(STATE_TO_RESULT));\n        int result = STATE_TO_RESULT[state];\n        if (result == ANJAY_SEND_SUCCESS) {\n            if (response->header.code != AVS_COAP_CODE_CHANGED) {\n                result = -response->header.code;\n            } else if (response->payload_size) {\n                send_log(WARNING,\n                         _(\"Unexpected payload received in response to Send\"));\n            }\n        }\n        call_finished_handler(entry, result);\n    }\n    if (state == AVS_COAP_CLIENT_REQUEST_PARTIAL_CONTENT) {\n        // We don't want/need to read the rest of the content, so we cancel the\n        // exchange. Note that this will call this handler again with state set\n        // to AVS_COAP_CLIENT_REQUEST_CANCEL.\n        avs_coap_exchange_cancel(ctx, exchange_id);\n    } else {\n        entry->exchange_status.id = AVS_COAP_EXCHANGE_ID_INVALID;\n        AVS_LIST(anjay_send_entry_t) *entry_ptr =\n                (AVS_LIST(anjay_send_entry_t) *) AVS_LIST_FIND_PTR(\n                        &entry->anjay->sender.entries, entry);\n        assert(entry_ptr);\n        assert(*entry_ptr == entry);\n        delete_send_entry(entry_ptr);\n    }\n}\n\nstatic AVS_LIST(anjay_send_entry_t) *\ncreate_exchange(anjay_unlocked_t *anjay,\n                anjay_ssid_t target_ssid,\n                bool deferrable,\n                anjay_send_finished_handler_t *finished_handler,\n                void *finished_handler_data,\n                const anjay_send_batch_t *batch) {\n    anjay_batch_t *payload_batch =\n            _anjay_batch_acquire(cast_to_const_batch(batch));\n    if (!payload_batch) {\n        send_log(ERROR, _(\"could not acquire batch\"));\n    }\n\n    AVS_LIST(anjay_send_entry_t) entry =\n            AVS_LIST_NEW_ELEMENT(anjay_send_entry_t);\n    if (!entry) {\n        _anjay_log_oom();\n        return NULL;\n    }\n    entry->anjay = anjay;\n    entry->finished_handler = finished_handler;\n    entry->finished_handler_data = finished_handler_data;\n    entry->target_ssid = target_ssid;\n    entry->deferrable = deferrable;\n    entry->payload_batch = payload_batch;\n\n    AVS_LIST(anjay_send_entry_t) *insert_ptr = &anjay->sender.entries;\n    while (*insert_ptr && (*insert_ptr)->target_ssid < target_ssid) {\n        AVS_LIST_ADVANCE_PTR(&insert_ptr);\n    }\n    AVS_LIST_INSERT(insert_ptr, entry);\n\n    return insert_ptr;\n}\n\nstatic avs_error_t start_send_exchange(anjay_send_entry_t *entry,\n                                       anjay_connection_ref_t connection) {\n    assert(!avs_coap_exchange_id_valid(entry->exchange_status.id));\n    assert(!entry->exchange_status.memstream);\n    assert(!entry->exchange_status.out_ctx);\n    assert(!entry->exchange_status.output_state);\n\n    assert(connection.server);\n    assert(_anjay_server_ssid(connection.server) == entry->target_ssid);\n\n    if (!_anjay_connection_get_online_socket(connection)) {\n        if (_anjay_schedule_refresh_server(connection.server,\n                                           AVS_TIME_DURATION_ZERO)) {\n            return avs_errno(AVS_ENOMEM);\n        } else {\n            // once the connection is up, _anjay_send_sched_retry_deferred()\n            // will be called; we're done here\n            return AVS_OK;\n        }\n    }\n\n    avs_coap_ctx_t *coap = _anjay_connection_get_coap(connection);\n    if (!coap) {\n        return avs_errno(AVS_EBADF);\n    }\n\n    uint16_t content_format =\n#        if defined(ANJAY_DEFAULT_SEND_FORMAT) \\\n                && ANJAY_DEFAULT_SEND_FORMAT != AVS_COAP_FORMAT_NONE\n            ANJAY_DEFAULT_SEND_FORMAT\n#        else  // defined(ANJAY_DEFAULT_SEND_FORMAT)\n               // && ANJAY_DEFAULT_SEND_FORMAT != AVS_COAP_FORMAT_NONE\n            _anjay_default_hierarchical_format(\n                    _anjay_server_registration_info(connection.server)\n                            ->lwm2m_version)\n#        endif // defined(ANJAY_DEFAULT_SEND_FORMAT)\n               // && ANJAY_DEFAULT_SEND_FORMAT != AVS_COAP_FORMAT_NONE\n            ;\n\n    const anjay_url_t *server_uri = _anjay_connection_uri(connection);\n    assert(server_uri);\n\n    avs_coap_request_header_t request = {\n        .code = AVS_COAP_CODE_POST\n    };\n\n    anjay_uri_path_t base_path = MAKE_ROOT_PATH();\n    _anjay_batch_update_common_path_prefix(&(const anjay_uri_path_t *) { NULL },\n                                           &base_path, entry->payload_batch);\n\n    avs_error_t err;\n    if (avs_is_err((err = avs_coap_options_dynamic_init(&request.options)))\n            || avs_is_err(\n                       (err = setup_send_options(&request.options, server_uri,\n                                                 content_format)))) {\n        goto finish;\n    }\n\n    size_t item_count;\n    if (!(entry->exchange_status.memstream = avs_stream_membuf_create())\n            || (_anjay_output_dynamic_send_construct(\n                       &entry->exchange_status.out_ctx,\n                       entry->exchange_status.memstream, &base_path,\n                       content_format,\n                       _anjay_batch_outputable_item_count(\n                               entry->anjay, entry->payload_batch,\n                               entry->target_ssid, &item_count)\n                               ? NULL\n                               : &item_count))) {\n        send_log(ERROR, _(\"could not create output context\"));\n        err = avs_errno(AVS_ENOMEM);\n        goto finish;\n    }\n    entry->exchange_status.expected_offset = 0;\n    entry->exchange_status.serialization_time = avs_time_real_now();\n\n    err = avs_coap_client_send_async_request(coap, &entry->exchange_status.id,\n                                             &request, request_payload_writer,\n                                             entry, response_handler, entry);\n    _anjay_connection_schedule_queue_mode_close(connection);\nfinish:\n    avs_coap_options_cleanup(&request.options);\n    if (avs_is_err(err)) {\n        clear_exchange_status(&entry->exchange_status);\n#        ifdef ANJAY_WITH_COMMUNICATION_TIMESTAMP_API\n    } else {\n        _anjay_server_set_last_communication_time(connection.server);\n#        endif // ANJAY_WITH_COMMUNICATION_TIMESTAMP_API\n    }\n    return err;\n}\n\nstatic bool is_deferrable_condition(anjay_send_result_t condition) {\n    return condition == ANJAY_SEND_ERR_OFFLINE\n           || condition == ANJAY_SEND_ERR_BOOTSTRAP;\n}\n\nstatic anjay_send_result_t\ncheck_send_possibility(anjay_unlocked_t *anjay,\n                       anjay_ssid_t ssid,\n                       anjay_connection_ref_t *out_ref) {\n    anjay_iid_t server_iid;\n    if (_anjay_find_server_iid(anjay, ssid, &server_iid)) {\n        return ANJAY_SEND_ERR_SSID;\n    }\n\n    bool is_lwm2m_send_muted;\n    if (_anjay_dm_read_resource_bool(\n                anjay,\n                &MAKE_RESOURCE_PATH(ANJAY_DM_OID_SERVER,\n                                    server_iid,\n                                    ANJAY_DM_RID_SERVER_MUTE_SEND),\n                &is_lwm2m_send_muted)\n            || is_lwm2m_send_muted) {\n        return ANJAY_SEND_ERR_MUTED;\n    }\n\n    if (_anjay_bootstrap_in_progress(anjay)) {\n        send_log(DEBUG, _(\"Cannot perform LwM2M Send during bootstrap\"));\n        return ANJAY_SEND_ERR_BOOTSTRAP;\n    }\n\n    out_ref->conn_type = ANJAY_CONNECTION_PRIMARY;\n    out_ref->server = _anjay_servers_find_active(anjay, ssid);\n    if (!out_ref->server\n            || !_anjay_connection_ready_for_outgoing_message(*out_ref)\n            || !_anjay_socket_transport_is_online(\n                       anjay, _anjay_connection_transport(*out_ref))) {\n        send_log(DEBUG,\n                 _(\"SSID \") \"%u\" _(\" does not refer to a server connection \"\n                                   \"that is currently online\"),\n                 ssid);\n        return ANJAY_SEND_ERR_OFFLINE;\n    } else if (_anjay_server_registration_info(out_ref->server)->lwm2m_version\n               < ANJAY_LWM2M_VERSION_1_1) {\n        send_log(\n                DEBUG,\n                _(\"Server SSID \") \"%u\" _(\n                        \" is registered with LwM2M version \") \"%s\" _(\", which \"\n                                                                     \"does not \"\n                                                                     \"support \"\n                                                                     \"Send\"),\n                ssid,\n                _anjay_lwm2m_version_as_string(\n                        _anjay_server_registration_info(out_ref->server)\n                                ->lwm2m_version));\n        return ANJAY_SEND_ERR_PROTOCOL;\n    }\n\n    return ANJAY_SEND_OK;\n}\n\nstatic anjay_send_result_t\nsend_impl(anjay_unlocked_t *anjay,\n          anjay_ssid_t ssid,\n          bool deferrable,\n          const anjay_send_batch_t *data,\n          anjay_send_finished_handler_t *finished_handler,\n          void *finished_handler_data) {\n    anjay_connection_ref_t ref = {\n        .server = NULL\n    };\n    anjay_send_result_t result = check_send_possibility(anjay, ssid, &ref);\n    bool should_defer = (deferrable && is_deferrable_condition(result));\n    if (result != ANJAY_SEND_OK && !should_defer) {\n        return result;\n    }\n\n    AVS_LIST(anjay_send_entry_t) *entry_ptr =\n            create_exchange(anjay, ssid, deferrable, finished_handler,\n                            finished_handler_data, data);\n    if (!entry_ptr || !*entry_ptr) {\n        return ANJAY_SEND_ERR_INTERNAL;\n    }\n\n    if (!should_defer) {\n        assert(ref.server);\n        if (avs_is_err(start_send_exchange(*entry_ptr, ref))) {\n            delete_send_entry(entry_ptr);\n            return ANJAY_SEND_ERR_INTERNAL;\n        }\n    }\n    return ANJAY_SEND_OK;\n}\n\nanjay_send_result_t\n_anjay_send_deferrable_unlocked(anjay_unlocked_t *anjay,\n                                anjay_ssid_t ssid,\n                                const anjay_send_batch_t *data,\n                                anjay_send_finished_handler_t *finished_handler,\n                                void *finished_handler_data) {\n    return send_impl(anjay, ssid, true, data, finished_handler,\n                     finished_handler_data);\n}\n\nanjay_send_result_t\nanjay_send_deferrable(anjay_t *anjay_locked,\n                      anjay_ssid_t ssid,\n                      const anjay_send_batch_t *data,\n                      anjay_send_finished_handler_t *finished_handler,\n                      void *finished_handler_data) {\n    anjay_send_result_t result = ANJAY_SEND_ERR_INTERNAL;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    result =\n            _anjay_send_deferrable_unlocked(anjay, ssid, data, finished_handler,\n                                            finished_handler_data);\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return result;\n}\n\nanjay_send_result_t anjay_send(anjay_t *anjay_locked,\n                               anjay_ssid_t ssid,\n                               const anjay_send_batch_t *data,\n                               anjay_send_finished_handler_t *finished_handler,\n                               void *finished_handler_data) {\n    anjay_send_result_t result = ANJAY_SEND_ERR_INTERNAL;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    result = send_impl(anjay, ssid, false, data, finished_handler,\n                       finished_handler_data);\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return result;\n}\n\nanjay_send_batch_builder_t *anjay_send_batch_builder_new(void) {\n    return cast_to_send_builder(_anjay_batch_builder_new());\n}\n\nvoid anjay_send_batch_builder_cleanup(\n        anjay_send_batch_builder_t **builder_ptr) {\n    anjay_batch_builder_t *builder = cast_to_builder(*builder_ptr);\n    _anjay_batch_builder_cleanup(&builder);\n    assert(!builder);\n    *builder_ptr = NULL;\n}\n\nint anjay_send_batch_add_int(anjay_send_batch_builder_t *builder,\n                             anjay_oid_t oid,\n                             anjay_iid_t iid,\n                             anjay_rid_t rid,\n                             anjay_riid_t riid,\n                             avs_time_real_t timestamp,\n                             int64_t value) {\n    return _anjay_batch_add_int(cast_to_builder(builder),\n                                &MAKE_RESOURCE_INSTANCE_PATH(oid, iid, rid,\n                                                             riid),\n                                timestamp, value);\n}\n\nint anjay_send_batch_add_uint(anjay_send_batch_builder_t *builder,\n                              anjay_oid_t oid,\n                              anjay_iid_t iid,\n                              anjay_rid_t rid,\n                              anjay_riid_t riid,\n                              avs_time_real_t timestamp,\n                              uint64_t value) {\n    return _anjay_batch_add_uint(cast_to_builder(builder),\n                                 &MAKE_RESOURCE_INSTANCE_PATH(oid, iid, rid,\n                                                              riid),\n                                 timestamp, value);\n}\n\nint anjay_send_batch_add_double(anjay_send_batch_builder_t *builder,\n                                anjay_oid_t oid,\n                                anjay_iid_t iid,\n                                anjay_rid_t rid,\n                                anjay_riid_t riid,\n                                avs_time_real_t timestamp,\n                                double value) {\n    return _anjay_batch_add_double(cast_to_builder(builder),\n                                   &MAKE_RESOURCE_INSTANCE_PATH(oid, iid, rid,\n                                                                riid),\n                                   timestamp, value);\n}\n\nint anjay_send_batch_add_bool(anjay_send_batch_builder_t *builder,\n                              anjay_oid_t oid,\n                              anjay_iid_t iid,\n                              anjay_rid_t rid,\n                              anjay_riid_t riid,\n                              avs_time_real_t timestamp,\n                              bool value) {\n    return _anjay_batch_add_bool(cast_to_builder(builder),\n                                 &MAKE_RESOURCE_INSTANCE_PATH(oid, iid, rid,\n                                                              riid),\n                                 timestamp, value);\n}\n\nint anjay_send_batch_add_string(anjay_send_batch_builder_t *builder,\n                                anjay_oid_t oid,\n                                anjay_iid_t iid,\n                                anjay_rid_t rid,\n                                anjay_riid_t riid,\n                                avs_time_real_t timestamp,\n                                const char *str) {\n    return _anjay_batch_add_string(\n            cast_to_builder(builder),\n            &MAKE_RESOURCE_INSTANCE_PATH(oid, iid, rid, riid), timestamp, str);\n}\n\nint anjay_send_batch_add_bytes(anjay_send_batch_builder_t *builder,\n                               anjay_oid_t oid,\n                               anjay_iid_t iid,\n                               anjay_rid_t rid,\n                               anjay_riid_t riid,\n                               avs_time_real_t timestamp,\n                               const void *data,\n                               size_t length) {\n    return _anjay_batch_add_bytes(cast_to_builder(builder),\n                                  &MAKE_RESOURCE_INSTANCE_PATH(oid, iid, rid,\n                                                               riid),\n                                  timestamp, data, length);\n}\n\nint anjay_send_batch_add_objlnk(anjay_send_batch_builder_t *builder,\n                                anjay_oid_t oid,\n                                anjay_iid_t iid,\n                                anjay_rid_t rid,\n                                anjay_riid_t riid,\n                                avs_time_real_t timestamp,\n                                anjay_oid_t objlnk_oid,\n                                anjay_iid_t objlnk_iid) {\n    return _anjay_batch_add_objlnk(cast_to_builder(builder),\n                                   &MAKE_RESOURCE_INSTANCE_PATH(oid, iid, rid,\n                                                                riid),\n                                   timestamp, objlnk_oid, objlnk_iid);\n}\n\n#        ifdef ANJAY_WITH_LWM2M_GATEWAY\nint anjay_lwm2m_gateway_send_batch_add_int(anjay_send_batch_builder_t *builder,\n                                           anjay_iid_t gateway_iid,\n                                           anjay_oid_t oid,\n                                           anjay_iid_t iid,\n                                           anjay_rid_t rid,\n                                           anjay_riid_t riid,\n                                           avs_time_real_t timestamp,\n                                           int64_t value) {\n    anjay_uri_path_t path;\n    _anjay_uri_path_with_prefix_from_end_device_iid(&path, gateway_iid, oid,\n                                                    iid, rid, riid);\n    return _anjay_batch_add_int(cast_to_builder(builder), &path, timestamp,\n                                value);\n}\n\nint anjay_lwm2m_gateway_send_batch_add_uint(anjay_send_batch_builder_t *builder,\n                                            anjay_iid_t gateway_iid,\n                                            anjay_oid_t oid,\n                                            anjay_iid_t iid,\n                                            anjay_rid_t rid,\n                                            anjay_riid_t riid,\n                                            avs_time_real_t timestamp,\n                                            uint64_t value) {\n    anjay_uri_path_t path;\n    _anjay_uri_path_with_prefix_from_end_device_iid(&path, gateway_iid, oid,\n                                                    iid, rid, riid);\n    return _anjay_batch_add_uint(cast_to_builder(builder), &path, timestamp,\n                                 value);\n}\n\nint anjay_lwm2m_gateway_send_batch_add_double(\n        anjay_send_batch_builder_t *builder,\n        anjay_iid_t gateway_iid,\n        anjay_oid_t oid,\n        anjay_iid_t iid,\n        anjay_rid_t rid,\n        anjay_riid_t riid,\n        avs_time_real_t timestamp,\n        double value) {\n    anjay_uri_path_t path;\n    _anjay_uri_path_with_prefix_from_end_device_iid(&path, gateway_iid, oid,\n                                                    iid, rid, riid);\n    return _anjay_batch_add_double(cast_to_builder(builder), &path, timestamp,\n                                   value);\n}\n\nint anjay_lwm2m_gateway_send_batch_add_bool(anjay_send_batch_builder_t *builder,\n                                            anjay_iid_t gateway_iid,\n                                            anjay_oid_t oid,\n                                            anjay_iid_t iid,\n                                            anjay_rid_t rid,\n                                            anjay_riid_t riid,\n                                            avs_time_real_t timestamp,\n                                            bool value) {\n    anjay_uri_path_t path;\n    _anjay_uri_path_with_prefix_from_end_device_iid(&path, gateway_iid, oid,\n                                                    iid, rid, riid);\n    return _anjay_batch_add_bool(cast_to_builder(builder), &path, timestamp,\n                                 value);\n}\n\nint anjay_lwm2m_gateway_send_batch_add_string(\n        anjay_send_batch_builder_t *builder,\n        anjay_iid_t gateway_iid,\n        anjay_oid_t oid,\n        anjay_iid_t iid,\n        anjay_rid_t rid,\n        anjay_riid_t riid,\n        avs_time_real_t timestamp,\n        const char *str) {\n    anjay_uri_path_t path;\n    _anjay_uri_path_with_prefix_from_end_device_iid(&path, gateway_iid, oid,\n                                                    iid, rid, riid);\n    return _anjay_batch_add_string(cast_to_builder(builder), &path, timestamp,\n                                   str);\n}\n\nint anjay_lwm2m_gateway_send_batch_add_bytes(\n        anjay_send_batch_builder_t *builder,\n        anjay_iid_t gateway_iid,\n        anjay_oid_t oid,\n        anjay_iid_t iid,\n        anjay_rid_t rid,\n        anjay_riid_t riid,\n        avs_time_real_t timestamp,\n        const void *data,\n        size_t length) {\n    anjay_uri_path_t path;\n    _anjay_uri_path_with_prefix_from_end_device_iid(&path, gateway_iid, oid,\n                                                    iid, rid, riid);\n    return _anjay_batch_add_bytes(cast_to_builder(builder), &path, timestamp,\n                                  data, length);\n}\n\nint anjay_lwm2m_gateway_send_batch_add_objlnk(\n        anjay_send_batch_builder_t *builder,\n        anjay_iid_t gateway_iid,\n        anjay_oid_t oid,\n        anjay_iid_t iid,\n        anjay_rid_t rid,\n        anjay_riid_t riid,\n        avs_time_real_t timestamp,\n        anjay_oid_t objlnk_oid,\n        anjay_iid_t objlnk_iid) {\n    anjay_uri_path_t path;\n    _anjay_uri_path_with_prefix_from_end_device_iid(&path, gateway_iid, oid,\n                                                    iid, rid, riid);\n    return _anjay_batch_add_objlnk(cast_to_builder(builder), &path, timestamp,\n                                   objlnk_oid, objlnk_iid);\n}\n\n#        endif // ANJAY_WITH_LWM2M_GATEWAY\n\nstatic int\nbatch_data_add_current_impl(anjay_send_batch_builder_t *builder,\n                            anjay_unlocked_t *anjay,\n                            const anjay_dm_t *dm,\n                            anjay_iid_t gateway_iid,\n                            anjay_oid_t oid,\n                            anjay_iid_t iid,\n                            anjay_rid_t rid,\n                            const avs_time_real_t *forced_timestamp) {\n    (void) gateway_iid;\n    assert(builder);\n    assert(anjay);\n\n    if (iid == ANJAY_ID_INVALID || rid == ANJAY_ID_INVALID) {\n        return -1;\n    }\n\n    const anjay_dm_installed_object_t *obj =\n            _anjay_dm_find_object_by_oid(dm, oid);\n    if (!obj) {\n#        ifdef ANJAY_WITH_LWM2M_GATEWAY\n        send_log(ERROR, _(\"unregistered Object ID: \") \"%s/%u\",\n                 obj->prefix ? obj->prefix : \"\", oid);\n#        else  // ANJAY_WITH_LWM2M_GATEWAY\n        send_log(ERROR, _(\"unregistered Object ID: \") \"%u\", oid);\n#        endif // ANJAY_WITH_LWM2M_GATEWAY\n        return ANJAY_ERR_NOT_FOUND;\n    }\n    anjay_dm_path_info_t path_info;\n    anjay_uri_path_t path;\n#        ifdef ANJAY_WITH_LWM2M_GATEWAY\n    if (gateway_iid != ANJAY_ID_INVALID) {\n        _anjay_uri_path_with_prefix_from_end_device_iid(\n                &path, gateway_iid, oid, iid, rid, ANJAY_ID_INVALID);\n    } else\n#        endif // ANJAY_WITH_LWM2M_GATEWAY\n    {\n        path = MAKE_RESOURCE_PATH(oid, iid, rid);\n    }\n    int result = _anjay_dm_path_info(anjay, obj, &path, &path_info);\n    if (result) {\n        return result;\n    }\n    return _anjay_dm_read_into_batch(cast_to_builder(builder), anjay, obj,\n                                     &path_info, ANJAY_SSID_BOOTSTRAP,\n                                     forced_timestamp);\n}\n\nstatic int\n_anjay_send_batch_data_add_current_unlocked(anjay_send_batch_builder_t *builder,\n                                            anjay_unlocked_t *anjay,\n                                            anjay_iid_t gateway_iid,\n                                            anjay_oid_t oid,\n                                            anjay_iid_t iid,\n                                            anjay_rid_t rid) {\n    const anjay_dm_t *dm;\n#        ifdef ANJAY_WITH_LWM2M_GATEWAY\n    if (gateway_iid != ANJAY_ID_INVALID) {\n        _anjay_lwm2m_gateway_iid_to_dm(anjay, gateway_iid, &dm);\n        if (!dm) {\n            dm_log(ERROR, _(\"No End Device with specified iid found\"));\n            return ANJAY_ERR_NOT_FOUND;\n        }\n    } else\n#        endif // ANJAY_WITH_LWM2M_GATEWAY\n    {\n        dm = &anjay->dm;\n    }\n\n    return batch_data_add_current_impl(builder, anjay, dm, gateway_iid, oid,\n                                       iid, rid, NULL);\n}\n\nint anjay_send_batch_data_add_current(anjay_send_batch_builder_t *builder,\n                                      anjay_t *anjay_locked,\n                                      anjay_oid_t oid,\n                                      anjay_iid_t iid,\n                                      anjay_rid_t rid) {\n    int result = -1;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    result = _anjay_send_batch_data_add_current_unlocked(\n            builder, anjay, ANJAY_ID_INVALID, oid, iid, rid);\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return result;\n}\n\nint _anjay_send_batch_data_add_current_multiple_unlocked(\n        anjay_send_batch_builder_t *builder,\n        anjay_unlocked_t *anjay,\n        anjay_iid_t gateway_iid,\n        const anjay_send_resource_path_t *paths,\n        size_t paths_length,\n        bool ignore_not_found) {\n    assert(builder);\n    assert(anjay);\n\n    anjay_batch_builder_t *batch_builder = cast_to_builder(builder);\n    AVS_LIST(anjay_batch_entry_t) *append_ptr = batch_builder->append_ptr;\n    avs_time_real_t timestamp = avs_time_real_now();\n\n    const anjay_dm_t *dm;\n    int result;\n#        ifdef ANJAY_WITH_LWM2M_GATEWAY\n    if (gateway_iid != ANJAY_ID_INVALID) {\n        _anjay_lwm2m_gateway_iid_to_dm(anjay, gateway_iid, &dm);\n        if (!dm) {\n            dm_log(ERROR, _(\"No End Device with specified iid found\"));\n            result = ANJAY_ERR_NOT_FOUND;\n            goto cleanup;\n        }\n    } else\n#        endif // ANJAY_WITH_LWM2M_GATEWAY\n    {\n        dm = &anjay->dm;\n    }\n\n    for (size_t i = 0; i < paths_length; i++) {\n        result = batch_data_add_current_impl(builder, anjay, dm, gateway_iid,\n                                             paths[i].oid, paths[i].iid,\n                                             paths[i].rid, &timestamp);\n        if (result == ANJAY_ERR_NOT_FOUND && ignore_not_found) {\n            anjay_uri_path_t uri_path = {\n                .ids = { paths[i].oid, paths[i].iid, paths[i].rid,\n                         ANJAY_ID_INVALID }\n            };\n#        ifdef ANJAY_WITH_LWM2M_GATEWAY\n            if (gateway_iid != ANJAY_ID_INVALID) {\n                avs_simple_snprintf(uri_path.prefix, sizeof(uri_path.prefix),\n                                    \"dev%\" PRIu16, gateway_iid);\n            }\n#        endif // ANJAY_WITH_LWM2M_GATEWAY\n            send_log(WARNING, _(\"resource \") \"/%s\" _(\" not found, ignoring\"),\n                     ANJAY_DEBUG_MAKE_PATH(&uri_path));\n        } else if (result) {\n            goto cleanup;\n        }\n    }\n    return 0;\n\ncleanup:\n    batch_builder->append_ptr = append_ptr;\n    _anjay_batch_entry_list_cleanup(batch_builder->append_ptr);\n    return result;\n}\n\nint anjay_send_batch_data_add_current_multiple(\n        anjay_send_batch_builder_t *builder,\n        anjay_t *anjay_locked,\n        const anjay_send_resource_path_t *paths,\n        size_t paths_length) {\n    int result = -1;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    result = _anjay_send_batch_data_add_current_multiple_unlocked(\n            builder, anjay, ANJAY_ID_INVALID, paths, paths_length, false);\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return result;\n}\n\nint anjay_send_batch_data_add_current_multiple_ignore_not_found(\n        anjay_send_batch_builder_t *builder,\n        anjay_t *anjay_locked,\n        const anjay_send_resource_path_t *paths,\n        size_t paths_length) {\n    int result = -1;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    result = _anjay_send_batch_data_add_current_multiple_unlocked(\n            builder, anjay, ANJAY_ID_INVALID, paths, paths_length, true);\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return result;\n}\n\n#        ifdef ANJAY_WITH_LWM2M_GATEWAY\n\nint anjay_lwm2m_gateway_send_batch_data_add_current(\n        anjay_send_batch_builder_t *builder,\n        anjay_t *anjay_locked,\n        anjay_iid_t gateway_iid,\n        anjay_oid_t oid,\n        anjay_iid_t iid,\n        anjay_rid_t rid) {\n    int result = -1;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    result = _anjay_send_batch_data_add_current_unlocked(\n            builder, anjay, gateway_iid, oid, iid, rid);\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return result;\n}\n\nint anjay_lwm2m_gateway_send_batch_data_add_current_multiple(\n        anjay_send_batch_builder_t *builder,\n        anjay_t *anjay_locked,\n        anjay_iid_t gateway_iid,\n        const anjay_send_resource_path_t *paths,\n        size_t paths_length) {\n    int result = -1;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    result = _anjay_send_batch_data_add_current_multiple_unlocked(\n            builder, anjay, gateway_iid, paths, paths_length, false);\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return result;\n}\n\nint anjay_lwm2m_gateway_send_batch_data_add_current_multiple_ignore_not_found(\n        anjay_send_batch_builder_t *builder,\n        anjay_t *anjay_locked,\n        anjay_iid_t gateway_iid,\n        const anjay_send_resource_path_t *paths,\n        size_t paths_length) {\n    int result = -1;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    result = _anjay_send_batch_data_add_current_multiple_unlocked(\n            builder, anjay, gateway_iid, paths, paths_length, true);\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return result;\n}\n\n#        endif // ANJAY_WITH_LWM2M_GATEWAY\n\nanjay_send_batch_t *\nanjay_send_batch_builder_compile(anjay_send_batch_builder_t **builder_ptr) {\n    anjay_batch_builder_t *builder = cast_to_builder(*builder_ptr);\n    anjay_send_batch_t *result =\n            cast_to_send_batch(_anjay_batch_builder_compile(&builder));\n    *builder_ptr = cast_to_send_builder(builder);\n    return result;\n}\n\nanjay_send_batch_t *anjay_send_batch_acquire(const anjay_send_batch_t *batch) {\n    return cast_to_send_batch(_anjay_batch_acquire(cast_to_const_batch(batch)));\n}\n\nvoid anjay_send_batch_release(anjay_send_batch_t **batch_ptr) {\n    anjay_batch_t *batch = cast_to_batch(*batch_ptr);\n    _anjay_batch_release(&batch);\n    assert(!batch);\n    *batch_ptr = NULL;\n}\n\nstatic void cancel_send_entry(AVS_LIST(anjay_send_entry_t) *entry_ptr,\n                              int result) {\n    call_finished_handler(*entry_ptr, result);\n    delete_send_entry(entry_ptr);\n}\n\nbool _anjay_send_in_progress(anjay_connection_ref_t ref) {\n    assert(ref.server);\n    avs_coap_ctx_t *coap = NULL;\n    if (ref.conn_type == ANJAY_CONNECTION_PRIMARY) {\n        coap = _anjay_connection_get_coap(ref);\n    }\n    if (!coap) {\n        return false;\n    }\n    AVS_LIST(anjay_send_entry_t) entry;\n    AVS_LIST_FOREACH(entry, _anjay_from_server(ref.server)->sender.entries) {\n        if (entry->target_ssid == _anjay_server_ssid(ref.server)\n                && avs_coap_exchange_id_valid(entry->exchange_status.id)) {\n            return true;\n        } else if (entry->target_ssid > _anjay_server_ssid(ref.server)) {\n            break;\n        }\n    }\n    return false;\n}\n\nvoid _anjay_send_interrupt(anjay_connection_ref_t ref) {\n    assert(ref.server);\n    avs_coap_ctx_t *coap = NULL;\n    if (ref.conn_type == ANJAY_CONNECTION_PRIMARY) {\n        coap = _anjay_connection_get_coap(ref);\n    }\n    if (!coap) {\n        return;\n    }\n    AVS_LIST(anjay_send_entry_t) *entry_ptr;\n    AVS_LIST(anjay_send_entry_t) helper;\n    AVS_LIST_DELETABLE_FOREACH_PTR(\n            entry_ptr, helper,\n            &_anjay_from_server(ref.server)->sender.entries) {\n        if ((*entry_ptr)->target_ssid == _anjay_server_ssid(ref.server)\n                && avs_coap_exchange_id_valid(\n                           (*entry_ptr)->exchange_status.id)) {\n            avs_coap_exchange_cancel(coap, (*entry_ptr)->exchange_status.id);\n        } else if ((*entry_ptr)->target_ssid > _anjay_server_ssid(ref.server)) {\n            break;\n        }\n    }\n}\n\nvoid _anjay_send_cleanup(anjay_sender_t *sender) {\n    while (sender->entries) {\n        cancel_send_entry(&sender->entries, ANJAY_SEND_ABORT);\n    }\n}\n\nstatic void retry_deferred_job(avs_sched_t *sched, const void *ssid_) {\n    anjay_t *anjay_locked = _anjay_get_from_sched(sched);\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    anjay_ssid_t ssid_or_any = *(const anjay_ssid_t *) ssid_;\n\n    anjay_ssid_t send_condition_ssid = ANJAY_SSID_ANY;\n    anjay_send_result_t send_condition = ANJAY_SEND_ERR_INTERNAL;\n    anjay_connection_ref_t connection = {\n        .server = NULL\n    };\n\n    AVS_LIST(anjay_send_entry_t) *entry_ptr;\n    AVS_LIST(anjay_send_entry_t) helper;\n    AVS_LIST_DELETABLE_FOREACH_PTR(entry_ptr, helper, &anjay->sender.entries) {\n        if ((*entry_ptr)->exchange_status.memstream) {\n            // Entry is not deferred\n            continue;\n        }\n\n        if (ssid_or_any != ANJAY_SSID_ANY) {\n            if ((*entry_ptr)->target_ssid < ssid_or_any) {\n                continue;\n            } else if ((*entry_ptr)->target_ssid > ssid_or_any) {\n                break;\n            }\n        }\n\n        assert((*entry_ptr)->target_ssid != ANJAY_SSID_ANY);\n        if (send_condition_ssid != (*entry_ptr)->target_ssid) {\n            send_condition_ssid = (*entry_ptr)->target_ssid;\n            send_condition = check_send_possibility(anjay, send_condition_ssid,\n                                                    &connection);\n        }\n\n        if ((send_condition != ANJAY_SEND_OK\n             && (!(*entry_ptr)->deferrable\n                 || !is_deferrable_condition(send_condition)))\n                || (send_condition == ANJAY_SEND_OK\n                    && avs_is_err(\n                               start_send_exchange(*entry_ptr, connection)))) {\n            cancel_send_entry(entry_ptr, ANJAY_SEND_DEFERRED_ERROR);\n        }\n    }\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n}\n\n#        ifndef ANJAY_WITHOUT_QUEUE_MODE_AUTOCLOSE\nbool _anjay_send_has_deferred(anjay_unlocked_t *anjay, anjay_ssid_t ssid) {\n    assert(ssid != ANJAY_SSID_ANY);\n    AVS_LIST(anjay_send_entry_t) it;\n    AVS_LIST_FOREACH(it, anjay->sender.entries) {\n        if (it->target_ssid > ssid) {\n            break;\n        } else if (it->exchange_status.memstream) {\n            // Entry is not deferred\n            continue;\n        } else if (it->target_ssid == ssid) {\n            return true;\n        }\n    }\n    return false;\n}\n#        endif // ANJAY_WITHOUT_QUEUE_MODE_AUTOCLOSE\n\nint _anjay_send_sched_retry_deferred(anjay_unlocked_t *anjay,\n                                     anjay_ssid_t ssid) {\n    int result = AVS_SCHED_NOW(anjay->sched, NULL, retry_deferred_job, &ssid,\n                               sizeof(ssid));\n    if (result) {\n        send_log(WARNING,\n                 _(\"Could not schedule deferred retry for Send requests for \"\n                   \"SSID = \") \"%\" PRIu16,\n                 ssid);\n    }\n    return result;\n}\n\n#    endif // ANJAY_WITH_SEND\n\n#endif // ANJAY_WITH_LWM2M11\n"
  },
  {
    "path": "src/core/anjay_lwm2m_send.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ANJAY_LWM2M_SEND_H\n#define ANJAY_LWM2M_SEND_H\n\n#include <avsystem/commons/avs_list.h>\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\ntypedef struct anjay_send_entry anjay_send_entry_t;\n\ntypedef struct {\n    AVS_LIST(anjay_send_entry_t) entries;\n} anjay_sender_t;\n\nbool _anjay_send_in_progress(anjay_connection_ref_t ref);\n\nvoid _anjay_send_interrupt(anjay_connection_ref_t ref);\n\nvoid _anjay_send_cleanup(anjay_sender_t *sender);\n\n#ifndef ANJAY_WITHOUT_QUEUE_MODE_AUTOCLOSE\nbool _anjay_send_has_deferred(anjay_unlocked_t *anjay, anjay_ssid_t ssid);\n#endif // ANJAY_WITHOUT_QUEUE_MODE_AUTOCLOSE\n\nint _anjay_send_sched_retry_deferred(anjay_unlocked_t *anjay,\n                                     anjay_ssid_t ssid);\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif /* ANJAY_LWM2M_SEND_H */\n"
  },
  {
    "path": "src/core/anjay_notify.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#include <anjay_modules/anjay_dm_utils.h>\n#include <anjay_modules/anjay_notify.h>\n#ifdef ANJAY_WITH_LWM2M_GATEWAY\n#    include <anjay_modules/anjay_lwm2m_gateway.h>\n#endif // ANJAY_WITH_LWM2M_GATEWAY\n\n#include \"coap/anjay_content_format.h\"\n\n#include \"anjay_access_utils_private.h\"\n#include \"anjay_core.h\"\n#include \"anjay_servers_utils.h\"\n#include \"observe/anjay_observe_core.h\"\n\nVISIBILITY_SOURCE_BEGIN\n\n#ifdef ANJAY_WITH_OBSERVE\nstatic int observe_notify(anjay_unlocked_t *anjay,\n                          anjay_ssid_t origin_ssid,\n                          anjay_notify_queue_t queue) {\n    int ret = 0;\n    AVS_LIST(anjay_notify_queue_object_entry_t) it;\n    AVS_LIST_FOREACH(it, queue) {\n        anjay_uri_path_t path = MAKE_OBJECT_PATH(it->oid);\n#    ifdef ANJAY_WITH_LWM2M_GATEWAY\n        if (it->prefix[0] != '\\0') {\n            strcpy(path.prefix, it->prefix);\n        }\n#    endif // ANJAY_WITH_LWM2M_GATEWAY\n        if (it->instance_set_changes.instance_set_changed) {\n            _anjay_update_ret(&ret,\n                              _anjay_observe_notify(anjay, &path, origin_ssid,\n                                                    true));\n        } else {\n            AVS_LIST(anjay_notify_queue_resource_entry_t) it2;\n            AVS_LIST_FOREACH(it2, it->resources_changed) {\n                path.ids[ANJAY_ID_IID] = it2->iid;\n                path.ids[ANJAY_ID_RID] = it2->rid;\n                _anjay_update_ret(&ret,\n                                  _anjay_observe_notify(anjay, &path,\n                                                        origin_ssid, true));\n            }\n        }\n    }\n    return ret;\n}\n#else // ANJAY_WITH_OBSERVE\n#    define observe_notify(anjay, origin_ssid, queue) (0)\n#endif // ANJAY_WITH_OBSERVE\n\nstatic int\nsecurity_modified_notify(anjay_unlocked_t *anjay,\n                         anjay_notify_queue_object_entry_t *security) {\n    int ret = 0;\n    int32_t last_iid = -1;\n    AVS_LIST(anjay_notify_queue_resource_entry_t) it;\n    AVS_LIST_FOREACH(it, security->resources_changed) {\n        if (it->iid != last_iid) {\n            _anjay_update_ret(&ret,\n                              _anjay_schedule_socket_update(anjay, it->iid));\n            last_iid = it->iid;\n        }\n    }\n    // NOTE: If anjay->update_immediately_on_dm_change is true,\n    // then this will be called from anjay_notify_perform_impl() itself\n    if (!anjay->update_immediately_on_dm_change\n            && security->instance_set_changes.instance_set_changed) {\n        _anjay_update_ret(&ret, _anjay_schedule_reload_servers(anjay));\n    }\n    return ret;\n}\n\nstatic int server_modified_notify(anjay_unlocked_t *anjay,\n                                  anjay_notify_queue_object_entry_t *server) {\n    int ret = 0;\n    if (server->instance_set_changes.instance_set_changed) {\n        // NOTE: If anjay->update_immediately_on_dm_change is true,\n        // then this will be called from anjay_notify_perform_impl() itself\n        if (!anjay->update_immediately_on_dm_change) {\n            _anjay_update_ret(&ret, _anjay_schedule_reload_servers(anjay));\n        }\n#ifdef ANJAY_WITH_SEND\n        // servers may have been removed from data model\n        // if so, abort their Send requests as well\n        _anjay_update_ret(\n                &ret, _anjay_send_sched_retry_deferred(anjay, ANJAY_SSID_ANY));\n#endif // ANJAY_WITH_SEND\n    } else {\n        AVS_LIST(anjay_notify_queue_resource_entry_t) it;\n        AVS_LIST_FOREACH(it, server->resources_changed) {\n            if (it->rid != ANJAY_DM_RID_SERVER_BINDING\n                    && it->rid != ANJAY_DM_RID_SERVER_LIFETIME\n#ifdef ANJAY_WITH_LWM2M11\n                    && it->rid != ANJAY_DM_RID_SERVER_PREFERRED_TRANSPORT\n#    ifdef ANJAY_WITH_SEND\n                    && it->rid != ANJAY_DM_RID_SERVER_MUTE_SEND\n#    endif // ANJAY_WITH_SEND\n#endif     // ANJAY_WITH_LWM2M11\n            ) {\n                continue;\n            }\n            const anjay_uri_path_t path =\n                    MAKE_RESOURCE_PATH(ANJAY_DM_OID_SERVER, it->iid,\n                                       ANJAY_DM_RID_SERVER_SSID);\n            int64_t ssid;\n            if (_anjay_dm_read_resource_i64(anjay, &path, &ssid) || ssid <= 0\n                    || ssid >= UINT16_MAX) {\n                _anjay_update_ret(&ret, -1);\n#ifdef ANJAY_WITH_SEND\n            } else if (it->rid == ANJAY_DM_RID_SERVER_MUTE_SEND) {\n                _anjay_update_ret(&ret, _anjay_send_sched_retry_deferred(\n                                                anjay, (anjay_ssid_t) ssid));\n                continue;\n#endif // ANJAY_WITH_SEND\n            } else if (_anjay_servers_find_active(anjay, (anjay_ssid_t) ssid)) {\n                _anjay_update_ret(&ret,\n                                  _anjay_schedule_registration_update_unlocked(\n                                          anjay, (anjay_ssid_t) ssid));\n            }\n        }\n    }\n    return ret;\n}\n\nstatic int anjay_notify_perform_impl(anjay_unlocked_t *anjay,\n                                     anjay_ssid_t origin_ssid,\n                                     anjay_notify_queue_t *queue_ptr,\n                                     bool server_notify) {\n    if (!queue_ptr || !*queue_ptr) {\n        return 0;\n    }\n    bool instances_modified = false;\n    int ret = 0;\n    _anjay_update_ret(&ret, _anjay_sync_access_control(anjay, origin_ssid,\n                                                       queue_ptr));\n    AVS_LIST(anjay_notify_queue_object_entry_t) it;\n    AVS_LIST_FOREACH(it, *queue_ptr) {\n        if (it->instance_set_changes.instance_set_changed) {\n            instances_modified = true;\n        }\n        if (it->oid == ANJAY_DM_OID_SECURITY) {\n            _anjay_update_ret(&ret, security_modified_notify(anjay, it));\n        } else if (server_notify && it->oid == ANJAY_DM_OID_SERVER) {\n            _anjay_update_ret(&ret, server_modified_notify(anjay, it));\n        }\n    }\n    if (instances_modified && anjay->update_immediately_on_dm_change) {\n        _anjay_update_ret(&ret, _anjay_schedule_reload_servers(anjay));\n    }\n    _anjay_update_ret(&ret, observe_notify(anjay,\n                                           anjay->enable_self_notify\n                                                   ? ANJAY_SSID_BOOTSTRAP\n                                                   : origin_ssid,\n                                           *queue_ptr));\n#ifdef ANJAY_WITH_ATTR_STORAGE\n    _anjay_update_ret(&ret, _anjay_attr_storage_notify(anjay, *queue_ptr));\n#endif // ANJAY_WITH_ATTR_STORAGE\n    return ret;\n}\n\nint _anjay_notify_perform(anjay_unlocked_t *anjay,\n                          anjay_ssid_t origin_ssid,\n                          anjay_notify_queue_t *queue_ptr) {\n    return anjay_notify_perform_impl(anjay, origin_ssid, queue_ptr, true);\n}\n\nint _anjay_notify_perform_without_servers(anjay_unlocked_t *anjay,\n                                          anjay_ssid_t origin_ssid,\n                                          anjay_notify_queue_t *queue_ptr) {\n    return anjay_notify_perform_impl(anjay, origin_ssid, queue_ptr, false);\n}\n\nint _anjay_notify_flush(anjay_unlocked_t *anjay,\n                        anjay_ssid_t origin_ssid,\n                        anjay_notify_queue_t *queue_ptr) {\n    int result = _anjay_notify_perform(anjay, origin_ssid, queue_ptr);\n    _anjay_notify_clear_queue(queue_ptr);\n    return result;\n}\n\nstatic AVS_LIST(anjay_notify_queue_object_entry_t) *\nfind_or_create_object_entry(anjay_notify_queue_t *out_queue,\n                            const anjay_uri_path_t *path) {\n    AVS_LIST(anjay_notify_queue_object_entry_t) *it;\n    AVS_LIST_FOREACH_PTR(it, out_queue) {\n#ifdef ANJAY_WITH_LWM2M_GATEWAY\n        // for gateway the search must include comparing the prefix as well\n        if ((!strcmp((*it)->prefix, path->prefix)))\n#endif // ANJAY_WITH_LWM2M_GATEWAY\n        {\n            if ((*it)->oid == path->ids[ANJAY_ID_OID]) {\n                return it;\n            } else if ((*it)->oid > path->ids[ANJAY_ID_OID]) {\n                break;\n            }\n        }\n    }\n    if (AVS_LIST_INSERT_NEW(anjay_notify_queue_object_entry_t, it)) {\n        (*it)->oid = path->ids[ANJAY_ID_OID];\n#ifdef ANJAY_WITH_LWM2M_GATEWAY\n        strcpy((*it)->prefix, path->prefix);\n#endif // ANJAY_WITH_LWM2M_GATEWAY\n        return it;\n    } else {\n        return NULL;\n    }\n}\n\nstatic int add_entry_to_iid_set(AVS_LIST(anjay_iid_t) *iid_set_ptr,\n                                anjay_iid_t iid) {\n    AVS_LIST_ITERATE_PTR(iid_set_ptr) {\n        if (**iid_set_ptr == iid) {\n            return 0;\n        } else if (**iid_set_ptr > iid) {\n            break;\n        }\n    }\n    if (AVS_LIST_INSERT_NEW(anjay_iid_t, iid_set_ptr)) {\n        **iid_set_ptr = iid;\n        return 0;\n    } else {\n        return -1;\n    }\n}\n\nstatic void remove_entry_from_iid_set(AVS_LIST(anjay_iid_t) *iid_set_ptr,\n                                      anjay_iid_t iid) {\n    AVS_LIST_ITERATE_PTR(iid_set_ptr) {\n        if (**iid_set_ptr >= iid) {\n            if (**iid_set_ptr == iid) {\n                AVS_LIST_DELETE(iid_set_ptr);\n            }\n            return;\n        }\n    }\n}\n\nstatic void delete_notify_queue_object_entry_if_empty(\n        AVS_LIST(anjay_notify_queue_object_entry_t) *entry_ptr) {\n    if (!entry_ptr || !*entry_ptr) {\n        return;\n    }\n    if ((*entry_ptr)->instance_set_changes.instance_set_changed\n            || (*entry_ptr)->resources_changed) {\n        // entry not empty\n        return;\n    }\n    assert(!(*entry_ptr)->instance_set_changes.known_added_iids);\n    AVS_LIST_DELETE(entry_ptr);\n}\n\nint _anjay_notify_queue_instance_created(anjay_notify_queue_t *out_queue,\n                                         const anjay_uri_path_t *path) {\n    AVS_LIST(anjay_notify_queue_object_entry_t) *entry_ptr =\n            find_or_create_object_entry(out_queue, path);\n    if (!entry_ptr || !*entry_ptr) {\n        _anjay_log_oom();\n        return -1;\n    }\n    if (add_entry_to_iid_set(\n                &(*entry_ptr)->instance_set_changes.known_added_iids,\n                path->ids[ANJAY_ID_IID])) {\n        _anjay_log_oom();\n        delete_notify_queue_object_entry_if_empty(entry_ptr);\n        return -1;\n    }\n    (*entry_ptr)->instance_set_changes.instance_set_changed = true;\n    return 0;\n}\n\nint _anjay_notify_queue_instance_removed(anjay_notify_queue_t *out_queue,\n                                         const anjay_uri_path_t *path) {\n    AVS_LIST(anjay_notify_queue_object_entry_t) *entry_ptr =\n            find_or_create_object_entry(out_queue, path);\n    if (!entry_ptr || !*entry_ptr) {\n        _anjay_log_oom();\n        return -1;\n    }\n    remove_entry_from_iid_set(\n            &(*entry_ptr)->instance_set_changes.known_added_iids,\n            path->ids[ANJAY_ID_IID]);\n    (*entry_ptr)->instance_set_changes.instance_set_changed = true;\n    return 0;\n}\n\nint _anjay_notify_queue_instance_set_unknown_change(\n        anjay_notify_queue_t *out_queue, const anjay_uri_path_t *path) {\n    AVS_LIST(anjay_notify_queue_object_entry_t) *entry_ptr =\n            find_or_create_object_entry(out_queue, path);\n    if (!entry_ptr || !*entry_ptr) {\n        _anjay_log_oom();\n        return -1;\n    }\n    (*entry_ptr)->instance_set_changes.instance_set_changed = true;\n    return 0;\n}\n\nstatic int\ncompare_resource_entries(const anjay_notify_queue_resource_entry_t *left,\n                         const anjay_notify_queue_resource_entry_t *right) {\n    int result = left->iid - right->iid;\n    if (!result) {\n        result = left->rid - right->rid;\n    }\n    return result;\n}\n\nint _anjay_notify_queue_resource_change(anjay_notify_queue_t *out_queue,\n                                        const anjay_uri_path_t *path) {\n    AVS_LIST(anjay_notify_queue_object_entry_t) *obj_entry_ptr =\n            find_or_create_object_entry(out_queue, path);\n    if (!obj_entry_ptr) {\n        _anjay_log_oom();\n        return -1;\n    }\n    anjay_notify_queue_resource_entry_t new_entry = {\n        .iid = path->ids[ANJAY_ID_IID],\n        .rid = path->ids[ANJAY_ID_RID]\n    };\n    AVS_LIST(anjay_notify_queue_resource_entry_t) *res_entry_ptr;\n    AVS_LIST_FOREACH_PTR(res_entry_ptr, &(*obj_entry_ptr)->resources_changed) {\n        int compare = compare_resource_entries(*res_entry_ptr, &new_entry);\n        if (compare == 0) {\n            return 0;\n        } else if (compare > 0) {\n            break;\n        }\n    }\n    if (!AVS_LIST_INSERT_NEW(anjay_notify_queue_resource_entry_t,\n                             res_entry_ptr)) {\n        _anjay_log_oom();\n        if (!(*obj_entry_ptr)->instance_set_changes.instance_set_changed\n                && !(*obj_entry_ptr)->resources_changed) {\n            AVS_LIST_DELETE(obj_entry_ptr);\n        }\n        return -1;\n    }\n    **res_entry_ptr = new_entry;\n    return 0;\n}\n\nvoid _anjay_notify_clear_queue(anjay_notify_queue_t *out_queue) {\n    AVS_LIST_CLEAR(out_queue) {\n        AVS_LIST_CLEAR(&(*out_queue)->instance_set_changes.known_added_iids);\n        AVS_LIST_CLEAR(&(*out_queue)->resources_changed);\n    }\n}\n\nstatic void notify_clb(avs_sched_t *sched, const void *dummy) {\n    (void) dummy;\n    anjay_t *anjay_locked = _anjay_get_from_sched(sched);\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    _anjay_notify_flush(anjay, ANJAY_SSID_BOOTSTRAP,\n                        &anjay->scheduled_notify.queue);\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n}\n\nstatic int reschedule_notify(anjay_unlocked_t *anjay) {\n    if (anjay->scheduled_notify.handle) {\n        return 0;\n    }\n    return AVS_SCHED_NOW(anjay->sched, &anjay->scheduled_notify.handle,\n                         notify_clb, NULL, 0);\n}\n\nint _anjay_notify_instance_created(anjay_unlocked_t *anjay,\n                                   anjay_oid_t oid,\n                                   anjay_iid_t iid) {\n    int retval;\n    (void) ((retval = _anjay_notify_queue_instance_created(\n                     &anjay->scheduled_notify.queue,\n                     &MAKE_INSTANCE_PATH(oid, iid)))\n            || (retval = reschedule_notify(anjay)));\n    return retval;\n}\n\nint _anjay_notify_changed_unlocked(anjay_unlocked_t *anjay,\n                                   anjay_oid_t oid,\n                                   anjay_iid_t iid,\n                                   anjay_rid_t rid) {\n    int retval;\n    (void) ((retval = _anjay_notify_queue_resource_change(\n                     &anjay->scheduled_notify.queue,\n                     &MAKE_RESOURCE_PATH(oid, iid, rid)))\n            || (retval = reschedule_notify(anjay)));\n    return retval;\n}\n\nint anjay_notify_changed(anjay_t *anjay_locked,\n                         anjay_oid_t oid,\n                         anjay_iid_t iid,\n                         anjay_rid_t rid) {\n    int retval = -1;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    retval = _anjay_notify_changed_unlocked(anjay, oid, iid, rid);\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return retval;\n}\n\n#ifdef ANJAY_WITH_LWM2M_GATEWAY\nint _anjay_notify_changed_gw_unlocked(anjay_unlocked_t *anjay,\n                                      const char *prefix,\n                                      anjay_oid_t oid,\n                                      anjay_iid_t iid,\n                                      anjay_rid_t rid) {\n    int retval;\n    anjay_uri_path_t path = MAKE_RESOURCE_PATH(oid, iid, rid);\n    strcpy(path.prefix, prefix);\n    (void) ((retval = _anjay_notify_queue_resource_change(\n                     &anjay->scheduled_notify.queue, &path))\n            || (retval = reschedule_notify(anjay)));\n    return retval;\n}\n\nint _anjay_notify_instances_changed_gw_unlocked(anjay_unlocked_t *anjay,\n                                                const char *prefix,\n                                                anjay_oid_t oid) {\n    int retval;\n    anjay_uri_path_t path = MAKE_OBJECT_PATH(oid);\n    strcpy(path.prefix, prefix);\n    (void) ((retval = _anjay_notify_queue_instance_set_unknown_change(\n                     &anjay->scheduled_notify.queue, &path))\n            || (retval = reschedule_notify(anjay)));\n    return retval;\n}\n\n#endif // ANJAY_WITH_LWM2M_GATEWAY\n\nint _anjay_notify_instances_changed_unlocked(anjay_unlocked_t *anjay,\n                                             anjay_oid_t oid) {\n    int retval;\n    (void) ((retval = _anjay_notify_queue_instance_set_unknown_change(\n                     &anjay->scheduled_notify.queue, &MAKE_OBJECT_PATH(oid)))\n            || (retval = reschedule_notify(anjay)));\n    return retval;\n}\n\nint anjay_notify_instances_changed(anjay_t *anjay_locked, anjay_oid_t oid) {\n    int retval = -1;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    retval = _anjay_notify_instances_changed_unlocked(anjay, oid);\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return retval;\n}\n\n#ifdef ANJAY_WITH_OBSERVATION_STATUS\nvoid _anjay_notify_observation_status_impl_unlocked(\n        anjay_unlocked_t *anjay,\n        anjay_resource_observation_status_t *status,\n        const char *prefix,\n        anjay_oid_t oid,\n        anjay_iid_t iid,\n        anjay_rid_t rid) {\n    (void) prefix;\n\n    if (oid == ANJAY_ID_INVALID || iid == ANJAY_ID_INVALID\n            || rid == ANJAY_ID_INVALID || (prefix && prefix[0] == '\\0')) {\n        return;\n    }\n\n    anjay_uri_path_t path = MAKE_RESOURCE_PATH(oid, iid, rid);\n#    ifdef ANJAY_WITH_LWM2M_GATEWAY\n    if (prefix) {\n        strcpy(path.prefix, prefix);\n        *status = _anjay_observe_status(anjay, &path);\n        return;\n    }\n#    endif // ANJAY_WITH_LWM2M_GATEWAY\n\n    if (oid == ANJAY_DM_OID_SECURITY\n            && _anjay_servers_find_active_by_security_iid(anjay, iid)) {\n        // All resources in active Security instances are always considered\n        // observed, as server connections need to be refreshed if they\n        // changed; compare with _anjay_notify_perform()\n        status->is_observed = true;\n    } else if (oid == ANJAY_DM_OID_SERVER\n               && (rid == ANJAY_DM_RID_SERVER_LIFETIME\n                   || rid == ANJAY_DM_RID_SERVER_BINDING\n#    ifdef ANJAY_WITH_LWM2M11\n                   || rid == ANJAY_DM_RID_SERVER_PREFERRED_TRANSPORT\n#    endif // ANJAY_WITH_LWM2M11\n                   )) {\n        // Lifetime and Binding in Server Object are always considered\n        // observed, as server connections need to be refreshed if they\n        // changed; compare with _anjay_notify_perform()\n        status->is_observed = true;\n    } else {\n        // Note: some modules may also depend on resource notifications,\n        // particularly Firmware Update depends on notifications on /5/0/3,\n        // but it also implements that object and generates relevant\n        // notifications internally, so there's no need to check that here.\n        *status = _anjay_observe_status(anjay, &path);\n    }\n}\n\nanjay_resource_observation_status_t\nanjay_resource_observation_status(anjay_t *anjay_locked,\n                                  anjay_oid_t oid,\n                                  anjay_iid_t iid,\n                                  anjay_rid_t rid) {\n    anjay_resource_observation_status_t retval = {\n        .is_observed = false,\n        .min_period = 0,\n        .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n#    if (ANJAY_MAX_OBSERVATION_SERVERS_REPORTED_NUMBER > 0)\n        .servers_number = 0\n#    endif // (ANJAY_MAX_OBSERVATION_SERVERS_REPORTED_NUMBER > 0)\n    };\n\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    _anjay_notify_observation_status_impl_unlocked(anjay, &retval, NULL, oid,\n                                                   iid, rid);\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return retval;\n}\n\n#endif // ANJAY_WITH_OBSERVATION_STATUS\n"
  },
  {
    "path": "src/core/anjay_raw_buffer.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#include <assert.h>\n#include <stdlib.h>\n#include <string.h>\n\n#include <anjay_modules/anjay_raw_buffer.h>\n\n#include <avsystem/commons/avs_memory.h>\n\nVISIBILITY_SOURCE_BEGIN\n\nvoid _anjay_raw_buffer_clear(anjay_raw_buffer_t *buffer) {\n    avs_free(buffer->data);\n    buffer->data = NULL;\n    buffer->size = 0;\n    buffer->capacity = 0;\n}\n\nint _anjay_raw_buffer_clone(anjay_raw_buffer_t *dst,\n                            const anjay_raw_buffer_t *src) {\n    return _anjay_raw_buffer_from_data(dst, src->data, src->size);\n}\n\nint _anjay_raw_buffer_alloc(anjay_raw_buffer_t *dst, size_t capacity) {\n    assert(!dst->data && !dst->size);\n    if (!capacity) {\n        return 0;\n    }\n    dst->data = avs_malloc(capacity);\n    if (!dst->data) {\n        return -1;\n    }\n    dst->capacity = capacity;\n    return 0;\n}\n\nint _anjay_raw_buffer_from_data(anjay_raw_buffer_t *dst,\n                                const void *src,\n                                size_t size) {\n    int result = _anjay_raw_buffer_alloc(dst, size);\n    if (!result) {\n        dst->size = size;\n        memcpy(dst->data, src, size);\n    }\n    return result;\n}\n"
  },
  {
    "path": "src/core/anjay_servers_inactive.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ANJAY_SERVERS_INACTIVE_H\n#define ANJAY_SERVERS_INACTIVE_H\n\n#include \"anjay_servers_private.h\"\n\n#if !defined(ANJAY_SERVERS_INTERNALS) && !defined(ANJAY_OBSERVE_SOURCE) \\\n        && !defined(ANJAY_TEST)\n#    error \"servers_inactive.h shall only be included from \" \\\n           \"servers/ or from the Observe subsystem\"\n#endif\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\nanjay_server_info_t *_anjay_servers_find(anjay_unlocked_t *anjay,\n                                         anjay_ssid_t ssid);\n\nbool _anjay_server_is_disable_scheduled(anjay_server_info_t *server);\n\nbool _anjay_server_active(anjay_server_info_t *server);\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif // ANJAY_SERVERS_INACTIVE_H\n"
  },
  {
    "path": "src/core/anjay_servers_private.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ANJAY_SERVERS_PRIVATE_H\n#define ANJAY_SERVERS_PRIVATE_H\n\n#include <anjay/core.h>\n\n#include <anjay_modules/anjay_sched.h>\n#include <anjay_modules/anjay_servers.h>\n\n#include <avsystem/commons/avs_persistence.h>\n#include <avsystem/commons/avs_time.h>\n#include <avsystem/commons/avs_utils.h>\n\n#include <avsystem/coap/ctx.h>\n\n#include \"anjay_utils_private.h\"\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\n////////////////////////////////////////////////////////////////////////////////\n///\n/// This is the API of the servers subsystem. Any of the files outside the\n/// servers/ subdirectory are ONLY supposed to call:\n///\n/// - APIs in this file\n/// - APIs in <anjay_modules/servers.h>\n/// - public APIs that are implemented inside the servers/ subdirectory - they\n///   can be queried using the following command after compilation:\n///\n///       nm CMakeFiles/anjay.dir/src/servers/*.o | grep ' T anjay'\n///\n///   and at the time of writing this documentation, consist of:\n///\n///     - anjay_all_connections_failed()\n///     - anjay_disable_server()\n///     - anjay_disable_server_with_timeout()\n///     - anjay_enable_server()\n///     - anjay_transport_enter_offline()\n///     - anjay_transport_exit_offline()\n///     - anjay_get_socket_entries()\n///     - anjay_transport_is_offline()\n///     - anjay_transport_schedule_reconnect()\n///     - anjay_schedule_registration_update()\n///\n/// As the documentation in public headers is written as a user's manual, the\n/// technical/developer documentation for public functions is written in the\n/// implementation files (*.c).\n///\n/// You may also want to look at servers_internal.h for data structure\n/// documentation.\n///\n////////////////////////////////////////////////////////////////////////////////\n\n/**\n * Token that changes to a new unique value every time the CoAP endpoint\n * association (i.e., DTLS session or raw UDP socket) has been established anew.\n *\n * It is currently implemented as a consecutive counter values associated with\n * the Anjay instance because it's trivial to generate such unique value that\n * way as long as it is never persisted.\n */\ntypedef struct {\n    uint64_t value;\n} anjay_conn_session_token_t;\n\nstatic inline void\n_anjay_conn_session_token_reset(anjay_conn_session_token_t *out,\n                                uint64_t *counter) {\n    // pre-incrementation is needed to avoid the assignment of 0, since such a\n    // token may already by assigned during the initialization\n    out->value = ++(*counter);\n}\n\nstatic inline bool\n_anjay_conn_session_tokens_equal(anjay_conn_session_token_t left,\n                                 anjay_conn_session_token_t right) {\n    return left.value == right.value;\n}\n\n// 6.2.2 Object Version format:\n// \"The Object Version of an Object is composed of 2 digits separated by a dot\"\n// However, we're a bit lenient to support proper numbers and not just digits.\n#define ANJAY_DM_OBJECT_VERSION_BUF_LENGTH (2 * AVS_UINT_STR_BUF_SIZE(unsigned))\n\ntypedef struct {\n    anjay_oid_t oid;\n    char version[ANJAY_DM_OBJECT_VERSION_BUF_LENGTH];\n    AVS_LIST(anjay_iid_t) instances;\n} anjay_dm_cache_object_t;\n\ntypedef struct {\n    int64_t lifetime_s;\n    char *dm;\n    anjay_binding_mode_t binding_mode;\n} anjay_update_parameters_t;\n\ntypedef struct {\n    anjay_conn_session_token_t session_token;\n    AVS_LIST(const anjay_string_t) endpoint_path;\n    anjay_lwm2m_version_t lwm2m_version;\n    bool queue_mode;\n    avs_time_real_t expire_time;\n\n    /**\n     * This flag is set whenever the Update request is forced to be sent, either\n     * manually using anjay_schedule_registration_update(), or through a\n     * scheduler job that executes near lifetime expiration.\n     */\n    bool update_forced;\n\n    anjay_update_parameters_t last_update_params;\n\n#ifdef ANJAY_WITH_COMMUNICATION_TIMESTAMP_API\n    /**\n     * Stores the time at with the last registration or registration update was\n     * performed successfully.\n     */\n    avs_time_real_t last_registration_time;\n#endif // ANJAY_WITH_COMMUNICATION_TIMESTAMP_API\n} anjay_registration_info_t;\n\n////////////////////////////////////////////////////////////////////////////////\n// METHODS ON THE WHOLE SERVERS SUBSYSTEM //////////////////////////////////////\n////////////////////////////////////////////////////////////////////////////////\n\n#ifndef ANJAY_WITHOUT_DEREGISTER\n/**\n * Deregisters from every active server. It is currently only ever called from\n * anjay_delete_impl(), because there are two flavours of anjay_delete() - the\n * regular anjay_delete() is supposed to deregister from all servers on exit,\n * but anjay_delete_with_core_persistence() (present only in versions which\n * include the Core Persistence commercial feature) shall not deregister from\n * anywhere, because it would defeat its purpose.\n *\n * We could have _anjay_servers_cleanup() with a flag, but we decided that\n * having a separate function for de-registration is more elegant.\n */\nvoid _anjay_servers_deregister(anjay_unlocked_t *anjay);\n#else // ANJAY_WITHOUT_DEREGISTER\n#    define _anjay_servers_deregister(Anjay) ((void) (Anjay))\n#endif // ANJAY_WITHOUT_DEREGISTER\n\n/**\n * Clears up the servers struct, and releases any allocated resources.\n *\n * It takes the whole anjay object as argument, because it needs to delete\n * scheduler jobs (so it needs the scheduler reference from somewhere), but it's\n * basically a fancy destructor for anjay->servers.\n *\n * Only ever called from anjay_delete_impl().\n */\nvoid _anjay_servers_cleanup(anjay_unlocked_t *anjay);\n\n/**\n * Removes all references to inactive servers except the bootstrap server (see\n * docs for anjay_server_info_t above for an information what is considered\n * \"active\") from internal structures.\n *\n * This is currently only called from start_bootstrap_if_not_already_started().\n * Inactive servers are removed during the bootstrap process - this unschedules\n * all reactivation jobs and generally prevents the inactive servers from\n * interfering in any way. The inactive servers will be recreated (and most\n * likely, scheduled to be reactivated, as an inactive server is basically a\n * pathological case) during the next \"reload\" job.\n *\n * This was introduced in internal diff D5678, specifically to address the case\n * that connections which failed registration attempts were retrying during the\n * bootstrap procedure, which we were implementing as fallback. Probably there\n * are other ways to achieve the same thing.\n */\nvoid _anjay_servers_cleanup_inactive_nonbootstrap(anjay_unlocked_t *anjay);\n\ntypedef int anjay_servers_foreach_ssid_handler_t(anjay_unlocked_t *anjay,\n                                                 anjay_ssid_t ssid,\n                                                 void *data);\n\n/**\n * Iterates over ALL servers known to the server subsystem (note that they are\n * cached since last reload, the data model is NOT queried directly) and returns\n * their SSIDs.\n *\n * Currently used in two places:\n * - access_control_utils.c :: is_single_ssid_environment() - to determine\n *   whether Access Control is even applicable\n * - _anjay_observe_gc() - to determine for which SSIDs to keep observe request\n *   information\n */\nint _anjay_servers_foreach_ssid(anjay_unlocked_t *anjay,\n                                anjay_servers_foreach_ssid_handler_t *handler,\n                                void *data);\n\ntypedef int anjay_servers_foreach_handler_t(anjay_unlocked_t *anjay,\n                                            anjay_server_info_t *server,\n                                            void *data);\n\n/**\n * Iterates over ACTIVE servers known to the server subsystem. Active servers\n * are those which have a valid socket created - valid, but not necessarily\n * ready for communication, e.g. in queue mode.\n *\n * The sockets are returned as pointers to anjay_server_info_t, which allows\n * calling more methods on them.\n */\nint _anjay_servers_foreach_active(anjay_unlocked_t *anjay,\n                                  anjay_servers_foreach_handler_t *handler,\n                                  void *data);\n\n/**\n * Schedules the \"servers reload\" operation. It can primarily be thought as\n * synchronizing the server connections contained within anjay->servers with the\n * data model, but it also handles some actions that shall be executed when the\n * servers are discovered to have changed state (registration, deregistration,\n * sending outstanding notifications etc.).\n *\n * The job is scheduled immediately and the callback is\n * reload_servers_sched_job(). Its semantics are as follows:\n *\n * 1. For each instance of the LwM2M Security object:\n * 1.1. Read the SSID (and the Bootstrap Server flag, mapping it to 65535)\n * 1.2. Call reload_server_by_ssid(), which does:\n * 1.2.1. If the server has already existed and been active, call\n *        reload_active_server(), which does:\n * 1.2.1.1. If it's not a Bootstrap Server and its registration has expired,\n *          deactivate it (scheduling a reactivation immediately) - see below\n *          for explanation of the activation and deactivation flows\n * 1.2.1.2. Call _anjay_active_server_refresh(). If it fails, deactivate the\n *          server. See below for information about what that function does.\n * 1.2.1.3. If it is a bootstrap server which does not have the \"primary\"\n *          connection configured yet (for details, see\n *          _anjay_server_primary_connection_valid()) - signifying a freshly\n *          activated (or failed?) instance - call\n *          _anjay_bootstrap_update_reconnected(), which reschedules\n *          Client-Initiated Bootstrap if applicable. Eventually,\n *          request_bootstrap() will call\n *          _anjay_server_setup_primary_connection()\n * 1.2.1.4. If it is a non-bootstrap server which does not have the \"primary\"\n *          connection configured yet, invalidate the registration (to enforce\n *          Register once everything goes well) and deactivate the server.\n * 1.2.1.5. If it is a non-bootstrap server with proper connectivity, schedule\n *          flush of the notifications (_anjay_observe_sched_flush()).\n * 1.2.2. If the server has already existed, been inactive, is not already\n *        scheduled for reactivation, but has data_inactive.reactivate_time set\n *        - schedule reactivation. See the docs of that field in\n *        anjay_server_info_t for details.\n * 1.2.3. If the server has already existed and been inactive, in any other case\n *        than described in 1.2.2 - consider reload a success.\n * 1.2.4. If we're here, it means that the server has not previously existed.\n *        Create a new inactive server entry and schedule its activation\n *        immediately (unless it's the Bootstrap Server and legacy\n *        Server-Initiated Bootstrap is disabled - Client-Initiated Bootstrap\n *        will be handled later).\n * 2. If stage 1 was a success, and if the result is that we have only one\n *    server in the data model, which is the Bootstrap Server, that is inactive\n *    and not scheduled for activation - schedule its activation immediately.\n *    This compensates for step 1.2.4 for Client-Initiated Bootstrap when legacy\n *    Server-Initiated Bootstrap is disabled.\n * 3. If the reload in stage 1 was unsuccessful (which may happen in the case of\n *    some REALLY FATAL error, such as failure to iterate over the data model or\n *    to schedule a job), retain all the servers that has been successfully\n *    reloaded, move the untouched remainder of servers that existed before\n *    reloading to the current state, and reschedule whole procedure after\n *    5 seconds.\n * 4. Call _anjay_observe_gc() to remove observation entries for servers that\n *    ceased to exist.\n * 5. Call Deregister on all servers that ceased to exist but were previously\n *    active.\n * 6. Clean up.\n *\n * The \"server reactivation\" procedure is performed within the\n * activate_server_job() function and basically consists of the following:\n *\n * 1. Call initialize_active_server(), which does:\n * 1.1. Fail immediately if we're in offline mode.\n * 1.2. Read the URI from the Security instance.\n * 1.3. Call _anjay_active_server_refresh(), returning a failure (but see notes\n *      below) if it fails\n * 1.4. If it's not a Bootstrap Server, call\n *      _anjay_server_ensure_valid_registration(), which:\n *      - will do nothing if the server has valid registration and there is no\n *        need to send the Update message whatsoever\n *      - will send UPDATE message if the server has valid registration but some\n *        of its details has changed (i.e., the values sent within the Update\n *        message)\n *      - will send REGISTER message if the server has no valid registration\n *        (i.e. it's new or its session has been replaced instead of resumed),\n *        or if the aforementioned attempt to send Update failed (Updates\n *        automatically degenerate to Registers within this function, and\n *        internal structures tracking registration state are updated\n *        accordingly) - NOTE THAT THIS IS THE **ONLY** PLACE IN THE ENTIRE CODE\n *        FLOW IN WHICH THE REGISTER MESSAGE MAY BE SENT\n * 1.5. If it is a Bootstrap Server, call _anjay_bootstrap_account_prepare(),\n *      which will schedule Client-Initiated Bootstrap if applicable.\n * 1.6. If the above was a success, reset the reactivate_failed flag, the\n *      num_icmp_failures counter and the reactivate_time value.\n * 2. If stage 1 was unsuccessful:\n * 2.1. Clean up the sockets (essentially deactivating the server).\n * 2.2. If there was an ECONNREFUSED error during _anjay_active_server_refresh()\n *      (which covers DTLS handshake, but NOT the Register/Update messages -\n *      TODO: NEEDS FIXING), increase the num_icmp_failures counter\n * 2.3. If there was a 4.03 Forbidden response to the attempt to send Register\n *      (note that Update is unaffected as it immediately degenerates to\n *      Register) or if _anjay_active_server_refresh() failed due to EPROTO or\n *      ETIMEDOUT (essentially a network failure during DTLS handshake other\n *      than Connection Refused), max out the num_icmp_failures counter, causing\n *      to immediately land in step 2.6.\n * 2.4. If there was any other failure (e.g. some other error response to\n *      Register) - do not touch the num_icmp_failures counter. Such failures\n *      may happen indefinitely.\n * 2.5. If the num_icmp_failures counter is smaller than the limit, retry the\n *      activation job (uses backoff controlled by\n *      _anjay_servers_schedule_{first,next}_retryable()).\n * 2.6. If the ICMP failures limit has been reached:\n * 2.6.1. If we're attempting to activate the Bootstrap Server, abort all\n *        further Client-Initiated Bootstrap attempts.\n * 2.6.2. Otherwise, if there is a Bootstrap Server, there are no active\n *        non-Bootstrap servers, all other servers have reached the ICMP\n *        failures limit and Bootstrap is not already in progress:\n * 2.6.2.1. If the Bootstrap Server is active, call\n *          _anjay_bootstrap_account_prepare(), eventually sending Request\n *          Bootstrap.\n * 2.6.2.2. Otherwise, schedule immediate activation of the Bootstrap Server -\n *          this covers the case when Server-Initiated Bootstrap is disabled and\n *          the Bootstrap Server is not active when not in the Client-Initiated\n *          Bootstrap procedure.\n * 2.6.3. Prevent activate_server_job() from ever being called again, until\n *        anjay_transport_schedule_reconnect() is manually called.\n *\n * Server deactivation is normally handled by deactivate_server(). The server\n * might also enter inactive state through enter_offline_job(), or in case of\n * failure in activate_server_job(). Still, deactivate_server() works as\n * follows:\n *\n * 1. If the server has a valid registration - send Deregister. Intentionally\n *    ignore errors if it fails for any reason - Deregister is optional anyway.\n * 2. Call _anjay_server_clean_active_data(), which cleans up the sockets and\n *    unschedules any planned jobs, and reset primary_conn_type to UNSET.\n *    However, anjay_server_connection_t::nontransient_state fields are\n *    intentionally NOT cleaned up, and they contain:\n *    - preferred_endpoint, i.e. the preference which server IP address to use\n *      if multiple are returned during DNS resolution\n *    - DTLS session cache\n *    - Last bound local port\n * 3. Schedule reactivation after the specified amount of time.\n */\nint _anjay_schedule_reload_servers(anjay_unlocked_t *anjay);\n\n/**\n * Interrupts any ongoing communication with connections that are\n * administratively set to be offline.\n *\n * Intended to be calling when entering offline mode, to prevent data being sent\n * between scheduler ticks.\n */\nvoid _anjay_servers_interrupt_offline(anjay_unlocked_t *anjay);\n\n////////////////////////////////////////////////////////////////////////////////\n// METHODS ON ACTIVE SERVERS ///////////////////////////////////////////////////\n////////////////////////////////////////////////////////////////////////////////\n\n/**\n * Gets the SSID of the server in question.\n */\nanjay_ssid_t _anjay_server_ssid(anjay_server_info_t *server);\n\nanjay_iid_t _anjay_server_last_used_security_iid(anjay_server_info_t *server);\n\nanjay_unlocked_t *_anjay_from_server(anjay_server_info_t *server);\n\n/**\n * Gets the administratively configured binding mode of the server in question.\n */\nconst anjay_binding_mode_t *\n_anjay_server_binding_mode(anjay_server_info_t *server);\n\n#ifdef ANJAY_WITH_COMMUNICATION_TIMESTAMP_API\n/*\n * Sets the last communication timestamp for a given server.\n */\nvoid _anjay_server_set_last_communication_time(anjay_server_info_t *server);\n#endif // ANJAY_WITH_COMMUNICATION_TIMESTAMP_API\n/**\n * Gets the token uniquely identifying the CoAP endpoint association (i.e., DTLS\n * session or raw UDP socket) of the server's primary connection.\n *\n * It is used to determine whether reconnect operation re-used the previous\n * association or created a new one.\n */\nanjay_conn_session_token_t\n_anjay_server_primary_session_token(anjay_server_info_t *server);\n\n/**\n * Gets the information about current registration status of the server. These\n * include the data sent within the Update method's payload, and also the\n * endpoint path and the expiration time (the point in time at which the\n * registration lifetime passes).\n */\nconst anjay_registration_info_t *\n_anjay_server_registration_info(anjay_server_info_t *server);\n\n/**\n * Updates the registration information (the same that can be queried through\n * _anjay_server_registration_info()) within the server. The endpoint path and\n * Update parameters can be updated directly through the arguments, and the\n * expiration time is calculated by adding move_params->lifetime_s seconds to\n * the RTC reading at the time of calling this function.\n *\n * NOTE: If any of the move_* parameters are NULL, the relevant fields are NOT\n * updated, i.e., they are left untouched rather than being replaced with NULLs.\n *\n * This is called from _anjay_register() and _anjay_update_registration() to\n * update the internally stored values with actual negotiated data, and from\n * _anjay_schedule_socket_update() to invalidate registration.\n */\nvoid _anjay_server_update_registration_info(\n        anjay_server_info_t *server,\n        AVS_LIST(const anjay_string_t) *move_endpoint_path,\n        anjay_lwm2m_version_t lwm2m_version,\n        bool queue_mode,\n        anjay_update_parameters_t *move_params);\n\n/**\n * Handles a critical error (including network communication error) on the\n * primary connection of the server. Effectively disables the server, and might\n * schedule Client-Initiated Bootstrap if applicable.\n */\nvoid _anjay_server_on_failure(anjay_server_info_t *server,\n                              const char *debug_msg);\n\n/** Calls @ref _anjay_server_on_failure() through the scheduler. */\nvoid _anjay_server_on_server_communication_error(anjay_server_info_t *server,\n                                                 avs_error_t err);\n\n/**\n * Handles a network timeout during Registration (or Request Bootstrap) on the\n * primary connection of the server - this retries connection if the connection\n * was stable (i.e. not just freshly connected and not stateless), or calls\n * @ref _anjay_server_on_server_communication_error otherwise.\n */\nvoid _anjay_server_on_server_communication_timeout(anjay_server_info_t *server);\n\nvoid _anjay_server_on_fatal_coap_error(anjay_connection_ref_t conn_ref,\n                                       avs_error_t err);\n\n////////////////////////////////////////////////////////////////////////////////\n// METHODS ON SERVER CONNECTIONS ///////////////////////////////////////////////\n////////////////////////////////////////////////////////////////////////////////\n\n/**\n * Returns the CACHED URI of the given connection - the one that was most\n * recently read in initialize_active_server().\n *\n * It is called from send_request_bootstrap() and send_register() to fill the\n * Uri-Path option in the outgoing messages.\n */\nconst anjay_url_t *_anjay_connection_uri(anjay_connection_ref_t ref);\n\n#ifdef ANJAY_WITHOUT_QUEUE_MODE_AUTOCLOSE\n#    define _anjay_connection_schedule_queue_mode_close(...) ((void) 0)\n#else  // ANJAY_WITHOUT_QUEUE_MODE_AUTOCLOSE\n/**\n * This function schedules closing of the socket (suspending the connection)\n * after MAX_TRANSMIT_WAIT passes. It is supposed to be called after finishing\n * each interaction with a server - generally after each call to\n * avs_coap_streaming_handle_incoming_packet(),\n * avs_coap_client_send_async_request(), avs_coap_notify_async(), as well as\n * after (re)connecting a socket even if no outgoing message is being sent.\n */\nvoid _anjay_connection_schedule_queue_mode_close(anjay_connection_ref_t ref);\n#endif // ANJAY_WITHOUT_QUEUE_MODE_AUTOCLOSE\n\n/**\n * Returns the socket associated with a given connection, if it exists and is in\n * online state, ready for communication.\n *\n * It's used in _anjay_bind_connection(), get_online_connection_socket()\n * and find_by_udp_socket_clb() to actually get the sockets, but also in various\n * places to just check whether the connection is online.\n */\navs_net_socket_t *\n_anjay_connection_get_online_socket(anjay_connection_ref_t ref);\n\nbool _anjay_connection_ready_for_outgoing_message(anjay_connection_ref_t ref);\n\n/**\n * This function only makes sense when the connection is online. It marks the\n * connection as \"stable\", which makes it eligible for reconnection if\n * communication error occurs from now on.\n *\n * It is generally called whenever an incoming message is received on a given\n * connection. Specifically:\n * - from handle_register_response() and receive_update_response() in\n *   anjay_register.c\n * - from _anjay_server_on_refreshed() in anjay_activate.c for the Bootstrap\n *   Server connection\n * - from handle_notify_delivery() in anjay_observe_core.c\n * - from response_handler() in anjay_lwm2m_send.c\n */\nvoid _anjay_connection_mark_stable(anjay_connection_ref_t ref);\n\n/**\n * This function only makes sense when the connection is in a suspended (active\n * but not online) state. It rebinds and reconnects the socket. Data model is\n * not queried, as all information (hostname, ports, DTLS security information)\n * is retrieved from the pre-existing socket.\n *\n * It is called from observe_core.c :: ensure_conn_online().\n */\nvoid _anjay_connection_bring_online(anjay_connection_ref_t ref);\n\n/**\n * Suspends the specified connection. Suspending the connection means closing\n * the socket, but not cleaning it up. The connection (and server) is then still\n * considered active, but not online.\n */\nvoid _anjay_connection_suspend(anjay_connection_ref_t conn_ref);\n\n/**\n * Returns true if there are any outgoing CoAP exchanges in progress for a given\n * connection. This may include Register, Update, Confirmable Notify, Send, or\n * same-socket downloads. If none of these are in progress, returns false.\n */\nbool _anjay_connection_outgoing_exchanges_in_progress(\n        anjay_connection_ref_t conn_ref);\n\nanjay_socket_transport_t\n_anjay_connection_transport(anjay_connection_ref_t conn_ref);\n\n/**\n * Creates a list of all online non-SMS LwM2M sockets, the single SMS router\n * socket (if applicable) and all active download sockets (if applicable).\n */\nAVS_LIST(const anjay_socket_entry_t)\n_anjay_collect_socket_entries(anjay_unlocked_t *anjay, bool include_offline);\n\n#ifdef ANJAY_WITH_CONN_STATUS_API\n/**\n * Set connection status for the server specified by the server argument. This\n * function supports LwM2M bootstrap and regular LwM2M servers.\n */\nvoid _anjay_set_server_connection_status(anjay_server_info_t *server,\n                                         anjay_server_conn_status_t new_status);\n\n/**\n * Check the server parameters and set one of the following server states:\n * ANJAY_SERV_CONN_STATUS_UNKNOWN, ANJAY_SERV_CONN_STATUS_REGISTERING,\n * ANJAY_SERV_CONN_STATUS_REREGISTERING, ANJAY_SERV_CONN_STATUS_REGISTERED or\n * ANJAY_SERV_CONN_STATUS_UPDATING. This function supports only regular LwM2M\n * servers.\n */\nvoid _anjay_check_server_connection_status(anjay_server_info_t *server);\n#endif // ANJAY_WITH_CONN_STATUS_API\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif // ANJAY_SERVERS_PRIVATE_H\n"
  },
  {
    "path": "src/core/anjay_servers_reload.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ANJAY_SERVERS_RELOAD_H\n#define ANJAY_SERVERS_RELOAD_H\n\n#include <anjay_modules/anjay_servers.h>\n\n#if !(defined(ANJAY_SERVERS_INTERNALS) || defined(ANJAY_LWM2M_SEND_SOURCE) \\\n      || defined(ANJAY_TEST))\n#    error \"Headers from servers/ are not meant to be included from outside\"\n#endif\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\nint _anjay_schedule_delayed_reload_servers(anjay_unlocked_t *anjay);\n\nint _anjay_schedule_refresh_server(anjay_server_info_t *server,\n                                   avs_time_duration_t delay);\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif // ANJAY_SERVERS_RELOAD_H\n"
  },
  {
    "path": "src/core/anjay_servers_utils.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#include <avsystem/commons/avs_utils.h>\n\n#include <anjay_modules/anjay_dm_utils.h>\n\n#include \"anjay_core.h\"\n#include \"anjay_servers_private.h\"\n#include \"anjay_servers_utils.h\"\n\n#include \"dm/anjay_query.h\"\n\nVISIBILITY_SOURCE_BEGIN\n\ntypedef struct {\n    avs_net_socket_t *socket;\n    anjay_server_info_t *out;\n} find_by_primary_socket_args_t;\n\nstatic int find_by_primary_socket_clb(anjay_unlocked_t *anjay,\n                                      anjay_server_info_t *server,\n                                      void *args_) {\n    (void) anjay;\n    find_by_primary_socket_args_t *args =\n            (find_by_primary_socket_args_t *) args_;\n    const anjay_connection_ref_t ref = {\n        .server = server,\n        .conn_type = ANJAY_CONNECTION_PRIMARY\n    };\n    if (_anjay_connection_get_online_socket(ref) == args->socket) {\n        args->out = server;\n        return ANJAY_FOREACH_BREAK;\n    }\n    return ANJAY_FOREACH_CONTINUE;\n}\n\nanjay_server_info_t *\n_anjay_servers_find_by_primary_socket(anjay_unlocked_t *anjay,\n                                      avs_net_socket_t *socket) {\n    assert(socket);\n    find_by_primary_socket_args_t arg = {\n        .socket = socket,\n        .out = NULL\n    };\n    if (_anjay_servers_foreach_active(anjay, find_by_primary_socket_clb,\n                                      &arg)) {\n        return NULL;\n    }\n    return arg.out;\n}\n\ntypedef struct {\n    anjay_ssid_t ssid;\n    anjay_server_info_t *out;\n} find_active_args_t;\n\nstatic int find_active_clb(anjay_unlocked_t *anjay,\n                           anjay_server_info_t *server,\n                           void *args_) {\n    (void) anjay;\n    find_active_args_t *args = (find_active_args_t *) args_;\n    if (_anjay_server_ssid(server) == args->ssid) {\n        args->out = server;\n        return ANJAY_FOREACH_BREAK;\n    }\n    return ANJAY_FOREACH_CONTINUE;\n}\n\nanjay_server_info_t *_anjay_servers_find_active(anjay_unlocked_t *anjay,\n                                                anjay_ssid_t ssid) {\n    find_active_args_t arg = {\n        .ssid = ssid,\n        .out = NULL\n    };\n    if (_anjay_servers_foreach_active(anjay, find_active_clb, &arg)) {\n        return NULL;\n    }\n    return arg.out;\n}\n\ntypedef struct {\n    anjay_iid_t security_iid;\n    anjay_server_info_t *out;\n} find_active_by_security_iid_args_t;\n\nstatic int find_active_by_security_iid_clb(anjay_unlocked_t *anjay,\n                                           anjay_server_info_t *server,\n                                           void *args_) {\n    (void) anjay;\n    find_active_by_security_iid_args_t *args =\n            (find_active_by_security_iid_args_t *) args_;\n    if (_anjay_server_last_used_security_iid(server) == args->security_iid) {\n        args->out = server;\n        return ANJAY_FOREACH_BREAK;\n    }\n    return ANJAY_FOREACH_CONTINUE;\n}\n\nanjay_server_info_t *\n_anjay_servers_find_active_by_security_iid(anjay_unlocked_t *anjay,\n                                           anjay_iid_t security_iid) {\n    find_active_by_security_iid_args_t arg = {\n        .security_iid = security_iid,\n        .out = NULL\n    };\n    if (_anjay_servers_foreach_active(anjay, find_active_by_security_iid_clb,\n                                      &arg)) {\n        return NULL;\n    }\n    return arg.out;\n}\n\nanjay_connection_ref_t\n_anjay_servers_find_active_primary_connection(anjay_unlocked_t *anjay,\n                                              anjay_ssid_t ssid) {\n    anjay_connection_ref_t ref = {\n        .server = _anjay_servers_find_active(anjay, ssid),\n        .conn_type = ANJAY_CONNECTION_PRIMARY\n    };\n    return ref;\n}\n\nstatic inline void\nupdate_expiration_status(anjay_registration_expiration_status_t *status,\n                         anjay_registration_expiration_status_t new_status) {\n    if (status) {\n        *status = new_status;\n    }\n}\n\navs_time_real_t _anjay_registration_expire_time_with_status(\n        anjay_server_info_t *server,\n        anjay_registration_expiration_status_t *status) {\n    const anjay_registration_info_t *registration_info =\n            _anjay_server_registration_info(server);\n    assert(registration_info);\n\n    if (!_anjay_conn_session_tokens_equal(_anjay_server_primary_session_token(\n                                                  server),\n                                          registration_info->session_token)) {\n        anjay_log(DEBUG,\n                  _(\"Registration session changed for SSID = \") \"%u\" _(\n                          \", needs re-register\"),\n                  _anjay_server_ssid(server));\n        update_expiration_status(status,\n                                 ANJAY_REGISTRATION_EXPIRATION_STATUS_EXPIRED);\n        return AVS_TIME_REAL_INVALID;\n    }\n\n    if (registration_info->last_update_params.lifetime_s == 0) {\n        update_expiration_status(\n                status, ANJAY_REGISTRATION_EXPIRATION_STATUS_INFINITE_LIFETIME);\n        return AVS_TIME_REAL_INVALID;\n    }\n\n    // avs_time_real_before() returns false when either argument is INVALID;\n    // the direction of this comparison is chosen so that it causes the\n    // registration to be considered expired in such case\n    if (!avs_time_real_before(avs_time_real_now(),\n                              registration_info->expire_time)) {\n        anjay_log(DEBUG,\n                  _(\"Registration Lifetime expired for SSID = \") \"%u\" _(\n                          \", needs re-register\"),\n                  _anjay_server_ssid(server));\n        update_expiration_status(status,\n                                 ANJAY_REGISTRATION_EXPIRATION_STATUS_EXPIRED);\n        return AVS_TIME_REAL_INVALID;\n    }\n\n    update_expiration_status(status,\n                             ANJAY_REGISTRATION_EXPIRATION_STATUS_VALID);\n    return registration_info->expire_time;\n}\n\nbool _anjay_server_registration_expired(anjay_server_info_t *server) {\n    anjay_registration_expiration_status_t expiration;\n    _anjay_registration_expire_time_with_status(server, &expiration);\n    return expiration == ANJAY_REGISTRATION_EXPIRATION_STATUS_EXPIRED;\n}\n\nint _anjay_schedule_socket_update(anjay_unlocked_t *anjay,\n                                  anjay_iid_t security_iid) {\n    anjay_server_info_t *server;\n    if ((server = _anjay_servers_find_active_by_security_iid(anjay,\n                                                             security_iid))) {\n        // mark the registration as expired; prevents superfluous Deregister\n        _anjay_server_update_registration_info(\n                server, NULL,\n#ifdef ANJAY_WITH_LWM2M11\n                anjay->lwm2m_version_config.minimum_version,\n#else  // ANJAY_WITH_LWM2M11\n                ANJAY_LWM2M_VERSION_1_0,\n#endif // ANJAY_WITH_LWM2M11\n                false,\n                &(anjay_update_parameters_t) {\n                    .lifetime_s = -1\n                });\n        return _anjay_schedule_disable_server_with_explicit_timeout_unlocked(\n                anjay, _anjay_server_ssid(server), AVS_TIME_DURATION_ZERO);\n    }\n    return 0;\n}\n\nAVS_LIST(avs_net_socket_t *const) anjay_get_sockets(anjay_t *anjay) {\n    // We rely on the fact that the \"socket\" field is first in\n    // anjay_socket_entry_t, which means that both \"entry\" and \"&entry->socket\"\n    // point to exactly the same memory location. The \"next\" pointer location in\n    // AVS_LIST is independent from the stored data type, so it's safe to do\n    // such \"cast\".\n    AVS_STATIC_ASSERT(offsetof(anjay_socket_entry_t, socket) == 0,\n                      entry_socket_is_first_field);\n    return &anjay_get_socket_entries(anjay)->socket;\n}\n"
  },
  {
    "path": "src/core/anjay_servers_utils.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ANJAY_SERVERS_UTILS_H\n#define ANJAY_SERVERS_UTILS_H\n\n#include \"anjay_servers_private.h\"\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\n/**\n * Returns a server object for given SSID.\n *\n * NOTE: the bootstrap server is identified by the ANJAY_SSID_BOOTSTRAP\n * constant instead of its actual SSID.\n */\nanjay_server_info_t *_anjay_servers_find_active(anjay_unlocked_t *anjay,\n                                                anjay_ssid_t ssid);\n\nanjay_server_info_t *\n_anjay_servers_find_active_by_security_iid(anjay_unlocked_t *anjay,\n                                           anjay_iid_t security_iid);\n\nanjay_connection_ref_t\n_anjay_servers_find_active_primary_connection(anjay_unlocked_t *anjay,\n                                              anjay_ssid_t ssid);\n\n/**\n *\n * @param server server for which expire time is going to be returned.\n * @param expiration Status of expiration. It can be\n * ANJAY_REGISTRATION_EXPIRATION_STATUS_VALID,\n * ANJAY_REGISTRATION_EXPIRATION_STATUS_EXPIRED or\n * ANJAY_REGISTRATION_EXPIRATION_STATUS_INFINITE_LIFETIME.\n * @returns Point in time at which the server registration expires.\n */\navs_time_real_t _anjay_registration_expire_time_with_status(\n        anjay_server_info_t *server,\n        anjay_registration_expiration_status_t *status);\n\nbool _anjay_server_registration_expired(anjay_server_info_t *server);\n\nint _anjay_schedule_socket_update(anjay_unlocked_t *anjay,\n                                  anjay_iid_t security_iid);\n\nbool _anjay_server_connection_active(anjay_connection_ref_t ref);\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif // ANJAY_SERVERS_H\n"
  },
  {
    "path": "src/core/anjay_stats.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#include <anjay/stats.h>\n#include <anjay_modules/anjay_dm_utils.h>\n\n#include \"anjay_core.h\"\n#include \"anjay_servers_utils.h\"\n#include \"anjay_stats.h\"\n\nVISIBILITY_SOURCE_BEGIN\n\n#define stats_log(...) _anjay_log(anjay_stats, __VA_ARGS__)\n\n#ifdef ANJAY_WITH_NET_STATS\n\ntypedef enum {\n    NET_STATS_BYTES_SENT,\n    NET_STATS_BYTES_RECEIVED,\n    NET_STATS_OUTGOING_RETRANSMISSIONS,\n    NET_STATS_INCOMING_RETRANSMISSIONS\n} net_stats_type_t;\n\nstatic uint64_t get_socket_stats(avs_net_socket_t *socket,\n                                 net_stats_type_t type) {\n    avs_net_socket_opt_value_t bytes_stats;\n    switch (type) {\n    case NET_STATS_BYTES_SENT: {\n        avs_error_t err =\n                avs_net_socket_get_opt(socket, AVS_NET_SOCKET_OPT_BYTES_SENT,\n                                       &bytes_stats);\n        if (avs_is_err(err)) {\n            stats_log(DEBUG, _(\"retrieving socket stats failed (\") \"%s\" _(\")\"),\n                      AVS_COAP_STRERROR(err));\n            return 0;\n        }\n        return bytes_stats.bytes_sent;\n    }\n    case NET_STATS_BYTES_RECEIVED: {\n        avs_error_t err = avs_net_socket_get_opt(\n                socket, AVS_NET_SOCKET_OPT_BYTES_RECEIVED, &bytes_stats);\n        if (avs_is_err(err)) {\n            stats_log(DEBUG, _(\"retrieving socket stats failed (\") \"%s\" _(\")\"),\n                      AVS_COAP_STRERROR(err));\n            return 0;\n        }\n        return bytes_stats.bytes_received;\n    }\n    default:\n        AVS_UNREACHABLE(\"this function accepts only NET_STATS_BYTES_SENT or \"\n                        \"NET_STATS_BYTES_RECEIVED\");\n        return 0;\n    }\n}\n\nstatic uint64_t get_current_stats_of_connection(anjay_connection_ref_t conn_ref,\n                                                net_stats_type_t type) {\n    avs_net_socket_t *socket;\n    avs_coap_ctx_t *coap_ctx;\n    switch (type) {\n    case NET_STATS_BYTES_SENT:\n    case NET_STATS_BYTES_RECEIVED:\n        socket = _anjay_connection_get_online_socket(conn_ref);\n        return socket ? get_socket_stats(socket, type) : 0;\n    case NET_STATS_OUTGOING_RETRANSMISSIONS:\n        coap_ctx = _anjay_connection_get_coap(conn_ref);\n        return coap_ctx ? avs_coap_get_stats(coap_ctx)\n                                  .outgoing_retransmissions_count\n                        : 0;\n    case NET_STATS_INCOMING_RETRANSMISSIONS:\n        coap_ctx = _anjay_connection_get_coap(conn_ref);\n        return coap_ctx ? avs_coap_get_stats(coap_ctx)\n                                  .incoming_retransmissions_count\n                        : 0;\n    }\n    AVS_UNREACHABLE(\"invalid enum value\");\n    return 0;\n}\n\nstatic uint64_t get_stats_of_closed_connections(anjay_unlocked_t *anjay,\n                                                net_stats_type_t type) {\n    switch (type) {\n    case NET_STATS_BYTES_SENT:\n        return anjay->closed_connections_stats.socket_stats.bytes_sent;\n    case NET_STATS_BYTES_RECEIVED:\n        return anjay->closed_connections_stats.socket_stats.bytes_received;\n    case NET_STATS_OUTGOING_RETRANSMISSIONS:\n        return anjay->closed_connections_stats.coap_stats\n                .outgoing_retransmissions_count;\n    case NET_STATS_INCOMING_RETRANSMISSIONS:\n        return anjay->closed_connections_stats.coap_stats\n                .incoming_retransmissions_count;\n    }\n    AVS_UNREACHABLE(\"invalid enum value\");\n    return 0;\n}\n\ntypedef struct {\n    net_stats_type_t net_stats_type;\n    uint64_t result_for_active_servers;\n} get_current_stats_of_server_args_t;\n\nstatic int get_current_stats_of_server(anjay_unlocked_t *anjay,\n                                       anjay_server_info_t *server,\n                                       void *args_) {\n    (void) anjay;\n    get_current_stats_of_server_args_t *args =\n            (get_current_stats_of_server_args_t *) args_;\n    anjay_connection_type_t conn_type;\n    ANJAY_CONNECTION_TYPE_FOREACH(conn_type) {\n        const anjay_connection_ref_t conn_ref = {\n            .server = server,\n            .conn_type = conn_type\n        };\n        args->result_for_active_servers +=\n                get_current_stats_of_connection(conn_ref, args->net_stats_type);\n    }\n    return 0;\n}\n\nstatic uint64_t get_stats_of_all_connections(anjay_unlocked_t *anjay,\n                                             net_stats_type_t type) {\n    get_current_stats_of_server_args_t args = {\n        .net_stats_type = type,\n        .result_for_active_servers = 0\n    };\n    _anjay_servers_foreach_active(anjay, get_current_stats_of_server, &args);\n    return args.result_for_active_servers\n           + get_stats_of_closed_connections(anjay, type);\n}\n\nuint64_t anjay_get_tx_bytes(anjay_t *anjay_locked) {\n    uint64_t result = 0;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    result = get_stats_of_all_connections(anjay, NET_STATS_BYTES_SENT);\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return result;\n}\n\nuint64_t anjay_get_rx_bytes(anjay_t *anjay_locked) {\n    uint64_t result = 0;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    result = get_stats_of_all_connections(anjay, NET_STATS_BYTES_RECEIVED);\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return result;\n}\n\nuint64_t anjay_get_num_incoming_retransmissions(anjay_t *anjay_locked) {\n    uint64_t result = 0;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    result = get_stats_of_all_connections(anjay,\n                                          NET_STATS_INCOMING_RETRANSMISSIONS);\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return result;\n}\n\nuint64_t anjay_get_num_outgoing_retransmissions(anjay_t *anjay_locked) {\n    uint64_t result = 0;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    result = get_stats_of_all_connections(anjay,\n                                          NET_STATS_OUTGOING_RETRANSMISSIONS);\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return result;\n}\n\nvoid _anjay_coap_ctx_cleanup(anjay_unlocked_t *anjay, avs_coap_ctx_t **ctx) {\n    if (ctx && *ctx) {\n        avs_coap_stats_t stats = avs_coap_get_stats(*ctx);\n        anjay->closed_connections_stats.coap_stats\n                .outgoing_retransmissions_count +=\n                stats.outgoing_retransmissions_count;\n        anjay->closed_connections_stats.coap_stats\n                .incoming_retransmissions_count +=\n                stats.incoming_retransmissions_count;\n    }\n    avs_coap_ctx_cleanup(ctx);\n}\n\n#else // ANJAY_WITH_NET_STATS\n\nuint64_t anjay_get_tx_bytes(anjay_t *anjay) {\n    (void) anjay;\n    stats_log(ERROR,\n              _(\"NET_STATS feature disabled. Anjay was compiled without \"\n                \"ANJAY_WITH_NET_STATS option.\"));\n    return 0;\n}\n\nuint64_t anjay_get_rx_bytes(anjay_t *anjay) {\n    (void) anjay;\n    stats_log(ERROR,\n              _(\"NET_STATS feature disabled. Anjay was compiled without \"\n                \"ANJAY_WITH_NET_STATS option.\"));\n    return 0;\n}\n\nuint64_t anjay_get_num_incoming_retransmissions(anjay_t *anjay) {\n    (void) anjay;\n    stats_log(ERROR,\n              _(\"NET_STATS feature disabled. Anjay was compiled without \"\n                \"ANJAY_WITH_NET_STATS option.\"));\n    return 0;\n}\n\nuint64_t anjay_get_num_outgoing_retransmissions(anjay_t *anjay) {\n    (void) anjay;\n    stats_log(ERROR,\n              _(\"NET_STATS feature disabled. Anjay was compiled without \"\n                \"ANJAY_WITH_NET_STATS option.\"));\n    return 0;\n}\n\nvoid _anjay_coap_ctx_cleanup(anjay_unlocked_t *anjay, avs_coap_ctx_t **ctx) {\n    (void) anjay;\n    avs_coap_ctx_cleanup(ctx);\n}\n\n#endif // ANJAY_WITH_NET_STATS\n\navs_error_t _anjay_socket_cleanup(anjay_unlocked_t *anjay,\n                                  avs_net_socket_t **socket) {\n    assert(socket);\n    if (*socket) {\n        avs_net_socket_shutdown(*socket);\n#ifdef ANJAY_WITH_NET_STATS\n        anjay->closed_connections_stats.socket_stats.bytes_sent +=\n                get_socket_stats(*socket, NET_STATS_BYTES_SENT);\n        anjay->closed_connections_stats.socket_stats.bytes_received +=\n                get_socket_stats(*socket, NET_STATS_BYTES_RECEIVED);\n#endif // ANJAY_WITH_NET_STATS\n        (void) anjay;\n    }\n    return avs_net_socket_cleanup(socket);\n}\n"
  },
  {
    "path": "src/core/anjay_stats.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ANJAY_STATS_H\n#define ANJAY_STATS_H\n\n#include <anjay_init.h>\n\n#include <stdint.h>\n\n#include <anjay/core.h>\n#include <avsystem/coap/ctx.h>\n#include <avsystem/commons/avs_socket.h>\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\n#ifdef ANJAY_WITH_NET_STATS\n\n/**\n * Structure for aggregating statistics of all closed coap contexts and sockets.\n */\ntypedef struct {\n    avs_coap_stats_t coap_stats;\n    struct {\n        uint64_t bytes_sent;\n        uint64_t bytes_received;\n    } socket_stats;\n} closed_connections_stats_t;\n\n#endif // ANJAY_WITH_NET_STATS\n\nvoid _anjay_coap_ctx_cleanup(anjay_unlocked_t *anjay, avs_coap_ctx_t **ctx);\n\navs_error_t _anjay_socket_cleanup(anjay_unlocked_t *anjay,\n                                  avs_net_socket_t **socket);\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif // ANJAY_STATS_H\n"
  },
  {
    "path": "src/core/anjay_utils_core.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#include <assert.h>\n#include <ctype.h>\n#include <errno.h>\n#include <inttypes.h>\n#include <stdarg.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n#include \"anjay_core.h\"\n\n#include \"dm/anjay_query.h\"\n\n#include <anjay_modules/anjay_dm_utils.h>\n#include <anjay_modules/anjay_servers.h>\n\n#include <avsystem/commons/avs_errno.h>\n#include <avsystem/commons/avs_persistence.h>\n#include <avsystem/commons/avs_url.h>\n#include <avsystem/commons/avs_utils.h>\n\nVISIBILITY_SOURCE_BEGIN\n\ntypedef enum {\n    URL_PARSE_HINT_NONE,\n    URL_PARSE_HINT_SKIP_TRAILING_SEPARATOR\n} url_parse_chunks_hint_t;\n\nstatic int url_parse_chunks(const char **url,\n                            char delimiter,\n                            char parser_terminator,\n                            url_parse_chunks_hint_t hint,\n                            AVS_LIST(const anjay_string_t) *out_chunks) {\n    const char *chunk_begin = *url;\n    const char *chunk_end = *url;\n    while (true) {\n        if (*chunk_end == '\\0' || *chunk_end == delimiter\n                || *chunk_end == parser_terminator) {\n            const size_t chunk_len = (size_t) (chunk_end - chunk_begin);\n\n            if (hint == URL_PARSE_HINT_SKIP_TRAILING_SEPARATOR\n                    && (*chunk_end == '\\0' || *chunk_end == parser_terminator)\n                    && !chunk_len) {\n                // trailing separator, ignoring\n                *url = chunk_end;\n                return 0;\n            }\n\n            if (out_chunks) {\n                AVS_LIST(anjay_string_t) chunk = (AVS_LIST(\n                        anjay_string_t)) AVS_LIST_NEW_BUFFER(chunk_len + 1);\n                if (!chunk) {\n                    _anjay_log_oom();\n                    return -1;\n                }\n                AVS_LIST_APPEND(out_chunks, chunk);\n\n                if (chunk_len) {\n                    memcpy(chunk, chunk_begin, chunk_len);\n                    size_t unescaped_length;\n                    if (avs_url_percent_decode(chunk->c_str,\n                                               &unescaped_length)) {\n                        return -1;\n                    }\n                }\n            }\n\n            if (*chunk_end == delimiter) {\n                chunk_begin = chunk_end + 1;\n            } else {\n                *url = chunk_end;\n                return 0;\n            }\n        }\n\n        ++chunk_end;\n    }\n}\n\nstatic int copy_nullable_string(char *out, size_t out_size, const char *in) {\n    assert(out_size > 0);\n    if (!in) {\n        out[0] = '\\0';\n        return 0;\n    }\n    size_t len = strlen(in);\n    if (len >= out_size) {\n        return -1;\n    }\n    strcpy(out, in);\n    return 0;\n}\n\nint _anjay_url_parse_path_and_query(const char *path,\n                                    AVS_LIST(const anjay_string_t) *out_path,\n                                    AVS_LIST(const anjay_string_t) *out_query) {\n    assert(out_path);\n    assert(!*out_path);\n    assert(out_query);\n    assert(!*out_query);\n    int result = 0;\n    if (path) {\n        if (avs_url_validate_relative_path(path)) {\n            return -1;\n        }\n        if (*path == '/') {\n            ++path;\n        }\n        result = url_parse_chunks(&path, '/', '?',\n                                  URL_PARSE_HINT_SKIP_TRAILING_SEPARATOR,\n                                  out_path);\n        if (!result && *path == '?') {\n            ++path;\n            result = url_parse_chunks(&path, '&', '\\0', URL_PARSE_HINT_NONE,\n                                      out_query);\n        }\n    }\n    if (result) {\n        AVS_LIST_CLEAR(out_path);\n        AVS_LIST_CLEAR(out_query);\n    }\n    return result;\n}\n\nint _anjay_url_from_avs_url(const avs_url_t *avs_url,\n                            anjay_url_t *out_parsed_url) {\n    if (!avs_url) {\n        return -1;\n    }\n    int result = (avs_url_user(avs_url) || avs_url_password(avs_url)) ? -1 : 0;\n    if (!result) {\n        const char *host = avs_url_host(avs_url);\n        const char *port = avs_url_port(avs_url);\n        const char *path = avs_url_path(avs_url);\n        (void) ((result = copy_nullable_string(out_parsed_url->host,\n                                               sizeof(out_parsed_url->host),\n                                               host))\n                || (result = (port && !*port) ? -1 : 0)\n                || (result = copy_nullable_string(out_parsed_url->port,\n                                                  sizeof(out_parsed_url->port),\n                                                  avs_url_port(avs_url))));\n        if (!result) {\n            result =\n                    _anjay_url_parse_path_and_query(path,\n                                                    &out_parsed_url->uri_path,\n                                                    &out_parsed_url->uri_query);\n        }\n    }\n    if (!result && !out_parsed_url->port[0]) {\n        const anjay_transport_info_t *transport_info = NULL;\n        const char *protocol = avs_url_protocol(avs_url);\n        if (protocol) {\n            transport_info = _anjay_transport_info_by_uri_scheme(protocol);\n        }\n        if (transport_info && transport_info->default_port) {\n            assert(strlen(transport_info->default_port)\n                   < sizeof(out_parsed_url->port));\n            strcpy(out_parsed_url->port, transport_info->default_port);\n        }\n    }\n    return result;\n}\n\nint _anjay_url_parse(const char *raw_url, anjay_url_t *out_parsed_url) {\n    avs_url_t *avs_url = avs_url_parse_lenient(raw_url);\n    int result = _anjay_url_from_avs_url(avs_url, out_parsed_url);\n    avs_url_free(avs_url);\n    return result;\n}\n\nvoid _anjay_url_cleanup(anjay_url_t *url) {\n    AVS_LIST_CLEAR(&url->uri_path);\n    AVS_LIST_CLEAR(&url->uri_query);\n}\n\nAVS_LIST(const anjay_string_t) _anjay_make_string_list(const char *string,\n                                                       ... /* strings */) {\n    va_list list;\n    va_start(list, string);\n\n    AVS_LIST(anjay_string_t) strings_list = NULL;\n    AVS_LIST(anjay_string_t) *strings_list_endptr = &strings_list;\n    const char *str = string;\n\n    while (str) {\n        size_t len = strlen(str) + 1;\n        if (!(*strings_list_endptr =\n                      (AVS_LIST(anjay_string_t)) AVS_LIST_NEW_BUFFER(len))) {\n            _anjay_log_oom();\n            AVS_LIST_CLEAR(&strings_list);\n            break;\n        }\n\n        memcpy((*strings_list_endptr)->c_str, str, len);\n        AVS_LIST_ADVANCE_PTR(&strings_list_endptr);\n        str = va_arg(list, const char *);\n    }\n\n    va_end(list);\n    return strings_list;\n}\n\nstatic bool is_valid_lwm2m_1_0_binding_mode(const char *binding_mode) {\n    static const char *const VALID_BINDINGS[] = { \"U\",  \"UQ\", \"S\",\n                                                  \"SQ\", \"US\", \"UQS\" };\n    for (size_t i = 0; i < AVS_ARRAY_SIZE(VALID_BINDINGS); ++i) {\n        if (strcmp(binding_mode, VALID_BINDINGS[i]) == 0) {\n            return true;\n        }\n    }\n\n    return false;\n}\n\nstatic const anjay_binding_info_t BINDING_INFOS[] = {\n    { 'U', ANJAY_SOCKET_TRANSPORT_UDP },\n    { 'S', ANJAY_SOCKET_TRANSPORT_SMS },\n#ifdef ANJAY_WITH_LWM2M11\n    { 'T', ANJAY_SOCKET_TRANSPORT_TCP },\n    { 'N', ANJAY_SOCKET_TRANSPORT_NIDD }\n#endif // ANJAY_WITH_LWM2M11\n};\n\nconst anjay_binding_info_t *\n_anjay_binding_info_by_transport(anjay_socket_transport_t transport) {\n    for (size_t i = 0; i < AVS_ARRAY_SIZE(BINDING_INFOS); ++i) {\n        if (BINDING_INFOS[i].transport == transport) {\n            return &BINDING_INFOS[i];\n        }\n    }\n\n    AVS_UNREACHABLE(\"anjay_socket_transport_t value missing in BINDING_INFOS\");\n    return NULL;\n}\n\n#if defined(ANJAY_WITH_LWM2M11)\nconst anjay_binding_info_t *_anjay_binding_info_by_letter(char letter) {\n    for (size_t i = 0; i < AVS_ARRAY_SIZE(BINDING_INFOS); ++i) {\n        if (BINDING_INFOS[i].letter == letter) {\n            return &BINDING_INFOS[i];\n        }\n    }\n\n    return NULL;\n}\n#endif // defined(ANJAY_WITH_LWM2M11) || defined(ANJAY_WITH_CORE_PERSISTENCE)\n\n#ifdef ANJAY_WITH_LWM2M11\nstatic bool is_valid_lwm2m_1_1_binding_mode(const char *binding_mode) {\n    for (const char *p = binding_mode; *p; ++p) {\n        if (_anjay_binding_info_by_letter(*p) == NULL) {\n            anjay_log(DEBUG, _(\"unexpected character in binding mode: \") \"%c\",\n                      *p);\n            return false;\n        } else if (strchr(p + 1, *p)) {\n            anjay_log(DEBUG, _(\"duplicate character in binding mode: \") \"%c\",\n                      *p);\n            return false;\n        }\n    }\n\n    return true;\n}\n#endif // ANJAY_WITH_LWM2M11\n\nbool anjay_binding_mode_valid(const char *binding_mode) {\n    return is_valid_lwm2m_1_0_binding_mode(binding_mode)\n#ifdef ANJAY_WITH_LWM2M11\n           || is_valid_lwm2m_1_1_binding_mode(binding_mode)\n#endif // ANJAY_WITH_LWM2M11\n            ;\n}\n\n#ifdef ANJAY_WITH_LWM2M11\nconst char *_anjay_lwm2m_version_as_string(anjay_lwm2m_version_t version) {\n    switch (version) {\n    case ANJAY_LWM2M_VERSION_1_0:\n        return \"1.0\";\n    case ANJAY_LWM2M_VERSION_1_1:\n        return \"1.1\";\n#    ifdef ANJAY_WITH_LWM2M12\n    case ANJAY_LWM2M_VERSION_1_2:\n        return \"1.2\";\n#    endif // ANJAY_WITH_LWM2M12\n    }\n    AVS_UNREACHABLE(\"The switch statement above is supposed to be exhaustive\");\n    return NULL;\n}\n#endif // ANJAY_WITH_LWM2M11\n\nbool _anjay_socket_is_online(avs_net_socket_t *socket) {\n    if (!socket) {\n        return false;\n    }\n    avs_net_socket_opt_value_t opt;\n    if (avs_is_err(avs_net_socket_get_opt(socket, AVS_NET_SOCKET_OPT_STATE,\n                                          &opt))) {\n        anjay_log(DEBUG, _(\"Could not get socket state\"));\n        return false;\n    }\n    return opt.state == AVS_NET_SOCKET_STATE_CONNECTED;\n}\n\navs_error_t _anjay_coap_add_query_options(avs_coap_options_t *opts,\n                                          const anjay_lwm2m_version_t *version,\n                                          const char *endpoint_name,\n                                          const int64_t *lifetime,\n                                          const char *binding_mode,\n                                          bool lwm2m11_queue_mode,\n                                          const char *sms_msisdn) {\n    avs_error_t err;\n    if (version\n            && avs_is_err(\n                       (err = avs_coap_options_add_string_f(\n                                opts, AVS_COAP_OPTION_URI_QUERY, \"lwm2m=%s\",\n                                _anjay_lwm2m_version_as_string(*version))))) {\n        return err;\n    }\n\n    if (endpoint_name\n            && avs_is_err((err = avs_coap_options_add_string_f(\n                                   opts, AVS_COAP_OPTION_URI_QUERY, \"ep=%s\",\n                                   endpoint_name)))) {\n        return err;\n    }\n\n    assert(lifetime == NULL || *lifetime >= 0);\n    if (lifetime\n            && avs_is_err((err = avs_coap_options_add_string_f(\n                                   opts, AVS_COAP_OPTION_URI_QUERY, \"lt=%s\",\n                                   AVS_INT64_AS_STRING(*lifetime))))) {\n        return err;\n    }\n\n    if (binding_mode\n            && avs_is_err((err = avs_coap_options_add_string_f(\n                                   opts, AVS_COAP_OPTION_URI_QUERY, \"b=%s\",\n                                   binding_mode)))) {\n        return err;\n    }\n\n    (void) lwm2m11_queue_mode;\n#ifdef ANJAY_WITH_LWM2M11\n    if (lwm2m11_queue_mode\n            && avs_is_err((err = avs_coap_options_add_string(\n                                   opts, AVS_COAP_OPTION_URI_QUERY, \"Q\")))) {\n        return err;\n    }\n#endif // ANJAY_WITH_LWM2M11\n\n    (void) sms_msisdn;\n\n    return AVS_OK;\n}\n\navs_error_t\n_anjay_coap_add_string_options(avs_coap_options_t *opts,\n                               AVS_LIST(const anjay_string_t) strings,\n                               uint16_t opt_number) {\n    AVS_LIST(const anjay_string_t) it;\n    AVS_LIST_FOREACH(it, strings) {\n        avs_error_t err =\n                avs_coap_options_add_string(opts, opt_number, it->c_str);\n        if (avs_is_err(err)) {\n            return err;\n        }\n    }\n    return AVS_OK;\n}\n\nstatic const anjay_transport_info_t TRANSPORTS[] = {\n    {\n        .transport = ANJAY_SOCKET_TRANSPORT_UDP,\n        .socket_type = &(const avs_net_socket_type_t) { AVS_NET_UDP_SOCKET },\n        .uri_scheme = \"coap\",\n        .default_port = \"5683\",\n        .security = ANJAY_TRANSPORT_NOSEC\n    },\n    {\n        .transport = ANJAY_SOCKET_TRANSPORT_UDP,\n        .socket_type = &(const avs_net_socket_type_t) { AVS_NET_DTLS_SOCKET },\n        .uri_scheme = \"coaps\",\n        .default_port = \"5684\",\n        .security = ANJAY_TRANSPORT_ENCRYPTED\n    },\n    {\n        .transport = ANJAY_SOCKET_TRANSPORT_TCP,\n        .socket_type = &(const avs_net_socket_type_t) { AVS_NET_TCP_SOCKET },\n        .uri_scheme = \"coap+tcp\",\n        .default_port = \"5683\",\n        .security = ANJAY_TRANSPORT_NOSEC\n    },\n    {\n        .transport = ANJAY_SOCKET_TRANSPORT_TCP,\n        .socket_type = &(const avs_net_socket_type_t) { AVS_NET_SSL_SOCKET },\n        .uri_scheme = \"coaps+tcp\",\n        .default_port = \"5684\",\n        .security = ANJAY_TRANSPORT_ENCRYPTED\n    },\n    {\n        .transport = ANJAY_SOCKET_TRANSPORT_SMS,\n        .socket_type = NULL,\n        .uri_scheme = ANJAY_SMS_URI_SCHEME,\n        .default_port = \"\",\n        .security = ANJAY_TRANSPORT_SECURITY_UNDEFINED\n    },\n#ifdef ANJAY_WITH_LWM2M11\n    {\n        .transport = ANJAY_SOCKET_TRANSPORT_NIDD,\n        .socket_type = NULL,\n        .uri_scheme = \"coap+nidd\",\n        .default_port = \"\",\n        .security = ANJAY_TRANSPORT_NOSEC\n    },\n    {\n        .transport = ANJAY_SOCKET_TRANSPORT_NIDD,\n        .socket_type = NULL,\n        .uri_scheme = \"coaps+nidd\",\n        .default_port = \"\",\n        .security = ANJAY_TRANSPORT_ENCRYPTED\n    }\n#endif // ANJAY_WITH_LWM2M11\n};\n\nconst anjay_transport_info_t *\n_anjay_transport_info_by_uri_scheme(const char *uri_or_scheme) {\n    if (!uri_or_scheme) {\n        anjay_log(ERROR, _(\"URL scheme not specified\"));\n        return NULL;\n    }\n\n    for (size_t i = 0; i < AVS_ARRAY_SIZE(TRANSPORTS); ++i) {\n        size_t scheme_size = strlen(TRANSPORTS[i].uri_scheme);\n        if (avs_strncasecmp(uri_or_scheme, TRANSPORTS[i].uri_scheme,\n                            scheme_size)\n                        == 0\n                && (uri_or_scheme[scheme_size] == '\\0'\n                    || uri_or_scheme[scheme_size] == ':')) {\n            return &TRANSPORTS[i];\n        }\n    }\n\n    anjay_log(WARNING, _(\"unsupported URI scheme: \") \"%s\", uri_or_scheme);\n    return NULL;\n}\n\nint _anjay_copy_tls_ciphersuites(avs_net_socket_tls_ciphersuites_t *dest,\n                                 const avs_net_socket_tls_ciphersuites_t *src) {\n    assert(!dest->ids);\n    if (src->num_ids) {\n        if (!(dest->ids = (uint32_t *) avs_calloc(src->num_ids,\n                                                  sizeof(*dest->ids)))) {\n            _anjay_log_oom();\n            return -1;\n        }\n        memcpy(dest->ids, src->ids, src->num_ids * sizeof(*src->ids));\n    }\n    dest->num_ids = src->num_ids;\n    return 0;\n}\n\n#define DEFAULT_COAPS_PORT \"5684\"\n#define DEFAULT_COAP_PORT \"5683\"\n\nstatic bool url_service_matches(const avs_url_t *left,\n                                const avs_url_t *right,\n                                const char *default_port) {\n    const char *protocol_left = avs_url_protocol(left);\n    const char *protocol_right = avs_url_protocol(right);\n    // NULL protocol means that the URL is protocol-relative (e.g.\n    // //avsystem.com). In that case protocol is essentially undefined (i.e.,\n    // dependent on where such link is contained). We don't consider two\n    // undefined protocols as equivalent, similar to comparing NaNs.\n    if (!protocol_left || !protocol_right\n            || strcmp(protocol_left, protocol_right) != 0) {\n        return false;\n    }\n    const char *port_left = avs_url_port(left);\n    const char *port_right = avs_url_port(right);\n    if (!port_left) {\n        port_left = default_port;\n    }\n    if (!port_right) {\n        port_right = default_port;\n    }\n    return strcmp(port_left, port_right) == 0;\n}\n\ntypedef union {\n    struct {\n        anjay_security_config_t *out;\n        anjay_security_config_cache_t *cache;\n        bool config_found;\n    } security;\n    struct {\n        avs_coap_ctx_t *coap;\n        // socket used by the `coap` instance above\n        avs_net_socket_t *socket;\n    } socket_info;\n} security_or_socket_info_t;\n\ntypedef int\ntry_security_instance_callback_t(anjay_unlocked_t *anjay,\n                                 security_or_socket_info_t *out_info,\n                                 anjay_ssid_t ssid,\n                                 anjay_iid_t security_iid,\n                                 const avs_url_t *url,\n                                 const avs_url_t *server_url);\n\ntypedef struct {\n    security_or_socket_info_t *info;\n    const avs_url_t *url;\n    try_security_instance_callback_t *clb;\n} try_security_instance_args_t;\n\nstatic bool has_valid_keys(const avs_net_security_info_t *info) {\n    switch (info->mode) {\n    case AVS_NET_SECURITY_CERTIFICATE:\n        return info->data.cert.server_cert_validation\n               || info->data.cert.client_cert.desc.info.buffer.buffer_size > 0\n               || info->data.cert.client_key.desc.info.buffer.buffer_size > 0;\n    case AVS_NET_SECURITY_PSK:\n        return info->data.psk.identity.desc.source\n                       != AVS_CRYPTO_DATA_SOURCE_EMPTY\n               || info->data.psk.key.desc.source\n                          != AVS_CRYPTO_DATA_SOURCE_EMPTY;\n    }\n    return false;\n}\n\nstatic int\ntry_security_instance_read_security(anjay_unlocked_t *anjay,\n                                    security_or_socket_info_t *out_info,\n                                    anjay_ssid_t ssid,\n                                    anjay_iid_t security_iid,\n                                    const avs_url_t *url,\n                                    const avs_url_t *server_url) {\n    anjay_security_config_t new_result;\n    anjay_security_config_cache_t cache_backup = *out_info->security.cache;\n    memset(out_info->security.cache, 0, sizeof(*out_info->security.cache));\n    if (avs_is_err(_anjay_get_security_config(anjay, &new_result,\n                                              out_info->security.cache, ssid,\n                                              security_iid))) {\n        anjay_log(WARNING,\n                  _(\"Could not read security information for \"\n                    \"server \") \"/%\" PRIu16 \"/%\" PRIu16,\n                  ANJAY_DM_OID_SECURITY, security_iid);\n    } else if (!has_valid_keys(&new_result.security_info)\n               && !new_result.dane_tlsa_record) {\n        anjay_log(DEBUG,\n                  _(\"Server \") \"/%\" PRIu16\n                               \"/%\" PRIu16 _(\" does not use encrypted \"\n                                             \"connection, ignoring\"),\n                  ANJAY_DM_OID_SECURITY, security_iid);\n    } else {\n        _anjay_security_config_cache_cleanup(&cache_backup);\n        *out_info->security.out = new_result;\n        out_info->security.config_found = true;\n        if (url_service_matches(server_url, url, DEFAULT_COAPS_PORT)) {\n            // this is the best match we could get\n            return ANJAY_FOREACH_BREAK;\n        }\n        // and here we are left with \"some match\", not necessarily the best\n        // one, and thus we'll continue looking\n        return ANJAY_FOREACH_CONTINUE;\n    }\n    _anjay_security_config_cache_cleanup(out_info->security.cache);\n    *out_info->security.cache = cache_backup;\n    return ANJAY_FOREACH_CONTINUE;\n}\n\nstatic int\ntry_security_instance_get_coap_and_socket(anjay_unlocked_t *anjay,\n                                          security_or_socket_info_t *out_info,\n                                          anjay_ssid_t ssid,\n                                          anjay_iid_t security_iid,\n                                          const avs_url_t *url,\n                                          const avs_url_t *server_url) {\n    (void) security_iid;\n    const anjay_transport_info_t *transport_info =\n            _anjay_transport_info_by_uri_scheme(avs_url_protocol(url));\n    if (!transport_info\n            || !url_service_matches(server_url, url,\n                                    transport_info->default_port)) {\n        return ANJAY_FOREACH_CONTINUE;\n    }\n    int result = ANJAY_FOREACH_CONTINUE;\n    AVS_LIST(const anjay_socket_entry_t) socket_entries =\n            _anjay_collect_socket_entries(anjay, /* include_offline = */ false);\n    AVS_LIST(const anjay_socket_entry_t) it;\n    AVS_LIST_FOREACH(it, socket_entries) {\n        if (it->ssid == ssid) {\n            anjay_connection_ref_t connection = {\n                .server = _anjay_servers_find_by_primary_socket(anjay,\n                                                                it->socket),\n                .conn_type = ANJAY_CONNECTION_PRIMARY\n            };\n            if (!connection.server) {\n                break;\n            }\n            out_info->socket_info.coap = _anjay_connection_get_coap(connection);\n            out_info->socket_info.socket = it->socket;\n            anjay_log(DEBUG,\n                      _(\"using coap context of SSID=\") \"%\" PRIu16 _(\n                              \" to conduct the download\"),\n                      ssid);\n            result = ANJAY_FOREACH_BREAK;\n        }\n    }\n    AVS_LIST_CLEAR(&socket_entries);\n    return result;\n}\n\nstatic bool optional_strings_equal(const char *left, const char *right) {\n    if (left && right) {\n        return strcmp(left, right) == 0;\n    } else {\n        return !left && !right;\n    }\n}\n\nstatic int try_security_instance(anjay_unlocked_t *anjay,\n                                 const anjay_dm_installed_object_t *obj,\n                                 anjay_iid_t security_iid,\n                                 void *args_) {\n    (void) obj;\n    try_security_instance_args_t *args = (try_security_instance_args_t *) args_;\n\n    char raw_server_url[ANJAY_MAX_URL_RAW_LENGTH];\n    const anjay_uri_path_t path =\n            MAKE_RESOURCE_PATH(ANJAY_DM_OID_SECURITY, security_iid,\n                               ANJAY_DM_RID_SECURITY_SERVER_URI);\n\n    if (_anjay_dm_read_resource_string(anjay, &path, raw_server_url,\n                                       sizeof(raw_server_url))) {\n        anjay_log(WARNING, _(\"could not read LwM2M server URI from \") \"%s\",\n                  ANJAY_DEBUG_MAKE_PATH(&path));\n        return ANJAY_FOREACH_CONTINUE;\n    }\n\n    avs_url_t *server_url = avs_url_parse_lenient(raw_server_url);\n    if (!server_url) {\n        anjay_log(WARNING, _(\"Could not parse URL from \") \"%s\" _(\": \") \"%s\",\n                  ANJAY_DEBUG_MAKE_PATH(&path), raw_server_url);\n        return ANJAY_FOREACH_CONTINUE;\n    }\n\n    int retval = ANJAY_FOREACH_CONTINUE;\n    if (optional_strings_equal(avs_url_host(server_url),\n                               avs_url_host(args->url))) {\n        anjay_ssid_t ssid;\n        if (!_anjay_ssid_from_security_iid(anjay, security_iid, &ssid)) {\n            retval = args->clb(anjay, args->info, ssid, security_iid, args->url,\n                               server_url);\n        }\n    }\n\n    avs_url_free(server_url);\n    return retval;\n}\n\nstatic void try_get_info_from_dm(anjay_unlocked_t *anjay,\n                                 const char *raw_url,\n                                 security_or_socket_info_t *out_info,\n                                 try_security_instance_callback_t *clb) {\n    assert(anjay);\n\n    const anjay_dm_installed_object_t *security_obj =\n            _anjay_dm_find_object_by_oid(&anjay->dm, ANJAY_DM_OID_SECURITY);\n    if (!security_obj) {\n        anjay_log(ERROR, _(\"Security object not installed\"));\n        return;\n    }\n\n    avs_url_t *url = avs_url_parse_lenient(raw_url);\n    if (!url) {\n        anjay_log(ERROR, _(\"Could not parse URL: \") \"%s\", raw_url);\n        return;\n    }\n    try_security_instance_args_t args = {\n        .info = out_info,\n        .url = url,\n        .clb = clb\n    };\n    _anjay_dm_foreach_instance(anjay, security_obj, try_security_instance,\n                               &args);\n    avs_url_free(url);\n}\n\nint _anjay_security_config_from_dm_unlocked(anjay_unlocked_t *anjay,\n                                            anjay_security_config_t *out_config,\n                                            const char *raw_url) {\n    security_or_socket_info_t info = {\n        .security = {\n            .out = out_config,\n            .cache = &anjay->security_config_from_dm_cache\n        }\n    };\n    try_get_info_from_dm(anjay, raw_url, &info,\n                         try_security_instance_read_security);\n    if (!info.security.config_found) {\n        anjay_log(WARNING,\n                  _(\"Matching security information not found in data model for \"\n                    \"URL: \") \"%s\",\n                  raw_url);\n        return -1;\n    }\n    return 0;\n}\n\nint anjay_security_config_from_dm(anjay_t *anjay_locked,\n                                  anjay_security_config_t *out_config,\n                                  const char *raw_url) {\n    int result = -1;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    result =\n            _anjay_security_config_from_dm_unlocked(anjay, out_config, raw_url);\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return result;\n}\n\n#ifdef ANJAY_WITH_LWM2M11\nconst anjay_trust_store_t *\n_anjay_get_trust_store(anjay_unlocked_t *anjay,\n                       anjay_ssid_t for_ssid,\n                       anjay_security_mode_t security_mode) {\n    (void) for_ssid;\n    (void) security_mode;\n    if (_anjay_trust_store_valid(&anjay->initial_trust_store)) {\n        return &anjay->initial_trust_store;\n    }\n    return NULL;\n}\n\nanjay_security_config_t\n_anjay_security_config_pkix_unlocked(anjay_unlocked_t *anjay) {\n    avs_net_certificate_info_t cert_info;\n    memset(&cert_info, 0, sizeof(cert_info));\n    const anjay_trust_store_t *trust_store =\n            _anjay_get_trust_store(anjay, 0, ANJAY_SECURITY_CERTIFICATE);\n    if (trust_store) {\n        cert_info.server_cert_validation = true;\n        cert_info.ignore_system_trust_store = !trust_store->use_system_wide;\n        cert_info.trusted_certs =\n                avs_crypto_certificate_chain_info_from_list(trust_store->certs);\n        cert_info.cert_revocation_lists =\n                avs_crypto_cert_revocation_list_info_from_list(\n                        trust_store->crls);\n    }\n    return (anjay_security_config_t) {\n        .security_info = avs_net_security_info_from_certificates(cert_info),\n        .tls_ciphersuites = anjay->default_tls_ciphersuites,\n    };\n}\n\nanjay_security_config_t anjay_security_config_pkix(anjay_t *anjay_locked) {\n    avs_net_certificate_info_t cert_info = { 0 };\n    anjay_security_config_t result = {\n        .security_info = avs_net_security_info_from_certificates(cert_info)\n    };\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    result = _anjay_security_config_pkix_unlocked(anjay);\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return result;\n}\n#endif // ANJAY_WITH_LWM2M11\n\nvoid _anjay_find_matching_coap_context_and_socket(\n        anjay_unlocked_t *anjay,\n        const char *raw_url,\n        avs_coap_ctx_t **out_coap,\n        avs_net_socket_t **out_socket) {\n    security_or_socket_info_t info;\n    memset(&info, 0, sizeof(info));\n    try_get_info_from_dm(anjay, raw_url, &info,\n                         try_security_instance_get_coap_and_socket);\n\n    if (!info.socket_info.coap) {\n        anjay_log(WARNING,\n                  _(\"Matching CoAP Context not found in data model for \"\n                    \"URL: \") \"%s\",\n                  raw_url);\n    }\n    assert(!!info.socket_info.coap == !!info.socket_info.socket);\n    *out_coap = info.socket_info.coap;\n    *out_socket = info.socket_info.socket;\n}\n\nstatic int map_str_conversion_result(const char *input, const char *endptr) {\n    return (!*input || isspace((unsigned char) *input) || errno || !endptr\n            || *endptr)\n                   ? -1\n                   : 0;\n}\n\nint _anjay_safe_strtoll(const char *in, long long *value) {\n    errno = 0;\n    char *endptr = NULL;\n    *value = strtoll(in, &endptr, 10);\n    return map_str_conversion_result(in, endptr);\n}\n\nint _anjay_safe_strtoull(const char *in, unsigned long long *value) {\n    errno = 0;\n    char *endptr = NULL;\n    *value = strtoull(in, &endptr, 10);\n    return map_str_conversion_result(in, endptr);\n}\n\nint _anjay_safe_strtod(const char *in, double *value) {\n    errno = 0;\n    char *endptr = NULL;\n    *value = strtod(in, &endptr);\n    return map_str_conversion_result(in, endptr);\n}\n\n// || defined(ANJAY_WITH_CORE_PERSISTENCE))\n\nvoid _anjay_log_oom(void) {\n    anjay_log(ERROR, _(\"out of memory\"));\n}\n\nanjay_dm_t *_anjay_get_dm(anjay_unlocked_t *anjay) {\n    return &anjay->dm;\n}\n\n#ifdef ANJAY_TEST\n#    include \"tests/core/utils.c\"\n#endif // ANJAY_TEST\n"
  },
  {
    "path": "src/core/anjay_utils_private.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ANJAY_UTILS_PRIVATE_H\n#define ANJAY_UTILS_PRIVATE_H\n\n#include <avsystem/commons/avs_list.h>\n#include <avsystem/commons/avs_persistence.h>\n#include <avsystem/commons/avs_socket.h>\n#include <avsystem/commons/avs_utils.h>\n\n#include <avsystem/coap/option.h>\n#include <avsystem/coap/token.h>\n\n#include <anjay_modules/anjay_raw_buffer.h>\n#include <anjay_modules/anjay_utils_core.h>\n\n#include <anjay/dm.h>\n\n#include <stdbool.h>\n#include <stddef.h>\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\n#define anjay_log(...) _anjay_log(anjay, __VA_ARGS__)\n\nint _anjay_safe_strtoll(const char *in, long long *value);\nint _anjay_safe_strtoull(const char *in, unsigned long long *value);\nint _anjay_safe_strtod(const char *in, double *value);\n\nAVS_LIST(const anjay_string_t)\n_anjay_make_string_list(const char *string, ... /* strings */) AVS_F_SENTINEL;\n\n// Const pointer cast is to ensure, that passed NULL will have the proper type,\n// regardless of toolchain used\n#define ANJAY_MAKE_STRING_LIST(...) \\\n    _anjay_make_string_list(__VA_ARGS__, (const char *) NULL)\n\n#ifdef ANJAY_WITH_LWM2M11\nconst char *_anjay_lwm2m_version_as_string(anjay_lwm2m_version_t version);\n#else // ANJAY_WITH_LWM2M11\ntypedef enum { ANJAY_LWM2M_DUMMY_VERSION } anjay_lwm2m_dummy_version_t;\n#    define anjay_lwm2m_version_t anjay_lwm2m_dummy_version_t\n#    define ANJAY_LWM2M_VERSION_1_0 ANJAY_LWM2M_DUMMY_VERSION\n\nstatic inline const char *\n_anjay_lwm2m_version_as_string(anjay_lwm2m_version_t version) {\n    (void) version;\n    return \"1.0\";\n}\n#endif // ANJAY_WITH_LWM2M11\n\nbool _anjay_socket_is_online(avs_net_socket_t *socket);\n\navs_error_t _anjay_coap_add_query_options(avs_coap_options_t *opts,\n                                          const anjay_lwm2m_version_t *version,\n                                          const char *endpoint_name,\n                                          const int64_t *lifetime,\n                                          const char *binding_mode,\n                                          bool lwm2m11_queue_mode,\n                                          const char *sms_msisdn);\n\navs_error_t\n_anjay_coap_add_string_options(avs_coap_options_t *opts,\n                               AVS_LIST(const anjay_string_t) strings,\n                               uint16_t opt_number);\n\nstatic inline size_t _anjay_max_power_of_2_not_greater_than(size_t bound) {\n    int exponent = -1;\n    while (bound) {\n        bound >>= 1;\n        ++exponent;\n    }\n    return (exponent >= 0) ? ((size_t) 1 << exponent) : 0;\n}\n\nstatic inline const char *_anjay_token_to_string(const avs_coap_token_t *token,\n                                                 char *out_buffer,\n                                                 size_t out_size) {\n    if (avs_hexlify(out_buffer, out_size, NULL, token->bytes, token->size)) {\n        AVS_UNREACHABLE(\"avs_hexlify() failed\");\n    }\n    return out_buffer;\n}\n\n#define ANJAY_TOKEN_TO_STRING(token)                                       \\\n    _anjay_token_to_string(&(token),                                       \\\n                           &(char[sizeof((token).bytes) * 2 + 1]){ 0 }[0], \\\n                           sizeof((token).bytes) * 2 + 1)\n\nstatic inline bool _anjay_was_session_resumed(avs_net_socket_t *socket) {\n    avs_net_socket_opt_value_t session_resumed;\n    if (avs_is_err(avs_net_socket_get_opt(socket,\n                                          AVS_NET_SOCKET_OPT_SESSION_RESUMED,\n                                          &session_resumed))) {\n        return false;\n    }\n    return session_resumed.flag;\n}\n\nstatic inline bool _anjay_was_connection_id_resumed(avs_net_socket_t *socket) {\n    avs_net_socket_opt_value_t connection_id_resumed;\n    if (avs_is_err(\n                avs_net_socket_get_opt(socket,\n                                       AVS_NET_SOCKET_OPT_CONNECTION_ID_RESUMED,\n                                       &connection_id_resumed))) {\n        return false;\n    }\n    return connection_id_resumed.flag;\n}\n\nint _anjay_copy_tls_ciphersuites(avs_net_socket_tls_ciphersuites_t *dest,\n                                 const avs_net_socket_tls_ciphersuites_t *src);\n\nanjay_transport_set_t\n_anjay_transport_set_remove_unavailable(anjay_unlocked_t *anjay,\n                                        anjay_transport_set_t set);\n\nbool _anjay_socket_transport_included(anjay_transport_set_t set,\n                                      anjay_socket_transport_t transport);\n\nbool _anjay_socket_transport_is_online(anjay_unlocked_t *anjay,\n                                       anjay_socket_transport_t transport);\n\n// || defined(ANJAY_WITH_CORE_PERSISTENCE))\n\n#define ANJAY_SMS_URI_SCHEME \"tel\"\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif // ANJAY_UTILS_PRIVATE_H\n"
  },
  {
    "path": "src/core/attr_storage/anjay_attr_storage.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#ifdef ANJAY_WITH_ATTR_STORAGE\n\n#    include <assert.h>\n#    include <inttypes.h>\n#    include <math.h>\n#    include <string.h>\n\n#    include <avsystem/commons/avs_stream_membuf.h>\n\n#    include <anjay_modules/anjay_dm_utils.h>\n#    include <anjay_modules/anjay_raw_buffer.h>\n#    ifdef ANJAY_WITH_LWM2M_GATEWAY\n#        include <anjay_modules/anjay_lwm2m_gateway.h>\n#    endif // ANJAY_WITH_LWM2M_GATEWAY\n\n#    include \"../anjay_core.h\"\n\n#    define ANJAY_ATTR_STORAGE_INTERNALS\n\n#    include \"anjay_attr_storage_private.h\"\n\nVISIBILITY_SOURCE_BEGIN\n\n//// LIFETIME AND OBJECT HANDLING //////////////////////////////////////////////\n\nint _anjay_attr_storage_init(anjay_attr_storage_t *as, anjay_dm_t *dm) {\n    assert(as);\n    if (!(as->saved_state.persist_data = avs_stream_membuf_create())) {\n        return -1;\n    }\n    as->dm = dm;\n    return 0;\n}\n\nvoid _anjay_attr_storage_cleanup(anjay_attr_storage_t *as) {\n    assert(as);\n    _anjay_attr_storage_clear(as);\n    avs_stream_cleanup(&as->saved_state.persist_data);\n}\n\nbool anjay_attr_storage_is_modified(anjay_t *anjay_locked) {\n    bool result = false;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    result = anjay->attr_storage.modified_since_persist;\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return result;\n}\n\nvoid _anjay_attr_storage_clear(anjay_attr_storage_t *as) {\n    while (as->objects) {\n        remove_object_entry(as, &as->objects);\n    }\n}\n\nvoid anjay_attr_storage_purge(anjay_t *anjay_locked) {\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    _anjay_attr_storage_clear(&anjay->attr_storage);\n    _anjay_attr_storage_mark_modified(&anjay->attr_storage);\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n}\n\n//// HELPERS ///////////////////////////////////////////////////////////////////\n\nAVS_STATIC_ASSERT(offsetof(as_object_entry_t, oid) == 0, object_id_offset);\nAVS_STATIC_ASSERT(offsetof(as_instance_entry_t, iid) == 0, instance_id_offset);\nAVS_STATIC_ASSERT(offsetof(as_resource_entry_t, rid) == 0, resource_id_offset);\nAVS_STATIC_ASSERT(offsetof(as_resource_instance_entry_t, riid) == 0,\n                  resource_instance_id_offset);\n\n#    ifdef ANJAY_WITH_LWM2M_GATEWAY\nstatic anjay_attr_storage_t *\nget_attr_storage(anjay_unlocked_t *anjay,\n                 const anjay_dm_installed_object_t *def_ptr) {\n    if (def_ptr->prefix) {\n        anjay_attr_storage_t *as = NULL;\n        _anjay_lwm2m_gateway_prefix_to_as(anjay, def_ptr->prefix, &as);\n        assert(as);\n        return as;\n    }\n    return &anjay->attr_storage;\n}\n#    else\nstatic inline anjay_attr_storage_t *\nget_attr_storage(anjay_unlocked_t *anjay,\n                 const anjay_dm_installed_object_t *def_ptr) {\n    (void) def_ptr;\n    return &anjay->attr_storage;\n}\n#    endif // ANJAY_WITH_LWM2M_GATEWAY\n\nstatic AVS_LIST(void) *\nfind_or_create_entry_impl(AVS_LIST(void) *children_list_ptr,\n                          size_t entry_size,\n                          uint16_t id,\n                          bool allow_create) {\n    AVS_LIST(void) *entry_ptr;\n    AVS_LIST_FOREACH_PTR(entry_ptr, children_list_ptr) {\n        if (*(uint16_t *) *entry_ptr >= id) {\n            break;\n        }\n    }\n    if (!*entry_ptr || *(uint16_t *) *entry_ptr != id) {\n        if (allow_create) {\n            AVS_LIST(void) new_entry = AVS_LIST_NEW_BUFFER(entry_size);\n            if (!new_entry) {\n                _anjay_log_oom();\n                return NULL;\n            }\n            *(uint16_t *) new_entry = id;\n            AVS_LIST_INSERT(entry_ptr, new_entry);\n        } else {\n            return NULL;\n        }\n    }\n    return entry_ptr;\n}\n\nstatic inline AVS_LIST(as_object_entry_t) *\nfind_object(anjay_attr_storage_t *parent, anjay_oid_t id) {\n    return (AVS_LIST(as_object_entry_t) *) find_or_create_entry_impl(\n            (AVS_LIST(void) *) &parent->objects, sizeof(as_object_entry_t), id,\n            false);\n}\n\nstatic inline AVS_LIST(as_object_entry_t) *\nfind_or_create_object(anjay_attr_storage_t *parent, anjay_oid_t id) {\n    return (AVS_LIST(as_object_entry_t) *) find_or_create_entry_impl(\n            (AVS_LIST(void) *) &parent->objects, sizeof(as_object_entry_t), id,\n            true);\n}\n\nstatic inline AVS_LIST(as_instance_entry_t) *\nfind_instance(as_object_entry_t *parent, anjay_iid_t id) {\n    return (AVS_LIST(as_instance_entry_t) *) find_or_create_entry_impl(\n            (AVS_LIST(void) *) &parent->instances, sizeof(as_instance_entry_t),\n            id, false);\n}\n\nstatic inline AVS_LIST(as_instance_entry_t) *\nfind_or_create_instance(as_object_entry_t *parent, anjay_iid_t id) {\n    return (AVS_LIST(as_instance_entry_t) *) find_or_create_entry_impl(\n            (AVS_LIST(void) *) &parent->instances, sizeof(as_instance_entry_t),\n            id, true);\n}\n\nstatic inline AVS_LIST(as_resource_entry_t) *\nfind_resource(as_instance_entry_t *parent, anjay_rid_t id) {\n    return (AVS_LIST(as_resource_entry_t) *) find_or_create_entry_impl(\n            (AVS_LIST(void) *) &parent->resources, sizeof(as_resource_entry_t),\n            id, false);\n}\n\nstatic inline AVS_LIST(as_resource_entry_t) *\nfind_or_create_resource(as_instance_entry_t *parent, anjay_rid_t id) {\n    return (AVS_LIST(as_resource_entry_t) *) find_or_create_entry_impl(\n            (AVS_LIST(void) *) &parent->resources, sizeof(as_resource_entry_t),\n            id, true);\n}\n\n#    ifdef ANJAY_WITH_LWM2M11\nstatic inline AVS_LIST(as_resource_instance_entry_t) *\nfind_resource_instance(as_resource_entry_t *parent, anjay_riid_t id) {\n    return (AVS_LIST(as_resource_instance_entry_t) *) find_or_create_entry_impl(\n            (AVS_LIST(void) *) &parent->resource_instances,\n            sizeof(as_resource_instance_entry_t), id, false);\n}\n\nstatic inline AVS_LIST(as_resource_instance_entry_t) *\nfind_or_create_resource_instance(as_resource_entry_t *parent, anjay_riid_t id) {\n    return (AVS_LIST(as_resource_instance_entry_t) *) find_or_create_entry_impl(\n            (AVS_LIST(void) *) &parent->resource_instances,\n            sizeof(as_resource_instance_entry_t), id, true);\n}\n#    endif // ANJAY_WITH_LWM2M11\n\nstatic void remove_instance_if_empty(AVS_LIST(as_instance_entry_t) *entry_ptr) {\n    if (!(*entry_ptr)->default_attrs && !(*entry_ptr)->resources) {\n        AVS_LIST_DELETE(entry_ptr);\n    }\n}\n\nstatic void remove_resource_if_empty(AVS_LIST(as_resource_entry_t) *entry_ptr) {\n    if (!(*entry_ptr)->attrs\n#    ifdef ANJAY_WITH_LWM2M11\n            && !(*entry_ptr)->resource_instances\n#    endif // ANJAY_WITH_LWM2M11\n    ) {\n        AVS_LIST_DELETE(entry_ptr);\n    }\n}\n\n#    ifdef ANJAY_WITH_LWM2M11\nstatic void remove_resource_instance_if_empty(\n        AVS_LIST(as_resource_instance_entry_t) *entry_ptr) {\n    if (!(*entry_ptr)->attrs) {\n        AVS_LIST_DELETE(entry_ptr);\n    }\n}\n#    endif // ANJAY_WITH_LWM2M11\n\nstatic inline bool is_ssid_reference_object(anjay_oid_t oid) {\n    return oid == ANJAY_DM_OID_SECURITY || oid == ANJAY_DM_OID_SERVER;\n}\n\nstatic inline anjay_rid_t ssid_rid(anjay_oid_t oid) {\n    switch (oid) {\n    case ANJAY_DM_OID_SECURITY:\n        return ANJAY_DM_RID_SECURITY_SSID;\n    case ANJAY_DM_OID_SERVER:\n        return ANJAY_DM_RID_SERVER_SSID;\n    default:\n        AVS_UNREACHABLE(\"Invalid object for Short Server ID query\");\n    }\n    as_log(ERROR, _(\"Could not get valid RID\"));\n    return (anjay_rid_t) -1;\n}\n\nstatic anjay_ssid_t\nquery_ssid(anjay_unlocked_t *anjay, anjay_oid_t oid, anjay_iid_t iid) {\n    if (!is_ssid_reference_object(oid)) {\n        return 0;\n    }\n    int64_t ssid;\n    const anjay_uri_path_t uri = MAKE_RESOURCE_PATH(oid, iid, ssid_rid(oid));\n    int result = _anjay_dm_read_resource_i64(anjay, &uri, &ssid);\n    if (result || ssid <= 0 || ssid >= UINT16_MAX) {\n        /* Most likely a Bootstrap instance, ignore. */\n        return 0;\n    }\n    return (anjay_ssid_t) ssid;\n}\n\nstatic void remove_attrs_entry(anjay_attr_storage_t *as,\n                               AVS_LIST(void) *attrs_ptr) {\n    AVS_LIST_DELETE(attrs_ptr);\n    _anjay_attr_storage_mark_modified(as);\n}\n\nstatic void\nremove_attrs_for_servers_not_on_list(anjay_attr_storage_t *as,\n                                     AVS_LIST(void) *attrs_ptr,\n                                     AVS_LIST(anjay_ssid_t) ssid_list) {\n    AVS_LIST(anjay_ssid_t) ssid_ptr = ssid_list;\n    while (*attrs_ptr) {\n        if (!ssid_ptr || *get_ssid_ptr(*attrs_ptr) < *ssid_ptr) {\n            remove_attrs_entry(as, attrs_ptr);\n        } else {\n            while (ssid_ptr && *get_ssid_ptr(*attrs_ptr) > *ssid_ptr) {\n                AVS_LIST_ADVANCE(&ssid_ptr);\n            }\n            if (ssid_ptr && *get_ssid_ptr(*attrs_ptr) == *ssid_ptr) {\n                AVS_LIST_ADVANCE(&ssid_ptr);\n                AVS_LIST_ADVANCE_PTR(&attrs_ptr);\n            }\n        }\n    }\n}\n\nstatic void remove_servers_not_on_ssid_list(anjay_attr_storage_t *as,\n                                            AVS_LIST(anjay_ssid_t) ssid_list) {\n    AVS_LIST(as_object_entry_t) *object_ptr;\n    AVS_LIST(as_object_entry_t) object_helper;\n    AVS_LIST_DELETABLE_FOREACH_PTR(object_ptr, object_helper, &as->objects) {\n        remove_attrs_for_servers_not_on_list(\n                as, (AVS_LIST(void) *) &(*object_ptr)->default_attrs,\n                ssid_list);\n        AVS_LIST(as_instance_entry_t) *instance_ptr;\n        AVS_LIST(as_instance_entry_t) instance_helper;\n        AVS_LIST_DELETABLE_FOREACH_PTR(instance_ptr, instance_helper,\n                                       &(*object_ptr)->instances) {\n            remove_attrs_for_servers_not_on_list(\n                    as, (AVS_LIST(void) *) &(*instance_ptr)->default_attrs,\n                    ssid_list);\n            AVS_LIST(as_resource_entry_t) *res_ptr;\n            AVS_LIST(as_resource_entry_t) res_helper;\n            AVS_LIST_DELETABLE_FOREACH_PTR(res_ptr, res_helper,\n                                           &(*instance_ptr)->resources) {\n                remove_attrs_for_servers_not_on_list(\n                        as, (AVS_LIST(void) *) &(*res_ptr)->attrs, ssid_list);\n#    ifdef ANJAY_WITH_LWM2M11\n                AVS_LIST(as_resource_instance_entry_t) *res_instance_ptr;\n                AVS_LIST(as_resource_instance_entry_t) res_instance_helper;\n                AVS_LIST_DELETABLE_FOREACH_PTR(\n                        res_instance_ptr, res_instance_helper,\n                        &(*res_ptr)->resource_instances) {\n                    remove_attrs_for_servers_not_on_list(\n                            as, (AVS_LIST(void) *) &(*res_instance_ptr)->attrs,\n                            ssid_list);\n\n                    remove_resource_instance_if_empty(res_instance_ptr);\n                }\n#    endif // ANJAY_WITH_LWM2M11\n                remove_resource_if_empty(res_ptr);\n            }\n            remove_instance_if_empty(instance_ptr);\n        }\n        remove_object_if_empty(object_ptr);\n    }\n}\n\nint _anjay_attr_storage_remove_absent_instances_clb(\n        anjay_unlocked_t *anjay,\n        const anjay_dm_installed_object_t *def_ptr,\n        anjay_iid_t iid,\n        void *instance_ptr_ptr_) {\n    (void) def_ptr;\n    anjay_attr_storage_t *as = get_attr_storage(anjay, def_ptr);\n    AVS_LIST(as_instance_entry_t) **instance_ptr_ptr =\n            (AVS_LIST(as_instance_entry_t) **) instance_ptr_ptr_;\n    if (**instance_ptr_ptr && (**instance_ptr_ptr)->iid < iid) {\n        while (**instance_ptr_ptr && (**instance_ptr_ptr)->iid < iid) {\n            remove_instance_entry(as, *instance_ptr_ptr);\n        }\n    }\n    if (**instance_ptr_ptr && (**instance_ptr_ptr)->iid == iid) {\n        AVS_LIST_ADVANCE_PTR(instance_ptr_ptr);\n    }\n    return 0;\n}\n\nstatic int\nremove_absent_resources_clb(anjay_unlocked_t *anjay,\n                            const anjay_dm_installed_object_t *def_ptr,\n                            anjay_iid_t iid,\n                            anjay_rid_t rid,\n                            anjay_dm_resource_kind_t kind,\n                            anjay_dm_resource_presence_t presence,\n                            void *resource_ptr_ptr_) {\n    (void) anjay;\n    (void) def_ptr;\n    (void) iid;\n    (void) kind;\n    anjay_attr_storage_t *as = get_attr_storage(anjay, def_ptr);\n    AVS_LIST(as_resource_entry_t) **resource_ptr_ptr =\n            (AVS_LIST(as_resource_entry_t) **) resource_ptr_ptr_;\n    while (**resource_ptr_ptr && (**resource_ptr_ptr)->rid < rid) {\n        remove_resource_entry(as, *resource_ptr_ptr);\n    }\n    if (**resource_ptr_ptr && (**resource_ptr_ptr)->rid == rid) {\n        if (presence == ANJAY_DM_RES_ABSENT) {\n            remove_resource_entry(as, *resource_ptr_ptr);\n        } else {\n            AVS_LIST_ADVANCE_PTR(resource_ptr_ptr);\n        }\n    }\n    return 0;\n}\n\nint _anjay_attr_storage_remove_absent_resources(\n        anjay_unlocked_t *anjay,\n        AVS_LIST(as_instance_entry_t) *instance_ptr,\n        const anjay_dm_installed_object_t *def_ptr) {\n    anjay_attr_storage_t *as = get_attr_storage(anjay, def_ptr);\n    AVS_LIST(as_resource_entry_t) *resource_ptr = &(*instance_ptr)->resources;\n    int result = 0;\n    if (def_ptr) {\n        result =\n                _anjay_dm_foreach_resource(anjay, def_ptr, (*instance_ptr)->iid,\n                                           remove_absent_resources_clb,\n                                           &resource_ptr);\n    }\n    if (!result) {\n        while (*resource_ptr) {\n            remove_resource_entry(as, resource_ptr);\n        }\n    }\n    remove_instance_if_empty(instance_ptr);\n    return result;\n}\n\n#    ifdef ANJAY_WITH_LWM2M11\nstatic int\nremove_absent_resource_instances_clb(anjay_unlocked_t *anjay,\n                                     const anjay_dm_installed_object_t *def_ptr,\n                                     anjay_iid_t iid,\n                                     anjay_rid_t rid,\n                                     anjay_riid_t riid,\n                                     void *resource_instance_ptr_ptr_) {\n    (void) anjay;\n    (void) def_ptr;\n    (void) iid;\n    (void) rid;\n    anjay_attr_storage_t *as = get_attr_storage(anjay, def_ptr);\n    AVS_LIST(as_resource_instance_entry_t) **resource_instance_ptr_ptr =\n            (AVS_LIST(as_resource_instance_entry_t) **)\n                    resource_instance_ptr_ptr_;\n    while (**resource_instance_ptr_ptr\n           && (**resource_instance_ptr_ptr)->riid < riid) {\n        remove_resource_instance_entry(as, *resource_instance_ptr_ptr);\n    }\n    if (**resource_instance_ptr_ptr\n            && (**resource_instance_ptr_ptr)->riid == riid) {\n        AVS_LIST_ADVANCE_PTR(resource_instance_ptr_ptr);\n    }\n    return 0;\n}\n\nint _anjay_attr_storage_remove_absent_resource_instances(\n        anjay_unlocked_t *anjay,\n        const anjay_dm_installed_object_t *def_ptr,\n        anjay_iid_t iid,\n        AVS_LIST(as_resource_entry_t) *resource_ptr) {\n    anjay_attr_storage_t *as = get_attr_storage(anjay, def_ptr);\n    AVS_LIST(as_resource_instance_entry_t) *resource_instance_ptr =\n            &(*resource_ptr)->resource_instances;\n    int result = 0;\n    if (def_ptr) {\n        anjay_dm_resource_kind_t resource_kind;\n        (void) ((result = _anjay_dm_resource_kind_and_presence(\n                         anjay, def_ptr, iid, (*resource_ptr)->rid,\n                         &resource_kind, NULL))\n                || !_anjay_dm_res_kind_multiple(resource_kind)\n                || (result = _anjay_dm_foreach_resource_instance(\n                            anjay, def_ptr, iid, (*resource_ptr)->rid,\n                            remove_absent_resource_instances_clb,\n                            &resource_instance_ptr)));\n    }\n    if (!result) {\n        while (*resource_instance_ptr) {\n            remove_resource_instance_entry(as, resource_instance_ptr);\n        }\n    }\n    remove_resource_if_empty(resource_ptr);\n    return result;\n}\n#    endif // ANJAY_WITH_LWM2M11\n\nstatic void read_default_attrs(AVS_LIST(as_default_attrs_t) attrs,\n                               anjay_ssid_t ssid,\n                               anjay_dm_oi_attributes_t *out) {\n    AVS_LIST_ITERATE(attrs) {\n        if (attrs->ssid == ssid) {\n            *out = attrs->attrs;\n            return;\n        } else if (attrs->ssid > ssid) {\n            break;\n        }\n    }\n    *out = ANJAY_DM_OI_ATTRIBUTES_EMPTY;\n}\n\nstatic void read_resource_attrs(AVS_LIST(as_resource_attrs_t) attrs,\n                                anjay_ssid_t ssid,\n                                anjay_dm_r_attributes_t *out) {\n    AVS_LIST_ITERATE(attrs) {\n        if (attrs->ssid == ssid) {\n            *out = attrs->attrs;\n            return;\n        } else if (attrs->ssid > ssid) {\n            break;\n        }\n    }\n    *out = ANJAY_DM_R_ATTRIBUTES_EMPTY;\n}\n\nstatic int write_attrs_impl(anjay_attr_storage_t *as,\n                            AVS_LIST(void) *out_attrs,\n                            size_t element_size,\n                            size_t attrs_field_offset,\n                            size_t attrs_field_size,\n                            is_empty_func_t *is_empty_func,\n                            anjay_ssid_t ssid,\n                            const void *attrs) {\n    AVS_LIST_ITERATE_PTR(out_attrs) {\n        if (*get_ssid_ptr(*out_attrs) >= ssid) {\n            break;\n        }\n    }\n    bool found = (*out_attrs && *get_ssid_ptr(*out_attrs) == ssid);\n    bool filled = !is_empty_func(attrs);\n    if (filled) {\n        // writing non-empty set of attributes\n        if (!found) {\n            // entry does not exist, creating\n            AVS_LIST(void) new_attrs = AVS_LIST_NEW_BUFFER(element_size);\n            if (!new_attrs) {\n                _anjay_log_oom();\n                return ANJAY_ERR_INTERNAL;\n            }\n            *get_ssid_ptr(new_attrs) = ssid;\n            AVS_LIST_INSERT(out_attrs, new_attrs);\n        }\n        memcpy(get_attrs_ptr(*out_attrs, attrs_field_offset), attrs,\n               attrs_field_size);\n        _anjay_attr_storage_mark_modified(as);\n    } else if (found) {\n        // entry exists, but writing EMPTY set of attributes\n        // hence - removing\n        remove_attrs_entry(as, (AVS_LIST(void) *) out_attrs);\n    }\n    return 0;\n}\n\n#    define WRITE_ATTRS(As, OutAttrs, IsEmptyFunc, Ssid, Attrs)               \\\n        write_attrs_impl((As), (AVS_LIST(void) *) (OutAttrs),                 \\\n                         sizeof(**(OutAttrs)),                                \\\n                         (size_t) ((char *) &(*(OutAttrs))->attrs             \\\n                                   - (char *) *(OutAttrs)),                   \\\n                         sizeof((*(OutAttrs))->attrs), (IsEmptyFunc), (Ssid), \\\n                         (Attrs))\n\nstatic int write_object_attrs(anjay_unlocked_t *anjay,\n                              anjay_ssid_t ssid,\n                              const anjay_dm_installed_object_t *obj_ptr,\n                              const anjay_dm_oi_attributes_t *attrs) {\n    anjay_attr_storage_t *as = get_attr_storage(anjay, obj_ptr);\n    AVS_LIST(as_object_entry_t) *object_ptr =\n            find_or_create_object(as, _anjay_dm_installed_object_oid(obj_ptr));\n    if (!object_ptr) {\n        return -1;\n    }\n    int result = WRITE_ATTRS(as, &(*object_ptr)->default_attrs,\n                             default_attrs_empty, ssid, attrs);\n    remove_object_if_empty(object_ptr);\n    return result;\n}\n\nstatic int write_instance_attrs(anjay_unlocked_t *anjay,\n                                anjay_ssid_t ssid,\n                                const anjay_dm_installed_object_t *obj_ptr,\n                                anjay_iid_t iid,\n                                const anjay_dm_oi_attributes_t *attrs) {\n    assert(iid != ANJAY_ID_INVALID);\n    int result = -1;\n    AVS_LIST(as_object_entry_t) *object_ptr = NULL;\n    AVS_LIST(as_instance_entry_t) *instance_ptr = NULL;\n    anjay_attr_storage_t *as = get_attr_storage(anjay, obj_ptr);\n    if ((object_ptr = find_or_create_object(as, _anjay_dm_installed_object_oid(\n                                                        obj_ptr)))\n            && (instance_ptr = find_or_create_instance(*object_ptr, iid))) {\n        result = WRITE_ATTRS(as, &(*instance_ptr)->default_attrs,\n                             default_attrs_empty, ssid, attrs);\n    }\n\n    if (instance_ptr) {\n        remove_instance_if_empty(instance_ptr);\n    }\n    if (object_ptr) {\n        remove_object_if_empty(object_ptr);\n    }\n    return result;\n}\n\nstatic int write_resource_attrs(anjay_unlocked_t *anjay,\n                                anjay_ssid_t ssid,\n                                const anjay_dm_installed_object_t *obj_ptr,\n                                anjay_iid_t iid,\n                                anjay_rid_t rid,\n                                const anjay_dm_r_attributes_t *attrs) {\n    assert(iid != ANJAY_ID_INVALID && rid != ANJAY_ID_INVALID);\n    int result = -1;\n    AVS_LIST(as_object_entry_t) *object_ptr = NULL;\n    AVS_LIST(as_instance_entry_t) *instance_ptr = NULL;\n    AVS_LIST(as_resource_entry_t) *resource_ptr = NULL;\n    anjay_attr_storage_t *as = get_attr_storage(anjay, obj_ptr);\n    if ((object_ptr = find_or_create_object(as, _anjay_dm_installed_object_oid(\n                                                        obj_ptr)))\n            && (instance_ptr = find_or_create_instance(*object_ptr, iid))\n            && (resource_ptr = find_or_create_resource(*instance_ptr, rid))) {\n        result = WRITE_ATTRS(as, &(*resource_ptr)->attrs, resource_attrs_empty,\n                             ssid, attrs);\n    }\n\n    if (resource_ptr) {\n        remove_resource_if_empty(resource_ptr);\n    }\n    if (instance_ptr) {\n        remove_instance_if_empty(instance_ptr);\n    }\n    if (object_ptr) {\n        remove_object_if_empty(object_ptr);\n    }\n    return result;\n}\n\n#    ifdef ANJAY_WITH_LWM2M11\nstatic int\nwrite_resource_instance_attrs(anjay_unlocked_t *anjay,\n                              anjay_ssid_t ssid,\n                              const anjay_dm_installed_object_t *obj_ptr,\n                              anjay_iid_t iid,\n                              anjay_rid_t rid,\n                              anjay_riid_t riid,\n                              const anjay_dm_r_attributes_t *attrs) {\n    assert(iid != ANJAY_ID_INVALID && rid != ANJAY_ID_INVALID\n           && riid != ANJAY_ID_INVALID);\n    int result = -1;\n    AVS_LIST(as_object_entry_t) *object_ptr = NULL;\n    AVS_LIST(as_instance_entry_t) *instance_ptr = NULL;\n    AVS_LIST(as_resource_entry_t) *resource_ptr = NULL;\n    AVS_LIST(as_resource_instance_entry_t) *resource_instance_ptr = NULL;\n    anjay_attr_storage_t *as = get_attr_storage(anjay, obj_ptr);\n    if ((object_ptr = find_or_create_object(as, _anjay_dm_installed_object_oid(\n                                                        obj_ptr)))\n            && (instance_ptr = find_or_create_instance(*object_ptr, iid))\n            && (resource_ptr = find_or_create_resource(*instance_ptr, rid))\n            && (resource_instance_ptr = find_or_create_resource_instance(\n                        *resource_ptr, riid))) {\n        result = WRITE_ATTRS(as, &(*resource_instance_ptr)->attrs,\n                             resource_attrs_empty, ssid, attrs);\n    }\n\n    if (resource_instance_ptr) {\n        remove_resource_instance_if_empty(resource_instance_ptr);\n    }\n    if (resource_ptr) {\n        remove_resource_if_empty(resource_ptr);\n    }\n    if (instance_ptr) {\n        remove_instance_if_empty(instance_ptr);\n    }\n    if (object_ptr) {\n        remove_object_if_empty(object_ptr);\n    }\n    return result;\n}\n#    endif // ANJAY_WITH_LWM2M11\n\n//// NOTIFICATION HANDLING /////////////////////////////////////////////////////\n\ntypedef struct {\n    AVS_LIST(as_instance_entry_t) *instance_ptr;\n    AVS_LIST(anjay_ssid_t) *ssid_ptr;\n} remove_absent_instances_and_enumerate_ssids_args_t;\n\nstatic int remove_absent_instances_and_enumerate_ssids_clb(\n        anjay_unlocked_t *anjay,\n        const anjay_dm_installed_object_t *def_ptr,\n        anjay_iid_t iid,\n        void *args_) {\n    remove_absent_instances_and_enumerate_ssids_args_t *args =\n            (remove_absent_instances_and_enumerate_ssids_args_t *) args_;\n    int result = 0;\n    if (args->instance_ptr) {\n        result = _anjay_attr_storage_remove_absent_instances_clb(\n                anjay, def_ptr, iid, &args->instance_ptr);\n        if (result) {\n            return result;\n        }\n    }\n    anjay_ssid_t ssid =\n            query_ssid(anjay, _anjay_dm_installed_object_oid(def_ptr), iid);\n    if (ssid) {\n        assert(!*args->ssid_ptr);\n        if (!(*args->ssid_ptr = AVS_LIST_NEW_ELEMENT(anjay_ssid_t))) {\n            return ANJAY_ERR_INTERNAL;\n        }\n        // scan-build-7 is unable to deduce this is not NULL despite it\n        // being checked in the if() above\n        assert(*args->ssid_ptr);\n        **args->ssid_ptr = ssid;\n        AVS_LIST_ADVANCE_PTR(&args->ssid_ptr);\n    }\n    return 0;\n}\n\nstatic int remove_absent_instances_and_enumerate_ssids(\n        anjay_unlocked_t *anjay,\n        const anjay_dm_installed_object_t *def_ptr,\n        AVS_LIST(as_object_entry_t) *object_ptr,\n        AVS_LIST(anjay_ssid_t) *out_ssids) {\n    assert(out_ssids && !*out_ssids);\n    remove_absent_instances_and_enumerate_ssids_args_t args = {\n        .instance_ptr = object_ptr ? &(*object_ptr)->instances : NULL,\n        .ssid_ptr = out_ssids\n    };\n    int result = _anjay_dm_foreach_instance(\n            anjay, def_ptr, remove_absent_instances_and_enumerate_ssids_clb,\n            &args);\n    if (result) {\n        return result;\n    }\n    anjay_attr_storage_t *as = get_attr_storage(anjay, def_ptr);\n    while (args.instance_ptr && *args.instance_ptr) {\n        remove_instance_entry(as, args.instance_ptr);\n    }\n    return 0;\n}\n\nstatic int compare_u16ids(const void *a, const void *b, size_t element_size) {\n    assert(element_size == sizeof(uint16_t));\n    (void) element_size;\n    return *(const uint16_t *) a - *(const uint16_t *) b;\n}\n\nstatic int remove_absent_resources(anjay_unlocked_t *anjay,\n                                   AVS_LIST(as_object_entry_t) *as_object_ptr,\n                                   const anjay_dm_installed_object_t *obj_ptr,\n                                   anjay_iid_t iid) {\n    assert(as_object_ptr);\n    AVS_LIST(as_instance_entry_t) *instance_ptr =\n            find_instance(*as_object_ptr, iid);\n    if (!instance_ptr) {\n        return 0;\n    }\n    int result = 0;\n    if (obj_ptr) {\n        result =\n                _anjay_attr_storage_remove_absent_resources(anjay, instance_ptr,\n                                                            obj_ptr);\n    }\n    return result;\n}\n\nstatic int remove_absent_resources_in_all_instances(\n        anjay_unlocked_t *anjay,\n        const anjay_dm_installed_object_t *def_ptr,\n        AVS_LIST(anjay_notify_queue_resource_entry_t) resources_changed) {\n    int result = 0;\n    anjay_attr_storage_t *as = get_attr_storage(anjay, def_ptr);\n    AVS_LIST(as_object_entry_t) *object_ptr =\n            find_object(as, _anjay_dm_installed_object_oid(def_ptr));\n    if (object_ptr) {\n        anjay_iid_t last_iid = ANJAY_ID_INVALID;\n        AVS_LIST(anjay_notify_queue_resource_entry_t) resource_entry;\n        AVS_LIST_FOREACH(resource_entry, resources_changed) {\n            if (resource_entry->iid != last_iid) {\n                // note that remove_absent_resources() does NOT call\n                // remove_object_if_empty().\n                _anjay_update_ret(&result, remove_absent_resources(\n                                                   anjay, object_ptr, def_ptr,\n                                                   resource_entry->iid));\n            }\n            last_iid = resource_entry->iid;\n        }\n        remove_object_if_empty(object_ptr);\n    }\n    return result;\n}\n\nint _anjay_attr_storage_notify(anjay_unlocked_t *anjay,\n                               anjay_notify_queue_t queue) {\n    int result = 0;\n    AVS_LIST(anjay_notify_queue_object_entry_t) object_entry;\n    AVS_LIST_FOREACH(object_entry, queue) {\n        AVS_LIST(as_object_entry_t) *object_ptr =\n                find_object(&anjay->attr_storage, object_entry->oid);\n        assert(!object_ptr || *object_ptr);\n        if (!object_ptr && !is_ssid_reference_object(object_entry->oid)) {\n            continue;\n        }\n        const anjay_dm_installed_object_t *def_ptr =\n                _anjay_dm_find_object_by_oid(&anjay->dm, object_entry->oid);\n        if (!def_ptr && object_ptr) {\n            remove_object_entry(&anjay->attr_storage, object_ptr);\n            continue;\n        }\n        AVS_LIST(anjay_ssid_t) ssids = NULL;\n        int partial_result =\n                remove_absent_instances_and_enumerate_ssids(anjay, def_ptr,\n                                                            object_ptr, &ssids);\n        if (object_ptr) {\n            remove_object_if_empty(object_ptr);\n        }\n        if (!partial_result && is_ssid_reference_object(object_entry->oid)) {\n            AVS_LIST_SORT(&ssids, compare_u16ids);\n            remove_servers_not_on_ssid_list(&anjay->attr_storage, ssids);\n        }\n        AVS_LIST_CLEAR(&ssids);\n        if (!partial_result) {\n            // NOTE: This looks up object_ptr the second time, which is\n            // necessary because the above code might have removed\n            // as_object_entry_t entries, thus potentially invalidating\n            // object_ptr\n            assert(def_ptr);\n            partial_result = remove_absent_resources_in_all_instances(\n                    anjay, def_ptr, object_entry->resources_changed);\n        }\n        _anjay_update_ret(&result, partial_result);\n    }\n    return result;\n}\n\n//// ATTRIBUTE HANDLERS ////////////////////////////////////////////////////////\n\nstatic int object_read_default_attrs(anjay_unlocked_t *anjay,\n                                     const anjay_dm_installed_object_t obj_ptr,\n                                     anjay_ssid_t ssid,\n                                     anjay_dm_oi_attributes_t *out) {\n    anjay_attr_storage_t *as = get_attr_storage(anjay, &obj_ptr);\n    AVS_LIST(as_object_entry_t) *object_ptr =\n            find_object(as, _anjay_dm_installed_object_oid(&obj_ptr));\n    read_default_attrs(object_ptr ? (*object_ptr)->default_attrs : NULL, ssid,\n                       out);\n    return 0;\n}\n\nstatic int object_write_default_attrs(anjay_unlocked_t *anjay,\n                                      const anjay_dm_installed_object_t obj_ptr,\n                                      anjay_ssid_t ssid,\n                                      const anjay_dm_oi_attributes_t *attrs) {\n    return write_object_attrs(anjay, ssid, &obj_ptr, attrs) ? ANJAY_ERR_INTERNAL\n                                                            : 0;\n}\n\nstatic int\ninstance_read_default_attrs(anjay_unlocked_t *anjay,\n                            const anjay_dm_installed_object_t obj_ptr,\n                            anjay_iid_t iid,\n                            anjay_ssid_t ssid,\n                            anjay_dm_oi_attributes_t *out) {\n    anjay_attr_storage_t *as = get_attr_storage(anjay, &obj_ptr);\n    AVS_LIST(as_object_entry_t) *object_ptr =\n            find_object(as, _anjay_dm_installed_object_oid(&obj_ptr));\n    AVS_LIST(as_instance_entry_t) *instance_ptr =\n            object_ptr ? find_instance(*object_ptr, iid) : NULL;\n    read_default_attrs(instance_ptr ? (*instance_ptr)->default_attrs : NULL,\n                       ssid, out);\n    return 0;\n}\n\nstatic int\ninstance_write_default_attrs(anjay_unlocked_t *anjay,\n                             const anjay_dm_installed_object_t obj_ptr,\n                             anjay_iid_t iid,\n                             anjay_ssid_t ssid,\n                             const anjay_dm_oi_attributes_t *attrs) {\n    return write_instance_attrs(anjay, ssid, &obj_ptr, iid, attrs)\n                   ? ANJAY_ERR_INTERNAL\n                   : 0;\n}\n\nstatic int resource_read_attrs(anjay_unlocked_t *anjay,\n                               const anjay_dm_installed_object_t obj_ptr,\n                               anjay_iid_t iid,\n                               anjay_rid_t rid,\n                               anjay_ssid_t ssid,\n                               anjay_dm_r_attributes_t *out) {\n    anjay_attr_storage_t *as = get_attr_storage(anjay, &obj_ptr);\n    AVS_LIST(as_object_entry_t) *object_ptr =\n            find_object(as, _anjay_dm_installed_object_oid(&obj_ptr));\n    AVS_LIST(as_instance_entry_t) *instance_ptr =\n            object_ptr ? find_instance(*object_ptr, iid) : NULL;\n    AVS_LIST(as_resource_entry_t) *res_ptr =\n            instance_ptr ? find_resource(*instance_ptr, rid) : NULL;\n    read_resource_attrs(res_ptr ? (*res_ptr)->attrs : NULL, ssid, out);\n    return 0;\n}\n\nstatic int resource_write_attrs(anjay_unlocked_t *anjay,\n                                const anjay_dm_installed_object_t obj_ptr,\n                                anjay_iid_t iid,\n                                anjay_rid_t rid,\n                                anjay_ssid_t ssid,\n                                const anjay_dm_r_attributes_t *attrs) {\n    return write_resource_attrs(anjay, ssid, &obj_ptr, iid, rid, attrs)\n                   ? ANJAY_ERR_INTERNAL\n                   : 0;\n}\n\n#    ifdef ANJAY_WITH_LWM2M11\nstatic int\nresource_instance_read_attrs(anjay_unlocked_t *anjay,\n                             const anjay_dm_installed_object_t obj_ptr,\n                             anjay_iid_t iid,\n                             anjay_rid_t rid,\n                             anjay_riid_t riid,\n                             anjay_ssid_t ssid,\n                             anjay_dm_r_attributes_t *out) {\n    anjay_attr_storage_t *as = get_attr_storage(anjay, &obj_ptr);\n    AVS_LIST(as_object_entry_t) *object_ptr =\n            find_object(as, _anjay_dm_installed_object_oid(&obj_ptr));\n    AVS_LIST(as_instance_entry_t) *instance_ptr =\n            object_ptr ? find_instance(*object_ptr, iid) : NULL;\n    AVS_LIST(as_resource_entry_t) *res_ptr =\n            instance_ptr ? find_resource(*instance_ptr, rid) : NULL;\n    AVS_LIST(as_resource_instance_entry_t) *res_instance_ptr =\n            res_ptr ? find_resource_instance(*res_ptr, riid) : NULL;\n    read_resource_attrs(res_instance_ptr ? (*res_instance_ptr)->attrs : NULL,\n                        ssid, out);\n    return 0;\n}\n\nstatic int\nresource_instance_write_attrs(anjay_unlocked_t *anjay,\n                              const anjay_dm_installed_object_t obj_ptr,\n                              anjay_iid_t iid,\n                              anjay_rid_t rid,\n                              anjay_riid_t riid,\n                              anjay_ssid_t ssid,\n                              const anjay_dm_r_attributes_t *attrs) {\n    return write_resource_instance_attrs(anjay, ssid, &obj_ptr, iid, rid, riid,\n                                         attrs)\n                   ? ANJAY_ERR_INTERNAL\n                   : 0;\n}\n#    endif // ANJAY_WITH_LWM2M11\n\nconst anjay_unlocked_dm_handlers_t _ANJAY_ATTR_STORAGE_HANDLERS = {\n    .object_read_default_attrs = object_read_default_attrs,\n    .object_write_default_attrs = object_write_default_attrs,\n    .instance_read_default_attrs = instance_read_default_attrs,\n    .instance_write_default_attrs = instance_write_default_attrs,\n    .resource_read_attrs = resource_read_attrs,\n    .resource_write_attrs = resource_write_attrs,\n#    ifdef ANJAY_WITH_LWM2M11\n    .resource_instance_read_attrs = resource_instance_read_attrs,\n    .resource_instance_write_attrs = resource_instance_write_attrs,\n#    endif // ANJAY_WITH_LWM2M11\n};\n\n//// ACTIVE PROXY HANDLERS /////////////////////////////////////////////////////\n\nstatic void saved_state_reset(anjay_attr_storage_t *as) {\n    avs_stream_reset(as->saved_state.persist_data);\n    avs_stream_membuf_fit(as->saved_state.persist_data);\n}\n\navs_error_t _anjay_attr_storage_transaction_begin(anjay_attr_storage_t *as) {\n    as->saved_state.modified_since_persist = as->modified_since_persist;\n    return _anjay_attr_storage_persist_inner(as, as->saved_state.persist_data);\n}\n\nvoid _anjay_attr_storage_transaction_commit(anjay_attr_storage_t *as) {\n    saved_state_reset(as);\n}\n\navs_error_t _anjay_attr_storage_transaction_rollback(anjay_unlocked_t *anjay,\n                                                     anjay_attr_storage_t *as) {\n    avs_error_t err;\n    if (avs_is_err((err = _anjay_attr_storage_restore_inner(\n                            anjay, as, as->saved_state.persist_data)))) {\n        as->modified_since_persist = true;\n    } else {\n        as->modified_since_persist = as->saved_state.modified_since_persist;\n    }\n    saved_state_reset(as);\n    return err;\n}\n\nstatic const anjay_dm_installed_object_t *\nmaybe_get_object_before_setting_attrs(anjay_unlocked_t *anjay,\n                                      anjay_ssid_t ssid,\n                                      anjay_oid_t oid,\n                                      const void *attrs) {\n    if (!attrs) {\n        as_log(ERROR, _(\"attributes cannot be NULL\"));\n        return NULL;\n    }\n    if (ssid == ANJAY_SSID_BOOTSTRAP || !_anjay_dm_ssid_exists(anjay, ssid)) {\n        as_log(ERROR, _(\"SSID \") \"%\" PRIu16 _(\" does not exist\"), ssid);\n        return NULL;\n    }\n    const anjay_dm_installed_object_t *obj =\n            _anjay_dm_find_object_by_oid(&anjay->dm, oid);\n    if (!obj) {\n        as_log(ERROR, \"/%\" PRIu16 _(\" does not exist\"), oid);\n    }\n    return obj;\n}\n\n#    define ERR_HANDLERS_IMPLEMENTED_BY_BACKEND           \\\n        _(\"cannot set \")                                  \\\n        \"%s\" _(\" level attribs: \") \"%s\" _(\" or \") \"%s\" _( \\\n                \" is implemented by the backend object\")\n\n#    define ERR_INSTANCE_PRESENCE_CHECK                              \\\n        _(\"instance \")                                               \\\n        DM_LOG_PREFIX                                                \\\n        \"/%\" PRIu16                                                  \\\n        \"/%\" PRIu16 _(\" does not exist or an error occurred during \" \\\n                      \"querying its presence\")\n\n#    define ERR_RESOURCE_PRESENCE_CHECK                                    \\\n        _(\"resource \")                                                     \\\n        DM_LOG_PREFIX                                                      \\\n        \"/%\" PRIu16 \"/%\" PRIu16 \"/%\" PRIu16 _(                             \\\n                \"does not exist or an error occurred during querying its \" \\\n                \"presence\")\n\n#    define ERR_RESOURCE_INSTANCE_PRESENCE_CHECK                           \\\n        _(\"resource instance \")                                            \\\n        DM_LOG_PREFIX                                                      \\\n        \"/%\" PRIu16 \"/%\" PRIu16 \"/%\" PRIu16 \"/%\" PRIu16 _(                 \\\n                \"does not exist or an error occurred during querying its \" \\\n                \"presence\")\n\nint anjay_attr_storage_set_object_attrs(anjay_t *anjay_locked,\n                                        anjay_ssid_t ssid,\n                                        anjay_oid_t oid,\n                                        const anjay_dm_oi_attributes_t *attrs) {\n    int result = -1;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    const anjay_dm_installed_object_t *obj =\n            maybe_get_object_before_setting_attrs(anjay, ssid, oid, attrs);\n    if (obj) {\n        if (_anjay_dm_implements_any_object_default_attrs_handlers(obj)) {\n            as_log(DEBUG, ERR_HANDLERS_IMPLEMENTED_BY_BACKEND, \"object\",\n                   \"object_read_default_attrs\", \"object_write_default_attrs\");\n        } else if (!(result = write_object_attrs(anjay, ssid, obj, attrs))) {\n            (void) _anjay_notify_instances_changed_unlocked(anjay, oid);\n        }\n    }\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return result;\n}\n\nint anjay_attr_storage_set_instance_attrs(\n        anjay_t *anjay_locked,\n        anjay_ssid_t ssid,\n        anjay_oid_t oid,\n        anjay_iid_t iid,\n        const anjay_dm_oi_attributes_t *attrs) {\n    int result = -1;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    const anjay_dm_installed_object_t *obj =\n            maybe_get_object_before_setting_attrs(anjay, ssid, oid, attrs);\n    if (obj) {\n        if (_anjay_dm_implements_any_instance_default_attrs_handlers(obj)) {\n            as_log(DEBUG, ERR_HANDLERS_IMPLEMENTED_BY_BACKEND, \"instance\",\n                   \"instance_read_default_attrs\",\n                   \"instance_write_default_attrs\");\n        } else if (_anjay_dm_verify_instance_present(anjay, obj, iid)) {\n            as_log(DEBUG, ERR_INSTANCE_PRESENCE_CHECK,\n                   DM_LOG_PREFIX_OBJ_ARG(obj) oid, iid);\n        } else if (!(result = write_instance_attrs(anjay, ssid, obj, iid,\n                                                   attrs))) {\n            (void) _anjay_notify_instances_changed_unlocked(anjay, oid);\n        }\n    }\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return result;\n}\n\nint anjay_attr_storage_set_resource_attrs(\n        anjay_t *anjay_locked,\n        anjay_ssid_t ssid,\n        anjay_oid_t oid,\n        anjay_iid_t iid,\n        anjay_rid_t rid,\n        const anjay_dm_r_attributes_t *attrs) {\n    int result = -1;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    const anjay_dm_installed_object_t *obj =\n            maybe_get_object_before_setting_attrs(anjay, ssid, oid, attrs);\n    if (obj) {\n        if (_anjay_dm_implements_any_resource_attrs_handlers(obj)) {\n            as_log(DEBUG, ERR_HANDLERS_IMPLEMENTED_BY_BACKEND, \"resource\",\n                   \"resource_read_attrs\", \"resource_write_attrs\");\n        } else if (_anjay_dm_verify_instance_present(anjay, obj, iid)) {\n            as_log(DEBUG, ERR_INSTANCE_PRESENCE_CHECK,\n                   DM_LOG_PREFIX_OBJ_ARG(obj) oid, iid);\n        } else if (_anjay_dm_verify_resource_present(anjay, obj, iid, rid,\n                                                     NULL)) {\n            as_log(DEBUG, ERR_RESOURCE_PRESENCE_CHECK,\n                   DM_LOG_PREFIX_OBJ_ARG(obj) oid, iid, rid);\n        } else if (!(result = write_resource_attrs(anjay, ssid, obj, iid, rid,\n                                                   attrs))) {\n            (void) _anjay_notify_instances_changed_unlocked(anjay, oid);\n        }\n    }\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return result;\n}\n\n#    ifdef ANJAY_WITH_LWM2M11\nint anjay_attr_storage_set_resource_instance_attrs(\n        anjay_t *anjay_locked,\n        anjay_ssid_t ssid,\n        anjay_oid_t oid,\n        anjay_iid_t iid,\n        anjay_rid_t rid,\n        anjay_riid_t riid,\n        const anjay_dm_r_attributes_t *attrs) {\n    int result = -1;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    anjay_dm_resource_kind_t kind;\n    const anjay_dm_installed_object_t *obj =\n            maybe_get_object_before_setting_attrs(anjay, ssid, oid, attrs);\n    if (obj) {\n        if (_anjay_dm_implements_any_resource_instance_attrs_handlers(obj)) {\n            as_log(DEBUG, ERR_HANDLERS_IMPLEMENTED_BY_BACKEND,\n                   \"resource instance\", \"resource_instance_read_attrs\",\n                   \"resource_instance_write_attrs\");\n        } else if (_anjay_dm_verify_instance_present(anjay, obj, iid)) {\n            as_log(DEBUG, ERR_INSTANCE_PRESENCE_CHECK,\n                   DM_LOG_PREFIX_OBJ_ARG(obj) oid, iid);\n        } else if (_anjay_dm_verify_resource_present(anjay, obj, iid, rid,\n                                                     &kind)\n                   || !_anjay_dm_res_kind_multiple(kind)) {\n            as_log(DEBUG, ERR_RESOURCE_PRESENCE_CHECK,\n                   DM_LOG_PREFIX_OBJ_ARG(obj) oid, iid, rid);\n        } else if (_anjay_dm_verify_resource_instance_present(anjay, obj, iid,\n                                                              rid, riid)) {\n            as_log(DEBUG, ERR_RESOURCE_INSTANCE_PRESENCE_CHECK,\n                   DM_LOG_PREFIX_OBJ_ARG(obj) oid, iid, rid, riid);\n        } else if (!(result = write_resource_instance_attrs(\n                             anjay, ssid, obj, iid, rid, riid, attrs))) {\n            (void) _anjay_notify_instances_changed_unlocked(anjay, oid);\n        }\n    }\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return result;\n}\n#    endif // ANJAY_WITH_LWM2M11\n\n#    ifdef ANJAY_TEST\n#        include \"tests/core/attr_storage/attr_storage.c\"\n#    endif // ANJAY_TEST\n\n#endif // ANJAY_WITH_ATTR_STORAGE\n"
  },
  {
    "path": "src/core/attr_storage/anjay_attr_storage.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ANJAY_ATTR_STORAGE_H\n#define ANJAY_ATTR_STORAGE_H\n\n#include <anjay/attr_storage.h>\n#include <anjay/core.h>\n\n#include <anjay_modules/anjay_attr_storage_utils.h>\n#include <anjay_modules/anjay_utils_core.h>\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\ntypedef struct as_object_entry as_object_entry_t;\n\ntypedef struct {\n    avs_stream_t *persist_data;\n    bool modified_since_persist;\n} as_saved_state_t;\n\nstruct anjay_attr_storage_struct {\n    AVS_LIST(as_object_entry_t) objects;\n    bool modified_since_persist;\n    as_saved_state_t saved_state;\n    anjay_dm_t *dm;\n};\n\nstatic inline bool _anjay_dm_implements_any_object_default_attrs_handlers(\n        const anjay_dm_installed_object_t *obj_ptr) {\n    return _anjay_dm_handler_implemented(\n                   obj_ptr, ANJAY_DM_HANDLER_object_read_default_attrs)\n           || _anjay_dm_handler_implemented(\n                      obj_ptr, ANJAY_DM_HANDLER_object_write_default_attrs);\n}\n\nstatic inline bool _anjay_dm_implements_any_instance_default_attrs_handlers(\n        const anjay_dm_installed_object_t *obj_ptr) {\n    return _anjay_dm_handler_implemented(\n                   obj_ptr, ANJAY_DM_HANDLER_instance_read_default_attrs)\n           || _anjay_dm_handler_implemented(\n                      obj_ptr, ANJAY_DM_HANDLER_instance_write_default_attrs);\n}\n\nstatic inline bool _anjay_dm_implements_any_resource_attrs_handlers(\n        const anjay_dm_installed_object_t *obj_ptr) {\n    return _anjay_dm_handler_implemented(obj_ptr,\n                                         ANJAY_DM_HANDLER_resource_read_attrs)\n           || _anjay_dm_handler_implemented(\n                      obj_ptr, ANJAY_DM_HANDLER_resource_write_attrs);\n}\n\n#ifdef ANJAY_WITH_LWM2M11\nstatic inline bool _anjay_dm_implements_any_resource_instance_attrs_handlers(\n        const anjay_dm_installed_object_t *obj_ptr) {\n    return _anjay_dm_handler_implemented(\n                   obj_ptr, ANJAY_DM_HANDLER_resource_instance_read_attrs)\n           || _anjay_dm_handler_implemented(\n                      obj_ptr, ANJAY_DM_HANDLER_resource_instance_write_attrs);\n}\n#endif // ANJAY_WITH_LWM2M11\n\nint _anjay_attr_storage_init(anjay_attr_storage_t *as, anjay_dm_t *dm);\nvoid _anjay_attr_storage_cleanup(anjay_attr_storage_t *as);\n\navs_error_t _anjay_attr_storage_transaction_begin(anjay_attr_storage_t *as);\nvoid _anjay_attr_storage_transaction_commit(anjay_attr_storage_t *as);\navs_error_t _anjay_attr_storage_transaction_rollback(anjay_unlocked_t *anjay,\n                                                     anjay_attr_storage_t *as);\n\nint _anjay_attr_storage_notify(anjay_unlocked_t *anjay,\n                               anjay_notify_queue_t queue);\n\nextern const anjay_unlocked_dm_handlers_t _ANJAY_ATTR_STORAGE_HANDLERS;\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif /* ANJAY_ATTR_STORAGE_H */\n"
  },
  {
    "path": "src/core/attr_storage/anjay_attr_storage_persistence.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#ifdef ANJAY_WITH_ATTR_STORAGE\n\n#    include <stdio.h>\n#    include <string.h>\n\n#    include \"core/anjay_core.h\"\n\n#    include <avsystem/commons/avs_persistence.h>\n\n#    include <anjay_modules/anjay_dm_utils.h>\n#    include <anjay_modules/anjay_io_utils.h>\n#    include <anjay_modules/anjay_raw_buffer.h>\n\n#    define ANJAY_ATTR_STORAGE_INTERNALS\n\n#    include \"anjay_attr_storage_private.h\"\n\nVISIBILITY_SOURCE_BEGIN\n\n//// VERSIONS //////////////////////////////////////////////////////////////////\n\n/**\n * NOTE: Anjay Attr Storage is called FAS in the magic header for historical\n * reasons stemming from Anjay's initial codename which started with an F.\n *\n * NOTE: Magic header is followed by one byte which is supposed to be a version\n * number.\n *\n * Known versions are:\n * - 0: used in development versions and up to Anjay 1.3.1\n * - 1: briefly used and released as part of Anjay 1.0.0, when the attributes\n *   were temporarily unified (i.e., Objects could have lt/gt/st attributes)\n * - 2: Anjay 2.0.5, doesn't support Resource Instance attributes\n * - 3: Anjay 2.1.0, supports Resource Instance attributes\n * - 4: Anjay 2.2.0, supports min/max eval period attributes\n * - 5: Anjay 3.0.0, supports hqmax/edge attributes (commercial version only)\n */\nstatic const char *MAGIC = \"FAS\";\n\n// clang-format off\n#define SUPPORTED_VERSIONS              \\\n    AS_PERSISTENCE_VERSION_ANJAY_1_3_1, \\\n    AS_PERSISTENCE_VERSION_ANJAY_1_0_0, \\\n    AS_PERSISTENCE_VERSION_ANJAY_2_0_5, \\\n    AS_PERSISTENCE_VERSION_ANJAY_2_1_0, \\\n    AS_PERSISTENCE_VERSION_ANJAY_2_2_0, \\\n    AS_PERSISTENCE_VERSION_ANJAY_3_0_0\n// clang-format on\n\ntypedef enum {\n    SUPPORTED_VERSIONS,\n    AS_PERSISTENCE_VERSION_NEXT,\n    AS_PERSISTENCE_VERSION_CURRENT = AS_PERSISTENCE_VERSION_NEXT - 1\n} as_persistence_version_t;\n\nstatic inline int32_t version_to_bitmask(as_persistence_version_t version) {\n    switch (version) {\n    case AS_PERSISTENCE_VERSION_ANJAY_1_0_0:\n    case AS_PERSISTENCE_VERSION_ANJAY_1_3_1:\n        return 0;\n    case AS_PERSISTENCE_VERSION_ANJAY_2_0_5:\n    case AS_PERSISTENCE_VERSION_ANJAY_2_1_0:\n        return ANJAY_PERSIST_CON_ATTR;\n    case AS_PERSISTENCE_VERSION_ANJAY_2_2_0:\n        return ANJAY_PERSIST_EVAL_PERIODS_ATTR | ANJAY_PERSIST_CON_ATTR;\n    case AS_PERSISTENCE_VERSION_ANJAY_3_0_0:\n        return ANJAY_PERSIST_EVAL_PERIODS_ATTR | ANJAY_PERSIST_CON_ATTR\n               | ANJAY_PERSIST_HQMAX_ATTR | ANJAY_PERSIST_EDGE_ATTR;\n    case AS_PERSISTENCE_VERSION_NEXT:\n        break;\n    }\n    AVS_UNREACHABLE(\"Invalid persistence version\");\n    return -1;\n}\n\nstatic const uint8_t SUPPORTED_VERSIONS_ARRAY[] = { SUPPORTED_VERSIONS };\n\n#    undef SUPPORTED_VERSIONS\n\n//// DATA STRUCTURE HANDLERS ///////////////////////////////////////////////////\n\n#    define HANDLE_LIST(Type, Ctx, ListPtr, UserPtr)                      \\\n        avs_persistence_list((Ctx), (AVS_LIST(void) *) (ListPtr),         \\\n                             sizeof(**(ListPtr)), handle_##Type, UserPtr, \\\n                             NULL)\n\nstatic avs_error_t handle_default_attrs(avs_persistence_context_t *ctx,\n                                        void *attrs_,\n                                        void *version_as_ptr) {\n    as_default_attrs_t *attrs = (as_default_attrs_t *) attrs_;\n    avs_error_t err;\n    (void) (avs_is_err((err = avs_persistence_u16(ctx, &attrs->ssid)))\n            || avs_is_err((err = _anjay_persistence_dm_oi_attributes(\n                                   ctx, &attrs->attrs,\n                                   version_to_bitmask(\n                                           (as_persistence_version_t) (intptr_t)\n                                                   version_as_ptr)))));\n    return err;\n}\n\nstatic avs_error_t handle_resource_attrs(avs_persistence_context_t *ctx,\n                                         void *attrs_,\n                                         void *version_as_ptr) {\n    as_resource_attrs_t *attrs = (as_resource_attrs_t *) attrs_;\n    avs_error_t err;\n    (void) (avs_is_err((err = avs_persistence_u16(ctx, &attrs->ssid)))\n            || avs_is_err((err = _anjay_persistence_dm_r_attributes(\n                                   ctx, &attrs->attrs,\n                                   version_to_bitmask(\n                                           (as_persistence_version_t) (intptr_t)\n                                                   version_as_ptr)))));\n    return err;\n}\n\nstatic avs_error_t\nhandle_resource_instance_entry(avs_persistence_context_t *ctx,\n                               void *resource_instance_,\n                               void *version_as_ptr) {\n    as_resource_instance_entry_t *resource_instance =\n            (as_resource_instance_entry_t *) resource_instance_;\n    avs_error_t err;\n    (void) (avs_is_err(\n                    (err = avs_persistence_u16(ctx, &resource_instance->riid)))\n            || avs_is_err((err = HANDLE_LIST(resource_attrs, ctx,\n                                             &resource_instance->attrs,\n                                             version_as_ptr))));\n    return err;\n}\n\nstatic avs_error_t handle_resource_entry(avs_persistence_context_t *ctx,\n                                         void *resource_,\n                                         void *version_as_ptr) {\n    const as_persistence_version_t version =\n            (as_persistence_version_t) (intptr_t) version_as_ptr;\n    as_resource_entry_t *resource = (as_resource_entry_t *) resource_;\n    avs_error_t err;\n    (void) (avs_is_err((err = avs_persistence_u16(ctx, &resource->rid)))\n            || avs_is_err(\n                       (err = HANDLE_LIST(resource_attrs, ctx, &resource->attrs,\n                                          version_as_ptr))));\n    if (avs_is_ok(err) && version >= AS_PERSISTENCE_VERSION_ANJAY_2_1_0) {\n        AVS_LIST(as_resource_instance_entry_t) *resource_instances_ptr =\n#    ifdef ANJAY_WITH_LWM2M11\n                &resource->resource_instances;\n#    else  // ANJAY_WITH_LWM2M11\n                &(AVS_LIST(as_resource_instance_entry_t)) { NULL };\n#    endif // ANJAY_WITH_LWM2M11\n        err = HANDLE_LIST(resource_instance_entry, ctx, resource_instances_ptr,\n                          version_as_ptr);\n#    ifndef ANJAY_WITH_LWM2M11\n        AVS_LIST_CLEAR(resource_instances_ptr) {\n            AVS_LIST_CLEAR(&(*resource_instances_ptr)->attrs);\n        }\n#    endif // ANJAY_WITH_LWM2M11\n    }\n    return err;\n}\n\nstatic avs_error_t handle_instance_entry(avs_persistence_context_t *ctx,\n                                         void *instance_,\n                                         void *version_as_ptr) {\n    as_instance_entry_t *instance = (as_instance_entry_t *) instance_;\n    avs_error_t err;\n    (void) (avs_is_err((err = avs_persistence_u16(ctx, &instance->iid)))\n            || avs_is_err((err = HANDLE_LIST(default_attrs, ctx,\n                                             &instance->default_attrs,\n                                             version_as_ptr)))\n            || avs_is_err((err = HANDLE_LIST(resource_entry, ctx,\n                                             &instance->resources,\n                                             version_as_ptr))));\n    return err;\n}\n\nstatic avs_error_t handle_object(avs_persistence_context_t *ctx,\n                                 void *object_,\n                                 void *version_as_ptr) {\n    as_object_entry_t *object = (as_object_entry_t *) object_;\n    avs_error_t err;\n    (void) (avs_is_err((err = avs_persistence_u16(ctx, &object->oid)))\n            || avs_is_err((err = HANDLE_LIST(default_attrs, ctx,\n                                             &object->default_attrs,\n                                             version_as_ptr)))\n            || avs_is_err((err = HANDLE_LIST(instance_entry, ctx,\n                                             &object->instances,\n                                             version_as_ptr))));\n    return err;\n}\n\n//// HELPERS ///////////////////////////////////////////////////////////////////\n\nstatic bool is_attrs_list_sane(AVS_LIST(void) attrs_list,\n                               size_t attrs_field_offset,\n                               is_empty_func_t *is_empty_func) {\n    int32_t last_ssid = -1;\n    AVS_LIST(void) attrs;\n    AVS_LIST_FOREACH(attrs, attrs_list) {\n        if (*get_ssid_ptr(attrs) <= last_ssid\n                || is_empty_func(get_attrs_ptr(attrs, attrs_field_offset))) {\n            return false;\n        }\n        last_ssid = *get_ssid_ptr(attrs);\n    }\n    return true;\n}\n\n#    ifdef ANJAY_WITH_LWM2M11\nstatic bool is_resource_instances_list_sane(\n        AVS_LIST(as_resource_instance_entry_t) resource_instances) {\n    int32_t last_riid = -1;\n    AVS_LIST(as_resource_instance_entry_t) resource_instance;\n    AVS_LIST_FOREACH(resource_instance, resource_instances) {\n        if (resource_instance->riid <= last_riid) {\n            return false;\n        }\n        last_riid = resource_instance->riid;\n        if (!is_attrs_list_sane(resource_instance->attrs,\n                                offsetof(as_resource_instance_entry_t, attrs),\n                                resource_attrs_empty)) {\n            return false;\n        }\n    }\n    return true;\n}\n#    endif // ANJAY_WITH_LWM2M11\n\nstatic bool is_resources_list_sane(AVS_LIST(as_resource_entry_t) resources) {\n    int32_t last_rid = -1;\n    AVS_LIST(as_resource_entry_t) resource;\n    AVS_LIST_FOREACH(resource, resources) {\n        if (resource->rid <= last_rid) {\n            return false;\n        }\n        last_rid = resource->rid;\n        if (!is_attrs_list_sane(resource->attrs,\n                                offsetof(as_resource_attrs_t, attrs),\n                                resource_attrs_empty)\n#    ifdef ANJAY_WITH_LWM2M11\n                || !is_resource_instances_list_sane(\n                           resource->resource_instances)\n#    endif // ANJAY_WITH_LWM2M11\n        ) {\n            return false;\n        }\n    }\n    return true;\n}\n\nstatic bool is_instances_list_sane(AVS_LIST(as_instance_entry_t) instances) {\n    int32_t last_iid = -1;\n    AVS_LIST(as_instance_entry_t) instance;\n    AVS_LIST_FOREACH(instance, instances) {\n        if (instance->iid <= last_iid) {\n            return false;\n        }\n        last_iid = instance->iid;\n        if (!is_attrs_list_sane(instance->default_attrs,\n                                offsetof(as_default_attrs_t, attrs),\n                                default_attrs_empty)\n                || !is_resources_list_sane(instance->resources)) {\n            return false;\n        }\n    }\n    return true;\n}\n\nstatic bool is_object_sane(as_object_entry_t *object) {\n    return is_attrs_list_sane(object->default_attrs,\n                              offsetof(as_default_attrs_t, attrs),\n                              default_attrs_empty)\n           && is_instances_list_sane(object->instances);\n}\n\nstatic bool is_attr_storage_sane(anjay_attr_storage_t *as) {\n    int32_t last_oid = -1;\n    AVS_LIST(as_object_entry_t) *object_ptr;\n    AVS_LIST(as_object_entry_t) object_helper;\n    AVS_LIST_DELETABLE_FOREACH_PTR(object_ptr, object_helper, &as->objects) {\n        if ((*object_ptr)->oid <= last_oid) {\n            return false;\n        }\n        last_oid = (*object_ptr)->oid;\n        if (!is_object_sane(*object_ptr)) {\n            return false;\n        }\n    }\n    return true;\n}\n\n#    ifdef ANJAY_WITH_LWM2M11\nstatic int clear_nonexistent_riids(anjay_unlocked_t *anjay,\n                                   AVS_LIST(as_instance_entry_t) *instance_ptr,\n                                   const anjay_dm_installed_object_t *def_ptr) {\n    if (!*instance_ptr) {\n        return 0;\n    }\n\n    AVS_LIST(as_resource_entry_t) *resource_ptr;\n    AVS_LIST(as_resource_entry_t) resource_helper;\n    AVS_LIST_DELETABLE_FOREACH_PTR(resource_ptr, resource_helper,\n                                   &(*instance_ptr)->resources) {\n        if (_anjay_attr_storage_remove_absent_resource_instances(\n                    anjay, def_ptr, (*instance_ptr)->iid, resource_ptr)) {\n            return -1;\n        }\n    }\n    return 0;\n}\n#    endif // ANJAY_WITH_LWM2M11\n\nstatic int clear_nonexistent_rids(anjay_unlocked_t *anjay,\n                                  AVS_LIST(as_object_entry_t) *object_ptr,\n                                  const anjay_dm_installed_object_t *def_ptr) {\n    AVS_LIST(as_instance_entry_t) *instance_ptr;\n    AVS_LIST(as_instance_entry_t) instance_helper;\n    AVS_LIST_DELETABLE_FOREACH_PTR(instance_ptr, instance_helper,\n                                   &(*object_ptr)->instances) {\n        if (\n#    ifdef ANJAY_WITH_LWM2M11\n                    clear_nonexistent_riids(anjay, instance_ptr, def_ptr) ||\n#    endif // ANJAY_WITH_LWM2M11\n                        _anjay_attr_storage_remove_absent_resources(\n                                anjay, instance_ptr, def_ptr)) {\n            return -1;\n        }\n    }\n    return 0;\n}\n\nstatic avs_error_t clear_nonexistent_entries(anjay_unlocked_t *anjay,\n                                             anjay_attr_storage_t *as) {\n    AVS_LIST(as_object_entry_t) *object_ptr;\n    AVS_LIST(as_object_entry_t) object_helper;\n    AVS_LIST_DELETABLE_FOREACH_PTR(object_ptr, object_helper, &as->objects) {\n        const anjay_dm_installed_object_t *def_ptr =\n                _anjay_dm_find_object_by_oid(as->dm, (*object_ptr)->oid);\n        if (!def_ptr) {\n            remove_object_entry(as, object_ptr);\n        } else {\n            AVS_LIST(as_instance_entry_t) *instance_ptr =\n                    &(*object_ptr)->instances;\n            int retval = _anjay_dm_foreach_instance(\n                    anjay, def_ptr,\n                    _anjay_attr_storage_remove_absent_instances_clb,\n                    &instance_ptr);\n            while (!retval && instance_ptr && *instance_ptr) {\n                remove_instance_entry(as, instance_ptr);\n            }\n            if (retval || clear_nonexistent_rids(anjay, object_ptr, def_ptr)) {\n                return avs_errno(AVS_EPROTO);\n            }\n            remove_object_if_empty(object_ptr);\n        }\n    }\n    return AVS_OK;\n}\n\n//// PUBLIC FUNCTIONS //////////////////////////////////////////////////////////\n\navs_error_t\n_anjay_attr_storage_persist_inner(anjay_attr_storage_t *attr_storage,\n                                  avs_stream_t *out) {\n    avs_persistence_context_t ctx = avs_persistence_store_context_create(out);\n    as_persistence_version_t version = AS_PERSISTENCE_VERSION_CURRENT;\n    avs_error_t err;\n    (void) (avs_is_err((err = avs_persistence_magic_string(&ctx, MAGIC)))\n            || avs_is_err((err = avs_persistence_version(\n                                   &ctx, (uint8_t *) &version,\n                                   SUPPORTED_VERSIONS_ARRAY,\n                                   sizeof(SUPPORTED_VERSIONS_ARRAY))))\n            || avs_is_err(\n                       (err = HANDLE_LIST(object, &ctx, &attr_storage->objects,\n                                          (void *) version))));\n    return err;\n}\n\navs_error_t _anjay_attr_storage_restore_inner(anjay_unlocked_t *anjay,\n                                              anjay_attr_storage_t *as,\n                                              avs_stream_t *in) {\n    _anjay_attr_storage_clear(as);\n\n    avs_persistence_context_t ctx = avs_persistence_restore_context_create(in);\n    as_persistence_version_t version = (as_persistence_version_t) 0;\n    avs_error_t err;\n\n    if (avs_is_err((err = avs_persistence_magic_string(&ctx, MAGIC)))\n            || avs_is_err((err = avs_persistence_version(\n                                   &ctx, (uint8_t *) &version,\n                                   SUPPORTED_VERSIONS_ARRAY,\n                                   sizeof(SUPPORTED_VERSIONS_ARRAY))))\n            || avs_is_err((err = HANDLE_LIST(object, &ctx, &as->objects,\n                                             (void *) version)))\n            || avs_is_err((err = (is_attr_storage_sane(as)\n                                          ? AVS_OK\n                                          : avs_errno(AVS_EBADMSG))))\n            || avs_is_err((err = clear_nonexistent_entries(anjay, as)))) {\n        _anjay_attr_storage_clear(as);\n    }\n    return err;\n}\n\navs_error_t anjay_attr_storage_persist(anjay_t *anjay_locked,\n                                       avs_stream_t *out) {\n    avs_error_t err = avs_errno(AVS_EINVAL);\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    if (avs_is_ok((err = _anjay_attr_storage_persist_inner(&anjay->attr_storage,\n                                                           out)))) {\n        anjay->attr_storage.modified_since_persist = false;\n        as_log(INFO, _(\"Attribute Storage state persisted\"));\n    }\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return err;\n}\n\navs_error_t anjay_attr_storage_restore(anjay_t *anjay_locked,\n                                       avs_stream_t *in) {\n    avs_error_t err = avs_errno(AVS_EINVAL);\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    if (avs_is_ok((err = _anjay_attr_storage_transaction_begin(\n                           &anjay->attr_storage)))) {\n        if (avs_is_ok((err = _anjay_attr_storage_restore_inner(\n                               anjay, &anjay->attr_storage, in)))) {\n            _anjay_attr_storage_transaction_commit(&anjay->attr_storage);\n            anjay->attr_storage.modified_since_persist = false;\n\n            as_log(INFO, _(\"Attribute Storage state restored\"));\n        } else {\n            avs_error_t rollback_err = _anjay_attr_storage_transaction_rollback(\n                    anjay, &anjay->attr_storage);\n            if (avs_is_err(rollback_err)) {\n                err = rollback_err;\n            }\n        }\n    }\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return err;\n}\n\n#    ifdef ANJAY_TEST\n#        include \"tests/core/attr_storage/persistence.c\"\n#    endif // ANJAY_TEST\n\n#endif // ANJAY_WITH_ATTR_STORAGE\n"
  },
  {
    "path": "src/core/attr_storage/anjay_attr_storage_private.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ANJAY_ATTR_STORAGE_PRIVATE_H\n#define ANJAY_ATTR_STORAGE_PRIVATE_H\n\n#include \"anjay_attr_storage.h\"\n\n#ifndef ANJAY_ATTR_STORAGE_INTERNALS\n#    error \"anjay_attr_storage_private.h is not meant to be included from outside\"\n#endif\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\n#define as_log(...) _anjay_log(anjay_attr_storage, __VA_ARGS__)\n\ntypedef struct {\n    anjay_ssid_t ssid;\n    anjay_dm_oi_attributes_t attrs;\n} as_default_attrs_t;\n\ntypedef struct {\n    anjay_ssid_t ssid;\n    anjay_dm_r_attributes_t attrs;\n} as_resource_attrs_t;\n\ntypedef struct {\n    anjay_riid_t riid;\n    AVS_LIST(as_resource_attrs_t) attrs;\n} as_resource_instance_entry_t;\n\ntypedef struct {\n    anjay_rid_t rid;\n    AVS_LIST(as_resource_attrs_t) attrs;\n#ifdef ANJAY_WITH_LWM2M11\n    AVS_LIST(as_resource_instance_entry_t) resource_instances;\n#endif // ANJAY_WITH_LWM2M11\n} as_resource_entry_t;\n\ntypedef struct {\n    anjay_iid_t iid;\n    AVS_LIST(as_default_attrs_t) default_attrs;\n    AVS_LIST(as_resource_entry_t) resources;\n} as_instance_entry_t;\n\nstruct as_object_entry {\n    anjay_oid_t oid;\n    AVS_LIST(as_default_attrs_t) default_attrs;\n    AVS_LIST(as_instance_entry_t) instances;\n};\n\nvoid _anjay_attr_storage_clear(anjay_attr_storage_t *as);\n\n/**\n * @param instance_ptr_ptr_\n * Conceptually of type AVS_LIST(as_instance_entry_t) **. Pointer to a variable\n * that is used to iterate over a list of instances. Before the first call in\n * iretarion, it shall point always points to the list's head.\n */\nint _anjay_attr_storage_remove_absent_instances_clb(\n        anjay_unlocked_t *anjay,\n        const anjay_dm_installed_object_t *def_ptr,\n        anjay_iid_t iid,\n        void *instance_ptr_ptr_);\n\ntypedef struct {\n    anjay_rid_t rid;\n    anjay_dm_resource_kind_t kind;\n    anjay_dm_resource_presence_t presence;\n} resource_entry_t;\n\nint _anjay_attr_storage_remove_absent_resources(\n        anjay_unlocked_t *anjay,\n        AVS_LIST(as_instance_entry_t) *instance_ptr,\n        const anjay_dm_installed_object_t *def_ptr);\n\n#ifdef ANJAY_WITH_LWM2M11\nint _anjay_attr_storage_remove_absent_resource_instances(\n        anjay_unlocked_t *anjay,\n        const anjay_dm_installed_object_t *def_ptr,\n        anjay_iid_t iid,\n        AVS_LIST(as_resource_entry_t) *resource_ptr);\n#endif // ANJAY_WITH_LWM2M11\n\nstatic inline void _anjay_attr_storage_mark_modified(anjay_attr_storage_t *as) {\n    as->modified_since_persist = true;\n}\n\n#ifdef ANJAY_WITH_LWM2M11\nstatic void remove_resource_instance_entry(\n        anjay_attr_storage_t *as,\n        AVS_LIST(as_resource_instance_entry_t) *entry_ptr) {\n    AVS_LIST_CLEAR(&(*entry_ptr)->attrs);\n    AVS_LIST_DELETE(entry_ptr);\n    _anjay_attr_storage_mark_modified(as);\n}\n#endif // ANJAY_WITH_LWM2M11\n\nstatic void remove_resource_entry(anjay_attr_storage_t *as,\n                                  AVS_LIST(as_resource_entry_t) *entry_ptr) {\n    AVS_LIST_CLEAR(&(*entry_ptr)->attrs);\n#ifdef ANJAY_WITH_LWM2M11\n    while ((*entry_ptr)->resource_instances) {\n        remove_resource_instance_entry(as, &(*entry_ptr)->resource_instances);\n    }\n#endif // ANJAY_WITH_LWM2M11\n    AVS_LIST_DELETE(entry_ptr);\n    _anjay_attr_storage_mark_modified(as);\n}\n\nstatic void remove_instance_entry(anjay_attr_storage_t *as,\n                                  AVS_LIST(as_instance_entry_t) *entry_ptr) {\n    AVS_LIST_CLEAR(&(*entry_ptr)->default_attrs);\n    while ((*entry_ptr)->resources) {\n        remove_resource_entry(as, &(*entry_ptr)->resources);\n    }\n    AVS_LIST_DELETE(entry_ptr);\n    _anjay_attr_storage_mark_modified(as);\n}\n\nstatic void remove_object_entry(anjay_attr_storage_t *as,\n                                AVS_LIST(as_object_entry_t) *entry_ptr) {\n    AVS_LIST_CLEAR(&(*entry_ptr)->default_attrs);\n    while ((*entry_ptr)->instances) {\n        remove_instance_entry(as, &(*entry_ptr)->instances);\n    }\n    AVS_LIST_DELETE(entry_ptr);\n    _anjay_attr_storage_mark_modified(as);\n}\n\nstatic void remove_object_if_empty(AVS_LIST(as_object_entry_t) *entry_ptr) {\n    if (!(*entry_ptr)->default_attrs && !(*entry_ptr)->instances) {\n        AVS_LIST_DELETE(entry_ptr);\n    }\n}\n\nstatic inline anjay_ssid_t *get_ssid_ptr(void *generic_attrs) {\n    AVS_STATIC_ASSERT(offsetof(as_default_attrs_t, ssid) == 0,\n                      default_attrs_ssid_offset);\n    AVS_STATIC_ASSERT(offsetof(as_resource_attrs_t, ssid) == 0,\n                      resource_attrs_ssid_offset);\n    return (anjay_ssid_t *) generic_attrs;\n}\n\nstatic inline void *get_attrs_ptr(void *generic_attrs,\n                                  size_t attrs_field_offset) {\n    return (char *) generic_attrs + attrs_field_offset;\n}\n\ntypedef bool is_empty_func_t(const void *attrs);\n\nstatic bool default_attrs_empty(const void *attrs) {\n    return _anjay_dm_attributes_empty((const anjay_dm_oi_attributes_t *) attrs);\n}\n\nstatic bool resource_attrs_empty(const void *attrs) {\n    return _anjay_dm_resource_attributes_empty(\n            (const anjay_dm_r_attributes_t *) attrs);\n}\n\navs_error_t\n_anjay_attr_storage_persist_inner(anjay_attr_storage_t *attr_storage,\n                                  avs_stream_t *out);\n\navs_error_t _anjay_attr_storage_restore_inner(anjay_unlocked_t *anjay,\n                                              anjay_attr_storage_t *as,\n                                              avs_stream_t *in);\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif /* ANJAY_ATTR_STORAGE_PRIVATE_H */\n"
  },
  {
    "path": "src/core/coap/anjay_content_format.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ANJAY_COAP_CONTENT_FORMAT_H\n#define ANJAY_COAP_CONTENT_FORMAT_H\n\n/* for AVS_COAP_FORMAT_NONE */\n#include <avsystem/coap/option.h>\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\n/** Auxiliary constants for common Content-Format Option values */\n\n#ifdef ANJAY_WITH_LEGACY_CONTENT_FORMAT_SUPPORT\n#    define ANJAY_COAP_FORMAT_LEGACY_PLAINTEXT 1541\n#    define ANJAY_COAP_FORMAT_LEGACY_TLV 1542\n#    define ANJAY_COAP_FORMAT_LEGACY_JSON 1543\n#    define ANJAY_COAP_FORMAT_LEGACY_OPAQUE 1544\n#endif // ANJAY_WITH_LEGACY_CONTENT_FORMAT_SUPPORT\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif // ANJAY_COAP_CONTENT_FORMAT_H\n"
  },
  {
    "path": "src/core/coap/anjay_msg_details.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ANJAY_COAP_MSG_DETAILS_H\n#define ANJAY_COAP_MSG_DETAILS_H\n\n#include \"../anjay_utils_private.h\"\n\n#include <avsystem/coap/streaming.h>\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\ntypedef struct anjay_msg_details {\n    uint8_t msg_code;\n    uint16_t format;\n    /* target URI path */\n    AVS_LIST(const anjay_string_t) uri_path;\n    AVS_LIST(const anjay_string_t) uri_query;\n    /* path of the resource created using Create RPC */\n    AVS_LIST(const anjay_string_t) location_path;\n} anjay_msg_details_t;\n\nstatic inline avs_error_t\n_anjay_coap_fill_response_header(avs_coap_response_header_t *out_response,\n                                 const anjay_msg_details_t *details) {\n    *out_response = (avs_coap_response_header_t) {\n        .code = details->msg_code\n    };\n    avs_error_t err = avs_coap_options_dynamic_init(&out_response->options);\n    if (avs_is_err(err)) {\n        return err;\n    }\n    (void) (avs_is_err((err = avs_coap_options_set_content_format(\n                                &out_response->options, details->format)))\n            || avs_is_err((err = _anjay_coap_add_string_options(\n                                   &out_response->options,\n                                   details->location_path,\n                                   AVS_COAP_OPTION_LOCATION_PATH))));\n    if (avs_is_err(err)) {\n        avs_coap_options_cleanup(&out_response->options);\n    }\n    return err;\n}\n\nstatic inline avs_stream_t *\n_anjay_coap_setup_response_stream(avs_coap_streaming_request_ctx_t *request_ctx,\n                                  const anjay_msg_details_t *details) {\n    avs_coap_response_header_t response;\n    avs_stream_t *stream = NULL;\n\n    if (avs_is_ok(_anjay_coap_fill_response_header(&response, details))) {\n        stream = avs_coap_streaming_setup_response(request_ctx, &response);\n    }\n\n    avs_coap_options_cleanup(&response.options);\n    return stream;\n}\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif // ANJAY_COAP_MSG_DETAILS_H\n"
  },
  {
    "path": "src/core/dm/anjay_discover.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#ifdef ANJAY_WITH_DISCOVER\n\n#    include <inttypes.h>\n\n#    include <anjay_modules/anjay_time_defs.h>\n\n#    include \"anjay_discover.h\"\n#    include \"anjay_query.h\"\n\n#    include \"../anjay_access_utils_private.h\"\n#    include \"../anjay_core.h\"\n#    include \"../anjay_dm_core.h\"\n\nVISIBILITY_SOURCE_BEGIN\n\nstatic int\nprint_integer_attr(avs_stream_t *stream, const char *name, int32_t t) {\n    if (t < 0) {\n        return 0;\n    }\n    return avs_is_ok(avs_stream_write_f(stream, \";%s=%lu\", name,\n                                        (unsigned long) t))\n                   ? 0\n                   : -1;\n}\n\n#    ifdef ANJAY_WITH_CON_ATTR\nstatic int print_con_attr(avs_stream_t *stream, anjay_dm_con_attr_t value) {\n    if (value < 0) {\n        return 0;\n    }\n    return avs_is_ok(avs_stream_write_f(stream, \";\" ANJAY_CUSTOM_ATTR_CON \"=%d\",\n                                        (int) value))\n                   ? 0\n                   : -1;\n}\n#    else // ANJAY_WITH_CON_ATTR\n#        define print_con_attr(...) 0\n#    endif // ANJAY_WITH_CON_ATTR\n\nstatic int\nprint_double_attr(avs_stream_t *stream, const char *name, double value) {\n    if (isnan(value)) {\n        return 0;\n    }\n    return avs_is_ok(avs_stream_write_f(stream, \";%s=%s\", name,\n                                        AVS_DOUBLE_AS_STRING(value, 17)))\n                   ? 0\n                   : -1;\n}\n\nstatic int print_oi_attrs(avs_stream_t *stream,\n                          const anjay_dm_oi_attributes_t *attrs) {\n    int result = 0;\n    (void) ((result = print_integer_attr(stream, ANJAY_ATTR_PMIN,\n                                         attrs->min_period))\n            || (result = print_integer_attr(stream, ANJAY_ATTR_PMAX,\n                                            attrs->max_period))\n            || (result = print_integer_attr(stream, ANJAY_ATTR_EPMIN,\n                                            attrs->min_eval_period))\n            || (result = print_integer_attr(stream, ANJAY_ATTR_EPMAX,\n                                            attrs->max_eval_period))\n#    ifdef ANJAY_WITH_LWM2M12\n            || (result = print_integer_attr(stream, ANJAY_ATTR_HQMAX,\n                                            attrs->hqmax))\n#    endif // ANJAY_WITH_LWM2M12\n            || (result = print_con_attr(stream, attrs->con)));\n    return result;\n}\n\nstatic int print_resource_dim(avs_stream_t *stream, int32_t dim) {\n    if (dim >= 0) {\n        return avs_is_ok(avs_stream_write_f(stream, \";dim=%\" PRIu32,\n                                            (uint32_t) dim))\n                       ? 0\n                       : -1;\n    }\n    return 0;\n}\n\nstatic int print_r_attrs(avs_stream_t *stream,\n                         const anjay_dm_r_attributes_t *attrs) {\n    int result;\n    (void) ((result = print_oi_attrs(stream, &attrs->common))\n            || (result = print_double_attr(stream, ANJAY_ATTR_GT,\n                                           attrs->greater_than))\n            || (result = print_double_attr(stream, ANJAY_ATTR_LT,\n                                           attrs->less_than))\n            || (result = print_double_attr(stream, ANJAY_ATTR_ST, attrs->step))\n#    ifdef ANJAY_WITH_LWM2M12\n            || (result = print_integer_attr(stream, ANJAY_ATTR_EDGE,\n                                            attrs->edge))\n#    endif // ANJAY_WITH_LWM2M12\n    );\n    return result;\n}\n\nstatic int print_discovered_object(avs_stream_t *stream,\n                                   const anjay_dm_installed_object_t *obj,\n                                   const anjay_dm_oi_attributes_t *attrs,\n                                   anjay_lwm2m_version_t version) {\n    if (avs_is_err(avs_stream_write_f(stream, \"</%\" PRIu16 \">\",\n                                      _anjay_dm_installed_object_oid(obj)))) {\n        return -1;\n    }\n    (void) version;\n    const char *format = \";ver=\\\"%s\\\"\";\n#    ifdef ANJAY_WITH_LWM2M11\n    if (version > ANJAY_LWM2M_VERSION_1_0) {\n        format = \";ver=%s\";\n    }\n#    endif // ANJAY_WITH_LWM2M11\n    if (_anjay_dm_installed_object_version(obj)\n            && avs_is_err(avs_stream_write_f(stream, format,\n                                             _anjay_dm_installed_object_version(\n                                                     obj)))) {\n        return -1;\n    }\n    return print_oi_attrs(stream, attrs);\n}\n\nstatic int print_discovered_instance(avs_stream_t *stream,\n                                     const anjay_dm_installed_object_t *obj,\n                                     anjay_iid_t iid,\n                                     const anjay_dm_oi_attributes_t *attrs) {\n    if (avs_is_err(avs_stream_write_f(stream, \"</%\" PRIu16 \"/%\" PRIu16 \">\",\n                                      _anjay_dm_installed_object_oid(obj),\n                                      iid))) {\n        return -1;\n    }\n    return print_oi_attrs(stream, attrs);\n}\n\nstatic int print_discovered_resource(avs_stream_t *stream,\n                                     const anjay_dm_installed_object_t *obj,\n                                     anjay_iid_t iid,\n                                     anjay_rid_t rid,\n                                     int32_t resource_dim,\n                                     const anjay_dm_r_attributes_t *attrs) {\n    if (avs_is_err(avs_stream_write_f(\n                stream, \"</%\" PRIu16 \"/%\" PRIu16 \"/%\" PRIu16 \">\",\n                _anjay_dm_installed_object_oid(obj), iid, rid))\n            || print_resource_dim(stream, resource_dim)\n            || print_r_attrs(stream, attrs)) {\n        return -1;\n    }\n    return 0;\n}\n\nstatic int print_separator(avs_stream_t *stream) {\n    return avs_is_ok(avs_stream_write(stream, \",\", 1)) ? 0 : -1;\n}\n\nstatic int read_resource_dim_clb(anjay_unlocked_t *anjay,\n                                 const anjay_dm_installed_object_t *obj,\n                                 anjay_iid_t iid,\n                                 anjay_rid_t rid,\n                                 anjay_riid_t riid,\n                                 void *out_dim_) {\n    (void) anjay;\n    (void) obj;\n    (void) iid;\n    (void) rid;\n    (void) riid;\n    ++*(int32_t *) out_dim_;\n    return 0;\n}\n\nstatic int read_resource_dim(anjay_unlocked_t *anjay,\n                             const anjay_dm_installed_object_t *obj,\n                             anjay_iid_t iid,\n                             anjay_rid_t rid,\n                             int32_t *out_dim) {\n    *out_dim = 0;\n    int result =\n            _anjay_dm_foreach_resource_instance(anjay, obj, iid, rid,\n                                                read_resource_dim_clb, out_dim);\n    if (result == ANJAY_ERR_METHOD_NOT_ALLOWED\n            || result == ANJAY_ERR_NOT_IMPLEMENTED) {\n        *out_dim = -1;\n        return 0;\n    }\n    return result;\n}\n\nstatic int read_attrs(anjay_unlocked_t *anjay,\n                      const anjay_dm_installed_object_t *obj,\n                      anjay_iid_t iid,\n                      anjay_rid_t rid,\n                      anjay_riid_t riid,\n                      anjay_ssid_t ssid,\n                      anjay_lwm2m_version_t lwm2m_version,\n                      anjay_id_type_t root_path_type,\n                      anjay_dm_r_attributes_t *out) {\n    (void) riid;\n    (void) lwm2m_version;\n    *out = ANJAY_DM_R_ATTRIBUTES_EMPTY;\n    if (iid == ANJAY_ID_INVALID) {\n        return _anjay_dm_call_object_read_default_attrs(anjay, obj, ssid,\n                                                        &out->common);\n    }\n    if (root_path_type == ANJAY_ID_OID\n#    ifdef ANJAY_WITH_LWM2M12\n            && lwm2m_version <= ANJAY_LWM2M_VERSION_1_1\n#    endif // ANJAY_WITH_LWM2M12\n    ) {\n        // When Discover is issued on an Object,\n        // attributes from lower levels are not reported in LwM2M <=1.1\n        return 0;\n    }\n    if (root_path_type == ANJAY_ID_RIID\n            || (root_path_type == ANJAY_ID_RID && riid == ANJAY_ID_INVALID)\n            || (root_path_type == ANJAY_ID_IID && rid == ANJAY_ID_INVALID)) {\n        // Read all attached attributes\n        return _anjay_dm_effective_attrs(\n                anjay,\n                &(const anjay_dm_attrs_query_details_t) {\n                    .obj = obj,\n                    .iid = iid,\n                    .rid = rid,\n                    .riid = riid,\n                    .ssid = ssid,\n                    /**\n                     * Spec says we care about inherited attributes only.\n                     */\n                    .with_server_level_attrs = false\n                },\n                out);\n    }\n#    ifdef ANJAY_WITH_LWM2M11\n    if (riid != ANJAY_ID_INVALID) {\n        return _anjay_dm_call_resource_instance_read_attrs(anjay, obj, iid, rid,\n                                                           riid, ssid, out);\n    }\n#    endif // ANJAY_WITH_LWM2M11\n    if (rid != ANJAY_ID_INVALID) {\n        return _anjay_dm_call_resource_read_attrs(anjay, obj, iid, rid, ssid,\n                                                  out);\n    }\n    return _anjay_dm_call_instance_read_default_attrs(anjay, obj, iid, ssid,\n                                                      &out->common);\n}\n\ntypedef struct {\n    avs_stream_t *stream;\n    anjay_ssid_t ssid;\n    anjay_lwm2m_version_t lwm2m_version;\n    anjay_id_type_t root_path_type;\n    anjay_id_type_t leaf_path_type;\n} discover_clb_args_t;\n\n#    ifdef ANJAY_WITH_LWM2M11\nstatic int\nprint_discovered_resource_instance(avs_stream_t *stream,\n                                   const anjay_dm_installed_object_t *obj,\n                                   anjay_iid_t iid,\n                                   anjay_rid_t rid,\n                                   anjay_riid_t riid,\n                                   const anjay_dm_r_attributes_t *attrs) {\n    if (avs_is_err(avs_stream_write_f(\n                stream, \"</%\" PRIu16 \"/%\" PRIu16 \"/%\" PRIu16 \"/%\" PRIu16 \">\",\n                _anjay_dm_installed_object_oid(obj), iid, rid, riid))\n            || print_r_attrs(stream, attrs)) {\n        return -1;\n    }\n    return 0;\n}\n\nstatic int\ndiscover_resource_instance_clb(anjay_unlocked_t *anjay,\n                               const anjay_dm_installed_object_t *obj,\n                               anjay_iid_t iid,\n                               anjay_rid_t rid,\n                               anjay_riid_t riid,\n                               void *args_) {\n    discover_clb_args_t *args = (discover_clb_args_t *) args_;\n\n    anjay_dm_r_attributes_t attributes = ANJAY_DM_R_ATTRIBUTES_EMPTY;\n    int result;\n    (void) ((result = read_attrs(anjay, obj, iid, rid, riid, args->ssid,\n                                 args->lwm2m_version, args->root_path_type,\n                                 &attributes))\n            || (result = print_separator(args->stream))\n            || (result = print_discovered_resource_instance(\n                        args->stream, obj, iid, rid, riid, &attributes)));\n    return result;\n}\n#    endif // ANJAY_WITH_LWM2M11\n\nstatic int discover_resource(anjay_unlocked_t *anjay,\n                             avs_stream_t *stream,\n                             const anjay_dm_installed_object_t *obj,\n                             anjay_iid_t iid,\n                             anjay_rid_t rid,\n                             anjay_ssid_t ssid,\n                             anjay_lwm2m_version_t lwm2m_version,\n                             anjay_dm_resource_kind_t kind,\n                             anjay_id_type_t root_path_type,\n                             anjay_id_type_t leaf_path_type) {\n    int32_t resource_dim = -1;\n    int result = 0;\n\n    if (_anjay_dm_res_kind_multiple(kind)\n            && (root_path_type != ANJAY_ID_OID\n#    ifdef ANJAY_WITH_LWM2M12\n                || lwm2m_version >= ANJAY_LWM2M_VERSION_1_2\n#    endif // ANJAY_WITH_LWM2M12\n                )\n            && (result = read_resource_dim(anjay, obj, iid, rid,\n                                           &resource_dim))) {\n        return result;\n    }\n\n    anjay_dm_r_attributes_t attributes;\n    result = read_attrs(anjay, obj, iid, rid, ANJAY_ID_INVALID, ssid,\n                        lwm2m_version, root_path_type, &attributes);\n    if (!result) {\n        result = print_discovered_resource(stream, obj, iid, rid, resource_dim,\n                                           &attributes);\n    }\n#    ifdef ANJAY_WITH_LWM2M11\n    if (!result && leaf_path_type > ANJAY_ID_RID\n            && lwm2m_version >= ANJAY_LWM2M_VERSION_1_1\n            && _anjay_dm_res_kind_multiple(kind)) {\n        result = _anjay_dm_foreach_resource_instance(\n                anjay, obj, iid, rid, discover_resource_instance_clb,\n                &(discover_clb_args_t) {\n                    .stream = stream,\n                    .ssid = ssid,\n                    .lwm2m_version = lwm2m_version,\n                    .root_path_type = root_path_type,\n                    .leaf_path_type = leaf_path_type\n                });\n    }\n#    else  // ANJAY_WITH_LWM2M11\n    (void) leaf_path_type;\n#    endif // ANJAY_WITH_LWM2M11\n    return result;\n}\n\nstatic int\ndiscover_instance_resource_clb(anjay_unlocked_t *anjay,\n                               const anjay_dm_installed_object_t *obj,\n                               anjay_iid_t iid,\n                               anjay_rid_t rid,\n                               anjay_dm_resource_kind_t kind,\n                               anjay_dm_resource_presence_t presence,\n                               void *args_) {\n    discover_clb_args_t *args = (discover_clb_args_t *) args_;\n    int result = 0;\n    if (presence != ANJAY_DM_RES_ABSENT\n            && !(result = print_separator(args->stream))) {\n        result = discover_resource(anjay, args->stream, obj, iid, rid,\n                                   args->ssid, args->lwm2m_version, kind,\n                                   args->root_path_type, args->leaf_path_type);\n    }\n    return result;\n}\nstatic int discover_instance(anjay_unlocked_t *anjay,\n                             avs_stream_t *stream,\n                             const anjay_dm_installed_object_t *obj,\n                             anjay_iid_t iid,\n                             anjay_ssid_t ssid,\n                             anjay_lwm2m_version_t lwm2m_version,\n                             anjay_id_type_t root_path_type,\n                             anjay_id_type_t leaf_path_type) {\n    anjay_dm_r_attributes_t attributes;\n    int result = 0;\n    (void) ((result = read_attrs(anjay, obj, iid, ANJAY_ID_INVALID,\n                                 ANJAY_ID_INVALID, ssid, lwm2m_version,\n                                 root_path_type, &attributes))\n            || (result = print_discovered_instance(stream, obj, iid,\n                                                   &attributes.common)));\n    if (!result && leaf_path_type > ANJAY_ID_IID) {\n        result =\n                _anjay_dm_foreach_resource(anjay, obj, iid,\n                                           discover_instance_resource_clb,\n                                           &(discover_clb_args_t) {\n                                               .stream = stream,\n                                               .ssid = ssid,\n                                               .lwm2m_version = lwm2m_version,\n                                               .root_path_type = root_path_type,\n                                               .leaf_path_type = leaf_path_type\n                                           });\n    }\n    return result;\n}\n\nstatic int discover_object_instance_clb(anjay_unlocked_t *anjay,\n                                        const anjay_dm_installed_object_t *obj,\n                                        anjay_iid_t iid,\n                                        void *args_) {\n    discover_clb_args_t *args = (discover_clb_args_t *) args_;\n    int result = 0;\n    (void) ((result = print_separator(args->stream))\n            || (result = discover_instance(anjay, args->stream, obj, iid,\n                                           args->ssid, args->lwm2m_version,\n                                           args->root_path_type,\n                                           args->leaf_path_type)));\n    return result;\n}\n\nstatic int discover_object(anjay_unlocked_t *anjay,\n                           avs_stream_t *stream,\n                           const anjay_dm_installed_object_t *obj,\n                           anjay_ssid_t ssid,\n                           anjay_lwm2m_version_t lwm2m_version,\n                           anjay_id_type_t root_path_type,\n                           anjay_id_type_t leaf_path_type) {\n    anjay_dm_r_attributes_t attributes = ANJAY_DM_R_ATTRIBUTES_EMPTY;\n    int result = 0;\n    (void) ((result = read_attrs(anjay, obj, ANJAY_ID_INVALID, ANJAY_ID_INVALID,\n                                 ANJAY_ID_INVALID, ssid, lwm2m_version,\n                                 root_path_type, &attributes))\n            || (result = print_discovered_object(\n                        stream, obj, &attributes.common, lwm2m_version)));\n    if (!result && leaf_path_type > ANJAY_ID_OID) {\n        result =\n                _anjay_dm_foreach_instance(anjay, obj,\n                                           discover_object_instance_clb,\n                                           &(discover_clb_args_t) {\n                                               .stream = stream,\n                                               .ssid = ssid,\n                                               .lwm2m_version = lwm2m_version,\n                                               .root_path_type = root_path_type,\n                                               .leaf_path_type = leaf_path_type\n                                           });\n    }\n    return result;\n}\n\nint _anjay_discover(anjay_unlocked_t *anjay,\n                    avs_stream_t *stream,\n                    const anjay_dm_installed_object_t *obj,\n                    anjay_iid_t iid,\n                    anjay_rid_t rid,\n                    uint8_t depth,\n                    anjay_ssid_t ssid,\n                    anjay_lwm2m_version_t lwm2m_version) {\n    assert(obj);\n\n    if (iid == ANJAY_ID_INVALID) {\n        return discover_object(\n                anjay, stream, obj, ssid, lwm2m_version, ANJAY_ID_OID,\n                (anjay_id_type_t) AVS_MIN(ANJAY_ID_OID + depth, ANJAY_ID_RIID));\n    }\n\n    int result = _anjay_dm_verify_instance_present(anjay, obj, iid);\n    if (result) {\n        return result;\n    }\n\n    const anjay_action_info_t info = {\n        .oid = _anjay_dm_installed_object_oid(obj),\n        .iid = iid,\n        .ssid = ssid,\n        .action = ANJAY_ACTION_DISCOVER\n    };\n    if (!_anjay_instance_action_allowed(anjay, &info)) {\n        return ANJAY_ERR_UNAUTHORIZED;\n    }\n\n    if (rid == ANJAY_ID_INVALID) {\n        return discover_instance(\n                anjay, stream, obj, iid, ssid, lwm2m_version, ANJAY_ID_IID,\n                (anjay_id_type_t) AVS_MIN(ANJAY_ID_IID + depth, ANJAY_ID_RIID));\n    }\n\n    anjay_dm_resource_kind_t kind;\n    if ((result = _anjay_dm_verify_resource_present(anjay, obj, iid, rid,\n                                                    &kind))) {\n        return result;\n    }\n\n    return discover_resource(anjay, stream, obj, iid, rid, ssid, lwm2m_version,\n                             kind, ANJAY_ID_RID,\n                             (anjay_id_type_t) AVS_MIN(ANJAY_ID_RID + depth,\n                                                       ANJAY_ID_RIID));\n}\n\n#    ifdef ANJAY_WITH_BOOTSTRAP\nstatic int print_ssid_attr(avs_stream_t *stream, uint16_t ssid) {\n    return avs_is_ok(avs_stream_write_f(stream, \";\" ANJAY_ATTR_SSID \"=%\" PRIu16,\n                                        ssid))\n                   ? 0\n                   : -1;\n}\n\nstatic int print_enabler_version(avs_stream_t *stream,\n                                 anjay_lwm2m_version_t version) {\n    (void) version;\n    const char *format = \"lwm2m=\\\"%s\\\"\";\n#        ifdef ANJAY_WITH_LWM2M11\n    // Bug in specification.\n    // Technically it should be always with `</>;`, but we can't be sure 1.0\n    // servers will accept it, because it's defined in 1.1.1 TS.\n    if (version > ANJAY_LWM2M_VERSION_1_0) {\n        format = \"</>;lwm2m=%s\";\n    }\n#        endif // ANJAY_WITH_LWM2M11\n    return avs_is_ok(avs_stream_write_f(\n                   stream, format, _anjay_lwm2m_version_as_string(version)))\n                   ? 0\n                   : -1;\n}\n\n#        ifdef ANJAY_WITH_LWM2M11\nstatic int print_uri_attr(avs_stream_t *stream, const char *uri) {\n    avs_error_t err = avs_stream_write_f(stream, \";uri=\\\"\");\n    // escape '\"' and\n    for (const char *ch = uri; avs_is_ok(err) && *ch; ++ch) {\n        if (*ch == '\\\\' || *ch == '\"') {\n            err = avs_stream_write(stream, \"\\\\\", 1);\n        }\n        if (avs_is_ok(err)) {\n            err = avs_stream_write(stream, ch, 1);\n        }\n    }\n    if (avs_is_ok(err)) {\n        err = avs_stream_write(stream, \"\\\"\", 1);\n    }\n    return avs_is_ok(err) ? 0 : -1;\n}\n#        endif // ANJAY_WITH_LWM2M11\n\ntypedef struct {\n    avs_stream_t *stream;\n    anjay_lwm2m_version_t lwm2m_version;\n} bootstrap_discover_object_instance_args_t;\n\nstatic int\nbootstrap_discover_object_instance(anjay_unlocked_t *anjay,\n                                   const anjay_dm_installed_object_t *obj,\n                                   anjay_iid_t iid,\n                                   void *args_) {\n    bootstrap_discover_object_instance_args_t *args =\n            (bootstrap_discover_object_instance_args_t *) args_;\n    int result = 0;\n    (void) ((result = print_separator(args->stream))\n            || (result = print_discovered_instance(\n                        args->stream, obj, iid,\n                        &ANJAY_DM_OI_ATTRIBUTES_EMPTY)));\n    if (result) {\n        return result;\n    }\n    if (_anjay_dm_installed_object_oid(obj) == ANJAY_DM_OID_SECURITY) {\n        anjay_ssid_t ssid;\n        int query_result = _anjay_ssid_from_security_iid(anjay, iid, &ssid);\n        if (!query_result && ssid != ANJAY_SSID_BOOTSTRAP) {\n            result = print_ssid_attr(args->stream, ssid);\n        }\n#        ifdef ANJAY_WITH_LWM2M11\n        if (!result && args->lwm2m_version > ANJAY_LWM2M_VERSION_1_0) {\n            char buffer[ANJAY_MAX_URL_RAW_LENGTH];\n            query_result =\n                    _anjay_server_uri_from_security_iid(anjay, iid, buffer,\n                                                        sizeof(buffer));\n            if (!query_result) {\n                result = print_uri_attr(args->stream, buffer);\n            }\n        }\n#        endif // ANJAY_WITH_LWM2M11\n    } else if (_anjay_dm_installed_object_oid(obj) == ANJAY_DM_OID_SERVER) {\n        anjay_ssid_t ssid;\n        int query_result = _anjay_ssid_from_server_iid(anjay, iid, &ssid);\n        if (!query_result) {\n            result = print_ssid_attr(args->stream, ssid);\n        }\n    }\n    return result;\n}\n\ntypedef struct {\n    avs_stream_t *stream;\n    anjay_lwm2m_version_t lwm2m_version;\n} bootstrap_discover_object_args_t;\n\nstatic int bootstrap_discover_object(anjay_unlocked_t *anjay,\n                                     const anjay_dm_installed_object_t *obj,\n                                     void *args_) {\n    bootstrap_discover_object_args_t *args =\n            (bootstrap_discover_object_args_t *) args_;\n    bootstrap_discover_object_instance_args_t instance_args = {\n        .stream = args->stream,\n        .lwm2m_version = args->lwm2m_version\n    };\n    int result;\n    (void) ((result = print_separator(instance_args.stream))\n            || (result = print_discovered_object(instance_args.stream, obj,\n                                                 &ANJAY_DM_OI_ATTRIBUTES_EMPTY,\n                                                 instance_args.lwm2m_version))\n            || (result = _anjay_dm_foreach_instance(\n                        anjay, obj, bootstrap_discover_object_instance,\n                        &instance_args)));\n    return result;\n}\n\nint _anjay_bootstrap_discover(anjay_unlocked_t *anjay,\n                              avs_stream_t *stream,\n                              anjay_oid_t oid,\n                              anjay_lwm2m_version_t lwm2m_version) {\n    const anjay_dm_installed_object_t *obj = NULL;\n    if (oid != ANJAY_ID_INVALID) {\n        obj = _anjay_dm_find_object_by_oid(&anjay->dm, oid);\n        if (!obj) {\n            return ANJAY_ERR_NOT_FOUND;\n        }\n    }\n    int result = print_enabler_version(stream, lwm2m_version);\n    if (result) {\n        return result;\n    }\n    bootstrap_discover_object_args_t args = {\n        .stream = stream,\n        .lwm2m_version = lwm2m_version\n    };\n    if (obj) {\n        return bootstrap_discover_object(anjay, obj, &args);\n    } else {\n        return _anjay_dm_foreach_object(anjay, &anjay->dm,\n                                        bootstrap_discover_object, &args);\n    }\n}\n#    endif\n\n#endif // ANJAY_WITH_DISCOVER\n"
  },
  {
    "path": "src/core/dm/anjay_discover.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ANJAY_DM_DISCOVER_H\n#define ANJAY_DM_DISCOVER_H\n\n#include <anjay/dm.h>\n\n#include <anjay_modules/anjay_dm_utils.h>\n\n#include <avsystem/commons/avs_stream.h>\n#include <avsystem/commons/avs_stream_v_table.h>\n\n#include \"../anjay_utils_private.h\"\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\n#ifdef ANJAY_WITH_DISCOVER\n/**\n * Performs LwM2M Discover operation.\n *\n * @param anjay         ANJAY object to operate on.\n * @param stream        Stream where result of Discover shall be written.\n * @param obj           Object on which Discover shall be performed.\n * @param iid           ID of the Object Instance on which Discover shall be\n *                      performed,\n *                      or ANJAY_ID_INVALID if it is to be performed on the\n *                      Object.\n * @param rid           ID of the Resource on which Discover shall be performed,\n *                      or ANJAY_ID_INVALID if it is to be performed on the\n *                      Object or Object Instance.\n * @param depth         Number of nesting levels into which to recursively\n *                      perform the discover operation.\n * @param ssid          SSID of the server for which the operation is performed.\n * @param lwm2m_version LwM2M version to which the response shall comply.\n *\n * @return 0 on success, negative value in case of an error.\n */\nint _anjay_discover(anjay_unlocked_t *anjay,\n                    avs_stream_t *stream,\n                    const anjay_dm_installed_object_t *obj,\n                    anjay_iid_t iid,\n                    anjay_rid_t rid,\n                    uint8_t depth,\n                    anjay_ssid_t ssid,\n                    anjay_lwm2m_version_t lwm2m_version);\n\n#    ifdef ANJAY_WITH_BOOTSTRAP\n/**\n * Performs LwM2M Bootstrap Discover operation.\n *\n * @param anjay         ANJAY object to operate on.\n * @param stream        Stream where result of Bootstrap Discover shall be\n *                      written.\n * @param oid           ID of the Object on which to perform the operation - may\n *                      also be ANJAY_ID_INVALID, in which case it will be\n *                      interpreted as a Discover on the root path.\n * @param lwm2m_version LwM2M version to which the response shall comply.\n * @return 0 on success, negative value in case of an error.\n */\nint _anjay_bootstrap_discover(anjay_unlocked_t *anjay,\n                              avs_stream_t *stream,\n                              anjay_oid_t oid,\n                              anjay_lwm2m_version_t lwm2m_version);\n#    endif // ANJAY_WITH_BOOTSTRAP\n\n#endif // ANJAY_WITH_DISCOVER\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif /* ANJAY_DM_DISCOVER_H */\n"
  },
  {
    "path": "src/core/dm/anjay_dm_attributes.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#include <inttypes.h>\n#include <stdint.h>\n\n#include \"../anjay_core.h\"\n#include \"../anjay_utils_private.h\"\n\n#include \"anjay_dm_attributes.h\"\n#include \"anjay_query.h\"\n\nVISIBILITY_SOURCE_BEGIN\n\n#ifdef ANJAY_WITH_CON_ATTR\n#    define _ANJAY_DM_CUSTOM_CON_ATTR_INITIALIZER \\\n        ,                                         \\\n                .con = ANJAY_DM_CON_ATTR_NONE\n#else // ANJAY_WITH_CON_ATTR\n#    define _ANJAY_DM_CUSTOM_CON_ATTR_INITIALIZER\n#endif // ANJAY_WITH_CON_ATTR\n\n#ifdef ANJAY_WITH_LWM2M12\n#    define _ANJAY_DM_OI_ATTRIBUTES_EMPTY                  \\\n        {                                                  \\\n            .min_period = ANJAY_ATTRIB_INTEGER_NONE,       \\\n            .max_period = ANJAY_ATTRIB_INTEGER_NONE,       \\\n            .min_eval_period = ANJAY_ATTRIB_INTEGER_NONE,  \\\n            .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE   \\\n                    _ANJAY_DM_CUSTOM_CON_ATTR_INITIALIZER, \\\n            .hqmax = ANJAY_ATTRIB_INTEGER_NONE             \\\n        }\n#else // ANJAY_WITH_LWM2M12\n#    define _ANJAY_DM_OI_ATTRIBUTES_EMPTY                 \\\n        {                                                 \\\n            .min_period = ANJAY_ATTRIB_INTEGER_NONE,      \\\n            .max_period = ANJAY_ATTRIB_INTEGER_NONE,      \\\n            .min_eval_period = ANJAY_ATTRIB_INTEGER_NONE, \\\n            .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE  \\\n                    _ANJAY_DM_CUSTOM_CON_ATTR_INITIALIZER \\\n        }\n#endif // ANJAY_WITH_LWM2M12\n\n#ifdef ANJAY_WITH_LWM2M12\n#    define _ANJAY_DM_R_ATTRIBUTES_EMPTY              \\\n        {                                             \\\n            .common = _ANJAY_DM_OI_ATTRIBUTES_EMPTY,  \\\n            .greater_than = ANJAY_ATTRIB_DOUBLE_NONE, \\\n            .less_than = ANJAY_ATTRIB_DOUBLE_NONE,    \\\n            .step = ANJAY_ATTRIB_DOUBLE_NONE,         \\\n            .edge = ANJAY_DM_EDGE_ATTR_NONE           \\\n        }\n#else // ANJAY_WITH_LWM2M12\n#    define _ANJAY_DM_R_ATTRIBUTES_EMPTY              \\\n        {                                             \\\n            .common = _ANJAY_DM_OI_ATTRIBUTES_EMPTY,  \\\n            .greater_than = ANJAY_ATTRIB_DOUBLE_NONE, \\\n            .less_than = ANJAY_ATTRIB_DOUBLE_NONE,    \\\n            .step = ANJAY_ATTRIB_DOUBLE_NONE          \\\n        }\n#endif // ANJAY_WITH_LWM2M12\n\nconst anjay_dm_oi_attributes_t ANJAY_DM_OI_ATTRIBUTES_EMPTY =\n        _ANJAY_DM_OI_ATTRIBUTES_EMPTY;\nconst anjay_dm_r_attributes_t ANJAY_DM_R_ATTRIBUTES_EMPTY =\n        _ANJAY_DM_R_ATTRIBUTES_EMPTY;\n\nstatic inline void combine_integer(int32_t *out, int32_t other) {\n    if (*out < 0) {\n        *out = other;\n    }\n}\n\nstatic inline void combine_double(double *out, double other) {\n    if (isnan(*out)) {\n        *out = other;\n    }\n}\n\nstatic inline void combine_attrs(anjay_dm_oi_attributes_t *out,\n                                 const anjay_dm_oi_attributes_t *other) {\n#ifdef ANJAY_WITH_CON_ATTR\n    if (out->con < 0) {\n        out->con = other->con;\n    }\n#endif\n    combine_integer(&out->min_period, other->min_period);\n    combine_integer(&out->max_period, other->max_period);\n    combine_integer(&out->min_eval_period, other->min_eval_period);\n    combine_integer(&out->max_eval_period, other->max_eval_period);\n#ifdef ANJAY_WITH_LWM2M12\n    combine_integer(&out->hqmax, other->hqmax);\n#endif // ANJAY_WITH_LWM2M12\n}\n\nstatic inline void\ncombine_resource_attrs(anjay_dm_r_attributes_t *out,\n                       const anjay_dm_r_attributes_t *other) {\n    combine_attrs(&out->common, &other->common);\n    combine_double(&out->greater_than, other->greater_than);\n    combine_double(&out->less_than, other->less_than);\n    combine_double(&out->step, other->step);\n#ifdef ANJAY_WITH_LWM2M12\n    if (out->edge < 0) {\n        out->edge = other->edge;\n    }\n#endif // ANJAY_WITH_LWM2M12\n}\n\nint _anjay_read_period(anjay_unlocked_t *anjay,\n                       anjay_iid_t server_iid,\n                       anjay_rid_t rid,\n                       int32_t *out) {\n    int64_t value;\n    const anjay_uri_path_t path =\n            MAKE_RESOURCE_PATH(ANJAY_DM_OID_SERVER, server_iid, rid);\n\n    int result = _anjay_dm_read_resource_i64(anjay, &path, &value);\n    if (result == ANJAY_ERR_METHOD_NOT_ALLOWED\n            || result == ANJAY_ERR_NOT_FOUND) {\n        *out = ANJAY_ATTRIB_INTEGER_NONE;\n        return 0;\n    } else if (result < 0) {\n        return result;\n    } else if (value < 0 || value > INT32_MAX) {\n        return ANJAY_ATTRIB_INTEGER_NONE;\n    } else {\n        *out = (int32_t) value;\n        return 0;\n    }\n}\n\nstatic int read_combined_period(anjay_unlocked_t *anjay,\n                                anjay_iid_t server_iid,\n                                anjay_rid_t rid,\n                                int32_t *out) {\n    if (*out < 0) {\n        return _anjay_read_period(anjay, server_iid, rid, out);\n    } else {\n        return 0;\n    }\n}\n\nint _anjay_dm_read_combined_server_attrs(anjay_unlocked_t *anjay,\n                                         anjay_ssid_t ssid,\n                                         anjay_dm_oi_attributes_t *out) {\n    if (out->min_period >= 0 && out->max_period >= 0) {\n        return 0;\n    }\n    anjay_iid_t server_iid = ANJAY_ID_INVALID;\n    if (_anjay_find_server_iid(anjay, ssid, &server_iid)) {\n        anjay_log(\n                WARNING,\n                _(\"Could not find Server IID for Short Server ID: \") \"%\" PRIu16,\n                ssid);\n    } else {\n        int result;\n        if ((result = read_combined_period(anjay, server_iid,\n                                           ANJAY_DM_RID_SERVER_DEFAULT_PMIN,\n                                           &out->min_period))\n                || (result = read_combined_period(\n                            anjay, server_iid, ANJAY_DM_RID_SERVER_DEFAULT_PMAX,\n                            &out->max_period))) {\n            return result;\n        }\n    }\n    if (out->min_period < 0) {\n        out->min_period = ANJAY_DM_DEFAULT_PMIN_VALUE;\n    }\n    return 0;\n}\n\nstatic int\ndm_read_combined_resource_attrs(anjay_unlocked_t *anjay,\n                                const anjay_dm_installed_object_t *obj,\n                                anjay_iid_t iid,\n                                anjay_rid_t rid,\n                                anjay_ssid_t ssid,\n                                anjay_dm_r_attributes_t *out) {\n    if (!_anjay_dm_resource_attributes_full(out)) {\n        anjay_dm_r_attributes_t resattrs = ANJAY_DM_R_ATTRIBUTES_EMPTY;\n        int result = _anjay_dm_call_resource_read_attrs(anjay, obj, iid, rid,\n                                                        ssid, &resattrs);\n        if (result) {\n            return result;\n        }\n        combine_resource_attrs(out, &resattrs);\n    }\n    return 0;\n}\n\nstatic int\ndm_read_combined_instance_attrs(anjay_unlocked_t *anjay,\n                                const anjay_dm_installed_object_t *obj,\n                                anjay_iid_t iid,\n                                anjay_ssid_t ssid,\n                                anjay_dm_oi_attributes_t *out) {\n    if (!_anjay_dm_attributes_full(out)) {\n        anjay_dm_oi_attributes_t instattrs = ANJAY_DM_OI_ATTRIBUTES_EMPTY;\n        int result =\n                _anjay_dm_call_instance_read_default_attrs(anjay, obj, iid,\n                                                           ssid, &instattrs);\n        if (result) {\n            return result;\n        }\n        combine_attrs(out, &instattrs);\n    }\n    return 0;\n}\n\nstatic int dm_read_combined_object_attrs(anjay_unlocked_t *anjay,\n                                         const anjay_dm_installed_object_t *obj,\n                                         anjay_ssid_t ssid,\n                                         anjay_dm_oi_attributes_t *out) {\n    if (!_anjay_dm_attributes_full(out)) {\n        anjay_dm_oi_attributes_t objattrs = ANJAY_DM_OI_ATTRIBUTES_EMPTY;\n        int result = _anjay_dm_call_object_read_default_attrs(anjay, obj, ssid,\n                                                              &objattrs);\n        if (result) {\n            return result;\n        }\n        combine_attrs(out, &objattrs);\n    }\n    return 0;\n}\n\nbool _anjay_dm_attributes_empty(const anjay_dm_oi_attributes_t *attrs) {\n    return attrs->min_period < 0 && attrs->max_period < 0\n           && attrs->min_eval_period < 0 && attrs->max_eval_period < 0\n#ifdef ANJAY_WITH_LWM2M12\n           && attrs->hqmax < 0\n#endif // ANJAY_WITH_LWM2M12\n#ifdef ANJAY_WITH_CON_ATTR\n           && attrs->con < 0\n#endif // ANJAY_WITH_CON_ATTR\n            ;\n}\n\nbool _anjay_dm_resource_attributes_empty(const anjay_dm_r_attributes_t *attrs) {\n    return _anjay_dm_attributes_empty(&attrs->common)\n           && isnan(attrs->greater_than) && isnan(attrs->less_than)\n           && isnan(attrs->step)\n#ifdef ANJAY_WITH_LWM2M12\n           && attrs->edge < 0\n#endif // ANJAY_WITH_LWM2M12\n            ;\n}\n\nbool _anjay_dm_attributes_full(const anjay_dm_oi_attributes_t *attrs) {\n    return attrs->min_period >= 0 && attrs->max_period >= 0\n           && attrs->min_eval_period >= 0 && attrs->max_eval_period >= 0\n#ifdef ANJAY_WITH_LWM2M12\n           && attrs->hqmax >= 0\n#endif // ANJAY_WITH_LWM2M12\n#ifdef ANJAY_WITH_CON_ATTR\n           && attrs->con >= 0\n#endif // ANJAY_WITH_CON_ATTR\n            ;\n}\n\nbool _anjay_dm_resource_attributes_full(const anjay_dm_r_attributes_t *attrs) {\n    // _anjay_dm_attributes_full() already checks if\n    // con != ANJAY_DM_CON_ATTR_NONE\n    return _anjay_dm_attributes_full(&attrs->common)\n           && !isnan(attrs->greater_than) && !isnan(attrs->less_than)\n           && !isnan(attrs->step)\n#ifdef ANJAY_WITH_LWM2M12\n           && attrs->edge >= 0\n#endif // ANJAY_WITH_LWM2M12\n            ;\n}\n\nint _anjay_dm_effective_attrs(anjay_unlocked_t *anjay,\n                              const anjay_dm_attrs_query_details_t *query,\n                              anjay_dm_r_attributes_t *out) {\n    int result = 0;\n    *out = ANJAY_DM_R_ATTRIBUTES_EMPTY;\n\n    if (query->obj) {\n        assert(_anjay_uri_path_normalized(\n                &MAKE_URI_PATH(_anjay_dm_installed_object_oid(query->obj),\n                               query->iid, query->rid, query->riid)));\n\n#ifdef ANJAY_WITH_LWM2M11\n        if (query->riid != ANJAY_ID_INVALID) {\n            result = _anjay_dm_call_resource_instance_read_attrs(\n                    anjay, query->obj, query->iid, query->rid, query->riid,\n                    query->ssid, out);\n            if (result) {\n                return result;\n            }\n        }\n#endif // ANJAY_WITH_LWM2M11\n\n        if (query->rid != ANJAY_ID_INVALID) {\n            result = dm_read_combined_resource_attrs(anjay, query->obj,\n                                                     query->iid, query->rid,\n                                                     query->ssid, out);\n            if (result) {\n                return result;\n            }\n        }\n\n        if (query->iid != ANJAY_ID_INVALID) {\n            result = dm_read_combined_instance_attrs(\n                    anjay, query->obj, query->iid, query->ssid, &out->common);\n            if (result) {\n                return result;\n            }\n        }\n\n        result = dm_read_combined_object_attrs(anjay, query->obj, query->ssid,\n                                               &out->common);\n    }\n    if (!result && query->with_server_level_attrs) {\n        return _anjay_dm_read_combined_server_attrs(anjay, query->ssid,\n                                                    &out->common);\n    }\n    return result;\n}\n"
  },
  {
    "path": "src/core/dm/anjay_dm_attributes.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ANJAY_DM_ATTRIBUTES_H\n#define ANJAY_DM_ATTRIBUTES_H\n#include <anjay_modules/anjay_dm_utils.h>\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\n#define ANJAY_ATTR_PMIN \"pmin\"\n#define ANJAY_ATTR_PMAX \"pmax\"\n#define ANJAY_ATTR_EPMIN \"epmin\"\n#define ANJAY_ATTR_EPMAX \"epmax\"\n#ifdef ANJAY_WITH_LWM2M12\n#    define ANJAY_ATTR_HQMAX \"hqmax\"\n#endif // ANJAY_WITH_LWM2M12\n#define ANJAY_ATTR_GT \"gt\"\n#define ANJAY_ATTR_LT \"lt\"\n#define ANJAY_ATTR_ST \"st\"\n#ifdef ANJAY_WITH_LWM2M12\n#    define ANJAY_ATTR_EDGE \"edge\"\n#endif // ANJAY_WITH_LWM2M12\n#define ANJAY_ATTR_SSID \"ssid\"\n\n#define ANJAY_CUSTOM_ATTR_CON \"con\"\n\ntypedef struct {\n    /** Object whose Instance is being queried. */\n    const anjay_dm_installed_object_t *obj;\n    /** Instance whose Resource is being queried. */\n    anjay_iid_t iid;\n    /**\n     * Resource whose Attributes are being queried, or ANJAY_ID_INVALID in case\n     * when query on an Instance is only performed.\n     */\n    anjay_rid_t rid;\n    /**\n     * Resource Instance whose Attributes are being queried, or ANJAY_ID_INVALID\n     * in case when query on a Resource is only performed.\n     */\n    anjay_riid_t riid;\n    /** Server, for which Attributes shall be obtained. */\n    anjay_ssid_t ssid;\n    /**\n     * true if no matter what we are interested in inherited Server level\n     * attributes.\n     */\n    bool with_server_level_attrs;\n} anjay_dm_attrs_query_details_t;\n\n/**\n * Obtains attributes for a specific LwM2M path by combining attributes from\n * different levels.\n *\n * WARNING: This function does not check whether path is valid, i.e. whether\n * Resource and/or Instance is present - caller must ensure that it is indeed\n * the case.\n *\n * Attribute inheritance logic (assuming Resource and Instance ids are\n * provided):\n *\n *  0. Set *out to ANJAY_DM_INTERNAL_R_ATTRS_EMPTY.\n *  1. Read Resource Instance attributes and combine them with *out attributes.\n *  2. Read Resource attributes and combine them with *out attributes.\n *  3. Read Instance attributes and combine them with *out attributes.\n *  4. Read Object attributes and combine them with *out attributes.\n *  5. (If with_server_level_attrs is set) Read Server attributes and combine\n *     them with *out attributes.\n *\n * Additional information:\n * - If any step from above fails, then the function returns negative value.\n * - If @p query->rid is negative, then attributes of the Resource are not\n *   queried.\n * - If @p query->iid is ANJAY_ID_INVALID, then attributes of the Instance\n *   are not queried.\n *\n * @param anjay     ANJAY object to operate on.\n * @param query     Query details.\n * @param out       Result of query.\n * @return 0 on success, negative value in case of an error.\n */\nint _anjay_dm_effective_attrs(anjay_unlocked_t *anjay,\n                              const anjay_dm_attrs_query_details_t *query,\n                              anjay_dm_r_attributes_t *out);\n\n/**\n * Reads an integer resource value for some server instance.\n * Designed to read values of Default Minimum/Maximum Period resources.\n *\n * @param anjay      ANJAY object to operate on.\n * @param server_iid Server instance id to read value from.\n * @param rid        Resource id.\n * @param out        Result of the read.\n * @return 0 on success, negative value in case of an error.\n */\nint _anjay_read_period(anjay_unlocked_t *anjay,\n                       anjay_iid_t server_iid,\n                       anjay_rid_t rid,\n                       int32_t *out);\n\n/**\n * If Minimum/Maximum Period attribute is not present, it sets it to the value\n * of the Default Minimum/Maximum Period resource of the given server instance.\n *\n * @param anjay      ANJAY object to operate on.\n * @param ssid       SSID of the server.\n * @param out        Attributes to which te result values should be written.\n * @return 0 on success, negative value in case of an error.\n */\nint _anjay_dm_read_combined_server_attrs(anjay_unlocked_t *anjay,\n                                         anjay_ssid_t ssid,\n                                         anjay_dm_oi_attributes_t *out);\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif // ANJAY_DM_ATTRIBUTES_H\n"
  },
  {
    "path": "src/core/dm/anjay_dm_create.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#include <avsystem/commons/avs_utils.h>\n\n#include \"anjay_dm_create.h\"\n#include \"anjay_dm_write.h\"\n\n#include \"../anjay_access_utils_private.h\"\n#include \"../io/anjay_vtable.h\"\n\n#include <inttypes.h>\n\nVISIBILITY_SOURCE_BEGIN\n\nstatic int\nsetup_create_response(anjay_oid_t oid,\n                      anjay_iid_t iid,\n                      avs_coap_streaming_request_ctx_t *request_ctx) {\n    AVS_STATIC_ASSERT(((anjay_oid_t) -1) == 65535, oid_is_u16);\n    AVS_STATIC_ASSERT(((anjay_iid_t) -1) == 65535, iid_is_u16);\n    char oid_str[6];\n    char iid_str[6];\n    int result =\n            avs_simple_snprintf(oid_str, sizeof(oid_str), \"%\" PRIu16, oid) < 0\n                    ? -1\n                    : 0;\n    if (!result) {\n        result = avs_simple_snprintf(iid_str, sizeof(iid_str), \"%\" PRIu16, iid)\n                                 < 0\n                         ? -1\n                         : 0;\n    }\n    if (!result) {\n        anjay_msg_details_t msg_details = {\n            .msg_code =\n                    _anjay_dm_make_success_response_code(ANJAY_ACTION_CREATE),\n            .format = AVS_COAP_FORMAT_NONE,\n            .location_path = ANJAY_MAKE_STRING_LIST(oid_str, iid_str)\n        };\n        if (!msg_details.location_path\n                || !_anjay_coap_setup_response_stream(request_ctx,\n                                                      &msg_details)) {\n            result = -1;\n        }\n        AVS_LIST_CLEAR(&msg_details.location_path);\n    }\n    return result;\n}\n\nstatic int dm_create_select_iid_clb(anjay_unlocked_t *anjay,\n                                    const anjay_dm_installed_object_t *obj,\n                                    anjay_iid_t iid,\n                                    void *new_iid_ptr_) {\n    (void) anjay;\n    (void) obj;\n    anjay_iid_t *new_iid_ptr = (anjay_iid_t *) new_iid_ptr_;\n    if (iid == *new_iid_ptr) {\n        ++*new_iid_ptr;\n        return ANJAY_FOREACH_CONTINUE;\n    } else {\n        return ANJAY_FOREACH_BREAK;\n    }\n}\n\nint _anjay_dm_select_free_iid(anjay_unlocked_t *anjay,\n                              const anjay_dm_installed_object_t *obj,\n                              anjay_iid_t *new_iid_ptr) {\n    *new_iid_ptr = 0;\n    int result =\n            _anjay_dm_foreach_instance(anjay, obj, dm_create_select_iid_clb,\n                                       new_iid_ptr);\n    if (!result && *new_iid_ptr == ANJAY_ID_INVALID) {\n        dm_log(ERROR, _(\"65535 object instances already exist\"));\n        return ANJAY_ERR_BAD_REQUEST;\n    }\n    return result;\n}\n\nstatic int\ndm_create_inner_and_move_to_next_entry(anjay_unlocked_t *anjay,\n                                       const anjay_dm_installed_object_t *obj,\n                                       anjay_iid_t iid,\n                                       anjay_unlocked_input_ctx_t *in_ctx) {\n    assert(iid != ANJAY_ID_INVALID);\n    int result = _anjay_dm_call_instance_create(anjay, obj, iid);\n    if (result) {\n        dm_log(DEBUG,\n               _(\"Instance Create handler for object \") DM_LOG_PREFIX\n               \"/%\" PRIu16 _(\" failed\"),\n               DM_LOG_PREFIX_OBJ_ARG(obj) _anjay_dm_installed_object_oid(obj));\n        return result;\n    } else if ((result =\n                        _anjay_dm_write_created_instance_and_move_to_next_entry(\n                                anjay, obj, iid, in_ctx))) {\n        dm_log(DEBUG,\n               _(\"Writing Resources for newly created \") DM_LOG_PREFIX\n               \"/%\" PRIu16 \"/%\" PRIu16 _(\" failed; removing\"),\n               DM_LOG_PREFIX_OBJ_ARG(obj) _anjay_dm_installed_object_oid(obj),\n               iid);\n    }\n    return result;\n}\n\nstatic int dm_create_with_explicit_iid(anjay_unlocked_t *anjay,\n                                       const anjay_dm_installed_object_t *obj,\n                                       anjay_iid_t iid,\n                                       anjay_unlocked_input_ctx_t *in_ctx) {\n    if (iid == ANJAY_ID_INVALID) {\n        return ANJAY_ERR_BAD_REQUEST;\n    }\n    int result = _anjay_dm_instance_present(anjay, obj, iid);\n    if (result > 0) {\n        dm_log(DEBUG,\n               _(\"Instance \") DM_LOG_PREFIX \"/%\" PRIu16\n                                            \"/%\" PRIu16 _(\" already exists\"),\n               DM_LOG_PREFIX_OBJ_ARG(obj) _anjay_dm_installed_object_oid(obj),\n               iid);\n        return ANJAY_ERR_BAD_REQUEST;\n    } else if (result) {\n        dm_log(DEBUG,\n               _(\"Instance Present handler for \") DM_LOG_PREFIX\n               \"/%\" PRIu16 \"/%\" PRIu16 _(\" failed\"),\n               DM_LOG_PREFIX_OBJ_ARG(obj) _anjay_dm_installed_object_oid(obj),\n               iid);\n        return result;\n    }\n    result = dm_create_inner_and_move_to_next_entry(anjay, obj, iid, in_ctx);\n    if (!result) {\n        result = _anjay_input_get_path(in_ctx, NULL, NULL);\n        if (result == ANJAY_GET_PATH_END) {\n            return 0;\n        } else {\n            dm_log(DEBUG, _(\"More than one Object Instance or broken input \"\n                            \"stream while processing Object Create\"));\n            return result ? result : ANJAY_ERR_BAD_REQUEST;\n        }\n    }\n    return result;\n}\n\nint _anjay_dm_create(anjay_unlocked_t *anjay,\n                     const anjay_dm_installed_object_t *obj,\n                     const anjay_request_t *request,\n                     anjay_ssid_t ssid,\n                     anjay_unlocked_input_ctx_t *in_ctx) {\n    dm_log(LAZY_DEBUG, _(\"Create \") \"%s\", ANJAY_DEBUG_MAKE_PATH(&request->uri));\n    assert(_anjay_uri_path_leaf_is(&request->uri, ANJAY_ID_OID));\n\n    if (!_anjay_instance_action_allowed(anjay, &REQUEST_TO_ACTION_INFO(request,\n                                                                       ssid))) {\n        return ANJAY_ERR_UNAUTHORIZED;\n    }\n\n    anjay_uri_path_t path = MAKE_ROOT_PATH();\n    int result = _anjay_input_get_path(in_ctx, &path, NULL);\n    if (!result || result == ANJAY_GET_PATH_END) {\n        if (_anjay_uri_path_has(&path, ANJAY_ID_IID)) {\n            result =\n                    dm_create_with_explicit_iid(anjay, obj,\n                                                path.ids[ANJAY_ID_IID], in_ctx);\n        } else {\n            path = MAKE_OBJECT_PATH(_anjay_dm_installed_object_oid(obj));\n            (void) ((result = _anjay_dm_select_free_iid(\n                             anjay, obj, &path.ids[ANJAY_ID_IID]))\n                    || (result = _anjay_input_update_root_path(in_ctx, &path))\n                    || (result = dm_create_inner_and_move_to_next_entry(\n                                anjay, obj, path.ids[ANJAY_ID_IID], in_ctx)));\n        }\n    }\n    if (!result) {\n        dm_log(LAZY_DEBUG, _(\"created: \") \"%s\", ANJAY_DEBUG_MAKE_PATH(&path));\n        if ((result = setup_create_response(_anjay_dm_installed_object_oid(obj),\n                                            path.ids[ANJAY_ID_IID],\n                                            request->ctx))) {\n            dm_log(DEBUG, _(\"Could not prepare response message.\"));\n        }\n    }\n    if (!result) {\n        anjay_notify_queue_t notify_queue = NULL;\n        path.ids[ANJAY_ID_OID] = request->uri.ids[ANJAY_ID_OID];\n#ifdef ANJAY_WITH_LWM2M_GATEWAY\n        if (_anjay_uri_path_has_prefix(&request->uri)) {\n            strcpy(path.prefix, request->uri.prefix);\n        }\n#endif // ANJAY_WITH_LWM2M_GATEWAY\n        (void) ((result = _anjay_notify_queue_instance_created(&notify_queue,\n                                                               &path))\n                || (result = _anjay_notify_flush(anjay, ssid, &notify_queue)));\n    }\n    return result;\n}\n"
  },
  {
    "path": "src/core/dm/anjay_dm_create.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ANJAY_CREATE_CORE_H\n#define ANJAY_CREATE_CORE_H\n\n#include <anjay/core.h>\n\n#include \"../anjay_dm_core.h\"\n#include \"../anjay_io_core.h\"\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\nint _anjay_dm_create(anjay_unlocked_t *anjay,\n                     const anjay_dm_installed_object_t *obj,\n                     const anjay_request_t *request,\n                     anjay_ssid_t ssid,\n                     anjay_unlocked_input_ctx_t *in_ctx);\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif // ANJAY_CREATE_CORE_H\n"
  },
  {
    "path": "src/core/dm/anjay_dm_execute.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n#include <assert.h>\n#include <ctype.h>\n#include <stdio.h>\n#include <stdlib.h>\n\n#include \"../anjay_dm_core.h\"\n#include \"../anjay_io_core.h\"\n#include \"anjay_dm_execute.h\"\n\nVISIBILITY_SOURCE_BEGIN\n\nstatic anjay_execute_state_t state_read_value(anjay_unlocked_execute_ctx_t *ctx,\n                                              int ch);\nstatic anjay_execute_state_t\nstate_read_argument(anjay_unlocked_execute_ctx_t *ctx, int ch);\n\nstatic int get_next_char(anjay_unlocked_execute_ctx_t *ctx) {\n    char read_char;\n    avs_error_t err = avs_stream_read_reliably(\n            ctx->payload_stream, &read_char, sizeof(read_char));\n\n    if (avs_is_ok(err)) {\n        return read_char;\n    }\n\n    return EOF;\n}\n\nstatic int peek_next_char(anjay_unlocked_execute_ctx_t *ctx) {\n    char peeked_char;\n    avs_error_t err = avs_stream_peek(ctx->payload_stream, 0, &peeked_char);\n\n    if (avs_is_ok(err)) {\n        return peeked_char;\n    }\n\n    return EOF;\n}\n\nstatic bool is_arg_separator(int byte) {\n    return byte == ',';\n}\n\nstatic bool is_value_delimiter(int byte) {\n    return byte == '\\'';\n}\n\nstatic bool is_value(int byte) {\n    /* See OMA Specification Execute section, for more details. */\n    // clang-format off\n    return byte == '!'\n        || (byte >= 0x23 && byte <= 0x26)\n        || (byte >= 0x28 && byte <= 0x5b)\n        || (byte >= 0x5d && byte <= 0x7e);\n    // clang-format on\n}\n\nstatic bool is_value_assignment(int byte) {\n    return byte == '=';\n}\n\nstatic int try_reading_next_arg(anjay_unlocked_execute_ctx_t *ctx) {\n    if (ctx->state == STATE_ERROR) {\n        return -1;\n    }\n    ctx->arg = -1;\n    ctx->arg_has_value = false;\n\n    // STATE_FINISHED_READING_ARGUMENT means that the last read character was\n    // arg separator. We reject payload with trailing separator.\n    int next_char = get_next_char(ctx);\n    if (ctx->state == STATE_FINISHED_READING_ARGUMENT && next_char == EOF) {\n        return -1;\n    }\n\n    while (true) {\n        ctx->state = state_read_argument(ctx, next_char);\n        if (ctx->state != STATE_READ_ARGUMENT) {\n            break;\n        }\n        next_char = get_next_char(ctx);\n    }\n\n    if (ctx->arg == -1 && ctx->state == STATE_EOF) {\n        return ANJAY_EXECUTE_GET_ARG_END;\n    } else if (ctx->arg == -1 || ctx->state == STATE_ERROR) {\n        return -1;\n    }\n    return 0;\n}\n\nint _anjay_execute_get_arg_value_unlocked(anjay_unlocked_execute_ctx_t *ctx,\n                                          size_t *out_bytes_read,\n                                          char *out_buf,\n                                          size_t buf_size) {\n    size_t read_bytes = 0;\n    bool value_finished = true;\n    if (ctx->state == STATE_READ_VALUE) {\n        if (buf_size < 2 || !out_buf) {\n            dm_log(ERROR,\n                   _(\"Invalid arguments passed to \"\n                     \"anjay_execute_get_arg_value(): \"\n                     \"needs a buffer with at least 2 bytes size\"));\n            return -1;\n        }\n\n        while (read_bytes < buf_size - 1) {\n            int ch = get_next_char(ctx);\n            ctx->state = state_read_value(ctx, ch);\n\n            if (ctx->state == STATE_READ_VALUE) {\n                out_buf[read_bytes++] = (char) ch;\n                value_finished = is_value_delimiter(peek_next_char(ctx));\n            } else {\n                value_finished = true;\n                break;\n            }\n        }\n    }\n    out_buf[read_bytes] = '\\0';\n\n    if (out_bytes_read) {\n        *out_bytes_read = read_bytes;\n    }\n\n    if (ctx->state == STATE_ERROR) {\n        return ANJAY_ERR_BAD_REQUEST;\n    } else if (value_finished) {\n        return 0;\n    } else {\n        return ANJAY_BUFFER_TOO_SHORT;\n    }\n}\n\nstatic int skip_value(anjay_unlocked_execute_ctx_t *ctx) {\n    /*\n     * If we are in the middle of reading the value assigned to the argument,\n     * we'll skip the rest of it.\n     */\n    int ret = 0;\n    if (ctx->state == STATE_READ_VALUE) {\n        char buf[64];\n        do {\n            ret = _anjay_execute_get_arg_value_unlocked(\n                    ctx, NULL, buf, sizeof(buf));\n        } while (ret == ANJAY_BUFFER_TOO_SHORT);\n    }\n    return ret;\n}\n\nint _anjay_execute_get_next_arg_unlocked(anjay_unlocked_execute_ctx_t *ctx,\n                                         int *out_arg,\n                                         bool *out_has_value) {\n    if (skip_value(ctx)) {\n        return ANJAY_ERR_BAD_REQUEST;\n    }\n\n    if (ctx->state == STATE_EOF) {\n        *out_arg = -1;\n        *out_has_value = false;\n        return ANJAY_EXECUTE_GET_ARG_END;\n    }\n\n    int result = try_reading_next_arg(ctx);\n    if (result < 0) {\n        return ANJAY_ERR_BAD_REQUEST;\n    }\n    *out_arg = ctx->arg;\n    *out_has_value = ctx->arg_has_value;\n    return result;\n}\n\nint anjay_execute_get_next_arg(anjay_execute_ctx_t *ctx,\n                               int *out_arg,\n                               bool *out_has_value) {\n    int result = -1;\n#ifdef ANJAY_WITH_THREAD_SAFETY\n    ANJAY_MUTEX_LOCK(anjay, ctx->anjay_locked);\n#endif // ANJAY_WITH_THREAD_SAFETY\n    result = _anjay_execute_get_next_arg_unlocked(\n            _anjay_execute_get_unlocked(ctx), out_arg, out_has_value);\n#ifdef ANJAY_WITH_THREAD_SAFETY\n    ANJAY_MUTEX_UNLOCK(ctx->anjay_locked);\n#endif // ANJAY_WITH_THREAD_SAFETY\n    return result;\n}\n\nint anjay_execute_get_arg_value(anjay_execute_ctx_t *ctx,\n                                size_t *out_bytes_read,\n                                char *out_buf,\n                                size_t buf_size) {\n    int result = -1;\n#ifdef ANJAY_WITH_THREAD_SAFETY\n    ANJAY_MUTEX_LOCK(anjay, ctx->anjay_locked);\n#endif // ANJAY_WITH_THREAD_SAFETY\n    result = _anjay_execute_get_arg_value_unlocked(_anjay_execute_get_unlocked(\n                                                           ctx),\n                                                   out_bytes_read,\n                                                   out_buf,\n                                                   buf_size);\n#ifdef ANJAY_WITH_THREAD_SAFETY\n    ANJAY_MUTEX_UNLOCK(ctx->anjay_locked);\n#endif // ANJAY_WITH_THREAD_SAFETY\n    return result;\n}\n\nanjay_unlocked_execute_ctx_t *\n_anjay_execute_ctx_create(avs_stream_t *payload_stream) {\n    anjay_unlocked_execute_ctx_t *ret =\n            (anjay_unlocked_execute_ctx_t *) avs_calloc(\n                    1, sizeof(anjay_unlocked_execute_ctx_t));\n    if (ret) {\n        ret->payload_stream = payload_stream;\n        ret->arg = -1;\n        ret->state = STATE_READ_ARGUMENT;\n    }\n    return ret;\n}\n\nvoid _anjay_execute_ctx_destroy(anjay_unlocked_execute_ctx_t **ctx) {\n    if (ctx) {\n        avs_free(*ctx);\n        *ctx = NULL;\n    }\n}\n\nstatic anjay_execute_state_t\nexpect_separator_or_eof(anjay_unlocked_execute_ctx_t *ctx, int ch) {\n    (void) ctx;\n    if (is_arg_separator(ch)) {\n        return STATE_FINISHED_READING_ARGUMENT;\n    } else if (ch == EOF) {\n        return STATE_EOF;\n    }\n    return STATE_ERROR;\n}\n\nstatic anjay_execute_state_t state_read_value(anjay_unlocked_execute_ctx_t *ctx,\n                                              int ch) {\n    if (is_value(ch)) {\n        return STATE_READ_VALUE;\n    } else if (is_value_delimiter(ch)) {\n        return expect_separator_or_eof(ctx, get_next_char(ctx));\n    }\n    return STATE_ERROR;\n}\n\nstatic anjay_execute_state_t expect_value(anjay_unlocked_execute_ctx_t *ctx,\n                                          int ch) {\n    (void) ctx;\n    if (is_value_delimiter(ch)) {\n        return STATE_READ_VALUE;\n    } else {\n        return STATE_ERROR;\n    }\n}\n\nstatic anjay_execute_state_t\nexpect_separator_or_assignment_or_eof(anjay_unlocked_execute_ctx_t *ctx,\n                                      int ch) {\n    /*\n     * This state is being entered only after some argument has been read\n     * successfully, and it determines whether we should expect new argument, or\n     * whether we should proceed with value read.\n     */\n    if (is_arg_separator(ch)) {\n        ctx->arg_has_value = false;\n        return STATE_FINISHED_READING_ARGUMENT;\n    } else if (is_value_assignment(ch)) {\n        ctx->arg_has_value = true;\n        return expect_value(ctx, get_next_char(ctx));\n    } else if (ch == EOF) {\n        return STATE_EOF;\n    }\n    return STATE_ERROR;\n}\n\nstatic anjay_execute_state_t\nstate_read_argument(anjay_unlocked_execute_ctx_t *ctx, int ch) {\n    assert(ch == EOF || (0 <= ch && ch <= UINT8_MAX));\n\n    if (isdigit(ch)) {\n        ctx->arg = ch - '0';\n        return expect_separator_or_assignment_or_eof(ctx, get_next_char(ctx));\n    } else if (ch == EOF) {\n        return STATE_EOF;\n    }\n    return STATE_ERROR;\n}\n"
  },
  {
    "path": "src/core/dm/anjay_dm_execute.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ANJAY_EXECUTE_CORE_H\n#define ANJAY_EXECUTE_CORE_H\n\n#include <anjay_modules/dm/anjay_execute.h>\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\ntypedef enum {\n    STATE_READ_ARGUMENT = 0,\n    STATE_READ_VALUE,\n    STATE_FINISHED_READING_ARGUMENT,\n    STATE_EOF,\n    STATE_ERROR\n} anjay_execute_state_t;\n\nstruct anjay_unlocked_execute_ctx_struct {\n    avs_stream_t *payload_stream;\n    anjay_execute_state_t state;\n    int arg;\n    bool arg_has_value;\n};\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif // ANJAY_EXECUTE_CORE_H\n"
  },
  {
    "path": "src/core/dm/anjay_dm_handlers.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#include <avsystem/coap/code.h>\n\n#include <anjay_modules/anjay_dm_utils.h>\n\n#include \"../anjay_core.h\"\n#include \"../anjay_io_core.h\"\n#include \"../anjay_utils_private.h\"\n\n#ifdef ANJAY_WITH_ATTR_STORAGE\n#    include \"../attr_storage/anjay_attr_storage.h\"\n#endif // ANJAY_WITH_ATTR_STORAGE\n#ifdef ANJAY_WITH_LWM2M_GATEWAY\n#    include \"anjay_modules/anjay_lwm2m_gateway.h\"\n#endif // ANJAY_WITH_LWM2M_GATEWAY\n\nVISIBILITY_SOURCE_BEGIN\n\n#define dm_log(...) _anjay_log(anjay_dm, __VA_ARGS__)\n\n#ifdef ANJAY_WITH_THREAD_SAFETY\n\nstatic int\nunlocking_object_read_default_attrs(anjay_unlocked_t *anjay,\n                                    const anjay_dm_installed_object_t obj_def,\n                                    anjay_ssid_t ssid,\n                                    anjay_dm_oi_attributes_t *out) {\n    assert(obj_def.type == ANJAY_DM_OBJECT_USER_PROVIDED);\n    assert(obj_def.impl.user_provided);\n    assert(*obj_def.impl.user_provided);\n    assert((*obj_def.impl.user_provided)->handlers.object_read_default_attrs);\n    int result = -1;\n    ANJAY_MUTEX_UNLOCK_FOR_CALLBACK(anjay_locked, anjay);\n    result = (*obj_def.impl.user_provided)\n                     ->handlers.object_read_default_attrs(\n                             anjay_locked, obj_def.impl.user_provided, ssid,\n                             out);\n    ANJAY_MUTEX_LOCK_AFTER_CALLBACK(anjay_locked);\n    return result;\n}\n\nstatic int\nunlocking_object_write_default_attrs(anjay_unlocked_t *anjay,\n                                     const anjay_dm_installed_object_t obj_def,\n                                     anjay_ssid_t ssid,\n                                     const anjay_dm_oi_attributes_t *attrs) {\n    assert(obj_def.type == ANJAY_DM_OBJECT_USER_PROVIDED);\n    assert(obj_def.impl.user_provided);\n    assert(*obj_def.impl.user_provided);\n    assert((*obj_def.impl.user_provided)->handlers.object_write_default_attrs);\n    int result = -1;\n    ANJAY_MUTEX_UNLOCK_FOR_CALLBACK(anjay_locked, anjay);\n    result = (*obj_def.impl.user_provided)\n                     ->handlers.object_write_default_attrs(\n                             anjay_locked, obj_def.impl.user_provided, ssid,\n                             attrs);\n    ANJAY_MUTEX_LOCK_AFTER_CALLBACK(anjay_locked);\n    return result;\n}\n\nstatic int unlocking_list_instances(anjay_unlocked_t *anjay,\n                                    const anjay_dm_installed_object_t obj_def,\n                                    anjay_unlocked_dm_list_ctx_t *ctx) {\n    assert(obj_def.type == ANJAY_DM_OBJECT_USER_PROVIDED);\n    assert(obj_def.impl.user_provided);\n    assert(*obj_def.impl.user_provided);\n    assert((*obj_def.impl.user_provided)->handlers.list_instances);\n    int result = -1;\n    ANJAY_MUTEX_UNLOCK_FOR_CALLBACK(anjay_locked, anjay);\n    result = (*obj_def.impl.user_provided)\n                     ->handlers.list_instances(anjay_locked,\n                                               obj_def.impl.user_provided,\n                                               &(anjay_dm_list_ctx_t) {\n                                                   .anjay_locked = anjay_locked,\n                                                   .unlocked_ctx = ctx\n                                               });\n    ANJAY_MUTEX_LOCK_AFTER_CALLBACK(anjay_locked);\n    return result;\n}\n\nstatic int unlocking_instance_reset(anjay_unlocked_t *anjay,\n                                    const anjay_dm_installed_object_t obj_def,\n                                    anjay_iid_t iid) {\n    assert(obj_def.type == ANJAY_DM_OBJECT_USER_PROVIDED);\n    assert(obj_def.impl.user_provided);\n    assert(*obj_def.impl.user_provided);\n    assert((*obj_def.impl.user_provided)->handlers.instance_reset);\n    int result = -1;\n    ANJAY_MUTEX_UNLOCK_FOR_CALLBACK(anjay_locked, anjay);\n    result = (*obj_def.impl.user_provided)\n                     ->handlers.instance_reset(anjay_locked,\n                                               obj_def.impl.user_provided, iid);\n    ANJAY_MUTEX_LOCK_AFTER_CALLBACK(anjay_locked);\n    return result;\n}\n\nstatic int unlocking_instance_create(anjay_unlocked_t *anjay,\n                                     const anjay_dm_installed_object_t obj_def,\n                                     anjay_iid_t iid) {\n    assert(obj_def.type == ANJAY_DM_OBJECT_USER_PROVIDED);\n    assert(obj_def.impl.user_provided);\n    assert(*obj_def.impl.user_provided);\n    assert((*obj_def.impl.user_provided)->handlers.instance_create);\n    int result = -1;\n    ANJAY_MUTEX_UNLOCK_FOR_CALLBACK(anjay_locked, anjay);\n    result =\n            (*obj_def.impl.user_provided)\n                    ->handlers.instance_create(anjay_locked,\n                                               obj_def.impl.user_provided, iid);\n    ANJAY_MUTEX_LOCK_AFTER_CALLBACK(anjay_locked);\n    return result;\n}\n\nstatic int unlocking_instance_remove(anjay_unlocked_t *anjay,\n                                     const anjay_dm_installed_object_t obj_def,\n                                     anjay_iid_t iid) {\n    assert(obj_def.type == ANJAY_DM_OBJECT_USER_PROVIDED);\n    assert(obj_def.impl.user_provided);\n    assert(*obj_def.impl.user_provided);\n    assert((*obj_def.impl.user_provided)->handlers.instance_remove);\n    int result = -1;\n    ANJAY_MUTEX_UNLOCK_FOR_CALLBACK(anjay_locked, anjay);\n    result =\n            (*obj_def.impl.user_provided)\n                    ->handlers.instance_remove(anjay_locked,\n                                               obj_def.impl.user_provided, iid);\n    ANJAY_MUTEX_LOCK_AFTER_CALLBACK(anjay_locked);\n    return result;\n}\n\nstatic int\nunlocking_instance_read_default_attrs(anjay_unlocked_t *anjay,\n                                      const anjay_dm_installed_object_t obj_def,\n                                      anjay_iid_t iid,\n                                      anjay_ssid_t ssid,\n                                      anjay_dm_oi_attributes_t *out) {\n    assert(obj_def.type == ANJAY_DM_OBJECT_USER_PROVIDED);\n    assert(obj_def.impl.user_provided);\n    assert(*obj_def.impl.user_provided);\n    assert((*obj_def.impl.user_provided)->handlers.instance_read_default_attrs);\n    int result = -1;\n    ANJAY_MUTEX_UNLOCK_FOR_CALLBACK(anjay_locked, anjay);\n    result = (*obj_def.impl.user_provided)\n                     ->handlers.instance_read_default_attrs(\n                             anjay_locked, obj_def.impl.user_provided, iid,\n                             ssid, out);\n    ANJAY_MUTEX_LOCK_AFTER_CALLBACK(anjay_locked);\n    return result;\n}\n\nstatic int unlocking_instance_write_default_attrs(\n        anjay_unlocked_t *anjay,\n        const anjay_dm_installed_object_t obj_def,\n        anjay_iid_t iid,\n        anjay_ssid_t ssid,\n        const anjay_dm_oi_attributes_t *attrs) {\n    assert(obj_def.type == ANJAY_DM_OBJECT_USER_PROVIDED);\n    assert(obj_def.impl.user_provided);\n    assert(*obj_def.impl.user_provided);\n    assert((*obj_def.impl.user_provided)\n                   ->handlers.instance_write_default_attrs);\n    int result = -1;\n    ANJAY_MUTEX_UNLOCK_FOR_CALLBACK(anjay_locked, anjay);\n    result = (*obj_def.impl.user_provided)\n                     ->handlers.instance_write_default_attrs(\n                             anjay_locked, obj_def.impl.user_provided, iid,\n                             ssid, attrs);\n    ANJAY_MUTEX_LOCK_AFTER_CALLBACK(anjay_locked);\n    return result;\n}\n\nstatic int\nunlocking_list_resources(anjay_unlocked_t *anjay,\n                         const anjay_dm_installed_object_t obj_def,\n                         anjay_iid_t iid,\n                         anjay_unlocked_dm_resource_list_ctx_t *ctx) {\n    assert(obj_def.type == ANJAY_DM_OBJECT_USER_PROVIDED);\n    assert(obj_def.impl.user_provided);\n    assert(*obj_def.impl.user_provided);\n    assert((*obj_def.impl.user_provided)->handlers.list_resources);\n    int result = -1;\n    ANJAY_MUTEX_UNLOCK_FOR_CALLBACK(anjay_locked, anjay);\n    result = (*obj_def.impl.user_provided)\n                     ->handlers.list_resources(anjay_locked,\n                                               obj_def.impl.user_provided, iid,\n                                               &(anjay_dm_resource_list_ctx_t) {\n                                                   .unlocked_ctx = ctx\n                                               });\n    ANJAY_MUTEX_LOCK_AFTER_CALLBACK(anjay_locked);\n    return result;\n}\n\nstatic int unlocking_resource_read(anjay_unlocked_t *anjay,\n                                   const anjay_dm_installed_object_t obj_def,\n                                   anjay_iid_t iid,\n                                   anjay_rid_t rid,\n                                   anjay_riid_t riid,\n                                   anjay_unlocked_output_ctx_t *ctx) {\n    assert(obj_def.type == ANJAY_DM_OBJECT_USER_PROVIDED);\n    assert(obj_def.impl.user_provided);\n    assert(*obj_def.impl.user_provided);\n    assert((*obj_def.impl.user_provided)->handlers.resource_read);\n    int result = -1;\n    ANJAY_MUTEX_UNLOCK_FOR_CALLBACK(anjay_locked, anjay);\n    result = (*obj_def.impl.user_provided)\n                     ->handlers.resource_read(anjay_locked,\n                                              obj_def.impl.user_provided, iid,\n                                              rid, riid,\n                                              &(anjay_output_ctx_t) {\n                                                  .anjay_locked = anjay_locked,\n                                                  .unlocked_ctx = ctx\n                                              });\n    ANJAY_MUTEX_LOCK_AFTER_CALLBACK(anjay_locked);\n    return result;\n}\n\nstatic int unlocking_resource_write(anjay_unlocked_t *anjay,\n                                    const anjay_dm_installed_object_t obj_def,\n                                    anjay_iid_t iid,\n                                    anjay_rid_t rid,\n                                    anjay_riid_t riid,\n                                    anjay_unlocked_input_ctx_t *ctx) {\n    assert(obj_def.type == ANJAY_DM_OBJECT_USER_PROVIDED);\n    assert(obj_def.impl.user_provided);\n    assert(*obj_def.impl.user_provided);\n    assert((*obj_def.impl.user_provided)->handlers.resource_write);\n    int result = -1;\n    ANJAY_MUTEX_UNLOCK_FOR_CALLBACK(anjay_locked, anjay);\n    result = (*obj_def.impl.user_provided)\n                     ->handlers.resource_write(anjay_locked,\n                                               obj_def.impl.user_provided, iid,\n                                               rid, riid,\n                                               &(anjay_input_ctx_t) {\n                                                   .anjay_locked = anjay_locked,\n                                                   .unlocked_ctx = ctx\n                                               });\n    ANJAY_MUTEX_LOCK_AFTER_CALLBACK(anjay_locked);\n    return result;\n}\n\nstatic int unlocking_resource_execute(anjay_unlocked_t *anjay,\n                                      const anjay_dm_installed_object_t obj_def,\n                                      anjay_iid_t iid,\n                                      anjay_rid_t rid,\n                                      anjay_unlocked_execute_ctx_t *ctx) {\n    assert(obj_def.type == ANJAY_DM_OBJECT_USER_PROVIDED);\n    assert(obj_def.impl.user_provided);\n    assert(*obj_def.impl.user_provided);\n    assert((*obj_def.impl.user_provided)->handlers.resource_execute);\n    int result = -1;\n    ANJAY_MUTEX_UNLOCK_FOR_CALLBACK(anjay_locked, anjay);\n    result = (*obj_def.impl.user_provided)\n                     ->handlers.resource_execute(\n                             anjay_locked, obj_def.impl.user_provided, iid, rid,\n                             &(anjay_execute_ctx_t) {\n                                 .anjay_locked = anjay_locked,\n                                 .unlocked_ctx = ctx\n                             });\n    ANJAY_MUTEX_LOCK_AFTER_CALLBACK(anjay_locked);\n    return result;\n}\n\nstatic int unlocking_resource_reset(anjay_unlocked_t *anjay,\n                                    const anjay_dm_installed_object_t obj_def,\n                                    anjay_iid_t iid,\n                                    anjay_rid_t rid) {\n    assert(obj_def.type == ANJAY_DM_OBJECT_USER_PROVIDED);\n    assert(obj_def.impl.user_provided);\n    assert(*obj_def.impl.user_provided);\n    assert((*obj_def.impl.user_provided)->handlers.resource_reset);\n    int result = -1;\n    ANJAY_MUTEX_UNLOCK_FOR_CALLBACK(anjay_locked, anjay);\n    result = (*obj_def.impl.user_provided)\n                     ->handlers.resource_reset(anjay_locked,\n                                               obj_def.impl.user_provided, iid,\n                                               rid);\n    ANJAY_MUTEX_LOCK_AFTER_CALLBACK(anjay_locked);\n    return result;\n}\n\nstatic int\nunlocking_list_resource_instances(anjay_unlocked_t *anjay,\n                                  const anjay_dm_installed_object_t obj_def,\n                                  anjay_iid_t iid,\n                                  anjay_rid_t rid,\n                                  anjay_unlocked_dm_list_ctx_t *ctx) {\n    assert(obj_def.type == ANJAY_DM_OBJECT_USER_PROVIDED);\n    assert(obj_def.impl.user_provided);\n    assert(*obj_def.impl.user_provided);\n    assert((*obj_def.impl.user_provided)->handlers.list_resource_instances);\n    int result = -1;\n    ANJAY_MUTEX_UNLOCK_FOR_CALLBACK(anjay_locked, anjay);\n    result = (*obj_def.impl.user_provided)\n                     ->handlers.list_resource_instances(\n                             anjay_locked, obj_def.impl.user_provided, iid, rid,\n                             &(anjay_dm_list_ctx_t) {\n                                 .anjay_locked = anjay_locked,\n                                 .unlocked_ctx = ctx\n                             });\n    ANJAY_MUTEX_LOCK_AFTER_CALLBACK(anjay_locked);\n    return result;\n}\n\nstatic int\nunlocking_resource_read_attrs(anjay_unlocked_t *anjay,\n                              const anjay_dm_installed_object_t obj_def,\n                              anjay_iid_t iid,\n                              anjay_rid_t rid,\n                              anjay_ssid_t ssid,\n                              anjay_dm_r_attributes_t *out) {\n    assert(obj_def.type == ANJAY_DM_OBJECT_USER_PROVIDED);\n    assert(obj_def.impl.user_provided);\n    assert(*obj_def.impl.user_provided);\n    assert((*obj_def.impl.user_provided)->handlers.resource_read_attrs);\n    int result = -1;\n    ANJAY_MUTEX_UNLOCK_FOR_CALLBACK(anjay_locked, anjay);\n    result = (*obj_def.impl.user_provided)\n                     ->handlers.resource_read_attrs(anjay_locked,\n                                                    obj_def.impl.user_provided,\n                                                    iid, rid, ssid, out);\n    ANJAY_MUTEX_LOCK_AFTER_CALLBACK(anjay_locked);\n    return result;\n}\n\nstatic int\nunlocking_resource_write_attrs(anjay_unlocked_t *anjay,\n                               const anjay_dm_installed_object_t obj_def,\n                               anjay_iid_t iid,\n                               anjay_rid_t rid,\n                               anjay_ssid_t ssid,\n                               const anjay_dm_r_attributes_t *attrs) {\n    assert(obj_def.type == ANJAY_DM_OBJECT_USER_PROVIDED);\n    assert(obj_def.impl.user_provided);\n    assert(*obj_def.impl.user_provided);\n    assert((*obj_def.impl.user_provided)->handlers.resource_write_attrs);\n    int result = -1;\n    ANJAY_MUTEX_UNLOCK_FOR_CALLBACK(anjay_locked, anjay);\n    result = (*obj_def.impl.user_provided)\n                     ->handlers.resource_write_attrs(anjay_locked,\n                                                     obj_def.impl.user_provided,\n                                                     iid, rid, ssid, attrs);\n    ANJAY_MUTEX_LOCK_AFTER_CALLBACK(anjay_locked);\n    return result;\n}\n\nstatic int\nunlocking_transaction_begin(anjay_unlocked_t *anjay,\n                            const anjay_dm_installed_object_t obj_def) {\n    assert(obj_def.type == ANJAY_DM_OBJECT_USER_PROVIDED);\n    assert(obj_def.impl.user_provided);\n    assert(*obj_def.impl.user_provided);\n    assert((*obj_def.impl.user_provided)->handlers.transaction_begin);\n    int result = -1;\n    ANJAY_MUTEX_UNLOCK_FOR_CALLBACK(anjay_locked, anjay);\n    result = (*obj_def.impl.user_provided)\n                     ->handlers.transaction_begin(anjay_locked,\n                                                  obj_def.impl.user_provided);\n    ANJAY_MUTEX_LOCK_AFTER_CALLBACK(anjay_locked);\n    return result;\n}\n\nstatic int\nunlocking_transaction_validate(anjay_unlocked_t *anjay,\n                               const anjay_dm_installed_object_t obj_def) {\n    assert(obj_def.type == ANJAY_DM_OBJECT_USER_PROVIDED);\n    assert(obj_def.impl.user_provided);\n    assert(*obj_def.impl.user_provided);\n    assert((*obj_def.impl.user_provided)->handlers.transaction_validate);\n    int result = -1;\n    ANJAY_MUTEX_UNLOCK_FOR_CALLBACK(anjay_locked, anjay);\n    result =\n            (*obj_def.impl.user_provided)\n                    ->handlers.transaction_validate(anjay_locked,\n                                                    obj_def.impl.user_provided);\n    ANJAY_MUTEX_LOCK_AFTER_CALLBACK(anjay_locked);\n    return result;\n}\n\nstatic int\nunlocking_transaction_commit(anjay_unlocked_t *anjay,\n                             const anjay_dm_installed_object_t obj_def) {\n    assert(obj_def.type == ANJAY_DM_OBJECT_USER_PROVIDED);\n    assert(obj_def.impl.user_provided);\n    assert(*obj_def.impl.user_provided);\n    assert((*obj_def.impl.user_provided)->handlers.transaction_commit);\n    int result = -1;\n    ANJAY_MUTEX_UNLOCK_FOR_CALLBACK(anjay_locked, anjay);\n    result = (*obj_def.impl.user_provided)\n                     ->handlers.transaction_commit(anjay_locked,\n                                                   obj_def.impl.user_provided);\n    ANJAY_MUTEX_LOCK_AFTER_CALLBACK(anjay_locked);\n    return result;\n}\n\nstatic int\nunlocking_transaction_rollback(anjay_unlocked_t *anjay,\n                               const anjay_dm_installed_object_t obj_def) {\n    assert(obj_def.type == ANJAY_DM_OBJECT_USER_PROVIDED);\n    assert(obj_def.impl.user_provided);\n    assert(*obj_def.impl.user_provided);\n    assert((*obj_def.impl.user_provided)->handlers.transaction_rollback);\n    int result = -1;\n    ANJAY_MUTEX_UNLOCK_FOR_CALLBACK(anjay_locked, anjay);\n    result =\n            (*obj_def.impl.user_provided)\n                    ->handlers.transaction_rollback(anjay_locked,\n                                                    obj_def.impl.user_provided);\n    ANJAY_MUTEX_LOCK_AFTER_CALLBACK(anjay_locked);\n    return result;\n}\n\n#    ifdef ANJAY_WITH_LWM2M11\nstatic int unlocking_resource_instance_read_attrs(\n        anjay_unlocked_t *anjay,\n        const anjay_dm_installed_object_t obj_def,\n        anjay_iid_t iid,\n        anjay_rid_t rid,\n        anjay_riid_t riid,\n        anjay_ssid_t ssid,\n        anjay_dm_r_attributes_t *out) {\n    assert(obj_def.type == ANJAY_DM_OBJECT_USER_PROVIDED);\n    assert(obj_def.impl.user_provided);\n    assert(*obj_def.impl.user_provided);\n    assert((*obj_def.impl.user_provided)\n                   ->handlers.resource_instance_read_attrs);\n    int result = -1;\n    ANJAY_MUTEX_UNLOCK_FOR_CALLBACK(anjay_locked, anjay);\n    result = (*obj_def.impl.user_provided)\n                     ->handlers.resource_instance_read_attrs(\n                             anjay_locked, obj_def.impl.user_provided, iid, rid,\n                             riid, ssid, out);\n    ANJAY_MUTEX_LOCK_AFTER_CALLBACK(anjay_locked);\n    return result;\n}\n\nstatic int unlocking_resource_instance_write_attrs(\n        anjay_unlocked_t *anjay,\n        const anjay_dm_installed_object_t obj_def,\n        anjay_iid_t iid,\n        anjay_rid_t rid,\n        anjay_riid_t riid,\n        anjay_ssid_t ssid,\n        const anjay_dm_r_attributes_t *attrs) {\n    assert(obj_def.type == ANJAY_DM_OBJECT_USER_PROVIDED);\n    assert(obj_def.impl.user_provided);\n    assert(*obj_def.impl.user_provided);\n    assert((*obj_def.impl.user_provided)\n                   ->handlers.resource_instance_write_attrs);\n    int result = -1;\n    ANJAY_MUTEX_UNLOCK_FOR_CALLBACK(anjay_locked, anjay);\n    result = (*obj_def.impl.user_provided)\n                     ->handlers.resource_instance_write_attrs(\n                             anjay_locked, obj_def.impl.user_provided, iid, rid,\n                             riid, ssid, attrs);\n    ANJAY_MUTEX_LOCK_AFTER_CALLBACK(anjay_locked);\n    return result;\n}\n#    endif // ANJAY_WITH_LWM2M11\n\n#    ifdef ANJAY_WITH_LWM2M12\nstatic int\nunlocking_resource_instance_remove(anjay_unlocked_t *anjay,\n                                   const anjay_dm_installed_object_t obj_def,\n                                   anjay_iid_t iid,\n                                   anjay_rid_t rid,\n                                   anjay_riid_t riid) {\n    assert(obj_def.type == ANJAY_DM_OBJECT_USER_PROVIDED);\n    assert(obj_def.impl.user_provided);\n    assert(*obj_def.impl.user_provided);\n    assert((*obj_def.impl.user_provided)->handlers.resource_instance_remove);\n    int result = -1;\n    ANJAY_MUTEX_UNLOCK_FOR_CALLBACK(anjay_locked, anjay);\n    result = (*obj_def.impl.user_provided)\n                     ->handlers.resource_instance_remove(\n                             anjay_locked, obj_def.impl.user_provided, iid, rid,\n                             riid);\n    ANJAY_MUTEX_LOCK_AFTER_CALLBACK(anjay_locked);\n    return result;\n}\n#    endif // ANJAY_WITH_LWM2M12\n\nstatic const anjay_unlocked_dm_handlers_t UNLOCKING_HANDLER_WRAPPERS = {\n    unlocking_object_read_default_attrs,\n    unlocking_object_write_default_attrs,\n    unlocking_list_instances,\n    unlocking_instance_reset,\n    unlocking_instance_create,\n    unlocking_instance_remove,\n    unlocking_instance_read_default_attrs,\n    unlocking_instance_write_default_attrs,\n    unlocking_list_resources,\n    unlocking_resource_read,\n    unlocking_resource_write,\n    unlocking_resource_execute,\n    unlocking_resource_reset,\n    unlocking_list_resource_instances,\n    unlocking_resource_read_attrs,\n    unlocking_resource_write_attrs,\n    unlocking_transaction_begin,\n    unlocking_transaction_validate,\n    unlocking_transaction_commit,\n    unlocking_transaction_rollback,\n#    ifdef ANJAY_WITH_LWM2M11\n    unlocking_resource_instance_read_attrs,\n    unlocking_resource_instance_write_attrs,\n#    endif // ANJAY_WITH_LWM2M11\n#    ifdef ANJAY_WITH_LWM2M12\n    unlocking_resource_instance_remove,\n#    endif // ANJAY_WITH_LWM2M12\n};\n\nstatic bool has_handler_locked(const anjay_dm_handlers_t *def,\n                               anjay_dm_handler_t handler_type) {\n#    define HANDLER_CASE(HandlerName)    \\\n    case ANJAY_DM_HANDLER_##HandlerName: \\\n        return def->HandlerName\n\n    switch (handler_type) {\n        HANDLER_CASE(object_read_default_attrs);\n        HANDLER_CASE(object_write_default_attrs);\n        HANDLER_CASE(list_instances);\n        HANDLER_CASE(instance_reset);\n        HANDLER_CASE(instance_create);\n        HANDLER_CASE(instance_remove);\n        HANDLER_CASE(instance_read_default_attrs);\n        HANDLER_CASE(instance_write_default_attrs);\n        HANDLER_CASE(list_resources);\n        HANDLER_CASE(resource_read);\n        HANDLER_CASE(resource_write);\n        HANDLER_CASE(resource_execute);\n        HANDLER_CASE(resource_reset);\n        HANDLER_CASE(list_resource_instances);\n        HANDLER_CASE(resource_read_attrs);\n        HANDLER_CASE(resource_write_attrs);\n        HANDLER_CASE(transaction_begin);\n        HANDLER_CASE(transaction_validate);\n        HANDLER_CASE(transaction_commit);\n        HANDLER_CASE(transaction_rollback);\n#    ifdef ANJAY_WITH_LWM2M11\n        HANDLER_CASE(resource_instance_read_attrs);\n        HANDLER_CASE(resource_instance_write_attrs);\n#    endif // ANJAY_WITH_LWM2M11\n#    ifdef ANJAY_WITH_LWM2M12\n        HANDLER_CASE(resource_instance_remove);\n#    endif // ANJAY_WITH_LWM2M12\n    }\n#    undef HANDLER_CASE\n    AVS_UNREACHABLE(\"unknown handler type passed\");\n    return false;\n}\n\n#endif // ANJAY_WITH_THREAD_SAFETY\n\nstatic bool has_handler_unlocked(const anjay_unlocked_dm_handlers_t *def,\n                                 anjay_dm_handler_t handler_type) {\n#define HANDLER_CASE(HandlerName)        \\\n    case ANJAY_DM_HANDLER_##HandlerName: \\\n        return def->HandlerName\n\n    switch (handler_type) {\n        HANDLER_CASE(object_read_default_attrs);\n        HANDLER_CASE(object_write_default_attrs);\n        HANDLER_CASE(list_instances);\n        HANDLER_CASE(instance_reset);\n        HANDLER_CASE(instance_create);\n        HANDLER_CASE(instance_remove);\n        HANDLER_CASE(instance_read_default_attrs);\n        HANDLER_CASE(instance_write_default_attrs);\n        HANDLER_CASE(list_resources);\n        HANDLER_CASE(resource_read);\n        HANDLER_CASE(resource_write);\n        HANDLER_CASE(resource_execute);\n        HANDLER_CASE(resource_reset);\n        HANDLER_CASE(list_resource_instances);\n        HANDLER_CASE(resource_read_attrs);\n        HANDLER_CASE(resource_write_attrs);\n        HANDLER_CASE(transaction_begin);\n        HANDLER_CASE(transaction_validate);\n        HANDLER_CASE(transaction_commit);\n        HANDLER_CASE(transaction_rollback);\n#ifdef ANJAY_WITH_LWM2M11\n        HANDLER_CASE(resource_instance_read_attrs);\n        HANDLER_CASE(resource_instance_write_attrs);\n#endif // ANJAY_WITH_LWM2M11\n#ifdef ANJAY_WITH_LWM2M12\n        HANDLER_CASE(resource_instance_remove);\n#endif // ANJAY_WITH_LWM2M12\n    }\n#undef HANDLER_CASE\n    AVS_UNREACHABLE(\"unknown handler type passed\");\n    return false;\n}\n\nstatic const anjay_unlocked_dm_handlers_t *\nget_handler(const anjay_dm_installed_object_t *obj_ptr,\n            anjay_dm_handler_t handler_type) {\n#ifdef ANJAY_WITH_ATTR_STORAGE\n    switch (handler_type) {\n    case ANJAY_DM_HANDLER_object_read_default_attrs:\n    case ANJAY_DM_HANDLER_object_write_default_attrs:\n        if (!_anjay_dm_implements_any_object_default_attrs_handlers(obj_ptr)) {\n            return &_ANJAY_ATTR_STORAGE_HANDLERS;\n        }\n        break;\n\n    case ANJAY_DM_HANDLER_instance_read_default_attrs:\n    case ANJAY_DM_HANDLER_instance_write_default_attrs:\n        if (!_anjay_dm_implements_any_instance_default_attrs_handlers(\n                    obj_ptr)) {\n            return &_ANJAY_ATTR_STORAGE_HANDLERS;\n        }\n        break;\n\n    case ANJAY_DM_HANDLER_resource_read_attrs:\n    case ANJAY_DM_HANDLER_resource_write_attrs:\n        if (!_anjay_dm_implements_any_resource_attrs_handlers(obj_ptr)) {\n            return &_ANJAY_ATTR_STORAGE_HANDLERS;\n        }\n        break;\n\n#    ifdef ANJAY_WITH_LWM2M11\n    case ANJAY_DM_HANDLER_resource_instance_read_attrs:\n    case ANJAY_DM_HANDLER_resource_instance_write_attrs:\n        if (!_anjay_dm_implements_any_resource_instance_attrs_handlers(\n                    obj_ptr)) {\n            return &_ANJAY_ATTR_STORAGE_HANDLERS;\n        }\n        break;\n#    endif // ANJAY_WITH_LWM2M11\n\n    default:\n        break;\n    }\n#endif // ANJAY_WITH_ATTR_STORAGE\n#ifdef ANJAY_WITH_THREAD_SAFETY\n    switch (obj_ptr->type) {\n    case ANJAY_DM_OBJECT_USER_PROVIDED:\n        assert(obj_ptr->impl.user_provided);\n        assert(*obj_ptr->impl.user_provided);\n        if (has_handler_locked(&(*obj_ptr->impl.user_provided)->handlers,\n                               handler_type)) {\n            return &UNLOCKING_HANDLER_WRAPPERS;\n        }\n        return NULL;\n\n    case ANJAY_DM_OBJECT_UNLOCKED:\n        assert(obj_ptr->impl.unlocked);\n        assert(*obj_ptr->impl.unlocked);\n        if (has_handler_unlocked(&(*obj_ptr->impl.unlocked)->handlers,\n                                 handler_type)) {\n            return &(*obj_ptr->impl.unlocked)->handlers;\n        }\n        return NULL;\n    }\n    AVS_UNREACHABLE(\"Invalid value of anjay_dm_installed_object_type_t\");\n    return NULL;\n#else  // ANJAY_WITH_THREAD_SAFETY\n    assert(*obj_ptr);\n    assert(**obj_ptr);\n    if (has_handler_unlocked(&(**obj_ptr)->handlers, handler_type)) {\n        return &(**obj_ptr)->handlers;\n    }\n    return NULL;\n#endif // ANJAY_WITH_THREAD_SAFETY\n}\n\nbool _anjay_dm_handler_implemented(const anjay_dm_installed_object_t *obj_ptr,\n                                   anjay_dm_handler_t handler_type) {\n#ifdef ANJAY_WITH_THREAD_SAFETY\n    switch (obj_ptr->type) {\n    case ANJAY_DM_OBJECT_USER_PROVIDED:\n        assert(obj_ptr->impl.user_provided);\n        assert(*obj_ptr->impl.user_provided);\n        return has_handler_locked(&(*obj_ptr->impl.user_provided)->handlers,\n                                  handler_type);\n\n    case ANJAY_DM_OBJECT_UNLOCKED:\n        assert(obj_ptr->impl.unlocked);\n        assert(*obj_ptr->impl.unlocked);\n        return has_handler_unlocked(&(*obj_ptr->impl.unlocked)->handlers,\n                                    handler_type);\n    }\n    AVS_UNREACHABLE(\"Invalid value of anjay_dm_installed_object_type_t\");\n    return false;\n#else  // ANJAY_WITH_THREAD_SAFETY\n    assert(*obj_ptr);\n    assert(**obj_ptr);\n    return has_handler_unlocked(&(**obj_ptr)->handlers, handler_type);\n#endif // ANJAY_WITH_THREAD_SAFETY\n}\n\n#define CHECKED_TAIL_CALL_HANDLER(ObjPtr, HandlerName, ...)                   \\\n    do {                                                                      \\\n        const anjay_unlocked_dm_handlers_t *handler =                         \\\n                get_handler((ObjPtr), ANJAY_DM_HANDLER_##HandlerName);        \\\n        if (handler) {                                                        \\\n            int AVS_CONCAT(result, __LINE__) =                                \\\n                    handler->HandlerName(__VA_ARGS__);                        \\\n            if (AVS_CONCAT(result, __LINE__)) {                               \\\n                dm_log(DEBUG, #HandlerName _(\" failed with code \") \"%d (%s)\", \\\n                       AVS_CONCAT(result, __LINE__),                          \\\n                       AVS_COAP_CODE_STRING(_anjay_make_error_response_code(  \\\n                               AVS_CONCAT(result, __LINE__))));               \\\n            }                                                                 \\\n            return AVS_CONCAT(result, __LINE__);                              \\\n        } else {                                                              \\\n            dm_log(DEBUG,                                                     \\\n                   #HandlerName _(\" handler not set for object \") \"/%u\",      \\\n                   _anjay_dm_installed_object_oid(ObjPtr));                   \\\n            return ANJAY_ERR_METHOD_NOT_ALLOWED;                              \\\n        }                                                                     \\\n    } while (0)\n\nint _anjay_dm_call_object_read_default_attrs(\n        anjay_unlocked_t *anjay,\n        const anjay_dm_installed_object_t *obj_ptr,\n        anjay_ssid_t ssid,\n        anjay_dm_oi_attributes_t *out) {\n    dm_log(TRACE, _(\"object_read_default_attrs \") DM_LOG_PREFIX \"/%u\",\n           DM_LOG_PREFIX_OBJ_ARG(obj_ptr)\n                   _anjay_dm_installed_object_oid(obj_ptr));\n    CHECKED_TAIL_CALL_HANDLER(obj_ptr, object_read_default_attrs, anjay,\n                              *obj_ptr, ssid, out);\n}\n\nint _anjay_dm_call_object_write_default_attrs(\n        anjay_unlocked_t *anjay,\n        const anjay_dm_installed_object_t *obj_ptr,\n        anjay_ssid_t ssid,\n        const anjay_dm_oi_attributes_t *attrs) {\n    dm_log(TRACE, _(\"object_write_default_attrs \") DM_LOG_PREFIX \"/%u\",\n           DM_LOG_PREFIX_OBJ_ARG(obj_ptr)\n                   _anjay_dm_installed_object_oid(obj_ptr));\n    CHECKED_TAIL_CALL_HANDLER(obj_ptr, object_write_default_attrs, anjay,\n                              *obj_ptr, ssid, attrs);\n}\n\nint _anjay_dm_call_list_instances(anjay_unlocked_t *anjay,\n                                  const anjay_dm_installed_object_t *obj_ptr,\n                                  anjay_unlocked_dm_list_ctx_t *ctx) {\n    dm_log(TRACE, _(\"list_instances \") DM_LOG_PREFIX \"/%u\",\n           DM_LOG_PREFIX_OBJ_ARG(obj_ptr)\n                   _anjay_dm_installed_object_oid(obj_ptr));\n    CHECKED_TAIL_CALL_HANDLER(obj_ptr, list_instances, anjay, *obj_ptr, ctx);\n}\n\nint _anjay_dm_call_instance_reset(anjay_unlocked_t *anjay,\n                                  const anjay_dm_installed_object_t *obj_ptr,\n                                  anjay_iid_t iid) {\n    dm_log(TRACE, _(\"instance_reset \") DM_LOG_PREFIX \"/%u/%u\",\n           DM_LOG_PREFIX_OBJ_ARG(obj_ptr)\n                   _anjay_dm_installed_object_oid(obj_ptr),\n           iid);\n    int result = _anjay_dm_transaction_include_object(anjay, obj_ptr);\n    if (result) {\n        return result;\n    }\n    CHECKED_TAIL_CALL_HANDLER(obj_ptr, instance_reset, anjay, *obj_ptr, iid);\n}\n\nint _anjay_dm_call_instance_create(anjay_unlocked_t *anjay,\n                                   const anjay_dm_installed_object_t *obj_ptr,\n                                   anjay_iid_t iid) {\n    dm_log(TRACE, _(\"instance_create \") DM_LOG_PREFIX \"/%u/%u\",\n           DM_LOG_PREFIX_OBJ_ARG(obj_ptr)\n                   _anjay_dm_installed_object_oid(obj_ptr),\n           iid);\n    int result = _anjay_dm_transaction_include_object(anjay, obj_ptr);\n    if (result) {\n        return result;\n    }\n    CHECKED_TAIL_CALL_HANDLER(obj_ptr, instance_create, anjay, *obj_ptr, iid);\n}\n\nint _anjay_dm_call_instance_remove(anjay_unlocked_t *anjay,\n                                   const anjay_dm_installed_object_t *obj_ptr,\n                                   anjay_iid_t iid) {\n    dm_log(TRACE, _(\"instance_remove \") DM_LOG_PREFIX \"/%u/%u\",\n           DM_LOG_PREFIX_OBJ_ARG(obj_ptr)\n                   _anjay_dm_installed_object_oid(obj_ptr),\n           iid);\n    int result = _anjay_dm_transaction_include_object(anjay, obj_ptr);\n    if (result) {\n        return result;\n    }\n    CHECKED_TAIL_CALL_HANDLER(obj_ptr, instance_remove, anjay, *obj_ptr, iid);\n}\n\nint _anjay_dm_call_instance_read_default_attrs(\n        anjay_unlocked_t *anjay,\n        const anjay_dm_installed_object_t *obj_ptr,\n        anjay_iid_t iid,\n        anjay_ssid_t ssid,\n        anjay_dm_oi_attributes_t *out) {\n    dm_log(TRACE, _(\"instance_read_default_attrs \") DM_LOG_PREFIX \"/%u/%u\",\n           DM_LOG_PREFIX_OBJ_ARG(obj_ptr)\n                   _anjay_dm_installed_object_oid(obj_ptr),\n           iid);\n    CHECKED_TAIL_CALL_HANDLER(obj_ptr, instance_read_default_attrs, anjay,\n                              *obj_ptr, iid, ssid, out);\n}\n\nint _anjay_dm_call_instance_write_default_attrs(\n        anjay_unlocked_t *anjay,\n        const anjay_dm_installed_object_t *obj_ptr,\n        anjay_iid_t iid,\n        anjay_ssid_t ssid,\n        const anjay_dm_oi_attributes_t *attrs) {\n    dm_log(TRACE, _(\"instance_write_default_attrs \") DM_LOG_PREFIX \"/%u/%u\",\n           DM_LOG_PREFIX_OBJ_ARG(obj_ptr)\n                   _anjay_dm_installed_object_oid(obj_ptr),\n           iid);\n    CHECKED_TAIL_CALL_HANDLER(obj_ptr, instance_write_default_attrs, anjay,\n                              *obj_ptr, iid, ssid, attrs);\n}\n\nint _anjay_dm_call_list_resources(anjay_unlocked_t *anjay,\n                                  const anjay_dm_installed_object_t *obj_ptr,\n                                  anjay_iid_t iid,\n                                  anjay_unlocked_dm_resource_list_ctx_t *ctx) {\n    dm_log(TRACE, _(\"list_resources \") DM_LOG_PREFIX \"/%u/%u\",\n           DM_LOG_PREFIX_OBJ_ARG(obj_ptr)\n                   _anjay_dm_installed_object_oid(obj_ptr),\n           iid);\n    CHECKED_TAIL_CALL_HANDLER(obj_ptr, list_resources, anjay, *obj_ptr, iid,\n                              ctx);\n}\n\nint _anjay_dm_call_resource_read(anjay_unlocked_t *anjay,\n                                 const anjay_dm_installed_object_t *obj_ptr,\n                                 anjay_iid_t iid,\n                                 anjay_rid_t rid,\n                                 anjay_riid_t riid,\n                                 anjay_unlocked_output_ctx_t *ctx) {\n    anjay_uri_path_t path =\n            MAKE_RESOURCE_INSTANCE_PATH(_anjay_dm_installed_object_oid(obj_ptr),\n                                        iid, rid, riid);\n    (void) path;\n#ifdef ANJAY_WITH_LWM2M_GATEWAY\n    if (obj_ptr->prefix) {\n        strncpy(path.prefix, obj_ptr->prefix, sizeof(path.prefix));\n    }\n#endif // ANJAY_WITH_LWM2M_GATEWAY\n    dm_log(LAZY_TRACE, _(\"resource_read \") \"%s\", ANJAY_DEBUG_MAKE_PATH(&path));\n    CHECKED_TAIL_CALL_HANDLER(obj_ptr, resource_read, anjay, *obj_ptr, iid, rid,\n                              riid, ctx);\n}\n\nint _anjay_dm_call_resource_write(anjay_unlocked_t *anjay,\n                                  const anjay_dm_installed_object_t *obj_ptr,\n                                  anjay_iid_t iid,\n                                  anjay_rid_t rid,\n                                  anjay_riid_t riid,\n                                  anjay_unlocked_input_ctx_t *ctx) {\n    anjay_uri_path_t path =\n            MAKE_RESOURCE_INSTANCE_PATH(_anjay_dm_installed_object_oid(obj_ptr),\n                                        iid, rid, riid);\n    (void) path;\n#ifdef ANJAY_WITH_LWM2M_GATEWAY\n    if (obj_ptr->prefix) {\n        strncpy(path.prefix, obj_ptr->prefix, sizeof(path.prefix));\n    }\n#endif // ANJAY_WITH_LWM2M_GATEWAY\n    dm_log(TRACE, _(\"resource_write \") \"%s\", ANJAY_DEBUG_MAKE_PATH(&path));\n    int result = _anjay_dm_transaction_include_object(anjay, obj_ptr);\n    if (result) {\n        return result;\n    }\n    CHECKED_TAIL_CALL_HANDLER(obj_ptr, resource_write, anjay, *obj_ptr, iid,\n                              rid, riid, ctx);\n}\n\nint _anjay_dm_call_resource_execute(anjay_unlocked_t *anjay,\n                                    const anjay_dm_installed_object_t *obj_ptr,\n                                    anjay_iid_t iid,\n                                    anjay_rid_t rid,\n                                    anjay_unlocked_execute_ctx_t *execute_ctx) {\n    dm_log(TRACE, _(\"resource_execute \") DM_LOG_PREFIX \"/%u/%u/%u\",\n           DM_LOG_PREFIX_OBJ_ARG(obj_ptr)\n                   _anjay_dm_installed_object_oid(obj_ptr),\n           iid, rid);\n    CHECKED_TAIL_CALL_HANDLER(obj_ptr, resource_execute, anjay, *obj_ptr, iid,\n                              rid, execute_ctx);\n}\n\nint _anjay_dm_call_resource_reset(anjay_unlocked_t *anjay,\n                                  const anjay_dm_installed_object_t *obj_ptr,\n                                  anjay_iid_t iid,\n                                  anjay_rid_t rid) {\n    dm_log(TRACE, _(\"resource_reset \") DM_LOG_PREFIX \"/%u/%u/%u\",\n           DM_LOG_PREFIX_OBJ_ARG(obj_ptr)\n                   _anjay_dm_installed_object_oid(obj_ptr),\n           iid, rid);\n    int result = _anjay_dm_transaction_include_object(anjay, obj_ptr);\n    if (result) {\n        return result;\n    }\n    CHECKED_TAIL_CALL_HANDLER(obj_ptr, resource_reset, anjay, *obj_ptr, iid,\n                              rid);\n}\n\nint _anjay_dm_call_list_resource_instances(\n        anjay_unlocked_t *anjay,\n        const anjay_dm_installed_object_t *obj_ptr,\n        anjay_iid_t iid,\n        anjay_rid_t rid,\n        anjay_unlocked_dm_list_ctx_t *ctx) {\n    dm_log(TRACE, _(\"list_resource_instances \") DM_LOG_PREFIX \"/%u/%u/%u\",\n           DM_LOG_PREFIX_OBJ_ARG(obj_ptr)\n                   _anjay_dm_installed_object_oid(obj_ptr),\n           iid, rid);\n    if (!_anjay_dm_handler_implemented(\n                obj_ptr, ANJAY_DM_HANDLER_list_resource_instances)) {\n        dm_log(TRACE,\n               _(\"list_resource_instances handler not set for object \") \"/%u\",\n               _anjay_dm_installed_object_oid(obj_ptr));\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n    CHECKED_TAIL_CALL_HANDLER(obj_ptr, list_resource_instances, anjay, *obj_ptr,\n                              iid, rid, ctx);\n}\n\nint _anjay_dm_call_resource_read_attrs(\n        anjay_unlocked_t *anjay,\n        const anjay_dm_installed_object_t *obj_ptr,\n        anjay_iid_t iid,\n        anjay_rid_t rid,\n        anjay_ssid_t ssid,\n        anjay_dm_r_attributes_t *out) {\n    dm_log(TRACE, _(\"resource_read_attrs \") DM_LOG_PREFIX \"/%u/%u/%u\",\n           DM_LOG_PREFIX_OBJ_ARG(obj_ptr)\n                   _anjay_dm_installed_object_oid(obj_ptr),\n           iid, rid);\n    CHECKED_TAIL_CALL_HANDLER(obj_ptr, resource_read_attrs, anjay, *obj_ptr,\n                              iid, rid, ssid, out);\n}\n\nint _anjay_dm_call_resource_write_attrs(\n        anjay_unlocked_t *anjay,\n        const anjay_dm_installed_object_t *obj_ptr,\n        anjay_iid_t iid,\n        anjay_rid_t rid,\n        anjay_ssid_t ssid,\n        const anjay_dm_r_attributes_t *attrs) {\n    dm_log(TRACE, _(\"resource_write_attrs \") DM_LOG_PREFIX \"/%u/%u/%u\",\n           DM_LOG_PREFIX_OBJ_ARG(obj_ptr)\n                   _anjay_dm_installed_object_oid(obj_ptr),\n           iid, rid);\n    CHECKED_TAIL_CALL_HANDLER(obj_ptr, resource_write_attrs, anjay, *obj_ptr,\n                              iid, rid, ssid, attrs);\n}\n\n#ifdef ANJAY_WITH_LWM2M11\nint _anjay_dm_call_resource_instance_read_attrs(\n        anjay_unlocked_t *anjay,\n        const anjay_dm_installed_object_t *obj_ptr,\n        anjay_iid_t iid,\n        anjay_rid_t rid,\n        anjay_riid_t riid,\n        anjay_ssid_t ssid,\n        anjay_dm_r_attributes_t *out) {\n    dm_log(TRACE,\n           _(\"resource_instance_read_attrs \") DM_LOG_PREFIX \"/%u/%u/%u/%u\",\n           DM_LOG_PREFIX_OBJ_ARG(obj_ptr)\n                   _anjay_dm_installed_object_oid(obj_ptr),\n           iid, rid, riid);\n    CHECKED_TAIL_CALL_HANDLER(obj_ptr, resource_instance_read_attrs, anjay,\n                              *obj_ptr, iid, rid, riid, ssid, out);\n}\n\nint _anjay_dm_call_resource_instance_write_attrs(\n        anjay_unlocked_t *anjay,\n        const anjay_dm_installed_object_t *obj_ptr,\n        anjay_iid_t iid,\n        anjay_rid_t rid,\n        anjay_riid_t riid,\n        anjay_ssid_t ssid,\n        const anjay_dm_r_attributes_t *attrs) {\n    dm_log(TRACE,\n           _(\"resource_instance_write_attrs \") DM_LOG_PREFIX \"/%u/%u/%u/%u\",\n           DM_LOG_PREFIX_OBJ_ARG(obj_ptr)\n                   _anjay_dm_installed_object_oid(obj_ptr),\n           iid, rid, riid);\n    CHECKED_TAIL_CALL_HANDLER(obj_ptr, resource_instance_write_attrs, anjay,\n                              *obj_ptr, iid, rid, riid, ssid, attrs);\n}\n\n#    ifdef ANJAY_WITH_LWM2M12\nint _anjay_dm_call_resource_instance_remove(\n        anjay_unlocked_t *anjay,\n        const anjay_dm_installed_object_t *obj_ptr,\n        anjay_iid_t iid,\n        anjay_rid_t rid,\n        anjay_riid_t riid) {\n    dm_log(TRACE, _(\"resource_instance_remove\") DM_LOG_PREFIX \"/%u/%u/%u/%u\",\n           DM_LOG_PREFIX_OBJ_ARG(obj_ptr)\n                   _anjay_dm_installed_object_oid(obj_ptr),\n           iid, rid, riid);\n    int result = _anjay_dm_transaction_include_object(anjay, obj_ptr);\n    if (result) {\n        return result;\n    }\n    CHECKED_TAIL_CALL_HANDLER(obj_ptr, resource_instance_remove, anjay,\n                              *obj_ptr, iid, rid, riid);\n}\n#    endif // ANJAY_WITH_LWM2M12\n#endif     // ANJAY_WITH_LWM2M11\n\nint _anjay_dm_call_transaction_begin(\n        anjay_unlocked_t *anjay, const anjay_dm_installed_object_t *obj_ptr) {\n    dm_log(TRACE, _(\"begin_object_transaction \") DM_LOG_PREFIX \"/%u\",\n           DM_LOG_PREFIX_OBJ_ARG(obj_ptr)\n                   _anjay_dm_installed_object_oid(obj_ptr));\n    CHECKED_TAIL_CALL_HANDLER(obj_ptr, transaction_begin, anjay, *obj_ptr);\n}\n\nint _anjay_dm_call_transaction_validate(\n        anjay_unlocked_t *anjay, const anjay_dm_installed_object_t *obj_ptr) {\n    dm_log(TRACE, _(\"validate_object \") DM_LOG_PREFIX \"/%u\",\n           DM_LOG_PREFIX_OBJ_ARG(obj_ptr)\n                   _anjay_dm_installed_object_oid(obj_ptr));\n    CHECKED_TAIL_CALL_HANDLER(obj_ptr, transaction_validate, anjay, *obj_ptr);\n}\n\nint _anjay_dm_call_transaction_commit(\n        anjay_unlocked_t *anjay, const anjay_dm_installed_object_t *obj_ptr) {\n    dm_log(TRACE, _(\"commit_object \") DM_LOG_PREFIX \"/%u\",\n           DM_LOG_PREFIX_OBJ_ARG(obj_ptr)\n                   _anjay_dm_installed_object_oid(obj_ptr));\n    CHECKED_TAIL_CALL_HANDLER(obj_ptr, transaction_commit, anjay, *obj_ptr);\n}\n\nint _anjay_dm_call_transaction_rollback(\n        anjay_unlocked_t *anjay, const anjay_dm_installed_object_t *obj_ptr) {\n    dm_log(TRACE, _(\"rollback_object \") DM_LOG_PREFIX \"/%u\",\n           DM_LOG_PREFIX_OBJ_ARG(obj_ptr)\n                   _anjay_dm_installed_object_oid(obj_ptr));\n    CHECKED_TAIL_CALL_HANDLER(obj_ptr, transaction_rollback, anjay, *obj_ptr);\n}\n\n#define MAX_SANE_TRANSACTION_DEPTH 64\n\navs_error_t _anjay_dm_transaction_begin(anjay_unlocked_t *anjay) {\n    dm_log(TRACE, _(\"transaction_begin\"));\n    avs_error_t err = AVS_OK;\n#ifdef ANJAY_WITH_ATTR_STORAGE\n    err = _anjay_attr_storage_transaction_begin(&anjay->attr_storage);\n#endif // ANJAY_WITH_ATTR_STORAGE\n    if (avs_is_ok(err)) {\n        ++anjay->transaction_state.depth;\n    }\n    assert(anjay->transaction_state.depth < MAX_SANE_TRANSACTION_DEPTH);\n    return err;\n}\n\nint _anjay_dm_transaction_include_object(\n        anjay_unlocked_t *anjay, const anjay_dm_installed_object_t *obj_ptr) {\n    dm_log(TRACE, _(\"transaction_include_object \") DM_LOG_PREFIX \"/%u\",\n           DM_LOG_PREFIX_OBJ_ARG(obj_ptr)\n                   _anjay_dm_installed_object_oid(obj_ptr));\n    assert(anjay->transaction_state.depth > 0);\n    AVS_LIST(const anjay_dm_installed_object_t *) *it;\n    AVS_LIST_FOREACH_PTR(it, &anjay->transaction_state.objs_in_transaction) {\n        if (**it >= obj_ptr) {\n            break;\n        }\n    }\n    if (!*it || **it != obj_ptr) {\n        AVS_LIST(const anjay_dm_installed_object_t *) new_entry =\n                AVS_LIST_NEW_ELEMENT(const anjay_dm_installed_object_t *);\n        if (!new_entry) {\n            _anjay_log_oom();\n            return -1;\n        }\n        *new_entry = obj_ptr;\n        AVS_LIST_INSERT(it, new_entry);\n#if defined(ANJAY_WITH_LWM2M_GATEWAY) && defined(ANJAY_WITH_ATTR_STORAGE)\n        if (obj_ptr->prefix) {\n            anjay_attr_storage_t *as = NULL;\n            if (_anjay_lwm2m_gateway_prefix_to_as(anjay, obj_ptr->prefix, &as)\n                    || avs_is_err(_anjay_attr_storage_transaction_begin(as))) {\n                return -1;\n            }\n        }\n#endif // defined(ANJAY_WITH_LWM2M_GATEWAY) && defined(ANJAY_WITH_ATTR_STORAGE)\n        int result = _anjay_dm_call_transaction_begin(anjay, obj_ptr);\n        if (result) {\n            // transaction_begin may have added new entries\n            while (*it != new_entry) {\n                AVS_LIST_ADVANCE_PTR(&it);\n            }\n            AVS_LIST_DELETE(it);\n            return result;\n        }\n    }\n    return 0;\n}\n\nstatic int commit_or_rollback_object(anjay_unlocked_t *anjay,\n                                     const anjay_dm_installed_object_t *obj,\n                                     int predicate) {\n    int result;\n    if (predicate) {\n        if ((result = _anjay_dm_call_transaction_rollback(anjay, obj))) {\n            dm_log(ERROR,\n                   _(\"cannot rollback transaction on \") DM_LOG_PREFIX\n                   \"/%u\" _(\", object may be left in undefined state\"),\n                   DM_LOG_PREFIX_OBJ_ARG(obj)\n                           _anjay_dm_installed_object_oid(obj));\n            return result;\n        }\n    } else if ((result = _anjay_dm_call_transaction_commit(anjay, obj))) {\n        dm_log(ERROR, _(\"cannot commit transaction on \") DM_LOG_PREFIX \"/%u\",\n               DM_LOG_PREFIX_OBJ_ARG(obj) _anjay_dm_installed_object_oid(obj));\n        predicate = result;\n    }\n    return predicate;\n}\n\nint _anjay_dm_transaction_validate(anjay_unlocked_t *anjay) {\n    dm_log(TRACE, _(\"transaction_validate\"));\n    AVS_LIST(const anjay_dm_installed_object_t *) obj;\n    AVS_LIST_FOREACH(obj, anjay->transaction_state.objs_in_transaction) {\n        dm_log(TRACE, _(\"validate_object \") DM_LOG_PREFIX \"/%u\",\n               DM_LOG_PREFIX_OBJ_ARG(*obj)\n                       _anjay_dm_installed_object_oid(*obj));\n        int result = _anjay_dm_call_transaction_validate(anjay, *obj);\n        if (result) {\n            dm_log(ERROR, _(\"Validation failed for \") DM_LOG_PREFIX \"/%u\",\n                   DM_LOG_PREFIX_OBJ_ARG(*obj)\n                           _anjay_dm_installed_object_oid(*obj));\n            return result;\n        }\n    }\n    return 0;\n}\n\nint _anjay_dm_transaction_finish_without_validation(anjay_unlocked_t *anjay,\n                                                    int result) {\n    dm_log(TRACE, _(\"transaction_finish\"));\n    assert(anjay->transaction_state.depth > 0);\n    if (--anjay->transaction_state.depth != 0) {\n        return result;\n    }\n\n#if defined(ANJAY_WITH_LWM2M_GATEWAY) && defined(ANJAY_WITH_ATTR_STORAGE)\n    AVS_LIST(anjay_attr_storage_t *) affected_end_devs_attr_storages = NULL;\n#endif // defined(ANJAY_WITH_LWM2M_GATEWAY) && defined(ANJAY_WITH_ATTR_STORAGE)\n\n    int final_result = result;\n    AVS_LIST_CLEAR(&anjay->transaction_state.objs_in_transaction) {\n        int commit_result = commit_or_rollback_object(\n                anjay, *anjay->transaction_state.objs_in_transaction, result);\n        if (!final_result && commit_result) {\n            final_result = commit_result;\n        }\n#if defined(ANJAY_WITH_LWM2M_GATEWAY) && defined(ANJAY_WITH_ATTR_STORAGE)\n        // add affected Attr Storages to a list, if they are not already there\n        const char *prefix =\n                (*anjay->transaction_state.objs_in_transaction)->prefix;\n        if (prefix) {\n            anjay_attr_storage_t *as = NULL;\n            if (_anjay_lwm2m_gateway_prefix_to_as(anjay, prefix, &as)) {\n                // object or end device deleted during transaction\n                continue;\n            }\n            bool found = false;\n            AVS_LIST(anjay_attr_storage_t *) it;\n            AVS_LIST_FOREACH(it, affected_end_devs_attr_storages) {\n                if (*it == as) {\n                    found = true;\n                    break;\n                }\n            }\n            if (!found) {\n                AVS_LIST(anjay_attr_storage_t *) attr_storage =\n                        AVS_LIST_NEW_ELEMENT(anjay_attr_storage_t *);\n                if (!attr_storage) {\n                    _anjay_log_oom();\n                    AVS_LIST_CLEAR(&affected_end_devs_attr_storages);\n                    return -1;\n                }\n                *attr_storage = as;\n                AVS_LIST_APPEND(&affected_end_devs_attr_storages, attr_storage);\n            }\n        }\n#endif // defined(ANJAY_WITH_LWM2M_GATEWAY) && defined(ANJAY_WITH_ATTR_STORAGE)\n    }\n#ifdef ANJAY_WITH_ATTR_STORAGE\n    if (!final_result) {\n        _anjay_attr_storage_transaction_commit(&anjay->attr_storage);\n    } else {\n        (void) _anjay_attr_storage_transaction_rollback(anjay,\n                                                        &anjay->attr_storage);\n    }\n#    ifdef ANJAY_WITH_LWM2M_GATEWAY\n    AVS_LIST_CLEAR(&affected_end_devs_attr_storages) {\n        if (!final_result) {\n            _anjay_attr_storage_transaction_commit(\n                    *affected_end_devs_attr_storages);\n        } else {\n            (void) _anjay_attr_storage_transaction_rollback(\n                    anjay, *affected_end_devs_attr_storages);\n        }\n    }\n#    endif // ANJAY_WITH_LWM2M_GATEWAY\n#endif     // ANJAY_WITH_ATTR_STORAGE\n    return final_result;\n}\n\nint _anjay_dm_transaction_finish(anjay_unlocked_t *anjay, int result) {\n    if (!result && anjay->transaction_state.depth == 1) {\n        result = _anjay_dm_transaction_validate(anjay);\n    }\n    return _anjay_dm_transaction_finish_without_validation(anjay, result);\n}\n\nbool _anjay_dm_transaction_object_included(\n        anjay_unlocked_t *anjay, const anjay_dm_installed_object_t *obj_ptr) {\n    if (anjay->transaction_state.depth > 0) {\n        AVS_LIST(const anjay_dm_installed_object_t *) *it;\n        AVS_LIST_FOREACH_PTR(it,\n                             &anjay->transaction_state.objs_in_transaction) {\n            if (**it == obj_ptr) {\n                return true;\n            } else if (**it > obj_ptr) {\n                break;\n            }\n        }\n    }\n    return false;\n}\n\nint anjay_dm_list_instances_SINGLE(anjay_t *anjay,\n                                   const anjay_dm_object_def_t *const *obj_ptr,\n                                   anjay_dm_list_ctx_t *ctx) {\n    (void) anjay;\n    (void) obj_ptr;\n    anjay_dm_emit(ctx, 0);\n    return 0;\n}\n\nint anjay_dm_transaction_NOOP(anjay_t *anjay,\n                              const anjay_dm_object_def_t *const *obj_ptr) {\n    (void) anjay;\n    (void) obj_ptr;\n    return 0;\n}\n"
  },
  {
    "path": "src/core/dm/anjay_dm_read.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#include <avsystem/coap/code.h>\n\n#include <avsystem/commons/avs_stream_membuf.h>\n\n#ifdef ANJAY_WITH_LWM2M_GATEWAY\n#    include <anjay_modules/anjay_lwm2m_gateway.h>\n#endif // ANJAY_WITH_LWM2M_GATEWAY\n\n#include \"anjay_dm_read.h\"\n\n#include \"../anjay_access_utils_private.h\"\n#include \"../coap/anjay_content_format.h\"\n#include \"../io/anjay_vtable.h\"\n\nVISIBILITY_SOURCE_BEGIN\n\nstatic int\nread_resource_instance_internal(anjay_unlocked_t *anjay,\n                                const anjay_dm_installed_object_t *obj,\n                                anjay_iid_t iid,\n                                anjay_rid_t rid,\n                                anjay_riid_t riid,\n                                anjay_unlocked_output_ctx_t *out_ctx) {\n    int result;\n    anjay_uri_path_t path =\n            MAKE_RESOURCE_INSTANCE_PATH(_anjay_dm_installed_object_oid(obj),\n                                        iid, rid, riid);\n#ifdef ANJAY_WITH_LWM2M_GATEWAY\n    if (obj->prefix) {\n        strcpy(path.prefix, obj->prefix);\n    }\n#endif // ANJAY_WITH_LWM2M_GATEWAY\n    (void) ((result = _anjay_output_set_path(out_ctx, &path))\n            || (result = _anjay_dm_call_resource_read(anjay, obj, iid, rid,\n                                                      riid, out_ctx)));\n    return result;\n}\n\n#ifdef ANJAY_WITH_LWM2M11\nstatic int read_resource_instance(anjay_unlocked_t *anjay,\n                                  const anjay_dm_installed_object_t *obj,\n                                  anjay_iid_t iid,\n                                  anjay_rid_t rid,\n                                  anjay_riid_t riid,\n                                  anjay_dm_resource_kind_t kind,\n                                  anjay_unlocked_output_ctx_t *out_ctx) {\n    if (!_anjay_dm_res_kind_readable(kind)) {\n        anjay_log(DEBUG, \"/%u/%u/%u\" _(\" is not readable\"),\n                  _anjay_dm_installed_object_oid(obj), iid, rid);\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n    if (!_anjay_dm_res_kind_multiple(kind)) {\n        anjay_log(DEBUG,\n                  _(\"cannot read \") \"/%u/%u/%u/%u\" _(\" because \") \"/%u/%u/%u\" _(\n                          \" is not a multiple resource\"),\n                  _anjay_dm_installed_object_oid(obj), iid, rid, riid,\n                  _anjay_dm_installed_object_oid(obj), iid, rid);\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n    return read_resource_instance_internal(anjay, obj, iid, rid, riid, out_ctx);\n}\n#endif // ANJAY_WITH_LWM2M11\n\nstatic int read_resource_instance_clb(anjay_unlocked_t *anjay,\n                                      const anjay_dm_installed_object_t *obj,\n                                      anjay_iid_t iid,\n                                      anjay_rid_t rid,\n                                      anjay_riid_t riid,\n                                      void *out_ctx_) {\n    anjay_unlocked_output_ctx_t *out_ctx =\n            (anjay_unlocked_output_ctx_t *) out_ctx_;\n    int result = read_resource_instance_internal(anjay, obj, iid, rid, riid,\n                                                 out_ctx);\n    if ((result == ANJAY_ERR_METHOD_NOT_ALLOWED\n         || result == ANJAY_ERR_NOT_FOUND)\n            && !(result = _anjay_output_clear_path(out_ctx))) {\n        dm_log(DEBUG,\n               \"%s\" _(\" when attempted to read \") DM_LOG_PREFIX\n               \"/%u/%u/%u/%u\" _(\", skipping\"),\n               DM_LOG_PREFIX_OBJ_ARG(obj)\n                       AVS_COAP_CODE_STRING((uint8_t) -result),\n               _anjay_dm_installed_object_oid(obj), iid, rid, riid);\n    }\n    return result;\n}\n\nstatic int read_multiple_resource(anjay_unlocked_t *anjay,\n                                  const anjay_dm_installed_object_t *obj,\n                                  anjay_iid_t iid,\n                                  anjay_rid_t rid,\n                                  anjay_unlocked_output_ctx_t *out_ctx) {\n    int result;\n    anjay_uri_path_t path =\n            MAKE_RESOURCE_PATH(_anjay_dm_installed_object_oid(obj), iid, rid);\n#ifdef ANJAY_WITH_LWM2M_GATEWAY\n    if (obj->prefix) {\n        strcpy(path.prefix, obj->prefix);\n    }\n#endif // ANJAY_WITH_LWM2M_GATEWAY\n    (void) ((result = _anjay_output_set_path(out_ctx, &path))\n            || (result = _anjay_output_start_aggregate(out_ctx))\n            || (result = _anjay_dm_foreach_resource_instance(\n                        anjay, obj, iid, rid, read_resource_instance_clb,\n                        out_ctx)));\n    return result;\n}\n\nstatic int read_resource_internal(anjay_unlocked_t *anjay,\n                                  const anjay_dm_installed_object_t *obj,\n                                  anjay_iid_t iid,\n                                  anjay_rid_t rid,\n                                  anjay_dm_resource_kind_t kind,\n                                  anjay_unlocked_output_ctx_t *out_ctx) {\n    if (_anjay_dm_res_kind_multiple(kind)) {\n        return read_multiple_resource(anjay, obj, iid, rid, out_ctx);\n    } else {\n        int result;\n        anjay_uri_path_t path =\n                MAKE_RESOURCE_PATH(_anjay_dm_installed_object_oid(obj), iid,\n                                   rid);\n#ifdef ANJAY_WITH_LWM2M_GATEWAY\n        if (obj->prefix) {\n            strcpy(path.prefix, obj->prefix);\n        }\n#endif // ANJAY_WITH_LWM2M_GATEWAY\n        (void) ((result = _anjay_output_set_path(out_ctx, &path))\n                || (result = _anjay_dm_call_resource_read(\n                            anjay, obj, iid, rid, ANJAY_ID_INVALID, out_ctx)));\n        return result;\n    }\n}\n\nstatic int read_resource(anjay_unlocked_t *anjay,\n                         const anjay_dm_installed_object_t *obj,\n                         anjay_iid_t iid,\n                         anjay_rid_t rid,\n                         anjay_dm_resource_kind_t kind,\n                         anjay_unlocked_output_ctx_t *out_ctx) {\n    if (!_anjay_dm_res_kind_readable(kind)) {\n        dm_log(DEBUG, DM_LOG_PREFIX \"/%u/%u/%u\" _(\" is not readable\"),\n               DM_LOG_PREFIX_OBJ_ARG(obj) _anjay_dm_installed_object_oid(obj),\n               iid, rid);\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n    return read_resource_internal(anjay, obj, iid, rid, kind, out_ctx);\n}\n\ntypedef struct {\n    anjay_unlocked_output_ctx_t *out_ctx;\n    anjay_ssid_t requesting_ssid;\n} read_instance_resource_clb_args_t;\n\nstatic int read_instance_resource_clb(anjay_unlocked_t *anjay,\n                                      const anjay_dm_installed_object_t *obj,\n                                      anjay_iid_t iid,\n                                      anjay_rid_t rid,\n                                      anjay_dm_resource_kind_t kind,\n                                      anjay_dm_resource_presence_t presence,\n                                      void *args_) {\n    read_instance_resource_clb_args_t *args =\n            (read_instance_resource_clb_args_t *) args_;\n    if (presence == ANJAY_DM_RES_ABSENT) {\n        dm_log(DEBUG, DM_LOG_PREFIX \"/%u/%u/%u\" _(\" is not present, skipping\"),\n               DM_LOG_PREFIX_OBJ_ARG(obj) _anjay_dm_installed_object_oid(obj),\n               iid, rid);\n        return 0;\n    }\n    bool read_allowed = _anjay_dm_res_kind_readable(kind);\n    if (!read_allowed && args->requesting_ssid == ANJAY_SSID_BOOTSTRAP) {\n        read_allowed = _anjay_dm_res_kind_bootstrappable(kind)\n                       || _anjay_dm_res_kind_writable(kind);\n    }\n    if (!read_allowed) {\n        dm_log(DEBUG, DM_LOG_PREFIX \"/%u/%u/%u\" _(\" is not readable, skipping\"),\n               DM_LOG_PREFIX_OBJ_ARG(obj) _anjay_dm_installed_object_oid(obj),\n               iid, rid);\n        return 0;\n    }\n\n    int result =\n            read_resource_internal(anjay, obj, iid, rid, kind, args->out_ctx);\n    if ((result == ANJAY_ERR_METHOD_NOT_ALLOWED\n         || result == ANJAY_ERR_NOT_FOUND)\n            && !(result = _anjay_output_clear_path(args->out_ctx))) {\n        dm_log(DEBUG,\n               \"%s\" _(\" when attempted to read \") DM_LOG_PREFIX\n               \"/%u/%u/%u\" _(\", skipping\"),\n               AVS_COAP_CODE_STRING((uint8_t) -result),\n               DM_LOG_PREFIX_OBJ_ARG(obj) _anjay_dm_installed_object_oid(obj),\n               iid, rid);\n    }\n    return result;\n}\n\nstatic int read_instance(anjay_unlocked_t *anjay,\n                         const anjay_dm_installed_object_t *obj,\n                         anjay_iid_t iid,\n                         anjay_ssid_t requesting_ssid,\n                         anjay_unlocked_output_ctx_t *out_ctx) {\n    int result;\n    anjay_uri_path_t path =\n            MAKE_INSTANCE_PATH(_anjay_dm_installed_object_oid(obj), iid);\n#ifdef ANJAY_WITH_LWM2M_GATEWAY\n    if (obj->prefix) {\n        strcpy(path.prefix, obj->prefix);\n    }\n#endif // ANJAY_WITH_LWM2M_GATEWAY\n    (void) ((result = _anjay_output_set_path(out_ctx, &path))\n            || (result = _anjay_output_start_aggregate(out_ctx))\n            || (result = _anjay_dm_foreach_resource(\n                        anjay, obj, iid, read_instance_resource_clb,\n                        &(read_instance_resource_clb_args_t) {\n                            .out_ctx = out_ctx,\n                            .requesting_ssid = requesting_ssid\n                        })));\n    return result;\n}\n\ntypedef struct {\n    anjay_uri_path_t uri;\n    anjay_ssid_t requesting_ssid;\n    anjay_unlocked_output_ctx_t *out_ctx;\n} read_instance_clb_args_t;\n\nstatic int read_instance_clb(anjay_unlocked_t *anjay,\n                             const anjay_dm_installed_object_t *obj,\n                             anjay_iid_t iid,\n                             void *args_) {\n    read_instance_clb_args_t *args = (read_instance_clb_args_t *) args_;\n    anjay_action_info_t info = {\n#ifdef ANJAY_WITH_LWM2M_GATEWAY\n        .end_device = _anjay_uri_path_has_prefix(&args->uri),\n#endif // ANJAY_WITH_LWM2M_GATEWAY\n        .oid = args->uri.ids[ANJAY_ID_OID],\n        .iid = iid,\n        .ssid = args->requesting_ssid,\n        .action = ANJAY_ACTION_READ\n    };\n    if (!_anjay_instance_action_allowed(anjay, &info)) {\n        return ANJAY_FOREACH_CONTINUE;\n    }\n    return read_instance(anjay, obj, iid, args->requesting_ssid, args->out_ctx);\n}\n\nstatic int read_object(anjay_unlocked_t *anjay,\n                       const anjay_dm_installed_object_t *obj,\n                       const anjay_uri_path_t *uri,\n                       anjay_ssid_t requesting_ssid,\n                       anjay_unlocked_output_ctx_t *out_ctx) {\n    assert(_anjay_uri_path_has(uri, ANJAY_ID_OID));\n    return _anjay_dm_foreach_instance(anjay, obj, read_instance_clb,\n                                      &(read_instance_clb_args_t) {\n                                          .uri = *uri,\n                                          .requesting_ssid = requesting_ssid,\n                                          .out_ctx = out_ctx\n                                      });\n}\n\ntypedef struct {\n    anjay_ssid_t requesting_ssid;\n    anjay_unlocked_output_ctx_t *out_ctx;\n} read_object_clb_args_t;\n\nstatic int read_object_clb(anjay_unlocked_t *anjay,\n                           const anjay_dm_installed_object_t *obj,\n                           void *args_) {\n    if (_anjay_dm_installed_object_oid(obj) == ANJAY_DM_OID_SECURITY) {\n        return ANJAY_FOREACH_CONTINUE;\n    }\n    read_object_clb_args_t *args = (read_object_clb_args_t *) args_;\n    return read_object(anjay, obj,\n                       &MAKE_OBJECT_PATH(_anjay_dm_installed_object_oid(obj)),\n                       args->requesting_ssid, args->out_ctx);\n}\n\nstatic int read_root(anjay_unlocked_t *anjay,\n                     anjay_ssid_t requesting_ssid,\n                     anjay_unlocked_output_ctx_t *out_ctx) {\n    return _anjay_dm_foreach_object(anjay, &anjay->dm, read_object_clb,\n                                    &(read_object_clb_args_t) {\n                                        .requesting_ssid = requesting_ssid,\n                                        .out_ctx = out_ctx\n                                    });\n}\n\nint _anjay_dm_read(anjay_unlocked_t *anjay,\n                   const anjay_dm_installed_object_t *obj,\n                   const anjay_dm_path_info_t *path_info,\n                   anjay_ssid_t requesting_ssid,\n                   anjay_unlocked_output_ctx_t *out_ctx) {\n    if (!path_info->is_present) {\n        return ANJAY_ERR_NOT_FOUND;\n    }\n    if (_anjay_uri_path_has(&path_info->uri, ANJAY_ID_IID)) {\n        const anjay_action_info_t action_info = {\n#ifdef ANJAY_WITH_LWM2M_GATEWAY\n            .end_device = _anjay_uri_path_has_prefix(&path_info->uri),\n#endif // ANJAY_WITH_LWM2M_GATEWAY\n            .iid = path_info->uri.ids[ANJAY_ID_IID],\n            .oid = path_info->uri.ids[ANJAY_ID_OID],\n            .ssid = requesting_ssid,\n            .action = ANJAY_ACTION_READ\n        };\n        if (!_anjay_instance_action_allowed(anjay, &action_info)) {\n            return ANJAY_ERR_UNAUTHORIZED;\n        }\n    }\n    if (_anjay_uri_path_length(&path_info->uri) == 0) {\n        assert(!obj);\n        return read_root(anjay, requesting_ssid, out_ctx);\n    }\n    assert(obj);\n    assert(path_info->uri.ids[ANJAY_ID_OID]\n           == _anjay_dm_installed_object_oid(obj));\n    if (_anjay_uri_path_leaf_is(&path_info->uri, ANJAY_ID_OID)) {\n        return read_object(anjay, obj, &path_info->uri, requesting_ssid,\n                           out_ctx);\n    } else if (_anjay_uri_path_leaf_is(&path_info->uri, ANJAY_ID_IID)) {\n        return read_instance(anjay, obj, path_info->uri.ids[ANJAY_ID_IID],\n                             requesting_ssid, out_ctx);\n    } else if (_anjay_uri_path_leaf_is(&path_info->uri, ANJAY_ID_RID)) {\n        return read_resource(anjay, obj, path_info->uri.ids[ANJAY_ID_IID],\n                             path_info->uri.ids[ANJAY_ID_RID], path_info->kind,\n                             out_ctx);\n    } else {\n        assert(_anjay_uri_path_leaf_is(&path_info->uri, ANJAY_ID_RIID));\n#ifdef ANJAY_WITH_LWM2M11\n        return read_resource_instance(anjay, obj,\n                                      path_info->uri.ids[ANJAY_ID_IID],\n                                      path_info->uri.ids[ANJAY_ID_RID],\n                                      path_info->uri.ids[ANJAY_ID_RIID],\n                                      path_info->kind, out_ctx);\n#else  // ANJAY_WITH_LWM2M11\n        dm_log(ERROR, _(\"Read on Resource Instances is not supported in this \"\n                        \"version of Anjay\"));\n        return ANJAY_ERR_BAD_REQUEST;\n#endif // ANJAY_WITH_LWM2M11\n    }\n}\n\nint _anjay_dm_read_and_destroy_ctx(anjay_unlocked_t *anjay,\n                                   const anjay_dm_installed_object_t *obj,\n                                   const anjay_dm_path_info_t *path_info,\n                                   anjay_ssid_t requesting_ssid,\n                                   anjay_unlocked_output_ctx_t **out_ctx_ptr) {\n    dm_log(LAZY_DEBUG, _(\"Read \") \"%s\", ANJAY_DEBUG_MAKE_PATH(&path_info->uri));\n    return _anjay_output_ctx_destroy_and_process_result(\n            out_ctx_ptr, _anjay_dm_read(anjay, obj, path_info, requesting_ssid,\n                                        *out_ctx_ptr));\n}\n\nanjay_msg_details_t\n_anjay_dm_response_details_for_read(anjay_unlocked_t *anjay,\n                                    const anjay_request_t *request,\n                                    bool requires_hierarchical_format,\n                                    anjay_lwm2m_version_t lwm2m_version) {\n#ifdef ANJAY_WITH_LWM2M11\n    assert(request->action == ANJAY_ACTION_READ\n           || request->action == ANJAY_ACTION_READ_COMPOSITE);\n#else  // ANJAY_WITH_LWM2M11\n    assert(request->action == ANJAY_ACTION_READ);\n#endif // ANJAY_WITH_LWM2M11\n    uint16_t format = request->requested_format;\n    if (format == AVS_COAP_FORMAT_NONE) {\n        if (requires_hierarchical_format) {\n            format = _anjay_default_hierarchical_format(lwm2m_version);\n        } else {\n            format = _anjay_default_simple_format(anjay, lwm2m_version);\n        }\n    }\n    return (const anjay_msg_details_t) {\n        .msg_code = _anjay_dm_make_success_response_code(request->action),\n        .format = format\n    };\n}\n\nint _anjay_dm_read_or_observe(anjay_connection_ref_t connection,\n                              const anjay_dm_installed_object_t *obj,\n                              const anjay_request_t *request) {\n    assert(_anjay_uri_path_has(&request->uri, ANJAY_ID_OID));\n    if (request->observe) {\n        dm_log(LAZY_DEBUG, _(\"Observe \") \"%s\",\n               ANJAY_DEBUG_MAKE_PATH(&request->uri));\n#ifdef ANJAY_WITH_OBSERVE\n        return _anjay_observe_handle(connection, request);\n#else  // ANJAY_WITH_OBSERVE\n        dm_log(ERROR, _(\"Observe support disabled\"));\n        return ANJAY_ERR_BAD_OPTION;\n#endif // ANJAY_WITH_OBSERVE\n    }\n\n    anjay_unlocked_t *anjay = _anjay_from_server(connection.server);\n    anjay_dm_path_info_t path_info;\n    int result = _anjay_dm_path_info(anjay, obj, &request->uri, &path_info);\n    if (result) {\n        return result;\n    }\n    const anjay_msg_details_t details = _anjay_dm_response_details_for_read(\n            anjay, request, path_info.is_hierarchical,\n            _anjay_server_registration_info(connection.server)->lwm2m_version);\n\n    avs_stream_t *response_stream =\n            _anjay_coap_setup_response_stream(request->ctx, &details);\n    if (!response_stream) {\n        return ANJAY_ERR_INTERNAL;\n    }\n\n    anjay_unlocked_output_ctx_t *out_ctx = NULL;\n    if ((result = _anjay_output_dynamic_construct(&out_ctx, response_stream,\n                                                  &request->uri, details.format,\n                                                  NULL, ANJAY_ACTION_READ))) {\n        return result;\n    }\n    return _anjay_dm_read_and_destroy_ctx(anjay, obj, &path_info,\n                                          _anjay_server_ssid(connection.server),\n                                          &out_ctx);\n}\n\nint _anjay_dm_read_resource_into_ctx(anjay_unlocked_t *anjay,\n                                     const anjay_uri_path_t *path,\n                                     anjay_unlocked_output_ctx_t *ctx) {\n    assert(_anjay_uri_path_leaf_is(path, ANJAY_ID_RID));\n    const anjay_dm_t *dm;\n#ifdef ANJAY_WITH_LWM2M_GATEWAY\n    if (_anjay_uri_path_has_prefix(path)) {\n        if (_anjay_lwm2m_gateway_prefix_to_dm(anjay, path->prefix, &dm)) {\n            return -1;\n        }\n    } else\n#endif // ANJAY_WITH_LWM2M_GATEWAY\n    {\n        dm = &anjay->dm;\n    }\n    const anjay_dm_installed_object_t *obj =\n            _anjay_dm_find_object_by_oid(dm, path->ids[ANJAY_ID_OID]);\n    if (!obj) {\n        dm_log(ERROR, _(\"unregistered Object ID: \") \"%u\",\n               path->ids[ANJAY_ID_OID]);\n        return -1;\n    }\n\n    anjay_dm_resource_kind_t kind;\n    int result;\n    (void) ((result = _anjay_dm_verify_resource_present(\n                     anjay, obj, path->ids[ANJAY_ID_IID],\n                     path->ids[ANJAY_ID_RID], &kind))\n            || (result = read_resource_internal(\n                        anjay, obj, path->ids[ANJAY_ID_IID],\n                        path->ids[ANJAY_ID_RID], kind, ctx)));\n    return result;\n}\n\nint _anjay_dm_read_resource_into_stream(anjay_unlocked_t *anjay,\n                                        const anjay_uri_path_t *path,\n                                        avs_stream_t *stream) {\n    anjay_output_buf_ctx_t ctx = _anjay_output_buf_ctx_init(stream);\n    return _anjay_dm_read_resource_into_ctx(\n            anjay, path, (anjay_unlocked_output_ctx_t *) &ctx);\n}\n\nint _anjay_dm_read_resource_into_buffer(anjay_unlocked_t *anjay,\n                                        const anjay_uri_path_t *path,\n                                        char *buffer,\n                                        size_t buffer_size,\n                                        size_t *out_bytes_read) {\n    avs_stream_outbuf_t stream = AVS_STREAM_OUTBUF_STATIC_INITIALIZER;\n    avs_stream_outbuf_set_buffer(&stream, buffer, buffer_size);\n\n    int result = _anjay_dm_read_resource_into_stream(anjay, path,\n                                                     (avs_stream_t *) &stream);\n    if (out_bytes_read) {\n        *out_bytes_read = avs_stream_outbuf_offset(&stream);\n    }\n    return result;\n}\n\n#ifdef ANJAY_WITH_LWM2M11\ntypedef struct {\n    anjay_unlocked_output_ctx_t base;\n    avs_stream_t *stream;\n} anjay_dm_read_resource_u32_array_ctx_t;\n\nstatic int u32_array_ret_uint(anjay_unlocked_output_ctx_t *ctx_,\n                              uint64_t value) {\n    anjay_dm_read_resource_u32_array_ctx_t *ctx =\n            (anjay_dm_read_resource_u32_array_ctx_t *) ctx_;\n    if (value > UINT32_MAX) {\n        return -1;\n    }\n    uint32_t value32 = (uint32_t) value;\n    return avs_is_ok(avs_stream_write((avs_stream_t *) ctx->stream, &value32,\n                                      sizeof(value32)))\n                   ? 0\n                   : -1;\n}\n\nstatic const anjay_output_ctx_vtable_t U32_ARRAY_CTX = {\n    .uint = u32_array_ret_uint\n};\n\nint _anjay_dm_read_resource_u32_array(anjay_unlocked_t *anjay,\n                                      const anjay_uri_path_t *path,\n                                      uint32_t **out_array,\n                                      size_t *out_array_size_elements) {\n    assert(_anjay_uri_path_leaf_is(path, ANJAY_ID_RID));\n    assert(out_array);\n    assert(out_array_size_elements);\n    const anjay_dm_t *dm;\n#    ifdef ANJAY_WITH_LWM2M_GATEWAY\n    if (_anjay_uri_path_has_prefix(path)) {\n        if (_anjay_lwm2m_gateway_prefix_to_dm(anjay, path->prefix, &dm)) {\n            return -1;\n        }\n    } else\n#    endif // ANJAY_WITH_LWM2M_GATEWAY\n    {\n        dm = &anjay->dm;\n    }\n    const anjay_dm_installed_object_t *obj =\n            _anjay_dm_find_object_by_oid(dm, path->ids[ANJAY_ID_OID]);\n    if (!obj) {\n        return -1;\n    }\n\n    anjay_dm_resource_kind_t kind;\n    int result =\n            _anjay_dm_verify_resource_present(anjay, obj,\n                                              path->ids[ANJAY_ID_IID],\n                                              path->ids[ANJAY_ID_RID], &kind);\n    if (result) {\n        return result;\n    }\n    if (!_anjay_dm_res_kind_readable(kind)\n            || !_anjay_dm_res_kind_multiple(kind)) {\n        return -1;\n    }\n\n    avs_stream_t *stream = avs_stream_membuf_create();\n    if (!stream) {\n        return -1;\n    }\n    anjay_dm_read_resource_u32_array_ctx_t ctx = {\n        .base = {\n            .vtable = &U32_ARRAY_CTX\n        },\n        .stream = stream\n    };\n    anjay_unlocked_output_ctx_t *out_ctx = (anjay_unlocked_output_ctx_t *) &ctx;\n\n    void *u32_array = NULL;\n    size_t u32_array_size_bytes = 0;\n    (void) ((result = _anjay_dm_foreach_resource_instance(\n                     anjay, obj, path->ids[ANJAY_ID_IID],\n                     path->ids[ANJAY_ID_RID], read_resource_instance_clb,\n                     out_ctx))\n            || (result = (avs_is_ok(avs_stream_membuf_take_ownership(\n                                  stream, &u32_array, &u32_array_size_bytes))\n                                  ? 0\n                                  : -1)));\n\n    AVS_ASSERT(((uintptr_t) u32_array) % AVS_ALIGNOF(uint32_t) == 0,\n               \"avs_stream_membuf_take_ownership returned misaligned pointer\");\n    if (result == 0) {\n        size_t u32_array_size_elems = u32_array_size_bytes / sizeof(uint32_t);\n\n        *out_array = (uint32_t *) u32_array;\n        *out_array_size_elements = u32_array_size_elems;\n    }\n\n    avs_stream_cleanup(&stream);\n    return result;\n}\n\n#    ifndef ANJAY_WITHOUT_COMPOSITE_OPERATIONS\nstatic int cache_all_paths(anjay_unlocked_input_ctx_t *in_ctx,\n                           AVS_LIST(anjay_uri_path_t) *out_paths) {\n    AVS_LIST(anjay_uri_path_t) *endptr = out_paths;\n    int result;\n    anjay_uri_path_t path;\n    while (!(result = _anjay_input_get_path(in_ctx, &path, NULL))) {\n        AVS_LIST(anjay_uri_path_t) next =\n                AVS_LIST_NEW_ELEMENT(anjay_uri_path_t);\n        if (!next) {\n            result = -1;\n            break;\n        }\n        *next = path;\n        AVS_LIST_APPEND(endptr, next);\n        AVS_LIST_ADVANCE_PTR(&endptr);\n\n        if (_anjay_input_next_entry(in_ctx)) {\n            result = ANJAY_ERR_BAD_REQUEST;\n            break;\n        }\n    }\n    if (result == ANJAY_GET_PATH_END) {\n        result = 0;\n    } else {\n        AVS_LIST_CLEAR(out_paths);\n    }\n    return result;\n}\n\nint _anjay_dm_read_or_observe_composite(anjay_connection_ref_t connection,\n                                        const anjay_request_t *request,\n                                        anjay_unlocked_input_ctx_t *in_ctx) {\n    if (_anjay_uri_path_has(&request->uri, ANJAY_ID_OID)) {\n        dm_log(DEBUG, _(\"Read Composite with Uri-Path is not allowed\"));\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n\n    AVS_LIST(anjay_uri_path_t) cached_paths = NULL;\n    int result = cache_all_paths(in_ctx, &cached_paths);\n    if (result) {\n        return result;\n    }\n    if (request->observe) {\n        dm_log(DEBUG, _(\"Observe Composite\"));\n#        ifdef ANJAY_WITH_OBSERVE\n        result = _anjay_observe_composite_handle(connection, cached_paths,\n                                                 request);\n#        else  // ANJAY_WITH_OBSERVE\n        dm_log(ERROR, _(\"Observe support disabled\"));\n        return ANJAY_ERR_BAD_OPTION;\n#        endif // ANJAY_WITH_OBSERVE\n    } else {\n        anjay_unlocked_t *anjay = _anjay_from_server(connection.server);\n        const anjay_msg_details_t details = _anjay_dm_response_details_for_read(\n                anjay, request, true,\n                _anjay_server_registration_info(connection.server)\n                        ->lwm2m_version);\n        avs_stream_t *response_stream =\n                _anjay_coap_setup_response_stream(request->ctx, &details);\n        if (!response_stream) {\n            return ANJAY_ERR_INTERNAL;\n        }\n\n        anjay_uri_path_t root_path = MAKE_ROOT_PATH();\n        const anjay_uri_path_t *prefix_path = NULL;\n        {\n            AVS_LIST(anjay_uri_path_t) path = cached_paths;\n            AVS_LIST_FOREACH(path, cached_paths) {\n                _anjay_uri_path_update_common_prefix(&prefix_path, &root_path,\n                                                     path);\n            }\n        }\n\n        anjay_unlocked_output_ctx_t *out_ctx = NULL;\n        (void) ((result = _anjay_output_dynamic_construct(\n                         &out_ctx, response_stream, &root_path, details.format,\n                         NULL, ANJAY_ACTION_READ_COMPOSITE)));\n        while (!result && cached_paths) {\n            const anjay_uri_path_t path = *cached_paths;\n            AVS_LIST_DELETE(&cached_paths);\n\n            dm_log(DEBUG, _(\"Read Composite \") \"%s\",\n                   ANJAY_DEBUG_MAKE_PATH(&path));\n\n#        ifdef ANJAY_WITH_LWM2M_GATEWAY\n            if (_anjay_uri_path_has_prefix(&path)) {\n                dm_log(ERROR,\n                       _(\"Read Composite on End Devices DMs is not supported\"));\n                result = ANJAY_ERR_METHOD_NOT_ALLOWED;\n                break;\n            }\n#        endif // ANJAY_WITH_LWM2M_GATEWAY\n\n            const anjay_dm_installed_object_t *obj = NULL;\n            if (_anjay_uri_path_has(&path, ANJAY_ID_OID)) {\n                obj = _anjay_dm_find_object_by_oid(&anjay->dm,\n                                                   path.ids[ANJAY_ID_OID]);\n\n                if (!obj) {\n                    dm_log(DEBUG,\n                           _(\"Object not found: \") DM_LOG_PREFIX\n                           \"/%u\" _(\", ignoring it\"),\n                           DM_LOG_PREFIX_ARG(path.prefix)\n                                   path.ids[ANJAY_ID_OID]);\n                    continue;\n                }\n            }\n            anjay_dm_path_info_t path_info;\n            (void) ((result =\n                             _anjay_dm_path_info(anjay, obj, &path, &path_info))\n                    || (result = _anjay_dm_read(anjay, obj, &path_info,\n                                                _anjay_server_ssid(\n                                                        connection.server),\n                                                out_ctx)));\n            if (result\n                    && avs_coap_code_is_client_error(\n                               _anjay_make_error_response_code(result))) {\n                result = 0;\n            }\n        }\n        result = _anjay_output_ctx_destroy_and_process_result(&out_ctx, result);\n    }\n    AVS_LIST_CLEAR(&cached_paths);\n    return result;\n}\n#    endif // ANJAY_WITHOUT_COMPOSITE_OPERATIONS\n#endif     // ANJAY_WITH_LWM2M11\n"
  },
  {
    "path": "src/core/dm/anjay_dm_read.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ANJAY_READ_CORE_H\n#define ANJAY_READ_CORE_H\n\n#include <anjay/core.h>\n\n#include \"../anjay_dm_core.h\"\n#include \"../anjay_io_core.h\"\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\nanjay_msg_details_t\n_anjay_dm_response_details_for_read(anjay_unlocked_t *anjay,\n                                    const anjay_request_t *request,\n                                    bool requires_hierarchical_format,\n                                    anjay_lwm2m_version_t lwm2m_version);\n\nint _anjay_dm_read_or_observe(anjay_connection_ref_t connection,\n                              const anjay_dm_installed_object_t *obj,\n                              const anjay_request_t *request);\n\nint _anjay_dm_read(anjay_unlocked_t *anjay,\n                   const anjay_dm_installed_object_t *obj,\n                   const anjay_dm_path_info_t *path_info,\n                   anjay_ssid_t requesting_ssid,\n                   anjay_unlocked_output_ctx_t *out_ctx);\n\nint _anjay_dm_read_and_destroy_ctx(anjay_unlocked_t *anjay,\n                                   const anjay_dm_installed_object_t *obj,\n                                   const anjay_dm_path_info_t *path_info,\n                                   anjay_ssid_t requesting_ssid,\n                                   anjay_unlocked_output_ctx_t **out_ctx_ptr);\n\n#if defined(ANJAY_WITH_LWM2M11) && !defined(ANJAY_WITHOUT_COMPOSITE_OPERATIONS)\nint _anjay_dm_read_or_observe_composite(anjay_connection_ref_t connection,\n                                        const anjay_request_t *request,\n                                        anjay_unlocked_input_ctx_t *in_ctx);\n#endif // defined(ANJAY_WITH_LWM2M11) &&\n       // !defined(ANJAY_WITHOUT_COMPOSITE_OPERATIONS)\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif // ANJAY_READ_CORE_H\n"
  },
  {
    "path": "src/core/dm/anjay_dm_write.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#include \"anjay_dm_write.h\"\n\n#include \"../anjay_access_utils_private.h\"\n\n#include <avsystem/commons/avs_stream_inbuf.h>\n\nVISIBILITY_SOURCE_BEGIN\n\nstatic int\npreverify_resource_before_writing(anjay_unlocked_t *anjay,\n                                  const anjay_dm_installed_object_t *obj,\n                                  const anjay_uri_path_t *request_path,\n                                  const anjay_uri_path_t *payload_path,\n                                  bool allow_non_writable,\n                                  anjay_dm_resource_kind_t *out_kind,\n                                  anjay_dm_resource_presence_t *out_presence) {\n    assert(payload_path);\n    assert(out_kind);\n    assert(_anjay_uri_path_has(payload_path, ANJAY_ID_RID));\n    assert(payload_path->ids[ANJAY_ID_OID]\n           == _anjay_dm_installed_object_oid(obj));\n\n    int result = _anjay_dm_resource_kind_and_presence(\n            anjay, obj, payload_path->ids[ANJAY_ID_IID],\n            payload_path->ids[ANJAY_ID_RID], out_kind, out_presence);\n    if (result) {\n        return result;\n    }\n    if (!_anjay_dm_res_kind_writable(*out_kind)\n            && (!allow_non_writable\n                || !(_anjay_dm_res_kind_readable(*out_kind)\n                     || _anjay_dm_res_kind_bootstrappable(*out_kind)))) {\n        anjay_log(LAZY_DEBUG, \"%s\" _(\" is not writable\"),\n                  ANJAY_DEBUG_MAKE_PATH(payload_path));\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n\n    if (_anjay_uri_path_has(payload_path, ANJAY_ID_RIID)\n            && !_anjay_dm_res_kind_multiple(*out_kind)) {\n        anjay_log(LAZY_DEBUG,\n                  _(\"cannot write \") \"%s\" _(\" because the path does not point \"\n                                            \"inside a multiple resource\"),\n                  ANJAY_DEBUG_MAKE_PATH(payload_path));\n        return (request_path != NULL\n                && _anjay_uri_path_has(request_path, ANJAY_ID_RIID))\n                       ? ANJAY_ERR_METHOD_NOT_ALLOWED\n                       : ANJAY_ERR_BAD_REQUEST;\n    }\n\n    return 0;\n}\n\n#ifdef ANJAY_WITH_LWM2M11\n/**\n * Writes to resource instance whose location is determined by the path\n * extracted from Input Context (@p in_ctx).\n */\nstatic int write_resource_instance(anjay_unlocked_t *anjay,\n                                   const anjay_dm_installed_object_t *obj,\n                                   const anjay_uri_path_t *path,\n                                   anjay_dm_resource_presence_t presence,\n                                   anjay_unlocked_input_ctx_t *in_ctx,\n                                   anjay_notify_queue_t *notify_queue,\n                                   bool create_nonexistent) {\n    assert(_anjay_uri_path_leaf_is(path, ANJAY_ID_RIID));\n\n    if (!create_nonexistent && presence == ANJAY_DM_RES_ABSENT) {\n        return ANJAY_ERR_NOT_FOUND;\n    }\n\n    int result;\n    if (!create_nonexistent\n            && (result = _anjay_dm_verify_resource_instance_present(\n                        anjay, obj, path->ids[ANJAY_ID_IID],\n                        path->ids[ANJAY_ID_RID], path->ids[ANJAY_ID_RIID]))) {\n        return result;\n    }\n\n    result = _anjay_dm_call_resource_write(anjay, obj, path->ids[ANJAY_ID_IID],\n                                           path->ids[ANJAY_ID_RID],\n                                           path->ids[ANJAY_ID_RIID], in_ctx);\n\n    if (!result && notify_queue) {\n        result = _anjay_notify_queue_resource_change(notify_queue, path);\n    }\n    return result;\n}\n#endif // ANJAY_WITH_LWM2M11\n\nstatic int return_with_moving_to_next_entry(anjay_unlocked_input_ctx_t *in_ctx,\n                                            int result) {\n    int next_entry_result = _anjay_input_next_entry(in_ctx);\n    if (next_entry_result\n            && (!result || result == ANJAY_ERR_NOT_FOUND\n                || result == ANJAY_ERR_NOT_IMPLEMENTED)) {\n        // LwM2M Core spec 1.2.2, paragraph 6.3.3 Write Operation\n        // In a \"Write\" operation targeting an Object Instance, any optional\n        // Resources that are not supported by, or unknown to, the LwM2M\n        // Client MUST NOT be interpreted as an error by the LwM2M Client.\n        return next_entry_result;\n    }\n    return result;\n}\n\nstatic int write_single_resource_and_move_to_next_entry(\n        anjay_unlocked_t *anjay,\n        const anjay_dm_installed_object_t *obj,\n        const anjay_uri_path_t *path,\n        bool is_array,\n        anjay_unlocked_input_ctx_t *in_ctx) {\n    assert(_anjay_uri_path_has(path, ANJAY_ID_RID));\n    if (is_array || _anjay_uri_path_has(path, ANJAY_ID_RIID)) {\n        dm_log(LAZY_DEBUG,\n               _(\"cannot write \") \"%s\" _(\" because the path does not point \"\n                                         \"inside a multiple resource\"),\n               ANJAY_DEBUG_MAKE_PATH(path));\n        return ANJAY_ERR_BAD_REQUEST;\n    }\n    return return_with_moving_to_next_entry(\n            in_ctx, _anjay_dm_call_resource_write(\n                            anjay, obj, path->ids[ANJAY_ID_IID],\n                            path->ids[ANJAY_ID_RID], ANJAY_ID_INVALID, in_ctx));\n}\n\nstatic int write_multiple_resource_and_move_to_next_entry(\n        anjay_unlocked_t *anjay,\n        const anjay_dm_installed_object_t *obj,\n        const anjay_uri_path_t *first_path,\n        bool is_array,\n        anjay_unlocked_input_ctx_t *in_ctx,\n        anjay_dm_write_type_t write_type) {\n    anjay_uri_path_t path = *first_path;\n    assert(_anjay_uri_path_has(&path, ANJAY_ID_RID));\n    if (!is_array && _anjay_uri_path_leaf_is(&path, ANJAY_ID_RID)) {\n        dm_log(LAZY_DEBUG,\n               \"%s\" _(\" is a multiple resource, but the payload attempted to \"\n                      \"treat it as single\"),\n               ANJAY_DEBUG_MAKE_PATH(&path));\n        return ANJAY_ERR_BAD_REQUEST;\n    }\n\n    int result = 0;\n\n    if ((write_type != ANJAY_DM_WRITE_TYPE_UPDATE\n         && (result = _anjay_dm_call_resource_reset(anjay, obj,\n                                                    path.ids[ANJAY_ID_IID],\n                                                    path.ids[ANJAY_ID_RID])))\n            || !_anjay_uri_path_leaf_is(&path, ANJAY_ID_RIID)) {\n        return return_with_moving_to_next_entry(in_ctx, result);\n    }\n\n    while (!result) {\n        if ((result = _anjay_dm_call_resource_write(\n                     anjay, obj, path.ids[ANJAY_ID_IID], path.ids[ANJAY_ID_RID],\n                     path.ids[ANJAY_ID_RIID], in_ctx))) {\n            return return_with_moving_to_next_entry(in_ctx, result);\n        }\n        if ((result = _anjay_input_next_entry(in_ctx))) {\n            break;\n        }\n        if ((result = _anjay_input_get_path(in_ctx, &path, NULL))) {\n            if (result == ANJAY_GET_PATH_END) {\n                result = 0;\n            }\n            break;\n        }\n        if (path.ids[ANJAY_ID_IID] != first_path->ids[ANJAY_ID_IID]\n                || path.ids[ANJAY_ID_RID] != first_path->ids[ANJAY_ID_RID]\n                || !_anjay_uri_path_leaf_is(&path, ANJAY_ID_RIID)) {\n            break;\n        }\n    }\n    return result;\n}\n\nstatic int\nwrite_resource_and_move_to_next_entry(anjay_unlocked_t *anjay,\n                                      const anjay_dm_installed_object_t *obj,\n                                      const anjay_uri_path_t *path,\n                                      anjay_dm_resource_kind_t kind,\n                                      bool is_array,\n                                      anjay_unlocked_input_ctx_t *in_ctx,\n                                      anjay_notify_queue_t *notify_queue,\n                                      anjay_dm_write_type_t write_type) {\n    int result;\n    if (_anjay_dm_res_kind_multiple(kind)) {\n        result = write_multiple_resource_and_move_to_next_entry(\n                anjay, obj, path, is_array, in_ctx, write_type);\n    } else {\n        result = write_single_resource_and_move_to_next_entry(anjay, obj, path,\n                                                              is_array, in_ctx);\n    }\n    if (!result && notify_queue) {\n        result = _anjay_notify_queue_resource_change(notify_queue, path);\n    }\n    return result;\n}\n\nint _anjay_dm_write_resource_and_move_to_next_entry(\n        anjay_unlocked_t *anjay,\n        const anjay_dm_installed_object_t *obj,\n        anjay_unlocked_input_ctx_t *in_ctx,\n        anjay_notify_queue_t *notify_queue) {\n    anjay_uri_path_t path;\n    bool is_array;\n    int result = _anjay_input_get_path(in_ctx, &path, &is_array);\n    if (result == ANJAY_GET_PATH_END) {\n        /* there was no header describing the resource, and that is fatal */\n        return ANJAY_ERR_BAD_REQUEST;\n    } else if (result) {\n        return result;\n    }\n\n    anjay_dm_resource_kind_t kind;\n    if ((result = preverify_resource_before_writing(anjay, obj, NULL, &path,\n                                                    true, &kind, NULL))) {\n        return return_with_moving_to_next_entry(in_ctx, result);\n    }\n\n    return write_resource_and_move_to_next_entry(anjay, obj, &path, kind,\n                                                 is_array, in_ctx, notify_queue,\n                                                 ANJAY_DM_WRITE_TYPE_REPLACE);\n}\n\n#ifdef ANJAY_WITH_LWM2M11\nstatic int write_resource_raw(anjay_unlocked_t *anjay,\n                              anjay_uri_path_t path,\n                              void *value,\n                              size_t value_size,\n                              anjay_notify_queue_t *notify_queue) {\n    const anjay_dm_installed_object_t *obj =\n            _anjay_dm_find_object_by_oid(&anjay->dm, path.ids[ANJAY_ID_OID]);\n    if (!obj) {\n        return ANJAY_ERR_NOT_FOUND;\n    }\n\n    avs_stream_inbuf_t inbuf_stream = AVS_STREAM_INBUF_STATIC_INITIALIZER;\n    avs_stream_inbuf_set_buffer(&inbuf_stream, value, value_size);\n    avs_stream_t *stream = (avs_stream_t *) &inbuf_stream;\n    anjay_input_buf_ctx_t temp_ctx = _anjay_input_buf_ctx_init(stream, &path);\n\n    int result = ANJAY_ERR_INTERNAL;\n    if (avs_is_ok(_anjay_dm_transaction_begin(anjay))) {\n        result = _anjay_dm_write_resource_and_move_to_next_entry(\n                anjay, obj, (anjay_unlocked_input_ctx_t *) &temp_ctx,\n                notify_queue);\n        result = _anjay_dm_transaction_finish(anjay, result);\n    }\n    if (result) {\n        anjay_log(DEBUG, _(\"writing to \") \"%s\" _(\" failed: \") \"%d\",\n                  ANJAY_DEBUG_MAKE_PATH(&path), result);\n    }\n    return result;\n}\n\nint _anjay_dm_write_resource_i64(anjay_unlocked_t *anjay,\n                                 anjay_uri_path_t path,\n                                 int64_t value,\n                                 anjay_notify_queue_t *notify_queue) {\n    return write_resource_raw(anjay, path, &value, sizeof(value), notify_queue);\n}\n\nint _anjay_dm_write_resource_u64(anjay_unlocked_t *anjay,\n                                 anjay_uri_path_t path,\n                                 uint64_t value,\n                                 anjay_notify_queue_t *notify_queue) {\n    return write_resource_raw(anjay, path, &value, sizeof(value), notify_queue);\n}\n#endif // ANJAY_WITH_LWM2M11\n\n/**\n * Writes to instance whose location is determined by the path extracted\n * from Input Context (@p in_ctx).\n */\nstatic int\nwrite_instance_and_move_to_next_entry(anjay_unlocked_t *anjay,\n                                      const anjay_dm_installed_object_t *obj,\n                                      anjay_iid_t iid,\n                                      anjay_unlocked_input_ctx_t *in_ctx,\n                                      anjay_notify_queue_t *notify_queue,\n                                      anjay_dm_write_type_t write_type) {\n    int result;\n    do {\n        anjay_uri_path_t path;\n        bool is_array;\n        if ((result = _anjay_input_get_path(in_ctx, &path, &is_array))) {\n            if (result == ANJAY_GET_PATH_END) {\n                result = 0;\n            }\n            break;\n        }\n        if (!_anjay_uri_path_has(&path, ANJAY_ID_IID)\n                || path.ids[ANJAY_ID_IID] != iid) {\n            /* more than one instance in the payload is not allowed */\n            return ANJAY_ERR_BAD_REQUEST;\n        }\n        anjay_dm_resource_kind_t kind;\n        if (!_anjay_uri_path_has(&path, ANJAY_ID_RID)) {\n            return _anjay_input_next_entry(in_ctx);\n        }\n        bool next_entry_called = false;\n        if (!(result = preverify_resource_before_writing(\n                      anjay, obj, NULL, &path, false, &kind, NULL))) {\n            result = write_resource_and_move_to_next_entry(anjay, obj, &path,\n                                                           kind, is_array,\n                                                           in_ctx, notify_queue,\n                                                           write_type);\n            next_entry_called = true;\n        }\n        if (result == ANJAY_ERR_NOT_FOUND\n                || result == ANJAY_ERR_NOT_IMPLEMENTED) {\n            // LwM2M Core spec 1.2.2, paragraph 6.3.3 Write Operation\n            // In a \"Write\" operation targeting an Object Instance, any optional\n            // Resources that are not supported by, or unknown to, the LwM2M\n            // Client MUST NOT be interpreted as an error by the LwM2M Client.\n            result = 0;\n        }\n        if (!next_entry_called) {\n            result = return_with_moving_to_next_entry(in_ctx, result);\n        }\n    } while (!result);\n    return result;\n}\n\nint _anjay_dm_write(anjay_unlocked_t *anjay,\n                    const anjay_dm_installed_object_t *obj,\n                    const anjay_request_t *request,\n                    anjay_ssid_t ssid,\n                    anjay_unlocked_input_ctx_t *in_ctx) {\n    dm_log(LAZY_DEBUG, _(\"Write \") \"%s\", ANJAY_DEBUG_MAKE_PATH(&request->uri));\n    if (!_anjay_uri_path_has(&request->uri, ANJAY_ID_IID)) {\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n    int result =\n            _anjay_dm_verify_instance_present(anjay, obj,\n                                              request->uri.ids[ANJAY_ID_IID]);\n    if (result) {\n        return result;\n    }\n    if (!_anjay_instance_action_allowed(anjay, &REQUEST_TO_ACTION_INFO(request,\n                                                                       ssid))) {\n        return ANJAY_ERR_UNAUTHORIZED;\n    }\n\n    anjay_notify_queue_t notify_queue = NULL;\n    anjay_dm_write_type_t write_type =\n            _anjay_dm_write_type_from_request_action(request->action);\n    if (_anjay_uri_path_leaf_is(&request->uri, ANJAY_ID_IID)) {\n        if (write_type != ANJAY_DM_WRITE_TYPE_UPDATE\n                && (result = _anjay_dm_call_instance_reset(\n                            anjay, obj, request->uri.ids[ANJAY_ID_IID]))) {\n            return result;\n        }\n        result = write_instance_and_move_to_next_entry(\n                anjay, obj, request->uri.ids[ANJAY_ID_IID], in_ctx,\n                &notify_queue, write_type);\n    } else {\n        anjay_uri_path_t path;\n        bool is_array;\n        result = _anjay_input_get_path(in_ctx, &path, &is_array);\n        if (result == ANJAY_GET_PATH_END) {\n            /* there was no header describing the resource, and that is fatal */\n            result = ANJAY_ERR_BAD_REQUEST;\n        } else if (!result) {\n            anjay_dm_resource_kind_t kind;\n            anjay_dm_resource_presence_t presence;\n            if (!(result = preverify_resource_before_writing(\n                          anjay, obj, &request->uri, &path, false, &kind,\n                          &presence))) {\n                if (_anjay_uri_path_leaf_is(&request->uri, ANJAY_ID_RID)) {\n                    result = write_resource_and_move_to_next_entry(\n                            anjay, obj, &path, kind, is_array, in_ctx,\n                            &notify_queue, write_type);\n                } else {\n                    assert(_anjay_uri_path_leaf_is(&request->uri,\n                                                   ANJAY_ID_RIID));\n#ifdef ANJAY_WITH_LWM2M11\n                    result = write_resource_instance(anjay, obj, &path,\n                                                     presence, in_ctx,\n                                                     &notify_queue, false);\n#else  // ANJAY_WITH_LWM2M11\n                    dm_log(ERROR,\n                           _(\"Write on Resource Instances is not supported in \"\n                             \"this version of Anjay\"));\n                    result = ANJAY_ERR_BAD_REQUEST;\n#endif // ANJAY_WITH_LWM2M11\n                }\n            }\n        }\n    }\n    if (!result) {\n        result = _anjay_notify_perform(anjay, ssid, &notify_queue);\n    }\n    _anjay_notify_clear_queue(&notify_queue);\n    return result;\n}\n\n#if defined(ANJAY_WITH_LWM2M11) && !defined(ANJAY_WITHOUT_COMPOSITE_OPERATIONS)\nint _anjay_dm_write_composite(anjay_unlocked_t *anjay,\n                              const anjay_request_t *request,\n                              anjay_ssid_t ssid,\n                              anjay_unlocked_input_ctx_t *in_ctx) {\n    if (_anjay_uri_path_has(&request->uri, ANJAY_ID_OID)) {\n        dm_log(DEBUG, _(\"Write Composite with Uri-Path is not allowed\"));\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n\n    anjay_notify_queue_t notify_queue = NULL;\n    anjay_uri_path_t path;\n    bool is_array;\n    int result;\n    while (!(result = _anjay_input_get_path(in_ctx, &path, &is_array))) {\n        dm_log(DEBUG, _(\"Write Composite \") \"%s\", ANJAY_DEBUG_MAKE_PATH(&path));\n        if (!_anjay_uri_path_has(&path, ANJAY_ID_RID)) {\n            dm_log(DEBUG, _(\"cannot perform Write Composite on \"\n                            \"non-resource/resource instance\"));\n            result = ANJAY_ERR_BAD_REQUEST;\n            goto finish;\n        }\n\n#    ifdef ANJAY_WITH_LWM2M_GATEWAY\n        if (_anjay_uri_path_has_prefix(&path)) {\n            dm_log(ERROR,\n                   _(\"Write Composite on End Devices DMs is not supported\"));\n            result = ANJAY_ERR_METHOD_NOT_ALLOWED;\n            goto finish;\n        }\n#    endif // ANJAY_WITH_LWM2M_GATEWAY\n\n        const anjay_dm_installed_object_t *obj =\n                _anjay_dm_find_object_by_oid(&anjay->dm,\n                                             path.ids[ANJAY_ID_OID]);\n\n        if (!obj) {\n            dm_log(DEBUG, _(\"Object not found: \") DM_LOG_PREFIX \"/%u\",\n                   DM_LOG_PREFIX_ARG(path.prefix) path.ids[ANJAY_ID_OID]);\n            result = ANJAY_ERR_NOT_FOUND;\n            goto finish;\n        }\n\n        if ((result = _anjay_dm_verify_instance_present(\n                     anjay, obj, path.ids[ANJAY_ID_IID]))) {\n            goto finish;\n        }\n\n        anjay_dm_resource_kind_t kind;\n        anjay_dm_resource_presence_t presence;\n        if (!(result = preverify_resource_before_writing(\n                      anjay, obj, NULL, &path, false, &kind, &presence))) {\n            if (_anjay_uri_path_leaf_is(&path, ANJAY_ID_RID)) {\n                result = write_resource_and_move_to_next_entry(\n                        anjay, obj, &path, kind, is_array, in_ctx,\n                        &notify_queue,\n                        _anjay_dm_write_type_from_request_action(\n                                request->action));\n            } else {\n#    ifdef ANJAY_WITH_LWM2M12\n                if (!_anjay_input_get_null(in_ctx)) {\n                    // Delete Resource Instance\n                    if (presence != ANJAY_DM_RES_ABSENT) {\n                        (void) ((result =\n                                         _anjay_dm_verify_resource_instance_present(\n                                                 anjay, obj,\n                                                 path.ids[ANJAY_ID_IID],\n                                                 path.ids[ANJAY_ID_RID],\n                                                 path.ids[ANJAY_ID_RIID]))\n                                || (result =\n                                            _anjay_dm_call_resource_instance_remove(\n                                                    anjay, obj,\n                                                    path.ids[ANJAY_ID_IID],\n                                                    path.ids[ANJAY_ID_RID],\n                                                    path.ids[ANJAY_ID_RIID])));\n                    }\n                    if (presence == ANJAY_DM_RES_ABSENT\n                            || result == ANJAY_ERR_NOT_FOUND) {\n                        // Entity did not exist, so technically removing\n                        // succeeded\n                        result = 0;\n                    } else if (!result) {\n                        result = _anjay_notify_queue_resource_change(\n                                &notify_queue, &path);\n                    }\n                } else\n#    endif // ANJAY_WITH_LWM2M12\n                {\n                    result = write_resource_instance(anjay, obj, &path,\n                                                     presence, in_ctx,\n                                                     &notify_queue, true);\n                }\n                if (!result) {\n                    result = _anjay_input_next_entry(in_ctx);\n                }\n            }\n        }\n        if (result) {\n            goto finish;\n        }\n    }\n    if (result == ANJAY_GET_PATH_END) {\n        result = 0;\n    }\nfinish:\n    if (!result) {\n        result = _anjay_notify_perform(anjay, ssid, &notify_queue);\n    }\n    _anjay_notify_clear_queue(&notify_queue);\n    return result;\n}\n#endif // defined(ANJAY_WITH_LWM2M11) &&\n       // !defined(ANJAY_WITHOUT_COMPOSITE_OPERATIONS)\n\nint _anjay_dm_write_created_instance_and_move_to_next_entry(\n        anjay_unlocked_t *anjay,\n        const anjay_dm_installed_object_t *obj,\n        anjay_iid_t iid,\n        anjay_unlocked_input_ctx_t *in_ctx) {\n    return write_instance_and_move_to_next_entry(\n            anjay, obj, iid, in_ctx, NULL,\n            _anjay_dm_write_type_from_request_action(ANJAY_ACTION_CREATE));\n}\n"
  },
  {
    "path": "src/core/dm/anjay_dm_write.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ANJAY_WRITE_CORE_H\n#define ANJAY_WRITE_CORE_H\n\n#include <anjay/core.h>\n\n#include \"../anjay_dm_core.h\"\n#include \"../anjay_io_core.h\"\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\nint _anjay_dm_write(anjay_unlocked_t *anjay,\n                    const anjay_dm_installed_object_t *obj,\n                    const anjay_request_t *request,\n                    anjay_ssid_t ssid,\n                    anjay_unlocked_input_ctx_t *in_ctx);\n\n#if defined(ANJAY_WITH_LWM2M11) && !defined(ANJAY_WITHOUT_COMPOSITE_OPERATIONS)\nint _anjay_dm_write_composite(anjay_unlocked_t *anjay,\n                              const anjay_request_t *request,\n                              anjay_ssid_t ssid,\n                              anjay_unlocked_input_ctx_t *in_ctx);\n#endif // defined(ANJAY_WITH_LWM2M11) &&\n       // !defined(ANJAY_WITHOUT_COMPOSITE_OPERATIONS)\n\n/**\n * NOTE: This function is used in one situation, that is: after LwM2M Create to\n * initialize newly created Instance with data contained within the request\n * payload (handled by input context @p in_ctx).\n *\n * Apart from that, the function has no more applications and @ref\n * _anjay_dm_write() shall be used instead.\n */\nint _anjay_dm_write_created_instance_and_move_to_next_entry(\n        anjay_unlocked_t *anjay,\n        const anjay_dm_installed_object_t *obj,\n        anjay_iid_t iid,\n        anjay_unlocked_input_ctx_t *in_ctx);\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif // ANJAY_WRITE_CORE_H\n"
  },
  {
    "path": "src/core/dm/anjay_dm_write_attrs.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#include \"anjay_dm_write_attrs.h\"\n\n#include \"../anjay_access_utils_private.h\"\n\nVISIBILITY_SOURCE_BEGIN\n\nstatic void update_oi_attrs(anjay_dm_oi_attributes_t *attrs_ptr,\n                            const anjay_request_attributes_t *request_attrs) {\n    if (request_attrs->has_min_period) {\n        attrs_ptr->min_period = request_attrs->values.common.min_period;\n    }\n    if (request_attrs->has_max_period) {\n        attrs_ptr->max_period = request_attrs->values.common.max_period;\n    }\n    if (request_attrs->has_min_eval_period) {\n        attrs_ptr->min_eval_period =\n                request_attrs->values.common.min_eval_period;\n    }\n    if (request_attrs->has_max_eval_period) {\n        attrs_ptr->max_eval_period =\n                request_attrs->values.common.max_eval_period;\n    }\n#ifdef ANJAY_WITH_LWM2M12\n    if (request_attrs->has_hqmax) {\n        attrs_ptr->hqmax = request_attrs->values.common.hqmax;\n    }\n#endif // ANJAY_WITH_LWM2M12\n#ifdef ANJAY_WITH_CON_ATTR\n    if (request_attrs->has_con) {\n        attrs_ptr->con = request_attrs->values.common.con;\n    }\n#endif\n}\n\nvoid _anjay_update_r_attrs(anjay_dm_r_attributes_t *attrs_ptr,\n                           const anjay_request_attributes_t *request_attrs) {\n    update_oi_attrs(&attrs_ptr->common, request_attrs);\n    if (request_attrs->has_greater_than) {\n        attrs_ptr->greater_than = request_attrs->values.greater_than;\n    }\n    if (request_attrs->has_less_than) {\n        attrs_ptr->less_than = request_attrs->values.less_than;\n    }\n    if (request_attrs->has_step) {\n        attrs_ptr->step = request_attrs->values.step;\n    }\n#ifdef ANJAY_WITH_LWM2M12\n    if (request_attrs->has_edge) {\n        attrs_ptr->edge = request_attrs->values.edge;\n    }\n#endif // ANJAY_WITH_LWM2M12\n}\n\nstatic bool oi_attrs_valid(const anjay_dm_oi_attributes_t *attrs) {\n    if (attrs->min_eval_period >= 0 && attrs->max_eval_period >= 0\n            && attrs->min_eval_period >= attrs->max_eval_period) {\n        dm_log(DEBUG, _(\"Attempted to set attributes that fail the 'epmin < \"\n                        \"epmax' precondition\"));\n        return false;\n    }\n    return true;\n}\n\nbool _anjay_r_attrs_valid(const anjay_dm_r_attributes_t *attrs) {\n    if (!oi_attrs_valid(&attrs->common)) {\n        return false;\n    }\n\n    double step = 0.0;\n    if (!isnan(attrs->step)) {\n        if (attrs->step < 0.0) {\n            dm_log(DEBUG, _(\"Attempted to set negative step attribute\"));\n            return false;\n        }\n        step = attrs->step;\n    }\n    if (!isnan(attrs->less_than) && !isnan(attrs->greater_than)\n            && attrs->less_than + 2 * step >= attrs->greater_than) {\n        dm_log(DEBUG, _(\"Attempted to set attributes that fail the 'lt + 2*st \"\n                        \"< gt' precondition\"));\n        return false;\n    }\n    return true;\n}\n\nbool _anjay_dm_resource_specific_request_attrs_empty(\n        const anjay_request_attributes_t *attrs) {\n    return !attrs->has_greater_than && !attrs->has_less_than && !attrs->has_step\n#ifdef ANJAY_WITH_LWM2M12\n           && !attrs->has_edge\n#endif // ANJAY_WITH_LWM2M12\n            ;\n}\n\nbool _anjay_dm_request_attrs_empty(const anjay_request_attributes_t *attrs) {\n    return !attrs->has_min_period && !attrs->has_max_period\n           && !attrs->has_min_eval_period && !attrs->has_max_eval_period\n#ifdef ANJAY_WITH_LWM2M12\n           && !attrs->has_hqmax\n#endif // ANJAY_WITH_LWM2M12\n#ifdef ANJAY_WITH_CON_ATTR\n           && !attrs->has_con\n#endif\n           && _anjay_dm_resource_specific_request_attrs_empty(attrs);\n}\n\n#ifdef ANJAY_WITH_LWM2M11\nstatic int\ndm_write_resource_instance_attrs(anjay_unlocked_t *anjay,\n                                 const anjay_dm_installed_object_t *obj,\n                                 anjay_iid_t iid,\n                                 anjay_rid_t rid,\n                                 anjay_riid_t riid,\n                                 anjay_ssid_t ssid,\n                                 const anjay_request_attributes_t *attributes) {\n    anjay_dm_r_attributes_t attrs = ANJAY_DM_R_ATTRIBUTES_EMPTY;\n    int result;\n    (void) ((result = _anjay_dm_verify_resource_instance_present(\n                     anjay, obj, iid, rid, riid))\n            || (result = _anjay_dm_call_resource_instance_read_attrs(\n                        anjay, obj, iid, rid, riid, ssid, &attrs)));\n    if (!result) {\n        _anjay_update_r_attrs(&attrs, attributes);\n        if (!_anjay_r_attrs_valid(&attrs)) {\n            result = ANJAY_ERR_BAD_REQUEST;\n        } else {\n            result = _anjay_dm_call_resource_instance_write_attrs(\n                    anjay, obj, iid, rid, riid, ssid, &attrs);\n        }\n    }\n    return result;\n}\n#endif // ANJAY_WITH_LWM2M11\n\nstatic int\ndm_write_resource_attrs(anjay_unlocked_t *anjay,\n                        const anjay_dm_installed_object_t *obj,\n                        anjay_iid_t iid,\n                        anjay_rid_t rid,\n                        anjay_ssid_t ssid,\n                        const anjay_request_attributes_t *attributes) {\n    anjay_dm_r_attributes_t attrs = ANJAY_DM_R_ATTRIBUTES_EMPTY;\n    int result;\n    (void) ((result = _anjay_dm_verify_resource_present(anjay, obj, iid, rid,\n                                                        NULL))\n            || (result = _anjay_dm_call_resource_read_attrs(\n                        anjay, obj, iid, rid, ssid, &attrs)));\n    if (!result) {\n        _anjay_update_r_attrs(&attrs, attributes);\n        if (!_anjay_r_attrs_valid(&attrs)) {\n            result = ANJAY_ERR_BAD_REQUEST;\n        } else {\n            result = _anjay_dm_call_resource_write_attrs(anjay, obj, iid, rid,\n                                                         ssid, &attrs);\n        }\n    }\n    return result;\n}\n\nstatic int\ndm_write_instance_attrs(anjay_unlocked_t *anjay,\n                        const anjay_dm_installed_object_t *obj,\n                        anjay_iid_t iid,\n                        anjay_ssid_t ssid,\n                        const anjay_request_attributes_t *attributes) {\n    anjay_dm_oi_attributes_t attrs = ANJAY_DM_OI_ATTRIBUTES_EMPTY;\n    int result = _anjay_dm_call_instance_read_default_attrs(anjay, obj, iid,\n                                                            ssid, &attrs);\n    if (!result) {\n        update_oi_attrs(&attrs, attributes);\n        if (!oi_attrs_valid(&attrs)) {\n            result = ANJAY_ERR_BAD_REQUEST;\n        } else {\n            result =\n                    _anjay_dm_call_instance_write_default_attrs(anjay, obj, iid,\n                                                                ssid, &attrs);\n        }\n    }\n    return result;\n}\n\nstatic int dm_write_object_attrs(anjay_unlocked_t *anjay,\n                                 const anjay_dm_installed_object_t *obj,\n                                 anjay_ssid_t ssid,\n                                 const anjay_request_attributes_t *attributes) {\n    anjay_dm_oi_attributes_t attrs = ANJAY_DM_OI_ATTRIBUTES_EMPTY;\n    int result =\n            _anjay_dm_call_object_read_default_attrs(anjay, obj, ssid, &attrs);\n    if (!result) {\n        update_oi_attrs(&attrs, attributes);\n        if (!oi_attrs_valid(&attrs)) {\n            result = ANJAY_ERR_BAD_REQUEST;\n        } else {\n            result = _anjay_dm_call_object_write_default_attrs(anjay, obj, ssid,\n                                                               &attrs);\n        }\n    }\n    return result;\n}\n\nint _anjay_dm_write_attributes(anjay_unlocked_t *anjay,\n                               const anjay_dm_installed_object_t *obj,\n                               const anjay_request_t *request,\n                               anjay_ssid_t ssid) {\n    dm_log(LAZY_DEBUG, _(\"Write Attributes \") \"%s\",\n           ANJAY_DEBUG_MAKE_PATH(&request->uri));\n    assert(_anjay_uri_path_has(&request->uri, ANJAY_ID_OID));\n    if (_anjay_dm_request_attrs_empty(&request->attributes)) {\n        return 0;\n    }\n    if (!_anjay_uri_path_has(&request->uri, ANJAY_ID_RID)\n            && !_anjay_dm_resource_specific_request_attrs_empty(\n                       &request->attributes)) {\n        return ANJAY_ERR_BAD_REQUEST;\n    }\n\n    int result;\n    if (_anjay_uri_path_has(&request->uri, ANJAY_ID_IID)) {\n        if (!(result = _anjay_dm_verify_instance_present(\n                      anjay, obj, request->uri.ids[ANJAY_ID_IID]))) {\n            if (!_anjay_instance_action_allowed(\n                        anjay, &REQUEST_TO_ACTION_INFO(request, ssid))) {\n                result = ANJAY_ERR_UNAUTHORIZED;\n            } else if (_anjay_uri_path_has(&request->uri, ANJAY_ID_RIID)) {\n#ifdef ANJAY_WITH_LWM2M11\n                result = dm_write_resource_instance_attrs(\n                        anjay, obj, request->uri.ids[ANJAY_ID_IID],\n                        request->uri.ids[ANJAY_ID_RID],\n                        request->uri.ids[ANJAY_ID_RIID], ssid,\n                        &request->attributes);\n#else  // ANJAY_WITH_LWM2M11\n                dm_log(ERROR,\n                       _(\"Resource Instance Attributes not supported in this \"\n                         \"version of Anjay\"));\n                return ANJAY_ERR_BAD_REQUEST;\n#endif // ANJAY_WITH_LWM2M11\n            } else if (_anjay_uri_path_has(&request->uri, ANJAY_ID_RID)) {\n                result = dm_write_resource_attrs(anjay, obj,\n                                                 request->uri.ids[ANJAY_ID_IID],\n                                                 request->uri.ids[ANJAY_ID_RID],\n                                                 ssid, &request->attributes);\n            } else {\n                result = dm_write_instance_attrs(anjay, obj,\n                                                 request->uri.ids[ANJAY_ID_IID],\n                                                 ssid, &request->attributes);\n            }\n        }\n    } else {\n        result = dm_write_object_attrs(anjay, obj, ssid, &request->attributes);\n    }\n#ifdef ANJAY_WITH_OBSERVE\n    if (!result) {\n        // verify that new attributes are \"seen\" by the observe code\n        result = _anjay_observe_notify(anjay, &request->uri, ssid, false);\n    }\n#endif // ANJAY_WITH_OBSERVE\n    return result;\n}\n"
  },
  {
    "path": "src/core/dm/anjay_dm_write_attrs.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ANJAY_WRITE_ATTRS_CORE_H\n#define ANJAY_WRITE_ATTRS_CORE_H\n\n#include <anjay/core.h>\n\n#include \"../anjay_dm_core.h\"\n#include \"../anjay_io_core.h\"\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\nbool _anjay_dm_resource_specific_request_attrs_empty(\n        const anjay_request_attributes_t *attrs);\nbool _anjay_dm_request_attrs_empty(const anjay_request_attributes_t *attrs);\n\nvoid _anjay_update_r_attrs(anjay_dm_r_attributes_t *attrs_ptr,\n                           const anjay_request_attributes_t *request_attrs);\n\nbool _anjay_r_attrs_valid(const anjay_dm_r_attributes_t *attrs);\n\nint _anjay_dm_write_attributes(anjay_unlocked_t *anjay,\n                               const anjay_dm_installed_object_t *obj,\n                               const anjay_request_t *request,\n                               anjay_ssid_t ssid);\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif // ANJAY_WRITE_ATTRS_CORE_H\n"
  },
  {
    "path": "src/core/dm/anjay_modules.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#include \"../anjay_core.h\"\n\nVISIBILITY_SOURCE_BEGIN\n\nAVS_LIST(anjay_dm_installed_module_t) *\n_anjay_dm_module_find_ptr(anjay_unlocked_t *anjay,\n                          anjay_dm_module_deleter_t *module_deleter) {\n    if (!anjay) {\n        return NULL;\n    }\n    AVS_LIST(anjay_dm_installed_module_t) *entry_ptr;\n    AVS_LIST_FOREACH_PTR(entry_ptr, &anjay->dm.modules) {\n        if ((*entry_ptr)->deleter == module_deleter) {\n            return entry_ptr;\n        }\n    }\n    return NULL;\n}\n\nint _anjay_dm_module_install(anjay_unlocked_t *anjay,\n                             anjay_dm_module_deleter_t *module_deleter,\n                             void *arg) {\n    assert(module_deleter);\n    if (_anjay_dm_module_find_ptr(anjay, module_deleter)) {\n        anjay_log(ERROR, _(\"module \") \"%p\" _(\" is already installed\"),\n                  (const void *) (uintptr_t) module_deleter);\n        return -1;\n    }\n    AVS_LIST(anjay_dm_installed_module_t) new_entry =\n            AVS_LIST_NEW_ELEMENT(anjay_dm_installed_module_t);\n    if (!new_entry) {\n        _anjay_log_oom();\n        return -1;\n    }\n    new_entry->deleter = module_deleter;\n    new_entry->arg = arg;\n    AVS_LIST_INSERT(&anjay->dm.modules, new_entry);\n    return 0;\n}\n\nint _anjay_dm_module_uninstall(anjay_unlocked_t *anjay,\n                               anjay_dm_module_deleter_t *module_deleter) {\n    AVS_LIST(anjay_dm_installed_module_t) *module_ptr =\n            _anjay_dm_module_find_ptr(anjay, module_deleter);\n    if (!module_ptr) {\n        anjay_log(ERROR, _(\"attempting to uninstall a non-installed module\"));\n        return -1;\n    }\n    module_deleter((*module_ptr)->arg);\n    AVS_LIST_DELETE(module_ptr);\n    return 0;\n}\n\nvoid *_anjay_dm_module_get_arg(anjay_unlocked_t *anjay,\n                               anjay_dm_module_deleter_t *module_deleter) {\n    AVS_LIST(anjay_dm_installed_module_t) *entry_ptr =\n            _anjay_dm_module_find_ptr(anjay, module_deleter);\n    return entry_ptr ? (*entry_ptr)->arg : NULL;\n}\n"
  },
  {
    "path": "src/core/dm/anjay_query.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#include <anjay_modules/anjay_time_defs.h>\n\n#include \"anjay_query.h\"\n\n#include \"../anjay_core.h\"\n#include \"../anjay_dm_core.h\"\n\nVISIBILITY_SOURCE_BEGIN\n\ntypedef struct {\n    anjay_ssid_t ssid;\n    anjay_iid_t out_iid;\n} find_iid_args_t;\n\nstatic int find_server_iid_handler(anjay_unlocked_t *anjay,\n                                   const anjay_dm_installed_object_t *obj,\n                                   anjay_iid_t iid,\n                                   void *args_) {\n    (void) obj;\n    find_iid_args_t *args = (find_iid_args_t *) args_;\n    int64_t ssid;\n    const anjay_uri_path_t ssid_path =\n            MAKE_RESOURCE_PATH(ANJAY_DM_OID_SERVER, iid,\n                               ANJAY_DM_RID_SERVER_SSID);\n\n    if (_anjay_dm_read_resource_i64(anjay, &ssid_path, &ssid)) {\n        return -1;\n    }\n    if (ssid == (int32_t) args->ssid) {\n        args->out_iid = iid;\n        return ANJAY_FOREACH_BREAK;\n    }\n    return 0;\n}\n\nint _anjay_find_server_iid(anjay_unlocked_t *anjay,\n                           anjay_ssid_t ssid,\n                           anjay_iid_t *out_iid) {\n    find_iid_args_t args = {\n        .ssid = ssid,\n        .out_iid = ANJAY_ID_INVALID\n    };\n\n    const anjay_dm_installed_object_t *obj =\n            _anjay_dm_find_object_by_oid(&anjay->dm, ANJAY_DM_OID_SERVER);\n    if (ssid == ANJAY_SSID_ANY || ssid == ANJAY_SSID_BOOTSTRAP\n            || _anjay_dm_foreach_instance(anjay, obj, find_server_iid_handler,\n                                          &args)\n            || args.out_iid == ANJAY_ID_INVALID) {\n        return -1;\n    }\n    *out_iid = args.out_iid;\n    return 0;\n}\n\nbool _anjay_dm_ssid_exists(anjay_unlocked_t *anjay, anjay_ssid_t ssid) {\n    assert(ssid != ANJAY_SSID_BOOTSTRAP);\n    anjay_iid_t dummy_iid;\n    return !_anjay_find_server_iid(anjay, ssid, &dummy_iid);\n}\n\nint _anjay_ssid_from_server_iid(anjay_unlocked_t *anjay,\n                                anjay_iid_t server_iid,\n                                anjay_ssid_t *out_ssid) {\n    int64_t ssid;\n    const anjay_uri_path_t ssid_path =\n            MAKE_RESOURCE_PATH(ANJAY_DM_OID_SERVER, server_iid,\n                               ANJAY_DM_RID_SERVER_SSID);\n    if (_anjay_dm_read_resource_i64(anjay, &ssid_path, &ssid)) {\n        return -1;\n    }\n    *out_ssid = (anjay_ssid_t) ssid;\n    return 0;\n}\n\nint _anjay_ssid_from_security_iid(anjay_unlocked_t *anjay,\n                                  anjay_iid_t security_iid,\n                                  uint16_t *out_ssid) {\n    assert(security_iid != ANJAY_ID_INVALID);\n    if (_anjay_is_bootstrap_security_instance(anjay, security_iid)) {\n        *out_ssid = ANJAY_SSID_BOOTSTRAP;\n        return 0;\n    }\n\n    int64_t _ssid;\n    const anjay_uri_path_t path =\n            MAKE_RESOURCE_PATH(ANJAY_DM_OID_SECURITY, security_iid,\n                               ANJAY_DM_RID_SECURITY_SSID);\n\n    if (_anjay_dm_read_resource_i64(anjay, &path, &_ssid) || _ssid <= 0\n            || _ssid > UINT16_MAX) {\n        anjay_log(ERROR, _(\"could not get Short Server ID from \") \"%s\",\n                  ANJAY_DEBUG_MAKE_PATH(&path));\n        return -1;\n    }\n\n    *out_ssid = (uint16_t) _ssid;\n    return 0;\n}\n\n#ifdef ANJAY_WITH_LWM2M11\nint _anjay_server_uri_from_security_iid(anjay_unlocked_t *anjay,\n                                        anjay_iid_t security_iid,\n                                        char *out_uri,\n                                        size_t out_size) {\n    const anjay_uri_path_t path =\n            MAKE_RESOURCE_PATH(ANJAY_DM_OID_SECURITY, security_iid,\n                               ANJAY_DM_RID_SECURITY_SERVER_URI);\n    if (_anjay_dm_read_resource_string(anjay, &path, out_uri, out_size)) {\n        anjay_log(ERROR, _(\"could not get Server URI from \") \"%s\",\n                  ANJAY_DEBUG_MAKE_PATH(&path));\n        return -1;\n    }\n    return 0;\n}\n#endif // ANJAY_WITH_LWM2M11\n\n#ifdef ANJAY_WITH_BOOTSTRAP\nbool _anjay_is_bootstrap_security_instance(anjay_unlocked_t *anjay,\n                                           anjay_iid_t security_iid) {\n    bool is_bootstrap;\n    const anjay_uri_path_t path =\n            MAKE_RESOURCE_PATH(ANJAY_DM_OID_SECURITY, security_iid,\n                               ANJAY_DM_RID_SECURITY_BOOTSTRAP);\n\n    if (_anjay_dm_read_resource_bool(anjay, &path, &is_bootstrap)\n            || !is_bootstrap) {\n        return false;\n    }\n\n    return true;\n}\n\nstatic int\nbootstrap_security_iid_find_helper(anjay_unlocked_t *anjay,\n                                   const anjay_dm_installed_object_t *obj,\n                                   anjay_iid_t iid,\n                                   void *result_ptr) {\n    (void) obj;\n    if (_anjay_is_bootstrap_security_instance(anjay, iid)) {\n        *(anjay_iid_t *) result_ptr = iid;\n        return ANJAY_FOREACH_BREAK;\n    }\n    return ANJAY_FOREACH_CONTINUE;\n}\n\nanjay_iid_t _anjay_find_bootstrap_security_iid(anjay_unlocked_t *anjay) {\n    anjay_iid_t result = ANJAY_ID_INVALID;\n    const anjay_dm_installed_object_t *obj =\n            _anjay_dm_find_object_by_oid(&anjay->dm, ANJAY_DM_OID_SECURITY);\n    if (_anjay_dm_foreach_instance(\n                anjay, obj, bootstrap_security_iid_find_helper, &result)) {\n        return ANJAY_ID_INVALID;\n    }\n    return result;\n}\n#endif\n\navs_time_duration_t\n_anjay_disable_timeout_from_server_iid(anjay_unlocked_t *anjay,\n                                       anjay_iid_t server_iid) {\n    static const int32_t DEFAULT_DISABLE_TIMEOUT_S = NUM_SECONDS_IN_A_DAY;\n\n    int64_t timeout_s;\n    const anjay_uri_path_t path =\n            MAKE_RESOURCE_PATH(ANJAY_DM_OID_SERVER, server_iid,\n                               ANJAY_DM_RID_SERVER_DISABLE_TIMEOUT);\n\n    if (_anjay_dm_read_resource_i64(anjay, &path, &timeout_s)\n            || timeout_s < 0) {\n        timeout_s = DEFAULT_DISABLE_TIMEOUT_S;\n    } else if (timeout_s > INT32_MAX) {\n        timeout_s = INT32_MAX;\n    }\n\n    return avs_time_duration_from_scalar(timeout_s, AVS_TIME_S);\n}\n"
  },
  {
    "path": "src/core/dm/anjay_query.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ANJAY_DM_QUERY_H\n#define ANJAY_DM_QUERY_H\n\n#include <anjay_modules/anjay_utils_core.h>\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\nint _anjay_find_server_iid(anjay_unlocked_t *anjay,\n                           anjay_ssid_t ssid,\n                           anjay_iid_t *out_iid);\n\nint _anjay_ssid_from_server_iid(anjay_unlocked_t *anjay,\n                                anjay_iid_t server_iid,\n                                anjay_ssid_t *out_ssid);\n\n#ifdef ANJAY_WITH_BOOTSTRAP\nbool _anjay_is_bootstrap_security_instance(anjay_unlocked_t *anjay,\n                                           anjay_iid_t security_iid);\n\nanjay_iid_t _anjay_find_bootstrap_security_iid(anjay_unlocked_t *anjay);\n#else\n#    define _anjay_is_bootstrap_security_instance(...) (false)\n#    define _anjay_find_bootstrap_security_iid(...) ANJAY_ID_INVALID\n#endif\n\navs_time_duration_t\n_anjay_disable_timeout_from_server_iid(anjay_unlocked_t *anjay,\n                                       anjay_iid_t server_iid);\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif // ANJAY_DM_QUERY_H\n"
  },
  {
    "path": "src/core/downloader/anjay_coap.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#ifdef ANJAY_WITH_COAP_DOWNLOAD\n\n#    ifndef ANJAY_WITH_DOWNLOADER\n#        error \"ANJAY_WITH_COAP_DOWNLOAD requires ANJAY_WITH_DOWNLOADER to be enabled\"\n#    endif // ANJAY_WITH_DOWNLOADER\n\n#    include <inttypes.h>\n\n#    include <avsystem/commons/avs_errno.h>\n#    include <avsystem/commons/avs_utils.h>\n\n#    include <avsystem/coap/avs_coap_config.h>\n#    include <avsystem/coap/coap.h>\n\n#    include <avsystem/commons/avs_shared_buffer.h>\n\n#    define ANJAY_DOWNLOADER_INTERNALS\n\n#    include \"anjay_private.h\"\n\nVISIBILITY_SOURCE_BEGIN\n\nAVS_STATIC_ASSERT(offsetof(anjay_etag_t, value)\n                          == offsetof(avs_coap_etag_t, bytes),\n                  coap_etag_layout_compatible);\nAVS_STATIC_ASSERT(AVS_ALIGNOF(anjay_etag_t) == AVS_ALIGNOF(avs_coap_etag_t),\n                  coap_etag_alignment_compatible);\n\ntypedef struct {\n    anjay_download_ctx_common_t common;\n\n    anjay_socket_transport_t transport;\n    anjay_url_t uri;\n    size_t bytes_downloaded;\n    size_t initial_block_size;\n    avs_coap_etag_t etag;\n\n    avs_net_socket_t *socket;\n    avs_net_resolved_endpoint_t preferred_endpoint;\n    char dtls_session_buffer[ANJAY_DTLS_SESSION_BUFFER_SIZE];\n\n    avs_coap_exchange_id_t exchange_id;\n    union {\n#    ifdef WITH_AVS_COAP_UDP\n        struct {\n            avs_coap_udp_tx_params_t tx_params;\n        } udp;\n#    endif // WITH_AVS_COAP_UDP\n#    ifdef WITH_AVS_COAP_TCP\n        struct {\n            avs_time_duration_t request_timeout;\n        } tcp;\n#    endif // WITH_AVS_COAP_TCP\n    } protocol;\n    avs_coap_ctx_t *coap;\n\n    avs_sched_handle_t job_start;\n    bool aborting;\n    bool reconnecting;\n    bool retry_in_progress;\n    size_t coap_downloader_retry_count;\n    size_t retry_count;\n    avs_time_duration_t coap_downloader_retry_delay;\n} anjay_coap_download_ctx_t;\n\ntypedef struct {\n    avs_coap_ctx_t *coap_ctx;\n    avs_net_socket_t *socket;\n} cleanup_coap_context_args_t;\n\nstatic void suspend_coap_transfer(anjay_download_ctx_t *ctx_);\nstatic avs_error_t sched_reconnect(anjay_coap_download_ctx_t *ctx);\n\nstatic void cleanup_coap_context_unlocked(anjay_unlocked_t *anjay,\n                                          cleanup_coap_context_args_t args) {\n    _anjay_coap_ctx_cleanup(anjay, &args.coap_ctx);\n#    ifndef ANJAY_TEST\n    _anjay_socket_cleanup(anjay, &args.socket);\n#    endif // ANJAY_TEST\n}\n\nstatic void cleanup_coap_context(avs_sched_t *sched, const void *args) {\n    anjay_t *anjay_locked = _anjay_get_from_sched(sched);\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    cleanup_coap_context_unlocked(anjay,\n                                  *(const cleanup_coap_context_args_t *) args);\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n}\n\nstatic void cleanup_coap_transfer(AVS_LIST(anjay_download_ctx_t) *ctx_ptr) {\n    anjay_coap_download_ctx_t *ctx = (anjay_coap_download_ctx_t *) *ctx_ptr;\n    avs_sched_del(&ctx->job_start);\n    avs_sched_del(&ctx->common.reconnect_job_handle);\n    _anjay_url_cleanup(&ctx->uri);\n\n    if (ctx->common.same_socket_download) {\n        // nothing more to cleanup here - both CoAP ctx and socket are\n        // maintained by the upper layers\n        ctx->aborting = true;\n        avs_coap_exchange_cancel(ctx->coap, ctx->exchange_id);\n        AVS_LIST_DELETE(ctx_ptr);\n        return;\n    }\n    anjay_unlocked_t *anjay = _anjay_downloader_get_anjay(ctx->common.dl);\n\n    const cleanup_coap_context_args_t args = {\n        .coap_ctx = ctx->coap,\n        .socket = ctx->socket\n    };\n    if (ctx->coap) {\n        ctx->aborting = true;\n        /**\n         * HACK: this is necessary, because if the download is canceled\n         * externally, cleanup_coap_context() would be called after \"ctx_ptr\" is\n         * freed. The problem is: cleanup_coap_context() leads to exchange\n         * cancelation, which calls handle_coap_response, and that'd use already\n         * freed memory (i.e. from \"ctx_ptr\"). It's also non-trivial to move the\n         * AVS_LIST_DELETE(ctx_ptr) to cleanup_coap_context().\n         */\n        avs_coap_exchange_cancel(ctx->coap, ctx->exchange_id);\n        /**\n         * HACK: this is necessary, because CoAP context may be destroyed while\n         * handling a response, and when the control returns, it may access some\n         * of its internal fields.\n         */\n        if (!anjay->sched\n                || AVS_SCHED_NOW(anjay->sched, NULL, cleanup_coap_context,\n                                 &args, sizeof(args))) {\n            cleanup_coap_context_unlocked(NULL, args);\n        }\n    } else {\n#    ifndef ANJAY_TEST\n        /**\n         * HACK: if download is aborted between sched_reconnect() call and\n         * reconnect_job() execution coap context may not exist, in this case we\n         * need to cleanup socket here.\n         */\n        _anjay_socket_cleanup(anjay, &ctx->socket);\n#    endif // ANJAY_TEST\n    }\n    AVS_LIST_DELETE(ctx_ptr);\n}\n\nstatic int read_etag(const avs_coap_response_header_t *hdr,\n                     avs_coap_etag_t *out_etag) {\n    switch (avs_coap_options_get_etag(&hdr->options, out_etag)) {\n    case 0:\n        break;\n    case AVS_COAP_OPTION_MISSING:\n        dl_log(TRACE, _(\"no ETag option\"));\n        return 0;\n    default:\n        dl_log(DEBUG, _(\"invalid ETag option size\"));\n        return -1;\n    }\n\n    dl_log(TRACE, _(\"ETag: \") \"%s\", AVS_COAP_ETAG_HEX(out_etag));\n    return 0;\n}\n\nstatic inline bool etag_matches(const avs_coap_etag_t *a,\n                                const avs_coap_etag_t *b) {\n    return a->size == b->size && !memcmp(a->bytes, b->bytes, a->size);\n}\n\nstatic void abort_download_transfer(anjay_coap_download_ctx_t *dl_ctx,\n                                    anjay_download_status_t status) {\n    if (dl_ctx->aborting) {\n        return;\n    }\n    // avoid all kinds of situations in which abort_download_transfer() may be\n    // called more than once which would lead to use-after-free.\n    dl_ctx->aborting = true;\n\n    avs_coap_exchange_cancel(dl_ctx->coap, dl_ctx->exchange_id);\n    assert(!avs_coap_exchange_id_valid(dl_ctx->exchange_id));\n\n    AVS_LIST(anjay_download_ctx_t) *dl_ctx_ptr =\n            _anjay_downloader_find_ctx_ptr_by_id(dl_ctx->common.dl,\n                                                 dl_ctx->common.id);\n    if (dl_ctx_ptr) {\n        _anjay_downloader_abort_transfer(dl_ctx_ptr, status);\n    }\n}\n\nstatic void\nhandle_coap_response(avs_coap_ctx_t *ctx,\n                     avs_coap_exchange_id_t id,\n                     avs_coap_client_request_state_t result,\n                     const avs_coap_client_async_response_t *response,\n                     avs_error_t err,\n                     void *arg) {\n    (void) ctx;\n    anjay_coap_download_ctx_t *dl_ctx = (anjay_coap_download_ctx_t *) arg;\n\n    assert(dl_ctx->exchange_id.value == id.value);\n    (void) id;\n    if (result != AVS_COAP_CLIENT_REQUEST_PARTIAL_CONTENT) {\n        // The exchange is being finished one way or another, so let's set the\n        // exchange_id field so that it can be used to check if there is an\n        // ongoing exchange or not (it is checked in suspend_coap_transfer()\n        // and reconnect_coap_transfer()).\n        dl_ctx->exchange_id = AVS_COAP_EXCHANGE_ID_INVALID;\n    }\n\n    switch (result) {\n    case AVS_COAP_CLIENT_REQUEST_OK:\n    case AVS_COAP_CLIENT_REQUEST_PARTIAL_CONTENT: {\n        const uint8_t code = response->header.code;\n        if (code != AVS_COAP_CODE_CONTENT) {\n            dl_log(DEBUG,\n                   _(\"server responded with \") \"%s\" _(\" (expected \") \"%s\" _(\n                           \")\"),\n                   AVS_COAP_CODE_STRING(code),\n                   AVS_COAP_CODE_STRING(AVS_COAP_CODE_CONTENT));\n            abort_download_transfer(\n                    dl_ctx, _anjay_download_status_invalid_response(code));\n            return;\n        }\n        avs_coap_etag_t etag;\n        if (read_etag(&response->header, &etag)) {\n            dl_log(DEBUG, _(\"could not parse CoAP response\"));\n            abort_download_transfer(dl_ctx,\n                                    _anjay_download_status_failed(\n                                            avs_errno(AVS_EPROTO)));\n            return;\n        }\n        // NOTE: avs_coap normally performs ETag validation for blockwise\n        // transfers. However, if we resumed the download from persistence\n        // information, avs_coap wouldn't know about the ETag used before, and\n        // would blindly accept any ETag.\n        if (dl_ctx->etag.size == 0) {\n            dl_ctx->etag = etag;\n        } else if (!etag_matches(&dl_ctx->etag, &etag)) {\n            dl_log(DEBUG, _(\"remote resource expired, aborting download\"));\n            abort_download_transfer(dl_ctx, _anjay_download_status_expired());\n            return;\n        }\n        assert(dl_ctx->bytes_downloaded == response->payload_offset);\n        if (avs_is_err((err = _anjay_downloader_call_on_next_block(\n                                &dl_ctx->common,\n                                (const uint8_t *) response->payload,\n                                response->payload_size,\n                                etag.size > 0 ? (const anjay_etag_t *) &etag\n                                              : NULL)))) {\n            abort_download_transfer(dl_ctx, _anjay_download_status_failed(err));\n            return;\n        }\n        if (dl_ctx->bytes_downloaded == response->payload_offset) {\n            dl_ctx->bytes_downloaded += response->payload_size;\n        }\n        if (result == AVS_COAP_CLIENT_REQUEST_OK) {\n            dl_log(INFO, _(\"transfer id = \") \"%\" PRIuPTR _(\" finished\"),\n                   dl_ctx->common.id);\n            abort_download_transfer(dl_ctx, _anjay_download_status_success());\n        } else {\n            dl_log(TRACE,\n                   _(\"transfer id = \") \"%\" PRIuPTR _(\": \") \"%lu\" _(\n                           \" B downloaded\"),\n                   dl_ctx->common.id, (unsigned long) dl_ctx->bytes_downloaded);\n            dl_ctx->retry_count = 0;\n        }\n        break;\n    }\n    case AVS_COAP_CLIENT_REQUEST_FAIL: {\n        dl_log(DEBUG, _(\"download failed: \") \"%s\", AVS_COAP_STRERROR(err));\n        if (err.category == AVS_COAP_ERR_CATEGORY\n                && err.code == AVS_COAP_ERR_ETAG_MISMATCH) {\n            abort_download_transfer(dl_ctx, _anjay_download_status_expired());\n        } else if (((err.category == AVS_COAP_ERR_CATEGORY\n                     && err.code == AVS_COAP_ERR_TIMEOUT)\n                    || err.category == AVS_ERRNO_CATEGORY)\n                   && dl_ctx->retry_count\n                              < dl_ctx->coap_downloader_retry_count) {\n            dl_ctx->retry_count++;\n            // shutdown the socket and cancel the exchange before reconnecting\n            suspend_coap_transfer((anjay_download_ctx_t *) dl_ctx);\n            if (dl_ctx->aborting) {\n                // suspend_coap_transfer() may abort the download\n                err = avs_errno(AVS_UNKNOWN_ERROR);\n            } else {\n                err = sched_reconnect(dl_ctx);\n            }\n            if (avs_is_err(err)) {\n                dl_log(ERROR, _(\"could not schedule download connect job\"));\n                abort_download_transfer(dl_ctx,\n                                        _anjay_download_status_failed(err));\n            }\n        } else {\n            abort_download_transfer(dl_ctx, _anjay_download_status_failed(err));\n        }\n        break;\n    }\n    case AVS_COAP_CLIENT_REQUEST_CANCEL:\n        dl_log(DEBUG, _(\"download request canceled\"));\n        if (!dl_ctx->reconnecting && !dl_ctx->retry_in_progress) {\n            abort_download_transfer(dl_ctx, _anjay_download_status_aborted());\n        }\n        break;\n    }\n}\n\nstatic void handle_coap_message(AVS_LIST(anjay_download_ctx_t) *ctx_ptr) {\n    // NOTE: The return value is ignored as there is not a lot we can do with\n    // it.\n    (void) avs_coap_async_handle_incoming_packet(\n            ((anjay_coap_download_ctx_t *) *ctx_ptr)->coap, NULL, NULL);\n}\n\nstatic avs_net_socket_t *get_coap_socket(anjay_download_ctx_t *ctx) {\n    return ((anjay_coap_download_ctx_t *) ctx)->socket;\n}\n\nstatic anjay_socket_transport_t\nget_coap_socket_transport(anjay_download_ctx_t *ctx) {\n    return ((anjay_coap_download_ctx_t *) ctx)->transport;\n}\n\n#    ifdef ANJAY_TEST\n#        include \"tests/core/socket_mock.h\"\n#    endif // ANJAY_TEST\n\nstatic void start_download_job(avs_sched_t *sched, const void *id_ptr) {\n    anjay_t *anjay_locked = _anjay_get_from_sched(sched);\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    uintptr_t id = *(const uintptr_t *) id_ptr;\n    AVS_LIST(anjay_download_ctx_t) *dl_ctx_ptr =\n            _anjay_downloader_find_ctx_ptr_by_id(&anjay->downloader, id);\n    if (!dl_ctx_ptr) {\n        dl_log(DEBUG, _(\"download id = \") \"%\" PRIuPTR _(\" expired\"), id);\n    } else {\n        anjay_coap_download_ctx_t *ctx =\n                (anjay_coap_download_ctx_t *) *dl_ctx_ptr;\n        ctx->reconnecting = false;\n        ctx->retry_in_progress = false;\n\n        avs_error_t err;\n        avs_coap_options_t options;\n        const uint8_t code = AVS_COAP_CODE_GET;\n        if (avs_is_err((err = avs_coap_options_dynamic_init(&options)))) {\n            dl_log(ERROR,\n                   _(\"download id = \") \"%\" PRIuPTR _(\n                           \" cannot start: out of memory\"),\n                   id);\n            goto end;\n        }\n\n        AVS_LIST(const anjay_string_t) elem;\n        AVS_LIST_FOREACH(elem, ctx->uri.uri_path) {\n            if (avs_is_err((err = avs_coap_options_add_string(\n                                    &options, AVS_COAP_OPTION_URI_PATH,\n                                    elem->c_str)))) {\n                goto end;\n            }\n        }\n        AVS_LIST_FOREACH(elem, ctx->uri.uri_query) {\n            if (avs_is_err((err = avs_coap_options_add_string(\n                                    &options, AVS_COAP_OPTION_URI_QUERY,\n                                    elem->c_str)))) {\n                goto end;\n            }\n        }\n\n        assert(!avs_coap_exchange_id_valid(ctx->exchange_id));\n        (void) (avs_is_err((err = avs_coap_client_send_async_request(\n                                    ctx->coap, &ctx->exchange_id,\n                                    &(avs_coap_request_header_t) {\n                                        .code = code,\n                                        .options = options\n                                    },\n                                    NULL, NULL, handle_coap_response,\n                                    (void *) ctx)))\n                || avs_is_err((\n                           err = avs_coap_client_set_next_response_payload_offset(\n                                   ctx->coap, ctx->exchange_id,\n                                   ctx->bytes_downloaded))));\n\n    end:\n        avs_coap_options_cleanup(&options);\n\n        if (avs_is_err(err)) {\n            _anjay_downloader_abort_transfer(\n                    dl_ctx_ptr, _anjay_download_status_failed(err));\n        }\n    }\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n}\n\nstatic avs_error_t reset_coap_ctx(anjay_coap_download_ctx_t *ctx) {\n    assert(!ctx->common.same_socket_download);\n    anjay_unlocked_t *anjay = _anjay_downloader_get_anjay(ctx->common.dl);\n\n    _anjay_coap_ctx_cleanup(anjay, &ctx->coap);\n    assert(!avs_coap_exchange_id_valid(ctx->exchange_id));\n\n    switch (ctx->transport) {\n#    ifdef WITH_AVS_COAP_UDP\n    case ANJAY_SOCKET_TRANSPORT_UDP:\n        // NOTE: we set udp_response_cache to NULL, because it should never be\n        // necessary. It's used to cache responses generated by us whenever we\n        // handle an incoming request, and contexts used for downloads don't\n        // expect receiving any requests that would need handling.\n        if ((ctx->coap = avs_coap_udp_ctx_create(\n                     _anjay_get_coap_sched(anjay), &ctx->protocol.udp.tx_params,\n                     anjay->in_shared_buffer, anjay->out_shared_buffer, NULL,\n                     anjay->prng_ctx.ctx))) {\n            avs_coap_set_exchange_max_time(ctx->coap,\n                                           anjay->udp_exchange_timeout);\n        }\n        break;\n#    endif // WITH_AVS_COAP_UDP\n\n#    ifdef WITH_AVS_COAP_TCP\n    case ANJAY_SOCKET_TRANSPORT_TCP:\n        if ((ctx->coap = avs_coap_tcp_ctx_create(\n                     _anjay_get_coap_sched(anjay), anjay->in_shared_buffer,\n                     anjay->out_shared_buffer, anjay->coap_tcp_max_options_size,\n                     ctx->protocol.tcp.request_timeout, anjay->prng_ctx.ctx))) {\n            avs_coap_set_exchange_max_time(ctx->coap,\n                                           anjay->tcp_exchange_timeout);\n        }\n        break;\n#    endif // WITH_AVS_COAP_TCP\n    default:\n        dl_log(ERROR,\n               _(\"anjay_coap_download_ctx_t is compatible only with \"\n                 \"ANJAY_SOCKET_TRANSPORT_UDP and \"\n                 \"ANJAY_SOCKET_TRANSPORT_TCP (if they are compiled-in)\"));\n        return avs_errno(AVS_EPROTONOSUPPORT);\n    }\n\n    if (!ctx->coap) {\n        dl_log(ERROR, _(\"could not create CoAP context\"));\n        return avs_errno(AVS_ENOMEM);\n    }\n\n    avs_error_t err = avs_coap_ctx_set_socket(ctx->coap, ctx->socket);\n    if (avs_is_err(err)) {\n        anjay_log(ERROR, _(\"could not assign socket to CoAP context\"));\n        _anjay_coap_ctx_cleanup(anjay, &ctx->coap);\n    }\n\n    return err;\n}\n\nstatic void suspend_coap_transfer(anjay_download_ctx_t *ctx_) {\n    anjay_coap_download_ctx_t *ctx = (anjay_coap_download_ctx_t *) ctx_;\n    dl_log(INFO, _(\"suspending download \") \"%\" PRIuPTR, ctx->common.id);\n    ctx->reconnecting = true;\n    ctx->retry_in_progress = false;\n    avs_sched_del(&ctx->job_start);\n    avs_sched_del(&ctx->common.reconnect_job_handle);\n    if (avs_coap_exchange_id_valid(ctx->exchange_id)) {\n        assert(ctx->coap);\n        avs_coap_exchange_cancel(ctx->coap, ctx->exchange_id);\n        assert(!avs_coap_exchange_id_valid(ctx->exchange_id));\n    }\n    if (ctx->common.same_socket_download) {\n        return;\n    }\n    avs_error_t err = avs_net_socket_shutdown(ctx->socket);\n    // not calling close because that might clean up remote hostname and\n    // port fields that will be necessary for reconnection\n    if (_anjay_socket_is_online(ctx->socket)) {\n        // avs_net_socket_shutdown() failed - suspending the transfer is not\n        // supported, let's abort it instead\n        abort_download_transfer(\n                ctx,\n                _anjay_download_status_failed(\n                        avs_is_err(err) ? err : avs_errno(AVS_UNKNOWN_ERROR)));\n    }\n}\n\nstatic avs_error_t sched_start_download(anjay_coap_download_ctx_t *ctx) {\n    anjay_unlocked_t *anjay = _anjay_downloader_get_anjay(ctx->common.dl);\n    if (AVS_SCHED_NOW(anjay->sched, &ctx->job_start, start_download_job,\n                      &ctx->common.id, sizeof(ctx->common.id))) {\n        dl_log(WARNING,\n               _(\"could not schedule download job for id = \") \"%\" PRIuPTR,\n               ctx->common.id);\n        return avs_errno(AVS_ENOMEM);\n    }\n    dl_log(INFO, _(\"scheduling download \") \"%\" PRIuPTR, ctx->common.id);\n    return AVS_OK;\n}\n\nstatic avs_error_t\nreconnect_coap_transfer(AVS_LIST(anjay_download_ctx_t) *ctx_ptr) {\n    anjay_coap_download_ctx_t *ctx = (anjay_coap_download_ctx_t *) *ctx_ptr;\n    ctx->reconnecting = true;\n\n    if (ctx->common.same_socket_download) {\n        // Cancel the exchange and schedule the download to let\n        // the Registration be sent even if NSTART=1.\n        avs_coap_exchange_cancel(ctx->coap, ctx->exchange_id);\n        assert(!avs_coap_exchange_id_valid(ctx->exchange_id));\n        return sched_start_download(ctx);\n    }\n\n    avs_net_socket_shutdown(ctx->socket);\n    avs_net_socket_close(ctx->socket);\n    avs_error_t err =\n            avs_net_socket_connect(ctx->socket, ctx->uri.host, ctx->uri.port);\n    if (avs_is_err(err)) {\n        dl_log(WARNING,\n               _(\"could not connect socket for download id = \") \"%\" PRIuPTR,\n               ctx->common.id);\n        if (ctx->retry_count < ctx->coap_downloader_retry_count) {\n            // Retry the download, err is reset to AVS_OK if sched_reconnect\n            // succeeds\n            ctx->retry_count++;\n            err = sched_reconnect(ctx);\n        }\n    } else {\n        // A new DTLS session requires resetting the CoAP context.\n        // If we manage to resume the session, we can simply continue sending\n        // retransmissions as if nothing happened.\n        if ((!ctx->coap || !_anjay_was_session_resumed(ctx->socket))\n                && avs_is_err((err = reset_coap_ctx(ctx)))) {\n            return err;\n        }\n        if (!avs_coap_exchange_id_valid(ctx->exchange_id)) {\n            err = sched_start_download(ctx);\n        }\n    }\n    return err;\n}\n\nstatic avs_error_t sched_reconnect(anjay_coap_download_ctx_t *ctx) {\n    anjay_unlocked_t *anjay = _anjay_downloader_get_anjay(ctx->common.dl);\n    ctx->retry_in_progress = true;\n\n    dl_log(INFO,\n           _(\"retrying download \") \"%\" PRIuPTR _(\", attempt number \") \"%zu\" _(\n                   \", with delay \") \"%s\",\n           ctx->common.id, ctx->retry_count,\n           AVS_TIME_DURATION_AS_STRING(ctx->coap_downloader_retry_delay));\n\n    if (ctx->common.reconnect_job_handle) {\n        return AVS_OK;\n    }\n\n    if (AVS_SCHED_DELAYED(anjay->sched, &ctx->common.reconnect_job_handle,\n                          ctx->coap_downloader_retry_delay,\n                          _anjay_downloader_reconnect_job, &ctx->common.id,\n                          sizeof(ctx->common.id))) {\n        dl_log(WARNING,\n               _(\"could not schedule reconnect job for id = \") \"%\" PRIuPTR,\n               ctx->common.id);\n        return avs_errno(AVS_ENOMEM);\n    }\n\n    dl_log(DEBUG, _(\"scheduling reconnect \") \"%\" PRIuPTR, ctx->common.id);\n    return AVS_OK;\n}\n\nstatic avs_error_t set_next_coap_block_offset(anjay_download_ctx_t *ctx_,\n                                              size_t next_block_offset) {\n    anjay_coap_download_ctx_t *ctx = (anjay_coap_download_ctx_t *) ctx_;\n    avs_error_t err = AVS_OK;\n    if (avs_coap_exchange_id_valid(ctx->exchange_id)) {\n        err = avs_coap_client_set_next_response_payload_offset(\n                ctx->coap, ctx->exchange_id, next_block_offset);\n    }\n    if (avs_is_ok(err)) {\n        ctx->bytes_downloaded = next_block_offset;\n    }\n    return err;\n}\n\nstatic bool is_socket_online_or_retry_in_progress(anjay_download_ctx_t *ctx_) {\n    anjay_coap_download_ctx_t *ctx = (anjay_coap_download_ctx_t *) ctx_;\n    if (ctx->retry_in_progress) {\n        return true;\n    } else {\n        return _anjay_socket_is_online(ctx->socket);\n    }\n}\n\navs_error_t\n_anjay_downloader_coap_ctx_new(anjay_downloader_t *dl,\n                               AVS_LIST(anjay_download_ctx_t) *out_dl_ctx,\n                               const anjay_download_config_t *cfg,\n                               uintptr_t id,\n                               avs_coap_ctx_t *forced_coap_ctx,\n                               avs_net_socket_t *forced_coap_socket) {\n    anjay_unlocked_t *anjay = _anjay_downloader_get_anjay(dl);\n    assert(!*out_dl_ctx);\n    AVS_LIST(anjay_coap_download_ctx_t) ctx =\n            AVS_LIST_NEW_ELEMENT(anjay_coap_download_ctx_t);\n    if (!ctx) {\n        _anjay_log_oom();\n        return avs_errno(AVS_ENOMEM);\n    }\n    if (forced_coap_ctx) {\n        ctx->common.same_socket_download = true;\n        ctx->coap = forced_coap_ctx;\n        ctx->socket = forced_coap_socket;\n    }\n\n    avs_net_ssl_configuration_t ssl_config;\n    avs_error_t err = AVS_OK;\n    static const anjay_download_ctx_vtable_t VTABLE = {\n        .get_socket = get_coap_socket,\n        .get_socket_transport = get_coap_socket_transport,\n        .handle_packet = handle_coap_message,\n        .cleanup = cleanup_coap_transfer,\n        .suspend = suspend_coap_transfer,\n        .reconnect = reconnect_coap_transfer,\n        .set_next_block_offset = set_next_coap_block_offset,\n        .is_socket_online_or_retry_in_progress =\n                is_socket_online_or_retry_in_progress\n    };\n    ctx->common.vtable = &VTABLE;\n    ctx->coap_downloader_retry_count = anjay->coap_downloader_retry_count;\n    ctx->coap_downloader_retry_delay = anjay->coap_downloader_retry_delay;\n\n    const anjay_transport_info_t *transport_info =\n            _anjay_transport_info_by_uri_scheme(cfg->url);\n    if (!transport_info || _anjay_url_parse(cfg->url, &ctx->uri)) {\n        dl_log(ERROR, _(\"invalid URL: \") \"%s\", cfg->url);\n        err = avs_errno(AVS_EINVAL);\n        goto error;\n    }\n    ctx->transport = transport_info->transport;\n\n    if (cfg->etag && cfg->etag->size > sizeof(ctx->etag.bytes)) {\n        dl_log(ERROR, _(\"ETag too long\"));\n        err = avs_errno(AVS_EPROTO);\n        goto error;\n    }\n\n    if (!cfg->on_next_block || !cfg->on_download_finished) {\n        dl_log(ERROR, _(\"invalid download config: handlers not set up\"));\n        err = avs_errno(AVS_EINVAL);\n        goto error;\n    }\n\n    if (!ctx->common.same_socket_download) {\n        ssl_config = (avs_net_ssl_configuration_t) {\n            .version = anjay->dtls_version,\n            .security = cfg->security_config.security_info,\n            .session_resumption_buffer = ctx->dtls_session_buffer,\n            .session_resumption_buffer_size = sizeof(ctx->dtls_session_buffer),\n            .ciphersuites = cfg->security_config.tls_ciphersuites.num_ids\n                                    ? cfg->security_config.tls_ciphersuites\n                                    : anjay->default_tls_ciphersuites,\n            .backend_configuration = anjay->socket_config,\n            .prng_ctx = anjay->prng_ctx.ctx,\n            .use_connection_id = anjay->use_connection_id,\n#    ifdef ANJAY_WITH_LWM2M11\n            .server_name_indication =\n                    cfg->security_config.server_name_indication,\n#    endif // ANJAY_WITH_LWM2M11\n        };\n        ssl_config.backend_configuration.reuse_addr = 1;\n        ssl_config.backend_configuration.preferred_endpoint =\n                &ctx->preferred_endpoint;\n\n        if (!transport_info->socket_type) {\n            dl_log(ERROR,\n                   _(\"URI scheme \") \"%s\" _(\n                           \" uses a non-IP transport, which is not \"\n                           \"supported for downloads\"),\n                   transport_info->uri_scheme);\n            err = avs_errno(AVS_EPROTONOSUPPORT);\n            goto error;\n        }\n\n        assert(transport_info->security != ANJAY_TRANSPORT_SECURITY_UNDEFINED);\n\n        // Downloader sockets MUST NOT reuse the same local port as LwM2M\n        // sockets. If they do, and the client attempts to download anything\n        // from the same host:port as is used by an LwM2M server, we will get\n        // two sockets with identical local/remote host/port tuples. Depending\n        // on the socket implementation, we may not be able to create such\n        // socket, packets might get duplicated between these \"identical\"\n        // sockets, or we may get some kind of load-balancing behavior. In the\n        // last case, the client would randomly handle or ignore LwM2M requests\n        // and CoAP download responses.\n        switch (*transport_info->socket_type) {\n        case AVS_NET_TCP_SOCKET:\n            err = avs_net_tcp_socket_create(&ctx->socket,\n                                            &ssl_config.backend_configuration);\n            break;\n        case AVS_NET_UDP_SOCKET:\n            err = avs_net_udp_socket_create(&ctx->socket,\n                                            &ssl_config.backend_configuration);\n            break;\n        case AVS_NET_SSL_SOCKET:\n            err = avs_net_ssl_socket_create(&ctx->socket, &ssl_config);\n            break;\n        case AVS_NET_DTLS_SOCKET:\n            err = avs_net_dtls_socket_create(&ctx->socket, &ssl_config);\n            break;\n        default:\n            err = avs_errno(AVS_EPROTONOSUPPORT);\n            break;\n        }\n        if (avs_is_err(err)) {\n            dl_log(ERROR, _(\"could not create CoAP socket\"));\n        } else if (cfg->security_config.dane_tlsa_record\n                   && avs_is_err((\n                              err = avs_net_socket_set_opt(\n                                      ctx->socket,\n                                      AVS_NET_SOCKET_OPT_DANE_TLSA_ARRAY,\n                                      (avs_net_socket_opt_value_t) {\n                                          .dane_tlsa_array = {\n                                              .array_ptr =\n                                                      cfg->security_config\n                                                              .dane_tlsa_record,\n                                              .array_element_count = 1\n                                          }\n                                      })))) {\n            anjay_log(ERROR, _(\"could not configure DANE TLSA record\"));\n            _anjay_socket_cleanup(anjay, &ctx->socket);\n        }\n        if (!ctx->socket) {\n            assert(avs_is_err(err));\n            dl_log(ERROR, _(\"could not create CoAP socket\"));\n            goto error;\n        }\n    }\n\n    ctx->common.dl = dl;\n    ctx->common.id = id;\n    ctx->common.on_next_block = cfg->on_next_block;\n    ctx->common.on_download_finished = cfg->on_download_finished;\n    ctx->common.user_data = cfg->user_data;\n    ctx->bytes_downloaded = cfg->start_offset;\n\n    if (cfg->etag) {\n        ctx->etag.size = cfg->etag->size;\n        memcpy(ctx->etag.bytes, cfg->etag->value, ctx->etag.size);\n    }\n\n#    ifdef WITH_AVS_COAP_UDP\n    if (ctx->transport == ANJAY_SOCKET_TRANSPORT_UDP) {\n        if (!cfg->coap_tx_params) {\n            ctx->protocol.udp.tx_params = anjay->udp_tx_params;\n        } else {\n            const char *error_string = NULL;\n            if (avs_coap_udp_tx_params_valid(cfg->coap_tx_params,\n                                             &error_string)) {\n                ctx->protocol.udp.tx_params = *cfg->coap_tx_params;\n            } else {\n                dl_log(ERROR, _(\"invalid tx_params: \") \"%s\", error_string);\n                goto error;\n            }\n        }\n    }\n#    endif // WITH_AVS_COAP_UDP\n\n#    ifdef WITH_AVS_COAP_TCP\n    if (ctx->transport == ANJAY_SOCKET_TRANSPORT_TCP) {\n        if (avs_time_duration_less(AVS_TIME_DURATION_ZERO,\n                                   cfg->tcp_request_timeout)) {\n            ctx->protocol.tcp.request_timeout = cfg->tcp_request_timeout;\n        } else {\n            ctx->protocol.tcp.request_timeout = anjay->coap_tcp_request_timeout;\n        }\n    }\n#    endif // WITH_AVS_COAP_TCP\n\n    if (_anjay_downloader_sched_reconnect_ctx((anjay_download_ctx_t *) ctx)) {\n        dl_log(ERROR, _(\"could not schedule download connect job\"));\n        err = avs_errno(AVS_ENOMEM);\n        goto error;\n    }\n\n    *out_dl_ctx = (AVS_LIST(anjay_download_ctx_t)) ctx;\n    return AVS_OK;\nerror:\n    cleanup_coap_transfer((AVS_LIST(anjay_download_ctx_t) *) &ctx);\n    return err;\n}\n\n#    ifdef ANJAY_TEST\n#        include \"tests/core/downloader/downloader.c\"\n#    endif // ANJAY_TEST\n\n#endif // ANJAY_WITH_COAP_DOWNLOAD\n"
  },
  {
    "path": "src/core/downloader/anjay_downloader.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#ifdef ANJAY_WITH_DOWNLOADER\n\n#    include <inttypes.h>\n\n#    include <avsystem/commons/avs_errno.h>\n#    include <avsystem/commons/avs_memory.h>\n#    include <avsystem/commons/avs_utils.h>\n\n#    include \"../anjay_core.h\"\n#    include \"../anjay_downloader.h\"\n\n#    define ANJAY_DOWNLOADER_INTERNALS\n\n#    include \"anjay_private.h\"\n\n#    define INVALID_DOWNLOAD_ID ((uintptr_t) NULL)\n\nVISIBILITY_SOURCE_BEGIN\n\nstruct anjay_download_ctx {\n    anjay_download_ctx_common_t common;\n};\n\nint _anjay_downloader_init(anjay_downloader_t *dl, anjay_unlocked_t *anjay) {\n    assert(anjay);\n    assert(_anjay_downloader_get_anjay(dl) == anjay);\n    assert(_anjay_downloader_get_anjay(dl)->sched);\n\n    if (!anjay || anjay != _anjay_downloader_get_anjay(dl) || !anjay->sched) {\n        dl_log(ERROR, _(\"invalid anjay pointer passed\"));\n        return -1;\n    }\n\n    *dl = (anjay_downloader_t) {\n        .next_id = 1,\n        .downloads = NULL,\n    };\n    return 0;\n}\n\nstatic void cleanup_transfer(AVS_LIST(anjay_download_ctx_t) *ctx_ptr) {\n    assert(ctx_ptr);\n    assert(*ctx_ptr);\n    assert((*ctx_ptr)->common.vtable);\n\n    (*ctx_ptr)->common.vtable->cleanup(ctx_ptr);\n}\n\navs_error_t\n_anjay_downloader_call_on_next_block(anjay_download_ctx_common_t *ctx,\n                                     const uint8_t *data,\n                                     size_t data_size,\n                                     const anjay_etag_t *etag) {\n    anjay_unlocked_t *anjay = _anjay_downloader_get_anjay(ctx->dl);\n    anjay_download_next_block_handler_t *handler = ctx->on_next_block;\n    void *user_data = ctx->user_data;\n    assert(handler);\n\n    avs_error_t err = avs_errno(AVS_EINVAL);\n    (void) err;\n    ANJAY_MUTEX_UNLOCK_FOR_CALLBACK(anjay_locked, anjay);\n    err = handler(anjay_locked, data, data_size, etag, user_data);\n    ANJAY_MUTEX_LOCK_AFTER_CALLBACK(anjay_locked);\n    return err;\n}\n\nstatic void call_on_download_finished(anjay_download_ctx_t *ctx,\n                                      anjay_download_status_t status) {\n    anjay_unlocked_t *anjay = _anjay_downloader_get_anjay(ctx->common.dl);\n    anjay_download_finished_handler_t *handler =\n            ctx->common.on_download_finished;\n    void *user_data = ctx->common.user_data;\n    assert(handler);\n\n    ANJAY_MUTEX_UNLOCK_FOR_CALLBACK(anjay_locked, anjay);\n    handler(anjay_locked, status, user_data);\n    ANJAY_MUTEX_LOCK_AFTER_CALLBACK(anjay_locked);\n}\n\nvoid _anjay_downloader_abort_transfer(AVS_LIST(anjay_download_ctx_t) *ctx_ptr,\n                                      anjay_download_status_t status) {\n    assert(ctx_ptr);\n    assert(*ctx_ptr);\n\n    switch (status.result) {\n    case ANJAY_DOWNLOAD_FINISHED:\n        dl_log(TRACE,\n               _(\"aborting download id = \") \"%\" PRIuPTR _(\n                       \": finished successfully\"),\n               (*ctx_ptr)->common.id);\n        break;\n    case ANJAY_DOWNLOAD_ERR_FAILED:\n        dl_log(TRACE,\n               _(\"aborting download id = \") \"%\" PRIuPTR _(\n                       \": failed, error: \") \"%s\",\n               (*ctx_ptr)->common.id, AVS_COAP_STRERROR(status.details.error));\n        break;\n    case ANJAY_DOWNLOAD_ERR_INVALID_RESPONSE:\n        dl_log(TRACE,\n               _(\"aborting download id = \") \"%\" PRIuPTR _(\n                       \": invalid response, status code: \") \"%d\",\n               (*ctx_ptr)->common.id, status.details.status_code);\n        break;\n    case ANJAY_DOWNLOAD_ERR_EXPIRED:\n        dl_log(TRACE, _(\"aborting download id = \") \"%\" PRIuPTR _(\": expired\"),\n               (*ctx_ptr)->common.id);\n        break;\n    case ANJAY_DOWNLOAD_ERR_ABORTED:\n        dl_log(TRACE, _(\"aborting download id = \") \"%\" PRIuPTR _(\": aborted\"),\n               (*ctx_ptr)->common.id);\n        break;\n    }\n\n    call_on_download_finished(*ctx_ptr, status);\n\n    avs_sched_del(&(*ctx_ptr)->common.reconnect_job_handle);\n    cleanup_transfer(ctx_ptr);\n}\n\nstatic void suspend_transfer(anjay_download_ctx_t *ctx) {\n    assert(ctx);\n    assert(ctx->common.vtable);\n    ctx->common.vtable->suspend(ctx);\n}\n\nstatic void reconnect_transfer(AVS_LIST(anjay_download_ctx_t) *ctx_ptr) {\n    assert(ctx_ptr);\n    assert(*ctx_ptr);\n    assert((*ctx_ptr)->common.vtable);\n\n    avs_error_t err = (*ctx_ptr)->common.vtable->reconnect(ctx_ptr);\n    if (avs_is_err(err)) {\n        _anjay_downloader_abort_transfer(ctx_ptr,\n                                         _anjay_download_status_failed(err));\n    }\n}\n\nvoid _anjay_downloader_cleanup(anjay_downloader_t *dl) {\n    assert(dl);\n    while (dl->downloads) {\n        _anjay_downloader_abort_transfer(&dl->downloads,\n                                         _anjay_download_status_aborted());\n    }\n}\n\nstatic avs_net_socket_t *get_ctx_socket(anjay_download_ctx_t *ctx) {\n    assert(ctx);\n    assert(ctx->common.vtable);\n    return ctx->common.vtable->get_socket(ctx);\n}\n\nstatic anjay_socket_transport_t\nget_ctx_socket_transport(anjay_download_ctx_t *ctx) {\n    assert(ctx);\n    assert(ctx->common.vtable);\n    return ctx->common.vtable->get_socket_transport(ctx);\n}\n\nstatic AVS_LIST(anjay_download_ctx_t) *\nfind_ctx_ptr_by_socket(anjay_downloader_t *dl, avs_net_socket_t *socket) {\n    assert(socket);\n    AVS_LIST(anjay_download_ctx_t) *ctx_ptr;\n    AVS_LIST_FOREACH_PTR(ctx_ptr, &dl->downloads) {\n        if ((*ctx_ptr)->common.same_socket_download) {\n            continue;\n        }\n        if (get_ctx_socket(*ctx_ptr) == socket) {\n            return ctx_ptr;\n        }\n    }\n    return NULL;\n}\n\nint _anjay_downloader_get_sockets(anjay_downloader_t *dl,\n                                  AVS_LIST(anjay_socket_entry_t) *out_socks,\n                                  bool include_offline) {\n    AVS_LIST(anjay_socket_entry_t) sockets = NULL;\n    AVS_LIST(anjay_download_ctx_t) dl_ctx;\n\n    AVS_LIST_FOREACH(dl_ctx, dl->downloads) {\n        if (dl_ctx->common.same_socket_download) {\n            continue;\n        }\n        avs_net_socket_t *socket = get_ctx_socket(dl_ctx);\n        if (socket && (include_offline || _anjay_socket_is_online(socket))) {\n            AVS_LIST(anjay_socket_entry_t) elem =\n                    AVS_LIST_NEW_ELEMENT(anjay_socket_entry_t);\n            if (!elem) {\n                AVS_LIST_CLEAR(&sockets);\n                return -1;\n            }\n\n            elem->socket = socket;\n            elem->transport = get_ctx_socket_transport(dl_ctx);\n            elem->ssid = ANJAY_SSID_ANY;\n            elem->queue_mode = false;\n            AVS_LIST_INSERT(&sockets, elem);\n        }\n    }\n\n    AVS_LIST_INSERT(out_socks, sockets);\n    return 0;\n}\n\nAVS_LIST(anjay_download_ctx_t) *\n_anjay_downloader_find_ctx_ptr_by_id(anjay_downloader_t *dl, uintptr_t id) {\n    AVS_LIST(anjay_download_ctx_t) *ctx_ptr;\n    AVS_LIST_FOREACH_PTR(ctx_ptr, &dl->downloads) {\n        if ((*ctx_ptr)->common.id == id) {\n            return ctx_ptr;\n        }\n    }\n\n    return NULL;\n}\n\nint _anjay_downloader_handle_packet(anjay_downloader_t *dl,\n                                    avs_net_socket_t *socket) {\n    assert(&_anjay_downloader_get_anjay(dl)->downloader == dl);\n\n    AVS_LIST(anjay_download_ctx_t) *ctx_ptr =\n            find_ctx_ptr_by_socket(dl, socket);\n    if (!ctx_ptr) {\n        // unknown socket\n        return -1;\n    }\n\n    assert(*ctx_ptr);\n    assert((*ctx_ptr)->common.vtable);\n    (*ctx_ptr)->common.vtable->handle_packet(ctx_ptr);\n    return 0;\n}\n\n#    if defined(ANJAY_WITH_HTTP_DOWNLOAD) || defined(ANJAY_WITH_COAP_DOWNLOAD)\nstatic uintptr_t find_free_id(anjay_downloader_t *dl) {\n    uintptr_t id;\n\n    // One could think this can loop forever if all download IDs are in use.\n    // However, uintptr_t is an integer as large as a pointer, and a normal\n    // pointer needs to be able to address every byte that may be allocated\n    // by avs_malloc(). Since we use more than one byte per download object,\n    // we can safely assume we will run out of RAM before running out\n    // of download IDs.\n    do {\n        id = dl->next_id++;\n    } while (id == INVALID_DOWNLOAD_ID\n             || _anjay_downloader_find_ctx_ptr_by_id(dl, id) != NULL);\n\n    return id;\n}\n#    else // ANJAY_WITH_HTTP_DOWNLOAD || ANJAY_WITH_COAP_DOWNLOAD\n#        error \"ANJAY_WITH_DOWNLOADER is enabled but no transport selected. Please enable at least one of ANJAY_WITH_HTTP_DOWNLOAD or ANJAY_WITH_COAP_DOWNLOAD\"\n#    endif // ANJAY_WITH_HTTP_DOWNLOAD || ANJAY_WITH_COAP_DOWNLOAD\n\n#    ifdef ANJAY_WITH_HTTP_DOWNLOAD\nstatic bool starts_with(const char *haystack, const char *needle) {\n    return avs_strncasecmp(haystack, needle, strlen(needle)) == 0;\n}\n#    endif // ANJAY_WITH_HTTP_DOWNLOAD\n\nstatic avs_error_t find_downloader_ctx_constructor(\n        const char *url,\n        anjay_downloader_ctx_constructor_t **out_constructor,\n        anjay_socket_transport_t *out_transport) {\n#    ifdef ANJAY_WITH_COAP_DOWNLOAD\n    const anjay_transport_info_t *transport_info =\n            _anjay_transport_info_by_uri_scheme(url);\n    if (transport_info != NULL) {\n        *out_constructor = _anjay_downloader_coap_ctx_new;\n        *out_transport = transport_info->transport;\n        return AVS_OK;\n    }\n#    endif // ANJAY_WITH_COAP_DOWNLOAD\n#    ifdef ANJAY_WITH_HTTP_DOWNLOAD\n    if (starts_with(url, \"http\")) {\n        *out_constructor = _anjay_downloader_http_ctx_new;\n        *out_transport = ANJAY_SOCKET_TRANSPORT_TCP;\n        return AVS_OK;\n    }\n#    endif // ANJAY_WITH_HTTP_DOWNLOAD\n    dl_log(WARNING, _(\"unrecognized protocol in URL: \") \"%s\", url);\n    *out_constructor = NULL;\n    return avs_errno(AVS_EPROTONOSUPPORT);\n}\n\navs_error_t _anjay_downloader_download(anjay_downloader_t *dl,\n                                       anjay_download_handle_t *out_handle,\n                                       const anjay_download_config_t *config,\n                                       avs_coap_ctx_t *forced_coap_ctx,\n                                       avs_net_socket_t *forced_coap_socket) {\n    assert(&_anjay_downloader_get_anjay(dl)->downloader == dl);\n    assert(out_handle);\n\n    anjay_downloader_ctx_constructor_t *constructor = NULL;\n    anjay_socket_transport_t transport;\n    avs_error_t err = find_downloader_ctx_constructor(config->url, &constructor,\n                                                      &transport);\n\n    AVS_LIST(anjay_download_ctx_t) dl_ctx = NULL;\n    if (avs_is_ok(err)) {\n        assert(constructor);\n        if (_anjay_socket_transport_included(\n                    _anjay_downloader_get_anjay(dl)->online_transports,\n                    transport)) {\n            err = constructor(dl, &dl_ctx, config, find_free_id(dl),\n                              forced_coap_ctx, forced_coap_socket);\n        } else {\n            dl_log(WARNING, _(\"transport currently offline for URL: \") \"%s\",\n                   config->url);\n            err = avs_errno(AVS_ENODEV);\n        }\n    }\n\n    if (dl_ctx) {\n        AVS_LIST_APPEND(&dl->downloads, dl_ctx);\n\n        assert(dl_ctx->common.id != INVALID_DOWNLOAD_ID);\n        dl_log(INFO, _(\"download scheduled: \") \"%s\", config->url);\n        *out_handle = (anjay_download_handle_t) dl_ctx->common.id;\n        return AVS_OK;\n    } else {\n        assert(avs_is_err(err));\n        return err;\n    }\n}\n\navs_error_t\n_anjay_downloader_set_next_block_offset(anjay_downloader_t *dl,\n                                        anjay_download_handle_t handle,\n                                        size_t next_block_offset) {\n    uintptr_t id = (uintptr_t) handle;\n\n    AVS_LIST(anjay_download_ctx_t) *ctx_ptr =\n            _anjay_downloader_find_ctx_ptr_by_id(dl, id);\n    if (!ctx_ptr) {\n        dl_log(DEBUG, _(\"download id = \") \"%\" PRIuPTR _(\" not found\"), id);\n        return avs_errno(AVS_ENOENT);\n    }\n    return (*ctx_ptr)->common.vtable->set_next_block_offset(*ctx_ptr,\n                                                            next_block_offset);\n}\n\nvoid _anjay_downloader_abort(anjay_downloader_t *dl,\n                             anjay_download_handle_t handle) {\n    uintptr_t id = (uintptr_t) handle;\n\n    AVS_LIST(anjay_download_ctx_t) *ctx_ptr =\n            _anjay_downloader_find_ctx_ptr_by_id(dl, id);\n    if (!ctx_ptr) {\n        dl_log(DEBUG,\n               _(\"download id = \") \"%\" PRIuPTR _(\" not found (expired?)\"), id);\n    } else {\n        _anjay_downloader_abort_transfer(ctx_ptr,\n                                         _anjay_download_status_aborted());\n    }\n}\n\nvoid _anjay_downloader_suspend(anjay_downloader_t *dl,\n                               anjay_download_handle_t handle) {\n    uintptr_t id = (uintptr_t) handle;\n\n    AVS_LIST(anjay_download_ctx_t) *ctx_ptr =\n            _anjay_downloader_find_ctx_ptr_by_id(dl, id);\n    if (!ctx_ptr) {\n        dl_log(DEBUG,\n               _(\"download id = \") \"%\" PRIuPTR _(\" not found (expired?)\"), id);\n    } else if (!(*ctx_ptr)->common.administratively_suspended) {\n        (*ctx_ptr)->common.administratively_suspended = true;\n        suspend_transfer(*ctx_ptr);\n    }\n}\n\nvoid _anjay_downloader_reconnect_job(avs_sched_t *sched, const void *id_ptr) {\n    anjay_t *anjay_locked = _anjay_get_from_sched(sched);\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    uintptr_t id = *(const uintptr_t *) id_ptr;\n    AVS_LIST(anjay_download_ctx_t) *ctx_ptr =\n            _anjay_downloader_find_ctx_ptr_by_id(&anjay->downloader, id);\n    if (!ctx_ptr) {\n        dl_log(DEBUG,\n               _(\"download id = \") \"%\" PRIuPTR _(\" not found (expired?)\"), id);\n    } else if (!(*ctx_ptr)->common.administratively_suspended) {\n        if (_anjay_socket_transport_included(anjay->online_transports,\n                                             get_ctx_socket_transport(\n                                                     *ctx_ptr))) {\n            reconnect_transfer(ctx_ptr);\n        } else {\n            suspend_transfer(*ctx_ptr);\n        }\n    }\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n}\n\nint _anjay_downloader_sched_reconnect_ctx(anjay_download_ctx_t *ctx) {\n    return AVS_SCHED_NOW(_anjay_downloader_get_anjay(ctx->common.dl)->sched,\n                         &ctx->common.reconnect_job_handle,\n                         _anjay_downloader_reconnect_job, &ctx->common.id,\n                         sizeof(ctx->common.id));\n}\n\nint _anjay_downloader_sched_reconnect_by_handle(\n        anjay_downloader_t *dl, anjay_download_handle_t handle) {\n    uintptr_t id = (uintptr_t) handle;\n\n    AVS_LIST(anjay_download_ctx_t) *ctx_ptr =\n            _anjay_downloader_find_ctx_ptr_by_id(dl, id);\n    if (!ctx_ptr) {\n        dl_log(DEBUG,\n               _(\"download id = \") \"%\" PRIuPTR _(\" not found (expired?)\"), id);\n        return -1;\n    }\n    (*ctx_ptr)->common.administratively_suspended = false;\n    if (_anjay_socket_transport_included(\n                _anjay_downloader_get_anjay(dl)->online_transports,\n                get_ctx_socket_transport(*ctx_ptr))) {\n        return _anjay_downloader_sched_reconnect_ctx(*ctx_ptr);\n    }\n    return 0;\n}\n\nbool _anjay_downloader_same_socket_transfer_ongoing(anjay_downloader_t *dl,\n                                                    avs_net_socket_t *socket) {\n    assert(dl);\n    if (!socket) {\n        return false;\n    }\n    AVS_LIST(anjay_download_ctx_t) ctx;\n    AVS_LIST_FOREACH(ctx, dl->downloads) {\n        if (ctx->common.same_socket_download && get_ctx_socket(ctx) == socket) {\n            return true;\n        }\n    }\n    return false;\n}\n\nvoid _anjay_downloader_suspend_same_socket(anjay_downloader_t *dl,\n                                           avs_net_socket_t *socket) {\n    assert(dl);\n    if (!socket) {\n        return;\n    }\n    AVS_LIST(anjay_download_ctx_t) *ctx_ptr;\n    AVS_LIST(anjay_download_ctx_t) helper;\n    AVS_LIST_DELETABLE_FOREACH_PTR(ctx_ptr, helper, &dl->downloads) {\n        if (!(*ctx_ptr)->common.administratively_suspended\n                && (*ctx_ptr)->common.same_socket_download\n                && get_ctx_socket(*ctx_ptr) == socket) {\n            suspend_transfer(*ctx_ptr);\n        }\n    }\n}\n\nvoid _anjay_downloader_abort_same_socket(anjay_downloader_t *dl,\n                                         avs_net_socket_t *socket) {\n    assert(dl);\n    if (!socket) {\n        return;\n    }\n    AVS_LIST(anjay_download_ctx_t) *ctx_ptr;\n    AVS_LIST(anjay_download_ctx_t) helper;\n    AVS_LIST_DELETABLE_FOREACH_PTR(ctx_ptr, helper, &dl->downloads) {\n        if ((*ctx_ptr)->common.same_socket_download\n                && get_ctx_socket(*ctx_ptr) == socket) {\n            _anjay_downloader_abort_transfer(&dl->downloads,\n                                             _anjay_download_status_aborted());\n        }\n    }\n}\n\nint _anjay_downloader_sched_reconnect_by_transports(\n        anjay_downloader_t *dl, anjay_transport_set_t transport_set) {\n    int result = 0;\n    AVS_LIST(anjay_download_ctx_t) ctx;\n    AVS_LIST_FOREACH(ctx, dl->downloads) {\n        if (_anjay_socket_transport_included(transport_set,\n                                             get_ctx_socket_transport(ctx))) {\n            int partial_result = _anjay_downloader_sched_reconnect_ctx(ctx);\n            if (!result && partial_result) {\n                result = partial_result;\n            }\n        }\n    }\n    return result;\n}\n\nint _anjay_downloader_sync_online_transports(anjay_downloader_t *dl) {\n    int result = 0;\n    AVS_LIST(anjay_download_ctx_t) ctx;\n    AVS_LIST_FOREACH(ctx, dl->downloads) {\n\n        /**\n         * This condition implements the XOR logic;\n         * if the downloader transport is not included in the\n         * online_transports while the socket is online, or if the\n         * downloader transport is included in the online_transports\n         * while the socket is offline, then call\n         * _anjay_downloader_sched_reconnect_ctx() to synchronize\n         * the downloader transport and suspend or reconnect the\n         * download.\n         */\n        if (_anjay_socket_transport_included(\n                    _anjay_downloader_get_anjay(dl)->online_transports,\n                    get_ctx_socket_transport(ctx))\n                /**\n                 * The reason why we need a callback here instead\n                 * of a simple call to _anjay_socket_is_online()\n                 * is that there is an additional mechanism to\n                 * handle CoAP downloader retries (the user can\n                 * enable it by modifying the\n                 * coap_downloader_retry_count variable). For\n                 * example, when Anjay enters offline mode, the\n                 * suspend callback should be called if a retry\n                 * is in progress - even if the socket is already\n                 * offline - to cancel any scheduled retry job.\n                 */\n                != ctx->common.vtable->is_socket_online_or_retry_in_progress(\n                           ctx)) {\n            int partial_result = _anjay_downloader_sched_reconnect_ctx(ctx);\n            if (!result && partial_result) {\n                result = partial_result;\n            }\n        }\n    }\n    return result;\n}\n\n#endif // ANJAY_WITH_DOWNLOADER\n"
  },
  {
    "path": "src/core/downloader/anjay_http.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#ifdef ANJAY_WITH_HTTP_DOWNLOAD\n\n#    ifndef ANJAY_WITH_DOWNLOADER\n#        error \"ANJAY_WITH_HTTP_DOWNLOAD requires ANJAY_WITH_DOWNLOADER to be enabled\"\n#    endif // ANJAY_WITH_DOWNLOADER\n\n#    include <errno.h>\n#    include <inttypes.h>\n\n#    include <avsystem/commons/avs_errno.h>\n#    include <avsystem/commons/avs_http.h>\n#    include <avsystem/commons/avs_memory.h>\n#    include <avsystem/commons/avs_stream_net.h>\n#    include <avsystem/commons/avs_utils.h>\n\n#    define ANJAY_DOWNLOADER_INTERNALS\n\n#    include \"anjay_private.h\"\n\nVISIBILITY_SOURCE_BEGIN\n\ntypedef struct {\n    anjay_download_ctx_common_t common;\n    avs_net_ssl_configuration_t ssl_configuration;\n    anjay_security_config_cache_t security_config_cache;\n    avs_net_resolved_endpoint_t preferred_endpoint;\n    avs_time_duration_t request_timeout;\n    avs_http_t *client;\n    avs_url_t *parsed_url;\n    avs_stream_t *stream;\n    avs_sched_handle_t next_action_job;\n\n    // State related to download resumption:\n    anjay_etag_t *etag;\n    size_t bytes_downloaded; // current offset in the remote resource\n    size_t bytes_written;    // current offset in the local file\n    // Note that the two values above may be different, for example when\n    // we request Range: bytes=1200-, but the server responds with\n    // Content-Range: bytes 1024-..., because it insists on using regular block\n    // boundaries; we would then need to ignore 176 bytes without writing them.\n} anjay_http_download_ctx_t;\n\nstatic int parse_number(const char **inout_ptr, unsigned long long *out_value) {\n    assert(inout_ptr);\n    if (**inout_ptr == '-') {\n        return -1;\n    }\n    char *endptr;\n    *out_value = strtoull(*inout_ptr, &endptr, 10);\n    if (endptr == *inout_ptr || (*out_value == ULLONG_MAX && errno == ERANGE)) {\n        return -1;\n    }\n    *inout_ptr = endptr;\n    return 0;\n}\n\nstatic int read_start_byte_from_content_range(const char *content_range,\n                                              uint64_t *out_start_byte) {\n    unsigned long long complete_length;\n    unsigned long long start;\n    unsigned long long end;\n    if (avs_match_token(&content_range, \"bytes\", AVS_SPACES)\n            || parse_number(&content_range, &start) || *content_range++ != '-'\n            || parse_number(&content_range, &end) || *content_range++ != '/'\n            || *content_range == '\\0') {\n        return -1;\n    }\n\n    *out_start_byte = start;\n\n    return (strcmp(content_range, \"*\") == 0\n            || (*content_range != '-'\n                && !_anjay_safe_strtoull(content_range, &complete_length)\n                && complete_length >= 1 && complete_length - 1 == end))\n                   ? 0\n                   : -1;\n}\n\nstatic anjay_etag_t *read_etag(const char *text) {\n    size_t len = strlen(text);\n    if (len < 2 || len > UINT8_MAX + 2 || text[0] != '\"'\n            || text[len - 1] != '\"') {\n        return NULL;\n    }\n    anjay_etag_t *result = anjay_etag_new((uint8_t) (len - 2));\n    if (result) {\n        memcpy(result->value, &text[1], result->size);\n    }\n    return result;\n}\n\nstatic inline bool etag_matches(const anjay_etag_t *etag, const char *text) {\n    size_t len = strlen(text);\n    return len == (size_t) (etag->size + 2) && text[0] == '\"'\n           && text[len - 1] == '\"'\n           && memcmp(etag->value, &text[1], etag->size) == 0;\n}\n\nstatic void\nhandle_http_packet_with_locked_buffer(AVS_LIST(anjay_download_ctx_t) *ctx_ptr,\n                                      uint8_t *buffer) {\n    anjay_http_download_ctx_t *ctx = (anjay_http_download_ctx_t *) *ctx_ptr;\n    anjay_unlocked_t *anjay = _anjay_downloader_get_anjay(ctx->common.dl);\n    bool nonblock_read_ready;\n    do {\n        size_t bytes_read;\n        bool message_finished = false;\n\n        avs_error_t err =\n                avs_stream_read(ctx->stream, &bytes_read, &message_finished,\n                                buffer, anjay->in_shared_buffer->capacity);\n        if (avs_is_err(err)) {\n            _anjay_downloader_abort_transfer(\n                    ctx_ptr, _anjay_download_status_failed(err));\n            return;\n        }\n        if (bytes_read) {\n            assert(ctx->bytes_written >= ctx->bytes_downloaded);\n            ctx->bytes_downloaded += bytes_read;\n            while (ctx->bytes_downloaded > ctx->bytes_written) {\n                size_t bytes_to_write =\n                        ctx->bytes_downloaded - ctx->bytes_written;\n                assert(bytes_read >= bytes_to_write);\n                size_t original_offset = ctx->bytes_written;\n                if (avs_is_err((err = _anjay_downloader_call_on_next_block(\n                                        &ctx->common,\n                                        &buffer[bytes_read - bytes_to_write],\n                                        bytes_to_write, ctx->etag)))) {\n                    _anjay_downloader_abort_transfer(\n                            ctx_ptr, _anjay_download_status_failed(err));\n                    return;\n                }\n                if (ctx->bytes_written == original_offset) {\n                    ctx->bytes_written += bytes_to_write;\n                }\n            }\n        }\n        if (message_finished) {\n            dl_log(INFO, _(\"HTTP transfer id = \") \"%\" PRIuPTR _(\" finished\"),\n                   ctx->common.id);\n            _anjay_downloader_abort_transfer(ctx_ptr,\n                                             _anjay_download_status_success());\n            return;\n        }\n        nonblock_read_ready = avs_stream_nonblock_read_ready(ctx->stream);\n    } while (nonblock_read_ready);\n    // NOTE: ctx->next_action_job might be NULL\n    // if anjay_download_suspend() was called\n    if (ctx->next_action_job) {\n        int result = AVS_RESCHED_DELAYED(&ctx->next_action_job,\n                                         ctx->request_timeout);\n        assert(!result);\n        (void) result;\n    }\n}\n\nstatic void handle_http_packet(AVS_LIST(anjay_download_ctx_t) *ctx_ptr) {\n    anjay_http_download_ctx_t *ctx = (anjay_http_download_ctx_t *) *ctx_ptr;\n    anjay_unlocked_t *anjay = _anjay_downloader_get_anjay(ctx->common.dl);\n    uint8_t *buffer = avs_shared_buffer_acquire(anjay->in_shared_buffer);\n    assert(buffer);\n    handle_http_packet_with_locked_buffer(ctx_ptr, buffer);\n    avs_shared_buffer_release(anjay->in_shared_buffer);\n}\n\nstatic void timeout_job(avs_sched_t *sched, const void *id_ptr) {\n    anjay_t *anjay_locked = _anjay_get_from_sched(sched);\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    uintptr_t id = *(const uintptr_t *) id_ptr;\n    AVS_LIST(anjay_download_ctx_t) *ctx_ptr =\n            _anjay_downloader_find_ctx_ptr_by_id(&anjay->downloader, id);\n    if (!ctx_ptr) {\n        dl_log(DEBUG, _(\"download id = \") \"%\" PRIuPTR _(\"expired\"), id);\n    } else {\n        _anjay_downloader_abort_transfer(ctx_ptr,\n                                         _anjay_download_status_failed(\n                                                 avs_errno(AVS_ETIMEDOUT)));\n    }\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n}\n\nstatic void send_request_unlocked(anjay_unlocked_t *anjay, uintptr_t id) {\n    AVS_LIST(anjay_download_ctx_t) *ctx_ptr =\n            _anjay_downloader_find_ctx_ptr_by_id(&anjay->downloader, id);\n    if (!ctx_ptr) {\n        dl_log(DEBUG, _(\"download id = \") \"%\" PRIuPTR _(\"expired\"), id);\n        return;\n    }\n\n    AVS_LIST(const avs_http_header_t) received_headers = NULL;\n    anjay_http_download_ctx_t *ctx = (anjay_http_download_ctx_t *) *ctx_ptr;\n    avs_error_t err =\n            avs_http_open_stream(&ctx->stream, ctx->client, AVS_HTTP_GET,\n                                 AVS_HTTP_CONTENT_IDENTITY, ctx->parsed_url,\n                                 NULL, NULL);\n    if (avs_is_err(err) || !ctx->stream) {\n        _anjay_downloader_abort_transfer(ctx_ptr,\n                                         _anjay_download_status_failed(err));\n        return;\n    }\n\n    avs_http_set_header_storage(ctx->stream, &received_headers);\n\n    char ifmatch[258];\n    if (ctx->etag) {\n        if (avs_simple_snprintf(ifmatch, sizeof(ifmatch), \"\\\"%.*s\\\"\",\n                                (int) ctx->etag->size, ctx->etag->value)\n                        < 0\n                || avs_http_add_header(ctx->stream, \"If-Match\", ifmatch)) {\n            dl_log(ERROR, _(\"Could not send If-Match header\"));\n            _anjay_downloader_abort_transfer(ctx_ptr,\n                                             _anjay_download_status_failed(\n                                                     avs_errno(AVS_ENOMEM)));\n            return;\n        }\n    }\n\n    // see docs on UINT_STR_BUF_SIZE in Commons for details on this formula\n    char range[sizeof(\"bytes=-\") + (12 * sizeof(size_t)) / 5 + 1];\n    if (ctx->bytes_written > 0) {\n        if (avs_simple_snprintf(range, sizeof(range), \"bytes=%lu-\",\n                                (unsigned long) ctx->bytes_written)\n                        < 0\n                || avs_http_add_header(ctx->stream, \"Range\", range)) {\n            dl_log(ERROR, _(\"Could not resume HTTP download: could not send \"\n                            \"Range header\"));\n            _anjay_downloader_abort_transfer(ctx_ptr,\n                                             _anjay_download_status_failed(\n                                                     avs_errno(AVS_ENOMEM)));\n            return;\n        }\n    }\n\n    if (avs_is_err((err = avs_stream_finish_message(ctx->stream)))) {\n        int http_status = 200;\n        if (err.category == AVS_HTTP_ERROR_CATEGORY) {\n            http_status = avs_http_status_code(ctx->stream);\n        }\n        if (http_status < 200 || http_status >= 300) {\n            dl_log(WARNING, _(\"HTTP error code \") \"%d\" _(\" received\"),\n                   http_status);\n            if (http_status == 412) { // Precondition Failed\n                _anjay_downloader_abort_transfer(\n                        ctx_ptr, _anjay_download_status_expired());\n            } else {\n                _anjay_downloader_abort_transfer(\n                        ctx_ptr,\n                        _anjay_download_status_invalid_response(http_status));\n            }\n        } else {\n            dl_log(ERROR, _(\"Could not send HTTP request: \") \"%s\",\n                   AVS_COAP_STRERROR(err));\n            _anjay_downloader_abort_transfer(\n                    ctx_ptr, _anjay_download_status_failed(err));\n        }\n        return;\n    }\n\n    ctx->bytes_downloaded = 0;\n\n    AVS_LIST(const avs_http_header_t) it;\n    AVS_LIST_FOREACH(it, received_headers) {\n        if (avs_strcasecmp(it->key, \"Content-Range\") == 0) {\n            uint64_t bytes_downloaded;\n            if (read_start_byte_from_content_range(it->value, &bytes_downloaded)\n                    || bytes_downloaded > ctx->bytes_written) {\n                dl_log(ERROR,\n                       _(\"Could not resume HTTP download: invalid \"\n                         \"Content-Range: \") \"%s\",\n                       it->value);\n                _anjay_downloader_abort_transfer(\n                        ctx_ptr,\n                        _anjay_download_status_failed(avs_errno(AVS_EPROTO)));\n                return;\n            }\n            ctx->bytes_downloaded = (size_t) bytes_downloaded;\n        } else if (avs_strcasecmp(it->key, \"ETag\") == 0) {\n            if (ctx->etag) {\n                if (!etag_matches(ctx->etag, it->value)) {\n                    dl_log(ERROR, _(\"ETag does not match\"));\n                    _anjay_downloader_abort_transfer(\n                            ctx_ptr, _anjay_download_status_expired());\n                    return;\n                }\n            } else if (!(ctx->etag = read_etag(it->value))) {\n                dl_log(WARNING,\n                       _(\"Could not store ETag of the download: \") \"%s\",\n                       it->value);\n            }\n        }\n    }\n    avs_http_set_header_storage(ctx->stream, NULL);\n\n    if (AVS_SCHED_DELAYED(anjay->sched, &ctx->next_action_job,\n                          ctx->request_timeout, timeout_job, &ctx->common.id,\n                          sizeof(ctx->common.id))) {\n        dl_log(ERROR, _(\"could not schedule timeout job\"));\n        _anjay_downloader_abort_transfer(\n                ctx_ptr, _anjay_download_status_failed(avs_errno(AVS_ENOMEM)));\n        return;\n    }\n\n    /*\n     * If the whole downloaded file is small enough and is received before\n     * we end handling HTTP headers, it may be read by the underlying\n     * buffered_netstream alongside the last chunk of HTTP headers. In that\n     * case, the poll()/select() recommended for use in main program loop\n     * will never report data being available on the download socket, even\n     * though we have *some* data cached in the buffered_netstream internal\n     * buffer. We avoid this case by explicitly handling any buffered data\n     * here.\n     *\n     * Also, we must not call handle_http_packet unconditionally, because if\n     * there is no data buffered, the call would block waiting until a first\n     * chunk of data is received from the server.\n     */\n    if (avs_stream_nonblock_read_ready(ctx->stream)) {\n        handle_http_packet(ctx_ptr);\n    }\n}\n\nstatic void send_request(avs_sched_t *sched, const void *id_ptr) {\n    anjay_t *anjay_locked = _anjay_get_from_sched(sched);\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    send_request_unlocked(anjay, *(const uintptr_t *) id_ptr);\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n}\n\nstatic avs_net_socket_t *get_http_socket(anjay_download_ctx_t *ctx) {\n    return avs_stream_net_getsock(((anjay_http_download_ctx_t *) ctx)->stream);\n}\n\nstatic anjay_socket_transport_t\nget_http_socket_transport(anjay_download_ctx_t *ctx) {\n    (void) ctx;\n    return ANJAY_SOCKET_TRANSPORT_TCP;\n}\n\nstatic void\ncleanup_http_stream_unlocked(AVS_LIST(anjay_download_ctx_t) detached_ctx) {\n    anjay_http_download_ctx_t *ctx = (anjay_http_download_ctx_t *) detached_ctx;\n    avs_free(ctx->etag);\n    avs_stream_cleanup(&ctx->stream);\n    avs_url_free(ctx->parsed_url);\n    avs_http_free(ctx->client);\n    _anjay_security_config_cache_cleanup(&ctx->security_config_cache);\n    AVS_LIST_DELETE(&detached_ctx);\n    assert(!detached_ctx);\n}\n\nstatic void cleanup_http_stream(avs_sched_t *sched,\n                                const void *detached_ctx_ptr) {\n    anjay_t *anjay_locked = _anjay_get_from_sched(sched);\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    cleanup_http_stream_unlocked(\n            *(AVS_LIST(anjay_download_ctx_t) const *) detached_ctx_ptr);\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n}\n\nstatic void cleanup_http_transfer(AVS_LIST(anjay_download_ctx_t) *ctx_ptr) {\n    anjay_http_download_ctx_t *ctx = (anjay_http_download_ctx_t *) *ctx_ptr;\n    anjay_unlocked_t *anjay = _anjay_downloader_get_anjay(ctx->common.dl);\n\n    avs_sched_del(&ctx->next_action_job);\n    AVS_LIST(anjay_download_ctx_t) detached_ctx = AVS_LIST_DETACH(ctx_ptr);\n    /**\n     * HACK: this is necessary, because the download might be aborted from\n     * anjay_serve() inside an event loop - freeing everything directly would\n     * cause the underlying socket to be destroyed, which could cause\n     * use-after-free in the event loop when attempting to query the destroyed\n     * socket's file descriptor.\n     */\n    if (!anjay->sched\n            || AVS_SCHED_NOW(anjay->sched, NULL, cleanup_http_stream,\n                             &detached_ctx, sizeof(detached_ctx))) {\n        cleanup_http_stream_unlocked(detached_ctx);\n    }\n}\n\nstatic void suspend_http_transfer(anjay_download_ctx_t *ctx_) {\n    anjay_http_download_ctx_t *ctx = (anjay_http_download_ctx_t *) ctx_;\n    avs_sched_del(&ctx->next_action_job);\n    avs_stream_cleanup(&ctx->stream);\n}\n\nstatic avs_error_t\nreconnect_http_transfer(AVS_LIST(anjay_download_ctx_t) *ctx_ptr) {\n    anjay_http_download_ctx_t *ctx = (anjay_http_download_ctx_t *) *ctx_ptr;\n    avs_stream_cleanup(&ctx->stream);\n    anjay_unlocked_t *anjay = _anjay_downloader_get_anjay(ctx->common.dl);\n    if (AVS_SCHED_NOW(anjay->sched, &ctx->next_action_job, send_request,\n                      &ctx->common.id, sizeof(ctx->common.id))) {\n        dl_log(ERROR, _(\"could not schedule download job\"));\n        return avs_errno(AVS_ENOMEM);\n    }\n    return AVS_OK;\n}\n\nstatic avs_error_t set_next_http_block_offset(anjay_download_ctx_t *ctx_,\n                                              size_t next_block_offset) {\n    anjay_http_download_ctx_t *ctx = (anjay_http_download_ctx_t *) ctx_;\n    if (next_block_offset <= ctx->bytes_written) {\n        dl_log(DEBUG, _(\"attempted to move download offset backwards\"));\n        return avs_errno(AVS_EINVAL);\n    }\n    ctx->bytes_written = next_block_offset;\n    return AVS_OK;\n}\n\nstatic avs_error_t copy_psk_info(avs_net_psk_info_t *dest,\n                                 const avs_net_psk_info_t *src,\n                                 anjay_security_config_cache_t *cache) {\n    *dest = *src;\n    avs_error_t err;\n    if (avs_is_err(\n                (err = avs_crypto_psk_key_info_copy(&cache->psk_key, src->key)))\n            || avs_is_err((err = avs_crypto_psk_identity_info_copy(\n                                   &cache->psk_identity, src->identity)))) {\n        return err;\n    }\n    dest->key = *cache->psk_key;\n    dest->identity = *cache->psk_identity;\n    return AVS_OK;\n}\n\nstatic avs_error_t\ncopy_certificate_chain(avs_crypto_certificate_chain_info_t *dest,\n                       const avs_crypto_certificate_chain_info_t *src,\n                       avs_crypto_certificate_chain_info_t **cache_ptr) {\n    if (src->desc.source == AVS_CRYPTO_DATA_SOURCE_EMPTY) {\n        *dest = *src;\n        return AVS_OK;\n    }\n    size_t element_count;\n    avs_error_t err = avs_crypto_certificate_chain_info_copy_as_array(\n            cache_ptr, &element_count, *src);\n    if (avs_is_err(err)) {\n        return err;\n    }\n    *dest = avs_crypto_certificate_chain_info_from_array(*cache_ptr,\n                                                         element_count);\n    return AVS_OK;\n}\n\nstatic avs_error_t copy_cert_info(avs_net_certificate_info_t *dest,\n                                  const avs_net_certificate_info_t *src,\n                                  anjay_security_config_cache_t *cache) {\n    *dest = *src;\n    avs_error_t err;\n    if (avs_is_err((err = copy_certificate_chain(&dest->trusted_certs,\n                                                 &src->trusted_certs,\n                                                 &cache->trusted_certs_array)))\n            || avs_is_err((\n                       err = copy_certificate_chain(&dest->client_cert,\n                                                    &src->client_cert,\n                                                    &cache->client_cert_array)))\n            || avs_is_err((err = avs_crypto_private_key_info_copy(\n                                   &cache->client_key, src->client_key)))) {\n        return err;\n    }\n    dest->client_key = *cache->client_key;\n    if (src->cert_revocation_lists.desc.source\n            == AVS_CRYPTO_DATA_SOURCE_EMPTY) {\n        dest->cert_revocation_lists = src->cert_revocation_lists;\n    } else {\n        size_t element_count;\n        if (avs_is_err(\n                    (err = avs_crypto_cert_revocation_list_info_copy_as_array(\n                             &cache->cert_revocation_lists_array,\n                             &element_count, src->cert_revocation_lists)))) {\n            return err;\n        }\n        dest->cert_revocation_lists =\n                avs_crypto_cert_revocation_list_info_from_array(\n                        cache->cert_revocation_lists_array, element_count);\n    }\n    return AVS_OK;\n}\n\nstatic avs_error_t copy_security_info(avs_net_security_info_t *dest,\n                                      const avs_net_security_info_t *src,\n                                      anjay_security_config_cache_t *cache) {\n    dest->mode = src->mode;\n    switch (src->mode) {\n    case AVS_NET_SECURITY_PSK:\n        return copy_psk_info(&dest->data.psk, &src->data.psk, cache);\n    case AVS_NET_SECURITY_CERTIFICATE:\n        return copy_cert_info(&dest->data.cert, &src->data.cert, cache);\n    default:\n        dl_log(ERROR, _(\"Invalid security mode: \") \"%d\", (int) src->mode);\n        return avs_errno(AVS_EINVAL);\n    }\n}\n\nstatic avs_error_t http_ssl_pre_connect_cb(avs_http_t *http,\n                                           avs_net_socket_t *socket,\n                                           const char *hostname,\n                                           const char *port,\n                                           void *ctx_) {\n    (void) http;\n    (void) port;\n    anjay_http_download_ctx_t *ctx = (anjay_http_download_ctx_t *) ctx_;\n    if (!hostname || !ctx->security_config_cache.dane_tlsa_record) {\n        return AVS_OK;\n    }\n    const char *configured_hostname = avs_url_host(ctx->parsed_url);\n    if (!configured_hostname || strcmp(configured_hostname, hostname)) {\n        // non-original hostname - we're after redirection; do nothing\n        return AVS_OK;\n    }\n    return avs_net_socket_set_opt(\n            socket, AVS_NET_SOCKET_OPT_DANE_TLSA_ARRAY,\n            (avs_net_socket_opt_value_t) {\n                .dane_tlsa_array = {\n                    .array_ptr = ctx->security_config_cache.dane_tlsa_record,\n                    .array_element_count = 1\n                }\n            });\n}\n\nstatic bool is_socket_online_or_retry_in_progress(anjay_download_ctx_t *ctx_) {\n    return _anjay_socket_is_online(get_http_socket(ctx_));\n}\n\navs_error_t\n_anjay_downloader_http_ctx_new(anjay_downloader_t *dl,\n                               AVS_LIST(anjay_download_ctx_t) *out_dl_ctx,\n                               const anjay_download_config_t *cfg,\n                               uintptr_t id,\n                               avs_coap_ctx_t *forced_coap_ctx,\n                               avs_net_socket_t *forced_coap_socket) {\n    (void) forced_coap_ctx;\n    (void) forced_coap_socket;\n\n    if (!cfg->on_next_block || !cfg->on_download_finished) {\n        dl_log(ERROR, _(\"invalid download config: handlers not set up\"));\n        return avs_errno(AVS_EINVAL);\n    }\n\n    anjay_unlocked_t *anjay = _anjay_downloader_get_anjay(dl);\n    AVS_LIST(anjay_http_download_ctx_t) ctx =\n            AVS_LIST_NEW_ELEMENT(anjay_http_download_ctx_t);\n    if (!ctx) {\n        _anjay_log_oom();\n        return avs_errno(AVS_ENOMEM);\n    }\n\n    static const anjay_download_ctx_vtable_t VTABLE = {\n        .get_socket = get_http_socket,\n        .get_socket_transport = get_http_socket_transport,\n        .handle_packet = handle_http_packet,\n        .cleanup = cleanup_http_transfer,\n        .suspend = suspend_http_transfer,\n        .reconnect = reconnect_http_transfer,\n        .set_next_block_offset = set_next_http_block_offset,\n        .is_socket_online_or_retry_in_progress =\n                is_socket_online_or_retry_in_progress\n    };\n    ctx->common.vtable = &VTABLE;\n\n    avs_http_buffer_sizes_t http_buffer_sizes = AVS_HTTP_DEFAULT_BUFFER_SIZES;\n    if (cfg->start_offset > 0) {\n        // prevent sending Accept-Encoding\n        http_buffer_sizes.content_coding_input = 0;\n    }\n\n    avs_error_t err = AVS_OK;\n    if (!(ctx->client = avs_http_new(&http_buffer_sizes))) {\n        err = avs_errno(AVS_ENOMEM);\n        goto error;\n    }\n    if (avs_is_err(\n                (err = copy_security_info(&ctx->ssl_configuration.security,\n                                          &cfg->security_config.security_info,\n                                          &ctx->security_config_cache)))) {\n        goto error;\n    }\n    ctx->ssl_configuration.ciphersuites = anjay->default_tls_ciphersuites;\n    if (cfg->security_config.tls_ciphersuites.num_ids) {\n        if (_anjay_copy_tls_ciphersuites(\n                    &ctx->security_config_cache.ciphersuites,\n                    &cfg->security_config.tls_ciphersuites)) {\n            err = avs_errno(AVS_ENOMEM);\n            goto error;\n        }\n        ctx->ssl_configuration.ciphersuites =\n                ctx->security_config_cache.ciphersuites;\n    }\n    if (cfg->security_config.dane_tlsa_record\n            && !(ctx->security_config_cache.dane_tlsa_record =\n                         avs_net_socket_dane_tlsa_array_copy((\n                                 const avs_net_socket_dane_tlsa_array_t) {\n                             .array_ptr = cfg->security_config.dane_tlsa_record,\n                             .array_element_count = 1\n                         }))) {\n        goto error;\n    }\n    ctx->ssl_configuration.backend_configuration.preferred_endpoint =\n            &ctx->preferred_endpoint;\n    ctx->ssl_configuration.prng_ctx = anjay->prng_ctx.ctx;\n    avs_http_ssl_configuration(ctx->client, &ctx->ssl_configuration);\n    avs_http_ssl_pre_connect_cb(ctx->client, http_ssl_pre_connect_cb, ctx);\n\n    if (avs_time_duration_less(AVS_TIME_DURATION_ZERO,\n                               cfg->tcp_request_timeout)) {\n        ctx->request_timeout = cfg->tcp_request_timeout;\n    } else {\n        ctx->request_timeout = AVS_NET_SOCKET_DEFAULT_RECV_TIMEOUT;\n    }\n\n    if (!(ctx->parsed_url = avs_url_parse(cfg->url))) {\n        err = avs_errno(AVS_EINVAL);\n        goto error;\n    }\n\n    ctx->common.dl = dl;\n    ctx->common.id = id;\n    ctx->common.on_next_block = cfg->on_next_block;\n    ctx->common.on_download_finished = cfg->on_download_finished;\n    ctx->common.user_data = cfg->user_data;\n    ctx->bytes_written = cfg->start_offset;\n    if (cfg->etag) {\n        if (!(ctx->etag = anjay_etag_clone(cfg->etag))) {\n            dl_log(ERROR, _(\"could not copy ETag\"));\n            err = avs_errno(AVS_ENOMEM);\n            goto error;\n        }\n    }\n\n    if (AVS_SCHED_NOW(_anjay_downloader_get_anjay(dl)->sched,\n                      &ctx->next_action_job, send_request, &ctx->common.id,\n                      sizeof(ctx->common.id))) {\n        dl_log(ERROR, _(\"could not schedule download job\"));\n        err = avs_errno(AVS_ENOMEM);\n        goto error;\n    }\n\n    *out_dl_ctx = (AVS_LIST(anjay_download_ctx_t)) ctx;\n    return AVS_OK;\nerror:\n    cleanup_http_transfer((AVS_LIST(anjay_download_ctx_t) *) &ctx);\n    assert(avs_is_err(err));\n    return err;\n}\n\n#endif // ANJAY_WITH_HTTP_DOWNLOAD\n"
  },
  {
    "path": "src/core/downloader/anjay_private.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ANJAY_DOWNLOADER_PRIVATE_H\n#define ANJAY_DOWNLOADER_PRIVATE_H\n\n#include \"../anjay_core.h\"\n#include \"../anjay_downloader.h\"\n\n#ifndef ANJAY_DOWNLOADER_INTERNALS\n#    error \"downloader/private.h is not meant to be included from outside\"\n#endif\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\n#define dl_log(...) _anjay_log(downloader, __VA_ARGS__)\n\ntypedef struct {\n    avs_net_socket_t *(*get_socket)(anjay_download_ctx_t *ctx);\n    anjay_socket_transport_t (*get_socket_transport)(anjay_download_ctx_t *ctx);\n    void (*handle_packet)(AVS_LIST(anjay_download_ctx_t) *ctx_ptr);\n    void (*cleanup)(AVS_LIST(anjay_download_ctx_t) *ctx_ptr);\n    void (*suspend)(anjay_download_ctx_t *ctx);\n    avs_error_t (*reconnect)(AVS_LIST(anjay_download_ctx_t) *ctx_ptr);\n    avs_error_t (*set_next_block_offset)(anjay_download_ctx_t *ctx,\n                                         size_t next_block_offset);\n    bool (*is_socket_online_or_retry_in_progress)(\n            anjay_download_ctx_t *ctx_ptr);\n} anjay_download_ctx_vtable_t;\n\ntypedef struct {\n    const anjay_download_ctx_vtable_t *vtable;\n\n    anjay_downloader_t *dl;\n    uintptr_t id;\n    avs_sched_handle_t reconnect_job_handle;\n\n    anjay_download_next_block_handler_t *on_next_block;\n    anjay_download_finished_handler_t *on_download_finished;\n    void *user_data;\n\n    bool administratively_suspended;\n    // This download re-uses socket of the already existing connection.\n    bool same_socket_download;\n} anjay_download_ctx_common_t;\n\nstatic inline anjay_unlocked_t *\n_anjay_downloader_get_anjay(anjay_downloader_t *dl) {\n    return AVS_CONTAINER_OF(dl, anjay_unlocked_t, downloader);\n}\n\nAVS_LIST(anjay_download_ctx_t) *\n_anjay_downloader_find_ctx_ptr_by_id(anjay_downloader_t *dl, uintptr_t id);\n\nvoid _anjay_downloader_abort_transfer(AVS_LIST(anjay_download_ctx_t) *ctx_ptr,\n                                      anjay_download_status_t status);\n\nvoid _anjay_downloader_reconnect_job(avs_sched_t *sched, const void *id_ptr);\n\nint _anjay_downloader_sched_reconnect_ctx(anjay_download_ctx_t *ctx);\n\navs_error_t\n_anjay_downloader_call_on_next_block(anjay_download_ctx_common_t *ctx,\n                                     const uint8_t *data,\n                                     size_t data_size,\n                                     const anjay_etag_t *etag);\n\nstatic inline anjay_download_status_t _anjay_download_status_success(void) {\n    return (anjay_download_status_t) {\n        .result = ANJAY_DOWNLOAD_FINISHED\n    };\n}\n\nstatic inline anjay_download_status_t\n_anjay_download_status_failed(avs_error_t error) {\n    return (anjay_download_status_t) {\n        .result = ANJAY_DOWNLOAD_ERR_FAILED,\n        .details = {\n            .error = error\n        }\n    };\n}\n\nstatic inline anjay_download_status_t\n_anjay_download_status_invalid_response(int status_code) {\n    return (anjay_download_status_t) {\n        .result = ANJAY_DOWNLOAD_ERR_INVALID_RESPONSE,\n        .details = {\n            .status_code = status_code\n        }\n    };\n}\n\nstatic inline anjay_download_status_t _anjay_download_status_expired(void) {\n    return (anjay_download_status_t) {\n        .result = ANJAY_DOWNLOAD_ERR_EXPIRED\n    };\n}\n\nstatic inline anjay_download_status_t _anjay_download_status_aborted(void) {\n    return (anjay_download_status_t) {\n        .result = ANJAY_DOWNLOAD_ERR_ABORTED\n    };\n}\n\ntypedef avs_error_t\nanjay_downloader_ctx_constructor_t(anjay_downloader_t *dl,\n                                   AVS_LIST(anjay_download_ctx_t) *out_dl_ctx,\n                                   const anjay_download_config_t *cfg,\n                                   uintptr_t id,\n                                   avs_coap_ctx_t *forced_coap_ctx,\n                                   avs_net_socket_t *forced_coap_socket);\n\n#ifdef ANJAY_WITH_COAP_DOWNLOAD\nanjay_downloader_ctx_constructor_t _anjay_downloader_coap_ctx_new;\n#endif // ANJAY_WITH_COAP_DOWNLOAD\n\n#ifdef ANJAY_WITH_HTTP_DOWNLOAD\nanjay_downloader_ctx_constructor_t _anjay_downloader_http_ctx_new;\n#endif // ANJAY_WITH_HTTP_DOWNLOAD\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif /* ANJAY_DOWNLOADER_PRIVATE_H */\n"
  },
  {
    "path": "src/core/io/anjay_base64_out.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n#include <avsystem/commons/avs_base64.h>\n#include <avsystem/commons/avs_stream.h>\n\n#include <anjay/core.h>\n\n#include \"../anjay_utils_private.h\"\n#include \"anjay_base64_out.h\"\n#include \"anjay_vtable.h\"\n\nVISIBILITY_SOURCE_BEGIN\n\ntypedef struct base64_ret_bytes_ctx {\n    const anjay_ret_bytes_ctx_vtable_t *vtable;\n    avs_stream_t *stream;\n    avs_base64_config_t config;\n    uint8_t bytes_cached[2];\n    size_t num_bytes_cached;\n    size_t num_bytes_left;\n} base64_ret_bytes_ctx_t;\n\n#define TEXT_CHUNK_SIZE (3 * 64u)\nAVS_STATIC_ASSERT(TEXT_CHUNK_SIZE % 3 == 0, chunk_must_be_a_multiple_of_3);\n\nstatic int base64_ret_encode_and_write(base64_ret_bytes_ctx_t *ctx,\n                                       const uint8_t *buffer,\n                                       const size_t buffer_size) {\n    if (!buffer_size) {\n        return 0;\n    }\n    char encoded[4 * (TEXT_CHUNK_SIZE / 3) + 1];\n    size_t encoded_size =\n            avs_base64_encoded_size_custom(buffer_size, ctx->config);\n    assert(encoded_size <= sizeof(encoded));\n    int retval = avs_base64_encode_custom(encoded, encoded_size, buffer,\n                                          buffer_size, ctx->config);\n    if (retval) {\n        return retval;\n    }\n    if (avs_is_err(avs_stream_write(ctx->stream, encoded, encoded_size - 1))) {\n        return -1;\n    }\n    return 0;\n}\n\nstatic int base64_ret_bytes_flush(base64_ret_bytes_ctx_t *ctx,\n                                  const uint8_t **dataptr,\n                                  size_t bytes_to_write) {\n    uint8_t chunk[TEXT_CHUNK_SIZE];\n    while (bytes_to_write > 0) {\n        memcpy(chunk, ctx->bytes_cached, ctx->num_bytes_cached);\n        size_t new_bytes_written =\n                AVS_MIN(TEXT_CHUNK_SIZE - ctx->num_bytes_cached,\n                        bytes_to_write);\n        assert(new_bytes_written <= TEXT_CHUNK_SIZE);\n        memcpy(&chunk[ctx->num_bytes_cached], *dataptr, new_bytes_written);\n        *dataptr += new_bytes_written;\n\n        int retval;\n        if ((retval = base64_ret_encode_and_write(\n                     ctx, chunk, new_bytes_written + ctx->num_bytes_cached))) {\n            return retval;\n        }\n        bytes_to_write -= new_bytes_written;\n        ctx->num_bytes_left -= new_bytes_written;\n        ctx->num_bytes_cached = 0;\n    }\n    return 0;\n}\n\nstatic int base64_ret_bytes_append(anjay_unlocked_ret_bytes_ctx_t *ctx_,\n                                   const void *data,\n                                   size_t size) {\n    base64_ret_bytes_ctx_t *ctx = (base64_ret_bytes_ctx_t *) ctx_;\n    if (size > ctx->num_bytes_left) {\n        return -1;\n    }\n    const uint8_t *dataptr = (const uint8_t *) data;\n    size_t bytes_to_store;\n    if (size + ctx->num_bytes_cached < 3) {\n        bytes_to_store = size;\n    } else {\n        bytes_to_store = (ctx->num_bytes_cached + size) % 3;\n    }\n    assert(bytes_to_store <= 2);\n\n    int retval = base64_ret_bytes_flush(ctx, &dataptr, size - bytes_to_store);\n    if (retval) {\n        return retval;\n    }\n    assert(ctx->num_bytes_cached + bytes_to_store <= sizeof(ctx->bytes_cached));\n    memcpy(&ctx->bytes_cached[ctx->num_bytes_cached], dataptr, bytes_to_store);\n    ctx->num_bytes_cached += bytes_to_store;\n    ctx->num_bytes_left -= bytes_to_store;\n    return 0;\n}\n\nstatic const anjay_ret_bytes_ctx_vtable_t BASE64_OUT_BYTES_VTABLE = {\n    .append = base64_ret_bytes_append\n};\n\nanjay_unlocked_ret_bytes_ctx_t *_anjay_base64_ret_bytes_ctx_new(\n        avs_stream_t *stream, avs_base64_config_t config, size_t length) {\n    base64_ret_bytes_ctx_t *ctx = (base64_ret_bytes_ctx_t *) avs_calloc(\n            1, sizeof(base64_ret_bytes_ctx_t));\n    if (ctx) {\n        ctx->vtable = &BASE64_OUT_BYTES_VTABLE;\n        ctx->stream = stream;\n        ctx->config = config;\n        ctx->num_bytes_left = length;\n    }\n    return (anjay_unlocked_ret_bytes_ctx_t *) ctx;\n}\n\nint _anjay_base64_ret_bytes_ctx_close(anjay_unlocked_ret_bytes_ctx_t *ctx_) {\n    base64_ret_bytes_ctx_t *ctx = (base64_ret_bytes_ctx_t *) ctx_;\n    if (ctx->num_bytes_left != 0) {\n        /* Some bytes were not written as we have expected */\n        return 0;\n    }\n    return base64_ret_encode_and_write(ctx, ctx->bytes_cached,\n                                       ctx->num_bytes_cached);\n}\n\nvoid _anjay_base64_ret_bytes_ctx_delete(anjay_unlocked_ret_bytes_ctx_t **ctx_) {\n    if (!ctx_ || !*ctx_) {\n        return;\n    }\n    base64_ret_bytes_ctx_t *ctx = (base64_ret_bytes_ctx_t *) *ctx_;\n    assert(ctx->vtable == &BASE64_OUT_BYTES_VTABLE);\n    avs_free(ctx);\n    *ctx_ = NULL;\n}\n"
  },
  {
    "path": "src/core/io/anjay_base64_out.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ANJAY_IO_BASE64_OUT_H\n#define ANJAY_IO_BASE64_OUT_H\n\n#include <avsystem/commons/avs_stream.h>\n\n#include <anjay/io.h>\n\n#include <anjay_modules/anjay_dm_utils.h>\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\nanjay_unlocked_ret_bytes_ctx_t *_anjay_base64_ret_bytes_ctx_new(\n        avs_stream_t *stream, avs_base64_config_t config, size_t length);\nint _anjay_base64_ret_bytes_ctx_close(anjay_unlocked_ret_bytes_ctx_t *ctx);\n\nvoid _anjay_base64_ret_bytes_ctx_delete(anjay_unlocked_ret_bytes_ctx_t **ctx);\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif /* ANJAY_IO_BASE64_OUT_H */\n"
  },
  {
    "path": "src/core/io/anjay_batch_builder.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#if defined(ANJAY_WITH_OBSERVE) || defined(ANJAY_WITH_SEND)\n\n#    include \"../anjay_access_utils_private.h\"\n#    include \"../anjay_utils_private.h\"\n#    include \"../dm/anjay_dm_read.h\"\n#    include \"anjay_batch_builder.h\"\n#    include \"anjay_vtable.h\"\n\n#    include <avsystem/commons/avs_list.h>\n#    include <avsystem/commons/avs_utils.h>\n\n#    ifdef ANJAY_WITH_THREAD_SAFETY\n#        include <avsystem/commons/avs_init_once.h>\n#    endif // ANJAY_WITH_THREAD_SAFETY\n\n#    include <anjay_modules/anjay_dm_utils.h>\n\n#    include <string.h>\n\nVISIBILITY_SOURCE_BEGIN\n\n#    define batch_log(level, ...) _anjay_log(batch_builder, level, __VA_ARGS__)\n\ntypedef enum {\n    ANJAY_BATCH_DATA_BYTES,\n    ANJAY_BATCH_DATA_STRING,\n    ANJAY_BATCH_DATA_INT,\n    ANJAY_BATCH_DATA_DOUBLE,\n    ANJAY_BATCH_DATA_BOOL,\n    ANJAY_BATCH_DATA_OBJLNK,\n    ANJAY_BATCH_DATA_START_AGGREGATE,\n#    ifdef ANJAY_WITH_LWM2M11\n    ANJAY_BATCH_DATA_UINT,\n#    endif // ANJAY_WITH_LWM2M11\n} anjay_batch_data_type_t;\n\ntypedef struct {\n    anjay_batch_data_type_t type;\n    union {\n        struct {\n            const void *data;\n            size_t length;\n        } bytes;\n        const char *string;\n        int64_t int_value;\n#    ifdef ANJAY_WITH_LWM2M11\n        uint64_t uint_value;\n#    endif // ANJAY_WITH_LWM2M11\n        double double_value;\n        bool bool_value;\n        struct {\n            anjay_oid_t oid;\n            anjay_iid_t iid;\n        } objlnk;\n    } value;\n} anjay_batch_data_t;\n\nstruct anjay_batch_entry {\n    anjay_uri_path_t path;\n    anjay_batch_data_t data;\n    avs_time_real_t timestamp;\n};\n\nstruct anjay_batch_struct {\n    AVS_LIST(anjay_batch_entry_t) list;\n    size_t ref_count;\n    avs_time_real_t compilation_time;\n};\n\nstruct anjay_batch_data_output_state_struct {\n    anjay_batch_entry_t entry;\n};\n\ntypedef struct {\n    const anjay_ret_bytes_ctx_vtable_t *vtable;\n    void *data;\n    size_t remaining_bytes;\n} builder_bytes_t;\n\ntypedef struct builder_out_struct {\n    anjay_unlocked_output_ctx_t base;\n    anjay_batch_builder_t *builder;\n    builder_bytes_t bytes;\n\n    anjay_uri_path_t root_path;\n    anjay_uri_path_t path;\n\n    avs_time_real_t timestamp;\n} builder_out_ctx_t;\n\nanjay_batch_builder_t *_anjay_batch_builder_new(void) {\n    anjay_batch_builder_t *builder =\n            (anjay_batch_builder_t *) avs_calloc(1,\n                                                 sizeof(anjay_batch_builder_t));\n    if (!builder) {\n        return NULL;\n    }\n    builder->append_ptr = &builder->list;\n    return builder;\n}\n\nstatic int make_data_with_duplicated_string(anjay_batch_data_t *batch_data,\n                                            const char *str) {\n    assert(batch_data);\n    assert(str);\n    char *new_str = avs_strdup(str);\n    if (!new_str) {\n        return -1;\n    }\n    *batch_data = (anjay_batch_data_t) {\n        .type = ANJAY_BATCH_DATA_STRING,\n        .value = {\n            .string = new_str\n        }\n    };\n    return 0;\n}\n\nstatic void batch_data_cleanup(anjay_batch_data_t *data) {\n    if (data->type == ANJAY_BATCH_DATA_STRING) {\n        avs_free((void *) (intptr_t) data->value.string);\n    } else if (data->type == ANJAY_BATCH_DATA_BYTES) {\n        avs_free((void *) (intptr_t) data->value.bytes.data);\n    }\n}\n\nstatic int batch_data_add(anjay_batch_builder_t *builder,\n                          const anjay_uri_path_t *uri,\n                          avs_time_real_t timestamp,\n                          anjay_batch_data_t data) {\n    assert(builder);\n    if (data.type != ANJAY_BATCH_DATA_START_AGGREGATE\n            && !_anjay_uri_path_has(uri, ANJAY_ID_RID)) {\n        batch_data_cleanup(&data);\n        return -1;\n    }\n    *builder->append_ptr = AVS_LIST_NEW_ELEMENT(anjay_batch_entry_t);\n    if (!*builder->append_ptr) {\n        batch_data_cleanup(&data);\n        return -1;\n    }\n    **builder->append_ptr = (anjay_batch_entry_t) {\n        .path = *uri,\n        .timestamp = timestamp,\n        .data = data\n    };\n    AVS_LIST_ADVANCE_PTR(&builder->append_ptr);\n    return 0;\n}\n\nint _anjay_batch_add_int(anjay_batch_builder_t *builder,\n                         const anjay_uri_path_t *uri,\n                         avs_time_real_t timestamp,\n                         int64_t value) {\n    const anjay_batch_data_t data = {\n        .type = ANJAY_BATCH_DATA_INT,\n        .value = {\n            .int_value = value\n        }\n    };\n    return batch_data_add(builder, uri, timestamp, data);\n}\n\n#    ifdef ANJAY_WITH_LWM2M11\nint _anjay_batch_add_uint(anjay_batch_builder_t *builder,\n                          const anjay_uri_path_t *uri,\n                          avs_time_real_t timestamp,\n                          uint64_t value) {\n    const anjay_batch_data_t data = {\n        .type = ANJAY_BATCH_DATA_UINT,\n        .value = {\n            .uint_value = value\n        }\n    };\n    return batch_data_add(builder, uri, timestamp, data);\n}\n#    endif // ANJAY_WITH_LWM2M11\n\nint _anjay_batch_add_double(anjay_batch_builder_t *builder,\n                            const anjay_uri_path_t *uri,\n                            avs_time_real_t timestamp,\n                            double value) {\n    const anjay_batch_data_t data = {\n        .type = ANJAY_BATCH_DATA_DOUBLE,\n        .value = {\n            .double_value = value\n        }\n    };\n    return batch_data_add(builder, uri, timestamp, data);\n}\n\nint _anjay_batch_add_bool(anjay_batch_builder_t *builder,\n                          const anjay_uri_path_t *uri,\n                          avs_time_real_t timestamp,\n                          bool value) {\n    const anjay_batch_data_t data = {\n        .type = ANJAY_BATCH_DATA_BOOL,\n        .value = {\n            .bool_value = value\n        }\n    };\n    return batch_data_add(builder, uri, timestamp, data);\n}\n\nint _anjay_batch_add_string(anjay_batch_builder_t *builder,\n                            const anjay_uri_path_t *uri,\n                            avs_time_real_t timestamp,\n                            const char *str) {\n    anjay_batch_data_t str_data;\n    if (make_data_with_duplicated_string(&str_data, str)) {\n        return -1;\n    }\n    return batch_data_add(builder, uri, timestamp, str_data);\n}\n\n#    ifdef ANJAY_WITH_LWM2M11\nstatic int make_data_with_duplicated_bytes(anjay_batch_data_t *batch_data,\n                                           const void *data,\n                                           size_t length) {\n    assert(batch_data);\n    if (!data && length) {\n        return -1;\n    }\n    void *new_data = NULL;\n\n    if (data && length) {\n        new_data = avs_malloc(length);\n        if (!new_data) {\n            return -1;\n        }\n        memcpy(new_data, data, length);\n    }\n\n    *batch_data = (anjay_batch_data_t) {\n        .type = ANJAY_BATCH_DATA_BYTES,\n        .value = {\n            .bytes = {\n                .data = new_data,\n                .length = length\n            }\n        }\n    };\n    return 0;\n}\n\nint _anjay_batch_add_bytes(anjay_batch_builder_t *builder,\n                           const anjay_uri_path_t *uri,\n                           avs_time_real_t timestamp,\n                           const void *data,\n                           size_t length) {\n    anjay_batch_data_t bytes_data;\n    if (make_data_with_duplicated_bytes(&bytes_data, data, length)) {\n        return -1;\n    }\n    return batch_data_add(builder, uri, timestamp, bytes_data);\n}\n#    endif // ANJAY_WITH_LWM2M11\n\nint _anjay_batch_add_objlnk(anjay_batch_builder_t *builder,\n                            const anjay_uri_path_t *uri,\n                            avs_time_real_t timestamp,\n                            anjay_oid_t objlnk_oid,\n                            anjay_iid_t objlnk_iid) {\n    const anjay_batch_data_t data = {\n        .type = ANJAY_BATCH_DATA_OBJLNK,\n        .value = {\n            .objlnk = {\n                .oid = objlnk_oid,\n                .iid = objlnk_iid\n            }\n        }\n    };\n    return batch_data_add(builder, uri, timestamp, data);\n}\n\nstatic void batch_entry_cleanup(void *entry_) {\n    anjay_batch_entry_t *entry = (anjay_batch_entry_t *) entry_;\n    batch_data_cleanup(&entry->data);\n}\n\nvoid _anjay_batch_entry_list_cleanup(AVS_LIST(anjay_batch_entry_t) *list) {\n    AVS_LIST_CLEAR(list) {\n        batch_entry_cleanup(*list);\n    }\n}\n\nvoid _anjay_batch_builder_cleanup(anjay_batch_builder_t **builder) {\n    if (builder && *builder) {\n        _anjay_batch_entry_list_cleanup(&(*builder)->list);\n        avs_free(*builder);\n        *builder = NULL;\n    }\n}\n\n#    ifdef ANJAY_WITH_THREAD_SAFETY\nstatic avs_init_once_handle_t REF_COUNT_MUTEX_INIT_HANDLE;\nstatic avs_mutex_t *REF_COUNT_MUTEX;\n\nstatic int init_ref_count_mutex(void *dummy) {\n    (void) dummy;\n    assert(!REF_COUNT_MUTEX);\n    return avs_mutex_create(&REF_COUNT_MUTEX);\n}\n\nstatic int ensure_ref_count_mutex_initialized(void) {\n    int result = avs_init_once(&REF_COUNT_MUTEX_INIT_HANDLE,\n                               init_ref_count_mutex, NULL);\n    if (result) {\n        batch_log(ERROR, _(\"Could not initialize thread safety for batches\"));\n    }\n    return result;\n}\n#    endif // ANJAY_WITH_THREAD_SAFETY\n\nanjay_batch_t *_anjay_batch_builder_compile(anjay_batch_builder_t **builder) {\n    assert(builder && *builder);\n#    ifdef ANJAY_WITH_THREAD_SAFETY\n    if (ensure_ref_count_mutex_initialized()) {\n        return NULL;\n    }\n    assert(REF_COUNT_MUTEX);\n#    endif // ANJAY_WITH_THREAD_SAFETY\n    anjay_batch_t *batch =\n            (anjay_batch_t *) avs_calloc(1, sizeof(anjay_batch_t));\n    if (!batch) {\n        return NULL;\n    }\n    batch->list = (*builder)->list;\n    batch->ref_count = 1;\n    batch->compilation_time = avs_time_real_now();\n    avs_free(*builder);\n    *builder = NULL;\n    return batch;\n}\n\nanjay_batch_t *_anjay_batch_acquire(const anjay_batch_t *batch_) {\n    assert(batch_);\n    anjay_batch_t *batch = (anjay_batch_t *) (intptr_t) batch_;\n#    ifdef ANJAY_WITH_THREAD_SAFETY\n    if (avs_mutex_lock(REF_COUNT_MUTEX)) {\n        batch_log(ERROR, _(\"Could not lock mutex\"));\n        return NULL;\n    }\n#    endif // ANJAY_WITH_THREAD_SAFETY\n    ++batch->ref_count;\n#    ifdef ANJAY_WITH_THREAD_SAFETY\n    avs_mutex_unlock(REF_COUNT_MUTEX);\n#    endif // ANJAY_WITH_THREAD_SAFETY\n    return batch;\n}\n\nvoid _anjay_batch_release(anjay_batch_t **batch) {\n    assert(batch && *batch);\n#    ifdef ANJAY_WITH_THREAD_SAFETY\n    int mutex_lock_result = avs_mutex_lock(REF_COUNT_MUTEX);\n    if (mutex_lock_result) {\n        batch_log(ERROR, _(\"Could not lock mutex\"));\n    }\n#    endif // ANJAY_WITH_THREAD_SAFETY\n    assert((*batch)->ref_count);\n    size_t old_count = ((*batch)->ref_count)--;\n#    ifdef ANJAY_WITH_THREAD_SAFETY\n    if (!mutex_lock_result) {\n        avs_mutex_unlock(REF_COUNT_MUTEX);\n    }\n#    endif // ANJAY_WITH_THREAD_SAFETY\n\n    if (old_count <= 1) {\n        _anjay_batch_entry_list_cleanup(&(*batch)->list);\n        avs_free(*batch);\n    }\n    *batch = NULL;\n}\n\n#    ifdef ANJAY_WITH_LWM2M11\nvoid _anjay_batch_update_common_path_prefix(const anjay_uri_path_t **prefix_ptr,\n                                            anjay_uri_path_t *prefix_buf,\n                                            const anjay_batch_t *batch) {\n    AVS_LIST(anjay_batch_entry_t) element;\n    AVS_LIST_FOREACH(element, batch->list) {\n        _anjay_uri_path_update_common_prefix(prefix_ptr, prefix_buf,\n                                             &element->path);\n    }\n}\n#    endif // ANJAY_WITH_LWM2M11\n\nstatic void value_returned(builder_out_ctx_t *ctx) {\n    ctx->path = MAKE_ROOT_PATH();\n}\n\nstatic int bytes_append(anjay_unlocked_ret_bytes_ctx_t *ctx,\n                        const void *data,\n                        size_t length) {\n    builder_bytes_t *bytes = (builder_bytes_t *) ctx;\n    if (length > bytes->remaining_bytes) {\n        batch_log(DEBUG,\n                  _(\"tried to write too many bytes, expected \") \"%u\" _(\n                          \", got \") \"%u\",\n                  (unsigned) bytes->remaining_bytes, (unsigned) length);\n        return -1;\n    }\n\n    memcpy(bytes->data, data, length);\n    bytes->data = ((uint8_t *) bytes->data) + length;\n    bytes->remaining_bytes -= length;\n    return 0;\n}\n\nstatic const anjay_ret_bytes_ctx_vtable_t BYTES_VTABLE = {\n    .append = bytes_append\n};\n\nstatic int bytes_begin(anjay_unlocked_output_ctx_t *ctx_,\n                       size_t length,\n                       anjay_unlocked_ret_bytes_ctx_t **out_bytes_ctx) {\n    builder_out_ctx_t *ctx = (builder_out_ctx_t *) ctx_;\n    if (ctx->bytes.remaining_bytes) {\n        batch_log(ERROR, _(\"bytes already being returned\"));\n        return -1;\n    }\n\n    if (!_anjay_uri_path_has(&ctx->path, ANJAY_ID_RID)) {\n        return -1;\n    }\n\n    void *buf = NULL;\n    if (length) {\n        buf = avs_malloc(length);\n        if (!buf) {\n            return -1;\n        }\n    }\n\n    anjay_batch_data_t data = {\n        .type = ANJAY_BATCH_DATA_BYTES,\n        .value.bytes = {\n            .data = buf,\n            .length = length\n        }\n    };\n\n    if (batch_data_add(ctx->builder, &ctx->path, ctx->timestamp, data)) {\n        avs_free(buf);\n        return -1;\n    }\n\n    value_returned(ctx);\n\n    // Doesn't change owner of buf\n    ctx->bytes.data = buf;\n    ctx->bytes.remaining_bytes = length;\n    *out_bytes_ctx = (anjay_unlocked_ret_bytes_ctx_t *) &ctx->bytes;\n    return 0;\n}\n\nstatic int ret_string(anjay_unlocked_output_ctx_t *ctx_, const char *str) {\n    builder_out_ctx_t *ctx = (builder_out_ctx_t *) ctx_;\n    int result = -1;\n    if (_anjay_uri_path_has(&ctx->path, ANJAY_ID_RID)) {\n        result = _anjay_batch_add_string(ctx->builder, &ctx->path,\n                                         ctx->timestamp, str);\n        value_returned(ctx);\n    }\n    return result;\n}\n\nstatic int ret_integer(anjay_unlocked_output_ctx_t *ctx_, int64_t value) {\n    builder_out_ctx_t *ctx = (builder_out_ctx_t *) ctx_;\n    int result = -1;\n    if (_anjay_uri_path_has(&ctx->path, ANJAY_ID_RID)) {\n        result = _anjay_batch_add_int(ctx->builder, &ctx->path, ctx->timestamp,\n                                      value);\n        value_returned(ctx);\n    }\n    return result;\n}\n\n#    ifdef ANJAY_WITH_LWM2M11\nstatic int ret_uint(anjay_unlocked_output_ctx_t *ctx_, uint64_t value) {\n    builder_out_ctx_t *ctx = (builder_out_ctx_t *) ctx_;\n    int result = -1;\n    if (_anjay_uri_path_has(&ctx->path, ANJAY_ID_RID)) {\n        result = _anjay_batch_add_uint(ctx->builder, &ctx->path, ctx->timestamp,\n                                       value);\n        value_returned(ctx);\n    }\n    return result;\n}\n#    endif // ANJAY_WITH_LWM2M11\n\nstatic int ret_double(anjay_unlocked_output_ctx_t *ctx_, double value) {\n    builder_out_ctx_t *ctx = (builder_out_ctx_t *) ctx_;\n    int result = -1;\n    if (_anjay_uri_path_has(&ctx->path, ANJAY_ID_RID)) {\n        result = _anjay_batch_add_double(ctx->builder, &ctx->path,\n                                         ctx->timestamp, value);\n        value_returned(ctx);\n    }\n    return result;\n}\n\nstatic int ret_bool(anjay_unlocked_output_ctx_t *ctx_, bool value) {\n    builder_out_ctx_t *ctx = (builder_out_ctx_t *) ctx_;\n    int result = -1;\n    if (_anjay_uri_path_has(&ctx->path, ANJAY_ID_RID)) {\n        result = _anjay_batch_add_bool(ctx->builder, &ctx->path, ctx->timestamp,\n                                       value);\n        value_returned(ctx);\n    }\n    return result;\n}\n\nstatic int ret_objlnk(anjay_unlocked_output_ctx_t *ctx_,\n                      anjay_oid_t objlnk_oid,\n                      anjay_iid_t objlnk_iid) {\n    builder_out_ctx_t *ctx = (builder_out_ctx_t *) ctx_;\n    int result = -1;\n    if (_anjay_uri_path_has(&ctx->path, ANJAY_ID_RID)) {\n        result = _anjay_batch_add_objlnk(ctx->builder,\n                                         &ctx->path,\n                                         ctx->timestamp,\n                                         objlnk_oid,\n                                         objlnk_iid);\n        value_returned(ctx);\n    }\n    return result;\n}\n\nstatic int ret_start_aggregate(anjay_unlocked_output_ctx_t *ctx_) {\n    builder_out_ctx_t *ctx = (builder_out_ctx_t *) ctx_;\n    int result = -1;\n    if (_anjay_uri_path_leaf_is(&ctx->path, ANJAY_ID_IID)\n            || _anjay_uri_path_leaf_is(&ctx->path, ANJAY_ID_RID)) {\n        result = batch_data_add(ctx->builder, &ctx->path, AVS_TIME_REAL_INVALID,\n                                (const anjay_batch_data_t) {\n                                    .type = ANJAY_BATCH_DATA_START_AGGREGATE\n                                });\n        // Start Aggregate MUST be followed by some kind of set_path(),\n        // so it's safe to treat it as a quasi-value of itself\n        value_returned(ctx);\n    }\n    return result;\n}\n\nstatic int set_path(anjay_unlocked_output_ctx_t *ctx_,\n                    const anjay_uri_path_t *path) {\n    builder_out_ctx_t *ctx = (builder_out_ctx_t *) ctx_;\n    AVS_ASSERT(!_anjay_uri_path_outside_base(path, &ctx->root_path),\n               \"Attempted to use batch builder context with resources outside \"\n               \"the declared root path\");\n    if (_anjay_uri_path_length(&ctx->path) > 0) {\n        batch_log(ERROR, _(\"Path already set\"));\n        return -1;\n    }\n    ctx->path = *path;\n    return 0;\n}\n\nstatic int clear_path(anjay_unlocked_output_ctx_t *ctx_) {\n    builder_out_ctx_t *ctx = (builder_out_ctx_t *) ctx_;\n    if (_anjay_uri_path_length(&ctx->path) == 0) {\n        batch_log(ERROR, _(\"Path not set\"));\n        return -1;\n    }\n    ctx->path = MAKE_ROOT_PATH();\n    return 0;\n}\n\nstatic int output_close(anjay_unlocked_output_ctx_t *ctx_) {\n    builder_out_ctx_t *ctx = (builder_out_ctx_t *) ctx_;\n    if (ctx->bytes.remaining_bytes) {\n        batch_log(ERROR,\n                  _(\"not all declared bytes passed by user, buffer is filled \"\n                    \"with random bytes\"));\n        return -1;\n    } else if (_anjay_uri_path_length(&ctx->path) > 0) {\n        batch_log(ERROR, _(\"set_path() called without returning a value\"));\n        return ANJAY_OUTCTXERR_ANJAY_RET_NOT_CALLED;\n    }\n    return 0;\n}\n\nstatic const anjay_output_ctx_vtable_t BUILDER_OUT_VTABLE = {\n    .bytes_begin = bytes_begin,\n    .string = ret_string,\n    .integer = ret_integer,\n#    ifdef ANJAY_WITH_LWM2M11\n    .uint = ret_uint,\n#    endif // ANJAY_WITH_LWM2M11\n    .floating = ret_double,\n    .boolean = ret_bool,\n    .objlnk = ret_objlnk,\n    .start_aggregate = ret_start_aggregate,\n    .set_path = set_path,\n    .clear_path = clear_path,\n    .close = output_close\n};\n\nstatic inline builder_out_ctx_t\nbuilder_out_ctx_new(anjay_batch_builder_t *builder,\n                    const anjay_uri_path_t *uri,\n                    const avs_time_real_t *forced_timestamp) {\n    builder_out_ctx_t ctx = {\n        .base = {\n            .vtable = &BUILDER_OUT_VTABLE\n        },\n        .builder = builder,\n        .bytes = {\n            .vtable = &BYTES_VTABLE,\n            .remaining_bytes = 0\n        },\n        .root_path = *uri,\n        .path = MAKE_ROOT_PATH(),\n        .timestamp = forced_timestamp ? *forced_timestamp : avs_time_real_now()\n    };\n    return ctx;\n}\n\nstatic int read_into_batch(anjay_batch_builder_t *builder,\n                           anjay_unlocked_t *anjay,\n                           const anjay_dm_installed_object_t *obj,\n                           const anjay_dm_path_info_t *path_info,\n                           anjay_ssid_t requesting_ssid,\n                           const avs_time_real_t *forced_timestamp) {\n    builder_out_ctx_t ctx =\n            builder_out_ctx_new(builder, &path_info->uri, forced_timestamp);\n    int retval = _anjay_dm_read(anjay, obj, path_info, requesting_ssid,\n                                (anjay_unlocked_output_ctx_t *) &ctx);\n    int close_retval = output_close((anjay_unlocked_output_ctx_t *) &ctx);\n    return close_retval ? close_retval : retval;\n}\n\nint _anjay_dm_read_into_batch(anjay_batch_builder_t *builder,\n                              anjay_unlocked_t *anjay,\n                              const anjay_dm_installed_object_t *obj,\n                              const anjay_dm_path_info_t *path_info,\n                              anjay_ssid_t requesting_ssid,\n                              const avs_time_real_t *forced_timestamp) {\n    assert(builder);\n    assert(anjay);\n    assert(!obj\n           || path_info->uri.ids[ANJAY_ID_OID]\n                      == _anjay_dm_installed_object_oid(obj));\n\n    AVS_LIST(anjay_batch_entry_t) *initial_append_ptr = builder->append_ptr;\n    int result = read_into_batch(builder, anjay, obj, path_info,\n                                 requesting_ssid, forced_timestamp);\n\n    // Despite of failure, the new element may be added. Remove it.\n    if (result) {\n        builder->append_ptr = initial_append_ptr;\n        _anjay_batch_entry_list_cleanup(builder->append_ptr);\n    }\n    return result;\n}\n\nstatic bool is_timestamp_absolute(avs_time_real_t timestamp) {\n    /**\n     * timestamp.since_real_epoch contatins time measured since reboot if no\n     * source of real time is provided. We assume that no device will run for\n     * SENML_TIME_SECONDS_THRESHOLD or longer without reboot.\n     */\n    return timestamp.since_real_epoch.seconds >= SENML_TIME_SECONDS_THRESHOLD;\n}\n\nstatic bool is_timestamp_relative(avs_time_real_t timestamp) {\n    return !is_timestamp_absolute(timestamp);\n}\n\nstatic double convert_to_senml_time(avs_time_real_t timestamp,\n                                    avs_time_real_t serialization_time) {\n    if (avs_time_real_before(serialization_time, timestamp)) {\n        batch_log(DEBUG, _(\"serialization time precedes timestamp, time \"\n                           \"measurement may be corrupted\"));\n        return NAN;\n    }\n    if (is_timestamp_absolute(timestamp)\n            && is_timestamp_absolute(serialization_time)) {\n        return avs_time_real_to_fscalar(timestamp, AVS_TIME_S);\n    } else if (is_timestamp_relative(timestamp)\n               && is_timestamp_relative(serialization_time)) {\n        double result = avs_time_duration_to_fscalar(\n                avs_time_real_diff(timestamp, serialization_time), AVS_TIME_S);\n        AVS_ASSERT(result <= 0, \"relative time must not be positive\");\n        return result;\n    } else {\n        batch_log(DEBUG, _(\"timestamp and serialization time should be both \"\n                           \"absolute or both relative\"));\n        return NAN;\n    }\n}\n\nstatic int serialize_batch_entry(const anjay_batch_entry_t *entry,\n                                 avs_time_real_t serialization_time,\n                                 anjay_unlocked_output_ctx_t *output) {\n    int result = _anjay_output_set_path(output, &entry->path);\n    if (result) {\n        return result;\n    }\n    if (avs_time_real_valid(entry->timestamp)\n            && (result = _anjay_output_set_time(\n                        output,\n                        convert_to_senml_time(entry->timestamp,\n                                              serialization_time)))) {\n        return result;\n    }\n    switch (entry->data.type) {\n    case ANJAY_BATCH_DATA_BYTES:\n        return _anjay_ret_bytes_unlocked(output,\n                                         entry->data.value.bytes.data,\n                                         entry->data.value.bytes.length);\n    case ANJAY_BATCH_DATA_STRING:\n        return _anjay_ret_string_unlocked(output, entry->data.value.string);\n    case ANJAY_BATCH_DATA_INT:\n        return _anjay_ret_i64_unlocked(output, entry->data.value.int_value);\n#    ifdef ANJAY_WITH_LWM2M11\n    case ANJAY_BATCH_DATA_UINT:\n        return _anjay_ret_u64_unlocked(output, entry->data.value.uint_value);\n#    endif // ANJAY_WITH_LWM2M11\n    case ANJAY_BATCH_DATA_DOUBLE:\n        return _anjay_ret_double_unlocked(output,\n                                          entry->data.value.double_value);\n    case ANJAY_BATCH_DATA_BOOL:\n        return _anjay_ret_bool_unlocked(output, entry->data.value.bool_value);\n    case ANJAY_BATCH_DATA_OBJLNK:\n        return _anjay_ret_objlnk_unlocked(output,\n                                          entry->data.value.objlnk.oid,\n                                          entry->data.value.objlnk.iid);\n    case ANJAY_BATCH_DATA_START_AGGREGATE:\n        return _anjay_output_start_aggregate(output);\n    default:\n        AVS_UNREACHABLE(\"invalid enum value\");\n        return -1;\n    }\n}\n\nint _anjay_batch_data_output(anjay_unlocked_t *anjay,\n                             const anjay_batch_t *batch,\n                             anjay_ssid_t target_ssid,\n                             anjay_unlocked_output_ctx_t *out_ctx) {\n    const avs_time_real_t serialization_time = avs_time_real_now();\n    const anjay_batch_data_output_state_t *state = NULL;\n    int result = 0;\n    do {\n        result = _anjay_batch_data_output_entry(\n                anjay, batch, target_ssid, serialization_time, &state, out_ctx);\n    } while (!result && state);\n    return result;\n}\n\nint _anjay_batch_data_output_entry(\n        anjay_unlocked_t *anjay,\n        const anjay_batch_t *batch,\n        anjay_ssid_t target_ssid,\n        avs_time_real_t serialization_time,\n        const anjay_batch_data_output_state_t **state,\n        anjay_unlocked_output_ctx_t *out_ctx) {\n    assert(state);\n    AVS_LIST(const anjay_batch_entry_t) it;\n    if (!*state) {\n        it = batch->list;\n    } else {\n        it = &(*state)->entry;\n        assert(AVS_LIST_FIND_PTR(&batch->list, it) != NULL);\n    }\n    while (it\n           && !_anjay_instance_action_allowed(\n                      anjay,\n                      &(const anjay_action_info_t) {\n#    ifdef ANJAY_WITH_LWM2M_GATEWAY\n                          .end_device = _anjay_uri_path_has_prefix(&it->path),\n#    endif // ANJAY_WITH_LWM2M_GATEWAY\n                          .oid = it->path.ids[ANJAY_ID_OID],\n                          .iid = it->path.ids[ANJAY_ID_IID],\n                          .ssid = target_ssid,\n                          .action = ANJAY_ACTION_READ\n                      })) {\n        AVS_LIST_ADVANCE((AVS_LIST(anjay_batch_entry_t) *) (intptr_t) &it);\n    }\n    int result = 0;\n    if (it) {\n        result = serialize_batch_entry(it, serialization_time, out_ctx);\n        AVS_LIST_ADVANCE((AVS_LIST(anjay_batch_entry_t) *) (intptr_t) &it);\n    }\n    *state = AVS_CONTAINER_OF(it, anjay_batch_data_output_state_t, entry);\n    return result;\n}\n\nstatic bool batch_data_equal(const anjay_batch_data_t *a,\n                             const anjay_batch_data_t *b) {\n    if (a->type != b->type) {\n        return false;\n    }\n    switch (a->type) {\n    case ANJAY_BATCH_DATA_BYTES:\n        return a->value.bytes.length == b->value.bytes.length\n               && !memcmp(a->value.bytes.data,\n                          b->value.bytes.data,\n                          a->value.bytes.length);\n    case ANJAY_BATCH_DATA_STRING:\n        return !strcmp(a->value.string, b->value.string);\n    case ANJAY_BATCH_DATA_INT:\n        return a->value.int_value == b->value.int_value;\n#    ifdef ANJAY_WITH_LWM2M11\n    case ANJAY_BATCH_DATA_UINT:\n        return a->value.uint_value == b->value.uint_value;\n#    endif // ANJAY_WITH_LWM2M11\n    case ANJAY_BATCH_DATA_DOUBLE:\n        return a->value.double_value == b->value.double_value;\n    case ANJAY_BATCH_DATA_BOOL:\n        return a->value.bool_value == b->value.bool_value;\n    case ANJAY_BATCH_DATA_OBJLNK:\n        return a->value.objlnk.oid == b->value.objlnk.oid\n               && a->value.objlnk.iid == b->value.objlnk.iid;\n    case ANJAY_BATCH_DATA_START_AGGREGATE:;\n    }\n    return true;\n}\n\nbool _anjay_batch_values_equal(const anjay_batch_t *a, const anjay_batch_t *b) {\n    if (!a || !b) {\n        return !a && !b;\n    }\n    AVS_LIST(anjay_batch_entry_t) ait = a->list;\n    AVS_LIST(anjay_batch_entry_t) bit = b->list;\n    while (ait && bit) {\n        if (!_anjay_uri_path_equal(&ait->path, &bit->path)\n                || !batch_data_equal(&ait->data, &bit->data)) {\n            return false;\n        }\n        AVS_LIST_ADVANCE(&ait);\n        AVS_LIST_ADVANCE(&bit);\n    }\n    return !ait && !bit;\n}\n\nbool _anjay_batch_data_requires_hierarchical_format(\n        const anjay_batch_t *batch) {\n    if (!batch || !batch->list || AVS_LIST_NEXT(batch->list)) {\n        // entry list is not exactly 1 element long\n        return true;\n    }\n    const anjay_batch_entry_t *const entry = batch->list;\n    if (entry->data.type == ANJAY_BATCH_DATA_START_AGGREGATE) {\n        // batch consists of an empty aggregate, so isn't a single simple value\n        return true;\n    }\n    assert(_anjay_uri_path_has(&entry->path, ANJAY_ID_RID));\n    return false;\n}\n\nint _anjay_batch_outputable_item_count(anjay_unlocked_t *anjay,\n                                       const anjay_batch_t *batch,\n                                       anjay_ssid_t target_ssid,\n                                       size_t *out_count) {\n    size_t count = 0;\n    if (batch) {\n        AVS_LIST(anjay_batch_entry_t) it;\n        AVS_LIST_FOREACH(it, batch->list) {\n            anjay_instance_action_allowed_stateless_result_t result =\n                    _anjay_instance_action_allowed_stateless(\n                            anjay,\n                            &(const anjay_action_info_t) {\n#    ifdef ANJAY_WITH_LWM2M_GATEWAY\n                                .end_device =\n                                        _anjay_uri_path_has_prefix(&it->path),\n#    endif // ANJAY_WITH_LWM2M_GATEWAY\n                                .oid = it->path.ids[ANJAY_ID_OID],\n                                .iid = it->path.ids[ANJAY_ID_IID],\n                                .ssid = target_ssid,\n                                .action = ANJAY_ACTION_READ\n                            });\n#    ifdef ANJAY_WITH_ACCESS_CONTROL\n            if (result == ANJAY_INSTANCE_ACTION_NEEDS_ACL_CHECK) {\n                return -1;\n            }\n#    endif // ANJAY_WITH_ACCESS_CONTROL\n            if (result == ANJAY_INSTANCE_ACTION_ALLOWED\n                    && it->data.type != ANJAY_BATCH_DATA_START_AGGREGATE) {\n                ++count;\n            }\n        }\n    }\n    *out_count = count;\n    return 0;\n}\n\ndouble _anjay_batch_data_numeric_value(const anjay_batch_t *batch) {\n    if (_anjay_batch_data_requires_hierarchical_format(batch)) {\n        // not a simple value\n        return NAN;\n    }\n    const anjay_batch_entry_t *const entry = batch->list;\n    switch (entry->data.type) {\n    case ANJAY_BATCH_DATA_INT:\n        return (double) entry->data.value.int_value;\n#    ifdef ANJAY_WITH_LWM2M11\n    case ANJAY_BATCH_DATA_UINT:\n        return (double) entry->data.value.uint_value;\n#    endif // ANJAY_WITH_LWM2M11\n    case ANJAY_BATCH_DATA_DOUBLE:\n        return entry->data.value.double_value;\n    default:\n        return NAN;\n    }\n}\n\nint _anjay_batch_data_boolean_value(const anjay_batch_t *batch,\n                                    bool *out_value) {\n    if (_anjay_batch_data_requires_hierarchical_format(batch)) {\n        // not a simple value\n        return -1;\n    }\n    const anjay_batch_entry_t *const entry = batch->list;\n    if (entry->data.type == ANJAY_BATCH_DATA_BOOL) {\n        if (out_value) {\n            *out_value = entry->data.value.bool_value;\n        }\n        return 0;\n    }\n    return -1;\n}\n\navs_time_real_t _anjay_batch_get_compilation_time(const anjay_batch_t *batch) {\n    return batch->compilation_time;\n}\n\n#    ifdef ANJAY_TEST\n#        include \"tests/core/io/batch_builder.c\"\n#        ifdef ANJAY_WITH_LWM2M11\n#            include \"tests/core/io/dm_batch.c\"\n#        endif // ANJAY_WITH_LWM2M11\n#    endif\n\n#endif // defined(ANJAY_WITH_OBSERVE) || defined(ANJAY_WITH_SEND)\n"
  },
  {
    "path": "src/core/io/anjay_batch_builder.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ANJAY_BATCH_BUILDER_H\n#define ANJAY_BATCH_BUILDER_H\n\n#include <anjay/anjay.h>\n\n#include \"../anjay_dm_core.h\"\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\n/**\n * From RFC8428 4.5.3. Time:\n * - Values less than 268,435,456 (2**28) represent time relative to the current\n *   time.\n * - Values greater than or equal to 2**28 represent an absolute time relative\n *   to the Unix epoch (1970-01-01T00:00Z in UTC time)\n */\n#define SENML_TIME_SECONDS_THRESHOLD (1 << 28)\n\ntypedef struct anjay_batch_entry anjay_batch_entry_t;\n\ntypedef struct anjay_batch_builder_struct {\n    AVS_LIST(anjay_batch_entry_t) list;\n    AVS_LIST(anjay_batch_entry_t) *append_ptr;\n} anjay_batch_builder_t;\n\ntypedef struct anjay_batch_struct anjay_batch_t;\n\ntypedef struct anjay_batch_data_output_state_struct\n        anjay_batch_data_output_state_t;\n\nanjay_batch_builder_t *_anjay_batch_builder_new(void);\n\n/**\n * Adds values of various types to the batch.\n *\n * @returns 0 on success, negative value otherwise.\n */\n/**@{*/\nint _anjay_batch_add_int(anjay_batch_builder_t *builder,\n                         const anjay_uri_path_t *uri,\n                         avs_time_real_t timestamp,\n                         int64_t value);\n\n#ifdef ANJAY_WITH_LWM2M11\nint _anjay_batch_add_uint(anjay_batch_builder_t *builder,\n                          const anjay_uri_path_t *uri,\n                          avs_time_real_t timestamp,\n                          uint64_t value);\n#endif // ANJAY_WITH_LWM2M11\n\nint _anjay_batch_add_double(anjay_batch_builder_t *builder,\n                            const anjay_uri_path_t *uri,\n                            avs_time_real_t timestamp,\n                            double value);\n\nint _anjay_batch_add_bool(anjay_batch_builder_t *builder,\n                          const anjay_uri_path_t *uri,\n                          avs_time_real_t timestamp,\n                          bool value);\n\nint _anjay_batch_add_string(anjay_batch_builder_t *builder,\n                            const anjay_uri_path_t *uri,\n                            avs_time_real_t timestamp,\n                            const char *str);\n\n#ifdef ANJAY_WITH_LWM2M11\nint _anjay_batch_add_bytes(anjay_batch_builder_t *builder,\n                           const anjay_uri_path_t *uri,\n                           avs_time_real_t timestamp,\n                           const void *data,\n                           size_t length);\n#endif // ANJAY_WITH_LWM2M11\n\nint _anjay_batch_add_objlnk(anjay_batch_builder_t *builder,\n                            const anjay_uri_path_t *uri,\n                            avs_time_real_t timestamp,\n                            anjay_oid_t objlnk_oid,\n                            anjay_iid_t objlnk_iid);\n/**@}*/\n\n/**\n * Releases batch builder and discards all data. It has no effect if builder was\n * previously compiled.\n *\n * @param builder Pointer to pointer to data builder. Set to NULL after cleanup.\n */\nvoid _anjay_batch_builder_cleanup(anjay_batch_builder_t **builder);\n\n/**\n * Compiles data from the batch builder into a reference-counted (with count\n * initialized to 1) immutable data batch.\n *\n * @param builder Pointer to pointer to batch builder. Set to NULL after\n *                successful return.\n *\n * @returns Pointer to compiled batch in case of success, NULL otherwise. If\n *          this function fails, batch builder is not modified and must be freed\n *          manually with @ref _anjay_batch_builder_cleanup() if it's not to be\n *          used anymore.\n */\nanjay_batch_t *_anjay_batch_builder_compile(anjay_batch_builder_t **builder);\n\n/**\n * Increments the refcount for a *batch.\n *\n * Note that conventionally, const anjay_batch_t * pointers are passed when\n * \"borrowing\" the batch, and non-const pointers when passing ownership of a\n * batch reference.\n *\n * @param batch Non-null batch which refcount will be incremented\n *\n * @returns @p batch\n */\nanjay_batch_t *_anjay_batch_acquire(const anjay_batch_t *batch);\n\n/**\n * Decreases the refcount for a *batch, sets it to NULL, and frees it if the\n * refcount has reached zero.\n *\n * @param *batch Pointer to compiled data batch.\n */\nvoid _anjay_batch_release(anjay_batch_t **batch);\n\nvoid _anjay_batch_entry_list_cleanup(AVS_LIST(anjay_batch_entry_t) *list);\n\nint _anjay_dm_read_into_batch(anjay_batch_builder_t *builder,\n                              anjay_unlocked_t *anjay,\n                              const anjay_dm_installed_object_t *obj,\n                              const anjay_dm_path_info_t *path_info,\n                              anjay_ssid_t requesting_ssid,\n                              const avs_time_real_t *forced_timestamp);\n\n/**\n * Filters content of the batch for server with specified @p target_ssid\n * according to Access Control permissions of this server. Then outputs the data\n * into @p out_ctx . The output will contain only those entries of @p data which\n * paths were configured by @ref anjay_access_control_set_acl with enabled\n * @c ANJAY_ACCESS_MASK_READ .\n *\n * @param anjay       Anjay object to operate on\n * @param batch       Compiled batch\n * @param target_ssid SSID of the server for which this batch is being\n *                    serialized\n * @param out_ctx     Output context to serialize into\n *\n * @returns 0 for success, or a negative value in case of error.\n */\nint _anjay_batch_data_output(anjay_unlocked_t *anjay,\n                             const anjay_batch_t *batch,\n                             anjay_ssid_t target_ssid,\n                             anjay_unlocked_output_ctx_t *out_ctx);\n\n/**\n * Serializes part of the batch associated with a single Resource or Resource\n * Instance.\n *\n * <example>\n * The following code is equivalent to @ref _anjay_batch_data_output:\n *\n * @code\n * const avs_time_real_t serialization_time = avs_time_real_now();\n * const anjay_batch_data_output_state_t *state = NULL;\n * int result = 0;\n * do {\n *     result = _anjay_batch_data_output_entry(\n *             anjay, batch, target_ssid, serialization_time, &state, out_ctx);\n * } while (!result && state);\n * @endcode\n * </example>\n *\n * @param [in]    anjay              Anjay object to operate on.\n *\n * @param [in]    batch              Compiled batch.\n *\n * @param [in]    target_ssid        SSID of the server for which this batch is\n *                                   being serialized.\n *\n * @param [in]    serialization_time Time point that shall be treated as the\n *                                   current time during serialization.\n *                                   Serialized values of timestamps may depend\n *                                   on it, so the same time SHOULD be passed to\n *                                   all calls in a given iteration.\n *\n * @param [inout] state              Pointer to a state variable. Before the\n *                                   initial call, <c>*state</c> shall be\n *                                   <c>NULL</c> - this function will then\n *                                   serialize the first batch element, and set\n *                                   <c>*state</c> so that the next call will\n *                                   serialize the subsequent entry. If\n *                                   <c>state</c> is <c>NULL</c> on return, it\n *                                   means that there are no more entries to\n *                                   serialize.\n *\n *                                   The value of <c>*state</c> shall be treated\n *                                   as opaque - the caller MUST NOT ever\n *                                   attempt to dereference, deallocate or\n *                                   otherwise access it in any way other than\n *                                   passing it to\n *                                   @ref _anjay_batch_data_output_entry again.\n *                                   Iteration does not allocate any additional\n *                                   resources, so it is memory-safe, and does\n *                                   not require any additional deallocation, to\n *                                   stop serializing without reaching the end.\n *\n * @param [in]    out_ctx            Output context to serialize into.\n *\n * @returns 0 for success, or a negative value in case of error. On error, the\n *          value of <c>*state</c> shall be treated as invalid.\n *\n * If <c>*state</c> is neither <c>NULL</c> nor a value set by a previous call to\n * this function with otherwise the same set of arguments, the behaviour is\n * undefined.\n */\nint _anjay_batch_data_output_entry(\n        anjay_unlocked_t *anjay,\n        const anjay_batch_t *batch,\n        anjay_ssid_t target_ssid,\n        avs_time_real_t serialization_time,\n        const anjay_batch_data_output_state_t **state,\n        anjay_unlocked_output_ctx_t *out_ctx);\n\n/**\n * Calculates the number of entries that will be generated in a SenML document\n * if @ref _anjay_batch_data_output_entry is called on a given @p batch.\n *\n * This is calculated only if it's possible to do so without accessing the data\n * model. That might not be true if the Access Control mechanism is in use, i.e.\n * it is enabled and there are multiple LwM2M Server Accounts actually\n * configured.\n *\n * @param [in]    anjay       Anjay object to operate on.\n *\n * @param [in]    batch       Compiled batch.\n *\n * @param [in]    target_ssid SSID of the server for which this batch is being\n *                            serialized. Will be used in relation to the Access\n *                            Control mechanism.\n *\n * @param [out]   out_count   Pointer to a variable that, on successful return,\n *                            will be filled with the calculated number of\n *                            items.\n *\n * @returns 0 for success, or a negative value if the count depends on the data\n *          in the Access Control object.\n */\nint _anjay_batch_outputable_item_count(anjay_unlocked_t *anjay,\n                                       const anjay_batch_t *batch,\n                                       anjay_ssid_t target_ssid,\n                                       size_t *out_count);\n\n/**\n * Returns whether two batches have exactly the same data.\n *\n * NOTE: Timestamps are NOT taken into account in comparisons.\n *\n * NOTE: Batches that contain the same data set, but in different order, are\n * treated as different.\n *\n * NOTE: Data that has equivalent value, but different data type (e.g. int(42)\n * and double(42.0)) is treated as different.\n */\nbool _anjay_batch_values_equal(const anjay_batch_t *a, const anjay_batch_t *b);\n\nbool _anjay_batch_data_requires_hierarchical_format(const anjay_batch_t *batch);\n\n/**\n * If batch consists of a single entry pertaining to a Single Resource or\n * Resource Instance, with a value of numeric type (int, uint or double), then\n * returns its numerical value. Otherwise returns NAN.\n */\ndouble _anjay_batch_data_numeric_value(const anjay_batch_t *batch);\n\n/**\n * If batch consists of a single entry pertaining to a Single Resource or\n * Resource Instance, with a value of boolean type, then writes the value to\n * @c out_value pointer and returns 0. Otherwise returns -1.\n */\nint _anjay_batch_data_boolean_value(const anjay_batch_t *batch,\n                                    bool *out_value);\n\n/**\n * Returns the time when the batch was returned from @ref\n * _anjay_batch_builder_compile\n */\navs_time_real_t _anjay_batch_get_compilation_time(const anjay_batch_t *batch);\n\n#ifdef ANJAY_WITH_LWM2M11\nvoid _anjay_batch_update_common_path_prefix(const anjay_uri_path_t **prefix_ptr,\n                                            anjay_uri_path_t *prefix_buf,\n                                            const anjay_batch_t *batch);\n#endif // ANJAY_WITH_LWM2M11\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif // ANJAY_BATCH_BUILDER_H\n"
  },
  {
    "path": "src/core/io/anjay_cbor_in.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#ifdef ANJAY_WITH_CBOR\n\n#    include <ctype.h>\n#    include <errno.h>\n#    include <inttypes.h>\n#    include <limits.h>\n#    include <stdlib.h>\n#    include <string.h>\n\n#    include <avsystem/commons/avs_stream.h>\n#    include <avsystem/commons/avs_stream_membuf.h>\n#    include <avsystem/commons/avs_utils.h>\n\n#    include <anjay/core.h>\n\n#    include \"../anjay_utils_private.h\"\n#    include \"../coap/anjay_content_format.h\"\n#    include \"anjay_common.h\"\n#    include \"anjay_json_like_decoder.h\"\n#    include \"anjay_vtable.h\"\n\n#    include \"cbor/anjay_json_like_cbor_decoder.h\"\n\nVISIBILITY_SOURCE_BEGIN\n\ntypedef struct {\n    const anjay_input_ctx_vtable_t *vtable;\n    avs_stream_t *stream;\n    anjay_uri_path_t request_uri;\n    bool msg_finished;\n\n    anjay_json_like_decoder_t *cbor_decoder;\n\n    bool is_bytes_ctx;\n    anjay_io_cbor_bytes_ctx_t bytes_ctx;\n} cbor_in_t;\n\nstatic int cbor_get_some_bytes(anjay_unlocked_input_ctx_t *ctx_,\n                               size_t *out_bytes_read,\n                               bool *out_msg_finished,\n                               void *out_buf,\n                               size_t buf_size) {\n    cbor_in_t *ctx = (cbor_in_t *) ctx_;\n    *out_msg_finished = false;\n    *out_bytes_read = 0;\n\n    anjay_json_like_value_type_t value_type;\n    if (_anjay_json_like_decoder_current_value_type(ctx->cbor_decoder,\n                                                    &value_type)\n            || value_type != ANJAY_JSON_LIKE_VALUE_BYTE_STRING) {\n        return ANJAY_ERR_BAD_REQUEST;\n    }\n\n    if (!ctx->is_bytes_ctx) {\n        if (_anjay_io_cbor_get_bytes_ctx(ctx->cbor_decoder, &ctx->bytes_ctx)) {\n            return -1;\n        }\n        ctx->is_bytes_ctx = true;\n    }\n\n    if (_anjay_io_cbor_get_some_bytes(ctx->cbor_decoder,\n                                      &ctx->bytes_ctx,\n                                      out_buf,\n                                      buf_size,\n                                      out_bytes_read,\n                                      out_msg_finished)) {\n        return -1;\n    }\n    ctx->msg_finished = *out_msg_finished;\n    if (*out_msg_finished) {\n        ctx->is_bytes_ctx = false;\n    }\n    return 0;\n}\n\nstatic int cbor_get_string(anjay_unlocked_input_ctx_t *ctx_,\n                           char *out_buf,\n                           size_t buf_size) {\n    assert(buf_size);\n    cbor_in_t *ctx = (cbor_in_t *) ctx_;\n\n    anjay_json_like_value_type_t value_type;\n    if (_anjay_json_like_decoder_current_value_type(ctx->cbor_decoder,\n                                                    &value_type)\n            || value_type != ANJAY_JSON_LIKE_VALUE_TEXT_STRING) {\n        return ANJAY_ERR_BAD_REQUEST;\n    }\n\n    if (!ctx->is_bytes_ctx) {\n        if (_anjay_io_cbor_get_bytes_ctx(ctx->cbor_decoder, &ctx->bytes_ctx)) {\n            return -1;\n        }\n        ctx->is_bytes_ctx = true;\n    }\n\n    size_t bytes_read;\n    if (_anjay_io_cbor_get_some_bytes(ctx->cbor_decoder,\n                                      &ctx->bytes_ctx,\n                                      out_buf,\n                                      // make space for null terminator\n                                      buf_size - 1,\n                                      &bytes_read,\n                                      &ctx->msg_finished)) {\n        return -1;\n    }\n\n    assert(bytes_read < buf_size);\n    out_buf[bytes_read] = '\\0';\n    if (!ctx->msg_finished) {\n        return ANJAY_BUFFER_TOO_SHORT;\n    }\n    ctx->is_bytes_ctx = false;\n    return 0;\n}\n\nstatic int cbor_get_integer(anjay_unlocked_input_ctx_t *ctx_, int64_t *value) {\n    cbor_in_t *ctx = (cbor_in_t *) ctx_;\n\n    anjay_json_like_value_type_t value_type;\n    int result = _anjay_json_like_decoder_current_value_type(ctx->cbor_decoder,\n                                                             &value_type);\n    if (result\n            || (value_type != ANJAY_JSON_LIKE_VALUE_UINT\n                && value_type != ANJAY_JSON_LIKE_VALUE_NEGATIVE_INT\n                && value_type != ANJAY_JSON_LIKE_VALUE_FLOAT\n                && value_type != ANJAY_JSON_LIKE_VALUE_DOUBLE)) {\n        return ANJAY_ERR_BAD_REQUEST;\n    }\n\n    anjay_json_like_number_t decoded_value;\n    int retval =\n            _anjay_json_like_decoder_number(ctx->cbor_decoder, &decoded_value);\n\n    if (retval) {\n        return retval;\n    }\n\n    switch (decoded_value.type) {\n    case ANJAY_JSON_LIKE_VALUE_UINT:\n        if (decoded_value.value.u64 > INT64_MAX) {\n            return -1;\n        }\n        *value = (int64_t) decoded_value.value.u64;\n        break;\n\n    case ANJAY_JSON_LIKE_VALUE_NEGATIVE_INT:\n        *value = decoded_value.value.i64;\n        break;\n\n    case ANJAY_JSON_LIKE_VALUE_FLOAT:\n        if (avs_double_convertible_to_int64(decoded_value.value.f32)) {\n            *value = (int64_t) decoded_value.value.f32;\n        } else {\n            return ANJAY_ERR_BAD_REQUEST;\n        }\n        break;\n\n    case ANJAY_JSON_LIKE_VALUE_DOUBLE:\n        if (avs_double_convertible_to_int64(decoded_value.value.f64)) {\n            *value = (int64_t) decoded_value.value.f64;\n        } else {\n            return ANJAY_ERR_BAD_REQUEST;\n        }\n        break;\n\n    default:\n        AVS_UNREACHABLE(\"Bug in CBOR decoder\");\n        return -1;\n    }\n\n    ctx->msg_finished = true;\n    return 0;\n}\n\nstatic int cbor_get_uint(anjay_unlocked_input_ctx_t *ctx_, uint64_t *value) {\n    cbor_in_t *ctx = (cbor_in_t *) ctx_;\n\n    anjay_json_like_value_type_t value_type;\n    int result = _anjay_json_like_decoder_current_value_type(ctx->cbor_decoder,\n                                                             &value_type);\n    if (result\n            || (value_type != ANJAY_JSON_LIKE_VALUE_UINT\n                && value_type != ANJAY_JSON_LIKE_VALUE_FLOAT\n                && value_type != ANJAY_JSON_LIKE_VALUE_DOUBLE)) {\n        return ANJAY_ERR_BAD_REQUEST;\n    }\n\n    anjay_json_like_number_t decoded_value;\n    int retval =\n            _anjay_json_like_decoder_number(ctx->cbor_decoder, &decoded_value);\n    if (retval) {\n        return retval;\n    }\n\n    switch (decoded_value.type) {\n    case ANJAY_JSON_LIKE_VALUE_UINT:\n        *value = decoded_value.value.u64;\n        break;\n\n    case ANJAY_JSON_LIKE_VALUE_FLOAT:\n        if (avs_double_convertible_to_uint64(decoded_value.value.f32)) {\n            *value = (uint64_t) decoded_value.value.f32;\n        } else {\n            return ANJAY_ERR_BAD_REQUEST;\n        }\n        break;\n\n    case ANJAY_JSON_LIKE_VALUE_DOUBLE:\n        if (avs_double_convertible_to_uint64(decoded_value.value.f64)) {\n            *value = (uint64_t) decoded_value.value.f64;\n        } else {\n            return ANJAY_ERR_BAD_REQUEST;\n        }\n        break;\n\n    default:\n        AVS_UNREACHABLE(\"Bug in CBOR decoder\");\n        return -1;\n    }\n\n    ctx->msg_finished = true;\n    return 0;\n}\n\nstatic int cbor_get_bool(anjay_unlocked_input_ctx_t *ctx_, bool *value) {\n    cbor_in_t *ctx = (cbor_in_t *) ctx_;\n    anjay_json_like_value_type_t value_type;\n    int result = _anjay_json_like_decoder_current_value_type(ctx->cbor_decoder,\n                                                             &value_type);\n    if (result || value_type != ANJAY_JSON_LIKE_VALUE_BOOL) {\n        return ANJAY_ERR_BAD_REQUEST;\n    }\n    ctx->msg_finished = true;\n    return _anjay_json_like_decoder_bool(ctx->cbor_decoder, value);\n}\n\nstatic int cbor_get_double(anjay_unlocked_input_ctx_t *ctx_, double *value) {\n    cbor_in_t *ctx = (cbor_in_t *) ctx_;\n\n    anjay_json_like_value_type_t value_type;\n    int result = _anjay_json_like_decoder_current_value_type(ctx->cbor_decoder,\n                                                             &value_type);\n    if (result\n            || (value_type != ANJAY_JSON_LIKE_VALUE_FLOAT\n                && value_type != ANJAY_JSON_LIKE_VALUE_DOUBLE\n                && value_type != ANJAY_JSON_LIKE_VALUE_NEGATIVE_INT\n                && value_type != ANJAY_JSON_LIKE_VALUE_UINT)) {\n        return ANJAY_ERR_BAD_REQUEST;\n    }\n\n    anjay_json_like_number_t decoded_value;\n    int retval =\n            _anjay_json_like_decoder_number(ctx->cbor_decoder, &decoded_value);\n    if (retval) {\n        return retval;\n    }\n\n    switch (decoded_value.type) {\n    case ANJAY_JSON_LIKE_VALUE_FLOAT:\n        *value = decoded_value.value.f32;\n        break;\n\n    case ANJAY_JSON_LIKE_VALUE_DOUBLE:\n        *value = decoded_value.value.f64;\n        break;\n\n    case ANJAY_JSON_LIKE_VALUE_UINT:\n        *value = (double) decoded_value.value.u64;\n        break;\n\n    case ANJAY_JSON_LIKE_VALUE_NEGATIVE_INT:\n        *value = (double) decoded_value.value.i64;\n        break;\n\n    default:\n        AVS_UNREACHABLE(\"Bug in CBOR decoder\");\n        return -1;\n    }\n\n    ctx->msg_finished = true;\n    return 0;\n}\n\nstatic int cbor_get_objlnk(anjay_unlocked_input_ctx_t *ctx_,\n                           anjay_oid_t *out_oid,\n                           anjay_iid_t *out_iid) {\n    char buf[MAX_OBJLNK_STRING_SIZE];\n\n    int retval = cbor_get_string(ctx_, buf, sizeof(buf));\n    if (retval) {\n        if (retval == ANJAY_BUFFER_TOO_SHORT) {\n            retval = ANJAY_ERR_BAD_REQUEST;\n        }\n        return retval;\n    }\n\n    if (_anjay_io_parse_objlnk(buf, out_oid, out_iid)) {\n        return ANJAY_ERR_BAD_REQUEST;\n    }\n    return 0;\n}\n\nstatic int cbor_in_close(anjay_unlocked_input_ctx_t *ctx_) {\n    cbor_in_t *ctx = (cbor_in_t *) ctx_;\n    _anjay_json_like_decoder_delete(&ctx->cbor_decoder);\n    return 0;\n}\n\nstatic int cbor_get_path(anjay_unlocked_input_ctx_t *ctx,\n                         anjay_uri_path_t *out_path,\n                         bool *out_is_array) {\n    cbor_in_t *in = (cbor_in_t *) ctx;\n    if (in->msg_finished) {\n        return ANJAY_GET_PATH_END;\n    }\n    if (!_anjay_uri_path_has(&in->request_uri, ANJAY_ID_RID)) {\n        return ANJAY_ERR_BAD_REQUEST;\n    }\n    *out_is_array = false;\n    *out_path = in->request_uri;\n    return 0;\n}\n\nstatic int cbor_next_entry(anjay_unlocked_input_ctx_t *ctx) {\n    (void) ctx;\n    return 0;\n}\n\nstatic const anjay_input_ctx_vtable_t CBOR_IN_VTABLE = {\n    .some_bytes = cbor_get_some_bytes,\n    .string = cbor_get_string,\n    .integer = cbor_get_integer,\n    .uint = cbor_get_uint,\n    .floating = cbor_get_double,\n    .boolean = cbor_get_bool,\n    .objlnk = cbor_get_objlnk,\n    .get_path = cbor_get_path,\n    .next_entry = cbor_next_entry,\n    .close = cbor_in_close\n};\n\nint _anjay_input_cbor_create(anjay_unlocked_input_ctx_t **out,\n                             avs_stream_t *stream_ptr,\n                             const anjay_uri_path_t *request_uri) {\n    cbor_in_t *ctx = (cbor_in_t *) avs_calloc(1, sizeof(cbor_in_t));\n    *out = (anjay_unlocked_input_ctx_t *) ctx;\n    if (!ctx) {\n        return -1;\n    }\n\n    ctx->vtable = &CBOR_IN_VTABLE;\n    ctx->stream = stream_ptr;\n    ctx->request_uri = request_uri ? *request_uri : MAKE_ROOT_PATH();\n    ctx->cbor_decoder =\n            _anjay_cbor_decoder_new(stream_ptr,\n                                    MAX_SIMPLE_CBOR_NEST_STACK_SIZE);\n\n    if (!ctx->cbor_decoder) {\n        avs_free(ctx);\n        return -1;\n    }\n    return 0;\n}\n\n#    ifdef ANJAY_TEST\n#        include \"tests/core/io/raw_cbor_in.c\"\n#    endif // ANJAY_TEST\n\n#endif // ANJAY_WITH_CBOR\n"
  },
  {
    "path": "src/core/io/anjay_cbor_out.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#ifdef ANJAY_WITH_CBOR\n\n#    include <ctype.h>\n#    include <errno.h>\n#    include <inttypes.h>\n#    include <limits.h>\n#    include <stdlib.h>\n#    include <string.h>\n\n#    include <avsystem/commons/avs_stream.h>\n#    include <avsystem/commons/avs_utils.h>\n\n#    include <anjay/core.h>\n\n#    include \"anjay_common.h\"\n#    include \"anjay_vtable.h\"\n\n#    include \"cbor/anjay_cbor_encoder_ll.h\"\n\nVISIBILITY_SOURCE_BEGIN\n\ntypedef enum { STATE_INITIAL, STATE_PATH_SET, STATE_FINISHED } cbor_out_state_t;\n\ntypedef struct {\n    const anjay_ret_bytes_ctx_vtable_t *vtable;\n    size_t num_bytes_left;\n} cbor_ret_bytes_ctx_t;\n\ntypedef struct {\n    anjay_unlocked_output_ctx_t base;\n    cbor_ret_bytes_ctx_t bytes;\n    avs_stream_t *stream;\n    cbor_out_state_t state;\n} cbor_out_t;\n\nstatic int cbor_bytes_append(anjay_unlocked_ret_bytes_ctx_t *ctx_,\n                             const void *data,\n                             size_t data_len) {\n    cbor_ret_bytes_ctx_t *ctx = (cbor_ret_bytes_ctx_t *) ctx_;\n    if (!data_len) {\n        return 0;\n    }\n    if (data_len > ctx->num_bytes_left) {\n        return -1;\n    }\n    cbor_out_t *cbor_ctx = AVS_CONTAINER_OF(ctx, cbor_out_t, bytes);\n    int retval = _anjay_cbor_ll_bytes_append(cbor_ctx->stream, data, data_len);\n\n    if (!retval) {\n        ctx->num_bytes_left -= data_len;\n    }\n    if (!ctx->num_bytes_left) {\n        cbor_ctx->state = STATE_FINISHED;\n    }\n\n    return retval;\n}\n\nstatic const anjay_ret_bytes_ctx_vtable_t CBOR_OUT_BYTES_VTABLE = {\n    .append = cbor_bytes_append\n};\n\nstatic int\ncbor_ret_bytes_begin(anjay_unlocked_output_ctx_t *ctx_,\n                     size_t length,\n                     anjay_unlocked_ret_bytes_ctx_t **out_bytes_ctx) {\n    cbor_out_t *ctx = (cbor_out_t *) ctx_;\n    if (ctx->bytes.num_bytes_left || ctx->state != STATE_PATH_SET) {\n        return -1;\n    }\n    if (_anjay_cbor_ll_bytes_begin(ctx->stream, length)) {\n        return -1;\n    }\n\n    ctx->bytes.vtable = &CBOR_OUT_BYTES_VTABLE;\n    ctx->bytes.num_bytes_left = length;\n    if (length == 0) {\n        ctx->state = STATE_FINISHED;\n    }\n    *out_bytes_ctx = (anjay_unlocked_ret_bytes_ctx_t *) &ctx->bytes;\n    return 0;\n}\n\nstatic int cbor_ret_string(anjay_unlocked_output_ctx_t *ctx_,\n                           const char *value) {\n    cbor_out_t *ctx = (cbor_out_t *) ctx_;\n    if (ctx->bytes.num_bytes_left) {\n        return -1;\n    }\n\n    int retval = -1;\n    if (ctx->state == STATE_PATH_SET\n            && !(retval = _anjay_cbor_ll_encode_string(ctx->stream, value))) {\n        ctx->state = STATE_FINISHED;\n    }\n    return retval;\n}\n\nstatic int cbor_ret_integer(anjay_unlocked_output_ctx_t *ctx_, int64_t value) {\n    cbor_out_t *ctx = (cbor_out_t *) ctx_;\n    if (ctx->bytes.num_bytes_left) {\n        return -1;\n    }\n\n    int retval = -1;\n    if (ctx->state == STATE_PATH_SET\n            && !(retval = _anjay_cbor_ll_encode_int(ctx->stream, value))) {\n        ctx->state = STATE_FINISHED;\n    }\n    return retval;\n}\n\nstatic int cbor_ret_uint(anjay_unlocked_output_ctx_t *ctx_, uint64_t value) {\n    cbor_out_t *ctx = (cbor_out_t *) ctx_;\n    if (ctx->bytes.num_bytes_left) {\n        return -1;\n    }\n\n    int retval = -1;\n    if (ctx->state == STATE_PATH_SET\n            && !(retval = _anjay_cbor_ll_encode_uint(ctx->stream, value))) {\n        ctx->state = STATE_FINISHED;\n    }\n    return retval;\n}\n\nstatic int cbor_ret_double(anjay_unlocked_output_ctx_t *ctx_, double value) {\n    cbor_out_t *ctx = (cbor_out_t *) ctx_;\n    if (ctx->bytes.num_bytes_left) {\n        return -1;\n    }\n\n    int retval = -1;\n    if (ctx->state == STATE_PATH_SET\n            && !(retval = _anjay_cbor_ll_encode_double(ctx->stream, value))) {\n        ctx->state = STATE_FINISHED;\n    }\n    return retval;\n}\n\nstatic int cbor_ret_bool(anjay_unlocked_output_ctx_t *ctx_, bool value) {\n    cbor_out_t *ctx = (cbor_out_t *) ctx_;\n    if (ctx->bytes.num_bytes_left) {\n        return -1;\n    }\n\n    int retval = -1;\n    if (ctx->state == STATE_PATH_SET\n            && !(retval = _anjay_cbor_ll_encode_bool(ctx->stream, value))) {\n        ctx->state = STATE_FINISHED;\n    }\n    return retval;\n}\n\nstatic int cbor_ret_objlnk(anjay_unlocked_output_ctx_t *ctx_,\n                           anjay_oid_t oid,\n                           anjay_iid_t iid) {\n    char objlnk[MAX_OBJLNK_STRING_SIZE];\n    int retval = avs_simple_snprintf(\n            objlnk, sizeof(objlnk), \"%\" PRIu16 \":%\" PRIu16, oid, iid);\n    assert(retval > 0);\n    (void) retval;\n\n    return cbor_ret_string(ctx_, objlnk);\n}\n\nstatic int cbor_set_path(anjay_unlocked_output_ctx_t *ctx_,\n                         const anjay_uri_path_t *path) {\n    cbor_out_t *ctx = (cbor_out_t *) ctx_;\n    if (ctx->state == STATE_PATH_SET) {\n        return -1;\n    } else if (ctx->state != STATE_INITIAL || ctx->bytes.num_bytes_left\n               || !_anjay_uri_path_has(path, ANJAY_ID_RID)) {\n        return ANJAY_OUTCTXERR_FORMAT_MISMATCH;\n    }\n    ctx->state = STATE_PATH_SET;\n    return 0;\n}\n\nstatic int cbor_clear_path(anjay_unlocked_output_ctx_t *ctx_) {\n    cbor_out_t *ctx = (cbor_out_t *) ctx_;\n    if (ctx->state != STATE_PATH_SET || ctx->bytes.num_bytes_left) {\n        return -1;\n    }\n    ctx->state = STATE_INITIAL;\n    return 0;\n}\n\nstatic int cbor_ret_close(anjay_unlocked_output_ctx_t *ctx_) {\n    cbor_out_t *ctx = (cbor_out_t *) ctx_;\n    int result = 0;\n    if (ctx->state != STATE_FINISHED) {\n        result = ANJAY_OUTCTXERR_ANJAY_RET_NOT_CALLED;\n    }\n    return result;\n}\n\nstatic const anjay_output_ctx_vtable_t CBOR_OUT_VTABLE = {\n    .bytes_begin = cbor_ret_bytes_begin,\n    .string = cbor_ret_string,\n    .integer = cbor_ret_integer,\n    .uint = cbor_ret_uint,\n    .floating = cbor_ret_double,\n    .boolean = cbor_ret_bool,\n    .objlnk = cbor_ret_objlnk,\n    .set_path = cbor_set_path,\n    .clear_path = cbor_clear_path,\n    .close = cbor_ret_close\n};\n\nanjay_unlocked_output_ctx_t *_anjay_output_cbor_create(avs_stream_t *stream) {\n    cbor_out_t *ctx = (cbor_out_t *) avs_calloc(1, sizeof(cbor_out_t));\n    if (ctx) {\n        ctx->base.vtable = &CBOR_OUT_VTABLE;\n        ctx->stream = stream;\n    }\n    return (anjay_unlocked_output_ctx_t *) ctx;\n}\n\n#endif // ANJAY_WITH_CBOR\n"
  },
  {
    "path": "src/core/io/anjay_common.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#include \"anjay_common.h\"\n\nVISIBILITY_SOURCE_BEGIN\n\nint _anjay_io_parse_objlnk(char *objlnk,\n                           anjay_oid_t *out_oid,\n                           anjay_iid_t *out_iid) {\n    char *colon = strchr(objlnk, ':');\n    if (!colon) {\n        return -1;\n    }\n    *colon = '\\0';\n    long long oid;\n    long long iid;\n    if (_anjay_safe_strtoll(objlnk, &oid)\n            || _anjay_safe_strtoll(colon + 1, &iid) || oid < 0\n            || oid > UINT16_MAX || iid < 0 || iid > UINT16_MAX) {\n        return -1;\n    }\n    *out_oid = (anjay_oid_t) oid;\n    *out_iid = (anjay_iid_t) iid;\n    return 0;\n}\n"
  },
  {
    "path": "src/core/io/anjay_common.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ANJAY_IO_COMMON_H\n#define ANJAY_IO_COMMON_H\n\n#include <anjay_modules/anjay_dm_utils.h>\n#include <avsystem/commons/avs_utils.h>\n\n#include \"../anjay_io_core.h\"\n\n#include <assert.h>\n#include <inttypes.h>\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\n#define MAX_OBJLNK_STRING_SIZE sizeof(\"65535:65535\")\n/**\n * Enumeration for supported SenML labels. Their numeric values correspond to\n * their CBOR representation wherever possible.\n */\ntypedef enum {\n    SENML_LABEL_BASE_TIME = -3,\n    SENML_LABEL_BASE_NAME = -2,\n    SENML_LABEL_NAME = 0,\n    SENML_LABEL_VALUE = 2,\n    SENML_LABEL_VALUE_STRING = 3,\n    SENML_LABEL_VALUE_BOOL = 4,\n    SENML_LABEL_TIME = 6,\n    SENML_LABEL_VALUE_OPAQUE = 8,\n    /* NOTE: Objlnk is represented as a string \"vlo\" */\n    SENML_EXT_LABEL_OBJLNK = 0x766C6F /* \"vlo */\n} senml_label_t;\n\n#define SENML_EXT_OBJLNK_REPR \"vlo\"\n\n/**\n * Converts Object link from string format OID:IID to numeric OID and IID.\n *\n * @p out_oid and @p out_iid are modified only if this function succeeded.\n *\n * NOTE: @p objlnk may be modified by this function.\n */\nint _anjay_io_parse_objlnk(char *objlnk,\n                           anjay_oid_t *out_oid,\n                           anjay_iid_t *out_iid);\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif /* ANJAY_IO_COMMON_H */\n"
  },
  {
    "path": "src/core/io/anjay_corelnk.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <assert.h>\n#include <stdbool.h>\n\n#include <anjay/core.h>\n#include <anjay_init.h>\n\n#include <anjay_modules/anjay_dm_utils.h>\n#include <anjay_modules/anjay_utils_core.h>\n#include <anjay_modules/dm/anjay_modules.h>\n\n#include <avsystem/commons/avs_errno.h>\n#include <avsystem/commons/avs_stream_membuf.h>\n\n#include \"../anjay_core.h\"\n#include \"../anjay_utils_private.h\"\n#include \"anjay_corelnk.h\"\n\nVISIBILITY_SOURCE_BEGIN\n\ntypedef struct {\n    bool first;\n    avs_stream_t *stream;\n    anjay_lwm2m_version_t version;\n} query_dm_args_t;\n\nstatic int query_dm_instance(anjay_unlocked_t *anjay,\n                             const anjay_dm_installed_object_t *obj,\n                             anjay_iid_t iid,\n                             void *args_) {\n    (void) anjay;\n    query_dm_args_t *args = (query_dm_args_t *) args_;\n    avs_error_t err =\n            avs_stream_write_f(args->stream, \"%s</%u/%u>\",\n                               args->first ? \"\" : \",\",\n                               _anjay_dm_installed_object_oid(obj), iid);\n    args->first = false;\n    return avs_is_ok(err) ? 0 : -1;\n}\n\nstatic int query_dm_object(anjay_unlocked_t *anjay,\n                           const anjay_dm_installed_object_t *obj,\n                           void *args_) {\n    anjay_oid_t oid = _anjay_dm_installed_object_oid(obj);\n    if (oid == ANJAY_DM_OID_SECURITY) {\n        /* LwM2M TS 1.1, 6.2.1. Register says that \"The Security Object ID:0,\n         * and OSCORE Object ID:21, if present, MUST NOT be part of the\n         * Registration Objects and Object Instances list.\" */\n        return 0;\n    }\n\n    query_dm_args_t *args = (query_dm_args_t *) args_;\n    if (args->first) {\n        args->first = false;\n    } else if (avs_is_err(avs_stream_write(args->stream, \",\", 1))) {\n        return -1;\n    }\n    bool obj_written = false;\n    const char *version = _anjay_dm_installed_object_version(obj);\n    if (version) {\n        const char *format = \"</%u>;ver=\\\"%s\\\"\";\n#ifdef ANJAY_WITH_LWM2M11\n        if (args->version > ANJAY_LWM2M_VERSION_1_0) {\n            format = \"</%u>;ver=%s\";\n        }\n#endif // ANJAY_WITH_LWM2M11\n\n        if (avs_is_err(\n                    avs_stream_write_f(args->stream, format, oid, version))) {\n            return -1;\n        }\n        obj_written = true;\n    }\n    query_dm_args_t instance_args = {\n        .first = !obj_written,\n        .stream = args->stream,\n        .version = args->version\n    };\n    int result = _anjay_dm_foreach_instance(anjay, obj, query_dm_instance,\n                                            &instance_args);\n    if (result) {\n        return result;\n    }\n    if (!instance_args.first) {\n        obj_written = true;\n    }\n    if (!obj_written\n            && avs_is_err(avs_stream_write_f(args->stream, \"</%u>\", oid))) {\n        return -1;\n    }\n    return 0;\n}\n\nint _anjay_corelnk_query_dm(anjay_unlocked_t *anjay,\n                            anjay_dm_t *dm,\n                            anjay_lwm2m_version_t version,\n                            char **buffer) {\n    assert(buffer);\n    assert(!*buffer);\n    avs_stream_t *stream = avs_stream_membuf_create();\n    if (!stream) {\n        _anjay_log_oom();\n        return -1;\n    }\n    int retval;\n    if ((retval = _anjay_dm_foreach_object(anjay, dm, query_dm_object,\n                                           &(query_dm_args_t) {\n                                               .first = true,\n                                               .stream = stream,\n                                               .version = version\n                                           }))\n            || (retval =\n                        (avs_is_ok(avs_stream_write(stream, \"\\0\", 1)) ? 0 : -1))\n            || (retval = (avs_is_ok(avs_stream_membuf_take_ownership(\n                                  stream, (void **) buffer, NULL))\n                                  ? 0\n                                  : -1))) {\n        anjay_log(ERROR, _(\"could not enumerate objects\"));\n    }\n    avs_stream_cleanup(&stream);\n    return retval;\n}\n\n#ifdef ANJAY_TEST\n#    include \"tests/core/io/corelnk.c\"\n#endif\n"
  },
  {
    "path": "src/core/io/anjay_corelnk.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ANJAY_IO_CORELNK_H\n#define ANJAY_IO_CORELNK_H\n\n#include <anjay/core.h>\n\n#include <anjay_modules/anjay_dm_utils.h>\n#include <anjay_modules/anjay_utils_core.h>\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\n/**\n * Function that returns a null-terminated string containing a list of objects,\n * their instances and version. It can be used as a payload for Register and\n * Update operations or as a /25/x/3 resource value.\n *\n * @param anjay        Anjay object to operate on.\n * @param dm           Data model on which the query will be performed.\n * @param version      LwM2M version for which the query will be prepared.\n * @param buffer       The pointer that will be set to the buffer with the\n *                     prepared string. It is the caller's responsibility to\n *                     free the buffer using avs_free().\n *\n * @returns 0 on success, a negative value in case of error.\n */\nint _anjay_corelnk_query_dm(anjay_unlocked_t *anjay,\n                            anjay_dm_t *dm,\n                            anjay_lwm2m_version_t version,\n                            char **buffer);\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif // ANJAY_IO_CORELNK_H\n"
  },
  {
    "path": "src/core/io/anjay_dynamic.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#include <inttypes.h>\n#include <stdlib.h>\n#include <string.h>\n\n#include <avsystem/commons/avs_stream.h>\n\n#include \"../anjay_core.h\"\n#include \"../anjay_dm_core.h\"\n#include \"../anjay_io_core.h\"\n#include \"../coap/anjay_content_format.h\"\n\n#include \"anjay_vtable.h\"\n\nVISIBILITY_SOURCE_BEGIN\n\n/////////////////////////////////////////////////////////////////////// ENCODING\n\nstatic anjay_unlocked_output_ctx_t *spawn_opaque(avs_stream_t *stream,\n                                                 const anjay_uri_path_t *uri,\n                                                 const size_t *items_count) {\n    (void) uri;\n    (void) items_count;\n    return _anjay_output_opaque_create(stream);\n}\n\n#ifndef ANJAY_WITHOUT_PLAINTEXT\nstatic anjay_unlocked_output_ctx_t *spawn_text(avs_stream_t *stream,\n                                               const anjay_uri_path_t *uri,\n                                               const size_t *items_count) {\n    (void) uri;\n    (void) items_count;\n    return _anjay_output_text_create(stream);\n}\n#endif // ANJAY_WITHOUT_PLAINTEXT\n\n#ifndef ANJAY_WITHOUT_TLV\nstatic anjay_unlocked_output_ctx_t *spawn_tlv(avs_stream_t *stream,\n                                              const anjay_uri_path_t *uri,\n                                              const size_t *items_count) {\n    (void) items_count;\n    return _anjay_output_tlv_create(stream, uri);\n}\n#endif // ANJAY_WITHOUT_TLV\n\n#ifdef ANJAY_WITH_LWM2M_JSON\nstatic anjay_unlocked_output_ctx_t *spawn_json(avs_stream_t *stream,\n                                               const anjay_uri_path_t *uri,\n                                               const size_t *items_count) {\n    return _anjay_output_senml_like_create(\n            stream, uri, AVS_COAP_FORMAT_OMA_LWM2M_JSON, items_count);\n}\n#endif // ANJAY_WITH_LWM2M_JSON\n\n#ifdef ANJAY_WITH_SENML_JSON\nstatic anjay_unlocked_output_ctx_t *\nspawn_senml_json(avs_stream_t *stream,\n                 const anjay_uri_path_t *uri,\n                 const size_t *items_count) {\n    return _anjay_output_senml_like_create(\n            stream, uri, AVS_COAP_FORMAT_SENML_JSON, items_count);\n}\n#endif // ANJAY_WITH_SENML_JSON\n\n#ifdef ANJAY_WITH_CBOR\nstatic anjay_unlocked_output_ctx_t *\nspawn_senml_cbor(avs_stream_t *stream,\n                 const anjay_uri_path_t *uri,\n                 const size_t *items_count) {\n    return _anjay_output_senml_like_create(\n            stream, uri, AVS_COAP_FORMAT_SENML_CBOR, items_count);\n}\n\nstatic anjay_unlocked_output_ctx_t *spawn_cbor(avs_stream_t *stream,\n                                               const anjay_uri_path_t *uri,\n                                               const size_t *items_count) {\n    (void) uri;\n    (void) items_count;\n    return _anjay_output_cbor_create(stream);\n}\n\n#    ifdef ANJAY_WITH_LWM2M12\nstatic anjay_unlocked_output_ctx_t *\nspawn_lwm2m_cbor(avs_stream_t *stream,\n                 const anjay_uri_path_t *uri,\n                 const size_t *items_count) {\n    (void) uri;\n    (void) items_count;\n    return _anjay_output_lwm2m_cbor_create(stream, uri);\n}\n#    endif // ANJAY_WITH_LWM2M12\n#endif     // ANJAY_WITH_CBOR\n\ntypedef struct {\n    uint16_t format;\n    anjay_input_ctx_constructor_t *input_ctx_constructor;\n    anjay_unlocked_output_ctx_t *(*output_ctx_spawn_func)(\n            avs_stream_t *stream,\n            const anjay_uri_path_t *uri,\n            const size_t *items_count);\n} dynamic_format_def_t;\n\nstatic const dynamic_format_def_t SUPPORTED_SIMPLE_FORMATS[] = {\n    { AVS_COAP_FORMAT_OCTET_STREAM, _anjay_input_opaque_create, spawn_opaque },\n#ifndef ANJAY_WITHOUT_PLAINTEXT\n    { AVS_COAP_FORMAT_PLAINTEXT, _anjay_input_text_create, spawn_text },\n#endif // ANJAY_WITHOUT_PLAINTEXT\n#ifdef ANJAY_WITH_CBOR\n    { AVS_COAP_FORMAT_CBOR, _anjay_input_cbor_create, spawn_cbor },\n#endif // ANJAY_WITH_CBOR\n    { AVS_COAP_FORMAT_NONE, NULL, NULL }\n};\n\nstatic const dynamic_format_def_t SUPPORTED_HIERARCHICAL_FORMATS[] = {\n#ifndef ANJAY_WITHOUT_TLV\n    { AVS_COAP_FORMAT_OMA_LWM2M_TLV, _anjay_input_tlv_create, spawn_tlv },\n#endif // ANJAY_WITHOUT_TLV\n#ifdef ANJAY_WITH_LWM2M_JSON\n    { AVS_COAP_FORMAT_OMA_LWM2M_JSON, NULL, spawn_json },\n#endif // ANJAY_WITH_LWM2M_JSON\n#ifdef ANJAY_WITH_SENML_JSON\n    { AVS_COAP_FORMAT_SENML_JSON, _anjay_input_json_create, spawn_senml_json },\n#endif // ANJAY_WITH_SENML_JSON\n#ifdef ANJAY_WITH_CBOR\n    { AVS_COAP_FORMAT_SENML_CBOR, _anjay_input_senml_cbor_create,\n      spawn_senml_cbor },\n#    ifdef ANJAY_WITH_LWM2M12\n    { AVS_COAP_FORMAT_OMA_LWM2M_CBOR, _anjay_input_lwm2m_cbor_create,\n      spawn_lwm2m_cbor },\n#    endif // ANJAY_WITH_LWM2M12\n#endif     // ANJAY_WITH_CBOR\n    { AVS_COAP_FORMAT_NONE, NULL, NULL }\n};\n\nAVS_STATIC_ASSERT(AVS_ARRAY_SIZE(SUPPORTED_HIERARCHICAL_FORMATS) > 1,\n                  at_least_one_hierarchical_format_must_be_enabled);\n\n#if defined(ANJAY_WITH_LWM2M11) && !defined(ANJAY_WITHOUT_COMPOSITE_OPERATIONS)\nstatic const dynamic_format_def_t SUPPORTED_COMPOSITE_READ_FORMATS[] = {\n#    ifdef ANJAY_WITH_CBOR\n    { AVS_COAP_FORMAT_SENML_CBOR, _anjay_input_senml_cbor_composite_read_create,\n      spawn_senml_cbor },\n#        ifdef ANJAY_WITH_LWM2M12\n    { AVS_COAP_FORMAT_SENML_ETCH_CBOR,\n      _anjay_input_senml_cbor_composite_read_create, NULL },\n    { AVS_COAP_FORMAT_OMA_LWM2M_CBOR, NULL, spawn_lwm2m_cbor },\n#        endif // ANJAY_WITH_LWM2M12\n#    endif     // ANJAY_WITH_CBOR\n#    ifdef ANJAY_WITH_SENML_JSON\n    { AVS_COAP_FORMAT_SENML_JSON, _anjay_input_json_composite_read_create,\n      spawn_senml_json },\n#        ifdef ANJAY_WITH_LWM2M12\n    { AVS_COAP_FORMAT_SENML_ETCH_JSON, _anjay_input_json_composite_read_create,\n      NULL },\n#        endif // ANJAY_WITH_LWM2M12\n#    endif     // ANJAY_WITH_SENML_JSON\n    { AVS_COAP_FORMAT_NONE, NULL, NULL }\n};\n#endif // defined(ANJAY_WITH_LWM2M11) &&\n       // !defined(ANJAY_WITHOUT_COMPOSITE_OPERATIONS)\n\n#ifdef ANJAY_WITH_LWM2M11\nstatic const dynamic_format_def_t SUPPORTED_COMPOSITE_WRITE_FORMATS[] = {\n#    ifdef ANJAY_WITH_CBOR\n    { AVS_COAP_FORMAT_SENML_CBOR, _anjay_input_senml_cbor_create, NULL },\n#        ifdef ANJAY_WITH_LWM2M12\n    { AVS_COAP_FORMAT_SENML_ETCH_CBOR, _anjay_input_senml_cbor_create, NULL },\n    { AVS_COAP_FORMAT_OMA_LWM2M_CBOR, _anjay_input_lwm2m_cbor_create, NULL },\n#        endif // ANJAY_WITH_LWM2M12\n#    endif     // ANJAY_WITH_CBOR\n#    ifdef ANJAY_WITH_SENML_JSON\n    { AVS_COAP_FORMAT_SENML_JSON, _anjay_input_json_create, NULL },\n#        ifdef ANJAY_WITH_LWM2M12\n    { AVS_COAP_FORMAT_SENML_ETCH_JSON, _anjay_input_json_create, NULL },\n#        endif // ANJAY_WITH_LWM2M12\n#    endif     // ANJAY_WITH_SENML_JSON\n    { AVS_COAP_FORMAT_NONE, NULL, NULL }\n};\n#endif // ANJAY_WITH_LWM2M11\n\n#ifdef ANJAY_WITH_SEND\nstatic const dynamic_format_def_t SUPPORTED_SEND_FORMATS[] = {\n#    ifdef ANJAY_WITH_CBOR\n    { AVS_COAP_FORMAT_SENML_CBOR, _anjay_input_senml_cbor_composite_read_create,\n      spawn_senml_cbor },\n#        ifdef ANJAY_WITH_LWM2M12\n    { AVS_COAP_FORMAT_OMA_LWM2M_CBOR, NULL, spawn_lwm2m_cbor },\n#        endif // ANJAY_WITH_LWM2M12\n#    endif     // ANJAY_WITH_CBOR\n#    ifdef ANJAY_WITH_SENML_JSON\n    { AVS_COAP_FORMAT_SENML_JSON, _anjay_input_json_composite_read_create,\n      spawn_senml_json },\n#    endif // ANJAY_WITH_SENML_JSON\n    { AVS_COAP_FORMAT_NONE, NULL, NULL }\n};\n#endif // ANJAY_WITH_SEND\n\nstatic const dynamic_format_def_t *\nfind_format(const dynamic_format_def_t *supported_formats, uint16_t format) {\n    for (const dynamic_format_def_t *candidate = supported_formats;\n         candidate->format != AVS_COAP_FORMAT_NONE;\n         ++candidate) {\n        if (_anjay_translate_legacy_content_format(format)\n                == candidate->format) {\n            return candidate;\n        }\n    }\n    return NULL;\n}\n\nstatic int spawn_output_ctx(anjay_unlocked_output_ctx_t **out_ctx,\n                            avs_stream_t *stream,\n                            const anjay_uri_path_t *uri,\n                            uint16_t format,\n                            const size_t *items_count,\n                            const dynamic_format_def_t *def) {\n    if (!def || !def->output_ctx_spawn_func) {\n        anjay_log(DEBUG,\n                  _(\"Could not find an appropriate output context for \"\n                    \"format: \") \"%\" PRIu16,\n                  format);\n        return ANJAY_ERR_NOT_ACCEPTABLE;\n    }\n\n    if (!(*out_ctx = def->output_ctx_spawn_func(stream, uri, items_count))) {\n        anjay_log(DEBUG, _(\"Failed to spawn output context\"));\n        return ANJAY_ERR_INTERNAL;\n    }\n\n    return 0;\n}\n\nuint16_t _anjay_default_hierarchical_format(anjay_lwm2m_version_t version) {\n#ifdef ANJAY_WITH_LWM2M11\n    switch (version) {\n#    ifdef ANJAY_WITH_LWM2M12\n    case ANJAY_LWM2M_VERSION_1_2:\n#    endif // ANJAY_WITH_LWM2M12\n    case ANJAY_LWM2M_VERSION_1_1:\n#    if defined(ANJAY_WITH_CBOR)\n        return AVS_COAP_FORMAT_SENML_CBOR;\n#    elif defined(ANJAY_WITH_SENML_JSON)\n        return AVS_COAP_FORMAT_SENML_JSON;\n#    endif\n        /* fall-through */\n    case ANJAY_LWM2M_VERSION_1_0:\n        return AVS_COAP_FORMAT_OMA_LWM2M_TLV;\n    }\n    AVS_UNREACHABLE(\"The switch statement above is supposed to be exhaustive\");\n#else  // ANJAY_WITH_LWM2M11\n    (void) version;\n#endif // ANJAY_WITH_LWM2M11\n    return AVS_COAP_FORMAT_OMA_LWM2M_TLV;\n}\n\nuint16_t _anjay_default_simple_format(anjay_unlocked_t *anjay,\n                                      anjay_lwm2m_version_t version) {\n    if (anjay->prefer_hierarchical_formats) {\n        return _anjay_default_hierarchical_format(version);\n    }\n\n#ifdef ANJAY_WITHOUT_PLAINTEXT\n#    ifdef ANJAY_WITH_CBOR\n    if (version >= ANJAY_LWM2M_VERSION_1_1) {\n        return AVS_COAP_FORMAT_CBOR;\n    }\n#    endif // ANJAY_WITH_CBOR\n    return _anjay_default_hierarchical_format(version);\n#else  // ANJAY_WITHOUT_PLAINTEXT\n    return AVS_COAP_FORMAT_PLAINTEXT;\n#endif // ANJAY_WITHOUT_PLAINTEXT\n}\n\nint _anjay_output_dynamic_construct(anjay_unlocked_output_ctx_t **out_ctx,\n                                    avs_stream_t *stream,\n                                    const anjay_uri_path_t *uri,\n                                    uint16_t format,\n                                    const size_t *items_count,\n                                    anjay_request_action_t action) {\n    if (format == AVS_COAP_FORMAT_NONE) {\n        return -1;\n    }\n\n    const dynamic_format_def_t *def = NULL;\n    switch (action) {\n    case ANJAY_ACTION_READ:\n        (void) ((def = find_format(SUPPORTED_SIMPLE_FORMATS, format))\n                || (def = find_format(SUPPORTED_HIERARCHICAL_FORMATS, format)));\n        break;\n#if defined(ANJAY_WITH_LWM2M11) && !defined(ANJAY_WITHOUT_COMPOSITE_OPERATIONS)\n    case ANJAY_ACTION_READ_COMPOSITE:\n        def = find_format(SUPPORTED_COMPOSITE_READ_FORMATS, format);\n        break;\n#endif // defined(ANJAY_WITH_LWM2M11) &&\n       // !defined(ANJAY_WITHOUT_COMPOSITE_OPERATIONS)\n    default:\n        break;\n    }\n    return spawn_output_ctx(out_ctx, stream, uri, format, items_count, def);\n}\n\n/////////////////////////////////////////////////////////////////////// DECODING\n\nint _anjay_input_dynamic_construct_raw(anjay_unlocked_input_ctx_t **out,\n                                       avs_stream_t *stream,\n                                       uint16_t format,\n                                       anjay_request_action_t action,\n                                       const anjay_uri_path_t *uri) {\n    if (format == AVS_COAP_FORMAT_NONE) {\n        format = AVS_COAP_FORMAT_PLAINTEXT;\n    }\n    anjay_input_ctx_constructor_t *constructor = NULL;\n    const dynamic_format_def_t *def;\n    switch (action) {\n    case ANJAY_ACTION_WRITE:\n    case ANJAY_ACTION_WRITE_UPDATE:\n    case ANJAY_ACTION_CREATE: {\n        if ((def = find_format(SUPPORTED_SIMPLE_FORMATS, format))\n                || (def = find_format(SUPPORTED_HIERARCHICAL_FORMATS,\n                                      format))) {\n            constructor = def->input_ctx_constructor;\n        }\n        break;\n    }\n    case ANJAY_ACTION_EXECUTE:\n        *out = NULL;\n        return 0;\n#ifdef ANJAY_WITH_LWM2M11\n    case ANJAY_ACTION_WRITE_COMPOSITE:\n        if ((def = find_format(SUPPORTED_COMPOSITE_WRITE_FORMATS, format))) {\n            constructor = def->input_ctx_constructor;\n        }\n        break;\n#    ifndef ANJAY_WITHOUT_COMPOSITE_OPERATIONS\n    case ANJAY_ACTION_READ_COMPOSITE:\n        if ((def = find_format(SUPPORTED_COMPOSITE_READ_FORMATS, format))) {\n            constructor = def->input_ctx_constructor;\n        }\n        break;\n#    endif // ANJAY_WITHOUT_COMPOSITE_OPERATIONS\n#endif     // ANJAY_WITH_LWM2M11\n    default:\n        // Nothing to prepare - the action does not need an input context.\n        return 0;\n    }\n    if (constructor) {\n        return constructor(out, stream, uri);\n    }\n    return ANJAY_ERR_UNSUPPORTED_CONTENT_FORMAT;\n}\n\nint _anjay_input_dynamic_construct(anjay_unlocked_input_ctx_t **out,\n                                   avs_stream_t *stream,\n                                   const anjay_request_t *request) {\n    return _anjay_input_dynamic_construct_raw(out, stream,\n                                              request->content_format,\n                                              request->action, &request->uri);\n}\n\n#ifdef ANJAY_WITH_SEND\nint _anjay_output_dynamic_send_construct(anjay_unlocked_output_ctx_t **out_ctx,\n                                         avs_stream_t *stream,\n                                         const anjay_uri_path_t *uri,\n                                         uint16_t format,\n                                         const size_t *items_count) {\n    if (format == AVS_COAP_FORMAT_NONE) {\n        return -1;\n    }\n\n    const dynamic_format_def_t *def =\n            find_format(SUPPORTED_SEND_FORMATS, format);\n\n    return spawn_output_ctx(out_ctx, stream, uri, format, items_count, def);\n}\n#endif // ANJAY_WITH_SEND\n\n#ifdef ANJAY_TEST\n#    include \"tests/core/io/dynamic.c\"\n#endif\n"
  },
  {
    "path": "src/core/io/anjay_input_buf.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#ifdef ANJAY_WITH_LWM2M11\n\n#    include \"anjay_vtable.h\"\n\nVISIBILITY_SOURCE_BEGIN\n\nstatic int get_complete_value_or_fail(anjay_unlocked_input_ctx_t *ctx_,\n                                      void *out_value,\n                                      size_t out_value_size) {\n    anjay_input_buf_ctx_t *ctx = (anjay_input_buf_ctx_t *) ctx_;\n\n    if (ctx->msg_finished) {\n        return -1;\n    }\n\n    size_t bytes_read;\n    if (avs_is_err(avs_stream_read(ctx->stream,\n                                   &bytes_read,\n                                   &ctx->msg_finished,\n                                   out_value,\n                                   out_value_size))\n            || bytes_read != out_value_size || !ctx->msg_finished) {\n        // Set it anyway to prevent reading from stream again\n        ctx->msg_finished = true;\n        return -1;\n    }\n\n    return 0;\n}\n\nstatic int input_buf_get_integer(anjay_unlocked_input_ctx_t *ctx,\n                                 int64_t *value) {\n    return get_complete_value_or_fail(ctx, value, sizeof(*value));\n}\n\nstatic int input_buf_get_uint(anjay_unlocked_input_ctx_t *ctx,\n                              uint64_t *value) {\n    return get_complete_value_or_fail(ctx, value, sizeof(*value));\n}\n\nstatic int input_buf_get_path(anjay_unlocked_input_ctx_t *ctx_,\n                              anjay_uri_path_t *out_path,\n                              bool *out_is_array) {\n    anjay_input_buf_ctx_t *ctx = (anjay_input_buf_ctx_t *) ctx_;\n    if (ctx->msg_finished) {\n        return ANJAY_GET_PATH_END;\n    }\n    if (!_anjay_uri_path_has(&ctx->path, ANJAY_ID_RID)) {\n        return ANJAY_ERR_BAD_REQUEST;\n    }\n    *out_is_array = false;\n    *out_path = ctx->path;\n    return 0;\n}\n\nstatic int noop(anjay_unlocked_input_ctx_t *ctx) {\n    (void) ctx;\n    return 0;\n}\n\nstatic const anjay_input_ctx_vtable_t BUF_IN_VTABLE = {\n    .integer = input_buf_get_integer,\n    .uint = input_buf_get_uint,\n    .get_path = input_buf_get_path,\n    .next_entry = noop,\n    .close = noop\n};\n\nanjay_input_buf_ctx_t _anjay_input_buf_ctx_init(avs_stream_t *stream,\n                                                anjay_uri_path_t *path) {\n    return (anjay_input_buf_ctx_t) {\n        .base = {\n            .vtable = &BUF_IN_VTABLE\n        },\n        .stream = stream,\n        .path = *path\n    };\n}\n\n#endif // ANJAY_WITH_LWM2M11\n"
  },
  {
    "path": "src/core/io/anjay_json_encoder.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#if defined(ANJAY_WITH_LWM2M_JSON) || defined(ANJAY_WITH_SENML_JSON)\n\n#    include <avsystem/commons/avs_base64.h>\n#    include <avsystem/commons/avs_log.h>\n#    include <avsystem/commons/avs_utils.h>\n\n#    include \"../anjay_io_core.h\"\n#    include \"../coap/anjay_content_format.h\"\n#    include \"anjay_base64_out.h\"\n#    include \"anjay_common.h\"\n#    include \"anjay_senml_like_encoder_vtable.h\"\n\n#    include <inttypes.h>\n\nVISIBILITY_SOURCE_BEGIN\n\n#    define json_log(level, ...) _anjay_log(json, level, __VA_ARGS__)\n\n#    define JSON_CONTEXT_LEVEL_ARRAY 0\n#    define JSON_CONTEXT_LEVEL_MAP 1\n#    define JSON_CONTEXT_LEVEL_BYTES 2\n\n#    define JSON_MAX_CONTEXT_LEVEL 2\n\ntypedef struct json_encoder_struct json_encoder_t;\n\ntypedef int (*key_encoder_t)(json_encoder_t *, senml_label_t);\n\nstruct json_encoder_struct {\n    const anjay_senml_like_encoder_vtable_t *vtable;\n    key_encoder_t key_encoder;\n    avs_base64_config_t base64_config;\n    avs_stream_t *stream;\n    anjay_unlocked_ret_bytes_ctx_t *bytes;\n    uint8_t level;\n    bool needs_separator;\n#    ifdef ANJAY_WITH_SENML_JSON\n    double last_encoded_time_s;\n#    endif // ANJAY_WITH_SENML_JSON\n};\n\nstatic int begin_pair(json_encoder_t *ctx, senml_label_t type);\n\nstatic int write_quoted_string(avs_stream_t *stream, const char *value) {\n    if (avs_is_err(avs_stream_write(stream, \"\\\"\", 1))) {\n        return -1;\n    }\n    for (size_t i = 0; i < strlen(value); ++i) {\n        /**\n         * RFC 4627 section 2.5 Strings:\n         *\n         * \"(...)\n         *  All Unicode characters may be placed within the\n         *  quotation marks except for the characters that must be escaped:\n         *  quotation mark, reverse solidus, and the control characters (U+0000\n         *  through U+001F).\n         * \"\n         */\n        if ((value[i] == '\\\\' || value[i] == '\"')\n                && avs_is_err(avs_stream_write(stream, \"\\\\\", 1))) {\n            return -1;\n        }\n\n        avs_error_t err;\n        if ((uint8_t) value[i] >= 0x20 && (uint8_t) value[i] < 127) {\n            err = avs_stream_write(stream, &value[i], 1);\n        } else if (value[i] == '\\b') {\n            err = avs_stream_write(stream, \"\\\\b\", 2);\n        } else if (value[i] == '\\f') {\n            err = avs_stream_write(stream, \"\\\\f\", 2);\n        } else if (value[i] == '\\n') {\n            err = avs_stream_write(stream, \"\\\\n\", 2);\n        } else if (value[i] == '\\r') {\n            err = avs_stream_write(stream, \"\\\\r\", 2);\n        } else if (value[i] == '\\t') {\n            err = avs_stream_write(stream, \"\\\\t\", 2);\n        } else {\n            const int nibble0 = ((uint8_t) value[i] >> 4) & 0xF;\n            const int nibble1 = ((uint8_t) value[i]) & 0xF;\n            err = avs_stream_write_f(stream, \"\\\\u00%x%x\", nibble0, nibble1);\n        }\n\n        if (avs_is_err(err)) {\n            return -1;\n        }\n    }\n    return avs_is_ok(avs_stream_write(stream, \"\\\"\", 1)) ? 0 : -1;\n}\n\nstatic inline void nested_context_push(json_encoder_t *ctx, uint8_t level) {\n    assert(ctx);\n    assert(ctx->level < JSON_MAX_CONTEXT_LEVEL);\n    assert(ctx->level == level - 1);\n    (void) level;\n    ctx->level++;\n}\n\nstatic inline void nested_context_pop(json_encoder_t *ctx) {\n    (void) ctx;\n    assert(ctx->level);\n    ctx->level--;\n}\n\nstatic inline int maybe_write_name(json_encoder_t *ctx, const char *name) {\n    int retval = 0;\n    if (name) {\n        (void) ((retval = begin_pair(ctx, SENML_LABEL_NAME))\n                || (retval = write_quoted_string(ctx->stream, name)));\n    }\n    return retval;\n}\n\nstatic int maybe_write_separator(json_encoder_t *ctx) {\n    if (ctx->needs_separator) {\n        ctx->needs_separator = false;\n        if (avs_is_err(avs_stream_write(ctx->stream, \",\", 1))) {\n            return -1;\n        }\n    }\n    return 0;\n}\n\n#    ifdef ANJAY_WITH_LWM2M_JSON\nstatic inline int maybe_write_time(json_encoder_t *ctx, double time_s) {\n    if (!isnan(time_s)) {\n        if (begin_pair(ctx, SENML_LABEL_TIME)\n                || avs_is_err(avs_stream_write_f(ctx->stream, \"%s\",\n                                                 AVS_DOUBLE_AS_STRING(time_s,\n                                                                      17)))) {\n            return -1;\n        }\n    }\n    return 0;\n}\n\nstatic int encode_key(json_encoder_t *ctx, senml_label_t type) {\n    const char *key = NULL;\n    switch (type) {\n    case SENML_LABEL_BASE_NAME:\n        key = \"\\\"bn\\\":\";\n        break;\n    case SENML_LABEL_NAME:\n        key = \"\\\"n\\\":\";\n        break;\n    case SENML_LABEL_VALUE:\n        key = \"\\\"v\\\":\";\n        break;\n    case SENML_LABEL_VALUE_STRING:\n    case SENML_LABEL_VALUE_OPAQUE:\n        key = \"\\\"sv\\\":\";\n        break;\n    case SENML_LABEL_VALUE_BOOL:\n        key = \"\\\"bv\\\":\";\n        break;\n    case SENML_LABEL_TIME:\n        key = \"\\\"t\\\":\";\n        break;\n    case SENML_EXT_LABEL_OBJLNK:\n        key = \"\\\"ov\\\":\";\n        break;\n    default:\n        AVS_UNREACHABLE(\"invalid data type\");\n        return -1;\n    }\n    return avs_is_ok(avs_stream_write(ctx->stream, key, strlen(key))) ? 0 : -1;\n}\n\nstatic int element_begin(anjay_senml_like_encoder_t *ctx_,\n                         const char *basename,\n                         const char *name,\n                         double time_s) {\n    assert(basename == NULL);\n    (void) basename;\n    json_encoder_t *ctx = (json_encoder_t *) ctx_;\n\n    nested_context_push(ctx, JSON_CONTEXT_LEVEL_MAP);\n    if (maybe_write_separator(ctx)\n            || avs_is_err(avs_stream_write(ctx->stream, \"{\", 1))\n            || maybe_write_name(ctx, name) || maybe_write_time(ctx, time_s)) {\n        return -1;\n    }\n    return 0;\n}\n\nstatic int encoder_cleanup(anjay_senml_like_encoder_t **ctx_) {\n    json_encoder_t *ctx = (json_encoder_t *) *ctx_;\n    int retval = -1;\n\n    if (ctx->level == JSON_CONTEXT_LEVEL_ARRAY\n            && avs_is_ok(avs_stream_write(ctx->stream, \"]}\", 2))) {\n        retval = 0;\n    }\n\n    avs_free(*ctx_);\n    *ctx_ = NULL;\n    return retval;\n}\n#    endif // ANJAY_WITH_LWM2M_JSON\n\n#    ifdef ANJAY_WITH_SENML_JSON\nstatic inline int maybe_write_basetime(json_encoder_t *ctx, double time_s) {\n    if (isnan(time_s)) {\n        time_s = 0.0;\n    }\n    if (ctx->last_encoded_time_s == time_s) {\n        return 0;\n    }\n\n    ctx->last_encoded_time_s = time_s;\n\n    if (begin_pair(ctx, SENML_LABEL_BASE_TIME)\n            || avs_is_err(avs_stream_write_f(\n                       ctx->stream, \"%s\", AVS_DOUBLE_AS_STRING(time_s, 17)))) {\n        return -1;\n    }\n    return 0;\n}\n\nstatic inline int maybe_write_basename(json_encoder_t *ctx,\n                                       const char *basename) {\n    int retval = 0;\n    if (basename) {\n        (void) ((retval = begin_pair(ctx, SENML_LABEL_BASE_NAME))\n                || (retval = write_quoted_string(ctx->stream, basename)));\n    }\n    return retval;\n}\n\nstatic int senml_encode_key(json_encoder_t *ctx, senml_label_t type) {\n    const char *key = NULL;\n    switch (type) {\n    case SENML_LABEL_BASE_NAME:\n        key = \"\\\"bn\\\":\";\n        break;\n    case SENML_LABEL_NAME:\n        key = \"\\\"n\\\":\";\n        break;\n    case SENML_LABEL_VALUE:\n        key = \"\\\"v\\\":\";\n        break;\n    case SENML_LABEL_VALUE_STRING:\n        key = \"\\\"vs\\\":\";\n        break;\n    case SENML_LABEL_VALUE_BOOL:\n        key = \"\\\"vb\\\":\";\n        break;\n    case SENML_LABEL_VALUE_OPAQUE:\n        key = \"\\\"vd\\\":\";\n        break;\n    case SENML_LABEL_BASE_TIME:\n        key = \"\\\"bt\\\":\";\n        break;\n    case SENML_EXT_LABEL_OBJLNK:\n        key = \"\\\"vlo\\\":\";\n        break;\n    default:\n        AVS_UNREACHABLE(\"invalid data type\");\n        return -1;\n    }\n    return avs_is_ok(avs_stream_write(ctx->stream, key, strlen(key))) ? 0 : -1;\n}\n\nstatic int senml_element_begin(anjay_senml_like_encoder_t *ctx_,\n                               const char *basename,\n                               const char *name,\n                               double time_s) {\n    json_encoder_t *ctx = (json_encoder_t *) ctx_;\n\n    nested_context_push(ctx, JSON_CONTEXT_LEVEL_MAP);\n    if (maybe_write_separator(ctx)\n            || avs_is_err(avs_stream_write(ctx->stream, \"{\", 1))\n            || maybe_write_basename(ctx, basename)\n            || maybe_write_name(ctx, name)\n            || maybe_write_basetime(ctx, time_s)) {\n        return -1;\n    }\n    return 0;\n}\n\nstatic int senml_encoder_cleanup(anjay_senml_like_encoder_t **ctx_) {\n    json_encoder_t *ctx = (json_encoder_t *) *ctx_;\n    int retval = -1;\n\n    if (ctx->level == JSON_CONTEXT_LEVEL_ARRAY\n            && avs_is_ok(avs_stream_write(ctx->stream, \"]\", 1))) {\n        retval = 0;\n    }\n\n    avs_free(*ctx_);\n    *ctx_ = NULL;\n    return retval;\n}\n#    endif // ANJAY_WITH_SENML_JSON\n\nstatic int begin_pair(json_encoder_t *ctx, senml_label_t type) {\n    int retval = -1;\n    if (ctx->level == JSON_CONTEXT_LEVEL_MAP) {\n        (void) ((retval = maybe_write_separator(ctx))\n                || (retval = ctx->key_encoder(ctx, type)));\n    }\n    // Separator is in fact needed after encoding value, not key. Anyway,\n    // maybe_encode_basename() isn't called before encoding a value, so setting\n    // this flag here allows to simplify encode_*() functions.\n    ctx->needs_separator = true;\n    return retval;\n}\n\nstatic int encode_uint(anjay_senml_like_encoder_t *ctx_, uint64_t value) {\n    json_encoder_t *ctx = (json_encoder_t *) ctx_;\n    if (begin_pair(ctx, SENML_LABEL_VALUE)\n            || avs_is_err(avs_stream_write_f(ctx->stream, \"%s\",\n                                             AVS_UINT64_AS_STRING(value)))) {\n        return -1;\n    }\n    return 0;\n}\n\nstatic int encode_int(anjay_senml_like_encoder_t *ctx_, int64_t value) {\n    json_encoder_t *ctx = (json_encoder_t *) ctx_;\n    if (begin_pair(ctx, SENML_LABEL_VALUE)\n            || avs_is_err(avs_stream_write_f(ctx->stream, \"%s\",\n                                             AVS_INT64_AS_STRING(value)))) {\n        return -1;\n    }\n    return 0;\n}\n\n// Source of format specifiers:\n// https://randomascii.wordpress.com/2012/03/08/float-precisionfrom-zero-to-100-digits-2/\n\nstatic int encode_double(anjay_senml_like_encoder_t *ctx_, double value) {\n    json_encoder_t *ctx = (json_encoder_t *) ctx_;\n    if (begin_pair(ctx, SENML_LABEL_VALUE)\n            || avs_is_err(avs_stream_write_f(\n                       ctx->stream, \"%s\", AVS_DOUBLE_AS_STRING(value, 17)))) {\n        return -1;\n    }\n    return 0;\n}\n\nstatic int encode_bool(anjay_senml_like_encoder_t *ctx_, bool value) {\n    json_encoder_t *ctx = (json_encoder_t *) ctx_;\n    if (begin_pair(ctx, SENML_LABEL_VALUE_BOOL)\n            || avs_is_err(avs_stream_write_f(ctx->stream, \"%s\",\n                                             value ? \"true\" : \"false\"))) {\n        return -1;\n    }\n    return 0;\n}\n\nstatic int encode_string(anjay_senml_like_encoder_t *ctx_, const char *value) {\n    json_encoder_t *ctx = (json_encoder_t *) ctx_;\n    int retval;\n    (void) ((retval = begin_pair(ctx, SENML_LABEL_VALUE_STRING))\n            || (retval = write_quoted_string(ctx->stream, value)));\n    return retval;\n}\n\nstatic int encode_objlnk(anjay_senml_like_encoder_t *ctx_, const char *value) {\n    json_encoder_t *ctx = (json_encoder_t *) ctx_;\n    int retval;\n    (void) ((retval = begin_pair(ctx, SENML_EXT_LABEL_OBJLNK))\n            || (retval = write_quoted_string(ctx->stream, value)));\n    return retval;\n}\n\nstatic int element_end(anjay_senml_like_encoder_t *ctx_) {\n    json_encoder_t *ctx = (json_encoder_t *) ctx_;\n\n    nested_context_pop(ctx);\n    ctx->needs_separator = true;\n    return avs_is_ok(avs_stream_write(ctx->stream, \"}\", 1)) ? 0 : -1;\n}\n\nstatic int bytes_begin(anjay_senml_like_encoder_t *ctx_, size_t size) {\n    json_encoder_t *ctx = (json_encoder_t *) ctx_;\n\n    nested_context_push(ctx, JSON_CONTEXT_LEVEL_BYTES);\n    if (!(ctx->bytes = _anjay_base64_ret_bytes_ctx_new(\n                  ctx->stream, ctx->base64_config, size))\n            || maybe_write_separator(ctx)\n            || ctx->key_encoder(ctx, SENML_LABEL_VALUE_OPAQUE)\n            || avs_is_err(avs_stream_write(ctx->stream, \"\\\"\", 1))) {\n        _anjay_base64_ret_bytes_ctx_delete(&ctx->bytes);\n        return -1;\n    }\n    return 0;\n}\n\nstatic int\nbytes_append(anjay_senml_like_encoder_t *ctx_, const void *data, size_t size) {\n    json_encoder_t *ctx = (json_encoder_t *) ctx_;\n    return _anjay_ret_bytes_append_unlocked(ctx->bytes, data, size);\n}\n\nstatic int bytes_end(anjay_senml_like_encoder_t *ctx_) {\n    json_encoder_t *ctx = (json_encoder_t *) ctx_;\n\n    int retval;\n    if (_anjay_base64_ret_bytes_ctx_close(ctx->bytes)\n            || avs_is_err(avs_stream_write(ctx->stream, \"\\\"\", 1))) {\n        retval = -1;\n    } else {\n        retval = 0;\n    }\n\n    _anjay_base64_ret_bytes_ctx_delete(&ctx->bytes);\n    nested_context_pop(ctx);\n    return retval;\n}\n\n#    define JSON_VTABLE_COMMON_DEF                 \\\n        .senml_like_encode_uint = encode_uint,     \\\n        .senml_like_encode_int = encode_int,       \\\n        .senml_like_encode_double = encode_double, \\\n        .senml_like_encode_bool = encode_bool,     \\\n        .senml_like_encode_string = encode_string, \\\n        .senml_like_encode_objlnk = encode_objlnk, \\\n        .senml_like_element_end = element_end,     \\\n        .senml_like_bytes_begin = bytes_begin,     \\\n        .senml_like_bytes_append = bytes_append,   \\\n        .senml_like_bytes_end = bytes_end\n\n#    ifdef ANJAY_WITH_LWM2M_JSON\nstatic const anjay_senml_like_encoder_vtable_t LWM2M_JSON_ENCODER_VTABLE = {\n    JSON_VTABLE_COMMON_DEF,\n    .senml_like_element_begin = element_begin,\n    .senml_like_encoder_cleanup = encoder_cleanup\n};\n#    endif // ANJAY_WITH_LWM2M_JSON\n\n#    ifdef ANJAY_WITH_SENML_JSON\nstatic const anjay_senml_like_encoder_vtable_t SENML_JSON_ENCODER_VTABLE = {\n    JSON_VTABLE_COMMON_DEF,\n    .senml_like_element_begin = senml_element_begin,\n    .senml_like_encoder_cleanup = senml_encoder_cleanup\n};\n#    endif // ANJAY_WITH_SENML_JSON\n\nstatic json_encoder_t *\njson_encoder_new(avs_stream_t *stream,\n                 const anjay_senml_like_encoder_vtable_t *vtable,\n                 key_encoder_t key_encoder,\n                 avs_base64_config_t base64_config) {\n    if (!stream) {\n        json_log(DEBUG, _(\"no stream provided\"));\n        return NULL;\n    }\n\n    json_encoder_t *ctx =\n            (json_encoder_t *) avs_calloc(1, sizeof(json_encoder_t));\n    if (ctx) {\n        ctx->stream = stream;\n        ctx->vtable = vtable;\n        ctx->key_encoder = key_encoder;\n        ctx->base64_config = base64_config;\n    } else {\n        json_log(DEBUG, _(\"failed to allocate encoder context\"));\n    }\n    return ctx;\n}\n\n#    ifdef ANJAY_WITH_LWM2M_JSON\nstatic int write_lwm2m_json_response_preamble(json_encoder_t *ctx,\n                                              const char *basename) {\n    if (avs_is_err(avs_stream_write(ctx->stream, \"{\", 1))\n            || (basename\n                && avs_is_err(avs_stream_write_f(ctx->stream, \"\\\"bn\\\":\\\"%s\\\",\",\n                                                 basename)))\n            || avs_is_err(avs_stream_write(ctx->stream, \"\\\"e\\\":[\", 5))) {\n        return -1;\n    }\n    return 0;\n}\n\nanjay_senml_like_encoder_t *\n_anjay_lwm2m_json_encoder_new(avs_stream_t *stream, const char *basename) {\n    json_encoder_t *ctx =\n            json_encoder_new(stream, &LWM2M_JSON_ENCODER_VTABLE, encode_key,\n                             AVS_BASE64_DEFAULT_STRICT_CONFIG);\n    if (ctx && write_lwm2m_json_response_preamble(ctx, basename)) {\n        avs_free(ctx);\n        ctx = NULL;\n    }\n    return (anjay_senml_like_encoder_t *) ctx;\n}\n#    endif // ANJAY_WITH_LWM2M_JSON\n\n#    ifdef ANJAY_WITH_SENML_JSON\nanjay_senml_like_encoder_t *\n_anjay_senml_json_encoder_new(avs_stream_t *stream) {\n    static const avs_base64_config_t BASE64_CONFIG = {\n        .alphabet = AVS_BASE64_URL_SAFE_CHARS,\n        .padding_char = '\\0',\n        .allow_whitespace = false,\n        .require_padding = false,\n        .without_null_termination = false,\n    };\n    json_encoder_t *ctx = json_encoder_new(stream, &SENML_JSON_ENCODER_VTABLE,\n                                           senml_encode_key, BASE64_CONFIG);\n\n    if (ctx) {\n        if (avs_is_err(avs_stream_write(ctx->stream, \"[\", 1))) {\n            avs_free(ctx);\n            ctx = NULL;\n        }\n    }\n\n    if (!ctx) {\n        json_log(ERROR, _(\"failed to create json encoder\"));\n        return NULL;\n    }\n\n    return (anjay_senml_like_encoder_t *) ctx;\n}\n#    endif // ANJAY_WITH_SENML_JSON\n\n#endif // defined(ANJAY_WITH_LWM2M_JSON) || defined(ANJAY_WITH_SENML_JSON)\n"
  },
  {
    "path": "src/core/io/anjay_json_like_decoder.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#if defined(ANJAY_WITH_CBOR) || defined(ANJAY_WITH_SENML_JSON)\n\n#    include \"anjay_json_like_decoder_vtable.h\"\n\n#    include <avsystem/commons/avs_log.h>\n\n#    include \"../anjay_utils_private.h\"\n\n#    define LOG(...) _anjay_log(json_like_decoder, __VA_ARGS__)\n\nVISIBILITY_SOURCE_BEGIN\n\nstruct anjay_json_like_decoder_struct {\n    const anjay_json_like_decoder_vtable_t *vtable;\n};\n\nvoid _anjay_json_like_decoder_delete(anjay_json_like_decoder_t **ctx) {\n    if (ctx && *ctx) {\n        assert((*ctx)->vtable);\n        assert((*ctx)->vtable->cleanup);\n        (*ctx)->vtable->cleanup(ctx);\n        assert(!*ctx);\n    }\n}\n\nanjay_json_like_decoder_state_t\n_anjay_json_like_decoder_state(const anjay_json_like_decoder_t *ctx) {\n    assert(ctx && ctx->vtable);\n    assert(ctx->vtable->state);\n    return ctx->vtable->state(ctx);\n}\n\nint _anjay_json_like_decoder_current_value_type(\n        anjay_json_like_decoder_t *ctx,\n        anjay_json_like_value_type_t *out_type) {\n    assert(ctx && ctx->vtable);\n    assert(ctx->vtable->current_value_type);\n    return ctx->vtable->current_value_type(ctx, out_type);\n}\n\n#    ifdef ANJAY_WITH_LWM2M12\nint _anjay_json_like_decoder_null(anjay_json_like_decoder_t *ctx) {\n    assert(ctx && ctx->vtable);\n    assert(ctx->vtable->read_null);\n    return ctx->vtable->read_null(ctx);\n}\n#    endif // ANJAY_WITH_LWM2M12\n\nint _anjay_json_like_decoder_bool(anjay_json_like_decoder_t *ctx,\n                                  bool *out_value) {\n    assert(ctx && ctx->vtable);\n    assert(ctx->vtable->read_bool);\n    return ctx->vtable->read_bool(ctx, out_value);\n}\n\nint _anjay_json_like_decoder_number(anjay_json_like_decoder_t *ctx,\n                                    anjay_json_like_number_t *out_value) {\n    assert(ctx && ctx->vtable);\n    assert(ctx->vtable->number);\n    return ctx->vtable->number(ctx, out_value);\n}\n\nint _anjay_json_like_decoder_bytes(anjay_json_like_decoder_t *ctx,\n                                   avs_stream_t *target_stream) {\n    assert(ctx && ctx->vtable);\n    assert(ctx->vtable->bytes);\n    return ctx->vtable->bytes(ctx, target_stream);\n}\n\nint _anjay_json_like_decoder_enter_array(anjay_json_like_decoder_t *ctx) {\n    assert(ctx && ctx->vtable);\n    assert(ctx->vtable->enter_array);\n    return ctx->vtable->enter_array(ctx);\n}\n\nint _anjay_json_like_decoder_enter_map(anjay_json_like_decoder_t *ctx) {\n    assert(ctx && ctx->vtable);\n    assert(ctx->vtable->enter_map);\n    return ctx->vtable->enter_map(ctx);\n}\n\nsize_t _anjay_json_like_decoder_nesting_level(anjay_json_like_decoder_t *ctx) {\n    assert(ctx && ctx->vtable);\n    assert(ctx->vtable->nesting_level);\n    return ctx->vtable->nesting_level(ctx);\n}\n\nstatic inline void print_number_conversion_warning(const char *expected) {\n    LOG(WARNING,\n        _(\"expected \") \"%s\" _(\", got something else instead\"),\n        expected);\n}\n\nint _anjay_json_like_decoder_get_i64_from_number(\n        const anjay_json_like_number_t *number, int64_t *out_value) {\n    if (number->type == ANJAY_JSON_LIKE_VALUE_UINT) {\n        if (number->value.u64 > INT64_MAX) {\n            return -1;\n        }\n        *out_value = (int64_t) number->value.u64;\n    } else if (number->type == ANJAY_JSON_LIKE_VALUE_NEGATIVE_INT) {\n        *out_value = number->value.i64;\n    } else if (number->type == ANJAY_JSON_LIKE_VALUE_FLOAT\n               && avs_double_convertible_to_int64((double) number->value.f32)) {\n        *out_value = (int64_t) number->value.f32;\n    } else if (number->type == ANJAY_JSON_LIKE_VALUE_DOUBLE\n               && avs_double_convertible_to_int64(number->value.f64)) {\n        *out_value = (int64_t) number->value.f64;\n    } else {\n        print_number_conversion_warning(\"int\");\n        return -1;\n    }\n    return 0;\n}\n\nint _anjay_json_like_decoder_get_u64_from_number(\n        const anjay_json_like_number_t *number, uint64_t *out_value) {\n    if (number->type == ANJAY_JSON_LIKE_VALUE_UINT) {\n        *out_value = number->value.u64;\n    } else if (number->type == ANJAY_JSON_LIKE_VALUE_FLOAT\n               && avs_double_convertible_to_uint64(\n                          (double) number->value.f32)) {\n        *out_value = (uint64_t) number->value.f32;\n    } else if (number->type == ANJAY_JSON_LIKE_VALUE_DOUBLE\n               && avs_double_convertible_to_uint64(number->value.f64)) {\n        *out_value = (uint64_t) number->value.f64;\n    } else {\n        print_number_conversion_warning(\"uint\");\n        return -1;\n    }\n    return 0;\n}\n\nint _anjay_json_like_decoder_get_double_from_number(\n        const anjay_json_like_number_t *number, double *out_value) {\n    switch (number->type) {\n    case ANJAY_JSON_LIKE_VALUE_FLOAT:\n        *out_value = number->value.f32;\n        break;\n    case ANJAY_JSON_LIKE_VALUE_DOUBLE:\n        *out_value = number->value.f64;\n        break;\n    case ANJAY_JSON_LIKE_VALUE_UINT:\n        *out_value = (double) number->value.u64;\n        break;\n    case ANJAY_JSON_LIKE_VALUE_NEGATIVE_INT:\n        *out_value = (double) number->value.i64;\n        break;\n    default:\n        print_number_conversion_warning(\"double\");\n        return -1;\n    }\n    return 0;\n}\n\n#endif // defined(ANJAY_WITH_CBOR) || defined(ANJAY_WITH_SENML_JSON)\n"
  },
  {
    "path": "src/core/io/anjay_json_like_decoder.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ANJAY_IO_JSON_LIKE_DECODER_H\n#define ANJAY_IO_JSON_LIKE_DECODER_H\n\n#include <inttypes.h>\n\n#include <avsystem/commons/avs_stream.h>\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\ntypedef enum {\n    ANJAY_JSON_LIKE_VALUE_NULL,\n    ANJAY_JSON_LIKE_VALUE_UINT,\n    ANJAY_JSON_LIKE_VALUE_NEGATIVE_INT,\n    ANJAY_JSON_LIKE_VALUE_BYTE_STRING,\n    ANJAY_JSON_LIKE_VALUE_TEXT_STRING,\n    ANJAY_JSON_LIKE_VALUE_ARRAY,\n    ANJAY_JSON_LIKE_VALUE_MAP,\n    ANJAY_JSON_LIKE_VALUE_FLOAT,\n    ANJAY_JSON_LIKE_VALUE_DOUBLE,\n    ANJAY_JSON_LIKE_VALUE_BOOL\n} anjay_json_like_value_type_t;\n\ntypedef enum {\n    /* decoder is operational */\n    ANJAY_JSON_LIKE_DECODER_STATE_OK,\n    /* decoder reached end of stream */\n    ANJAY_JSON_LIKE_DECODER_STATE_FINISHED,\n    /* decoder could not make sense out of some part of the stream */\n    ANJAY_JSON_LIKE_DECODER_STATE_ERROR\n} anjay_json_like_decoder_state_t;\n\ntypedef struct anjay_json_like_decoder_struct anjay_json_like_decoder_t;\n\nvoid _anjay_json_like_decoder_delete(anjay_json_like_decoder_t **ctx);\n\nanjay_json_like_decoder_state_t\n_anjay_json_like_decoder_state(const anjay_json_like_decoder_t *ctx);\n\n/**\n * Returns the type of the current value that can be (or currently is) extracted\n * from the context.\n *\n * Before consuming (or preparing to consumption in some cases) the value with\n * one of the:\n *  - @ref _anjay_json_like_decoder_number(),\n *  - @ref _anjay_json_like_decoder_bool(),\n *  - @ref _anjay_json_like_decoder_bytes(),\n *  - @ref _anjay_json_like_decoder_enter_array(),\n *  - @ref _anjay_json_like_decoder_enter_map()\n *\n * the function is guaranteed to return same results each time it is called.\n *\n * @param[in]   ctx         Decoder context to operate on.\n * @param[out]  out_type    Ponter to a variable where next type shall be\n *                          stored.\n *\n * @returns 0 on success, negative value if an error occurred, or if the decoder\n * is not in @ref ANJAY_JSON_LIKE_DECODER_STATE_OK state.\n */\nint _anjay_json_like_decoder_current_value_type(\n        anjay_json_like_decoder_t *ctx, anjay_json_like_value_type_t *out_type);\n\n#ifdef ANJAY_WITH_LWM2M12\n/**\n * Consumes a simple null value.\n *\n * NOTE: May only be called when the next value type is @ref\n * ANJAY_JSON_LIKE_VALUE_NULL, otherwise an error will be reported.\n *\n * @param[in]   ctx         Decoder context to operate on.\n *\n * @returns 0 on success, negative value if an error occurred (including the\n * case where the decoder is not in @ref ANJAY_JSON_LIKE_DECODER_STATE_OK\n * state).\n */\nint _anjay_json_like_decoder_null(anjay_json_like_decoder_t *ctx);\n#endif // ANJAY_WITH_LWM2M12\n\n/**\n * Consumes a simple boolean value.\n *\n * NOTE: May only be called when the next value type is @ref\n * ANJAY_JSON_LIKE_VALUE_BOOL, otherwise an error will be reported.\n *\n * @param[in]   ctx         Decoder context to operate on.\n * @param[out]  out_value   Pointer to a variable where the value shall\n *                          be stored.\n *\n * @returns 0 on success, negative value if an error occurred (including the\n * case where the decoder is not in @ref ANJAY_JSON_LIKE_DECODER_STATE_OK\n * state).\n */\nint _anjay_json_like_decoder_bool(anjay_json_like_decoder_t *ctx,\n                                  bool *out_value);\n\ntypedef struct {\n    anjay_json_like_value_type_t type;\n    union {\n        uint64_t u64;\n        int64_t i64;\n        float f32;\n        double f64;\n    } value;\n} anjay_json_like_number_t;\n\n/**\n * Consumes a scalar value from the context.\n *\n * NOTE: May only be called when the next value type is either:\n *  - @ref ANJAY_JSON_LIKE_VALUE_UINT,\n *  - @ref ANJAY_JSON_LIKE_VALUE_NEGATIVE_INT,\n *  - @ref ANJAY_JSON_LIKE_VALUE_FLOAT,\n *  - @ref ANJAY_JSON_LIKE_VALUE_DOUBLE.\n *\n * @param[in]   ctx         Decoder context to operate on\n * @param[out]  out_value   Pointer to a variable where the value shall\n *                          be stored.\n * @returns 0 on success, negative value if an error occurred (including the\n * case where the decoder is not in @ref ANJAY_JSON_LIKE_DECODER_STATE_OK\n * state).\n */\nint _anjay_json_like_decoder_number(anjay_json_like_decoder_t *ctx,\n                                    anjay_json_like_number_t *out_value);\n\n/**\n * Reads encoded bytes or text into a stream provided by the caller.\n *\n * NOTE: May only be called when the next value type is either:\n *  - @ref ANJAY_JSON_LIKE_VALUE_BYTE_STRING,\n *  - @ref ANJAY_JSON_LIKE_VALUE_TEXT_STRING.\n *\n * @param ctx           Decoder context to operate on.\n *\n * @param target_stream A stream into which the read bytes will be written.\n *\n * @returns 0 on success, negative value if an error occurred (including the\n * case where the decoder is not in @ref ANJAY_JSON_LIKE_DECODER_STATE_OK\n * state).\n */\nint _anjay_json_like_decoder_bytes(anjay_json_like_decoder_t *ctx,\n                                   avs_stream_t *target_stream);\n\n/**\n * Prepares to the consumption of the array.\n *\n * NOTE: May only be called when the next value type is @ref\n * ANJAY_JSON_LIKE_VALUE_ARRAY.\n *\n * NOTE: The decoder may have a limit of structure nesting levels (e.g.\n * MAX_NEST_STACK_SIZE defined in cbor_decoder.c). Any payload with higher\n * nesting degree will be rejected by the decoder by entering the @ref\n * ANJAY_JSON_LIKE_DECODER_STATE_ERROR state.\n *\n * @param[in]   ctx             Decoder context to operate on.\n *\n * @returns 0 on success, negative value if an error occurred (including the\n * case where the decoder is not in @ref ANJAY_JSON_LIKE_DECODER_STATE_OK\n * state).\n */\nint _anjay_json_like_decoder_enter_array(anjay_json_like_decoder_t *ctx);\n\n/**\n * Prepares to the consumption of the map.\n *\n * NOTE: May only be called when the next value type is @ref\n * ANJAY_JSON_LIKE_VALUE_MAP.\n *\n * NOTE: The decoder may have a limit of structure nesting levels (e.g.\n * MAX_NEST_STACK_SIZE defined in cbor_decoder.c). Any payload with higher\n * nesting degree will be rejected.\n *\n * @param[in]   ctx             Decoder context to operate on.\n *\n * @returns 0 on success, negative value if an error occurred (including the\n * case where the decoder is not in @ref ANJAY_JSON_LIKE_DECODER_STATE_OK\n * state).\n */\nint _anjay_json_like_decoder_enter_map(anjay_json_like_decoder_t *ctx);\n\n/**\n * @returns Number of compound entities that the parser is currently inside.\n *          The number is incremented by 1 after a successfull call to\n *          @ref _anjay_json_like_decoder_enter_array or\n *          @ref _anjay_json_like_decoder_enter_map, and decreased after reading\n *          the last element of that array or map. In particular, if the array\n *          or map has zero elements, its value will not be visibly incremented\n *          at all.\n */\nsize_t _anjay_json_like_decoder_nesting_level(anjay_json_like_decoder_t *ctx);\n\n/**\n * Converts @ref anjay_json_like_number_t to @c int64_t , if possible.\n */\nint _anjay_json_like_decoder_get_i64_from_number(\n        const anjay_json_like_number_t *number, int64_t *out_value);\n\n/**\n * Converts @ref anjay_json_like_number_t to @c uint64_t , if possible.\n */\nint _anjay_json_like_decoder_get_u64_from_number(\n        const anjay_json_like_number_t *number, uint64_t *out_value);\n\n/**\n * Converts @ref anjay_json_like_number_t to @c double , if possible.\n */\nint _anjay_json_like_decoder_get_double_from_number(\n        const anjay_json_like_number_t *number, double *out_value);\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif /* ANJAY_IO_JSON_LIKE_DECODER_H */\n"
  },
  {
    "path": "src/core/io/anjay_json_like_decoder_vtable.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ANJAY_IO_JSON_LIKE_DECODER_VTABLE_H\n#define ANJAY_IO_JSON_LIKE_DECODER_VTABLE_H\n\n#include \"anjay_json_like_decoder.h\"\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\ntypedef anjay_json_like_decoder_state_t\njson_like_decoder_state_t(const anjay_json_like_decoder_t *ctx);\n\ntypedef int\njson_like_decoder_current_value_type_t(anjay_json_like_decoder_t *ctx,\n                                       anjay_json_like_value_type_t *out_type);\n\n#ifdef ANJAY_WITH_LWM2M12\ntypedef int json_like_decoder_null_t(anjay_json_like_decoder_t *ctx);\n#endif // ANJAY_WITH_LWM2M12\n\ntypedef int json_like_decoder_bool_t(anjay_json_like_decoder_t *ctx,\n                                     bool *out_value);\n\ntypedef int json_like_decoder_number_t(anjay_json_like_decoder_t *ctx,\n                                       anjay_json_like_number_t *out_value);\n\ntypedef int json_like_decoder_bytes_t(anjay_json_like_decoder_t *ctx,\n                                      avs_stream_t *target_stream);\n\ntypedef int json_like_decoder_enter_array_t(anjay_json_like_decoder_t *ctx);\n\ntypedef int json_like_decoder_enter_map_t(anjay_json_like_decoder_t *ctx);\n\ntypedef size_t\njson_like_decoder_nesting_level_t(anjay_json_like_decoder_t *ctx);\n\ntypedef void json_like_decoder_cleanup_t(anjay_json_like_decoder_t **ctx);\n\ntypedef struct {\n    json_like_decoder_state_t *state;\n    json_like_decoder_current_value_type_t *current_value_type;\n#ifdef ANJAY_WITH_LWM2M12\n    json_like_decoder_null_t *read_null;\n#endif // ANJAY_WITH_LWM2M12\n    json_like_decoder_bool_t *read_bool;\n    json_like_decoder_number_t *number;\n    json_like_decoder_bytes_t *bytes;\n    json_like_decoder_enter_array_t *enter_array;\n    json_like_decoder_enter_map_t *enter_map;\n    json_like_decoder_nesting_level_t *nesting_level;\n    json_like_decoder_cleanup_t *cleanup;\n} anjay_json_like_decoder_vtable_t;\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif /* ANJAY_IO_JSON_LIKE_DECODER_VTABLE_H */\n"
  },
  {
    "path": "src/core/io/anjay_lwm2m_cbor_in.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#if defined(ANJAY_WITH_CBOR) && defined(ANJAY_WITH_LWM2M12)\n\n#    include <avsystem/commons/avs_base64.h>\n#    include <avsystem/commons/avs_stream_membuf.h>\n#    include <avsystem/commons/avs_stream_v_table.h>\n#    include <avsystem/commons/avs_utils.h>\n\n#    ifdef ANJAY_WITH_LWM2M_GATEWAY\n#        include <anjay/lwm2m_gateway.h>\n#    endif // ANJAY_WITH_LWM2M_GATEWAY\n\n#    include \"../anjay_utils_private.h\"\n\n#    include \"cbor/anjay_json_like_cbor_decoder.h\"\n\n#    include \"anjay_common.h\"\n#    include \"anjay_vtable.h\"\n\n#    include <errno.h>\n#    include <math.h>\n\nVISIBILITY_SOURCE_BEGIN\n\n#    define LOG(...) _anjay_log(lwm2m_cbor_in, __VA_ARGS__)\n\n#    ifdef ANJAY_WITH_LWM2M_GATEWAY\n// 5 relative paths: optional prefix and up to 4 IDs\n#        define MAX_RELATIVE_PATHS (1 + _ANJAY_URI_PATH_MAX_LENGTH)\n#    else\n// 4 relative paths with one ID\n#        define MAX_RELATIVE_PATHS _ANJAY_URI_PATH_MAX_LENGTH\n#    endif // ANJAY_WITH_LWM2M_GATEWAY\n\ntypedef struct {\n#    ifdef ANJAY_WITH_LWM2M_GATEWAY\n    char prefix[ANJAY_GATEWAY_MAX_PREFIX_LEN];\n#    endif // ANJAY_WITH_LWM2M_GATEWAY\n    uint16_t ids[_ANJAY_URI_PATH_MAX_LENGTH];\n    uint8_t ids_length;\n} lwm2m_cbor_relative_path_t;\n\ntypedef struct {\n    anjay_uri_path_t path;\n    uint8_t relative_paths_ids_lengths[MAX_RELATIVE_PATHS];\n    uint8_t relative_paths_num;\n} path_stack_t;\n\nstatic void relative_path_init(lwm2m_cbor_relative_path_t *out_path) {\n    out_path->ids_length = 0;\n#    ifdef ANJAY_WITH_LWM2M_GATEWAY\n    out_path->prefix[0] = '\\0';\n#    endif // ANJAY_WITH_LWM2M_GATEWAY\n}\n\n#    ifdef ANJAY_WITH_LWM2M_GATEWAY\nstatic bool relative_path_has_prefix(const lwm2m_cbor_relative_path_t *path) {\n    return path->prefix[0] != '\\0';\n}\n#    endif // ANJAY_WITH_LWM2M_GATEWAY\n\nstatic int path_push(path_stack_t *stack,\n                     const lwm2m_cbor_relative_path_t *relative_path) {\n    size_t relative_path_length = relative_path->ids_length;\n    size_t stored_path_ids_length = _anjay_uri_path_length(&stack->path);\n\n#    ifdef ANJAY_WITH_LWM2M_GATEWAY\n    // TODO: rework anjay_uri_path APIs so that it's clear when we care about\n    // the whole URI, or just the numerical part\n    size_t stored_path_length = stored_path_ids_length;\n\n    if (relative_path_has_prefix(relative_path)) {\n        relative_path_length++;\n    }\n    if (_anjay_uri_path_has_prefix(&stack->path)) {\n        stored_path_length++;\n    }\n#    endif // ANJAY_WITH_LWM2M_GATEWAY\n\n    if (relative_path_length == 0\n            || stored_path_ids_length + relative_path->ids_length\n                           > _ANJAY_URI_PATH_MAX_LENGTH) {\n        return -1;\n    }\n#    ifdef ANJAY_WITH_LWM2M_GATEWAY\n    if (relative_path_has_prefix(relative_path)) {\n        if (stored_path_length > 0) {\n            LOG(DEBUG, _(\"prefix can only be the first element of a path\"));\n            return -1;\n        }\n        strcpy(stack->path.prefix, relative_path->prefix);\n    }\n#    endif // ANJAY_WITH_LWM2M_GATEWAY\n\n    assert(stack->relative_paths_num < MAX_RELATIVE_PATHS);\n\n    stack->relative_paths_ids_lengths[stack->relative_paths_num] =\n            relative_path->ids_length;\n    stack->relative_paths_num++;\n\n    for (uint8_t i = 0; i < relative_path->ids_length; i++) {\n        stack->path.ids[stored_path_ids_length++] = relative_path->ids[i];\n    }\n\n    return 0;\n}\n\nstatic int path_pop(path_stack_t *stack) {\n    if (stack->relative_paths_num == 0) {\n        return -1;\n    }\n\n    size_t path_ids_length = _anjay_uri_path_length(&stack->path);\n    uint8_t last_relative_path_ids_length =\n            stack->relative_paths_ids_lengths[stack->relative_paths_num - 1];\n\n#    ifdef ANJAY_WITH_LWM2M_GATEWAY\n    assert(stack->relative_paths_num == 1 || last_relative_path_ids_length > 0);\n\n    if (stack->relative_paths_num == 1) {\n        stack->path.prefix[0] = '\\0';\n    }\n#    else  // ANJAY_WITH_LWM2M_GATEWAY\n    assert(last_relative_path_ids_length > 0);\n#    endif // ANJAY_WITH_LWM2M_GATEWAY\n\n    assert(path_ids_length >= last_relative_path_ids_length);\n\n    while (last_relative_path_ids_length--) {\n        stack->path.ids[--path_ids_length] = ANJAY_ID_INVALID;\n    }\n\n    stack->relative_paths_num--;\n    return 0;\n}\n\nstatic int path_pop_n(path_stack_t *stack, size_t n) {\n    while (n--) {\n        if (path_pop(stack)) {\n            return -1;\n        }\n    }\n    return 0;\n}\n\nstatic anjay_uri_path_t path_get(path_stack_t *stack) {\n    return stack->path;\n}\n\ntypedef struct {\n    const anjay_input_ctx_vtable_t *input_ctx_vtable;\n    anjay_json_like_decoder_t *ctx;\n\n    /* A path which must be a prefix of the currently processed `path`. */\n    anjay_uri_path_t base;\n    /* Currently processed path. */\n    anjay_uri_path_t path;\n    /* Keeps info about path nesting. */\n    path_stack_t path_stack;\n\n    bool value_read;\n\n    bool decoding_bytes;\n    size_t bytes_initial_nesting_level;\n    anjay_io_cbor_bytes_ctx_t bytes_ctx;\n} lwm2m_cbor_in_t;\n\nstatic bool can_return_value(lwm2m_cbor_in_t *in) {\n    return in->path.ids[0] != ANJAY_ID_INVALID && !in->value_read;\n}\n\n// Must be called if the complete value has been read.\nstatic int update_path_stack(lwm2m_cbor_in_t *in,\n                             size_t initial_nesting_level) {\n    size_t current_nesting_level =\n            _anjay_json_like_decoder_nesting_level(in->ctx);\n\n    assert(current_nesting_level <= initial_nesting_level);\n\n    if (current_nesting_level > 0) {\n        size_t paths_to_pop = initial_nesting_level - current_nesting_level + 1;\n        return path_pop_n(&in->path_stack, paths_to_pop);\n    } else if (_anjay_json_like_decoder_state(in->ctx)\n               != ANJAY_JSON_LIKE_DECODER_STATE_FINISHED) {\n        // If nesting level is 0, no more data is expected and the state should\n        // be FINISHED\n        return -1;\n    }\n\n    return 0;\n}\n\nstatic int get_some_bytes(lwm2m_cbor_in_t *in,\n                          size_t *out_bytes_read,\n                          bool *out_message_finished,\n                          void *out_buf,\n                          size_t buf_size) {\n    if (!in->decoding_bytes) {\n        // Read and cache it here, because the _anjay_io_cbor_get_bytes_ctx()\n        // call may enter an indefinite length bytes struct and increase the\n        // nesting level.\n        in->bytes_initial_nesting_level =\n                _anjay_json_like_decoder_nesting_level(in->ctx);\n        if (_anjay_io_cbor_get_bytes_ctx(in->ctx, &in->bytes_ctx)) {\n            return -1;\n        }\n        in->decoding_bytes = true;\n    }\n\n    if (_anjay_io_cbor_get_some_bytes(in->ctx, &in->bytes_ctx, out_buf,\n                                      buf_size, out_bytes_read,\n                                      out_message_finished)) {\n        return -1;\n    }\n\n    if (*out_message_finished) {\n        in->decoding_bytes = false;\n        in->value_read = true;\n        return update_path_stack(in, in->bytes_initial_nesting_level);\n    }\n\n    return 0;\n}\n\nstatic int lwm2m_cbor_get_some_bytes(anjay_unlocked_input_ctx_t *ctx,\n                                     size_t *out_bytes_read,\n                                     bool *out_message_finished,\n                                     void *out_buf,\n                                     size_t buf_size) {\n    lwm2m_cbor_in_t *in = (lwm2m_cbor_in_t *) ctx;\n    if (!can_return_value(in)) {\n        return -1;\n    }\n\n    anjay_json_like_value_type_t type;\n    if (_anjay_json_like_decoder_current_value_type(in->ctx, &type)) {\n        return -1;\n    }\n\n    if (type != ANJAY_JSON_LIKE_VALUE_BYTE_STRING) {\n        return ANJAY_ERR_BAD_REQUEST;\n    }\n\n    return get_some_bytes(in, out_bytes_read, out_message_finished, out_buf,\n                          buf_size);\n}\n\nstatic int lwm2m_cbor_get_string(anjay_unlocked_input_ctx_t *ctx,\n                                 char *out_buf,\n                                 size_t buf_size) {\n    assert(buf_size);\n    lwm2m_cbor_in_t *in = (lwm2m_cbor_in_t *) ctx;\n    if (!can_return_value(in)) {\n        return -1;\n    }\n\n    anjay_json_like_value_type_t type;\n    if (_anjay_json_like_decoder_current_value_type(in->ctx, &type)) {\n        return -1;\n    }\n\n    if (type != ANJAY_JSON_LIKE_VALUE_TEXT_STRING) {\n        return ANJAY_ERR_BAD_REQUEST;\n    }\n\n    size_t bytes_read;\n    bool msg_finished;\n    if (get_some_bytes(in, &bytes_read, &msg_finished, out_buf, buf_size - 1)) {\n        return -1;\n    }\n    out_buf[bytes_read] = '\\0';\n\n    if (!msg_finished) {\n        return ANJAY_BUFFER_TOO_SHORT;\n    }\n\n    return 0;\n}\n\nstatic int lwm2m_cbor_get_integer(anjay_unlocked_input_ctx_t *ctx,\n                                  int64_t *out_value) {\n    lwm2m_cbor_in_t *in = (lwm2m_cbor_in_t *) ctx;\n    if (!can_return_value(in)) {\n        return -1;\n    }\n\n    size_t initial_nesting_level =\n            _anjay_json_like_decoder_nesting_level(in->ctx);\n\n    anjay_json_like_number_t value;\n    if (_anjay_json_like_decoder_number(in->ctx, &value)\n            || _anjay_json_like_decoder_get_i64_from_number(&value,\n                                                            out_value)) {\n        return ANJAY_ERR_BAD_REQUEST;\n    }\n\n    in->value_read = true;\n\n    return update_path_stack(in, initial_nesting_level);\n}\n\nstatic int lwm2m_cbor_get_uint(anjay_unlocked_input_ctx_t *ctx,\n                               uint64_t *out_value) {\n    lwm2m_cbor_in_t *in = (lwm2m_cbor_in_t *) ctx;\n    if (!can_return_value(in)) {\n        return -1;\n    }\n\n    size_t initial_nesting_level =\n            _anjay_json_like_decoder_nesting_level(in->ctx);\n\n    anjay_json_like_number_t value;\n    if (_anjay_json_like_decoder_number(in->ctx, &value)\n            || _anjay_json_like_decoder_get_u64_from_number(&value,\n                                                            out_value)) {\n        return ANJAY_ERR_BAD_REQUEST;\n    }\n\n    in->value_read = true;\n\n    return update_path_stack(in, initial_nesting_level);\n}\n\nstatic int lwm2m_cbor_get_double(anjay_unlocked_input_ctx_t *ctx,\n                                 double *out_value) {\n    lwm2m_cbor_in_t *in = (lwm2m_cbor_in_t *) ctx;\n    if (!can_return_value(in)) {\n        return -1;\n    }\n\n    size_t initial_nesting_level =\n            _anjay_json_like_decoder_nesting_level(in->ctx);\n\n    anjay_json_like_number_t value;\n    if (_anjay_json_like_decoder_number(in->ctx, &value)\n            || _anjay_json_like_decoder_get_double_from_number(&value,\n                                                               out_value)) {\n        return ANJAY_ERR_BAD_REQUEST;\n    }\n\n    in->value_read = true;\n\n    return update_path_stack(in, initial_nesting_level);\n}\n\nstatic int lwm2m_cbor_get_bool(anjay_unlocked_input_ctx_t *ctx,\n                               bool *out_value) {\n    lwm2m_cbor_in_t *in = (lwm2m_cbor_in_t *) ctx;\n\n    if (!can_return_value(in)) {\n        return -1;\n    }\n\n    size_t initial_nesting_level =\n            _anjay_json_like_decoder_nesting_level(in->ctx);\n\n    if (_anjay_json_like_decoder_bool(in->ctx, out_value)) {\n        return ANJAY_ERR_BAD_REQUEST;\n    }\n\n    in->value_read = true;\n\n    return update_path_stack(in, initial_nesting_level);\n}\n\nstatic int lwm2m_cbor_get_objlnk(anjay_unlocked_input_ctx_t *ctx,\n                                 anjay_oid_t *out_oid,\n                                 anjay_iid_t *out_iid) {\n    char objlnk[MAX_OBJLNK_STRING_SIZE];\n    if (_anjay_get_string_unlocked(ctx, objlnk, sizeof(objlnk))\n            || _anjay_io_parse_objlnk(objlnk, out_oid, out_iid)) {\n        return ANJAY_ERR_BAD_REQUEST;\n    }\n    return 0;\n}\n\nstatic int lwm2m_cbor_get_null(anjay_unlocked_input_ctx_t *ctx) {\n    lwm2m_cbor_in_t *in = (lwm2m_cbor_in_t *) ctx;\n\n    if (!can_return_value(in)) {\n        return -1;\n    }\n\n    size_t initial_nesting_level =\n            _anjay_json_like_decoder_nesting_level(in->ctx);\n\n    if (_anjay_json_like_decoder_null(in->ctx)) {\n        return -1;\n    }\n\n    in->value_read = true;\n\n    return update_path_stack(in, initial_nesting_level);\n}\n\n#    ifdef ANJAY_WITH_LWM2M_GATEWAY\nstatic int get_prefix(anjay_json_like_decoder_t *ctx, char *out_prefix) {\n    anjay_io_cbor_bytes_ctx_t bytes_ctx;\n    if (_anjay_io_cbor_get_bytes_ctx(ctx, &bytes_ctx)) {\n        return -1;\n    }\n\n    bool message_finished;\n    size_t bytes_read;\n    if (_anjay_io_cbor_get_some_bytes(ctx, &bytes_ctx, out_prefix,\n                                      ANJAY_GATEWAY_MAX_PREFIX_LEN - 1,\n                                      &bytes_read, &message_finished)) {\n        return -1;\n    }\n    out_prefix[bytes_read] = '\\0';\n\n    return message_finished && bytes_read > 0 ? 0 : -1;\n}\n#    endif // ANJAY_WITH_LWM2M_GATEWAY\n\nstatic int get_id(anjay_json_like_decoder_t *ctx, uint16_t *out_id) {\n    anjay_json_like_number_t number;\n    if (_anjay_json_like_decoder_number(ctx, &number)\n            || number.type != ANJAY_JSON_LIKE_VALUE_UINT\n            || number.value.u64 >= ANJAY_ID_INVALID) {\n        LOG(DEBUG, _(\"invalid path ID\"));\n        return -1;\n    }\n\n    *out_id = (uint16_t) number.value.u64;\n    return 0;\n}\n\nstatic int\narray_to_relative_path(anjay_json_like_decoder_t *ctx,\n                       lwm2m_cbor_relative_path_t *out_relative_path) {\n    size_t initial_nesting_level = _anjay_json_like_decoder_nesting_level(ctx);\n\n    if (_anjay_json_like_decoder_enter_array(ctx)\n            || _anjay_json_like_decoder_nesting_level(ctx)\n                           == initial_nesting_level) {\n        return -1;\n    }\n\n#    ifdef ANJAY_WITH_LWM2M_GATEWAY\n    anjay_json_like_value_type_t type;\n    if (_anjay_json_like_decoder_current_value_type(ctx, &type)) {\n        return -1;\n    }\n\n    if (type == ANJAY_JSON_LIKE_VALUE_TEXT_STRING\n            && get_prefix(ctx, out_relative_path->prefix)) {\n        return -1;\n    }\n#    endif // ANJAY_WITH_LWM2M_GATEWAY\n\n    while (_anjay_json_like_decoder_nesting_level(ctx)\n           > initial_nesting_level) {\n        if (out_relative_path->ids_length == _ANJAY_URI_PATH_MAX_LENGTH) {\n            LOG(DEBUG, _(\"path too long\"));\n            return -1;\n        }\n\n        if (get_id(ctx,\n                   &out_relative_path->ids[out_relative_path->ids_length])) {\n            return -1;\n        }\n        out_relative_path->ids_length++;\n    }\n\n    return 0;\n}\n\nstatic int\nuint_to_relative_path(anjay_json_like_decoder_t *ctx,\n                      lwm2m_cbor_relative_path_t *out_relative_path) {\n    if (get_id(ctx, &out_relative_path->ids[0])) {\n        return -1;\n    }\n    out_relative_path->ids_length = 1;\n    return 0;\n}\n\n#    ifdef ANJAY_WITH_LWM2M_GATEWAY\nstatic int\nstring_to_relative_path(anjay_json_like_decoder_t *ctx,\n                        lwm2m_cbor_relative_path_t *out_relative_path) {\n    return get_prefix(ctx, out_relative_path->prefix);\n}\n#    endif // ANJAY_WITH_LWM2M_GATEWAY\n\nstatic int decode_path_fragment_and_update_stack(anjay_json_like_decoder_t *ctx,\n                                                 path_stack_t *stack) {\n    anjay_json_like_value_type_t type;\n    if (_anjay_json_like_decoder_current_value_type(ctx, &type)) {\n        return -1;\n    }\n\n    lwm2m_cbor_relative_path_t relative_path;\n    relative_path_init(&relative_path);\n\n    if (type == ANJAY_JSON_LIKE_VALUE_ARRAY) {\n        if (array_to_relative_path(ctx, &relative_path)) {\n            return -1;\n        }\n    } else if (type == ANJAY_JSON_LIKE_VALUE_UINT) {\n        if (uint_to_relative_path(ctx, &relative_path)) {\n            return -1;\n        }\n#    ifdef ANJAY_WITH_LWM2M_GATEWAY\n    } else if (type == ANJAY_JSON_LIKE_VALUE_TEXT_STRING) {\n        if (string_to_relative_path(ctx, &relative_path)) {\n            return -1;\n        }\n#    endif // ANJAY_WITH_LWM2M_GATEWAY\n    } else {\n        LOG(DEBUG, _(\"unexpected value in key\"));\n        return -1;\n    }\n\n    return path_push(stack, &relative_path);\n}\n\nstatic int decode_and_get_path(lwm2m_cbor_in_t *cbor_ctx,\n                               anjay_json_like_decoder_t *ctx,\n                               anjay_uri_path_t *out_path) {\n    anjay_json_like_value_type_t type;\n    do {\n        if (decode_path_fragment_and_update_stack(ctx, &cbor_ctx->path_stack)) {\n            return -1;\n        }\n\n        if (_anjay_json_like_decoder_current_value_type(ctx, &type)\n                || type == ANJAY_JSON_LIKE_VALUE_ARRAY) {\n            LOG(DEBUG, _(\"expected map or value\"));\n            return -1;\n        }\n\n        if (type == ANJAY_JSON_LIKE_VALUE_MAP\n                && _anjay_json_like_decoder_enter_map(ctx)) {\n            return -1;\n        }\n    } while (type == ANJAY_JSON_LIKE_VALUE_MAP);\n\n    *out_path = path_get(&cbor_ctx->path_stack);\n    return 0;\n}\n\nstatic bool uri_path_outside_base(const anjay_uri_path_t *path,\n                                  const anjay_uri_path_t *base) {\n#    ifdef ANJAY_WITH_LWM2M_GATEWAY\n    bool base_set = _anjay_uri_path_length(base) > 0\n                    || _anjay_uri_path_has_prefix(base);\n    if (base_set && !_anjay_uri_path_prefix_equal(path, base)) {\n        return true;\n    }\n#    endif // ANJAY_WITH_LWM2M_GATEWAY\n    return _anjay_uri_path_outside_base(path, base);\n}\n\nstatic int lwm2m_cbor_get_path(anjay_unlocked_input_ctx_t *ctx,\n                               anjay_uri_path_t *out_path,\n                               bool *out_is_array) {\n    lwm2m_cbor_in_t *in = (lwm2m_cbor_in_t *) ctx;\n\n    // Not representable in LwM2M CBOR\n    *out_is_array = false;\n\n    if (in->path.ids[0] != ANJAY_ID_INVALID) {\n        *out_path = in->path;\n        return 0;\n    }\n\n    if (_anjay_json_like_decoder_state(in->ctx)\n            == ANJAY_JSON_LIKE_DECODER_STATE_FINISHED) {\n        return ANJAY_GET_PATH_END;\n    }\n\n    if (decode_and_get_path(in, in->ctx, &in->path)) {\n        return ANJAY_ERR_BAD_REQUEST;\n    }\n\n    if (uri_path_outside_base(&in->path, &in->base)) {\n        LOG(LAZY_DEBUG,\n            _(\"parsed path \") \"%s\" _(\" would be outside of uri-path \") \"%s\",\n            ANJAY_DEBUG_MAKE_PATH(&in->path), ANJAY_DEBUG_MAKE_PATH(&in->base));\n        return ANJAY_ERR_BAD_REQUEST;\n    }\n\n    *out_path = in->path;\n    return 0;\n}\n\nstatic int lwm2m_cbor_next_entry(anjay_unlocked_input_ctx_t *ctx) {\n    lwm2m_cbor_in_t *in = (lwm2m_cbor_in_t *) ctx;\n    if (in->path.ids[0] == ANJAY_ID_INVALID) {\n        return 0;\n    }\n    in->path = MAKE_ROOT_PATH();\n    in->value_read = false;\n    return 0;\n}\n\nstatic int lwm2m_cbor_close(anjay_unlocked_input_ctx_t *ctx) {\n    lwm2m_cbor_in_t *in = (lwm2m_cbor_in_t *) ctx;\n\n    int result = 0;\n    if (_anjay_json_like_decoder_state(in->ctx)\n            != ANJAY_JSON_LIKE_DECODER_STATE_FINISHED) {\n        LOG(WARNING, _(\"LwM2M CBOR payload contains extraneous data\"));\n        result = ANJAY_ERR_BAD_REQUEST;\n    }\n\n    _anjay_json_like_decoder_delete(&in->ctx);\n    return result;\n}\n\nstatic const anjay_input_ctx_vtable_t LWM2M_CBOR_IN_VTABLE = {\n    .some_bytes = lwm2m_cbor_get_some_bytes,\n    .string = lwm2m_cbor_get_string,\n    .integer = lwm2m_cbor_get_integer,\n    .uint = lwm2m_cbor_get_uint,\n    .floating = lwm2m_cbor_get_double,\n    .boolean = lwm2m_cbor_get_bool,\n    .objlnk = lwm2m_cbor_get_objlnk,\n    .null = lwm2m_cbor_get_null,\n    .get_path = lwm2m_cbor_get_path,\n    .next_entry = lwm2m_cbor_next_entry,\n    .close = lwm2m_cbor_close\n};\n\nint _anjay_input_lwm2m_cbor_create(anjay_unlocked_input_ctx_t **out,\n                                   avs_stream_t *stream_ptr,\n                                   const anjay_uri_path_t *request_uri) {\n    int retval = -1;\n    lwm2m_cbor_in_t *in = NULL;\n    anjay_json_like_decoder_t *ctx =\n            _anjay_cbor_decoder_new(stream_ptr, MAX_LWM2M_CBOR_NEST_STACK_SIZE);\n    if (!ctx) {\n        return -1;\n    }\n\n    if (_anjay_json_like_decoder_enter_map(ctx)) {\n        retval = ANJAY_ERR_BAD_REQUEST;\n        goto error;\n    }\n    in = (lwm2m_cbor_in_t *) avs_calloc(1, sizeof(lwm2m_cbor_in_t));\n    if (!in) {\n        goto error;\n    }\n    in->input_ctx_vtable = &LWM2M_CBOR_IN_VTABLE;\n    in->ctx = ctx;\n    in->base = *request_uri;\n    in->path = MAKE_ROOT_PATH();\n    in->path_stack.path = MAKE_ROOT_PATH();\n    *out = (anjay_unlocked_input_ctx_t *) in;\n    return 0;\n\nerror:\n    _anjay_json_like_decoder_delete(&ctx);\n    avs_free(in);\n    return retval;\n}\n\n#    ifdef ANJAY_TEST\n#        include \"tests/core/io/lwm2m_cbor_in.c\"\n#    endif // ANJAY_TEST\n\n#endif // defined(ANJAY_WITH_CBOR) || defined(ANJAY_WITH_LWM2M12)\n"
  },
  {
    "path": "src/core/io/anjay_lwm2m_cbor_out.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#if defined(ANJAY_WITH_CBOR) && defined(ANJAY_WITH_LWM2M12)\n\n#    include <anjay/core.h>\n\n#    include <assert.h>\n#    include <inttypes.h>\n#    include <string.h>\n\n#    include <avsystem/commons/avs_stream.h>\n#    include <avsystem/commons/avs_utils.h>\n\n#    include \"anjay_common.h\"\n#    include \"anjay_vtable.h\"\n\n#    include \"cbor/anjay_cbor_encoder_ll.h\"\n\n#    define lwm2m_cbor_log(level, ...) \\\n        _anjay_log(lwm2m_cbor_out, level, __VA_ARGS__)\n\nVISIBILITY_SOURCE_BEGIN\n\ntypedef struct {\n    const anjay_ret_bytes_ctx_vtable_t *vtable;\n    size_t remaining_bytes;\n} lwm2m_cbor_bytes_t;\n\ntypedef struct senml_out_struct {\n    anjay_unlocked_output_ctx_t base;\n    avs_stream_t *stream;\n\n    anjay_uri_path_t path;\n    anjay_uri_path_t previous_path;\n    anjay_uri_path_t base_path;\n    bool base_path_processed;\n\n    lwm2m_cbor_bytes_t bytes;\n    bool returning_bytes;\n\n    uint8_t maps_opened;\n} lwm2m_cbor_out_t;\n\nstatic int encode_base_path(avs_stream_t *stream,\n                            const anjay_uri_path_t *path,\n                            size_t path_length) {\n    assert(path_length);\n#    ifdef ANJAY_WITH_LWM2M_GATEWAY\n    bool has_prefix = _anjay_uri_path_has_prefix(path);\n#    endif // ANJAY_WITH_LWM2M_GATEWAY\n\n    if (path_length == 1) {\n#    ifdef ANJAY_WITH_LWM2M_GATEWAY\n        if (has_prefix) {\n            return _anjay_cbor_ll_encode_string(stream, path->prefix);\n        }\n#    endif // ANJAY_WITH_LWM2M_GATEWAY\n        return _anjay_cbor_ll_encode_uint(stream, path->ids[0]);\n    } else {\n        if (_anjay_cbor_ll_definite_array_begin(stream, path_length)) {\n            return -1;\n        }\n\n#    ifdef ANJAY_WITH_LWM2M_GATEWAY\n        if (has_prefix) {\n            if (_anjay_cbor_ll_encode_string(stream, path->prefix)) {\n                return -1;\n            }\n            path_length--;\n        }\n#    endif // ANJAY_WITH_LWM2M_GATEWAY\n\n        for (size_t i = 0; i < path_length; i++) {\n            if (_anjay_cbor_ll_encode_uint(stream, path->ids[i])) {\n                return -1;\n            }\n        }\n        return 0;\n    }\n}\n\nstatic int encode_subpath(lwm2m_cbor_out_t *ctx,\n                          const anjay_uri_path_t *path,\n                          size_t start_index) {\n    size_t path_length = _anjay_uri_path_length(path);\n    assert(start_index < path_length);\n\n#    ifdef ANJAY_WITH_LWM2M_GATEWAY\n    if (start_index == 0 && _anjay_uri_path_has_prefix(path)\n            && !_anjay_uri_path_prefix_equal(&ctx->base_path, path)) {\n        if (_anjay_cbor_ll_encode_string(ctx->stream, path->prefix)) {\n            return -1;\n        }\n    } else {\n#    endif // ANJAY_WITH_LWM2M_GATEWAY\n        if (_anjay_cbor_ll_encode_uint(ctx->stream, path->ids[start_index])) {\n            return -1;\n        }\n        start_index++;\n#    ifdef ANJAY_WITH_LWM2M_GATEWAY\n    }\n#    endif // ANJAY_WITH_LWM2M_GATEWAY\n\n    for (; start_index < path_length; start_index++) {\n        if (_anjay_cbor_ll_indefinite_map_begin(ctx->stream)\n                || _anjay_cbor_ll_encode_uint(ctx->stream,\n                                              path->ids[start_index])) {\n            return -1;\n        }\n        ctx->maps_opened++;\n    }\n\n    return 0;\n}\n\n/**\n * @param a First path\n * @param b Second path\n *\n * @returns Number of consecutive equal IDs counted from the beginning of paths.\n * @c _ANJAY_URI_PATH_MAX_LENGTH if paths are equal, even if they're shorter.\n */\nstatic size_t uri_path_span(const anjay_uri_path_t *a,\n                            const anjay_uri_path_t *b) {\n    size_t equal_ids = 0;\n    for (; equal_ids < _ANJAY_URI_PATH_MAX_LENGTH; equal_ids++) {\n        if (a->ids[equal_ids] != b->ids[equal_ids]) {\n            break;\n        }\n    }\n    return equal_ids;\n}\n\nstatic int encode_path(lwm2m_cbor_out_t *ctx) {\n    if (!ctx->base_path_processed) {\n        size_t base_path_length = _anjay_uri_path_length(&ctx->base_path);\n#    ifdef ANJAY_WITH_LWM2M_GATEWAY\n        base_path_length +=\n                (size_t) _anjay_uri_path_has_prefix(&ctx->base_path);\n#    endif // ANJAY_WITH_LWM2M_GATEWAY\n        ctx->base_path_processed = true;\n        ctx->previous_path = ctx->base_path;\n        if (base_path_length) {\n            // If base path is specified, encode it as a single ID or an array\n            // of them.\n            if (encode_base_path(\n                        ctx->stream, &ctx->base_path, base_path_length)) {\n                return -1;\n            }\n\n            // If the currently processed path is the same as the base path,\n            // return immediately, as the map element is already started.\n            // Otherwise, start a map, so the next ID could be written.\n            if (_anjay_uri_path_equal(&ctx->base_path, &ctx->path)\n#    ifdef ANJAY_WITH_LWM2M_GATEWAY\n                    && _anjay_uri_path_prefix_equal(&ctx->base_path, &ctx->path)\n#    endif // ANJAY_WITH_LWM2M_GATEWAY\n            ) {\n                return 0;\n            } else {\n                if (_anjay_cbor_ll_indefinite_map_begin(ctx->stream)) {\n                    return -1;\n                }\n                ctx->maps_opened++;\n            }\n        }\n    }\n\n    size_t path_span = uri_path_span(&ctx->previous_path, &ctx->path);\n    size_t subpath_start_index = 0;\n    if (path_span == _ANJAY_URI_PATH_MAX_LENGTH) {\n        // Previous path is the same as current path, so we don't need to close\n        // the map, but only encode the last ID of the new path.\n        assert(_anjay_uri_path_length(&ctx->path) != 0);\n        subpath_start_index = _anjay_uri_path_length(&ctx->path) - 1;\n    } else {\n        // Paths are different. If the previous path without the last ID doesn't\n        // match the beginning of the new one, then some of the opened maps must\n        // be closed first.\n        subpath_start_index = path_span;\n        size_t previous_path_length =\n                _anjay_uri_path_length(&ctx->previous_path);\n        if (path_span < previous_path_length) {\n            size_t maps_to_pop = previous_path_length - path_span - 1;\n#    ifdef ANJAY_WITH_LWM2M_GATEWAY\n            if (!_anjay_uri_path_prefix_equal(&ctx->previous_path,\n                                              &ctx->path)) {\n                maps_to_pop++;\n            }\n#    endif // ANJAY_WITH_LWM2M_GATEWAY\n            assert(maps_to_pop < ctx->maps_opened);\n            while (maps_to_pop--) {\n                if (_anjay_cbor_ll_indefinite_map_end(ctx->stream)) {\n                    return -1;\n                }\n                ctx->maps_opened--;\n            }\n        }\n    }\n\n    return encode_subpath(ctx, &ctx->path, subpath_start_index);\n}\n\nstatic int finish_ret_bytes(lwm2m_cbor_out_t *ctx) {\n    ctx->returning_bytes = false;\n    return ctx->bytes.remaining_bytes ? -1 : 0;\n}\n\nstatic int element_begin(lwm2m_cbor_out_t *ctx) {\n    if (!_anjay_uri_path_has(&ctx->path, ANJAY_ID_RID)) {\n        return -1;\n    }\n\n    if (ctx->returning_bytes) {\n        int result = finish_ret_bytes(ctx);\n        if (result) {\n            return result;\n        }\n    }\n\n    if (encode_path(ctx)) {\n        return -1;\n    }\n\n    ctx->previous_path = ctx->path;\n    ctx->path = MAKE_ROOT_PATH();\n    return 0;\n}\n\nstatic int streamed_bytes_append(anjay_unlocked_ret_bytes_ctx_t *ctx_,\n                                 const void *data,\n                                 size_t length) {\n    lwm2m_cbor_bytes_t *bytes_ctx = (lwm2m_cbor_bytes_t *) ctx_;\n    if (length > bytes_ctx->remaining_bytes) {\n        return -1;\n    }\n\n    lwm2m_cbor_out_t *ctx =\n            AVS_CONTAINER_OF(bytes_ctx, lwm2m_cbor_out_t, bytes);\n    int retval = _anjay_cbor_ll_bytes_append(ctx->stream, data, length);\n    if (!retval) {\n        bytes_ctx->remaining_bytes -= length;\n    }\n    return retval;\n}\n\nstatic const anjay_ret_bytes_ctx_vtable_t STREAMED_BYTES_VTABLE = {\n    .append = streamed_bytes_append\n};\n\nstatic int\nlwm2m_cbor_ret_bytes(anjay_unlocked_output_ctx_t *ctx_,\n                     size_t length,\n                     anjay_unlocked_ret_bytes_ctx_t **out_bytes_ctx) {\n    lwm2m_cbor_out_t *ctx = (lwm2m_cbor_out_t *) ctx_;\n\n    if (element_begin(ctx) || _anjay_cbor_ll_bytes_begin(ctx->stream, length)) {\n        return -1;\n    }\n\n    ctx->returning_bytes = true;\n    ctx->bytes.remaining_bytes = length;\n    *out_bytes_ctx = (anjay_unlocked_ret_bytes_ctx_t *) &ctx->bytes;\n    return 0;\n}\n\nstatic int lwm2m_cbor_ret_string(anjay_unlocked_output_ctx_t *ctx_,\n                                 const char *value) {\n    lwm2m_cbor_out_t *ctx = (lwm2m_cbor_out_t *) ctx_;\n    if (element_begin(ctx)\n            || _anjay_cbor_ll_encode_string(ctx->stream, value)) {\n        return -1;\n    }\n    return 0;\n}\n\nstatic int lwm2m_cbor_ret_integer(anjay_unlocked_output_ctx_t *ctx_,\n                                  int64_t value) {\n    lwm2m_cbor_out_t *ctx = (lwm2m_cbor_out_t *) ctx_;\n    if (element_begin(ctx) || _anjay_cbor_ll_encode_int(ctx->stream, value)) {\n        return -1;\n    }\n    return 0;\n}\n\nstatic int lwm2m_cbor_ret_uint(anjay_unlocked_output_ctx_t *ctx_,\n                               uint64_t value) {\n    lwm2m_cbor_out_t *ctx = (lwm2m_cbor_out_t *) ctx_;\n    if (element_begin(ctx) || _anjay_cbor_ll_encode_uint(ctx->stream, value)) {\n        return -1;\n    }\n    return 0;\n}\n\nstatic int lwm2m_cbor_ret_double(anjay_unlocked_output_ctx_t *ctx_,\n                                 double value) {\n    lwm2m_cbor_out_t *ctx = (lwm2m_cbor_out_t *) ctx_;\n    if (element_begin(ctx)\n            || _anjay_cbor_ll_encode_double(ctx->stream, value)) {\n        return -1;\n    }\n    return 0;\n}\n\nstatic int lwm2m_cbor_ret_bool(anjay_unlocked_output_ctx_t *ctx_, bool value) {\n    lwm2m_cbor_out_t *ctx = (lwm2m_cbor_out_t *) ctx_;\n    if (element_begin(ctx) || _anjay_cbor_ll_encode_bool(ctx->stream, value)) {\n        return -1;\n    }\n    return 0;\n}\n\nstatic int lwm2m_cbor_ret_objlnk(anjay_unlocked_output_ctx_t *ctx_,\n                                 anjay_oid_t oid,\n                                 anjay_iid_t iid) {\n    char buf[MAX_OBJLNK_STRING_SIZE];\n    if (avs_simple_snprintf(buf, sizeof(buf), \"%\" PRIu16 \":%\" PRIu16, oid, iid)\n            < 0) {\n        return -1;\n    }\n\n    return lwm2m_cbor_ret_string(ctx_, buf);\n}\n\nstatic int lwm2m_cbor_ret_start_aggregate(anjay_unlocked_output_ctx_t *ctx_) {\n    lwm2m_cbor_out_t *ctx = (lwm2m_cbor_out_t *) ctx_;\n    if (_anjay_uri_path_leaf_is(&ctx->path, ANJAY_ID_IID)\n            || _anjay_uri_path_leaf_is(&ctx->path, ANJAY_ID_RID)) {\n        ctx->path = MAKE_ROOT_PATH();\n        return 0;\n    } else {\n        return -1;\n    }\n}\n\nstatic bool uri_path_outside_base(const anjay_uri_path_t *path,\n                                  const anjay_uri_path_t *base) {\n#    ifdef ANJAY_WITH_LWM2M_GATEWAY\n    bool base_set = _anjay_uri_path_length(base) > 0\n                    || _anjay_uri_path_has_prefix(base);\n    if (base_set && !_anjay_uri_path_prefix_equal(path, base)) {\n        return true;\n    }\n#    endif // ANJAY_WITH_LWM2M_GATEWAY\n    return _anjay_uri_path_outside_base(path, base);\n}\n\nstatic int lwm2m_cbor_set_path(anjay_unlocked_output_ctx_t *ctx_,\n                               const anjay_uri_path_t *uri) {\n    lwm2m_cbor_out_t *ctx = (lwm2m_cbor_out_t *) ctx_;\n    AVS_ASSERT(!uri_path_outside_base(uri, &ctx->base_path),\n               \"Attempted to set path outside the context's base path. \"\n               \"This is a bug in resource reading logic.\");\n    if (_anjay_uri_path_length(&ctx->path) > 0\n#    ifdef ANJAY_WITH_LWM2M_GATEWAY\n            || _anjay_uri_path_has_prefix(&ctx->path)\n#    endif // ANJAY_WITH_LWM2M_GATEWAY\n    ) {\n        lwm2m_cbor_log(ERROR, _(\"Path already set\"));\n        return -1;\n    }\n    ctx->path = *uri;\n    return 0;\n}\n\nstatic int lwm2m_cbor_clear_path(anjay_unlocked_output_ctx_t *ctx_) {\n    lwm2m_cbor_out_t *ctx = (lwm2m_cbor_out_t *) ctx_;\n    if (_anjay_uri_path_length(&ctx->path) == 0\n#    ifdef ANJAY_WITH_LWM2M_GATEWAY\n            && !_anjay_uri_path_has_prefix(&ctx->path)\n#    endif // ANJAY_WITH_LWM2M_GATEWAY\n    ) {\n        lwm2m_cbor_log(ERROR, _(\"Path not set\"));\n        return -1;\n    }\n    ctx->path = MAKE_ROOT_PATH();\n    return 0;\n}\n\nstatic int lwm2m_cbor_output_close(anjay_unlocked_output_ctx_t *ctx_) {\n    lwm2m_cbor_out_t *ctx = (lwm2m_cbor_out_t *) ctx_;\n    int result = 0;\n    if (ctx->returning_bytes) {\n        result = finish_ret_bytes(ctx);\n    }\n\n    while (ctx->maps_opened--) {\n        _anjay_update_ret(&result,\n                          _anjay_cbor_ll_indefinite_map_end(ctx->stream));\n    }\n\n    if (_anjay_uri_path_length(&ctx->path) > 0\n#    ifdef ANJAY_WITH_LWM2M_GATEWAY\n            || _anjay_uri_path_has_prefix(&ctx->path)\n#    endif // ANJAY_WITH_LWM2M_GATEWAY\n    ) {\n        _anjay_update_ret(&result, ANJAY_OUTCTXERR_ANJAY_RET_NOT_CALLED);\n    }\n    return result;\n}\n\nstatic const anjay_output_ctx_vtable_t LWM2M_CBOR_OUT_VTABLE = {\n    .bytes_begin = lwm2m_cbor_ret_bytes,\n    .string = lwm2m_cbor_ret_string,\n    .integer = lwm2m_cbor_ret_integer,\n    .uint = lwm2m_cbor_ret_uint,\n    .floating = lwm2m_cbor_ret_double,\n    .boolean = lwm2m_cbor_ret_bool,\n    .objlnk = lwm2m_cbor_ret_objlnk,\n    .start_aggregate = lwm2m_cbor_ret_start_aggregate,\n    .set_path = lwm2m_cbor_set_path,\n    .clear_path = lwm2m_cbor_clear_path,\n    .close = lwm2m_cbor_output_close\n};\n\nanjay_unlocked_output_ctx_t *\n_anjay_output_lwm2m_cbor_create(avs_stream_t *stream,\n                                const anjay_uri_path_t *uri) {\n    if (_anjay_cbor_ll_indefinite_map_begin(stream)) {\n        return NULL;\n    }\n\n    lwm2m_cbor_out_t *ctx =\n            (lwm2m_cbor_out_t *) avs_calloc(1, sizeof(lwm2m_cbor_out_t));\n    if (!ctx) {\n        return NULL;\n    }\n\n    ctx->maps_opened = 1;\n    ctx->stream = stream;\n    ctx->base.vtable = &LWM2M_CBOR_OUT_VTABLE;\n    ctx->bytes.vtable = &STREAMED_BYTES_VTABLE;\n    ctx->path = MAKE_ROOT_PATH();\n    ctx->base_path = *uri;\n\n    return (anjay_unlocked_output_ctx_t *) ctx;\n}\n\n#    ifdef ANJAY_TEST\n#        include \"tests/core/io/lwm2m_cbor_out.c\"\n#    endif // ANJAY_TEST\n\n#endif // defined(ANJAY_WITH_CBOR) && defined(ANJAY_WITH_LWM2M12)\n"
  },
  {
    "path": "src/core/io/anjay_opaque.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#include <stdlib.h>\n\n#include <avsystem/commons/avs_stream.h>\n\n#include \"../coap/anjay_content_format.h\"\n\n#include \"anjay_vtable.h\"\n\nVISIBILITY_SOURCE_BEGIN\n\ntypedef struct {\n    const anjay_ret_bytes_ctx_vtable_t *vtable;\n    avs_stream_t *stream;\n    size_t bytes_left;\n} opaque_bytes_t;\n\nstatic int opaque_ret_bytes_append(anjay_unlocked_ret_bytes_ctx_t *ctx_,\n                                   const void *data,\n                                   size_t length) {\n    opaque_bytes_t *ctx = (opaque_bytes_t *) ctx_;\n    if (!length) {\n        return 0;\n    }\n    if (length <= ctx->bytes_left\n            && avs_is_ok(avs_stream_write(ctx->stream, data, length))) {\n        ctx->bytes_left -= length;\n        return 0;\n    }\n    return -1;\n}\n\nstatic const anjay_ret_bytes_ctx_vtable_t OPAQUE_BYTES_VTABLE = {\n    .append = opaque_ret_bytes_append\n};\n\ntypedef enum {\n    STATE_INITIAL,\n    STATE_PATH_SET,\n    STATE_RETURNING\n} opaque_out_state_t;\n\ntypedef struct {\n    anjay_unlocked_output_ctx_t base;\n    opaque_out_state_t state;\n    opaque_bytes_t bytes;\n} opaque_out_t;\n\nstatic int opaque_ret_bytes(anjay_unlocked_output_ctx_t *ctx_,\n                            size_t length,\n                            anjay_unlocked_ret_bytes_ctx_t **out_bytes_ctx) {\n    opaque_out_t *ctx = (opaque_out_t *) ctx_;\n    if (ctx->state == STATE_PATH_SET) {\n        ctx->state = STATE_RETURNING;\n        ctx->bytes.bytes_left = length;\n        *out_bytes_ctx = (anjay_unlocked_ret_bytes_ctx_t *) &ctx->bytes;\n        return 0;\n    }\n    return -1;\n}\n\nstatic int opaque_set_path(anjay_unlocked_output_ctx_t *ctx_,\n                           const anjay_uri_path_t *path) {\n    opaque_out_t *ctx = (opaque_out_t *) ctx_;\n    if (ctx->state == STATE_PATH_SET) {\n        return -1;\n    } else if (ctx->state != STATE_INITIAL\n               || !_anjay_uri_path_has(path, ANJAY_ID_RID)) {\n        return ANJAY_OUTCTXERR_FORMAT_MISMATCH;\n    }\n    ctx->state = STATE_PATH_SET;\n    return 0;\n}\n\nstatic int opaque_clear_path(anjay_unlocked_output_ctx_t *ctx_) {\n    opaque_out_t *ctx = (opaque_out_t *) ctx_;\n    if (ctx->state != STATE_PATH_SET) {\n        return -1;\n    }\n    ctx->state = STATE_INITIAL;\n    return 0;\n}\n\nstatic int opaque_ret_close(anjay_unlocked_output_ctx_t *ctx_) {\n    opaque_out_t *ctx = (opaque_out_t *) ctx_;\n    return ctx->state == STATE_RETURNING ? 0\n                                         : ANJAY_OUTCTXERR_ANJAY_RET_NOT_CALLED;\n}\n\nstatic const anjay_output_ctx_vtable_t OPAQUE_OUT_VTABLE = {\n    .bytes_begin = opaque_ret_bytes,\n    .set_path = opaque_set_path,\n    .clear_path = opaque_clear_path,\n    .close = opaque_ret_close\n};\n\nanjay_unlocked_output_ctx_t *_anjay_output_opaque_create(avs_stream_t *stream) {\n    opaque_out_t *ctx = (opaque_out_t *) avs_calloc(1, sizeof(opaque_out_t));\n    if (ctx) {\n        ctx->base.vtable = &OPAQUE_OUT_VTABLE;\n        ctx->bytes.vtable = &OPAQUE_BYTES_VTABLE;\n        ctx->bytes.stream = stream;\n    }\n    return (anjay_unlocked_output_ctx_t *) ctx;\n}\n\ntypedef struct {\n    const anjay_input_ctx_vtable_t *vtable;\n    avs_stream_t *stream;\n    bool msg_finished;\n\n    anjay_uri_path_t request_uri;\n} opaque_in_t;\n\nstatic int opaque_get_some_bytes(anjay_unlocked_input_ctx_t *ctx,\n                                 size_t *out_bytes_read,\n                                 bool *out_message_finished,\n                                 void *out_buf,\n                                 size_t buf_size) {\n    avs_error_t err =\n            avs_stream_read(((opaque_in_t *) ctx)->stream, out_bytes_read,\n                            out_message_finished, out_buf, buf_size);\n    ((opaque_in_t *) ctx)->msg_finished = *out_message_finished;\n    return avs_is_ok(err) ? 0 : -1;\n}\n\nstatic int opaque_in_close(anjay_unlocked_input_ctx_t *ctx_) {\n    (void) ctx_;\n    return 0;\n}\n\nstatic int opaque_in_get_path(anjay_unlocked_input_ctx_t *ctx_,\n                              anjay_uri_path_t *out_path,\n                              bool *out_is_array) {\n    opaque_in_t *ctx = (opaque_in_t *) ctx_;\n    if (ctx->msg_finished) {\n        return ANJAY_GET_PATH_END;\n    }\n    if (!_anjay_uri_path_has(&ctx->request_uri, ANJAY_ID_RID)) {\n        return ANJAY_ERR_BAD_REQUEST;\n    }\n    *out_is_array = false;\n    *out_path = ctx->request_uri;\n    return 0;\n}\n\nstatic int opaque_in_next_entry(anjay_unlocked_input_ctx_t *ctx) {\n    (void) ctx;\n    return 0;\n}\n\nstatic int bad_request() {\n    return ANJAY_ERR_BAD_REQUEST;\n}\n\nstatic const anjay_input_ctx_vtable_t OPAQUE_IN_VTABLE = {\n    .some_bytes = opaque_get_some_bytes,\n    .close = opaque_in_close,\n    .string = (anjay_input_ctx_string_t) bad_request,\n    .integer = (anjay_input_ctx_integer_t) bad_request,\n#ifdef ANJAY_WITH_LWM2M11\n    .uint = (anjay_input_ctx_uint_t) bad_request,\n#endif // ANJAY_WITH_LWM2M11\n    .floating = (anjay_input_ctx_floating_t) bad_request,\n    .boolean = (anjay_input_ctx_boolean_t) bad_request,\n    .objlnk = (anjay_input_ctx_objlnk_t) bad_request,\n    .get_path = opaque_in_get_path,\n    .update_root_path = (anjay_input_ctx_update_root_path_t) bad_request,\n    .next_entry = opaque_in_next_entry\n};\n\nint _anjay_input_opaque_create(anjay_unlocked_input_ctx_t **out,\n                               avs_stream_t *stream_ptr,\n                               const anjay_uri_path_t *request_uri) {\n    opaque_in_t *ctx = (opaque_in_t *) avs_calloc(1, sizeof(opaque_in_t));\n    *out = (anjay_unlocked_input_ctx_t *) ctx;\n    if (!ctx) {\n        return -1;\n    }\n\n    ctx->vtable = &OPAQUE_IN_VTABLE;\n    ctx->stream = stream_ptr;\n    ctx->request_uri = request_uri ? *request_uri : MAKE_ROOT_PATH();\n\n    return 0;\n}\n"
  },
  {
    "path": "src/core/io/anjay_output_buf.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#include \"anjay_vtable.h\"\n\nVISIBILITY_SOURCE_BEGIN\n\nstatic int output_buf_ret_bytes(anjay_unlocked_output_ctx_t *ctx_,\n                                const void *data,\n                                size_t data_size) {\n    anjay_output_buf_ctx_t *ctx = (anjay_output_buf_ctx_t *) ctx_;\n    return avs_is_ok(avs_stream_write((avs_stream_t *) ctx->stream, data,\n                                      data_size))\n                   ? 0\n                   : -1;\n}\n\nstatic int output_buf_ret_string(anjay_unlocked_output_ctx_t *ctx,\n                                 const char *str) {\n    return output_buf_ret_bytes(ctx, str, strlen(str));\n}\n\n#define DEFINE_RET_HANDLER(Suffix, Type)                                 \\\n    static int output_buf_ret_##Suffix(anjay_unlocked_output_ctx_t *ctx, \\\n                                       Type value) {                     \\\n        return output_buf_ret_bytes(ctx, &value, sizeof(value));         \\\n    }\n\nDEFINE_RET_HANDLER(integer, int64_t) // output_buf_ret_integer\n#ifdef ANJAY_WITH_LWM2M11\nDEFINE_RET_HANDLER(uint, uint64_t) // output_buf_ret_u64\n#endif                             // ANJAY_WITH_LWM2M11\nDEFINE_RET_HANDLER(double, double) // output_buf_ret_double\nDEFINE_RET_HANDLER(bool, bool)     // output_buf_ret_bool\n\nstatic int\noutput_buf_ret_bytes_begin(anjay_unlocked_output_ctx_t *ctx,\n                           size_t length,\n                           anjay_unlocked_ret_bytes_ctx_t **out_bytes_ctx) {\n    (void) length;\n    *out_bytes_ctx =\n            (anjay_unlocked_ret_bytes_ctx_t *) &((anjay_output_buf_ctx_t *) ctx)\n                    ->ret_bytes_vtable;\n    return 0;\n}\n\nstatic int output_buf_ret_bytes_append(anjay_unlocked_ret_bytes_ctx_t *ctx,\n                                       const void *data,\n                                       size_t size) {\n    return output_buf_ret_bytes(\n            (anjay_unlocked_output_ctx_t *) AVS_CONTAINER_OF(\n                    ctx, anjay_output_buf_ctx_t, ret_bytes_vtable),\n            data, size);\n}\n\nstatic int output_buf_ret_objlnk(anjay_unlocked_output_ctx_t *ctx,\n                                 anjay_oid_t oid,\n                                 anjay_iid_t iid) {\n    const uint32_t objlnk_encoded = (uint32_t) (oid << 16) | iid;\n    return output_buf_ret_bytes(ctx, &objlnk_encoded, sizeof(objlnk_encoded));\n}\n\nstatic const anjay_output_ctx_vtable_t BUF_OUT_VTABLE = {\n    .bytes_begin = output_buf_ret_bytes_begin,\n    .string = output_buf_ret_string,\n    .integer = output_buf_ret_integer,\n#ifdef ANJAY_WITH_LWM2M11\n    .uint = output_buf_ret_uint,\n#endif // ANJAY_WITH_LWM2M11\n    .floating = output_buf_ret_double,\n    .boolean = output_buf_ret_bool,\n    .objlnk = output_buf_ret_objlnk\n};\n\nstatic const anjay_ret_bytes_ctx_vtable_t BUF_BYTES_VTABLE = {\n    .append = output_buf_ret_bytes_append\n};\n\nanjay_output_buf_ctx_t _anjay_output_buf_ctx_init(avs_stream_t *stream) {\n    return (anjay_output_buf_ctx_t) {\n        .base = {\n            .vtable = &BUF_OUT_VTABLE\n        },\n        .ret_bytes_vtable = &BUF_BYTES_VTABLE,\n        .stream = stream\n    };\n}\n"
  },
  {
    "path": "src/core/io/anjay_senml_in.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#if defined(ANJAY_WITH_CBOR) || defined(ANJAY_WITH_SENML_JSON)\n\n#    include <avsystem/commons/avs_base64.h>\n#    include <avsystem/commons/avs_stream_membuf.h>\n#    include <avsystem/commons/avs_stream_v_table.h>\n#    include <avsystem/commons/avs_utils.h>\n\n#    include \"../anjay_utils_private.h\"\n\n#    ifdef ANJAY_WITH_CBOR\n#        include \"cbor/anjay_json_like_cbor_decoder.h\"\n#    endif // ANJAY_WITH_CBOR\n\n#    ifdef ANJAY_WITH_SENML_JSON\n#        include \"json/anjay_json_decoder.h\"\n#    endif // ANJAY_WITH_SENML_JSON\n\n#    include \"anjay_common.h\"\n#    include \"anjay_vtable.h\"\n\n#    ifdef ANJAY_WITH_LWM2M_GATEWAY\n#        include <ctype.h>\n#    endif // ANJAY_WITH_LWM2M_GATEWAY\n#    include <errno.h>\n#    include <math.h>\n\nVISIBILITY_SOURCE_BEGIN\n\n#    define LOG(...) _anjay_log(senml_in, __VA_ARGS__)\n\ntypedef struct senml_in senml_in_t;\n\ntypedef int get_senml_label_t(senml_in_t *in, senml_label_t *out_label);\n\ntypedef int parse_opaque_value_t(senml_in_t *in);\n\ntypedef struct {\n    get_senml_label_t *get_senml_label;\n    parse_opaque_value_t *parse_opaque_value;\n} senml_deserialization_vtable_t;\n\ntypedef struct {\n    char path[MAX_PATH_STRING_SIZE];\n    anjay_json_like_value_type_t type;\n    union {\n        bool boolean;\n        anjay_json_like_number_t number;\n        struct {\n            void *data;\n            size_t size;\n            size_t bytes_read;\n        } bytes;\n    } value;\n} senml_cached_entry_t;\n\nstatic void cached_entry_reset(senml_cached_entry_t *entry) {\n    if (entry->type == ANJAY_JSON_LIKE_VALUE_TEXT_STRING\n            || entry->type == ANJAY_JSON_LIKE_VALUE_BYTE_STRING) {\n        avs_free(entry->value.bytes.data);\n    }\n    memset(entry, 0, sizeof(*entry));\n}\n\nstruct senml_in {\n    const anjay_input_ctx_vtable_t *input_ctx_vtable;\n    const senml_deserialization_vtable_t *deserialization_vtable;\n    anjay_json_like_decoder_t *ctx;\n\n    bool composite_read;\n\n    /* Currently processed entry - shared between entire context chain. */\n    senml_cached_entry_t *entry;\n    /* Set to true if the value associated with an entry has been read. */\n    bool value_read;\n    /* Current basename set in the payload. */\n    char basename[MAX_PATH_STRING_SIZE];\n    /* A path which must be a prefix of the currently processed `path`. */\n    anjay_uri_path_t base;\n\n    /* Currently processed path. */\n    anjay_uri_path_t path;\n};\n\nstatic int get_i64(senml_in_t *in, int64_t *out_value) {\n    anjay_json_like_number_t value;\n    if (_anjay_json_like_decoder_number(in->ctx, &value)) {\n        return -1;\n    }\n    return _anjay_json_like_decoder_get_i64_from_number(&value, out_value);\n}\n\nstatic int get_short_string(senml_in_t *in, char *out_string, size_t size) {\n    assert(size > 0);\n    avs_stream_outbuf_t stream = AVS_STREAM_OUTBUF_STATIC_INITIALIZER;\n    avs_stream_outbuf_set_buffer(&stream, out_string, size - 1);\n    if (_anjay_json_like_decoder_bytes(in->ctx, (avs_stream_t *) &stream)) {\n        return -1;\n    }\n    out_string[avs_stream_outbuf_offset(&stream)] = '\\0';\n    return 0;\n}\n\nstatic bool can_return_value(senml_in_t *in) {\n    return in->path.ids[0] != ANJAY_ID_INVALID && !in->value_read;\n}\n\nstatic int read_some_cached_bytes(senml_in_t *in,\n                                  anjay_json_like_value_type_t bytes_type,\n                                  void *out_buf,\n                                  size_t *inout_size,\n                                  bool *out_message_finished) {\n    assert(bytes_type == ANJAY_JSON_LIKE_VALUE_BYTE_STRING\n           || bytes_type == ANJAY_JSON_LIKE_VALUE_TEXT_STRING);\n\n    if (!can_return_value(in)) {\n        return -1;\n    }\n    if (in->entry->type != bytes_type) {\n        return ANJAY_ERR_BAD_REQUEST;\n    }\n    const size_t bytes_left =\n            in->entry->value.bytes.size - in->entry->value.bytes.bytes_read;\n    const size_t bytes_to_write = AVS_MIN(*inout_size, bytes_left);\n    memcpy(out_buf,\n           &((const uint8_t *) in->entry->value.bytes\n                     .data)[in->entry->value.bytes.bytes_read],\n           bytes_to_write);\n    in->entry->value.bytes.bytes_read += bytes_to_write;\n    *inout_size = bytes_to_write;\n\n    const bool finished =\n            (in->entry->value.bytes.bytes_read == in->entry->value.bytes.size);\n    if (out_message_finished) {\n        *out_message_finished = finished;\n    }\n    if (!finished) {\n        return ANJAY_BUFFER_TOO_SHORT;\n    }\n    in->value_read = true;\n    return 0;\n}\n\nstatic int senml_get_some_bytes(anjay_unlocked_input_ctx_t *ctx,\n                                size_t *out_bytes_read,\n                                bool *out_message_finished,\n                                void *out_buf,\n                                size_t buf_size) {\n    senml_in_t *in = (senml_in_t *) ctx;\n    int retval = read_some_cached_bytes(in,\n                                        ANJAY_JSON_LIKE_VALUE_BYTE_STRING,\n                                        out_buf,\n                                        &buf_size,\n                                        out_message_finished);\n    *out_bytes_read = buf_size;\n    if (retval == ANJAY_BUFFER_TOO_SHORT) {\n        return 0;\n    }\n    return retval;\n}\n\nstatic int senml_get_string(anjay_unlocked_input_ctx_t *ctx,\n                            char *out_buf,\n                            size_t buf_size) {\n    assert(buf_size);\n    senml_in_t *in = (senml_in_t *) ctx;\n\n    /* make space for null terminator */\n    --buf_size;\n    int retval = read_some_cached_bytes(in, ANJAY_JSON_LIKE_VALUE_TEXT_STRING,\n                                        out_buf, &buf_size, NULL);\n    out_buf[buf_size] = '\\0';\n    return retval;\n}\n\nstatic bool is_type_numeric(anjay_json_like_value_type_t type) {\n    switch (type) {\n    case ANJAY_JSON_LIKE_VALUE_UINT:\n    case ANJAY_JSON_LIKE_VALUE_NEGATIVE_INT:\n    case ANJAY_JSON_LIKE_VALUE_FLOAT:\n    case ANJAY_JSON_LIKE_VALUE_DOUBLE:\n        return true;\n    default:\n        return false;\n    }\n}\n\nstatic int senml_get_integer(anjay_unlocked_input_ctx_t *ctx,\n                             int64_t *out_value) {\n    senml_in_t *in = (senml_in_t *) ctx;\n    if (!can_return_value(in)) {\n        return -1;\n    }\n\n    if (!is_type_numeric(in->entry->type)\n            || _anjay_json_like_decoder_get_i64_from_number(\n                       &in->entry->value.number, out_value)) {\n        return ANJAY_ERR_BAD_REQUEST;\n    }\n\n    in->value_read = true;\n    return 0;\n}\n\nstatic int senml_get_uint(anjay_unlocked_input_ctx_t *ctx,\n                          uint64_t *out_value) {\n    senml_in_t *in = (senml_in_t *) ctx;\n    if (!can_return_value(in)) {\n        return -1;\n    }\n\n    if (!is_type_numeric(in->entry->type)\n            || _anjay_json_like_decoder_get_u64_from_number(\n                       &in->entry->value.number, out_value)) {\n        return ANJAY_ERR_BAD_REQUEST;\n    }\n\n    in->value_read = true;\n    return 0;\n}\n\nstatic int senml_get_double(anjay_unlocked_input_ctx_t *ctx,\n                            double *out_value) {\n    senml_in_t *in = (senml_in_t *) ctx;\n    if (!can_return_value(in)) {\n        return -1;\n    }\n\n    if (!is_type_numeric(in->entry->type)\n            || _anjay_json_like_decoder_get_double_from_number(\n                       &in->entry->value.number, out_value)) {\n        return ANJAY_ERR_BAD_REQUEST;\n    }\n\n    in->value_read = true;\n    return 0;\n}\n\nstatic int senml_get_bool(anjay_unlocked_input_ctx_t *ctx, bool *out_value) {\n    senml_in_t *in = (senml_in_t *) ctx;\n\n    if (!can_return_value(in)) {\n        return -1;\n    }\n    if (in->entry->type != ANJAY_JSON_LIKE_VALUE_BOOL) {\n        return ANJAY_ERR_BAD_REQUEST;\n    }\n    *out_value = in->entry->value.boolean;\n    in->value_read = true;\n    return 0;\n}\n\nstatic int senml_get_objlnk(anjay_unlocked_input_ctx_t *ctx,\n                            anjay_oid_t *out_oid,\n                            anjay_iid_t *out_iid) {\n    senml_in_t *in = (senml_in_t *) ctx;\n    if (in->entry->type != ANJAY_JSON_LIKE_VALUE_TEXT_STRING) {\n        return ANJAY_ERR_BAD_REQUEST;\n    }\n    char objlnk[MAX_OBJLNK_STRING_SIZE];\n    if (_anjay_get_string_unlocked(ctx, objlnk, sizeof(objlnk))) {\n        return ANJAY_ERR_BAD_REQUEST;\n    }\n\n    if (_anjay_io_parse_objlnk(objlnk, out_oid, out_iid)) {\n        return ANJAY_ERR_BAD_REQUEST;\n    }\n    return 0;\n}\n\n#    ifdef ANJAY_WITH_LWM2M12\nstatic int senml_get_null(anjay_unlocked_input_ctx_t *ctx) {\n    senml_in_t *in = (senml_in_t *) ctx;\n\n    if (!can_return_value(in)) {\n        return -1;\n    }\n    if (in->entry->type != ANJAY_JSON_LIKE_VALUE_NULL) {\n        return ANJAY_ERR_BAD_REQUEST;\n    }\n    in->value_read = true;\n    return 0;\n}\n#    endif // ANJAY_WITH_LWM2M12\n\nstatic int parse_id(uint16_t *out_id, const char **id_begin) {\n    errno = 0;\n    char *endptr = NULL;\n    long value = strtol(*id_begin, &endptr, 10);\n    // clang-format off\n    if (errno == ERANGE\n            || endptr == *id_begin\n            || value < 0\n            || value >= ANJAY_ID_INVALID) {\n        return -1;\n    }\n    // clang-format on\n    *id_begin = endptr;\n    *out_id = (uint16_t) value;\n    return 0;\n}\n\nstatic int parse_absolute_path(anjay_uri_path_t *out_path, const char *input) {\n    if (!*input || *input != '/') {\n        return -1;\n    }\n    *out_path = MAKE_ROOT_PATH();\n\n    if (!strcmp(input, \"/\")) {\n        return 0;\n    }\n\n    const char *ch;\n#    ifdef ANJAY_WITH_LWM2M_GATEWAY\n    bool is_prefix = false;\n    size_t prefix_len = 0;\n    for (ch = &input[1]; *ch != '/' && *ch; ch++, prefix_len++) {\n        if (!isdigit(*ch)) {\n            is_prefix = true;\n        }\n    }\n    if (is_prefix) {\n        if (prefix_len >= ANJAY_GATEWAY_MAX_PREFIX_LEN) {\n            return -1;\n        }\n        memcpy(out_path->prefix, &input[1], prefix_len);\n        assert(out_path->prefix[prefix_len] == '\\0');\n        input = ch;\n    }\n#    endif // ANJAY_WITH_LWM2M_GATEWAY\n\n    size_t curr_len = 0;\n    for (ch = input; *ch;) {\n        if (*ch++ != '/') {\n            return -1;\n        }\n        if (curr_len >= AVS_ARRAY_SIZE(out_path->ids)) {\n            LOG(DEBUG, _(\"absolute path is too long\"));\n            return -1;\n        }\n        if (parse_id(&out_path->ids[curr_len], &ch)) {\n            return -1;\n        }\n        curr_len++;\n    }\n    return 0;\n}\n\nstatic bool uri_path_outside_base(const anjay_uri_path_t *path,\n                                  const anjay_uri_path_t *base) {\n#    ifdef ANJAY_WITH_LWM2M_GATEWAY\n    bool base_set = _anjay_uri_path_length(base) > 0\n                    || _anjay_uri_path_has_prefix(base);\n    if (base_set && !_anjay_uri_path_prefix_equal(path, base)) {\n        return true;\n    }\n#    endif // ANJAY_WITH_LWM2M_GATEWAY\n    return _anjay_uri_path_outside_base(path, base);\n}\n\nstatic int parse_next_absolute_path(senml_in_t *in) {\n    char full_path[MAX_PATH_STRING_SIZE];\n    if (avs_simple_snprintf(full_path,\n                            sizeof(full_path),\n                            \"%s%s\",\n                            in->basename,\n                            in->entry->path)\n            < 0) {\n        LOG(DEBUG, _(\"basename + path is longer than a maximum path length\"));\n        return ANJAY_ERR_BAD_REQUEST;\n    }\n    if (parse_absolute_path(&in->path, full_path)) {\n        return ANJAY_ERR_BAD_REQUEST;\n    }\n    if (uri_path_outside_base(&in->path, &in->base)) {\n        LOG(LAZY_DEBUG,\n            _(\"parsed path \") \"%s\" _(\" would be outside of uri-path \") \"%s\",\n            ANJAY_DEBUG_MAKE_PATH(&in->path), ANJAY_DEBUG_MAKE_PATH(&in->base));\n        return ANJAY_ERR_BAD_REQUEST;\n    }\n    if (!in->composite_read && !_anjay_uri_path_has(&in->path, ANJAY_ID_RID)) {\n        LOG(LAZY_DEBUG,\n            _(\"path \") \"%s\" _(\" inappropriate for this context, Resource or \"\n                              \"Resource Instance path expected\"),\n            ANJAY_DEBUG_MAKE_PATH(&in->path));\n        return ANJAY_ERR_BAD_REQUEST;\n    }\n\n    return 0;\n}\n\nstatic int parse_senml_name(senml_in_t *in, bool *has_name) {\n    if (*has_name) {\n        LOG(DEBUG, _(\"duplicated SenML Name in entry\"));\n        return -1;\n    }\n    *has_name = true;\n\n    char in_path[MAX_PATH_STRING_SIZE];\n    if (get_short_string(in, in_path, sizeof(in_path))) {\n        return -1;\n    }\n    const size_t len = strlen(in->entry->path);\n    if (avs_simple_snprintf(in->entry->path + len,\n                            sizeof(in->entry->path) - len,\n                            \"%s\",\n                            in_path)\n            < 0) {\n        return -1;\n    }\n    return 0;\n}\n\nstatic int read_all_bytes(anjay_json_like_decoder_t *ctx,\n                          void **out_data,\n                          size_t *out_size) {\n    avs_stream_t *membuf = avs_stream_membuf_create();\n    if (!membuf) {\n        LOG(DEBUG, _(\"could not allocate membuf for value cache\"));\n        return -1;\n    }\n    int result = _anjay_json_like_decoder_bytes(ctx, membuf);\n    if (!result\n            && avs_is_err(avs_stream_membuf_take_ownership(membuf, out_data,\n                                                           out_size))) {\n        result = -1;\n    }\n    avs_stream_cleanup(&membuf);\n    return result;\n}\n\nstatic int\nparse_senml_value(senml_in_t *in, bool *has_value, senml_label_t label) {\n    if (*has_value) {\n        LOG(DEBUG, _(\"duplicated SenML value type in entry\"));\n        return -1;\n    }\n    *has_value = true;\n\n    if (_anjay_json_like_decoder_current_value_type(in->ctx,\n                                                    &in->entry->type)) {\n        return -1;\n    }\n    if (label == SENML_LABEL_VALUE_OPAQUE) {\n        return in->deserialization_vtable->parse_opaque_value(in);\n    }\n    switch (in->entry->type) {\n#    ifdef ANJAY_WITH_LWM2M12\n    case ANJAY_JSON_LIKE_VALUE_NULL:\n        if (label != SENML_LABEL_VALUE) {\n            return ANJAY_ERR_BAD_REQUEST;\n        }\n        return _anjay_json_like_decoder_null(in->ctx);\n#    endif // ANJAY_WITH_LWM2M12\n    case ANJAY_JSON_LIKE_VALUE_BYTE_STRING:\n        return ANJAY_ERR_BAD_REQUEST;\n    case ANJAY_JSON_LIKE_VALUE_TEXT_STRING:\n        if (label != SENML_LABEL_VALUE_STRING\n                && label != SENML_EXT_LABEL_OBJLNK) {\n            return ANJAY_ERR_BAD_REQUEST;\n        }\n        return read_all_bytes(in->ctx, &in->entry->value.bytes.data,\n                              &in->entry->value.bytes.size);\n    case ANJAY_JSON_LIKE_VALUE_BOOL:\n        if (label != SENML_LABEL_VALUE_BOOL) {\n            return ANJAY_ERR_BAD_REQUEST;\n        }\n        return _anjay_json_like_decoder_bool(in->ctx,\n                                             &in->entry->value.boolean);\n    default:\n        if (label != SENML_LABEL_VALUE) {\n            return ANJAY_ERR_BAD_REQUEST;\n        }\n        return _anjay_json_like_decoder_number(in->ctx,\n                                               &in->entry->value.number);\n    }\n}\n\nstatic int parse_senml_basename(senml_in_t *in, bool *has_basename) {\n    if (*has_basename) {\n        LOG(DEBUG, _(\"duplicated SenML Base Name in entry\"));\n        return -1;\n    }\n    *has_basename = true;\n    if (get_short_string(in, in->basename, sizeof(in->basename))) {\n        return ANJAY_ERR_BAD_REQUEST;\n    }\n    return 0;\n}\n\nstatic int parse_next_entry(senml_in_t *in) {\n    if (_anjay_json_like_decoder_state(in->ctx)\n            == ANJAY_JSON_LIKE_DECODER_STATE_FINISHED) {\n        return ANJAY_GET_PATH_END;\n    }\n\n    cached_entry_reset(in->entry);\n\n    senml_label_t senml_label;\n    size_t outer_level = _anjay_json_like_decoder_nesting_level(in->ctx);\n    if (_anjay_json_like_decoder_enter_map(in->ctx)) {\n        return ANJAY_ERR_BAD_REQUEST;\n    }\n\n    bool has_name = false;\n    bool has_value = false;\n    bool has_basename = false;\n    while (_anjay_json_like_decoder_nesting_level(in->ctx) >= outer_level + 1) {\n        if (in->deserialization_vtable->get_senml_label(in, &senml_label)) {\n            return ANJAY_ERR_BAD_REQUEST;\n        }\n        int result = 0;\n        switch (senml_label) {\n        case SENML_LABEL_NAME:\n            result = parse_senml_name(in, &has_name);\n            break;\n        case SENML_LABEL_VALUE:\n        case SENML_LABEL_VALUE_BOOL:\n        case SENML_LABEL_VALUE_OPAQUE:\n        case SENML_LABEL_VALUE_STRING:\n        case SENML_EXT_LABEL_OBJLNK:\n            if (in->composite_read) {\n                LOG(DEBUG, _(\"unexpected value in SenML payload\"));\n                result = ANJAY_ERR_BAD_REQUEST;\n            } else {\n                result = parse_senml_value(in, &has_value, senml_label);\n            }\n            break;\n        case SENML_LABEL_BASE_NAME:\n            result = parse_senml_basename(in, &has_basename);\n            break;\n        default:\n            LOG(DEBUG,\n                _(\"unsupported entry SenML Label \") \"%d\" _(\" - ignoring\"),\n                (int) senml_label);\n            break;\n        }\n        if (result) {\n            return ANJAY_ERR_BAD_REQUEST;\n        }\n    }\n    if (_anjay_json_like_decoder_state(in->ctx)\n            == ANJAY_JSON_LIKE_DECODER_STATE_ERROR) {\n        return ANJAY_ERR_BAD_REQUEST;\n    }\n    return parse_next_absolute_path(in);\n}\n\nstatic int senml_get_path(anjay_unlocked_input_ctx_t *ctx,\n                          anjay_uri_path_t *out_path,\n                          bool *out_is_array) {\n    senml_in_t *in = (senml_in_t *) ctx;\n    if (in->path.ids[0] == ANJAY_ID_INVALID) {\n        int retval = parse_next_entry(in);\n        if (retval) {\n            return retval;\n        }\n    }\n    *out_path = in->path;\n    /**\n     * This is never true, because there is no way we are able to figure out\n     * that a path /OID/IID/RID is path to a Multiple Resource - it is simply\n     * non-representable in SenML.\n     */\n    *out_is_array = false;\n    return 0;\n}\n\nstatic int senml_next_entry(anjay_unlocked_input_ctx_t *ctx) {\n    senml_in_t *in = (senml_in_t *) ctx;\n    if (in->path.ids[0] == ANJAY_ID_INVALID) {\n        return 0;\n    }\n    in->path = MAKE_ROOT_PATH();\n    cached_entry_reset(in->entry);\n    in->value_read = false;\n    return 0;\n}\n\nstatic int senml_close(anjay_unlocked_input_ctx_t *ctx) {\n    senml_in_t *in = (senml_in_t *) ctx;\n\n    int result = 0;\n    if (_anjay_json_like_decoder_state(in->ctx)\n            != ANJAY_JSON_LIKE_DECODER_STATE_FINISHED) {\n        LOG(WARNING, _(\"SenML payload contains extraneous data\"));\n        result = ANJAY_ERR_BAD_REQUEST;\n    }\n\n    cached_entry_reset(in->entry);\n    avs_free(in->entry);\n    _anjay_json_like_decoder_delete(&in->ctx);\n    return result;\n}\n\nstatic const anjay_input_ctx_vtable_t SENML_IN_VTABLE = {\n    .some_bytes = senml_get_some_bytes,\n    .string = senml_get_string,\n    .integer = senml_get_integer,\n    .uint = senml_get_uint,\n    .floating = senml_get_double,\n    .boolean = senml_get_bool,\n    .objlnk = senml_get_objlnk,\n#    ifdef ANJAY_WITH_LWM2M12\n    .null = senml_get_null,\n#    endif // ANJAY_WITH_LWM2M12\n    .get_path = senml_get_path,\n    .next_entry = senml_next_entry,\n    .close = senml_close\n};\n\nstatic int\ninput_senml_create(anjay_unlocked_input_ctx_t **out,\n                   anjay_json_like_decoder_t *ctx,\n                   const anjay_uri_path_t *request_uri,\n                   const senml_deserialization_vtable_t *deserialization_vtable,\n                   bool composite_read) {\n    int retval = -1;\n    senml_in_t *in = NULL;\n    assert(_anjay_json_like_decoder_nesting_level(ctx) == 0);\n    if (_anjay_json_like_decoder_enter_array(ctx)) {\n        retval = ANJAY_ERR_BAD_REQUEST;\n        goto error;\n    }\n    in = (senml_in_t *) avs_calloc(1, sizeof(senml_in_t));\n    if (!in) {\n        goto error;\n    }\n    in->entry =\n            (senml_cached_entry_t *) avs_calloc(1,\n                                                sizeof(senml_cached_entry_t));\n    if (!in->entry) {\n        goto error;\n    }\n    in->input_ctx_vtable = &SENML_IN_VTABLE;\n    in->deserialization_vtable = deserialization_vtable;\n    in->ctx = ctx;\n    in->base = *request_uri;\n    in->path = MAKE_ROOT_PATH();\n    in->composite_read = composite_read;\n    *out = (anjay_unlocked_input_ctx_t *) in;\n    return 0;\n\nerror:\n    _anjay_json_like_decoder_delete(&ctx);\n    avs_free(in);\n    return retval;\n}\n\n#    ifdef ANJAY_WITH_CBOR\nstatic int get_senml_cbor_label(senml_in_t *in, senml_label_t *out_label) {\n    anjay_json_like_value_type_t type;\n    if (_anjay_json_like_decoder_current_value_type(in->ctx, &type)) {\n        return -1;\n    }\n    /**\n     * SenML numerical labels do not contain anything related to LwM2M objlnk\n     * datatype. Additionally:\n     *\n     * > 6.  CBOR Representation (application/senml+cbor)\n     * > [...]\n     * >\n     * > For compactness, the CBOR representation uses integers for the\n     * > labels, as defined in Table 4.  This table is conclusive, i.e.,\n     * > there is no intention to define any additional integer map keys;\n     * > any extensions will use **string** map keys.\n     */\n    if (type == ANJAY_JSON_LIKE_VALUE_TEXT_STRING) {\n        char label[sizeof(SENML_EXT_OBJLNK_REPR)];\n        if (get_short_string(in, label, sizeof(label))\n                || strcmp(label, SENML_EXT_OBJLNK_REPR)) {\n            return -1;\n        }\n        *out_label = SENML_EXT_LABEL_OBJLNK;\n        return 0;\n    }\n    int64_t numeric_label;\n    if (get_i64(in, &numeric_label)) {\n        return -1;\n    }\n    switch (numeric_label) {\n    case SENML_LABEL_BASE_TIME:\n    case SENML_LABEL_BASE_NAME:\n    case SENML_LABEL_NAME:\n    case SENML_LABEL_VALUE:\n    case SENML_LABEL_VALUE_STRING:\n    case SENML_LABEL_VALUE_BOOL:\n    case SENML_LABEL_TIME:\n    case SENML_LABEL_VALUE_OPAQUE:\n        *out_label = (senml_label_t) numeric_label;\n        return 0;\n    default:\n        return -1;\n    }\n}\n\nstatic int parse_cbor_opaque_value(senml_in_t *in) {\n    if (in->entry->type != ANJAY_JSON_LIKE_VALUE_BYTE_STRING) {\n        return ANJAY_ERR_BAD_REQUEST;\n    }\n    return read_all_bytes(in->ctx, &in->entry->value.bytes.data,\n                          &in->entry->value.bytes.size);\n}\n\nstatic const senml_deserialization_vtable_t\n        SENML_CBOR_DESERIALIZATION_VTABLE = {\n            .get_senml_label = get_senml_cbor_label,\n            .parse_opaque_value = parse_cbor_opaque_value\n        };\n\nint _anjay_input_senml_cbor_create(anjay_unlocked_input_ctx_t **out,\n                                   avs_stream_t *stream_ptr,\n                                   const anjay_uri_path_t *request_uri) {\n    anjay_json_like_decoder_t *cbor_ctx =\n            _anjay_cbor_decoder_new(stream_ptr, MAX_SENML_CBOR_NEST_STACK_SIZE);\n    if (!cbor_ctx) {\n        return -1;\n    }\n    return input_senml_create(out, cbor_ctx, request_uri,\n                              &SENML_CBOR_DESERIALIZATION_VTABLE, false);\n}\n\nint _anjay_input_senml_cbor_composite_read_create(\n        anjay_unlocked_input_ctx_t **out,\n        avs_stream_t *stream_ptr,\n        const anjay_uri_path_t *request_uri) {\n    anjay_json_like_decoder_t *cbor_ctx =\n            _anjay_cbor_decoder_new(stream_ptr, MAX_SENML_CBOR_NEST_STACK_SIZE);\n    if (!cbor_ctx) {\n        return -1;\n    }\n    return input_senml_create(out, cbor_ctx, request_uri,\n                              &SENML_CBOR_DESERIALIZATION_VTABLE, true);\n}\n#    endif // ANJAY_WITH_CBOR\n\n#    ifdef ANJAY_WITH_SENML_JSON\nstatic int get_senml_json_label(senml_in_t *in, senml_label_t *out_label) {\n    anjay_json_like_value_type_t type;\n    if (_anjay_json_like_decoder_current_value_type(in->ctx, &type)) {\n        return -1;\n    }\n    char label[sizeof(SENML_EXT_OBJLNK_REPR)];\n    if (type != ANJAY_JSON_LIKE_VALUE_TEXT_STRING\n            || get_short_string(in, label, sizeof(label))) {\n        return -1;\n    }\n    if (strcmp(label, SENML_EXT_OBJLNK_REPR) == 0) {\n        *out_label = SENML_EXT_LABEL_OBJLNK;\n    } else if (strcmp(label, \"bt\") == 0) {\n        *out_label = SENML_LABEL_BASE_TIME;\n    } else if (strcmp(label, \"bn\") == 0) {\n        *out_label = SENML_LABEL_BASE_NAME;\n    } else if (strcmp(label, \"n\") == 0) {\n        *out_label = SENML_LABEL_NAME;\n    } else if (strcmp(label, \"v\") == 0) {\n        *out_label = SENML_LABEL_VALUE;\n    } else if (strcmp(label, \"vs\") == 0) {\n        *out_label = SENML_LABEL_VALUE_STRING;\n    } else if (strcmp(label, \"vb\") == 0) {\n        *out_label = SENML_LABEL_VALUE_BOOL;\n    } else if (strcmp(label, \"t\") == 0) {\n        *out_label = SENML_LABEL_TIME;\n    } else if (strcmp(label, \"vd\") == 0) {\n        *out_label = SENML_LABEL_VALUE_OPAQUE;\n    } else {\n        return -1;\n    }\n    return 0;\n}\n\ntypedef struct {\n    const avs_stream_v_table_t *const vtable;\n    avs_stream_t *backend;\n    char buffer[5]; // 4 bytes + null terminator\n    size_t buffer_pos;\n} base64_stream_wrapper_t;\n\nstatic avs_error_t base64_flush(base64_stream_wrapper_t *stream) {\n    static const avs_base64_config_t BASE64_CONFIG = {\n        .alphabet = AVS_BASE64_URL_SAFE_CHARS,\n        .padding_char = '\\0',\n        .allow_whitespace = false,\n        .require_padding = false\n    };\n    if (stream->buffer_pos == 0) {\n        return AVS_OK;\n    }\n    assert(stream->buffer_pos < sizeof(stream->buffer));\n    stream->buffer[stream->buffer_pos] = '\\0';\n    size_t decoded;\n    if (avs_base64_decode_custom(&decoded, (uint8_t *) stream->buffer,\n                                 sizeof(stream->buffer) - 1, stream->buffer,\n                                 BASE64_CONFIG)) {\n        return avs_errno(AVS_EBADMSG);\n    }\n    stream->buffer_pos = 0;\n    return avs_stream_write(stream->backend, stream->buffer, decoded);\n}\n\nstatic avs_error_t base64_write_some(avs_stream_t *stream_,\n                                     const void *buffer_,\n                                     size_t *inout_data_length) {\n    base64_stream_wrapper_t *stream = (base64_stream_wrapper_t *) stream_;\n    const char *buffer = (const char *) buffer_;\n    size_t bytes_remaining = *inout_data_length;\n    while (bytes_remaining) {\n        size_t bytes_to_process =\n                AVS_MIN(bytes_remaining,\n                        sizeof(stream->buffer) - 1 - stream->buffer_pos);\n        bytes_remaining -= bytes_to_process;\n        memcpy(stream->buffer + stream->buffer_pos, buffer, bytes_to_process);\n        buffer += bytes_to_process;\n        stream->buffer_pos += bytes_to_process;\n        if (stream->buffer_pos == sizeof(stream->buffer) - 1) {\n            avs_error_t err = base64_flush(stream);\n            if (avs_is_err(err)) {\n                return err;\n            }\n        }\n    }\n    return AVS_OK;\n}\n\nstatic const avs_stream_v_table_t BASE64_STREAM_WRAPPER_VTABLE = {\n    .write_some = base64_write_some\n};\n\nstatic int parse_json_opaque_value(senml_in_t *in) {\n    if (in->entry->type != ANJAY_JSON_LIKE_VALUE_TEXT_STRING) {\n        return ANJAY_ERR_BAD_REQUEST;\n    }\n    base64_stream_wrapper_t stream = {\n        .vtable = &BASE64_STREAM_WRAPPER_VTABLE,\n        .backend = avs_stream_membuf_create()\n    };\n    if (!stream.backend) {\n        LOG(DEBUG, _(\"could not allocate membuf for value cache\"));\n        return -1;\n    }\n    int result = 0;\n    if (_anjay_json_like_decoder_bytes(in->ctx, (avs_stream_t *) &stream)\n            || avs_is_err(base64_flush(&stream))\n            || avs_is_err(avs_stream_membuf_take_ownership(\n                       stream.backend, &in->entry->value.bytes.data,\n                       &in->entry->value.bytes.size))) {\n        result = -1;\n    }\n    avs_stream_cleanup(&stream.backend);\n    if (!result) {\n        in->entry->type = ANJAY_JSON_LIKE_VALUE_BYTE_STRING;\n    }\n    return result;\n}\n\nstatic const senml_deserialization_vtable_t\n        SENML_JSON_DESERIALIZATION_VTABLE = {\n            .get_senml_label = get_senml_json_label,\n            .parse_opaque_value = parse_json_opaque_value\n        };\n\nint _anjay_input_json_create(anjay_unlocked_input_ctx_t **out,\n                             avs_stream_t *stream_ptr,\n                             const anjay_uri_path_t *request_uri) {\n    anjay_json_like_decoder_t *json_ctx = _anjay_json_decoder_new(stream_ptr);\n    if (!json_ctx) {\n        return -1;\n    }\n    return input_senml_create(out, json_ctx, request_uri,\n                              &SENML_JSON_DESERIALIZATION_VTABLE, false);\n}\n\nint _anjay_input_json_composite_read_create(\n        anjay_unlocked_input_ctx_t **out,\n        avs_stream_t *stream_ptr,\n        const anjay_uri_path_t *request_uri) {\n    anjay_json_like_decoder_t *json_ctx = _anjay_json_decoder_new(stream_ptr);\n    if (!json_ctx) {\n        return -1;\n    }\n    return input_senml_create(out, json_ctx, request_uri,\n                              &SENML_JSON_DESERIALIZATION_VTABLE, true);\n}\n#    endif // ANJAY_WITH_SENML_JSON\n\n#    ifdef ANJAY_WITH_CBOR\n#        ifdef ANJAY_TEST\n#            include \"tests/core/io/cbor_in.c\"\n#        endif // ANJAY_TEST\n#    endif     // ANJAY_WITH_CBOR\n\n#    ifdef ANJAY_WITH_SENML_JSON\n#        ifdef ANJAY_TEST\n#            include \"tests/core/io/json_in.c\"\n#        endif // ANJAY_TEST\n#    endif     // ANJAY_WITH_SENML_JSON\n\n#endif // defined(ANJAY_WITH_CBOR) || defined(ANJAY_WITH_SENML_JSON)\n"
  },
  {
    "path": "src/core/io/anjay_senml_like_encoder.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#if defined(ANJAY_WITH_LWM2M_JSON) || defined(ANJAY_WITH_SENML_JSON) \\\n        || defined(ANJAY_WITH_CBOR)\n\n#    include \"anjay_senml_like_encoder.h\"\n#    include \"anjay_senml_like_encoder_vtable.h\"\n\nVISIBILITY_SOURCE_BEGIN\n\nint _anjay_senml_like_encode_int(anjay_senml_like_encoder_t *ctx,\n                                 int64_t data) {\n    assert(ctx && ctx->vtable);\n    assert(ctx->vtable->senml_like_encode_int);\n    return ctx->vtable->senml_like_encode_int(ctx, data);\n}\n\n#    ifdef ANJAY_WITH_LWM2M11\nint _anjay_senml_like_encode_uint(anjay_senml_like_encoder_t *ctx,\n                                  uint64_t data) {\n    assert(ctx && ctx->vtable);\n    assert(ctx->vtable->senml_like_encode_uint);\n    return ctx->vtable->senml_like_encode_uint(ctx, data);\n}\n#    endif // ANJAY_WITH_LWM2M11\n\nint _anjay_senml_like_encode_double(anjay_senml_like_encoder_t *ctx,\n                                    double data) {\n    assert(ctx && ctx->vtable);\n    assert(ctx->vtable->senml_like_encode_double);\n    return ctx->vtable->senml_like_encode_double(ctx, data);\n}\n\nint _anjay_senml_like_encode_bool(anjay_senml_like_encoder_t *ctx, bool data) {\n    assert(ctx && ctx->vtable);\n    assert(ctx->vtable->senml_like_encode_bool);\n    return ctx->vtable->senml_like_encode_bool(ctx, data);\n}\n\nint _anjay_senml_like_encode_string(anjay_senml_like_encoder_t *ctx,\n                                    const char *data) {\n    assert(ctx && ctx->vtable);\n    assert(ctx->vtable->senml_like_encode_string);\n    return ctx->vtable->senml_like_encode_string(ctx, data);\n}\n\nint _anjay_senml_like_encode_objlnk(anjay_senml_like_encoder_t *ctx,\n                                    const char *data) {\n    assert(ctx && ctx->vtable);\n    assert(ctx->vtable->senml_like_encode_objlnk);\n    return ctx->vtable->senml_like_encode_objlnk(ctx, data);\n}\n\nint _anjay_senml_like_element_begin(anjay_senml_like_encoder_t *ctx,\n                                    const char *basename,\n                                    const char *name,\n                                    double time_s) {\n    assert(ctx && ctx->vtable);\n    assert(ctx->vtable->senml_like_element_begin);\n    return ctx->vtable->senml_like_element_begin(ctx, basename, name, time_s);\n}\n\nint _anjay_senml_like_element_end(anjay_senml_like_encoder_t *ctx) {\n    assert(ctx && ctx->vtable);\n    assert(ctx->vtable->senml_like_element_end);\n    return ctx->vtable->senml_like_element_end(ctx);\n}\n\nint _anjay_senml_like_bytes_begin(anjay_senml_like_encoder_t *ctx,\n                                  size_t size) {\n    assert(ctx && ctx->vtable);\n    assert(ctx->vtable->senml_like_bytes_begin);\n    return ctx->vtable->senml_like_bytes_begin(ctx, size);\n}\n\nint _anjay_senml_like_bytes_append(anjay_senml_like_encoder_t *ctx,\n                                   const void *data,\n                                   size_t size) {\n    assert(ctx && ctx->vtable);\n    assert(ctx->vtable->senml_like_bytes_append);\n    return ctx->vtable->senml_like_bytes_append(ctx, data, size);\n}\n\nint _anjay_senml_like_bytes_end(anjay_senml_like_encoder_t *ctx) {\n    assert(ctx && ctx->vtable);\n    assert(ctx->vtable->senml_like_bytes_end);\n    return ctx->vtable->senml_like_bytes_end(ctx);\n}\n\nint _anjay_senml_like_encoder_cleanup(anjay_senml_like_encoder_t **ctx) {\n    assert(ctx && *ctx && (*ctx)->vtable);\n    assert((*ctx)->vtable->senml_like_encoder_cleanup);\n    return (*ctx)->vtable->senml_like_encoder_cleanup(ctx);\n}\n\n#endif // defined(ANJAY_WITH_LWM2M_JSON) || defined(ANJAY_WITH_SENML_JSON) ||\n       // defined(ANJAY_WITH_CBOR)\n"
  },
  {
    "path": "src/core/io/anjay_senml_like_encoder.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ANJAY_IO_SENML_LIKE_ENCODER_H\n#define ANJAY_IO_SENML_LIKE_ENCODER_H\n\n#include <avsystem/commons/avs_stream.h>\n#include <avsystem/commons/avs_time.h>\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\ntypedef struct anjay_senml_like_encoder_struct anjay_senml_like_encoder_t;\n\n/**\n * All <c>_anjay_senml_like_encode_*</c> functions below encode a pair of\n * automatically deduced label and value to stream and require that element was\n * started before.\n *\n * Return 0 in case of success and negative value otherwise.\n */\n\nint _anjay_senml_like_encode_int(anjay_senml_like_encoder_t *ctx, int64_t data);\n\n#ifdef ANJAY_WITH_LWM2M11\nint _anjay_senml_like_encode_uint(anjay_senml_like_encoder_t *ctx,\n                                  uint64_t data);\n#endif // ANJAY_WITH_LWM2M11\n\nint _anjay_senml_like_encode_double(anjay_senml_like_encoder_t *ctx,\n                                    double data);\n\nint _anjay_senml_like_encode_bool(anjay_senml_like_encoder_t *ctx, bool data);\n\nint _anjay_senml_like_encode_string(anjay_senml_like_encoder_t *ctx,\n                                    const char *data);\n\nint _anjay_senml_like_encode_objlnk(anjay_senml_like_encoder_t *ctx,\n                                    const char *data);\n\n/**\n * Starts a map containing optional basename and/or name.\n * Only one value can be encoded to this map.\n *\n * @param ctx      Pointer to SenML-like encoder.\n * @param basename Zero-terminated string with basename; if NULL, basename is\n *                 not encoded.\n * @param name     Zero-terminated string with name; if NULL, name is not\n *                 encoded.\n * @param time_s   Time value in seconds to be encoded. NAN if it has to be\n *                 ommited.\n * @returns 0 in case of success, negative value otherwise.\n */\nint _anjay_senml_like_element_begin(anjay_senml_like_encoder_t *ctx,\n                                    const char *basename,\n                                    const char *name,\n                                    double time_s);\n\n/**\n * @param ctx Pointer to SenML-like encoder.\n * @returns 0 in case of success, negative value otherwise.\n */\nint _anjay_senml_like_element_end(anjay_senml_like_encoder_t *ctx);\n\n/**\n * @param ctx  Pointer to SenML-like encoder.\n * @param size Size of data in bytes.\n * @returns 0 in case of success, negative value otherwise.\n */\nint _anjay_senml_like_bytes_begin(anjay_senml_like_encoder_t *ctx, size_t size);\n\n/**\n * Appends bytes to started bytes value.\n *\n * @param ctx  Pointer to SenML-like encoder.\n * @param data Pointer to data.\n * @param size Size of data.\n * @returns 0 in case of success, negative value otherwise.\n */\nint _anjay_senml_like_bytes_append(anjay_senml_like_encoder_t *ctx,\n                                   const void *data,\n                                   size_t size);\n\n/**\n * @param ctx Pointer to SenML-like encoder.\n * @returns 0 in case of success, negative value otherwise.\n */\nint _anjay_senml_like_bytes_end(anjay_senml_like_encoder_t *ctx);\n\n/**\n * Deletes SenML-like encoder. Performs validation if necessary.\n *\n * @param ctx Pointer to pointer to SenML-like encoder.\n * @returns 0 in case of success, negative value otherwise.\n */\nint _anjay_senml_like_encoder_cleanup(anjay_senml_like_encoder_t **ctx);\n\n#ifdef ANJAY_WITH_CBOR\n/**\n * Creates SenML CBOR encoder (content format 112).\n *\n * @param stream      Stream to encode data to. Encoder doesn't take ownership\n *                    of stream.\n * @param items_count Pointer to a variable that specifies the number of entries\n *                    in the outermost definite array, or NULL if not known.\n *\n * @returns Pointer to encoder in case of success, NULL otherwise.\n *\n * In current implementation, all data are cached and written to stream during\n * call to <c>_anjay_senml_like_encoder_cleanup</c>.\n */\nanjay_senml_like_encoder_t *\n_anjay_senml_cbor_encoder_new(avs_stream_t *stream, const size_t *items_count);\n#endif // ANJAY_WITH_CBOR\n\n#ifdef ANJAY_WITH_SENML_JSON\n/**\n * Creates SenML JSON encoder (content format 110).\n * Writes <c>[</c> to stream.\n *\n * @param stream Stream to encode data to. Encoder doesn't take ownership of\n *               stream.\n * @returns Pointer to encoder in case of success, NULL otherwise.\n */\nanjay_senml_like_encoder_t *_anjay_senml_json_encoder_new(avs_stream_t *stream);\n#endif // ANJAY_WITH_SENML_JSON\n\n/**\n * Creates LwM2M 1.0 JSON encoder (content format 11543).\n * Writes <c>{\"bn\":basename,\"e\":[</c> to stream.\n *\n * @param stream   Stream to encode data to. Encoder doesn't take ownership of\n *                 stream.\n * @param basename Pointer to zero-terminated string with basename.\n * @returns Pointer to encoder in case of success, NULL otherwise.\n */\nanjay_senml_like_encoder_t *_anjay_lwm2m_json_encoder_new(avs_stream_t *stream,\n                                                          const char *basename);\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif // ANJAY_IO_SENML_LIKE_ENCODER_H\n"
  },
  {
    "path": "src/core/io/anjay_senml_like_encoder_vtable.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ANJAY_SENML_LIKE_ENCODER_VTABLE_H\n#define ANJAY_SENML_LIKE_ENCODER_VTABLE_H\n\n#include <anjay/core.h>\n\n#include \"anjay_senml_like_encoder.h\"\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\ntypedef int (*senml_like_encode_uint_t)(anjay_senml_like_encoder_t *, uint64_t);\ntypedef int (*senml_like_encode_int_t)(anjay_senml_like_encoder_t *, int64_t);\ntypedef int (*senml_like_encode_double_t)(anjay_senml_like_encoder_t *, double);\ntypedef int (*senml_like_encode_bool_t)(anjay_senml_like_encoder_t *, bool);\ntypedef int (*senml_like_encode_string_t)(anjay_senml_like_encoder_t *,\n                                          const char *);\ntypedef int (*senml_like_encode_objlnk_t)(anjay_senml_like_encoder_t *,\n                                          const char *);\ntypedef int (*senml_like_element_begin_t)(anjay_senml_like_encoder_t *,\n                                          const char *basename,\n                                          const char *name,\n                                          double time_s);\ntypedef int (*senml_like_element_end_t)(anjay_senml_like_encoder_t *);\ntypedef int (*senml_like_bytes_begin_t)(anjay_senml_like_encoder_t *, size_t);\ntypedef int (*senml_like_bytes_append_t)(anjay_senml_like_encoder_t *,\n                                         const void *,\n                                         size_t);\ntypedef int (*senml_like_bytes_end_t)(anjay_senml_like_encoder_t *);\ntypedef int (*senml_like_encoder_cleanup_t)(anjay_senml_like_encoder_t **);\n\ntypedef struct {\n    senml_like_encode_uint_t senml_like_encode_uint;\n    senml_like_encode_int_t senml_like_encode_int;\n    senml_like_encode_double_t senml_like_encode_double;\n    senml_like_encode_bool_t senml_like_encode_bool;\n    senml_like_encode_string_t senml_like_encode_string;\n    senml_like_encode_objlnk_t senml_like_encode_objlnk;\n    senml_like_element_begin_t senml_like_element_begin;\n    senml_like_element_end_t senml_like_element_end;\n    senml_like_bytes_begin_t senml_like_bytes_begin;\n    senml_like_bytes_append_t senml_like_bytes_append;\n    senml_like_bytes_end_t senml_like_bytes_end;\n    senml_like_encoder_cleanup_t senml_like_encoder_cleanup;\n} anjay_senml_like_encoder_vtable_t;\n\nstruct anjay_senml_like_encoder_struct {\n    const anjay_senml_like_encoder_vtable_t *vtable;\n};\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif // ANJAY_SENML_LIKE_ENCODER_VTABLE_H\n"
  },
  {
    "path": "src/core/io/anjay_senml_like_out.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#if defined(ANJAY_WITH_LWM2M_JSON) || defined(ANJAY_WITH_SENML_JSON) \\\n        || defined(ANJAY_WITH_CBOR)\n\n#    include <anjay/core.h>\n\n#    include <assert.h>\n#    include <inttypes.h>\n#    include <string.h>\n\n#    include <avsystem/commons/avs_stream.h>\n#    include <avsystem/commons/avs_utils.h>\n\n#    include \"../coap/anjay_content_format.h\"\n\n#    include \"anjay_common.h\"\n#    include \"anjay_senml_like_encoder.h\"\n#    include \"anjay_vtable.h\"\n\n#    ifdef ANJAY_WITH_LWM2M_GATEWAY\n#        include <anjay/lwm2m_gateway.h>\n#    endif // ANJAY_WITH_LWM2M_GATEWAY\n\n#    define senml_log(level, ...) _anjay_log(senml_like_out, level, __VA_ARGS__)\n\nVISIBILITY_SOURCE_BEGIN\n\ntypedef struct {\n    const anjay_ret_bytes_ctx_vtable_t *vtable;\n} senml_bytes_t;\n\ntypedef struct senml_out_struct {\n    anjay_unlocked_output_ctx_t base;\n    anjay_senml_like_encoder_t *encoder;\n    anjay_uri_path_t path;\n    anjay_uri_path_t base_path;\n    senml_bytes_t bytes;\n    bool returning_bytes;\n    bool basename_written;\n    double timestamp;\n} senml_out_t;\n\nstatic int path_to_string(const anjay_uri_path_t *path,\n                          size_t start_index,\n                          size_t end_index,\n                          char *dest,\n                          size_t size) {\n#    ifdef ANJAY_WITH_LWM2M_GATEWAY\n    if (_anjay_uri_path_has_prefix(path)) {\n        if (start_index == 0) {\n            int written_chars =\n                    avs_simple_snprintf(dest, size, \"/%s\", path->prefix);\n            if (written_chars < 0) {\n                return -1;\n            }\n            dest += written_chars;\n            size -= (size_t) written_chars;\n        } else {\n            start_index--;\n        }\n        end_index--;\n    }\n#    endif // ANJAY_WITH_LWM2M_GATEWAY\n    for (; start_index < end_index; start_index++) {\n        int written_chars = avs_simple_snprintf(dest, size, \"/%\" PRIu16,\n                                                path->ids[start_index]);\n        if (written_chars < 0) {\n            return -1;\n        }\n        dest += written_chars;\n        size -= (size_t) written_chars;\n    }\n    return 0;\n}\n\nstatic char *maybe_get_basename(senml_out_t *ctx, char *buf, size_t size) {\n    size_t base_path_length = _anjay_uri_path_length(&ctx->base_path);\n#    ifdef ANJAY_WITH_LWM2M_GATEWAY\n    base_path_length += (size_t) _anjay_uri_path_has_prefix(&ctx->base_path);\n#    endif // ANJAY_WITH_LWM2M_GATEWAY\n    char *retptr = NULL;\n    if (!ctx->basename_written && base_path_length > 0) {\n        *buf = '\\0';\n        int result =\n                path_to_string(&ctx->base_path, 0, base_path_length, buf, size);\n        AVS_ASSERT(result == 0, \"buffer too small\");\n        (void) result;\n        retptr = buf;\n    }\n    return retptr;\n}\n\nstatic char *maybe_get_name(senml_out_t *ctx, char *buf, size_t size) {\n    size_t base_path_length = _anjay_uri_path_length(&ctx->base_path);\n    size_t path_length = _anjay_uri_path_length(&ctx->path);\n#    ifdef ANJAY_WITH_LWM2M_GATEWAY\n    base_path_length += (size_t) _anjay_uri_path_has_prefix(&ctx->base_path);\n    path_length += (size_t) _anjay_uri_path_has_prefix(&ctx->path);\n#    endif // ANJAY_WITH_LWM2M_GATEWAY\n    char *retptr = NULL;\n    if (path_length > base_path_length) {\n        *buf = '\\0';\n        int result = path_to_string(&ctx->path, base_path_length, path_length,\n                                    buf, size);\n        AVS_ASSERT(result == 0, \"buffer too small\");\n        (void) result;\n        retptr = buf;\n    }\n    return retptr;\n}\n\nstatic int finish_ret_bytes(senml_out_t *ctx) {\n    int retval;\n    (void) ((retval = _anjay_senml_like_bytes_end(ctx->encoder))\n            || (retval = _anjay_senml_like_element_end(ctx->encoder)));\n    ctx->returning_bytes = false;\n    return retval;\n}\n\nstatic int element_begin(senml_out_t *ctx) {\n    if (!_anjay_uri_path_has(&ctx->path, ANJAY_ID_RID)) {\n        return -1;\n    }\n\n    if (ctx->returning_bytes) {\n        int result = finish_ret_bytes(ctx);\n        if (result) {\n            return result;\n        }\n    }\n\n    char basename_buf[MAX_PATH_STRING_SIZE];\n    char name_buf[MAX_PATH_STRING_SIZE];\n    char *name = maybe_get_name(ctx, name_buf, sizeof(name_buf));\n    char *basename =\n            maybe_get_basename(ctx, basename_buf, sizeof(basename_buf));\n    ctx->basename_written = true;\n    int result = _anjay_senml_like_element_begin(ctx->encoder, basename, name,\n                                                 ctx->timestamp);\n    ctx->timestamp = NAN;\n    ctx->path = MAKE_ROOT_PATH();\n    return result;\n}\n\nstatic int streamed_bytes_append(anjay_unlocked_ret_bytes_ctx_t *ctx_,\n                                 const void *data,\n                                 size_t length) {\n    senml_out_t *ctx = AVS_CONTAINER_OF(ctx_, senml_out_t, bytes);\n    return _anjay_senml_like_bytes_append(ctx->encoder, data, length);\n}\n\nstatic const anjay_ret_bytes_ctx_vtable_t STREAMED_BYTES_VTABLE = {\n    .append = streamed_bytes_append\n};\n\nstatic int senml_ret_bytes(anjay_unlocked_output_ctx_t *ctx_,\n                           size_t length,\n                           anjay_unlocked_ret_bytes_ctx_t **out_bytes_ctx) {\n    senml_out_t *ctx = (senml_out_t *) ctx_;\n    int retval;\n    if (!(retval = element_begin(ctx))\n            && !(retval =\n                         _anjay_senml_like_bytes_begin(ctx->encoder, length))) {\n        ctx->returning_bytes = true;\n        *out_bytes_ctx = (anjay_unlocked_ret_bytes_ctx_t *) &ctx->bytes;\n    }\n    return retval;\n}\n\nstatic int senml_ret_string(anjay_unlocked_output_ctx_t *ctx_,\n                            const char *value) {\n    senml_out_t *ctx = (senml_out_t *) ctx_;\n    int retval;\n    (void) ((retval = element_begin(ctx))\n            || (retval = _anjay_senml_like_encode_string(ctx->encoder, value))\n            || (retval = _anjay_senml_like_element_end(ctx->encoder)));\n    return retval;\n}\n\nstatic int senml_ret_integer(anjay_unlocked_output_ctx_t *ctx_, int64_t value) {\n    senml_out_t *ctx = (senml_out_t *) ctx_;\n    int retval;\n    (void) ((retval = element_begin(ctx))\n            || (retval = _anjay_senml_like_encode_int(ctx->encoder, value))\n            || (retval = _anjay_senml_like_element_end(ctx->encoder)));\n    return retval;\n}\n\n#    ifdef ANJAY_WITH_LWM2M11\nstatic int senml_ret_uint(anjay_unlocked_output_ctx_t *ctx_, uint64_t value) {\n    senml_out_t *ctx = (senml_out_t *) ctx_;\n    int retval;\n    (void) ((retval = element_begin(ctx))\n            || (retval = _anjay_senml_like_encode_uint(ctx->encoder, value))\n            || (retval = _anjay_senml_like_element_end(ctx->encoder)));\n    return retval;\n}\n#    endif // ANJAY_WITH_LWM2M11\n\nstatic int senml_ret_double(anjay_unlocked_output_ctx_t *ctx_, double value) {\n    senml_out_t *ctx = (senml_out_t *) ctx_;\n    int retval;\n    (void) ((retval = element_begin(ctx))\n            || (retval = _anjay_senml_like_encode_double(ctx->encoder, value))\n            || (retval = _anjay_senml_like_element_end(ctx->encoder)));\n    return retval;\n}\n\nstatic int senml_ret_bool(anjay_unlocked_output_ctx_t *ctx_, bool value) {\n    senml_out_t *ctx = (senml_out_t *) ctx_;\n    int retval;\n    (void) ((retval = element_begin(ctx))\n            || (retval = _anjay_senml_like_encode_bool(ctx->encoder, value))\n            || (retval = _anjay_senml_like_element_end(ctx->encoder)));\n    return retval;\n}\n\nstatic int senml_ret_objlnk(anjay_unlocked_output_ctx_t *ctx_,\n                            anjay_oid_t oid,\n                            anjay_iid_t iid) {\n    senml_out_t *ctx = (senml_out_t *) ctx_;\n    char buf[MAX_OBJLNK_STRING_SIZE];\n    int retval;\n    (void) (((retval = avs_simple_snprintf(buf, sizeof(buf),\n                                           \"%\" PRIu16 \":%\" PRIu16, oid, iid))\n             < 0)\n            || (retval = element_begin(ctx))\n            || (retval = _anjay_senml_like_encode_objlnk(ctx->encoder, buf))\n            || (retval = _anjay_senml_like_element_end(ctx->encoder)));\n    return retval;\n}\n\nstatic int senml_ret_start_aggregate(anjay_unlocked_output_ctx_t *ctx_) {\n    senml_out_t *ctx = (senml_out_t *) ctx_;\n    if (_anjay_uri_path_leaf_is(&ctx->path, ANJAY_ID_IID)\n            || _anjay_uri_path_leaf_is(&ctx->path, ANJAY_ID_RID)) {\n        ctx->path = MAKE_ROOT_PATH();\n        return 0;\n    } else {\n        return -1;\n    }\n}\n\nstatic bool uri_path_outside_base(const anjay_uri_path_t *path,\n                                  const anjay_uri_path_t *base) {\n#    ifdef ANJAY_WITH_LWM2M_GATEWAY\n    if (_anjay_uri_path_has_prefix(base)\n            && !_anjay_uri_path_prefix_equal(path, base)) {\n        return true;\n    }\n#    endif // ANJAY_WITH_LWM2M_GATEWAY\n    return _anjay_uri_path_outside_base(path, base);\n}\n\nstatic int senml_set_path(anjay_unlocked_output_ctx_t *ctx_,\n                          const anjay_uri_path_t *uri) {\n    senml_out_t *ctx = (senml_out_t *) ctx_;\n    AVS_ASSERT(!uri_path_outside_base(uri, &ctx->base_path),\n               \"Attempted to set path outside the context's base path. \"\n               \"This is a bug in resource reading logic.\");\n    if (_anjay_uri_path_length(&ctx->path) > 0\n#    ifdef ANJAY_WITH_LWM2M_GATEWAY\n            || _anjay_uri_path_has_prefix(&ctx->path)\n#    endif // ANJAY_WITH_LWM2M_GATEWAY\n    ) {\n        senml_log(ERROR, _(\"Path already set\"));\n        return -1;\n    }\n    ctx->path = *uri;\n    return 0;\n}\n\nstatic int senml_clear_path(anjay_unlocked_output_ctx_t *ctx_) {\n    senml_out_t *ctx = (senml_out_t *) ctx_;\n    if (_anjay_uri_path_length(&ctx->path) == 0\n#    ifdef ANJAY_WITH_LWM2M_GATEWAY\n            && !_anjay_uri_path_has_prefix(&ctx->path)\n#    endif // ANJAY_WITH_LWM2M_GATEWAY\n    ) {\n        senml_log(ERROR, _(\"Path not set\"));\n        return -1;\n    }\n    ctx->path = MAKE_ROOT_PATH();\n    return 0;\n}\n\nstatic int senml_set_time(anjay_unlocked_output_ctx_t *ctx_, double value) {\n    senml_out_t *ctx = (senml_out_t *) ctx_;\n    ctx->timestamp = value;\n    return 0;\n}\n\nstatic int senml_output_close(anjay_unlocked_output_ctx_t *ctx_) {\n    senml_out_t *ctx = (senml_out_t *) ctx_;\n    int result = 0;\n    if (ctx->returning_bytes) {\n        result = finish_ret_bytes(ctx);\n    }\n    _anjay_update_ret(&result,\n                      _anjay_senml_like_encoder_cleanup(&ctx->encoder));\n\n    if (_anjay_uri_path_length(&ctx->path) > 0\n#    ifdef ANJAY_WITH_LWM2M_GATEWAY\n            || _anjay_uri_path_has_prefix(&ctx->path)\n#    endif // ANJAY_WITH_LWM2M_GATEWAY\n    ) {\n        _anjay_update_ret(&result, ANJAY_OUTCTXERR_ANJAY_RET_NOT_CALLED);\n    }\n    return result;\n}\n\nstatic const anjay_output_ctx_vtable_t SENML_OUT_VTABLE = {\n    .bytes_begin = senml_ret_bytes,\n    .string = senml_ret_string,\n    .integer = senml_ret_integer,\n#    ifdef ANJAY_WITH_LWM2M11\n    .uint = senml_ret_uint,\n#    endif // ANJAY_WITH_LWM2M11\n    .floating = senml_ret_double,\n    .boolean = senml_ret_bool,\n    .objlnk = senml_ret_objlnk,\n    .start_aggregate = senml_ret_start_aggregate,\n    .set_path = senml_set_path,\n    .clear_path = senml_clear_path,\n    .set_time = senml_set_time,\n    .close = senml_output_close\n};\n\nanjay_unlocked_output_ctx_t *\n_anjay_output_senml_like_create(avs_stream_t *stream,\n                                const anjay_uri_path_t *uri,\n                                uint16_t format,\n                                const size_t *items_count) {\n#    ifndef ANJAY_WITH_CBOR\n    (void) items_count;\n#    endif // ANJAY_WITH_CBOR\n    senml_out_t *ctx = (senml_out_t *) avs_calloc(1, sizeof(senml_out_t));\n    if (!ctx) {\n        return NULL;\n    }\n    ctx->base.vtable = &SENML_OUT_VTABLE;\n    ctx->bytes.vtable = &STREAMED_BYTES_VTABLE;\n    ctx->timestamp = NAN;\n    ctx->path = MAKE_ROOT_PATH();\n    ctx->base_path = *uri;\n\n    switch (format) {\n#    ifdef ANJAY_WITH_LWM2M_JSON\n    case AVS_COAP_FORMAT_OMA_LWM2M_JSON: {\n        char basename_buf[MAX_PATH_STRING_SIZE];\n        char *basename =\n                maybe_get_basename(ctx, basename_buf, sizeof(basename_buf));\n        ctx->encoder = _anjay_lwm2m_json_encoder_new(stream, basename);\n        ctx->basename_written = true;\n        break;\n    }\n#    endif // ANJAY_WITH_LWM2M_JSON\n#    ifdef ANJAY_WITH_SENML_JSON\n    case AVS_COAP_FORMAT_SENML_JSON:\n        ctx->encoder = _anjay_senml_json_encoder_new(stream);\n        break;\n#    endif // ANJAY_WITH_SENML_JSON\n#    ifdef ANJAY_WITH_CBOR\n    case AVS_COAP_FORMAT_SENML_CBOR:\n        ctx->encoder = _anjay_senml_cbor_encoder_new(stream, items_count);\n        break;\n#    endif // ANJAY_WITH_CBOR\n    default:\n        senml_log(WARNING, _(\"unsupported content format\"));\n        goto error;\n    }\n\n    if (!ctx->encoder) {\n        goto error;\n    }\n\n    senml_log(DEBUG, _(\"created SenML-like context\"));\n    return (anjay_unlocked_output_ctx_t *) ctx;\n\nerror:\n    senml_log(DEBUG, _(\"failed to create SenML-like encoder\"));\n    avs_free(ctx);\n    return NULL;\n}\n\n#    if defined(ANJAY_TEST) && defined(ANJAY_WITH_CBOR)\n#        include \"tests/core/io/senml_cbor_out.c\"\n#    endif // defined(ANJAY_TEST) && defined(ANJAY_WITH_CBOR)\n\n#endif // defined(ANJAY_WITH_LWM2M_JSON) || defined(ANJAY_WITH_SENML_JSON) ||\n       // defined(ANJAY_WITH_CBOR)\n"
  },
  {
    "path": "src/core/io/anjay_text.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#ifndef ANJAY_WITHOUT_PLAINTEXT\n\n#    include <ctype.h>\n#    include <errno.h>\n#    include <inttypes.h>\n#    include <limits.h>\n#    include <stdlib.h>\n#    include <string.h>\n\n#    include <avsystem/commons/avs_base64.h>\n#    include <avsystem/commons/avs_stream.h>\n#    include <avsystem/commons/avs_utils.h>\n\n#    include <anjay/core.h>\n\n#    include \"../anjay_utils_private.h\"\n#    include \"../coap/anjay_content_format.h\"\n#    include \"anjay_base64_out.h\"\n#    include \"anjay_common.h\"\n#    include \"anjay_vtable.h\"\n\nVISIBILITY_SOURCE_BEGIN\n\n/////////////////////////////////////////////////////////////////////// ENCODING\n\ntypedef enum { STATE_INITIAL, STATE_PATH_SET, STATE_FINISHED } text_out_state_t;\n\ntypedef struct {\n    anjay_unlocked_output_ctx_t base;\n    anjay_unlocked_ret_bytes_ctx_t *bytes;\n    avs_stream_t *stream;\n    text_out_state_t state;\n} text_out_t;\n\nstatic int\ntext_ret_bytes_begin(anjay_unlocked_output_ctx_t *ctx_,\n                     size_t length,\n                     anjay_unlocked_ret_bytes_ctx_t **out_bytes_ctx) {\n    text_out_t *ctx = (text_out_t *) ctx_;\n    if (ctx->bytes || ctx->state != STATE_PATH_SET) {\n        return -1;\n    }\n    ctx->bytes = _anjay_base64_ret_bytes_ctx_new(\n            ctx->stream, AVS_BASE64_DEFAULT_STRICT_CONFIG, length);\n    *out_bytes_ctx = ctx->bytes;\n    return *out_bytes_ctx ? 0 : -1;\n}\n\nstatic int text_ret_string(anjay_unlocked_output_ctx_t *ctx_,\n                           const char *value) {\n    text_out_t *ctx = (text_out_t *) ctx_;\n    if (ctx->bytes) {\n        return -1;\n    }\n\n    if (ctx->state == STATE_PATH_SET\n            && avs_is_ok(avs_stream_write(ctx->stream, value, strlen(value)))) {\n        ctx->state = STATE_FINISHED;\n        return 0;\n    }\n    return -1;\n}\n\nstatic int text_ret_integer(anjay_unlocked_output_ctx_t *ctx_, int64_t value) {\n    text_out_t *ctx = (text_out_t *) ctx_;\n    if (ctx->bytes) {\n        return -1;\n    }\n\n    if (ctx->state == STATE_PATH_SET\n            && avs_is_ok(avs_stream_write_f(ctx->stream, \"%s\",\n                                            AVS_INT64_AS_STRING(value)))) {\n        ctx->state = STATE_FINISHED;\n        return 0;\n    }\n    return -1;\n}\n\n#    ifdef ANJAY_WITH_LWM2M11\nstatic int text_ret_uint(anjay_unlocked_output_ctx_t *ctx_, uint64_t value) {\n    text_out_t *ctx = (text_out_t *) ctx_;\n    if (ctx->bytes) {\n        return -1;\n    }\n\n    if (ctx->state == STATE_PATH_SET\n            && avs_is_ok(avs_stream_write_f(ctx->stream, \"%s\",\n                                            AVS_UINT64_AS_STRING(value)))) {\n        ctx->state = STATE_FINISHED;\n        return 0;\n    }\n    return -1;\n}\n#    endif // ANJAY_WITH_LWM2M11\n\nstatic int text_ret_double(anjay_unlocked_output_ctx_t *ctx_, double value) {\n    text_out_t *ctx = (text_out_t *) ctx_;\n    if (ctx->bytes) {\n        return -1;\n    }\n    // FIXME: The spec calls for a \"decimal\" representation, which, in my\n    // understanding, excludes exponential representation.\n    // As printing floating-point numbers in C as pure decimal with sane\n    // precision is tricky, let's take the spec a bit loosely for now.\n    if (ctx->state == STATE_PATH_SET\n            && avs_is_ok(avs_stream_write_f(ctx->stream, \"%s\",\n                                            AVS_DOUBLE_AS_STRING(value, 17)))) {\n        ctx->state = STATE_FINISHED;\n        return 0;\n    }\n    return -1;\n}\n\nstatic int text_ret_bool(anjay_unlocked_output_ctx_t *ctx, bool value) {\n    return text_ret_integer(ctx, value);\n}\n\nstatic int text_ret_objlnk(anjay_unlocked_output_ctx_t *ctx_,\n                           anjay_oid_t oid,\n                           anjay_iid_t iid) {\n    text_out_t *ctx = (text_out_t *) ctx_;\n    if (ctx->bytes) {\n        return -1;\n    }\n    if (ctx->state == STATE_PATH_SET\n            && avs_is_ok(avs_stream_write_f(ctx->stream, \"%\" PRIu16 \":%\" PRIu16,\n                                            oid, iid))) {\n        ctx->state = STATE_FINISHED;\n        return 0;\n    }\n    return -1;\n}\n\nstatic int text_set_path(anjay_unlocked_output_ctx_t *ctx_,\n                         const anjay_uri_path_t *path) {\n    text_out_t *ctx = (text_out_t *) ctx_;\n    if (ctx->state == STATE_PATH_SET) {\n        return -1;\n    } else if (ctx->state != STATE_INITIAL || ctx->bytes\n               || !_anjay_uri_path_has(path, ANJAY_ID_RID)) {\n        return ANJAY_OUTCTXERR_FORMAT_MISMATCH;\n    }\n    ctx->state = STATE_PATH_SET;\n    return 0;\n}\n\nstatic int text_clear_path(anjay_unlocked_output_ctx_t *ctx_) {\n    text_out_t *ctx = (text_out_t *) ctx_;\n    if (ctx->state != STATE_PATH_SET || ctx->bytes) {\n        return -1;\n    }\n    ctx->state = STATE_INITIAL;\n    return 0;\n}\n\nstatic int text_ret_close(anjay_unlocked_output_ctx_t *ctx_) {\n    text_out_t *ctx = (text_out_t *) ctx_;\n    int result = 0;\n    if (ctx->bytes) {\n        result = _anjay_base64_ret_bytes_ctx_close(ctx->bytes);\n        _anjay_base64_ret_bytes_ctx_delete(&ctx->bytes);\n    } else if (ctx->state != STATE_FINISHED) {\n        result = ANJAY_OUTCTXERR_ANJAY_RET_NOT_CALLED;\n    }\n    return result;\n}\n\nstatic const anjay_output_ctx_vtable_t TEXT_OUT_VTABLE = {\n    .bytes_begin = text_ret_bytes_begin,\n    .string = text_ret_string,\n    .integer = text_ret_integer,\n#    ifdef ANJAY_WITH_LWM2M11\n    .uint = text_ret_uint,\n#    endif // ANJAY_WITH_LWM2M11\n    .floating = text_ret_double,\n    .boolean = text_ret_bool,\n    .objlnk = text_ret_objlnk,\n    .set_path = text_set_path,\n    .clear_path = text_clear_path,\n    .close = text_ret_close\n};\n\nanjay_unlocked_output_ctx_t *_anjay_output_text_create(avs_stream_t *stream) {\n    text_out_t *ctx = (text_out_t *) avs_calloc(1, sizeof(text_out_t));\n    if (ctx) {\n        ctx->base.vtable = &TEXT_OUT_VTABLE;\n        ctx->stream = stream;\n    }\n    return (anjay_unlocked_output_ctx_t *) ctx;\n}\n\n/////////////////////////////////////////////////////////////////////// DECODING\n\ntypedef struct {\n    const anjay_input_ctx_vtable_t *vtable;\n    avs_stream_t *stream;\n    // if bytes_mode == true, then only raw bytes can be read from the context\n    // and any other reading operation will fail\n    bool bytes_mode;\n    uint8_t bytes_cached[3];\n    size_t num_bytes_cached;\n    bool msg_finished;\n\n    anjay_uri_path_t request_uri;\n} text_in_t;\n\nstatic int\nhas_valid_padding(const char *buffer, size_t size, bool msg_finished) {\n    const char *last = buffer + size;\n    // Note: buffer is size+1 in length. Last byte is a NULL terminator though.\n    assert(!*last);\n    return last - 1 >= buffer && *(last - 1) == '=' && !msg_finished ? -1 : 0;\n}\n\nstatic void text_get_some_bytes_cache_flush(text_in_t *ctx,\n                                            uint8_t **out_buf,\n                                            size_t *buf_size) {\n    size_t bytes_to_copy = AVS_MIN(ctx->num_bytes_cached, *buf_size);\n    memcpy(*out_buf, ctx->bytes_cached, bytes_to_copy);\n    memmove(ctx->bytes_cached, ctx->bytes_cached + bytes_to_copy,\n            sizeof(ctx->bytes_cached) - bytes_to_copy);\n    ctx->num_bytes_cached -= bytes_to_copy;\n    *buf_size -= bytes_to_copy;\n    *out_buf += bytes_to_copy;\n}\n\nstatic int text_get_some_bytes(anjay_unlocked_input_ctx_t *ctx_,\n                               size_t *out_bytes_read,\n                               bool *out_msg_finished,\n                               void *out_buf,\n                               size_t buf_size) {\n    text_in_t *ctx = (text_in_t *) ctx_;\n    ctx->bytes_mode = true;\n    uint8_t *current = (uint8_t *) out_buf;\n    *out_msg_finished = false;\n    *out_bytes_read = 0;\n\n    text_get_some_bytes_cache_flush(ctx, &current, &buf_size);\n    // 4bytes + null terminator\n    char encoded[5];\n    size_t stream_bytes_read;\n    bool stream_msg_finished = false;\n\n    while (buf_size > 0) {\n        if (avs_is_err(avs_stream_read(ctx->stream, &stream_bytes_read,\n                                       &stream_msg_finished, encoded,\n                                       sizeof(encoded) - 1))) {\n            return -1;\n        }\n        encoded[stream_bytes_read] = '\\0';\n        if (stream_bytes_read % 4) {\n            return ANJAY_ERR_BAD_REQUEST;\n        }\n        if (has_valid_padding(encoded, stream_bytes_read,\n                              stream_msg_finished)) {\n            return ANJAY_ERR_BAD_REQUEST;\n        }\n        assert(ctx->num_bytes_cached == 0);\n        size_t num_decoded;\n        if (avs_base64_decode_strict(&num_decoded,\n                                     (uint8_t *) ctx->bytes_cached,\n                                     sizeof(ctx->bytes_cached), encoded)) {\n            return ANJAY_ERR_BAD_REQUEST;\n        }\n        ctx->num_bytes_cached = num_decoded;\n        text_get_some_bytes_cache_flush(ctx, &current, &buf_size);\n        if (stream_msg_finished) {\n            break;\n        }\n    }\n    ctx->msg_finished = stream_msg_finished;\n    *out_msg_finished = ctx->msg_finished && !ctx->num_bytes_cached;\n    *out_bytes_read = (size_t) (current - (uint8_t *) out_buf);\n    return 0;\n}\n\nstatic int text_get_string(anjay_unlocked_input_ctx_t *ctx,\n                           char *out_buf,\n                           size_t buf_size) {\n    assert(buf_size);\n    text_in_t *in = (text_in_t *) ctx;\n    if (in->bytes_mode) {\n        return -1;\n    }\n    char *ptr = out_buf;\n    char *endptr = out_buf + (buf_size - 1);\n    do {\n        size_t bytes_read = 0;\n        if (avs_is_err(avs_stream_read(in->stream, &bytes_read,\n                                       &in->msg_finished, ptr,\n                                       (size_t) (endptr - ptr)))) {\n            return -1;\n        }\n        ptr += bytes_read;\n    } while (!in->msg_finished && ptr < endptr);\n    *ptr = '\\0';\n    return in->msg_finished ? 0 : ANJAY_BUFFER_TOO_SHORT;\n}\n\nstatic int map_get_string_error(int retval) {\n    /**\n     * NOTE: this function should be used ONLY when getting data to a fixed\n     * buffer and when we know for sure that the input cannot be longer.\n     */\n    if (retval == ANJAY_BUFFER_TOO_SHORT) {\n        return ANJAY_ERR_BAD_REQUEST;\n    }\n    return retval;\n}\n\nstatic int text_get_integer(anjay_unlocked_input_ctx_t *ctx, int64_t *value) {\n    char buf[AVS_INT_STR_BUF_SIZE(long long)];\n    int retval = _anjay_get_string_unlocked(ctx, buf, sizeof(buf));\n    if (retval) {\n        return map_get_string_error(retval);\n    }\n    long long ll;\n    if (_anjay_safe_strtoll(buf, &ll)\n#    if LLONG_MAX != INT64_MAX\n            || ll < INT64_MIN || ll > INT64_MAX\n#    endif\n    ) {\n        return ANJAY_ERR_BAD_REQUEST;\n    }\n    *value = (int64_t) ll;\n    return 0;\n}\n\n#    ifdef ANJAY_WITH_LWM2M11\nstatic int text_get_uint(anjay_unlocked_input_ctx_t *ctx, uint64_t *value) {\n    char buf[AVS_UINT_STR_BUF_SIZE(unsigned long long)];\n    int retval = _anjay_get_string_unlocked(ctx, buf, sizeof(buf));\n    if (retval) {\n        return map_get_string_error(retval);\n    }\n    unsigned long long ull;\n    if (_anjay_safe_strtoull(buf, &ull)\n#        if ULLONG_MAX != UINT64_MAX\n            || ull > UINT64_MAX\n#        endif\n    ) {\n        return ANJAY_ERR_BAD_REQUEST;\n    }\n    *value = (uint64_t) ull;\n    return 0;\n}\n#    endif // ANJAY_WITH_LWM2M11\n\nstatic int text_get_bool(anjay_unlocked_input_ctx_t *ctx, bool *value) {\n    int64_t i64;\n    int retval = text_get_integer(ctx, &i64);\n    if (retval) {\n        return retval;\n    }\n    if (i64 == 0) {\n        *value = false;\n    } else if (i64 == 1) {\n        *value = true;\n    } else {\n        return ANJAY_ERR_BAD_REQUEST;\n    }\n    return 0;\n}\n\nstatic int text_get_double(anjay_unlocked_input_ctx_t *ctx, double *value) {\n    char buf[ANJAY_MAX_DOUBLE_STRING_SIZE];\n    int retval = _anjay_get_string_unlocked(ctx, buf, sizeof(buf));\n    if (retval) {\n        return map_get_string_error(retval);\n    }\n    if (_anjay_safe_strtod(buf, value)) {\n        return ANJAY_ERR_BAD_REQUEST;\n    }\n    return 0;\n}\n\nstatic int text_get_objlnk(anjay_unlocked_input_ctx_t *ctx,\n                           anjay_oid_t *out_oid,\n                           anjay_iid_t *out_iid) {\n    char buf[MAX_OBJLNK_STRING_SIZE];\n    int retval = _anjay_get_string_unlocked(ctx, buf, sizeof(buf));\n    if (retval) {\n        return map_get_string_error(retval);\n    }\n\n    if (_anjay_io_parse_objlnk(buf, out_oid, out_iid)) {\n        return ANJAY_ERR_BAD_REQUEST;\n    }\n    return 0;\n}\n\nstatic int text_in_close(anjay_unlocked_input_ctx_t *ctx_) {\n    (void) ctx_;\n    return 0;\n}\n\nstatic int text_get_path(anjay_unlocked_input_ctx_t *ctx,\n                         anjay_uri_path_t *out_path,\n                         bool *out_is_array) {\n    text_in_t *in = (text_in_t *) ctx;\n    if (in->msg_finished) {\n        return ANJAY_GET_PATH_END;\n    }\n    if (!_anjay_uri_path_has(&in->request_uri, ANJAY_ID_RID)) {\n        return ANJAY_ERR_BAD_REQUEST;\n    }\n    *out_is_array = false;\n    *out_path = in->request_uri;\n    return 0;\n}\n\nstatic int text_next_entry(anjay_unlocked_input_ctx_t *ctx) {\n    (void) ctx;\n    return 0;\n}\n\nstatic const anjay_input_ctx_vtable_t TEXT_IN_VTABLE = {\n    .some_bytes = text_get_some_bytes,\n    .string = text_get_string,\n    .integer = text_get_integer,\n#    ifdef ANJAY_WITH_LWM2M11\n    .uint = text_get_uint,\n#    endif // ANJAY_WITH_LWM2M11\n    .floating = text_get_double,\n    .boolean = text_get_bool,\n    .objlnk = text_get_objlnk,\n    .get_path = text_get_path,\n    .next_entry = text_next_entry,\n    .close = text_in_close\n};\n\nint _anjay_input_text_create(anjay_unlocked_input_ctx_t **out,\n                             avs_stream_t *stream_ptr,\n                             const anjay_uri_path_t *request_uri) {\n    text_in_t *ctx = (text_in_t *) avs_calloc(1, sizeof(text_in_t));\n    *out = (anjay_unlocked_input_ctx_t *) ctx;\n    if (!ctx) {\n        return -1;\n    }\n\n    ctx->vtable = &TEXT_IN_VTABLE;\n    ctx->stream = stream_ptr;\n    ctx->request_uri = request_uri ? *request_uri : MAKE_ROOT_PATH();\n\n    return 0;\n}\n\n#    ifdef ANJAY_TEST\n#        include \"tests/core/io/text.c\"\n#    endif\n\n#endif // ANJAY_WITHOUT_PLAINTEXT\n"
  },
  {
    "path": "src/core/io/anjay_tlv.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ANJAY_IO_TLV_H\n#define ANJAY_IO_TLV_H\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\ntypedef enum {\n    TLV_ID_IID = 0,\n    TLV_ID_RIID = 1,\n    TLV_ID_RID_ARRAY = 2,\n    TLV_ID_RID = 3\n} tlv_id_type_t;\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif /* ANJAY_IO_TLV_H */\n"
  },
  {
    "path": "src/core/io/anjay_tlv_in.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#ifndef ANJAY_WITHOUT_TLV\n\n#    include <avsystem/commons/avs_stream_v_table.h>\n#    include <avsystem/commons/avs_utils.h>\n\n#    include \"../anjay_utils_private.h\"\n\n#    include \"anjay_common.h\"\n#    include \"anjay_tlv.h\"\n#    include \"anjay_vtable.h\"\n\nVISIBILITY_SOURCE_BEGIN\n\n#    define LOG(...) _anjay_log(tlv_in, __VA_ARGS__)\n\ntypedef struct {\n    anjay_id_type_t type;\n    size_t length;\n    size_t bytes_read;\n} tlv_entry_t;\n\ntypedef struct {\n    const anjay_input_ctx_vtable_t *vtable;\n    avs_stream_t *stream;\n\n    anjay_uri_path_t uri_path;\n    // Currently processed path\n    bool has_path;\n    bool is_array;\n    anjay_uri_path_t current_path;\n    // we need to store separate length\n    // because there might be a \"hole\" for unspecified IID\n    size_t current_path_len;\n\n    AVS_LIST(tlv_entry_t) entries;\n    bool finished;\n} tlv_in_t;\n\nstatic tlv_entry_t *tlv_entry_push(tlv_in_t *ctx) {\n    return AVS_LIST_INSERT_NEW(tlv_entry_t, &ctx->entries);\n}\n\nstatic void tlv_entry_pop(tlv_in_t *ctx) {\n    AVS_LIST_DELETE(&ctx->entries);\n}\n\nstatic int tlv_get_some_bytes(anjay_unlocked_input_ctx_t *ctx_,\n                              size_t *out_bytes_read,\n                              bool *out_message_finished,\n                              void *out_buf,\n                              size_t buf_size) {\n    tlv_in_t *ctx = (tlv_in_t *) ctx_;\n    if (!ctx->has_path) {\n        int result = _anjay_input_get_path(ctx_, NULL, NULL);\n        if (result == ANJAY_GET_PATH_END) {\n            *out_message_finished = true;\n            *out_bytes_read = 0;\n            return 0;\n        } else if (result) {\n            return result;\n        }\n    }\n    if (!ctx->entries) {\n        return -1;\n    }\n    bool stream_finished;\n    *out_bytes_read = 0;\n    buf_size =\n            AVS_MIN(buf_size, ctx->entries->length - ctx->entries->bytes_read);\n    avs_error_t err = avs_stream_read(ctx->stream, out_bytes_read,\n                                      &stream_finished, out_buf, buf_size);\n    ctx->entries->bytes_read += *out_bytes_read;\n    if (avs_is_err(err)) {\n        return -1;\n    }\n    ctx->finished = stream_finished;\n    if (!(*out_message_finished =\n                  (ctx->entries->bytes_read == ctx->entries->length))\n            && stream_finished) {\n        return ANJAY_ERR_BAD_REQUEST;\n    }\n    return 0;\n}\n\nstatic int tlv_read_to_end(anjay_unlocked_input_ctx_t *ctx,\n                           size_t *out_bytes_read,\n                           void *out_buf,\n                           size_t buf_size) {\n    bool message_finished;\n    char *ptr = (char *) out_buf;\n    char *endptr = ptr + buf_size;\n    do {\n        size_t bytes_read = 0;\n        int retval = tlv_get_some_bytes(ctx, &bytes_read, &message_finished,\n                                        ptr, (size_t) (endptr - ptr));\n        if (retval) {\n            return retval;\n        }\n        ptr += bytes_read;\n    } while (!message_finished && ptr < endptr);\n\n    *out_bytes_read = (size_t) (ptr - (char *) out_buf);\n    return message_finished ? 0 : ANJAY_BUFFER_TOO_SHORT;\n}\n\nstatic int tlv_read_whole_entry(anjay_unlocked_input_ctx_t *ctx_,\n                                size_t *out_bytes_read,\n                                void *out_buf,\n                                size_t buf_size) {\n    tlv_in_t *ctx = (tlv_in_t *) ctx_;\n    if (!ctx->has_path) {\n        int result = _anjay_input_get_path(ctx_, NULL, NULL);\n        if (result == ANJAY_GET_PATH_END) {\n            *out_bytes_read = 0;\n            return 0;\n        } else if (result) {\n            return result;\n        }\n    }\n    if (!ctx->entries || ctx->entries->bytes_read) {\n        return -1;\n    }\n    return tlv_read_to_end(ctx_, out_bytes_read, out_buf, buf_size);\n}\n\nstatic int tlv_get_string(anjay_unlocked_input_ctx_t *ctx,\n                          char *out_buf,\n                          size_t buf_size) {\n    assert(buf_size);\n    size_t bytes_read = 0;\n    int retval = tlv_read_to_end(ctx, &bytes_read, out_buf, buf_size - 1);\n    out_buf[bytes_read] = '\\0';\n    return retval;\n}\n\nstatic int tlv_get_integer(anjay_unlocked_input_ctx_t *ctx, int64_t *value) {\n    uint8_t bytes[8];\n    size_t bytes_read = 0;\n    int retval;\n    if ((retval = tlv_read_whole_entry(ctx, &bytes_read, bytes, sizeof(bytes)))\n            || !avs_is_power_of_2(bytes_read)) {\n        return retval ? retval : ANJAY_ERR_BAD_REQUEST;\n    }\n    *value = (bytes_read > 0 && ((int8_t) bytes[0]) < 0) ? -1 : 0;\n    for (size_t i = 0; i < bytes_read; ++i) {\n        *(uint64_t *) value <<= 8;\n        *value += bytes[i];\n    }\n    return 0;\n}\n\n#    ifdef ANJAY_WITH_LWM2M11\nstatic int tlv_get_uint(anjay_unlocked_input_ctx_t *ctx, uint64_t *value) {\n    uint8_t bytes[8];\n    size_t bytes_read = 0;\n    int retval;\n    if ((retval = tlv_read_whole_entry(ctx, &bytes_read, bytes, sizeof(bytes)))\n            || !avs_is_power_of_2(bytes_read)) {\n        return retval ? retval : ANJAY_ERR_BAD_REQUEST;\n    }\n    *value = 0;\n    for (size_t i = 0; i < bytes_read; ++i) {\n        *value <<= 8;\n        *value += bytes[i];\n    }\n    return 0;\n}\n#    endif // ANJAY_WITH_LWM2M11\n\nstatic int tlv_get_double(anjay_unlocked_input_ctx_t *ctx, double *value) {\n    union {\n        uint32_t f32;\n        uint64_t f64;\n    } data;\n    size_t bytes_read = 0;\n    int retval = tlv_read_whole_entry(ctx, &bytes_read, &data, 8);\n    if (retval) {\n        return retval;\n    }\n    switch (bytes_read) {\n    case 4:\n        *value = avs_ntohf(data.f32);\n        return 0;\n    case 8:\n        *value = avs_ntohd(data.f64);\n        return 0;\n    default:\n        return ANJAY_ERR_BAD_REQUEST;\n    }\n}\n\nstatic int tlv_get_bool(anjay_unlocked_input_ctx_t *ctx, bool *value) {\n    char raw;\n    size_t bytes_read = 0;\n    int retval = tlv_read_whole_entry(ctx, &bytes_read, &raw, 1);\n    if (retval == ANJAY_BUFFER_TOO_SHORT || bytes_read != 1) {\n        return ANJAY_ERR_BAD_REQUEST;\n    } else if (retval) {\n        return retval;\n    }\n    switch (raw) {\n    case 0:\n        *value = false;\n        return 0;\n    case 1:\n        *value = true;\n        return 0;\n    default:\n        return ANJAY_ERR_BAD_REQUEST;\n    }\n}\n\nstatic int tlv_get_objlnk(anjay_unlocked_input_ctx_t *ctx,\n                          anjay_oid_t *out_oid,\n                          anjay_iid_t *out_iid) {\n    AVS_STATIC_ASSERT(sizeof(uint16_t[2]) == 4, uint16_t_array_size);\n    uint16_t raw[2];\n    size_t bytes_read = 0;\n    int retval = tlv_read_whole_entry(ctx, &bytes_read, raw, 4);\n    if (retval == ANJAY_BUFFER_TOO_SHORT || bytes_read != 4) {\n        return ANJAY_ERR_BAD_REQUEST;\n    } else if (retval) {\n        return retval;\n    }\n    *out_oid = avs_convert_be16(raw[0]);\n    *out_iid = avs_convert_be16(raw[1]);\n    return 0;\n}\n\n#    define DEF_READ_SHORTENED(Type)                                           \\\n        static int read_shortened_##Type(avs_stream_t *stream, size_t length,  \\\n                                         Type *out) {                          \\\n            uint8_t bytes[sizeof(Type)];                                       \\\n            if (avs_is_err(avs_stream_read_reliably(stream, bytes, length))) { \\\n                return -1;                                                     \\\n            }                                                                  \\\n            *out = 0;                                                          \\\n            for (size_t i = 0; i < length; ++i) {                              \\\n                *out = (Type) ((*out << 8) + bytes[i]);                        \\\n            }                                                                  \\\n            return 0;                                                          \\\n        }\n\nDEF_READ_SHORTENED(uint16_t)\nDEF_READ_SHORTENED(size_t)\n\nstatic tlv_id_type_t tlv_type_from_typefield(uint8_t typefield) {\n    return (tlv_id_type_t) ((typefield >> 6) & 3);\n}\n\nstatic anjay_id_type_t convert_id_type(uint8_t typefield) {\n    switch (tlv_type_from_typefield(typefield)) {\n    default:\n        AVS_UNREACHABLE(\"Invalid TLV ID type\");\n    case TLV_ID_IID:\n        return ANJAY_ID_IID;\n    case TLV_ID_RIID:\n        return ANJAY_ID_RIID;\n    case TLV_ID_RID_ARRAY:\n    case TLV_ID_RID:\n        return ANJAY_ID_RID;\n    }\n}\n\nstatic int get_id(tlv_in_t *ctx,\n                  anjay_id_type_t *out_type,\n                  uint16_t *out_id,\n                  bool *out_has_value,\n                  size_t *out_bytes_read,\n                  bool *out_is_array) {\n    uint8_t typefield;\n    avs_error_t err = avs_stream_read_reliably(ctx->stream, &typefield, 1);\n    if (avs_is_eof(err)) {\n        return ANJAY_GET_PATH_END;\n    } else if (avs_is_err(err)) {\n        return -1;\n    }\n    *out_bytes_read = 1;\n    tlv_id_type_t tlv_type = tlv_type_from_typefield(typefield);\n    *out_is_array = (tlv_type == TLV_ID_RID_ARRAY);\n    *out_type = convert_id_type(typefield);\n    size_t id_length = (typefield & 0x20) ? 2 : 1;\n    if (read_shortened_uint16_t(ctx->stream, id_length, out_id)) {\n        return -1;\n    }\n    *out_bytes_read += id_length;\n\n    size_t length_length = ((typefield >> 3) & 3);\n    if (!length_length) {\n        ctx->entries->length = (typefield & 7);\n    } else if (read_shortened_size_t(ctx->stream, length_length,\n                                     &ctx->entries->length)) {\n        return -1;\n    }\n    *out_bytes_read += length_length;\n    /**\n     * This may seem a little bit strange, but entries that do not have any\n     * payload may be considered as having a value - that is, an empty one. On\n     * the other hand, if they DO have the payload, then it only makes sense to\n     * return them if they're \"terminal\" - i.e. they're either resource\n     * instances or single resources with value.\n     */\n    *out_has_value = !ctx->entries->length || tlv_type == TLV_ID_RIID\n                     || tlv_type == TLV_ID_RID;\n    ctx->entries->bytes_read = 0;\n    ctx->entries->type = *out_type;\n    return 0;\n}\n\nstatic int tlv_get_path(anjay_unlocked_input_ctx_t *ctx,\n                        anjay_uri_path_t *out_path,\n                        bool *out_is_array) {\n    tlv_in_t *in = (tlv_in_t *) ctx;\n    if (in->finished) {\n        return ANJAY_GET_PATH_END;\n    }\n    if (in->has_path) {\n        *out_path = in->current_path;\n        *out_is_array = in->is_array;\n        return 0;\n    }\n    bool has_value = false;\n    anjay_id_type_t type;\n    uint16_t id;\n    int result = 0;\n    while (!has_value) {\n        tlv_entry_t *parent = in->entries;\n        if (!tlv_entry_push(in)) {\n            return ANJAY_ERR_INTERNAL;\n        }\n        size_t header_len;\n        if ((result = get_id(in, &type, &id, &has_value, &header_len,\n                             &in->is_array))) {\n            break;\n        }\n        if (id == ANJAY_ID_INVALID) {\n            return ANJAY_ERR_BAD_REQUEST;\n        }\n        if (parent) {\n            // Assume the child entry is fully read (which is in fact necessary\n            // to be able to return back to the parent).\n            parent->bytes_read += in->entries->length + header_len;\n            if (parent->bytes_read > parent->length) {\n                LOG(DEBUG, _(\"child entry is longer than its parent\"));\n                return ANJAY_ERR_BAD_REQUEST;\n            }\n        }\n        in->current_path.ids[(size_t) type] = id;\n        in->current_path_len = (size_t) type + 1;\n\n        if (_anjay_uri_path_outside_base(&in->current_path, &in->uri_path)) {\n            LOG(LAZY_DEBUG,\n                _(\"parsed path \") \"%s\" _(\" would be outside of uri-path \") \"%s\",\n                ANJAY_DEBUG_MAKE_PATH(&in->current_path),\n                ANJAY_DEBUG_MAKE_PATH(&in->uri_path));\n            return ANJAY_ERR_BAD_REQUEST;\n        }\n    }\n    *out_path = in->current_path;\n    *out_is_array = in->is_array;\n    in->has_path = true;\n    return result;\n}\n\nstatic int tlv_next_entry(anjay_unlocked_input_ctx_t *ctx) {\n    tlv_in_t *in = (tlv_in_t *) ctx;\n    if (!in->has_path) {\n        // Next entry is already available and should be processed.\n        return 0;\n    }\n    if (!in->entries) {\n        return -1;\n    }\n    bool finished = false;\n    while (!finished) {\n        char ignored[64];\n        size_t bytes_read;\n        int retval = tlv_get_some_bytes(ctx, &bytes_read, &finished, ignored,\n                                        sizeof(ignored));\n        if (retval) {\n            return retval;\n        }\n    }\n    in->has_path = false;\n    in->is_array = false;\n    while (in->entries && in->entries->length == in->entries->bytes_read) {\n        in->current_path.ids[in->entries->type] = ANJAY_ID_INVALID;\n        in->current_path_len = (size_t) in->entries->type;\n        tlv_entry_pop(in);\n    }\n    return 0;\n}\n\nstatic int tlv_update_root_path(anjay_unlocked_input_ctx_t *ctx_,\n                                const anjay_uri_path_t *root_path) {\n    tlv_in_t *ctx = (tlv_in_t *) ctx_;\n    anjay_uri_path_t new_path = (root_path ? *root_path : MAKE_ROOT_PATH());\n    size_t i;\n    for (i = 0; i < AVS_ARRAY_SIZE(new_path.ids)\n                && new_path.ids[i] != ANJAY_ID_INVALID;\n         ++i) {\n        if (ctx->uri_path.ids[i] == ANJAY_ID_INVALID\n                && i < ctx->current_path_len\n                && ctx->current_path.ids[i] != ANJAY_ID_INVALID) {\n            // updating the root path would overwrite value actually read from\n            // payload\n            return -1;\n        } else {\n            ctx->current_path.ids[i] = ctx->uri_path.ids[i] = new_path.ids[i];\n        }\n    }\n    ctx->current_path_len = AVS_MAX(ctx->current_path_len, i);\n    return 0;\n}\n\nstatic int tlv_in_close(anjay_unlocked_input_ctx_t *ctx_) {\n    tlv_in_t *ctx = (tlv_in_t *) ctx_;\n    if (ctx->entries && !ctx->finished) {\n        LOG(DEBUG, _(\"input context is destroyed but not fully processed yet\"));\n    }\n    AVS_LIST_CLEAR(&ctx->entries);\n    return 0;\n}\n\nstatic const anjay_input_ctx_vtable_t TLV_IN_VTABLE = {\n    .some_bytes = tlv_get_some_bytes,\n    .string = tlv_get_string,\n    .integer = tlv_get_integer,\n#    ifdef ANJAY_WITH_LWM2M11\n    .uint = tlv_get_uint,\n#    endif // ANJAY_WITH_LWM2M11\n    .floating = tlv_get_double,\n    .boolean = tlv_get_bool,\n    .objlnk = tlv_get_objlnk,\n    .get_path = tlv_get_path,\n    .next_entry = tlv_next_entry,\n    .update_root_path = tlv_update_root_path,\n    .close = tlv_in_close\n};\n\nint _anjay_input_tlv_create(anjay_unlocked_input_ctx_t **out,\n                            avs_stream_t *stream_ptr,\n                            const anjay_uri_path_t *request_uri) {\n    tlv_in_t *ctx = (tlv_in_t *) avs_calloc(1, sizeof(tlv_in_t));\n    *out = (anjay_unlocked_input_ctx_t *) ctx;\n    if (!ctx) {\n        return -1;\n    }\n    ctx->vtable = &TLV_IN_VTABLE;\n    ctx->stream = stream_ptr;\n    ctx->uri_path = (request_uri ? *request_uri : MAKE_ROOT_PATH());\n    ctx->current_path = ctx->uri_path;\n\n    return 0;\n}\n\n#    ifdef ANJAY_TEST\n#        include \"tests/core/io/tlv_in.c\"\n#    endif\n\n#endif // ANJAY_WITHOUT_TLV\n"
  },
  {
    "path": "src/core/io/anjay_tlv_out.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#ifndef ANJAY_WITHOUT_TLV\n\n#    include <assert.h>\n#    include <string.h>\n\n#    include <avsystem/commons/avs_list.h>\n#    include <avsystem/commons/avs_memory.h>\n#    include <avsystem/commons/avs_stream.h>\n#    include <avsystem/commons/avs_utils.h>\n\n#    include \"../anjay_io_core.h\"\n#    include \"../coap/anjay_content_format.h\"\n#    include \"anjay_tlv.h\"\n#    include \"anjay_vtable.h\"\n\nVISIBILITY_SOURCE_BEGIN\n\ntypedef struct {\n    tlv_id_type_t type;\n    uint16_t id;\n} tlv_id_t;\n\n#    define TLV_MAX_LENGTH ((1 << 24) - 1)\n\ntypedef struct {\n    size_t data_length;\n    tlv_id_t id;\n    char data[];\n} tlv_entry_t;\n\ntypedef struct {\n    const anjay_ret_bytes_ctx_vtable_t *vtable;\n    union {\n        char *buffer_ptr;\n        avs_stream_t *stream;\n    } output;\n    size_t bytes_left;\n} tlv_bytes_t;\n\ntypedef struct {\n    AVS_LIST(tlv_entry_t) entries;\n    AVS_LIST(tlv_entry_t) *next_entry_ptr;\n\n    // ID that will be used when serializing the next element.\n    // ANJAY_ID_INVALID if it's not set.\n    uint16_t next_id;\n\n    tlv_bytes_t bytes_ctx;\n} tlv_out_level_t;\n\ntypedef enum {\n    TLV_OUT_LEVEL_IID = 0,\n    TLV_OUT_LEVEL_RID = 1,\n    TLV_OUT_LEVEL_RIID = 2,\n    _TLV_OUT_LEVEL_LIMIT\n} tlv_out_level_id_t;\n\ntypedef struct tlv_out_struct {\n    anjay_unlocked_output_ctx_t base;\n    avs_stream_t *stream;\n    anjay_uri_path_t root_path;\n    tlv_out_level_t levels[_TLV_OUT_LEVEL_LIMIT];\n    tlv_out_level_id_t level;\n} tlv_out_t;\n\nstatic inline uint8_t u32_length(uint32_t value) {\n    uint8_t result = 1;\n    while ((value >>= 8)) {\n        ++result;\n    }\n    return result;\n}\n\nstatic inline uint8_t typefield_length(uint32_t length) {\n    if (length <= 7) {\n        return (uint8_t) length;\n    } else {\n        return (uint8_t) (u32_length(length) << 3);\n    }\n}\n\nstatic int write_shortened_u32(avs_stream_t *stream, uint32_t value) {\n    uint8_t length = u32_length(value);\n    assert(length <= 4);\n    union {\n        uint32_t uval;\n        char tab[4];\n    } value32;\n    value32.uval = avs_convert_be32(value);\n    return avs_is_ok(\n                   avs_stream_write(stream, value32.tab + (4 - length), length))\n                   ? 0\n                   : -1;\n}\n\nstatic size_t header_size(uint16_t id, size_t length) {\n    assert(length == (uint32_t) length);\n    return 1 + (size_t) u32_length(id)\n           + ((length > 7) ? (size_t) u32_length((uint32_t) length) : 0);\n}\n\nstatic int write_header(avs_stream_t *stream,\n                        tlv_id_type_t type,\n                        uint16_t id,\n                        size_t length) {\n    if (id == ANJAY_ID_INVALID || length > TLV_MAX_LENGTH) {\n        return -1;\n    }\n    uint8_t typefield =\n            (uint8_t) (((type & 3) << 6) | ((id > UINT8_MAX) ? 0x20 : 0)\n                       | typefield_length((uint32_t) length));\n    if (avs_is_err(avs_stream_write(stream, &typefield, 1))\n            || write_shortened_u32(stream, id)) {\n        return -1;\n    }\n    if (length > 7) {\n        return write_shortened_u32(stream, (uint32_t) length);\n    }\n    return 0;\n}\n\nstatic int get_root_level(const anjay_uri_path_t *root_path,\n                          tlv_out_level_id_t *out) {\n    switch (_anjay_uri_path_length(root_path)) {\n    case 1: // object path\n        *out = TLV_OUT_LEVEL_IID;\n        return 0;\n    case 2: // instance path\n    case 3: // resource path\n        *out = TLV_OUT_LEVEL_RID;\n        return 0;\n    case 4: // resource instance path\n        *out = TLV_OUT_LEVEL_RIID;\n        return 0;\n    default:\n        AVS_UNREACHABLE(\"Invalid root path\");\n        return -1;\n    }\n}\n\nstatic inline tlv_out_level_t *current_level(tlv_out_t *ctx) {\n    return &ctx->levels[ctx->level];\n}\n\nstatic int get_current_level_value_type(tlv_out_t *ctx, tlv_id_type_t *out) {\n    AVS_ASSERT(current_level(ctx)->next_id != ANJAY_ID_INVALID,\n               \"Attempted to serialize value without setting path. This is a \"\n               \"bug in resource reading logic.\");\n    switch (ctx->level) {\n    case TLV_OUT_LEVEL_RID:\n        *out = TLV_ID_RID;\n        return 0;\n    case TLV_OUT_LEVEL_RIID:\n        *out = TLV_ID_RIID;\n        return 0;\n    default:\n        AVS_UNREACHABLE(\"Attempted to serialize value with path set to neither \"\n                        \"Resource nor Resource Instance. This is a bug in \"\n                        \"resource reading logic.\");\n        return -1;\n    }\n}\n\nstatic int write_entry(avs_stream_t *stream,\n                       const tlv_id_t *id,\n                       const void *buf,\n                       size_t length) {\n    if (write_header(stream, id->type, id->id, length)\n            || avs_is_err(avs_stream_write(stream, buf, length))) {\n        return -1;\n    }\n    return 0;\n}\n\nstatic char *\nadd_buffered_entry(tlv_out_t *ctx, tlv_id_type_t type, size_t length) {\n    tlv_entry_t *new_entry =\n            (tlv_entry_t *) AVS_LIST_NEW_BUFFER(sizeof(tlv_entry_t) + length);\n    if (!new_entry) {\n        return NULL;\n    }\n    new_entry->data_length = length;\n    new_entry->id.type = type;\n    new_entry->id.id = current_level(ctx)->next_id;\n    current_level(ctx)->next_id = ANJAY_ID_INVALID;\n    *current_level(ctx)->next_entry_ptr = new_entry;\n    AVS_LIST_ADVANCE_PTR(&current_level(ctx)->next_entry_ptr);\n    return new_entry->data;\n}\n\nstatic int streamed_bytes_append(anjay_unlocked_ret_bytes_ctx_t *ctx_,\n                                 const void *data,\n                                 size_t length);\n\nstatic const anjay_ret_bytes_ctx_vtable_t STREAMED_BYTES_VTABLE = {\n    .append = streamed_bytes_append\n};\n\nstatic int streamed_bytes_append(anjay_unlocked_ret_bytes_ctx_t *ctx_,\n                                 const void *data,\n                                 size_t length) {\n    tlv_bytes_t *ctx = (tlv_bytes_t *) ctx_;\n    assert(ctx->vtable == &STREAMED_BYTES_VTABLE);\n    if (length) {\n        if (length > ctx->bytes_left\n                || avs_is_err(avs_stream_write(ctx->output.stream, data,\n                                               length))) {\n            return -1;\n        }\n        ctx->bytes_left -= length;\n    }\n    return 0;\n}\n\nstatic int buffered_bytes_append(anjay_unlocked_ret_bytes_ctx_t *ctx_,\n                                 const void *data,\n                                 size_t length);\n\nstatic const anjay_ret_bytes_ctx_vtable_t BUFFERED_BYTES_VTABLE = {\n    .append = buffered_bytes_append\n};\n\nstatic int buffered_bytes_append(anjay_unlocked_ret_bytes_ctx_t *ctx_,\n                                 const void *data,\n                                 size_t length) {\n    tlv_bytes_t *ctx = (tlv_bytes_t *) ctx_;\n    assert(ctx->vtable == &BUFFERED_BYTES_VTABLE);\n    int retval = 0;\n    if (length) {\n        if (length > ctx->bytes_left) {\n            retval = -1;\n        } else {\n            memcpy(ctx->output.buffer_ptr, data, length);\n            ctx->output.buffer_ptr += length;\n        }\n    }\n    if (!retval) {\n        ctx->bytes_left -= length;\n    }\n    return retval;\n}\n\nstatic anjay_unlocked_ret_bytes_ctx_t *\nadd_entry(tlv_out_t *ctx, tlv_id_type_t type, size_t length) {\n    tlv_out_level_t *out_level = current_level(ctx);\n    tlv_out_level_id_t root_level;\n    if (length > TLV_MAX_LENGTH || out_level->bytes_ctx.bytes_left\n            || get_root_level(&ctx->root_path, &root_level)) {\n        return NULL;\n    }\n    if (ctx->level > root_level) {\n        if ((out_level->bytes_ctx.output.buffer_ptr =\n                     add_buffered_entry(ctx, type, length))) {\n            out_level->bytes_ctx.vtable = &BUFFERED_BYTES_VTABLE;\n            out_level->bytes_ctx.bytes_left = length;\n            return (anjay_unlocked_ret_bytes_ctx_t *) &out_level->bytes_ctx;\n        }\n    } else {\n        int retval =\n                write_header(ctx->stream, type, out_level->next_id, length);\n        out_level->next_id = ANJAY_ID_INVALID;\n        if (!retval) {\n            out_level->bytes_ctx.vtable = &STREAMED_BYTES_VTABLE;\n            out_level->bytes_ctx.output.stream = ctx->stream;\n            out_level->bytes_ctx.bytes_left = length;\n            return (anjay_unlocked_ret_bytes_ctx_t *) &out_level->bytes_ctx;\n        }\n    }\n    return NULL;\n}\n\nstatic int tlv_ret_bytes(anjay_unlocked_output_ctx_t *ctx_,\n                         size_t length,\n                         anjay_unlocked_ret_bytes_ctx_t **out_bytes_ctx) {\n    tlv_out_t *ctx = (tlv_out_t *) ctx_;\n    tlv_id_type_t current_level_value_type;\n    int result = get_current_level_value_type(ctx, &current_level_value_type);\n    if (!result) {\n        *out_bytes_ctx = add_entry(ctx, current_level_value_type, length);\n        if (!*out_bytes_ctx) {\n            result = -1;\n        }\n    }\n    return result;\n}\n\nstatic int tlv_ret_string(anjay_unlocked_output_ctx_t *ctx, const char *value) {\n    return _anjay_ret_bytes_unlocked(ctx, value, strlen(value));\n}\n\n#    define DEF_IRET(Half, Bits)                                     \\\n        static int tlv_ret_i##Bits(anjay_unlocked_output_ctx_t *ctx, \\\n                                   int##Bits##_t value) {            \\\n            if (value == (int##Half##_t) value) {                    \\\n                return tlv_ret_i##Half(ctx, (int##Half##_t) value);  \\\n            }                                                        \\\n            uint##Bits##_t portable =                                \\\n                    avs_convert_be##Bits((uint##Bits##_t) value);    \\\n            return _anjay_ret_bytes_unlocked(ctx, &portable,         \\\n                                             sizeof(portable));      \\\n        }\n\nstatic int tlv_ret_i8(anjay_unlocked_output_ctx_t *ctx, int8_t value) {\n    return _anjay_ret_bytes_unlocked(ctx, &value, 1);\n}\n\nDEF_IRET(8, 16)\nDEF_IRET(16, 32)\nDEF_IRET(32, 64)\n\n#    ifdef ANJAY_WITH_LWM2M11\n#        define DEF_URET(Half, Bits)                                     \\\n            static int tlv_ret_u##Bits(anjay_unlocked_output_ctx_t *ctx, \\\n                                       uint##Bits##_t value) {           \\\n                if (value == (uint##Half##_t) value) {                   \\\n                    return tlv_ret_u##Half(ctx, (uint##Half##_t) value); \\\n                }                                                        \\\n                uint##Bits##_t portable =                                \\\n                        avs_convert_be##Bits((uint##Bits##_t) value);    \\\n                return _anjay_ret_bytes_unlocked(ctx, &portable,         \\\n                                                 sizeof(portable));      \\\n            }\n\nstatic int tlv_ret_u8(anjay_unlocked_output_ctx_t *ctx, uint8_t value) {\n    return _anjay_ret_bytes_unlocked(ctx, &value, 1);\n}\n\nDEF_URET(8, 16)\nDEF_URET(16, 32)\nDEF_URET(32, 64)\n#    endif // ANJAY_WITH_LWM2M11\n\nstatic int tlv_ret_float(anjay_unlocked_output_ctx_t *ctx, float value) {\n    uint32_t portable = avs_htonf(value);\n    return _anjay_ret_bytes_unlocked(ctx, &portable, sizeof(portable));\n}\n\nstatic int tlv_ret_double(anjay_unlocked_output_ctx_t *ctx, double value) {\n    if (((double) ((float) value)) == value) {\n        return tlv_ret_float(ctx, (float) value);\n    } else {\n        uint64_t portable = avs_htond(value);\n        return _anjay_ret_bytes_unlocked(ctx, &portable, sizeof(portable));\n    }\n}\n\nstatic int tlv_ret_bool(anjay_unlocked_output_ctx_t *ctx, bool value) {\n    return tlv_ret_i8(ctx, value);\n}\n\nstatic int tlv_ret_objlnk(anjay_unlocked_output_ctx_t *ctx,\n                          anjay_oid_t oid,\n                          anjay_iid_t iid) {\n    uint32_t portable =\n            avs_convert_be32(((uint32_t) oid << 16) | (uint32_t) iid);\n    return _anjay_ret_bytes_unlocked(ctx, &portable, sizeof(portable));\n}\n\nstatic void tlv_slave_start(tlv_out_t *ctx);\n\nstatic int tlv_slave_finish(tlv_out_t *ctx) {\n    tlv_out_level_id_t root_level;\n    if (get_root_level(&ctx->root_path, &root_level)\n            || ctx->level <= root_level) {\n        AVS_UNREACHABLE(\"Already at root level of TLV structure\");\n        return -1;\n    }\n    size_t data_size = 0;\n    {\n        tlv_entry_t *entry = NULL;\n        AVS_LIST_FOREACH(entry, current_level(ctx)->entries) {\n            data_size += header_size(entry->id.id, entry->data_length)\n                         + entry->data_length;\n        }\n    }\n    char *buffer = (char *) (data_size ? avs_malloc(data_size) : NULL);\n    int retval = ((!data_size || buffer) ? 0 : -1);\n    avs_stream_outbuf_t outbuf = AVS_STREAM_OUTBUF_STATIC_INITIALIZER;\n    avs_stream_outbuf_set_buffer(&outbuf, buffer, data_size);\n    AVS_LIST_CLEAR(&current_level(ctx)->entries) {\n        if (!retval) {\n            retval = write_entry((avs_stream_t *) &outbuf,\n                                 &current_level(ctx)->entries->id,\n                                 current_level(ctx)->entries->data,\n                                 current_level(ctx)->entries->data_length);\n        }\n    }\n    ctx->level = (tlv_out_level_id_t) (ctx->level - 1);\n    if (!retval) {\n        size_t length = avs_stream_outbuf_offset(&outbuf);\n        anjay_unlocked_ret_bytes_ctx_t *bytes = NULL;\n        switch (ctx->level) {\n        case TLV_OUT_LEVEL_RID:\n            bytes = add_entry(ctx, TLV_ID_RID_ARRAY, length);\n            break;\n        case TLV_OUT_LEVEL_IID:\n            bytes = add_entry(ctx, TLV_ID_IID, length);\n            break;\n        default:;\n        }\n        retval = !bytes ? -1\n                        : _anjay_ret_bytes_append_unlocked(bytes, buffer,\n                                                           length);\n    }\n    avs_free(buffer);\n    return retval;\n}\n\nstatic int tlv_start_aggregate(anjay_unlocked_output_ctx_t *ctx_) {\n    tlv_out_t *ctx = (tlv_out_t *) ctx_;\n    if (ctx->level == TLV_OUT_LEVEL_RID) {\n        if (current_level(ctx)->next_id != ANJAY_ID_INVALID) {\n            // STARTING THE RESOURCE INSTANCE ARRAY\n            // We have been called after set_path() on a Resource path - hence\n            // the current level is RID and we have a valid next_id. We're\n            // starting aggregate on the Resource level, i.e., an array of\n            // Resource Instances - so we're starting the slave context that\n            // will expect Resource Instance entries, or serialize to an empty\n            // array if no Resource Instances will follow.\n            tlv_slave_start(ctx);\n        } else {\n            AVS_ASSERT(_anjay_uri_path_leaf_is(&ctx->root_path, ANJAY_ID_IID),\n                       \"Called tlv_start_aggregate in inappropriate state\");\n            // INSTANCE IS THE ROOT\n            // This case will happen if the TLV context is rooted at the\n            // Instance level, i.e., we're responding to a Read with URI\n            // pointing to an Object Instance. In that case, the TLV context is\n            // configured so that Resource entities are serialized at the top\n            // level (hence the top level is RID, because we cannot serialize\n            // anything above it, but ID is not set yet), so there is nothing to\n            // do to \"start the aggregate\", we are already the aggregate we are\n            // looking for. read_instance() calls start_aggregate() before\n            // iterating over resources, so to make it work, we just return\n            // success.\n        }\n    } else if (ctx->level == TLV_OUT_LEVEL_IID) {\n        assert(current_level(ctx)->next_id != ANJAY_ID_INVALID);\n        // STARTING THE OBJECT INSTANCE\n        // We have been called after set_path() on an Object Instance path -\n        // hence the current level is IID and we have a valid next_id. We're\n        // starting aggregate on the Instance level, i.e. an array of Resources\n        // - so we're starting the slave context that will expect Resource\n        // entries, or serialize to an empty array if no Resources will follow.\n        tlv_slave_start(ctx);\n    } else {\n        AVS_UNREACHABLE(\"tlv_start_aggregate called in invalid state\");\n        return -1;\n    }\n    return 0;\n}\n\nstatic inline int get_leaf_level(const anjay_uri_path_t *path,\n                                 tlv_out_level_id_t *out) {\n    size_t path_length = _anjay_uri_path_length(path);\n    switch (path_length) {\n    case 2: // instance path (OID, IID)\n        *out = TLV_OUT_LEVEL_IID;\n        return 0;\n    case 3: // resource path (OID, IID, RID)\n        *out = TLV_OUT_LEVEL_RID;\n        return 0;\n    case 4: // resource instance level (OID, IID, RID, RIID)\n        *out = TLV_OUT_LEVEL_RIID;\n        return 0;\n    default:\n        AVS_UNREACHABLE(\"Invalid target path\");\n        return -1;\n    }\n}\n\nstatic inline int get_id_from_path(const anjay_uri_path_t *path,\n                                   tlv_out_level_id_t level,\n                                   uint16_t *out_id) {\n    switch (level) {\n    case TLV_OUT_LEVEL_IID:\n        assert(_anjay_uri_path_has(path, ANJAY_ID_IID));\n        *out_id = path->ids[ANJAY_ID_IID];\n        return 0;\n    case TLV_OUT_LEVEL_RID:\n        assert(_anjay_uri_path_has(path, ANJAY_ID_RID));\n        *out_id = path->ids[ANJAY_ID_RID];\n        return 0;\n    case TLV_OUT_LEVEL_RIID:\n        assert(_anjay_uri_path_has(path, ANJAY_ID_RIID));\n        *out_id = path->ids[ANJAY_ID_RIID];\n        return 0;\n    default:\n        AVS_UNREACHABLE(\"Invalid level\");\n        return -1;\n    }\n}\n\nstatic int tlv_set_path(anjay_unlocked_output_ctx_t *ctx_,\n                        const anjay_uri_path_t *path) {\n    tlv_out_t *ctx = (tlv_out_t *) ctx_;\n    assert(path);\n    AVS_ASSERT(!_anjay_uri_path_outside_base(path, &ctx->root_path),\n               \"Attempted to set path outside the context's root path. \"\n               \"This is a bug in resource reading logic.\");\n\n    tlv_out_level_id_t lowest_level;\n    tlv_out_level_id_t new_level;\n    if (get_root_level(&ctx->root_path, &lowest_level)\n            || get_leaf_level(path, &new_level)\n            || (new_level >= lowest_level\n                && current_level(ctx)->next_id != ANJAY_ID_INVALID)) {\n        // path already set\n        return -1;\n    }\n\n    // note that when the root path is an IID path,\n    // lowest_level == TLV_OUT_LEVEL_RID. That's because the lowest level\n    // entities we're serializing are Resources. However, read_instance()\n    // initially calls set_path() with an IID path, which causes new_level to be\n    // lower than lowest_level. _anjay_uri_path_outside_base() call above makes\n    // sure that we're not escaping the root, so we handle that just by\n    // returning to the lowest level and not setting the ID.\n    int result = 0;\n    tlv_out_level_id_t finish_level = AVS_MAX(new_level, lowest_level);\n    for (int i = lowest_level; i < (int) finish_level; ++i) {\n        uint16_t id;\n        if ((result = get_id_from_path(path, (tlv_out_level_id_t) i, &id))) {\n            return result;\n        }\n        if (ctx->levels[i].next_id != id) {\n            finish_level = (tlv_out_level_id_t) i;\n            break;\n        }\n    }\n\n    while (ctx->level > finish_level) {\n        if ((result = tlv_slave_finish(ctx))) {\n            return result;\n        }\n    }\n    for (int i = ctx->level; i < (int) new_level; ++i) {\n        if ((result = get_id_from_path(path, (tlv_out_level_id_t) i,\n                                       &ctx->levels[i].next_id))) {\n            return result;\n        }\n        tlv_slave_start(ctx);\n    }\n    assert(ctx->level == AVS_MAX(new_level, lowest_level));\n    if (new_level >= lowest_level) {\n        result = get_id_from_path(path, ctx->level,\n                                  &current_level(ctx)->next_id);\n    } else {\n        current_level(ctx)->next_id = ANJAY_ID_INVALID;\n    }\n    return result;\n}\n\nstatic int tlv_clear_path(anjay_unlocked_output_ctx_t *ctx_) {\n    tlv_out_t *ctx = (tlv_out_t *) ctx_;\n    uint16_t *next_id = &current_level(ctx)->next_id;\n    if (*next_id == ANJAY_ID_INVALID && ctx->level >= TLV_OUT_LEVEL_RID) {\n        return -1;\n    }\n    *next_id = ANJAY_ID_INVALID;\n    return 0;\n}\n\nstatic int tlv_output_close(anjay_unlocked_output_ctx_t *ctx_) {\n    tlv_out_t *ctx = (tlv_out_t *) ctx_;\n    int result = 0;\n    if (current_level(ctx)->next_id != ANJAY_ID_INVALID) {\n        // path set but value not returned\n        result = ANJAY_OUTCTXERR_ANJAY_RET_NOT_CALLED;\n    }\n    tlv_out_level_id_t root_level;\n    if (!get_root_level(&ctx->root_path, &root_level)) {\n        while (ctx->level > root_level) {\n            _anjay_update_ret(&result, tlv_slave_finish(ctx));\n        }\n    }\n    for (uint8_t i = 0; i < AVS_ARRAY_SIZE(ctx->levels); ++i) {\n        AVS_LIST_CLEAR(&ctx->levels[i].entries);\n    }\n    return result;\n}\n\nstatic const anjay_output_ctx_vtable_t TLV_OUT_VTABLE = {\n    .bytes_begin = tlv_ret_bytes,\n    .string = tlv_ret_string,\n    .integer = tlv_ret_i64,\n#    ifdef ANJAY_WITH_LWM2M11\n    .uint = tlv_ret_u64,\n#    endif // ANJAY_WITH_LWM2M11\n    .floating = tlv_ret_double,\n    .boolean = tlv_ret_bool,\n    .objlnk = tlv_ret_objlnk,\n    .start_aggregate = tlv_start_aggregate,\n    .set_path = tlv_set_path,\n    .clear_path = tlv_clear_path,\n    .close = tlv_output_close\n};\n\nstatic void tlv_slave_start(tlv_out_t *ctx) {\n    assert((size_t) (ctx->level + 1) <= AVS_ARRAY_SIZE(ctx->levels));\n    ctx->level = (tlv_out_level_id_t) (ctx->level + 1);\n    assert(!current_level(ctx)->entries);\n    current_level(ctx)->next_entry_ptr = &current_level(ctx)->entries;\n    current_level(ctx)->next_id = ANJAY_ID_INVALID;\n}\n\nanjay_unlocked_output_ctx_t *\n_anjay_output_tlv_create(avs_stream_t *stream, const anjay_uri_path_t *uri) {\n    assert(_anjay_uri_path_has(uri, ANJAY_ID_OID));\n    tlv_out_t *ctx = (tlv_out_t *) avs_calloc(1, sizeof(tlv_out_t));\n    if (!ctx) {\n        return NULL;\n    }\n    if (get_root_level(uri, &ctx->level)) {\n        AVS_UNREACHABLE(\"Invalid URI\");\n        avs_free(ctx);\n        return NULL;\n    }\n    ctx->base.vtable = &TLV_OUT_VTABLE;\n    ctx->stream = stream;\n    ctx->root_path = *uri;\n    current_level(ctx)->next_entry_ptr = &current_level(ctx)->entries;\n    current_level(ctx)->next_id = ANJAY_ID_INVALID;\n    return (anjay_unlocked_output_ctx_t *) ctx;\n}\n\n#    ifdef ANJAY_TEST\n#        include \"tests/core/io/tlv_out.c\"\n#    endif\n\n#endif // ANJAY_WITHOUT_TLV\n"
  },
  {
    "path": "src/core/io/anjay_vtable.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ANJAY_IO_VTABLE_H\n#define ANJAY_IO_VTABLE_H\n\n#include <anjay/core.h>\n\n#include \"../anjay_io_core.h\"\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\ntypedef void (*anjay_dm_list_ctx_emit_t)(anjay_unlocked_dm_list_ctx_t *,\n                                         uint16_t id);\n\ntypedef struct {\n    anjay_dm_list_ctx_emit_t emit;\n} anjay_dm_list_ctx_vtable_t;\n\ntypedef int (*anjay_output_ctx_bytes_begin_t)(\n        anjay_unlocked_output_ctx_t *,\n        size_t,\n        anjay_unlocked_ret_bytes_ctx_t **);\ntypedef int (*anjay_output_ctx_string_t)(anjay_unlocked_output_ctx_t *,\n                                         const char *);\ntypedef int (*anjay_output_ctx_integer_t)(anjay_unlocked_output_ctx_t *,\n                                          int64_t);\n#ifdef ANJAY_WITH_LWM2M11\ntypedef int (*anjay_output_ctx_uint_t)(anjay_unlocked_output_ctx_t *, uint64_t);\n#endif // ANJAY_WITH_LWM2M11\ntypedef int (*anjay_output_ctx_floating_t)(anjay_unlocked_output_ctx_t *,\n                                           double);\ntypedef int (*anjay_output_ctx_boolean_t)(anjay_unlocked_output_ctx_t *, bool);\ntypedef int (*anjay_output_ctx_objlnk_t)(anjay_unlocked_output_ctx_t *,\n                                         anjay_oid_t,\n                                         anjay_iid_t);\n#if defined(ANJAY_WITH_SECURITY_STRUCTURED)\ntypedef int (*anjay_output_ctx_security_info_t)(\n        anjay_unlocked_output_ctx_t *,\n        const avs_crypto_security_info_union_t *);\n#endif /* defined(ANJAY_WITH_SECURITY_STRUCTURED) || \\\n          defined(ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT) */\ntypedef int (*anjay_output_ctx_start_aggregate_t)(\n        anjay_unlocked_output_ctx_t *);\ntypedef int (*anjay_output_ctx_set_path_t)(anjay_unlocked_output_ctx_t *,\n                                           const anjay_uri_path_t *);\ntypedef int (*anjay_output_ctx_clear_path_t)(anjay_unlocked_output_ctx_t *);\ntypedef int (*anjay_output_ctx_set_time_t)(anjay_unlocked_output_ctx_t *,\n                                           double);\ntypedef int (*anjay_output_ctx_close_t)(anjay_unlocked_output_ctx_t *);\n\nstruct anjay_output_ctx_vtable_struct {\n    anjay_output_ctx_bytes_begin_t bytes_begin;\n    anjay_output_ctx_string_t string;\n    anjay_output_ctx_integer_t integer;\n#ifdef ANJAY_WITH_LWM2M11\n    anjay_output_ctx_uint_t uint;\n#endif // ANJAY_WITH_LWM2M11\n    anjay_output_ctx_floating_t floating;\n    anjay_output_ctx_boolean_t boolean;\n    anjay_output_ctx_objlnk_t objlnk;\n#if defined(ANJAY_WITH_SECURITY_STRUCTURED)\n    anjay_output_ctx_security_info_t security_info;\n#endif /* defined(ANJAY_WITH_SECURITY_STRUCTURED) || \\\n          defined(ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT) */\n    anjay_output_ctx_start_aggregate_t start_aggregate;\n    anjay_output_ctx_set_path_t set_path;\n    anjay_output_ctx_clear_path_t clear_path;\n    anjay_output_ctx_set_time_t set_time;\n    anjay_output_ctx_close_t close;\n};\n\ntypedef int (*anjay_ret_bytes_ctx_append_t)(anjay_unlocked_ret_bytes_ctx_t *,\n                                            const void *,\n                                            size_t);\n\ntypedef struct {\n    anjay_ret_bytes_ctx_append_t append;\n} anjay_ret_bytes_ctx_vtable_t;\n\ntypedef int (*anjay_input_ctx_bytes_t)(\n        anjay_unlocked_input_ctx_t *, size_t *, bool *, void *, size_t);\ntypedef int (*anjay_input_ctx_string_t)(anjay_unlocked_input_ctx_t *,\n                                        char *,\n                                        size_t);\ntypedef int (*anjay_input_ctx_integer_t)(anjay_unlocked_input_ctx_t *,\n                                         int64_t *);\n#ifdef ANJAY_WITH_LWM2M11\ntypedef int (*anjay_input_ctx_uint_t)(anjay_unlocked_input_ctx_t *, uint64_t *);\n#endif // ANJAY_WITH_LWM2M11\ntypedef int (*anjay_input_ctx_floating_t)(anjay_unlocked_input_ctx_t *,\n                                          double *);\ntypedef int (*anjay_input_ctx_boolean_t)(anjay_unlocked_input_ctx_t *, bool *);\ntypedef int (*anjay_input_ctx_objlnk_t)(anjay_unlocked_input_ctx_t *,\n                                        anjay_oid_t *,\n                                        anjay_iid_t *);\n#ifdef ANJAY_WITH_LWM2M12\ntypedef int (*anjay_input_ctx_null_t)(anjay_unlocked_input_ctx_t *);\n#endif // ANJAY_WITH_LWM2M12\ntypedef int (*anjay_input_ctx_next_entry_t)(anjay_unlocked_input_ctx_t *);\ntypedef int (*anjay_input_ctx_close_t)(anjay_unlocked_input_ctx_t *);\n\ntypedef int (*anjay_input_ctx_get_path_t)(anjay_unlocked_input_ctx_t *,\n                                          anjay_uri_path_t *,\n                                          bool *);\ntypedef int (*anjay_input_ctx_update_root_path_t)(anjay_unlocked_input_ctx_t *,\n                                                  const anjay_uri_path_t *);\n\nstruct anjay_input_ctx_vtable_struct {\n    anjay_input_ctx_bytes_t some_bytes;\n    anjay_input_ctx_string_t string;\n    anjay_input_ctx_integer_t integer;\n#ifdef ANJAY_WITH_LWM2M11\n    anjay_input_ctx_uint_t uint;\n#endif // ANJAY_WITH_LWM2M11\n    anjay_input_ctx_floating_t floating;\n    anjay_input_ctx_boolean_t boolean;\n    anjay_input_ctx_objlnk_t objlnk;\n#ifdef ANJAY_WITH_LWM2M12\n    anjay_input_ctx_null_t null;\n#endif // ANJAY_WITH_LWM2M12\n    anjay_input_ctx_get_path_t get_path;\n    anjay_input_ctx_next_entry_t next_entry;\n    anjay_input_ctx_update_root_path_t update_root_path;\n    anjay_input_ctx_close_t close;\n};\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif /* ANJAY_IO_VTABLE_H */\n"
  },
  {
    "path": "src/core/io/cbor/anjay_cbor_encoder_ll.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#ifdef ANJAY_WITH_CBOR\n\n#    include <assert.h>\n#    include <math.h>\n#    include <string.h>\n\n#    include <avsystem/commons/avs_memory.h>\n#    include <avsystem/commons/avs_stream.h>\n#    include <avsystem/commons/avs_stream_membuf.h>\n#    include <avsystem/commons/avs_utils.h>\n\n#    include \"../anjay_common.h\"\n#    include \"../anjay_senml_like_encoder_vtable.h\"\n\n#    include \"anjay_cbor_encoder_ll.h\"\n#    include \"anjay_cbor_types.h\"\n\nVISIBILITY_SOURCE_BEGIN\n\ntypedef struct cbor_encoder_struct {\n    const anjay_senml_like_encoder_vtable_t *vtable;\n} cbor_encoder_t;\n\nstatic inline int write_cbor_header(avs_stream_t *stream,\n                                    cbor_major_type_t major_type,\n                                    uint8_t value) {\n    assert(value < 32);\n    uint8_t header = (uint8_t) ((((uint8_t) major_type) << 5) | value);\n    return avs_is_ok(avs_stream_write(stream, &header, 1)) ? 0 : -1;\n}\n\nstatic int encode_type_and_number(avs_stream_t *stream,\n                                  cbor_major_type_t major_type,\n                                  uint64_t value) {\n    if (value < 24) {\n        return write_cbor_header(stream, major_type, (uint8_t) value);\n    } else if (value <= UINT8_MAX) {\n        uint8_t portable = (uint8_t) value;\n        if (write_cbor_header(stream, major_type, CBOR_EXT_LENGTH_1BYTE)\n                || avs_is_err(avs_stream_write(stream, &portable,\n                                               sizeof(portable)))) {\n            return -1;\n        }\n    } else if (value <= UINT16_MAX) {\n        uint16_t portable = avs_convert_be16((uint16_t) value);\n        if (write_cbor_header(stream, major_type, CBOR_EXT_LENGTH_2BYTE)\n                || avs_is_err(avs_stream_write(stream, &portable,\n                                               sizeof(portable)))) {\n            return -1;\n        }\n    } else if (value <= UINT32_MAX) {\n        uint32_t portable = avs_convert_be32((uint32_t) value);\n        if (write_cbor_header(stream, major_type, CBOR_EXT_LENGTH_4BYTE)\n                || avs_is_err(avs_stream_write(stream, &portable,\n                                               sizeof(portable)))) {\n            return -1;\n        }\n    } else {\n        uint64_t portable = avs_convert_be64((uint64_t) value);\n        if (write_cbor_header(stream, major_type, CBOR_EXT_LENGTH_8BYTE)\n                || avs_is_err(avs_stream_write(stream, &portable,\n                                               sizeof(portable)))) {\n            return -1;\n        }\n    }\n    return 0;\n}\n\nint _anjay_cbor_ll_encode_uint(avs_stream_t *stream, uint64_t value) {\n    return encode_type_and_number(stream, CBOR_MAJOR_TYPE_UINT, value);\n}\n\nint _anjay_cbor_ll_encode_int(avs_stream_t *stream, int64_t value) {\n    if (value >= 0) {\n        return _anjay_cbor_ll_encode_uint(stream, (uint64_t) value);\n    }\n\n    value = -(value + 1);\n    return encode_type_and_number(stream, CBOR_MAJOR_TYPE_NEGATIVE_INT,\n                                  (uint64_t) value);\n}\n\nint _anjay_cbor_ll_encode_bool(avs_stream_t *stream, bool value) {\n    return write_cbor_header(stream, CBOR_MAJOR_TYPE_FLOAT_OR_SIMPLE_VALUE,\n                             value ? CBOR_VALUE_BOOL_TRUE\n                                   : CBOR_VALUE_BOOL_FALSE);\n}\n\nint _anjay_cbor_ll_encode_float(avs_stream_t *stream, float value) {\n    uint32_t portable = avs_htonf(value);\n    int retval = write_cbor_header(stream,\n                                   CBOR_MAJOR_TYPE_FLOAT_OR_SIMPLE_VALUE,\n                                   CBOR_EXT_LENGTH_4BYTE);\n    if (!retval\n            && avs_is_err(\n                       avs_stream_write(stream, &portable, sizeof(portable)))) {\n        retval = -1;\n    }\n    return retval;\n}\n\nint _anjay_cbor_ll_encode_double(avs_stream_t *stream, double value) {\n    if ((double) ((float) value) == value) {\n        // cast to float and back to double did not change the value, which\n        // menas it fits in the size and precision of float type\n        return _anjay_cbor_ll_encode_float(stream, (float) value);\n    }\n\n    uint64_t portable = avs_htond(value);\n    int retval = write_cbor_header(stream,\n                                   CBOR_MAJOR_TYPE_FLOAT_OR_SIMPLE_VALUE,\n                                   CBOR_EXT_LENGTH_8BYTE);\n    if (!retval\n            && avs_is_err(\n                       avs_stream_write(stream, &portable, sizeof(portable)))) {\n        retval = -1;\n    }\n    return retval;\n}\n\nint _anjay_cbor_ll_bytes_begin(avs_stream_t *stream, size_t size) {\n    return encode_type_and_number(stream, CBOR_MAJOR_TYPE_BYTE_STRING, size);\n}\n\nint _anjay_cbor_ll_encode_string(avs_stream_t *stream, const char *data) {\n    size_t size = strlen(data);\n    int retval =\n            encode_type_and_number(stream, CBOR_MAJOR_TYPE_TEXT_STRING, size);\n    if (!retval && avs_is_err(avs_stream_write(stream, data, size))) {\n        retval = -1;\n    }\n    return retval;\n}\n\nint _anjay_cbor_ll_definite_map_begin(avs_stream_t *stream,\n                                      size_t items_count) {\n    return encode_type_and_number(stream, CBOR_MAJOR_TYPE_MAP, items_count);\n}\n\nint _anjay_cbor_ll_definite_array_begin(avs_stream_t *stream,\n                                        size_t items_count) {\n    return encode_type_and_number(stream, CBOR_MAJOR_TYPE_ARRAY, items_count);\n}\n\n#    ifdef ANJAY_WITH_LWM2M12\nint _anjay_cbor_ll_indefinite_map_begin(avs_stream_t *stream) {\n    return write_cbor_header(stream, CBOR_MAJOR_TYPE_MAP,\n                             CBOR_EXT_LENGTH_INDEFINITE);\n}\n\nint _anjay_cbor_ll_indefinite_map_end(avs_stream_t *stream) {\n    const unsigned char break_char = CBOR_INDEFINITE_STRUCTURE_BREAK;\n    return avs_is_ok(avs_stream_write(stream, &break_char, sizeof(break_char)))\n                   ? 0\n                   : -1;\n}\n#    endif // ANJAY_WITH_LWM2M12\n\n#endif // ANJAY_WITH_CBOR\n"
  },
  {
    "path": "src/core/io/cbor/anjay_cbor_encoder_ll.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ANJAY_IO_CBOR_ENCODER_LL_H\n#define ANJAY_IO_CBOR_ENCODER_LL_H\n\n#include <avsystem/commons/avs_stream.h>\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\n/**\n * This is a stateless low-level CBOR encoder.\n *\n * User is responsible for ensuring, that all declared bytes or map elements\n * were written before encoding other value type.\n */\n\nint _anjay_cbor_ll_encode_uint(avs_stream_t *stream, uint64_t value);\n\nint _anjay_cbor_ll_encode_int(avs_stream_t *stream, int64_t value);\n\nint _anjay_cbor_ll_encode_bool(avs_stream_t *stream, bool value);\n\nint _anjay_cbor_ll_encode_float(avs_stream_t *stream, float value);\n\nint _anjay_cbor_ll_encode_double(avs_stream_t *stream, double value);\n\nint _anjay_cbor_ll_encode_string(avs_stream_t *stream, const char *data);\n\nint _anjay_cbor_ll_bytes_begin(avs_stream_t *stream, size_t size);\n\nstatic inline int _anjay_cbor_ll_bytes_append(avs_stream_t *stream,\n                                              const void *data,\n                                              size_t size) {\n    return avs_is_ok(avs_stream_write(stream, data, size)) ? 0 : -1;\n}\n\nint _anjay_cbor_ll_definite_map_begin(avs_stream_t *stream, size_t items_count);\n\nint _anjay_cbor_ll_definite_array_begin(avs_stream_t *stream,\n                                        size_t items_count);\n\n#ifdef ANJAY_WITH_LWM2M12\nint _anjay_cbor_ll_indefinite_map_begin(avs_stream_t *stream);\n\nint _anjay_cbor_ll_indefinite_map_end(avs_stream_t *stream);\n#endif // ANJAY_WITH_LWM2M12\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif // ANJAY_IO_CBOR_ENCODER_LL_H\n"
  },
  {
    "path": "src/core/io/cbor/anjay_cbor_types.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n#ifndef ANJAY_IO_CBOR_TYPES_H\n#define ANJAY_IO_CBOR_TYPES_H\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\n/* See \"2.1.  Major Types\" in RFC 7049 */\ntypedef enum {\n    _CBOR_MAJOR_TYPE_BEGIN = 0,\n    CBOR_MAJOR_TYPE_UINT = 0,\n    CBOR_MAJOR_TYPE_NEGATIVE_INT = 1,\n    CBOR_MAJOR_TYPE_BYTE_STRING = 2,\n    CBOR_MAJOR_TYPE_TEXT_STRING = 3,\n    CBOR_MAJOR_TYPE_ARRAY = 4,\n    CBOR_MAJOR_TYPE_MAP = 5,\n    CBOR_MAJOR_TYPE_TAG = 6,\n    CBOR_MAJOR_TYPE_FLOAT_OR_SIMPLE_VALUE = 7,\n    _CBOR_MAJOR_TYPE_END\n} cbor_major_type_t;\n\ntypedef enum {\n    /**\n     * Section \"2.  Specification of the CBOR Encoding\":\n     *\n     * > When it [5 lower bits of major type] is 24 to 27, the additional\n     * > bytes for a variable-length integer immediately follow; the values 24\n     * > to 27 of the additional information specify that its length is a 1-,\n     * > 2-, 4-, or 8-byte unsigned integer, respectively.\n     *\n     * > Additional informationvalue 31 is used for indefinite-length items,\n     * > described in Section 2.2.  Additional information values 28 to 30 are\n     * > reserved for future expansion.\n     */\n    CBOR_EXT_LENGTH_1BYTE = 24,\n    CBOR_EXT_LENGTH_2BYTE = 25,\n    CBOR_EXT_LENGTH_4BYTE = 26,\n    CBOR_EXT_LENGTH_8BYTE = 27,\n    CBOR_EXT_LENGTH_INDEFINITE = 31\n} cbor_ext_length_t;\n\n/**\n * This enum is for:\n *\n * > Major type 7:  floating-point numbers and simple data types that need\n * > no content, as well as the \"break\" stop code.  See Section 2.3.\n */\ntypedef enum {\n    /* See \"2.3.  Floating-Point Numbers and Values with No Content\" */\n    CBOR_VALUE_BOOL_FALSE = 20,\n    CBOR_VALUE_BOOL_TRUE = 21,\n    CBOR_VALUE_NULL = 22,\n    CBOR_VALUE_UNDEFINED = 23,\n    CBOR_VALUE_IN_NEXT_BYTE = CBOR_EXT_LENGTH_1BYTE,\n    CBOR_VALUE_FLOAT_16 = CBOR_EXT_LENGTH_2BYTE,\n    CBOR_VALUE_FLOAT_32 = CBOR_EXT_LENGTH_4BYTE,\n    CBOR_VALUE_FLOAT_64 = CBOR_EXT_LENGTH_8BYTE\n} cbor_primitive_value_t;\n\n#define CBOR_INDEFINITE_STRUCTURE_BREAK 0xFF\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif /* ANJAY_IO_CBOR_TYPES_H */\n"
  },
  {
    "path": "src/core/io/cbor/anjay_json_like_cbor_decoder.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#ifdef ANJAY_WITH_CBOR\n\n#    include <assert.h>\n#    include <stdbool.h>\n#    include <string.h>\n\n#    include \"anjay_cbor_types.h\"\n#    include \"anjay_json_like_cbor_decoder.h\"\n\n#    include \"../anjay_json_like_decoder_vtable.h\"\n\n#    include <avsystem/commons/avs_memory.h>\n#    include <avsystem/commons/avs_stream_membuf.h>\n#    include <avsystem/commons/avs_utils.h>\n\n#    include <anjay_modules/anjay_io_utils.h>\n#    include <anjay_modules/anjay_utils_core.h>\n\nVISIBILITY_SOURCE_BEGIN\n\n#    define LOG(...) _anjay_log(cbor, __VA_ARGS__)\n\n#    define NUM_ITEMS_INDEFINITE (-1)\n\ntypedef struct {\n    /* Type of the nested structure (ANJAY_CBOR_VALUE_ARRAY or\n     * ANJAY_CBOR_VALUE_MAP). */\n    anjay_json_like_value_type_t type;\n    /* Number of items of the entry that were parsed */\n    size_t items_parsed;\n    /* Number of all items to be parsed (in case of definite length), or\n     * NUM_ITEMS_INDEFINITE. */\n    ptrdiff_t all_items;\n} cbor_nested_state_t;\n\nstatic bool is_indefinite(cbor_nested_state_t *state) {\n    return state->all_items == NUM_ITEMS_INDEFINITE;\n}\n\ntypedef struct {\n    const anjay_json_like_decoder_vtable_t *vtable;\n    avs_stream_t *stream;\n    anjay_json_like_decoder_state_t state;\n    /**\n     * This structure contains information about currently processed value. The\n     * value is \"processed\" as long as it is not fully consumed, so for example,\n     * the current_item::value_type is of type \"bytes\" until it gets read\n     * entirely by the user.\n     */\n    struct {\n        /* Type to be decoded or currently being decoded. */\n        anjay_json_like_value_type_t value_type;\n\n        /* A value corresponding to one of cbor_major_type_t enum values. */\n        cbor_major_type_t major_type;\n        /**\n         * Additional (decoded) info, which may be:\n         *  - extended length size,\n         *  - short value.\n         */\n        int additional_info;\n    } current_item;\n\n    size_t nest_stack_size;\n    size_t max_nest_stack_size;\n    /**\n     * A stack of recently entered nested types (e.g. arrays/maps). The type\n     * lands on a nest_stack, if one of the following functions is called:\n     *  - _anjay_cbor_decoder_enter_array(),\n     *  - _anjay_cbor_decoder_enter_map().\n     *\n     * The last element (if any) indicates what kind of recursive structure we\n     * are currently parsing. If too many nest levels are found, the parser\n     * exits with error.\n     */\n    cbor_nested_state_t nest_stack[];\n} anjay_cbor_decoder_t;\n\ntypedef enum { CBOR_DECODER_TAG_DECIMAL_FRACTION = 4 } cbor_decoder_tag_t;\n\nstatic int parse_major_type(const uint8_t initial_byte) {\n    return initial_byte >> 5;\n}\n\nstatic int parse_additional_info(const uint8_t initial_byte) {\n    return initial_byte & 0x1f;\n}\n\nstatic int parse_ext_length_size(const anjay_cbor_decoder_t *ctx,\n                                 size_t *out_ext_len_size) {\n    switch (ctx->current_item.additional_info) {\n    case CBOR_EXT_LENGTH_1BYTE:\n        *out_ext_len_size = 1;\n        break;\n    case CBOR_EXT_LENGTH_2BYTE:\n        *out_ext_len_size = 2;\n        break;\n    case CBOR_EXT_LENGTH_4BYTE:\n        *out_ext_len_size = 4;\n        break;\n    case CBOR_EXT_LENGTH_8BYTE:\n        *out_ext_len_size = 8;\n        break;\n    default:\n        LOG(DEBUG,\n            _(\"unexpected extended length value: \") \"%d\",\n            (int) ctx->current_item.additional_info);\n        return -1;\n    }\n    return 0;\n}\n\nstatic bool is_length_extended(const anjay_cbor_decoder_t *ctx) {\n    switch (ctx->current_item.additional_info) {\n    case CBOR_EXT_LENGTH_1BYTE:\n    case CBOR_EXT_LENGTH_2BYTE:\n    case CBOR_EXT_LENGTH_4BYTE:\n    case CBOR_EXT_LENGTH_8BYTE:\n        return true;\n    default:\n        return false;\n    }\n}\n\nstatic void parse_float_or_simple_value(anjay_cbor_decoder_t *ctx) {\n    assert(ctx->current_item.major_type\n           == CBOR_MAJOR_TYPE_FLOAT_OR_SIMPLE_VALUE);\n\n    /* See \"2.3.  Floating-Point Numbers and Values with No Content\" */\n    switch (ctx->current_item.additional_info) {\n    case CBOR_VALUE_BOOL_FALSE:\n    case CBOR_VALUE_BOOL_TRUE:\n        ctx->current_item.value_type = ANJAY_JSON_LIKE_VALUE_BOOL;\n        break;\n    case CBOR_VALUE_NULL:\n        ctx->current_item.value_type = ANJAY_JSON_LIKE_VALUE_NULL;\n        break;\n    case CBOR_VALUE_FLOAT_16:\n    case CBOR_VALUE_FLOAT_32:\n        ctx->current_item.value_type = ANJAY_JSON_LIKE_VALUE_FLOAT;\n        break;\n    case CBOR_VALUE_FLOAT_64:\n        ctx->current_item.value_type = ANJAY_JSON_LIKE_VALUE_DOUBLE;\n        break;\n    case CBOR_VALUE_UNDEFINED:\n        LOG(DEBUG, _(\"unsupported simple type undefined\"));\n        goto error;\n    case CBOR_VALUE_IN_NEXT_BYTE:\n        /* As per \"Table 2: Simple Values\", range 32..255 is unassigned, so\n         * we may call it an error. */\n    default:\n        LOG(DEBUG,\n            _(\"unsupported simple type \") \"%d\",\n            ctx->current_item.additional_info);\n        goto error;\n    }\n    return;\n\nerror:\n    ctx->state = ANJAY_JSON_LIKE_DECODER_STATE_ERROR;\n}\n\nstatic void ignore_tag(anjay_cbor_decoder_t *ctx) {\n    assert(ctx->current_item.major_type == CBOR_MAJOR_TYPE_TAG);\n    assert(ctx->current_item.additional_info\n           != CBOR_DECODER_TAG_DECIMAL_FRACTION);\n    size_t ext_len_size = 0;\n    uint64_t ignored;\n    if (is_length_extended(ctx)) {\n        if (parse_ext_length_size(ctx, &ext_len_size)\n                || avs_is_err(avs_stream_read_reliably(\n                           ctx->stream, &ignored, ext_len_size))) {\n            ctx->state = ANJAY_JSON_LIKE_DECODER_STATE_ERROR;\n        }\n    }\n    (void) ignored;\n}\n\nstatic int nested_state_push(anjay_cbor_decoder_t *ctx);\nstatic void nested_state_pop(anjay_cbor_decoder_t *ctx);\nstatic cbor_nested_state_t *nested_state_top(anjay_cbor_decoder_t *ctx) {\n    assert(ctx->nest_stack_size);\n    if (ctx->nest_stack_size) {\n        return &ctx->nest_stack[ctx->nest_stack_size - 1];\n    } else {\n        /* should not happen, but still */\n        return NULL;\n    }\n}\n\nstatic void preprocess_next_value(anjay_cbor_decoder_t *ctx) {\n    bool data_must_follow = false;\n\n    while (ctx->state == ANJAY_JSON_LIKE_DECODER_STATE_OK) {\n        uint8_t byte;\n        avs_error_t err = avs_stream_getch(ctx->stream, (char *) &byte, NULL);\n        if (avs_is_eof(err)) {\n            if (data_must_follow) {\n                ctx->state = ANJAY_JSON_LIKE_DECODER_STATE_ERROR;\n            } else {\n                ctx->state = ANJAY_JSON_LIKE_DECODER_STATE_FINISHED;\n            }\n            return;\n        } else if (avs_is_err(err)) {\n            ctx->state = ANJAY_JSON_LIKE_DECODER_STATE_ERROR;\n            return;\n        }\n\n        if (byte == CBOR_INDEFINITE_STRUCTURE_BREAK) {\n            /* end of the indefinite map, array or byte/text string */\n            if (!ctx->nest_stack_size\n                    || (nested_state_top(ctx)->type == ANJAY_JSON_LIKE_VALUE_MAP\n                        && nested_state_top(ctx)->items_parsed % 2)\n                    || !is_indefinite(nested_state_top(ctx))) {\n                ctx->state = ANJAY_JSON_LIKE_DECODER_STATE_ERROR;\n            } else {\n                nested_state_pop(ctx);\n            }\n            continue;\n        }\n        ctx->current_item.major_type =\n                (cbor_major_type_t) parse_major_type(byte);\n        ctx->current_item.additional_info = parse_additional_info(byte);\n\n        if (ctx->current_item.major_type < _CBOR_MAJOR_TYPE_BEGIN\n                || ctx->current_item.major_type >= _CBOR_MAJOR_TYPE_END) {\n            LOG(DEBUG,\n                _(\"invalid major type: \") \"%d\",\n                ctx->current_item.major_type);\n            ctx->state = ANJAY_JSON_LIKE_DECODER_STATE_ERROR;\n            return;\n        }\n        if (ctx->current_item.major_type == CBOR_MAJOR_TYPE_UINT) {\n            ctx->current_item.value_type = ANJAY_JSON_LIKE_VALUE_UINT;\n        } else if (ctx->current_item.major_type\n                   == CBOR_MAJOR_TYPE_NEGATIVE_INT) {\n            ctx->current_item.value_type = ANJAY_JSON_LIKE_VALUE_NEGATIVE_INT;\n        } else if (ctx->current_item.major_type\n                   == CBOR_MAJOR_TYPE_BYTE_STRING) {\n            ctx->current_item.value_type = ANJAY_JSON_LIKE_VALUE_BYTE_STRING;\n        } else if (ctx->current_item.major_type\n                   == CBOR_MAJOR_TYPE_TEXT_STRING) {\n            ctx->current_item.value_type = ANJAY_JSON_LIKE_VALUE_TEXT_STRING;\n        } else if (ctx->current_item.major_type == CBOR_MAJOR_TYPE_ARRAY) {\n            ctx->current_item.value_type = ANJAY_JSON_LIKE_VALUE_ARRAY;\n        } else if (ctx->current_item.major_type == CBOR_MAJOR_TYPE_MAP) {\n            ctx->current_item.value_type = ANJAY_JSON_LIKE_VALUE_MAP;\n        } else if (ctx->current_item.major_type\n                   == CBOR_MAJOR_TYPE_FLOAT_OR_SIMPLE_VALUE) {\n            parse_float_or_simple_value(ctx);\n        } else if (ctx->current_item.major_type == CBOR_MAJOR_TYPE_TAG) {\n            /**\n             * From section \"2.4.  Optional Tagging of Items\":\n             * > Decoders do not need to understand tags, and thus tags may be\n             * > of little value in applications where the implementation\n             * > creating a particular CBOR data item and the implementation\n             * > decoding that stream know the semantic meaning of each item in\n             * > the data flow.\n             * >\n             * > [...]\n             * >\n             * > Understanding the semantic tags is optional for a decoder; it\n             * > can just jump over the initial bytes of the tag and interpret\n             * > the tagged data item itself.\n             *\n             * Also:\n             * > The initial bytes of the tag follow the rules for positive\n             * > integers (major type 0).\n             *\n             * However, SenML specification, \"6.  CBOR Representation\n             * (application/senml+cbor)\" says:\n             *\n             * > The CBOR [RFC7049] representation is equivalent to the JSON\n             * > representation, with the following changes:\n             * >\n             * > o  For JSON Numbers, the CBOR representation can use integers,\n             * >  floating-point numbers, or decimal fractions (CBOR Tag 4);\n             *\n             * so, we are basically forced to support tag 4.\n             */\n            if (ctx->current_item.additional_info\n                    == CBOR_DECODER_TAG_DECIMAL_FRACTION) {\n                /**\n                 * The idea, of course, is to pack decoded decimal fraction into\n                 * double and just hope for the best -- there is no dedicated\n                 * type in LwM2M for decimal fractions.\n                 */\n                ctx->current_item.value_type = ANJAY_JSON_LIKE_VALUE_DOUBLE;\n            } else {\n                ignore_tag(ctx);\n                /* All tags must be followed with data, otherwise the CBOR\n                 * payload is malformed */\n                data_must_follow = true;\n                continue;\n            }\n        } else {\n            LOG(DEBUG,\n                _(\"unsupported major type \") \"%d\",\n                (int) ctx->current_item.major_type);\n            ctx->state = ANJAY_JSON_LIKE_DECODER_STATE_ERROR;\n        }\n        break;\n    }\n\n    if (ctx->state == ANJAY_JSON_LIKE_DECODER_STATE_ERROR) {\n        return;\n    }\n\n    while (ctx->nest_stack_size) {\n        cbor_nested_state_t *top = nested_state_top(ctx);\n        if (is_indefinite(top)) {\n            if (top->items_parsed == SIZE_MAX) {\n                ctx->state = ANJAY_JSON_LIKE_DECODER_STATE_ERROR;\n                LOG(DEBUG,\n                    _(\"number of items in indefinite map exceeded SIZE_MAX\"));\n            } else {\n                top->items_parsed++;\n            }\n            return;\n        } else if ((size_t) top->all_items - top->items_parsed) {\n            top->items_parsed++;\n            return;\n        } else {\n            nested_state_pop(ctx);\n        }\n    }\n}\n\nstatic int\ncbor_decoder_current_value_type(anjay_json_like_decoder_t *ctx_,\n                                anjay_json_like_value_type_t *out_type) {\n    anjay_cbor_decoder_t *ctx = (anjay_cbor_decoder_t *) ctx_;\n    if (ctx->state == ANJAY_JSON_LIKE_DECODER_STATE_OK) {\n        *out_type = ctx->current_item.value_type;\n        return 0;\n    }\n    return -1;\n}\n\nstatic int parse_uint(anjay_cbor_decoder_t *ctx, uint64_t *out_value) {\n    int retval = -1;\n    if (!is_length_extended(ctx)) {\n        *out_value = (uint64_t) ctx->current_item.additional_info;\n        return 0;\n    }\n\n    size_t ext_len_size;\n    if (parse_ext_length_size(ctx, &ext_len_size)) {\n        goto finish;\n    }\n    if (ext_len_size == 1) {\n        uint8_t u8;\n        if (avs_is_ok(avs_stream_read_reliably(ctx->stream, &u8, sizeof(u8)))) {\n            *out_value = u8;\n            retval = 0;\n        }\n    } else if (ext_len_size == 2) {\n        uint16_t u16;\n        if (avs_is_ok(\n                    avs_stream_read_reliably(ctx->stream, &u16, sizeof(u16)))) {\n            *out_value = avs_convert_be16(u16);\n            retval = 0;\n        }\n    } else if (ext_len_size == 4) {\n        uint32_t u32;\n        if (avs_is_ok(\n                    avs_stream_read_reliably(ctx->stream, &u32, sizeof(u32)))) {\n            *out_value = avs_convert_be32(u32);\n            retval = 0;\n        }\n    } else if (ext_len_size == 8) {\n        uint64_t u64;\n        if (avs_is_ok(\n                    avs_stream_read_reliably(ctx->stream, &u64, sizeof(u64)))) {\n            *out_value = avs_convert_be64(u64);\n            retval = 0;\n        }\n    } else {\n        AVS_UNREACHABLE(\"unsupported extended length size\");\n    }\nfinish:\n    if (retval) {\n        ctx->state = ANJAY_JSON_LIKE_DECODER_STATE_ERROR;\n    }\n    return retval;\n}\n\nstatic int parse_size(anjay_cbor_decoder_t *ctx, size_t *out_value) {\n    uint64_t u64;\n    if (parse_uint(ctx, &u64) || u64 > SIZE_MAX) {\n        return -1;\n    }\n    *out_value = (size_t) u64;\n    return 0;\n}\n\nstatic int parse_ptrdiff(anjay_cbor_decoder_t *ctx, ptrdiff_t *out_value) {\n    size_t size;\n    if (parse_size(ctx, &size) || size > SIZE_MAX / 2) {\n        return -1;\n    }\n    *out_value = (ptrdiff_t) size;\n    return 0;\n}\n\nstatic int nested_state_push(anjay_cbor_decoder_t *ctx) {\n    assert(ctx->state == ANJAY_JSON_LIKE_DECODER_STATE_OK);\n    assert(ctx->current_item.value_type == ANJAY_JSON_LIKE_VALUE_ARRAY\n           || ctx->current_item.value_type == ANJAY_JSON_LIKE_VALUE_MAP\n           || ((ctx->current_item.value_type\n                        == ANJAY_JSON_LIKE_VALUE_BYTE_STRING\n                || ctx->current_item.value_type\n                           == ANJAY_JSON_LIKE_VALUE_TEXT_STRING)\n               && ctx->current_item.additional_info\n                          == CBOR_EXT_LENGTH_INDEFINITE));\n\n    cbor_nested_state_t state = {\n        .type = ctx->current_item.value_type\n    };\n\n    if (ctx->nest_stack_size == ctx->max_nest_stack_size) {\n        LOG(DEBUG,\n            _(\"too many nested structures, the limit is: \") \"%u\",\n            (unsigned) ctx->max_nest_stack_size);\n        goto error;\n    }\n\n    switch (state.type) {\n    case ANJAY_JSON_LIKE_VALUE_ARRAY:\n        if (ctx->current_item.additional_info == CBOR_EXT_LENGTH_INDEFINITE) {\n            /* indefinite array */\n            state.all_items = NUM_ITEMS_INDEFINITE;\n        } else if (parse_ptrdiff(ctx, &state.all_items)) {\n            LOG(DEBUG, _(\"could not parse array length\"));\n            goto error;\n        }\n        break;\n    case ANJAY_JSON_LIKE_VALUE_MAP:\n        if (ctx->current_item.additional_info == CBOR_EXT_LENGTH_INDEFINITE) {\n            /* indefinite map */\n            state.all_items = NUM_ITEMS_INDEFINITE;\n        } else if (parse_ptrdiff(ctx, &state.all_items)\n                   || state.all_items > PTRDIFF_MAX / 2) {\n            LOG(DEBUG,\n                _(\"map length could not be parsed, or there is too many items \"\n                  \"in the map\"));\n            goto error;\n        } else {\n            /**\n             * A map contains (key, value) pairs, which, in effect doubles the\n             * number of expected entries.\n             */\n            state.all_items *= 2;\n        }\n        break;\n    case ANJAY_JSON_LIKE_VALUE_BYTE_STRING:\n    case ANJAY_JSON_LIKE_VALUE_TEXT_STRING:\n        state.all_items = NUM_ITEMS_INDEFINITE;\n        break;\n    default:\n        AVS_UNREACHABLE(\"this switch statement must be exhaustive\");\n        goto error;\n    }\n\n    ctx->nest_stack_size++;\n    *nested_state_top(ctx) = state;\n    return 0;\nerror:\n    ctx->state = ANJAY_JSON_LIKE_DECODER_STATE_ERROR;\n    return -1;\n}\n\nstatic void nested_state_pop(anjay_cbor_decoder_t *ctx) {\n    assert(is_indefinite(nested_state_top(ctx))\n           || ((size_t) nested_state_top(ctx)->all_items\n               - nested_state_top(ctx)->items_parsed)\n                      == 0);\n\n    ctx->nest_stack_size--;\n}\n\nstatic int decode_uint(anjay_cbor_decoder_t *ctx, uint64_t *out_value) {\n    if (ctx->state != ANJAY_JSON_LIKE_DECODER_STATE_OK\n            || ctx->current_item.value_type != ANJAY_JSON_LIKE_VALUE_UINT) {\n        return -1;\n    }\n    int retval = parse_uint(ctx, out_value);\n    preprocess_next_value(ctx);\n    return retval;\n}\n\nstatic int decode_negative_int(anjay_cbor_decoder_t *ctx, int64_t *out_value) {\n    if (ctx->state != ANJAY_JSON_LIKE_DECODER_STATE_OK\n            || ctx->current_item.value_type\n                           != ANJAY_JSON_LIKE_VALUE_NEGATIVE_INT) {\n        return -1;\n    }\n    uint64_t u64;\n    if (parse_uint(ctx, &u64)) {\n        return -1;\n    }\n    /* equivalent to if (u64 >= -INT64_MIN) */\n    if (u64 >= (uint64_t) INT64_MAX + 1) {\n        ctx->state = ANJAY_JSON_LIKE_DECODER_STATE_ERROR;\n        return -1;\n    }\n    *out_value = -(int64_t) u64 - INT64_C(1);\n    preprocess_next_value(ctx);\n    return 0;\n}\n\nstatic float decode_half_float(uint16_t half) {\n    /* Code adapted from https://tools.ietf.org/html/rfc7049#appendix-D */\n    const int exponent = (half >> 10) & 0x1f;\n    const int mantissa = half & 0x3ff;\n    float value;\n    if (exponent == 0) {\n        value = ldexpf((float) mantissa, -24);\n    } else if (exponent != 31) {\n        value = ldexpf((float) (mantissa + 1024), exponent - 25);\n    } else if (mantissa == 0) {\n        value = INFINITY;\n    } else {\n        value = NAN;\n    }\n    return (half & 0x8000) ? -value : value;\n}\n\nstatic int decode_float(anjay_cbor_decoder_t *ctx, float *out_value) {\n    if (ctx->state != ANJAY_JSON_LIKE_DECODER_STATE_OK\n            || ctx->current_item.value_type != ANJAY_JSON_LIKE_VALUE_FLOAT) {\n        return -1;\n    }\n    int result = -1;\n    if (ctx->current_item.additional_info == CBOR_VALUE_FLOAT_16) {\n        uint16_t value;\n        if (avs_is_ok(avs_stream_read_reliably(\n                    ctx->stream, &value, sizeof(value)))) {\n            *out_value = decode_half_float(avs_convert_be16(value));\n            result = 0;\n        }\n    } else {\n        assert(ctx->current_item.additional_info == CBOR_VALUE_FLOAT_32);\n        uint32_t value;\n        if (avs_is_ok(avs_stream_read_reliably(\n                    ctx->stream, &value, sizeof(value)))) {\n            *out_value = avs_ntohf(value);\n            result = 0;\n        }\n    }\n    if (result) {\n        ctx->state = ANJAY_JSON_LIKE_DECODER_STATE_ERROR;\n    } else {\n        preprocess_next_value(ctx);\n    }\n    return result;\n}\n\nstatic int reinterpret_integer_as_double(anjay_json_like_decoder_t *ctx,\n                                         double *out_value) {\n    anjay_json_like_number_t n;\n    if (_anjay_json_like_decoder_number(ctx, &n)) {\n        return -1;\n    }\n    if (n.type == ANJAY_JSON_LIKE_VALUE_UINT) {\n        *out_value = (double) n.value.u64;\n    } else if (n.type == ANJAY_JSON_LIKE_VALUE_NEGATIVE_INT) {\n        *out_value = (double) n.value.i64;\n    } else {\n        return -1;\n    }\n    return 0;\n}\n\nstatic int decode_decimal_fraction(anjay_cbor_decoder_t *ctx,\n                                   double *out_value) {\n    preprocess_next_value(ctx);\n    anjay_json_like_decoder_t *json_like_ctx =\n            (anjay_json_like_decoder_t *) ctx;\n    /**\n     * RFC7049 \"2.4.3.  Decimal Fractions and Bigfloats\":\n     *\n     * > A decimal fraction or a bigfloat is represented as a tagged array\n     * > that contains exactly two integer numbers: an exponent e and a\n     * > mantissa m.  Decimal fractions (tag 4) use base-10 exponents; the\n     * > value of a decimal fraction data item is m*(10**e).\n     */\n    size_t array_level =\n            _anjay_json_like_decoder_nesting_level(json_like_ctx) + 1;\n    if (_anjay_json_like_decoder_enter_array(json_like_ctx)) {\n        return -1;\n    }\n    double exponent;\n    double mantissa;\n    if (_anjay_json_like_decoder_nesting_level(json_like_ctx) != array_level\n            || reinterpret_integer_as_double(json_like_ctx, &exponent)\n            || _anjay_json_like_decoder_nesting_level(json_like_ctx)\n                           != array_level\n            || reinterpret_integer_as_double(json_like_ctx, &mantissa)\n            || _anjay_json_like_decoder_nesting_level(json_like_ctx)\n                           == array_level) {\n        return -1;\n    }\n    *out_value = mantissa * pow(10.0, exponent);\n    return 0;\n}\n\nstatic int decode_double(anjay_cbor_decoder_t *ctx, double *out_value) {\n    if (ctx->state != ANJAY_JSON_LIKE_DECODER_STATE_OK\n            || ctx->current_item.value_type != ANJAY_JSON_LIKE_VALUE_DOUBLE) {\n        return -1;\n    }\n    int result = -1;\n\n    /**\n     * NOTE: This if is safe, because decimal fraction tag (4) does not\n     * conflict with any kind of floating-point-type value. Also we wouldn't\n     * land in this function for non-floating-point types (as ensured by the\n     * if above).\n     */\n    if (ctx->current_item.additional_info\n            == CBOR_DECODER_TAG_DECIMAL_FRACTION) {\n        assert(ctx->current_item.major_type == CBOR_MAJOR_TYPE_TAG);\n        result = decode_decimal_fraction(ctx, out_value);\n    } else {\n        uint64_t value;\n        if (avs_is_ok(avs_stream_read_reliably(\n                    ctx->stream, &value, sizeof(value)))) {\n            *out_value = avs_ntohd(value);\n            result = 0;\n        }\n    }\n    if (result) {\n        ctx->state = ANJAY_JSON_LIKE_DECODER_STATE_ERROR;\n    } else {\n        preprocess_next_value(ctx);\n    }\n    return result;\n}\n\n#    ifdef ANJAY_WITH_LWM2M12\nstatic int cbor_decoder_null(anjay_json_like_decoder_t *ctx_) {\n    anjay_cbor_decoder_t *ctx = (anjay_cbor_decoder_t *) ctx_;\n    if (ctx->state != ANJAY_JSON_LIKE_DECODER_STATE_OK\n            || ctx->current_item.value_type != ANJAY_JSON_LIKE_VALUE_NULL) {\n        return -1;\n    }\n    preprocess_next_value(ctx);\n    return 0;\n}\n#    endif // ANJAY_WITH_LWM2M12\n\nstatic int cbor_decoder_bool(anjay_json_like_decoder_t *ctx_, bool *out_value) {\n    anjay_cbor_decoder_t *ctx = (anjay_cbor_decoder_t *) ctx_;\n    if (ctx->state != ANJAY_JSON_LIKE_DECODER_STATE_OK\n            || ctx->current_item.value_type != ANJAY_JSON_LIKE_VALUE_BOOL) {\n        return -1;\n    }\n    switch (ctx->current_item.additional_info) {\n    case CBOR_VALUE_BOOL_FALSE:\n        *out_value = false;\n        break;\n    case CBOR_VALUE_BOOL_TRUE:\n        *out_value = true;\n        break;\n    default:\n        AVS_UNREACHABLE(\"expected boolean, but got something else instead\");\n        return -1;\n    }\n    preprocess_next_value(ctx);\n    return 0;\n}\n\nstatic int cbor_get_bytes_size(anjay_cbor_decoder_t *ctx,\n                               size_t *out_bytes_size) {\n    if (ctx->state != ANJAY_JSON_LIKE_DECODER_STATE_OK\n            || (ctx->current_item.value_type\n                        != ANJAY_JSON_LIKE_VALUE_BYTE_STRING\n                && ctx->current_item.value_type\n                           != ANJAY_JSON_LIKE_VALUE_TEXT_STRING)\n            || parse_size(ctx, out_bytes_size)) {\n        return -1;\n    }\n    return 0;\n}\n\nstatic int cbor_decoder_bytes(anjay_json_like_decoder_t *ctx_,\n                              avs_stream_t *target_stream) {\n    anjay_cbor_decoder_t *ctx = (anjay_cbor_decoder_t *) ctx_;\n\n    anjay_io_cbor_bytes_ctx_t bytes_ctx;\n    if (_anjay_io_cbor_get_bytes_ctx(ctx_, &bytes_ctx)) {\n        return -1;\n    }\n\n    bool message_finished = false;\n    while (!message_finished) {\n        // ignore errors here - target_stream might not even be a membuf\n        avs_stream_membuf_ensure_free_bytes(target_stream,\n                                            bytes_ctx.bytes_available);\n\n        char chunk[32];\n        size_t bytes_read;\n        if (_anjay_io_cbor_get_some_bytes(ctx_,\n                                          &bytes_ctx,\n                                          &chunk,\n                                          sizeof(chunk),\n                                          &bytes_read,\n                                          &message_finished)\n                || avs_is_err(avs_stream_write(\n                           target_stream, chunk, bytes_read))) {\n            ctx->state = ANJAY_JSON_LIKE_DECODER_STATE_ERROR;\n            return -1;\n        }\n    }\n\n    return 0;\n}\n\nstatic int cbor_decoder_enter_array(anjay_json_like_decoder_t *ctx_) {\n    anjay_cbor_decoder_t *ctx = (anjay_cbor_decoder_t *) ctx_;\n    if (ctx->state != ANJAY_JSON_LIKE_DECODER_STATE_OK\n            || ctx->current_item.value_type != ANJAY_JSON_LIKE_VALUE_ARRAY\n            || nested_state_push(ctx)) {\n        return -1;\n    }\n    preprocess_next_value(ctx);\n    return 0;\n}\n\nstatic int cbor_decoder_enter_map(anjay_json_like_decoder_t *ctx_) {\n    anjay_cbor_decoder_t *ctx = (anjay_cbor_decoder_t *) ctx_;\n    if (ctx->state != ANJAY_JSON_LIKE_DECODER_STATE_OK\n            || ctx->current_item.value_type != ANJAY_JSON_LIKE_VALUE_MAP\n            || nested_state_push(ctx)) {\n        return -1;\n    }\n    preprocess_next_value(ctx);\n    return 0;\n}\n\nstatic size_t cbor_decoder_nesting_level(anjay_json_like_decoder_t *ctx_) {\n    anjay_cbor_decoder_t *ctx = (anjay_cbor_decoder_t *) ctx_;\n    return ctx->state == ANJAY_JSON_LIKE_DECODER_STATE_OK ? ctx->nest_stack_size\n                                                          : 0;\n}\n\nstatic int cbor_decoder_number(anjay_json_like_decoder_t *ctx_,\n                               anjay_json_like_number_t *out_value) {\n    anjay_cbor_decoder_t *ctx = (anjay_cbor_decoder_t *) ctx_;\n    if (ctx->state != ANJAY_JSON_LIKE_DECODER_STATE_OK) {\n        return -1;\n    }\n    out_value->type = ctx->current_item.value_type;\n    switch (ctx->current_item.value_type) {\n    case ANJAY_JSON_LIKE_VALUE_UINT:\n        return decode_uint(ctx, &out_value->value.u64);\n    case ANJAY_JSON_LIKE_VALUE_NEGATIVE_INT:\n        return decode_negative_int(ctx, &out_value->value.i64);\n    case ANJAY_JSON_LIKE_VALUE_FLOAT:\n        return decode_float(ctx, &out_value->value.f32);\n    case ANJAY_JSON_LIKE_VALUE_DOUBLE:\n        return decode_double(ctx, &out_value->value.f64);\n    default:\n        return -1;\n    }\n}\n\nstatic void cbor_decoder_cleanup(anjay_json_like_decoder_t **ctx) {\n    if (ctx && *ctx) {\n        avs_free(*ctx);\n        *ctx = NULL;\n    }\n}\n\nstatic anjay_json_like_decoder_state_t\ncbor_decoder_state(const anjay_json_like_decoder_t *ctx) {\n    return ((const anjay_cbor_decoder_t *) ctx)->state;\n}\n\nstatic const anjay_json_like_decoder_vtable_t VTABLE = {\n    .state = cbor_decoder_state,\n    .current_value_type = cbor_decoder_current_value_type,\n#    ifdef ANJAY_WITH_LWM2M12\n    .read_null = cbor_decoder_null,\n#    endif // ANJAY_WITH_LWM2M12\n    .read_bool = cbor_decoder_bool,\n    .number = cbor_decoder_number,\n    .bytes = cbor_decoder_bytes,\n    .enter_array = cbor_decoder_enter_array,\n    .enter_map = cbor_decoder_enter_map,\n    .nesting_level = cbor_decoder_nesting_level,\n    .cleanup = cbor_decoder_cleanup\n};\n\nanjay_json_like_decoder_t *_anjay_cbor_decoder_new(avs_stream_t *stream,\n                                                   size_t max_nesting_depth) {\n    anjay_cbor_decoder_t *ctx = (anjay_cbor_decoder_t *) avs_calloc(\n            1,\n            sizeof(anjay_cbor_decoder_t)\n                    + max_nesting_depth * sizeof(cbor_nested_state_t));\n    if (ctx) {\n        ctx->vtable = &VTABLE;\n        ctx->stream = stream;\n        ctx->state = ANJAY_JSON_LIKE_DECODER_STATE_OK;\n        ctx->max_nest_stack_size = max_nesting_depth;\n        preprocess_next_value(ctx);\n    }\n    return (anjay_json_like_decoder_t *) ctx;\n}\n\nstatic int try_preprocess_next_bytes_chunk(anjay_cbor_decoder_t *ctx,\n                                           anjay_io_cbor_bytes_ctx_t *bytes_ctx,\n                                           bool *out_message_finished) {\n    preprocess_next_value(ctx);\n\n    if (bytes_ctx->initial_nesting_level == ctx->nest_stack_size) {\n        if (cbor_get_bytes_size(ctx, &bytes_ctx->bytes_available)) {\n            ctx->state = ANJAY_JSON_LIKE_DECODER_STATE_ERROR;\n            return -1;\n        }\n    } else {\n        *out_message_finished = true;\n    }\n\n    return 0;\n}\n\nint _anjay_io_cbor_get_bytes_ctx(anjay_json_like_decoder_t *ctx_,\n                                 anjay_io_cbor_bytes_ctx_t *out_bytes_ctx) {\n    anjay_cbor_decoder_t *ctx = (anjay_cbor_decoder_t *) ctx_;\n    assert(ctx->vtable == &VTABLE);\n    memset(out_bytes_ctx, 0, sizeof(*out_bytes_ctx));\n\n    if (ctx->current_item.additional_info == CBOR_EXT_LENGTH_INDEFINITE) {\n        out_bytes_ctx->indefinite = true;\n        if (nested_state_push(ctx)) {\n            ctx->state = ANJAY_JSON_LIKE_DECODER_STATE_ERROR;\n            return -1;\n        }\n        out_bytes_ctx->initial_nesting_level = ctx->nest_stack_size;\n        return try_preprocess_next_bytes_chunk(\n                ctx, out_bytes_ctx, &out_bytes_ctx->empty);\n    } else if (cbor_get_bytes_size(ctx, &out_bytes_ctx->bytes_available)) {\n        ctx->state = ANJAY_JSON_LIKE_DECODER_STATE_ERROR;\n        return -1;\n    }\n\n    return 0;\n}\n\nstatic int handle_end_of_bytes(anjay_cbor_decoder_t *ctx,\n                               anjay_io_cbor_bytes_ctx_t *bytes_ctx,\n                               bool *out_message_finished) {\n    if (bytes_ctx->indefinite) {\n        if (try_preprocess_next_bytes_chunk(\n                    ctx, bytes_ctx, out_message_finished)) {\n            return -1;\n        }\n    } else {\n        *out_message_finished = true;\n        preprocess_next_value(ctx);\n    }\n    return 0;\n}\n\nint _anjay_io_cbor_get_some_bytes(anjay_json_like_decoder_t *ctx_,\n                                  anjay_io_cbor_bytes_ctx_t *bytes_ctx,\n                                  void *out_buf,\n                                  size_t buf_size,\n                                  size_t *out_bytes_read,\n                                  bool *out_message_finished) {\n    anjay_cbor_decoder_t *ctx = (anjay_cbor_decoder_t *) ctx_;\n    assert(ctx->vtable == &VTABLE);\n\n    if (bytes_ctx->empty) {\n        *out_bytes_read = 0;\n        *out_message_finished = true;\n        return 0;\n    }\n\n    *out_message_finished = false;\n    size_t total_bytes_read = 0;\n\n    // This may look complicated, but we want to read more data only if:\n    // - message is not finished, which is pretty obvious AND\n    // - buf_size is different than 0 or bytes_ctx->bytes_available is equal to\n    //   zero, cause it's possible that the next chunk (or the complete data)\n    //   has the length of 0, so the read may be successful even if there's no\n    //   space left in buffer.\n    while (*out_message_finished == false\n           && (buf_size != 0 || bytes_ctx->bytes_available == 0)) {\n        // This may be equal to 0 and this is intentional.\n        size_t bytes_to_read = AVS_MIN(buf_size, bytes_ctx->bytes_available);\n        if (avs_is_err(avs_stream_read_reliably(\n                    ctx->stream, out_buf, bytes_to_read))) {\n            ctx->state = ANJAY_JSON_LIKE_DECODER_STATE_ERROR;\n            return -1;\n        }\n\n        out_buf = (char *) out_buf + bytes_to_read;\n        buf_size -= bytes_to_read;\n        total_bytes_read += bytes_to_read;\n        bytes_ctx->bytes_available -= bytes_to_read;\n\n        if (!bytes_ctx->bytes_available\n                && handle_end_of_bytes(ctx, bytes_ctx, out_message_finished)) {\n            return -1;\n        }\n    }\n\n    *out_bytes_read = total_bytes_read;\n    return 0;\n}\n\n#    ifdef ANJAY_TEST\n#        include \"tests/core/io/cbor/cbor_decoder.c\"\n#    endif\n\n#endif // ANJAY_WITH_CBOR\n"
  },
  {
    "path": "src/core/io/cbor/anjay_json_like_cbor_decoder.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ANJAY_IO_JSON_LIKE_CBOR_DECODER_H\n#define ANJAY_IO_JSON_LIKE_CBOR_DECODER_H\n\n#include <anjay/anjay_config.h>\n\n#include \"../anjay_json_like_decoder.h\"\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\n/**\n * Only decimal fractions or indefinite length bytes can cause nesting.\n */\n#define MAX_SIMPLE_CBOR_NEST_STACK_SIZE 1\n\n/**\n * LwM2M requires wrapping entries in [ {} ], but keys/values that are a string\n * (byte/text) or a decimal fraction add another level of nesting.\n */\n#define MAX_SENML_CBOR_NEST_STACK_SIZE 3\n\n#ifdef ANJAY_WITH_LWM2M_GATEWAY\n/**\n * Compared to variant without support for LwM2M gateway:\n * - the paths can be up to 5 components long, as they might contain a prefix\n *   which selects an end device,\n * - the prefix is a string; it can be a key directly, or an initial element of\n *   an array key,\n * - the prefix, if any, is the first component of a path, therefore only the\n *   root map can contain such keys.\n *\n * This means that:\n * - the root map can be 5 levels deep now (so that's 1 more),\n * - CBOR decoder's stack when parsing a key can grow by 2 levels now (array key\n *   with prefix as indefinite text string), but that is valid only for the root\n *   map, of which the maximum stack growth determined by inner maps will be\n *   larger anyway.\n *\n * Therefore, the maximum stack size is 1+1+1+1+2 = 6;\n */\n#    define MAX_LWM2M_CBOR_NEST_STACK_SIZE 6\n#else // ANJAY_WITH_LWM2M_GATEWAY\n/**\n * LwM2M CBOR is a tree of nested maps. Root map is up to 4 levels deep. This\n * happens in case there's a value of multi-instance resource, and key for each\n * nested map adds only 1 path component, in a form like:\n * {<key>: {<key>: {<key>: {<key>: <value>}}}}\n *\n * When parsing a map, CBOR decoder's stack grows by 1 + whathever is incurred\n * by its contents, i.e. key-value pairs.\n *\n * In LwM2M CBOR, each key is an uint, or an array of uints (possibly of size\n * just 1), which needs 1 nesting level.\n *\n * The value is:\n * - a scalar, or\n * - an indefinite length string (byte/text) or a decimal fraction, which needs\n *   1 nesting level, or\n * - a nested map (unless we're at maximum depth).\n *\n * Therefore, when entering the innermost map CBOR decoder's stack will grow by\n * 1+1=2 levels at max. For outer maps it's 1 + maximum growth incurred by\n * contents, which essentially is 1 + maximum growth incurred by inner maps.\n *\n * Therefore, the maximum stack size is 1+1+1+2 = 5;\n */\n#    define MAX_LWM2M_CBOR_NEST_STACK_SIZE 5\n#endif // ANJAY_WITH_LWM2M_GATEWAY\n\nanjay_json_like_decoder_t *_anjay_cbor_decoder_new(avs_stream_t *stream,\n                                                   size_t max_nesting_depth);\n\ntypedef struct {\n    bool indefinite;\n    // Indefinite length struct may be completely empty.\n    bool empty;\n    // Used only for indefinite length bytes.\n    size_t initial_nesting_level;\n    // If indefinite, this contains available bytes only for the current chunk.\n    size_t bytes_available;\n} anjay_io_cbor_bytes_ctx_t;\n\nint _anjay_io_cbor_get_bytes_ctx(anjay_json_like_decoder_t *ctx_,\n                                 anjay_io_cbor_bytes_ctx_t *out_bytes_ctx);\n\nint _anjay_io_cbor_get_some_bytes(anjay_json_like_decoder_t *ctx_,\n                                  anjay_io_cbor_bytes_ctx_t *bytes_ctx,\n                                  void *out_buf,\n                                  size_t buf_size,\n                                  size_t *out_bytes_read,\n                                  bool *out_message_finished);\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif // ANJAY_IO_JSON_LIKE_CBOR_DECODER_H\n"
  },
  {
    "path": "src/core/io/cbor/anjay_senml_cbor_encoder.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#ifdef ANJAY_WITH_CBOR\n\n#    include <assert.h>\n#    include <math.h>\n#    include <string.h>\n\n#    include <avsystem/commons/avs_memory.h>\n#    include <avsystem/commons/avs_stream.h>\n#    include <avsystem/commons/avs_stream_membuf.h>\n#    include <avsystem/commons/avs_utils.h>\n\n#    include \"../anjay_common.h\"\n#    include \"../anjay_senml_like_encoder_vtable.h\"\n\n#    include \"anjay_cbor_encoder_ll.h\"\n\nVISIBILITY_SOURCE_BEGIN\n\n#    define cbor_log(...) _anjay_log(cbor_encoder, __VA_ARGS__)\n\n/**\n * Most complex scenario in SenML CBOR:\n * ROOT\n * |_ ARRAY\n *    |_ MAP\n *       |_ BYTES\n */\n#    define MAX_NEST_STACK_SIZE 4\n\ntypedef enum {\n    CBOR_CONTEXT_TYPE_ROOT = 0,\n    CBOR_CONTEXT_TYPE_UNKNOWN_LENGTH_ARRAY,\n    CBOR_CONTEXT_TYPE_KNOWN_LENGTH_ARRAY,\n    CBOR_CONTEXT_TYPE_BYTES,\n    CBOR_CONTEXT_TYPE_MAP\n} cbor_context_type_t;\n\ntypedef struct cbor_encoder_level {\n    cbor_context_type_t context_type;\n    avs_stream_t *stream;\n    /* Number of values in current object or number of bytes remaining in bytes\n     * value */\n    size_t size;\n} cbor_encoder_internal_t;\n\ntypedef struct cbor_encoder_struct {\n    const anjay_senml_like_encoder_vtable_t *vtable;\n    cbor_encoder_internal_t nest_stack[MAX_NEST_STACK_SIZE];\n    uint8_t stack_size;\n\n    // Used for definite-length map validation.\n    uint8_t map_remaining_items;\n    double last_encoded_time_s;\n} cbor_encoder_t;\n\nstatic inline cbor_encoder_internal_t *nested_context_top(cbor_encoder_t *ctx) {\n    assert(ctx);\n    assert(ctx->stack_size);\n\n    return &ctx->nest_stack[ctx->stack_size - 1];\n}\n\nstatic inline cbor_encoder_internal_t *nested_context_push(\n        cbor_encoder_t *ctx, avs_stream_t *stream, cbor_context_type_t type) {\n    assert(ctx);\n    assert(ctx->stack_size < MAX_NEST_STACK_SIZE);\n\n    cbor_encoder_internal_t *new_ctx = &ctx->nest_stack[ctx->stack_size];\n    ctx->stack_size++;\n\n    new_ctx->stream = stream;\n    new_ctx->context_type = type;\n    new_ctx->size = 0;\n\n    return new_ctx;\n}\n\nstatic inline void nested_context_pop(cbor_encoder_t *ctx) {\n    (void) ctx;\n    assert(ctx->stack_size);\n    ctx->stack_size--;\n}\n\nstatic int cbor_encode_uint(cbor_encoder_t *ctx, uint64_t value) {\n    cbor_encoder_internal_t *top_ctx = nested_context_top(ctx);\n    assert(top_ctx->context_type != CBOR_CONTEXT_TYPE_BYTES);\n\n    top_ctx->size++;\n    return _anjay_cbor_ll_encode_uint(top_ctx->stream, value);\n}\n\nstatic int cbor_encode_int(cbor_encoder_t *ctx, int64_t value) {\n    cbor_encoder_internal_t *top_ctx = nested_context_top(ctx);\n    assert(top_ctx->context_type != CBOR_CONTEXT_TYPE_BYTES);\n\n    top_ctx->size++;\n    return _anjay_cbor_ll_encode_int(top_ctx->stream, value);\n}\n\nstatic int cbor_encode_bool(cbor_encoder_t *ctx, bool value) {\n    cbor_encoder_internal_t *top_ctx = nested_context_top(ctx);\n    assert(top_ctx->context_type != CBOR_CONTEXT_TYPE_BYTES);\n\n    top_ctx->size++;\n    return _anjay_cbor_ll_encode_bool(top_ctx->stream, value);\n}\n\nstatic int cbor_encode_double(cbor_encoder_t *ctx, double value) {\n    cbor_encoder_internal_t *top_ctx = nested_context_top(ctx);\n    assert(top_ctx->context_type != CBOR_CONTEXT_TYPE_BYTES);\n\n    top_ctx->size++;\n    return _anjay_cbor_ll_encode_double(top_ctx->stream, value);\n}\n\nstatic int cbor_bytes_begin(cbor_encoder_t *ctx, size_t size) {\n    cbor_encoder_internal_t *top_ctx = nested_context_top(ctx);\n    assert(top_ctx->context_type != CBOR_CONTEXT_TYPE_BYTES);\n\n    // No caching required, so current stream can be used.\n    cbor_encoder_internal_t *bytes_ctx =\n            nested_context_push(ctx, top_ctx->stream, CBOR_CONTEXT_TYPE_BYTES);\n    bytes_ctx->size = size;\n\n    int retval = _anjay_cbor_ll_bytes_begin(bytes_ctx->stream, size);\n    if (retval) {\n        nested_context_pop(ctx);\n    }\n    return retval;\n}\n\nstatic int\ncbor_bytes_append(cbor_encoder_t *ctx, const void *data, size_t size) {\n    cbor_encoder_internal_t *top_ctx = nested_context_top(ctx);\n    assert(top_ctx->context_type == CBOR_CONTEXT_TYPE_BYTES);\n\n    if (size > top_ctx->size) {\n        cbor_log(DEBUG, _(\"passed more bytes than declared\"));\n        return -1;\n    }\n\n    top_ctx->size -= size;\n    return _anjay_cbor_ll_bytes_append(top_ctx->stream, data, size);\n}\n\nstatic int cbor_bytes_end(cbor_encoder_t *ctx) {\n    assert(ctx);\n    cbor_encoder_internal_t *top_ctx = nested_context_top(ctx);\n    assert(top_ctx->context_type == CBOR_CONTEXT_TYPE_BYTES);\n\n    int retval = 0;\n    if (top_ctx->size) {\n        cbor_log(DEBUG, _(\"not all bytes were written, invalid data encoded\"));\n        retval = -1;\n    }\n    nested_context_pop(ctx);\n\n    top_ctx = nested_context_top(ctx);\n    top_ctx->size++;\n    return retval;\n}\n\nstatic int cbor_encode_string(cbor_encoder_t *ctx, const char *data) {\n    cbor_encoder_internal_t *top_ctx = nested_context_top(ctx);\n    assert(top_ctx->context_type != CBOR_CONTEXT_TYPE_BYTES);\n\n    top_ctx->size++;\n    return _anjay_cbor_ll_encode_string(top_ctx->stream, data);\n}\n\nstatic int cbor_definite_map_begin(cbor_encoder_t *ctx, size_t items_count) {\n    assert(ctx);\n    cbor_encoder_internal_t *top_ctx = nested_context_top(ctx);\n    assert(top_ctx->context_type != CBOR_CONTEXT_TYPE_BYTES);\n\n    // No caching required, so current stream can be used.\n    cbor_encoder_internal_t *map_ctx =\n            nested_context_push(ctx, top_ctx->stream, CBOR_CONTEXT_TYPE_MAP);\n\n    int retval =\n            _anjay_cbor_ll_definite_map_begin(map_ctx->stream, items_count);\n    if (retval) {\n        nested_context_pop(ctx);\n    }\n    return retval;\n}\n\nstatic int cbor_definite_map_end(cbor_encoder_t *ctx) {\n    assert(ctx);\n    cbor_encoder_internal_t *top_ctx = nested_context_top(ctx);\n    if (top_ctx->context_type != CBOR_CONTEXT_TYPE_MAP) {\n        cbor_log(DEBUG, _(\"trying to finish map, but it is not started\"));\n        return -1;\n    }\n\n    int retval = 0;\n    if (top_ctx->size % 2 != 0) {\n        cbor_log(DEBUG,\n                 _(\"invalid map encoded, not all keys have value assigned\"));\n        retval = -1;\n    }\n\n    nested_context_pop(ctx);\n\n    top_ctx = nested_context_top(ctx);\n    top_ctx->size++;\n    return retval;\n}\n\nstatic int cbor_known_length_definite_array_begin(cbor_encoder_t *ctx,\n                                                  size_t items_count) {\n    assert(ctx);\n    cbor_encoder_internal_t *top_ctx = nested_context_top(ctx);\n    assert(top_ctx->context_type != CBOR_CONTEXT_TYPE_BYTES);\n\n    cbor_encoder_internal_t *array_ctx =\n            nested_context_push(ctx, top_ctx->stream,\n                                CBOR_CONTEXT_TYPE_KNOWN_LENGTH_ARRAY);\n\n    int retval =\n            _anjay_cbor_ll_definite_array_begin(array_ctx->stream, items_count);\n    if (retval) {\n        nested_context_pop(ctx);\n    }\n    return retval;\n}\n\nstatic int cbor_unknown_length_definite_array_begin(cbor_encoder_t *ctx) {\n    cbor_encoder_internal_t *top_ctx = nested_context_top(ctx);\n    assert(top_ctx->context_type != CBOR_CONTEXT_TYPE_BYTES);\n    (void) top_ctx;\n\n    avs_stream_t *stream = avs_stream_membuf_create();\n    if (!stream) {\n        return -1;\n    }\n\n    nested_context_push(ctx, stream, CBOR_CONTEXT_TYPE_UNKNOWN_LENGTH_ARRAY);\n    return 0;\n}\n\nstatic int copy_stream(avs_stream_t *dst, avs_stream_t *src) {\n    char buffer[128];\n    bool msg_finished;\n    do {\n        size_t bytes_read;\n        if (avs_is_err(avs_stream_read(src, &bytes_read, &msg_finished, buffer,\n                                       sizeof(buffer)))\n                || avs_is_err(avs_stream_write(dst, buffer, bytes_read))) {\n            return -1;\n        }\n    } while (!msg_finished);\n    return 0;\n}\n\nstatic int cbor_definite_array_end(cbor_encoder_t *ctx) {\n    assert(ctx);\n    cbor_encoder_internal_t *top_ctx = nested_context_top(ctx);\n    cbor_context_type_t context_type = top_ctx->context_type;\n    if (context_type != CBOR_CONTEXT_TYPE_UNKNOWN_LENGTH_ARRAY\n            && context_type != CBOR_CONTEXT_TYPE_KNOWN_LENGTH_ARRAY) {\n        cbor_log(DEBUG, _(\"trying to finish array, but it is not started\"));\n        return -1;\n    }\n    avs_stream_t *array_stream = top_ctx->stream;\n    size_t entries = top_ctx->size;\n    nested_context_pop(ctx);\n\n    top_ctx = nested_context_top(ctx);\n\n    assert((top_ctx->stream == array_stream)\n           == (context_type == CBOR_CONTEXT_TYPE_KNOWN_LENGTH_ARRAY));\n\n    int retval = 0;\n    if (context_type == CBOR_CONTEXT_TYPE_UNKNOWN_LENGTH_ARRAY) {\n        (void) ((retval = _anjay_cbor_ll_definite_array_begin(top_ctx->stream,\n                                                              entries))\n                || (retval = copy_stream(top_ctx->stream, array_stream)));\n        avs_stream_cleanup(&array_stream);\n    }\n    top_ctx->size++;\n    return retval;\n}\n\nstatic int senml_cbor_encode_uint(anjay_senml_like_encoder_t *ctx_,\n                                  uint64_t value) {\n    cbor_encoder_t *ctx = (cbor_encoder_t *) ctx_;\n    assert(ctx->map_remaining_items);\n    int retval;\n    (void) ((retval = cbor_encode_uint(ctx, SENML_LABEL_VALUE))\n            || (retval = cbor_encode_uint(ctx, value)));\n    ctx->map_remaining_items--;\n    return retval;\n}\n\nstatic int senml_cbor_encode_int(anjay_senml_like_encoder_t *ctx_,\n                                 int64_t value) {\n    cbor_encoder_t *ctx = (cbor_encoder_t *) ctx_;\n    assert(ctx->map_remaining_items);\n    int retval;\n    (void) ((retval = cbor_encode_uint(ctx, SENML_LABEL_VALUE))\n            || (retval = cbor_encode_int(ctx, value)));\n    ctx->map_remaining_items--;\n    return retval;\n}\n\nstatic int senml_cbor_encode_double(anjay_senml_like_encoder_t *ctx_,\n                                    double value) {\n    cbor_encoder_t *ctx = (cbor_encoder_t *) ctx_;\n    assert(ctx->map_remaining_items);\n    int retval;\n    (void) ((retval = cbor_encode_uint(ctx, SENML_LABEL_VALUE))\n            || (retval = cbor_encode_double(ctx, value)));\n    ctx->map_remaining_items--;\n    return retval;\n}\n\nstatic int senml_cbor_encode_bool(anjay_senml_like_encoder_t *ctx_,\n                                  bool value) {\n    cbor_encoder_t *ctx = (cbor_encoder_t *) ctx_;\n    assert(ctx->map_remaining_items);\n    int retval;\n    (void) ((retval = cbor_encode_uint(ctx, SENML_LABEL_VALUE_BOOL))\n            || (retval = cbor_encode_bool(ctx, value)));\n    ctx->map_remaining_items--;\n    return retval;\n}\n\nstatic int senml_cbor_encode_string(anjay_senml_like_encoder_t *ctx_,\n                                    const char *value) {\n    cbor_encoder_t *ctx = (cbor_encoder_t *) ctx_;\n    assert(ctx->map_remaining_items);\n    int retval;\n    (void) ((retval = cbor_encode_uint(ctx, SENML_LABEL_VALUE_STRING))\n            || (retval = cbor_encode_string(ctx, value)));\n    ctx->map_remaining_items--;\n    return retval;\n}\n\nstatic int senml_cbor_encode_objlnk(anjay_senml_like_encoder_t *ctx_,\n                                    const char *value) {\n    cbor_encoder_t *ctx = (cbor_encoder_t *) ctx_;\n    assert(ctx->map_remaining_items);\n    int retval;\n    (void) ((retval = cbor_encode_string(ctx, SENML_EXT_OBJLNK_REPR))\n            || (retval = cbor_encode_string(ctx, value)));\n    ctx->map_remaining_items--;\n    return retval;\n}\n\nstatic int maybe_encode_basetime(cbor_encoder_t *ctx, double time_s) {\n    if (ctx->last_encoded_time_s == time_s) {\n        return 0;\n    }\n\n    ctx->last_encoded_time_s = time_s;\n\n    assert(ctx->map_remaining_items);\n    int retval;\n    (void) ((retval = cbor_encode_int(ctx, SENML_LABEL_BASE_TIME))\n            || (retval = cbor_encode_double(ctx, time_s)));\n    ctx->map_remaining_items--;\n    return retval;\n}\n\nstatic inline int maybe_encode_basename(cbor_encoder_t *ctx,\n                                        const char *basename) {\n    if (basename) {\n        assert(ctx->map_remaining_items);\n        int retval;\n        (void) ((retval = cbor_encode_int(ctx, SENML_LABEL_BASE_NAME))\n                || (retval = cbor_encode_string(ctx, basename)));\n        ctx->map_remaining_items--;\n        return retval;\n    }\n    return 0;\n}\n\nstatic inline int maybe_encode_name(cbor_encoder_t *ctx, const char *name) {\n    if (name) {\n        assert(ctx->map_remaining_items);\n        int retval;\n        (void) ((retval = cbor_encode_int(ctx, SENML_LABEL_NAME))\n                || (retval = cbor_encode_string(ctx, name)));\n        ctx->map_remaining_items--;\n        return retval;\n    }\n    return 0;\n}\n\nstatic int senml_cbor_element_begin(anjay_senml_like_encoder_t *ctx_,\n                                    const char *basename,\n                                    const char *name,\n                                    double time_s) {\n    cbor_encoder_t *ctx = (cbor_encoder_t *) ctx_;\n    if (isnan(time_s)) {\n        time_s = 0.0;\n    }\n\n    ctx->map_remaining_items =\n            (uint8_t) (!!basename + !!name\n                       + (ctx->last_encoded_time_s != time_s) + 1);\n    int retval;\n    (void) ((retval = cbor_definite_map_begin(ctx, ctx->map_remaining_items))\n            || (retval = maybe_encode_basename(ctx, basename))\n            || (retval = maybe_encode_name(ctx, name))\n            || (retval = maybe_encode_basetime(ctx, time_s)));\n    return retval;\n}\n\nstatic int senml_cbor_element_end(anjay_senml_like_encoder_t *ctx_) {\n    cbor_encoder_t *ctx = (cbor_encoder_t *) ctx_;\n    assert(ctx->map_remaining_items == 0);\n    return cbor_definite_map_end(ctx);\n}\n\nstatic int senml_cbor_bytes_begin(anjay_senml_like_encoder_t *ctx_,\n                                  size_t size) {\n    cbor_encoder_t *ctx = (cbor_encoder_t *) ctx_;\n    int retval;\n    (void) ((retval = cbor_encode_uint(ctx, SENML_LABEL_VALUE_OPAQUE))\n            || (retval = cbor_bytes_begin(ctx, size)));\n    return retval;\n}\n\nstatic int senml_cbor_bytes_append(anjay_senml_like_encoder_t *ctx_,\n                                   const void *data,\n                                   size_t size) {\n    return cbor_bytes_append((cbor_encoder_t *) ctx_, data, size);\n}\n\nstatic int senml_cbor_bytes_end(anjay_senml_like_encoder_t *ctx_) {\n    cbor_encoder_t *ctx = (cbor_encoder_t *) ctx_;\n    assert(ctx->map_remaining_items);\n    ctx->map_remaining_items--;\n    return cbor_bytes_end(ctx);\n}\n\nstatic int senml_cbor_encoder_cleanup(anjay_senml_like_encoder_t **ctx_) {\n    cbor_encoder_t *ctx = (cbor_encoder_t *) *ctx_;\n\n    int retval = cbor_definite_array_end(ctx);\n    if (retval) {\n        cbor_log(DEBUG, _(\"failed to close CBOR array\"));\n    }\n    if (!retval && ctx->stack_size > 1) {\n        cbor_log(DEBUG,\n                 _(\"some not closed objects left, serialized data may be \"\n                   \"invalid\"));\n        retval = -1;\n    }\n\n    while (ctx->stack_size > 1) {\n        cbor_encoder_internal_t *top_ctx = nested_context_top(ctx);\n        if (top_ctx->context_type == CBOR_CONTEXT_TYPE_UNKNOWN_LENGTH_ARRAY) {\n            avs_stream_cleanup(&top_ctx->stream);\n        }\n        nested_context_pop(ctx);\n    }\n\n    avs_free(*ctx_);\n    *ctx_ = NULL;\n    return retval;\n}\n\nstatic const anjay_senml_like_encoder_vtable_t SENML_CBOR_ENCODER_VTABLE = {\n    .senml_like_encode_uint = senml_cbor_encode_uint,\n    .senml_like_encode_int = senml_cbor_encode_int,\n    .senml_like_encode_double = senml_cbor_encode_double,\n    .senml_like_encode_bool = senml_cbor_encode_bool,\n    .senml_like_encode_string = senml_cbor_encode_string,\n    .senml_like_encode_objlnk = senml_cbor_encode_objlnk,\n    .senml_like_element_begin = senml_cbor_element_begin,\n    .senml_like_element_end = senml_cbor_element_end,\n    .senml_like_bytes_begin = senml_cbor_bytes_begin,\n    .senml_like_bytes_append = senml_cbor_bytes_append,\n    .senml_like_bytes_end = senml_cbor_bytes_end,\n    .senml_like_encoder_cleanup = senml_cbor_encoder_cleanup\n};\n\nanjay_senml_like_encoder_t *\n_anjay_senml_cbor_encoder_new(avs_stream_t *stream, const size_t *items_count) {\n    if (!stream) {\n        cbor_log(DEBUG, _(\"no stream provided\"));\n        return NULL;\n    }\n\n    cbor_encoder_t *ctx =\n            (cbor_encoder_t *) avs_calloc(1, sizeof(cbor_encoder_t));\n    if (!ctx) {\n        cbor_log(DEBUG, _(\"failed to allocate encoder context\"));\n        return NULL;\n    }\n\n    nested_context_push(ctx, stream, CBOR_CONTEXT_TYPE_ROOT);\n    int result;\n    if (items_count) {\n        result = cbor_known_length_definite_array_begin(ctx, *items_count);\n    } else {\n        result = cbor_unknown_length_definite_array_begin(ctx);\n    }\n    if (result) {\n        avs_free(ctx);\n        return NULL;\n    }\n    ctx->vtable = &SENML_CBOR_ENCODER_VTABLE;\n\n    return (anjay_senml_like_encoder_t *) ctx;\n}\n\n#    ifdef ANJAY_TEST\n#        include \"tests/core/io/cbor/cbor_encoder.c\"\n#    endif\n\n#endif // ANJAY_WITH_CBOR\n"
  },
  {
    "path": "src/core/io/json/anjay_json_decoder.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#ifdef ANJAY_WITH_SENML_JSON\n\n#    include <ctype.h>\n#    include <errno.h>\n#    include <string.h>\n\n#    include <avsystem/commons/avs_memory.h>\n\n#    include \"../../anjay_utils_private.h\"\n#    include \"../anjay_json_like_decoder_vtable.h\"\n#    include \"anjay_json_decoder.h\"\n\nVISIBILITY_SOURCE_BEGIN\n\n#    define LOG(...) _anjay_log(json, __VA_ARGS__)\n\n#    define MAX_NEST_STACK_SIZE 2\n\ntypedef enum {\n    JSON_NESTED_NONE,\n    JSON_NESTED_ARRAY_ELEMENT,\n    JSON_NESTED_MAP_KEY,\n    JSON_NESTED_MAP_VALUE\n} json_nested_type_t;\n\ntypedef struct {\n    const anjay_json_like_decoder_vtable_t *vtable;\n    avs_stream_t *stream;\n    anjay_json_like_decoder_state_t state;\n    anjay_json_like_value_type_t current_item_type;\n    json_nested_type_t nested_types[MAX_NEST_STACK_SIZE];\n} anjay_json_decoder_t;\n\nstatic anjay_json_like_decoder_state_t\njson_decoder_state(const anjay_json_like_decoder_t *ctx) {\n    return ((const anjay_json_decoder_t *) ctx)->state;\n}\n\nstatic int\njson_decoder_current_value_type(anjay_json_like_decoder_t *ctx_,\n                                anjay_json_like_value_type_t *out_type) {\n    anjay_json_decoder_t *ctx = (anjay_json_decoder_t *) ctx_;\n    if (ctx->state == ANJAY_JSON_LIKE_DECODER_STATE_OK) {\n        *out_type = ctx->current_item_type;\n        return 0;\n    }\n    return -1;\n}\n\nstatic bool is_json_whitespace(int ch) {\n    return strchr(\" \\r\\n\\t\", ch);\n}\n\nstatic size_t json_decoder_nesting_level(anjay_json_like_decoder_t *ctx_) {\n    anjay_json_decoder_t *ctx = (anjay_json_decoder_t *) ctx_;\n    if (ctx->state != ANJAY_JSON_LIKE_DECODER_STATE_OK) {\n        return 0;\n    }\n    size_t nesting_level = 0;\n    while (nesting_level < AVS_ARRAY_SIZE(ctx->nested_types)\n           && ctx->nested_types[nesting_level] != JSON_NESTED_NONE) {\n        ++nesting_level;\n    }\n    return nesting_level;\n}\n\nstatic json_nested_type_t *top_level_nesting_ptr(anjay_json_decoder_t *ctx) {\n    size_t nesting_level =\n            json_decoder_nesting_level((anjay_json_like_decoder_t *) ctx);\n    return nesting_level ? &ctx->nested_types[nesting_level - 1] : NULL;\n}\n\nstatic int preprocess_possible_value(anjay_json_decoder_t *ctx) {\n    assert(ctx->state == ANJAY_JSON_LIKE_DECODER_STATE_OK);\n    json_nested_type_t *nested_type = top_level_nesting_ptr(ctx);\n    while (true) {\n        unsigned char value;\n        avs_error_t err = avs_stream_peek(ctx->stream, 0, (char *) &value);\n        if (avs_is_eof(err)) {\n            ctx->state = ANJAY_JSON_LIKE_DECODER_STATE_FINISHED;\n            return value;\n        } else if (avs_is_err(err)) {\n            LOG(DEBUG,\n                _(\"JSON parse error: could not read input stream: \") \"%s\",\n                AVS_COAP_STRERROR(err));\n            ctx->state = ANJAY_JSON_LIKE_DECODER_STATE_ERROR;\n            return value;\n        }\n\n        if (is_json_whitespace(value)) {\n            err = avs_stream_getch(ctx->stream, (char *) &value, NULL);\n            assert(avs_is_ok(err));\n            assert(is_json_whitespace(value));\n            (void) err;\n            continue;\n        }\n\n        if (isdigit(value) || value == '-') {\n            ctx->current_item_type = ANJAY_JSON_LIKE_VALUE_DOUBLE;\n        } else {\n            switch (value) {\n            case 'n':\n                ctx->current_item_type = ANJAY_JSON_LIKE_VALUE_NULL;\n                break;\n\n            case '\"':\n                ctx->current_item_type = ANJAY_JSON_LIKE_VALUE_TEXT_STRING;\n                break;\n\n            case '{':\n                ctx->current_item_type = ANJAY_JSON_LIKE_VALUE_MAP;\n                break;\n\n            case '[':\n                ctx->current_item_type = ANJAY_JSON_LIKE_VALUE_ARRAY;\n                break;\n\n            case 't':\n            case 'f':\n                ctx->current_item_type = ANJAY_JSON_LIKE_VALUE_BOOL;\n                break;\n\n            default:\n                return value;\n            }\n        }\n        if (nested_type && *nested_type == JSON_NESTED_MAP_KEY\n                && ctx->current_item_type\n                               != ANJAY_JSON_LIKE_VALUE_TEXT_STRING) {\n            LOG(DEBUG, _(\"JSON parse error: only strings can be map keys\"));\n            ctx->state = ANJAY_JSON_LIKE_DECODER_STATE_ERROR;\n        }\n        return 0;\n    }\n}\n\nstatic void preprocess_value(anjay_json_decoder_t *ctx) {\n    int value = preprocess_possible_value(ctx);\n    if (ctx->state == ANJAY_JSON_LIKE_DECODER_STATE_FINISHED) {\n        LOG(DEBUG, _(\"JSON parse error: empty input\"));\n        ctx->state = ANJAY_JSON_LIKE_DECODER_STATE_ERROR;\n    } else if (value > 0) {\n        LOG(DEBUG, _(\"JSON parse error: unexpected character \\\\x\") \"%02\" PRIX8,\n            (uint8_t) value);\n        ctx->state = ANJAY_JSON_LIKE_DECODER_STATE_ERROR;\n    }\n}\n\nstatic int push_nested_type(anjay_json_decoder_t *ctx,\n                            json_nested_type_t nested_type) {\n    size_t nesting_level =\n            json_decoder_nesting_level((anjay_json_like_decoder_t *) ctx);\n    if (nesting_level >= AVS_ARRAY_SIZE(ctx->nested_types)) {\n        ctx->state = ANJAY_JSON_LIKE_DECODER_STATE_ERROR;\n        return -1;\n    }\n    ctx->nested_types[nesting_level] = nested_type;\n    return 0;\n}\n\nstatic void preprocess_next_value(anjay_json_decoder_t *ctx);\n\nstatic void preprocess_first_nested_value(anjay_json_decoder_t *ctx) {\n    json_nested_type_t *nested_type = top_level_nesting_ptr(ctx);\n    assert(nested_type);\n    assert(*nested_type == JSON_NESTED_ARRAY_ELEMENT\n           || *nested_type == JSON_NESTED_MAP_KEY);\n    int value = preprocess_possible_value(ctx);\n    if (ctx->state == ANJAY_JSON_LIKE_DECODER_STATE_FINISHED) {\n        LOG(DEBUG, _(\"JSON parse error: unexpected end-of-file\"));\n        ctx->state = ANJAY_JSON_LIKE_DECODER_STATE_ERROR;\n    } else if ((*nested_type == JSON_NESTED_ARRAY_ELEMENT && value == ']')\n               || (*nested_type == JSON_NESTED_MAP_KEY && value == '}')) {\n        unsigned char ch;\n        avs_error_t err = avs_stream_getch(ctx->stream, (char *) &ch, NULL);\n        assert(avs_is_ok(err));\n        assert(ch == value);\n        (void) ch;\n        (void) err;\n        *nested_type = JSON_NESTED_NONE;\n        preprocess_next_value(ctx);\n    } else if (value > 0) {\n        LOG(DEBUG, _(\"JSON parse error: unexpected character \\\\x\") \"%02\" PRIX8,\n            (uint8_t) value);\n        ctx->state = ANJAY_JSON_LIKE_DECODER_STATE_ERROR;\n    }\n}\n\nstatic void preprocess_next_value(anjay_json_decoder_t *ctx) {\n    while (true) {\n        json_nested_type_t *nested_type = top_level_nesting_ptr(ctx);\n        unsigned char ch;\n        avs_error_t err;\n        do {\n            err = avs_stream_getch(ctx->stream, (char *) &ch, NULL);\n        } while (avs_is_ok(err) && is_json_whitespace(ch));\n\n        if (avs_is_eof(err) && !nested_type) {\n            ctx->state = ANJAY_JSON_LIKE_DECODER_STATE_FINISHED;\n            return;\n        } else if (avs_is_err(err)) {\n            LOG(DEBUG,\n                _(\"JSON parse error: could not read input stream: \") \"%s\",\n                AVS_COAP_STRERROR(err));\n            ctx->state = ANJAY_JSON_LIKE_DECODER_STATE_ERROR;\n            return;\n        } else if (nested_type) {\n            if (*nested_type == JSON_NESTED_ARRAY_ELEMENT && ch == ',') {\n                preprocess_value(ctx);\n                return;\n            } else if (*nested_type == JSON_NESTED_MAP_KEY && ch == ':') {\n                *nested_type = JSON_NESTED_MAP_VALUE;\n                preprocess_value(ctx);\n                return;\n            } else if (*nested_type == JSON_NESTED_MAP_VALUE && ch == ',') {\n                *nested_type = JSON_NESTED_MAP_KEY;\n                preprocess_value(ctx);\n                return;\n            } else if (((*nested_type == JSON_NESTED_ARRAY_ELEMENT && ch == ']')\n                        || (*nested_type == JSON_NESTED_MAP_VALUE\n                            && ch == '}'))) {\n                *nested_type = JSON_NESTED_NONE;\n                continue;\n            }\n        }\n        LOG(DEBUG, _(\"JSON parse error: unexpected character \\\\x\") \"%02\" PRIX8,\n            (uint8_t) ch);\n        ctx->state = ANJAY_JSON_LIKE_DECODER_STATE_ERROR;\n        return;\n    }\n}\n\n#    ifdef ANJAY_WITH_LWM2M12\nstatic int json_decoder_null(anjay_json_like_decoder_t *ctx_) {\n    anjay_json_decoder_t *ctx = (anjay_json_decoder_t *) ctx_;\n    if (ctx->state != ANJAY_JSON_LIKE_DECODER_STATE_OK\n            || ctx->current_item_type != ANJAY_JSON_LIKE_VALUE_NULL) {\n        return -1;\n    }\n    char buf[4];\n    if (avs_is_err(avs_stream_read_reliably(ctx->stream, buf, sizeof(buf)))) {\n        goto error;\n    }\n    if (memcmp(buf, \"null\", 4) != 0) {\n        LOG(DEBUG, _(\"JSON parse error: invalid null value\"));\n        goto error;\n    }\n    preprocess_next_value(ctx);\n    return 0;\nerror:\n    ctx->state = ANJAY_JSON_LIKE_DECODER_STATE_ERROR;\n    return -1;\n}\n#    endif // ANJAY_WITH_LWM2M12\n\nstatic int json_decoder_bool(anjay_json_like_decoder_t *ctx_, bool *out_value) {\n    anjay_json_decoder_t *ctx = (anjay_json_decoder_t *) ctx_;\n    if (ctx->state != ANJAY_JSON_LIKE_DECODER_STATE_OK\n            || ctx->current_item_type != ANJAY_JSON_LIKE_VALUE_BOOL) {\n        return -1;\n    }\n    char buf[4];\n    if (avs_is_err(avs_stream_read_reliably(ctx->stream, buf, sizeof(buf)))) {\n        goto error;\n    }\n    if (memcmp(buf, \"true\", 4) == 0) {\n        *out_value = true;\n    } else if (memcmp(buf, \"fals\", 4) == 0) {\n        char ch;\n        if (avs_is_err(avs_stream_getch(ctx->stream, &ch, NULL)) || ch != 'e') {\n            goto error;\n        }\n        *out_value = false;\n    } else {\n        LOG(DEBUG, _(\"JSON parse error: invalid boolean value\"));\n        goto error;\n    }\n    preprocess_next_value(ctx);\n    return 0;\nerror:\n    ctx->state = ANJAY_JSON_LIKE_DECODER_STATE_ERROR;\n    return -1;\n}\n\nstatic bool is_valid_json_number_character(int ch) {\n    return isdigit(ch) || strchr(\"+-.Ee\", ch);\n}\n\nstatic int validate_number(const char *str) {\n    // strtod() is a bit more lenient than the JSON spec\n    // make sure we don't accept invalid strings\n    if (*str == '-') {\n        ++str;\n    }\n    if (!isdigit(*(const unsigned char *) str)) {\n        // leading decimal point is invalid\n        return -1;\n    }\n    if (*str == '0' && isdigit(((const unsigned char *) str)[1])) {\n        // leading zero must be followed by\n        // decimal point, exponent, or end-of-string - never another digit\n        return -1;\n    }\n    // other cases are handled by strtod()\n    return 0;\n}\n\nstatic int json_decoder_number(anjay_json_like_decoder_t *ctx_,\n                               anjay_json_like_number_t *out_value) {\n    anjay_json_decoder_t *ctx = (anjay_json_decoder_t *) ctx_;\n    if (ctx->state != ANJAY_JSON_LIKE_DECODER_STATE_OK\n            || ctx->current_item_type != ANJAY_JSON_LIKE_VALUE_DOUBLE) {\n        return -1;\n    }\n    size_t length = 0;\n    char buf[ANJAY_MAX_DOUBLE_STRING_SIZE];\n    while (true) {\n        unsigned char ch;\n        avs_error_t err = avs_stream_peek(ctx->stream, 0, (char *) &ch);\n        if (avs_is_err(err) && !avs_is_eof(err)) {\n            goto error;\n        } else if (avs_is_eof(err) || !is_valid_json_number_character(ch)) {\n            buf[length] = '\\0';\n            break;\n        }\n\n        if (avs_is_err(avs_stream_read_reliably(ctx->stream, buf + length, 1))\n                || ++length >= sizeof(buf)) {\n            goto error;\n        }\n        assert(((const unsigned char *) buf)[length - 1] == ch);\n    }\n    if (validate_number(buf)\n            || _anjay_safe_strtod(buf, &out_value->value.f64)) {\n        goto error;\n    }\n    out_value->type = ANJAY_JSON_LIKE_VALUE_DOUBLE;\n    preprocess_next_value(ctx);\n    return 0;\nerror:\n    ctx->state = ANJAY_JSON_LIKE_DECODER_STATE_ERROR;\n    return -1;\n}\n\nstatic int handle_unicode_escape(anjay_json_decoder_t *ctx,\n                                 avs_stream_t *target_stream) {\n    char hex[5] = \"\";\n    if (avs_is_err(avs_stream_read_reliably(ctx->stream, &hex, sizeof(hex) - 1))\n            || !hex[0] || isspace((unsigned char) hex[0])) {\n        return -1;\n    }\n    errno = 0;\n    char *endptr = NULL;\n    long codepoint = strtol(hex, &endptr, 16);\n    if (errno || !endptr || *endptr || codepoint < 0 || codepoint > 0xFFFF) {\n        return -1;\n    }\n    // UTF-8 multibyte encodings\n    if (codepoint < 0x80) {\n        return avs_is_ok(avs_stream_write(\n                       target_stream,\n                       &(unsigned char) { (unsigned char) codepoint }, 1))\n                       ? 0\n                       : -1;\n    } else if (codepoint < 0x800) {\n        return avs_is_ok(avs_stream_write(\n                       target_stream,\n                       &(const unsigned char[]) {\n                               (unsigned char) (0xC0 | (codepoint >> 6)),\n                               (unsigned char) (0x80 | (codepoint & 0x3F)) }[0],\n                       2))\n                       ? 0\n                       : -1;\n    } else {\n        return avs_is_ok(avs_stream_write(\n                       target_stream,\n                       &(const unsigned char[]) {\n                               (unsigned char) (0xE0 | (codepoint >> 12)),\n                               (unsigned char) (0x80\n                                                | ((codepoint >> 6) & 0x3F)),\n                               (unsigned char) (0x80 | (codepoint & 0x3F)) }[0],\n                       3))\n                       ? 0\n                       : -1;\n    }\n}\n\nstatic int handle_string_escape(anjay_json_decoder_t *ctx,\n                                avs_stream_t *target_stream) {\n    unsigned char ch;\n    if (avs_is_err(avs_stream_getch(ctx->stream, (char *) &ch, NULL))) {\n        return -1;\n    }\n    switch (ch) {\n    case '\"':\n    case '\\\\':\n    case '/':\n        break;\n    case 'b':\n        ch = '\\b';\n        break;\n    case 'f':\n        ch = '\\f';\n        break;\n    case 'n':\n        ch = '\\n';\n        break;\n    case 'r':\n        ch = '\\r';\n        break;\n    case 't':\n        ch = '\\t';\n        break;\n    case 'u':\n        return handle_unicode_escape(ctx, target_stream);\n\n    default:\n        return -1;\n    }\n    return avs_is_ok(avs_stream_write(target_stream, &ch, 1)) ? 0 : -1;\n}\n\nstatic int json_decoder_bytes(anjay_json_like_decoder_t *ctx_,\n                              avs_stream_t *target_stream) {\n    anjay_json_decoder_t *ctx = (anjay_json_decoder_t *) ctx_;\n    if (ctx->state != ANJAY_JSON_LIKE_DECODER_STATE_OK\n            || ctx->current_item_type != ANJAY_JSON_LIKE_VALUE_TEXT_STRING) {\n        return -1;\n    }\n    unsigned char ch;\n    avs_error_t err = avs_stream_getch(ctx->stream, (char *) &ch, NULL);\n    assert(avs_is_ok(err));\n    assert(ch == '\"'); // previously checked using peek in preprocess_next_value\n    (void) err;\n    while (avs_is_ok(avs_stream_getch(ctx->stream, (char *) &ch, NULL))) {\n        AVS_STATIC_ASSERT(' ' == 0x20, ascii);\n        if (ch == '\"') {\n            preprocess_next_value(ctx);\n            return 0;\n        } else if (ch < ' ') {\n            // Note: includes EOF and other negative values\n            break;\n        } else if (ch == '\\\\') {\n            if (handle_string_escape(ctx, target_stream)) {\n                break;\n            }\n        } else if (avs_is_err(avs_stream_write(target_stream,\n                                               &(unsigned char) {\n                                                       (unsigned char) ch },\n                                               1))) {\n            // Note: the \"ch < ' '\" case includes EOF and other negative values\n            break;\n        }\n    }\n    ctx->state = ANJAY_JSON_LIKE_DECODER_STATE_ERROR;\n    return -1;\n}\n\nstatic int json_decoder_enter_array(anjay_json_like_decoder_t *ctx_) {\n    anjay_json_decoder_t *ctx = (anjay_json_decoder_t *) ctx_;\n    if (ctx->state != ANJAY_JSON_LIKE_DECODER_STATE_OK\n            || ctx->current_item_type != ANJAY_JSON_LIKE_VALUE_ARRAY\n            || push_nested_type(ctx, JSON_NESTED_ARRAY_ELEMENT)) {\n        return -1;\n    }\n    unsigned char ch;\n    avs_error_t err = avs_stream_getch(ctx->stream, (char *) &ch, NULL);\n    assert(avs_is_ok(err));\n    assert(ch == '['); // previously checked using peek in preprocess_next_value\n    (void) err;\n    (void) ch;\n    preprocess_first_nested_value(ctx);\n    return 0;\n}\n\nstatic int json_decoder_enter_map(anjay_json_like_decoder_t *ctx_) {\n    anjay_json_decoder_t *ctx = (anjay_json_decoder_t *) ctx_;\n    if (ctx->state != ANJAY_JSON_LIKE_DECODER_STATE_OK\n            || ctx->current_item_type != ANJAY_JSON_LIKE_VALUE_MAP\n            || push_nested_type(ctx, JSON_NESTED_MAP_KEY)) {\n        return -1;\n    }\n    unsigned char ch;\n    avs_error_t err = avs_stream_getch(ctx->stream, (char *) &ch, NULL);\n    assert(avs_is_ok(err));\n    assert(ch == '{'); // previously checked using peek in preprocess_next_value\n    (void) err;\n    (void) ch;\n    preprocess_first_nested_value(ctx);\n    return 0;\n}\n\nstatic void json_decoder_cleanup(anjay_json_like_decoder_t **ctx) {\n    if (ctx && *ctx) {\n        avs_free(*ctx);\n        *ctx = NULL;\n    }\n}\n\nstatic const anjay_json_like_decoder_vtable_t VTABLE = {\n    .state = json_decoder_state,\n    .current_value_type = json_decoder_current_value_type,\n#    ifdef ANJAY_WITH_LWM2M12\n    .read_null = json_decoder_null,\n#    endif // ANJAY_WITH_LWM2M12\n    .read_bool = json_decoder_bool,\n    .number = json_decoder_number,\n    .bytes = json_decoder_bytes,\n    .enter_array = json_decoder_enter_array,\n    .enter_map = json_decoder_enter_map,\n    .nesting_level = json_decoder_nesting_level,\n    .cleanup = json_decoder_cleanup\n};\n\nanjay_json_like_decoder_t *_anjay_json_decoder_new(avs_stream_t *stream) {\n    anjay_json_decoder_t *ctx =\n            (anjay_json_decoder_t *) avs_calloc(1, sizeof(*ctx));\n    if (ctx) {\n        ctx->vtable = &VTABLE;\n        ctx->stream = stream;\n        ctx->state = ANJAY_JSON_LIKE_DECODER_STATE_OK;\n        preprocess_value(ctx);\n    }\n    return (anjay_json_like_decoder_t *) ctx;\n}\n\n#    ifdef ANJAY_TEST\n#        include \"tests/core/io/json/json_decoder.c\"\n#    endif\n\n#endif // ANJAY_WITH_SENML_JSON\n"
  },
  {
    "path": "src/core/io/json/anjay_json_decoder.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ANJAY_IO_JSON_DECODER_H\n#define ANJAY_IO_JSON_DECODER_H\n\n#include \"../anjay_json_like_decoder.h\"\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\nanjay_json_like_decoder_t *_anjay_json_decoder_new(avs_stream_t *stream);\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif // ANJAY_IO_JSON_DECODER_H\n"
  },
  {
    "path": "src/core/observe/anjay_observe_core.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#ifdef ANJAY_WITH_OBSERVE\n\n#    include <inttypes.h>\n#    include <math.h>\n\n#    include <avsystem/commons/avs_errno.h>\n#    include <avsystem/commons/avs_stream_membuf.h>\n#    include <avsystem/commons/avs_stream_v_table.h>\n\n#    include <anjay_modules/anjay_time_defs.h>\n#    ifdef ANJAY_WITH_LWM2M_GATEWAY\n#        include <string.h>\n\n#        include <anjay_modules/anjay_dm_utils.h>\n#        include <anjay_modules/anjay_lwm2m_gateway.h>\n#    endif // ANJAY_WITH_LWM2M_GATEWAY\n\n#    include \"../anjay_core.h\"\n#    include \"../anjay_io_core.h\"\n#    include \"../anjay_servers_utils.h\"\n#    include \"../coap/anjay_content_format.h\"\n#    include \"../dm/anjay_dm_attributes.h\"\n#    include \"../dm/anjay_dm_read.h\"\n#    include \"../dm/anjay_dm_write_attrs.h\"\n#    include \"../dm/anjay_query.h\"\n\n#    define ANJAY_OBSERVE_SOURCE\n\n#    include \"../anjay_servers_inactive.h\"\n\n#    include \"anjay_observe_internal.h\"\n\nVISIBILITY_SOURCE_BEGIN\n\nstatic int32_t connection_ref_cmp(const anjay_connection_ref_t *left,\n                                  const anjay_connection_ref_t *right) {\n    int32_t tmp_diff = (int32_t) _anjay_server_ssid(left->server)\n                       - (int32_t) _anjay_server_ssid(right->server);\n    if (!tmp_diff) {\n        tmp_diff = (int32_t) left->conn_type - (int32_t) right->conn_type;\n    }\n    return tmp_diff;\n}\n\nint _anjay_observe_token_cmp(const avs_coap_token_t *left,\n                             const avs_coap_token_t *right) {\n    int tmp_diff = left->size - right->size;\n    if (!tmp_diff) {\n        tmp_diff = memcmp(left->bytes, right->bytes, left->size);\n    }\n    return tmp_diff < 0 ? -1 : (tmp_diff > 0 ? 1 : 0);\n}\n\nint _anjay_observation_cmp(const void *left, const void *right) {\n    return _anjay_observe_token_cmp(\n            &((const anjay_observation_t *) left)->token,\n            &((const anjay_observation_t *) right)->token);\n}\n\nint _anjay_observe_path_entry_cmp(const void *left, const void *right) {\n    return _anjay_uri_path_compare(\n            &((const anjay_observe_path_entry_t *) left)->path,\n            &((const anjay_observe_path_entry_t *) right)->path);\n}\n\nvoid _anjay_observe_init(anjay_observe_state_t *observe,\n                         bool confirmable_notifications,\n                         size_t stored_notification_limit) {\n    assert(!observe->connection_entries);\n    observe->confirmable_notifications = confirmable_notifications;\n\n    if (stored_notification_limit == 0) {\n        observe->notify_queue_limit_mode = NOTIFY_QUEUE_UNLIMITED;\n    } else {\n        observe->notify_queue_limit = stored_notification_limit;\n        observe->notify_queue_limit_mode = NOTIFY_QUEUE_DROP_OLDEST;\n    }\n}\n\nstatic inline bool is_error_value(const anjay_observation_value_t *value) {\n    return _anjay_observe_is_error_details(&value->details);\n}\n\nstatic void delete_value(anjay_unlocked_t *anjay,\n                         AVS_LIST(anjay_observation_value_t) *value_ptr) {\n    (void) anjay;\n    assert(value_ptr && *value_ptr);\n    if (!is_error_value(*value_ptr)) {\n        for (size_t i = 0; i < (*value_ptr)->ref->paths_count; ++i) {\n            if ((*value_ptr)->values[i]) {\n                _anjay_batch_release(&(*value_ptr)->values[i]);\n            }\n        }\n    }\n#    ifdef ANJAY_WITH_LWM2M12\n    (*value_ptr)->ref->notifications_count--;\n#    endif // ANJAY_WITH_LWM2M12\n    AVS_LIST_DELETE(value_ptr);\n}\n\nstatic inline const anjay_observe_path_entry_t *\npath_entry_query(const anjay_uri_path_t *path) {\n    return AVS_CONTAINER_OF(path, anjay_observe_path_entry_t, path);\n}\n\nstatic AVS_SORTED_SET_ELEM(anjay_observe_path_entry_t)\nfind_or_create_observe_path_entry(anjay_observe_connection_entry_t *connection,\n                                  const anjay_uri_path_t *path) {\n    AVS_SORTED_SET_ELEM(anjay_observe_path_entry_t) entry =\n            AVS_SORTED_SET_FIND(connection->observed_paths,\n                                path_entry_query(path));\n    if (!entry) {\n        AVS_SORTED_SET_ELEM(anjay_observe_path_entry_t) new_entry =\n                AVS_SORTED_SET_ELEM_NEW(anjay_observe_path_entry_t);\n        if (!new_entry) {\n            _anjay_log_oom();\n            return NULL;\n        }\n\n        memcpy((void *) (intptr_t) (const void *) &new_entry->path, path,\n               sizeof(*path));\n        entry = AVS_SORTED_SET_INSERT(connection->observed_paths, new_entry);\n        assert(entry == new_entry);\n    }\n    return entry;\n}\n\nstatic int add_path_to_observed_paths(\n        anjay_observe_connection_entry_t *conn,\n        const anjay_uri_path_t *path,\n        AVS_SORTED_SET_ELEM(anjay_observation_t) observation) {\n    AVS_SORTED_SET_ELEM(anjay_observe_path_entry_t) observed_path =\n            find_or_create_observe_path_entry(conn, path);\n    if (!observed_path) {\n        return -1;\n    }\n    AVS_LIST(AVS_SORTED_SET_ELEM(anjay_observation_t)) entry =\n            AVS_LIST_INSERT_NEW(AVS_SORTED_SET_ELEM(anjay_observation_t),\n                                &observed_path->refs);\n    if (!entry) {\n        _anjay_log_oom();\n        if (!observed_path->refs) {\n            AVS_SORTED_SET_DELETE_ELEM(conn->observed_paths, &observed_path);\n        }\n        return -1;\n    }\n    *entry = observation;\n    return 0;\n}\n\nstatic void remove_path_from_observed_paths(\n        anjay_observe_connection_entry_t *conn,\n        const anjay_uri_path_t *path,\n        AVS_SORTED_SET_ELEM(anjay_observation_t) observation) {\n    AVS_SORTED_SET_ELEM(anjay_observe_path_entry_t) observed_path =\n            AVS_SORTED_SET_FIND(conn->observed_paths, path_entry_query(path));\n    assert(observed_path);\n    AVS_LIST(AVS_SORTED_SET_ELEM(anjay_observation_t)) *ref_ptr;\n    AVS_LIST_FOREACH_PTR(ref_ptr, &observed_path->refs) {\n        if (**ref_ptr == observation) {\n            AVS_LIST_DELETE(ref_ptr);\n            if (!observed_path->refs) {\n                AVS_SORTED_SET_DELETE_ELEM(conn->observed_paths,\n                                           &observed_path);\n            }\n            return;\n        }\n    }\n    AVS_UNREACHABLE(\"Observation not attached to observed paths\");\n}\n\nint _anjay_observe_add_to_observed_paths(\n        anjay_observe_connection_entry_t *conn,\n        AVS_SORTED_SET_ELEM(anjay_observation_t) observation) {\n    for (size_t i = 0; i < observation->paths_count; ++i) {\n        int result = add_path_to_observed_paths(conn, &observation->paths[i],\n                                                observation);\n        if (result) {\n            for (size_t j = 0; j < i; ++j) {\n                remove_path_from_observed_paths(conn, &observation->paths[j],\n                                                observation);\n            }\n            return result;\n        }\n    }\n    return 0;\n}\n\nstatic void remove_from_observed_paths(\n        anjay_observe_connection_entry_t *conn,\n        AVS_SORTED_SET_ELEM(anjay_observation_t) observation) {\n    for (size_t i = 0; i < observation->paths_count; ++i) {\n        remove_path_from_observed_paths(conn, &observation->paths[i],\n                                        observation);\n    }\n}\n\nstatic void clear_observation(anjay_observe_connection_entry_t *connection,\n                              anjay_observation_t *observation) {\n    anjay_unlocked_t *anjay = _anjay_from_server(connection->conn_ref.server);\n    avs_sched_del(&observation->notify_task);\n    while (observation->last_sent) {\n        delete_value(anjay, &observation->last_sent);\n    }\n\n    if (observation->last_unsent) {\n        anjay_observation_value_t **unsent_ptr;\n        anjay_observation_value_t *helper;\n        anjay_observation_value_t *server_last_unsent = NULL;\n        AVS_LIST_DELETABLE_FOREACH_PTR(unsent_ptr, helper,\n                                       &connection->unsent) {\n            if ((*unsent_ptr)->ref != observation) {\n                server_last_unsent = *unsent_ptr;\n            } else {\n                delete_value(anjay, unsent_ptr);\n            }\n        }\n        connection->unsent_last = server_last_unsent;\n        observation->last_unsent = NULL;\n    }\n}\n\nstatic void\ndetach_observation(anjay_observe_connection_entry_t *conn,\n                   AVS_SORTED_SET_ELEM(anjay_observation_t) observation) {\n    AVS_SORTED_SET_DETACH(conn->observations, observation);\n    remove_from_observed_paths(conn, observation);\n}\n\nstatic void\ncleanup_observation(anjay_observe_connection_entry_t *conn,\n                    AVS_SORTED_SET_ELEM(anjay_observation_t) observation) {\n    if (conn->observed_paths) {\n        remove_from_observed_paths(conn, observation);\n    }\n    avs_sched_del(&observation->notify_task);\n    if (observation->last_sent) {\n        delete_value(_anjay_from_server(conn->conn_ref.server),\n                     &observation->last_sent);\n    }\n    assert(!observation->last_sent);\n}\n\nstatic void cleanup_connection_without_observations_set(\n        anjay_observe_connection_entry_t *conn) {\n    if (conn->conn_ref.server) {\n        anjay_unlocked_t *anjay = _anjay_from_server(conn->conn_ref.server);\n        while (conn->unsent) {\n            delete_value(anjay, &conn->unsent);\n        }\n    }\n    assert(!conn->unsent);\n    AVS_SORTED_SET_ELEM(anjay_observation_t) observation;\n    AVS_SORTED_SET_FOREACH(observation, conn->observations) {\n        cleanup_observation(conn, observation);\n    }\n    if (conn->observed_paths) {\n        assert(!AVS_SORTED_SET_FIRST(conn->observed_paths));\n        AVS_SORTED_SET_DELETE(&conn->observed_paths);\n    }\n    if (conn->flush_task) {\n        avs_sched_del(&conn->flush_task);\n    }\n}\n\nvoid _anjay_observe_cleanup_connection(anjay_observe_connection_entry_t *conn) {\n    cleanup_connection_without_observations_set(conn);\n    AVS_SORTED_SET_DELETE(&conn->observations);\n}\n\nstatic void\ncancel_ongoing_notify_exchange(anjay_observe_connection_entry_t *conn) {\n    avs_coap_ctx_t *coap = _anjay_connection_get_coap(conn->conn_ref);\n    if (coap && avs_coap_exchange_id_valid(conn->notify_exchange_id)) {\n        avs_coap_exchange_cancel(coap, conn->notify_exchange_id);\n    }\n    assert(!avs_coap_exchange_id_valid(conn->notify_exchange_id));\n    assert(!conn->serialization_state.membuf_stream);\n    assert(!conn->serialization_state.out_ctx);\n}\n\nvoid _anjay_observe_invalidate(anjay_connection_ref_t ref) {\n    AVS_LIST(anjay_observe_connection_entry_t) *conn_ptr =\n            _anjay_observe_find_connection_state(ref);\n    if (!conn_ptr) {\n        return;\n    }\n    cancel_ongoing_notify_exchange(*conn_ptr);\n\n    // We clean up the observations but not remove the actual sorted set\n    // elements, so that we still have the tokens ready for the later phase.\n    cleanup_connection_without_observations_set(*conn_ptr);\n\n    // Detach the connection entry so that it won't be found by\n    // _anjay_observe_find_connection_state() in _anjay_observe_cancel_handler()\n    AVS_LIST(anjay_observe_connection_entry_t) conn = AVS_LIST_DETACH(conn_ptr);\n    avs_coap_ctx_t *coap = _anjay_connection_get_coap(ref);\n\n    // Now cancel the observations. We're doing it after having cleaned up all\n    // the state first - otherwise cancelling each observation would reschedule\n    // things related to the next scheduled one, might re-read attributes and\n    // such. That would be very suboptimal considering that we're removing all\n    // of them.\n    AVS_SORTED_SET_DELETE(&conn->observations) {\n        if (coap) {\n            avs_coap_observe_cancel(coap,\n                                    (avs_coap_observe_id_t) {\n                                        .token = (*conn->observations)->token\n                                    });\n        }\n    }\n    AVS_LIST_DELETE(&conn);\n}\n\nvoid _anjay_observe_cleanup(anjay_observe_state_t *observe) {\n    AVS_LIST_CLEAR(&observe->connection_entries) {\n        _anjay_observe_cleanup_connection(observe->connection_entries);\n    }\n}\n\nstatic void\ndelete_connection(AVS_LIST(anjay_observe_connection_entry_t) *conn_ptr) {\n    _anjay_observe_cleanup_connection(*conn_ptr);\n    AVS_LIST_DELETE(conn_ptr);\n}\n\nstatic void delete_connection_if_empty(\n        AVS_LIST(anjay_observe_connection_entry_t) *conn_ptr) {\n    if (!AVS_SORTED_SET_FIRST((*conn_ptr)->observations)) {\n        assert(!AVS_SORTED_SET_FIRST((*conn_ptr)->observed_paths));\n        assert(!(*conn_ptr)->unsent);\n        assert(!(*conn_ptr)->unsent_last);\n        delete_connection(conn_ptr);\n    }\n}\n\ntypedef struct {\n    anjay_observe_connection_entry_t *conn_state;\n    anjay_observation_t *observation;\n} trigger_observe_args_t;\n\nstatic void trigger_observe(avs_sched_t *sched, const void *args_);\n\nstatic const anjay_observation_value_t *\nnewest_value(const anjay_observation_t *observation) {\n    if (observation->last_unsent) {\n        return observation->last_unsent;\n    } else {\n        assert(observation->last_sent);\n        return observation->last_sent;\n    }\n}\n\ntypedef enum {\n    SCHEDULE_PERIOD_MIN,\n    SCHEDULE_PERIOD_MAX\n} schedule_period_type_t;\n\nstatic int schedule_trigger(anjay_observe_connection_entry_t *conn_state,\n                            anjay_observation_t *observation,\n                            int32_t period,\n                            schedule_period_type_t period_type) {\n    if (period < 0) {\n        return 0;\n    }\n\n    avs_time_monotonic_t monotonic_now = avs_time_monotonic_now();\n    avs_time_real_t real_now = avs_time_real_now();\n\n    avs_time_real_t trigger_instant_real = avs_time_real_add(\n            newest_value(observation)->timestamp,\n            avs_time_duration_from_scalar(period, AVS_TIME_S));\n    if (avs_time_real_before(trigger_instant_real, real_now)) {\n        trigger_instant_real = real_now;\n    }\n\n    if (!avs_time_real_before(conn_state->next_trigger, trigger_instant_real)) {\n        conn_state->next_trigger = trigger_instant_real;\n    }\n    if (period_type == SCHEDULE_PERIOD_MAX\n            && !avs_time_real_before(observation->next_pmax_trigger,\n                                     trigger_instant_real)) {\n        observation->next_pmax_trigger = trigger_instant_real;\n        if (!avs_time_real_before(conn_state->next_pmax_trigger,\n                                  trigger_instant_real)) {\n            conn_state->next_pmax_trigger = trigger_instant_real;\n        }\n    }\n\n    avs_time_monotonic_t trigger_instant_monotonic = avs_time_monotonic_add(\n            monotonic_now, avs_time_real_diff(trigger_instant_real, real_now));\n    if (avs_time_monotonic_before(avs_sched_time(&observation->notify_task),\n                                  trigger_instant_monotonic)) {\n        anjay_log(\n                LAZY_TRACE,\n                _(\"Notify for token \") \"%s\" _(\" already scheduled earlier \"\n                                              \"than requested \") \"%ld.%09lds\",\n                ANJAY_TOKEN_TO_STRING(observation->token),\n                (long) trigger_instant_monotonic.since_monotonic_epoch.seconds,\n                (long) trigger_instant_monotonic.since_monotonic_epoch\n                        .nanoseconds);\n        return 0;\n    }\n\n    anjay_log(\n            LAZY_TRACE,\n            _(\"Notify for token \") \"%s\" _(\" scheduled: \") \"%ld.%09lds\",\n            ANJAY_TOKEN_TO_STRING(observation->token),\n            (long) trigger_instant_monotonic.since_monotonic_epoch.seconds,\n            (long) trigger_instant_monotonic.since_monotonic_epoch.nanoseconds);\n\n    int retval =\n            AVS_SCHED_AT(_anjay_from_server(conn_state->conn_ref.server)->sched,\n                         &observation->notify_task, trigger_instant_monotonic,\n                         trigger_observe,\n                         (&(const trigger_observe_args_t) {\n                             .conn_state = conn_state,\n                             .observation = observation\n                         }),\n                         sizeof(trigger_observe_args_t));\n    if (retval) {\n        anjay_log(ERROR,\n                  _(\"Could not schedule automatic notification trigger, \"\n                    \"result: \") \"%d\",\n                  retval);\n    }\n    return retval;\n}\n\nstatic AVS_LIST(anjay_observation_value_t)\ncreate_observation_value(const anjay_msg_details_t *details,\n                         avs_coap_notify_reliability_hint_t reliability_hint,\n                         anjay_observation_t *ref,\n                         const avs_time_real_t *timestamp,\n                         const anjay_batch_t *const *values) {\n    const size_t values_count =\n            _anjay_observe_is_error_details(details) ? 0 : ref->paths_count;\n    const size_t element_size = offsetof(anjay_observation_value_t, values)\n                                + values_count * sizeof(anjay_batch_t *);\n    AVS_LIST(anjay_observation_value_t) result = (AVS_LIST(\n            anjay_observation_value_t)) AVS_LIST_NEW_BUFFER(element_size);\n    if (!result) {\n        _anjay_log_oom();\n        return NULL;\n    }\n    result->details = *details;\n    result->reliability_hint = reliability_hint;\n    memcpy((void *) (intptr_t) (const void *) &result->ref, &ref, sizeof(ref));\n    result->timestamp = *timestamp;\n    for (size_t i = 0; i < values_count; ++i) {\n        assert(values);\n        assert(values[i]);\n        if (!(result->values[i] = _anjay_batch_acquire(values[i]))) {\n            AVS_LIST_CLEAR(&result);\n            break;\n        }\n    }\n    return result;\n}\n\nstatic size_t count_queued_notifications(const anjay_observe_state_t *observe) {\n    size_t count = 0;\n\n    AVS_LIST(anjay_observe_connection_entry_t) conn;\n    AVS_LIST_FOREACH(conn, observe->connection_entries) {\n        count += AVS_LIST_SIZE(conn->unsent);\n    }\n\n    return count;\n}\n\nstatic bool is_observe_queue_full(const anjay_observe_state_t *observe) {\n    if (observe->notify_queue_limit_mode == NOTIFY_QUEUE_UNLIMITED) {\n        return false;\n    }\n\n    size_t num_queued = count_queued_notifications(observe);\n    anjay_log(TRACE, \"%u/%u\" _(\" queued notifications\"), (unsigned) num_queued,\n              (unsigned) observe->notify_queue_limit);\n\n    assert(num_queued <= observe->notify_queue_limit);\n    return num_queued >= observe->notify_queue_limit;\n}\n\nstatic AVS_LIST(anjay_observe_connection_entry_t)\nfind_oldest_queued_notification(anjay_observe_state_t *observe) {\n    AVS_LIST(anjay_observe_connection_entry_t) oldest = NULL;\n\n    AVS_LIST(anjay_observe_connection_entry_t) conn;\n    AVS_LIST_FOREACH(conn, observe->connection_entries) {\n        if (conn->unsent) {\n            if (!oldest\n                    || avs_time_real_before(conn->unsent->timestamp,\n                                            oldest->unsent->timestamp)) {\n                oldest = conn;\n            }\n        }\n    }\n\n    return oldest;\n}\n\nstatic anjay_observation_value_t *\ndetach_first_unsent_value(anjay_observe_connection_entry_t *conn_state) {\n    assert(conn_state->unsent);\n    anjay_observation_t *observation = conn_state->unsent->ref;\n    if (observation->last_unsent == conn_state->unsent) {\n        observation->last_unsent = NULL;\n    }\n    anjay_observation_value_t *result = AVS_LIST_DETACH(&conn_state->unsent);\n    if (conn_state->unsent_last == result) {\n        assert(!conn_state->unsent);\n        conn_state->unsent_last = NULL;\n    }\n    return result;\n}\n\nstatic void drop_oldest_queued_notification(anjay_unlocked_t *anjay,\n                                            anjay_observe_state_t *observe) {\n    AVS_LIST(anjay_observe_connection_entry_t) oldest =\n            find_oldest_queued_notification(observe);\n\n    AVS_ASSERT(oldest, \"function is not supposed to be called when there are \"\n                       \"no queued notifications\");\n\n    anjay_observation_value_t *entry = detach_first_unsent_value(oldest);\n    delete_value(anjay, &entry);\n}\n\n#    ifdef ANJAY_WITH_LWM2M12\nstatic bool is_historical_queue_full(const anjay_observation_t *observation) {\n    if (observation->historical_queue_size < 1) {\n        return false;\n    }\n\n    anjay_log(TRACE, \"%u/%u\" _(\" notifications in historical queue\"),\n              (unsigned) observation->notifications_count,\n              (unsigned) observation->historical_queue_size);\n\n    return observation->notifications_count\n           >= (size_t) observation->historical_queue_size;\n}\n\nstatic AVS_LIST(anjay_observation_value_t) *\nfind_oldest_queued_notification_from_observation(\n        anjay_observe_connection_entry_t *conn,\n        anjay_observation_t *observation) {\n    AVS_LIST(anjay_observation_value_t) *unsent_value;\n    AVS_LIST_FOREACH_PTR(unsent_value, &conn->unsent) {\n        if (unsent_value && *unsent_value\n                && (*unsent_value)->ref == observation) {\n            return unsent_value;\n        }\n    }\n\n    return NULL;\n}\n\nstatic anjay_observation_value_t *detach_first_unsent_value_from_observation(\n        AVS_LIST(anjay_observation_value_t) *value,\n        anjay_observe_connection_entry_t *conn_state,\n        anjay_observation_t *observation) {\n    assert(conn_state->unsent);\n    if (observation->last_unsent == *value) {\n        observation->last_unsent = NULL;\n    }\n\n    anjay_observation_value_t *result = AVS_LIST_DETACH(value);\n\n    if (conn_state->unsent_last == result) {\n        assert(!conn_state->unsent);\n        conn_state->unsent_last = NULL;\n    }\n\n    return result;\n}\n\nstatic void drop_oldest_queued_notification_from_observation(\n        anjay_observe_connection_entry_t *conn_state,\n        anjay_observation_t *observation) {\n    anjay_unlocked_t *anjay = _anjay_from_server(conn_state->conn_ref.server);\n    AVS_LIST(anjay_observation_value_t) *oldest_value =\n            find_oldest_queued_notification_from_observation(conn_state,\n                                                             observation);\n\n    AVS_ASSERT(oldest_value,\n               \"function is not supposed to be called when there are \"\n               \"no notifications in historical queue\");\n\n    anjay_observation_value_t *entry =\n            detach_first_unsent_value_from_observation(oldest_value, conn_state,\n                                                       observation);\n    delete_value(anjay, &entry);\n}\n#    endif // ANJAY_WITH_LWM2M12\n\nstatic int insert_new_value(anjay_observe_connection_entry_t *conn_state,\n                            anjay_observation_t *observation,\n                            avs_coap_notify_reliability_hint_t reliability_hint,\n                            const anjay_msg_details_t *details,\n                            const avs_time_real_t *timestamp,\n                            const anjay_batch_t *const *values) {\n    anjay_unlocked_t *anjay = _anjay_from_server(conn_state->conn_ref.server);\n    anjay_observe_state_t *observe = &anjay->observe;\n#    ifdef ANJAY_WITH_LWM2M12\n    if (is_historical_queue_full(observation)) {\n        drop_oldest_queued_notification_from_observation(conn_state,\n                                                         observation);\n    } else\n#    endif // ANJAY_WITH_LWM2M12\n            if (is_observe_queue_full(observe)) {\n        switch (observe->notify_queue_limit_mode) {\n        case NOTIFY_QUEUE_UNLIMITED:\n            AVS_UNREACHABLE(\"is_observe_queue_full broken\");\n            return -1;\n\n        case NOTIFY_QUEUE_DROP_OLDEST:\n            assert(observe->notify_queue_limit != 0);\n            drop_oldest_queued_notification(anjay, observe);\n            break;\n        }\n    }\n\n    AVS_LIST(anjay_observation_value_t) res_value =\n            create_observation_value(details, reliability_hint, observation,\n                                     timestamp, values);\n    if (!res_value) {\n        return -1;\n    }\n\n    AVS_LIST_APPEND(&conn_state->unsent_last, res_value);\n#    ifdef ANJAY_WITH_LWM2M12\n    observation->notifications_count++;\n#    endif // ANJAY_WITH_LWM2M12\n    conn_state->unsent_last = res_value;\n    if (!conn_state->unsent) {\n        conn_state->unsent = res_value;\n    }\n    observation->last_unsent = res_value;\n    return 0;\n}\n\nstatic int insert_error(anjay_observe_connection_entry_t *conn_state,\n                        anjay_observation_t *observation,\n                        int outer_result) {\n    avs_sched_del(&observation->notify_task);\n    const anjay_msg_details_t details = {\n        .msg_code = _anjay_make_error_response_code(outer_result),\n        .format = AVS_COAP_FORMAT_NONE\n    };\n    if (details.msg_code != -outer_result) {\n        anjay_log(DEBUG, _(\"invalid error code: \") \"%d\", outer_result);\n    }\n    const avs_time_real_t timestamp = avs_time_real_now();\n    return insert_new_value(conn_state, observation,\n                            AVS_COAP_NOTIFY_PREFER_CONFIRMABLE, &details,\n                            &timestamp, NULL);\n}\n\nstatic int get_effective_attrs(anjay_unlocked_t *anjay,\n                               anjay_dm_r_attributes_t *out_attrs,\n                               const anjay_uri_path_t *path,\n                               anjay_ssid_t ssid) {\n    const anjay_dm_t *dm;\n#    ifdef ANJAY_WITH_LWM2M_GATEWAY\n    if (_anjay_uri_path_has_prefix(path)) {\n        if (_anjay_lwm2m_gateway_prefix_to_dm(anjay, path->prefix, &dm)) {\n            return ANJAY_ERR_NOT_FOUND;\n        }\n    } else\n#    endif // ANJAY_WITH_LWM2M_GATEWAY\n    {\n        dm = &anjay->dm;\n    }\n    anjay_dm_attrs_query_details_t details = {\n        .obj = _anjay_uri_path_has(path, ANJAY_ID_OID)\n                       ? _anjay_dm_find_object_by_oid(dm,\n                                                      path->ids[ANJAY_ID_OID])\n                       : NULL,\n        .iid = ANJAY_ID_INVALID,\n        .rid = ANJAY_ID_INVALID,\n        .riid = ANJAY_ID_INVALID,\n        .ssid = ssid,\n        .with_server_level_attrs = true\n    };\n\n    if (details.obj && _anjay_uri_path_has(path, ANJAY_ID_IID)\n            && !_anjay_dm_verify_instance_present(anjay, details.obj,\n                                                  path->ids[ANJAY_ID_IID])) {\n        details.iid = path->ids[ANJAY_ID_IID];\n    } else {\n        return _anjay_dm_effective_attrs(anjay, &details, out_attrs);\n    }\n\n    if (_anjay_uri_path_has(path, ANJAY_ID_RID)\n            && !_anjay_dm_verify_resource_present(\n                       anjay, details.obj, path->ids[ANJAY_ID_IID],\n                       path->ids[ANJAY_ID_RID], NULL)) {\n        details.rid = path->ids[ANJAY_ID_RID];\n    } else {\n        return _anjay_dm_effective_attrs(anjay, &details, out_attrs);\n    }\n\n    if (_anjay_uri_path_has(path, ANJAY_ID_RIID)\n            && !_anjay_dm_verify_resource_instance_present(\n                       anjay, details.obj, path->ids[ANJAY_ID_IID],\n                       path->ids[ANJAY_ID_RID], path->ids[ANJAY_ID_RIID])) {\n        details.riid = path->ids[ANJAY_ID_RIID];\n    }\n    return _anjay_dm_effective_attrs(anjay, &details, out_attrs);\n}\n\nstatic inline bool is_pmax_valid(anjay_dm_oi_attributes_t attr) {\n    if (attr.max_period < 0) {\n        return false;\n    }\n\n    if (attr.max_period == 0 || attr.max_period < attr.min_period) {\n        anjay_log(DEBUG,\n                  _(\"invalid pmax (\") \"%\" PRIi32 _(\n                          \"); expected pmax > 0 && pmax >= pmin (\") \"%\" PRIi32\n                          _(\")\"),\n                  attr.max_period, attr.min_period);\n        return false;\n    }\n\n    return true;\n}\n\nstatic void update_batch_pmax(int32_t *out_ptr,\n                              const anjay_dm_r_attributes_t *attrs) {\n    if (is_pmax_valid(attrs->common)\n            && (*out_ptr < 0 || attrs->common.max_period < *out_ptr)) {\n        *out_ptr = attrs->common.max_period;\n    }\n}\n\nint _anjay_observe_schedule_pmax_trigger(\n        anjay_observe_connection_entry_t *conn_state,\n        anjay_observation_t *observation) {\n    int32_t pmax = -1;\n\n#    ifdef ANJAY_WITH_OBSERVATION_ATTRIBUTES\n    if (!_anjay_dm_resource_attributes_empty(&observation->attrs)) {\n        pmax = observation->attrs.common.max_period;\n        if (pmax < 0) {\n            int result;\n            anjay_iid_t server_iid;\n            anjay_unlocked_t *anjay =\n                    _anjay_from_server(conn_state->conn_ref.server);\n            if ((result = _anjay_find_server_iid(\n                         anjay, _anjay_server_ssid(conn_state->conn_ref.server),\n                         &server_iid))\n                    || (result = _anjay_read_period(\n                                anjay, server_iid,\n                                ANJAY_DM_RID_SERVER_DEFAULT_PMAX, &pmax))) {\n                return result;\n            }\n        }\n    } else {\n#    endif // ANJAY_WITH_OBSERVATION_ATTRIBUTES\n        for (size_t i = 0; i < observation->paths_count; ++i) {\n            anjay_dm_r_attributes_t attrs;\n            int result = get_effective_attrs(\n                    _anjay_from_server(conn_state->conn_ref.server), &attrs,\n                    &observation->paths[i],\n                    _anjay_server_ssid(conn_state->conn_ref.server));\n            if (result) {\n                anjay_log(DEBUG,\n                          _(\"Could not get observe attributes, result: \") \"%d\",\n                          result);\n                return result;\n            }\n\n            update_batch_pmax(&pmax, &attrs);\n        }\n#    ifdef ANJAY_WITH_OBSERVATION_ATTRIBUTES\n    }\n#    endif // ANJAY_WITH_OBSERVATION_ATTRIBUTES\n\n    if (pmax >= 0) {\n        return schedule_trigger(conn_state, observation, pmax,\n                                SCHEDULE_PERIOD_MAX);\n    }\n    return 0;\n}\n\nstatic int insert_initial_value(anjay_observe_connection_entry_t *conn_state,\n                                anjay_observation_t *observation,\n                                const anjay_msg_details_t *details,\n                                const avs_time_real_t *timestamp,\n                                const anjay_batch_t *const *values) {\n    assert(!observation->last_sent);\n    assert(!observation->last_unsent);\n\n    avs_time_real_t now = avs_time_real_now();\n\n    int result = -1;\n    // we assume that the initial value should be treated as sent,\n    // even though we haven't actually sent it ourselves\n    if ((observation->last_sent = create_observation_value(\n                 details, AVS_COAP_NOTIFY_PREFER_NON_CONFIRMABLE, observation,\n                 timestamp, values))\n            && !(result = _anjay_observe_schedule_pmax_trigger(conn_state,\n                                                               observation))) {\n        observation->last_confirmable = now;\n    }\n    return result;\n}\n\ntypedef enum { PATHS_POINTER_LIST, PATHS_POINTER_ARRAY } paths_pointer_type_t;\n\ntypedef struct {\n    paths_pointer_type_t type;\n    const anjay_uri_path_t *paths;\n    size_t count;\n} paths_arg_t;\n\nstatic AVS_SORTED_SET_ELEM(anjay_observation_t)\ncreate_detached_observation(const avs_coap_token_t *token,\n                            const anjay_request_t *request,\n                            const paths_arg_t *paths\n#    ifdef ANJAY_WITH_OBSERVATION_ATTRIBUTES\n                            ,\n                            const anjay_dm_r_attributes_t *attributes\n#    endif // ANJAY_WITH_OBSERVATION_ATTRIBUTES\n) {\n    AVS_SORTED_SET_ELEM(anjay_observation_t) new_observation =\n            (AVS_SORTED_SET_ELEM(anjay_observation_t))\n                    AVS_SORTED_SET_ELEM_NEW_BUFFER(\n                            offsetof(anjay_observation_t, paths)\n                            + paths->count * sizeof(const anjay_uri_path_t));\n    if (!new_observation) {\n        _anjay_log_oom();\n        return NULL;\n    }\n    memcpy((void *) (intptr_t) (const void *) &new_observation->token, token,\n           sizeof(*token));\n    memcpy((void *) (intptr_t) (const void *) &new_observation->action,\n           &request->action, sizeof(request->action));\n    memcpy((void *) (intptr_t) (const void *) &new_observation->paths_count,\n           &paths->count, sizeof(paths->count));\n    if (paths->type == PATHS_POINTER_LIST) {\n        AVS_LIST(anjay_uri_path_t) it =\n                (AVS_LIST(anjay_uri_path_t)) (intptr_t) paths->paths;\n        for (size_t i = 0; i < paths->count; ++i) {\n            memcpy((void *) (intptr_t) (const void *) &new_observation\n                           ->paths[i],\n                   it, sizeof(*it));\n            AVS_LIST_ADVANCE(&it);\n        }\n    } else {\n        assert(paths->count == 1);\n        memcpy((void *) (intptr_t) (const void *) &new_observation->paths[0],\n               paths->paths, sizeof(*paths->paths));\n    }\n    new_observation->next_pmax_trigger = AVS_TIME_REAL_INVALID;\n#    ifdef ANJAY_WITH_LWM2M12\n    new_observation->historical_queue_size = -1;\n#    endif // ANJAY_WITH_LWM2M12\n#    ifdef ANJAY_WITH_OBSERVATION_ATTRIBUTES\n    memcpy((void *) (intptr_t) (const void *) &new_observation->attrs,\n           attributes, sizeof(new_observation->attrs));\n#    endif // ANJAY_WITH_OBSERVATION_ATTRIBUTES\n    return new_observation;\n}\n\nstatic AVS_LIST(anjay_observe_connection_entry_t) *\nfind_connection_state_insert_ptr(anjay_connection_ref_t ref) {\n    AVS_LIST(anjay_observe_connection_entry_t) *conn_ptr =\n            &_anjay_from_server(ref.server)->observe.connection_entries;\n    while (*conn_ptr && connection_ref_cmp(&(*conn_ptr)->conn_ref, &ref) < 0) {\n        AVS_LIST_ADVANCE_PTR(&conn_ptr);\n    }\n    return conn_ptr;\n}\n\nAVS_LIST(anjay_observe_connection_entry_t) *\n_anjay_observe_find_connection_state(anjay_connection_ref_t ref) {\n    AVS_LIST(anjay_observe_connection_entry_t) *conn_ptr =\n            find_connection_state_insert_ptr(ref);\n    if (*conn_ptr && connection_ref_cmp(&(*conn_ptr)->conn_ref, &ref) == 0) {\n        return conn_ptr;\n    }\n    return NULL;\n}\n\nstatic AVS_LIST(anjay_observe_connection_entry_t) *\nfind_or_create_connection_state(anjay_connection_ref_t ref) {\n    AVS_LIST(anjay_observe_connection_entry_t) *conn_ptr =\n            find_connection_state_insert_ptr(ref);\n    if (!*conn_ptr || connection_ref_cmp(&(*conn_ptr)->conn_ref, &ref) != 0) {\n        if (!AVS_LIST_INSERT_NEW(anjay_observe_connection_entry_t, conn_ptr)\n                || !((*conn_ptr)->observations = AVS_SORTED_SET_NEW(\n                             anjay_observation_t, _anjay_observation_cmp))\n                || !((*conn_ptr)->observed_paths = AVS_SORTED_SET_NEW(\n                             anjay_observe_path_entry_t,\n                             _anjay_observe_path_entry_cmp))) {\n            _anjay_log_oom();\n            if (*conn_ptr) {\n                AVS_SORTED_SET_DELETE(&(*conn_ptr)->observations);\n                AVS_LIST_DELETE(conn_ptr);\n            }\n            return NULL;\n        }\n        memcpy((void *) (intptr_t) (const void *) &(*conn_ptr)->conn_ref, &ref,\n               sizeof(ref));\n        (*conn_ptr)->next_trigger = AVS_TIME_REAL_INVALID;\n        (*conn_ptr)->next_pmax_trigger = AVS_TIME_REAL_INVALID;\n    }\n    return conn_ptr;\n}\n\nstatic int schedule_all_triggers(anjay_observe_connection_entry_t *conn) {\n    int result = 0;\n    AVS_SORTED_SET_ELEM(anjay_observation_t) observation;\n    AVS_SORTED_SET_FOREACH(observation, conn->observations) {\n        if (!observation->notify_task) {\n            _anjay_update_ret(&result, _anjay_observe_schedule_pmax_trigger(\n                                               conn, observation));\n        }\n    }\n    return result;\n}\n\nstatic void flush_next_unsent(anjay_observe_connection_entry_t *conn);\n\nstatic void flush_send_queue_job(avs_sched_t *sched, const void *conn_ptr) {\n    anjay_t *anjay_locked = _anjay_get_from_sched(sched);\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    anjay_observe_connection_entry_t *conn =\n            *(anjay_observe_connection_entry_t *const *) conn_ptr;\n    if (conn && conn->unsent\n            && !avs_coap_exchange_id_valid(conn->notify_exchange_id)\n            && _anjay_connection_ready_for_outgoing_message(conn->conn_ref)\n            && _anjay_connection_get_online_socket(conn->conn_ref)) {\n        flush_next_unsent(conn);\n    }\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n}\n\nstatic int\nsched_flush_send_queue(AVS_LIST(anjay_observe_connection_entry_t) conn) {\n    if (conn->flush_task\n            || avs_coap_exchange_id_valid(conn->notify_exchange_id)) {\n        anjay_log(TRACE, _(\"skipping notification flush scheduling: flush \"\n                           \"already scheduled\"));\n        return 0;\n    }\n    if (AVS_SCHED_NOW(_anjay_from_server(conn->conn_ref.server)->sched,\n                      &conn->flush_task, flush_send_queue_job, &conn,\n                      sizeof(conn))) {\n        anjay_log(WARNING, _(\"Could not schedule notification flush\"));\n        return -1;\n    }\n    return 0;\n}\n\nstatic int sched_flush(anjay_observe_connection_entry_t *conn) {\n    if (conn->unsent) {\n        return sched_flush_send_queue(conn);\n    } else {\n        return schedule_all_triggers(conn);\n    }\n}\n\nstatic void\ndelete_observation(AVS_LIST(anjay_observe_connection_entry_t) *conn_ptr,\n                   AVS_SORTED_SET_ELEM(anjay_observation_t) *observation_ptr) {\n    AVS_LIST(anjay_observe_connection_entry_t) conn = *conn_ptr;\n    clear_observation(conn, *observation_ptr);\n    detach_observation(conn, *observation_ptr);\n    AVS_SORTED_SET_ELEM_DELETE_DETACHED(observation_ptr);\n    delete_connection_if_empty(conn_ptr);\n\n    if (*conn_ptr == conn) {\n        // Connection has not been removed\n        sched_flush(conn);\n    }\n}\n\nstatic void\nrecalculate_conn_trigger_times(anjay_observe_connection_entry_t *conn) {\n    avs_time_monotonic_t monotonic_now = avs_time_monotonic_now();\n    avs_time_real_t real_now = avs_time_real_now();\n    conn->next_trigger = AVS_TIME_REAL_INVALID;\n    conn->next_pmax_trigger = AVS_TIME_REAL_INVALID;\n    AVS_SORTED_SET_ELEM(anjay_observation_t) observation;\n    AVS_SORTED_SET_FOREACH(observation, conn->observations) {\n        if (avs_time_real_valid(observation->next_pmax_trigger)\n                && !avs_time_real_before(conn->next_pmax_trigger,\n                                         observation->next_pmax_trigger)) {\n            conn->next_pmax_trigger = observation->next_pmax_trigger;\n        }\n        avs_time_real_t next_trigger = avs_time_real_add(\n                real_now,\n                avs_time_monotonic_diff(avs_sched_time(\n                                                &observation->notify_task),\n                                        monotonic_now));\n        if (avs_time_real_valid(next_trigger)\n                && !avs_time_real_before(conn->next_trigger, next_trigger)) {\n            conn->next_trigger = next_trigger;\n        }\n    }\n}\n\nstatic void observe_remove_entry(anjay_connection_ref_t connection,\n                                 const avs_coap_token_t *token) {\n    AVS_LIST(anjay_observe_connection_entry_t) *conn_ptr =\n            _anjay_observe_find_connection_state(connection);\n    if (!conn_ptr) {\n        return;\n    }\n    cancel_ongoing_notify_exchange(*conn_ptr);\n    AVS_SORTED_SET_ELEM(anjay_observation_t) observation =\n            AVS_SORTED_SET_FIND((*conn_ptr)->observations,\n                                _anjay_observation_query(token));\n    if (observation) {\n        delete_observation(conn_ptr, &observation);\n    }\n    if (*conn_ptr) {\n        recalculate_conn_trigger_times(*conn_ptr);\n    }\n}\n\nvoid _anjay_observe_cancel_handler(avs_coap_observe_id_t id, void *ref_ptr) {\n    observe_remove_entry(*(anjay_connection_ref_t *) ref_ptr, &id.token);\n    avs_free(ref_ptr);\n}\n\nstatic int start_coap_observe(anjay_connection_ref_t connection,\n                              const anjay_request_t *request) {\n    anjay_connection_ref_t *heap_conn = (anjay_connection_ref_t *) avs_malloc(\n            sizeof(anjay_connection_ref_t));\n    if (!heap_conn) {\n        return -1;\n    }\n    *heap_conn = connection;\n    if (avs_is_err(avs_coap_observe_streaming_start(\n                request->ctx, *request->observe, _anjay_observe_cancel_handler,\n                heap_conn))) {\n        avs_free(heap_conn);\n        return -1;\n    }\n    return 0;\n}\n\nstatic int\nattach_new_observation(anjay_observe_connection_entry_t *conn_state,\n                       AVS_SORTED_SET_ELEM(anjay_observation_t) observation) {\n    AVS_SORTED_SET_INSERT(conn_state->observations, observation);\n    int result = _anjay_observe_add_to_observed_paths(conn_state, observation);\n    if (result) {\n        AVS_SORTED_SET_DETACH(conn_state->observations, observation);\n    }\n    return result;\n}\n\nstatic AVS_SORTED_SET_ELEM(anjay_observation_t)\nput_entry_into_connection_state(const anjay_request_t *request,\n                                anjay_observe_connection_entry_t *conn_state,\n                                const paths_arg_t *paths\n#    ifdef ANJAY_WITH_OBSERVATION_ATTRIBUTES\n                                ,\n                                const anjay_dm_r_attributes_t *attributes\n#    endif // ANJAY_WITH_OBSERVATION_ATTRIBUTES\n) {\n    AVS_SORTED_SET_ELEM(anjay_observation_t) observation =\n            create_detached_observation(&request->observe->token, request, paths\n#    ifdef ANJAY_WITH_OBSERVATION_ATTRIBUTES\n                                        ,\n                                        attributes\n#    endif // ANJAY_WITH_OBSERVATION_ATTRIBUTES\n            );\n    if (!observation) {\n        return NULL;\n    }\n    assert(!AVS_SORTED_SET_FIND(conn_state->observations, observation));\n\n    if (attach_new_observation(conn_state, observation)) {\n        clear_observation(conn_state, observation);\n        AVS_SORTED_SET_ELEM_DELETE_DETACHED(&observation);\n        return NULL;\n    }\n    return observation;\n}\n\nstatic int read_as_batch(anjay_unlocked_t *anjay,\n                         const anjay_dm_installed_object_t *obj_ptr,\n                         const anjay_dm_path_info_t *path_info,\n                         anjay_request_action_t action,\n                         anjay_ssid_t connection_ssid,\n                         const avs_time_real_t *timestamp,\n                         anjay_batch_t **out_batch) {\n    assert(out_batch && !*out_batch);\n    anjay_batch_builder_t *builder = _anjay_batch_builder_new();\n    if (!builder) {\n        _anjay_log_oom();\n        return -1;\n    }\n\n    int result = _anjay_dm_read_into_batch(builder, anjay, obj_ptr, path_info,\n                                           connection_ssid, timestamp);\n#    if defined(ANJAY_WITH_LWM2M11) \\\n            && !defined(ANJAY_WITHOUT_COMPOSITE_OPERATIONS)\n    if (action == ANJAY_ACTION_READ_COMPOSITE\n            && (result == ANJAY_ERR_UNAUTHORIZED\n                || result == ANJAY_ERR_NOT_FOUND)) {\n        result = 0;\n    }\n#    else  // defined(ANJAY_WITH_LWM2M11) &&\n           // !defined(ANJAY_WITHOUT_COMPOSITE_OPERATIONS)\n    (void) action;\n#    endif // defined(ANJAY_WITH_LWM2M11) &&\n           // !defined(ANJAY_WITHOUT_COMPOSITE_OPERATIONS)\n    if (!result && !(*out_batch = _anjay_batch_builder_compile(&builder))) {\n        _anjay_log_oom();\n        result = -1;\n    }\n    _anjay_batch_builder_cleanup(&builder);\n    return result;\n}\n\nstatic inline const anjay_batch_t *const *\ncast_to_const_batch_array(anjay_batch_t **batch_array) {\n    return (const anjay_batch_t *const *) batch_array;\n}\n\n#    if defined(ANJAY_WITH_LWM2M11) \\\n            && !defined(ANJAY_WITHOUT_COMPOSITE_OPERATIONS)\nstatic anjay_uri_path_t\nget_composite_root_path(const anjay_batch_t *const *values,\n                        size_t values_count) {\n    anjay_uri_path_t prefix_buf = MAKE_ROOT_PATH();\n    const anjay_uri_path_t *prefix_ptr = NULL;\n    for (size_t i = 0; i < values_count; ++i) {\n        _anjay_batch_update_common_path_prefix(&prefix_ptr, &prefix_buf,\n                                               values[i]);\n    }\n    return prefix_buf;\n}\n#    endif // defined(ANJAY_WITH_LWM2M11) &&\n           // !defined(ANJAY_WITHOUT_COMPOSITE_OPERATIONS)\n\nstatic anjay_uri_path_t get_response_path(anjay_observation_value_t *value) {\n    anjay_observation_t *observation = value->ref;\n#    if defined(ANJAY_WITH_LWM2M11) \\\n            && !defined(ANJAY_WITHOUT_COMPOSITE_OPERATIONS)\n    if (observation->action == ANJAY_ACTION_READ_COMPOSITE) {\n        return get_composite_root_path(cast_to_const_batch_array(value->values),\n                                       observation->paths_count);\n    }\n#    endif // defined(ANJAY_WITH_LWM2M11) &&\n           // !defined(ANJAY_WITHOUT_COMPOSITE_OPERATIONS)\n    return observation->paths[0];\n}\n\nstatic int write_notify_payload(size_t payload_offset,\n                                void *payload_buf,\n                                size_t payload_buf_size,\n                                size_t *out_payload_chunk_size,\n                                void *conn_) {\n    anjay_observe_connection_entry_t *conn =\n            (anjay_observe_connection_entry_t *) conn_;\n    if (payload_offset != conn->serialization_state.expected_offset) {\n        anjay_log(DEBUG,\n                  _(\"Server requested unexpected chunk of payload (expected \"\n                    \"offset \") \"%u\" _(\", got \") \"%u\" _(\")\"),\n                  (unsigned) conn->serialization_state.expected_offset,\n                  (unsigned) payload_offset);\n        return -1;\n    }\n\n    anjay_unlocked_t *anjay = _anjay_from_server(conn->conn_ref.server);\n    anjay_observation_value_t *value = conn->unsent;\n    anjay_observation_t *observation = value->ref;\n\n    char *write_ptr = (char *) payload_buf;\n    const char *end_ptr = write_ptr + payload_buf_size;\n    while (true) {\n        size_t bytes_read;\n        if (avs_is_err(avs_stream_read(conn->serialization_state.membuf_stream,\n                                       &bytes_read, NULL, write_ptr,\n                                       (size_t) (end_ptr - write_ptr)))) {\n            return -1;\n        }\n        write_ptr += bytes_read;\n        if (write_ptr >= end_ptr || !conn->serialization_state.out_ctx) {\n            break;\n        }\n        // NOTE: Access Control permissions have been checked during the\n        // read_as_batch() stage, so we're \"spoofing\" ANJAY_SSID_BOOTSTRAP\n        // as the permissions are checked now\n        int result = _anjay_batch_data_output_entry(\n                anjay, value->values[conn->serialization_state.curr_value_idx],\n                ANJAY_SSID_BOOTSTRAP,\n                conn->serialization_state.serialization_time,\n                &conn->serialization_state.output_state,\n                conn->serialization_state.out_ctx);\n        if (!result && !conn->serialization_state.output_state) {\n            ++conn->serialization_state.curr_value_idx;\n            if (conn->serialization_state.curr_value_idx\n                    >= observation->paths_count) {\n                result = _anjay_output_ctx_destroy_and_process_result(\n                        &conn->serialization_state.out_ctx, result);\n            }\n        }\n        if (result) {\n            return result;\n        }\n    }\n    *out_payload_chunk_size = (size_t) (write_ptr - (char *) payload_buf);\n    conn->serialization_state.expected_offset += *out_payload_chunk_size;\n    return 0;\n}\n\nstatic anjay_msg_details_t\ninitial_response_details(anjay_unlocked_t *anjay,\n                         const anjay_request_t *request,\n                         anjay_lwm2m_version_t lwm2m_version,\n                         const anjay_batch_t *const *values) {\n    bool requires_hierarchical_format;\n#    if defined(ANJAY_WITH_LWM2M11) \\\n            && !defined(ANJAY_WITHOUT_COMPOSITE_OPERATIONS)\n    if (request->action == ANJAY_ACTION_READ_COMPOSITE) {\n        requires_hierarchical_format = true;\n    } else\n#    endif // defined(ANJAY_WITH_LWM2M11) &&\n           // !defined(ANJAY_WITHOUT_COMPOSITE_OPERATIONS)\n    {\n        assert(request->action == ANJAY_ACTION_READ);\n        assert(values);\n        requires_hierarchical_format =\n                _anjay_batch_data_requires_hierarchical_format(values[0]);\n    }\n    return _anjay_dm_response_details_for_read(\n            anjay, request, requires_hierarchical_format, lwm2m_version);\n}\n\nstatic int multiple_batches_item_count(anjay_unlocked_t *anjay,\n                                       size_t values_count,\n                                       const anjay_batch_t *const *values,\n                                       size_t *out_count) {\n    size_t item_count = 0;\n    for (size_t i = 0; i < values_count; ++i) {\n        size_t batch_count;\n        if (_anjay_batch_outputable_item_count(\n                    anjay, values[i], ANJAY_SSID_BOOTSTRAP, &batch_count)) {\n            return -1;\n        } else {\n            item_count += batch_count;\n        }\n    }\n    *out_count = item_count;\n    return 0;\n}\n\nstatic int send_initial_response(anjay_unlocked_t *anjay,\n                                 const anjay_msg_details_t *details,\n                                 const anjay_request_t *request,\n                                 size_t values_count,\n                                 const anjay_batch_t *const *values) {\n\n    avs_stream_t *notify_stream =\n            _anjay_coap_setup_response_stream(request->ctx, details);\n    if (!notify_stream) {\n        return -1;\n    }\n\n    anjay_uri_path_t root_path = request->uri;\n#    if defined(ANJAY_WITH_LWM2M11) \\\n            && !defined(ANJAY_WITHOUT_COMPOSITE_OPERATIONS)\n    if (request->action == ANJAY_ACTION_READ_COMPOSITE) {\n        root_path = get_composite_root_path(values, values_count);\n    }\n#    endif // defined(ANJAY_WITH_LWM2M11) &&\n           // !defined(ANJAY_WITHOUT_COMPOSITE_OPERATIONS)\n\n    size_t item_count;\n    anjay_unlocked_output_ctx_t *out_ctx = NULL;\n    int result = _anjay_output_dynamic_construct(\n            &out_ctx, notify_stream, &root_path, details->format,\n            multiple_batches_item_count(anjay, values_count, values,\n                                        &item_count)\n                    ? NULL\n                    : &item_count,\n            request->action);\n    for (size_t i = 0; !result && i < values_count; ++i) {\n        // NOTE: Access Control permissions have been checked during the\n        // read_as_batch() stage, so we're \"spoofing\" ANJAY_SSID_BOOTSTRAP\n        // as the permissions are checked now\n        result = _anjay_batch_data_output(anjay, values[i],\n                                          ANJAY_SSID_BOOTSTRAP, out_ctx);\n    }\n    return _anjay_output_ctx_destroy_and_process_result(&out_ctx, result);\n}\n\n#    ifdef ANJAY_TEST\n// This defines a mock macro for send_initial_response(),\n// so it needs to be included after definition of the real\n// send_initial_response(), but before its usages.\n#        include \"tests/core/observe/observe_mock.h\"\n#    endif // ANJAY_TEST\n\nstatic void delete_batch_array(anjay_batch_t ***batches_ptr,\n                               size_t batches_count) {\n    for (size_t i = 0; i < batches_count; ++i) {\n        if ((*batches_ptr)[i]) {\n            _anjay_batch_release(&(*batches_ptr)[i]);\n        }\n    }\n    avs_free(*batches_ptr);\n    *batches_ptr = NULL;\n}\n\nstatic int read_observation_path(anjay_unlocked_t *anjay,\n                                 const anjay_uri_path_t *path,\n                                 anjay_request_action_t action,\n                                 anjay_ssid_t connection_ssid,\n                                 const avs_time_real_t *timestamp,\n                                 anjay_batch_t **out_batch) {\n    const anjay_dm_installed_object_t *obj = NULL;\n    if (_anjay_uri_path_has(path, ANJAY_ID_OID)) {\n        const anjay_dm_t *dm;\n#    ifdef ANJAY_WITH_LWM2M_GATEWAY\n        if (_anjay_uri_path_has_prefix(path)) {\n            if (_anjay_lwm2m_gateway_prefix_to_dm(anjay, path->prefix, &dm)) {\n                return ANJAY_ERR_NOT_FOUND;\n            }\n        } else\n#    endif // ANJAY_WITH_LWM2M_GATEWAY\n        {\n            dm = &anjay->dm;\n        }\n        obj = _anjay_dm_find_object_by_oid(dm, path->ids[ANJAY_ID_OID]);\n    }\n    int result;\n    anjay_dm_path_info_t path_info;\n    (void) ((result = _anjay_dm_path_info(anjay, obj, path, &path_info))\n            || (result = read_as_batch(anjay, obj, &path_info, action,\n                                       connection_ssid, timestamp, out_batch)));\n    return result;\n}\n\nstatic int read_observation_values(anjay_unlocked_t *anjay,\n                                   const paths_arg_t *paths,\n                                   anjay_request_action_t action,\n                                   anjay_ssid_t connection_ssid,\n                                   const avs_time_real_t *timestamp,\n                                   anjay_batch_t ***out_batches) {\n    assert(out_batches && !*out_batches);\n    assert(paths->type != PATHS_POINTER_LIST\n           || paths->count == AVS_LIST_SIZE(paths->paths));\n\n    if (paths->count\n            && !(*out_batches = (anjay_batch_t **) avs_calloc(\n                         paths->count, sizeof(anjay_batch_t *)))) {\n        _anjay_log_oom();\n        return -1;\n    }\n\n    int result = 0;\n    switch (paths->type) {\n    case PATHS_POINTER_LIST: {\n        size_t index = 0;\n        AVS_LIST(const anjay_uri_path_t) path;\n        AVS_LIST_FOREACH(path, paths->paths) {\n#    ifdef ANJAY_WITH_LWM2M_GATEWAY\n            if (action == ANJAY_ACTION_READ_COMPOSITE\n                    && _anjay_uri_path_has_prefix(path)) {\n                dm_log(ERROR, _(\"Observe Composite on End Devices DMs is not \"\n                                \"supported\"));\n                result = ANJAY_ERR_METHOD_NOT_ALLOWED;\n                break;\n            }\n#    endif // ANJAY_WITH_LWM2M_GATEWAY\n            if ((result = read_observation_path(anjay, path, action,\n                                                connection_ssid, timestamp,\n                                                &(*out_batches)[index]))) {\n                break;\n            }\n            ++index;\n        }\n        break;\n    }\n    case PATHS_POINTER_ARRAY:\n        for (size_t index = 0; index < paths->count; ++index) {\n#    ifdef ANJAY_WITH_LWM2M_GATEWAY\n            if (action == ANJAY_ACTION_READ_COMPOSITE\n                    && _anjay_uri_path_has_prefix(&paths->paths[index])) {\n                dm_log(ERROR, _(\"Observe Composite on End Devices DMs is not \"\n                                \"supported\"));\n                result = ANJAY_ERR_METHOD_NOT_ALLOWED;\n                break;\n            }\n#    endif // ANJAY_WITH_LWM2M_GATEWAY\n            if ((result = read_observation_path(\n                         anjay, &paths->paths[index], action, connection_ssid,\n                         timestamp, &(*out_batches)[index]))) {\n                break;\n            }\n        }\n        break;\n    default:\n        AVS_UNREACHABLE(\"The switch above shall be exhaustive\");\n        result = -1;\n    }\n\n    if (result) {\n        delete_batch_array(out_batches, paths->count);\n    }\n    return result;\n}\n\nstatic int observe_handle(anjay_connection_ref_t ref,\n                          const paths_arg_t *paths,\n                          const anjay_request_t *request) {\n#    ifdef ANJAY_WITH_OBSERVATION_ATTRIBUTES\n    anjay_dm_r_attributes_t attributes = ANJAY_DM_R_ATTRIBUTES_EMPTY;\n#    endif // ANJAY_WITH_OBSERVATION_ATTRIBUTES\n\n    if (!_anjay_dm_request_attrs_empty(&request->attributes)) {\n#    ifdef ANJAY_WITH_OBSERVATION_ATTRIBUTES\n        if (_anjay_server_registration_info(ref.server)->lwm2m_version\n                < ANJAY_LWM2M_VERSION_1_2) {\n            return ANJAY_ERR_BAD_REQUEST;\n        }\n\n        _anjay_update_r_attrs(&attributes, &request->attributes);\n        if (!_anjay_r_attrs_valid(&attributes)) {\n            return ANJAY_ERR_BAD_REQUEST;\n        }\n#    else  // ANJAY_WITH_OBSERVATION_ATTRIBUTES\n        return ANJAY_ERR_BAD_REQUEST;\n#    endif // ANJAY_WITH_OBSERVATION_ATTRIBUTES\n    }\n\n    AVS_LIST(anjay_observe_connection_entry_t) *conn_ptr =\n            find_or_create_connection_state(ref);\n    if (!conn_ptr) {\n        return -1;\n    }\n\n    anjay_unlocked_t *anjay = _anjay_from_server(ref.server);\n    const avs_time_real_t timestamp = avs_time_real_now();\n\n    AVS_SORTED_SET_ELEM(anjay_observation_t) observation = NULL;\n    anjay_msg_details_t response_details;\n    anjay_batch_t **batches = NULL;\n    int send_result = -1;\n    int result = read_observation_values(anjay, paths, request->action,\n                                         _anjay_server_ssid(ref.server),\n                                         &timestamp, &batches);\n    if (result) {\n        delete_connection_if_empty(conn_ptr);\n        return result;\n    }\n    response_details = initial_response_details(\n            anjay, request,\n            _anjay_server_registration_info(ref.server)->lwm2m_version,\n            cast_to_const_batch_array(batches));\n\n    if (!(observation =\n                  put_entry_into_connection_state(request, *conn_ptr, paths\n#    ifdef ANJAY_WITH_OBSERVATION_ATTRIBUTES\n                                                  ,\n                                                  &attributes\n#    endif // ANJAY_WITH_OBSERVATION_ATTRIBUTES\n                                                  ))\n            || insert_initial_value(*conn_ptr, observation, &response_details,\n                                    &timestamp,\n                                    cast_to_const_batch_array(batches))\n            || start_coap_observe(ref, request)) {\n        result = -1;\n    }\n    // No matter if we succeeded with adding the observation to internal\n    // state or not, as long as we have some payload, we may as well just\n    // \"process the request as usual\" (RFC 7641, section 4.1).\n    send_result = send_initial_response(anjay, &response_details, request,\n                                        paths->count,\n                                        cast_to_const_batch_array(batches));\n\n    if (result || send_result) {\n        observe_remove_entry(ref, &request->observe->token);\n        if (conn_ptr && *conn_ptr) {\n            delete_connection_if_empty(conn_ptr);\n        }\n    }\n    delete_batch_array(&batches, paths->count);\n\n    if (result && !send_result) {\n        // we sent the response as if it was a read request\n        result = 0;\n    }\n    return result;\n}\n\nint _anjay_observe_handle(anjay_connection_ref_t ref,\n                          const anjay_request_t *request) {\n    assert(request->action == ANJAY_ACTION_READ);\n#    ifdef ANJAY_WITH_OBSERVATION_ATTRIBUTES\n    if (!_anjay_uri_path_has(&request->uri, ANJAY_ID_RID)\n            && !_anjay_dm_resource_specific_request_attrs_empty(\n                       &request->attributes)) {\n        return ANJAY_ERR_BAD_REQUEST;\n    }\n#    endif // ANJAY_WITH_OBSERVATION_ATTRIBUTES\n    return observe_handle(ref,\n                          &(const paths_arg_t) {\n                              .type = PATHS_POINTER_ARRAY,\n                              .paths = &request->uri,\n                              .count = 1\n                          },\n                          request);\n}\n\n#    if defined(ANJAY_WITH_LWM2M11) \\\n            && !defined(ANJAY_WITHOUT_COMPOSITE_OPERATIONS)\nint _anjay_observe_composite_handle(anjay_connection_ref_t ref,\n                                    AVS_LIST(anjay_uri_path_t) paths,\n                                    const anjay_request_t *request) {\n    assert(request->action == ANJAY_ACTION_READ_COMPOSITE);\n#        ifdef ANJAY_WITH_OBSERVATION_ATTRIBUTES\n    if (!_anjay_dm_resource_specific_request_attrs_empty(\n                &request->attributes)) {\n        return ANJAY_ERR_BAD_REQUEST;\n    }\n#        endif // ANJAY_WITH_OBSERVATION_ATTRIBUTES\n    return observe_handle(ref,\n                          &(const paths_arg_t) {\n                              .type = PATHS_POINTER_LIST,\n                              .paths = paths,\n                              .count = AVS_LIST_SIZE(paths)\n                          },\n                          request);\n}\n#    endif // defined(ANJAY_WITH_LWM2M11) &&\n           // !defined(ANJAY_WITHOUT_COMPOSITE_OPERATIONS)\n\nstatic int observe_gc_ssid_iterate(anjay_unlocked_t *anjay,\n                                   anjay_ssid_t ssid,\n                                   void *conn_ptr_ptr_) {\n    (void) anjay;\n    AVS_LIST(anjay_observe_connection_entry_t) **conn_ptr_ptr =\n            (AVS_LIST(anjay_observe_connection_entry_t) **) conn_ptr_ptr_;\n    while (**conn_ptr_ptr\n           && _anjay_server_ssid((**conn_ptr_ptr)->conn_ref.server) < ssid) {\n        delete_connection(*conn_ptr_ptr);\n    }\n    while (**conn_ptr_ptr\n           && _anjay_server_ssid((**conn_ptr_ptr)->conn_ref.server) == ssid) {\n        AVS_LIST_ADVANCE_PTR(conn_ptr_ptr);\n    }\n    return 0;\n}\n\nvoid _anjay_observe_gc(anjay_unlocked_t *anjay) {\n    AVS_LIST(anjay_observe_connection_entry_t) *conn_ptr =\n            &anjay->observe.connection_entries;\n    _anjay_servers_foreach_ssid(anjay, observe_gc_ssid_iterate, &conn_ptr);\n    while (*conn_ptr) {\n        delete_connection(conn_ptr);\n    }\n}\n\nstatic bool has_pmax_expired(const anjay_observation_value_t *value,\n                             const anjay_dm_oi_attributes_t *attrs) {\n    return is_pmax_valid(*attrs)\n           && avs_time_real_diff(avs_time_real_now(), value->timestamp).seconds\n                      >= attrs->max_period;\n}\n\nstatic bool has_epmin_expired(const anjay_batch_t *value_element,\n                              const anjay_dm_oi_attributes_t *attrs) {\n    return attrs->min_eval_period == ANJAY_ATTRIB_INTEGER_NONE\n           || avs_time_real_diff(avs_time_real_now(),\n                                 _anjay_batch_get_compilation_time(\n                                         value_element))\n                              .seconds\n                      >= attrs->min_eval_period;\n}\n\nstatic bool process_step(const anjay_dm_r_attributes_t *attrs,\n                         double previous_value,\n                         double new_value) {\n    return !isnan(attrs->step)\n           && fabs(new_value - previous_value) >= attrs->step;\n}\n\nstatic bool\nprocess_ltgt(double threshold, double previous_value, double new_value) {\n    return !isnan(threshold)\n           && ((previous_value <= threshold && new_value > threshold)\n               || (previous_value >= threshold && new_value < threshold));\n}\n\n#    ifdef ANJAY_WITH_LWM2M12\nstatic bool process_edge(anjay_dm_edge_attr_t edge,\n                         bool new_value,\n                         bool is_previous_value_boolean,\n                         bool is_new_value_boolean) {\n    if (!is_previous_value_boolean || !is_new_value_boolean) {\n        return false;\n    }\n    switch (edge) {\n    case ANJAY_DM_EDGE_ATTR_FALLING:\n        return !new_value;\n    case ANJAY_DM_EDGE_ATTR_RISING:\n        return new_value;\n    default:\n        return false;\n    }\n}\n#    endif // ANJAY_WITH_LWM2M12\n\nstatic bool should_update(const anjay_uri_path_t *path,\n                          const anjay_dm_r_attributes_t *attrs,\n                          const anjay_batch_t *previous_value,\n                          const anjay_batch_t *new_value) {\n    if (_anjay_batch_values_equal(previous_value, new_value)) {\n        return false;\n    }\n\n    double previous_numeric = NAN;\n    double new_numeric = NAN;\n    bool is_previous_value_boolean = false;\n    bool is_new_value_boolean = false;\n    bool new_boolean = false;\n    if (_anjay_uri_path_has(path, ANJAY_ID_RID)) {\n        previous_numeric = _anjay_batch_data_numeric_value(previous_value);\n        new_numeric = _anjay_batch_data_numeric_value(new_value);\n        if (!_anjay_batch_data_boolean_value(previous_value, NULL)) {\n            is_previous_value_boolean = true;\n        }\n        if (!_anjay_batch_data_boolean_value(new_value, &new_boolean)) {\n            is_new_value_boolean = true;\n        }\n    }\n    if ((isnan(new_numeric) && !is_new_value_boolean)\n            || (isnan(previous_numeric) && !is_previous_value_boolean)\n            || (isnan(attrs->greater_than) && isnan(attrs->less_than)\n                && isnan(attrs->step)\n#    ifdef ANJAY_WITH_LWM2M12\n                && attrs->edge == ANJAY_DM_EDGE_ATTR_NONE\n#    endif // ANJAY_WITH_LWM2M12\n                )) {\n        // either previous or current value is not numeric/boolean, or none of\n        // lt/gt/st/edge attributes are set - notifying each value change\n        return true;\n    }\n\n    return process_step(attrs, previous_numeric, new_numeric)\n           || process_ltgt(attrs->less_than, previous_numeric, new_numeric)\n           || process_ltgt(attrs->greater_than, previous_numeric, new_numeric)\n#    ifdef ANJAY_WITH_LWM2M12\n           || process_edge(attrs->edge, new_boolean, is_previous_value_boolean,\n                           is_new_value_boolean)\n#    endif // ANJAY_WITH_LWM2M12\n            ;\n}\n\nstatic bool confirmable_required(const anjay_observe_connection_entry_t *conn) {\n    anjay_unlocked_t *anjay = _anjay_from_server(conn->conn_ref.server);\n    anjay_socket_transport_t transport =\n            _anjay_connection_transport(conn->conn_ref);\n    anjay_observation_t *observation = conn->unsent->ref;\n    avs_time_real_t confirmable_necessary_at = avs_time_real_add(\n            observation->last_confirmable,\n            avs_time_duration_diff(\n                    avs_time_duration_from_scalar(1, AVS_TIME_DAY),\n                    _anjay_max_transmit_wait_for_transport(anjay, transport)));\n    return !avs_time_real_before(avs_time_real_now(), confirmable_necessary_at);\n}\n\nstatic void value_sent(anjay_observe_connection_entry_t *conn_state) {\n    anjay_unlocked_t *anjay = _anjay_from_server(conn_state->conn_ref.server);\n    anjay_observation_value_t *sent = detach_first_unsent_value(conn_state);\n    anjay_observation_t *observation = sent->ref;\n    assert(AVS_LIST_SIZE(observation->last_sent) <= 1);\n    if (observation->last_sent) {\n        delete_value(anjay, &observation->last_sent);\n    }\n    observation->last_sent = sent;\n}\n\nstatic bool notification_storing_enabled(anjay_connection_ref_t conn_ref) {\n    anjay_unlocked_t *anjay = _anjay_from_server(conn_ref.server);\n    anjay_iid_t server_iid;\n    if (!_anjay_find_server_iid(anjay, _anjay_server_ssid(conn_ref.server),\n                                &server_iid)) {\n        const anjay_uri_path_t path =\n                MAKE_RESOURCE_PATH(ANJAY_DM_OID_SERVER, server_iid,\n                                   ANJAY_DM_RID_SERVER_NOTIFICATION_STORING);\n        bool storing;\n        if (!_anjay_dm_read_resource_bool(anjay, &path, &storing) && !storing) {\n            // default value is true, use false only if explicitly set\n            return false;\n        }\n    }\n    return true;\n}\n\nstatic void remove_all_unsent_values(anjay_observe_connection_entry_t *conn) {\n    anjay_unlocked_t *anjay = _anjay_from_server(conn->conn_ref.server);\n    while (conn->unsent && !is_error_value(conn->unsent)) {\n        AVS_LIST(anjay_observation_value_t) value =\n                detach_first_unsent_value(conn);\n        delete_value(anjay, &value);\n    }\n}\n\n#    ifdef ANJAY_WITH_LWM2M12\nstatic void remove_last_unsent_value_from_observation(\n        anjay_observe_connection_entry_t *conn,\n        anjay_observation_t *observation) {\n    anjay_unlocked_t *anjay = _anjay_from_server(conn->conn_ref.server);\n    AVS_ASSERT(observation->historical_queue_size == 0 && conn->unsent_last,\n               \"function is supposed to be called if there is a stored \"\n               \"notification which must be dropped\");\n    AVS_LIST(anjay_observation_value_t) *value_to_drop =\n            find_oldest_queued_notification_from_observation(conn, observation);\n    AVS_ASSERT(\n            *value_to_drop == conn->unsent_last,\n            \"there must be only one notification referencing the observation\");\n    AVS_LIST(anjay_observation_value_t) detached_value =\n            detach_first_unsent_value_from_observation(value_to_drop, conn,\n                                                       observation);\n    delete_value(anjay, &detached_value);\n}\n#    endif // ANJAY_WITH_LWM2M12\n\nstatic bool connection_exists(anjay_unlocked_t *anjay,\n                              anjay_observe_connection_entry_t *conn) {\n    AVS_LIST(anjay_observe_connection_entry_t) *conn_ptr =\n            (AVS_LIST(anjay_observe_connection_entry_t) *) AVS_LIST_FIND_PTR(\n                    &anjay->observe.connection_entries, conn);\n    return conn_ptr && *conn_ptr == conn;\n}\n\nstatic void on_network_error(anjay_connection_ref_t conn_ref, avs_error_t err) {\n    anjay_log(WARNING, _(\"network communication error while sending Notify\"));\n    if (conn_ref.conn_type == ANJAY_CONNECTION_PRIMARY) {\n        _anjay_server_on_server_communication_error(conn_ref.server, err);\n    }\n}\n\nstatic void on_entry_flushed(anjay_observe_connection_entry_t *conn,\n                             avs_error_t err) {\n    if (avs_is_ok(err)) {\n        sched_flush(conn);\n        return;\n    }\n\n    if (err.category == AVS_COAP_ERR_CATEGORY) {\n        if (avs_coap_error_recovery_action(err)\n                == AVS_COAP_ERR_RECOVERY_RECREATE_CONTEXT) {\n            on_network_error(conn->conn_ref, err);\n            return;\n        } else if (err.code == AVS_COAP_ERR_UDP_RESET_RECEIVED\n                   || err.code == AVS_COAP_ERR_EXCHANGE_CANCELED) {\n            // These cases are handled by avs_coap;\n            // observation has been already cancelled.\n            return;\n        } else {\n            // All other cases are non-fatal errors that we handle by cancelling\n            // the observation anyway. Fall through to outside this 'if' ladder.\n        }\n    } else if (err.category == AVS_ERRNO_CATEGORY\n               && (err.code == AVS_EINVAL || err.code == AVS_EMSGSIZE\n                   || err.code == AVS_ENOMEM)) {\n        // These are socket-layer errors: AVS_EINVAL - some invalid argument has\n        // been passed somewhere; AVS_EMSGISZE - truncated datagram received;\n        // AVS_ENOMEM - out of memory. In each of these cases, socket is still\n        // ready for further communication. We treat them as non-fatal errors -\n        // fall through to outside this 'if' ladder.\n    } else {\n        // All other errors are treated as fatal socket errors, i.e., we assume\n        // that the socket is no longer usable.\n        on_network_error(conn->conn_ref, err);\n        return;\n    }\n\n    // NOTE: we couldn't send the notification due to some kind of non-fatal\n    // condition, but observe is not canceled on CoAP layer.\n    if (!notification_storing_enabled(conn->conn_ref)) {\n        remove_all_unsent_values(conn);\n    }\n    anjay_log(WARNING, _(\"Could not send Observe notification: \") \"%s\",\n              AVS_COAP_STRERROR(err));\n}\n\nstatic void\ncleanup_serialization_state(anjay_observation_serialization_state_t *state) {\n    _anjay_output_ctx_destroy(&state->out_ctx);\n    avs_stream_cleanup(&state->membuf_stream);\n}\n\nstatic int\ninitialize_serialization_state(anjay_observe_connection_entry_t *conn) {\n    assert(!conn->serialization_state.membuf_stream);\n    assert(!conn->serialization_state.out_ctx);\n    memset(&conn->serialization_state, 0, sizeof(conn->serialization_state));\n\n    anjay_observation_value_t *value = conn->unsent;\n    const anjay_uri_path_t root_path = get_response_path(value);\n\n    size_t item_count;\n    if (!(conn->serialization_state.membuf_stream = avs_stream_membuf_create())\n            || _anjay_output_dynamic_construct(\n                       &conn->serialization_state.out_ctx,\n                       conn->serialization_state.membuf_stream, &root_path,\n                       value->details.format,\n                       multiple_batches_item_count(\n                               _anjay_from_server(conn->conn_ref.server),\n                               value->ref->paths_count,\n                               cast_to_const_batch_array(value->values),\n                               &item_count)\n                               ? NULL\n                               : &item_count,\n                       value->ref->action)) {\n        return -1;\n    }\n    conn->serialization_state.serialization_time = avs_time_real_now();\n    return 0;\n}\n\nstatic void\nhandle_notify_delivery(avs_coap_ctx_t *coap, avs_error_t err, void *conn_) {\n    (void) coap;\n    anjay_observe_connection_entry_t *conn =\n            (anjay_observe_connection_entry_t *) conn_;\n\n    anjay_unlocked_t *anjay = _anjay_from_server(conn->conn_ref.server);\n    if (anjay->confirmable_notification_status_cb\n            && (conn->unsent->reliability_hint\n                        == AVS_COAP_NOTIFY_PREFER_CONFIRMABLE\n                || _anjay_connection_transport(conn->conn_ref)\n                           == ANJAY_SOCKET_TRANSPORT_TCP)) {\n        ANJAY_MUTEX_UNLOCK_FOR_CALLBACK(anjay_locked, anjay);\n        anjay->confirmable_notification_status_cb(\n                anjay_locked, _anjay_server_ssid(conn->conn_ref.server),\n                conn->unsent->ref->paths, conn->unsent->ref->paths_count, err);\n        ANJAY_MUTEX_LOCK_AFTER_CALLBACK(anjay_locked);\n    }\n\n    bool is_error = is_error_value(conn->unsent);\n    conn->notify_exchange_id = AVS_COAP_EXCHANGE_ID_INVALID;\n    cleanup_serialization_state(&conn->serialization_state);\n    if (avs_is_ok(err)) {\n        _anjay_connection_mark_stable(conn->conn_ref);\n        if (conn->unsent->reliability_hint\n                == AVS_COAP_NOTIFY_PREFER_CONFIRMABLE) {\n            conn->unsent->ref->last_confirmable = avs_time_real_now();\n        }\n        value_sent(conn);\n    }\n    if (!is_error || avs_is_err(err)) {\n        on_entry_flushed(conn, err);\n    }\n}\n\nstatic void flush_next_unsent(anjay_observe_connection_entry_t *conn) {\n    assert(conn->unsent);\n    anjay_observation_t *observation = conn->unsent->ref;\n    anjay_msg_details_t details = conn->unsent->details;\n\n    if (confirmable_required(conn)) {\n        conn->unsent->reliability_hint = AVS_COAP_NOTIFY_PREFER_CONFIRMABLE;\n    }\n\n    anjay_connection_ref_t conn_ref = conn->conn_ref;\n    anjay_unlocked_t *anjay = _anjay_from_server(conn_ref.server);\n    avs_coap_ctx_t *coap = _anjay_connection_get_coap(conn_ref);\n    assert(coap);\n\n    // Note: if we are dealing with a non-composite Observe, we assert that the\n    // observation was issued on exactly one path and we use it as the root\n    // path. That way, if that path is not the leaf (e.g. it's an Object path\n    // that contains Instances), the output context will be able to serialize\n    // the paths as relative to the root, using basename and name SenML\n    // attributes if available. For Observe-Composite, we cannot make assumption\n    // that there is any common root, so we use /.\n    assert(observation->action != ANJAY_ACTION_READ\n           || observation->paths_count == 1);\n    assert(!avs_coap_exchange_id_valid(conn->notify_exchange_id));\n\n    avs_coap_response_header_t response = { 0 };\n    avs_error_t err = _anjay_coap_fill_response_header(&response, &details);\n    if (avs_is_err(err)) {\n        on_entry_flushed(conn, err);\n    } else {\n        avs_coap_payload_writer_t *payload_writer = NULL;\n        if (!is_error_value(conn->unsent)) {\n            payload_writer = write_notify_payload;\n            if (initialize_serialization_state(conn)) {\n                err = avs_errno(AVS_ENOMEM);\n            }\n        }\n        if (avs_is_err(err)) {\n            on_entry_flushed(conn, err);\n        } else {\n            // NOTE: handle_notify_delivery() or _anjay_observe_cancel_handler()\n            // may be called by avs_coap_notify_async(), which may invalidate\n            // conn. That's also why we need this intermediate exchange_id\n            avs_coap_exchange_id_t exchange_id = AVS_COAP_EXCHANGE_ID_INVALID;\n            err = avs_coap_notify_async(coap, &exchange_id,\n                                        (avs_coap_observe_id_t) {\n                                            .token = observation->token\n                                        },\n                                        &response,\n                                        conn->unsent->reliability_hint,\n                                        payload_writer, conn,\n                                        handle_notify_delivery, conn);\n            if (avs_is_err(err)) {\n                if (connection_exists(anjay, conn)) {\n                    cleanup_serialization_state(&conn->serialization_state);\n                    on_entry_flushed(conn, err);\n                }\n            } else {\n                if (connection_exists(anjay, conn)) {\n                    assert(!avs_coap_exchange_id_valid(\n                            conn->notify_exchange_id));\n                    conn->notify_exchange_id = exchange_id;\n                }\n            }\n        }\n    }\n    avs_coap_options_cleanup(&response.options);\n#    ifndef ANJAY_WITHOUT_QUEUE_MODE_AUTOCLOSE\n    // on_entry_flushed() may have closed the socket already,\n    // so we need to check if it's still open\n    if (_anjay_connection_get_online_socket(conn_ref)) {\n        _anjay_connection_schedule_queue_mode_close(conn_ref);\n    }\n#    endif // ANJAY_WITHOUT_QUEUE_MODE_AUTOCLOSE\n\n#    ifdef ANJAY_WITH_COMMUNICATION_TIMESTAMP_API\n    if (avs_is_ok(err)) {\n        _anjay_server_set_last_communication_time(conn_ref.server);\n    }\n#    endif // ANJAY_WITH_COMMUNICATION_TIMESTAMP_API\n}\n\nvoid _anjay_observe_interrupt(anjay_connection_ref_t ref) {\n    AVS_LIST(anjay_observe_connection_entry_t) *conn_ptr =\n            _anjay_observe_find_connection_state(ref);\n    if (!conn_ptr) {\n        return;\n    }\n    if ((*conn_ptr)->flush_task) {\n        anjay_log(TRACE,\n                  _(\"Cancelling notifications flush task for server \"\n                    \"SSID \") \"%u\" _(\", connection type \") \"%d\",\n                  _anjay_server_ssid(ref.server), ref.conn_type);\n        avs_sched_del(&(*conn_ptr)->flush_task);\n    }\n    if (avs_coap_exchange_id_valid((*conn_ptr)->notify_exchange_id)) {\n        anjay_log(TRACE,\n                  _(\"Cancelling notification attempt for server SSID \") \"%u\" _(\n                          \", connection type \") \"%d\",\n                  _anjay_server_ssid(ref.server), ref.conn_type);\n        avs_coap_exchange_cancel(_anjay_connection_get_coap(ref),\n                                 (*conn_ptr)->notify_exchange_id);\n        assert(!avs_coap_exchange_id_valid((*conn_ptr)->notify_exchange_id));\n    }\n}\n\nbool _anjay_observe_confirmable_in_delivery(anjay_connection_ref_t ref) {\n    AVS_LIST(anjay_observe_connection_entry_t) *conn_ptr =\n            _anjay_observe_find_connection_state(ref);\n    return conn_ptr\n           && avs_coap_exchange_id_valid((*conn_ptr)->notify_exchange_id);\n}\n\n#    ifndef ANJAY_WITHOUT_QUEUE_MODE_AUTOCLOSE\nbool _anjay_observe_needs_flushing(anjay_connection_ref_t ref) {\n    AVS_LIST(anjay_observe_connection_entry_t) *conn_ptr =\n            _anjay_observe_find_connection_state(ref);\n    if (!conn_ptr) {\n        return false;\n    }\n    return (*conn_ptr)->unsent && !(*conn_ptr)->flush_task\n           && !avs_coap_exchange_id_valid((*conn_ptr)->notify_exchange_id);\n}\n#    endif // ANJAY_WITHOUT_QUEUE_MODE_AUTOCLOSE\n\nint _anjay_observe_sched_flush(anjay_connection_ref_t ref) {\n    anjay_log(TRACE,\n              _(\"scheduling notifications flush for server SSID \") \"%u\" _(\n                      \", connection type \") \"%d\",\n              _anjay_server_ssid(ref.server), ref.conn_type);\n    AVS_LIST(anjay_observe_connection_entry_t) *conn_ptr =\n            _anjay_observe_find_connection_state(ref);\n    if (!conn_ptr) {\n        anjay_log(TRACE, _(\"skipping notification flush scheduling: no \"\n                           \"appropriate connection found\"));\n        return 0;\n    }\n    return sched_flush(*conn_ptr);\n}\n\nstatic int\nupdate_notification_value(anjay_observe_connection_entry_t *conn_state,\n                          anjay_observation_t *observation) {\n    if (is_error_value(newest_value(observation))) {\n        return 0;\n    }\n\n    anjay_unlocked_t *anjay = _anjay_from_server(conn_state->conn_ref.server);\n    anjay_ssid_t ssid = _anjay_server_ssid(conn_state->conn_ref.server);\n    anjay_batch_t **batches = NULL;\n    bool should_update_batch = false;\n    int32_t pmax = -1;\n    anjay_dm_con_attr_t con = ANJAY_DM_CON_ATTR_NONE;\n#    ifdef ANJAY_WITH_LWM2M12\n    int32_t hqmax = -1;\n#    endif // ANJAY_WITH_LWM2M12\n\n    if (observation->paths_count\n            && !(batches = (anjay_batch_t **) avs_calloc(\n                         observation->paths_count, sizeof(anjay_batch_t *)))) {\n        _anjay_log_oom();\n        return -1;\n    }\n\n    const avs_time_real_t timestamp = avs_time_real_now();\n\n    int result = 0;\n    for (size_t i = 0; i < observation->paths_count; ++i) {\n        anjay_dm_r_attributes_t attrs;\n#    ifdef ANJAY_WITH_OBSERVATION_ATTRIBUTES\n        if (observation->action != ANJAY_ACTION_READ_COMPOSITE\n                && !_anjay_dm_resource_attributes_empty(&observation->attrs)) {\n            attrs = observation->attrs;\n            // pmin and pmax values should be set to default if not defined\n            _anjay_dm_read_combined_server_attrs(anjay, ssid, &attrs.common);\n        } else\n#    endif // ANJAY_WITH_OBSERVATION_ATTRIBUTES\n                if ((result = get_effective_attrs(\n                             anjay, &attrs, &observation->paths[i], ssid))) {\n            anjay_log(ERROR, _(\"Could not get attributes of path \") \"%s\",\n                      ANJAY_DEBUG_MAKE_PATH(&observation->paths[i]));\n            goto finish;\n        }\n#    ifdef ANJAY_WITH_LWM2M12\n        hqmax = AVS_MAX(hqmax, attrs.common.hqmax);\n#    endif // ANJAY_WITH_LWM2M12\n#    ifdef ANJAY_WITH_OBSERVATION_ATTRIBUTES\n        if (observation->action == ANJAY_ACTION_READ_COMPOSITE\n                && !_anjay_dm_resource_attributes_empty(&observation->attrs)) {\n            attrs.common = observation->attrs.common;\n            // pmin and pmax values should be set to default if not defined\n            _anjay_dm_read_combined_server_attrs(anjay, ssid, &attrs.common);\n        }\n\n#    endif // ANJAY_WITH_OBSERVATION_ATTRIBUTES\n\n        if (has_epmin_expired(newest_value(observation)->values[i],\n                              &attrs.common)) {\n            if ((result = read_observation_path(anjay, &observation->paths[i],\n                                                observation->action, ssid,\n                                                &timestamp, &batches[i]))) {\n                anjay_log(ERROR,\n                          _(\"Could not read path \") \"%s\" _(\" for notifying\"),\n                          ANJAY_DEBUG_MAKE_PATH(&observation->paths[i]));\n                goto finish;\n            }\n        } else {\n            anjay_log(DEBUG,\n                      _(\"epmin == \") \"%\" PRId32 _(\" set for path \") \"%s\" _(\n                              \" caused holding from reading a new value\"),\n                      attrs.common.min_eval_period,\n                      ANJAY_DEBUG_MAKE_PATH(&observation->paths[i]));\n            // Do not even call read_handler, just copy previous value\n            if (!(batches[i] = _anjay_batch_acquire(\n                          newest_value(observation)->values[i]))) {\n                result = -1;\n                goto finish;\n            }\n        }\n\n        if (!should_update_batch\n                && (has_pmax_expired(newest_value(observation), &attrs.common)\n                    || should_update(&observation->paths[i], &attrs,\n                                     newest_value(observation)->values[i],\n                                     batches[i]))) {\n            should_update_batch = true;\n        }\n\n        update_batch_pmax(&pmax, &attrs);\n#    ifdef ANJAY_WITH_CON_ATTR\n        con = AVS_MAX(con, attrs.common.con);\n#    endif // ANJAY_WITH_CON_ATTR\n    }\n\n#    ifdef ANJAY_WITH_LWM2M12\n    if (observation->historical_queue_size != hqmax\n            && hqmax != ANJAY_ATTRIB_INTEGER_NONE) {\n        observation->historical_queue_size = hqmax;\n    }\n#    endif // ANJAY_WITH_LWM2M12\n\n    if (should_update_batch) {\n        if (con < 0 && anjay->observe.confirmable_notifications) {\n            con = ANJAY_DM_CON_ATTR_CON;\n        }\n\n        avs_coap_notify_reliability_hint_t reliability_hint =\n                (con > 0) ? AVS_COAP_NOTIFY_PREFER_CONFIRMABLE\n                          : AVS_COAP_NOTIFY_PREFER_NON_CONFIRMABLE;\n        result = insert_new_value(conn_state, observation, reliability_hint,\n                                  &newest_value(observation)->details,\n                                  &timestamp,\n                                  cast_to_const_batch_array(batches));\n    }\n\n    if (!result && pmax >= 0) {\n        schedule_trigger(conn_state, observation, pmax, SCHEDULE_PERIOD_MAX);\n    }\n\nfinish:\n    delete_batch_array(&batches, observation->paths_count);\n    return result;\n}\n\nstatic void trigger_observe(avs_sched_t *sched, const void *args_) {\n    anjay_t *anjay_locked = _anjay_get_from_sched(sched);\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    const trigger_observe_args_t *args = (const trigger_observe_args_t *) args_;\n    assert(args->conn_state);\n    assert(args->observation);\n    args->observation->next_pmax_trigger = AVS_TIME_REAL_INVALID;\n    recalculate_conn_trigger_times(args->conn_state);\n    bool ready_for_notifying =\n            _anjay_connection_ready_for_outgoing_message(\n                    args->conn_state->conn_ref)\n            && _anjay_socket_transport_is_online(\n                       _anjay_from_server(args->conn_state->conn_ref.server),\n                       _anjay_connection_transport(args->conn_state->conn_ref));\n    if (!ready_for_notifying\n            && _anjay_server_registration_expired(\n                       args->conn_state->conn_ref.server)) {\n        // Registration expired - notifications would be cleared at the time of\n        // Register anyway, so we might as well do it here to conserve memory\n        _anjay_observe_invalidate(args->conn_state->conn_ref);\n    } else {\n        if (ready_for_notifying\n                || notification_storing_enabled(args->conn_state->conn_ref)) {\n            int result = update_notification_value(args->conn_state,\n                                                   args->observation);\n            if (result) {\n                insert_error(args->conn_state, args->observation, result);\n            }\n        }\n        if (args->conn_state->unsent) {\n            if (ready_for_notifying\n                    && !avs_coap_exchange_id_valid(\n                               args->conn_state->notify_exchange_id)) {\n                avs_sched_del(&args->conn_state->flush_task);\n                assert(!args->conn_state->flush_task);\n                if (_anjay_connection_get_online_socket(\n                            args->conn_state->conn_ref)) {\n                    flush_next_unsent(args->conn_state);\n                } else if (_anjay_server_registration_info(\n                                   args->conn_state->conn_ref.server)\n                                   ->queue_mode) {\n                    _anjay_connection_bring_online(args->conn_state->conn_ref);\n                    // once the connection is up, _anjay_observe_sched_flush()\n                    // will be called; we're done here\n                } else if (!notification_storing_enabled(\n                                   args->conn_state->conn_ref)) {\n                    remove_all_unsent_values(args->conn_state);\n                }\n            }\n#    ifdef ANJAY_WITH_LWM2M12\n            else if (args->observation->historical_queue_size == 0\n                     && notification_storing_enabled(\n                                args->conn_state->conn_ref)) {\n                remove_last_unsent_value_from_observation(args->conn_state,\n                                                          args->observation);\n            }\n#    endif // ANJAY_WITH_LWM2M12\n        }\n    }\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n}\n\nstatic anjay_dm_oi_attributes_t\nget_oi_attributes(anjay_observe_connection_entry_t *connection,\n                  anjay_observe_path_entry_t *path_entry) {\n    anjay_dm_r_attributes_t attrs = ANJAY_DM_R_ATTRIBUTES_EMPTY;\n    if (get_effective_attrs(_anjay_from_server(connection->conn_ref.server),\n                            &attrs, &path_entry->path,\n                            _anjay_server_ssid(connection->conn_ref.server))) {\n        return ANJAY_DM_OI_ATTRIBUTES_EMPTY;\n    }\n    return attrs.common;\n}\n\nstatic int notify_path_changed(anjay_observe_connection_entry_t *connection,\n                               anjay_observe_path_entry_t *path_entry,\n                               void *result_ptr) {\n    int32_t period = get_oi_attributes(connection, path_entry).min_period;\n    period = AVS_MAX(period, 0);\n\n    AVS_LIST(AVS_SORTED_SET_ELEM(anjay_observation_t)) ref;\n    AVS_LIST_FOREACH(ref, path_entry->refs) {\n        assert(ref);\n        assert(*ref);\n        int observation_period = period;\n#    ifdef ANJAY_WITH_OBSERVATION_ATTRIBUTES\n        if (!_anjay_dm_resource_attributes_empty(&(*ref)->attrs)) {\n            observation_period = (*ref)->attrs.common.min_period;\n            if (observation_period < 0) {\n                anjay_iid_t server_iid;\n                if (_anjay_find_server_iid(\n                            _anjay_from_server(connection->conn_ref.server),\n                            _anjay_server_ssid(connection->conn_ref.server),\n                            &server_iid)) {\n                    return -1;\n                }\n\n                _anjay_read_period(_anjay_from_server(\n                                           connection->conn_ref.server),\n                                   server_iid, ANJAY_DM_RID_SERVER_DEFAULT_PMIN,\n                                   &observation_period);\n                if (observation_period < 0) {\n                    observation_period = ANJAY_DM_DEFAULT_PMIN_VALUE;\n                }\n            }\n        }\n#    endif // ANJAY_WITH_OBSERVATION_ATTRIBUTES\n        _anjay_update_ret((int *) result_ptr,\n                          schedule_trigger(connection, *ref, observation_period,\n                                           SCHEDULE_PERIOD_MIN));\n    }\n    return 0;\n}\n\ntypedef int\nobserve_for_each_matching_clb_t(anjay_observe_connection_entry_t *connection,\n                                anjay_observe_path_entry_t *path_entry,\n                                void *arg);\n\nstatic int\nobserve_for_each_in_bounds(anjay_observe_connection_entry_t *connection,\n                           const anjay_uri_path_t *lower_bound,\n                           const anjay_uri_path_t *upper_bound,\n                           observe_for_each_matching_clb_t *clb,\n                           void *clb_arg) {\n    int retval = 0;\n    AVS_SORTED_SET_ELEM(anjay_observe_path_entry_t) it =\n            AVS_SORTED_SET_LOWER_BOUND(connection->observed_paths,\n                                       path_entry_query(lower_bound));\n    AVS_SORTED_SET_ELEM(anjay_observe_path_entry_t) end =\n            AVS_SORTED_SET_UPPER_BOUND(connection->observed_paths,\n                                       path_entry_query(upper_bound));\n    // if it == NULL, end must also be NULL\n    assert(it || !end);\n\n    for (; it != end; it = AVS_SORTED_SET_ELEM_NEXT(it)) {\n        assert(it);\n        if ((retval = clb(connection, it, clb_arg))) {\n            return retval;\n        }\n    }\n    return 0;\n}\n\nstatic int\nobserve_for_each_in_wildcard(anjay_observe_connection_entry_t *connection,\n                             const anjay_uri_path_t *specimen_path,\n                             anjay_id_type_t wildcard_level,\n                             observe_for_each_matching_clb_t *clb,\n                             void *clb_arg) {\n    anjay_uri_path_t path = *specimen_path;\n    for (int i = wildcard_level; i < _ANJAY_URI_PATH_MAX_LENGTH; ++i) {\n        path.ids[i] = ANJAY_ID_INVALID;\n    }\n    return observe_for_each_in_bounds(connection, &path, &path, clb, clb_arg);\n}\n\n/**\n * Calls <c>clb()</c> on all registered Observe path entries that match\n * <c>path</c>.\n *\n * This is harder than may seem at the first glance, because both\n * <c>path</c> (the query) and keys of the registered Observe path entries\n * may contain wildcards.\n *\n * An observation may be registered for either of:\n * - A whole object (OID)\n * - A whole object instance (OID+IID)\n * - A specific resource (OID+IID+RID)\n * - A specific resource instance (OID+IID+RID+RIID)\n *\n * Each of these cases needs to be addressed in a slightly different manner.\n *\n * Wildcard representation\n * -----------------------\n * A wildcard for any type of ID is represented as the number 65535. The\n * registered observation entries for any given connection are stored in a\n * sorted tree, with the sort key being ((prefix), OID, IID, RID, RIID) - in\n * lexicographical order over all elements of that tuple - much like C++11's\n * <c>std::tuple</c> comparison operators.\n *\n * Example: querying for OID+IID\n * -----------------------------\n * When the queried path is only OID+IID, we actually perform three\n * searches:\n * - the entry for the root path, i.e. (U16_MAX, U16_MAX, U16_MAX, U16_MAX)\n * - the entry for the Object, i.e. (OID, U16_MAX, U16_MAX, U16_MAX)\n * - entries between (OID, IID, 0, 0) and (OID, IID, U16_MAX, U16_MAX), i.e.\n *   entries for the Instance or any Resources or Resource Instances under\n * that Object Instance\n *\n * For paths of different lengths, there will be appropriately more or less\n * wildcard searches. If the search term is the root path, only the final\n * bounded search (between (0, 0, 0, 0) and\n * (U16_MAX, U16_MAX, U16_MAX, U16_MAX)) will be performed. If the search\n * term is OID+IID+RID+RIID, there will be five searches - for parent paths\n * of all lengths up to OID+IID+RID, and the final search for the actual\n * search term path.\n */\nstatic int\nobserve_for_each_matching(anjay_observe_connection_entry_t *connection,\n                          const anjay_uri_path_t *path,\n                          observe_for_each_matching_clb_t *clb,\n                          void *clb_arg) {\n    int retval = 0;\n    anjay_uri_path_t lower_bound = *path;\n    anjay_uri_path_t upper_bound = *path;\n\n    size_t path_length = _anjay_uri_path_length(path);\n    for (size_t i = 0; i < path_length; ++i) {\n        if ((retval = observe_for_each_in_wildcard(\n                     connection, path, (anjay_id_type_t) i, clb, clb_arg))) {\n            goto finish;\n        }\n    }\n\n    for (size_t i = path_length; i < _ANJAY_URI_PATH_MAX_LENGTH; ++i) {\n        lower_bound.ids[i] = 0;\n        upper_bound.ids[i] = ANJAY_ID_INVALID;\n    }\n\n    retval = observe_for_each_in_bounds(connection, &lower_bound, &upper_bound,\n                                        clb, clb_arg);\nfinish:\n    return retval == ANJAY_FOREACH_BREAK ? 0 : retval;\n}\n\nstatic int observe_notify_impl(anjay_unlocked_t *anjay,\n                               const anjay_uri_path_t *path,\n                               anjay_ssid_t ssid,\n                               bool invert_server_match,\n                               observe_for_each_matching_clb_t *clb) {\n    // iterate through all SSIDs we have\n    int result = 0;\n    AVS_LIST(anjay_observe_connection_entry_t) connection;\n    AVS_LIST_FOREACH(connection, anjay->observe.connection_entries) {\n        /* Some compilers complain about promotion of comparison result, so\n         * we're casting it to bool explicitly */\n        if ((bool) (_anjay_server_ssid(connection->conn_ref.server) == ssid)\n                == invert_server_match) {\n            continue;\n        }\n        observe_for_each_matching(connection, path, clb, &result);\n    }\n    return result;\n}\n\nint _anjay_observe_notify(anjay_unlocked_t *anjay,\n                          const anjay_uri_path_t *path,\n                          anjay_ssid_t ssid,\n                          bool invert_ssid_match) {\n    // This extra level of indirection is required to be able to mock\n    // notify_path_changed in unit tests.\n    // Hopefully compilers will inline it in production builds.\n    return observe_notify_impl(anjay, path, ssid, invert_ssid_match,\n                               notify_path_changed);\n}\n\n#    ifdef ANJAY_WITH_OBSERVATION_STATUS\nstatic int get_observe_status(anjay_observe_connection_entry_t *connection,\n                              anjay_observe_path_entry_t *entry,\n                              void *out_status_) {\n    anjay_resource_observation_status_t *out_status =\n            (anjay_resource_observation_status_t *) out_status_;\n    anjay_dm_oi_attributes_t attrs = get_oi_attributes(connection, entry);\n    out_status->is_observed = true;\n    if (attrs.min_period != ANJAY_ATTRIB_INTEGER_NONE\n            && (attrs.min_period < out_status->min_period\n                || out_status->min_period == ANJAY_ATTRIB_INTEGER_NONE)) {\n        out_status->min_period = attrs.min_period;\n    }\n    if (attrs.max_eval_period != ANJAY_ATTRIB_INTEGER_NONE\n            && (attrs.max_eval_period < out_status->max_eval_period\n                || out_status->max_eval_period == ANJAY_ATTRIB_INTEGER_NONE)) {\n        out_status->max_eval_period = attrs.max_eval_period;\n    }\n#        if (ANJAY_MAX_OBSERVATION_SERVERS_REPORTED_NUMBER > 0)\n    if (out_status->servers_number\n            < ANJAY_MAX_OBSERVATION_SERVERS_REPORTED_NUMBER) {\n        out_status->servers[out_status->servers_number++] =\n                _anjay_server_ssid(connection->conn_ref.server);\n    }\n#        endif //(ANJAY_MAX_OBSERVATION_SERVERS_REPORTED_NUMBER > 0)\n    return 0;\n}\n\nanjay_resource_observation_status_t\n_anjay_observe_status(anjay_unlocked_t *anjay, const anjay_uri_path_t *path) {\n    assert(path);\n    assert(path->ids[0] != ANJAY_ID_INVALID);\n    assert(path->ids[1] != ANJAY_ID_INVALID);\n    assert(path->ids[2] != ANJAY_ID_INVALID);\n\n    anjay_resource_observation_status_t result = {\n        .is_observed = false,\n        .min_period = ANJAY_ATTRIB_INTEGER_NONE,\n        .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE\n    };\n\n    AVS_LIST(anjay_observe_connection_entry_t) connection;\n    AVS_LIST_FOREACH(connection, anjay->observe.connection_entries) {\n        int retval = observe_for_each_matching(connection, path,\n                                               get_observe_status, &result);\n        assert(!retval);\n        (void) retval;\n    }\n\n    result.min_period = AVS_MAX(result.min_period, 0);\n\n    return result;\n}\n\n#    endif // ANJAY_WITH_OBSERVATION_STATUS\n\n#    ifdef ANJAY_TEST\n#        include \"tests/core/observe/observe.c\"\n#    endif // ANJAY_TEST\n\n#endif // ANJAY_WITH_OBSERVE\n"
  },
  {
    "path": "src/core/observe/anjay_observe_core.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ANJAY_OBSERVE_CORE_H\n#define ANJAY_OBSERVE_CORE_H\n\n#include <avsystem/commons/avs_persistence.h>\n#include <avsystem/commons/avs_sorted_set.h>\n\n#include \"../anjay_servers_private.h\"\n#include \"../coap/anjay_msg_details.h\"\n#include \"../io/anjay_batch_builder.h\"\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\ntypedef struct anjay_observation_struct anjay_observation_t;\ntypedef struct anjay_observe_connection_entry_struct\n        anjay_observe_connection_entry_t;\n\ntypedef enum {\n    NOTIFY_QUEUE_UNLIMITED,\n    NOTIFY_QUEUE_DROP_OLDEST\n} notify_queue_limit_mode_t;\n\ntypedef struct {\n    AVS_LIST(anjay_observe_connection_entry_t) connection_entries;\n    bool confirmable_notifications;\n\n    notify_queue_limit_mode_t notify_queue_limit_mode;\n    size_t notify_queue_limit;\n} anjay_observe_state_t;\n\ntypedef struct {\n    anjay_observation_t *const ref;\n    anjay_msg_details_t details;\n    avs_coap_notify_reliability_hint_t reliability_hint;\n    avs_time_real_t timestamp;\n\n    // Array size is ref->paths_count for \"normal\" entry, or 0 for error entry\n    // (determined based on is_error_value()). values[i] is a value\n    // corresponding to ref->paths[i]. Note that each values[i] element might\n    // contain multiple entries itself if ref->paths[i] is hierarchical (e.g.\n    // Object Instance).\n    anjay_batch_t *values[];\n} anjay_observation_value_t;\n\n#ifdef ANJAY_WITH_OBSERVE\n\nvoid _anjay_observe_init(anjay_observe_state_t *observe,\n                         bool confirmable_notifications,\n                         size_t stored_notification_limit);\n\nvoid _anjay_observe_cleanup(anjay_observe_state_t *observe);\n\nvoid _anjay_observe_gc(anjay_unlocked_t *anjay);\n\nint _anjay_observe_handle(anjay_connection_ref_t ref,\n                          const anjay_request_t *request);\n\n#    if defined(ANJAY_WITH_LWM2M11) \\\n            && !defined(ANJAY_WITHOUT_COMPOSITE_OPERATIONS)\nint _anjay_observe_composite_handle(anjay_connection_ref_t ref,\n                                    AVS_LIST(anjay_uri_path_t) paths,\n                                    const anjay_request_t *request);\n#    endif // defined(ANJAY_WITH_LWM2M11) &&\n           // !defined(ANJAY_WITHOUT_COMPOSITE_OPERATIONS)\n\nvoid _anjay_observe_interrupt(anjay_connection_ref_t ref);\n\nvoid _anjay_observe_invalidate(anjay_connection_ref_t ref);\n\nbool _anjay_observe_confirmable_in_delivery(anjay_connection_ref_t ref);\n\n#    ifndef ANJAY_WITHOUT_QUEUE_MODE_AUTOCLOSE\nbool _anjay_observe_needs_flushing(anjay_connection_ref_t ref);\n#    endif // ANJAY_WITHOUT_QUEUE_MODE_AUTOCLOSE\n\nint _anjay_observe_sched_flush(anjay_connection_ref_t ref);\n\nint _anjay_observe_notify(anjay_unlocked_t *anjay,\n                          const anjay_uri_path_t *path,\n                          anjay_ssid_t ssid,\n                          bool invert_ssid_match);\n\n#    ifdef ANJAY_WITH_OBSERVATION_STATUS\nanjay_resource_observation_status_t\n_anjay_observe_status(anjay_unlocked_t *anjay, const anjay_uri_path_t *path);\n#    endif // ANJAY_WITH_OBSERVATION_STATUS\n\n#else // ANJAY_WITH_OBSERVE\n\n#    define _anjay_observe_init(...) ((void) 0)\n#    define _anjay_observe_cleanup(...) ((void) 0)\n#    define _anjay_observe_gc(...) ((void) 0)\n#    define _anjay_observe_interrupt(...) ((void) 0)\n#    define _anjay_observe_invalidate(...) ((void) 0)\n#    define _anjay_observe_confirmable_in_delivery(...) false\n#    define _anjay_observe_needs_flushing(...) false\n#    define _anjay_observe_sched_flush(...) 0\n\n#    ifdef ANJAY_WITH_OBSERVATION_STATUS\n#        define _anjay_observe_status(...)         \\\n            ((anjay_resource_observation_status) { \\\n                .is_observed = false,              \\\n                .min_period = -1                   \\\n            })\n#    endif // ANJAY_WITH_OBSERVATION_STATUS\n\n#endif // ANJAY_WITH_OBSERVE\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif /* ANJAY_OBSERVE_CORE_H */\n"
  },
  {
    "path": "src/core/observe/anjay_observe_internal.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ANJAY_OBSERVE_INTERNAL_H\n#define ANJAY_OBSERVE_INTERNAL_H\n\n#include \"anjay_observe_core.h\"\n\n#include <avsystem/coap/code.h>\n\n#include <anjay_modules/anjay_io_utils.h>\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\nstruct anjay_observation_struct {\n    const avs_coap_token_t token;\n\n    const anjay_request_action_t action;\n\n    avs_sched_handle_t notify_task;\n    avs_time_real_t last_confirmable;\n    avs_time_real_t next_pmax_trigger;\n\n    // last_sent has ALWAYS EXACTLY one element,\n    // but is stored as a list to allow easy moving from unsent\n    AVS_LIST(anjay_observation_value_t) last_sent;\n\n    // pointer to some element of the\n    // anjay_observe_connection_entry_t::unsent list\n    // may or may not be the same as\n    // anjay_observe_connection_entry_t::unsent_last\n    // (depending on whether the last unsent value in the server refers\n    // to this resource+format or not)\n    AVS_LIST(anjay_observation_value_t) last_unsent;\n\n#ifdef ANJAY_WITH_LWM2M12\n    int32_t historical_queue_size;\n    size_t notifications_count;\n#    ifdef ANJAY_WITH_OBSERVATION_ATTRIBUTES\n    anjay_dm_r_attributes_t attrs;\n#    endif // ANJAY_WITH_OBSERVATION_ATTRIBUTES\n#endif     // ANJAY_WITH_LWM2M12\n    const size_t paths_count;\n    const anjay_uri_path_t paths[];\n};\n\ntypedef struct {\n    const anjay_uri_path_t path;\n\n    // List of observations (pointers to elements inside\n    // anjay_observe_connection_entry_t::observations) that include \"path\"\n    AVS_LIST(AVS_SORTED_SET_ELEM(anjay_observation_t)) refs;\n} anjay_observe_path_entry_t;\n\ntypedef struct {\n    avs_stream_t *membuf_stream;\n    anjay_unlocked_output_ctx_t *out_ctx;\n    size_t expected_offset;\n    avs_time_real_t serialization_time;\n    size_t curr_value_idx;\n    const anjay_batch_data_output_state_t *output_state;\n} anjay_observation_serialization_state_t;\n\nstruct anjay_observe_connection_entry_struct {\n    const anjay_connection_ref_t conn_ref;\n\n    AVS_SORTED_SET(anjay_observation_t) observations;\n    AVS_SORTED_SET(anjay_observe_path_entry_t) observed_paths;\n    avs_sched_handle_t flush_task;\n    avs_coap_exchange_id_t notify_exchange_id;\n    anjay_observation_serialization_state_t serialization_state;\n    avs_time_real_t next_trigger;\n    avs_time_real_t next_pmax_trigger;\n\n    AVS_LIST(anjay_observation_value_t) unsent;\n    // pointer to the last element of unsent\n    AVS_LIST(anjay_observation_value_t) unsent_last;\n};\n\n#ifdef ANJAY_WITH_OBSERVE\n\nstatic inline bool\n_anjay_observe_is_error_details(const anjay_msg_details_t *details) {\n    return avs_coap_code_get_class(details->msg_code) >= 4;\n}\n\nstatic inline const anjay_observation_t *\n_anjay_observation_query(const avs_coap_token_t *token) {\n    return AVS_CONTAINER_OF(token, anjay_observation_t, token);\n}\n\nvoid _anjay_observe_cleanup_connection(anjay_observe_connection_entry_t *conn);\n\nint _anjay_observe_token_cmp(const avs_coap_token_t *left,\n                             const avs_coap_token_t *right);\nint _anjay_observation_cmp(const void *left, const void *right);\nint _anjay_observe_path_entry_cmp(const void *left, const void *right);\n\nint _anjay_observe_add_to_observed_paths(\n        anjay_observe_connection_entry_t *conn,\n        AVS_SORTED_SET_ELEM(anjay_observation_t) observation);\n\nint _anjay_observe_schedule_pmax_trigger(\n        anjay_observe_connection_entry_t *conn_state,\n        anjay_observation_t *entry);\n\nAVS_LIST(anjay_observe_connection_entry_t) *\n_anjay_observe_find_connection_state(anjay_connection_ref_t ref);\n\nvoid _anjay_observe_cancel_handler(avs_coap_observe_id_t id, void *ref_ptr);\n\n#endif // ANJAY_WITH_OBSERVE\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif /* ANJAY_OBSERVE_INTERNAL_H */\n"
  },
  {
    "path": "src/core/observe/anjay_observe_planning.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#include \"../anjay_servers_utils.h\"\n\n#include \"anjay_observe_internal.h\"\n\nVISIBILITY_SOURCE_BEGIN\n\n#ifdef ANJAY_WITH_OBSERVE\ntypedef int foreach_relevant_connection_cb_t(\n        AVS_LIST(anjay_observe_connection_entry_t) *conn_ptr, void *data);\n\ntypedef struct {\n    unsigned conn_type_mask;\n    anjay_transport_set_t transport_set;\n    foreach_relevant_connection_cb_t *cb;\n    void *cb_data;\n} foreach_relevant_connection_helper_arg_t;\n\nstatic int foreach_relevant_connection_helper(anjay_unlocked_t *anjay,\n                                              anjay_server_info_t *server,\n                                              void *arg_) {\n    (void) anjay;\n    foreach_relevant_connection_helper_arg_t *arg =\n            (foreach_relevant_connection_helper_arg_t *) arg_;\n    anjay_connection_type_t conn_type;\n    ANJAY_CONNECTION_TYPE_FOREACH(conn_type) {\n        if (arg->conn_type_mask & (1U << conn_type)) {\n            anjay_connection_ref_t ref = {\n                .server = server,\n                .conn_type = conn_type\n            };\n            AVS_LIST(anjay_observe_connection_entry_t) *conn_ptr =\n                    _anjay_observe_find_connection_state(ref);\n            if (conn_ptr && *conn_ptr && _anjay_server_connection_active(ref)\n                    && _anjay_socket_transport_included(\n                               arg->transport_set,\n                               _anjay_connection_transport(ref))) {\n                int result = arg->cb(conn_ptr, arg->cb_data);\n                if (result) {\n                    return result;\n                }\n            }\n        }\n    }\n    return 0;\n}\n\nstatic int foreach_relevant_connection(anjay_unlocked_t *anjay,\n                                       anjay_ssid_t ssid,\n                                       unsigned conn_type_mask,\n                                       anjay_transport_set_t transport_set,\n                                       foreach_relevant_connection_cb_t *cb,\n                                       void *cb_data) {\n    foreach_relevant_connection_helper_arg_t arg = {\n        .conn_type_mask = conn_type_mask,\n        .transport_set = transport_set,\n        .cb = cb,\n        .cb_data = cb_data\n    };\n    if (ssid == ANJAY_SSID_ANY) {\n        return _anjay_servers_foreach_active(\n                anjay, foreach_relevant_connection_helper, &arg);\n    } else {\n        anjay_server_info_t *server = _anjay_servers_find_active(anjay, ssid);\n        if (!server) {\n            anjay_log(WARNING, _(\"no server with SSID = \") \"%u\", ssid);\n            return 0;\n        } else {\n            int result =\n                    foreach_relevant_connection_helper(anjay, server, &arg);\n            return result == ANJAY_FOREACH_BREAK ? 0 : result;\n        }\n    }\n}\n\n#else // ANJAY_WITH_OBSERVE\n\n#    define foreach_relevant_connection(Anjay, Ssid, ConnTypeMask, \\\n                                        TransportSet, Cb, CbData)  \\\n        ((void) (Anjay), (void) (Ssid), (void) (ConnTypeMask),     \\\n         (void) (TransportSet))\n\n#endif // ANJAY_WITH_OBSERVE\n\ntypedef struct {\n    size_t trigger_field_offset;\n    avs_time_real_t result;\n} next_planned_trigger_cb_arg_t;\n\n#ifdef ANJAY_WITH_OBSERVE\nstatic int\nnext_planned_trigger_cb(AVS_LIST(anjay_observe_connection_entry_t) *conn_ptr,\n                        void *arg_) {\n    next_planned_trigger_cb_arg_t *arg = (next_planned_trigger_cb_arg_t *) arg_;\n    avs_time_real_t trigger_time = *AVS_APPLY_OFFSET(avs_time_real_t, *conn_ptr,\n                                                     arg->trigger_field_offset);\n    if (!avs_time_real_valid(arg->result)\n            || avs_time_real_before(trigger_time, arg->result)) {\n        arg->result = trigger_time;\n    }\n    return 0;\n}\n#endif // ANJAY_WITH_OBSERVE\n\nstatic avs_time_real_t next_planned_trigger(anjay_t *anjay_locked,\n                                            anjay_ssid_t ssid,\n                                            unsigned conn_type_mask,\n                                            anjay_transport_set_t transport_set,\n                                            size_t trigger_field_offset) {\n    next_planned_trigger_cb_arg_t arg = {\n        .trigger_field_offset = trigger_field_offset,\n        .result = AVS_TIME_REAL_INVALID\n    };\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    foreach_relevant_connection(anjay, ssid, conn_type_mask, transport_set,\n                                next_planned_trigger_cb, &arg);\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return arg.result;\n}\n\navs_time_real_t anjay_next_planned_notify_trigger(anjay_t *anjay,\n                                                  anjay_ssid_t ssid) {\n    return next_planned_trigger(\n            anjay, ssid, 1 << ANJAY_CONNECTION_PRIMARY, ANJAY_TRANSPORT_SET_ALL,\n            offsetof(anjay_observe_connection_entry_t, next_trigger));\n}\n\navs_time_real_t anjay_next_planned_pmax_notify_trigger(anjay_t *anjay,\n                                                       anjay_ssid_t ssid) {\n    return next_planned_trigger(\n            anjay, ssid, 1 << ANJAY_CONNECTION_PRIMARY, ANJAY_TRANSPORT_SET_ALL,\n            offsetof(anjay_observe_connection_entry_t, next_pmax_trigger));\n}\n\navs_time_real_t anjay_transport_next_planned_notify_trigger(\n        anjay_t *anjay, anjay_transport_set_t transport_set) {\n    return next_planned_trigger(\n            anjay, ANJAY_SSID_ANY, (1 << ANJAY_CONNECTION_LIMIT_) - 1,\n            transport_set,\n            offsetof(anjay_observe_connection_entry_t, next_trigger));\n}\n\navs_time_real_t anjay_transport_next_planned_pmax_notify_trigger(\n        anjay_t *anjay, anjay_transport_set_t transport_set) {\n    return next_planned_trigger(\n            anjay, ANJAY_SSID_ANY, (1 << ANJAY_CONNECTION_LIMIT_) - 1,\n            transport_set,\n            offsetof(anjay_observe_connection_entry_t, next_pmax_trigger));\n}\n\n#ifdef ANJAY_WITH_OBSERVE\nstatic int has_unsent_notifications_cb(\n        AVS_LIST(anjay_observe_connection_entry_t) *conn_ptr,\n        void *out_result) {\n    if ((*conn_ptr)->unsent && !(*conn_ptr)->flush_task\n            && !avs_coap_exchange_id_valid((*conn_ptr)->notify_exchange_id)) {\n        *(bool *) out_result = true;\n        return ANJAY_FOREACH_BREAK;\n    }\n    return 0;\n}\n#endif // ANJAY_WITH_OBSERVE\n\nbool anjay_has_unsent_notifications(anjay_t *anjay_locked, anjay_ssid_t ssid) {\n    bool result = false;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    foreach_relevant_connection(anjay, ssid, 1 << ANJAY_CONNECTION_PRIMARY,\n                                ANJAY_TRANSPORT_SET_ALL,\n                                has_unsent_notifications_cb, &result);\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return result;\n}\n\nbool anjay_transport_has_unsent_notifications(\n        anjay_t *anjay_locked, anjay_transport_set_t transport_set) {\n    bool result = false;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    foreach_relevant_connection(anjay, ANJAY_SSID_ANY,\n                                (1 << ANJAY_CONNECTION_LIMIT_) - 1,\n                                transport_set, has_unsent_notifications_cb,\n                                &result);\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return result;\n}\n"
  },
  {
    "path": "src/core/servers/anjay_activate.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#include <avsystem/commons/avs_errno.h>\n#include <avsystem/commons/avs_sched.h>\n\n#include <inttypes.h>\n\n#define ANJAY_SERVERS_INTERNALS\n\n#include \"../anjay_servers_inactive.h\"\n#include \"../anjay_servers_reload.h\"\n#include \"../anjay_servers_utils.h\"\n#include \"../dm/anjay_query.h\"\n\n#include \"anjay_activate.h\"\n#include \"anjay_register.h\"\n#include \"anjay_server_connections.h\"\n#include \"anjay_servers_internal.h\"\n\nVISIBILITY_SOURCE_BEGIN\n\n/**\n * Deactivates the active server entry @p server .\n *\n * If <c>server->reactivate_time</c> is not AVS_TIME_DURATION_INVALID, schedules\n * a reactivate job for that time. The job is a retryable one, so the caller\n * does not need to worry about reactivating the server manually.\n */\nstatic int deactivate_server(anjay_server_info_t *server) {\n    assert(server);\n#ifdef ANJAY_WITH_CONN_STATUS_API\n    if (server->suspending\n            && server->connection_status != ANJAY_SERV_CONN_STATUS_SUSPENDED) {\n        _anjay_set_server_connection_status(server,\n                                            ANJAY_SERV_CONN_STATUS_SUSPENDING);\n    }\n#endif // ANJAY_WITH_CONN_STATUS_API\n#ifndef ANJAY_WITHOUT_DEREGISTER\n    if (server->ssid != ANJAY_SSID_BOOTSTRAP\n            && !_anjay_bootstrap_in_progress(server->anjay)) {\n        if (_anjay_server_active(server)\n                && !_anjay_server_registration_expired(server)) {\n            // Return value intentionally ignored.\n            // There isn't much we can do in case it fails and De-Register is\n            // optional anyway. _anjay_serve_deregister logs the error cause.\n            _anjay_server_deregister(server);\n        }\n#    ifdef ANJAY_WITH_CONN_STATUS_API\n        else if (server->connection_status != ANJAY_SERV_CONN_STATUS_ERROR\n                 && server->connection_status != ANJAY_SERV_CONN_STATUS_INITIAL\n                 && server->connection_status\n                            != ANJAY_SERV_CONN_STATUS_REG_FAILURE\n                 && server->connection_status\n                            != ANJAY_SERV_CONN_STATUS_SUSPENDED\n                 && !avs_time_real_before(server->reactivate_time,\n                                          avs_time_real_now())) {\n            _anjay_set_server_connection_status(\n                    server, ANJAY_SERV_CONN_STATUS_DEREGISTERED);\n        }\n#    endif // ANJAY_WITH_CONN_STATUS_API\n    }\n#endif // ANJAY_WITHOUT_DEREGISTER\n    _anjay_server_clean_active_data(server);\n    server->registration_info.expire_time = AVS_TIME_REAL_INVALID;\n    anjay_connection_type_t conn_type;\n    ANJAY_CONNECTION_TYPE_FOREACH(conn_type) {\n        _anjay_connection_internal_invalidate_session(\n                _anjay_connection_get(&server->connections, conn_type));\n    }\n    if (avs_time_real_valid(server->reactivate_time)\n            && _anjay_server_sched_activate(server)) {\n        // not much we can do other than removing the server altogether\n        anjay_log(ERROR, _(\"could not reschedule server reactivation\"));\n#ifdef ANJAY_WITH_CONN_STATUS_API\n        _anjay_check_server_connection_status(server);\n        _anjay_set_server_suspending_flag(server->anjay, server->ssid, false);\n#endif // ANJAY_WITH_CONN_STATUS_API\n        AVS_LIST(anjay_server_info_t) *server_ptr =\n                _anjay_servers_find_ptr(&server->anjay->servers, server->ssid);\n        assert(server_ptr);\n        assert(*server_ptr == server);\n        AVS_LIST_DELETE(server_ptr);\n        return -1;\n    }\n#ifdef ANJAY_WITH_CONN_STATUS_API\n    if (server->suspending) {\n        _anjay_set_server_connection_status(server,\n                                            ANJAY_SERV_CONN_STATUS_SUSPENDED);\n        _anjay_set_server_suspending_flag(server->anjay, server->ssid, false);\n    }\n#endif // ANJAY_WITH_CONN_STATUS_API\n    return 0;\n}\n\n#ifdef ANJAY_WITH_LWM2M11\nstatic void try_read_server_resource_u32(anjay_server_info_t *server,\n                                         anjay_rid_t rid,\n                                         int64_t min_value,\n                                         uint32_t *out_result) {\n    anjay_iid_t server_iid = ANJAY_ID_INVALID;\n    (void) _anjay_find_server_iid(server->anjay, server->ssid, &server_iid);\n    int64_t result;\n\n    if (server_iid != ANJAY_ID_INVALID\n            && !_anjay_dm_read_resource_i64(\n                       server->anjay,\n                       &MAKE_RESOURCE_PATH(ANJAY_DM_OID_SERVER, server_iid,\n                                           rid),\n                       &result)\n            && result >= min_value && result <= UINT32_MAX) {\n        *out_result = (uint32_t) result;\n    }\n}\n\ntypedef struct {\n    uint32_t retry_count;\n    uint32_t retry_timer_s;\n    uint32_t sequence_retry_count;\n    uint32_t sequence_delay_timer_s;\n} communication_retry_params_t;\n\n// NOTE: See \"Table: 6.2.1.1.-1 Registration Procedures Default Values\", it's\n// where the default values are taken from.\nstatic const communication_retry_params_t COMMUNICATION_RETRY_PARAMS_DEFAULT = {\n    .retry_count = 5,\n    .retry_timer_s = 60,\n    .sequence_retry_count = 1,\n    .sequence_delay_timer_s = 86400\n};\n\nstatic communication_retry_params_t\nquery_server_communication_retry_params(anjay_server_info_t *server) {\n    assert(server->ssid != ANJAY_SSID_BOOTSTRAP);\n    communication_retry_params_t params = COMMUNICATION_RETRY_PARAMS_DEFAULT;\n    try_read_server_resource_u32(server,\n                                 ANJAY_DM_RID_SERVER_COMMUNICATION_RETRY_COUNT,\n                                 1, &params.retry_count);\n    try_read_server_resource_u32(server,\n                                 ANJAY_DM_RID_SERVER_COMMUNICATION_RETRY_TIMER,\n                                 0, &params.retry_timer_s);\n    try_read_server_resource_u32(\n            server, ANJAY_DM_RID_SERVER_COMMUNICATION_SEQUENCE_RETRY_COUNT, 0,\n            &params.sequence_retry_count);\n    try_read_server_resource_u32(\n            server, ANJAY_DM_RID_SERVER_COMMUNICATION_SEQUENCE_DELAY_TIMER, 0,\n            &params.sequence_delay_timer_s);\n    return params;\n}\n#endif // ANJAY_WITH_LWM2M11\n\nvoid _anjay_server_on_failure(anjay_server_info_t *server,\n                              const char *debug_msg) {\n    _anjay_server_clean_active_data(server);\n    server->refresh_failed = true;\n\n    if (server->ssid == ANJAY_SSID_BOOTSTRAP) {\n        anjay_log(DEBUG,\n                  _(\"Bootstrap Server: \") \"%s\" _(\n                          \". Disabling it indefinitely.\"),\n                  debug_msg);\n        // Abort any further bootstrap retries.\n        _anjay_bootstrap_cleanup(server->anjay);\n#ifdef ANJAY_WITH_CONN_STATUS_API\n        _anjay_set_server_connection_status(server,\n                                            ANJAY_SERV_CONN_STATUS_ERROR);\n#endif // ANJAY_WITH_CONN_STATUS_API\n    } else {\n#ifdef ANJAY_WITH_LWM2M11\n        if (server->registration_attempts > 0) {\n            // When this value is > 0, it means there is an ongoing registration\n            // sequence. Otherwise, this whole function was called due to some\n            // other communication failure.\n            const communication_retry_params_t params =\n                    query_server_communication_retry_params(server);\n            if (server->registration_attempts < params.retry_count) {\n                anjay_log(INFO, _(\"Registration Retry \") \"%\" PRIu32 \"/%\" PRIu32,\n                          server->registration_attempts,\n                          params.retry_count - 1);\n\n                const avs_time_duration_t retry_timer = avs_time_duration_mul(\n                        avs_time_duration_from_scalar(params.retry_timer_s,\n                                                      AVS_TIME_S),\n                        (1 << (server->registration_attempts - 1)));\n                if (!avs_time_duration_valid(retry_timer)) {\n                    anjay_log(WARNING, _(\"Calculated retry time overflowed. \"\n                                         \"Assuming infinity\"));\n                }\n                server->reactivate_time =\n                        avs_time_real_add(avs_time_real_now(), retry_timer);\n                deactivate_server(server);\n                return;\n            } else if (server->registration_sequences_performed + 1\n                       < params.sequence_retry_count) {\n                anjay_log(INFO, _(\"Sequence Retry \") \"%\" PRIu32 \"/%\" PRIu32,\n                          server->registration_sequences_performed + 1,\n                          params.sequence_retry_count - 1);\n\n                ++server->registration_sequences_performed;\n                server->registration_attempts = 0;\n                avs_time_duration_t disable_duration =\n                        avs_time_duration_from_scalar(\n                                params.sequence_delay_timer_s, AVS_TIME_S);\n                if (params.sequence_delay_timer_s == UINT32_MAX) {\n                    // E.2 LwM2M Object: LwM2M Server: \"MAX_VALUE means do not\n                    // perform another communication sequence.\"\n                    anjay_log(INFO,\n                              _(\"Communication Sequence Delay Timer is \"\n                                \"saturated. Disabling server \") \"%\" PRIu16\n                                      _(\" indefinitely.\"),\n                              server->ssid);\n                    disable_duration = AVS_TIME_DURATION_INVALID;\n                }\n                server->reactivate_time = avs_time_real_add(avs_time_real_now(),\n                                                            disable_duration);\n                deactivate_server(server);\n                return;\n            }\n        }\n#endif // ANJAY_WITH_LWM2M11\n       // Either a failure not due to registration, or the number of\n       // registration attempts already exceeded communication retry counter.\n        anjay_log(DEBUG,\n                  _(\"Non-Bootstrap Server \") \"%\" PRIu16 _(\": \") \"%s\" _(\".\"),\n                  server->ssid, debug_msg);\n#ifdef ANJAY_WITH_CONN_STATUS_API\n        if (server->connection_status != ANJAY_SERV_CONN_STATUS_REG_FAILURE) {\n            _anjay_set_server_connection_status(server,\n                                                ANJAY_SERV_CONN_STATUS_ERROR);\n        }\n#endif // ANJAY_WITH_CONN_STATUS_API\n        (void) _anjay_perform_bootstrap_action_if_appropriate(\n                server->anjay,\n                _anjay_servers_find_active(server->anjay, ANJAY_SSID_BOOTSTRAP),\n                _anjay_requested_bootstrap_action(server->anjay));\n    }\n    // make sure that the server will not be reactivated at next refresh\n    server->reactivate_time = AVS_TIME_REAL_INVALID;\n}\n\nvoid _anjay_server_on_server_communication_error(anjay_server_info_t *server,\n                                                 avs_error_t err) {\n    assert(avs_is_err(err));\n    if (_anjay_server_reschedule_next_action(\n                server, AVS_TIME_DURATION_ZERO,\n                ANJAY_SERVER_NEXT_ACTION_COMMUNICATION_ERROR)) {\n        anjay_log(ERROR, _(\"could not schedule \"\n                           \"ANJAY_SERVER_NEXT_ACTION_COMMUNICATION_ERROR\"));\n        server->refresh_failed = true;\n    }\n\n#ifdef ANJAY_WITH_SSL_ERROR_API\n    if (err.category == AVS_NET_SSL_ALERT_CATEGORY\n            || err.category == AVS_NET_SSL_LIB_ERROR_CATEGORY) {\n        assert(server);\n\n        if (server->anjay->ssl_error_cb) {\n            ANJAY_MUTEX_UNLOCK_FOR_CALLBACK(anjay_locked, server->anjay);\n            server->anjay->ssl_error_cb(server->anjay->ssl_error_cb_arg,\n                                        anjay_locked, server->ssid, err);\n            ANJAY_MUTEX_LOCK_AFTER_CALLBACK(anjay_locked);\n        }\n    }\n\n#endif // ANJAY_WITH_SSL_ERROR_API\n\n#ifdef ANJAY_WITH_LWM2M11\n    if (err.category == AVS_NET_SSL_ALERT_CATEGORY) {\n        _anjay_server_update_last_ssl_alert_code(\n                server, avs_net_ssl_alert_level(err),\n                avs_net_ssl_alert_description(err));\n    }\n#endif // ANJAY_WITH_LWM2M11\n}\n\nvoid _anjay_server_on_server_communication_timeout(\n        anjay_server_info_t *server) {\n    anjay_connection_ref_t ref = {\n        .server = server,\n        .conn_type = ANJAY_CONNECTION_PRIMARY\n    };\n    assert(server);\n    assert(ref.conn_type != ANJAY_CONNECTION_UNSET);\n    anjay_server_connection_t *connection = _anjay_get_server_connection(ref);\n    if (connection->state == ANJAY_SERVER_CONNECTION_STABLE\n            && connection->stateful\n            && !_anjay_schedule_disable_server_with_explicit_timeout_unlocked(\n                       server->anjay, server->ssid, AVS_TIME_DURATION_ZERO)) {\n        server->refresh_failed = true;\n    } else {\n#ifdef ANJAY_WITH_CONN_STATUS_API\n        if (server->ssid != ANJAY_SSID_BOOTSTRAP) {\n            _anjay_set_server_connection_status(\n                    server, ANJAY_SERV_CONN_STATUS_REG_FAILURE);\n        }\n#endif // ANJAY_WITH_CONN_STATUS_API\n        _anjay_server_on_server_communication_error(server,\n                                                    avs_errno(AVS_EBADF));\n    }\n}\n\nvoid _anjay_server_on_fatal_coap_error(anjay_connection_ref_t conn_ref,\n                                       avs_error_t err) {\n    assert(avs_coap_error_recovery_action(err)\n           == AVS_COAP_ERR_RECOVERY_RECREATE_CONTEXT);\n    anjay_server_connection_t *conn =\n            _anjay_connection_get(&conn_ref.server->connections,\n                                  conn_ref.conn_type);\n    if (conn_ref.conn_type == ANJAY_CONNECTION_PRIMARY\n            && conn->state != ANJAY_SERVER_CONNECTION_STABLE\n            && _anjay_server_registration_expired(conn_ref.server)) {\n        _anjay_server_on_server_communication_error(conn_ref.server, err);\n    } else {\n        _anjay_connection_internal_clean_socket(conn_ref.server->anjay, conn);\n        _anjay_active_server_refresh(conn_ref.server);\n    }\n}\n\nvoid _anjay_server_on_refreshed(anjay_server_info_t *server,\n                                anjay_server_connection_state_t state,\n                                avs_error_t err) {\n    assert(server);\n    anjay_connection_ref_t primary_ref = {\n        .server = server,\n        .conn_type = ANJAY_CONNECTION_PRIMARY\n    };\n    anjay_server_connection_t *primary_conn =\n            _anjay_get_server_connection(primary_ref);\n    if (state == ANJAY_SERVER_CONNECTION_OFFLINE) {\n        if (avs_is_err(err)) {\n            anjay_log(TRACE,\n                      _(\"could not initialize sockets for SSID \") \"%\" PRIu16,\n                      server->ssid);\n            if (server->ssid != ANJAY_SSID_BOOTSTRAP) {\n                if (server->anjay->connection_error_is_registration_failure) {\n                    anjay_connection_type_t conn_type;\n                    ANJAY_CONNECTION_TYPE_FOREACH(conn_type) {\n                        _anjay_observe_invalidate((anjay_connection_ref_t) {\n                            .server = server,\n                            .conn_type = conn_type\n                        });\n                    }\n                    ++server->registration_attempts;\n                    server->registration_info.expire_time =\n                            AVS_TIME_REAL_INVALID;\n                    server->registration_info.update_forced = false;\n                    // defined(ANJAY_WITH_CORE_PERSISTENCE)\n#ifdef ANJAY_WITH_CONN_STATUS_API\n                    _anjay_set_server_connection_status(\n                            server, ANJAY_SERV_CONN_STATUS_REG_FAILURE);\n#endif // ANJAY_WITH_CONN_STATUS_API\n                } else {\n                    // Interrupt any registration flow that may be in progress\n                    server->registration_attempts = 0;\n                    server->registration_sequences_performed = 0;\n                }\n            }\n            _anjay_server_on_server_communication_error(server, err);\n        } else if (_anjay_socket_transport_supported(server->anjay,\n                                                     primary_conn->transport)\n                   && _anjay_socket_transport_is_online(\n                              server->anjay, primary_conn->transport)) {\n            assert(server->registration_info.queue_mode);\n            anjay_log(TRACE,\n                      _(\"Server with SSID \") \"%\" PRIu16 _(\n                              \" is suspended due to queue mode\"),\n                      server->ssid);\n            _anjay_server_reschedule_update_job(server);\n        } else {\n            anjay_log(TRACE, _(\"Server with SSID \") \"%\" PRIu16 _(\" is offline\"),\n                      server->ssid);\n            if (!avs_time_real_valid(server->reactivate_time)) {\n                // make the server reactivate when it comes back online\n                server->reactivate_time = avs_time_real_now();\n            }\n        }\n    } else if (server->ssid == ANJAY_SSID_BOOTSTRAP) {\n        assert(avs_is_ok(err));\n        anjay_bootstrap_action_t action =\n                _anjay_requested_bootstrap_action(server->anjay);\n        server->refresh_failed =\n                !!_anjay_perform_bootstrap_action_if_appropriate(\n                        server->anjay, server, action);\n        if (action == ANJAY_BOOTSTRAP_ACTION_NONE) {\n            _anjay_connection_mark_stable(primary_ref);\n        }\n        if (!server->refresh_failed) {\n            server->reactivate_time = AVS_TIME_REAL_INVALID;\n        }\n        // _anjay_bootstrap_request_if_appropriate() may fail only due to\n        // failure to schedule a job. Not much that we can do about it then.\n#ifdef ANJAY_WITH_CONN_STATUS_API\n        if (server->connection_status == ANJAY_SERV_CONN_STATUS_CONNECTING) {\n            if (server->refresh_failed) {\n                _anjay_set_server_connection_status(\n                        server, ANJAY_SERV_CONN_STATUS_ERROR);\n            } else if (action == ANJAY_BOOTSTRAP_ACTION_NONE\n                       && !avs_coap_exchange_id_valid(\n                                  server->anjay->bootstrap\n                                          .outgoing_request_exchange_id)) {\n                _anjay_set_server_connection_status(\n                        server, ANJAY_SERV_CONN_STATUS_BOOTSTRAPPED);\n            }\n        }\n#endif // ANJAY_WITH_CONN_STATUS_API\n    } else {\n        assert(avs_is_ok(err));\n        _anjay_server_ensure_valid_registration(server);\n    }\n}\n\nvoid _anjay_server_on_updated_registration(anjay_server_info_t *server,\n                                           anjay_registration_result_t result,\n                                           avs_error_t err) {\n#ifdef ANJAY_WITH_CONN_STATUS_API\n    if ((result == ANJAY_REGISTRATION_ERROR_NETWORK\n         || result == ANJAY_REGISTRATION_ERROR_OTHER)\n            && server->connection_status\n                           != ANJAY_SERV_CONN_STATUS_REG_FAILURE) {\n        _anjay_set_server_connection_status(server,\n                                            ANJAY_SERV_CONN_STATUS_ERROR);\n    } else {\n        _anjay_check_server_connection_status(server);\n    }\n#endif // ANJAY_WITH_CONN_STATUS_API\n    if (result == ANJAY_REGISTRATION_SUCCESS) {\n        if (_anjay_server_reschedule_update_job(server)) {\n            // Updates are retryable, we only need to reschedule after success\n            result = ANJAY_REGISTRATION_ERROR_OTHER;\n        } else {\n            server->registration_attempts = 0;\n            server->registration_sequences_performed = 0;\n        }\n    }\n    switch (result) {\n    case ANJAY_REGISTRATION_SUCCESS:\n        server->reactivate_time = AVS_TIME_REAL_INVALID;\n        server->refresh_failed = false;\n#ifdef ANJAY_WITH_COMMUNICATION_TIMESTAMP_API\n        server->registration_info.last_registration_time = avs_time_real_now();\n#endif // ANJAY_WITH_COMMUNICATION_TIMESTAMP_API\n       // Failure to handle Bootstrap state is not a failure of the\n       // Register operation - hence, not checking return value.\n        _anjay_bootstrap_notify_regular_connection_available(server->anjay);\n        _anjay_connections_flush_notifications(&server->connections);\n#ifdef ANJAY_WITH_SEND\n        _anjay_send_sched_retry_deferred(server->anjay, server->ssid);\n#endif // ANJAY_WITH_SEND\n        break;\n    case ANJAY_REGISTRATION_ERROR_TIMEOUT:\n        _anjay_server_on_server_communication_timeout(server);\n        break;\n    default:\n        _anjay_server_on_server_communication_error(\n                server, avs_is_err(err) ? err : avs_errno(AVS_EPROTO));\n        break;\n    }\n}\n\n#if defined(ANJAY_WITH_LWM2M11) && defined(ANJAY_WITH_BOOTSTRAP)\nstatic bool\nserver_bootstrap_on_registration_failure(anjay_unlocked_t *anjay,\n                                         anjay_server_info_t *server) {\n    if (server->ssid == ANJAY_SSID_BOOTSTRAP) {\n        return false;\n    }\n    // See \"Table: 6.2.1.1.-1 Registration Procedures Default Values\"\n    bool force_bootstrap = true;\n\n    anjay_iid_t server_iid = ANJAY_ID_INVALID;\n    (void) _anjay_find_server_iid(anjay, server->ssid, &server_iid);\n\n    if (server_iid != ANJAY_ID_INVALID) {\n        (void) _anjay_dm_read_resource_bool(\n                anjay,\n                &MAKE_RESOURCE_PATH(\n                        ANJAY_DM_OID_SERVER, server_iid,\n                        ANJAY_DM_RID_SERVER_BOOTSTRAP_ON_REGISTRATION_FAILURE),\n                &force_bootstrap);\n    }\n    return force_bootstrap;\n}\n#endif // defined(ANJAY_WITH_LWM2M11) && defined(ANJAY_WITH_BOOTSTRAP)\n\n#ifdef ANJAY_WITH_BOOTSTRAP\nstatic bool should_retry_bootstrap(anjay_unlocked_t *anjay) {\n    if (anjay->bootstrap.bootstrap_trigger) {\n        return true;\n    }\n    bool bootstrap_server_exists = false;\n    bool possibly_active_server_exists = false;\n    bool registration_failure_must_trigger_bootstrap = false;\n    AVS_LIST(anjay_server_info_t) it;\n    AVS_LIST_FOREACH(it, anjay->servers) {\n        if (it->ssid == ANJAY_SSID_BOOTSTRAP) {\n            if (anjay->bootstrap.in_progress) {\n                // Bootstrap already in progress, there may be no need to retry\n                return !_anjay_conn_session_tokens_equal(\n                        anjay->bootstrap.bootstrap_session_token,\n                        _anjay_server_primary_session_token(it));\n            }\n            bootstrap_server_exists = true;\n        } else if (!it->refresh_failed || _anjay_server_active(it)) {\n            possibly_active_server_exists = true;\n        }\n#    if defined(ANJAY_WITH_LWM2M11)\n        else if (!registration_failure_must_trigger_bootstrap\n                 && server_bootstrap_on_registration_failure(anjay, it)) {\n            registration_failure_must_trigger_bootstrap = true;\n        }\n#    endif // defined(ANJAY_WITH_LWM2M11)\n    }\n    return bootstrap_server_exists\n           && (!possibly_active_server_exists\n               || registration_failure_must_trigger_bootstrap);\n}\n#endif // ANJAY_WITH_BOOTSTRAP\n\nanjay_bootstrap_action_t\n_anjay_requested_bootstrap_action(anjay_unlocked_t *anjay) {\n#ifdef ANJAY_WITH_BOOTSTRAP\n    // if Bootstrap attempt is already ongoing, there's no need to do anything\n    if (!avs_coap_exchange_id_valid(\n                anjay->bootstrap.outgoing_request_exchange_id)) {\n        if (should_retry_bootstrap(anjay)) {\n            return ANJAY_BOOTSTRAP_ACTION_REQUEST;\n        }\n    }\n#endif // ANJAY_WITH_BOOTSTRAP\n    (void) anjay;\n    return ANJAY_BOOTSTRAP_ACTION_NONE;\n}\n\n/**\n * Checks whether all servers are inactive and have reached the limit of ICMP\n * failures (see the activation flow described in\n * _anjay_schedule_reload_servers() docs for details).\n */\nbool anjay_all_connections_failed(anjay_t *anjay_locked) {\n    bool result = false;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    if (anjay->servers) {\n        result = true;\n        AVS_LIST(anjay_server_info_t) it;\n        AVS_LIST_FOREACH(it, anjay->servers) {\n            if (_anjay_server_active(it) || !it->refresh_failed) {\n                result = false;\n                break;\n            }\n        }\n    }\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return result;\n}\n\nint _anjay_server_sched_activate(anjay_server_info_t *server) {\n    // start the backoff procedure from the beginning\n    assert(!_anjay_server_active(server));\n    assert(avs_time_real_valid(server->reactivate_time));\n    server->refresh_failed = false;\n    if (_anjay_server_is_disable_scheduled(server)) {\n        // Server is in the process of being disabled. Let it happen, it will be\n        // re-enabled afterwards, but make sure that reactivate_time is honored.\n        server->next_action =\n                ANJAY_SERVER_NEXT_ACTION_DISABLE_WITH_EXPLICIT_TIMEOUT;\n        return 0;\n    } else {\n        return _anjay_schedule_refresh_server(\n                server, avs_time_real_diff(server->reactivate_time,\n                                           avs_time_real_now()));\n    }\n}\n\nint _anjay_servers_sched_reactivate_all_given_up(anjay_unlocked_t *anjay) {\n    int result = 0;\n    bool active_server_exists = false;\n    anjay_server_info_t *bootstrap_server = NULL;\n\n    AVS_LIST(anjay_server_info_t) it;\n    AVS_LIST_FOREACH(it, anjay->servers) {\n        if (_anjay_server_active(it) || !it->refresh_failed) {\n            active_server_exists = true;\n            continue;\n        }\n        if (it->ssid == ANJAY_SSID_BOOTSTRAP) {\n            bootstrap_server = it;\n            if (!_anjay_bootstrap_legacy_server_initiated_allowed(anjay)) {\n                continue;\n            }\n        }\n        it->reactivate_time = avs_time_real_now();\n        it->registration_attempts = 0;\n        it->registration_sequences_performed = 0;\n        if (!_anjay_server_sched_activate(it)) {\n            active_server_exists = true;\n        } else {\n            result = -1;\n        }\n    }\n\n    // if legacy Server-Initiated Bootstrap is not allowed and no other servers\n    // exist, we want to reconnect the Bootstrap Server connection anyway.\n    if (!active_server_exists && bootstrap_server) {\n        assert(!_anjay_server_active(bootstrap_server));\n        assert(bootstrap_server->refresh_failed);\n        bootstrap_server->reactivate_time = avs_time_real_now();\n        if (_anjay_server_sched_activate(bootstrap_server)) {\n            result = -1;\n        }\n    }\n\n    return result;\n}\n\nvoid _anjay_servers_add(AVS_LIST(anjay_server_info_t) *servers,\n                        AVS_LIST(anjay_server_info_t) server) {\n    assert(AVS_LIST_SIZE(server) == 1);\n    AVS_LIST(anjay_server_info_t) *insert_ptr =\n            _anjay_servers_find_insert_ptr(servers, server->ssid);\n\n    assert(insert_ptr);\n    AVS_ASSERT((!*insert_ptr || (*insert_ptr)->ssid != server->ssid),\n               \"attempting to insert a duplicate of an already existing server \"\n               \"entry\");\n\n    AVS_LIST_INSERT(insert_ptr, server);\n}\n\nAVS_LIST(anjay_server_info_t)\n_anjay_servers_create_inactive(anjay_unlocked_t *anjay, anjay_ssid_t ssid) {\n    AVS_LIST(anjay_server_info_t) new_server =\n            AVS_LIST_NEW_ELEMENT(anjay_server_info_t);\n    if (!new_server) {\n        _anjay_log_oom();\n        return NULL;\n    }\n\n    new_server->anjay = anjay;\n    new_server->ssid = ssid;\n    new_server->last_used_security_iid = ANJAY_ID_INVALID;\n    new_server->registration_info.last_update_params.lifetime_s = -1;\n    _anjay_connection_get(&new_server->connections, ANJAY_CONNECTION_PRIMARY)\n            ->transport = ANJAY_SOCKET_TRANSPORT_INVALID;\n    new_server->reactivate_time = AVS_TIME_REAL_INVALID;\n    new_server->registration_info.lwm2m_version =\n#ifdef ANJAY_WITH_LWM2M11\n            anjay->lwm2m_version_config.maximum_version;\n#else  // ANJAY_WITH_LWM2M11\n            ANJAY_LWM2M_VERSION_1_0;\n#endif // ANJAY_WITH_LWM2M11\n#ifdef ANJAY_WITH_COMMUNICATION_TIMESTAMP_API\n    new_server->registration_info.last_registration_time =\n            AVS_TIME_REAL_INVALID;\n    new_server->last_communication_time = AVS_TIME_REAL_INVALID;\n#endif // ANJAY_WITH_COMMUNICATION_TIMESTAMP_API\n    return new_server;\n}\n\nvoid _anjay_disable_server_with_timeout_from_dm_sync(\n        anjay_server_info_t *server) {\n    anjay_iid_t server_iid;\n    if (_anjay_find_server_iid(server->anjay, server->ssid, &server_iid)) {\n        anjay_log(DEBUG,\n                  _(\"no Server Object Instance with SSID = \") \"%\" PRIu16 _(\n                          \", disabling skipped\"),\n                  server->ssid);\n    } else {\n        const avs_time_duration_t disable_timeout =\n                _anjay_disable_timeout_from_server_iid(server->anjay,\n                                                       server_iid);\n        server->reactivate_time =\n                avs_time_real_add(avs_time_real_now(), disable_timeout);\n        server->disabled_explicitly = true;\n        if (deactivate_server(server)) {\n            anjay_log(ERROR, _(\"unable to deactivate server: \") \"%\" PRIu16,\n                      server->ssid);\n        } else {\n            anjay_log(INFO, _(\"server \") \"%\" PRIu16 _(\" deactivated\"),\n                      server->ssid);\n        }\n    }\n}\n\nstatic int disable_server_impl(anjay_unlocked_t *anjay,\n                               anjay_ssid_t ssid,\n                               anjay_server_next_action_t disable_action,\n                               const char *disable_action_str,\n                               avs_time_duration_t timeout) {\n    assert(disable_action\n                   == ANJAY_SERVER_NEXT_ACTION_DISABLE_WITH_TIMEOUT_FROM_DM\n           || disable_action\n                      == ANJAY_SERVER_NEXT_ACTION_DISABLE_WITH_EXPLICIT_TIMEOUT);\n    if (ssid == ANJAY_SSID_ANY) {\n        anjay_log(WARNING, _(\"invalid SSID: \") \"%\" PRIu16, ssid);\n        return -1;\n    }\n\n    anjay_server_info_t *server = _anjay_servers_find(anjay, ssid);\n    if (!server) {\n        return -1;\n    }\n\n    bool server_active = _anjay_server_active(server);\n    if (!server_active && server->next_action_handle\n            && server->next_action\n                           == ANJAY_SERVER_NEXT_ACTION_COMMUNICATION_ERROR) {\n        return -1;\n    }\n\n    if (_anjay_server_reschedule_next_action(server, AVS_TIME_DURATION_ZERO,\n                                             disable_action)) {\n        anjay_log(ERROR, _(\"could not schedule \") \"%s\", disable_action_str);\n        return -1;\n    }\n    // EMB#4894: We want to avoid disabling the server when Anjay waits for\n    // Update response, so we cancel the coap exchange related to Update message\n    _anjay_registration_exchange_state_cleanup(\n            &server->registration_exchange_state);\n\n    if (disable_action\n            == ANJAY_SERVER_NEXT_ACTION_DISABLE_WITH_EXPLICIT_TIMEOUT) {\n        server->reactivate_time =\n                avs_time_real_add(avs_time_real_now(), timeout);\n    }\n    return 0;\n}\n\n/**\n * Disables a specified server - in a scheduler job which calls\n * deactivate_server(). The reactivation timeout is read from data model.\n * See the documentation of _anjay_schedule_reload_servers() for details on how\n * does the deactivation procedure work.\n */\nint anjay_disable_server(anjay_t *anjay_locked, anjay_ssid_t ssid) {\n    int result = -1;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n#ifdef ANJAY_WITH_CONN_STATUS_API\n    anjay_server_info_t *server = _anjay_servers_find(anjay, ssid);\n    if (server) {\n        _anjay_set_server_suspending_flag(server->anjay, server->ssid, true);\n    }\n#endif // ANJAY_WITH_CONN_STATUS_API\n    result = disable_server_impl(\n            anjay, ssid, ANJAY_SERVER_NEXT_ACTION_DISABLE_WITH_TIMEOUT_FROM_DM,\n            \"ANJAY_SERVER_NEXT_ACTION_DISABLE_WITH_TIMEOUT_FROM_DM\",\n            AVS_TIME_DURATION_INVALID);\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return result;\n}\n\nvoid _anjay_disable_server_with_explicit_timeout_sync(\n        anjay_server_info_t *server) {\n    server->disabled_explicitly = true;\n    if (deactivate_server(server)) {\n        anjay_log(ERROR, _(\"unable to deactivate server: \") \"%\" PRIu16,\n                  server->ssid);\n    } else {\n        if (avs_time_real_valid(server->reactivate_time)) {\n            anjay_log(INFO, _(\"server \") \"%\" PRIu16 _(\" disabled for \") \"%s\",\n                      server->ssid,\n                      AVS_TIME_DURATION_AS_STRING(avs_time_real_diff(\n                              server->reactivate_time, avs_time_real_now())));\n        } else {\n            anjay_log(INFO, _(\"server \") \"%\" PRIu16 _(\" disabled\"),\n                      server->ssid);\n        }\n    }\n}\n\nint _anjay_schedule_disable_server_with_explicit_timeout_unlocked(\n        anjay_unlocked_t *anjay,\n        anjay_ssid_t ssid,\n        avs_time_duration_t timeout) {\n    return disable_server_impl(\n            anjay, ssid, ANJAY_SERVER_NEXT_ACTION_DISABLE_WITH_EXPLICIT_TIMEOUT,\n            \"ANJAY_SERVER_NEXT_ACTION_DISABLE_WITH_EXPLICIT_TIMEOUT\", timeout);\n}\n\nint anjay_disable_server_with_timeout(anjay_t *anjay_locked,\n                                      anjay_ssid_t ssid,\n                                      avs_time_duration_t timeout) {\n    int result = -1;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n#ifdef ANJAY_WITH_CONN_STATUS_API\n    anjay_server_info_t *server = _anjay_servers_find(anjay, ssid);\n    if (server) {\n        _anjay_set_server_suspending_flag(server->anjay, server->ssid, true);\n    }\n#endif // ANJAY_WITH_CONN_STATUS_API\n    result = _anjay_schedule_disable_server_with_explicit_timeout_unlocked(\n            anjay, ssid, timeout);\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return result;\n}\n\nint _anjay_enable_server_unlocked(anjay_unlocked_t *anjay, anjay_ssid_t ssid) {\n    if (ssid == ANJAY_SSID_ANY) {\n        anjay_log(WARNING, _(\"invalid SSID: \") \"%\" PRIu16, ssid);\n        return -1;\n    }\n\n    AVS_LIST(anjay_server_info_t) *server_ptr =\n            _anjay_servers_find_ptr(&anjay->servers, ssid);\n\n    if (!server_ptr || !*server_ptr || _anjay_server_active(*server_ptr)) {\n        anjay_log(TRACE, _(\"not an inactive server: SSID = \") \"%\" PRIu16, ssid);\n        return -1;\n    }\n\n    if (ssid == ANJAY_SSID_BOOTSTRAP\n            && !_anjay_bootstrap_legacy_server_initiated_allowed(anjay)\n            && _anjay_requested_bootstrap_action(anjay)\n                           == ANJAY_BOOTSTRAP_ACTION_NONE) {\n        anjay_log(DEBUG,\n                  _(\"1.0-style Server-Initiated Bootstrap is disabled and \"\n                    \"Client - Initiated Bootstrap is currently not allowed, \"\n                    \"not enabling Bootstrap Server\"));\n        return -1;\n    }\n\n    (*server_ptr)->reactivate_time = avs_time_real_now();\n    return _anjay_server_sched_activate(*server_ptr);\n}\n\nint anjay_enable_server(anjay_t *anjay_locked, anjay_ssid_t ssid) {\n    int result = -1;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    result = _anjay_enable_server_unlocked(anjay, ssid);\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return result;\n}\n\nint anjay_server_schedule_reconnect(anjay_t *anjay_locked, anjay_ssid_t ssid) {\n    if (ssid == ANJAY_SSID_ANY) {\n        anjay_log(WARNING, _(\"invalid SSID: \") \"%\" PRIu16, ssid);\n        return -1;\n    }\n\n    int result = -1;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    AVS_LIST(anjay_server_info_t) *server_ptr =\n            _anjay_servers_find_ptr(&anjay->servers, ssid);\n\n    if (!server_ptr || !*server_ptr || !_anjay_server_active(*server_ptr)) {\n        anjay_log(TRACE, _(\"not an active server: SSID = \") \"%\" PRIu16, ssid);\n    } else {\n        anjay_connection_type_t conn_type;\n        ANJAY_CONNECTION_TYPE_FOREACH(conn_type) {\n            const anjay_connection_ref_t ref = {\n                .server = *server_ptr,\n                .conn_type = conn_type\n            };\n            _anjay_connection_suspend(ref);\n        }\n        result = _anjay_schedule_refresh_server(*server_ptr,\n                                                AVS_TIME_DURATION_ZERO);\n    }\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return result;\n}\n"
  },
  {
    "path": "src/core/servers/anjay_activate.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ANJAY_SERVERS_ACTIVATE_H\n#define ANJAY_SERVERS_ACTIVATE_H\n\n#include <anjay_modules/anjay_time_defs.h>\n\n#include \"anjay_connections.h\"\n#include \"anjay_register.h\"\n\n#include \"../anjay_core.h\"\n#include \"../anjay_utils_private.h\"\n\n#ifndef ANJAY_SERVERS_INTERNALS\n#    error \"Headers from servers/ are not meant to be included from outside\"\n#endif\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\n/**\n * This function is called as a \"callback\" whenever\n * @ref _anjay_active_server_refresh finishes its operation.\n *\n * @param server Server object for which the refresh was performed\n *\n * @param state  State of the server's primary connection after refresh\n *\n * @param err    If @p state is @ref ANJAY_SERVER_CONNECTION_ERROR, it shall be\n *               set to the reason of the failure. Otherwise, it shall be\n *               @ref AVS_OK. Currently it is only used to update the \"TLS/DTLS\n *               Alert Code\" resource if applicable.\n *\n * It performs any operations necessary after the refresh. In particular:\n *\n * - In case of error, refresh_failed flag is updated and retry of either server\n *   refresh or Client-Initiated Bootstrap is scheduled as appropriate.\n * - In case of success on a non-Bootstrap server, the valid registration state\n *   is asserted - Register or Update messages are sent and handled as\n *   necessary.\n * - In case of success on the Bootstrap server, Client-Initiated Bootstrap is\n *   scheduled to be performed if necessary.\n */\nvoid _anjay_server_on_refreshed(anjay_server_info_t *server,\n                                anjay_server_connection_state_t state,\n                                avs_error_t err);\n\nvoid _anjay_server_on_updated_registration(anjay_server_info_t *server,\n                                           anjay_registration_result_t result,\n                                           avs_error_t err);\n\n/**\n * Schedules a @ref _anjay_server_activate execution on\n * <c>server->reactivate_time</c>. Set that field before calling this function\n * to specify the intended reactivation time.\n *\n * Activation is performed as a retryable job, so it does not need to be\n * repeated by the caller.\n *\n * After the activation succeeds, the scheduled job takes care of any required\n * Registration Updates.\n */\nint _anjay_server_sched_activate(anjay_server_info_t *server);\n\nint _anjay_servers_sched_reactivate_all_given_up(anjay_unlocked_t *anjay);\n\n/**\n * Inserts an active server entry into @p servers .\n *\n * This function is meant to be used only for initialization of the @p servers\n * object, which should NOT contain any server entry with the same SSID as\n * @p server .\n *\n * Does not modify scheduled update job for @p server.\n */\nvoid _anjay_servers_add(AVS_LIST(anjay_server_info_t) *servers,\n                        AVS_LIST(anjay_server_info_t) server);\n\n/**\n * Creates a new detached inactive server entry for given @p ssid .\n *\n * Does not schedule the reactivate job for created entry.\n */\nAVS_LIST(anjay_server_info_t)\n_anjay_servers_create_inactive(anjay_unlocked_t *anjay, anjay_ssid_t ssid);\n\n/**\n * Synchronous part of @ref anjay_disable_server - this function does what that\n * public API schedules to be executed in an async job.\n */\nvoid _anjay_disable_server_with_timeout_from_dm_sync(\n        anjay_server_info_t *server);\n\n/**\n * Synchronous part of\n * @ref _anjay_schedule_disable_server_with_explicit_timeout_unlocked - this\n * function does what that API schedules to be executed in an async job.\n */\nvoid _anjay_disable_server_with_explicit_timeout_sync(\n        anjay_server_info_t *server);\n\n/**\n * Checks whether now is a right moment to initiate Client Initiated Bootstrap\n * as per requirements in the specification.\n *\n * @returns true if all requirements for Client Initiated Bootstrap are met,\n *          false otherwise.\n */\nanjay_bootstrap_action_t\n_anjay_requested_bootstrap_action(anjay_unlocked_t *anjay);\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif // ANJAY_SERVERS_ACTIVATE_H\n"
  },
  {
    "path": "src/core/servers/anjay_connection_ip.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#include <avsystem/commons/avs_errno.h>\n#include <avsystem/commons/avs_utils.h>\n\n#ifdef WITH_AVS_COAP_UDP\n#    include <avsystem/coap/udp.h>\n#endif // WITH_AVS_COAP_UDP\n#ifdef WITH_AVS_COAP_TCP\n#    include <avsystem/coap/tcp.h>\n#endif // WITH_AVS_COAP_TCP\n\n#include <inttypes.h>\n\n#define ANJAY_SERVERS_CONNECTION_SOURCE\n#define ANJAY_SERVERS_INTERNALS\n\n#include \"anjay_connections_internal.h\"\n#include \"anjay_server_connections.h\"\n#include \"anjay_servers_internal.h\"\n\n#ifdef ANJAY_TEST\n#    include \"tests/core/socket_mock.h\"\n#endif // ANJAY_TEST\n\nVISIBILITY_SOURCE_BEGIN\n\n#if defined(WITH_AVS_COAP_UDP) \\\n        || (defined(ANJAY_WITH_LWM2M11) && defined(WITH_AVS_COAP_TCP))\nstatic const avs_net_dtls_handshake_timeouts_t *\nget_tls_handshake_timeouts(anjay_unlocked_t *anjay) {\n    return &anjay->udp_dtls_hs_tx_params;\n}\n\nstatic avs_error_t\nprepare_connection(anjay_unlocked_t *anjay,\n                   anjay_server_connection_t *out_conn,\n                   const avs_net_ssl_configuration_t *socket_config,\n                   const avs_net_socket_dane_tlsa_record_t *dane_tlsa_record,\n                   const anjay_connection_info_t *info) {\n    (void) anjay;\n\n    const char *uri_scheme = avs_url_protocol(info->uri);\n    if (!info->transport_info || !info->transport_info->socket_type) {\n        anjay_log(ERROR,\n                  _(\"Protocol \") \"%s\" _(\" is not supported for IP transports\"),\n                  uri_scheme ? uri_scheme : \"(unknown)\");\n        return avs_errno(AVS_EINVAL);\n    }\n\n    if (_anjay_url_from_avs_url(info->uri, &out_conn->uri)) {\n        return avs_errno(AVS_ENOMEM);\n    }\n\n    out_conn->stateful = true;\n    avs_net_socket_t *socket = NULL;\n    bool is_tls = false;\n    avs_error_t err;\n    switch (*info->transport_info->socket_type) {\n    case AVS_NET_TCP_SOCKET:\n        err = avs_net_tcp_socket_create(&socket,\n                                        &socket_config->backend_configuration);\n        break;\n    case AVS_NET_UDP_SOCKET:\n        err = avs_net_udp_socket_create(&socket,\n                                        &socket_config->backend_configuration);\n        out_conn->stateful = false;\n        break;\n    case AVS_NET_SSL_SOCKET:\n        is_tls = true;\n        err = avs_net_ssl_socket_create(&socket, socket_config);\n        break;\n    case AVS_NET_DTLS_SOCKET:\n        is_tls = true;\n        err = avs_net_dtls_socket_create(&socket, socket_config);\n        break;\n    default:\n        break;\n    }\n    if (socket) {\n        assert(avs_is_ok(err));\n    } else {\n        anjay_log(ERROR, _(\"could not create CoAP socket\"));\n        if (avs_is_ok(err)) {\n            err = avs_errno(AVS_ENOMEM);\n        }\n        return err;\n    }\n\n    if (is_tls && dane_tlsa_record\n            && avs_is_err((err = avs_net_socket_set_opt(\n                                   socket, AVS_NET_SOCKET_OPT_DANE_TLSA_ARRAY,\n                                   (avs_net_socket_opt_value_t) {\n                                       .dane_tlsa_array = {\n                                           .array_ptr = dane_tlsa_record,\n                                           .array_element_count = 1\n                                       }\n                                   })))) {\n        _anjay_socket_cleanup(anjay, &socket);\n        anjay_log(ERROR, _(\"could not configure DANE TLSA record: \") \"%s\",\n                  AVS_COAP_STRERROR(err));\n        return err;\n    }\n\n    out_conn->conn_socket_ = socket;\n    return AVS_OK;\n}\n\nstatic avs_error_t connect_socket(anjay_unlocked_t *anjay,\n                                  anjay_server_connection_t *connection) {\n    (void) anjay;\n\n    avs_net_socket_t *socket =\n            _anjay_connection_internal_get_socket(connection);\n    avs_error_t err = avs_net_socket_connect(socket, connection->uri.host,\n                                             connection->uri.port);\n    if (avs_is_err(err)) {\n        anjay_log(ERROR, _(\"could not connect to \") \"%s\" _(\":\") \"%s\",\n                  connection->uri.host, connection->uri.port);\n        return err;\n    }\n\n    char local_port[sizeof(connection->nontransient_state.last_local_port)] =\n            \"\";\n    if (avs_is_ok(avs_net_socket_get_local_port(socket, local_port,\n                                                sizeof(local_port)))) {\n        anjay_log(DEBUG, _(\"bound to port \") \"%s\", local_port);\n    } else {\n        anjay_log(WARNING, _(\"could not store bound local port\"));\n        local_port[0] = '\\0';\n    }\n\n    if (strcmp(local_port, connection->nontransient_state.last_local_port)\n            != 0) {\n        strcpy(connection->nontransient_state.last_local_port, local_port);\n    }\n    return AVS_OK;\n}\n#endif /* defined(WITH_AVS_COAP_UDP) \\\n          || (defined(ANJAY_WITH_LWM2M11) && defined(WITH_AVS_COAP_TCP)) */\n\n#if defined(ANJAY_WITH_LWM2M11) && defined(WITH_AVS_COAP_TCP)\n\nstatic int ensure_tcp_coap_context(anjay_connection_ref_t ref) {\n    anjay_unlocked_t *anjay = _anjay_from_server(ref.server);\n    anjay_server_connection_t *connection = _anjay_get_server_connection(ref);\n    if (!connection->coap_ctx) {\n        connection->coap_ctx = avs_coap_tcp_ctx_create(\n                _anjay_get_coap_sched(anjay), anjay->in_shared_buffer,\n                anjay->out_shared_buffer, anjay->coap_tcp_max_options_size,\n                anjay->coap_tcp_request_timeout, anjay->prng_ctx.ctx);\n        if (!connection->coap_ctx) {\n            anjay_log(ERROR, _(\"could not create CoAP/TCP context\"));\n            return -1;\n        }\n    }\n\n    return 0;\n}\n\n#endif // defined(ANJAY_WITH_LWM2M11) && defined(WITH_AVS_COAP_TCP)\n\n#ifdef WITH_AVS_COAP_UDP\n\nstatic int ensure_udp_coap_context(anjay_connection_ref_t ref) {\n    anjay_unlocked_t *anjay = _anjay_from_server(ref.server);\n    anjay_server_connection_t *connection = _anjay_get_server_connection(ref);\n    if (!connection->coap_ctx) {\n        connection->coap_ctx = avs_coap_udp_ctx_create(\n                _anjay_get_coap_sched(anjay), &anjay->udp_tx_params,\n                anjay->in_shared_buffer, anjay->out_shared_buffer,\n                anjay->udp_response_cache, anjay->prng_ctx.ctx);\n        if (!connection->coap_ctx) {\n            anjay_log(ERROR, _(\"could not create CoAP/UDP context\"));\n            return -1;\n        }\n    }\n    return 0;\n}\n\nstatic avs_error_t\ntry_bind_to_static_preferred_port(anjay_unlocked_t *anjay,\n                                  anjay_server_connection_t *connection) {\n    if (anjay->udp_listen_port) {\n        char static_preferred_port[ANJAY_MAX_URL_PORT_SIZE] = \"\";\n        if (anjay->udp_listen_port\n                && avs_simple_snprintf(static_preferred_port,\n                                       sizeof(static_preferred_port),\n                                       \"%\" PRIu16, anjay->udp_listen_port)\n                               < 0) {\n            AVS_UNREACHABLE(\"Could not convert preferred port number\");\n        }\n        avs_error_t err = avs_net_socket_bind(\n                _anjay_connection_internal_get_socket(connection), NULL,\n                static_preferred_port);\n        if (avs_is_err(err)) {\n            anjay_log(ERROR, _(\"could not bind socket to port \") \"%s\",\n                      static_preferred_port);\n            return err;\n        }\n    }\n    return AVS_OK;\n}\n\nstatic avs_error_t\ntry_bind_to_last_local_port(anjay_server_connection_t *connection,\n                            const char *local_addr) {\n    avs_error_t err = avs_errno(AVS_EBADF);\n    if (*connection->nontransient_state.last_local_port) {\n        if (avs_is_ok(avs_net_socket_bind(\n                    _anjay_connection_internal_get_socket(connection),\n                    local_addr,\n                    connection->nontransient_state.last_local_port))) {\n            return AVS_OK;\n        }\n        // Binding to a specific address family may not work if a different\n        // family has been forced. Let's try without the local address.\n        if (avs_is_ok((\n                    err = avs_net_socket_bind(\n                            _anjay_connection_internal_get_socket(connection),\n                            NULL,\n                            connection->nontransient_state.last_local_port)))) {\n            return AVS_OK;\n        }\n        anjay_log(WARNING, _(\"could not bind socket to port \") \"%s\",\n                  connection->nontransient_state.last_local_port);\n    }\n    return err;\n}\n\nstatic const char *\nget_preferred_local_addr(const anjay_server_connection_t *connection) {\n#    ifdef ANJAY_WITHOUT_IP_STICKINESS\n    (void) connection;\n#    else  // ANJAY_WITHOUT_IP_STICKINESS\n    /*\n     * Whenever the socket is bound by connect(), the address family is set to\n     * match the remote address. If the socket is bound by a bind() call with\n     * NULL local_addr argument, the address family falls back to the original\n     * socket preference - by default, AF_UNSPEC. This causes avs_net to attempt\n     * to bind to [::]:$PORT, even though the remote host may be an IPv4\n     * address. This generally works, because IPv4-mapped IPv6 addresses are a\n     * thing.\n     *\n     * On FreeBSD though, IPv4-mapped IPv6 are disabled by default (see:\n     * \"Interaction between IPv4/v6 sockets\" at\n     * https://www.freebsd.org/cgi/man.cgi?query=inet6&sektion=4), which\n     * effectively breaks all connect() calls after re-binding to a recently\n     * used port.\n     *\n     * To avoid that, we need to provide a local wildcard address appropriate\n     * for the family used by the remote host. However, the first time we\n     * connect to the server, there is no \"preferred endpoint\" set yet, so\n     * endpoint is left uninitialized (filled with zeros) - that's why we check\n     * the size first.\n     */\n    char remote_preferred_host[sizeof(\n            \"ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255\")];\n    if (connection->nontransient_state.preferred_endpoint.size > 0\n            && avs_is_ok(avs_net_resolved_endpoint_get_host(\n                       &connection->nontransient_state.preferred_endpoint,\n                       remote_preferred_host, sizeof(remote_preferred_host)))) {\n        if (strchr(remote_preferred_host, ':') != NULL) {\n            return \"::\";\n        } else if (strchr(remote_preferred_host, '.') != NULL) {\n            return \"0.0.0.0\";\n        }\n    }\n#    endif // ANJAY_WITHOUT_IP_STICKINESS\n    return NULL;\n}\n\nstatic avs_error_t connect_udp_socket(anjay_unlocked_t *anjay,\n                                      anjay_server_connection_t *connection) {\n    const char *local_addr = get_preferred_local_addr(connection);\n    avs_error_t err;\n    if (avs_is_err(try_bind_to_last_local_port(connection, local_addr))\n            && avs_is_err((err = try_bind_to_static_preferred_port(\n                                   anjay, connection)))) {\n        return err;\n    }\n\n    return connect_socket(anjay, connection);\n}\n\nconst anjay_connection_type_definition_t ANJAY_CONNECTION_DEF_UDP = {\n    .name = \"UDP\",\n    .get_dtls_handshake_timeouts = get_tls_handshake_timeouts,\n    .prepare_connection = prepare_connection,\n    .ensure_coap_context = ensure_udp_coap_context,\n    .connect_socket = connect_udp_socket\n};\n\n#endif // WITH_AVS_COAP_UDP\n\n#if defined(ANJAY_WITH_LWM2M11) && defined(WITH_AVS_COAP_TCP)\nconst anjay_connection_type_definition_t ANJAY_CONNECTION_DEF_TCP = {\n    .name = \"TCP\",\n    .get_dtls_handshake_timeouts = get_tls_handshake_timeouts,\n    .prepare_connection = prepare_connection,\n    .ensure_coap_context = ensure_tcp_coap_context,\n    .connect_socket = connect_socket\n};\n#endif // defined(ANJAY_WITH_LWM2M11) && defined(WITH_AVS_COAP_TCP)\n"
  },
  {
    "path": "src/core/servers/anjay_connections.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#include <inttypes.h>\n\n#include <avsystem/commons/avs_errno.h>\n#include <avsystem/commons/avs_stream_inbuf.h>\n#include <avsystem/commons/avs_stream_membuf.h>\n#include <avsystem/commons/avs_utils.h>\n\n#ifdef WITH_AVS_COAP_UDP\n#    include <avsystem/coap/udp.h>\n#endif // WITH_AVS_COAP_UDP\n#ifdef WITH_AVS_COAP_TCP\n#    include <avsystem/coap/tcp.h>\n#endif // WITH_AVS_COAP_TCP\n\n#define ANJAY_SERVERS_CONNECTION_SOURCE\n#define ANJAY_SERVERS_INTERNALS\n\n#include \"../anjay_core.h\"\n#include \"../anjay_io_core.h\"\n#include \"../anjay_servers_reload.h\"\n#include \"../anjay_servers_utils.h\"\n#if defined(ANJAY_WITH_DOWNLOADER) && defined(ANJAY_WITH_LWM2M11)\n#    include \"../anjay_downloader.h\"\n#endif // defined(ANJAY_WITH_DOWNLOADER) && defined(ANJAY_WITH_LWM2M11)\n\n#include \"../dm/anjay_query.h\"\n\n#include \"../io/anjay_vtable.h\"\n\n#include \"anjay_activate.h\"\n#include \"anjay_connections_internal.h\"\n#include \"anjay_security.h\"\n#include \"anjay_server_connections.h\"\n\n#ifdef ANJAY_WITH_CONN_STATUS_API\n#    include \"../anjay_servers_inactive.h\"\n#endif // ANJAY_WITH_CONN_STATUS_API\n\nVISIBILITY_SOURCE_BEGIN\n\navs_net_socket_t *_anjay_connection_internal_get_socket(\n        const anjay_server_connection_t *connection) {\n    return connection->conn_socket_;\n}\n\nvoid _anjay_connection_internal_clean_socket(\n        anjay_unlocked_t *anjay, anjay_server_connection_t *connection) {\n#ifdef ANJAY_WITH_DOWNLOADER\n    // This would normally happen as part of _anjay_coap_ctx_cleanup() anyway\n    // (by means of the exchange result callback), but on rare occasions the\n    // download might be being suspended, and deliberately keep existing even\n    // though the exchange is cancelled. So let's abort download manually.\n    _anjay_downloader_abort_same_socket(&anjay->downloader,\n                                        connection->conn_socket_);\n#endif // ANJAY_WITH_DOWNLOADER\n    _anjay_coap_ctx_cleanup(anjay, &connection->coap_ctx);\n    _anjay_socket_cleanup(anjay, &connection->conn_socket_);\n#ifndef ANJAY_WITHOUT_QUEUE_MODE_AUTOCLOSE\n    avs_sched_del(&connection->queue_mode_close_socket_clb);\n#endif // ANJAY_WITHOUT_QUEUE_MODE_AUTOCLOSE\n}\n\ntypedef struct {\n    anjay_unlocked_output_ctx_t base;\n    const anjay_ret_bytes_ctx_vtable_t *ret_bytes_vtable;\n    avs_crypto_security_info_tag_t tag;\n    avs_crypto_security_info_union_t *out_array;\n    size_t out_element_count;\n    size_t bytes_remaining;\n} read_security_info_ctx_t;\n\nstatic int read_security_info_ret_bytes_begin(\n        anjay_unlocked_output_ctx_t *ctx_,\n        size_t length,\n        anjay_unlocked_ret_bytes_ctx_t **out_bytes_ctx) {\n    read_security_info_ctx_t *ctx = (read_security_info_ctx_t *) ctx_;\n    if (ctx->out_array) {\n        anjay_log(ERROR, _(\"value already returned\"));\n        return -1;\n    }\n    if (!(ctx->out_array = (avs_crypto_security_info_union_t *) avs_malloc(\n                  sizeof(*ctx->out_array) + length))) {\n        _anjay_log_oom();\n        return -1;\n    }\n    *ctx->out_array = (const avs_crypto_security_info_union_t) {\n        .type = ctx->tag,\n        .source = AVS_CRYPTO_DATA_SOURCE_BUFFER,\n        .info.buffer = {\n            .buffer = (char *) ctx->out_array + sizeof(*ctx->out_array),\n            .buffer_size = length\n        }\n    };\n    ctx->out_element_count = 1;\n    ctx->bytes_remaining = length;\n    *out_bytes_ctx = (anjay_unlocked_ret_bytes_ctx_t *) &ctx->ret_bytes_vtable;\n    return 0;\n}\n\nstatic int read_security_info_ret_bytes_append(\n        anjay_unlocked_ret_bytes_ctx_t *ctx_, const void *data, size_t size) {\n    read_security_info_ctx_t *ctx =\n            (read_security_info_ctx_t *) AVS_CONTAINER_OF(\n                    ctx_, read_security_info_ctx_t, ret_bytes_vtable);\n    assert(ctx->out_array);\n    if (size > ctx->bytes_remaining) {\n        anjay_log(DEBUG, _(\"tried to write too many bytes\"));\n        return -1;\n    }\n    memcpy(((char *) (intptr_t) ctx->out_array->info.buffer.buffer)\n                   + (ctx->out_array->info.buffer.buffer_size\n                      - ctx->bytes_remaining),\n           data, size);\n    ctx->bytes_remaining -= size;\n    return 0;\n}\n\n#if defined(ANJAY_WITH_SECURITY_STRUCTURED)\nstatic int read_security_info_ret_security_info(\n        anjay_unlocked_output_ctx_t *ctx_,\n        const avs_crypto_security_info_union_t *info) {\n    read_security_info_ctx_t *ctx = (read_security_info_ctx_t *) ctx_;\n    if (ctx->out_array) {\n        anjay_log(ERROR, _(\"value already returned\"));\n        return -1;\n    }\n    if (info->type != ctx->tag) {\n        anjay_log(ERROR, _(\"wrong type of security info passed\"));\n        return -1;\n    }\n    switch (ctx->tag) {\n    case AVS_CRYPTO_SECURITY_INFO_CERTIFICATE_CHAIN:\n        if (avs_is_err(avs_crypto_certificate_chain_info_copy_as_array(\n                    (avs_crypto_certificate_chain_info_t **) &ctx->out_array,\n                    &ctx->out_element_count,\n                    (const avs_crypto_certificate_chain_info_t) {\n                        .desc = *info\n                    }))) {\n            assert(!ctx->out_array);\n            assert(!ctx->out_element_count);\n            return -1;\n        } else {\n            assert(ctx->out_array || !ctx->out_element_count);\n            return 0;\n        }\n    case AVS_CRYPTO_SECURITY_INFO_PRIVATE_KEY:\n        if (avs_is_err(avs_crypto_private_key_info_copy(\n                    (avs_crypto_private_key_info_t **) &ctx->out_array,\n                    (const avs_crypto_private_key_info_t) {\n                        .desc = *info\n                    }))) {\n            assert(!ctx->out_array);\n            return -1;\n        } else {\n            assert(ctx->out_array);\n            ctx->out_element_count = 1;\n            return 0;\n        }\n    case AVS_CRYPTO_SECURITY_INFO_PSK_IDENTITY:\n        if (avs_is_err(avs_crypto_psk_identity_info_copy(\n                    (avs_crypto_psk_identity_info_t **) &ctx->out_array,\n                    (const avs_crypto_psk_identity_info_t) {\n                        .desc = *info\n                    }))) {\n            assert(!ctx->out_array);\n            return -1;\n        } else {\n            assert(ctx->out_array);\n            ctx->out_element_count = 1;\n            return 0;\n        }\n    case AVS_CRYPTO_SECURITY_INFO_PSK_KEY:\n        if (avs_is_err(avs_crypto_psk_key_info_copy(\n                    (avs_crypto_psk_key_info_t **) &ctx->out_array,\n                    (const avs_crypto_psk_key_info_t) {\n                        .desc = *info\n                    }))) {\n            assert(!ctx->out_array);\n            return -1;\n        } else {\n            assert(ctx->out_array);\n            ctx->out_element_count = 1;\n            return 0;\n        }\n    default:\n        AVS_UNREACHABLE(\"invalid tag\");\n        return -1;\n    }\n}\n#endif /* defined(ANJAY_WITH_SECURITY_STRUCTURED) || \\\n          defined(ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT) */\n\nstatic const anjay_output_ctx_vtable_t READ_SECURITY_INFO_VTABLE = {\n    .bytes_begin = read_security_info_ret_bytes_begin\n#if defined(ANJAY_WITH_SECURITY_STRUCTURED)\n    ,\n    .security_info = read_security_info_ret_security_info\n#endif /* defined(ANJAY_WITH_SECURITY_STRUCTURED) || \\\n          defined(ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT) */\n};\n\nstatic const anjay_ret_bytes_ctx_vtable_t READ_SECURITY_INFO_BYTES_VTABLE = {\n    .append = read_security_info_ret_bytes_append\n};\n\navs_error_t\n_anjay_dm_read_security_info(anjay_unlocked_t *anjay,\n                             anjay_iid_t security_iid,\n                             anjay_rid_t security_rid,\n                             avs_crypto_security_info_tag_t tag,\n                             avs_crypto_security_info_union_t **out_array,\n                             size_t *out_element_count) {\n    assert(anjay);\n    assert(out_array);\n    assert(!*out_array);\n    assert(out_element_count);\n    read_security_info_ctx_t ctx = {\n        .base = {\n            .vtable = &READ_SECURITY_INFO_VTABLE\n        },\n        .ret_bytes_vtable = &READ_SECURITY_INFO_BYTES_VTABLE,\n        .tag = tag\n    };\n    const anjay_uri_path_t path =\n            MAKE_RESOURCE_PATH(ANJAY_DM_OID_SECURITY, security_iid,\n                               security_rid);\n    if (_anjay_dm_read_resource_into_ctx(anjay, &path,\n                                         (anjay_unlocked_output_ctx_t *) &ctx)\n            || ctx.bytes_remaining) {\n        anjay_log(WARNING, _(\"read \") \"%s\" _(\" failed\"),\n                  ANJAY_DEBUG_MAKE_PATH(&path));\n        avs_free(ctx.out_array);\n        return avs_errno(AVS_EPROTO);\n    }\n    *out_array = ctx.out_array;\n    *out_element_count = ctx.out_element_count;\n    return AVS_OK;\n}\n\navs_error_t\n_anjay_connection_init_psk_security(anjay_unlocked_t *anjay,\n                                    anjay_iid_t security_iid,\n                                    anjay_rid_t identity_rid,\n                                    anjay_rid_t secret_key_rid,\n                                    anjay_security_config_t *security,\n                                    anjay_security_config_cache_t *cache) {\n    assert(anjay);\n    avs_error_t err;\n    size_t element_count = 0;\n\n    avs_net_psk_info_t psk_info;\n    memset(&psk_info, 0, sizeof(psk_info));\n\n    AVS_STATIC_ASSERT(sizeof(*cache->psk_key)\n                              == sizeof(avs_crypto_security_info_union_t),\n                      psk_key_info_equivalent_to_union);\n    if (avs_is_err(\n                (err = _anjay_dm_read_security_info(\n                         anjay, security_iid, secret_key_rid,\n                         AVS_CRYPTO_SECURITY_INFO_PSK_KEY,\n                         (avs_crypto_security_info_union_t **) &cache->psk_key,\n                         &element_count)))) {\n        return err;\n    }\n    assert(element_count == 1);\n    psk_info.key = *cache->psk_key;\n\n    AVS_STATIC_ASSERT(sizeof(*cache->psk_identity)\n                              == sizeof(avs_crypto_security_info_union_t),\n                      psk_identity_info_equivalent_to_union);\n    if (avs_is_err((err = _anjay_dm_read_security_info(\n                            anjay, security_iid, identity_rid,\n                            AVS_CRYPTO_SECURITY_INFO_PSK_IDENTITY,\n                            (avs_crypto_security_info_union_t **) &cache\n                                    ->psk_identity,\n                            &element_count)))) {\n        return err;\n    }\n    assert(element_count == 1);\n    psk_info.identity = *cache->psk_identity;\n\n    security->security_info = avs_net_security_info_from_psk(psk_info);\n#ifdef ANJAY_WITH_LWM2M11\n    if (avs_is_err((err = _anjay_server_read_sni(anjay, security_iid, security,\n                                                 cache)))) {\n        return err;\n    }\n#endif // ANJAY_WITH_LWM2M11\n    return AVS_OK;\n}\n\nstatic const anjay_connection_type_definition_t *\nget_connection_type_def(anjay_socket_transport_t type) {\n    switch (type) {\n#ifdef WITH_AVS_COAP_UDP\n    case ANJAY_SOCKET_TRANSPORT_UDP:\n        return &ANJAY_CONNECTION_DEF_UDP;\n#endif // WITH_AVS_COAP_UDP\n#if defined(ANJAY_WITH_LWM2M11) && defined(WITH_AVS_COAP_TCP)\n    case ANJAY_SOCKET_TRANSPORT_TCP:\n        return &ANJAY_CONNECTION_DEF_TCP;\n#endif // defined(ANJAY_WITH_LWM2M11) && defined(WITH_AVS_COAP_TCP)\n    default:\n        return NULL;\n    }\n}\n\nstatic void update_exchange_timeout(anjay_server_info_t *server,\n                                    anjay_connection_type_t conn_type) {\n    anjay_server_connection_t *conn =\n            _anjay_connection_get(&server->connections, conn_type);\n    assert(conn->coap_ctx);\n    avs_time_duration_t exchange_max_time;\n    switch (conn->transport) {\n#ifdef WITH_AVS_COAP_UDP\n    case ANJAY_SOCKET_TRANSPORT_UDP:\n        exchange_max_time = server->anjay->udp_exchange_timeout;\n        break;\n#endif // WITH_AVS_COAP_UDP\n#if defined(ANJAY_WITH_LWM2M11) && defined(WITH_AVS_COAP_TCP)\n    case ANJAY_SOCKET_TRANSPORT_TCP:\n        exchange_max_time = server->anjay->tcp_exchange_timeout;\n        break;\n#endif // defined(ANJAY_WITH_LWM2M11) && defined(WITH_AVS_COAP_TCP)\n    default:\n        AVS_UNREACHABLE(\"Invalid connection type\");\n        return;\n    }\n    avs_coap_set_exchange_max_time(conn->coap_ctx, exchange_max_time);\n}\n\nint _anjay_connection_ensure_coap_context(anjay_server_info_t *server,\n                                          anjay_connection_type_t conn_type) {\n    anjay_server_connection_t *conn =\n            _anjay_connection_get(&server->connections, conn_type);\n    const anjay_connection_type_definition_t *def =\n            get_connection_type_def(conn->transport);\n    assert(def);\n    int result = def->ensure_coap_context((anjay_connection_ref_t) {\n        .server = server,\n        .conn_type = conn_type\n    });\n    if (!result) {\n        update_exchange_timeout(server, conn_type);\n    }\n    return result;\n}\n\navs_error_t _anjay_server_connection_internal_bring_online(\n        anjay_server_info_t *server, anjay_connection_type_t conn_type) {\n    assert(server);\n    anjay_server_connection_t *connection =\n            _anjay_connection_get(&server->connections, conn_type);\n    assert(connection);\n    assert(connection->conn_socket_);\n\n    const anjay_connection_type_definition_t *def =\n            get_connection_type_def(connection->transport);\n    assert(def);\n\n    if (_anjay_connection_is_online(connection)) {\n        anjay_log(DEBUG, _(\"socket already connected\"));\n        connection->needs_observe_flush = true;\n        return AVS_OK;\n    }\n\n#ifdef ANJAY_WITH_CONN_STATUS_API\n    if (conn_type == ANJAY_CONNECTION_PRIMARY) {\n        _anjay_set_server_connection_status(server,\n                                            ANJAY_SERV_CONN_STATUS_CONNECTING);\n    }\n#endif // ANJAY_WITH_CONN_STATUS_API\n       // defined(ANJAY_WITH_CORE_PERSISTENCE)\n\n    bool session_resumed;\n    avs_error_t err = AVS_OK;\n    if (avs_is_err((err = def->connect_socket(server->anjay, connection)))) {\n        goto error;\n    }\n\n    if (!(session_resumed =\n                  _anjay_was_session_resumed(connection->conn_socket_))) {\n        _anjay_conn_session_token_reset(&connection->session_token,\n                                        &server->anjay->session_token_counter);\n        // Clean up and recreate the CoAP context to discard observations\n        // NOTE: In old versions of avs_coap, this was sending the Release\n        // message. This may need to be revised if it's ever reintroduced.\n        _anjay_coap_ctx_cleanup(server->anjay, &connection->coap_ctx);\n    }\n\n    if (_anjay_connection_ensure_coap_context(server, conn_type)) {\n        err = avs_errno(AVS_ENOMEM);\n        goto error;\n    }\n    if (!avs_coap_ctx_has_socket(connection->coap_ctx)\n            && avs_is_err((err = avs_coap_ctx_set_socket(\n                                   connection->coap_ctx,\n                                   connection->conn_socket_)))) {\n        anjay_log(ERROR, _(\"could not assign socket to CoAP/UDP context\"));\n        goto error;\n    }\n\n    if (session_resumed) {\n        if (!connection->stateful\n                || _anjay_was_connection_id_resumed(connection->conn_socket_)) {\n            connection->state = ANJAY_SERVER_CONNECTION_STABLE;\n            anjay_log(INFO, \"statelessly resumed connection\");\n        } else {\n            connection->state = ANJAY_SERVER_CONNECTION_FRESHLY_CONNECTED;\n            anjay_log(INFO, \"statefully resumed connection\");\n        }\n    } else {\n        connection->state = ANJAY_SERVER_CONNECTION_FRESHLY_CONNECTED;\n        anjay_log(INFO, \"reconnected\");\n    }\n    // NOTE: needs_observe_flush also controls flushing Send messages,\n    // so we need it even if there are no observations due to new session\n    connection->needs_observe_flush = true;\n    return AVS_OK;\n\nerror:\n    connection->state = ANJAY_SERVER_CONNECTION_OFFLINE;\n    _anjay_coap_ctx_cleanup(server->anjay, &connection->coap_ctx);\n\n    if (connection->conn_socket_\n            && avs_is_err(avs_net_socket_close(connection->conn_socket_))) {\n        anjay_log(WARNING, _(\"Could not close the socket (?!)\"));\n    }\n    return err;\n}\n\nstatic void connection_cleanup(anjay_unlocked_t *anjay,\n                               anjay_server_connection_t *connection) {\n    _anjay_connection_internal_clean_socket(anjay, connection);\n    _anjay_url_cleanup(&connection->uri);\n}\n\nvoid _anjay_connections_close(anjay_unlocked_t *anjay,\n                              anjay_connections_t *connections) {\n    anjay_connection_type_t conn_type;\n    ANJAY_CONNECTION_TYPE_FOREACH(conn_type) {\n        connection_cleanup(anjay,\n                           _anjay_connection_get(connections, conn_type));\n    }\n}\n\nanjay_conn_session_token_t\n_anjay_connections_get_primary_session_token(anjay_connections_t *connections) {\n    return _anjay_connection_get(connections, ANJAY_CONNECTION_PRIMARY)\n            ->session_token;\n}\n\nvoid _anjay_connection_internal_invalidate_session(\n        anjay_server_connection_t *connection) {\n    memset(connection->nontransient_state.dtls_session_buffer, 0,\n           sizeof(connection->nontransient_state.dtls_session_buffer));\n}\n\nstatic avs_error_t\nrecreate_socket(anjay_unlocked_t *anjay,\n                const anjay_connection_type_definition_t *def,\n                anjay_server_connection_t *connection,\n                anjay_connection_info_t *inout_info) {\n    avs_net_ssl_configuration_t socket_config;\n    memset(&socket_config, 0, sizeof(socket_config));\n\n    assert(!_anjay_connection_internal_get_socket(connection));\n    socket_config.backend_configuration = anjay->socket_config;\n    socket_config.backend_configuration.reuse_addr = 1;\n#ifndef ANJAY_WITHOUT_IP_STICKINESS\n    socket_config.backend_configuration.preferred_endpoint =\n            &connection->nontransient_state.preferred_endpoint;\n#endif // ANJAY_WITHOUT_IP_STICKINESS\n    socket_config.version = anjay->dtls_version;\n    socket_config.session_resumption_buffer =\n            connection->nontransient_state.dtls_session_buffer;\n    socket_config.session_resumption_buffer_size =\n            sizeof(connection->nontransient_state.dtls_session_buffer);\n    socket_config.dtls_handshake_timeouts =\n            def->get_dtls_handshake_timeouts(anjay);\n    socket_config.additional_configuration_clb =\n            anjay->additional_tls_config_clb;\n    socket_config.use_connection_id = anjay->use_connection_id;\n    socket_config.prng_ctx = anjay->prng_ctx.ctx;\n\n    // At this point, inout_info has \"global\" settings filled,\n    // but transport-specific (i.e. UDP or SMS) fields are not\n    anjay_security_config_t security_config;\n    anjay_security_config_cache_t security_config_cache;\n    memset(&security_config_cache, 0, sizeof(security_config_cache));\n    avs_error_t err;\n    {\n        err = _anjay_connection_security_generic_get_config(\n                anjay, &security_config, &security_config_cache, inout_info);\n    }\n    if (avs_is_err(err)) {\n        anjay_log(DEBUG,\n                  _(\"could not get \") \"%s\" _(\n                          \" security config for server \") \"/%u/%u\",\n                  def->name, ANJAY_DM_OID_SECURITY, inout_info->security_iid);\n    } else {\n        socket_config.security = security_config.security_info;\n        socket_config.ciphersuites = security_config.tls_ciphersuites;\n        socket_config.server_name_indication =\n                security_config.server_name_indication;\n        if (avs_is_err((err = def->prepare_connection(\n                                anjay, connection, &socket_config,\n                                security_config.dane_tlsa_record, inout_info)))\n                && connection->conn_socket_) {\n            avs_net_socket_shutdown(connection->conn_socket_);\n            avs_net_socket_close(connection->conn_socket_);\n        }\n    }\n    _anjay_security_config_cache_cleanup(&security_config_cache);\n    return err;\n}\n\nstatic avs_error_t\nensure_socket_connected(anjay_server_info_t *server,\n                        anjay_connection_type_t conn_type,\n                        anjay_connection_info_t *inout_info) {\n    anjay_server_connection_t *connection =\n            _anjay_connection_get(&server->connections, conn_type);\n    assert(connection);\n    const anjay_connection_type_definition_t *def =\n            get_connection_type_def(connection->transport);\n    assert(def);\n    avs_net_socket_t *existing_socket =\n            _anjay_connection_internal_get_socket(connection);\n\n    if (existing_socket == NULL) {\n        avs_error_t err =\n                recreate_socket(server->anjay, def, connection, inout_info);\n        if (avs_is_err(err)) {\n            connection->state = ANJAY_SERVER_CONNECTION_OFFLINE;\n            return err;\n        }\n    }\n\n    return _anjay_server_connection_internal_bring_online(server, conn_type);\n}\n\n#ifdef ANJAY_WITHOUT_QUEUE_MODE_AUTOCLOSE\n#    define should_primary_connection_be_online(...) true\n#else // ANJAY_WITHOUT_QUEUE_MODE_AUTOCLOSE\nstatic bool should_primary_connection_be_online(anjay_server_info_t *server) {\n    anjay_connection_ref_t ref = {\n        .server = server,\n        .conn_type = ANJAY_CONNECTION_PRIMARY\n    };\n    anjay_server_connection_t *connection = _anjay_get_server_connection(ref);\n    avs_net_socket_t *socket =\n            _anjay_connection_internal_get_socket(connection);\n    // Server is supposed to be active, so we need to create the socket\n    return !socket\n           // Bootstrap Server has no concept of queue mode\n           || server->ssid == ANJAY_SSID_BOOTSTRAP\n           // if connection is already online, no reason to disconnect it\n           || _anjay_connection_get_online_socket(ref)\n           // if registration expired, we need to connect to renew it\n           || server->registration_info.update_forced\n           || _anjay_server_registration_expired(server)\n           // if queue mode is not enabled, server shall always be online\n           || !server->registration_info.queue_mode\n           // if there are notifications to be sent, we need to send them\n           || _anjay_observe_needs_flushing(ref)\n#    ifdef ANJAY_WITH_SEND\n           // if there are Send messages to be sent, we need to send them\n           || _anjay_send_has_deferred(server->anjay, server->ssid)\n#    endif // ANJAY_WITH_SEND\n            ;\n}\n#endif     // ANJAY_WITHOUT_QUEUE_MODE_AUTOCLOSE\n\nstatic avs_error_t refresh_connection(anjay_server_info_t *server,\n                                      anjay_connection_type_t conn_type,\n                                      bool enabled,\n                                      anjay_connection_info_t *inout_info) {\n    anjay_server_connection_t *out_connection =\n            _anjay_connection_get(&server->connections, conn_type);\n    assert(out_connection);\n\n    _anjay_url_cleanup(&out_connection->uri);\n\n    if (!enabled) {\n        if (conn_type == ANJAY_CONNECTION_PRIMARY) {\n            _anjay_connection_suspend((anjay_connection_ref_t) {\n                .server = server,\n                .conn_type = ANJAY_CONNECTION_PRIMARY\n            });\n            out_connection->state = ANJAY_SERVER_CONNECTION_OFFLINE;\n        } else {\n            // Disabled trigger connection does not matter much,\n            // so treat it as stable\n            _anjay_connection_internal_clean_socket(server->anjay,\n                                                    out_connection);\n            out_connection->state = ANJAY_SERVER_CONNECTION_STABLE;\n        }\n        out_connection->needs_observe_flush = false;\n        return AVS_OK;\n    } else {\n        return ensure_socket_connected(server, conn_type, inout_info);\n    }\n}\n\nvoid _anjay_server_connections_refresh(anjay_server_info_t *server,\n                                       anjay_iid_t security_iid,\n                                       avs_url_t **move_uri) {\n    anjay_connection_info_t server_info = {\n        .ssid = server->ssid,\n        .security_iid = security_iid,\n    };\n    if (*move_uri) {\n        server_info.uri = *move_uri;\n        server_info.transport_info = _anjay_transport_info_by_uri_scheme(\n                avs_url_protocol(*move_uri));\n        *move_uri = NULL;\n    }\n\n    if (security_iid != ANJAY_ID_INVALID) {\n        server->last_used_security_iid = security_iid;\n    }\n    anjay_server_connection_t *primary_conn =\n            _anjay_connection_get(&server->connections,\n                                  ANJAY_CONNECTION_PRIMARY);\n    if (server_info.transport_info\n            && (!_anjay_socket_transport_supported(\n                        server->anjay, server_info.transport_info->transport)\n                || !_anjay_socket_transport_is_online(\n                           server->anjay,\n                           server_info.transport_info->transport))) {\n        anjay_log(WARNING,\n                  _(\"transport required for protocol \") \"%s\" _(\n                          \" is not supported or offline\"),\n                  server_info.transport_info->uri_scheme);\n        server_info.transport_info = NULL;\n    }\n    if (server_info.transport_info\n            && primary_conn->transport\n                           != server_info.transport_info->transport) {\n        char old_binding[] = \"(none)\";\n        if (primary_conn->transport != ANJAY_SOCKET_TRANSPORT_INVALID) {\n            old_binding[0] =\n                    _anjay_binding_info_by_transport(primary_conn->transport)\n                            ->letter;\n            old_binding[1] = '\\0';\n        }\n        char new_binding = _anjay_binding_info_by_transport(\n                                   server_info.transport_info->transport)\n                                   ->letter;\n        const char *host = avs_url_host(server_info.uri);\n        const char *port = avs_url_port(server_info.uri);\n        anjay_log(INFO,\n                  _(\"server /0/\") \"%u\" _(\": transport change: \") \"%s\" _(\n                          \" -> \") \"%c\" _(\" (uri: \") \"%s://%s%s%s\" _(\")\"),\n                  security_iid, old_binding, new_binding,\n                  server_info.transport_info->uri_scheme, host ? host : \"\",\n                  port ? \":\" : \"\", port ? port : \"\");\n        // change in transport binding requries creating a different type of\n        // socket and possibly CoAP context\n        connection_cleanup(server->anjay, primary_conn);\n        primary_conn->transport = server_info.transport_info->transport;\n        server->registration_info.expire_time = AVS_TIME_REAL_INVALID;\n    }\n\n    anjay_connection_type_t conn_type;\n    ANJAY_CONNECTION_TYPE_FOREACH(conn_type) {\n        anjay_server_connection_t *connection =\n                _anjay_connection_get(&server->connections, conn_type);\n#ifndef ANJAY_WITHOUT_QUEUE_MODE_AUTOCLOSE\n        avs_sched_del(&connection->queue_mode_close_socket_clb);\n#endif // ANJAY_WITHOUT_QUEUE_MODE_AUTOCLOSE\n    }\n    avs_error_t err = refresh_connection(\n            server, ANJAY_CONNECTION_PRIMARY,\n            !!server_info.transport_info\n                    && should_primary_connection_be_online(server),\n            &server_info);\n\n    // TODO T2391: fall back to another transport if connection failed\n    _anjay_server_on_refreshed(server, primary_conn->state, err);\n    _anjay_connection_info_cleanup(&server_info);\n}\n\navs_error_t _anjay_get_security_config(anjay_unlocked_t *anjay,\n                                       anjay_security_config_t *out_config,\n                                       anjay_security_config_cache_t *cache,\n                                       anjay_ssid_t ssid,\n                                       anjay_iid_t security_iid) {\n    anjay_connection_info_t info = {\n        .ssid = ssid,\n        .security_iid = security_iid\n    };\n    avs_error_t err =\n            _anjay_connection_security_generic_get_config(anjay, out_config,\n                                                          cache, &info);\n    _anjay_connection_info_cleanup(&info);\n    return err;\n}\n\n#ifdef ANJAY_WITH_LWM2M11\nvoid _anjay_server_update_last_ssl_alert_code(const anjay_server_info_t *info,\n                                              uint8_t level,\n                                              uint8_t description) {\n    (void) level;\n\n    if (info->ssid == ANJAY_SSID_BOOTSTRAP) {\n        // This operation does not make sense for Bootstrap Server, because it\n        // has no Server Instance.\n        return;\n    }\n\n    anjay_iid_t server_iid;\n    if (_anjay_find_server_iid(info->anjay, info->ssid, &server_iid)) {\n        anjay_log(\n                DEBUG,\n                _(\"could not find Server Instance associated with SSID \") \"%u\",\n                (unsigned) info->ssid);\n        return;\n    }\n\n    anjay_log(DEBUG,\n              _(\"last SSL alert code for server with SSID \") \"%u\" _(\":\") \" %u\",\n              (unsigned) info->ssid, (unsigned) description);\n\n    anjay_uri_path_t path =\n            MAKE_RESOURCE_PATH(ANJAY_DM_OID_SERVER, server_iid,\n                               ANJAY_DM_RID_SERVER_TLS_DTLS_ALERT_CODE);\n\n    (void) _anjay_dm_write_resource_u64(info->anjay, path, description, NULL);\n}\n#endif // ANJAY_WITH_LWM2M11\n\nbool _anjay_socket_transport_supported(anjay_unlocked_t *anjay,\n                                       anjay_socket_transport_t type) {\n    if (get_connection_type_def(type) == NULL) {\n        return false;\n    }\n\n    (void) anjay;\n    return true;\n}\n\n#ifdef ANJAY_WITH_CONN_STATUS_API\nvoid _anjay_set_server_suspending_flag(anjay_unlocked_t *anjay,\n                                       anjay_ssid_t ssid,\n                                       bool val) {\n    assert(ssid != ANJAY_SSID_ANY);\n    anjay_server_info_t *server = _anjay_servers_find(anjay, ssid);\n    if (!server) {\n        return;\n    }\n    server->suspending = val;\n}\n\nvoid _anjay_set_server_connection_status(\n        anjay_server_info_t *server, anjay_server_conn_status_t new_status) {\n    if (new_status != server->connection_status) {\n        server->connection_status = new_status;\n        if (server->anjay->server_connection_status_cb) {\n            ANJAY_MUTEX_UNLOCK_FOR_CALLBACK(anjay_locked, server->anjay);\n            server->anjay->server_connection_status_cb(\n                    server->anjay->server_connection_status_cb_arg,\n                    anjay_locked, server->ssid, new_status);\n            ANJAY_MUTEX_LOCK_AFTER_CALLBACK(anjay_locked);\n        }\n    }\n\n    if (new_status == ANJAY_SERV_CONN_STATUS_REGISTERED\n            || new_status == ANJAY_SERV_CONN_STATUS_INVALID\n            || new_status == ANJAY_SERV_CONN_STATUS_INITIAL\n            || new_status == ANJAY_SERV_CONN_STATUS_ERROR\n            || new_status == ANJAY_SERV_CONN_STATUS_DEREGISTERING\n            || new_status == ANJAY_SERV_CONN_STATUS_SUSPENDING\n            || new_status == ANJAY_SERV_CONN_STATUS_UPDATING) {\n        server->reregistration = false;\n    }\n}\n\nvoid _anjay_check_server_connection_status(anjay_server_info_t *server) {\n    if (!server || server->ssid == ANJAY_SSID_BOOTSTRAP) {\n        return;\n    }\n    assert(server->ssid != ANJAY_SSID_ANY);\n\n    anjay_server_conn_status_t current_status = server->connection_status;\n\n    if (_anjay_server_connection_active((anjay_connection_ref_t) {\n            .server = server,\n            .conn_type = ANJAY_CONNECTION_PRIMARY\n        })) {\n        /* expired mean that we aren't registered or lifetime is exceeded */\n        if (!_anjay_server_registration_expired(server)) {\n            if (avs_coap_exchange_id_valid(\n                        server->registration_exchange_state.exchange_id)) {\n                current_status = ANJAY_SERV_CONN_STATUS_UPDATING;\n            } else {\n                current_status = ANJAY_SERV_CONN_STATUS_REGISTERED;\n            }\n        } else if (avs_coap_exchange_id_valid(\n                           server->registration_exchange_state.exchange_id)) {\n            current_status = server->reregistration\n                                     ? ANJAY_SERV_CONN_STATUS_REREGISTERING\n                                     : ANJAY_SERV_CONN_STATUS_REGISTERING;\n        }\n    } else {\n        // In the contexts in which this function is called,\n        // this could only be an error\n        current_status = ANJAY_SERV_CONN_STATUS_ERROR;\n    }\n    _anjay_set_server_connection_status(server, current_status);\n}\n\nanjay_server_conn_status_t\nanjay_get_server_connection_status(anjay_t *anjay, anjay_ssid_t ssid) {\n    if (ssid == ANJAY_SSID_ANY) {\n        return ANJAY_SERV_CONN_STATUS_INVALID;\n    }\n    anjay_server_conn_status_t ret = ANJAY_SERV_CONN_STATUS_INVALID;\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    anjay_server_info_t *server = _anjay_servers_find(anjay_unlocked, ssid);\n    if (server) {\n        ret = server->connection_status;\n    }\n    ANJAY_MUTEX_UNLOCK(anjay);\n    return ret;\n}\n#endif // ANJAY_WITH_CONN_STATUS_API\n\n#ifdef ANJAY_WITH_LWM2M11\navs_error_t _anjay_server_read_sni(anjay_unlocked_t *anjay,\n                                   anjay_iid_t security_iid,\n                                   anjay_security_config_t *security,\n                                   anjay_security_config_cache_t *cache) {\n    avs_error_t err = AVS_OK;\n\n    security->server_name_indication = NULL;\n    const anjay_uri_path_t server_sni =\n            MAKE_RESOURCE_PATH(ANJAY_DM_OID_SECURITY, security_iid,\n                               ANJAY_DM_RID_SECURITY_SNI);\n    int result = _anjay_dm_read_resource_string(\n            anjay, &server_sni, cache->server_name_indication,\n            sizeof(cache->server_name_indication));\n    if (!result) {\n        anjay_log(INFO, _(\"using SNI \") \"%s\" _(\" for /0/\") \"%u\",\n                  cache->server_name_indication, (unsigned) security_iid);\n        security->server_name_indication = cache->server_name_indication;\n    } else if (result == ANJAY_ERR_NOT_FOUND\n               || result == ANJAY_ERR_METHOD_NOT_ALLOWED) {\n        anjay_log(INFO, _(\"no SNI for /0/\") \"%u\" _(\", using defaults\"),\n                  (unsigned) security_iid);\n    } else {\n        anjay_log(WARNING, _(\"reading \") \"%s\" _(\" failed\"),\n                  ANJAY_DEBUG_MAKE_PATH(&server_sni));\n        err = avs_errno(AVS_EPROTO);\n    }\n\n    return err;\n}\n#endif // ANJAY_WITH_LWM2M11\n"
  },
  {
    "path": "src/core/servers/anjay_connections.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ANJAY_SERVERS_CONNECTIONS_H\n#define ANJAY_SERVERS_CONNECTIONS_H\n\n#include \"../anjay_core.h\"\n#include \"../anjay_utils_private.h\"\n\n#include <avsystem/commons/avs_url.h>\n\n#include <avsystem/coap/ctx.h>\n\n#if !defined(ANJAY_SERVERS_INTERNALS) && !defined(ANJAY_TEST)\n#    error \"Headers from servers/ are not meant to be included from outside\"\n#endif\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\ntypedef struct {\n#ifndef ANJAY_WITHOUT_IP_STICKINESS\n    avs_net_resolved_endpoint_t preferred_endpoint;\n#endif // ANJAY_WITHOUT_IP_STICKINESS\n    char dtls_session_buffer[ANJAY_DTLS_SESSION_BUFFER_SIZE];\n    char last_local_port[ANJAY_MAX_URL_PORT_SIZE];\n} anjay_server_connection_nontransient_state_t;\n\ntypedef enum {\n    /**\n     * Server connection object has just been created, and the connection has\n     * not yet reached a usable state.\n     */\n    ANJAY_SERVER_CONNECTION_INVALID,\n\n    /**\n     * If _anjay_server_on_refreshed() is called with server connection in this\n     * state, it means that the connection has just entered a usable state after\n     * completing the \"connect\" operation.\n     *\n     * As a consequence, it probably does not make sense to retry connecting if\n     * an error occurs.\n     */\n    ANJAY_SERVER_CONNECTION_FRESHLY_CONNECTED,\n\n    /**\n     * If _anjay_server_on_refreshed() is called with server connection in this\n     * state, it means that it is not the first time it is called for that\n     * connection since it entered a usable state.\n     *\n     * As a consequence, it might make sense to retry connecting if an error\n     * occurs and the connection is stateful.\n     */\n    ANJAY_SERVER_CONNECTION_STABLE,\n\n    /**\n     * Connection is offline. Possible causes include:\n     * - failure to read connection configuration from the data model\n     * - error when creating the socket\n     * - error during the \"connect\" operation\n     * - none of the supported transports is available\n     */\n    ANJAY_SERVER_CONNECTION_OFFLINE\n} anjay_server_connection_state_t;\n\n/**\n * State of a specific connection to an LwM2M server. One server entry may have\n * up to 2 connections, if the SMS trigger feature is used.\n */\ntypedef struct {\n    /**\n     * Cached URI of the given connection - this is exactly the value returned\n     * by _anjay_connection_uri().\n     */\n    anjay_url_t uri;\n\n    /**\n     * CoAP transport layer type (UDP/TCP/SMS etc.). Initialized during socket\n     * refresh, Used to select an appropriate connection_def and CoAP context\n     * type.\n     *\n     * NOTE: At creation time, it is initialized to a special value of\n     * ANJAY_SOCKET_TRANSPORT_INVALID to avoid unconditionally treating newly\n     * created connections as UDP.\n     */\n    anjay_socket_transport_t transport;\n\n    /**\n     * Socket used for communication with the given server. Aside from being\n     * used for actual communication, the value of this field is also used as\n     * kind of a three-state flag:\n     *\n     * - When it is NULL - it means either of the three:\n     *   - the server is either inactive (see docs to anjay_server_info_t for\n     *     details)\n     *   - initial attempt to connect the socket failed - the server may still\n     *     be active if some other transport could be connected -\n     *     _anjay_active_server_refresh() reschedules reload_servers_sched_job()\n     *     in such case\n     *   - the transport represented by this connection object is not used in\n     *     the current binding\n     *\n     * - The socket may exist, but be offline (closed), when:\n     *   - reconnection is scheduled, as part of the execution path of\n     *     anjay_transport_schedule_reconnect() - see that function's docs and\n     *     call graph for details\n     *   - when the queue mode for this connection is used, and\n     *     MAX_TRANSMIT_WAIT passed since last communication\n     *   - when Client- or Server-Initiated Bootstrap is in progress - all\n     *     non-Bootstrap sockets are disconnected in such a case.\n     *\n     *   Note that the server is still considered active if it has a created,\n     *   but disconnected socket. Such closed socket still retains some of its\n     *   previous state (including the remote endpoint's hostname and security\n     *   keys etc.) in avs_commons' internal structures. This is used by\n     *   _anjay_connection_internal_ensure_online() to reconnect the socket if\n     *   necessary.\n     *\n     *   We cannot rely on reading the connection information from the data\n     *   model instead, because it may be gone - for example when trying to\n     *   De-register from a server that has just been deleted by a Bootstrap\n     *   Server. At least that example was used in the docs prior to the June\n     *   2018 server subsystem docs rewrite, because currently we don't seem to\n     *   send Deregister messages in such a case anyway, so this might be a TODO\n     *   for investigating.\n     *\n     * - The socket may exist and be online (ready for communication) - this is\n     *   the normal, fully active state.\n     */\n    avs_net_socket_t *conn_socket_;\n#if defined(__GNUC__) && !defined(__CC_ARM) \\\n        && !(defined(ANJAY_SERVERS_CONNECTION_SOURCE) || defined(ANJAY_TEST))\n#    pragma GCC poison conn_socket_\n#endif\n\n    avs_coap_ctx_t *coap_ctx;\n\n    /**\n     * Token that changes to a new unique value every time the CoAP endpoint\n     * association (i.e., DTLS session or raw UDP socket) every time it has been\n     * established anew.\n     *\n     * It is used to determine whether reconnect operation re-used the previous\n     * association or created a new one.\n     */\n    anjay_conn_session_token_t session_token;\n\n    /**\n     * True if the \"connect\" operation on the socket involves some actual\n     * network traffic. Used to determine whether it is meaningful to attempt\n     * reconnection as an error recovery step.\n     */\n    bool stateful;\n\n    /**\n     * State of the socket connection.\n     */\n    anjay_server_connection_state_t state;\n\n    /**\n     * Flag that is set to true whenever the attempt to bring the socket up from\n     * any other state is made. It signals that any outstanding notifications\n     * shall be scheduled to send after the connection refresh is finished.\n     */\n    bool needs_observe_flush;\n\n    /**\n     * The part of active connection state that is intentionally NOT cleaned up\n     * when deactivating the server. It contains:\n     *\n     * - preferred_endpoint, i.e. the preference which server IP address to use\n     *   if multiple are returned during DNS resolution\n     * - DTLS session cache\n     * - Last bound local port\n     *\n     * These information will be used during the next reactivation to attempt\n     * recreating the socket in a state most similar possible to how it was\n     * before.\n     */\n    anjay_server_connection_nontransient_state_t nontransient_state;\n\n#ifndef ANJAY_WITHOUT_QUEUE_MODE_AUTOCLOSE\n    /**\n     * Handle to scheduled queue_mode_close_socket() scheduler job. Generally\n     * scheduled by _anjay_connection_schedule_queue_mode_close(), although it\n     * can also be rescheduled by itself (queue_mode_close_socket() function) to\n     * defer the action if CoAP exchanges are in progress.\n     */\n    avs_sched_handle_t queue_mode_close_socket_clb;\n#endif // ANJAY_WITHOUT_QUEUE_MODE_AUTOCLOSE\n} anjay_server_connection_t;\n\ntypedef struct {\n    /**\n     * Connection (socket, binding) entries - see docs to\n     * anjay_server_connection_t for details.\n     */\n    anjay_server_connection_t connections_[ANJAY_CONNECTION_LIMIT_];\n} anjay_connections_t;\n\nstatic inline anjay_server_connection_t *\n_anjay_connection_get(anjay_connections_t *connections,\n                      anjay_connection_type_t conn_type) {\n    assert(conn_type >= (anjay_connection_type_t) 0\n           && conn_type < ANJAY_CONNECTION_LIMIT_);\n    return &connections->connections_[conn_type];\n}\n\n#if defined(__GNUC__) && !defined(__CC_ARM)\n#    pragma GCC poison connections_\n#endif\n\navs_net_socket_t *_anjay_connection_internal_get_socket(\n        const anjay_server_connection_t *connection);\n\nvoid _anjay_connection_internal_clean_socket(\n        anjay_unlocked_t *anjay, anjay_server_connection_t *connection);\n\nanjay_conn_session_token_t\n_anjay_connections_get_primary_session_token(anjay_connections_t *connections);\n\nvoid _anjay_connection_internal_invalidate_session(\n        anjay_server_connection_t *connection);\n\nstatic inline bool\n_anjay_connection_is_online(anjay_server_connection_t *connection) {\n    return _anjay_socket_is_online(\n            _anjay_connection_internal_get_socket(connection));\n}\n\nint _anjay_connection_ensure_coap_context(anjay_server_info_t *server,\n                                          anjay_connection_type_t conn_type);\n\navs_error_t _anjay_server_connection_internal_bring_online(\n        anjay_server_info_t *server, anjay_connection_type_t conn_type);\n\nvoid _anjay_connections_close(anjay_unlocked_t *anjay,\n                              anjay_connections_t *connections);\n\n/**\n * Makes sure that socket connections for a given server are up-to-date with the\n * current configuration; (re)connects any sockets and schedules Register/Update\n * operations as necessary.\n *\n * Any errors are reported by calling _anjay_connections_on_refreshed().\n *\n * @param server            Server information object to operate on.\n *\n * @param security_iid      Security Object Instance ID related to the server\n *                          being refreshed.\n *\n * @param move_uri          Pointer to a server URL to connect to. This function\n *                          will take ownership of data allocated inside that\n *                          object.\n */\nvoid _anjay_server_connections_refresh(anjay_server_info_t *server,\n                                       anjay_iid_t security_iid,\n                                       avs_url_t **move_uri);\n\n#ifdef ANJAY_WITH_LWM2M11\nvoid _anjay_server_update_last_ssl_alert_code(const anjay_server_info_t *info,\n                                              uint8_t level,\n                                              uint8_t description);\n\navs_error_t _anjay_server_read_sni(anjay_unlocked_t *anjay,\n                                   anjay_iid_t security_iid,\n                                   anjay_security_config_t *security,\n                                   anjay_security_config_cache_t *cache);\n#endif // ANJAY_WITH_LWM2M11\n\nbool _anjay_socket_transport_supported(anjay_unlocked_t *anjay,\n                                       anjay_socket_transport_t type);\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif // ANJAY_SERVERS_CONNECTIONS_H\n"
  },
  {
    "path": "src/core/servers/anjay_connections_internal.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ANJAY_SERVERS_CONNECTIONS_INTERNAL_H\n#define ANJAY_SERVERS_CONNECTIONS_INTERNAL_H\n\n#include \"../anjay_servers_private.h\"\n\n#include \"anjay_connections.h\"\n#include \"anjay_security.h\"\n\n#if !defined(ANJAY_TEST)                      \\\n        && (!defined(ANJAY_SERVERS_INTERNALS) \\\n            || !defined(ANJAY_SERVERS_CONNECTION_SOURCE))\n#    error \"connections_internal.h shall only be included from connection_*.c or security_*.c files\"\n#endif\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\nstatic inline void\n_anjay_connection_info_cleanup(anjay_connection_info_t *info) {\n    avs_url_free(info->uri);\n    info->uri = NULL;\n    info->transport_info = NULL;\n}\n\ntypedef const avs_net_dtls_handshake_timeouts_t *\nanjay_connection_get_dtls_handshake_timeouts_t(anjay_unlocked_t *anjay);\n\ntypedef avs_error_t anjay_connection_prepare_t(\n        anjay_unlocked_t *anjay,\n        anjay_server_connection_t *out_connection,\n        const avs_net_ssl_configuration_t *socket_config,\n        const avs_net_socket_dane_tlsa_record_t *dane_tlsa_record,\n        const anjay_connection_info_t *info);\n\ntypedef avs_error_t\nanjay_connection_connect_socket_t(anjay_unlocked_t *anjay,\n                                  anjay_server_connection_t *connection);\n\ntypedef int anjay_connection_ensure_coap_context_t(anjay_connection_ref_t ref);\n\ntypedef struct {\n    const char *name;\n    anjay_connection_get_dtls_handshake_timeouts_t *get_dtls_handshake_timeouts;\n    anjay_connection_prepare_t *prepare_connection;\n    anjay_connection_ensure_coap_context_t *ensure_coap_context;\n    anjay_connection_connect_socket_t *connect_socket;\n} anjay_connection_type_definition_t;\n\n#ifdef WITH_AVS_COAP_UDP\nextern const anjay_connection_type_definition_t ANJAY_CONNECTION_DEF_UDP;\n#endif // WITH_AVS_COAP_UDP\n#if defined(ANJAY_WITH_LWM2M11) && defined(WITH_AVS_COAP_TCP)\nextern const anjay_connection_type_definition_t ANJAY_CONNECTION_DEF_TCP;\n#endif // defined(ANJAY_WITH_LWM2M11) && defined(WITH_AVS_COAP_TCP)\n\navs_error_t\n_anjay_dm_read_security_info(anjay_unlocked_t *anjay,\n                             anjay_iid_t security_iid,\n                             anjay_rid_t security_rid,\n                             avs_crypto_security_info_tag_t tag,\n                             avs_crypto_security_info_union_t **out_array,\n                             size_t *out_element_count);\n\navs_error_t\n_anjay_connection_init_psk_security(anjay_unlocked_t *anjay,\n                                    anjay_iid_t security_iid,\n                                    anjay_rid_t identity_rid,\n                                    anjay_rid_t secret_key_rid,\n                                    anjay_security_config_t *security,\n                                    anjay_security_config_cache_t *cache);\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif /* ANJAY_SERVERS_CONNECTIONS_INTERNAL_H */\n"
  },
  {
    "path": "src/core/servers/anjay_register.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#include <inttypes.h>\n\n#include <avsystem/commons/avs_errno.h>\n\n#include <avsystem/coap/async_client.h>\n#include <avsystem/coap/code.h>\n\n#include <anjay_modules/anjay_time_defs.h>\n\n#define ANJAY_SERVERS_INTERNALS\n\n#include \"../anjay_core.h\"\n#include \"../anjay_servers_inactive.h\"\n#include \"../anjay_servers_private.h\"\n#include \"../anjay_servers_reload.h\"\n#include \"../anjay_servers_utils.h\"\n#include \"../dm/anjay_query.h\"\n#include \"../io/anjay_corelnk.h\"\n\n#include \"anjay_activate.h\"\n#include \"anjay_register.h\"\n#include \"anjay_server_connections.h\"\n#include \"anjay_servers_internal.h\"\n\n// !defined(ANJAY_WITH_CONN_STATUS_API)\n\nVISIBILITY_SOURCE_BEGIN\n\n/** Update messages are sent to the server every\n * LIFETIME/ANJAY_UPDATE_INTERVAL_FACTOR seconds. */\n#define ANJAY_UPDATE_INTERVAL_MARGIN_FACTOR 2\n\n/** To avoid flooding the network in case of a very small lifetime, Update\n * messages are not sent more often than every ANJAY_MIN_UPDATE_INTERVAL_S\n * seconds. */\n#define ANJAY_MIN_UPDATE_INTERVAL_S 1\n\nstatic int schedule_register_for_server(anjay_server_info_t *server) {\n    int result = 0;\n    if (_anjay_server_active(server)) {\n        result = _anjay_schedule_refresh_server(server, AVS_TIME_DURATION_ZERO);\n    }\n    if (!result) {\n        anjay_server_connection_t *connection =\n                _anjay_connection_get(&server->connections,\n                                      ANJAY_CONNECTION_PRIMARY);\n        _anjay_conn_session_token_reset(&connection->session_token,\n                                        &server->anjay->session_token_counter);\n    }\n    return result;\n}\n\nstatic int schedule_register_for_all_servers(anjay_unlocked_t *anjay) {\n    int result = 0;\n\n    AVS_LIST(anjay_server_info_t) it;\n    AVS_LIST_FOREACH(it, anjay->servers) {\n        _anjay_update_ret(&result, schedule_register_for_server(it));\n    }\n\n    return result;\n}\n\nint anjay_schedule_register(anjay_t *anjay_locked, anjay_ssid_t ssid) {\n    int result = -1;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    if (ssid == ANJAY_SSID_ANY) {\n        result = schedule_register_for_all_servers(anjay);\n    } else {\n        AVS_LIST(anjay_server_info_t) *server_ptr =\n                _anjay_servers_find_ptr(&anjay->servers, ssid);\n        if (!server_ptr || !*server_ptr) {\n            anjay_log(WARNING, _(\"no known server with SSID = \") \"%u\", ssid);\n            result = -1;\n        } else {\n            result = schedule_register_for_server(*server_ptr);\n        }\n    }\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return result;\n}\n\nstatic avs_time_real_t\ncalculate_time_of_next_update(anjay_server_info_t *server) {\n    avs_time_real_t expire_time =\n            _anjay_registration_expire_time_with_status(server, NULL);\n    if (!avs_time_real_valid(expire_time)) {\n        return AVS_TIME_REAL_INVALID;\n    }\n    avs_time_duration_t lifetime = avs_time_duration_from_scalar(\n            server->registration_info.last_update_params.lifetime_s,\n            AVS_TIME_S);\n    avs_time_duration_t half_lifetime =\n            avs_time_duration_div(lifetime,\n                                  ANJAY_UPDATE_INTERVAL_MARGIN_FACTOR);\n    anjay_server_connection_t *connection =\n            _anjay_connection_get(&server->connections,\n                                  ANJAY_CONNECTION_PRIMARY);\n    avs_time_duration_t max_transmit_wait =\n            _anjay_max_transmit_wait_for_transport(server->anjay,\n                                                   connection->transport);\n    avs_time_duration_t interval_margin =\n            avs_time_duration_less(half_lifetime, max_transmit_wait)\n                    ? half_lifetime\n                    : max_transmit_wait;\n    return avs_time_real_add(expire_time,\n                             avs_time_duration_mul(interval_margin, -1));\n}\n\nstatic avs_time_real_t get_time_of_next_update(anjay_server_info_t *server) {\n    if (server->next_action_handle\n            && (server->next_action == ANJAY_SERVER_NEXT_ACTION_SEND_UPDATE\n                || (server->next_action == ANJAY_SERVER_NEXT_ACTION_REFRESH\n                    && server->registration_info.update_forced))) {\n        // Update is scheduled - just return the time of that job\n        avs_time_real_t real_now = avs_time_real_now();\n        avs_time_monotonic_t monotonic_now = avs_time_monotonic_now();\n        return avs_time_real_add(\n                real_now, avs_time_monotonic_diff(\n                                  avs_sched_time(&server->next_action_handle),\n                                  monotonic_now));\n    }\n    // We don't have Update scheduled, so let's calculate it from scratch\n    return calculate_time_of_next_update(server);\n}\n\nstatic int schedule_next_update(anjay_server_info_t *server) {\n    if (server->registration_info.last_update_params.lifetime_s == 0\n            || !_anjay_server_active(server)) {\n        // Skip scheduling Update if the server is in the process of being\n        // disabled or when lifetime is set to infinity (lifetime==0).\n        return 0;\n    }\n    avs_time_real_t update_time = calculate_time_of_next_update(server);\n    avs_time_duration_t min_margin =\n            avs_time_duration_from_scalar(ANJAY_MIN_UPDATE_INTERVAL_S,\n                                          AVS_TIME_S);\n    avs_time_duration_t delay =\n            avs_time_real_diff(update_time, avs_time_real_now());\n    if (avs_time_duration_less(delay, min_margin)) {\n        delay = min_margin;\n    }\n\n    anjay_log(DEBUG, _(\"scheduling update for SSID \") \"%u\" _(\" after \") \"%s\",\n              server->ssid, AVS_TIME_DURATION_AS_STRING(delay));\n\n    return _anjay_server_reschedule_next_action(\n            server, delay, ANJAY_SERVER_NEXT_ACTION_SEND_UPDATE);\n}\n\nint _anjay_server_reschedule_update_job(anjay_server_info_t *server) {\n    if (schedule_next_update(server)) {\n        anjay_log(ERROR, _(\"could not schedule next Update for server \") \"%u\",\n                  server->ssid);\n        return -1;\n    }\n    return 0;\n}\n\nstatic int reschedule_update_for_server(anjay_server_info_t *server) {\n    int result = _anjay_schedule_refresh_server(server, AVS_TIME_DURATION_ZERO);\n    if (!result) {\n        // Make sure that Update is actually sent during the refresh.\n        server->registration_info.update_forced = true;\n    }\n    return result;\n}\n\nstatic int reschedule_update_for_all_servers(anjay_unlocked_t *anjay) {\n    int result = 0;\n\n    AVS_LIST(anjay_server_info_t) it;\n    AVS_LIST_FOREACH(it, anjay->servers) {\n        if (it->ssid == ANJAY_SSID_BOOTSTRAP) {\n            continue;\n        }\n\n        if (_anjay_server_active(it)) {\n            int partial = reschedule_update_for_server(it);\n            if (!result) {\n                result = partial;\n            }\n        }\n    }\n\n    return result;\n}\n\nint _anjay_schedule_registration_update_unlocked(anjay_unlocked_t *anjay,\n                                                 anjay_ssid_t ssid) {\n    if (ssid == ANJAY_SSID_BOOTSTRAP) {\n        return 0;\n    }\n\n    int result = 0;\n    if (ssid == ANJAY_SSID_ANY) {\n        result = reschedule_update_for_all_servers(anjay);\n    } else {\n        anjay_server_info_t *server = _anjay_servers_find_active(anjay, ssid);\n        if (!server) {\n            anjay_log(WARNING, _(\"no active server with SSID = \") \"%u\", ssid);\n            result = -1;\n        } else {\n            result = reschedule_update_for_server(server);\n        }\n    }\n\n    return result;\n}\n\nint anjay_schedule_registration_update(anjay_t *anjay_locked,\n                                       anjay_ssid_t ssid) {\n    int result = -1;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    result = _anjay_schedule_registration_update_unlocked(anjay, ssid);\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return result;\n}\n\nstatic int dm_payload_writer(size_t payload_offset,\n                             void *payload_buf,\n                             size_t payload_buf_size,\n                             size_t *out_payload_chunk_size,\n                             void *state_) {\n    anjay_registration_async_exchange_state_t *state =\n            (anjay_registration_async_exchange_state_t *) state_;\n    size_t length = state->new_params.dm ? strlen(state->new_params.dm) : 0;\n    assert(payload_offset <= length);\n    if ((*out_payload_chunk_size =\n                 AVS_MIN(length - payload_offset, payload_buf_size))) {\n        memcpy(payload_buf, &state->new_params.dm[payload_offset],\n               *out_payload_chunk_size);\n    }\n    return 0;\n}\n\nstatic int get_server_lifetime(anjay_unlocked_t *anjay,\n                               anjay_ssid_t ssid,\n                               int64_t *out_lifetime) {\n    anjay_iid_t server_iid;\n    if (_anjay_find_server_iid(anjay, ssid, &server_iid)) {\n        return -1;\n    }\n\n    const anjay_uri_path_t path =\n            MAKE_RESOURCE_PATH(ANJAY_DM_OID_SERVER, server_iid,\n                               ANJAY_DM_RID_SERVER_LIFETIME);\n    int64_t lifetime;\n    int read_ret = _anjay_dm_read_resource_i64(anjay, &path, &lifetime);\n\n    if (read_ret) {\n        anjay_log(ERROR, _(\"could not read lifetime for LwM2M server \") \"%u\",\n                  ssid);\n        return -1;\n    } else if (lifetime < 0) {\n        anjay_log(ERROR,\n                  _(\"lifetime returned by LwM2M server \") \"%u\" _(\" is < 0\"),\n                  ssid);\n        return -1;\n    }\n    *out_lifetime = lifetime;\n\n    return 0;\n}\n\nstatic void update_parameters_cleanup(anjay_update_parameters_t *params) {\n    avs_free(params->dm);\n    params->dm = NULL;\n}\n\nstatic void\nget_binding_mode_for_version(anjay_server_info_t *server,\n                             anjay_lwm2m_version_t lwm2m_version,\n                             anjay_binding_mode_t *out_binding_mode) {\n    const anjay_binding_mode_t *server_binding_mode =\n            _anjay_server_binding_mode(server);\n    size_t out_ptr = 0;\n    for (size_t in_ptr = 0; in_ptr < sizeof(*server_binding_mode) - 1\n                            && server_binding_mode->data[in_ptr];\n         ++in_ptr) {\n#ifdef ANJAY_WITH_LWM2M11\n        if (lwm2m_version >= ANJAY_LWM2M_VERSION_1_1\n                && server_binding_mode->data[in_ptr] == 'Q') {\n            continue;\n        }\n#else  // ANJAY_WITH_LWM2M11\n        (void) lwm2m_version;\n#endif // ANJAY_WITH_LWM2M11\n        out_binding_mode->data[out_ptr++] = server_binding_mode->data[in_ptr];\n    }\n}\n\nstatic avs_error_t\nupdate_parameters_init(anjay_server_info_t *server,\n                       anjay_lwm2m_version_t lwm2m_version,\n                       anjay_update_parameters_t *out_params) {\n    avs_error_t err = avs_errno(AVS_UNKNOWN_ERROR);\n    memset(out_params, 0, sizeof(*out_params));\n    if (!_anjay_server_connection_active((anjay_connection_ref_t) {\n            .server = server,\n            .conn_type = ANJAY_CONNECTION_PRIMARY\n        })) {\n        anjay_log(ERROR,\n                  _(\"No valid connection to Registration Interface for \"\n                    \"SSID = \") \"%u\",\n                  server->ssid);\n        err = avs_errno(AVS_EBADF);\n        goto error;\n    }\n    if (_anjay_corelnk_query_dm(server->anjay, &server->anjay->dm,\n                                lwm2m_version, &out_params->dm)) {\n        goto error;\n    }\n    if (get_server_lifetime(server->anjay, _anjay_server_ssid(server),\n                            &out_params->lifetime_s)) {\n        goto error;\n    }\n    get_binding_mode_for_version(server, lwm2m_version,\n                                 &out_params->binding_mode);\n    return AVS_OK;\nerror:\n    update_parameters_cleanup(out_params);\n    return err;\n}\n\nvoid _anjay_registration_info_cleanup(anjay_registration_info_t *info) {\n    AVS_LIST_CLEAR(&info->endpoint_path);\n    update_parameters_cleanup(&info->last_update_params);\n}\n\nvoid _anjay_registration_exchange_state_cleanup(\n        anjay_registration_async_exchange_state_t *state) {\n    avs_coap_ctx_t *coap = _anjay_connection_get_coap((anjay_connection_ref_t) {\n        .server = AVS_CONTAINER_OF(state, anjay_server_info_t,\n                                   registration_exchange_state),\n        .conn_type = ANJAY_CONNECTION_PRIMARY\n    });\n    if (coap && avs_coap_exchange_id_valid(state->exchange_id)) {\n        avs_coap_exchange_cancel(coap, state->exchange_id);\n        assert(!avs_coap_exchange_id_valid(state->exchange_id));\n    }\n    update_parameters_cleanup(&state->new_params);\n}\n\nstatic bool should_use_queue_mode(anjay_server_info_t *server,\n                                  anjay_lwm2m_version_t lwm2m_version) {\n#ifdef ANJAY_WITH_LWM2M11\n    switch (server->anjay->queue_mode_preference) {\n    case ANJAY_FORCE_QUEUE_MODE:\n        return true;\n    case ANJAY_PREFER_QUEUE_MODE:\n        if (lwm2m_version >= ANJAY_LWM2M_VERSION_1_1) {\n            return true;\n        }\n        // fall-through\n    case ANJAY_PREFER_ONLINE_MODE:\n#else  // ANJAY_WITH_LWM2M11\n    (void) server;\n    (void) lwm2m_version;\n#endif // ANJAY_WITH_LWM2M11\n        return !!strchr(_anjay_server_binding_mode(server)->data, 'Q');\n#ifdef ANJAY_WITH_LWM2M11\n    case ANJAY_FORCE_ONLINE_MODE:\n        return false;\n    }\n\n    AVS_UNREACHABLE(\"Invalid anjay_queue_mode_preference_t value\");\n    return false;\n#endif // ANJAY_WITH_LWM2M11\n}\n\n#ifdef ANJAY_WITH_LWM2M11\nstatic bool lwm2m11_queue_mode_changed(anjay_server_info_t *server) {\n    if (server->registration_info.lwm2m_version < ANJAY_LWM2M_VERSION_1_1) {\n        return false;\n    }\n    bool is_currently_using_queue_mode =\n            avs_coap_exchange_id_valid(\n                    server->registration_exchange_state.exchange_id)\n                    ? server->registration_exchange_state.lwm2m11_queue_mode\n                    : server->registration_info.queue_mode;\n    bool should_use_queue_mode_now =\n            should_use_queue_mode(server,\n                                  server->registration_info.lwm2m_version);\n    if (is_currently_using_queue_mode != should_use_queue_mode_now) {\n        anjay_log(DEBUG,\n                  _(\"State of 1.1-style queue mode changed for SSID = \") \"%u\" _(\n                          \", forcing re-register\"),\n                  server->ssid);\n        return true;\n    }\n    return false;\n}\n#endif // ANJAY_WITH_LWM2M11\n\nstatic int get_endpoint_path(AVS_LIST(const anjay_string_t) *out_path,\n                             const avs_coap_options_t *opts) {\n    assert(*out_path == NULL);\n\n    int result;\n    char buffer[ANJAY_MAX_URI_SEGMENT_SIZE];\n    size_t attr_size;\n\n    avs_coap_option_iterator_t it = AVS_COAP_OPTION_ITERATOR_EMPTY;\n    while ((result = avs_coap_options_get_string_it(\n                    opts, AVS_COAP_OPTION_LOCATION_PATH, &it, &attr_size,\n                    buffer, sizeof(buffer) - 1))\n           == 0) {\n        buffer[attr_size] = '\\0';\n\n        AVS_LIST(anjay_string_t) segment =\n                (AVS_LIST(anjay_string_t)) AVS_LIST_NEW_BUFFER(attr_size + 1);\n        if (!segment) {\n            _anjay_log_oom();\n            goto fail;\n        }\n\n        memcpy(segment, buffer, attr_size + 1);\n        AVS_LIST_APPEND(out_path, segment);\n    }\n\n    if (result == AVS_COAP_OPTION_MISSING) {\n        return 0;\n    }\n\nfail:\n    AVS_LIST_CLEAR(out_path);\n    return result;\n}\n\nstatic const char *assemble_endpoint_path(char *buffer,\n                                          size_t buffer_size,\n                                          AVS_LIST(const anjay_string_t) path) {\n    size_t off = 0;\n    AVS_LIST(const anjay_string_t) segment;\n    AVS_LIST_FOREACH(segment, path) {\n        int result = avs_simple_snprintf(buffer + off, buffer_size - off, \"/%s\",\n                                         segment->c_str);\n        if (result < 0) {\n            return \"<ERROR>\";\n        }\n        off += (size_t) result;\n    }\n\n    return buffer;\n}\n\nstatic anjay_registration_result_t map_coap_error(avs_error_t coap_err) {\n    assert(avs_is_err(coap_err));\n    if (coap_err.category == AVS_COAP_ERR_CATEGORY\n            && coap_err.code == AVS_COAP_ERR_TIMEOUT) {\n        return ANJAY_REGISTRATION_ERROR_TIMEOUT;\n    } else {\n        anjay_log(DEBUG, _(\"mapping CoAP error (\") \"%s\" _(\") to network error\"),\n                  AVS_COAP_STRERROR(coap_err));\n        return ANJAY_REGISTRATION_ERROR_NETWORK;\n    }\n}\n\nstatic avs_error_t\nsetup_register_request_options(avs_coap_options_t *opts,\n                               anjay_lwm2m_version_t lwm2m_version,\n                               const char *endpoint_name,\n                               const char *msisdn,\n                               const anjay_url_t *uri,\n                               bool lwm2m11_queue_mode,\n                               int64_t lifetime_s,\n                               const anjay_binding_mode_t *binding_mode) {\n    assert(opts->size == 0);\n\n    avs_error_t err;\n    if (avs_is_err((err = avs_coap_options_set_content_format(\n                            opts, AVS_COAP_FORMAT_LINK_FORMAT)))\n            || avs_is_err(\n                       (err = _anjay_coap_add_string_options(\n                                opts, uri->uri_path, AVS_COAP_OPTION_URI_PATH)))\n            || avs_is_err((err = avs_coap_options_add_string(\n                                   opts, AVS_COAP_OPTION_URI_PATH, \"rd\")))\n            || avs_is_err((err = _anjay_coap_add_string_options(\n                                   opts, uri->uri_query,\n                                   AVS_COAP_OPTION_URI_QUERY)))\n            || avs_is_err((err = _anjay_coap_add_query_options(\n                                   opts, &lwm2m_version, endpoint_name,\n                                   &lifetime_s,\n                                   strcmp(binding_mode->data, \"U\") == 0\n                                           ? NULL\n                                           : binding_mode->data,\n                                   lwm2m11_queue_mode, msisdn)))) {\n        anjay_log(ERROR, _(\"could not initialize request headers\"));\n    }\n    return err;\n}\n\nstatic anjay_registration_result_t\ncheck_register_response(const avs_coap_response_header_t *response,\n                        AVS_LIST(const anjay_string_t) *out_endpoint_path) {\n    if (response->code != AVS_COAP_CODE_CREATED) {\n        anjay_log(WARNING,\n                  _(\"server responded with \") \"%s\" _(\" (expected \") \"%s\" _(\")\"),\n                  AVS_COAP_CODE_STRING(response->code),\n                  AVS_COAP_CODE_STRING(AVS_COAP_CODE_CREATED));\n        assert(response->code != 0);\n        return response->code == AVS_COAP_CODE_PRECONDITION_FAILED\n                       ? ANJAY_REGISTRATION_ERROR_FALLBACK_REQUESTED\n                       : ANJAY_REGISTRATION_ERROR_REJECTED;\n    }\n\n    AVS_LIST_CLEAR(out_endpoint_path);\n    if (get_endpoint_path(out_endpoint_path, &response->options)) {\n        anjay_log(ERROR, _(\"could not store Update location\"));\n        return ANJAY_REGISTRATION_ERROR_OTHER;\n    }\n\n    char location_buf[256] = \"\";\n    anjay_log(INFO, _(\"registration successful, location = \") \"%s\",\n              assemble_endpoint_path(location_buf, sizeof(location_buf),\n                                     *out_endpoint_path));\n    return ANJAY_REGISTRATION_SUCCESS;\n}\n\nstatic void register_with_version(anjay_server_info_t *server,\n                                  anjay_lwm2m_version_t lwm2m_version,\n                                  anjay_update_parameters_t *move_params);\n\nstatic void\nhandle_register_response(anjay_server_info_t *server,\n                         anjay_lwm2m_version_t attempted_version,\n                         AVS_LIST(const anjay_string_t) *move_endpoint_path,\n                         anjay_update_parameters_t *move_params,\n                         anjay_registration_result_t result,\n                         avs_error_t err) {\n    if (avs_is_ok(err)) {\n        _anjay_connection_mark_stable((anjay_connection_ref_t) {\n            .server = server,\n            .conn_type = ANJAY_CONNECTION_PRIMARY\n        });\n    }\n    if (result != ANJAY_REGISTRATION_SUCCESS) {\n        anjay_log(WARNING, _(\"could not register to server \") \"%u\",\n                  _anjay_server_ssid(server));\n        AVS_LIST_CLEAR(move_endpoint_path);\n#ifdef ANJAY_WITH_CONN_STATUS_API\n        if (result != ANJAY_REGISTRATION_ERROR_TIMEOUT) {\n            _anjay_set_server_connection_status(\n                    server, ANJAY_SERV_CONN_STATUS_REG_FAILURE);\n        }\n#endif // ANJAY_WITH_CONN_STATUS_API\n    } else {\n        _anjay_server_update_registration_info(\n                server, move_endpoint_path, attempted_version,\n                should_use_queue_mode(server, attempted_version), move_params);\n        assert(!*move_endpoint_path);\n    }\n\n    if (result == ANJAY_REGISTRATION_ERROR_FALLBACK_REQUESTED) {\n#ifdef ANJAY_WITH_LWM2M11\n        if (attempted_version\n                > server->anjay->lwm2m_version_config.minimum_version) {\n            attempted_version = (anjay_lwm2m_version_t) (attempted_version - 1);\n            anjay_log(WARNING,\n                      _(\"attempting to fall back to LwM2M version \") \"%s\",\n                      _anjay_lwm2m_version_as_string(attempted_version));\n            // NOTE: update_parameters format may differ slightly between\n            // LwM2M versions, so we need to rebuild them\n            update_parameters_cleanup(move_params);\n            if (avs_is_err(update_parameters_init(server, attempted_version,\n                                                  move_params))) {\n                result = ANJAY_REGISTRATION_ERROR_OTHER;\n            } else {\n                register_with_version(server, attempted_version, move_params);\n            }\n        } else\n#endif // ANJAY_WITH_LWM2M11\n        {\n            result = ANJAY_REGISTRATION_ERROR_REJECTED;\n        }\n    }\n    if (result != ANJAY_REGISTRATION_ERROR_FALLBACK_REQUESTED) {\n        _anjay_server_on_updated_registration(server, result, err);\n    }\n}\n\nstatic void\nreceive_register_response(avs_coap_ctx_t *coap,\n                          avs_coap_exchange_id_t exchange_id,\n                          avs_coap_client_request_state_t request_state,\n                          const avs_coap_client_async_response_t *response,\n                          avs_error_t err,\n                          void *state_) {\n    anjay_registration_async_exchange_state_t *state =\n            (anjay_registration_async_exchange_state_t *) state_;\n    anjay_registration_result_t result = ANJAY_REGISTRATION_ERROR_OTHER;\n    AVS_LIST(const anjay_string_t) endpoint_path = NULL;\n    if (request_state != AVS_COAP_CLIENT_REQUEST_PARTIAL_CONTENT) {\n        state->exchange_id = AVS_COAP_EXCHANGE_ID_INVALID;\n    }\n\n    switch (request_state) {\n    case AVS_COAP_CLIENT_REQUEST_PARTIAL_CONTENT:\n        // Note: this will recursively call this function with\n        // AVS_COAP_CLIENT_REQUEST_CANCEL.\n        avs_coap_exchange_cancel(coap, exchange_id);\n        // fall-through\n\n    case AVS_COAP_CLIENT_REQUEST_OK:\n        result = check_register_response(&response->header, &endpoint_path);\n        break;\n\n    case AVS_COAP_CLIENT_REQUEST_FAIL: {\n        assert(avs_is_err(err));\n        anjay_log(WARNING,\n                  _(\"failure while receiving Register response: \") \"%s\",\n                  AVS_COAP_STRERROR(err));\n        result = map_coap_error(err);\n        break;\n    }\n\n    case AVS_COAP_CLIENT_REQUEST_CANCEL:\n        return;\n    }\n\n    handle_register_response(AVS_CONTAINER_OF(state, anjay_server_info_t,\n                                              registration_exchange_state),\n                             state->attempted_version, &endpoint_path,\n                             &state->new_params, result, err);\n    assert(!endpoint_path);\n}\n\nstatic void move_assign_update_params(anjay_update_parameters_t *out,\n                                      anjay_update_parameters_t *move_in) {\n    assert(out);\n    assert(move_in);\n    if (out != move_in) {\n        if (move_in->dm) {\n            avs_free(out->dm);\n            out->dm = move_in->dm;\n            move_in->dm = NULL;\n        }\n\n        out->lifetime_s = move_in->lifetime_s;\n        memcpy(&out->binding_mode, &move_in->binding_mode,\n               sizeof(out->binding_mode));\n    }\n}\n\nstatic void send_register(anjay_server_info_t *server,\n                          avs_coap_ctx_t *coap,\n                          anjay_lwm2m_version_t lwm2m_version,\n                          anjay_update_parameters_t *move_params) {\n    const anjay_url_t *const connection_uri =\n            _anjay_connection_uri((anjay_connection_ref_t) {\n                .server = server,\n                .conn_type = ANJAY_CONNECTION_PRIMARY\n            });\n\n    avs_coap_request_header_t request = {\n        .code = AVS_COAP_CODE_POST\n    };\n\n    get_binding_mode_for_version(server, lwm2m_version,\n                                 &move_params->binding_mode);\n\n#ifdef ANJAY_WITH_LWM2M11\n    bool queue_mode = should_use_queue_mode(server, lwm2m_version);\n    bool lwm2m11_queue_mode =\n            (queue_mode && lwm2m_version >= ANJAY_LWM2M_VERSION_1_1);\n#endif // ANJAY_WITH_LWM2M11\n\n    avs_error_t err;\n    if (avs_is_err((err = avs_coap_options_dynamic_init(&request.options)))\n            || avs_is_err((err = setup_register_request_options(\n                                   &request.options, lwm2m_version,\n                                   server->anjay->endpoint_name, NULL,\n                                   connection_uri,\n#ifdef ANJAY_WITH_LWM2M11\n                                   lwm2m11_queue_mode,\n#else  // ANJAY_WITH_LWM2M11\n                                   false,\n#endif // ANJAY_WITH_LWM2M11\n                                   move_params->lifetime_s,\n                                   &move_params->binding_mode)))) {\n        _anjay_server_on_updated_registration(\n                server, ANJAY_REGISTRATION_ERROR_OTHER, err);\n        goto cleanup;\n    }\n    ++server->registration_attempts;\n\n    anjay_log(DEBUG, _(\"sending Register\"));\n\n    if (avs_coap_exchange_id_valid(\n                server->registration_exchange_state.exchange_id)) {\n        avs_coap_exchange_cancel(\n                coap, server->registration_exchange_state.exchange_id);\n    }\n    assert(!avs_coap_exchange_id_valid(\n            server->registration_exchange_state.exchange_id));\n    server->registration_exchange_state.attempted_version = lwm2m_version;\n    move_assign_update_params(&server->registration_exchange_state.new_params,\n                              move_params);\n#ifdef ANJAY_WITH_LWM2M11\n    server->registration_exchange_state.lwm2m11_queue_mode = lwm2m11_queue_mode;\n#endif // ANJAY_WITH_LWM2M11\n    if (avs_is_err(\n                (err = avs_coap_client_send_async_request(\n                         coap, &server->registration_exchange_state.exchange_id,\n                         &request, dm_payload_writer,\n                         &server->registration_exchange_state,\n                         receive_register_response,\n                         &server->registration_exchange_state)))) {\n        anjay_log(ERROR, _(\"could not send Register: \") \"%s\",\n                  AVS_COAP_STRERROR(err));\n        _anjay_server_on_updated_registration(server, map_coap_error(err), err);\n    } else {\n        anjay_log(INFO, _(\"Register sent\"));\n        server->registration_info.update_forced = false;\n#ifdef ANJAY_WITH_COMMUNICATION_TIMESTAMP_API\n        _anjay_server_set_last_communication_time(server);\n#endif // ANJAY_WITH_COMMUNICATION_TIMESTAMP_API\n    }\n#ifdef ANJAY_WITH_CONN_STATUS_API\n    _anjay_check_server_connection_status(server);\n#endif // ANJAY_WITH_CONN_STATUS_API\ncleanup:\n    avs_coap_options_cleanup(&request.options);\n}\n\nstatic void register_with_version(anjay_server_info_t *server,\n                                  anjay_lwm2m_version_t lwm2m_version,\n                                  anjay_update_parameters_t *move_params) {\n    anjay_connection_ref_t connection = {\n        .server = server,\n        .conn_type = ANJAY_CONNECTION_PRIMARY\n    };\n    if (!_anjay_connection_get_online_socket(connection)) {\n        anjay_log(ERROR, _(\"server connection is not online\"));\n        _anjay_server_on_updated_registration(\n                server, ANJAY_REGISTRATION_ERROR_OTHER, avs_errno(AVS_EBADF));\n    } else {\n        send_register(server, _anjay_connection_get_coap(connection),\n                      lwm2m_version, move_params);\n        _anjay_connection_schedule_queue_mode_close((anjay_connection_ref_t) {\n            .server = server,\n            .conn_type = ANJAY_CONNECTION_PRIMARY\n        });\n    }\n}\n\nstatic void do_register(anjay_server_info_t *server,\n                        anjay_update_parameters_t *move_params) {\n    anjay_connection_type_t conn_type;\n    ANJAY_CONNECTION_TYPE_FOREACH(conn_type) {\n        _anjay_observe_invalidate((anjay_connection_ref_t) {\n            .server = server,\n            .conn_type = conn_type\n        });\n    }\n    anjay_lwm2m_version_t attempted_version =\n#ifdef ANJAY_WITH_LWM2M11\n            server->anjay->lwm2m_version_config.maximum_version;\n#else  // ANJAY_WITH_LWM2M11\n            ANJAY_LWM2M_VERSION_1_0;\n#endif // ANJAY_WITH_LWM2M11\n    anjay_log(INFO, _(\"Attempting to register with LwM2M version \") \"%s\",\n              _anjay_lwm2m_version_as_string(attempted_version));\n    register_with_version(server, attempted_version, move_params);\n}\n\nstatic inline bool dm_caches_equal(const char *left, const char *right) {\n    return strcmp(left ? left : \"\", right ? right : \"\") == 0;\n}\n\nstatic avs_error_t\nsetup_update_request_options(anjay_unlocked_t *anjay,\n                             avs_coap_options_t *opts,\n                             AVS_LIST(const anjay_string_t) endpoint_path,\n                             const anjay_update_parameters_t *old_params,\n                             const anjay_update_parameters_t *new_params,\n                             bool *out_dm_changed_since_last_update) {\n    (void) anjay;\n    assert(opts->size == 0);\n\n    const int64_t *lifetime_s_ptr = NULL;\n    assert(new_params->lifetime_s >= 0);\n    if (new_params->lifetime_s != old_params->lifetime_s) {\n        lifetime_s_ptr = &new_params->lifetime_s;\n    }\n\n    const char *binding_mode = (strcmp(old_params->binding_mode.data,\n                                       new_params->binding_mode.data)\n                                == 0)\n                                       ? NULL\n                                       : new_params->binding_mode.data;\n    const char *sms_msisdn = NULL;\n    *out_dm_changed_since_last_update =\n            !dm_caches_equal(old_params->dm, new_params->dm);\n\n    avs_error_t err;\n    (void) ((*out_dm_changed_since_last_update\n             && avs_is_err((err = avs_coap_options_set_content_format(\n                                    opts, AVS_COAP_FORMAT_LINK_FORMAT))))\n            || avs_is_err(\n                       (err = _anjay_coap_add_string_options(\n                                opts, endpoint_path, AVS_COAP_OPTION_URI_PATH)))\n            || avs_is_err((err = _anjay_coap_add_query_options(\n                                   /* opts = */ opts, /* version = */ NULL,\n                                   /* endpoint_name = */ NULL,\n                                   /* lifetime = */ lifetime_s_ptr,\n                                   /* binding_mode = */ binding_mode,\n                                   /* lwm2m11_queue_mode = */ false,\n                                   /* sms_msisdn = */ sms_msisdn))));\n\n    return err;\n}\n\nstatic anjay_registration_result_t\ncheck_update_response(const avs_coap_response_header_t *response) {\n    if (response->code == AVS_COAP_CODE_CHANGED) {\n        anjay_log(INFO, _(\"registration successfully updated\"));\n        return ANJAY_REGISTRATION_SUCCESS;\n    } else {\n        /* 4.xx (client error) response means that a server received a\n         * request it considers invalid, so retransmission of the same\n         * message will most likely fail again. That may happen if:\n         * - the registration already expired (4.04 Not Found response),\n         * - the server is unable to parse our Update request or unwilling\n         *   to process it,\n         * - the server is broken.\n         *\n         * In the first case, the correct response is to Register again.\n         * Otherwise, we might as well do the same, as server is required to\n         * replace client registration information in such case.\n         *\n         * Any other response is either an 5.xx (server error), in which\n         * case retransmission may succeed, or an unexpected non-error\n         * response. However, as we don't do retransmissions, degenerating\n         * to Register seems the best thing we can do. */\n        anjay_log(DEBUG,\n                  _(\"Update rejected: \") \"%s\" _(\" (expected \") \"%s\" _(\")\"),\n                  AVS_COAP_CODE_STRING(response->code),\n                  AVS_COAP_CODE_STRING(AVS_COAP_CODE_CHANGED));\n        assert(response->code != 0);\n        return ANJAY_REGISTRATION_ERROR_REJECTED;\n    }\n}\n\nstatic void\non_registration_update_result(anjay_server_info_t *server,\n                              anjay_update_parameters_t *move_params,\n                              anjay_registration_result_t result,\n                              avs_error_t err) {\n    switch (result) {\n    case ANJAY_REGISTRATION_ERROR_TIMEOUT:\n        anjay_log(WARNING,\n                  _(\"timeout while updating registration for SSID==\") \"%\" PRIu16\n                          _(\"; trying to re-register\"),\n                  server->ssid);\n        server->registration_info.expire_time = AVS_TIME_REAL_INVALID;\n        do_register(server, move_params);\n        break;\n\n    case ANJAY_REGISTRATION_ERROR_REJECTED:\n        anjay_log(DEBUG,\n                  _(\"update rejected for SSID = \") \"%u\" _(\n                          \"; needs re-registration\"),\n                  server->ssid);\n        server->registration_info.expire_time = AVS_TIME_REAL_INVALID;\n        do_register(server, move_params);\n        break;\n\n    case ANJAY_REGISTRATION_SUCCESS: {\n        const anjay_registration_info_t *old_info =\n                _anjay_server_registration_info(server);\n        _anjay_server_update_registration_info(\n                server, NULL, old_info->lwm2m_version,\n                should_use_queue_mode(server, old_info->lwm2m_version),\n                move_params);\n        update_parameters_cleanup(move_params);\n        _anjay_server_on_updated_registration(server, result, err);\n        break;\n    }\n\n    default:\n        anjay_log(\n                ERROR,\n                _(\"could not send registration update for SSID==\") \"%\" PRIu16 _(\n                        \": \") \"%d\",\n                server->ssid, (int) result);\n\n        update_parameters_cleanup(move_params);\n        _anjay_server_on_updated_registration(server, result, err);\n    }\n}\nstatic void\nreceive_update_response(avs_coap_ctx_t *coap,\n                        avs_coap_exchange_id_t exchange_id,\n                        avs_coap_client_request_state_t request_state,\n                        const avs_coap_client_async_response_t *response,\n                        avs_error_t err,\n                        void *state_) {\n    anjay_registration_async_exchange_state_t *state =\n            (anjay_registration_async_exchange_state_t *) state_;\n    anjay_server_info_t *server = AVS_CONTAINER_OF(state, anjay_server_info_t,\n                                                   registration_exchange_state);\n\n    anjay_registration_result_t result = ANJAY_REGISTRATION_ERROR_OTHER;\n    if (request_state != AVS_COAP_CLIENT_REQUEST_PARTIAL_CONTENT) {\n        state->exchange_id = AVS_COAP_EXCHANGE_ID_INVALID;\n    }\n\n    if (request_state != AVS_COAP_CLIENT_REQUEST_CANCEL && avs_is_ok(err)) {\n        _anjay_connection_mark_stable((anjay_connection_ref_t) {\n            .server = server,\n            .conn_type = ANJAY_CONNECTION_PRIMARY\n        });\n    }\n\n    switch (request_state) {\n    case AVS_COAP_CLIENT_REQUEST_PARTIAL_CONTENT:\n        // Note: this will recursively call this function with\n        // AVS_COAP_CLIENT_REQUEST_CANCEL.\n        avs_coap_exchange_cancel(coap, exchange_id);\n        server->registration_info.update_forced = false;\n        // fall-through\n\n    case AVS_COAP_CLIENT_REQUEST_OK:\n        result = check_update_response(&response->header);\n        break;\n\n    case AVS_COAP_CLIENT_REQUEST_FAIL: {\n        assert(avs_is_err(err));\n        anjay_log(WARNING, _(\"failure while receiving Update response: \") \"%s\",\n                  AVS_COAP_STRERROR(err));\n        result = map_coap_error(err);\n        break;\n    }\n\n    case AVS_COAP_CLIENT_REQUEST_CANCEL:\n        // Interrupted Update - make sure it is restarted after next refresh\n        server->registration_info.update_forced = true;\n        return;\n    }\n#ifdef ANJAY_WITH_CONN_STATUS_API\n    if (result == ANJAY_REGISTRATION_ERROR_TIMEOUT\n            || result == ANJAY_REGISTRATION_ERROR_REJECTED) {\n        server->reregistration = true;\n    }\n#endif // ANJAY_WITH_CONN_STATUS_API\n\n    on_registration_update_result(server, &state->new_params, result, err);\n}\n\nstatic void send_update(anjay_server_info_t *server,\n                        avs_coap_ctx_t *coap,\n                        anjay_update_parameters_t *move_params) {\n    const anjay_registration_info_t *old_info =\n            _anjay_server_registration_info(server);\n    avs_coap_request_header_t request = {\n        .code = AVS_COAP_CODE_POST\n    };\n    bool dm_changed_since_last_update;\n\n    avs_error_t err;\n    if (avs_is_err((err = avs_coap_options_dynamic_init(&request.options)))\n            || avs_is_err((err = setup_update_request_options(\n                                   server->anjay, &request.options,\n                                   old_info->endpoint_path,\n                                   &old_info->last_update_params, move_params,\n                                   &dm_changed_since_last_update)))) {\n        anjay_log(ERROR, _(\"could not setup update request\"));\n        on_registration_update_result(server, move_params,\n                                      ANJAY_REGISTRATION_ERROR_OTHER, err);\n        goto end;\n    }\n\n    anjay_log(DEBUG, _(\"sending Update\"));\n\n    if (avs_coap_exchange_id_valid(\n                server->registration_exchange_state.exchange_id)) {\n        avs_coap_exchange_cancel(\n                coap, server->registration_exchange_state.exchange_id);\n    }\n    assert(!avs_coap_exchange_id_valid(\n            server->registration_exchange_state.exchange_id));\n    server->registration_exchange_state.attempted_version =\n            old_info->lwm2m_version;\n    move_assign_update_params(&server->registration_exchange_state.new_params,\n                              move_params);\n#ifdef ANJAY_WITH_LWM2M11\n    server->registration_exchange_state.lwm2m11_queue_mode =\n            (old_info->queue_mode\n             && old_info->lwm2m_version >= ANJAY_LWM2M_VERSION_1_1);\n#endif // ANJAY_WITH_LWM2M11\n    if (avs_is_err((\n                err = avs_coap_client_send_async_request(\n                        coap, &server->registration_exchange_state.exchange_id,\n                        &request,\n                        dm_changed_since_last_update ? dm_payload_writer : NULL,\n                        &server->registration_exchange_state,\n                        receive_update_response,\n                        &server->registration_exchange_state)))) {\n        anjay_log(ERROR, _(\"could not send Update: \") \"%s\",\n                  AVS_COAP_STRERROR(err));\n        on_registration_update_result(server, move_params, map_coap_error(err),\n                                      err);\n    } else {\n        anjay_log(INFO, _(\"Update sent\"));\n        server->registration_info.update_forced = false;\n#ifdef ANJAY_WITH_COMMUNICATION_TIMESTAMP_API\n        _anjay_server_set_last_communication_time(server);\n#endif // ANJAY_WITH_COMMUNICATION_TIMESTAMP_API\n    }\n#ifdef ANJAY_WITH_CONN_STATUS_API\n    _anjay_check_server_connection_status(server);\n#endif // ANJAY_WITH_CONN_STATUS_API\nend:\n    avs_coap_options_cleanup(&request.options);\n}\n\nstatic bool params_require_registration_update(\n        const anjay_update_parameters_t *old_params,\n        const anjay_update_parameters_t *new_params) {\n    return old_params->lifetime_s != new_params->lifetime_s\n           || strcmp(old_params->binding_mode.data,\n                     new_params->binding_mode.data)\n           || !dm_caches_equal(old_params->dm, new_params->dm);\n}\n\nstatic void update_registration(anjay_server_info_t *server,\n                                anjay_update_parameters_t *move_params) {\n    anjay_connection_ref_t connection = {\n        .server = server,\n        .conn_type = ANJAY_CONNECTION_PRIMARY\n    };\n    if (!_anjay_connection_get_online_socket(connection)) {\n        anjay_log(ERROR, _(\"server connection is not online\"));\n        on_registration_update_result(server, move_params,\n                                      ANJAY_REGISTRATION_ERROR_OTHER,\n                                      avs_errno(AVS_EBADF));\n    } else {\n        send_update(server, _anjay_connection_get_coap(connection),\n                    move_params);\n        _anjay_connection_schedule_queue_mode_close((anjay_connection_ref_t) {\n            .server = server,\n            .conn_type = ANJAY_CONNECTION_PRIMARY\n        });\n    }\n}\n\nvoid _anjay_server_ensure_valid_registration(anjay_server_info_t *server) {\n    assert(server->ssid != ANJAY_SSID_BOOTSTRAP);\n    if (!_anjay_server_active(server)) {\n        // This may happen if the server is in the process of being disabled.\n        // Skip Register/Update in that case.\n        return;\n    }\n\n    anjay_update_parameters_t new_params;\n    avs_error_t err = update_parameters_init(\n            server, server->registration_info.lwm2m_version, &new_params);\n    if (avs_is_err(err)) {\n        on_registration_update_result(server, &new_params,\n                                      ANJAY_REGISTRATION_ERROR_OTHER, err);\n    } else {\n        bool registration_or_update_in_progress = avs_coap_exchange_id_valid(\n                server->registration_exchange_state.exchange_id);\n        bool registration_expired = _anjay_server_registration_expired(server);\n        bool needs_reregistration =\n                !registration_or_update_in_progress && registration_expired;\n#ifdef ANJAY_WITH_LWM2M11\n        if (!needs_reregistration && lwm2m11_queue_mode_changed(server)) {\n            needs_reregistration = true;\n        }\n#endif // ANJAY_WITH_LWM2M11\n        bool needs_update = false;\n        if (!needs_reregistration) {\n            const anjay_registration_info_t *info =\n                    _anjay_server_registration_info(server);\n            if (info->update_forced) {\n                needs_update = true;\n            } else {\n                const anjay_update_parameters_t *current_params;\n                if (registration_or_update_in_progress) {\n                    current_params =\n                            &server->registration_exchange_state.new_params;\n                } else {\n                    current_params = &info->last_update_params;\n                }\n                needs_update =\n                        params_require_registration_update(current_params,\n                                                           &new_params);\n            }\n        }\n        if (needs_reregistration\n                || (registration_or_update_in_progress && registration_expired\n                    && needs_update)) {\n            on_registration_update_result(server, &new_params,\n                                          ANJAY_REGISTRATION_ERROR_REJECTED,\n                                          avs_errno(AVS_UNKNOWN_ERROR));\n        } else if (!needs_update) {\n            update_parameters_cleanup(&new_params);\n            if (!registration_or_update_in_progress) {\n                _anjay_server_on_updated_registration(\n                        server, ANJAY_REGISTRATION_SUCCESS, AVS_OK);\n#ifndef ANJAY_WITHOUT_QUEUE_MODE_AUTOCLOSE\n                anjay_connection_ref_t ref = {\n                    .server = server,\n                    .conn_type = ANJAY_CONNECTION_PRIMARY\n                };\n                anjay_server_connection_t *connection =\n                        _anjay_get_server_connection(ref);\n                if (!connection->queue_mode_close_socket_clb) {\n                    _anjay_connection_schedule_queue_mode_close(ref);\n                }\n#endif // ANJAY_WITHOUT_QUEUE_MODE_AUTOCLOSE\n            }\n        } else {\n            update_registration(server, &new_params);\n        }\n    }\n}\n\n#ifndef ANJAY_WITHOUT_DEREGISTER\nstatic avs_error_t\nsetup_deregister_request(avs_coap_request_header_t *out_request,\n                         AVS_LIST(const anjay_string_t) endpoint_path) {\n    *out_request = (avs_coap_request_header_t) {\n        .code = AVS_COAP_CODE_DELETE\n    };\n\n    avs_error_t err;\n    if (avs_is_err((err = avs_coap_options_dynamic_init(&out_request->options)))\n            || avs_is_err((err = _anjay_coap_add_string_options(\n                                   &out_request->options,\n                                   endpoint_path,\n                                   AVS_COAP_OPTION_URI_PATH)))) {\n        anjay_log(ERROR, _(\"could not initialize request headers\"));\n    }\n    return err;\n}\n\nstatic avs_error_t deregister(anjay_server_info_t *server) {\n    // server is supposed to be bound at this point\n    avs_coap_ctx_t *coap = _anjay_connection_get_coap((anjay_connection_ref_t) {\n        .server = server,\n        .conn_type = ANJAY_CONNECTION_PRIMARY\n    });\n    AVS_ASSERT(coap, \"Register is not supposed to be called on a connection \"\n                     \"that has no CoAP context\");\n\n#    ifdef ANJAY_WITH_CONN_STATUS_API\n    _anjay_set_server_connection_status(server,\n                                        ANJAY_SERV_CONN_STATUS_DEREGISTERING);\n#    endif // ANJAY_WITH_CONN_STATUS_API\n\n    avs_coap_request_header_t request = { 0 };\n    avs_coap_response_header_t response = { 0 };\n    avs_error_t err =\n            setup_deregister_request(&request,\n                                     server->registration_info.endpoint_path);\n    if (avs_is_err(err)) {\n        goto end;\n    }\n\n    if (avs_is_err((err = avs_coap_streaming_send_request(\n                            coap, &request, NULL, NULL, &response, NULL)))) {\n        anjay_log(ERROR, _(\"Could not perform De-registration\"));\n        goto end;\n    }\n\n    if (response.code != AVS_COAP_CODE_DELETED) {\n        anjay_log(WARNING,\n                  _(\"server responded with \") \"%s\" _(\" (expected \") \"%s\" _(\")\"),\n                  AVS_COAP_CODE_STRING(response.code),\n                  AVS_COAP_CODE_STRING(AVS_COAP_CODE_DELETED));\n        err = avs_errno(AVS_EPROTO);\n        goto end;\n    }\n#    ifdef ANJAY_WITH_CONN_STATUS_API\n    _anjay_set_server_connection_status(server,\n                                        ANJAY_SERV_CONN_STATUS_DEREGISTERED);\n#    endif // ANJAY_WITH_CONN_STATUS_API\n\n    anjay_log(INFO, _(\"De-register sent\"));\n    err = AVS_OK;\n#    ifdef ANJAY_WITH_COMMUNICATION_TIMESTAMP_API\n    _anjay_server_set_last_communication_time(server);\n#    endif // ANJAY_WITH_COMMUNICATION_TIMESTAMP_API\n\nend:\n    avs_coap_options_cleanup(&request.options);\n    avs_coap_options_cleanup(&response.options);\n    return err;\n}\n\navs_error_t _anjay_server_deregister(anjay_server_info_t *server) {\n    // make sure to cancel the reconnect/register/update job. there's no point\n    // in doing that if we don't want to be registered to the server.\n    avs_sched_del(&server->next_action_handle);\n\n    assert(_anjay_server_active(server));\n    anjay_connection_ref_t connection = {\n        .server = server,\n        .conn_type = ANJAY_CONNECTION_PRIMARY\n    };\n    if (!_anjay_connection_get_online_socket(connection)) {\n        anjay_log(ERROR, _(\"server connection is not online, skipping\"));\n#    ifdef ANJAY_WITH_CONN_STATUS_API\n        _anjay_set_server_connection_status(\n                server, ANJAY_SERV_CONN_STATUS_DEREGISTERED);\n#    endif // ANJAY_WITH_CONN_STATUS_API\n        return AVS_OK;\n    }\n\n    avs_error_t err = deregister(server);\n    if (avs_is_err(err)) {\n#    ifdef ANJAY_WITH_CONN_STATUS_API\n        _anjay_set_server_connection_status(server,\n                                            ANJAY_SERV_CONN_STATUS_ERROR);\n#    endif // ANJAY_WITH_CONN_STATUS_API\n        anjay_log(ERROR, _(\"could not send De-Register request: \") \"%s\",\n                  AVS_COAP_STRERROR(err));\n    }\n    return err;\n}\n#endif // ANJAY_WITHOUT_DEREGISTER\n\nconst anjay_registration_info_t *\n_anjay_server_registration_info(anjay_server_info_t *server) {\n    return &server->registration_info;\n}\n\nstatic avs_time_real_t get_registration_expire_time(int64_t lifetime_s) {\n    if (lifetime_s == 0) {\n        return AVS_TIME_REAL_INVALID;\n    }\n\n    return avs_time_real_add(avs_time_real_now(),\n                             avs_time_duration_from_scalar(lifetime_s,\n                                                           AVS_TIME_S));\n}\n\nvoid _anjay_server_update_registration_info(\n        anjay_server_info_t *server,\n        AVS_LIST(const anjay_string_t) *move_endpoint_path,\n        anjay_lwm2m_version_t lwm2m_version,\n        bool queue_mode,\n        anjay_update_parameters_t *move_params) {\n    anjay_registration_info_t *info = &server->registration_info;\n\n    if (move_endpoint_path && move_endpoint_path != &info->endpoint_path) {\n        AVS_LIST_CLEAR(&info->endpoint_path);\n        info->endpoint_path = *move_endpoint_path;\n        *move_endpoint_path = NULL;\n    }\n\n    if (move_params) {\n        move_assign_update_params(&info->last_update_params, move_params);\n    }\n\n    info->lwm2m_version = lwm2m_version;\n    info->queue_mode = queue_mode;\n    info->expire_time =\n            get_registration_expire_time(info->last_update_params.lifetime_s);\n    info->update_forced = false;\n    info->session_token = _anjay_server_primary_session_token(server);\n}\n\nstatic int\nserver_object_instances_count_clb(anjay_unlocked_t *anjay,\n                                  const anjay_dm_installed_object_t *obj,\n                                  anjay_iid_t iid,\n                                  void *count_ptr) {\n    (void) anjay;\n    (void) obj;\n    (void) iid;\n    ++*(size_t *) count_ptr;\n    return 0;\n}\n\nstatic size_t server_object_instances_count(anjay_unlocked_t *anjay) {\n    const anjay_dm_installed_object_t *server_obj =\n            _anjay_dm_find_object_by_oid(&anjay->dm, ANJAY_DM_OID_SERVER);\n    if (!server_obj) {\n        return 0;\n    }\n    size_t count = 0;\n    _anjay_dm_foreach_instance(anjay, server_obj,\n                               server_object_instances_count_clb, &count);\n    return count;\n}\n\nstatic bool server_state_stable(anjay_server_info_t *server) {\n    if (server->ssid == ANJAY_SSID_BOOTSTRAP) {\n        // Bootstrap server connection is considered stable if it's in the idle\n        // state waiting for 1.0-style Server-Initiated Bootstrap. That state\n        // does not expire.\n        return !_anjay_bootstrap_scheduled(server->anjay);\n    } else if (!_anjay_server_active(server)) {\n        return server->disabled_explicitly;\n    } else {\n        // Management servers connections are considered stable when they have\n        // a valid, non-expired registration.\n        return !_anjay_server_registration_expired(server);\n    }\n}\n\nbool _anjay_ongoing_registration_exists_unlocked(anjay_unlocked_t *anjay) {\n    if (_anjay_bootstrap_in_progress(anjay)) {\n        return true;\n    }\n\n    size_t dm_servers_count = server_object_instances_count(anjay);\n    if (dm_servers_count == 0) {\n        return false;\n    }\n\n    size_t loaded_servers_count = 0;\n    anjay_server_info_t *server;\n    AVS_LIST_FOREACH(server, anjay->servers) {\n        if (server->ssid != ANJAY_SSID_BOOTSTRAP) {\n            loaded_servers_count++;\n        }\n    }\n\n    if (dm_servers_count != loaded_servers_count) {\n        return true;\n    }\n\n    AVS_LIST_FOREACH(server, anjay->servers) {\n        if (!server->refresh_failed && !server_state_stable(server)) {\n            return true;\n        }\n    }\n\n    return false;\n}\n\nbool anjay_ongoing_registration_exists(anjay_t *anjay_locked) {\n    bool result = false;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    result = _anjay_ongoing_registration_exists_unlocked(anjay);\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return result;\n}\n\navs_time_real_t anjay_registration_expiration_time_with_status(\n        anjay_t *anjay_locked,\n        anjay_ssid_t ssid,\n        anjay_registration_expiration_status_t *out_status) {\n    avs_time_real_t result = AVS_TIME_REAL_INVALID;\n\n    if (out_status) {\n        *out_status = ANJAY_REGISTRATION_EXPIRATION_STATUS_EXPIRED;\n    }\n\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    anjay_server_info_t *server = _anjay_servers_find_active(anjay, ssid);\n\n    if (server) {\n        result =\n                _anjay_registration_expire_time_with_status(server, out_status);\n    }\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return result;\n}\n\nstatic avs_time_real_t\nnext_planned_lifecycle_operation(anjay_server_info_t *server) {\n    // If the server is inactive, but scheduled for reactivation - return the\n    // time at which it is scheduled. The additional server->next_action_handle\n    // check is intended to filter out the case when the transport is offline\n    // (see _anjay_server_on_refreshed(), ANJAY_SERVER_CONNECTION_OFFLINE case).\n    bool server_active = _anjay_server_active(server);\n    if (!server_active && server->next_action_handle\n            && avs_time_real_valid(server->reactivate_time)) {\n        return server->reactivate_time;\n    }\n\n    assert(server->anjay);\n    if (server->ssid == ANJAY_SSID_BOOTSTRAP) {\n        avs_time_real_t result = AVS_TIME_REAL_INVALID;\n#ifdef ANJAY_WITH_BOOTSTRAP\n        avs_time_monotonic_t client_initiated_bootstrap_time_monotonic =\n                avs_sched_time(&server->anjay->bootstrap\n                                        .client_initiated_bootstrap_handle);\n        result = avs_time_real_add(\n                avs_time_real_now(),\n                avs_time_monotonic_diff(\n                        client_initiated_bootstrap_time_monotonic,\n                        avs_time_monotonic_now()));\n#endif // ANJAY_WITH_BOOTSTRAP\n        return result;\n    } else if (_anjay_bootstrap_in_progress(server->anjay)) {\n        return AVS_TIME_REAL_INVALID;\n    } else if (server_active && server->registration_info.update_forced) {\n        return avs_time_real_now();\n    } else {\n        return get_time_of_next_update(server);\n    }\n}\n\navs_time_real_t anjay_next_planned_lifecycle_operation(anjay_t *anjay_locked,\n                                                       anjay_ssid_t ssid) {\n    avs_time_real_t result = AVS_TIME_REAL_INVALID;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    if (ssid == ANJAY_SSID_ANY) {\n        AVS_LIST(anjay_server_info_t) it;\n        AVS_LIST_FOREACH(it, anjay->servers) {\n            avs_time_real_t server_result =\n                    next_planned_lifecycle_operation(it);\n            if (!avs_time_real_valid(result)\n                    || avs_time_real_before(server_result, result)) {\n                result = server_result;\n            }\n        }\n    } else {\n        anjay_server_info_t *server = _anjay_servers_find(anjay, ssid);\n        if (!server) {\n            anjay_log(WARNING, _(\"no server with SSID = \") \"%u\", ssid);\n        } else {\n            result = next_planned_lifecycle_operation(server);\n        }\n    }\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return result;\n}\n\navs_time_real_t anjay_transport_next_planned_lifecycle_operation(\n        anjay_t *anjay_locked, anjay_transport_set_t transport_set) {\n    avs_time_real_t result = AVS_TIME_REAL_INVALID;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    AVS_LIST(anjay_server_info_t) it;\n    AVS_LIST_FOREACH(it, anjay->servers) {\n        anjay_server_connection_t *conn =\n                _anjay_connection_get(&it->connections,\n                                      ANJAY_CONNECTION_PRIMARY);\n        if (conn->transport != ANJAY_SOCKET_TRANSPORT_INVALID\n                && _anjay_socket_transport_included(transport_set,\n                                                    conn->transport)) {\n            avs_time_real_t server_result =\n                    next_planned_lifecycle_operation(it);\n            if (!avs_time_real_valid(result)\n                    || avs_time_real_before(server_result, result)) {\n                result = server_result;\n            }\n        }\n    }\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return result;\n}\n\n#ifdef ANJAY_WITH_COMMUNICATION_TIMESTAMP_API\navs_error_t anjay_get_server_last_registration_time(anjay_t *anjay,\n                                                    anjay_ssid_t ssid,\n                                                    avs_time_real_t *out_time) {\n    assert(anjay);\n    assert(out_time);\n\n    avs_error_t err = avs_errno(AVS_EBUSY);\n    *out_time = AVS_TIME_REAL_INVALID;\n\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    if (ssid == ANJAY_SSID_ANY) {\n        if (!anjay_unlocked->servers) {\n            anjay_log(WARNING, _(\"no servers found\"));\n            err = avs_errno(AVS_EEXIST);\n        } else {\n            AVS_LIST(anjay_server_info_t) it;\n            AVS_LIST_FOREACH(it, anjay_unlocked->servers) {\n                avs_time_real_t server_result =\n                        it->registration_info.last_registration_time;\n                if (!avs_time_real_valid(*out_time)\n                        || avs_time_real_before(*out_time, server_result)) {\n                    *out_time = server_result;\n                }\n            }\n            err = AVS_OK;\n        }\n    } else if (ssid == ANJAY_SSID_BOOTSTRAP) {\n        err = avs_errno(AVS_EINVAL);\n    } else {\n        anjay_server_info_t *server = _anjay_servers_find(anjay_unlocked, ssid);\n        if (!server) {\n            anjay_log(WARNING, _(\"no server with SSID = \") \"%u\", ssid);\n            err = avs_errno(AVS_EEXIST);\n        } else {\n            *out_time = server->registration_info.last_registration_time;\n            err = AVS_OK;\n        }\n    }\n    ANJAY_MUTEX_UNLOCK(anjay);\n\n    return err;\n}\n\navs_error_t anjay_get_server_next_update_time(anjay_t *anjay,\n                                              anjay_ssid_t ssid,\n                                              avs_time_real_t *out_time) {\n    assert(anjay);\n    assert(out_time);\n\n    avs_error_t err = avs_errno(AVS_EBUSY);\n    *out_time = AVS_TIME_REAL_INVALID;\n\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    if (ssid == ANJAY_SSID_ANY) {\n        if (!anjay_unlocked->servers) {\n            anjay_log(WARNING, _(\"no servers found\"));\n            err = avs_errno(AVS_EEXIST);\n        } else {\n            AVS_LIST(anjay_server_info_t) it;\n            AVS_LIST_FOREACH(it, anjay_unlocked->servers) {\n                avs_time_real_t server_result = get_time_of_next_update(it);\n                if (!avs_time_real_valid(*out_time)\n                        || avs_time_real_before(server_result, *out_time)) {\n                    *out_time = server_result;\n                }\n            }\n            err = AVS_OK;\n        }\n    } else if (ssid == ANJAY_SSID_BOOTSTRAP) {\n        err = avs_errno(AVS_EINVAL);\n    } else {\n        anjay_server_info_t *server = _anjay_servers_find(anjay_unlocked, ssid);\n        if (!server) {\n            anjay_log(WARNING, _(\"no server with SSID = \") \"%u\", ssid);\n            err = avs_errno(AVS_EEXIST);\n        } else {\n            *out_time = get_time_of_next_update(server);\n            err = AVS_OK;\n        }\n    }\n    ANJAY_MUTEX_UNLOCK(anjay);\n\n    return err;\n}\n\navs_error_t anjay_get_server_last_communication_time(\n        anjay_t *anjay, anjay_ssid_t ssid, avs_time_real_t *out_time) {\n    assert(anjay);\n    assert(out_time);\n\n    avs_error_t err = avs_errno(AVS_EBUSY);\n    *out_time = AVS_TIME_REAL_INVALID;\n\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    if (ssid == ANJAY_SSID_ANY) {\n        if (!anjay_unlocked->servers) {\n            anjay_log(WARNING, _(\"no servers found\"));\n            err = avs_errno(AVS_EEXIST);\n        } else {\n            AVS_LIST(anjay_server_info_t) it;\n            AVS_LIST_FOREACH(it, anjay_unlocked->servers) {\n                avs_time_real_t server_result = it->last_communication_time;\n                if (!avs_time_real_valid(*out_time)\n                        || avs_time_real_before(*out_time, server_result)) {\n                    *out_time = server_result;\n                }\n            }\n            err = AVS_OK;\n        }\n    } else if (ssid == ANJAY_SSID_BOOTSTRAP) {\n        err = avs_errno(AVS_EINVAL);\n    } else {\n        anjay_server_info_t *server = _anjay_servers_find(anjay_unlocked, ssid);\n        if (!server) {\n            anjay_log(WARNING, _(\"no server with SSID = \") \"%u\", ssid);\n            err = avs_errno(AVS_EEXIST);\n        } else {\n            *out_time = server->last_communication_time;\n            err = AVS_OK;\n        }\n    }\n    ANJAY_MUTEX_UNLOCK(anjay);\n\n    return err;\n}\n#endif // ANJAY_WITH_COMMUNICATION_TIMESTAMP_API\n"
  },
  {
    "path": "src/core/servers/anjay_register.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ANJAY_SERVERS_REGISTER_H\n#define ANJAY_SERVERS_REGISTER_H\n\n#include \"../anjay_core.h\"\n\n#include \"anjay_servers_internal.h\"\n\n#ifndef ANJAY_SERVERS_INTERNALS\n#    error \"Headers from servers/ are not meant to be included from outside\"\n#endif\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\nvoid _anjay_registration_info_cleanup(anjay_registration_info_t *info);\n\nvoid _anjay_registration_exchange_state_cleanup(\n        anjay_registration_async_exchange_state_t *state);\n\ntypedef enum {\n    /** Successfully registered/updated */\n    ANJAY_REGISTRATION_SUCCESS,\n    /** No response received */\n    ANJAY_REGISTRATION_ERROR_TIMEOUT,\n    /** A non-timeout communication error */\n    ANJAY_REGISTRATION_ERROR_NETWORK,\n    /** Non-success CoAP response received */\n    ANJAY_REGISTRATION_ERROR_REJECTED,\n    /**\n     * Fallback to older protocol version requested. Fully handled internally,\n     * should not be returned from _anjay_register/_anjay_update_registration.\n     */\n    ANJAY_REGISTRATION_ERROR_FALLBACK_REQUESTED,\n    /** Other failure */\n    ANJAY_REGISTRATION_ERROR_OTHER\n} anjay_registration_result_t;\n\n/**\n * Makes sure that the @p server has a valid registration state. May send\n * Register or Update messages as necessary. If the server is already properly\n * registered, does nothing - unless\n * server->data_active.registration_info.needs_update is set.\n *\n * @param server Active non-bootstrap server for which to manage the\n *               registration state.\n */\nvoid _anjay_server_ensure_valid_registration(anjay_server_info_t *server);\n\nint _anjay_server_reschedule_update_job(anjay_server_info_t *server);\n\n#ifndef ANJAY_WITHOUT_DEREGISTER\navs_error_t _anjay_server_deregister(anjay_server_info_t *server);\n#endif // ANJAY_WITHOUT_DEREGISTER\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif // ANJAY_SERVERS_REGISTER_H\n"
  },
  {
    "path": "src/core/servers/anjay_reload.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#include <inttypes.h>\n\n#define ANJAY_SERVERS_INTERNALS\n\n#include \"../anjay_servers_inactive.h\"\n#include \"../anjay_servers_reload.h\"\n#include \"../dm/anjay_query.h\"\n\n#include \"anjay_activate.h\"\n#include \"anjay_register.h\"\n#include \"anjay_server_connections.h\"\n#include \"anjay_servers_internal.h\"\n\nVISIBILITY_SOURCE_BEGIN\n\nstatic int reload_server_by_ssid(anjay_unlocked_t *anjay,\n                                 AVS_LIST(anjay_server_info_t) *old_servers,\n                                 anjay_ssid_t ssid) {\n    anjay_log(TRACE, _(\"reloading server SSID \") \"%u\", ssid);\n\n    AVS_LIST(anjay_server_info_t) *server_ptr =\n            _anjay_servers_find_ptr(old_servers, ssid);\n    if (server_ptr) {\n        AVS_LIST(anjay_server_info_t) server = AVS_LIST_DETACH(server_ptr);\n        _anjay_servers_add(&anjay->servers, server);\n        if (ssid == ANJAY_SSID_BOOTSTRAP\n                || !_anjay_bootstrap_in_progress(anjay)) {\n            if (_anjay_server_active(server)) {\n                anjay_log(TRACE, _(\"reloading active server SSID \") \"%u\", ssid);\n                return _anjay_schedule_refresh_server(server,\n                                                      AVS_TIME_DURATION_ZERO);\n            } else if (!server->next_action_handle\n                       && avs_time_real_valid(server->reactivate_time)) {\n                return _anjay_server_sched_activate(server);\n            }\n        }\n        return 0;\n    }\n\n    anjay_log(TRACE, _(\"creating server SSID \") \"%u\", ssid);\n    AVS_LIST(anjay_server_info_t) new_server =\n            _anjay_servers_create_inactive(anjay, ssid);\n    if (!new_server) {\n        return -1;\n    }\n\n    _anjay_servers_add(&anjay->servers, new_server);\n    int result = 0;\n    if ((ssid != ANJAY_SSID_BOOTSTRAP && !_anjay_bootstrap_in_progress(anjay))\n            || _anjay_bootstrap_legacy_server_initiated_allowed(anjay)) {\n        new_server->reactivate_time = avs_time_real_now();\n        result = _anjay_server_sched_activate(new_server);\n    }\n#ifdef ANJAY_WITH_CONN_STATUS_API\n    _anjay_set_server_connection_status(new_server,\n                                        ANJAY_SERV_CONN_STATUS_INITIAL);\n#endif // ANJAY_WITH_CONN_STATUS_API\n    return result;\n}\n\ntypedef struct {\n    AVS_LIST(anjay_server_info_t) *old_servers;\n    int retval;\n} reload_servers_state_t;\n\nstatic int reload_server_by_server_iid(anjay_unlocked_t *anjay,\n                                       const anjay_dm_installed_object_t *obj,\n                                       anjay_iid_t iid,\n                                       void *state_) {\n    (void) obj;\n    reload_servers_state_t *state = (reload_servers_state_t *) state_;\n\n    anjay_ssid_t ssid;\n    if (_anjay_ssid_from_server_iid(anjay, iid, &ssid)) {\n        state->retval = -1;\n        return 0;\n    }\n\n    if (reload_server_by_ssid(anjay, state->old_servers, ssid)) {\n        anjay_log(TRACE, _(\"could not reload server SSID \") \"%u\", ssid);\n        state->retval = -1;\n    }\n\n    return 0;\n}\n\nstatic void reload_servers_sched_job(avs_sched_t *sched, const void *unused) {\n    (void) unused;\n    anjay_log(TRACE, _(\"reloading servers\"));\n\n    anjay_t *anjay_locked = _anjay_get_from_sched(sched);\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    AVS_LIST(anjay_server_info_t) old_servers = anjay->servers;\n    anjay->servers = NULL;\n    reload_servers_state_t reload_state = {\n        .old_servers = &old_servers,\n        .retval = 0\n    };\n\n    const anjay_dm_installed_object_t *obj =\n            _anjay_dm_find_object_by_oid(&anjay->dm, ANJAY_DM_OID_SERVER);\n    if (obj\n            && _anjay_dm_foreach_instance(\n                       anjay, obj, reload_server_by_server_iid, &reload_state)\n            && !reload_state.retval) {\n        reload_state.retval = -1;\n    }\n    if (!reload_state.retval\n            && _anjay_find_bootstrap_security_iid(anjay) != ANJAY_ID_INVALID) {\n        reload_state.retval = reload_server_by_ssid(anjay, &old_servers,\n                                                    ANJAY_SSID_BOOTSTRAP);\n    }\n\n    // If the only entry we have is a bootstrap server that's inactive and not\n    // scheduled for activation - schedule that. It's necessary to perform\n    // Client-Initiated Bootstrap if 1.0-style Server-Initiated Bootstrap is\n    // disabled in configuration.\n    if (!reload_state.retval && anjay->servers && !AVS_LIST_NEXT(anjay->servers)\n            && anjay->servers->ssid == ANJAY_SSID_BOOTSTRAP\n            && !_anjay_server_active(anjay->servers)\n            && !anjay->servers->next_action_handle\n            && !anjay->servers->refresh_failed) {\n        anjay->servers->reactivate_time = avs_time_real_now();\n        reload_state.retval = _anjay_server_sched_activate(anjay->servers);\n    }\n\n    if (reload_state.retval) {\n        // re-add old servers, don't discard them\n        AVS_LIST(anjay_server_info_t) *server_ptr;\n        AVS_LIST(anjay_server_info_t) helper;\n        AVS_LIST_DELETABLE_FOREACH_PTR(server_ptr, helper, &old_servers) {\n            if (_anjay_server_active(*server_ptr)) {\n                _anjay_servers_add(&anjay->servers,\n                                   AVS_LIST_DETACH(server_ptr));\n            }\n        }\n        anjay_log(WARNING, _(\"reloading servers failed, re-scheduling job\"));\n        _anjay_schedule_delayed_reload_servers(anjay);\n    } else {\n        if (obj) {\n            anjay_log(INFO, _(\"servers reloaded\"));\n        } else {\n            anjay_log(WARNING,\n                      _(\"Security object not present, no servers to create\"));\n        }\n        _anjay_observe_gc(anjay);\n    }\n\n    _anjay_servers_internal_deregister(&old_servers);\n    _anjay_servers_internal_cleanup(&old_servers);\n    anjay_log(TRACE, \"%lu\" _(\" servers reloaded\"),\n              (unsigned long) AVS_LIST_SIZE(anjay->servers));\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n}\n\nstatic int schedule_reload_servers(anjay_unlocked_t *anjay, bool delayed) {\n    static const long RELOAD_DELAY_S = 5;\n    if (!anjay->sched\n            || AVS_SCHED_DELAYED(\n                       anjay->sched, &anjay->reload_servers_sched_job_handle,\n                       avs_time_duration_from_scalar(\n                               delayed ? RELOAD_DELAY_S : 0, AVS_TIME_S),\n                       reload_servers_sched_job, NULL, 0)) {\n        anjay_log(ERROR, _(\"could not schedule reload_servers_job\"));\n        return -1;\n    }\n    return 0;\n}\n\nint _anjay_schedule_reload_servers(anjay_unlocked_t *anjay) {\n    return schedule_reload_servers(anjay, false);\n}\n\nint _anjay_schedule_delayed_reload_servers(anjay_unlocked_t *anjay) {\n    return schedule_reload_servers(anjay, true);\n}\n\nint _anjay_schedule_refresh_server(anjay_server_info_t *server,\n                                   avs_time_duration_t delay) {\n    if (_anjay_server_reschedule_next_action(\n                server, delay, ANJAY_SERVER_NEXT_ACTION_REFRESH)) {\n        anjay_log(ERROR,\n                  _(\"could not schedule ANJAY_SERVER_NEXT_ACTION_REFRESH\"));\n        return -1;\n    }\n    return 0;\n}\n\nconst anjay_transport_set_t ANJAY_TRANSPORT_SET_ALL = {\n    .udp = true,\n    .tcp = true\n};\n\nconst anjay_transport_set_t ANJAY_TRANSPORT_SET_IP = {\n    .udp = true,\n    .tcp = true\n};\n\nconst anjay_transport_set_t ANJAY_TRANSPORT_SET_UDP = {\n    .udp = true\n};\n\nconst anjay_transport_set_t ANJAY_TRANSPORT_SET_TCP = {\n    .tcp = true\n};\n\nstatic anjay_transport_set_t transport_set_not(anjay_transport_set_t set) {\n    return (anjay_transport_set_t) {\n        .udp = !set.udp,\n        .tcp = !set.tcp\n    };\n}\n\nstatic anjay_transport_set_t transport_set_union(anjay_transport_set_t left,\n                                                 anjay_transport_set_t right) {\n    return (anjay_transport_set_t) {\n        .udp = left.udp || right.udp,\n        .tcp = left.tcp || right.tcp\n    };\n}\n\nstatic anjay_transport_set_t\ntransport_set_intersection(anjay_transport_set_t left,\n                           anjay_transport_set_t right) {\n    return (anjay_transport_set_t) {\n        .udp = left.udp && right.udp,\n        .tcp = left.tcp && right.tcp\n    };\n}\n\nstatic bool transport_set_empty(anjay_transport_set_t set) {\n    return !(set.udp || set.tcp);\n}\n\nanjay_transport_set_t\n_anjay_transport_set_remove_unavailable(anjay_unlocked_t *anjay,\n                                        anjay_transport_set_t set) {\n    (void) anjay;\n    return (anjay_transport_set_t) {\n        .udp = set.udp,\n        .tcp = set.tcp\n    };\n}\n\nbool _anjay_socket_transport_included(anjay_transport_set_t set,\n                                      anjay_socket_transport_t transport) {\n    switch (transport) {\n    case ANJAY_SOCKET_TRANSPORT_UDP:\n        return set.udp;\n    case ANJAY_SOCKET_TRANSPORT_TCP:\n        return set.tcp;\n    case ANJAY_SOCKET_TRANSPORT_SMS:\n        break;\n    case ANJAY_SOCKET_TRANSPORT_NIDD:\n        break;\n    case ANJAY_SOCKET_TRANSPORT_INVALID:\n        break;\n    }\n    AVS_UNREACHABLE(\"Invalid transport\");\n    return false;\n}\n\nbool _anjay_socket_transport_is_online(anjay_unlocked_t *anjay,\n                                       anjay_socket_transport_t transport) {\n    return _anjay_socket_transport_included(anjay->online_transports,\n                                            transport);\n}\n\nstatic anjay_transport_set_t get_online_transports(anjay_t *anjay_locked) {\n    anjay_transport_set_t result = { 0 };\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    result = anjay->online_transports;\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return result;\n}\n\nbool anjay_transport_is_offline(anjay_t *anjay,\n                                anjay_transport_set_t transport_set) {\n    return transport_set_empty(transport_set_intersection(\n            get_online_transports(anjay), transport_set));\n}\n\nstatic int set_online_unlocked(anjay_unlocked_t *anjay,\n                               anjay_transport_set_t transport_set) {\n    anjay_transport_set_t orig_online_transports = anjay->online_transports;\n    anjay->online_transports =\n            _anjay_transport_set_remove_unavailable(anjay, transport_set);\n    bool reload_was_scheduled = !!anjay->reload_servers_sched_job_handle;\n    int result = _anjay_schedule_reload_servers(anjay);\n#ifdef ANJAY_WITH_DOWNLOADER\n    if (!result\n            && (result = _anjay_downloader_sync_online_transports(\n                        &anjay->downloader))\n            && !reload_was_scheduled) {\n        avs_sched_del(&anjay->reload_servers_sched_job_handle);\n    }\n#else  // ANJAY_WITH_DOWNLOADER\n    (void) reload_was_scheduled;\n#endif // ANJAY_WITH_DOWNLOADER\n    if (!result) {\n        _anjay_servers_interrupt_offline(anjay);\n    } else {\n        anjay->online_transports = orig_online_transports;\n    }\n    return result;\n}\n\nint anjay_transport_enter_offline(anjay_t *anjay_locked,\n                                  anjay_transport_set_t transport_set) {\n    int result = -1;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    result = set_online_unlocked(\n            anjay,\n            transport_set_intersection(anjay->online_transports,\n                                       transport_set_not(transport_set)));\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return result;\n}\n\nstatic int exit_offline_unlocked(anjay_unlocked_t *anjay,\n                                 anjay_transport_set_t transport_set) {\n    return set_online_unlocked(anjay,\n                               transport_set_union(anjay->online_transports,\n                                                   transport_set));\n}\n\nint anjay_transport_exit_offline(anjay_t *anjay_locked,\n                                 anjay_transport_set_t transport_set) {\n    int result = -1;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    result = exit_offline_unlocked(anjay, transport_set);\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return result;\n}\n\nint anjay_transport_set_online(anjay_t *anjay_locked,\n                               anjay_transport_set_t transport_set) {\n    int result = -1;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    result = set_online_unlocked(anjay, transport_set);\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return result;\n}\n\n/**\n * Schedules reconnection of all servers, and even downloader sockets. This\n * basically:\n *\n * - Immediately closes (but doesn't clean up - so that the servers are still\n *   considered active) all relevant sockets\n * - Exits offline mode if it is currently enabled - this will call\n *   _anjay_schedule_reload_servers(), which will eventually reconnect all\n *   servers\n * - Reschedules activation (calls _anjay_server_sched_activate()) for all\n *   servers that have reached the ICMP failure limit\n * - Calls _anjay_downloader_sched_reconnect_all() to reconnect downloader\n *   sockets\n */\nint anjay_transport_schedule_reconnect(anjay_t *anjay_locked,\n                                       anjay_transport_set_t transport_set) {\n    int result = -1;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    if (!(result = exit_offline_unlocked(anjay, transport_set))) {\n        AVS_LIST(anjay_server_info_t) server;\n        AVS_LIST_FOREACH(server, anjay->servers) {\n            anjay_connection_type_t conn_type;\n            ANJAY_CONNECTION_TYPE_FOREACH(conn_type) {\n                const anjay_connection_ref_t ref = {\n                    .server = server,\n                    .conn_type = conn_type\n                };\n                anjay_server_connection_t *connection =\n                        _anjay_get_server_connection(ref);\n                if (_anjay_connection_internal_get_socket(connection)\n                        && _anjay_socket_transport_included(\n                                   transport_set, connection->transport)) {\n                    _anjay_connection_suspend(ref);\n                }\n            }\n        }\n        result = _anjay_servers_sched_reactivate_all_given_up(anjay);\n#ifdef ANJAY_WITH_DOWNLOADER\n        if (!result) {\n            result = _anjay_downloader_sched_reconnect_by_transports(\n                    &anjay->downloader, transport_set);\n        }\n#endif // ANJAY_WITH_DOWNLOADER\n    }\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return result;\n}\n\nvoid _anjay_security_config_cache_cleanup(\n        anjay_security_config_cache_t *cache) {\n    avs_free(cache->psk_key);\n    avs_free(cache->psk_identity);\n    avs_free(cache->trusted_certs_array);\n    avs_free(cache->cert_revocation_lists_array);\n    avs_free(cache->client_cert_array);\n    avs_free(cache->client_key);\n    avs_free(cache->dane_tlsa_record);\n    avs_free(cache->ciphersuites.ids);\n    memset(cache, 0, sizeof(*cache));\n}\n"
  },
  {
    "path": "src/core/servers/anjay_security.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ANJAY_SERVERS_SECURITY_H\n#define ANJAY_SERVERS_SECURITY_H\n\n#include \"anjay_connections.h\"\n\n#if !defined(ANJAY_SERVERS_INTERNALS) && !defined(ANJAY_TEST)\n#    error \"Headers from servers/ are not meant to be included from outside\"\n#endif\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\ntypedef struct {\n    anjay_ssid_t ssid;\n    anjay_iid_t security_iid;\n    avs_url_t *uri;\n    const anjay_transport_info_t *transport_info;\n    bool is_encrypted;\n} anjay_connection_info_t;\n\nint _anjay_connection_security_generic_get_uri(\n        anjay_unlocked_t *anjay,\n        anjay_iid_t security_iid,\n        avs_url_t **out_uri,\n        const anjay_transport_info_t **out_transport_info);\n\navs_error_t _anjay_connection_security_generic_get_config(\n        anjay_unlocked_t *anjay,\n        anjay_security_config_t *out_config,\n        anjay_security_config_cache_t *cache,\n        anjay_connection_info_t *inout_info);\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif // ANJAY_SERVERS_SECURITY_H\n"
  },
  {
    "path": "src/core/servers/anjay_security_generic.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#include <inttypes.h>\n\n#include <avsystem/commons/avs_stream_membuf.h>\n#include <avsystem/commons/avs_utils.h>\n\n#include \"../anjay_io_core.h\"\n\n#define ANJAY_SERVERS_CONNECTION_SOURCE\n#define ANJAY_SERVERS_INTERNALS\n\n#include \"anjay_connections_internal.h\"\n#include \"anjay_security.h\"\n\nVISIBILITY_SOURCE_BEGIN\n\nint _anjay_connection_security_generic_get_uri(\n        anjay_unlocked_t *anjay,\n        anjay_iid_t security_iid,\n        avs_url_t **out_uri,\n        const anjay_transport_info_t **out_transport_info) {\n    assert(out_uri);\n    assert(!*out_uri);\n    assert(out_transport_info);\n    assert(!*out_transport_info);\n    char raw_uri[ANJAY_MAX_URL_RAW_LENGTH];\n\n    const anjay_uri_path_t path =\n            MAKE_RESOURCE_PATH(ANJAY_DM_OID_SECURITY, security_iid,\n                               ANJAY_DM_RID_SECURITY_SERVER_URI);\n\n    if (_anjay_dm_read_resource_string(anjay, &path, raw_uri,\n                                       sizeof(raw_uri))) {\n        anjay_log(ERROR, _(\"could not read LwM2M server URI from \") \"%s\",\n                  ANJAY_DEBUG_MAKE_PATH(&path));\n        return -1;\n    }\n\n    if (!(*out_uri = avs_url_parse_lenient(raw_uri))\n            || !(*out_transport_info = _anjay_transport_info_by_uri_scheme(\n                         avs_url_protocol(*out_uri)))\n            || avs_url_user(*out_uri) || avs_url_password(*out_uri)\n            || (avs_url_port(*out_uri) && !*avs_url_port(*out_uri))) {\n        if (*out_uri) {\n            avs_url_free(*out_uri);\n        }\n        *out_uri = NULL;\n        *out_transport_info = NULL;\n        anjay_log(ERROR, _(\"could not parse LwM2M server URI: \") \"%s\", raw_uri);\n        return -1;\n    }\n    return 0;\n}\n\nstatic int get_security_mode(anjay_unlocked_t *anjay,\n                             anjay_iid_t security_iid,\n                             anjay_security_mode_t *out_mode) {\n    int64_t mode;\n    const anjay_uri_path_t path =\n            MAKE_RESOURCE_PATH(ANJAY_DM_OID_SECURITY, security_iid,\n                               ANJAY_DM_RID_SECURITY_MODE);\n\n    if (_anjay_dm_read_resource_i64(anjay, &path, &mode)) {\n        anjay_log(ERROR,\n                  _(\"could not read LwM2M server security mode from \") \"%s\",\n                  ANJAY_DEBUG_MAKE_PATH(&path));\n        return -1;\n    }\n\n    switch (mode) {\n    case ANJAY_SECURITY_NOSEC:\n    case ANJAY_SECURITY_PSK:\n    case ANJAY_SECURITY_CERTIFICATE:\n    case ANJAY_SECURITY_EST:\n        *out_mode = (anjay_security_mode_t) mode;\n        return 0;\n    case ANJAY_SECURITY_RPK:\n    default:\n        anjay_log(ERROR, _(\"unsupported security mode: \") \"%s\",\n                  AVS_INT64_AS_STRING(mode));\n        return -1;\n    }\n}\n\nstatic bool\nsecurity_matches_transport(anjay_security_mode_t security_mode,\n                           const anjay_transport_info_t *transport_info) {\n    assert(transport_info);\n    if (transport_info->security == ANJAY_TRANSPORT_SECURITY_UNDEFINED) {\n        // URI scheme does not specify security,\n        // so it is valid for all security modes\n        return true;\n    }\n\n    const bool is_secure_transport =\n            (transport_info->security == ANJAY_TRANSPORT_ENCRYPTED);\n    const bool needs_secure_transport = (security_mode != ANJAY_SECURITY_NOSEC);\n\n    if (is_secure_transport != needs_secure_transport) {\n        anjay_log(WARNING,\n                  _(\"security mode \") \"%d\" _(\" requires \") \"%s\" _(\n                          \"secure protocol, but '\") \"%s\" _(\"' was configured\"),\n                  (int) security_mode, needs_secure_transport ? \"\" : \"in\",\n                  transport_info->uri_scheme);\n        return false;\n    }\n\n    return true;\n}\n\n#ifdef ANJAY_WITH_LWM2M11\nstatic avs_error_t\nget_tlsa_settings(anjay_unlocked_t *anjay,\n                  anjay_iid_t security_iid,\n                  anjay_security_mode_t security_mode,\n                  avs_net_socket_dane_tlsa_record_t *out_record) {\n    if (security_mode != ANJAY_SECURITY_CERTIFICATE\n            && security_mode != ANJAY_SECURITY_EST) {\n        return AVS_OK;\n    }\n\n    uint64_t tmp;\n    if (!_anjay_dm_read_resource_u64(\n                anjay,\n                &MAKE_RESOURCE_PATH(ANJAY_DM_OID_SECURITY, security_iid,\n                                    ANJAY_DM_RID_SECURITY_MATCHING_TYPE),\n                &tmp)) {\n        switch (tmp) {\n        case 0:\n            out_record->matching_type = AVS_NET_SOCKET_DANE_MATCH_FULL;\n            break;\n        case 1:\n            out_record->matching_type = AVS_NET_SOCKET_DANE_MATCH_SHA256;\n            break;\n        case 3:\n            out_record->matching_type = AVS_NET_SOCKET_DANE_MATCH_SHA512;\n            break;\n        case 2:\n            // Matching Type 2 is defined in LwM2M as SHA384\n            // which is not supported\n        default:\n            anjay_log(WARNING, _(\"unsupported matching type: \") \"%s\",\n                      AVS_UINT64_AS_STRING(tmp));\n        }\n    }\n\n    if (!_anjay_dm_read_resource_u64(\n                anjay,\n                &MAKE_RESOURCE_PATH(ANJAY_DM_OID_SECURITY, security_iid,\n                                    ANJAY_DM_RID_SECURITY_CERTIFICATE_USAGE),\n                &tmp)) {\n\n        anjay_log(DEBUG, _(\"server \") \"/%u/%u\" _(\": certificate usage = \") \"%s\",\n                  ANJAY_DM_OID_SECURITY, security_iid,\n                  AVS_UINT64_AS_STRING(tmp));\n        switch (tmp) {\n        case (uint64_t) AVS_NET_SOCKET_DANE_CA_CONSTRAINT:\n        case (uint64_t) AVS_NET_SOCKET_DANE_SERVICE_CERTIFICATE_CONSTRAINT:\n        case (uint64_t) AVS_NET_SOCKET_DANE_TRUST_ANCHOR_ASSERTION:\n        case (uint64_t) AVS_NET_SOCKET_DANE_DOMAIN_ISSUED_CERTIFICATE:\n            out_record->certificate_usage =\n                    (avs_net_socket_dane_certificate_usage_t) tmp;\n            break;\n        default:\n            anjay_log(WARNING, _(\"unsupported certificate usage: \") \"%s\",\n                      AVS_UINT64_AS_STRING(tmp));\n        }\n    }\n\n    return AVS_OK;\n}\n#endif // ANJAY_WITH_LWM2M11\n\nstatic avs_error_t init_cert_security(anjay_unlocked_t *anjay,\n                                      anjay_ssid_t ssid,\n                                      anjay_iid_t security_iid,\n                                      anjay_security_config_t *security,\n                                      anjay_security_mode_t security_mode,\n                                      anjay_security_config_cache_t *cache) {\n    avs_net_certificate_info_t certificate_info = {\n        .ignore_system_trust_store = true\n    };\n\n    {\n        AVS_STATIC_ASSERT(sizeof(*cache->client_cert_array)\n                                  == sizeof(avs_crypto_security_info_union_t),\n                          certificate_chain_info_equivalent_to_union);\n        size_t element_count = 0;\n        avs_error_t err = _anjay_dm_read_security_info(\n                anjay, security_iid, ANJAY_DM_RID_SECURITY_PK_OR_IDENTITY,\n                AVS_CRYPTO_SECURITY_INFO_CERTIFICATE_CHAIN,\n                (avs_crypto_security_info_union_t **) &cache->client_cert_array,\n                &element_count);\n        if (avs_is_err(err)) {\n            return err;\n        }\n        switch (element_count) {\n        case 0:\n            break;\n        case 1:\n            certificate_info.client_cert = cache->client_cert_array[0];\n            break;\n        default:\n            certificate_info.client_cert =\n                    avs_crypto_certificate_chain_info_from_array(\n                            cache->client_cert_array, element_count);\n        }\n    }\n\n    {\n        AVS_STATIC_ASSERT(sizeof(*cache->client_key)\n                                  == sizeof(avs_crypto_security_info_union_t),\n                          private_key_info_equivalent_to_union);\n        size_t element_count = 0;\n        avs_error_t err = _anjay_dm_read_security_info(\n                anjay, security_iid, ANJAY_DM_RID_SECURITY_SECRET_KEY,\n                AVS_CRYPTO_SECURITY_INFO_PRIVATE_KEY,\n                (avs_crypto_security_info_union_t **) &cache->client_key,\n                &element_count);\n        if (avs_is_err(err)) {\n            return err;\n        }\n        assert(element_count == 1);\n        certificate_info.client_key = *cache->client_key;\n    }\n\n    avs_stream_t *server_pk_membuf = avs_stream_membuf_create();\n    if (!server_pk_membuf) {\n        _anjay_log_oom();\n        return avs_errno(AVS_ENOMEM);\n    }\n    avs_net_socket_dane_tlsa_record_t dane_tlsa_record = {\n        .certificate_usage = AVS_NET_SOCKET_DANE_DOMAIN_ISSUED_CERTIFICATE\n    };\n    avs_error_t err = AVS_OK;\n#ifdef ANJAY_WITH_LWM2M11\n    err = get_tlsa_settings(anjay, security_iid, security_mode,\n                            &dane_tlsa_record);\n#endif // ANJAY_WITH_LWM2M11\n    if (avs_is_ok(err)) {\n        err = avs_stream_write(server_pk_membuf, &dane_tlsa_record,\n                               sizeof(dane_tlsa_record));\n    }\n    if (avs_is_ok(err)) {\n        anjay_output_buf_ctx_t server_pk_ctx =\n                _anjay_output_buf_ctx_init(server_pk_membuf);\n        const anjay_uri_path_t server_pk_path =\n                MAKE_RESOURCE_PATH(ANJAY_DM_OID_SECURITY, security_iid,\n                                   ANJAY_DM_RID_SECURITY_SERVER_PK_OR_IDENTITY);\n        if (_anjay_dm_read_resource_into_ctx(\n                    anjay, &server_pk_path,\n                    (anjay_unlocked_output_ctx_t *) &server_pk_ctx)) {\n            anjay_log(WARNING, _(\"read \") \"%s\" _(\" failed\"),\n                      ANJAY_DEBUG_MAKE_PATH(&server_pk_path));\n            err = avs_errno(AVS_EPROTO);\n        }\n    }\n    if (avs_is_ok(err)) {\n        void *buffer = NULL;\n        size_t buffer_size;\n        if (avs_is_ok((err = avs_stream_membuf_take_ownership(\n                               server_pk_membuf, &buffer, &buffer_size)))) {\n            AVS_ASSERT(\n                    ((uintptr_t) buffer)\n                                    % AVS_ALIGNOF(\n                                              avs_net_socket_dane_tlsa_record_t)\n                            == 0,\n                    \"avs_stream_membuf_take_ownership returned misaligned \"\n                    \"pointer\");\n            if (buffer_size > sizeof(avs_net_socket_dane_tlsa_record_t)) {\n                cache->dane_tlsa_record =\n                        (avs_net_socket_dane_tlsa_record_t *) buffer;\n                cache->dane_tlsa_record->association_data =\n                        (char *) buffer\n                        + sizeof(avs_net_socket_dane_tlsa_record_t);\n                cache->dane_tlsa_record->association_data_size =\n                        buffer_size - sizeof(avs_net_socket_dane_tlsa_record_t);\n            } else {\n                avs_free(buffer);\n            }\n        }\n    }\n    avs_stream_cleanup(&server_pk_membuf);\n#ifdef ANJAY_WITH_LWM2M11\n    if (avs_is_ok(err)) {\n        err = _anjay_server_read_sni(anjay, security_iid, security, cache);\n    }\n#endif // ANJAY_WITH_LWM2M11\n    if (avs_is_err(err)) {\n        return err;\n    }\n\n    if (cache->dane_tlsa_record) {\n        certificate_info.server_cert_validation = true;\n        certificate_info.dane = true;\n        security->dane_tlsa_record = cache->dane_tlsa_record;\n    }\n\n#ifdef ANJAY_WITH_LWM2M11\n    const anjay_trust_store_t *trust_store =\n            _anjay_get_trust_store(anjay, ssid, security_mode);\n    if (trust_store) {\n        // Enforce validation of peer certificate chain\n        certificate_info.server_cert_validation = true;\n        certificate_info.ignore_system_trust_store =\n                !trust_store->use_system_wide;\n        certificate_info.trusted_certs =\n                avs_crypto_certificate_chain_info_from_list(trust_store->certs);\n        certificate_info.cert_revocation_lists =\n                avs_crypto_cert_revocation_list_info_from_list(\n                        trust_store->crls);\n        certificate_info.rebuild_client_cert_chain =\n                anjay->rebuild_client_cert_chain;\n        if (trust_store != &anjay->initial_trust_store) {\n        }\n    }\n    if (dane_tlsa_record.certificate_usage == AVS_NET_SOCKET_DANE_CA_CONSTRAINT\n            || dane_tlsa_record.certificate_usage\n                           == AVS_NET_SOCKET_DANE_SERVICE_CERTIFICATE_CONSTRAINT) {\n        // Certificate Usage modes 0 and 1 require PKIX validation,\n        // so enable validation even if no certificate is explicitly\n        // specified\n        certificate_info.server_cert_validation = true;\n    }\n#endif // ANJAY_WITH_LWM2M11\n    (void) ssid;\n    (void) security_mode;\n\n    security->security_info =\n            avs_net_security_info_from_certificates(certificate_info);\n\n    return AVS_OK;\n}\n\nstatic avs_error_t init_security(anjay_unlocked_t *anjay,\n                                 anjay_ssid_t ssid,\n                                 anjay_iid_t security_iid,\n                                 anjay_security_config_t *security,\n                                 anjay_security_mode_t security_mode,\n                                 anjay_security_config_cache_t *cache) {\n    switch (security_mode) {\n    case ANJAY_SECURITY_NOSEC:\n        return AVS_OK;\n    case ANJAY_SECURITY_PSK:\n        return _anjay_connection_init_psk_security(\n                anjay, security_iid, ANJAY_DM_RID_SECURITY_PK_OR_IDENTITY,\n                ANJAY_DM_RID_SECURITY_SECRET_KEY, security, cache);\n    case ANJAY_SECURITY_CERTIFICATE:\n    case ANJAY_SECURITY_EST:\n        return init_cert_security(anjay, ssid, security_iid, security,\n                                  security_mode, cache);\n    case ANJAY_SECURITY_RPK:\n    default:\n        return avs_errno(AVS_EINVAL);\n    }\n}\n\n#ifdef ANJAY_WITH_LWM2M11\nstatic int read_ciphersuite_list(anjay_unlocked_t *anjay,\n                                 anjay_iid_t security_iid,\n                                 uint32_t **out_u32_ciphersuites,\n                                 size_t *out_num_ciphersuites) {\n    assert(out_u32_ciphersuites);\n    assert(!*out_u32_ciphersuites);\n    assert(out_num_ciphersuites);\n    assert(!*out_num_ciphersuites);\n\n    const anjay_uri_path_t path =\n            MAKE_RESOURCE_PATH(ANJAY_DM_OID_SECURITY, security_iid,\n                               ANJAY_DM_RID_SECURITY_DTLS_TLS_CIPHERSUITE);\n\n    int result = _anjay_dm_read_resource_u32_array(\n            anjay, &path, out_u32_ciphersuites, out_num_ciphersuites);\n    if (result) {\n        assert(!*out_u32_ciphersuites);\n        assert(!*out_num_ciphersuites);\n        if (result == ANJAY_ERR_NOT_FOUND\n                || result == ANJAY_ERR_METHOD_NOT_ALLOWED) {\n            return 0;\n        } else {\n            return result;\n        }\n    }\n\n    for (size_t i = 0; i < *out_num_ciphersuites; ++i) {\n        if ((*out_u32_ciphersuites)[i] > UINT16_MAX) {\n            anjay_log(ERROR,\n                      _(\"cipher ID too large: \") \"%\" PRIu32 _(\" > \") \"%\" PRIu16,\n                      (*out_u32_ciphersuites)[i], UINT16_MAX);\n            avs_free(*out_u32_ciphersuites);\n            *out_u32_ciphersuites = NULL;\n            *out_num_ciphersuites = 0;\n            return -1;\n        }\n    }\n\n    return 0;\n}\n#endif // ANJAY_WITH_LWM2M11\n\navs_error_t _anjay_connection_security_generic_get_config(\n        anjay_unlocked_t *anjay,\n        anjay_security_config_t *out_config,\n        anjay_security_config_cache_t *cache,\n        anjay_connection_info_t *inout_info) {\n    anjay_security_mode_t security_mode;\n    if (get_security_mode(anjay, inout_info->security_iid, &security_mode)) {\n        return avs_errno(AVS_EPROTO);\n    }\n\n    memset(out_config, 0, sizeof(*out_config));\n    out_config->tls_ciphersuites = anjay->default_tls_ciphersuites;\n\n    if (inout_info->transport_info\n            && !security_matches_transport(security_mode,\n                                           inout_info->transport_info)) {\n        return avs_errno(AVS_EPROTO);\n    }\n\n#ifdef ANJAY_WITH_LWM2M11\n    if (security_mode != ANJAY_SECURITY_NOSEC\n            && read_ciphersuite_list(anjay, inout_info->security_iid,\n                                     &cache->ciphersuites.ids,\n                                     &cache->ciphersuites.num_ids)) {\n        assert(!cache->ciphersuites.ids);\n        return avs_errno(AVS_EPROTO);\n    }\n\n    if (cache->ciphersuites.num_ids == 0) {\n        anjay_log(DEBUG,\n                  _(\"no ciphers configured for security IID \") \"%\" PRIu16 _(\n                          \", using \") \"%s\" _(\" defaults\"),\n                  inout_info->security_iid,\n                  anjay->default_tls_ciphersuites.num_ids > 0\n                          ? \"anjay_configuration_t\"\n                          : \"TLS backend\");\n    } else {\n        out_config->tls_ciphersuites = cache->ciphersuites;\n    }\n#endif // ANJAY_WITH_LWM2M11\n\n    avs_error_t err =\n            init_security(anjay, inout_info->ssid, inout_info->security_iid,\n                          out_config, security_mode, cache);\n    if (avs_is_err(err)) {\n        return err;\n    }\n    inout_info->is_encrypted = (security_mode != ANJAY_SECURITY_NOSEC);\n    anjay_log(DEBUG, _(\"server \") \"/%u/%u\" _(\": security mode = \") \"%d\",\n              ANJAY_DM_OID_SECURITY, inout_info->security_iid,\n              (int) security_mode);\n    return AVS_OK;\n}\n"
  },
  {
    "path": "src/core/servers/anjay_server_connections.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#include <inttypes.h>\n\n#include <avsystem/commons/avs_stream_net.h>\n#include <avsystem/commons/avs_utils.h>\n\n#define ANJAY_SERVERS_INTERNALS\n\n#include \"../anjay_servers_reload.h\"\n#include \"../anjay_servers_utils.h\"\n#include \"../anjay_utils_private.h\"\n#include \"../dm/anjay_query.h\"\n\n#include \"anjay_activate.h\"\n#include \"anjay_security.h\"\n#include \"anjay_server_connections.h\"\n#include \"anjay_servers_internal.h\"\n\nVISIBILITY_SOURCE_BEGIN\n\nstatic int read_binding_info(anjay_unlocked_t *anjay,\n                             anjay_ssid_t ssid,\n                             anjay_binding_mode_t *out_binding_mode,\n                             char *out_preferred_transport) {\n    anjay_uri_path_t path =\n            MAKE_RESOURCE_PATH(ANJAY_DM_OID_SERVER, ANJAY_ID_INVALID,\n                               ANJAY_DM_RID_SERVER_BINDING);\n    if (_anjay_find_server_iid(anjay, ssid, &path.ids[ANJAY_ID_IID])) {\n        anjay_log(WARNING,\n                  _(\"could not find Server instance for LwM2M server \") \"%u\",\n                  ssid);\n        return -1;\n    }\n    if (_anjay_dm_read_resource_string(anjay, &path, out_binding_mode->data,\n                                       sizeof(out_binding_mode->data))) {\n        anjay_log(WARNING,\n                  _(\"could not read binding mode for LwM2M server \") \"%u\",\n                  ssid);\n        return -1;\n    }\n    if (!anjay_binding_mode_valid(out_binding_mode->data)) {\n        anjay_log(WARNING,\n                  _(\"invalid binding mode \\\"\") \"%s\" _(\n                          \"\\\" for LwM2M server \") \"%u\",\n                  out_binding_mode->data, ssid);\n        return -1;\n    }\n#ifdef ANJAY_WITH_LWM2M11\n    char preferred_transport[2];\n    if (!_anjay_dm_read_resource_string(\n                anjay,\n                &MAKE_RESOURCE_PATH(ANJAY_DM_OID_SERVER, path.ids[ANJAY_ID_IID],\n                                    ANJAY_DM_RID_SERVER_PREFERRED_TRANSPORT),\n                preferred_transport, sizeof(preferred_transport))\n            && _anjay_binding_info_by_letter(preferred_transport[0]) != NULL) {\n        *out_preferred_transport = preferred_transport[0];\n    } else\n#endif // ANJAY_WITH_LWM2M11\n    {\n        *out_preferred_transport = '\\0';\n    }\n    return 0;\n}\n\nanjay_conn_session_token_t\n_anjay_server_primary_session_token(anjay_server_info_t *server) {\n    return _anjay_connections_get_primary_session_token(&server->connections);\n}\n\ntypedef struct {\n    const anjay_ssid_t ssid;\n    anjay_binding_mode_t *binding_mode;\n    const char preferred_transport;\n    anjay_iid_t selected_iid;\n    avs_url_t *selected_uri;\n    size_t selected_rank;\n} select_security_instance_state_t;\n\n/**\n * NOTE: *out_rank is set to one of the following:\n *\n * - 0, if transport_info matches preferred_transport\n * - [1 .. sizeof(binding_mode->data)], if transport_info matches nth letter of\n *   binding_mode->data. 1 corresponds to binding_mode->data[0].\n * - sizeof(binding_mode->data) + 1 (one more than anything possible for the\n *   above), if transport_info is applicable for UDP, but binding_mode->data\n *   does not include 'U'. See below for explanation.\n *\n * Smaller rank number is considered better.\n *\n * Additionally, if a specific transport is not online at the moment, the rank\n * is increased by an additional penalty of sizeof(binding_mode->data) + 2, so\n * that all online protocols have better rank than offline ones. We can't\n * completely eliminate offline transports at this moment, because it is not\n * considered an error if a transport is offline.\n */\nstatic int rank_uri(anjay_unlocked_t *anjay,\n                    anjay_binding_mode_t *binding_mode,\n                    char preferred_transport,\n                    const anjay_transport_info_t *transport_info,\n                    size_t *out_rank) {\n    assert(transport_info);\n    if (!_anjay_socket_transport_supported(anjay, transport_info->transport)) {\n        anjay_log(WARNING, _(\"support for protocol \") \"%s\" _(\" is not enabled\"),\n                  transport_info->uri_scheme);\n        return -1;\n    }\n    const char *rank_ptr;\n    char transport_binding =\n            _anjay_binding_info_by_transport(transport_info->transport)->letter;\n    if (transport_binding == preferred_transport) {\n        *out_rank = 0;\n    } else if ((rank_ptr = strchr(binding_mode->data,\n                                  *(uint8_t *) &transport_binding))) {\n        *out_rank = (size_t) (rank_ptr - binding_mode->data) + 1;\n    } else if (transport_binding == 'U') {\n        // According to LwM2M TS 1.1.1, 6.2.1.2. Behaviour with Current\n        // Transport Binding and Modes:\n        // > The client SHALL assume that the server supports the UDP binding\n        // > even if the server does not include UDP (\"U\") in the \"binding\"\n        // > resource of the LwM2M server object (/1/x/7).\n        *out_rank = sizeof(binding_mode->data) + 1;\n    } else {\n        anjay_log(DEBUG,\n                  _(\"protocol \") \"%s\" _(\" is not present in Binding resource\"),\n                  transport_info->uri_scheme);\n        return -1;\n    }\n    if (!_anjay_socket_transport_is_online(anjay, transport_info->transport)) {\n        *out_rank += sizeof(binding_mode->data) + 2;\n    }\n    return 0;\n}\n\nstatic void update_selected_security_instance_if_ranked_better(\n        anjay_unlocked_t *anjay,\n        select_security_instance_state_t *state,\n        anjay_iid_t iid,\n        avs_url_t **move_uri,\n        const anjay_transport_info_t *transport_info) {\n    size_t rank;\n    if (!rank_uri(anjay, state->binding_mode, state->preferred_transport,\n                  transport_info, &rank)\n            && (state->selected_iid == ANJAY_ID_INVALID\n                || rank < state->selected_rank)) {\n        // This is the first matching entry or it has better rank than the\n        // previously selected one - let's store it.\n        avs_url_t *tmp_uri = state->selected_uri;\n        state->selected_uri = *move_uri;\n        *move_uri = tmp_uri; // for cleanup below\n        state->selected_iid = iid;\n        state->selected_rank = rank;\n    }\n\n    avs_url_free(*move_uri);\n    *move_uri = NULL;\n}\n\nstatic int select_security_instance_clb(anjay_unlocked_t *anjay,\n                                        const anjay_dm_installed_object_t *obj,\n                                        anjay_iid_t iid,\n                                        void *state_) {\n    (void) obj;\n    select_security_instance_state_t *state =\n            (select_security_instance_state_t *) state_;\n    anjay_ssid_t ssid;\n    avs_url_t *uri = NULL;\n    const anjay_transport_info_t *transport_info = NULL;\n    if (_anjay_ssid_from_security_iid(anjay, iid, &ssid)\n            || ssid != state->ssid) {\n        return ANJAY_FOREACH_CONTINUE;\n    }\n\n    if (!_anjay_connection_security_generic_get_uri(anjay, iid, &uri,\n                                                    &transport_info)) {\n        update_selected_security_instance_if_ranked_better(\n                anjay, state, iid, &uri, transport_info);\n    }\n    assert(!uri);\n\n    return ANJAY_FOREACH_CONTINUE;\n}\n\nstatic int select_security_instance(anjay_unlocked_t *anjay,\n                                    anjay_ssid_t ssid,\n                                    anjay_binding_mode_t *binding_mode,\n                                    char preferred_transport,\n                                    anjay_iid_t *out_security_iid,\n                                    avs_url_t **out_uri) {\n    const anjay_dm_installed_object_t *obj =\n            _anjay_dm_find_object_by_oid(&anjay->dm, ANJAY_DM_OID_SECURITY);\n    select_security_instance_state_t state = {\n        .ssid = ssid,\n        .binding_mode = binding_mode,\n        .preferred_transport = preferred_transport,\n        .selected_iid = ANJAY_ID_INVALID,\n        .selected_uri = NULL,\n        .selected_rank = SIZE_MAX\n    };\n    int result =\n            _anjay_dm_foreach_instance(anjay, obj, select_security_instance_clb,\n                                       &state);\n    if (result) {\n        avs_url_free(state.selected_uri);\n        return result;\n    }\n    if (state.selected_iid == ANJAY_ID_INVALID) {\n        assert(!state.selected_uri);\n        anjay_log(\n                WARNING,\n                _(\"could not find Security Instance matching Server \") \"%\" PRIu16\n                        _(\" configuration\"),\n                ssid);\n        return -1;\n    }\n    *out_security_iid = state.selected_iid;\n    *out_uri = state.selected_uri;\n    return 0;\n}\n\nvoid _anjay_active_server_refresh(anjay_server_info_t *server) {\n    anjay_log(TRACE, _(\"refreshing SSID \") \"%u\", server->ssid);\n\n    // Refreshed server is not deemed explicitly disabled anymore\n    server->disabled_explicitly = false;\n\n    int result = 0;\n    anjay_iid_t security_iid = ANJAY_ID_INVALID;\n    avs_url_t *uri = NULL;\n    if (server->ssid == ANJAY_SSID_BOOTSTRAP) {\n        const anjay_transport_info_t *transport_info = NULL;\n        if ((security_iid = _anjay_find_bootstrap_security_iid(server->anjay))\n                == ANJAY_ID_INVALID) {\n            anjay_log(ERROR, _(\"could not find server Security IID\"));\n            result = -1;\n        } else if (!_anjay_connection_security_generic_get_uri(\n                           server->anjay, security_iid, &uri,\n                           &transport_info)) {\n            assert(uri);\n            assert(transport_info);\n            if ((result = avs_simple_snprintf(server->binding_mode.data,\n                                              sizeof(server->binding_mode.data),\n                                              \"%c\",\n                                              _anjay_binding_info_by_transport(\n                                                      transport_info->transport)\n                                                      ->letter))\n                    >= 0) {\n                result = 0;\n            }\n        }\n    } else {\n        char preferred_transport;\n        (void) ((result = read_binding_info(server->anjay, server->ssid,\n                                            &server->binding_mode,\n                                            &preferred_transport))\n                || (result = select_security_instance(\n                            server->anjay, server->ssid, &server->binding_mode,\n                            preferred_transport, &security_iid, &uri)));\n    }\n    if (!result) {\n        _anjay_server_connections_refresh(server, security_iid, &uri);\n    }\n    avs_url_free(uri);\n    if (result) {\n        _anjay_server_on_refreshed(server, ANJAY_SERVER_CONNECTION_OFFLINE,\n                                   avs_errno(AVS_EPROTO));\n    }\n}\n\nbool _anjay_connection_outgoing_exchanges_in_progress(\n        anjay_connection_ref_t conn_ref) {\n    assert(conn_ref.server->ssid != ANJAY_SSID_BOOTSTRAP);\n    if (conn_ref.conn_type == ANJAY_CONNECTION_PRIMARY\n            && avs_coap_exchange_id_valid(\n                       conn_ref.server->registration_exchange_state\n                               .exchange_id)) {\n        return true;\n    }\n    if (_anjay_observe_confirmable_in_delivery(conn_ref)) {\n        return true;\n    }\n#ifdef ANJAY_WITH_DOWNLOADER\n    if (_anjay_downloader_same_socket_transfer_ongoing(\n                &conn_ref.server->anjay->downloader,\n                _anjay_connection_internal_get_socket(\n                        _anjay_get_server_connection(conn_ref)))) {\n        return true;\n    }\n#endif // ANJAY_WITH_DOWNLOADER\n#ifdef ANJAY_WITH_SEND\n    if (_anjay_send_in_progress(conn_ref)) {\n        return true;\n    }\n#endif // ANJAY_WITH_SEND\n    return false;\n}\n\nstatic void cancel_exchanges(anjay_connection_ref_t conn_ref) {\n    anjay_server_connection_t *conn = _anjay_get_server_connection(conn_ref);\n#ifdef ANJAY_WITH_DOWNLOADER\n    _anjay_downloader_suspend_same_socket(&conn_ref.server->anjay->downloader,\n                                          _anjay_connection_internal_get_socket(\n                                                  conn));\n#endif // ANJAY_WITH_DOWNLOADER\n    if (conn_ref.conn_type == ANJAY_CONNECTION_PRIMARY) {\n#ifdef ANJAY_WITH_BOOTSTRAP\n        if (conn_ref.server->ssid == ANJAY_SSID_BOOTSTRAP) {\n            if (avs_coap_exchange_id_valid(\n                        conn_ref.server->anjay->bootstrap\n                                .outgoing_request_exchange_id)) {\n                avs_coap_exchange_cancel(conn->coap_ctx,\n                                         conn_ref.server->anjay->bootstrap\n                                                 .outgoing_request_exchange_id);\n            }\n        } else\n#endif // ANJAY_WITH_BOOTSTRAP\n                if (avs_coap_exchange_id_valid(\n                            conn_ref.server->registration_exchange_state\n                                    .exchange_id)) {\n            avs_coap_exchange_cancel(\n                    conn->coap_ctx,\n                    conn_ref.server->registration_exchange_state.exchange_id);\n        }\n    }\n    _anjay_observe_interrupt(conn_ref);\n#ifdef ANJAY_WITH_SEND\n    _anjay_send_interrupt(conn_ref);\n#endif // ANJAY_WITH_SEND\n}\n\nvoid _anjay_servers_interrupt_offline(anjay_unlocked_t *anjay) {\n    AVS_LIST(anjay_server_info_t) it;\n    AVS_LIST_FOREACH(it, anjay->servers) {\n        anjay_connection_type_t conn_type;\n        ANJAY_CONNECTION_TYPE_FOREACH(conn_type) {\n            anjay_connection_ref_t ref = {\n                .server = it,\n                .conn_type = conn_type\n            };\n            anjay_server_connection_t *conn = _anjay_get_server_connection(ref);\n            avs_net_socket_t *socket =\n                    _anjay_connection_internal_get_socket(conn);\n            if (socket\n                    && !_anjay_socket_transport_is_online(anjay,\n                                                          conn->transport)) {\n                cancel_exchanges(ref);\n                _anjay_observe_interrupt(ref);\n                if (conn_type == ANJAY_CONNECTION_PRIMARY) {\n                    avs_sched_del(&it->next_action_handle);\n#ifdef ANJAY_WITH_BOOTSTRAP\n                    if (it->ssid == ANJAY_SSID_BOOTSTRAP) {\n                        avs_sched_del(\n                                &anjay->bootstrap\n                                         .client_initiated_bootstrap_handle);\n                    }\n#endif // ANJAY_WITH_BOOTSTRAP\n                }\n            }\n        }\n    }\n}\n\nvoid _anjay_connection_suspend(anjay_connection_ref_t conn_ref) {\n    anjay_server_connection_t *conn = _anjay_get_server_connection(conn_ref);\n    avs_net_socket_t *socket = _anjay_connection_internal_get_socket(conn);\n    cancel_exchanges(conn_ref);\n    if (socket) {\n        avs_net_socket_shutdown(socket);\n        avs_net_socket_close(socket);\n    }\n}\n\nanjay_socket_transport_t\n_anjay_connection_transport(anjay_connection_ref_t conn_ref) {\n    anjay_server_connection_t *connection =\n            _anjay_get_server_connection(conn_ref);\n    assert(connection);\n    assert(_anjay_connection_internal_get_socket(connection));\n    return connection->transport;\n}\n\nvoid _anjay_connection_mark_stable(anjay_connection_ref_t ref) {\n    anjay_server_connection_t *connection = _anjay_get_server_connection(ref);\n    assert(connection);\n    assert(_anjay_connection_is_online(connection));\n    connection->state = ANJAY_SERVER_CONNECTION_STABLE;\n}\n\nvoid _anjay_connection_bring_online(anjay_connection_ref_t ref) {\n    anjay_server_connection_t *connection = _anjay_get_server_connection(ref);\n    (void) connection;\n    assert(connection);\n    assert(!_anjay_connection_is_online(connection));\n    assert(connection->transport != ANJAY_SOCKET_TRANSPORT_INVALID);\n    assert(_anjay_socket_transport_supported(ref.server->anjay,\n                                             connection->transport));\n    if (!_anjay_socket_transport_is_online(ref.server->anjay,\n                                           connection->transport)) {\n        anjay_log(DEBUG, _(\"transport is entering offline mode, not bringing \"\n                           \"the socket online\"));\n    } else {\n        avs_error_t err =\n                _anjay_server_connection_internal_bring_online(ref.server,\n                                                               ref.conn_type);\n        anjay_server_connection_state_t state =\n                _anjay_connection_get(&ref.server->connections,\n                                      ANJAY_CONNECTION_PRIMARY)\n                        ->state;\n        _anjay_server_on_refreshed(ref.server, state, err);\n    }\n}\n\n#ifndef ANJAY_WITHOUT_QUEUE_MODE_AUTOCLOSE\nstatic void queue_mode_close_socket(avs_sched_t *sched, const void *ref_ptr) {\n    static const long RETRY_DELAY_S = 1;\n    anjay_t *anjay_locked = _anjay_get_from_sched(sched);\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    anjay_connection_ref_t ref = *(const anjay_connection_ref_t *) ref_ptr;\n    bool skip_suspend = false;\n    if (_anjay_connection_outgoing_exchanges_in_progress(ref)) {\n        anjay_log(DEBUG, _(\"outgoing exchanges in progress, deferring socket \"\n                           \"closure for queue mode\"));\n        anjay_server_connection_t *connection =\n                _anjay_get_server_connection(ref);\n        if (connection\n                && !AVS_SCHED_DELAYED(\n                           sched, &connection->queue_mode_close_socket_clb,\n                           avs_time_duration_from_scalar(RETRY_DELAY_S,\n                                                         AVS_TIME_S),\n                           queue_mode_close_socket, &ref, sizeof(ref))) {\n            skip_suspend = true;\n        } else {\n            anjay_log(WARNING, _(\"could not delay queue mode operations, \"\n                                 \"suspending the connection now\"));\n        }\n    }\n    if (!skip_suspend) {\n        _anjay_connection_suspend(ref);\n    }\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n}\n\nvoid _anjay_connection_schedule_queue_mode_close(anjay_connection_ref_t ref) {\n    anjay_server_connection_t *connection = _anjay_get_server_connection(ref);\n    assert(connection);\n    assert(_anjay_connection_is_online(connection));\n\n    avs_sched_del(&connection->queue_mode_close_socket_clb);\n    if (ref.conn_type != ANJAY_CONNECTION_PRIMARY\n            || !ref.server->registration_info.queue_mode) {\n        return;\n    }\n\n    avs_time_duration_t delay =\n            _anjay_max_transmit_wait_for_transport(ref.server->anjay,\n                                                   connection->transport);\n\n    // see comment on field declaration for logic summary\n    if (AVS_SCHED_DELAYED(ref.server->anjay->sched,\n                          &connection->queue_mode_close_socket_clb, delay,\n                          queue_mode_close_socket, &ref, sizeof(ref))) {\n        anjay_log(ERROR, _(\"could not schedule queue mode operations\"));\n    }\n}\n#endif // ANJAY_WITHOUT_QUEUE_MODE_AUTOCLOSE\n\nconst anjay_url_t *_anjay_connection_uri(anjay_connection_ref_t ref) {\n    return &_anjay_get_server_connection(ref)->uri;\n}\n\nvoid _anjay_connections_flush_notifications(anjay_connections_t *connections) {\n    anjay_server_info_t *server =\n            AVS_CONTAINER_OF(connections, anjay_server_info_t, connections);\n    if (_anjay_server_registration_expired(server)) {\n        anjay_log(TRACE, _(\"Server has no valid registration, not flushing \"\n                           \"notifications\"));\n        return;\n    }\n\n    anjay_connection_type_t conn_type;\n    ANJAY_CONNECTION_TYPE_FOREACH(conn_type) {\n        const anjay_connection_ref_t ref = {\n            .server = server,\n            .conn_type = conn_type\n        };\n        anjay_server_connection_t *connection =\n                _anjay_connection_get(connections, ref.conn_type);\n        if (connection->needs_observe_flush\n                && _anjay_connection_is_online(connection)\n                && (server->ssid == ANJAY_SSID_BOOTSTRAP\n                    || !_anjay_observe_sched_flush(ref))) {\n            connection->needs_observe_flush = false;\n        }\n    }\n}\n\ntypedef struct {\n    const avs_coap_udp_tx_params_t *tx_params;\n    anjay_transport_set_t transport_set;\n} update_server_instance_tx_params_args_t;\n\nstatic int update_server_instance_tx_params(anjay_unlocked_t *anjay,\n                                            anjay_server_info_t *server,\n                                            void *args_) {\n    (void) anjay;\n\n    update_server_instance_tx_params_args_t *args =\n            (update_server_instance_tx_params_args_t *) args_;\n\n    anjay_connection_type_t type;\n    ANJAY_CONNECTION_TYPE_FOREACH(type) {\n        anjay_server_connection_t *conn =\n                _anjay_connection_get(&server->connections, type);\n        if (conn->coap_ctx\n                && _anjay_socket_transport_included(args->transport_set,\n                                                    conn->transport)) {\n            avs_coap_udp_ctx_set_tx_params(conn->coap_ctx, args->tx_params);\n        }\n    }\n\n    return 0;\n}\n\navs_error_t\nanjay_update_transport_tx_params(anjay_t *anjay_locked,\n                                 anjay_transport_set_t transport_set,\n                                 const avs_coap_udp_tx_params_t *tx_params) {\n    assert(anjay_locked);\n\n    if (!tx_params) {\n        anjay_log(ERROR, _(\"given transmission parameters are NULL\"));\n        return avs_errno(AVS_EINVAL);\n    } else {\n        const char *error_message;\n        if (!avs_coap_udp_tx_params_valid(tx_params, &error_message)) {\n            assert(error_message);\n            anjay_log(ERROR,\n                      _(\"UDP transmission params validation failed with the \"\n                        \"following error message: \") \"%s\",\n                      error_message);\n            return avs_errno(AVS_EINVAL);\n        }\n    }\n\n    avs_error_t err = avs_errno(AVS_EINVAL);\n\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n\n#ifdef WITH_AVS_COAP_UDP\n    if (transport_set.udp) {\n        anjay->udp_tx_params = *tx_params;\n        err = AVS_OK;\n    }\n#endif // WITH_AVS_COAP_UDP\n\n    if (avs_is_err(err)) {\n        anjay_log(ERROR, _(\"no transport for which transmission parameters \"\n                           \"could be changed was given\"));\n    } else {\n        _anjay_servers_foreach_active(\n                anjay, update_server_instance_tx_params,\n                &(update_server_instance_tx_params_args_t) {\n                    .tx_params = tx_params,\n                    .transport_set = transport_set\n                });\n    }\n\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return err;\n}\n\ntypedef struct {\n    avs_time_duration_t exchange_update_timeout;\n    anjay_transport_set_t transport_set;\n} update_server_exchange_deadline_args_t;\n\nstatic int update_server_coap_exchange_timeout(anjay_unlocked_t *anjay,\n                                               anjay_server_info_t *server,\n                                               void *args_) {\n    (void) anjay;\n\n    update_server_exchange_deadline_args_t *args =\n            (update_server_exchange_deadline_args_t *) args_;\n\n    anjay_connection_type_t type;\n    ANJAY_CONNECTION_TYPE_FOREACH(type) {\n        anjay_server_connection_t *conn =\n                _anjay_connection_get(&server->connections, type);\n        if (conn->coap_ctx\n                && _anjay_socket_transport_included(args->transport_set,\n                                                    conn->transport)) {\n            avs_coap_set_exchange_max_time(conn->coap_ctx,\n                                           args->exchange_update_timeout);\n        }\n    }\n\n    return 0;\n}\n\navs_error_t\nanjay_update_coap_exchange_timeout(anjay_t *anjay_locked,\n                                   anjay_transport_set_t transport_set,\n                                   avs_time_duration_t exchange_timeout) {\n    assert(anjay_locked);\n\n    avs_error_t err = avs_errno(AVS_EINVAL);\n\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n\n#ifdef WITH_AVS_COAP_UDP\n    if (transport_set.udp) {\n        anjay->udp_exchange_timeout = exchange_timeout;\n        err = AVS_OK;\n    }\n#endif // WITH_AVS_COAP_UDP\n#if defined(ANJAY_WITH_LWM2M11) && defined(WITH_AVS_COAP_TCP)\n    if (transport_set.tcp) {\n        anjay->tcp_exchange_timeout = exchange_timeout;\n        err = AVS_OK;\n    }\n#endif // defined(ANJAY_WITH_LWM2M11) && defined(WITH_AVS_COAP_TCP)\n\n    if (avs_is_err(err)) {\n        anjay_log(ERROR, _(\"no transport for which exchange timeout could be \"\n                           \"changed was given\"));\n    } else {\n        _anjay_servers_foreach_active(\n                anjay, update_server_coap_exchange_timeout,\n                &(update_server_exchange_deadline_args_t) {\n                    .exchange_update_timeout = exchange_timeout,\n                    .transport_set = transport_set\n                });\n    }\n\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return err;\n}\n"
  },
  {
    "path": "src/core/servers/anjay_server_connections.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ANJAY_SERVERS_SERVER_CONNECTIONS_H\n#define ANJAY_SERVERS_SERVER_CONNECTIONS_H\n\n#include \"../anjay_core.h\"\n#include \"../anjay_utils_private.h\"\n\n#if !defined(ANJAY_SERVERS_INTERNALS) && !defined(ANJAY_TEST)\n#    error \"Headers from servers/ are not meant to be included from outside\"\n#endif\n\n#include \"anjay_connections.h\"\n#include \"anjay_servers_internal.h\"\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\nstatic inline anjay_server_connection_t *\n_anjay_get_server_connection(anjay_connection_ref_t ref) {\n    assert(ref.server);\n    return _anjay_connection_get(&ref.server->connections, ref.conn_type);\n}\n\nvoid _anjay_active_server_refresh(anjay_server_info_t *server);\n\nvoid _anjay_connections_flush_notifications(anjay_connections_t *connections);\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif // ANJAY_SERVERS_SERVER_CONNECTIONS_H\n"
  },
  {
    "path": "src/core/servers/anjay_servers_internal.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#include <inttypes.h>\n\n#include <anjay_modules/anjay_time_defs.h>\n\n#include <avsystem/commons/avs_memory.h>\n\n#define ANJAY_SERVERS_INTERNALS\n\n#include \"../anjay_core.h\"\n#include \"../anjay_servers_inactive.h\"\n#include \"../anjay_servers_private.h\"\n#include \"../anjay_servers_utils.h\"\n#include \"../dm/anjay_query.h\"\n\n#include \"anjay_activate.h\"\n#include \"anjay_register.h\"\n#include \"anjay_server_connections.h\"\n#include \"anjay_servers_internal.h\"\n\nVISIBILITY_SOURCE_BEGIN\n\nvoid _anjay_server_clean_active_data(anjay_server_info_t *server) {\n    avs_sched_del(&server->next_action_handle);\n    _anjay_registration_exchange_state_cleanup(\n            &server->registration_exchange_state);\n    _anjay_connections_close(server->anjay, &server->connections);\n}\n\nvoid _anjay_server_cleanup(anjay_server_info_t *server) {\n    anjay_log(TRACE, _(\"clear_server SSID \") \"%u\", server->ssid);\n\n    _anjay_server_clean_active_data(server);\n    _anjay_registration_info_cleanup(&server->registration_info);\n}\n\n#ifndef ANJAY_WITHOUT_DEREGISTER\nvoid _anjay_servers_internal_deregister(\n        AVS_LIST(anjay_server_info_t) *servers) {\n    AVS_LIST(anjay_server_info_t) server;\n    AVS_LIST_FOREACH(server, *servers) {\n        if (_anjay_server_active(server) && server->ssid != ANJAY_SSID_BOOTSTRAP\n                && !_anjay_server_registration_expired(server)) {\n            _anjay_server_deregister(server);\n        }\n    }\n}\n#endif // ANJAY_WITHOUT_DEREGISTER\n\nvoid _anjay_servers_internal_cleanup(AVS_LIST(anjay_server_info_t) *servers) {\n    anjay_log(TRACE, _(\"cleaning up \") \"%lu\" _(\" servers\"),\n              (unsigned long) AVS_LIST_SIZE(*servers));\n\n    AVS_LIST_CLEAR(servers) {\n        _anjay_server_cleanup(*servers);\n    }\n}\n\n#ifndef ANJAY_WITHOUT_DEREGISTER\nvoid _anjay_servers_deregister(anjay_unlocked_t *anjay) {\n    _anjay_servers_internal_deregister(&anjay->servers);\n}\n#endif // ANJAY_WITHOUT_DEREGISTER\n\nvoid _anjay_servers_cleanup(anjay_unlocked_t *anjay) {\n    _anjay_servers_internal_cleanup(&anjay->servers);\n    AVS_LIST_CLEAR(&anjay->cached_public_sockets);\n}\n\nvoid _anjay_servers_cleanup_inactive_nonbootstrap(anjay_unlocked_t *anjay) {\n    AVS_LIST(anjay_server_info_t) *server_ptr;\n    AVS_LIST(anjay_server_info_t) helper;\n    AVS_LIST_DELETABLE_FOREACH_PTR(server_ptr, helper, &anjay->servers) {\n        if ((*server_ptr)->ssid != ANJAY_SSID_BOOTSTRAP\n                && !_anjay_server_active(*server_ptr)) {\n            _anjay_server_cleanup(*server_ptr);\n            AVS_LIST_DELETE(server_ptr);\n        }\n    }\n}\n\navs_coap_ctx_t *_anjay_connection_get_coap(anjay_connection_ref_t ref) {\n    assert(ref.server);\n    return _anjay_get_server_connection(ref)->coap_ctx;\n}\n\navs_net_socket_t *\n_anjay_connection_get_online_socket(anjay_connection_ref_t ref) {\n    anjay_server_connection_t *connection = _anjay_get_server_connection(ref);\n    if (!connection || !_anjay_connection_is_online(connection)) {\n        return NULL;\n    }\n    return _anjay_connection_internal_get_socket(connection);\n}\n\nbool _anjay_connection_ready_for_outgoing_message(anjay_connection_ref_t ref) {\n    // It is now possible for the socket to exist and be connected even though\n    // the server has no valid registration. This may happen during the\n    // _anjay_connection_internal_bring_online() backoff. We don't want to send\n    // notifications if we don't have a valid registration, so we treat such\n    // server as inactive for notification purposes.\n    anjay_unlocked_t *anjay = _anjay_from_server(ref.server);\n    return !_anjay_bootstrap_in_progress(anjay)\n           && _anjay_server_connection_active(ref)\n           && !_anjay_server_registration_expired(ref.server)\n           && !_anjay_server_registration_info(ref.server)->update_forced;\n}\n\nstatic int add_socket_onto_list(AVS_LIST(anjay_socket_entry_t) *tail_ptr,\n                                avs_net_socket_t *socket,\n                                anjay_socket_transport_t transport,\n                                anjay_ssid_t ssid,\n                                bool queue_mode) {\n    assert(!*tail_ptr);\n    AVS_LIST_INSERT_NEW(anjay_socket_entry_t, tail_ptr);\n    if (!*tail_ptr) {\n        _anjay_log_oom();\n        return -1;\n    }\n    (*tail_ptr)->socket = socket;\n    (*tail_ptr)->transport = transport;\n    (*tail_ptr)->ssid = ssid;\n    (*tail_ptr)->queue_mode = queue_mode;\n    return 0;\n}\n\nAVS_LIST(const anjay_socket_entry_t)\n_anjay_collect_socket_entries(anjay_unlocked_t *anjay, bool include_offline) {\n    AVS_LIST(anjay_socket_entry_t) result = NULL;\n    AVS_LIST(anjay_socket_entry_t) *tail_ptr = &result;\n\n    anjay_connection_ref_t ref;\n    AVS_LIST_FOREACH(ref.server, anjay->servers) {\n        ref.conn_type = ANJAY_CONNECTION_PRIMARY;\n        anjay_server_connection_t *conn = _anjay_get_server_connection(ref);\n        assert(conn);\n        avs_net_socket_t *socket = _anjay_connection_internal_get_socket(conn);\n        if (socket && (include_offline || _anjay_socket_is_online(socket))) {\n            if (!add_socket_onto_list(\n                        tail_ptr, socket, conn->transport, ref.server->ssid,\n                        ref.server->registration_info.queue_mode)) {\n                AVS_LIST_ADVANCE_PTR(&tail_ptr);\n            }\n        }\n    }\n\n#ifdef ANJAY_WITH_DOWNLOADER\n    _anjay_downloader_get_sockets(&anjay->downloader, tail_ptr,\n                                  include_offline);\n#endif // ANJAY_WITH_DOWNLOADER\n    return result;\n}\n\nAVS_LIST(const anjay_socket_entry_t)\nanjay_get_socket_entries(anjay_t *anjay_locked) {\n    AVS_LIST(const anjay_socket_entry_t) result = NULL;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    AVS_LIST_CLEAR(&anjay->cached_public_sockets);\n    anjay->cached_public_sockets =\n            _anjay_collect_socket_entries(anjay, /* include_offline = */ false);\n    result = anjay->cached_public_sockets;\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return result;\n}\n\nAVS_LIST(anjay_server_info_t) *\n_anjay_servers_find_insert_ptr(AVS_LIST(anjay_server_info_t) *servers,\n                               anjay_ssid_t ssid) {\n    AVS_LIST(anjay_server_info_t) *it;\n    AVS_LIST_FOREACH_PTR(it, servers) {\n        if ((*it)->ssid >= ssid) {\n            return it;\n        }\n    }\n    return it;\n}\n\nAVS_LIST(anjay_server_info_t) *\n_anjay_servers_find_ptr(AVS_LIST(anjay_server_info_t) *servers,\n                        anjay_ssid_t ssid) {\n    AVS_LIST(anjay_server_info_t) *ptr =\n            _anjay_servers_find_insert_ptr(servers, ssid);\n    if (*ptr && (*ptr)->ssid == ssid) {\n        return ptr;\n    }\n\n    anjay_log(TRACE, _(\"no server with SSID \") \"%u\", ssid);\n    return NULL;\n}\n\nanjay_server_info_t *_anjay_servers_find(anjay_unlocked_t *anjay,\n                                         anjay_ssid_t ssid) {\n    AVS_LIST(anjay_server_info_t) *ptr =\n            _anjay_servers_find_ptr(&anjay->servers, ssid);\n    return ptr ? *ptr : NULL;\n}\n\nbool _anjay_server_is_disable_scheduled(anjay_server_info_t *server) {\n    return server->next_action_handle\n           && (server->next_action\n                       == ANJAY_SERVER_NEXT_ACTION_DISABLE_WITH_TIMEOUT_FROM_DM\n               || server->next_action\n                          == ANJAY_SERVER_NEXT_ACTION_DISABLE_WITH_EXPLICIT_TIMEOUT);\n}\n\nbool _anjay_server_connection_active(anjay_connection_ref_t ref) {\n    if (_anjay_server_is_disable_scheduled(ref.server)) {\n        return false;\n    }\n    return !!_anjay_connection_internal_get_socket(\n            _anjay_get_server_connection(ref));\n}\n\nbool _anjay_server_active(anjay_server_info_t *server) {\n    if (_anjay_server_is_disable_scheduled(server)) {\n        return false;\n    }\n    anjay_connection_type_t conn_type;\n    ANJAY_CONNECTION_TYPE_FOREACH(conn_type) {\n        if (_anjay_connection_internal_get_socket(\n                    _anjay_get_server_connection((anjay_connection_ref_t) {\n                        .server = server,\n                        .conn_type = conn_type\n                    }))) {\n            return true;\n        }\n    }\n\n    return false;\n}\n\nanjay_unlocked_t *_anjay_from_server(anjay_server_info_t *server) {\n    return server->anjay;\n}\n\nanjay_ssid_t _anjay_server_ssid(anjay_server_info_t *server) {\n    return server->ssid;\n}\n\nanjay_iid_t _anjay_server_last_used_security_iid(anjay_server_info_t *server) {\n    return server->last_used_security_iid;\n}\n\nconst anjay_binding_mode_t *\n_anjay_server_binding_mode(anjay_server_info_t *server) {\n    return (const anjay_binding_mode_t *) &server->binding_mode;\n}\n\n#ifdef ANJAY_WITH_COMMUNICATION_TIMESTAMP_API\nvoid _anjay_server_set_last_communication_time(anjay_server_info_t *server) {\n    server->last_communication_time = avs_time_real_now();\n    anjay_log(TRACE,\n              _(\"Update server (SSID: \") \"%d\" _(\n                      \") last communication time to \") \"%s\",\n              server->ssid,\n              AVS_TIME_DURATION_AS_STRING(\n                      server->last_communication_time.since_real_epoch));\n}\n#endif // ANJAY_WITH_COMMUNICATION_TIMESTAMP_API\n\nint _anjay_servers_foreach_ssid(anjay_unlocked_t *anjay,\n                                anjay_servers_foreach_ssid_handler_t *handler,\n                                void *data) {\n    AVS_LIST(anjay_server_info_t) it;\n    AVS_LIST_FOREACH(it, anjay->servers) {\n        int result = handler(anjay, it->ssid, data);\n        if (result == ANJAY_FOREACH_BREAK) {\n            anjay_log(DEBUG, _(\"servers_foreach_ssid: break on \") \"%u\",\n                      it->ssid);\n            return 0;\n        } else if (result) {\n            anjay_log(WARNING,\n                      _(\"servers_foreach_ssid handler failed for \") \"%u\" _(\n                              \" (\") \"%d\" _(\")\"),\n                      it->ssid, result);\n            return result;\n        }\n    }\n\n    return 0;\n}\n\nint _anjay_servers_foreach_active(anjay_unlocked_t *anjay,\n                                  anjay_servers_foreach_handler_t *handler,\n                                  void *data) {\n    AVS_LIST(anjay_server_info_t) it;\n    AVS_LIST_FOREACH(it, anjay->servers) {\n        if (!_anjay_server_active(it)) {\n            continue;\n        }\n        int result = handler(anjay, it, data);\n        if (result == ANJAY_FOREACH_BREAK) {\n            anjay_log(DEBUG, _(\"servers_foreach_ssid: break on \") \"%u\",\n                      it->ssid);\n            return 0;\n        } else if (result) {\n            anjay_log(WARNING,\n                      _(\"servers_foreach_ssid handler failed for \") \"%u\" _(\n                              \" (\") \"%d\" _(\")\"),\n                      it->ssid, result);\n            return result;\n        }\n    }\n\n    return 0;\n}\n\n#if defined(ANJAY_WITH_LWM2M11)\nbool _anjay_bootstrap_server_exists(anjay_unlocked_t *anjay) {\n    AVS_STATIC_ASSERT(ANJAY_SSID_BOOTSTRAP == UINT16_MAX,\n                      bootstrap_server_is_last);\n    AVS_LIST(anjay_server_info_t) candidate = AVS_LIST_TAIL(anjay->servers);\n    return candidate && candidate->ssid == ANJAY_SSID_BOOTSTRAP;\n}\n#endif // defined(ANJAY_WITH_LWM2M11) || defined(ANJAY_WITH_EST)\n\nstatic void server_next_action_job(avs_sched_t *sched, const void *server_ptr) {\n    anjay_t *anjay_locked = _anjay_get_from_sched(sched);\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    anjay_server_info_t *server = *(anjay_server_info_t *const *) server_ptr;\n    switch (server->next_action) {\n    case ANJAY_SERVER_NEXT_ACTION_COMMUNICATION_ERROR:\n        _anjay_server_on_failure(server, \"not reachable\");\n        break;\n\n    case ANJAY_SERVER_NEXT_ACTION_DISABLE_WITH_TIMEOUT_FROM_DM:\n        _anjay_disable_server_with_timeout_from_dm_sync(server);\n        break;\n\n    case ANJAY_SERVER_NEXT_ACTION_DISABLE_WITH_EXPLICIT_TIMEOUT:\n        _anjay_disable_server_with_explicit_timeout_sync(server);\n        break;\n\n    case ANJAY_SERVER_NEXT_ACTION_SEND_UPDATE:\n        server->registration_info.update_forced = true;\n        _anjay_active_server_refresh(server);\n        break;\n\n    case ANJAY_SERVER_NEXT_ACTION_REFRESH:\n        if (server->ssid != ANJAY_SSID_BOOTSTRAP\n                && _anjay_bootstrap_in_progress(anjay)) {\n            anjay_log(TRACE,\n                      _(\"Bootstrap is in progress, not refreshing server \"\n                        \"SSID \") \"%\" PRIu16,\n                      server->ssid);\n            // NOTE: Bootstrap Finish will trigger\n            // _anjay_schedule_reload_servers(), server will be refreshed then.\n        } else {\n            _anjay_active_server_refresh(server);\n        }\n        break;\n    default:\n        AVS_UNREACHABLE(\"Invalid next_action enum value\");\n    }\n\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n}\n\nint _anjay_server_reschedule_next_action(\n        anjay_server_info_t *server,\n        avs_time_duration_t delay,\n        anjay_server_next_action_t next_action) {\n    if (avs_time_duration_less(delay, AVS_TIME_DURATION_ZERO)) {\n        // Ensure that the job won't execute before already scheduled jobs\n        delay = AVS_TIME_DURATION_ZERO;\n    }\n    int result;\n    if (server->next_action_handle) {\n        result = AVS_RESCHED_DELAYED(&server->next_action_handle, delay);\n    } else {\n        result = AVS_SCHED_DELAYED(server->anjay->sched,\n                                   &server->next_action_handle, delay,\n                                   server_next_action_job, &server,\n                                   sizeof(server));\n    }\n    if (!result) {\n        server->next_action = next_action;\n    }\n    return result;\n}\n"
  },
  {
    "path": "src/core/servers/anjay_servers_internal.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ANJAY_SERVERS_SERVERS_H\n#define ANJAY_SERVERS_SERVERS_H\n\n#include <anjay/core.h>\n\n#include \"../anjay_servers_private.h\"\n\n#include \"anjay_connections.h\"\n\n#if !defined(ANJAY_SERVERS_INTERNALS) && !defined(ANJAY_TEST)\n#    error \"Headers from servers/ are not meant to be included from outside\"\n#endif\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\ntypedef struct {\n    avs_coap_exchange_id_t exchange_id;\n    anjay_lwm2m_version_t attempted_version;\n    anjay_update_parameters_t new_params;\n#ifdef ANJAY_WITH_LWM2M11\n    bool lwm2m11_queue_mode;\n#endif // ANJAY_WITH_LWM2M11\n} anjay_registration_async_exchange_state_t;\n\ntypedef enum {\n    /**\n     * Handles connectivity failures, which involves scheduling reconnection,\n     * etc. Scheduled by _anjay_server_on_server_communication_error(), which\n     * is called in a number of error handling paths.\n     */\n    ANJAY_SERVER_NEXT_ACTION_COMMUNICATION_ERROR,\n\n    /**\n     * Disables the server and schedules its reactivation after the delay\n     * specified by the /1/x/5 resource. Scheduled by the anjay_disable_server()\n     * public API.\n     */\n    ANJAY_SERVER_NEXT_ACTION_DISABLE_WITH_TIMEOUT_FROM_DM,\n\n    /**\n     * Disables the server and schedules its reactivation after the delay\n     * specified by the anjay_server_info_t::reactivate_time field. Scheduled\n     * by the _anjay_schedule_disable_server_with_explicit_timeout_unlocked()\n     * API.\n     */\n    ANJAY_SERVER_NEXT_ACTION_DISABLE_WITH_EXPLICIT_TIMEOUT,\n\n    /**\n     * Updates the registration. Makes sense only for active servers. Scheduled\n     * either immediately (normally via anjay_schedule_registration_update()),\n     * when Update is forced, or delayed by \"lifetime minus eta\", scheduled\n     * after a successful Register or Update operation.\n     */\n    ANJAY_SERVER_NEXT_ACTION_SEND_UPDATE,\n\n    /**\n     * Scheduled from _anjay_schedule_refresh_server(), calls\n     * _anjay_active_server_refresh(). Used in many places, including\n     * _anjay_server_sched_activate(), _anjay_schedule_reload_servers()\n     * _anjay_schedule_registration_update_unlocked(), as well as in\n     * start_send_exchange() (to force getting out of the queue mode, if\n     * applicable). See the code and documentation for those functions for\n     * details.\n     */\n    ANJAY_SERVER_NEXT_ACTION_REFRESH\n} anjay_server_next_action_t;\n\n/**\n * Information about a known LwM2M server.\n *\n * The server may be considered \"active\" or \"inactive\". A server is \"active\" if\n * it has any socket created - not necessarily connected an online, but created.\n * The active state is normal for servers. Here are the circumstances in which\n * inactive server entries may exist:\n *\n * - Freshly after creation - all server entries are created in the inactive\n *   state, and activated afterwards.\n * - After activation failure - if e.g. there was an error connecting the\n *   socket.\n * - Administratively disabled - one may call anjay_disable_server() or\n *   anjay_disable_server_with_timeout(); this shall normally done only in\n *   reaction to an Execute operation on the Disable resource in the Server\n *   object.\n * - When Re-Registration to the server is necessary - it will be deactivated\n *   and activated again for Registration, as initialize_active_server() is the\n *   only place in the codebase that may order sending Register message.\n * - When the library is ordered to enter into Offline mode using\n *   anjay_enter_offline() - all servers are deactivated then.\n *\n * See documentation to _anjay_schedule_reload_servers() for details on\n * activation and deactivation flow.\n */\nstruct anjay_server_info_struct {\n    anjay_unlocked_t *anjay;\n\n    anjay_ssid_t ssid; // or ANJAY_SSID_BOOTSTRAP\n\n    anjay_iid_t last_used_security_iid;\n\n    /**\n     * Scheduler jobs that shall be executed for the given server are scheduled\n     * using this handle. The specific action to perform is controlled by the\n     * <c>next_action</c> field.\n     */\n    avs_sched_handle_t next_action_handle;\n\n    /**\n     * Action to be performed by the job scheduled in <c>next_action_handle</c>.\n     * See @ref anjay_server_next_action_t for specific actions.\n     */\n    anjay_server_next_action_t next_action;\n\n    /**\n     * Administratively configured binding mode, cached from the data model.\n     */\n    anjay_binding_mode_t binding_mode;\n\n    /**\n     * State of all connections to remote servers possible for a given server.\n     * The anjay_connections_t type wraps the actual server connections,\n     * information about which is currently the \"primary\" one, and manages the\n     * connection state flow.\n     *\n     * This object is also used for determining whether the server is active or\n     * not (as sockets are stored inside, see the main docstring for\n     * anjay_server_info_t for details), and also holds non-transient data that\n     * is of no use when the server is inactive, but is preserved between\n     * activation attempts (so that session resumption works across\n     * activations).\n     */\n    anjay_connections_t connections;\n\n    /**\n     * Information about current registration status of the server. See the\n     * docs for _anjay_server_registration_info() and\n     * _anjay_server_update_registration_info() for details.\n     */\n    anjay_registration_info_t registration_info;\n\n    anjay_registration_async_exchange_state_t registration_exchange_state;\n\n    /**\n     * Specifies the time at which the reactivate job shall be executed.\n     *\n     * If Anjay enters offline mode, we delete all such jobs (because we don't\n     * want servers to be activated during offline mode) - but thanks to this\n     * value, we can reschedule activation at appropriate time even after\n     * exiting offline mode.\n     *\n     * This logic has been first introduced in internal diff D7056, which\n     * limited the number of places in code where Registers and Updates may\n     * happen, to deliver more consistent behaviour of those. Previously,\n     * enter_offline_job() did not completely deactivate the servers, but just\n     * suspended (closed) their sockets, and\n     * _anjay_server_ensure_valid_registration() was called directly from\n     * reload_active_server() (as the servers exiting from offline modes were\n     * considered active). This yielded inconsistent behaviour of Update error\n     * handling - Updates generated in this way were not degenerating to\n     * Registers immediately.\n     */\n    avs_time_real_t reactivate_time;\n\n    /**\n     * True if the server is explicitly disabled by:\n     * - LwM2M Server executing Disable resource on Server object\n     * - user calling @ref anjay_disable_server\n     * - user calling @ref anjay_disable_server_with_timeout\n     *\n     * This allows to find out whether server's <c>reactivate_time</c>\n     * should be loaded from core persistence or not.\n     */\n    bool disabled_explicitly;\n\n    /**\n     * True if, and only if, the last activation attempt was unsuccessful, for\n     * whatever reason - not necessarily those included in num_icmp_failures\n     * logic.\n     */\n    bool refresh_failed;\n\n    /**\n     * Number of attempted (potentially) failed registrations. It is incremented\n     * in send_register() (and also _anjay_server_on_refreshed() in case of\n     * connection error if connection_error_is_registration_failure is enabled),\n     * then compared (if non-zero) against \"Communication Retry Count\" resource\n     * in _anjay_server_on_failure(). When the registration succeeds or in case\n     * of a connection error (when connection_error_is_registration_failure is\n     * disabled), it is reset to 0.\n     */\n    uint32_t registration_attempts;\n\n    /**\n     * Number of completely performed Communication Retry Sequences.\n     */\n    uint32_t registration_sequences_performed;\n\n#ifdef ANJAY_WITH_COMMUNICATION_TIMESTAMP_API\n    /**\n     * Stores the time when the last communication with a given server was done.\n     * Note that some messages don't get any confirmation from the server so the\n     * point in time this variable holds is an approximation.\n     */\n    avs_time_real_t last_communication_time;\n#endif // ANJAY_WITH_COMMUNICATION_TIMESTAMP_API\n\n#ifdef ANJAY_WITH_CONN_STATUS_API\n    /**\n     * Stores current server connection status.\n     */\n    anjay_server_conn_status_t connection_status;\n\n    /**\n     * Indicates that the server is in the process of suspending. It is checked\n     * by the ANJAY_SERVER_NEXT_ACTION_DISABLE_WITH_EXPLICIT_TIMEOUT and the\n     * ANJAY_SERVER_NEXT_ACTION_DISABLE_WITH_TIMEOUT_FROM_DM actions to\n     * see if it was called due to /1/x/4 resource execution, function\n     * anjay_disable_server call or function anjay_disable_server_with_timeout\n     * call.\n     */\n    bool suspending;\n\n    /**\n     * Indicates that re-registration will be carried out due to an error\n     * (ANJAY_REGISTRATION_ERROR_TIMEOUT or ANJAY_REGISTRATION_ERROR_REJECTED)\n     * during update operation. If the reregistration process fails, this flag\n     * keeps its value.\n     */\n    bool reregistration;\n#endif // ANJAY_WITH_CONN_STATUS_API\n};\n\n#ifndef ANJAY_WITHOUT_DEREGISTER\nvoid _anjay_servers_internal_deregister(AVS_LIST(anjay_server_info_t) *servers);\n#else // ANJAY_WITHOUT_DEREGISTER\n#    define _anjay_servers_internal_deregister(Servers) ((void) (Servers))\n#endif // ANJAY_WITHOUT_DEREGISTER\n\nvoid _anjay_servers_internal_cleanup(AVS_LIST(anjay_server_info_t) *servers);\n\nvoid _anjay_server_clean_active_data(anjay_server_info_t *server);\n\n/**\n * Cleans up server data. Does not send De-Register message.\n */\nvoid _anjay_server_cleanup(anjay_server_info_t *server);\n\nAVS_LIST(anjay_server_info_t) *\n_anjay_servers_find_insert_ptr(AVS_LIST(anjay_server_info_t) *servers,\n                               anjay_ssid_t ssid);\n\nAVS_LIST(anjay_server_info_t) *\n_anjay_servers_find_ptr(AVS_LIST(anjay_server_info_t) *servers,\n                        anjay_ssid_t ssid);\n\nint _anjay_server_reschedule_next_action(\n        anjay_server_info_t *server,\n        avs_time_duration_t delay,\n        anjay_server_next_action_t next_action);\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif // ANJAY_SERVERS_SERVERS_H\n"
  },
  {
    "path": "src/modules/access_control/anjay_access_control_handlers.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#ifdef ANJAY_WITH_MODULE_ACCESS_CONTROL\n\n#    include <inttypes.h>\n#    include <string.h>\n\n#    include <anjay_modules/anjay_access_utils.h>\n#    include <anjay_modules/anjay_notify.h>\n#    include <avsystem/commons/avs_sorted_set.h>\n\n#    include \"anjay_mod_access_control.h\"\n\nVISIBILITY_SOURCE_BEGIN\n\nstatic access_control_instance_t *\nfind_instance(access_control_t *access_control, anjay_iid_t iid) {\n    if (!access_control->last_accessed_instance\n            || access_control->last_accessed_instance->iid != iid) {\n        access_control->last_accessed_instance = NULL;\n        access_control_instance_t *it;\n        AVS_LIST_FOREACH(it, access_control->current.instances) {\n            if (it->iid >= iid) {\n                if (it->iid == iid) {\n                    access_control->last_accessed_instance = it;\n                }\n                break;\n            }\n        }\n    }\n    return access_control->last_accessed_instance;\n}\n\nstatic int ac_list_instances(anjay_unlocked_t *anjay,\n                             obj_ptr_t obj_ptr,\n                             anjay_unlocked_dm_list_ctx_t *ctx) {\n    (void) anjay;\n    access_control_t *access_control =\n            _anjay_access_control_from_obj_ptr(obj_ptr);\n    if (!access_control) {\n        return ANJAY_ERR_INTERNAL;\n    }\n    AVS_LIST(access_control_instance_t) it;\n    AVS_LIST_FOREACH(it, access_control->current.instances) {\n        _anjay_dm_emit_unlocked(ctx, it->iid);\n    }\n    return 0;\n}\n\nstatic int\nac_instance_reset(anjay_unlocked_t *anjay, obj_ptr_t obj_ptr, anjay_iid_t iid) {\n    (void) anjay;\n    access_control_t *access_control =\n            _anjay_access_control_from_obj_ptr(obj_ptr);\n    access_control_instance_t *inst = find_instance(access_control, iid);\n    if (!inst) {\n        return ANJAY_ERR_NOT_FOUND;\n    }\n    AVS_LIST_CLEAR(&inst->acl);\n    inst->has_acl = false;\n    inst->owner = 0;\n    access_control->needs_validation = true;\n    _anjay_access_control_mark_modified(access_control);\n    return 0;\n}\n\nstatic int ac_instance_create(anjay_unlocked_t *anjay,\n                              const anjay_dm_installed_object_t obj_ptr,\n                              anjay_iid_t iid) {\n    (void) anjay;\n    access_control_t *access_control =\n            _anjay_access_control_from_obj_ptr(obj_ptr);\n    AVS_LIST(access_control_instance_t) new_instance =\n            AVS_LIST_NEW_ELEMENT(access_control_instance_t);\n    if (!new_instance) {\n        _anjay_log_oom();\n        return ANJAY_ERR_INTERNAL;\n    }\n    *new_instance = (access_control_instance_t) {\n        .iid = iid,\n        .target = {\n            .oid = 0,\n            .iid = -1\n        },\n        .owner = ANJAY_SSID_BOOTSTRAP,\n        .has_acl = false,\n        .acl = NULL\n    };\n    int retval = _anjay_access_control_add_instance(access_control,\n                                                    new_instance, NULL);\n    if (retval) {\n        AVS_LIST_CLEAR(&new_instance);\n    }\n    access_control->needs_validation = true;\n    _anjay_access_control_mark_modified(access_control);\n    return retval;\n}\n\nstatic int ac_instance_remove(anjay_unlocked_t *anjay,\n                              const anjay_dm_installed_object_t obj_ptr,\n                              anjay_iid_t iid) {\n    (void) anjay;\n    access_control_t *access_control =\n            _anjay_access_control_from_obj_ptr(obj_ptr);\n    AVS_LIST(access_control_instance_t) *it;\n    AVS_LIST_FOREACH_PTR(it, &access_control->current.instances) {\n        if ((*it)->iid == iid) {\n            if (access_control->last_accessed_instance\n                    && access_control->last_accessed_instance->iid == iid) {\n                access_control->last_accessed_instance = NULL;\n            }\n            AVS_LIST_CLEAR(&(*it)->acl);\n            AVS_LIST_DELETE(it);\n            _anjay_access_control_mark_modified(access_control);\n            return 0;\n        } else if ((*it)->iid > iid) {\n            break;\n        }\n    }\n    return ANJAY_ERR_NOT_FOUND;\n}\n\nstatic int ac_list_resources(anjay_unlocked_t *anjay,\n                             obj_ptr_t obj_ptr,\n                             anjay_iid_t iid,\n                             anjay_unlocked_dm_resource_list_ctx_t *ctx) {\n    (void) anjay;\n    access_control_instance_t *inst =\n            find_instance(_anjay_access_control_from_obj_ptr(obj_ptr), iid);\n\n    _anjay_dm_emit_res_unlocked(ctx, ANJAY_DM_RID_ACCESS_CONTROL_OID,\n                                ANJAY_DM_RES_R, ANJAY_DM_RES_PRESENT);\n    _anjay_dm_emit_res_unlocked(ctx, ANJAY_DM_RID_ACCESS_CONTROL_OIID,\n                                ANJAY_DM_RES_R, ANJAY_DM_RES_PRESENT);\n    _anjay_dm_emit_res_unlocked(ctx, ANJAY_DM_RID_ACCESS_CONTROL_ACL,\n                                ANJAY_DM_RES_RWM,\n                                (inst && inst->has_acl) ? ANJAY_DM_RES_PRESENT\n                                                        : ANJAY_DM_RES_ABSENT);\n    _anjay_dm_emit_res_unlocked(ctx, ANJAY_DM_RID_ACCESS_CONTROL_OWNER,\n                                ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT);\n    return 0;\n}\n\nstatic int ac_resource_read(anjay_unlocked_t *anjay,\n                            obj_ptr_t obj_ptr,\n                            anjay_iid_t iid,\n                            anjay_rid_t rid,\n                            anjay_riid_t riid,\n                            anjay_unlocked_output_ctx_t *ctx) {\n    (void) anjay;\n    (void) obj_ptr;\n    access_control_instance_t *inst =\n            find_instance(_anjay_access_control_from_obj_ptr(obj_ptr), iid);\n    if (!inst) {\n        return ANJAY_ERR_NOT_FOUND;\n    }\n\n    switch (rid) {\n    case ANJAY_DM_RID_ACCESS_CONTROL_OID:\n        assert(riid == ANJAY_ID_INVALID);\n        return _anjay_ret_i64_unlocked(ctx, (int32_t) inst->target.oid);\n    case ANJAY_DM_RID_ACCESS_CONTROL_OIID:\n        assert(riid == ANJAY_ID_INVALID);\n        return _anjay_ret_i64_unlocked(ctx, (int32_t) inst->target.iid);\n    case ANJAY_DM_RID_ACCESS_CONTROL_ACL: {\n        acl_entry_t *it;\n        AVS_LIST_FOREACH(it, inst->acl) {\n            if (it->ssid >= riid) {\n                break;\n            }\n        }\n        if (!it || it->ssid != riid) {\n            return ANJAY_ERR_NOT_FOUND;\n        }\n        return _anjay_ret_i64_unlocked(ctx, it->mask);\n    }\n    case ANJAY_DM_RID_ACCESS_CONTROL_OWNER:\n        assert(riid == ANJAY_ID_INVALID);\n        return _anjay_ret_i64_unlocked(ctx, (int32_t) inst->owner);\n    default:\n        AVS_UNREACHABLE(\"Read called on unknown Access Control resource\");\n        return ANJAY_ERR_NOT_IMPLEMENTED;\n    }\n}\n\nstatic int write_to_acl_array(AVS_LIST(acl_entry_t) *acl,\n                              anjay_ssid_t ssid,\n                              anjay_unlocked_input_ctx_t *ctx) {\n    int32_t mask;\n    if (_anjay_get_i32_unlocked(ctx, &mask)) {\n        return ANJAY_ERR_INTERNAL;\n    }\n    AVS_LIST(acl_entry_t) *it;\n    AVS_LIST_FOREACH_PTR(it, acl) {\n        if ((*it)->ssid >= ssid) {\n            if ((*it)->ssid == ssid) {\n                (*it)->mask = (anjay_access_mask_t) mask;\n            }\n            break;\n        }\n    }\n\n    if (!*it || (*it)->ssid != ssid) {\n        AVS_LIST(acl_entry_t) new_entry = AVS_LIST_NEW_ELEMENT(acl_entry_t);\n        if (!new_entry) {\n            return ANJAY_ERR_INTERNAL;\n        }\n        *new_entry = (acl_entry_t) {\n            .ssid = ssid,\n            .mask = (anjay_access_mask_t) mask\n        };\n        AVS_LIST_INSERT(it, new_entry);\n    }\n    return 0;\n}\n\nstatic int ac_resource_write(anjay_unlocked_t *anjay,\n                             obj_ptr_t obj_ptr,\n                             anjay_iid_t iid,\n                             anjay_rid_t rid,\n                             anjay_riid_t riid,\n                             anjay_unlocked_input_ctx_t *ctx) {\n    (void) anjay;\n    access_control_t *access_control =\n            _anjay_access_control_from_obj_ptr(obj_ptr);\n    access_control_instance_t *inst = find_instance(access_control, iid);\n    if (!inst) {\n        return ANJAY_ERR_NOT_FOUND;\n    }\n\n    switch (rid) {\n    case ANJAY_DM_RID_ACCESS_CONTROL_OID: {\n        assert(riid == ANJAY_ID_INVALID);\n        int32_t oid;\n        int retval = _anjay_get_i32_unlocked(ctx, &oid);\n        if (retval) {\n            return retval;\n        } else if (!_anjay_access_control_target_oid_valid(oid)) {\n            return ANJAY_ERR_BAD_REQUEST;\n        }\n        inst->target.oid = (anjay_oid_t) oid;\n        access_control->needs_validation = true;\n        _anjay_access_control_mark_modified(access_control);\n        return 0;\n    }\n    case ANJAY_DM_RID_ACCESS_CONTROL_OIID: {\n        assert(riid == ANJAY_ID_INVALID);\n        int32_t oiid;\n        int retval = _anjay_get_i32_unlocked(ctx, &oiid);\n        if (retval) {\n            return retval;\n        } else if (oiid < 0 || oiid > UINT16_MAX) {\n            return ANJAY_ERR_BAD_REQUEST;\n        }\n        inst->target.iid = (anjay_iid_t) oiid;\n        access_control->needs_validation = true;\n        _anjay_access_control_mark_modified(access_control);\n        return 0;\n    }\n    case ANJAY_DM_RID_ACCESS_CONTROL_ACL: {\n        int retval = write_to_acl_array(&inst->acl, riid, ctx);\n        if (!retval) {\n            inst->has_acl = true;\n            access_control->needs_validation = true;\n            _anjay_access_control_mark_modified(access_control);\n        }\n        return retval;\n    }\n    case ANJAY_DM_RID_ACCESS_CONTROL_OWNER: {\n        assert(riid == ANJAY_ID_INVALID);\n        int32_t ssid;\n        int retval = _anjay_get_i32_unlocked(ctx, &ssid);\n        if (retval) {\n            return retval;\n        } else if (ssid <= 0 || ssid > ANJAY_SSID_BOOTSTRAP) {\n            return ANJAY_ERR_BAD_REQUEST;\n        }\n        inst->owner = (anjay_ssid_t) ssid;\n        access_control->needs_validation = true;\n        _anjay_access_control_mark_modified(access_control);\n        return 0;\n    }\n    default:\n        AVS_UNREACHABLE(\"Write called on unknown Access Control resource\");\n        return ANJAY_ERR_NOT_FOUND;\n    }\n}\n\nstatic int ac_resource_reset(anjay_unlocked_t *anjay,\n                             const anjay_dm_installed_object_t obj_ptr,\n                             anjay_iid_t iid,\n                             anjay_rid_t rid) {\n    (void) anjay;\n    access_control_t *access_control =\n            _anjay_access_control_from_obj_ptr(obj_ptr);\n    access_control_instance_t *inst = find_instance(access_control, iid);\n    if (!inst) {\n        return ANJAY_ERR_NOT_FOUND;\n    }\n\n    assert(rid == ANJAY_DM_RID_ACCESS_CONTROL_ACL);\n    (void) rid;\n    AVS_LIST_CLEAR(&inst->acl);\n    inst->has_acl = true;\n    access_control->needs_validation = true;\n    _anjay_access_control_mark_modified(access_control);\n    return 0;\n}\n\n#    ifdef ANJAY_WITH_LWM2M12\nstatic int\nac_resource_instance_remove(anjay_unlocked_t *anjay,\n                            const anjay_dm_installed_object_t obj_ptr,\n                            anjay_iid_t iid,\n                            anjay_rid_t rid,\n                            anjay_riid_t riid) {\n    (void) anjay;\n    access_control_t *access_control =\n            _anjay_access_control_from_obj_ptr(obj_ptr);\n    access_control_instance_t *inst = find_instance(access_control, iid);\n    if (!inst) {\n        return ANJAY_ERR_NOT_FOUND;\n    }\n\n    assert(rid == ANJAY_DM_RID_ACCESS_CONTROL_ACL);\n    (void) rid;\n\n    AVS_LIST(acl_entry_t) *it;\n    AVS_LIST_FOREACH_PTR(it, &inst->acl) {\n        if ((*it)->ssid >= riid) {\n            break;\n        }\n    }\n    if (!it || !*it || (*it)->ssid != riid) {\n        AVS_UNREACHABLE(\"Attempted to remove a non-existent Resource Instance\");\n        return ANJAY_ERR_NOT_FOUND;\n    }\n    AVS_LIST_DELETE(it);\n    inst->has_acl = true;\n    access_control->needs_validation = true;\n    _anjay_access_control_mark_modified(access_control);\n    return 0;\n}\n#    endif // ANJAY_WITH_LWM2M12\n\nstatic int ac_list_resource_instances(anjay_unlocked_t *anjay,\n                                      obj_ptr_t obj_ptr,\n                                      anjay_iid_t iid,\n                                      anjay_rid_t rid,\n                                      anjay_unlocked_dm_list_ctx_t *ctx) {\n    (void) anjay;\n    access_control_t *access_control =\n            _anjay_access_control_from_obj_ptr(obj_ptr);\n    access_control_instance_t *inst = find_instance(access_control, iid);\n    if (!inst) {\n        return ANJAY_ERR_NOT_FOUND;\n    }\n\n    switch (rid) {\n    case ANJAY_DM_RID_ACCESS_CONTROL_ACL: {\n        acl_entry_t *it;\n        AVS_LIST_FOREACH(it, inst->acl) {\n            _anjay_dm_emit_unlocked(ctx, it->ssid);\n        }\n        return 0;\n    }\n    default:\n        AVS_UNREACHABLE(\n                \"Attempted to list instances in a single-instance resource\");\n        return ANJAY_ERR_INTERNAL;\n    }\n}\n\nstatic int ac_transaction_begin(anjay_unlocked_t *anjay, obj_ptr_t obj_ptr) {\n    (void) anjay;\n    access_control_t *ac = _anjay_access_control_from_obj_ptr(obj_ptr);\n    assert(!ac->in_transaction);\n    if (_anjay_access_control_clone_state(&ac->saved_state, &ac->current)) {\n        _anjay_log_oom();\n        return ANJAY_ERR_INTERNAL;\n    }\n    ac->in_transaction = true;\n    return 0;\n}\n\nstatic int anjay_ssid_cmp(const void *left, const void *right) {\n    return *(const anjay_ssid_t *) left - *(const anjay_ssid_t *) right;\n}\n\nstatic int add_ssid(AVS_SORTED_SET(anjay_ssid_t) ssids_list,\n                    anjay_ssid_t ssid) {\n    // here it is actually more likely for the SSID to be already present\n    // so we use find-then-insert logic to avoid unnecessary allocations\n    AVS_SORTED_SET_ELEM(anjay_ssid_t) elem =\n            AVS_SORTED_SET_FIND(ssids_list, &ssid);\n    if (!elem) {\n        if (!(elem = AVS_SORTED_SET_ELEM_NEW(anjay_ssid_t))) {\n            _anjay_log_oom();\n            return -1;\n        }\n        *elem = ssid;\n        if (AVS_SORTED_SET_INSERT(ssids_list, elem) != elem) {\n            AVS_UNREACHABLE(\"Internal error: cannot add tree element\");\n        }\n    }\n    return 0;\n}\n\n/**\n * Validates that <c>ssid</c> can be used as a key (RIID) in the ACL - it needs\n * to either reference a valid server, or be equal to @ref ANJAY_SSID_ANY (0).\n */\nint _anjay_access_control_validate_ssid(anjay_unlocked_t *anjay,\n                                        anjay_ssid_t ssid) {\n    return (ssid != ANJAY_SSID_BOOTSTRAP\n            && (ssid == ANJAY_SSID_ANY || _anjay_dm_ssid_exists(anjay, ssid)))\n                   ? 0\n                   : -1;\n}\n\nstatic int ac_transaction_validate(anjay_unlocked_t *anjay, obj_ptr_t obj_ptr) {\n    access_control_t *access_control =\n            _anjay_access_control_from_obj_ptr(obj_ptr);\n    assert(access_control->in_transaction);\n    int result = 0;\n    anjay_acl_ref_validation_ctx_t validation_ctx =\n            _anjay_acl_ref_validation_ctx_new();\n    AVS_SORTED_SET(anjay_ssid_t) ssids_used = NULL;\n    if (access_control->needs_validation) {\n        if (!(ssids_used = AVS_SORTED_SET_NEW(anjay_ssid_t, anjay_ssid_cmp))) {\n            _anjay_log_oom();\n            goto finish;\n        }\n        access_control_instance_t *inst;\n        result = ANJAY_ERR_BAD_REQUEST;\n        AVS_LIST_FOREACH(inst, access_control->current.instances) {\n            if (!_anjay_access_control_target_oid_valid(inst->target.oid)\n                    || !_anjay_access_control_target_iid_valid(inst->target.iid)\n                    || _anjay_acl_ref_validate_inst_ref(\n                               anjay, &validation_ctx, inst->target.oid,\n                               (anjay_iid_t) inst->target.iid)\n                    || (inst->owner != ANJAY_SSID_BOOTSTRAP\n                        && add_ssid(ssids_used, inst->owner))) {\n                ac_log(WARNING,\n                       _(\"Validation failed for target: \") \"/%\" PRIu16\n                                                           \"/%\" PRId32,\n                       inst->target.oid, inst->target.iid);\n                goto finish;\n            }\n            acl_entry_t *acl;\n            AVS_LIST_FOREACH(acl, inst->acl) {\n                if (add_ssid(ssids_used, acl->ssid)) {\n                    goto finish;\n                }\n            }\n        }\n        AVS_SORTED_SET_DELETE(&ssids_used) {\n            if (_anjay_access_control_validate_ssid(anjay, **ssids_used)) {\n                ac_log(WARNING,\n                       _(\"Validation failed: invalid SSID: \") \"%\" PRIu16,\n                       **ssids_used);\n                goto finish;\n            }\n        }\n        result = 0;\n        access_control->needs_validation = false;\n    }\nfinish:\n    _anjay_acl_ref_validation_ctx_cleanup(&validation_ctx);\n    AVS_SORTED_SET_DELETE(&ssids_used);\n    return result;\n}\n\nstatic int ac_transaction_commit(anjay_unlocked_t *anjay, obj_ptr_t obj_ptr) {\n    (void) anjay;\n    access_control_t *ac = _anjay_access_control_from_obj_ptr(obj_ptr);\n    assert(ac->in_transaction);\n    _anjay_access_control_clear_state(&ac->saved_state);\n    ac->needs_validation = false;\n    ac->in_transaction = false;\n    return 0;\n}\n\nstatic int ac_transaction_rollback(anjay_unlocked_t *anjay, obj_ptr_t obj_ptr) {\n    (void) anjay;\n    access_control_t *ac = _anjay_access_control_from_obj_ptr(obj_ptr);\n    assert(ac->in_transaction);\n    _anjay_access_control_clear_state(&ac->current);\n    ac->current = ac->saved_state;\n    memset(&ac->saved_state, 0, sizeof(ac->saved_state));\n    ac->needs_validation = false;\n    ac->in_transaction = false;\n    ac->last_accessed_instance = NULL;\n    return 0;\n}\n\nstatic void ac_delete(void *access_control_) {\n    access_control_t *access_control = (access_control_t *) access_control_;\n    _anjay_access_control_clear_state(&access_control->current);\n    _anjay_access_control_clear_state(&access_control->saved_state);\n    // NOTE: access_control itself will be freed when cleaning the objects list\n}\n\nvoid anjay_access_control_purge(anjay_t *anjay_locked) {\n    assert(anjay_locked);\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    access_control_t *ac = _anjay_access_control_get(anjay);\n    if (!ac) {\n        ac_log(ERROR, _(\"Access Control object is not registered\"));\n    } else {\n        _anjay_access_control_clear_state(&ac->current);\n        _anjay_access_control_mark_modified(ac);\n        ac->last_accessed_instance = NULL;\n        ac->needs_validation = false;\n        if (_anjay_notify_instances_changed_unlocked(\n                    anjay, ANJAY_DM_OID_ACCESS_CONTROL)) {\n            ac_log(WARNING, _(\"Could not schedule access control instance \"\n                              \"changes notifications\"));\n        }\n    }\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n}\n\nbool anjay_access_control_is_modified(anjay_t *anjay_locked) {\n    assert(anjay_locked);\n    bool result = false;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    access_control_t *ac = _anjay_access_control_get(anjay);\n    if (!ac) {\n        ac_log(ERROR, _(\"Access Control object is not registered\"));\n    } else if (ac->in_transaction) {\n        result = ac->saved_state.modified_since_persist;\n    } else {\n        result = ac->current.modified_since_persist;\n    }\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return result;\n}\n\nstatic const anjay_unlocked_dm_object_def_t ACCESS_CONTROL = {\n    .oid = ANJAY_DM_OID_ACCESS_CONTROL,\n    .handlers = {\n        .list_instances = ac_list_instances,\n        .instance_reset = ac_instance_reset,\n        .instance_create = ac_instance_create,\n        .instance_remove = ac_instance_remove,\n        .list_resources = ac_list_resources,\n        .resource_read = ac_resource_read,\n        .resource_write = ac_resource_write,\n        .resource_reset = ac_resource_reset,\n        .list_resource_instances = ac_list_resource_instances,\n        .transaction_begin = ac_transaction_begin,\n        .transaction_validate = ac_transaction_validate,\n        .transaction_commit = ac_transaction_commit,\n        .transaction_rollback = ac_transaction_rollback\n#    ifdef ANJAY_WITH_LWM2M12\n        ,\n        .resource_instance_remove = ac_resource_instance_remove\n#    endif // ANJAY_WITH_LWM2M12\n    }\n};\n\nint anjay_access_control_install(anjay_t *anjay_locked) {\n    if (!anjay_locked) {\n        ac_log(ERROR, _(\"ANJAY object must not be NULL\"));\n        return -1;\n    }\n    int result = -1;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    AVS_LIST(access_control_t) access_control =\n            AVS_LIST_NEW_ELEMENT(access_control_t);\n    if (access_control) {\n        access_control->obj_def = &ACCESS_CONTROL;\n        _anjay_dm_installed_object_init_unlocked(&access_control->obj_def_ptr,\n                                                 &access_control->obj_def);\n        if (!_anjay_dm_module_install(anjay, ac_delete, access_control)) {\n            _ANJAY_ASSERT_INSTALLED_OBJECT_IS_FIRST_FIELD(access_control_t,\n                                                          obj_def_ptr);\n            AVS_LIST(anjay_dm_installed_object_t) entry =\n                    &access_control->obj_def_ptr;\n            if (_anjay_register_object_unlocked(anjay, &entry)) {\n                result = _anjay_dm_module_uninstall(anjay, ac_delete);\n                assert(!result);\n                result = -1;\n            } else {\n                result = 0;\n            }\n        }\n        if (result) {\n            AVS_LIST_CLEAR(&access_control);\n        }\n    }\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return result;\n}\n\naccess_control_t *_anjay_access_control_get(anjay_unlocked_t *anjay) {\n    return (access_control_t *) _anjay_dm_module_get_arg(anjay, ac_delete);\n}\n\n#endif // ANJAY_WITH_MODULE_ACCESS_CONTROL\n"
  },
  {
    "path": "src/modules/access_control/anjay_access_control_persistence.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#ifdef ANJAY_WITH_MODULE_ACCESS_CONTROL\n\n#    ifdef AVS_COMMONS_WITH_AVS_PERSISTENCE\n#        include <avsystem/commons/avs_persistence.h>\n#    endif // AVS_COMMONS_WITH_AVS_PERSISTENCE\n\n#    include <anjay/access_control.h>\n\n#    include \"anjay_mod_access_control.h\"\n\n#    include <string.h>\n\nVISIBILITY_SOURCE_BEGIN\n\n#    ifdef AVS_COMMONS_WITH_AVS_PERSISTENCE\n\nstatic avs_error_t handle_acl_entry(avs_persistence_context_t *ctx,\n                                    void *element_,\n                                    void *user_data) {\n    (void) user_data;\n    acl_entry_t *element = (acl_entry_t *) element_;\n    avs_error_t err;\n    (void) (avs_is_err((err = avs_persistence_u16(ctx, &element->mask)))\n            || avs_is_err((err = avs_persistence_u16(ctx, &element->ssid))));\n    return err;\n}\n\nstatic void cleanup_acl_entry(void *element) {\n    // no resources allocated in acl_entry_t\n    (void) element;\n}\n\nstatic avs_error_t handle_acl(avs_persistence_context_t *ctx,\n                              access_control_instance_t *inst) {\n    avs_error_t err = avs_persistence_bool(ctx, &inst->has_acl);\n    if (avs_is_ok(err) && inst->has_acl) {\n        err = avs_persistence_list(ctx, (AVS_LIST(void) *) &inst->acl,\n                                   sizeof(*inst->acl), handle_acl_entry, NULL,\n                                   cleanup_acl_entry);\n    }\n    return err;\n}\n\nstatic avs_error_t persist_instance(avs_persistence_context_t *ctx,\n                                    void *element_,\n                                    void *user_data) {\n    (void) user_data;\n    access_control_instance_t *element = (access_control_instance_t *) element_;\n    anjay_iid_t target_iid = (anjay_iid_t) element->target.iid;\n    if (target_iid != element->target.iid) {\n        return avs_errno(AVS_EINVAL);\n    }\n    avs_error_t err;\n    (void) (avs_is_err((err = avs_persistence_u16(ctx, &element->target.oid)))\n            || avs_is_err((err = avs_persistence_u16(ctx, &element->iid)))\n            || avs_is_err((err = avs_persistence_u16(ctx, &target_iid)))\n            || avs_is_err((err = avs_persistence_u16(ctx, &element->owner)))\n            || avs_is_err((err = handle_acl(ctx, element))));\n    return err;\n}\n\nstatic bool is_object_registered(anjay_unlocked_t *anjay, anjay_oid_t oid) {\n    return oid != ANJAY_DM_OID_SECURITY\n           && _anjay_dm_find_object_by_oid(_anjay_get_dm(anjay), oid) != NULL;\n}\n\nstatic avs_error_t restore_instance(access_control_instance_t *out_instance,\n                                    avs_persistence_context_t *ctx) {\n    anjay_iid_t target_iid = 0;\n    avs_error_t err;\n    (void) (avs_is_err((err = avs_persistence_u16(ctx, &out_instance->iid)))\n            || avs_is_err((err = avs_persistence_u16(ctx, &target_iid)))\n            || avs_is_err(\n                       (err = avs_persistence_u16(ctx, &out_instance->owner)))\n            || avs_is_err((err = handle_acl(ctx, out_instance))));\n    if (avs_is_ok(err)) {\n        out_instance->target.iid = target_iid;\n    }\n    return err;\n}\n\nstatic avs_error_t\nrestore_instances(anjay_unlocked_t *anjay,\n                  AVS_LIST(access_control_instance_t) *instances_ptr,\n                  avs_persistence_context_t *restore_ctx) {\n    uint32_t count;\n    avs_error_t err = avs_persistence_u32(restore_ctx, &count);\n    if (avs_is_err(err)) {\n        return err;\n    }\n    if (count > UINT16_MAX) {\n        return avs_errno(AVS_EBADMSG);\n    }\n    AVS_LIST(access_control_instance_t) *tail = instances_ptr;\n    while (count--) {\n        access_control_instance_t instance;\n        memset(&instance, 0, sizeof(instance));\n        if (avs_is_err((err = avs_persistence_u16(restore_ctx,\n                                                  &instance.target.oid)))\n                || avs_is_err(\n                           (err = restore_instance(&instance, restore_ctx)))) {\n            return err;\n        }\n\n        if (!is_object_registered(anjay, instance.target.oid)) {\n            AVS_LIST_CLEAR(&instance.acl);\n        } else {\n            AVS_LIST(access_control_instance_t) entry =\n                    AVS_LIST_NEW_ELEMENT(access_control_instance_t);\n            if (!entry) {\n                _anjay_log_oom();\n                return avs_errno(AVS_ENOMEM);\n            }\n            *entry = instance;\n            AVS_LIST_INSERT(tail, entry);\n            AVS_LIST_ADVANCE_PTR(&tail);\n        }\n    }\n    return AVS_OK;\n}\n\nstatic avs_error_t\nrestore(anjay_unlocked_t *anjay, access_control_t *ac, avs_stream_t *in) {\n    avs_persistence_context_t restore_ctx =\n            avs_persistence_restore_context_create(in);\n    access_control_state_t state = { NULL, false };\n    avs_error_t err = restore_instances(anjay, &state.instances, &restore_ctx);\n    if (avs_is_err(err)) {\n        _anjay_access_control_clear_state(&state);\n        return err;\n    }\n    _anjay_access_control_clear_state(&ac->current);\n    ac->current = state;\n    ac->last_accessed_instance = NULL;\n    return AVS_OK;\n}\n\nstatic const char MAGIC[] = { 'A', 'C', 'O', '\\1' };\n\navs_error_t anjay_access_control_persist(anjay_t *anjay_locked,\n                                         avs_stream_t *out) {\n    avs_error_t err = avs_errno(AVS_EINVAL);\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    access_control_t *ac = _anjay_access_control_get(anjay);\n    if (!ac) {\n        ac_log(ERROR, _(\"Access Control not installed in this Anjay object\"));\n        err = avs_errno(AVS_EBADF);\n    } else if (avs_is_ok((err = avs_stream_write(out, MAGIC, sizeof(MAGIC))))) {\n        avs_persistence_context_t ctx =\n                avs_persistence_store_context_create(out);\n        AVS_LIST(access_control_instance_t) *list_ptr =\n                ac->in_transaction ? &ac->saved_state.instances\n                                   : &ac->current.instances;\n        err = avs_persistence_list(&ctx, (AVS_LIST(void) *) list_ptr,\n                                   sizeof(**list_ptr), persist_instance, NULL,\n                                   NULL);\n        if (avs_is_ok(err)) {\n            ac_log(INFO, _(\"Access Control state persisted\"));\n            _anjay_access_control_clear_modified(ac);\n        }\n    }\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return err;\n}\n\navs_error_t anjay_access_control_restore(anjay_t *anjay_locked,\n                                         avs_stream_t *in) {\n    avs_error_t err = avs_errno(AVS_EINVAL);\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    err = avs_errno(AVS_EBADF);\n    char magic_header[sizeof(MAGIC)];\n    access_control_t *ac = _anjay_access_control_get(anjay);\n    if (!ac) {\n        ac_log(ERROR, _(\"Access Control not installed in this Anjay object\"));\n    } else if (ac->in_transaction) {\n        ac_log(ERROR, _(\"Cannot restore Access Control state while the object \"\n                        \"is in transaction\"));\n    } else if (avs_is_err((err = avs_stream_read_reliably(\n                                   in, magic_header, sizeof(magic_header))))) {\n        ac_log(WARNING, _(\"magic constant not found\"));\n    } else if (memcmp(magic_header, MAGIC, sizeof(MAGIC))) {\n        ac_log(WARNING, _(\"header magic constant mismatch\"));\n        err = avs_errno(AVS_EBADMSG);\n    } else if (avs_is_ok((err = restore(anjay, ac, in)))) {\n        _anjay_access_control_clear_modified(ac);\n        ac_log(INFO, _(\"Access Control state restored\"));\n    }\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return err;\n}\n\n#        ifdef ANJAY_TEST\n#            include \"tests/modules/access_control/persistence.c\"\n#        endif // ANJAY_TEST\n\n#    else // AVS_COMMONS_WITH_AVS_PERSISTENCE\n\navs_error_t anjay_access_control_persist(anjay_t *anjay, avs_stream_t *out) {\n    (void) anjay;\n    (void) out;\n    ac_log(ERROR, _(\"Persistence not compiled in\"));\n    return avs_errno(AVS_ENOTSUP);\n}\n\navs_error_t anjay_access_control_restore(anjay_t *anjay, avs_stream_t *in) {\n    (void) anjay;\n    (void) in;\n    ac_log(ERROR, _(\"Persistence not compiled in\"));\n    return avs_errno(AVS_ENOTSUP);\n}\n\n#    endif // AVS_COMMONS_WITH_AVS_PERSISTENCE\n\n#endif // ANJAY_WITH_MODULE_ACCESS_CONTROL\n"
  },
  {
    "path": "src/modules/access_control/anjay_mod_access_control.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#ifdef ANJAY_WITH_MODULE_ACCESS_CONTROL\n\n#    include <inttypes.h>\n\n#    include \"anjay_mod_access_control.h\"\n\nVISIBILITY_SOURCE_BEGIN\n\n//// HELPERS ///////////////////////////////////////////////////////////////////\naccess_control_t *_anjay_access_control_from_obj_ptr(obj_ptr_t obj_ptr) {\n    return AVS_CONTAINER_OF(_anjay_dm_installed_object_get_unlocked(&obj_ptr),\n                            access_control_t, obj_def);\n}\n\nstatic void\nac_instances_cleanup(AVS_LIST(access_control_instance_t) *instance_ptr) {\n    AVS_LIST_CLEAR(instance_ptr) {\n        AVS_LIST_CLEAR(&(*instance_ptr)->acl);\n    }\n}\n\nvoid _anjay_access_control_clear_state(access_control_state_t *state) {\n    ac_instances_cleanup(&state->instances);\n    state->modified_since_persist = false;\n}\n\nint _anjay_access_control_clone_state(access_control_state_t *dest,\n                                      const access_control_state_t *src) {\n    assert(!dest->instances);\n    AVS_LIST(access_control_instance_t) *dest_tail = &dest->instances;\n    AVS_LIST(access_control_instance_t) src_inst;\n    AVS_LIST_FOREACH(src_inst, src->instances) {\n        AVS_LIST(access_control_instance_t) dest_inst =\n                AVS_LIST_NEW_ELEMENT(access_control_instance_t);\n        if (!dest_inst) {\n            goto error;\n        }\n        AVS_LIST_INSERT(dest_tail, dest_inst);\n        AVS_LIST_ADVANCE_PTR(&dest_tail);\n        *dest_inst = *src_inst;\n        dest_inst->acl = NULL;\n        AVS_LIST(acl_entry_t) *dest_acl_tail = &dest_inst->acl;\n        AVS_LIST(acl_entry_t) src_acl;\n        AVS_LIST_FOREACH(src_acl, src_inst->acl) {\n            AVS_LIST(acl_entry_t) dest_acl = AVS_LIST_NEW_ELEMENT(acl_entry_t);\n            if (!dest_acl) {\n                goto error;\n            }\n            AVS_LIST_INSERT(dest_acl_tail, dest_acl);\n            AVS_LIST_ADVANCE_PTR(&dest_acl_tail);\n            *dest_acl = *src_acl;\n        }\n    }\n    dest->modified_since_persist = src->modified_since_persist;\n    return 0;\nerror:\n    _anjay_access_control_clear_state(dest);\n    return -1;\n}\n\nstatic int add_instances_without_iids(\n        access_control_t *access_control,\n        AVS_LIST(access_control_instance_t) *instances_to_move,\n        anjay_notify_queue_t *out_dm_changes) {\n    AVS_LIST(access_control_instance_t) *insert_ptr =\n            &access_control->current.instances;\n    anjay_iid_t proposed_iid = 0;\n    while (*instances_to_move && proposed_iid < ANJAY_ID_INVALID) {\n        assert((*instances_to_move)->iid == ANJAY_ID_INVALID);\n        if (!*insert_ptr || proposed_iid < (*insert_ptr)->iid) {\n            if (out_dm_changes) {\n                int result = _anjay_notify_queue_instance_created(\n                        out_dm_changes,\n                        &MAKE_INSTANCE_PATH(ANJAY_DM_OID_ACCESS_CONTROL,\n                                            proposed_iid));\n                if (result) {\n                    return result;\n                }\n            }\n            AVS_LIST_INSERT(insert_ptr, AVS_LIST_DETACH(instances_to_move));\n            (*insert_ptr)->iid = proposed_iid;\n        }\n        // proposed_iid cannot possibly be GREATER than (*insert_ptr)->iid\n        assert(proposed_iid == (*insert_ptr)->iid);\n        ++proposed_iid;\n        AVS_LIST_ADVANCE_PTR(&insert_ptr);\n    }\n\n    if (*instances_to_move) {\n        ac_log(ERROR, _(\"no free IIDs left\"));\n        return -1;\n    }\n    return 0;\n}\n\nint _anjay_access_control_add_instance(\n        access_control_t *access_control,\n        AVS_LIST(access_control_instance_t) instance,\n        anjay_notify_queue_t *out_dm_changes) {\n    assert(!AVS_LIST_NEXT(instance));\n    if (instance->iid == ANJAY_ID_INVALID) {\n        return add_instances_without_iids(access_control, &instance,\n                                          out_dm_changes);\n    }\n\n    AVS_LIST(access_control_instance_t) *ptr;\n    AVS_LIST_FOREACH_PTR(ptr, &access_control->current.instances) {\n        if ((*ptr)->iid == instance->iid) {\n            ac_log(WARNING,\n                   _(\"element with IID == \") \"%\" PRIu16 _(\" already exists\"),\n                   instance->iid);\n            return -1;\n        } else if ((*ptr)->iid > instance->iid) {\n            break;\n        }\n    }\n    int result = 0;\n    if (out_dm_changes) {\n        result = _anjay_notify_queue_instance_created(\n                out_dm_changes, &MAKE_INSTANCE_PATH(ANJAY_DM_OID_ACCESS_CONTROL,\n                                                    instance->iid));\n    }\n    if (!result) {\n        AVS_LIST_INSERT(ptr, instance);\n    }\n    return result;\n}\n\nstatic int\nac_commit_new_instance(anjay_unlocked_t *anjay,\n                       access_control_t *ac,\n                       AVS_LIST(access_control_instance_t) ac_instance) {\n    int result = _anjay_notify_instances_changed_unlocked(\n            anjay, ANJAY_DM_OID_ACCESS_CONTROL);\n    if (result) {\n        ac_log(ERROR,\n               _(\"error while calling anjay_notify_instances_changed()\"));\n        return result;\n    }\n    anjay_notify_queue_t dm_changes = NULL;\n    if (!(result = _anjay_access_control_add_instance(ac, ac_instance,\n                                                      &dm_changes))) {\n        assert(AVS_LIST_SIZE(dm_changes) == 1);\n        assert(AVS_LIST_SIZE(dm_changes->instance_set_changes.known_added_iids)\n               == 1);\n        assert(!dm_changes->resources_changed);\n        _anjay_access_control_mark_modified(ac);\n        _anjay_notify_instance_created(\n                anjay, dm_changes->oid,\n                *dm_changes->instance_set_changes.known_added_iids);\n        _anjay_notify_clear_queue(&dm_changes);\n    }\n    return result;\n}\n\nstatic bool target_instance_reachable(anjay_unlocked_t *anjay,\n                                      anjay_oid_t oid,\n                                      anjay_iid_t iid) {\n    if (!_anjay_access_control_target_oid_valid(oid)\n            || !_anjay_access_control_target_iid_valid(iid)) {\n        return false;\n    }\n    obj_ptr_t *target_obj =\n            _anjay_dm_find_object_by_oid(_anjay_get_dm(anjay), oid);\n    if (!target_obj) {\n        return false;\n    }\n    return iid == ANJAY_ID_INVALID\n           || _anjay_dm_instance_present(anjay, target_obj, iid) > 0;\n}\n\nAVS_LIST(access_control_instance_t)\n_anjay_access_control_create_missing_ac_instance(const acl_target_t *target) {\n    AVS_LIST(access_control_instance_t) aco_instance =\n            AVS_LIST_NEW_ELEMENT(access_control_instance_t);\n    if (!aco_instance) {\n        return NULL;\n    }\n\n    *aco_instance = (access_control_instance_t) {\n        .iid = ANJAY_ID_INVALID,\n        .target = *target,\n        .owner = ANJAY_SSID_BOOTSTRAP,\n        .has_acl = true,\n        .acl = NULL\n    };\n    return aco_instance;\n}\n\nstatic AVS_LIST(access_control_instance_t)\ncreate_missing_ac_instance_with_validation(anjay_unlocked_t *anjay,\n                                           anjay_oid_t oid,\n                                           anjay_iid_t iid) {\n    if (!target_instance_reachable(anjay, oid, iid)) {\n        ac_log(WARNING,\n               _(\"cannot set ACL: object instance \") \"/%\" PRIu16 \"/%\" PRIu16 _(\n                       \" does not exist\"),\n               oid, iid);\n        return NULL;\n    }\n    AVS_LIST(access_control_instance_t) result =\n            _anjay_access_control_create_missing_ac_instance(\n                    &(const acl_target_t) { oid, iid });\n    if (!result) {\n        ac_log(WARNING,\n               _(\"cannot set ACL: Access Control instance for \") \"/%u/%u\" _(\n                       \" does not exist and it could not be created\"),\n               oid, iid);\n    }\n    return result;\n}\n\nstatic access_control_instance_t *\nfind_ac_instance(access_control_t *ac, anjay_oid_t oid, anjay_iid_t iid) {\n    AVS_LIST(access_control_instance_t) it;\n    AVS_LIST_FOREACH(it, ac->current.instances) {\n        if (it->target.oid == oid && it->target.iid == iid) {\n            return it;\n        }\n    }\n\n    return NULL;\n}\n\nstatic int set_acl_in_instance(anjay_unlocked_t *anjay,\n                               access_control_instance_t *ac_instance,\n                               anjay_ssid_t ssid,\n                               anjay_access_mask_t access_mask) {\n    AVS_LIST(acl_entry_t) *insert_ptr;\n    AVS_LIST_FOREACH_PTR(insert_ptr, &ac_instance->acl) {\n        if ((*insert_ptr)->ssid >= ssid) {\n            break;\n        }\n    }\n\n    if (!*insert_ptr || (*insert_ptr)->ssid != ssid) {\n        if (_anjay_access_control_validate_ssid(anjay, ssid)) {\n            ac_log(WARNING,\n                   _(\"cannot set ACL: Server with SSID==\") \"%\" PRIu16 _(\n                           \" does not exist\"),\n                   ssid);\n            return -1;\n        }\n\n        AVS_LIST(acl_entry_t) new_entry = AVS_LIST_NEW_ELEMENT(acl_entry_t);\n        if (!new_entry) {\n            _anjay_log_oom();\n            return -1;\n        }\n\n        AVS_LIST_INSERT(insert_ptr, new_entry);\n        ac_instance->has_acl = true;\n        new_entry->ssid = ssid;\n    }\n\n    (*insert_ptr)->mask = access_mask;\n    return 0;\n}\n\nstatic int set_acl(anjay_unlocked_t *anjay,\n                   access_control_t *ac,\n                   anjay_oid_t oid,\n                   anjay_iid_t iid,\n                   anjay_ssid_t ssid,\n                   anjay_access_mask_t access_mask) {\n    bool ac_instance_needs_inserting = false;\n    AVS_LIST(access_control_instance_t) ac_instance =\n            find_ac_instance(ac, oid, iid);\n    if (!ac_instance) {\n        ac_instance =\n                create_missing_ac_instance_with_validation(anjay, oid, iid);\n        if (!ac_instance) {\n            return -1;\n        }\n        ac_instance_needs_inserting = true;\n    }\n\n    int result = set_acl_in_instance(anjay, ac_instance, ssid, access_mask);\n    if (!ac_instance_needs_inserting) {\n        if (!result) {\n            _anjay_access_control_mark_modified(ac);\n            _anjay_notify_changed_unlocked(anjay, ANJAY_DM_OID_ACCESS_CONTROL,\n                                           ac_instance->iid,\n                                           ANJAY_DM_RID_ACCESS_CONTROL_ACL);\n        }\n        return result;\n    }\n    if (!result) {\n        result = ac_commit_new_instance(anjay, ac, ac_instance);\n    }\n    if (result) {\n        ac_instances_cleanup(&ac_instance);\n    }\n    return result;\n}\n\nint anjay_access_control_set_acl(anjay_t *anjay_locked,\n                                 anjay_oid_t oid,\n                                 anjay_iid_t iid,\n                                 anjay_ssid_t ssid,\n                                 anjay_access_mask_t access_mask) {\n    int result = -1;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    access_control_t *access_control = _anjay_access_control_get(anjay);\n    if (!access_control) {\n        ac_log(ERROR, _(\"Access Control not installed in this Anjay object\"));\n    } else if (ssid == ANJAY_SSID_BOOTSTRAP) {\n        ac_log(ERROR,\n               _(\"cannot set ACL: SSID = \") \"%u\" _(\" is a reserved value\"),\n               ssid);\n    } else if ((access_mask & ANJAY_ACCESS_MASK_FULL) != access_mask) {\n        ac_log(ERROR, _(\"cannot set ACL: invalid permission mask\"));\n    } else if (iid != ANJAY_ID_INVALID\n               && (access_mask & ANJAY_ACCESS_MASK_CREATE)) {\n        ac_log(ERROR,\n               _(\"cannot set ACL: Create permission makes no sense for \"\n                 \"Object Instances\"));\n    } else if (iid == ANJAY_ID_INVALID\n               && (access_mask & ANJAY_ACCESS_MASK_CREATE) != access_mask) {\n        ac_log(ERROR,\n               _(\"cannot set ACL: only Create permission makes sense for \"\n                 \"creation instance\"));\n    } else {\n        result = set_acl(anjay, access_control, oid, iid, ssid, access_mask);\n    }\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return result;\n}\n\nstatic int ac_set_owner_unlocked(anjay_unlocked_t *anjay,\n                                 access_control_t *ac,\n                                 anjay_oid_t target_oid,\n                                 anjay_iid_t target_iid,\n                                 anjay_ssid_t owner_ssid,\n                                 anjay_iid_t *inout_acl_iid) {\n    if (owner_ssid == ANJAY_SSID_ANY) {\n        ac_log(ERROR, _(\"Cannot set ACL owner: SSID = 0 is a reserved value\"));\n        return -1;\n    }\n\n    bool ac_instance_needs_inserting = false;\n    AVS_LIST(access_control_instance_t) ac_instance =\n            find_ac_instance(ac, target_oid, target_iid);\n    if (ac_instance && inout_acl_iid && *inout_acl_iid != ANJAY_ID_INVALID\n            && *inout_acl_iid != ac_instance->iid) {\n        ac_log(ERROR,\n               _(\"Cannot set ACL Instance \") \"%\" PRIu16 _(\n                       \": conflicting instance \") \"%\" PRIu16,\n               *inout_acl_iid, ac_instance->iid);\n        *inout_acl_iid = ac_instance->iid;\n        return -1;\n    }\n\n    if (!ac_instance) {\n        ac_instance =\n                create_missing_ac_instance_with_validation(anjay, target_oid,\n                                                           target_iid);\n        if (!ac_instance) {\n            return -1;\n        }\n        ac_instance_needs_inserting = true;\n        if (inout_acl_iid) {\n            ac_instance->iid = *inout_acl_iid;\n        }\n    }\n    if (owner_ssid != ac_instance->owner) {\n        if (owner_ssid != ANJAY_SSID_BOOTSTRAP\n                && _anjay_access_control_validate_ssid(anjay, owner_ssid)) {\n            if (ac_instance_needs_inserting) {\n                ac_instances_cleanup(&ac_instance);\n            }\n            ac_log(WARNING,\n                   _(\"cannot set ACL owner: Server with SSID==\") \"%\" PRIu16 _(\n                           \" does not exist\"),\n                   owner_ssid);\n            return -1;\n        }\n        ac_instance->owner = owner_ssid;\n    }\n    int result = 0;\n    if (!ac_instance_needs_inserting) {\n        _anjay_access_control_mark_modified(ac);\n        _anjay_notify_changed_unlocked(anjay, ANJAY_DM_OID_ACCESS_CONTROL,\n                                       ac_instance->iid,\n                                       ANJAY_DM_RID_ACCESS_CONTROL_OWNER);\n    } else if ((result = ac_commit_new_instance(anjay, ac, ac_instance))) {\n        ac_instances_cleanup(&ac_instance);\n    }\n    if (!result && inout_acl_iid) {\n        *inout_acl_iid = ac_instance->iid;\n    }\n    return result;\n}\n\nint anjay_access_control_set_owner(anjay_t *anjay_locked,\n                                   anjay_oid_t target_oid,\n                                   anjay_iid_t target_iid,\n                                   anjay_ssid_t owner_ssid,\n                                   anjay_iid_t *inout_acl_iid) {\n    int result = -1;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    access_control_t *ac = _anjay_access_control_get(anjay);\n    if (!ac) {\n        ac_log(ERROR, _(\"Access Control not installed in this Anjay object\"));\n    } else {\n        result = ac_set_owner_unlocked(anjay, ac, target_oid, target_iid,\n                                       owner_ssid, inout_acl_iid);\n    }\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return result;\n}\n\n#    ifdef ANJAY_TEST\n#        include \"tests/modules/access_control/access_control.c\"\n#    endif // ANJAY_TEST\n\n#endif // ANJAY_WITH_MODULE_ACCESS_CONTROL\n"
  },
  {
    "path": "src/modules/access_control/anjay_mod_access_control.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef MOD_ACCESS_CONTROL_H\n#define MOD_ACCESS_CONTROL_H\n\n#include <assert.h>\n\n#include <avsystem/commons/avs_stream_membuf.h>\n\n#include <anjay/access_control.h>\n\n#include <anjay_modules/anjay_dm_utils.h>\n#include <anjay_modules/anjay_notify.h>\n#include <anjay_modules/anjay_utils_core.h>\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\n#define ac_log(...) _anjay_log(access_control, __VA_ARGS__)\n\ntypedef struct {\n    anjay_access_mask_t mask;\n    anjay_ssid_t ssid;\n} acl_entry_t;\n\ntypedef struct {\n    anjay_oid_t oid;\n    int32_t iid; // negative means \"not set yet\"; must be set before commit\n} acl_target_t;\n\ntypedef struct {\n    anjay_iid_t iid;\n    acl_target_t target;\n    anjay_ssid_t owner;\n    bool has_acl;\n    AVS_LIST(acl_entry_t) acl;\n} access_control_instance_t;\n\ntypedef struct {\n    AVS_LIST(access_control_instance_t) instances;\n    bool modified_since_persist;\n} access_control_state_t;\n\ntypedef struct {\n    anjay_dm_installed_object_t obj_def_ptr;\n    const anjay_unlocked_dm_object_def_t *obj_def;\n    access_control_state_t current;\n    access_control_state_t saved_state;\n    bool in_transaction;\n    access_control_instance_t *last_accessed_instance;\n    bool needs_validation;\n    bool sync_in_progress;\n} access_control_t;\n\nstatic inline void _anjay_access_control_mark_modified(access_control_t *repr) {\n    repr->current.modified_since_persist = true;\n}\n\nstatic inline void\n_anjay_access_control_clear_modified(access_control_t *repr) {\n    repr->current.modified_since_persist = false;\n}\n\ntypedef const anjay_dm_installed_object_t obj_ptr_t;\n\naccess_control_t *\n_anjay_access_control_from_obj_ptr(const anjay_dm_installed_object_t obj_ptr);\n\naccess_control_t *_anjay_access_control_get(anjay_unlocked_t *anjay);\n\nvoid _anjay_access_control_clear_state(access_control_state_t *state);\n\nint _anjay_access_control_clone_state(access_control_state_t *dest,\n                                      const access_control_state_t *src);\n\nint _anjay_access_control_validate_ssid(anjay_unlocked_t *anjay,\n                                        anjay_ssid_t ssid);\n\nint _anjay_access_control_add_instance(\n        access_control_t *access_control,\n        AVS_LIST(access_control_instance_t) instance,\n        anjay_notify_queue_t *out_dm_changes);\n\nAVS_LIST(access_control_instance_t)\n_anjay_access_control_create_missing_ac_instance(const acl_target_t *target);\n\nstatic inline bool _anjay_access_control_target_oid_valid(int32_t oid) {\n    return oid >= 1 && oid != ANJAY_DM_OID_ACCESS_CONTROL && oid < UINT16_MAX;\n}\n\nstatic inline bool _anjay_access_control_target_iid_valid(int32_t iid) {\n    // checks whether iid is within valid range for anjay_iid_t; otherwise\n    // (canonically, iid == -1), it would mean that it is not present\n    return iid == (anjay_iid_t) iid;\n}\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif /* MOD_ACCESS_CONTROL_H */\n"
  },
  {
    "path": "src/modules/advanced_fw_update/anjay_advanced_fw_update.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#ifdef ANJAY_WITH_MODULE_ADVANCED_FW_UPDATE\n\n#    include <inttypes.h>\n\n#    include <avsystem/commons/avs_log.h>\n#    include <avsystem/commons/avs_stream_membuf.h>\n#    include <avsystem/commons/avs_url.h>\n\n#    include <avsystem/coap/code.h>\n\n#    include <anjay/dm.h>\n\n#    ifdef ANJAY_WITH_DOWNLOADER\n#        include <anjay/download.h>\n#    endif // ANJAY_WITH_DOWNLOADER\n\n#    ifdef ANJAY_WITH_SEND\n#        include <anjay/lwm2m_send.h>\n#    endif // ANJAY_WITH_SEND\n\n#    include <anjay/advanced_fw_update.h>\n#    include <anjay_modules/anjay_io_utils.h>\n#    include <anjay_modules/anjay_sched.h>\n#    include <anjay_modules/anjay_utils_core.h>\n#    include <anjay_modules/dm/anjay_modules.h>\n\n#    define fw_log(level, ...) avs_log(advanced_fw_update, level, __VA_ARGS__)\n#    define _(Arg) AVS_DISPOSABLE_LOG(Arg)\n\n#    define ADV_FW_RES_PACKAGE 0\n#    define ADV_FW_RES_PACKAGE_URI 1\n#    define ADV_FW_RES_UPDATE 2\n#    define ADV_FW_RES_STATE 3\n#    define ADV_FW_RES_UPDATE_RESULT 5\n#    define ADV_FW_RES_PKG_NAME 6\n#    define ADV_FW_RES_PKG_VERSION 7\n#    define ADV_FW_RES_UPDATE_PROTOCOL_SUPPORT 8\n#    define ADV_FW_RES_UPDATE_DELIVERY_METHOD 9\n#    define ADV_FW_RES_CANCEL 10\n#    define ADV_FW_RES_SEVERITY 11\n#    define ADV_FW_RES_LAST_STATE_CHANGE_TIME 12\n#    define ADV_FW_RES_MAX_DEFER_PERIOD 13\n#    define ADV_FW_RES_COMPONENT_NAME 14\n#    define ADV_FW_RES_CURRENT_VERSION 15\n#    define ADV_FW_RES_LINKED_INSTANCES 16\n#    define ADV_FW_RES_CONFLICTING_INSTANCES 17\n\nVISIBILITY_SOURCE_BEGIN\n\ntypedef struct {\n    const anjay_advanced_fw_update_handlers_t *handlers;\n    void *arg;\n    anjay_advanced_fw_update_state_t state;\n} advanced_fw_user_state_t;\n\ntypedef struct {\n    anjay_iid_t iid;\n\n    const char *component_name;\n\n    advanced_fw_user_state_t user_state;\n\n    anjay_advanced_fw_update_state_t state;\n    anjay_advanced_fw_update_result_t result;\n    const char *package_uri;\n    avs_sched_handle_t update_job;\n#    ifdef ANJAY_WITH_DOWNLOADER\n    bool retry_download_on_expired;\n    avs_sched_handle_t resume_download_job;\n    avs_time_monotonic_t resume_download_deadline;\n#    endif // ANJAY_WITH_DOWNLOADER\n    anjay_advanced_fw_update_severity_t severity;\n    avs_time_real_t last_state_change_time;\n    int max_defer_period;\n    avs_time_real_t update_deadline;\n\n    anjay_iid_t *linked_instances;\n    size_t linked_instances_count;\n\n    anjay_iid_t *conflicting_instances;\n    size_t conflicting_instances_count;\n} advanced_fw_instance_t;\n\ntypedef struct {\n    anjay_iid_t iid;\n    anjay_download_handle_t download_handle;\n} current_download_t;\n\ntypedef struct {\n    anjay_dm_installed_object_t def_ptr;\n    const anjay_unlocked_dm_object_def_t *def;\n\n#    ifdef ANJAY_WITH_DOWNLOADER\n    bool prefer_same_socket_downloads;\n#    endif // ANJAY_WITH_DOWNLOADER\n#    ifdef ANJAY_WITH_SEND\n    bool use_lwm2m_send;\n#    endif // ANJAY_WITH_SEND\n\n    anjay_iid_t *supplemental_iid_cache;\n    size_t supplemental_iid_cache_count;\n\n#    ifdef ANJAY_WITH_DOWNLOADER\n    current_download_t current_download;\n    bool downloads_suspended;\n    AVS_LIST(anjay_download_config_t) download_queue;\n#    endif // ANJAY_WITH_DOWNLOADER\n\n    AVS_LIST(advanced_fw_instance_t) instances;\n} advanced_fw_repr_t;\n\nstatic inline advanced_fw_repr_t *\nget_fw(const anjay_dm_installed_object_t obj_ptr) {\n    return AVS_CONTAINER_OF(_anjay_dm_installed_object_get_unlocked(&obj_ptr),\n                            advanced_fw_repr_t, def);\n}\n\n#    ifdef ANJAY_WITH_SEND\n#        define SEND_RES_PATH(Oid, Iid, Rid) \\\n            {                                \\\n                .oid = (Oid),                \\\n                .iid = (Iid),                \\\n                .rid = (Rid)                 \\\n            }\n\n#        define SEND_FW_RES_PATH(Iid, Res) \\\n            SEND_RES_PATH(ANJAY_ADVANCED_FW_UPDATE_OID, Iid, ADV_FW_RES_##Res)\n\nstatic int perform_send(anjay_unlocked_t *anjay,\n                        const anjay_dm_installed_object_t *obj,\n                        anjay_iid_t iid,\n                        void *batch) {\n    (void) obj;\n    anjay_ssid_t ssid;\n    const anjay_uri_path_t ssid_path =\n            MAKE_RESOURCE_PATH(ANJAY_DM_OID_SERVER, iid,\n                               ANJAY_DM_RID_SERVER_SSID);\n\n    if (_anjay_dm_read_resource_u16(anjay, &ssid_path, &ssid)) {\n        return 0;\n    }\n\n    if (_anjay_send_deferrable_unlocked(\n                anjay, ssid, (anjay_send_batch_t *) batch, NULL, NULL)\n            != ANJAY_SEND_OK) {\n        fw_log(WARNING, _(\"failed to perform Send, SSID: \") \"%\" PRIu16, ssid);\n    }\n\n    return 0;\n}\n\nstatic void send_batch_to_all_servers(anjay_unlocked_t *anjay,\n                                      anjay_send_batch_t *batch) {\n    const anjay_dm_installed_object_t *obj =\n            _anjay_dm_find_object_by_oid(_anjay_get_dm(anjay),\n                                         ANJAY_DM_OID_SERVER);\n\n    if (_anjay_dm_foreach_instance(anjay, obj, perform_send, batch)) {\n        fw_log(ERROR, _(\"failed to perform Send to all servers\"));\n    }\n}\n\nstatic void perform_lwm2m_send(anjay_unlocked_t *anjay,\n                               const anjay_send_resource_path_t *paths,\n                               size_t paths_len) {\n    assert(paths);\n\n    anjay_send_batch_builder_t *batch_builder = anjay_send_batch_builder_new();\n    if (!batch_builder) {\n        _anjay_log_oom();\n        return;\n    }\n    if (_anjay_send_batch_data_add_current_multiple_unlocked(\n                batch_builder, anjay, ANJAY_ID_INVALID, paths, paths_len,\n                true)) {\n        fw_log(ERROR, _(\"failed to add data to batch\"));\n        anjay_send_batch_builder_cleanup(&batch_builder);\n        return;\n    }\n    anjay_send_batch_t *batch =\n            anjay_send_batch_builder_compile(&batch_builder);\n    if (!batch) {\n        anjay_send_batch_builder_cleanup(&batch_builder);\n        _anjay_log_oom();\n        return;\n    }\n    send_batch_to_all_servers(anjay, batch);\n    anjay_send_batch_release(&batch);\n}\n\nstatic void send_state_and_update_result(anjay_unlocked_t *anjay,\n                                         const advanced_fw_repr_t *fw,\n                                         anjay_iid_t iid,\n                                         bool with_version_info) {\n    if (!fw->use_lwm2m_send) {\n        return;\n    }\n\n    anjay_send_resource_path_t paths[5] = { SEND_FW_RES_PATH(iid, STATE),\n                                            SEND_FW_RES_PATH(iid,\n                                                             UPDATE_RESULT) };\n    size_t path_count = 2;\n    if (with_version_info) {\n        paths[path_count++] =\n                (anjay_send_resource_path_t) SEND_FW_RES_PATH(iid,\n                                                              CURRENT_VERSION);\n        paths[path_count++] =\n                (anjay_send_resource_path_t) SEND_RES_PATH(3, 0, 3);\n        paths[path_count++] =\n                (anjay_send_resource_path_t) SEND_RES_PATH(3, 0, 19);\n    }\n    perform_lwm2m_send(anjay, paths, path_count);\n}\n#    endif // ANJAY_WITH_SEND\n\nstatic int set_update_result(anjay_unlocked_t *anjay,\n                             advanced_fw_instance_t *inst,\n                             anjay_advanced_fw_update_result_t new_result) {\n    if (inst->result != new_result) {\n        fw_log(DEBUG,\n               _(\"Advanced Firmware Update Instance \") \"%\" PRIu16 _(\n                       \" Result change: \") \"%d\" _(\" -> \") \"%d\",\n               inst->iid, (int) inst->result, (int) new_result);\n        inst->result = new_result;\n        _anjay_notify_changed_unlocked(anjay, ANJAY_ADVANCED_FW_UPDATE_OID,\n                                       inst->iid, ADV_FW_RES_UPDATE_RESULT);\n        return 0;\n    }\n    return -1;\n}\n\nstatic int set_state(anjay_unlocked_t *anjay,\n                     advanced_fw_instance_t *inst,\n                     anjay_advanced_fw_update_state_t new_state) {\n    if (inst->state != new_state) {\n        inst->last_state_change_time = avs_time_real_now();\n        fw_log(DEBUG,\n               _(\"Advanced Firmware Update Instance \") \"%\" PRIu16 _(\n                       \" State change: \") \"%d\" _(\" -> \") \"%d\",\n               inst->iid, (int) inst->state, (int) new_state);\n        inst->state = new_state;\n        _anjay_notify_changed_unlocked(anjay, ANJAY_ADVANCED_FW_UPDATE_OID,\n                                       inst->iid, ADV_FW_RES_STATE);\n        return 0;\n    }\n    return -1;\n}\n\nstatic void\nupdate_state_and_update_result(anjay_unlocked_t *anjay,\n                               advanced_fw_repr_t *fw,\n                               advanced_fw_instance_t *inst,\n                               anjay_advanced_fw_update_state_t new_state,\n                               anjay_advanced_fw_update_result_t new_result) {\n    int set_res = set_update_result(anjay, inst, new_result);\n    int set_st = set_state(anjay, inst, new_state);\n#    ifdef ANJAY_WITH_SEND\n    bool send_version_info =\n            inst->state == ANJAY_ADVANCED_FW_UPDATE_STATE_UPDATING\n            && new_state == ANJAY_ADVANCED_FW_UPDATE_STATE_IDLE\n            && new_result == ANJAY_ADVANCED_FW_UPDATE_RESULT_SUCCESS;\n    if (!set_res || !set_st) {\n        send_state_and_update_result(anjay, fw, inst->iid, send_version_info);\n    }\n#    else\n    (void) fw;\n    (void) set_res;\n    (void) set_st;\n#    endif // ANJAY_WITH_SEND\n}\n\nstatic void set_user_state(advanced_fw_user_state_t *user,\n                           anjay_advanced_fw_update_state_t new_state) {\n    fw_log(DEBUG, _(\"user->state change: \") \"%d\" _(\" -> \") \"%d\",\n           (int) user->state, (int) new_state);\n    user->state = new_state;\n}\n\nstatic int user_state_ensure_stream_open(anjay_unlocked_t *anjay,\n                                         advanced_fw_instance_t *inst) {\n    advanced_fw_user_state_t *const user = &inst->user_state;\n\n    if (user->state == ANJAY_ADVANCED_FW_UPDATE_STATE_DOWNLOADING) {\n        return 0;\n    }\n    assert(user->state == ANJAY_ADVANCED_FW_UPDATE_STATE_IDLE);\n\n    int result = -1;\n    ANJAY_MUTEX_UNLOCK_FOR_CALLBACK(anjay_locked, anjay);\n    result = user->handlers->stream_open(inst->iid, user->arg);\n    ANJAY_MUTEX_LOCK_AFTER_CALLBACK(anjay_locked);\n    if (!result) {\n        set_user_state(user, ANJAY_ADVANCED_FW_UPDATE_STATE_DOWNLOADING);\n    }\n    return result;\n}\n\nstatic int user_state_stream_write(anjay_unlocked_t *anjay,\n                                   advanced_fw_instance_t *inst,\n                                   const void *data,\n                                   size_t length) {\n    assert(inst->user_state.state\n           == ANJAY_ADVANCED_FW_UPDATE_STATE_DOWNLOADING);\n    int result = -1;\n    ANJAY_MUTEX_UNLOCK_FOR_CALLBACK(anjay_locked, anjay);\n    result = inst->user_state.handlers->stream_write(\n            inst->iid, inst->user_state.arg, data, length);\n    ANJAY_MUTEX_LOCK_AFTER_CALLBACK(anjay_locked);\n    return result;\n}\n\nstatic const char *user_state_get_pkg_name(anjay_unlocked_t *anjay,\n                                           advanced_fw_instance_t *inst) {\n    if (!inst->user_state.handlers->get_pkg_name\n            || inst->user_state.state\n                           != ANJAY_ADVANCED_FW_UPDATE_STATE_DOWNLOADED) {\n        return NULL;\n    }\n    const char *result = NULL;\n    ANJAY_MUTEX_UNLOCK_FOR_CALLBACK(anjay_locked, anjay);\n    result = inst->user_state.handlers->get_pkg_name(inst->iid,\n                                                     inst->user_state.arg);\n    ANJAY_MUTEX_LOCK_AFTER_CALLBACK(anjay_locked);\n    return result;\n}\n\nstatic const char *user_state_get_pkg_version(anjay_unlocked_t *anjay,\n                                              advanced_fw_instance_t *inst) {\n    if (!inst->user_state.handlers->get_pkg_version\n            || inst->user_state.state\n                           != ANJAY_ADVANCED_FW_UPDATE_STATE_DOWNLOADED) {\n        return NULL;\n    }\n    const char *result = NULL;\n    ANJAY_MUTEX_UNLOCK_FOR_CALLBACK(anjay_locked, anjay);\n    result = inst->user_state.handlers->get_pkg_version(inst->iid,\n                                                        inst->user_state.arg);\n    ANJAY_MUTEX_LOCK_AFTER_CALLBACK(anjay_locked);\n    return result;\n}\n\nstatic const char *\nuser_state_get_current_version(anjay_unlocked_t *anjay,\n                               advanced_fw_instance_t *inst) {\n    if (!inst->user_state.handlers->get_current_version) {\n        return NULL;\n    }\n    const char *result = NULL;\n    ANJAY_MUTEX_UNLOCK_FOR_CALLBACK(anjay_locked, anjay);\n    result = inst->user_state.handlers->get_current_version(\n            inst->iid, inst->user_state.arg);\n    ANJAY_MUTEX_LOCK_AFTER_CALLBACK(anjay_locked);\n    return result;\n}\n\nstatic int user_state_perform_upgrade(anjay_unlocked_t *anjay,\n                                      advanced_fw_instance_t *inst,\n                                      const anjay_iid_t *supplemental_iids,\n                                      size_t supplemental_iids_count) {\n    advanced_fw_user_state_t *user = &inst->user_state;\n\n    int result = -1;\n    ANJAY_MUTEX_UNLOCK_FOR_CALLBACK(anjay_locked, anjay);\n    result = user->handlers->perform_upgrade(\n            inst->iid, user->arg, supplemental_iids, supplemental_iids_count);\n    ANJAY_MUTEX_LOCK_AFTER_CALLBACK(anjay_locked);\n    // If the state was changed during perform_upgrade handler, this means\n    // @ref anjay_advanced_fw_update_set_state_and_result was called and\n    // has overwritten the State and Result. In that case, change State to\n    // Updating if update was not deferred or to Downloaded if failed due to\n    // dependency error.\n    if (!result && user->state == ANJAY_ADVANCED_FW_UPDATE_STATE_DOWNLOADED\n            && inst->result != ANJAY_ADVANCED_FW_UPDATE_RESULT_DEFERRED\n            && inst->result\n                           != ANJAY_ADVANCED_FW_UPDATE_RESULT_DEPENDENCY_ERROR) {\n        set_user_state(user, ANJAY_ADVANCED_FW_UPDATE_STATE_UPDATING);\n    }\n    return result;\n}\n\nstatic int finish_user_stream(anjay_unlocked_t *anjay,\n                              advanced_fw_instance_t *inst) {\n    assert(inst->user_state.state\n           == ANJAY_ADVANCED_FW_UPDATE_STATE_DOWNLOADING);\n    int result = -1;\n    ANJAY_MUTEX_UNLOCK_FOR_CALLBACK(anjay_locked, anjay);\n    result = inst->user_state.handlers->stream_finish(inst->iid,\n                                                      inst->user_state.arg);\n    ANJAY_MUTEX_LOCK_AFTER_CALLBACK(anjay_locked);\n    if (result) {\n        set_user_state(&inst->user_state, ANJAY_ADVANCED_FW_UPDATE_STATE_IDLE);\n    } else {\n        set_user_state(&inst->user_state,\n                       ANJAY_ADVANCED_FW_UPDATE_STATE_DOWNLOADED);\n    }\n    return result;\n}\n\nstatic void reset_user_state(anjay_unlocked_t *anjay,\n                             advanced_fw_instance_t *inst) {\n    ANJAY_MUTEX_UNLOCK_FOR_CALLBACK(anjay_locked, anjay);\n    inst->user_state.handlers->reset(inst->iid, inst->user_state.arg);\n    ANJAY_MUTEX_LOCK_AFTER_CALLBACK(anjay_locked);\n    set_user_state(&inst->user_state, ANJAY_ADVANCED_FW_UPDATE_STATE_IDLE);\n}\n\n#    ifdef ANJAY_WITH_DOWNLOADER\n\nstatic int get_security_config(anjay_unlocked_t *anjay,\n                               advanced_fw_instance_t *inst,\n                               anjay_security_config_t *out_security_config) {\n    assert(inst->user_state.state == ANJAY_ADVANCED_FW_UPDATE_STATE_IDLE\n           || inst->user_state.state\n                      == ANJAY_ADVANCED_FW_UPDATE_STATE_DOWNLOADING);\n    if (inst->user_state.handlers->get_security_config) {\n        int result = -1;\n        ANJAY_MUTEX_UNLOCK_FOR_CALLBACK(anjay_locked, anjay);\n        result = inst->user_state.handlers->get_security_config(\n                inst->iid, inst->user_state.arg, out_security_config,\n                inst->package_uri);\n        ANJAY_MUTEX_LOCK_AFTER_CALLBACK(anjay_locked);\n        return result;\n    } else {\n        if (!_anjay_security_config_from_dm_unlocked(anjay, out_security_config,\n                                                     inst->package_uri)) {\n            return 0;\n        }\n#        ifdef ANJAY_WITH_LWM2M11\n        *out_security_config = _anjay_security_config_pkix_unlocked(anjay);\n        if (out_security_config->security_info.data.cert\n                    .server_cert_validation) {\n            return 0;\n        }\n#        endif // ANJAY_WITH_LWM2M11\n        return -1;\n    }\n}\n\nstatic int get_coap_tx_params(anjay_unlocked_t *anjay,\n                              advanced_fw_instance_t *inst,\n                              avs_coap_udp_tx_params_t *out_tx_params) {\n    if (inst->user_state.handlers->get_coap_tx_params) {\n        ANJAY_MUTEX_UNLOCK_FOR_CALLBACK(anjay_locked, anjay);\n        *out_tx_params = inst->user_state.handlers->get_coap_tx_params(\n                inst->iid, inst->user_state.arg, inst->package_uri);\n        ANJAY_MUTEX_LOCK_AFTER_CALLBACK(anjay_locked);\n        return 0;\n    }\n    return -1;\n}\n\nstatic avs_time_duration_t\nget_tcp_request_timeout(anjay_unlocked_t *anjay, advanced_fw_instance_t *inst) {\n    avs_time_duration_t result = AVS_TIME_DURATION_INVALID;\n    if (inst->user_state.handlers->get_tcp_request_timeout) {\n        ANJAY_MUTEX_UNLOCK_FOR_CALLBACK(anjay_locked, anjay);\n        result = inst->user_state.handlers->get_tcp_request_timeout(\n                inst->iid, inst->user_state.arg, inst->package_uri);\n        ANJAY_MUTEX_LOCK_AFTER_CALLBACK(anjay_locked);\n    }\n    return result;\n}\n#    endif // ANJAY_WITH_DOWNLOADER\n\nstatic void\nhandle_err_result(anjay_unlocked_t *anjay,\n                  advanced_fw_repr_t *fw,\n                  advanced_fw_instance_t *inst,\n                  anjay_advanced_fw_update_state_t new_state,\n                  int result,\n                  anjay_advanced_fw_update_result_t default_result) {\n    anjay_advanced_fw_update_result_t new_result;\n\n    switch (result) {\n    case -ANJAY_ADVANCED_FW_UPDATE_RESULT_NOT_ENOUGH_SPACE:\n    case -ANJAY_ADVANCED_FW_UPDATE_RESULT_OUT_OF_MEMORY:\n    case -ANJAY_ADVANCED_FW_UPDATE_RESULT_INTEGRITY_FAILURE:\n    case -ANJAY_ADVANCED_FW_UPDATE_RESULT_UNSUPPORTED_PACKAGE_TYPE:\n    case -ANJAY_ADVANCED_FW_UPDATE_RESULT_DEFERRED:\n    case -ANJAY_ADVANCED_FW_UPDATE_RESULT_CONFLICTING_STATE:\n    case -ANJAY_ADVANCED_FW_UPDATE_RESULT_DEPENDENCY_ERROR:\n        new_result = (anjay_advanced_fw_update_result_t) (-result);\n        break;\n    default:\n        new_result = default_result;\n    }\n    update_state_and_update_result(anjay, fw, inst, new_state, new_result);\n}\n\nstatic void reset_state(anjay_unlocked_t *anjay,\n                        advanced_fw_repr_t *fw,\n                        advanced_fw_instance_t *inst) {\n    reset_user_state(anjay, inst);\n    update_state_and_update_result(anjay, fw, inst,\n                                   ANJAY_ADVANCED_FW_UPDATE_STATE_IDLE,\n                                   ANJAY_ADVANCED_FW_UPDATE_RESULT_INITIAL);\n    fw_log(INFO,\n           _(\"Advanced Firmware Object Instance \") \"%\" PRIu16 _(\" state reset\"),\n           inst->iid);\n}\n\nstatic int fw_list_instances(anjay_unlocked_t *anjay,\n                             const anjay_dm_installed_object_t obj_ptr,\n                             anjay_unlocked_dm_list_ctx_t *ctx) {\n    (void) anjay;\n\n    advanced_fw_repr_t *fw = get_fw(obj_ptr);\n    AVS_LIST(advanced_fw_instance_t) inst;\n    AVS_LIST_FOREACH(inst, fw->instances) {\n        _anjay_dm_emit_unlocked(ctx, inst->iid);\n    }\n    return 0;\n}\n\nstatic advanced_fw_instance_t *get_fw_instance(advanced_fw_repr_t *fw,\n                                               anjay_iid_t iid) {\n    assert(fw);\n\n    AVS_LIST(advanced_fw_instance_t) inst;\n    AVS_LIST_FOREACH(inst, fw->instances) {\n        if (inst->iid > iid) {\n            break;\n        } else if (inst->iid == iid) {\n            return inst;\n        }\n    }\n    return NULL;\n}\n\nstatic int fw_list_resources(anjay_unlocked_t *anjay,\n                             const anjay_dm_installed_object_t obj_ptr,\n                             anjay_iid_t iid,\n                             anjay_unlocked_dm_resource_list_ctx_t *ctx) {\n    advanced_fw_repr_t *fw = get_fw(obj_ptr);\n    advanced_fw_instance_t *inst = get_fw_instance(fw, iid);\n    assert(inst);\n\n    _anjay_dm_emit_res_unlocked(ctx, ADV_FW_RES_PACKAGE, ANJAY_DM_RES_W,\n                                ANJAY_DM_RES_PRESENT);\n    _anjay_dm_emit_res_unlocked(ctx, ADV_FW_RES_PACKAGE_URI, ANJAY_DM_RES_RW,\n                                ANJAY_DM_RES_PRESENT);\n    _anjay_dm_emit_res_unlocked(ctx, ADV_FW_RES_UPDATE, ANJAY_DM_RES_E,\n                                ANJAY_DM_RES_PRESENT);\n    _anjay_dm_emit_res_unlocked(ctx, ADV_FW_RES_STATE, ANJAY_DM_RES_R,\n                                ANJAY_DM_RES_PRESENT);\n    _anjay_dm_emit_res_unlocked(ctx, ADV_FW_RES_UPDATE_RESULT, ANJAY_DM_RES_R,\n                                ANJAY_DM_RES_PRESENT);\n    _anjay_dm_emit_res_unlocked(ctx, ADV_FW_RES_PKG_NAME, ANJAY_DM_RES_R,\n                                user_state_get_pkg_name(anjay, inst)\n                                        ? ANJAY_DM_RES_PRESENT\n                                        : ANJAY_DM_RES_ABSENT);\n    _anjay_dm_emit_res_unlocked(ctx, ADV_FW_RES_PKG_VERSION, ANJAY_DM_RES_R,\n                                user_state_get_pkg_version(anjay, inst)\n                                        ? ANJAY_DM_RES_PRESENT\n                                        : ANJAY_DM_RES_ABSENT);\n    _anjay_dm_emit_res_unlocked(ctx, ADV_FW_RES_UPDATE_PROTOCOL_SUPPORT,\n                                ANJAY_DM_RES_RM, ANJAY_DM_RES_PRESENT);\n    _anjay_dm_emit_res_unlocked(ctx, ADV_FW_RES_UPDATE_DELIVERY_METHOD,\n                                ANJAY_DM_RES_R, ANJAY_DM_RES_PRESENT);\n    _anjay_dm_emit_res_unlocked(ctx, ADV_FW_RES_CANCEL, ANJAY_DM_RES_E,\n                                ANJAY_DM_RES_PRESENT);\n    _anjay_dm_emit_res_unlocked(ctx, ADV_FW_RES_SEVERITY, ANJAY_DM_RES_RW,\n                                ANJAY_DM_RES_PRESENT);\n    _anjay_dm_emit_res_unlocked(ctx, ADV_FW_RES_LAST_STATE_CHANGE_TIME,\n                                ANJAY_DM_RES_R, ANJAY_DM_RES_PRESENT);\n    _anjay_dm_emit_res_unlocked(ctx, ADV_FW_RES_MAX_DEFER_PERIOD,\n                                ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT);\n    _anjay_dm_emit_res_unlocked(ctx, ADV_FW_RES_COMPONENT_NAME, ANJAY_DM_RES_R,\n                                inst->component_name ? ANJAY_DM_RES_PRESENT\n                                                     : ANJAY_DM_RES_ABSENT);\n    _anjay_dm_emit_res_unlocked(ctx, ADV_FW_RES_CURRENT_VERSION, ANJAY_DM_RES_R,\n                                user_state_get_current_version(anjay, inst)\n                                        ? ANJAY_DM_RES_PRESENT\n                                        : ANJAY_DM_RES_ABSENT);\n    if (AVS_LIST_NEXT(fw->instances)) {\n        // at least 2 instances exist\n        _anjay_dm_emit_res_unlocked(ctx, ADV_FW_RES_LINKED_INSTANCES,\n                                    ANJAY_DM_RES_RM, ANJAY_DM_RES_PRESENT);\n        _anjay_dm_emit_res_unlocked(ctx, ADV_FW_RES_CONFLICTING_INSTANCES,\n                                    ANJAY_DM_RES_RM, ANJAY_DM_RES_PRESENT);\n    }\n    return 0;\n}\n\nstatic const int32_t SUPPORTED_PROTOCOLS[] = {\n#    ifdef WITH_AVS_COAP_UDP\n    0, /* CoAP */\n#        ifndef AVS_COMMONS_WITHOUT_TLS\n    1,         /* CoAPS */\n#        endif // AVS_COMMONS_WITHOUT_TLS\n#    endif     // WITH_AVS_COAP_UDP\n#    ifdef ANJAY_WITH_HTTP_DOWNLOAD\n    2, /* HTTP 1.1 */\n#        ifndef AVS_COMMONS_WITHOUT_TLS\n    3,         /* HTTPS 1.1 */\n#        endif // AVS_COMMONS_WITHOUT_TLS\n#    endif     // ANJAY_WITH_HTTP_DOWNLOAD\n#    ifdef WITH_AVS_COAP_TCP\n    4, /* CoAP over TCP */\n#        ifndef AVS_COMMONS_WITHOUT_TLS\n    5,         /* CoAP over TLS */\n#        endif // AVS_COMMONS_WITHOUT_TLS\n#    endif     // WITH_AVS_COAP_TCP\n};\n\nstatic int fw_read(anjay_unlocked_t *anjay,\n                   const anjay_dm_installed_object_t obj_ptr,\n                   anjay_iid_t iid,\n                   anjay_rid_t rid,\n                   anjay_riid_t riid,\n                   anjay_unlocked_output_ctx_t *ctx) {\n    advanced_fw_repr_t *fw = get_fw(obj_ptr);\n    advanced_fw_instance_t *inst = get_fw_instance(fw, iid);\n    assert(inst);\n    switch (rid) {\n    case ADV_FW_RES_PACKAGE_URI:\n        return _anjay_ret_string_unlocked(\n                ctx, inst->package_uri ? inst->package_uri : \"\");\n    case ADV_FW_RES_STATE:\n        return _anjay_ret_i64_unlocked(ctx, (int32_t) inst->state);\n    case ADV_FW_RES_UPDATE_RESULT:\n        return _anjay_ret_i64_unlocked(ctx, (int32_t) inst->result);\n    case ADV_FW_RES_PKG_NAME: {\n        const char *name = user_state_get_pkg_name(anjay, inst);\n\n        if (name) {\n            return _anjay_ret_string_unlocked(ctx, name);\n        } else {\n            return ANJAY_ERR_NOT_FOUND;\n        }\n    }\n    case ADV_FW_RES_PKG_VERSION: {\n        const char *version = user_state_get_pkg_version(anjay, inst);\n\n        if (version) {\n            return _anjay_ret_string_unlocked(ctx, version);\n        } else {\n            return ANJAY_ERR_NOT_FOUND;\n        }\n    }\n    case ADV_FW_RES_UPDATE_PROTOCOL_SUPPORT:\n        assert(riid < AVS_ARRAY_SIZE(SUPPORTED_PROTOCOLS));\n        return _anjay_ret_i64_unlocked(ctx, SUPPORTED_PROTOCOLS[riid]);\n    case ADV_FW_RES_UPDATE_DELIVERY_METHOD:\n#    ifdef ANJAY_WITH_DOWNLOADER\n        return _anjay_ret_i64_unlocked(ctx, 2); // 2 -> pull && push\n#    else                                       // ANJAY_WITH_DOWNLOADER\n        return _anjay_ret_i64_unlocked(ctx, 1); // 1 -> push only\n#    endif                                      // ANJAY_WITH_DOWNLOADER\n    case ADV_FW_RES_SEVERITY:\n        return _anjay_ret_i64_unlocked(ctx, (int32_t) inst->severity);\n    case ADV_FW_RES_LAST_STATE_CHANGE_TIME: {\n        int64_t last_state_change_timestamp = 0;\n\n        avs_time_real_to_scalar(&last_state_change_timestamp, AVS_TIME_S,\n                                inst->last_state_change_time);\n        return _anjay_ret_i64_unlocked(ctx, last_state_change_timestamp);\n    }\n    case ADV_FW_RES_MAX_DEFER_PERIOD:\n        return _anjay_ret_i64_unlocked(ctx, inst->max_defer_period);\n    case ADV_FW_RES_COMPONENT_NAME:\n        return _anjay_ret_string_unlocked(ctx, inst->component_name);\n    case ADV_FW_RES_CURRENT_VERSION: {\n        const char *version = user_state_get_current_version(anjay, inst);\n\n        if (version) {\n            return _anjay_ret_string_unlocked(ctx, version);\n        } else {\n            return ANJAY_ERR_NOT_FOUND;\n        }\n    }\n    case ADV_FW_RES_LINKED_INSTANCES:\n        return _anjay_ret_objlnk_unlocked(ctx, ANJAY_ADVANCED_FW_UPDATE_OID,\n                                          riid);\n    case ADV_FW_RES_CONFLICTING_INSTANCES:\n        return _anjay_ret_objlnk_unlocked(ctx, ANJAY_ADVANCED_FW_UPDATE_OID,\n                                          riid);\n    default:\n        AVS_UNREACHABLE(\"Read called on unknown or non-readable Firmware \"\n                        \"Update resource\");\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\n#    if defined(ANJAY_WITH_COAP_DOWNLOAD) || defined(ANJAY_WITH_HTTP_DOWNLOAD)\nstatic anjay_transport_security_t\ntransport_security_from_protocol(const char *protocol) {\n#        ifdef ANJAY_WITH_COAP_DOWNLOAD\n    const anjay_transport_info_t *info =\n            _anjay_transport_info_by_uri_scheme(protocol);\n    if (info) {\n        return info->security;\n    }\n#        endif // ANJAY_WITH_COAP_DOWNLOAD\n\n#        ifdef ANJAY_WITH_HTTP_DOWNLOAD\n    if (avs_strcasecmp(protocol, \"http\") == 0) {\n        return ANJAY_TRANSPORT_NOSEC;\n    }\n    if (avs_strcasecmp(protocol, \"https\") == 0) {\n        return ANJAY_TRANSPORT_ENCRYPTED;\n    }\n#        endif // ANJAY_WITH_HTTP_DOWNLOAD\n\n    return ANJAY_TRANSPORT_SECURITY_UNDEFINED;\n}\n\nstatic anjay_transport_security_t transport_security_from_uri(const char *uri) {\n    avs_url_t *parsed_url = avs_url_parse_lenient(uri);\n    if (!parsed_url) {\n        return ANJAY_TRANSPORT_SECURITY_UNDEFINED;\n    }\n    anjay_transport_security_t result = ANJAY_TRANSPORT_SECURITY_UNDEFINED;\n\n    const char *protocol = avs_url_protocol(parsed_url);\n    if (protocol) {\n        result = transport_security_from_protocol(protocol);\n    }\n    avs_url_free(parsed_url);\n    return result;\n}\n#    else  // ANJAY_WITH_COAP_DOWNLOAD || ANJAY_WITH_HTTP_DOWNLOAD\nstatic anjay_transport_security_t transport_security_from_uri(const char *uri) {\n    (void) uri;\n    return ANJAY_TRANSPORT_SECURITY_UNDEFINED;\n}\n#    endif // ANJAY_WITH_COAP_DOWNLOAD || ANJAY_WITH_HTTP_DOWNLOAD\n\nstatic void set_update_deadline(advanced_fw_instance_t *inst) {\n    if (inst->max_defer_period <= 0) {\n        inst->update_deadline = AVS_TIME_REAL_INVALID;\n        return;\n    }\n    inst->update_deadline = avs_time_real_add(\n            avs_time_real_now(),\n            avs_time_duration_from_scalar(inst->max_defer_period, AVS_TIME_S));\n}\n\n#    ifdef ANJAY_WITH_DOWNLOADER\n\nstatic avs_error_t download_write_block(anjay_t *anjay_locked,\n                                        const uint8_t *data,\n                                        size_t data_size,\n                                        const anjay_etag_t *etag,\n                                        void *inst_) {\n    (void) etag;\n    int result = -1;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    const anjay_dm_installed_object_t *obj =\n            _anjay_dm_find_object_by_oid(_anjay_get_dm(anjay),\n                                         ANJAY_ADVANCED_FW_UPDATE_OID);\n    if (!obj) {\n        fw_log(WARNING, _(\"Advanced Firmware Update object not installed\"));\n    } else {\n        advanced_fw_repr_t *fw = get_fw(*obj);\n        advanced_fw_instance_t *inst = (advanced_fw_instance_t *) inst_;\n        result = user_state_ensure_stream_open(anjay, inst);\n        if (!result && data_size > 0) {\n            result = user_state_stream_write(anjay, inst, data, data_size);\n        }\n        if (result) {\n            fw_log(ERROR, _(\"could not write firmware\"));\n\n            handle_err_result(anjay, fw, inst,\n                              ANJAY_ADVANCED_FW_UPDATE_STATE_IDLE, result,\n                              ANJAY_ADVANCED_FW_UPDATE_RESULT_NOT_ENOUGH_SPACE);\n        }\n    }\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return result ? avs_errno(AVS_UNKNOWN_ERROR) : AVS_OK;\n}\n\nstatic int schedule_background_anjay_download(anjay_unlocked_t *anjay,\n                                              advanced_fw_repr_t *fw,\n                                              advanced_fw_instance_t *inst);\n\nstatic int schedule_download_now(anjay_unlocked_t *anjay,\n                                 advanced_fw_repr_t *fw,\n                                 advanced_fw_instance_t *inst,\n                                 anjay_download_config_t *cfg) {\n    if (transport_security_from_uri(cfg->url) == ANJAY_TRANSPORT_ENCRYPTED) {\n        int result = get_security_config(anjay, inst, &cfg->security_config);\n\n        if (result) {\n            handle_err_result(\n                    anjay, fw, inst, ANJAY_ADVANCED_FW_UPDATE_STATE_IDLE,\n                    result,\n                    ANJAY_ADVANCED_FW_UPDATE_RESULT_UNSUPPORTED_PROTOCOL);\n            return -1;\n        }\n    }\n    avs_error_t err =\n            _anjay_download_unlocked(anjay, cfg,\n                                     &fw->current_download.download_handle);\n    if (avs_is_err(err)) {\n        anjay_advanced_fw_update_result_t update_result =\n                ANJAY_ADVANCED_FW_UPDATE_RESULT_CONNECTION_LOST;\n        if (err.category == AVS_ERRNO_CATEGORY) {\n            switch (err.code) {\n            case AVS_EADDRNOTAVAIL:\n            case AVS_EINVAL:\n                update_result = ANJAY_ADVANCED_FW_UPDATE_RESULT_INVALID_URI;\n                break;\n            case AVS_ENOMEM:\n                update_result = ANJAY_ADVANCED_FW_UPDATE_RESULT_OUT_OF_MEMORY;\n                break;\n            case AVS_EPROTONOSUPPORT:\n                update_result =\n                        ANJAY_ADVANCED_FW_UPDATE_RESULT_UNSUPPORTED_PROTOCOL;\n                break;\n            }\n        }\n        reset_user_state(anjay, inst);\n        set_update_result(anjay, inst, update_result);\n#        ifdef ANJAY_WITH_SEND\n        send_state_and_update_result(anjay, fw, inst->iid, false);\n#        endif // ANJAY_WITH_SEND\n        return -1;\n    }\n    fw->current_download.iid = inst->iid;\n    if (fw->downloads_suspended) {\n        _anjay_download_suspend_unlocked(anjay,\n                                         fw->current_download.download_handle);\n    }\n    inst->retry_download_on_expired = (false);\n    update_state_and_update_result(anjay, fw, inst,\n                                   ANJAY_ADVANCED_FW_UPDATE_STATE_DOWNLOADING,\n                                   ANJAY_ADVANCED_FW_UPDATE_RESULT_INITIAL);\n    fw_log(INFO, _(\"IID \") \"%\" PRIu16 _(\": download started: \") \"%s\", inst->iid,\n           inst->package_uri);\n    return 0;\n}\n\nstatic void start_next_download_if_waiting(anjay_unlocked_t *anjay,\n                                           advanced_fw_repr_t *fw) {\n    if (fw->download_queue != NULL) {\n        advanced_fw_instance_t *inst =\n                (advanced_fw_instance_t *) fw->download_queue->user_data;\n        if (schedule_download_now(anjay, fw, inst, fw->download_queue)) {\n            fw_log(WARNING, _(\"Scheduling next waiting download failed\"));\n        }\n        fw_log(TRACE, _(\"Scheduled download for instance %\") PRIu16, inst->iid);\n        avs_free((void *) (intptr_t) fw->download_queue->url);\n        avs_free((void *) fw->download_queue->coap_tx_params);\n        AVS_LIST_DELETE(&fw->download_queue);\n    }\n}\n\nstatic void download_finished(anjay_t *anjay_locked,\n                              anjay_download_status_t status,\n                              void *inst_) {\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    const anjay_dm_installed_object_t *obj =\n            _anjay_dm_find_object_by_oid(_anjay_get_dm(anjay),\n                                         ANJAY_ADVANCED_FW_UPDATE_OID);\n    if (!obj) {\n        fw_log(WARNING, _(\"Advanced Firmware Update object not installed\"));\n    } else {\n        advanced_fw_repr_t *fw = get_fw(*obj);\n        advanced_fw_instance_t *inst = (advanced_fw_instance_t *) inst_;\n        fw->current_download.download_handle = NULL;\n        fw->current_download.iid = ANJAY_ID_INVALID;\n        if (inst->state != ANJAY_ADVANCED_FW_UPDATE_STATE_DOWNLOADING) {\n            // something already failed in download_write_block()\n            reset_user_state(anjay, inst);\n            start_next_download_if_waiting(anjay, fw);\n        } else if (status.result != ANJAY_DOWNLOAD_FINISHED) {\n            anjay_advanced_fw_update_result_t update_result =\n                    ANJAY_ADVANCED_FW_UPDATE_RESULT_CONNECTION_LOST;\n\n            if (status.result == ANJAY_DOWNLOAD_ERR_FAILED) {\n                if (status.details.error.category == AVS_ERRNO_CATEGORY) {\n                    if (status.details.error.code == AVS_ENOMEM) {\n                        update_result =\n                                ANJAY_ADVANCED_FW_UPDATE_RESULT_OUT_OF_MEMORY;\n                    } else if (status.details.error.code == AVS_EADDRNOTAVAIL) {\n                        update_result =\n                                ANJAY_ADVANCED_FW_UPDATE_RESULT_INVALID_URI;\n                    }\n                }\n            } else if (status.result == ANJAY_DOWNLOAD_ERR_INVALID_RESPONSE\n                       && (status.details.status_code == AVS_COAP_CODE_NOT_FOUND\n                           || status.details.status_code == 404)) {\n                // NOTE: We should only check for the status code appropriate\n                // for the download protocol, but 132 (AVS_COAP_CODE_NOT_FOUND)\n                // is unlikely as a HTTP status code, and 12.20 (404 according\n                // to CoAP convention) is not representable on a single byte, so\n                // this is good enough.\n                update_result = ANJAY_ADVANCED_FW_UPDATE_RESULT_INVALID_URI;\n            }\n            reset_user_state(anjay, inst);\n            if (inst->retry_download_on_expired\n                    && status.result == ANJAY_DOWNLOAD_ERR_EXPIRED) {\n                fw_log(INFO,\n                       _(\"Could not resume firmware download (result = \") \"%\"\n                                                                          \"d\" _(\"), retrying from the beginning\"),\n                       (int) status.result);\n                if (schedule_background_anjay_download(anjay, fw, inst)) {\n                    fw_log(WARNING, _(\"Could not retry firmware download\"));\n                    set_state(anjay, inst, ANJAY_ADVANCED_FW_UPDATE_STATE_IDLE);\n#        ifdef ANJAY_WITH_SEND\n                    send_state_and_update_result(anjay, fw, inst->iid, false);\n#        endif // ANJAY_WITH_SEND\n                }\n            } else {\n                fw_log(WARNING, _(\"download aborted: result = \") \"%d\",\n                       (int) status.result);\n                update_state_and_update_result(\n                        anjay, fw, inst, ANJAY_ADVANCED_FW_UPDATE_STATE_IDLE,\n                        update_result);\n            }\n        } else {\n            int result = user_state_ensure_stream_open(anjay, inst);\n\n            if (!result) {\n                result = finish_user_stream(anjay, inst);\n            }\n            if (result) {\n                handle_err_result(\n                        anjay, fw, inst, ANJAY_ADVANCED_FW_UPDATE_STATE_IDLE,\n                        result,\n                        ANJAY_ADVANCED_FW_UPDATE_RESULT_NOT_ENOUGH_SPACE);\n            } else {\n                update_state_and_update_result(\n                        anjay, fw, inst,\n                        ANJAY_ADVANCED_FW_UPDATE_STATE_DOWNLOADED,\n                        ANJAY_ADVANCED_FW_UPDATE_RESULT_INITIAL);\n            }\n            start_next_download_if_waiting(anjay, fw);\n        }\n    }\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n}\n\nstatic bool is_any_download_in_progress(advanced_fw_repr_t *fw) {\n    return fw->current_download.download_handle || fw->download_queue;\n}\n\nstatic int enqueue_download(anjay_unlocked_t *anjay,\n                            advanced_fw_repr_t *fw,\n                            advanced_fw_instance_t *inst,\n                            anjay_download_config_t *cfg) {\n#        ifndef NDEBUG\n    AVS_LIST(anjay_download_config_t) queued_cfg;\n    AVS_LIST_FOREACH(queued_cfg, fw->download_queue) {\n        advanced_fw_instance_t *queued_inst =\n                (advanced_fw_instance_t *) queued_cfg->user_data;\n        assert(queued_inst->iid != inst->iid);\n    }\n#        endif // NDEBUG\n    AVS_LIST(anjay_download_config_t) new_download =\n            AVS_LIST_APPEND_NEW(anjay_download_config_t, &fw->download_queue);\n    if (!new_download) {\n        goto cleanup;\n    }\n    memcpy(new_download, cfg, sizeof(anjay_download_config_t));\n    new_download->url = avs_strdup(cfg->url);\n    if (!new_download->url) {\n        goto cleanup;\n    }\n    if (cfg->coap_tx_params) {\n        new_download->coap_tx_params = (avs_coap_udp_tx_params_t *) avs_malloc(\n                sizeof(avs_coap_udp_tx_params_t));\n        if (!new_download->coap_tx_params) {\n            goto cleanup;\n        }\n        memcpy(new_download->coap_tx_params, cfg->coap_tx_params,\n               sizeof(avs_coap_udp_tx_params_t));\n    }\n\n    update_state_and_update_result(anjay, fw, inst,\n                                   ANJAY_ADVANCED_FW_UPDATE_STATE_DOWNLOADING,\n                                   ANJAY_ADVANCED_FW_UPDATE_RESULT_INITIAL);\n    fw_log(INFO,\n           _(\"There is a download in progress. New download from \") \"%s\" _(\n                   \" added to queue\"),\n           inst->package_uri);\n    return 0;\n\ncleanup:\n    _anjay_log_oom();\n    if (new_download) {\n        avs_free((void *) (intptr_t) new_download->url);\n        AVS_LIST_DELETE(&new_download);\n    }\n    return -1;\n}\n\nstatic int schedule_download(anjay_unlocked_t *anjay,\n                             advanced_fw_repr_t *fw,\n                             advanced_fw_instance_t *inst) {\n    anjay_download_config_t cfg = {\n        .url = inst->package_uri,\n        .on_next_block = download_write_block,\n        .on_download_finished = download_finished,\n        .user_data = inst,\n        .prefer_same_socket_downloads = fw->prefer_same_socket_downloads\n    };\n    avs_coap_udp_tx_params_t tx_params;\n    if (!get_coap_tx_params(anjay, inst, &tx_params)) {\n        cfg.coap_tx_params = &tx_params;\n    }\n    cfg.tcp_request_timeout = get_tcp_request_timeout(anjay, inst);\n    if (is_any_download_in_progress(fw)) {\n        return enqueue_download(anjay, fw, inst, &cfg);\n    }\n    return schedule_download_now(anjay, fw, inst, &cfg);\n}\n\nstruct schedule_download_args {\n    anjay_t *anjay;\n    advanced_fw_repr_t *fw;\n    advanced_fw_instance_t *inst;\n    size_t start_offset;\n    // actually a FAM\n};\n\nstatic int schedule_background_anjay_download(anjay_unlocked_t *anjay,\n                                              advanced_fw_repr_t *fw,\n                                              advanced_fw_instance_t *inst) {\n    return schedule_download(anjay, fw, inst);\n}\n#    endif // ANJAY_WITH_DOWNLOADER\n\nstatic int write_firmware_to_stream(anjay_unlocked_t *anjay,\n                                    advanced_fw_repr_t *fw,\n                                    advanced_fw_instance_t *inst,\n                                    anjay_unlocked_input_ctx_t *ctx,\n                                    bool *out_is_reset_request) {\n    int result = 0;\n    size_t written = 0;\n    bool finished = false;\n    int first_byte = EOF;\n\n    *out_is_reset_request = false;\n    while (!finished) {\n        size_t bytes_read;\n        char buffer[1024];\n\n        result = _anjay_get_bytes_unlocked(ctx, &bytes_read, &finished, buffer,\n                                           sizeof(buffer));\n        if (result) {\n            fw_log(ERROR, _(\"anjay_get_bytes() failed\"));\n\n            update_state_and_update_result(\n                    anjay, fw, inst, ANJAY_ADVANCED_FW_UPDATE_STATE_IDLE,\n                    ANJAY_ADVANCED_FW_UPDATE_RESULT_CONNECTION_LOST);\n            return result;\n        }\n        if (bytes_read > 0) {\n            if (first_byte == EOF) {\n                first_byte = (unsigned char) buffer[0];\n            }\n            result = user_state_stream_write(anjay, inst, buffer, bytes_read);\n        }\n        if (result) {\n            handle_err_result(anjay, fw, inst,\n                              ANJAY_ADVANCED_FW_UPDATE_STATE_IDLE, result,\n                              ANJAY_ADVANCED_FW_UPDATE_RESULT_NOT_ENOUGH_SPACE);\n            return ANJAY_ERR_INTERNAL;\n        }\n        written += bytes_read;\n    }\n    *out_is_reset_request = (written == 1 && first_byte == '\\0');\n    fw_log(INFO, _(\"write finished, \") \"%lu\" _(\" B written\"),\n           (unsigned long) written);\n    return 0;\n}\n\nstatic int expect_single_nullbyte(anjay_unlocked_input_ctx_t *ctx) {\n    char bytes[2];\n    size_t bytes_read;\n    bool finished = false;\n\n    if (_anjay_get_bytes_unlocked(ctx, &bytes_read, &finished, bytes,\n                                  sizeof(bytes))) {\n        fw_log(ERROR, _(\"anjay_get_bytes() failed\"));\n        return ANJAY_ERR_INTERNAL;\n    } else if (bytes_read != 1 || !finished || bytes[0] != '\\0') {\n        return ANJAY_ERR_BAD_REQUEST;\n    }\n    return 0;\n}\n\nstatic int write_firmware(anjay_unlocked_t *anjay,\n                          advanced_fw_repr_t *fw,\n                          advanced_fw_instance_t *inst,\n                          anjay_unlocked_input_ctx_t *ctx,\n                          bool *out_is_reset_request) {\n    assert(inst->state != ANJAY_ADVANCED_FW_UPDATE_STATE_DOWNLOADING);\n    if (user_state_ensure_stream_open(anjay, inst)) {\n        return -1;\n    }\n\n    int result = write_firmware_to_stream(anjay, fw, inst, ctx,\n                                          out_is_reset_request);\n\n    if (result) {\n        reset_user_state(anjay, inst);\n    } else if (!*out_is_reset_request) {\n        // stream_finish_result deliberately not propagated up:\n        // write itself succeeded\n        int stream_finish_result = finish_user_stream(anjay, inst);\n\n        if (stream_finish_result) {\n            handle_err_result(anjay, fw, inst,\n                              ANJAY_ADVANCED_FW_UPDATE_STATE_IDLE,\n                              stream_finish_result,\n                              ANJAY_ADVANCED_FW_UPDATE_RESULT_NOT_ENOUGH_SPACE);\n        } else {\n            update_state_and_update_result(\n                    anjay, fw, inst, ANJAY_ADVANCED_FW_UPDATE_STATE_DOWNLOADED,\n                    ANJAY_ADVANCED_FW_UPDATE_RESULT_INITIAL);\n        }\n    }\n    return result;\n}\n\n#    ifdef ANJAY_WITH_DOWNLOADER\nstatic void download_queue_entry_cleanup(anjay_download_config_t *cfg) {\n    avs_free((void *) (intptr_t) cfg->url);\n    avs_free((void *) cfg->coap_tx_params);\n}\n\nstatic void\ncancel_existing_download_if_in_progress(anjay_unlocked_t *anjay,\n                                        advanced_fw_repr_t *fw,\n                                        advanced_fw_instance_t *inst) {\n    if (inst->state == ANJAY_ADVANCED_FW_UPDATE_STATE_DOWNLOADING) {\n        if (fw->current_download.download_handle\n                && fw->current_download.iid == inst->iid) {\n            _anjay_download_abort_unlocked(\n                    anjay, fw->current_download.download_handle);\n            assert(!fw->current_download.download_handle);\n            fw->current_download.iid = ANJAY_ID_INVALID;\n            fw_log(TRACE,\n                   _(\"Aborted ongoing download for instance \") \"%\" PRIu16,\n                   inst->iid);\n            start_next_download_if_waiting(anjay, fw);\n            return;\n        }\n        AVS_LIST(anjay_download_config_t) *queued_cfg;\n        AVS_LIST_FOREACH_PTR(queued_cfg, &fw->download_queue) {\n            advanced_fw_instance_t *queued_inst =\n                    (advanced_fw_instance_t *) (*queued_cfg)->user_data;\n            if (queued_inst->iid == inst->iid) {\n                download_queue_entry_cleanup(*queued_cfg);\n                AVS_LIST_DELETE(queued_cfg);\n                fw_log(TRACE,\n                       _(\"Removed instance \") \"%\" PRIu16 _(\n                               \" from download queue\"),\n                       inst->iid);\n                return;\n            }\n        }\n    }\n}\n#    endif // ANJAY_WITH_DOWNLOADER\n\nstatic int fw_write(anjay_unlocked_t *anjay,\n                    const anjay_dm_installed_object_t obj_ptr,\n                    anjay_iid_t iid,\n                    anjay_rid_t rid,\n                    anjay_riid_t riid,\n                    anjay_unlocked_input_ctx_t *ctx) {\n    (void) riid;\n\n    advanced_fw_repr_t *fw = get_fw(obj_ptr);\n    advanced_fw_instance_t *inst = get_fw_instance(fw, iid);\n\n    assert(inst);\n\n    switch (rid) {\n    case ADV_FW_RES_PACKAGE: {\n        assert(riid == ANJAY_ID_INVALID);\n\n        int result = 0;\n#    ifdef ANJAY_WITH_DOWNLOADER\n        bool is_any_in_progress = is_any_download_in_progress(fw);\n#    endif // ANJAY_WITH_DOWNLOADER\n\n        if (inst->state == ANJAY_ADVANCED_FW_UPDATE_STATE_UPDATING) {\n            fw_log(WARNING, _(\"cannot set Package resource while updating\"));\n            return ANJAY_ERR_METHOD_NOT_ALLOWED;\n        } else if (inst->state == ANJAY_ADVANCED_FW_UPDATE_STATE_IDLE\n#    ifdef ANJAY_WITH_DOWNLOADER\n                   && !is_any_in_progress\n#    endif // ANJAY_WITH_DOWNLOADER\n        ) {\n            bool is_reset_request = false;\n\n            result = write_firmware(anjay, fw, inst, ctx, &is_reset_request);\n            if (!result && is_reset_request) {\n                reset_state(anjay, fw, inst);\n            }\n        } else {\n            result = expect_single_nullbyte(ctx);\n            if (!result) {\n#    ifdef ANJAY_WITH_DOWNLOADER\n                cancel_existing_download_if_in_progress(anjay, fw, inst);\n#    endif // ANJAY_WITH_DOWNLOADER\n                reset_state(anjay, fw, inst);\n            }\n#    ifdef ANJAY_WITH_DOWNLOADER\n            else if (is_any_in_progress) {\n                fw_log(ERROR, _(\"There is a download already in progress or in \"\n                                \"queue. Rejecting push mode download due do \"\n                                \"implementation limitation\"));\n                return ANJAY_ERR_METHOD_NOT_ALLOWED;\n            }\n#    endif // ANJAY_WITH_DOWNLOADER\n        }\n        return result;\n    }\n    case ADV_FW_RES_PACKAGE_URI: {\n        assert(riid == ANJAY_ID_INVALID);\n        char *new_uri = NULL;\n        int result = _anjay_io_fetch_string(ctx, &new_uri);\n        size_t len = (new_uri ? strlen(new_uri) : 0);\n\n        if (!result && len == 0) {\n            avs_free(new_uri);\n\n            if (inst->state == ANJAY_ADVANCED_FW_UPDATE_STATE_UPDATING) {\n                fw_log(WARNING,\n                       _(\"cannot set Package URI resource while updating\"));\n                return ANJAY_ERR_METHOD_NOT_ALLOWED;\n            }\n\n#    ifdef ANJAY_WITH_DOWNLOADER\n            cancel_existing_download_if_in_progress(anjay, fw, inst);\n#    endif // ANJAY_WITH_DOWNLOADER\n\n            avs_free((void *) (intptr_t) inst->package_uri);\n            inst->package_uri = NULL;\n            reset_state(anjay, fw, inst);\n            return 0;\n        }\n\n        if (!result && inst->state != ANJAY_ADVANCED_FW_UPDATE_STATE_IDLE) {\n            result = ANJAY_ERR_BAD_REQUEST;\n        }\n\n        if (!result\n                && transport_security_from_uri(new_uri)\n                               == ANJAY_TRANSPORT_SECURITY_UNDEFINED) {\n            fw_log(WARNING,\n                   _(\"unsupported download protocol required for uri \") \"%s\",\n                   new_uri);\n            set_update_result(\n                    anjay, inst,\n                    ANJAY_ADVANCED_FW_UPDATE_RESULT_UNSUPPORTED_PROTOCOL);\n#    ifdef ANJAY_WITH_SEND\n            send_state_and_update_result(anjay, fw, iid, false);\n#    endif // ANJAY_WITH_SEND\n            result = ANJAY_ERR_BAD_REQUEST;\n        }\n\n#    ifdef ANJAY_WITH_DOWNLOADER\n        if (!result) {\n            avs_free((void *) (intptr_t) inst->package_uri);\n            inst->package_uri = new_uri;\n            new_uri = NULL;\n\n            int dl_res = schedule_background_anjay_download(anjay, fw, inst);\n\n            if (dl_res) {\n                fw_log(WARNING,\n                       _(\"schedule_download_in_background failed: \") \"%d\",\n                       dl_res);\n            }\n            // write itself succeeded; do not propagate error\n        }\n#    endif // ANJAY_WITH_DOWNLOADER\n\n        avs_free(new_uri);\n\n        return result;\n    }\n    case ADV_FW_RES_SEVERITY: {\n        assert(riid == ANJAY_ID_INVALID);\n\n        int32_t severity = ANJAY_ADVANCED_FW_UPDATE_SEVERITY_MANDATORY;\n\n        if (_anjay_get_i32_unlocked(ctx, &severity)\n                || severity < (int32_t)\n                                      ANJAY_ADVANCED_FW_UPDATE_SEVERITY_CRITICAL\n                || severity > (int32_t)\n                                      ANJAY_ADVANCED_FW_UPDATE_SEVERITY_OPTIONAL) {\n            return ANJAY_ERR_BAD_REQUEST;\n        }\n        inst->severity = (anjay_advanced_fw_update_severity_t) severity;\n        return 0;\n    }\n    case ADV_FW_RES_MAX_DEFER_PERIOD: {\n        assert(riid == ANJAY_ID_INVALID);\n\n        int max_defer_period = 0;\n\n        if (_anjay_get_i32_unlocked(ctx, &max_defer_period)\n                || max_defer_period < 0) {\n            return ANJAY_ERR_BAD_REQUEST;\n        }\n        inst->max_defer_period = max_defer_period;\n        return 0;\n    }\n    default:\n        // Bootstrap Server may try to write to other resources,\n        // so no AVS_UNREACHABLE() here\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\nstatic int fw_resource_instances(anjay_unlocked_t *anjay,\n                                 const anjay_dm_installed_object_t obj_ptr,\n                                 anjay_iid_t iid,\n                                 anjay_rid_t rid,\n                                 anjay_unlocked_dm_list_ctx_t *ctx) {\n    (void) anjay;\n    advanced_fw_repr_t *fw = get_fw(obj_ptr);\n    advanced_fw_instance_t *inst = get_fw_instance(fw, iid);\n    assert(inst);\n\n    switch (rid) {\n    case ADV_FW_RES_UPDATE_PROTOCOL_SUPPORT: {\n        for (anjay_riid_t i = 0; i < AVS_ARRAY_SIZE(SUPPORTED_PROTOCOLS); ++i) {\n            _anjay_dm_emit_unlocked(ctx, i);\n        }\n        return 0;\n    }\n    case ADV_FW_RES_LINKED_INSTANCES: {\n        for (size_t i = 0; i < inst->linked_instances_count; ++i) {\n            _anjay_dm_emit_unlocked(ctx, inst->linked_instances[i]);\n        }\n        return 0;\n    }\n    case ADV_FW_RES_CONFLICTING_INSTANCES: {\n        for (size_t i = 0; i < inst->conflicting_instances_count; ++i) {\n            _anjay_dm_emit_unlocked(ctx, inst->conflicting_instances[i]);\n        }\n        return 0;\n    }\n    default:\n        AVS_UNREACHABLE(\n                \"Attempted to list instances in a single-instance resource\");\n        return ANJAY_ERR_INTERNAL;\n    }\n}\n\nstatic void reset_supplemental_iid_cache(advanced_fw_repr_t *fw) {\n    avs_free(fw->supplemental_iid_cache);\n    fw->supplemental_iid_cache = NULL;\n    fw->supplemental_iid_cache_count = 0;\n}\n\nstruct upgrade_job_args {\n    advanced_fw_repr_t *fw;\n    advanced_fw_instance_t *inst;\n};\n\nstatic void perform_upgrade(avs_sched_t *sched, const void *args_) {\n    struct upgrade_job_args *args =\n            (struct upgrade_job_args *) (intptr_t) args_;\n\n    set_update_deadline(args->inst);\n    anjay_t *anjay_locked = _anjay_get_from_sched(sched);\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    int result =\n            user_state_perform_upgrade(anjay,\n                                       args->inst,\n                                       args->fw->supplemental_iid_cache,\n                                       args->fw->supplemental_iid_cache_count);\n    reset_supplemental_iid_cache(args->fw);\n    if (result) {\n        fw_log(ERROR, _(\"user_state_perform_upgrade() failed: \") \"%d\", result);\n        handle_err_result(anjay, args->fw, args->inst,\n                          ANJAY_ADVANCED_FW_UPDATE_STATE_DOWNLOADED, result,\n                          ANJAY_ADVANCED_FW_UPDATE_RESULT_FAILED);\n    }\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n}\n\nstatic void schedule_upgrade(avs_sched_t *sched, const void *args_) {\n    struct upgrade_job_args *args =\n            (struct upgrade_job_args *) (intptr_t) args_;\n    anjay_t *anjay_locked = _anjay_get_from_sched(sched);\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    // Let's defer actually performing the upgrade to yet another scheduler run\n    // - the notification for the UPDATING state is probably being scheduled in\n    // the current one.\n    if (args->inst->state == ANJAY_ADVANCED_FW_UPDATE_STATE_UPDATING\n            && args->inst->user_state.state\n                           != ANJAY_ADVANCED_FW_UPDATE_STATE_UPDATING\n            && AVS_SCHED_NOW(sched, &args->inst->update_job, perform_upgrade,\n                             args, sizeof(*args))) {\n        reset_supplemental_iid_cache(args->fw);\n        update_state_and_update_result(\n                anjay, args->fw, args->inst,\n                ANJAY_ADVANCED_FW_UPDATE_STATE_DOWNLOADED,\n                ANJAY_ADVANCED_FW_UPDATE_RESULT_OUT_OF_MEMORY);\n    }\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n}\n\nstatic int sort_supplemental_iid_cache(advanced_fw_repr_t *fw) {\n    // Simple insertion sort.\n    // The count should be usually low enough for it to not be a performance\n    // hog.\n    for (size_t i = 1; i < fw->supplemental_iid_cache_count; ++i) {\n        for (ptrdiff_t j = (ptrdiff_t) i; j > 0; --j) {\n            if (fw->supplemental_iid_cache[j - 1]\n                    == fw->supplemental_iid_cache[j]) {\n                fw_log(ERROR, _(\"Duplicate instances specified in \"\n                                \"Firmare Update arguments\"));\n                return ANJAY_ERR_BAD_REQUEST;\n            } else if (fw->supplemental_iid_cache[j - 1]\n                       < fw->supplemental_iid_cache[j]) {\n                break;\n            }\n\n            anjay_iid_t tmp = fw->supplemental_iid_cache[j - 1];\n\n            fw->supplemental_iid_cache[j - 1] = fw->supplemental_iid_cache[j];\n            fw->supplemental_iid_cache[j] = tmp;\n        }\n    }\n    return 0;\n}\n\nstatic int handle_fw_execute_args(advanced_fw_repr_t *fw,\n                                  anjay_iid_t main_iid,\n                                  anjay_unlocked_execute_ctx_t *ctx) {\n    int arg;\n    bool arg_has_value;\n\n    reset_supplemental_iid_cache(fw);\n\n    if (_anjay_execute_get_next_arg_unlocked(ctx, &arg, &arg_has_value)) {\n        return 0;\n    }\n\n    if (arg != 0) {\n        fw_log(ERROR, _(\"Invalid Advanced Firmware Update argument: \") \"%d\",\n               arg);\n        return ANJAY_ERR_BAD_REQUEST;\n    }\n\n    avs_stream_t *supplemental_iid_cache_membuf = avs_stream_membuf_create();\n\n    if (!supplemental_iid_cache_membuf) {\n        _anjay_log_oom();\n        return ANJAY_ERR_INTERNAL;\n    }\n\n    char arg_buf[sizeof(\"</\" AVS_QUOTE_MACRO(ADV_FW_OID) \"/65535>,\")];\n    size_t arg_buf_offset = 0;\n    size_t arg_buf_bytes_read;\n    int result = ANJAY_BUFFER_TOO_SHORT;\n    void *supplemental_iid_cache = NULL;\n    size_t supplemental_iid_cache_size = 0;\n\n    while (true) {\n        if (result == ANJAY_BUFFER_TOO_SHORT\n                && sizeof(arg_buf) - arg_buf_offset > 1) {\n            result = _anjay_execute_get_arg_value_unlocked(\n                    ctx, &arg_buf_bytes_read, arg_buf + arg_buf_offset,\n                    sizeof(arg_buf) - arg_buf_offset);\n            if (result && result != ANJAY_BUFFER_TOO_SHORT) {\n                fw_log(ERROR,\n                       _(\"Error while reading Advanced Firmware Update \"\n                         \"arguments\"));\n                goto finish;\n            }\n        }\n\n        if (arg_buf_offset == 0 && arg_buf_bytes_read == 0) {\n            break;\n        }\n\n        anjay_oid_t supplemental_oid;\n        anjay_iid_t supplemental_iid;\n        advanced_fw_instance_t *supplemental_inst = NULL;\n        int char_count = 0;\n        int scanf_result =\n                sscanf(arg_buf, \"</%\" SCNu16 \"/%\" SCNu16 \">%n\",\n                       &supplemental_oid, &supplemental_iid, &char_count);\n\n        if (scanf_result == 2 && char_count\n                && supplemental_oid == ANJAY_ADVANCED_FW_UPDATE_OID\n                && supplemental_iid != main_iid\n                && supplemental_iid != ANJAY_ID_INVALID) {\n            supplemental_inst = get_fw_instance(fw, supplemental_iid);\n        }\n\n        if (!supplemental_inst) {\n            fw_log(ERROR, _(\"Invalid argument for Advanced Firmware Update\"));\n            result = ANJAY_ERR_BAD_REQUEST;\n            goto finish;\n        }\n\n        if (supplemental_inst->state\n                != ANJAY_ADVANCED_FW_UPDATE_STATE_DOWNLOADED) {\n            fw_log(WARNING,\n                   _(\"Advanced Firmware Update including supplemental \"\n                     \"instance \") \"%\" PRIu16 _(\" requested, but firmware not \"\n                                               \"yet downloaded (state \"\n                                               \"= \") \"%d\" _(\")\"),\n                   supplemental_iid, supplemental_inst->state);\n            result = ANJAY_ERR_METHOD_NOT_ALLOWED;\n            goto finish;\n        }\n\n        if (avs_is_err(avs_stream_write(supplemental_iid_cache_membuf,\n                                        &supplemental_iid,\n                                        sizeof(supplemental_iid)))) {\n            _anjay_log_oom();\n            result = ANJAY_ERR_INTERNAL;\n            goto finish;\n        }\n\n        if (!result && !arg_buf[char_count]) {\n            break;\n        } else if (arg_buf[char_count] == ',') {\n            memmove(arg_buf, arg_buf + char_count + 1,\n                    sizeof(arg_buf) - (size_t) char_count - 1);\n            arg_buf_offset += arg_buf_bytes_read;\n            arg_buf_offset -= (size_t) char_count + 1;\n        } else {\n            fw_log(ERROR, _(\"Invalid argument for Advanced Firmware Update\"));\n            result = ANJAY_ERR_BAD_REQUEST;\n            goto finish;\n        }\n    }\n\n    if (!fw->supplemental_iid_cache_count) {\n        // empty list is different from non-existing one in this case\n        // let's allocated a dummy byte so that the resulting pointer is not\n        // NULL\n        avs_stream_write(supplemental_iid_cache_membuf, &(uint8_t) { 0 }, 1);\n    }\n\n    avs_stream_membuf_fit(supplemental_iid_cache_membuf);\n\n    if (avs_is_err(\n                avs_stream_membuf_take_ownership(supplemental_iid_cache_membuf,\n                                                 &supplemental_iid_cache,\n                                                 &supplemental_iid_cache_size))\n            || !supplemental_iid_cache) {\n        fw_log(ERROR, _(\"Could not take ownership of the buffer\"));\n        result = ANJAY_ERR_INTERNAL;\n        goto finish;\n    }\n\n    fw->supplemental_iid_cache = (anjay_iid_t *) supplemental_iid_cache;\n    fw->supplemental_iid_cache_count =\n            supplemental_iid_cache_size / sizeof(anjay_iid_t);\n\n    result = sort_supplemental_iid_cache(fw);\n\n    if (!result\n            && _anjay_execute_get_next_arg_unlocked(ctx, &arg, &arg_has_value)\n                           != ANJAY_EXECUTE_GET_ARG_END) {\n        fw_log(ERROR, _(\"Superfluous Advanced Firmware Update argument: \") \"%d\",\n               arg);\n        result = ANJAY_ERR_BAD_REQUEST;\n    }\n\nfinish:\n    avs_stream_cleanup(&supplemental_iid_cache_membuf);\n\n    if (result) {\n        reset_supplemental_iid_cache(fw);\n    }\n    return result;\n}\n\nstatic int fw_execute(anjay_unlocked_t *anjay,\n                      const anjay_dm_installed_object_t obj_ptr,\n                      anjay_iid_t iid,\n                      anjay_rid_t rid,\n                      anjay_unlocked_execute_ctx_t *ctx) {\n    advanced_fw_repr_t *fw = get_fw(obj_ptr);\n    advanced_fw_instance_t *inst = get_fw_instance(fw, iid);\n    assert(inst);\n\n    switch (rid) {\n    case ADV_FW_RES_UPDATE: {\n        if (inst->state != ANJAY_ADVANCED_FW_UPDATE_STATE_DOWNLOADED) {\n            fw_log(WARNING,\n                   _(\"Advanced Firmware Update for instance \") \"%\" PRIu16 _(\n                           \" requested, but firmware not yet downloaded \"\n                           \"(state = \") \"%d\" _(\")\"),\n                   iid, inst->state);\n            return ANJAY_ERR_METHOD_NOT_ALLOWED;\n        }\n\n        int result = handle_fw_execute_args(fw, iid, ctx);\n\n        if (result) {\n            return result;\n        }\n\n        update_state_and_update_result(anjay, fw, inst,\n                                       ANJAY_ADVANCED_FW_UPDATE_STATE_UPDATING,\n                                       ANJAY_ADVANCED_FW_UPDATE_RESULT_INITIAL);\n        // NOTE: This has to be called after update_state_and_update_result(),\n        // to make sure that schedule_upgrade() is called after notify_clb()\n        // and consequently, perform_upgrade() is called after trigger_observe()\n        // (if it's not delayed due to pmin).\n        const struct upgrade_job_args upgrade_job_args = {\n            .fw = fw,\n            .inst = inst\n        };\n\n        if (AVS_SCHED_NOW(_anjay_get_scheduler_unlocked(anjay),\n                          &inst->update_job, schedule_upgrade,\n                          &upgrade_job_args, sizeof(upgrade_job_args))) {\n            fw_log(WARNING, _(\"Could not schedule the upgrade job\"));\n            update_state_and_update_result(\n                    anjay, fw, inst, ANJAY_ADVANCED_FW_UPDATE_STATE_DOWNLOADED,\n                    ANJAY_ADVANCED_FW_UPDATE_RESULT_OUT_OF_MEMORY);\n            reset_supplemental_iid_cache(fw);\n            return ANJAY_ERR_INTERNAL;\n        }\n        return 0;\n    }\n    case ADV_FW_RES_CANCEL:\n        if (inst->state != ANJAY_ADVANCED_FW_UPDATE_STATE_DOWNLOADING\n                && inst->state != ANJAY_ADVANCED_FW_UPDATE_STATE_DOWNLOADED) {\n            fw_log(WARNING,\n                   _(\"Advanced Firmware Update Cancel requested, but the \"\n                     \"firmware is being installed or has already been \"\n                     \"installed (state = \") \"%d\" _(\")\"),\n                   inst->state);\n            return ANJAY_ERR_METHOD_NOT_ALLOWED;\n        }\n#    ifdef ANJAY_WITH_DOWNLOADER\n        cancel_existing_download_if_in_progress(anjay, fw, inst);\n#    endif // ANJAY_WITH_DOWNLOADER\n\n        reset_user_state(anjay, inst);\n        update_state_and_update_result(\n                anjay, fw, inst, ANJAY_ADVANCED_FW_UPDATE_STATE_IDLE,\n                ANJAY_ADVANCED_FW_UPDATE_RESULT_UPDATE_CANCELLED);\n        return 0;\n    default:\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\nstatic int fw_transaction_noop(anjay_unlocked_t *anjay,\n                               const anjay_dm_installed_object_t obj_ptr) {\n    (void) anjay;\n    (void) obj_ptr;\n    return 0;\n}\n\nstatic const anjay_unlocked_dm_object_def_t FIRMWARE_UPDATE = {\n    .oid = ANJAY_ADVANCED_FW_UPDATE_OID,\n    .handlers = {\n        .list_instances = fw_list_instances,\n        .list_resources = fw_list_resources,\n        .resource_read = fw_read,\n        .resource_write = fw_write,\n        .list_resource_instances = fw_resource_instances,\n        .resource_execute = fw_execute,\n        .transaction_begin = fw_transaction_noop,\n        .transaction_validate = fw_transaction_noop,\n        .transaction_commit = fw_transaction_noop,\n        .transaction_rollback = fw_transaction_noop\n    }\n};\n\nstatic AVS_LIST(advanced_fw_instance_t) initialize_fw_instance(\n        anjay_unlocked_t *anjay,\n        advanced_fw_repr_t *fw,\n        anjay_iid_t iid,\n        const char *component_name,\n        const anjay_advanced_fw_update_handlers_t *handlers,\n        void *user_arg,\n        const anjay_advanced_fw_update_initial_state_t *initial_state) {\n    AVS_LIST(advanced_fw_instance_t) inst =\n            AVS_LIST_NEW_ELEMENT(advanced_fw_instance_t);\n\n    if (!inst) {\n        _anjay_log_oom();\n        return NULL;\n    }\n\n    inst->iid = iid;\n    inst->component_name = component_name;\n    inst->user_state.handlers = handlers;\n    inst->user_state.arg = user_arg;\n\n    if (!initial_state) {\n        return inst;\n    }\n\n    inst->severity = initial_state->persisted_severity;\n    inst->last_state_change_time =\n            initial_state->persisted_last_state_change_time;\n    inst->update_deadline = initial_state->persisted_update_deadline;\n\n    if ((initial_state->state != ANJAY_ADVANCED_FW_UPDATE_STATE_IDLE\n         && initial_state->result != ANJAY_ADVANCED_FW_UPDATE_RESULT_INITIAL)\n            || (initial_state->state == ANJAY_ADVANCED_FW_UPDATE_STATE_IDLE\n                && initial_state->result\n                           != ANJAY_ADVANCED_FW_UPDATE_RESULT_INITIAL\n                && initial_state->result\n                           != ANJAY_ADVANCED_FW_UPDATE_RESULT_SUCCESS\n                && initial_state->result\n                           != ANJAY_ADVANCED_FW_UPDATE_RESULT_INTEGRITY_FAILURE\n                && initial_state->result\n                           != ANJAY_ADVANCED_FW_UPDATE_RESULT_FAILED\n                && initial_state->result\n                           != ANJAY_ADVANCED_FW_UPDATE_RESULT_DEPENDENCY_ERROR)) {\n        fw_log(ERROR,\n               _(\"Invalid initial_state->result for the specified \"\n                 \"initial_state->state\"));\n        goto fail;\n    }\n\n    switch (initial_state->state) {\n    case ANJAY_ADVANCED_FW_UPDATE_STATE_IDLE:\n        inst->result = initial_state->result;\n        return inst;\n    case ANJAY_ADVANCED_FW_UPDATE_STATE_DOWNLOADING: {\n#    ifdef ANJAY_WITH_DOWNLOADER\n        inst->user_state.state = ANJAY_ADVANCED_FW_UPDATE_STATE_DOWNLOADING;\n        reset_user_state(anjay, inst);\n        if (inst->result == ANJAY_ADVANCED_FW_UPDATE_RESULT_CONNECTION_LOST\n                && schedule_background_anjay_download(anjay, fw, inst)) {\n            fw_log(WARNING, _(\"Could not retry firmware download\"));\n        }\n#    else  // ANJAY_WITH_DOWNLOADER\n        (void) anjay;\n        (void) fw;\n        fw_log(WARNING,\n               _(\"Unable to resume download: PULL download not supported\"));\n#    endif // ANJAY_WITH_DOWNLOADER\n        return inst;\n    }\n    case ANJAY_ADVANCED_FW_UPDATE_STATE_DOWNLOADED:\n        inst->user_state.state = ANJAY_ADVANCED_FW_UPDATE_STATE_DOWNLOADED;\n        inst->state = ANJAY_ADVANCED_FW_UPDATE_STATE_DOWNLOADED;\n        return inst;\n    case ANJAY_ADVANCED_FW_UPDATE_STATE_UPDATING:\n        inst->user_state.state = ANJAY_ADVANCED_FW_UPDATE_STATE_UPDATING;\n        inst->state = ANJAY_ADVANCED_FW_UPDATE_STATE_UPDATING;\n        inst->result = ANJAY_ADVANCED_FW_UPDATE_RESULT_INITIAL;\n        return inst;\n    }\n\n    fw_log(ERROR, _(\"Invalid initial_state->state\"));\nfail:\n    AVS_LIST_DELETE(&inst);\n    return NULL;\n}\n\nstatic void fw_delete(void *fw_) {\n    advanced_fw_repr_t *fw = (advanced_fw_repr_t *) fw_;\n    AVS_LIST_CLEAR(&fw->instances) {\n        AVS_LIST(advanced_fw_instance_t) inst = fw->instances;\n        avs_sched_del(&inst->update_job);\n#    ifdef ANJAY_WITH_DOWNLOADER\n        avs_sched_del(&inst->resume_download_job);\n#    endif // ANJAY_WITH_DOWNLOADER\n        avs_free(inst->linked_instances);\n        avs_free(inst->conflicting_instances);\n        avs_free((void *) (intptr_t) inst->package_uri);\n    }\n#    ifdef ANJAY_WITH_DOWNLOADER\n    AVS_LIST_CLEAR(&fw->download_queue) {\n        download_queue_entry_cleanup(fw->download_queue);\n    }\n#    endif // ANJAY_WITH_DOWNLOADER\n    // NOTE: fw itself will be freed when cleaning the objects list\n}\n\nint anjay_advanced_fw_update_install(\n        anjay_t *anjay_locked,\n        const anjay_advanced_fw_update_global_config_t *config) {\n    assert(anjay_locked);\n    int result = -1;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    AVS_LIST(advanced_fw_repr_t) repr =\n            AVS_LIST_NEW_ELEMENT(advanced_fw_repr_t);\n    if (!repr) {\n        _anjay_log_oom();\n    } else {\n        repr->def = &FIRMWARE_UPDATE;\n        repr->current_download.iid = ANJAY_ID_INVALID;\n        if (config) {\n#    ifdef ANJAY_WITH_DOWNLOADER\n            repr->prefer_same_socket_downloads =\n                    config->prefer_same_socket_downloads;\n#    endif // ANJAY_WITH_DOWNLOADER\n#    ifdef ANJAY_WITH_SEND\n            repr->use_lwm2m_send = config->use_lwm2m_send;\n#    endif // ANJAY_WITH_SEND\n        }\n        _anjay_dm_installed_object_init_unlocked(&repr->def_ptr, &repr->def);\n        if (!_anjay_dm_module_install(anjay, fw_delete, repr)) {\n            _ANJAY_ASSERT_INSTALLED_OBJECT_IS_FIRST_FIELD(advanced_fw_repr_t,\n                                                          def_ptr);\n            AVS_LIST(anjay_dm_installed_object_t) entry = &repr->def_ptr;\n            if (_anjay_register_object_unlocked(anjay, &entry)) {\n                result = _anjay_dm_module_uninstall(anjay, fw_delete);\n                assert(!result);\n                result = -1;\n            } else {\n                result = 0;\n            }\n        }\n        if (result) {\n            AVS_LIST_CLEAR(&repr);\n        }\n    }\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return result;\n}\n\nint anjay_advanced_fw_update_instance_add(\n        anjay_t *anjay_locked,\n        anjay_iid_t iid,\n        const char *component_name,\n        const anjay_advanced_fw_update_handlers_t *handlers,\n        void *user_arg,\n        const anjay_advanced_fw_update_initial_state_t *initial_state) {\n    assert(anjay_locked);\n    assert(handlers);\n    int retval = -1;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    const anjay_dm_installed_object_t *obj =\n            _anjay_dm_find_object_by_oid(_anjay_get_dm(anjay),\n                                         ANJAY_ADVANCED_FW_UPDATE_OID);\n    if (!obj) {\n        fw_log(WARNING, _(\"Advanced Firmware Update object not installed\"));\n    } else {\n        advanced_fw_repr_t *fw = get_fw(*obj);\n\n        assert(fw);\n        advanced_fw_instance_t **inst_insert_ptr = &fw->instances;\n\n        AVS_LIST_ITERATE_PTR(inst_insert_ptr) {\n            if ((*inst_insert_ptr)->iid >= iid) {\n                break;\n            }\n        }\n\n        if (*inst_insert_ptr && (*inst_insert_ptr)->iid == iid) {\n            fw_log(ERROR, _(\"Instance already initialized\"));\n        } else if ((fw->instances || iid != 0)\n                   && (!component_name || !handlers->get_current_version)) {\n            fw_log(ERROR,\n                   _(\"Component Name and Current Version is mandatory if \"\n                     \"multiple instances are present\"));\n        } else {\n            AVS_LIST(advanced_fw_instance_t) inst =\n                    initialize_fw_instance(anjay, fw, iid, component_name,\n                                           handlers, user_arg, initial_state);\n\n            if (inst) {\n                AVS_LIST_INSERT(inst_insert_ptr, inst);\n                retval = 0;\n\n#    ifdef ANJAY_WITH_SEND\n                if (initial_state->state != ANJAY_ADVANCED_FW_UPDATE_STATE_IDLE\n                        || initial_state->result\n                                       != ANJAY_ADVANCED_FW_UPDATE_RESULT_INITIAL) {\n                    send_state_and_update_result(anjay, fw, iid, true);\n                }\n#    endif // ANJAY_WITH_SEND\n            }\n        }\n    }\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return retval;\n}\n\nstatic bool\nis_state_change_allowed(anjay_advanced_fw_update_state_t current_state,\n                        anjay_advanced_fw_update_state_t new_state,\n                        anjay_advanced_fw_update_result_t new_result) {\n    switch (current_state) {\n    case ANJAY_ADVANCED_FW_UPDATE_STATE_IDLE:\n        return (((new_state == ANJAY_ADVANCED_FW_UPDATE_STATE_DOWNLOADING\n                  || new_state == ANJAY_ADVANCED_FW_UPDATE_STATE_DOWNLOADED)\n                 && new_result == ANJAY_ADVANCED_FW_UPDATE_RESULT_INITIAL)\n                || (new_state == ANJAY_ADVANCED_FW_UPDATE_STATE_DOWNLOADED\n                    && new_result == ANJAY_ADVANCED_FW_UPDATE_RESULT_DEFERRED));\n    case ANJAY_ADVANCED_FW_UPDATE_STATE_DOWNLOADING:\n        return (new_state == ANJAY_ADVANCED_FW_UPDATE_STATE_IDLE\n                && new_result != ANJAY_ADVANCED_FW_UPDATE_RESULT_SUCCESS\n                && new_result != ANJAY_ADVANCED_FW_UPDATE_RESULT_DEFERRED)\n               || (new_state == ANJAY_ADVANCED_FW_UPDATE_STATE_DOWNLOADED\n                   && (new_result == ANJAY_ADVANCED_FW_UPDATE_RESULT_INITIAL\n                       || new_result\n                                  == ANJAY_ADVANCED_FW_UPDATE_RESULT_DEFERRED));\n    case ANJAY_ADVANCED_FW_UPDATE_STATE_DOWNLOADED:\n        return (new_state == ANJAY_ADVANCED_FW_UPDATE_STATE_IDLE\n                && (new_result == ANJAY_ADVANCED_FW_UPDATE_RESULT_INITIAL\n                    || new_result\n                               == ANJAY_ADVANCED_FW_UPDATE_RESULT_UPDATE_CANCELLED))\n               || (new_state == ANJAY_ADVANCED_FW_UPDATE_STATE_DOWNLOADED\n                   && new_result == ANJAY_ADVANCED_FW_UPDATE_RESULT_DEFERRED)\n               || (new_state == ANJAY_ADVANCED_FW_UPDATE_STATE_UPDATING\n                   && new_result == ANJAY_ADVANCED_FW_UPDATE_RESULT_INITIAL);\n    case ANJAY_ADVANCED_FW_UPDATE_STATE_UPDATING:\n        return (new_state == ANJAY_ADVANCED_FW_UPDATE_STATE_IDLE\n                && new_result\n                           != ANJAY_ADVANCED_FW_UPDATE_RESULT_UPDATE_CANCELLED\n                && new_result != ANJAY_ADVANCED_FW_UPDATE_RESULT_DEFERRED\n                && new_result\n                           != ANJAY_ADVANCED_FW_UPDATE_RESULT_CONFLICTING_STATE)\n               || (new_state == ANJAY_ADVANCED_FW_UPDATE_STATE_DOWNLOADED\n                   && (new_result == ANJAY_ADVANCED_FW_UPDATE_RESULT_FAILED\n                       || new_result == ANJAY_ADVANCED_FW_UPDATE_RESULT_DEFERRED\n                       || new_result\n                                  == ANJAY_ADVANCED_FW_UPDATE_RESULT_DEPENDENCY_ERROR));\n    }\n    AVS_UNREACHABLE(\"invalid enum value\");\n    return false;\n}\n\nint anjay_advanced_fw_update_set_state_and_result(\n        anjay_t *anjay_locked,\n        anjay_iid_t iid,\n        anjay_advanced_fw_update_state_t state,\n        anjay_advanced_fw_update_result_t result) {\n    assert(anjay_locked);\n    int retval = -1;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    const anjay_dm_installed_object_t *obj =\n            _anjay_dm_find_object_by_oid(_anjay_get_dm(anjay),\n                                         ANJAY_ADVANCED_FW_UPDATE_OID);\n    if (!obj) {\n        fw_log(WARNING, _(\"Advanced Firmware Update object not installed\"));\n    } else {\n        advanced_fw_repr_t *fw = get_fw(*obj);\n\n        assert(fw);\n        advanced_fw_instance_t *inst = get_fw_instance(fw, iid);\n\n        if (!inst) {\n            fw_log(ERROR, _(\"Instance does not exist\"));\n        } else if (!is_state_change_allowed(inst->state, state, result)) {\n            fw_log(WARNING,\n                   _(\"Advanced Firmware Update State and Result change \"\n                     \"from \") \"%d/%d\" _(\" to \") \"%d/%d\" _(\" is not \"\n                                                          \"allowed\"),\n                   (int) inst->state, (int) inst->result, (int) state,\n                   (int) result);\n        } else {\n            if (state == ANJAY_ADVANCED_FW_UPDATE_STATE_IDLE) {\n                reset_user_state(anjay, inst);\n            }\n            update_state_and_update_result(anjay, fw, inst, state, result);\n            retval = 0;\n        }\n    }\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return retval;\n}\n\nint anjay_advanced_fw_update_get_state(\n        anjay_t *anjay_locked,\n        anjay_iid_t iid,\n        anjay_advanced_fw_update_state_t *out_state) {\n    assert(anjay_locked);\n    assert(out_state);\n    int retval = -1;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    const anjay_dm_installed_object_t *obj =\n            _anjay_dm_find_object_by_oid(_anjay_get_dm(anjay),\n                                         ANJAY_ADVANCED_FW_UPDATE_OID);\n    if (!obj) {\n        fw_log(WARNING, _(\"Advanced Firmware Update object not installed\"));\n    } else {\n        advanced_fw_repr_t *fw = get_fw(*obj);\n\n        assert(fw);\n        advanced_fw_instance_t *inst = get_fw_instance(fw, iid);\n\n        if (!inst) {\n            fw_log(ERROR, _(\"Instance does not exist\"));\n        } else {\n            *out_state = inst->state;\n            retval = 0;\n        }\n    }\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return retval;\n}\n\nint anjay_advanced_fw_update_get_result(\n        anjay_t *anjay_locked,\n        anjay_iid_t iid,\n        anjay_advanced_fw_update_result_t *out_result) {\n    assert(anjay_locked);\n    assert(out_result);\n    int retval = -1;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    const anjay_dm_installed_object_t *obj =\n            _anjay_dm_find_object_by_oid(_anjay_get_dm(anjay),\n                                         ANJAY_ADVANCED_FW_UPDATE_OID);\n    if (!obj) {\n        fw_log(WARNING, _(\"Advanced Firmware Update object not installed\"));\n    } else {\n        advanced_fw_repr_t *fw = get_fw(*obj);\n\n        assert(fw);\n        advanced_fw_instance_t *inst = get_fw_instance(fw, iid);\n\n        if (!inst) {\n            fw_log(ERROR, _(\"Instance does not exist\"));\n        } else {\n            *out_result = inst->result;\n            retval = 0;\n        }\n    }\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return retval;\n}\n\nstatic int validate_target_iid_list(advanced_fw_repr_t *fw,\n                                    anjay_iid_t iid,\n                                    const anjay_iid_t *target_iids,\n                                    size_t target_iids_count) {\n    for (size_t i = 1; i < target_iids_count; ++i) {\n        if (target_iids[i - 1] == target_iids[i]) {\n            fw_log(ERROR, _(\"Duplicate target instance\"));\n            return -1;\n        } else if (target_iids[i - 1] > target_iids[i]) {\n            fw_log(ERROR, _(\"Target instance list not sorted\"));\n            return -1;\n        }\n    }\n\n    AVS_LIST(advanced_fw_instance_t) inst = fw->instances;\n\n    for (size_t i = 0; i < target_iids_count; ++i) {\n        if (target_iids[i] == iid) {\n            fw_log(ERROR, _(\"Linked Instances or Conflicting Instances \"\n                            \"cannot reference self\"));\n            return -1;\n        }\n        while (inst && inst->iid < target_iids[i]) {\n            AVS_LIST_ADVANCE(&inst);\n        }\n        if (!inst || inst->iid != target_iids[i]) {\n            fw_log(ERROR, _(\"Target instance does not exist\"));\n            return -1;\n        }\n    }\n\n    return 0;\n}\n\nstatic int copy_target_iid_list(anjay_iid_t **out_instances,\n                                size_t *out_size,\n                                const anjay_iid_t *target_iids,\n                                size_t target_iids_count) {\n    anjay_iid_t *new_ptr = NULL;\n\n    if (!target_iids_count) {\n        avs_free(*out_instances);\n    } else {\n        new_ptr = (anjay_iid_t *) avs_realloc(\n                *out_instances, target_iids_count * sizeof(anjay_iid_t));\n    }\n\n    if (target_iids_count && !new_ptr) {\n        _anjay_log_oom();\n        return -1;\n    }\n\n    *out_instances = new_ptr;\n    *out_size = target_iids_count;\n    if (target_iids_count) {\n        memcpy(*out_instances, target_iids,\n               target_iids_count * sizeof(anjay_iid_t));\n    }\n\n    return 0;\n}\n\nint anjay_advanced_fw_update_set_linked_instances(\n        anjay_t *anjay_locked,\n        anjay_iid_t iid,\n        const anjay_iid_t *target_iids,\n        size_t target_iids_count) {\n    assert(anjay_locked);\n    int retval = -1;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    const anjay_dm_installed_object_t *obj =\n            _anjay_dm_find_object_by_oid(_anjay_get_dm(anjay),\n                                         ANJAY_ADVANCED_FW_UPDATE_OID);\n    if (!obj) {\n        fw_log(WARNING, _(\"Advanced Firmware Update object not installed\"));\n    } else {\n        advanced_fw_repr_t *fw = get_fw(*obj);\n\n        assert(fw);\n        advanced_fw_instance_t *inst = get_fw_instance(fw, iid);\n\n        if (!inst) {\n            fw_log(ERROR, _(\"Instance does not exist\"));\n        } else {\n            retval = validate_target_iid_list(fw, iid, target_iids,\n                                              target_iids_count);\n\n            if (!retval) {\n                retval = copy_target_iid_list(&inst->linked_instances,\n                                              &inst->linked_instances_count,\n                                              target_iids, target_iids_count);\n            }\n\n            if (!retval) {\n                _anjay_notify_changed_unlocked(anjay,\n                                               ANJAY_ADVANCED_FW_UPDATE_OID,\n                                               iid,\n                                               ADV_FW_RES_LINKED_INSTANCES);\n            }\n        }\n    }\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return retval;\n}\n\nint anjay_advanced_fw_update_get_linked_instances(\n        anjay_t *anjay_locked,\n        anjay_iid_t iid,\n        const anjay_iid_t **out_target_iids,\n        size_t *out_target_iids_count) {\n    assert(anjay_locked);\n\n    int retval = -1;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    const anjay_dm_installed_object_t *obj =\n            _anjay_dm_find_object_by_oid(_anjay_get_dm(anjay),\n                                         ANJAY_ADVANCED_FW_UPDATE_OID);\n    if (!obj) {\n        fw_log(WARNING, _(\"Advanced Firmware Update object not installed\"));\n    } else {\n        advanced_fw_repr_t *fw = get_fw(*obj);\n\n        assert(fw);\n        advanced_fw_instance_t *inst = get_fw_instance(fw, iid);\n\n        if (!inst) {\n            fw_log(ERROR, _(\"Instance does not exist\"));\n        } else {\n            *out_target_iids = inst->linked_instances;\n            *out_target_iids_count = inst->linked_instances_count;\n            retval = 0;\n        }\n    }\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return retval;\n}\n\nint anjay_advanced_fw_update_set_conflicting_instances(\n        anjay_t *anjay_locked,\n        anjay_iid_t iid,\n        const anjay_iid_t *target_iids,\n        size_t target_iids_count) {\n    assert(anjay_locked);\n    int retval = -1;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    const anjay_dm_installed_object_t *obj =\n            _anjay_dm_find_object_by_oid(_anjay_get_dm(anjay),\n                                         ANJAY_ADVANCED_FW_UPDATE_OID);\n    if (!obj) {\n        fw_log(WARNING, _(\"Advanced Firmware Update object not installed\"));\n    } else {\n        advanced_fw_repr_t *fw = get_fw(*obj);\n\n        assert(fw);\n        advanced_fw_instance_t *inst = get_fw_instance(fw, iid);\n\n        if (!inst) {\n            fw_log(ERROR, _(\"Instance does not exist\"));\n        } else {\n            retval = validate_target_iid_list(fw, iid, target_iids,\n                                              target_iids_count);\n\n            if (!retval) {\n                retval =\n                        copy_target_iid_list(&inst->conflicting_instances,\n                                             &inst->conflicting_instances_count,\n                                             target_iids, target_iids_count);\n            }\n\n            if (!retval) {\n                _anjay_notify_changed_unlocked(\n                        anjay, ANJAY_ADVANCED_FW_UPDATE_OID, iid,\n                        ADV_FW_RES_CONFLICTING_INSTANCES);\n            }\n        }\n    }\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return retval;\n}\n\nint anjay_advanced_fw_update_get_conflicting_instances(\n        anjay_t *anjay_locked,\n        anjay_iid_t iid,\n        const anjay_iid_t **out_target_iids,\n        size_t *out_target_iids_count) {\n    assert(anjay_locked);\n    int retval = -1;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    const anjay_dm_installed_object_t *obj =\n            _anjay_dm_find_object_by_oid(_anjay_get_dm(anjay),\n                                         ANJAY_ADVANCED_FW_UPDATE_OID);\n    if (!obj) {\n        fw_log(WARNING, _(\"Advanced Firmware Update object not installed\"));\n    } else {\n        advanced_fw_repr_t *fw = get_fw(*obj);\n\n        assert(fw);\n        advanced_fw_instance_t *inst = get_fw_instance(fw, iid);\n\n        if (!inst) {\n            fw_log(ERROR, _(\"Instance does not exist\"));\n        } else {\n            *out_target_iids = inst->conflicting_instances;\n            *out_target_iids_count = inst->conflicting_instances_count;\n            retval = 0;\n        }\n    }\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return retval;\n}\n\navs_time_real_t anjay_advanced_fw_update_get_deadline(anjay_t *anjay_locked,\n                                                      anjay_iid_t iid) {\n    assert(anjay_locked);\n    avs_time_real_t result = AVS_TIME_REAL_INVALID;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    const anjay_dm_installed_object_t *obj =\n            _anjay_dm_find_object_by_oid(_anjay_get_dm(anjay),\n                                         ANJAY_ADVANCED_FW_UPDATE_OID);\n    if (!obj) {\n        fw_log(WARNING, _(\"Advanced Firmware Update object not installed\"));\n    } else {\n        advanced_fw_repr_t *fw = get_fw(*obj);\n\n        assert(fw);\n        advanced_fw_instance_t *inst = get_fw_instance(fw, iid);\n\n        if (!inst) {\n            fw_log(ERROR, _(\"Instance does not exist\"));\n        } else {\n            result = inst->update_deadline;\n        }\n    }\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return result;\n}\n\nanjay_advanced_fw_update_severity_t\nanjay_advanced_fw_update_get_severity(anjay_t *anjay_locked, anjay_iid_t iid) {\n    assert(anjay_locked);\n    anjay_advanced_fw_update_severity_t result =\n            ANJAY_ADVANCED_FW_UPDATE_SEVERITY_MANDATORY;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    const anjay_dm_installed_object_t *obj =\n            _anjay_dm_find_object_by_oid(_anjay_get_dm(anjay),\n                                         ANJAY_ADVANCED_FW_UPDATE_OID);\n    if (!obj) {\n        fw_log(WARNING, _(\"Advanced Firmware Update object not installed\"));\n    } else {\n        advanced_fw_repr_t *fw = get_fw(*obj);\n\n        assert(fw);\n        advanced_fw_instance_t *inst = get_fw_instance(fw, iid);\n\n        if (!inst) {\n            fw_log(ERROR, _(\"Instance does not exist\"));\n        } else {\n            result = inst->severity;\n        }\n    }\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return result;\n}\n\navs_time_real_t\nanjay_advanced_fw_update_get_last_state_change_time(anjay_t *anjay_locked,\n                                                    anjay_iid_t iid) {\n    assert(anjay_locked);\n    avs_time_real_t result = AVS_TIME_REAL_INVALID;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    const anjay_dm_installed_object_t *obj =\n            _anjay_dm_find_object_by_oid(_anjay_get_dm(anjay),\n                                         ANJAY_ADVANCED_FW_UPDATE_OID);\n    if (!obj) {\n        fw_log(WARNING, _(\"Advanced Firmware Update object not installed\"));\n    } else {\n        advanced_fw_repr_t *fw = get_fw(*obj);\n\n        assert(fw);\n        advanced_fw_instance_t *inst = get_fw_instance(fw, iid);\n\n        if (!inst) {\n            fw_log(ERROR, _(\"Instance does not exist\"));\n        } else {\n            result = inst->last_state_change_time;\n        }\n    }\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return result;\n}\n\n#    ifdef ANJAY_WITH_DOWNLOADER\nvoid anjay_advanced_fw_update_pull_suspend(anjay_t *anjay_locked) {\n    assert(anjay_locked);\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    const anjay_dm_installed_object_t *obj =\n            _anjay_dm_find_object_by_oid(_anjay_get_dm(anjay),\n                                         ANJAY_ADVANCED_FW_UPDATE_OID);\n    if (!obj) {\n        fw_log(WARNING, _(\"Advanced Firmware Update object not installed\"));\n    } else {\n        advanced_fw_repr_t *fw = get_fw(*obj);\n        assert(fw);\n        if (fw->current_download.download_handle) {\n            _anjay_download_suspend_unlocked(\n                    anjay, fw->current_download.download_handle);\n        }\n        fw->downloads_suspended = true;\n    }\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n}\n\nint anjay_advanced_fw_update_pull_reconnect(anjay_t *anjay_locked) {\n    assert(anjay_locked);\n    int result = -1;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    const anjay_dm_installed_object_t *obj =\n            _anjay_dm_find_object_by_oid(_anjay_get_dm(anjay),\n                                         ANJAY_ADVANCED_FW_UPDATE_OID);\n    if (!obj) {\n        fw_log(WARNING, _(\"Advanced Firmware Update object not installed\"));\n    } else {\n        advanced_fw_repr_t *fw = get_fw(*obj);\n        assert(fw);\n        fw->downloads_suspended = false;\n        if (fw->current_download.download_handle) {\n            result = _anjay_download_reconnect_unlocked(\n                    anjay, fw->current_download.download_handle);\n        } else {\n            result = 0;\n        }\n    }\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return result;\n}\n#    endif // ANJAY_WITH_DOWNLOADER\n\n#endif // ANJAY_WITH_MODULE_ADVANCED_FW_UPDATE\n"
  },
  {
    "path": "src/modules/factory_provisioning/anjay_provisioning.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n#include <anjay_init.h>\n\n#ifdef ANJAY_WITH_MODULE_FACTORY_PROVISIONING\n\n#    ifndef ANJAY_WITH_CBOR\n#        error \"CBOR content format must be enabled to use factory provisioning\"\n#    endif // ANJAY_WITH_CBOR\n\n#    include <avsystem/commons/avs_errno.h>\n#    include <avsystem/commons/avs_stream_membuf.h>\n#    include <avsystem/commons/avs_utils.h>\n\n#    include <anjay_modules/anjay_bootstrap.h>\n#    include <anjay_modules/anjay_dm_utils.h>\n#    include <anjay_modules/anjay_io_utils.h>\n#    include <anjay_modules/anjay_utils_core.h>\n\n#    include <anjay/factory_provisioning.h>\n\n#    include \"core/dm/anjay_dm_write.h\"\n\nVISIBILITY_SOURCE_BEGIN\n\n#    define provisioning_log(...) \\\n        _anjay_log(anjay_factory_provision, __VA_ARGS__)\n\nstatic avs_error_t factory_provisioning_unlocked(anjay_unlocked_t *anjay,\n                                                 avs_stream_t *data_stream) {\n    if (_anjay_bootstrap_in_progress(anjay)) {\n        provisioning_log(ERROR,\n                         _(\"Transaction with LwM2M Bootstrap Server in \"\n                           \"progress, refusing to perform local bootstrap\"));\n        return avs_errno(AVS_EAGAIN);\n    }\n\n    avs_error_t err = _anjay_bootstrap_delete_everything(anjay);\n    if (avs_is_ok(err)) {\n        anjay_unlocked_input_ctx_t *input_ctx;\n\n        if (_anjay_input_senml_cbor_create(\n                    &input_ctx, data_stream, &MAKE_ROOT_PATH())) {\n            provisioning_log(ERROR, _(\"Cannot create CBOR context\"));\n            err = avs_errno(AVS_ENOMEM);\n        } else {\n            if (_anjay_bootstrap_write_composite(anjay, input_ctx)) {\n                provisioning_log(ERROR,\n                                 _(\"Error occured during writing bootstrap \"\n                                   \"information\"));\n                err = avs_errno(AVS_EPROTO);\n            }\n            (void) _anjay_input_ctx_destroy(&input_ctx);\n        }\n    }\n\n    if (avs_is_ok(err) && _anjay_bootstrap_finish(anjay)) {\n        provisioning_log(ERROR, _(\"Could not apply bootstrap information\"));\n        err = avs_errno(AVS_EBADMSG);\n    }\n\n    if (avs_is_ok(err)) {\n        provisioning_log(INFO, _(\"Finished factory provisioning\"));\n    } else {\n        _anjay_bootstrap_cleanup(anjay);\n    }\n    return err;\n}\n\navs_error_t anjay_factory_provision(anjay_t *anjay_locked,\n                                    avs_stream_t *data_stream) {\n    avs_error_t err = avs_errno(AVS_EINVAL);\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    err = factory_provisioning_unlocked(anjay, data_stream);\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return err;\n}\n#endif // ANJAY_WITH_MODULE_FACTORY_PROVISIONING\n"
  },
  {
    "path": "src/modules/fw_update/anjay_fw_update.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#ifdef ANJAY_WITH_MODULE_FW_UPDATE\n\n#    if !defined(ANJAY_WITH_DOWNLOADER) \\\n            && defined(ANJAY_WITHOUT_MODULE_FW_UPDATE_PUSH_MODE)\n#        error \"ANJAY_WITH_MODULE_FW_UPDATE requires at least one of PUSH or PULL modes to be possible: please either disable ANJAY_WITHOUT_MODULE_FW_UPDATE_PUSH_MODE or enable ANJAY_WITH_DOWNLOADER\"\n#    endif // !defined(ANJAY_WITH_DOWNLOADER) &&\n           // defined(ANJAY_WITHOUT_MODULE_FW_UPDATE_PUSH_MODE)\n\n#    include <string.h>\n\n#    ifdef ANJAY_WITH_DOWNLOADER\n#        include <anjay/download.h>\n#    endif // ANJAY_WITH_DOWNLOADER\n\n#    include <anjay/fw_update.h>\n\n#    ifdef ANJAY_WITH_SEND\n#        include <anjay/lwm2m_send.h>\n#    endif // ANJAY_WITH_SEND\n\n#    include <anjay_modules/anjay_dm_utils.h>\n#    include <anjay_modules/anjay_io_utils.h>\n#    include <anjay_modules/anjay_sched.h>\n#    include <anjay_modules/anjay_utils_core.h>\n#    include <anjay_modules/dm/anjay_modules.h>\n\n#    include <avsystem/coap/code.h>\n\n#    include <avsystem/commons/avs_errno.h>\n#    include <avsystem/commons/avs_url.h>\n#    include <avsystem/commons/avs_utils.h>\n\nVISIBILITY_SOURCE_BEGIN\n\n#    define fw_log(level, ...) _anjay_log(fw_update, level, __VA_ARGS__)\n\n#    define FW_OID 5\n\n#    define FW_RES_PACKAGE 0\n#    define FW_RES_PACKAGE_URI 1\n#    define FW_RES_UPDATE 2\n#    define FW_RES_STATE 3\n#    define FW_RES_UPDATE_RESULT 5\n#    define FW_RES_PKG_NAME 6\n#    define FW_RES_PKG_VERSION 7\n#    define FW_RES_UPDATE_PROTOCOL_SUPPORT 8\n#    define FW_RES_UPDATE_DELIVERY_METHOD 9\n#    ifdef ANJAY_WITH_LWM2M11\n#        define FW_RES_CANCEL 10\n#        ifdef ANJAY_WITH_MODULE_FW_UPDATE_V11_RESOURCES\n#            define FW_RES_SEVERITY 11\n#            define FW_RES_LAST_STATE_CHANGE_TIME 12\n#            define FW_RES_MAX_DEFER_PERIOD 13\n#        endif // ANJAY_WITH_MODULE_FW_UPDATE_V11_RESOURCES\n#    endif     // ANJAY_WITH_LWM2M11\n\ntypedef enum {\n    UPDATE_STATE_IDLE = 0,\n    UPDATE_STATE_DOWNLOADING,\n    UPDATE_STATE_DOWNLOADED,\n    UPDATE_STATE_UPDATING\n} fw_update_state_t;\n\ntypedef struct {\n    const anjay_fw_update_handlers_t *handlers;\n    void *arg;\n    fw_update_state_t state;\n} fw_user_state_t;\n\ntypedef struct fw_repr {\n    anjay_dm_installed_object_t def_ptr;\n    const anjay_unlocked_dm_object_def_t *def;\n\n    fw_user_state_t user_state;\n\n    fw_update_state_t state;\n    anjay_fw_update_result_t result;\n    const char *package_uri;\n    avs_sched_handle_t update_job;\n#    ifdef ANJAY_WITH_DOWNLOADER\n    bool retry_download_on_expired;\n    anjay_download_handle_t download_handle;\n    bool prefer_same_socket_downloads;\n    bool downloads_suspended;\n    avs_sched_handle_t resume_download_job;\n    avs_time_monotonic_t resume_download_deadline;\n#    endif // ANJAY_WITH_DOWNLOADER\n#    ifdef ANJAY_WITH_SEND\n    bool use_lwm2m_send;\n#    endif // ANJAY_WITH_SEND\n#    if defined(ANJAY_WITH_LWM2M11) \\\n            && defined(ANJAY_WITH_MODULE_FW_UPDATE_V11_RESOURCES)\n    anjay_fw_update_severity_t severity;\n    avs_time_real_t last_state_change_time;\n    int32_t max_defer_period;\n    avs_time_real_t update_deadline;\n#    endif /* defined(ANJAY_WITH_LWM2M11) && \\\n              defined(ANJAY_WITH_MODULE_FW_UPDATE_V11_RESOURCES) */\n} fw_repr_t;\n\nstatic inline fw_repr_t *get_fw(const anjay_dm_installed_object_t obj_ptr) {\n    return AVS_CONTAINER_OF(_anjay_dm_installed_object_get_unlocked(&obj_ptr),\n                            fw_repr_t, def);\n}\n\n#    ifdef ANJAY_WITH_SEND\n#        define SEND_RES_PATH(Oid, Iid, Rid) \\\n            {                                \\\n                .oid = (Oid),                \\\n                .iid = (Iid),                \\\n                .rid = (Rid)                 \\\n            }\n\n#        define SEND_FW_RES_PATH(Res) SEND_RES_PATH(FW_OID, 0, FW_RES_##Res)\n\nstatic int perform_send(anjay_unlocked_t *anjay,\n                        const anjay_dm_installed_object_t *obj,\n                        anjay_iid_t iid,\n                        void *batch) {\n    (void) obj;\n    anjay_ssid_t ssid;\n    const anjay_uri_path_t ssid_path =\n            MAKE_RESOURCE_PATH(ANJAY_DM_OID_SERVER, iid,\n                               ANJAY_DM_RID_SERVER_SSID);\n\n    if (_anjay_dm_read_resource_u16(anjay, &ssid_path, &ssid)) {\n        return 0;\n    }\n\n    if (_anjay_send_deferrable_unlocked(anjay, (anjay_ssid_t) ssid,\n                                        (anjay_send_batch_t *) batch, NULL,\n                                        NULL)\n            != ANJAY_SEND_OK) {\n        fw_log(WARNING, _(\"failed to perform Send, SSID: \") \"%d\", ssid);\n    }\n\n    return 0;\n}\n\nstatic void send_batch_to_all_servers(anjay_unlocked_t *anjay,\n                                      anjay_send_batch_t *batch) {\n    const anjay_dm_installed_object_t *obj =\n            _anjay_dm_find_object_by_oid(_anjay_get_dm(anjay),\n                                         ANJAY_DM_OID_SERVER);\n\n    if (_anjay_dm_foreach_instance(anjay, obj, perform_send, batch)) {\n        fw_log(ERROR, _(\"failed to perform Send to all servers\"));\n    }\n}\n\nstatic void perform_lwm2m_send(anjay_unlocked_t *anjay,\n                               const anjay_send_resource_path_t *paths,\n                               size_t paths_len) {\n    assert(paths);\n\n    anjay_send_batch_builder_t *batch_builder = anjay_send_batch_builder_new();\n    if (!batch_builder) {\n        _anjay_log_oom();\n        return;\n    }\n\n    if (_anjay_send_batch_data_add_current_multiple_unlocked(\n                batch_builder, anjay, ANJAY_ID_INVALID, paths, paths_len,\n                true)) {\n        fw_log(ERROR, _(\"failed to add data to batch\"));\n        anjay_send_batch_builder_cleanup(&batch_builder);\n        return;\n    }\n\n    anjay_send_batch_t *batch =\n            anjay_send_batch_builder_compile(&batch_builder);\n    if (!batch) {\n        anjay_send_batch_builder_cleanup(&batch_builder);\n        _anjay_log_oom();\n        return;\n    }\n\n    send_batch_to_all_servers(anjay, batch);\n\n    anjay_send_batch_release(&batch);\n}\n\nstatic void send_state_and_update_result(anjay_unlocked_t *anjay,\n                                         const fw_repr_t *fw) {\n    if (!fw->use_lwm2m_send) {\n        return;\n    }\n\n    const anjay_send_resource_path_t paths[] = {\n        SEND_FW_RES_PATH(STATE), SEND_FW_RES_PATH(UPDATE_RESULT)\n    };\n    perform_lwm2m_send(anjay, paths, AVS_ARRAY_SIZE(paths));\n}\n#    endif // ANJAY_WITH_SEND\n\nstatic void set_update_result(anjay_unlocked_t *anjay,\n                              fw_repr_t *fw,\n                              anjay_fw_update_result_t new_result) {\n    if (fw->result != new_result) {\n        fw_log(DEBUG, _(\"Firmware Update Result change: \") \"%d\" _(\" -> \") \"%d\",\n               (int) fw->result, (int) new_result);\n        fw->result = new_result;\n        _anjay_notify_changed_unlocked(anjay, FW_OID, 0, FW_RES_UPDATE_RESULT);\n    }\n}\n\nstatic void\nset_state(anjay_unlocked_t *anjay, fw_repr_t *fw, fw_update_state_t new_state) {\n    if (fw->state != new_state) {\n#    if defined(ANJAY_WITH_LWM2M11) \\\n            && defined(ANJAY_WITH_MODULE_FW_UPDATE_V11_RESOURCES)\n        fw->last_state_change_time = avs_time_real_now();\n#    endif /* defined(ANJAY_WITH_LWM2M11) && \\\n              defined(ANJAY_WITH_MODULE_FW_UPDATE_V11_RESOURCES) */\n        fw_log(DEBUG, _(\"Firmware Update State change: \") \"%d\" _(\" -> \") \"%d\",\n               (int) fw->state, (int) new_state);\n        fw->state = new_state;\n        _anjay_notify_changed_unlocked(anjay, FW_OID, 0, FW_RES_STATE);\n    }\n}\n\nstatic void\nupdate_state_and_update_result(anjay_unlocked_t *anjay,\n                               fw_repr_t *fw,\n                               fw_update_state_t new_state,\n                               anjay_fw_update_result_t new_result) {\n    set_update_result(anjay, fw, new_result);\n    set_state(anjay, fw, new_state);\n#    ifdef ANJAY_WITH_SEND\n    send_state_and_update_result(anjay, fw);\n#    endif // ANJAY_WITH_SEND\n}\n\nstatic void set_user_state(fw_user_state_t *user, fw_update_state_t new_state) {\n    fw_log(DEBUG, _(\"user->state change: \") \"%d\" _(\" -> \") \"%d\",\n           (int) user->state, (int) new_state);\n    user->state = new_state;\n}\n\nstatic int\nuser_state_ensure_stream_open(anjay_unlocked_t *anjay,\n                              fw_user_state_t *user,\n                              const char *package_uri,\n                              const struct anjay_etag *package_etag) {\n    if (user->state == UPDATE_STATE_DOWNLOADING) {\n        return 0;\n    }\n    assert(user->state == UPDATE_STATE_IDLE);\n    int result = -1;\n    ANJAY_MUTEX_UNLOCK_FOR_CALLBACK(anjay_locked, anjay);\n    result = user->handlers->stream_open(user->arg, package_uri, package_etag);\n    ANJAY_MUTEX_LOCK_AFTER_CALLBACK(anjay_locked);\n    if (!result) {\n        set_user_state(user, UPDATE_STATE_DOWNLOADING);\n    }\n    return result;\n}\n\nstatic int user_state_stream_write(anjay_unlocked_t *anjay,\n                                   fw_user_state_t *user,\n                                   const void *data,\n                                   size_t length) {\n    assert(user->state == UPDATE_STATE_DOWNLOADING);\n    int result = -1;\n    ANJAY_MUTEX_UNLOCK_FOR_CALLBACK(anjay_locked, anjay);\n    result = user->handlers->stream_write(user->arg, data, length);\n    ANJAY_MUTEX_LOCK_AFTER_CALLBACK(anjay_locked);\n    return result;\n}\n\nstatic const char *user_state_get_name(anjay_unlocked_t *anjay,\n                                       fw_user_state_t *user) {\n    if (!user->handlers->get_name || user->state != UPDATE_STATE_DOWNLOADED) {\n        return NULL;\n    }\n    const char *result = NULL;\n    ANJAY_MUTEX_UNLOCK_FOR_CALLBACK(anjay_locked, anjay);\n    result = user->handlers->get_name(user->arg);\n    ANJAY_MUTEX_LOCK_AFTER_CALLBACK(anjay_locked);\n    return result;\n}\n\nstatic const char *user_state_get_version(anjay_unlocked_t *anjay,\n                                          fw_user_state_t *user) {\n    if (!user->handlers->get_version\n            || user->state != UPDATE_STATE_DOWNLOADED) {\n        return NULL;\n    }\n    const char *result = NULL;\n    ANJAY_MUTEX_UNLOCK_FOR_CALLBACK(anjay_locked, anjay);\n    result = user->handlers->get_version(user->arg);\n    ANJAY_MUTEX_LOCK_AFTER_CALLBACK(anjay_locked);\n    return result;\n}\n\nstatic int user_state_perform_upgrade(anjay_unlocked_t *anjay, fw_repr_t *fw) {\n    fw_user_state_t *user = &fw->user_state;\n    if (user->state != UPDATE_STATE_DOWNLOADED) {\n        fw_log(WARNING,\n               _(\"Update State \") \"%d\" _(\" != \") \"%d\" _(\n                       \" (DOWNLOADED); aborting\"),\n               (int) user->state, (int) UPDATE_STATE_DOWNLOADED);\n        return -1;\n    }\n\n    int result = -1;\n    ANJAY_MUTEX_UNLOCK_FOR_CALLBACK(anjay_locked, anjay);\n    result = user->handlers->perform_upgrade(user->arg);\n    ANJAY_MUTEX_LOCK_AFTER_CALLBACK(anjay_locked);\n    // If the state was changed during perform_upgrade handler, this means\n    // @ref anjay_fw_update_set_result was called and has overwritten the\n    // State and Result. In that case, change State to Updating if update was\n    // not deferred.\n    if (!result && user->state == UPDATE_STATE_DOWNLOADED\n#    ifdef ANJAY_WITH_LWM2M11\n            && fw->result != ANJAY_FW_UPDATE_RESULT_DEFERRED\n#    endif // ANJAY_WITH_LWM2M11\n    ) {\n        set_user_state(user, UPDATE_STATE_UPDATING);\n    }\n    return result;\n}\n\nstatic int finish_user_stream(anjay_unlocked_t *anjay, fw_repr_t *fw) {\n    assert(fw->user_state.state == UPDATE_STATE_DOWNLOADING);\n    int result = -1;\n    ANJAY_MUTEX_UNLOCK_FOR_CALLBACK(anjay_locked, anjay);\n    result = fw->user_state.handlers->stream_finish(fw->user_state.arg);\n    ANJAY_MUTEX_LOCK_AFTER_CALLBACK(anjay_locked);\n    if (result) {\n        set_user_state(&fw->user_state, UPDATE_STATE_IDLE);\n    } else {\n        set_user_state(&fw->user_state, UPDATE_STATE_DOWNLOADED);\n    }\n    return result;\n}\n\nstatic void reset_user_state(anjay_unlocked_t *anjay, fw_repr_t *fw) {\n    ANJAY_MUTEX_UNLOCK_FOR_CALLBACK(anjay_locked, anjay);\n    fw->user_state.handlers->reset(fw->user_state.arg);\n    ANJAY_MUTEX_LOCK_AFTER_CALLBACK(anjay_locked);\n    set_user_state(&fw->user_state, UPDATE_STATE_IDLE);\n}\n\n#    ifdef ANJAY_WITH_DOWNLOADER\nstatic int get_security_config(anjay_unlocked_t *anjay,\n                               fw_repr_t *fw,\n                               anjay_security_config_t *out_security_config) {\n    assert(fw->user_state.state == UPDATE_STATE_IDLE\n           || fw->user_state.state == UPDATE_STATE_DOWNLOADING);\n    if (fw->user_state.handlers->get_security_config) {\n        int result = -1;\n        ANJAY_MUTEX_UNLOCK_FOR_CALLBACK(anjay_locked, anjay);\n        result = fw->user_state.handlers->get_security_config(\n                fw->user_state.arg, out_security_config, fw->package_uri);\n        ANJAY_MUTEX_LOCK_AFTER_CALLBACK(anjay_locked);\n        return result;\n    } else {\n        if (!_anjay_security_config_from_dm_unlocked(anjay, out_security_config,\n                                                     fw->package_uri)) {\n            return 0;\n        }\n#        ifdef ANJAY_WITH_LWM2M11\n        *out_security_config = _anjay_security_config_pkix_unlocked(anjay);\n        if (out_security_config->security_info.data.cert\n                    .server_cert_validation) {\n            return 0;\n        }\n#        endif // ANJAY_WITH_LWM2M11\n        return -1;\n    }\n}\n\nstatic int get_coap_tx_params(anjay_unlocked_t *anjay,\n                              fw_repr_t *fw,\n                              avs_coap_udp_tx_params_t *out_tx_params) {\n    if (fw->user_state.handlers->get_coap_tx_params) {\n        ANJAY_MUTEX_UNLOCK_FOR_CALLBACK(anjay_locked, anjay);\n        *out_tx_params =\n                fw->user_state.handlers->get_coap_tx_params(fw->user_state.arg,\n                                                            fw->package_uri);\n        ANJAY_MUTEX_LOCK_AFTER_CALLBACK(anjay_locked);\n        return 0;\n    }\n    return -1;\n}\n\nstatic avs_time_duration_t get_tcp_request_timeout(anjay_unlocked_t *anjay,\n                                                   fw_repr_t *fw) {\n    avs_time_duration_t result = AVS_TIME_DURATION_INVALID;\n    if (fw->user_state.handlers->get_tcp_request_timeout) {\n        ANJAY_MUTEX_UNLOCK_FOR_CALLBACK(anjay_locked, anjay);\n        result = fw->user_state.handlers->get_tcp_request_timeout(\n                fw->user_state.arg, fw->package_uri);\n        ANJAY_MUTEX_LOCK_AFTER_CALLBACK(anjay_locked);\n    }\n    return result;\n}\n#    endif // ANJAY_WITH_DOWNLOADER\n\nstatic void handle_err_result(anjay_unlocked_t *anjay,\n                              fw_repr_t *fw,\n                              fw_update_state_t new_state,\n                              int result,\n                              anjay_fw_update_result_t default_result) {\n    anjay_fw_update_result_t new_result;\n    switch (result) {\n    case -ANJAY_FW_UPDATE_RESULT_NOT_ENOUGH_SPACE:\n    case -ANJAY_FW_UPDATE_RESULT_OUT_OF_MEMORY:\n    case -ANJAY_FW_UPDATE_RESULT_INTEGRITY_FAILURE:\n    case -ANJAY_FW_UPDATE_RESULT_UNSUPPORTED_PACKAGE_TYPE:\n#    ifdef ANJAY_WITH_LWM2M11\n    case -ANJAY_FW_UPDATE_RESULT_DEFERRED:\n#    endif // ANJAY_WITH_LWM2M11\n        new_result = (anjay_fw_update_result_t) -result;\n        break;\n    default:\n        new_result = default_result;\n    }\n    update_state_and_update_result(anjay, fw, new_state, new_result);\n}\n\nstatic void reset(anjay_unlocked_t *anjay, fw_repr_t *fw) {\n    reset_user_state(anjay, fw);\n    update_state_and_update_result(anjay, fw, UPDATE_STATE_IDLE,\n                                   ANJAY_FW_UPDATE_RESULT_INITIAL);\n    fw_log(INFO, _(\"Firmware Object state reset\"));\n}\n\nstatic int fw_list_instances(anjay_unlocked_t *anjay,\n                             const anjay_dm_installed_object_t obj_ptr,\n                             anjay_unlocked_dm_list_ctx_t *ctx) {\n    (void) anjay;\n    (void) obj_ptr;\n    _anjay_dm_emit_unlocked(ctx, 0);\n    return 0;\n}\n\nstatic int fw_list_resources(anjay_unlocked_t *anjay,\n                             const anjay_dm_installed_object_t obj_ptr,\n                             anjay_iid_t iid,\n                             anjay_unlocked_dm_resource_list_ctx_t *ctx) {\n    (void) iid;\n    fw_repr_t *fw = get_fw(obj_ptr);\n\n    _anjay_dm_emit_res_unlocked(ctx, FW_RES_PACKAGE, ANJAY_DM_RES_W,\n                                ANJAY_DM_RES_PRESENT);\n    _anjay_dm_emit_res_unlocked(ctx, FW_RES_PACKAGE_URI, ANJAY_DM_RES_RW,\n                                ANJAY_DM_RES_PRESENT);\n    _anjay_dm_emit_res_unlocked(ctx, FW_RES_UPDATE, ANJAY_DM_RES_E,\n                                ANJAY_DM_RES_PRESENT);\n    _anjay_dm_emit_res_unlocked(ctx, FW_RES_STATE, ANJAY_DM_RES_R,\n                                ANJAY_DM_RES_PRESENT);\n    _anjay_dm_emit_res_unlocked(ctx, FW_RES_UPDATE_RESULT, ANJAY_DM_RES_R,\n                                ANJAY_DM_RES_PRESENT);\n    _anjay_dm_emit_res_unlocked(ctx, FW_RES_PKG_NAME, ANJAY_DM_RES_R,\n                                user_state_get_name(anjay, &fw->user_state)\n                                        ? ANJAY_DM_RES_PRESENT\n                                        : ANJAY_DM_RES_ABSENT);\n    _anjay_dm_emit_res_unlocked(ctx, FW_RES_PKG_VERSION, ANJAY_DM_RES_R,\n                                user_state_get_version(anjay, &fw->user_state)\n                                        ? ANJAY_DM_RES_PRESENT\n                                        : ANJAY_DM_RES_ABSENT);\n    _anjay_dm_emit_res_unlocked(ctx, FW_RES_UPDATE_PROTOCOL_SUPPORT,\n                                ANJAY_DM_RES_RM, ANJAY_DM_RES_PRESENT);\n    _anjay_dm_emit_res_unlocked(ctx, FW_RES_UPDATE_DELIVERY_METHOD,\n                                ANJAY_DM_RES_R, ANJAY_DM_RES_PRESENT);\n#    ifdef ANJAY_WITH_LWM2M11\n    _anjay_dm_emit_res_unlocked(ctx, FW_RES_CANCEL, ANJAY_DM_RES_E,\n                                ANJAY_DM_RES_PRESENT);\n#        ifdef ANJAY_WITH_MODULE_FW_UPDATE_V11_RESOURCES\n    _anjay_dm_emit_res_unlocked(ctx, FW_RES_SEVERITY, ANJAY_DM_RES_RW,\n                                ANJAY_DM_RES_PRESENT);\n    _anjay_dm_emit_res_unlocked(ctx, FW_RES_LAST_STATE_CHANGE_TIME,\n                                ANJAY_DM_RES_R, ANJAY_DM_RES_PRESENT);\n    _anjay_dm_emit_res_unlocked(ctx, FW_RES_MAX_DEFER_PERIOD, ANJAY_DM_RES_RW,\n                                ANJAY_DM_RES_PRESENT);\n#        endif // ANJAY_WITH_MODULE_FW_UPDATE_V11_RESOURCES\n#    endif     // ANJAY_WITH_LWM2M11\n    return 0;\n}\n\nstatic const int32_t SUPPORTED_PROTOCOLS[] = {\n#    ifdef WITH_AVS_COAP_UDP\n    0, /* CoAP */\n#        ifndef AVS_COMMONS_WITHOUT_TLS\n    1,         /* CoAPS */\n#        endif // AVS_COMMONS_WITHOUT_TLS\n#    endif     // WITH_AVS_COAP_UDP\n#    ifdef ANJAY_WITH_HTTP_DOWNLOAD\n    2, /* HTTP 1.1 */\n#        ifndef AVS_COMMONS_WITHOUT_TLS\n    3,         /* HTTPS 1.1 */\n#        endif // AVS_COMMONS_WITHOUT_TLS\n#    endif     // ANJAY_WITH_HTTP_DOWNLOAD\n#    ifdef WITH_AVS_COAP_TCP\n    4, /* CoAP over TCP */\n#        ifndef AVS_COMMONS_WITHOUT_TLS\n    5,         /* CoAP over TLS */\n#        endif // AVS_COMMONS_WITHOUT_TLS\n#    endif     // WITH_AVS_COAP_TCP\n};\n\nstatic int fw_read(anjay_unlocked_t *anjay,\n                   const anjay_dm_installed_object_t obj_ptr,\n                   anjay_iid_t iid,\n                   anjay_rid_t rid,\n                   anjay_riid_t riid,\n                   anjay_unlocked_output_ctx_t *ctx) {\n    (void) iid;\n    fw_repr_t *fw = get_fw(obj_ptr);\n    switch (rid) {\n    case FW_RES_PACKAGE_URI:\n        return _anjay_ret_string_unlocked(ctx, fw->package_uri ? fw->package_uri\n                                                               : \"\");\n    case FW_RES_STATE:\n        return _anjay_ret_i64_unlocked(ctx, (int32_t) fw->state);\n    case FW_RES_UPDATE_RESULT:\n        return _anjay_ret_i64_unlocked(ctx, (int32_t) fw->result);\n    case FW_RES_PKG_NAME: {\n        const char *name = user_state_get_name(anjay, &fw->user_state);\n        if (name) {\n            return _anjay_ret_string_unlocked(ctx, name);\n        } else {\n            return ANJAY_ERR_NOT_FOUND;\n        }\n    }\n    case FW_RES_PKG_VERSION: {\n        const char *version = user_state_get_version(anjay, &fw->user_state);\n        if (version) {\n            return _anjay_ret_string_unlocked(ctx, version);\n        } else {\n            return ANJAY_ERR_NOT_FOUND;\n        }\n    }\n    case FW_RES_UPDATE_PROTOCOL_SUPPORT:\n        assert(riid < AVS_ARRAY_SIZE(SUPPORTED_PROTOCOLS));\n        return _anjay_ret_i64_unlocked(ctx, SUPPORTED_PROTOCOLS[riid]);\n    case FW_RES_UPDATE_DELIVERY_METHOD:\n#    ifdef ANJAY_WITHOUT_MODULE_FW_UPDATE_PUSH_MODE\n        // 0 -> pull only\n        return _anjay_ret_i64_unlocked(ctx, 0);\n#    else // ANJAY_WITHOUT_MODULE_FW_UPDATE_PUSH_MODE\n#        ifdef ANJAY_WITH_DOWNLOADER\n        // 2 -> pull && push\n        return _anjay_ret_i64_unlocked(ctx, 2);\n#        else  // ANJAY_WITH_DOWNLOADER\n        // 1 -> push only\n        return _anjay_ret_i64_unlocked(ctx, 1);\n#        endif // ANJAY_WITH_DOWNLOADER\n#    endif     // ANJAY_WITHOUT_MODULE_FW_UPDATE_PUSH_MODE\n#    if defined(ANJAY_WITH_LWM2M11) \\\n            && defined(ANJAY_WITH_MODULE_FW_UPDATE_V11_RESOURCES)\n    case FW_RES_SEVERITY:\n        return _anjay_ret_i64_unlocked(ctx, (int32_t) fw->severity);\n    case FW_RES_LAST_STATE_CHANGE_TIME: {\n        int64_t last_state_change_timestamp = 0;\n        avs_time_real_to_scalar(&last_state_change_timestamp, AVS_TIME_S,\n                                fw->last_state_change_time);\n        return _anjay_ret_i64_unlocked(ctx, last_state_change_timestamp);\n    }\n    case FW_RES_MAX_DEFER_PERIOD:\n        return _anjay_ret_i64_unlocked(ctx, fw->max_defer_period);\n#    endif /* defined(ANJAY_WITH_LWM2M11) && \\\n              defined(ANJAY_WITH_MODULE_FW_UPDATE_V11_RESOURCES) */\n    default:\n        AVS_UNREACHABLE(\"Read called on unknown or non-readable Firmware \"\n                        \"Update resource\");\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\n#    if defined(ANJAY_WITH_COAP_DOWNLOAD) || defined(ANJAY_WITH_HTTP_DOWNLOAD)\nstatic anjay_transport_security_t\ntransport_security_from_protocol(const char *protocol) {\n#        ifdef ANJAY_WITH_COAP_DOWNLOAD\n    const anjay_transport_info_t *info =\n            _anjay_transport_info_by_uri_scheme(protocol);\n    if (info) {\n        return info->security;\n    }\n#        endif // ANJAY_WITH_COAP_DOWNLOAD\n\n#        ifdef ANJAY_WITH_HTTP_DOWNLOAD\n    if (avs_strcasecmp(protocol, \"http\") == 0) {\n        return ANJAY_TRANSPORT_NOSEC;\n    }\n    if (avs_strcasecmp(protocol, \"https\") == 0) {\n        return ANJAY_TRANSPORT_ENCRYPTED;\n    }\n#        endif // ANJAY_WITH_HTTP_DOWNLOAD\n\n    return ANJAY_TRANSPORT_SECURITY_UNDEFINED;\n}\n\nstatic anjay_transport_security_t transport_security_from_uri(const char *uri) {\n    avs_url_t *parsed_url = avs_url_parse_lenient(uri);\n    if (!parsed_url) {\n        return ANJAY_TRANSPORT_SECURITY_UNDEFINED;\n    }\n    anjay_transport_security_t result = ANJAY_TRANSPORT_SECURITY_UNDEFINED;\n\n    const char *protocol = avs_url_protocol(parsed_url);\n    if (protocol) {\n        result = transport_security_from_protocol(protocol);\n    }\n    avs_url_free(parsed_url);\n    return result;\n}\n#    else  // ANJAY_WITH_COAP_DOWNLOAD || ANJAY_WITH_HTTP_DOWNLOAD\nstatic anjay_transport_security_t transport_security_from_uri(const char *uri) {\n    (void) uri;\n    return ANJAY_TRANSPORT_SECURITY_UNDEFINED;\n}\n#    endif // ANJAY_WITH_COAP_DOWNLOAD || ANJAY_WITH_HTTP_DOWNLOAD\n\n#    if defined(ANJAY_WITH_LWM2M11) \\\n            && defined(ANJAY_WITH_MODULE_FW_UPDATE_V11_RESOURCES)\nstatic void set_update_deadline(fw_repr_t *fw) {\n    if (fw->max_defer_period <= 0) {\n        fw->update_deadline = AVS_TIME_REAL_INVALID;\n        return;\n    }\n    fw->update_deadline = avs_time_real_add(\n            avs_time_real_now(),\n            avs_time_duration_from_scalar(fw->max_defer_period, AVS_TIME_S));\n}\n#    endif /* defined(ANJAY_WITH_LWM2M11) && \\\n              defined(ANJAY_WITH_MODULE_FW_UPDATE_V11_RESOURCES) */\n\n#    ifdef ANJAY_WITH_DOWNLOADER\nstatic avs_error_t download_write_block(anjay_t *anjay_locked,\n                                        const uint8_t *data,\n                                        size_t data_size,\n                                        const anjay_etag_t *etag,\n                                        void *fw_) {\n    (void) etag;\n    int result = -1;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    fw_repr_t *fw = (fw_repr_t *) fw_;\n    result = user_state_ensure_stream_open(anjay, &fw->user_state,\n                                           fw->package_uri, etag);\n    if (!result && data_size > 0) {\n        result = user_state_stream_write(anjay, &fw->user_state, data,\n                                         data_size);\n    }\n    if (result) {\n        fw_log(ERROR, _(\"could not write firmware\"));\n        handle_err_result(anjay, fw, UPDATE_STATE_IDLE, result,\n                          ANJAY_FW_UPDATE_RESULT_NOT_ENOUGH_SPACE);\n    }\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return result ? avs_errno(AVS_UNKNOWN_ERROR) : AVS_OK;\n}\n\nstatic int schedule_background_anjay_download(anjay_unlocked_t *anjay,\n                                              fw_repr_t *fw,\n                                              size_t start_offset,\n                                              const anjay_etag_t *etag);\n\nstatic void download_finished(anjay_t *anjay_locked,\n                              anjay_download_status_t status,\n                              void *fw_) {\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    fw_repr_t *fw = (fw_repr_t *) fw_;\n    fw->download_handle = NULL;\n    if (fw->state != UPDATE_STATE_DOWNLOADING) {\n        // something already failed in download_write_block()\n        reset_user_state(anjay, fw);\n    } else if (status.result != ANJAY_DOWNLOAD_FINISHED) {\n        anjay_fw_update_result_t update_result =\n                ANJAY_FW_UPDATE_RESULT_CONNECTION_LOST;\n        if (status.result == ANJAY_DOWNLOAD_ERR_FAILED) {\n            if (status.details.error.category == AVS_ERRNO_CATEGORY) {\n                if (status.details.error.code == AVS_ENOMEM) {\n                    update_result = ANJAY_FW_UPDATE_RESULT_OUT_OF_MEMORY;\n                } else if (status.details.error.code == AVS_EADDRNOTAVAIL) {\n                    update_result = ANJAY_FW_UPDATE_RESULT_INVALID_URI;\n                }\n            }\n        } else if (status.result == ANJAY_DOWNLOAD_ERR_INVALID_RESPONSE\n                   && (status.details.status_code == AVS_COAP_CODE_NOT_FOUND\n                       || status.details.status_code == 404)) {\n            // NOTE: We should only check for the status code appropriate for\n            // the download protocol, but 132 (AVS_COAP_CODE_NOT_FOUND) is\n            // unlikely as a HTTP status code, and 12.20 (404 according to CoAP\n            // convention) is not representable on a single byte, so this is\n            // good enough.\n            update_result = ANJAY_FW_UPDATE_RESULT_INVALID_URI;\n        }\n        reset_user_state(anjay, fw);\n        if (fw->retry_download_on_expired\n                && status.result == ANJAY_DOWNLOAD_ERR_EXPIRED) {\n            fw_log(INFO,\n                   _(\"Could not resume firmware download (result = \") \"%d\" _(\n                           \"), retrying from the beginning\"),\n                   (int) status.result);\n            if (schedule_background_anjay_download(anjay, fw, 0, NULL)) {\n                fw_log(WARNING, _(\"Could not retry firmware download\"));\n                set_state(anjay, fw, UPDATE_STATE_IDLE);\n#        ifdef ANJAY_WITH_SEND\n                send_state_and_update_result(anjay, fw);\n#        endif // ANJAY_WITH_SEND\n            }\n        } else {\n            fw_log(WARNING, _(\"download aborted: result = \") \"%d\",\n                   (int) status.result);\n            update_state_and_update_result(anjay, fw, UPDATE_STATE_IDLE,\n                                           update_result);\n        }\n    } else {\n        int result;\n        if ((result = user_state_ensure_stream_open(anjay, &fw->user_state,\n                                                    fw->package_uri, NULL))\n                || (result = finish_user_stream(anjay, fw))) {\n            handle_err_result(anjay, fw, UPDATE_STATE_IDLE, result,\n                              ANJAY_FW_UPDATE_RESULT_NOT_ENOUGH_SPACE);\n        } else {\n            update_state_and_update_result(anjay, fw, UPDATE_STATE_DOWNLOADED,\n                                           ANJAY_FW_UPDATE_RESULT_INITIAL);\n        }\n    }\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n}\n\nstatic int schedule_download(anjay_unlocked_t *anjay,\n                             fw_repr_t *fw,\n                             size_t start_offset,\n                             const anjay_etag_t *etag) {\n    anjay_download_config_t cfg = {\n        .url = fw->package_uri,\n        .start_offset = start_offset,\n        .etag = etag,\n        .on_next_block = download_write_block,\n        .on_download_finished = download_finished,\n        .user_data = fw,\n        .prefer_same_socket_downloads = fw->prefer_same_socket_downloads\n    };\n\n    if (transport_security_from_uri(fw->package_uri)\n            == ANJAY_TRANSPORT_ENCRYPTED) {\n        int result = get_security_config(anjay, fw, &cfg.security_config);\n        if (result) {\n            handle_err_result(anjay, fw, UPDATE_STATE_IDLE, result,\n                              ANJAY_FW_UPDATE_RESULT_UNSUPPORTED_PROTOCOL);\n            return -1;\n        }\n    }\n\n    avs_coap_udp_tx_params_t tx_params;\n    if (!get_coap_tx_params(anjay, fw, &tx_params)) {\n        cfg.coap_tx_params = &tx_params;\n    }\n    cfg.tcp_request_timeout = get_tcp_request_timeout(anjay, fw);\n\n    assert(!fw->download_handle);\n    avs_error_t err =\n            _anjay_download_unlocked(anjay, &cfg, &fw->download_handle);\n    if (!fw->download_handle) {\n        anjay_fw_update_result_t update_result =\n                ANJAY_FW_UPDATE_RESULT_CONNECTION_LOST;\n        if (avs_is_err(err) && err.category == AVS_ERRNO_CATEGORY) {\n            switch (err.code) {\n            case AVS_EADDRNOTAVAIL:\n            case AVS_EINVAL:\n                update_result = ANJAY_FW_UPDATE_RESULT_INVALID_URI;\n                break;\n            case AVS_ENOMEM:\n                update_result = ANJAY_FW_UPDATE_RESULT_OUT_OF_MEMORY;\n                break;\n            case AVS_EPROTONOSUPPORT:\n                update_result = ANJAY_FW_UPDATE_RESULT_UNSUPPORTED_PROTOCOL;\n                break;\n            }\n        }\n        reset_user_state(anjay, fw);\n        set_update_result(anjay, fw, update_result);\n#        ifdef ANJAY_WITH_SEND\n        send_state_and_update_result(anjay, fw);\n#        endif // ANJAY_WITH_SEND\n        return -1;\n    }\n\n    if (fw->downloads_suspended) {\n        _anjay_download_suspend_unlocked(anjay, fw->download_handle);\n    }\n    fw->retry_download_on_expired = (etag != NULL);\n    update_state_and_update_result(anjay, fw, UPDATE_STATE_DOWNLOADING,\n                                   ANJAY_FW_UPDATE_RESULT_INITIAL);\n    fw_log(INFO, _(\"download started: \") \"%s\", fw->package_uri);\n    return 0;\n}\n\ntypedef struct {\n    fw_repr_t *fw;\n    size_t start_offset;\n    // actually a FAM\n    anjay_etag_t etag;\n} schedule_download_args_t;\n\nstatic size_t schedule_download_args_size(size_t etag_length) {\n    return offsetof(schedule_download_args_t, etag)\n           + offsetof(anjay_etag_t, value) + etag_length;\n}\n\nstatic void resume_download_job(avs_sched_t *sched, const void *args_) {\n    schedule_download_args_t *args =\n            (schedule_download_args_t *) (intptr_t) args_;\n    anjay_t *anjay_locked = _anjay_get_from_sched(sched);\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    if (!_anjay_ongoing_registration_exists_unlocked(anjay)) {\n        fw_log(DEBUG,\n               _(\"all registrations settled down, scheduling download \"\n                 \"resumption\"));\n        if (schedule_download(anjay, args->fw, args->start_offset,\n                              &args->etag)) {\n            fw_log(ERROR, _(\"could not resume firmware download\"));\n        }\n    } else if (avs_time_monotonic_before(args->fw->resume_download_deadline,\n                                         avs_time_monotonic_now())) {\n        fw_log(DEBUG,\n               _(\"registrations not settled within 5 minutes, canceling \"\n                 \"download resumption\"));\n        reset_user_state(anjay, args->fw);\n        update_state_and_update_result(anjay, args->fw, UPDATE_STATE_IDLE,\n                                       ANJAY_FW_UPDATE_RESULT_CONNECTION_LOST);\n    } else {\n        fw_log(DEBUG,\n               _(\"ongoing registration exists, delaying download resumption\"));\n        if (AVS_SCHED_DELAYED(sched, &args->fw->resume_download_job,\n                              avs_time_duration_from_scalar(1, AVS_TIME_S),\n                              resume_download_job, args,\n                              schedule_download_args_size(args->etag.size))) {\n            fw_log(WARNING, _(\"could not schedule another resumption attempt\"));\n            reset_user_state(anjay, args->fw);\n            update_state_and_update_result(\n                    anjay, args->fw, UPDATE_STATE_IDLE,\n                    ANJAY_FW_UPDATE_RESULT_OUT_OF_MEMORY);\n        }\n    }\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n}\n\nstatic int schedule_background_anjay_download(anjay_unlocked_t *anjay,\n                                              fw_repr_t *fw,\n                                              size_t start_offset,\n                                              const anjay_etag_t *etag) {\n    if (fw->prefer_same_socket_downloads && etag) {\n        avs_sched_t *sched = _anjay_get_scheduler_unlocked(anjay);\n        assert(sched);\n        schedule_download_args_t *args =\n                (schedule_download_args_t *) avs_malloc(\n                        schedule_download_args_size(etag->size));\n        if (args) {\n            args->fw = fw;\n            args->start_offset = start_offset;\n            args->etag.size = etag->size;\n            memcpy(args->etag.value, etag->value, etag->size);\n\n            fw->resume_download_deadline = avs_time_monotonic_add(\n                    avs_time_monotonic_now(),\n                    avs_time_duration_from_scalar(5, AVS_TIME_MIN));\n            if (!AVS_SCHED_NOW(sched, &fw->resume_download_job,\n                               resume_download_job, args,\n                               schedule_download_args_size(etag->size))) {\n                fw_log(DEBUG,\n                       _(\"same socket download initiated, waiting for \"\n                         \"server registrations to settle down\"));\n                update_state_and_update_result(anjay, fw,\n                                               UPDATE_STATE_DOWNLOADING,\n                                               ANJAY_FW_UPDATE_RESULT_INITIAL);\n                avs_free(args);\n                return 0;\n            }\n        }\n        avs_free(args);\n        fw_log(WARNING, _(\"could not resume download on the same socket\"));\n    }\n    return schedule_download(anjay, fw, start_offset, etag);\n}\n#    endif // ANJAY_WITH_DOWNLOADER\n\n#    ifndef ANJAY_WITHOUT_MODULE_FW_UPDATE_PUSH_MODE\nstatic int write_firmware_to_stream(anjay_unlocked_t *anjay,\n                                    fw_repr_t *fw,\n                                    anjay_unlocked_input_ctx_t *ctx,\n                                    bool *out_is_reset_request) {\n    int result = 0;\n    size_t written = 0;\n    bool finished = false;\n    int first_byte = EOF;\n\n    *out_is_reset_request = false;\n    while (!finished) {\n        size_t bytes_read;\n        char buffer[1024];\n        if ((result = _anjay_get_bytes_unlocked(ctx, &bytes_read, &finished,\n                                                buffer, sizeof(buffer)))) {\n            fw_log(ERROR, _(\"anjay_get_bytes() failed\"));\n\n            update_state_and_update_result(\n                    anjay, fw, UPDATE_STATE_IDLE,\n                    ANJAY_FW_UPDATE_RESULT_CONNECTION_LOST);\n            return result;\n        }\n\n        if (bytes_read > 0) {\n            if (first_byte == EOF) {\n                first_byte = (unsigned char) buffer[0];\n            }\n            result = user_state_stream_write(anjay, &fw->user_state, buffer,\n                                             bytes_read);\n        }\n        if (result) {\n            handle_err_result(anjay, fw, UPDATE_STATE_IDLE, result,\n                              ANJAY_FW_UPDATE_RESULT_NOT_ENOUGH_SPACE);\n            return ANJAY_ERR_INTERNAL;\n        }\n        written += bytes_read;\n    }\n\n    *out_is_reset_request = (written == 1 && first_byte == '\\0');\n\n    fw_log(INFO, _(\"write finished, \") \"%lu\" _(\" B written\"),\n           (unsigned long) written);\n    return 0;\n}\n\nstatic int expect_single_nullbyte(anjay_unlocked_input_ctx_t *ctx) {\n    char bytes[2];\n    size_t bytes_read;\n    bool finished = false;\n    if (_anjay_get_bytes_unlocked(ctx, &bytes_read, &finished, bytes,\n                                  sizeof(bytes))) {\n        fw_log(ERROR, _(\"anjay_get_bytes() failed\"));\n        return ANJAY_ERR_INTERNAL;\n    } else if (bytes_read != 1 || !finished || bytes[0] != '\\0') {\n        return ANJAY_ERR_BAD_REQUEST;\n    }\n    return 0;\n}\n\nstatic int write_firmware(anjay_unlocked_t *anjay,\n                          fw_repr_t *fw,\n                          anjay_unlocked_input_ctx_t *ctx,\n                          bool *out_is_reset_request) {\n    assert(fw->state != UPDATE_STATE_DOWNLOADING);\n    if (user_state_ensure_stream_open(anjay, &fw->user_state, NULL, NULL)) {\n        return -1;\n    }\n\n    int result = write_firmware_to_stream(anjay, fw, ctx, out_is_reset_request);\n    if (result) {\n        reset_user_state(anjay, fw);\n    } else if (!*out_is_reset_request) {\n        // stream_finish_result deliberately not propagated up:\n        // write itself succeeded\n        int stream_finish_result = finish_user_stream(anjay, fw);\n        if (stream_finish_result) {\n            handle_err_result(anjay, fw, UPDATE_STATE_IDLE,\n                              stream_finish_result,\n                              ANJAY_FW_UPDATE_RESULT_NOT_ENOUGH_SPACE);\n        } else {\n            update_state_and_update_result(anjay, fw, UPDATE_STATE_DOWNLOADED,\n                                           ANJAY_FW_UPDATE_RESULT_INITIAL);\n        }\n    }\n    return result;\n}\n#    endif // ANJAY_WITHOUT_MODULE_FW_UPDATE_PUSH_MODE\n\n#    ifdef ANJAY_WITH_DOWNLOADER\nstatic void cancel_existing_download_if_in_progress(anjay_unlocked_t *anjay,\n                                                    fw_repr_t *fw) {\n    if (fw->state == UPDATE_STATE_DOWNLOADING) {\n        if (fw->resume_download_job) {\n            assert(!fw->download_handle);\n            avs_sched_del(&fw->resume_download_job);\n            return;\n        }\n        AVS_ASSERT(fw->download_handle,\n                   \"download_handle is NULL - another Write handler called \"\n                   \"during a PUSH-mode download?!\");\n        _anjay_download_abort_unlocked(anjay, fw->download_handle);\n        assert(!fw->download_handle);\n    }\n}\n#    endif // ANJAY_WITH_DOWNLOADER\n\nstatic int fw_write(anjay_unlocked_t *anjay,\n                    const anjay_dm_installed_object_t obj_ptr,\n                    anjay_iid_t iid,\n                    anjay_rid_t rid,\n                    anjay_riid_t riid,\n                    anjay_unlocked_input_ctx_t *ctx) {\n    (void) iid;\n    (void) riid;\n\n    fw_repr_t *fw = get_fw(obj_ptr);\n    switch (rid) {\n    case FW_RES_PACKAGE: {\n#    ifdef ANJAY_WITHOUT_MODULE_FW_UPDATE_PUSH_MODE\n        return ANJAY_ERR_BAD_REQUEST;\n#    else // ANJAY_WITHOUT_MODULE_FW_UPDATE_PUSH_MODE\n        assert(riid == ANJAY_ID_INVALID);\n        int result = 0;\n        if (fw->state == UPDATE_STATE_UPDATING) {\n            fw_log(WARNING, _(\"cannot set Package resource while updating\"));\n            return ANJAY_ERR_METHOD_NOT_ALLOWED;\n        } else if (fw->state == UPDATE_STATE_IDLE) {\n            bool is_reset_request = false;\n            result = write_firmware(anjay, fw, ctx, &is_reset_request);\n            if (!result && is_reset_request) {\n                reset(anjay, fw);\n            }\n        } else {\n            result = expect_single_nullbyte(ctx);\n            if (!result) {\n#        ifdef ANJAY_WITH_DOWNLOADER\n                cancel_existing_download_if_in_progress(anjay, fw);\n#        endif // ANJAY_WITH_DOWNLOADER\n                reset(anjay, fw);\n            }\n        }\n        return result;\n#    endif     // ANJAY_WITHOUT_MODULE_FW_UPDATE_PUSH_MODE\n    }\n    case FW_RES_PACKAGE_URI: {\n        assert(riid == ANJAY_ID_INVALID);\n        char *new_uri = NULL;\n        int result = _anjay_io_fetch_string(ctx, &new_uri);\n        size_t len = (new_uri ? strlen(new_uri) : 0);\n\n        if (!result && len == 0) {\n            avs_free(new_uri);\n\n            if (fw->state == UPDATE_STATE_UPDATING) {\n                fw_log(WARNING,\n                       _(\"cannot set Package URI resource while updating\"));\n                return ANJAY_ERR_METHOD_NOT_ALLOWED;\n            }\n\n#    ifdef ANJAY_WITH_DOWNLOADER\n            cancel_existing_download_if_in_progress(anjay, fw);\n#    endif // ANJAY_WITH_DOWNLOADER\n\n            avs_free((void *) (intptr_t) fw->package_uri);\n            fw->package_uri = NULL;\n            reset(anjay, fw);\n            return 0;\n        }\n\n        if (!result && fw->state != UPDATE_STATE_IDLE) {\n            result = ANJAY_ERR_BAD_REQUEST;\n        }\n\n        if (!result\n                && transport_security_from_uri(new_uri)\n                               == ANJAY_TRANSPORT_SECURITY_UNDEFINED) {\n            fw_log(WARNING,\n                   _(\"unsupported download protocol required for uri \") \"%s\",\n                   new_uri);\n            set_update_result(anjay, fw,\n                              ANJAY_FW_UPDATE_RESULT_UNSUPPORTED_PROTOCOL);\n#    ifdef ANJAY_WITH_SEND\n            send_state_and_update_result(anjay, fw);\n#    endif // ANJAY_WITH_SEND\n            result = ANJAY_ERR_BAD_REQUEST;\n        }\n\n#    ifdef ANJAY_WITH_DOWNLOADER\n        if (!result) {\n            avs_free((void *) (intptr_t) fw->package_uri);\n            fw->package_uri = new_uri;\n            new_uri = NULL;\n\n            int dl_res = schedule_background_anjay_download(anjay, fw, 0, NULL);\n            if (dl_res) {\n                fw_log(WARNING,\n                       _(\"schedule_download_in_background failed: \") \"%d\",\n                       dl_res);\n            }\n            // write itself succeeded; do not propagate error\n        }\n#    endif // ANJAY_WITH_DOWNLOADER\n\n        avs_free(new_uri);\n\n        return result;\n    }\n#    if defined(ANJAY_WITH_LWM2M11) \\\n            && defined(ANJAY_WITH_MODULE_FW_UPDATE_V11_RESOURCES)\n    case FW_RES_SEVERITY: {\n        assert(riid == ANJAY_ID_INVALID);\n        int32_t severity = ANJAY_FW_UPDATE_SEVERITY_MANDATORY;\n        if (_anjay_get_i32_unlocked(ctx, &severity)\n                || severity < (int32_t) ANJAY_FW_UPDATE_SEVERITY_CRITICAL\n                || severity > (int32_t) ANJAY_FW_UPDATE_SEVERITY_OPTIONAL) {\n            return ANJAY_ERR_BAD_REQUEST;\n        }\n        fw->severity = (anjay_fw_update_severity_t) severity;\n        return 0;\n    }\n    case FW_RES_MAX_DEFER_PERIOD: {\n        assert(riid == ANJAY_ID_INVALID);\n        int32_t max_defer_period = 0;\n        if (_anjay_get_i32_unlocked(ctx, &max_defer_period)\n                || max_defer_period < 0) {\n            return ANJAY_ERR_BAD_REQUEST;\n        }\n        fw->max_defer_period = max_defer_period;\n        return 0;\n    }\n#    endif /* defined(ANJAY_WITH_LWM2M11) && \\\n              defined(ANJAY_WITH_MODULE_FW_UPDATE_V11_RESOURCES) */\n    default:\n        // Bootstrap Server may try to write to other resources,\n        // so no AVS_UNREACHABLE() here\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\nstatic int fw_resource_instances(anjay_unlocked_t *anjay,\n                                 const anjay_dm_installed_object_t obj_ptr,\n                                 anjay_iid_t iid,\n                                 anjay_rid_t rid,\n                                 anjay_unlocked_dm_list_ctx_t *ctx) {\n    (void) anjay;\n    (void) obj_ptr;\n    (void) iid;\n\n    switch (rid) {\n    case FW_RES_UPDATE_PROTOCOL_SUPPORT:\n        for (anjay_riid_t i = 0; i < AVS_ARRAY_SIZE(SUPPORTED_PROTOCOLS); ++i) {\n            _anjay_dm_emit_unlocked(ctx, i);\n        }\n        return 0;\n    default:\n        AVS_UNREACHABLE(\n                \"Attempted to list instances in a single-instance resource\");\n        return ANJAY_ERR_INTERNAL;\n    }\n}\n\nstatic void perform_upgrade(avs_sched_t *sched, const void *fw_ptr) {\n    fw_repr_t *fw = *(fw_repr_t *const *) fw_ptr;\n\n#    if defined(ANJAY_WITH_LWM2M11) \\\n            && defined(ANJAY_WITH_MODULE_FW_UPDATE_V11_RESOURCES)\n    set_update_deadline(fw);\n#    endif /* defined(ANJAY_WITH_LWM2M11) && \\\n              defined(ANJAY_WITH_MODULE_FW_UPDATE_V11_RESOURCES) */\n    anjay_t *anjay_locked = _anjay_get_from_sched(sched);\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    int result = user_state_perform_upgrade(anjay, fw);\n    if (result) {\n        fw_log(ERROR, _(\"user_state_perform_upgrade() failed: \") \"%d\", result);\n        handle_err_result(anjay, fw, UPDATE_STATE_DOWNLOADED, result,\n                          ANJAY_FW_UPDATE_RESULT_FAILED);\n    }\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n}\n\nstatic void schedule_upgrade(avs_sched_t *sched, const void *fw_ptr) {\n    fw_repr_t *fw = *(fw_repr_t *const *) fw_ptr;\n    anjay_t *anjay_locked = _anjay_get_from_sched(sched);\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    // Let's defer actually performing the upgrade to yet another scheduler run\n    // - the notification for the UPDATING state is probably being scheduled in\n    // the current one.\n    if (fw->state == UPDATE_STATE_UPDATING\n            && fw->user_state.state != UPDATE_STATE_UPDATING\n            && AVS_SCHED_NOW(sched, &fw->update_job, perform_upgrade, &fw,\n                             sizeof(fw))) {\n        update_state_and_update_result(anjay, fw, UPDATE_STATE_DOWNLOADED,\n                                       ANJAY_FW_UPDATE_RESULT_OUT_OF_MEMORY);\n    }\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n}\n\nstatic int fw_execute(anjay_unlocked_t *anjay,\n                      const anjay_dm_installed_object_t obj_ptr,\n                      anjay_iid_t iid,\n                      anjay_rid_t rid,\n                      anjay_unlocked_execute_ctx_t *ctx) {\n    (void) iid;\n    (void) ctx;\n\n    fw_repr_t *fw = get_fw(obj_ptr);\n    switch (rid) {\n    case FW_RES_UPDATE:\n        if (fw->state != UPDATE_STATE_DOWNLOADED) {\n            fw_log(WARNING,\n                   _(\"Firmware Update requested, but firmware not yet \"\n                     \"downloaded (state = \") \"%d\" _(\")\"),\n                   fw->state);\n            return ANJAY_ERR_METHOD_NOT_ALLOWED;\n        }\n        update_state_and_update_result(anjay, fw, UPDATE_STATE_UPDATING,\n                                       ANJAY_FW_UPDATE_RESULT_INITIAL);\n        // NOTE: This has to be called after update_state_and_update_result(),\n        // to make sure that schedule_upgrade() is called after notify_clb()\n        // and consequently, perform_upgrade() is called after trigger_observe()\n        // (if it's not delayed due to pmin).\n        if (AVS_SCHED_NOW(_anjay_get_scheduler_unlocked(anjay), &fw->update_job,\n                          schedule_upgrade, &fw, sizeof(fw))) {\n            fw_log(WARNING, _(\"Could not schedule the upgrade job\"));\n            update_state_and_update_result(\n                    anjay, fw, UPDATE_STATE_DOWNLOADED,\n                    ANJAY_FW_UPDATE_RESULT_OUT_OF_MEMORY);\n            return ANJAY_ERR_INTERNAL;\n        }\n        return 0;\n#    ifdef ANJAY_WITH_LWM2M11\n    case FW_RES_CANCEL:\n        if (fw->state != UPDATE_STATE_DOWNLOADING\n                && fw->state != UPDATE_STATE_DOWNLOADED) {\n            fw_log(WARNING,\n                   _(\"Firmware Update Cancel requested, but the firmware is \"\n                     \"being installed or has already been installed \"\n                     \"(state = \") \"%d\" _(\")\"),\n                   fw->state);\n            return ANJAY_ERR_METHOD_NOT_ALLOWED;\n        }\n#        ifdef ANJAY_WITH_DOWNLOADER\n        cancel_existing_download_if_in_progress(anjay, fw);\n#        endif // ANJAY_WITH_DOWNLOADER\n        reset_user_state(anjay, fw);\n        update_state_and_update_result(anjay, fw, UPDATE_STATE_IDLE,\n                                       ANJAY_FW_UPDATE_RESULT_UPDATE_CANCELLED);\n        return 0;\n#    endif // ANJAY_WITH_LWM2M11\n    default:\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\nstatic int fw_transaction_noop(anjay_unlocked_t *anjay,\n                               const anjay_dm_installed_object_t obj_ptr) {\n    (void) anjay;\n    (void) obj_ptr;\n    return 0;\n}\n\nstatic const anjay_unlocked_dm_object_def_t FIRMWARE_UPDATE = {\n    .oid = FW_OID,\n    .handlers = {\n        .list_instances = fw_list_instances,\n        .list_resources = fw_list_resources,\n        .resource_read = fw_read,\n        .resource_write = fw_write,\n        .list_resource_instances = fw_resource_instances,\n        .resource_execute = fw_execute,\n        .transaction_begin = fw_transaction_noop,\n        .transaction_validate = fw_transaction_noop,\n        .transaction_commit = fw_transaction_noop,\n        .transaction_rollback = fw_transaction_noop\n    }\n};\n\n#    ifdef ANJAY_WITH_SEND\nstatic void send_result_after_fw_update(anjay_unlocked_t *anjay,\n                                        fw_repr_t *fw) {\n    if (!fw->use_lwm2m_send) {\n        return;\n    }\n\n    const anjay_send_resource_path_t paths[] = {\n        SEND_FW_RES_PATH(STATE), SEND_FW_RES_PATH(UPDATE_RESULT),\n        SEND_RES_PATH(ANJAY_DM_OID_DEVICE, 0,\n                      ANJAY_DM_RID_DEVICE_FIRMWARE_VERSION),\n        SEND_RES_PATH(ANJAY_DM_OID_DEVICE, 0,\n                      ANJAY_DM_RID_DEVICE_SOFTWARE_VERSION)\n    };\n    perform_lwm2m_send(anjay, paths, AVS_ARRAY_SIZE(paths));\n}\n#    endif // ANJAY_WITH_SEND\n\nstatic void fw_delete(void *fw_) {\n    fw_repr_t *fw = (fw_repr_t *) fw_;\n    avs_sched_del(&fw->update_job);\n#    ifdef ANJAY_WITH_DOWNLOADER\n    avs_sched_del(&fw->resume_download_job);\n#    endif // ANJAY_WITH_DOWNLOADER\n    avs_free((void *) (intptr_t) fw->package_uri);\n    // NOTE: fw itself will be freed when cleaning the objects list\n}\n\nstatic int\ninitialize_fw_repr(anjay_unlocked_t *anjay,\n                   fw_repr_t *repr,\n                   const anjay_fw_update_initial_state_t *initial_state) {\n    if (!initial_state) {\n        return 0;\n    }\n#    ifdef ANJAY_WITH_DOWNLOADER\n    repr->prefer_same_socket_downloads =\n            initial_state->prefer_same_socket_downloads;\n#    endif // ANJAY_WITH_DOWNLOADER\n#    if defined(ANJAY_WITH_LWM2M11) \\\n            && defined(ANJAY_WITH_MODULE_FW_UPDATE_V11_RESOURCES)\n    repr->severity = initial_state->persisted_severity;\n    repr->last_state_change_time =\n            initial_state->persisted_last_state_change_time;\n    repr->update_deadline = initial_state->persisted_update_deadline;\n#    endif /* defined(ANJAY_WITH_LWM2M11) && \\\n              defined(ANJAY_WITH_MODULE_FW_UPDATE_V11_RESOURCES) */\n#    ifdef ANJAY_WITH_SEND\n    repr->use_lwm2m_send = initial_state->use_lwm2m_send;\n#    endif // ANJAY_WITH_SEND\n\n    switch (initial_state->result) {\n    case ANJAY_FW_UPDATE_INITIAL_DOWNLOADED:\n        if (initial_state->persisted_uri\n                && !(repr->package_uri =\n                             avs_strdup(initial_state->persisted_uri))) {\n            fw_log(WARNING, _(\"Could not copy the persisted Package URI\"));\n        }\n        repr->user_state.state = UPDATE_STATE_DOWNLOADED;\n        repr->state = UPDATE_STATE_DOWNLOADED;\n        return 0;\n    case ANJAY_FW_UPDATE_INITIAL_DOWNLOADING: {\n#    ifdef ANJAY_WITH_DOWNLOADER\n        repr->user_state.state = UPDATE_STATE_DOWNLOADING;\n        size_t resume_offset = initial_state->resume_offset;\n        if (resume_offset > 0 && !initial_state->resume_etag) {\n            fw_log(WARNING,\n                   _(\"ETag not set, need to start from the beginning\"));\n            reset_user_state(anjay, repr);\n            resume_offset = 0;\n        }\n        if (!initial_state->persisted_uri\n                || !(repr->package_uri =\n                             avs_strdup(initial_state->persisted_uri))) {\n            fw_log(WARNING, _(\"Could not copy the persisted Package URI, not \"\n                              \"resuming firmware download\"));\n            reset_user_state(anjay, repr);\n        } else if (schedule_background_anjay_download(\n                           anjay, repr, resume_offset,\n                           initial_state->resume_etag)) {\n            fw_log(WARNING, _(\"Could not resume firmware download\"));\n            reset_user_state(anjay, repr);\n            if (repr->result == ANJAY_FW_UPDATE_RESULT_CONNECTION_LOST\n                    && initial_state->resume_etag\n                    && schedule_background_anjay_download(anjay, repr, 0,\n                                                          NULL)) {\n                fw_log(WARNING, _(\"Could not retry firmware download\"));\n            }\n        }\n#    else  // ANJAY_WITH_DOWNLOADER\n        (void) anjay;\n        fw_log(WARNING,\n               _(\"Unable to resume download: PULL download not supported\"));\n#    endif // ANJAY_WITH_DOWNLOADER\n        return 0;\n    }\n    case ANJAY_FW_UPDATE_INITIAL_UPDATING:\n        repr->user_state.state = UPDATE_STATE_UPDATING;\n        repr->state = UPDATE_STATE_UPDATING;\n        repr->result = ANJAY_FW_UPDATE_RESULT_INITIAL;\n        return 0;\n    case ANJAY_FW_UPDATE_INITIAL_NEUTRAL:\n    case ANJAY_FW_UPDATE_INITIAL_SUCCESS:\n    case ANJAY_FW_UPDATE_INITIAL_INTEGRITY_FAILURE:\n    case ANJAY_FW_UPDATE_INITIAL_FAILED:\n        repr->result = (anjay_fw_update_result_t) initial_state->result;\n        return 0;\n    default:\n        fw_log(ERROR, _(\"Invalid initial_state->result\"));\n        return -1;\n    }\n}\n\nint anjay_fw_update_install(\n        anjay_t *anjay_locked,\n        const anjay_fw_update_handlers_t *handlers,\n        void *user_arg,\n        const anjay_fw_update_initial_state_t *initial_state) {\n    assert(anjay_locked);\n    int result = -1;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    AVS_LIST(fw_repr_t) repr = AVS_LIST_NEW_ELEMENT(fw_repr_t);\n    if (!repr) {\n        _anjay_log_oom();\n    } else {\n        repr->def = &FIRMWARE_UPDATE;\n        _anjay_dm_installed_object_init_unlocked(&repr->def_ptr, &repr->def);\n        repr->user_state.handlers = handlers;\n        repr->user_state.arg = user_arg;\n\n        if (!initialize_fw_repr(anjay, repr, initial_state)\n                && !_anjay_dm_module_install(anjay, fw_delete, repr)) {\n            _ANJAY_ASSERT_INSTALLED_OBJECT_IS_FIRST_FIELD(fw_repr_t, def_ptr);\n            AVS_LIST(anjay_dm_installed_object_t) entry = &repr->def_ptr;\n            if (_anjay_register_object_unlocked(anjay, &entry)) {\n                result = _anjay_dm_module_uninstall(anjay, fw_delete);\n                assert(!result);\n                result = -1;\n            } else {\n#    ifdef ANJAY_WITH_SEND\n                if (initial_state->result != ANJAY_FW_UPDATE_INITIAL_NEUTRAL) {\n                    send_result_after_fw_update(anjay, repr);\n                }\n#    endif // ANJAY_WITH_SEND\n                result = 0;\n            }\n        }\n        if (result) {\n            AVS_LIST_CLEAR(&repr);\n        }\n    }\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return result;\n}\n\nstatic bool is_error_result(anjay_fw_update_result_t result) {\n    return result != ANJAY_FW_UPDATE_RESULT_INITIAL\n           && result != ANJAY_FW_UPDATE_RESULT_SUCCESS;\n}\n\nstatic bool is_result_change_allowed(fw_update_state_t current_state,\n                                     anjay_fw_update_result_t new_result) {\n#    ifdef ANJAY_WITH_LWM2M11\n    if (new_result == ANJAY_FW_UPDATE_RESULT_UPDATE_CANCELLED) {\n        // it is never allowed\n        return false;\n    }\n#    endif // ANJAY_WITH_LWM2M11\n    switch (current_state) {\n    case UPDATE_STATE_IDLE:\n        // changing result while nothing is going on is pointless\n        return false;\n    case UPDATE_STATE_DOWNLOADING:\n    case UPDATE_STATE_DOWNLOADED:\n        // FOTA is not supposed to be performed unless requested by the server;\n        // failing while downloading should still be an option\n        return is_error_result(new_result);\n    case UPDATE_STATE_UPDATING:\n        // unexpected reset is likely to confuse the server\n        return new_result != ANJAY_FW_UPDATE_RESULT_INITIAL;\n    }\n\n    AVS_UNREACHABLE(\"invalid enum value\");\n    return false;\n}\n\nint anjay_fw_update_set_result(anjay_t *anjay_locked,\n                               anjay_fw_update_result_t result) {\n    assert(anjay_locked);\n    int retval = -1;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    const anjay_dm_installed_object_t *obj =\n            _anjay_dm_find_object_by_oid(_anjay_get_dm(anjay),\n                                         ANJAY_DM_OID_FIRMWARE_UPDATE);\n    if (!obj) {\n        fw_log(WARNING, _(\"Firmware Update object not installed\"));\n    } else {\n        fw_repr_t *fw = get_fw(*obj);\n        assert(fw);\n\n        if (!is_result_change_allowed(fw->state, result)) {\n            fw_log(WARNING,\n                   _(\"Firmware Update Result change to \") \"%d\" _(\n                           \" not allowed in State \") \"%d\",\n                   (int) result, (int) fw->state);\n        }\n#    ifdef ANJAY_WITH_LWM2M11\n        else if (result == ANJAY_FW_UPDATE_RESULT_DEFERRED) {\n            update_state_and_update_result(anjay, fw, UPDATE_STATE_DOWNLOADED,\n                                           result);\n            retval = 0;\n        }\n#    endif // ANJAY_WITH_LWM2M11\n        else {\n            reset_user_state(anjay, fw);\n            update_state_and_update_result(anjay, fw, UPDATE_STATE_IDLE,\n                                           result);\n            retval = 0;\n        }\n    }\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return retval;\n}\n\n#    ifdef ANJAY_WITH_DOWNLOADER\nvoid anjay_fw_update_pull_suspend(anjay_t *anjay_locked) {\n    assert(anjay_locked);\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    const anjay_dm_installed_object_t *obj =\n            _anjay_dm_find_object_by_oid(_anjay_get_dm(anjay),\n                                         ANJAY_DM_OID_FIRMWARE_UPDATE);\n    if (!obj) {\n        fw_log(WARNING, _(\"Firmware Update object not installed\"));\n    } else {\n        fw_repr_t *fw = get_fw(*obj);\n        assert(fw);\n        if (fw->download_handle) {\n            _anjay_download_suspend_unlocked(anjay, fw->download_handle);\n        }\n        fw->downloads_suspended = true;\n    }\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n}\n\nint anjay_fw_update_pull_reconnect(anjay_t *anjay_locked) {\n    assert(anjay_locked);\n    int result = -1;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    const anjay_dm_installed_object_t *obj =\n            _anjay_dm_find_object_by_oid(_anjay_get_dm(anjay),\n                                         ANJAY_DM_OID_FIRMWARE_UPDATE);\n    if (!obj) {\n        fw_log(WARNING, _(\"Firmware Update object not installed\"));\n    } else {\n        fw_repr_t *fw = get_fw(*obj);\n        assert(fw);\n        fw->downloads_suspended = false;\n        if (fw->download_handle) {\n            result = _anjay_download_reconnect_unlocked(anjay,\n                                                        fw->download_handle);\n        } else {\n            result = 0;\n        }\n    }\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return result;\n}\n#    endif // ANJAY_WITH_DOWNLOADER\n\n#    if defined(ANJAY_WITH_LWM2M11) \\\n            && defined(ANJAY_WITH_MODULE_FW_UPDATE_V11_RESOURCES)\navs_time_real_t anjay_fw_update_get_deadline(anjay_t *anjay_locked) {\n    assert(anjay_locked);\n    avs_time_real_t result = AVS_TIME_REAL_INVALID;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    const anjay_dm_installed_object_t *obj =\n            _anjay_dm_find_object_by_oid(_anjay_get_dm(anjay),\n                                         ANJAY_DM_OID_FIRMWARE_UPDATE);\n    if (!obj) {\n        fw_log(WARNING, _(\"Firmware Update object not installed\"));\n    } else {\n        fw_repr_t *fw = get_fw(*obj);\n        assert(fw);\n        result = fw->update_deadline;\n    }\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return result;\n}\n\nanjay_fw_update_severity_t anjay_fw_update_get_severity(anjay_t *anjay_locked) {\n    assert(anjay_locked);\n    anjay_fw_update_severity_t result = ANJAY_FW_UPDATE_SEVERITY_MANDATORY;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    const anjay_dm_installed_object_t *obj =\n            _anjay_dm_find_object_by_oid(_anjay_get_dm(anjay),\n                                         ANJAY_DM_OID_FIRMWARE_UPDATE);\n    if (!obj) {\n        fw_log(WARNING, _(\"Firmware Update object not installed\"));\n    } else {\n        fw_repr_t *fw = get_fw(*obj);\n        assert(fw);\n        result = fw->severity;\n    }\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return result;\n}\n\navs_time_real_t\nanjay_fw_update_get_last_state_change_time(anjay_t *anjay_locked) {\n    assert(anjay_locked);\n    avs_time_real_t result = AVS_TIME_REAL_INVALID;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    const anjay_dm_installed_object_t *obj =\n            _anjay_dm_find_object_by_oid(_anjay_get_dm(anjay),\n                                         ANJAY_DM_OID_FIRMWARE_UPDATE);\n    if (!obj) {\n        fw_log(WARNING, _(\"Firmware Update object not installed\"));\n    } else {\n        fw_repr_t *fw = get_fw(*obj);\n        assert(fw);\n        result = fw->last_state_change_time;\n    }\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return result;\n}\n#    endif /* defined(ANJAY_WITH_LWM2M11) && \\\n              defined(ANJAY_WITH_MODULE_FW_UPDATE_V11_RESOURCES) */\n\n#endif // ANJAY_WITH_MODULE_FW_UPDATE\n"
  },
  {
    "path": "src/modules/ipso/anjay_ipso_3d_sensor.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n#include <anjay_init.h>\n\n#ifdef ANJAY_WITH_MODULE_IPSO_OBJECTS\n\n#    include <assert.h>\n#    include <stdbool.h>\n\n#    include <anjay/anjay.h>\n#    include <anjay/dm.h>\n#    include <anjay/ipso_objects.h>\n\n#    include <anjay_modules/anjay_dm_utils.h>\n#    include <anjay_modules/anjay_utils_core.h>\n\n#    include <avsystem/commons/avs_defs.h>\n#    include <avsystem/commons/avs_log.h>\n#    include <avsystem/commons/avs_memory.h>\n\nVISIBILITY_SOURCE_BEGIN\n\n/**\n * Min Range Value: R, Single, Optional\n * type: float, range: N/A, unit: N/A\n * The minimum value that can be measured the sensor.\n */\n#    define RID_MIN_RANGE_VALUE 5603\n\n/**\n * Max Range Value: R, Single, Optional\n * type: float, range: N/A, unit: N/A\n * The maximum value that can be measured by the sensor.\n */\n#    define RID_MAX_RANGE_VALUE 5604\n\n/**\n * Sensor Units: R, Single, Optional\n * type: string, range: N/A, unit: N/A\n * Measurement Units Definition.\n */\n#    define RID_SENSOR_UNITS 5701\n\n/**\n * X Value: R, Single, Mandatory\n * type: float, range: N/A, unit: N/A\n * The measured value along the X axis.\n */\n#    define RID_X_VALUE 5702\n\n/**\n * Y Value: R, Single, Optional\n * type: float, range: N/A, unit: N/A\n * The measured value along the Y axis.\n */\n#    define RID_Y_VALUE 5703\n\n/**\n * Z Value: R, Single, Optional\n * type: float, range: N/A, unit: N/A\n * The measured value along the Z axis.\n */\n#    define RID_Z_VALUE 5704\n\ntypedef struct {\n    anjay_ipso_3d_sensor_impl_t impl;\n    bool initialized;\n\n    double x_value;\n    double y_value;\n    double z_value;\n} anjay_ipso_3d_sensor_instance_t;\n\ntypedef struct {\n    anjay_dm_installed_object_t obj_def_ptr;\n    const anjay_unlocked_dm_object_def_t *obj_def;\n    anjay_unlocked_dm_object_def_t def;\n\n    size_t num_instances;\n    anjay_ipso_3d_sensor_instance_t instances[];\n} anjay_ipso_3d_sensor_t;\n\nstatic anjay_ipso_3d_sensor_t *\nget_obj(const anjay_dm_installed_object_t *obj_ptr) {\n    assert(obj_ptr);\n    return AVS_CONTAINER_OF(_anjay_dm_installed_object_get_unlocked(obj_ptr),\n                            anjay_ipso_3d_sensor_t, obj_def);\n}\n\nstatic int\nipso_3d_sensor_list_instances(anjay_unlocked_t *anjay,\n                              const anjay_dm_installed_object_t obj_ptr,\n                              anjay_unlocked_dm_list_ctx_t *ctx) {\n    (void) anjay;\n\n    anjay_ipso_3d_sensor_t *obj = get_obj(&obj_ptr);\n\n    for (anjay_iid_t iid = 0; iid < obj->num_instances; iid++) {\n        if (obj->instances[iid].initialized) {\n            _anjay_dm_emit_unlocked(ctx, iid);\n        }\n    }\n\n    return 0;\n}\n\nstatic int\nipso_3d_sensor_list_resources(anjay_unlocked_t *anjay,\n                              const anjay_dm_installed_object_t obj_ptr,\n                              anjay_iid_t iid,\n                              anjay_unlocked_dm_resource_list_ctx_t *ctx) {\n    (void) anjay;\n\n    anjay_ipso_3d_sensor_t *obj = get_obj(&obj_ptr);\n    assert(iid < obj->num_instances);\n    anjay_ipso_3d_sensor_instance_t *inst = &obj->instances[iid];\n    assert(inst->initialized);\n\n    if (!isnan(inst->impl.min_range_value)) {\n        _anjay_dm_emit_res_unlocked(ctx, RID_MIN_RANGE_VALUE, ANJAY_DM_RES_R,\n                                    ANJAY_DM_RES_PRESENT);\n    }\n    if (!isnan(inst->impl.max_range_value)) {\n        _anjay_dm_emit_res_unlocked(ctx, RID_MAX_RANGE_VALUE, ANJAY_DM_RES_R,\n                                    ANJAY_DM_RES_PRESENT);\n    }\n    _anjay_dm_emit_res_unlocked(ctx, RID_SENSOR_UNITS, ANJAY_DM_RES_R,\n                                ANJAY_DM_RES_PRESENT);\n    _anjay_dm_emit_res_unlocked(ctx, RID_X_VALUE, ANJAY_DM_RES_R,\n                                ANJAY_DM_RES_PRESENT);\n    if (inst->impl.use_y_value) {\n        _anjay_dm_emit_res_unlocked(ctx, RID_Y_VALUE, ANJAY_DM_RES_R,\n                                    ANJAY_DM_RES_PRESENT);\n    }\n    if (inst->impl.use_z_value) {\n        _anjay_dm_emit_res_unlocked(ctx, RID_Z_VALUE, ANJAY_DM_RES_R,\n                                    ANJAY_DM_RES_PRESENT);\n    }\n\n    return 0;\n}\n\nstatic int update_values(anjay_unlocked_t *anjay,\n                         anjay_oid_t oid,\n                         anjay_iid_t iid,\n                         anjay_ipso_3d_sensor_instance_t *inst) {\n    double x_value = NAN, y_value = NAN, z_value = NAN;\n    int err = -1;\n    ANJAY_MUTEX_UNLOCK_FOR_CALLBACK(anjay_locked, anjay);\n    err = inst->impl.get_values(iid, inst->impl.user_context, &x_value,\n                                &y_value, &z_value);\n    ANJAY_MUTEX_LOCK_AFTER_CALLBACK(anjay_locked);\n\n    if (err) {\n        return err;\n    }\n\n    if (x_value != inst->x_value) {\n        inst->x_value = x_value;\n        (void) _anjay_notify_changed_unlocked(anjay, oid, iid, RID_X_VALUE);\n    }\n\n    if (inst->impl.use_y_value && y_value != inst->y_value) {\n        inst->y_value = y_value;\n        (void) _anjay_notify_changed_unlocked(anjay, oid, iid, RID_Y_VALUE);\n    }\n\n    if (inst->impl.use_z_value && z_value != inst->z_value) {\n        inst->z_value = z_value;\n        (void) _anjay_notify_changed_unlocked(anjay, oid, iid, RID_Z_VALUE);\n    }\n\n    return 0;\n}\n\nstatic int\nipso_3d_sensor_resource_read(anjay_unlocked_t *anjay,\n                             const anjay_dm_installed_object_t obj_ptr,\n                             anjay_iid_t iid,\n                             anjay_rid_t rid,\n                             anjay_riid_t riid,\n                             anjay_unlocked_output_ctx_t *ctx) {\n    (void) anjay;\n    (void) riid;\n\n    anjay_ipso_3d_sensor_t *obj = get_obj(&obj_ptr);\n    assert(iid < obj->num_instances);\n    anjay_ipso_3d_sensor_instance_t *inst = &obj->instances[iid];\n    assert(inst->initialized);\n\n    switch (rid) {\n    case RID_SENSOR_UNITS:\n        assert(riid == ANJAY_ID_INVALID);\n        return _anjay_ret_string_unlocked(ctx, inst->impl.unit);\n\n    case RID_X_VALUE:\n        assert(riid == ANJAY_ID_INVALID);\n        (void) update_values(anjay, obj->def.oid, iid, inst);\n        return _anjay_ret_double_unlocked(ctx, inst->x_value);\n\n    case RID_Y_VALUE:\n        assert(riid == ANJAY_ID_INVALID);\n        if (inst->impl.use_y_value) {\n            (void) update_values(anjay, obj->def.oid, iid, inst);\n            return _anjay_ret_double_unlocked(ctx, inst->y_value);\n        } else {\n            return ANJAY_ERR_METHOD_NOT_ALLOWED;\n        }\n\n    case RID_Z_VALUE:\n        assert(riid == ANJAY_ID_INVALID);\n        if (inst->impl.use_z_value) {\n            (void) update_values(anjay, obj->def.oid, iid, inst);\n            return _anjay_ret_double_unlocked(ctx, inst->z_value);\n        } else {\n            return ANJAY_ERR_METHOD_NOT_ALLOWED;\n        }\n\n    case RID_MIN_RANGE_VALUE:\n        assert(riid == ANJAY_ID_INVALID);\n        if (isnan(inst->impl.min_range_value)) {\n            return ANJAY_ERR_METHOD_NOT_ALLOWED;\n        } else {\n            return _anjay_ret_double_unlocked(ctx, inst->impl.min_range_value);\n        }\n\n    case RID_MAX_RANGE_VALUE:\n        assert(riid == ANJAY_ID_INVALID);\n        if (isnan(inst->impl.max_range_value)) {\n            return ANJAY_ERR_METHOD_NOT_ALLOWED;\n        } else {\n            return _anjay_ret_double_unlocked(ctx, inst->impl.max_range_value);\n        }\n\n    default:\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\nstatic anjay_ipso_3d_sensor_t *obj_from_oid(anjay_unlocked_t *anjay,\n                                            anjay_oid_t oid) {\n    const anjay_dm_installed_object_t *installed_obj_ptr =\n            _anjay_dm_find_object_by_oid(_anjay_get_dm(anjay), oid);\n    if (!_anjay_dm_installed_object_is_valid_unlocked(installed_obj_ptr)) {\n        return NULL;\n    }\n\n    // Checks if it is really an instance of anjay_ipso_3d_sensor_t\n    return (*_anjay_dm_installed_object_get_unlocked(installed_obj_ptr))\n                                   ->handlers.list_instances\n                           == ipso_3d_sensor_list_instances\n                   ? get_obj(installed_obj_ptr)\n                   : NULL;\n}\n\nint anjay_ipso_3d_sensor_install(anjay_t *anjay_locked,\n                                 anjay_oid_t oid,\n                                 size_t num_instances) {\n    if (!anjay_locked) {\n        _anjay_log(ipso, ERROR, _(\"Anjay pointer is NULL\"));\n        return -1;\n    }\n\n    int err = 0;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n\n    AVS_LIST(anjay_dm_installed_object_t) entry;\n    anjay_ipso_3d_sensor_t *obj =\n            (anjay_ipso_3d_sensor_t *) AVS_LIST_NEW_BUFFER(\n                    sizeof(anjay_ipso_3d_sensor_t)\n                    + num_instances * sizeof(anjay_ipso_3d_sensor_instance_t));\n    if (!obj) {\n        _anjay_log_oom();\n        err = -1;\n        goto finish;\n    }\n\n    obj->def = (anjay_unlocked_dm_object_def_t) {\n        .oid = oid,\n        .handlers = {\n            .list_instances = ipso_3d_sensor_list_instances,\n            .list_resources = ipso_3d_sensor_list_resources,\n            .resource_read = ipso_3d_sensor_resource_read,\n        }\n    };\n\n    obj->obj_def = &obj->def;\n    obj->num_instances = num_instances;\n\n    _anjay_dm_installed_object_init_unlocked(&obj->obj_def_ptr, &obj->obj_def);\n    _ANJAY_ASSERT_INSTALLED_OBJECT_IS_FIRST_FIELD(anjay_ipso_3d_sensor_t,\n                                                  obj_def_ptr);\n    entry = &obj->obj_def_ptr;\n    if (_anjay_register_object_unlocked(anjay, &entry)) {\n        avs_free(obj);\n        err = -1;\n    }\n\nfinish:;\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return err;\n}\n\nint anjay_ipso_3d_sensor_instance_add(anjay_t *anjay_locked,\n                                      anjay_oid_t oid,\n                                      anjay_iid_t iid,\n                                      anjay_ipso_3d_sensor_impl_t impl) {\n    if (!anjay_locked) {\n        _anjay_log(ipso, ERROR, _(\"Anjay pointer is NULL\"));\n        return -1;\n    }\n\n    int err = 0;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n\n    anjay_ipso_3d_sensor_instance_t *inst;\n    anjay_ipso_3d_sensor_t *obj = obj_from_oid(anjay, oid);\n    if (!obj) {\n        _anjay_log(ipso, ERROR, _(\"Object\") \" %d\" _(\" not installed\"), oid);\n        err = -1;\n        goto finish;\n    }\n\n    if (iid >= obj->num_instances) {\n        _anjay_log(ipso, ERROR, _(\"IID too large\"));\n        err = -1;\n        goto finish;\n    }\n\n    if (!impl.get_values) {\n        _anjay_log(ipso, ERROR, _(\"Callback is NULL\"));\n        goto finish;\n    }\n\n    double x_value, y_value, z_value;\n    ANJAY_MUTEX_UNLOCK_FOR_CALLBACK(anjay_locked_2, anjay);\n    if (impl.get_values(iid, impl.user_context, &x_value, &y_value, &z_value)) {\n        _anjay_log(ipso, WARNING, _(\"Read of\") \" /%d/%d\" _(\" failed\"), oid,\n                   iid);\n        x_value = NAN;\n        y_value = NAN;\n        z_value = NAN;\n    }\n    ANJAY_MUTEX_LOCK_AFTER_CALLBACK(anjay_locked_2);\n\n    inst = &obj->instances[iid];\n\n    inst->initialized = true;\n    inst->impl = impl;\n    inst->x_value = x_value;\n    if (inst->impl.use_y_value) {\n        inst->y_value = y_value;\n    }\n    if (inst->impl.use_z_value) {\n        inst->z_value = z_value;\n    }\n\n    _anjay_notify_instances_changed_unlocked(anjay, oid);\n\n    (void) _anjay_notify_changed_unlocked(anjay, oid, iid, RID_X_VALUE);\n    if (inst->impl.use_y_value) {\n        (void) _anjay_notify_changed_unlocked(anjay, oid, iid, RID_Y_VALUE);\n    }\n    if (inst->impl.use_z_value) {\n        (void) _anjay_notify_changed_unlocked(anjay, oid, iid, RID_Z_VALUE);\n    }\n\nfinish:;\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return err;\n}\n\nint anjay_ipso_3d_sensor_instance_remove(anjay_t *anjay_locked,\n                                         anjay_oid_t oid,\n                                         anjay_iid_t iid) {\n    if (!anjay_locked) {\n        _anjay_log(ipso, ERROR, _(\"Anjay pointer is NULL\"));\n        return -1;\n    }\n\n    int err = 0;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n\n    anjay_ipso_3d_sensor_t *obj = obj_from_oid(anjay, oid);\n    if (!obj) {\n        _anjay_log(ipso, ERROR, _(\"Object\") \" %d\" _(\" not installed\"), oid);\n        err = -1;\n        goto finish;\n    }\n\n    if (iid >= obj->num_instances || !obj->instances[iid].initialized) {\n        _anjay_log(ipso, ERROR, _(\"Object\") \" %d\" _(\" has no instance\") \" %d\",\n                   oid, iid);\n        err = -1;\n    } else {\n        obj->instances[iid].initialized = false;\n    }\n\n    _anjay_notify_instances_changed_unlocked(anjay, oid);\n\nfinish:;\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return err;\n}\n\nint anjay_ipso_3d_sensor_update(anjay_t *anjay_locked,\n                                anjay_oid_t oid,\n                                anjay_iid_t iid) {\n    if (!anjay_locked) {\n        _anjay_log(ipso, ERROR, _(\"Anjay pointer is NULL\"));\n        return -1;\n    }\n\n    int err = 0;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n\n    anjay_ipso_3d_sensor_t *obj = obj_from_oid(anjay, oid);\n    if (!obj) {\n        _anjay_log(ipso, ERROR, _(\"Object\") \" %d\" _(\" not installed\"), oid);\n        err = -1;\n        goto finish;\n    }\n\n    anjay_ipso_3d_sensor_instance_t *inst;\n    if (iid > obj->num_instances\n            || !(inst = &obj->instances[iid])->initialized) {\n        _anjay_log(ipso, ERROR, _(\"Object\") \" %d\" _(\" has no instance\") \" %d\",\n                   oid, iid);\n        err = -1;\n        goto finish;\n    }\n\n    if (update_values(anjay, oid, iid, inst)) {\n        _anjay_log(ipso, WARNING, _(\"Update of\") \" /%d/%d\" _(\" failed\"), oid,\n                   iid);\n        err = -1;\n    }\n\nfinish:;\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return err;\n}\n\n#endif // ANJAY_WITH_MODULE_IPSO_OBJECTS\n"
  },
  {
    "path": "src/modules/ipso/anjay_ipso_basic_sensor.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n#include <anjay_init.h>\n\n#ifdef ANJAY_WITH_MODULE_IPSO_OBJECTS\n\n#    include <assert.h>\n#    include <math.h>\n#    include <stdbool.h>\n\n#    include <anjay/anjay.h>\n#    include <anjay/dm.h>\n#    include <anjay/ipso_objects.h>\n\n#    include <anjay_modules/anjay_dm_utils.h>\n#    include <anjay_modules/anjay_utils_core.h>\n\n#    include <avsystem/commons/avs_defs.h>\n#    include <avsystem/commons/avs_log.h>\n#    include <avsystem/commons/avs_memory.h>\n\nVISIBILITY_SOURCE_BEGIN\n\n/**\n * Min Measured Value: R, Single, Optional\n * type: float, range: N/A, unit: N/A\n * The minimum value measured by the sensor since power ON or reset.\n */\n#    define RID_MIN_MEASURED_VALUE 5601\n\n/**\n * Max Measured Value: R, Single, Optional\n * type: float, range: N/A, unit: N/A\n * The maximum value measured by the sensor since power ON or reset.\n */\n#    define RID_MAX_MEASURED_VALUE 5602\n\n/**\n * Min Range Value: R, Single, Optional\n * type: float, range: N/A, unit: N/A\n * The minimum value that can be measured the sensor.\n */\n#    define RID_MIN_RANGE_VALUE 5603\n\n/**\n * Max Range Value: R, Single, Optional\n * type: float, range: N/A, unit: N/A\n * The maximum value that can be measured by the sensor.\n */\n#    define RID_MAX_RANGE_VALUE 5604\n\n/**\n * Reset Min and Max Measured Values: E, Single, Optional\n * type: N/A, range: N/A, unit: N/A\n * Reset the Min and Max Measured Values to Current Value.\n */\n#    define RID_RESET_MIN_AND_MAX_MEASURED_VALUES 5605\n\n/**\n * Sensor Value: R, Single, Mandatory\n * type: float, range: N/A, unit: N/A\n * Last or Current Measured Value from the Sensor.\n */\n#    define RID_SENSOR_VALUE 5700\n\n/**\n * Sensor Units: R, Single, Optional\n * type: string, range: N/A, unit: N/A\n * Measurement Units Definition.\n */\n#    define RID_SENSOR_UNITS 5701\n\ntypedef struct {\n    anjay_ipso_basic_sensor_impl_t impl;\n    bool initialized;\n\n    double curr_value;\n    double min_value;\n    double max_value;\n} anjay_ipso_basic_sensor_instance_t;\n\ntypedef struct {\n    anjay_dm_installed_object_t obj_def_ptr;\n    const anjay_unlocked_dm_object_def_t *obj_def;\n    anjay_unlocked_dm_object_def_t def;\n\n    size_t num_instances;\n    anjay_ipso_basic_sensor_instance_t instances[];\n} anjay_ipso_basic_sensor_t;\n\nstatic anjay_ipso_basic_sensor_t *\nget_obj(const anjay_dm_installed_object_t *obj_ptr) {\n    assert(obj_ptr);\n    return AVS_CONTAINER_OF(_anjay_dm_installed_object_get_unlocked(obj_ptr),\n                            anjay_ipso_basic_sensor_t, obj_def);\n}\n\nstatic int\nbasic_sensor_list_instances(anjay_unlocked_t *anjay,\n                            const anjay_dm_installed_object_t obj_ptr,\n                            anjay_unlocked_dm_list_ctx_t *ctx) {\n    (void) anjay;\n\n    anjay_ipso_basic_sensor_t *obj = get_obj(&obj_ptr);\n\n    for (anjay_iid_t iid = 0; iid < obj->num_instances; iid++) {\n        if (obj->instances[iid].initialized) {\n            _anjay_dm_emit_unlocked(ctx, iid);\n        }\n    }\n\n    return 0;\n}\n\nstatic int\nbasic_sensor_list_resources(anjay_unlocked_t *anjay,\n                            const anjay_dm_installed_object_t obj_ptr,\n                            anjay_iid_t iid,\n                            anjay_unlocked_dm_resource_list_ctx_t *ctx) {\n    (void) anjay;\n\n    anjay_ipso_basic_sensor_t *obj = get_obj(&obj_ptr);\n    assert(iid < obj->num_instances);\n    anjay_ipso_basic_sensor_instance_t *inst = &obj->instances[iid];\n    assert(inst->initialized);\n\n    _anjay_dm_emit_res_unlocked(ctx, RID_MIN_MEASURED_VALUE, ANJAY_DM_RES_R,\n                                ANJAY_DM_RES_PRESENT);\n    _anjay_dm_emit_res_unlocked(ctx, RID_MAX_MEASURED_VALUE, ANJAY_DM_RES_R,\n                                ANJAY_DM_RES_PRESENT);\n    if (!isnan(inst->impl.min_range_value)) {\n        _anjay_dm_emit_res_unlocked(ctx, RID_MIN_RANGE_VALUE, ANJAY_DM_RES_R,\n                                    ANJAY_DM_RES_PRESENT);\n    }\n    if (!isnan(inst->impl.max_range_value)) {\n        _anjay_dm_emit_res_unlocked(ctx, RID_MAX_RANGE_VALUE, ANJAY_DM_RES_R,\n                                    ANJAY_DM_RES_PRESENT);\n    }\n    _anjay_dm_emit_res_unlocked(ctx, RID_RESET_MIN_AND_MAX_MEASURED_VALUES,\n                                ANJAY_DM_RES_E, ANJAY_DM_RES_PRESENT);\n    _anjay_dm_emit_res_unlocked(ctx, RID_SENSOR_VALUE, ANJAY_DM_RES_R,\n                                ANJAY_DM_RES_PRESENT);\n    _anjay_dm_emit_res_unlocked(ctx, RID_SENSOR_UNITS, ANJAY_DM_RES_R,\n                                ANJAY_DM_RES_PRESENT);\n\n    return 0;\n}\n\nstatic int update_curr_value(anjay_unlocked_t *anjay,\n                             anjay_oid_t oid,\n                             anjay_iid_t iid,\n                             anjay_ipso_basic_sensor_instance_t *inst) {\n    double value = NAN;\n    int err = -1;\n    ANJAY_MUTEX_UNLOCK_FOR_CALLBACK(anjay_locked, anjay);\n    err = inst->impl.get_value(iid, inst->impl.user_context, &value);\n    ANJAY_MUTEX_LOCK_AFTER_CALLBACK(anjay_locked);\n\n    if (err) {\n        return err;\n    }\n\n    if (value != inst->curr_value) {\n        inst->curr_value = value;\n        (void) _anjay_notify_changed_unlocked(anjay, oid, iid,\n                                              RID_SENSOR_VALUE);\n    }\n\n    return 0;\n}\n\nstatic int update_values(anjay_unlocked_t *anjay,\n                         anjay_oid_t oid,\n                         anjay_iid_t iid,\n                         anjay_ipso_basic_sensor_instance_t *inst) {\n    if (update_curr_value(anjay, oid, iid, inst)) {\n        return -1;\n    }\n\n    const double min_value = AVS_MIN(inst->min_value, inst->curr_value);\n    if (min_value != inst->min_value) {\n        inst->min_value = min_value;\n        (void) _anjay_notify_changed_unlocked(anjay, oid, iid,\n                                              RID_MIN_MEASURED_VALUE);\n    }\n\n    const double max_value = AVS_MAX(inst->max_value, inst->curr_value);\n    if (max_value != inst->max_value) {\n        inst->max_value = max_value;\n        (void) _anjay_notify_changed_unlocked(anjay, oid, iid,\n                                              RID_MAX_MEASURED_VALUE);\n    }\n\n    return 0;\n}\n\nstatic int basic_sensor_resource_read(anjay_unlocked_t *anjay,\n                                      const anjay_dm_installed_object_t obj_ptr,\n                                      anjay_iid_t iid,\n                                      anjay_rid_t rid,\n                                      anjay_riid_t riid,\n                                      anjay_unlocked_output_ctx_t *ctx) {\n    (void) anjay;\n    (void) riid;\n\n    anjay_ipso_basic_sensor_t *obj = get_obj(&obj_ptr);\n    assert(iid < obj->num_instances);\n    anjay_ipso_basic_sensor_instance_t *inst = &obj->instances[iid];\n    assert(inst->initialized);\n\n    switch (rid) {\n    case RID_MIN_MEASURED_VALUE:\n        assert(riid == ANJAY_ID_INVALID);\n        (void) update_values(anjay, obj->def.oid, iid, inst);\n        return _anjay_ret_double_unlocked(ctx, inst->min_value);\n\n    case RID_MAX_MEASURED_VALUE:\n        assert(riid == ANJAY_ID_INVALID);\n        (void) update_values(anjay, obj->def.oid, iid, inst);\n        return _anjay_ret_double_unlocked(ctx, inst->max_value);\n\n    case RID_SENSOR_VALUE:\n        assert(riid == ANJAY_ID_INVALID);\n        (void) update_values(anjay, obj->def.oid, iid, inst);\n        return _anjay_ret_double_unlocked(ctx, inst->curr_value);\n\n    case RID_SENSOR_UNITS:\n        assert(riid == ANJAY_ID_INVALID);\n        return _anjay_ret_string_unlocked(ctx, inst->impl.unit);\n\n    case RID_MIN_RANGE_VALUE:\n        assert(riid == ANJAY_ID_INVALID);\n        if (isnan(inst->impl.min_range_value)) {\n            return ANJAY_ERR_METHOD_NOT_ALLOWED;\n        } else {\n            return _anjay_ret_double_unlocked(ctx, inst->impl.min_range_value);\n        }\n\n    case RID_MAX_RANGE_VALUE:\n        assert(riid == ANJAY_ID_INVALID);\n        if (isnan(inst->impl.max_range_value)) {\n            return ANJAY_ERR_METHOD_NOT_ALLOWED;\n        } else {\n            return _anjay_ret_double_unlocked(ctx, inst->impl.max_range_value);\n        }\n\n    default:\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\nstatic int\nbasic_sensor_resource_execute(anjay_unlocked_t *anjay,\n                              const anjay_dm_installed_object_t obj_ptr,\n                              anjay_iid_t iid,\n                              anjay_rid_t rid,\n                              anjay_unlocked_execute_ctx_t *arg_ctx) {\n    (void) arg_ctx;\n    (void) anjay;\n\n    anjay_ipso_basic_sensor_t *obj = get_obj(&obj_ptr);\n    assert(iid < obj->num_instances);\n    anjay_ipso_basic_sensor_instance_t *inst = &obj->instances[iid];\n    assert(inst->initialized);\n\n    switch (rid) {\n    case RID_RESET_MIN_AND_MAX_MEASURED_VALUES:\n        (void) update_curr_value(anjay, obj->def.oid, iid, inst);\n\n        if (inst->max_value != inst->curr_value) {\n            inst->max_value = inst->curr_value;\n            (void) _anjay_notify_changed_unlocked(anjay, obj->def.oid, iid,\n                                                  RID_MIN_MEASURED_VALUE);\n        }\n        if (inst->min_value != inst->curr_value) {\n            inst->min_value = inst->curr_value;\n            (void) _anjay_notify_changed_unlocked(anjay, obj->def.oid, iid,\n                                                  RID_MAX_MEASURED_VALUE);\n        }\n\n        return 0;\n\n    default:\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\nstatic anjay_ipso_basic_sensor_t *obj_from_oid(anjay_unlocked_t *anjay,\n                                               anjay_oid_t oid) {\n    const anjay_dm_installed_object_t *installed_obj_ptr =\n            _anjay_dm_find_object_by_oid(_anjay_get_dm(anjay), oid);\n    if (!_anjay_dm_installed_object_is_valid_unlocked(installed_obj_ptr)) {\n        return NULL;\n    }\n\n    // Checks if it is really an instance of anjay_ipso_basic_sensor_t\n    return (*_anjay_dm_installed_object_get_unlocked(installed_obj_ptr))\n                                   ->handlers.list_instances\n                           == basic_sensor_list_instances\n                   ? get_obj(installed_obj_ptr)\n                   : NULL;\n}\n\nint anjay_ipso_basic_sensor_install(anjay_t *anjay_locked,\n                                    anjay_oid_t oid,\n                                    size_t num_instances) {\n    if (!anjay_locked) {\n        _anjay_log(ipso, ERROR, _(\"Anjay pointer is NULL\"));\n        return -1;\n    }\n\n    int err = 0;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n\n    AVS_LIST(anjay_dm_installed_object_t) entry;\n    anjay_ipso_basic_sensor_t *obj =\n            (anjay_ipso_basic_sensor_t *) AVS_LIST_NEW_BUFFER(\n                    sizeof(anjay_ipso_basic_sensor_t)\n                    + num_instances\n                              * sizeof(anjay_ipso_basic_sensor_instance_t));\n    if (!obj) {\n        _anjay_log_oom();\n        err = -1;\n        goto finish;\n    }\n\n    obj->def = (anjay_unlocked_dm_object_def_t) {\n        .oid = oid,\n        .handlers = {\n            .list_instances = basic_sensor_list_instances,\n            .list_resources = basic_sensor_list_resources,\n            .resource_read = basic_sensor_resource_read,\n            .resource_execute = basic_sensor_resource_execute\n        }\n    };\n\n    obj->obj_def = &obj->def;\n    obj->num_instances = num_instances;\n\n    _anjay_dm_installed_object_init_unlocked(&obj->obj_def_ptr, &obj->obj_def);\n    _ANJAY_ASSERT_INSTALLED_OBJECT_IS_FIRST_FIELD(anjay_ipso_basic_sensor_t,\n                                                  obj_def_ptr);\n    entry = &obj->obj_def_ptr;\n    if (_anjay_register_object_unlocked(anjay, &entry)) {\n        avs_free(obj);\n        err = -1;\n    }\n\nfinish:;\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return err;\n}\n\nint anjay_ipso_basic_sensor_instance_add(\n        anjay_t *anjay_locked,\n        anjay_oid_t oid,\n        anjay_iid_t iid,\n        const anjay_ipso_basic_sensor_impl_t impl) {\n    if (!anjay_locked) {\n        _anjay_log(ipso, ERROR, _(\"Anjay pointer is NULL\"));\n        return -1;\n    }\n\n    int err = 0;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n\n    anjay_ipso_basic_sensor_instance_t *inst;\n    anjay_ipso_basic_sensor_t *obj = obj_from_oid(anjay, oid);\n    if (!obj) {\n        _anjay_log(ipso, ERROR, _(\"Object\") \" %d\" _(\" not installed\"), oid);\n        err = -1;\n        goto finish;\n    }\n\n    if (iid >= obj->num_instances) {\n        _anjay_log(ipso, ERROR, _(\"IID too large\"));\n        err = -1;\n        goto finish;\n    }\n\n    if (!impl.get_value) {\n        _anjay_log(ipso, ERROR, _(\"Callback is NULL\"));\n        goto finish;\n    }\n\n    double value;\n    ANJAY_MUTEX_UNLOCK_FOR_CALLBACK(anjay_locked_2, anjay);\n    if (impl.get_value(iid, impl.user_context, &value)) {\n        _anjay_log(ipso, WARNING, _(\"Read of\") \" /%d/%d\" _(\" failed\"), oid,\n                   iid);\n        value = NAN;\n    }\n    ANJAY_MUTEX_LOCK_AFTER_CALLBACK(anjay_locked_2);\n\n    inst = &obj->instances[iid];\n    inst->initialized = true;\n    inst->impl = impl;\n    inst->curr_value = value;\n    inst->min_value = value;\n    inst->max_value = value;\n\n    _anjay_notify_instances_changed_unlocked(anjay, oid);\n\n    (void) _anjay_notify_changed_unlocked(anjay, oid, iid, RID_SENSOR_VALUE);\n    (void) _anjay_notify_changed_unlocked(anjay, oid, iid,\n                                          RID_MIN_MEASURED_VALUE);\n    (void) _anjay_notify_changed_unlocked(anjay, oid, iid,\n                                          RID_MAX_MEASURED_VALUE);\n\nfinish:;\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return err;\n}\n\nint anjay_ipso_basic_sensor_instance_remove(anjay_t *anjay_locked,\n                                            anjay_oid_t oid,\n                                            anjay_iid_t iid) {\n    if (!anjay_locked) {\n        _anjay_log(ipso, ERROR, _(\"Anjay pointer is NULL\"));\n        return -1;\n    }\n\n    int err = 0;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n\n    anjay_ipso_basic_sensor_t *obj = obj_from_oid(anjay, oid);\n    if (!obj) {\n        _anjay_log(ipso, WARNING, _(\"Object\") \" %d\" _(\" not installed\"), oid);\n        err = -1;\n        goto finish;\n    }\n\n    if (iid >= obj->num_instances || !obj->instances[iid].initialized) {\n        _anjay_log(ipso, ERROR, _(\"Object\") \" %d\" _(\" has no instance\") \" %d\",\n                   oid, iid);\n        err = -1;\n    } else {\n        obj->instances[iid].initialized = false;\n    }\n\n    _anjay_notify_instances_changed_unlocked(anjay, oid);\n\nfinish:;\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return err;\n}\n\nint anjay_ipso_basic_sensor_update(anjay_t *anjay_locked,\n                                   anjay_oid_t oid,\n                                   anjay_iid_t iid) {\n    if (!anjay_locked) {\n        _anjay_log(ipso, ERROR, _(\"Anjay pointer is NULL\"));\n        return -1;\n    }\n\n    int err = 0;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n\n    anjay_ipso_basic_sensor_instance_t *inst;\n    anjay_ipso_basic_sensor_t *obj = obj_from_oid(anjay, oid);\n    if (!obj) {\n        _anjay_log(ipso, ERROR, _(\"Object\") \" %d\" _(\" is not installed\"), oid);\n        err = -1;\n        goto finish;\n    }\n\n    if (iid > obj->num_instances\n            || !(inst = &obj->instances[iid])->initialized) {\n        _anjay_log(ipso, ERROR, _(\"Object\") \" %d\" _(\" has no instance\") \" %d\",\n                   oid, iid);\n        err = -1;\n        goto finish;\n    }\n\n    if (update_values(anjay, oid, iid, inst)) {\n        _anjay_log(ipso, WARNING, _(\"Update of\") \" /%d/%d\" _(\" failed\"), oid,\n                   iid);\n        err = -1;\n    }\n\nfinish:;\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return err;\n}\n\n#endif // ANJAY_WITH_MODULE_IPSO_OBJECTS\n"
  },
  {
    "path": "src/modules/ipso/anjay_ipso_button.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n#include <anjay_init.h>\n\n#ifdef ANJAY_WITH_MODULE_IPSO_OBJECTS\n\n#    include <assert.h>\n#    include <stdbool.h>\n#    include <string.h>\n\n#    include <anjay/anjay.h>\n#    include <anjay/dm.h>\n#    include <anjay/ipso_objects.h>\n\n#    include <anjay_modules/anjay_dm_utils.h>\n#    include <anjay_modules/anjay_utils_core.h>\n\n#    include <avsystem/commons/avs_defs.h>\n#    include <avsystem/commons/avs_log.h>\n#    include <avsystem/commons/avs_memory.h>\n\nVISIBILITY_SOURCE_BEGIN\n\n#    define PUSH_BUTTON_OID 3347\n#    define PUSH_BUTTON_APPLICATION_TYPE_STR_LEN 40\n\n/**\n * Digital Input State: R, Single, Mandatory\n * type: boolean, range: N/A, unit: N/A\n * The current state of a digital input.\n */\n#    define RID_DIGITAL_INPUT_STATE 5500\n\n/**\n * Digital Input Counter: R, Single, Optional\n * type: integer, range: N/A, unit: N/A\n * The cumulative value of active state detected.\n */\n#    define RID_DIGITAL_INPUT_COUNTER 5501\n\n/**\n * Application type: RW, Single, Optional\n * type: string, range: N/A, unit: N/A\n * The application type of the sensor or actuator\n * as a string depending on the use case.\n */\n#    define RID_APPLICATION_TYPE 5750\n\ntypedef struct {\n    bool initialized;\n\n    bool pressed;\n    uint16_t counter;\n    char application_type[PUSH_BUTTON_APPLICATION_TYPE_STR_LEN];\n    char application_type_backup[PUSH_BUTTON_APPLICATION_TYPE_STR_LEN];\n} anjay_ipso_button_instance_t;\n\ntypedef struct {\n    anjay_dm_installed_object_t obj_def_ptr;\n    const anjay_unlocked_dm_object_def_t *obj_def;\n\n    size_t num_instances;\n    anjay_ipso_button_instance_t instances[];\n} anjay_ipso_button_t;\n\nstatic anjay_ipso_button_t *\nget_obj(const anjay_dm_installed_object_t *obj_ptr) {\n    assert(obj_ptr);\n    return AVS_CONTAINER_OF(_anjay_dm_installed_object_get_unlocked(obj_ptr),\n                            anjay_ipso_button_t, obj_def);\n}\n\nstatic int ipso_button_list_instances(anjay_unlocked_t *anjay,\n                                      const anjay_dm_installed_object_t obj_ptr,\n                                      anjay_unlocked_dm_list_ctx_t *ctx) {\n    (void) anjay;\n\n    anjay_ipso_button_t *obj = get_obj(&obj_ptr);\n\n    for (anjay_iid_t iid = 0; iid < obj->num_instances; iid++) {\n        if (obj->instances[iid].initialized) {\n            _anjay_dm_emit_unlocked(ctx, iid);\n        }\n    }\n\n    return 0;\n}\n\nstatic int\nipso_button_list_resources(anjay_unlocked_t *anjay,\n                           const anjay_dm_installed_object_t obj_ptr,\n                           anjay_iid_t iid,\n                           anjay_unlocked_dm_resource_list_ctx_t *ctx) {\n    (void) anjay;\n    (void) obj_ptr;\n    (void) iid;\n\n    _anjay_dm_emit_res_unlocked(ctx, RID_DIGITAL_INPUT_STATE, ANJAY_DM_RES_R,\n                                ANJAY_DM_RES_PRESENT);\n    _anjay_dm_emit_res_unlocked(ctx, RID_DIGITAL_INPUT_COUNTER, ANJAY_DM_RES_R,\n                                ANJAY_DM_RES_PRESENT);\n    _anjay_dm_emit_res_unlocked(ctx, RID_APPLICATION_TYPE, ANJAY_DM_RES_RW,\n                                ANJAY_DM_RES_PRESENT);\n\n    return 0;\n}\n\nstatic int ipso_button_resource_read(anjay_unlocked_t *anjay,\n                                     const anjay_dm_installed_object_t obj_ptr,\n                                     anjay_iid_t iid,\n                                     anjay_rid_t rid,\n                                     anjay_riid_t riid,\n                                     anjay_unlocked_output_ctx_t *ctx) {\n    (void) anjay;\n    (void) riid;\n\n    anjay_ipso_button_t *obj = get_obj(&obj_ptr);\n    assert(iid < obj->num_instances);\n    anjay_ipso_button_instance_t *inst = &obj->instances[iid];\n    assert(inst->initialized);\n\n    switch (rid) {\n    case RID_DIGITAL_INPUT_STATE:\n        assert(riid == ANJAY_ID_INVALID);\n        return _anjay_ret_bool_unlocked(ctx, inst->pressed);\n\n    case RID_DIGITAL_INPUT_COUNTER:\n        assert(riid == ANJAY_ID_INVALID);\n        return _anjay_ret_i64_unlocked(ctx, inst->counter);\n\n    case RID_APPLICATION_TYPE:\n        assert(riid == ANJAY_ID_INVALID);\n        return _anjay_ret_string_unlocked(ctx, inst->application_type);\n\n    default:\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\nstatic int ipso_button_resource_write(anjay_unlocked_t *anjay,\n                                      const anjay_dm_installed_object_t obj_ptr,\n                                      anjay_iid_t iid,\n                                      anjay_rid_t rid,\n                                      anjay_riid_t riid,\n                                      anjay_unlocked_input_ctx_t *ctx) {\n    (void) anjay;\n    (void) riid;\n\n    anjay_ipso_button_t *obj = get_obj(&obj_ptr);\n    assert(iid < obj->num_instances);\n    anjay_ipso_button_instance_t *inst = &obj->instances[iid];\n    assert(inst->initialized);\n\n    switch (rid) {\n\n    case RID_APPLICATION_TYPE:\n        assert(riid == ANJAY_ID_INVALID);\n        return _anjay_get_string_unlocked(ctx, inst->application_type,\n                                          PUSH_BUTTON_APPLICATION_TYPE_STR_LEN);\n\n    default:\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\nstatic int ipso_button_transaction_begin(anjay_unlocked_t *anjay,\n                                         anjay_dm_installed_object_t obj_ptr) {\n    (void) anjay;\n\n    anjay_ipso_button_t *obj = get_obj(&obj_ptr);\n\n    for (anjay_iid_t iid = 0; iid < obj->num_instances; iid++) {\n        if (obj->instances[iid].initialized) {\n            strcpy(obj->instances[iid].application_type_backup,\n                   obj->instances[iid].application_type);\n        }\n    }\n\n    return 0;\n}\n\nstatic int\nipso_button_transaction_rollback(anjay_unlocked_t *anjay,\n                                 anjay_dm_installed_object_t obj_ptr) {\n    (void) anjay;\n\n    anjay_ipso_button_t *obj = get_obj(&obj_ptr);\n\n    for (anjay_iid_t iid = 0; iid < obj->num_instances; iid++) {\n        if (obj->instances[iid].initialized) {\n            strcpy(obj->instances[iid].application_type,\n                   obj->instances[iid].application_type_backup);\n        }\n    }\n\n    return 0;\n}\n\nstatic int ipso_button_transaction_NOOP(anjay_unlocked_t *anjay,\n                                        anjay_dm_installed_object_t obj_ptr) {\n    (void) anjay;\n    (void) obj_ptr;\n    return 0;\n}\n\nstatic const anjay_unlocked_dm_object_def_t OBJECT_DEF = {\n    .oid = PUSH_BUTTON_OID,\n    .handlers = {\n        .list_instances = ipso_button_list_instances,\n        .list_resources = ipso_button_list_resources,\n        .resource_read = ipso_button_resource_read,\n        .resource_write = ipso_button_resource_write,\n\n        .transaction_begin = ipso_button_transaction_begin,\n        .transaction_validate = ipso_button_transaction_NOOP,\n        .transaction_commit = ipso_button_transaction_NOOP,\n        .transaction_rollback = ipso_button_transaction_rollback\n    }\n};\n\nstatic anjay_ipso_button_t *obj_from_anjay(anjay_unlocked_t *anjay) {\n    const anjay_dm_installed_object_t *installed_obj_ptr =\n            _anjay_dm_find_object_by_oid(_anjay_get_dm(anjay), PUSH_BUTTON_OID);\n    if (!_anjay_dm_installed_object_is_valid_unlocked(installed_obj_ptr)) {\n        return NULL;\n    }\n\n    // Checks if it is really an instance of anjay_ipso_button_t\n    return *_anjay_dm_installed_object_get_unlocked(installed_obj_ptr)\n                           == &OBJECT_DEF\n                   ? get_obj(installed_obj_ptr)\n                   : NULL;\n}\n\nint anjay_ipso_button_install(anjay_t *anjay_locked, size_t num_instances) {\n    if (!anjay_locked) {\n        _anjay_log(ipso, ERROR, _(\"Anjay pointer is NULL\"));\n        return -1;\n    }\n\n    int err = 0;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n\n    AVS_LIST(anjay_dm_installed_object_t) entry;\n    anjay_ipso_button_t *obj = (anjay_ipso_button_t *) AVS_LIST_NEW_BUFFER(\n            sizeof(anjay_ipso_button_t)\n            + num_instances * sizeof(anjay_ipso_button_instance_t));\n    if (!obj) {\n        _anjay_log_oom();\n        err = -1;\n        goto finish;\n    }\n\n    obj->obj_def = &OBJECT_DEF;\n    obj->num_instances = num_instances;\n\n    _anjay_dm_installed_object_init_unlocked(&obj->obj_def_ptr, &obj->obj_def);\n    _ANJAY_ASSERT_INSTALLED_OBJECT_IS_FIRST_FIELD(anjay_ipso_button_t,\n                                                  obj_def_ptr);\n    entry = &obj->obj_def_ptr;\n    if (_anjay_register_object_unlocked(anjay, &entry)) {\n        avs_free(obj);\n        err = -1;\n    }\n\nfinish:;\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return err;\n}\n\nint anjay_ipso_button_instance_add(anjay_t *anjay_locked,\n                                   anjay_iid_t iid,\n                                   const char *application_type) {\n    if (!anjay_locked) {\n        _anjay_log(ipso, ERROR, _(\"Anjay pointer is NULL\"));\n        return -1;\n    }\n\n    int err = 0;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n\n    anjay_ipso_button_instance_t *inst;\n    anjay_ipso_button_t *obj = obj_from_anjay(anjay);\n    if (!obj) {\n        _anjay_log(ipso, ERROR, _(\"Push Button Object not installed\"));\n        err = -1;\n        goto finish;\n    }\n\n    if (iid >= obj->num_instances) {\n        _anjay_log(ipso, ERROR, _(\"IID too large\"));\n        err = -1;\n        goto finish;\n    }\n\n    if (strlen(application_type) >= PUSH_BUTTON_APPLICATION_TYPE_STR_LEN) {\n        _anjay_log(ipso, ERROR, _(\"Application Type is too long\"));\n        err = -1;\n        goto finish;\n    }\n\n    inst = &obj->instances[iid];\n    inst->initialized = true;\n    inst->counter = 0;\n    inst->pressed = false;\n\n    (void) strcpy(inst->application_type, application_type);\n\n    _anjay_notify_instances_changed_unlocked(anjay, PUSH_BUTTON_OID);\n\n    (void) _anjay_notify_changed_unlocked(anjay, PUSH_BUTTON_OID, iid,\n                                          RID_DIGITAL_INPUT_COUNTER);\n    (void) _anjay_notify_changed_unlocked(anjay, PUSH_BUTTON_OID, iid,\n                                          RID_DIGITAL_INPUT_STATE);\n    (void) _anjay_notify_changed_unlocked(anjay, PUSH_BUTTON_OID, iid,\n                                          RID_APPLICATION_TYPE);\n\nfinish:;\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return err;\n}\n\nint anjay_ipso_button_instance_remove(anjay_t *anjay_locked, anjay_iid_t iid) {\n    if (!anjay_locked) {\n        _anjay_log(ipso, ERROR, _(\"Anjay pointer is NULL\"));\n        return -1;\n    }\n\n    int err = 0;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n\n    anjay_ipso_button_t *obj = obj_from_anjay(anjay);\n    if (!obj) {\n        _anjay_log(ipso, ERROR, _(\"Push Button Object not installed\"));\n        err = -1;\n        goto finish;\n    }\n\n    if (iid >= obj->num_instances || !obj->instances[iid].initialized) {\n        _anjay_log(ipso, ERROR, _(\"Push Button Object has no instance\") \" %d\",\n                   iid);\n        err = -1;\n    } else {\n        obj->instances[iid].initialized = false;\n    }\n\n    _anjay_notify_instances_changed_unlocked(anjay, PUSH_BUTTON_OID);\n\nfinish:;\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return err;\n}\n\nint anjay_ipso_button_update(anjay_t *anjay_locked,\n                             anjay_iid_t iid,\n                             bool pressed) {\n    if (!anjay_locked) {\n        _anjay_log(ipso, ERROR, _(\"Anjay pointer is NULL\"));\n        return -1;\n    }\n\n    int err = 0;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n\n    anjay_ipso_button_t *obj = obj_from_anjay(anjay);\n    if (!obj) {\n        _anjay_log(ipso, ERROR, _(\"Push Button Object not installed\"));\n        err = -1;\n        goto finish;\n    }\n\n    anjay_ipso_button_instance_t *inst;\n    if (iid > obj->num_instances\n            || !(inst = &obj->instances[iid])->initialized) {\n        _anjay_log(ipso, ERROR, _(\"Push Button Object has no instance\") \" %d\",\n                   iid);\n        err = -1;\n        goto finish;\n    }\n\n    if (inst->pressed != pressed) {\n        inst->pressed = pressed;\n        (void) _anjay_notify_changed_unlocked(anjay, PUSH_BUTTON_OID, iid,\n                                              RID_DIGITAL_INPUT_STATE);\n\n        if (inst->pressed) {\n            inst->counter++;\n            (void) _anjay_notify_changed_unlocked(anjay, PUSH_BUTTON_OID, iid,\n                                                  RID_DIGITAL_INPUT_COUNTER);\n        }\n    }\n\nfinish:;\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return err;\n}\n\n#endif // ANJAY_WITH_MODULE_IPSO_OBJECTS\n"
  },
  {
    "path": "src/modules/ipso_v2/anjay_ipso_v2_3d_sensor.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n#include <anjay_init.h>\n\n#ifdef ANJAY_WITH_MODULE_IPSO_OBJECTS_V2\n\n#    include <assert.h>\n#    include <math.h>\n#    include <stdbool.h>\n\n#    include <anjay/anjay.h>\n#    include <anjay/dm.h>\n#    include <anjay/ipso_objects_v2.h>\n\n#    include <anjay_modules/anjay_dm_utils.h>\n#    include <anjay_modules/anjay_utils_core.h>\n\n#    include <avsystem/commons/avs_defs.h>\n#    include <avsystem/commons/avs_log.h>\n#    include <avsystem/commons/avs_memory.h>\n\nVISIBILITY_SOURCE_BEGIN\n\n/**\n * Min X Value: R, Single, Optional\n * type: float, range: N/A, unit: N/A\n * The minimum measured value along the X axis.\n */\n#    define RID_MIN_X_VALUE 5508\n\n/**\n * Max X Value: R, Single, Optional\n * type: float, range: N/A, unit: N/A\n * The maximum measured value along the X axis.\n */\n#    define RID_MAX_X_VALUE 5509\n\n/**\n * Min Y Value: R, Single, Optional\n * type: float, range: N/A, unit: N/A\n * The minimum measured value along the Y axis.\n */\n#    define RID_MIN_Y_VALUE 5510\n\n/**\n * Max Y Value: R, Single, Optional\n * type: float, range: N/A, unit: N/A\n * The maximum measured value along the Y axis.\n */\n#    define RID_MAX_Y_VALUE 5511\n\n/**\n * Min Z Value: R, Single, Optional\n * type: float, range: N/A, unit: N/A\n * The minimum measured value along the Z axis.\n */\n#    define RID_MIN_Z_VALUE 5512\n\n/**\n * Max Z Value: R, Single, Optional\n * type: float, range: N/A, unit: N/A\n * The maximum measured value along the Z axis.\n */\n#    define RID_MAX_Z_VALUE 5513\n\n/**\n * Min Range Value: R, Single, Optional\n * type: float, range: N/A, unit: N/A\n * The minimum value that can be measured the sensor.\n */\n#    define RID_MIN_RANGE_VALUE 5603\n\n/**\n * Max Range Value: R, Single, Optional\n * type: float, range: N/A, unit: N/A\n * The maximum value that can be measured by the sensor.\n */\n#    define RID_MAX_RANGE_VALUE 5604\n\n/**\n * Reset Min and Max Measured Values: E, Single, Optional\n * type: N/A, range: N/A, unit: N/A\n * Reset the Min and Max Measured Values to Current Value.\n */\n#    define RID_RESET_MIN_AND_MAX_MEASURED_VALUES 5605\n\n/**\n * Sensor Units: R, Single, Optional\n * type: string, range: N/A, unit: N/A\n * Measurement Units Definition.\n */\n#    define RID_SENSOR_UNITS 5701\n\n/**\n * X Value: R, Single, Mandatory\n * type: float, range: N/A, unit: N/A\n * The measured value along the X axis.\n */\n#    define RID_X_VALUE 5702\n\n/**\n * Y Value: R, Single, Optional\n * type: float, range: N/A, unit: N/A\n * The measured value along the Y axis.\n */\n#    define RID_Y_VALUE 5703\n\n/**\n * Z Value: R, Single, Optional\n * type: float, range: N/A, unit: N/A\n * The measured value along the Z axis.\n */\n#    define RID_Z_VALUE 5704\n\ntypedef anjay_ipso_v2_3d_sensor_meta_t sensor_meta_t;\ntypedef anjay_ipso_v2_3d_sensor_value_t sensor_value_t;\n\ntypedef struct {\n    bool initialized;\n    sensor_meta_t meta;\n    sensor_value_t curr_value;\n    sensor_value_t min_value;\n    sensor_value_t max_value;\n} instance_t;\n\ntypedef struct {\n    anjay_dm_installed_object_t installed_obj;\n    anjay_unlocked_dm_object_def_t def;\n    const anjay_unlocked_dm_object_def_t *def_ptr;\n\n    size_t instance_count;\n    instance_t instances[];\n} object_t;\n\nstatic void log_invalid_parameters_error(void) {\n    _anjay_log(ipso, ERROR, _(\"Invalid parameters\"));\n}\n\n#    define log_invalid_parameters(...)           \\\n        do {                                      \\\n            log_invalid_parameters_error();       \\\n            _anjay_log(ipso, DEBUG, __VA_ARGS__); \\\n        } while (0)\n\nstatic object_t *get_obj(const anjay_dm_installed_object_t *installed_obj_ptr) {\n    assert(installed_obj_ptr);\n    return AVS_CONTAINER_OF(_anjay_dm_installed_object_get_unlocked(\n                                    installed_obj_ptr),\n                            object_t, def_ptr);\n}\n\nstatic int list_instances(anjay_unlocked_t *anjay,\n                          const anjay_dm_installed_object_t installed_obj,\n                          anjay_unlocked_dm_list_ctx_t *ctx) {\n    (void) anjay;\n\n    object_t *obj = get_obj(&installed_obj);\n\n    for (anjay_iid_t iid = 0; iid < obj->instance_count; iid++) {\n        if (obj->instances[iid].initialized) {\n            _anjay_dm_emit_unlocked(ctx, iid);\n        }\n    }\n\n    return 0;\n}\n\nstatic int list_resources(anjay_unlocked_t *anjay,\n                          const anjay_dm_installed_object_t installed_obj,\n                          anjay_iid_t iid,\n                          anjay_unlocked_dm_resource_list_ctx_t *ctx) {\n    (void) anjay;\n\n    object_t *obj = get_obj(&installed_obj);\n    assert(iid < obj->instance_count);\n    instance_t *inst = &obj->instances[iid];\n    assert(inst->initialized);\n\n    if (inst->meta.min_max_measured_value_present) {\n        _anjay_dm_emit_res_unlocked(ctx, RID_MIN_X_VALUE, ANJAY_DM_RES_R,\n                                    ANJAY_DM_RES_PRESENT);\n        _anjay_dm_emit_res_unlocked(ctx, RID_MAX_X_VALUE, ANJAY_DM_RES_R,\n                                    ANJAY_DM_RES_PRESENT);\n        if (inst->meta.y_axis_present) {\n            _anjay_dm_emit_res_unlocked(ctx, RID_MIN_Y_VALUE, ANJAY_DM_RES_R,\n                                        ANJAY_DM_RES_PRESENT);\n            _anjay_dm_emit_res_unlocked(ctx, RID_MAX_Y_VALUE, ANJAY_DM_RES_R,\n                                        ANJAY_DM_RES_PRESENT);\n        }\n        if (inst->meta.z_axis_present) {\n            _anjay_dm_emit_res_unlocked(ctx, RID_MIN_Z_VALUE, ANJAY_DM_RES_R,\n                                        ANJAY_DM_RES_PRESENT);\n            _anjay_dm_emit_res_unlocked(ctx, RID_MAX_Z_VALUE, ANJAY_DM_RES_R,\n                                        ANJAY_DM_RES_PRESENT);\n        }\n    }\n    if (!isnan(inst->meta.min_range_value)) {\n        _anjay_dm_emit_res_unlocked(ctx, RID_MIN_RANGE_VALUE, ANJAY_DM_RES_R,\n                                    ANJAY_DM_RES_PRESENT);\n    }\n    if (!isnan(inst->meta.max_range_value)) {\n        _anjay_dm_emit_res_unlocked(ctx, RID_MAX_RANGE_VALUE, ANJAY_DM_RES_R,\n                                    ANJAY_DM_RES_PRESENT);\n    }\n    if (inst->meta.min_max_measured_value_present) {\n        _anjay_dm_emit_res_unlocked(ctx, RID_RESET_MIN_AND_MAX_MEASURED_VALUES,\n                                    ANJAY_DM_RES_E, ANJAY_DM_RES_PRESENT);\n    }\n    if (inst->meta.unit) {\n        _anjay_dm_emit_res_unlocked(ctx, RID_SENSOR_UNITS, ANJAY_DM_RES_R,\n                                    ANJAY_DM_RES_PRESENT);\n    }\n    _anjay_dm_emit_res_unlocked(ctx, RID_X_VALUE, ANJAY_DM_RES_R,\n                                ANJAY_DM_RES_PRESENT);\n    if (inst->meta.y_axis_present) {\n        _anjay_dm_emit_res_unlocked(ctx, RID_Y_VALUE, ANJAY_DM_RES_R,\n                                    ANJAY_DM_RES_PRESENT);\n    }\n    if (inst->meta.z_axis_present) {\n        _anjay_dm_emit_res_unlocked(ctx, RID_Z_VALUE, ANJAY_DM_RES_R,\n                                    ANJAY_DM_RES_PRESENT);\n    }\n\n    return 0;\n}\n\nstatic int resource_read(anjay_unlocked_t *anjay,\n                         const anjay_dm_installed_object_t installed_obj,\n                         anjay_iid_t iid,\n                         anjay_rid_t rid,\n                         anjay_riid_t riid,\n                         anjay_unlocked_output_ctx_t *ctx) {\n    (void) anjay;\n    (void) riid;\n\n    object_t *obj = get_obj(&installed_obj);\n    assert(iid < obj->instance_count);\n    instance_t *inst = &obj->instances[iid];\n    assert(inst->initialized);\n\n    switch (rid) {\n    case RID_MIN_X_VALUE:\n        assert(riid == ANJAY_ID_INVALID);\n        assert(inst->meta.min_max_measured_value_present);\n        return _anjay_ret_double_unlocked(ctx, inst->min_value.x);\n\n    case RID_MAX_X_VALUE:\n        assert(riid == ANJAY_ID_INVALID);\n        assert(inst->meta.min_max_measured_value_present);\n        return _anjay_ret_double_unlocked(ctx, inst->max_value.x);\n\n    case RID_MIN_Y_VALUE:\n        assert(riid == ANJAY_ID_INVALID);\n        assert(inst->meta.y_axis_present);\n        assert(inst->meta.min_max_measured_value_present);\n        return _anjay_ret_double_unlocked(ctx, inst->min_value.y);\n\n    case RID_MAX_Y_VALUE:\n        assert(riid == ANJAY_ID_INVALID);\n        assert(inst->meta.y_axis_present);\n        assert(inst->meta.min_max_measured_value_present);\n        return _anjay_ret_double_unlocked(ctx, inst->max_value.y);\n\n    case RID_MIN_Z_VALUE:\n        assert(riid == ANJAY_ID_INVALID);\n        assert(inst->meta.z_axis_present);\n        assert(inst->meta.min_max_measured_value_present);\n        return _anjay_ret_double_unlocked(ctx, inst->min_value.z);\n\n    case RID_MAX_Z_VALUE:\n        assert(riid == ANJAY_ID_INVALID);\n        assert(inst->meta.z_axis_present);\n        assert(inst->meta.min_max_measured_value_present);\n        return _anjay_ret_double_unlocked(ctx, inst->max_value.z);\n\n    case RID_SENSOR_UNITS:\n        assert(riid == ANJAY_ID_INVALID);\n        assert(inst->meta.unit);\n        return _anjay_ret_string_unlocked(ctx, inst->meta.unit);\n\n    case RID_X_VALUE:\n        assert(riid == ANJAY_ID_INVALID);\n        return _anjay_ret_double_unlocked(ctx, inst->curr_value.x);\n\n    case RID_Y_VALUE:\n        assert(riid == ANJAY_ID_INVALID);\n        assert(inst->meta.y_axis_present);\n        return _anjay_ret_double_unlocked(ctx, inst->curr_value.y);\n\n    case RID_Z_VALUE:\n        assert(riid == ANJAY_ID_INVALID);\n        assert(inst->meta.z_axis_present);\n        return _anjay_ret_double_unlocked(ctx, inst->curr_value.z);\n\n    case RID_MIN_RANGE_VALUE:\n        assert(riid == ANJAY_ID_INVALID);\n        assert(!isnan(inst->meta.min_range_value));\n        return _anjay_ret_double_unlocked(ctx, inst->meta.min_range_value);\n\n    case RID_MAX_RANGE_VALUE:\n        assert(riid == ANJAY_ID_INVALID);\n        assert(!isnan(inst->meta.max_range_value));\n        return _anjay_ret_double_unlocked(ctx, inst->meta.max_range_value);\n\n    default:\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\nstatic int resource_execute(anjay_unlocked_t *anjay,\n                            const anjay_dm_installed_object_t installed_obj,\n                            anjay_iid_t iid,\n                            anjay_rid_t rid,\n                            anjay_unlocked_execute_ctx_t *arg_ctx) {\n    (void) arg_ctx;\n    (void) anjay;\n\n    object_t *obj = get_obj(&installed_obj);\n    assert(iid < obj->instance_count);\n    instance_t *inst = &obj->instances[iid];\n    assert(inst->initialized);\n\n    switch (rid) {\n    case RID_RESET_MIN_AND_MAX_MEASURED_VALUES:\n        assert(inst->meta.min_max_measured_value_present);\n\n        if (inst->min_value.x != inst->curr_value.x) {\n            inst->min_value.x = inst->curr_value.x;\n            _anjay_notify_changed_unlocked(anjay, obj->def.oid, iid,\n                                           RID_MIN_X_VALUE);\n        }\n        if (inst->max_value.x != inst->curr_value.x) {\n            inst->max_value.x = inst->curr_value.x;\n            _anjay_notify_changed_unlocked(anjay, obj->def.oid, iid,\n                                           RID_MAX_X_VALUE);\n        }\n\n        if (inst->meta.y_axis_present) {\n            if (inst->min_value.y != inst->curr_value.y) {\n                inst->min_value.y = inst->curr_value.y;\n                _anjay_notify_changed_unlocked(anjay, obj->def.oid, iid,\n                                               RID_MIN_Y_VALUE);\n            }\n            if (inst->max_value.y != inst->curr_value.y) {\n                inst->max_value.y = inst->curr_value.y;\n                _anjay_notify_changed_unlocked(anjay, obj->def.oid, iid,\n                                               RID_MAX_Y_VALUE);\n            }\n        }\n\n        if (inst->meta.z_axis_present) {\n            if (inst->min_value.z != inst->curr_value.z) {\n                inst->min_value.z = inst->curr_value.z;\n                _anjay_notify_changed_unlocked(anjay, obj->def.oid, iid,\n                                               RID_MIN_Z_VALUE);\n            }\n            if (inst->max_value.y != inst->curr_value.y) {\n                inst->max_value.y = inst->curr_value.y;\n                _anjay_notify_changed_unlocked(anjay, obj->def.oid, iid,\n                                               RID_MAX_Y_VALUE);\n            }\n        }\n\n        return 0;\n\n    default:\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\nstatic object_t *obj_from_oid(anjay_unlocked_t *anjay, anjay_oid_t oid) {\n    const anjay_dm_installed_object_t *installed_obj_ptr =\n            _anjay_dm_find_object_by_oid(_anjay_get_dm(anjay), oid);\n    if (!_anjay_dm_installed_object_is_valid_unlocked(installed_obj_ptr)) {\n        return NULL;\n    }\n\n    // Checks if it is really an instance of object_t\n    return (*_anjay_dm_installed_object_get_unlocked(installed_obj_ptr))\n                                   ->handlers.list_instances\n                           == list_instances\n                   ? get_obj(installed_obj_ptr)\n                   : NULL;\n}\n\nstatic inline bool value_valid(const sensor_meta_t *meta,\n                               const sensor_value_t *value) {\n    return isfinite(value->x) && (!meta->y_axis_present || isfinite(value->y))\n           && (!meta->z_axis_present || isfinite(value->z));\n}\n\nstatic int sensor_install_unlocked(anjay_unlocked_t *anjay,\n                                   anjay_oid_t oid,\n                                   const char *version,\n                                   size_t instance_count) {\n    if (instance_count == 0 || instance_count >= ANJAY_ID_INVALID) {\n        log_invalid_parameters(_(\"Instance count of out range\"));\n        return -1;\n    }\n    AVS_LIST(anjay_dm_installed_object_t) entry;\n    object_t *obj = (object_t *) AVS_LIST_NEW_BUFFER(\n            sizeof(object_t) + instance_count * sizeof(instance_t));\n    if (!obj) {\n        _anjay_log_oom();\n        return -1;\n    }\n\n    obj->def = (anjay_unlocked_dm_object_def_t) {\n        .oid = oid,\n        .version = version,\n        .handlers = {\n            .list_instances = list_instances,\n            .list_resources = list_resources,\n            .resource_read = resource_read,\n            .resource_execute = resource_execute\n        }\n    };\n\n    obj->def_ptr = &obj->def;\n    obj->instance_count = instance_count;\n\n    _anjay_dm_installed_object_init_unlocked(&obj->installed_obj,\n                                             &obj->def_ptr);\n    _ANJAY_ASSERT_INSTALLED_OBJECT_IS_FIRST_FIELD(object_t, installed_obj);\n    entry = &obj->installed_obj;\n    if (_anjay_register_object_unlocked(anjay, &entry)) {\n        avs_free(obj);\n        return -1;\n    }\n\n    return 0;\n}\n\nint anjay_ipso_v2_3d_sensor_install(anjay_t *anjay_locked,\n                                    anjay_oid_t oid,\n                                    const char *version,\n                                    size_t instance_count) {\n    assert(anjay_locked);\n\n    int res = -1;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    res = sensor_install_unlocked(anjay, oid, version, instance_count);\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n\n    return res;\n}\n\nstatic int sensor_instance_add_unlocked(anjay_unlocked_t *anjay,\n                                        anjay_oid_t oid,\n                                        anjay_iid_t iid,\n                                        const sensor_value_t *initial_value,\n                                        const sensor_meta_t *meta) {\n    object_t *obj = obj_from_oid(anjay, oid);\n    if (!obj) {\n        log_invalid_parameters(_(\"Object\") \" %d\" _(\" not installed\"), oid);\n        return -1;\n    }\n\n    if (iid >= obj->instance_count) {\n        log_invalid_parameters(_(\"IID too large\"));\n        return -1;\n    }\n\n    if ((!isnan(meta->min_range_value) && !isfinite(meta->min_range_value))\n            || (!isnan(meta->max_range_value)\n                && !isfinite(meta->max_range_value))) {\n        log_invalid_parameters(_(\"Min/max range values not finite\"));\n        return -1;\n    }\n\n    if (!isnan(meta->min_range_value) && !isnan(meta->max_range_value)\n            && meta->min_range_value > meta->max_range_value) {\n        log_invalid_parameters(_(\"Min range larger than max range value\"));\n        return -1;\n    }\n\n    if (!value_valid(meta, initial_value)) {\n        log_invalid_parameters(_(\"Initial value invalid\"));\n        return -1;\n    }\n\n    instance_t *inst = &obj->instances[iid];\n    if (inst->initialized) {\n        log_invalid_parameters(_(\"Instance already initialized\"));\n        return -1;\n    }\n\n    inst->initialized = true;\n    inst->meta = *meta;\n    inst->curr_value = *initial_value;\n    inst->min_value = *initial_value;\n    inst->max_value = *initial_value;\n\n    _anjay_notify_instances_changed_unlocked(anjay, oid);\n    return 0;\n}\n\nint anjay_ipso_v2_3d_sensor_instance_add(anjay_t *anjay_locked,\n                                         anjay_oid_t oid,\n                                         anjay_iid_t iid,\n                                         const sensor_value_t *initial_value,\n                                         const sensor_meta_t *meta) {\n    assert(anjay_locked);\n    assert(initial_value);\n    assert(meta);\n\n    int res = -1;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    res = sensor_instance_add_unlocked(anjay, oid, iid, initial_value, meta);\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n\n    return res;\n}\n\nstatic int sensor_instance_remove_unlocked(anjay_unlocked_t *anjay,\n                                           anjay_oid_t oid,\n                                           anjay_iid_t iid) {\n    object_t *obj = obj_from_oid(anjay, oid);\n    if (!obj) {\n        log_invalid_parameters(_(\"Object\") \" %d\" _(\" not installed\"), oid);\n        return -1;\n    }\n\n    if (iid >= obj->instance_count || !obj->instances[iid].initialized) {\n        log_invalid_parameters(_(\"Object\") \" %d\" _(\" has no instance\") \" %d\",\n                               oid, iid);\n        return -1;\n    }\n\n    obj->instances[iid].initialized = false;\n    _anjay_notify_instances_changed_unlocked(anjay, oid);\n\n    return 0;\n}\n\nint anjay_ipso_v2_3d_sensor_instance_remove(anjay_t *anjay_locked,\n                                            anjay_oid_t oid,\n                                            anjay_iid_t iid) {\n    assert(anjay_locked);\n\n    int res = -1;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    res = sensor_instance_remove_unlocked(anjay, oid, iid);\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n\n    return res;\n}\n\nstatic void update_curr_value(anjay_unlocked_t *anjay,\n                              anjay_oid_t oid,\n                              anjay_iid_t iid,\n                              instance_t *inst,\n                              const sensor_value_t *value) {\n    if (value->x != inst->curr_value.x) {\n        inst->curr_value.x = value->x;\n        (void) _anjay_notify_changed_unlocked(anjay, oid, iid, RID_X_VALUE);\n    }\n    if (inst->meta.y_axis_present && value->y != inst->curr_value.y) {\n        inst->curr_value.y = value->y;\n        (void) _anjay_notify_changed_unlocked(anjay, oid, iid, RID_Y_VALUE);\n    }\n    if (inst->meta.z_axis_present && value->z != inst->curr_value.z) {\n        inst->curr_value.z = value->z;\n        (void) _anjay_notify_changed_unlocked(anjay, oid, iid, RID_Z_VALUE);\n    }\n}\n\nstatic void update_x_min_max(anjay_unlocked_t *anjay,\n                             anjay_oid_t oid,\n                             anjay_iid_t iid,\n                             instance_t *inst,\n                             const sensor_value_t *value) {\n    if (value->x < inst->min_value.x) {\n        inst->min_value.x = value->x;\n        (void) _anjay_notify_changed_unlocked(anjay, oid, iid, RID_MIN_X_VALUE);\n    }\n    if (value->x > inst->max_value.x) {\n        inst->max_value.x = value->x;\n        (void) _anjay_notify_changed_unlocked(anjay, oid, iid, RID_MAX_X_VALUE);\n    }\n}\n\nstatic void update_y_min_max(anjay_unlocked_t *anjay,\n                             anjay_oid_t oid,\n                             anjay_iid_t iid,\n                             instance_t *inst,\n                             const sensor_value_t *value) {\n    if (value->y < inst->min_value.y) {\n        inst->min_value.y = value->y;\n        (void) _anjay_notify_changed_unlocked(anjay, oid, iid, RID_MIN_Y_VALUE);\n    }\n    if (value->y > inst->max_value.y) {\n        inst->max_value.y = value->y;\n        (void) _anjay_notify_changed_unlocked(anjay, oid, iid, RID_MAX_Y_VALUE);\n    }\n}\n\nstatic void update_z_min_max(anjay_unlocked_t *anjay,\n                             anjay_oid_t oid,\n                             anjay_iid_t iid,\n                             instance_t *inst,\n                             const sensor_value_t *value) {\n    if (value->z < inst->min_value.z) {\n        inst->min_value.z = value->z;\n        (void) _anjay_notify_changed_unlocked(anjay, oid, iid, RID_MIN_Z_VALUE);\n    }\n    if (value->z > inst->max_value.z) {\n        inst->max_value.z = value->z;\n        (void) _anjay_notify_changed_unlocked(anjay, oid, iid, RID_MAX_Z_VALUE);\n    }\n}\n\nstatic int sensor_value_update_unlocked(anjay_unlocked_t *anjay,\n                                        anjay_oid_t oid,\n                                        anjay_iid_t iid,\n                                        const sensor_value_t *value) {\n    object_t *obj = obj_from_oid(anjay, oid);\n    if (!obj) {\n        log_invalid_parameters(_(\"Object\") \" %d\" _(\" not installed\"), oid);\n        return -1;\n    }\n\n    instance_t *inst;\n    if (iid > obj->instance_count\n            || !(inst = &obj->instances[iid])->initialized) {\n        log_invalid_parameters(_(\"Object\") \" %d\" _(\" has no instance\") \" %d\",\n                               oid, iid);\n        return -1;\n    }\n\n    if (!value_valid(&inst->meta, value)) {\n        log_invalid_parameters(_(\"Update of\") \" /%d/%d\" _(\" failed\"), oid, iid);\n        return -1;\n    }\n\n    update_curr_value(anjay, oid, iid, inst, value);\n    if (inst->meta.min_max_measured_value_present) {\n        update_x_min_max(anjay, oid, iid, inst, value);\n        if (inst->meta.y_axis_present) {\n            update_y_min_max(anjay, oid, iid, inst, value);\n        }\n        if (inst->meta.z_axis_present) {\n            update_z_min_max(anjay, oid, iid, inst, value);\n        }\n    }\n\n    return 0;\n}\n\nint anjay_ipso_v2_3d_sensor_value_update(anjay_t *anjay_locked,\n                                         anjay_oid_t oid,\n                                         anjay_iid_t iid,\n                                         const sensor_value_t *value) {\n    assert(anjay_locked);\n    assert(value);\n\n    int res = -1;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    res = sensor_value_update_unlocked(anjay, oid, iid, value);\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n\n    return res;\n}\n\n#endif // ANJAY_WITH_MODULE_IPSO_OBJECTS_V2\n"
  },
  {
    "path": "src/modules/ipso_v2/anjay_ipso_v2_basic_sensor.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n#include <anjay_init.h>\n\n#ifdef ANJAY_WITH_MODULE_IPSO_OBJECTS_V2\n\n#    include <assert.h>\n#    include <math.h>\n#    include <stdbool.h>\n\n#    include <anjay/anjay.h>\n#    include <anjay/dm.h>\n#    include <anjay/ipso_objects_v2.h>\n\n#    include <anjay_modules/anjay_dm_utils.h>\n#    include <anjay_modules/anjay_utils_core.h>\n\n#    include <avsystem/commons/avs_defs.h>\n#    include <avsystem/commons/avs_log.h>\n#    include <avsystem/commons/avs_memory.h>\n\nVISIBILITY_SOURCE_BEGIN\n\n/**\n * Min Measured Value: R, Single, Optional\n * type: float, range: N/A, unit: N/A\n * The minimum value measured by the sensor since power ON or reset.\n */\n#    define RID_MIN_MEASURED_VALUE 5601\n\n/**\n * Max Measured Value: R, Single, Optional\n * type: float, range: N/A, unit: N/A\n * The maximum value measured by the sensor since power ON or reset.\n */\n#    define RID_MAX_MEASURED_VALUE 5602\n\n/**\n * Min Range Value: R, Single, Optional\n * type: float, range: N/A, unit: N/A\n * The minimum value that can be measured the sensor.\n */\n#    define RID_MIN_RANGE_VALUE 5603\n\n/**\n * Max Range Value: R, Single, Optional\n * type: float, range: N/A, unit: N/A\n * The maximum value that can be measured by the sensor.\n */\n#    define RID_MAX_RANGE_VALUE 5604\n\n/**\n * Reset Min and Max Measured Values: E, Single, Optional\n * type: N/A, range: N/A, unit: N/A\n * Reset the Min and Max Measured Values to Current Value.\n */\n#    define RID_RESET_MIN_AND_MAX_MEASURED_VALUES 5605\n\n/**\n * Sensor Value: R, Single, Mandatory\n * type: float, range: N/A, unit: N/A\n * Last or Current Measured Value from the Sensor.\n */\n#    define RID_SENSOR_VALUE 5700\n\n/**\n * Sensor Units: R, Single, Optional\n * type: string, range: N/A, unit: N/A\n * Measurement Units Definition.\n */\n#    define RID_SENSOR_UNITS 5701\n\ntypedef anjay_ipso_v2_basic_sensor_meta_t sensor_meta_t;\n\ntypedef struct {\n    bool initialized;\n    sensor_meta_t meta;\n    double curr_value;\n    double min_value;\n    double max_value;\n} instance_t;\n\ntypedef struct {\n    anjay_dm_installed_object_t installed_obj;\n    anjay_unlocked_dm_object_def_t def;\n    const anjay_unlocked_dm_object_def_t *def_ptr;\n\n    size_t instance_count;\n    instance_t instances[];\n} object_t;\n\nstatic void log_invalid_parameters_error(void) {\n    _anjay_log(ipso, ERROR, _(\"Invalid parameters\"));\n}\n\n#    define log_invalid_parameters(...)           \\\n        do {                                      \\\n            log_invalid_parameters_error();       \\\n            _anjay_log(ipso, DEBUG, __VA_ARGS__); \\\n        } while (0)\n\nstatic object_t *get_obj(const anjay_dm_installed_object_t *installed_obj_ptr) {\n    assert(installed_obj_ptr);\n    return AVS_CONTAINER_OF(_anjay_dm_installed_object_get_unlocked(\n                                    installed_obj_ptr),\n                            object_t, def_ptr);\n}\n\nstatic int list_instances(anjay_unlocked_t *anjay,\n                          const anjay_dm_installed_object_t installed_obj,\n                          anjay_unlocked_dm_list_ctx_t *ctx) {\n    (void) anjay;\n\n    object_t *obj = get_obj(&installed_obj);\n\n    for (anjay_iid_t iid = 0; iid < obj->instance_count; iid++) {\n        if (obj->instances[iid].initialized) {\n            _anjay_dm_emit_unlocked(ctx, iid);\n        }\n    }\n\n    return 0;\n}\n\nstatic int list_resources(anjay_unlocked_t *anjay,\n                          const anjay_dm_installed_object_t installed_obj,\n                          anjay_iid_t iid,\n                          anjay_unlocked_dm_resource_list_ctx_t *ctx) {\n    (void) anjay;\n\n    object_t *obj = get_obj(&installed_obj);\n    assert(iid < obj->instance_count);\n    instance_t *inst = &obj->instances[iid];\n    assert(inst->initialized);\n\n    if (inst->meta.min_max_measured_value_present) {\n        _anjay_dm_emit_res_unlocked(ctx, RID_MIN_MEASURED_VALUE, ANJAY_DM_RES_R,\n                                    ANJAY_DM_RES_PRESENT);\n    }\n    if (inst->meta.min_max_measured_value_present) {\n        _anjay_dm_emit_res_unlocked(ctx, RID_MAX_MEASURED_VALUE, ANJAY_DM_RES_R,\n                                    ANJAY_DM_RES_PRESENT);\n    }\n    if (!isnan(inst->meta.min_range_value)) {\n        _anjay_dm_emit_res_unlocked(ctx, RID_MIN_RANGE_VALUE, ANJAY_DM_RES_R,\n                                    ANJAY_DM_RES_PRESENT);\n    }\n    if (!isnan(inst->meta.max_range_value)) {\n        _anjay_dm_emit_res_unlocked(ctx, RID_MAX_RANGE_VALUE, ANJAY_DM_RES_R,\n                                    ANJAY_DM_RES_PRESENT);\n    }\n    if (inst->meta.min_max_measured_value_present) {\n        _anjay_dm_emit_res_unlocked(ctx, RID_RESET_MIN_AND_MAX_MEASURED_VALUES,\n                                    ANJAY_DM_RES_E, ANJAY_DM_RES_PRESENT);\n    }\n    _anjay_dm_emit_res_unlocked(ctx, RID_SENSOR_VALUE, ANJAY_DM_RES_R,\n                                ANJAY_DM_RES_PRESENT);\n    if (inst->meta.unit) {\n        _anjay_dm_emit_res_unlocked(ctx, RID_SENSOR_UNITS, ANJAY_DM_RES_R,\n                                    ANJAY_DM_RES_PRESENT);\n    }\n\n    return 0;\n}\n\nstatic int resource_read(anjay_unlocked_t *anjay,\n                         const anjay_dm_installed_object_t installed_obj,\n                         anjay_iid_t iid,\n                         anjay_rid_t rid,\n                         anjay_riid_t riid,\n                         anjay_unlocked_output_ctx_t *ctx) {\n    (void) anjay;\n    (void) riid;\n\n    object_t *obj = get_obj(&installed_obj);\n    assert(iid < obj->instance_count);\n    instance_t *inst = &obj->instances[iid];\n    assert(inst->initialized);\n\n    switch (rid) {\n    case RID_MIN_MEASURED_VALUE:\n        assert(riid == ANJAY_ID_INVALID);\n        assert(inst->meta.min_max_measured_value_present);\n        return _anjay_ret_double_unlocked(ctx, inst->min_value);\n\n    case RID_MAX_MEASURED_VALUE:\n        assert(riid == ANJAY_ID_INVALID);\n        assert(inst->meta.min_max_measured_value_present);\n        return _anjay_ret_double_unlocked(ctx, inst->max_value);\n\n    case RID_SENSOR_VALUE:\n        assert(riid == ANJAY_ID_INVALID);\n        return _anjay_ret_double_unlocked(ctx, inst->curr_value);\n\n    case RID_SENSOR_UNITS:\n        assert(riid == ANJAY_ID_INVALID);\n        assert(inst->meta.unit);\n        return _anjay_ret_string_unlocked(ctx, inst->meta.unit);\n\n    case RID_MIN_RANGE_VALUE:\n        assert(riid == ANJAY_ID_INVALID);\n        assert(!isnan(inst->meta.min_range_value));\n        return _anjay_ret_double_unlocked(ctx, inst->meta.min_range_value);\n\n    case RID_MAX_RANGE_VALUE:\n        assert(riid == ANJAY_ID_INVALID);\n        assert(!isnan(inst->meta.max_range_value));\n        return _anjay_ret_double_unlocked(ctx, inst->meta.max_range_value);\n\n    default:\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\nstatic int resource_execute(anjay_unlocked_t *anjay,\n                            const anjay_dm_installed_object_t installed_obj,\n                            anjay_iid_t iid,\n                            anjay_rid_t rid,\n                            anjay_unlocked_execute_ctx_t *arg_ctx) {\n    (void) arg_ctx;\n    (void) anjay;\n\n    object_t *obj = get_obj(&installed_obj);\n    assert(iid < obj->instance_count);\n    instance_t *inst = &obj->instances[iid];\n    assert(inst->initialized);\n\n    switch (rid) {\n    case RID_RESET_MIN_AND_MAX_MEASURED_VALUES:\n        assert(inst->meta.min_max_measured_value_present);\n\n        if (inst->min_value != inst->curr_value) {\n            inst->min_value = inst->curr_value;\n            (void) _anjay_notify_changed_unlocked(anjay, obj->def.oid, iid,\n                                                  RID_MIN_MEASURED_VALUE);\n        }\n        if (inst->max_value != inst->curr_value) {\n            inst->max_value = inst->curr_value;\n            (void) _anjay_notify_changed_unlocked(anjay, obj->def.oid, iid,\n                                                  RID_MAX_MEASURED_VALUE);\n        }\n\n        return 0;\n\n    default:\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\nstatic object_t *obj_from_oid(anjay_unlocked_t *anjay, anjay_oid_t oid) {\n    const anjay_dm_installed_object_t *installed_obj_ptr =\n            _anjay_dm_find_object_by_oid(_anjay_get_dm(anjay), oid);\n    if (!_anjay_dm_installed_object_is_valid_unlocked(installed_obj_ptr)) {\n        return NULL;\n    }\n\n    // Checks if it is really an instance of object_t\n    return (*_anjay_dm_installed_object_get_unlocked(installed_obj_ptr))\n                                   ->handlers.list_instances\n                           == list_instances\n                   ? get_obj(installed_obj_ptr)\n                   : NULL;\n}\n\nstatic int sensor_install_unlocked(anjay_unlocked_t *anjay,\n                                   anjay_oid_t oid,\n                                   const char *version,\n                                   size_t instance_count) {\n    if (instance_count == 0 || instance_count >= ANJAY_ID_INVALID) {\n        log_invalid_parameters(_(\"Instance count of out range\"));\n        return -1;\n    }\n    AVS_LIST(anjay_dm_installed_object_t) entry;\n    object_t *obj = (object_t *) AVS_LIST_NEW_BUFFER(\n            sizeof(object_t) + instance_count * sizeof(instance_t));\n    if (!obj) {\n        _anjay_log_oom();\n        return -1;\n    }\n\n    obj->def = (anjay_unlocked_dm_object_def_t) {\n        .oid = oid,\n        .version = version,\n        .handlers = {\n            .list_instances = list_instances,\n            .list_resources = list_resources,\n            .resource_read = resource_read,\n            .resource_execute = resource_execute\n        }\n    };\n\n    obj->def_ptr = &obj->def;\n    obj->instance_count = instance_count;\n\n    _anjay_dm_installed_object_init_unlocked(&obj->installed_obj,\n                                             &obj->def_ptr);\n    entry = &obj->installed_obj;\n    if (_anjay_register_object_unlocked(anjay, &entry)) {\n        avs_free(obj);\n        return -1;\n    }\n\n    return 0;\n}\n\nint anjay_ipso_v2_basic_sensor_install(anjay_t *anjay_locked,\n                                       anjay_oid_t oid,\n                                       const char *version,\n                                       size_t instance_count) {\n    assert(anjay_locked);\n\n    int res = -1;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    res = sensor_install_unlocked(anjay, oid, version, instance_count);\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n\n    return res;\n}\n\nstatic int sensor_instance_add_unlocked(anjay_unlocked_t *anjay,\n                                        anjay_oid_t oid,\n                                        anjay_iid_t iid,\n                                        double initial_value,\n                                        const sensor_meta_t *meta) {\n    object_t *obj = obj_from_oid(anjay, oid);\n    if (!obj) {\n        log_invalid_parameters(_(\"Object\") \" %d\" _(\" not installed\"), oid);\n        return -1;\n    }\n\n    if (iid >= obj->instance_count) {\n        log_invalid_parameters(_(\"IID too large\"));\n        return -1;\n    }\n\n    if ((!isnan(meta->min_range_value) && !isfinite(meta->min_range_value))\n            || (!isnan(meta->max_range_value)\n                && !isfinite(meta->max_range_value))) {\n        log_invalid_parameters(_(\"Min/max range values not finite\"));\n        return -1;\n    }\n\n    if (!isnan(meta->min_range_value) && !isnan(meta->max_range_value)\n            && meta->min_range_value > meta->max_range_value) {\n        log_invalid_parameters(_(\"Min range larger than max range value\"));\n        return -1;\n    }\n\n    if (!isfinite(initial_value)) {\n        log_invalid_parameters(_(\"Initial value invalid\"));\n        return -1;\n    }\n\n    instance_t *inst = &obj->instances[iid];\n    if (inst->initialized) {\n        log_invalid_parameters(_(\"Instance already initialized\"));\n        return -1;\n    }\n\n    inst->initialized = true;\n    inst->meta = *meta;\n    inst->curr_value = initial_value;\n    inst->min_value = initial_value;\n    inst->max_value = initial_value;\n\n    _anjay_notify_instances_changed_unlocked(anjay, oid);\n    return 0;\n}\n\nint anjay_ipso_v2_basic_sensor_instance_add(\n        anjay_t *anjay_locked,\n        anjay_oid_t oid,\n        anjay_iid_t iid,\n        double initial_value,\n        const anjay_ipso_v2_basic_sensor_meta_t *meta) {\n    assert(anjay_locked);\n    assert(meta);\n\n    int res = -1;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    res = sensor_instance_add_unlocked(anjay, oid, iid, initial_value, meta);\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n\n    return res;\n}\n\nstatic int sensor_instance_remove_unlocked(anjay_unlocked_t *anjay,\n                                           anjay_oid_t oid,\n                                           anjay_iid_t iid) {\n    object_t *obj = obj_from_oid(anjay, oid);\n    if (!obj) {\n        log_invalid_parameters(_(\"Object\") \" %d\" _(\" not installed\"), oid);\n        return -1;\n    }\n\n    if (iid >= obj->instance_count || !obj->instances[iid].initialized) {\n        log_invalid_parameters(_(\"Object\") \" %d\" _(\" has no instance\") \" %d\",\n                               oid, iid);\n        return -1;\n    }\n\n    obj->instances[iid].initialized = false;\n    _anjay_notify_instances_changed_unlocked(anjay, oid);\n\n    return 0;\n}\n\nint anjay_ipso_v2_basic_sensor_instance_remove(anjay_t *anjay_locked,\n                                               anjay_oid_t oid,\n                                               anjay_iid_t iid) {\n    assert(anjay_locked);\n\n    int res = -1;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    res = sensor_instance_remove_unlocked(anjay, oid, iid);\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n\n    return res;\n}\n\nstatic int sensor_value_update_unlocked(anjay_unlocked_t *anjay,\n                                        anjay_oid_t oid,\n                                        anjay_iid_t iid,\n                                        double value) {\n    object_t *obj = obj_from_oid(anjay, oid);\n    if (!obj) {\n        log_invalid_parameters(_(\"Object\") \" %d\" _(\" not installed\"), oid);\n        return -1;\n    }\n\n    instance_t *inst;\n    if (iid > obj->instance_count\n            || !(inst = &obj->instances[iid])->initialized) {\n        log_invalid_parameters(_(\"Object\") \" %d\" _(\" has no instance\") \" %d\",\n                               oid, iid);\n        return -1;\n    }\n\n    if (!isfinite(value)) {\n        log_invalid_parameters(_(\"Update of\") \" /%d/%d\" _(\" failed\"), oid, iid);\n        return -1;\n    }\n\n    if (value != inst->curr_value) {\n        inst->curr_value = value;\n        (void) _anjay_notify_changed_unlocked(anjay, oid, iid,\n                                              RID_SENSOR_VALUE);\n    }\n    if (inst->meta.min_max_measured_value_present) {\n        if (value < inst->min_value) {\n            inst->min_value = value;\n            (void) _anjay_notify_changed_unlocked(anjay, oid, iid,\n                                                  RID_MIN_MEASURED_VALUE);\n        }\n        if (value > inst->max_value) {\n            inst->max_value = value;\n            (void) _anjay_notify_changed_unlocked(anjay, oid, iid,\n                                                  RID_MAX_MEASURED_VALUE);\n        }\n    }\n\n    return 0;\n}\n\nint anjay_ipso_v2_basic_sensor_value_update(anjay_t *anjay_locked,\n                                            anjay_oid_t oid,\n                                            anjay_iid_t iid,\n                                            double value) {\n    assert(anjay_locked);\n\n    int res = -1;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    res = sensor_value_update_unlocked(anjay, oid, iid, value);\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n\n    return res;\n}\n\n#endif // ANJAY_WITH_MODULE_IPSO_OBJECTS_V2\n"
  },
  {
    "path": "src/modules/lwm2m_gateway/anjay_lwm2m_gateway.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n#include <anjay_init.h>\n\n#ifdef ANJAY_WITH_LWM2M_GATEWAY\n#    include <assert.h>\n#    include <inttypes.h>\n#    include <stdbool.h>\n#    include <string.h>\n\n#    include <anjay_modules/anjay_attr_storage_utils.h>\n#    include <anjay_modules/anjay_dm_utils.h>\n#    include <anjay_modules/anjay_lwm2m_gateway.h>\n#    include <anjay_modules/anjay_notify.h>\n#    include <anjay_modules/anjay_utils_core.h>\n#    include <anjay_modules/dm/anjay_modules.h>\n\n#    include <anjay/anjay.h>\n#    include <anjay/lwm2m_gateway.h>\n#    include <avsystem/commons/avs_defs.h>\n#    include <avsystem/commons/avs_list.h>\n#    include <avsystem/commons/avs_memory.h>\n\n#    include \"../../core/anjay_dm_core.h\"\n#    include \"../../core/attr_storage/anjay_attr_storage.h\"\n#    include \"../../core/io/anjay_corelnk.h\"\n\nVISIBILITY_SOURCE_BEGIN\n\n#    if !defined(ANJAY_WITH_LWM2M11)\n#        error \"LwM2M Gateway requires LwM2M version 1.1 or above!\"\n#    endif\n\n#    define gw_log(level, ...) _anjay_log(lwm2m_gateway, level, __VA_ARGS__)\n\n/**\n * Device ID: R, Single, Mandatory\n * type: string, range: N/A, unit: N/A\n * This resource identifies the IoT Device connected to the LwM2M\n * Gateway.\n */\n#    define RID_DEVICE_ID 0\n\n/**\n * Prefix: R, Single, Mandatory\n * type: string, range: N/A, unit: N/A\n * This resource defines what prefix MUST be used for access to LwM2M\n * Objects of this IoT Device.\n */\n#    define RID_PREFIX 1\n\n/**\n * IoT Device Objects: R, Single, Mandatory\n * type: corelnk, range: N/A, unit: N/A\n * This resource contains the Objects and Object Instances exposed by the\n * LwM2M Gateway on behalf of the IoT Device. It uses the same CoreLnk\n * format as Registration Interface.\n */\n#    define RID_IOT_DEVICE_OBJECTS 3\n\ntypedef struct lwm2m_gateway_instance_struct {\n    anjay_iid_t iid;\n\n    const char *device_id;\n    char prefix[ANJAY_GATEWAY_MAX_PREFIX_LEN];\n    anjay_dm_t dm;\n#    ifdef ANJAY_WITH_ATTR_STORAGE\n    anjay_attr_storage_t as;\n#    endif // ANJAY_WITH_ATTR_STORAGE\n} lwm2m_gateway_instance_t;\n\ntypedef struct lwm2m_gateway_object_struct {\n    anjay_dm_installed_object_t obj_def_ptr;\n    const anjay_unlocked_dm_object_def_t *obj_def;\n\n    AVS_LIST(lwm2m_gateway_instance_t) instances;\n} lwm2m_gateway_obj_t;\n\nstatic inline void\ndelete_instance(AVS_LIST(lwm2m_gateway_instance_t) *instances,\n                lwm2m_gateway_instance_t *inst) {\n    AVS_LIST(lwm2m_gateway_instance_t) *inst_ptr =\n            AVS_LIST_FIND_PTR(instances, inst);\n    AVS_LIST_DELETE(inst_ptr);\n}\n\nstatic anjay_iid_t get_new_iid(AVS_LIST(lwm2m_gateway_instance_t) instances) {\n    anjay_iid_t iid = 0;\n    AVS_LIST(lwm2m_gateway_instance_t) it;\n    AVS_LIST_FOREACH(it, instances) {\n        if (it->iid == iid) {\n            ++iid;\n        } else if (it->iid > iid) {\n            break;\n        }\n    }\n    return iid;\n}\n\nstatic inline lwm2m_gateway_instance_t *find_instance(lwm2m_gateway_obj_t *gw,\n                                                      anjay_iid_t iid) {\n    AVS_LIST(lwm2m_gateway_instance_t) it;\n    AVS_LIST_FOREACH(it, gw->instances) {\n        if (it->iid == iid) {\n            return it;\n        } else if (it->iid > iid) {\n            break;\n        }\n    }\n    return NULL;\n}\n\nstatic inline lwm2m_gateway_obj_t *\nget_obj(const anjay_dm_installed_object_t *obj_ptr) {\n    return AVS_CONTAINER_OF(_anjay_dm_installed_object_get_unlocked(obj_ptr),\n                            lwm2m_gateway_obj_t, obj_def);\n}\n\nstatic int gateway_list_instances(anjay_unlocked_t *anjay,\n                                  const anjay_dm_installed_object_t obj_ptr,\n                                  anjay_unlocked_dm_list_ctx_t *ctx) {\n    (void) anjay;\n\n    AVS_LIST(lwm2m_gateway_instance_t) it;\n    AVS_LIST_FOREACH(it, get_obj(&obj_ptr)->instances) {\n        _anjay_dm_emit_unlocked(ctx, it->iid);\n    }\n\n    return 0;\n}\n\nstatic int init_instance(lwm2m_gateway_instance_t *inst, anjay_iid_t iid) {\n    if (avs_simple_snprintf(inst->prefix, sizeof(inst->prefix), \"dev%\" PRIu16,\n                            iid)\n            < (int) strlen(\"dev0\")) {\n        return -1;\n    }\n    inst->iid = iid;\n    return 0;\n}\n\nstatic lwm2m_gateway_instance_t *\ngateway_instance_create(lwm2m_gateway_obj_t *gw, anjay_iid_t iid) {\n    assert(iid != ANJAY_ID_INVALID);\n\n    AVS_LIST(lwm2m_gateway_instance_t) created =\n            AVS_LIST_NEW_ELEMENT(lwm2m_gateway_instance_t);\n    if (!created) {\n        _anjay_log_oom();\n        return NULL;\n    }\n    if (init_instance(created, iid)) {\n        // failed, clean up newly added instance\n        delete_instance(&gw->instances, created);\n        return NULL;\n    }\n\n#    ifdef ANJAY_WITH_ATTR_STORAGE\n    if (_anjay_attr_storage_init(&created->as, &created->dm)) {\n        delete_instance(&gw->instances, created);\n        return NULL;\n    }\n#    endif // ANJAY_WITH_ATTR_STORAGE\n\n    AVS_LIST(lwm2m_gateway_instance_t) *ptr;\n    AVS_LIST_FOREACH_PTR(ptr, &gw->instances) {\n        if ((*ptr)->iid > created->iid) {\n            break;\n        }\n    }\n\n    AVS_LIST_INSERT(ptr, created);\n    return created;\n}\n\nstatic int gateway_list_resources(anjay_unlocked_t *anjay,\n                                  const anjay_dm_installed_object_t obj_ptr,\n                                  anjay_iid_t iid,\n                                  anjay_unlocked_dm_resource_list_ctx_t *ctx) {\n    (void) anjay;\n\n    lwm2m_gateway_obj_t *gw = get_obj(&obj_ptr);\n    lwm2m_gateway_instance_t *inst = find_instance(gw, iid);\n    assert(inst);\n\n    _anjay_dm_emit_res_unlocked(ctx, RID_DEVICE_ID, ANJAY_DM_RES_R,\n                                ANJAY_DM_RES_PRESENT);\n    _anjay_dm_emit_res_unlocked(ctx, RID_PREFIX, ANJAY_DM_RES_R,\n                                ANJAY_DM_RES_PRESENT);\n    _anjay_dm_emit_res_unlocked(ctx, RID_IOT_DEVICE_OBJECTS, ANJAY_DM_RES_R,\n                                ANJAY_DM_RES_PRESENT);\n    return 0;\n}\n\nstatic int gateway_resource_read(anjay_unlocked_t *anjay,\n                                 const anjay_dm_installed_object_t obj_ptr,\n                                 anjay_iid_t iid,\n                                 anjay_rid_t rid,\n                                 anjay_riid_t riid,\n                                 anjay_unlocked_output_ctx_t *ctx) {\n    (void) anjay;\n\n    lwm2m_gateway_obj_t *gw = get_obj(&obj_ptr);\n    lwm2m_gateway_instance_t *inst = find_instance(gw, iid);\n    assert(inst);\n\n    switch (rid) {\n    case RID_DEVICE_ID: {\n        assert(riid == ANJAY_ID_INVALID);\n        return _anjay_ret_string_unlocked(ctx, inst->device_id);\n    }\n    case RID_PREFIX: {\n        assert(riid == ANJAY_ID_INVALID);\n        return _anjay_ret_string_unlocked(ctx, inst->prefix);\n    }\n    case RID_IOT_DEVICE_OBJECTS: {\n        assert(riid == ANJAY_ID_INVALID);\n        int ret = 0;\n        char *dm_buffer = NULL;\n        // There is no chance to determine what is the LwM2M Version that the\n        // Anjay client registered with to the server, that is now performing\n        // this read (anjay_registration_info_t::lwm2m_version). Lets just\n        // assume version 1.1.\n        if (_anjay_corelnk_query_dm(anjay, &inst->dm, ANJAY_LWM2M_VERSION_1_1,\n                                    &dm_buffer)) {\n            return ANJAY_ERR_INTERNAL;\n        }\n        ret = _anjay_ret_string_unlocked(ctx, dm_buffer);\n        avs_free(dm_buffer);\n        return ret;\n    }\n    default:\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\nstatic const anjay_unlocked_dm_object_def_t LWM2M_GATEWAY = {\n    .oid = ANJAY_DM_OID_LWM2M_GATEWAY,\n    .version = \"2.0\",\n    .handlers = {\n        .list_instances = gateway_list_instances,\n        .list_resources = gateway_list_resources,\n        .resource_read = gateway_resource_read,\n    }\n};\n\nstatic void gateway_delete(void *lwm2m_gateway_) {\n    lwm2m_gateway_obj_t *gw = (lwm2m_gateway_obj_t *) lwm2m_gateway_;\n\n    AVS_LIST_CLEAR(&gw->instances) {\n#    ifdef ANJAY_WITH_ATTR_STORAGE\n        _anjay_attr_storage_cleanup(&gw->instances->as);\n#    endif // ANJAY_WITH_ATTR_STORAGE\n        _anjay_dm_cleanup(&gw->instances->dm);\n    }\n}\n\nint anjay_lwm2m_gateway_install(anjay_t *anjay_locked) {\n    assert(anjay_locked);\n\n    int result = -1;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    AVS_LIST(lwm2m_gateway_obj_t) lwm2m_gateway =\n            AVS_LIST_NEW_ELEMENT(lwm2m_gateway_obj_t);\n    if (lwm2m_gateway) {\n        lwm2m_gateway->obj_def = &LWM2M_GATEWAY;\n        _anjay_dm_installed_object_init_unlocked(&lwm2m_gateway->obj_def_ptr,\n                                                 &lwm2m_gateway->obj_def);\n\n        if (!_anjay_dm_module_install(anjay, gateway_delete, lwm2m_gateway)) {\n            _ANJAY_ASSERT_INSTALLED_OBJECT_IS_FIRST_FIELD(lwm2m_gateway_obj_t,\n                                                          obj_def_ptr);\n\n            AVS_LIST(anjay_dm_installed_object_t) entry =\n                    &lwm2m_gateway->obj_def_ptr;\n            if (_anjay_register_object_unlocked(anjay, &entry)) {\n                result = _anjay_dm_module_uninstall(anjay, gateway_delete);\n                assert(!result);\n                result = -1;\n            } else {\n                result = 0;\n            }\n        }\n        if (result) {\n            AVS_LIST_CLEAR(&lwm2m_gateway);\n        }\n    } else {\n        _anjay_log_oom();\n    }\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return result;\n}\n\nstatic lwm2m_gateway_instance_t *add_instance(lwm2m_gateway_obj_t *gw,\n                                              anjay_iid_t *inout_iid) {\n    if (*inout_iid != ANJAY_ID_INVALID) {\n        // verify if user-defined iid is free\n        if (find_instance(gw, *inout_iid)) {\n            return NULL;\n        }\n    } else {\n        // assign new, free iid\n        *inout_iid = get_new_iid(gw->instances);\n        if (*inout_iid == ANJAY_ID_INVALID) {\n            return NULL;\n        }\n    }\n\n    lwm2m_gateway_instance_t *inst = gateway_instance_create(gw, *inout_iid);\n    if (!inst) {\n        return NULL;\n    }\n\n    return inst;\n}\n\nint anjay_lwm2m_gateway_register_device(anjay_t *anjay_locked,\n                                        const char *device_id,\n                                        anjay_iid_t *inout_iid) {\n    assert(anjay_locked && device_id && inout_iid);\n    int retval = -1;\n\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    lwm2m_gateway_obj_t *gw =\n            (lwm2m_gateway_obj_t *) _anjay_dm_module_get_arg(anjay,\n                                                             gateway_delete);\n    if (!gw) {\n        gw_log(ERROR, _(\"LwM2M Gateway object not installed\"));\n    } else {\n        lwm2m_gateway_instance_t *inst = NULL;\n        if ((inst = add_instance(gw, inout_iid))) {\n            inst->device_id = device_id;\n            gw_log(INFO,\n                   _(\"Registered new device: \") \"%s\"\n                                                \" with ID: %\" PRIu16,\n                   device_id, *inout_iid);\n            _anjay_notify_instances_changed_unlocked(\n                    anjay, ANJAY_DM_OID_LWM2M_GATEWAY);\n            retval = 0;\n        } else {\n            gw_log(ERROR,\n                   _(\"Failed to register new device: \") \"%s\"\n                                                        \" with ID: %\" PRIu16,\n                   device_id, *inout_iid);\n        }\n    }\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n\n    return retval;\n}\n\nint anjay_lwm2m_gateway_deregister_device(anjay_t *anjay_locked,\n                                          anjay_iid_t iid) {\n    assert(anjay_locked);\n    int retval = -1;\n\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    lwm2m_gateway_obj_t *gw =\n            (lwm2m_gateway_obj_t *) _anjay_dm_module_get_arg(anjay,\n                                                             gateway_delete);\n    if (!gw) {\n        gw_log(ERROR, _(\"LwM2M Gateway object not installed\"));\n    } else {\n        lwm2m_gateway_instance_t *inst;\n        if ((inst = find_instance(gw, iid))) {\n            _anjay_dm_cleanup(&inst->dm);\n#    ifdef ANJAY_WITH_ATTR_STORAGE\n            _anjay_attr_storage_cleanup(&inst->as);\n#    endif // ANJAY_WITH_ATTR_STORAGE\n            delete_instance(&gw->instances, inst);\n\n            _anjay_notify_instances_changed_unlocked(\n                    anjay, ANJAY_DM_OID_LWM2M_GATEWAY);\n            gw_log(INFO, _(\"Device deregistered: \") \"%\" PRIu16, iid);\n\n            retval = 0;\n        } else {\n            gw_log(WARNING,\n                   _(\"LwM2M Gateway instance %\" PRIu16 \" does not exist\"), iid);\n        }\n    }\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n\n    return retval;\n}\n\nvoid _anjay_lwm2m_gateway_iid_to_dm(anjay_unlocked_t *anjay,\n                                    anjay_iid_t iid,\n                                    const anjay_dm_t **dm) {\n    assert(anjay && dm);\n    *dm = NULL;\n\n    lwm2m_gateway_obj_t *gw =\n            (lwm2m_gateway_obj_t *) _anjay_dm_module_get_arg(anjay,\n                                                             gateway_delete);\n    if (!gw) {\n        gw_log(WARNING, _(\"LwM2M Gateway object not installed\"));\n    } else {\n        AVS_LIST(lwm2m_gateway_instance_t) it;\n        AVS_LIST_FOREACH(it, gw->instances) {\n            if (it->iid == iid) {\n                *dm = &it->dm;\n                break;\n            }\n        }\n    }\n}\n\n/**\n * This function is extracted from anjay_lwm2m_gateway_register_object() to\n * allow easier to read early returns without goto.\n * It has a lot in common with anjay_register_object() but is tweaked to allow\n * to operate on a different DM than Anjay's.\n */\nstatic int\nregister_object_unlocked(anjay_unlocked_t *anjay,\n                         anjay_iid_t iid,\n                         const anjay_dm_object_def_t *const *def_ptr) {\n    lwm2m_gateway_obj_t *gw =\n            (lwm2m_gateway_obj_t *) _anjay_dm_module_get_arg(anjay,\n                                                             gateway_delete);\n    if (!gw) {\n        gw_log(ERROR, _(\"LwM2M Gateway object not installed\"));\n        return -1;\n    }\n    lwm2m_gateway_instance_t *inst;\n    if (!(inst = find_instance(gw, iid))) {\n        gw_log(ERROR, _(\"End Device %\" PRIu16 \" is not registered\"), iid);\n        return -1;\n    }\n\n    AVS_LIST(anjay_dm_installed_object_t) new_elem =\n            _anjay_prepare_user_provided_object(def_ptr);\n    if (!new_elem) {\n        return -1;\n    }\n\n    new_elem->prefix = inst->prefix;\n\n    //_anjay_dm_installed_object_init_unlocked(obj_ptr, new_elem);\n    if (_anjay_dm_register_object(&inst->dm, &new_elem)) {\n        gw_log(ERROR, _(\"Object registration failed\"));\n        AVS_LIST_CLEAR(&new_elem);\n        return -1;\n    }\n\n#    ifdef ANJAY_WITH_LWM2M12\n    _anjay_dm_check_implemented_handlers(anjay, new_elem);\n#    endif // ANJAY_WITH_LWM2M12\n\n    // no need to call _anjay_notify_instances_changed_unlocked() or\n    // _anjay_schedule_registration_update_unlocked() as the\n    // End Devices DM's contents are not reported in Register and Update\n    // messages\n    gw_log(DEBUG, _(\"Successfully registered object \") \"/%s/%\" PRIu16,\n           new_elem->prefix, _anjay_dm_installed_object_oid(new_elem));\n    return 0;\n}\n\nint anjay_lwm2m_gateway_register_object(\n        anjay_t *anjay_locked,\n        anjay_iid_t iid,\n        const anjay_dm_object_def_t *const *def_ptr) {\n    assert(anjay_locked);\n    int result = -1;\n\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    result = register_object_unlocked(anjay, iid, def_ptr);\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n\n    return result;\n}\n\n/**\n * This function is extracted from anjay_lwm2m_gateway_unregister_object() to\n * allow easier to read early returns without goto.\n * It has a lot in common with anjay_unregister_object() but is tweaked to allow\n * to operate on a different DM than Anjay's.\n */\nstatic int\nunregister_object_unlocked(anjay_unlocked_t *anjay,\n                           anjay_iid_t iid,\n                           const anjay_dm_object_def_t *const *def_ptr) {\n    lwm2m_gateway_obj_t *gw =\n            (lwm2m_gateway_obj_t *) _anjay_dm_module_get_arg(anjay,\n                                                             gateway_delete);\n    if (!gw) {\n        gw_log(ERROR, _(\"LwM2M Gateway object not installed\"));\n        return -1;\n    }\n    lwm2m_gateway_instance_t *inst;\n    if (!(inst = find_instance(gw, iid))) {\n        gw_log(ERROR, _(\"End Device %\" PRIu16 \" is not registered\"), iid);\n        return -1;\n    }\n\n    AVS_LIST(anjay_dm_installed_object_t) *obj =\n            _anjay_find_and_verify_object_to_unregister(&inst->dm, def_ptr);\n    if (!obj) {\n        gw_log(ERROR, _(\"Object not installed for given End Device\"));\n        return -1;\n    }\n\n    assert(AVS_LIST_FIND_PTR(&inst->dm.objects, *obj));\n    AVS_LIST(anjay_dm_installed_object_t) detached = AVS_LIST_DETACH(obj);\n\n    _anjay_unregister_object_handle_transaction_state(anjay, detached);\n    _anjay_unregister_object_handle_notify_queue(anjay, detached);\n\n    gw_log(INFO, _(\"Successfully unregistered object \") \"/%s/%\" PRIu16,\n           detached->prefix, _anjay_dm_installed_object_oid(detached));\n    AVS_LIST_DELETE(&detached);\n\n    // no need to call _anjay_notify_instances_changed_unlocked() or\n    // _anjay_schedule_registration_update_unlocked() as the\n    // End Devices DM's contents are not reported in Register and Update\n    // messages\n\n    return 0;\n}\n\nint anjay_lwm2m_gateway_unregister_object(\n        anjay_t *anjay_locked,\n        anjay_iid_t iid,\n        const anjay_dm_object_def_t *const *def_ptr) {\n    assert(anjay_locked);\n\n    if (!def_ptr || !*def_ptr) {\n        gw_log(ERROR, _(\"invalid object pointer\"));\n        return -1;\n    }\n\n    int result = -1;\n\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    result = unregister_object_unlocked(anjay, iid, def_ptr);\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n\n    return result;\n}\n\nstatic lwm2m_gateway_instance_t *\nfind_instance_by_prefix(anjay_unlocked_t *anjay, const char *prefix) {\n    lwm2m_gateway_obj_t *gw =\n            (lwm2m_gateway_obj_t *) _anjay_dm_module_get_arg(anjay,\n                                                             gateway_delete);\n    if (!gw) {\n        gw_log(WARNING, _(\"LwM2M Gateway object not installed\"));\n    } else {\n        AVS_LIST(lwm2m_gateway_instance_t) it;\n        AVS_LIST_FOREACH(it, gw->instances) {\n            if (!strcmp(it->prefix, prefix)) {\n                return it;\n            }\n        }\n    }\n    return NULL;\n}\n\nint _anjay_lwm2m_gateway_prefix_to_dm(anjay_unlocked_t *anjay,\n                                      const char *prefix,\n                                      const anjay_dm_t **dm) {\n    assert(anjay && prefix && dm);\n    *dm = NULL;\n\n    lwm2m_gateway_instance_t *inst = find_instance_by_prefix(anjay, prefix);\n    if (inst) {\n        *dm = &inst->dm;\n        return 0;\n    }\n    return -1;\n}\n\n#    ifdef ANJAY_WITH_ATTR_STORAGE\nint _anjay_lwm2m_gateway_prefix_to_as(anjay_unlocked_t *anjay,\n                                      const char *prefix,\n                                      anjay_attr_storage_t **as) {\n    assert(anjay && prefix && as);\n    *as = NULL;\n\n    lwm2m_gateway_instance_t *inst = find_instance_by_prefix(anjay, prefix);\n    if (inst) {\n        *as = &inst->as;\n        return 0;\n    }\n    return -1;\n}\n#    endif // ANJAY_WITH_ATTR_STORAGE\n\nstatic int gateway_notify_changed_unlocked(anjay_unlocked_t *anjay,\n                                           anjay_iid_t end_dev,\n                                           anjay_oid_t oid,\n                                           anjay_iid_t iid,\n                                           anjay_rid_t rid) {\n    lwm2m_gateway_obj_t *gw =\n            (lwm2m_gateway_obj_t *) _anjay_dm_module_get_arg(anjay,\n                                                             gateway_delete);\n    if (!gw) {\n        gw_log(ERROR, _(\"LwM2M Gateway object not installed\"));\n        return -1;\n    }\n    lwm2m_gateway_instance_t *inst;\n    if (!(inst = find_instance(gw, end_dev))) {\n        gw_log(ERROR, _(\"End Device %\" PRIu16 \" is not registered\"), end_dev);\n        return -1;\n    }\n    return _anjay_notify_changed_gw_unlocked(anjay, inst->prefix, oid, iid,\n                                             rid);\n}\n\nint anjay_lwm2m_gateway_notify_changed(anjay_t *anjay_locked,\n                                       anjay_iid_t end_dev,\n                                       anjay_oid_t oid,\n                                       anjay_iid_t iid,\n                                       anjay_rid_t rid) {\n    assert(anjay_locked);\n    int result = -1;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    result = gateway_notify_changed_unlocked(anjay, end_dev, oid, iid, rid);\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return result;\n}\n\nstatic int gateway_notify_instances_changed_unlocked(anjay_unlocked_t *anjay,\n                                                     anjay_iid_t end_dev,\n                                                     anjay_oid_t oid) {\n    lwm2m_gateway_obj_t *gw =\n            (lwm2m_gateway_obj_t *) _anjay_dm_module_get_arg(anjay,\n                                                             gateway_delete);\n    if (!gw) {\n        gw_log(ERROR, _(\"LwM2M Gateway object not installed\"));\n        return -1;\n    }\n    lwm2m_gateway_instance_t *inst;\n    if (!(inst = find_instance(gw, end_dev))) {\n        gw_log(ERROR, _(\"End Device %\" PRIu16 \" is not registered\"), end_dev);\n        return -1;\n    }\n\n    return _anjay_notify_instances_changed_gw_unlocked(anjay, inst->prefix,\n                                                       oid);\n}\n\nint anjay_lwm2m_gateway_notify_instances_changed(anjay_t *anjay_locked,\n                                                 anjay_iid_t end_dev,\n                                                 anjay_oid_t oid) {\n    assert(anjay_locked);\n    int result = -1;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    result = gateway_notify_instances_changed_unlocked(anjay, end_dev, oid);\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return result;\n}\n\n#    ifdef ANJAY_WITH_OBSERVATION_STATUS\nanjay_resource_observation_status_t\nanjay_lwm2m_gateway_resource_observation_status(anjay_t *anjay_locked,\n                                                anjay_iid_t end_dev,\n                                                anjay_oid_t oid,\n                                                anjay_iid_t iid,\n                                                anjay_rid_t rid) {\n    assert(anjay_locked);\n    anjay_resource_observation_status_t retval = {\n        .is_observed = false,\n        .min_period = 0,\n        .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n#        if (ANJAY_MAX_OBSERVATION_SERVERS_REPORTED_NUMBER > 0)\n        .servers_number = 0\n#        endif // (ANJAY_MAX_OBSERVATION_SERVERS_REPORTED_NUMBER > 0)\n    };\n\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    lwm2m_gateway_obj_t *gw =\n            (lwm2m_gateway_obj_t *) _anjay_dm_module_get_arg(anjay,\n                                                             gateway_delete);\n    if (!gw) {\n        gw_log(ERROR, _(\"LwM2M Gateway object not installed\"));\n        goto exit;\n    }\n    lwm2m_gateway_instance_t *inst;\n    if (!(inst = find_instance(gw, end_dev))) {\n        gw_log(ERROR, _(\"End Device %\" PRIu16 \" is not registered\"), end_dev);\n        goto exit;\n    }\n    _anjay_notify_observation_status_impl_unlocked(anjay, &retval, inst->prefix,\n                                                   oid, iid, rid);\nexit:\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return retval;\n}\n#    endif // ANJAY_WITH_OBSERVATION_STATUS\n#    ifdef ANJAY_TEST\n#        include \"tests/modules/lwm2m_gateway/lwm2m_gateway.c\"\n#    endif\n\n#endif // ANJAY_WITH_LWM2M_GATEWAY\n"
  },
  {
    "path": "src/modules/security/anjay_mod_security.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#ifdef ANJAY_WITH_MODULE_SECURITY\n\n#    include <assert.h>\n#    include <stdlib.h>\n#    include <string.h>\n\n#    include <anjay_modules/anjay_io_utils.h>\n\n#    include \"anjay_mod_security.h\"\n#    include \"anjay_security_transaction.h\"\n#    include \"anjay_security_utils.h\"\n\nVISIBILITY_SOURCE_BEGIN\n\nstatic const security_rid_t SECURITY_RESOURCE_ID[] = {\n    SEC_RES_LWM2M_SERVER_URI,  SEC_RES_BOOTSTRAP_SERVER,\n    SEC_RES_SECURITY_MODE,     SEC_RES_PK_OR_IDENTITY,\n    SEC_RES_SERVER_PK,         SEC_RES_SECRET_KEY,\n    SEC_RES_SHORT_SERVER_ID,   SEC_RES_CLIENT_HOLD_OFF_TIME,\n    SEC_RES_BOOTSTRAP_TIMEOUT,\n#    ifdef ANJAY_WITH_LWM2M11\n    SEC_RES_MATCHING_TYPE,     SEC_RES_SNI,\n    SEC_RES_CERTIFICATE_USAGE, SEC_RES_DTLS_TLS_CIPHERSUITE,\n#    endif // ANJAY_WITH_LWM2M11\n};\n\nvoid _anjay_sec_instance_update_resource_presence(sec_instance_t *inst) {\n    // Sets presence of mandatory resources and updates presence of resources\n    // which presence is not persisted and depends on resource value\n    inst->present_resources[SEC_RES_LWM2M_SERVER_URI] = true;\n    inst->present_resources[SEC_RES_BOOTSTRAP_SERVER] = true;\n    inst->present_resources[SEC_RES_SECURITY_MODE] = true;\n    inst->present_resources[SEC_RES_PK_OR_IDENTITY] = true;\n    inst->present_resources[SEC_RES_SERVER_PK] = true;\n    inst->present_resources[SEC_RES_SECRET_KEY] = true;\n    inst->present_resources[SEC_RES_CLIENT_HOLD_OFF_TIME] =\n            (inst->holdoff_s >= 0);\n    inst->present_resources[SEC_RES_BOOTSTRAP_TIMEOUT] =\n            (inst->bs_timeout_s >= 0);\n#    ifdef ANJAY_WITH_LWM2M11\n    inst->present_resources[SEC_RES_MATCHING_TYPE] = (inst->matching_type >= 0);\n    inst->present_resources[SEC_RES_SNI] = !!inst->server_name_indication;\n    inst->present_resources[SEC_RES_CERTIFICATE_USAGE] =\n            (inst->certificate_usage >= 0);\n    inst->present_resources[SEC_RES_DTLS_TLS_CIPHERSUITE] = true;\n#    endif // ANJAY_WITH_LWM2M11\n}\n\nstatic inline sec_instance_t *find_instance(sec_repr_t *repr, anjay_iid_t iid) {\n    if (!repr) {\n        return NULL;\n    }\n    AVS_LIST(sec_instance_t) it;\n    AVS_LIST_FOREACH(it, repr->instances) {\n        if (it->iid == iid) {\n            return it;\n        } else if (it->iid > iid) {\n            break;\n        }\n    }\n    return NULL;\n}\n\nstatic anjay_iid_t get_new_iid(AVS_LIST(sec_instance_t) instances) {\n    anjay_iid_t iid = 0;\n    AVS_LIST(sec_instance_t) it;\n    AVS_LIST_FOREACH(it, instances) {\n        if (it->iid == iid) {\n            ++iid;\n        } else if (it->iid > iid) {\n            break;\n        }\n    }\n    return iid;\n}\n\nstatic int assign_iid(sec_repr_t *repr, anjay_iid_t *inout_iid) {\n    *inout_iid = get_new_iid(repr->instances);\n    if (*inout_iid == ANJAY_ID_INVALID) {\n        return -1;\n    }\n    return 0;\n}\n\nstatic void init_instance(sec_instance_t *instance, anjay_iid_t iid) {\n    memset(instance, 0, sizeof(sec_instance_t));\n    instance->iid = iid;\n#    ifdef ANJAY_WITH_LWM2M11\n    instance->matching_type = -1;\n    instance->certificate_usage = -1;\n#    endif // ANJAY_WITH_LWM2M11\n    _anjay_sec_instance_update_resource_presence(instance);\n}\n\nstatic int add_instance(sec_repr_t *repr,\n                        const anjay_security_instance_t *instance,\n                        anjay_iid_t *inout_iid) {\n    if (*inout_iid == ANJAY_ID_INVALID) {\n        if (assign_iid(repr, inout_iid)) {\n            return -1;\n        }\n    } else if (find_instance(repr, *inout_iid)) {\n        return -1;\n    }\n    AVS_LIST(sec_instance_t) new_instance =\n            AVS_LIST_NEW_ELEMENT(sec_instance_t);\n    if (!new_instance) {\n        _anjay_log_oom();\n        return -1;\n    }\n    init_instance(new_instance, *inout_iid);\n    if (instance->server_uri) {\n        new_instance->server_uri = avs_strdup(instance->server_uri);\n        if (!new_instance->server_uri) {\n            goto error;\n        }\n    }\n    new_instance->is_bootstrap = instance->bootstrap_server;\n    new_instance->security_mode = instance->security_mode;\n    new_instance->holdoff_s = instance->client_holdoff_s;\n    new_instance->bs_timeout_s = instance->bootstrap_timeout_s;\n\n#    ifdef ANJAY_WITH_SECURITY_STRUCTURED\n    if ((instance->public_cert_or_psk_identity\n         || instance->public_cert_or_psk_identity_size)\n                    + (instance->public_cert.desc.source\n                       != AVS_CRYPTO_DATA_SOURCE_EMPTY)\n                    + (instance->psk_identity.desc.source\n                       != AVS_CRYPTO_DATA_SOURCE_EMPTY)\n            > 1) {\n        security_log(ERROR, _(\"more than one variant of the Public Key Or \"\n                              \"Identity field specified at the same time\"));\n        goto error;\n    }\n    if (instance->public_cert.desc.source != AVS_CRYPTO_DATA_SOURCE_EMPTY) {\n        if (_anjay_sec_init_certificate_chain_resource(\n                    &new_instance->public_cert_or_psk_identity,\n                    SEC_KEY_AS_KEY_EXTERNAL, &instance->public_cert)) {\n            goto error;\n        }\n    } else if (instance->psk_identity.desc.source\n               != AVS_CRYPTO_DATA_SOURCE_EMPTY) {\n        if (_anjay_sec_init_psk_identity_resource(\n                    &new_instance->public_cert_or_psk_identity,\n                    SEC_KEY_AS_KEY_EXTERNAL, &instance->psk_identity)) {\n            goto error;\n        }\n    } else\n#    endif // ANJAY_WITH_SECURITY_STRUCTURED\n    {\n        new_instance->public_cert_or_psk_identity.type = SEC_KEY_AS_DATA;\n        if (_anjay_raw_buffer_clone(\n                    &new_instance->public_cert_or_psk_identity.value.data,\n                    &(const anjay_raw_buffer_t) {\n                        .data = (void *) (intptr_t)\n                                        instance->public_cert_or_psk_identity,\n                        .size = instance->public_cert_or_psk_identity_size\n                    })) {\n            goto error;\n        }\n    }\n\n#    ifdef ANJAY_WITH_SECURITY_STRUCTURED\n    if ((instance->private_cert_or_psk_key\n         || instance->private_cert_or_psk_key_size)\n                    + (instance->private_key.desc.source\n                       != AVS_CRYPTO_DATA_SOURCE_EMPTY)\n                    + (instance->psk_key.desc.source\n                       != AVS_CRYPTO_DATA_SOURCE_EMPTY)\n            > 1) {\n        security_log(ERROR, _(\"more than one variant of the Secret Key field \"\n                              \"specified at the same time\"));\n        goto error;\n    }\n    if (instance->private_key.desc.source != AVS_CRYPTO_DATA_SOURCE_EMPTY) {\n        if (_anjay_sec_init_private_key_resource(\n                    &new_instance->private_cert_or_psk_key,\n                    SEC_KEY_AS_KEY_EXTERNAL,\n                    &instance->private_key)) {\n            goto error;\n        }\n    } else if (instance->psk_key.desc.source != AVS_CRYPTO_DATA_SOURCE_EMPTY) {\n        if (_anjay_sec_init_psk_key_resource(\n                    &new_instance->private_cert_or_psk_key,\n                    SEC_KEY_AS_KEY_EXTERNAL,\n                    &instance->psk_key)) {\n            goto error;\n        }\n    } else\n#    endif // ANJAY_WITH_SECURITY_STRUCTURED\n    {\n        new_instance->private_cert_or_psk_key.type = SEC_KEY_AS_DATA;\n        if (_anjay_raw_buffer_clone(\n                    &new_instance->private_cert_or_psk_key.value.data,\n                    &(const anjay_raw_buffer_t) {\n                        .data = (void *) (intptr_t)\n                                        instance->private_cert_or_psk_key,\n                        .size = instance->private_cert_or_psk_key_size\n                    })) {\n            goto error;\n        }\n    }\n\n    if (_anjay_raw_buffer_clone(\n                &new_instance->server_public_key,\n                &(const anjay_raw_buffer_t) {\n                    .data = (void *) (intptr_t) instance->server_public_key,\n                    .size = instance->server_public_key_size\n                })) {\n        goto error;\n    }\n\n    if (!new_instance->is_bootstrap) {\n        new_instance->ssid = instance->ssid;\n        new_instance->present_resources[SEC_RES_SHORT_SERVER_ID] = true;\n    }\n\n#    ifdef ANJAY_WITH_LWM2M11\n    if (instance->matching_type) {\n        // values higher than INT8_MAX are invalid anyway,\n        // and validation will be done in _anjay_sec_object_validate().\n        // This is simpler than adding another validation here.\n        new_instance->matching_type =\n                (int8_t) AVS_MIN(*instance->matching_type, INT8_MAX);\n    }\n    if (instance->server_name_indication\n            && !(new_instance->server_name_indication =\n                         avs_strdup(instance->server_name_indication))) {\n        _anjay_log_oom();\n        goto error;\n    }\n    if (instance->certificate_usage) {\n        // same story as with Matching Type\n        new_instance->certificate_usage =\n                (int8_t) AVS_MIN(*instance->certificate_usage, INT8_MAX);\n    }\n    if (instance->ciphersuites.num_ids > ANJAY_ID_INVALID) {\n        security_log(ERROR, _(\"Too many ciphersuites specified\"));\n        goto error;\n    }\n    for (int32_t i = (int32_t) instance->ciphersuites.num_ids - 1; i >= 0;\n         --i) {\n        AVS_LIST(sec_cipher_instance_t) cipher_instance =\n                AVS_LIST_NEW_ELEMENT(sec_cipher_instance_t);\n        if (!cipher_instance) {\n            _anjay_log_oom();\n            goto error;\n        }\n        cipher_instance->riid = (anjay_riid_t) i;\n        cipher_instance->cipher_id = instance->ciphersuites.ids[i];\n        AVS_LIST_INSERT(&new_instance->enabled_ciphersuites, cipher_instance);\n    }\n#    endif // ANJAY_WITH_LWM2M11\n\n    _anjay_sec_instance_update_resource_presence(new_instance);\n\n    AVS_LIST(sec_instance_t) *ptr;\n    AVS_LIST_FOREACH_PTR(ptr, &repr->instances) {\n        if ((*ptr)->iid > new_instance->iid) {\n            break;\n        }\n    }\n    AVS_LIST_INSERT(ptr, new_instance);\n\n    if (instance->bootstrap_server) {\n        security_log(INFO,\n                     _(\"Added instance \") \"%u\" _(\" (bootstrap, URI: \") \"%s\" _(\n                             \")\"),\n                     *inout_iid, instance->server_uri);\n    } else {\n        security_log(INFO,\n                     _(\"Added instance \") \"%u\" _(\" (SSID: \") \"%u\" _(\n                             \", URI: \") \"%s\" _(\")\"),\n                     *inout_iid, instance->ssid, instance->server_uri);\n    }\n\n    _anjay_sec_mark_modified(repr);\n    return 0;\n\nerror:\n    _anjay_sec_destroy_instances(&new_instance, true);\n    return -1;\n}\n\nstatic int del_instance(sec_repr_t *repr, anjay_iid_t iid) {\n    AVS_LIST(sec_instance_t) *it;\n    AVS_LIST_FOREACH_PTR(it, &repr->instances) {\n        if ((*it)->iid == iid) {\n            AVS_LIST(sec_instance_t) element = AVS_LIST_DETACH(it);\n            _anjay_sec_destroy_instances(&element, true);\n            _anjay_sec_mark_modified(repr);\n            return 0;\n        }\n    }\n\n    assert(0);\n    return ANJAY_ERR_NOT_FOUND;\n}\n\nstatic int sec_list_resources(anjay_unlocked_t *anjay,\n                              const anjay_dm_installed_object_t obj_ptr,\n                              anjay_iid_t iid,\n                              anjay_unlocked_dm_resource_list_ctx_t *ctx) {\n    (void) anjay;\n    const sec_instance_t *inst = find_instance(_anjay_sec_get(obj_ptr), iid);\n    assert(inst);\n\n    for (size_t resource = 0; resource < AVS_ARRAY_SIZE(SECURITY_RESOURCE_ID);\n         resource++) {\n        const anjay_rid_t rid = SECURITY_RESOURCE_ID[resource];\n        _anjay_dm_emit_res_unlocked(ctx, rid,\n#    ifdef ANJAY_WITH_LWM2M11\n                                    rid != SEC_RES_DTLS_TLS_CIPHERSUITE\n                                            ? ANJAY_DM_RES_R\n                                            : ANJAY_DM_RES_RM,\n#    else\n                                    ANJAY_DM_RES_R,\n#    endif // ANJAY_WITH_LWM2M11\n                                    inst->present_resources[rid]\n                                            ? ANJAY_DM_RES_PRESENT\n                                            : ANJAY_DM_RES_ABSENT);\n    }\n\n    return 0;\n}\n\n#    ifdef ANJAY_WITH_LWM2M11\nstatic int\nsec_list_resource_instances(anjay_unlocked_t *anjay,\n                            const anjay_dm_installed_object_t obj_ptr,\n                            anjay_iid_t iid,\n                            anjay_rid_t rid,\n                            anjay_unlocked_dm_list_ctx_t *ctx) {\n    (void) anjay;\n\n    assert(rid == SEC_RES_DTLS_TLS_CIPHERSUITE);\n    (void) rid;\n\n    const sec_instance_t *inst = find_instance(_anjay_sec_get(obj_ptr), iid);\n    assert(inst);\n\n    AVS_LIST(sec_cipher_instance_t) it;\n    AVS_LIST_FOREACH(it, inst->enabled_ciphersuites) {\n        _anjay_dm_emit_unlocked(ctx, it->riid);\n    }\n\n    return 0;\n}\n\nstatic AVS_LIST(sec_cipher_instance_t) *\nfind_cipher_instance_insert_ptr(AVS_LIST(sec_cipher_instance_t) *instances,\n                                anjay_riid_t riid) {\n    AVS_LIST(sec_cipher_instance_t) *it;\n    AVS_LIST_FOREACH_PTR(it, instances) {\n        if ((*it)->riid >= riid) {\n            break;\n        }\n    }\n    return it;\n}\n\nstatic AVS_LIST(sec_cipher_instance_t)\nfind_cipher_instance(AVS_LIST(sec_cipher_instance_t) instances,\n                     anjay_riid_t riid) {\n    AVS_LIST(sec_cipher_instance_t) *it =\n            find_cipher_instance_insert_ptr(&instances, riid);\n    if (it && (*it)->riid == riid) {\n        return *it;\n    }\n    return NULL;\n}\n#    endif // ANJAY_WITH_LWM2M11\n\nstatic int ret_sec_key_or_data(anjay_unlocked_output_ctx_t *ctx,\n                               const sec_key_or_data_t *res) {\n    switch (res->type) {\n    case SEC_KEY_AS_DATA:\n        return _anjay_ret_bytes_unlocked(ctx, res->value.data.data,\n                                         res->value.data.size);\n#    if defined(ANJAY_WITH_SECURITY_STRUCTURED)\n    case SEC_KEY_AS_KEY_EXTERNAL:\n    case SEC_KEY_AS_KEY_OWNED:\n        return _anjay_ret_security_info_unlocked(ctx, &res->value.key.info);\n#    endif /* defined(ANJAY_WITH_SECURITY_STRUCTURED) || \\\n              defined(ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT) */\n    default:\n        AVS_UNREACHABLE(\"invalid value of sec_key_or_data_type_t\");\n        return ANJAY_ERR_INTERNAL;\n    }\n}\n\nstatic int sec_read(anjay_unlocked_t *anjay,\n                    const anjay_dm_installed_object_t obj_ptr,\n                    anjay_iid_t iid,\n                    anjay_rid_t rid,\n                    anjay_riid_t riid,\n                    anjay_unlocked_output_ctx_t *ctx) {\n    (void) anjay;\n    (void) riid;\n#    ifdef ANJAY_WITH_LWM2M11\n    assert(riid == ANJAY_ID_INVALID || rid == SEC_RES_DTLS_TLS_CIPHERSUITE);\n#    else  // ANJAY_WITH_LWM2M11\n    assert(riid == ANJAY_ID_INVALID);\n#    endif // ANJAY_WITH_LWM2M11\n\n    const sec_instance_t *inst = find_instance(_anjay_sec_get(obj_ptr), iid);\n    assert(inst);\n\n    switch ((security_rid_t) rid) {\n    case SEC_RES_LWM2M_SERVER_URI:\n        return _anjay_ret_string_unlocked(ctx, inst->server_uri);\n    case SEC_RES_BOOTSTRAP_SERVER:\n        return _anjay_ret_bool_unlocked(ctx, inst->is_bootstrap);\n    case SEC_RES_SECURITY_MODE:\n        return _anjay_ret_i64_unlocked(ctx, (int32_t) inst->security_mode);\n    case SEC_RES_SERVER_PK:\n        return _anjay_ret_bytes_unlocked(ctx, inst->server_public_key.data,\n                                         inst->server_public_key.size);\n    case SEC_RES_PK_OR_IDENTITY:\n        return ret_sec_key_or_data(ctx, &inst->public_cert_or_psk_identity);\n    case SEC_RES_SECRET_KEY:\n        return ret_sec_key_or_data(ctx, &inst->private_cert_or_psk_key);\n    case SEC_RES_SHORT_SERVER_ID:\n        return _anjay_ret_i64_unlocked(ctx, (int32_t) inst->ssid);\n    case SEC_RES_CLIENT_HOLD_OFF_TIME:\n        return _anjay_ret_i64_unlocked(ctx, inst->holdoff_s);\n    case SEC_RES_BOOTSTRAP_TIMEOUT:\n        return _anjay_ret_i64_unlocked(ctx, inst->bs_timeout_s);\n#    ifdef ANJAY_WITH_LWM2M11\n    case SEC_RES_MATCHING_TYPE:\n        return _anjay_ret_u64_unlocked(\n                ctx, (uint64_t) (uint32_t) inst->matching_type);\n    case SEC_RES_SNI:\n        assert(inst->server_name_indication);\n        return _anjay_ret_string_unlocked(ctx, inst->server_name_indication);\n    case SEC_RES_CERTIFICATE_USAGE:\n        return _anjay_ret_u64_unlocked(\n                ctx, (uint64_t) (uint32_t) inst->certificate_usage);\n    case SEC_RES_DTLS_TLS_CIPHERSUITE: {\n        AVS_LIST(const sec_cipher_instance_t) rinst =\n                find_cipher_instance(inst->enabled_ciphersuites, riid);\n        if (!rinst) {\n            return ANJAY_ERR_NOT_FOUND;\n        }\n        return _anjay_ret_u64_unlocked(ctx, rinst->cipher_id);\n    }\n#    endif // ANJAY_WITH_LWM2M11\n    default:\n        AVS_UNREACHABLE(\"Read handler called on unknown Security resource\");\n        return ANJAY_ERR_NOT_IMPLEMENTED;\n    }\n}\n\n#    ifdef ANJAY_WITH_LWM2M11\nstatic AVS_LIST(sec_cipher_instance_t)\nfind_or_create_cipher_instance(AVS_LIST(sec_cipher_instance_t) *instances,\n                               anjay_riid_t riid) {\n    AVS_LIST(sec_cipher_instance_t) *it =\n            find_cipher_instance_insert_ptr(instances, riid);\n\n    AVS_LIST(sec_cipher_instance_t) cipher =\n            AVS_LIST_INSERT_NEW(sec_cipher_instance_t, it);\n    if (cipher) {\n        cipher->riid = riid;\n    }\n    return cipher;\n}\n#    endif // ANJAY_WITH_LWM2M11\n\nstatic int fetch_sec_key_or_data(anjay_unlocked_input_ctx_t *ctx,\n                                 sec_key_or_data_t *res) {\n    _anjay_sec_key_or_data_cleanup(res, true);\n    assert(res->type == SEC_KEY_AS_DATA);\n    assert(!res->prev_ref);\n    assert(!res->next_ref);\n    return _anjay_io_fetch_bytes(ctx, &res->value.data);\n}\n\nstatic int sec_write(anjay_unlocked_t *anjay,\n                     const anjay_dm_installed_object_t obj_ptr,\n                     anjay_iid_t iid,\n                     anjay_rid_t rid,\n                     anjay_riid_t riid,\n                     anjay_unlocked_input_ctx_t *ctx) {\n    (void) anjay;\n    (void) riid;\n#    ifdef ANJAY_WITH_LWM2M11\n    assert(riid == ANJAY_ID_INVALID || rid == SEC_RES_DTLS_TLS_CIPHERSUITE);\n#    else  // ANJAY_WITH_LWM2M11\n    assert(riid == ANJAY_ID_INVALID);\n#    endif // ANJAY_WITH_LWM2M11\n    sec_repr_t *repr = _anjay_sec_get(obj_ptr);\n    sec_instance_t *inst = find_instance(repr, iid);\n    int retval;\n    assert(inst);\n\n    _anjay_sec_mark_modified(repr);\n\n    switch ((security_rid_t) rid) {\n    case SEC_RES_LWM2M_SERVER_URI:\n        retval = _anjay_io_fetch_string(ctx, &inst->server_uri);\n        break;\n    case SEC_RES_BOOTSTRAP_SERVER:\n        retval = _anjay_get_bool_unlocked(ctx, &inst->is_bootstrap);\n        break;\n    case SEC_RES_SECURITY_MODE:\n        retval = _anjay_sec_fetch_security_mode(ctx, &inst->security_mode);\n        break;\n    case SEC_RES_PK_OR_IDENTITY:\n        retval = fetch_sec_key_or_data(ctx, &inst->public_cert_or_psk_identity);\n        break;\n    case SEC_RES_SERVER_PK:\n        retval = _anjay_io_fetch_bytes(ctx, &inst->server_public_key);\n        break;\n    case SEC_RES_SECRET_KEY:\n        retval = fetch_sec_key_or_data(ctx, &inst->private_cert_or_psk_key);\n        break;\n    case SEC_RES_SHORT_SERVER_ID:\n        retval = _anjay_sec_fetch_short_server_id(ctx, &inst->ssid);\n        break;\n    case SEC_RES_CLIENT_HOLD_OFF_TIME:\n        retval = _anjay_get_i32_unlocked(ctx, &inst->holdoff_s);\n        break;\n    case SEC_RES_BOOTSTRAP_TIMEOUT:\n        retval = _anjay_get_i32_unlocked(ctx, &inst->bs_timeout_s);\n        break;\n#    ifdef ANJAY_WITH_LWM2M11\n    case SEC_RES_MATCHING_TYPE: {\n        uint32_t matching_type;\n        if (!(retval = _anjay_get_u32_unlocked(ctx, &matching_type))) {\n            if (matching_type > 3) {\n                retval = ANJAY_ERR_BAD_REQUEST;\n            } else {\n                inst->matching_type = (int8_t) matching_type;\n            }\n        }\n        break;\n    }\n    case SEC_RES_SNI:\n        retval = _anjay_io_fetch_string(ctx, &inst->server_name_indication);\n        break;\n    case SEC_RES_CERTIFICATE_USAGE: {\n        uint32_t certificate_usage;\n        if (!(retval = _anjay_get_u32_unlocked(ctx, &certificate_usage))) {\n            if (certificate_usage > 3) {\n                retval = ANJAY_ERR_BAD_REQUEST;\n            } else {\n                inst->certificate_usage = (int8_t) certificate_usage;\n            }\n        }\n        break;\n    }\n    case SEC_RES_DTLS_TLS_CIPHERSUITE: {\n        uint32_t cipher_id;\n        if (!(retval = _anjay_get_u32_unlocked(ctx, &cipher_id))) {\n            if (cipher_id == 0) {\n                security_log(\n                        WARNING,\n                        _(\"TLS-NULL-WITH-NULL-NULL cipher is not allowed\"));\n                retval = ANJAY_ERR_BAD_REQUEST;\n            } else if (cipher_id > UINT16_MAX) {\n                security_log(WARNING,\n                             _(\"Ciphersuite ID > 65535 is not allowed\"));\n                retval = ANJAY_ERR_BAD_REQUEST;\n            } else {\n                AVS_LIST(sec_cipher_instance_t) cipher =\n                        find_or_create_cipher_instance(\n                                &inst->enabled_ciphersuites, riid);\n                if (!cipher) {\n                    retval = ANJAY_ERR_INTERNAL;\n                } else {\n                    cipher->cipher_id = cipher_id;\n                }\n            }\n        }\n        break;\n    }\n#    endif // ANJAY_WITH_LWM2M11\n    default:\n        AVS_UNREACHABLE(\"Write handler called on unknown Security resource\");\n        return ANJAY_ERR_NOT_FOUND;\n    }\n\n    if (!retval) {\n        inst->present_resources[rid] = true;\n    }\n\n    return retval;\n}\n\n#    ifdef ANJAY_WITH_LWM2M11\nstatic int sec_resource_reset(anjay_unlocked_t *anjay,\n                              const anjay_dm_installed_object_t obj_ptr,\n                              anjay_iid_t iid,\n                              anjay_rid_t rid) {\n    (void) anjay;\n\n    assert(rid == SEC_RES_DTLS_TLS_CIPHERSUITE);\n    (void) rid;\n\n    const sec_instance_t *inst = find_instance(_anjay_sec_get(obj_ptr), iid);\n    assert(inst);\n\n    AVS_LIST_CLEAR(&inst->enabled_ciphersuites);\n    return 0;\n}\n\n#        ifdef ANJAY_WITH_LWM2M12\nstatic int\nsec_resource_instance_remove(anjay_unlocked_t *anjay,\n                             const anjay_dm_installed_object_t obj_ptr,\n                             anjay_iid_t iid,\n                             anjay_rid_t rid,\n                             anjay_riid_t riid) {\n    (void) anjay;\n\n    assert(rid == SEC_RES_DTLS_TLS_CIPHERSUITE);\n    (void) rid;\n\n    sec_instance_t *inst = find_instance(_anjay_sec_get(obj_ptr), iid);\n    assert(inst);\n    AVS_LIST(sec_cipher_instance_t) *rinst_ptr =\n            find_cipher_instance_insert_ptr(&inst->enabled_ciphersuites, riid);\n    assert(rinst_ptr && *rinst_ptr && (*rinst_ptr)->riid);\n    AVS_LIST_DELETE(rinst_ptr);\n    return 0;\n}\n#        endif // ANJAY_WITH_LWM2M12\n#    endif     // ANJAY_WITH_LWM2M11\n\nstatic int sec_list_instances(anjay_unlocked_t *anjay,\n                              const anjay_dm_installed_object_t obj_ptr,\n                              anjay_unlocked_dm_list_ctx_t *ctx) {\n    (void) anjay;\n\n    sec_repr_t *repr = _anjay_sec_get(obj_ptr);\n    AVS_LIST(sec_instance_t) it;\n    AVS_LIST_FOREACH(it, repr->instances) {\n        _anjay_dm_emit_unlocked(ctx, it->iid);\n    }\n    return 0;\n}\n\nstatic int sec_instance_create(anjay_unlocked_t *anjay,\n                               const anjay_dm_installed_object_t obj_ptr,\n                               anjay_iid_t iid) {\n    (void) anjay;\n    sec_repr_t *repr = _anjay_sec_get(obj_ptr);\n    assert(iid != ANJAY_ID_INVALID);\n\n    AVS_LIST(sec_instance_t) created = AVS_LIST_NEW_ELEMENT(sec_instance_t);\n    if (!created) {\n        return ANJAY_ERR_INTERNAL;\n    }\n\n    init_instance(created, iid);\n\n    AVS_LIST(sec_instance_t) *ptr;\n    AVS_LIST_FOREACH_PTR(ptr, &repr->instances) {\n        if ((*ptr)->iid > created->iid) {\n            break;\n        }\n    }\n\n    AVS_LIST_INSERT(ptr, created);\n    _anjay_sec_mark_modified(repr);\n    return 0;\n}\n\nstatic int sec_instance_remove(anjay_unlocked_t *anjay,\n                               const anjay_dm_installed_object_t obj_ptr,\n                               anjay_iid_t iid) {\n    (void) anjay;\n    return del_instance(_anjay_sec_get(obj_ptr), iid);\n}\n\nstatic int sec_transaction_begin(anjay_unlocked_t *anjay,\n                                 const anjay_dm_installed_object_t obj_ptr) {\n    (void) anjay;\n    return _anjay_sec_transaction_begin_impl(_anjay_sec_get(obj_ptr));\n}\n\nstatic int sec_transaction_commit(anjay_unlocked_t *anjay,\n                                  const anjay_dm_installed_object_t obj_ptr) {\n    (void) anjay;\n    return _anjay_sec_transaction_commit_impl(_anjay_sec_get(obj_ptr));\n}\n\nstatic int sec_transaction_validate(anjay_unlocked_t *anjay,\n                                    const anjay_dm_installed_object_t obj_ptr) {\n    (void) anjay;\n    return _anjay_sec_transaction_validate_impl(anjay, _anjay_sec_get(obj_ptr));\n}\n\nstatic int sec_transaction_rollback(anjay_unlocked_t *anjay,\n                                    const anjay_dm_installed_object_t obj_ptr) {\n    (void) anjay;\n    return _anjay_sec_transaction_rollback_impl(_anjay_sec_get(obj_ptr));\n}\n\nstatic int sec_instance_reset(anjay_unlocked_t *anjay,\n                              const anjay_dm_installed_object_t obj_ptr,\n                              anjay_iid_t iid) {\n    (void) anjay;\n    sec_instance_t *inst = find_instance(_anjay_sec_get(obj_ptr), iid);\n    assert(inst);\n\n    _anjay_sec_destroy_instance_fields(inst, true);\n    init_instance(inst, iid);\n    return 0;\n}\n\nstatic const anjay_unlocked_dm_object_def_t SECURITY = {\n    .oid = ANJAY_DM_OID_SECURITY,\n    .handlers = {\n        .list_instances = sec_list_instances,\n        .instance_create = sec_instance_create,\n        .instance_remove = sec_instance_remove,\n        .instance_reset = sec_instance_reset,\n        .list_resources = sec_list_resources,\n#    ifdef ANJAY_WITH_LWM2M11\n        .list_resource_instances = sec_list_resource_instances,\n#    endif // ANJAY_WITH_LWM2M11\n        .resource_read = sec_read,\n        .resource_write = sec_write,\n#    ifdef ANJAY_WITH_LWM2M11\n        .resource_reset = sec_resource_reset,\n#    endif // ANJAY_WITH_LWM2M11\n        .transaction_begin = sec_transaction_begin,\n        .transaction_commit = sec_transaction_commit,\n        .transaction_validate = sec_transaction_validate,\n        .transaction_rollback = sec_transaction_rollback\n#    ifdef ANJAY_WITH_LWM2M12\n        ,\n        .resource_instance_remove = sec_resource_instance_remove\n#    endif // ANJAY_WITH_LWM2M12\n    }\n};\n\nsec_repr_t *_anjay_sec_get(const anjay_dm_installed_object_t obj_ptr) {\n    const anjay_unlocked_dm_object_def_t *const *unlocked_def =\n            _anjay_dm_installed_object_get_unlocked(&obj_ptr);\n    assert(*unlocked_def == &SECURITY);\n    return AVS_CONTAINER_OF(unlocked_def, sec_repr_t, def);\n}\n\nint anjay_security_object_add_instance(\n        anjay_t *anjay_locked,\n        const anjay_security_instance_t *instance,\n        anjay_iid_t *inout_iid) {\n    assert(anjay_locked);\n    int retval = -1;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    const anjay_dm_installed_object_t *obj_ptr =\n            _anjay_dm_find_object_by_oid(_anjay_get_dm(anjay), SECURITY.oid);\n    sec_repr_t *repr = obj_ptr ? _anjay_sec_get(*obj_ptr) : NULL;\n    if (!repr) {\n        security_log(ERROR, _(\"Security object is not registered\"));\n        retval = -1;\n    } else {\n        const bool modified_since_persist = repr->modified_since_persist;\n        if (!(retval = add_instance(repr, instance, inout_iid))\n                && (retval = _anjay_sec_object_validate_and_process_keys(\n                            anjay, repr))) {\n            (void) del_instance(repr, *inout_iid);\n            if (!modified_since_persist) {\n                /* validation failed and so in the end no instace is added */\n                _anjay_sec_clear_modified(repr);\n            }\n        }\n\n        if (!retval) {\n            if (_anjay_notify_instances_changed_unlocked(anjay, SECURITY.oid)) {\n                security_log(WARNING, _(\"Could not schedule socket reload\"));\n            }\n        }\n    }\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return retval;\n}\n\nstatic void security_delete(void *repr_) {\n    sec_repr_t *repr = (sec_repr_t *) repr_;\n    if (repr->in_transaction) {\n        _anjay_sec_destroy_instances(&repr->instances, true);\n        _anjay_sec_destroy_instances(&repr->saved_instances,\n                                     repr->saved_modified_since_persist);\n    } else {\n        assert(!repr->saved_instances);\n        _anjay_sec_destroy_instances(&repr->instances,\n                                     repr->modified_since_persist);\n    }\n    // NOTE: repr itself will be freed when cleaning the objects list\n}\n\nvoid anjay_security_object_purge(anjay_t *anjay_locked) {\n    assert(anjay_locked);\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    const anjay_dm_installed_object_t *sec_obj =\n            _anjay_dm_find_object_by_oid(_anjay_get_dm(anjay), SECURITY.oid);\n    sec_repr_t *repr = sec_obj ? _anjay_sec_get(*sec_obj) : NULL;\n\n    if (!repr) {\n        security_log(ERROR, _(\"Security object is not registered\"));\n    } else {\n        if (repr->instances) {\n            _anjay_sec_mark_modified(repr);\n        }\n        _anjay_sec_destroy_instances(&repr->saved_instances, true);\n        _anjay_sec_destroy_instances(&repr->instances, true);\n        if (_anjay_notify_instances_changed_unlocked(anjay, SECURITY.oid)) {\n            security_log(WARNING, _(\"Could not schedule socket reload\"));\n        }\n    }\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n}\n\nbool anjay_security_object_is_modified(anjay_t *anjay_locked) {\n    assert(anjay_locked);\n    bool result = false;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    const anjay_dm_installed_object_t *sec_obj =\n            _anjay_dm_find_object_by_oid(_anjay_get_dm(anjay), SECURITY.oid);\n    if (!sec_obj) {\n        security_log(ERROR, _(\"Security object is not registered\"));\n    } else {\n        sec_repr_t *repr = _anjay_sec_get(*sec_obj);\n        if (repr->in_transaction) {\n            result = repr->saved_modified_since_persist;\n        } else {\n            result = repr->modified_since_persist;\n        }\n    }\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return result;\n}\n\nstatic sec_repr_t *security_install_unlocked(anjay_unlocked_t *anjay) {\n    AVS_LIST(sec_repr_t) repr = AVS_LIST_NEW_ELEMENT(sec_repr_t);\n    if (!repr) {\n        _anjay_log_oom();\n        return NULL;\n    }\n    int result = -1;\n    repr->def = &SECURITY;\n    _anjay_dm_installed_object_init_unlocked(&repr->def_ptr, &repr->def);\n    if (!_anjay_dm_module_install(anjay, security_delete, repr)) {\n        _ANJAY_ASSERT_INSTALLED_OBJECT_IS_FIRST_FIELD(sec_repr_t, def_ptr);\n        AVS_LIST(anjay_dm_installed_object_t) entry = &repr->def_ptr;\n        if (_anjay_register_object_unlocked(anjay, &entry)) {\n            result = _anjay_dm_module_uninstall(anjay, security_delete);\n            assert(!result);\n            result = -1;\n        } else {\n            result = 0;\n        }\n    }\n    if (result) {\n        AVS_LIST_CLEAR(&repr);\n    }\n    return repr;\n}\n\nint anjay_security_object_install(anjay_t *anjay_locked) {\n    assert(anjay_locked);\n    sec_repr_t *repr = NULL;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    repr = security_install_unlocked(anjay);\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return repr ? 0 : -1;\n}\n\n#    ifdef ANJAY_TEST\n#        include \"tests/modules/security/api.c\"\n#    endif\n\n#endif // ANJAY_WITH_MODULE_SECURITY\n"
  },
  {
    "path": "src/modules/security/anjay_mod_security.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef SECURITY_SECURITY_H\n#define SECURITY_SECURITY_H\n#include <anjay_init.h>\n\n#include <anjay/security.h>\n\n#include <anjay_modules/anjay_raw_buffer.h>\n#include <anjay_modules/dm/anjay_modules.h>\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\ntypedef enum {\n    SEC_RES_LWM2M_SERVER_URI = 0,\n    SEC_RES_BOOTSTRAP_SERVER = 1,\n    SEC_RES_SECURITY_MODE = 2,\n    SEC_RES_PK_OR_IDENTITY = 3,\n    SEC_RES_SERVER_PK = 4,\n    SEC_RES_SECRET_KEY = 5,\n    SEC_RES_SHORT_SERVER_ID = 10,\n    SEC_RES_CLIENT_HOLD_OFF_TIME = 11,\n    SEC_RES_BOOTSTRAP_TIMEOUT = 12,\n#ifdef ANJAY_WITH_LWM2M11\n    SEC_RES_MATCHING_TYPE = 13,\n    SEC_RES_SNI = 14,\n    SEC_RES_CERTIFICATE_USAGE = 15,\n    SEC_RES_DTLS_TLS_CIPHERSUITE = 16,\n#endif // ANJAY_WITH_LWM2M11\n    _SEC_RES_COUNT\n} security_rid_t;\n\ntypedef struct {\n    anjay_riid_t riid;\n    uint32_t cipher_id;\n} sec_cipher_instance_t;\n\ntypedef enum {\n    SEC_KEY_AS_DATA,\n    SEC_KEY_AS_KEY_EXTERNAL,\n    SEC_KEY_AS_KEY_OWNED\n} sec_key_or_data_type_t;\n\ntypedef struct sec_key_or_data_struct sec_key_or_data_t;\nstruct sec_key_or_data_struct {\n    sec_key_or_data_type_t type;\n    union {\n        anjay_raw_buffer_t data;\n#if defined(ANJAY_WITH_SECURITY_STRUCTURED)\n        struct {\n            avs_crypto_security_info_union_t info;\n            void *heap_buf;\n        } key;\n#endif /* defined(ANJAY_WITH_SECURITY_STRUCTURED) || \\\n          defined(ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT) */\n    } value;\n\n    // HERE GOES MAGIC.\n    //\n    // sec_key_or_data_t is, in a way, semantically something like a\n    // shared_ptr<variant<anjay_raw_buffer_t, security_info_and_heap_buf>>.\n    // Note that the instances of sec_key_or_data_t itself are NOT individually\n    // allocated on the heap, as they are fields in sec_instance_t.\n    //\n    // These two fields organize multiple instances of sec_key_or_data_t that\n    // refer to the same heap buffer (either via value.data.data or\n    // value.key.heap_buf) in a doubly linked list. That way, when multiple\n    // instances referring to the same buffer exist, and one of them is to be\n    // cleaned up, that cleaned up instance can be removed from the list without\n    // needing any other pointers (which wouldn't work if that was a singly\n    // linked list).\n    //\n    // When the last (or only) instance referring to a given buffer is being\n    // cleaned up, both prev_ref and next_ref will be NULL, which is a signal\n    // to actually free the resources.\n    //\n    // These pointers are manipulated in _anjay_sec_key_or_data_cleanup() and\n    // sec_key_or_data_create_ref(), so see there for the actual implementation.\n    // Also note that in practice, it is not expected for more than two\n    // references (one in instances and one in saved_instances) to the same\n    // buffer to exist, but a generic solution isn't more complicated, so...\n    sec_key_or_data_t *prev_ref;\n    sec_key_or_data_t *next_ref;\n};\n\ntypedef struct {\n    anjay_iid_t iid;\n    char *server_uri;\n    bool is_bootstrap;\n    anjay_security_mode_t security_mode;\n    sec_key_or_data_t public_cert_or_psk_identity;\n    sec_key_or_data_t private_cert_or_psk_key;\n    anjay_raw_buffer_t server_public_key;\n\n    anjay_ssid_t ssid;\n    int32_t holdoff_s;\n    int32_t bs_timeout_s;\n\n#ifdef ANJAY_WITH_LWM2M11\n    int8_t matching_type;\n    char *server_name_indication;\n    int8_t certificate_usage;\n    AVS_LIST(sec_cipher_instance_t) enabled_ciphersuites;\n#endif // ANJAY_WITH_LWM2M11\n\n    bool present_resources[_SEC_RES_COUNT];\n} sec_instance_t;\n\ntypedef struct {\n    anjay_dm_installed_object_t def_ptr;\n    const anjay_unlocked_dm_object_def_t *def;\n    AVS_LIST(sec_instance_t) instances;\n    AVS_LIST(sec_instance_t) saved_instances;\n    bool modified_since_persist;\n    bool saved_modified_since_persist;\n    bool in_transaction;\n} sec_repr_t;\n\nstatic inline void _anjay_sec_mark_modified(sec_repr_t *repr) {\n    repr->modified_since_persist = true;\n}\n\nstatic inline void _anjay_sec_clear_modified(sec_repr_t *repr) {\n    repr->modified_since_persist = false;\n}\n\nvoid _anjay_sec_instance_update_resource_presence(sec_instance_t *inst);\n\n#define security_log(level, ...) _anjay_log(security, level, __VA_ARGS__)\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif /* SECURITY_SECURITY_H */\n"
  },
  {
    "path": "src/modules/security/anjay_security_persistence.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#ifdef ANJAY_WITH_MODULE_SECURITY\n\n#    ifdef AVS_COMMONS_WITH_AVS_PERSISTENCE\n#        include <avsystem/commons/avs_persistence.h>\n#    endif // AVS_COMMONS_WITH_AVS_PERSISTENCE\n\n#    include <anjay_modules/anjay_dm_utils.h>\n\n#    include <inttypes.h>\n#    include <string.h>\n\n#    include \"anjay_security_transaction.h\"\n#    include \"anjay_security_utils.h\"\n\nVISIBILITY_SOURCE_BEGIN\n\n#    define persistence_log(level, ...) \\\n        _anjay_log(security_persistence, level, __VA_ARGS__)\n\n#    ifdef AVS_COMMONS_WITH_AVS_PERSISTENCE\n\nstatic const char MAGIC_V0[] = { 'S', 'E', 'C', '\\0' };\nstatic const char MAGIC_V1[] = { 'S', 'E', 'C', '\\1' };\nstatic const char MAGIC_V2[] = { 'S', 'E', 'C', '\\2' };\nstatic const char MAGIC_V3[] = { 'S', 'E', 'C', '\\3' };\nstatic const char MAGIC_V4[] = { 'S', 'E', 'C', '\\4' };\nstatic const char MAGIC_V5[] = { 'S', 'E', 'C', '\\5' };\n\nstatic avs_error_t handle_sized_v0_fields(avs_persistence_context_t *ctx,\n                                          sec_instance_t *element) {\n    avs_error_t err;\n    (void) (avs_is_err((err = avs_persistence_u16(ctx, &element->iid)))\n            || avs_is_err((err = avs_persistence_bool(\n                                   ctx, &element->present_resources\n                                                 [SEC_RES_BOOTSTRAP_SERVER])))\n            || avs_is_err((err = avs_persistence_bool(\n                                   ctx, &element->present_resources\n                                                 [SEC_RES_SECURITY_MODE])))\n            || avs_is_err((err = avs_persistence_bool(\n                                   ctx, &element->present_resources\n                                                 [SEC_RES_SHORT_SERVER_ID])))\n            || avs_is_err((\n                       err = avs_persistence_bool(ctx, &element->is_bootstrap)))\n            || avs_is_err((err = avs_persistence_u16(ctx, &element->ssid)))\n            || avs_is_err((err = avs_persistence_u32(\n                                   ctx, (uint32_t *) &element->holdoff_s)))\n            || avs_is_err((err = avs_persistence_u32(\n                                   ctx, (uint32_t *) &element->bs_timeout_s))));\n    return err;\n}\n\nstatic avs_error_t handle_sized_v1_fields(avs_persistence_context_t *ctx,\n                                          sec_instance_t *element) {\n    avs_error_t err;\n    (void) element;\n    (void) (avs_is_err((err = avs_persistence_bool(ctx, &(bool) { false })))\n            || avs_is_err((err = avs_persistence_bool(ctx, &(bool) { false })))\n            || avs_is_err(\n                       (err = avs_persistence_bool(ctx, &(bool) { false }))));\n    return err;\n}\n\nstatic avs_error_t handle_ciphersuite_entry(avs_persistence_context_t *ctx,\n                                            void *element,\n                                            void *user_data) {\n    (void) user_data;\n\n    sec_cipher_instance_t *inst = (sec_cipher_instance_t *) element;\n    avs_error_t err;\n    (void) (avs_is_err((err = avs_persistence_u16(ctx, &inst->riid)))\n            || avs_is_err((err = avs_persistence_u32(ctx, &inst->cipher_id))));\n    if (avs_is_ok(err) && inst->cipher_id == 0) {\n        return avs_errno(AVS_EBADMSG);\n    }\n    return err;\n}\n\nstatic avs_error_t handle_sized_v2_fields(avs_persistence_context_t *ctx,\n                                          sec_instance_t *element) {\n    AVS_LIST(sec_cipher_instance_t) enabled_ciphersuites = NULL;\n    char *server_name_indication = NULL;\n#        ifdef ANJAY_WITH_LWM2M11\n    enabled_ciphersuites = element->enabled_ciphersuites;\n    server_name_indication = element->server_name_indication;\n#        endif // ANJAY_WITH_LWM2M11\n    avs_error_t err;\n    (void) (avs_is_err((err = avs_persistence_list(\n                                ctx, (void **) &enabled_ciphersuites,\n                                sizeof(*enabled_ciphersuites),\n                                handle_ciphersuite_entry, NULL, avs_free)))\n            || avs_is_err((err = avs_persistence_string(\n                                   ctx, &server_name_indication)))\n            || avs_is_err((err = avs_persistence_bool(\n                                   ctx, (bool *) &(bool) { false })))\n            || avs_is_err((err = avs_persistence_u16(\n                                   ctx, (uint16_t *) &(uint16_t) { 0 }))));\n#        ifdef ANJAY_WITH_LWM2M11\n    element->enabled_ciphersuites = enabled_ciphersuites;\n    element->server_name_indication = server_name_indication;\n#        else  // ANJAY_WITH_LWM2M11\n    (void) element;\n    AVS_LIST_CLEAR(&enabled_ciphersuites);\n    avs_free(server_name_indication);\n#        endif // ANJAY_WITH_LWM2M11\n    return err;\n}\n\nstatic avs_error_t handle_sized_v3_fields(avs_persistence_context_t *ctx,\n                                          sec_instance_t *element) {\n#        ifndef ANJAY_WITH_LWM2M11\n    (void) element;\n#        endif // ANJAY_WITH_LWM2M11\n    avs_error_t err;\n    (void) (avs_is_err((err = avs_persistence_i8(ctx,\n#        ifdef ANJAY_WITH_LWM2M11\n                                                 &element->matching_type\n#        else  // ANJAY_WITH_LWM2M11\n                                                 &(int8_t) { -1 }\n#        endif // ANJAY_WITH_LWM2M11\n                                                 )))\n            || avs_is_err((err = avs_persistence_i8(ctx,\n#        ifdef ANJAY_WITH_LWM2M11\n                                                    &element->certificate_usage\n#        else  // ANJAY_WITH_LWM2M11\n                                                    &(int8_t) { -1 }\n#        endif // ANJAY_WITH_LWM2M11\n                                                    ))));\n    return err;\n}\n\n#        ifdef ANJAY_WITH_LWM2M11\nstatic void reset_v3_fields(sec_instance_t *element) {\n    element->matching_type = -1;\n    element->certificate_usage = -1;\n}\n#        endif // ANJAY_WITH_LWM2M11\n\n#        if defined(ANJAY_WITH_SECURITY_STRUCTURED)\nstatic avs_error_t handle_sec_key_or_data_type(avs_persistence_context_t *ctx,\n                                               sec_key_or_data_type_t *type) {\n    avs_persistence_direction_t direction = avs_persistence_direction(ctx);\n    int8_t type_ch;\n    if (direction == AVS_PERSISTENCE_STORE) {\n        switch (*type) {\n        case SEC_KEY_AS_DATA:\n            type_ch = 'D';\n            break;\n        case SEC_KEY_AS_KEY_EXTERNAL:\n            type_ch = 'K';\n            break;\n        case SEC_KEY_AS_KEY_OWNED:\n            type_ch = 'O';\n            break;\n        default:\n            AVS_UNREACHABLE(\"invalid value of sec_key_or_data_type_t\");\n            return avs_errno(AVS_EINVAL);\n        }\n    }\n\n    avs_error_t err = avs_persistence_i8(ctx, &type_ch);\n    if (avs_is_err(err)) {\n        return err;\n    }\n\n    if (direction == AVS_PERSISTENCE_RESTORE) {\n        switch (type_ch) {\n        case 'D':\n            *type = SEC_KEY_AS_DATA;\n            break;\n        case 'K':\n            *type = SEC_KEY_AS_KEY_EXTERNAL;\n            break;\n        case 'O':\n            *type = SEC_KEY_AS_KEY_OWNED;\n            break;\n        default:\n            return avs_errno(AVS_EIO);\n        }\n    }\n    return AVS_OK;\n}\n\nstatic avs_error_t handle_sec_key_tag(avs_persistence_context_t *ctx,\n                                      avs_crypto_security_info_tag_t *tag) {\n    avs_persistence_direction_t direction = avs_persistence_direction(ctx);\n    int8_t tag_ch;\n    if (direction == AVS_PERSISTENCE_STORE) {\n        switch (*tag) {\n        case AVS_CRYPTO_SECURITY_INFO_CERTIFICATE_CHAIN:\n            tag_ch = 'C';\n            break;\n        case AVS_CRYPTO_SECURITY_INFO_PRIVATE_KEY:\n            tag_ch = 'K';\n            break;\n        case AVS_CRYPTO_SECURITY_INFO_PSK_IDENTITY:\n            tag_ch = 'I';\n            break;\n        case AVS_CRYPTO_SECURITY_INFO_PSK_KEY:\n            tag_ch = 'P';\n            break;\n        default:\n            AVS_UNREACHABLE(\"invalid value of avs_crypto_security_info_tag_t\");\n            return avs_errno(AVS_EINVAL);\n        }\n    }\n\n    avs_error_t err = avs_persistence_i8(ctx, &tag_ch);\n    if (avs_is_err(err)) {\n        return err;\n    }\n\n    if (direction == AVS_PERSISTENCE_RESTORE) {\n        switch (tag_ch) {\n        case 'C':\n            *tag = AVS_CRYPTO_SECURITY_INFO_CERTIFICATE_CHAIN;\n            break;\n        case 'K':\n            *tag = AVS_CRYPTO_SECURITY_INFO_PRIVATE_KEY;\n            break;\n        case 'I':\n            *tag = AVS_CRYPTO_SECURITY_INFO_PSK_IDENTITY;\n            break;\n        case 'P':\n            *tag = AVS_CRYPTO_SECURITY_INFO_PSK_KEY;\n            break;\n        default:\n            return avs_errno(AVS_EIO);\n        }\n    }\n    return AVS_OK;\n}\n\nstatic avs_error_t\nhandle_sec_key_certificate_chain(avs_persistence_context_t *ctx,\n                                 sec_key_or_data_t *value) {\n    assert(value->type == SEC_KEY_AS_KEY_EXTERNAL\n           || value->type == SEC_KEY_AS_KEY_OWNED);\n    if (avs_persistence_direction(ctx) == AVS_PERSISTENCE_STORE) {\n        return avs_crypto_certificate_chain_info_persist(\n                ctx, (avs_crypto_certificate_chain_info_t) {\n                         .desc = value->value.key.info\n                     });\n    } else {\n        avs_crypto_certificate_chain_info_t *array = NULL;\n        size_t element_count;\n        avs_error_t err = avs_crypto_certificate_chain_info_array_persistence(\n                ctx, &array, &element_count);\n        if (avs_is_ok(err)) {\n            assert(!value->value.key.heap_buf);\n            assert(!value->prev_ref);\n            assert(!value->next_ref);\n            value->value.key.info =\n                    avs_crypto_certificate_chain_info_from_array(array,\n                                                                 element_count)\n                            .desc;\n            value->value.key.heap_buf = array;\n        }\n        return err;\n    }\n}\n\nstatic avs_error_t handle_sec_key_private_key(avs_persistence_context_t *ctx,\n                                              sec_key_or_data_t *value) {\n    assert(value->type == SEC_KEY_AS_KEY_EXTERNAL\n           || value->type == SEC_KEY_AS_KEY_OWNED);\n    avs_crypto_private_key_info_t *key_info = NULL;\n    if (avs_persistence_direction(ctx) == AVS_PERSISTENCE_STORE) {\n        key_info = AVS_CONTAINER_OF(&value->value.key.info,\n                                    avs_crypto_private_key_info_t, desc);\n    }\n    avs_error_t err = avs_crypto_private_key_info_persistence(ctx, &key_info);\n    if (avs_is_ok(err)\n            && avs_persistence_direction(ctx) == AVS_PERSISTENCE_RESTORE) {\n        assert(!value->value.key.heap_buf);\n        assert(!value->prev_ref);\n        assert(!value->next_ref);\n        value->value.key.info = key_info->desc;\n        value->value.key.heap_buf = key_info;\n    }\n    return err;\n}\n\nstatic avs_error_t handle_sec_key_psk_identity(avs_persistence_context_t *ctx,\n                                               sec_key_or_data_t *value) {\n    assert(value->type == SEC_KEY_AS_KEY_EXTERNAL\n           || value->type == SEC_KEY_AS_KEY_OWNED);\n    avs_crypto_psk_identity_info_t *key_info = NULL;\n    if (avs_persistence_direction(ctx) == AVS_PERSISTENCE_STORE) {\n        key_info = AVS_CONTAINER_OF(&value->value.key.info,\n                                    avs_crypto_psk_identity_info_t, desc);\n    }\n    avs_error_t err = avs_crypto_psk_identity_info_persistence(ctx, &key_info);\n    if (avs_is_ok(err)\n            && avs_persistence_direction(ctx) == AVS_PERSISTENCE_RESTORE) {\n        assert(!value->value.key.heap_buf);\n        assert(!value->prev_ref);\n        assert(!value->next_ref);\n        value->value.key.info = key_info->desc;\n        value->value.key.heap_buf = key_info;\n    }\n    return err;\n}\n\nstatic avs_error_t handle_sec_key_psk_key(avs_persistence_context_t *ctx,\n                                          sec_key_or_data_t *value) {\n    assert(value->type == SEC_KEY_AS_KEY_EXTERNAL\n           || value->type == SEC_KEY_AS_KEY_OWNED);\n    avs_crypto_psk_key_info_t *key_info = NULL;\n    if (avs_persistence_direction(ctx) == AVS_PERSISTENCE_STORE) {\n        key_info = AVS_CONTAINER_OF(&value->value.key.info,\n                                    avs_crypto_psk_key_info_t, desc);\n    }\n    avs_error_t err = avs_crypto_psk_key_info_persistence(ctx, &key_info);\n    if (avs_is_ok(err)\n            && avs_persistence_direction(ctx) == AVS_PERSISTENCE_RESTORE) {\n        assert(!value->value.key.heap_buf);\n        assert(!value->prev_ref);\n        assert(!value->next_ref);\n        value->value.key.info = key_info->desc;\n        value->value.key.heap_buf = key_info;\n    }\n    return err;\n}\n#        endif /* defined(ANJAY_WITH_SECURITY_STRUCTURED) || \\\n                  defined(ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT) */\n\nstatic avs_error_t handle_raw_buffer(avs_persistence_context_t *ctx,\n                                     anjay_raw_buffer_t *buffer) {\n    avs_error_t err =\n            avs_persistence_sized_buffer(ctx, &buffer->data, &buffer->size);\n    if (!buffer->capacity) {\n        buffer->capacity = buffer->size;\n    }\n    return err;\n}\n\nstatic avs_error_t\nhandle_sec_key_or_data(avs_persistence_context_t *ctx,\n                       sec_key_or_data_t *value,\n                       intptr_t stream_version,\n                       intptr_t min_version_for_key,\n                       avs_crypto_security_info_tag_t default_tag) {\n#        if defined(ANJAY_WITH_SECURITY_STRUCTURED)\n    if (stream_version >= min_version_for_key) {\n        avs_error_t err = handle_sec_key_or_data_type(ctx, &value->type);\n        if (avs_is_err(err)) {\n            return err;\n        }\n\n        if (value->type == SEC_KEY_AS_KEY_EXTERNAL\n                || value->type == SEC_KEY_AS_KEY_OWNED) {\n            avs_crypto_security_info_tag_t tag = default_tag;\n            if (stream_version >= 5) {\n                if (avs_persistence_direction(ctx) == AVS_PERSISTENCE_STORE) {\n                    tag = value->value.key.info.type;\n                }\n                if (avs_is_err((err = handle_sec_key_tag(ctx, &tag)))) {\n                    return err;\n                }\n            }\n\n            switch (tag) {\n            case AVS_CRYPTO_SECURITY_INFO_CERTIFICATE_CHAIN:\n                return handle_sec_key_certificate_chain(ctx, value);\n            case AVS_CRYPTO_SECURITY_INFO_PRIVATE_KEY:\n                return handle_sec_key_private_key(ctx, value);\n            case AVS_CRYPTO_SECURITY_INFO_PSK_IDENTITY:\n                return handle_sec_key_psk_identity(ctx, value);\n            case AVS_CRYPTO_SECURITY_INFO_PSK_KEY:\n                return handle_sec_key_psk_key(ctx, value);\n            default:\n                AVS_UNREACHABLE(\n                        \"invalid value of avs_crypto_security_info_tag_t\");\n                return avs_errno(AVS_EINVAL);\n            }\n        }\n    }\n#        endif /* defined(ANJAY_WITH_SECURITY_STRUCTURED) || \\\n                  defined(ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT) */\n    (void) stream_version;\n    (void) min_version_for_key;\n    (void) default_tag;\n    assert(value->type == SEC_KEY_AS_DATA);\n    avs_error_t err = handle_raw_buffer(ctx, &value->value.data);\n    assert(avs_is_err(err)\n           || avs_persistence_direction(ctx) != AVS_PERSISTENCE_RESTORE\n           || (!value->prev_ref && !value->next_ref));\n    return err;\n}\n\nstatic avs_error_t handle_instance(avs_persistence_context_t *ctx,\n                                   void *element_,\n                                   void *stream_version_) {\n    sec_instance_t *element = (sec_instance_t *) element_;\n    const intptr_t stream_version = (intptr_t) stream_version_;\n\n    avs_error_t err = AVS_OK;\n    uint16_t security_mode = (uint16_t) element->security_mode;\n    if (avs_is_err((err = handle_sized_v0_fields(ctx, element)))\n            || avs_is_err((err = avs_persistence_u16(ctx, &security_mode)))\n            || avs_is_err((\n                       err = avs_persistence_string(ctx, &element->server_uri)))\n            || avs_is_err((err = handle_sec_key_or_data(\n                                   ctx, &element->public_cert_or_psk_identity,\n                                   stream_version,\n                                   /* min_version_for_key = */ 4,\n                                   AVS_CRYPTO_SECURITY_INFO_CERTIFICATE_CHAIN)))\n            || avs_is_err((err = handle_sec_key_or_data(\n                                   ctx, &element->private_cert_or_psk_key,\n                                   stream_version,\n                                   /* min_version_for_key = */ 4,\n                                   AVS_CRYPTO_SECURITY_INFO_PRIVATE_KEY)))\n            || avs_is_err((err = handle_raw_buffer(\n                                   ctx, &element->server_public_key)))) {\n        return err;\n    }\n    element->security_mode = (anjay_security_mode_t) security_mode;\n    if (stream_version >= 1) {\n        uint16_t sms_security_mode = 3; // NoSec\n        sec_key_or_data_t *sms_key_params_ptr =\n                &(sec_key_or_data_t) { SEC_KEY_AS_DATA };\n        sec_key_or_data_t *sms_secret_key_ptr =\n                &(sec_key_or_data_t) { SEC_KEY_AS_DATA };\n        char *sms_number = NULL;\n        if (avs_is_err((err = handle_sized_v1_fields(ctx, element)))\n                || avs_is_err(\n                           (err = avs_persistence_u16(ctx, &sms_security_mode)))\n                || avs_is_err((err = handle_sec_key_or_data(\n                                       ctx, sms_key_params_ptr, stream_version,\n                                       /* min_version_for_key = */ 5,\n                                       AVS_CRYPTO_SECURITY_INFO_PSK_IDENTITY)))\n                || avs_is_err((err = handle_sec_key_or_data(\n                                       ctx, sms_secret_key_ptr, stream_version,\n                                       /* min_version_for_key = */ 5,\n                                       AVS_CRYPTO_SECURITY_INFO_PSK_KEY)))\n                || avs_is_err(\n                           (err = avs_persistence_string(ctx, &sms_number)))) {\n            return err;\n        }\n        _anjay_sec_key_or_data_cleanup(sms_key_params_ptr, false);\n        _anjay_sec_key_or_data_cleanup(sms_secret_key_ptr, false);\n        avs_free(sms_number);\n    }\n    if (stream_version >= 2) {\n        err = handle_sized_v2_fields(ctx, element);\n    }\n    if (avs_is_ok(err)) {\n        if (stream_version >= 3) {\n            err = handle_sized_v3_fields(ctx, element);\n        }\n#        ifdef ANJAY_WITH_LWM2M11\n        else if (avs_persistence_direction(ctx) == AVS_PERSISTENCE_RESTORE) {\n            reset_v3_fields(element);\n        }\n#        endif // ANJAY_WITH_LWM2M11\n    }\n\n    if (avs_persistence_direction(ctx) == AVS_PERSISTENCE_RESTORE) {\n        _anjay_sec_instance_update_resource_presence(element);\n    }\n\n    return err;\n}\n\navs_error_t anjay_security_object_persist(anjay_t *anjay_locked,\n                                          avs_stream_t *out_stream) {\n    assert(anjay_locked);\n    avs_error_t err = avs_errno(AVS_EINVAL);\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    const anjay_dm_installed_object_t *sec_obj =\n            _anjay_dm_find_object_by_oid(_anjay_get_dm(anjay),\n                                         ANJAY_DM_OID_SECURITY);\n    sec_repr_t *repr = sec_obj ? _anjay_sec_get(*sec_obj) : NULL;\n    if (!repr) {\n        err = avs_errno(AVS_EBADF);\n    } else if (avs_is_ok((err = avs_stream_write(out_stream, MAGIC_V5,\n                                                 sizeof(MAGIC_V5))))) {\n        avs_persistence_context_t ctx =\n                avs_persistence_store_context_create(out_stream);\n        err = avs_persistence_list(\n                &ctx,\n                (AVS_LIST(void) *) (repr->in_transaction\n                                            ? &repr->saved_instances\n                                            : &repr->instances),\n                sizeof(sec_instance_t), handle_instance, (void *) (intptr_t) 5,\n                NULL);\n        if (avs_is_ok(err)) {\n            _anjay_sec_clear_modified(repr);\n            persistence_log(INFO, _(\"Security Object state persisted\"));\n        }\n    }\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return err;\n}\n\navs_error_t anjay_security_object_restore(anjay_t *anjay_locked,\n                                          avs_stream_t *in_stream) {\n    assert(anjay_locked);\n    avs_error_t err = avs_errno(AVS_EINVAL);\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    const anjay_dm_installed_object_t *sec_obj =\n            _anjay_dm_find_object_by_oid(_anjay_get_dm(anjay),\n                                         ANJAY_DM_OID_SECURITY);\n    sec_repr_t *repr = sec_obj ? _anjay_sec_get(*sec_obj) : NULL;\n    if (!repr || repr->in_transaction) {\n        err = avs_errno(AVS_EBADF);\n    } else {\n        sec_repr_t backup = *repr;\n\n        AVS_STATIC_ASSERT(sizeof(MAGIC_V0) == sizeof(MAGIC_V1),\n                          magic_size_v0_v1);\n        AVS_STATIC_ASSERT(sizeof(MAGIC_V1) == sizeof(MAGIC_V2),\n                          magic_size_v1_v2);\n        AVS_STATIC_ASSERT(sizeof(MAGIC_V2) == sizeof(MAGIC_V3),\n                          magic_size_v2_v3);\n        AVS_STATIC_ASSERT(sizeof(MAGIC_V3) == sizeof(MAGIC_V4),\n                          magic_size_v3_v4);\n        AVS_STATIC_ASSERT(sizeof(MAGIC_V4) == sizeof(MAGIC_V5),\n                          magic_size_v4_v5);\n        char magic_header[sizeof(MAGIC_V0)];\n        int version = -1;\n        if (avs_is_err(\n                    (err = avs_stream_read_reliably(in_stream, magic_header,\n                                                    sizeof(magic_header))))) {\n            persistence_log(WARNING,\n                            _(\"Could not read Security Object header\"));\n        } else if (!memcmp(magic_header, MAGIC_V0, sizeof(MAGIC_V0))) {\n            version = 0;\n        } else if (!memcmp(magic_header, MAGIC_V1, sizeof(MAGIC_V1))) {\n            version = 1;\n        } else if (!memcmp(magic_header, MAGIC_V2, sizeof(MAGIC_V2))) {\n            version = 2;\n        } else if (!memcmp(magic_header, MAGIC_V3, sizeof(MAGIC_V3))) {\n            version = 3;\n        } else if (!memcmp(magic_header, MAGIC_V4, sizeof(MAGIC_V4))) {\n            version = 4;\n        } else if (!memcmp(magic_header, MAGIC_V5, sizeof(MAGIC_V5))) {\n            version = 5;\n        } else {\n            persistence_log(WARNING, _(\"Header magic constant mismatch\"));\n            err = avs_errno(AVS_EBADMSG);\n        }\n        if (avs_is_ok(err)) {\n            avs_persistence_context_t restore_ctx =\n                    avs_persistence_restore_context_create(in_stream);\n            repr->instances = NULL;\n            err = avs_persistence_list(&restore_ctx,\n                                       (AVS_LIST(void) *) &repr->instances,\n                                       sizeof(sec_instance_t), handle_instance,\n                                       (void *) (intptr_t) version, NULL);\n            if (avs_is_ok(err)\n                    && _anjay_sec_object_validate_and_process_keys(anjay,\n                                                                   repr)) {\n                err = avs_errno(AVS_EPROTO);\n            }\n            if (avs_is_err(err)) {\n                _anjay_sec_destroy_instances(&repr->instances, true);\n                repr->instances = backup.instances;\n            } else {\n                _anjay_sec_destroy_instances(&backup.instances, true);\n                _anjay_sec_clear_modified(repr);\n                persistence_log(INFO, _(\"Security Object state restored\"));\n            }\n        }\n    }\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return err;\n}\n\n#        ifdef ANJAY_TEST\n#            include \"tests/modules/security/persistence.c\"\n#        endif\n\n#    else // AVS_COMMONS_WITH_AVS_PERSISTENCE\n\navs_error_t anjay_security_object_persist(anjay_t *anjay,\n                                          avs_stream_t *out_stream) {\n    (void) anjay;\n    (void) out_stream;\n    persistence_log(ERROR, _(\"Persistence not compiled in\"));\n    return avs_errno(AVS_ENOTSUP);\n}\n\navs_error_t anjay_security_object_restore(anjay_t *anjay,\n                                          avs_stream_t *in_stream) {\n    (void) anjay;\n    (void) in_stream;\n    persistence_log(ERROR, _(\"Persistence not compiled in\"));\n    return avs_errno(AVS_ENOTSUP);\n}\n\n#    endif // AVS_COMMONS_WITH_AVS_PERSISTENCE\n\n#endif // ANJAY_WITH_MODULE_SECURITY\n"
  },
  {
    "path": "src/modules/security/anjay_security_transaction.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#ifdef ANJAY_WITH_MODULE_SECURITY\n\n#    include <assert.h>\n#    include <string.h>\n\n#    include \"anjay_security_transaction.h\"\n#    include \"anjay_security_utils.h\"\n\n#    include <anjay_modules/anjay_dm_utils.h>\n#    include <anjay_modules/anjay_utils_core.h>\n\nVISIBILITY_SOURCE_BEGIN\n\ntypedef struct {\n    anjay_ssid_t ssid;\n    anjay_socket_transport_t transport;\n} ssid_transport_pair_t;\n\nstatic int\nssid_transport_pair_cmp(const void *a_, const void *b_, size_t element_size) {\n    assert(element_size == sizeof(ssid_transport_pair_t));\n    (void) element_size;\n    const ssid_transport_pair_t *a = (const ssid_transport_pair_t *) a_;\n    const ssid_transport_pair_t *b = (const ssid_transport_pair_t *) b_;\n    if (a->ssid != b->ssid) {\n        return a->ssid - b->ssid;\n    }\n    return (int) a->transport - (int) b->transport;\n}\n\nstatic bool uri_protocol_matching(anjay_security_mode_t security_mode,\n                                  const char *uri) {\n    const anjay_transport_info_t *transport_info =\n            _anjay_transport_info_by_uri_scheme(uri);\n    if (!transport_info) {\n        return false;\n    }\n    if (transport_info->security == ANJAY_TRANSPORT_SECURITY_UNDEFINED) {\n        // URI scheme does not specify security,\n        // so it is valid for all security modes\n        return true;\n    }\n\n    const bool is_secure_uri =\n            (transport_info->security == ANJAY_TRANSPORT_ENCRYPTED);\n    const bool needs_secure_uri = (security_mode != ANJAY_SECURITY_NOSEC);\n    return is_secure_uri == needs_secure_uri;\n}\n\nstatic bool\nsec_key_or_data_valid(const sec_key_or_data_t *value,\n                      const avs_crypto_security_info_tag_t *expected_tag) {\n    (void) expected_tag;\n    switch (value->type) {\n    case SEC_KEY_AS_DATA:\n        return value->value.data.data;\n#    if defined(ANJAY_WITH_SECURITY_STRUCTURED)\n    case SEC_KEY_AS_KEY_EXTERNAL:\n    case SEC_KEY_AS_KEY_OWNED:\n        return expected_tag\n               && value->value.key.info.source != AVS_CRYPTO_DATA_SOURCE_EMPTY\n               && value->value.key.info.type == *expected_tag;\n#    endif /* defined(ANJAY_WITH_SECURITY_STRUCTURED) || \\\n              defined(ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT) */\n    default:\n        AVS_UNREACHABLE(\"invalid value of sec_key_or_data_type_t\");\n        return false;\n    }\n}\n\n#    define LOG_VALIDATION_FAILED(SecInstance, ...)           \\\n        security_log(                                         \\\n                WARNING, \"/%u/%u: \" AVS_VARARG0(__VA_ARGS__), \\\n                ANJAY_DM_OID_SECURITY,                        \\\n                (unsigned) (SecInstance)->iid AVS_VARARG_REST(__VA_ARGS__))\n\nstatic int validate_instance(sec_instance_t *it) {\n    if (!it->server_uri) {\n        LOG_VALIDATION_FAILED(it,\n                              \"missing mandatory 'Server URI' resource value\");\n        return -1;\n    }\n    if (!it->present_resources[SEC_RES_BOOTSTRAP_SERVER]) {\n        LOG_VALIDATION_FAILED(\n                it, \"missing mandatory 'Bootstrap Server' resource value\");\n        return -1;\n    }\n    if (!it->present_resources[SEC_RES_SECURITY_MODE]) {\n        LOG_VALIDATION_FAILED(\n                it, \"missing mandatory 'Security Mode' resource value\");\n        return -1;\n    }\n    if (!it->is_bootstrap && !it->present_resources[SEC_RES_SHORT_SERVER_ID]) {\n        LOG_VALIDATION_FAILED(\n                it, \"missing mandatory 'Short Server ID' resource value\");\n        return -1;\n    }\n    if (_anjay_sec_validate_security_mode((int32_t) it->security_mode)) {\n        LOG_VALIDATION_FAILED(it, \"Security mode %d not supported\",\n                              (int) it->security_mode);\n        return -1;\n    }\n    if (!uri_protocol_matching(it->security_mode, it->server_uri)) {\n        LOG_VALIDATION_FAILED(\n                it,\n                \"Incorrect protocol in Server Uri '%s' due to security \"\n                \"configuration (coap:// instead of coaps:// or vice versa?)\",\n                it->server_uri);\n        return -1;\n    }\n    if (it->security_mode != ANJAY_SECURITY_NOSEC\n            && it->security_mode != ANJAY_SECURITY_EST) {\n        if (!sec_key_or_data_valid(\n                    &it->public_cert_or_psk_identity,\n                    &(const avs_crypto_security_info_tag_t) {\n                            it->security_mode == ANJAY_SECURITY_PSK\n                                    ? AVS_CRYPTO_SECURITY_INFO_PSK_IDENTITY\n                                    : AVS_CRYPTO_SECURITY_INFO_CERTIFICATE_CHAIN })\n                || !sec_key_or_data_valid(\n                           &it->private_cert_or_psk_key,\n                           &(const avs_crypto_security_info_tag_t) {\n                                   it->security_mode == ANJAY_SECURITY_PSK\n                                           ? AVS_CRYPTO_SECURITY_INFO_PSK_KEY\n                                           : AVS_CRYPTO_SECURITY_INFO_PRIVATE_KEY })) {\n            LOG_VALIDATION_FAILED(it,\n                                  \"security credentials not fully configured\");\n            return -1;\n        }\n    }\n#    ifdef ANJAY_WITH_LWM2M11\n    if (it->matching_type > 3) {\n        LOG_VALIDATION_FAILED(it, \"Matching Type set to an invalid value\");\n        return -1;\n    }\n    if (it->matching_type == 2) {\n        LOG_VALIDATION_FAILED(it, \"SHA-384 Matching Type is not supported\");\n        return -1;\n    }\n    if (it->certificate_usage > 3) {\n        LOG_VALIDATION_FAILED(it, \"Certificate Usage set to an invalid value\");\n        return -1;\n    }\n#    endif // ANJAY_WITH_LWM2M11\n    return 0;\n}\n\nstatic int sec_object_validate(anjay_unlocked_t *anjay, sec_repr_t *repr) {\n    AVS_LIST(ssid_transport_pair_t) seen_ssid_transport_pairs = NULL;\n    AVS_LIST(sec_instance_t) it;\n    int result = 0;\n    bool bootstrap_server_present = false;\n    (void) anjay;\n\n    AVS_LIST_FOREACH(it, repr->instances) {\n        /* Assume something will go wrong */\n        result = ANJAY_ERR_BAD_REQUEST;\n        if (validate_instance(it)) {\n            goto finish;\n        }\n\n        if (it->is_bootstrap) {\n            if (bootstrap_server_present) {\n                goto finish;\n            }\n            bootstrap_server_present = true;\n        } else {\n            const anjay_transport_info_t *transport_info =\n                    _anjay_transport_info_by_uri_scheme(it->server_uri);\n            if (!transport_info\n                    || !AVS_LIST_INSERT_NEW(ssid_transport_pair_t,\n                                            &seen_ssid_transport_pairs)) {\n                result = ANJAY_ERR_INTERNAL;\n                goto finish;\n            }\n            seen_ssid_transport_pairs->ssid = it->ssid;\n            seen_ssid_transport_pairs->transport = transport_info->transport;\n        }\n\n        /* We are still there - nothing went wrong, continue */\n        result = 0;\n    }\n\n    if (!result && seen_ssid_transport_pairs) {\n        AVS_LIST_SORT(&seen_ssid_transport_pairs, ssid_transport_pair_cmp);\n        AVS_LIST(ssid_transport_pair_t) prev = seen_ssid_transport_pairs;\n        AVS_LIST(ssid_transport_pair_t) next =\n                AVS_LIST_NEXT(seen_ssid_transport_pairs);\n        while (next) {\n            if (prev->ssid == next->ssid\n                    && prev->transport == next->transport) {\n                /* Duplicate found */\n                result = ANJAY_ERR_BAD_REQUEST;\n                break;\n            }\n            prev = next;\n            next = AVS_LIST_NEXT(next);\n        }\n    }\nfinish:\n    AVS_LIST_CLEAR(&seen_ssid_transport_pairs);\n    return result;\n}\n\nint _anjay_sec_object_validate_and_process_keys(anjay_unlocked_t *anjay,\n                                                sec_repr_t *repr) {\n    int result = sec_object_validate(anjay, repr);\n    return result;\n}\n\nint _anjay_sec_transaction_begin_impl(sec_repr_t *repr) {\n    assert(!repr->saved_instances);\n    assert(!repr->in_transaction);\n    repr->saved_instances = _anjay_sec_clone_instances(repr);\n    if (!repr->saved_instances && repr->instances) {\n        return ANJAY_ERR_INTERNAL;\n    }\n    repr->saved_modified_since_persist = repr->modified_since_persist;\n    repr->in_transaction = true;\n    return 0;\n}\n\nint _anjay_sec_transaction_commit_impl(sec_repr_t *repr) {\n    assert(repr->in_transaction);\n    _anjay_sec_destroy_instances(&repr->saved_instances, true);\n    repr->in_transaction = false;\n    return 0;\n}\n\nint _anjay_sec_transaction_validate_impl(anjay_unlocked_t *anjay,\n                                         sec_repr_t *repr) {\n    assert(repr->in_transaction);\n    return _anjay_sec_object_validate_and_process_keys(anjay, repr);\n}\n\nint _anjay_sec_transaction_rollback_impl(sec_repr_t *repr) {\n    assert(repr->in_transaction);\n    _anjay_sec_destroy_instances(&repr->instances, true);\n    repr->instances = repr->saved_instances;\n    repr->saved_instances = NULL;\n    repr->modified_since_persist = repr->saved_modified_since_persist;\n    repr->in_transaction = false;\n    return 0;\n}\n\n#endif // ANJAY_WITH_MODULE_SECURITY\n"
  },
  {
    "path": "src/modules/security/anjay_security_transaction.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef SECURITY_TRANSACTION_H\n#define SECURITY_TRANSACTION_H\n#include <anjay_init.h>\n\n#include \"anjay_mod_security.h\"\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\nint _anjay_sec_object_validate_and_process_keys(anjay_unlocked_t *anjay,\n                                                sec_repr_t *repr);\n\nint _anjay_sec_transaction_begin_impl(sec_repr_t *repr);\nint _anjay_sec_transaction_commit_impl(sec_repr_t *repr);\nint _anjay_sec_transaction_validate_impl(anjay_unlocked_t *anjay,\n                                         sec_repr_t *repr);\nint _anjay_sec_transaction_rollback_impl(sec_repr_t *repr);\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif /* SECURITY_TRANSACTION_H */\n"
  },
  {
    "path": "src/modules/security/anjay_security_utils.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#ifdef ANJAY_WITH_MODULE_SECURITY\n\n#    include <string.h>\n\n#    include <anjay_modules/anjay_dm_utils.h>\n\n#    include \"anjay_security_utils.h\"\n\nVISIBILITY_SOURCE_BEGIN\n\nint _anjay_sec_validate_security_mode(int32_t security_mode) {\n    switch (security_mode) {\n    case ANJAY_SECURITY_NOSEC:\n    case ANJAY_SECURITY_PSK:\n    case ANJAY_SECURITY_CERTIFICATE:\n    case ANJAY_SECURITY_EST:\n        return 0;\n    case ANJAY_SECURITY_RPK:\n        security_log(ERROR, _(\"Raw Public Key mode not supported\"));\n        return ANJAY_ERR_NOT_IMPLEMENTED;\n    default:\n        security_log(ERROR, _(\"Invalid Security Mode\"));\n        return ANJAY_ERR_BAD_REQUEST;\n    }\n}\n\nint _anjay_sec_fetch_security_mode(anjay_unlocked_input_ctx_t *ctx,\n                                   anjay_security_mode_t *out) {\n    int32_t value;\n    int retval = _anjay_get_i32_unlocked(ctx, &value);\n    if (!retval) {\n        retval = _anjay_sec_validate_security_mode(value);\n    }\n    if (!retval) {\n        *out = (anjay_security_mode_t) value;\n    }\n    return retval;\n}\n\nstatic int _anjay_sec_validate_short_server_id(int32_t ssid) {\n    return ssid > 0 && ssid <= UINT16_MAX ? 0 : -1;\n}\n\nint _anjay_sec_fetch_short_server_id(anjay_unlocked_input_ctx_t *ctx,\n                                     anjay_ssid_t *out) {\n    int32_t value;\n    int retval = _anjay_get_i32_unlocked(ctx, &value);\n    if (!retval) {\n        retval = _anjay_sec_validate_short_server_id(value);\n    }\n    if (!retval) {\n        *out = (anjay_ssid_t) value;\n    }\n    return retval;\n}\n\n#    if defined(ANJAY_WITH_SECURITY_STRUCTURED)\nint _anjay_sec_init_certificate_chain_resource(\n        sec_key_or_data_t *out_resource,\n        sec_key_or_data_type_t type,\n        const avs_crypto_certificate_chain_info_t *in_value) {\n    avs_crypto_certificate_chain_info_t *array = NULL;\n    size_t array_element_count;\n    if (avs_is_err(avs_crypto_certificate_chain_info_copy_as_array(\n                &array, &array_element_count, *in_value))) {\n        return -1;\n    }\n    assert(array || !array_element_count);\n    assert(!out_resource->prev_ref);\n    assert(!out_resource->next_ref);\n    assert(type == SEC_KEY_AS_KEY_EXTERNAL || type == SEC_KEY_AS_KEY_OWNED);\n    out_resource->type = type;\n    out_resource->value.key.info =\n            avs_crypto_certificate_chain_info_from_array(array,\n                                                         array_element_count)\n                    .desc;\n    out_resource->value.key.heap_buf = array;\n    return 0;\n}\n\nint _anjay_sec_init_private_key_resource(\n        sec_key_or_data_t *out_resource,\n        sec_key_or_data_type_t type,\n        const avs_crypto_private_key_info_t *in_value) {\n    avs_crypto_private_key_info_t *private_key = NULL;\n    if (avs_is_err(avs_crypto_private_key_info_copy(&private_key, *in_value))) {\n        return -1;\n    }\n    assert(private_key);\n    assert(!out_resource->prev_ref);\n    assert(!out_resource->next_ref);\n    assert(type == SEC_KEY_AS_KEY_EXTERNAL || type == SEC_KEY_AS_KEY_OWNED);\n    out_resource->type = type;\n    out_resource->value.key.info = private_key->desc;\n    out_resource->value.key.heap_buf = private_key;\n    return 0;\n}\n#    endif /* defined(ANJAY_WITH_SECURITY_STRUCTURED) ||             \\\n              (defined(ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT) && \\\n              defined(AVS_COMMONS_WITH_AVS_CRYPTO_PKI_ENGINE)) */\n\n#    if defined(ANJAY_WITH_SECURITY_STRUCTURED)\nint _anjay_sec_init_psk_identity_resource(\n        sec_key_or_data_t *out_resource,\n        sec_key_or_data_type_t type,\n        const avs_crypto_psk_identity_info_t *in_value) {\n    avs_crypto_psk_identity_info_t *psk_identity = NULL;\n    if (avs_is_err(\n                avs_crypto_psk_identity_info_copy(&psk_identity, *in_value))) {\n        return -1;\n    }\n    assert(psk_identity);\n    assert(!out_resource->prev_ref);\n    assert(!out_resource->next_ref);\n    assert(type == SEC_KEY_AS_KEY_EXTERNAL || type == SEC_KEY_AS_KEY_OWNED);\n    out_resource->type = type;\n    out_resource->value.key.info = psk_identity->desc;\n    out_resource->value.key.heap_buf = psk_identity;\n    return 0;\n}\n\nint _anjay_sec_init_psk_key_resource(\n        sec_key_or_data_t *out_resource,\n        sec_key_or_data_type_t type,\n        const avs_crypto_psk_key_info_t *in_value) {\n    avs_crypto_psk_key_info_t *psk_key = NULL;\n    if (avs_is_err(avs_crypto_psk_key_info_copy(&psk_key, *in_value))) {\n        return -1;\n    }\n    assert(psk_key);\n    assert(!out_resource->prev_ref);\n    assert(!out_resource->next_ref);\n    assert(type == SEC_KEY_AS_KEY_EXTERNAL || type == SEC_KEY_AS_KEY_OWNED);\n    out_resource->type = type;\n    out_resource->value.key.info = psk_key->desc;\n    out_resource->value.key.heap_buf = psk_key;\n    return 0;\n}\n#    endif /* defined(ANJAY_WITH_SECURITY_STRUCTURED) ||             \\\n              (defined(ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT) && \\\n              defined(AVS_COMMONS_WITH_AVS_CRYPTO_PSK_ENGINE)) */\n\nvoid _anjay_sec_key_or_data_cleanup(sec_key_or_data_t *value,\n                                    bool remove_from_engine) {\n    (void) remove_from_engine;\n    if (!value->prev_ref && !value->next_ref) {\n        switch (value->type) {\n        case SEC_KEY_AS_DATA:\n#    if defined(AVS_COMMONS_WITH_AVS_CRYPTO)\n            avs_crypto_clear_buffer(value->value.data.data,\n                                    value->value.data.capacity);\n#    else\n            memset(value->value.data.data, 0, value->value.data.capacity);\n#    endif // defined(AVS_COMMONS_WITH_AVS_CRYPTO)\n\n            _anjay_raw_buffer_clear(&value->value.data);\n            break;\n#    if defined(ANJAY_WITH_SECURITY_STRUCTURED)\n        case SEC_KEY_AS_KEY_OWNED:\n            // fall-through\n        case SEC_KEY_AS_KEY_EXTERNAL:\n            if ((value->value.key.info.type == AVS_CRYPTO_SECURITY_INFO_PSK_KEY\n                 || value->value.key.info.type\n                            == AVS_CRYPTO_SECURITY_INFO_PRIVATE_KEY)\n                    && value->value.key.info.source\n                                   == AVS_CRYPTO_DATA_SOURCE_BUFFER) {\n                size_t heap_buf_size =\n                        value->value.key.info.info.buffer.buffer_size;\n#        if defined(AVS_COMMONS_WITH_AVS_CRYPTO)\n                avs_crypto_clear_buffer(value->value.key.heap_buf,\n                                        heap_buf_size);\n#        else\n                memset(value->value.key.heap_buf, 0, heap_buf_size);\n#        endif // defined(AVS_COMMONS_WITH_AVS_CRYPTO)\n            }\n            avs_free(value->value.key.heap_buf);\n            break;\n#    endif /* defined(ANJAY_WITH_SECURITY_STRUCTURED) || \\\n              defined(ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT) */\n        default:\n            AVS_UNREACHABLE(\"invalid value of sec_key_or_data_type_t\");\n        }\n    } else {\n        if (value->prev_ref) {\n            value->prev_ref->next_ref = value->next_ref;\n        }\n        if (value->next_ref) {\n            value->next_ref->prev_ref = value->prev_ref;\n        }\n    }\n    memset(value, 0, sizeof(*value));\n    assert(value->type == SEC_KEY_AS_DATA);\n}\n\nvoid _anjay_sec_destroy_instance_fields(sec_instance_t *instance,\n                                        bool remove_from_engine) {\n    if (!instance) {\n        return;\n    }\n    avs_free((char *) (intptr_t) instance->server_uri);\n    _anjay_sec_key_or_data_cleanup(&instance->public_cert_or_psk_identity,\n                                   remove_from_engine);\n    _anjay_sec_key_or_data_cleanup(&instance->private_cert_or_psk_key,\n                                   remove_from_engine);\n    _anjay_raw_buffer_clear(&instance->server_public_key);\n#    ifdef ANJAY_WITH_LWM2M11\n    AVS_LIST_CLEAR(&instance->enabled_ciphersuites);\n    avs_free(instance->server_name_indication);\n#    endif // ANJAY_WITH_LWM2M11\n}\n\nvoid _anjay_sec_destroy_instances(AVS_LIST(sec_instance_t) *instances_ptr,\n                                  bool remove_from_engine) {\n    AVS_LIST_CLEAR(instances_ptr) {\n        _anjay_sec_destroy_instance_fields(*instances_ptr, remove_from_engine);\n    }\n}\n\nstatic void sec_key_or_data_create_ref(sec_key_or_data_t *dest,\n                                       sec_key_or_data_t *src) {\n    *dest = *src;\n    dest->prev_ref = src;\n    dest->next_ref = src->next_ref;\n    if (src->next_ref) {\n        src->next_ref->prev_ref = dest;\n    }\n    src->next_ref = dest;\n}\n\nstatic int _anjay_sec_clone_instance(sec_instance_t *dest,\n                                     sec_instance_t *src) {\n    *dest = *src;\n\n    assert(src->server_uri);\n    dest->server_uri = avs_strdup(src->server_uri);\n    if (!dest->server_uri) {\n        security_log(ERROR, _(\"Cannot clone Server Uri resource\"));\n        return -1;\n    }\n\n    sec_key_or_data_create_ref(&dest->public_cert_or_psk_identity,\n                               &src->public_cert_or_psk_identity);\n    sec_key_or_data_create_ref(&dest->private_cert_or_psk_key,\n                               &src->private_cert_or_psk_key);\n\n    dest->server_public_key = ANJAY_RAW_BUFFER_EMPTY;\n    if (_anjay_raw_buffer_clone(&dest->server_public_key,\n                                &src->server_public_key)) {\n        security_log(ERROR, _(\"Cannot clone Server Public Key resource\"));\n        return -1;\n    }\n\n#    ifdef ANJAY_WITH_LWM2M11\n    dest->server_name_indication = NULL;\n    if (src->server_name_indication\n            && !(dest->server_name_indication =\n                         avs_strdup(src->server_name_indication))) {\n        security_log(ERROR, _(\"Cannot clone SNI resource\"));\n        return -1;\n    }\n#    endif // ANJAY_WITH_LWM2M11\n\n    return 0;\n}\n\nAVS_LIST(sec_instance_t) _anjay_sec_clone_instances(const sec_repr_t *repr) {\n    AVS_LIST(sec_instance_t) retval = NULL;\n    AVS_LIST(sec_instance_t) current;\n    AVS_LIST(sec_instance_t) *last;\n    last = &retval;\n\n    AVS_LIST_FOREACH(current, repr->instances) {\n        if (AVS_LIST_INSERT_NEW(sec_instance_t, last)) {\n            if (_anjay_sec_clone_instance(*last, current)) {\n                security_log(ERROR,\n                             _(\"Cannot clone Security Object Instances\"));\n                _anjay_sec_destroy_instances(&retval, false);\n                return NULL;\n            }\n            AVS_LIST_ADVANCE_PTR(&last);\n        }\n    }\n    return retval;\n}\n\n#endif // ANJAY_WITH_MODULE_SECURITY\n"
  },
  {
    "path": "src/modules/security/anjay_security_utils.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef SECURITY_UTILS_H\n#define SECURITY_UTILS_H\n#include <anjay_init.h>\n\n#include <assert.h>\n\n#include \"anjay_mod_security.h\"\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\nsec_repr_t *_anjay_sec_get(const anjay_dm_installed_object_t obj_ptr);\n\n/**\n * Fetches UDP Security Mode from @p ctx, performs validation and in case of\n * success sets @p *out to one of @p anjay_security_mode_t enum value.\n */\nint _anjay_sec_fetch_security_mode(anjay_unlocked_input_ctx_t *ctx,\n                                   anjay_security_mode_t *out);\n\nint _anjay_sec_validate_security_mode(int32_t security_mode);\n\n/**\n * Fetches SSID from @p ctx, performs validation and in case of success sets\n * @p *out .\n */\nint _anjay_sec_fetch_short_server_id(anjay_unlocked_input_ctx_t *ctx,\n                                     anjay_ssid_t *out);\n\n#if defined(ANJAY_WITH_SECURITY_STRUCTURED)\nint _anjay_sec_init_certificate_chain_resource(\n        sec_key_or_data_t *out_resource,\n        sec_key_or_data_type_t type,\n        const avs_crypto_certificate_chain_info_t *in_value);\n\nint _anjay_sec_init_private_key_resource(\n        sec_key_or_data_t *out_resource,\n        sec_key_or_data_type_t type,\n        const avs_crypto_private_key_info_t *in_value);\n#endif /* defined(ANJAY_WITH_SECURITY_STRUCTURED) ||             \\\n          (defined(ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT) && \\\n          defined(AVS_COMMONS_WITH_AVS_CRYPTO_PKI_ENGINE)) */\n\n#if defined(ANJAY_WITH_SECURITY_STRUCTURED)\nint _anjay_sec_init_psk_identity_resource(\n        sec_key_or_data_t *out_resource,\n        sec_key_or_data_type_t type,\n        const avs_crypto_psk_identity_info_t *in_value);\n\nint _anjay_sec_init_psk_key_resource(sec_key_or_data_t *out_resource,\n                                     sec_key_or_data_type_t type,\n                                     const avs_crypto_psk_key_info_t *in_value);\n#endif /* defined(ANJAY_WITH_SECURITY_STRUCTURED) ||             \\\n          (defined(ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT) && \\\n          defined(AVS_COMMONS_WITH_AVS_CRYPTO_PSK_ENGINE)) */\n\nvoid _anjay_sec_key_or_data_cleanup(sec_key_or_data_t *value,\n                                    bool remove_from_engine);\n\n/**\n * Frees all resources held in the @p instance.\n */\nvoid _anjay_sec_destroy_instance_fields(sec_instance_t *instance,\n                                        bool remove_from_engine);\n\n/**\n * Frees all resources held in instances from the @p instances_ptr list,\n * and the list itself.\n */\nvoid _anjay_sec_destroy_instances(AVS_LIST(sec_instance_t) *instances_ptr,\n                                  bool remove_from_engine);\n\n/**\n * Clones all instances of the given Security Object @p repr . Return NULL\n * if either there was nothing to clone or an error has occurred.\n */\nAVS_LIST(sec_instance_t) _anjay_sec_clone_instances(const sec_repr_t *repr);\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif /* SECURITY_UTILS_H */\n"
  },
  {
    "path": "src/modules/server/anjay_mod_server.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#ifdef ANJAY_WITH_MODULE_SERVER\n\n#    include <inttypes.h>\n#    include <string.h>\n\n#    include <anjay/server.h>\n\n#    include <anjay_modules/anjay_bootstrap.h>\n#    include <anjay_modules/anjay_servers.h>\n\n#    include \"anjay_mod_server.h\"\n#    include \"anjay_server_transaction.h\"\n#    include \"anjay_server_utils.h\"\n\nVISIBILITY_SOURCE_BEGIN\n\nstatic const struct {\n    server_rid_t rid;\n    anjay_dm_resource_kind_t kind;\n} SERVER_RESOURCE_INFO[] = {\n    {\n        .rid = SERV_RES_SSID,\n        .kind = ANJAY_DM_RES_R\n    },\n    {\n        .rid = SERV_RES_LIFETIME,\n        .kind = ANJAY_DM_RES_RW\n    },\n    {\n        .rid = SERV_RES_DEFAULT_MIN_PERIOD,\n        .kind = ANJAY_DM_RES_RW\n    },\n    {\n        .rid = SERV_RES_DEFAULT_MAX_PERIOD,\n        .kind = ANJAY_DM_RES_RW\n    },\n#    ifndef ANJAY_WITHOUT_DEREGISTER\n    {\n        .rid = SERV_RES_DISABLE,\n        .kind = ANJAY_DM_RES_E\n    },\n    {\n        .rid = SERV_RES_DISABLE_TIMEOUT,\n        .kind = ANJAY_DM_RES_RW\n    },\n#    endif // ANJAY_WITHOUT_DEREGISTER\n    {\n        .rid = SERV_RES_NOTIFICATION_STORING_WHEN_DISABLED_OR_OFFLINE,\n        .kind = ANJAY_DM_RES_RW\n    },\n    {\n        .rid = SERV_RES_BINDING,\n        .kind = ANJAY_DM_RES_RW\n    },\n    {\n        .rid = SERV_RES_REGISTRATION_UPDATE_TRIGGER,\n        .kind = ANJAY_DM_RES_E\n    },\n#    ifdef ANJAY_WITH_LWM2M11\n    {\n        .rid = SERV_RES_BOOTSTRAP_REQUEST_TRIGGER,\n        .kind = ANJAY_DM_RES_E\n    },\n    {\n        .rid = SERV_RES_TLS_DTLS_ALERT_CODE,\n        .kind = ANJAY_DM_RES_R\n    },\n    {\n        .rid = SERV_RES_LAST_BOOTSTRAPPED,\n        .kind = ANJAY_DM_RES_R\n    },\n    {\n        .rid = SERV_RES_BOOTSTRAP_ON_REGISTRATION_FAILURE,\n        .kind = ANJAY_DM_RES_R\n    },\n    {\n        .rid = SERV_RES_SERVER_COMMUNICATION_RETRY_COUNT,\n        .kind = ANJAY_DM_RES_RW\n    },\n    {\n        .rid = SERV_RES_SERVER_COMMUNICATION_RETRY_TIMER,\n        .kind = ANJAY_DM_RES_RW\n    },\n    {\n        .rid = SERV_RES_SERVER_COMMUNICATION_SEQUENCE_DELAY_TIMER,\n        .kind = ANJAY_DM_RES_RW\n    },\n    {\n        .rid = SERV_RES_SERVER_COMMUNICATION_SEQUENCE_RETRY_COUNT,\n        .kind = ANJAY_DM_RES_RW\n    },\n    {\n        .rid = SERV_RES_PREFERRED_TRANSPORT,\n        .kind = ANJAY_DM_RES_RW\n    },\n#        ifdef ANJAY_WITH_SEND\n    {\n        .rid = SERV_RES_MUTE_SEND,\n        .kind = ANJAY_DM_RES_RW\n    },\n#        endif // ANJAY_WITH_SEND\n#    endif     // ANJAY_WITH_LWM2M11\n};\n\nstatic inline server_instance_t *find_instance(server_repr_t *repr,\n                                               anjay_iid_t iid) {\n    if (!repr) {\n        return NULL;\n    }\n    AVS_LIST(server_instance_t) it;\n    AVS_LIST_FOREACH(it, repr->instances) {\n        if (it->iid == iid) {\n            return it;\n        } else if (it->iid > iid) {\n            break;\n        }\n    }\n    return NULL;\n}\n\nstatic anjay_iid_t get_new_iid(AVS_LIST(server_instance_t) instances) {\n    anjay_iid_t iid = 0;\n    AVS_LIST(server_instance_t) it;\n    AVS_LIST_FOREACH(it, instances) {\n        if (it->iid == iid) {\n            ++iid;\n        } else if (it->iid > iid) {\n            break;\n        }\n    }\n    return iid;\n}\n\nstatic int assign_iid(server_repr_t *repr, anjay_iid_t *inout_iid) {\n    *inout_iid = get_new_iid(repr->instances);\n    if (*inout_iid == ANJAY_ID_INVALID) {\n        return -1;\n    }\n    return 0;\n}\n\nstatic void insert_created_instance(server_repr_t *repr,\n                                    AVS_LIST(server_instance_t) new_instance) {\n    AVS_LIST(server_instance_t) *ptr;\n    AVS_LIST_FOREACH_PTR(ptr, &repr->instances) {\n        assert((*ptr)->iid != new_instance->iid);\n        if ((*ptr)->iid > new_instance->iid) {\n            break;\n        }\n    }\n    _anjay_serv_mark_modified(repr);\n    AVS_LIST_INSERT(ptr, new_instance);\n}\n\nstatic int add_instance(server_repr_t *repr,\n                        const anjay_server_instance_t *instance,\n                        anjay_iid_t *inout_iid) {\n    if (*inout_iid == ANJAY_ID_INVALID) {\n        if (assign_iid(repr, inout_iid)) {\n            return -1;\n        }\n    } else if (find_instance(repr, *inout_iid)) {\n        return -1;\n    }\n    AVS_LIST(server_instance_t) new_instance =\n            AVS_LIST_NEW_ELEMENT(server_instance_t);\n    if (!new_instance) {\n        _anjay_log_oom();\n        return -1;\n    }\n    if (instance->binding) {\n        if (!anjay_binding_mode_valid(instance->binding)\n                || avs_simple_snprintf(new_instance->binding.data,\n                                       sizeof(new_instance->binding.data), \"%s\",\n                                       instance->binding)\n                               < 0) {\n            server_log(ERROR, _(\"Unsupported binding mode: \") \"%s\",\n                       instance->binding);\n            AVS_LIST_CLEAR(&new_instance);\n            return -1;\n        }\n        new_instance->present_resources[SERV_RES_BINDING] = true;\n    }\n    new_instance->iid = *inout_iid;\n    new_instance->present_resources[SERV_RES_SSID] = true;\n    new_instance->ssid = instance->ssid;\n    new_instance->present_resources[SERV_RES_LIFETIME] = true;\n    new_instance->lifetime = instance->lifetime;\n    if (instance->default_min_period >= 0) {\n        new_instance->present_resources[SERV_RES_DEFAULT_MIN_PERIOD] = true;\n        new_instance->default_min_period = instance->default_min_period;\n    }\n\n    if (instance->default_max_period >= 0) {\n        new_instance->present_resources[SERV_RES_DEFAULT_MAX_PERIOD] = true;\n        new_instance->default_max_period = instance->default_max_period;\n    }\n#    ifndef ANJAY_WITHOUT_DEREGISTER\n    new_instance->present_resources[SERV_RES_DISABLE] = true;\n    if (instance->disable_timeout >= 0) {\n        new_instance->present_resources[SERV_RES_DISABLE_TIMEOUT] = true;\n        new_instance->disable_timeout = instance->disable_timeout;\n    }\n#    endif // ANJAY_WITHOUT_DEREGISTER\n    new_instance->present_resources\n            [SERV_RES_NOTIFICATION_STORING_WHEN_DISABLED_OR_OFFLINE] = true;\n    new_instance->notification_storing = instance->notification_storing;\n    new_instance->present_resources[SERV_RES_REGISTRATION_UPDATE_TRIGGER] =\n            true;\n#    ifdef ANJAY_WITH_LWM2M11\n#        ifdef ANJAY_WITH_BOOTSTRAP\n    new_instance->present_resources[SERV_RES_BOOTSTRAP_REQUEST_TRIGGER] = true;\n    new_instance\n            ->present_resources[SERV_RES_BOOTSTRAP_ON_REGISTRATION_FAILURE] =\n            true;\n#        endif // ANJAY_WITH_BOOTSTRAP\n    new_instance->bootstrap_on_registration_failure =\n            instance->bootstrap_on_registration_failure\n                    ? *instance->bootstrap_on_registration_failure\n                    : true;\n    if (instance->communication_retry_count) {\n        new_instance\n                ->present_resources[SERV_RES_SERVER_COMMUNICATION_RETRY_COUNT] =\n                true;\n        new_instance->server_communication_retry_count =\n                *instance->communication_retry_count;\n    }\n    if (instance->communication_retry_timer) {\n        new_instance\n                ->present_resources[SERV_RES_SERVER_COMMUNICATION_RETRY_TIMER] =\n                true;\n        new_instance->server_communication_retry_timer =\n                *instance->communication_retry_timer;\n    }\n    if (instance->preferred_transport) {\n        new_instance->preferred_transport = instance->preferred_transport;\n        new_instance->present_resources[SERV_RES_PREFERRED_TRANSPORT] = true;\n    }\n    if (instance->communication_sequence_retry_count) {\n        new_instance->present_resources\n                [SERV_RES_SERVER_COMMUNICATION_SEQUENCE_RETRY_COUNT] = true;\n        new_instance->server_communication_sequence_retry_count =\n                *instance->communication_sequence_retry_count;\n    }\n    if (instance->communication_sequence_delay_timer) {\n        new_instance->present_resources\n                [SERV_RES_SERVER_COMMUNICATION_SEQUENCE_DELAY_TIMER] = true;\n        new_instance->server_communication_sequence_delay_timer =\n                *instance->communication_sequence_delay_timer;\n    }\n#        ifdef ANJAY_WITH_SEND\n    new_instance->present_resources[SERV_RES_MUTE_SEND] = true;\n    new_instance->mute_send = instance->mute_send;\n#        endif // ANJAY_WITH_SEND\n#    endif     // ANJAY_WITH_LWM2M11\n\n    insert_created_instance(repr, new_instance);\n    server_log(INFO, _(\"Added instance \") \"%u\" _(\" (SSID: \") \"%u\" _(\")\"),\n               *inout_iid, instance->ssid);\n    return 0;\n}\n\nstatic int del_instance(server_repr_t *repr, anjay_iid_t iid) {\n    AVS_LIST(server_instance_t) *it;\n    AVS_LIST_FOREACH_PTR(it, &repr->instances) {\n        if ((*it)->iid == iid) {\n            AVS_LIST_DELETE(it);\n            _anjay_serv_mark_modified(repr);\n            return 0;\n        } else if ((*it)->iid > iid) {\n            break;\n        }\n    }\n\n    assert(0);\n    return ANJAY_ERR_NOT_FOUND;\n}\n\nstatic int serv_list_instances(anjay_unlocked_t *anjay,\n                               const anjay_dm_installed_object_t obj_ptr,\n                               anjay_unlocked_dm_list_ctx_t *ctx) {\n    (void) anjay;\n\n    server_repr_t *repr = _anjay_serv_get(obj_ptr);\n    AVS_LIST(server_instance_t) it;\n    AVS_LIST_FOREACH(it, repr->instances) {\n        _anjay_dm_emit_unlocked(ctx, it->iid);\n    }\n    return 0;\n}\n\nstatic int serv_instance_create(anjay_unlocked_t *anjay,\n                                const anjay_dm_installed_object_t obj_ptr,\n                                anjay_iid_t iid) {\n    (void) anjay;\n    server_repr_t *repr = _anjay_serv_get(obj_ptr);\n    assert(iid != ANJAY_ID_INVALID);\n    AVS_LIST(server_instance_t) created =\n            AVS_LIST_NEW_ELEMENT(server_instance_t);\n    if (!created) {\n        return ANJAY_ERR_INTERNAL;\n    }\n    created->iid = iid;\n    _anjay_serv_reset_instance(created);\n\n    insert_created_instance(repr, created);\n    return 0;\n}\n\nstatic int serv_instance_remove(anjay_unlocked_t *anjay,\n                                const anjay_dm_installed_object_t obj_ptr,\n                                anjay_iid_t iid) {\n    (void) anjay;\n    return del_instance(_anjay_serv_get(obj_ptr), iid);\n}\n\nstatic int serv_instance_reset(anjay_unlocked_t *anjay,\n                               const anjay_dm_installed_object_t obj_ptr,\n                               anjay_iid_t iid) {\n    (void) anjay;\n    server_instance_t *inst = find_instance(_anjay_serv_get(obj_ptr), iid);\n    assert(inst);\n\n    anjay_ssid_t ssid = inst->ssid;\n    _anjay_serv_reset_instance(inst);\n    inst->present_resources[SERV_RES_SSID] = true;\n    inst->ssid = ssid;\n    return 0;\n}\n\nstatic int serv_list_resources(anjay_unlocked_t *anjay,\n                               const anjay_dm_installed_object_t obj_ptr,\n                               anjay_iid_t iid,\n                               anjay_unlocked_dm_resource_list_ctx_t *ctx) {\n    (void) anjay;\n    const server_instance_t *inst =\n            find_instance(_anjay_serv_get(obj_ptr), iid);\n    assert(inst);\n\n    for (size_t resource = 0; resource < AVS_ARRAY_SIZE(SERVER_RESOURCE_INFO);\n         resource++) {\n        anjay_rid_t rid = SERVER_RESOURCE_INFO[resource].rid;\n        _anjay_dm_emit_res_unlocked(ctx, rid,\n                                    SERVER_RESOURCE_INFO[resource].kind,\n                                    inst->present_resources[rid]\n                                            ? ANJAY_DM_RES_PRESENT\n                                            : ANJAY_DM_RES_ABSENT);\n    }\n\n    return 0;\n}\n\nstatic int serv_read(anjay_unlocked_t *anjay,\n                     const anjay_dm_installed_object_t obj_ptr,\n                     anjay_iid_t iid,\n                     anjay_rid_t rid,\n                     anjay_riid_t riid,\n                     anjay_unlocked_output_ctx_t *ctx) {\n    (void) anjay;\n    (void) riid;\n    assert(riid == ANJAY_ID_INVALID);\n\n    const server_instance_t *inst =\n            find_instance(_anjay_serv_get(obj_ptr), iid);\n    assert(inst);\n\n    switch ((server_rid_t) rid) {\n    case SERV_RES_SSID:\n        return _anjay_ret_i64_unlocked(ctx, inst->ssid);\n    case SERV_RES_LIFETIME:\n        return _anjay_ret_i64_unlocked(ctx, inst->lifetime);\n    case SERV_RES_DEFAULT_MIN_PERIOD:\n        return _anjay_ret_i64_unlocked(ctx, inst->default_min_period);\n    case SERV_RES_DEFAULT_MAX_PERIOD:\n        return _anjay_ret_i64_unlocked(ctx, inst->default_max_period);\n#    ifndef ANJAY_WITHOUT_DEREGISTER\n    case SERV_RES_DISABLE_TIMEOUT:\n        return _anjay_ret_i64_unlocked(ctx, inst->disable_timeout);\n#    endif // ANJAY_WITHOUT_DEREGISTER\n    case SERV_RES_NOTIFICATION_STORING_WHEN_DISABLED_OR_OFFLINE:\n        return _anjay_ret_bool_unlocked(ctx, inst->notification_storing);\n    case SERV_RES_BINDING:\n        return _anjay_ret_string_unlocked(ctx, inst->binding.data);\n#    ifdef ANJAY_WITH_LWM2M11\n    case SERV_RES_TLS_DTLS_ALERT_CODE:\n        return _anjay_ret_u64_unlocked(ctx, inst->last_alert);\n    case SERV_RES_LAST_BOOTSTRAPPED:\n        return _anjay_ret_i64_unlocked(ctx, inst->last_bootstrapped_timestamp);\n#        ifdef ANJAY_WITH_BOOTSTRAP\n    case SERV_RES_BOOTSTRAP_ON_REGISTRATION_FAILURE:\n        return _anjay_ret_bool_unlocked(\n                ctx, inst->bootstrap_on_registration_failure);\n#        endif // ANJAY_WITH_BOOTSTRAP\n    case SERV_RES_SERVER_COMMUNICATION_RETRY_COUNT:\n        return _anjay_ret_u64_unlocked(ctx,\n                                       inst->server_communication_retry_count);\n    case SERV_RES_SERVER_COMMUNICATION_RETRY_TIMER:\n        return _anjay_ret_u64_unlocked(ctx,\n                                       inst->server_communication_retry_timer);\n    case SERV_RES_SERVER_COMMUNICATION_SEQUENCE_RETRY_COUNT:\n        return _anjay_ret_u64_unlocked(\n                ctx, inst->server_communication_sequence_retry_count);\n    case SERV_RES_SERVER_COMMUNICATION_SEQUENCE_DELAY_TIMER:\n        return _anjay_ret_u64_unlocked(\n                ctx, inst->server_communication_sequence_delay_timer);\n    case SERV_RES_PREFERRED_TRANSPORT: {\n        char tmp[2] = { inst->preferred_transport, '\\0' };\n        return _anjay_ret_string_unlocked(ctx, tmp);\n    }\n#        ifdef ANJAY_WITH_SEND\n    case SERV_RES_MUTE_SEND:\n        return _anjay_ret_bool_unlocked(ctx, inst->mute_send);\n#        endif // ANJAY_WITH_SEND\n#    endif     // ANJAY_WITH_LWM2M11\n    default:\n        AVS_UNREACHABLE(\n                \"Read called on unknown or non-readable Server resource\");\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\nstatic int serv_write(anjay_unlocked_t *anjay,\n                      const anjay_dm_installed_object_t obj_ptr,\n                      anjay_iid_t iid,\n                      anjay_rid_t rid,\n                      anjay_riid_t riid,\n                      anjay_unlocked_input_ctx_t *ctx) {\n    (void) anjay;\n    (void) riid;\n    assert(riid == ANJAY_ID_INVALID);\n\n    server_repr_t *repr = _anjay_serv_get(obj_ptr);\n    server_instance_t *inst = find_instance(repr, iid);\n    assert(inst);\n    int retval;\n\n    _anjay_serv_mark_modified(repr);\n\n    switch ((server_rid_t) rid) {\n    case SERV_RES_SSID:\n        retval = _anjay_serv_fetch_ssid(ctx, &inst->ssid);\n        break;\n    case SERV_RES_LIFETIME:\n        retval = _anjay_get_i32_unlocked(ctx, &inst->lifetime);\n        break;\n    case SERV_RES_DEFAULT_MIN_PERIOD:\n        retval = _anjay_serv_fetch_validated_i32(ctx, 0, INT32_MAX,\n                                                 &inst->default_min_period);\n        break;\n    case SERV_RES_DEFAULT_MAX_PERIOD:\n        retval = _anjay_serv_fetch_validated_i32(ctx, 1, INT32_MAX,\n                                                 &inst->default_max_period);\n        break;\n#    ifndef ANJAY_WITHOUT_DEREGISTER\n    case SERV_RES_DISABLE_TIMEOUT:\n        retval = _anjay_serv_fetch_validated_i32(ctx, 0, INT32_MAX,\n                                                 &inst->disable_timeout);\n        break;\n#    endif // ANJAY_WITHOUT_DEREGISTER\n    case SERV_RES_BINDING:\n        retval = _anjay_serv_fetch_binding(ctx, &inst->binding);\n        break;\n    case SERV_RES_NOTIFICATION_STORING_WHEN_DISABLED_OR_OFFLINE:\n        retval = _anjay_get_bool_unlocked(ctx, &inst->notification_storing);\n        break;\n#    ifdef ANJAY_WITH_LWM2M11\n    case SERV_RES_TLS_DTLS_ALERT_CODE: {\n        uint32_t last_alert;\n        if (!(retval = _anjay_get_u32_unlocked(ctx, &last_alert))) {\n            inst->last_alert = (uint8_t) last_alert;\n        }\n        break;\n    }\n    case SERV_RES_LAST_BOOTSTRAPPED:\n        retval = _anjay_get_i64_unlocked(ctx,\n                                         &inst->last_bootstrapped_timestamp);\n        break;\n#        ifdef ANJAY_WITH_BOOTSTRAP\n    case SERV_RES_BOOTSTRAP_ON_REGISTRATION_FAILURE:\n        retval = _anjay_get_bool_unlocked(\n                ctx, &inst->bootstrap_on_registration_failure);\n        break;\n#        endif // ANJAY_WITH_BOOTSTRAP\n    case SERV_RES_SERVER_COMMUNICATION_RETRY_COUNT:\n        if (!(retval = _anjay_get_u32_unlocked(\n                      ctx, &inst->server_communication_retry_count))\n                && inst->server_communication_retry_count == 0) {\n            server_log(ERROR, \"Server Communication Retry Count cannot be 0\");\n            retval = ANJAY_ERR_BAD_REQUEST;\n        }\n        break;\n    case SERV_RES_SERVER_COMMUNICATION_RETRY_TIMER:\n        retval = _anjay_get_u32_unlocked(\n                ctx, &inst->server_communication_retry_timer);\n        break;\n    case SERV_RES_SERVER_COMMUNICATION_SEQUENCE_RETRY_COUNT:\n        if (!(retval = _anjay_get_u32_unlocked(\n                      ctx, &inst->server_communication_sequence_retry_count))\n                && inst->server_communication_sequence_retry_count == 0) {\n            server_log(ERROR,\n                       \"Server Sequence Communication Retry Count cannot be 0\");\n            retval = ANJAY_ERR_BAD_REQUEST;\n        }\n        break;\n    case SERV_RES_SERVER_COMMUNICATION_SEQUENCE_DELAY_TIMER:\n        retval = _anjay_get_u32_unlocked(\n                ctx, &inst->server_communication_sequence_delay_timer);\n        break;\n    case SERV_RES_PREFERRED_TRANSPORT: {\n        char tmp[2];\n        if (!(retval = _anjay_get_string_unlocked(ctx, tmp, sizeof(tmp)))) {\n            inst->preferred_transport = tmp[0];\n        } else if (retval == ANJAY_BUFFER_TOO_SHORT) {\n            retval = ANJAY_ERR_BAD_REQUEST;\n        }\n        break;\n    }\n#        ifdef ANJAY_WITH_SEND\n    case SERV_RES_MUTE_SEND:\n        retval = _anjay_get_bool_unlocked(ctx, &inst->mute_send);\n        break;\n#        endif // ANJAY_WITH_SEND\n#    endif     // ANJAY_WITH_LWM2M11\n    default:\n        AVS_UNREACHABLE(\n                \"Write called on unknown or non-read/writable Server resource\");\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n\n    if (!retval) {\n        inst->present_resources[rid] = true;\n    }\n\n    return retval;\n}\n\nstatic int serv_execute(anjay_unlocked_t *anjay,\n                        const anjay_dm_installed_object_t obj_ptr,\n                        anjay_iid_t iid,\n                        anjay_rid_t rid,\n                        anjay_unlocked_execute_ctx_t *ctx) {\n    (void) anjay;\n    (void) obj_ptr;\n    (void) ctx;\n    server_instance_t *inst = find_instance(_anjay_serv_get(obj_ptr), iid);\n    assert(inst);\n\n    switch ((server_rid_t) rid) {\n#    ifndef ANJAY_WITHOUT_DEREGISTER\n    case SERV_RES_DISABLE: {\n        avs_time_duration_t disable_timeout = avs_time_duration_from_scalar(\n                inst->present_resources[SERV_RES_DISABLE_TIMEOUT]\n                        ? inst->disable_timeout\n                        : 86400,\n                AVS_TIME_S);\n        return _anjay_schedule_disable_server_with_explicit_timeout_unlocked(\n                anjay, inst->ssid, disable_timeout);\n    }\n#    endif // ANJAY_WITHOUT_DEREGISTER\n    case SERV_RES_REGISTRATION_UPDATE_TRIGGER:\n        return _anjay_schedule_registration_update_unlocked(anjay, inst->ssid)\n                       ? ANJAY_ERR_BAD_REQUEST\n                       : 0;\n#    if defined(ANJAY_WITH_LWM2M11) && defined(ANJAY_WITH_BOOTSTRAP)\n    case SERV_RES_BOOTSTRAP_REQUEST_TRIGGER:\n        return _anjay_schedule_bootstrap_request_unlocked(anjay)\n                       ? ANJAY_ERR_METHOD_NOT_ALLOWED\n                       : 0;\n#    endif // defined(ANJAY_WITH_LWM2M11) && defined(ANJAY_WITH_BOOTSTRAP)\n    default:\n        AVS_UNREACHABLE(\n                \"Execute called on unknown or non-executable Server resource\");\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\nstatic int serv_transaction_begin(anjay_unlocked_t *anjay,\n                                  const anjay_dm_installed_object_t obj_ptr) {\n    (void) anjay;\n    return _anjay_serv_transaction_begin_impl(_anjay_serv_get(obj_ptr));\n}\n\nstatic int serv_transaction_commit(anjay_unlocked_t *anjay,\n                                   const anjay_dm_installed_object_t obj_ptr) {\n    (void) anjay;\n    return _anjay_serv_transaction_commit_impl(_anjay_serv_get(obj_ptr));\n}\n\nstatic int\nserv_transaction_validate(anjay_unlocked_t *anjay,\n                          const anjay_dm_installed_object_t obj_ptr) {\n    (void) anjay;\n    return _anjay_serv_transaction_validate_impl(_anjay_serv_get(obj_ptr));\n}\n\nstatic int\nserv_transaction_rollback(anjay_unlocked_t *anjay,\n                          const anjay_dm_installed_object_t obj_ptr) {\n    (void) anjay;\n    return _anjay_serv_transaction_rollback_impl(_anjay_serv_get(obj_ptr));\n}\n\nstatic const anjay_unlocked_dm_object_def_t SERVER = {\n    .oid = ANJAY_DM_OID_SERVER,\n    .handlers = {\n        .list_instances = serv_list_instances,\n        .instance_create = serv_instance_create,\n        .instance_remove = serv_instance_remove,\n        .instance_reset = serv_instance_reset,\n        .list_resources = serv_list_resources,\n        .resource_read = serv_read,\n        .resource_write = serv_write,\n        .resource_execute = serv_execute,\n        .transaction_begin = serv_transaction_begin,\n        .transaction_validate = serv_transaction_validate,\n        .transaction_commit = serv_transaction_commit,\n        .transaction_rollback = serv_transaction_rollback\n    }\n};\n\nserver_repr_t *_anjay_serv_get(const anjay_dm_installed_object_t obj_ptr) {\n    const anjay_unlocked_dm_object_def_t *const *unlocked_def =\n            _anjay_dm_installed_object_get_unlocked(&obj_ptr);\n    assert(*unlocked_def == &SERVER);\n    return AVS_CONTAINER_OF(unlocked_def, server_repr_t, def);\n}\n\nint anjay_server_object_add_instance(anjay_t *anjay_locked,\n                                     const anjay_server_instance_t *instance,\n                                     anjay_iid_t *inout_iid) {\n    assert(anjay_locked);\n    int retval = -1;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    const anjay_dm_installed_object_t *obj_ptr =\n            _anjay_dm_find_object_by_oid(_anjay_get_dm(anjay), SERVER.oid);\n    server_repr_t *repr = obj_ptr ? _anjay_serv_get(*obj_ptr) : NULL;\n    if (!repr) {\n        server_log(ERROR, _(\"Server object is not registered\"));\n        retval = -1;\n    } else {\n        const bool modified_since_persist = repr->modified_since_persist;\n        if (!(retval = add_instance(repr, instance, inout_iid))\n                && (retval = _anjay_serv_object_validate(repr))) {\n            (void) del_instance(repr, *inout_iid);\n            if (!modified_since_persist) {\n                /* validation failed and so in the end no instace is added */\n                _anjay_serv_clear_modified(repr);\n            }\n        }\n\n        if (!retval) {\n            if (_anjay_notify_instances_changed_unlocked(anjay, SERVER.oid)) {\n                server_log(WARNING, _(\"Could not schedule socket reload\"));\n            }\n        }\n    }\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return retval;\n}\n\nstatic void server_purge(server_repr_t *repr) {\n    if (repr->instances) {\n        _anjay_serv_mark_modified(repr);\n    }\n    _anjay_serv_destroy_instances(&repr->instances);\n    _anjay_serv_destroy_instances(&repr->saved_instances);\n}\n\nstatic void server_delete(void *repr) {\n    server_purge((server_repr_t *) repr);\n    // NOTE: repr itself will be freed when cleaning the objects list\n}\n\nvoid anjay_server_object_purge(anjay_t *anjay_locked) {\n    assert(anjay_locked);\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    const anjay_dm_installed_object_t *server_obj =\n            _anjay_dm_find_object_by_oid(_anjay_get_dm(anjay), SERVER.oid);\n    server_repr_t *repr = server_obj ? _anjay_serv_get(*server_obj) : NULL;\n\n    if (!repr) {\n        server_log(ERROR, _(\"Server object is not registered\"));\n    } else {\n        server_purge(repr);\n        if (_anjay_notify_instances_changed_unlocked(anjay, SERVER.oid)) {\n            server_log(WARNING, _(\"Could not schedule socket reload\"));\n        }\n    }\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n}\n\nAVS_LIST(const anjay_ssid_t) anjay_server_get_ssids(anjay_t *anjay_locked) {\n    assert(anjay_locked);\n    AVS_LIST(server_instance_t) source = NULL;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    const anjay_dm_installed_object_t *server_obj =\n            _anjay_dm_find_object_by_oid(_anjay_get_dm(anjay), SERVER.oid);\n    server_repr_t *repr = _anjay_serv_get(*server_obj);\n    if (_anjay_dm_transaction_object_included(anjay, server_obj)) {\n        source = repr->saved_instances;\n    } else {\n        source = repr->instances;\n    }\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    // We rely on the fact that the \"ssid\" field is first in server_instance_t,\n    // which means that both \"source\" and \"&source->ssid\" point to exactly the\n    // same memory location. The \"next\" pointer location in AVS_LIST is\n    // independent from the stored data type, so it's safe to do such \"cast\".\n    AVS_STATIC_ASSERT(offsetof(server_instance_t, ssid) == 0,\n                      instance_ssid_is_first_field);\n    return &source->ssid;\n}\n\nbool anjay_server_object_is_modified(anjay_t *anjay_locked) {\n    assert(anjay_locked);\n    bool result = false;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    const anjay_dm_installed_object_t *server_obj =\n            _anjay_dm_find_object_by_oid(_anjay_get_dm(anjay), SERVER.oid);\n    if (!server_obj) {\n        server_log(ERROR, _(\"Server object is not registered\"));\n    } else {\n        server_repr_t *repr = _anjay_serv_get(*server_obj);\n        if (repr->in_transaction) {\n            result = repr->saved_modified_since_persist;\n        } else {\n            result = repr->modified_since_persist;\n        }\n    }\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return result;\n}\n\nint anjay_server_object_install(anjay_t *anjay_locked) {\n    assert(anjay_locked);\n    int result = -1;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    AVS_LIST(server_repr_t) repr = AVS_LIST_NEW_ELEMENT(server_repr_t);\n    if (!repr) {\n        _anjay_log_oom();\n    } else {\n        repr->def = &SERVER;\n        _anjay_dm_installed_object_init_unlocked(&repr->def_ptr, &repr->def);\n        if (!_anjay_dm_module_install(anjay, server_delete, repr)) {\n            _ANJAY_ASSERT_INSTALLED_OBJECT_IS_FIRST_FIELD(server_repr_t,\n                                                          def_ptr);\n            AVS_LIST(anjay_dm_installed_object_t) entry = &repr->def_ptr;\n            if (_anjay_register_object_unlocked(anjay, &entry)) {\n                result = _anjay_dm_module_uninstall(anjay, server_delete);\n                assert(!result);\n                result = -1;\n            } else {\n                result = 0;\n            }\n        }\n        if (result) {\n            AVS_LIST_CLEAR(&repr);\n        }\n    }\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return result;\n}\n\nint anjay_server_object_set_lifetime(anjay_t *anjay_locked,\n                                     anjay_iid_t iid,\n                                     int32_t lifetime) {\n    if (lifetime <= 0) {\n        server_log(ERROR, _(\"lifetime MUST BE strictly positive\"));\n        return -1;\n    }\n    assert(anjay_locked);\n    int result = -1;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    const anjay_dm_installed_object_t *server_obj =\n            _anjay_dm_find_object_by_oid(_anjay_get_dm(anjay), SERVER.oid);\n    server_repr_t *repr = _anjay_serv_get(*server_obj);\n    if (repr->saved_instances) {\n        server_log(ERROR, _(\"cannot set Lifetime while some transaction is \"\n                            \"started on the Server Object\"));\n    } else {\n        AVS_LIST(server_instance_t) it;\n        AVS_LIST_FOREACH(it, repr->instances) {\n            if (it->iid >= iid) {\n                break;\n            }\n        }\n\n        if (!it || it->iid != iid) {\n            server_log(ERROR, _(\"instance \") \"%\" PRIu16 _(\" not found\"), iid);\n        } else if (it->lifetime != lifetime) {\n            if (_anjay_notify_changed_unlocked(anjay, ANJAY_DM_OID_SERVER,\n                                               it->iid,\n                                               ANJAY_DM_RID_SERVER_LIFETIME)) {\n                server_log(WARNING, _(\"could not notify lifetime change\"));\n            }\n            repr->modified_since_persist = true;\n            it->lifetime = lifetime;\n            result = 0;\n        }\n    }\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return result;\n}\n\n#    ifdef ANJAY_TEST\n#        include \"tests/modules/server/api.c\"\n#    endif\n\n#endif // ANJAY_WITH_MODULE_SERVER\n"
  },
  {
    "path": "src/modules/server/anjay_mod_server.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef SERVER_MOD_SERVER_H\n#define SERVER_MOD_SERVER_H\n#include <anjay_init.h>\n\n#include <anjay_modules/anjay_utils_core.h>\n#include <anjay_modules/dm/anjay_modules.h>\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\ntypedef enum {\n    SERV_RES_SSID = 0,\n    SERV_RES_LIFETIME = 1,\n    SERV_RES_DEFAULT_MIN_PERIOD = 2,\n    SERV_RES_DEFAULT_MAX_PERIOD = 3,\n#ifndef ANJAY_WITHOUT_DEREGISTER\n    SERV_RES_DISABLE = 4,\n    SERV_RES_DISABLE_TIMEOUT = 5,\n#endif // ANJAY_WITHOUT_DEREGISTER\n    SERV_RES_NOTIFICATION_STORING_WHEN_DISABLED_OR_OFFLINE = 6,\n    SERV_RES_BINDING = 7,\n    SERV_RES_REGISTRATION_UPDATE_TRIGGER = 8,\n#ifdef ANJAY_WITH_LWM2M11\n    SERV_RES_BOOTSTRAP_REQUEST_TRIGGER = 9,\n    SERV_RES_TLS_DTLS_ALERT_CODE = 11,\n    SERV_RES_LAST_BOOTSTRAPPED = 12,\n    SERV_RES_BOOTSTRAP_ON_REGISTRATION_FAILURE = 16,\n    SERV_RES_SERVER_COMMUNICATION_RETRY_COUNT = 17,\n    SERV_RES_SERVER_COMMUNICATION_RETRY_TIMER = 18,\n    SERV_RES_SERVER_COMMUNICATION_SEQUENCE_DELAY_TIMER = 19,\n    SERV_RES_SERVER_COMMUNICATION_SEQUENCE_RETRY_COUNT = 20,\n    SERV_RES_PREFERRED_TRANSPORT = 22,\n#    ifdef ANJAY_WITH_SEND\n    SERV_RES_MUTE_SEND = 23,\n#    endif // ANJAY_WITH_SEND\n#endif     // ANJAY_WITH_LWM2M11\n    _SERV_RES_COUNT\n} server_rid_t;\n\ntypedef struct {\n    /* mandatory resources */\n    anjay_ssid_t ssid;\n    anjay_binding_mode_t binding;\n    int32_t lifetime;\n    int32_t default_min_period;\n    int32_t default_max_period;\n#ifndef ANJAY_WITHOUT_DEREGISTER\n    int32_t disable_timeout;\n#endif // ANJAY_WITHOUT_DEREGISTER\n    bool notification_storing;\n\n    anjay_iid_t iid;\n\n#ifdef ANJAY_WITH_LWM2M11\n    int64_t last_bootstrapped_timestamp;\n    uint8_t last_alert;\n    bool bootstrap_on_registration_failure;\n    uint32_t server_communication_retry_count;\n    uint32_t server_communication_retry_timer;\n    uint32_t server_communication_sequence_retry_count;\n    uint32_t server_communication_sequence_delay_timer;\n    char preferred_transport;\n#    ifdef ANJAY_WITH_SEND\n    bool mute_send;\n#    endif // ANJAY_WITH_SEND\n#endif     // ANJAY_WITH_LWM2M11\n\n    bool present_resources[_SERV_RES_COUNT];\n} server_instance_t;\n\ntypedef struct {\n    anjay_dm_installed_object_t def_ptr;\n    const anjay_unlocked_dm_object_def_t *def;\n    AVS_LIST(server_instance_t) instances;\n    AVS_LIST(server_instance_t) saved_instances;\n    bool modified_since_persist;\n    bool saved_modified_since_persist;\n    bool in_transaction;\n} server_repr_t;\n\nstatic inline void _anjay_serv_mark_modified(server_repr_t *repr) {\n    repr->modified_since_persist = true;\n}\n\nstatic inline void _anjay_serv_clear_modified(server_repr_t *repr) {\n    repr->modified_since_persist = false;\n}\n\n#define server_log(level, ...) _anjay_log(server, level, __VA_ARGS__)\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif /* SERVER_MOD_SERVER_H */\n"
  },
  {
    "path": "src/modules/server/anjay_server_persistence.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#ifdef ANJAY_WITH_MODULE_SERVER\n\n#    ifdef AVS_COMMONS_WITH_AVS_PERSISTENCE\n#        include <avsystem/commons/avs_persistence.h>\n#    endif // AVS_COMMONS_WITH_AVS_PERSISTENCE\n#    include <avsystem/commons/avs_utils.h>\n\n#    include <anjay/server.h>\n\n#    include <anjay_modules/anjay_dm_utils.h>\n\n#    include <inttypes.h>\n#    include <string.h>\n\n#    include \"anjay_server_transaction.h\"\n#    include \"anjay_server_utils.h\"\n\nVISIBILITY_SOURCE_BEGIN\n\n#    define persistence_log(level, ...) \\\n        _anjay_log(server_persistence, level, __VA_ARGS__)\n\n#    ifdef AVS_COMMONS_WITH_AVS_PERSISTENCE\n\ntypedef enum {\n    PERSISTENCE_VERSION_0,\n\n    /** Binding resource as string instead of enum */\n    PERSISTENCE_VERSION_1,\n\n    /**\n     * New resources:\n     * - 11: TLS-DTLS Alert Code\n     * - 12: Last Bootstrapped\n     * - 16: Bootstrap on Registration Failure\n     * - 17: Communication Retry Count\n     * - 18: Communication Retry Timer\n     * - 19: Communication Sequence Delay Timer\n     * - 20: Communication Sequence Retry Count\n     * - 22: Preferred Transport\n     * - 23: Mute Send\n     */\n    PERSISTENCE_VERSION_2,\n\n    /**\n     * New resource: Trigger\n     */\n    PERSISTENCE_VERSION_3\n} server_persistence_version_t;\n\ntypedef char magic_t[4];\nstatic const magic_t MAGIC_V0 = { 'S', 'R', 'V', PERSISTENCE_VERSION_0 };\nstatic const magic_t MAGIC_V1 = { 'S', 'R', 'V', PERSISTENCE_VERSION_1 };\nstatic const magic_t MAGIC_V2 = { 'S', 'R', 'V', PERSISTENCE_VERSION_2 };\nstatic const magic_t MAGIC_V3 = { 'S', 'R', 'V', PERSISTENCE_VERSION_3 };\n\nstatic avs_error_t handle_v0_v1_sized_fields(avs_persistence_context_t *ctx,\n                                             server_instance_t *element) {\n    assert(avs_persistence_direction(ctx) == AVS_PERSISTENCE_RESTORE);\n    avs_error_t err;\n    (void) (avs_is_err((err = avs_persistence_u16(ctx, &element->iid)))\n            || avs_is_err((err = avs_persistence_bool(\n                                   ctx,\n                                   &element->present_resources[SERV_RES_SSID])))\n            || avs_is_err(\n                       (err = avs_persistence_bool(\n                                ctx,\n                                &element->present_resources[SERV_RES_BINDING])))\n            || avs_is_err((\n                       err = avs_persistence_bool(\n                               ctx,\n                               &element->present_resources[SERV_RES_LIFETIME])))\n            || avs_is_err((\n                       err = avs_persistence_bool(\n                               ctx,\n                               &element->present_resources\n                                        [SERV_RES_NOTIFICATION_STORING_WHEN_DISABLED_OR_OFFLINE])))\n            || avs_is_err((err = avs_persistence_u16(ctx, &element->ssid)))\n            || avs_is_err((err = avs_persistence_u32(\n                                   ctx, (uint32_t *) &element->lifetime)))\n            || avs_is_err((\n                       err = avs_persistence_u32(\n                               ctx, (uint32_t *) &element->default_min_period)))\n            || avs_is_err((\n                       err = avs_persistence_u32(\n                               ctx, (uint32_t *) &element->default_max_period)))\n            || avs_is_err((err = avs_persistence_u32(\n                                   ctx,\n#        ifndef ANJAY_WITHOUT_DEREGISTER\n                                   (uint32_t *) &element->disable_timeout\n#        else  // ANJAY_WITHOUT_DEREGISTER\n                                   (uint32_t *) &(int32_t) { -1 }\n#        endif // ANJAY_WITHOUT_DEREGISTER\n                                   )))\n            || avs_is_err((err = avs_persistence_bool(\n                                   ctx, &element->notification_storing))));\n    if (avs_is_ok(err)) {\n        element->present_resources[SERV_RES_DEFAULT_MIN_PERIOD] =\n                (element->default_min_period >= 0);\n        element->present_resources[SERV_RES_DEFAULT_MAX_PERIOD] =\n                (element->default_max_period >= 0);\n#        ifndef ANJAY_WITHOUT_DEREGISTER\n        element->present_resources[SERV_RES_DISABLE_TIMEOUT] =\n                (element->disable_timeout >= 0);\n#        endif // ANJAY_WITHOUT_DEREGISTER\n        element->present_resources\n                [SERV_RES_NOTIFICATION_STORING_WHEN_DISABLED_OR_OFFLINE] = true;\n    }\n    return err;\n}\n\nstatic avs_error_t\nhandle_v2_lwm2m11_sized_fields(avs_persistence_context_t *ctx,\n                               server_instance_t *element) {\n#        ifndef ANJAY_WITH_LWM2M11\n    enum {\n        SERV_RES_TLS_DTLS_ALERT_CODE,\n        SERV_RES_LAST_BOOTSTRAPPED,\n        SERV_RES_SERVER_COMMUNICATION_RETRY_COUNT,\n        SERV_RES_SERVER_COMMUNICATION_RETRY_TIMER,\n        SERV_RES_SERVER_COMMUNICATION_SEQUENCE_RETRY_COUNT,\n        SERV_RES_SERVER_COMMUNICATION_SEQUENCE_DELAY_TIMER,\n        SERV_RES_PREFERRED_TRANSPORT,\n        _FAKE_RESOURCES_NUM\n    };\n\n    (void) element;\n    struct {\n        int64_t last_bootstrapped_timestamp;\n        uint8_t last_alert;\n        bool bootstrap_on_registration_failure;\n        uint32_t server_communication_retry_count;\n        uint32_t server_communication_retry_timer;\n        uint32_t server_communication_sequence_retry_count;\n        uint32_t server_communication_sequence_delay_timer;\n        char preferred_transport;\n        bool mute_send;\n\n        bool present_resources[_FAKE_RESOURCES_NUM];\n    } dummy_element = {\n        .bootstrap_on_registration_failure = true\n    };\n#            define element (&dummy_element)\n#        endif // ANJAY_WITH_LWM2M11\n\n    avs_error_t err;\n    (void) (avs_is_err((err = avs_persistence_bool(\n                                ctx,\n                                &element->present_resources\n                                         [SERV_RES_TLS_DTLS_ALERT_CODE])))\n            || avs_is_err((err = avs_persistence_u8(ctx, &element->last_alert)))\n            || avs_is_err((err = avs_persistence_bool(\n                                   ctx,\n                                   &element->present_resources\n                                            [SERV_RES_LAST_BOOTSTRAPPED])))\n            || avs_is_err((err = avs_persistence_i64(\n                                   ctx, &element->last_bootstrapped_timestamp)))\n            || avs_is_err(\n                       (err = avs_persistence_bool(\n                                ctx,\n                                &element->bootstrap_on_registration_failure)))\n            || avs_is_err((\n                       err = avs_persistence_bool(\n                               ctx,\n                               &element->present_resources\n                                        [SERV_RES_SERVER_COMMUNICATION_RETRY_COUNT])))\n            || avs_is_err((err = avs_persistence_u32(\n                                   ctx,\n                                   &element->server_communication_retry_count)))\n            || avs_is_err((\n                       err = avs_persistence_bool(\n                               ctx,\n                               &element->present_resources\n                                        [SERV_RES_SERVER_COMMUNICATION_RETRY_TIMER])))\n            || avs_is_err((err = avs_persistence_u32(\n                                   ctx,\n\n                                   &element->server_communication_retry_timer)))\n            || avs_is_err((\n                       err = avs_persistence_bool(\n                               ctx,\n                               &element->present_resources\n                                        [SERV_RES_SERVER_COMMUNICATION_SEQUENCE_DELAY_TIMER])))\n            || avs_is_err((\n                       err = avs_persistence_u32(\n                               ctx,\n                               &element->server_communication_sequence_delay_timer)))\n            || avs_is_err((\n                       err = avs_persistence_bool(\n                               ctx,\n                               &element->present_resources\n                                        [SERV_RES_SERVER_COMMUNICATION_SEQUENCE_RETRY_COUNT])))\n            || avs_is_err((\n                       err = avs_persistence_u32(\n                               ctx,\n                               &element->server_communication_sequence_retry_count)))\n            || avs_is_err((\n                       err = avs_persistence_u8(\n                               ctx, (uint8_t *) &element->preferred_transport)))\n            || avs_is_err((err = avs_persistence_bool(ctx,\n#        ifdef ANJAY_WITH_SEND\n                                                      &element->mute_send\n#        else  // ANJAY_WITH_SEND\n                                                      &(bool) { false }\n#        endif // ANJAY_WITH_SEND\n                                                      ))));\n    if (avs_is_ok(err)) {\n        element->present_resources[SERV_RES_PREFERRED_TRANSPORT] =\n                !!element->preferred_transport;\n    }\n    return err;\n#        undef element\n}\n\nstatic avs_error_t handle_v2_sized_fields(avs_persistence_context_t *ctx,\n                                          server_instance_t *element) {\n    avs_error_t err;\n    (void) (avs_is_err((err = avs_persistence_u16(ctx, &element->iid)))\n            || avs_is_err((err = avs_persistence_bool(\n                                   ctx,\n                                   &element->present_resources[SERV_RES_SSID])))\n            || avs_is_err(\n                       (err = avs_persistence_bool(\n                                ctx,\n                                &element->present_resources[SERV_RES_BINDING])))\n            || avs_is_err((\n                       err = avs_persistence_bool(\n                               ctx,\n                               &element->present_resources[SERV_RES_LIFETIME])))\n            || avs_is_err((err = avs_persistence_bool(\n                                   ctx,\n                                   &element->present_resources\n                                            [SERV_RES_DEFAULT_MIN_PERIOD])))\n            || avs_is_err((err = avs_persistence_bool(\n                                   ctx,\n                                   &element->present_resources\n                                            [SERV_RES_DEFAULT_MAX_PERIOD])))\n            || avs_is_err((err = avs_persistence_bool(\n                                   ctx,\n#        ifndef ANJAY_WITHOUT_DEREGISTER\n                                   &element->present_resources\n                                            [SERV_RES_DISABLE_TIMEOUT]\n#        else  // ANJAY_WITHOUT_DEREGISTER\n                                   &(bool) { false }\n#        endif // ANJAY_WITHOUT_DEREGISTER\n                                   )))\n            || avs_is_err((\n                       err = avs_persistence_bool(\n                               ctx,\n                               &element->present_resources\n                                        [SERV_RES_NOTIFICATION_STORING_WHEN_DISABLED_OR_OFFLINE])))\n            || avs_is_err((err = avs_persistence_u16(ctx, &element->ssid)))\n            || avs_is_err((err = avs_persistence_u32(\n                                   ctx, (uint32_t *) &element->lifetime)))\n            || avs_is_err((\n                       err = avs_persistence_u32(\n                               ctx, (uint32_t *) &element->default_min_period)))\n            || avs_is_err((\n                       err = avs_persistence_u32(\n                               ctx, (uint32_t *) &element->default_max_period)))\n            || avs_is_err((err = avs_persistence_u32(\n                                   ctx,\n#        ifndef ANJAY_WITHOUT_DEREGISTER\n                                   (uint32_t *) &element->disable_timeout\n#        else  // ANJAY_WITHOUT_DEREGISTER\n                                   (uint32_t *) &(int32_t) { -1 }\n#        endif // ANJAY_WITHOUT_DEREGISTER\n                                   )))\n            || avs_is_err((err = avs_persistence_bool(\n                                   ctx, &element->notification_storing)))\n            || avs_is_err(\n                       (err = handle_v2_lwm2m11_sized_fields(ctx, element))));\n    return err;\n}\n\nstatic avs_error_t handle_v3_sized_fields(avs_persistence_context_t *ctx,\n                                          server_instance_t *element) {\n    avs_error_t err;\n    (void) (avs_is_err((err = handle_v2_sized_fields(ctx, element)))\n            || avs_is_err((err = avs_persistence_bool(ctx, &(bool) { false })))\n            || avs_is_err(\n                       (err = avs_persistence_bool(ctx, &(bool) { false }))));\n    return err;\n}\n\nstatic avs_error_t handle_v1_v2_v3_binding_mode(avs_persistence_context_t *ctx,\n                                                server_instance_t *element) {\n    avs_error_t err = avs_persistence_bytes(ctx, element->binding.data,\n                                            sizeof(element->binding.data));\n    if (avs_is_err(err)) {\n        return err;\n    }\n    if (!memchr(element->binding.data, '\\0', sizeof(element->binding.data))\n            || !anjay_binding_mode_valid(element->binding.data)) {\n        return avs_errno(AVS_EBADMSG);\n    }\n    return AVS_OK;\n}\n\nstatic avs_error_t restore_v0_binding_mode(avs_persistence_context_t *ctx,\n                                           server_instance_t *element) {\n    assert(avs_persistence_direction(ctx) == AVS_PERSISTENCE_RESTORE);\n    uint32_t binding;\n    avs_error_t err = avs_persistence_u32(ctx, &binding);\n    if (avs_is_err(err)) {\n        return err;\n    }\n\n    enum {\n        V0_BINDING_NONE,\n        V0_BINDING_U,\n        V0_BINDING_UQ,\n        V0_BINDING_S,\n        V0_BINDING_SQ,\n        V0_BINDING_US,\n        V0_BINDING_UQS\n    };\n\n    const char *binding_str = \"\";\n    switch (binding) {\n    case V0_BINDING_NONE:\n        binding_str = \"\";\n        break;\n    case V0_BINDING_U:\n        binding_str = \"U\";\n        break;\n    case V0_BINDING_UQ:\n        binding_str = \"UQ\";\n        break;\n    case V0_BINDING_S:\n        binding_str = \"S\";\n        break;\n    case V0_BINDING_SQ:\n        binding_str = \"SQ\";\n        break;\n    case V0_BINDING_US:\n        binding_str = \"US\";\n        break;\n    case V0_BINDING_UQS:\n        binding_str = \"UQS\";\n        break;\n    default:\n        persistence_log(WARNING, _(\"Invalid binding mode: \") \"%\" PRIu32,\n                        binding);\n        err = avs_errno(AVS_EBADMSG);\n        break;\n    }\n    if (avs_is_ok(err)\n            && avs_simple_snprintf(element->binding.data,\n                                   sizeof(element->binding.data), \"%s\",\n                                   binding_str)\n                           < 0) {\n        persistence_log(WARNING, _(\"Could not restore binding: \") \"%s\",\n                        binding_str);\n        err = avs_errno(AVS_EBADMSG);\n    }\n    return err;\n}\n\nstatic avs_error_t server_instance_persistence_handler(\n        avs_persistence_context_t *ctx, void *element_, void *version_) {\n    server_instance_t *element = (server_instance_t *) element_;\n    server_persistence_version_t *version =\n            (server_persistence_version_t *) version_;\n    AVS_ASSERT(avs_persistence_direction(ctx) != AVS_PERSISTENCE_STORE\n                       || *version == PERSISTENCE_VERSION_3,\n               \"persistence storing is impossible in legacy mode\");\n\n    // Ensure every field initialized regardless of persistence version\n    if (avs_persistence_direction(ctx) == AVS_PERSISTENCE_RESTORE) {\n        _anjay_serv_reset_instance(element);\n    }\n\n    avs_error_t err;\n    switch (*version) {\n    case PERSISTENCE_VERSION_0:\n        (void) (avs_is_err((err = handle_v0_v1_sized_fields(ctx, element)))\n                || avs_is_err((err = restore_v0_binding_mode(ctx, element))));\n        break;\n    case PERSISTENCE_VERSION_1:\n        (void) (avs_is_err((err = handle_v0_v1_sized_fields(ctx, element)))\n                || avs_is_err(\n                           (err = handle_v1_v2_v3_binding_mode(ctx, element))));\n        break;\n    case PERSISTENCE_VERSION_2:\n        (void) (avs_is_err((err = handle_v2_sized_fields(ctx, element)))\n                || avs_is_err(\n                           (err = handle_v1_v2_v3_binding_mode(ctx, element))));\n        break;\n    case PERSISTENCE_VERSION_3:\n        (void) (avs_is_err((err = handle_v3_sized_fields(ctx, element)))\n                || avs_is_err(\n                           (err = handle_v1_v2_v3_binding_mode(ctx, element))));\n        break;\n    default:\n        AVS_UNREACHABLE(\"invalid enum value\");\n    }\n    return err;\n}\n\navs_error_t anjay_server_object_persist(anjay_t *anjay_locked,\n                                        avs_stream_t *out_stream) {\n    assert(anjay_locked);\n    avs_error_t err = avs_errno(AVS_EINVAL);\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    const anjay_dm_installed_object_t *server_obj =\n            _anjay_dm_find_object_by_oid(_anjay_get_dm(anjay),\n                                         ANJAY_DM_OID_SERVER);\n    server_repr_t *repr = server_obj ? _anjay_serv_get(*server_obj) : NULL;\n    if (!repr) {\n        err = avs_errno(AVS_EBADF);\n    } else {\n        avs_persistence_context_t persist_ctx =\n                avs_persistence_store_context_create(out_stream);\n        if (avs_is_ok((err = avs_persistence_bytes(&persist_ctx,\n                                                   (void *) (intptr_t) MAGIC_V3,\n                                                   sizeof(MAGIC_V3))))) {\n            server_persistence_version_t persistence_version =\n                    PERSISTENCE_VERSION_3;\n            err = avs_persistence_list(\n                    &persist_ctx,\n                    (AVS_LIST(void) *) (repr->in_transaction\n                                                ? &repr->saved_instances\n                                                : &repr->instances),\n                    sizeof(server_instance_t),\n                    server_instance_persistence_handler, &persistence_version,\n                    NULL);\n            if (avs_is_ok(err)) {\n                _anjay_serv_clear_modified(repr);\n                persistence_log(INFO, _(\"Server Object state persisted\"));\n            }\n        }\n    }\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return err;\n}\n\nstatic int check_magic_header(magic_t magic_header,\n                              server_persistence_version_t *out_version) {\n    if (!memcmp(magic_header, MAGIC_V0, sizeof(magic_t))) {\n        *out_version = PERSISTENCE_VERSION_0;\n        return 0;\n    }\n    if (!memcmp(magic_header, MAGIC_V1, sizeof(magic_t))) {\n        *out_version = PERSISTENCE_VERSION_1;\n        return 0;\n    }\n    if (!memcmp(magic_header, MAGIC_V2, sizeof(magic_t))) {\n        *out_version = PERSISTENCE_VERSION_2;\n        return 0;\n    }\n    if (!memcmp(magic_header, MAGIC_V3, sizeof(magic_t))) {\n        *out_version = PERSISTENCE_VERSION_3;\n        return 0;\n    }\n    return -1;\n}\n\navs_error_t anjay_server_object_restore(anjay_t *anjay_locked,\n                                        avs_stream_t *in_stream) {\n    assert(anjay_locked);\n    avs_error_t err = avs_errno(AVS_EINVAL);\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    const anjay_dm_installed_object_t *server_obj =\n            _anjay_dm_find_object_by_oid(_anjay_get_dm(anjay),\n                                         ANJAY_DM_OID_SERVER);\n    server_repr_t *repr = server_obj ? _anjay_serv_get(*server_obj) : NULL;\n    if (!repr || repr->in_transaction) {\n        err = avs_errno(AVS_EBADF);\n    } else {\n        server_repr_t backup = *repr;\n        avs_persistence_context_t restore_ctx =\n                avs_persistence_restore_context_create(in_stream);\n\n        magic_t magic_header;\n        if (avs_is_err((err = avs_persistence_bytes(&restore_ctx, magic_header,\n                                                    sizeof(magic_header))))) {\n            persistence_log(WARNING, _(\"Could not read Server Object header\"));\n        } else {\n            server_persistence_version_t persistence_version;\n            if (check_magic_header(magic_header, &persistence_version)) {\n                persistence_log(WARNING, _(\"Header magic constant mismatch\"));\n                err = avs_errno(AVS_EBADMSG);\n            } else {\n                repr->instances = NULL;\n                err = avs_persistence_list(&restore_ctx,\n                                           (AVS_LIST(void) *) &repr->instances,\n                                           sizeof(server_instance_t),\n                                           server_instance_persistence_handler,\n                                           &persistence_version, NULL);\n                if (avs_is_ok(err) && _anjay_serv_object_validate(repr)) {\n                    err = avs_errno(AVS_EBADMSG);\n                }\n                if (avs_is_err(err)) {\n                    _anjay_serv_destroy_instances(&repr->instances);\n                    repr->instances = backup.instances;\n                } else {\n                    _anjay_serv_destroy_instances(&backup.instances);\n                    _anjay_serv_clear_modified(repr);\n                    persistence_log(INFO, _(\"Server Object state restored\"));\n                }\n            }\n        }\n    }\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return err;\n}\n\n#        ifdef ANJAY_TEST\n#            include \"tests/modules/server/persistence.c\"\n#        endif\n\n#    else // AVS_COMMONS_WITH_AVS_PERSISTENCE\n\navs_error_t anjay_server_object_persist(anjay_t *anjay,\n                                        avs_stream_t *out_stream) {\n    (void) anjay;\n    (void) out_stream;\n    persistence_log(ERROR, _(\"Persistence not compiled in\"));\n    return avs_errno(AVS_ENOTSUP);\n}\n\navs_error_t anjay_server_object_restore(anjay_t *anjay,\n                                        avs_stream_t *in_stream) {\n    (void) anjay;\n    (void) in_stream;\n    persistence_log(ERROR, _(\"Persistence not compiled in\"));\n    return avs_errno(AVS_ENOTSUP);\n}\n\n#    endif // AVS_COMMONS_WITH_AVS_PERSISTENCE\n\n#endif // ANJAY_WITH_MODULE_SERVER\n"
  },
  {
    "path": "src/modules/server/anjay_server_transaction.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#ifdef ANJAY_WITH_MODULE_SERVER\n\n#    include <assert.h>\n#    include <inttypes.h>\n#    include <string.h>\n\n#    include \"anjay_server_transaction.h\"\n#    include \"anjay_server_utils.h\"\n\n#    include <anjay_modules/anjay_dm_utils.h>\n\nVISIBILITY_SOURCE_BEGIN\n\nstatic int ssid_cmp(const void *a, const void *b, size_t size) {\n    assert(size == sizeof(anjay_ssid_t));\n    (void) size;\n    return *((const anjay_ssid_t *) a) - *((const anjay_ssid_t *) b);\n}\n\n#    define LOG_VALIDATION_FAILED(ServInstance, ...)          \\\n        server_log(                                           \\\n                WARNING, \"/%u/%u: \" AVS_VARARG0(__VA_ARGS__), \\\n                ANJAY_DM_OID_SERVER,                          \\\n                (unsigned) (ServInstance)->iid AVS_VARARG_REST(__VA_ARGS__))\n\nstatic int validate_instance(server_instance_t *it) {\n    if (!it->present_resources[SERV_RES_SSID]) {\n        LOG_VALIDATION_FAILED(\n                it, _(\"missing mandatory 'Short Server ID' resource value\"));\n        return -1;\n    }\n    if (it->ssid < 1 || it->ssid >= UINT16_MAX) {\n        LOG_VALIDATION_FAILED(\n                it, _(\"invalid 'Short Server ID' resource value: \") \"%\" PRIu16,\n                it->ssid);\n        return -1;\n    }\n    if (!it->present_resources[SERV_RES_BINDING]) {\n        LOG_VALIDATION_FAILED(it,\n                              _(\"missing mandatory 'Binding' resource value\"));\n        return -1;\n    }\n    if (!it->present_resources[SERV_RES_LIFETIME]) {\n        LOG_VALIDATION_FAILED(it,\n                              _(\"missing mandatory 'Lifetime' resource value\"));\n        return -1;\n    }\n    if (!it->present_resources\n                 [SERV_RES_NOTIFICATION_STORING_WHEN_DISABLED_OR_OFFLINE]) {\n        LOG_VALIDATION_FAILED(it,\n                              _(\"missing mandatory 'Notification Storing \"\n                                \"when disabled or offline' resource value\"));\n        return -1;\n    }\n    if (it->lifetime < 0) {\n        LOG_VALIDATION_FAILED(it, _(\"Lifetime value is negative: \") \"%\" PRId32,\n                              it->lifetime);\n        return -1;\n    }\n    if (it->present_resources[SERV_RES_DEFAULT_MAX_PERIOD]\n            && it->default_max_period <= 0) {\n        LOG_VALIDATION_FAILED(it, _(\"Default Max Period is non-positive\"));\n        return -1;\n    }\n    if (it->present_resources[SERV_RES_DEFAULT_MIN_PERIOD]\n            && it->default_min_period < 0) {\n        LOG_VALIDATION_FAILED(it, _(\"Default Min Period is negative\"));\n        return -1;\n    }\n#    ifndef ANJAY_WITHOUT_DEREGISTER\n    if (it->present_resources[SERV_RES_DISABLE_TIMEOUT]\n            && it->disable_timeout < 0) {\n        LOG_VALIDATION_FAILED(it, _(\"Disable Timeout is negative\"));\n        return -1;\n    }\n#    endif // ANJAY_WITHOUT_DEREGISTER\n    if (!anjay_binding_mode_valid(it->binding.data)) {\n        LOG_VALIDATION_FAILED(it, _(\"Incorrect binding mode \") \"%s\",\n                              it->binding.data);\n        return -1;\n    }\n#    ifdef ANJAY_WITH_LWM2M11\n    if (it->present_resources[SERV_RES_LAST_BOOTSTRAPPED]\n            && it->last_bootstrapped_timestamp < 0) {\n        LOG_VALIDATION_FAILED(it, _(\"Last Bootstrapped is negative\"));\n        return -1;\n    }\n    if (it->present_resources[SERV_RES_PREFERRED_TRANSPORT]\n            && !_anjay_binding_info_by_letter(it->preferred_transport)) {\n        LOG_VALIDATION_FAILED(it, _(\"Incorrect Preferred Transport: \") \"%c\",\n                              it->preferred_transport);\n        return -1;\n    }\n    if (it->present_resources[SERV_RES_SERVER_COMMUNICATION_RETRY_COUNT]\n            && it->server_communication_retry_count == 0) {\n        LOG_VALIDATION_FAILED(it,\n                              _(\"Communication Retry Count cannot be zero\"));\n        return -1;\n    }\n    if (it->present_resources\n                [SERV_RES_SERVER_COMMUNICATION_SEQUENCE_RETRY_COUNT]\n            && it->server_communication_sequence_retry_count == 0) {\n        LOG_VALIDATION_FAILED(\n                it, _(\"Communication Sequence Retry Count cannot be zero\"));\n        return -1;\n    }\n#    endif // ANJAY_WITH_LWM2M11\n\n    return 0;\n}\n\nint _anjay_serv_object_validate(server_repr_t *repr) {\n    if (!repr->instances) {\n        return 0;\n    }\n    int result = 0;\n\n    AVS_LIST(anjay_ssid_t) seen_ssids = NULL;\n    AVS_LIST(server_instance_t) it;\n    AVS_LIST_FOREACH(it, repr->instances) {\n        if (validate_instance(it)) {\n            result = ANJAY_ERR_BAD_REQUEST;\n            break;\n        }\n\n        if (!AVS_LIST_INSERT_NEW(anjay_ssid_t, &seen_ssids)) {\n            result = ANJAY_ERR_INTERNAL;\n            break;\n        }\n        *seen_ssids = it->ssid;\n    }\n\n    /* Test for SSID duplication */\n    if (!result) {\n        AVS_LIST_SORT(&seen_ssids, ssid_cmp);\n        AVS_LIST(anjay_ssid_t) prev = seen_ssids;\n        AVS_LIST(anjay_ssid_t) next = AVS_LIST_NEXT(seen_ssids);\n        while (next) {\n            if (*prev == *next) {\n                /* Duplicate found */\n                result = ANJAY_ERR_BAD_REQUEST;\n                break;\n            }\n            prev = next;\n            next = AVS_LIST_NEXT(next);\n        }\n    }\n    AVS_LIST_CLEAR(&seen_ssids);\n    return result;\n}\n\nint _anjay_serv_transaction_begin_impl(server_repr_t *repr) {\n    assert(!repr->saved_instances);\n    assert(!repr->in_transaction);\n    repr->saved_instances = _anjay_serv_clone_instances(repr);\n    if (!repr->saved_instances && repr->instances) {\n        return ANJAY_ERR_INTERNAL;\n    }\n    repr->saved_modified_since_persist = repr->modified_since_persist;\n    repr->in_transaction = true;\n    return 0;\n}\n\nint _anjay_serv_transaction_commit_impl(server_repr_t *repr) {\n    assert(repr->in_transaction);\n    _anjay_serv_destroy_instances(&repr->saved_instances);\n    repr->in_transaction = false;\n    return 0;\n}\n\nint _anjay_serv_transaction_validate_impl(server_repr_t *repr) {\n    assert(repr->in_transaction);\n    return _anjay_serv_object_validate(repr);\n}\n\nint _anjay_serv_transaction_rollback_impl(server_repr_t *repr) {\n    assert(repr->in_transaction);\n    _anjay_serv_destroy_instances(&repr->instances);\n    repr->modified_since_persist = repr->saved_modified_since_persist;\n    repr->instances = repr->saved_instances;\n    repr->saved_instances = NULL;\n    repr->in_transaction = false;\n    return 0;\n}\n\n#endif // ANJAY_WITH_MODULE_SERVER\n"
  },
  {
    "path": "src/modules/server/anjay_server_transaction.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef SERVER_TRANSACTION_H\n#define SERVER_TRANSACTION_H\n#include <anjay_init.h>\n\n#include \"anjay_mod_server.h\"\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\nint _anjay_serv_object_validate(server_repr_t *repr);\n\nint _anjay_serv_transaction_begin_impl(server_repr_t *repr);\nint _anjay_serv_transaction_commit_impl(server_repr_t *repr);\nint _anjay_serv_transaction_validate_impl(server_repr_t *repr);\nint _anjay_serv_transaction_rollback_impl(server_repr_t *repr);\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif /* SERVER_TRANSACTION_H */\n"
  },
  {
    "path": "src/modules/server/anjay_server_utils.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#ifdef ANJAY_WITH_MODULE_SERVER\n\n#    include <anjay_modules/anjay_dm_utils.h>\n\n#    include \"anjay_server_utils.h\"\n\n#    include <string.h>\n\nVISIBILITY_SOURCE_BEGIN\n\nint _anjay_serv_fetch_ssid(anjay_unlocked_input_ctx_t *ctx,\n                           anjay_ssid_t *out_ssid) {\n    int32_t ssid;\n    int retval = _anjay_get_i32_unlocked(ctx, &ssid);\n    if (retval) {\n        return retval;\n    }\n    *out_ssid = (anjay_ssid_t) ssid;\n    return 0;\n}\n\nint _anjay_serv_fetch_validated_i32(anjay_unlocked_input_ctx_t *ctx,\n                                    int32_t min_value,\n                                    int32_t max_value,\n                                    int32_t *out_value) {\n    int retval = _anjay_get_i32_unlocked(ctx, out_value);\n    if (!retval && (*out_value < min_value || *out_value > max_value)) {\n        return ANJAY_ERR_BAD_REQUEST;\n    }\n    return retval;\n}\n\nint _anjay_serv_fetch_binding(anjay_unlocked_input_ctx_t *ctx,\n                              anjay_binding_mode_t *out_binding) {\n    int retval;\n    if ((retval = _anjay_get_string_unlocked(\n                 ctx, out_binding->data, sizeof(out_binding->data)))) {\n        return retval;\n    }\n    return anjay_binding_mode_valid(out_binding->data) ? 0\n                                                       : ANJAY_ERR_BAD_REQUEST;\n}\n\nAVS_LIST(server_instance_t)\n_anjay_serv_clone_instances(const server_repr_t *repr) {\n    return AVS_LIST_SIMPLE_CLONE(repr->instances);\n}\n\nvoid _anjay_serv_destroy_instances(AVS_LIST(server_instance_t) *instances) {\n    AVS_LIST_CLEAR(instances);\n}\n\nvoid _anjay_serv_reset_instance(server_instance_t *serv) {\n    const anjay_iid_t iid = serv->iid;\n    memset(serv, 0, sizeof(*serv));\n    /* This is not a resource, therefore must be restored */\n    serv->iid = iid;\n    serv->present_resources[SERV_RES_REGISTRATION_UPDATE_TRIGGER] = true;\n#    ifndef ANJAY_WITHOUT_DEREGISTER\n    serv->present_resources[SERV_RES_DISABLE] = true;\n#    endif // ANJAY_WITHOUT_DEREGISTER\n#    ifdef ANJAY_WITH_LWM2M11\n    serv->bootstrap_on_registration_failure = true;\n    serv->present_resources[SERV_RES_BOOTSTRAP_ON_REGISTRATION_FAILURE] = true;\n#        ifdef ANJAY_WITH_BOOTSTRAP\n    serv->present_resources[SERV_RES_BOOTSTRAP_REQUEST_TRIGGER] = true;\n#        endif // ANJAY_WITH_BOOTSTRAP\n#        ifdef ANJAY_WITH_SEND\n    serv->present_resources[SERV_RES_MUTE_SEND] = true;\n#        endif // ANJAY_WITH_SEND\n#    endif     // ANJAY_WITH_LWM2M11\n}\n\n#endif // ANJAY_WITH_MODULE_SERVER\n"
  },
  {
    "path": "src/modules/server/anjay_server_utils.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef SERVER_UTILS_H\n#define SERVER_UTILS_H\n#include <anjay_init.h>\n\n#include <assert.h>\n\n#include \"anjay_mod_server.h\"\n\nVISIBILITY_PRIVATE_HEADER_BEGIN\n\nserver_repr_t *_anjay_serv_get(const anjay_dm_installed_object_t obj_ptr);\n\nint _anjay_serv_fetch_ssid(anjay_unlocked_input_ctx_t *ctx,\n                           anjay_ssid_t *out_ssid);\nint _anjay_serv_fetch_validated_i32(anjay_unlocked_input_ctx_t *ctx,\n                                    int32_t min_value,\n                                    int32_t max_value,\n                                    int32_t *out_value);\nint _anjay_serv_fetch_binding(anjay_unlocked_input_ctx_t *ctx,\n                              anjay_binding_mode_t *out_binding);\n\nAVS_LIST(server_instance_t)\n_anjay_serv_clone_instances(const server_repr_t *repr);\nvoid _anjay_serv_destroy_instances(AVS_LIST(server_instance_t) *instances);\nvoid _anjay_serv_reset_instance(server_instance_t *serv);\n\nVISIBILITY_PRIVATE_HEADER_END\n\n#endif /* SERVER_UTILS_H */\n"
  },
  {
    "path": "src/modules/sw_mgmt/anjay_sw_mgmt.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#ifdef ANJAY_WITH_MODULE_SW_MGMT\n\n#    include <inttypes.h>\n\n#    include <anjay/sw_mgmt.h>\n\n#    include <anjay_modules/anjay_dm_utils.h>\n#    include <anjay_modules/anjay_io_utils.h>\n#    include <anjay_modules/anjay_sched.h>\n#    include <anjay_modules/anjay_utils_core.h>\n#    include <anjay_modules/dm/anjay_modules.h>\n\n#    ifdef ANJAY_WITH_DOWNLOADER\n#        include <anjay/download.h>\n\n#        include <avsystem/coap/code.h>\n\n#        include <avsystem/commons/avs_errno.h>\n#        include <avsystem/commons/avs_url.h>\n#        include <avsystem/commons/avs_utils.h>\n#    endif // ANJAY_WITH_DOWNLOADER\n\nVISIBILITY_SOURCE_BEGIN\n\n#    define sw_mgmt_log(level, ...) _anjay_log(sw_mgmt, level, __VA_ARGS__)\n\n#    define sw_mgmt_log_inst(level, inst, ...)                             \\\n        _anjay_log(sw_mgmt, level,                                         \\\n                   _(\"[iid=\") \"%\" PRIu16 _(\"] \") AVS_VARARG0(__VA_ARGS__), \\\n                   inst AVS_VARARG_REST(__VA_ARGS__))\n\n#    define OID 9\n\n#    define RID_PKGNAME 0\n#    define RID_PKGVERSION 1\n#    define RID_PACKAGE 2\n#    define RID_PACKAGE_URI 3\n#    define RID_INSTALL 4\n#    define RID_UNINSTALL 6\n#    define RID_UPDATE_STATE 7\n#    define RID_UPDATE_RESULT 9\n#    define RID_ACTIVATE 10\n#    define RID_DEACTIVATE 11\n#    define RID_ACTIVATION_STATE 12\n\n#    define UNINSTALL_ARG_UNINSTALL 0\n#    define UNINSTALL_ARG_FOR_UPDATE 1\n\n#    define UNLOCK_FOR_SW_MGMT_CALLBACK(AnjayLockedVar, AnjayUnlockedVar, \\\n                                        SwMgmtInstancePtr)                \\\n        (SwMgmtInstancePtr)->cannot_delete = true;                        \\\n        ANJAY_MUTEX_UNLOCK_FOR_CALLBACK((AnjayLockedVar), (AnjayUnlockedVar))\n#    define LOCK_AFTER_SW_MGMT_CALLBACK(AnjayLockedVar, SwMgmtInstancePtr) \\\n        ANJAY_MUTEX_LOCK_AFTER_CALLBACK((AnjayLockedVar));                 \\\n        (SwMgmtInstancePtr)->cannot_delete = false\n\ntypedef enum {\n    SW_MGMT_INTERNAL_STATE_IDLE,\n    SW_MGMT_INTERNAL_STATE_DOWNLOADING,\n    SW_MGMT_INTERNAL_STATE_DOWNLOADED,\n    SW_MGMT_INTERNAL_STATE_DELIVERED,\n    SW_MGMT_INTERNAL_STATE_INSTALLING,\n    SW_MGMT_INTERNAL_STATE_INSTALLED_DEACTIVATED,\n    SW_MGMT_INTERNAL_STATE_INSTALLED_ACTIVATED\n} sw_mgmt_internal_state_t;\n\ntypedef enum {\n    SW_MGMT_UPDATE_STATE_INITIAL = 0,\n    SW_MGMT_UPDATE_STATE_DOWNLOAD_STARTED = 1,\n    SW_MGMT_UPDATE_STATE_DOWNLOADED = 2,\n    SW_MGMT_UPDATE_STATE_DELIVERED = 3,\n    SW_MGMT_UPDATE_STATE_INSTALLED = 4\n} sw_mgmt_update_state_t;\n\ntypedef struct sw_mgmt_instance_struct {\n    anjay_iid_t iid;\n    void *inst_ctx;\n\n    sw_mgmt_internal_state_t internal_state;\n    anjay_sw_mgmt_update_result_t update_result;\n\n    avs_sched_handle_t install_and_integrity_jobs_handle;\n\n    bool cannot_delete;\n\n#    ifdef ANJAY_WITH_DOWNLOADER\n    anjay_download_handle_t pull_download_handle;\n    bool pull_download_stream_opened;\n#    endif // ANJAY_WITH_DOWNLOADER\n} sw_mgmt_instance_t;\n\ntypedef struct sw_mgmt_object_struct {\n    anjay_dm_installed_object_t def_ptr;\n    const anjay_unlocked_dm_object_def_t *def;\n\n    const anjay_sw_mgmt_handlers_t *handlers;\n    void *obj_ctx;\n\n    AVS_LIST(sw_mgmt_instance_t) instances;\n\n#    if defined(ANJAY_WITH_DOWNLOADER)\n    bool prefer_same_socket_downloads;\n    bool downloads_suspended;\n#    endif // defined(ANJAY_WITH_DOWNLOADER)\n} sw_mgmt_object_t;\n\nstatic anjay_dm_module_deleter_t sw_mgmt_delete;\n\nstatic inline sw_mgmt_internal_state_t\ninitial_state_to_internal_state(anjay_sw_mgmt_initial_state_t initial_state) {\n    // clang-format off\n    static const sw_mgmt_internal_state_t map[] = {\n        [ANJAY_SW_MGMT_INITIAL_STATE_IDLE] =\n                SW_MGMT_INTERNAL_STATE_IDLE,\n        [ANJAY_SW_MGMT_INITIAL_STATE_DOWNLOADED] =\n                SW_MGMT_INTERNAL_STATE_DOWNLOADED,\n        [ANJAY_SW_MGMT_INITIAL_STATE_DELIVERED] =\n                SW_MGMT_INTERNAL_STATE_DELIVERED,\n        [ANJAY_SW_MGMT_INITIAL_STATE_INSTALLING] =\n                SW_MGMT_INTERNAL_STATE_INSTALLING,\n        [ANJAY_SW_MGMT_INITIAL_STATE_INSTALLED_DEACTIVATED] =\n                SW_MGMT_INTERNAL_STATE_INSTALLED_DEACTIVATED,\n        [ANJAY_SW_MGMT_INITIAL_STATE_INSTALLED_ACTIVATED] =\n                SW_MGMT_INTERNAL_STATE_INSTALLED_ACTIVATED,\n    };\n    // clang-format on\n    assert(initial_state >= 0);\n    assert(initial_state < AVS_ARRAY_SIZE(map));\n    return map[initial_state];\n}\n\nstatic inline anjay_sw_mgmt_update_result_t\ninitial_state_to_update_result(anjay_sw_mgmt_initial_state_t initial_state) {\n    // clang-format off\n    static const anjay_sw_mgmt_update_result_t map[] = {\n        [ANJAY_SW_MGMT_INITIAL_STATE_IDLE] =\n                ANJAY_SW_MGMT_UPDATE_RESULT_INITIAL,\n        [ANJAY_SW_MGMT_INITIAL_STATE_DOWNLOADED] =\n                ANJAY_SW_MGMT_UPDATE_RESULT_INITIAL,\n        [ANJAY_SW_MGMT_INITIAL_STATE_DELIVERED] =\n                ANJAY_SW_MGMT_UPDATE_RESULT_INITIAL,\n        [ANJAY_SW_MGMT_INITIAL_STATE_INSTALLING] =\n                ANJAY_SW_MGMT_UPDATE_RESULT_INITIAL,\n        [ANJAY_SW_MGMT_INITIAL_STATE_INSTALLED_DEACTIVATED] =\n                ANJAY_SW_MGMT_UPDATE_RESULT_INSTALLED,\n        [ANJAY_SW_MGMT_INITIAL_STATE_INSTALLED_ACTIVATED] =\n                ANJAY_SW_MGMT_UPDATE_RESULT_INSTALLED,\n    };\n    // clang-format on\n    assert(initial_state >= 0);\n    assert(initial_state < AVS_ARRAY_SIZE(map));\n    return map[initial_state];\n}\n\nstatic inline sw_mgmt_update_state_t\ninternal_state_to_update_state(sw_mgmt_internal_state_t internal_state) {\n    // clang-format off\n    static const sw_mgmt_update_state_t map[] = {\n        [SW_MGMT_INTERNAL_STATE_IDLE] =\n                SW_MGMT_UPDATE_STATE_INITIAL,\n        [SW_MGMT_INTERNAL_STATE_DOWNLOADING] =\n                SW_MGMT_UPDATE_STATE_DOWNLOAD_STARTED,\n        [SW_MGMT_INTERNAL_STATE_DOWNLOADED] =\n                SW_MGMT_UPDATE_STATE_DOWNLOADED,\n        [SW_MGMT_INTERNAL_STATE_DELIVERED] =\n                SW_MGMT_UPDATE_STATE_DELIVERED,\n        [SW_MGMT_INTERNAL_STATE_INSTALLING] =\n                SW_MGMT_UPDATE_STATE_DELIVERED,\n        [SW_MGMT_INTERNAL_STATE_INSTALLED_DEACTIVATED] =\n                SW_MGMT_UPDATE_STATE_INSTALLED,\n        [SW_MGMT_INTERNAL_STATE_INSTALLED_ACTIVATED] =\n                SW_MGMT_UPDATE_STATE_INSTALLED\n    };\n    // clang-format on\n    assert(internal_state >= 0);\n    assert(internal_state < AVS_ARRAY_SIZE(map));\n    return map[internal_state];\n}\n\nstatic inline bool\ninternal_state_is_delivered(sw_mgmt_internal_state_t internal_state) {\n    return internal_state_to_update_state(internal_state)\n           == SW_MGMT_UPDATE_STATE_DELIVERED;\n}\n\nstatic inline bool\ninternal_state_is_installed(sw_mgmt_internal_state_t internal_state) {\n    return internal_state_to_update_state(internal_state)\n           == SW_MGMT_UPDATE_STATE_INSTALLED;\n}\n\nstatic inline bool\ninternal_state_is_activated(sw_mgmt_internal_state_t internal_state) {\n    return internal_state == SW_MGMT_INTERNAL_STATE_INSTALLED_ACTIVATED;\n}\n\nstatic inline bool package_available(sw_mgmt_internal_state_t internal_state) {\n    return internal_state_is_delivered(internal_state)\n           || internal_state_is_installed(internal_state);\n}\n\nstatic inline anjay_sw_mgmt_update_result_t\nretval_to_update_result(int retval) {\n    switch (retval) {\n    case ANJAY_SW_MGMT_ERR_NOT_ENOUGH_SPACE:\n    case ANJAY_SW_MGMT_ERR_OUT_OF_MEMORY:\n    case ANJAY_SW_MGMT_ERR_INTEGRITY_FAILURE:\n    case ANJAY_SW_MGMT_ERR_UNSUPPORTED_PACKAGE_TYPE:\n        return (anjay_sw_mgmt_update_result_t) -retval;\n    default:\n        return ANJAY_SW_MGMT_UPDATE_RESULT_UPDATE_ERROR;\n    }\n}\n\nstatic void change_internal_state_and_update_result(\n        anjay_unlocked_t *anjay,\n        sw_mgmt_instance_t *inst,\n        sw_mgmt_internal_state_t internal_state,\n        anjay_sw_mgmt_update_result_t update_result) {\n    // clang-format off\n    sw_mgmt_log_inst(DEBUG, inst->iid, _(\"fsm state and update result change\")\n                     _(\" from \") \"%d\" _(\", \") \"%d\"\n                     _(\" to \") \"%d\" _(\", \") \"%d\",\n                     (int) inst->internal_state, (int) inst->update_result,\n                     (int) internal_state, (int) update_result);\n    // clang-format on\n\n    if (internal_state != inst->internal_state) {\n        sw_mgmt_internal_state_t old_internal_state = inst->internal_state;\n        inst->internal_state = internal_state;\n\n        // internal_state controls Update State and Activation State resources\n        if (internal_state_to_update_state(inst->internal_state)\n                != internal_state_to_update_state(old_internal_state)) {\n            _anjay_notify_changed_unlocked(anjay, OID, inst->iid,\n                                           RID_UPDATE_STATE);\n        }\n\n        if (internal_state_is_activated(inst->internal_state)\n                != internal_state_is_activated(old_internal_state)) {\n            _anjay_notify_changed_unlocked(anjay, OID, inst->iid,\n                                           RID_ACTIVATION_STATE);\n        }\n\n        // version and name are available only when package is available\n        if (package_available(inst->internal_state)\n                != package_available(old_internal_state)) {\n            _anjay_notify_changed_unlocked(anjay, OID, inst->iid, RID_PKGNAME);\n            _anjay_notify_changed_unlocked(anjay, OID, inst->iid,\n                                           RID_PKGVERSION);\n        }\n    }\n    if (update_result != inst->update_result) {\n        inst->update_result = update_result;\n        _anjay_notify_changed_unlocked(anjay, OID, inst->iid,\n                                       RID_UPDATE_RESULT);\n    }\n}\n\nstatic inline sw_mgmt_object_t *\nget_obj(const anjay_dm_installed_object_t *obj_ptr) {\n    return AVS_CONTAINER_OF(_anjay_dm_installed_object_get_unlocked(obj_ptr),\n                            sw_mgmt_object_t, def);\n}\n\nstatic sw_mgmt_instance_t *find_instance(const sw_mgmt_object_t *obj,\n                                         anjay_iid_t iid) {\n    AVS_LIST(sw_mgmt_instance_t) it;\n    AVS_LIST_FOREACH(it, obj->instances) {\n        if (it->iid == iid) {\n            return it;\n        } else if (it->iid > iid) {\n            break;\n        }\n    }\n\n    return NULL;\n}\n\nstatic inline int call_stream_open(anjay_unlocked_t *anjay,\n                                   sw_mgmt_object_t *obj,\n                                   sw_mgmt_instance_t *inst) {\n    int result = -1;\n    UNLOCK_FOR_SW_MGMT_CALLBACK(anjay_locked, anjay, inst);\n    result =\n            obj->handlers->stream_open(obj->obj_ctx, inst->iid, inst->inst_ctx);\n    LOCK_AFTER_SW_MGMT_CALLBACK(anjay_locked, inst);\n    return result;\n}\n\nstatic inline int call_stream_write(anjay_unlocked_t *anjay,\n                                    sw_mgmt_object_t *obj,\n                                    sw_mgmt_instance_t *inst,\n                                    const void *data,\n                                    size_t length) {\n    int result = -1;\n    UNLOCK_FOR_SW_MGMT_CALLBACK(anjay_locked, anjay, inst);\n    result = obj->handlers->stream_write(obj->obj_ctx, inst->iid,\n                                         inst->inst_ctx, data, length);\n    LOCK_AFTER_SW_MGMT_CALLBACK(anjay_locked, inst);\n    return result;\n}\n\nstatic inline int call_stream_finish(anjay_unlocked_t *anjay,\n                                     sw_mgmt_object_t *obj,\n                                     sw_mgmt_instance_t *inst) {\n    int result = -1;\n    UNLOCK_FOR_SW_MGMT_CALLBACK(anjay_locked, anjay, inst);\n    result = obj->handlers->stream_finish(obj->obj_ctx, inst->iid,\n                                          inst->inst_ctx);\n    LOCK_AFTER_SW_MGMT_CALLBACK(anjay_locked, inst);\n    return result;\n}\n\nstatic inline void call_reset(anjay_unlocked_t *anjay,\n                              sw_mgmt_object_t *obj,\n                              sw_mgmt_instance_t *inst) {\n    UNLOCK_FOR_SW_MGMT_CALLBACK(anjay_locked, anjay, inst);\n    obj->handlers->reset(obj->obj_ctx, inst->iid, inst->inst_ctx);\n    LOCK_AFTER_SW_MGMT_CALLBACK(anjay_locked, inst);\n}\n\nstatic void pkg_install_job(avs_sched_t *sched, const void *inst_ptr) {\n    sw_mgmt_instance_t *inst = *(sw_mgmt_instance_t *const *) inst_ptr;\n    anjay_t *anjay_locked = _anjay_get_from_sched(sched);\n\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    assert(inst->internal_state == SW_MGMT_INTERNAL_STATE_INSTALLING);\n    sw_mgmt_object_t *obj =\n            (sw_mgmt_object_t *) _anjay_dm_module_get_arg(anjay,\n                                                          sw_mgmt_delete);\n\n    int result = -1;\n    UNLOCK_FOR_SW_MGMT_CALLBACK(anjay_relocked, anjay, inst);\n    result =\n            obj->handlers->pkg_install(obj->obj_ctx, inst->iid, inst->inst_ctx);\n    LOCK_AFTER_SW_MGMT_CALLBACK(anjay_relocked, inst);\n\n    if (result) {\n        sw_mgmt_log_inst(WARNING, inst->iid, _(\"pkg_install() failed: \") \"%d\",\n                         result);\n        change_internal_state_and_update_result(\n                anjay, inst, SW_MGMT_INTERNAL_STATE_DELIVERED,\n                ANJAY_SW_MGMT_UPDATE_RESULT_INSTALLATION_FAILURE);\n    } else {\n        sw_mgmt_log_inst(DEBUG, inst->iid, _(\"package installed successfully\"));\n    }\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n}\n\nstatic void check_integrity_job(avs_sched_t *sched, const void *inst_ptr) {\n    sw_mgmt_instance_t *inst = *(sw_mgmt_instance_t *const *) inst_ptr;\n    anjay_t *anjay_locked = _anjay_get_from_sched(sched);\n\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    assert(inst->internal_state == SW_MGMT_INTERNAL_STATE_DOWNLOADED);\n    sw_mgmt_object_t *obj =\n            (sw_mgmt_object_t *) _anjay_dm_module_get_arg(anjay,\n                                                          sw_mgmt_delete);\n\n    int result = -1;\n    UNLOCK_FOR_SW_MGMT_CALLBACK(anjay_relocked, anjay, inst);\n    result = obj->handlers->check_integrity(obj->obj_ctx, inst->iid,\n                                            inst->inst_ctx);\n    LOCK_AFTER_SW_MGMT_CALLBACK(anjay_relocked, inst);\n\n    if (result) {\n        sw_mgmt_log_inst(WARNING, inst->iid,\n                         _(\"check_integrity() failed: \") \"%d\", result);\n        call_reset(anjay, obj, inst);\n        change_internal_state_and_update_result(\n                anjay, inst, SW_MGMT_INTERNAL_STATE_IDLE,\n                retval_to_update_result(result));\n    } else {\n        change_internal_state_and_update_result(\n                anjay, inst, SW_MGMT_INTERNAL_STATE_DELIVERED,\n                ANJAY_SW_MGMT_UPDATE_RESULT_INITIAL);\n        sw_mgmt_log_inst(DEBUG, inst->iid, _(\"integrity checked successfully\"));\n    }\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n}\n\n/**\n * Schedule this additional intermediate function to extend the time for sending\n * a potential notification related to resource 9/x/7 and\n * SW_MGMT_UPDATE_STATE_DOWNLOADED state. However, it may not be enough because\n * of e.g. pmin attribute value.\n */\nstatic void schedule_check_integrity_job(avs_sched_t *sched,\n                                         const void *inst_ptr) {\n    sw_mgmt_instance_t *inst = *(sw_mgmt_instance_t *const *) inst_ptr;\n    anjay_t *anjay_locked = _anjay_get_from_sched(sched);\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    if (AVS_SCHED_NOW(_anjay_get_scheduler_unlocked(anjay),\n                      &inst->install_and_integrity_jobs_handle,\n                      check_integrity_job, &inst, sizeof(inst))) {\n        sw_mgmt_object_t *obj =\n                (sw_mgmt_object_t *) _anjay_dm_module_get_arg(anjay,\n                                                              sw_mgmt_delete);\n        if (obj) {\n            call_reset(anjay, obj, inst);\n        }\n        change_internal_state_and_update_result(\n                anjay, inst, SW_MGMT_INTERNAL_STATE_IDLE,\n                ANJAY_SW_MGMT_UPDATE_RESULT_OUT_OF_MEMORY);\n        sw_mgmt_log_inst(WARNING, inst->iid,\n                         _(\"Could not schedule check_integrity_job\"));\n    }\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n}\n\nstatic void ensure_not_stalled_in_downloaded_state(anjay_unlocked_t *anjay,\n                                                   sw_mgmt_object_t *obj,\n                                                   sw_mgmt_instance_t *inst) {\n    assert(inst->internal_state == SW_MGMT_INTERNAL_STATE_DOWNLOADED);\n\n    // check if integrity check is not already scheduled\n    if (!inst->install_and_integrity_jobs_handle) {\n        if (AVS_SCHED_NOW(_anjay_get_scheduler_unlocked(anjay),\n                          &inst->install_and_integrity_jobs_handle,\n                          schedule_check_integrity_job, &inst, sizeof(inst))) {\n            call_reset(anjay, obj, inst);\n            change_internal_state_and_update_result(\n                    anjay, inst, SW_MGMT_INTERNAL_STATE_IDLE,\n                    ANJAY_SW_MGMT_UPDATE_RESULT_OUT_OF_MEMORY);\n            sw_mgmt_log_inst(WARNING, inst->iid,\n                             _(\"Could not schedule \"\n                               \"schedule_check_integrity_job\"));\n        }\n    }\n}\n\nstatic inline void possibly_schedule_integrity_check(anjay_unlocked_t *anjay,\n                                                     sw_mgmt_object_t *obj,\n                                                     sw_mgmt_instance_t *inst) {\n    assert(inst->internal_state == SW_MGMT_INTERNAL_STATE_DOWNLOADING);\n\n    if (obj->handlers->check_integrity) {\n        change_internal_state_and_update_result(\n                anjay, inst, SW_MGMT_INTERNAL_STATE_DOWNLOADED,\n                ANJAY_SW_MGMT_UPDATE_RESULT_INITIAL);\n        ensure_not_stalled_in_downloaded_state(anjay, obj, inst);\n    } else {\n        change_internal_state_and_update_result(\n                anjay, inst, SW_MGMT_INTERNAL_STATE_DELIVERED,\n                ANJAY_SW_MGMT_UPDATE_RESULT_INITIAL);\n    }\n}\n\nstatic int package_push_download(anjay_unlocked_t *anjay,\n                                 sw_mgmt_object_t *obj,\n                                 sw_mgmt_instance_t *inst,\n                                 anjay_unlocked_input_ctx_t *ctx) {\n    assert(inst->internal_state == SW_MGMT_INTERNAL_STATE_IDLE);\n\n    if (call_stream_open(anjay, obj, inst)) {\n        return ANJAY_ERR_INTERNAL;\n    }\n\n    // nobody's gonna notice that in PUSH mode, but let's adhere to the spec...\n    change_internal_state_and_update_result(\n            anjay, inst, SW_MGMT_INTERNAL_STATE_DOWNLOADING,\n            ANJAY_SW_MGMT_UPDATE_RESULT_DOWNLOADING);\n\n    int result;\n    size_t written = 0;\n    bool finished = false;\n\n    while (!finished) {\n        size_t bytes_read;\n        char buffer[1024];\n\n        result = _anjay_get_bytes_unlocked(ctx, &bytes_read, &finished, buffer,\n                                           sizeof(buffer));\n        if (result) {\n            call_reset(anjay, obj, inst);\n            change_internal_state_and_update_result(\n                    anjay, inst, SW_MGMT_INTERNAL_STATE_IDLE,\n                    ANJAY_SW_MGMT_UPDATE_RESULT_CONNECTION_LOST);\n            return result;\n        }\n\n        if (bytes_read > 0) {\n            result = call_stream_write(anjay, obj, inst, buffer, bytes_read);\n        }\n        if (result) {\n            call_reset(anjay, obj, inst);\n            change_internal_state_and_update_result(\n                    anjay, inst, SW_MGMT_INTERNAL_STATE_IDLE,\n                    retval_to_update_result(result));\n            return ANJAY_ERR_INTERNAL;\n        }\n        written += bytes_read;\n    }\n\n    result = call_stream_finish(anjay, obj, inst);\n    if (result) {\n        call_reset(anjay, obj, inst);\n        change_internal_state_and_update_result(\n                anjay, inst, SW_MGMT_INTERNAL_STATE_IDLE,\n                retval_to_update_result(result));\n        return ANJAY_ERR_INTERNAL;\n    }\n\n    sw_mgmt_log_inst(DEBUG, inst->iid,\n                     _(\"stream write successfully finished, \") \"%zu\" _(\n                             \" B written\"),\n                     written);\n\n    possibly_schedule_integrity_check(anjay, obj, inst);\n    return 0;\n}\n\n#    ifdef ANJAY_WITH_DOWNLOADER\n#        if defined(ANJAY_WITH_COAP_DOWNLOAD) \\\n                || defined(ANJAY_WITH_HTTP_DOWNLOAD)\nstatic anjay_transport_security_t\ntransport_security_from_protocol(const char *protocol) {\n#            ifdef ANJAY_WITH_HTTP_DOWNLOAD\n    if (avs_strcasecmp(protocol, \"http\") == 0) {\n        return ANJAY_TRANSPORT_NOSEC;\n    }\n    if (avs_strcasecmp(protocol, \"https\") == 0) {\n        return ANJAY_TRANSPORT_ENCRYPTED;\n    }\n#            endif // ANJAY_WITH_HTTP_DOWNLOAD\n\n#            ifdef ANJAY_WITH_COAP_DOWNLOAD\n    const anjay_transport_info_t *info =\n            _anjay_transport_info_by_uri_scheme(protocol);\n    if (info) {\n        return info->security;\n    }\n#            endif // ANJAY_WITH_COAP_DOWNLOAD\n\n    return ANJAY_TRANSPORT_SECURITY_UNDEFINED;\n}\n\n#        endif // defined(ANJAY_WITH_COAP_DOWNLOAD) ||\n               // defined(ANJAY_WITH_HTTP_DOWNLOAD)\nstatic anjay_transport_security_t transport_security_from_uri(const char *uri) {\n#        if defined(ANJAY_WITH_COAP_DOWNLOAD) \\\n                || defined(ANJAY_WITH_HTTP_DOWNLOAD)\n    avs_url_t *parsed_url = avs_url_parse_lenient(uri);\n    if (!parsed_url) {\n        return ANJAY_TRANSPORT_SECURITY_UNDEFINED;\n    }\n    anjay_transport_security_t result = ANJAY_TRANSPORT_SECURITY_UNDEFINED;\n\n    const char *protocol = avs_url_protocol(parsed_url);\n    if (protocol) {\n        result = transport_security_from_protocol(protocol);\n    }\n    avs_url_free(parsed_url);\n    return result;\n#        else  // defined(ANJAY_WITH_COAP_DOWNLOAD) ||\n               // defined(ANJAY_WITH_HTTP_DOWNLOAD)\n    (void) uri;\n    return ANJAY_TRANSPORT_SECURITY_UNDEFINED;\n#        endif // defined(ANJAY_WITH_COAP_DOWNLOAD) ||\n               // defined(ANJAY_WITH_HTTP_DOWNLOAD)\n}\n\nstatic int get_security_config(anjay_unlocked_t *anjay,\n                               sw_mgmt_object_t *obj,\n                               sw_mgmt_instance_t *inst,\n                               const char *package_uri,\n                               anjay_security_config_t *out_security_config) {\n    if (obj->handlers->get_security_config) {\n        int result = -1;\n        UNLOCK_FOR_SW_MGMT_CALLBACK(anjay_locked, anjay, inst);\n        result = obj->handlers->get_security_config(obj->obj_ctx, inst->iid,\n                                                    inst->inst_ctx, package_uri,\n                                                    out_security_config);\n        LOCK_AFTER_SW_MGMT_CALLBACK(anjay_locked, inst);\n        return result;\n    } else {\n        if (!_anjay_security_config_from_dm_unlocked(anjay, out_security_config,\n                                                     package_uri)) {\n            return 0;\n        }\n#        ifdef ANJAY_WITH_LWM2M11\n        *out_security_config = _anjay_security_config_pkix_unlocked(anjay);\n        if (out_security_config->security_info.data.cert\n                    .server_cert_validation) {\n            return 0;\n        }\n#        endif // ANJAY_WITH_LWM2M11\n        return -1;\n    }\n}\n\nstatic int get_coap_tx_params(anjay_unlocked_t *anjay,\n                              sw_mgmt_object_t *obj,\n                              sw_mgmt_instance_t *inst,\n                              const char *package_uri,\n                              avs_coap_udp_tx_params_t *out_tx_params) {\n    assert(inst->internal_state == SW_MGMT_INTERNAL_STATE_IDLE);\n    if (obj->handlers->get_coap_tx_params) {\n        UNLOCK_FOR_SW_MGMT_CALLBACK(anjay_locked, anjay, inst);\n        *out_tx_params =\n                obj->handlers->get_coap_tx_params(obj->obj_ctx, inst->iid,\n                                                  inst->inst_ctx, package_uri);\n        LOCK_AFTER_SW_MGMT_CALLBACK(anjay_locked, inst);\n        return 0;\n    }\n    return -1;\n}\n\nstatic avs_time_duration_t get_tcp_request_timeout(anjay_unlocked_t *anjay,\n                                                   sw_mgmt_object_t *obj,\n                                                   sw_mgmt_instance_t *inst,\n                                                   const char *package_uri) {\n    assert(inst->internal_state == SW_MGMT_INTERNAL_STATE_IDLE);\n    avs_time_duration_t result = AVS_TIME_DURATION_INVALID;\n    if (obj->handlers->get_tcp_request_timeout) {\n        UNLOCK_FOR_SW_MGMT_CALLBACK(anjay_locked, anjay, inst);\n        result = obj->handlers->get_tcp_request_timeout(\n                obj->obj_ctx, inst->iid, inst->inst_ctx, package_uri);\n        LOCK_AFTER_SW_MGMT_CALLBACK(anjay_locked, inst);\n    }\n    return result;\n}\n\nstatic inline int pull_download_ensure_stream_opened(anjay_unlocked_t *anjay,\n                                                     sw_mgmt_object_t *obj,\n                                                     sw_mgmt_instance_t *inst) {\n    if (!inst->pull_download_stream_opened) {\n        if (call_stream_open(anjay, obj, inst)) {\n            sw_mgmt_log_inst(ERROR, inst->iid, _(\"could not open package\"));\n            return -1;\n        }\n        inst->pull_download_stream_opened = true;\n    }\n    return 0;\n}\n\nstatic avs_error_t pull_download_on_next_block(anjay_t *anjay_locked,\n                                               const uint8_t *data,\n                                               size_t data_size,\n                                               const anjay_etag_t *etag,\n                                               void *inst_) {\n    (void) etag;\n\n    int result = 0;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    sw_mgmt_object_t *obj =\n            (sw_mgmt_object_t *) _anjay_dm_module_get_arg(anjay,\n                                                          sw_mgmt_delete);\n    sw_mgmt_instance_t *inst = (sw_mgmt_instance_t *) inst_;\n\n    assert(inst->internal_state == SW_MGMT_INTERNAL_STATE_DOWNLOADING);\n\n    if (!pull_download_ensure_stream_opened(anjay, obj, inst)) {\n        if (data_size > 0) {\n            result = call_stream_write(anjay, obj, inst, data, data_size);\n        }\n\n        if (result) {\n            sw_mgmt_log_inst(ERROR, inst->iid, _(\"could not write package\"));\n            change_internal_state_and_update_result(\n                    anjay, inst, SW_MGMT_INTERNAL_STATE_IDLE,\n                    retval_to_update_result(result));\n        }\n    } else {\n        change_internal_state_and_update_result(\n                anjay, inst, SW_MGMT_INTERNAL_STATE_IDLE,\n                ANJAY_SW_MGMT_UPDATE_RESULT_UPDATE_ERROR);\n    }\n\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n\n    return result ? avs_errno(AVS_UNKNOWN_ERROR) : AVS_OK;\n}\n\nstatic inline anjay_sw_mgmt_update_result_t\nhandle_downloader_error(anjay_download_status_t status) {\n    anjay_sw_mgmt_update_result_t update_result =\n            ANJAY_SW_MGMT_UPDATE_RESULT_UPDATE_ERROR;\n\n    switch (status.result) {\n    case ANJAY_DOWNLOAD_ERR_FAILED:\n        if (status.details.error.category == AVS_ERRNO_CATEGORY) {\n            switch (status.details.error.code) {\n            case AVS_EADDRNOTAVAIL:\n                update_result = ANJAY_SW_MGMT_UPDATE_RESULT_INVALID_URI;\n                break;\n            case AVS_EPROTO:\n            case AVS_ECONNABORTED:\n            case AVS_ECONNREFUSED:\n            case AVS_ETIMEDOUT:\n                update_result = ANJAY_SW_MGMT_UPDATE_RESULT_CONNECTION_LOST;\n                break;\n            case AVS_ENOMEM:\n                update_result = ANJAY_SW_MGMT_UPDATE_RESULT_OUT_OF_MEMORY;\n                break;\n            }\n        } else if (status.details.error.category == AVS_NET_SSL_ALERT_CATEGORY\n                   || status.details.error.category\n                              == AVS_NET_SSL_LIB_ERROR_CATEGORY) {\n            update_result = ANJAY_SW_MGMT_UPDATE_RESULT_CONNECTION_LOST;\n        }\n        break;\n    case ANJAY_DOWNLOAD_ERR_INVALID_RESPONSE:\n#        ifdef ANJAY_WITH_COAP_DOWNLOAD\n        if (status.details.error.code == AVS_COAP_CODE_NOT_FOUND) {\n            update_result = ANJAY_SW_MGMT_UPDATE_RESULT_INVALID_URI;\n        }\n#        endif // ANJAY_WITH_COAP_DOWNLOAD\n#        ifdef ANJAY_WITH_HTTP_DOWNLOAD\n        if (status.details.error.code == 404) {\n            update_result = ANJAY_SW_MGMT_UPDATE_RESULT_INVALID_URI;\n        }\n#        endif // ANJAY_WITH_HTTP_DOWNLOAD\n        break;\n    case ANJAY_DOWNLOAD_ERR_EXPIRED:\n        update_result = ANJAY_SW_MGMT_UPDATE_RESULT_CONNECTION_LOST;\n        break;\n    default:\n        break;\n    }\n\n    return update_result;\n}\n\nstatic void pull_download_on_download_finished(anjay_t *anjay_locked,\n                                               anjay_download_status_t status,\n                                               void *inst_) {\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    sw_mgmt_object_t *obj =\n            (sw_mgmt_object_t *) _anjay_dm_module_get_arg(anjay,\n                                                          sw_mgmt_delete);\n    sw_mgmt_instance_t *inst = (sw_mgmt_instance_t *) inst_;\n\n    inst->pull_download_handle = NULL;\n\n    if (inst->internal_state != SW_MGMT_INTERNAL_STATE_DOWNLOADING) {\n        // pull_download_on_next_block() already failed\n        call_reset(anjay, obj, inst);\n    } else if (status.result != ANJAY_DOWNLOAD_FINISHED) {\n        call_reset(anjay, obj, inst);\n        change_internal_state_and_update_result(\n                anjay, inst, SW_MGMT_INTERNAL_STATE_IDLE,\n                handle_downloader_error(status));\n    } else {\n        // in case the downloaded file is empty\n        // stream_open should be called anyways\n        if (pull_download_ensure_stream_opened(anjay, obj, inst)\n                || call_stream_finish(anjay, obj, inst)) {\n            change_internal_state_and_update_result(\n                    anjay, inst, SW_MGMT_INTERNAL_STATE_IDLE,\n                    ANJAY_SW_MGMT_UPDATE_RESULT_UPDATE_ERROR);\n        } else {\n            possibly_schedule_integrity_check(anjay, obj, inst);\n        }\n    }\n    inst->pull_download_stream_opened = false;\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n}\n\nstatic int schedule_package_pull_download(anjay_unlocked_t *anjay,\n                                          sw_mgmt_object_t *obj,\n                                          sw_mgmt_instance_t *inst,\n                                          const char *package_uri) {\n    assert(inst->internal_state == SW_MGMT_INTERNAL_STATE_IDLE);\n\n    anjay_download_config_t cfg = {\n        .url = package_uri,\n        .start_offset = 0,\n        .on_next_block = pull_download_on_next_block,\n        .on_download_finished = pull_download_on_download_finished,\n        .user_data = inst,\n        .prefer_same_socket_downloads = obj->prefer_same_socket_downloads\n    };\n\n    if (transport_security_from_uri(package_uri) == ANJAY_TRANSPORT_ENCRYPTED) {\n        int result = get_security_config(anjay, obj, inst, package_uri,\n                                         &cfg.security_config);\n        if (result) {\n            change_internal_state_and_update_result(\n                    anjay, inst, SW_MGMT_INTERNAL_STATE_IDLE,\n                    ANJAY_SW_MGMT_UPDATE_RESULT_INVALID_URI);\n            return -1;\n        }\n    }\n\n#        ifdef ANJAY_WITH_COAP_DOWNLOAD\n    avs_coap_udp_tx_params_t tx_params;\n    if (!get_coap_tx_params(anjay, obj, inst, package_uri, &tx_params)) {\n        cfg.coap_tx_params = &tx_params;\n    }\n#        endif // ANJAY_WITH_COAP_DOWNLOAD\n    cfg.tcp_request_timeout =\n            get_tcp_request_timeout(anjay, obj, inst, package_uri);\n\n    assert(!inst->pull_download_handle);\n    avs_error_t err =\n            _anjay_download_unlocked(anjay, &cfg, &inst->pull_download_handle);\n    if (!inst->pull_download_handle) {\n        anjay_sw_mgmt_update_result_t update_result =\n                ANJAY_SW_MGMT_UPDATE_RESULT_UPDATE_ERROR;\n        if (avs_is_err(err) && err.category == AVS_ERRNO_CATEGORY) {\n            switch (err.code) {\n            case AVS_EADDRNOTAVAIL:\n            case AVS_EINVAL:\n                update_result = ANJAY_SW_MGMT_UPDATE_RESULT_INVALID_URI;\n                break;\n            case AVS_ENODEV:\n                update_result = ANJAY_SW_MGMT_UPDATE_RESULT_CONNECTION_LOST;\n                break;\n            case AVS_ENOMEM:\n                update_result = ANJAY_SW_MGMT_UPDATE_RESULT_OUT_OF_MEMORY;\n                break;\n            }\n        }\n        change_internal_state_and_update_result(\n                anjay, inst, SW_MGMT_INTERNAL_STATE_IDLE, update_result);\n        return -1;\n    }\n\n    if (obj->downloads_suspended) {\n        _anjay_download_suspend_unlocked(anjay, inst->pull_download_handle);\n    }\n\n    change_internal_state_and_update_result(\n            anjay, inst, SW_MGMT_INTERNAL_STATE_DOWNLOADING,\n            ANJAY_SW_MGMT_UPDATE_RESULT_DOWNLOADING);\n    sw_mgmt_log_inst(INFO, inst->iid, _(\"download started: \") \"%s\",\n                     package_uri);\n    return 0;\n}\n\nvoid anjay_sw_mgmt_pull_suspend(anjay_t *anjay_locked) {\n    assert(anjay_locked);\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    sw_mgmt_object_t *obj =\n            (sw_mgmt_object_t *) _anjay_dm_module_get_arg(anjay,\n                                                          sw_mgmt_delete);\n    if (!obj) {\n        sw_mgmt_log(WARNING, _(\"Software Management object not installed\"));\n    } else {\n        AVS_LIST(sw_mgmt_instance_t) *it;\n        AVS_LIST_FOREACH_PTR(it, &obj->instances) {\n            if ((*it)->pull_download_handle) {\n                _anjay_download_suspend_unlocked(anjay,\n                                                 (*it)->pull_download_handle);\n            }\n        }\n        obj->downloads_suspended = true;\n    }\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n}\n\nint anjay_sw_mgmt_pull_reconnect(anjay_t *anjay_locked) {\n    assert(anjay_locked);\n    int result = -1;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    sw_mgmt_object_t *obj =\n            (sw_mgmt_object_t *) _anjay_dm_module_get_arg(anjay,\n                                                          sw_mgmt_delete);\n    if (!obj) {\n        sw_mgmt_log(WARNING, _(\"Software Management object not installed\"));\n    } else {\n        result = 0;\n        obj->downloads_suspended = false;\n        AVS_LIST(sw_mgmt_instance_t) *it;\n        AVS_LIST_FOREACH_PTR(it, &obj->instances) {\n            if ((*it)->pull_download_handle) {\n                _anjay_update_ret(&result,\n                                  _anjay_download_reconnect_unlocked(\n                                          anjay, (*it)->pull_download_handle));\n            }\n        }\n    }\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return result;\n}\n#    endif // ANJAY_WITH_DOWNLOADER\n\nstatic void initialize_instance(\n        sw_mgmt_instance_t *inst,\n        const anjay_sw_mgmt_instance_initializer_t *instance_initializer) {\n    inst->iid = instance_initializer->iid;\n    inst->inst_ctx = instance_initializer->inst_ctx;\n    inst->internal_state = initial_state_to_internal_state(\n            instance_initializer->initial_state);\n    inst->update_result =\n            initial_state_to_update_result(instance_initializer->initial_state);\n}\n\nstatic void clean_up_instance(anjay_unlocked_t *anjay,\n                              sw_mgmt_instance_t *inst) {\n    (void) anjay;\n    avs_sched_del(&inst->install_and_integrity_jobs_handle);\n\n#    ifdef ANJAY_WITH_DOWNLOADER\n    // HACK: this method is called in sw_mgmt_delete which doesn't have\n    // access to anjay, but in case of whole library deinitialization\n    // all downloads are already cancelled\n    if (anjay) {\n        _anjay_download_abort_unlocked(anjay, inst->pull_download_handle);\n    }\n#    endif // ANJAY_WITH_DOWNLOADER\n}\n\nstatic void insert_instance(sw_mgmt_object_t *obj,\n                            sw_mgmt_instance_t *to_insert) {\n    AVS_LIST(sw_mgmt_instance_t) *it;\n    AVS_LIST_FOREACH_PTR(it, &obj->instances) {\n        assert((*it)->iid != to_insert->iid);\n        if ((*it)->iid > to_insert->iid) {\n            break;\n        }\n    }\n\n    AVS_LIST_INSERT(it, to_insert);\n}\n\nstatic int delete_instance(anjay_unlocked_t *anjay,\n                           sw_mgmt_object_t *obj,\n                           anjay_iid_t iid) {\n    AVS_LIST(sw_mgmt_instance_t) *it;\n    AVS_LIST_FOREACH_PTR(it, &obj->instances) {\n        if ((*it)->iid == iid) {\n            clean_up_instance(anjay, *it);\n            AVS_LIST_DELETE(it);\n            return 0;\n        } else if ((*it)->iid > iid) {\n            break;\n        }\n    }\n\n    return -1;\n}\n\nstatic int sw_mgmt_list_instances(anjay_unlocked_t *anjay,\n                                  const anjay_dm_installed_object_t obj_ptr,\n                                  anjay_unlocked_dm_list_ctx_t *ctx) {\n    (void) anjay;\n\n    sw_mgmt_object_t *obj = get_obj(&obj_ptr);\n    assert(obj);\n\n    AVS_LIST(sw_mgmt_instance_t) it;\n    AVS_LIST_FOREACH(it, obj->instances) {\n        _anjay_dm_emit_unlocked(ctx, it->iid);\n    }\n\n    return 0;\n}\n\nstatic int sw_mgmt_instance_create(anjay_unlocked_t *anjay,\n                                   const anjay_dm_installed_object_t obj_ptr,\n                                   anjay_iid_t iid) {\n    (void) anjay;\n\n    sw_mgmt_object_t *obj = get_obj(&obj_ptr);\n    assert(obj);\n\n    if (!obj->handlers->add_handler) {\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n\n    AVS_LIST(sw_mgmt_instance_t) created =\n            AVS_LIST_NEW_ELEMENT(sw_mgmt_instance_t);\n    if (!created) {\n        _anjay_log_oom();\n        return ANJAY_ERR_INTERNAL;\n    }\n\n    anjay_sw_mgmt_instance_initializer_t instance_initializer = {\n        .initial_state = ANJAY_SW_MGMT_INITIAL_STATE_IDLE,\n        .iid = iid\n    };\n\n    initialize_instance(created, &instance_initializer);\n\n    int result = -1;\n    UNLOCK_FOR_SW_MGMT_CALLBACK(anjay_locked, anjay, created);\n    result = obj->handlers->add_handler(obj->obj_ctx, iid, &created->inst_ctx);\n    LOCK_AFTER_SW_MGMT_CALLBACK(anjay_locked, created);\n\n    if (result) {\n        sw_mgmt_log_inst(\n                DEBUG, created->iid,\n                _(\"attempt to create sw_mgmt instance rejected by user\"));\n        clean_up_instance(anjay, created);\n        AVS_LIST_CLEAR(&created);\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n\n    insert_instance(obj, created);\n\n    return 0;\n}\n\nstatic int sw_mgmt_instance_remove(anjay_unlocked_t *anjay,\n                                   const anjay_dm_installed_object_t obj_ptr,\n                                   anjay_iid_t iid) {\n    (void) anjay;\n\n    sw_mgmt_object_t *obj = get_obj(&obj_ptr);\n    assert(obj);\n\n    if (!obj->handlers->remove_handler) {\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n\n    AVS_LIST(sw_mgmt_instance_t) *it;\n    AVS_LIST_FOREACH_PTR(it, &obj->instances) {\n        if ((*it)->iid == iid) {\n            int result = -1;\n            UNLOCK_FOR_SW_MGMT_CALLBACK(anjay_locked, anjay, *it);\n            result = obj->handlers->remove_handler(obj->obj_ctx, iid,\n                                                   (*it)->inst_ctx);\n            LOCK_AFTER_SW_MGMT_CALLBACK(anjay_locked, *it);\n\n            if (result) {\n                sw_mgmt_log_inst(DEBUG, (*it)->iid,\n                                 _(\"attempt to delete sw_mgmt instance \"\n                                   \"rejected by user\"));\n                return ANJAY_ERR_METHOD_NOT_ALLOWED;\n            }\n\n            clean_up_instance(anjay, *it);\n            AVS_LIST_DELETE(it);\n            return 0;\n        } else if ((*it)->iid > iid) {\n            break;\n        }\n    }\n\n    assert(0);\n    return ANJAY_ERR_NOT_FOUND;\n}\n\nstatic int sw_mgmt_list_resources(anjay_unlocked_t *anjay,\n                                  const anjay_dm_installed_object_t obj_ptr,\n                                  anjay_iid_t iid,\n                                  anjay_unlocked_dm_resource_list_ctx_t *ctx) {\n    (void) anjay;\n    (void) obj_ptr;\n    (void) iid;\n\n    _anjay_dm_emit_res_unlocked(ctx, RID_PKGNAME, ANJAY_DM_RES_R,\n                                ANJAY_DM_RES_PRESENT);\n    _anjay_dm_emit_res_unlocked(ctx, RID_PKGVERSION, ANJAY_DM_RES_R,\n                                ANJAY_DM_RES_PRESENT);\n    _anjay_dm_emit_res_unlocked(ctx, RID_PACKAGE, ANJAY_DM_RES_W,\n                                ANJAY_DM_RES_PRESENT);\n#    ifdef ANJAY_WITH_DOWNLOADER\n    _anjay_dm_emit_res_unlocked(ctx, RID_PACKAGE_URI, ANJAY_DM_RES_W,\n                                ANJAY_DM_RES_PRESENT);\n#    endif // ANJAY_WITH_DOWNLOADER\n    _anjay_dm_emit_res_unlocked(ctx, RID_INSTALL, ANJAY_DM_RES_E,\n                                ANJAY_DM_RES_PRESENT);\n    _anjay_dm_emit_res_unlocked(ctx, RID_UNINSTALL, ANJAY_DM_RES_E,\n                                ANJAY_DM_RES_PRESENT);\n    _anjay_dm_emit_res_unlocked(ctx, RID_UPDATE_STATE, ANJAY_DM_RES_R,\n                                ANJAY_DM_RES_PRESENT);\n    _anjay_dm_emit_res_unlocked(ctx, RID_UPDATE_RESULT, ANJAY_DM_RES_R,\n                                ANJAY_DM_RES_PRESENT);\n    _anjay_dm_emit_res_unlocked(ctx, RID_ACTIVATE, ANJAY_DM_RES_E,\n                                ANJAY_DM_RES_PRESENT);\n    _anjay_dm_emit_res_unlocked(ctx, RID_DEACTIVATE, ANJAY_DM_RES_E,\n                                ANJAY_DM_RES_PRESENT);\n    _anjay_dm_emit_res_unlocked(ctx, RID_ACTIVATION_STATE, ANJAY_DM_RES_R,\n                                ANJAY_DM_RES_PRESENT);\n\n    return 0;\n}\n\nstatic int sw_mgmt_resource_read(anjay_unlocked_t *anjay,\n                                 const anjay_dm_installed_object_t obj_ptr,\n                                 anjay_iid_t iid,\n                                 anjay_rid_t rid,\n                                 anjay_riid_t riid,\n                                 anjay_unlocked_output_ctx_t *ctx) {\n    (void) riid;\n\n    sw_mgmt_object_t *obj = get_obj(&obj_ptr);\n    assert(obj);\n\n    sw_mgmt_instance_t *inst = find_instance(obj, iid);\n    assert(inst);\n\n    switch (rid) {\n    case RID_PKGNAME: {\n        assert(riid == ANJAY_ID_INVALID);\n\n        const char *pkg_name = NULL;\n        if (package_available(inst->internal_state)) {\n            UNLOCK_FOR_SW_MGMT_CALLBACK(anjay_locked, anjay, inst);\n            pkg_name =\n                    obj->handlers->get_name(obj->obj_ctx, iid, inst->inst_ctx);\n            LOCK_AFTER_SW_MGMT_CALLBACK(anjay_locked, inst);\n        }\n\n        return _anjay_ret_string_unlocked(ctx, pkg_name ? pkg_name : \"\");\n    }\n    case RID_PKGVERSION: {\n        assert(riid == ANJAY_ID_INVALID);\n\n        const char *pkg_version = NULL;\n        if (package_available(inst->internal_state)) {\n            UNLOCK_FOR_SW_MGMT_CALLBACK(anjay_locked, anjay, inst);\n            pkg_version = obj->handlers->get_version(obj->obj_ctx, iid,\n                                                     inst->inst_ctx);\n            LOCK_AFTER_SW_MGMT_CALLBACK(anjay_locked, inst);\n        }\n\n        return _anjay_ret_string_unlocked(ctx, pkg_version ? pkg_version : \"\");\n    }\n    case RID_UPDATE_STATE: {\n        assert(riid == ANJAY_ID_INVALID);\n\n        return _anjay_ret_i64_unlocked(ctx, internal_state_to_update_state(\n                                                    inst->internal_state));\n    }\n    case RID_UPDATE_RESULT: {\n        assert(riid == ANJAY_ID_INVALID);\n\n        return _anjay_ret_i64_unlocked(ctx, inst->update_result);\n    }\n    case RID_ACTIVATION_STATE: {\n        assert(riid == ANJAY_ID_INVALID);\n\n        return _anjay_ret_bool_unlocked(ctx, internal_state_is_activated(\n                                                     inst->internal_state));\n    }\n    default:\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\nstatic int sw_mgmt_resource_write(anjay_unlocked_t *anjay,\n                                  const anjay_dm_installed_object_t obj_ptr,\n                                  anjay_iid_t iid,\n                                  anjay_rid_t rid,\n                                  anjay_riid_t riid,\n                                  anjay_unlocked_input_ctx_t *ctx) {\n    (void) anjay;\n    (void) riid;\n\n    sw_mgmt_object_t *obj = get_obj(&obj_ptr);\n    assert(obj);\n\n    sw_mgmt_instance_t *inst = find_instance(obj, iid);\n    assert(inst);\n\n    switch (rid) {\n    case RID_PACKAGE: {\n        assert(riid == ANJAY_ID_INVALID);\n\n        if (inst->internal_state != SW_MGMT_INTERNAL_STATE_IDLE) {\n            return ANJAY_ERR_METHOD_NOT_ALLOWED;\n        }\n\n        return package_push_download(anjay, obj, inst, ctx);\n    }\n#    ifdef ANJAY_WITH_DOWNLOADER\n    case RID_PACKAGE_URI: {\n        assert(riid == ANJAY_ID_INVALID);\n\n        if (inst->internal_state != SW_MGMT_INTERNAL_STATE_IDLE) {\n            return ANJAY_ERR_METHOD_NOT_ALLOWED;\n        }\n\n        char *uri = NULL;\n        if (_anjay_io_fetch_string(ctx, &uri)) {\n            return ANJAY_ERR_INTERNAL;\n        }\n\n        if (!*uri\n                || transport_security_from_uri(uri)\n                               == ANJAY_TRANSPORT_SECURITY_UNDEFINED) {\n            change_internal_state_and_update_result(\n                    anjay, inst, SW_MGMT_INTERNAL_STATE_IDLE,\n                    ANJAY_SW_MGMT_UPDATE_RESULT_INVALID_URI);\n            avs_free(uri);\n            return ANJAY_ERR_BAD_REQUEST;\n        }\n\n        int dl_res = schedule_package_pull_download(anjay, obj, inst, uri);\n        if (dl_res) {\n            sw_mgmt_log_inst(WARNING, iid,\n                             _(\"schedule_package_pull_download failed: \") \"%d\",\n                             dl_res);\n        }\n\n        avs_free(uri);\n        return 0;\n    }\n#    endif // ANJAY_WITH_DOWNLOADER\n    default:\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\nstatic int sw_mgmt_resource_execute(anjay_unlocked_t *anjay,\n                                    const anjay_dm_installed_object_t obj_ptr,\n                                    anjay_iid_t iid,\n                                    anjay_rid_t rid,\n                                    anjay_unlocked_execute_ctx_t *arg_ctx) {\n    (void) arg_ctx;\n\n    sw_mgmt_object_t *obj = get_obj(&obj_ptr);\n    assert(obj);\n\n    sw_mgmt_instance_t *inst = find_instance(obj, iid);\n    assert(inst);\n\n    switch (rid) {\n    case RID_INSTALL: {\n        if (inst->internal_state != SW_MGMT_INTERNAL_STATE_DELIVERED) {\n            return ANJAY_ERR_METHOD_NOT_ALLOWED;\n        }\n\n        if (AVS_SCHED_NOW(_anjay_get_scheduler_unlocked(anjay),\n                          &inst->install_and_integrity_jobs_handle,\n                          pkg_install_job,\n                          &inst,\n                          sizeof(inst))) {\n            sw_mgmt_log_inst(WARNING, inst->iid,\n                             _(\"couldn't schedule pkg_install_job\"));\n            return ANJAY_ERR_INTERNAL;\n        } else {\n            change_internal_state_and_update_result(\n                    anjay, inst, SW_MGMT_INTERNAL_STATE_INSTALLING,\n                    inst->update_result);\n            return 0;\n        }\n    }\n    case RID_UNINSTALL: {\n        int arg;\n        bool has_value;\n\n        switch (_anjay_execute_get_next_arg_unlocked(arg_ctx, &arg,\n                                                     &has_value)) {\n        case 0: {\n            if (has_value\n                    || (arg != UNINSTALL_ARG_UNINSTALL\n                        && arg != UNINSTALL_ARG_FOR_UPDATE)) {\n                return ANJAY_ERR_BAD_REQUEST;\n            }\n\n            // we don't expect more arguments\n            int arg_ignored;\n            if (_anjay_execute_get_next_arg_unlocked(arg_ctx, &arg_ignored,\n                                                     &has_value)\n                    != ANJAY_EXECUTE_GET_ARG_END) {\n                return ANJAY_ERR_BAD_REQUEST;\n            }\n            break;\n        }\n        case ANJAY_EXECUTE_GET_ARG_END: {\n            arg = UNINSTALL_ARG_UNINSTALL;\n            break;\n        }\n        default:\n            return ANJAY_ERR_BAD_REQUEST;\n        }\n\n        if (arg == UNINSTALL_ARG_UNINSTALL) {\n            if (internal_state_is_delivered(inst->internal_state)) {\n                if (inst->internal_state == SW_MGMT_INTERNAL_STATE_INSTALLING) {\n                    // remove potential install job\n                    avs_sched_del(&inst->install_and_integrity_jobs_handle);\n                }\n\n                call_reset(anjay, obj, inst);\n                change_internal_state_and_update_result(\n                        anjay, inst, SW_MGMT_INTERNAL_STATE_IDLE,\n                        inst->update_result);\n                return 0;\n            } else if (internal_state_is_installed(inst->internal_state)) {\n                int result = -1;\n                if (obj->handlers->pkg_uninstall) {\n                    UNLOCK_FOR_SW_MGMT_CALLBACK(anjay_locked, anjay, inst);\n                    result = obj->handlers->pkg_uninstall(obj->obj_ctx, iid,\n                                                          inst->inst_ctx);\n                    LOCK_AFTER_SW_MGMT_CALLBACK(anjay_locked, inst);\n                } else {\n                    return ANJAY_ERR_METHOD_NOT_ALLOWED;\n                }\n\n                if (result) {\n                    return ANJAY_ERR_INTERNAL;\n                } else {\n                    change_internal_state_and_update_result(\n                            anjay, inst, SW_MGMT_INTERNAL_STATE_IDLE,\n                            ANJAY_SW_MGMT_UPDATE_RESULT_INITIAL);\n                    return 0;\n                }\n            } else {\n                return ANJAY_ERR_METHOD_NOT_ALLOWED;\n            }\n        } else {\n            int result = -1;\n            if (obj->handlers->prepare_for_update\n                    && internal_state_is_installed(inst->internal_state)) {\n                UNLOCK_FOR_SW_MGMT_CALLBACK(anjay_locked, anjay, inst);\n                result = obj->handlers->prepare_for_update(obj->obj_ctx, iid,\n                                                           inst->inst_ctx);\n                LOCK_AFTER_SW_MGMT_CALLBACK(anjay_locked, inst);\n            }\n\n            if (result) {\n                return ANJAY_ERR_METHOD_NOT_ALLOWED;\n            } else {\n                change_internal_state_and_update_result(\n                        anjay, inst, SW_MGMT_INTERNAL_STATE_IDLE,\n                        ANJAY_SW_MGMT_UPDATE_RESULT_INITIAL);\n                return 0;\n            }\n        }\n    }\n    case RID_ACTIVATE: {\n        if (!internal_state_is_installed(inst->internal_state)) {\n            return ANJAY_ERR_METHOD_NOT_ALLOWED;\n        }\n\n        int result = 0;\n        if (obj->handlers->activate) {\n            UNLOCK_FOR_SW_MGMT_CALLBACK(anjay_locked, anjay, inst);\n            result = obj->handlers->activate(obj->obj_ctx, iid, inst->inst_ctx);\n            LOCK_AFTER_SW_MGMT_CALLBACK(anjay_locked, inst);\n        }\n\n        if (result) {\n            return ANJAY_ERR_METHOD_NOT_ALLOWED;\n        } else {\n            change_internal_state_and_update_result(\n                    anjay, inst, SW_MGMT_INTERNAL_STATE_INSTALLED_ACTIVATED,\n                    inst->update_result);\n            return 0;\n        }\n    }\n    case RID_DEACTIVATE: {\n        if (!internal_state_is_installed(inst->internal_state)) {\n            return ANJAY_ERR_METHOD_NOT_ALLOWED;\n        }\n\n        int result = 0;\n        if (obj->handlers->deactivate) {\n            UNLOCK_FOR_SW_MGMT_CALLBACK(anjay_locked, anjay, inst);\n            result = obj->handlers->deactivate(obj->obj_ctx, iid,\n                                               inst->inst_ctx);\n            LOCK_AFTER_SW_MGMT_CALLBACK(anjay_locked, inst);\n        }\n\n        if (result) {\n            return ANJAY_ERR_METHOD_NOT_ALLOWED;\n        } else {\n            change_internal_state_and_update_result(\n                    anjay, inst, SW_MGMT_INTERNAL_STATE_INSTALLED_DEACTIVATED,\n                    inst->update_result);\n            return 0;\n        }\n    }\n    default:\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\nstatic int sw_mgmt_transaction_noop(anjay_unlocked_t *anjay,\n                                    const anjay_dm_installed_object_t obj_ptr) {\n    (void) anjay;\n    (void) obj_ptr;\n    return 0;\n}\n\nstatic void sw_mgmt_delete(void *obj_) {\n    sw_mgmt_object_t *obj = (sw_mgmt_object_t *) obj_;\n    AVS_LIST_CLEAR(&obj->instances) {\n        clean_up_instance(NULL, obj->instances);\n    }\n    // NOTE: object will be freed when cleaning the object list\n}\n\nstatic const anjay_unlocked_dm_object_def_t OBJ_DEF = {\n    .oid = OID,\n    .handlers = {\n        .list_instances = sw_mgmt_list_instances,\n        .instance_create = sw_mgmt_instance_create,\n        .instance_remove = sw_mgmt_instance_remove,\n\n        .list_resources = sw_mgmt_list_resources,\n        .resource_read = sw_mgmt_resource_read,\n        .resource_write = sw_mgmt_resource_write,\n        .resource_execute = sw_mgmt_resource_execute,\n\n        .transaction_begin = sw_mgmt_transaction_noop,\n        .transaction_validate = sw_mgmt_transaction_noop,\n        .transaction_commit = sw_mgmt_transaction_noop,\n        .transaction_rollback = sw_mgmt_transaction_noop\n    }\n};\n\nint anjay_sw_mgmt_install(anjay_t *anjay_locked,\n                          const anjay_sw_mgmt_settings_t *settings) {\n    assert(anjay_locked);\n    assert(settings);\n\n    assert(settings->handlers);\n    assert(settings->handlers->stream_open);\n    assert(settings->handlers->stream_write);\n    assert(settings->handlers->stream_finish);\n    assert(settings->handlers->reset);\n    assert(settings->handlers->get_name);\n    assert(settings->handlers->get_version);\n    assert(settings->handlers->pkg_install);\n\n    assert(!!settings->handlers->activate == !!settings->handlers->deactivate);\n\n    int result = -1;\n\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n\n    AVS_LIST(sw_mgmt_object_t) obj = AVS_LIST_NEW_ELEMENT(sw_mgmt_object_t);\n    if (!obj) {\n        _anjay_log_oom();\n    } else {\n        obj->def = &OBJ_DEF;\n        _anjay_dm_installed_object_init_unlocked(&obj->def_ptr, &obj->def);\n        obj->handlers = settings->handlers;\n        obj->obj_ctx = settings->obj_ctx;\n#    if defined(ANJAY_WITH_DOWNLOADER)\n        obj->prefer_same_socket_downloads =\n                settings->prefer_same_socket_downloads;\n#    endif // defined(ANJAY_WITH_DOWNLOADER)\n\n        if (!_anjay_dm_module_install(anjay, sw_mgmt_delete, obj)) {\n            AVS_LIST(anjay_dm_installed_object_t) entry = &obj->def_ptr;\n\n            if (_anjay_register_object_unlocked(anjay, &entry)) {\n                result = _anjay_dm_module_uninstall(anjay, sw_mgmt_delete);\n                assert(!result);\n                result = -1;\n            } else {\n                result = 0;\n            }\n        }\n\n        if (result) {\n            AVS_LIST_CLEAR(&obj);\n        }\n    }\n\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return result;\n}\n\nint anjay_sw_mgmt_get_activation_state(anjay_t *anjay_locked,\n                                       anjay_iid_t iid,\n                                       bool *out_state) {\n    assert(anjay_locked);\n    int result = -1;\n\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n\n    sw_mgmt_object_t *obj =\n            (sw_mgmt_object_t *) _anjay_dm_module_get_arg(anjay,\n                                                          sw_mgmt_delete);\n    if (!obj) {\n        sw_mgmt_log(WARNING, _(\"Software Management object not installed\"));\n    } else {\n        sw_mgmt_instance_t *inst = find_instance(obj, iid);\n        if (!inst) {\n            sw_mgmt_log_inst(ERROR, iid, _(\"instance not found\"));\n        } else {\n            if (internal_state_is_installed(inst->internal_state)) {\n                result = 0;\n                *out_state = inst->internal_state\n                             == SW_MGMT_INTERNAL_STATE_INSTALLED_ACTIVATED;\n            }\n        }\n    }\n\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return result;\n}\n\nint anjay_sw_mgmt_finish_pkg_install(\n        anjay_t *anjay_locked,\n        anjay_iid_t iid,\n        anjay_sw_mgmt_finish_pkg_install_result_t pkg_install_result) {\n    assert(anjay_locked);\n    int result = -1;\n\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n\n    sw_mgmt_object_t *obj =\n            (sw_mgmt_object_t *) _anjay_dm_module_get_arg(anjay,\n                                                          sw_mgmt_delete);\n    if (!obj) {\n        sw_mgmt_log(WARNING, _(\"Software Management object not installed\"));\n    } else {\n        sw_mgmt_instance_t *inst = find_instance(obj, iid);\n        if (!inst) {\n            sw_mgmt_log_inst(ERROR, iid, _(\"instance not found\"));\n        } else {\n            if (inst->internal_state != SW_MGMT_INTERNAL_STATE_INSTALLING) {\n                sw_mgmt_log_inst(\n                        ERROR, inst->iid,\n                        _(\"anjay_sw_mgmt_finish_pkg_install may be \"\n                          \"only called when an installation was scheduled\"));\n            } else if (inst->install_and_integrity_jobs_handle) {\n                sw_mgmt_log_inst(ERROR, inst->iid,\n                                 _(\"cannot set installation result before \"\n                                   \"execution of install handler\"));\n            } else {\n                sw_mgmt_internal_state_t state = SW_MGMT_INTERNAL_STATE_IDLE;\n\n                switch (pkg_install_result) {\n                case ANJAY_SW_MGMT_FINISH_PKG_INSTALL_SUCCESS_INACTIVE:\n                    state = SW_MGMT_INTERNAL_STATE_INSTALLED_DEACTIVATED;\n                    break;\n                case ANJAY_SW_MGMT_FINISH_PKG_INSTALL_SUCCESS_ACTIVE:\n                    state = SW_MGMT_INTERNAL_STATE_INSTALLED_ACTIVATED;\n                    break;\n                case ANJAY_SW_MGMT_FINISH_PKG_INSTALL_FAILURE:\n                    state = SW_MGMT_INTERNAL_STATE_DELIVERED;\n                    break;\n                default:\n                    sw_mgmt_log_inst(ERROR, inst->iid,\n                                     _(\"wrong package install result passed to \"\n                                       \"anjay_sw_mgmt_finish_pkg_install\"));\n                }\n\n                if (state != SW_MGMT_INTERNAL_STATE_IDLE) {\n                    change_internal_state_and_update_result(\n                            anjay, inst, state,\n                            state == SW_MGMT_INTERNAL_STATE_DELIVERED\n                                    ? ANJAY_SW_MGMT_UPDATE_RESULT_INSTALLATION_FAILURE\n                                    : ANJAY_SW_MGMT_UPDATE_RESULT_INSTALLED);\n\n                    result = 0;\n                }\n            }\n        }\n    }\n\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return result;\n}\n\nint anjay_sw_mgmt_add_instance(\n        anjay_t *anjay_locked,\n        const anjay_sw_mgmt_instance_initializer_t *instance_initializer) {\n\n    assert(anjay_locked);\n    assert(instance_initializer);\n    int result = -1;\n\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n\n    sw_mgmt_object_t *obj =\n            (sw_mgmt_object_t *) _anjay_dm_module_get_arg(anjay,\n                                                          sw_mgmt_delete);\n    if (!obj) {\n        sw_mgmt_log(WARNING, _(\"Software Management object not installed\"));\n    } else {\n        anjay_iid_t iid = instance_initializer->iid;\n        if (find_instance(obj, iid)) {\n            sw_mgmt_log_inst(ERROR, iid, _(\"instance already in use\"));\n        } else {\n            AVS_LIST(sw_mgmt_instance_t) created =\n                    AVS_LIST_NEW_ELEMENT(sw_mgmt_instance_t);\n\n            if (!created) {\n                _anjay_log_oom();\n            } else {\n                initialize_instance(created, instance_initializer);\n                insert_instance(obj, created);\n                _anjay_notify_instances_changed_unlocked(anjay, OID);\n                if (created->internal_state\n                        == SW_MGMT_INTERNAL_STATE_DOWNLOADED) {\n                    if (obj->handlers->check_integrity) {\n                        ensure_not_stalled_in_downloaded_state(anjay, obj,\n                                                               created);\n                    } else {\n                        change_internal_state_and_update_result(\n                                anjay, created,\n                                SW_MGMT_INTERNAL_STATE_DELIVERED,\n                                ANJAY_SW_MGMT_UPDATE_RESULT_INITIAL);\n                    }\n                }\n                result = 0;\n            }\n\n            if (result) {\n                AVS_LIST_CLEAR(&created);\n            }\n        }\n    }\n\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return result;\n}\n\nint anjay_sw_mgmt_remove_instance(anjay_t *anjay_locked, anjay_iid_t iid) {\n    assert(anjay_locked);\n    int result = -1;\n\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n\n    sw_mgmt_object_t *obj =\n            (sw_mgmt_object_t *) _anjay_dm_module_get_arg(anjay,\n                                                          sw_mgmt_delete);\n    if (!obj) {\n        sw_mgmt_log(WARNING, _(\"Software Management object not installed\"));\n    } else {\n        sw_mgmt_instance_t *inst = find_instance(obj, iid);\n        if (!inst) {\n            sw_mgmt_log_inst(ERROR, iid, _(\"instance not found\"));\n        } else if (inst->cannot_delete) {\n            sw_mgmt_log_inst(ERROR, iid,\n                             _(\"some callback associated with this instance is \"\n                               \"currently being executed\"));\n            result = 1;\n        } else {\n            result = delete_instance(anjay, obj, iid);\n            if (!result) {\n                _anjay_notify_instances_changed_unlocked(anjay, OID);\n            }\n        }\n    }\n\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return result;\n}\n\n#endif // ANJAY_WITH_MODULE_SW_MGMT\n"
  },
  {
    "path": "standalone/security/standalone_mod_security.c",
    "content": "#include <assert.h>\n#include <stdlib.h>\n#include <string.h>\n\n#include \"standalone_mod_security.h\"\n#include \"standalone_security_transaction.h\"\n#include \"standalone_security_utils.h\"\n\n#ifdef ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT\n#    if !defined(AVS_COMMONS_WITH_AVS_CRYPTO_PKI_ENGINE) \\\n            && !defined(AVS_COMMONS_WITH_AVS_CRYPTO_PSK_ENGINE)\n#        error \"At least one of AVS_COMMONS_WITH_AVS_CRYPTO_PKI_ENGINE or AVS_COMMONS_WITH_AVS_CRYPTO_PSK_ENGINE is required for ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT\"\n#    endif /* !defined(AVS_COMMONS_WITH_AVS_CRYPTO_PKI_ENGINE) && \\\n              !defined(AVS_COMMONS_WITH_AVS_CRYPTO_PSK_ENGINE) */\n#endif     // ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT\n\nstatic const security_rid_t SECURITY_RESOURCE_ID[] = {\n    SEC_RES_LWM2M_SERVER_URI,\n    SEC_RES_BOOTSTRAP_SERVER,\n    SEC_RES_SECURITY_MODE,\n    SEC_RES_PK_OR_IDENTITY,\n    SEC_RES_SERVER_PK,\n    SEC_RES_SECRET_KEY,\n#ifdef ANJAY_WITH_SMS\n    SEC_RES_SMS_SECURITY_MODE,\n    SEC_RES_SMS_BINDING_KEY_PARAMS,\n    SEC_RES_SMS_BINDING_SECRET_KEYS,\n    SEC_RES_SERVER_SMS_NUMBER,\n#endif // ANJAY_WITH_SMS\n    SEC_RES_SHORT_SERVER_ID,\n    SEC_RES_CLIENT_HOLD_OFF_TIME,\n    SEC_RES_BOOTSTRAP_TIMEOUT,\n#ifdef ANJAY_WITH_LWM2M11\n    SEC_RES_MATCHING_TYPE,\n    SEC_RES_SNI,\n    SEC_RES_CERTIFICATE_USAGE,\n    SEC_RES_DTLS_TLS_CIPHERSUITE,\n#endif // ANJAY_WITH_LWM2M11\n#ifdef ANJAY_WITH_COAP_OSCORE\n    SEC_RES_OSCORE_SECURITY_MODE\n#endif // ANJAY_WITH_COAP_OSCORE\n};\n\nvoid _standalone_sec_instance_update_resource_presence(sec_instance_t *inst) {\n    // Sets presence of mandatory resources and updates presence of resources\n    // which presence is not persisted and depends on resource value\n    inst->present_resources[SEC_RES_LWM2M_SERVER_URI] = true;\n    inst->present_resources[SEC_RES_BOOTSTRAP_SERVER] = true;\n    inst->present_resources[SEC_RES_SECURITY_MODE] = true;\n    inst->present_resources[SEC_RES_PK_OR_IDENTITY] = true;\n    inst->present_resources[SEC_RES_SERVER_PK] = true;\n    inst->present_resources[SEC_RES_SECRET_KEY] = true;\n#ifdef ANJAY_WITH_SMS\n    inst->present_resources[SEC_RES_SERVER_SMS_NUMBER] = !!inst->sms_number;\n#endif // ANJAY_WITH_SMS\n    inst->present_resources[SEC_RES_CLIENT_HOLD_OFF_TIME] =\n            (inst->holdoff_s >= 0);\n    inst->present_resources[SEC_RES_BOOTSTRAP_TIMEOUT] =\n            (inst->bs_timeout_s >= 0);\n#ifdef ANJAY_WITH_LWM2M11\n    inst->present_resources[SEC_RES_MATCHING_TYPE] = (inst->matching_type >= 0);\n    inst->present_resources[SEC_RES_SNI] = !!inst->server_name_indication;\n    inst->present_resources[SEC_RES_CERTIFICATE_USAGE] =\n            (inst->certificate_usage >= 0);\n    inst->present_resources[SEC_RES_DTLS_TLS_CIPHERSUITE] = true;\n#endif // ANJAY_WITH_LWM2M11\n}\n\nstatic inline sec_instance_t *find_instance(sec_repr_t *repr, anjay_iid_t iid) {\n    if (!repr) {\n        return NULL;\n    }\n    AVS_LIST(sec_instance_t) it;\n    AVS_LIST_FOREACH(it, repr->instances) {\n        if (it->iid == iid) {\n            return it;\n        } else if (it->iid > iid) {\n            break;\n        }\n    }\n    return NULL;\n}\n\nstatic anjay_iid_t get_new_iid(AVS_LIST(sec_instance_t) instances) {\n    anjay_iid_t iid = 0;\n    AVS_LIST(sec_instance_t) it;\n    AVS_LIST_FOREACH(it, instances) {\n        if (it->iid == iid) {\n            ++iid;\n        } else if (it->iid > iid) {\n            break;\n        }\n    }\n    return iid;\n}\n\nstatic int assign_iid(sec_repr_t *repr, anjay_iid_t *inout_iid) {\n    *inout_iid = get_new_iid(repr->instances);\n    if (*inout_iid == ANJAY_ID_INVALID) {\n        return -1;\n    }\n    return 0;\n}\n\nstatic void init_instance(sec_instance_t *instance, anjay_iid_t iid) {\n    memset(instance, 0, sizeof(sec_instance_t));\n    instance->iid = iid;\n#ifdef ANJAY_WITH_LWM2M11\n    instance->matching_type = -1;\n    instance->certificate_usage = -1;\n#endif // ANJAY_WITH_LWM2M11\n    _standalone_sec_instance_update_resource_presence(instance);\n}\n\nstatic int add_instance(sec_repr_t *repr,\n                        const standalone_security_instance_t *instance,\n                        anjay_iid_t *inout_iid) {\n    if (*inout_iid == ANJAY_ID_INVALID) {\n        if (assign_iid(repr, inout_iid)) {\n            return -1;\n        }\n    } else if (find_instance(repr, *inout_iid)) {\n        return -1;\n    }\n    AVS_LIST(sec_instance_t) new_instance =\n            AVS_LIST_NEW_ELEMENT(sec_instance_t);\n    if (!new_instance) {\n        security_log(ERROR, _(\"out of memory\"));\n        return -1;\n    }\n    init_instance(new_instance, *inout_iid);\n    if (instance->server_uri) {\n        new_instance->server_uri = avs_strdup(instance->server_uri);\n        if (!new_instance->server_uri) {\n            goto error;\n        }\n    }\n    new_instance->is_bootstrap = instance->bootstrap_server;\n    new_instance->security_mode = instance->security_mode;\n    new_instance->holdoff_s = instance->client_holdoff_s;\n    new_instance->bs_timeout_s = instance->bootstrap_timeout_s;\n\n#ifdef ANJAY_WITH_SECURITY_STRUCTURED\n    if ((instance->public_cert_or_psk_identity\n         || instance->public_cert_or_psk_identity_size)\n                    + (instance->public_cert.desc.source\n                       != AVS_CRYPTO_DATA_SOURCE_EMPTY)\n                    + (instance->psk_identity.desc.source\n                       != AVS_CRYPTO_DATA_SOURCE_EMPTY)\n            > 1) {\n        security_log(ERROR, _(\"more than one variant of the Public Key Or \"\n                              \"Identity field specified at the same time\"));\n        goto error;\n    }\n    if (instance->public_cert.desc.source != AVS_CRYPTO_DATA_SOURCE_EMPTY) {\n        if (_standalone_sec_init_certificate_chain_resource(\n                    &new_instance->public_cert_or_psk_identity,\n                    SEC_KEY_AS_KEY_EXTERNAL, &instance->public_cert)) {\n            goto error;\n        }\n    } else if (instance->psk_identity.desc.source\n               != AVS_CRYPTO_DATA_SOURCE_EMPTY) {\n        if (_standalone_sec_init_psk_identity_resource(\n                    &new_instance->public_cert_or_psk_identity,\n                    SEC_KEY_AS_KEY_EXTERNAL, &instance->psk_identity)) {\n            goto error;\n        }\n    } else\n#endif // ANJAY_WITH_SECURITY_STRUCTURED\n    {\n        new_instance->public_cert_or_psk_identity.type = SEC_KEY_AS_DATA;\n        if (_standalone_raw_buffer_clone(\n                    &new_instance->public_cert_or_psk_identity.value.data,\n                    &(const standalone_raw_buffer_t) {\n                        .data = (void *) (intptr_t)\n                                        instance->public_cert_or_psk_identity,\n                        .size = instance->public_cert_or_psk_identity_size\n                    })) {\n            goto error;\n        }\n    }\n\n#ifdef ANJAY_WITH_SECURITY_STRUCTURED\n    if ((instance->private_cert_or_psk_key\n         || instance->private_cert_or_psk_key_size)\n                    + (instance->private_key.desc.source\n                       != AVS_CRYPTO_DATA_SOURCE_EMPTY)\n                    + (instance->psk_key.desc.source\n                       != AVS_CRYPTO_DATA_SOURCE_EMPTY)\n            > 1) {\n        security_log(ERROR, _(\"more than one variant of the Secret Key field \"\n                              \"specified at the same time\"));\n        goto error;\n    }\n    if (instance->private_key.desc.source != AVS_CRYPTO_DATA_SOURCE_EMPTY) {\n        if (_standalone_sec_init_private_key_resource(\n                    &new_instance->private_cert_or_psk_key,\n                    SEC_KEY_AS_KEY_EXTERNAL,\n                    &instance->private_key)) {\n            goto error;\n        }\n    } else if (instance->psk_key.desc.source != AVS_CRYPTO_DATA_SOURCE_EMPTY) {\n        if (_standalone_sec_init_psk_key_resource(\n                    &new_instance->private_cert_or_psk_key,\n                    SEC_KEY_AS_KEY_EXTERNAL,\n                    &instance->psk_key)) {\n            goto error;\n        }\n    } else\n#endif // ANJAY_WITH_SECURITY_STRUCTURED\n    {\n        new_instance->private_cert_or_psk_key.type = SEC_KEY_AS_DATA;\n        if (_standalone_raw_buffer_clone(\n                    &new_instance->private_cert_or_psk_key.value.data,\n                    &(const standalone_raw_buffer_t) {\n                        .data = (void *) (intptr_t)\n                                        instance->private_cert_or_psk_key,\n                        .size = instance->private_cert_or_psk_key_size\n                    })) {\n            goto error;\n        }\n    }\n\n    if (_standalone_raw_buffer_clone(\n                &new_instance->server_public_key,\n                &(const standalone_raw_buffer_t) {\n                    .data = (void *) (intptr_t) instance->server_public_key,\n                    .size = instance->server_public_key_size\n                })) {\n        goto error;\n    }\n\n    if (!new_instance->is_bootstrap) {\n        new_instance->ssid = instance->ssid;\n        new_instance->present_resources[SEC_RES_SHORT_SERVER_ID] = true;\n    }\n\n#ifdef ANJAY_WITH_LWM2M11\n    if (instance->matching_type) {\n        // values higher than INT8_MAX are invalid anyway,\n        // and validation will be done in _standalone_sec_object_validate().\n        // This is simpler than adding another validation here.\n        new_instance->matching_type =\n                (int8_t) AVS_MIN(*instance->matching_type, INT8_MAX);\n    }\n    if (instance->server_name_indication\n            && !(new_instance->server_name_indication =\n                         avs_strdup(instance->server_name_indication))) {\n        security_log(ERROR, _(\"Could not copy SNI: out of memory\"));\n        goto error;\n    }\n    if (instance->certificate_usage) {\n        // same story as with Matching Type\n        new_instance->certificate_usage =\n                (int8_t) AVS_MIN(*instance->certificate_usage, INT8_MAX);\n    }\n    if (instance->ciphersuites.num_ids > ANJAY_ID_INVALID) {\n        security_log(ERROR, _(\"Too many ciphersuites specified\"));\n        goto error;\n    }\n    for (int32_t i = (int32_t) instance->ciphersuites.num_ids - 1; i >= 0;\n         --i) {\n        AVS_LIST(sec_cipher_instance_t) cipher_instance =\n                AVS_LIST_NEW_ELEMENT(sec_cipher_instance_t);\n        if (!cipher_instance) {\n            security_log(ERROR,\n                         _(\"Could not copy ciphersuites: out of memory\"));\n            goto error;\n        }\n        cipher_instance->riid = (anjay_riid_t) i;\n        cipher_instance->cipher_id = instance->ciphersuites.ids[i];\n        AVS_LIST_INSERT(&new_instance->enabled_ciphersuites, cipher_instance);\n    }\n#endif // ANJAY_WITH_LWM2M11\n#ifdef ANJAY_WITH_COAP_OSCORE\n    if (instance->oscore_iid) {\n        new_instance->present_resources[SEC_RES_OSCORE_SECURITY_MODE] = true;\n        new_instance->oscore_iid = *instance->oscore_iid;\n    }\n#endif // ANJAY_WITH_COAP_OSCORE\n\n#ifdef ANJAY_WITH_SMS\n    new_instance->sms_security_mode = instance->sms_security_mode;\n    new_instance->present_resources[SEC_RES_SMS_SECURITY_MODE] =\n            !_standalone_sec_validate_sms_security_mode(\n                    (int32_t) instance->sms_security_mode);\n\n#    ifdef ANJAY_WITH_SECURITY_STRUCTURED\n    if (instance->sms_psk_identity.desc.source\n            != AVS_CRYPTO_DATA_SOURCE_EMPTY) {\n        if (instance->sms_key_parameters || instance->sms_key_parameters_size) {\n            security_log(ERROR,\n                         _(\"more than one variant of the SMS Binding Key \"\n                           \"Parameters field specified at the same time\"));\n            goto error;\n        }\n        if (_standalone_sec_init_psk_identity_resource(\n                    &new_instance->sms_key_params,\n                    SEC_KEY_AS_KEY_EXTERNAL,\n                    &instance->sms_psk_identity)) {\n            goto error;\n        }\n        new_instance->present_resources[SEC_RES_SMS_BINDING_KEY_PARAMS] = true;\n    } else\n#    endif // ANJAY_WITH_SECURITY_STRUCTURED\n    {\n        new_instance->sms_key_params.type = SEC_KEY_AS_DATA;\n        if (_standalone_raw_buffer_clone(\n                    &new_instance->sms_key_params.value.data,\n                    &(const standalone_raw_buffer_t) {\n                        .data = (void *) (intptr_t)\n                                        instance->sms_key_parameters,\n                        .size = instance->sms_key_parameters_size\n                    })) {\n            goto error;\n        }\n        new_instance->present_resources[SEC_RES_SMS_BINDING_KEY_PARAMS] =\n                !!instance->sms_key_parameters;\n    }\n\n#    ifdef ANJAY_WITH_SECURITY_STRUCTURED\n    if (instance->sms_psk_key.desc.source != AVS_CRYPTO_DATA_SOURCE_EMPTY) {\n        if (instance->sms_secret_key || instance->sms_secret_key_size) {\n            security_log(ERROR,\n                         _(\"more than one variant of the SMS Binding Secret \"\n                           \"Key(s) field specified at the same time\"));\n            goto error;\n        }\n        if (_standalone_sec_init_psk_key_resource(&new_instance->sms_secret_key,\n                                                  SEC_KEY_AS_KEY_EXTERNAL,\n                                                  &instance->sms_psk_key)) {\n            goto error;\n        }\n        new_instance->present_resources[SEC_RES_SMS_BINDING_SECRET_KEYS] = true;\n    } else\n#    endif // ANJAY_WITH_SECURITY_STRUCTURED\n    {\n        new_instance->sms_secret_key.type = SEC_KEY_AS_DATA;\n        if (_standalone_raw_buffer_clone(\n                    &new_instance->sms_secret_key.value.data,\n                    &(const standalone_raw_buffer_t) {\n                        .data = (void *) (intptr_t) instance->sms_secret_key,\n                        .size = instance->sms_secret_key_size\n                    })) {\n            goto error;\n        }\n        new_instance->present_resources[SEC_RES_SMS_BINDING_SECRET_KEYS] =\n                !!instance->sms_secret_key;\n    }\n\n    if (instance->server_sms_number) {\n        new_instance->sms_number = avs_strdup(instance->server_sms_number);\n    }\n#endif // ANJAY_WITH_SMS\n\n    _standalone_sec_instance_update_resource_presence(new_instance);\n\n    AVS_LIST(sec_instance_t) *ptr;\n    AVS_LIST_FOREACH_PTR(ptr, &repr->instances) {\n        if ((*ptr)->iid > new_instance->iid) {\n            break;\n        }\n    }\n    AVS_LIST_INSERT(ptr, new_instance);\n\n    if (instance->bootstrap_server) {\n        security_log(INFO,\n                     _(\"Added instance \") \"%u\" _(\" (bootstrap, URI: \") \"%s\" _(\n                             \")\"),\n                     *inout_iid, instance->server_uri);\n    } else {\n        security_log(INFO,\n                     _(\"Added instance \") \"%u\" _(\" (SSID: \") \"%u\" _(\n                             \", URI: \") \"%s\" _(\")\"),\n                     *inout_iid, instance->ssid, instance->server_uri);\n    }\n\n    _standalone_sec_mark_modified(repr);\n    return 0;\n\nerror:\n    _standalone_sec_destroy_instances(&new_instance, true);\n    return -1;\n}\n\nstatic int del_instance(sec_repr_t *repr, anjay_iid_t iid) {\n    AVS_LIST(sec_instance_t) *it;\n    AVS_LIST_FOREACH_PTR(it, &repr->instances) {\n        if ((*it)->iid == iid) {\n            AVS_LIST(sec_instance_t) element = AVS_LIST_DETACH(it);\n            _standalone_sec_destroy_instances(&element, true);\n            _standalone_sec_mark_modified(repr);\n            return 0;\n        }\n    }\n\n    assert(0);\n    return ANJAY_ERR_NOT_FOUND;\n}\n\nstatic int sec_list_resources(anjay_t *anjay,\n                              const anjay_dm_object_def_t *const *obj_ptr,\n                              anjay_iid_t iid,\n                              anjay_dm_resource_list_ctx_t *ctx) {\n    (void) anjay;\n    const sec_instance_t *inst =\n            find_instance(_standalone_sec_get(obj_ptr), iid);\n    assert(inst);\n\n    for (size_t resource = 0; resource < AVS_ARRAY_SIZE(SECURITY_RESOURCE_ID);\n         resource++) {\n        const anjay_rid_t rid = SECURITY_RESOURCE_ID[resource];\n        anjay_dm_emit_res(ctx, rid,\n#ifdef ANJAY_WITH_LWM2M11\n                          rid != SEC_RES_DTLS_TLS_CIPHERSUITE ? ANJAY_DM_RES_R\n                                                              : ANJAY_DM_RES_RM,\n#else\n                          ANJAY_DM_RES_R,\n#endif // ANJAY_WITH_LWM2M11\n                          inst->present_resources[rid] ? ANJAY_DM_RES_PRESENT\n                                                       : ANJAY_DM_RES_ABSENT);\n    }\n\n    return 0;\n}\n\n#ifdef ANJAY_WITH_LWM2M11\nstatic int\nsec_list_resource_instances(anjay_t *anjay,\n                            const anjay_dm_object_def_t *const *obj_ptr,\n                            anjay_iid_t iid,\n                            anjay_rid_t rid,\n                            anjay_dm_list_ctx_t *ctx) {\n    (void) anjay;\n\n    assert(rid == SEC_RES_DTLS_TLS_CIPHERSUITE);\n    (void) rid;\n\n    const sec_instance_t *inst =\n            find_instance(_standalone_sec_get(obj_ptr), iid);\n    assert(inst);\n\n    AVS_LIST(sec_cipher_instance_t) it;\n    AVS_LIST_FOREACH(it, inst->enabled_ciphersuites) {\n        anjay_dm_emit(ctx, it->riid);\n    }\n\n    return 0;\n}\n\nstatic AVS_LIST(sec_cipher_instance_t) *\nfind_cipher_instance_insert_ptr(AVS_LIST(sec_cipher_instance_t) *instances,\n                                anjay_riid_t riid) {\n    AVS_LIST(sec_cipher_instance_t) *it;\n    AVS_LIST_FOREACH_PTR(it, instances) {\n        if ((*it)->riid >= riid) {\n            break;\n        }\n    }\n    return it;\n}\n\nstatic AVS_LIST(sec_cipher_instance_t)\nfind_cipher_instance(AVS_LIST(sec_cipher_instance_t) instances,\n                     anjay_riid_t riid) {\n    AVS_LIST(sec_cipher_instance_t) *it =\n            find_cipher_instance_insert_ptr(&instances, riid);\n    if (it && (*it)->riid == riid) {\n        return *it;\n    }\n    return NULL;\n}\n#endif // ANJAY_WITH_LWM2M11\n\nstatic int ret_sec_key_or_data(anjay_output_ctx_t *ctx,\n                               const sec_key_or_data_t *res) {\n    switch (res->type) {\n    case SEC_KEY_AS_DATA:\n        return anjay_ret_bytes(ctx, res->value.data.data, res->value.data.size);\n#if defined(ANJAY_WITH_SECURITY_STRUCTURED) \\\n        || defined(ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT)\n    case SEC_KEY_AS_KEY_EXTERNAL:\n    case SEC_KEY_AS_KEY_OWNED:\n        switch (res->value.key.info.type) {\n        case AVS_CRYPTO_SECURITY_INFO_CERTIFICATE_CHAIN:\n            return anjay_ret_certificate_chain_info(\n                    ctx, (avs_crypto_certificate_chain_info_t) {\n                             .desc = res->value.key.info\n                         });\n        case AVS_CRYPTO_SECURITY_INFO_PRIVATE_KEY:\n            return anjay_ret_private_key_info(ctx,\n                                              (avs_crypto_private_key_info_t) {\n                                                  .desc = res->value.key.info\n                                              });\n        case AVS_CRYPTO_SECURITY_INFO_CERT_REVOCATION_LIST:\n            AVS_UNREACHABLE(\"unsupported tag\");\n            return -1;\n        case AVS_CRYPTO_SECURITY_INFO_PSK_IDENTITY:\n            return anjay_ret_psk_identity_info(\n                    ctx, (avs_crypto_psk_identity_info_t) {\n                             .desc = res->value.key.info\n                         });\n        case AVS_CRYPTO_SECURITY_INFO_PSK_KEY:\n            return anjay_ret_psk_key_info(ctx, (avs_crypto_psk_key_info_t) {\n                                                   .desc = res->value.key.info\n                                               });\n        }\n#endif /* defined(ANJAY_WITH_SECURITY_STRUCTURED) || \\\n          defined(ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT) */\n        // fall-through\n    default:\n        AVS_UNREACHABLE(\"invalid value of sec_key_or_data_type_t\");\n        return ANJAY_ERR_INTERNAL;\n    }\n}\n\nstatic int sec_read(anjay_t *anjay,\n                    const anjay_dm_object_def_t *const *obj_ptr,\n                    anjay_iid_t iid,\n                    anjay_rid_t rid,\n                    anjay_riid_t riid,\n                    anjay_output_ctx_t *ctx) {\n    (void) anjay;\n    (void) riid;\n#ifdef ANJAY_WITH_LWM2M11\n    assert(riid == ANJAY_ID_INVALID || rid == SEC_RES_DTLS_TLS_CIPHERSUITE);\n#else  // ANJAY_WITH_LWM2M11\n    assert(riid == ANJAY_ID_INVALID);\n#endif // ANJAY_WITH_LWM2M11\n\n    const sec_instance_t *inst =\n            find_instance(_standalone_sec_get(obj_ptr), iid);\n    assert(inst);\n\n    switch ((security_rid_t) rid) {\n    case SEC_RES_LWM2M_SERVER_URI:\n        return anjay_ret_string(ctx, inst->server_uri);\n    case SEC_RES_BOOTSTRAP_SERVER:\n        return anjay_ret_bool(ctx, inst->is_bootstrap);\n    case SEC_RES_SECURITY_MODE:\n        return anjay_ret_i64(ctx, (int32_t) inst->security_mode);\n    case SEC_RES_SERVER_PK:\n        return anjay_ret_bytes(ctx, inst->server_public_key.data,\n                               inst->server_public_key.size);\n    case SEC_RES_PK_OR_IDENTITY:\n        return ret_sec_key_or_data(ctx, &inst->public_cert_or_psk_identity);\n    case SEC_RES_SECRET_KEY:\n        return ret_sec_key_or_data(ctx, &inst->private_cert_or_psk_key);\n    case SEC_RES_SHORT_SERVER_ID:\n        return anjay_ret_i64(ctx, (int32_t) inst->ssid);\n    case SEC_RES_CLIENT_HOLD_OFF_TIME:\n        return anjay_ret_i64(ctx, inst->holdoff_s);\n    case SEC_RES_BOOTSTRAP_TIMEOUT:\n        return anjay_ret_i64(ctx, inst->bs_timeout_s);\n#ifdef ANJAY_WITH_SMS\n    case SEC_RES_SMS_SECURITY_MODE:\n        return anjay_ret_i64(ctx, (int32_t) inst->sms_security_mode);\n    case SEC_RES_SMS_BINDING_KEY_PARAMS:\n        return ret_sec_key_or_data(ctx, &inst->sms_key_params);\n    case SEC_RES_SMS_BINDING_SECRET_KEYS:\n        return ret_sec_key_or_data(ctx, &inst->sms_secret_key);\n    case SEC_RES_SERVER_SMS_NUMBER:\n        return anjay_ret_string(ctx, inst->sms_number);\n#endif // ANJAY_WITH_SMS\n#ifdef ANJAY_WITH_LWM2M11\n    case SEC_RES_MATCHING_TYPE:\n        return anjay_ret_u64(ctx, (uint64_t) (uint32_t) inst->matching_type);\n    case SEC_RES_SNI:\n        assert(inst->server_name_indication);\n        return anjay_ret_string(ctx, inst->server_name_indication);\n    case SEC_RES_CERTIFICATE_USAGE:\n        return anjay_ret_u64(ctx,\n                             (uint64_t) (uint32_t) inst->certificate_usage);\n    case SEC_RES_DTLS_TLS_CIPHERSUITE: {\n        AVS_LIST(const sec_cipher_instance_t) rinst =\n                find_cipher_instance(inst->enabled_ciphersuites, riid);\n        if (!rinst) {\n            return ANJAY_ERR_NOT_FOUND;\n        }\n        return anjay_ret_u64(ctx, rinst->cipher_id);\n    }\n#endif // ANJAY_WITH_LWM2M11\n#ifdef ANJAY_WITH_COAP_OSCORE\n    case SEC_RES_OSCORE_SECURITY_MODE:\n        return anjay_ret_objlnk(ctx, ANJAY_DM_OID_OSCORE, inst->oscore_iid);\n#endif // ANJAY_WITH_COAP_OSCORE\n    default:\n        AVS_UNREACHABLE(\"Read handler called on unknown Security resource\");\n        return ANJAY_ERR_NOT_IMPLEMENTED;\n    }\n}\n\n#ifdef ANJAY_WITH_LWM2M11\nstatic AVS_LIST(sec_cipher_instance_t)\nfind_or_create_cipher_instance(AVS_LIST(sec_cipher_instance_t) *instances,\n                               anjay_riid_t riid) {\n    AVS_LIST(sec_cipher_instance_t) *it =\n            find_cipher_instance_insert_ptr(instances, riid);\n\n    AVS_LIST(sec_cipher_instance_t) cipher =\n            AVS_LIST_INSERT_NEW(sec_cipher_instance_t, it);\n    if (cipher) {\n        cipher->riid = riid;\n    }\n    return cipher;\n}\n#endif // ANJAY_WITH_LWM2M11\n\nstatic int fetch_sec_key_or_data(anjay_input_ctx_t *ctx,\n                                 sec_key_or_data_t *res) {\n    _standalone_sec_key_or_data_cleanup(res, true);\n    assert(res->type == SEC_KEY_AS_DATA);\n    assert(!res->prev_ref);\n    assert(!res->next_ref);\n    return _standalone_io_fetch_bytes(ctx, &res->value.data);\n}\n\nstatic int sec_write(anjay_t *anjay,\n                     const anjay_dm_object_def_t *const *obj_ptr,\n                     anjay_iid_t iid,\n                     anjay_rid_t rid,\n                     anjay_riid_t riid,\n                     anjay_input_ctx_t *ctx) {\n    (void) anjay;\n    (void) riid;\n#ifdef ANJAY_WITH_LWM2M11\n    assert(riid == ANJAY_ID_INVALID || rid == SEC_RES_DTLS_TLS_CIPHERSUITE);\n#else  // ANJAY_WITH_LWM2M11\n    assert(riid == ANJAY_ID_INVALID);\n#endif // ANJAY_WITH_LWM2M11\n    sec_repr_t *repr = _standalone_sec_get(obj_ptr);\n    sec_instance_t *inst = find_instance(repr, iid);\n    int retval;\n    assert(inst);\n\n    _standalone_sec_mark_modified(repr);\n\n    switch ((security_rid_t) rid) {\n    case SEC_RES_LWM2M_SERVER_URI:\n        retval = _standalone_io_fetch_string(ctx, &inst->server_uri);\n        break;\n    case SEC_RES_BOOTSTRAP_SERVER:\n        retval = anjay_get_bool(ctx, &inst->is_bootstrap);\n        break;\n    case SEC_RES_SECURITY_MODE:\n        retval = _standalone_sec_fetch_security_mode(ctx, &inst->security_mode);\n        break;\n    case SEC_RES_PK_OR_IDENTITY:\n        retval = fetch_sec_key_or_data(ctx, &inst->public_cert_or_psk_identity);\n        break;\n    case SEC_RES_SERVER_PK:\n        retval = _standalone_io_fetch_bytes(ctx, &inst->server_public_key);\n        break;\n    case SEC_RES_SECRET_KEY:\n        retval = fetch_sec_key_or_data(ctx, &inst->private_cert_or_psk_key);\n        break;\n    case SEC_RES_SHORT_SERVER_ID:\n        retval = _standalone_sec_fetch_short_server_id(ctx, &inst->ssid);\n        break;\n    case SEC_RES_CLIENT_HOLD_OFF_TIME:\n        retval = anjay_get_i32(ctx, &inst->holdoff_s);\n        break;\n    case SEC_RES_BOOTSTRAP_TIMEOUT:\n        retval = anjay_get_i32(ctx, &inst->bs_timeout_s);\n        break;\n#ifdef ANJAY_WITH_SMS\n    case SEC_RES_SMS_SECURITY_MODE:\n        retval = _standalone_sec_fetch_sms_security_mode(\n                ctx, &inst->sms_security_mode);\n        break;\n    case SEC_RES_SMS_BINDING_KEY_PARAMS:\n        retval = fetch_sec_key_or_data(ctx, &inst->sms_key_params);\n        break;\n    case SEC_RES_SMS_BINDING_SECRET_KEYS:\n        retval = fetch_sec_key_or_data(ctx, &inst->sms_secret_key);\n        break;\n    case SEC_RES_SERVER_SMS_NUMBER:\n        retval = _standalone_io_fetch_string(ctx, &inst->sms_number);\n        break;\n#endif // ANJAY_WITH_SMS\n#ifdef ANJAY_WITH_LWM2M11\n    case SEC_RES_MATCHING_TYPE: {\n        uint32_t matching_type;\n        if (!(retval = anjay_get_u32(ctx, &matching_type))) {\n            if (matching_type > 3) {\n                retval = ANJAY_ERR_BAD_REQUEST;\n            } else {\n                inst->matching_type = (int8_t) matching_type;\n            }\n        }\n        break;\n    }\n    case SEC_RES_SNI:\n        retval =\n                _standalone_io_fetch_string(ctx, &inst->server_name_indication);\n        break;\n    case SEC_RES_CERTIFICATE_USAGE: {\n        uint32_t certificate_usage;\n        if (!(retval = anjay_get_u32(ctx, &certificate_usage))) {\n            if (certificate_usage > 3) {\n                retval = ANJAY_ERR_BAD_REQUEST;\n            } else {\n                inst->certificate_usage = (int8_t) certificate_usage;\n            }\n        }\n        break;\n    }\n#    ifdef ANJAY_WITH_COAP_OSCORE\n    case SEC_RES_OSCORE_SECURITY_MODE: {\n        anjay_oid_t oid;\n        if (!(retval = anjay_get_objlnk(ctx, &oid, &inst->oscore_iid))\n                && oid != ANJAY_DM_OID_OSCORE) {\n            retval = ANJAY_ERR_BAD_REQUEST;\n        }\n        break;\n    }\n#    endif // ANJAY_WITH_COAP_OSCORE\n    case SEC_RES_DTLS_TLS_CIPHERSUITE: {\n        uint32_t cipher_id;\n        if (!(retval = anjay_get_u32(ctx, &cipher_id))) {\n            if (cipher_id == 0) {\n                security_log(\n                        WARNING,\n                        _(\"TLS-NULL-WITH-NULL-NULL cipher is not allowed\"));\n                retval = ANJAY_ERR_BAD_REQUEST;\n            } else if (cipher_id > UINT16_MAX) {\n                security_log(WARNING,\n                             _(\"Ciphersuite ID > 65535 is not allowed\"));\n                retval = ANJAY_ERR_BAD_REQUEST;\n            } else {\n                AVS_LIST(sec_cipher_instance_t) cipher =\n                        find_or_create_cipher_instance(\n                                &inst->enabled_ciphersuites, riid);\n                if (!cipher) {\n                    retval = ANJAY_ERR_INTERNAL;\n                } else {\n                    cipher->cipher_id = cipher_id;\n                }\n            }\n        }\n        break;\n    }\n#endif // ANJAY_WITH_LWM2M11\n    default:\n        AVS_UNREACHABLE(\"Write handler called on unknown Security resource\");\n        return ANJAY_ERR_NOT_FOUND;\n    }\n\n    if (!retval) {\n        inst->present_resources[rid] = true;\n    }\n\n    return retval;\n}\n\n#ifdef ANJAY_WITH_LWM2M11\nstatic int sec_resource_reset(anjay_t *anjay,\n                              const anjay_dm_object_def_t *const *obj_ptr,\n                              anjay_iid_t iid,\n                              anjay_rid_t rid) {\n    (void) anjay;\n\n    assert(rid == SEC_RES_DTLS_TLS_CIPHERSUITE);\n    (void) rid;\n\n    const sec_instance_t *inst =\n            find_instance(_standalone_sec_get(obj_ptr), iid);\n    assert(inst);\n\n    AVS_LIST_CLEAR(&inst->enabled_ciphersuites);\n    return 0;\n}\n\n#    ifdef ANJAY_WITH_LWM2M12\nstatic int\nsec_resource_instance_remove(anjay_t *anjay,\n                             const anjay_dm_object_def_t *const *obj_ptr,\n                             anjay_iid_t iid,\n                             anjay_rid_t rid,\n                             anjay_riid_t riid) {\n    (void) anjay;\n\n    assert(rid == SEC_RES_DTLS_TLS_CIPHERSUITE);\n    (void) rid;\n\n    sec_instance_t *inst = find_instance(_standalone_sec_get(obj_ptr), iid);\n    assert(inst);\n    AVS_LIST(sec_cipher_instance_t) *rinst_ptr =\n            find_cipher_instance_insert_ptr(&inst->enabled_ciphersuites, riid);\n    assert(rinst_ptr && *rinst_ptr && (*rinst_ptr)->riid);\n    AVS_LIST_DELETE(rinst_ptr);\n    return 0;\n}\n#    endif // ANJAY_WITH_LWM2M12\n#endif     // ANJAY_WITH_LWM2M11\n\nstatic int sec_list_instances(anjay_t *anjay,\n                              const anjay_dm_object_def_t *const *obj_ptr,\n                              anjay_dm_list_ctx_t *ctx) {\n    (void) anjay;\n\n    sec_repr_t *repr = _standalone_sec_get(obj_ptr);\n    AVS_LIST(sec_instance_t) it;\n    AVS_LIST_FOREACH(it, repr->instances) {\n        anjay_dm_emit(ctx, it->iid);\n    }\n    return 0;\n}\n\nstatic int sec_instance_create(anjay_t *anjay,\n                               const anjay_dm_object_def_t *const *obj_ptr,\n                               anjay_iid_t iid) {\n    (void) anjay;\n    sec_repr_t *repr = _standalone_sec_get(obj_ptr);\n    assert(iid != ANJAY_ID_INVALID);\n\n    AVS_LIST(sec_instance_t) created = AVS_LIST_NEW_ELEMENT(sec_instance_t);\n    if (!created) {\n        return ANJAY_ERR_INTERNAL;\n    }\n\n    init_instance(created, iid);\n\n    AVS_LIST(sec_instance_t) *ptr;\n    AVS_LIST_FOREACH_PTR(ptr, &repr->instances) {\n        if ((*ptr)->iid > created->iid) {\n            break;\n        }\n    }\n\n    AVS_LIST_INSERT(ptr, created);\n    _standalone_sec_mark_modified(repr);\n    return 0;\n}\n\nstatic int sec_instance_remove(anjay_t *anjay,\n                               const anjay_dm_object_def_t *const *obj_ptr,\n                               anjay_iid_t iid) {\n    (void) anjay;\n    return del_instance(_standalone_sec_get(obj_ptr), iid);\n}\n\nstatic int sec_transaction_begin(anjay_t *anjay,\n                                 const anjay_dm_object_def_t *const *obj_ptr) {\n    (void) anjay;\n    return _standalone_sec_transaction_begin_impl(_standalone_sec_get(obj_ptr));\n}\n\nstatic int sec_transaction_commit(anjay_t *anjay,\n                                  const anjay_dm_object_def_t *const *obj_ptr) {\n    (void) anjay;\n    return _standalone_sec_transaction_commit_impl(\n            _standalone_sec_get(obj_ptr));\n}\n\nstatic int\nsec_transaction_validate(anjay_t *anjay,\n                         const anjay_dm_object_def_t *const *obj_ptr) {\n    (void) anjay;\n    return _standalone_sec_transaction_validate_impl(anjay, _standalone_sec_get(\n                                                                    obj_ptr));\n}\n\nstatic int\nsec_transaction_rollback(anjay_t *anjay,\n                         const anjay_dm_object_def_t *const *obj_ptr) {\n    (void) anjay;\n    return _standalone_sec_transaction_rollback_impl(\n            _standalone_sec_get(obj_ptr));\n}\n\nstatic int sec_instance_reset(anjay_t *anjay,\n                              const anjay_dm_object_def_t *const *obj_ptr,\n                              anjay_iid_t iid) {\n    (void) anjay;\n    sec_instance_t *inst = find_instance(_standalone_sec_get(obj_ptr), iid);\n    assert(inst);\n\n    _standalone_sec_destroy_instance_fields(inst, true);\n    init_instance(inst, iid);\n    return 0;\n}\n\nstatic const anjay_dm_object_def_t SECURITY = {\n    .oid = ANJAY_DM_OID_SECURITY,\n    .handlers = {\n        .list_instances = sec_list_instances,\n        .instance_create = sec_instance_create,\n        .instance_remove = sec_instance_remove,\n        .instance_reset = sec_instance_reset,\n        .list_resources = sec_list_resources,\n#ifdef ANJAY_WITH_LWM2M11\n        .list_resource_instances = sec_list_resource_instances,\n#endif // ANJAY_WITH_LWM2M11\n        .resource_read = sec_read,\n        .resource_write = sec_write,\n#ifdef ANJAY_WITH_LWM2M11\n        .resource_reset = sec_resource_reset,\n#endif // ANJAY_WITH_LWM2M11\n        .transaction_begin = sec_transaction_begin,\n        .transaction_commit = sec_transaction_commit,\n        .transaction_validate = sec_transaction_validate,\n        .transaction_rollback = sec_transaction_rollback\n#ifdef ANJAY_WITH_LWM2M12\n        ,\n        .resource_instance_remove = sec_resource_instance_remove\n#endif // ANJAY_WITH_LWM2M12\n    }\n};\n\nsec_repr_t *_standalone_sec_get(const anjay_dm_object_def_t *const *obj_ptr) {\n    assert(obj_ptr && *obj_ptr == &SECURITY);\n    return AVS_CONTAINER_OF(obj_ptr, sec_repr_t, def);\n}\n\nint standalone_security_object_add_instance(\n        const anjay_dm_object_def_t *const *obj_ptr,\n        const standalone_security_instance_t *instance,\n        anjay_iid_t *inout_iid) {\n    int retval = -1;\n    sec_repr_t *repr = _standalone_sec_get(obj_ptr);\n    if (!repr) {\n        security_log(ERROR, _(\"Security object is not registered\"));\n        retval = -1;\n    } else {\n        const bool modified_since_persist = repr->modified_since_persist;\n        if (!(retval = add_instance(repr, instance, inout_iid))\n                && (retval = _standalone_sec_object_validate_and_process_keys(\n                            repr->anjay, repr))) {\n            (void) del_instance(repr, *inout_iid);\n            if (!modified_since_persist) {\n                /* validation failed and so in the end no instace is added */\n                _standalone_sec_clear_modified(repr);\n            }\n        }\n\n        if (!retval) {\n            if (anjay_notify_instances_changed(repr->anjay, SECURITY.oid)) {\n                security_log(WARNING, _(\"Could not schedule socket reload\"));\n            }\n        }\n    }\n    return retval;\n}\n\nvoid standalone_security_object_cleanup(\n        const anjay_dm_object_def_t *const *obj_ptr) {\n    sec_repr_t *repr = _standalone_sec_get(obj_ptr);\n    if (repr->in_transaction) {\n        _standalone_sec_destroy_instances(&repr->instances, true);\n        _standalone_sec_destroy_instances(&repr->saved_instances,\n                                          repr->saved_modified_since_persist);\n    } else {\n        assert(!repr->saved_instances);\n        _standalone_sec_destroy_instances(&repr->instances,\n                                          repr->modified_since_persist);\n    }\n#ifdef ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT\n    if (repr->prng_ctx && !repr->prng_allocated_by_user) {\n        avs_crypto_prng_free(&repr->prng_ctx);\n    }\n#endif // ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT\n    avs_free(repr);\n}\n\nvoid standalone_security_object_purge(\n        const anjay_dm_object_def_t *const *obj_ptr) {\n    sec_repr_t *repr = _standalone_sec_get(obj_ptr);\n\n    if (!repr) {\n        security_log(ERROR, _(\"Security object is not registered\"));\n    } else {\n        if (repr->instances) {\n            _standalone_sec_mark_modified(repr);\n        }\n        _standalone_sec_destroy_instances(&repr->saved_instances, true);\n        _standalone_sec_destroy_instances(&repr->instances, true);\n        if (anjay_notify_instances_changed(repr->anjay, SECURITY.oid)) {\n            security_log(WARNING, _(\"Could not schedule socket reload\"));\n        }\n    }\n}\n\nbool standalone_security_object_is_modified(\n        const anjay_dm_object_def_t *const *obj_ptr) {\n    bool result = false;\n    sec_repr_t *repr = _standalone_sec_get(obj_ptr);\n    if (!repr) {\n        security_log(ERROR, _(\"Security object is not registered\"));\n    } else {\n        if (repr->in_transaction) {\n            result = repr->saved_modified_since_persist;\n        } else {\n            result = repr->modified_since_persist;\n        }\n    }\n    return result;\n}\n\nconst anjay_dm_object_def_t **\nstandalone_security_object_install(anjay_t *anjay) {\n    sec_repr_t *repr = (sec_repr_t *) avs_calloc(1, sizeof(sec_repr_t));\n    if (!repr) {\n        security_log(ERROR, _(\"out of memory\"));\n        return NULL;\n    }\n    repr->def = &SECURITY;\n    repr->anjay = anjay;\n    if (anjay_register_object(anjay, &repr->def)) {\n        avs_free(repr);\n        return NULL;\n    }\n    return &repr->def;\n}\n\n#ifdef ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT\nconst anjay_dm_object_def_t **standalone_security_object_install_with_hsm(\n        anjay_t *anjay,\n        const standalone_security_hsm_configuration_t *hsm_config,\n        avs_crypto_prng_ctx_t *prng_ctx) {\n    assert(anjay);\n    bool prng_allocated_by_user = !!prng_ctx;\n    if (!prng_allocated_by_user) {\n        prng_ctx = avs_crypto_prng_new(NULL, NULL);\n        if (!prng_ctx) {\n            security_log(ERROR, _(\"Could not create PRNG context\"));\n            return NULL;\n        }\n    }\n    const anjay_dm_object_def_t **result =\n            standalone_security_object_install(anjay);\n    if (result && hsm_config) {\n        sec_repr_t *repr = _standalone_sec_get(result);\n        repr->hsm_config = *hsm_config;\n        repr->prng_ctx = prng_ctx;\n        repr->prng_allocated_by_user = prng_allocated_by_user;\n    }\n    return result;\n}\n\nstatic void\nmark_hsm_sec_key_or_data_permanent(sec_repr_t *repr,\n                                   sec_key_or_data_t *sec_key_or_data) {\n    if (sec_key_or_data->type == SEC_KEY_AS_KEY_OWNED) {\n        sec_key_or_data->type = SEC_KEY_AS_KEY_EXTERNAL;\n        repr->modified_since_persist = true;\n        for (sec_key_or_data_t *it = sec_key_or_data->prev_ref; it;\n             it = it->prev_ref) {\n            it->type = SEC_KEY_AS_KEY_EXTERNAL;\n            if (repr->in_transaction) {\n                repr->saved_modified_since_persist = true;\n            }\n        }\n        for (sec_key_or_data_t *it = sec_key_or_data->next_ref; it;\n             it = it->next_ref) {\n            it->type = SEC_KEY_AS_KEY_EXTERNAL;\n            if (repr->in_transaction) {\n                repr->saved_modified_since_persist = true;\n            }\n        }\n    }\n}\n\nstatic void mark_hsm_instance_permanent(sec_repr_t *repr,\n                                        sec_instance_t *instance) {\n    mark_hsm_sec_key_or_data_permanent(repr,\n                                       &instance->public_cert_or_psk_identity);\n    mark_hsm_sec_key_or_data_permanent(repr,\n                                       &instance->private_cert_or_psk_key);\n#    ifdef ANJAY_WITH_SMS\n    mark_hsm_sec_key_or_data_permanent(repr, &instance->sms_key_params);\n    mark_hsm_sec_key_or_data_permanent(repr, &instance->sms_secret_key);\n#    endif // ANJAY_WITH_SMS\n}\n\nvoid standalone_security_mark_hsm_permanent(\n        const anjay_dm_object_def_t *const *obj_ptr, anjay_ssid_t ssid) {\n    sec_repr_t *repr = _standalone_sec_get(obj_ptr);\n    if (!repr) {\n        security_log(ERROR, _(\"Security object is not registered\"));\n    } else {\n        AVS_LIST(sec_instance_t) instance;\n        AVS_LIST_FOREACH(instance, repr->instances) {\n            if (ssid == ANJAY_SSID_ANY\n                    || (ssid == ANJAY_SSID_BOOTSTRAP\n                        && instance->present_resources[SEC_RES_BOOTSTRAP_SERVER]\n                        && instance->is_bootstrap)\n                    || ((!instance->present_resources[SEC_RES_BOOTSTRAP_SERVER]\n                         || !instance->is_bootstrap)\n                        && instance->present_resources[SEC_RES_SHORT_SERVER_ID]\n                        && ssid == instance->ssid)) {\n                mark_hsm_instance_permanent(repr, instance);\n            }\n        }\n    }\n}\n#endif // ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT\n"
  },
  {
    "path": "standalone/security/standalone_mod_security.h",
    "content": "#ifndef ANJAY_STANDALONE_SECURITY_SECURITY_H\n#define ANJAY_STANDALONE_SECURITY_SECURITY_H\n\n#include <avsystem/commons/avs_log.h>\n\n#include \"standalone_security.h\"\n\ntypedef enum {\n    SEC_RES_LWM2M_SERVER_URI = 0,\n    SEC_RES_BOOTSTRAP_SERVER = 1,\n    SEC_RES_SECURITY_MODE = 2,\n    SEC_RES_PK_OR_IDENTITY = 3,\n    SEC_RES_SERVER_PK = 4,\n    SEC_RES_SECRET_KEY = 5,\n#ifdef ANJAY_WITH_SMS\n    SEC_RES_SMS_SECURITY_MODE = 6,\n    SEC_RES_SMS_BINDING_KEY_PARAMS = 7,\n    SEC_RES_SMS_BINDING_SECRET_KEYS = 8,\n    SEC_RES_SERVER_SMS_NUMBER = 9,\n#endif // ANJAY_WITH_SMS\n    SEC_RES_SHORT_SERVER_ID = 10,\n    SEC_RES_CLIENT_HOLD_OFF_TIME = 11,\n    SEC_RES_BOOTSTRAP_TIMEOUT = 12,\n#ifdef ANJAY_WITH_LWM2M11\n    SEC_RES_MATCHING_TYPE = 13,\n    SEC_RES_SNI = 14,\n    SEC_RES_CERTIFICATE_USAGE = 15,\n    SEC_RES_DTLS_TLS_CIPHERSUITE = 16,\n#endif // ANJAY_WITH_LWM2M11\n#ifdef ANJAY_WITH_COAP_OSCORE\n    SEC_RES_OSCORE_SECURITY_MODE = 17,\n#endif // ANJAY_WITH_COAP_OSCORE\n    _SEC_RES_COUNT\n} security_rid_t;\n\ntypedef struct {\n    anjay_riid_t riid;\n    uint32_t cipher_id;\n} sec_cipher_instance_t;\n\ntypedef enum {\n    SEC_KEY_AS_DATA,\n    SEC_KEY_AS_KEY_EXTERNAL,\n    SEC_KEY_AS_KEY_OWNED\n} sec_key_or_data_type_t;\n\ntypedef struct {\n    void *data;\n    /** Amount of bytes currently stored in the buffer. */\n    size_t size;\n    /** Amount of bytes that might be stored in the buffer. */\n    size_t capacity;\n} standalone_raw_buffer_t;\n\ntypedef struct sec_key_or_data_struct sec_key_or_data_t;\nstruct sec_key_or_data_struct {\n    sec_key_or_data_type_t type;\n    union {\n        standalone_raw_buffer_t data;\n#if defined(ANJAY_WITH_SECURITY_STRUCTURED) \\\n        || defined(ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT)\n        struct {\n            avs_crypto_security_info_union_t info;\n            void *heap_buf;\n        } key;\n#endif /* defined(ANJAY_WITH_SECURITY_STRUCTURED) || \\\n          defined(ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT) */\n    } value;\n\n    // HERE GOES MAGIC.\n    //\n    // sec_key_or_data_t is, in a way, semantically something like a\n    // shared_ptr<variant<standalone_raw_buffer_t, security_info_and_heap_buf>>.\n    // Note that the instances of sec_key_or_data_t itself are NOT individually\n    // allocated on the heap, as they are fields in sec_instance_t.\n    //\n    // These two fields organize multiple instances of sec_key_or_data_t that\n    // refer to the same heap buffer (either via value.data.data or\n    // value.key.heap_buf) in a doubly linked list. That way, when multiple\n    // instances referring to the same buffer exist, and one of them is to be\n    // cleaned up, that cleaned up instance can be removed from the list without\n    // needing any other pointers (which wouldn't work if that was a singly\n    // linked list).\n    //\n    // When the last (or only) instance referring to a given buffer is being\n    // cleaned up, both prev_ref and next_ref will be NULL, which is a signal\n    // to actually free the resources.\n    //\n    // These pointers are manipulated in _standalone_sec_key_or_data_cleanup()\n    // and sec_key_or_data_create_ref(), so see there for the actual\n    // implementation. Also note that in practice, it is not expected for more\n    // than two references (one in instances and one in saved_instances) to the\n    // same buffer to exist, but a generic solution isn't more complicated,\n    // so...\n    sec_key_or_data_t *prev_ref;\n    sec_key_or_data_t *next_ref;\n};\n\ntypedef struct {\n    anjay_iid_t iid;\n    char *server_uri;\n    bool is_bootstrap;\n    anjay_security_mode_t security_mode;\n    sec_key_or_data_t public_cert_or_psk_identity;\n    sec_key_or_data_t private_cert_or_psk_key;\n    standalone_raw_buffer_t server_public_key;\n\n    anjay_ssid_t ssid;\n    int32_t holdoff_s;\n    int32_t bs_timeout_s;\n\n#ifdef ANJAY_WITH_SMS\n    anjay_sms_security_mode_t sms_security_mode;\n    sec_key_or_data_t sms_key_params;\n    sec_key_or_data_t sms_secret_key;\n    char *sms_number;\n#endif // ANJAY_WITH_SMS\n\n#ifdef ANJAY_WITH_LWM2M11\n    int8_t matching_type;\n    char *server_name_indication;\n    int8_t certificate_usage;\n    AVS_LIST(sec_cipher_instance_t) enabled_ciphersuites;\n#    ifdef ANJAY_WITH_COAP_OSCORE\n    anjay_iid_t oscore_iid;\n#    endif // ANJAY_WITH_COAP_OSCORE\n#endif     // ANJAY_WITH_LWM2M11\n\n    bool present_resources[_SEC_RES_COUNT];\n} sec_instance_t;\n\ntypedef struct {\n    const anjay_dm_object_def_t *def;\n    anjay_t *anjay;\n    AVS_LIST(sec_instance_t) instances;\n    AVS_LIST(sec_instance_t) saved_instances;\n    bool modified_since_persist;\n    bool saved_modified_since_persist;\n    bool in_transaction;\n#ifdef ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT\n    standalone_security_hsm_configuration_t hsm_config;\n    avs_crypto_prng_ctx_t *prng_ctx;\n    bool prng_allocated_by_user;\n#endif // ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT\n} sec_repr_t;\n\nstatic inline void _standalone_sec_mark_modified(sec_repr_t *repr) {\n    repr->modified_since_persist = true;\n}\n\nstatic inline void _standalone_sec_clear_modified(sec_repr_t *repr) {\n    repr->modified_since_persist = false;\n}\n\nvoid _standalone_sec_instance_update_resource_presence(sec_instance_t *inst);\n\n#define security_log(level, ...) avs_log(security, level, __VA_ARGS__)\n#define _(Arg) AVS_DISPOSABLE_LOG(Arg)\n\n#endif /* ANJAY_STANDALONE_SECURITY_SECURITY_H */\n"
  },
  {
    "path": "standalone/security/standalone_security.h",
    "content": "#ifndef ANJAY_STANDALONE_ANJAY_SECURITY_H\n#define ANJAY_STANDALONE_ANJAY_SECURITY_H\n\n#include <anjay/dm.h>\n\n#include <avsystem/commons/avs_stream.h>\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\ntypedef struct {\n    /** Resource: Short Server ID */\n    anjay_ssid_t ssid;\n    /** Resource: LwM2M Server URI */\n    const char *server_uri;\n    /** Resource: Bootstrap Server */\n    bool bootstrap_server;\n    /** Resource: Security Mode */\n    anjay_security_mode_t security_mode;\n    /** Resource: Client Hold Off Time */\n    int32_t client_holdoff_s;\n    /** Resource: Bootstrap Server Account Timeout */\n    int32_t bootstrap_timeout_s;\n    /** Resource: Public Key Or Identity */\n    const uint8_t *public_cert_or_psk_identity;\n    size_t public_cert_or_psk_identity_size;\n    /** Resource: Secret Key */\n    const uint8_t *private_cert_or_psk_key;\n    size_t private_cert_or_psk_key_size;\n    /** Resource: Server Public Key */\n    const uint8_t *server_public_key;\n    size_t server_public_key_size;\n#ifdef ANJAY_WITH_SMS\n    /** Resource: SMS Security Mode */\n    anjay_sms_security_mode_t sms_security_mode;\n    /** Resource: SMS Binding Key Parameters */\n    const uint8_t *sms_key_parameters;\n    size_t sms_key_parameters_size;\n    /** Resource: SMS Binding Secret Key(s) */\n    const uint8_t *sms_secret_key;\n    size_t sms_secret_key_size;\n    /** Resource: LwM2M Server SMS Number */\n    const char *server_sms_number;\n#endif // ANJAY_WITH_SMS\n#ifdef ANJAY_WITH_LWM2M11\n    /** Resource: Matching Type (NULL for not present) */\n    const uint8_t *matching_type;\n    /** Resource: SNI */\n    const char *server_name_indication;\n    /** Resource: Certificate Usage (NULL for not present) */\n    const uint8_t *certificate_usage;\n    /** Resource: DTLS/TLS Ciphersuite;\n     * Note: Passing a value with <c>num_ids == 0</c> (default) will cause the\n     * resource to be absent, resulting in a fallback to defaults. */\n    avs_net_socket_tls_ciphersuites_t ciphersuites;\n#    ifdef ANJAY_WITH_COAP_OSCORE\n    /** Resource: OSCORE Security Mode (NULL for not present) */\n    const anjay_iid_t *oscore_iid;\n#    endif // ANJAY_WITH_COAP_OSCORE\n#endif     // ANJAY_WITH_LWM2M11\n#ifdef ANJAY_WITH_SECURITY_STRUCTURED\n    /** Resource: Public Key Or Identity;\n     * This is an alternative to the @p public_cert_or_psk_identity and\n     * @p psk_identity fields that may be used only if @p security_mode is\n     * either @ref ANJAY_SECURITY_CERTIFICATE or @ref ANJAY_SECURITY_EST; it is\n     * also an error to specify non-empty values for more than one of these\n     * fields at the same time. */\n    avs_crypto_certificate_chain_info_t public_cert;\n    /** Resource: Secret Key;\n     * This is an alternative to the @p private_cert_or_psk_key and @ref psk_key\n     * fields that may be used only if @p security_mode is either\n     * @ref ANJAY_SECURITY_CERTIFICATE or @ref ANJAY_SECURITY_EST; it is also an\n     * error to specify non-empty values for more than one of these fields at\n     * the same time. */\n    avs_crypto_private_key_info_t private_key;\n    /** Resource: Public Key Or Identity;\n     * This is an alternative to the @p public_cert_or_psk_identity and\n     * @ref public_cert fields that may be used only if @p security_mode is\n     * @ref ANJAY_SECURITY_PSK; it is also an error to specify non-empty values\n     * for more than one of these fields at the same time. */\n    avs_crypto_psk_identity_info_t psk_identity;\n    /** Resource: Secret Key;\n     * This is an alternative to the @p private_cert_or_psk_key and\n     * @ref private_key fields that may be used only if @p security_mode is\n     * @ref ANJAY_SECURITY_PSK; it is also an error to specify non-empty values\n     * for more than one of these fields at the same time. */\n    avs_crypto_psk_key_info_t psk_key;\n#    ifdef ANJAY_WITH_SMS\n    /** Resource: SMS Binding Key Parameters;\n     * This is an alternative to the @p sms_key_parameters field that may be\n     * used only if @p sms_security_mode is @ref ANJAY_SMS_SECURITY_DTLS_PSK; it\n     * is also an error to specify non-empty values for both fields at the same\n     * time. */\n    avs_crypto_psk_identity_info_t sms_psk_identity;\n    /** Resource: SMS Binding Secret Key(s);\n     * This is an alternative to the @p sms_secret_key field that may be used\n     * only if @p sms_security_mode is @ref ANJAY_SMS_SECURITY_DTLS_PSK; it is\n     * also an error to specify non-empty values for both fields at the same\n     * time. */\n    avs_crypto_psk_key_info_t sms_psk_key;\n#    endif // ANJAY_WITH_SMS\n#endif     // ANJAY_WITH_SECURITY_STRUCTURED\n} standalone_security_instance_t;\n\n/**\n * Adds new Instance of Security Object and returns newly created Instance id\n * via @p inout_iid .\n *\n * Note: if @p *inout_iid is set to @ref ANJAY_ID_INVALID then the Instance id\n * is generated automatically, otherwise value of @p *inout_iid is used as a\n * new Security Instance Id.\n *\n * Note: @p instance may be safely freed by the user code after this function\n * finishes (internally a deep copy of @ref standalone_security_instance_t is\n * performed).\n *\n * Warning: calling this function during active communication with Bootstrap\n * Server may yield undefined behavior and unexpected failures may occur.\n *\n * @param obj_ptr   Installed Security Object to operate on.\n * @param instance  Security Instance to insert.\n * @param inout_iid Security Instance id to use or @ref ANJAY_ID_INVALID .\n *\n * @return 0 on success, negative value in case of an error or if the instance\n * of specified id already exists.\n */\nint standalone_security_object_add_instance(\n        const anjay_dm_object_def_t *const *obj_ptr,\n        const standalone_security_instance_t *instance,\n        anjay_iid_t *inout_iid);\n\n/**\n * Purges instances of Security Object leaving it in an empty state.\n *\n * @param obj_ptr Installed Security Object to purge.\n */\nvoid standalone_security_object_purge(\n        const anjay_dm_object_def_t *const *obj_ptr);\n\n/**\n * Dumps Security Object Instances to the @p out_stream.\n *\n * @param obj_ptr       Installed Security Object to operate on.\n * @param out_stream    Stream to write to.\n * @returns AVS_OK in case of success, or an error code.\n */\navs_error_t\nstandalone_security_object_persist(const anjay_dm_object_def_t *const *obj_ptr,\n                                   avs_stream_t *out_stream);\n\n/**\n * Attempts to restore Security Object Instances from specified @p in_stream.\n *\n * Note: if restore fails, then Security Object will be left untouched, on\n * success though all Instances stored within the Object will be purged.\n *\n * @param obj_ptr   Installed Security Object to operate on.\n * @param in_stream Stream to read from.\n * @returns AVS_OK in case of success, or an error code.\n */\navs_error_t\nstandalone_security_object_restore(const anjay_dm_object_def_t *const *obj_ptr,\n                                   avs_stream_t *in_stream);\n\n/**\n * Checks whether the Security Object has been modified since\n * last successful call to @ref standalone_security_object_persist or @ref\n * standalone_security_object_restore.\n */\nbool standalone_security_object_is_modified(\n        const anjay_dm_object_def_t *const *obj_ptr);\n\n/**\n * Creates the Security Object and installs it in an Anjay instance using\n * @ref anjay_register_object.\n *\n * Do NOT attempt to call @ref anjay_register_object with this object manually,\n * and do NOT try to use the same instance of the Security object with another\n * Anjay instance.\n *\n * @param anjay Anjay instance for which the Security Object is installed.\n *\n * @returns Handle to the created object that can be passed to other functions\n *          declared in this file, or <c>NULL</c> in case of error.\n */\nconst anjay_dm_object_def_t **\nstandalone_security_object_install(anjay_t *anjay);\n\n/**\n * Frees all system resources allocated by the Security Object.\n *\n * <strong>NOTE:</strong> Attempting to call this function before deregistering\n * the object using @ref anjay_unregister_object, @ref anjay_delete or\n * @ref anjay_delete_with_core_persistence is undefined behavior.\n *\n * @param obj_ptr Server Object to operate on.\n */\nvoid standalone_security_object_cleanup(\n        const anjay_dm_object_def_t *const *obj_ptr);\n\n#ifdef ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT\n/**\n * Type for a callback function that will be called by the Security object\n * implementation whenever a query string for storing a new security credential\n * (provisioned by means other than EST) in external security engine is\n * necessary. See also the fields of the @ref\n * standalone_security_hsm_configuration_t structure.\n *\n * @param iid       ID of the Security object Instance for which the credential\n *                  is to be stored.\n *\n * @param ssid      Short Server ID of the server account for which the\n *                  credential is to be stored (@ref ANJAY_SSID_BOOTSTRAP in\n *                  case of the Bootstrap Server).\n *\n * @param data      Pointer to a buffer containing the credential that will be\n *                  stored.\n *\n * @param data_size Size in bytes of the data located at @p data.\n *\n * @param arg       Opaque argument configured through a corresponding\n *                  <c>*_arg</c> field of\n *                  @ref standalone_security_hsm_configuration_t.\n *\n * @returns String that will be used as a query string for the provisioned\n *          security credential. The string will be copied shortly after this\n *          function returns, so it is safe to deallocate or overwrite it when\n *          control is returned to user code, or during the next call to this\n *          function, whichever happens first. Security Object code will never\n *          attempt to modify or deallocate the returned value.\n *\n * @attention The @p data and @p data_size are provided <strong>only as a\n *            hint</strong> for users who want the query strings to depend on\n *            the credential contents in any way. This callback <strong>shall\n *            not attempt to store the certificate</strong> itself. This will be\n *            performed by Anjay afterwards.\n */\ntypedef const char *standalone_security_hsm_query_cb_t(anjay_iid_t iid,\n                                                       anjay_ssid_t ssid,\n                                                       const void *data,\n                                                       size_t data_size,\n                                                       void *arg);\n\n/**\n * Configuration of the callbacks for generating the query string addresses\n * under which different kinds of security credentials will be stored on the\n * hardware security engine.\n */\ntypedef struct {\n    /**\n     * Callback function that will be called whenever a public client\n     * certificate needs to be stored in an external security engine.\n     *\n     * If NULL, public client certificates will be stored in main system memory\n     * unless explicitly requested via either EST or the <c>public_cert</c>\n     * field in @ref standalone_security_instance_t.\n     */\n    standalone_security_hsm_query_cb_t *public_cert_cb;\n\n    /**\n     * Opaque argument that will be passed to the function configured in the\n     * <c>public_cert_cb</c> field.\n     *\n     * If <c>public_cert_cb</c> is NULL, this field is ignored.\n     */\n    void *public_cert_cb_arg;\n\n    /**\n     * Callback function that will be called whenever a client private key needs\n     * to be stored in an external security engine.\n     *\n     * If NULL, client private keys will be stored in main system memory unless\n     * explicitly requested via either EST or the <c>private_key</c> field in\n     * @ref standalone_security_instance_t.\n     */\n    standalone_security_hsm_query_cb_t *private_key_cb;\n\n    /**\n     * Opaque argument that will be passed to the function configured in the\n     * <c>private_key_cb</c> field.\n     *\n     * If <c>private_key_cb</c> is NULL, this field is ignored.\n     */\n    void *private_key_cb_arg;\n\n    /**\n     * Callback function that will be called whenever a PSK identity for use\n     * with the main connection needs to be stored in an external security\n     * engine.\n     *\n     * If NULL, PSK identities for use with the main connection will be stored\n     * in main system memory unless explicitly requested via the\n     * <c>psk_identity</c> field in @ref standalone_security_instance_t.\n     */\n    standalone_security_hsm_query_cb_t *psk_identity_cb;\n\n    /**\n     * Opaque argument that will be passed to the function configured in the\n     * <c>psk_identity_cb</c> field.\n     *\n     * If <c>psk_identity_cb</c> is NULL, this field is ignored.\n     */\n    void *psk_identity_cb_arg;\n\n    /**\n     * Callback function that will be called whenever a PSK key for use with the\n     * main connection needs to be stored in an external security engine.\n     *\n     * If NULL, PSK keys for use with the main connection will be stored in main\n     * system memory unless explicitly requested via the <c>psk_key</c> field in\n     * @ref standalone_security_instance_t.\n     */\n    standalone_security_hsm_query_cb_t *psk_key_cb;\n\n    /**\n     * Opaque argument that will be passed to the function configured in the\n     * <c>psk_key_cb</c> field.\n     *\n     * If <c>psk_key_cb</c> is NULL, this field is ignored.\n     */\n    void *psk_key_cb_arg;\n#    ifdef ANJAY_WITH_SMS\n    /**\n     * Callback function that will be called whenever a PSK identity for use\n     * with SMS binding needs to be stored in an external security engine.\n     *\n     * If NULL, PSK identities for use with SMS binding will be stored in main\n     * system memory unless explicitly requested via the <c>sms_psk_identity</c>\n     * field in @ref standalone_security_instance_t.\n     */\n    standalone_security_hsm_query_cb_t *sms_psk_identity_cb;\n\n    /**\n     * Opaque argument that will be passed to the function configured in the\n     * <c>sms_psk_identity_cb</c> field.\n     *\n     * If <c>sms_psk_identity_cb</c> is NULL, this field is ignored.\n     */\n    void *sms_psk_identity_cb_arg;\n\n    /**\n     * Callback function that will be called whenever a PSK key for use with SMS\n     * binding needs to be stored in an external security engine.\n     *\n     * If NULL, PSK keys for use with SMS binding will be stored in main system\n     * memory unless explicitly requested via the <c>sms_psk_key</c> field in\n     * @ref standalone_security_instance_t.\n     */\n    standalone_security_hsm_query_cb_t *sms_psk_key_cb;\n\n    /**\n     * Opaque argument that will be passed to the function configured in the\n     * <c>sms_psk_key_cb</c> field.\n     *\n     * If <c>sms_psk_key_cb</c> is NULL, this field is ignored.\n     */\n    void *sms_psk_key_cb_arg;\n#    endif // ANJAY_WITH_SMS\n} standalone_security_hsm_configuration_t;\n\n/**\n * Creates the Security Object in an Anjay instance, with support for moving\n * security credentials to a hardware security module, and installs it in an\n * Anjay instance using @ref anjay_register_object.\n *\n * Do NOT attempt to call @ref anjay_register_object with this object manually,\n * and do NOT try to use the same instance of the Security object with another\n * Anjay instance.\n *\n * For each of the security credential type for which the query string\n * generation callback is provided, any credentials provisioned either using\n * @ref standalone_security_object_add_instance or by the Bootstrap Server, will\n * be stored in the hardware security module and wiped from the main system\n * memory. These credentials will be managed by Anjay and automatically deleted\n * when removed from the data model (either by the Bootstrap Server or\n * @ref standalone_security_object_purge) or when the object is cleaned up\n * without having been properly persisted (see the next paragraph for details).\n *\n * A call to @ref standalone_security_object_cleanup will also cause the removal\n * of all the keys moved into the hardware security module, unless unchanged\n * since the last call to @ref standalone_security_object_persist or @ref\n * standalone_security_object_restore, or marked permanent using @ref\n * standalone_security_mark_hsm_permanent.\n *\n * @param anjay      Anjay instance for which the Security Object is installed.\n *\n * @param hsm_config Configuration of the mechanism that moves security\n *                   credentials to the hardware security module. When the\n *                   pointer is <c>NULL</c> or all of the callback fields are\n *                   <c>NULL</c>, this functions is equivalent to\n *                   @ref standalone_security_object_install.\n *\n * @param prng_ctx   Custom PRNG context to use. If @c NULL , a default one is\n *                   used, with entropy source specific to selected cryptograpic\n *                   backend.\n *\n * NOTE: @p prng_ctx is used when moving security credentials into the HSM,\n * which may happen in one of three scenarios:\n *\n * - During the <c>transaction_validate</c> operation performed by Anjay,\n *   typically while processing a Bootstrap Finish message\n * - As part of @ref standalone_security_object_add_instance\n * - As part of @ref standalone_security_object_restore\n *\n * @returns Handle to the created object that can be passed to other functions\n *          declared in this file, or <c>NULL</c> in case of error.\n */\nconst anjay_dm_object_def_t **standalone_security_object_install_with_hsm(\n        anjay_t *anjay,\n        const standalone_security_hsm_configuration_t *hsm_config,\n        avs_crypto_prng_ctx_t *prng_ctx);\n\n/**\n * Marks security credential for a given Server Account as \"permanent\",\n * preventing them from being removed from the hardware security module.\n *\n * The credentials that are moved into hardware security module according to the\n * logic described for @ref standalone_security_object_install_with_hsm are, by\n * default, automatically removed whenever they are deleted from the data model\n * (e.g. by the Bootstrap Server or using @ref\n * standalone_security_object_purge).\n *\n * This function causes such credentials to be marked as \"permanent\", equivalent\n * to credentials provisioned using the <c>avs_crypto_*_from_engine</c> APIs\n * passed into the fields of the @ref standalone_security_instance_t structure.\n * This will prevent them from being automatically erased from the hardware even\n * if they are removed from the data model, or if the object is cleaned up\n * without up-to-date persistence status.\n *\n * @param obj_ptr Installed Security Object to operate on.\n *\n * @param ssid  Short Server ID of the Server Account whose credentials to mark\n *              as permanent. @ref ANJAY_SSID_BOOTSTRAP may be used to refer to\n *              the Bootstrap Server (if present), and @ref ANJAY_SSID_ANY may\n *              be used to mark <strong>all</strong> the Server Account\n *              credentials as permanent.\n */\nvoid standalone_security_mark_hsm_permanent(\n        const anjay_dm_object_def_t *const *obj_ptr, anjay_ssid_t ssid);\n#endif // ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif /* ANJAY_STANDALONE_ANJAY_SECURITY_H */\n"
  },
  {
    "path": "standalone/security/standalone_security_persistence.c",
    "content": "#include <inttypes.h>\n#include <string.h>\n\n#include \"standalone_mod_security.h\"\n#include \"standalone_security_transaction.h\"\n#include \"standalone_security_utils.h\"\n\n#ifdef AVS_COMMONS_WITH_AVS_PERSISTENCE\n#    include <avsystem/commons/avs_persistence.h>\n#endif // AVS_COMMONS_WITH_AVS_PERSISTENCE\n\n#define persistence_log(level, ...) \\\n    avs_log(security_persistence, level, __VA_ARGS__)\n\n#ifdef AVS_COMMONS_WITH_AVS_PERSISTENCE\n\nstatic const char MAGIC_V0[] = { 'S', 'E', 'C', '\\0' };\nstatic const char MAGIC_V1[] = { 'S', 'E', 'C', '\\1' };\nstatic const char MAGIC_V2[] = { 'S', 'E', 'C', '\\2' };\nstatic const char MAGIC_V3[] = { 'S', 'E', 'C', '\\3' };\nstatic const char MAGIC_V4[] = { 'S', 'E', 'C', '\\4' };\nstatic const char MAGIC_V5[] = { 'S', 'E', 'C', '\\5' };\n\nstatic avs_error_t handle_sized_v0_fields(avs_persistence_context_t *ctx,\n                                          sec_instance_t *element) {\n    avs_error_t err;\n    (void) (avs_is_err((err = avs_persistence_u16(ctx, &element->iid)))\n            || avs_is_err((err = avs_persistence_bool(\n                                   ctx, &element->present_resources\n                                                 [SEC_RES_BOOTSTRAP_SERVER])))\n            || avs_is_err((err = avs_persistence_bool(\n                                   ctx, &element->present_resources\n                                                 [SEC_RES_SECURITY_MODE])))\n            || avs_is_err((err = avs_persistence_bool(\n                                   ctx, &element->present_resources\n                                                 [SEC_RES_SHORT_SERVER_ID])))\n            || avs_is_err((\n                       err = avs_persistence_bool(ctx, &element->is_bootstrap)))\n            || avs_is_err((err = avs_persistence_u16(ctx, &element->ssid)))\n            || avs_is_err((err = avs_persistence_u32(\n                                   ctx, (uint32_t *) &element->holdoff_s)))\n            || avs_is_err((err = avs_persistence_u32(\n                                   ctx, (uint32_t *) &element->bs_timeout_s))));\n    return err;\n}\n\nstatic avs_error_t handle_sized_v1_fields(avs_persistence_context_t *ctx,\n                                          sec_instance_t *element) {\n    avs_error_t err;\n#    ifdef ANJAY_WITH_SMS\n    (void) (avs_is_err((err = avs_persistence_bool(\n                                ctx, &element->present_resources\n                                              [SEC_RES_SMS_SECURITY_MODE])))\n            || avs_is_err((err = avs_persistence_bool(\n                                   ctx,\n                                   &element->present_resources\n                                            [SEC_RES_SMS_BINDING_KEY_PARAMS])))\n            || avs_is_err(\n                       (err = avs_persistence_bool(\n                                ctx,\n                                &element->present_resources\n                                         [SEC_RES_SMS_BINDING_SECRET_KEYS]))));\n#    else  // ANJAY_WITH_SMS\n    (void) element;\n    (void) (avs_is_err((err = avs_persistence_bool(ctx, &(bool) { false })))\n            || avs_is_err((err = avs_persistence_bool(ctx, &(bool) { false })))\n            || avs_is_err(\n                       (err = avs_persistence_bool(ctx, &(bool) { false }))));\n#    endif // ANJAY_WITH_SMS\n    return err;\n}\n\nstatic avs_error_t handle_ciphersuite_entry(avs_persistence_context_t *ctx,\n                                            void *element,\n                                            void *user_data) {\n    (void) user_data;\n\n    sec_cipher_instance_t *inst = (sec_cipher_instance_t *) element;\n    avs_error_t err;\n    (void) (avs_is_err((err = avs_persistence_u16(ctx, &inst->riid)))\n            || avs_is_err((err = avs_persistence_u32(ctx, &inst->cipher_id))));\n    if (avs_is_ok(err) && inst->cipher_id == 0) {\n        return avs_errno(AVS_EBADMSG);\n    }\n    return err;\n}\n\nstatic avs_error_t handle_sized_v2_fields(avs_persistence_context_t *ctx,\n                                          sec_instance_t *element) {\n    AVS_LIST(sec_cipher_instance_t) enabled_ciphersuites = NULL;\n    char *server_name_indication = NULL;\n#    ifdef ANJAY_WITH_LWM2M11\n    enabled_ciphersuites = element->enabled_ciphersuites;\n    server_name_indication = element->server_name_indication;\n#    endif // ANJAY_WITH_LWM2M11\n    avs_error_t err;\n    (void) (avs_is_err((err = avs_persistence_list(\n                                ctx, (void **) &enabled_ciphersuites,\n                                sizeof(*enabled_ciphersuites),\n                                handle_ciphersuite_entry, NULL, avs_free)))\n            || avs_is_err((err = avs_persistence_string(\n                                   ctx, &server_name_indication)))\n#    ifdef ANJAY_WITH_COAP_OSCORE\n            || avs_is_err((err = avs_persistence_bool(\n                                   ctx,\n                                   &element->present_resources\n                                            [SEC_RES_OSCORE_SECURITY_MODE])))\n            || avs_is_err(\n                       (err = avs_persistence_u16(ctx, &element->oscore_iid)))\n#    else  // ANJAY_WITH_COAP_OSCORE\n            || avs_is_err((err = avs_persistence_bool(\n                                   ctx, (bool *) &(bool) { false })))\n            || avs_is_err((err = avs_persistence_u16(\n                                   ctx, (uint16_t *) &(uint16_t) { 0 })))\n#    endif // ANJAY_WITH_COAP_OSCORE\n    );\n#    ifdef ANJAY_WITH_LWM2M11\n    element->enabled_ciphersuites = enabled_ciphersuites;\n    element->server_name_indication = server_name_indication;\n#    else  // ANJAY_WITH_LWM2M11\n    (void) element;\n    AVS_LIST_CLEAR(&enabled_ciphersuites);\n    avs_free(server_name_indication);\n#    endif // ANJAY_WITH_LWM2M11\n    return err;\n}\n\nstatic avs_error_t handle_sized_v3_fields(avs_persistence_context_t *ctx,\n                                          sec_instance_t *element) {\n#    ifndef ANJAY_WITH_LWM2M11\n    (void) element;\n#    endif // ANJAY_WITH_LWM2M11\n    avs_error_t err;\n    (void) (avs_is_err((err = avs_persistence_i8(ctx,\n#    ifdef ANJAY_WITH_LWM2M11\n                                                 &element->matching_type\n#    else  // ANJAY_WITH_LWM2M11\n                                                 &(int8_t) { -1 }\n#    endif // ANJAY_WITH_LWM2M11\n                                                 )))\n            || avs_is_err((err = avs_persistence_i8(ctx,\n#    ifdef ANJAY_WITH_LWM2M11\n                                                    &element->certificate_usage\n#    else  // ANJAY_WITH_LWM2M11\n                                                    &(int8_t) { -1 }\n#    endif // ANJAY_WITH_LWM2M11\n                                                    ))));\n    return err;\n}\n\n#    ifdef ANJAY_WITH_LWM2M11\nstatic void reset_v3_fields(sec_instance_t *element) {\n    element->matching_type = -1;\n    element->certificate_usage = -1;\n}\n#    endif // ANJAY_WITH_LWM2M11\n\n#    if defined(ANJAY_WITH_SECURITY_STRUCTURED) \\\n            || defined(ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT)\nstatic avs_error_t handle_sec_key_or_data_type(avs_persistence_context_t *ctx,\n                                               sec_key_or_data_type_t *type) {\n    avs_persistence_direction_t direction = avs_persistence_direction(ctx);\n    int8_t type_ch;\n    if (direction == AVS_PERSISTENCE_STORE) {\n        switch (*type) {\n        case SEC_KEY_AS_DATA:\n            type_ch = 'D';\n            break;\n        case SEC_KEY_AS_KEY_EXTERNAL:\n            type_ch = 'K';\n            break;\n        case SEC_KEY_AS_KEY_OWNED:\n            type_ch = 'O';\n            break;\n        default:\n            AVS_UNREACHABLE(\"invalid value of sec_key_or_data_type_t\");\n            return avs_errno(AVS_EINVAL);\n        }\n    }\n\n    avs_error_t err = avs_persistence_i8(ctx, &type_ch);\n    if (avs_is_err(err)) {\n        return err;\n    }\n\n    if (direction == AVS_PERSISTENCE_RESTORE) {\n        switch (type_ch) {\n        case 'D':\n            *type = SEC_KEY_AS_DATA;\n            break;\n        case 'K':\n            *type = SEC_KEY_AS_KEY_EXTERNAL;\n            break;\n        case 'O':\n            *type = SEC_KEY_AS_KEY_OWNED;\n            break;\n        default:\n            return avs_errno(AVS_EIO);\n        }\n    }\n    return AVS_OK;\n}\n\nstatic avs_error_t handle_sec_key_tag(avs_persistence_context_t *ctx,\n                                      avs_crypto_security_info_tag_t *tag) {\n    avs_persistence_direction_t direction = avs_persistence_direction(ctx);\n    int8_t tag_ch;\n    if (direction == AVS_PERSISTENCE_STORE) {\n        switch (*tag) {\n        case AVS_CRYPTO_SECURITY_INFO_CERTIFICATE_CHAIN:\n            tag_ch = 'C';\n            break;\n        case AVS_CRYPTO_SECURITY_INFO_PRIVATE_KEY:\n            tag_ch = 'K';\n            break;\n        case AVS_CRYPTO_SECURITY_INFO_PSK_IDENTITY:\n            tag_ch = 'I';\n            break;\n        case AVS_CRYPTO_SECURITY_INFO_PSK_KEY:\n            tag_ch = 'P';\n            break;\n        default:\n            AVS_UNREACHABLE(\"invalid value of avs_crypto_security_info_tag_t\");\n            return avs_errno(AVS_EINVAL);\n        }\n    }\n\n    avs_error_t err = avs_persistence_i8(ctx, &tag_ch);\n    if (avs_is_err(err)) {\n        return err;\n    }\n\n    if (direction == AVS_PERSISTENCE_RESTORE) {\n        switch (tag_ch) {\n        case 'C':\n            *tag = AVS_CRYPTO_SECURITY_INFO_CERTIFICATE_CHAIN;\n            break;\n        case 'K':\n            *tag = AVS_CRYPTO_SECURITY_INFO_PRIVATE_KEY;\n            break;\n        case 'I':\n            *tag = AVS_CRYPTO_SECURITY_INFO_PSK_IDENTITY;\n            break;\n        case 'P':\n            *tag = AVS_CRYPTO_SECURITY_INFO_PSK_KEY;\n            break;\n        default:\n            return avs_errno(AVS_EIO);\n        }\n    }\n    return AVS_OK;\n}\n\nstatic avs_error_t\nhandle_sec_key_certificate_chain(avs_persistence_context_t *ctx,\n                                 sec_key_or_data_t *value) {\n    assert(value->type == SEC_KEY_AS_KEY_EXTERNAL\n           || value->type == SEC_KEY_AS_KEY_OWNED);\n    if (avs_persistence_direction(ctx) == AVS_PERSISTENCE_STORE) {\n        return avs_crypto_certificate_chain_info_persist(\n                ctx, (avs_crypto_certificate_chain_info_t) {\n                         .desc = value->value.key.info\n                     });\n    } else {\n        avs_crypto_certificate_chain_info_t *array = NULL;\n        size_t element_count;\n        avs_error_t err = avs_crypto_certificate_chain_info_array_persistence(\n                ctx, &array, &element_count);\n        if (avs_is_ok(err)) {\n            assert(!value->value.key.heap_buf);\n            assert(!value->prev_ref);\n            assert(!value->next_ref);\n            value->value.key.info =\n                    avs_crypto_certificate_chain_info_from_array(array,\n                                                                 element_count)\n                            .desc;\n            value->value.key.heap_buf = array;\n        }\n        return err;\n    }\n}\n\nstatic avs_error_t handle_sec_key_private_key(avs_persistence_context_t *ctx,\n                                              sec_key_or_data_t *value) {\n    assert(value->type == SEC_KEY_AS_KEY_EXTERNAL\n           || value->type == SEC_KEY_AS_KEY_OWNED);\n    avs_crypto_private_key_info_t *key_info = NULL;\n    if (avs_persistence_direction(ctx) == AVS_PERSISTENCE_STORE) {\n        key_info = AVS_CONTAINER_OF(&value->value.key.info,\n                                    avs_crypto_private_key_info_t, desc);\n    }\n    avs_error_t err = avs_crypto_private_key_info_persistence(ctx, &key_info);\n    if (avs_is_ok(err)\n            && avs_persistence_direction(ctx) == AVS_PERSISTENCE_RESTORE) {\n        assert(!value->value.key.heap_buf);\n        assert(!value->prev_ref);\n        assert(!value->next_ref);\n        value->value.key.info = key_info->desc;\n        value->value.key.heap_buf = key_info;\n    }\n    return err;\n}\n\nstatic avs_error_t handle_sec_key_psk_identity(avs_persistence_context_t *ctx,\n                                               sec_key_or_data_t *value) {\n    assert(value->type == SEC_KEY_AS_KEY_EXTERNAL\n           || value->type == SEC_KEY_AS_KEY_OWNED);\n    avs_crypto_psk_identity_info_t *key_info = NULL;\n    if (avs_persistence_direction(ctx) == AVS_PERSISTENCE_STORE) {\n        key_info = AVS_CONTAINER_OF(&value->value.key.info,\n                                    avs_crypto_psk_identity_info_t, desc);\n    }\n    avs_error_t err = avs_crypto_psk_identity_info_persistence(ctx, &key_info);\n    if (avs_is_ok(err)\n            && avs_persistence_direction(ctx) == AVS_PERSISTENCE_RESTORE) {\n        assert(!value->value.key.heap_buf);\n        assert(!value->prev_ref);\n        assert(!value->next_ref);\n        value->value.key.info = key_info->desc;\n        value->value.key.heap_buf = key_info;\n    }\n    return err;\n}\n\nstatic avs_error_t handle_sec_key_psk_key(avs_persistence_context_t *ctx,\n                                          sec_key_or_data_t *value) {\n    assert(value->type == SEC_KEY_AS_KEY_EXTERNAL\n           || value->type == SEC_KEY_AS_KEY_OWNED);\n    avs_crypto_psk_key_info_t *key_info = NULL;\n    if (avs_persistence_direction(ctx) == AVS_PERSISTENCE_STORE) {\n        key_info = AVS_CONTAINER_OF(&value->value.key.info,\n                                    avs_crypto_psk_key_info_t, desc);\n    }\n    avs_error_t err = avs_crypto_psk_key_info_persistence(ctx, &key_info);\n    if (avs_is_ok(err)\n            && avs_persistence_direction(ctx) == AVS_PERSISTENCE_RESTORE) {\n        assert(!value->value.key.heap_buf);\n        assert(!value->prev_ref);\n        assert(!value->next_ref);\n        value->value.key.info = key_info->desc;\n        value->value.key.heap_buf = key_info;\n    }\n    return err;\n}\n#    endif /* defined(ANJAY_WITH_SECURITY_STRUCTURED) || \\\n              defined(ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT) */\n\nstatic avs_error_t handle_raw_buffer(avs_persistence_context_t *ctx,\n                                     standalone_raw_buffer_t *buffer) {\n    avs_error_t err =\n            avs_persistence_sized_buffer(ctx, &buffer->data, &buffer->size);\n    if (!buffer->capacity) {\n        buffer->capacity = buffer->size;\n    }\n    return err;\n}\n\nstatic avs_error_t\nhandle_sec_key_or_data(avs_persistence_context_t *ctx,\n                       sec_key_or_data_t *value,\n                       intptr_t stream_version,\n                       intptr_t min_version_for_key,\n                       avs_crypto_security_info_tag_t default_tag) {\n#    if defined(ANJAY_WITH_SECURITY_STRUCTURED) \\\n            || defined(ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT)\n    if (stream_version >= min_version_for_key) {\n        avs_error_t err = handle_sec_key_or_data_type(ctx, &value->type);\n        if (avs_is_err(err)) {\n            return err;\n        }\n\n        if (value->type == SEC_KEY_AS_KEY_EXTERNAL\n                || value->type == SEC_KEY_AS_KEY_OWNED) {\n            avs_crypto_security_info_tag_t tag = default_tag;\n            if (stream_version >= 5) {\n                if (avs_persistence_direction(ctx) == AVS_PERSISTENCE_STORE) {\n                    tag = value->value.key.info.type;\n                }\n                if (avs_is_err((err = handle_sec_key_tag(ctx, &tag)))) {\n                    return err;\n                }\n            }\n\n            switch (tag) {\n            case AVS_CRYPTO_SECURITY_INFO_CERTIFICATE_CHAIN:\n                return handle_sec_key_certificate_chain(ctx, value);\n            case AVS_CRYPTO_SECURITY_INFO_PRIVATE_KEY:\n                return handle_sec_key_private_key(ctx, value);\n            case AVS_CRYPTO_SECURITY_INFO_PSK_IDENTITY:\n                return handle_sec_key_psk_identity(ctx, value);\n            case AVS_CRYPTO_SECURITY_INFO_PSK_KEY:\n                return handle_sec_key_psk_key(ctx, value);\n            default:\n                AVS_UNREACHABLE(\n                        \"invalid value of avs_crypto_security_info_tag_t\");\n                return avs_errno(AVS_EINVAL);\n            }\n        }\n    }\n#    endif /* defined(ANJAY_WITH_SECURITY_STRUCTURED) || \\\n              defined(ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT) */\n    (void) stream_version;\n    (void) min_version_for_key;\n    (void) default_tag;\n    assert(value->type == SEC_KEY_AS_DATA);\n    avs_error_t err = handle_raw_buffer(ctx, &value->value.data);\n    assert(avs_is_err(err)\n           || avs_persistence_direction(ctx) != AVS_PERSISTENCE_RESTORE\n           || (!value->prev_ref && !value->next_ref));\n    return err;\n}\n\nstatic avs_error_t handle_instance(avs_persistence_context_t *ctx,\n                                   void *element_,\n                                   void *stream_version_) {\n    sec_instance_t *element = (sec_instance_t *) element_;\n    const intptr_t stream_version = (intptr_t) stream_version_;\n\n    avs_error_t err = AVS_OK;\n    uint16_t security_mode = (uint16_t) element->security_mode;\n    if (avs_is_err((err = handle_sized_v0_fields(ctx, element)))\n            || avs_is_err((err = avs_persistence_u16(ctx, &security_mode)))\n            || avs_is_err((\n                       err = avs_persistence_string(ctx, &element->server_uri)))\n            || avs_is_err((err = handle_sec_key_or_data(\n                                   ctx, &element->public_cert_or_psk_identity,\n                                   stream_version,\n                                   /* min_version_for_key = */ 4,\n                                   AVS_CRYPTO_SECURITY_INFO_CERTIFICATE_CHAIN)))\n            || avs_is_err((err = handle_sec_key_or_data(\n                                   ctx, &element->private_cert_or_psk_key,\n                                   stream_version,\n                                   /* min_version_for_key = */ 4,\n                                   AVS_CRYPTO_SECURITY_INFO_PRIVATE_KEY)))\n            || avs_is_err((err = handle_raw_buffer(\n                                   ctx, &element->server_public_key)))) {\n        return err;\n    }\n    element->security_mode = (anjay_security_mode_t) security_mode;\n    if (stream_version >= 1) {\n#    ifdef ANJAY_WITH_SMS\n        uint16_t sms_security_mode = (uint16_t) element->sms_security_mode;\n        sec_key_or_data_t *sms_key_params_ptr = &element->sms_key_params;\n        sec_key_or_data_t *sms_secret_key_ptr = &element->sms_secret_key;\n        char *sms_number = element->sms_number;\n#    else  // ANJAY_WITH_SMS\n        uint16_t sms_security_mode = 3; // NoSec\n        sec_key_or_data_t *sms_key_params_ptr =\n                &(sec_key_or_data_t) { SEC_KEY_AS_DATA };\n        sec_key_or_data_t *sms_secret_key_ptr =\n                &(sec_key_or_data_t) { SEC_KEY_AS_DATA };\n        char *sms_number = NULL;\n#    endif // ANJAY_WITH_SMS\n        if (avs_is_err((err = handle_sized_v1_fields(ctx, element)))\n                || avs_is_err(\n                           (err = avs_persistence_u16(ctx, &sms_security_mode)))\n                || avs_is_err((err = handle_sec_key_or_data(\n                                       ctx, sms_key_params_ptr, stream_version,\n                                       /* min_version_for_key = */ 5,\n                                       AVS_CRYPTO_SECURITY_INFO_PSK_IDENTITY)))\n                || avs_is_err((err = handle_sec_key_or_data(\n                                       ctx, sms_secret_key_ptr, stream_version,\n                                       /* min_version_for_key = */ 5,\n                                       AVS_CRYPTO_SECURITY_INFO_PSK_KEY)))\n                || avs_is_err(\n                           (err = avs_persistence_string(ctx, &sms_number)))) {\n            return err;\n        }\n#    ifdef ANJAY_WITH_SMS\n        element->sms_security_mode =\n                (anjay_sms_security_mode_t) sms_security_mode;\n        element->sms_number = sms_number;\n#    else  // ANJAY_WITH_SMS\n        _standalone_sec_key_or_data_cleanup(sms_key_params_ptr, false);\n        _standalone_sec_key_or_data_cleanup(sms_secret_key_ptr, false);\n        avs_free(sms_number);\n#    endif // ANJAY_WITH_SMS\n    }\n    if (stream_version >= 2) {\n        err = handle_sized_v2_fields(ctx, element);\n    }\n    if (avs_is_ok(err)) {\n        if (stream_version >= 3) {\n            err = handle_sized_v3_fields(ctx, element);\n        }\n#    ifdef ANJAY_WITH_LWM2M11\n        else if (avs_persistence_direction(ctx) == AVS_PERSISTENCE_RESTORE) {\n            reset_v3_fields(element);\n        }\n#    endif // ANJAY_WITH_LWM2M11\n    }\n\n    if (avs_persistence_direction(ctx) == AVS_PERSISTENCE_RESTORE) {\n        _standalone_sec_instance_update_resource_presence(element);\n    }\n\n    return err;\n}\n\navs_error_t\nstandalone_security_object_persist(const anjay_dm_object_def_t *const *obj_ptr,\n                                   avs_stream_t *out_stream) {\n    avs_error_t err = avs_errno(AVS_EINVAL);\n    sec_repr_t *repr = _standalone_sec_get(obj_ptr);\n    if (!repr) {\n        err = avs_errno(AVS_EBADF);\n    } else if (avs_is_ok((err = avs_stream_write(out_stream, MAGIC_V5,\n                                                 sizeof(MAGIC_V5))))) {\n        avs_persistence_context_t ctx =\n                avs_persistence_store_context_create(out_stream);\n        err = avs_persistence_list(\n                &ctx,\n                (AVS_LIST(void) *) (repr->in_transaction\n                                            ? &repr->saved_instances\n                                            : &repr->instances),\n                sizeof(sec_instance_t), handle_instance, (void *) (intptr_t) 5,\n                NULL);\n        if (avs_is_ok(err)) {\n            _standalone_sec_clear_modified(repr);\n            persistence_log(INFO, _(\"Security Object state persisted\"));\n        }\n    }\n    return err;\n}\n\navs_error_t\nstandalone_security_object_restore(const anjay_dm_object_def_t *const *obj_ptr,\n                                   avs_stream_t *in_stream) {\n    avs_error_t err = avs_errno(AVS_EINVAL);\n    sec_repr_t *repr = _standalone_sec_get(obj_ptr);\n    if (!repr || repr->in_transaction) {\n        err = avs_errno(AVS_EBADF);\n    } else {\n        sec_repr_t backup = *repr;\n\n        AVS_STATIC_ASSERT(sizeof(MAGIC_V0) == sizeof(MAGIC_V1),\n                          magic_size_v0_v1);\n        AVS_STATIC_ASSERT(sizeof(MAGIC_V1) == sizeof(MAGIC_V2),\n                          magic_size_v1_v2);\n        AVS_STATIC_ASSERT(sizeof(MAGIC_V2) == sizeof(MAGIC_V3),\n                          magic_size_v2_v3);\n        AVS_STATIC_ASSERT(sizeof(MAGIC_V3) == sizeof(MAGIC_V4),\n                          magic_size_v3_v4);\n        AVS_STATIC_ASSERT(sizeof(MAGIC_V4) == sizeof(MAGIC_V5),\n                          magic_size_v4_v5);\n        char magic_header[sizeof(MAGIC_V0)];\n        int version = -1;\n        if (avs_is_err(\n                    (err = avs_stream_read_reliably(in_stream, magic_header,\n                                                    sizeof(magic_header))))) {\n            persistence_log(WARNING,\n                            _(\"Could not read Security Object header\"));\n        } else if (!memcmp(magic_header, MAGIC_V0, sizeof(MAGIC_V0))) {\n            version = 0;\n        } else if (!memcmp(magic_header, MAGIC_V1, sizeof(MAGIC_V1))) {\n            version = 1;\n        } else if (!memcmp(magic_header, MAGIC_V2, sizeof(MAGIC_V2))) {\n            version = 2;\n        } else if (!memcmp(magic_header, MAGIC_V3, sizeof(MAGIC_V3))) {\n            version = 3;\n        } else if (!memcmp(magic_header, MAGIC_V4, sizeof(MAGIC_V4))) {\n            version = 4;\n        } else if (!memcmp(magic_header, MAGIC_V5, sizeof(MAGIC_V5))) {\n            version = 5;\n        } else {\n            persistence_log(WARNING, _(\"Header magic constant mismatch\"));\n            err = avs_errno(AVS_EBADMSG);\n        }\n        if (avs_is_ok(err)) {\n            avs_persistence_context_t restore_ctx =\n                    avs_persistence_restore_context_create(in_stream);\n            repr->instances = NULL;\n            err = avs_persistence_list(&restore_ctx,\n                                       (AVS_LIST(void) *) &repr->instances,\n                                       sizeof(sec_instance_t), handle_instance,\n                                       (void *) (intptr_t) version, NULL);\n            if (avs_is_ok(err)\n                    && _standalone_sec_object_validate_and_process_keys(\n                               repr->anjay, repr)) {\n                err = avs_errno(AVS_EPROTO);\n            }\n            if (avs_is_err(err)) {\n                _standalone_sec_destroy_instances(&repr->instances, true);\n                repr->instances = backup.instances;\n            } else {\n                _standalone_sec_destroy_instances(&backup.instances, true);\n                _standalone_sec_clear_modified(repr);\n                persistence_log(INFO, _(\"Security Object state restored\"));\n            }\n        }\n    }\n    return err;\n}\n\n#else // AVS_COMMONS_WITH_AVS_PERSISTENCE\n\navs_error_t standalone_security_object_persist(anjay_t *anjay,\n                                               avs_stream_t *out_stream) {\n    (void) anjay;\n    (void) out_stream;\n    persistence_log(ERROR, _(\"Persistence not compiled in\"));\n    return avs_errno(AVS_ENOTSUP);\n}\n\navs_error_t standalone_security_object_restore(anjay_t *anjay,\n                                               avs_stream_t *in_stream) {\n    (void) anjay;\n    (void) in_stream;\n    persistence_log(ERROR, _(\"Persistence not compiled in\"));\n    return avs_errno(AVS_ENOTSUP);\n}\n\n#endif // AVS_COMMONS_WITH_AVS_PERSISTENCE\n"
  },
  {
    "path": "standalone/security/standalone_security_transaction.c",
    "content": "#include <assert.h>\n#include <string.h>\n\n#include \"standalone_security_transaction.h\"\n#include \"standalone_security_utils.h\"\n\ntypedef struct {\n    anjay_ssid_t ssid;\n    anjay_socket_transport_t transport;\n} ssid_transport_pair_t;\n\nstatic int\nssid_transport_pair_cmp(const void *a_, const void *b_, size_t element_size) {\n    assert(element_size == sizeof(ssid_transport_pair_t));\n    (void) element_size;\n    const ssid_transport_pair_t *a = (const ssid_transport_pair_t *) a_;\n    const ssid_transport_pair_t *b = (const ssid_transport_pair_t *) b_;\n    if (a->ssid != b->ssid) {\n        return a->ssid - b->ssid;\n    }\n    return (int) a->transport - (int) b->transport;\n}\n\ntypedef enum {\n    STANDALONE_TRANSPORT_SECURITY_UNDEFINED,\n    STANDALONE_TRANSPORT_NOSEC,\n    STANDALONE_TRANSPORT_ENCRYPTED\n} standalone_transport_security_t;\n\ntypedef struct {\n    const char *uri_scheme;\n    anjay_socket_transport_t transport;\n    standalone_transport_security_t security;\n} standalone_transport_info_t;\n\nstatic const standalone_transport_info_t TRANSPORTS[] = {\n    {\n        .transport = ANJAY_SOCKET_TRANSPORT_UDP,\n        .uri_scheme = \"coap\",\n        .security = STANDALONE_TRANSPORT_NOSEC\n    },\n    {\n        .transport = ANJAY_SOCKET_TRANSPORT_UDP,\n        .uri_scheme = \"coaps\",\n        .security = STANDALONE_TRANSPORT_ENCRYPTED\n    },\n    {\n        .transport = ANJAY_SOCKET_TRANSPORT_TCP,\n        .uri_scheme = \"coap+tcp\",\n        .security = STANDALONE_TRANSPORT_NOSEC\n    },\n    {\n        .transport = ANJAY_SOCKET_TRANSPORT_TCP,\n        .uri_scheme = \"coaps+tcp\",\n        .security = STANDALONE_TRANSPORT_ENCRYPTED\n    },\n    {\n        .transport = ANJAY_SOCKET_TRANSPORT_SMS,\n        .uri_scheme = \"tel\",\n        .security = STANDALONE_TRANSPORT_SECURITY_UNDEFINED\n    },\n#ifdef ANJAY_WITH_LWM2M11\n    {\n        .transport = ANJAY_SOCKET_TRANSPORT_NIDD,\n        .uri_scheme = \"coap+nidd\",\n        .security = STANDALONE_TRANSPORT_NOSEC\n    },\n    {\n        .transport = ANJAY_SOCKET_TRANSPORT_NIDD,\n        .uri_scheme = \"coaps+nidd\",\n        .security = STANDALONE_TRANSPORT_ENCRYPTED\n    }\n#endif // ANJAY_WITH_LWM2M11\n};\n\nstatic const standalone_transport_info_t *\n_standalone_transport_info_by_uri_scheme(const char *uri_or_scheme) {\n    if (!uri_or_scheme) {\n        security_log(ERROR, _(\"URL scheme not specified\"));\n        return NULL;\n    }\n\n    for (size_t i = 0; i < AVS_ARRAY_SIZE(TRANSPORTS); ++i) {\n        size_t scheme_size = strlen(TRANSPORTS[i].uri_scheme);\n        if (avs_strncasecmp(uri_or_scheme, TRANSPORTS[i].uri_scheme,\n                            scheme_size)\n                        == 0\n                && (uri_or_scheme[scheme_size] == '\\0'\n                    || uri_or_scheme[scheme_size] == ':')) {\n            return &TRANSPORTS[i];\n        }\n    }\n\n    security_log(WARNING, _(\"unsupported URI scheme: \") \"%s\", uri_or_scheme);\n    return NULL;\n}\n\nstatic bool uri_protocol_matching(anjay_security_mode_t security_mode,\n                                  const char *uri) {\n    const standalone_transport_info_t *transport_info =\n            _standalone_transport_info_by_uri_scheme(uri);\n    if (!transport_info) {\n        return false;\n    }\n    if (transport_info->security == STANDALONE_TRANSPORT_SECURITY_UNDEFINED) {\n        // URI scheme does not specify security,\n        // so it is valid for all security modes\n        return true;\n    }\n\n    const bool is_secure_uri =\n            (transport_info->security == STANDALONE_TRANSPORT_ENCRYPTED);\n    const bool needs_secure_uri = (security_mode != ANJAY_SECURITY_NOSEC);\n    return is_secure_uri == needs_secure_uri;\n}\n\nstatic bool\nsec_key_or_data_valid(const sec_key_or_data_t *value,\n                      const avs_crypto_security_info_tag_t *expected_tag) {\n    (void) expected_tag;\n    switch (value->type) {\n    case SEC_KEY_AS_DATA:\n        return value->value.data.data;\n#if defined(ANJAY_WITH_SECURITY_STRUCTURED) \\\n        || defined(ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT)\n    case SEC_KEY_AS_KEY_EXTERNAL:\n    case SEC_KEY_AS_KEY_OWNED:\n        return expected_tag\n               && value->value.key.info.source != AVS_CRYPTO_DATA_SOURCE_EMPTY\n               && value->value.key.info.type == *expected_tag;\n#endif /* defined(ANJAY_WITH_SECURITY_STRUCTURED) || \\\n          defined(ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT) */\n    default:\n        AVS_UNREACHABLE(\"invalid value of sec_key_or_data_type_t\");\n        return false;\n    }\n}\n\n#define LOG_VALIDATION_FAILED(SecInstance, ...)                \\\n    security_log(WARNING, \"/%u/%u: \" AVS_VARARG0(__VA_ARGS__), \\\n                 ANJAY_DM_OID_SECURITY,                        \\\n                 (unsigned) (SecInstance)->iid AVS_VARARG_REST(__VA_ARGS__))\n\nstatic int validate_instance(sec_instance_t *it) {\n    if (!it->server_uri) {\n        LOG_VALIDATION_FAILED(it,\n                              \"missing mandatory 'Server URI' resource value\");\n        return -1;\n    }\n    if (!it->present_resources[SEC_RES_BOOTSTRAP_SERVER]) {\n        LOG_VALIDATION_FAILED(\n                it, \"missing mandatory 'Bootstrap Server' resource value\");\n        return -1;\n    }\n    if (!it->present_resources[SEC_RES_SECURITY_MODE]) {\n        LOG_VALIDATION_FAILED(\n                it, \"missing mandatory 'Security Mode' resource value\");\n        return -1;\n    }\n    if (!it->is_bootstrap && !it->present_resources[SEC_RES_SHORT_SERVER_ID]) {\n        LOG_VALIDATION_FAILED(\n                it, \"missing mandatory 'Short Server ID' resource value\");\n        return -1;\n    }\n    if (_standalone_sec_validate_security_mode((int32_t) it->security_mode)) {\n        LOG_VALIDATION_FAILED(it, \"Security mode %d not supported\",\n                              (int) it->security_mode);\n        return -1;\n    }\n    if (!uri_protocol_matching(it->security_mode, it->server_uri)) {\n        LOG_VALIDATION_FAILED(\n                it,\n                \"Incorrect protocol in Server Uri '%s' due to security \"\n                \"configuration (coap:// instead of coaps:// or vice versa?)\",\n                it->server_uri);\n        return -1;\n    }\n    if (it->security_mode != ANJAY_SECURITY_NOSEC\n            && it->security_mode != ANJAY_SECURITY_EST) {\n        if (!sec_key_or_data_valid(\n                    &it->public_cert_or_psk_identity,\n                    &(const avs_crypto_security_info_tag_t) {\n                            it->security_mode == ANJAY_SECURITY_PSK\n                                    ? AVS_CRYPTO_SECURITY_INFO_PSK_IDENTITY\n                                    : AVS_CRYPTO_SECURITY_INFO_CERTIFICATE_CHAIN })\n                || !sec_key_or_data_valid(\n                           &it->private_cert_or_psk_key,\n                           &(const avs_crypto_security_info_tag_t) {\n                                   it->security_mode == ANJAY_SECURITY_PSK\n                                           ? AVS_CRYPTO_SECURITY_INFO_PSK_KEY\n                                           : AVS_CRYPTO_SECURITY_INFO_PRIVATE_KEY })) {\n            LOG_VALIDATION_FAILED(it,\n                                  \"security credentials not fully configured\");\n            return -1;\n        }\n    }\n#ifdef ANJAY_WITH_SMS\n    if (it->present_resources[SEC_RES_SMS_SECURITY_MODE]) {\n        if (_standalone_sec_validate_sms_security_mode(\n                    (int32_t) it->sms_security_mode)) {\n            LOG_VALIDATION_FAILED(it, \"SMS Security mode %d not supported\",\n                                  (int) it->sms_security_mode);\n            return -1;\n        }\n        if ((it->sms_security_mode == ANJAY_SMS_SECURITY_DTLS_PSK\n             || it->sms_security_mode == ANJAY_SMS_SECURITY_SECURE_PACKET)\n                && (!it->present_resources[SEC_RES_SMS_BINDING_KEY_PARAMS]\n                    || !it->present_resources[SEC_RES_SMS_BINDING_SECRET_KEYS]\n                    || !sec_key_or_data_valid(\n                               &it->sms_key_params,\n                               it->sms_security_mode\n                                               == ANJAY_SMS_SECURITY_DTLS_PSK\n                                       ? &(const avs_crypto_security_info_tag_t) { AVS_CRYPTO_SECURITY_INFO_PSK_IDENTITY }\n                                       : NULL)\n                    || !sec_key_or_data_valid(\n                               &it->sms_secret_key,\n                               it->sms_security_mode\n                                               == ANJAY_SMS_SECURITY_DTLS_PSK\n                                       ? &(const avs_crypto_security_info_tag_t) { AVS_CRYPTO_SECURITY_INFO_PSK_KEY }\n                                       : NULL))) {\n            LOG_VALIDATION_FAILED(\n                    it, \"SMS security credentials not fully configured\");\n            return -1;\n        }\n    }\n#endif // ANJAY_WITH_SMS\n#ifdef ANJAY_WITH_LWM2M11\n    if (it->matching_type > 3) {\n        LOG_VALIDATION_FAILED(it, \"Matching Type set to an invalid value\");\n        return -1;\n    }\n    if (it->matching_type == 2) {\n        LOG_VALIDATION_FAILED(it, \"SHA-384 Matching Type is not supported\");\n        return -1;\n    }\n    if (it->certificate_usage > 3) {\n        LOG_VALIDATION_FAILED(it, \"Certificate Usage set to an invalid value\");\n        return -1;\n    }\n#endif // ANJAY_WITH_LWM2M11\n    return 0;\n}\n\nstatic int sec_object_validate(anjay_t *anjay, sec_repr_t *repr) {\n    AVS_LIST(ssid_transport_pair_t) seen_ssid_transport_pairs = NULL;\n    AVS_LIST(sec_instance_t) it;\n    int result = 0;\n    bool bootstrap_server_present = false;\n    (void) anjay;\n\n    AVS_LIST_FOREACH(it, repr->instances) {\n        /* Assume something will go wrong */\n        result = ANJAY_ERR_BAD_REQUEST;\n        if (validate_instance(it)) {\n            goto finish;\n        }\n\n        if (it->is_bootstrap) {\n            if (bootstrap_server_present) {\n                goto finish;\n            }\n            bootstrap_server_present = true;\n        } else {\n            const standalone_transport_info_t *transport_info =\n                    _standalone_transport_info_by_uri_scheme(it->server_uri);\n            if (!transport_info\n                    || !AVS_LIST_INSERT_NEW(ssid_transport_pair_t,\n                                            &seen_ssid_transport_pairs)) {\n                result = ANJAY_ERR_INTERNAL;\n                goto finish;\n            }\n            seen_ssid_transport_pairs->ssid = it->ssid;\n            seen_ssid_transport_pairs->transport = transport_info->transport;\n        }\n\n        /* We are still there - nothing went wrong, continue */\n        result = 0;\n    }\n\n    if (!result && seen_ssid_transport_pairs) {\n        AVS_LIST_SORT(&seen_ssid_transport_pairs, ssid_transport_pair_cmp);\n        AVS_LIST(ssid_transport_pair_t) prev = seen_ssid_transport_pairs;\n        AVS_LIST(ssid_transport_pair_t) next =\n                AVS_LIST_NEXT(seen_ssid_transport_pairs);\n        while (next) {\n            if (prev->ssid == next->ssid\n                    && prev->transport == next->transport) {\n                /* Duplicate found */\n                result = ANJAY_ERR_BAD_REQUEST;\n                break;\n            }\n            prev = next;\n            next = AVS_LIST_NEXT(next);\n        }\n    }\nfinish:\n    AVS_LIST_CLEAR(&seen_ssid_transport_pairs);\n    return result;\n}\n\n#ifdef ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT\nstatic avs_error_t sec_key_store(sec_key_or_data_t *sec_key,\n                                 avs_crypto_security_info_tag_t tag,\n                                 avs_crypto_prng_ctx_t *prng_ctx,\n                                 const char *query) {\n    avs_crypto_security_info_union_t src_desc = {\n        .type = tag,\n        .source = AVS_CRYPTO_DATA_SOURCE_BUFFER,\n        .info = {\n            .buffer = {\n                .buffer = sec_key->value.data.data,\n                .buffer_size = sec_key->value.data.size\n            }\n        }\n    };\n    switch (tag) {\n#    ifdef AVS_COMMONS_WITH_AVS_CRYPTO_PKI_ENGINE\n    case AVS_CRYPTO_SECURITY_INFO_CERTIFICATE_CHAIN:\n        return avs_crypto_pki_engine_certificate_store(\n                query,\n                AVS_CONTAINER_OF(&src_desc, avs_crypto_certificate_chain_info_t,\n                                 desc));\n    case AVS_CRYPTO_SECURITY_INFO_PRIVATE_KEY:\n        return avs_crypto_pki_engine_key_store(\n                query,\n                AVS_CONTAINER_OF(&src_desc, avs_crypto_private_key_info_t,\n                                 desc),\n                prng_ctx);\n#    endif // AVS_COMMONS_WITH_AVS_CRYPTO_PKI_ENGINE\n#    ifdef AVS_COMMONS_WITH_AVS_CRYPTO_PSK_ENGINE\n    case AVS_CRYPTO_SECURITY_INFO_PSK_IDENTITY:\n        return avs_crypto_psk_engine_identity_store(\n                query, AVS_CONTAINER_OF(&src_desc,\n                                        avs_crypto_psk_identity_info_t, desc));\n    case AVS_CRYPTO_SECURITY_INFO_PSK_KEY:\n        return avs_crypto_psk_engine_key_store(\n                query,\n                AVS_CONTAINER_OF(&src_desc, avs_crypto_psk_key_info_t, desc));\n#    endif // AVS_COMMONS_WITH_AVS_CRYPTO_PSK_ENGINE\n    default:\n        AVS_UNREACHABLE(\"Unexpected tag value\");\n        return avs_errno(AVS_EINVAL);\n    }\n}\n\nstatic int\nmaybe_move_sec_key_to_hsm(sec_instance_t *instance,\n                          sec_key_or_data_t *sec_key,\n                          avs_crypto_security_info_tag_t tag,\n                          const char *tag_str,\n                          avs_crypto_prng_ctx_t *prng_ctx,\n                          standalone_security_hsm_query_cb_t *query_cb,\n                          void *query_cb_arg) {\n    if (sec_key->type != SEC_KEY_AS_DATA || !sec_key->value.data.size\n            || !query_cb) {\n        return 0;\n    }\n    const char *query =\n            query_cb(instance->iid,\n                     instance->present_resources[SEC_RES_SHORT_SERVER_ID]\n                             ? instance->ssid\n                             : ANJAY_SSID_BOOTSTRAP,\n                     sec_key->value.data.data, sec_key->value.data.size,\n                     query_cb_arg);\n    if (!query) {\n        security_log(ERROR,\n                     _(\"Generating HSM query string for \") \"%s\" _(\" failed\"),\n                     tag_str);\n        return -1;\n    }\n    if (avs_is_err(sec_key_store(sec_key, tag, prng_ctx, query))) {\n        security_log(ERROR, _(\"Could not store \") \"%s\" _(\" in HSM\"), tag_str);\n        return -1;\n    }\n    sec_key_or_data_t new_sec_key;\n    memset(&new_sec_key, 0, sizeof(new_sec_key));\n    avs_crypto_security_info_union_t dst_desc = {\n        .type = tag,\n        .source = AVS_CRYPTO_DATA_SOURCE_ENGINE,\n        .info = {\n            .engine = {\n                .query = query\n            }\n        }\n    };\n    int result;\n    switch (tag) {\n#    ifdef AVS_COMMONS_WITH_AVS_CRYPTO_PKI_ENGINE\n    case AVS_CRYPTO_SECURITY_INFO_CERTIFICATE_CHAIN:\n        if ((result = _standalone_sec_init_certificate_chain_resource(\n                     &new_sec_key, SEC_KEY_AS_KEY_OWNED,\n                     AVS_CONTAINER_OF(&dst_desc,\n                                      avs_crypto_certificate_chain_info_t,\n                                      desc)))) {\n            avs_crypto_pki_engine_certificate_rm(query);\n        }\n        break;\n    case AVS_CRYPTO_SECURITY_INFO_PRIVATE_KEY:\n        if ((result = _standalone_sec_init_private_key_resource(\n                     &new_sec_key, SEC_KEY_AS_KEY_OWNED,\n                     AVS_CONTAINER_OF(&dst_desc, avs_crypto_private_key_info_t,\n                                      desc)))) {\n            avs_crypto_pki_engine_key_rm(query);\n        }\n        break;\n#    endif // AVS_COMMONS_WITH_AVS_CRYPTO_PKI_ENGINE\n#    ifdef AVS_COMMONS_WITH_AVS_CRYPTO_PSK_ENGINE\n    case AVS_CRYPTO_SECURITY_INFO_PSK_IDENTITY:\n        if ((result = _standalone_sec_init_psk_identity_resource(\n                     &new_sec_key, SEC_KEY_AS_KEY_OWNED,\n                     AVS_CONTAINER_OF(&dst_desc, avs_crypto_psk_identity_info_t,\n                                      desc)))) {\n            avs_crypto_psk_engine_identity_rm(query);\n        }\n        break;\n    case AVS_CRYPTO_SECURITY_INFO_PSK_KEY:\n        if ((result = _standalone_sec_init_psk_key_resource(\n                     &new_sec_key, SEC_KEY_AS_KEY_OWNED,\n                     AVS_CONTAINER_OF(&dst_desc, avs_crypto_psk_key_info_t,\n                                      desc)))) {\n            avs_crypto_psk_engine_key_rm(query);\n        }\n        break;\n#    endif // AVS_COMMONS_WITH_AVS_CRYPTO_PSK_ENGINE\n    default:\n        AVS_UNREACHABLE(\"Unexpected tag value\");\n        result = -1;\n    }\n    if (result) {\n        security_log(ERROR,\n                     _(\"Could not allocate new sec_key_or_data_t object\"));\n        return result;\n    }\n    _standalone_sec_key_or_data_cleanup(sec_key, true);\n    assert(!new_sec_key.prev_ref);\n    assert(!new_sec_key.next_ref);\n    *sec_key = new_sec_key;\n    return 0;\n}\n\nstatic int sec_object_process_keys(sec_repr_t *repr,\n                                   avs_crypto_prng_ctx_t *prng_ctx) {\n    AVS_LIST(sec_instance_t) instance;\n    AVS_LIST_FOREACH(instance, repr->instances) {\n        switch (instance->security_mode) {\n#    ifdef AVS_COMMONS_WITH_AVS_CRYPTO_PSK_ENGINE\n        case ANJAY_SECURITY_PSK:\n            if (maybe_move_sec_key_to_hsm(\n                        instance, &instance->public_cert_or_psk_identity,\n                        AVS_CRYPTO_SECURITY_INFO_PSK_IDENTITY, \"PSK identity\",\n                        prng_ctx, repr->hsm_config.psk_identity_cb,\n                        repr->hsm_config.psk_identity_cb_arg)) {\n                return -1;\n            }\n            if (maybe_move_sec_key_to_hsm(\n                        instance, &instance->private_cert_or_psk_key,\n                        AVS_CRYPTO_SECURITY_INFO_PSK_KEY, \"PSK key\", prng_ctx,\n                        repr->hsm_config.psk_key_cb,\n                        repr->hsm_config.psk_key_cb_arg)) {\n                return -1;\n            }\n            break;\n#    endif // AVS_COMMONS_WITH_AVS_CRYPTO_PSK_ENGINE\n#    ifdef AVS_COMMONS_WITH_AVS_CRYPTO_PKI_ENGINE\n        case ANJAY_SECURITY_CERTIFICATE:\n        case ANJAY_SECURITY_EST:\n            if (maybe_move_sec_key_to_hsm(\n                        instance, &instance->public_cert_or_psk_identity,\n                        AVS_CRYPTO_SECURITY_INFO_CERTIFICATE_CHAIN,\n                        \"public certificate\", prng_ctx,\n                        repr->hsm_config.public_cert_cb,\n                        repr->hsm_config.public_cert_cb_arg)) {\n                return -1;\n            }\n            if (maybe_move_sec_key_to_hsm(\n                        instance, &instance->private_cert_or_psk_key,\n                        AVS_CRYPTO_SECURITY_INFO_PRIVATE_KEY, \"private key\",\n                        prng_ctx, repr->hsm_config.private_key_cb,\n                        repr->hsm_config.private_key_cb_arg)) {\n                return -1;\n            }\n            break;\n#    endif // AVS_COMMONS_WITH_AVS_CRYPTO_PKI_ENGINE\n        default:\n            // Do nothing\n            break;\n        }\n#    if defined(ANJAY_WITH_SMS) \\\n            && defined(AVS_COMMONS_WITH_AVS_CRYPTO_PSK_ENGINE)\n        if (instance->present_resources[SEC_RES_SMS_SECURITY_MODE]\n                && instance->sms_security_mode == ANJAY_SMS_SECURITY_DTLS_PSK) {\n            if (instance->present_resources[SEC_RES_SMS_BINDING_KEY_PARAMS]\n                    && maybe_move_sec_key_to_hsm(\n                               instance, &instance->sms_key_params,\n                               AVS_CRYPTO_SECURITY_INFO_PSK_IDENTITY,\n                               \"SMS PSK identity\", prng_ctx,\n                               repr->hsm_config.sms_psk_identity_cb,\n                               repr->hsm_config.sms_psk_identity_cb_arg)) {\n                return -1;\n            }\n            if (instance->present_resources[SEC_RES_SMS_BINDING_SECRET_KEYS]\n                    && maybe_move_sec_key_to_hsm(\n                               instance, &instance->sms_secret_key,\n                               AVS_CRYPTO_SECURITY_INFO_PSK_KEY, \"SMS PSK key\",\n                               prng_ctx, repr->hsm_config.sms_psk_key_cb,\n                               repr->hsm_config.sms_psk_key_cb_arg)) {\n                return -1;\n            }\n        }\n#    endif /* defined(ANJAY_WITH_SMS) && \\\n              defined(AVS_COMMONS_WITH_AVS_CRYPTO_PSK_ENGINE) */\n    }\n    return 0;\n}\n#endif // ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT\n\nint _standalone_sec_object_validate_and_process_keys(anjay_t *anjay,\n                                                     sec_repr_t *repr) {\n    int result = sec_object_validate(anjay, repr);\n#ifdef ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT\n    if (!result) {\n        // NOTE: THIS IS A HACK. We move key material to HSM storage during the\n        // validation stage, because:\n        // - We cannot do it during the write stage, because we need to know\n        //   what type of key we're dealing with, and the security mode might be\n        //   written later.\n        // - We shouldn't really do it at the commit stage, because the commit\n        //   operation is supposed to be as unlikely to fail as possible, and\n        //   storing keys on HSM can fail easily.\n        // So we exploit the fact that the act of moving a key to HSM is\n        // \"transparent\" in terms of the semantic data model state - i.e.,\n        // whether the operation is successful or not, the data model contains\n        // the same information as far as the LwM2M spec is concerned. In other\n        // words, what we do here does not change the data model, only its\n        // internal representation, so we should be safe doing that during the\n        // validation stage.\n        result = sec_object_process_keys(repr, repr->prng_ctx);\n    }\n#endif // ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT\n    return result;\n}\n\nint _standalone_sec_transaction_begin_impl(sec_repr_t *repr) {\n    assert(!repr->saved_instances);\n    assert(!repr->in_transaction);\n    repr->saved_instances = _standalone_sec_clone_instances(repr);\n    if (!repr->saved_instances && repr->instances) {\n        return ANJAY_ERR_INTERNAL;\n    }\n    repr->saved_modified_since_persist = repr->modified_since_persist;\n    repr->in_transaction = true;\n    return 0;\n}\n\nint _standalone_sec_transaction_commit_impl(sec_repr_t *repr) {\n    assert(repr->in_transaction);\n    _standalone_sec_destroy_instances(&repr->saved_instances, true);\n    repr->in_transaction = false;\n    return 0;\n}\n\nint _standalone_sec_transaction_validate_impl(anjay_t *anjay,\n                                              sec_repr_t *repr) {\n    assert(repr->in_transaction);\n    return _standalone_sec_object_validate_and_process_keys(anjay, repr);\n}\n\nint _standalone_sec_transaction_rollback_impl(sec_repr_t *repr) {\n    assert(repr->in_transaction);\n    _standalone_sec_destroy_instances(&repr->instances, true);\n    repr->instances = repr->saved_instances;\n    repr->saved_instances = NULL;\n    repr->modified_since_persist = repr->saved_modified_since_persist;\n    repr->in_transaction = false;\n    return 0;\n}\n"
  },
  {
    "path": "standalone/security/standalone_security_transaction.h",
    "content": "#ifndef ANJAY_STANDALONE_SECURITY_TRANSACTION_H\n#define ANJAY_STANDALONE_SECURITY_TRANSACTION_H\n\n#include \"standalone_mod_security.h\"\n\nint _standalone_sec_object_validate_and_process_keys(anjay_t *anjay,\n                                                     sec_repr_t *repr);\n\nint _standalone_sec_transaction_begin_impl(sec_repr_t *repr);\nint _standalone_sec_transaction_commit_impl(sec_repr_t *repr);\nint _standalone_sec_transaction_validate_impl(anjay_t *anjay, sec_repr_t *repr);\nint _standalone_sec_transaction_rollback_impl(sec_repr_t *repr);\n\n#endif /* ANJAY_STANDALONE_SECURITY_TRANSACTION_H */\n"
  },
  {
    "path": "standalone/security/standalone_security_utils.c",
    "content": "#include <string.h>\n\n#include \"standalone_security_utils.h\"\n\nint _standalone_sec_validate_security_mode(int32_t security_mode) {\n    switch (security_mode) {\n    case ANJAY_SECURITY_NOSEC:\n    case ANJAY_SECURITY_PSK:\n    case ANJAY_SECURITY_CERTIFICATE:\n    case ANJAY_SECURITY_EST:\n        return 0;\n    case ANJAY_SECURITY_RPK:\n        security_log(ERROR, _(\"Raw Public Key mode not supported\"));\n        return ANJAY_ERR_NOT_IMPLEMENTED;\n    default:\n        security_log(ERROR, _(\"Invalid Security Mode\"));\n        return ANJAY_ERR_BAD_REQUEST;\n    }\n}\n\nint _standalone_sec_fetch_security_mode(anjay_input_ctx_t *ctx,\n                                        anjay_security_mode_t *out) {\n    int32_t value;\n    int retval = anjay_get_i32(ctx, &value);\n    if (!retval) {\n        retval = _standalone_sec_validate_security_mode(value);\n    }\n    if (!retval) {\n        *out = (anjay_security_mode_t) value;\n    }\n    return retval;\n}\n\n#ifdef ANJAY_WITH_SMS\nint _standalone_sec_validate_sms_security_mode(int32_t security_mode) {\n    switch (security_mode) {\n    case ANJAY_SMS_SECURITY_DTLS_PSK:\n    case ANJAY_SMS_SECURITY_NOSEC:\n        return 0;\n    case ANJAY_SMS_SECURITY_SECURE_PACKET:\n        security_log(DEBUG, _(\"Secure Packet mode not supported\"));\n        return ANJAY_ERR_NOT_IMPLEMENTED;\n    default:\n        security_log(DEBUG, _(\"Invalid SMS Security Mode\"));\n        return ANJAY_ERR_BAD_REQUEST;\n    }\n}\n\nint _standalone_sec_fetch_sms_security_mode(anjay_input_ctx_t *ctx,\n                                            anjay_sms_security_mode_t *out) {\n    int32_t value;\n    int retval = anjay_get_i32(ctx, &value);\n    if (!retval) {\n        retval = _standalone_sec_validate_sms_security_mode(value);\n    }\n    if (!retval) {\n        *out = (anjay_sms_security_mode_t) value;\n    }\n    return retval;\n}\n#endif // ANJAY_WITH_SMS\n\nstatic int _standalone_sec_validate_short_server_id(int32_t ssid) {\n    return ssid > 0 && ssid <= UINT16_MAX ? 0 : -1;\n}\n\nint _standalone_sec_fetch_short_server_id(anjay_input_ctx_t *ctx,\n                                          anjay_ssid_t *out) {\n    int32_t value;\n    int retval = anjay_get_i32(ctx, &value);\n    if (!retval) {\n        retval = _standalone_sec_validate_short_server_id(value);\n    }\n    if (!retval) {\n        *out = (anjay_ssid_t) value;\n    }\n    return retval;\n}\n\n#if defined(ANJAY_WITH_SECURITY_STRUCTURED)                    \\\n        || (defined(ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT) \\\n            && defined(AVS_COMMONS_WITH_AVS_CRYPTO_PKI_ENGINE))\nint _standalone_sec_init_certificate_chain_resource(\n        sec_key_or_data_t *out_resource,\n        sec_key_or_data_type_t type,\n        const avs_crypto_certificate_chain_info_t *in_value) {\n    avs_crypto_certificate_chain_info_t *array = NULL;\n    size_t array_element_count;\n    if (avs_is_err(avs_crypto_certificate_chain_info_copy_as_array(\n                &array, &array_element_count, *in_value))) {\n        return -1;\n    }\n    assert(array || !array_element_count);\n    assert(!out_resource->prev_ref);\n    assert(!out_resource->next_ref);\n    assert(type == SEC_KEY_AS_KEY_EXTERNAL || type == SEC_KEY_AS_KEY_OWNED);\n    out_resource->type = type;\n    out_resource->value.key.info =\n            avs_crypto_certificate_chain_info_from_array(array,\n                                                         array_element_count)\n                    .desc;\n    out_resource->value.key.heap_buf = array;\n    return 0;\n}\n\nint _standalone_sec_init_private_key_resource(\n        sec_key_or_data_t *out_resource,\n        sec_key_or_data_type_t type,\n        const avs_crypto_private_key_info_t *in_value) {\n    avs_crypto_private_key_info_t *private_key = NULL;\n    if (avs_is_err(avs_crypto_private_key_info_copy(&private_key, *in_value))) {\n        return -1;\n    }\n    assert(private_key);\n    assert(!out_resource->prev_ref);\n    assert(!out_resource->next_ref);\n    assert(type == SEC_KEY_AS_KEY_EXTERNAL || type == SEC_KEY_AS_KEY_OWNED);\n    out_resource->type = type;\n    out_resource->value.key.info = private_key->desc;\n    out_resource->value.key.heap_buf = private_key;\n    return 0;\n}\n#endif /* defined(ANJAY_WITH_SECURITY_STRUCTURED) ||             \\\n          (defined(ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT) && \\\n          defined(AVS_COMMONS_WITH_AVS_CRYPTO_PKI_ENGINE)) */\n\n#if defined(ANJAY_WITH_SECURITY_STRUCTURED)                    \\\n        || (defined(ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT) \\\n            && defined(AVS_COMMONS_WITH_AVS_CRYPTO_PSK_ENGINE))\nint _standalone_sec_init_psk_identity_resource(\n        sec_key_or_data_t *out_resource,\n        sec_key_or_data_type_t type,\n        const avs_crypto_psk_identity_info_t *in_value) {\n    avs_crypto_psk_identity_info_t *psk_identity = NULL;\n    if (avs_is_err(\n                avs_crypto_psk_identity_info_copy(&psk_identity, *in_value))) {\n        return -1;\n    }\n    assert(psk_identity);\n    assert(!out_resource->prev_ref);\n    assert(!out_resource->next_ref);\n    assert(type == SEC_KEY_AS_KEY_EXTERNAL || type == SEC_KEY_AS_KEY_OWNED);\n    out_resource->type = type;\n    out_resource->value.key.info = psk_identity->desc;\n    out_resource->value.key.heap_buf = psk_identity;\n    return 0;\n}\n\nint _standalone_sec_init_psk_key_resource(\n        sec_key_or_data_t *out_resource,\n        sec_key_or_data_type_t type,\n        const avs_crypto_psk_key_info_t *in_value) {\n    avs_crypto_psk_key_info_t *psk_key = NULL;\n    if (avs_is_err(avs_crypto_psk_key_info_copy(&psk_key, *in_value))) {\n        return -1;\n    }\n    assert(psk_key);\n    assert(!out_resource->prev_ref);\n    assert(!out_resource->next_ref);\n    assert(type == SEC_KEY_AS_KEY_EXTERNAL || type == SEC_KEY_AS_KEY_OWNED);\n    out_resource->type = type;\n    out_resource->value.key.info = psk_key->desc;\n    out_resource->value.key.heap_buf = psk_key;\n    return 0;\n}\n#endif /* defined(ANJAY_WITH_SECURITY_STRUCTURED) ||             \\\n          (defined(ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT) && \\\n          defined(AVS_COMMONS_WITH_AVS_CRYPTO_PSK_ENGINE)) */\n\n#ifdef ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT\nstatic void\nremove_sec_key_from_engine(const avs_crypto_security_info_union_t *desc) {\n    assert(desc->source != AVS_CRYPTO_DATA_SOURCE_LIST);\n    if (desc->source == AVS_CRYPTO_DATA_SOURCE_ENGINE) {\n        avs_error_t err;\n        switch (desc->type) {\n#    ifdef AVS_COMMONS_WITH_AVS_CRYPTO_PKI_ENGINE\n        case AVS_CRYPTO_SECURITY_INFO_CERTIFICATE_CHAIN:\n            err = avs_crypto_pki_engine_certificate_rm(desc->info.engine.query);\n            break;\n        case AVS_CRYPTO_SECURITY_INFO_PRIVATE_KEY:\n            err = avs_crypto_pki_engine_key_rm(desc->info.engine.query);\n            break;\n#    endif // AVS_COMMONS_WITH_AVS_CRYPTO_PKI_ENGINE\n#    ifdef AVS_COMMONS_WITH_AVS_CRYPTO_PSK_ENGINE\n        case AVS_CRYPTO_SECURITY_INFO_PSK_IDENTITY:\n            err = avs_crypto_psk_engine_identity_rm(desc->info.engine.query);\n            break;\n        case AVS_CRYPTO_SECURITY_INFO_PSK_KEY:\n            err = avs_crypto_psk_engine_key_rm(desc->info.engine.query);\n            break;\n#    endif // AVS_COMMONS_WITH_AVS_CRYPTO_PSK_ENGINE\n        default:\n            err = avs_errno(AVS_EINVAL);\n        }\n        if (avs_is_err(err)) {\n            security_log(WARNING,\n                         _(\"could not remove \") \"%s\" _(\n                                 \" from the engine storage\"),\n                         desc->info.engine.query);\n        }\n    } else if (desc->source == AVS_CRYPTO_DATA_SOURCE_ARRAY) {\n        for (size_t i = 0; i < desc->info.array.element_count; ++i) {\n            remove_sec_key_from_engine(&desc->info.array.array_ptr[i]);\n        }\n    }\n}\n#endif // ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT\n\nvoid _standalone_sec_key_or_data_cleanup(sec_key_or_data_t *value,\n                                         bool remove_from_engine) {\n    (void) remove_from_engine;\n    if (!value->prev_ref && !value->next_ref) {\n        switch (value->type) {\n        case SEC_KEY_AS_DATA:\n#if defined(AVS_COMMONS_WITH_AVS_CRYPTO)\n            avs_crypto_clear_buffer(value->value.data.data,\n                                    value->value.data.capacity);\n#else\n            memset(value->value.data.data, 0, value->value.data.capacity);\n#endif // defined(AVS_COMMONS_WITH_AVS_CRYPTO)\n\n            _standalone_raw_buffer_clear(&value->value.data);\n            break;\n#if defined(ANJAY_WITH_SECURITY_STRUCTURED) \\\n        || defined(ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT)\n        case SEC_KEY_AS_KEY_OWNED:\n#    ifdef ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT\n            if (remove_from_engine) {\n                remove_sec_key_from_engine(&value->value.key.info);\n            }\n#    endif // ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT\n           // fall-through\n        case SEC_KEY_AS_KEY_EXTERNAL:\n            if ((value->value.key.info.type == AVS_CRYPTO_SECURITY_INFO_PSK_KEY\n                 || value->value.key.info.type\n                            == AVS_CRYPTO_SECURITY_INFO_PRIVATE_KEY)\n                    && value->value.key.info.source\n                                   == AVS_CRYPTO_DATA_SOURCE_BUFFER) {\n                size_t heap_buf_size =\n                        value->value.key.info.info.buffer.buffer_size;\n#    if defined(AVS_COMMONS_WITH_AVS_CRYPTO)\n                avs_crypto_clear_buffer(value->value.key.heap_buf,\n                                        heap_buf_size);\n#    else\n                memset(value->value.key.heap_buf, 0, heap_buf_size);\n#    endif // defined(AVS_COMMONS_WITH_AVS_CRYPTO)\n            }\n            avs_free(value->value.key.heap_buf);\n            break;\n#endif /* defined(ANJAY_WITH_SECURITY_STRUCTURED) || \\\n          defined(ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT) */\n        default:\n            AVS_UNREACHABLE(\"invalid value of sec_key_or_data_type_t\");\n        }\n    } else {\n        if (value->prev_ref) {\n            value->prev_ref->next_ref = value->next_ref;\n        }\n        if (value->next_ref) {\n            value->next_ref->prev_ref = value->prev_ref;\n        }\n    }\n    memset(value, 0, sizeof(*value));\n    assert(value->type == SEC_KEY_AS_DATA);\n}\n\nvoid _standalone_sec_destroy_instance_fields(sec_instance_t *instance,\n                                             bool remove_from_engine) {\n    if (!instance) {\n        return;\n    }\n    avs_free((char *) (intptr_t) instance->server_uri);\n    _standalone_sec_key_or_data_cleanup(&instance->public_cert_or_psk_identity,\n                                        remove_from_engine);\n    _standalone_sec_key_or_data_cleanup(&instance->private_cert_or_psk_key,\n                                        remove_from_engine);\n    _standalone_raw_buffer_clear(&instance->server_public_key);\n#ifdef ANJAY_WITH_LWM2M11\n    AVS_LIST_CLEAR(&instance->enabled_ciphersuites);\n    avs_free(instance->server_name_indication);\n#endif // ANJAY_WITH_LWM2M11\n#ifdef ANJAY_WITH_SMS\n    _standalone_sec_key_or_data_cleanup(&instance->sms_key_params,\n                                        remove_from_engine);\n    _standalone_sec_key_or_data_cleanup(&instance->sms_secret_key,\n                                        remove_from_engine);\n    avs_free((char *) (intptr_t) instance->sms_number);\n#endif // ANJAY_WITH_SMS\n}\n\nvoid _standalone_sec_destroy_instances(AVS_LIST(sec_instance_t) *instances_ptr,\n                                       bool remove_from_engine) {\n    AVS_LIST_CLEAR(instances_ptr) {\n        _standalone_sec_destroy_instance_fields(*instances_ptr,\n                                                remove_from_engine);\n    }\n}\n\nstatic void sec_key_or_data_create_ref(sec_key_or_data_t *dest,\n                                       sec_key_or_data_t *src) {\n    *dest = *src;\n    dest->prev_ref = src;\n    dest->next_ref = src->next_ref;\n    if (src->next_ref) {\n        src->next_ref->prev_ref = dest;\n    }\n    src->next_ref = dest;\n}\n\nstatic int _standalone_sec_clone_instance(sec_instance_t *dest,\n                                          sec_instance_t *src) {\n    *dest = *src;\n\n    assert(src->server_uri);\n    dest->server_uri = avs_strdup(src->server_uri);\n    if (!dest->server_uri) {\n        security_log(ERROR, _(\"Cannot clone Server Uri resource\"));\n        return -1;\n    }\n\n    sec_key_or_data_create_ref(&dest->public_cert_or_psk_identity,\n                               &src->public_cert_or_psk_identity);\n    sec_key_or_data_create_ref(&dest->private_cert_or_psk_key,\n                               &src->private_cert_or_psk_key);\n\n    memset(&dest->server_public_key, 0, sizeof(dest->server_public_key));\n    if (_standalone_raw_buffer_clone(&dest->server_public_key,\n                                     &src->server_public_key)) {\n        security_log(ERROR, _(\"Cannot clone Server Public Key resource\"));\n        return -1;\n    }\n\n#ifdef ANJAY_WITH_LWM2M11\n    dest->server_name_indication = NULL;\n    if (src->server_name_indication\n            && !(dest->server_name_indication =\n                         avs_strdup(src->server_name_indication))) {\n        security_log(ERROR, _(\"Cannot clone SNI resource\"));\n        return -1;\n    }\n#endif // ANJAY_WITH_LWM2M11\n\n#ifdef ANJAY_WITH_SMS\n    sec_key_or_data_create_ref(&dest->sms_key_params, &src->sms_key_params);\n    sec_key_or_data_create_ref(&dest->sms_secret_key, &src->sms_secret_key);\n\n    dest->sms_number = NULL;\n    if (src->sms_number) {\n        dest->sms_number = avs_strdup(src->sms_number);\n        if (!dest->sms_number) {\n            security_log(ERROR, _(\"Cannot clone Server SMS Number resource\"));\n            return -1;\n        }\n    }\n#endif // ANJAY_WITH_SMS\n\n    return 0;\n}\n\nAVS_LIST(sec_instance_t)\n_standalone_sec_clone_instances(const sec_repr_t *repr) {\n    AVS_LIST(sec_instance_t) retval = NULL;\n    AVS_LIST(sec_instance_t) current;\n    AVS_LIST(sec_instance_t) *last;\n    last = &retval;\n\n    AVS_LIST_FOREACH(current, repr->instances) {\n        if (AVS_LIST_INSERT_NEW(sec_instance_t, last)) {\n            if (_standalone_sec_clone_instance(*last, current)) {\n                security_log(ERROR,\n                             _(\"Cannot clone Security Object Instances\"));\n                _standalone_sec_destroy_instances(&retval, false);\n                return NULL;\n            }\n            AVS_LIST_ADVANCE_PTR(&last);\n        }\n    }\n    return retval;\n}\n\nvoid _standalone_raw_buffer_clear(standalone_raw_buffer_t *buffer) {\n    avs_free(buffer->data);\n    buffer->data = NULL;\n    buffer->size = 0;\n    buffer->capacity = 0;\n}\n\nint _standalone_raw_buffer_clone(standalone_raw_buffer_t *dst,\n                                 const standalone_raw_buffer_t *src) {\n    assert(!dst->data && !dst->size);\n    if (!src->size) {\n        return 0;\n    }\n    dst->data = avs_malloc(src->size);\n    if (!dst->data) {\n        return -1;\n    }\n    dst->capacity = src->size;\n    dst->size = src->size;\n    memcpy(dst->data, src->data, src->size);\n    return 0;\n}\n\ntypedef int chunk_getter_t(anjay_input_ctx_t *ctx,\n                           char *out,\n                           size_t out_size,\n                           bool *out_finished,\n                           size_t *out_bytes_read);\n\nstatic int bytes_getter(anjay_input_ctx_t *ctx,\n                        char *out,\n                        size_t size,\n                        bool *out_finished,\n                        size_t *out_bytes_read) {\n    return anjay_get_bytes(ctx, out_bytes_read, out_finished, out, size);\n}\n\nstatic int string_getter(anjay_input_ctx_t *ctx,\n                         char *out,\n                         size_t size,\n                         bool *out_finished,\n                         size_t *out_bytes_read) {\n    int result = anjay_get_string(ctx, out, size);\n    if (result < 0) {\n        return result;\n    }\n    *out_finished = true;\n    *out_bytes_read = strlen(out) + 1;\n    if (result == ANJAY_BUFFER_TOO_SHORT) {\n        *out_finished = false;\n        /**\n         * We don't want null terminator, because we're still in the phase of\n         * string chunk concatenation (and null terminators in the middle of\n         * the string are rather bad).\n         */\n        --*out_bytes_read;\n    }\n    return 0;\n}\n\nstatic int generic_getter(anjay_input_ctx_t *ctx,\n                          char **out,\n                          size_t *out_bytes_read,\n                          chunk_getter_t *getter) {\n    char tmp[128];\n    bool finished = false;\n    char *buffer = NULL;\n    size_t buffer_size = 0;\n    int result;\n    do {\n        size_t chunk_bytes_read = 0;\n        if ((result = getter(ctx, tmp, sizeof(tmp), &finished,\n                             &chunk_bytes_read))) {\n            goto error;\n        }\n        if (chunk_bytes_read > 0) {\n            char *bigger_buffer =\n                    (char *) avs_realloc(buffer,\n                                         buffer_size + chunk_bytes_read);\n            if (!bigger_buffer) {\n                result = ANJAY_ERR_INTERNAL;\n                goto error;\n            }\n            memcpy(bigger_buffer + buffer_size, tmp, chunk_bytes_read);\n            buffer = bigger_buffer;\n            buffer_size += chunk_bytes_read;\n        }\n    } while (!finished);\n    *out = buffer;\n    *out_bytes_read = buffer_size;\n    return 0;\nerror:\n    avs_free(buffer);\n    return result;\n}\n\nint _standalone_io_fetch_bytes(anjay_input_ctx_t *ctx,\n                               standalone_raw_buffer_t *buffer) {\n    _standalone_raw_buffer_clear(buffer);\n    int retval = generic_getter(ctx, (char **) &buffer->data, &buffer->size,\n                                bytes_getter);\n    buffer->capacity = buffer->size;\n    return retval;\n}\n\nint _standalone_io_fetch_string(anjay_input_ctx_t *ctx, char **out) {\n    avs_free(*out);\n    *out = NULL;\n    size_t bytes_read = 0;\n    return generic_getter(ctx, out, &bytes_read, string_getter);\n}\n"
  },
  {
    "path": "standalone/security/standalone_security_utils.h",
    "content": "#ifndef ANJAY_STANDALONE_SECURITY_UTILS_H\n#define ANJAY_STANDALONE_SECURITY_UTILS_H\n\n#include <assert.h>\n\n#include \"standalone_mod_security.h\"\n\nsec_repr_t *_standalone_sec_get(const anjay_dm_object_def_t *const *obj_ptr);\n\n/**\n * Fetches UDP Security Mode from @p ctx, performs validation and in case of\n * success sets @p *out to one of @p anjay_security_mode_t enum value.\n */\nint _standalone_sec_fetch_security_mode(anjay_input_ctx_t *ctx,\n                                        anjay_security_mode_t *out);\n\nint _standalone_sec_validate_security_mode(int32_t security_mode);\n\n#ifdef ANJAY_WITH_SMS\n/**\n * Fetches SMS Security Mode from @p ctx, performs validation and in case of\n * success sets @p *out to one of @p anjay_sms_security_mode_t enum value.\n */\nint _standalone_sec_fetch_sms_security_mode(anjay_input_ctx_t *ctx,\n                                            anjay_sms_security_mode_t *out);\n\nint _standalone_sec_validate_sms_security_mode(int32_t security_mode);\n#endif // ANJAY_WITH_SMS\n\n/**\n * Fetches SSID from @p ctx, performs validation and in case of success sets\n * @p *out .\n */\nint _standalone_sec_fetch_short_server_id(anjay_input_ctx_t *ctx,\n                                          anjay_ssid_t *out);\n\n#if defined(ANJAY_WITH_SECURITY_STRUCTURED)                    \\\n        || (defined(ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT) \\\n            && defined(AVS_COMMONS_WITH_AVS_CRYPTO_PKI_ENGINE))\nint _standalone_sec_init_certificate_chain_resource(\n        sec_key_or_data_t *out_resource,\n        sec_key_or_data_type_t type,\n        const avs_crypto_certificate_chain_info_t *in_value);\n\nint _standalone_sec_init_private_key_resource(\n        sec_key_or_data_t *out_resource,\n        sec_key_or_data_type_t type,\n        const avs_crypto_private_key_info_t *in_value);\n#endif /* defined(ANJAY_WITH_SECURITY_STRUCTURED) ||             \\\n          (defined(ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT) && \\\n          defined(AVS_COMMONS_WITH_AVS_CRYPTO_PKI_ENGINE)) */\n\n#if defined(ANJAY_WITH_SECURITY_STRUCTURED)                    \\\n        || (defined(ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT) \\\n            && defined(AVS_COMMONS_WITH_AVS_CRYPTO_PSK_ENGINE))\nint _standalone_sec_init_psk_identity_resource(\n        sec_key_or_data_t *out_resource,\n        sec_key_or_data_type_t type,\n        const avs_crypto_psk_identity_info_t *in_value);\n\nint _standalone_sec_init_psk_key_resource(\n        sec_key_or_data_t *out_resource,\n        sec_key_or_data_type_t type,\n        const avs_crypto_psk_key_info_t *in_value);\n#endif /* defined(ANJAY_WITH_SECURITY_STRUCTURED) ||             \\\n          (defined(ANJAY_WITH_MODULE_SECURITY_ENGINE_SUPPORT) && \\\n          defined(AVS_COMMONS_WITH_AVS_CRYPTO_PSK_ENGINE)) */\n\nvoid _standalone_sec_key_or_data_cleanup(sec_key_or_data_t *value,\n                                         bool remove_from_engine);\n\n/**\n * Frees all resources held in the @p instance.\n */\nvoid _standalone_sec_destroy_instance_fields(sec_instance_t *instance,\n                                             bool remove_from_engine);\n\n/**\n * Frees all resources held in instances from the @p instances_ptr list,\n * and the list itself.\n */\nvoid _standalone_sec_destroy_instances(AVS_LIST(sec_instance_t) *instances_ptr,\n                                       bool remove_from_engine);\n\n/**\n * Clones all instances of the given Security Object @p repr . Return NULL\n * if either there was nothing to clone or an error has occurred.\n */\nAVS_LIST(sec_instance_t)\n_standalone_sec_clone_instances(const sec_repr_t *repr);\n\nvoid _standalone_raw_buffer_clear(standalone_raw_buffer_t *buffer);\n\nint _standalone_raw_buffer_clone(standalone_raw_buffer_t *dst,\n                                 const standalone_raw_buffer_t *src);\n\n/**\n * Fetches bytes from @p ctx. On success it frees underlying @p buffer storage\n * via @p _anjay_sec_raw_buffer_clear and reinitializes @p buffer properly with\n * obtained data.\n */\nint _standalone_io_fetch_bytes(anjay_input_ctx_t *ctx,\n                               standalone_raw_buffer_t *buffer);\n\n/**\n * Fetches string from @p ctx. It calls avs_free() on @p *out and, on success,\n * reinitializes @p *out properly with a pointer to (heap allocated) obtained\n * data.\n */\nint _standalone_io_fetch_string(anjay_input_ctx_t *ctx, char **out);\n\n#endif /* ANJAY_STANDALONE_SECURITY_UTILS_H */\n"
  },
  {
    "path": "standalone/server/standalone_mod_server.c",
    "content": "#include <inttypes.h>\n#include <string.h>\n\n#include \"standalone_mod_server.h\"\n#include \"standalone_server_transaction.h\"\n#include \"standalone_server_utils.h\"\n\nstatic const struct {\n    server_rid_t rid;\n    anjay_dm_resource_kind_t kind;\n} SERVER_RESOURCE_INFO[] = {\n    {\n        .rid = SERV_RES_SSID,\n        .kind = ANJAY_DM_RES_R\n    },\n    {\n        .rid = SERV_RES_LIFETIME,\n        .kind = ANJAY_DM_RES_RW\n    },\n    {\n        .rid = SERV_RES_DEFAULT_MIN_PERIOD,\n        .kind = ANJAY_DM_RES_RW\n    },\n    {\n        .rid = SERV_RES_DEFAULT_MAX_PERIOD,\n        .kind = ANJAY_DM_RES_RW\n    },\n#ifndef ANJAY_WITHOUT_DEREGISTER\n    {\n        .rid = SERV_RES_DISABLE,\n        .kind = ANJAY_DM_RES_E\n    },\n    {\n        .rid = SERV_RES_DISABLE_TIMEOUT,\n        .kind = ANJAY_DM_RES_RW\n    },\n#endif // ANJAY_WITHOUT_DEREGISTER\n    {\n        .rid = SERV_RES_NOTIFICATION_STORING_WHEN_DISABLED_OR_OFFLINE,\n        .kind = ANJAY_DM_RES_RW\n    },\n    {\n        .rid = SERV_RES_BINDING,\n        .kind = ANJAY_DM_RES_RW\n    },\n    {\n        .rid = SERV_RES_REGISTRATION_UPDATE_TRIGGER,\n        .kind = ANJAY_DM_RES_E\n    },\n#ifdef ANJAY_WITH_LWM2M11\n    {\n        .rid = SERV_RES_BOOTSTRAP_REQUEST_TRIGGER,\n        .kind = ANJAY_DM_RES_E\n    },\n    {\n        .rid = SERV_RES_TLS_DTLS_ALERT_CODE,\n        .kind = ANJAY_DM_RES_R\n    },\n    {\n        .rid = SERV_RES_LAST_BOOTSTRAPPED,\n        .kind = ANJAY_DM_RES_R\n    },\n    {\n        .rid = SERV_RES_BOOTSTRAP_ON_REGISTRATION_FAILURE,\n        .kind = ANJAY_DM_RES_R\n    },\n    {\n        .rid = SERV_RES_SERVER_COMMUNICATION_RETRY_COUNT,\n        .kind = ANJAY_DM_RES_RW\n    },\n    {\n        .rid = SERV_RES_SERVER_COMMUNICATION_RETRY_TIMER,\n        .kind = ANJAY_DM_RES_RW\n    },\n    {\n        .rid = SERV_RES_SERVER_COMMUNICATION_SEQUENCE_DELAY_TIMER,\n        .kind = ANJAY_DM_RES_RW\n    },\n    {\n        .rid = SERV_RES_SERVER_COMMUNICATION_SEQUENCE_RETRY_COUNT,\n        .kind = ANJAY_DM_RES_RW\n    },\n#    ifdef ANJAY_WITH_SMS\n    {\n        .rid = SERV_RES_TRIGGER,\n        .kind = ANJAY_DM_RES_RW\n    },\n#    endif // ANJAY_WITH_SMS\n    {\n        .rid = SERV_RES_PREFERRED_TRANSPORT,\n        .kind = ANJAY_DM_RES_RW\n    },\n#    ifdef ANJAY_WITH_SEND\n    {\n        .rid = SERV_RES_MUTE_SEND,\n        .kind = ANJAY_DM_RES_RW\n    },\n#    endif // ANJAY_WITH_SEND\n#endif     // ANJAY_WITH_LWM2M11\n};\n\nstatic inline server_instance_t *find_instance(server_repr_t *repr,\n                                               anjay_iid_t iid) {\n    if (!repr) {\n        return NULL;\n    }\n    AVS_LIST(server_instance_t) it;\n    AVS_LIST_FOREACH(it, repr->instances) {\n        if (it->iid == iid) {\n            return it;\n        } else if (it->iid > iid) {\n            break;\n        }\n    }\n    return NULL;\n}\n\nstatic anjay_iid_t get_new_iid(AVS_LIST(server_instance_t) instances) {\n    anjay_iid_t iid = 0;\n    AVS_LIST(server_instance_t) it;\n    AVS_LIST_FOREACH(it, instances) {\n        if (it->iid == iid) {\n            ++iid;\n        } else if (it->iid > iid) {\n            break;\n        }\n    }\n    return iid;\n}\n\nstatic int assign_iid(server_repr_t *repr, anjay_iid_t *inout_iid) {\n    *inout_iid = get_new_iid(repr->instances);\n    if (*inout_iid == ANJAY_ID_INVALID) {\n        return -1;\n    }\n    return 0;\n}\n\nstatic void insert_created_instance(server_repr_t *repr,\n                                    AVS_LIST(server_instance_t) new_instance) {\n    AVS_LIST(server_instance_t) *ptr;\n    AVS_LIST_FOREACH_PTR(ptr, &repr->instances) {\n        assert((*ptr)->iid != new_instance->iid);\n        if ((*ptr)->iid > new_instance->iid) {\n            break;\n        }\n    }\n    _standalone_serv_mark_modified(repr);\n    AVS_LIST_INSERT(ptr, new_instance);\n}\n\nstatic int add_instance(server_repr_t *repr,\n                        const standalone_server_instance_t *instance,\n                        anjay_iid_t *inout_iid) {\n    if (*inout_iid == ANJAY_ID_INVALID) {\n        if (assign_iid(repr, inout_iid)) {\n            return -1;\n        }\n    } else if (find_instance(repr, *inout_iid)) {\n        return -1;\n    }\n    AVS_LIST(server_instance_t) new_instance =\n            AVS_LIST_NEW_ELEMENT(server_instance_t);\n    if (!new_instance) {\n        server_log(ERROR, _(\"out of memory\"));\n        return -1;\n    }\n    if (instance->binding) {\n        if (!anjay_binding_mode_valid(instance->binding)\n                || avs_simple_snprintf(new_instance->binding.data,\n                                       sizeof(new_instance->binding.data), \"%s\",\n                                       instance->binding)\n                               < 0) {\n            server_log(ERROR, _(\"Unsupported binding mode: \") \"%s\",\n                       instance->binding);\n            AVS_LIST_CLEAR(&new_instance);\n            return -1;\n        }\n        new_instance->present_resources[SERV_RES_BINDING] = true;\n    }\n    new_instance->iid = *inout_iid;\n    new_instance->present_resources[SERV_RES_SSID] = true;\n    new_instance->ssid = instance->ssid;\n    new_instance->present_resources[SERV_RES_LIFETIME] = true;\n    new_instance->lifetime = instance->lifetime;\n    if (instance->default_min_period >= 0) {\n        new_instance->present_resources[SERV_RES_DEFAULT_MIN_PERIOD] = true;\n        new_instance->default_min_period = instance->default_min_period;\n    }\n\n    if (instance->default_max_period >= 0) {\n        new_instance->present_resources[SERV_RES_DEFAULT_MAX_PERIOD] = true;\n        new_instance->default_max_period = instance->default_max_period;\n    }\n#ifndef ANJAY_WITHOUT_DEREGISTER\n    new_instance->present_resources[SERV_RES_DISABLE] = true;\n    if (instance->disable_timeout >= 0) {\n        new_instance->present_resources[SERV_RES_DISABLE_TIMEOUT] = true;\n        new_instance->disable_timeout = instance->disable_timeout;\n    }\n#endif // ANJAY_WITHOUT_DEREGISTER\n    new_instance->present_resources\n            [SERV_RES_NOTIFICATION_STORING_WHEN_DISABLED_OR_OFFLINE] = true;\n    new_instance->notification_storing = instance->notification_storing;\n    new_instance->present_resources[SERV_RES_REGISTRATION_UPDATE_TRIGGER] =\n            true;\n#ifdef ANJAY_WITH_LWM2M11\n#    ifdef ANJAY_WITH_BOOTSTRAP\n    new_instance->present_resources[SERV_RES_BOOTSTRAP_REQUEST_TRIGGER] = true;\n    new_instance\n            ->present_resources[SERV_RES_BOOTSTRAP_ON_REGISTRATION_FAILURE] =\n            true;\n#    endif // ANJAY_WITH_BOOTSTRAP\n    new_instance->bootstrap_on_registration_failure =\n            instance->bootstrap_on_registration_failure\n                    ? *instance->bootstrap_on_registration_failure\n                    : true;\n    if (instance->communication_retry_count) {\n        new_instance\n                ->present_resources[SERV_RES_SERVER_COMMUNICATION_RETRY_COUNT] =\n                true;\n        new_instance->server_communication_retry_count =\n                *instance->communication_retry_count;\n    }\n    if (instance->communication_retry_timer) {\n        new_instance\n                ->present_resources[SERV_RES_SERVER_COMMUNICATION_RETRY_TIMER] =\n                true;\n        new_instance->server_communication_retry_timer =\n                *instance->communication_retry_timer;\n    }\n    if (instance->preferred_transport) {\n        new_instance->preferred_transport = instance->preferred_transport;\n        new_instance->present_resources[SERV_RES_PREFERRED_TRANSPORT] = true;\n    }\n    if (instance->communication_sequence_retry_count) {\n        new_instance->present_resources\n                [SERV_RES_SERVER_COMMUNICATION_SEQUENCE_RETRY_COUNT] = true;\n        new_instance->server_communication_sequence_retry_count =\n                *instance->communication_sequence_retry_count;\n    }\n#    ifdef ANJAY_WITH_SMS\n    if (instance->trigger) {\n        new_instance->present_resources[SERV_RES_TRIGGER] = true;\n        new_instance->trigger = *instance->trigger;\n    }\n#    endif // ANJAY_WITH_SMS\n    if (instance->communication_sequence_delay_timer) {\n        new_instance->present_resources\n                [SERV_RES_SERVER_COMMUNICATION_SEQUENCE_DELAY_TIMER] = true;\n        new_instance->server_communication_sequence_delay_timer =\n                *instance->communication_sequence_delay_timer;\n    }\n#    ifdef ANJAY_WITH_SEND\n    new_instance->present_resources[SERV_RES_MUTE_SEND] = true;\n    new_instance->mute_send = instance->mute_send;\n#    endif // ANJAY_WITH_SEND\n#endif     // ANJAY_WITH_LWM2M11\n\n    insert_created_instance(repr, new_instance);\n    server_log(INFO, _(\"Added instance \") \"%u\" _(\" (SSID: \") \"%u\" _(\")\"),\n               *inout_iid, instance->ssid);\n    return 0;\n}\n\nstatic int del_instance(server_repr_t *repr, anjay_iid_t iid) {\n    AVS_LIST(server_instance_t) *it;\n    AVS_LIST_FOREACH_PTR(it, &repr->instances) {\n        if ((*it)->iid == iid) {\n            AVS_LIST_DELETE(it);\n            _standalone_serv_mark_modified(repr);\n            return 0;\n        } else if ((*it)->iid > iid) {\n            break;\n        }\n    }\n\n    assert(0);\n    return ANJAY_ERR_NOT_FOUND;\n}\n\nstatic int serv_list_instances(anjay_t *anjay,\n                               const anjay_dm_object_def_t *const *obj_ptr,\n                               anjay_dm_list_ctx_t *ctx) {\n    (void) anjay;\n\n    server_repr_t *repr = _standalone_serv_get(obj_ptr);\n    AVS_LIST(server_instance_t) it;\n    AVS_LIST_FOREACH(it, repr->instances) {\n        anjay_dm_emit(ctx, it->iid);\n    }\n    return 0;\n}\n\nstatic int serv_instance_create(anjay_t *anjay,\n                                const anjay_dm_object_def_t *const *obj_ptr,\n                                anjay_iid_t iid) {\n    (void) anjay;\n    server_repr_t *repr = _standalone_serv_get(obj_ptr);\n    assert(iid != ANJAY_ID_INVALID);\n    AVS_LIST(server_instance_t) created =\n            AVS_LIST_NEW_ELEMENT(server_instance_t);\n    if (!created) {\n        return ANJAY_ERR_INTERNAL;\n    }\n    created->iid = iid;\n    _standalone_serv_reset_instance(created);\n\n    insert_created_instance(repr, created);\n    return 0;\n}\n\nstatic int serv_instance_remove(anjay_t *anjay,\n                                const anjay_dm_object_def_t *const *obj_ptr,\n                                anjay_iid_t iid) {\n    (void) anjay;\n    return del_instance(_standalone_serv_get(obj_ptr), iid);\n}\n\nstatic int serv_instance_reset(anjay_t *anjay,\n                               const anjay_dm_object_def_t *const *obj_ptr,\n                               anjay_iid_t iid) {\n    (void) anjay;\n    server_instance_t *inst = find_instance(_standalone_serv_get(obj_ptr), iid);\n    assert(inst);\n\n    anjay_ssid_t ssid = inst->ssid;\n    _standalone_serv_reset_instance(inst);\n    inst->present_resources[SERV_RES_SSID] = true;\n    inst->ssid = ssid;\n    return 0;\n}\n\nstatic int serv_list_resources(anjay_t *anjay,\n                               const anjay_dm_object_def_t *const *obj_ptr,\n                               anjay_iid_t iid,\n                               anjay_dm_resource_list_ctx_t *ctx) {\n    (void) anjay;\n    const server_instance_t *inst =\n            find_instance(_standalone_serv_get(obj_ptr), iid);\n    assert(inst);\n\n    for (size_t resource = 0; resource < AVS_ARRAY_SIZE(SERVER_RESOURCE_INFO);\n         resource++) {\n        anjay_rid_t rid = SERVER_RESOURCE_INFO[resource].rid;\n        anjay_dm_emit_res(ctx, rid, SERVER_RESOURCE_INFO[resource].kind,\n                          inst->present_resources[rid] ? ANJAY_DM_RES_PRESENT\n                                                       : ANJAY_DM_RES_ABSENT);\n    }\n\n    return 0;\n}\n\nstatic int serv_read(anjay_t *anjay,\n                     const anjay_dm_object_def_t *const *obj_ptr,\n                     anjay_iid_t iid,\n                     anjay_rid_t rid,\n                     anjay_riid_t riid,\n                     anjay_output_ctx_t *ctx) {\n    (void) anjay;\n    (void) riid;\n    assert(riid == ANJAY_ID_INVALID);\n\n    const server_instance_t *inst =\n            find_instance(_standalone_serv_get(obj_ptr), iid);\n    assert(inst);\n\n    switch ((server_rid_t) rid) {\n    case SERV_RES_SSID:\n        return anjay_ret_i64(ctx, inst->ssid);\n    case SERV_RES_LIFETIME:\n        return anjay_ret_i64(ctx, inst->lifetime);\n    case SERV_RES_DEFAULT_MIN_PERIOD:\n        return anjay_ret_i64(ctx, inst->default_min_period);\n    case SERV_RES_DEFAULT_MAX_PERIOD:\n        return anjay_ret_i64(ctx, inst->default_max_period);\n#ifndef ANJAY_WITHOUT_DEREGISTER\n    case SERV_RES_DISABLE_TIMEOUT:\n        return anjay_ret_i64(ctx, inst->disable_timeout);\n#endif // ANJAY_WITHOUT_DEREGISTER\n    case SERV_RES_NOTIFICATION_STORING_WHEN_DISABLED_OR_OFFLINE:\n        return anjay_ret_bool(ctx, inst->notification_storing);\n    case SERV_RES_BINDING:\n        return anjay_ret_string(ctx, inst->binding.data);\n#ifdef ANJAY_WITH_LWM2M11\n    case SERV_RES_TLS_DTLS_ALERT_CODE:\n        return anjay_ret_u64(ctx, inst->last_alert);\n    case SERV_RES_LAST_BOOTSTRAPPED:\n        return anjay_ret_i64(ctx, inst->last_bootstrapped_timestamp);\n#    ifdef ANJAY_WITH_BOOTSTRAP\n    case SERV_RES_BOOTSTRAP_ON_REGISTRATION_FAILURE:\n        return anjay_ret_bool(ctx, inst->bootstrap_on_registration_failure);\n#    endif // ANJAY_WITH_BOOTSTRAP\n    case SERV_RES_SERVER_COMMUNICATION_RETRY_COUNT:\n        return anjay_ret_u64(ctx, inst->server_communication_retry_count);\n    case SERV_RES_SERVER_COMMUNICATION_RETRY_TIMER:\n        return anjay_ret_u64(ctx, inst->server_communication_retry_timer);\n    case SERV_RES_SERVER_COMMUNICATION_SEQUENCE_RETRY_COUNT:\n        return anjay_ret_u64(ctx,\n                             inst->server_communication_sequence_retry_count);\n    case SERV_RES_SERVER_COMMUNICATION_SEQUENCE_DELAY_TIMER:\n        return anjay_ret_u64(ctx,\n                             inst->server_communication_sequence_delay_timer);\n#    ifdef ANJAY_WITH_SMS\n    case SERV_RES_TRIGGER:\n        return anjay_ret_bool(ctx, inst->trigger);\n#    endif // ANJAY_WITH_SMS\n    case SERV_RES_PREFERRED_TRANSPORT: {\n        char tmp[2] = { inst->preferred_transport, '\\0' };\n        return anjay_ret_string(ctx, tmp);\n    }\n#    ifdef ANJAY_WITH_SEND\n    case SERV_RES_MUTE_SEND:\n        return anjay_ret_bool(ctx, inst->mute_send);\n#    endif // ANJAY_WITH_SEND\n#endif     // ANJAY_WITH_LWM2M11\n    default:\n        AVS_UNREACHABLE(\n                \"Read called on unknown or non-readable Server resource\");\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\nstatic int serv_write(anjay_t *anjay,\n                      const anjay_dm_object_def_t *const *obj_ptr,\n                      anjay_iid_t iid,\n                      anjay_rid_t rid,\n                      anjay_riid_t riid,\n                      anjay_input_ctx_t *ctx) {\n    (void) anjay;\n    (void) riid;\n    assert(riid == ANJAY_ID_INVALID);\n\n    server_repr_t *repr = _standalone_serv_get(obj_ptr);\n    server_instance_t *inst = find_instance(repr, iid);\n    assert(inst);\n    int retval;\n\n    _standalone_serv_mark_modified(repr);\n\n    switch ((server_rid_t) rid) {\n    case SERV_RES_SSID:\n        retval = _standalone_serv_fetch_ssid(ctx, &inst->ssid);\n        break;\n    case SERV_RES_LIFETIME:\n        retval = anjay_get_i32(ctx, &inst->lifetime);\n        break;\n    case SERV_RES_DEFAULT_MIN_PERIOD:\n        retval =\n                _standalone_serv_fetch_validated_i32(ctx, 0, INT32_MAX,\n                                                     &inst->default_min_period);\n        break;\n    case SERV_RES_DEFAULT_MAX_PERIOD:\n        retval =\n                _standalone_serv_fetch_validated_i32(ctx, 1, INT32_MAX,\n                                                     &inst->default_max_period);\n        break;\n#ifndef ANJAY_WITHOUT_DEREGISTER\n    case SERV_RES_DISABLE_TIMEOUT:\n        retval = _standalone_serv_fetch_validated_i32(ctx, 0, INT32_MAX,\n                                                      &inst->disable_timeout);\n        break;\n#endif // ANJAY_WITHOUT_DEREGISTER\n    case SERV_RES_BINDING:\n        retval = _standalone_serv_fetch_binding(ctx, &inst->binding);\n        break;\n    case SERV_RES_NOTIFICATION_STORING_WHEN_DISABLED_OR_OFFLINE:\n        retval = anjay_get_bool(ctx, &inst->notification_storing);\n        break;\n#ifdef ANJAY_WITH_LWM2M11\n    case SERV_RES_TLS_DTLS_ALERT_CODE: {\n        uint32_t last_alert;\n        if (!(retval = anjay_get_u32(ctx, &last_alert))) {\n            inst->last_alert = (uint8_t) last_alert;\n        }\n        break;\n    }\n    case SERV_RES_LAST_BOOTSTRAPPED:\n        retval = anjay_get_i64(ctx, &inst->last_bootstrapped_timestamp);\n        break;\n#    ifdef ANJAY_WITH_BOOTSTRAP\n    case SERV_RES_BOOTSTRAP_ON_REGISTRATION_FAILURE:\n        retval = anjay_get_bool(ctx, &inst->bootstrap_on_registration_failure);\n        break;\n#    endif // ANJAY_WITH_BOOTSTRAP\n    case SERV_RES_SERVER_COMMUNICATION_RETRY_COUNT:\n        if (!(retval = anjay_get_u32(ctx,\n                                     &inst->server_communication_retry_count))\n                && inst->server_communication_retry_count == 0) {\n            server_log(ERROR, \"Server Communication Retry Count cannot be 0\");\n            retval = ANJAY_ERR_BAD_REQUEST;\n        }\n        break;\n    case SERV_RES_SERVER_COMMUNICATION_RETRY_TIMER:\n        retval = anjay_get_u32(ctx, &inst->server_communication_retry_timer);\n        break;\n    case SERV_RES_SERVER_COMMUNICATION_SEQUENCE_RETRY_COUNT:\n        if (!(retval = anjay_get_u32(\n                      ctx, &inst->server_communication_sequence_retry_count))\n                && inst->server_communication_sequence_retry_count == 0) {\n            server_log(ERROR,\n                       \"Server Sequence Communication Retry Count cannot be 0\");\n            retval = ANJAY_ERR_BAD_REQUEST;\n        }\n        break;\n    case SERV_RES_SERVER_COMMUNICATION_SEQUENCE_DELAY_TIMER:\n        retval =\n                anjay_get_u32(ctx,\n                              &inst->server_communication_sequence_delay_timer);\n        break;\n#    ifdef ANJAY_WITH_SMS\n    case SERV_RES_TRIGGER:\n        retval = anjay_get_bool(ctx, &inst->trigger);\n        break;\n#    endif // ANJAY_WITH_SMS\n    case SERV_RES_PREFERRED_TRANSPORT: {\n        char tmp[2];\n        if (!(retval = anjay_get_string(ctx, tmp, sizeof(tmp)))) {\n            inst->preferred_transport = tmp[0];\n        } else if (retval == ANJAY_BUFFER_TOO_SHORT) {\n            retval = ANJAY_ERR_BAD_REQUEST;\n        }\n        break;\n    }\n#    ifdef ANJAY_WITH_SEND\n    case SERV_RES_MUTE_SEND:\n        retval = anjay_get_bool(ctx, &inst->mute_send);\n        break;\n#    endif // ANJAY_WITH_SEND\n#endif     // ANJAY_WITH_LWM2M11\n    default:\n        AVS_UNREACHABLE(\n                \"Write called on unknown or non-read/writable Server resource\");\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n\n    if (!retval) {\n        inst->present_resources[rid] = true;\n    }\n\n    return retval;\n}\n\nstatic int serv_execute(anjay_t *anjay,\n                        const anjay_dm_object_def_t *const *obj_ptr,\n                        anjay_iid_t iid,\n                        anjay_rid_t rid,\n                        anjay_execute_ctx_t *ctx) {\n    (void) anjay;\n    (void) obj_ptr;\n    (void) ctx;\n    server_instance_t *inst = find_instance(_standalone_serv_get(obj_ptr), iid);\n    assert(inst);\n\n    switch ((server_rid_t) rid) {\n#ifndef ANJAY_WITHOUT_DEREGISTER\n    case SERV_RES_DISABLE: {\n        avs_time_duration_t disable_timeout = avs_time_duration_from_scalar(\n                inst->present_resources[SERV_RES_DISABLE_TIMEOUT]\n                        ? inst->disable_timeout\n                        : 86400,\n                AVS_TIME_S);\n        return anjay_disable_server_with_timeout(anjay, inst->ssid,\n                                                 disable_timeout);\n    }\n#endif // ANJAY_WITHOUT_DEREGISTER\n    case SERV_RES_REGISTRATION_UPDATE_TRIGGER:\n        return anjay_schedule_registration_update(anjay, inst->ssid)\n                       ? ANJAY_ERR_BAD_REQUEST\n                       : 0;\n#if defined(ANJAY_WITH_LWM2M11) && defined(ANJAY_WITH_BOOTSTRAP)\n    case SERV_RES_BOOTSTRAP_REQUEST_TRIGGER:\n        return anjay_schedule_bootstrap_request(anjay)\n                       ? ANJAY_ERR_METHOD_NOT_ALLOWED\n                       : 0;\n#endif // defined(ANJAY_WITH_LWM2M11) && defined(ANJAY_WITH_BOOTSTRAP)\n    default:\n        AVS_UNREACHABLE(\n                \"Execute called on unknown or non-executable Server resource\");\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\nstatic int serv_transaction_begin(anjay_t *anjay,\n                                  const anjay_dm_object_def_t *const *obj_ptr) {\n    (void) anjay;\n    return _standalone_serv_transaction_begin_impl(\n            _standalone_serv_get(obj_ptr));\n}\n\nstatic int\nserv_transaction_commit(anjay_t *anjay,\n                        const anjay_dm_object_def_t *const *obj_ptr) {\n    (void) anjay;\n    return _standalone_serv_transaction_commit_impl(\n            _standalone_serv_get(obj_ptr));\n}\n\nstatic int\nserv_transaction_validate(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr) {\n    (void) anjay;\n    return _standalone_serv_transaction_validate_impl(\n            _standalone_serv_get(obj_ptr));\n}\n\nstatic int\nserv_transaction_rollback(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr) {\n    (void) anjay;\n    return _standalone_serv_transaction_rollback_impl(\n            _standalone_serv_get(obj_ptr));\n}\n\nstatic const anjay_dm_object_def_t SERVER = {\n    .oid = ANJAY_DM_OID_SERVER,\n    .handlers = {\n        .list_instances = serv_list_instances,\n        .instance_create = serv_instance_create,\n        .instance_remove = serv_instance_remove,\n        .instance_reset = serv_instance_reset,\n        .list_resources = serv_list_resources,\n        .resource_read = serv_read,\n        .resource_write = serv_write,\n        .resource_execute = serv_execute,\n        .transaction_begin = serv_transaction_begin,\n        .transaction_validate = serv_transaction_validate,\n        .transaction_commit = serv_transaction_commit,\n        .transaction_rollback = serv_transaction_rollback\n    }\n};\n\nserver_repr_t *\n_standalone_serv_get(const anjay_dm_object_def_t *const *obj_ptr) {\n    assert(obj_ptr && *obj_ptr == &SERVER);\n    return AVS_CONTAINER_OF(obj_ptr, server_repr_t, def);\n}\n\nint standalone_server_object_add_instance(\n        const anjay_dm_object_def_t *const *obj_ptr,\n        const standalone_server_instance_t *instance,\n        anjay_iid_t *inout_iid) {\n    int retval = -1;\n    server_repr_t *repr = _standalone_serv_get(obj_ptr);\n    if (!repr) {\n        server_log(ERROR, _(\"Server object is not registered\"));\n        retval = -1;\n    } else {\n        const bool modified_since_persist = repr->modified_since_persist;\n        if (!(retval = add_instance(repr, instance, inout_iid))\n                && (retval = _standalone_serv_object_validate(repr))) {\n            (void) del_instance(repr, *inout_iid);\n            if (!modified_since_persist) {\n                /* validation failed and so in the end no instace is added */\n                _standalone_serv_clear_modified(repr);\n            }\n        }\n\n        if (!retval) {\n            if (anjay_notify_instances_changed(repr->anjay, SERVER.oid)) {\n                server_log(WARNING, _(\"Could not schedule socket reload\"));\n            }\n        }\n    }\n    return retval;\n}\n\nstatic void server_purge(server_repr_t *repr) {\n    if (repr->instances) {\n        _standalone_serv_mark_modified(repr);\n    }\n    _standalone_serv_destroy_instances(&repr->instances);\n    _standalone_serv_destroy_instances(&repr->saved_instances);\n}\n\nvoid standalone_server_object_cleanup(\n        const anjay_dm_object_def_t *const *obj_ptr) {\n    server_repr_t *repr = _standalone_serv_get(obj_ptr);\n    server_purge(repr);\n    avs_free(repr);\n}\n\nvoid standalone_server_object_purge(\n        const anjay_dm_object_def_t *const *obj_ptr) {\n    server_repr_t *repr = _standalone_serv_get(obj_ptr);\n\n    if (!repr) {\n        server_log(ERROR, _(\"Server object is not registered\"));\n    } else {\n        server_purge(repr);\n        if (anjay_notify_instances_changed(repr->anjay, SERVER.oid)) {\n            server_log(WARNING, _(\"Could not schedule socket reload\"));\n        }\n    }\n}\n\nAVS_LIST(const anjay_ssid_t)\nstandalone_server_get_ssids(const anjay_dm_object_def_t *const *obj_ptr) {\n    AVS_LIST(server_instance_t) source = NULL;\n    server_repr_t *repr = _standalone_serv_get(obj_ptr);\n    if (repr->in_transaction) {\n        source = repr->saved_instances;\n    } else {\n        source = repr->instances;\n    }\n    // We rely on the fact that the \"ssid\" field is first in server_instance_t,\n    // which means that both \"source\" and \"&source->ssid\" point to exactly the\n    // same memory location. The \"next\" pointer location in AVS_LIST is\n    // independent from the stored data type, so it's safe to do such \"cast\".\n    AVS_STATIC_ASSERT(offsetof(server_instance_t, ssid) == 0,\n                      instance_ssid_is_first_field);\n    return &source->ssid;\n}\n\nbool standalone_server_object_is_modified(\n        const anjay_dm_object_def_t *const *obj_ptr) {\n    bool result = false;\n    server_repr_t *repr = _standalone_serv_get(obj_ptr);\n    if (!repr) {\n        server_log(ERROR, _(\"Server object is not registered\"));\n    } else {\n        if (repr->in_transaction) {\n            result = repr->saved_modified_since_persist;\n        } else {\n            result = repr->modified_since_persist;\n        }\n    }\n    return result;\n}\n\nconst anjay_dm_object_def_t **standalone_server_object_install(anjay_t *anjay) {\n    assert(anjay);\n    server_repr_t *repr =\n            (server_repr_t *) avs_calloc(1, sizeof(server_repr_t));\n    if (!repr) {\n        server_log(ERROR, _(\"out of memory\"));\n        return NULL;\n    }\n    repr->def = &SERVER;\n    repr->anjay = anjay;\n    if (anjay_register_object(anjay, &repr->def)) {\n        avs_free(repr);\n        return NULL;\n    }\n    return &repr->def;\n}\n\nint standalone_server_object_set_lifetime(\n        const anjay_dm_object_def_t *const *obj_ptr,\n        anjay_iid_t iid,\n        int32_t lifetime) {\n    if (lifetime <= 0) {\n        server_log(ERROR, _(\"lifetime MUST BE strictly positive\"));\n        return -1;\n    }\n    int result = -1;\n    server_repr_t *repr = _standalone_serv_get(obj_ptr);\n    if (repr->saved_instances) {\n        server_log(ERROR, _(\"cannot set Lifetime while some transaction is \"\n                            \"started on the Server Object\"));\n    } else {\n        AVS_LIST(server_instance_t) it;\n        AVS_LIST_FOREACH(it, repr->instances) {\n            if (it->iid >= iid) {\n                break;\n            }\n        }\n\n        if (!it || it->iid != iid) {\n            server_log(ERROR, _(\"instance \") \"%\" PRIu16 _(\" not found\"), iid);\n        } else if (it->lifetime != lifetime) {\n            if (anjay_notify_changed(repr->anjay, ANJAY_DM_OID_SERVER, it->iid,\n                                     SERV_RES_LIFETIME)) {\n                server_log(WARNING, _(\"could not notify lifetime change\"));\n            }\n            repr->modified_since_persist = true;\n            it->lifetime = lifetime;\n            result = 0;\n        }\n    }\n    return result;\n}\n"
  },
  {
    "path": "standalone/server/standalone_mod_server.h",
    "content": "#ifndef ANJAY_STANDALONE_SERVER_MOD_SERVER_H\n#define ANJAY_STANDALONE_SERVER_MOD_SERVER_H\n\n#include <avsystem/commons/avs_log.h>\n\n#include \"standalone_server.h\"\n\ntypedef struct {\n    char data[8];\n} standalone_binding_mode_t;\n\ntypedef enum {\n    SERV_RES_SSID = 0,\n    SERV_RES_LIFETIME = 1,\n    SERV_RES_DEFAULT_MIN_PERIOD = 2,\n    SERV_RES_DEFAULT_MAX_PERIOD = 3,\n#ifndef ANJAY_WITHOUT_DEREGISTER\n    SERV_RES_DISABLE = 4,\n    SERV_RES_DISABLE_TIMEOUT = 5,\n#endif // ANJAY_WITHOUT_DEREGISTER\n    SERV_RES_NOTIFICATION_STORING_WHEN_DISABLED_OR_OFFLINE = 6,\n    SERV_RES_BINDING = 7,\n    SERV_RES_REGISTRATION_UPDATE_TRIGGER = 8,\n#ifdef ANJAY_WITH_LWM2M11\n    SERV_RES_BOOTSTRAP_REQUEST_TRIGGER = 9,\n    SERV_RES_TLS_DTLS_ALERT_CODE = 11,\n    SERV_RES_LAST_BOOTSTRAPPED = 12,\n    SERV_RES_BOOTSTRAP_ON_REGISTRATION_FAILURE = 16,\n    SERV_RES_SERVER_COMMUNICATION_RETRY_COUNT = 17,\n    SERV_RES_SERVER_COMMUNICATION_RETRY_TIMER = 18,\n    SERV_RES_SERVER_COMMUNICATION_SEQUENCE_DELAY_TIMER = 19,\n    SERV_RES_SERVER_COMMUNICATION_SEQUENCE_RETRY_COUNT = 20,\n#    ifdef ANJAY_WITH_SMS\n    SERV_RES_TRIGGER = 21,\n#    endif // ANJAY_WITH_SMS\n    SERV_RES_PREFERRED_TRANSPORT = 22,\n#    ifdef ANJAY_WITH_SEND\n    SERV_RES_MUTE_SEND = 23,\n#    endif // ANJAY_WITH_SEND\n#endif     // ANJAY_WITH_LWM2M11\n    _SERV_RES_COUNT\n} server_rid_t;\n\ntypedef struct {\n    /* mandatory resources */\n    anjay_ssid_t ssid;\n    standalone_binding_mode_t binding;\n    int32_t lifetime;\n    int32_t default_min_period;\n    int32_t default_max_period;\n#ifndef ANJAY_WITHOUT_DEREGISTER\n    int32_t disable_timeout;\n#endif // ANJAY_WITHOUT_DEREGISTER\n    bool notification_storing;\n\n    anjay_iid_t iid;\n\n#ifdef ANJAY_WITH_LWM2M11\n    int64_t last_bootstrapped_timestamp;\n    uint8_t last_alert;\n    bool bootstrap_on_registration_failure;\n    uint32_t server_communication_retry_count;\n    uint32_t server_communication_retry_timer;\n    uint32_t server_communication_sequence_retry_count;\n    uint32_t server_communication_sequence_delay_timer;\n#    ifdef ANJAY_WITH_SMS\n    bool trigger;\n#    endif // ANJAY_WITH_SMS\n    char preferred_transport;\n#    ifdef ANJAY_WITH_SEND\n    bool mute_send;\n#    endif // ANJAY_WITH_SEND\n#endif     // ANJAY_WITH_LWM2M11\n\n    bool present_resources[_SERV_RES_COUNT];\n} server_instance_t;\n\ntypedef struct {\n    const anjay_dm_object_def_t *def;\n    anjay_t *anjay;\n    AVS_LIST(server_instance_t) instances;\n    AVS_LIST(server_instance_t) saved_instances;\n    bool modified_since_persist;\n    bool saved_modified_since_persist;\n    bool in_transaction;\n} server_repr_t;\n\nstatic inline void _standalone_serv_mark_modified(server_repr_t *repr) {\n    repr->modified_since_persist = true;\n}\n\nstatic inline void _standalone_serv_clear_modified(server_repr_t *repr) {\n    repr->modified_since_persist = false;\n}\n\n#define server_log(level, ...) avs_log(server, level, __VA_ARGS__)\n#define _(Arg) AVS_DISPOSABLE_LOG(Arg)\n\n#endif /* ANJAY_STANDALONE_SERVER_MOD_SERVER_H */\n"
  },
  {
    "path": "standalone/server/standalone_server.h",
    "content": "#ifndef ANJAY_STANDALONE_ANJAY_SERVER_H\n#define ANJAY_STANDALONE_ANJAY_SERVER_H\n\n#include <anjay/anjay_config.h>\n#include <anjay/dm.h>\n\n#include <avsystem/commons/avs_stream.h>\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\ntypedef struct {\n    /** Resource: Short Server ID */\n    anjay_ssid_t ssid;\n    /** Resource: Lifetime */\n    int32_t lifetime;\n    /** Resource: Default Minimum Period - or a negative value to disable\n     * presence */\n    int32_t default_min_period;\n    /** Resource: Default Maximum Period - or a negative value to disable\n     * presence */\n    int32_t default_max_period;\n    /** Resource: Disable Timeout - or a negative value to disable presence */\n    int32_t disable_timeout;\n    /** Resource: Binding */\n    const char *binding;\n    /** Resource: Notification Storing When Disabled or Offline */\n    bool notification_storing;\n#ifdef ANJAY_WITH_LWM2M11\n    /** Resource: Bootstrap on Registration Failure. True if not set. */\n    const bool *bootstrap_on_registration_failure;\n    /** Resource: Preferred Transport */\n    char preferred_transport;\n    /** Resource: Mute Send */\n    bool mute_send;\n    /** Resource: Communication Retry Count. NULL if not set. */\n    const uint32_t *communication_retry_count;\n    /** Resource: Communication Retry Timer. NULL if not set. */\n    const uint32_t *communication_retry_timer;\n    /** Resource: Communication Sequence Retry Count. NULL if not set. */\n    const uint32_t *communication_sequence_retry_count;\n    /** Resource: Communication Sequence Delay Timer (in seconds). NULL if not\n     * set. */\n    const uint32_t *communication_sequence_delay_timer;\n#    ifdef ANJAY_WITH_SMS\n    /** Resource: Trigger */\n    const bool *trigger;\n#    endif // ANJAY_WITH_SMS\n#endif     // ANJAY_WITH_LWM2M11\n} standalone_server_instance_t;\n\n/**\n * Adds new Instance of Server Object and returns newly created Instance id\n * via @p inout_iid .\n *\n * Note: if @p *inout_iid is set to @ref ANJAY_ID_INVALID then the Instance id\n * is generated automatically, otherwise value of @p *inout_iid is used as a\n * new Server Instance Id.\n *\n * Note: @p instance may be safely freed by the user code after this function\n * finishes (internally a deep copy of @ref standalone_server_instance_t is\n * performed).\n *\n * @param obj_ptr   Installed Server Object to operate on.\n * @param instance  Server Instance to insert.\n * @param inout_iid Server Instance id to use or @ref ANJAY_ID_INVALID .\n *\n * @return 0 on success, negative value in case of an error or if the instance\n * of specified id already exists.\n */\nint standalone_server_object_add_instance(\n        const anjay_dm_object_def_t *const *obj_ptr,\n        const standalone_server_instance_t *instance,\n        anjay_iid_t *inout_iid);\n\n/**\n * Removes all instances of Server Object leaving it in an empty state.\n *\n * @param obj_ptr Installed Server Object to operate on.\n */\nvoid standalone_server_object_purge(\n        const anjay_dm_object_def_t *const *obj_ptr);\n\n/**\n * Retrieves a list of SSIDs currently present in the Server object. The SSIDs\n * are NOT guaranteed to be returned in any particular order. Returned list may\n * not be freed nor modified.\n *\n * The returned list pointer shall be considered invalidated by any call to @ref\n * anjay_sched_run, @ref anjay_serve, @ref\n * standalone_server_object_add_instance,\n * @ref standalone_server_object_purge, @ref standalone_server_object_restore,\n * or, if called from within some callback handler, on return from that handler.\n *\n * If a transaction on the Server object is currently ongoing (e.g., during\n * Bootstrap), last known state from before the transaction will be returned.\n *\n * @param obj_ptr Installed Server Object to operate on.\n *\n * @returns A list of known SSIDs on success, NULL when the object is empty.\n */\nAVS_LIST(const anjay_ssid_t)\nstandalone_server_get_ssids(const anjay_dm_object_def_t *const *obj_ptr);\n\n/**\n * Dumps Server Object Instances into the @p out_stream .\n *\n * @param obj_ptr       Installed Server Object to operate on.\n * @param out_stream    Stream to write to.\n * @return AVS_OK in case of success, or an error code.\n */\navs_error_t\nstandalone_server_object_persist(const anjay_dm_object_def_t *const *obj_ptr,\n                                 avs_stream_t *out_stream);\n\n/**\n * Attempts to restore Server Object Instances from specified @p in_stream .\n *\n * Note: if restore fails, then Server Object will be left untouched, on\n * success though all Instances stored within the Object will be purged.\n *\n * @param obj_ptr   Installed Server Object to operate on.\n * @param in_stream Stream to read from.\n * @return AVS_OK in case of success, or an error code.\n */\navs_error_t\nstandalone_server_object_restore(const anjay_dm_object_def_t *const *obj_ptr,\n                                 avs_stream_t *in_stream);\n\n/**\n * Checks whether the Server Object has been modified since\n * last successful call to @ref standalone_server_object_persist or @ref\n * standalone_server_object_restore.\n */\nbool standalone_server_object_is_modified(\n        const anjay_dm_object_def_t *const *obj_ptr);\n\n/**\n * Creates a Server Object and installs it in an Anjay instance using\n * @ref anjay_register_object.\n *\n * Do NOT attempt to call @ref anjay_register_object with this object manually,\n * and do NOT try to use the same instance of the Server object with another\n * Anjay instance.\n *\n * @param anjay Anjay instance for which the Server Object is installed.\n *\n * @returns Handle to the created object that can be passed to other functions\n *          declared in this file, or <c>NULL</c> in case of error.\n */\nconst anjay_dm_object_def_t **standalone_server_object_install(anjay_t *anjay);\n\n/**\n * Frees all system resources allocated by the Server Object.\n *\n * <strong>NOTE:</strong> Attempting to call this function before deregistering\n * the object using @ref anjay_unregister_object, @ref anjay_delete or\n * @ref anjay_delete_with_core_persistence is undefined behavior.\n *\n * @param obj_ptr Server Object to operate on.\n */\nvoid standalone_server_object_cleanup(\n        const anjay_dm_object_def_t *const *obj_ptr);\n\n/**\n * Sets the Lifetime value for the specified Server Instance ID.\n *\n * NOTE: Calling this function MAY trigger sending LwM2M Update message to\n * an associated LwM2M Server.\n *\n * @param obj_ptr   Installed Server Object to operate on.\n * @param iid       Server Object Instance for which the Lifetime shall be\n *                  altered.\n * @param lifetime  New value of the Lifetime Resource. MUST BE strictly\n *                  positive.\n *\n * @returns 0 on success, negative value in case of an error. If an error\n * is returned, the Lifetime value remains unchanged.\n */\nint standalone_server_object_set_lifetime(\n        const anjay_dm_object_def_t *const *obj_ptr,\n        anjay_iid_t iid,\n        int32_t lifetime);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif /* ANJAY_STANDALONE_ANJAY_SERVER_H */\n"
  },
  {
    "path": "standalone/server/standalone_server_persistence.c",
    "content": "#include <inttypes.h>\n#include <string.h>\n\n#include \"standalone_server_transaction.h\"\n#include \"standalone_server_utils.h\"\n\n#ifdef AVS_COMMONS_WITH_AVS_PERSISTENCE\n#    include <avsystem/commons/avs_persistence.h>\n#endif // AVS_COMMONS_WITH_AVS_PERSISTENCE\n#include <avsystem/commons/avs_utils.h>\n\n#define persistence_log(level, ...) \\\n    avs_log(server_persistence, level, __VA_ARGS__)\n\n#ifdef AVS_COMMONS_WITH_AVS_PERSISTENCE\n\ntypedef enum {\n    PERSISTENCE_VERSION_0,\n\n    /** Binding resource as string instead of enum */\n    PERSISTENCE_VERSION_1,\n\n    /**\n     * New resources:\n     * - 11: TLS-DTLS Alert Code\n     * - 12: Last Bootstrapped\n     * - 16: Bootstrap on Registration Failure\n     * - 17: Communication Retry Count\n     * - 18: Communication Retry Timer\n     * - 19: Communication Sequence Delay Timer\n     * - 20: Communication Sequence Retry Count\n     * - 22: Preferred Transport\n     * - 23: Mute Send\n     */\n    PERSISTENCE_VERSION_2,\n\n    /**\n     * New resource: Trigger\n     */\n    PERSISTENCE_VERSION_3\n} server_persistence_version_t;\n\ntypedef char magic_t[4];\nstatic const magic_t MAGIC_V0 = { 'S', 'R', 'V', PERSISTENCE_VERSION_0 };\nstatic const magic_t MAGIC_V1 = { 'S', 'R', 'V', PERSISTENCE_VERSION_1 };\nstatic const magic_t MAGIC_V2 = { 'S', 'R', 'V', PERSISTENCE_VERSION_2 };\nstatic const magic_t MAGIC_V3 = { 'S', 'R', 'V', PERSISTENCE_VERSION_3 };\n\nstatic avs_error_t handle_v0_v1_sized_fields(avs_persistence_context_t *ctx,\n                                             server_instance_t *element) {\n    assert(avs_persistence_direction(ctx) == AVS_PERSISTENCE_RESTORE);\n    avs_error_t err;\n    (void) (avs_is_err((err = avs_persistence_u16(ctx, &element->iid)))\n            || avs_is_err((err = avs_persistence_bool(\n                                   ctx,\n                                   &element->present_resources[SERV_RES_SSID])))\n            || avs_is_err(\n                       (err = avs_persistence_bool(\n                                ctx,\n                                &element->present_resources[SERV_RES_BINDING])))\n            || avs_is_err((\n                       err = avs_persistence_bool(\n                               ctx,\n                               &element->present_resources[SERV_RES_LIFETIME])))\n            || avs_is_err((\n                       err = avs_persistence_bool(\n                               ctx,\n                               &element->present_resources\n                                        [SERV_RES_NOTIFICATION_STORING_WHEN_DISABLED_OR_OFFLINE])))\n            || avs_is_err((err = avs_persistence_u16(ctx, &element->ssid)))\n            || avs_is_err((err = avs_persistence_u32(\n                                   ctx, (uint32_t *) &element->lifetime)))\n            || avs_is_err((\n                       err = avs_persistence_u32(\n                               ctx, (uint32_t *) &element->default_min_period)))\n            || avs_is_err((\n                       err = avs_persistence_u32(\n                               ctx, (uint32_t *) &element->default_max_period)))\n            || avs_is_err((err = avs_persistence_u32(\n                                   ctx,\n#    ifndef ANJAY_WITHOUT_DEREGISTER\n                                   (uint32_t *) &element->disable_timeout\n#    else  // ANJAY_WITHOUT_DEREGISTER\n                                   (uint32_t *) &(int32_t) { -1 }\n#    endif // ANJAY_WITHOUT_DEREGISTER\n                                   )))\n            || avs_is_err((err = avs_persistence_bool(\n                                   ctx, &element->notification_storing))));\n    if (avs_is_ok(err)) {\n        element->present_resources[SERV_RES_DEFAULT_MIN_PERIOD] =\n                (element->default_min_period >= 0);\n        element->present_resources[SERV_RES_DEFAULT_MAX_PERIOD] =\n                (element->default_max_period >= 0);\n#    ifndef ANJAY_WITHOUT_DEREGISTER\n        element->present_resources[SERV_RES_DISABLE_TIMEOUT] =\n                (element->disable_timeout >= 0);\n#    endif // ANJAY_WITHOUT_DEREGISTER\n        element->present_resources\n                [SERV_RES_NOTIFICATION_STORING_WHEN_DISABLED_OR_OFFLINE] = true;\n    }\n    return err;\n}\n\nstatic avs_error_t\nhandle_v2_lwm2m11_sized_fields(avs_persistence_context_t *ctx,\n                               server_instance_t *element) {\n#    ifndef ANJAY_WITH_LWM2M11\n    enum {\n        SERV_RES_TLS_DTLS_ALERT_CODE,\n        SERV_RES_LAST_BOOTSTRAPPED,\n        SERV_RES_SERVER_COMMUNICATION_RETRY_COUNT,\n        SERV_RES_SERVER_COMMUNICATION_RETRY_TIMER,\n        SERV_RES_SERVER_COMMUNICATION_SEQUENCE_RETRY_COUNT,\n        SERV_RES_SERVER_COMMUNICATION_SEQUENCE_DELAY_TIMER,\n        SERV_RES_PREFERRED_TRANSPORT,\n        _FAKE_RESOURCES_NUM\n    };\n\n    (void) element;\n    struct {\n        int64_t last_bootstrapped_timestamp;\n        uint8_t last_alert;\n        bool bootstrap_on_registration_failure;\n        uint32_t server_communication_retry_count;\n        uint32_t server_communication_retry_timer;\n        uint32_t server_communication_sequence_retry_count;\n        uint32_t server_communication_sequence_delay_timer;\n        char preferred_transport;\n        bool mute_send;\n\n        bool present_resources[_FAKE_RESOURCES_NUM];\n    } dummy_element = {\n        .bootstrap_on_registration_failure = true\n    };\n#        define element (&dummy_element)\n#    endif // ANJAY_WITH_LWM2M11\n\n    avs_error_t err;\n    (void) (avs_is_err((err = avs_persistence_bool(\n                                ctx,\n                                &element->present_resources\n                                         [SERV_RES_TLS_DTLS_ALERT_CODE])))\n            || avs_is_err((err = avs_persistence_u8(ctx, &element->last_alert)))\n            || avs_is_err((err = avs_persistence_bool(\n                                   ctx,\n                                   &element->present_resources\n                                            [SERV_RES_LAST_BOOTSTRAPPED])))\n            || avs_is_err((err = avs_persistence_i64(\n                                   ctx, &element->last_bootstrapped_timestamp)))\n            || avs_is_err(\n                       (err = avs_persistence_bool(\n                                ctx,\n                                &element->bootstrap_on_registration_failure)))\n            || avs_is_err((\n                       err = avs_persistence_bool(\n                               ctx,\n                               &element->present_resources\n                                        [SERV_RES_SERVER_COMMUNICATION_RETRY_COUNT])))\n            || avs_is_err((err = avs_persistence_u32(\n                                   ctx,\n                                   &element->server_communication_retry_count)))\n            || avs_is_err((\n                       err = avs_persistence_bool(\n                               ctx,\n                               &element->present_resources\n                                        [SERV_RES_SERVER_COMMUNICATION_RETRY_TIMER])))\n            || avs_is_err((err = avs_persistence_u32(\n                                   ctx,\n\n                                   &element->server_communication_retry_timer)))\n            || avs_is_err((\n                       err = avs_persistence_bool(\n                               ctx,\n                               &element->present_resources\n                                        [SERV_RES_SERVER_COMMUNICATION_SEQUENCE_DELAY_TIMER])))\n            || avs_is_err((\n                       err = avs_persistence_u32(\n                               ctx,\n                               &element->server_communication_sequence_delay_timer)))\n            || avs_is_err((\n                       err = avs_persistence_bool(\n                               ctx,\n                               &element->present_resources\n                                        [SERV_RES_SERVER_COMMUNICATION_SEQUENCE_RETRY_COUNT])))\n            || avs_is_err((\n                       err = avs_persistence_u32(\n                               ctx,\n                               &element->server_communication_sequence_retry_count)))\n            || avs_is_err((\n                       err = avs_persistence_u8(\n                               ctx, (uint8_t *) &element->preferred_transport)))\n            || avs_is_err((err = avs_persistence_bool(ctx,\n#    ifdef ANJAY_WITH_SEND\n                                                      &element->mute_send\n#    else  // ANJAY_WITH_SEND\n                                                      &(bool) { false }\n#    endif // ANJAY_WITH_SEND\n                                                      ))));\n    if (avs_is_ok(err)) {\n        element->present_resources[SERV_RES_PREFERRED_TRANSPORT] =\n                !!element->preferred_transport;\n    }\n    return err;\n#    undef element\n}\n\nstatic avs_error_t handle_v2_sized_fields(avs_persistence_context_t *ctx,\n                                          server_instance_t *element) {\n    avs_error_t err;\n    (void) (avs_is_err((err = avs_persistence_u16(ctx, &element->iid)))\n            || avs_is_err((err = avs_persistence_bool(\n                                   ctx,\n                                   &element->present_resources[SERV_RES_SSID])))\n            || avs_is_err(\n                       (err = avs_persistence_bool(\n                                ctx,\n                                &element->present_resources[SERV_RES_BINDING])))\n            || avs_is_err((\n                       err = avs_persistence_bool(\n                               ctx,\n                               &element->present_resources[SERV_RES_LIFETIME])))\n            || avs_is_err((err = avs_persistence_bool(\n                                   ctx,\n                                   &element->present_resources\n                                            [SERV_RES_DEFAULT_MIN_PERIOD])))\n            || avs_is_err((err = avs_persistence_bool(\n                                   ctx,\n                                   &element->present_resources\n                                            [SERV_RES_DEFAULT_MAX_PERIOD])))\n            || avs_is_err((err = avs_persistence_bool(\n                                   ctx,\n#    ifndef ANJAY_WITHOUT_DEREGISTER\n                                   &element->present_resources\n                                            [SERV_RES_DISABLE_TIMEOUT]\n#    else  // ANJAY_WITHOUT_DEREGISTER\n                                   &(bool) { false }\n#    endif // ANJAY_WITHOUT_DEREGISTER\n                                   )))\n            || avs_is_err((\n                       err = avs_persistence_bool(\n                               ctx,\n                               &element->present_resources\n                                        [SERV_RES_NOTIFICATION_STORING_WHEN_DISABLED_OR_OFFLINE])))\n            || avs_is_err((err = avs_persistence_u16(ctx, &element->ssid)))\n            || avs_is_err((err = avs_persistence_u32(\n                                   ctx, (uint32_t *) &element->lifetime)))\n            || avs_is_err((\n                       err = avs_persistence_u32(\n                               ctx, (uint32_t *) &element->default_min_period)))\n            || avs_is_err((\n                       err = avs_persistence_u32(\n                               ctx, (uint32_t *) &element->default_max_period)))\n            || avs_is_err((err = avs_persistence_u32(\n                                   ctx,\n#    ifndef ANJAY_WITHOUT_DEREGISTER\n                                   (uint32_t *) &element->disable_timeout\n#    else  // ANJAY_WITHOUT_DEREGISTER\n                                   (uint32_t *) &(int32_t) { -1 }\n#    endif // ANJAY_WITHOUT_DEREGISTER\n                                   )))\n            || avs_is_err((err = avs_persistence_bool(\n                                   ctx, &element->notification_storing)))\n            || avs_is_err(\n                       (err = handle_v2_lwm2m11_sized_fields(ctx, element))));\n    return err;\n}\n\nstatic avs_error_t handle_v3_sized_fields(avs_persistence_context_t *ctx,\n                                          server_instance_t *element) {\n    avs_error_t err;\n    (void) (avs_is_err((err = handle_v2_sized_fields(ctx, element)))\n            || avs_is_err((err = avs_persistence_bool(\n                                   ctx,\n#    if defined(ANJAY_WITH_LWM2M11) && defined(ANJAY_WITH_SMS)\n                                   &element->present_resources[SERV_RES_TRIGGER]\n#    else  // defined(ANJAY_WITH_LWM2M11) && defined(ANJAY_WITH_SMS)\n                                   &(bool) { false }\n#    endif // defined(ANJAY_WITH_LWM2M11) && defined(ANJAY_WITH_SMS)\n                                   )))\n            || avs_is_err((err = avs_persistence_bool(ctx,\n#    if defined(ANJAY_WITH_LWM2M11) && defined(ANJAY_WITH_SMS)\n                                                      &element->trigger\n#    else  // defined(ANJAY_WITH_LWM2M11) && defined(ANJAY_WITH_SMS)\n                                                      &(bool) { false }\n#    endif // defined(ANJAY_WITH_LWM2M11) && defined(ANJAY_WITH_SMS)\n                                                      ))));\n    return err;\n}\n\nstatic avs_error_t handle_v1_v2_v3_binding_mode(avs_persistence_context_t *ctx,\n                                                server_instance_t *element) {\n    avs_error_t err = avs_persistence_bytes(ctx, element->binding.data,\n                                            sizeof(element->binding.data));\n    if (avs_is_err(err)) {\n        return err;\n    }\n    if (!memchr(element->binding.data, '\\0', sizeof(element->binding.data))\n            || !anjay_binding_mode_valid(element->binding.data)) {\n        return avs_errno(AVS_EBADMSG);\n    }\n    return AVS_OK;\n}\n\nstatic avs_error_t restore_v0_binding_mode(avs_persistence_context_t *ctx,\n                                           server_instance_t *element) {\n    assert(avs_persistence_direction(ctx) == AVS_PERSISTENCE_RESTORE);\n    uint32_t binding;\n    avs_error_t err = avs_persistence_u32(ctx, &binding);\n    if (avs_is_err(err)) {\n        return err;\n    }\n\n    enum {\n        V0_BINDING_NONE,\n        V0_BINDING_U,\n        V0_BINDING_UQ,\n        V0_BINDING_S,\n        V0_BINDING_SQ,\n        V0_BINDING_US,\n        V0_BINDING_UQS\n    };\n\n    const char *binding_str = \"\";\n    switch (binding) {\n    case V0_BINDING_NONE:\n        binding_str = \"\";\n        break;\n    case V0_BINDING_U:\n        binding_str = \"U\";\n        break;\n    case V0_BINDING_UQ:\n        binding_str = \"UQ\";\n        break;\n    case V0_BINDING_S:\n        binding_str = \"S\";\n        break;\n    case V0_BINDING_SQ:\n        binding_str = \"SQ\";\n        break;\n    case V0_BINDING_US:\n        binding_str = \"US\";\n        break;\n    case V0_BINDING_UQS:\n        binding_str = \"UQS\";\n        break;\n    default:\n        persistence_log(WARNING, _(\"Invalid binding mode: \") \"%\" PRIu32,\n                        binding);\n        err = avs_errno(AVS_EBADMSG);\n        break;\n    }\n    if (avs_is_ok(err)\n            && avs_simple_snprintf(element->binding.data,\n                                   sizeof(element->binding.data), \"%s\",\n                                   binding_str)\n                           < 0) {\n        persistence_log(WARNING, _(\"Could not restore binding: \") \"%s\",\n                        binding_str);\n        err = avs_errno(AVS_EBADMSG);\n    }\n    return err;\n}\n\nstatic avs_error_t server_instance_persistence_handler(\n        avs_persistence_context_t *ctx, void *element_, void *version_) {\n    server_instance_t *element = (server_instance_t *) element_;\n    server_persistence_version_t *version =\n            (server_persistence_version_t *) version_;\n    AVS_ASSERT(avs_persistence_direction(ctx) != AVS_PERSISTENCE_STORE\n                       || *version == PERSISTENCE_VERSION_3,\n               \"persistence storing is impossible in legacy mode\");\n\n    // Ensure every field initialized regardless of persistence version\n    if (avs_persistence_direction(ctx) == AVS_PERSISTENCE_RESTORE) {\n        _standalone_serv_reset_instance(element);\n    }\n\n    avs_error_t err;\n    switch (*version) {\n    case PERSISTENCE_VERSION_0:\n        (void) (avs_is_err((err = handle_v0_v1_sized_fields(ctx, element)))\n                || avs_is_err((err = restore_v0_binding_mode(ctx, element))));\n        break;\n    case PERSISTENCE_VERSION_1:\n        (void) (avs_is_err((err = handle_v0_v1_sized_fields(ctx, element)))\n                || avs_is_err(\n                           (err = handle_v1_v2_v3_binding_mode(ctx, element))));\n        break;\n    case PERSISTENCE_VERSION_2:\n        (void) (avs_is_err((err = handle_v2_sized_fields(ctx, element)))\n                || avs_is_err(\n                           (err = handle_v1_v2_v3_binding_mode(ctx, element))));\n        break;\n    case PERSISTENCE_VERSION_3:\n        (void) (avs_is_err((err = handle_v3_sized_fields(ctx, element)))\n                || avs_is_err(\n                           (err = handle_v1_v2_v3_binding_mode(ctx, element))));\n        break;\n    default:\n        AVS_UNREACHABLE(\"invalid enum value\");\n    }\n    return err;\n}\n\navs_error_t\nstandalone_server_object_persist(const anjay_dm_object_def_t *const *obj_ptr,\n                                 avs_stream_t *out_stream) {\n    avs_error_t err = avs_errno(AVS_EINVAL);\n    server_repr_t *repr = _standalone_serv_get(obj_ptr);\n    if (!repr) {\n        err = avs_errno(AVS_EBADF);\n    } else {\n        avs_persistence_context_t persist_ctx =\n                avs_persistence_store_context_create(out_stream);\n        if (avs_is_ok((err = avs_persistence_bytes(&persist_ctx,\n                                                   (void *) (intptr_t) MAGIC_V3,\n                                                   sizeof(MAGIC_V3))))) {\n            server_persistence_version_t persistence_version =\n                    PERSISTENCE_VERSION_3;\n            err = avs_persistence_list(\n                    &persist_ctx,\n                    (AVS_LIST(void) *) (repr->in_transaction\n                                                ? &repr->saved_instances\n                                                : &repr->instances),\n                    sizeof(server_instance_t),\n                    server_instance_persistence_handler, &persistence_version,\n                    NULL);\n            if (avs_is_ok(err)) {\n                _standalone_serv_clear_modified(repr);\n                persistence_log(INFO, _(\"Server Object state persisted\"));\n            }\n        }\n    }\n    return err;\n}\n\nstatic int check_magic_header(magic_t magic_header,\n                              server_persistence_version_t *out_version) {\n    if (!memcmp(magic_header, MAGIC_V0, sizeof(magic_t))) {\n        *out_version = PERSISTENCE_VERSION_0;\n        return 0;\n    }\n    if (!memcmp(magic_header, MAGIC_V1, sizeof(magic_t))) {\n        *out_version = PERSISTENCE_VERSION_1;\n        return 0;\n    }\n    if (!memcmp(magic_header, MAGIC_V2, sizeof(magic_t))) {\n        *out_version = PERSISTENCE_VERSION_2;\n        return 0;\n    }\n    if (!memcmp(magic_header, MAGIC_V3, sizeof(magic_t))) {\n        *out_version = PERSISTENCE_VERSION_3;\n        return 0;\n    }\n    return -1;\n}\n\navs_error_t\nstandalone_server_object_restore(const anjay_dm_object_def_t *const *obj_ptr,\n                                 avs_stream_t *in_stream) {\n    avs_error_t err = avs_errno(AVS_EINVAL);\n    server_repr_t *repr = _standalone_serv_get(obj_ptr);\n    if (!repr || repr->in_transaction) {\n        err = avs_errno(AVS_EBADF);\n    } else {\n        server_repr_t backup = *repr;\n        avs_persistence_context_t restore_ctx =\n                avs_persistence_restore_context_create(in_stream);\n\n        magic_t magic_header;\n        if (avs_is_err((err = avs_persistence_bytes(&restore_ctx, magic_header,\n                                                    sizeof(magic_header))))) {\n            persistence_log(WARNING, _(\"Could not read Server Object header\"));\n        } else {\n            server_persistence_version_t persistence_version;\n            if (check_magic_header(magic_header, &persistence_version)) {\n                persistence_log(WARNING, _(\"Header magic constant mismatch\"));\n                err = avs_errno(AVS_EBADMSG);\n            } else {\n                repr->instances = NULL;\n                err = avs_persistence_list(&restore_ctx,\n                                           (AVS_LIST(void) *) &repr->instances,\n                                           sizeof(server_instance_t),\n                                           server_instance_persistence_handler,\n                                           &persistence_version, NULL);\n                if (avs_is_ok(err) && _standalone_serv_object_validate(repr)) {\n                    err = avs_errno(AVS_EBADMSG);\n                }\n                if (avs_is_err(err)) {\n                    _standalone_serv_destroy_instances(&repr->instances);\n                    repr->instances = backup.instances;\n                } else {\n                    _standalone_serv_destroy_instances(&backup.instances);\n                    _standalone_serv_clear_modified(repr);\n                    persistence_log(INFO, _(\"Server Object state restored\"));\n                }\n            }\n        }\n    }\n    return err;\n}\n\n#else // AVS_COMMONS_WITH_AVS_PERSISTENCE\n\navs_error_t standalone_server_object_persist(anjay_t *anjay,\n                                             avs_stream_t *out_stream) {\n    (void) anjay;\n    (void) out_stream;\n    persistence_log(ERROR, _(\"Persistence not compiled in\"));\n    return avs_errno(AVS_ENOTSUP);\n}\n\navs_error_t standalone_server_object_restore(anjay_t *anjay,\n                                             avs_stream_t *in_stream) {\n    (void) anjay;\n    (void) in_stream;\n    persistence_log(ERROR, _(\"Persistence not compiled in\"));\n    return avs_errno(AVS_ENOTSUP);\n}\n\n#endif // AVS_COMMONS_WITH_AVS_PERSISTENCE\n"
  },
  {
    "path": "standalone/server/standalone_server_transaction.c",
    "content": "#include <assert.h>\n#include <inttypes.h>\n#include <string.h>\n\n#include \"standalone_server_transaction.h\"\n#include \"standalone_server_utils.h\"\n\nstatic int ssid_cmp(const void *a, const void *b, size_t size) {\n    assert(size == sizeof(anjay_ssid_t));\n    (void) size;\n    return *((const anjay_ssid_t *) a) - *((const anjay_ssid_t *) b);\n}\n\n#define LOG_VALIDATION_FAILED(ServInstance, ...)             \\\n    server_log(WARNING, \"/%u/%u: \" AVS_VARARG0(__VA_ARGS__), \\\n               ANJAY_DM_OID_SERVER,                          \\\n               (unsigned) (ServInstance)->iid AVS_VARARG_REST(__VA_ARGS__))\n\nstatic int validate_instance(server_instance_t *it) {\n    if (!it->present_resources[SERV_RES_SSID]) {\n        LOG_VALIDATION_FAILED(\n                it, _(\"missing mandatory 'Short Server ID' resource value\"));\n        return -1;\n    }\n    if (it->ssid < 1 || it->ssid >= UINT16_MAX) {\n        LOG_VALIDATION_FAILED(\n                it, _(\"invalid 'Short Server ID' resource value: \") \"%\" PRIu16,\n                it->ssid);\n        return -1;\n    }\n    if (!it->present_resources[SERV_RES_BINDING]) {\n        LOG_VALIDATION_FAILED(it,\n                              _(\"missing mandatory 'Binding' resource value\"));\n        return -1;\n    }\n    if (!it->present_resources[SERV_RES_LIFETIME]) {\n        LOG_VALIDATION_FAILED(it,\n                              _(\"missing mandatory 'Lifetime' resource value\"));\n        return -1;\n    }\n    if (!it->present_resources\n                 [SERV_RES_NOTIFICATION_STORING_WHEN_DISABLED_OR_OFFLINE]) {\n        LOG_VALIDATION_FAILED(it,\n                              _(\"missing mandatory 'Notification Storing \"\n                                \"when disabled or offline' resource value\"));\n        return -1;\n    }\n    if (it->lifetime < 0) {\n        LOG_VALIDATION_FAILED(it, _(\"Lifetime value is negative: \") \"%\" PRId32,\n                              it->lifetime);\n        return -1;\n    }\n    if (it->present_resources[SERV_RES_DEFAULT_MAX_PERIOD]\n            && it->default_max_period <= 0) {\n        LOG_VALIDATION_FAILED(it, _(\"Default Max Period is non-positive\"));\n        return -1;\n    }\n    if (it->present_resources[SERV_RES_DEFAULT_MIN_PERIOD]\n            && it->default_min_period < 0) {\n        LOG_VALIDATION_FAILED(it, _(\"Default Min Period is negative\"));\n        return -1;\n    }\n#ifndef ANJAY_WITHOUT_DEREGISTER\n    if (it->present_resources[SERV_RES_DISABLE_TIMEOUT]\n            && it->disable_timeout < 0) {\n        LOG_VALIDATION_FAILED(it, _(\"Disable Timeout is negative\"));\n        return -1;\n    }\n#endif // ANJAY_WITHOUT_DEREGISTER\n    if (!anjay_binding_mode_valid(it->binding.data)) {\n        LOG_VALIDATION_FAILED(it, _(\"Incorrect binding mode \") \"%s\",\n                              it->binding.data);\n        return -1;\n    }\n#ifdef ANJAY_WITH_LWM2M11\n    if (it->present_resources[SERV_RES_LAST_BOOTSTRAPPED]\n            && it->last_bootstrapped_timestamp < 0) {\n        LOG_VALIDATION_FAILED(it, _(\"Last Bootstrapped is negative\"));\n        return -1;\n    }\n    if (it->present_resources[SERV_RES_PREFERRED_TRANSPORT]\n            && !strchr(\"USTN\", it->preferred_transport)) {\n        LOG_VALIDATION_FAILED(it, _(\"Incorrect Preferred Transport: \") \"%c\",\n                              it->preferred_transport);\n        return -1;\n    }\n    if (it->present_resources[SERV_RES_SERVER_COMMUNICATION_RETRY_COUNT]\n            && it->server_communication_retry_count == 0) {\n        LOG_VALIDATION_FAILED(it,\n                              _(\"Communication Retry Count cannot be zero\"));\n        return -1;\n    }\n    if (it->present_resources\n                [SERV_RES_SERVER_COMMUNICATION_SEQUENCE_RETRY_COUNT]\n            && it->server_communication_sequence_retry_count == 0) {\n        LOG_VALIDATION_FAILED(\n                it, _(\"Communication Sequence Retry Count cannot be zero\"));\n        return -1;\n    }\n#endif // ANJAY_WITH_LWM2M11\n\n    return 0;\n}\n\nint _standalone_serv_object_validate(server_repr_t *repr) {\n    if (!repr->instances) {\n        return 0;\n    }\n    int result = 0;\n\n    AVS_LIST(anjay_ssid_t) seen_ssids = NULL;\n    AVS_LIST(server_instance_t) it;\n    AVS_LIST_FOREACH(it, repr->instances) {\n        if (validate_instance(it)) {\n            result = ANJAY_ERR_BAD_REQUEST;\n            break;\n        }\n\n        if (!AVS_LIST_INSERT_NEW(anjay_ssid_t, &seen_ssids)) {\n            result = ANJAY_ERR_INTERNAL;\n            break;\n        }\n        *seen_ssids = it->ssid;\n    }\n\n    /* Test for SSID duplication */\n    if (!result) {\n        AVS_LIST_SORT(&seen_ssids, ssid_cmp);\n        AVS_LIST(anjay_ssid_t) prev = seen_ssids;\n        AVS_LIST(anjay_ssid_t) next = AVS_LIST_NEXT(seen_ssids);\n        while (next) {\n            if (*prev == *next) {\n                /* Duplicate found */\n                result = ANJAY_ERR_BAD_REQUEST;\n                break;\n            }\n            prev = next;\n            next = AVS_LIST_NEXT(next);\n        }\n    }\n    AVS_LIST_CLEAR(&seen_ssids);\n    return result;\n}\n\nint _standalone_serv_transaction_begin_impl(server_repr_t *repr) {\n    assert(!repr->saved_instances);\n    assert(!repr->in_transaction);\n    repr->saved_instances = _standalone_serv_clone_instances(repr);\n    if (!repr->saved_instances && repr->instances) {\n        return ANJAY_ERR_INTERNAL;\n    }\n    repr->saved_modified_since_persist = repr->modified_since_persist;\n    repr->in_transaction = true;\n    return 0;\n}\n\nint _standalone_serv_transaction_commit_impl(server_repr_t *repr) {\n    assert(repr->in_transaction);\n    _standalone_serv_destroy_instances(&repr->saved_instances);\n    repr->in_transaction = false;\n    return 0;\n}\n\nint _standalone_serv_transaction_validate_impl(server_repr_t *repr) {\n    assert(repr->in_transaction);\n    return _standalone_serv_object_validate(repr);\n}\n\nint _standalone_serv_transaction_rollback_impl(server_repr_t *repr) {\n    assert(repr->in_transaction);\n    _standalone_serv_destroy_instances(&repr->instances);\n    repr->modified_since_persist = repr->saved_modified_since_persist;\n    repr->instances = repr->saved_instances;\n    repr->saved_instances = NULL;\n    repr->in_transaction = false;\n    return 0;\n}\n"
  },
  {
    "path": "standalone/server/standalone_server_transaction.h",
    "content": "#ifndef ANJAY_STANDALONE_SERVER_TRANSACTION_H\n#define ANJAY_STANDALONE_SERVER_TRANSACTION_H\n\n#include \"standalone_mod_server.h\"\n\nint _standalone_serv_object_validate(server_repr_t *repr);\n\nint _standalone_serv_transaction_begin_impl(server_repr_t *repr);\nint _standalone_serv_transaction_commit_impl(server_repr_t *repr);\nint _standalone_serv_transaction_validate_impl(server_repr_t *repr);\nint _standalone_serv_transaction_rollback_impl(server_repr_t *repr);\n\n#endif /* ANJAY_STANDALONE_SERVER_TRANSACTION_H */\n"
  },
  {
    "path": "standalone/server/standalone_server_utils.c",
    "content": "#include \"standalone_server_utils.h\"\n\n#include <string.h>\n\nint _standalone_serv_fetch_ssid(anjay_input_ctx_t *ctx,\n                                anjay_ssid_t *out_ssid) {\n    int32_t ssid;\n    int retval = anjay_get_i32(ctx, &ssid);\n    if (retval) {\n        return retval;\n    }\n    *out_ssid = (anjay_ssid_t) ssid;\n    return 0;\n}\n\nint _standalone_serv_fetch_validated_i32(anjay_input_ctx_t *ctx,\n                                         int32_t min_value,\n                                         int32_t max_value,\n                                         int32_t *out_value) {\n    int retval = anjay_get_i32(ctx, out_value);\n    if (!retval && (*out_value < min_value || *out_value > max_value)) {\n        return ANJAY_ERR_BAD_REQUEST;\n    }\n    return retval;\n}\n\nint _standalone_serv_fetch_binding(anjay_input_ctx_t *ctx,\n                                   standalone_binding_mode_t *out_binding) {\n    int retval;\n    if ((retval = anjay_get_string(\n                 ctx, out_binding->data, sizeof(out_binding->data)))) {\n        return retval;\n    }\n    return anjay_binding_mode_valid(out_binding->data) ? 0\n                                                       : ANJAY_ERR_BAD_REQUEST;\n}\n\nAVS_LIST(server_instance_t)\n_standalone_serv_clone_instances(const server_repr_t *repr) {\n    return AVS_LIST_SIMPLE_CLONE(repr->instances);\n}\n\nvoid _standalone_serv_destroy_instances(\n        AVS_LIST(server_instance_t) *instances) {\n    AVS_LIST_CLEAR(instances);\n}\n\nvoid _standalone_serv_reset_instance(server_instance_t *serv) {\n    const anjay_iid_t iid = serv->iid;\n    memset(serv, 0, sizeof(*serv));\n    /* This is not a resource, therefore must be restored */\n    serv->iid = iid;\n    serv->present_resources[SERV_RES_REGISTRATION_UPDATE_TRIGGER] = true;\n#ifndef ANJAY_WITHOUT_DEREGISTER\n    serv->present_resources[SERV_RES_DISABLE] = true;\n#endif // ANJAY_WITHOUT_DEREGISTER\n#ifdef ANJAY_WITH_LWM2M11\n    serv->bootstrap_on_registration_failure = true;\n    serv->present_resources[SERV_RES_BOOTSTRAP_ON_REGISTRATION_FAILURE] = true;\n#    ifdef ANJAY_WITH_BOOTSTRAP\n    serv->present_resources[SERV_RES_BOOTSTRAP_REQUEST_TRIGGER] = true;\n#    endif // ANJAY_WITH_BOOTSTRAP\n#    ifdef ANJAY_WITH_SEND\n    serv->present_resources[SERV_RES_MUTE_SEND] = true;\n#    endif // ANJAY_WITH_SEND\n#endif     // ANJAY_WITH_LWM2M11\n}\n"
  },
  {
    "path": "standalone/server/standalone_server_utils.h",
    "content": "#ifndef ANJAY_STANDALONE_SERVER_UTILS_H\n#define ANJAY_STANDALONE_SERVER_UTILS_H\n\n#include <assert.h>\n\n#include \"standalone_mod_server.h\"\n\nserver_repr_t *\n_standalone_serv_get(const anjay_dm_object_def_t *const *obj_ptr);\n\nint _standalone_serv_fetch_ssid(anjay_input_ctx_t *ctx, anjay_ssid_t *out_ssid);\nint _standalone_serv_fetch_validated_i32(anjay_input_ctx_t *ctx,\n                                         int32_t min_value,\n                                         int32_t max_value,\n                                         int32_t *out_value);\nint _standalone_serv_fetch_binding(anjay_input_ctx_t *ctx,\n                                   standalone_binding_mode_t *out_binding);\n\nAVS_LIST(server_instance_t)\n_standalone_serv_clone_instances(const server_repr_t *repr);\nvoid _standalone_serv_destroy_instances(AVS_LIST(server_instance_t) *instances);\nvoid _standalone_serv_reset_instance(server_instance_t *serv);\n\n#endif /* ANJAY_STANDALONE_SERVER_UTILS_H */\n"
  },
  {
    "path": "tests/codegen/CMakeLists.txt",
    "content": "# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nset(CODEGEN \"${CMAKE_CURRENT_SOURCE_DIR}/../../tools/anjay_codegen.py\")\nset(CODEGEN_TEST_INPUT_ROOT \"${CMAKE_CURRENT_SOURCE_DIR}/input\")\nset(CODEGEN_TEST_PREFIX \"test_codegen_\")\n\nfile(GLOB_RECURSE CODEGEN_INPUTS RELATIVE ${CODEGEN_TEST_INPUT_ROOT} \"${CODEGEN_TEST_INPUT_ROOT}/*.xml\")\n\nset(CODEGEN_CHECK_TARGETS)\n\nenable_language(CXX)\nstring(REPLACE \"-Wc++-compat\" \"\" CMAKE_CXX_FLAGS \"${CMAKE_C_FLAGS}\")\nstring(REPLACE \"-Wjump-misses-init\" \"\" CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS}\")\nstring(REGEX REPLACE \"-std=[a-z0-9]*\" \"\" CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS}\")\nset(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} -std=c++11\")\n\nforeach(CODEGEN_INPUT ${CODEGEN_INPUTS})\n    string(REGEX REPLACE \"^${CMAKE_CURRENT_SOURCE_DIR}\" \"\" CODEGEN_TEST \"${CODEGEN_INPUT}\")\n    string(REGEX REPLACE \"\\\\.xml\" \"\" CODEGEN_TEST \"${CODEGEN_TEST}\")\n    string(REGEX REPLACE \"/\" \".\" CODEGEN_TEST \"${CODEGEN_TEST}\")\n\n    set(INPUT \"${CODEGEN_TEST_INPUT_ROOT}/${CODEGEN_INPUT}\")\n    set(OUTPUT \"${CMAKE_CURRENT_BINARY_DIR}/${CODEGEN_TEST}.c\")\n    set(OUTPUT_CXX \"${CMAKE_CURRENT_BINARY_DIR}/${CODEGEN_TEST}.cpp\")\n    add_custom_command(OUTPUT \"${OUTPUT}\"\n                       COMMAND \"${CODEGEN}\" -i \"${INPUT}\" -o \"${OUTPUT}\"\n                       DEPENDS \"${CODEGEN}\" \"${INPUT}\")\n    add_custom_command(OUTPUT \"${OUTPUT_CXX}\"\n                       COMMAND \"${CODEGEN}\" -x -i \"${INPUT}\" -o \"${OUTPUT_CXX}\"\n                       DEPENDS \"${CODEGEN}\" \"${INPUT}\")\n    list(APPEND CODEGEN_SOURCES \"${OUTPUT}\")\n    list(APPEND CODEGEN_CXX_SOURCES \"${OUTPUT_CXX}\")\nendforeach()\n\nadd_library(codegen_check OBJECT EXCLUDE_FROM_ALL ${CODEGEN_SOURCES})\nset_target_properties(codegen_check PROPERTIES\n                      COMPILE_FLAGS \"-Wno-missing-declarations -Wno-unused-variable -Wno-unused-parameter\")\ntarget_include_directories(codegen_check PRIVATE\n                           $<TARGET_PROPERTY:anjay,INTERFACE_INCLUDE_DIRECTORIES>)\n\nadd_library(codegen_check_cxx OBJECT EXCLUDE_FROM_ALL ${CODEGEN_CXX_SOURCES})\nset_target_properties(codegen_check_cxx PROPERTIES\n                      COMPILE_FLAGS \"-Wno-missing-declarations -Wno-unused-variable -Wno-unused-parameter\")\ntarget_include_directories(codegen_check_cxx PRIVATE\n                           $<TARGET_PROPERTY:anjay,INTERFACE_INCLUDE_DIRECTORIES>)\n\nadd_custom_target(codegen_check_with_object_registry COMMAND\n                  \"${CMAKE_CURRENT_SOURCE_DIR}/check_with_object_registry.sh\" 0 1 2 3 4 5 17 18 22)\nadd_custom_target(codegen_check_cxx_with_object_registry COMMAND\n                  \"${CMAKE_CURRENT_SOURCE_DIR}/check_with_object_registry.sh\" \"--c++\" 0 1 2 3 4 5 17 18 22)\nadd_dependencies(codegen_check codegen_check_with_object_registry)\nadd_dependencies(codegen_check_cxx codegen_check_cxx_with_object_registry)\n\nadd_dependencies(check codegen_check codegen_check_cxx)\n"
  },
  {
    "path": "tests/codegen/check_with_object_registry.sh",
    "content": "#!/usr/bin/env bash\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nset -e\n. \"$(dirname \"$0\")/../../tools/utils.sh\"\nSCRIPT_DIR=\"$(dirname \"$(canonicalize \"$0\")\")\"\nOBJECT_REGISTRY_SCRIPT=\"$SCRIPT_DIR/../../tools/lwm2m_object_registry.py\"\nCODEGEN_SCRIPT=\"$SCRIPT_DIR/../../tools/anjay_codegen.py\"\nTEMP_DIR=\"$(mktemp -d)\"\natexit \"rm -r $TEMP_DIR\"\n\nif [ \"$1\" == \"--c++\" ]; then\n    CODEGEN_ARGS=\"$1\"\n    shift\nfi\nOIDS=\"$@\"\n\nlog \"running object registry test for objects: $OIDS\"\nlog \"code generator additional args: \\\"$CODEGEN_ARGS\\\"\"\n\nfor OID in $OIDS; do\n    OBJECT_DEFINITION_FILE=\"$TEMP_DIR/$OID-object-def.xml\"\n    OBJECT_REGISTRY_ERROR_FILE=\"$TEMP_DIR/$OID-object-err.log\"\n    CODEGEN_ERROR_FILE=\"$TEMP_DIR/$OID-codegen-err.log\"\n\n    check_object_registry_script_error() {\n        OBJECT_REGISTRY_ERROR=`cat \"$OBJECT_REGISTRY_ERROR_FILE\"`\n        # Ignore ValueError, it is not critical\n        echo \"$OBJECT_REGISTRY_ERROR\" | tail -1 | grep --quiet \"ValueError\" ||\n            die \"Unexpected error while getting object $OID definition:\\n$OBJECT_REGISTRY_ERROR\"\n    }\n    \"$OBJECT_REGISTRY_SCRIPT\" -g \"$OID\" 1>\"$OBJECT_DEFINITION_FILE\" \\\n        2>\"$OBJECT_REGISTRY_ERROR_FILE\" || check_object_registry_script_error && continue\n\n    check_codegen_script_error() {\n        CODEGEN_ERROR=`cat \"$CODEGEN_ERROR_FILE\"`\n        echo \"$CODEGEN_ERROR\" | tail -1 |\n            # Ignore \"multiple-instance executable resources\" error\n            grep --quiet \"multiple-instance executable resources are not supported\" ||\n            die \"Unexpected error while generating code for object $OID:\\n$CODEGEN_ERROR\"\n    }\n    \"$CODEGEN_SCRIPT\" $CODEGEN_ARGS -i \"$OBJECT_DEFINITION_FILE\" 1>/dev/null \\\n        2>\"$CODEGEN_ERROR_FILE\" || check_codegen_script_error\ndone\n"
  },
  {
    "path": "tests/codegen/input/execute.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\nCopyright 2017-2026 AVSystem <avsystem@avsystem.com>\nAVSystem Anjay LwM2M SDK\nAll rights reserved.\n\n Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\nSee the attached LICENSE file for details.\n-->\n<LWM2M xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:noNamespaceSchemaLocation=\"http://openmobilealliance.org/tech/profiles/LWM2M.xsd\">\n\t<Object ObjectType=\"MODefinition\">\n\t\t<Name>Test object</Name>\n\t\t<Description1>Test description</Description1>\n\t\t<ObjectID>12345</ObjectID>\n\t\t<ObjectURN>urn:avs:test:12345</ObjectURN>\n\t\t<MultipleInstances>Single</MultipleInstances>\n\t\t<Mandatory>Optional</Mandatory>\n\t\t<Resources>\n            <Item ID=\"0\">\n                <Name>Execute</Name>\n                <Operations>E</Operations>\n                <MultipleInstances>Single</MultipleInstances>\n                <Mandatory>Mandatory</Mandatory>\n                <Type></Type>\n                <RangeEnumeration></RangeEnumeration>\n                <Units></Units>\n                <Description></Description>\n            </Item>\n        </Resources>\n\t</Object>\n</LWM2M>\n\n"
  },
  {
    "path": "tests/codegen/input/multiple-object.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\nCopyright 2017-2026 AVSystem <avsystem@avsystem.com>\nAVSystem Anjay LwM2M SDK\nAll rights reserved.\n\n Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\nSee the attached LICENSE file for details.\n-->\n<LWM2M xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:noNamespaceSchemaLocation=\"http://openmobilealliance.org/tech/profiles/LWM2M.xsd\">\n\t<Object ObjectType=\"MODefinition\">\n\t\t<Name>Test object</Name>\n\t\t<Description1>Test description</Description1>\n\t\t<ObjectID>12345</ObjectID>\n\t\t<ObjectURN>urn:avs:test:12345</ObjectURN>\n\t\t<MultipleInstances>Multiple</MultipleInstances>\n\t\t<Mandatory>Optional</Mandatory>\n        <Resources>\n            <Item ID=\"0\">\n                <Name>Integer</Name>\n                <Operations>R</Operations>\n                <MultipleInstances>Single</MultipleInstances>\n                <Mandatory>Mandatory</Mandatory>\n                <Type>Integer</Type>\n                <RangeEnumeration></RangeEnumeration>\n                <Units></Units>\n                <Description></Description>\n            </Item>\n        </Resources>\n\t</Object>\n</LWM2M>\n\n"
  },
  {
    "path": "tests/codegen/input/read.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\nCopyright 2017-2026 AVSystem <avsystem@avsystem.com>\nAVSystem Anjay LwM2M SDK\nAll rights reserved.\n\n Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\nSee the attached LICENSE file for details.\n-->\n<LWM2M xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:noNamespaceSchemaLocation=\"http://openmobilealliance.org/tech/profiles/LWM2M.xsd\">\n\t<Object ObjectType=\"MODefinition\">\n\t\t<Name>Test object</Name>\n\t\t<Description1>Test description</Description1>\n\t\t<ObjectID>12345</ObjectID>\n\t\t<ObjectURN>urn:avs:test:12345</ObjectURN>\n\t\t<MultipleInstances>Single</MultipleInstances>\n\t\t<Mandatory>Optional</Mandatory>\n\t\t<Resources>\n            <Item ID=\"0\">\n                <Name>Boolean</Name>\n                <Operations>R</Operations>\n                <MultipleInstances>Single</MultipleInstances>\n                <Mandatory>Mandatory</Mandatory>\n                <Type>Boolean</Type>\n                <RangeEnumeration></RangeEnumeration>\n                <Units></Units>\n                <Description></Description>\n            </Item>\n            <Item ID=\"1\">\n                <Name>Integer</Name>\n                <Operations>R</Operations>\n                <MultipleInstances>Single</MultipleInstances>\n                <Mandatory>Mandatory</Mandatory>\n                <Type>Integer</Type>\n                <RangeEnumeration></RangeEnumeration>\n                <Units></Units>\n                <Description></Description>\n            </Item>\n            <Item ID=\"2\">\n                <Name>Float</Name>\n                <Operations>R</Operations>\n                <MultipleInstances>Single</MultipleInstances>\n                <Mandatory>Mandatory</Mandatory>\n                <Type>Float</Type>\n                <RangeEnumeration></RangeEnumeration>\n                <Units></Units>\n                <Description></Description>\n            </Item>\n            <Item ID=\"3\">\n                <Name>String</Name>\n                <Operations>R</Operations>\n                <MultipleInstances>Single</MultipleInstances>\n                <Mandatory>Mandatory</Mandatory>\n                <Type>String</Type>\n                <RangeEnumeration></RangeEnumeration>\n                <Units></Units>\n                <Description></Description>\n            </Item>\n            <Item ID=\"4\">\n                <Name>Opaque</Name>\n                <Operations>R</Operations>\n                <MultipleInstances>Single</MultipleInstances>\n                <Mandatory>Mandatory</Mandatory>\n                <Type>Opaque</Type>\n                <RangeEnumeration></RangeEnumeration>\n                <Units></Units>\n                <Description></Description>\n            </Item>\n            <Item ID=\"5\">\n                <Name>Time</Name>\n                <Operations>R</Operations>\n                <MultipleInstances>Single</MultipleInstances>\n                <Mandatory>Mandatory</Mandatory>\n                <Type>Time</Type>\n                <RangeEnumeration></RangeEnumeration>\n                <Units></Units>\n                <Description></Description>\n            </Item>\n            <Item ID=\"6\">\n                <Name>ObjLnk</Name>\n                <Operations>R</Operations>\n                <MultipleInstances>Single</MultipleInstances>\n                <Mandatory>Mandatory</Mandatory>\n                <Type>ObjLnk</Type>\n                <RangeEnumeration></RangeEnumeration>\n                <Units></Units>\n                <Description></Description>\n            </Item>\n            <Item ID=\"7\">\n                <Name>Boolean Array</Name>\n                <Operations>R</Operations>\n                <MultipleInstances>Multiple</MultipleInstances>\n                <Mandatory>Mandatory</Mandatory>\n                <Type>Boolean</Type>\n                <RangeEnumeration></RangeEnumeration>\n                <Units></Units>\n                <Description></Description>\n            </Item>\n            <Item ID=\"8\">\n                <Name>Integer Array</Name>\n                <Operations>R</Operations>\n                <MultipleInstances>Multiple</MultipleInstances>\n                <Mandatory>Mandatory</Mandatory>\n                <Type>Integer</Type>\n                <RangeEnumeration></RangeEnumeration>\n                <Units></Units>\n                <Description></Description>\n            </Item>\n            <Item ID=\"9\">\n                <Name>Float Array</Name>\n                <Operations>R</Operations>\n                <MultipleInstances>Multiple</MultipleInstances>\n                <Mandatory>Mandatory</Mandatory>\n                <Type>Float</Type>\n                <RangeEnumeration></RangeEnumeration>\n                <Units></Units>\n                <Description></Description>\n            </Item>\n            <Item ID=\"10\">\n                <Name>String Array</Name>\n                <Operations>R</Operations>\n                <MultipleInstances>Multiple</MultipleInstances>\n                <Mandatory>Mandatory</Mandatory>\n                <Type>String</Type>\n                <RangeEnumeration></RangeEnumeration>\n                <Units></Units>\n                <Description></Description>\n            </Item>\n            <Item ID=\"11\">\n                <Name>Opaque Array</Name>\n                <Operations>R</Operations>\n                <MultipleInstances>Multiple</MultipleInstances>\n                <Mandatory>Mandatory</Mandatory>\n                <Type>Opaque</Type>\n                <RangeEnumeration></RangeEnumeration>\n                <Units></Units>\n                <Description></Description>\n            </Item>\n            <Item ID=\"12\">\n                <Name>Time Array</Name>\n                <Operations>R</Operations>\n                <MultipleInstances>Multiple</MultipleInstances>\n                <Mandatory>Mandatory</Mandatory>\n                <Type>Time</Type>\n                <RangeEnumeration></RangeEnumeration>\n                <Units></Units>\n                <Description></Description>\n            </Item>\n            <Item ID=\"13\">\n                <Name>ObjLnk Array</Name>\n                <Operations>R</Operations>\n                <MultipleInstances>Multiple</MultipleInstances>\n                <Mandatory>Mandatory</Mandatory>\n                <Type>ObjLnk</Type>\n                <RangeEnumeration></RangeEnumeration>\n                <Units></Units>\n                <Description></Description>\n            </Item>\n        </Resources>\n\t</Object>\n</LWM2M>\n\n"
  },
  {
    "path": "tests/codegen/input/sanitization.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\nCopyright 2017-2026 AVSystem <avsystem@avsystem.com>\nAVSystem Anjay LwM2M SDK\nAll rights reserved.\n\n Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\nSee the attached LICENSE file for details.\n-->\n<LWM2M xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:noNamespaceSchemaLocation=\"http://openmobilealliance.org/tech/profiles/LWM2M.xsd\">\n\t<Object ObjectType=\"MODefinition\">\n\t\t<Name>1-this?begins.with-a digit and has some special characters</Name>\n\t\t<Description1>Test description</Description1>\n\t\t<ObjectID>12345</ObjectID>\n\t\t<ObjectURN>urn:avs:test:12345</ObjectURN>\n\t\t<MultipleInstances>Single</MultipleInstances>\n\t\t<Mandatory>Optional</Mandatory>\n\t\t<Resources>\n            <Item ID=\"0\">\n                <Name>Execute</Name>\n                <Operations>E</Operations>\n                <MultipleInstances>Single</MultipleInstances>\n                <Mandatory>Mandatory</Mandatory>\n                <Type></Type>\n                <RangeEnumeration></RangeEnumeration>\n                <Units></Units>\n                <Description></Description>\n            </Item>\n        </Resources>\n\t</Object>\n</LWM2M>\n\n"
  },
  {
    "path": "tests/codegen/input/write.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\nCopyright 2017-2026 AVSystem <avsystem@avsystem.com>\nAVSystem Anjay LwM2M SDK\nAll rights reserved.\n\n Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\nSee the attached LICENSE file for details.\n-->\n<LWM2M xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:noNamespaceSchemaLocation=\"http://openmobilealliance.org/tech/profiles/LWM2M.xsd\">\n\t<Object ObjectType=\"MODefinition\">\n\t\t<Name>Test object</Name>\n\t\t<Description1>Test description</Description1>\n\t\t<ObjectID>12345</ObjectID>\n\t\t<ObjectURN>urn:avs:test:12345</ObjectURN>\n\t\t<MultipleInstances>Single</MultipleInstances>\n\t\t<Mandatory>Optional</Mandatory>\n\t\t<Resources>\n            <Item ID=\"0\">\n                <Name>Boolean</Name>\n                <Operations>W</Operations>\n                <MultipleInstances>Single</MultipleInstances>\n                <Mandatory>Mandatory</Mandatory>\n                <Type>Boolean</Type>\n                <RangeEnumeration></RangeEnumeration>\n                <Units></Units>\n                <Description></Description>\n            </Item>\n            <Item ID=\"1\">\n                <Name>Integer</Name>\n                <Operations>W</Operations>\n                <MultipleInstances>Single</MultipleInstances>\n                <Mandatory>Mandatory</Mandatory>\n                <Type>Integer</Type>\n                <RangeEnumeration></RangeEnumeration>\n                <Units></Units>\n                <Description></Description>\n            </Item>\n            <Item ID=\"2\">\n                <Name>Float</Name>\n                <Operations>W</Operations>\n                <MultipleInstances>Single</MultipleInstances>\n                <Mandatory>Mandatory</Mandatory>\n                <Type>Float</Type>\n                <RangeEnumeration></RangeEnumeration>\n                <Units></Units>\n                <Description></Description>\n            </Item>\n            <Item ID=\"3\">\n                <Name>String</Name>\n                <Operations>W</Operations>\n                <MultipleInstances>Single</MultipleInstances>\n                <Mandatory>Mandatory</Mandatory>\n                <Type>String</Type>\n                <RangeEnumeration></RangeEnumeration>\n                <Units></Units>\n                <Description></Description>\n            </Item>\n            <Item ID=\"4\">\n                <Name>Opaque</Name>\n                <Operations>W</Operations>\n                <MultipleInstances>Single</MultipleInstances>\n                <Mandatory>Mandatory</Mandatory>\n                <Type>Opaque</Type>\n                <RangeEnumeration></RangeEnumeration>\n                <Units></Units>\n                <Description></Description>\n            </Item>\n            <Item ID=\"5\">\n                <Name>Time</Name>\n                <Operations>W</Operations>\n                <MultipleInstances>Single</MultipleInstances>\n                <Mandatory>Mandatory</Mandatory>\n                <Type>Time</Type>\n                <RangeEnumeration></RangeEnumeration>\n                <Units></Units>\n                <Description></Description>\n            </Item>\n            <Item ID=\"6\">\n                <Name>ObjLnk</Name>\n                <Operations>W</Operations>\n                <MultipleInstances>Single</MultipleInstances>\n                <Mandatory>Mandatory</Mandatory>\n                <Type>ObjLnk</Type>\n                <RangeEnumeration></RangeEnumeration>\n                <Units></Units>\n                <Description></Description>\n            </Item>\n            <Item ID=\"7\">\n                <Name>Boolean Array</Name>\n                <Operations>W</Operations>\n                <MultipleInstances>Multiple</MultipleInstances>\n                <Mandatory>Mandatory</Mandatory>\n                <Type>Boolean</Type>\n                <RangeEnumeration></RangeEnumeration>\n                <Units></Units>\n                <Description></Description>\n            </Item>\n            <Item ID=\"8\">\n                <Name>Integer Array</Name>\n                <Operations>W</Operations>\n                <MultipleInstances>Multiple</MultipleInstances>\n                <Mandatory>Mandatory</Mandatory>\n                <Type>Integer</Type>\n                <RangeEnumeration></RangeEnumeration>\n                <Units></Units>\n                <Description></Description>\n            </Item>\n            <Item ID=\"9\">\n                <Name>Float Array</Name>\n                <Operations>W</Operations>\n                <MultipleInstances>Multiple</MultipleInstances>\n                <Mandatory>Mandatory</Mandatory>\n                <Type>Float</Type>\n                <RangeEnumeration></RangeEnumeration>\n                <Units></Units>\n                <Description></Description>\n            </Item>\n            <Item ID=\"10\">\n                <Name>String Array</Name>\n                <Operations>W</Operations>\n                <MultipleInstances>Multiple</MultipleInstances>\n                <Mandatory>Mandatory</Mandatory>\n                <Type>String</Type>\n                <RangeEnumeration></RangeEnumeration>\n                <Units></Units>\n                <Description></Description>\n            </Item>\n            <Item ID=\"11\">\n                <Name>Opaque Array</Name>\n                <Operations>W</Operations>\n                <MultipleInstances>Multiple</MultipleInstances>\n                <Mandatory>Mandatory</Mandatory>\n                <Type>Opaque</Type>\n                <RangeEnumeration></RangeEnumeration>\n                <Units></Units>\n                <Description></Description>\n            </Item>\n            <Item ID=\"12\">\n                <Name>Time Array</Name>\n                <Operations>W</Operations>\n                <MultipleInstances>Multiple</MultipleInstances>\n                <Mandatory>Mandatory</Mandatory>\n                <Type>Time</Type>\n                <RangeEnumeration></RangeEnumeration>\n                <Units></Units>\n                <Description></Description>\n            </Item>\n            <Item ID=\"13\">\n                <Name>ObjLnk Array</Name>\n                <Operations>W</Operations>\n                <MultipleInstances>Multiple</MultipleInstances>\n                <Mandatory>Mandatory</Mandatory>\n                <Type>ObjLnk</Type>\n                <RangeEnumeration></RangeEnumeration>\n                <Units></Units>\n                <Description></Description>\n            </Item>\n        </Resources>\n\t</Object>\n</LWM2M>\n\n"
  },
  {
    "path": "tests/core/access_utils.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#include <avsystem/commons/avs_stream_membuf.h>\n\n#define AVS_UNIT_ENABLE_SHORT_ASSERTS\n#include <avsystem/commons/avs_unit_test.h>\n\n#include <stdarg.h>\n#include <stdio.h>\n\n#include <anjay/access_control.h>\n#include <anjay/core.h>\n#include <anjay/security.h>\n#include <anjay/server.h>\n\n#include \"src/anjay_modules/anjay_notify.h\"\n#include \"src/core/anjay_access_utils_private.h\"\n#include \"src/core/anjay_core.h\"\n#include \"src/core/servers/anjay_servers_internal.h\"\n\n#include \"tests/utils/utils.h\"\n\n#ifdef ANJAY_WITH_ACCESS_CONTROL\n\nstatic int dummy_list_instances(anjay_t *anjay,\n                                const anjay_dm_object_def_t *const *obj,\n                                anjay_dm_list_ctx_t *ctx) {\n    (void) anjay;\n    (void) obj;\n    // We emit instances 1, 2, 3 so validation passes\n    anjay_dm_emit(ctx, 1);\n    anjay_dm_emit(ctx, 2);\n    anjay_dm_emit(ctx, 3);\n    return 0;\n}\n\nstatic const anjay_dm_object_def_t OBJ_1234 = {\n    .oid = 1234,\n    .handlers = {\n        .list_instances = dummy_list_instances\n    }\n};\nstatic const anjay_dm_object_def_t *OBJ_DEF_1234 = &OBJ_1234;\n\nstatic const anjay_dm_object_def_t OBJ_1235 = {\n    .oid = 1235,\n    .handlers = {\n        .list_instances = dummy_list_instances\n    }\n};\nstatic const anjay_dm_object_def_t *OBJ_DEF_1235 = &OBJ_1235;\n\nstatic const anjay_dm_object_def_t OBJ_1236 = {\n    .oid = 1236,\n    .handlers = {\n        .list_instances = dummy_list_instances\n    }\n};\nstatic const anjay_dm_object_def_t *OBJ_DEF_1236 = &OBJ_1236;\n\ntypedef struct {\n    anjay_t *anjay;\n    anjay_iid_t sec_iid1;\n    anjay_iid_t sec_iid2;\n    anjay_iid_t serv_iid1;\n    anjay_iid_t serv_iid2;\n} core_access_test_env_t;\n\nstatic const anjay_configuration_t CONFIG = {\n    .endpoint_name = \"test\"\n};\n\nstatic void add_server_and_security_1(core_access_test_env_t *env) {\n    static const anjay_security_instance_t sec_instance_1 = {\n        .ssid = 1,\n        .server_uri = \"coap://1.2.3.4\",\n        .bootstrap_server = false,\n        .security_mode = ANJAY_SECURITY_NOSEC,\n        .client_holdoff_s = -1,\n        .bootstrap_timeout_s = -1\n    };\n    env->sec_iid1 = 1;\n\n    AVS_UNIT_ASSERT_SUCCESS(anjay_security_object_add_instance(\n            env->anjay, &sec_instance_1, &env->sec_iid1));\n\n    static const anjay_server_instance_t serv_instance_1 = {\n        .ssid = 1,\n        .lifetime = 42,\n        .default_min_period = -1,\n        .default_max_period = -1,\n        .disable_timeout = -1,\n        .binding = \"U\",\n        .notification_storing = false\n    };\n    env->serv_iid1 = 1;\n    AVS_UNIT_ASSERT_SUCCESS(anjay_server_object_add_instance(\n            env->anjay, &serv_instance_1, &env->serv_iid1));\n}\n\nstatic void add_server_and_security_2(core_access_test_env_t *env) {\n    static const anjay_security_instance_t sec_instance_2 = {\n        .ssid = 2,\n        .server_uri = \"coap://4.3.2.1\",\n        .bootstrap_server = false,\n        .security_mode = ANJAY_SECURITY_NOSEC,\n        .client_holdoff_s = -1,\n        .bootstrap_timeout_s = -1\n    };\n    env->sec_iid2 = 2;\n    AVS_UNIT_ASSERT_SUCCESS(anjay_security_object_add_instance(\n            env->anjay, &sec_instance_2, &env->sec_iid2));\n\n    static const anjay_server_instance_t serv_instance_2 = {\n        .ssid = 2,\n        .lifetime = 42,\n        .default_min_period = -1,\n        .default_max_period = -1,\n        .disable_timeout = -1,\n        .binding = \"U\",\n        .notification_storing = false\n    };\n    env->serv_iid2 = 2;\n    AVS_UNIT_ASSERT_SUCCESS(anjay_server_object_add_instance(\n            env->anjay, &serv_instance_2, &env->serv_iid2));\n}\n\nstatic core_access_test_env_t *core_access_test_env_create(void) {\n    core_access_test_env_t *env = (__typeof__(env)) avs_calloc(1, sizeof(*env));\n    AVS_UNIT_ASSERT_NOT_NULL(env);\n    env->anjay = anjay_new(&CONFIG);\n    AVS_UNIT_ASSERT_NOT_NULL(env->anjay);\n\n    // Install Security, Server, and Access Control\n    AVS_UNIT_ASSERT_SUCCESS(anjay_security_object_install(env->anjay));\n    AVS_UNIT_ASSERT_SUCCESS(anjay_server_object_install(env->anjay));\n    AVS_UNIT_ASSERT_SUCCESS(anjay_access_control_install(env->anjay));\n\n    // Install dummy objects\n    AVS_UNIT_ASSERT_SUCCESS(anjay_register_object(env->anjay, &OBJ_DEF_1234));\n    AVS_UNIT_ASSERT_SUCCESS(anjay_register_object(env->anjay, &OBJ_DEF_1235));\n    AVS_UNIT_ASSERT_SUCCESS(anjay_register_object(env->anjay, &OBJ_DEF_1236));\n\n    // Create Server and Security instances for SSID 1 and SSID 2\n    // We need more than one server instance to prevent\n    // `is_single_ssid_environment()` from returning true, which would bypass\n    // some AC checks.\n    add_server_and_security_1(env);\n    add_server_and_security_2(env);\n    return env;\n}\n\nstatic void core_access_test_env_destroy(core_access_test_env_t **env) {\n    anjay_delete((*env)->anjay);\n    avs_free(*env);\n}\n\n#    define SCOPED_CORE_ACCESS_TEST_ENV(Name)                            \\\n        SCOPED_PTR(core_access_test_env_t, core_access_test_env_destroy) \\\n        Name = core_access_test_env_create();\n\nstatic anjay_notify_queue_object_entry_t *\ncreate_add_entry(anjay_oid_t oid, const char *prefix, anjay_iid_t added_iid) {\n    (void) prefix;\n    anjay_notify_queue_object_entry_t *entry =\n            AVS_LIST_NEW_ELEMENT(anjay_notify_queue_object_entry_t);\n    AVS_UNIT_ASSERT_NOT_NULL(entry);\n    entry->oid = oid;\n#    ifdef ANJAY_WITH_LWM2M_GATEWAY\n    if (prefix) {\n        snprintf(entry->prefix, sizeof(entry->prefix), \"%s\", prefix);\n    } else {\n        entry->prefix[0] = '\\0';\n    }\n#    endif // ANJAY_WITH_LWM2M_GATEWAY\n    entry->instance_set_changes.instance_set_changed = true;\n    anjay_iid_t *iid_ptr = AVS_LIST_NEW_ELEMENT(anjay_iid_t);\n    AVS_UNIT_ASSERT_NOT_NULL(iid_ptr);\n    *iid_ptr = added_iid;\n    AVS_LIST_APPEND(&entry->instance_set_changes.known_added_iids, iid_ptr);\n    return entry;\n}\n\nstatic int count_ac_instances(anjay_t *anjay, anjay_oid_t target_oid) {\n    int count = 0;\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    const anjay_dm_installed_object_t *ac_obj =\n            get_access_control(anjay_unlocked);\n    // All tests add instance 1, so we check if AC instance exists for\n    // target_oid and IID 1\n    if (find_ac_instance_by_target(anjay_unlocked, ac_obj, NULL, target_oid, 1)\n            == 0) {\n        count = 1;\n    } else {\n        count = 0;\n    }\n    ANJAY_MUTEX_UNLOCK(anjay);\n    return count;\n}\n\n// TEST: Access Control creation - base case (no prefixes).\n// All 3 elements in the queue specify newly added instances of regular objects,\n// without any gateway prefix. They should all be parsed by what_changed() and\n// result in perform_adds() creating Access Control instances for each of them.\nAVS_UNIT_TEST(anjay_sync_access_control, adds_3_elements_no_prefix) {\n    SCOPED_CORE_ACCESS_TEST_ENV(env);\n    anjay_t *anjay = env->anjay;\n\n    anjay_notify_queue_t queue = NULL;\n    AVS_LIST_APPEND(&queue, create_add_entry(1234, NULL, 1));\n    AVS_LIST_APPEND(&queue, create_add_entry(1235, NULL, 1));\n    AVS_LIST_APPEND(&queue, create_add_entry(1236, NULL, 1));\n\n    // Using Origin SSID = 1 to differentiate from BOOTSTRAP.\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    AVS_UNIT_ASSERT_TRUE(_anjay_sync_access_control(anjay_unlocked, 1, &queue)\n                         == 0);\n    ANJAY_MUTEX_UNLOCK(anjay);\n\n    // AC instances should be generated for all 3 target objects.\n    AVS_UNIT_ASSERT_EQUAL(count_ac_instances(anjay, 1234), 1);\n    AVS_UNIT_ASSERT_EQUAL(count_ac_instances(anjay, 1235), 1);\n    AVS_UNIT_ASSERT_EQUAL(count_ac_instances(anjay, 1236), 1);\n\n    _anjay_notify_clear_queue(&queue);\n}\n\n// TEST: Access Control creation bypass for gateways.\n// The first element in the queue has a gateway prefix (\"gw-\").\n// what_changed() should skip generating AC instance for this specific entry.\n// For the 2nd and 3rd instances, AC creation should execute normally.\nAVS_UNIT_TEST(anjay_sync_access_control, adds_1st_element_prefix) {\n    SCOPED_CORE_ACCESS_TEST_ENV(env);\n    anjay_t *anjay = env->anjay;\n\n    anjay_notify_queue_t queue = NULL;\n    AVS_LIST_APPEND(&queue, create_add_entry(1234, \"gw-\", 1));\n    AVS_LIST_APPEND(&queue, create_add_entry(1235, NULL, 1));\n    AVS_LIST_APPEND(&queue, create_add_entry(1236, NULL, 1));\n\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_sync_access_control(anjay_unlocked, 1, &queue));\n    ANJAY_MUTEX_UNLOCK(anjay);\n\n    int expected_count = 1;\n#    ifdef ANJAY_WITH_LWM2M_GATEWAY\n    // Prefix \"gw-\" on 1st element excludes it from perform_adds().\n    expected_count = 0;\n#    endif // ANJAY_WITH_LWM2M_GATEWAY\n\n    AVS_UNIT_ASSERT_EQUAL(count_ac_instances(anjay, 1234), expected_count);\n    AVS_UNIT_ASSERT_EQUAL(count_ac_instances(anjay, 1235), 1);\n    AVS_UNIT_ASSERT_EQUAL(count_ac_instances(anjay, 1236), 1);\n\n    _anjay_notify_clear_queue(&queue);\n}\n\n// TEST: Access Control creation bypass for gateways (middle element).\n// Demonstrates that what_changed() ignores only the entry that actually has the\n// prefix, meaning that elements before and after it are processed.\nAVS_UNIT_TEST(anjay_sync_access_control, adds_2nd_element_prefix) {\n    SCOPED_CORE_ACCESS_TEST_ENV(env);\n    anjay_t *anjay = env->anjay;\n\n    anjay_notify_queue_t queue = NULL;\n    AVS_LIST_APPEND(&queue, create_add_entry(1234, NULL, 1));\n    AVS_LIST_APPEND(&queue, create_add_entry(1235, \"gw-\", 1));\n    AVS_LIST_APPEND(&queue, create_add_entry(1236, NULL, 1));\n\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    AVS_UNIT_ASSERT_TRUE(_anjay_sync_access_control(anjay_unlocked, 1, &queue)\n                         == 0);\n    ANJAY_MUTEX_UNLOCK(anjay);\n\n    int expected_count = 1;\n#    ifdef ANJAY_WITH_LWM2M_GATEWAY\n    // Prefix \"gw-\" on 2nd element excludes it from perform_adds().\n    expected_count = 0;\n#    endif // ANJAY_WITH_LWM2M_GATEWAY\n\n    AVS_UNIT_ASSERT_EQUAL(count_ac_instances(anjay, 1234), 1);\n    AVS_UNIT_ASSERT_EQUAL(count_ac_instances(anjay, 1235), expected_count);\n    AVS_UNIT_ASSERT_EQUAL(count_ac_instances(anjay, 1236), 1);\n\n    _anjay_notify_clear_queue(&queue);\n}\n\n#endif // ANJAY_WITH_ACCESS_CONTROL\n"
  },
  {
    "path": "tests/core/anjay.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#include <avsystem/commons/avs_stream_membuf.h>\n\n#define AVS_UNIT_ENABLE_SHORT_ASSERTS\n#include <avsystem/commons/avs_unit_test.h>\n\n#include <stdarg.h>\n#include <stdio.h>\n\n#include <avsystem/coap/ctx.h>\n\n#include <anjay_modules/anjay_dm_utils.h>\n\n#include \"src/core/anjay_servers_inactive.h\"\n#include \"src/core/anjay_servers_reload.h\"\n#include \"src/core/servers/anjay_server_connections.h\"\n#include \"tests/core/coap/utils.h\"\n#include \"tests/core/socket_mock.h\"\n#include \"tests/utils/dm.h\"\n#include \"tests/utils/utils.h\"\n\nAVS_UNIT_GLOBAL_INIT(verbose) {\n#if defined(AVS_COMMONS_WITH_AVS_LOG) && defined(ANJAY_WITH_LOGS)\n    if (verbose < 2) {\n        avs_log_set_default_level(AVS_LOG_QUIET);\n    }\n#endif\n}\n\n#define TEST_NULLABLE_STRING_EQUAL(Actual, Expected) \\\n    do {                                             \\\n        if (Expected != NULL) {                      \\\n            ASSERT_NOT_NULL((Actual));               \\\n            ASSERT_EQ_STR((Actual), (Expected));     \\\n        } else {                                     \\\n            ASSERT_NULL((Actual));                   \\\n        }                                            \\\n    } while (0)\n\n#define TEST_SPLIT_QUERY_STRING(QueryString, ExpectedKey, ExpectedValue) \\\n    do {                                                                 \\\n        char buf[] = QueryString;                                        \\\n        const char *key;                                                 \\\n        const char *value;                                               \\\n        split_query_string(buf, &key, &value);                           \\\n        TEST_NULLABLE_STRING_EQUAL(key, ExpectedKey);                    \\\n        TEST_NULLABLE_STRING_EQUAL(value, ExpectedValue);                \\\n    } while (0)\n\nAVS_UNIT_TEST(parse_headers, split_query_string) {\n    TEST_SPLIT_QUERY_STRING(\"\", \"\", NULL);\n    TEST_SPLIT_QUERY_STRING(\"key\", \"key\", NULL);\n    TEST_SPLIT_QUERY_STRING(\"key=\", \"key\", \"\");\n    TEST_SPLIT_QUERY_STRING(\"=value\", \"\", \"value\");\n    TEST_SPLIT_QUERY_STRING(\"key=value\", \"key\", \"value\");\n}\n\n#undef TEST_SPLIT_QUERY_STRING\n#undef TEST_NULLABLE_STRING_EQUAL\n\n#ifdef ANJAY_WITH_LWM2M12\n#    define PARSE_QUERY_WRAPPED parse_query\n#    define PARSE_QUERIES_WRAPPED parse_queries\n// NOTE: Additional parameter for code compatibility with the version including\n// LwM2M 1.2 commercial feature\n#else // ANJAY_WITH_LWM2M12\n#    define PARSE_QUERY_WRAPPED(OutAttrs, OutDepth, Key, Value)               \\\n        ({                                                                    \\\n            int parse_query_result = parse_query((OutAttrs), (Key), (Value)); \\\n            *(OutDepth) = -1;                                                 \\\n            parse_query_result;                                               \\\n        })\n#    define PARSE_QUERIES_WRAPPED(Header, OutAttrs, OutDepth)               \\\n        ({                                                                  \\\n            int parse_queries_result = parse_queries((Header), (OutAttrs)); \\\n            *(OutDepth) = -1;                                               \\\n            parse_queries_result;                                           \\\n        })\n#endif // ANJAY_WITH_LWM2M12\n\n#define TEST_PARSE_ATTRIBUTE_SUCCESS(Key, Value, ExpectedField,         \\\n                                     ExpectedHasField, ExpectedValue)   \\\n    do {                                                                \\\n        anjay_request_attributes_t attrs;                               \\\n        int8_t depth = -1;                                              \\\n        memset(&attrs, 0, sizeof(attrs));                               \\\n        ASSERT_OK(PARSE_QUERY_WRAPPED(&attrs, &depth, (Key), (Value))); \\\n        ASSERT_EQ(depth, -1);                                           \\\n        ASSERT_EQ(attrs.values.ExpectedField, (ExpectedValue));         \\\n        anjay_request_attributes_t expected;                            \\\n        memset(&expected, 0, sizeof(expected));                         \\\n        expected.ExpectedHasField = true;                               \\\n        expected.values.ExpectedField = (ExpectedValue);                \\\n        ASSERT_EQ_BYTES_SIZED(&attrs, &expected,                        \\\n                              sizeof(anjay_request_attributes_t));      \\\n    } while (0)\n\n#define TEST_PARSE_ATTRIBUTE_FAIL(Key, Value)                             \\\n    do {                                                                  \\\n        anjay_request_attributes_t attrs;                                 \\\n        int8_t depth = -1;                                                \\\n        memset(&attrs, 0, sizeof(attrs));                                 \\\n        ASSERT_FAIL(PARSE_QUERY_WRAPPED(&attrs, &depth, (Key), (Value))); \\\n    } while (0);\n\nAVS_UNIT_TEST(parse_headers, parse_attribute) {\n    TEST_PARSE_ATTRIBUTE_SUCCESS(\"pmin\", \"123\", common.min_period,\n                                 has_min_period, 123);\n    TEST_PARSE_ATTRIBUTE_SUCCESS(\"pmin\", NULL, common.min_period,\n                                 has_min_period, -1);\n    TEST_PARSE_ATTRIBUTE_FAIL(\"pmin\", \"123.4\");\n    TEST_PARSE_ATTRIBUTE_FAIL(\"pmin\", \"woof\");\n    TEST_PARSE_ATTRIBUTE_FAIL(\"pmin\", \"\");\n\n    TEST_PARSE_ATTRIBUTE_SUCCESS(\"pmax\", \"234\", common.max_period,\n                                 has_max_period, 234);\n    TEST_PARSE_ATTRIBUTE_SUCCESS(\"pmax\", NULL, common.max_period,\n                                 has_max_period, -1);\n    TEST_PARSE_ATTRIBUTE_FAIL(\"pmax\", \"234.5\");\n    TEST_PARSE_ATTRIBUTE_FAIL(\"pmax\", \"meow\");\n    TEST_PARSE_ATTRIBUTE_FAIL(\"pmax\", \"\");\n\n    TEST_PARSE_ATTRIBUTE_SUCCESS(\"gt\", \"345\", greater_than, has_greater_than,\n                                 345.0);\n    TEST_PARSE_ATTRIBUTE_SUCCESS(\"gt\", \"345.6\", greater_than, has_greater_than,\n                                 345.6);\n    TEST_PARSE_ATTRIBUTE_SUCCESS(\"gt\", NULL, greater_than, has_greater_than,\n                                 NAN);\n    TEST_PARSE_ATTRIBUTE_FAIL(\"gt\", \"tweet\");\n    TEST_PARSE_ATTRIBUTE_FAIL(\"gt\", \"\");\n\n    TEST_PARSE_ATTRIBUTE_SUCCESS(\"lt\", \"456\", less_than, has_less_than, 456.0);\n    TEST_PARSE_ATTRIBUTE_SUCCESS(\"lt\", \"456.7\", less_than, has_less_than,\n                                 456.7);\n    TEST_PARSE_ATTRIBUTE_SUCCESS(\"lt\", NULL, less_than, has_less_than, NAN);\n    TEST_PARSE_ATTRIBUTE_FAIL(\"lt\", \"squeak\");\n    TEST_PARSE_ATTRIBUTE_FAIL(\"lt\", \"\");\n\n    TEST_PARSE_ATTRIBUTE_SUCCESS(\"st\", \"567\", step, has_step, 567.0);\n    TEST_PARSE_ATTRIBUTE_SUCCESS(\"st\", \"567.8\", step, has_step, 567.8);\n    TEST_PARSE_ATTRIBUTE_SUCCESS(\"st\", NULL, step, has_step, NAN);\n    TEST_PARSE_ATTRIBUTE_FAIL(\"st\", \"moo\");\n    TEST_PARSE_ATTRIBUTE_FAIL(\"st\", \"\");\n\n    TEST_PARSE_ATTRIBUTE_FAIL(\"unknown\", \"wa-pa-pa-pa-pa-pa-pow\");\n    TEST_PARSE_ATTRIBUTE_FAIL(\"unknown\", NULL);\n    TEST_PARSE_ATTRIBUTE_FAIL(\"unknown\", \"\");\n}\n\n#undef TEST_PARSE_ATTRIBUTE_SUCCESS\n#undef TEST_PARSE_ATTRIBUTE_FAILED\n\n#ifdef ANJAY_WITH_CON_ATTR\n#    define ASSERT_CON_ATTRIBUTE_VALUES_EQUAL(actual, expected) \\\n        ASSERT_EQ(actual.common.con, expected.common.con)\n#else // ANJAY_WITH_CON_ATTR\n#    define ASSERT_CON_ATTRIBUTE_VALUES_EQUAL(actual, expected) ((void) 0)\n#endif // ANJAY_WITH_CON_ATTR\n\n#ifdef ANJAY_WITH_LWM2M12\n#    define ASSERT_LWM2M12_ATTRIBUTE_VALUES_EQUAL(actual, expected) \\\n        do {                                                        \\\n            ASSERT_EQ(actual.common.hqmax, expected.common.hqmax);  \\\n            ASSERT_EQ(actual.edge, expected.edge);                  \\\n        } while (0)\n#else // ANJAY_WITH_LWM2M12\n#    define ASSERT_LWM2M12_ATTRIBUTE_VALUES_EQUAL(actual, expected) ((void) 0)\n#endif // ANJAY_WITH_LWM2M12\n\n#define ASSERT_ATTRIBUTE_VALUES_EQUAL(actual, expected)                  \\\n    do {                                                                 \\\n        ASSERT_EQ(actual.common.min_period, expected.common.min_period); \\\n        ASSERT_EQ(actual.common.max_period, expected.common.max_period); \\\n        ASSERT_EQ(actual.common.min_eval_period,                         \\\n                  expected.common.min_eval_period);                      \\\n        ASSERT_EQ(actual.common.max_eval_period,                         \\\n                  expected.common.max_eval_period);                      \\\n        ASSERT_EQ(actual.greater_than, expected.greater_than);           \\\n        ASSERT_EQ(actual.less_than, expected.less_than);                 \\\n        ASSERT_EQ(actual.step, expected.step);                           \\\n        ASSERT_CON_ATTRIBUTE_VALUES_EQUAL(actual, expected);             \\\n        ASSERT_LWM2M12_ATTRIBUTE_VALUES_EQUAL(actual, expected);         \\\n    } while (0)\n\n#ifdef ANJAY_WITH_CON_ATTR\n#    define ASSERT_CON_ATTRIBUTE_FLAGS_EQUAL(actual, expected) \\\n        ASSERT_EQ(actual.has_con, expected.has_con)\n#else // ANJAY_WITH_CON_ATTR\n#    define ASSERT_CON_ATTRIBUTE_FLAGS_EQUAL(actual, expected) ((void) 0)\n#endif // ANJAY_WITH_CON_ATTR\n\n#ifdef ANJAY_WITH_LWM2M12\n#    define ASSERT_LWM2M12_ATTRIBUTE_FLAGS_EQUAL(actual, expected) \\\n        do {                                                       \\\n            ASSERT_EQ(actual.has_hqmax, expected.has_hqmax);       \\\n            ASSERT_EQ(actual.has_edge, expected.has_edge);         \\\n        } while (0)\n#else // ANJAY_WITH_LWM2M12\n#    define ASSERT_LWM2M12_ATTRIBUTE_FLAGS_EQUAL(actual, expected) ((void) 0)\n#endif // ANJAY_WITH_LWM2M12\n\n#define ASSERT_ATTRIBUTES_EQUAL(actual, expected)                            \\\n    do {                                                                     \\\n        ASSERT_EQ(actual.has_min_period, expected.has_min_period);           \\\n        ASSERT_EQ(actual.has_max_period, expected.has_max_period);           \\\n        ASSERT_EQ(actual.has_min_eval_period, expected.has_min_eval_period); \\\n        ASSERT_EQ(actual.has_max_eval_period, expected.has_max_eval_period); \\\n        ASSERT_EQ(actual.has_greater_than, expected.has_greater_than);       \\\n        ASSERT_EQ(actual.has_less_than, expected.has_less_than);             \\\n        ASSERT_EQ(actual.has_step, expected.has_step);                       \\\n        ASSERT_CON_ATTRIBUTE_FLAGS_EQUAL(actual, expected);                  \\\n        ASSERT_LWM2M12_ATTRIBUTE_FLAGS_EQUAL(actual, expected);              \\\n        ASSERT_ATTRIBUTE_VALUES_EQUAL(actual.values, expected.values);       \\\n    } while (0)\n\ntypedef struct {\n    uint8_t buffer[1024];\n    avs_coap_request_header_t header;\n} header_with_opts_storage_t;\n\nstatic avs_coap_request_header_t *header_with_string_opts(\n        header_with_opts_storage_t *storage, uint16_t string_option, ...) {\n    memset(storage, 0, sizeof(*storage));\n    storage->header.options =\n            avs_coap_options_create_empty(storage->buffer,\n                                          sizeof(storage->buffer));\n    va_list query;\n    va_start(query, string_option);\n    const char *arg = NULL;\n    while ((arg = va_arg(query, const char *))) {\n        ASSERT_OK(avs_coap_options_add_string(&storage->header.options,\n                                              string_option, arg));\n    }\n    va_end(query);\n    return &storage->header;\n}\n\nAVS_UNIT_TEST(parse_headers, parse_attributes) {\n    anjay_request_attributes_t attrs;\n    int8_t depth;\n    anjay_request_attributes_t empty_attrs;\n    memset(&empty_attrs, 0, sizeof(empty_attrs));\n    empty_attrs.values = ANJAY_DM_R_ATTRIBUTES_EMPTY;\n    anjay_request_attributes_t expected_attrs;\n    header_with_opts_storage_t header_storage;\n\n    // no query-strings\n    ASSERT_OK(PARSE_QUERIES_WRAPPED(\n            header_with_string_opts(&header_storage, AVS_COAP_OPTION_URI_QUERY,\n                                    NULL),\n            &attrs, &depth));\n    ASSERT_EQ_BYTES_SIZED(&attrs, &empty_attrs, sizeof(attrs));\n    ASSERT_EQ(depth, -1);\n\n    // single query-string\n    memcpy(&expected_attrs, &empty_attrs, sizeof(expected_attrs));\n    expected_attrs.has_min_period = true;\n    expected_attrs.values.common.min_period = 10;\n    ASSERT_OK(PARSE_QUERIES_WRAPPED(\n            header_with_string_opts(&header_storage, AVS_COAP_OPTION_URI_QUERY,\n                                    \"pmin=10\", NULL),\n            &attrs, &depth));\n    ASSERT_ATTRIBUTES_EQUAL(attrs, expected_attrs);\n    ASSERT_EQ(depth, -1);\n\n    // multiple query-strings\n    memcpy(&expected_attrs, &empty_attrs, sizeof(expected_attrs));\n    expected_attrs.has_min_period = true;\n    expected_attrs.values.common.min_period = 10;\n    expected_attrs.has_max_period = true;\n    expected_attrs.values.common.max_period = 20;\n    ASSERT_OK(PARSE_QUERIES_WRAPPED(\n            header_with_string_opts(&header_storage, AVS_COAP_OPTION_URI_QUERY,\n                                    \"pmin=10\", \"pmax=20\", NULL),\n            &attrs, &depth));\n    ASSERT_ATTRIBUTES_EQUAL(attrs, expected_attrs);\n    ASSERT_EQ(depth, -1);\n\n    // duplicate options\n    ASSERT_FAIL(PARSE_QUERIES_WRAPPED(\n            header_with_string_opts(&header_storage, AVS_COAP_OPTION_URI_QUERY,\n                                    \"pmin=10\", \"pmin=20\", NULL),\n            &attrs, &depth));\n\n    ASSERT_FAIL(PARSE_QUERIES_WRAPPED(\n            header_with_string_opts(&header_storage, AVS_COAP_OPTION_URI_QUERY,\n                                    \"lt=4\", \"lt=6\", NULL),\n            &attrs, &depth));\n\n    // unrecognized query-string only\n    ASSERT_FAIL(PARSE_QUERIES_WRAPPED(\n            header_with_string_opts(&header_storage, AVS_COAP_OPTION_URI_QUERY,\n                                    \"WhatsTheMeaningOf=Stonehenge\", NULL),\n            &attrs, &depth));\n\n    // unrecognized query-string first\n    ASSERT_FAIL(PARSE_QUERIES_WRAPPED(\n            header_with_string_opts(&header_storage, AVS_COAP_OPTION_URI_QUERY,\n                                    \"WhyDidTheyBuildThe=Stonehenge\", \"pmax=20\",\n                                    NULL),\n            &attrs, &depth));\n\n    // unrecognized query-string last\n    ASSERT_FAIL(PARSE_QUERIES_WRAPPED(\n            header_with_string_opts(&header_storage, AVS_COAP_OPTION_URI_QUERY,\n                                    \"gt=30.5\", \"AllICanThinkOfIsStonehenge\",\n                                    NULL),\n            &attrs, &depth));\n\n    // multiple unrecognized query-strings\n    ASSERT_FAIL(PARSE_QUERIES_WRAPPED(\n            header_with_string_opts(&header_storage, AVS_COAP_OPTION_URI_QUERY,\n                                    \"Stonehenge\", \"Stonehenge\",\n                                    \"LotsOfStonesInARow\", NULL),\n            &attrs, &depth));\n\n    // single query-string among multiple unrecognized ones\n    ASSERT_FAIL(PARSE_QUERIES_WRAPPED(\n            header_with_string_opts(&header_storage, AVS_COAP_OPTION_URI_QUERY,\n                                    \"TheyWere=25Tons\", \"EachStoneMyFriend\",\n                                    \"lt=40.5\", \"ButAmazinglyThey\",\n                                    \"GotThemAllDownInTheSand\", NULL),\n            &attrs, &depth));\n\n    // invalid query-string value\n    ASSERT_FAIL(PARSE_QUERIES_WRAPPED(\n            header_with_string_opts(&header_storage, AVS_COAP_OPTION_URI_QUERY,\n                                    \"st=What'sTheDealWithStonehenge\", NULL),\n            &attrs, &depth));\n\n    // unexpected value\n    ASSERT_FAIL(PARSE_QUERIES_WRAPPED(\n            header_with_string_opts(&header_storage, AVS_COAP_OPTION_URI_QUERY,\n                                    \"YouShouldHaveLeftATinyHint\", NULL),\n            &attrs, &depth));\n}\n\n#ifdef ANJAY_WITH_LWM2M12\nAVS_UNIT_TEST(parse_headers, parse_depth) {\n    anjay_request_attributes_t attrs;\n    anjay_request_attributes_t empty_attrs;\n    memset(&empty_attrs, 0, sizeof(empty_attrs));\n    empty_attrs.values = ANJAY_DM_R_ATTRIBUTES_EMPTY;\n    int8_t depth;\n    header_with_opts_storage_t header_storage;\n\n    // valid depth values\n    for (int expected_depth = 0; expected_depth <= 3; ++expected_depth) {\n        char opt[16];\n        snprintf(opt, sizeof(opt), \"depth=%d\", expected_depth);\n        ASSERT_OK(PARSE_QUERIES_WRAPPED(\n                header_with_string_opts(&header_storage,\n                                        AVS_COAP_OPTION_URI_QUERY, opt, NULL),\n                &attrs, &depth));\n        ASSERT_ATTRIBUTES_EQUAL(attrs, empty_attrs);\n        ASSERT_EQ(depth, expected_depth);\n    }\n\n    // depth out of range\n    ASSERT_FAIL(PARSE_QUERIES_WRAPPED(\n            header_with_string_opts(&header_storage, AVS_COAP_OPTION_URI_QUERY,\n                                    \"depth=-1\", NULL),\n            &attrs, &depth));\n    ASSERT_FAIL(PARSE_QUERIES_WRAPPED(\n            header_with_string_opts(&header_storage, AVS_COAP_OPTION_URI_QUERY,\n                                    \"depth=4\", NULL),\n            &attrs, &depth));\n\n    // duplicate depth\n    ASSERT_FAIL(PARSE_QUERIES_WRAPPED(\n            header_with_string_opts(&header_storage, AVS_COAP_OPTION_URI_QUERY,\n                                    \"depth=1\", \"depth=2\", NULL),\n            &attrs, &depth));\n}\n#endif // ANJAY_WITH_LWM2M12\n\n#undef ASSERT_ATTRIBUTES_EQUAL\n#undef ASSERT_ATTRIBUTE_VALUES_EQUAL\n\nstatic void parse_headers_parse_uri_standard() {\n    bool is_bs;\n    anjay_uri_path_t uri;\n    header_with_opts_storage_t header_storage;\n\n    // OID only\n    ASSERT_OK(parse_request_uri(\n            header_with_string_opts(&header_storage, AVS_COAP_OPTION_URI_PATH,\n                                    \"1\", NULL),\n            &is_bs, &uri));\n    ASSERT_FALSE(is_bs);\n    ASSERT_TRUE(_anjay_uri_path_equal(&uri, &MAKE_OBJECT_PATH(1)));\n\n    // OID+IID\n    ASSERT_OK(parse_request_uri(\n            header_with_string_opts(&header_storage, AVS_COAP_OPTION_URI_PATH,\n                                    \"2\", \"3\", NULL),\n            &is_bs, &uri));\n    ASSERT_FALSE(is_bs);\n    ASSERT_TRUE(_anjay_uri_path_equal(&uri, &MAKE_INSTANCE_PATH(2, 3)));\n\n    // OID+IID+RID\n    ASSERT_OK(parse_request_uri(\n            header_with_string_opts(&header_storage, AVS_COAP_OPTION_URI_PATH,\n                                    \"4\", \"5\", \"6\", NULL),\n            &is_bs, &uri));\n    ASSERT_FALSE(is_bs);\n    ASSERT_TRUE(_anjay_uri_path_equal(&uri, &MAKE_RESOURCE_PATH(4, 5, 6)));\n\n    // OID+IID+RID+RIID\n    ASSERT_OK(parse_request_uri(\n            header_with_string_opts(&header_storage, AVS_COAP_OPTION_URI_PATH,\n                                    \"7\", \"8\", \"9\", \"10\", NULL),\n            &is_bs, &uri));\n    ASSERT_FALSE(is_bs);\n    ASSERT_TRUE(_anjay_uri_path_equal(\n            &uri, &MAKE_RESOURCE_INSTANCE_PATH(7, 8, 9, 10)));\n\n    // max valid OID/IID/RID/RIID\n    ASSERT_OK(parse_request_uri(\n            header_with_string_opts(&header_storage, AVS_COAP_OPTION_URI_PATH,\n                                    \"65534\", \"65534\", \"65534\", \"65534\", NULL),\n            &is_bs, &uri));\n    ASSERT_FALSE(is_bs);\n    ASSERT_TRUE(_anjay_uri_path_equal(\n            &uri, &MAKE_RESOURCE_INSTANCE_PATH(65534, 65534, 65534, 65534)));\n\n    // Bootstrap URI\n    ASSERT_OK(parse_request_uri(\n            header_with_string_opts(&header_storage, AVS_COAP_OPTION_URI_PATH,\n                                    \"bs\", NULL),\n            &is_bs, &uri));\n    ASSERT_TRUE(is_bs);\n    ASSERT_TRUE(_anjay_uri_path_equal(&uri, &MAKE_ROOT_PATH()));\n\n    // no Request-Uri\n    ASSERT_OK(parse_request_uri(\n            header_with_string_opts(&header_storage, AVS_COAP_OPTION_URI_PATH,\n                                    NULL),\n            &is_bs, &uri));\n    ASSERT_FALSE(is_bs);\n    ASSERT_TRUE(_anjay_uri_path_equal(&uri, &MAKE_ROOT_PATH()));\n\n    // empty Request-Uri - permitted alternate form\n    ASSERT_OK(parse_request_uri(\n            header_with_string_opts(&header_storage, AVS_COAP_OPTION_URI_PATH,\n                                    \"\", NULL),\n            &is_bs, &uri));\n    ASSERT_FALSE(is_bs);\n    ASSERT_TRUE(_anjay_uri_path_equal(&uri, &MAKE_ROOT_PATH()));\n\n    // superfluous empty segments\n    ASSERT_FAIL(parse_request_uri(\n            header_with_string_opts(&header_storage, AVS_COAP_OPTION_URI_PATH,\n                                    \"\", \"1\", NULL),\n            &is_bs, &uri));\n    ASSERT_FAIL(parse_request_uri(\n            header_with_string_opts(&header_storage, AVS_COAP_OPTION_URI_PATH,\n                                    \"1\", \"\", \"2\", NULL),\n            &is_bs, &uri));\n\n    // prefix\n    ASSERT_FAIL(parse_request_uri(\n            header_with_string_opts(&header_storage, AVS_COAP_OPTION_URI_PATH,\n                                    \"they're taking the hobbits\", \"to isengard\",\n                                    \"7\", \"8\", \"9\", NULL),\n            &is_bs, &uri));\n\n    // prefix that looks like OID + OID+IID+RID+RIID\n    ASSERT_FAIL(parse_request_uri(\n            header_with_string_opts(&header_storage, AVS_COAP_OPTION_URI_PATH,\n                                    \"100\", \"10\", \"11\", \"12\", \"13\", NULL),\n            &is_bs, &uri));\n\n    // prefix that looks like OID/IID/RID + string + OID only\n    ASSERT_FAIL(parse_request_uri(\n            header_with_string_opts(&header_storage, AVS_COAP_OPTION_URI_PATH,\n                                    \"100\", \"101\", \"102\", \"wololo\", \"13\", NULL),\n            &is_bs, &uri));\n\n    // trailing non-numeric segment\n    ASSERT_FAIL(parse_request_uri(\n            header_with_string_opts(&header_storage, AVS_COAP_OPTION_URI_PATH,\n                                    \"14\", \"NopeChuckTesta\", NULL),\n            &is_bs, &uri));\n\n    // invalid OID\n    ASSERT_FAIL(parse_request_uri(\n            header_with_string_opts(&header_storage, AVS_COAP_OPTION_URI_PATH,\n                                    \"65535\", NULL),\n            &is_bs, &uri));\n\n    // invalid IID\n    ASSERT_FAIL(parse_request_uri(\n            header_with_string_opts(&header_storage, AVS_COAP_OPTION_URI_PATH,\n                                    \"15\", \"65535\", NULL),\n            &is_bs, &uri));\n\n    // invalid RID\n    ASSERT_FAIL(parse_request_uri(\n            header_with_string_opts(&header_storage, AVS_COAP_OPTION_URI_PATH,\n                                    \"16\", \"17\", \"65535\", NULL),\n            &is_bs, &uri));\n\n    // invalid RIID\n    ASSERT_FAIL(parse_request_uri(\n            header_with_string_opts(&header_storage, AVS_COAP_OPTION_URI_PATH,\n                                    \"16\", \"17\", \"18\", \"65535\", NULL),\n            &is_bs, &uri));\n}\n\n#ifndef ANJAY_WITH_LWM2M_GATEWAY\nAVS_UNIT_TEST(parse_headers, parse_uri) {\n    parse_headers_parse_uri_standard();\n\n    bool is_bs;\n    anjay_uri_path_t uri;\n    header_with_opts_storage_t header_storage;\n\n    // normal, single prefix\n    ASSERT_FAIL(parse_request_uri(\n            header_with_string_opts(&header_storage, AVS_COAP_OPTION_URI_PATH,\n                                    \"dev\", \"10\", \"11\", \"12\", \"13\", NULL),\n            &is_bs, &uri));\n\n    // \"bs\" and something more\n    ASSERT_FAIL(parse_request_uri(\n            header_with_string_opts(&header_storage, AVS_COAP_OPTION_URI_PATH,\n                                    \"bs\", \"1\", \"2\", NULL),\n            &is_bs, &uri));\n}\n\n#else  // ANJAY_WITH_LWM2M_GATEWAY\nAVS_UNIT_TEST(parse_headers, parse_uri_with_lwm2m_gateway_support) {\n    parse_headers_parse_uri_standard();\n\n    bool is_bs;\n    anjay_uri_path_t uri;\n    header_with_opts_storage_t header_storage;\n\n    // alphanumeric valid prefix ---------------------------------------------\n    // prefix + OID\n    ASSERT_OK(parse_request_uri(\n            header_with_string_opts(&header_storage, AVS_COAP_OPTION_URI_PATH,\n                                    \"dev1\", \"1\", NULL),\n            &is_bs, &uri));\n    ASSERT_FALSE(is_bs);\n    anjay_uri_path_t expected_path = MAKE_OBJECT_PATH_WITH_PREFIX(\"dev1\", 1);\n    ASSERT_TRUE(_anjay_uri_path_equal(&uri, &expected_path));\n\n    // prefix + OID + IID\n    ASSERT_OK(parse_request_uri(\n            header_with_string_opts(&header_storage, AVS_COAP_OPTION_URI_PATH,\n                                    \"dev1\", \"1\", \"2\", NULL),\n            &is_bs, &uri));\n    ASSERT_FALSE(is_bs);\n    expected_path = MAKE_INSTANCE_PATH_WITH_PREFIX(\"dev1\", 1, 2);\n    ASSERT_TRUE(_anjay_uri_path_equal(&uri, &expected_path));\n\n    // prefix + OID + IID + RID\n    ASSERT_OK(parse_request_uri(\n            header_with_string_opts(&header_storage, AVS_COAP_OPTION_URI_PATH,\n                                    \"dev1\", \"1\", \"2\", \"3\", NULL),\n            &is_bs, &uri));\n    ASSERT_FALSE(is_bs);\n    expected_path = MAKE_RESOURCE_PATH_WITH_PREFIX(\"dev1\", 1, 2, 3);\n    ASSERT_TRUE(_anjay_uri_path_equal(&uri, &expected_path));\n\n    // prefix + OID + IID + RID + RIID\n    ASSERT_OK(parse_request_uri(\n            header_with_string_opts(&header_storage, AVS_COAP_OPTION_URI_PATH,\n                                    \"dev1\", \"1\", \"2\", \"3\", \"4\", NULL),\n            &is_bs, &uri));\n    ASSERT_FALSE(is_bs);\n    expected_path = MAKE_RESOURCE_INSTANCE_PATH_WITH_PREFIX(\"dev1\", 1, 2, 3, 4);\n    ASSERT_TRUE(_anjay_uri_path_equal(&uri, &expected_path));\n\n    // invalid prefixes ------------------------------------------------\n    // invalid prefix starting with a number\n    ASSERT_FAIL(parse_request_uri(\n            header_with_string_opts(&header_storage, AVS_COAP_OPTION_URI_PATH,\n                                    \"1dev\", \"1\", \"2\", NULL),\n            &is_bs, &uri));\n\n    // 2 prefixes\n    ASSERT_FAIL(parse_request_uri(\n            header_with_string_opts(&header_storage, AVS_COAP_OPTION_URI_PATH,\n                                    \"dev1\", \"prefix\", \"1\", \"2\", NULL),\n            &is_bs, &uri));\n\n    // valid prefix + invalid options number --------------------------\n    ASSERT_FAIL(parse_request_uri(\n            header_with_string_opts(&header_storage, AVS_COAP_OPTION_URI_PATH,\n                                    \"1dev\", \"1\", \"2\", \"3\", \"4\", \"5\", NULL),\n            &is_bs, &uri));\n\n    // valid prefix max length -----------------------------------\n    char prefix[ANJAY_GATEWAY_MAX_PREFIX_LEN];\n    for (size_t i = 0; i < sizeof(prefix) - 1; i++) {\n        prefix[i] = 'a';\n    }\n    prefix[sizeof(prefix) - 1] = '\\0';\n    ASSERT_OK(parse_request_uri(\n            header_with_string_opts(&header_storage, AVS_COAP_OPTION_URI_PATH,\n                                    prefix, \"1\", NULL),\n            &is_bs, &uri));\n    ASSERT_FALSE(is_bs);\n    expected_path = MAKE_OBJECT_PATH(1);\n    strcpy(expected_path.prefix, prefix);\n    ASSERT_TRUE(_anjay_uri_path_equal(&uri, &expected_path));\n\n    // prefix too long -----------------------------------\n    char prefix_too_long[ANJAY_GATEWAY_MAX_PREFIX_LEN + 1];\n    for (size_t i = 0; i < sizeof(prefix_too_long) - 1; i++) {\n        prefix_too_long[i] = 'a';\n    }\n    prefix_too_long[sizeof(prefix_too_long) - 1] = '\\0';\n    ASSERT_FAIL(parse_request_uri(\n            header_with_string_opts(&header_storage, AVS_COAP_OPTION_URI_PATH,\n                                    prefix_too_long, \"1\", NULL),\n            &is_bs, &uri));\n\n    // reserved \"bs\" prefix -----------------------------------\n    // allowed at this point, but assume it's prefix, not a BS request\n    ASSERT_OK(parse_request_uri(\n            header_with_string_opts(&header_storage, AVS_COAP_OPTION_URI_PATH,\n                                    \"bs\", \"1\", NULL),\n            &is_bs, &uri));\n    ASSERT_FALSE(is_bs);\n    expected_path = MAKE_OBJECT_PATH_WITH_PREFIX(\"bs\", 1);\n    ASSERT_TRUE(_anjay_uri_path_equal(&uri, &expected_path));\n\n    ASSERT_OK(parse_request_uri(\n            header_with_string_opts(&header_storage, AVS_COAP_OPTION_URI_PATH,\n                                    \"bsprefix\", \"1\", NULL),\n            &is_bs, &uri));\n    ASSERT_FALSE(is_bs);\n    ASSERT_TRUE(_anjay_uri_path_prefix_is(&uri, \"bsprefix\"));\n}\n#endif // ANJAY_WITH_LWM2M_GATEWAY\n\nAVS_UNIT_TEST(parse_headers, parse_action) {\n    anjay_request_t request;\n    memset(&request, 0, sizeof(request));\n    request.content_format = AVS_COAP_FORMAT_NONE;\n    request.request_code = AVS_COAP_CODE_GET;\n\n    ASSERT_OK(parse_action(&(const avs_coap_request_header_t) {\n                               .code = AVS_COAP_CODE_GET\n                           },\n                           &request));\n    ASSERT_EQ(request.action, ANJAY_ACTION_READ);\n\n    request.request_code = AVS_COAP_CODE_GET;\n    char option_buffer[128];\n    avs_coap_request_header_t header_with_accept = {\n        .code = AVS_COAP_CODE_GET,\n        .options = avs_coap_options_create_empty(option_buffer,\n                                                 sizeof(option_buffer))\n    };\n    ASSERT_OK(avs_coap_options_add_u16(&header_with_accept.options,\n                                       AVS_COAP_OPTION_ACCEPT,\n                                       AVS_COAP_FORMAT_LINK_FORMAT));\n    ASSERT_OK(parse_action(&header_with_accept, &request));\n    ASSERT_EQ(request.action, ANJAY_ACTION_DISCOVER);\n\n    request.request_code = AVS_COAP_CODE_POST;\n    request.uri = MAKE_RESOURCE_PATH(0, 0, 0);\n    ASSERT_OK(parse_action(&(const avs_coap_request_header_t) {\n                               .code = AVS_COAP_CODE_GET\n                           },\n                           &request));\n    ASSERT_EQ(request.action, ANJAY_ACTION_EXECUTE);\n\n    request.request_code = AVS_COAP_CODE_POST;\n    request.uri = MAKE_OBJECT_PATH(0);\n    request.content_format = AVS_COAP_FORMAT_PLAINTEXT;\n    ASSERT_OK(parse_action(&(const avs_coap_request_header_t) {\n                               .code = AVS_COAP_CODE_GET\n                           },\n                           &request));\n    ASSERT_EQ(request.action, ANJAY_ACTION_CREATE);\n\n    request.request_code = AVS_COAP_CODE_POST;\n    request.uri = MAKE_INSTANCE_PATH(0, 0);\n    request.content_format = AVS_COAP_FORMAT_OMA_LWM2M_TLV;\n    ASSERT_OK(parse_action(&(const avs_coap_request_header_t) {\n                               .code = AVS_COAP_CODE_GET\n                           },\n                           &request));\n    ASSERT_EQ(request.action, ANJAY_ACTION_WRITE_UPDATE);\n\n    request.request_code = AVS_COAP_CODE_PUT;\n    request.content_format = AVS_COAP_FORMAT_NONE;\n    ASSERT_OK(parse_action(&(const avs_coap_request_header_t) {\n                               .code = AVS_COAP_CODE_GET\n                           },\n                           &request));\n    ASSERT_EQ(request.action, ANJAY_ACTION_WRITE_ATTRIBUTES);\n\n    request.request_code = AVS_COAP_CODE_PUT;\n    request.content_format = AVS_COAP_FORMAT_PLAINTEXT;\n    ASSERT_OK(parse_action(&(const avs_coap_request_header_t) {\n                               .code = AVS_COAP_CODE_GET\n                           },\n                           &request));\n    ASSERT_EQ(request.action, ANJAY_ACTION_WRITE);\n\n    request.request_code = AVS_COAP_CODE_DELETE;\n    ASSERT_OK(parse_action(&(const avs_coap_request_header_t) {\n                               .code = AVS_COAP_CODE_GET\n                           },\n                           &request));\n    ASSERT_EQ(request.action, ANJAY_ACTION_DELETE);\n\n    request.request_code = AVS_COAP_CODE_NOT_FOUND;\n    ASSERT_FAIL(parse_action(&(const avs_coap_request_header_t) {\n                                 .code = AVS_COAP_CODE_GET\n                             },\n                             &request));\n}\n\nAVS_UNIT_TEST(queue_mode, change) {\n    DM_TEST_INIT_WITH_OBJECTS(&OBJ, &FAKE_SECURITY2, &FAKE_SERVER);\n    anjay_server_connection_t *connection = NULL;\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    connection = _anjay_get_server_connection((const anjay_connection_ref_t) {\n        .server = anjay_unlocked->servers,\n        .conn_type = ANJAY_CONNECTION_PRIMARY\n    });\n    ANJAY_MUTEX_UNLOCK(anjay);\n    ASSERT_NOT_NULL(connection);\n    ////// WRITE NEW BINDING //////\n    // Write to Binding - dummy data to assert it is actually queried via Read\n    DM_TEST_REQUEST(mocksocks[0], CON, PUT, ID(0xFA3E), PATH(\"1\", \"1\", \"7\"),\n                    CONTENT_FORMAT(PLAINTEXT), PAYLOAD(\"dummy\"));\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &FAKE_SERVER, 0,\n            (const anjay_iid_t[]) { 1, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &FAKE_SERVER, 1, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { ANJAY_DM_RID_SERVER_SSID, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_PRESENT },\n                    { ANJAY_DM_RID_SERVER_LIFETIME, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT },\n                    { ANJAY_DM_RID_SERVER_DEFAULT_PMIN, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT },\n                    { ANJAY_DM_RID_SERVER_DEFAULT_PMAX, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT },\n                    { ANJAY_DM_RID_SERVER_NOTIFICATION_STORING, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT },\n                    { ANJAY_DM_RID_SERVER_BINDING, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_write(anjay, &FAKE_SERVER, 1,\n                                         ANJAY_DM_RID_SERVER_BINDING,\n                                         ANJAY_ID_INVALID,\n                                         ANJAY_MOCK_DM_STRING(0, \"dummy\"), 0);\n    // SSID will be read afterwards, twice (second time for attr_storage)\n    for (size_t i = 0; i < 2; ++i) {\n        if (i == 1) {\n            _anjay_mock_dm_expect_list_instances(\n                    anjay, &FAKE_SERVER, 0,\n                    (const anjay_iid_t[]) { 1, ANJAY_ID_INVALID });\n        }\n        _anjay_mock_dm_expect_list_resources(\n                anjay, &FAKE_SERVER, 1, 0,\n                (const anjay_mock_dm_res_entry_t[]) {\n                        { ANJAY_DM_RID_SERVER_SSID, ANJAY_DM_RES_R,\n                          ANJAY_DM_RES_PRESENT },\n                        { ANJAY_DM_RID_SERVER_LIFETIME, ANJAY_DM_RES_RW,\n                          ANJAY_DM_RES_ABSENT },\n                        { ANJAY_DM_RID_SERVER_DEFAULT_PMIN, ANJAY_DM_RES_RW,\n                          ANJAY_DM_RES_ABSENT },\n                        { ANJAY_DM_RID_SERVER_DEFAULT_PMAX, ANJAY_DM_RES_RW,\n                          ANJAY_DM_RES_ABSENT },\n                        { ANJAY_DM_RID_SERVER_NOTIFICATION_STORING,\n                          ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                        { ANJAY_DM_RID_SERVER_BINDING, ANJAY_DM_RES_RW,\n                          ANJAY_DM_RES_ABSENT },\n                        ANJAY_MOCK_DM_RES_END });\n        _anjay_mock_dm_expect_resource_read(anjay, &FAKE_SERVER, 1,\n                                            ANJAY_DM_RID_SERVER_SSID,\n                                            ANJAY_ID_INVALID, 0,\n                                            ANJAY_MOCK_DM_INT(0, 1));\n    }\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, CHANGED, ID(0xFA3E), NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    ASSERT_OK(anjay_serve(anjay, mocksocks[0]));\n\n    {\n        AVS_LIST(avs_net_socket_t *const) sockets = anjay_get_sockets(anjay);\n        ASSERT_NOT_NULL(sockets);\n        ASSERT_EQ(AVS_LIST_SIZE(sockets), 1);\n        avs_net_socket_t *socket = *sockets;\n\n        AVS_LIST(const anjay_socket_entry_t) entries =\n                anjay_get_socket_entries(anjay);\n        ASSERT_NOT_NULL(entries);\n        ASSERT_EQ(AVS_LIST_SIZE(entries), 1);\n        ASSERT_TRUE(entries->socket == socket);\n        ASSERT_EQ(entries->transport, ANJAY_SOCKET_TRANSPORT_UDP);\n        ASSERT_EQ(entries->ssid, 1);\n        ASSERT_FALSE(entries->queue_mode);\n    }\n#ifndef ANJAY_WITHOUT_QUEUE_MODE_AUTOCLOSE\n    ASSERT_NULL(connection->queue_mode_close_socket_clb);\n#endif // ANJAY_WITHOUT_QUEUE_MODE_AUTOCLOSE\n\n    ////// REFRESH BINDING MODE //////\n    // query SSID in Server\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &FAKE_SERVER, 0,\n            (const anjay_iid_t[]) { 1, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &FAKE_SERVER, 1, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { ANJAY_DM_RID_SERVER_SSID, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_PRESENT },\n                    { ANJAY_DM_RID_SERVER_LIFETIME, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_PRESENT },\n                    { ANJAY_DM_RID_SERVER_DEFAULT_PMIN, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT },\n                    { ANJAY_DM_RID_SERVER_DEFAULT_PMAX, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT },\n                    { ANJAY_DM_RID_SERVER_NOTIFICATION_STORING, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT },\n                    { ANJAY_DM_RID_SERVER_BINDING, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_PRESENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read(anjay, &FAKE_SERVER, 1,\n                                        ANJAY_DM_RID_SERVER_SSID,\n                                        ANJAY_ID_INVALID, 0,\n                                        ANJAY_MOCK_DM_INT(0, 1));\n    // get Binding\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &FAKE_SERVER, 1, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { ANJAY_DM_RID_SERVER_SSID, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_PRESENT },\n                    { ANJAY_DM_RID_SERVER_LIFETIME, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_PRESENT },\n                    { ANJAY_DM_RID_SERVER_DEFAULT_PMIN, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT },\n                    { ANJAY_DM_RID_SERVER_DEFAULT_PMAX, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT },\n                    { ANJAY_DM_RID_SERVER_NOTIFICATION_STORING, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT },\n                    { ANJAY_DM_RID_SERVER_BINDING, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_PRESENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read(anjay, &FAKE_SERVER, 1,\n                                        ANJAY_DM_RID_SERVER_BINDING,\n                                        ANJAY_ID_INVALID, 0,\n                                        ANJAY_MOCK_DM_STRING(0, \"UQ\"));\n#ifdef ANJAY_WITH_LWM2M11\n    // attempt to read Preferred Transport\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &FAKE_SERVER, 1, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { ANJAY_DM_RID_SERVER_SSID, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_PRESENT },\n                    { ANJAY_DM_RID_SERVER_LIFETIME, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_PRESENT },\n                    { ANJAY_DM_RID_SERVER_DEFAULT_PMIN, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT },\n                    { ANJAY_DM_RID_SERVER_DEFAULT_PMAX, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT },\n                    { ANJAY_DM_RID_SERVER_NOTIFICATION_STORING, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT },\n                    { ANJAY_DM_RID_SERVER_BINDING, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_PRESENT },\n                    ANJAY_MOCK_DM_RES_END });\n#endif // ANJAY_WITH_LWM2M11\n    // query SSID in Security\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &FAKE_SECURITY2, 0,\n            (const anjay_iid_t[]) { 1, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &FAKE_SECURITY2, 1, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { ANJAY_DM_RID_SECURITY_SERVER_URI, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_PRESENT },\n                    { ANJAY_DM_RID_SECURITY_BOOTSTRAP, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_PRESENT },\n                    { ANJAY_DM_RID_SECURITY_MODE, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_ABSENT },\n                    { ANJAY_DM_RID_SECURITY_SSID, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_PRESENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read(anjay, &FAKE_SECURITY2, 1,\n                                        ANJAY_DM_RID_SECURITY_BOOTSTRAP,\n                                        ANJAY_ID_INVALID, 0,\n                                        ANJAY_MOCK_DM_BOOL(0, false));\n\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &FAKE_SECURITY2, 1, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { ANJAY_DM_RID_SECURITY_SERVER_URI, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_PRESENT },\n                    { ANJAY_DM_RID_SECURITY_BOOTSTRAP, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_PRESENT },\n                    { ANJAY_DM_RID_SECURITY_MODE, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_ABSENT },\n                    { ANJAY_DM_RID_SECURITY_SSID, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_PRESENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read(anjay, &FAKE_SECURITY2, 1,\n                                        ANJAY_DM_RID_SECURITY_SSID,\n                                        ANJAY_ID_INVALID, 0,\n                                        ANJAY_MOCK_DM_INT(0, 1));\n    // get URI\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &FAKE_SECURITY2, 1, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { ANJAY_DM_RID_SECURITY_SERVER_URI, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_PRESENT },\n                    { ANJAY_DM_RID_SECURITY_BOOTSTRAP, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_PRESENT },\n                    { ANJAY_DM_RID_SECURITY_MODE, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_ABSENT },\n                    { ANJAY_DM_RID_SECURITY_SSID, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_PRESENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read(\n            anjay, &FAKE_SECURITY2, 1, ANJAY_DM_RID_SECURITY_SERVER_URI,\n            ANJAY_ID_INVALID, 0, ANJAY_MOCK_DM_STRING(0, \"coap://127.0.0.1\"));\n\n    // data model for the Update message - just fake an empty one\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &FAKE_SERVER, 0, (const anjay_iid_t[]) { ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0, (const anjay_iid_t[]) { ANJAY_ID_INVALID });\n    // lifetime\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &FAKE_SERVER, 0,\n            (const anjay_iid_t[]) { 1, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &FAKE_SERVER, 1, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { ANJAY_DM_RID_SERVER_SSID, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_PRESENT },\n                    { ANJAY_DM_RID_SERVER_LIFETIME, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_PRESENT },\n                    { ANJAY_DM_RID_SERVER_DEFAULT_PMIN, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT },\n                    { ANJAY_DM_RID_SERVER_DEFAULT_PMAX, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT },\n                    { ANJAY_DM_RID_SERVER_NOTIFICATION_STORING, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT },\n                    { ANJAY_DM_RID_SERVER_BINDING, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_PRESENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read(anjay, &FAKE_SERVER, 1,\n                                        ANJAY_DM_RID_SERVER_SSID,\n                                        ANJAY_ID_INVALID, 0,\n                                        ANJAY_MOCK_DM_INT(0, 1));\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &FAKE_SERVER, 1, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { ANJAY_DM_RID_SERVER_SSID, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_PRESENT },\n                    { ANJAY_DM_RID_SERVER_LIFETIME, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_PRESENT },\n                    { ANJAY_DM_RID_SERVER_DEFAULT_PMIN, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT },\n                    { ANJAY_DM_RID_SERVER_DEFAULT_PMAX, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT },\n                    { ANJAY_DM_RID_SERVER_NOTIFICATION_STORING, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT },\n                    { ANJAY_DM_RID_SERVER_BINDING, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_PRESENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read(anjay, &FAKE_SERVER, 1,\n                                        ANJAY_DM_RID_SERVER_LIFETIME,\n                                        ANJAY_ID_INVALID, 0,\n                                        ANJAY_MOCK_DM_INT(0, 9001));\n    const coap_test_msg_t *update =\n            COAP_MSG(CON, POST, ID_TOKEN_RAW(0x0000, nth_token(0)),\n                     CONTENT_FORMAT(LINK_FORMAT), QUERY(\"lt=9001\", \"b=UQ\"),\n                     PAYLOAD(\"</1>,</42>\"));\n    avs_unit_mocksock_expect_output(mocksocks[0], update->content,\n                                    update->length);\n    anjay_sched_run(anjay);\n\n    const coap_test_msg_t *update_response =\n            COAP_MSG(ACK, CHANGED, ID_TOKEN_RAW(0x0000, nth_token(0)),\n                     NO_PAYLOAD);\n    avs_unit_mocksock_input(mocksocks[0], update_response->content,\n                            update_response->length);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    ASSERT_OK(anjay_serve(anjay, mocksocks[0]));\n\n#ifndef ANJAY_WITHOUT_QUEUE_MODE_AUTOCLOSE\n    ASSERT_NOT_NULL(connection->queue_mode_close_socket_clb);\n    // After 93s from now, the socket should be closed. We first wait for 92s,\n    // ensure that the socket is still not closed, then wait one more second,\n    // and ensure that the socket got closed.\n    _anjay_mock_clock_advance(avs_time_duration_from_scalar(92, AVS_TIME_S));\n    anjay_sched_run(anjay);\n    ASSERT_NOT_NULL(connection->queue_mode_close_socket_clb);\n\n    _anjay_mock_clock_advance(avs_time_duration_from_scalar(1, AVS_TIME_S));\n    avs_unit_mocksock_expect_shutdown(mocksocks[0]);\n    anjay_sched_run(anjay);\n\n    ASSERT_NULL(anjay_get_sockets(anjay));\n    ASSERT_NULL(anjay_get_socket_entries(anjay));\n    ASSERT_NULL(connection->queue_mode_close_socket_clb);\n#endif // ANJAY_WITHOUT_QUEUE_MODE_AUTOCLOSE\n\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(anjay_new, no_endpoint_name) {\n    const anjay_configuration_t configuration = {\n        .endpoint_name = NULL,\n        .in_buffer_size = 4096,\n        .out_buffer_size = 4096\n    };\n    ASSERT_NULL(anjay_new(&configuration));\n}\n\nstatic const anjay_mock_dm_res_entry_t FAKE_SECURITY_RESOURCES[] = {\n    { ANJAY_DM_RID_SECURITY_SERVER_URI, ANJAY_DM_RES_R, ANJAY_DM_RES_PRESENT },\n    { ANJAY_DM_RID_SECURITY_MODE, ANJAY_DM_RES_R, ANJAY_DM_RES_PRESENT },\n    { ANJAY_DM_RID_SECURITY_SSID, ANJAY_DM_RES_R, ANJAY_DM_RES_PRESENT },\n    ANJAY_MOCK_DM_RES_END\n};\n\nstatic const anjay_mock_dm_res_entry_t FAKE_SERVER_RESOURCES[] = {\n    { ANJAY_DM_RID_SERVER_SSID, ANJAY_DM_RES_R, ANJAY_DM_RES_PRESENT },\n    { ANJAY_DM_RID_SERVER_LIFETIME, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n    { ANJAY_DM_RID_SERVER_BINDING, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n    ANJAY_MOCK_DM_RES_END\n};\n\ntypedef enum {\n    RECONNECT_VERIFY = 0,\n    RECONNECT_SUSPENDED,\n    RECONNECT_FULL,\n    RECONNECT_NONE\n} expect_refresh_server_reconnect_mode_t;\n\ntypedef struct {\n    int dummy;\n    expect_refresh_server_reconnect_mode_t with_reconnect;\n    anjay_ssid_t ssid;\n    size_t server_count;\n} expect_refresh_server_additional_args_t;\n\nstatic void\nexpect_refresh_server__(anjay_t *anjay,\n                        const expect_refresh_server_additional_args_t *args) {\n    anjay_ssid_t ssid = args->ssid ? args->ssid : 1;\n    size_t server_count = args->server_count ? args->server_count : 1;\n    AVS_UNIT_ASSERT_TRUE(ssid <= server_count);\n\n    anjay_iid_t fake_server_instances[server_count + 1];\n    for (size_t i = 0; i < server_count; ++i) {\n        fake_server_instances[i] = (anjay_iid_t) (i + 1);\n    }\n    fake_server_instances[server_count] = ANJAY_ID_INVALID;\n\n    _anjay_mock_dm_expect_list_instances(anjay, &FAKE_SERVER, 0,\n                                         fake_server_instances);\n    // Read SSID\n    for (anjay_ssid_t i = 1; i <= ssid; ++i) {\n        _anjay_mock_dm_expect_list_resources(anjay, &FAKE_SERVER, i, 0,\n                                             FAKE_SERVER_RESOURCES);\n        _anjay_mock_dm_expect_resource_read(anjay, &FAKE_SERVER, i,\n                                            ANJAY_DM_RID_SERVER_SSID,\n                                            ANJAY_ID_INVALID, 0,\n                                            ANJAY_MOCK_DM_INT(0, i));\n    }\n    // Read Binding\n    _anjay_mock_dm_expect_list_resources(anjay, &FAKE_SERVER, ssid, 0,\n                                         FAKE_SERVER_RESOURCES);\n    _anjay_mock_dm_expect_resource_read(anjay, &FAKE_SERVER, ssid,\n                                        ANJAY_DM_RID_SERVER_BINDING,\n                                        ANJAY_ID_INVALID, 0,\n                                        ANJAY_MOCK_DM_STRING(0, \"U\"));\n#ifdef ANJAY_WITH_LWM2M11\n    // attempt to read Preferred Transport\n    _anjay_mock_dm_expect_list_resources(anjay, &FAKE_SERVER, ssid, 0,\n                                         FAKE_SERVER_RESOURCES);\n#endif // ANJAY_WITH_LWM2M11\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &FAKE_SECURITY2, 0,\n            (const anjay_iid_t[]) { 1, ANJAY_ID_INVALID });\n    // attempt to read Bootstrap\n    _anjay_mock_dm_expect_list_resources(anjay, &FAKE_SECURITY2, 1, 0,\n                                         FAKE_SECURITY_RESOURCES);\n    // Read SSID\n    _anjay_mock_dm_expect_list_resources(anjay, &FAKE_SECURITY2, 1, 0,\n                                         FAKE_SECURITY_RESOURCES);\n    _anjay_mock_dm_expect_resource_read(anjay, &FAKE_SECURITY2, 1,\n                                        ANJAY_DM_RID_SECURITY_SSID,\n                                        ANJAY_ID_INVALID, 0,\n                                        ANJAY_MOCK_DM_INT(0, ssid));\n    // Read Server URI\n    _anjay_mock_dm_expect_list_resources(anjay, &FAKE_SECURITY2, 1, 0,\n                                         FAKE_SECURITY_RESOURCES);\n    _anjay_mock_dm_expect_resource_read(\n            anjay, &FAKE_SECURITY2, 1, ANJAY_DM_RID_SECURITY_SERVER_URI,\n            ANJAY_ID_INVALID, 0, ANJAY_MOCK_DM_STRING(0, \"coap://127.0.0.1\"));\n    if (args->with_reconnect == RECONNECT_NONE) {\n        return;\n    }\n    if (args->with_reconnect >= RECONNECT_FULL) {\n        _anjay_mock_dm_expect_list_resources(anjay, &FAKE_SECURITY2, 1, 0,\n                                             FAKE_SECURITY_RESOURCES);\n        _anjay_mock_dm_expect_resource_read(\n                anjay, &FAKE_SECURITY2, 1, ANJAY_DM_RID_SECURITY_MODE,\n                ANJAY_ID_INVALID, 0,\n                ANJAY_MOCK_DM_INT(0, ANJAY_SECURITY_NOSEC));\n    }\n    // Query the data model\n    _anjay_mock_dm_expect_list_instances(anjay, &FAKE_SERVER, 0,\n                                         fake_server_instances);\n    // attempt to read Bootstrap\n    _anjay_mock_dm_expect_list_instances(anjay, &FAKE_SERVER, 0,\n                                         fake_server_instances);\n    // Read SSID\n    for (anjay_ssid_t i = 1; i <= ssid; ++i) {\n        _anjay_mock_dm_expect_list_resources(anjay, &FAKE_SERVER, i, 0,\n                                             FAKE_SERVER_RESOURCES);\n        _anjay_mock_dm_expect_resource_read(anjay, &FAKE_SERVER, i,\n                                            ANJAY_DM_RID_SERVER_SSID,\n                                            ANJAY_ID_INVALID, 0,\n                                            ANJAY_MOCK_DM_INT(0, i));\n    }\n    // Read Lifetime\n    _anjay_mock_dm_expect_list_resources(anjay, &FAKE_SERVER, ssid, 0,\n                                         FAKE_SERVER_RESOURCES);\n    _anjay_mock_dm_expect_resource_read(anjay, &FAKE_SERVER, ssid,\n                                        ANJAY_DM_RID_SERVER_LIFETIME,\n                                        ANJAY_ID_INVALID, 0,\n                                        ANJAY_MOCK_DM_INT(0, 86400));\n}\n\n#define expect_refresh_server(...)                                             \\\n    expect_refresh_server__(AVS_VARARG0(__VA_ARGS__),                          \\\n                            &(const expect_refresh_server_additional_args_t) { \\\n                                .dummy = 0 AVS_VARARG_REST(__VA_ARGS__)        \\\n                            })\n\ntypedef struct {\n    int dummy;\n    anjay_ssid_t ssid;\n    size_t server_count;\n    uint64_t token_seed;\n} force_update_additional_args_t;\n\nstatic void force_update__(anjay_t *anjay,\n                           avs_net_socket_t *mocksock,\n                           const force_update_additional_args_t *args) {\n    anjay_ssid_t ssid = args->ssid ? args->ssid : 1;\n    size_t server_count = args->server_count ? args->server_count : 1;\n    AVS_UNIT_ASSERT_TRUE(ssid <= server_count);\n\n    AVS_UNIT_ASSERT_SUCCESS(anjay_schedule_registration_update(anjay, ssid));\n    expect_refresh_server(anjay,\n                          .ssid = ssid,\n                          .server_count = server_count);\n    avs_stream_t *payload_memstream = avs_stream_membuf_create();\n    AVS_UNIT_ASSERT_NOT_NULL(payload_memstream);\n    for (anjay_iid_t i = 1; i <= server_count; ++i) {\n        AVS_UNIT_ASSERT_SUCCESS(avs_stream_write_f(\n                payload_memstream, \"%s</1/%\" PRIu16 \">\", i > 1 ? \",\" : \"\", i));\n    }\n    void *payload = NULL;\n    size_t payload_size = 0;\n    AVS_UNIT_ASSERT_SUCCESS(avs_stream_membuf_take_ownership(\n            payload_memstream, &payload, &payload_size));\n    avs_stream_cleanup(&payload_memstream);\n    const coap_test_msg_t *update_request =\n            COAP_MSG(CON, POST,\n                     ID_TOKEN_RAW(0x0000, nth_token(args->token_seed)),\n                     CONTENT_FORMAT(LINK_FORMAT), QUERY(\"lt=86400\", \"b=U\"),\n                     PAYLOAD_EXTERNAL(payload, payload_size));\n    avs_unit_mocksock_expect_output(mocksock, update_request->content,\n                                    update_request->length);\n    anjay_sched_run(anjay);\n    avs_free(payload);\n    DM_TEST_REQUEST(mocksock, ACK, CHANGED,\n                    ID_TOKEN_RAW(0x0000, nth_token(args->token_seed)),\n                    NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksock, false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksock));\n    anjay_sched_run(anjay);\n}\n\n#define force_update(Anjay, ...)                               \\\n    force_update__((Anjay), AVS_VARARG0(__VA_ARGS__),          \\\n                   &(const force_update_additional_args_t) {   \\\n                       .dummy = 0 AVS_VARARG_REST(__VA_ARGS__) \\\n                   })\n\nAVS_UNIT_TEST(reconnect_after_update, test) {\n    DM_TEST_INIT_WITH_OBJECTS(&FAKE_SECURITY2, &FAKE_SERVER);\n    // Do an initial Update first, to update the registration expire time\n    force_update(anjay, mocksocks[0]);\n\n    AVS_UNIT_ASSERT_SUCCESS(anjay_schedule_registration_update(anjay, 1));\n    avs_unit_mocksock_expect_shutdown(mocksocks[0]);\n    AVS_UNIT_ASSERT_SUCCESS(\n            anjay_transport_schedule_reconnect(anjay, ANJAY_TRANSPORT_SET_ALL));\n\n    // ANJAY_SERVER_NEXT_ACTION_SEND_UPDATE\n    expect_refresh_server(anjay);\n    avs_unit_mocksock_expect_connect(mocksocks[0], \"\", \"\");\n    avs_unit_mocksock_expect_local_port(mocksocks[0], \"5683\");\n    avs_unit_mocksock_expect_get_opt(mocksocks[0],\n                                     AVS_NET_SOCKET_OPT_SESSION_RESUMED,\n                                     (avs_net_socket_opt_value_t) {\n                                         .flag = true\n                                     });\n    // reload_servers_sched_job\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &FAKE_SERVER, 0,\n            (const anjay_iid_t[]) { 1, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(anjay, &FAKE_SERVER, 1, 0,\n                                         FAKE_SERVER_RESOURCES);\n    _anjay_mock_dm_expect_resource_read(anjay, &FAKE_SERVER, 1,\n                                        ANJAY_DM_RID_SERVER_SSID,\n                                        ANJAY_ID_INVALID, 0,\n                                        ANJAY_MOCK_DM_INT(0, 1));\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &FAKE_SECURITY2, 0,\n            (const anjay_iid_t[]) { 1, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(anjay, &FAKE_SECURITY2, 1, 0,\n                                         (const anjay_mock_dm_res_entry_t[]) {\n                                                 ANJAY_MOCK_DM_RES_END });\n\n    // retry_or_request_expired_job\n    const coap_test_msg_t *update_request =\n            COAP_MSG(CON, POST, ID_TOKEN_RAW(0x0001, nth_token(1)), NO_PAYLOAD);\n    avs_unit_mocksock_expect_output(mocksocks[0], update_request->content,\n                                    update_request->length);\n    // ANJAY_SERVER_NEXT_ACTION_REFRESH\n    expect_refresh_server(anjay);\n    anjay_sched_run(anjay);\n\n    DM_TEST_REQUEST(mocksocks[0], ACK, CHANGED,\n                    ID_TOKEN_RAW(0x0001, nth_token(1)), NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    anjay_sched_run(anjay);\n\n    // Assert that second update is NOT scheduled\n    AVS_UNIT_ASSERT_TRUE(anjay_sched_calculate_wait_time_ms(anjay, INT_MAX)\n                         >= 1000);\n\n    DM_TEST_FINISH;\n}\n\n#ifdef ANJAY_WITH_LWM2M11\n#    define DM_REGISTER_TEST_CONFIGURATION                                    \\\n        DM_TEST_CONFIGURATION(.lwm2m_version_config = &(                      \\\n                                      const anjay_lwm2m_version_config_t) {   \\\n                                  .minimum_version = ANJAY_LWM2M_VERSION_1_0, \\\n                                  .maximum_version = ANJAY_LWM2M_VERSION_1_0  \\\n                              })\n#else // ANJAY_WITH_LWM2M11\n#    define DM_REGISTER_TEST_CONFIGURATION DM_TEST_CONFIGURATION()\n#endif // ANJAY_WITH_LWM2M11\n\n#define DM_REGISTER_TEST_INIT_WITH_SSIDS(...)                                  \\\n    const anjay_dm_object_def_t *const *obj_defs[] = { &FAKE_SECURITY2,        \\\n                                                       &FAKE_SERVER };         \\\n    anjay_ssid_t ssids[] = { __VA_ARGS__ };                                    \\\n    DM_TEST_INIT_GENERIC(obj_defs, ssids, DM_REGISTER_TEST_CONFIGURATION);     \\\n    do {                                                                       \\\n        /* Do initial Updates first to update the registration expire times */ \\\n        for (size_t _i = 0; _i < AVS_ARRAY_SIZE(ssids); ++_i) {                \\\n            force_update(anjay, mocksocks[_i],                                 \\\n                         .ssid = (anjay_ssid_t) (_i + 1),                      \\\n                         .server_count = AVS_ARRAY_SIZE(ssids),                \\\n                         .token_seed = _i);                                    \\\n        }                                                                      \\\n    } while (false)\n\nstatic void make_server_inactive(anjay_t *anjay,\n                                 anjay_ssid_t ssid,\n                                 avs_net_socket_t *mocksock) {\n    // Schedule server refresh and make it fail\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    anjay_server_info_t *server = _anjay_servers_find(anjay_unlocked, ssid);\n    AVS_UNIT_ASSERT_NOT_NULL(server);\n    _anjay_schedule_refresh_server(server, AVS_TIME_DURATION_ZERO);\n    ANJAY_MUTEX_UNLOCK(anjay);\n    _anjay_mock_dm_expect_list_instances(anjay, &FAKE_SERVER, -1,\n                                         (const anjay_iid_t[]) {\n                                                 ANJAY_ID_INVALID });\n    avs_unit_mocksock_expect_shutdown(mocksock);\n#ifdef ANJAY_WITH_NET_STATS\n    avs_unit_mocksock_expect_get_opt(mocksock, AVS_NET_SOCKET_OPT_BYTES_SENT,\n                                     (avs_net_socket_opt_value_t) {\n                                         .bytes_sent = 0\n                                     });\n    avs_unit_mocksock_expect_get_opt(mocksock,\n                                     AVS_NET_SOCKET_OPT_BYTES_RECEIVED,\n                                     (avs_net_socket_opt_value_t) {\n                                         .bytes_received = 0\n                                     });\n#endif // ANJAY_WITH_NET_STATS\n#ifdef ANJAY_WITH_LWM2M11\n    _anjay_mock_dm_expect_list_instances(anjay, &FAKE_SERVER, -1,\n                                         (const anjay_iid_t[]) {\n                                                 ANJAY_ID_INVALID });\n#endif // ANJAY_WITH_LWM2M11\n    anjay_sched_run(anjay);\n    AVS_UNIT_ASSERT_TRUE(anjay_sched_calculate_wait_time_ms(anjay, INT_MAX)\n                         >= 1000);\n}\n\nAVS_UNIT_TEST(reconnect_server, failures) {\n    DM_REGISTER_TEST_INIT_WITH_SSIDS(1);\n    // ANJAY_SSID_ANY\n    AVS_UNIT_ASSERT_FAILED(\n            anjay_server_schedule_reconnect(anjay, ANJAY_SSID_ANY));\n    // Nonexistent server\n    AVS_UNIT_ASSERT_FAILED(anjay_server_schedule_reconnect(anjay, 42));\n    // Inactive server\n    make_server_inactive(anjay, 1, mocksocks[0]);\n    AVS_UNIT_ASSERT_FAILED(anjay_server_schedule_reconnect(anjay, 1));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(reconnect_server, fresh_session) {\n    DM_REGISTER_TEST_INIT_WITH_SSIDS(1);\n    avs_unit_mocksock_expect_shutdown(mocksocks[0]);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_server_schedule_reconnect(anjay, 1));\n    expect_refresh_server(anjay,\n                          .with_reconnect = RECONNECT_SUSPENDED);\n    avs_unit_mocksock_expect_connect(mocksocks[0], \"\", \"\");\n    avs_unit_mocksock_expect_local_port(mocksocks[0], \"5683\");\n    avs_unit_mocksock_expect_get_opt(mocksocks[0],\n                                     AVS_NET_SOCKET_OPT_SESSION_RESUMED,\n                                     (avs_net_socket_opt_value_t) {\n                                         .flag = false\n                                     });\n    const coap_test_msg_t *register_request =\n            COAP_MSG(CON, POST, ID_TOKEN_RAW(0x0000, nth_token(1)),\n                     CONTENT_FORMAT(LINK_FORMAT), PATH(\"rd\"),\n                     QUERY(\"lwm2m=1.0\", \"ep=urn:dev:os:anjay-test\", \"lt=86400\"),\n                     PAYLOAD(\"</1/1>\"));\n    avs_unit_mocksock_expect_output(mocksocks[0], register_request->content,\n                                    register_request->length);\n    anjay_sched_run(anjay);\n    AVS_UNIT_ASSERT_TRUE(anjay_sched_calculate_wait_time_ms(anjay, INT_MAX)\n                         >= 1000);\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(reconnect_server, resumed_session) {\n    DM_REGISTER_TEST_INIT_WITH_SSIDS(1);\n    avs_unit_mocksock_expect_shutdown(mocksocks[0]);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_server_schedule_reconnect(anjay, 1));\n    expect_refresh_server(anjay);\n    avs_unit_mocksock_expect_connect(mocksocks[0], \"\", \"\");\n    avs_unit_mocksock_expect_local_port(mocksocks[0], \"5683\");\n    avs_unit_mocksock_expect_get_opt(mocksocks[0],\n                                     AVS_NET_SOCKET_OPT_SESSION_RESUMED,\n                                     (avs_net_socket_opt_value_t) {\n                                         .flag = true\n                                     });\n    anjay_sched_run(anjay);\n    AVS_UNIT_ASSERT_TRUE(anjay_sched_calculate_wait_time_ms(anjay, INT_MAX)\n                         >= 1000);\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(schedule_register, nonexistent) {\n    DM_REGISTER_TEST_INIT_WITH_SSIDS(1);\n    AVS_UNIT_ASSERT_FAILED(anjay_schedule_register(anjay, 42));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(schedule_register, active_server) {\n    DM_REGISTER_TEST_INIT_WITH_SSIDS(1);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_schedule_register(anjay, 1));\n    expect_refresh_server(anjay);\n    const coap_test_msg_t *register_request =\n            COAP_MSG(CON, POST, ID_TOKEN_RAW(0x0001, nth_token(1)),\n                     CONTENT_FORMAT(LINK_FORMAT), PATH(\"rd\"),\n                     QUERY(\"lwm2m=1.0\", \"ep=urn:dev:os:anjay-test\", \"lt=86400\"),\n                     PAYLOAD(\"</1/1>\"));\n    avs_unit_mocksock_expect_output(mocksocks[0], register_request->content,\n                                    register_request->length);\n    anjay_sched_run(anjay);\n    AVS_UNIT_ASSERT_TRUE(anjay_sched_calculate_wait_time_ms(anjay, INT_MAX)\n                         >= 1000);\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(schedule_register, reconnect_and_register) {\n    DM_REGISTER_TEST_INIT_WITH_SSIDS(1);\n    avs_unit_mocksock_expect_shutdown(mocksocks[0]);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_server_schedule_reconnect(anjay, 1));\n    AVS_UNIT_ASSERT_SUCCESS(anjay_schedule_register(anjay, 1));\n    expect_refresh_server(anjay);\n    avs_unit_mocksock_expect_connect(mocksocks[0], \"\", \"\");\n    avs_unit_mocksock_expect_local_port(mocksocks[0], \"5683\");\n    avs_unit_mocksock_expect_get_opt(mocksocks[0],\n                                     AVS_NET_SOCKET_OPT_SESSION_RESUMED,\n                                     (avs_net_socket_opt_value_t) {\n                                         .flag = true\n                                     });\n    const coap_test_msg_t *register_request =\n            COAP_MSG(CON, POST, ID_TOKEN_RAW(0x0001, nth_token(1)),\n                     CONTENT_FORMAT(LINK_FORMAT), PATH(\"rd\"),\n                     QUERY(\"lwm2m=1.0\", \"ep=urn:dev:os:anjay-test\", \"lt=86400\"),\n                     PAYLOAD(\"</1/1>\"));\n    avs_unit_mocksock_expect_output(mocksocks[0], register_request->content,\n                                    register_request->length);\n    anjay_sched_run(anjay);\n    AVS_UNIT_ASSERT_TRUE(anjay_sched_calculate_wait_time_ms(anjay, INT_MAX)\n                         >= 1000);\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(schedule_register, register_and_reconnect) {\n    DM_REGISTER_TEST_INIT_WITH_SSIDS(1);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_schedule_register(anjay, 1));\n    avs_unit_mocksock_expect_shutdown(mocksocks[0]);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_server_schedule_reconnect(anjay, 1));\n    expect_refresh_server(anjay);\n    avs_unit_mocksock_expect_connect(mocksocks[0], \"\", \"\");\n    avs_unit_mocksock_expect_local_port(mocksocks[0], \"5683\");\n    avs_unit_mocksock_expect_get_opt(mocksocks[0],\n                                     AVS_NET_SOCKET_OPT_SESSION_RESUMED,\n                                     (avs_net_socket_opt_value_t) {\n                                         .flag = true\n                                     });\n    const coap_test_msg_t *register_request =\n            COAP_MSG(CON, POST, ID_TOKEN_RAW(0x0001, nth_token(1)),\n                     CONTENT_FORMAT(LINK_FORMAT), PATH(\"rd\"),\n                     QUERY(\"lwm2m=1.0\", \"ep=urn:dev:os:anjay-test\", \"lt=86400\"),\n                     PAYLOAD(\"</1/1>\"));\n    avs_unit_mocksock_expect_output(mocksocks[0], register_request->content,\n                                    register_request->length);\n    anjay_sched_run(anjay);\n    AVS_UNIT_ASSERT_TRUE(anjay_sched_calculate_wait_time_ms(anjay, INT_MAX)\n                         >= 1000);\n    DM_TEST_FINISH;\n}\n\nstatic avs_net_socket_t **recreate_mocksock_once_ptr;\n\nstatic avs_error_t\nrecreate_udp_mocksock_once(avs_net_socket_t **socket,\n                           const avs_net_socket_configuration_t *config) {\n    (void) config;\n    AVS_UNIT_ASSERT_NULL(*socket);\n    *recreate_mocksock_once_ptr = _anjay_test_dm_create_socket(false);\n    *socket = *recreate_mocksock_once_ptr;\n    AVS_UNIT_MOCK(avs_net_udp_socket_create) = NULL;\n    recreate_mocksock_once_ptr = NULL;\n    avs_unit_mocksock_expect_connect(*socket, \"127.0.0.1\", \"5683\");\n    avs_unit_mocksock_expect_local_port(*socket, \"5683\");\n    avs_unit_mocksock_expect_get_opt(*socket,\n                                     AVS_NET_SOCKET_OPT_SESSION_RESUMED,\n                                     (avs_net_socket_opt_value_t) {\n                                         .flag = true\n                                     });\n    return AVS_OK;\n}\n\nAVS_UNIT_TEST(schedule_register,\n              inactive_server_doesnt_automatically_reregister) {\n    DM_REGISTER_TEST_INIT_WITH_SSIDS(1);\n    make_server_inactive(anjay, 1, mocksocks[0]);\n    AVS_UNIT_ASSERT_TRUE(anjay_sched_calculate_wait_time_ms(anjay, INT_MAX)\n                         >= 1000);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_enable_server(anjay, 1));\n    expect_refresh_server(anjay,\n                          .with_reconnect = RECONNECT_FULL);\n    recreate_mocksock_once_ptr = &mocksocks[0];\n    AVS_UNIT_MOCK(avs_net_udp_socket_create) = recreate_udp_mocksock_once;\n    anjay_sched_run(anjay);\n    AVS_UNIT_ASSERT_EQUAL(AVS_UNIT_MOCK_INVOCATIONS(avs_net_udp_socket_create),\n                          1);\n    anjay_sched_run(anjay);\n    AVS_UNIT_ASSERT_TRUE(anjay_sched_calculate_wait_time_ms(anjay, INT_MAX)\n                         >= 1000);\n    DM_TEST_FINISH;\n}\n\nstatic avs_error_t recreate_udp_mocksock_once_and_expect_register(\n        avs_net_socket_t **socket,\n        const avs_net_socket_configuration_t *config) {\n    AVS_UNIT_ASSERT_SUCCESS(recreate_udp_mocksock_once(socket, config));\n    const coap_test_msg_t *register_request =\n            COAP_MSG(CON, POST, ID_TOKEN_RAW(0x0000, nth_token(1)),\n                     CONTENT_FORMAT(LINK_FORMAT), PATH(\"rd\"),\n                     QUERY(\"lwm2m=1.0\", \"ep=urn:dev:os:anjay-test\", \"lt=86400\"),\n                     PAYLOAD(\"</1/1>\"));\n    avs_unit_mocksock_expect_output(*socket, register_request->content,\n                                    register_request->length);\n    return AVS_OK;\n}\n\nAVS_UNIT_TEST(schedule_register, inactive_server) {\n    DM_REGISTER_TEST_INIT_WITH_SSIDS(1);\n    make_server_inactive(anjay, 1, mocksocks[0]);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_schedule_register(anjay, 1));\n    AVS_UNIT_ASSERT_TRUE(anjay_sched_calculate_wait_time_ms(anjay, INT_MAX)\n                         >= 1000);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_enable_server(anjay, 1));\n    expect_refresh_server(anjay,\n                          .with_reconnect = RECONNECT_FULL);\n    recreate_mocksock_once_ptr = &mocksocks[0];\n    AVS_UNIT_MOCK(avs_net_udp_socket_create) =\n            recreate_udp_mocksock_once_and_expect_register;\n    anjay_sched_run(anjay);\n    AVS_UNIT_ASSERT_TRUE(anjay_sched_calculate_wait_time_ms(anjay, INT_MAX)\n                         >= 1000);\n    AVS_UNIT_ASSERT_EQUAL(AVS_UNIT_MOCK_INVOCATIONS(avs_net_udp_socket_create),\n                          1);\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(schedule_register, inactive_server_enable_first) {\n    DM_REGISTER_TEST_INIT_WITH_SSIDS(1);\n    make_server_inactive(anjay, 1, mocksocks[0]);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_enable_server(anjay, 1));\n    AVS_UNIT_ASSERT_SUCCESS(anjay_schedule_register(anjay, 1));\n    expect_refresh_server(anjay,\n                          .with_reconnect = RECONNECT_FULL);\n    recreate_mocksock_once_ptr = &mocksocks[0];\n    AVS_UNIT_MOCK(avs_net_udp_socket_create) =\n            recreate_udp_mocksock_once_and_expect_register;\n    anjay_sched_run(anjay);\n    AVS_UNIT_ASSERT_TRUE(anjay_sched_calculate_wait_time_ms(anjay, INT_MAX)\n                         >= 1000);\n    AVS_UNIT_ASSERT_EQUAL(AVS_UNIT_MOCK_INVOCATIONS(avs_net_udp_socket_create),\n                          1);\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(schedule_register, all_servers) {\n    DM_REGISTER_TEST_INIT_WITH_SSIDS(1, 2);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_schedule_register(anjay, ANJAY_SSID_ANY));\n    expect_refresh_server(anjay,\n                          .ssid = 1,\n                          .server_count = 2);\n    expect_refresh_server(anjay,\n                          .ssid = 2,\n                          .server_count = 2);\n    const coap_test_msg_t *register_request1 =\n            COAP_MSG(CON, POST, ID_TOKEN_RAW(0x0001, nth_token(2)),\n                     CONTENT_FORMAT(LINK_FORMAT), PATH(\"rd\"),\n                     QUERY(\"lwm2m=1.0\", \"ep=urn:dev:os:anjay-test\", \"lt=86400\"),\n                     PAYLOAD(\"</1/1>,</1/2>\"));\n    avs_unit_mocksock_expect_output(mocksocks[0], register_request1->content,\n                                    register_request1->length);\n    const coap_test_msg_t *register_request2 =\n            COAP_MSG(CON, POST, ID_TOKEN_RAW(0x0001, nth_token(3)),\n                     CONTENT_FORMAT(LINK_FORMAT), PATH(\"rd\"),\n                     QUERY(\"lwm2m=1.0\", \"ep=urn:dev:os:anjay-test\", \"lt=86400\"),\n                     PAYLOAD(\"</1/1>,</1/2>\"));\n    avs_unit_mocksock_expect_output(mocksocks[1], register_request2->content,\n                                    register_request2->length);\n    anjay_sched_run(anjay);\n    AVS_UNIT_ASSERT_TRUE(anjay_sched_calculate_wait_time_ms(anjay, INT_MAX)\n                         >= 1000);\n    DM_TEST_FINISH;\n}\n"
  },
  {
    "path": "tests/core/attr_storage/attr_storage.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <avsystem/commons/avs_stream_inbuf.h>\n#include <avsystem/commons/avs_unit_test.h>\n\n#include <string.h>\n\n#include <anjay/attr_storage.h>\n#include <anjay/core.h>\n\n#include <anjay_modules/anjay_dm_utils.h>\n#include <anjay_modules/dm/anjay_execute.h>\n\n#include \"attr_storage_test.h\"\n#include \"src/core/dm/anjay_dm_attributes.h\"\n#include \"tests/utils/dm.h\"\n\n//// PASSIVE PROXY HANDLERS ////////////////////////////////////////////////////\n\nstatic const anjay_dm_object_def_t *const OBJ2 = &(\n        const anjay_dm_object_def_t) {\n    .oid = 69,\n    .handlers = {\n        .list_instances = _anjay_mock_dm_list_instances,\n        .instance_create = _anjay_mock_dm_instance_create,\n        .instance_remove = _anjay_mock_dm_instance_remove,\n        .list_resources = _anjay_mock_dm_list_resources,\n        .resource_read = _anjay_mock_dm_resource_read,\n        .resource_write = _anjay_mock_dm_resource_write,\n        .resource_execute = _anjay_mock_dm_resource_execute,\n        .list_resource_instances = _anjay_mock_dm_list_resource_instances\n    }\n};\n\n#define DM_ATTR_STORAGE_TEST_INIT                                          \\\n    DM_TEST_INIT_WITH_OBJECTS(&OBJ, &OBJ2, &FAKE_SECURITY2, &FAKE_SERVER); \\\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);                               \\\n    _anjay_dm_transaction_begin(anjay_unlocked);                           \\\n    ANJAY_MUTEX_UNLOCK(anjay);                                             \\\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay)\n\n#define DM_ATTR_STORAGE_TEST_FINISH                                           \\\n    (void) mocksocks;                                                         \\\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_dm_transaction_finish(anjay_unlocked, 0)); \\\n    ANJAY_MUTEX_UNLOCK(anjay);                                                \\\n    DM_TEST_FINISH\n\n#ifdef ANJAY_WITH_THREAD_SAFETY\n#    define WRAP_OBJ_PTR(ObjPtr)                                              \\\n        ({                                                                    \\\n            const anjay_dm_installed_object_t *installed_obj =                \\\n                    _anjay_dm_find_object_by_oid(                             \\\n                            _anjay_get_dm(anjay_unlocked), (*(ObjPtr))->oid); \\\n            AVS_UNIT_ASSERT_NOT_NULL(installed_obj);                          \\\n            AVS_UNIT_ASSERT_TRUE(installed_obj->type                          \\\n                                 == ANJAY_DM_OBJECT_USER_PROVIDED);           \\\n            AVS_UNIT_ASSERT_TRUE(installed_obj->impl.user_provided            \\\n                                 == (ObjPtr));                                \\\n            installed_obj;                                                    \\\n        })\n#else // ANJAY_WITH_THREAD_SAFETY\n#    define WRAP_OBJ_PTR(ObjPtr)               \\\n        &(const anjay_dm_installed_object_t) { \\\n            (ObjPtr)                           \\\n        }\n#endif // ANJAY_WITH_THREAD_SAFETY\n\n#pragma GCC poison anjay_attr_storage_is_modified\n\nAVS_UNIT_TEST(attr_storage, instance_create) {\n    DM_ATTR_STORAGE_TEST_INIT;\n    _anjay_mock_dm_expect_instance_create(anjay, &OBJ, 42, 0);\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_dm_call_instance_create(\n            anjay_unlocked, WRAP_OBJ_PTR(&OBJ), 42));\n    _anjay_mock_dm_expect_instance_create(anjay, &OBJ, 0, -42);\n    AVS_UNIT_ASSERT_EQUAL(_anjay_dm_call_instance_create(anjay_unlocked,\n                                                         WRAP_OBJ_PTR(&OBJ), 0),\n                          -42);\n    AVS_UNIT_ASSERT_FALSE(anjay_unlocked->attr_storage.modified_since_persist);\n    DM_ATTR_STORAGE_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(attr_storage, resource_read) {\n    DM_ATTR_STORAGE_TEST_INIT;\n    _anjay_mock_dm_expect_resource_read(anjay, &OBJ, 514, 42, ANJAY_ID_INVALID,\n                                        0, ANJAY_MOCK_DM_NONE);\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_dm_call_resource_read(anjay_unlocked, WRAP_OBJ_PTR(&OBJ),\n                                         514, 42, ANJAY_ID_INVALID, NULL));\n    _anjay_mock_dm_expect_resource_read(anjay, &OBJ, 69, 14, ANJAY_ID_INVALID,\n                                        -7, ANJAY_MOCK_DM_NONE);\n    AVS_UNIT_ASSERT_EQUAL(\n            _anjay_dm_call_resource_read(anjay_unlocked, WRAP_OBJ_PTR(&OBJ), 69,\n                                         14, ANJAY_ID_INVALID, NULL),\n            -7);\n    AVS_UNIT_ASSERT_FALSE(anjay_unlocked->attr_storage.modified_since_persist);\n    DM_ATTR_STORAGE_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(attr_storage, resource_write) {\n    DM_ATTR_STORAGE_TEST_INIT;\n    _anjay_mock_dm_expect_resource_write(anjay, &OBJ, 514, 42, ANJAY_ID_INVALID,\n                                         ANJAY_MOCK_DM_NONE, 0);\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_dm_call_resource_write(anjay_unlocked, WRAP_OBJ_PTR(&OBJ),\n                                          514, 42, ANJAY_ID_INVALID, NULL));\n    _anjay_mock_dm_expect_resource_write(anjay, &OBJ, 69, 14, ANJAY_ID_INVALID,\n                                         ANJAY_MOCK_DM_NONE, -7);\n    AVS_UNIT_ASSERT_EQUAL(\n            _anjay_dm_call_resource_write(anjay_unlocked, WRAP_OBJ_PTR(&OBJ),\n                                          69, 14, ANJAY_ID_INVALID, NULL),\n            -7);\n    AVS_UNIT_ASSERT_FALSE(anjay_unlocked->attr_storage.modified_since_persist);\n    DM_ATTR_STORAGE_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(attr_storage, resource_execute) {\n    DM_ATTR_STORAGE_TEST_INIT;\n    avs_stream_inbuf_t null_stream = AVS_STREAM_INBUF_STATIC_INITIALIZER;\n    anjay_unlocked_execute_ctx_t *ctx =\n            _anjay_execute_ctx_create((avs_stream_t *) &null_stream);\n    AVS_UNIT_ASSERT_NOT_NULL(ctx);\n    _anjay_mock_dm_expect_resource_execute(anjay, &OBJ, 514, 42, NULL, 0);\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_dm_call_resource_execute(\n            anjay_unlocked, WRAP_OBJ_PTR(&OBJ), 514, 42, ctx));\n    _anjay_mock_dm_expect_resource_execute(anjay, &OBJ, 69, 14, NULL, -7);\n    AVS_UNIT_ASSERT_EQUAL(_anjay_dm_call_resource_execute(anjay_unlocked,\n                                                          WRAP_OBJ_PTR(&OBJ),\n                                                          69, 14, ctx),\n                          -7);\n    _anjay_execute_ctx_destroy(&ctx);\n    AVS_UNIT_ASSERT_FALSE(anjay_unlocked->attr_storage.modified_since_persist);\n    DM_ATTR_STORAGE_TEST_FINISH;\n}\n\n//// NOTIFICATION HANDLING /////////////////////////////////////////////////////\n\nAVS_UNIT_TEST(attr_storage, as_notify_callback_1) {\n    DM_ATTR_STORAGE_TEST_INIT;\n\n    // prepare initial state\n    AVS_LIST_APPEND(\n            &anjay_unlocked->attr_storage.objects,\n            test_object_entry(\n                    42,\n                    NULL,\n                    test_instance_entry(\n                            1,\n                            test_default_attrlist(\n                                    test_default_attrs(\n                                            0, 2, 514,\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n#ifdef ANJAY_WITH_LWM2M12\n                                            3,\n#endif // ANJAY_WITH_LWM2M12\n                                            ANJAY_DM_CON_ATTR_NONE),\n                                    test_default_attrs(\n                                            4, 1, ANJAY_ATTRIB_INTEGER_NONE,\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n#ifdef ANJAY_WITH_LWM2M12\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n#endif // ANJAY_WITH_LWM2M12\n                                            ANJAY_DM_CON_ATTR_NONE),\n                                    NULL),\n                            test_resource_entry(\n                                    3,\n                                    test_resource_attrs(\n                                            1, ANJAY_ATTRIB_INTEGER_NONE,\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n#ifdef ANJAY_WITH_LWM2M12\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n#endif // ANJAY_WITH_LWM2M12\n                                            42.0, 14.0, 3.0,\n#ifdef ANJAY_WITH_LWM2M12\n                                            ANJAY_DM_EDGE_ATTR_FALLING,\n#endif // ANJAY_WITH_LWM2M12\n                                            ANJAY_DM_CON_ATTR_NONE),\n                                    NULL),\n                            test_resource_entry(7, NULL),\n                            NULL),\n                    test_instance_entry(\n                            2,\n                            test_default_attrlist(\n                                    test_default_attrs(\n                                            0, 42, 44,\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n#ifdef ANJAY_WITH_LWM2M12\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n#endif // ANJAY_WITH_LWM2M12\n                                            ANJAY_DM_CON_ATTR_NONE),\n                                    test_default_attrs(\n                                            7, 33, 888,\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n#ifdef ANJAY_WITH_LWM2M12\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n#endif // ANJAY_WITH_LWM2M12\n                                            ANJAY_DM_CON_ATTR_NONE),\n                                    NULL),\n                            test_resource_entry(2, NULL),\n                            test_resource_entry(\n                                    4,\n                                    test_resource_attrs(\n                                            4, 1, 2, ANJAY_ATTRIB_INTEGER_NONE,\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n#ifdef ANJAY_WITH_LWM2M12\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n#endif // ANJAY_WITH_LWM2M12\n                                            3.0, 4.0, 5.0,\n#ifdef ANJAY_WITH_LWM2M12\n                                            ANJAY_DM_EDGE_ATTR_FALLING,\n#endif // ANJAY_WITH_LWM2M12\n                                            ANJAY_DM_CON_ATTR_NONE),\n                                    NULL),\n                            NULL),\n                    test_instance_entry(4, NULL, NULL),\n                    test_instance_entry(7, NULL, NULL),\n                    test_instance_entry(\n                            8,\n                            test_default_attrlist(\n                                    test_default_attrs(\n                                            0, 0, 0, ANJAY_ATTRIB_INTEGER_NONE,\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n#ifdef ANJAY_WITH_LWM2M12\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n#endif // ANJAY_WITH_LWM2M12\n                                            ANJAY_DM_CON_ATTR_NONE),\n                                    NULL),\n                            test_resource_entry(3, NULL),\n                            NULL),\n                    NULL));\n    AVS_LIST_APPEND(\n            &anjay_unlocked->attr_storage.objects,\n            test_object_entry(43,\n                              NULL,\n                              test_instance_entry(\n                                      1,\n                                      test_default_attrlist(\n                                              test_default_attrs(\n                                                      4, 2, 514,\n                                                      ANJAY_ATTRIB_INTEGER_NONE,\n                                                      ANJAY_ATTRIB_INTEGER_NONE,\n#ifdef ANJAY_WITH_LWM2M12\n                                                      ANJAY_ATTRIB_INTEGER_NONE,\n#endif // ANJAY_WITH_LWM2M12\n                                                      ANJAY_DM_CON_ATTR_NONE),\n                                              NULL),\n                                      NULL),\n                              NULL));\n\n    anjay_notify_queue_t queue = NULL;\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_notify_queue_instance_set_unknown_change(\n            &queue, &MAKE_OBJECT_PATH(0)));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_notify_queue_instance_set_unknown_change(\n            &queue, &MAKE_OBJECT_PATH(42)));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_notify_queue_instance_set_unknown_change(\n            &queue, &MAKE_OBJECT_PATH(43)));\n\n    // server mapping:\n    // /0/4/10 == 7\n    // /0/7/10 == 154\n    // /0/42/10 == 4\n    // /0/514/10 == -4 (invalid)\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &FAKE_SECURITY2, 0,\n            (const anjay_iid_t[]) { 4, 7, 42, 514, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &FAKE_SECURITY2, 4, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { ANJAY_DM_RID_SECURITY_SERVER_URI, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_ABSENT },\n                    { ANJAY_DM_RID_SECURITY_BOOTSTRAP, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_ABSENT },\n                    { ANJAY_DM_RID_SECURITY_MODE, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_ABSENT },\n                    { ANJAY_DM_RID_SECURITY_SSID, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_PRESENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read(anjay, &FAKE_SECURITY2, 4, 10,\n                                        ANJAY_ID_INVALID, 0,\n                                        ANJAY_MOCK_DM_INT(0, 7));\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &FAKE_SECURITY2, 7, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { ANJAY_DM_RID_SECURITY_SERVER_URI, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_ABSENT },\n                    { ANJAY_DM_RID_SECURITY_BOOTSTRAP, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_ABSENT },\n                    { ANJAY_DM_RID_SECURITY_MODE, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_ABSENT },\n                    { ANJAY_DM_RID_SECURITY_SSID, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_PRESENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read(anjay, &FAKE_SECURITY2, 7, 10,\n                                        ANJAY_ID_INVALID, 0,\n                                        ANJAY_MOCK_DM_INT(0, 514));\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &FAKE_SECURITY2, 42, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { ANJAY_DM_RID_SECURITY_SERVER_URI, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_ABSENT },\n                    { ANJAY_DM_RID_SECURITY_BOOTSTRAP, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_ABSENT },\n                    { ANJAY_DM_RID_SECURITY_MODE, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_ABSENT },\n                    { ANJAY_DM_RID_SECURITY_SSID, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_PRESENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read(anjay, &FAKE_SECURITY2, 42, 10,\n                                        ANJAY_ID_INVALID, 0,\n                                        ANJAY_MOCK_DM_INT(0, 4));\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &FAKE_SECURITY2, 514, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { ANJAY_DM_RID_SECURITY_SERVER_URI, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_ABSENT },\n                    { ANJAY_DM_RID_SECURITY_BOOTSTRAP, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_ABSENT },\n                    { ANJAY_DM_RID_SECURITY_MODE, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_ABSENT },\n                    { ANJAY_DM_RID_SECURITY_SSID, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_PRESENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read(anjay, &FAKE_SECURITY2, 514, 10,\n                                        ANJAY_ID_INVALID, 0,\n                                        ANJAY_MOCK_DM_INT(0, -4));\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0,\n            (const anjay_iid_t[]) { 2, 3, 7, 13, 42, ANJAY_ID_INVALID });\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_attr_storage_notify(anjay_unlocked, queue));\n    _anjay_notify_clear_queue(&queue);\n\n    AVS_UNIT_ASSERT_EQUAL(AVS_LIST_SIZE(anjay_unlocked->attr_storage.objects),\n                          1);\n    assert_object_equal(\n            anjay_unlocked->attr_storage.objects,\n            test_object_entry(\n                    42,\n                    NULL,\n                    test_instance_entry(\n                            2,\n                            test_default_attrlist(\n                                    test_default_attrs(\n                                            7, 33, 888,\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n#ifdef ANJAY_WITH_LWM2M12\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n#endif // ANJAY_WITH_LWM2M12\n                                            ANJAY_DM_CON_ATTR_NONE),\n                                    NULL),\n                            test_resource_entry(\n                                    4,\n                                    test_resource_attrs(\n                                            4, 1, 2, ANJAY_ATTRIB_INTEGER_NONE,\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n#ifdef ANJAY_WITH_LWM2M12\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n#endif // ANJAY_WITH_LWM2M12\n                                            3.0, 4.0, 5.0,\n#ifdef ANJAY_WITH_LWM2M12\n                                            ANJAY_DM_EDGE_ATTR_FALLING,\n#endif // ANJAY_WITH_LWM2M12\n                                            ANJAY_DM_CON_ATTR_NONE),\n                                    NULL),\n                            NULL),\n                    NULL));\n    AVS_UNIT_ASSERT_TRUE(anjay_unlocked->attr_storage.modified_since_persist);\n\n    anjay_unlocked->attr_storage.modified_since_persist = false;\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_notify_queue_instance_set_unknown_change(\n            &queue, &MAKE_OBJECT_PATH(2)));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_attr_storage_notify(anjay_unlocked, queue));\n    AVS_UNIT_ASSERT_FALSE(anjay_unlocked->attr_storage.modified_since_persist);\n    _anjay_notify_clear_queue(&queue);\n\n    // error\n    anjay_unlocked->attr_storage.modified_since_persist = false;\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_notify_queue_instance_set_unknown_change(\n            &queue, &MAKE_OBJECT_PATH(42)));\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, -11, (const anjay_iid_t[]) { 7, ANJAY_ID_INVALID });\n    AVS_UNIT_ASSERT_FAILED(_anjay_attr_storage_notify(anjay_unlocked, queue));\n    AVS_UNIT_ASSERT_NULL(anjay_unlocked->attr_storage.objects);\n    AVS_UNIT_ASSERT_TRUE(anjay_unlocked->attr_storage.modified_since_persist);\n    _anjay_notify_clear_queue(&queue);\n\n    DM_ATTR_STORAGE_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(attr_storage, as_notify_callback_2) {\n    DM_ATTR_STORAGE_TEST_INIT;\n\n    AVS_LIST_APPEND(\n            &anjay_unlocked->attr_storage.objects,\n            test_object_entry(\n                    42,\n                    test_default_attrlist(\n                            test_default_attrs(2, 5, 6,\n                                               ANJAY_ATTRIB_INTEGER_NONE,\n                                               ANJAY_ATTRIB_INTEGER_NONE,\n#ifdef ANJAY_WITH_LWM2M12\n                                               ANJAY_ATTRIB_INTEGER_NONE,\n#endif // ANJAY_WITH_LWM2M12\n                                               ANJAY_DM_CON_ATTR_NONE),\n                            NULL),\n                    test_instance_entry(\n                            2,\n                            test_default_attrlist(\n                                    test_default_attrs(\n                                            514, 3, 4,\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n#ifdef ANJAY_WITH_LWM2M12\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n#endif // ANJAY_WITH_LWM2M12\n                                            ANJAY_DM_CON_ATTR_NONE),\n                                    NULL),\n                            test_resource_entry(\n                                    1,\n                                    test_resource_attrs(\n                                            3, 9, 10, ANJAY_ATTRIB_INTEGER_NONE,\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n#ifdef ANJAY_WITH_LWM2M12\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n#endif // ANJAY_WITH_LWM2M12\n                                            -1.0, -2.0, -3.0,\n#ifdef ANJAY_WITH_LWM2M12\n                                            ANJAY_DM_EDGE_ATTR_NONE,\n#endif // ANJAY_WITH_LWM2M12\n                                            ANJAY_DM_CON_ATTR_NONE),\n                                    NULL),\n                            NULL),\n                    test_instance_entry(\n                            4,\n                            NULL,\n                            test_resource_entry(\n                                    1,\n                                    test_resource_attrs(\n                                            2, 1, 2, ANJAY_ATTRIB_INTEGER_NONE,\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n#ifdef ANJAY_WITH_LWM2M12\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n#endif // ANJAY_WITH_LWM2M12\n                                            3.0, 4.0, 5.0,\n#ifdef ANJAY_WITH_LWM2M12\n                                            ANJAY_DM_EDGE_ATTR_NONE,\n#endif // ANJAY_WITH_LWM2M12\n                                            ANJAY_DM_CON_ATTR_NONE),\n                                    NULL),\n                            test_resource_entry(\n                                    3,\n                                    test_resource_attrs(\n                                            2, 1, 2, ANJAY_ATTRIB_INTEGER_NONE,\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n#ifdef ANJAY_WITH_LWM2M12\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n#endif // ANJAY_WITH_LWM2M12\n                                            3.0, 4.0, 5.0,\n#ifdef ANJAY_WITH_LWM2M12\n                                            ANJAY_DM_EDGE_ATTR_NONE,\n#endif // ANJAY_WITH_LWM2M12\n                                            ANJAY_DM_CON_ATTR_NONE),\n                                    NULL),\n                            test_resource_entry(\n                                    6,\n                                    test_resource_attrs(\n                                            2, 1, 2, ANJAY_ATTRIB_INTEGER_NONE,\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n#ifdef ANJAY_WITH_LWM2M12\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n#endif // ANJAY_WITH_LWM2M12\n                                            3.0, 4.0, 5.0,\n#ifdef ANJAY_WITH_LWM2M12\n                                            ANJAY_DM_EDGE_ATTR_NONE,\n#endif // ANJAY_WITH_LWM2M12\n                                            ANJAY_DM_CON_ATTR_NONE),\n                                    NULL),\n                            NULL),\n                    test_instance_entry(\n                            7,\n                            NULL,\n                            test_resource_entry(\n                                    11,\n                                    test_resource_attrs(\n                                            2, 1, 2, ANJAY_ATTRIB_INTEGER_NONE,\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n#ifdef ANJAY_WITH_LWM2M12\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n#endif // ANJAY_WITH_LWM2M12\n                                            3.0, 4.0, 5.0,\n#ifdef ANJAY_WITH_LWM2M12\n                                            ANJAY_DM_EDGE_ATTR_NONE,\n#endif // ANJAY_WITH_LWM2M12\n                                            ANJAY_DM_CON_ATTR_NONE),\n                                    NULL),\n                            test_resource_entry(\n                                    42,\n                                    test_resource_attrs(\n                                            2, 1, 2, ANJAY_ATTRIB_INTEGER_NONE,\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n#ifdef ANJAY_WITH_LWM2M12\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n#endif // ANJAY_WITH_LWM2M12\n                                            3.0, 4.0, 5.0,\n#ifdef ANJAY_WITH_LWM2M12\n                                            ANJAY_DM_EDGE_ATTR_NONE,\n#endif // ANJAY_WITH_LWM2M12\n                                            ANJAY_DM_CON_ATTR_NONE),\n                                    NULL),\n                            NULL),\n                    test_instance_entry(\n                            21,\n                            NULL,\n                            test_resource_entry(\n                                    22,\n                                    test_resource_attrs(\n                                            2, 1, 2, ANJAY_ATTRIB_INTEGER_NONE,\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n#ifdef ANJAY_WITH_LWM2M12\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n#endif // ANJAY_WITH_LWM2M12\n                                            3.0, 4.0, 5.0,\n#ifdef ANJAY_WITH_LWM2M12\n                                            ANJAY_DM_EDGE_ATTR_FALLING,\n#endif // ANJAY_WITH_LWM2M12\n                                            ANJAY_DM_CON_ATTR_NONE),\n                                    NULL),\n                            test_resource_entry(\n                                    33,\n                                    test_resource_attrs(\n                                            2, 1, 2, ANJAY_ATTRIB_INTEGER_NONE,\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n#ifdef ANJAY_WITH_LWM2M12\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n#endif // ANJAY_WITH_LWM2M12\n                                            3.0, 4.0, 5.0,\n#ifdef ANJAY_WITH_LWM2M12\n                                            ANJAY_DM_EDGE_ATTR_FALLING,\n#endif // ANJAY_WITH_LWM2M12\n                                            ANJAY_DM_CON_ATTR_NONE),\n                                    NULL),\n                            NULL),\n                    test_instance_entry(\n                            42,\n                            NULL,\n                            test_resource_entry(\n                                    17,\n                                    test_resource_attrs(\n                                            2, 1, 2, ANJAY_ATTRIB_INTEGER_NONE,\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n#ifdef ANJAY_WITH_LWM2M12\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n#endif // ANJAY_WITH_LWM2M12\n                                            3.0, 4.0, 5.0,\n#ifdef ANJAY_WITH_LWM2M12\n                                            ANJAY_DM_EDGE_ATTR_FALLING,\n#endif // ANJAY_WITH_LWM2M12\n                                            ANJAY_DM_CON_ATTR_NONE),\n                                    NULL),\n                            test_resource_entry(\n                                    69,\n                                    test_resource_attrs(\n                                            2, 1, 2, ANJAY_ATTRIB_INTEGER_NONE,\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n#ifdef ANJAY_WITH_LWM2M12\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n#endif // ANJAY_WITH_LWM2M12\n                                            3.0, 4.0, 5.0,\n#ifdef ANJAY_WITH_LWM2M12\n                                            ANJAY_DM_EDGE_ATTR_RISING,\n#endif // ANJAY_WITH_LWM2M12\n                                            ANJAY_DM_CON_ATTR_NONE),\n                                    NULL),\n                            NULL),\n                    NULL));\n\n    anjay_notify_queue_t queue = NULL;\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_notify_queue_instance_set_unknown_change(\n            &queue, &MAKE_OBJECT_PATH(1)));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_notify_queue_resource_change(\n            &queue, &MAKE_RESOURCE_PATH(42, 4, 1)));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_notify_queue_resource_change(\n            &queue, &MAKE_RESOURCE_PATH(42, 4, 6)));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_notify_queue_resource_change(\n            &queue, &MAKE_RESOURCE_PATH(42, 7, 11)));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_notify_queue_resource_change(\n            &queue, &MAKE_RESOURCE_PATH(42, 21, 22)));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_notify_queue_resource_change(\n            &queue, &MAKE_RESOURCE_PATH(42, 21, 23)));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_notify_queue_resource_change(\n            &queue, &MAKE_RESOURCE_PATH(42, 42, 42)));\n\n    // server mapping:\n    // /1/9/0 == 514\n    // /1/10/0 == 2\n    // /1/11/0 == -5 (invalid)\n    AVS_UNIT_ASSERT_FALSE(anjay_unlocked->attr_storage.modified_since_persist);\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &FAKE_SERVER, 0,\n            (const anjay_iid_t[]) { 9, 10, 11, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &FAKE_SERVER, 9, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { ANJAY_DM_RID_SERVER_SSID, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_PRESENT },\n                    { ANJAY_DM_RID_SERVER_LIFETIME, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT },\n                    { ANJAY_DM_RID_SERVER_DEFAULT_PMIN, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT },\n                    { ANJAY_DM_RID_SERVER_DEFAULT_PMAX, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT },\n                    { ANJAY_DM_RID_SERVER_NOTIFICATION_STORING, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT },\n                    { ANJAY_DM_RID_SERVER_BINDING, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read(anjay, &FAKE_SERVER, 9, 0,\n                                        ANJAY_ID_INVALID, 0,\n                                        ANJAY_MOCK_DM_INT(0, 514));\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &FAKE_SERVER, 10, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { ANJAY_DM_RID_SERVER_SSID, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_PRESENT },\n                    { ANJAY_DM_RID_SERVER_LIFETIME, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT },\n                    { ANJAY_DM_RID_SERVER_DEFAULT_PMIN, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT },\n                    { ANJAY_DM_RID_SERVER_DEFAULT_PMAX, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT },\n                    { ANJAY_DM_RID_SERVER_NOTIFICATION_STORING, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT },\n                    { ANJAY_DM_RID_SERVER_BINDING, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read(anjay, &FAKE_SERVER, 10, 0,\n                                        ANJAY_ID_INVALID, 0,\n                                        ANJAY_MOCK_DM_INT(0, 2));\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &FAKE_SERVER, 11, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { ANJAY_DM_RID_SERVER_SSID, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_PRESENT },\n                    { ANJAY_DM_RID_SERVER_LIFETIME, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT },\n                    { ANJAY_DM_RID_SERVER_DEFAULT_PMIN, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT },\n                    { ANJAY_DM_RID_SERVER_DEFAULT_PMAX, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT },\n                    { ANJAY_DM_RID_SERVER_NOTIFICATION_STORING, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT },\n                    { ANJAY_DM_RID_SERVER_BINDING, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read(anjay, &FAKE_SERVER, 11, 0,\n                                        ANJAY_ID_INVALID, 0,\n                                        ANJAY_MOCK_DM_INT(0, -5));\n    AVS_UNIT_ASSERT_FALSE(anjay_unlocked->attr_storage.modified_since_persist);\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0,\n            (const anjay_iid_t[]) { 2, 4, 7, 21, 42, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 4, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                    { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                    { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                    { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 7, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_list_resources(anjay, &OBJ, 21, -11, NULL);\n    _anjay_mock_dm_expect_list_resources(anjay, &OBJ, 42, -514, NULL);\n    AVS_UNIT_ASSERT_FAILED(_anjay_attr_storage_notify(anjay_unlocked, queue));\n    _anjay_notify_clear_queue(&queue);\n\n    AVS_UNIT_ASSERT_TRUE(anjay_unlocked->attr_storage.modified_since_persist);\n    AVS_UNIT_ASSERT_EQUAL(AVS_LIST_SIZE(anjay_unlocked->attr_storage.objects),\n                          1);\n    assert_object_equal(\n            anjay_unlocked->attr_storage.objects,\n            test_object_entry(\n                    42,\n                    test_default_attrlist(\n                            test_default_attrs(2, 5, 6,\n                                               ANJAY_ATTRIB_INTEGER_NONE,\n                                               ANJAY_ATTRIB_INTEGER_NONE,\n#ifdef ANJAY_WITH_LWM2M12\n                                               ANJAY_ATTRIB_INTEGER_NONE,\n#endif // ANJAY_WITH_LWM2M12\n                                               ANJAY_DM_CON_ATTR_NONE),\n                            NULL),\n                    test_instance_entry(\n                            2,\n                            test_default_attrlist(\n                                    test_default_attrs(\n                                            514, 3, 4,\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n#ifdef ANJAY_WITH_LWM2M12\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n#endif // ANJAY_WITH_LWM2M12\n                                            ANJAY_DM_CON_ATTR_NONE),\n                                    NULL),\n                            NULL),\n                    test_instance_entry(\n                            4,\n                            NULL,\n                            test_resource_entry(\n                                    1,\n                                    test_resource_attrs(\n                                            2, 1, 2, ANJAY_ATTRIB_INTEGER_NONE,\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n#ifdef ANJAY_WITH_LWM2M12\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n#endif // ANJAY_WITH_LWM2M12\n                                            3.0, 4.0, 5.0,\n#ifdef ANJAY_WITH_LWM2M12\n                                            ANJAY_DM_EDGE_ATTR_NONE,\n#endif // ANJAY_WITH_LWM2M12\n                                            ANJAY_DM_CON_ATTR_NONE),\n                                    NULL),\n                            test_resource_entry(\n                                    6,\n                                    test_resource_attrs(\n                                            2, 1, 2, ANJAY_ATTRIB_INTEGER_NONE,\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n#ifdef ANJAY_WITH_LWM2M12\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n#endif // ANJAY_WITH_LWM2M12\n                                            3.0, 4.0, 5.0,\n#ifdef ANJAY_WITH_LWM2M12\n                                            ANJAY_DM_EDGE_ATTR_NONE,\n#endif // ANJAY_WITH_LWM2M12\n                                            ANJAY_DM_CON_ATTR_NONE),\n                                    NULL),\n                            NULL),\n                    test_instance_entry(\n                            21,\n                            NULL,\n                            test_resource_entry(\n                                    22,\n                                    test_resource_attrs(\n                                            2, 1, 2, ANJAY_ATTRIB_INTEGER_NONE,\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n#ifdef ANJAY_WITH_LWM2M12\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n#endif // ANJAY_WITH_LWM2M12\n                                            3.0, 4.0, 5.0,\n#ifdef ANJAY_WITH_LWM2M12\n                                            ANJAY_DM_EDGE_ATTR_FALLING,\n#endif // ANJAY_WITH_LWM2M12\n                                            ANJAY_DM_CON_ATTR_NONE),\n                                    NULL),\n                            test_resource_entry(\n                                    33,\n                                    test_resource_attrs(\n                                            2, 1, 2, ANJAY_ATTRIB_INTEGER_NONE,\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n#ifdef ANJAY_WITH_LWM2M12\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n#endif // ANJAY_WITH_LWM2M12\n                                            3.0, 4.0, 5.0,\n#ifdef ANJAY_WITH_LWM2M12\n                                            ANJAY_DM_EDGE_ATTR_FALLING,\n#endif // ANJAY_WITH_LWM2M12\n                                            ANJAY_DM_CON_ATTR_NONE),\n                                    NULL),\n                            NULL),\n                    test_instance_entry(\n                            42,\n                            NULL,\n                            test_resource_entry(\n                                    17,\n                                    test_resource_attrs(\n                                            2, 1, 2, ANJAY_ATTRIB_INTEGER_NONE,\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n#ifdef ANJAY_WITH_LWM2M12\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n#endif // ANJAY_WITH_LWM2M12\n                                            3.0, 4.0, 5.0,\n#ifdef ANJAY_WITH_LWM2M12\n                                            ANJAY_DM_EDGE_ATTR_FALLING,\n#endif // ANJAY_WITH_LWM2M12\n                                            ANJAY_DM_CON_ATTR_NONE),\n                                    NULL),\n                            test_resource_entry(\n                                    69,\n                                    test_resource_attrs(\n                                            2, 1, 2, ANJAY_ATTRIB_INTEGER_NONE,\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n#ifdef ANJAY_WITH_LWM2M12\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n#endif // ANJAY_WITH_LWM2M12\n                                            3.0, 4.0, 5.0,\n#ifdef ANJAY_WITH_LWM2M12\n                                            ANJAY_DM_EDGE_ATTR_RISING,\n#endif // ANJAY_WITH_LWM2M12\n                                            ANJAY_DM_CON_ATTR_NONE),\n                                    NULL),\n                            NULL),\n                    NULL));\n\n    DM_ATTR_STORAGE_TEST_FINISH;\n}\n\n//// ATTRIBUTE HANDLERS ////////////////////////////////////////////////////////\n\nAVS_UNIT_TEST(attr_storage, read_object_default_attrs_proxy) {\n    DM_ATTR_STORAGE_TEST_INIT;\n\n    anjay_dm_oi_attributes_t attrs;\n    _anjay_mock_dm_expect_object_read_default_attrs(\n            anjay, &OBJ, 4, 0, &ANJAY_DM_OI_ATTRIBUTES_EMPTY);\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_dm_call_object_read_default_attrs(\n            anjay_unlocked, WRAP_OBJ_PTR(&OBJ), 4, &attrs));\n    assert_attrs_equal(&attrs, &ANJAY_DM_OI_ATTRIBUTES_EMPTY);\n\n    _anjay_mock_dm_expect_object_read_default_attrs(\n            anjay, &OBJ, 42, -413, &ANJAY_DM_OI_ATTRIBUTES_EMPTY);\n    AVS_UNIT_ASSERT_EQUAL(_anjay_dm_call_object_read_default_attrs(\n                                  anjay_unlocked, WRAP_OBJ_PTR(&OBJ), 42,\n                                  &attrs),\n                          -413);\n\n    _anjay_mock_dm_expect_object_read_default_attrs(\n            anjay, &OBJ, 7, 0,\n            &(const anjay_dm_oi_attributes_t) {\n                .min_period = ANJAY_ATTRIB_INTEGER_NONE,\n                .max_period = 77,\n                .min_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n                .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE\n            });\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_dm_call_object_read_default_attrs(\n            anjay_unlocked, WRAP_OBJ_PTR(&OBJ), 7, &attrs));\n    assert_attrs_equal(&attrs,\n                       &(const anjay_dm_oi_attributes_t) {\n                           .min_period = ANJAY_ATTRIB_INTEGER_NONE,\n                           .max_period = 77,\n                           .min_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n                           .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE\n                       });\n    DM_ATTR_STORAGE_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(attr_storage, write_object_default_attrs_proxy) {\n    DM_ATTR_STORAGE_TEST_INIT;\n\n    _anjay_mock_dm_expect_object_write_default_attrs(\n            anjay, &OBJ, 42,\n            &(const anjay_dm_oi_attributes_t) {\n                .min_period = 43,\n                .max_period = ANJAY_ATTRIB_INTEGER_NONE,\n                .min_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n                .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE\n            },\n            0);\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_dm_call_object_write_default_attrs(\n            anjay_unlocked, WRAP_OBJ_PTR(&OBJ), 42,\n            &(const anjay_dm_oi_attributes_t) {\n                .min_period = 43,\n                .max_period = ANJAY_ATTRIB_INTEGER_NONE,\n                .min_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n                .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE\n            }));\n\n    _anjay_mock_dm_expect_object_write_default_attrs(\n            anjay, &OBJ, 7,\n            &(const anjay_dm_oi_attributes_t) {\n                .min_period = ANJAY_ATTRIB_INTEGER_NONE,\n                .max_period = 77,\n                .min_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n                .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE\n            },\n            0);\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_dm_call_object_write_default_attrs(\n            anjay_unlocked, WRAP_OBJ_PTR(&OBJ), 7,\n            &(const anjay_dm_oi_attributes_t) {\n                .min_period = ANJAY_ATTRIB_INTEGER_NONE,\n                .max_period = 77,\n                .min_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n                .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE\n            }));\n\n    _anjay_mock_dm_expect_object_write_default_attrs(\n            anjay, &OBJ, 8,\n            &(const anjay_dm_oi_attributes_t) {\n                .min_period = 88,\n                .max_period = 888,\n                .min_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n                .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE\n            },\n            -8888);\n    AVS_UNIT_ASSERT_EQUAL(\n            _anjay_dm_call_object_write_default_attrs(\n                    anjay_unlocked, WRAP_OBJ_PTR(&OBJ), 8,\n                    &(const anjay_dm_oi_attributes_t) {\n                        .min_period = 88,\n                        .max_period = 888,\n                        .min_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n                        .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE\n                    }),\n            -8888);\n\n    _anjay_mock_dm_expect_object_write_default_attrs(\n            anjay, &OBJ, 9,\n            &(const anjay_dm_oi_attributes_t) {\n                .min_period = 4,\n                .max_period = 99,\n                .min_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n                .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE\n            },\n            0);\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_dm_call_object_write_default_attrs(\n            anjay_unlocked, WRAP_OBJ_PTR(&OBJ), 9,\n            &(const anjay_dm_oi_attributes_t) {\n                .min_period = 4,\n                .max_period = 99,\n                .min_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n                .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE\n            }));\n\n    _anjay_mock_dm_expect_object_write_default_attrs(\n            anjay, &OBJ, 9, &ANJAY_DM_OI_ATTRIBUTES_EMPTY, 0);\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_dm_call_object_write_default_attrs(\n            anjay_unlocked, WRAP_OBJ_PTR(&OBJ), 9,\n            &ANJAY_DM_OI_ATTRIBUTES_EMPTY));\n\n    _anjay_mock_dm_expect_object_write_default_attrs(\n            anjay, &OBJ, 11, &ANJAY_DM_OI_ATTRIBUTES_EMPTY, 0);\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_dm_call_object_write_default_attrs(\n            anjay_unlocked, WRAP_OBJ_PTR(&OBJ), 11,\n            &ANJAY_DM_OI_ATTRIBUTES_EMPTY));\n\n    AVS_UNIT_ASSERT_NULL(anjay_unlocked->attr_storage.objects);\n    AVS_UNIT_ASSERT_FALSE(anjay_unlocked->attr_storage.modified_since_persist);\n\n    DM_ATTR_STORAGE_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(attr_storage, object_default_attrs) {\n    DM_ATTR_STORAGE_TEST_INIT;\n    AVS_UNIT_ASSERT_FALSE(anjay_unlocked->attr_storage.modified_since_persist);\n\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_dm_call_object_write_default_attrs(\n            anjay_unlocked, WRAP_OBJ_PTR(&OBJ2), 42,\n            &(const anjay_dm_oi_attributes_t) {\n                .min_period = 43,\n                .max_period = ANJAY_ATTRIB_INTEGER_NONE,\n                .min_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n                .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE\n#ifdef ANJAY_WITH_CON_ATTR\n                ,\n                .con = ANJAY_DM_CON_ATTR_NONE\n#endif // ANJAY_WITH_CON_ATTR\n#ifdef ANJAY_WITH_LWM2M12\n                ,\n                .hqmax = ANJAY_ATTRIB_INTEGER_NONE\n#endif // ANJAY_WITH_LWM2M12\n            }));\n    AVS_UNIT_ASSERT_TRUE(anjay_unlocked->attr_storage.modified_since_persist);\n    anjay_unlocked->attr_storage.modified_since_persist = false;\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_dm_call_object_write_default_attrs(\n            anjay_unlocked, WRAP_OBJ_PTR(&OBJ2), 7,\n            &(const anjay_dm_oi_attributes_t) {\n                .min_period = ANJAY_ATTRIB_INTEGER_NONE,\n                .max_period = 77,\n                .min_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n                .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE\n#ifdef ANJAY_WITH_CON_ATTR\n                ,\n                .con = ANJAY_DM_CON_ATTR_NONE\n#endif // ANJAY_WITH_CON_ATTR\n#ifdef ANJAY_WITH_LWM2M12\n                ,\n                .hqmax = ANJAY_ATTRIB_INTEGER_NONE\n#endif // ANJAY_WITH_LWM2M12\n            }));\n    AVS_UNIT_ASSERT_TRUE(anjay_unlocked->attr_storage.modified_since_persist);\n    anjay_unlocked->attr_storage.modified_since_persist = false;\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_dm_call_object_write_default_attrs(\n            anjay_unlocked, WRAP_OBJ_PTR(&OBJ2), 8,\n            &ANJAY_DM_OI_ATTRIBUTES_EMPTY));\n    // nothing actually changed\n    AVS_UNIT_ASSERT_FALSE(anjay_unlocked->attr_storage.modified_since_persist);\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_dm_call_object_write_default_attrs(\n            anjay_unlocked, WRAP_OBJ_PTR(&OBJ2), 9,\n            &(const anjay_dm_oi_attributes_t) {\n                .min_period = 4,\n                .max_period = 99,\n                .min_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n                .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE\n#ifdef ANJAY_WITH_CON_ATTR\n                ,\n                .con = ANJAY_DM_CON_ATTR_NONE\n#endif // ANJAY_WITH_CON_ATTR\n#ifdef ANJAY_WITH_LWM2M12\n                ,\n                .hqmax = ANJAY_ATTRIB_INTEGER_NONE\n#endif // ANJAY_WITH_LWM2M12\n            }));\n    AVS_UNIT_ASSERT_TRUE(anjay_unlocked->attr_storage.modified_since_persist);\n    anjay_unlocked->attr_storage.modified_since_persist = false;\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_dm_call_object_write_default_attrs(\n            anjay_unlocked, WRAP_OBJ_PTR(&OBJ2), 11,\n            &ANJAY_DM_OI_ATTRIBUTES_EMPTY));\n    // nothing actually changed\n    AVS_UNIT_ASSERT_FALSE(anjay_unlocked->attr_storage.modified_since_persist);\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_dm_call_object_write_default_attrs(\n            anjay_unlocked, WRAP_OBJ_PTR(&OBJ2), 9,\n            &ANJAY_DM_OI_ATTRIBUTES_EMPTY));\n    AVS_UNIT_ASSERT_TRUE(anjay_unlocked->attr_storage.modified_since_persist);\n    anjay_unlocked->attr_storage.modified_since_persist = false;\n\n    assert_object_equal(\n            anjay_unlocked->attr_storage.objects,\n            test_object_entry(\n                    69,\n                    test_default_attrlist(\n                            test_default_attrs(7, ANJAY_ATTRIB_INTEGER_NONE, 77,\n                                               ANJAY_ATTRIB_INTEGER_NONE,\n                                               ANJAY_ATTRIB_INTEGER_NONE,\n#ifdef ANJAY_WITH_LWM2M12\n                                               ANJAY_ATTRIB_INTEGER_NONE,\n#endif // ANJAY_WITH_LWM2M12\n                                               ANJAY_DM_CON_ATTR_NONE),\n                            test_default_attrs(42, 43,\n                                               ANJAY_ATTRIB_INTEGER_NONE,\n                                               ANJAY_ATTRIB_INTEGER_NONE,\n                                               ANJAY_ATTRIB_INTEGER_NONE,\n#ifdef ANJAY_WITH_LWM2M12\n                                               ANJAY_ATTRIB_INTEGER_NONE,\n#endif // ANJAY_WITH_LWM2M12\n                                               ANJAY_DM_CON_ATTR_NONE),\n                            NULL),\n                    NULL));\n\n    anjay_dm_oi_attributes_t attrs;\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_dm_call_object_read_default_attrs(\n            anjay_unlocked, WRAP_OBJ_PTR(&OBJ2), 4, &attrs));\n    assert_attrs_equal(&attrs, &ANJAY_DM_OI_ATTRIBUTES_EMPTY);\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_dm_call_object_read_default_attrs(\n            anjay_unlocked, WRAP_OBJ_PTR(&OBJ2), 42, &attrs));\n    assert_attrs_equal(&attrs,\n                       &(const anjay_dm_oi_attributes_t) {\n                           .min_period = 43,\n                           .max_period = ANJAY_ATTRIB_INTEGER_NONE,\n                           .min_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n                           .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE\n#ifdef ANJAY_WITH_CON_ATTR\n                           ,\n                           .con = ANJAY_DM_CON_ATTR_NONE\n#endif // ANJAY_WITH_CON_ATTR\n#ifdef ANJAY_WITH_LWM2M12\n                           ,\n                           .hqmax = ANJAY_ATTRIB_INTEGER_NONE\n#endif // ANJAY_WITH_LWM2M12\n                       });\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_dm_call_object_read_default_attrs(\n            anjay_unlocked, WRAP_OBJ_PTR(&OBJ2), 7, &attrs));\n    assert_attrs_equal(&attrs,\n                       &(const anjay_dm_oi_attributes_t) {\n                           .min_period = ANJAY_ATTRIB_INTEGER_NONE,\n                           .max_period = 77,\n                           .min_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n                           .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE\n#ifdef ANJAY_WITH_CON_ATTR\n                           ,\n                           .con = ANJAY_DM_CON_ATTR_NONE\n#endif // ANJAY_WITH_CON_ATTR\n#ifdef ANJAY_WITH_LWM2M12\n                           ,\n                           .hqmax = ANJAY_ATTRIB_INTEGER_NONE\n#endif // ANJAY_WITH_LWM2M12\n                       });\n    AVS_UNIT_ASSERT_FALSE(anjay_unlocked->attr_storage.modified_since_persist);\n    DM_ATTR_STORAGE_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(attr_storage, read_instance_default_attrs_proxy) {\n    DM_ATTR_STORAGE_TEST_INIT;\n\n    anjay_dm_oi_attributes_t attrs;\n    _anjay_mock_dm_expect_instance_read_default_attrs(\n            anjay, &OBJ, 5, 4, 0, &ANJAY_DM_OI_ATTRIBUTES_EMPTY);\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_dm_call_instance_read_default_attrs(\n            anjay_unlocked, WRAP_OBJ_PTR(&OBJ), 5, 4, &attrs));\n    assert_attrs_equal(&attrs, &ANJAY_DM_OI_ATTRIBUTES_EMPTY);\n\n    _anjay_mock_dm_expect_instance_read_default_attrs(\n            anjay, &OBJ, 5, 42, -413, &ANJAY_DM_OI_ATTRIBUTES_EMPTY);\n    AVS_UNIT_ASSERT_EQUAL(_anjay_dm_call_instance_read_default_attrs(\n                                  anjay_unlocked, WRAP_OBJ_PTR(&OBJ), 5, 42,\n                                  &attrs),\n                          -413);\n\n    _anjay_mock_dm_expect_instance_read_default_attrs(\n            anjay, &OBJ, 7, 4, 0,\n            &(const anjay_dm_oi_attributes_t) {\n                .min_period = ANJAY_ATTRIB_INTEGER_NONE,\n                .max_period = 77,\n                .min_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n                .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE\n#ifdef ANJAY_WITH_LWM2M12\n                ,\n                .hqmax = ANJAY_ATTRIB_INTEGER_NONE\n#endif // ANJAY_WITH_LWM2M12\n            });\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_dm_call_instance_read_default_attrs(\n            anjay_unlocked, WRAP_OBJ_PTR(&OBJ), 7, 4, &attrs));\n    assert_attrs_equal(&attrs,\n                       &(const anjay_dm_oi_attributes_t) {\n                           .min_period = ANJAY_ATTRIB_INTEGER_NONE,\n                           .max_period = 77,\n                           .min_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n                           .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE\n#ifdef ANJAY_WITH_LWM2M12\n                           ,\n                           .hqmax = ANJAY_ATTRIB_INTEGER_NONE\n#endif // ANJAY_WITH_LWM2M12\n                       });\n    AVS_UNIT_ASSERT_FALSE(anjay_unlocked->attr_storage.modified_since_persist);\n    DM_ATTR_STORAGE_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(attr_storage, write_instance_default_attrs_proxy) {\n    DM_ATTR_STORAGE_TEST_INIT;\n\n    _anjay_mock_dm_expect_instance_write_default_attrs(\n            anjay, &OBJ, 4, 42,\n            &(const anjay_dm_oi_attributes_t) {\n                .min_period = 43,\n                .max_period = ANJAY_ATTRIB_INTEGER_NONE,\n                .min_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n                .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE\n#ifdef ANJAY_WITH_LWM2M12\n                ,\n                .hqmax = ANJAY_ATTRIB_INTEGER_NONE\n#endif // ANJAY_WITH_LWM2M12\n            },\n            0);\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_dm_call_instance_write_default_attrs(\n            anjay_unlocked, WRAP_OBJ_PTR(&OBJ), 4, 42,\n            &(const anjay_dm_oi_attributes_t) {\n                .min_period = 43,\n                .max_period = ANJAY_ATTRIB_INTEGER_NONE,\n                .min_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n                .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE\n#ifdef ANJAY_WITH_LWM2M12\n                ,\n                .hqmax = ANJAY_ATTRIB_INTEGER_NONE\n#endif // ANJAY_WITH_LWM2M12\n            }));\n\n    _anjay_mock_dm_expect_instance_write_default_attrs(\n            anjay, &OBJ, 4, 7,\n            &(const anjay_dm_oi_attributes_t) {\n                .min_period = ANJAY_ATTRIB_INTEGER_NONE,\n                .max_period = 77,\n                .min_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n                .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE\n#ifdef ANJAY_WITH_LWM2M12\n                ,\n                .hqmax = ANJAY_ATTRIB_INTEGER_NONE\n#endif // ANJAY_WITH_LWM2M12\n            },\n            0);\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_dm_call_instance_write_default_attrs(\n            anjay_unlocked, WRAP_OBJ_PTR(&OBJ), 4, 7,\n            &(const anjay_dm_oi_attributes_t) {\n                .min_period = ANJAY_ATTRIB_INTEGER_NONE,\n                .max_period = 77,\n                .min_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n                .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE\n#ifdef ANJAY_WITH_LWM2M12\n                ,\n                .hqmax = ANJAY_ATTRIB_INTEGER_NONE\n#endif // ANJAY_WITH_LWM2M12\n            }));\n\n    _anjay_mock_dm_expect_instance_write_default_attrs(\n            anjay, &OBJ, 8, 7,\n            &(const anjay_dm_oi_attributes_t) {\n                .min_period = 88,\n                .max_period = 888,\n                .min_eval_period = 8888,\n                .max_eval_period = 88888\n#ifdef ANJAY_WITH_LWM2M12\n                ,\n                .hqmax = 888888\n#endif // ANJAY_WITH_LWM2M12\n            },\n            -8888);\n    AVS_UNIT_ASSERT_EQUAL(_anjay_dm_call_instance_write_default_attrs(\n                                  anjay_unlocked, WRAP_OBJ_PTR(&OBJ), 8, 7,\n                                  &(const anjay_dm_oi_attributes_t) {\n                                      .min_period = 88,\n                                      .max_period = 888,\n                                      .min_eval_period = 8888,\n                                      .max_eval_period = 88888\n#ifdef ANJAY_WITH_LWM2M12\n                                      ,\n                                      .hqmax = 888888\n#endif // ANJAY_WITH_LWM2M12\n                                  }),\n                          -8888);\n\n    _anjay_mock_dm_expect_instance_write_default_attrs(\n            anjay, &OBJ, 9, 4,\n            &(const anjay_dm_oi_attributes_t) {\n                .min_period = 4,\n                .max_period = 99,\n                .min_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n                .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE\n#ifdef ANJAY_WITH_LWM2M12\n                ,\n                .hqmax = ANJAY_ATTRIB_INTEGER_NONE\n#endif // ANJAY_WITH_LWM2M12\n            },\n            0);\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_dm_call_instance_write_default_attrs(\n            anjay_unlocked, WRAP_OBJ_PTR(&OBJ), 9, 4,\n            &(const anjay_dm_oi_attributes_t) {\n                .min_period = 4,\n                .max_period = 99,\n                .min_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n                .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE\n#ifdef ANJAY_WITH_LWM2M12\n                ,\n                .hqmax = ANJAY_ATTRIB_INTEGER_NONE\n#endif // ANJAY_WITH_LWM2M12\n            }));\n\n    _anjay_mock_dm_expect_instance_write_default_attrs(\n            anjay, &OBJ, 9, 4, &ANJAY_DM_OI_ATTRIBUTES_EMPTY, 0);\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_dm_call_instance_write_default_attrs(\n            anjay_unlocked, WRAP_OBJ_PTR(&OBJ), 9, 4,\n            &ANJAY_DM_OI_ATTRIBUTES_EMPTY));\n\n    _anjay_mock_dm_expect_instance_write_default_attrs(\n            anjay, &OBJ, 11, 11, &ANJAY_DM_OI_ATTRIBUTES_EMPTY, 0);\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_dm_call_instance_write_default_attrs(\n            anjay_unlocked, WRAP_OBJ_PTR(&OBJ), 11, 11,\n            &ANJAY_DM_OI_ATTRIBUTES_EMPTY));\n\n    AVS_UNIT_ASSERT_NULL(anjay_unlocked->attr_storage.objects);\n\n    AVS_UNIT_ASSERT_FALSE(anjay_unlocked->attr_storage.modified_since_persist);\n    DM_ATTR_STORAGE_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(attr_storage, instance_default_attrs) {\n    DM_ATTR_STORAGE_TEST_INIT;\n    AVS_UNIT_ASSERT_FALSE(anjay_unlocked->attr_storage.modified_since_persist);\n\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_dm_call_instance_write_default_attrs(\n            anjay_unlocked, WRAP_OBJ_PTR(&OBJ2), 42, 2,\n            &ANJAY_DM_OI_ATTRIBUTES_EMPTY));\n    // nothing actually changed\n    AVS_UNIT_ASSERT_FALSE(anjay_unlocked->attr_storage.modified_since_persist);\n    AVS_UNIT_ASSERT_NULL(anjay_unlocked->attr_storage.objects);\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_dm_call_instance_write_default_attrs(\n            anjay_unlocked, WRAP_OBJ_PTR(&OBJ2), 3, 2,\n            &(const anjay_dm_oi_attributes_t) {\n                .min_period = 4,\n                .max_period = 9,\n                .min_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n                .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE\n#ifdef ANJAY_WITH_CON_ATTR\n                ,\n                .con = ANJAY_DM_CON_ATTR_NONE\n#endif // ANJAY_WITH_CON_ATTR\n#ifdef ANJAY_WITH_LWM2M12\n                ,\n                .hqmax = ANJAY_ATTRIB_INTEGER_NONE\n#endif // ANJAY_WITH_LWM2M12\n            }));\n    AVS_UNIT_ASSERT_TRUE(anjay_unlocked->attr_storage.modified_since_persist);\n    anjay_unlocked->attr_storage.modified_since_persist = false;\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_dm_call_instance_write_default_attrs(\n            anjay_unlocked, WRAP_OBJ_PTR(&OBJ2), 3, 5,\n            &(const anjay_dm_oi_attributes_t) {\n                .min_period = 7,\n                .max_period = 15,\n                .min_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n                .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE\n#ifdef ANJAY_WITH_CON_ATTR\n                ,\n                .con = ANJAY_DM_CON_ATTR_NONE\n#endif // ANJAY_WITH_CON_ATTR\n#ifdef ANJAY_WITH_LWM2M12\n                ,\n                .hqmax = ANJAY_ATTRIB_INTEGER_NONE\n#endif // ANJAY_WITH_LWM2M12\n            }));\n    AVS_UNIT_ASSERT_TRUE(anjay_unlocked->attr_storage.modified_since_persist);\n    anjay_unlocked->attr_storage.modified_since_persist = false;\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_dm_call_instance_write_default_attrs(\n            anjay_unlocked, WRAP_OBJ_PTR(&OBJ2), 9, 5,\n            &(const anjay_dm_oi_attributes_t) {\n                .min_period = 1,\n                .max_period = ANJAY_ATTRIB_INTEGER_NONE,\n                .min_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n                .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE\n#ifdef ANJAY_WITH_CON_ATTR\n                ,\n                .con = ANJAY_DM_CON_ATTR_NONE\n#endif // ANJAY_WITH_CON_ATTR\n#ifdef ANJAY_WITH_LWM2M12\n                ,\n                .hqmax = ANJAY_ATTRIB_INTEGER_NONE\n#endif // ANJAY_WITH_LWM2M12\n            }));\n    AVS_UNIT_ASSERT_TRUE(anjay_unlocked->attr_storage.modified_since_persist);\n    anjay_unlocked->attr_storage.modified_since_persist = false;\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_dm_call_instance_write_default_attrs(\n            anjay_unlocked, WRAP_OBJ_PTR(&OBJ2), 14, 5,\n            &(const anjay_dm_oi_attributes_t) {\n                .min_period = ANJAY_ATTRIB_INTEGER_NONE,\n                .max_period = 10,\n                .min_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n                .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE\n#ifdef ANJAY_WITH_CON_ATTR\n                ,\n                .con = ANJAY_DM_CON_ATTR_NONE\n#endif // ANJAY_WITH_CON_ATTR\n#ifdef ANJAY_WITH_LWM2M12\n                ,\n                .hqmax = ANJAY_ATTRIB_INTEGER_NONE\n#endif // ANJAY_WITH_LWM2M12\n            }));\n    AVS_UNIT_ASSERT_TRUE(anjay_unlocked->attr_storage.modified_since_persist);\n    anjay_unlocked->attr_storage.modified_since_persist = false;\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_dm_call_instance_write_default_attrs(\n            anjay_unlocked, WRAP_OBJ_PTR(&OBJ2), 9, 5,\n            &ANJAY_DM_OI_ATTRIBUTES_EMPTY));\n    AVS_UNIT_ASSERT_TRUE(anjay_unlocked->attr_storage.modified_since_persist);\n    anjay_unlocked->attr_storage.modified_since_persist = false;\n\n    AVS_UNIT_ASSERT_EQUAL(AVS_LIST_SIZE(anjay_unlocked->attr_storage.objects),\n                          1);\n    assert_object_equal(\n            anjay_unlocked->attr_storage.objects,\n            test_object_entry(\n                    69, NULL,\n                    test_instance_entry(\n                            3,\n                            test_default_attrlist(\n                                    test_default_attrs(\n                                            2, 4, 9, ANJAY_ATTRIB_INTEGER_NONE,\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n#ifdef ANJAY_WITH_LWM2M12\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n#endif // ANJAY_WITH_LWM2M12\n                                            ANJAY_DM_CON_ATTR_NONE),\n                                    test_default_attrs(\n                                            5, 7, 15, ANJAY_ATTRIB_INTEGER_NONE,\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n#ifdef ANJAY_WITH_LWM2M12\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n#endif // ANJAY_WITH_LWM2M12\n                                            ANJAY_DM_CON_ATTR_NONE),\n                                    NULL),\n                            NULL),\n                    test_instance_entry(\n                            14,\n                            test_default_attrlist(\n                                    test_default_attrs(\n                                            5, ANJAY_ATTRIB_INTEGER_NONE, 10,\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n#ifdef ANJAY_WITH_LWM2M12\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n#endif // ANJAY_WITH_LWM2M12\n                                            ANJAY_DM_CON_ATTR_NONE),\n                                    NULL),\n                            NULL),\n                    NULL));\n\n    anjay_dm_oi_attributes_t attrs;\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_dm_call_instance_read_default_attrs(\n            anjay_unlocked, WRAP_OBJ_PTR(&OBJ2), 42, 2, &attrs));\n    assert_attrs_equal(&attrs, &ANJAY_DM_OI_ATTRIBUTES_EMPTY);\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_dm_call_instance_read_default_attrs(\n            anjay_unlocked, WRAP_OBJ_PTR(&OBJ2), 3, 2, &attrs));\n    assert_attrs_equal(&attrs,\n                       &(const anjay_dm_oi_attributes_t) {\n                           .min_period = 4,\n                           .max_period = 9,\n                           .min_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n                           .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE\n#ifdef ANJAY_WITH_CON_ATTR\n                           ,\n                           .con = ANJAY_DM_CON_ATTR_NONE\n#endif // ANJAY_WITH_CON_ATTR\n#ifdef ANJAY_WITH_LWM2M12\n                           ,\n                           .hqmax = ANJAY_ATTRIB_INTEGER_NONE\n#endif // ANJAY_WITH_LWM2M12\n                       });\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_dm_call_instance_read_default_attrs(\n            anjay_unlocked, WRAP_OBJ_PTR(&OBJ2), 3, 5, &attrs));\n    assert_attrs_equal(&attrs,\n                       &(const anjay_dm_oi_attributes_t) {\n                           .min_period = 7,\n                           .max_period = 15,\n                           .min_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n                           .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE\n#ifdef ANJAY_WITH_CON_ATTR\n                           ,\n                           .con = ANJAY_DM_CON_ATTR_NONE\n#endif // ANJAY_WITH_CON_ATTR\n#ifdef ANJAY_WITH_LWM2M12\n                           ,\n                           .hqmax = ANJAY_ATTRIB_INTEGER_NONE\n#endif // ANJAY_WITH_LWM2M12\n                       });\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_dm_call_instance_read_default_attrs(\n            anjay_unlocked, WRAP_OBJ_PTR(&OBJ2), 9, 5, &attrs));\n    assert_attrs_equal(&attrs, &ANJAY_DM_OI_ATTRIBUTES_EMPTY);\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_dm_call_instance_read_default_attrs(\n            anjay_unlocked, WRAP_OBJ_PTR(&OBJ2), 14, 5, &attrs));\n    assert_attrs_equal(&attrs,\n                       &(const anjay_dm_oi_attributes_t) {\n                           .min_period = ANJAY_ATTRIB_INTEGER_NONE,\n                           .max_period = 10,\n                           .min_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n                           .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE\n#ifdef ANJAY_WITH_CON_ATTR\n                           ,\n                           .con = ANJAY_DM_CON_ATTR_NONE\n#endif // ANJAY_WITH_CON_ATTR\n#ifdef ANJAY_WITH_LWM2M12\n                           ,\n                           .hqmax = ANJAY_ATTRIB_INTEGER_NONE\n#endif // ANJAY_WITH_LWM2M12\n                       });\n\n    AVS_UNIT_ASSERT_FALSE(anjay_unlocked->attr_storage.modified_since_persist);\n    DM_ATTR_STORAGE_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(attr_storage, read_resource_attrs_proxy) {\n    DM_ATTR_STORAGE_TEST_INIT;\n\n    anjay_dm_r_attributes_t attrs;\n    _anjay_mock_dm_expect_resource_read_attrs(anjay, &OBJ, 5, 6, 4, 0,\n                                              &ANJAY_DM_R_ATTRIBUTES_EMPTY);\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_dm_call_resource_read_attrs(\n            anjay_unlocked, WRAP_OBJ_PTR(&OBJ), 5, 6, 4, &attrs));\n    assert_res_attrs_equal(&attrs, &ANJAY_DM_R_ATTRIBUTES_EMPTY);\n\n    _anjay_mock_dm_expect_resource_read_attrs(anjay, &OBJ, 5, 7, 42, -413,\n                                              &ANJAY_DM_R_ATTRIBUTES_EMPTY);\n    AVS_UNIT_ASSERT_EQUAL(_anjay_dm_call_resource_read_attrs(anjay_unlocked,\n                                                             WRAP_OBJ_PTR(&OBJ),\n                                                             5, 7, 42, &attrs),\n                          -413);\n\n    _anjay_mock_dm_expect_resource_read_attrs(\n            anjay, &OBJ, 7, 17, 4, 0,\n            &(const anjay_dm_r_attributes_t) {\n                .common = {\n                    .min_period = ANJAY_ATTRIB_INTEGER_NONE,\n                    .max_period = 77,\n                    .min_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n                    .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE\n#ifdef ANJAY_WITH_LWM2M12\n                    ,\n                    .hqmax = ANJAY_ATTRIB_INTEGER_NONE\n#endif // ANJAY_WITH_LWM2M12\n                },\n                .greater_than = 44.0,\n                .less_than = ANJAY_ATTRIB_DOUBLE_NONE,\n                .step = .5\n            });\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_dm_call_resource_read_attrs(\n            anjay_unlocked, WRAP_OBJ_PTR(&OBJ), 7, 17, 4, &attrs));\n    assert_res_attrs_equal(&attrs,\n                           &(const anjay_dm_r_attributes_t) {\n                               .common = {\n                                   .min_period = ANJAY_ATTRIB_INTEGER_NONE,\n                                   .max_period = 77,\n                                   .min_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n                                   .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE\n#ifdef ANJAY_WITH_LWM2M12\n                                   ,\n                                   .hqmax = ANJAY_ATTRIB_INTEGER_NONE\n#endif // ANJAY_WITH_LWM2M12\n                               },\n                               .greater_than = 44.0,\n                               .less_than = ANJAY_ATTRIB_DOUBLE_NONE,\n                               .step = .5\n                           });\n    AVS_UNIT_ASSERT_FALSE(anjay_unlocked->attr_storage.modified_since_persist);\n    DM_ATTR_STORAGE_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(attr_storage, write_resource_attrs_proxy) {\n    DM_ATTR_STORAGE_TEST_INIT;\n\n    _anjay_mock_dm_expect_resource_write_attrs(\n            anjay, &OBJ, 4, 9, 42,\n            &(const anjay_dm_r_attributes_t) {\n                .common = {\n                    .min_period = 43,\n                    .max_period = ANJAY_ATTRIB_INTEGER_NONE,\n                    .min_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n                    .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE\n#ifdef ANJAY_WITH_LWM2M12\n                    ,\n                    .hqmax = ANJAY_ATTRIB_INTEGER_NONE\n#endif // ANJAY_WITH_LWM2M12\n                },\n                .greater_than = 13.0,\n                .less_than = ANJAY_ATTRIB_DOUBLE_NONE,\n                .step = ANJAY_ATTRIB_DOUBLE_NONE\n            },\n            0);\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_dm_call_resource_write_attrs(\n            anjay_unlocked, WRAP_OBJ_PTR(&OBJ), 4, 9, 42,\n            &(const anjay_dm_r_attributes_t) {\n                .common = {\n                    .min_period = 43,\n                    .max_period = ANJAY_ATTRIB_INTEGER_NONE,\n                    .min_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n                    .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE\n#ifdef ANJAY_WITH_LWM2M12\n                    ,\n                    .hqmax = ANJAY_ATTRIB_INTEGER_NONE\n#endif // ANJAY_WITH_LWM2M12\n                },\n                .greater_than = 13.0,\n                .less_than = ANJAY_ATTRIB_DOUBLE_NONE,\n                .step = ANJAY_ATTRIB_DOUBLE_NONE\n            }));\n\n    _anjay_mock_dm_expect_resource_write_attrs(\n            anjay, &OBJ, 4, 111, 7,\n            &(const anjay_dm_r_attributes_t) {\n                .common = {\n                    .min_period = ANJAY_ATTRIB_INTEGER_NONE,\n                    .max_period = 77,\n                    .min_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n                    .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE\n#ifdef ANJAY_WITH_LWM2M12\n                    ,\n                    .hqmax = ANJAY_ATTRIB_INTEGER_NONE\n#endif // ANJAY_WITH_LWM2M12\n                },\n                .greater_than = ANJAY_ATTRIB_DOUBLE_NONE,\n                .less_than = ANJAY_ATTRIB_DOUBLE_NONE,\n                .step = ANJAY_ATTRIB_DOUBLE_NONE\n            },\n            0);\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_dm_call_resource_write_attrs(\n            anjay_unlocked, WRAP_OBJ_PTR(&OBJ), 4, 111, 7,\n            &(const anjay_dm_r_attributes_t) {\n                .common = {\n                    .min_period = ANJAY_ATTRIB_INTEGER_NONE,\n                    .max_period = 77,\n                    .min_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n                    .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE\n#ifdef ANJAY_WITH_LWM2M12\n                    ,\n                    .hqmax = ANJAY_ATTRIB_INTEGER_NONE\n#endif // ANJAY_WITH_LWM2M12\n                },\n                .greater_than = ANJAY_ATTRIB_DOUBLE_NONE,\n                .less_than = ANJAY_ATTRIB_DOUBLE_NONE,\n                .step = ANJAY_ATTRIB_DOUBLE_NONE\n            }));\n\n    _anjay_mock_dm_expect_resource_write_attrs(\n            anjay, &OBJ, 8, 9, 7,\n            &(const anjay_dm_r_attributes_t) {\n                .common = {\n                    .min_period = ANJAY_ATTRIB_INTEGER_NONE,\n                    .max_period = ANJAY_ATTRIB_INTEGER_NONE,\n                    .min_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n                    .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE\n#ifdef ANJAY_WITH_LWM2M12\n                    ,\n                    .hqmax = ANJAY_ATTRIB_INTEGER_NONE\n#endif // ANJAY_WITH_LWM2M12\n                },\n                .greater_than = 0.8,\n                .less_than = 8.8,\n                .step = 88.8\n            },\n            -8888);\n    AVS_UNIT_ASSERT_EQUAL(\n            _anjay_dm_call_resource_write_attrs(\n                    anjay_unlocked, WRAP_OBJ_PTR(&OBJ), 8, 9, 7,\n                    &(const anjay_dm_r_attributes_t) {\n                        .common = {\n                            .min_period = ANJAY_ATTRIB_INTEGER_NONE,\n                            .max_period = ANJAY_ATTRIB_INTEGER_NONE,\n                            .min_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n                            .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE\n#ifdef ANJAY_WITH_LWM2M12\n                            ,\n                            .hqmax = ANJAY_ATTRIB_INTEGER_NONE\n#endif // ANJAY_WITH_LWM2M12\n                        },\n                        .greater_than = 0.8,\n                        .less_than = 8.8,\n                        .step = 88.8\n                    }),\n            -8888);\n\n    _anjay_mock_dm_expect_resource_write_attrs(\n            anjay, &OBJ, 9, 23, 4,\n            &(const anjay_dm_r_attributes_t) {\n                .common = {\n                    .min_period = 4,\n                    .max_period = ANJAY_ATTRIB_INTEGER_NONE,\n                    .min_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n                    .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE\n#ifdef ANJAY_WITH_LWM2M12\n                    ,\n                    .hqmax = ANJAY_ATTRIB_INTEGER_NONE\n#endif // ANJAY_WITH_LWM2M12\n                },\n                .greater_than = 99.0,\n                .less_than = ANJAY_ATTRIB_DOUBLE_NONE,\n                .step = ANJAY_ATTRIB_DOUBLE_NONE,\n            },\n            0);\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_dm_call_resource_write_attrs(\n            anjay_unlocked, WRAP_OBJ_PTR(&OBJ), 9, 23, 4,\n            &(const anjay_dm_r_attributes_t) {\n                .common = {\n                    .min_period = 4,\n                    .max_period = ANJAY_ATTRIB_INTEGER_NONE,\n                    .min_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n                    .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE\n#ifdef ANJAY_WITH_LWM2M12\n                    ,\n                    .hqmax = ANJAY_ATTRIB_INTEGER_NONE\n#endif // ANJAY_WITH_LWM2M12\n                },\n                .greater_than = 99.0,\n                .less_than = ANJAY_ATTRIB_DOUBLE_NONE,\n                .step = ANJAY_ATTRIB_DOUBLE_NONE,\n            }));\n\n    _anjay_mock_dm_expect_resource_write_attrs(anjay, &OBJ, 9, 23, 4,\n                                               &ANJAY_DM_R_ATTRIBUTES_EMPTY, 0);\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_dm_call_resource_write_attrs(\n            anjay_unlocked, WRAP_OBJ_PTR(&OBJ), 9, 23, 4,\n            &ANJAY_DM_R_ATTRIBUTES_EMPTY));\n\n    _anjay_mock_dm_expect_resource_write_attrs(anjay, &OBJ, 11, 11, 11,\n                                               &ANJAY_DM_R_ATTRIBUTES_EMPTY, 0);\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_dm_call_resource_write_attrs(\n            anjay_unlocked, WRAP_OBJ_PTR(&OBJ), 11, 11, 11,\n            &ANJAY_DM_R_ATTRIBUTES_EMPTY));\n\n    AVS_UNIT_ASSERT_NULL(anjay_unlocked->attr_storage.objects);\n\n    AVS_UNIT_ASSERT_FALSE(anjay_unlocked->attr_storage.modified_since_persist);\n    DM_ATTR_STORAGE_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(attr_storage, read_resource_attrs) {\n    DM_ATTR_STORAGE_TEST_INIT;\n\n    AVS_LIST_APPEND(&anjay_unlocked->attr_storage.objects,\n                    test_object_entry(\n                            69, NULL,\n                            test_instance_entry(\n                                    3, NULL,\n                                    test_resource_entry(\n                                            1,\n                                            test_resource_attrs(\n                                                    42, 1, 2, 6, 7,\n#ifdef ANJAY_WITH_LWM2M12\n                                                    14,\n#endif // ANJAY_WITH_LWM2M12\n                                                    3.0, 4.0, 5.0,\n#ifdef ANJAY_WITH_LWM2M12\n                                                    ANJAY_DM_EDGE_ATTR_FALLING,\n#endif // ANJAY_WITH_LWM2M12\n                                                    ANJAY_DM_CON_ATTR_NONE),\n                                            NULL),\n                                    NULL),\n                            NULL));\n\n    anjay_dm_r_attributes_t attrs;\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_dm_call_resource_read_attrs(\n            anjay_unlocked, WRAP_OBJ_PTR(&OBJ2), 3, 1, 42, &attrs));\n    assert_res_attrs_equal(&attrs,\n                           &(const anjay_dm_r_attributes_t) {\n                               .common = {\n                                   .min_period = 1,\n                                   .max_period = 2,\n                                   .min_eval_period = 6,\n                                   .max_eval_period = 7\n#ifdef ANJAY_WITH_CON_ATTR\n                                   ,\n                                   .con = ANJAY_DM_CON_ATTR_NONE\n#endif // ANJAY_WITH_CON_ATTR\n#ifdef ANJAY_WITH_LWM2M12\n                                   ,\n                                   .hqmax = 14\n#endif // ANJAY_WITH_LWM2M12\n                               },\n                               .greater_than = 3.0,\n                               .less_than = 4.0,\n                               .step = 5.0\n#ifdef ANJAY_WITH_LWM2M12\n                               ,\n                               .edge = ANJAY_DM_EDGE_ATTR_FALLING\n#endif // ANJAY_WITH_LWM2M12\n                           });\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_dm_call_resource_read_attrs(\n            anjay_unlocked, WRAP_OBJ_PTR(&OBJ2), 3, 1, 4, &attrs));\n    assert_res_attrs_equal(&attrs, &ANJAY_DM_R_ATTRIBUTES_EMPTY);\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_dm_call_resource_read_attrs(\n            anjay_unlocked, WRAP_OBJ_PTR(&OBJ2), 3, 2, 4, &attrs));\n    assert_res_attrs_equal(&attrs, &ANJAY_DM_R_ATTRIBUTES_EMPTY);\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_dm_call_resource_read_attrs(\n            anjay_unlocked, WRAP_OBJ_PTR(&OBJ2), 2, 2, 4, &attrs));\n    assert_res_attrs_equal(&attrs, &ANJAY_DM_R_ATTRIBUTES_EMPTY);\n\n    AVS_UNIT_ASSERT_FALSE(anjay_unlocked->attr_storage.modified_since_persist);\n    DM_ATTR_STORAGE_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(attr_storage, write_resource_attrs) {\n    DM_ATTR_STORAGE_TEST_INIT;\n    AVS_UNIT_ASSERT_FALSE(anjay_unlocked->attr_storage.modified_since_persist);\n\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_dm_call_resource_write_attrs(\n            anjay_unlocked, WRAP_OBJ_PTR(&OBJ2), 2, 5, 3,\n            &ANJAY_DM_R_ATTRIBUTES_EMPTY));\n    // nothing actually changed\n    AVS_UNIT_ASSERT_FALSE(anjay_unlocked->attr_storage.modified_since_persist);\n    AVS_UNIT_ASSERT_NULL(anjay_unlocked->attr_storage.objects);\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_dm_call_resource_write_attrs(\n            anjay_unlocked, WRAP_OBJ_PTR(&OBJ2), 2, 3, 1,\n            &(const anjay_dm_r_attributes_t) {\n                .common = {\n                    .min_period = 1,\n                    .max_period = ANJAY_ATTRIB_INTEGER_NONE,\n                    .min_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n                    .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE\n#ifdef ANJAY_WITH_CON_ATTR\n                    ,\n                    .con = ANJAY_DM_CON_ATTR_NONE\n#endif // ANJAY_WITH_CON_ATTR\n#ifdef ANJAY_WITH_LWM2M12\n                    ,\n                    .hqmax = ANJAY_ATTRIB_INTEGER_NONE\n#endif // ANJAY_WITH_LWM2M12\n                },\n                .greater_than = 34.0,\n                .less_than = ANJAY_ATTRIB_DOUBLE_NONE,\n                .step = ANJAY_ATTRIB_DOUBLE_NONE\n#ifdef ANJAY_WITH_LWM2M12\n                ,\n                ANJAY_DM_EDGE_ATTR_RISING\n#endif // ANJAY_WITH_LWM2M12\n            }));\n    AVS_UNIT_ASSERT_TRUE(anjay_unlocked->attr_storage.modified_since_persist);\n    anjay_unlocked->attr_storage.modified_since_persist = false;\n\n    AVS_UNIT_ASSERT_EQUAL(AVS_LIST_SIZE(anjay_unlocked->attr_storage.objects),\n                          1);\n    assert_object_equal(\n            anjay_unlocked->attr_storage.objects,\n            test_object_entry(\n                    69, NULL,\n                    test_instance_entry(\n                            2,\n                            NULL,\n                            test_resource_entry(\n                                    3,\n                                    test_resource_attrs(\n                                            1, 1, ANJAY_ATTRIB_INTEGER_NONE,\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n#ifdef ANJAY_WITH_LWM2M12\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n#endif // ANJAY_WITH_LWM2M12\n                                            34.0, ANJAY_ATTRIB_DOUBLE_NONE,\n                                            ANJAY_ATTRIB_DOUBLE_NONE,\n#ifdef ANJAY_WITH_LWM2M12\n                                            ANJAY_DM_EDGE_ATTR_RISING,\n#endif // ANJAY_WITH_LWM2M12\n                                            ANJAY_DM_CON_ATTR_NONE),\n                                    NULL),\n                            NULL),\n                    NULL));\n\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_dm_call_resource_write_attrs(\n            anjay_unlocked, WRAP_OBJ_PTR(&OBJ2), 2, 5, 3,\n            &(const anjay_dm_r_attributes_t) {\n                .common = {\n                    .min_period = 4,\n                    .max_period = 5,\n                    .min_eval_period = 99,\n                    .max_eval_period = 100\n#ifdef ANJAY_WITH_CON_ATTR\n                    ,\n                    .con = ANJAY_DM_CON_ATTR_NONE\n#endif // ANJAY_WITH_CON_ATTR\n#ifdef ANJAY_WITH_LWM2M12\n                    ,\n                    .hqmax = 33\n#endif // ANJAY_WITH_LWM2M12\n                },\n                .greater_than = 6.0,\n                .less_than = 7.0,\n                .step = 8.0\n#ifdef ANJAY_WITH_LWM2M12\n                ,\n                ANJAY_DM_EDGE_ATTR_RISING\n#endif // ANJAY_WITH_LWM2M12\n            }));\n    AVS_UNIT_ASSERT_TRUE(anjay_unlocked->attr_storage.modified_since_persist);\n    anjay_unlocked->attr_storage.modified_since_persist = false;\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_dm_call_resource_write_attrs(\n            anjay_unlocked, WRAP_OBJ_PTR(&OBJ2), 2, 3, 5,\n            &(const anjay_dm_r_attributes_t) {\n                .common = {\n                    .min_period = 9,\n                    .max_period = 10,\n                    .min_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n                    .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE\n#ifdef ANJAY_WITH_CON_ATTR\n                    ,\n                    .con = ANJAY_DM_CON_ATTR_NONE\n#endif // ANJAY_WITH_CON_ATTR\n#ifdef ANJAY_WITH_LWM2M12\n                    ,\n                    .hqmax = 12\n#endif // ANJAY_WITH_LWM2M12\n                },\n                .greater_than = 11.0,\n                .less_than = 22.0,\n                .step = 33.0\n#ifdef ANJAY_WITH_LWM2M12\n                ,\n                ANJAY_DM_EDGE_ATTR_FALLING\n#endif // ANJAY_WITH_LWM2M12\n            }));\n    AVS_UNIT_ASSERT_TRUE(anjay_unlocked->attr_storage.modified_since_persist);\n    anjay_unlocked->attr_storage.modified_since_persist = false;\n\n    AVS_UNIT_ASSERT_EQUAL(AVS_LIST_SIZE(anjay_unlocked->attr_storage.objects),\n                          1);\n    assert_object_equal(\n            anjay_unlocked->attr_storage.objects,\n            test_object_entry(\n                    69, NULL,\n                    test_instance_entry(\n                            2,\n                            NULL,\n                            test_resource_entry(\n                                    3,\n                                    test_resource_attrs(\n                                            1, 1, ANJAY_ATTRIB_INTEGER_NONE,\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n#ifdef ANJAY_WITH_LWM2M12\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n#endif // ANJAY_WITH_LWM2M12\n                                            34.0, ANJAY_ATTRIB_DOUBLE_NONE,\n                                            ANJAY_ATTRIB_DOUBLE_NONE,\n#ifdef ANJAY_WITH_LWM2M12\n                                            ANJAY_DM_EDGE_ATTR_RISING,\n#endif // ANJAY_WITH_LWM2M12\n                                            ANJAY_DM_CON_ATTR_NONE),\n                                    test_resource_attrs(\n                                            5, 9, 10, ANJAY_ATTRIB_INTEGER_NONE,\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n#ifdef ANJAY_WITH_LWM2M12\n                                            12,\n#endif // ANJAY_WITH_LWM2M12\n                                            11.0, 22.0, 33.0,\n#ifdef ANJAY_WITH_LWM2M12\n                                            ANJAY_DM_EDGE_ATTR_FALLING,\n#endif // ANJAY_WITH_LWM2M12\n                                            ANJAY_DM_CON_ATTR_NONE),\n                                    NULL),\n                            test_resource_entry(\n                                    5,\n                                    test_resource_attrs(\n                                            3, 4, 5, 99, 100,\n#ifdef ANJAY_WITH_LWM2M12\n                                            33,\n#endif // ANJAY_WITH_LWM2M12\n                                            6.0, 7.0, 8.0,\n#ifdef ANJAY_WITH_LWM2M12\n                                            ANJAY_DM_EDGE_ATTR_RISING,\n#endif // ANJAY_WITH_LWM2M12\n                                            ANJAY_DM_CON_ATTR_NONE),\n                                    NULL),\n                            NULL),\n                    NULL));\n\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_dm_call_instance_write_default_attrs(\n            anjay_unlocked, WRAP_OBJ_PTR(&OBJ2), 2, 4,\n            &(const anjay_dm_oi_attributes_t) {\n                .min_period = 4,\n                .max_period = ANJAY_ATTRIB_INTEGER_NONE,\n                .min_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n                .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE\n#ifdef ANJAY_WITH_CON_ATTR\n                ,\n                .con = ANJAY_DM_CON_ATTR_NONE\n#endif // ANJAY_WITH_CON_ATTR\n#ifdef ANJAY_WITH_LWM2M12\n                ,\n                .hqmax = ANJAY_ATTRIB_INTEGER_NONE\n#endif // ANJAY_WITH_LWM2M12\n            }));\n    AVS_UNIT_ASSERT_TRUE(anjay_unlocked->attr_storage.modified_since_persist);\n    anjay_unlocked->attr_storage.modified_since_persist = false;\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_dm_call_resource_write_attrs(\n            anjay_unlocked, WRAP_OBJ_PTR(&OBJ2), 2, 3, 5,\n            &ANJAY_DM_R_ATTRIBUTES_EMPTY));\n    AVS_UNIT_ASSERT_TRUE(anjay_unlocked->attr_storage.modified_since_persist);\n    anjay_unlocked->attr_storage.modified_since_persist = false;\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_dm_call_resource_write_attrs(\n            anjay_unlocked, WRAP_OBJ_PTR(&OBJ2), 2, 3, 1,\n            &ANJAY_DM_R_ATTRIBUTES_EMPTY));\n    AVS_UNIT_ASSERT_TRUE(anjay_unlocked->attr_storage.modified_since_persist);\n    anjay_unlocked->attr_storage.modified_since_persist = false;\n\n    AVS_UNIT_ASSERT_EQUAL(AVS_LIST_SIZE(anjay_unlocked->attr_storage.objects),\n                          1);\n    assert_object_equal(\n            anjay_unlocked->attr_storage.objects,\n            test_object_entry(\n                    69, NULL,\n                    test_instance_entry(\n                            2,\n                            test_default_attrlist(\n                                    test_default_attrs(\n                                            4, 4, ANJAY_ATTRIB_INTEGER_NONE,\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n#ifdef ANJAY_WITH_LWM2M12\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n#endif // ANJAY_WITH_LWM2M12\n                                            ANJAY_DM_CON_ATTR_NONE),\n                                    NULL),\n                            test_resource_entry(\n                                    5,\n                                    test_resource_attrs(\n                                            3, 4, 5, 99, 100,\n#ifdef ANJAY_WITH_LWM2M12\n                                            33,\n#endif // ANJAY_WITH_LWM2M12\n                                            6.0, 7.0, 8.0,\n#ifdef ANJAY_WITH_LWM2M12\n                                            ANJAY_DM_EDGE_ATTR_RISING,\n#endif // ANJAY_WITH_LWM2M12\n                                            ANJAY_DM_CON_ATTR_NONE),\n                                    NULL),\n                            NULL),\n                    NULL));\n\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_dm_call_resource_write_attrs(\n            anjay_unlocked, WRAP_OBJ_PTR(&OBJ2), 2, 5, 3,\n            &ANJAY_DM_R_ATTRIBUTES_EMPTY));\n    AVS_UNIT_ASSERT_TRUE(anjay_unlocked->attr_storage.modified_since_persist);\n    anjay_unlocked->attr_storage.modified_since_persist = false;\n\n    AVS_UNIT_ASSERT_EQUAL(AVS_LIST_SIZE(anjay_unlocked->attr_storage.objects),\n                          1);\n    assert_object_equal(\n            anjay_unlocked->attr_storage.objects,\n            test_object_entry(\n                    69, NULL,\n                    test_instance_entry(\n                            2,\n                            test_default_attrlist(\n                                    test_default_attrs(\n                                            4, 4, ANJAY_ATTRIB_INTEGER_NONE,\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n#ifdef ANJAY_WITH_LWM2M12\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n#endif // ANJAY_WITH_LWM2M12\n                                            ANJAY_DM_CON_ATTR_NONE),\n                                    NULL),\n                            NULL),\n                    NULL));\n\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_dm_call_resource_write_attrs(\n            anjay_unlocked, WRAP_OBJ_PTR(&OBJ2), 2, 3, 5,\n            &(const anjay_dm_r_attributes_t) {\n                .common = {\n                    .min_period = 9,\n                    .max_period = 10,\n                    .min_eval_period = 11,\n                    .max_eval_period = 12\n#ifdef ANJAY_WITH_CON_ATTR\n                    ,\n                    .con = ANJAY_DM_CON_ATTR_NONE\n#endif // ANJAY_WITH_CON_ATTR\n#ifdef ANJAY_WITH_LWM2M12\n                    ,\n                    .hqmax = 19\n#endif // ANJAY_WITH_LWM2M12\n                },\n                .greater_than = 11.0,\n                .less_than = 22.0,\n                .step = 33.0\n#ifdef ANJAY_WITH_LWM2M12\n                ,\n                0\n#endif // ANJAY_WITH_LWM2M12\n            }));\n    AVS_UNIT_ASSERT_TRUE(anjay_unlocked->attr_storage.modified_since_persist);\n    anjay_unlocked->attr_storage.modified_since_persist = false;\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_dm_call_instance_write_default_attrs(\n            anjay_unlocked, WRAP_OBJ_PTR(&OBJ2), 2, 4,\n            &ANJAY_DM_OI_ATTRIBUTES_EMPTY));\n    AVS_UNIT_ASSERT_TRUE(anjay_unlocked->attr_storage.modified_since_persist);\n    anjay_unlocked->attr_storage.modified_since_persist = false;\n\n    AVS_UNIT_ASSERT_EQUAL(AVS_LIST_SIZE(anjay_unlocked->attr_storage.objects),\n                          1);\n    assert_object_equal(\n            anjay_unlocked->attr_storage.objects,\n            test_object_entry(\n                    69, NULL,\n                    test_instance_entry(\n                            2,\n                            NULL,\n                            test_resource_entry(\n                                    3,\n                                    test_resource_attrs(5, 9, 10, 11, 12,\n#ifdef ANJAY_WITH_LWM2M12\n                                                        19,\n#endif // ANJAY_WITH_LWM2M12\n                                                        11.0, 22.0, 33.0,\n#ifdef ANJAY_WITH_LWM2M12\n                                                        0,\n#endif // ANJAY_WITH_LWM2M12\n                                                        ANJAY_DM_CON_ATTR_NONE),\n                                    NULL),\n                            NULL),\n                    NULL));\n\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_dm_call_resource_write_attrs(\n            anjay_unlocked, WRAP_OBJ_PTR(&OBJ2), 2, 3, 5,\n            &ANJAY_DM_R_ATTRIBUTES_EMPTY));\n    AVS_UNIT_ASSERT_TRUE(anjay_unlocked->attr_storage.modified_since_persist);\n    anjay_unlocked->attr_storage.modified_since_persist = false;\n    AVS_UNIT_ASSERT_NULL(anjay_unlocked->attr_storage.objects);\n\n    AVS_UNIT_ASSERT_FALSE(anjay_unlocked->attr_storage.modified_since_persist);\n    DM_ATTR_STORAGE_TEST_FINISH;\n}\n\n#ifdef ANJAY_WITH_LWM2M11\nAVS_UNIT_TEST(attr_storage, read_resource_instance_attrs_proxy) {\n    DM_ATTR_STORAGE_TEST_INIT;\n\n    anjay_dm_r_attributes_t attrs;\n    _anjay_mock_dm_expect_resource_instance_read_attrs(\n            anjay, &OBJ, 5, 6, 7, 4, 0, &ANJAY_DM_R_ATTRIBUTES_EMPTY);\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_dm_call_resource_instance_read_attrs(\n            anjay_unlocked, WRAP_OBJ_PTR(&OBJ), 5, 6, 7, 4, &attrs));\n    assert_res_attrs_equal(&attrs, &ANJAY_DM_R_ATTRIBUTES_EMPTY);\n\n    _anjay_mock_dm_expect_resource_instance_read_attrs(\n            anjay, &OBJ, 5, 7, 8, 42, -413, &ANJAY_DM_R_ATTRIBUTES_EMPTY);\n    AVS_UNIT_ASSERT_EQUAL(_anjay_dm_call_resource_instance_read_attrs(\n                                  anjay_unlocked, WRAP_OBJ_PTR(&OBJ), 5, 7, 8,\n                                  42, &attrs),\n                          -413);\n\n    _anjay_mock_dm_expect_resource_instance_read_attrs(\n            anjay, &OBJ, 7, 17, 1, 4, 0,\n            &(const anjay_dm_r_attributes_t) {\n                .common = {\n                    .min_period = 10,\n                    .max_period = 20,\n                    .min_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n                    .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE\n                },\n                .greater_than = 13.37,\n                .less_than = 1.,\n                .step = ANJAY_ATTRIB_DOUBLE_NONE\n            });\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_dm_call_resource_instance_read_attrs(\n            anjay_unlocked, WRAP_OBJ_PTR(&OBJ), 7, 17, 1, 4, &attrs));\n    assert_res_attrs_equal(&attrs,\n                           &(const anjay_dm_r_attributes_t) {\n                               .common = {\n                                   .min_period = 10,\n                                   .max_period = 20,\n                                   .min_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n                                   .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE\n                               },\n                               .greater_than = 13.37,\n                               .less_than = 1.,\n                               .step = ANJAY_ATTRIB_DOUBLE_NONE\n                           });\n\n    AVS_UNIT_ASSERT_FALSE(anjay_unlocked->attr_storage.modified_since_persist);\n    DM_ATTR_STORAGE_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(attr_storage, write_resource_instance_attrs_proxy) {\n    DM_ATTR_STORAGE_TEST_INIT;\n\n    _anjay_mock_dm_expect_resource_instance_write_attrs(\n            anjay, &OBJ, 1, 2, 3, 42,\n            &(const anjay_dm_r_attributes_t) {\n                .common = {\n                    .min_period = 1234,\n                    .max_period = 5678,\n                    .min_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n                    .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE\n                },\n                .greater_than = 13.,\n                .less_than = ANJAY_ATTRIB_DOUBLE_NONE,\n                .step = 37.\n            },\n            0);\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_dm_call_resource_instance_write_attrs(\n            anjay_unlocked, WRAP_OBJ_PTR(&OBJ), 1, 2, 3, 42,\n            &(const anjay_dm_r_attributes_t) {\n                .common = {\n                    .min_period = 1234,\n                    .max_period = 5678,\n                    .min_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n                    .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE\n                },\n                .greater_than = 13.,\n                .less_than = ANJAY_ATTRIB_DOUBLE_NONE,\n                .step = 37.\n            }));\n\n    _anjay_mock_dm_expect_resource_instance_write_attrs(\n            anjay, &OBJ, 99, 99, 99, 5, &ANJAY_DM_R_ATTRIBUTES_EMPTY, 0);\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_dm_call_resource_instance_write_attrs(\n            anjay_unlocked, WRAP_OBJ_PTR(&OBJ), 99, 99, 99, 5,\n            &ANJAY_DM_R_ATTRIBUTES_EMPTY));\n\n    AVS_UNIT_ASSERT_NULL(anjay_unlocked->attr_storage.objects);\n    AVS_UNIT_ASSERT_FALSE(anjay_unlocked->attr_storage.modified_since_persist);\n\n    DM_ATTR_STORAGE_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(attr_storage, resource_instance_attrs) {\n    DM_ATTR_STORAGE_TEST_INIT;\n    AVS_UNIT_ASSERT_FALSE(anjay_unlocked->attr_storage.modified_since_persist);\n\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_dm_call_object_write_default_attrs(\n            anjay_unlocked, WRAP_OBJ_PTR(&OBJ2), 42,\n            &(const anjay_dm_oi_attributes_t) {\n                // These will be ignored because we do not call\n                // _anjay_dm_effective_attrs()\n                .min_period = 1234,\n                .max_period = 5678,\n                .min_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n                .max_eval_period = 9999\n#    ifdef ANJAY_WITH_CON_ATTR\n                ,\n                .con = ANJAY_DM_CON_ATTR_NONE\n#    endif // ANJAY_WITH_CON_ATTR\n            }));\n    AVS_UNIT_ASSERT_TRUE(anjay_unlocked->attr_storage.modified_since_persist);\n\n    anjay_unlocked->attr_storage.modified_since_persist = false;\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_dm_call_resource_instance_write_attrs(\n            anjay_unlocked, WRAP_OBJ_PTR(&OBJ2), 1, 2, 3, 42,\n            &(const anjay_dm_r_attributes_t) {\n                .common = {\n                    .min_period = 10,\n                    .max_period = ANJAY_ATTRIB_INTEGER_NONE,\n                    .min_eval_period = 20,\n                    .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE\n#    ifdef ANJAY_WITH_CON_ATTR\n                    ,\n                    .con = ANJAY_DM_CON_ATTR_NONE\n#    endif // ANJAY_WITH_CON_ATTR\n                },\n                .greater_than = 13.,\n                .less_than = ANJAY_ATTRIB_DOUBLE_NONE,\n                .step = 37.\n            }));\n    AVS_UNIT_ASSERT_TRUE(anjay_unlocked->attr_storage.modified_since_persist);\n\n    anjay_dm_r_attributes_t attrs;\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_dm_call_resource_instance_read_attrs(\n            anjay_unlocked, WRAP_OBJ_PTR(&OBJ2), 1, 2, 3, 42, &attrs));\n    assert_res_attrs_equal(&attrs,\n                           &(const anjay_dm_r_attributes_t) {\n                               .common = {\n                                   .min_period = 10,\n                                   .max_period = ANJAY_ATTRIB_INTEGER_NONE,\n                                   .min_eval_period = 20,\n                                   .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE\n#    ifdef ANJAY_WITH_CON_ATTR\n                                   ,\n                                   .con = ANJAY_DM_CON_ATTR_NONE\n#    endif // ANJAY_WITH_CON_ATTR\n                               },\n                               .greater_than = 13.,\n                               .less_than = ANJAY_ATTRIB_DOUBLE_NONE,\n                               .step = 37.\n                           });\n\n    DM_ATTR_STORAGE_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_effective_attrs, resource_instance) {\n    DM_ATTR_STORAGE_TEST_INIT;\n\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_dm_call_object_write_default_attrs(\n            anjay_unlocked, WRAP_OBJ_PTR(&OBJ2), 42,\n            &(const anjay_dm_oi_attributes_t) {\n                .min_period = 1234,\n                .max_period = 5678,\n                .min_eval_period = 9101112,\n                .max_eval_period = 13141516\n#    ifdef ANJAY_WITH_CON_ATTR\n                ,\n                .con = ANJAY_DM_CON_ATTR_NONE\n#    endif // ANJAY_WITH_CON_ATTR\n            }));\n\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_dm_call_resource_instance_write_attrs(\n            anjay_unlocked, WRAP_OBJ_PTR(&OBJ2), 1, 2, 3, 42,\n            &(const anjay_dm_r_attributes_t) {\n                .common = {\n                    .min_period = 10,\n                    .max_period = ANJAY_ATTRIB_INTEGER_NONE,\n                    .min_eval_period = 20,\n                    .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE\n#    ifdef ANJAY_WITH_CON_ATTR\n                    ,\n                    .con = ANJAY_DM_CON_ATTR_NONE\n#    endif // ANJAY_WITH_CON_ATTR\n                },\n                .greater_than = 13.,\n                .less_than = ANJAY_ATTRIB_DOUBLE_NONE,\n                .step = 37.\n            }));\n\n    anjay_dm_r_attributes_t attrs;\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_dm_effective_attrs(anjay_unlocked,\n                                      &(const anjay_dm_attrs_query_details_t) {\n                                          .obj = WRAP_OBJ_PTR(&OBJ2),\n                                          .iid = 1,\n                                          .rid = 2,\n                                          .riid = 3,\n                                          .ssid = 42,\n                                          .with_server_level_attrs = false\n                                      },\n                                      &attrs));\n    assert_res_attrs_equal(&attrs,\n                           &(const anjay_dm_r_attributes_t) {\n                               .common = {\n                                   .min_period = 10,\n                                   .max_period = 5678,\n                                   // Inherited from the object\n                                   .min_eval_period = 20,\n                                   .max_eval_period = 13141516\n#    ifdef ANJAY_WITH_CON_ATTR\n                                   ,\n                                   .con = ANJAY_DM_CON_ATTR_NONE\n#    endif // ANJAY_WITH_CON_ATTR\n                               },\n                               .greater_than = 13.,\n                               .less_than = ANJAY_ATTRIB_DOUBLE_NONE,\n                               .step = 37.\n                           });\n\n    DM_ATTR_STORAGE_TEST_FINISH;\n}\n#endif // ANJAY_WITH_LWM2M11\n\n//// SSID HANDLING /////////////////////////////////////////////////////////////\n\nAVS_UNIT_TEST(set_attribs, fail_on_null_attribs) {\n    DM_TEST_INIT_WITH_OBJECTS(&OBJ_NOATTRS, &FAKE_SECURITY2);\n\n    AVS_UNIT_ASSERT_FAILED(anjay_attr_storage_set_object_attrs(\n            anjay, 1, OBJ_NOATTRS->oid, NULL));\n    AVS_UNIT_ASSERT_FAILED(anjay_attr_storage_set_instance_attrs(\n            anjay, 1, OBJ_NOATTRS->oid, 30, NULL));\n    AVS_UNIT_ASSERT_FAILED(anjay_attr_storage_set_resource_attrs(\n            anjay, 1, OBJ_NOATTRS->oid, 30, 50, NULL));\n    DM_TEST_FINISH;\n}\n\nstatic const anjay_dm_oi_attributes_t *FAKE_DM_ATTRS =\n        (anjay_dm_oi_attributes_t *) -1;\nstatic const anjay_dm_r_attributes_t *FAKE_DM_RES_ATTRS =\n        (anjay_dm_r_attributes_t *) -1;\n\nAVS_UNIT_TEST(set_attribs, fail_on_invalid_ssid) {\n    DM_TEST_INIT_WITH_OBJECTS(&OBJ_NOATTRS, &FAKE_SERVER);\n\n    const anjay_ssid_t SSIDS_TO_TEST[] = { ANJAY_SSID_ANY, ANJAY_SSID_BOOTSTRAP,\n                                           341 };\n    // Assumming no Server Instances\n    for (int i = 0; i < (int) AVS_ARRAY_SIZE(SSIDS_TO_TEST); ++i) {\n        // object\n        // attempt to query SSID\n        if (SSIDS_TO_TEST[i] != ANJAY_SSID_ANY\n                && SSIDS_TO_TEST[i] != ANJAY_SSID_BOOTSTRAP) {\n            _anjay_mock_dm_expect_list_instances(anjay, &FAKE_SERVER, 0,\n                                                 (const anjay_iid_t[]) {\n                                                         ANJAY_ID_INVALID });\n        }\n        AVS_UNIT_ASSERT_FAILED(anjay_attr_storage_set_object_attrs(\n                anjay, SSIDS_TO_TEST[i], OBJ_NOATTRS->oid, FAKE_DM_ATTRS));\n\n        // instance\n        // attempt to query SSID\n        if (SSIDS_TO_TEST[i] != ANJAY_SSID_ANY\n                && SSIDS_TO_TEST[i] != ANJAY_SSID_BOOTSTRAP) {\n            _anjay_mock_dm_expect_list_instances(anjay, &FAKE_SERVER, 0,\n                                                 (const anjay_iid_t[]) {\n                                                         ANJAY_ID_INVALID });\n        }\n        AVS_UNIT_ASSERT_FAILED(anjay_attr_storage_set_instance_attrs(\n                anjay, SSIDS_TO_TEST[i], OBJ_NOATTRS->oid, 0, FAKE_DM_ATTRS));\n\n        // resource\n        // attempt to query SSID\n        if (SSIDS_TO_TEST[i] != ANJAY_SSID_ANY\n                && SSIDS_TO_TEST[i] != ANJAY_SSID_BOOTSTRAP) {\n            _anjay_mock_dm_expect_list_instances(anjay, &FAKE_SERVER, 0,\n                                                 (const anjay_iid_t[]) {\n                                                         ANJAY_ID_INVALID });\n        }\n        AVS_UNIT_ASSERT_FAILED(anjay_attr_storage_set_resource_attrs(\n                anjay, SSIDS_TO_TEST[i], OBJ_NOATTRS->oid, 0, 0,\n                FAKE_DM_RES_ATTRS));\n    }\n\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(set_attribs, fail_on_invalid_object) {\n    DM_TEST_INIT_WITH_SSIDS(1);\n\n    // query SSID\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &FAKE_SERVER, 0,\n            (const anjay_iid_t[]) { 0, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &FAKE_SERVER, 0, 0,\n            (const anjay_mock_dm_res_entry_t[]) { { ANJAY_DM_RID_SERVER_SSID,\n                                                    ANJAY_DM_RES_R,\n                                                    ANJAY_DM_RES_PRESENT },\n                                                  ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read(anjay, &FAKE_SERVER, 0,\n                                        ANJAY_DM_RID_SERVER_SSID,\n                                        ANJAY_ID_INVALID, 0,\n                                        ANJAY_MOCK_DM_INT(0, 1));\n    AVS_UNIT_ASSERT_FAILED(\n            anjay_attr_storage_set_object_attrs(anjay, 1, 5, FAKE_DM_ATTRS));\n\n    // query SSID\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &FAKE_SERVER, 0,\n            (const anjay_iid_t[]) { 0, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &FAKE_SERVER, 0, 0,\n            (const anjay_mock_dm_res_entry_t[]) { { ANJAY_DM_RID_SERVER_SSID,\n                                                    ANJAY_DM_RES_R,\n                                                    ANJAY_DM_RES_PRESENT },\n                                                  ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read(anjay, &FAKE_SERVER, 0,\n                                        ANJAY_DM_RID_SERVER_SSID,\n                                        ANJAY_ID_INVALID, 0,\n                                        ANJAY_MOCK_DM_INT(0, 1));\n    AVS_UNIT_ASSERT_FAILED(anjay_attr_storage_set_instance_attrs(\n            anjay, 1, 5, 1, FAKE_DM_ATTRS));\n\n    // query SSID\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &FAKE_SERVER, 0,\n            (const anjay_iid_t[]) { 0, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &FAKE_SERVER, 0, 0,\n            (const anjay_mock_dm_res_entry_t[]) { { ANJAY_DM_RID_SERVER_SSID,\n                                                    ANJAY_DM_RES_R,\n                                                    ANJAY_DM_RES_PRESENT },\n                                                  ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read(anjay, &FAKE_SERVER, 0,\n                                        ANJAY_DM_RID_SERVER_SSID,\n                                        ANJAY_ID_INVALID, 0,\n                                        ANJAY_MOCK_DM_INT(0, 1));\n    AVS_UNIT_ASSERT_FAILED(anjay_attr_storage_set_resource_attrs(\n            anjay, 1, 5, 1, 0, FAKE_DM_RES_ATTRS));\n\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(set_attribs, fail_on_invalid_iid) {\n    DM_TEST_INIT_WITH_OBJECTS(&OBJ_NOATTRS, &FAKE_SERVER);\n\n    // attempt to query SSID\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &FAKE_SERVER, 0,\n            (const anjay_iid_t[]) { 1, ANJAY_ID_INVALID });\n\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &FAKE_SERVER, 1, 0,\n            (const anjay_mock_dm_res_entry_t[]) { { ANJAY_DM_RID_SERVER_SSID,\n                                                    ANJAY_DM_RES_R,\n                                                    ANJAY_DM_RES_PRESENT },\n                                                  ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read(anjay, &FAKE_SERVER, 1,\n                                        ANJAY_DM_RID_SERVER_SSID,\n                                        ANJAY_ID_INVALID, 0,\n                                        ANJAY_MOCK_DM_INT(0, 1));\n\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ_NOATTRS, 0, (const anjay_iid_t[]) { ANJAY_ID_INVALID });\n    AVS_UNIT_ASSERT_FAILED(anjay_attr_storage_set_instance_attrs(\n            anjay, 1, OBJ_NOATTRS->oid, ANJAY_ID_INVALID, FAKE_DM_ATTRS));\n\n    // attempt to query SSID\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &FAKE_SERVER, 0,\n            (const anjay_iid_t[]) { 1, ANJAY_ID_INVALID });\n\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &FAKE_SERVER, 1, 0,\n            (const anjay_mock_dm_res_entry_t[]) { { ANJAY_DM_RID_SERVER_SSID,\n                                                    ANJAY_DM_RES_R,\n                                                    ANJAY_DM_RES_PRESENT },\n                                                  ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read(anjay, &FAKE_SERVER, 1,\n                                        ANJAY_DM_RID_SERVER_SSID,\n                                        ANJAY_ID_INVALID, 0,\n                                        ANJAY_MOCK_DM_INT(0, 1));\n\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ_NOATTRS, 0, (const anjay_iid_t[]) { ANJAY_ID_INVALID });\n    AVS_UNIT_ASSERT_FAILED(anjay_attr_storage_set_resource_attrs(\n            anjay, 1, OBJ_NOATTRS->oid, ANJAY_ID_INVALID, 1,\n            FAKE_DM_RES_ATTRS));\n\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(set_attribs, fail_on_invalid_rid) {\n    DM_TEST_INIT_WITH_OBJECTS(&OBJ_NOATTRS, &FAKE_SERVER);\n\n    // attempt to query SSID\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &FAKE_SERVER, 0,\n            (const anjay_iid_t[]) { 1, ANJAY_ID_INVALID });\n\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &FAKE_SERVER, 1, 0,\n            (const anjay_mock_dm_res_entry_t[]) { { ANJAY_DM_RID_SERVER_SSID,\n                                                    ANJAY_DM_RES_R,\n                                                    ANJAY_DM_RES_PRESENT },\n                                                  ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read(anjay, &FAKE_SERVER, 1,\n                                        ANJAY_DM_RID_SERVER_SSID,\n                                        ANJAY_ID_INVALID, 0,\n                                        ANJAY_MOCK_DM_INT(0, 1));\n\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ_NOATTRS, 0,\n            (const anjay_iid_t[]) { 1, ANJAY_ID_INVALID });\n\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ_NOATTRS, 1, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    AVS_UNIT_ASSERT_FAILED(anjay_attr_storage_set_resource_attrs(\n            anjay, 1, OBJ_NOATTRS->oid, 1, 1, FAKE_DM_RES_ATTRS));\n\n    DM_TEST_FINISH;\n}\n\n#ifdef ANJAY_WITH_LWM2M11\nAVS_UNIT_TEST(set_attribs, success_on_resource_instance) {\n    DM_TEST_INIT_WITH_OBJECTS(&OBJ_NOATTRS, &FAKE_SERVER);\n\n    // attempt to query SSID\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &FAKE_SERVER, 0,\n            (const anjay_iid_t[]) { 1, ANJAY_ID_INVALID });\n\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &FAKE_SERVER, 1, 0,\n            (const anjay_mock_dm_res_entry_t[]) { { ANJAY_DM_RID_SERVER_SSID,\n                                                    ANJAY_DM_RES_R,\n                                                    ANJAY_DM_RES_PRESENT },\n                                                  ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read(anjay, &FAKE_SERVER, 1,\n                                        ANJAY_DM_RID_SERVER_SSID,\n                                        ANJAY_ID_INVALID, 0,\n                                        ANJAY_MOCK_DM_INT(0, 1));\n\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ_NOATTRS, 0,\n            (const anjay_iid_t[]) { 1, ANJAY_ID_INVALID });\n\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ_NOATTRS, 1, 0,\n            (const anjay_mock_dm_res_entry_t[]) { { 1337, ANJAY_DM_RES_RWM,\n                                                    ANJAY_DM_RES_PRESENT },\n                                                  ANJAY_MOCK_DM_RES_END });\n\n    _anjay_mock_dm_expect_list_resource_instances(\n            anjay, &OBJ_NOATTRS, 1, 1337, 0,\n            (const anjay_riid_t[]) { 12345, ANJAY_ID_INVALID });\n\n    AVS_UNIT_ASSERT_SUCCESS(anjay_attr_storage_set_resource_instance_attrs(\n            anjay, 1, OBJ_NOATTRS->oid, 1, 1337, 12345,\n            &(const anjay_dm_r_attributes_t) {\n                .common = {\n                    .min_period = 2,\n                    .max_period = ANJAY_ATTRIB_INTEGER_NONE,\n                    .min_eval_period = 10,\n                    .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE\n#    ifdef ANJAY_WITH_CON_ATTR\n                    ,\n                    .con = ANJAY_DM_CON_ATTR_NONE\n#    endif // ANJAY_WITH_CON_ATTR\n                },\n                .greater_than = ANJAY_ATTRIB_DOUBLE_NONE,\n                .less_than = ANJAY_ATTRIB_DOUBLE_NONE,\n                .step = ANJAY_ATTRIB_DOUBLE_NONE\n            }));\n\n    anjay_dm_r_attributes_t actual_attrs;\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    _anjay_dm_call_resource_instance_read_attrs(anjay_unlocked,\n                                                WRAP_OBJ_PTR(&OBJ_NOATTRS), 1,\n                                                1337, 12345, 1, &actual_attrs);\n    ANJAY_MUTEX_UNLOCK(anjay);\n    assert_res_attrs_equal(&actual_attrs,\n                           &(const anjay_dm_r_attributes_t) {\n                               .common = {\n                                   .min_period = 2,\n                                   .max_period = ANJAY_ATTRIB_INTEGER_NONE,\n                                   .min_eval_period = 10,\n                                   .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE\n#    ifdef ANJAY_WITH_CON_ATTR\n                                   ,\n                                   .con = ANJAY_DM_CON_ATTR_NONE\n#    endif // ANJAY_WITH_CON_ATTR\n                               },\n                               .greater_than = ANJAY_ATTRIB_DOUBLE_NONE,\n                               .less_than = ANJAY_ATTRIB_DOUBLE_NONE,\n                               .step = ANJAY_ATTRIB_DOUBLE_NONE\n                           });\n\n    DM_TEST_FINISH;\n}\n#endif // ANJAY_WITH_LWM2M11\n"
  },
  {
    "path": "tests/core/attr_storage/attr_storage_test.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ATTR_STORAGE_TEST_H\n#define ATTR_STORAGE_TEST_H\n\n#include <avsystem/commons/avs_list.h>\n#include <avsystem/commons/avs_unit_test.h>\n\n#include \"src/core/attr_storage/anjay_attr_storage_private.h\"\n#include \"tests/utils/utils.h\"\n\nstatic as_resource_attrs_t *test_resource_attrs(anjay_ssid_t ssid,\n                                                int32_t min_period,\n                                                int32_t max_period,\n                                                int32_t min_eval_period,\n                                                int32_t max_eval_period,\n#ifdef ANJAY_WITH_LWM2M12\n                                                int32_t hqmax,\n#endif // ANJAY_WITH_LWM2M12\n                                                double greater_than,\n                                                double less_than,\n                                                double step,\n#ifdef ANJAY_WITH_LWM2M12\n                                                anjay_dm_edge_attr_t edge,\n#endif // ANJAY_WITH_LWM2M12\n                                                anjay_dm_con_attr_t con) {\n    as_resource_attrs_t *attrs = AVS_LIST_NEW_ELEMENT(as_resource_attrs_t);\n    AVS_UNIT_ASSERT_NOT_NULL(attrs);\n    attrs->ssid = ssid;\n    attrs->attrs = (anjay_dm_r_attributes_t) {\n        .common = {\n            .min_period = min_period,\n            .max_period = max_period,\n            .min_eval_period = min_eval_period,\n            .max_eval_period = max_eval_period\n#ifdef ANJAY_WITH_LWM2M12\n            ,\n            .hqmax = hqmax\n#endif // ANJAY_WITH_LWM2M12\n        },\n        .greater_than = greater_than,\n        .less_than = less_than,\n        .step = step\n#ifdef ANJAY_WITH_LWM2M12\n        ,\n        .edge = edge\n#endif // ANJAY_WITH_LWM2M12\n    };\n#ifdef ANJAY_WITH_CON_ATTR\n    attrs->attrs.common.con = con;\n#endif // ANJAY_WITH_CON_ATTR\n    return attrs;\n}\n\n/* Using anjay_rid_t instead of int / unsigned in va_start is UB */\nstatic as_resource_entry_t *test_resource_entry(unsigned /*anjay_rid_t*/ rid,\n                                                ...) {\n    assert(rid <= UINT16_MAX);\n    as_resource_entry_t *resource = AVS_LIST_NEW_ELEMENT(as_resource_entry_t);\n    AVS_UNIT_ASSERT_NOT_NULL(resource);\n    resource->rid = (anjay_rid_t) rid;\n    va_list ap;\n    va_start(ap, rid);\n    as_resource_attrs_t *attrs;\n    while ((attrs = va_arg(ap, as_resource_attrs_t *))) {\n        AVS_LIST_APPEND(&resource->attrs, attrs);\n    }\n    va_end(ap);\n    return resource;\n}\n\nstatic as_default_attrs_t *test_default_attrs(anjay_ssid_t ssid,\n                                              int32_t min_period,\n                                              int32_t max_period,\n                                              int32_t min_eval_period,\n                                              int32_t max_eval_period,\n#ifdef ANJAY_WITH_LWM2M12\n                                              int32_t hqmax,\n#endif // ANJAY_WITH_LWM2M12\n                                              anjay_dm_con_attr_t con) {\n    as_default_attrs_t *attrs = AVS_LIST_NEW_ELEMENT(as_default_attrs_t);\n    AVS_UNIT_ASSERT_NOT_NULL(attrs);\n    attrs->ssid = ssid;\n    attrs->attrs = (anjay_dm_oi_attributes_t) {\n        .min_period = min_period,\n        .max_period = max_period,\n        .min_eval_period = min_eval_period,\n        .max_eval_period = max_eval_period,\n#ifdef ANJAY_WITH_LWM2M12\n        .hqmax = hqmax\n#endif // ANJAY_WITH_LWM2M12\n    };\n#ifdef ANJAY_WITH_CON_ATTR\n    attrs->attrs.con = con;\n#endif // ANJAY_WITH_CON_ATTR\n    return attrs;\n}\n\nstatic AVS_LIST(as_default_attrs_t)\ntest_default_attrlist(as_default_attrs_t *entry, ...) {\n    AVS_LIST(as_default_attrs_t) attrlist = NULL;\n    va_list ap;\n    va_start(ap, entry);\n    for (; entry; entry = va_arg(ap, as_default_attrs_t *)) {\n        AVS_LIST_APPEND(&attrlist, entry);\n    }\n    va_end(ap);\n    return attrlist;\n}\n\nstatic as_instance_entry_t *test_instance_entry(\n        anjay_iid_t iid, AVS_LIST(as_default_attrs_t) default_attrs, ...) {\n    as_instance_entry_t *instance = AVS_LIST_NEW_ELEMENT(as_instance_entry_t);\n    AVS_UNIT_ASSERT_NOT_NULL(instance);\n    instance->iid = iid;\n    instance->default_attrs = default_attrs;\n    va_list ap;\n    va_start(ap, default_attrs);\n    as_resource_entry_t *resource;\n    while ((resource = va_arg(ap, as_resource_entry_t *))) {\n        AVS_LIST_APPEND(&instance->resources, resource);\n    }\n    va_end(ap);\n    return instance;\n}\n\nstatic as_object_entry_t *test_object_entry(\n        anjay_oid_t oid, AVS_LIST(as_default_attrs_t) default_attrs, ...) {\n    as_object_entry_t *object = AVS_LIST_NEW_ELEMENT(as_object_entry_t);\n    AVS_UNIT_ASSERT_NOT_NULL(object);\n    object->oid = oid;\n    object->default_attrs = default_attrs;\n    va_list ap;\n    va_start(ap, default_attrs);\n    as_instance_entry_t *instance;\n    while ((instance = va_arg(ap, as_instance_entry_t *))) {\n        AVS_LIST_APPEND(&object->instances, instance);\n    }\n    va_end(ap);\n    return object;\n}\n\nstatic void assert_attrs_equal(const anjay_dm_oi_attributes_t *actual,\n                               const anjay_dm_oi_attributes_t *expected) {\n#ifdef ANJAY_WITH_CON_ATTR\n    AVS_UNIT_ASSERT_EQUAL(actual->con, expected->con);\n#endif // ANJAY_WITH_CON_ATTR\n    AVS_UNIT_ASSERT_EQUAL(actual->min_period, expected->min_period);\n    AVS_UNIT_ASSERT_EQUAL(actual->max_period, expected->max_period);\n    AVS_UNIT_ASSERT_EQUAL(actual->min_eval_period, expected->min_eval_period);\n    AVS_UNIT_ASSERT_EQUAL(actual->max_eval_period, expected->max_eval_period);\n#ifdef ANJAY_WITH_LWM2M12\n    AVS_UNIT_ASSERT_EQUAL(actual->hqmax, expected->hqmax);\n#endif // ANJAY_WITH_LWM2M12\n}\n\nstatic void assert_res_attrs_equal(const anjay_dm_r_attributes_t *actual,\n                                   const anjay_dm_r_attributes_t *expected) {\n    assert_attrs_equal(&actual->common, &expected->common);\n    AVS_UNIT_ASSERT_EQUAL(actual->greater_than, expected->greater_than);\n    AVS_UNIT_ASSERT_EQUAL(actual->less_than, expected->less_than);\n    AVS_UNIT_ASSERT_EQUAL(actual->step, expected->step);\n#ifdef ANJAY_WITH_LWM2M12\n    AVS_UNIT_ASSERT_EQUAL(actual->edge, expected->edge);\n#endif // ANJAY_WITH_LWM2M12\n}\n\nstatic void assert_as_default_attrs_equal(as_default_attrs_t *actual,\n                                          as_default_attrs_t *tmp_expected) {\n    AVS_UNIT_ASSERT_EQUAL(actual->ssid, tmp_expected->ssid);\n    assert_attrs_equal(&actual->attrs, &tmp_expected->attrs);\n    AVS_LIST_DELETE(&tmp_expected);\n}\n\nstatic void assert_as_resource_attrs_equal(as_resource_attrs_t *actual,\n                                           as_resource_attrs_t *tmp_expected) {\n    AVS_UNIT_ASSERT_EQUAL(actual->ssid, tmp_expected->ssid);\n    assert_res_attrs_equal(&actual->attrs, &tmp_expected->attrs);\n    AVS_LIST_DELETE(&tmp_expected);\n}\n\nstatic void assert_resource_equal(as_resource_entry_t *actual,\n                                  as_resource_entry_t *tmp_expected) {\n    AVS_UNIT_ASSERT_EQUAL(actual->rid, tmp_expected->rid);\n\n    size_t count = AVS_LIST_SIZE(tmp_expected->attrs);\n    AVS_UNIT_ASSERT_EQUAL(AVS_LIST_SIZE(actual->attrs), count);\n    AVS_LIST(as_resource_attrs_t) attrs = actual->attrs;\n    while (count--) {\n        assert_as_resource_attrs_equal(attrs,\n                                       AVS_LIST_DETACH(&tmp_expected->attrs));\n        attrs = AVS_LIST_NEXT(attrs);\n    }\n\n    AVS_LIST_DELETE(&tmp_expected);\n}\n\nstatic void assert_instance_equal(as_instance_entry_t *actual,\n                                  as_instance_entry_t *tmp_expected) {\n    AVS_UNIT_ASSERT_EQUAL(actual->iid, tmp_expected->iid);\n\n    size_t count = AVS_LIST_SIZE(tmp_expected->default_attrs);\n    AVS_UNIT_ASSERT_EQUAL(AVS_LIST_SIZE(actual->default_attrs), count);\n    AVS_LIST(as_default_attrs_t) default_attrs = actual->default_attrs;\n    while (count--) {\n        assert_as_default_attrs_equal(\n                default_attrs, AVS_LIST_DETACH(&tmp_expected->default_attrs));\n        default_attrs = AVS_LIST_NEXT(default_attrs);\n    }\n\n    count = AVS_LIST_SIZE(tmp_expected->resources);\n    AVS_UNIT_ASSERT_EQUAL(AVS_LIST_SIZE(actual->resources), count);\n    AVS_LIST(as_resource_entry_t) resource = actual->resources;\n    while (count--) {\n        assert_resource_equal(resource,\n                              AVS_LIST_DETACH(&tmp_expected->resources));\n        resource = AVS_LIST_NEXT(resource);\n    }\n\n    AVS_LIST_DELETE(&tmp_expected);\n}\n\nstatic void assert_object_equal(as_object_entry_t *actual,\n                                as_object_entry_t *tmp_expected) {\n    AVS_UNIT_ASSERT_EQUAL(actual->oid, tmp_expected->oid);\n    size_t count = AVS_LIST_SIZE(tmp_expected->default_attrs);\n    AVS_UNIT_ASSERT_EQUAL(AVS_LIST_SIZE(actual->default_attrs), count);\n    AVS_LIST(as_default_attrs_t) default_attrs = actual->default_attrs;\n    while (count--) {\n        assert_as_default_attrs_equal(\n                default_attrs, AVS_LIST_DETACH(&tmp_expected->default_attrs));\n        default_attrs = AVS_LIST_NEXT(default_attrs);\n    }\n\n    count = AVS_LIST_SIZE(tmp_expected->instances);\n    AVS_UNIT_ASSERT_EQUAL(AVS_LIST_SIZE(actual->instances), count);\n    AVS_LIST(as_instance_entry_t) instance = actual->instances;\n    while (count--) {\n        assert_instance_equal(instance,\n                              AVS_LIST_DETACH(&tmp_expected->instances));\n        instance = AVS_LIST_NEXT(instance);\n    }\n\n    AVS_LIST_DELETE(&tmp_expected);\n}\n\n#endif /* ATTR_STORAGE_TEST_H */\n"
  },
  {
    "path": "tests/core/attr_storage/persistence.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <string.h>\n\n#include <avsystem/commons/avs_stream.h>\n#include <avsystem/commons/avs_stream_inbuf.h>\n#include <avsystem/commons/avs_stream_outbuf.h>\n#include <avsystem/commons/avs_unit_test.h>\n\n#include <anjay/attr_storage.h>\n\n#include \"attr_storage_test.h\"\n#include \"src/core/attr_storage/anjay_attr_storage_private.h\"\n#include \"tests/utils/dm.h\"\n\n#define PERSIST_TEST_INIT(Size)                                        \\\n    char buf[Size];                                                    \\\n    avs_stream_outbuf_t outbuf = AVS_STREAM_OUTBUF_STATIC_INITIALIZER; \\\n    avs_stream_outbuf_set_buffer(&outbuf, buf, sizeof(buf));           \\\n    anjay_t *anjay = _anjay_test_dm_init(DM_TEST_CONFIGURATION());     \\\n    AVS_UNIT_ASSERT_NOT_NULL(anjay)\n\n#define PERSISTENCE_TEST_FINISH        \\\n    do {                               \\\n        _anjay_mock_dm_expect_clean(); \\\n        _anjay_test_dm_finish(anjay);  \\\n    } while (0)\n\n#define PERSIST_TEST_CHECK(Data)                                        \\\n    do {                                                                \\\n        AVS_UNIT_ASSERT_EQUAL(sizeof(Data) - 1,                         \\\n                              avs_stream_outbuf_offset(&outbuf));       \\\n        AVS_UNIT_ASSERT_EQUAL_BYTES_SIZED(Data, buf, sizeof(Data) - 1); \\\n        PERSISTENCE_TEST_FINISH;                                        \\\n    } while (0)\n\n#define MAGIC_HEADER_V0 \"FAS\\0\"\n#define MAGIC_HEADER_V5 \"FAS\\5\"\n\nAVS_UNIT_TEST(attr_storage_persistence, persist_empty) {\n    PERSIST_TEST_INIT(256);\n    AVS_UNIT_ASSERT_SUCCESS(\n            anjay_attr_storage_persist(anjay, (avs_stream_t *) &outbuf));\n    PERSIST_TEST_CHECK(MAGIC_HEADER_V5 \"\\x00\\x00\\x00\\x00\");\n}\n\n#define INSTALL_FAKE_OBJECT(Oid)                             \\\n    const anjay_dm_object_def_t *const OBJ##Oid =            \\\n            &(const anjay_dm_object_def_t) {                 \\\n                .oid = Oid,                                  \\\n                .handlers = { ANJAY_MOCK_DM_HANDLERS_BASIC } \\\n            };                                               \\\n    AVS_UNIT_ASSERT_SUCCESS(anjay_register_object(anjay, &OBJ##Oid))\n\nstatic void write_inst_attrs(anjay_unlocked_t *anjay,\n                             anjay_oid_t oid,\n                             anjay_iid_t iid,\n                             anjay_ssid_t ssid,\n                             const anjay_dm_oi_attributes_t *attrs) {\n    const anjay_dm_installed_object_t *obj =\n            _anjay_dm_find_object_by_oid(_anjay_get_dm(anjay), oid);\n    AVS_UNIT_ASSERT_NOT_NULL(obj);\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_dm_call_instance_write_default_attrs(\n            anjay, obj, iid, ssid, attrs));\n}\n\n#ifdef ANJAY_WITH_CON_ATTR\n\nstatic void write_obj_attrs(anjay_unlocked_t *anjay,\n                            anjay_oid_t oid,\n                            anjay_ssid_t ssid,\n                            const anjay_dm_oi_attributes_t *attrs) {\n    const anjay_dm_installed_object_t *obj =\n            _anjay_dm_find_object_by_oid(_anjay_get_dm(anjay), oid);\n    AVS_UNIT_ASSERT_NOT_NULL(obj);\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_dm_call_object_write_default_attrs(anjay, obj, ssid, attrs));\n}\n\nstatic void write_res_attrs(anjay_unlocked_t *anjay,\n                            anjay_oid_t oid,\n                            anjay_iid_t iid,\n                            anjay_rid_t rid,\n                            anjay_ssid_t ssid,\n                            const anjay_dm_r_attributes_t *attrs) {\n    const anjay_dm_installed_object_t *obj =\n            _anjay_dm_find_object_by_oid(_anjay_get_dm(anjay), oid);\n    AVS_UNIT_ASSERT_NOT_NULL(obj);\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_dm_call_resource_write_attrs(\n            anjay, obj, iid, rid, ssid, attrs));\n}\n\n#    ifdef ANJAY_WITH_LWM2M11\nstatic void write_res_instance_attrs(anjay_unlocked_t *anjay,\n                                     anjay_oid_t oid,\n                                     anjay_iid_t iid,\n                                     anjay_rid_t rid,\n                                     anjay_riid_t riid,\n                                     anjay_ssid_t ssid,\n                                     const anjay_dm_r_attributes_t *attrs) {\n    const anjay_dm_installed_object_t *obj =\n            _anjay_dm_find_object_by_oid(_anjay_get_dm(anjay), oid);\n    AVS_UNIT_ASSERT_NOT_NULL(obj);\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_dm_call_resource_instance_write_attrs(\n            anjay, obj, iid, rid, riid, ssid, attrs));\n}\n#    endif // ANJAY_WITH_LWM2M11\n\n#endif // ANJAY_WITH_CON_ATTR\n\n#ifdef ANJAY_WITH_LWM2M12\n#    define PERSISTED_HQMAX(value) value\n#    define PERSISTED_EDGE(value) value\n#else // ANJAY_WITH_LWM2M12\n#    define PERSISTED_HQMAX(value) \"\\xFF\\xFF\\xFF\\xFF\"\n#    define PERSISTED_EDGE(value) \"\\xFF\"\n#endif // ANJAY_WITH_LWM2M12\n\n// clang-format off\nstatic const char PERSIST_TEST_DATA[] =\n        MAGIC_HEADER_V5 \"\\x00\\x00\\x00\\x03\"  // 3 objects\n                        \"\\x00\\x04\"          // OID 4\n                        \"\\x00\\x00\\x00\\x02\"  // 2 object-level default attrs\n                        \"\\x00\\x0E\"          // SSID 14\n                        \"\\xFF\\xFF\\xFF\\xFF\"  // min period\n                        \"\\x00\\x00\\x00\\x03\"  // max period\n                        \"\\x00\\x00\\x00\\x0A\"  // min eval period\n                        \"\\x00\\x00\\x00\\x14\"  // max eval period\n        PERSISTED_HQMAX(\"\\xFF\\xFF\\xFF\\xFF\") // hqmax\n        \"\\xFF\"                              // confirmable\n        \"\\x00\\x21\"                          // SSID 33\n        \"\\x00\\x00\\x00\\x2A\"                  // min period\n        \"\\xFF\\xFF\\xFF\\xFF\"                  // max period\n        \"\\xFF\\xFF\\xFF\\xFF\"                  // min eval period\n        \"\\xFF\\xFF\\xFF\\xFF\"                  // max eval period\n        PERSISTED_HQMAX(\"\\x00\\x00\\x00\\x0A\") // hqmax\n        \"\\x00\"                              // confirmable\n        \"\\x00\\x00\\x00\\x00\"                  // 0 instance entries\n        \"\\x00\\x2A\"                          // OID 42\n        \"\\x00\\x00\\x00\\x00\"                  // 0 object-level default attrs\n        \"\\x00\\x00\\x00\\x01\"                  // 1 instance entry\n        \"\\x00\\x01\"                          // IID 1\n        \"\\x00\\x00\\x00\\x01\"                  // 1 instance-level default attr\n        \"\\x00\\x02\"                          // SSID 2\n        \"\\x00\\x00\\x00\\x07\"                  // min period\n        \"\\x00\\x00\\x00\\x0D\"                  // max period\n        \"\\xFF\\xFF\\xFF\\xFF\"                  // min eval period\n        \"\\xFF\\xFF\\xFF\\xFF\"                  // max eval period\n        PERSISTED_HQMAX(\"\\x00\\x00\\x00\\x02\") // hqmax\n        \"\\xFF\"                              // confirmable\n        \"\\x00\\x00\\x00\\x01\"                  // 1 resource entry\n        \"\\x00\\x03\"                          // RID 3\n        \"\\x00\\x00\\x00\\x02\"                  // 2 attr entries\n        \"\\x00\\x02\"                          // SSID 2\n        \"\\xFF\\xFF\\xFF\\xFF\"                  // min period\n        \"\\xFF\\xFF\\xFF\\xFF\"                  // max period\n        \"\\xFF\\xFF\\xFF\\xFF\"                  // min eval period\n        \"\\xFF\\xFF\\xFF\\xFF\"                  // max eval period\n        PERSISTED_HQMAX(\"\\xFF\\xFF\\xFF\\xFF\") // hqmax\n        /* greater than */ \"\\x3F\\xF0\\x00\\x00\\x00\\x00\\x00\\x00\"\n           /* less than */ \"\\xBF\\xF0\\x00\\x00\\x00\\x00\\x00\\x00\"\n                /* step */ \"\\x7F\\xF8\\x00\\x00\\x00\\x00\\x00\\x00\"\n                /* edge */ PERSISTED_EDGE(\"\\xFF\")\n        \"\\x01\"                              // confirmable\n        \"\\x00\\x07\"                          // SSID 7\n        \"\\x00\\x00\\x00\\x01\"                  // min period\n        \"\\x00\\x00\\x00\\x0E\"                  // max period\n        \"\\x00\\x00\\x00\\x03\"                  // min eval period\n        \"\\xFF\\xFF\\xFF\\xFF\"                  // max eval period\n        PERSISTED_HQMAX(\"\\xFF\\xFF\\xFF\\xFF\") // hqmax\n        /* greater than */ \"\\x7f\\xf8\\x00\\x00\\x00\\x00\\x00\\x00\"\n           /* less than */ \"\\x7f\\xf8\\x00\\x00\\x00\\x00\\x00\\x00\"\n                /* step */ \"\\x7f\\xf8\\x00\\x00\\x00\\x00\\x00\\x00\"\n                /* edge */ PERSISTED_EDGE(\"\\xFF\")\n        \"\\xFF\"                              // confirmable\n        \"\\x00\\x00\\x00\\x00\"                  // 0 resource instance entries\n        \"\\x02\\x05\"                          // OID 517\n        \"\\x00\\x00\\x00\\x00\"                  // 0 object-level default attrs\n        \"\\x00\\x00\\x00\\x01\"                  // 1 instance entry\n        \"\\x02\\x04\"                          // IID 516\n        \"\\x00\\x00\\x00\\x00\"                  // 0 instance-level default attrs\n        \"\\x00\\x00\\x00\\x01\"                  // 1 resource entry\n        \"\\x02\\x03\"                          // RID 515\n        \"\\x00\\x00\\x00\\x01\"                  // 1 attr entry\n        \"\\x02\\x02\"                          // SSID 514\n        \"\\x00\\x00\\x00\\x21\"                  // min period\n        \"\\xFF\\xFF\\xFF\\xFF\"                  // max period\n        \"\\xFF\\xFF\\xFF\\xFF\"                  // min eval period\n        \"\\x00\\x00\\x00\\x08\"                  // max eval period\n        PERSISTED_HQMAX(\"\\xFF\\xFF\\xFF\\xFF\") // hqmax\n        /* greater than */ \"\\x7f\\xf8\\x00\\x00\\x00\\x00\\x00\\x00\"\n           /* less than */ \"\\x7f\\xf8\\x00\\x00\\x00\\x00\\x00\\x00\"\n                /* step */ \"\\x40\\x45\\x00\\x00\\x00\\x00\\x00\\x00\"\n                /* edge */ PERSISTED_EDGE(\"\\xFF\")\n        \"\\xFF\"                              // confirmable\n#ifdef ANJAY_WITH_LWM2M11\n        \"\\x00\\x00\\x00\\x01\"  // 1 resource instance entry\n        \"\\x00\\x01\"          // RIID 1\n        \"\\x00\\x00\\x00\\x01\"  // 1 attr entry\n        \"\\x02\\x02\"          // SSID 514\n        \"\\x00\\x00\\x00\\x0A\"  // min period\n        \"\\x00\\x00\\x00\\x14\"  // max period\n        \"\\xFF\\xFF\\xFF\\xFF\"  // min eval period\n        \"\\xFF\\xFF\\xFF\\xFF\"  // max eval period\n        PERSISTED_HQMAX(\"\\x00\\x00\\x00\\x07\") // hqmax\n        /* greater than */ \"\\x40\\x45\\x00\\x00\\x00\\x00\\x00\\x00\"\n           /* less than */ \"\\x40\\x45\\x00\\x00\\x00\\x00\\x00\\x00\"\n                /* step */ \"\\x40\\x45\\x00\\x00\\x00\\x00\\x00\\x00\"\n                /* edge */ PERSISTED_EDGE(\"\\x00\")\n        \"\\xFF\"                              // confirmable\n#else  // ANJAY_WITH_LWM2M11\n        \"\\x00\\x00\\x00\\x00\"                  // 0 resource instance entries\n#endif // ANJAY_WITH_LWM2M11\n        ;\n// clang-format on\n\n#ifdef ANJAY_WITH_CON_ATTR\nstatic void persist_test_fill(anjay_t *anjay_locked) {\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    write_obj_attrs(anjay, 4, 33,\n                    &(const anjay_dm_oi_attributes_t) {\n                        .min_period = 42,\n                        .max_period = ANJAY_ATTRIB_INTEGER_NONE,\n                        .min_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n                        .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n                        .con = ANJAY_DM_CON_ATTR_NON\n#    ifdef ANJAY_WITH_LWM2M12\n                        ,\n                        .hqmax = 10\n#    endif // ANJAY_WITH_LWM2M12\n                    });\n    write_obj_attrs(anjay, 4, 14,\n                    &(const anjay_dm_oi_attributes_t) {\n                        .min_period = ANJAY_ATTRIB_INTEGER_NONE,\n                        .max_period = 3,\n                        .min_eval_period = 10,\n                        .max_eval_period = 20,\n                        .con = ANJAY_DM_CON_ATTR_NONE\n#    ifdef ANJAY_WITH_LWM2M12\n                        ,\n                        .hqmax = ANJAY_ATTRIB_INTEGER_NONE\n#    endif // ANJAY_WITH_LWM2M12\n                    });\n    write_inst_attrs(anjay, 42, 1, 2,\n                     &(const anjay_dm_oi_attributes_t) {\n                         .min_period = 7,\n                         .max_period = 13,\n                         .min_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n                         .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n                         .con = ANJAY_DM_CON_ATTR_NONE\n#    ifdef ANJAY_WITH_LWM2M12\n                         ,\n                         .hqmax = 2\n#    endif // ANJAY_WITH_LWM2M12\n                     });\n    write_res_attrs(anjay, 42, 1, 3, 2,\n                    &(const anjay_dm_r_attributes_t) {\n                        .common = {\n                            .min_period = ANJAY_ATTRIB_INTEGER_NONE,\n                            .max_period = ANJAY_ATTRIB_INTEGER_NONE,\n                            .min_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n                            .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n                            .con = ANJAY_DM_CON_ATTR_CON\n#    ifdef ANJAY_WITH_LWM2M12\n                            ,\n                            .hqmax = ANJAY_ATTRIB_INTEGER_NONE\n#    endif // ANJAY_WITH_LWM2M12\n                        },\n                        .greater_than = 1.0,\n                        .less_than = -1.0,\n                        .step = ANJAY_ATTRIB_DOUBLE_NONE\n#    ifdef ANJAY_WITH_LWM2M12\n                        ,\n                        .edge = ANJAY_DM_EDGE_ATTR_NONE\n#    endif // ANJAY_WITH_LWM2M12\n                    });\n    write_res_attrs(anjay, 42, 1, 3, 7,\n                    &(const anjay_dm_r_attributes_t) {\n                        .common = {\n                            .min_period = 1,\n                            .max_period = 14,\n                            .min_eval_period = 3,\n                            .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n                            .con = ANJAY_DM_CON_ATTR_NONE\n#    ifdef ANJAY_WITH_LWM2M12\n                            ,\n                            .hqmax = ANJAY_ATTRIB_INTEGER_NONE\n#    endif // ANJAY_WITH_LWM2M12\n                        },\n                        .greater_than = ANJAY_ATTRIB_DOUBLE_NONE,\n                        .less_than = ANJAY_ATTRIB_DOUBLE_NONE,\n                        .step = ANJAY_ATTRIB_DOUBLE_NONE\n#    ifdef ANJAY_WITH_LWM2M12\n                        ,\n                        .edge = ANJAY_DM_EDGE_ATTR_NONE\n#    endif // ANJAY_WITH_LWM2M12\n                    });\n    write_res_attrs(anjay, 517, 516, 515, 514,\n                    &(const anjay_dm_r_attributes_t) {\n                        .common = {\n                            .min_period = 33,\n                            .max_period = ANJAY_ATTRIB_INTEGER_NONE,\n                            .min_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n                            .max_eval_period = 8,\n                            .con = ANJAY_DM_CON_ATTR_NONE\n#    ifdef ANJAY_WITH_LWM2M12\n                            ,\n                            .hqmax = ANJAY_ATTRIB_INTEGER_NONE\n#    endif // ANJAY_WITH_LWM2M12\n                        },\n                        .greater_than = ANJAY_ATTRIB_DOUBLE_NONE,\n                        .less_than = ANJAY_ATTRIB_DOUBLE_NONE,\n                        .step = 42.0\n#    ifdef ANJAY_WITH_LWM2M12\n                        ,\n                        .edge = ANJAY_DM_EDGE_ATTR_NONE\n#    endif // ANJAY_WITH_LWM2M12\n                    });\n#    ifdef ANJAY_WITH_LWM2M11\n    write_res_instance_attrs(\n            anjay, 517, 516, 515, 1, 514,\n            &(const anjay_dm_r_attributes_t) {\n                .common = {\n                    .min_period = 10,\n                    .max_period = 20,\n                    .min_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n                    .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n                    .con = ANJAY_DM_CON_ATTR_NONE\n#        ifdef ANJAY_WITH_LWM2M12\n                    ,\n                    .hqmax = 7\n#        endif // ANJAY_WITH_LWM2M12\n                },\n                .greater_than = 42.0,\n                .less_than = 42.0,\n                .step = 42.0\n#        ifdef ANJAY_WITH_LWM2M12\n                ,\n                .edge = ANJAY_DM_EDGE_ATTR_FALLING\n#        endif // ANJAY_WITH_LWM2M12\n            });\n#    endif // ANJAY_WITH_LWM2M11\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n}\n\nAVS_UNIT_TEST(attr_storage_persistence, persist_full) {\n    PERSIST_TEST_INIT(512);\n    INSTALL_FAKE_OBJECT(4);\n    INSTALL_FAKE_OBJECT(42);\n    INSTALL_FAKE_OBJECT(517);\n    persist_test_fill(anjay);\n    AVS_UNIT_ASSERT_SUCCESS(\n            anjay_attr_storage_persist(anjay, (avs_stream_t *) &outbuf));\n    PERSIST_TEST_CHECK(PERSIST_TEST_DATA);\n}\n\nAVS_UNIT_TEST(attr_storage_persistence, persist_not_enough_space) {\n    PERSIST_TEST_INIT(128);\n    INSTALL_FAKE_OBJECT(4);\n    INSTALL_FAKE_OBJECT(42);\n    INSTALL_FAKE_OBJECT(517);\n    persist_test_fill(anjay);\n    AVS_UNIT_ASSERT_FAILED(\n            anjay_attr_storage_persist(anjay, (avs_stream_t *) &outbuf));\n    PERSISTENCE_TEST_FINISH;\n}\n#endif // ANJAY_WITH_CON_ATTR\n\n#define RESTORE_TEST_INIT(Data)                                     \\\n    avs_stream_inbuf_t inbuf = AVS_STREAM_INBUF_STATIC_INITIALIZER; \\\n    avs_stream_inbuf_set_buffer(&inbuf, (Data), sizeof(Data) - 1);  \\\n    anjay_t *anjay = _anjay_test_dm_init(DM_TEST_CONFIGURATION());  \\\n    AVS_UNIT_ASSERT_NOT_NULL(anjay)\n\nAVS_UNIT_TEST(attr_storage_persistence, restore_empty) {\n    RESTORE_TEST_INIT(\"\");\n    AVS_UNIT_ASSERT_FAILED(\n            anjay_attr_storage_restore(anjay, (avs_stream_t *) &inbuf));\n    PERSISTENCE_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(attr_storage_persistence, restore_no_objects) {\n    RESTORE_TEST_INIT(PERSIST_TEST_DATA);\n    AVS_UNIT_ASSERT_SUCCESS(\n            anjay_attr_storage_restore(anjay, (avs_stream_t *) &inbuf));\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    AVS_UNIT_ASSERT_NULL(anjay_unlocked->attr_storage.objects);\n    ANJAY_MUTEX_UNLOCK(anjay);\n    PERSISTENCE_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(attr_storage_persistence, restore_one_object) {\n    RESTORE_TEST_INIT(PERSIST_TEST_DATA);\n    INSTALL_FAKE_OBJECT(42);\n\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ42, 0, (const anjay_iid_t[]) { 1, ANJAY_ID_INVALID });\n    const anjay_mock_dm_res_entry_t resources[] = { { 3, ANJAY_DM_RES_RW,\n                                                      ANJAY_DM_RES_PRESENT },\n                                                    ANJAY_MOCK_DM_RES_END };\n    /**\n     * First call to list_resources from\n     * _anjay_attr_storage_remove_absent_resources()\n     */\n    _anjay_mock_dm_expect_list_resources(anjay, &OBJ42, 1, 0, resources);\n#ifdef ANJAY_WITH_LWM2M11\n    /**\n     * Second call to list_resources from\n     * _anjay_attr_storage_remove_absent_resource_instances() because it needs\n     * to determine if resource is multiple before calling\n     * _anjay_dm_foreach_resource_instance()\n     */\n    _anjay_mock_dm_expect_list_resources(anjay, &OBJ42, 1, 0, resources);\n#endif // ANJAY_WITH_LWM2M11\n    AVS_UNIT_ASSERT_SUCCESS(\n            anjay_attr_storage_restore(anjay, (avs_stream_t *) &inbuf));\n\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    AVS_UNIT_ASSERT_EQUAL(AVS_LIST_SIZE(anjay_unlocked->attr_storage.objects),\n                          1);\n    assert_object_equal(\n            anjay_unlocked->attr_storage.objects,\n            test_object_entry(\n                    42, NULL,\n                    test_instance_entry(\n                            1,\n                            test_default_attrlist(\n                                    test_default_attrs(\n                                            2, 7, 13, ANJAY_ATTRIB_INTEGER_NONE,\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n#ifdef ANJAY_WITH_LWM2M12\n                                            2,\n#endif // ANJAY_WITH_LWM2M12\n                                            ANJAY_DM_CON_ATTR_NONE),\n                                    NULL),\n                            test_resource_entry(\n                                    3,\n                                    test_resource_attrs(\n                                            2,\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n#ifdef ANJAY_WITH_LWM2M12\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n#endif // ANJAY_WITH_LWM2M12\n                                            1.0,\n                                            -1.0,\n                                            ANJAY_ATTRIB_DOUBLE_NONE,\n#ifdef ANJAY_WITH_LWM2M12\n                                            ANJAY_DM_EDGE_ATTR_NONE,\n#endif // ANJAY_WITH_LWM2M12\n                                            ANJAY_DM_CON_ATTR_CON),\n                                    test_resource_attrs(\n                                            7,\n                                            1,\n                                            14,\n                                            3,\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n#ifdef ANJAY_WITH_LWM2M12\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n#endif // ANJAY_WITH_LWM2M12\n                                            ANJAY_ATTRIB_DOUBLE_NONE,\n                                            ANJAY_ATTRIB_DOUBLE_NONE,\n                                            ANJAY_ATTRIB_DOUBLE_NONE,\n#ifdef ANJAY_WITH_LWM2M12\n                                            ANJAY_DM_EDGE_ATTR_NONE,\n#endif // ANJAY_WITH_LWM2M12\n                                            ANJAY_DM_CON_ATTR_NONE),\n                                    NULL),\n                            NULL),\n                    NULL));\n    ANJAY_MUTEX_UNLOCK(anjay);\n    PERSISTENCE_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(attr_storage_persistence, restore_all_objects) {\n    RESTORE_TEST_INIT(PERSIST_TEST_DATA);\n    INSTALL_FAKE_OBJECT(4);\n    INSTALL_FAKE_OBJECT(42);\n    INSTALL_FAKE_OBJECT(69);\n    INSTALL_FAKE_OBJECT(514);\n    INSTALL_FAKE_OBJECT(517);\n\n    // this will be cleared\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    write_inst_attrs(anjay_unlocked, 69, 68, 67,\n                     &(const anjay_dm_oi_attributes_t) {\n                         .min_period = 66,\n                         .max_period = 65\n#ifdef ANJAY_WITH_CON_ATTR\n                         ,\n                         .con = ANJAY_DM_CON_ATTR_NONE\n#endif // ANJAY_WITH_CON_ATTR\n                     });\n    ANJAY_MUTEX_UNLOCK(anjay);\n\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ4, 0, (const anjay_iid_t[]) { ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ42, 0, (const anjay_iid_t[]) { 1, ANJAY_ID_INVALID });\n    const anjay_mock_dm_res_entry_t resources_of_obj42[] = {\n        { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT }, ANJAY_MOCK_DM_RES_END\n    };\n    /**\n     * First call to list_resources from\n     * _anjay_attr_storage_remove_absent_resources()\n     */\n    _anjay_mock_dm_expect_list_resources(anjay, &OBJ42, 1, 0,\n                                         resources_of_obj42);\n#ifdef ANJAY_WITH_LWM2M11\n    /**\n     * Second call to list_resources from\n     * _anjay_attr_storage_remove_absent_resource_instances() because it needs\n     * to determine if resource is multiple before calling\n     * _anjay_dm_foreach_resource_instance()\n     */\n    _anjay_mock_dm_expect_list_resources(anjay, &OBJ42, 1, 0,\n                                         resources_of_obj42);\n#endif // ANJAY_WITH_LWM2M11\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ517, 0, (const anjay_iid_t[]) { 516, ANJAY_ID_INVALID });\n    const anjay_mock_dm_res_entry_t resources_of_obj517[] = {\n        { 515, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT }, ANJAY_MOCK_DM_RES_END\n    };\n    /**\n     * First call to list_resources from\n     * _anjay_attr_storage_remove_absent_resources()\n     */\n    _anjay_mock_dm_expect_list_resources(anjay, &OBJ517, 516, 0,\n                                         resources_of_obj517);\n#ifdef ANJAY_WITH_LWM2M11\n    /**\n     * Second call to list_resources from\n     * _anjay_attr_storage_remove_absent_resource_instances() because it needs\n     * to determine if resource is multiple before calling\n     * _anjay_dm_foreach_resource_instance()\n     */\n    _anjay_mock_dm_expect_list_resources(anjay, &OBJ517, 516, 0,\n                                         resources_of_obj517);\n#endif // ANJAY_WITH_LWM2M11\n    AVS_UNIT_ASSERT_SUCCESS(\n            anjay_attr_storage_restore(anjay, (avs_stream_t *) &inbuf));\n\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    AVS_UNIT_ASSERT_EQUAL(AVS_LIST_SIZE(anjay_unlocked->attr_storage.objects),\n                          3);\n\n    // object 4\n    assert_object_equal(\n            anjay_unlocked->attr_storage.objects,\n            test_object_entry(\n                    4,\n                    test_default_attrlist(\n                            test_default_attrs(14, ANJAY_ATTRIB_INTEGER_NONE, 3,\n                                               10, 20,\n#ifdef ANJAY_WITH_LWM2M12\n                                               ANJAY_ATTRIB_INTEGER_NONE,\n#endif // ANJAY_WITH_LWM2M12\n                                               ANJAY_DM_CON_ATTR_NONE),\n                            test_default_attrs(33, 42,\n                                               ANJAY_ATTRIB_INTEGER_NONE,\n                                               ANJAY_ATTRIB_INTEGER_NONE,\n                                               ANJAY_ATTRIB_INTEGER_NONE,\n#ifdef ANJAY_WITH_LWM2M12\n                                               10,\n#endif // ANJAY_WITH_LWM2M12\n                                               ANJAY_DM_CON_ATTR_NON),\n                            NULL),\n                    NULL));\n\n    // object 42\n    assert_object_equal(\n            AVS_LIST_NEXT(anjay_unlocked->attr_storage.objects),\n            test_object_entry(\n                    42, NULL,\n                    test_instance_entry(\n                            1,\n                            test_default_attrlist(\n                                    test_default_attrs(\n                                            2, 7, 13, ANJAY_ATTRIB_INTEGER_NONE,\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n#ifdef ANJAY_WITH_LWM2M12\n                                            2,\n#endif // ANJAY_WITH_LWM2M12\n                                            ANJAY_DM_CON_ATTR_NONE),\n                                    NULL),\n                            test_resource_entry(\n                                    3,\n                                    test_resource_attrs(\n                                            2,\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n#ifdef ANJAY_WITH_LWM2M12\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n#endif // ANJAY_WITH_LWM2M12\n                                            1.0,\n                                            -1.0,\n                                            ANJAY_ATTRIB_DOUBLE_NONE,\n#ifdef ANJAY_WITH_LWM2M12\n                                            ANJAY_DM_EDGE_ATTR_NONE,\n#endif // ANJAY_WITH_LWM2M12\n                                            ANJAY_DM_CON_ATTR_CON),\n                                    test_resource_attrs(\n                                            7,\n                                            1,\n                                            14,\n                                            3,\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n#ifdef ANJAY_WITH_LWM2M12\n                                            ANJAY_ATTRIB_INTEGER_NONE,\n#endif // ANJAY_WITH_LWM2M12\n                                            ANJAY_ATTRIB_DOUBLE_NONE,\n                                            ANJAY_ATTRIB_DOUBLE_NONE,\n                                            ANJAY_ATTRIB_DOUBLE_NONE,\n#ifdef ANJAY_WITH_LWM2M12\n                                            ANJAY_DM_EDGE_ATTR_NONE,\n#endif // ANJAY_WITH_LWM2M12\n                                            ANJAY_DM_CON_ATTR_NONE),\n                                    NULL),\n                            NULL),\n                    NULL));\n\n    // object 517\n    assert_object_equal(\n            AVS_LIST_NTH(anjay_unlocked->attr_storage.objects, 2),\n            test_object_entry(517, NULL,\n                              test_instance_entry(\n                                      516,\n                                      NULL,\n                                      test_resource_entry(\n                                              515,\n                                              test_resource_attrs(\n                                                      514,\n                                                      33,\n                                                      ANJAY_ATTRIB_INTEGER_NONE,\n                                                      ANJAY_ATTRIB_INTEGER_NONE,\n                                                      8,\n#ifdef ANJAY_WITH_LWM2M12\n                                                      ANJAY_ATTRIB_INTEGER_NONE,\n#endif // ANJAY_WITH_LWM2M12\n                                                      ANJAY_ATTRIB_DOUBLE_NONE,\n                                                      ANJAY_ATTRIB_DOUBLE_NONE,\n                                                      42.0,\n#ifdef ANJAY_WITH_LWM2M12\n                                                      ANJAY_DM_EDGE_ATTR_NONE,\n#endif // ANJAY_WITH_LWM2M12\n                                                      ANJAY_DM_CON_ATTR_NONE),\n                                              NULL),\n                                      NULL),\n                              NULL));\n    ANJAY_MUTEX_UNLOCK(anjay);\n    PERSISTENCE_TEST_FINISH;\n}\n\nstatic const char CLEARING_TEST_DATA[] =\n        MAGIC_HEADER_V0 \"\\x00\\x00\\x00\\x02\" // 2 objects\n                        \"\\x00\\x2A\"         // OID 42\n                        \"\\x00\\x00\\x00\\x00\" // 0 object-level default attrs\n                        \"\\x00\\x00\\x00\\x01\" // 1 instance entry\n                        \"\\x00\\x01\"         // IID 1\n                        \"\\x00\\x00\\x00\\x00\" // 0 instance-level default attr\n                        \"\\x00\\x00\\x00\\x01\" // 1 resource entry\n                        \"\\x00\\x03\"         // RID 3\n                        \"\\x00\\x00\\x00\\x02\" // 2 attr entries\n                        \"\\x00\\x02\"         // SSID 2\n                        \"\\xFF\\xFF\\xFF\\xFF\" // min period\n                        \"\\xFF\\xFF\\xFF\\xFF\" // max period\n                        /* greater than */ \"\\x3F\\xF0\\x00\\x00\\x00\\x00\\x00\\x00\"\n                        /* less than */ \"\\xBF\\xF0\\x00\\x00\\x00\\x00\\x00\\x00\"\n                        /* step */ \"\\x7F\\xF8\\x00\\x00\\x00\\x00\\x00\\x00\"\n                        \"\\x00\\x07\"         // SSID 7\n                        \"\\x00\\x00\\x00\\x01\" // min period\n                        \"\\x00\\x00\\x00\\x0E\" // max period\n                        /* greater than */ \"\\x7f\\xf8\\x00\\x00\\x00\\x00\\x00\\x00\"\n                        /* less than */ \"\\x7f\\xf8\\x00\\x00\\x00\\x00\\x00\\x00\"\n                        /* step */ \"\\x7f\\xf8\\x00\\x00\\x00\\x00\\x00\\x00\"\n                        \"\\x02\\x05\"         // OID 517\n                        \"\\x00\\x00\\x00\\x00\" // 0 object-level default attrs\n                        \"\\x00\\x00\\x00\\x01\" // 1 instance entry\n                        \"\\x02\\x04\"         // IID 516\n                        \"\\x00\\x00\\x00\\x00\" // 0 instance-level default attrs\n                        \"\\x00\\x00\\x00\\x01\" // 1 resource entry\n                        \"\\x02\\x03\"         // RID 515\n                        \"\\x00\\x00\\x00\\x01\" // 1 attr entry\n                        \"\\x02\\x02\"         // SSID 514\n                        \"\\x00\\x00\\x00\\x21\" // min period\n                        \"\\xFF\\xFF\\xFF\\xFF\" // max period\n                        /* greater than */ \"\\x7f\\xf8\\x00\\x00\\x00\\x00\\x00\\x00\"\n                        /* less than */ \"\\x7f\\xf8\\x00\\x00\\x00\\x00\\x00\\x00\"\n                        /* step */ \"\\x40\\x45\\x00\\x00\\x00\\x00\\x00\\x00\";\n\nAVS_UNIT_TEST(attr_storage_persistence, restore_no_instances) {\n    RESTORE_TEST_INIT(CLEARING_TEST_DATA);\n    INSTALL_FAKE_OBJECT(42);\n    INSTALL_FAKE_OBJECT(517);\n\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ42, 0, (const anjay_iid_t[]) { ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ517, 0, (const anjay_iid_t[]) { ANJAY_ID_INVALID });\n    AVS_UNIT_ASSERT_SUCCESS(\n            anjay_attr_storage_restore(anjay, (avs_stream_t *) &inbuf));\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    AVS_UNIT_ASSERT_NULL(anjay_unlocked->attr_storage.objects);\n    ANJAY_MUTEX_UNLOCK(anjay);\n    PERSISTENCE_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(attr_storage_persistence, restore_no_present_resources) {\n    RESTORE_TEST_INIT(CLEARING_TEST_DATA);\n    INSTALL_FAKE_OBJECT(42);\n    INSTALL_FAKE_OBJECT(517);\n\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ42, 0, (const anjay_iid_t[]) { 1, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ42, 1, 0,\n            (const anjay_mock_dm_res_entry_t[]) { { 3, ANJAY_DM_RES_RW,\n                                                    ANJAY_DM_RES_ABSENT },\n                                                  ANJAY_MOCK_DM_RES_END });\n#ifdef ANJAY_WITH_LWM2M11\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ42, 1, 0,\n            (const anjay_mock_dm_res_entry_t[]) { { 3, ANJAY_DM_RES_RW,\n                                                    ANJAY_DM_RES_ABSENT },\n                                                  ANJAY_MOCK_DM_RES_END });\n#endif // ANJAY_WITH_LWM2M11\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ517, 0, (const anjay_iid_t[]) { 516, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ517, 516, 0,\n            (const anjay_mock_dm_res_entry_t[]) { { 515, ANJAY_DM_RES_RW,\n                                                    ANJAY_DM_RES_ABSENT },\n                                                  ANJAY_MOCK_DM_RES_END });\n#ifdef ANJAY_WITH_LWM2M11\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ517, 516, 0,\n            (const anjay_mock_dm_res_entry_t[]) { { 515, ANJAY_DM_RES_RW,\n                                                    ANJAY_DM_RES_ABSENT },\n                                                  ANJAY_MOCK_DM_RES_END });\n#endif // ANJAY_WITH_LWM2M11\n    AVS_UNIT_ASSERT_SUCCESS(\n            anjay_attr_storage_restore(anjay, (avs_stream_t *) &inbuf));\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    AVS_UNIT_ASSERT_NULL(anjay_unlocked->attr_storage.objects);\n    ANJAY_MUTEX_UNLOCK(anjay);\n    PERSISTENCE_TEST_FINISH;\n}\n\nstatic const char RESTORE_BROKEN_DATA[] =\n        MAGIC_HEADER_V0 \"\\x00\\x00\\x00\\x03\" // 3 objects\n                        \"\\x00\\x04\"         // OID 4\n                        \"\\x00\\x00\\x00\\x02\" // 2 object-level default attrs\n                        \"\\x00\\x0E\"         // SSID 14\n                        \"\\xFF\\xFF\\xFF\\xFF\" // min period\n                        \"\\x00\\x00\\x00\\x03\" // max period\n                        \"\\x7f\\xf8\\x00\\x00\\x00\\x00\\x00\\x00\" // greater than\n                        \"\\x7f\\xf8\\x00\\x00\\x00\\x00\\x00\\x00\" // less than\n                        \"\\x7f\\xf8\\x00\\x00\\x00\\x00\\x00\\x00\" // step\n                        \"\\x00\\x21\"                         // SSID 33\n                        \"\\x00\\x00\\x00\\x2A\"                 // min period\n                        \"\\xFF\\xFF\\xFF\\xFF\"                 // max period\n                        \"\\x7f\\xf8\\x00\\x00\\x00\\x00\\x00\\x00\" // greater than\n                        \"\\x7f\\xf8\\x00\\x00\\x00\\x00\\x00\\x00\" // less than\n                        \"\\x7f\\xf8\\x00\\x00\\x00\\x00\\x00\\x00\" // step\n                        \"\\x00\\x00\\x00\\x00\"                 // 0 instance entries\n                        \"\\x00\\x2A\"                         // OID 42\n                        \"\\x00\\x00\\x00\\x00\" // 0 object-level default attrs\n                        \"\\x00\\x00\\x00\\x01\" // 1 instance entry\n                        \"\\x00\\x01\"         // IID 1\n                        \"\\x00\\x00\\x00\\x01\" // 1 instance-level default attr\n                        \"\\x00\\x02\"         // SSID 2\n                        \"\\x00\\x00\\x00\\x07\" // min period\n                        \"\\x00\\x00\\x00\\x0D\" // max period\n                        \"\\x7f\\xf8\\x00\\x00\\x00\\x00\\x00\\x00\" // greater than\n                        \"\\x7f\\xf8\\x00\\x00\\x00\\x00\\x00\\x00\" // less than\n                        \"\\x7f\\xf8\\x00\\x00\\x00\\x00\\x00\\x00\" // step\n                        \"\\x00\\x00\\x00\\x01\"                 // 1 resource entry\n                        \"\\x00\\x03\"                         // RID 3\n                        \"\\x00\\x00\\x00\\x02\"                 // 2 attr entries\n                        \"\\x00\\x02\"                         // SSID 2\n                        \"\\xFF\\xFF\\xFF\\xFF\"                 // min period\n                        \"\\xFF\\xFF\\xFF\\xFF\"                 // max period\n                        /* greater than */ \"\\x3F\\xF0\\x00\\x00\\x00\\x00\\x00\\x00\"\n                        /* less than */ \"\\xBF\\xF0\\x00\\x00\\x00\\x00\\x00\\x00\"\n                        \"\\x7f\"; /* premature end of data */\n\nAVS_UNIT_TEST(attr_storage_persistence, restore_broken_stream) {\n    RESTORE_TEST_INIT(RESTORE_BROKEN_DATA);\n    INSTALL_FAKE_OBJECT(4);\n    INSTALL_FAKE_OBJECT(42);\n    INSTALL_FAKE_OBJECT(517);\n\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    write_inst_attrs(anjay_unlocked, 517, 518, 519,\n                     &(const anjay_dm_oi_attributes_t) {\n                         .min_period = 520,\n                         .max_period = 521,\n                         .min_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n                         .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n#ifdef ANJAY_WITH_CON_ATTR\n                         .con = ANJAY_DM_CON_ATTR_NONE,\n#endif // ANJAY_WITH_CON_ATTR\n#ifdef ANJAY_WITH_LWM2M12\n                         .hqmax = ANJAY_ATTRIB_INTEGER_NONE,\n#endif // ANJAY_WITH_LWM2M12\n                     });\n    ANJAY_MUTEX_UNLOCK(anjay);\n\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ517, 0, (const anjay_iid_t[]) { 518, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(anjay, &OBJ517, 518, 0,\n                                         (const anjay_mock_dm_res_entry_t[]) {\n                                                 ANJAY_MOCK_DM_RES_END });\n\n    AVS_UNIT_ASSERT_FAILED(\n            anjay_attr_storage_restore(anjay, (avs_stream_t *) &inbuf));\n\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    // Previously set attributes should remain untouched\n    AVS_UNIT_ASSERT_EQUAL(AVS_LIST_SIZE(anjay_unlocked->attr_storage.objects),\n                          1);\n    assert_object_equal(\n            anjay_unlocked->attr_storage.objects,\n            test_object_entry(\n                    517, NULL,\n                    test_instance_entry(\n                            518,\n                            test_default_attrs(519,\n                                               520,\n                                               521,\n                                               ANJAY_ATTRIB_INTEGER_NONE,\n                                               ANJAY_ATTRIB_INTEGER_NONE,\n#ifdef ANJAY_WITH_LWM2M12\n                                               ANJAY_ATTRIB_INTEGER_NONE,\n#endif // ANJAY_WITH_LWM2M12\n                                               ANJAY_DM_CON_ATTR_NONE),\n                            NULL),\n                    NULL));\n    ANJAY_MUTEX_UNLOCK(anjay);\n    PERSISTENCE_TEST_FINISH;\n}\n\nstatic const char INSANE_TEST_DATA[] =\n        MAGIC_HEADER_V0 \"\\x00\\x00\\x00\\x03\" // 3 objects\n                        \"\\x00\\x04\"         // OID 4\n                        \"\\x00\\x00\\x00\\x02\" // 2 object-level default attrs\n                        \"\\x00\\x0E\"         // SSID 14\n                        \"\\xFF\\xFF\\xFF\\xFF\" // min period\n                        \"\\x00\\x00\\x00\\x03\" // max period\n                        \"\\x00\\x21\"         // SSID 33\n                        \"\\x00\\x00\\x00\\x2A\" // min period\n                        \"\\xFF\\xFF\\xFF\\xFF\" // max period\n                        \"\\x00\\x00\\x00\\x00\" // 0 instance entries\n                        \"\\x00\\x2A\"         // OID 42\n                        \"\\x00\\x00\\x00\\x00\" // 0 object-level default attrs\n                        \"\\x00\\x00\\x00\\x01\" // 1 instance entry\n                        \"\\x00\\x01\"         // IID 1\n                        \"\\x00\\x00\\x00\\x01\" // 1 instance-level default attr\n                        \"\\x00\\x02\"         // SSID 2\n                        \"\\x00\\x00\\x00\\x07\" // min period\n                        \"\\x00\\x00\\x00\\x0D\" // max period\n                        \"\\x00\\x00\\x00\\x01\" // 1 resource entry\n                        \"\\x00\\x03\"         // RID 3\n                        \"\\x00\\x00\\x00\\x02\" // 2 attr entries\n                        /*********** INVALID SSID ORDER FOLLOW ***********/\n                        \"\\x00\\x07\"         // SSID 7\n                        \"\\x00\\x00\\x00\\x01\" // min period\n                        \"\\x00\\x00\\x00\\x0E\" // max period\n                        /* greater than */ \"\\x7f\\xf8\\x00\\x00\\x00\\x00\\x00\\x00\"\n                        /* less than */ \"\\x7f\\xf8\\x00\\x00\\x00\\x00\\x00\\x00\"\n                        /* step */ \"\\x7f\\xf8\\x00\\x00\\x00\\x00\\x00\\x00\"\n                        \"\\x00\\x02\"         // SSID 2\n                        \"\\xFF\\xFF\\xFF\\xFF\" // min period\n                        \"\\xFF\\xFF\\xFF\\xFF\" // max period\n                        /* greater than */ \"\\x3F\\xF0\\x00\\x00\\x00\\x00\\x00\\x00\"\n                        /* less than */ \"\\xBF\\xF0\\x00\\x00\\x00\\x00\\x00\\x00\"\n                        /* step */ \"\\x7F\\xF8\\x00\\x00\\x00\\x00\\x00\\x00\"\n                        /************ INVALID SSID ORDER END ************/\n                        \"\\x02\\x05\"         // OID 517\n                        \"\\x00\\x00\\x00\\x00\" // 0 object-level default attrs\n                        \"\\x00\\x00\\x00\\x01\" // 1 instance entry\n                        \"\\x02\\x04\"         // IID 516\n                        \"\\x00\\x00\\x00\\x00\" // 0 instance-level default attrs\n                        \"\\x00\\x00\\x00\\x01\" // 1 resource entry\n                        \"\\x02\\x03\"         // RID 515\n                        \"\\x00\\x00\\x00\\x01\" // 1 attr entry\n                        \"\\x02\\x02\"         // SSID 514\n                        \"\\x00\\x00\\x00\\x21\" // min period\n                        \"\\xFF\\xFF\\xFF\\xFF\" // max period\n                        /* greater than */ \"\\x7f\\xf8\\x00\\x00\\x00\\x00\\x00\\x00\"\n                        /* less than */ \"\\x7f\\xf8\\x00\\x00\\x00\\x00\\x00\\x00\"\n                        /* step */ \"\\x40\\x45\\x00\\x00\\x00\\x00\\x00\\x00\";\n\nAVS_UNIT_TEST(attr_storage_persistence, restore_insane_data) {\n    RESTORE_TEST_INIT(INSANE_TEST_DATA);\n    INSTALL_FAKE_OBJECT(4);\n    INSTALL_FAKE_OBJECT(42);\n    INSTALL_FAKE_OBJECT(517);\n\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    write_inst_attrs(anjay_unlocked, 517, 518, 519,\n                     &(const anjay_dm_oi_attributes_t) {\n                         .min_period = 520,\n                         .max_period = 521,\n                         .min_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n                         .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n#ifdef ANJAY_WITH_CON_ATTR\n                         .con = ANJAY_DM_CON_ATTR_NONE,\n#endif // ANJAY_WITH_CON_ATTR\n#ifdef ANJAY_WITH_LWM2M12\n                         .hqmax = ANJAY_ATTRIB_INTEGER_NONE,\n#endif // ANJAY_WITH_LWM2M12\n                     });\n    ANJAY_MUTEX_UNLOCK(anjay);\n\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ517, 0, (const anjay_iid_t[]) { 518, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ517, 518, 0,\n            (const anjay_mock_dm_res_entry_t[]) { { 519, ANJAY_DM_RES_RW,\n                                                    ANJAY_DM_RES_PRESENT },\n                                                  ANJAY_MOCK_DM_RES_END });\n\n    AVS_UNIT_ASSERT_FAILED(\n            anjay_attr_storage_restore(anjay, (avs_stream_t *) &inbuf));\n\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    // Previously set attributes should remain untouched\n    AVS_UNIT_ASSERT_EQUAL(AVS_LIST_SIZE(anjay_unlocked->attr_storage.objects),\n                          1);\n    assert_object_equal(\n            anjay_unlocked->attr_storage.objects,\n            test_object_entry(\n                    517, NULL,\n                    test_instance_entry(\n                            518,\n                            test_default_attrs(519,\n                                               520,\n                                               521,\n                                               ANJAY_ATTRIB_INTEGER_NONE,\n                                               ANJAY_ATTRIB_INTEGER_NONE,\n#ifdef ANJAY_WITH_LWM2M12\n                                               ANJAY_ATTRIB_INTEGER_NONE,\n#endif // ANJAY_WITH_LWM2M12\n                                               ANJAY_DM_CON_ATTR_NONE),\n                            NULL),\n                    NULL));\n    ANJAY_MUTEX_UNLOCK(anjay);\n    PERSISTENCE_TEST_FINISH;\n}\n\nstatic const char TEST_DATA_WITH_EMPTY_OID_ATTRS[] =\n        MAGIC_HEADER_V0 \"\\x00\\x00\\x00\\x01\" // 3 objects\n                        \"\\x00\\x04\"         // OID 4\n                        \"\\x00\\x00\\x00\\x02\" // 2 object-level default attrs\n                        \"\\x00\\x0E\"         // SSID 14\n                        \"\\xFF\\xFF\\xFF\\xFF\" // min period\n                        \"\\x00\\x00\\x00\\x03\" // max period\n                        \"\\x00\\x21\"         // SSID 33\n                        /********* EMPTY ATTRIBUTES FOLLOW *********/\n                        \"\\xFF\\xFF\\xFF\\xFF\" // min period\n                        \"\\xFF\\xFF\\xFF\\xFF\" // max period\n                        /*********** EMPTY ATTRIBUTES END ***********/\n                        \"\\x00\\x00\\x00\\x00\"; // 0 instance entries\n\nstatic const char TEST_DATA_WITH_EMPTY_IID_ATTRS[] =\n        MAGIC_HEADER_V0 \"\\x00\\x00\\x00\\x01\" // 3 objects\n                        \"\\x00\\x2A\"         // OID 42\n                        \"\\x00\\x00\\x00\\x00\" // 0 object-level default attrs\n                        \"\\x00\\x00\\x00\\x01\" // 1 instance entry\n                        \"\\x00\\x01\"         // IID 1\n                        \"\\x00\\x00\\x00\\x01\" // 1 instance-level default attr\n                        \"\\x00\\x02\"         // SSID 2\n                        /********* EMPTY ATTRIBUTES FOLLOW *********/\n                        \"\\xFF\\xFF\\xFF\\xFF\" // min period\n                        \"\\xFF\\xFF\\xFF\\xFF\" // max period\n                        /*********** EMPTY ATTRIBUTES END ***********/\n                        \"\\x00\\x00\\x00\\x01\" // 1 resource entry\n                        \"\\x00\\x03\"         // RID 3\n                        \"\\x00\\x00\\x00\\x01\" // 2 attr entries\n                        \"\\x00\\x02\"         // SSID 2\n                        \"\\x00\\x00\\x00\\x01\" // min period\n                        \"\\x00\\x00\\x00\\x0E\" // max period\n                        /* greater than */ \"\\x7f\\xf8\\x00\\x00\\x00\\x00\\x00\\x00\"\n                        /* less than */ \"\\x7f\\xf8\\x00\\x00\\x00\\x00\\x00\\x00\"\n                        /* step */ \"\\x7f\\xf8\\x00\\x00\\x00\\x00\\x00\\x00\";\n\nstatic const char TEST_DATA_WITH_EMPTY_RID_ATTRS[] =\n        MAGIC_HEADER_V0 \"\\x00\\x00\\x00\\x01\" // 3 objects\n                        \"\\x02\\x05\"         // OID 517\n                        \"\\x00\\x00\\x00\\x00\" // 0 object-level default attrs\n                        \"\\x00\\x00\\x00\\x01\" // 1 instance entry\n                        \"\\x02\\x04\"         // IID 516\n                        \"\\x00\\x00\\x00\\x00\" // 0 instance-level default attrs\n                        \"\\x00\\x00\\x00\\x01\" // 1 resource entry\n                        \"\\x02\\x03\"         // RID 515\n                        \"\\x00\\x00\\x00\\x01\" // 1 attr entry\n                        \"\\x02\\x02\"         // SSID 514\n                        /********* EMPTY ATTRIBUTES FOLLOW *********/\n                        \"\\xFF\\xFF\\xFF\\xFF\" // min period\n                        \"\\xFF\\xFF\\xFF\\xFF\" // max period\n                        /* greater than */ \"\\x7f\\xf8\\x00\\x00\\x00\\x00\\x00\\x00\"\n                        /* less than */ \"\\x7f\\xf8\\x00\\x00\\x00\\x00\\x00\\x00\"\n                        /* step */ \"\\x7f\\xf8\\x00\\x00\\x00\\x00\\x00\\x00\";\n/*********** EMPTY ATTRIBUTES END ***********/\n\n#define DEFINE_UNIT_TEST_RESTORE_DATA_WITH_EMPTY(Suffix)                     \\\n    AVS_UNIT_TEST(attr_storage_persistence,                                  \\\n                  restore_data_with_empty_##Suffix) {                        \\\n        RESTORE_TEST_INIT(TEST_DATA_WITH_EMPTY_##Suffix);                    \\\n        INSTALL_FAKE_OBJECT(4);                                              \\\n        INSTALL_FAKE_OBJECT(42);                                             \\\n        INSTALL_FAKE_OBJECT(517);                                            \\\n                                                                             \\\n        AVS_UNIT_ASSERT_FAILED(                                              \\\n                anjay_attr_storage_restore(anjay, (avs_stream_t *) &inbuf)); \\\n                                                                             \\\n        ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);                             \\\n        AVS_UNIT_ASSERT_NULL(anjay_unlocked->attr_storage.objects);          \\\n        ANJAY_MUTEX_UNLOCK(anjay);                                           \\\n        PERSISTENCE_TEST_FINISH;                                             \\\n    }\n\nDEFINE_UNIT_TEST_RESTORE_DATA_WITH_EMPTY(OID_ATTRS)\nDEFINE_UNIT_TEST_RESTORE_DATA_WITH_EMPTY(IID_ATTRS)\nDEFINE_UNIT_TEST_RESTORE_DATA_WITH_EMPTY(RID_ATTRS)\n\nAVS_UNIT_TEST(attr_storage_persistence, restore_data_with_bad_magic) {\n    static const char DATA[] = \"FBS0\\x00\\x00\\x00\\x00\";\n\n    RESTORE_TEST_INIT(DATA);\n    INSTALL_FAKE_OBJECT(4);\n    INSTALL_FAKE_OBJECT(42);\n    INSTALL_FAKE_OBJECT(517);\n\n    AVS_UNIT_ASSERT_FAILED(\n            anjay_attr_storage_restore(anjay, (avs_stream_t *) &inbuf));\n\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    AVS_UNIT_ASSERT_NULL(anjay_unlocked->attr_storage.objects);\n    ANJAY_MUTEX_UNLOCK(anjay);\n    PERSISTENCE_TEST_FINISH;\n}\n\nstatic const char TEST_DATA_DUPLICATE_OID[] =\n        MAGIC_HEADER_V0 \"\\x00\\x00\\x00\\x02\"  // 2 objects\n                        \"\\x00\\x04\"          // OID 4\n                        \"\\x00\\x00\\x00\\x01\"  // 1 object-level default attr\n                        \"\\x00\\x0E\"          // SSID 14\n                        \"\\xFF\\xFF\\xFF\\xFF\"  // min period\n                        \"\\x00\\x00\\x00\\x03\"  // max period\n                        \"\\x00\\x00\\x00\\x00\"  // 0 instance entries\n                        \"\\x00\\x04\"          // OID 4\n                        \"\\x00\\x00\\x00\\x01\"  // 1 object-level default attr\n                        \"\\x00\\x07\"          // SSID 7\n                        \"\\xFF\\xFF\\xFF\\xFF\"  // min period\n                        \"\\x00\\x00\\x00\\x03\"  // max period\n                        \"\\x00\\x00\\x00\\x00\"; // 0 instance entries\n\nAVS_UNIT_TEST(attr_storage_persistence, restore_duplicate_oid) {\n    RESTORE_TEST_INIT(TEST_DATA_DUPLICATE_OID);\n    INSTALL_FAKE_OBJECT(4);\n\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    write_inst_attrs(anjay_unlocked, 4, 5, 6,\n                     &(const anjay_dm_oi_attributes_t) {\n                         .min_period = 7,\n                         .max_period = 8,\n                         .min_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n                         .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n#ifdef ANJAY_WITH_CON_ATTR\n                         .con = ANJAY_DM_CON_ATTR_NONE,\n#endif // ANJAY_WITH_CON_ATTR\n#ifdef ANJAY_WITH_LWM2M12\n                         .hqmax = ANJAY_ATTRIB_INTEGER_NONE,\n#endif // ANJAY_WITH_LWM2M12\n                     });\n    ANJAY_MUTEX_UNLOCK(anjay);\n\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ4, 0, (const anjay_iid_t[]) { 5, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(anjay, &OBJ4, 5, 0,\n                                         (const anjay_mock_dm_res_entry_t[]) {\n                                                 ANJAY_MOCK_DM_RES_END });\n\n    AVS_UNIT_ASSERT_FAILED(\n            anjay_attr_storage_restore(anjay, (avs_stream_t *) &inbuf));\n\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    // Previously set attributes should remain untouched\n    AVS_UNIT_ASSERT_EQUAL(AVS_LIST_SIZE(anjay_unlocked->attr_storage.objects),\n                          1);\n    assert_object_equal(\n            anjay_unlocked->attr_storage.objects,\n            test_object_entry(\n                    4, NULL,\n                    test_instance_entry(\n                            5,\n                            test_default_attrs(6,\n                                               7,\n                                               8,\n                                               ANJAY_ATTRIB_INTEGER_NONE,\n                                               ANJAY_ATTRIB_INTEGER_NONE,\n#ifdef ANJAY_WITH_LWM2M12\n                                               ANJAY_ATTRIB_INTEGER_NONE,\n#endif // ANJAY_WITH_LWM2M12\n                                               ANJAY_DM_CON_ATTR_NONE),\n                            NULL),\n                    NULL));\n    ANJAY_MUTEX_UNLOCK(anjay);\n    PERSISTENCE_TEST_FINISH;\n}\n\n// TODO: Actually test removing nonexistent IIDs and RIDs\n"
  },
  {
    "path": "tests/core/bootstrap.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#define AVS_UNIT_ENABLE_SHORT_ASSERTS\n#include <avsystem/commons/avs_unit_test.h>\n\n#define ANJAY_SERVERS_INTERNALS\n#include \"src/core/servers/anjay_activate.h\"\n#undef ANJAY_SERVERS_INTERNALS\n#include \"src/core/servers/anjay_servers_internal.h\"\n#include \"tests/core/coap/utils.h\"\n#include \"tests/utils/dm.h\"\n\n#ifdef ANJAY_WITH_LWM2M11\nAVS_UNIT_TEST(bootstrap_read, root_path) {\n    DM_TEST_INIT_WITH_SSIDS(ANJAY_SSID_BOOTSTRAP);\n    DM_TEST_REQUEST(mocksocks[0], CON, GET, ID(0xFA3E), NO_PAYLOAD);\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, METHOD_NOT_ALLOWED, ID(0xFA3E),\n                            NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(bootstrap_read, resource_path) {\n    DM_TEST_INIT_WITH_SSIDS(ANJAY_SSID_BOOTSTRAP);\n    DM_TEST_REQUEST(mocksocks[0], CON, GET, ID(0xFA3E), PATH(\"1\", \"0\", \"0\"),\n                    NO_PAYLOAD);\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, METHOD_NOT_ALLOWED, ID(0xFA3E),\n                            NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(bootstrap_read, resource_instance_path) {\n    DM_TEST_INIT_WITH_SSIDS(ANJAY_SSID_BOOTSTRAP);\n    DM_TEST_REQUEST(mocksocks[0], CON, GET, ID(0xFA3E),\n                    PATH(\"1\", \"0\", \"0\", \"0\"), NO_PAYLOAD);\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, METHOD_NOT_ALLOWED, ID(0xFA3E),\n                            NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(bootstrap_read, invalid_oid) {\n    DM_TEST_INIT_WITH_SSIDS(ANJAY_SSID_BOOTSTRAP);\n    DM_TEST_REQUEST(mocksocks[0], CON, GET, ID(0xFA3E), PATH(\"3\"), NO_PAYLOAD);\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, METHOD_NOT_ALLOWED, ID(0xFA3E),\n                            NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(bootstrap_read, server_object_instance) {\n    DM_TEST_INIT_WITH_SSIDS(ANJAY_SSID_BOOTSTRAP);\n    DM_TEST_REQUEST(mocksocks[0], CON, GET, ID(0xFA3E), PATH(\"1\", \"12\"),\n                    ACCEPT(AVS_COAP_FORMAT_OMA_LWM2M_TLV), NO_PAYLOAD);\n    const anjay_iid_t iid = 12;\n    const anjay_rid_t rid = 34;\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &FAKE_SERVER, 0,\n            (const anjay_iid_t[]) { iid, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &FAKE_SERVER, iid, 0,\n            (const anjay_mock_dm_res_entry_t[]) { { rid, ANJAY_DM_RES_R,\n                                                    ANJAY_DM_RES_PRESENT },\n                                                  ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read(anjay, &FAKE_SERVER, iid, rid,\n                                        ANJAY_ID_INVALID, 0,\n                                        ANJAY_MOCK_DM_STRING(0, \"whatever\"));\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, CONTENT, ID(0xFA3E),\n                            CONTENT_FORMAT(OMA_LWM2M_TLV),\n                            // [ 0xC8 ][ 0x22 ]\n                            // 11.............. Resource with Value\n                            // ..0............. Identifier field is 8 bits long\n                            // ...01............Length field is 8-bits and Bits\n                            //                  2-0 are ignored\n                            // ........00100010 ID = 34\n                            PAYLOAD(\"\\xC8\\x22\\x08\"\n                                    \"whatever\"));\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(bootstrap_read, server_object) {\n    DM_TEST_INIT_WITH_SSIDS(ANJAY_SSID_BOOTSTRAP);\n    DM_TEST_REQUEST(mocksocks[0], CON, GET, ID(0xFA3E), PATH(\"1\"),\n                    ACCEPT(AVS_COAP_FORMAT_SENML_JSON), NO_PAYLOAD);\n    const anjay_iid_t iid1 = 10;\n    const anjay_iid_t iid2 = 20;\n    const anjay_rid_t rid1 = 30;\n    const anjay_rid_t rid2 = 40;\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &FAKE_SERVER, 0,\n            (const anjay_iid_t[]) { iid1, iid2, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &FAKE_SERVER, iid1, 0,\n            (const anjay_mock_dm_res_entry_t[]) { { rid1, ANJAY_DM_RES_R,\n                                                    ANJAY_DM_RES_PRESENT },\n                                                  ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read(anjay, &FAKE_SERVER, iid1, rid1,\n                                        ANJAY_ID_INVALID, 0,\n                                        ANJAY_MOCK_DM_STRING(0, \"RES1\"));\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &FAKE_SERVER, iid2, 0,\n            (const anjay_mock_dm_res_entry_t[]) { { rid2, ANJAY_DM_RES_R,\n                                                    ANJAY_DM_RES_PRESENT },\n                                                  ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read(anjay, &FAKE_SERVER, iid2, rid2,\n                                        ANJAY_ID_INVALID, 0,\n                                        ANJAY_MOCK_DM_STRING(0, \"RES2\"));\n\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, CONTENT, ID(0xFA3E),\n                            CONTENT_FORMAT(SENML_JSON),\n                            PAYLOAD(\"[{\\\"bn\\\":\\\"/1\\\",\"\n                                    \"\\\"n\\\":\\\"/10/30\\\",\\\"vs\\\":\\\"RES1\\\"},\"\n                                    \"{\\\"n\\\":\\\"/20/40\\\",\\\"vs\\\":\\\"RES2\\\"}]\"));\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(bootstrap_read, non_readable_resources) {\n    DM_TEST_INIT_WITH_SSIDS(ANJAY_SSID_BOOTSTRAP);\n    DM_TEST_REQUEST(mocksocks[0], CON, GET, ID(0xFA3E), PATH(\"1\"),\n                    ACCEPT(AVS_COAP_FORMAT_SENML_JSON), NO_PAYLOAD);\n    const anjay_iid_t iid1 = 10;\n    const anjay_iid_t iid2 = 20;\n    const anjay_rid_t rid1 = 30;\n    const anjay_rid_t rid2 = 40;\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &FAKE_SERVER, 0,\n            (const anjay_iid_t[]) { iid1, iid2, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &FAKE_SERVER, iid1, 0,\n            (const anjay_mock_dm_res_entry_t[]) { { rid1, ANJAY_DM_RES_BS_RW,\n                                                    ANJAY_DM_RES_PRESENT },\n                                                  ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read(anjay, &FAKE_SERVER, iid1, rid1,\n                                        ANJAY_ID_INVALID, 0,\n                                        ANJAY_MOCK_DM_STRING(0, \"RES1\"));\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &FAKE_SERVER, iid2, 0,\n            (const anjay_mock_dm_res_entry_t[]) { { rid2, ANJAY_DM_RES_W,\n                                                    ANJAY_DM_RES_PRESENT },\n                                                  ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read(anjay, &FAKE_SERVER, iid2, rid2,\n                                        ANJAY_ID_INVALID, 0,\n                                        ANJAY_MOCK_DM_STRING(0, \"RES2\"));\n\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, CONTENT, ID(0xFA3E),\n                            CONTENT_FORMAT(SENML_JSON),\n                            PAYLOAD(\"[{\\\"bn\\\":\\\"/1\\\",\"\n                                    \"\\\"n\\\":\\\"/10/30\\\",\\\"vs\\\":\\\"RES1\\\"},\"\n                                    \"{\\\"n\\\":\\\"/20/40\\\",\\\"vs\\\":\\\"RES2\\\"}]\"));\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n#endif // ANJAY_WITH_LWM2M11\n\nAVS_UNIT_TEST(bootstrap_write, resource) {\n    DM_TEST_INIT_WITH_SSIDS(ANJAY_SSID_BOOTSTRAP);\n    DM_TEST_REQUEST(mocksocks[0], CON, PUT, ID(0xFA3E), PATH(\"42\", \"514\", \"4\"),\n                    CONTENT_FORMAT(PLAINTEXT), PAYLOAD(\"Hello\"));\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0,\n            (const anjay_iid_t[]) { 14, 42, 69, 514, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 514, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_write(anjay, &OBJ, 514, 4, ANJAY_ID_INVALID,\n                                         ANJAY_MOCK_DM_STRING(0, \"Hello\"), 0);\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, CHANGED, ID(0xFA3E), NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(bootstrap_write, resource_with_create) {\n    DM_TEST_INIT_WITH_SSIDS(ANJAY_SSID_BOOTSTRAP);\n    DM_TEST_REQUEST(mocksocks[0], CON, PUT, ID(0xFA3E), PATH(\"42\", \"514\", \"4\"),\n                    CONTENT_FORMAT(PLAINTEXT), PAYLOAD(\"Hello\"));\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0,\n            (const anjay_iid_t[]) { 14, 42, 69, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_instance_create(anjay, &OBJ, 514, 0);\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 514, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_write(anjay, &OBJ, 514, 4, ANJAY_ID_INVALID,\n                                         ANJAY_MOCK_DM_STRING(0, \"Hello\"), 0);\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, CHANGED, ID(0xFA3E), NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(bootstrap_write, resource_with_present_error) {\n    DM_TEST_INIT_WITH_SSIDS(ANJAY_SSID_BOOTSTRAP);\n    DM_TEST_REQUEST(mocksocks[0], CON, PUT, ID(0xFA3E), PATH(\"42\", \"514\", \"4\"),\n                    CONTENT_FORMAT(PLAINTEXT), PAYLOAD(\"Hello\"));\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, -1,\n            (const anjay_iid_t[]) { 14, 42, ANJAY_ID_INVALID });\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, INTERNAL_SERVER_ERROR,\n                            ID(0xFA3E), NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_FAILED(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(bootstrap_write, resource_with_create_error) {\n    DM_TEST_INIT_WITH_SSIDS(ANJAY_SSID_BOOTSTRAP);\n    DM_TEST_REQUEST(mocksocks[0], CON, PUT, ID(0xFA3E), PATH(\"42\", \"514\", \"4\"),\n                    CONTENT_FORMAT(PLAINTEXT), PAYLOAD(\"Hello\"));\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0,\n            (const anjay_iid_t[]) { 14, 42, 69, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_instance_create(anjay, &OBJ, 514, -1);\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, INTERNAL_SERVER_ERROR,\n                            ID(0xFA3E), NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_FAILED(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(bootstrap_write, resource_error) {\n    DM_TEST_INIT_WITH_SSIDS(ANJAY_SSID_BOOTSTRAP);\n    DM_TEST_REQUEST(mocksocks[0], CON, PUT, ID(0xFA3E), PATH(\"42\", \"514\", \"7\"),\n                    CONTENT_FORMAT(PLAINTEXT), PAYLOAD(\"Hello\"));\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0,\n            (const anjay_iid_t[]) { 14, 42, 69, 514, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 514, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, NOT_FOUND, ID(0xFA3E),\n                            NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(bootstrap_write, resource_with_mismatched_tlv_rid) {\n    DM_TEST_INIT_WITH_SSIDS(ANJAY_SSID_BOOTSTRAP);\n    DM_TEST_REQUEST(mocksocks[0], CON, PUT, ID(0xFA3E), PATH(\"42\", \"514\", \"4\"),\n                    CONTENT_FORMAT(OMA_LWM2M_TLV),\n                    PAYLOAD(\"\\xc5\\x05\"\n                            \"Hello\"));\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0,\n            (const anjay_iid_t[]) { 14, 42, 69, 514, ANJAY_ID_INVALID });\n    // mismatched resource id, RID Uri-Path was 4 but in the payload it is 5\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, BAD_REQUEST, ID(0xFA3E),\n                            NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(bootstrap_write, resource_error_with_create) {\n    const anjay_dm_object_def_t *const *obj_defs[] = { &OBJ_WITH_TRANSACTION,\n                                                       &FAKE_SECURITY,\n                                                       &FAKE_SERVER };\n    anjay_ssid_t ssids[] = { ANJAY_SSID_BOOTSTRAP };\n    DM_TEST_INIT_GENERIC(obj_defs, ssids, DM_TEST_CONFIGURATION());\n\n    DM_TEST_REQUEST(mocksocks[0], CON, PUT, ID(0xFA3E), PATH(\"69\", \"514\", \"7\"),\n                    CONTENT_FORMAT(PLAINTEXT), PAYLOAD(\"Hello\"));\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ_WITH_TRANSACTION, 0,\n            (const anjay_iid_t[]) { 14, 42, 69, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_transaction_begin(anjay, &OBJ_WITH_TRANSACTION, 0);\n    _anjay_mock_dm_expect_instance_create(anjay, &OBJ_WITH_TRANSACTION, 514, 0);\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ_WITH_TRANSACTION, 514, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, NOT_FOUND, ID(0xFA3E),\n                            NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    _anjay_mock_dm_expect_transaction_rollback(anjay, &OBJ_WITH_TRANSACTION, 0);\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(bootstrap_write, instance) {\n    DM_TEST_INIT_WITH_SSIDS(ANJAY_SSID_BOOTSTRAP);\n    DM_TEST_REQUEST(mocksocks[0], CON, PUT, ID(0xFA3E), PATH(\"42\", \"69\"),\n                    CONTENT_FORMAT(OMA_LWM2M_TLV),\n                    PAYLOAD(\"\\xc1\\x00\\x0d\\xc5\\x06\"\n                            \"Hello\"));\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0,\n            (const anjay_iid_t[]) { 14, 42, 69, 514, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 69, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_write(anjay, &OBJ, 69, 0, ANJAY_ID_INVALID,\n                                         ANJAY_MOCK_DM_INT(0, 13), 0);\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 69, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                    { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_write(anjay, &OBJ, 69, 6, ANJAY_ID_INVALID,\n                                         ANJAY_MOCK_DM_STRING(0, \"Hello\"), 0);\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, CHANGED, ID(0xFA3E), NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(bootstrap_write, instance_with_redundant_tlv_header) {\n    DM_TEST_INIT_WITH_SSIDS(ANJAY_SSID_BOOTSTRAP);\n    DM_TEST_REQUEST(mocksocks[0], CON, PUT, ID(0xFA3E), PATH(\"42\", \"69\"),\n                    CONTENT_FORMAT(OMA_LWM2M_TLV),\n                    PAYLOAD(\"\\x08\\x45\\x08\\xc6\\x06\"\n                            \"DDDDDD\"));\n    // Redundant (but consistent with the Uri-Path) \\x08\\x45\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0,\n            (const anjay_iid_t[]) { 14, 42, 69, 514, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 69, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                    { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_write(anjay, &OBJ, 69, 6, ANJAY_ID_INVALID,\n                                         ANJAY_MOCK_DM_STRING(0, \"DDDDDD\"), 0);\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, CHANGED, ID(0xFA3E), NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(bootstrap_write,\n              instance_with_redundant_and_incorrect_tlv_header) {\n    DM_TEST_INIT_WITH_SSIDS(ANJAY_SSID_BOOTSTRAP);\n    DM_TEST_REQUEST(mocksocks[0], CON, PUT, ID(0xFA3E), PATH(\"42\", \"69\"),\n                    CONTENT_FORMAT(OMA_LWM2M_TLV),\n                    PAYLOAD(\"\\x08\\x01\\x08\\xc6\\x0a\"\n                            \"DDDDDD\"));\n    // IID is 69 but TLV payload contains IID 1\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0,\n            (const anjay_iid_t[]) { 14, 42, 69, 514, ANJAY_ID_INVALID });\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, BAD_REQUEST, ID(0xFA3E),\n                            NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(bootstrap_write, instance_wrong_type) {\n    DM_TEST_INIT_WITH_SSIDS(ANJAY_SSID_BOOTSTRAP);\n    DM_TEST_REQUEST(mocksocks[0], CON, PUT, ID(0xFA3E), PATH(\"42\", \"69\"),\n                    CONTENT_FORMAT(OMA_LWM2M_TLV),\n                    PAYLOAD(\"\\xc1\\x00\\x0d\"\n                            \"\\x05\\x06\"\n                            \"Hello\"));\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0,\n            (const anjay_iid_t[]) { 14, 42, 69, 514, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 69, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_write(anjay, &OBJ, 69, 0, ANJAY_ID_INVALID,\n                                         ANJAY_MOCK_DM_INT(0, 13), 0);\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, BAD_REQUEST, ID(0xFA3E),\n                            NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(bootstrap_write, instance_error) {\n    DM_TEST_INIT_WITH_SSIDS(ANJAY_SSID_BOOTSTRAP);\n    DM_TEST_REQUEST(mocksocks[0], CON, PUT, ID(0xFA3E), PATH(\"42\", \"69\"),\n                    CONTENT_FORMAT(OMA_LWM2M_TLV),\n                    PAYLOAD(\"\\xc1\\x00\\x0d\"\n                            \"\\xc5\\x06\"\n                            \"Hello\"));\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0,\n            (const anjay_iid_t[]) { 14, 42, 69, 514, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 69, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_write(anjay, &OBJ, 69, 0, ANJAY_ID_INVALID,\n                                         ANJAY_MOCK_DM_INT(0, 13), -1);\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, INTERNAL_SERVER_ERROR,\n                            ID(0xFA3E), NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_FAILED(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(bootstrap_write, instance_some_unsupported) {\n    DM_TEST_INIT_WITH_SSIDS(ANJAY_SSID_BOOTSTRAP);\n    DM_TEST_REQUEST(mocksocks[0], CON, PUT, ID(0xFA3E), PATH(\"42\", \"69\"),\n                    CONTENT_FORMAT(OMA_LWM2M_TLV),\n                    PAYLOAD(\"\\xc1\\x00\\x0d\"\n                            \"\\xc5\\x07\"\n                            \"Hello\"));\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0,\n            (const anjay_iid_t[]) { 14, 42, 69, 514, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 69, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_write(anjay, &OBJ, 69, 0, ANJAY_ID_INVALID,\n                                         ANJAY_MOCK_DM_INT(0, 13), 0);\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 69, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                    { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, CHANGED, ID(0xFA3E), NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(bootstrap_write, object) {\n    DM_TEST_INIT_WITH_SSIDS(ANJAY_SSID_BOOTSTRAP);\n    DM_TEST_REQUEST(mocksocks[0], CON, PUT, ID(0xFA3E), PATH(\"42\"),\n                    CONTENT_FORMAT(OMA_LWM2M_TLV),\n                    PAYLOAD(\"\\x08\\x45\\x03\" // IID == 69\n                            \"\\xc1\\x00\\x2a\" // RID == 0\n                            \"\\x08\\x2a\\x03\" // IID == 42\n                            \"\\xc1\\x03\\x45\" /* RID == 3 */));\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0, (const anjay_iid_t[]) { 14, 42, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_instance_create(anjay, &OBJ, 69, 0);\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 69, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_write(anjay, &OBJ, 69, 0, ANJAY_ID_INVALID,\n                                         ANJAY_MOCK_DM_INT(0, 42), 0);\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0, (const anjay_iid_t[]) { 14, 42, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 42, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_write(anjay, &OBJ, 42, 3, ANJAY_ID_INVALID,\n                                         ANJAY_MOCK_DM_INT(0, 69), 0);\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, CHANGED, ID(0xFA3E), NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(bootstrap_write, object_error) {\n    DM_TEST_INIT_WITH_SSIDS(ANJAY_SSID_BOOTSTRAP);\n    DM_TEST_REQUEST(mocksocks[0], CON, PUT, ID(0xFA3E), PATH(\"42\"),\n                    CONTENT_FORMAT(OMA_LWM2M_TLV),\n                    PAYLOAD(\"\\x08\\x45\\x03\" // IID == 69\n                            \"\\xc1\\x00\\x2a\" // RID == 0\n                            \"\\x08\\x2a\\x03\" // IID == 42\n                            \"\\xc1\\x03\\x45\" /* RID == 3 */));\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0, (const anjay_iid_t[]) { 14, 42, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_instance_create(anjay, &OBJ, 69, 0);\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 69, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_write(anjay, &OBJ, 69, 0, ANJAY_ID_INVALID,\n                                         ANJAY_MOCK_DM_INT(0, 42), 0);\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0, (const anjay_iid_t[]) { 14, 42, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 42, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_write(anjay, &OBJ, 42, 3, ANJAY_ID_INVALID,\n                                         ANJAY_MOCK_DM_INT(0, 69), -1);\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, INTERNAL_SERVER_ERROR,\n                            ID(0xFA3E), NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_FAILED(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(bootstrap_write, object_error_index_end) {\n    DM_TEST_INIT_WITH_SSIDS(ANJAY_SSID_BOOTSTRAP);\n    DM_TEST_REQUEST(mocksocks[0], CON, PUT, ID(0xFA3E), PATH(\"42\"),\n                    CONTENT_FORMAT(OMA_LWM2M_TLV),\n                    PAYLOAD(\"\\x08\\x45\\x03\" // IID == 69\n                            \"\\xc1\\x00\\x2a\" // RID == 0\n                            \"\\x08\\x2a\\x03\" // IID == 42\n                            \"\\xc1\\x03\\x45\" /* RID == 3 */));\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0, (const anjay_iid_t[]) { 14, 42, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_instance_create(anjay, &OBJ, 69, 0);\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 69, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_write(anjay, &OBJ, 69, 0, ANJAY_ID_INVALID,\n                                         ANJAY_MOCK_DM_INT(0, 42), 0);\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0, (const anjay_iid_t[]) { 14, 42, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 42, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_write(anjay, &OBJ, 42, 3, ANJAY_ID_INVALID,\n                                         ANJAY_MOCK_DM_INT(0, 69),\n                                         ANJAY_GET_PATH_END);\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, INTERNAL_SERVER_ERROR,\n                            ID(0xFA3E), NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_FAILED(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(bootstrap_write, object_wrong_type) {\n    DM_TEST_INIT_WITH_SSIDS(ANJAY_SSID_BOOTSTRAP);\n    DM_TEST_REQUEST(mocksocks[0], CON, PUT, ID(0xFA3E), PATH(\"42\"),\n                    CONTENT_FORMAT(OMA_LWM2M_TLV),\n                    PAYLOAD(\"\\x08\\x45\\x03\" // IID == 69\n                            \"\\xc1\\x00\\x2a\" // RID == 0\n                            \"\\xc8\\x2a\\x03\" // RID in place of IID == 42\n                            \"\\xc1\\x03\\x45\" /* RID == 3 */));\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0, (const anjay_iid_t[]) { 14, 42, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_instance_create(anjay, &OBJ, 69, 0);\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 69, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_write(anjay, &OBJ, 69, 0, ANJAY_ID_INVALID,\n                                         ANJAY_MOCK_DM_INT(0, 42), 0);\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, BAD_REQUEST, ID(0xFA3E),\n                            NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(bootstrap_write, object_not_found) {\n    DM_TEST_INIT_WITH_SSIDS(ANJAY_SSID_BOOTSTRAP);\n    DM_TEST_REQUEST(mocksocks[0], CON, PUT, ID(0xFA3E), PATH(\"43\"),\n                    CONTENT_FORMAT(OMA_LWM2M_TLV),\n                    PAYLOAD(\"\\x08\\x45\\x03\" // IID == 69\n                            \"\\xc1\\x00\\x2a\" // RID == 0\n                            \"\\x08\\x2a\\x03\" // IID == 42\n                            \"\\xc1\\x03\\x45\" /* RID == 3 */));\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, NOT_FOUND, ID(0xFA3E),\n                            NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(bootstrap_write, object_missing) {\n    DM_TEST_INIT_WITH_SSIDS(ANJAY_SSID_BOOTSTRAP);\n    DM_TEST_REQUEST(mocksocks[0], CON, PUT, ID(0xFA3E),\n                    CONTENT_FORMAT(OMA_LWM2M_TLV),\n                    PAYLOAD(\"\\x08\\x45\\x03\" // IID == 69\n                            \"\\xc1\\x00\\x2a\" // RID == 0\n                            \"\\x08\\x2a\\x03\" // IID == 42\n                            \"\\xc1\\x03\\x45\" /* RID == 3 */));\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, METHOD_NOT_ALLOWED, ID(0xFA3E),\n                            NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(bootstrap_write, multiple_resource_followed_by_single_resoure) {\n    DM_TEST_INIT_WITH_SSIDS(ANJAY_SSID_BOOTSTRAP);\n    DM_TEST_REQUEST(mocksocks[0], CON, PUT, ID(0xFA3E), PATH(\"42\", \"69\"),\n                    CONTENT_FORMAT(OMA_LWM2M_TLV),\n                    PAYLOAD(\"\\x88\\x15\\x0e\"\n                            \"\\x45\\x03\"\n                            \"Hello\"\n                            \"\\x45\\x07\"\n                            \"world\"\n                            \"\\xe4\\x01\\xa4\"\n                            \"test\"));\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0, (const anjay_iid_t[]) { 69, ANJAY_ID_INVALID });\n    // Write /42/69/21\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 69, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 21, ANJAY_DM_RES_RWM, ANJAY_DM_RES_PRESENT },\n                    { 37, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                    { 420, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_reset(anjay, &OBJ, 69, 21, 0);\n    _anjay_mock_dm_expect_resource_write(anjay, &OBJ, 69, 21, 3,\n                                         ANJAY_MOCK_DM_STRING(0, \"Hello\"), 0);\n    _anjay_mock_dm_expect_resource_write(anjay, &OBJ, 69, 21, 7,\n                                         ANJAY_MOCK_DM_STRING(0, \"world\"), 0);\n    // Write /42/69/420\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 69, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 21, ANJAY_DM_RES_RWM, ANJAY_DM_RES_PRESENT },\n                    { 37, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                    { 420, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_write(anjay, &OBJ, 69, 420, ANJAY_ID_INVALID,\n                                         ANJAY_MOCK_DM_STRING(0, \"test\"), 0);\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, CHANGED, ID(0xFA3E), NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(bootstrap_delete, instance) {\n    DM_TEST_INIT_WITH_SSIDS(ANJAY_SSID_BOOTSTRAP);\n    DM_TEST_REQUEST(mocksocks[0], CON, DELETE, ID(0xFA3E), PATH(\"42\", \"34\"));\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0, (const anjay_iid_t[]) { 34, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_instance_remove(anjay, &OBJ, 34, 0);\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, DELETED, ID(0xfa3e), NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(bootstrap_delete, instance_missing) {\n    DM_TEST_INIT_WITH_SSIDS(ANJAY_SSID_BOOTSTRAP);\n    DM_TEST_REQUEST(mocksocks[0], CON, DELETE, ID(0xFA3E), PATH(\"42\", \"34\"));\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0, (const anjay_iid_t[]) { ANJAY_ID_INVALID });\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, DELETED, ID(0xfa3e), NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(bootstrap_delete, instance_error) {\n    DM_TEST_INIT_WITH_SSIDS(ANJAY_SSID_BOOTSTRAP);\n    DM_TEST_REQUEST(mocksocks[0], CON, DELETE, ID(0xFA3E), PATH(\"42\", \"34\"));\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0, (const anjay_iid_t[]) { 34, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_instance_remove(anjay, &OBJ, 34, -1);\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, INTERNAL_SERVER_ERROR,\n                            ID(0xfa3e), NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_FAILED(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(bootstrap_delete, instance_present_error) {\n    DM_TEST_INIT_WITH_SSIDS(ANJAY_SSID_BOOTSTRAP);\n    DM_TEST_REQUEST(mocksocks[0], CON, DELETE, ID(0xFA3E), PATH(\"42\", \"34\"));\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, -1, (const anjay_iid_t[]) { 34, ANJAY_ID_INVALID });\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, INTERNAL_SERVER_ERROR,\n                            ID(0xfa3e), NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_FAILED(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(bootstrap_delete, object) {\n    DM_TEST_INIT_WITH_SSIDS(ANJAY_SSID_BOOTSTRAP);\n    DM_TEST_REQUEST(mocksocks[0], CON, DELETE, ID(0xFA3E), PATH(\"42\"));\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0,\n            (const anjay_iid_t[]) { 34, 69, 514, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_instance_remove(anjay, &OBJ, 34, 0);\n    _anjay_mock_dm_expect_instance_remove(anjay, &OBJ, 69, 0);\n    _anjay_mock_dm_expect_instance_remove(anjay, &OBJ, 514, 0);\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, DELETED, ID(0xfa3e), NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(bootstrap_delete, object_it_error) {\n    DM_TEST_INIT_WITH_SSIDS(ANJAY_SSID_BOOTSTRAP);\n    DM_TEST_REQUEST(mocksocks[0], CON, DELETE, ID(0xFA3E), PATH(\"42\"));\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, -1,\n            (const anjay_iid_t[]) { 34, 69, ANJAY_ID_INVALID });\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, INTERNAL_SERVER_ERROR,\n                            ID(0xfa3e), NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_FAILED(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(bootstrap_delete, object_error) {\n    DM_TEST_INIT_WITH_SSIDS(ANJAY_SSID_BOOTSTRAP);\n    DM_TEST_REQUEST(mocksocks[0], CON, DELETE, ID(0xFA3E), PATH(\"42\"));\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0,\n            (const anjay_iid_t[]) { 34, 69, 514, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_instance_remove(anjay, &OBJ, 34, 0);\n    _anjay_mock_dm_expect_instance_remove(anjay, &OBJ, 69, -1);\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, INTERNAL_SERVER_ERROR,\n                            ID(0xfa3e), NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_FAILED(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(bootstrap_delete, object_missing) {\n    DM_TEST_INIT_WITH_SSIDS(ANJAY_SSID_BOOTSTRAP);\n    DM_TEST_REQUEST(mocksocks[0], CON, DELETE, ID(0xFA3E), PATH(\"77\"));\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, DELETED, ID(0xfa3e), NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(bootstrap_delete, everything) {\n    DM_TEST_INIT_WITH_SSIDS(ANJAY_SSID_BOOTSTRAP);\n    DM_TEST_REQUEST(mocksocks[0], CON, DELETE, ID(0xFA3E), NO_PAYLOAD);\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &FAKE_SERVER, 0,\n            (const anjay_iid_t[]) { 2, 3, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_instance_remove(anjay, &FAKE_SERVER, 2, 0);\n    _anjay_mock_dm_expect_instance_remove(anjay, &FAKE_SERVER, 3, 0);\n    _anjay_mock_dm_expect_list_instances(anjay, &OBJ_WITH_RESET, 0,\n                                         (const anjay_iid_t[]) {\n                                                 ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0,\n            (const anjay_iid_t[]) { 34, 69, 514, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_instance_remove(anjay, &OBJ, 34, 0);\n    _anjay_mock_dm_expect_instance_remove(anjay, &OBJ, 69, 0);\n    _anjay_mock_dm_expect_instance_remove(anjay, &OBJ, 514, 0);\n    _anjay_mock_dm_expect_list_instances(\n            anjay, (const anjay_dm_object_def_t *const *) &EXECUTE_OBJ, 0,\n            (const anjay_iid_t[]) { ANJAY_ID_INVALID });\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, DELETED, ID(0xfa3e), NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(bootstrap_delete, resource) {\n    DM_TEST_INIT_WITH_SSIDS(ANJAY_SSID_BOOTSTRAP);\n    DM_TEST_REQUEST(mocksocks[0], CON, DELETE, ID(0xFA3E),\n                    PATH(\"42\", \"34\", \"7\"));\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, BAD_REQUEST, ID(0xfa3e),\n                            NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(bootstrap_delete, bs) {\n    DM_TEST_INIT_WITH_SSIDS(ANJAY_SSID_BOOTSTRAP);\n    DM_TEST_REQUEST(mocksocks[0], CON, DELETE, ID(0xFA3E), PATH(\"bs\"));\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, BAD_REQUEST, ID(0xfa3e),\n                            NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nstatic int fail_notify_perform(anjay_unlocked_t *anjay,\n                               anjay_ssid_t origin_ssid,\n                               anjay_notify_queue_t *queue_ptr) {\n    (void) anjay;\n    (void) origin_ssid;\n    (void) queue_ptr;\n    return -1;\n}\n\nAVS_UNIT_TEST(bootstrap_finish, error) {\n    AVS_UNIT_MOCK(_anjay_notify_perform_without_servers) = fail_notify_perform;\n    DM_TEST_INIT_WITH_SSIDS(ANJAY_SSID_BOOTSTRAP);\n\n    // do some Write first to call notifications\n    DM_TEST_REQUEST(mocksocks[0], CON, PUT, ID(0xFA3E), PATH(\"42\", \"514\", \"4\"),\n                    CONTENT_FORMAT(PLAINTEXT), PAYLOAD(\"Hello\"));\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0,\n            (const anjay_iid_t[]) { 14, 42, 69, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_instance_create(anjay, &OBJ, 514, 0);\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 514, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_write(anjay, &OBJ, 514, 4, ANJAY_ID_INVALID,\n                                         ANJAY_MOCK_DM_STRING(0, \"Hello\"), 0);\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, CHANGED, ID(0xFA3E), NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n\n    // Bootstrap Finish\n    DM_TEST_REQUEST(mocksocks[0], CON, POST, ID(0xFA3E), PATH(\"bs\"));\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, INTERNAL_SERVER_ERROR,\n                            ID(0xFA3E), NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_FAILED(anjay_serve(anjay, mocksocks[0]));\n    AVS_UNIT_ASSERT_EQUAL(\n            AVS_UNIT_MOCK_INVOCATIONS(_anjay_dm_call_instance_remove), 0);\n    anjay_sched_run(anjay);\n    AVS_UNIT_ASSERT_EQUAL(\n            AVS_UNIT_MOCK_INVOCATIONS(_anjay_dm_call_instance_remove), 0);\n    _anjay_mock_clock_advance(avs_time_duration_from_scalar(1, AVS_TIME_S));\n    anjay_sched_run(anjay);\n    // still not removing\n    AVS_UNIT_ASSERT_EQUAL(\n            AVS_UNIT_MOCK_INVOCATIONS(_anjay_dm_call_instance_remove), 0);\n\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(bootstrap_invalid, invalid) {\n    DM_TEST_INIT_WITH_SSIDS(ANJAY_SSID_BOOTSTRAP);\n    DM_TEST_REQUEST(mocksocks[0], CON, POST, ID(0xFA3E), PATH(\"42\"),\n                    NO_PAYLOAD);\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, METHOD_NOT_ALLOWED, ID(0xFA3E),\n                            NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(schedule_update, bootstrap_update_is_not_scheduled) {\n    anjay_t *anjay;\n    const anjay_configuration_t CONFIG = {\n        .endpoint_name = \"anjay\"\n    };\n\n    anjay = anjay_new(&CONFIG);\n    ASSERT_NOT_NULL(anjay);\n\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n\n    AVS_LIST(anjay_server_info_t) bs_server =\n            _anjay_servers_create_inactive(anjay_unlocked,\n                                           ANJAY_SSID_BOOTSTRAP);\n    ASSERT_NOT_NULL(bs_server);\n    _anjay_servers_add(&anjay_unlocked->servers, bs_server);\n\n    AVS_UNIT_ASSERT_NULL(bs_server->next_action_handle);\n    AVS_UNIT_ASSERT_EQUAL(bs_server->next_action,\n                          (anjay_server_next_action_t) 0);\n\n    int result =\n            _anjay_schedule_registration_update_unlocked(anjay_unlocked,\n                                                         ANJAY_SSID_BOOTSTRAP);\n    AVS_UNIT_ASSERT_EQUAL(result, 0);\n\n    AVS_UNIT_ASSERT_NULL(bs_server->next_action_handle);\n    AVS_UNIT_ASSERT_EQUAL(bs_server->next_action,\n                          (anjay_server_next_action_t) 0);\n\n    ANJAY_MUTEX_UNLOCK(anjay);\n    anjay_delete(anjay);\n}\n"
  },
  {
    "path": "tests/core/bootstrap_mock.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef BOOTSTRAP_MOCK_H\n#define BOOTSTRAP_MOCK_H\n\n#include <avsystem/commons/avs_unit_mock_helpers.h>\n\nAVS_UNIT_MOCK_CREATE(_anjay_notify_perform_without_servers)\n#define _anjay_notify_perform_without_servers(...) \\\n    AVS_UNIT_MOCK_WRAPPER(_anjay_notify_perform_without_servers)(__VA_ARGS__)\n\nAVS_UNIT_MOCK_CREATE(_anjay_dm_call_instance_remove)\n#define _anjay_dm_call_instance_remove(...) \\\n    AVS_UNIT_MOCK_WRAPPER(_anjay_dm_call_instance_remove)(__VA_ARGS__)\n\n#endif /* BOOTSTRAP_MOCK_H */\n"
  },
  {
    "path": "tests/core/coap/utils.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#include <avsystem/commons/avs_utils.h>\n\n#include <avsystem/coap/coap.h>\n\n#include \"utils.h\"\n\nstatic uint64_t GLOBAL_TOKEN_VALUE;\n\nvoid reset_token_generator(void) {\n    GLOBAL_TOKEN_VALUE = 0;\n}\n\navs_error_t _avs_coap_ctx_generate_token(avs_coap_ctx_t *ctx,\n                                         avs_coap_token_t *out_token);\n\navs_error_t _avs_coap_ctx_generate_token(avs_coap_ctx_t *ctx,\n                                         avs_coap_token_t *out_token) {\n    (void) ctx;\n    *out_token = nth_token(GLOBAL_TOKEN_VALUE++);\n    return AVS_OK;\n}\n\navs_coap_token_t nth_token(uint64_t k) {\n    union {\n        uint8_t bytes[sizeof(uint64_t)];\n        uint64_t value;\n    } v;\n    v.value = avs_convert_be64(k);\n\n    avs_coap_token_t token;\n    token.size = sizeof(v.bytes);\n    memcpy(token.bytes, v.bytes, sizeof(v.bytes));\n    return token;\n}\n\navs_coap_token_t current_token(void) {\n    return nth_token(GLOBAL_TOKEN_VALUE);\n}\n"
  },
  {
    "path": "tests/core/coap/utils.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ANJAY_COAP_TEST_UTILS_H\n#define ANJAY_COAP_TEST_UTILS_H\n\n#include <avsystem/commons/avs_unit_mocksock.h>\n#include <avsystem/commons/avs_unit_test.h>\n\n#include <avsystem/commons/avs_defs.h>\n#include <avsystem/commons/avs_utils.h>\n\n#include <avsystem/coap/code.h>\n#include <avsystem/coap/ctx.h>\n\n#include <anjay/download.h>\n#include <anjay_modules/anjay_utils_core.h>\n\n#include \"src/core/anjay_utils_private.h\"\n\navs_coap_token_t nth_token(uint64_t k);\navs_coap_token_t current_token(void);\nvoid reset_token_generator(void);\n\ntypedef enum {\n    COAP_TEST_MSG_CONFIRMABLE,\n    COAP_TEST_MSG_NON_CONFIRMABLE,\n    COAP_TEST_MSG_ACKNOWLEDGEMENT,\n    COAP_TEST_MSG_RESET,\n\n    _COAP_TEST_MSG_FIRST = COAP_TEST_MSG_CONFIRMABLE,\n    _COAP_TEST_MSG_LAST = COAP_TEST_MSG_RESET\n} coap_test_msg_type_t;\n\ntypedef struct {\n    uint16_t msg_id;\n    avs_coap_token_t token;\n} coap_test_msg_identity_t;\n\ntypedef struct {\n    /**\n     * Length of the whole message (header + content). Does not include the\n     * length field itself.\n     */\n    uint32_t length; // whole message (header + content)\n\n    /**\n     * A FAM containing whole CoAP message: header + token + options + payload.\n     */\n    uint8_t content[];\n} coap_test_msg_t;\n\n/** Serialized CoAP message header. */\ntypedef struct coap_header {\n    uint8_t version_type_token_length;\n    uint8_t code;\n    uint8_t message_id[2];\n} coap_test_header_t;\n\nAVS_STATIC_ASSERT(AVS_ALIGNOF(coap_test_header_t) == 1,\n                  coap_header_must_always_be_properly_aligned_t);\n\n/** @{\n * Sanity checks that ensure no padding is inserted anywhere inside\n * @ref coap_test_header_t .\n */\nAVS_STATIC_ASSERT(offsetof(coap_test_header_t, version_type_token_length) == 0,\n                  vttl_field_is_at_start_of_coap_test_header_t);\nAVS_STATIC_ASSERT(offsetof(coap_test_header_t, code) == 1,\n                  no_padding_before_code_field_of_coap_test_header_t);\nAVS_STATIC_ASSERT(offsetof(coap_test_header_t, message_id) == 2,\n                  no_padding_before_message_id_field_of_coap_test_header_t);\nAVS_STATIC_ASSERT(sizeof(coap_test_header_t) == 4,\n                  no_padding_in_coap_test_header_t);\n/** @} */\n\n#define COAP_TEST_HEADER_SIZE sizeof(coap_test_header_t)\n\nstatic inline const uint8_t *\ncoap_test_header_end_const(const coap_test_msg_t *msg) {\n    return msg->content + COAP_TEST_HEADER_SIZE;\n}\n\nstatic inline uint8_t *coap_test_header_end(coap_test_msg_t *msg) {\n    return msg->content + COAP_TEST_HEADER_SIZE;\n}\n\n#define COAP_TEST_FIELD_GET(field, mask, shift) (((field) & (mask)) >> (shift))\n#define COAP_TEST_FIELD_SET(field, mask, shift, value) \\\n    ((field) = (uint8_t) (((field) & ~(mask))          \\\n                          | (uint8_t) (((value) << (shift)) & (mask))))\n\nstatic inline uint16_t extract_u16(const uint8_t *data) {\n    uint16_t result;\n    memcpy(&result, data, sizeof(uint16_t));\n    return avs_convert_be16(result);\n}\n\n#define COAP_TEST_HEADER_VERSION_MASK 0xC0\n#define COAP_TEST_HEADER_VERSION_SHIFT 6\n\nstatic inline uint8_t coap_test_header_get_version(const coap_test_msg_t *msg) {\n    const coap_test_header_t *hdr = (const coap_test_header_t *) msg->content;\n    int val = COAP_TEST_FIELD_GET(hdr->version_type_token_length,\n                                  COAP_TEST_HEADER_VERSION_MASK,\n                                  COAP_TEST_HEADER_VERSION_SHIFT);\n    assert(val >= 0 && val <= 3);\n    return (uint8_t) val;\n}\n\nstatic inline void coap_test_header_set_version(coap_test_msg_t *msg,\n                                                uint8_t version) {\n    assert(version <= 3);\n    coap_test_header_t *hdr = (coap_test_header_t *) msg->content;\n    COAP_TEST_FIELD_SET(hdr->version_type_token_length,\n                        COAP_TEST_HEADER_VERSION_MASK,\n                        COAP_TEST_HEADER_VERSION_SHIFT, version);\n}\n\n#define COAP_TEST_HEADER_TOKEN_LENGTH_MASK 0x0F\n#define COAP_TEST_HEADER_TOKEN_LENGTH_SHIFT 0\n\nstatic inline uint8_t\ncoap_test_header_get_token_length(const coap_test_msg_t *msg) {\n    const coap_test_header_t *hdr = (const coap_test_header_t *) msg->content;\n    int val = COAP_TEST_FIELD_GET(hdr->version_type_token_length,\n                                  COAP_TEST_HEADER_TOKEN_LENGTH_MASK,\n                                  COAP_TEST_HEADER_TOKEN_LENGTH_SHIFT);\n    assert(val >= 0 && val <= COAP_TEST_HEADER_TOKEN_LENGTH_MASK);\n    return (uint8_t) val;\n}\n\nstatic inline void coap_test_header_set_token_length(coap_test_msg_t *msg,\n                                                     uint8_t token_length) {\n    assert(token_length <= AVS_COAP_MAX_TOKEN_LENGTH);\n    coap_test_header_t *hdr = (coap_test_header_t *) msg->content;\n    COAP_TEST_FIELD_SET(hdr->version_type_token_length,\n                        COAP_TEST_HEADER_TOKEN_LENGTH_MASK,\n                        COAP_TEST_HEADER_TOKEN_LENGTH_SHIFT, token_length);\n}\n\n/** @{\n * Used for retrieving CoAP message type from @ref coap_msg_header_t .\n */\n#define COAP_TEST_HEADER_TYPE_MASK 0x30\n#define COAP_TEST_HEADER_TYPE_SHIFT 4\n/** @} */\n\nstatic inline coap_test_msg_type_t\ncoap_test_header_get_type(const coap_test_msg_t *msg) {\n    const coap_test_header_t *hdr = (const coap_test_header_t *) msg->content;\n    int val = COAP_TEST_FIELD_GET(hdr->version_type_token_length,\n                                  COAP_TEST_HEADER_TYPE_MASK,\n                                  COAP_TEST_HEADER_TYPE_SHIFT);\n    assert(val >= _COAP_TEST_MSG_FIRST && val <= _COAP_TEST_MSG_LAST);\n    return (coap_test_msg_type_t) val;\n}\n\nstatic inline void coap_test_header_set_type(coap_test_msg_t *msg,\n                                             coap_test_msg_type_t type) {\n    coap_test_header_t *hdr = (coap_test_header_t *) msg->content;\n    COAP_TEST_FIELD_SET(hdr->version_type_token_length,\n                        COAP_TEST_HEADER_TYPE_MASK, COAP_TEST_HEADER_TYPE_SHIFT,\n                        type);\n}\n\nstatic inline uint8_t coap_test_header_get_code(const coap_test_msg_t *msg) {\n    return msg->content[offsetof(coap_test_header_t, code)];\n}\n\nstatic inline void coap_test_header_set_code(coap_test_msg_t *msg,\n                                             uint8_t code) {\n    msg->content[offsetof(coap_test_header_t, code)] = code;\n}\n\nstatic inline uint16_t coap_test_header_get_id(const coap_test_msg_t *msg) {\n    return extract_u16(&msg->content[offsetof(coap_test_header_t, message_id)]);\n}\n\nstatic inline void coap_test_header_set_id(coap_test_msg_t *msg,\n                                           uint16_t msg_id) {\n    uint16_t msg_id_nbo = avs_convert_be16(msg_id);\n    memcpy(&msg->content[offsetof(coap_test_header_t, message_id)], &msg_id_nbo,\n           sizeof(msg_id_nbo));\n}\n\nstruct coap_msg_args {\n    coap_test_msg_type_t type;\n    uint8_t code;\n    coap_test_msg_identity_t id;\n\n    bool has_raw_token;\n    avs_coap_token_t token;\n\n    // The array is needed to set token with maximum length by convenient ID\n    // macro without invalid writing null-byte outside id.token.bytes[] array.\n    const char token_as_string__[AVS_COAP_MAX_TOKEN_LENGTH + 1];\n\n    const uint16_t *content_format;\n    const uint16_t *accept;\n    const uint32_t *observe;\n\n    const avs_coap_etag_t etag;\n    const bool has_etag;\n    const avs_coap_option_block_t block1;\n    const bool has_block1;\n    const avs_coap_option_block_t block2;\n    const bool has_block2;\n\n    const void *payload;\n    size_t payload_size;\n\n    AVS_LIST(const anjay_string_t) location_path;\n    AVS_LIST(const anjay_string_t) uri_path;\n    AVS_LIST(const anjay_string_t) uri_query;\n};\n\nstatic void add_string_options(avs_coap_options_t *options,\n                               uint16_t option_number,\n                               AVS_LIST(const anjay_string_t) values) {\n    AVS_LIST(const anjay_string_t) it;\n    AVS_LIST_FOREACH(it, values) {\n        AVS_UNIT_ASSERT_SUCCESS(\n                avs_coap_options_add_string(options, option_number, it->c_str));\n    }\n}\n\nstatic uint8_t *msg_end_ptr(coap_test_msg_t *msg) {\n    return &msg->content[msg->length];\n}\n\nstatic size_t bytes_remaining(const coap_test_msg_t *msg, size_t max_size) {\n    return max_size - msg->length - offsetof(coap_test_msg_t, content);\n}\n\nstatic void append_data(coap_test_msg_t *msg,\n                        const size_t max_size,\n                        const void *data,\n                        size_t data_size) {\n    AVS_UNIT_ASSERT_TRUE(data_size <= bytes_remaining(msg, max_size));\n    memcpy(msg_end_ptr(msg), data, data_size);\n    msg->length += (uint32_t) data_size;\n}\n\ntypedef struct aligned_buffer aligned_buffer_t;\n\nstatic inline aligned_buffer_t *ensure_aligned_buffer(void *buffer) {\n    AVS_ASSERT((uintptr_t) buffer % AVS_ALIGNOF(coap_test_msg_t) == 0,\n               \"Buffer alignment must be the same as of coap_test_msg_t\");\n    return (aligned_buffer_t *) buffer;\n}\n\nstatic inline const coap_test_msg_t *\ncoap_msg__(aligned_buffer_t *buf,\n           size_t buf_size,\n           const struct coap_msg_args *args) {\n    coap_test_msg_t *msg = (coap_test_msg_t *) buf;\n\n    coap_test_header_set_type(msg, args->type);\n    coap_test_header_set_version(msg, 1);\n    coap_test_header_set_code(msg, args->code);\n    coap_test_header_set_token_length(msg, args->id.token.size);\n    coap_test_header_set_id(msg, args->id.msg_id);\n    msg->length = (uint32_t) COAP_TEST_HEADER_SIZE;\n\n    avs_coap_token_t token = {\n        .bytes = { 0 },\n        .size = args->id.token.size\n    };\n    memcpy(token.bytes, args->id.token.bytes, args->id.token.size);\n    if (!args->has_raw_token) {\n        memcpy(token.bytes, args->token_as_string__, args->id.token.size);\n    }\n    append_data(msg, buf_size, token.bytes, token.size);\n\n    uint8_t options_buffer[1024];\n    avs_coap_options_t options =\n            avs_coap_options_create_empty(options_buffer,\n                                          sizeof(options_buffer));\n\n    if (args->has_block1) {\n        AVS_UNIT_ASSERT_SUCCESS(\n                avs_coap_options_add_block(&options, &args->block1));\n    }\n    if (args->has_block2) {\n        AVS_UNIT_ASSERT_SUCCESS(\n                avs_coap_options_add_block(&options, &args->block2));\n    }\n    if (args->has_etag) {\n        AVS_UNIT_ASSERT_SUCCESS(\n                avs_coap_options_add_etag(&options, &args->etag));\n    }\n\n    add_string_options(&options, AVS_COAP_OPTION_LOCATION_PATH,\n                       args->location_path);\n    add_string_options(&options, AVS_COAP_OPTION_URI_PATH, args->uri_path);\n    add_string_options(&options, AVS_COAP_OPTION_URI_QUERY, args->uri_query);\n\n    if (args->content_format) {\n        AVS_UNIT_ASSERT_SUCCESS(\n                avs_coap_options_add_u16(&options,\n                                         AVS_COAP_OPTION_CONTENT_FORMAT,\n                                         *args->content_format));\n    }\n    if (args->accept) {\n        AVS_UNIT_ASSERT_SUCCESS(avs_coap_options_add_u16(\n                &options, AVS_COAP_OPTION_ACCEPT, *args->accept));\n    }\n    if (args->observe) {\n        AVS_UNIT_ASSERT_SUCCESS(avs_coap_options_add_u32(\n                &options, AVS_COAP_OPTION_OBSERVE, *args->observe));\n    }\n\n    append_data(msg, buf_size, options.begin, options.size);\n\n    if (args->payload_size) {\n        // payload marker\n        append_data(msg, buf_size, &(const uint8_t) { 0xFF }, 1);\n        append_data(msg, buf_size, args->payload, args->payload_size);\n    }\n\n    AVS_LIST_CLEAR(\n            (AVS_LIST(anjay_string_t) *) (intptr_t) &args->location_path);\n    AVS_LIST_CLEAR((AVS_LIST(anjay_string_t) *) (intptr_t) &args->uri_path);\n    AVS_LIST_CLEAR((AVS_LIST(anjay_string_t) *) (intptr_t) &args->uri_query);\n    return msg;\n}\n\n/* Convenience macros for use in COAP_MSG */\n#define CON COAP_TEST_MSG_CONFIRMABLE\n#define NON COAP_TEST_MSG_NON_CONFIRMABLE\n#define ACK COAP_TEST_MSG_ACKNOWLEDGEMENT\n#define RST COAP_TEST_MSG_RESET\n\n/* Convenience macro for use in COAP_MSG, to allow skipping AVS_COAP_CODE_\n * prefix */\n#define CODE__(x) AVS_COAP_CODE_##x\n\n/* Convenience macro for use in COAP_MSG, to allow skipping\n * AVS_COAP_FORMAT_ prefix */\n#define FORMAT__(x) AVS_COAP_FORMAT_##x\n\n/* Allocates a 64k buffer on the stack, constructs a message inside it and\n * returns the message pointer.\n *\n * @p Type    - one of COAP_TEST_MSG_* constants or CON, NON, ACK, RST.\n * @p Code    - suffix of one of AVS_COAP_CODE_* constants, e.g. GET\n *              or BAD_REQUEST.\n * @p Id      - message identity specified with the ID() macro.\n * @p Payload - one of NO_PAYLOAD, PAYLOAD(), BLOCK2().\n * @p Opts... - additional options, e.g. ETAG(), PATH(), QUERY().\n *\n * Example usage:\n * @code\n * const coap_test_msg_t *msg = COAP_MSG(CON, GET, ID(0), NO_PAYLOAD);\n * const coap_test_msg_t *msg = COAP_MSG(ACK, CONTENT, ID(0),\n *                                       BLOCK2(0, 16, \"full_payload\"));\n * @endcode\n */\n#define COAP_MSG(Type, Code, Id, ... /* Payload, Opts... */)                 \\\n    coap_msg__(                                                              \\\n            ensure_aligned_buffer((uint8_t *) &(                             \\\n                    avs_max_align_t[65536 / sizeof(avs_max_align_t)]){ 0 }), \\\n            65536,                                                           \\\n            &(struct coap_msg_args) {                                        \\\n                .type = (Type),                                              \\\n                .code = CODE__(Code),                                        \\\n                Id,                                                          \\\n                __VA_ARGS__                                                  \\\n            })\n\n/* Used in COAP_MSG() to define message identity. */\n#define ID_TOKEN(MsgId, Token)                                                 \\\n    .id = (coap_test_msg_identity_t) { (uint16_t) (MsgId),                     \\\n                                       (avs_coap_token_t) { sizeof(Token) - 1, \\\n                                                            \"\" } },            \\\n    .token_as_string__ = Token\n\n/* Used in COAP_MSG() to define message identity with empty token. */\n#define ID(MsgId) ID_TOKEN((MsgId), \"\")\n\n/* Used in COAP_MSG() to pass message token. */\n#define ID_TOKEN_RAW(MsgId, Token)     \\\n    .has_raw_token = true,             \\\n    .id = (coap_test_msg_identity_t) { \\\n        (uint16_t)(MsgId), (Token)     \\\n    }\n\n/* Used in COAP_MSG() to specify ETag option value. */\n#define ETAG(Tag)                \\\n    .etag = (avs_coap_etag_t) {  \\\n        .size = sizeof(Tag) - 1, \\\n        .bytes = Tag             \\\n    },                           \\\n    .has_etag = true\n\n/* Used in COAP_MSG() to specify a list of Location-Path options. */\n#define LOCATION_PATH(... /* Segments */) \\\n    .location_path = ANJAY_MAKE_STRING_LIST(__VA_ARGS__)\n\n/* Used in COAP_MSG() to specify a list of Uri-Path options. */\n#define PATH(... /* Segments */) .uri_path = ANJAY_MAKE_STRING_LIST(__VA_ARGS__)\n\n/* Used in COAP_MSG() to specify a list of Uri-Query options. */\n#define QUERY(... /* Segments */) \\\n    .uri_query = ANJAY_MAKE_STRING_LIST(__VA_ARGS__)\n\n/* Used in COAP_MSG() to specify the Content-Format option even with\n * unsupported value. */\n#define CONTENT_FORMAT_VALUE(Format)        \\\n    .content_format = (const uint16_t[1]) { \\\n        (Format)                            \\\n    }\n\n/* Used in COAP_MSG() to specify the Content-Format option using predefined\n * constants. */\n#define CONTENT_FORMAT(Format) CONTENT_FORMAT_VALUE(FORMAT__(Format))\n\n/* Used in COAP_MSG() to specify the Accept option. */\n#define ACCEPT(Format)              \\\n    .accept = (const uint16_t[1]) { \\\n        (Format)                    \\\n    }\n\n/* Used in COAP_MSG() to specify the Observe option. */\n#define OBSERVE(Value)               \\\n    .observe = (const uint32_t[1]) { \\\n        (Value)                      \\\n    }\n\n/* Used in COAP_MSG() to define a message with no payload or BLOCK options. */\n#define NO_PAYLOAD   \\\n    .block1 = { 0 }, \\\n    .block2 = { 0 }, \\\n    .payload = NULL, \\\n    .payload_size = 0\n\n/* Used in COAP_MSG() to define a non-block message payload from external\n * variable (not only string literal). */\n#define PAYLOAD_EXTERNAL(Payload, PayloadSize) \\\n    .block1 = { 0 },                           \\\n    .block2 = { 0 },                           \\\n    .payload = Payload,                        \\\n    .payload_size = PayloadSize,\n\n/* Used in COAP_MSG() to define a non-block message payload (string literal).\n * Terminating nullbyte is not considered part of the payload. */\n#define PAYLOAD(Payload) PAYLOAD_EXTERNAL(Payload, sizeof(Payload) - 1)\n\n/**\n * Used in COAP_MSG to define BLOCK2 option, and optionally add block payload.\n * @p Seq     - the block sequence number.\n * @p Size    - block size.\n * @p Payload - if specified, FULL PAYLOAD OF WHOLE BLOCK-WISE TRANSFER (!),\n *              given as a string literal. The macro will extract the portion\n *              of it based on Seq and Size. Terminating nullbyte is not\n *              considered part of the payload.\n */\n#define BLOCK2(Seq, Size, ... /* Payload */)                               \\\n    .block1 = { 0 },                                                       \\\n    .block2 = {                                                            \\\n        .type = AVS_COAP_BLOCK2,                                           \\\n        .seq_num = (assert((Seq) < (1 << 23)), (uint32_t) (Seq)),          \\\n        .size = (assert((Size) < (1 << 15)), (uint16_t) (Size)),           \\\n        .has_more = ((Seq + 1) * (Size) + 1 < sizeof(\"\" __VA_ARGS__))      \\\n    },                                                                     \\\n    .has_block2 = true,                                                    \\\n    .payload = ((const uint8_t *) (\"\" __VA_ARGS__)) + (Seq) * (Size),      \\\n    .payload_size =                                                        \\\n            sizeof(\"\" __VA_ARGS__) == sizeof(\"\")                           \\\n                    ? 0                                                    \\\n                    : ((((Seq) + 1) * (Size) + 1 < sizeof(\"\" __VA_ARGS__)) \\\n                               ? (Size)                                    \\\n                               : (sizeof(\"\" __VA_ARGS__) - 1               \\\n                                  - (Seq) * (Size)))\n\nstatic inline void expect_has_buffered_data_check(avs_net_socket_t *mocksock,\n                                                  bool has_buffered_data) {\n    avs_unit_mocksock_expect_get_opt(mocksock, AVS_NET_SOCKET_HAS_BUFFERED_DATA,\n                                     (avs_net_socket_opt_value_t) {\n                                         .flag = has_buffered_data\n                                     });\n}\n\n#endif // ANJAY_COAP_TEST_UTILS_H\n"
  },
  {
    "path": "tests/core/dm.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#include <math.h>\n\n#define AVS_UNIT_ENABLE_SHORT_ASSERTS\n#include <avsystem/commons/avs_unit_mocksock.h>\n#include <avsystem/commons/avs_unit_test.h>\n\n#include \"src/core/anjay_core.h\"\n#include \"src/core/io/anjay_vtable.h\"\n#include \"src/core/servers/anjay_servers_internal.h\"\n#include \"tests/core/coap/utils.h\"\n#include \"tests/utils/dm.h\"\n\nAVS_UNIT_TEST(debug, debug_make_path_macro) {\n    anjay_request_t request;\n    request.uri = MAKE_ROOT_PATH();\n    AVS_UNIT_ASSERT_EQUAL_STRING(ANJAY_DEBUG_MAKE_PATH(&request.uri), \"/\");\n    request.uri = MAKE_OBJECT_PATH(0);\n    AVS_UNIT_ASSERT_EQUAL_STRING(ANJAY_DEBUG_MAKE_PATH(&request.uri), \"/0\");\n    request.uri = MAKE_INSTANCE_PATH(0, 1);\n    AVS_UNIT_ASSERT_EQUAL_STRING(ANJAY_DEBUG_MAKE_PATH(&request.uri), \"/0/1\");\n    request.uri = MAKE_RESOURCE_PATH(0, 1, 2);\n    AVS_UNIT_ASSERT_EQUAL_STRING(ANJAY_DEBUG_MAKE_PATH(&request.uri), \"/0/1/2\");\n\n    request.uri = MAKE_RESOURCE_PATH(65534, 65534, 65534);\n    AVS_UNIT_ASSERT_EQUAL_STRING(ANJAY_DEBUG_MAKE_PATH(&request.uri),\n                                 \"/65534/65534/65534\");\n}\n\n#ifdef ANJAY_WITH_LWM2M11\nAVS_UNIT_TEST(dm_read, resource_instance) {\n    DM_TEST_INIT;\n    DM_TEST_REQUEST(mocksocks[0], CON, GET, ID(0xFA3E),\n                    PATH(\"42\", \"53\", \"64\", \"75\"), NO_PAYLOAD);\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0, (const anjay_iid_t[]) { 53, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 53, 0,\n            (const anjay_mock_dm_res_entry_t[]) { { 64, ANJAY_DM_RES_RM,\n                                                    ANJAY_DM_RES_PRESENT },\n                                                  ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_list_resource_instances(\n            anjay, &OBJ, 53, 64, 0,\n            (const anjay_riid_t[]) { 75, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_resource_read(anjay, &OBJ, 53, 64, 75, 0,\n                                        ANJAY_MOCK_DM_INT(0, 514));\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, CONTENT, ID(0xFA3E),\n                            CONTENT_FORMAT(PLAINTEXT), PAYLOAD(\"514\"));\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_read, resource_instance_read_err_concrete) {\n    DM_TEST_INIT;\n    DM_TEST_REQUEST(mocksocks[0], CON, GET, ID(0xFA3E),\n                    PATH(\"42\", \"53\", \"64\", \"75\"), NO_PAYLOAD);\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0, (const anjay_iid_t[]) { 53, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 53, 0,\n            (const anjay_mock_dm_res_entry_t[]) { { 64, ANJAY_DM_RES_RM,\n                                                    ANJAY_DM_RES_PRESENT },\n                                                  ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_list_resource_instances(\n            anjay, &OBJ, 53, 64, 0,\n            (const anjay_riid_t[]) { 75, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_resource_read(anjay, &OBJ, 53, 64, 75,\n                                        ANJAY_ERR_UNAUTHORIZED,\n                                        ANJAY_MOCK_DM_NONE);\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, UNAUTHORIZED, ID(0xFA3E),\n                            NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_read, resource_instance_read_err_generic) {\n    DM_TEST_INIT;\n    DM_TEST_REQUEST(mocksocks[0], CON, GET, ID(0xFA3E),\n                    PATH(\"42\", \"53\", \"64\", \"75\"), NO_PAYLOAD);\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0, (const anjay_iid_t[]) { 53, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 53, 0,\n            (const anjay_mock_dm_res_entry_t[]) { { 64, ANJAY_DM_RES_RM,\n                                                    ANJAY_DM_RES_PRESENT },\n                                                  ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_list_resource_instances(\n            anjay, &OBJ, 53, 64, 0,\n            (const anjay_riid_t[]) { 75, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_resource_read(anjay, &OBJ, 53, 64, 75, -1,\n                                        ANJAY_MOCK_DM_NONE);\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, INTERNAL_SERVER_ERROR,\n                            ID(0xFA3E), NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_FAILED(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_read, resource_instance_not_found_because_not_present) {\n    DM_TEST_INIT;\n    DM_TEST_REQUEST(mocksocks[0], CON, GET, ID(0xFA3E),\n                    PATH(\"42\", \"53\", \"64\", \"75\"), NO_PAYLOAD);\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0, (const anjay_iid_t[]) { 53, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 53, 0,\n            (const anjay_mock_dm_res_entry_t[]) { { 64, ANJAY_DM_RES_RM,\n                                                    ANJAY_DM_RES_PRESENT },\n                                                  ANJAY_MOCK_DM_RES_END });\n    // Empty multiple resource\n    _anjay_mock_dm_expect_list_resource_instances(anjay, &OBJ, 53, 64, 0,\n                                                  (const anjay_riid_t[]) {\n                                                          ANJAY_ID_INVALID });\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, NOT_FOUND, ID(0xFA3E),\n                            NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n#endif // ANJAY_WITH_LWM2M11\n\nAVS_UNIT_TEST(dm_read, resource) {\n    DM_TEST_INIT;\n    DM_TEST_REQUEST(mocksocks[0], CON, GET, ID(0xFA3E), PATH(\"42\", \"69\", \"4\"),\n                    NO_PAYLOAD);\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0,\n            (const anjay_iid_t[]) { 14, 42, 69, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 69, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                    { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read(anjay, &OBJ, 69, 4, ANJAY_ID_INVALID, 0,\n                                        ANJAY_MOCK_DM_INT(0, 514));\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, CONTENT, ID(0xFA3E),\n                            CONTENT_FORMAT(PLAINTEXT), PAYLOAD(\"514\"));\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_read, resource_read_err_concrete) {\n    DM_TEST_INIT;\n    DM_TEST_REQUEST(mocksocks[0], CON, GET, ID(0xFA3E), PATH(\"42\", \"69\", \"4\"),\n                    NO_PAYLOAD);\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0,\n            (const anjay_iid_t[]) { 14, 42, 69, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 69, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                    { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read(anjay, &OBJ, 69, 4, ANJAY_ID_INVALID,\n                                        ANJAY_ERR_UNAUTHORIZED,\n                                        ANJAY_MOCK_DM_NONE);\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, UNAUTHORIZED, ID(0xFA3E),\n                            NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_read, resource_read_err_generic) {\n    DM_TEST_INIT;\n    DM_TEST_REQUEST(mocksocks[0], CON, GET, ID(0xFA3E), PATH(\"42\", \"69\", \"4\"),\n                    NO_PAYLOAD);\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0,\n            (const anjay_iid_t[]) { 14, 42, 69, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 69, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                    { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read(anjay, &OBJ, 69, 4, ANJAY_ID_INVALID,\n                                        -1, ANJAY_MOCK_DM_NONE);\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, INTERNAL_SERVER_ERROR,\n                            ID(0xFA3E), NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_FAILED(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_read, resource_not_found_because_not_present) {\n    DM_TEST_INIT;\n    DM_TEST_REQUEST(mocksocks[0], CON, GET, ID(0xFA3E), PATH(\"42\", \"69\", \"4\"),\n                    NO_PAYLOAD);\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0,\n            (const anjay_iid_t[]) { 14, 42, 69, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 69, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, NOT_FOUND, ID(0xFA3E),\n                            NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_read, instance_empty) {\n    DM_TEST_INIT;\n    DM_TEST_REQUEST(mocksocks[0], CON, GET, ID(0xFA3E), PATH(\"42\", \"13\"),\n                    NO_PAYLOAD);\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0, (const anjay_iid_t[]) { 13, 14, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 13, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, CONTENT, ID(0xFA3E),\n                            CONTENT_FORMAT(OMA_LWM2M_TLV), NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_read, instance_some) {\n    DM_TEST_INIT;\n    DM_TEST_REQUEST(mocksocks[0], CON, GET, ID(0xFA3E), PATH(\"42\", \"13\"),\n                    NO_PAYLOAD);\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0, (const anjay_iid_t[]) { 13, 14, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 13, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                    { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read(anjay, &OBJ, 13, 0, ANJAY_ID_INVALID, 0,\n                                        ANJAY_MOCK_DM_INT(0, 69));\n    _anjay_mock_dm_expect_resource_read(anjay, &OBJ, 13, 6, ANJAY_ID_INVALID, 0,\n                                        ANJAY_MOCK_DM_STRING(0, \"Hello\"));\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, CONTENT, ID(0xFA3E),\n                            CONTENT_FORMAT(OMA_LWM2M_TLV),\n                            PAYLOAD(\"\\xc1\\x00\\x45\"\n                                    \"\\xc5\\x06\"\n                                    \"Hello\"));\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_read, instance_resource_not_found) {\n    DM_TEST_INIT;\n    DM_TEST_REQUEST(mocksocks[0], CON, GET, ID(0xFA3E), PATH(\"42\", \"13\"),\n                    NO_PAYLOAD);\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0, (const anjay_iid_t[]) { 13, 14, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 13, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                    { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                    { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read(anjay, &OBJ, 13, 0, ANJAY_ID_INVALID,\n                                        ANJAY_ERR_NOT_FOUND,\n                                        ANJAY_MOCK_DM_NONE);\n    _anjay_mock_dm_expect_resource_read(anjay, &OBJ, 13, 1, ANJAY_ID_INVALID, 0,\n                                        ANJAY_MOCK_DM_INT(0, 69));\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, CONTENT, ID(0xFA3E),\n                            CONTENT_FORMAT(OMA_LWM2M_TLV),\n                            PAYLOAD(\"\\xc1\\x01\\x45\"));\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_read, instance_not_found) {\n    DM_TEST_INIT;\n    DM_TEST_REQUEST(mocksocks[0], CON, GET, ID(0xFA3E), PATH(\"42\", \"13\"),\n                    NO_PAYLOAD);\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0,\n            (const anjay_iid_t[]) { 4, 14, 69, ANJAY_ID_INVALID });\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, NOT_FOUND, ID(0xFA3E),\n                            NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_read, instance_err_concrete) {\n    DM_TEST_INIT;\n    DM_TEST_REQUEST(mocksocks[0], CON, GET, ID(0xFA3E), PATH(\"42\", \"13\"),\n                    NO_PAYLOAD);\n    _anjay_mock_dm_expect_list_instances(anjay, &OBJ, ANJAY_ERR_UNAUTHORIZED,\n                                         (const anjay_iid_t[]) {\n                                                 ANJAY_ID_INVALID });\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, UNAUTHORIZED, ID(0xFA3E),\n                            NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_read, instance_err_generic) {\n    DM_TEST_INIT;\n    DM_TEST_REQUEST(mocksocks[0], CON, GET, ID(0xFA3E), PATH(\"42\", \"13\"),\n                    NO_PAYLOAD);\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, -1, (const anjay_iid_t[]) { ANJAY_ID_INVALID });\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, INTERNAL_SERVER_ERROR,\n                            ID(0xFA3E), NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_FAILED(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_read, object_empty) {\n    DM_TEST_INIT;\n    DM_TEST_REQUEST(mocksocks[0], CON, GET, ID(0xFA3E), PATH(\"42\"), NO_PAYLOAD);\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0, (const anjay_iid_t[]) { ANJAY_ID_INVALID });\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, CONTENT, ID(0xFA3E),\n                            CONTENT_FORMAT(OMA_LWM2M_TLV), NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_read, object_not_found) {\n    DM_TEST_INIT;\n    DM_TEST_REQUEST(mocksocks[0], CON, GET, ID(0xFA3E), PATH(\"3\"), NO_PAYLOAD);\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, NOT_FOUND, ID(0xFA3E),\n                            NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_read, object_some) {\n    DM_TEST_INIT;\n    DM_TEST_REQUEST(mocksocks[0], CON, GET, ID(0xFA3E), PATH(\"42\"), NO_PAYLOAD);\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0, (const anjay_iid_t[]) { 3, 7, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 3, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 7, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, CONTENT, ID(0xFA3E),\n                            CONTENT_FORMAT(OMA_LWM2M_TLV),\n                            PAYLOAD(\"\\x00\\x03\\x00\\x07\"));\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_read, object_err_concrete) {\n    DM_TEST_INIT;\n    DM_TEST_REQUEST(mocksocks[0], CON, GET, ID(0xFA3E), PATH(\"42\"), NO_PAYLOAD);\n    _anjay_mock_dm_expect_list_instances(anjay, &OBJ, ANJAY_ERR_UNAUTHORIZED,\n                                         (const anjay_iid_t[]) {\n                                                 ANJAY_ID_INVALID });\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, UNAUTHORIZED, ID(0xFA3E),\n                            NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_read, object_err_generic) {\n    DM_TEST_INIT;\n    DM_TEST_REQUEST(mocksocks[0], CON, GET, ID(0xFA3E), PATH(\"42\"), NO_PAYLOAD);\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, -1, (const anjay_iid_t[]) { ANJAY_ID_INVALID });\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, INTERNAL_SERVER_ERROR,\n                            ID(0xFA3E), NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_FAILED(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_read, no_object) {\n    DM_TEST_INIT;\n    DM_TEST_REQUEST(mocksocks[0], CON, GET, ID(0xFA3E), NO_PAYLOAD);\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, BAD_REQUEST, ID(0xFA3E),\n                            NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_read, query) {\n    DM_TEST_INIT;\n    DM_TEST_REQUEST(mocksocks[0], CON, GET, ID(0xFA3E), PATH(\"42\", \"69\", \"4\"),\n                    QUERY(\"depth=1\"), NO_PAYLOAD);\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, BAD_OPTION, ID(0xFA3E),\n                            NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_read_accept, force_tlv) {\n    DM_TEST_INIT;\n    DM_TEST_REQUEST(mocksocks[0], CON, GET, ID(0xFA3E), PATH(\"42\", \"69\", \"4\"),\n                    ACCEPT(0x2d16), NO_PAYLOAD);\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0, (const anjay_iid_t[]) { 69, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 69, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                    { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read(anjay, &OBJ, 69, 4, ANJAY_ID_INVALID, 0,\n                                        ANJAY_MOCK_DM_INT(0, 514));\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, CONTENT, ID(0xFA3E),\n                            CONTENT_FORMAT(OMA_LWM2M_TLV),\n                            PAYLOAD(\"\\xc2\\x04\\x02\\x02\"));\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_read_accept, force_text_ok) {\n    DM_TEST_INIT;\n    DM_TEST_REQUEST(mocksocks[0], CON, GET, ID(0xFA3E), PATH(\"42\", \"69\", \"4\"),\n                    ACCEPT(0), NO_PAYLOAD);\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0, (const anjay_iid_t[]) { 69, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 69, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                    { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read(anjay, &OBJ, 69, 4, ANJAY_ID_INVALID, 0,\n                                        ANJAY_MOCK_DM_INT(0, 514));\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, CONTENT, ID(0xFA3E),\n                            CONTENT_FORMAT(PLAINTEXT), PAYLOAD(\"514\"));\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_read_accept, force_text_on_bytes) {\n    DM_TEST_INIT;\n    DM_TEST_REQUEST(mocksocks[0], CON, GET, ID(0xFA3E), PATH(\"42\", \"69\", \"4\"),\n                    ACCEPT(0), NO_PAYLOAD);\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0, (const anjay_iid_t[]) { 69, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 69, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                    { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read(anjay, &OBJ, 69, 4, ANJAY_ID_INVALID, 0,\n                                        ANJAY_MOCK_DM_BYTES(0, \"bytes\"));\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, CONTENT, ID(0xFA3E),\n                            CONTENT_FORMAT(PLAINTEXT), PAYLOAD(\"Ynl0ZXM=\"));\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_read_accept, force_text_invalid) {\n    DM_TEST_INIT;\n    DM_TEST_REQUEST(mocksocks[0], CON, GET, ID(0xFA3E), PATH(\"42\", \"69\"),\n                    ACCEPT(0), NO_PAYLOAD);\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0, (const anjay_iid_t[]) { 69, ANJAY_ID_INVALID });\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, NOT_ACCEPTABLE, ID(0xFA3E),\n                            NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_read_accept, force_opaque_ok) {\n    DM_TEST_INIT;\n    DM_TEST_REQUEST(mocksocks[0], CON, GET, ID(0xFA3E), PATH(\"42\", \"69\", \"4\"),\n                    ACCEPT(0x2a), NO_PAYLOAD);\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0, (const anjay_iid_t[]) { 69, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 69, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                    { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read(anjay, &OBJ, 69, 4, ANJAY_ID_INVALID, 0,\n                                        ANJAY_MOCK_DM_BYTES(0, \"bytes\"));\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, CONTENT, ID(0xFA3E),\n                            CONTENT_FORMAT(OCTET_STREAM), PAYLOAD(\"bytes\"));\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_read_accept, force_opaque_mismatch) {\n    DM_TEST_INIT;\n    DM_TEST_REQUEST(mocksocks[0], CON, GET, ID(0xFA3E), PATH(\"42\", \"69\", \"4\"),\n                    ACCEPT(0x2a), NO_PAYLOAD);\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0, (const anjay_iid_t[]) { 69, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 69, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                    { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read(\n            anjay, &OBJ, 69, 4, ANJAY_ID_INVALID, -1,\n            ANJAY_MOCK_DM_INT(ANJAY_OUTCTXERR_METHOD_NOT_IMPLEMENTED, 514));\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, NOT_ACCEPTABLE, ID(0xFA3E),\n                            NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_read_accept, force_opaque_invalid) {\n    DM_TEST_INIT;\n    DM_TEST_REQUEST(mocksocks[0], CON, GET, ID(0xFA3E), PATH(\"42\", \"69\"),\n                    ACCEPT(0x2a), NO_PAYLOAD);\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0, (const anjay_iid_t[]) { 69, ANJAY_ID_INVALID });\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, NOT_ACCEPTABLE, ID(0xFA3E),\n                            NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_read_accept, invalid_format) {\n    DM_TEST_INIT;\n    DM_TEST_REQUEST(mocksocks[0], CON, GET, ID(0xFA3E), PATH(\"42\", \"69\", \"4\"),\n                    ACCEPT(0x4242), NO_PAYLOAD);\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0, (const anjay_iid_t[]) { 69, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 69, 0,\n            (const anjay_mock_dm_res_entry_t[]) { { 4, ANJAY_DM_RES_WM,\n                                                    ANJAY_DM_RES_PRESENT },\n                                                  ANJAY_MOCK_DM_RES_END });\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, NOT_ACCEPTABLE, ID(0xFA3E),\n                            NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\n#ifdef ANJAY_WITH_LWM2M11\nAVS_UNIT_TEST(dm_write, resource_instance) {\n    DM_TEST_INIT;\n    DM_TEST_REQUEST(mocksocks[0], CON, PUT, ID(0xFA3E),\n                    PATH(\"42\", \"53\", \"64\", \"75\"), CONTENT_FORMAT(PLAINTEXT),\n                    PAYLOAD(\"Hello\"));\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0, (const anjay_iid_t[]) { 53, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 53, 0,\n            (const anjay_mock_dm_res_entry_t[]) { { 64, ANJAY_DM_RES_WM,\n                                                    ANJAY_DM_RES_PRESENT },\n                                                  ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_list_resource_instances(\n            anjay, &OBJ, 53, 64, 0,\n            (const anjay_riid_t[]) { 75, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_resource_write(anjay, &OBJ, 53, 64, 75,\n                                         ANJAY_MOCK_DM_STRING(0, \"Hello\"), 0);\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, CHANGED, ID(0xFA3E), NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_write, resource_instance_resource_absent) {\n    DM_TEST_INIT;\n    DM_TEST_REQUEST(mocksocks[0], CON, PUT, ID(0xFA3E),\n                    PATH(\"42\", \"53\", \"64\", \"75\"), CONTENT_FORMAT(PLAINTEXT),\n                    PAYLOAD(\"Hello\"));\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0, (const anjay_iid_t[]) { 53, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 53, 0,\n            (const anjay_mock_dm_res_entry_t[]) { { 64, ANJAY_DM_RES_WM,\n                                                    ANJAY_DM_RES_ABSENT },\n                                                  ANJAY_MOCK_DM_RES_END });\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, NOT_FOUND, ID(0xFA3E),\n                            NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_write, resource_instance_resource_not_writable) {\n    DM_TEST_INIT;\n    DM_TEST_REQUEST(mocksocks[0], CON, PUT, ID(0xFA3E),\n                    PATH(\"42\", \"53\", \"64\", \"75\"), CONTENT_FORMAT(PLAINTEXT),\n                    PAYLOAD(\"Hello\"));\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0, (const anjay_iid_t[]) { 53, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 53, 0,\n            (const anjay_mock_dm_res_entry_t[]) { { 64, ANJAY_DM_RES_RM,\n                                                    ANJAY_DM_RES_PRESENT },\n                                                  ANJAY_MOCK_DM_RES_END });\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, METHOD_NOT_ALLOWED, ID(0xFA3E),\n                            NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_write, resource_instance_resource_not_multiple) {\n    DM_TEST_INIT;\n    DM_TEST_REQUEST(mocksocks[0], CON, PUT, ID(0xFA3E),\n                    PATH(\"42\", \"53\", \"64\", \"75\"), CONTENT_FORMAT(PLAINTEXT),\n                    PAYLOAD(\"Hello\"));\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0, (const anjay_iid_t[]) { 53, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 53, 0,\n            (const anjay_mock_dm_res_entry_t[]) { { 64, ANJAY_DM_RES_W,\n                                                    ANJAY_DM_RES_PRESENT },\n                                                  ANJAY_MOCK_DM_RES_END });\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, METHOD_NOT_ALLOWED, ID(0xFA3E),\n                            NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_write, resource_instance_absent) {\n    DM_TEST_INIT;\n    DM_TEST_REQUEST(mocksocks[0], CON, PUT, ID(0xFA3E),\n                    PATH(\"42\", \"53\", \"64\", \"75\"), CONTENT_FORMAT(PLAINTEXT),\n                    PAYLOAD(\"Hello\"));\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0, (const anjay_iid_t[]) { 53, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 53, 0,\n            (const anjay_mock_dm_res_entry_t[]) { { 64, ANJAY_DM_RES_WM,\n                                                    ANJAY_DM_RES_PRESENT },\n                                                  ANJAY_MOCK_DM_RES_END });\n    // Empty multiple resource\n    _anjay_mock_dm_expect_list_resource_instances(anjay, &OBJ, 53, 64, 0,\n                                                  (const anjay_riid_t[]) {\n                                                          ANJAY_ID_INVALID });\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, NOT_FOUND, ID(0xFA3E),\n                            NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_write, resource_instance_coap_format_tlv) {\n    DM_TEST_INIT;\n    DM_TEST_REQUEST(mocksocks[0], CON, PUT, ID(0xFA3E),\n                    PATH(\"42\", \"53\", \"64\", \"75\"), CONTENT_FORMAT(OMA_LWM2M_TLV),\n                    // [ 0x45 ][ 0x4B ]\n                    // 01.............. Resource Instance\n                    // ..0............. Identifier field is 8 bits long\n                    // ...00........... No length field\n                    // .....101........ Length = 5\n                    // ........01001011 ID = 75\n                    PAYLOAD(\"\\x45\\x4BHello\"));\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0, (const anjay_iid_t[]) { 53, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 53, 0,\n            (const anjay_mock_dm_res_entry_t[]) { { 64, ANJAY_DM_RES_WM,\n                                                    ANJAY_DM_RES_PRESENT },\n                                                  ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_list_resource_instances(\n            anjay, &OBJ, 53, 64, 0,\n            (const anjay_riid_t[]) { 75, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_resource_write(anjay, &OBJ, 53, 64, 75,\n                                         ANJAY_MOCK_DM_STRING(0, \"Hello\"), 0);\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, CHANGED, ID(0xFA3E), NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_write, resource_instance_coap_format_senml_cbor) {\n    DM_TEST_INIT;\n    DM_TEST_REQUEST(mocksocks[0], CON, PUT, ID(0xFA3E),\n                    PATH(\"42\", \"53\", \"64\", \"75\"), CONTENT_FORMAT(SENML_CBOR),\n                    // [{0 (CBOR_SENML_LABEL_NAME): \"/42/53/64/75\",\n                    //   3 (CBOR_SENML_LABEL_VALUE_STRING): \"Hello\"}]\n                    PAYLOAD(\"\\x81\\xA2\\x00\\x6C/42/53/64/75\\x03\\x65Hello\"));\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0, (const anjay_iid_t[]) { 53, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 53, 0,\n            (const anjay_mock_dm_res_entry_t[]) { { 64, ANJAY_DM_RES_WM,\n                                                    ANJAY_DM_RES_PRESENT },\n                                                  ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_list_resource_instances(\n            anjay, &OBJ, 53, 64, 0,\n            (const anjay_riid_t[]) { 75, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_resource_write(anjay, &OBJ, 53, 64, 75,\n                                         ANJAY_MOCK_DM_STRING(0, \"Hello\"), 0);\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, CHANGED, ID(0xFA3E), NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n#endif // ANJAY_WITH_LWM2M11\n\nAVS_UNIT_TEST(dm_write, resource) {\n    DM_TEST_INIT;\n    DM_TEST_REQUEST(mocksocks[0], CON, PUT, ID(0xFA3E), PATH(\"42\", \"514\", \"4\"),\n                    CONTENT_FORMAT(PLAINTEXT), PAYLOAD(\"Hello\"));\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0,\n            (const anjay_iid_t[]) { 14, 42, 69, 514, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 514, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_write(anjay, &OBJ, 514, 4, ANJAY_ID_INVALID,\n                                         ANJAY_MOCK_DM_STRING(0, \"Hello\"), 0);\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, CHANGED, ID(0xFA3E), NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_write, resource_plaintext_integer_with_leading_zero) {\n    DM_TEST_INIT;\n    DM_TEST_REQUEST(mocksocks[0], CON, PUT, ID(0xFA3E), PATH(\"42\", \"514\", \"4\"),\n                    CONTENT_FORMAT(PLAINTEXT), PAYLOAD(\"0101\"));\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0,\n            (const anjay_iid_t[]) { 14, 42, 69, 514, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 514, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_write(anjay, &OBJ, 514, 4, ANJAY_ID_INVALID,\n                                         ANJAY_MOCK_DM_INT(0, 101), 0);\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, CHANGED, ID(0xFA3E), NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_write, resource_unsupported_format) {\n    DM_TEST_INIT;\n    DM_TEST_REQUEST(mocksocks[0], CON, PUT, ID(0xFA3E), PATH(\"42\", \"514\", \"4\"),\n                    CONTENT_FORMAT_VALUE(0x4242), PAYLOAD(\"Hello\"));\n    // 4.15 Unsupported Content Format.\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, UNSUPPORTED_CONTENT_FORMAT,\n                            ID(0xFA3E), NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_write, resource_with_mismatched_tlv_rid) {\n    DM_TEST_INIT;\n    DM_TEST_REQUEST(mocksocks[0], CON, PUT, ID(0xFA3E), PATH(\"42\", \"514\", \"4\"),\n                    CONTENT_FORMAT(OMA_LWM2M_TLV),\n                    PAYLOAD(\"\\xc5\\x05\"\n                            \"Hello\"));\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0,\n            (const anjay_iid_t[]) { 14, 42, 69, 514, ANJAY_ID_INVALID });\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, BAD_REQUEST, ID(0xFA3E),\n                            NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_write, instance) {\n    DM_TEST_INIT;\n    DM_TEST_REQUEST(mocksocks[0], CON, PUT, ID(0xFA3E), PATH(\"42\", \"69\"),\n                    CONTENT_FORMAT(OMA_LWM2M_TLV),\n                    PAYLOAD(\"\\xc1\\x00\\x0d\"\n                            \"\\xc5\\x06\"\n                            \"Hello\"));\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0,\n            (const anjay_iid_t[]) { 14, 42, 69, 514, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 69, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_write(anjay, &OBJ, 69, 0, ANJAY_ID_INVALID,\n                                         ANJAY_MOCK_DM_INT(0, 13), 0);\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 69, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                    { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_write(anjay, &OBJ, 69, 6, ANJAY_ID_INVALID,\n                                         ANJAY_MOCK_DM_STRING(0, \"Hello\"), 0);\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, CHANGED, ID(0xFA3E), NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_write, instance_unsupported_format) {\n    DM_TEST_INIT;\n    DM_TEST_REQUEST(mocksocks[0], CON, PUT, ID(0xFA3E), PATH(\"42\", \"69\"),\n                    CONTENT_FORMAT_VALUE(0x4242),\n                    PAYLOAD(\"\\xc1\\x00\\x0d\"\n                            \"\\xc5\\x06\"\n                            \"Hello\"));\n    // 4.15 Unsupported Content Format\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, UNSUPPORTED_CONTENT_FORMAT,\n                            ID(0xFA3E), NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_write, instance_partial) {\n    DM_TEST_INIT;\n    DM_TEST_REQUEST(mocksocks[0], CON, POST, ID(0xFA3E), PATH(\"42\", \"69\"),\n                    CONTENT_FORMAT(OMA_LWM2M_TLV),\n                    PAYLOAD(\"\\xc1\\x00\\x0d\"\n                            \"\\xc5\\x06\"\n                            \"Hello\"));\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0,\n            (const anjay_iid_t[]) { 14, 42, 69, 514, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 69, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_write(anjay, &OBJ, 69, 0, ANJAY_ID_INVALID,\n                                         ANJAY_MOCK_DM_INT(0, 13), 0);\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 69, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                    { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_write(anjay, &OBJ, 69, 6, ANJAY_ID_INVALID,\n                                         ANJAY_MOCK_DM_STRING(0, \"Hello\"), 0);\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, CHANGED, ID(0xFA3E), NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_write, instance_full) {\n    DM_TEST_INIT;\n    DM_TEST_REQUEST(mocksocks[0], CON, PUT, ID(0xFA3E), PATH(\"25\", \"69\"),\n                    CONTENT_FORMAT(OMA_LWM2M_TLV),\n                    PAYLOAD(\"\\xc1\\x00\\x0d\"\n                            \"\\xc5\\x06\"\n                            \"Hello\"));\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ_WITH_RESET, 0,\n            (const anjay_iid_t[]) { 14, 42, 69, 514, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_instance_reset(anjay, &OBJ_WITH_RESET, 69, 0);\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ_WITH_RESET, 69, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_write(anjay, &OBJ_WITH_RESET, 69, 0,\n                                         ANJAY_ID_INVALID,\n                                         ANJAY_MOCK_DM_INT(0, 13), 0);\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ_WITH_RESET, 69, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                    { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_write(anjay, &OBJ_WITH_RESET, 69, 6,\n                                         ANJAY_ID_INVALID,\n                                         ANJAY_MOCK_DM_STRING(0, \"Hello\"), 0);\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, CHANGED, ID(0xFA3E), NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_write, instance_superfluous_instance) {\n    DM_TEST_INIT;\n    DM_TEST_REQUEST(mocksocks[0], CON, PUT, ID(0xFA3E), PATH(\"25\", \"69\"),\n                    CONTENT_FORMAT(OMA_LWM2M_TLV),\n                    PAYLOAD(\"\\x08\\x45\\x0a\"\n                            \"\\xc1\\x00\\x0d\"\n                            \"\\xc5\\x06\"\n                            \"Hello\"));\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ_WITH_RESET, 0,\n            (const anjay_iid_t[]) { 14, 42, 69, 514, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_instance_reset(anjay, &OBJ_WITH_RESET, 69, 0);\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ_WITH_RESET, 69, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_write(anjay, &OBJ_WITH_RESET, 69, 0,\n                                         ANJAY_ID_INVALID,\n                                         ANJAY_MOCK_DM_INT(0, 13), 0);\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ_WITH_RESET, 69, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                    { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_write(anjay, &OBJ_WITH_RESET, 69, 6,\n                                         ANJAY_ID_INVALID,\n                                         ANJAY_MOCK_DM_STRING(0, \"Hello\"), 0);\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, CHANGED, ID(0xFA3E), NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_write, instance_superfluous_and_empty) {\n    DM_TEST_INIT;\n    DM_TEST_REQUEST(mocksocks[0], CON, PUT, ID(0xFA3E), PATH(\"25\", \"1\"),\n                    CONTENT_FORMAT(OMA_LWM2M_TLV), PAYLOAD(\"\\x00\\x01\"));\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ_WITH_RESET, 0,\n            (const anjay_iid_t[]) { 1, 14, 42, 69, 514, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_instance_reset(anjay, &OBJ_WITH_RESET, 1, 0);\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, CHANGED, ID(0xFA3E), NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_write, instance_inconsistent_instance) {\n    DM_TEST_INIT;\n    DM_TEST_REQUEST(mocksocks[0], CON, PUT, ID(0xFA3E), PATH(\"42\", \"69\"),\n                    CONTENT_FORMAT(OMA_LWM2M_TLV),\n                    PAYLOAD(\"\\x08\\x4d\\x0a\"\n                            \"\\xc1\\x00\\x0d\"\n                            \"\\xc5\\x06\"\n                            \"Hello\"));\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0,\n            (const anjay_iid_t[]) { 14, 42, 69, 514, ANJAY_ID_INVALID });\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, BAD_REQUEST, ID(0xFA3E),\n                            NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_write, instance_wrong_type) {\n    DM_TEST_INIT;\n    DM_TEST_REQUEST(mocksocks[0], CON, PUT, ID(0xFA3E), PATH(\"42\", \"69\"),\n                    CONTENT_FORMAT(OMA_LWM2M_TLV),\n                    PAYLOAD(\"\\x01\\x00\\x0d\"\n                            \"\\xc5\\x06\"\n                            \"Hello\"));\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0,\n            (const anjay_iid_t[]) { 14, 42, 69, 514, ANJAY_ID_INVALID });\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, BAD_REQUEST, ID(0xFA3E),\n                            NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_write, instance_nonexistent) {\n    DM_TEST_INIT;\n    DM_TEST_REQUEST(mocksocks[0], CON, PUT, ID(0xFA3E), PATH(\"42\", \"69\"),\n                    CONTENT_FORMAT(OMA_LWM2M_TLV),\n                    PAYLOAD(\"\\xc1\\x00\\x0d\"\n                            \"\\xc5\\x06\"\n                            \"Hello\"));\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0,\n            (const anjay_iid_t[]) { 4, 14, 514, ANJAY_ID_INVALID });\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, NOT_FOUND, ID(0xFA3E),\n                            NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_write, no_instance) {\n    DM_TEST_INIT;\n    DM_TEST_REQUEST(mocksocks[0], CON, PUT, ID(0xFA3E), PATH(\"42\"),\n                    CONTENT_FORMAT(OMA_LWM2M_TLV),\n                    PAYLOAD(\"\\x08\\x45\\x0a\"\n                            \"\\xc1\\x00\\x0d\"\n                            \"\\xc5\\x06\"\n                            \"Hello\"));\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, METHOD_NOT_ALLOWED, ID(0xFA3E),\n                            NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_write, multiple_resource_followed_by_single_resoure) {\n    DM_TEST_INIT;\n    DM_TEST_REQUEST(mocksocks[0], CON, PUT, ID(0xFA3E), PATH(\"42\", \"69\"),\n                    CONTENT_FORMAT(OMA_LWM2M_TLV),\n                    PAYLOAD(\"\\x88\\x15\\x0e\"\n                            \"\\x45\\x03\"\n                            \"Hello\"\n                            \"\\x45\\x07\"\n                            \"world\"\n                            \"\\xe4\\x01\\xa4\"\n                            \"test\"));\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0, (const anjay_iid_t[]) { 69, ANJAY_ID_INVALID });\n    // Write /42/69/21\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 69, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 21, ANJAY_DM_RES_RWM, ANJAY_DM_RES_PRESENT },\n                    { 37, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                    { 420, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_reset(anjay, &OBJ, 69, 21, 0);\n    _anjay_mock_dm_expect_resource_write(anjay, &OBJ, 69, 21, 3,\n                                         ANJAY_MOCK_DM_STRING(0, \"Hello\"), 0);\n    _anjay_mock_dm_expect_resource_write(anjay, &OBJ, 69, 21, 7,\n                                         ANJAY_MOCK_DM_STRING(0, \"world\"), 0);\n    // Write /42/69/420\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 69, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 21, ANJAY_DM_RES_RWM, ANJAY_DM_RES_PRESENT },\n                    { 37, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                    { 420, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_write(anjay, &OBJ, 69, 420, ANJAY_ID_INVALID,\n                                         ANJAY_MOCK_DM_STRING(0, \"test\"), 0);\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, CHANGED, ID(0xFA3E), NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_write, multiple_resource_reset_not_found) {\n    DM_TEST_INIT;\n    DM_TEST_REQUEST(mocksocks[0], CON, PUT, ID(0xFA3E), PATH(\"42\", \"69\"),\n                    CONTENT_FORMAT(OMA_LWM2M_TLV),\n                    PAYLOAD(\"\\x88\\x15\\x0e\"\n                            \"\\x45\\x03\"\n                            \"Hello\"\n                            \"\\x45\\x07\"\n                            \"world\"\n                            \"\\xe4\\x01\\xa4\"\n                            \"test\"));\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0, (const anjay_iid_t[]) { 69, ANJAY_ID_INVALID });\n    // Reset attempt for /42/69/21/3\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 69, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 21, ANJAY_DM_RES_RWM, ANJAY_DM_RES_PRESENT },\n                    { 37, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                    { 420, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_reset(anjay, &OBJ, 69, 21,\n                                         ANJAY_ERR_NOT_FOUND);\n    // Reset attempt for /42/69/21/3\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 69, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 21, ANJAY_DM_RES_RWM, ANJAY_DM_RES_PRESENT },\n                    { 37, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                    { 420, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_reset(anjay, &OBJ, 69, 21,\n                                         ANJAY_ERR_NOT_FOUND);\n    // Write /42/69/420\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 69, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 21, ANJAY_DM_RES_RWM, ANJAY_DM_RES_PRESENT },\n                    { 37, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                    { 420, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_write(anjay, &OBJ, 69, 420, ANJAY_ID_INVALID,\n                                         ANJAY_MOCK_DM_STRING(0, \"test\"), 0);\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, CHANGED, ID(0xFA3E), NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_write, multiple_resource_first_instance_not_found) {\n    DM_TEST_INIT;\n    DM_TEST_REQUEST(mocksocks[0], CON, PUT, ID(0xFA3E), PATH(\"42\", \"69\"),\n                    CONTENT_FORMAT(OMA_LWM2M_TLV),\n                    PAYLOAD(\"\\x88\\x15\\x0e\"\n                            \"\\x45\\x03\"\n                            \"Hello\"\n                            \"\\x45\\x07\"\n                            \"world\"\n                            \"\\xe4\\x01\\xa4\"\n                            \"test\"));\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0, (const anjay_iid_t[]) { 69, ANJAY_ID_INVALID });\n    // Write attempt for /42/69/21/3\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 69, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 21, ANJAY_DM_RES_RWM, ANJAY_DM_RES_PRESENT },\n                    { 37, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                    { 420, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_reset(anjay, &OBJ, 69, 21, 0);\n    _anjay_mock_dm_expect_resource_write(anjay, &OBJ, 69, 21, 3,\n                                         ANJAY_MOCK_DM_STRING(0, \"Hello\"),\n                                         ANJAY_ERR_NOT_FOUND);\n    // Write attempt for /42/69/21/7\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 69, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 21, ANJAY_DM_RES_RWM, ANJAY_DM_RES_PRESENT },\n                    { 37, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                    { 420, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_reset(anjay, &OBJ, 69, 21, 0);\n    _anjay_mock_dm_expect_resource_write(anjay, &OBJ, 69, 21, 7,\n                                         ANJAY_MOCK_DM_STRING(0, \"world\"), 0);\n    // Write /42/69/420\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 69, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 21, ANJAY_DM_RES_RWM, ANJAY_DM_RES_PRESENT },\n                    { 37, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                    { 420, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_write(anjay, &OBJ, 69, 420, ANJAY_ID_INVALID,\n                                         ANJAY_MOCK_DM_STRING(0, \"test\"), 0);\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, CHANGED, ID(0xFA3E), NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_write, multiple_resource_second_instance_not_found) {\n    DM_TEST_INIT;\n    DM_TEST_REQUEST(mocksocks[0], CON, PUT, ID(0xFA3E), PATH(\"42\", \"69\"),\n                    CONTENT_FORMAT(OMA_LWM2M_TLV),\n                    PAYLOAD(\"\\x88\\x15\\x0e\"\n                            \"\\x45\\x03\"\n                            \"Hello\"\n                            \"\\x45\\x07\"\n                            \"world\"\n                            \"\\xe4\\x01\\xa4\"\n                            \"test\"));\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0, (const anjay_iid_t[]) { 69, ANJAY_ID_INVALID });\n    // Write /42/69/21\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 69, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 21, ANJAY_DM_RES_RWM, ANJAY_DM_RES_PRESENT },\n                    { 37, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                    { 420, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_reset(anjay, &OBJ, 69, 21, 0);\n    _anjay_mock_dm_expect_resource_write(anjay, &OBJ, 69, 21, 3,\n                                         ANJAY_MOCK_DM_STRING(0, \"Hello\"), 0);\n    _anjay_mock_dm_expect_resource_write(anjay, &OBJ, 69, 21, 7,\n                                         ANJAY_MOCK_DM_STRING(0, \"world\"),\n                                         ANJAY_ERR_NOT_FOUND);\n    // Write /42/69/420\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 69, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 21, ANJAY_DM_RES_RWM, ANJAY_DM_RES_PRESENT },\n                    { 37, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                    { 420, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_write(anjay, &OBJ, 69, 420, ANJAY_ID_INVALID,\n                                         ANJAY_MOCK_DM_STRING(0, \"test\"), 0);\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, CHANGED, ID(0xFA3E), NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\n#ifdef ANJAY_WITH_LWM2M11\nAVS_UNIT_TEST(dm_write_composite, write_to_resource_of_nonexistent_instance) {\n    DM_TEST_INIT;\n    static const char PAYLOAD[] = \"\\x81\\xa2\\x00\\x67\"\n                                  \"/42/1/2\"\n                                  \"\\x02\\x18\\x2a\";\n    DM_TEST_REQUEST(mocksocks[0], CON, IPATCH, ID(0xFA3E),\n                    CONTENT_FORMAT(SENML_CBOR),\n                    PAYLOAD_EXTERNAL(PAYLOAD, sizeof(PAYLOAD) - 1));\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0,\n            (const anjay_iid_t[]) { 4, 14, 514, ANJAY_ID_INVALID });\n\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, NOT_FOUND, ID(0xFA3E),\n                            NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n#endif // ANJAY_WITH_LWM2M11\n\nAVS_UNIT_TEST(dm_execute, success) {\n    DM_TEST_INIT;\n    DM_TEST_REQUEST(mocksocks[0], CON, POST, ID(0xFA3E),\n                    PATH(\"42\", \"514\", \"4\"));\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0,\n            (const anjay_iid_t[]) { 14, 42, 69, 514, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 514, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_E, ANJAY_DM_RES_ABSENT },\n                    { 1, ANJAY_DM_RES_E, ANJAY_DM_RES_ABSENT },\n                    { 2, ANJAY_DM_RES_E, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_E, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_E, ANJAY_DM_RES_PRESENT },\n                    { 5, ANJAY_DM_RES_E, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_E, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_execute(anjay, &OBJ, 514, 4, NULL, 0);\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, CHANGED, ID(0xFA3E), NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_execute, data) {\n    DM_TEST_INIT;\n#define NYANCAT \"Nyanyanyanyanyanyanya!\"\n    DM_TEST_REQUEST(mocksocks[0], CON, POST, ID(0xFA3E), PATH(\"42\", \"514\", \"4\"),\n                    PAYLOAD(\"7='\" NYANCAT \"'\"));\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0,\n            (const anjay_iid_t[]) { 14, 42, 69, 514, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 514, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_E, ANJAY_DM_RES_ABSENT },\n                    { 1, ANJAY_DM_RES_E, ANJAY_DM_RES_ABSENT },\n                    { 2, ANJAY_DM_RES_E, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_E, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_E, ANJAY_DM_RES_PRESENT },\n                    { 5, ANJAY_DM_RES_E, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_E, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_execute(\n            anjay, &OBJ, 514, 4,\n            ANJAY_MOCK_DM_EXECUTE(ANJAY_MOCK_DM_EXECUTE_ARG(0, 7, NYANCAT)), 0);\n#undef NYANCAT\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, CHANGED, ID(0xFA3E), NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_execute, error) {\n    DM_TEST_INIT;\n    DM_TEST_REQUEST(mocksocks[0], CON, POST, ID(0xFA3E),\n                    PATH(\"42\", \"514\", \"4\"));\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0,\n            (const anjay_iid_t[]) { 14, 42, 69, 514, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 514, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_E, ANJAY_DM_RES_ABSENT },\n                    { 1, ANJAY_DM_RES_E, ANJAY_DM_RES_ABSENT },\n                    { 2, ANJAY_DM_RES_E, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_E, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_E, ANJAY_DM_RES_PRESENT },\n                    { 5, ANJAY_DM_RES_E, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_E, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_execute(anjay, &OBJ, 514, 4, NULL,\n                                           ANJAY_ERR_INTERNAL);\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, INTERNAL_SERVER_ERROR,\n                            ID(0xFA3E), NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_FAILED(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_execute, resource_inexistent) {\n    DM_TEST_INIT;\n    DM_TEST_REQUEST(mocksocks[0], CON, POST, ID(0xFA3E),\n                    PATH(\"42\", \"514\", \"1\"));\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0,\n            (const anjay_iid_t[]) { 14, 42, 69, 514, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 514, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_E, ANJAY_DM_RES_ABSENT },\n                    { 1, ANJAY_DM_RES_E, ANJAY_DM_RES_ABSENT },\n                    { 2, ANJAY_DM_RES_E, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_E, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_E, ANJAY_DM_RES_PRESENT },\n                    { 5, ANJAY_DM_RES_E, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_E, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, NOT_FOUND, ID(0xFA3E),\n                            NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_execute, instance_inexistent) {\n    DM_TEST_INIT;\n    DM_TEST_REQUEST(mocksocks[0], CON, POST, ID(0xFA3E),\n                    PATH(\"42\", \"666\", \"1\"));\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0,\n            (const anjay_iid_t[]) { 14, 42, 69, 514, ANJAY_ID_INVALID });\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, NOT_FOUND, ID(0xFA3E),\n                            NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nstatic int\nexecute_get_arg_value_invalid_args(anjay_t *anjay,\n                                   const anjay_dm_object_def_t *const *obj_ptr,\n                                   anjay_iid_t iid,\n                                   anjay_rid_t rid,\n                                   anjay_execute_ctx_t *ctx) {\n    (void) iid;\n    (void) rid;\n    (void) anjay;\n    (void) obj_ptr;\n    int ret;\n    int arg;\n    bool has_value;\n\n    ret = anjay_execute_get_next_arg(ctx, &arg, &has_value);\n    AVS_UNIT_ASSERT_EQUAL(ret, 0);\n    AVS_UNIT_ASSERT_EQUAL(arg, 0);\n    AVS_UNIT_ASSERT_EQUAL(has_value, true);\n\n    char buf[32];\n    // buf_size < 2\n    AVS_UNIT_ASSERT_FAILED(anjay_execute_get_arg_value(ctx, NULL, buf, 1));\n\n    // buf == NULL\n    AVS_UNIT_ASSERT_FAILED(anjay_execute_get_arg_value(ctx, NULL, NULL, 974));\n    return 0;\n}\n\nAVS_UNIT_TEST(dm_execute, execute_get_arg_value_invalid_args) {\n    DM_TEST_INIT;\n    EXECUTE_OBJ->handlers.resource_execute = execute_get_arg_value_invalid_args;\n    DM_TEST_REQUEST(mocksocks[0], CON, POST, ID(0xFA3E),\n                    PATH(\"128\", \"514\", \"1\"), PAYLOAD(\"0='foobarbaz'\"));\n\n    _anjay_mock_dm_expect_list_instances(\n            anjay, (const anjay_dm_object_def_t *const *) &EXECUTE_OBJ, 0,\n            (const anjay_iid_t[]) { 14, 42, 69, 514, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, (const anjay_dm_object_def_t *const *) &EXECUTE_OBJ, 514, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_E, ANJAY_DM_RES_ABSENT },\n                    { 1, ANJAY_DM_RES_E, ANJAY_DM_RES_PRESENT },\n                    { 2, ANJAY_DM_RES_E, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_E, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_E, ANJAY_DM_RES_ABSENT },\n                    { 5, ANJAY_DM_RES_E, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_E, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, CHANGED, ID(0xFA3E), NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nstatic int valid_args_execute(anjay_t *anjay,\n                              const anjay_dm_object_def_t *const *obj_ptr,\n                              anjay_iid_t iid,\n                              anjay_rid_t rid,\n                              anjay_execute_ctx_t *ctx) {\n    (void) iid;\n    (void) rid;\n    (void) anjay;\n    (void) obj_ptr;\n    int ret;\n    int arg;\n    bool has_value;\n\n    ret = anjay_execute_get_next_arg(ctx, &arg, &has_value);\n    AVS_UNIT_ASSERT_EQUAL(ret, 0);\n    AVS_UNIT_ASSERT_EQUAL(arg, 0);\n    AVS_UNIT_ASSERT_EQUAL(has_value, false);\n\n    ret = anjay_execute_get_next_arg(ctx, &arg, &has_value);\n    AVS_UNIT_ASSERT_EQUAL(ret, 0);\n    AVS_UNIT_ASSERT_EQUAL(arg, 1);\n    AVS_UNIT_ASSERT_EQUAL(has_value, false);\n\n    ret = anjay_execute_get_next_arg(ctx, &arg, &has_value);\n    AVS_UNIT_ASSERT_EQUAL(ret, 0);\n    AVS_UNIT_ASSERT_EQUAL(arg, 2);\n    AVS_UNIT_ASSERT_EQUAL(has_value, false);\n\n    ret = anjay_execute_get_next_arg(ctx, &arg, &has_value);\n    AVS_UNIT_ASSERT_EQUAL(ret, 1);\n    AVS_UNIT_ASSERT_EQUAL(arg, -1);\n    AVS_UNIT_ASSERT_EQUAL(has_value, false);\n    return 0;\n}\n\nAVS_UNIT_TEST(dm_execute, valid_args) {\n    DM_TEST_INIT;\n    EXECUTE_OBJ->handlers.resource_execute = valid_args_execute;\n    DM_TEST_REQUEST(mocksocks[0], CON, POST, ID(0xFA3E),\n                    PATH(\"128\", \"514\", \"1\"), PAYLOAD(\"0,1,2\"));\n\n    _anjay_mock_dm_expect_list_instances(\n            anjay, (const anjay_dm_object_def_t *const *) &EXECUTE_OBJ, 0,\n            (const anjay_iid_t[]) { 14, 42, 69, 514, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, (const anjay_dm_object_def_t *const *) &EXECUTE_OBJ, 514, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_E, ANJAY_DM_RES_ABSENT },\n                    { 1, ANJAY_DM_RES_E, ANJAY_DM_RES_PRESENT },\n                    { 2, ANJAY_DM_RES_E, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_E, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_E, ANJAY_DM_RES_ABSENT },\n                    { 5, ANJAY_DM_RES_E, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_E, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, CHANGED, ID(0xFA3E), NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nstatic int\nvalid_args_with_values_execute(anjay_t *anjay,\n                               const anjay_dm_object_def_t *const *obj_ptr,\n                               anjay_iid_t iid,\n                               anjay_rid_t rid,\n                               anjay_execute_ctx_t *ctx) {\n    (void) iid;\n    (void) rid;\n    (void) anjay;\n    (void) obj_ptr;\n    int ret;\n    int arg;\n    bool has_value;\n\n    ret = anjay_execute_get_next_arg(ctx, &arg, &has_value);\n    AVS_UNIT_ASSERT_EQUAL(ret, 0);\n    AVS_UNIT_ASSERT_EQUAL(arg, 0);\n    AVS_UNIT_ASSERT_EQUAL(has_value, false);\n\n    ret = anjay_execute_get_next_arg(ctx, &arg, &has_value);\n    AVS_UNIT_ASSERT_EQUAL(ret, 0);\n    AVS_UNIT_ASSERT_EQUAL(arg, 1);\n    AVS_UNIT_ASSERT_EQUAL(has_value, true);\n\n    char buf[32];\n    size_t read_bytes;\n    AVS_UNIT_ASSERT_SUCCESS(\n            anjay_execute_get_arg_value(ctx, &read_bytes, buf, 32));\n    AVS_UNIT_ASSERT_EQUAL(read_bytes, strlen(\"value\"));\n    AVS_UNIT_ASSERT_EQUAL_STRING(buf, \"value\");\n    /* Already read everything. */\n    AVS_UNIT_ASSERT_SUCCESS(\n            anjay_execute_get_arg_value(ctx, &read_bytes, buf, 32));\n    AVS_UNIT_ASSERT_EQUAL(read_bytes, 0);\n\n    ret = anjay_execute_get_next_arg(ctx, &arg, &has_value);\n    AVS_UNIT_ASSERT_EQUAL(ret, 0);\n    AVS_UNIT_ASSERT_EQUAL(arg, 2);\n    AVS_UNIT_ASSERT_EQUAL(has_value, false);\n\n    ret = anjay_execute_get_next_arg(ctx, &arg, &has_value);\n    AVS_UNIT_ASSERT_EQUAL(ret, 1);\n    AVS_UNIT_ASSERT_EQUAL(arg, -1);\n    AVS_UNIT_ASSERT_EQUAL(has_value, false);\n    return 0;\n}\n\nAVS_UNIT_TEST(dm_execute, valid_args_with_values) {\n    DM_TEST_INIT;\n    EXECUTE_OBJ->handlers.resource_execute = valid_args_with_values_execute;\n    DM_TEST_REQUEST(mocksocks[0], CON, POST, ID(0xFA3E),\n                    PATH(\"128\", \"514\", \"1\"), PAYLOAD(\"0,1='value',2\"));\n\n    _anjay_mock_dm_expect_list_instances(\n            anjay, (const anjay_dm_object_def_t *const *) &EXECUTE_OBJ, 0,\n            (const anjay_iid_t[]) { 14, 42, 69, 514, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, (const anjay_dm_object_def_t *const *) &EXECUTE_OBJ, 514, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_E, ANJAY_DM_RES_ABSENT },\n                    { 1, ANJAY_DM_RES_E, ANJAY_DM_RES_PRESENT },\n                    { 2, ANJAY_DM_RES_E, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_E, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_E, ANJAY_DM_RES_ABSENT },\n                    { 5, ANJAY_DM_RES_E, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_E, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, CHANGED, ID(0xFA3E), NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nstatic int\nvalid_values_partial_read_execute(anjay_t *anjay,\n                                  const anjay_dm_object_def_t *const *obj_ptr,\n                                  anjay_iid_t iid,\n                                  anjay_rid_t rid,\n                                  anjay_execute_ctx_t *ctx) {\n    (void) iid;\n    (void) rid;\n    (void) anjay;\n    (void) obj_ptr;\n    int ret;\n    int arg;\n    bool has_value;\n\n    ret = anjay_execute_get_next_arg(ctx, &arg, &has_value);\n    AVS_UNIT_ASSERT_EQUAL(ret, 0);\n    AVS_UNIT_ASSERT_EQUAL(arg, 1);\n    AVS_UNIT_ASSERT_EQUAL(has_value, true);\n\n    char buf[32];\n    /* Read in 2 parts. */\n    size_t read_bytes;\n    AVS_UNIT_ASSERT_EQUAL(anjay_execute_get_arg_value(ctx, &read_bytes, buf, 5),\n                          ANJAY_BUFFER_TOO_SHORT);\n    AVS_UNIT_ASSERT_EQUAL(read_bytes, strlen(\"very\"));\n    AVS_UNIT_ASSERT_EQUAL_STRING(buf, \"very\");\n    AVS_UNIT_ASSERT_SUCCESS(\n            anjay_execute_get_arg_value(ctx, &read_bytes, buf, 32));\n    AVS_UNIT_ASSERT_EQUAL(read_bytes, strlen(\"longvalue\"));\n    AVS_UNIT_ASSERT_EQUAL_STRING(buf, \"longvalue\");\n\n    ret = anjay_execute_get_next_arg(ctx, &arg, &has_value);\n    AVS_UNIT_ASSERT_EQUAL(ret, 1);\n    AVS_UNIT_ASSERT_EQUAL(arg, -1);\n    AVS_UNIT_ASSERT_EQUAL(has_value, false);\n    return 0;\n}\n\nAVS_UNIT_TEST(dm_execute, valid_values_partial_read) {\n    DM_TEST_INIT;\n    EXECUTE_OBJ->handlers.resource_execute = valid_values_partial_read_execute;\n    DM_TEST_REQUEST(mocksocks[0], CON, POST, ID(0xFA3E),\n                    PATH(\"128\", \"514\", \"1\"), PAYLOAD(\"1='verylongvalue'\"));\n\n    _anjay_mock_dm_expect_list_instances(\n            anjay, (const anjay_dm_object_def_t *const *) &EXECUTE_OBJ, 0,\n            (const anjay_iid_t[]) { 14, 42, 69, 514, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, (const anjay_dm_object_def_t *const *) &EXECUTE_OBJ, 514, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_E, ANJAY_DM_RES_ABSENT },\n                    { 1, ANJAY_DM_RES_E, ANJAY_DM_RES_PRESENT },\n                    { 2, ANJAY_DM_RES_E, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_E, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_E, ANJAY_DM_RES_ABSENT },\n                    { 5, ANJAY_DM_RES_E, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_E, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, CHANGED, ID(0xFA3E), NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nstatic int\nvalid_values_skipping_execute(anjay_t *anjay,\n                              const anjay_dm_object_def_t *const *obj_ptr,\n                              anjay_iid_t iid,\n                              anjay_rid_t rid,\n                              anjay_execute_ctx_t *ctx) {\n    (void) iid;\n    (void) rid;\n    (void) anjay;\n    (void) obj_ptr;\n    int ret;\n    int arg;\n    bool has_value;\n\n    ret = anjay_execute_get_next_arg(ctx, &arg, &has_value);\n    AVS_UNIT_ASSERT_EQUAL(ret, 0);\n    AVS_UNIT_ASSERT_EQUAL(arg, 1);\n    AVS_UNIT_ASSERT_EQUAL(has_value, true);\n\n    char buf[2];\n    size_t bytes_read;\n    AVS_UNIT_ASSERT_EQUAL(anjay_execute_get_arg_value(ctx, &bytes_read, buf, 2),\n                          ANJAY_BUFFER_TOO_SHORT);\n    AVS_UNIT_ASSERT_EQUAL(bytes_read, 1);\n    /* Don't care about the rest, ignore. */\n    ret = anjay_execute_get_next_arg(ctx, &arg, &has_value);\n    AVS_UNIT_ASSERT_EQUAL(ret, 0);\n    AVS_UNIT_ASSERT_EQUAL(arg, 2);\n    AVS_UNIT_ASSERT_EQUAL(has_value, false);\n\n    ret = anjay_execute_get_next_arg(ctx, &arg, &has_value);\n    AVS_UNIT_ASSERT_EQUAL(ret, 0);\n    AVS_UNIT_ASSERT_EQUAL(arg, 3);\n    AVS_UNIT_ASSERT_EQUAL(has_value, false);\n\n    AVS_UNIT_ASSERT_SUCCESS(\n            anjay_execute_get_arg_value(ctx, &bytes_read, buf, 2));\n    AVS_UNIT_ASSERT_EQUAL(bytes_read, 0);\n\n    ret = anjay_execute_get_next_arg(ctx, &arg, &has_value);\n    AVS_UNIT_ASSERT_EQUAL(ret, 1);\n    AVS_UNIT_ASSERT_EQUAL(arg, -1);\n    AVS_UNIT_ASSERT_EQUAL(has_value, false);\n\n    return 0;\n}\n\nAVS_UNIT_TEST(dm_execute, valid_values_skipping) {\n    DM_TEST_INIT;\n    EXECUTE_OBJ->handlers.resource_execute = valid_values_skipping_execute;\n    DM_TEST_REQUEST(\n            mocksocks[0], CON, POST, ID(0xFA3E), PATH(\"128\", \"514\", \"1\"),\n            PAYLOAD(\"1='ludicrously-long-value-because-we-want-this-to-not-fit-\"\n                    \"inside-a-single-64-byte-buffer-that-we-use-for-skipping-\"\n                    \"unread-values-and-also-the-spec-says-that-spaces-are-\"\n                    \"illegal-inside-Execute-arguments-so-thanks-OMA',2,3\"));\n\n    _anjay_mock_dm_expect_list_instances(\n            anjay, (const anjay_dm_object_def_t *const *) &EXECUTE_OBJ, 0,\n            (const anjay_iid_t[]) { 14, 42, 69, 514, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, (const anjay_dm_object_def_t *const *) &EXECUTE_OBJ, 514, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_E, ANJAY_DM_RES_ABSENT },\n                    { 1, ANJAY_DM_RES_E, ANJAY_DM_RES_PRESENT },\n                    { 2, ANJAY_DM_RES_E, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_E, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_E, ANJAY_DM_RES_ABSENT },\n                    { 5, ANJAY_DM_RES_E, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_E, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, CHANGED, ID(0xFA3E), NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nstatic int invalid_input_execute(anjay_t *anjay,\n                                 const anjay_dm_object_def_t *const *obj_ptr,\n                                 anjay_iid_t iid,\n                                 anjay_rid_t rid,\n                                 anjay_execute_ctx_t *ctx) {\n    (void) iid;\n    (void) rid;\n    (void) anjay;\n    (void) obj_ptr;\n    int ret;\n    int arg;\n    bool has_value;\n    char arg_value_buffer[2];\n\n    do {\n        ret = anjay_execute_get_next_arg(ctx, &arg, &has_value);\n        // anjay_execute_get_arg_value() should never trigger an error other\n        // than ANJAY_ERR_BAD_REQUEST for valid function arguments\n        int get_arg_value_ret =\n                anjay_execute_get_arg_value(ctx, NULL, arg_value_buffer,\n                                            AVS_ARRAY_SIZE(arg_value_buffer));\n        AVS_UNIT_ASSERT_TRUE(get_arg_value_ret == 0\n                             || get_arg_value_ret == ANJAY_BUFFER_TOO_SHORT\n                             || get_arg_value_ret == ANJAY_ERR_BAD_REQUEST);\n    } while (!ret);\n\n    return ret == ANJAY_EXECUTE_GET_ARG_END ? 0 : ret;\n}\n\nAVS_UNIT_TEST(dm_execute, invalid_input) {\n    // clang-format off\n    static const char* invalid_inputs[] = {\n        \"a\",\n        \"0=\",\n        \"0=1,2,3\",\n        \"0='val,1\",\n        \"0='val',1='val',3'',4\",\n        \"=\",\n        \"11\",\n        \"0='val',11\",\n        \"0='val\",\n        \"0=1=\",\n        \",0\",\n        \",,0\",\n        \"0,\",\n        \"0,,\",\n        \"0=,\",\n        \",0=\",\n        \"0='\\\"'\"\n    };\n    // clang-format on\n\n    EXECUTE_OBJ->handlers.resource_execute = invalid_input_execute;\n    for (size_t i = 0; i < AVS_ARRAY_SIZE(invalid_inputs); i++) {\n        DM_TEST_INIT;\n        DM_TEST_REQUEST(\n                mocksocks[0], CON, POST, ID(0xFA3E), PATH(\"128\", \"514\", \"1\"),\n                PAYLOAD_EXTERNAL(invalid_inputs[i], strlen(invalid_inputs[i])));\n        _anjay_mock_dm_expect_list_instances(\n                anjay, (const anjay_dm_object_def_t *const *) &EXECUTE_OBJ, 0,\n                (const anjay_iid_t[]) { 14, 42, 69, 514, ANJAY_ID_INVALID });\n        _anjay_mock_dm_expect_list_resources(\n                anjay, (const anjay_dm_object_def_t *const *) &EXECUTE_OBJ, 514,\n                0,\n                (const anjay_mock_dm_res_entry_t[]) {\n                        { 0, ANJAY_DM_RES_E, ANJAY_DM_RES_ABSENT },\n                        { 1, ANJAY_DM_RES_E, ANJAY_DM_RES_PRESENT },\n                        { 2, ANJAY_DM_RES_E, ANJAY_DM_RES_ABSENT },\n                        { 3, ANJAY_DM_RES_E, ANJAY_DM_RES_ABSENT },\n                        { 4, ANJAY_DM_RES_E, ANJAY_DM_RES_ABSENT },\n                        { 5, ANJAY_DM_RES_E, ANJAY_DM_RES_ABSENT },\n                        { 6, ANJAY_DM_RES_E, ANJAY_DM_RES_ABSENT },\n                        ANJAY_MOCK_DM_RES_END });\n        DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, BAD_REQUEST, ID(0xFA3E),\n                                NO_PAYLOAD);\n        expect_has_buffered_data_check(mocksocks[0], false);\n        AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n        DM_TEST_FINISH;\n    }\n}\n\nstatic int valid_input_execute(anjay_t *anjay,\n                               const anjay_dm_object_def_t *const *obj_ptr,\n                               anjay_iid_t iid,\n                               anjay_rid_t rid,\n                               anjay_execute_ctx_t *ctx) {\n    (void) iid;\n    (void) rid;\n    (void) anjay;\n    (void) obj_ptr;\n    int ret;\n    int arg;\n    bool has_value;\n\n    do {\n        ret = anjay_execute_get_next_arg(ctx, &arg, &has_value);\n    } while (!ret);\n\n    return ret < 0 ? -1 : 0;\n}\n\nAVS_UNIT_TEST(dm_execute, valid_input) {\n    static const char *valid_inputs[] = { \"\", \"0='ala'\", \"2='10.3'\",\n                                          \"7,0='https://www.oma.org'\",\n                                          \"0,1,2,3,4\" };\n\n    EXECUTE_OBJ->handlers.resource_execute = valid_input_execute;\n    for (size_t i = 0; i < AVS_ARRAY_SIZE(valid_inputs); i++) {\n        DM_TEST_INIT;\n        DM_TEST_REQUEST(\n                mocksocks[0], CON, POST, ID(0xFA3E), PATH(\"128\", \"514\", \"1\"),\n                PAYLOAD_EXTERNAL(valid_inputs[i], strlen(valid_inputs[i])));\n        _anjay_mock_dm_expect_list_instances(\n                anjay, (const anjay_dm_object_def_t *const *) &EXECUTE_OBJ, 0,\n                (const anjay_iid_t[]) { 14, 42, 69, 514, ANJAY_ID_INVALID });\n        _anjay_mock_dm_expect_list_resources(\n                anjay, (const anjay_dm_object_def_t *const *) &EXECUTE_OBJ, 514,\n                0,\n                (const anjay_mock_dm_res_entry_t[]) {\n                        { 0, ANJAY_DM_RES_E, ANJAY_DM_RES_ABSENT },\n                        { 1, ANJAY_DM_RES_E, ANJAY_DM_RES_PRESENT },\n                        { 2, ANJAY_DM_RES_E, ANJAY_DM_RES_ABSENT },\n                        { 3, ANJAY_DM_RES_E, ANJAY_DM_RES_ABSENT },\n                        { 4, ANJAY_DM_RES_E, ANJAY_DM_RES_ABSENT },\n                        { 5, ANJAY_DM_RES_E, ANJAY_DM_RES_ABSENT },\n                        { 6, ANJAY_DM_RES_E, ANJAY_DM_RES_ABSENT },\n                        ANJAY_MOCK_DM_RES_END });\n        DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, CHANGED, ID(0xFA3E),\n                                NO_PAYLOAD);\n        expect_has_buffered_data_check(mocksocks[0], false);\n        AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n        DM_TEST_FINISH;\n    }\n}\n\nAVS_UNIT_TEST(dm_write_attributes, resource) {\n    DM_TEST_INIT_WITH_SSIDS(77);\n    DM_TEST_REQUEST(mocksocks[0], CON, PUT, ID(0xFA3E), PATH(\"42\", \"514\", \"4\"),\n                    QUERY(\"pmin=42\", \"st=0.7\", \"epmax=2\"));\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0,\n            (const anjay_iid_t[]) { 14, 42, 69, 514, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 514, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                    { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read_attrs(anjay, &OBJ, 514, 4, 77, 0,\n                                              &ANJAY_DM_R_ATTRIBUTES_EMPTY);\n    _anjay_mock_dm_expect_resource_write_attrs(\n            anjay, &OBJ, 514, 4, 77,\n            &(const anjay_dm_r_attributes_t) {\n                .common = {\n                    .min_period = 42,\n                    .max_period = ANJAY_ATTRIB_INTEGER_NONE,\n                    .min_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n                    .max_eval_period = 2\n#ifdef ANJAY_WITH_CON_ATTR\n                    ,\n                    .con = ANJAY_DM_CON_ATTR_NONE\n#endif // ANJAY_WITH_CON_ATTR\n#ifdef ANJAY_WITH_LWM2M12\n                    ,\n                    .hqmax = ANJAY_ATTRIB_INTEGER_NONE\n#endif // ANJAY_WITH_LWM2M12\n                },\n                .greater_than = ANJAY_ATTRIB_DOUBLE_NONE,\n                .less_than = ANJAY_ATTRIB_DOUBLE_NONE,\n                .step = 0.7\n#ifdef ANJAY_WITH_LWM2M12\n                ,\n                .edge = ANJAY_DM_EDGE_ATTR_NONE\n#endif // ANJAY_WITH_LWM2M12\n            },\n            0);\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, CHANGED, ID(0xFA3E), NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_write_attributes, instance) {\n    DM_TEST_INIT_WITH_SSIDS(42);\n    DM_TEST_REQUEST(mocksocks[0], CON, PUT, ID(0xFA3E), PATH(\"42\", \"77\"),\n                    QUERY(\"pmin=69\", \"epmin=70\"));\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0, (const anjay_iid_t[]) { 69, 77, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_instance_read_default_attrs(\n            anjay, &OBJ, 77, 42, 0, &ANJAY_DM_OI_ATTRIBUTES_EMPTY);\n    _anjay_mock_dm_expect_instance_write_default_attrs(\n            anjay, &OBJ, 77, 42,\n            &(const anjay_dm_oi_attributes_t) {\n                .min_period = 69,\n                .max_period = ANJAY_ATTRIB_INTEGER_NONE,\n                .min_eval_period = 70,\n                .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE\n#ifdef ANJAY_WITH_CON_ATTR\n                ,\n                .con = ANJAY_DM_CON_ATTR_NONE\n#endif // ANJAY_WITH_CON_ATTR\n#ifdef ANJAY_WITH_LWM2M12\n                ,\n                .hqmax = ANJAY_ATTRIB_INTEGER_NONE\n#endif // ANJAY_WITH_LWM2M12\n            },\n            0);\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, CHANGED, ID(0xFA3E), NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_write_attributes, object) {\n    DM_TEST_INIT_WITH_SSIDS(666);\n    DM_TEST_REQUEST(mocksocks[0], CON, PUT, ID(0xFA3E), PATH(\"42\"),\n                    QUERY(\"pmax=514\", \"epmin=10\", \"epmax=20\"));\n    _anjay_mock_dm_expect_object_read_default_attrs(\n            anjay, &OBJ, 666, 0, &ANJAY_DM_OI_ATTRIBUTES_EMPTY);\n    _anjay_mock_dm_expect_object_write_default_attrs(\n            anjay, &OBJ, 666,\n            &(const anjay_dm_oi_attributes_t) {\n                .min_period = ANJAY_ATTRIB_INTEGER_NONE,\n                .max_period = 514,\n                .min_eval_period = 10,\n                .max_eval_period = 20\n#ifdef ANJAY_WITH_CON_ATTR\n                ,\n                .con = ANJAY_DM_CON_ATTR_NONE\n#endif // ANJAY_WITH_CON_ATTR\n#ifdef ANJAY_WITH_LWM2M12\n                ,\n                .hqmax = ANJAY_ATTRIB_INTEGER_NONE\n#endif // ANJAY_WITH_LWM2M12\n            },\n            0);\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, CHANGED, ID(0xFA3E), NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_write_attributes, no_resource) {\n    DM_TEST_INIT_WITH_SSIDS(1);\n    DM_TEST_REQUEST(mocksocks[0], CON, PUT, ID(0xFA3E), PATH(\"42\", \"2\", \"3\"),\n                    QUERY(\"pmin=42\"));\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0, (const anjay_iid_t[]) { 2, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 2, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, NOT_FOUND, ID(0xFA3E),\n                            NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_write_attributes, no_instance) {\n    DM_TEST_INIT_WITH_SSIDS(4);\n    DM_TEST_REQUEST(mocksocks[0], CON, PUT, ID(0xFA3E), PATH(\"42\", \"5\", \"6\"),\n                    QUERY(\"pmin=42\"));\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0, (const anjay_iid_t[]) { 2, ANJAY_ID_INVALID });\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, NOT_FOUND, ID(0xFA3E),\n                            NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_write_attributes, negative_pmin) {\n    DM_TEST_INIT_WITH_SSIDS(42);\n    DM_TEST_REQUEST(mocksocks[0], CON, PUT, ID(0xFA3E), PATH(\"42\", \"77\"),\n                    QUERY(\"pmin=-1\"));\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, BAD_OPTION, ID(0xFA3E),\n                            NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_write_attributes, negative_pmax) {\n    DM_TEST_INIT_WITH_SSIDS(42);\n    DM_TEST_REQUEST(mocksocks[0], CON, PUT, ID(0xFA3E), PATH(\"42\", \"77\"),\n                    QUERY(\"pmax=-1\"));\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, BAD_OPTION, ID(0xFA3E),\n                            NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\n#ifdef ANJAY_WITH_LWM2M11\nAVS_UNIT_TEST(dm_discover, resource_instance_attrs) {\n    DM_TEST_INIT_WITH_SSIDS(34, 45);\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    anjay_unlocked->servers->registration_info.lwm2m_version =\n            ANJAY_LWM2M_VERSION_1_1;\n    ANJAY_MUTEX_UNLOCK(anjay);\n    DM_TEST_REQUEST(mocksocks[0], CON, GET, ID(0xFA3E), PATH(\"42\", \"69\", \"4\"),\n                    ACCEPT(0x28), NO_PAYLOAD);\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0,\n            (const anjay_iid_t[]) { 14, 42, 69, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 69, 0,\n            (const anjay_mock_dm_res_entry_t[]) { { 4, ANJAY_DM_RES_RM,\n                                                    ANJAY_DM_RES_PRESENT },\n                                                  ANJAY_MOCK_DM_RES_END });\n    const anjay_riid_t resource_instances[] = { 123, 456, 789,\n                                                ANJAY_ID_INVALID };\n\n    // First call of dm_list_resource_instances - compute resource dim\n    _anjay_mock_dm_expect_list_resource_instances(anjay, &OBJ, 69, 4, 0,\n                                                  resource_instances);\n\n    _anjay_mock_dm_expect_resource_read_attrs(\n            anjay, &OBJ, 69, 4, 34, 0,\n            &(const anjay_dm_r_attributes_t) {\n                .common = {\n                    .min_period = 10,\n                    .max_period = ANJAY_ATTRIB_INTEGER_NONE,\n                    .min_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n                    .max_eval_period = 5\n#    ifdef ANJAY_WITH_CON_ATTR\n                    ,\n                    .con = ANJAY_DM_CON_ATTR_NONE\n#    endif // ANJAY_WITH_CON_ATTR\n#    ifdef ANJAY_WITH_LWM2M12\n                    ,\n                    .hqmax = ANJAY_ATTRIB_INTEGER_NONE\n#    endif // ANJAY_WITH_LWM2M12\n                },\n                .greater_than = ANJAY_ATTRIB_DOUBLE_NONE,\n                .less_than = ANJAY_ATTRIB_DOUBLE_NONE,\n                .step = ANJAY_ATTRIB_DOUBLE_NONE\n#    ifdef ANJAY_WITH_LWM2M12\n                ,\n                .edge = ANJAY_DM_EDGE_ATTR_NONE\n#    endif // ANJAY_WITH_LWM2M12\n            });\n    _anjay_mock_dm_expect_instance_read_default_attrs(\n            anjay, &OBJ, 69, 34, 0, &ANJAY_DM_OI_ATTRIBUTES_EMPTY);\n    _anjay_mock_dm_expect_object_read_default_attrs(\n            anjay, &OBJ, 34, 0, &ANJAY_DM_OI_ATTRIBUTES_EMPTY);\n\n    // Second call of dm_list_resource_instances - actually print riids\n    _anjay_mock_dm_expect_list_resource_instances(anjay, &OBJ, 69, 4, 0,\n                                                  resource_instances);\n    for (const anjay_riid_t *riid = resource_instances;\n         *riid != ANJAY_ID_INVALID;\n         riid++) {\n        _anjay_mock_dm_expect_resource_instance_read_attrs(\n                anjay, &OBJ, 69, 4, *riid, 34, 0,\n                &(const anjay_dm_r_attributes_t) {\n                    .common = {\n                        .min_period = ANJAY_ATTRIB_INTEGER_NONE,\n                        .max_period = *riid,\n                        .min_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n                        .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE\n#    ifdef ANJAY_WITH_CON_ATTR\n                        ,\n                        .con = ANJAY_DM_CON_ATTR_NONE\n#    endif // ANJAY_WITH_CON_ATTR\n#    ifdef ANJAY_WITH_LWM2M12\n                        ,\n                        .hqmax = ANJAY_ATTRIB_INTEGER_NONE\n#    endif // ANJAY_WITH_LWM2M12\n                    },\n                    .greater_than = ANJAY_ATTRIB_DOUBLE_NONE,\n                    .less_than = ANJAY_ATTRIB_DOUBLE_NONE,\n                    .step = ANJAY_ATTRIB_DOUBLE_NONE\n#    ifdef ANJAY_WITH_LWM2M12\n                    ,\n                    .edge = ANJAY_DM_EDGE_ATTR_NONE\n#    endif // ANJAY_WITH_LWM2M12\n                });\n    }\n\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, CONTENT, ID(0xfa3e),\n                            CONTENT_FORMAT(LINK_FORMAT),\n                            PAYLOAD(\"</42/69/4>;dim=3;pmin=10;epmax=5,\"\n                                    \"</42/69/4/123>;pmax=123,\"\n                                    \"</42/69/4/456>;pmax=456,\"\n                                    \"</42/69/4/789>;pmax=789\"));\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n#endif // ANJAY_WITH_LWM2M11\n\nAVS_UNIT_TEST(dm_discover, resource) {\n    DM_TEST_INIT_WITH_SSIDS(7);\n    DM_TEST_REQUEST(mocksocks[0], CON, GET, ID(0xFA3E), PATH(\"42\", \"69\", \"4\"),\n                    ACCEPT(0x28), NO_PAYLOAD);\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0,\n            (const anjay_iid_t[]) { 14, 42, 69, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 69, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                    { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read_attrs(\n            anjay, &OBJ, 69, 4, 7, 0,\n            &(const anjay_dm_r_attributes_t) {\n                .common = {\n                    .min_period = ANJAY_ATTRIB_INTEGER_NONE,\n                    .max_period = 514,\n                    .min_eval_period = 25,\n                    .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE\n#ifdef ANJAY_WITH_CON_ATTR\n                    ,\n                    .con = ANJAY_DM_CON_ATTR_NONE\n#endif // ANJAY_WITH_CON_ATTR\n#ifdef ANJAY_WITH_LWM2M12\n                    ,\n                    .hqmax = ANJAY_ATTRIB_INTEGER_NONE\n#endif // ANJAY_WITH_LWM2M12\n                },\n                .greater_than = ANJAY_ATTRIB_DOUBLE_NONE,\n                .less_than = 6.46,\n                .step = ANJAY_ATTRIB_DOUBLE_NONE\n#ifdef ANJAY_WITH_LWM2M12\n                ,\n                .edge = ANJAY_DM_EDGE_ATTR_NONE\n#endif // ANJAY_WITH_LWM2M12\n            });\n\n    _anjay_mock_dm_expect_instance_read_default_attrs(\n            anjay, &OBJ, 69, 7, 0,\n            &(const anjay_dm_oi_attributes_t) {\n                .min_period = ANJAY_ATTRIB_INTEGER_NONE,\n                .max_period = ANJAY_ATTRIB_INTEGER_NONE,\n                .min_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n                .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE\n#ifdef ANJAY_WITH_CON_ATTR\n                ,\n                .con = ANJAY_DM_CON_ATTR_NONE\n#endif // ANJAY_WITH_CON_ATTR\n#ifdef ANJAY_WITH_LWM2M12\n                ,\n                .hqmax = ANJAY_ATTRIB_INTEGER_NONE\n#endif // ANJAY_WITH_LWM2M12\n            });\n\n    _anjay_mock_dm_expect_object_read_default_attrs(\n            anjay, &OBJ, 7, 0,\n            &(const anjay_dm_oi_attributes_t) {\n                .min_period = 10,\n                .max_period = ANJAY_ATTRIB_INTEGER_NONE,\n                .min_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n                .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE\n#ifdef ANJAY_WITH_CON_ATTR\n                ,\n                .con = ANJAY_DM_CON_ATTR_NONE\n#endif // ANJAY_WITH_CON_ATTR\n#ifdef ANJAY_WITH_LWM2M12\n                ,\n                .hqmax = ANJAY_ATTRIB_INTEGER_NONE\n#endif // ANJAY_WITH_LWM2M12\n            });\n\n    DM_TEST_EXPECT_RESPONSE(\n            mocksocks[0], ACK, CONTENT, ID(0xfa3e), CONTENT_FORMAT(LINK_FORMAT),\n            PAYLOAD(\"</42/69/4>;pmin=10;pmax=514;epmin=25;lt=6.46\"));\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\n#ifdef ANJAY_WITH_LWM2M11\nAVS_UNIT_TEST(dm_discover, resource_multiple_servers) {\n    DM_TEST_INIT_WITH_SSIDS(34, 45);\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    anjay_unlocked->servers->registration_info.lwm2m_version =\n            ANJAY_LWM2M_VERSION_1_1;\n    ANJAY_MUTEX_UNLOCK(anjay);\n    DM_TEST_REQUEST(mocksocks[0], CON, GET, ID(0xFA3E), PATH(\"42\", \"69\", \"4\"),\n                    ACCEPT(0x28), NO_PAYLOAD);\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0,\n            (const anjay_iid_t[]) { 14, 42, 69, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 69, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_RWM, ANJAY_DM_RES_PRESENT },\n                    { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    const anjay_riid_t resource_instances[] = { 0, 1, 2, ANJAY_ID_INVALID };\n\n    // First call of dm_list_resource_instances - compute resource dim\n    _anjay_mock_dm_expect_list_resource_instances(anjay, &OBJ, 69, 4, 0,\n                                                  resource_instances);\n\n    _anjay_mock_dm_expect_resource_read_attrs(\n            anjay, &OBJ, 69, 4, 34, 0,\n            &(const anjay_dm_r_attributes_t) {\n                .common = {\n                    .min_period = 10,\n                    .max_period = 514,\n                    .min_eval_period = 3,\n                    .max_eval_period = 600\n#    ifdef ANJAY_WITH_CON_ATTR\n                    ,\n                    .con = ANJAY_DM_CON_ATTR_NONE\n#    endif // ANJAY_WITH_CON_ATTR\n#    ifdef ANJAY_WITH_LWM2M12\n                    ,\n                    .hqmax = 4\n#    endif // ANJAY_WITH_LWM2M12\n                },\n                .greater_than = ANJAY_ATTRIB_DOUBLE_NONE,\n                .less_than = 6.46,\n                .step = ANJAY_ATTRIB_DOUBLE_NONE\n#    ifdef ANJAY_WITH_LWM2M12\n                ,\n                .edge = ANJAY_DM_EDGE_ATTR_RISING\n#    endif // ANJAY_WITH_LWM2M12\n            });\n#    ifdef ANJAY_WITH_CON_ATTR\n    _anjay_mock_dm_expect_instance_read_default_attrs(\n            anjay, &OBJ, 69, 34, 0, &ANJAY_DM_OI_ATTRIBUTES_EMPTY);\n    _anjay_mock_dm_expect_object_read_default_attrs(\n            anjay, &OBJ, 34, 0, &ANJAY_DM_OI_ATTRIBUTES_EMPTY);\n#    endif // ANJAY_WITH_CON_ATTR\n\n    // Second call of dm_list_resource_instances - actually print riids\n    _anjay_mock_dm_expect_list_resource_instances(anjay, &OBJ, 69, 4, 0,\n                                                  resource_instances);\n    for (const anjay_riid_t *riid = resource_instances;\n         *riid != ANJAY_ID_INVALID;\n         riid++) {\n        _anjay_mock_dm_expect_resource_instance_read_attrs(\n                anjay, &OBJ, 69, 4, *riid, 34, 0, &ANJAY_DM_R_ATTRIBUTES_EMPTY);\n    }\n\n    DM_TEST_EXPECT_RESPONSE(\n            mocksocks[0], ACK, CONTENT, ID(0xfa3e), CONTENT_FORMAT(LINK_FORMAT),\n#    ifdef ANJAY_WITH_LWM2M12\n            PAYLOAD(\"</42/69/\"\n                    \"4>;dim=3;pmin=10;pmax=514;epmin=3;epmax=600;hqmax=4;\"\n                    \"lt=6.46;edge=1,</42/69/4/0>,</42/69/4/1>,</42/69/4/2>\"));\n#    else  // ANJAY_WITH_LWM2M12\n            PAYLOAD(\"</42/69/\"\n                    \"4>;dim=3;pmin=10;pmax=514;epmin=3;epmax=600;\"\n                    \"lt=6.46,</42/69/4/0>,</42/69/4/1>,</42/69/4/2>\"));\n#    endif // ANJAY_WITH_LWM2M12\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n#endif // ANJAY_WITH_LWM2M11\n\nAVS_UNIT_TEST(dm_discover, instance) {\n    DM_TEST_INIT_WITH_SSIDS(69);\n    DM_TEST_REQUEST(mocksocks[0], CON, GET, ID(0xFA3E), PATH(\"42\", \"514\"),\n                    ACCEPT(0x28), NO_PAYLOAD);\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0,\n            (const anjay_iid_t[]) { 14, 42, 69, 514, ANJAY_ID_INVALID });\n\n    _anjay_mock_dm_expect_instance_read_default_attrs(\n            anjay, &OBJ, 514, 69, 0,\n            &(const anjay_dm_oi_attributes_t) {\n                .min_period = ANJAY_ATTRIB_INTEGER_NONE,\n                .max_period = 777,\n                .min_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n                .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE\n#ifdef ANJAY_WITH_CON_ATTR\n                ,\n                .con = ANJAY_DM_CON_ATTR_NONE\n#endif // ANJAY_WITH_CON_ATTR\n#ifdef ANJAY_WITH_LWM2M12\n                ,\n                .hqmax = ANJAY_ATTRIB_INTEGER_NONE\n#endif // ANJAY_WITH_LWM2M12\n            });\n    _anjay_mock_dm_expect_object_read_default_attrs(\n            anjay, &OBJ, 69, 0,\n            &(const anjay_dm_oi_attributes_t) {\n                .min_period = 666,\n                .max_period = ANJAY_ATTRIB_INTEGER_NONE,\n                .min_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n                .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE\n#ifdef ANJAY_WITH_CON_ATTR\n                ,\n                .con = ANJAY_DM_CON_ATTR_NONE\n#endif // ANJAY_WITH_CON_ATTR\n#ifdef ANJAY_WITH_LWM2M12\n                ,\n                .hqmax = ANJAY_ATTRIB_INTEGER_NONE\n#endif // ANJAY_WITH_LWM2M12\n            });\n\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 514, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                    { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                    { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    for (anjay_iid_t rid = 0; rid < 2; ++rid) {\n        anjay_dm_r_attributes_t attrs = ANJAY_DM_R_ATTRIBUTES_EMPTY;\n        attrs.greater_than = (double) rid;\n        _anjay_mock_dm_expect_resource_read_attrs(anjay, &OBJ, 514, rid, 69, 0,\n                                                  &attrs);\n    }\n\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, CONTENT, ID(0xfa3e),\n                            CONTENT_FORMAT(LINK_FORMAT),\n                            PAYLOAD(\"</42/514>;pmin=666;pmax=777,\"\n                                    \"</42/514/0>;gt=0,</42/514/1>;gt=1\"));\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_discover, instance_multiple_servers) {\n    DM_TEST_INIT_WITH_SSIDS(69, 96);\n    DM_TEST_REQUEST(mocksocks[0], CON, GET, ID(0xFA3E), PATH(\"42\", \"514\"),\n                    ACCEPT(0x28), NO_PAYLOAD);\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0,\n            (const anjay_iid_t[]) { 14, 42, 69, 514, ANJAY_ID_INVALID });\n\n    _anjay_mock_dm_expect_instance_read_default_attrs(\n            anjay, &OBJ, 514, 69, 0,\n            &(const anjay_dm_oi_attributes_t) {\n                .min_period = ANJAY_ATTRIB_INTEGER_NONE,\n                .max_period = 777,\n                .min_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n                .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE\n#ifdef ANJAY_WITH_CON_ATTR\n                ,\n                .con = ANJAY_DM_CON_ATTR_NONE\n#endif // ANJAY_WITH_CON_ATTR\n#ifdef ANJAY_WITH_LWM2M12\n                ,\n                .hqmax = ANJAY_ATTRIB_INTEGER_NONE\n#endif // ANJAY_WITH_LWM2M12\n            });\n    _anjay_mock_dm_expect_object_read_default_attrs(\n            anjay, &OBJ, 69, 0,\n            &(const anjay_dm_oi_attributes_t) {\n                .min_period = 666,\n                .max_period = ANJAY_ATTRIB_INTEGER_NONE,\n                .min_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n                .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE\n#ifdef ANJAY_WITH_CON_ATTR\n                ,\n                .con = ANJAY_DM_CON_ATTR_NONE\n#endif // ANJAY_WITH_CON_ATTR\n#ifdef ANJAY_WITH_LWM2M12\n                ,\n                .hqmax = ANJAY_ATTRIB_INTEGER_NONE\n#endif // ANJAY_WITH_LWM2M12\n            });\n\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 514, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                    { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                    { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    for (anjay_iid_t rid = 0; rid < 2; ++rid) {\n        anjay_dm_r_attributes_t attrs = ANJAY_DM_R_ATTRIBUTES_EMPTY;\n        attrs.greater_than = (double) rid;\n        _anjay_mock_dm_expect_resource_read_attrs(anjay, &OBJ, 514, rid, 69, 0,\n                                                  &attrs);\n    }\n\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, CONTENT, ID(0xfa3e),\n                            CONTENT_FORMAT(LINK_FORMAT),\n                            PAYLOAD(\"</42/514>;pmin=666;pmax=777,\"\n                                    \"</42/514/0>;gt=0,</42/514/1>;gt=1\"));\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nstatic void test_discover_object(anjay_t *anjay, avs_net_socket_t *mocksock) {\n    _anjay_mock_dm_expect_object_read_default_attrs(\n            anjay, &OBJ, 2, 0,\n            &(const anjay_dm_oi_attributes_t) {\n                .min_period = ANJAY_ATTRIB_INTEGER_NONE,\n                .max_period = 514,\n                .min_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n                .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE\n#ifdef ANJAY_WITH_CON_ATTR\n                ,\n                .con = ANJAY_DM_CON_ATTR_NONE\n#endif // ANJAY_WITH_CON_ATTR\n#ifdef ANJAY_WITH_LWM2M12\n                ,\n                .hqmax = ANJAY_ATTRIB_INTEGER_NONE\n#endif // ANJAY_WITH_LWM2M12\n            });\n\n    const anjay_mock_dm_res_entry_t *resources[] = {\n        (const anjay_mock_dm_res_entry_t[]) {\n                { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                ANJAY_MOCK_DM_RES_END },\n        (const anjay_mock_dm_res_entry_t[]) {\n                { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                ANJAY_MOCK_DM_RES_END }\n    };\n    const size_t ITERATIONS = AVS_ARRAY_SIZE(resources);\n    anjay_iid_t iids[ITERATIONS + 1];\n    for (anjay_iid_t iid = 0; iid < ITERATIONS; ++iid) {\n        iids[iid] = iid;\n    }\n    iids[ITERATIONS] = ANJAY_ID_INVALID;\n    _anjay_mock_dm_expect_list_instances(anjay, &OBJ, 0, iids);\n    for (anjay_iid_t iid = 0; iid < ITERATIONS; ++iid) {\n        _anjay_mock_dm_expect_list_resources(anjay, &OBJ, iid, 0,\n                                             resources[iid]);\n    }\n\n    DM_TEST_EXPECT_RESPONSE(mocksock, ACK, CONTENT, ID(0xfa3e),\n                            CONTENT_FORMAT(LINK_FORMAT),\n                            PAYLOAD(\"</42>;pmax=514,</42/0>,</42/0/0>,\"\n                                    \"</42/0/3>,</42/0/4>,</42/0/6>,</42/1>,\"\n                                    \"</42/1/4>,</42/1/5>,</42/1/6>\"));\n    expect_has_buffered_data_check(mocksock, false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksock));\n}\n\nAVS_UNIT_TEST(dm_discover, object) {\n    DM_TEST_INIT_WITH_SSIDS(2);\n    DM_TEST_REQUEST(mocksocks[0], CON, GET, ID(0xFA3E), PATH(\"42\"),\n                    ACCEPT(0x28), NO_PAYLOAD);\n    test_discover_object(anjay, mocksocks[0]);\n    DM_TEST_FINISH;\n}\n\n#ifdef ANJAY_WITH_LWM2M12\nAVS_UNIT_TEST(dm_discover, object_with_depth_ignored) {\n    DM_TEST_INIT_WITH_SSIDS(2);\n    DM_TEST_REQUEST(mocksocks[0], CON, GET, ID(0xFA3E), PATH(\"42\"),\n                    QUERY(\"depth=1\"), ACCEPT(0x28), NO_PAYLOAD);\n    // This is LwM2M 1.0, so depth is ignored\n    test_discover_object(anjay, mocksocks[0]);\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_discover, object_with_depth_ignored_lwm2m11) {\n    DM_TEST_INIT_WITH_SSIDS(2);\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    anjay_unlocked->servers->registration_info.lwm2m_version =\n            ANJAY_LWM2M_VERSION_1_1;\n    ANJAY_MUTEX_UNLOCK(anjay);\n    DM_TEST_REQUEST(mocksocks[0], CON, GET, ID(0xFA3E), PATH(\"42\"),\n                    QUERY(\"depth=1\"), ACCEPT(0x28), NO_PAYLOAD);\n    // This is LwM2M 1.1, so depth is ignored\n    test_discover_object(anjay, mocksocks[0]);\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_discover, object_lwm2m12) {\n    DM_TEST_INIT_WITH_SSIDS(2);\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    anjay_unlocked->servers->registration_info.lwm2m_version =\n            ANJAY_LWM2M_VERSION_1_2;\n    ANJAY_MUTEX_UNLOCK(anjay);\n    DM_TEST_REQUEST(mocksocks[0], CON, GET, ID(0xFA3E), PATH(\"42\"),\n                    ACCEPT(0x28), NO_PAYLOAD);\n    _anjay_mock_dm_expect_object_read_default_attrs(\n            anjay, &OBJ, 2, 0,\n            &(const anjay_dm_oi_attributes_t) {\n                .min_period = ANJAY_ATTRIB_INTEGER_NONE,\n                .max_period = 514,\n                .min_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n                .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE\n#    ifdef ANJAY_WITH_CON_ATTR\n                ,\n                .con = ANJAY_DM_CON_ATTR_NONE\n#    endif // ANJAY_WITH_CON_ATTR\n                ,\n                .hqmax = ANJAY_ATTRIB_INTEGER_NONE\n            });\n\n    const anjay_mock_dm_res_entry_t *resources[] = {\n        (const anjay_mock_dm_res_entry_t[]) {\n                { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                ANJAY_MOCK_DM_RES_END },\n        (const anjay_mock_dm_res_entry_t[]) {\n                { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                ANJAY_MOCK_DM_RES_END }\n    };\n    const size_t ITERATIONS = AVS_ARRAY_SIZE(resources);\n    anjay_iid_t iids[ITERATIONS + 1];\n    for (anjay_iid_t iid = 0; iid < ITERATIONS; ++iid) {\n        iids[iid] = iid;\n    }\n    iids[ITERATIONS] = ANJAY_ID_INVALID;\n    _anjay_mock_dm_expect_list_instances(anjay, &OBJ, 0, iids);\n    for (anjay_iid_t iid = 0; iid < ITERATIONS; ++iid) {\n        anjay_dm_oi_attributes_t instance_attrs = ANJAY_DM_OI_ATTRIBUTES_EMPTY;\n        instance_attrs.min_period = iid;\n        _anjay_mock_dm_expect_instance_read_default_attrs(anjay, &OBJ, iid, 2,\n                                                          0, &instance_attrs);\n        _anjay_mock_dm_expect_list_resources(anjay, &OBJ, iid, 0,\n                                             resources[iid]);\n        for (const anjay_mock_dm_res_entry_t *resource_entry = resources[iid];\n             resource_entry->rid != ANJAY_ID_INVALID;\n             ++resource_entry) {\n            if (resource_entry->presence == ANJAY_DM_RES_ABSENT) {\n                continue;\n            }\n            anjay_dm_r_attributes_t resource_attrs =\n                    ANJAY_DM_R_ATTRIBUTES_EMPTY;\n            resource_attrs.greater_than = (double) resource_entry->rid;\n            _anjay_mock_dm_expect_resource_read_attrs(anjay, &OBJ, iid,\n                                                      resource_entry->rid, 2, 0,\n                                                      &resource_attrs);\n        }\n    }\n\n    DM_TEST_EXPECT_RESPONSE(\n            mocksocks[0], ACK, CONTENT, ID(0xfa3e), CONTENT_FORMAT(LINK_FORMAT),\n            PAYLOAD(\"</42>;pmax=514,</42/0>;pmin=0,</42/0/0>;gt=0,</42/0/3>;\"\n                    \"gt=3,</42/0/4>;gt=4,</42/0/6>;gt=6,</42/1>;pmin=1,\"\n                    \"</42/1/4>;gt=4,</42/1/5>;gt=5,</42/1/6>;gt=6\"));\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n#endif // ANJAY_WITH_LWM2M12\n\nAVS_UNIT_TEST(dm_discover, object_multiple_servers) {\n    DM_TEST_INIT_WITH_SSIDS(2, 3);\n    DM_TEST_REQUEST(mocksocks[0], CON, GET, ID(0xFA3E), PATH(\"42\"),\n                    ACCEPT(0x28), NO_PAYLOAD);\n    _anjay_mock_dm_expect_object_read_default_attrs(\n            anjay, &OBJ, 2, 0,\n            &(const anjay_dm_oi_attributes_t) {\n                .min_period = ANJAY_ATTRIB_INTEGER_NONE,\n                .max_period = 514,\n                .min_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n                .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE\n#ifdef ANJAY_WITH_CON_ATTR\n                ,\n                .con = ANJAY_DM_CON_ATTR_NONE\n#endif // ANJAY_WITH_CON_ATTR\n#ifdef ANJAY_WITH_LWM2M12\n                ,\n                .hqmax = ANJAY_ATTRIB_INTEGER_NONE\n#endif // ANJAY_WITH_LWM2M12\n            });\n\n    const anjay_mock_dm_res_entry_t *resources[] = {\n        (const anjay_mock_dm_res_entry_t[]) {\n                { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                ANJAY_MOCK_DM_RES_END },\n        (const anjay_mock_dm_res_entry_t[]) {\n                { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                ANJAY_MOCK_DM_RES_END }\n    };\n    const size_t ITERATIONS = AVS_ARRAY_SIZE(resources);\n    anjay_iid_t iids[ITERATIONS + 1];\n    for (anjay_iid_t iid = 0; iid < ITERATIONS; ++iid) {\n        iids[iid] = iid;\n    }\n    iids[ITERATIONS] = ANJAY_ID_INVALID;\n    _anjay_mock_dm_expect_list_instances(anjay, &OBJ, 0, iids);\n    for (anjay_iid_t iid = 0; iid < ITERATIONS; ++iid) {\n        _anjay_mock_dm_expect_list_resources(anjay, &OBJ, iid, 0,\n                                             resources[iid]);\n    }\n\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, CONTENT, ID(0xfa3e),\n                            CONTENT_FORMAT(LINK_FORMAT),\n                            PAYLOAD(\"</42>;pmax=514,</42/0>,</42/0/0>,\"\n                                    \"</42/0/3>,</42/0/4>,</42/0/6>,</42/1>,\"\n                                    \"</42/1/4>,</42/1/5>,</42/1/6>\"));\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_discover, error) {\n    DM_TEST_INIT_WITH_SSIDS(7);\n    DM_TEST_REQUEST(mocksocks[0], CON, GET, ID(0xFA3E), PATH(\"42\", \"69\", \"4\"),\n                    ACCEPT(0x28), NO_PAYLOAD);\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0,\n            (const anjay_iid_t[]) { 14, 42, 69, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 69, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                    { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read_attrs(anjay, &OBJ, 69, 4, 7,\n                                              ANJAY_ERR_INTERNAL,\n                                              &ANJAY_DM_R_ATTRIBUTES_EMPTY);\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, INTERNAL_SERVER_ERROR,\n                            ID(0xfa3e), NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_FAILED(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_discover, multiple_servers_empty) {\n    DM_TEST_INIT_WITH_SSIDS(34, 45);\n    DM_TEST_REQUEST(mocksocks[0], CON, GET, ID(0xFA3E), PATH(\"42\", \"69\", \"4\"),\n                    ACCEPT(0x28), NO_PAYLOAD);\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0,\n            (const anjay_iid_t[]) { 14, 42, 69, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 69, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                    { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read_attrs(anjay, &OBJ, 69, 4, 34, 0,\n                                              &ANJAY_DM_R_ATTRIBUTES_EMPTY);\n    _anjay_mock_dm_expect_instance_read_default_attrs(\n            anjay, &OBJ, 69, 34, 0, &ANJAY_DM_OI_ATTRIBUTES_EMPTY);\n    _anjay_mock_dm_expect_object_read_default_attrs(\n            anjay, &OBJ, 34, 0, &ANJAY_DM_OI_ATTRIBUTES_EMPTY);\n\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, CONTENT, ID(0xfa3e),\n                            CONTENT_FORMAT(LINK_FORMAT), PAYLOAD(\"</42/69/4>\"));\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_create, only_iid) {\n    DM_TEST_INIT;\n    DM_TEST_REQUEST(mocksocks[0], CON, POST, ID(0xFA3E), PATH(\"42\"),\n                    CONTENT_FORMAT(OMA_LWM2M_TLV),\n                    PAYLOAD(\"\\x20\"\n                            \"\\x02\\x02\"));\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0,\n            (const anjay_iid_t[]) { 14, 42, 69, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_instance_create(anjay, &OBJ, 514, 0);\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, CREATED, ID(0xFA3E),\n                            LOCATION_PATH(\"42\", \"514\"));\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_create, failure) {\n    DM_TEST_INIT;\n    DM_TEST_REQUEST(mocksocks[0], CON, POST, ID(0xFA3E), PATH(\"42\"),\n                    CONTENT_FORMAT(OMA_LWM2M_TLV),\n                    PAYLOAD(\"\\x20\"\n                            \"\\x02\\x02\"));\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0,\n            (const anjay_iid_t[]) { 14, 42, 69, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_instance_create(anjay, &OBJ, 514, -1);\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, INTERNAL_SERVER_ERROR,\n                            ID(0xfa3e), NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_FAILED(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_create, already_exists) {\n    DM_TEST_INIT;\n    DM_TEST_REQUEST(mocksocks[0], CON, POST, ID(0xFA3E), PATH(\"42\"),\n                    CONTENT_FORMAT(OMA_LWM2M_TLV),\n                    PAYLOAD(\"\\x00\"\n                            \"\\x45\"));\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0,\n            (const anjay_iid_t[]) { 14, 42, 69, ANJAY_ID_INVALID });\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, BAD_REQUEST, ID(0xfa3e),\n                            NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_create, no_iid) {\n    DM_TEST_INIT;\n    DM_TEST_REQUEST(mocksocks[0], CON, POST, ID(0xFA3E), PATH(\"42\"),\n                    CONTENT_FORMAT(OMA_LWM2M_TLV), NO_PAYLOAD);\n\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0,\n            (const anjay_iid_t[]) { 0, 1, 2, 3, 4, 5, 7, 8, 9,\n                                    ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_instance_create(anjay, &OBJ, 6, 0);\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, CREATED, ID(0xFA3E),\n                            LOCATION_PATH(\"42\", \"6\"));\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_create, with_data) {\n    DM_TEST_INIT;\n    DM_TEST_REQUEST(mocksocks[0], CON, POST, ID(0xFA3E), PATH(\"42\"),\n                    CONTENT_FORMAT(OMA_LWM2M_TLV),\n                    PAYLOAD(\"\\xc1\\x00\\x0d\"\n                            \"\\xc5\\x06\"\n                            \"Hello\"));\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0,\n            (const anjay_iid_t[]) { 0, 1, 3, 4, 5, 6, 7, 8, 9,\n                                    ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_instance_create(anjay, &OBJ, 2, 0);\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 2, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_write(anjay, &OBJ, 2, 0, ANJAY_ID_INVALID,\n                                         ANJAY_MOCK_DM_INT(0, 13), 0);\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 2, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                    { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_write(anjay, &OBJ, 2, 6, ANJAY_ID_INVALID,\n                                         ANJAY_MOCK_DM_STRING(0, \"Hello\"), 0);\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, CREATED, ID(0xFA3E),\n                            LOCATION_PATH(\"42\", \"2\"));\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_create, with_iid_and_data) {\n    DM_TEST_INIT;\n    DM_TEST_REQUEST(mocksocks[0], CON, POST, ID(0xFA3E), PATH(\"42\"),\n                    CONTENT_FORMAT(OMA_LWM2M_TLV),\n                    PAYLOAD(\"\\x08\\x45\\x0a\"\n                            \"\\xc1\\x00\\x0d\"\n                            \"\\xc5\\x06\"\n                            \"Hello\"));\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0, (const anjay_iid_t[]) { 4, 14, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_instance_create(anjay, &OBJ, 69, 0);\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 69, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_write(anjay, &OBJ, 69, 0, ANJAY_ID_INVALID,\n                                         ANJAY_MOCK_DM_INT(0, 13), 0);\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 69, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                    { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_write(anjay, &OBJ, 69, 6, ANJAY_ID_INVALID,\n                                         ANJAY_MOCK_DM_STRING(0, \"Hello\"), 0);\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, CREATED, ID(0xFA3E),\n                            LOCATION_PATH(\"42\", \"69\"));\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_create, multiple_iids) {\n    DM_TEST_INIT;\n    DM_TEST_REQUEST(mocksocks[0], CON, POST, ID(0xFA3E), PATH(\"42\"),\n                    CONTENT_FORMAT(OMA_LWM2M_TLV),\n                    PAYLOAD(\"\\x08\\x45\\x03\"\n                            \"\\xc1\\x00\\x2a\"\n                            \"\\x08\\x2a\\x03\"\n                            \"\\xc1\\x03\\x45\"));\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0, (const anjay_iid_t[]) { 4, 14, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_instance_create(anjay, &OBJ, 69, 0);\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 69, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_write(anjay, &OBJ, 69, 0, ANJAY_ID_INVALID,\n                                         ANJAY_MOCK_DM_INT(0, 42), 0);\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, BAD_REQUEST, ID(0xfa3e),\n                            NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_create, multiple_iids_after_multiple_resource) {\n    DM_TEST_INIT;\n    DM_TEST_REQUEST(mocksocks[0], CON, POST, ID(0xFA3E), PATH(\"42\"),\n                    CONTENT_FORMAT(OMA_LWM2M_TLV),\n                    PAYLOAD(\"\\x08\\x45\\x11\"\n                            \"\\x88\\x15\\x0e\"\n                            \"\\x45\\x03\"\n                            \"Hello\"\n                            \"\\x45\\x07\"\n                            \"world\"\n                            \"\\x27\\x01\\xa4\"\n                            \"\\xe4\\x08\\x59\"\n                            \"test\"));\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0, (const anjay_iid_t[]) { 4, 14, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_instance_create(anjay, &OBJ, 69, 0);\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 69, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 21, ANJAY_DM_RES_RWM, ANJAY_DM_RES_ABSENT },\n                    { 37, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 420, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_write(anjay, &OBJ, 69, 21, 3,\n                                         ANJAY_MOCK_DM_STRING(0, \"Hello\"), 0);\n    _anjay_mock_dm_expect_resource_write(anjay, &OBJ, 69, 21, 7,\n                                         ANJAY_MOCK_DM_STRING(0, \"world\"), 0);\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, BAD_REQUEST, ID(0xfa3e),\n                            NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_delete, success) {\n    DM_TEST_INIT;\n    DM_TEST_REQUEST(mocksocks[0], CON, DELETE, ID(0xFA3E), PATH(\"42\", \"34\"));\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0, (const anjay_iid_t[]) { 34, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_instance_remove(anjay, &OBJ, 34, 0);\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, DELETED, ID(0xfa3e), NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_delete, no_iid) {\n    DM_TEST_INIT;\n    DM_TEST_REQUEST(mocksocks[0], CON, DELETE, ID(0xFA3E), PATH(\"42\"));\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, METHOD_NOT_ALLOWED, ID(0xfa3e),\n                            NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_delete, superfluous_rid) {\n    DM_TEST_INIT;\n    DM_TEST_REQUEST(mocksocks[0], CON, DELETE, ID(0xFA3E),\n                    PATH(\"42\", \"514\", \"2\"));\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, METHOD_NOT_ALLOWED, ID(0xfa3e),\n                            NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_delete, not_exists) {\n    DM_TEST_INIT;\n    DM_TEST_REQUEST(mocksocks[0], CON, DELETE, ID(0xFA3E), PATH(\"42\", \"69\"));\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0, (const anjay_iid_t[]) { 34, ANJAY_ID_INVALID });\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, NOT_FOUND, ID(0xfa3e),\n                            NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_delete, failure) {\n    DM_TEST_INIT;\n    DM_TEST_REQUEST(mocksocks[0], CON, DELETE, ID(0xFA3E), PATH(\"42\", \"84\"));\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0, (const anjay_iid_t[]) { 84, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_instance_remove(anjay, &OBJ, 84, ANJAY_ERR_INTERNAL);\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, INTERNAL_SERVER_ERROR,\n                            ID(0xfa3e), NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_FAILED(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\n#ifdef ANJAY_WITH_LWM2M12\nAVS_UNIT_TEST(dm_delete, resource_instance_success) {\n    DM_TEST_INIT;\n    DM_TEST_REQUEST(mocksocks[0], CON, DELETE, ID(0xFA3E),\n                    PATH(\"42\", \"21\", \"69\", \"37\"));\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0, (const anjay_iid_t[]) { 21, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 21, 0,\n            (const anjay_mock_dm_res_entry_t[]) { { 69, ANJAY_DM_RES_WM,\n                                                    ANJAY_DM_RES_PRESENT },\n                                                  ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_list_resource_instances(\n            anjay, &OBJ, 21, 69, 0,\n            (const anjay_riid_t[]) { 37, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_resource_instance_remove(anjay, &OBJ, 21, 69, 37, 0);\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, DELETED, ID(0xfa3e), NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_delete, resource_instance_not_exists) {\n    DM_TEST_INIT;\n    DM_TEST_REQUEST(mocksocks[0], CON, DELETE, ID(0xFA3E),\n                    PATH(\"42\", \"21\", \"69\", \"37\"));\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0, (const anjay_iid_t[]) { 21, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 21, 0,\n            (const anjay_mock_dm_res_entry_t[]) { { 69, ANJAY_DM_RES_WM,\n                                                    ANJAY_DM_RES_PRESENT },\n                                                  ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_list_resource_instances(anjay, &OBJ, 21, 69, 0,\n                                                  (const anjay_riid_t[]) {\n                                                          ANJAY_ID_INVALID });\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, NOT_FOUND, ID(0xfa3e),\n                            NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_delete, resource_instance_not_writable) {\n    DM_TEST_INIT;\n    DM_TEST_REQUEST(mocksocks[0], CON, DELETE, ID(0xFA3E),\n                    PATH(\"42\", \"21\", \"69\", \"37\"));\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0, (const anjay_iid_t[]) { 21, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 21, 0,\n            (const anjay_mock_dm_res_entry_t[]) { { 69, ANJAY_DM_RES_RM,\n                                                    ANJAY_DM_RES_PRESENT },\n                                                  ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_list_resource_instances(\n            anjay, &OBJ, 21, 69, 0,\n            (const anjay_riid_t[]) { 37, ANJAY_ID_INVALID });\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, METHOD_NOT_ALLOWED, ID(0xfa3e),\n                            NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_delete, resource_instance_failure) {\n    DM_TEST_INIT;\n    DM_TEST_REQUEST(mocksocks[0], CON, DELETE, ID(0xFA3E),\n                    PATH(\"42\", \"21\", \"69\", \"37\"));\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0, (const anjay_iid_t[]) { 21, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 21, 0,\n            (const anjay_mock_dm_res_entry_t[]) { { 69, ANJAY_DM_RES_WM,\n                                                    ANJAY_DM_RES_PRESENT },\n                                                  ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_list_resource_instances(\n            anjay, &OBJ, 21, 69, 0,\n            (const anjay_riid_t[]) { 37, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_resource_instance_remove(anjay, &OBJ, 21, 69, 37,\n                                                   ANJAY_ERR_INTERNAL);\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, INTERNAL_SERVER_ERROR,\n                            ID(0xfa3e), NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_FAILED(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n#endif // ANJAY_WITH_LWM2M12\n\n#warning \\\n        \"TODO: requires proper setup of server connection along with CoAP2 streaming context\"\n#if 0\nstatic int setup_response_succeed(avs_stream_t *stream,\n                                  const anjay_msg_details_t *details) {\n    (void) stream;\n    (void) details;\n    return 0;\n}\n\nstatic int mock_get_path(anjay_input_ctx_t *in_ctx,\n                         anjay_uri_path_t *out_path,\n                         bool *out_is_array) {\n    (void) in_ctx;\n    *out_path = MAKE_RESOURCE_PATH(ANJAY_ID_INVALID, ANJAY_ID_INVALID, 0);\n    *out_is_array = false;\n    return 0;\n}\n\nstatic int fail() {\n    AVS_UNIT_ASSERT_TRUE(false);\n    return -1;\n}\n\ntypedef struct coap_stream_mock {\n    const avs_stream_v_table_t *vtable;\n} coap_stream_mock_t;\n\nAVS_UNIT_TEST(dm_operations, unimplemented) {\n    anjay_t anjay;\n    memset(&anjay, 0, sizeof(anjay));\n\n    coap_stream_mock_t mock = {\n        .vtable = &(const avs_stream_v_table_t) {\n            .write_some = (avs_stream_write_some_t) fail,\n            .finish_message = (avs_stream_finish_message_t) fail,\n            .read = (avs_stream_read_t) fail,\n            .peek = (avs_stream_peek_t) fail,\n            .reset = (avs_stream_reset_t) fail,\n            .close = (avs_stream_close_t) fail,\n            .get_errno = (avs_stream_errno_t) fail\n        }\n    };\n\n    const anjay_dm_object_def_t OBJ_DEF = {\n        .oid = 1337\n    };\n    const anjay_dm_object_def_t *const def_ptr = &OBJ_DEF;\n\n    anjay_input_ctx_vtable_t in_ctx_vtable = {\n        .some_bytes = (anjay_input_ctx_bytes_t) fail,\n        .string = (anjay_input_ctx_string_t) fail,\n        .integer = (anjay_input_ctx_integer_t) fail,\n        .floating = (anjay_input_ctx_floating_t) fail,\n        .boolean = (anjay_input_ctx_boolean_t) fail,\n        .objlnk = (anjay_input_ctx_objlnk_t) fail,\n        .get_path = (anjay_input_ctx_get_path_t) fail,\n        .close = (anjay_input_ctx_close_t) fail\n    };\n    struct {\n        const anjay_input_ctx_vtable_t *vtable;\n    } in_ctx = {\n        .vtable = &in_ctx_vtable\n    };\n\n#    define ASSERT_ACTION_FAILS(...)                                         \\\n        {                                                                    \\\n            avs_coap_msg_identity_t request_identity = { 0 };                \\\n            anjay_request_t request = {                                      \\\n                .requested_format = AVS_COAP_FORMAT_NONE,                    \\\n                .action = __VA_ARGS__                                        \\\n            };                                                               \\\n            AVS_UNIT_ASSERT_FAILED(                                          \\\n                    invoke_action(&anjay, &def_ptr, &request_identity,       \\\n                                  &request, (anjay_input_ctx_t *) &in_ctx)); \\\n        }\n\n    anjay.comm_stream = (avs_stream_t *) &mock;\n    AVS_UNIT_MOCK(_anjay_coap_stream_setup_response) = setup_response_succeed;\n    anjay.current_connection.server = &(anjay_server_info_t) {\n        .ssid = 0\n    };\n    anjay_uri_path_t uri_object = MAKE_OBJECT_PATH(1337);\n    anjay_uri_path_t uri_instance = MAKE_INSTANCE_PATH(1337, 0);\n    anjay_uri_path_t uri_resource = MAKE_RESOURCE_PATH(1337, 0, 0);\n    ASSERT_ACTION_FAILS(ANJAY_ACTION_READ,\n                        .uri = uri_resource)\n    ASSERT_ACTION_FAILS(ANJAY_ACTION_DISCOVER,\n                        .uri = uri_resource)\n    ASSERT_ACTION_FAILS(ANJAY_ACTION_WRITE,\n                        .uri = uri_resource)\n    ASSERT_ACTION_FAILS(ANJAY_ACTION_WRITE_UPDATE,\n                        .uri = uri_resource)\n    ASSERT_ACTION_FAILS(ANJAY_ACTION_WRITE_ATTRIBUTES,\n                        .uri = uri_resource,\n                        .attributes = {\n                            .has_min_period = true\n                        })\n    ASSERT_ACTION_FAILS(ANJAY_ACTION_EXECUTE,\n                        .uri = uri_resource)\n    ASSERT_ACTION_FAILS(ANJAY_ACTION_DELETE,\n                        .uri = uri_instance)\n    in_ctx_vtable.get_path = mock_get_path;\n    ASSERT_ACTION_FAILS(ANJAY_ACTION_CREATE,\n                        .uri = uri_object)\n\n    // Cancel Observe does not call any handlers, so it does not fail\n}\n#endif\n\nstatic const anjay_dm_attrs_query_details_t\n        DM_EFFECTIVE_ATTRS_STANDARD_QUERY = {\n            .obj = &(const anjay_dm_installed_object_t) {\n#ifdef ANJAY_WITH_THREAD_SAFETY\n                .type = ANJAY_DM_OBJECT_USER_PROVIDED,\n                .impl.user_provided =\n#endif // ANJAY_WITH_THREAD_SAFETY\n                        &OBJ\n            },\n            .iid = 69,\n            .rid = 4,\n            .riid = ANJAY_ID_INVALID,\n            .ssid = 1,\n            .with_server_level_attrs = true\n        };\n\nAVS_UNIT_TEST(dm_effective_attrs, resource_full) {\n    DM_TEST_INIT;\n    (void) mocksocks;\n    static const anjay_dm_r_attributes_t RES_ATTRS = {\n        .common = {\n            .min_period = 14,\n            .max_period = 42,\n            .min_eval_period = 99,\n            .max_eval_period = 150\n        },\n        .greater_than = 77.2,\n        .less_than = ANJAY_ATTRIB_DOUBLE_NONE,\n        .step = ANJAY_ATTRIB_DOUBLE_NONE\n    };\n    _anjay_mock_dm_expect_resource_read_attrs(anjay, &OBJ, 69, 4, 1, 0,\n                                              &RES_ATTRS);\n\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    anjay_dm_r_attributes_t attrs;\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_dm_effective_attrs(\n            anjay_unlocked, &DM_EFFECTIVE_ATTRS_STANDARD_QUERY, &attrs));\n    _anjay_mock_dm_assert_attributes_equal(&attrs, &RES_ATTRS);\n    ANJAY_MUTEX_UNLOCK(anjay);\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_effective_attrs, fallback_to_instance) {\n    DM_TEST_INIT;\n    (void) mocksocks;\n    _anjay_mock_dm_expect_resource_read_attrs(\n            anjay, &OBJ, 69, 4, 1, 0,\n            &(const anjay_dm_r_attributes_t) {\n                .common = {\n                    .min_period = 14,\n                    .max_period = ANJAY_ATTRIB_INTEGER_NONE,\n                    .min_eval_period = 15,\n                    .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE\n                },\n                .greater_than = ANJAY_ATTRIB_DOUBLE_NONE,\n                .less_than = ANJAY_ATTRIB_DOUBLE_NONE,\n                .step = ANJAY_ATTRIB_DOUBLE_NONE\n            });\n    _anjay_mock_dm_expect_instance_read_default_attrs(\n            anjay, &OBJ, 69, 1, 0,\n            &(const anjay_dm_oi_attributes_t) {\n                .min_period = 514,\n                .max_period = 42,\n                .min_eval_period = 99,\n                .max_eval_period = 190\n            });\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    anjay_dm_r_attributes_t attrs;\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_dm_effective_attrs(\n            anjay_unlocked, &DM_EFFECTIVE_ATTRS_STANDARD_QUERY, &attrs));\n    _anjay_mock_dm_assert_attributes_equal(\n            &attrs,\n            &(const anjay_dm_r_attributes_t) {\n                .common = {\n                    .min_period = 14,\n                    .max_period = 42,\n                    .min_eval_period = 15,\n                    .max_eval_period = 190\n                },\n                .greater_than = ANJAY_ATTRIB_DOUBLE_NONE,\n                .less_than = ANJAY_ATTRIB_DOUBLE_NONE,\n                .step = ANJAY_ATTRIB_DOUBLE_NONE\n            });\n    ANJAY_MUTEX_UNLOCK(anjay);\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_effective_attrs, fallback_to_object) {\n    DM_TEST_INIT;\n    (void) mocksocks;\n    _anjay_mock_dm_expect_resource_read_attrs(\n            anjay, &OBJ, 69, 4, 1, 0,\n            &(const anjay_dm_r_attributes_t) {\n                .common = ANJAY_DM_OI_ATTRIBUTES_EMPTY,\n                .greater_than = 43.7,\n                .less_than = 17.3,\n                .step = 6.9\n            });\n    _anjay_mock_dm_expect_instance_read_default_attrs(\n            anjay, &OBJ, 69, 1, 0,\n            &(const anjay_dm_oi_attributes_t) {\n                .min_period = ANJAY_ATTRIB_INTEGER_NONE,\n                .max_period = 777,\n                .min_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n                .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE\n            });\n    _anjay_mock_dm_expect_object_read_default_attrs(\n            anjay, &OBJ, 1, 0,\n            &(const anjay_dm_oi_attributes_t) {\n                .min_period = 514,\n                .max_period = 69,\n                .min_eval_period = 100,\n                .max_eval_period = 800\n            });\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    anjay_dm_r_attributes_t attrs;\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_dm_effective_attrs(\n            anjay_unlocked, &DM_EFFECTIVE_ATTRS_STANDARD_QUERY, &attrs));\n    _anjay_mock_dm_assert_attributes_equal(&attrs,\n                                           &(const anjay_dm_r_attributes_t) {\n                                               .common = {\n                                                   .min_period = 514,\n                                                   .max_period = 777,\n                                                   .min_eval_period = 100,\n                                                   .max_eval_period = 800\n                                               },\n                                               .greater_than = 43.7,\n                                               .less_than = 17.3,\n                                               .step = 6.9\n                                           });\n    ANJAY_MUTEX_UNLOCK(anjay);\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_effective_attrs, fallback_to_server) {\n    DM_TEST_INIT;\n    (void) mocksocks;\n    _anjay_mock_dm_expect_resource_read_attrs(anjay, &OBJ, 69, 4, 1, 0,\n                                              &ANJAY_DM_R_ATTRIBUTES_EMPTY);\n    _anjay_mock_dm_expect_instance_read_default_attrs(\n            anjay, &OBJ, 69, 1, 0, &ANJAY_DM_OI_ATTRIBUTES_EMPTY);\n    _anjay_mock_dm_expect_object_read_default_attrs(\n            anjay, &OBJ, 1, 0,\n            &(const anjay_dm_oi_attributes_t) {\n                .min_period = 4,\n                .max_period = ANJAY_ATTRIB_INTEGER_NONE,\n                .min_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n                .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE\n            });\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &FAKE_SERVER, 0,\n            (const anjay_iid_t[]) { 1, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &FAKE_SERVER, 1, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { ANJAY_DM_RID_SERVER_SSID, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_PRESENT },\n                    { ANJAY_DM_RID_SERVER_LIFETIME, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT },\n                    { ANJAY_DM_RID_SERVER_DEFAULT_PMIN, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT },\n                    { ANJAY_DM_RID_SERVER_DEFAULT_PMAX, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_PRESENT },\n                    { ANJAY_DM_RID_SERVER_NOTIFICATION_STORING, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT },\n                    { ANJAY_DM_RID_SERVER_BINDING, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read(anjay, &FAKE_SERVER, 1,\n                                        ANJAY_DM_RID_SERVER_SSID,\n                                        ANJAY_ID_INVALID, 0,\n                                        ANJAY_MOCK_DM_INT(0, 1));\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &FAKE_SERVER, 1, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { ANJAY_DM_RID_SERVER_SSID, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_PRESENT },\n                    { ANJAY_DM_RID_SERVER_LIFETIME, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT },\n                    { ANJAY_DM_RID_SERVER_DEFAULT_PMIN, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT },\n                    { ANJAY_DM_RID_SERVER_DEFAULT_PMAX, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_PRESENT },\n                    { ANJAY_DM_RID_SERVER_NOTIFICATION_STORING, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT },\n                    { ANJAY_DM_RID_SERVER_BINDING, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read(anjay, &FAKE_SERVER, 1,\n                                        ANJAY_DM_RID_SERVER_DEFAULT_PMAX,\n                                        ANJAY_ID_INVALID, 0,\n                                        ANJAY_MOCK_DM_INT(0, 42));\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    anjay_dm_r_attributes_t attrs;\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_dm_effective_attrs(\n            anjay_unlocked, &DM_EFFECTIVE_ATTRS_STANDARD_QUERY, &attrs));\n    _anjay_mock_dm_assert_attributes_equal(\n            &attrs,\n            &(const anjay_dm_r_attributes_t) {\n                .common = {\n                    .min_period = 4,\n                    .max_period = 42,\n                    .min_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n                    .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE\n                },\n                .greater_than = ANJAY_ATTRIB_DOUBLE_NONE,\n                .less_than = ANJAY_ATTRIB_DOUBLE_NONE,\n                .step = ANJAY_ATTRIB_DOUBLE_NONE\n#ifdef ANJAY_WITH_LWM2M12\n                ,\n                .edge = ANJAY_DM_EDGE_ATTR_NONE\n#endif // ANJAY_WITH_LWM2M12\n            });\n    ANJAY_MUTEX_UNLOCK(anjay);\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_effective_attrs, resource_fail) {\n    DM_TEST_INIT;\n    (void) mocksocks;\n    _anjay_mock_dm_expect_resource_read_attrs(anjay, &OBJ, 69, 4, 1, -1, NULL);\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    anjay_dm_r_attributes_t attrs = ANJAY_DM_R_ATTRIBUTES_EMPTY;\n    AVS_UNIT_ASSERT_FAILED(_anjay_dm_effective_attrs(\n            anjay_unlocked, &DM_EFFECTIVE_ATTRS_STANDARD_QUERY, &attrs));\n    ANJAY_MUTEX_UNLOCK(anjay);\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_effective_attrs, for_instance) {\n    DM_TEST_INIT;\n    (void) mocksocks;\n    _anjay_mock_dm_expect_instance_read_default_attrs(\n            anjay, &OBJ, 69, 1, 0,\n            &(const anjay_dm_oi_attributes_t) {\n                .min_period = 9,\n                .max_period = 77,\n                .min_eval_period = 10,\n                .max_eval_period = 88\n            });\n    anjay_dm_r_attributes_t attrs;\n    anjay_dm_attrs_query_details_t details = DM_EFFECTIVE_ATTRS_STANDARD_QUERY;\n    details.rid = ANJAY_ID_INVALID;\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_dm_effective_attrs(anjay_unlocked, &details, &attrs));\n    _anjay_mock_dm_assert_attributes_equal(\n            &attrs,\n            &(const anjay_dm_r_attributes_t) {\n                .common = {\n                    .min_period = 9,\n                    .max_period = 77,\n                    .min_eval_period = 10,\n                    .max_eval_period = 88\n                },\n                .greater_than = ANJAY_ATTRIB_DOUBLE_NONE,\n                .less_than = ANJAY_ATTRIB_DOUBLE_NONE,\n                .step = ANJAY_ATTRIB_DOUBLE_NONE\n#ifdef ANJAY_WITH_LWM2M12\n                ,\n                .edge = ANJAY_DM_EDGE_ATTR_NONE\n#endif // ANJAY_WITH_LWM2M12\n            });\n    ANJAY_MUTEX_UNLOCK(anjay);\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_effective_attrs, instance_fail) {\n    DM_TEST_INIT;\n    (void) mocksocks;\n    _anjay_mock_dm_expect_instance_read_default_attrs(anjay, &OBJ, 69, 1, -1,\n                                                      NULL);\n    anjay_dm_r_attributes_t attrs;\n    anjay_dm_attrs_query_details_t details = DM_EFFECTIVE_ATTRS_STANDARD_QUERY;\n    details.rid = ANJAY_ID_INVALID;\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    AVS_UNIT_ASSERT_FAILED(\n            _anjay_dm_effective_attrs(anjay_unlocked, &details, &attrs));\n    ANJAY_MUTEX_UNLOCK(anjay);\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_effective_attrs, for_object) {\n    DM_TEST_INIT;\n    (void) mocksocks;\n    _anjay_mock_dm_expect_object_read_default_attrs(\n            anjay, &OBJ, 1, 0,\n            &(const anjay_dm_oi_attributes_t) {\n                .min_period = 6,\n                .max_period = 54,\n                .min_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n                .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE\n            });\n    anjay_dm_r_attributes_t attrs;\n    anjay_dm_attrs_query_details_t details = DM_EFFECTIVE_ATTRS_STANDARD_QUERY;\n    details.rid = ANJAY_ID_INVALID;\n    details.iid = ANJAY_ID_INVALID;\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_dm_effective_attrs(anjay_unlocked, &details, &attrs));\n    _anjay_mock_dm_assert_attributes_equal(\n            &attrs,\n            &(const anjay_dm_r_attributes_t) {\n                .common = {\n                    .min_period = 6,\n                    .max_period = 54,\n                    .min_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n                    .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE\n                },\n                .greater_than = ANJAY_ATTRIB_DOUBLE_NONE,\n                .less_than = ANJAY_ATTRIB_DOUBLE_NONE,\n                .step = ANJAY_ATTRIB_DOUBLE_NONE\n#ifdef ANJAY_WITH_LWM2M12\n                ,\n                .edge = ANJAY_DM_EDGE_ATTR_NONE\n#endif // ANJAY_WITH_LWM2M12\n            });\n    ANJAY_MUTEX_UNLOCK(anjay);\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_effective_attrs, object_fail) {\n    DM_TEST_INIT;\n    (void) mocksocks;\n    _anjay_mock_dm_expect_object_read_default_attrs(anjay, &OBJ, 1, -1, NULL);\n    anjay_dm_r_attributes_t attrs;\n    anjay_dm_attrs_query_details_t details = DM_EFFECTIVE_ATTRS_STANDARD_QUERY;\n    details.rid = ANJAY_ID_INVALID;\n    details.iid = ANJAY_ID_INVALID;\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    AVS_UNIT_ASSERT_FAILED(\n            _anjay_dm_effective_attrs(anjay_unlocked, &details, &attrs));\n    ANJAY_MUTEX_UNLOCK(anjay);\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_effective_attrs, server_default) {\n    DM_TEST_INIT;\n    (void) mocksocks;\n    _anjay_mock_dm_expect_object_read_default_attrs(\n            anjay, &OBJ, 1, 0, &ANJAY_DM_OI_ATTRIBUTES_EMPTY);\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &FAKE_SERVER, 0,\n            (const anjay_iid_t[]) { 1, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &FAKE_SERVER, 1, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { ANJAY_DM_RID_SERVER_SSID, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_PRESENT },\n                    { ANJAY_DM_RID_SERVER_LIFETIME, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT },\n                    { ANJAY_DM_RID_SERVER_DEFAULT_PMIN, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_PRESENT },\n                    { ANJAY_DM_RID_SERVER_DEFAULT_PMAX, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_PRESENT },\n                    { ANJAY_DM_RID_SERVER_NOTIFICATION_STORING, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT },\n                    { ANJAY_DM_RID_SERVER_BINDING, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read(anjay, &FAKE_SERVER, 1,\n                                        ANJAY_DM_RID_SERVER_SSID,\n                                        ANJAY_ID_INVALID, 0,\n                                        ANJAY_MOCK_DM_INT(0, 1));\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &FAKE_SERVER, 1, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { ANJAY_DM_RID_SERVER_SSID, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_PRESENT },\n                    { ANJAY_DM_RID_SERVER_LIFETIME, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT },\n                    { ANJAY_DM_RID_SERVER_DEFAULT_PMIN, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_PRESENT },\n                    { ANJAY_DM_RID_SERVER_DEFAULT_PMAX, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_PRESENT },\n                    { ANJAY_DM_RID_SERVER_NOTIFICATION_STORING, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT },\n                    { ANJAY_DM_RID_SERVER_BINDING, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read(anjay, &FAKE_SERVER, 1,\n                                        ANJAY_DM_RID_SERVER_DEFAULT_PMIN,\n                                        ANJAY_ID_INVALID, 0,\n                                        ANJAY_MOCK_DM_INT(0, 0));\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &FAKE_SERVER, 1, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { ANJAY_DM_RID_SERVER_SSID, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_PRESENT },\n                    { ANJAY_DM_RID_SERVER_LIFETIME, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT },\n                    { ANJAY_DM_RID_SERVER_DEFAULT_PMIN, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_PRESENT },\n                    { ANJAY_DM_RID_SERVER_DEFAULT_PMAX, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_PRESENT },\n                    { ANJAY_DM_RID_SERVER_NOTIFICATION_STORING, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT },\n                    { ANJAY_DM_RID_SERVER_BINDING, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read(anjay, &FAKE_SERVER, 1,\n                                        ANJAY_DM_RID_SERVER_DEFAULT_PMAX,\n                                        ANJAY_ID_INVALID, 0,\n                                        ANJAY_MOCK_DM_INT(0, 404));\n    anjay_dm_r_attributes_t attrs;\n    anjay_dm_attrs_query_details_t details = DM_EFFECTIVE_ATTRS_STANDARD_QUERY;\n    details.rid = ANJAY_ID_INVALID;\n    details.iid = ANJAY_ID_INVALID;\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_dm_effective_attrs(anjay_unlocked, &details, &attrs));\n    _anjay_mock_dm_assert_attributes_equal(\n            &attrs,\n            &(const anjay_dm_r_attributes_t) {\n                .common = {\n                    .min_period = 0,\n                    .max_period = 404,\n                    .min_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n                    .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE\n#ifdef ANJAY_WITH_CON_ATTR\n                    ,\n                    .con = ANJAY_DM_CON_ATTR_NONE\n#endif // ANJAY_WITH_CON_ATTR\n#ifdef ANJAY_WITH_LWM2M12\n                    ,\n                    .hqmax = ANJAY_ATTRIB_INTEGER_NONE\n#endif // ANJAY_WITH_LWM2M12\n                },\n                .greater_than = ANJAY_ATTRIB_DOUBLE_NONE,\n                .less_than = ANJAY_ATTRIB_DOUBLE_NONE,\n                .step = ANJAY_ATTRIB_DOUBLE_NONE\n#ifdef ANJAY_WITH_LWM2M12\n                ,\n                .edge = ANJAY_DM_EDGE_ATTR_NONE\n#endif // ANJAY_WITH_LWM2M12\n            });\n    ANJAY_MUTEX_UNLOCK(anjay);\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_effective_attrs, no_server) {\n    DM_TEST_INIT;\n    (void) mocksocks;\n    _anjay_mock_dm_expect_object_read_default_attrs(\n            anjay, &OBJ, 1, 0, &ANJAY_DM_OI_ATTRIBUTES_EMPTY);\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &FAKE_SERVER, 0, (const anjay_iid_t[]) { ANJAY_ID_INVALID });\n    anjay_dm_r_attributes_t attrs;\n    anjay_dm_attrs_query_details_t details = DM_EFFECTIVE_ATTRS_STANDARD_QUERY;\n    details.rid = ANJAY_ID_INVALID;\n    details.iid = ANJAY_ID_INVALID;\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_dm_effective_attrs(anjay_unlocked, &details, &attrs));\n    _anjay_mock_dm_assert_attributes_equal(\n            &attrs,\n            &(const anjay_dm_r_attributes_t) {\n                .common = {\n                    .min_period = ANJAY_DM_DEFAULT_PMIN_VALUE,\n                    .max_period = ANJAY_ATTRIB_INTEGER_NONE,\n                    .min_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n                    .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE\n#ifdef ANJAY_WITH_CON_ATTR\n                    ,\n                    .con = ANJAY_DM_CON_ATTR_NONE\n#endif // ANJAY_WITH_CON_ATTR\n#ifdef ANJAY_WITH_LWM2M12\n                    ,\n                    .hqmax = ANJAY_ATTRIB_INTEGER_NONE\n#endif // ANJAY_WITH_LWM2M12\n                },\n                .greater_than = ANJAY_ATTRIB_DOUBLE_NONE,\n                .less_than = ANJAY_ATTRIB_DOUBLE_NONE,\n                .step = ANJAY_ATTRIB_DOUBLE_NONE\n#ifdef ANJAY_WITH_LWM2M12\n                ,\n                .edge = ANJAY_DM_EDGE_ATTR_NONE\n#endif // ANJAY_WITH_LWM2M12\n            });\n    ANJAY_MUTEX_UNLOCK(anjay);\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_effective_attrs, no_resources) {\n    DM_TEST_INIT;\n    (void) mocksocks;\n    _anjay_mock_dm_expect_object_read_default_attrs(\n            anjay, &OBJ, 1, 0, &ANJAY_DM_OI_ATTRIBUTES_EMPTY);\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &FAKE_SERVER, 0,\n            (const anjay_iid_t[]) { 1, ANJAY_ID_INVALID });\n\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &FAKE_SERVER, 1, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { ANJAY_DM_RID_SERVER_SSID, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_PRESENT },\n                    { ANJAY_DM_RID_SERVER_LIFETIME, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT },\n                    { ANJAY_DM_RID_SERVER_DEFAULT_PMIN, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT },\n                    { ANJAY_DM_RID_SERVER_DEFAULT_PMAX, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT },\n                    { ANJAY_DM_RID_SERVER_NOTIFICATION_STORING, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT },\n                    { ANJAY_DM_RID_SERVER_BINDING, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n\n    _anjay_mock_dm_expect_resource_read(anjay, &FAKE_SERVER, 1,\n                                        ANJAY_DM_RID_SERVER_SSID,\n                                        ANJAY_ID_INVALID, 0,\n                                        ANJAY_MOCK_DM_INT(0, 1));\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &FAKE_SERVER, 1, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { ANJAY_DM_RID_SERVER_SSID, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_PRESENT },\n                    { ANJAY_DM_RID_SERVER_LIFETIME, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT },\n                    { ANJAY_DM_RID_SERVER_DEFAULT_PMIN, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT },\n                    { ANJAY_DM_RID_SERVER_DEFAULT_PMAX, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT },\n                    { ANJAY_DM_RID_SERVER_NOTIFICATION_STORING, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT },\n                    { ANJAY_DM_RID_SERVER_BINDING, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &FAKE_SERVER, 1, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { ANJAY_DM_RID_SERVER_SSID, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_PRESENT },\n                    { ANJAY_DM_RID_SERVER_LIFETIME, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT },\n                    { ANJAY_DM_RID_SERVER_DEFAULT_PMIN, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT },\n                    { ANJAY_DM_RID_SERVER_DEFAULT_PMAX, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT },\n                    { ANJAY_DM_RID_SERVER_NOTIFICATION_STORING, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT },\n                    { ANJAY_DM_RID_SERVER_BINDING, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    anjay_dm_r_attributes_t attrs;\n    anjay_dm_attrs_query_details_t details = DM_EFFECTIVE_ATTRS_STANDARD_QUERY;\n    details.rid = ANJAY_ID_INVALID;\n    details.iid = ANJAY_ID_INVALID;\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_dm_effective_attrs(anjay_unlocked, &details, &attrs));\n    _anjay_mock_dm_assert_attributes_equal(\n            &attrs,\n            &(const anjay_dm_r_attributes_t) {\n                .common = {\n                    .min_period = ANJAY_DM_DEFAULT_PMIN_VALUE,\n                    .max_period = ANJAY_ATTRIB_INTEGER_NONE,\n                    .min_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n                    .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE\n#ifdef ANJAY_WITH_CON_ATTR\n                    ,\n                    .con = ANJAY_DM_CON_ATTR_NONE\n#endif // ANJAY_WITH_CON_ATTR\n#ifdef ANJAY_WITH_LWM2M12\n                    ,\n                    .hqmax = ANJAY_ATTRIB_INTEGER_NONE\n#endif // ANJAY_WITH_LWM2M12\n                },\n                .greater_than = ANJAY_ATTRIB_DOUBLE_NONE,\n                .less_than = ANJAY_ATTRIB_DOUBLE_NONE,\n                .step = ANJAY_ATTRIB_DOUBLE_NONE\n#ifdef ANJAY_WITH_LWM2M12\n                ,\n                .edge = ANJAY_DM_EDGE_ATTR_NONE\n#endif // ANJAY_WITH_LWM2M12\n            });\n    ANJAY_MUTEX_UNLOCK(anjay);\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_effective_attrs, read_error) {\n    DM_TEST_INIT;\n    (void) mocksocks;\n    _anjay_mock_dm_expect_object_read_default_attrs(\n            anjay, &OBJ, 1, 0, &ANJAY_DM_OI_ATTRIBUTES_EMPTY);\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &FAKE_SERVER, 0,\n            (const anjay_iid_t[]) { 1, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &FAKE_SERVER, 1, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { ANJAY_DM_RID_SERVER_SSID, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_PRESENT },\n                    { ANJAY_DM_RID_SERVER_LIFETIME, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT },\n                    { ANJAY_DM_RID_SERVER_DEFAULT_PMIN, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_PRESENT },\n                    { ANJAY_DM_RID_SERVER_DEFAULT_PMAX, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_PRESENT },\n                    { ANJAY_DM_RID_SERVER_NOTIFICATION_STORING, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT },\n                    { ANJAY_DM_RID_SERVER_BINDING, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read(anjay, &FAKE_SERVER, 1,\n                                        ANJAY_DM_RID_SERVER_SSID,\n                                        ANJAY_ID_INVALID, 0,\n                                        ANJAY_MOCK_DM_INT(0, 1));\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &FAKE_SERVER, 1, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { ANJAY_DM_RID_SERVER_SSID, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_PRESENT },\n                    { ANJAY_DM_RID_SERVER_LIFETIME, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT },\n                    { ANJAY_DM_RID_SERVER_DEFAULT_PMIN, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_PRESENT },\n                    { ANJAY_DM_RID_SERVER_DEFAULT_PMAX, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_PRESENT },\n                    { ANJAY_DM_RID_SERVER_NOTIFICATION_STORING, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT },\n                    { ANJAY_DM_RID_SERVER_BINDING, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read(anjay, &FAKE_SERVER, 1,\n                                        ANJAY_DM_RID_SERVER_DEFAULT_PMIN,\n                                        ANJAY_ID_INVALID, 0,\n                                        ANJAY_MOCK_DM_INT(0, 7));\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &FAKE_SERVER, 1, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { ANJAY_DM_RID_SERVER_SSID, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_PRESENT },\n                    { ANJAY_DM_RID_SERVER_LIFETIME, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT },\n                    { ANJAY_DM_RID_SERVER_DEFAULT_PMIN, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_PRESENT },\n                    { ANJAY_DM_RID_SERVER_DEFAULT_PMAX, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_PRESENT },\n                    { ANJAY_DM_RID_SERVER_NOTIFICATION_STORING, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT },\n                    { ANJAY_DM_RID_SERVER_BINDING, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read(anjay, &FAKE_SERVER, 1,\n                                        ANJAY_DM_RID_SERVER_DEFAULT_PMAX,\n                                        ANJAY_ID_INVALID, -1,\n                                        ANJAY_MOCK_DM_NONE);\n    anjay_dm_r_attributes_t attrs;\n    anjay_dm_attrs_query_details_t details = DM_EFFECTIVE_ATTRS_STANDARD_QUERY;\n    details.rid = ANJAY_ID_INVALID;\n    details.iid = ANJAY_ID_INVALID;\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    AVS_UNIT_ASSERT_FAILED(\n            _anjay_dm_effective_attrs(anjay_unlocked, &details, &attrs));\n    ANJAY_MUTEX_UNLOCK(anjay);\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_effective_attrs, read_invalid) {\n    DM_TEST_INIT;\n    (void) mocksocks;\n    _anjay_mock_dm_expect_object_read_default_attrs(\n            anjay, &OBJ, 1, 0, &ANJAY_DM_OI_ATTRIBUTES_EMPTY);\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &FAKE_SERVER, 0,\n            (const anjay_iid_t[]) { 1, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &FAKE_SERVER, 1, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { ANJAY_DM_RID_SERVER_SSID, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_PRESENT },\n                    { ANJAY_DM_RID_SERVER_LIFETIME, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT },\n                    { ANJAY_DM_RID_SERVER_DEFAULT_PMIN, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_PRESENT },\n                    { ANJAY_DM_RID_SERVER_DEFAULT_PMAX, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_PRESENT },\n                    { ANJAY_DM_RID_SERVER_NOTIFICATION_STORING, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT },\n                    { ANJAY_DM_RID_SERVER_BINDING, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read(anjay, &FAKE_SERVER, 1,\n                                        ANJAY_DM_RID_SERVER_SSID,\n                                        ANJAY_ID_INVALID, 0,\n                                        ANJAY_MOCK_DM_INT(0, 1));\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &FAKE_SERVER, 1, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { ANJAY_DM_RID_SERVER_SSID, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_PRESENT },\n                    { ANJAY_DM_RID_SERVER_LIFETIME, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT },\n                    { ANJAY_DM_RID_SERVER_DEFAULT_PMIN, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_PRESENT },\n                    { ANJAY_DM_RID_SERVER_DEFAULT_PMAX, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_PRESENT },\n                    { ANJAY_DM_RID_SERVER_NOTIFICATION_STORING, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT },\n                    { ANJAY_DM_RID_SERVER_BINDING, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read(anjay, &FAKE_SERVER, 1,\n                                        ANJAY_DM_RID_SERVER_DEFAULT_PMIN,\n                                        ANJAY_ID_INVALID, 0,\n                                        ANJAY_MOCK_DM_INT(0, 7));\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &FAKE_SERVER, 1, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { ANJAY_DM_RID_SERVER_SSID, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_PRESENT },\n                    { ANJAY_DM_RID_SERVER_LIFETIME, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT },\n                    { ANJAY_DM_RID_SERVER_DEFAULT_PMIN, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_PRESENT },\n                    { ANJAY_DM_RID_SERVER_DEFAULT_PMAX, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_PRESENT },\n                    { ANJAY_DM_RID_SERVER_NOTIFICATION_STORING, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT },\n                    { ANJAY_DM_RID_SERVER_BINDING, ANJAY_DM_RES_RW,\n                      ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read(anjay, &FAKE_SERVER, 1,\n                                        ANJAY_DM_RID_SERVER_DEFAULT_PMAX,\n                                        ANJAY_ID_INVALID, 0,\n                                        ANJAY_MOCK_DM_INT(0, -1));\n    anjay_dm_r_attributes_t attrs;\n    anjay_dm_attrs_query_details_t details = DM_EFFECTIVE_ATTRS_STANDARD_QUERY;\n    details.rid = ANJAY_ID_INVALID;\n    details.iid = ANJAY_ID_INVALID;\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    AVS_UNIT_ASSERT_FAILED(\n            _anjay_dm_effective_attrs(anjay_unlocked, &details, &attrs));\n    ANJAY_MUTEX_UNLOCK(anjay);\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_resource_operations, nonreadable_resource) {\n    DM_TEST_INIT;\n    DM_TEST_REQUEST(mocksocks[0], CON, GET, ID(0xFA3E), PATH(\"42\", \"69\", \"4\"),\n                    NO_PAYLOAD);\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0,\n            (const anjay_iid_t[]) { 14, 42, 69, 514, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 69, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_E, ANJAY_DM_RES_PRESENT },\n                    { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    // 4.05 Method Not Allowed\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, METHOD_NOT_ALLOWED, ID(0xfa3e),\n                            NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_resource_operations, nonexecutable_resource) {\n    DM_TEST_INIT;\n    DM_TEST_REQUEST(mocksocks[0], CON, POST, ID(0xFA3E), PATH(\"42\", \"69\", \"4\"));\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0,\n            (const anjay_iid_t[]) { 14, 42, 69, 514, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 69, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_E, ANJAY_DM_RES_ABSENT },\n                    { 1, ANJAY_DM_RES_E, ANJAY_DM_RES_ABSENT },\n                    { 2, ANJAY_DM_RES_E, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_E, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_W, ANJAY_DM_RES_PRESENT },\n                    { 5, ANJAY_DM_RES_E, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_E, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    // 4.05 Method Not Allowed\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, METHOD_NOT_ALLOWED, ID(0xfa3e),\n                            NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_resource_operations, nonwritable_resource) {\n    DM_TEST_INIT;\n    DM_TEST_REQUEST(mocksocks[0], CON, PUT, ID(0xFA3E), PATH(\"42\", \"69\", \"4\"),\n                    CONTENT_FORMAT(PLAINTEXT), PAYLOAD(\"content\"));\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0,\n            (const anjay_iid_t[]) { 14, 42, 69, 514, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 69, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_R, ANJAY_DM_RES_ABSENT },\n                    { 1, ANJAY_DM_RES_R, ANJAY_DM_RES_ABSENT },\n                    { 2, ANJAY_DM_RES_R, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_R, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_R, ANJAY_DM_RES_PRESENT },\n                    { 5, ANJAY_DM_RES_R, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_R, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    // 4.05 Method Not Allowed\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, METHOD_NOT_ALLOWED, ID(0xfa3e),\n                            NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_resource_operations, readable_resource) {\n    DM_TEST_INIT;\n    DM_TEST_REQUEST(mocksocks[0], CON, GET, ID(0xFA3E), PATH(\"42\", \"69\", \"4\"),\n                    NO_PAYLOAD);\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0,\n            (const anjay_iid_t[]) { 14, 42, 69, 514, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 69, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_R, ANJAY_DM_RES_ABSENT },\n                    { 1, ANJAY_DM_RES_R, ANJAY_DM_RES_ABSENT },\n                    { 2, ANJAY_DM_RES_R, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_R, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_R, ANJAY_DM_RES_PRESENT },\n                    { 5, ANJAY_DM_RES_R, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_R, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read(anjay, &OBJ, 69, 4, ANJAY_ID_INVALID, 0,\n                                        ANJAY_MOCK_DM_INT(0, 514));\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, CONTENT, ID(0xFA3E),\n                            CONTENT_FORMAT(PLAINTEXT), PAYLOAD(\"514\"));\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_resource_operations, executable_resource) {\n    DM_TEST_INIT;\n    DM_TEST_REQUEST(mocksocks[0], CON, POST, ID(0xFA3E),\n                    PATH(\"42\", \"514\", \"4\"));\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0,\n            (const anjay_iid_t[]) { 14, 42, 69, 514, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 514, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_R, ANJAY_DM_RES_ABSENT },\n                    { 1, ANJAY_DM_RES_R, ANJAY_DM_RES_ABSENT },\n                    { 2, ANJAY_DM_RES_R, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_R, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_E, ANJAY_DM_RES_PRESENT },\n                    { 5, ANJAY_DM_RES_R, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_R, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_execute(anjay, &OBJ, 514, 4, NULL, 0);\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, CHANGED, ID(0xFA3E), NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_resource_operations, writable_resource) {\n    DM_TEST_INIT;\n    DM_TEST_REQUEST(mocksocks[0], CON, PUT, ID(0xFA3E), PATH(\"42\", \"514\", \"4\"),\n                    CONTENT_FORMAT(PLAINTEXT), PAYLOAD(\"Hello\"));\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0,\n            (const anjay_iid_t[]) { 14, 42, 69, 514, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 514, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_R, ANJAY_DM_RES_ABSENT },\n                    { 1, ANJAY_DM_RES_R, ANJAY_DM_RES_ABSENT },\n                    { 2, ANJAY_DM_RES_R, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_R, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_W, ANJAY_DM_RES_PRESENT },\n                    { 5, ANJAY_DM_RES_R, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_R, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_write(anjay, &OBJ, 514, 4, ANJAY_ID_INVALID,\n                                         ANJAY_MOCK_DM_STRING(0, \"Hello\"), 0);\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, CHANGED, ID(0xFA3E), NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_res_read, no_space) {\n    DM_TEST_INIT;\n\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 42, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                    { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read(anjay, &OBJ, 42, 3, ANJAY_ID_INVALID, 0,\n                                        ANJAY_MOCK_DM_STRING(0, \"\"));\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_dm_read_resource_into_buffer(\n            anjay_unlocked, &MAKE_RESOURCE_PATH(OBJ->oid, 42, 3), NULL, 0,\n            NULL));\n    ANJAY_MUTEX_UNLOCK(anjay);\n\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 514, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                    { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read(anjay, &OBJ, 514, 4, ANJAY_ID_INVALID,\n                                        -1, ANJAY_MOCK_DM_STRING(-1, \"Hello\"));\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    AVS_UNIT_ASSERT_FAILED(_anjay_dm_read_resource_into_buffer(\n            anjay_unlocked, &MAKE_RESOURCE_PATH(OBJ->oid, 514, 4), NULL, 0,\n            NULL));\n    ANJAY_MUTEX_UNLOCK(anjay);\n\n    char fake_string = 42;\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 69, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                    { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read(anjay, &OBJ, 69, 5, ANJAY_ID_INVALID, 0,\n                                        ANJAY_MOCK_DM_STRING(0, \"\"));\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_dm_read_resource_string(\n            anjay_unlocked, &MAKE_RESOURCE_PATH(OBJ->oid, 69, 5), &fake_string,\n            1));\n    ANJAY_MUTEX_UNLOCK(anjay);\n    AVS_UNIT_ASSERT_EQUAL(fake_string, 0);\n\n    fake_string = 69;\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 32, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read(anjay, &OBJ, 32, 6, ANJAY_ID_INVALID,\n                                        -1,\n                                        ANJAY_MOCK_DM_STRING(-1, \"Goodbye\"));\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    AVS_UNIT_ASSERT_FAILED(_anjay_dm_read_resource_string(\n            anjay_unlocked, &MAKE_RESOURCE_PATH(OBJ->oid, 32, 6), &fake_string,\n            1));\n    ANJAY_MUTEX_UNLOCK(anjay);\n    AVS_UNIT_ASSERT_EQUAL(fake_string, 69);\n\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_res_read, objlnk) {\n    DM_TEST_INIT;\n\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 42, 0,\n            (const anjay_mock_dm_res_entry_t[]) { { 1, ANJAY_DM_RES_R,\n                                                    ANJAY_DM_RES_PRESENT },\n                                                  ANJAY_MOCK_DM_RES_END });\n\n    _anjay_mock_dm_expect_resource_read(anjay, &OBJ, 42, 1, ANJAY_ID_INVALID, 0,\n                                        ANJAY_MOCK_DM_OBJLNK(0, 123, 456));\n\n    anjay_oid_t oid = 0;\n    anjay_iid_t iid = 0;\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    ASSERT_OK(_anjay_dm_read_resource_objlnk(\n            anjay_unlocked, &MAKE_RESOURCE_PATH(OBJ->oid, 42, 1), &oid, &iid));\n    ANJAY_MUTEX_UNLOCK(anjay);\n    ASSERT_EQ(oid, 123);\n    ASSERT_EQ(iid, 456);\n\n    DM_TEST_FINISH;\n}\n\n#ifdef ANJAY_WITH_LWM2M11\nAVS_UNIT_TEST(dm_res_read, u32_array_missing) {\n    DM_TEST_INIT;\n\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 42, 0,\n            (const anjay_mock_dm_res_entry_t[]) { { 1, ANJAY_DM_RES_RM,\n                                                    ANJAY_DM_RES_ABSENT },\n                                                  ANJAY_MOCK_DM_RES_END });\n\n    uint32_t *values;\n    size_t num_values;\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    ASSERT_EQ(_anjay_dm_read_resource_u32_array(\n                      anjay_unlocked, &MAKE_RESOURCE_PATH(OBJ->oid, 42, 1),\n                      &values, &num_values),\n              ANJAY_ERR_NOT_FOUND);\n    ANJAY_MUTEX_UNLOCK(anjay);\n\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_res_read, u32_array_empty) {\n    DM_TEST_INIT;\n\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 42, 0,\n            (const anjay_mock_dm_res_entry_t[]) { { 1, ANJAY_DM_RES_RM,\n                                                    ANJAY_DM_RES_PRESENT },\n                                                  ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_list_resource_instances(\n            anjay, &OBJ, 42, 1, 0, (const anjay_riid_t[]) { ANJAY_ID_INVALID });\n\n    uint32_t *values;\n    size_t num_values;\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    ASSERT_OK(_anjay_dm_read_resource_u32_array(\n            anjay_unlocked, &MAKE_RESOURCE_PATH(OBJ->oid, 42, 1), &values,\n            &num_values));\n    ANJAY_MUTEX_UNLOCK(anjay);\n    ASSERT_EQ(num_values, 0);\n    avs_free(values);\n\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(dm_res_read, u64_array_multiple_elements) {\n    DM_TEST_INIT;\n\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 42, 0,\n            (const anjay_mock_dm_res_entry_t[]) { { 1, ANJAY_DM_RES_RM,\n                                                    ANJAY_DM_RES_PRESENT },\n                                                  ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_list_resource_instances(\n            anjay, &OBJ, 42, 1, 0,\n            (const anjay_riid_t[]) { 12, 34, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_resource_read(anjay, &OBJ, 42, 1, 12, 0,\n                                        ANJAY_MOCK_DM_UINT(0, 111));\n    _anjay_mock_dm_expect_resource_read(anjay, &OBJ, 42, 1, 34, 0,\n                                        ANJAY_MOCK_DM_UINT(0, 222));\n\n    uint32_t *values;\n    size_t num_values;\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    ASSERT_OK(_anjay_dm_read_resource_u32_array(\n            anjay_unlocked, &MAKE_RESOURCE_PATH(OBJ->oid, 42, 1), &values,\n            &num_values));\n    ANJAY_MUTEX_UNLOCK(anjay);\n    ASSERT_NOT_NULL(values);\n    ASSERT_EQ(num_values, 2);\n    ASSERT_EQ(values[0], 111);\n    ASSERT_EQ(values[1], 222);\n    avs_free(values);\n\n    DM_TEST_FINISH;\n}\n#endif // ANJAY_WITH_LWM2M11\n"
  },
  {
    "path": "tests/core/downloader/downloader.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <avsystem/commons/avs_memory.h>\n#include <avsystem/commons/avs_unit_mock_helpers.h>\n#include <avsystem/commons/avs_unit_mocksock.h>\n#include <avsystem/commons/avs_unit_test.h>\n\n#include \"tests/core/coap/utils.h\"\n#include \"tests/utils/coap/socket.h\"\n#include \"tests/utils/mock_clock.h\"\n#include \"tests/utils/utils.h\"\n\n#define DIV_CEIL(a, b) (((a) + (b) -1) / (b))\n\n#define ASSERT_ALMOST_EQ(a, b) \\\n    AVS_UNIT_ASSERT_TRUE(fabs((a) - (b)) < 0.01 /* epsilon */)\n\ntypedef struct {\n    anjay_unlocked_t *anjay;\n    avs_net_socket_t *mocksock[4];\n    size_t num_mocksocks;\n} dl_test_env_t;\n\ndl_test_env_t ENV;\n\nconst avs_coap_udp_tx_params_t DETERMINISTIC_TX_PARAMS = {\n    .ack_timeout = { 2, 0 },\n    /* disable randomization */\n    .ack_random_factor = 1.0,\n    .max_retransmit = 4,\n    .nstart = 1\n};\n\nstatic avs_error_t\nallocate_mocksock(avs_net_socket_t **socket,\n                  const avs_net_socket_configuration_t *configuration) {\n    (void) configuration;\n\n    AVS_UNIT_ASSERT_TRUE(ENV.num_mocksocks < AVS_ARRAY_SIZE(ENV.mocksock));\n    *socket = ENV.mocksock[ENV.num_mocksocks++];\n\n    return AVS_OK;\n}\n\nstatic void setup(void) {\n    reset_token_generator();\n\n    memset(&ENV, 0, sizeof(ENV));\n\n    AVS_UNIT_MOCK(avs_net_udp_socket_create) = allocate_mocksock;\n\n    for (size_t i = 0; i < AVS_ARRAY_SIZE(ENV.mocksock); ++i) {\n        _anjay_mocksock_create(&ENV.mocksock[i], 1252, 1252);\n        avs_unit_mocksock_enable_state_getopt(ENV.mocksock[i]);\n    }\n\n    anjay_t *anjay_locked =\n            avs_calloc(1,\n#ifdef ANJAY_WITH_THREAD_SAFETY\n                       offsetof(anjay_t, anjay_unlocked_placeholder) +\n#endif // ANJAY_WITH_THREAD_SAFETY\n                               sizeof(anjay_unlocked_t));\n    AVS_UNIT_ASSERT_NOT_NULL(anjay_locked);\n    ENV.anjay =\n#ifdef ANJAY_WITH_THREAD_SAFETY\n            (anjay_unlocked_t *) &anjay_locked->anjay_unlocked_placeholder\n#else  // ANJAY_WITH_THREAD_SAFETY\n            anjay_locked\n#endif // ANJAY_WITH_THREAD_SAFETY\n            ;\n#ifdef ANJAY_WITH_THREAD_SAFETY\n    AVS_UNIT_ASSERT_SUCCESS(avs_mutex_create(&anjay_locked->mutex));\n    AVS_UNIT_ASSERT_SUCCESS(avs_mutex_lock(anjay_locked->mutex));\n    ENV.anjay->coap_sched = avs_sched_new(\"Anjay-test-CoAP\", NULL);\n#endif // ANJAY_WITH_THREAD_SAFETY\n    ENV.anjay->online_transports = ANJAY_TRANSPORT_SET_ALL;\n    ENV.anjay->sched = avs_sched_new(\"Anjay-test\", anjay_locked);\n    ENV.anjay->udp_tx_params = DETERMINISTIC_TX_PARAMS;\n    ENV.anjay->prng_ctx = (anjay_prng_ctx_t) {\n        .ctx = avs_crypto_prng_new(NULL, NULL),\n        .allocated_by_user = true\n    };\n\n    AVS_UNIT_ASSERT_NOT_NULL(ENV.anjay->prng_ctx.ctx);\n\n    _anjay_downloader_init(&ENV.anjay->downloader, ENV.anjay);\n\n    // NOTE: Special initialization value is used to ensure CoAP Message ID\n    // starts with 0.\n    _anjay_mock_clock_start(\n            avs_time_monotonic_from_scalar(4235699843U, AVS_TIME_S));\n\n    enum { ARBITRARY_SIZE = 4096 };\n    // used by the downloader internally\n    ENV.anjay->out_shared_buffer = avs_shared_buffer_new(ARBITRARY_SIZE);\n    AVS_UNIT_ASSERT_NOT_NULL(ENV.anjay->out_shared_buffer);\n\n    ENV.anjay->in_shared_buffer = avs_shared_buffer_new(ARBITRARY_SIZE);\n    AVS_UNIT_ASSERT_NOT_NULL(ENV.anjay->in_shared_buffer);\n}\n\nstatic void teardown() {\n    _anjay_downloader_cleanup(&ENV.anjay->downloader);\n    avs_sched_cleanup(&ENV.anjay->coap_sched);\n#ifdef ANJAY_WITH_THREAD_SAFETY\n    anjay_t *anjay_locked =\n            AVS_CONTAINER_OF(ENV.anjay, anjay_t, anjay_unlocked_placeholder);\n    avs_mutex_unlock(anjay_locked->mutex);\n#endif // ANJAY_WITH_THREAD_SAFETY\n    avs_sched_cleanup(&ENV.anjay->sched);\n\n    for (size_t i = 0; i < AVS_ARRAY_SIZE(ENV.mocksock); ++i) {\n        _anjay_mocksock_expect_stats_zero(ENV.mocksock[i]);\n        _anjay_socket_cleanup(ENV.anjay, &ENV.mocksock[i]);\n    }\n\n    avs_free(ENV.anjay->out_shared_buffer);\n    avs_free(ENV.anjay->in_shared_buffer);\n    avs_crypto_prng_free(&ENV.anjay->prng_ctx.ctx);\n\n#ifdef ANJAY_WITH_THREAD_SAFETY\n    avs_mutex_cleanup(&anjay_locked->mutex);\n    avs_free(anjay_locked);\n#else  // ANJAY_WITH_THREAD_SAFETY\n    avs_free(ENV.anjay);\n#endif // ANJAY_WITH_THREAD_SAFETY\n\n    memset(&ENV, 0, sizeof(ENV));\n\n    _anjay_mock_clock_finish();\n}\n\ntypedef struct {\n    char data[1024];\n    size_t data_size;\n    const anjay_etag_t *etag;\n    avs_error_t result;\n} on_next_block_args_t;\n\ntypedef struct {\n    anjay_unlocked_t *anjay;\n    AVS_LIST(on_next_block_args_t) on_next_block_calls;\n    bool finish_call_expected;\n    anjay_download_status_t expected_download_status;\n} handler_data_t;\n\nstatic void expect_next_block(handler_data_t *data,\n                              on_next_block_args_t expected_args) {\n    on_next_block_args_t *args = AVS_LIST_NEW_ELEMENT(on_next_block_args_t);\n    AVS_UNIT_ASSERT_NOT_NULL(args);\n\n    *args = expected_args;\n    AVS_UNIT_ASSERT_TRUE(args->data_size <= sizeof(args->data));\n\n    AVS_LIST_APPEND(&data->on_next_block_calls, args);\n}\n\nstatic void expect_download_finished(handler_data_t *data,\n                                     anjay_download_status_t expected_status) {\n    data->expected_download_status = expected_status;\n    data->finish_call_expected = true;\n}\n\nstatic avs_error_t on_next_block(anjay_t *anjay,\n                                 const uint8_t *data,\n                                 size_t data_size,\n                                 const anjay_etag_t *etag,\n                                 void *user_data) {\n    handler_data_t *hd = (handler_data_t *) user_data;\n    AVS_UNIT_ASSERT_NOT_NULL(hd->on_next_block_calls);\n\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    AVS_UNIT_ASSERT_TRUE(anjay_unlocked == hd->anjay);\n    ANJAY_MUTEX_UNLOCK(anjay);\n\n    on_next_block_args_t *args = AVS_LIST_DETACH(&hd->on_next_block_calls);\n    if (etag && etag->size > 0) {\n        AVS_UNIT_ASSERT_EQUAL(args->etag->size, etag->size);\n        AVS_UNIT_ASSERT_EQUAL_BYTES_SIZED(args->etag->value, etag->value,\n                                          etag->size);\n    } else {\n        AVS_UNIT_ASSERT_NULL(args->etag);\n    }\n    AVS_UNIT_ASSERT_EQUAL(args->data_size, data_size);\n    AVS_UNIT_ASSERT_EQUAL_BYTES_SIZED(args->data, data, data_size);\n    avs_error_t result = args->result;\n\n    AVS_LIST_DELETE(&args);\n    return result;\n}\n\nstatic void on_download_finished(anjay_t *anjay,\n                                 anjay_download_status_t status,\n                                 void *user_data) {\n    handler_data_t *hd = (handler_data_t *) user_data;\n\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    AVS_UNIT_ASSERT_TRUE(anjay_unlocked == hd->anjay);\n    ANJAY_MUTEX_UNLOCK(anjay);\n    AVS_UNIT_ASSERT_TRUE(hd->finish_call_expected);\n    AVS_UNIT_ASSERT_EQUAL_BYTES_SIZED(&status, &hd->expected_download_status,\n                                      sizeof(status));\n\n    hd->finish_call_expected = false;\n}\n\ntypedef struct {\n    dl_test_env_t *base;\n    handler_data_t data;\n    anjay_download_config_t cfg;\n    avs_net_socket_t *mocksock; // alias for SIMPLE_ENV.base->mocksock[0]\n} dl_simple_test_env_t;\n\ndl_simple_test_env_t SIMPLE_ENV;\n\nstatic void setup_simple_with_etag(const char *url, const anjay_etag_t *etag) {\n    memset(&SIMPLE_ENV, 0, sizeof(SIMPLE_ENV));\n    setup();\n    SIMPLE_ENV.base = &ENV;\n    SIMPLE_ENV.data = (handler_data_t) {\n        .anjay = SIMPLE_ENV.base->anjay\n    };\n    SIMPLE_ENV.cfg = (anjay_download_config_t) {\n        .url = url,\n        .on_next_block = on_next_block,\n        .on_download_finished = on_download_finished,\n        .user_data = &SIMPLE_ENV.data,\n        .etag = etag\n    };\n    SIMPLE_ENV.mocksock = SIMPLE_ENV.base->mocksock[0];\n}\n\nstatic void setup_simple(const char *url) {\n    setup_simple_with_etag(url, NULL);\n}\n\nstatic void teardown_simple() {\n    teardown();\n    memset(&SIMPLE_ENV, 0, sizeof(SIMPLE_ENV));\n}\n\nstatic int handle_packet(void) {\n    AVS_LIST(anjay_socket_entry_t) sock = NULL;\n    _anjay_downloader_get_sockets(&SIMPLE_ENV.base->anjay->downloader, &sock,\n                                  /* include_offline = */ false);\n    if (!sock) {\n        return -1;\n    }\n\n    AVS_UNIT_ASSERT_EQUAL(1, AVS_LIST_SIZE(sock));\n    AVS_UNIT_ASSERT_TRUE(SIMPLE_ENV.mocksock == sock->socket);\n\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_downloader_handle_packet(\n            &SIMPLE_ENV.base->anjay->downloader, sock->socket));\n\n    AVS_LIST_CLEAR(&sock);\n    return 0;\n}\n\nstatic void perform_simple_download(void) {\n    anjay_download_handle_t handle = NULL;\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_downloader_download(&SIMPLE_ENV.base->anjay->downloader,\n                                       &handle, &SIMPLE_ENV.cfg, NULL, NULL));\n    AVS_UNIT_ASSERT_NOT_NULL(handle);\n\n    do {\n        ANJAY_MUTEX_UNLOCK_FOR_CALLBACK(anjay_locked, SIMPLE_ENV.base->anjay);\n        while (avs_time_duration_equal(avs_sched_time_to_next(\n                                               SIMPLE_ENV.base->anjay->sched),\n                                       AVS_TIME_DURATION_ZERO)) {\n            avs_sched_run(SIMPLE_ENV.base->anjay->sched);\n        }\n        ANJAY_MUTEX_LOCK_AFTER_CALLBACK(anjay_locked);\n    } while (!handle_packet());\n\n    avs_unit_mocksock_assert_expects_met(SIMPLE_ENV.mocksock);\n}\n\nAVS_UNIT_TEST(downloader, empty_has_no_sockets) {\n    setup();\n\n    AVS_LIST(anjay_socket_entry_t) socks = NULL;\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_downloader_get_sockets(&ENV.anjay->downloader, &socks,\n                                          /* include_offline = */ false));\n    AVS_UNIT_ASSERT_NULL(socks);\n\n    teardown();\n}\n\nstatic void assert_download_not_possible(anjay_downloader_t *dl,\n                                         const anjay_download_config_t *cfg) {\n    size_t num_downloads = 0;\n\n    AVS_LIST(anjay_socket_entry_t) socks = NULL;\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_downloader_get_sockets(dl, &socks,\n                                          /* include_offline = */ false));\n    num_downloads = AVS_LIST_SIZE(socks);\n    AVS_LIST_CLEAR(&socks);\n\n    anjay_download_handle_t handle = NULL;\n    avs_error_t err = _anjay_downloader_download(dl, &handle, cfg, NULL, NULL\n\n    );\n    AVS_UNIT_ASSERT_EQUAL(err.category, AVS_ERRNO_CATEGORY);\n    AVS_UNIT_ASSERT_EQUAL(err.code, AVS_EINVAL);\n    AVS_UNIT_ASSERT_NULL(handle);\n\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_downloader_get_sockets(dl, &socks,\n                                          /* include_offline = */ false));\n    AVS_UNIT_ASSERT_EQUAL(num_downloads, AVS_LIST_SIZE(socks));\n    AVS_LIST_CLEAR(&socks);\n}\n\nAVS_UNIT_TEST(downloader, cannot_download_without_handlers) {\n    setup_simple(\"coap://127.0.0.1:5683\");\n\n    SIMPLE_ENV.cfg.on_next_block = NULL;\n    SIMPLE_ENV.cfg.on_download_finished = NULL;\n    assert_download_not_possible(&SIMPLE_ENV.base->anjay->downloader,\n                                 &SIMPLE_ENV.cfg);\n\n    SIMPLE_ENV.cfg.on_next_block = NULL;\n    SIMPLE_ENV.cfg.on_download_finished = on_download_finished;\n    assert_download_not_possible(&SIMPLE_ENV.base->anjay->downloader,\n                                 &SIMPLE_ENV.cfg);\n\n    SIMPLE_ENV.cfg.on_next_block = on_next_block;\n    SIMPLE_ENV.cfg.on_download_finished = NULL;\n    assert_download_not_possible(&SIMPLE_ENV.base->anjay->downloader,\n                                 &SIMPLE_ENV.cfg);\n\n    teardown_simple();\n}\n\n#define DESPAIR                                                      \\\n    \"Despair is when you're debugging a kernel driver and you look \" \\\n    \"at a memory dump and you see that a pointer has a value of 7.\"\n\nstatic void expect_download_single_block(avs_net_socket_t *socket,\n                                         void *dummy) {\n    assert(socket == SIMPLE_ENV.mocksock);\n    (void) dummy;\n    const coap_test_msg_t *req =\n            COAP_MSG(CON, GET, ID_TOKEN_RAW(0, nth_token(0)), NO_PAYLOAD);\n    const coap_test_msg_t *res =\n            COAP_MSG(ACK, CONTENT, ID_TOKEN_RAW(0, nth_token(0)),\n                     BLOCK2(0, 128, DESPAIR));\n\n    avs_unit_mocksock_expect_output(SIMPLE_ENV.mocksock, &req->content,\n                                    req->length);\n    avs_unit_mocksock_input(SIMPLE_ENV.mocksock, &res->content, res->length);\n    expect_has_buffered_data_check(SIMPLE_ENV.mocksock, false);\n}\n\nAVS_UNIT_TEST(downloader, coap_download_single_block) {\n    setup_simple(\"coap://127.0.0.1:5683\");\n\n    avs_unit_mocksock_expect_shutdown(SIMPLE_ENV.mocksock);\n    avs_unit_mocksock_expect_mid_close(SIMPLE_ENV.mocksock);\n    avs_unit_mocksock_expect_connect(SIMPLE_ENV.mocksock, \"127.0.0.1\", \"5683\",\n                                     .and_then = expect_download_single_block);\n\n    // expect handler calls\n    expect_next_block(&SIMPLE_ENV.data,\n                      (on_next_block_args_t) {\n                          .data = DESPAIR,\n                          .data_size = sizeof(DESPAIR) - 1,\n                          .result = AVS_OK\n                      });\n    expect_download_finished(&SIMPLE_ENV.data,\n                             _anjay_download_status_success());\n\n    perform_simple_download();\n\n    teardown_simple();\n}\n\nstatic void expect_download_multiple_blocks(avs_net_socket_t *socket,\n                                            void *dummy) {\n    assert(socket == SIMPLE_ENV.mocksock);\n    (void) dummy;\n\n    static const size_t BLOCK_SIZE = 16;\n\n    size_t num_blocks = DIV_CEIL(sizeof(DESPAIR) - 1, BLOCK_SIZE);\n    for (size_t i = 0; i < num_blocks; ++i) {\n        const coap_test_msg_t *req =\n                i == 0 ? COAP_MSG(CON, GET, ID_TOKEN_RAW(i, nth_token(i)),\n                                  NO_PAYLOAD)\n                       : COAP_MSG(CON, GET, ID_TOKEN_RAW(i, nth_token(i)),\n                                  BLOCK2(i, BLOCK_SIZE, \"\"));\n        const coap_test_msg_t *res =\n                COAP_MSG(ACK, CONTENT, ID_TOKEN_RAW(i, nth_token(i)),\n                         BLOCK2(i, BLOCK_SIZE, DESPAIR));\n\n        avs_unit_mocksock_expect_output(SIMPLE_ENV.mocksock, &req->content,\n                                        req->length);\n        avs_unit_mocksock_input(SIMPLE_ENV.mocksock, &res->content,\n                                res->length);\n\n        bool is_last_block = (i + 1) * BLOCK_SIZE >= sizeof(DESPAIR) - 1;\n        size_t size = is_last_block ? sizeof(DESPAIR) - 1 - i * BLOCK_SIZE\n                                    : BLOCK_SIZE;\n\n        on_next_block_args_t args = {\n            .data_size = size,\n            .result = AVS_OK\n        };\n        memcpy(args.data, &DESPAIR[i * BLOCK_SIZE], size);\n        expect_next_block(&SIMPLE_ENV.data, args);\n\n        expect_has_buffered_data_check(SIMPLE_ENV.mocksock, !is_last_block);\n        if (is_last_block) {\n            expect_download_finished(&SIMPLE_ENV.data,\n                                     _anjay_download_status_success());\n        }\n    }\n}\n\nAVS_UNIT_TEST(downloader, coap_download_multiple_blocks) {\n    setup_simple(\"coap://127.0.0.1:5683\");\n\n    // setup expects\n    avs_unit_mocksock_expect_shutdown(SIMPLE_ENV.mocksock);\n    avs_unit_mocksock_expect_mid_close(SIMPLE_ENV.mocksock);\n    avs_unit_mocksock_expect_connect(SIMPLE_ENV.mocksock, \"127.0.0.1\", \"5683\",\n                                     .and_then =\n                                             expect_download_multiple_blocks);\n\n    perform_simple_download();\n\n    teardown_simple();\n}\n\nAVS_UNIT_TEST(downloader, download_abort_on_cleanup) {\n    setup_simple(\"coap://127.0.0.1:5683\");\n\n    anjay_download_handle_t handle = NULL;\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_downloader_download(&SIMPLE_ENV.base->anjay->downloader,\n                                       &handle, &SIMPLE_ENV.cfg, NULL, NULL\n\n                                       ));\n    AVS_UNIT_ASSERT_NOT_NULL(handle);\n\n    expect_download_finished(&SIMPLE_ENV.data,\n                             _anjay_download_status_aborted());\n    _anjay_downloader_cleanup(&SIMPLE_ENV.base->anjay->downloader);\n\n    teardown_simple();\n}\n\nstatic void expect_download_abort_on_reset_response(avs_net_socket_t *socket,\n                                                    void *dummy) {\n    assert(socket == SIMPLE_ENV.mocksock);\n    (void) dummy;\n\n    // expect packets\n    const coap_test_msg_t *req =\n            COAP_MSG(CON, GET, ID_TOKEN_RAW(0, nth_token(0)), NO_PAYLOAD);\n    const coap_test_msg_t *res = COAP_MSG(RST, EMPTY, ID(0), NO_PAYLOAD);\n\n    avs_unit_mocksock_expect_output(SIMPLE_ENV.mocksock, &req->content,\n                                    req->length);\n    avs_unit_mocksock_input(SIMPLE_ENV.mocksock, &res->content, res->length);\n    expect_has_buffered_data_check(SIMPLE_ENV.mocksock, false);\n}\n\nAVS_UNIT_TEST(downloader, download_abort_on_reset_response) {\n    setup_simple(\"coap://127.0.0.1:5683\");\n\n    avs_unit_mocksock_expect_shutdown(SIMPLE_ENV.mocksock);\n    avs_unit_mocksock_expect_mid_close(SIMPLE_ENV.mocksock);\n    avs_unit_mocksock_expect_connect(\n            SIMPLE_ENV.mocksock, \"127.0.0.1\", \"5683\",\n            .and_then = expect_download_abort_on_reset_response);\n\n    // expect handler calls\n    expect_download_finished(&SIMPLE_ENV.data,\n                             _anjay_download_status_failed((avs_error_t) {\n                                 .category = AVS_COAP_ERR_CATEGORY,\n                                 .code = AVS_COAP_ERR_UDP_RESET_RECEIVED\n                             }));\n\n    perform_simple_download();\n\n    teardown_simple();\n}\n\nAVS_UNIT_TEST(downloader, unsupported_protocol) {\n    setup_simple(\"gopher://127.0.0.1:5683\");\n\n    anjay_download_handle_t handle = NULL;\n    avs_error_t err =\n            _anjay_downloader_download(&SIMPLE_ENV.base->anjay->downloader,\n                                       &handle, &SIMPLE_ENV.cfg, NULL, NULL);\n    AVS_UNIT_ASSERT_EQUAL(err.category, AVS_ERRNO_CATEGORY);\n    AVS_UNIT_ASSERT_EQUAL(err.code, AVS_EPROTONOSUPPORT);\n    AVS_UNIT_ASSERT_NULL(handle);\n\n    teardown_simple();\n}\n\nAVS_UNIT_TEST(downloader, unrelated_socket) {\n    setup();\n\n    AVS_UNIT_ASSERT_FAILED(_anjay_downloader_handle_packet(\n            &ENV.anjay->downloader, ENV.mocksock[0]));\n\n    teardown();\n}\n\nstatic void expect_download_separate_response(avs_net_socket_t *socket,\n                                              void *dummy) {\n    assert(socket == SIMPLE_ENV.mocksock);\n    (void) dummy;\n\n    // expect packets\n    const coap_test_msg_t *req =\n            COAP_MSG(CON, GET, ID_TOKEN_RAW(0, nth_token(0)), NO_PAYLOAD);\n    const coap_test_msg_t *res =\n            COAP_MSG(CON, CONTENT, ID_TOKEN_RAW(1, nth_token(0)),\n                     BLOCK2(0, 128, DESPAIR));\n    const coap_test_msg_t *res_res = COAP_MSG(ACK, EMPTY, ID(1), NO_PAYLOAD);\n\n    avs_unit_mocksock_expect_output(SIMPLE_ENV.mocksock, &req->content,\n                                    req->length);\n    avs_unit_mocksock_input(SIMPLE_ENV.mocksock, &res->content, res->length);\n    avs_unit_mocksock_expect_output(SIMPLE_ENV.mocksock, &res_res->content,\n                                    res_res->length);\n    expect_has_buffered_data_check(SIMPLE_ENV.mocksock, false);\n}\n\nAVS_UNIT_TEST(downloader, coap_download_separate_response) {\n    setup_simple(\"coap://127.0.0.1:5683\");\n\n    avs_unit_mocksock_expect_shutdown(SIMPLE_ENV.mocksock);\n    avs_unit_mocksock_expect_mid_close(SIMPLE_ENV.mocksock);\n    avs_unit_mocksock_expect_connect(SIMPLE_ENV.mocksock, \"127.0.0.1\", \"5683\",\n                                     .and_then =\n                                             expect_download_separate_response);\n\n    // expect handler calls\n    expect_next_block(&SIMPLE_ENV.data,\n                      (on_next_block_args_t) {\n                          .data = DESPAIR,\n                          .data_size = sizeof(DESPAIR) - 1,\n                          .result = AVS_OK\n                      });\n    expect_download_finished(&SIMPLE_ENV.data,\n                             _anjay_download_status_success());\n\n    perform_simple_download();\n\n    teardown_simple();\n}\n\nstatic void expect_download_unexpected_packet(avs_net_socket_t *socket,\n                                              void *dummy) {\n    assert(socket == SIMPLE_ENV.mocksock);\n    (void) dummy;\n\n    // expect packets\n    const coap_test_msg_t *req =\n            COAP_MSG(CON, GET, ID_TOKEN_RAW(0, nth_token(0)), NO_PAYLOAD);\n    const coap_test_msg_t *unk1 = COAP_MSG(RST, CONTENT, ID(1), NO_PAYLOAD);\n    const coap_test_msg_t *unk2 = COAP_MSG(NON, CONTENT, ID(2), NO_PAYLOAD);\n    const coap_test_msg_t *res =\n            COAP_MSG(ACK, CONTENT, ID_TOKEN_RAW(0, nth_token(0)),\n                     BLOCK2(0, 128, DESPAIR));\n\n    avs_unit_mocksock_expect_output(SIMPLE_ENV.mocksock, &req->content,\n                                    req->length);\n    avs_unit_mocksock_input(SIMPLE_ENV.mocksock, &unk1->content, unk1->length);\n    expect_has_buffered_data_check(SIMPLE_ENV.mocksock, true);\n    avs_unit_mocksock_input(SIMPLE_ENV.mocksock, &unk2->content, unk2->length);\n    expect_has_buffered_data_check(SIMPLE_ENV.mocksock, true);\n    avs_unit_mocksock_input(SIMPLE_ENV.mocksock, &res->content, res->length);\n    expect_has_buffered_data_check(SIMPLE_ENV.mocksock, false);\n}\n\nAVS_UNIT_TEST(downloader, coap_download_unexpected_packet) {\n    setup_simple(\"coap://127.0.0.1:5683\");\n\n    avs_unit_mocksock_expect_shutdown(SIMPLE_ENV.mocksock);\n    avs_unit_mocksock_expect_mid_close(SIMPLE_ENV.mocksock);\n    avs_unit_mocksock_expect_connect(SIMPLE_ENV.mocksock, \"127.0.0.1\", \"5683\",\n                                     .and_then =\n                                             expect_download_unexpected_packet);\n\n    // expect handler calls\n    expect_next_block(&SIMPLE_ENV.data,\n                      (on_next_block_args_t) {\n                          .data = DESPAIR,\n                          .data_size = sizeof(DESPAIR) - 1,\n                          .result = AVS_OK\n                      });\n    expect_download_finished(&SIMPLE_ENV.data,\n                             _anjay_download_status_success());\n\n    perform_simple_download();\n\n    teardown_simple();\n}\n\nstatic void expect_download_abort_from_handler(avs_net_socket_t *socket,\n                                               void *dummy) {\n    assert(socket == SIMPLE_ENV.mocksock);\n    (void) dummy;\n\n    // expect packets\n    const coap_test_msg_t *req =\n            COAP_MSG(CON, GET, ID_TOKEN_RAW(0, nth_token(0)), NO_PAYLOAD);\n    const coap_test_msg_t *res =\n            COAP_MSG(ACK, CONTENT, ID_TOKEN_RAW(0, nth_token(0)),\n                     BLOCK2(0, 128, DESPAIR));\n\n    avs_unit_mocksock_expect_output(SIMPLE_ENV.mocksock, &req->content,\n                                    req->length);\n    avs_unit_mocksock_input(SIMPLE_ENV.mocksock, &res->content, res->length);\n    expect_has_buffered_data_check(SIMPLE_ENV.mocksock, false);\n}\n\nAVS_UNIT_TEST(downloader, coap_download_abort_from_handler) {\n    setup_simple(\"coap://127.0.0.1:5683\");\n\n    avs_unit_mocksock_expect_shutdown(SIMPLE_ENV.mocksock);\n    avs_unit_mocksock_expect_mid_close(SIMPLE_ENV.mocksock);\n    avs_unit_mocksock_expect_connect(\n            SIMPLE_ENV.mocksock, \"127.0.0.1\", \"5683\",\n            .and_then = expect_download_abort_from_handler);\n\n    // expect handler calls\n    expect_next_block(&SIMPLE_ENV.data,\n                      (on_next_block_args_t) {\n                          .data = DESPAIR,\n                          .data_size = sizeof(DESPAIR) - 1,\n                          .result = avs_errno(AVS_EINTR) // request abort\n                      });\n    expect_download_finished(&SIMPLE_ENV.data,\n                             _anjay_download_status_failed(\n                                     avs_errno(AVS_EINTR)));\n\n    perform_simple_download();\n\n    teardown_simple();\n}\n\nstatic void expect_download_expired(avs_net_socket_t *socket, void *dummy) {\n    assert(socket == SIMPLE_ENV.mocksock);\n    (void) dummy;\n\n    // expect packets\n    const coap_test_msg_t *req1 =\n            COAP_MSG(CON, GET, ID_TOKEN_RAW(0, nth_token(0)), NO_PAYLOAD);\n    const coap_test_msg_t *res1 =\n            COAP_MSG(ACK, CONTENT, ID_TOKEN_RAW(0, nth_token(0)), ETAG(\"tag\"),\n                     BLOCK2(0, 64, DESPAIR));\n\n    const coap_test_msg_t *req2 =\n            COAP_MSG(CON, GET, ID_TOKEN_RAW(1, nth_token(1)),\n                     BLOCK2(1, 64, \"\"));\n    const coap_test_msg_t *res2 =\n            COAP_MSG(ACK, CONTENT, ID_TOKEN_RAW(1, nth_token(1)), ETAG(\"nje\"),\n                     BLOCK2(1, 64, DESPAIR));\n\n    avs_unit_mocksock_expect_output(SIMPLE_ENV.mocksock, &req1->content,\n                                    req1->length);\n    avs_unit_mocksock_input(SIMPLE_ENV.mocksock, &res1->content, res1->length);\n    expect_has_buffered_data_check(SIMPLE_ENV.mocksock, true);\n    avs_unit_mocksock_expect_output(SIMPLE_ENV.mocksock, &req2->content,\n                                    req2->length);\n    avs_unit_mocksock_input(SIMPLE_ENV.mocksock, &res2->content, res2->length);\n    expect_has_buffered_data_check(SIMPLE_ENV.mocksock, false);\n}\n\nAVS_UNIT_TEST(downloader, coap_download_expired) {\n    setup_simple(\"coap://127.0.0.1:5683\");\n\n    avs_unit_mocksock_expect_shutdown(SIMPLE_ENV.mocksock);\n    avs_unit_mocksock_expect_mid_close(SIMPLE_ENV.mocksock);\n    avs_unit_mocksock_expect_connect(SIMPLE_ENV.mocksock, \"127.0.0.1\", \"5683\",\n                                     .and_then = expect_download_expired);\n\n    static const avs_coap_etag_t etag = {\n        .size = 3,\n        .bytes = \"tag\"\n    };\n    // expect handler calls\n    expect_next_block(&SIMPLE_ENV.data,\n                      (on_next_block_args_t) {\n                          .data = DESPAIR,\n                          .data_size = 64,\n                          .etag = (const anjay_etag_t *) &etag,\n                          .result = AVS_OK // request abort\n                      });\n    expect_download_finished(&SIMPLE_ENV.data,\n                             _anjay_download_status_expired());\n\n    perform_simple_download();\n\n    teardown_simple();\n}\n\nAVS_UNIT_TEST(downloader, buffer_too_small_to_download) {\n    setup_simple(\"coap://127.0.0.1:5683\");\n\n    size_t new_capacity = 3;\n    memcpy((void *) (intptr_t) &SIMPLE_ENV.base->anjay->out_shared_buffer\n                   ->capacity,\n           &new_capacity, sizeof(new_capacity));\n    avs_unit_mocksock_expect_shutdown(SIMPLE_ENV.mocksock);\n    avs_unit_mocksock_expect_mid_close(SIMPLE_ENV.mocksock);\n    avs_unit_mocksock_expect_connect(SIMPLE_ENV.mocksock, \"127.0.0.1\", \"5683\");\n\n    anjay_download_handle_t handle = NULL;\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_downloader_download(&SIMPLE_ENV.base->anjay->downloader,\n                                       &handle, &SIMPLE_ENV.cfg, NULL, NULL));\n    AVS_UNIT_ASSERT_NOT_NULL(handle);\n\n    expect_download_finished(&SIMPLE_ENV.data,\n                             _anjay_download_status_failed((avs_error_t) {\n                                 .category = AVS_COAP_ERR_CATEGORY,\n                                 .code = AVS_COAP_ERR_MESSAGE_TOO_BIG\n                             }));\n    ANJAY_MUTEX_UNLOCK_FOR_CALLBACK(anjay_locked, SIMPLE_ENV.base->anjay);\n    while (avs_time_duration_equal(avs_sched_time_to_next(\n                                           SIMPLE_ENV.base->anjay->sched),\n                                   AVS_TIME_DURATION_ZERO)) {\n        avs_sched_run(SIMPLE_ENV.base->anjay->sched);\n    }\n    ANJAY_MUTEX_LOCK_AFTER_CALLBACK(anjay_locked);\n\n    teardown_simple();\n}\n\nstatic void expect_single_req(avs_net_socket_t *socket, void *req_) {\n    assert(socket == SIMPLE_ENV.mocksock);\n    const coap_test_msg_t *req = (const coap_test_msg_t *) req_;\n    avs_unit_mocksock_expect_output(SIMPLE_ENV.mocksock, &req->content,\n                                    req->length);\n}\n\nAVS_UNIT_TEST(downloader, retry) {\n    setup_simple(\"coap://127.0.0.1:5683\");\n\n    const coap_test_msg_t *req =\n            COAP_MSG(CON, GET, ID_TOKEN_RAW(0, nth_token(0)), NO_PAYLOAD);\n    const coap_test_msg_t *res =\n            COAP_MSG(ACK, CONTENT, ID_TOKEN_RAW(0, nth_token(0)), ETAG(\"tag\"),\n                     BLOCK2(0, 128, DESPAIR));\n\n    avs_unit_mocksock_expect_shutdown(SIMPLE_ENV.mocksock);\n    avs_unit_mocksock_expect_mid_close(SIMPLE_ENV.mocksock);\n    avs_unit_mocksock_expect_connect(SIMPLE_ENV.mocksock, \"127.0.0.1\", \"5683\",\n                                     .and_then = expect_single_req,\n                                     .and_then_arg = (void *) (intptr_t) req);\n\n    anjay_download_handle_t handle = NULL;\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_downloader_download(&SIMPLE_ENV.base->anjay->downloader,\n                                       &handle, &SIMPLE_ENV.cfg, NULL, NULL));\n    AVS_UNIT_ASSERT_NOT_NULL(handle);\n\n    // initial request\n    ANJAY_MUTEX_UNLOCK_FOR_CALLBACK(anjay_locked, SIMPLE_ENV.base->anjay);\n    while (avs_time_duration_equal(avs_sched_time_to_next(\n                                           SIMPLE_ENV.base->anjay->sched),\n                                   AVS_TIME_DURATION_ZERO)) {\n        avs_sched_run(SIMPLE_ENV.base->anjay->sched);\n    }\n\n    // request retransmissions\n    avs_time_duration_t last_time_to_next = AVS_TIME_DURATION_INVALID;\n    for (size_t i = 0; i < 4; ++i) {\n        // make sure there's a retransmission job scheduled\n        avs_time_duration_t time_to_next =\n                avs_sched_time_to_next(SIMPLE_ENV.base->anjay->sched);\n        AVS_UNIT_ASSERT_TRUE(avs_time_duration_valid(time_to_next));\n        _anjay_mock_clock_advance(time_to_next);\n\n        avs_unit_mocksock_expect_output(SIMPLE_ENV.mocksock, &req->content,\n                                        req->length);\n        avs_sched_run(SIMPLE_ENV.base->anjay->sched);\n\n        // ...and it's roughly exponential backoff\n        if (avs_time_duration_valid(last_time_to_next)) {\n            double ratio =\n                    avs_time_duration_to_fscalar(time_to_next, AVS_TIME_S)\n                    / avs_time_duration_to_fscalar(last_time_to_next,\n                                                   AVS_TIME_S);\n            ASSERT_ALMOST_EQ(ratio, 2.0);\n        }\n        last_time_to_next = time_to_next;\n    }\n    ANJAY_MUTEX_LOCK_AFTER_CALLBACK(anjay_locked);\n\n    // handle response\n    avs_unit_mocksock_input(SIMPLE_ENV.mocksock, &res->content, res->length);\n    expect_has_buffered_data_check(SIMPLE_ENV.mocksock, false);\n\n    static const avs_coap_etag_t etag = {\n        .size = 3,\n        .bytes = \"tag\"\n    };\n    expect_next_block(&SIMPLE_ENV.data,\n                      (on_next_block_args_t) {\n                          .data = DESPAIR,\n                          .data_size = sizeof(DESPAIR) - 1,\n                          .etag = (const anjay_etag_t *) &etag,\n                          .result = AVS_OK // request abort\n                      });\n    expect_download_finished(&SIMPLE_ENV.data,\n                             _anjay_download_status_success());\n\n    handle_packet();\n\n    // TODO: remove after T2217.\n    // CoAP context cleanup. It's a side effect of a hack in\n    // coap.c:cleanup_coap_transfer().\n    ANJAY_MUTEX_UNLOCK_FOR_CALLBACK(anjay_locked, SIMPLE_ENV.base->anjay);\n    avs_sched_run(SIMPLE_ENV.base->anjay->sched);\n    ANJAY_MUTEX_LOCK_AFTER_CALLBACK(anjay_locked);\n\n    // retransmission job should be canceled\n    AVS_UNIT_ASSERT_FALSE(avs_time_duration_valid(\n            avs_sched_time_to_next(SIMPLE_ENV.base->anjay->sched)));\n\n    avs_unit_mocksock_assert_expects_met(SIMPLE_ENV.mocksock);\n\n    teardown_simple();\n}\n\nAVS_UNIT_TEST(downloader, missing_separate_response) {\n    setup_simple(\"coap://127.0.0.1:5683\");\n\n    const coap_test_msg_t *req =\n            COAP_MSG(CON, GET, ID_TOKEN_RAW(0, nth_token(0)), NO_PAYLOAD);\n    const coap_test_msg_t *req_ack = COAP_MSG(ACK, EMPTY, ID(0), NO_PAYLOAD);\n\n    avs_unit_mocksock_expect_shutdown(SIMPLE_ENV.mocksock);\n    avs_unit_mocksock_expect_mid_close(SIMPLE_ENV.mocksock);\n    avs_unit_mocksock_expect_connect(SIMPLE_ENV.mocksock, \"127.0.0.1\", \"5683\",\n                                     .and_then = expect_single_req,\n                                     .and_then_arg = (void *) (intptr_t) req);\n\n    anjay_download_handle_t handle = NULL;\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_downloader_download(&SIMPLE_ENV.base->anjay->downloader,\n                                       &handle, &SIMPLE_ENV.cfg, NULL, NULL));\n    AVS_UNIT_ASSERT_NOT_NULL(handle);\n\n    // initial request\n    ANJAY_MUTEX_UNLOCK_FOR_CALLBACK(anjay_locked, SIMPLE_ENV.base->anjay);\n    while (avs_time_duration_equal(avs_sched_time_to_next(\n                                           SIMPLE_ENV.base->anjay->sched),\n                                   AVS_TIME_DURATION_ZERO)) {\n        avs_sched_run(SIMPLE_ENV.base->anjay->sched);\n    }\n    ANJAY_MUTEX_LOCK_AFTER_CALLBACK(anjay_locked);\n\n    // retransmission job should be scheduled\n    avs_time_duration_t time_to_next =\n            avs_sched_time_to_next(SIMPLE_ENV.base->anjay->sched);\n    AVS_UNIT_ASSERT_TRUE(avs_time_duration_to_fscalar(time_to_next, AVS_TIME_S)\n                         < 5.0);\n\n    // separate ACK\n    avs_unit_mocksock_input(SIMPLE_ENV.mocksock, &req_ack->content,\n                            req_ack->length);\n    expect_has_buffered_data_check(SIMPLE_ENV.mocksock, false);\n    AVS_UNIT_ASSERT_SUCCESS(handle_packet());\n\n    time_to_next = avs_sched_time_to_next(SIMPLE_ENV.base->anjay->sched);\n    AVS_UNIT_ASSERT_TRUE(avs_time_duration_valid(time_to_next));\n\n    // no separate response should abort the transfer after\n    // EXCHANGE_LIFETIME\n    expect_download_finished(&SIMPLE_ENV.data,\n                             _anjay_download_status_failed((avs_error_t) {\n                                 .category = AVS_COAP_ERR_CATEGORY,\n                                 .code = AVS_COAP_ERR_TIMEOUT\n                             }));\n\n    // abort job should be scheduled to run after EXCHANGE_LIFETIME\n    _anjay_mock_clock_advance(\n            avs_coap_udp_exchange_lifetime(&DETERMINISTIC_TX_PARAMS));\n    ANJAY_MUTEX_UNLOCK_FOR_CALLBACK(anjay_locked, SIMPLE_ENV.base->anjay);\n    avs_sched_run(SIMPLE_ENV.base->anjay->sched);\n    ANJAY_MUTEX_LOCK_AFTER_CALLBACK(anjay_locked);\n\n    avs_unit_mocksock_assert_expects_met(SIMPLE_ENV.mocksock);\n\n    teardown_simple();\n}\n\nstatic size_t num_downloads_in_progress(void) {\n    AVS_LIST(anjay_socket_entry_t) sock = NULL;\n    _anjay_downloader_get_sockets(&SIMPLE_ENV.base->anjay->downloader, &sock,\n                                  /* include_offline = */ false);\n    size_t result = AVS_LIST_SIZE(sock);\n    AVS_LIST_CLEAR(&sock);\n    return result;\n}\n\nAVS_UNIT_TEST(downloader, abort_coap) {\n    setup_simple(\"coap://127.0.0.1:5683\");\n\n    anjay_download_handle_t handle = NULL;\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_downloader_download(&SIMPLE_ENV.base->anjay->downloader,\n                                       &handle, &SIMPLE_ENV.cfg, NULL, NULL));\n    AVS_UNIT_ASSERT_NOT_NULL(handle);\n\n    // start_download_job is scheduled\n    AVS_UNIT_ASSERT_TRUE(avs_time_duration_valid(\n            avs_sched_time_to_next(SIMPLE_ENV.base->anjay->sched)));\n\n    expect_download_finished(&SIMPLE_ENV.data,\n                             _anjay_download_status_aborted());\n    _anjay_downloader_abort(&SIMPLE_ENV.base->anjay->downloader, handle);\n\n    // start_download_job is canceled\n    AVS_UNIT_ASSERT_FALSE(avs_time_duration_valid(\n            avs_sched_time_to_next(SIMPLE_ENV.base->anjay->sched)));\n    AVS_UNIT_ASSERT_EQUAL(0, num_downloads_in_progress());\n\n    teardown_simple();\n}\n\n#ifdef ANJAY_WITH_HTTP_DOWNLOAD\nAVS_UNIT_TEST(downloader, abort_http) {\n    setup_simple(\"http://127.0.0.1\");\n\n    anjay_download_handle_t handle = NULL;\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_downloader_download(&SIMPLE_ENV.base->anjay->downloader,\n                                       &handle, &SIMPLE_ENV.cfg, NULL, NULL));\n    AVS_UNIT_ASSERT_NOT_NULL(handle);\n\n    // start_download_job is scheduled\n    AVS_UNIT_ASSERT_TRUE(avs_time_duration_valid(\n            avs_sched_time_to_next(SIMPLE_ENV.base->anjay->sched)));\n\n    expect_download_finished(&SIMPLE_ENV.data,\n                             _anjay_download_status_aborted());\n    _anjay_downloader_abort(&SIMPLE_ENV.base->anjay->downloader, handle);\n\n    // cleanup_http_stream is run through the scheduler\n    ANJAY_MUTEX_UNLOCK_FOR_CALLBACK(anjay_locked, SIMPLE_ENV.base->anjay);\n    avs_sched_run(SIMPLE_ENV.base->anjay->sched);\n    ANJAY_MUTEX_LOCK_AFTER_CALLBACK(anjay_locked);\n\n    // start_download_job is canceled\n    AVS_UNIT_ASSERT_FALSE(avs_time_duration_valid(\n            avs_sched_time_to_next(SIMPLE_ENV.base->anjay->sched)));\n    AVS_UNIT_ASSERT_EQUAL(0, num_downloads_in_progress());\n\n    teardown_simple();\n}\n#endif // ANJAY_WITH_HTTP_DOWNLOAD\n\nstatic void expect_uri_path_query(avs_net_socket_t *socket, void *dummy) {\n    assert(socket == SIMPLE_ENV.mocksock);\n    (void) dummy;\n\n    // expect packets\n    const coap_test_msg_t *req =\n            COAP_MSG(CON, GET, ID_TOKEN_RAW(0, nth_token(0)),\n                     PATH(\"uri\", \"path\"), QUERY(\"query=string\", \"another\"),\n                     NO_PAYLOAD);\n    const coap_test_msg_t *res =\n            COAP_MSG(ACK, CONTENT, ID_TOKEN_RAW(0, nth_token(0)),\n                     BLOCK2(0, 128, DESPAIR));\n\n    avs_unit_mocksock_expect_output(SIMPLE_ENV.mocksock, &req->content,\n                                    req->length);\n    avs_unit_mocksock_input(SIMPLE_ENV.mocksock, &res->content, res->length);\n    expect_has_buffered_data_check(SIMPLE_ENV.mocksock, false);\n}\n\nAVS_UNIT_TEST(downloader, uri_path_query) {\n    setup_simple(\"coap://127.0.0.1:5683/uri/path?query=string&another\");\n\n    avs_unit_mocksock_expect_shutdown(SIMPLE_ENV.mocksock);\n    avs_unit_mocksock_expect_mid_close(SIMPLE_ENV.mocksock);\n    avs_unit_mocksock_expect_connect(SIMPLE_ENV.mocksock, \"127.0.0.1\", \"5683\",\n                                     .and_then = expect_uri_path_query);\n\n    // expect handler calls\n    expect_next_block(&SIMPLE_ENV.data,\n                      (on_next_block_args_t) {\n                          .data = DESPAIR,\n                          .data_size = sizeof(DESPAIR) - 1,\n                          .result = AVS_OK\n                      });\n    expect_download_finished(&SIMPLE_ENV.data,\n                             _anjay_download_status_success());\n\n    perform_simple_download();\n\n    teardown_simple();\n}\n\nstatic void expect_in_buffer_size_enforces_smaller_initial_block_size(\n        avs_net_socket_t *socket, void *dummy) {\n    assert(socket == SIMPLE_ENV.mocksock);\n    (void) dummy;\n\n    // expect packets\n    const coap_test_msg_t *req =\n            COAP_MSG(CON, GET, ID_TOKEN_RAW(0, nth_token(0)), NO_PAYLOAD);\n    const coap_test_msg_t *res =\n            COAP_MSG(ACK, CONTENT, ID_TOKEN_RAW(0, nth_token(0)),\n                     BLOCK2(0, 128, DESPAIR));\n\n    avs_unit_mocksock_expect_output(SIMPLE_ENV.mocksock, &req->content,\n                                    req->length);\n    avs_unit_mocksock_input(SIMPLE_ENV.mocksock, &res->content, res->length);\n    expect_has_buffered_data_check(SIMPLE_ENV.mocksock, false);\n}\n\nAVS_UNIT_TEST(downloader, in_buffer_size_enforces_smaller_initial_block_size) {\n    setup_simple(\"coap://127.0.0.1:5683\");\n\n    // the downloader should realize it cannot hold blocks bigger than 128\n    // bytes and request that size\n    size_t new_capacity = 256;\n    memcpy((void *) (intptr_t) &SIMPLE_ENV.base->anjay->in_shared_buffer\n                   ->capacity,\n           &new_capacity, sizeof(new_capacity));\n\n    avs_unit_mocksock_expect_shutdown(SIMPLE_ENV.mocksock);\n    avs_unit_mocksock_expect_mid_close(SIMPLE_ENV.mocksock);\n    avs_unit_mocksock_expect_connect(\n            SIMPLE_ENV.mocksock, \"127.0.0.1\", \"5683\",\n            .and_then =\n                    expect_in_buffer_size_enforces_smaller_initial_block_size);\n\n    // expect handler calls\n    expect_next_block(&SIMPLE_ENV.data,\n                      (on_next_block_args_t) {\n                          .data = DESPAIR,\n                          .data_size = sizeof(DESPAIR) - 1,\n                          .result = AVS_OK\n                      });\n    expect_download_finished(&SIMPLE_ENV.data,\n                             _anjay_download_status_success());\n\n    perform_simple_download();\n\n    teardown_simple();\n}\n\nstatic void expect_renegotiation_while_requesting_more_than_available(\n        avs_net_socket_t *socket, void *dummy) {\n    assert(socket == SIMPLE_ENV.mocksock);\n    (void) dummy;\n\n    // We request as much as we can (i.e. 1024 bytes)\n    const coap_test_msg_t *req =\n            COAP_MSG(CON, GET, ID_TOKEN_RAW(0, nth_token(0)), NO_PAYLOAD);\n\n    // However, the server responds with 128 bytes only, which triggers\n    // block size negotiation logic\n    const coap_test_msg_t *res =\n            COAP_MSG(ACK, CONTENT, ID_TOKEN_RAW(0, nth_token(0)),\n                     BLOCK2(0, 128, DESPAIR));\n\n    avs_unit_mocksock_expect_output(SIMPLE_ENV.mocksock, &req->content,\n                                    req->length);\n    avs_unit_mocksock_input(SIMPLE_ENV.mocksock, &res->content, res->length);\n    expect_has_buffered_data_check(SIMPLE_ENV.mocksock, false);\n}\n\nAVS_UNIT_TEST(downloader, renegotiation_while_requesting_more_than_available) {\n    setup_simple(\"coap://127.0.0.1:5683\");\n\n    avs_unit_mocksock_expect_shutdown(SIMPLE_ENV.mocksock);\n    avs_unit_mocksock_expect_mid_close(SIMPLE_ENV.mocksock);\n    avs_unit_mocksock_expect_connect(\n            SIMPLE_ENV.mocksock, \"127.0.0.1\", \"5683\",\n            .and_then =\n                    expect_renegotiation_while_requesting_more_than_available);\n\n    // expect handler calls\n    expect_next_block(&SIMPLE_ENV.data,\n                      (on_next_block_args_t) {\n                          .data = DESPAIR,\n                          .data_size = sizeof(DESPAIR) - 1,\n                          .result = AVS_OK\n                      });\n    expect_download_finished(&SIMPLE_ENV.data,\n                             _anjay_download_status_success());\n\n    perform_simple_download();\n\n    teardown_simple();\n}\n\nstatic void expect_renegotiation_after_first_packet(avs_net_socket_t *socket,\n                                                    void *dummy) {\n    assert(socket == SIMPLE_ENV.mocksock);\n    (void) dummy;\n\n    const coap_test_msg_t *req =\n            COAP_MSG(CON, GET, ID_TOKEN_RAW(0, nth_token(0)), NO_PAYLOAD);\n\n    // The server responds with 64 bytes of the first block\n    const coap_test_msg_t *res =\n            COAP_MSG(ACK, CONTENT, ID_TOKEN_RAW(0, nth_token(0)),\n                     BLOCK2(0, 64, DESPAIR));\n\n    avs_unit_mocksock_expect_output(SIMPLE_ENV.mocksock, &req->content,\n                                    req->length);\n    avs_unit_mocksock_input(SIMPLE_ENV.mocksock, &res->content, res->length);\n    expect_has_buffered_data_check(SIMPLE_ENV.mocksock, true);\n\n    // We then request another block with negotiated 64 bytes\n    req = COAP_MSG(CON, GET, ID_TOKEN_RAW(1, nth_token(1)), BLOCK2(1, 64, \"\"));\n    // But the server is weird, and responds with an even smaller block size\n    // with a different seq-num that is however valid in terms of offset,\n    // i.e. it has seq_num=2 which corresponds to the data past the first 64\n    // bytes\n    res = COAP_MSG(ACK, CONTENT, ID_TOKEN_RAW(1, nth_token(1)),\n                   BLOCK2(2, 32, DESPAIR));\n    avs_unit_mocksock_expect_output(SIMPLE_ENV.mocksock, &req->content,\n                                    req->length);\n    avs_unit_mocksock_input(SIMPLE_ENV.mocksock, &res->content, res->length);\n    expect_has_buffered_data_check(SIMPLE_ENV.mocksock, true);\n\n    // Last block - no surprises this time.\n    req = COAP_MSG(CON, GET, ID_TOKEN_RAW(2, nth_token(2)), BLOCK2(3, 32, \"\"));\n    res = COAP_MSG(ACK, CONTENT, ID_TOKEN_RAW(2, nth_token(2)),\n                   BLOCK2(3, 32, DESPAIR));\n    avs_unit_mocksock_expect_output(SIMPLE_ENV.mocksock, &req->content,\n                                    req->length);\n    avs_unit_mocksock_input(SIMPLE_ENV.mocksock, &res->content, res->length);\n    expect_has_buffered_data_check(SIMPLE_ENV.mocksock, false);\n}\n\nAVS_UNIT_TEST(downloader, renegotiation_after_first_packet) {\n    setup_simple(\"coap://127.0.0.1:5683\");\n\n    avs_unit_mocksock_expect_shutdown(SIMPLE_ENV.mocksock);\n    avs_unit_mocksock_expect_mid_close(SIMPLE_ENV.mocksock);\n    avs_unit_mocksock_expect_connect(\n            SIMPLE_ENV.mocksock, \"127.0.0.1\", \"5683\",\n            .and_then = expect_renegotiation_after_first_packet);\n\n    on_next_block_args_t args;\n    memset(&args, 0, sizeof(args));\n\n    // We request as much as we can (i.e. 64 bytes due to limit of\n    // in_buffer_size)\n    size_t new_capacity = 128;\n    memcpy((void *) (intptr_t) &SIMPLE_ENV.base->anjay->in_shared_buffer\n                   ->capacity,\n           &new_capacity, sizeof(new_capacity));\n\n    memset(args.data, 0, sizeof(args.data));\n    assert(strlen(DESPAIR) > 64);\n    memcpy(args.data, DESPAIR, 64);\n    args.data_size = strlen(args.data);\n    expect_next_block(&SIMPLE_ENV.data, args);\n\n    memset(args.data, 0, sizeof(args.data));\n    strncpy(args.data, DESPAIR + 64, 32);\n    args.data_size = strlen(args.data);\n    expect_next_block(&SIMPLE_ENV.data, args);\n\n    memset(args.data, 0, sizeof(args.data));\n    strncpy(args.data, DESPAIR + 64 + 32, 32);\n    args.data_size = strlen(args.data);\n    expect_next_block(&SIMPLE_ENV.data, args);\n\n    expect_download_finished(&SIMPLE_ENV.data,\n                             _anjay_download_status_success());\n\n    perform_simple_download();\n\n    teardown_simple();\n}\n\nstatic void expect_resumption_at_some_offset(avs_net_socket_t *socket,\n                                             void *offset_) {\n    assert(socket == SIMPLE_ENV.mocksock);\n    size_t offset = (uintptr_t) offset_;\n\n    on_next_block_args_t args;\n    memset(&args, 0, sizeof(args));\n\n    enum { BLOCK_SIZE = 32 };\n\n    size_t current_offset = offset;\n    size_t msg_id = 0;\n    while (sizeof(DESPAIR) - current_offset > 0) {\n        size_t seq_num = current_offset / BLOCK_SIZE;\n        const coap_test_msg_t *req =\n                seq_num == 0 ? COAP_MSG(CON, GET,\n                                        ID_TOKEN_RAW(msg_id, nth_token(msg_id)),\n                                        NO_PAYLOAD)\n                             : COAP_MSG(CON, GET,\n                                        ID_TOKEN_RAW(msg_id, nth_token(msg_id)),\n                                        BLOCK2(seq_num, BLOCK_SIZE, \"\"));\n        const coap_test_msg_t *res =\n                COAP_MSG(ACK, CONTENT, ID_TOKEN_RAW(msg_id, nth_token(msg_id)),\n                         BLOCK2(seq_num, BLOCK_SIZE, DESPAIR));\n        avs_unit_mocksock_expect_output(SIMPLE_ENV.mocksock, &req->content,\n                                        req->length);\n        avs_unit_mocksock_input(SIMPLE_ENV.mocksock, &res->content,\n                                res->length);\n\n        // Copy contents from the current_offset till the end of the\n        // enclosing block.\n        const size_t bytes_till_block_end =\n                AVS_MIN((seq_num + 1) * BLOCK_SIZE - current_offset,\n                        sizeof(DESPAIR) - current_offset);\n\n        memset(args.data, 0, sizeof(args.data));\n        // User handler gets the data from a specified offset, even if\n        // it is pointing at the middle of the block that has to be\n        // received for a given offset.\n        memcpy(args.data, DESPAIR + current_offset, bytes_till_block_end);\n        // See BLOCK2 macro - it ignores terminating '\\0', so strlen()\n        // must be used to compute actual data length.\n        args.data_size = strlen(args.data);\n        expect_next_block(&SIMPLE_ENV.data, args);\n\n        current_offset += bytes_till_block_end;\n        ++msg_id;\n\n        expect_has_buffered_data_check(SIMPLE_ENV.mocksock,\n                                       sizeof(DESPAIR) - current_offset > 0);\n    }\n}\n\nAVS_UNIT_TEST(downloader, resumption_at_some_offset) {\n    for (size_t offset = 0; offset < sizeof(DESPAIR); ++offset) {\n        setup_simple(\"coap://127.0.0.1:5683\");\n\n        avs_unit_mocksock_expect_shutdown(SIMPLE_ENV.mocksock);\n        avs_unit_mocksock_expect_mid_close(SIMPLE_ENV.mocksock);\n        avs_unit_mocksock_expect_connect(\n                SIMPLE_ENV.mocksock, \"127.0.0.1\", \"5683\",\n                .and_then = expect_resumption_at_some_offset,\n                .and_then_arg = (void *) (uintptr_t) offset);\n\n        size_t new_capacity = 64;\n        memcpy((void *) (intptr_t) &SIMPLE_ENV.base->anjay->in_shared_buffer\n                       ->capacity,\n               &new_capacity, sizeof(new_capacity));\n\n        expect_download_finished(&SIMPLE_ENV.data,\n                                 _anjay_download_status_success());\n\n        SIMPLE_ENV.cfg.start_offset = offset;\n        anjay_download_handle_t handle = NULL;\n        AVS_UNIT_ASSERT_SUCCESS(_anjay_downloader_download(\n                &SIMPLE_ENV.base->anjay->downloader, &handle, &SIMPLE_ENV.cfg,\n                NULL, NULL));\n        AVS_UNIT_ASSERT_NOT_NULL(handle);\n\n        do {\n            ANJAY_MUTEX_UNLOCK_FOR_CALLBACK(anjay_locked,\n                                            SIMPLE_ENV.base->anjay);\n            while (avs_time_duration_equal(\n                    avs_sched_time_to_next(SIMPLE_ENV.base->anjay->sched),\n                    AVS_TIME_DURATION_ZERO)) {\n                avs_sched_run(SIMPLE_ENV.base->anjay->sched);\n            }\n            ANJAY_MUTEX_LOCK_AFTER_CALLBACK(anjay_locked);\n        } while (!handle_packet());\n\n        avs_unit_mocksock_assert_expects_met(SIMPLE_ENV.mocksock);\n\n        teardown_simple();\n    }\n}\n\nAVS_UNIT_TEST(downloader, resumption_without_etag_and_block_estimation) {\n    setup_simple(\"coap://127.0.0.1:5683\");\n\n    on_next_block_args_t args;\n    memset(&args, 0, sizeof(args));\n\n    size_t new_capacity = 64 + // max 64B block size\n                          12 + // CoAP header\n                          6 +  // max size of BLOCK2 option\n                          9;   // ETag option\n    memcpy((void *) (intptr_t) &SIMPLE_ENV.base->anjay->in_shared_buffer\n                   ->capacity,\n           &new_capacity, sizeof(new_capacity));\n\n    SIMPLE_ENV.cfg.start_offset = 64;\n    const coap_test_msg_t *req =\n            COAP_MSG(CON, GET, ID_TOKEN_RAW(0, nth_token(0)),\n                     BLOCK2(1, 64, \"\"));\n\n    avs_unit_mocksock_expect_shutdown(SIMPLE_ENV.mocksock);\n    avs_unit_mocksock_expect_mid_close(SIMPLE_ENV.mocksock);\n    avs_unit_mocksock_expect_connect(SIMPLE_ENV.mocksock, \"127.0.0.1\", \"5683\",\n                                     .and_then = expect_single_req,\n                                     .and_then_arg = (void *) (intptr_t) req);\n\n    anjay_download_handle_t handle = NULL;\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_downloader_download(&SIMPLE_ENV.base->anjay->downloader,\n                                       &handle, &SIMPLE_ENV.cfg, NULL, NULL));\n    AVS_UNIT_ASSERT_NOT_NULL(handle);\n\n    // We only care about verifying initial BLOCK2 size.\n    ANJAY_MUTEX_UNLOCK_FOR_CALLBACK(anjay_locked, SIMPLE_ENV.base->anjay);\n    while (avs_time_duration_equal(avs_sched_time_to_next(\n                                           SIMPLE_ENV.base->anjay->sched),\n                                   AVS_TIME_DURATION_ZERO)) {\n        avs_sched_run(SIMPLE_ENV.base->anjay->sched);\n    }\n    ANJAY_MUTEX_LOCK_AFTER_CALLBACK(anjay_locked);\n\n    expect_download_finished(&SIMPLE_ENV.data,\n                             _anjay_download_status_aborted());\n    _anjay_downloader_abort(&SIMPLE_ENV.base->anjay->downloader, handle);\n    teardown_simple();\n}\n\nAVS_UNIT_TEST(downloader, resumption_with_etag_and_block_estimation) {\n#define DL_ETAG \"AAAABBBB\"\n    anjay_etag_t *etag = anjay_etag_new(sizeof(DL_ETAG) - 1);\n    memcpy(etag->value, DL_ETAG, sizeof(DL_ETAG) - 1);\n\n    setup_simple_with_etag(\"coap://127.0.0.1:5683\", etag);\n\n    on_next_block_args_t args;\n    memset(&args, 0, sizeof(args));\n\n    size_t new_capacity = 64 + // max 64B block size\n                          12 + // CoAP header\n                          6;   // max size of BLOCK2 option\n    // Intentionally not including ETag in calculations\n    memcpy((void *) (intptr_t) &SIMPLE_ENV.base->anjay->in_shared_buffer\n                   ->capacity,\n           &new_capacity, sizeof(new_capacity));\n\n    SIMPLE_ENV.cfg.start_offset = 96;\n    // ETag is not taken into account during initial calculation\n    const coap_test_msg_t *req =\n            COAP_MSG(CON, GET, ID_TOKEN_RAW(0, nth_token(0)),\n                     BLOCK2(1, 64, \"\"));\n\n    avs_unit_mocksock_expect_shutdown(SIMPLE_ENV.mocksock);\n    avs_unit_mocksock_expect_mid_close(SIMPLE_ENV.mocksock);\n    avs_unit_mocksock_expect_connect(SIMPLE_ENV.mocksock, \"127.0.0.1\", \"5683\",\n                                     .and_then = expect_single_req,\n                                     .and_then_arg = (void *) (intptr_t) req);\n\n    anjay_download_handle_t handle = NULL;\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_downloader_download(&SIMPLE_ENV.base->anjay->downloader,\n                                       &handle, &SIMPLE_ENV.cfg, NULL, NULL));\n    AVS_UNIT_ASSERT_NOT_NULL(handle);\n    ANJAY_MUTEX_UNLOCK_FOR_CALLBACK(anjay_locked, SIMPLE_ENV.base->anjay);\n    while (avs_time_duration_equal(avs_sched_time_to_next(\n                                           SIMPLE_ENV.base->anjay->sched),\n                                   AVS_TIME_DURATION_ZERO)) {\n        avs_sched_run(SIMPLE_ENV.base->anjay->sched);\n    }\n    ANJAY_MUTEX_LOCK_AFTER_CALLBACK(anjay_locked);\n\n    const coap_test_msg_t *res =\n            COAP_MSG(ACK, CONTENT, ID_TOKEN_RAW(0, nth_token(0)),\n                     BLOCK2(1, 64, DESPAIR), ETAG(DL_ETAG));\n\n    avs_unit_mocksock_input(SIMPLE_ENV.mocksock, &res->content, res->length);\n\n    // avs_coap will retry with smaller block size\n    req = COAP_MSG(CON, GET, ID_TOKEN_RAW(1, nth_token(1)), BLOCK2(3, 32, \"\"));\n    avs_unit_mocksock_expect_output(SIMPLE_ENV.mocksock, &req->content,\n                                    req->length);\n\n    expect_has_buffered_data_check(SIMPLE_ENV.mocksock, false);\n    handle_packet();\n\n    // We only care about verifying initial BLOCK2 size.\n    expect_download_finished(&SIMPLE_ENV.data,\n                             _anjay_download_status_aborted());\n    _anjay_downloader_abort(&SIMPLE_ENV.base->anjay->downloader, handle);\n    teardown_simple();\n\n    avs_free(etag);\n#undef DL_ETAG\n}\n"
  },
  {
    "path": "tests/core/io/batch_builder.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <avsystem/commons/avs_unit_test.h>\n\n#include \"src/core/coap/anjay_content_format.h\"\n\n#include <string.h>\n\ntypedef struct {\n    const char *data;\n    size_t size;\n} test_data_t;\n\n#define MAKE_TEST_STRING(Data) \\\n    (test_data_t) {            \\\n        .data = Data,          \\\n        .size = sizeof(Data)   \\\n    }\n\n#define MAKE_TEST_DATA(Data)     \\\n    (test_data_t) {              \\\n        .data = Data,            \\\n        .size = sizeof(Data) - 1 \\\n    }\n\nstatic void builder_teardown(anjay_batch_builder_t *builder) {\n    _anjay_batch_builder_cleanup(&builder);\n    AVS_UNIT_ASSERT_NULL(builder);\n}\n\nstatic anjay_batch_builder_t *builder_setup(void) {\n    anjay_batch_builder_t *builder = _anjay_batch_builder_new();\n    AVS_UNIT_ASSERT_NOT_NULL(builder);\n    return builder;\n}\n\nAVS_UNIT_TEST(batch_builder, empty) {\n    anjay_batch_builder_t *builder = builder_setup();\n    builder_teardown(builder);\n}\n\nAVS_UNIT_TEST(batch_builder, single_int_entry) {\n    anjay_batch_builder_t *builder = builder_setup();\n\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_batch_add_int(\n            builder, &MAKE_RESOURCE_INSTANCE_PATH(0, 0, 0, 0),\n            AVS_TIME_REAL_INVALID, 0));\n    AVS_UNIT_ASSERT_EQUAL(AVS_LIST_SIZE(builder->list), 1);\n\n    builder_teardown(builder);\n}\n\nAVS_UNIT_TEST(batch_builder, two_entries) {\n    anjay_batch_builder_t *builder = builder_setup();\n\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_batch_add_int(\n            builder, &MAKE_RESOURCE_INSTANCE_PATH(0, 0, 0, 0),\n            AVS_TIME_REAL_INVALID, 0));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_batch_add_int(\n            builder, &MAKE_RESOURCE_INSTANCE_PATH(0, 0, 0, 0),\n            AVS_TIME_REAL_INVALID, 0));\n    AVS_UNIT_ASSERT_EQUAL(AVS_LIST_SIZE(builder->list), 2);\n\n    builder_teardown(builder);\n}\n\nAVS_UNIT_TEST(batch_builder, string_copy) {\n    anjay_batch_builder_t *builder = builder_setup();\n\n    test_data_t test_string = MAKE_TEST_STRING(\"raz dwa trzy\");\n\n    char *str = (char *) avs_malloc(test_string.size);\n    AVS_UNIT_ASSERT_NOT_NULL(str);\n    memcpy(str, test_string.data, test_string.size);\n\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_batch_add_string(\n            builder, &MAKE_RESOURCE_INSTANCE_PATH(0, 0, 0, 0),\n            AVS_TIME_REAL_INVALID, str));\n    AVS_UNIT_ASSERT_EQUAL(AVS_LIST_SIZE(builder->list), 1);\n\n    // Passed string shouldn't be required anymore.\n    avs_free(str);\n\n    AVS_LIST(anjay_batch_entry_t) entry = AVS_LIST_TAIL(builder->list);\n    AVS_UNIT_ASSERT_EQUAL_STRING(entry->data.value.string, test_string.data);\n\n    builder_teardown(builder);\n}\n\n#ifdef ANJAY_WITH_LWM2M11\nAVS_UNIT_TEST(batch_builder, bytes_copy) {\n    anjay_batch_builder_t *builder = builder_setup();\n\n    test_data_t test_bytes = MAKE_TEST_DATA(\"\\x01\\x02\\x03\\x04\\x05\");\n\n    char *bytes = (char *) avs_malloc(test_bytes.size);\n    AVS_UNIT_ASSERT_NOT_NULL(bytes);\n    memcpy(bytes, test_bytes.data, test_bytes.size);\n\n    _anjay_batch_add_bytes(builder, &MAKE_RESOURCE_INSTANCE_PATH(0, 0, 0, 0),\n                           AVS_TIME_REAL_INVALID, bytes, test_bytes.size);\n    AVS_UNIT_ASSERT_EQUAL(AVS_LIST_SIZE(builder->list), 1);\n\n    // Passed bytes shouldn't be required anymore.\n    avs_free(bytes);\n\n    AVS_LIST(anjay_batch_entry_t) entry = AVS_LIST_TAIL(builder->list);\n    AVS_UNIT_ASSERT_EQUAL_BYTES_SIZED(entry->data.value.bytes.data,\n                                      test_bytes.data, test_bytes.size);\n\n    builder_teardown(builder);\n}\n\nAVS_UNIT_TEST(batch_builder, empty_bytes) {\n    anjay_batch_builder_t *builder = builder_setup();\n\n    _anjay_batch_add_bytes(builder, &MAKE_RESOURCE_INSTANCE_PATH(0, 0, 0, 0),\n                           AVS_TIME_REAL_INVALID, NULL, 0);\n    AVS_UNIT_ASSERT_EQUAL(AVS_LIST_SIZE(builder->list), 1);\n\n    AVS_LIST(anjay_batch_entry_t) entry = AVS_LIST_TAIL(builder->list);\n    AVS_UNIT_ASSERT_NULL(entry->data.value.bytes.data);\n    AVS_UNIT_ASSERT_EQUAL(entry->data.value.bytes.length, 0);\n\n    builder_teardown(builder);\n}\n#endif // ANJAY_WITH_LWM2M11\n\nAVS_UNIT_TEST(batch_builder, compile) {\n    anjay_batch_builder_t *builder = builder_setup();\n\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_batch_add_int(\n            builder, &MAKE_RESOURCE_INSTANCE_PATH(0, 0, 0, 0),\n            AVS_TIME_REAL_INVALID, 0));\n    AVS_UNIT_ASSERT_EQUAL(AVS_LIST_SIZE(builder->list), 1);\n\n    anjay_batch_t *batch = _anjay_batch_builder_compile(&builder);\n    AVS_UNIT_ASSERT_NULL(builder);\n\n    AVS_UNIT_ASSERT_EQUAL(AVS_LIST_SIZE(batch->list), 1);\n    AVS_UNIT_ASSERT_EQUAL(batch->ref_count, 1);\n\n    _anjay_batch_release(&batch);\n    AVS_UNIT_ASSERT_NULL(batch);\n}\n"
  },
  {
    "path": "tests/core/io/bigdata.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ANJAY_IO_TEST_BIGDATA_H\n#define ANJAY_IO_TEST_BIGDATA_H\n\n// clang-format off\n#define DATA10B \"0123456789\"\n#define DATA100B \\\n        DATA10B DATA10B DATA10B DATA10B DATA10B \\\n        DATA10B DATA10B DATA10B DATA10B DATA10B\n#define DATA1kB \\\n        DATA100B DATA100B DATA100B DATA100B DATA100B \\\n        DATA100B DATA100B DATA100B DATA100B DATA100B\n#define DATA10kB \\\n        DATA1kB DATA1kB DATA1kB DATA1kB DATA1kB \\\n        DATA1kB DATA1kB DATA1kB DATA1kB DATA1kB\n#define DATA100kB \\\n        DATA10kB DATA10kB DATA10kB DATA10kB DATA10kB \\\n        DATA10kB DATA10kB DATA10kB DATA10kB DATA10kB\n#define DATA1MB \\\n        DATA100kB DATA100kB DATA100kB DATA100kB DATA100kB \\\n        DATA100kB DATA100kB DATA100kB DATA100kB DATA100kB\n#define DATA10MB \\\n        DATA1MB DATA1MB DATA1MB DATA1MB DATA1MB \\\n        DATA1MB DATA1MB DATA1MB DATA1MB DATA1MB\n#define DATA20MB DATA10MB DATA10MB\n// clang-format on\n\n#endif /* ANJAY_IO_TEST_BIGDATA_H */\n"
  },
  {
    "path": "tests/core/io/cbor/cbor_decoder.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#include <avsystem/commons/avs_stream_membuf.h>\n#include <avsystem/commons/avs_stream_outbuf.h>\n#define AVS_UNIT_ENABLE_SHORT_ASSERTS\n#include <avsystem/commons/avs_unit_test.h>\n\n#include \"src/core/io/cbor/anjay_json_like_cbor_decoder.h\"\n#include \"tests/utils/utils.h\"\n\n#define SCOPED_TEST_ENV(Data, Size)                                            \\\n    SCOPED_PTR(avs_stream_t, avs_stream_cleanup)                               \\\n    STREAM = avs_stream_membuf_create();                                       \\\n    ASSERT_NOT_NULL(STREAM);                                                   \\\n    ASSERT_OK(avs_stream_write(STREAM, (Data), (Size)));                       \\\n    SCOPED_PTR(anjay_json_like_decoder_t, _anjay_json_like_decoder_delete)     \\\n    DECODER = _anjay_cbor_decoder_new(STREAM, MAX_SENML_CBOR_NEST_STACK_SIZE); \\\n    ASSERT_NOT_NULL(DECODER);\n\ntypedef struct {\n    const char *data;\n    size_t size;\n} test_data_t;\n\n#define TEST_DATA_INITIALIZER(Data) \\\n    {                               \\\n        .data = Data,               \\\n        .size = sizeof(Data) - 1    \\\n    }\n\n/**\n * Convenience macro used with test_decode_uint(), e.g.:\n * > test_decode_uint(MAKE_TEST_DATA(\"\\x00\"), 0);\n */\n#define MAKE_TEST_DATA(Data) ((test_data_t) TEST_DATA_INITIALIZER(Data))\n\nAVS_UNIT_TEST(cbor_decoder, tags_are_ignored) {\n    static const test_data_t inputs[] = {\n        // tag with 1 byte extended length, with one byte of follow up\n        TEST_DATA_INITIALIZER(\"\\xD8\\x01\\x0F\"),\n        // tag with 2 bytes extended length, with one byte of follow up\n        TEST_DATA_INITIALIZER(\"\\xD9\\x01\\x02\\x0F\"),\n        // tag with 4 bytes extended length, with one byte of follow up\n        TEST_DATA_INITIALIZER(\"\\xDA\\x01\\x02\\x03\\x04\\x0F\"),\n        // tag with 8 bytes extended length, with one byte of follow up\n        TEST_DATA_INITIALIZER(\"\\xDB\\x01\\x02\\x03\\x04\\x05\\x06\\x07\\x08\\x0F\"),\n    };\n    for (size_t i = 0; i < AVS_ARRAY_SIZE(inputs); ++i) {\n        SCOPED_TEST_ENV(inputs[i].data, inputs[i].size);\n        ASSERT_EQ(_anjay_json_like_decoder_state(DECODER),\n                  ANJAY_JSON_LIKE_DECODER_STATE_OK);\n    }\n}\n\nAVS_UNIT_TEST(cbor_decoder, tags_without_following_bytes_are_invalid) {\n    static const char data[] = \"\\xC0\";\n    SCOPED_TEST_ENV(data, sizeof(data) - 1);\n    ASSERT_EQ(_anjay_json_like_decoder_state(DECODER),\n              ANJAY_JSON_LIKE_DECODER_STATE_ERROR);\n}\n\nAVS_UNIT_TEST(cbor_decoder,\n              tag_followed_by_tag_without_following_bytes_are_invalid) {\n    static const char data[] = \"\\xC0\\xC0\";\n    SCOPED_TEST_ENV(data, sizeof(data) - 1);\n    ASSERT_EQ(_anjay_json_like_decoder_state(DECODER),\n              ANJAY_JSON_LIKE_DECODER_STATE_ERROR);\n}\n\nstatic const uint64_t DECODE_UINT_FAILURE = UINT64_MAX;\n\nstatic int test_decode_uint(test_data_t test_data, uint64_t expected_value) {\n    SCOPED_TEST_ENV(test_data.data, test_data.size);\n    anjay_json_like_value_type_t type;\n    if (_anjay_json_like_decoder_current_value_type(DECODER, &type)\n            || type != ANJAY_JSON_LIKE_VALUE_UINT) {\n        return -1;\n    }\n\n    anjay_json_like_number_t decoded_number;\n    if (_anjay_json_like_decoder_number(DECODER, &decoded_number)) {\n        return -1;\n    }\n    ASSERT_EQ(decoded_number.type, ANJAY_JSON_LIKE_VALUE_UINT);\n    if (expected_value == DECODE_UINT_FAILURE) {\n        ASSERT_EQ(_anjay_json_like_decoder_state(DECODER),\n                  ANJAY_JSON_LIKE_DECODER_STATE_ERROR);\n    } else {\n        ASSERT_EQ(_anjay_json_like_decoder_state(DECODER),\n                  ANJAY_JSON_LIKE_DECODER_STATE_FINISHED);\n        ASSERT_EQ(decoded_number.value.u64, expected_value);\n    }\n    return 0;\n}\n\nAVS_UNIT_TEST(cbor_decoder, uint_small) {\n    for (unsigned small_value = 0; small_value < 24; ++small_value) {\n        const uint8_t data =\n                (uint8_t) ((CBOR_MAJOR_TYPE_UINT << 5) | small_value);\n        ASSERT_OK(test_decode_uint((test_data_t) { &data, sizeof(data) },\n                                   small_value));\n    }\n}\n\nAVS_UNIT_TEST(cbor_decoder, uint_extended_length_of_1_byte) {\n    ASSERT_OK(test_decode_uint(MAKE_TEST_DATA(\"\\x18\\xFF\"), 0xFF));\n    ASSERT_FAIL(test_decode_uint(MAKE_TEST_DATA(\"\\x18\"), DECODE_UINT_FAILURE));\n}\n\nAVS_UNIT_TEST(cbor_decoder, uint_extended_length_of_2_byte) {\n    ASSERT_OK(test_decode_uint(MAKE_TEST_DATA(\"\\x19\\xAA\\xBB\"), 0xAABB));\n    ASSERT_FAIL(test_decode_uint(MAKE_TEST_DATA(\"\\x19\"), DECODE_UINT_FAILURE));\n    ASSERT_FAIL(\n            test_decode_uint(MAKE_TEST_DATA(\"\\x19\\xAA\"), DECODE_UINT_FAILURE));\n}\n\nAVS_UNIT_TEST(cbor_decoder, uint_extended_length_of_4_byte) {\n    ASSERT_OK(test_decode_uint(MAKE_TEST_DATA(\"\\x1A\\xAA\\xBB\\xCC\\xDD\"),\n                               0xAABBCCDD));\n    ASSERT_FAIL(\n            test_decode_uint(MAKE_TEST_DATA(\"\\x1A\\xAA\"), DECODE_UINT_FAILURE));\n    ASSERT_FAIL(test_decode_uint(MAKE_TEST_DATA(\"\\x1A\\xAA\\xBB\"),\n                                 DECODE_UINT_FAILURE));\n    ASSERT_FAIL(test_decode_uint(MAKE_TEST_DATA(\"\\x1A\\xAA\\xBB\\xCC\"),\n                                 DECODE_UINT_FAILURE));\n}\n\nAVS_UNIT_TEST(cbor_decoder, uint_extended_length_of_8_byte) {\n    ASSERT_OK(test_decode_uint(MAKE_TEST_DATA(\n                                       \"\\x1B\\xAA\\xBB\\xCC\\xDD\\x00\\x11\\x22\\x33\"),\n                               0xAABBCCDD00112233ULL));\n    ASSERT_FAIL(\n            test_decode_uint(MAKE_TEST_DATA(\"\\x1B\\xAA\\xBB\\xCC\\xDD\\x00\\x11\\x22\"),\n                             DECODE_UINT_FAILURE));\n    ASSERT_FAIL(test_decode_uint(MAKE_TEST_DATA(\"\\x1B\\xAA\\xBB\\xCC\\xDD\\x00\\x11\"),\n                                 DECODE_UINT_FAILURE));\n    ASSERT_FAIL(test_decode_uint(MAKE_TEST_DATA(\"\\x1B\\xAA\\xBB\\xCC\\xDD\\x00\"),\n                                 DECODE_UINT_FAILURE));\n    ASSERT_FAIL(test_decode_uint(MAKE_TEST_DATA(\"\\x1B\\xAA\\xBB\\xCC\\xDD\"),\n                                 DECODE_UINT_FAILURE));\n    ASSERT_FAIL(test_decode_uint(MAKE_TEST_DATA(\"\\x1B\\xAA\\xBB\\xCC\"),\n                                 DECODE_UINT_FAILURE));\n    ASSERT_FAIL(test_decode_uint(MAKE_TEST_DATA(\"\\x1B\\xAA\\xBB\"),\n                                 DECODE_UINT_FAILURE));\n    ASSERT_FAIL(\n            test_decode_uint(MAKE_TEST_DATA(\"\\x1B\\xAA\"), DECODE_UINT_FAILURE));\n    ASSERT_FAIL(test_decode_uint(MAKE_TEST_DATA(\"\\x1B\"), DECODE_UINT_FAILURE));\n}\n\nstatic const int64_t DECODE_NEGATIVE_INT_FAILURE = INT64_MAX;\n\nstatic int test_decode_negative_int(test_data_t test_data,\n                                    int64_t expected_value) {\n    SCOPED_TEST_ENV(test_data.data, test_data.size);\n    anjay_json_like_value_type_t type;\n    if (_anjay_json_like_decoder_current_value_type(DECODER, &type)\n            || type != ANJAY_JSON_LIKE_VALUE_NEGATIVE_INT) {\n        return -1;\n    }\n\n    anjay_json_like_number_t decoded_number;\n    if (_anjay_json_like_decoder_number(DECODER, &decoded_number)) {\n        return -1;\n    }\n    ASSERT_EQ(ANJAY_JSON_LIKE_VALUE_NEGATIVE_INT, decoded_number.type);\n    if (expected_value == DECODE_NEGATIVE_INT_FAILURE) {\n        ASSERT_EQ(_anjay_json_like_decoder_state(DECODER),\n                  ANJAY_JSON_LIKE_DECODER_STATE_ERROR);\n    } else {\n        ASSERT_EQ(_anjay_json_like_decoder_state(DECODER),\n                  ANJAY_JSON_LIKE_DECODER_STATE_FINISHED);\n        ASSERT_EQ(decoded_number.value.i64, expected_value);\n    }\n    return 0;\n}\n\nAVS_UNIT_TEST(cbor_decoder, neg_int_small) {\n    for (int small_value = 0; small_value < 24; ++small_value) {\n        const uint8_t data =\n                (uint8_t) ((CBOR_MAJOR_TYPE_NEGATIVE_INT << 5) | small_value);\n        ASSERT_OK(test_decode_negative_int(\n                (test_data_t) { &data, sizeof(data) }, -small_value - 1));\n    }\n    const uint8_t data = (uint8_t) ((CBOR_MAJOR_TYPE_NEGATIVE_INT << 5) | 24);\n    ASSERT_FAIL(test_decode_negative_int((test_data_t) { &data, sizeof(data) },\n                                         DECODE_NEGATIVE_INT_FAILURE));\n}\n\nAVS_UNIT_TEST(cbor_decoder, neg_int_extended_length_of_1_byte) {\n    ASSERT_OK(test_decode_negative_int(MAKE_TEST_DATA(\"\\x38\\xFF\"), -256));\n    ASSERT_FAIL(test_decode_negative_int(MAKE_TEST_DATA(\"\\x38\"),\n                                         DECODE_NEGATIVE_INT_FAILURE));\n}\n\nAVS_UNIT_TEST(cbor_decoder, neg_int_extended_length_of_2_byte) {\n    ASSERT_OK(test_decode_negative_int(MAKE_TEST_DATA(\"\\x39\\x00\\x01\"), -2));\n    ASSERT_FAIL(test_decode_negative_int(MAKE_TEST_DATA(\"\\x39\\x00\"),\n                                         DECODE_NEGATIVE_INT_FAILURE));\n    ASSERT_FAIL(test_decode_negative_int(MAKE_TEST_DATA(\"\\x39\"),\n                                         DECODE_NEGATIVE_INT_FAILURE));\n}\n\nAVS_UNIT_TEST(cbor_decoder, neg_int_boundary) {\n    ASSERT_OK(test_decode_negative_int(\n            MAKE_TEST_DATA(\"\\x3B\\x7F\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\"), INT64_MIN));\n    // Overflow.\n    ASSERT_FAIL(test_decode_negative_int(\n            MAKE_TEST_DATA(\"\\x3B\\x80\\x00\\x00\\x00\\x00\\x00\\x00\\x00\"),\n            DECODE_NEGATIVE_INT_FAILURE));\n}\n\nAVS_UNIT_TEST(cbor_decoder, bytes_short) {\n    // - 1st byte: code,\n    // - rest: maximum 23 bytes of payload.\n    uint8_t input_bytes[1 + 23];\n    uint8_t output_bytes[sizeof(input_bytes) - 1];\n\n    for (size_t short_len = 0; short_len < 24; ++short_len) {\n        input_bytes[0] =\n                (uint8_t) ((CBOR_MAJOR_TYPE_BYTE_STRING << 5) | short_len);\n        for (size_t i = 0; i < short_len; ++i) {\n            input_bytes[i + 1] = (uint8_t) rand();\n        }\n        SCOPED_TEST_ENV(input_bytes, short_len + 1);\n        avs_stream_outbuf_t stream = AVS_STREAM_OUTBUF_STATIC_INITIALIZER;\n        avs_stream_outbuf_set_buffer(\n                &stream, output_bytes, sizeof(output_bytes));\n        // consume the bytes\n        ASSERT_OK(_anjay_json_like_decoder_bytes(DECODER,\n                                                 (avs_stream_t *) &stream));\n        ASSERT_EQ(avs_stream_outbuf_offset(&stream), short_len);\n        ASSERT_EQ(_anjay_json_like_decoder_state(DECODER),\n                  ANJAY_JSON_LIKE_DECODER_STATE_FINISHED);\n        ASSERT_EQ_BYTES_SIZED(output_bytes, &input_bytes[1], short_len);\n    }\n}\n\nAVS_UNIT_TEST(cbor_decoder, bytes_indefinite) {\n    // (_ h'AABBCCDD', h'EEFF99')\n    uint8_t input_bytes[] = { 0x5F, 0x44, 0xAA, 0xBB, 0xCC, 0xDD,\n                              0x43, 0xEE, 0xFF, 0x99, 0xFF };\n#define TEST_BYTES \"\\xAA\\xBB\\xCC\\xDD\\xEE\\xFF\\x99\"\n    uint8_t output_bytes[sizeof(TEST_BYTES) - 1];\n\n    SCOPED_TEST_ENV(input_bytes, sizeof(input_bytes));\n    avs_stream_outbuf_t stream = AVS_STREAM_OUTBUF_STATIC_INITIALIZER;\n    avs_stream_outbuf_set_buffer(&stream, output_bytes, sizeof(output_bytes));\n    // consume the bytes\n    ASSERT_OK(\n            _anjay_json_like_decoder_bytes(DECODER, (avs_stream_t *) &stream));\n    ASSERT_EQ(avs_stream_outbuf_offset(&stream), sizeof(output_bytes));\n    ASSERT_EQ(_anjay_json_like_decoder_state(DECODER),\n              ANJAY_JSON_LIKE_DECODER_STATE_FINISHED);\n    ASSERT_EQ_BYTES_SIZED(output_bytes, TEST_BYTES, sizeof(output_bytes));\n#undef TEST_BYTES\n}\n\nAVS_UNIT_TEST(cbor_decoder, bytes_indefinite_empty) {\n    // (_ )\n    uint8_t input_bytes[] = { 0x5F, 0xFF };\n    uint8_t output_bytes[1];\n\n    SCOPED_TEST_ENV(input_bytes, sizeof(input_bytes));\n    avs_stream_outbuf_t stream = AVS_STREAM_OUTBUF_STATIC_INITIALIZER;\n    avs_stream_outbuf_set_buffer(&stream, output_bytes, sizeof(output_bytes));\n    // consume the bytes\n    ASSERT_OK(\n            _anjay_json_like_decoder_bytes(DECODER, (avs_stream_t *) &stream));\n    ASSERT_EQ(avs_stream_outbuf_offset(&stream), 0);\n    ASSERT_EQ(_anjay_json_like_decoder_state(DECODER),\n              ANJAY_JSON_LIKE_DECODER_STATE_FINISHED);\n}\n\nAVS_UNIT_TEST(cbor_decoder, bytes_indefinite_invalid_integer_inside) {\n    // (_ 21 )\n    uint8_t input_bytes[] = { 0x5F, 0x15, 0xFF };\n    uint8_t output_bytes[1];\n\n    SCOPED_TEST_ENV(input_bytes, sizeof(input_bytes));\n    avs_stream_outbuf_t stream = AVS_STREAM_OUTBUF_STATIC_INITIALIZER;\n    avs_stream_outbuf_set_buffer(&stream, output_bytes, sizeof(output_bytes));\n    ASSERT_FAIL(\n            _anjay_json_like_decoder_bytes(DECODER, (avs_stream_t *) &stream));\n    ASSERT_EQ(avs_stream_outbuf_offset(&stream), 0);\n    ASSERT_EQ(_anjay_json_like_decoder_state(DECODER),\n              ANJAY_JSON_LIKE_DECODER_STATE_ERROR);\n}\n\nAVS_UNIT_TEST(cbor_decoder, bytes_indefinite_invalid_map_inside) {\n    // (_ {2: 5} )\n    uint8_t input_bytes[] = { 0x5F, 0xA1, 0x02, 0x05, 0xFF };\n    uint8_t output_bytes[1];\n\n    SCOPED_TEST_ENV(input_bytes, sizeof(input_bytes));\n    avs_stream_outbuf_t stream = AVS_STREAM_OUTBUF_STATIC_INITIALIZER;\n    avs_stream_outbuf_set_buffer(&stream, output_bytes, sizeof(output_bytes));\n    ASSERT_FAIL(\n            _anjay_json_like_decoder_bytes(DECODER, (avs_stream_t *) &stream));\n    ASSERT_EQ(avs_stream_outbuf_offset(&stream), 0);\n    ASSERT_EQ(_anjay_json_like_decoder_state(DECODER),\n              ANJAY_JSON_LIKE_DECODER_STATE_ERROR);\n}\n\nAVS_UNIT_TEST(cbor_decoder, bytes_indefinite_invalid_bytes_and_map_inside) {\n    // (_ h'001122', {2: 5} )\n    uint8_t input_bytes[] = { 0x5F, 0x43, 0x00, 0x11, 0x22,\n                              0xA1, 0x02, 0x05, 0xFF };\n    uint8_t output_bytes[4];\n\n    SCOPED_TEST_ENV(input_bytes, sizeof(input_bytes));\n    avs_stream_outbuf_t stream = AVS_STREAM_OUTBUF_STATIC_INITIALIZER;\n    avs_stream_outbuf_set_buffer(&stream, output_bytes, sizeof(output_bytes));\n    ASSERT_FAIL(\n            _anjay_json_like_decoder_bytes(DECODER, (avs_stream_t *) &stream));\n    ASSERT_EQ(avs_stream_outbuf_offset(&stream), 0);\n    ASSERT_EQ(_anjay_json_like_decoder_state(DECODER),\n              ANJAY_JSON_LIKE_DECODER_STATE_ERROR);\n}\n\nAVS_UNIT_TEST(cbor_decoder, bytes_long) {\n    // - 1st byte: code,\n    // - 2nd byte: extended length high byte,\n    // - 3rd byte: extended length low byte,\n    // - rest: 256 bytes of payload.\n    enum { PAYLOAD_LEN = 256 };\n    uint8_t input_bytes[3 + PAYLOAD_LEN];\n\n    input_bytes[0] = 0x59; // major-type=bytes, extended-length=2bytes\n    input_bytes[1] = PAYLOAD_LEN >> 8;\n    input_bytes[2] = PAYLOAD_LEN & 0xFF;\n    for (size_t i = 3; i < sizeof(input_bytes); ++i) {\n        input_bytes[i] = (uint8_t) rand();\n    }\n\n    SCOPED_TEST_ENV(input_bytes, sizeof(input_bytes));\n\n    avs_stream_t *membuf = avs_stream_membuf_create();\n    ASSERT_NOT_NULL(membuf);\n    ASSERT_OK(_anjay_json_like_decoder_bytes(DECODER, membuf));\n    void *output_bytes = NULL;\n    size_t buffer_size;\n    ASSERT_OK(avs_stream_membuf_take_ownership(\n            membuf, &output_bytes, &buffer_size));\n    avs_stream_cleanup(&membuf);\n    ASSERT_NOT_NULL(output_bytes);\n    ASSERT_EQ(buffer_size, PAYLOAD_LEN);\n    ASSERT_EQ_BYTES_SIZED(output_bytes, &input_bytes[3], PAYLOAD_LEN);\n    avs_free(output_bytes);\n\n    ASSERT_EQ(_anjay_json_like_decoder_state(DECODER),\n              ANJAY_JSON_LIKE_DECODER_STATE_FINISHED);\n}\n\nAVS_UNIT_TEST(cbor_decoder, flat_array) {\n    // array [1u, 2u, 3u]\n    static const char data[] = \"\\x83\\x01\\x02\\x03\";\n    SCOPED_TEST_ENV(data, sizeof(data) - 1);\n    anjay_json_like_value_type_t type;\n    ASSERT_OK(_anjay_json_like_decoder_current_value_type(DECODER, &type));\n    ASSERT_EQ(ANJAY_JSON_LIKE_VALUE_ARRAY, type);\n\n    ASSERT_EQ(_anjay_json_like_decoder_nesting_level(DECODER), 0);\n    ASSERT_OK(_anjay_json_like_decoder_enter_array(DECODER));\n\n    anjay_json_like_number_t value;\n    ASSERT_EQ(_anjay_json_like_decoder_nesting_level(DECODER), 1);\n    ASSERT_OK(_anjay_json_like_decoder_number(DECODER, &value));\n    ASSERT_EQ(value.type, ANJAY_JSON_LIKE_VALUE_UINT);\n    ASSERT_EQ(value.value.u64, 1);\n\n    ASSERT_EQ(_anjay_json_like_decoder_nesting_level(DECODER), 1);\n    ASSERT_OK(_anjay_json_like_decoder_number(DECODER, &value));\n    ASSERT_EQ(value.type, ANJAY_JSON_LIKE_VALUE_UINT);\n    ASSERT_EQ(value.value.u64, 2);\n\n    ASSERT_EQ(_anjay_json_like_decoder_nesting_level(DECODER), 1);\n    ASSERT_OK(_anjay_json_like_decoder_number(DECODER, &value));\n    ASSERT_EQ(value.type, ANJAY_JSON_LIKE_VALUE_UINT);\n    ASSERT_EQ(value.value.u64, 3);\n\n    ASSERT_EQ(_anjay_json_like_decoder_nesting_level(DECODER), 0);\n    ASSERT_EQ(_anjay_json_like_decoder_state(DECODER),\n              ANJAY_JSON_LIKE_DECODER_STATE_FINISHED);\n\n    ASSERT_FAIL(_anjay_json_like_decoder_number(DECODER, &value));\n}\n\nAVS_UNIT_TEST(cbor_decoder, flat_empty_array) {\n    SCOPED_TEST_ENV(\"\\x80\", 1);\n    anjay_json_like_value_type_t type;\n    ASSERT_OK(_anjay_json_like_decoder_current_value_type(DECODER, &type));\n    ASSERT_EQ(type, ANJAY_JSON_LIKE_VALUE_ARRAY);\n\n    ASSERT_EQ(_anjay_json_like_decoder_nesting_level(DECODER), 0);\n    ASSERT_OK(_anjay_json_like_decoder_enter_array(DECODER));\n    ASSERT_EQ(_anjay_json_like_decoder_nesting_level(DECODER), 0);\n\n    ASSERT_EQ(_anjay_json_like_decoder_state(DECODER),\n              ANJAY_JSON_LIKE_DECODER_STATE_FINISHED);\n\n    ASSERT_FAIL(_anjay_json_like_decoder_current_value_type(DECODER, &type));\n}\n\nAVS_UNIT_TEST(cbor_decoder, flat_empty_array_with_uint_afterwards) {\n    SCOPED_TEST_ENV(\"\\x80\\x01\", 2);\n    anjay_json_like_value_type_t type;\n    ASSERT_OK(_anjay_json_like_decoder_current_value_type(DECODER, &type));\n    ASSERT_EQ(type, ANJAY_JSON_LIKE_VALUE_ARRAY);\n\n    ASSERT_EQ(_anjay_json_like_decoder_nesting_level(DECODER), 0);\n    ASSERT_OK(_anjay_json_like_decoder_enter_array(DECODER));\n    ASSERT_EQ(_anjay_json_like_decoder_nesting_level(DECODER), 0);\n\n    ASSERT_OK(_anjay_json_like_decoder_current_value_type(DECODER, &type));\n    ASSERT_EQ(type, ANJAY_JSON_LIKE_VALUE_UINT);\n\n    anjay_json_like_number_t value;\n    ASSERT_OK(_anjay_json_like_decoder_number(DECODER, &value));\n    ASSERT_EQ(value.type, ANJAY_JSON_LIKE_VALUE_UINT);\n    ASSERT_EQ(value.value.u64, 1);\n\n    ASSERT_EQ(_anjay_json_like_decoder_state(DECODER),\n              ANJAY_JSON_LIKE_DECODER_STATE_FINISHED);\n\n    ASSERT_FAIL(_anjay_json_like_decoder_current_value_type(DECODER, &type));\n}\n\nAVS_UNIT_TEST(cbor_decoder, nested_array) {\n    // array [[1u,2u,3u], 4]\n    {\n        static const char data_array_first[] = \"\\x82\\x83\\x01\\x02\\x03\\x04\";\n        SCOPED_TEST_ENV(data_array_first, sizeof(data_array_first) - 1);\n        anjay_json_like_value_type_t type;\n        ASSERT_OK(_anjay_json_like_decoder_current_value_type(DECODER, &type));\n        ASSERT_EQ(type, ANJAY_JSON_LIKE_VALUE_ARRAY);\n\n        ASSERT_EQ(_anjay_json_like_decoder_nesting_level(DECODER), 0);\n        ASSERT_OK(_anjay_json_like_decoder_enter_array(DECODER));\n\n        ASSERT_EQ(_anjay_json_like_decoder_nesting_level(DECODER), 1);\n        ASSERT_OK(_anjay_json_like_decoder_enter_array(DECODER));\n\n        anjay_json_like_number_t value;\n        ASSERT_EQ(_anjay_json_like_decoder_nesting_level(DECODER), 2);\n        ASSERT_OK(_anjay_json_like_decoder_number(DECODER, &value));\n        ASSERT_EQ(value.type, ANJAY_JSON_LIKE_VALUE_UINT);\n        ASSERT_EQ(value.value.u64, 1);\n\n        ASSERT_EQ(_anjay_json_like_decoder_nesting_level(DECODER), 2);\n        ASSERT_OK(_anjay_json_like_decoder_number(DECODER, &value));\n        ASSERT_EQ(value.type, ANJAY_JSON_LIKE_VALUE_UINT);\n        ASSERT_EQ(value.value.u64, 2);\n\n        ASSERT_EQ(_anjay_json_like_decoder_nesting_level(DECODER), 2);\n        ASSERT_OK(_anjay_json_like_decoder_number(DECODER, &value));\n        ASSERT_EQ(value.type, ANJAY_JSON_LIKE_VALUE_UINT);\n        ASSERT_EQ(value.value.u64, 3);\n\n        ASSERT_EQ(_anjay_json_like_decoder_nesting_level(DECODER), 1);\n        ASSERT_OK(_anjay_json_like_decoder_number(DECODER, &value));\n        ASSERT_EQ(value.type, ANJAY_JSON_LIKE_VALUE_UINT);\n        ASSERT_EQ(value.value.u64, 4);\n\n        ASSERT_EQ(_anjay_json_like_decoder_nesting_level(DECODER), 0);\n        ASSERT_EQ(_anjay_json_like_decoder_state(DECODER),\n                  ANJAY_JSON_LIKE_DECODER_STATE_FINISHED);\n    }\n\n    {\n        // array [1u, [2u, 3u, 4u]]\n        static const char data_array_last[] = \"\\x82\\x01\\x83\\x02\\x03\\x04\";\n        SCOPED_TEST_ENV(data_array_last, sizeof(data_array_last) - 1);\n        anjay_json_like_value_type_t type;\n        ASSERT_OK(_anjay_json_like_decoder_current_value_type(DECODER, &type));\n        ASSERT_EQ(type, ANJAY_JSON_LIKE_VALUE_ARRAY);\n\n        ASSERT_EQ(_anjay_json_like_decoder_nesting_level(DECODER), 0);\n        ASSERT_OK(_anjay_json_like_decoder_enter_array(DECODER));\n\n        ASSERT_EQ(_anjay_json_like_decoder_nesting_level(DECODER), 1);\n        anjay_json_like_number_t value;\n        ASSERT_OK(_anjay_json_like_decoder_number(DECODER, &value));\n        ASSERT_EQ(value.type, ANJAY_JSON_LIKE_VALUE_UINT);\n        ASSERT_EQ(value.value.u64, 1);\n\n        ASSERT_EQ(_anjay_json_like_decoder_nesting_level(DECODER), 1);\n        ASSERT_OK(_anjay_json_like_decoder_enter_array(DECODER));\n\n        ASSERT_EQ(_anjay_json_like_decoder_nesting_level(DECODER), 2);\n        ASSERT_OK(_anjay_json_like_decoder_number(DECODER, &value));\n        ASSERT_EQ(value.type, ANJAY_JSON_LIKE_VALUE_UINT);\n        ASSERT_EQ(value.value.u64, 2);\n\n        ASSERT_EQ(_anjay_json_like_decoder_nesting_level(DECODER), 2);\n        ASSERT_OK(_anjay_json_like_decoder_number(DECODER, &value));\n        ASSERT_EQ(value.type, ANJAY_JSON_LIKE_VALUE_UINT);\n        ASSERT_EQ(value.value.u64, 3);\n\n        ASSERT_EQ(_anjay_json_like_decoder_nesting_level(DECODER), 2);\n        ASSERT_OK(_anjay_json_like_decoder_number(DECODER, &value));\n        ASSERT_EQ(value.type, ANJAY_JSON_LIKE_VALUE_UINT);\n        ASSERT_EQ(value.value.u64, 4);\n\n        ASSERT_EQ(_anjay_json_like_decoder_nesting_level(DECODER), 0);\n        ASSERT_EQ(_anjay_json_like_decoder_state(DECODER),\n                  ANJAY_JSON_LIKE_DECODER_STATE_FINISHED);\n    }\n}\n\nAVS_UNIT_TEST(cbor_decoder, array_too_many_nest_levels) {\n    // array [[[[[]]]]]\n    static const char data[] = \"\\x81\\x81\\x81\\x81\\x80\";\n    SCOPED_TEST_ENV(data, sizeof(data) - 1);\n    ASSERT_EQ(_anjay_json_like_decoder_nesting_level(DECODER), 0);\n    ASSERT_OK(_anjay_json_like_decoder_enter_array(DECODER));\n    ASSERT_EQ(_anjay_json_like_decoder_nesting_level(DECODER), 1);\n    ASSERT_OK(_anjay_json_like_decoder_enter_array(DECODER));\n    ASSERT_EQ(_anjay_json_like_decoder_nesting_level(DECODER), 2);\n    ASSERT_OK(_anjay_json_like_decoder_enter_array(DECODER));\n    ASSERT_EQ(_anjay_json_like_decoder_nesting_level(DECODER), 3);\n    ASSERT_FAIL(_anjay_json_like_decoder_enter_array(DECODER));\n\n    ASSERT_EQ(_anjay_json_like_decoder_state(DECODER),\n              ANJAY_JSON_LIKE_DECODER_STATE_ERROR);\n}\n\nAVS_UNIT_TEST(cbor_decoder, flat_map) {\n    // map { 42: 300 }\n    static const char data[] = \"\\xA1\\x18\\x2A\\x19\\x01\\x2C\";\n    SCOPED_TEST_ENV(data, sizeof(data) - 1);\n    anjay_json_like_value_type_t type;\n    ASSERT_OK(_anjay_json_like_decoder_current_value_type(DECODER, &type));\n    ASSERT_EQ(type, ANJAY_JSON_LIKE_VALUE_MAP);\n\n    ASSERT_EQ(_anjay_json_like_decoder_nesting_level(DECODER), 0);\n    ASSERT_OK(_anjay_json_like_decoder_enter_map(DECODER));\n    ASSERT_EQ(_anjay_json_like_decoder_nesting_level(DECODER), 1);\n\n    anjay_json_like_number_t key;\n    ASSERT_OK(_anjay_json_like_decoder_number(DECODER, &key));\n    ASSERT_EQ(key.type, ANJAY_JSON_LIKE_VALUE_UINT);\n    ASSERT_EQ(key.value.u64, 42);\n\n    anjay_json_like_number_t value;\n    ASSERT_OK(_anjay_json_like_decoder_number(DECODER, &value));\n    ASSERT_EQ(value.type, ANJAY_JSON_LIKE_VALUE_UINT);\n    ASSERT_EQ(value.value.u64, 300);\n    ASSERT_EQ(_anjay_json_like_decoder_nesting_level(DECODER), 0);\n\n    ASSERT_EQ(_anjay_json_like_decoder_state(DECODER),\n              ANJAY_JSON_LIKE_DECODER_STATE_FINISHED);\n}\n\nAVS_UNIT_TEST(cbor_decoder, empty_map) {\n    static const char data[] = \"\\xA0\";\n    SCOPED_TEST_ENV(data, 1);\n    ASSERT_OK(_anjay_json_like_decoder_enter_map(DECODER));\n    // We enter the map, and then we immediately exit it, because it is empty.\n    ASSERT_EQ(_anjay_json_like_decoder_nesting_level(DECODER), 0);\n\n    ASSERT_EQ(_anjay_json_like_decoder_state(DECODER),\n              ANJAY_JSON_LIKE_DECODER_STATE_FINISHED);\n}\n\n#define TEST_HALF(Name, Value, Expected)                             \\\n    AVS_UNIT_TEST(cbor_decoder, Name) {                              \\\n        static const char data[] = \"\\xF9\" Value;                     \\\n        SCOPED_TEST_ENV(data, sizeof(data) - 1);                     \\\n        anjay_json_like_number_t value;                              \\\n        ASSERT_OK(_anjay_json_like_decoder_number(DECODER, &value)); \\\n        ASSERT_EQ(value.type, ANJAY_JSON_LIKE_VALUE_FLOAT);          \\\n        ASSERT_EQ(value.value.f32, Expected);                        \\\n    }\n\nTEST_HALF(half_float_value, \"\\x50\\x00\", 32.0f);\nTEST_HALF(half_float_nan, \"\\x7E\\x00\", NAN);\nTEST_HALF(half_float_inf, \"\\x7C\\x00\", INFINITY);\n\nAVS_UNIT_TEST(cbor_decoder, boolean_true_and_false) {\n    {\n        static const char data[] = \"\\xF5\";\n        SCOPED_TEST_ENV(data, 1);\n        bool value;\n        ASSERT_OK(_anjay_json_like_decoder_bool(DECODER, &value));\n        ASSERT_EQ(value, true);\n        ASSERT_EQ(_anjay_json_like_decoder_state(DECODER),\n                  ANJAY_JSON_LIKE_DECODER_STATE_FINISHED);\n    }\n    {\n        static const char data[] = \"\\xF4\";\n        SCOPED_TEST_ENV(data, 1);\n        bool value;\n        ASSERT_OK(_anjay_json_like_decoder_bool(DECODER, &value));\n        ASSERT_EQ(value, false);\n        ASSERT_EQ(_anjay_json_like_decoder_state(DECODER),\n                  ANJAY_JSON_LIKE_DECODER_STATE_FINISHED);\n    }\n}\n\nAVS_UNIT_TEST(cbor_decoder, boolean_integers_are_not_real_booleans) {\n    {\n        static const char data[] = \"\\x00\";\n        SCOPED_TEST_ENV(data, 1);\n        bool value;\n        ASSERT_FAIL(_anjay_json_like_decoder_bool(DECODER, &value));\n    }\n\n    {\n        static const char data[] = \"\\x01\";\n        SCOPED_TEST_ENV(data, 1);\n        bool value;\n        ASSERT_FAIL(_anjay_json_like_decoder_bool(DECODER, &value));\n    }\n}\n\nstatic uint8_t make_header(cbor_major_type_t major_type, uint8_t value) {\n    return (uint8_t) ((((uint8_t) major_type) << 5) | value);\n}\n\nstatic void encode_int(char **out_buffer, int64_t value) {\n    const uint64_t encoded =\n            avs_convert_be64((uint64_t) (value < 0 ? -(value + 1) : value));\n    const uint8_t major_type =\n            value < 0 ? CBOR_MAJOR_TYPE_NEGATIVE_INT : CBOR_MAJOR_TYPE_UINT;\n    const uint8_t header = make_header(major_type, CBOR_EXT_LENGTH_8BYTE);\n\n    memcpy(*out_buffer, &header, 1);\n    *out_buffer += 1;\n    memcpy(*out_buffer, &encoded, sizeof(encoded));\n    *out_buffer += sizeof(encoded);\n}\n\n#define TEST_TYPICAL_DECIMAL_FRACTION(Name, Exponent, Mantissa)               \\\n    AVS_UNIT_TEST(cbor_decoder, typical_decimal_##Name) {                     \\\n        /* Tag(4), Array [ Exponent, Mantissa ] */                            \\\n        char data[2 + 2 * (sizeof(uint8_t) + sizeof(uint64_t))] = \"\\xC4\\x82\"; \\\n        char *integers = &data[2];                                            \\\n        encode_int(&integers, (Exponent));                                    \\\n        encode_int(&integers, (Mantissa));                                    \\\n        SCOPED_TEST_ENV(data, sizeof(data));                                  \\\n        anjay_json_like_number_t value;                                       \\\n        ASSERT_OK(_anjay_json_like_decoder_number(DECODER, &value));          \\\n        ASSERT_EQ(value.type, ANJAY_JSON_LIKE_VALUE_DOUBLE);                  \\\n        ASSERT_EQ(value.value.f64, (Mantissa) *pow(10.0, (Exponent)));        \\\n    }\n\nTEST_TYPICAL_DECIMAL_FRACTION(small, 2, 3);\nTEST_TYPICAL_DECIMAL_FRACTION(small_negative_mantissa, 2, -3);\nTEST_TYPICAL_DECIMAL_FRACTION(small_negative_exponent, -2, 3);\nTEST_TYPICAL_DECIMAL_FRACTION(small_negative_exponent_and_mantissa, -2, -3);\nTEST_TYPICAL_DECIMAL_FRACTION(big_exponent, 100, 2);\nTEST_TYPICAL_DECIMAL_FRACTION(big_negative_exponent, -100, 2);\nTEST_TYPICAL_DECIMAL_FRACTION(big_negative_exponent_and_mantissa, -100, -2);\n\nAVS_UNIT_TEST(cbor_decoder, decimal_fraction_tag_after_tag) {\n    static const char data[] = \"\\xC4\\xC4\\x82\\x02\\x03\";\n    SCOPED_TEST_ENV(data, sizeof(data) - 1);\n    anjay_json_like_number_t value;\n    ASSERT_FAIL(_anjay_json_like_decoder_number(DECODER, &value));\n}\n\nAVS_UNIT_TEST(cbor_decoder, decimal_fraction_tag_but_no_data) {\n    static const char data[] = \"\\xC4\";\n    SCOPED_TEST_ENV(data, sizeof(data) - 1);\n    anjay_json_like_value_type_t value_type;\n    ASSERT_OK(\n            _anjay_json_like_decoder_current_value_type(DECODER, &value_type));\n    ASSERT_EQ(value_type, ANJAY_JSON_LIKE_VALUE_DOUBLE);\n    anjay_json_like_number_t value;\n    ASSERT_FAIL(_anjay_json_like_decoder_number(DECODER, &value));\n}\n\nstatic const char *read_short_string(anjay_json_like_decoder_t *ctx) {\n    static char short_string[128];\n    avs_stream_outbuf_t stream = AVS_STREAM_OUTBUF_STATIC_INITIALIZER;\n    avs_stream_outbuf_set_buffer(\n            &stream, short_string, sizeof(short_string) - 1);\n    ASSERT_OK(_anjay_json_like_decoder_bytes(ctx, (avs_stream_t *) &stream));\n    short_string[avs_stream_outbuf_offset(&stream)] = '\\0';\n    return short_string;\n}\n\nAVS_UNIT_TEST(cbor_decoder, indefinite_map) {\n    // indefinite_map {\n    //      \"Fun\": true,\n    //      \"Stuff\": -2,\n    // }\n    static const char data[] = \"\\xBF\\x63\"\n                               \"Fun\"\n                               \"\\xF5\\x65\"\n                               \"Stuff\"\n                               \"\\x21\\xFF\";\n    SCOPED_TEST_ENV(data, sizeof(data) - 1);\n    anjay_json_like_value_type_t type;\n    ASSERT_OK(_anjay_json_like_decoder_current_value_type(DECODER, &type));\n    ASSERT_EQ(type, ANJAY_JSON_LIKE_VALUE_MAP);\n\n    ASSERT_OK(_anjay_json_like_decoder_enter_map(DECODER));\n    ASSERT_EQ_BYTES_SIZED(read_short_string(DECODER), \"Fun\", sizeof(\"Fun\"));\n    bool value;\n    ASSERT_OK(_anjay_json_like_decoder_bool(DECODER, &value));\n    ASSERT_EQ(value, true);\n\n    ASSERT_EQ_BYTES_SIZED(read_short_string(DECODER), \"Stuff\", sizeof(\"Stuff\"));\n    anjay_json_like_number_t number;\n    ASSERT_OK(_anjay_json_like_decoder_number(DECODER, &number));\n    ASSERT_EQ(number.type, ANJAY_JSON_LIKE_VALUE_NEGATIVE_INT);\n    ASSERT_EQ(number.value.i64, -2);\n\n    ASSERT_EQ(_anjay_json_like_decoder_state(DECODER),\n              ANJAY_JSON_LIKE_DECODER_STATE_FINISHED);\n}\n\nAVS_UNIT_TEST(cbor_decoder, indefinite_map_with_odd_number_of_items) {\n    // indefinite_map {\n    //      \"Fun\": true,\n    //      \"Stuff\":\n    // }\n    static const char data[] = \"\\xBF\\x63\"\n                               \"Fun\"\n                               \"\\xF5\\x65\"\n                               \"Stuff\"\n                               \"\\xFF\";\n    SCOPED_TEST_ENV(data, sizeof(data) - 1);\n    ASSERT_OK(_anjay_json_like_decoder_enter_map(DECODER));\n    ASSERT_EQ_BYTES_SIZED(read_short_string(DECODER), \"Fun\", sizeof(\"Fun\"));\n    bool value;\n    ASSERT_OK(_anjay_json_like_decoder_bool(DECODER, &value));\n    ASSERT_EQ(value, true);\n\n    ASSERT_EQ_BYTES_SIZED(read_short_string(DECODER), \"Stuff\", sizeof(\"Stuff\"));\n    ASSERT_EQ(_anjay_json_like_decoder_state(DECODER),\n              ANJAY_JSON_LIKE_DECODER_STATE_ERROR);\n}\n\nAVS_UNIT_TEST(cbor_decoder, indefinite_map_that_is_empty) {\n    // indefinite_map {}\n    static const char data[] = \"\\xBF\\xFF\";\n    SCOPED_TEST_ENV(data, sizeof(data) - 1);\n    ASSERT_OK(_anjay_json_like_decoder_enter_map(DECODER));\n    ASSERT_EQ(_anjay_json_like_decoder_nesting_level(DECODER), 0);\n}\n"
  },
  {
    "path": "tests/core/io/cbor/cbor_encoder.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <avsystem/commons/avs_unit_test.h>\n\n#include <avsystem/commons/avs_stream_outbuf.h>\n#include <avsystem/commons/avs_utils.h>\n\nstatic cbor_encoder_t *cbor_encoder_new(avs_stream_t *stream) {\n    cbor_encoder_t *ctx =\n            (cbor_encoder_t *) avs_calloc(1, sizeof(cbor_encoder_t));\n    AVS_UNIT_ASSERT_NOT_NULL(ctx);\n    nested_context_push(ctx, stream, CBOR_CONTEXT_TYPE_ROOT);\n    return ctx;\n}\n\nstatic int cbor_encoder_delete(cbor_encoder_t **ctx) {\n    avs_free(*ctx);\n    *ctx = NULL;\n    return 0;\n}\n\ntypedef struct cbor_test_env {\n    avs_stream_outbuf_t outbuf;\n    char *buf; // heap-allocated to make Valgrind check for out-of-bounds access\n    cbor_encoder_t *encoder;\n} cbor_test_env_t;\n\ntypedef struct {\n    const char *data;\n    size_t size;\n} test_data_t;\n\n#define MAKE_TEST_DATA(Data)     \\\n    (test_data_t) {              \\\n        .data = Data,            \\\n        .size = sizeof(Data) - 1 \\\n    }\n\nstatic void cbor_test_setup(cbor_test_env_t *env, size_t buf_size) {\n    memcpy(&env->outbuf,\n           &AVS_STREAM_OUTBUF_STATIC_INITIALIZER,\n           sizeof(avs_stream_outbuf_t));\n    env->buf = avs_malloc(buf_size);\n    AVS_UNIT_ASSERT_NOT_NULL(env->buf);\n    avs_stream_outbuf_set_buffer(&env->outbuf, env->buf, buf_size);\n    env->encoder = cbor_encoder_new((avs_stream_t *) &env->outbuf);\n}\n\n#define VERIFY_BYTES(Env, Data)                                      \\\n    do {                                                             \\\n        AVS_UNIT_ASSERT_EQUAL(avs_stream_outbuf_offset(&Env.outbuf), \\\n                              sizeof(Data) - 1);                     \\\n        AVS_UNIT_ASSERT_EQUAL_BYTES(Env.buf, Data);                  \\\n    } while (0)\n\nstatic void verify_bytes(cbor_test_env_t *env, test_data_t *data) {\n    AVS_UNIT_ASSERT_EQUAL(avs_stream_outbuf_offset(&env->outbuf), data->size);\n    AVS_UNIT_ASSERT_EQUAL_BYTES_SIZED(env->buf, data->data, data->size);\n}\n\nAVS_UNIT_TEST(cbor_encoder, empty) {\n    cbor_test_env_t env;\n    cbor_test_setup(&env, 32);\n    cbor_encoder_t *encoder = env.encoder;\n\n    AVS_UNIT_ASSERT_SUCCESS(cbor_encoder_delete(&encoder));\n    AVS_UNIT_ASSERT_NULL(encoder);\n    VERIFY_BYTES(env, \"\");\n\n    avs_free(env.buf);\n}\n\nstatic void test_int(int64_t value, test_data_t *data) {\n    cbor_test_env_t env;\n    cbor_test_setup(&env, 32);\n\n    AVS_UNIT_ASSERT_SUCCESS(cbor_encode_int(env.encoder, value));\n    AVS_UNIT_ASSERT_EQUAL(nested_context_top(env.encoder)->size, 1);\n    AVS_UNIT_ASSERT_SUCCESS(cbor_encoder_delete(&env.encoder));\n    AVS_UNIT_ASSERT_NULL(env.encoder);\n\n    verify_bytes(&env, data);\n    avs_free(env.buf);\n};\n\n#define TEST_INT_IMPL(Name, Num, Data)           \\\n    AVS_UNIT_TEST(cbor_encoder, Name) {          \\\n        test_data_t data = MAKE_TEST_DATA(Data); \\\n        test_int(Num, &data);                    \\\n    }\n\n#define TEST_INT(Num, Data) TEST_INT_IMPL(AVS_CONCAT(int, __LINE__), Num, Data);\n\nTEST_INT(0, \"\\x00\")\nTEST_INT(1, \"\\x01\")\nTEST_INT(10, \"\\x0A\")\nTEST_INT(23, \"\\x17\")\nTEST_INT(24, \"\\x18\\x18\")\nTEST_INT(25, \"\\x18\\x19\")\nTEST_INT(100, \"\\x18\\x64\")\nTEST_INT(221, \"\\x18\\xDD\")\nTEST_INT(1000, \"\\x19\\x03\\xE8\")\nTEST_INT(INT16_MAX, \"\\x19\\x7F\\xFF\")\nTEST_INT(INT16_MAX + 1, \"\\x19\\x80\\x00\")\nTEST_INT(UINT16_MAX, \"\\x19\\xFF\\xFF\")\nTEST_INT(UINT16_MAX + 1, \"\\x1A\\x00\\x01\\x00\\x00\")\nTEST_INT(1000000, \"\\x1A\\x00\\x0F\\x42\\x40\")\nTEST_INT(INT32_MAX, \"\\x1A\\x7F\\xFF\\xFF\\xFF\")\nTEST_INT((int64_t) INT32_MAX + 1, \"\\x1A\\x80\\x00\\x00\\x00\")\nTEST_INT(UINT32_MAX, \"\\x1A\\xFF\\xFF\\xFF\\xFF\")\nTEST_INT((int64_t) UINT32_MAX + 1, \"\\x1B\\x00\\x00\\x00\\x01\\x00\\x00\\x00\\x00\")\nTEST_INT(INT64_MAX, \"\\x1B\\x7F\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\")\n\nTEST_INT(-1, \"\\x20\")\nTEST_INT(-10, \"\\x29\")\nTEST_INT(-24, \"\\x37\")\nTEST_INT(-25, \"\\x38\\x18\")\nTEST_INT(-100, \"\\x38\\x63\")\nTEST_INT(-256, \"\\x38\\xFF\")\nTEST_INT(-257, \"\\x39\\x01\\x00\")\nTEST_INT(-1000, \"\\x39\\x03\\xE7\")\nTEST_INT(INT64_MIN, \"\\x3B\\x7F\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\")\n\nAVS_UNIT_TEST(cbor_encoder, uint64_max) {\n    cbor_test_env_t env;\n    cbor_test_setup(&env, 32);\n    cbor_encoder_t *encoder = env.encoder;\n\n    AVS_UNIT_ASSERT_SUCCESS(cbor_encode_uint(encoder, UINT64_MAX));\n    AVS_UNIT_ASSERT_EQUAL(nested_context_top(encoder)->size, 1);\n    AVS_UNIT_ASSERT_SUCCESS(cbor_encoder_delete(&encoder));\n    AVS_UNIT_ASSERT_NULL(encoder);\n\n    AVS_UNIT_ASSERT_EQUAL_BYTES(env.buf,\n                                \"\\x1B\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\");\n\n    avs_free(env.buf);\n}\n\n#define TEST_BOOL(Val, Data)                                         \\\n    AVS_UNIT_TEST(cbor_encoder, bool_##Val) {                        \\\n        cbor_test_env_t env;                                         \\\n        cbor_test_setup(&env, 32);                                   \\\n        cbor_encoder_t *encoder = env.encoder;                       \\\n        test_data_t expected = MAKE_TEST_DATA(Data);                 \\\n                                                                     \\\n        AVS_UNIT_ASSERT_SUCCESS(cbor_encode_bool(encoder, Val));     \\\n        AVS_UNIT_ASSERT_EQUAL(nested_context_top(encoder)->size, 1); \\\n        AVS_UNIT_ASSERT_SUCCESS(cbor_encoder_delete(&encoder));      \\\n        verify_bytes(&env, &expected);                               \\\n                                                                     \\\n        avs_free(env.buf);                                           \\\n    }\n\nTEST_BOOL(true, \"\\xF5\")\nTEST_BOOL(false, \"\\xF4\")\nTEST_BOOL(1, \"\\xF5\")\nTEST_BOOL(0, \"\\xF4\")\nTEST_BOOL(42, \"\\xF5\")\n\nstatic void test_string(const char *input, test_data_t *expected) {\n    cbor_test_env_t env;\n    cbor_test_setup(&env, 512);\n    cbor_encoder_t *encoder = env.encoder;\n\n    AVS_UNIT_ASSERT_SUCCESS(cbor_encode_string(encoder, input));\n    AVS_UNIT_ASSERT_EQUAL(nested_context_top(encoder)->size, 1);\n    AVS_UNIT_ASSERT_SUCCESS(cbor_encoder_delete(&encoder));\n    verify_bytes(&env, expected);\n\n    avs_free(env.buf);\n}\n\n#define TEST_STRING_NAMED(Name, Text, ExpectedHeader)               \\\n    AVS_UNIT_TEST(cbor_encoder, string_##Name) {                    \\\n        test_data_t expected = MAKE_TEST_DATA(ExpectedHeader Text); \\\n        test_string(Text, &expected);                               \\\n    }\n\n#define TEST_STRING(Text, ExpectedHeader) \\\n    TEST_STRING_NAMED(Text, AVS_QUOTE(Text), ExpectedHeader)\n\nTEST_STRING(, \"\\x60\")\nTEST_STRING(a, \"\\x61\")\nTEST_STRING(IETF, \"\\x64\")\nTEST_STRING_NAMED(dzborg, \"DZBORG:DD\", \"\\x69\")\nTEST_STRING_NAMED(escaped, \"\\\"\\\\\", \"\\x62\")\nTEST_STRING_NAMED(\n        255chars,\n        \"oxazxnwrmthhloqwchkumektviptdztidxeelvgffcdoodpijsbikkkvrmtrxddmpidudj\"\n        \"ptfmqqgfkjlrsqrmagculcyjjbmxombbiqdhimwafcfaswhmmykezictjpidmxtoqnjmja\"\n        \"xzgvqdybtgneqsmlzhxqeuhibjopnregwykgpcdogguszhhffdeixispwfnwcufnmsxycy\"\n        \"qxquiqsuqwgkwafkeedsacxvvjwhpokaabxelqxzqutwa\",\n        \"\\x78\\xFF\")\nTEST_STRING_NAMED(\n        256chars,\n        \"oqndmcvrgmvswuvcskllakhhersslftmmuwwwzirelnbtnlmvmezrqktqqnlpldqwyvtbv\"\n        \"yryqcurqxnhzxoladzzmnumrifhqbcywuetmuyyjxpiwquzrekjxzgiknqcmwzwuzxvrxb\"\n        \"zycnfrhyigwgkmbtlfyrhkolnsikvdelvkztkvonimtmvrivrnevgyxvjdjzvobsiufbwt\"\n        \"atfqeavhvfdfbnsumtletbaheyacrkwgectlrdrizenuvi\",\n        \"\\x79\\x01\\x00\")\n\nstatic void test_float(float value, test_data_t *expected) {\n    cbor_test_env_t env;\n    cbor_test_setup(&env, 32);\n    cbor_encoder_t *encoder = env.encoder;\n\n    AVS_UNIT_ASSERT_SUCCESS(cbor_encode_double(encoder, value));\n    AVS_UNIT_ASSERT_EQUAL(nested_context_top(encoder)->size, 1);\n    AVS_UNIT_ASSERT_SUCCESS(cbor_encoder_delete(&encoder));\n    verify_bytes(&env, expected);\n\n    avs_free(env.buf);\n}\n\n#define TEST_FLOAT_IMPL(Name, Type, Num, Data)       \\\n    AVS_UNIT_TEST(cbor_encoder, Name) {              \\\n        test_data_t expected = MAKE_TEST_DATA(Data); \\\n        test_float(Num, &expected);                  \\\n    }\n\n#define TEST_FLOAT(Num, Data) \\\n    TEST_FLOAT_IMPL(AVS_CONCAT(float, __LINE__), float, Num, Data)\n\n// TODO: Make those tests work as defined in RFC7049 Appendix C, it requires\n// half floats support.\n// TEST_FLOAT(0.0, \"\\xF9\\x00\\x00\")\n// TEST_FLOAT(-0.0, \"\\xF9\\x80\\x00\")\n// TEST_FLOAT(1.0, \"\\xF9\\x3C\\x00\")\nTEST_FLOAT(-0.0, \"\\xFA\\x80\\x00\\x00\\x00\")\nTEST_FLOAT(100000.0, \"\\xFA\\x47\\xC3\\x50\\x00\")\n\nstatic void test_double(double value, test_data_t *expected) {\n    cbor_test_env_t env;\n    cbor_test_setup(&env, 32);\n    cbor_encoder_t *encoder = env.encoder;\n\n    AVS_UNIT_ASSERT_SUCCESS(cbor_encode_double(encoder, value));\n    AVS_UNIT_ASSERT_EQUAL(nested_context_top(encoder)->size, 1);\n    AVS_UNIT_ASSERT_SUCCESS(cbor_encoder_delete(&encoder));\n    verify_bytes(&env, expected);\n\n    avs_free(env.buf);\n}\n\n#define TEST_DOUBLE_IMPL(Name, Type, Num, Data)      \\\n    AVS_UNIT_TEST(cbor_encoder, Name) {              \\\n        test_data_t expected = MAKE_TEST_DATA(Data); \\\n        test_double(Num, &expected);                 \\\n    }\n\n#define TEST_DOUBLE(Num, Data) \\\n    TEST_DOUBLE_IMPL(AVS_CONCAT(double, __LINE__), double, Num, Data)\n\nTEST_DOUBLE(1.1, \"\\xFB\\x3F\\xF1\\x99\\x99\\x99\\x99\\x99\\x9A\")\nTEST_DOUBLE(100000.0, \"\\xFA\\x47\\xC3\\x50\\x00\")\nTEST_DOUBLE(1.0e+300, \"\\xFB\\x7E\\x37\\xE4\\x3C\\x88\\x00\\x75\\x9C\")\nTEST_DOUBLE(-4.1, \"\\xFB\\xC0\\x10\\x66\\x66\\x66\\x66\\x66\\x66\")\n\nstatic void test_bytes(test_data_t *input, test_data_t *expected) {\n    cbor_test_env_t env;\n    cbor_test_setup(&env, 512);\n    cbor_encoder_t *encoder = env.encoder;\n\n    AVS_UNIT_ASSERT_SUCCESS(cbor_bytes_begin(encoder, input->size));\n    AVS_UNIT_ASSERT_SUCCESS(\n            cbor_bytes_append(encoder, input->data, input->size));\n    AVS_UNIT_ASSERT_SUCCESS(cbor_bytes_end(encoder));\n    AVS_UNIT_ASSERT_EQUAL(nested_context_top(encoder)->size, 1);\n    AVS_UNIT_ASSERT_SUCCESS(cbor_encoder_delete(&encoder));\n    verify_bytes(&env, expected);\n\n    avs_free(env.buf);\n}\n\n#define TEST_BYTES(Name, Data, ExpectedHeader)                      \\\n    AVS_UNIT_TEST(cbor_encoder, bytes_##Name) {                     \\\n        test_data_t input = MAKE_TEST_DATA(Data);                   \\\n        test_data_t expected = MAKE_TEST_DATA(ExpectedHeader Data); \\\n        test_bytes(&input, &expected);                              \\\n    }\n\nTEST_BYTES(0bytes, \"\", \"\\x40\")\nTEST_BYTES(4bytes, \"\\x01\\x02\\x03\\x04\", \"\\x44\")\nTEST_BYTES(5bytes, \"\\x64\\x49\\x45\\x54\\x46\", \"\\x45\")\nTEST_BYTES(23bytes,\n           \"\\x84\\x11\\xDB\\xB8\\xAA\\xF7\\xC3\\xEF\\xBA\\xC0\\x2F\\x50\\xC2\\x88\\xAF\\x1B\"\n           \"\\x8F\\xD2\\xE4\\xC9\\x5A\\xD7\\xEC\",\n           \"\\x57\")\nTEST_BYTES(24bytes,\n           \"\\x46\\x0A\\x00\\x2D\\xC0\\x68\\xD4\\xE5\\x8D\\xDC\\x37\\x5D\\xF0\\x83\\xCD\\xD8\"\n           \"\\x3F\\xAC\\x35\\x03\\x16\\x1E\\x32\\x0A\",\n           \"\\x58\\x18\")\nTEST_BYTES(\n        255bytes,\n        \"\\xD6\\xFB\\x20\\x80\\xCE\\x44\\x31\\x3B\\xE1\\x63\\xD9\\x89\\x36\\x90\\x06\\x56\\x9C\"\n        \"\\xF6\\x4C\\x24\\x04\\x34\\xEA\\x8D\\xF3\\xF1\\x40\\xEA\\x3A\\x41\\xE1\\x57\\xFF\\x92\"\n        \"\\xCC\\xAE\\x42\\x10\\x27\\x48\\x47\\x6E\\x7C\\x11\\x9B\\x5A\\x21\\x5A\\x51\\xF7\\x45\"\n        \"\\xB0\\x5E\\x3B\\x81\\x26\\xE9\\xB0\\x8A\\xF1\\x93\\xCA\\xA6\\xB3\\xD7\\xE0\\x16\\xEC\"\n        \"\\xBF\\xF5\\x21\\x16\\xC7\\x50\\x6C\\x9A\\xA8\\x8E\\x49\\xA9\\xF1\\x59\\x8C\\xC3\\x80\"\n        \"\\x0F\\x34\\x21\\x26\\xCD\\xB5\\x30\\xEE\\xC5\\x48\\xBB\\x6F\\x03\\x62\\xC2\\x7B\\x21\"\n        \"\\x60\\x08\\xE2\\x58\\xD3\\xE0\\x64\\x3A\\x4B\\x59\\x16\\xFD\\x8E\\x05\\x41\\x46\\xBD\"\n        \"\\xFB\\xC8\\x7B\\x4D\\xC3\\x38\\x01\\x94\\x31\\x50\\xFC\\xE7\\xBE\\x7A\\xDA\\xD6\\x56\"\n        \"\\x74\\x1C\\x7F\\x75\\xB1\\x59\\x15\\x4E\\x86\\x8E\\x71\\xB0\\xFF\\x69\\x60\\xDC\\xBC\"\n        \"\\x52\\xB6\\xEA\\xFA\\x4E\\x09\\xD3\\xB8\\x40\\x85\\x7D\\xDA\\xB1\\xC8\\xFF\\x65\\xB7\"\n        \"\\xFF\\xA9\\xAB\\x9E\\x67\\x04\\x0A\\x3A\\x1B\\xE7\\x77\\x53\\x9A\\xA1\\x6D\\xDA\\xA0\"\n        \"\\xBB\\xC0\\x91\\xA1\\x38\\x93\\x0E\\x33\\xDF\\x4B\\x9E\\x83\\x0C\\xF4\\x73\\x1E\\xD6\"\n        \"\\x83\\x92\\x54\\x3D\\x73\\x1F\\xEC\\xCA\\xD9\\x1F\\xE2\\x3D\\x57\\xD1\\x7C\\x54\\x88\"\n        \"\\xFB\\x3E\\xCF\\x7E\\x8A\\x29\\x98\\x89\\x4A\\xBB\\x2F\\xE5\\xB1\\x36\\x2B\\x8B\\x8F\"\n        \"\\xBF\\x46\\x19\\x74\\x1D\\xC4\\x7B\\xFB\\x52\\xA4\\x32\\x47\\xA7\\x5C\\xA1\\x5C\\x1A\",\n        \"\\x58\\xFF\")\nTEST_BYTES(256bytes,\n           \"\\xD8\\xE2\\xE6\\xED\\x90\\x05\\x29\\x3B\\x17\\xAC\\x8D\\x33\\x93\\x52\\xD9\\x6B\"\n           \"\\xF2\\xFB\\x20\\x74\\x3E\\x9C\\xEF\\xAD\\xBB\\x03\\xCE\\x0E\\xC5\\xBD\\x0D\\x2F\"\n           \"\\x42\\x6D\\x1C\\xD6\\xDB\\x29\\xF8\\xF6\\xA4\\x96\\x3D\\x7A\\x8A\\xEE\\xE6\\xF2\"\n           \"\\x56\\x1C\\xBE\\xCE\\x71\\x30\\x3B\\xEC\\xC9\\x86\\x71\\x96\\x86\\x51\\xA2\\xCA\"\n           \"\\x23\\x8A\\x0B\\x1D\\x67\\x3C\\x50\\xB8\\x66\\x4C\\x64\\x8C\\x31\\xCD\\x11\\x05\"\n           \"\\xCA\\x56\\x4B\\xBB\\x79\\x18\\x8F\\x5B\\xF1\\xE0\\x1E\\x85\\x38\\xBE\\x7A\\x6F\"\n           \"\\x30\\x4A\\xFD\\xB3\\x1B\\xA9\\x52\\xB4\\x0E\\x95\\x73\\x83\\xA5\\x33\\x9F\\x0C\"\n           \"\\x04\\x2E\\x33\\xB3\\xD5\\x0B\\x6E\\x02\\x0C\\xC7\\x0D\\x1A\\x1A\\x48\\x0C\\x92\"\n           \"\\x1B\\x62\\x83\\xCF\\xC1\\x5C\\x90\\xBC\\x83\\x3B\\x92\\xBF\\x8E\\xCE\\x7C\\xD6\"\n           \"\\x99\\x77\\xF2\\x66\\x92\\x0C\\xC6\\x0A\\x11\\x80\\xBE\\x03\\x59\\x23\\x89\\xF6\"\n           \"\\xEF\\x3A\\x5A\\x07\\xEB\\xEF\\x47\\xF0\\x1F\\xF0\\xB4\\x96\\x01\\x1B\\xE9\\x51\"\n           \"\\x40\\x70\\x16\\xDD\\xB2\\x9B\\xEB\\x42\\xAC\\x6E\\x45\\xE6\\xAE\\x8F\\xCE\\x9A\"\n           \"\\xC4\\xCB\\x09\\xE7\\x2C\\xE4\\x48\\x86\\xF0\\x9C\\x56\\x2C\\xEF\\x1B\\xD0\\x8E\"\n           \"\\x92\\xD4\\x61\\x15\\x46\\x76\\x19\\x32\\xDF\\x9F\\x98\\xC0\\x0A\\xF7\\xAE\\xA9\"\n           \"\\xD7\\x61\\xEC\\x8B\\x78\\xE5\\xAA\\xC6\\x0B\\x5D\\x98\\x1D\\x86\\xE6\\x57\\x67\"\n           \"\\x97\\x56\\x82\\x29\\xFF\\x8F\\x61\\x6C\\xA5\\xD0\\x08\\x20\\xAE\\x49\\x5B\\x04\",\n           \"\\x59\\x01\\x00\")\n\n// {}\nAVS_UNIT_TEST(cbor_encoder, empty_map) {\n    cbor_test_env_t env;\n    cbor_test_setup(&env, 32);\n    cbor_encoder_t *encoder = env.encoder;\n\n    AVS_UNIT_ASSERT_SUCCESS(cbor_definite_map_begin(encoder, 0));\n    AVS_UNIT_ASSERT_SUCCESS(cbor_definite_map_end(encoder));\n    AVS_UNIT_ASSERT_EQUAL(nested_context_top(encoder)->size, 1);\n    AVS_UNIT_ASSERT_SUCCESS(cbor_encoder_delete(&encoder));\n    VERIFY_BYTES(env, \"\\xA0\");\n\n    avs_free(env.buf);\n}\n\n// {\"a\": 1}\nAVS_UNIT_TEST(cbor_encoder, map1el) {\n    cbor_test_env_t env;\n    cbor_test_setup(&env, 32);\n    cbor_encoder_t *encoder = env.encoder;\n\n    AVS_UNIT_ASSERT_SUCCESS(cbor_definite_map_begin(encoder, 1));\n    AVS_UNIT_ASSERT_SUCCESS(cbor_encode_string(encoder, \"a\"));\n    AVS_UNIT_ASSERT_SUCCESS(cbor_encode_int(encoder, 1));\n    AVS_UNIT_ASSERT_SUCCESS(cbor_definite_map_end(encoder));\n    AVS_UNIT_ASSERT_EQUAL(nested_context_top(encoder)->size, 1);\n    AVS_UNIT_ASSERT_SUCCESS(cbor_encoder_delete(&encoder));\n    VERIFY_BYTES(env, \"\\xA1\\x61\\x61\\x01\");\n\n    avs_free(env.buf);\n}\n\n// {1.1: \"test\", 256: 65536}\nAVS_UNIT_TEST(cbor_encoder, map2el) {\n    cbor_test_env_t env;\n    cbor_test_setup(&env, 32);\n    cbor_encoder_t *encoder = env.encoder;\n\n    AVS_UNIT_ASSERT_SUCCESS(cbor_definite_map_begin(encoder, 2));\n    AVS_UNIT_ASSERT_SUCCESS(cbor_encode_double(encoder, 1.1));\n    AVS_UNIT_ASSERT_SUCCESS(cbor_encode_string(encoder, \"test\"));\n    AVS_UNIT_ASSERT_SUCCESS(cbor_encode_int(encoder, 256));\n    AVS_UNIT_ASSERT_SUCCESS(cbor_encode_int(encoder, 65536));\n    AVS_UNIT_ASSERT_SUCCESS(cbor_definite_map_end(encoder));\n    AVS_UNIT_ASSERT_EQUAL(nested_context_top(encoder)->size, 1);\n    AVS_UNIT_ASSERT_SUCCESS(cbor_encoder_delete(&encoder));\n    VERIFY_BYTES(env,\n                 \"\\xA2\\xFB\\x3F\\xF1\\x99\\x99\\x99\\x99\\x99\\x9A\\x64\\x74\\x65\\x73\\x74\"\n                 \"\\x19\\x01\\x00\\x1A\\x00\\x01\\x00\\x00\");\n\n    avs_free(env.buf);\n}\n\n// DEFINITE ARRAY TESTS\n\n// [1, \"cwiercz\", 200]\nAVS_UNIT_TEST(cbor_encoder, definite_array) {\n    cbor_test_env_t env;\n    cbor_test_setup(&env, 32);\n    cbor_encoder_t *encoder = env.encoder;\n\n    AVS_UNIT_ASSERT_SUCCESS(cbor_known_length_definite_array_begin(encoder, 3));\n    AVS_UNIT_ASSERT_SUCCESS(cbor_encode_int(encoder, 1));\n    AVS_UNIT_ASSERT_SUCCESS(cbor_encode_string(encoder, \"cwiercz\"));\n    AVS_UNIT_ASSERT_SUCCESS(cbor_encode_int(encoder, 200));\n    AVS_UNIT_ASSERT_SUCCESS(cbor_definite_array_end(encoder));\n    AVS_UNIT_ASSERT_EQUAL(nested_context_top(encoder)->size, 1);\n    AVS_UNIT_ASSERT_SUCCESS(cbor_encoder_delete(&encoder));\n    VERIFY_BYTES(env, \"\\x83\\x01\\x67\\x63\\x77\\x69\\x65\\x72\\x63\\x7A\\x18\\xC8\");\n\n    avs_free(env.buf);\n}\n\n// [1, \"cwiercz\", 200]\nAVS_UNIT_TEST(cbor_encoder, cached_definite_array) {\n    cbor_test_env_t env;\n    cbor_test_setup(&env, 32);\n    cbor_encoder_t *encoder = env.encoder;\n\n    AVS_UNIT_ASSERT_SUCCESS(cbor_unknown_length_definite_array_begin(encoder));\n    AVS_UNIT_ASSERT_SUCCESS(cbor_encode_int(encoder, 1));\n    AVS_UNIT_ASSERT_SUCCESS(cbor_encode_string(encoder, \"cwiercz\"));\n    AVS_UNIT_ASSERT_SUCCESS(cbor_encode_int(encoder, 200));\n    AVS_UNIT_ASSERT_SUCCESS(cbor_definite_array_end(encoder));\n    AVS_UNIT_ASSERT_EQUAL(nested_context_top(encoder)->size, 1);\n    AVS_UNIT_ASSERT_SUCCESS(cbor_encoder_delete(&encoder));\n    VERIFY_BYTES(env, \"\\x83\\x01\\x67\\x63\\x77\\x69\\x65\\x72\\x63\\x7A\\x18\\xC8\");\n\n    avs_free(env.buf);\n}\n\n// []\nAVS_UNIT_TEST(cbor_encoder, empty_definite_array) {\n    cbor_test_env_t env;\n    cbor_test_setup(&env, 32);\n    cbor_encoder_t *encoder = env.encoder;\n\n    AVS_UNIT_ASSERT_SUCCESS(cbor_known_length_definite_array_begin(encoder, 0));\n    AVS_UNIT_ASSERT_SUCCESS(cbor_definite_array_end(encoder));\n    AVS_UNIT_ASSERT_EQUAL(nested_context_top(encoder)->size, 1);\n    AVS_UNIT_ASSERT_SUCCESS(cbor_encoder_delete(&encoder));\n\n    VERIFY_BYTES(env, \"\\x80\");\n\n    avs_free(env.buf);\n}\n\n// []\nAVS_UNIT_TEST(cbor_encoder, cached_empty_definite_array) {\n    cbor_test_env_t env;\n    cbor_test_setup(&env, 32);\n    cbor_encoder_t *encoder = env.encoder;\n\n    AVS_UNIT_ASSERT_SUCCESS(cbor_unknown_length_definite_array_begin(encoder));\n    AVS_UNIT_ASSERT_SUCCESS(cbor_definite_array_end(encoder));\n    AVS_UNIT_ASSERT_EQUAL(nested_context_top(encoder)->size, 1);\n    AVS_UNIT_ASSERT_SUCCESS(cbor_encoder_delete(&encoder));\n\n    VERIFY_BYTES(env, \"\\x80\");\n\n    avs_free(env.buf);\n}\n\n// [1, [2]]\nAVS_UNIT_TEST(cbor_encoder, nested_definite_arrays1) {\n    cbor_test_env_t env;\n    cbor_test_setup(&env, 32);\n    cbor_encoder_t *encoder = env.encoder;\n\n    AVS_UNIT_ASSERT_SUCCESS(cbor_known_length_definite_array_begin(encoder, 2));\n    AVS_UNIT_ASSERT_SUCCESS(cbor_encode_int(encoder, 1));\n    AVS_UNIT_ASSERT_SUCCESS(cbor_unknown_length_definite_array_begin(encoder));\n    AVS_UNIT_ASSERT_SUCCESS(cbor_encode_int(encoder, 2));\n    AVS_UNIT_ASSERT_SUCCESS(cbor_definite_array_end(encoder));\n    AVS_UNIT_ASSERT_SUCCESS(cbor_definite_array_end(encoder));\n    AVS_UNIT_ASSERT_EQUAL(nested_context_top(encoder)->size, 1);\n    AVS_UNIT_ASSERT_SUCCESS(cbor_encoder_delete(&encoder));\n\n    VERIFY_BYTES(env, \"\\x82\\x01\\x81\\x02\");\n\n    avs_free(env.buf);\n}\n\n// [1, 2, [3, 4, 5]]\nAVS_UNIT_TEST(cbor_encoder, nested_definite_arrays2) {\n    cbor_test_env_t env;\n    cbor_test_setup(&env, 32);\n    cbor_encoder_t *encoder = env.encoder;\n\n    AVS_UNIT_ASSERT_SUCCESS(cbor_unknown_length_definite_array_begin(encoder));\n    AVS_UNIT_ASSERT_SUCCESS(cbor_encode_int(encoder, 1));\n    AVS_UNIT_ASSERT_SUCCESS(cbor_encode_int(encoder, 2));\n    AVS_UNIT_ASSERT_SUCCESS(cbor_known_length_definite_array_begin(encoder, 3));\n    AVS_UNIT_ASSERT_SUCCESS(cbor_encode_int(encoder, 3));\n    AVS_UNIT_ASSERT_SUCCESS(cbor_encode_int(encoder, 4));\n    AVS_UNIT_ASSERT_SUCCESS(cbor_encode_int(encoder, 5));\n    AVS_UNIT_ASSERT_SUCCESS(cbor_definite_array_end(encoder));\n    AVS_UNIT_ASSERT_SUCCESS(cbor_definite_array_end(encoder));\n    AVS_UNIT_ASSERT_EQUAL(nested_context_top(encoder)->size, 1);\n    AVS_UNIT_ASSERT_SUCCESS(cbor_encoder_delete(&encoder));\n\n    VERIFY_BYTES(env, \"\\x83\\x01\\x02\\x83\\x03\\x04\\x05\");\n\n    avs_free(env.buf);\n}\n\n// {[h'00', h'11']}\nAVS_UNIT_TEST(cbor_encoder, map_with_array_with_bytes) {\n    cbor_test_env_t env;\n    cbor_test_setup(&env, 32);\n    cbor_encoder_t *encoder = env.encoder;\n\n    AVS_UNIT_ASSERT_SUCCESS(cbor_definite_map_begin(encoder, 1));\n    AVS_UNIT_ASSERT_SUCCESS(cbor_encode_string(encoder, \"array\"));\n    AVS_UNIT_ASSERT_SUCCESS(cbor_known_length_definite_array_begin(encoder, 2));\n    AVS_UNIT_ASSERT_SUCCESS(cbor_bytes_begin(encoder, 1));\n    AVS_UNIT_ASSERT_SUCCESS(cbor_bytes_append(encoder, \"\\x00\", 1));\n    AVS_UNIT_ASSERT_SUCCESS(cbor_bytes_end(encoder));\n    AVS_UNIT_ASSERT_SUCCESS(cbor_bytes_begin(encoder, 1));\n    AVS_UNIT_ASSERT_SUCCESS(cbor_bytes_append(encoder, \"\\x11\", 1));\n    AVS_UNIT_ASSERT_SUCCESS(cbor_bytes_end(encoder));\n    AVS_UNIT_ASSERT_SUCCESS(cbor_definite_array_end(encoder));\n    AVS_UNIT_ASSERT_SUCCESS(cbor_definite_map_end(encoder));\n    AVS_UNIT_ASSERT_EQUAL(nested_context_top(encoder)->size, 1);\n    AVS_UNIT_ASSERT_SUCCESS(cbor_encoder_delete(&encoder));\n\n    VERIFY_BYTES(env, \"\\xA1\\x65\\x61\\x72\\x72\\x61\\x79\\x82\\x41\\x00\\x41\\x11\");\n\n    avs_free(env.buf);\n}\n\n// [[]]\nAVS_UNIT_TEST(cbor_encoder, empty_nested_arrays) {\n    cbor_test_env_t env;\n    cbor_test_setup(&env, 32);\n    cbor_encoder_t *encoder = env.encoder;\n\n    AVS_UNIT_ASSERT_SUCCESS(cbor_unknown_length_definite_array_begin(encoder));\n    AVS_UNIT_ASSERT_SUCCESS(cbor_unknown_length_definite_array_begin(encoder));\n    AVS_UNIT_ASSERT_SUCCESS(cbor_definite_array_end(encoder));\n    AVS_UNIT_ASSERT_SUCCESS(cbor_definite_array_end(encoder));\n    AVS_UNIT_ASSERT_EQUAL(nested_context_top(encoder)->size, 1);\n    AVS_UNIT_ASSERT_SUCCESS(cbor_encoder_delete(&encoder));\n\n    VERIFY_BYTES(env, \"\\x81\\x80\");\n\n    avs_free(env.buf);\n}\n\n// [1, [2, [3]]]\nAVS_UNIT_TEST(cbor_encoder, double_nested_arrays) {\n    cbor_test_env_t env;\n    cbor_test_setup(&env, 32);\n    cbor_encoder_t *encoder = env.encoder;\n\n    AVS_UNIT_ASSERT_SUCCESS(cbor_unknown_length_definite_array_begin(encoder));\n    AVS_UNIT_ASSERT_SUCCESS(cbor_encode_int(encoder, 1));\n    AVS_UNIT_ASSERT_SUCCESS(cbor_unknown_length_definite_array_begin(encoder));\n    AVS_UNIT_ASSERT_SUCCESS(cbor_encode_int(encoder, 2));\n    AVS_UNIT_ASSERT_SUCCESS(cbor_unknown_length_definite_array_begin(encoder));\n    AVS_UNIT_ASSERT_SUCCESS(cbor_encode_int(encoder, 3));\n    AVS_UNIT_ASSERT_SUCCESS(cbor_definite_array_end(encoder));\n    AVS_UNIT_ASSERT_SUCCESS(cbor_definite_array_end(encoder));\n    AVS_UNIT_ASSERT_SUCCESS(cbor_definite_array_end(encoder));\n\n    AVS_UNIT_ASSERT_EQUAL(nested_context_top(encoder)->size, 1);\n    AVS_UNIT_ASSERT_SUCCESS(cbor_encoder_delete(&encoder));\n    VERIFY_BYTES(env, \"\\x82\\x01\\x82\\x02\\x81\\x03\");\n\n    avs_free(env.buf);\n}\n\n// [[1, 2], [3, 4], [5, 6]]\nAVS_UNIT_TEST(cbor_encoder, three_nested_arrays) {\n    cbor_test_env_t env;\n    cbor_test_setup(&env, 32);\n    cbor_encoder_t *encoder = env.encoder;\n\n    AVS_UNIT_ASSERT_SUCCESS(cbor_unknown_length_definite_array_begin(encoder));\n    AVS_UNIT_ASSERT_SUCCESS(cbor_unknown_length_definite_array_begin(encoder));\n    AVS_UNIT_ASSERT_SUCCESS(cbor_encode_uint(encoder, 1));\n    AVS_UNIT_ASSERT_SUCCESS(cbor_encode_int(encoder, 2));\n    AVS_UNIT_ASSERT_SUCCESS(cbor_definite_array_end(encoder));\n    AVS_UNIT_ASSERT_SUCCESS(cbor_unknown_length_definite_array_begin(encoder));\n    AVS_UNIT_ASSERT_SUCCESS(cbor_encode_uint(encoder, 3));\n    AVS_UNIT_ASSERT_SUCCESS(cbor_encode_int(encoder, 4));\n    AVS_UNIT_ASSERT_SUCCESS(cbor_definite_array_end(encoder));\n    AVS_UNIT_ASSERT_SUCCESS(cbor_unknown_length_definite_array_begin(encoder));\n    AVS_UNIT_ASSERT_SUCCESS(cbor_encode_uint(encoder, 5));\n    AVS_UNIT_ASSERT_SUCCESS(cbor_encode_int(encoder, 6));\n    AVS_UNIT_ASSERT_SUCCESS(cbor_definite_array_end(encoder));\n    AVS_UNIT_ASSERT_SUCCESS(cbor_definite_array_end(encoder));\n\n    AVS_UNIT_ASSERT_EQUAL(nested_context_top(encoder)->size, 1);\n    AVS_UNIT_ASSERT_SUCCESS(cbor_encoder_delete(&encoder));\n    VERIFY_BYTES(env, \"\\x83\\x82\\x01\\x02\\x82\\x03\\x04\\x82\\x05\\x06\");\n\n    avs_free(env.buf);\n}\n\n// [{\"A\": 1}]\nAVS_UNIT_TEST(cbor_encoder, array_with_one_map) {\n    cbor_test_env_t env;\n    cbor_test_setup(&env, 32);\n    cbor_encoder_t *encoder = env.encoder;\n\n    AVS_UNIT_ASSERT_SUCCESS(cbor_unknown_length_definite_array_begin(encoder));\n    AVS_UNIT_ASSERT_SUCCESS(cbor_definite_map_begin(encoder, 1));\n    AVS_UNIT_ASSERT_SUCCESS(cbor_encode_string(encoder, \"A\"));\n    AVS_UNIT_ASSERT_SUCCESS(cbor_encode_int(encoder, 1));\n    AVS_UNIT_ASSERT_SUCCESS(cbor_definite_map_end(encoder));\n    AVS_UNIT_ASSERT_SUCCESS(cbor_definite_array_end(encoder));\n\n    AVS_UNIT_ASSERT_EQUAL(nested_context_top(encoder)->size, 1);\n    AVS_UNIT_ASSERT_SUCCESS(cbor_encoder_delete(&encoder));\n    VERIFY_BYTES(env, \"\\x81\\xA1\\x61\\x41\\x01\");\n\n    avs_free(env.buf);\n}\n\n// [{\"A\": 1}, {\"B\": 2}]\nAVS_UNIT_TEST(cbor_encoder, array_with_two_maps) {\n    cbor_test_env_t env;\n    cbor_test_setup(&env, 32);\n    cbor_encoder_t *encoder = env.encoder;\n\n    AVS_UNIT_ASSERT_SUCCESS(cbor_unknown_length_definite_array_begin(encoder));\n    AVS_UNIT_ASSERT_SUCCESS(cbor_definite_map_begin(encoder, 1));\n    AVS_UNIT_ASSERT_SUCCESS(cbor_encode_string(encoder, \"A\"));\n    AVS_UNIT_ASSERT_SUCCESS(cbor_encode_int(encoder, 1));\n    AVS_UNIT_ASSERT_SUCCESS(cbor_definite_map_end(encoder));\n    AVS_UNIT_ASSERT_SUCCESS(cbor_definite_map_begin(encoder, 1));\n    AVS_UNIT_ASSERT_SUCCESS(cbor_encode_string(encoder, \"B\"));\n    AVS_UNIT_ASSERT_SUCCESS(cbor_encode_int(encoder, 2));\n    AVS_UNIT_ASSERT_SUCCESS(cbor_definite_map_end(encoder));\n    AVS_UNIT_ASSERT_SUCCESS(cbor_definite_array_end(encoder));\n\n    AVS_UNIT_ASSERT_EQUAL(nested_context_top(encoder)->size, 1);\n    AVS_UNIT_ASSERT_SUCCESS(cbor_encoder_delete(&encoder));\n    VERIFY_BYTES(env, \"\\x82\\xA1\\x61\\x41\\x01\\xA1\\x61\\x42\\x02\");\n\n    avs_free(env.buf);\n}\n\n// [h'AABBCC', h'DDEEFF']\nAVS_UNIT_TEST(cbor_encoder, array_with_bytes) {\n    cbor_test_env_t env;\n    cbor_test_setup(&env, 32);\n    cbor_encoder_t *encoder = env.encoder;\n\n    AVS_UNIT_ASSERT_SUCCESS(cbor_unknown_length_definite_array_begin(encoder));\n    AVS_UNIT_ASSERT_SUCCESS(cbor_bytes_begin(encoder, 3));\n    AVS_UNIT_ASSERT_SUCCESS(cbor_bytes_append(encoder, \"\\xAA\\xBB\\xCC\", 3));\n    AVS_UNIT_ASSERT_SUCCESS(cbor_bytes_end(encoder));\n    AVS_UNIT_ASSERT_SUCCESS(cbor_bytes_begin(encoder, 3));\n    AVS_UNIT_ASSERT_SUCCESS(cbor_bytes_append(encoder, \"\\xDD\\xEE\\xFF\", 3));\n    AVS_UNIT_ASSERT_SUCCESS(cbor_bytes_end(encoder));\n    AVS_UNIT_ASSERT_SUCCESS(cbor_definite_array_end(encoder));\n\n    AVS_UNIT_ASSERT_EQUAL(nested_context_top(encoder)->size, 1);\n    AVS_UNIT_ASSERT_SUCCESS(cbor_encoder_delete(&encoder));\n    VERIFY_BYTES(env, \"\\x82\\x43\\xAA\\xBB\\xCC\\x43\\xDD\\xEE\\xFF\");\n\n    avs_free(env.buf);\n}\n\n/* Some invalid inputs */\n\nAVS_UNIT_TEST(cbor_encoder, too_few_bytes) {\n    cbor_test_env_t env;\n    cbor_test_setup(&env, 32);\n    cbor_encoder_t *encoder = env.encoder;\n\n    AVS_UNIT_ASSERT_SUCCESS(cbor_bytes_begin(encoder, 3));\n    AVS_UNIT_ASSERT_SUCCESS(cbor_bytes_append(encoder, \"\\x00\", 1));\n    // This should fail and close bytes context\n    AVS_UNIT_ASSERT_FAILED(cbor_bytes_end(encoder));\n    AVS_UNIT_ASSERT_EQUAL(encoder->stack_size, 1);\n\n    AVS_UNIT_ASSERT_SUCCESS(cbor_encoder_delete(&encoder));\n\n    avs_free(env.buf);\n}\n\n// h'000102'\nAVS_UNIT_TEST(cbor_encoder, too_many_bytes) {\n    cbor_test_env_t env;\n    cbor_test_setup(&env, 32);\n    cbor_encoder_t *encoder = env.encoder;\n\n    AVS_UNIT_ASSERT_SUCCESS(cbor_bytes_begin(encoder, 3));\n    // This should fail and don't modify bytes context\n    AVS_UNIT_ASSERT_FAILED(cbor_bytes_append(encoder, \"\\x00\\x01\\x02\\x03\", 4));\n    AVS_UNIT_ASSERT_SUCCESS(cbor_bytes_append(encoder, \"\\x00\\x01\\x02\", 3));\n    AVS_UNIT_ASSERT_SUCCESS(cbor_bytes_end(encoder));\n\n    AVS_UNIT_ASSERT_EQUAL(nested_context_top(encoder)->size, 1);\n    AVS_UNIT_ASSERT_SUCCESS(cbor_encoder_delete(&encoder));\n    VERIFY_BYTES(env, \"\\x43\\x00\\x01\\x02\");\n\n    avs_free(env.buf);\n}\n\n// {_ 1234:\nAVS_UNIT_TEST(cbor_encoder, invalid_number_of_elements_in_map) {\n    cbor_test_env_t env;\n    cbor_test_setup(&env, 32);\n    cbor_encoder_t *encoder = env.encoder;\n\n    AVS_UNIT_ASSERT_SUCCESS(cbor_definite_map_begin(encoder, 1));\n    AVS_UNIT_ASSERT_SUCCESS(cbor_encode_int(encoder, 1234));\n    AVS_UNIT_ASSERT_FAILED(cbor_definite_map_end(encoder));\n\n    AVS_UNIT_ASSERT_SUCCESS(cbor_encoder_delete(&encoder));\n\n    avs_free(env.buf);\n}\n"
  },
  {
    "path": "tests/core/io/cbor_in.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#include <avsystem/commons/avs_memory.h>\n#include <avsystem/commons/avs_stream_inbuf.h>\n#include <avsystem/commons/avs_vector.h>\n\n#include \"senml_in_common.h\"\n\n#define TEST_ENV(Data, Path)                                                \\\n    avs_stream_inbuf_t stream = AVS_STREAM_INBUF_STATIC_INITIALIZER;        \\\n    avs_stream_inbuf_set_buffer(&stream, Data, sizeof(Data) - 1);           \\\n    anjay_unlocked_input_ctx_t *in;                                         \\\n    ASSERT_OK(_anjay_input_senml_cbor_create(&in, (avs_stream_t *) &stream, \\\n                                             &(Path)));\n\nAVS_UNIT_TEST(cbor_in_resource, single_instance) {\n    static const char RESOURCE[] = {\n        \"\\x81\"         // array(1)\n        \"\\xA2\"         // map(2)\n        \"\\x00\"         // unsigned(0) => SenML Name\n        \"\\x68/13/26/1\" // text(8)\n        \"\\x02\"         // unsigned(2) => SenML Value\n        \"\\x18\\x2A\"     // unsigned(42)\n    };\n    TEST_ENV(RESOURCE, TEST_RESOURCE_PATH);\n    check_paths(in, &MAKE_RESOURCE_PATH(13, 26, 1), 1);\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(cbor_in_resource_permuted, single_instance) {\n    static const char RESOURCE[] = {\n        \"\\x81\"         // array(1)\n        \"\\xA2\"         // map(2)\n        \"\\x02\"         // unsigned(2) => SenML Value\n        \"\\x18\\x2A\"     // unsigned(42)\n        \"\\x00\"         // unsigned(0) => SenML Name\n        \"\\x68/13/26/1\" // text(8)\n    };\n    TEST_ENV(RESOURCE, TEST_RESOURCE_PATH);\n    check_paths(in, &MAKE_RESOURCE_PATH(13, 26, 1), 1);\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(cbor_in_resource, single_instance_but_more_than_one) {\n    static const char RESOURCES[] = {\n        \"\\x82\"         // array(2)\n        \"\\xA2\"         // map(2)\n        \"\\x00\"         // unsigned(0) => SenML Name\n        \"\\x68/13/26/1\" // text(8)\n        \"\\x02\"         // unsigned(2) => SenML Value\n        \"\\x18\\x2A\"     // unsigned(42)\n                       // ,\n        \"\\xA2\"         // map(2)\n        \"\\x00\"         // unsigned(0) => SenML Name\n        \"\\x68/13/26/2\" // text(8)\n        \"\\x02\"         // unsigned(2) => SenML Value\n        \"\\x18\\x2B\"     // unsigned(43)\n    };\n    TEST_ENV(RESOURCES, TEST_RESOURCE_PATH);\n    test_single_instance_but_more_than_one(in, &MAKE_RESOURCE_PATH(13, 26, 1));\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(cbor_in_resource,\n              single_instance_but_more_than_one_without_last_get_path) {\n    static const char RESOURCES[] = {\n        \"\\x82\"         // array(2)\n        \"\\xA2\"         // map(2)\n        \"\\x00\"         // unsigned(0) => SenML Name\n        \"\\x68/13/26/1\" // text(8)\n        \"\\x02\"         // unsigned(2) => SenML Value\n        \"\\x18\\x2A\"     // unsigned(42)\n                       // ,\n        \"\\xA2\"         // map(2)\n        \"\\x00\"         // unsigned(0) => SenML Name\n        \"\\x68/13/26/2\" // text(8)\n        \"\\x02\"         // unsigned(2) => SenML Value\n        \"\\x18\\x2B\"     // unsigned(43)\n    };\n    TEST_ENV(RESOURCES, TEST_RESOURCE_PATH);\n    check_path(in, &MAKE_RESOURCE_PATH(13, 26, 1), 42);\n\n    // Context is restricted to /13/26/1, but it has more data to obtain,\n    // which means the request is broken.\n    TEST_TEARDOWN(FAIL);\n}\n\nAVS_UNIT_TEST(cbor_in_resource, single_instance_with_first_resource_unrelated) {\n    static const char RESOURCES[] = {\n        \"\\x82\"         // array(2)\n        \"\\xA2\"         // map(2)\n        \"\\x00\"         // unsigned(0) => SenML Name\n        \"\\x68/13/26/2\" // text(8)\n        \"\\x02\"         // unsigned(2) => SenML Value\n        \"\\x18\\x2A\"     // unsigned(42)\n                       // ,\n        \"\\xA2\"         // map(2)\n        \"\\x00\"         // unsigned(0) => SenML Name\n        \"\\x68/13/26/1\" // text(8)\n        \"\\x02\"         // unsigned(2) => SenML Value\n        \"\\x18\\x2B\"     // unsigned(43)\n    };\n    // NOTE: Request is on /13/26/1 but the first resource in the payload is\n    // /13/26/2.\n    TEST_ENV(RESOURCES, TEST_RESOURCE_PATH);\n\n    ASSERT_EQ(_anjay_input_get_path(in, NULL, NULL), ANJAY_ERR_BAD_REQUEST);\n\n    // Basically nothing was extracted from the context, because it was broken\n    // from the very beginning.\n    TEST_TEARDOWN(FAIL);\n}\n\nAVS_UNIT_TEST(cbor_in_resource_permuted, single_instance_but_more_than_one) {\n    static const char RESOURCES[] = {\n        \"\\x82\"         // array(2)\n        \"\\xA2\"         // map(2)\n        \"\\x02\"         // unsigned(2) => SenML Value\n        \"\\x18\\x2A\"     // unsigned(42)\n        \"\\x00\"         // unsigned(0) => SenML Name\n        \"\\x68/13/26/1\" // text(8)\n                       // ,\n        \"\\xA2\"         // map(2)\n        \"\\x02\"         // unsigned(2) => SenML Value\n        \"\\x18\\x2B\"     // unsigned(43)\n        \"\\x00\"         // unsigned(0) => SenML Name\n        \"\\x68/13/26/2\" // text(8)\n    };\n    TEST_ENV(RESOURCES, MAKE_RESOURCE_PATH(13, 26, 1));\n    test_single_instance_but_more_than_one(in, &MAKE_RESOURCE_PATH(13, 26, 1));\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(cbor_in_resource, multiple_instance) {\n    static const char RESOURCES[] = {\n        \"\\x82\"           // array(2)\n        \"\\xA2\"           // map(2)\n        \"\\x00\"           // unsigned(0) => SenML Name\n        \"\\x6A/13/26/1/4\" // text(10)\n        \"\\x02\"           // unsigned(2) => SenML Value\n        \"\\x18\\x2A\"       // unsigned(42)\n                         // ,\n        \"\\xA2\"           // map(2)\n        \"\\x00\"           // unsigned(0) => SenML Name\n        \"\\x6A/13/26/1/5\" // text(10)\n        \"\\x02\"           // unsigned(2) => SenML Value\n        \"\\x18\\x2B\"       // unsigned(43)\n    };\n    TEST_ENV(RESOURCES, TEST_RESOURCE_PATH);\n    check_paths(in,\n                (const anjay_uri_path_t[2]) {\n                        MAKE_RESOURCE_INSTANCE_PATH(13, 26, 1, 4),\n                        MAKE_RESOURCE_INSTANCE_PATH(13, 26, 1, 5) },\n                2);\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(cbor_in_resource_permuted, multiple_instance) {\n    static const char RESOURCES[] = {\n        \"\\x82\"           // array(2)\n        \"\\xA2\"           // map(2)\n        \"\\x02\"           // unsigned(2) => SenML Value\n        \"\\x18\\x2A\"       // unsigned(42)\n        \"\\x00\"           // unsigned(0) => SenML Name\n        \"\\x6A/13/26/1/4\" // text(10)\n                         // ,\n        \"\\xA2\"           // map(2)\n        \"\\x02\"           // unsigned(2) => SenML Value\n        \"\\x18\\x2B\"       // unsigned(43)\n        \"\\x00\"           // unsigned(0) => SenML Name\n        \"\\x6A/13/26/1/5\" // text(10)\n    };\n    TEST_ENV(RESOURCES, TEST_RESOURCE_PATH);\n    check_paths(in,\n                (const anjay_uri_path_t[2]) {\n                        MAKE_RESOURCE_INSTANCE_PATH(13, 26, 1, 4),\n                        MAKE_RESOURCE_INSTANCE_PATH(13, 26, 1, 5) },\n                2);\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(cbor_in_instance, with_simple_resource) {\n    static const char RESOURCE[] = {\n        \"\\x81\"         // array(1)\n        \"\\xA2\"         // map(2)\n        \"\\x00\"         // unsigned(0) => SenML Name\n        \"\\x68/13/26/1\" // text(8)\n        \"\\x02\"         // unsigned(2) => SenML Value\n        \"\\x18\\x2A\"     // unsigned(42)\n    };\n    TEST_ENV(RESOURCE, TEST_INSTANCE_PATH);\n    check_paths(in, &MAKE_RESOURCE_PATH(13, 26, 1), 1);\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(cbor_in_instance, with_more_than_one_resource) {\n    static const char RESOURCES[] = {\n        \"\\x82\"         // array(2)\n        \"\\xA2\"         // map(2)\n        \"\\x00\"         // unsigned(0) => SenML Name\n        \"\\x68/13/26/1\" // text(8)\n        \"\\x02\"         // unsigned(2) => SenML Value\n        \"\\x18\\x2A\"     // unsigned(42)\n                       // ,\n        \"\\xA2\"         // map(2)\n        \"\\x00\"         // unsigned(0) => SenML Name\n        \"\\x68/13/26/2\" // text(8)\n        \"\\x02\"         // unsigned(2) => SenML Value\n        \"\\x18\\x2B\"     // unsigned(43)\n    };\n    TEST_ENV(RESOURCES, TEST_INSTANCE_PATH);\n    check_paths(in,\n                (const anjay_uri_path_t[2]) { MAKE_RESOURCE_PATH(13, 26, 1),\n                                              MAKE_RESOURCE_PATH(13, 26, 2) },\n                2);\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(cbor_in_instance, resource_skipping) {\n    static const char RESOURCES[] = {\n        \"\\x82\"         // array(2)\n        \"\\xA2\"         // map(2)\n        \"\\x00\"         // unsigned(0) => SenML Name\n        \"\\x68/13/26/1\" // text(8)\n        \"\\x02\"         // unsigned(2) => SenML Value\n        \"\\x18\\x2A\"     // unsigned(42)\n                       // ,\n        \"\\xA2\"         // map(2)\n        \"\\x00\"         // unsigned(0) => SenML Name\n        \"\\x68/13/26/2\" // text(8)\n        \"\\x02\"         // unsigned(2) => SenML Value\n        \"\\x18\\x2B\"     // unsigned(43)\n    };\n    TEST_ENV(RESOURCES, TEST_INSTANCE_PATH);\n    test_skipping(in,\n                  (const anjay_uri_path_t[2]) { MAKE_RESOURCE_PATH(13, 26, 1),\n                                                MAKE_RESOURCE_PATH(13, 26, 2) },\n                  2);\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(cbor_in_instance_permuted, resource_skipping) {\n    static const char RESOURCES[] = {\n        \"\\x82\"         // array(2)\n        \"\\xA2\"         // map(2)\n        \"\\x02\"         // unsigned(2) => SenML Value\n        \"\\x18\\x2A\"     // unsigned(42)\n        \"\\x00\"         // unsigned(0) => SenML Name\n        \"\\x68/13/26/1\" // text(8)\n                       // ,\n        \"\\xA2\"         // map(2)\n        \"\\x02\"         // unsigned(2) => SenML Value\n        \"\\x18\\x2B\"     // unsigned(43)\n        \"\\x00\"         // unsigned(0) => SenML Name\n        \"\\x68/13/26/2\" // text(8)\n    };\n    TEST_ENV(RESOURCES, TEST_INSTANCE_PATH);\n    test_skipping(in,\n                  (const anjay_uri_path_t[2]) { MAKE_RESOURCE_PATH(13, 26, 1),\n                                                MAKE_RESOURCE_PATH(13, 26, 2) },\n                  2);\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(cbor_in_instance, multiple_resource_skipping) {\n    static const char RESOURCES[] = {\n        \"\\x82\"           // array(2)\n        \"\\xA2\"           // map(2)\n        \"\\x00\"           // unsigned(0) => SenML Name\n        \"\\x6A/13/26/1/4\" // text(10)\n        \"\\x02\"           // unsigned(2) => SenML Value\n        \"\\x18\\x2A\"       // unsigned(42)\n                         // ,\n        \"\\xA2\"           // map(2)\n        \"\\x00\"           // unsigned(0) => SenML Name\n        \"\\x6A/13/26/2/5\" // text(10)\n        \"\\x02\"           // unsigned(2) => SenML Value\n        \"\\x18\\x2B\"       // unsigned(43)\n    };\n    TEST_ENV(RESOURCES, TEST_INSTANCE_PATH);\n    test_skipping(in,\n                  (const anjay_uri_path_t[2]) {\n                          MAKE_RESOURCE_INSTANCE_PATH(13, 26, 1, 4),\n                          MAKE_RESOURCE_INSTANCE_PATH(13, 26, 2, 5) },\n                  2);\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(cbor_in_object, with_single_instance_and_some_resources) {\n    static const char RESOURCES[] = {\n        \"\\x82\"         // array(2)\n        \"\\xA2\"         // map(2)\n        \"\\x00\"         // unsigned(0) => SenML Name\n        \"\\x68/13/26/1\" // text(8)\n        \"\\x02\"         // unsigned(2) => SenML Value\n        \"\\x18\\x2A\"     // unsigned(42)\n                       // ,\n        \"\\xA2\"         // map(2)\n        \"\\x00\"         // unsigned(0) => SenML Name\n        \"\\x68/13/26/2\" // text(8)\n        \"\\x02\"         // unsigned(2) => SenML Value\n        \"\\x18\\x2B\"     // unsigned(43)\n    };\n    TEST_ENV(RESOURCES, MAKE_OBJECT_PATH(13));\n    check_paths(in,\n                (const anjay_uri_path_t[2]) { MAKE_RESOURCE_PATH(13, 26, 1),\n                                              MAKE_RESOURCE_PATH(13, 26, 2) },\n                2);\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(cbor_in_object, with_some_instances_and_some_resources) {\n    static const char RESOURCES[] = {\n        \"\\x84\"         // array(4)\n        \"\\xA2\"         // map(2)\n        \"\\x00\"         // unsigned(0) => SenML Name\n        \"\\x68/13/26/1\" // text(8)\n        \"\\x02\"         // unsigned(2) => SenML Value\n        \"\\x18\\x2A\"     // unsigned(42)\n                       // ,\n        \"\\xA2\"         // map(2)\n        \"\\x00\"         // unsigned(0) => SenML Name\n        \"\\x68/13/26/2\" // text(8)\n        \"\\x02\"         // unsigned(2) => SenML Value\n        \"\\x18\\x2B\"     // unsigned(43)\n                       //\n        \"\\xA2\"         // map(2)\n        \"\\x00\"         // unsigned(0) => SenML Name\n        \"\\x68/13/27/3\" // text(8)\n        \"\\x02\"         // unsigned(2) => SenML Value\n        \"\\x18\\x2C\"     // unsigned(44)\n                       // ,\n        \"\\xA2\"         // map(2)\n        \"\\x00\"         // unsigned(0) => SenML Name\n        \"\\x68/13/27/4\" // text(8)\n        \"\\x02\"         // unsigned(2) => SenML Value\n        \"\\x18\\x2D\"     // unsigned(45)\n    };\n    TEST_ENV(RESOURCES, MAKE_OBJECT_PATH(13));\n    check_paths(in,\n                (const anjay_uri_path_t[4]) { MAKE_RESOURCE_PATH(13, 26, 1),\n                                              MAKE_RESOURCE_PATH(13, 26, 2),\n                                              MAKE_RESOURCE_PATH(13, 27, 3),\n                                              MAKE_RESOURCE_PATH(13, 27, 4) },\n                4);\n    TEST_TEARDOWN(OK);\n}\n\n#define TEST_VALUE_ENV(TypeAndValue)                                        \\\n    static const char RESOURCE[] = { \"\\x81\" /* array(1) */                  \\\n                                     \"\\xA2\" /* map(2) */                    \\\n                                     \"\\x00\" /* unsigned(0) => SenML Name */ \\\n                                     \"\\x68/13/26/1\" /* text(8) */           \\\n                                     TypeAndValue };                        \\\n    TEST_ENV(RESOURCE, MAKE_RESOURCE_PATH(13, 26, 1));                      \\\n    {                                                                       \\\n        anjay_uri_path_t path;                                              \\\n        ASSERT_OK(_anjay_input_get_path(in, &path, NULL));                  \\\n        URI_EQUAL(&path, &MAKE_RESOURCE_PATH(13, 26, 1));                   \\\n    }\n\nAVS_UNIT_TEST(cbor_in_value, string_with_zero_length_buffer) {\n    // unsigned(3) => SenML String & string(foobar)\n    TEST_VALUE_ENV(\"\\x03\\x66\"\n                   \"foobar\");\n\n    char buf[16] = \"nothing\";\n    ASSERT_EQ(_anjay_get_string_unlocked(in, buf, 0), ANJAY_BUFFER_TOO_SHORT);\n    ASSERT_EQ(buf[0], 'n');\n\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(cbor_in_value, bytes_with_too_short_buffer) {\n    // unsigned(8) => SenML Data & bytes(foobar)\n    TEST_VALUE_ENV(\"\\x08\\x46\"\n                   \"foobar\");\n\n    char buf[16] = \"nothing\";\n    size_t bytes_read;\n    bool message_finished;\n    ASSERT_OK(_anjay_get_bytes_unlocked(in, &bytes_read, &message_finished, buf,\n                                        0));\n    ASSERT_EQ(bytes_read, 0);\n    ASSERT_EQ(message_finished, false);\n    ASSERT_EQ(buf[0], 'n');\n\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(cbor_in_value, u64_as_double_within_range) {\n    // unsigned(2) => SenML Value & unsigned(9007199254740992)\n    TEST_VALUE_ENV(\"\\x02\\x1B\\x00\\x20\\x00\\x00\\x00\\x00\\x00\\x00\");\n\n    double value;\n    ASSERT_OK(_anjay_get_double_unlocked(in, &value));\n    ASSERT_EQ(value, UINT64_C(9007199254740992));\n\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(cbor_in_value, u64_as_double_out_of_range) {\n    // unsigned(2) => SenML Value & unsigned(9007199254740993)\n    TEST_VALUE_ENV(\"\\x02\\x1B\\x00\\x20\\x00\\x00\\x00\\x00\\x00\\x01\");\n\n    double value;\n    ASSERT_OK(_anjay_get_double_unlocked(in, &value));\n    // precision is lost, but we don't care\n    ASSERT_EQ(value, 9007199254740992.0);\n\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(cbor_in_value, i64_as_double_within_range) {\n    // unsigned(2) => SenML Value & negative(9007199254740991)\n    TEST_VALUE_ENV(\"\\x02\\x3B\\x00\\x1F\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\");\n\n    double value;\n    ASSERT_OK(_anjay_get_double_unlocked(in, &value));\n    ASSERT_EQ(value, -INT64_C(9007199254740992));\n\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(cbor_in_value, i64_as_double_out_of_range) {\n    // unsigned(2) => SenML Value & negative(9007199254740993)\n    TEST_VALUE_ENV(\"\\x02\\x3B\\x00\\x20\\x00\\x00\\x00\\x00\\x00\\x00\");\n\n    double value;\n    ASSERT_OK(_anjay_get_double_unlocked(in, &value));\n    // precision is lost, but we don't care\n    ASSERT_EQ(value, -9007199254740992.0);\n\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(cbor_in_value, float_as_i64_when_convertible) {\n    // unsigned(2) => SenML Value & simple_f32(3.0)\n    TEST_VALUE_ENV(\"\\x02\\xFA\\x40\\x40\\x00\\x00\");\n\n    int64_t value;\n    ASSERT_OK(_anjay_get_i64_unlocked(in, &value));\n    ASSERT_EQ(value, 3);\n\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(cbor_in_value, float_as_i64_when_not_convertible) {\n    // unsigned(2) => SenML Value & simple_f32(3.1415926535)\n    TEST_VALUE_ENV(\"\\x02\\xFA\\x40\\x49\\x0f\\xdb\");\n\n    int64_t value;\n    ASSERT_FAIL(_anjay_get_i64_unlocked(in, &value));\n\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(cbor_in_value, double_as_i64_when_convertible) {\n    // unsigned(2) => SenML Value & simple_f64(3)\n    TEST_VALUE_ENV(\"\\x02\\xFB\\x40\\x08\\x00\\x00\\x00\\x00\\x00\\x00\");\n\n    int64_t value;\n    ASSERT_OK(_anjay_get_i64_unlocked(in, &value));\n    ASSERT_EQ(value, 3);\n\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(cbor_in_value, double_as_u64_when_convertible) {\n    // unsigned(2) => SenML Value & simple_f64(3)\n    TEST_VALUE_ENV(\"\\x02\\xFB\\x40\\x08\\x00\\x00\\x00\\x00\\x00\\x00\");\n\n    uint64_t value;\n    ASSERT_OK(_anjay_get_u64_unlocked(in, &value));\n    ASSERT_EQ(value, 3);\n\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(cbor_in_value, double_as_huge_u64_when_convertible) {\n    // unsigned(2) => SenML Value & simple_f64(1.844674407370955e19)\n    TEST_VALUE_ENV(\"\\x02\\xFB\\x43\\xEF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\");\n\n    uint64_t value;\n    ASSERT_OK(_anjay_get_u64_unlocked(in, &value));\n    ASSERT_EQ(value, UINT64_MAX - 2047);\n\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(cbor_in_value, double_as_i64_not_convertible) {\n    // unsigned(2) => SenML Value & simple_f64(3.1415926535)\n    TEST_VALUE_ENV(\"\\x02\\xFB\\x40\\x09\\x21\\xfb\\x54\\x41\\x17\\x44\");\n\n    int64_t value;\n    ASSERT_FAIL(_anjay_get_i64_unlocked(in, &value));\n\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(cbor_in_value, half_read_as_double) {\n    // unsigned(2) => SenML Value & simple_f16(32)\n    TEST_VALUE_ENV(\"\\x02\\xF9\\x50\\x00\");\n    double value;\n    ASSERT_OK(_anjay_get_double_unlocked(in, &value));\n    ASSERT_EQ(value, 32.0);\n\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(cbor_in_value, objlnk_valid) {\n    // text(3) => \"vlo\" & string(32:42532)\n    TEST_VALUE_ENV(\"\\x63\"\n                   \"vlo\"\n                   \"\\x68\"\n                   \"32:42532\");\n\n    anjay_oid_t oid;\n    anjay_iid_t iid;\n    ASSERT_OK(_anjay_get_objlnk_unlocked(in, &oid, &iid));\n    ASSERT_EQ(oid, 32);\n    ASSERT_EQ(iid, 42532);\n\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(cbor_in_value, objlnk_with_trash_at_the_end) {\n    // text(3) => \"vlo\" & string(32:42foo)\n    TEST_VALUE_ENV(\"\\x63\"\n                   \"vlo\"\n                   \"\\x68\"\n                   \"32:42foo\");\n\n    anjay_oid_t oid;\n    anjay_iid_t iid;\n    ASSERT_FAIL(_anjay_get_objlnk_unlocked(in, &oid, &iid));\n\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(cbor_in_value, objlnk_with_overflow) {\n    // text(3) => \"vlo\" & string(1:423444)\n    TEST_VALUE_ENV(\"\\x63\"\n                   \"vlo\"\n                   \"\\x68\"\n                   \"1:423444\");\n\n    anjay_oid_t oid;\n    anjay_iid_t iid;\n    ASSERT_FAIL(_anjay_get_objlnk_unlocked(in, &oid, &iid));\n\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(cbor_in, valid_paths) {\n    anjay_uri_path_t path;\n    ASSERT_OK(parse_absolute_path(&path, \"/\"));\n    ASSERT_OK(parse_absolute_path(&path, \"/1\"));\n    ASSERT_OK(parse_absolute_path(&path, \"/1/2\"));\n    ASSERT_OK(parse_absolute_path(&path, \"/1/2/3\"));\n    ASSERT_OK(parse_absolute_path(&path, \"/1/2/3/4\"));\n    ASSERT_OK(parse_absolute_path(&path, \"/1/2/3/65534\"));\n    ASSERT_OK(parse_absolute_path(&path, \"/65534/65534/65534/65534\"));\n}\n\nAVS_UNIT_TEST(cbor_in, invalid_paths) {\n    anjay_uri_path_t path;\n    ASSERT_FAIL(parse_absolute_path(&path, \"\"));\n    ASSERT_FAIL(parse_absolute_path(&path, \"1\"));\n    ASSERT_FAIL(parse_absolute_path(&path, \"//\"));\n    ASSERT_FAIL(parse_absolute_path(&path, \"/1/\"));\n    ASSERT_FAIL(parse_absolute_path(&path, \"/1/2/\"));\n    ASSERT_FAIL(parse_absolute_path(&path, \"/1/2/3/\"));\n    ASSERT_FAIL(parse_absolute_path(&path, \"/1/2/3/4/\"));\n    ASSERT_FAIL(parse_absolute_path(&path, \"/1/2/3/65535\"));\n    ASSERT_FAIL(parse_absolute_path(&path, \"/1/2/3/65536\"));\n    ASSERT_FAIL(parse_absolute_path(&path, \"/1/2//3\"));\n#ifndef ANJAY_WITH_LWM2M_GATEWAY\n    ASSERT_FAIL(parse_absolute_path(&path, \"/-1/2/3\"));\n#endif // ANJAY_WITH_LWM2M_GATEWAY\n}\n#undef TEST_VALUE_ENV\n\n#define TEST_VALUE_ENV(TypeAndValue)                                        \\\n    static const char RESOURCE[] = { \"\\x81\" /* array(1) */                  \\\n                                     \"\\xA2\" /* map(2) */                    \\\n                                     \"\\x00\" /* unsigned(0) => SenML Name */ \\\n                                     \"\\x68/13/26/1\" /* text(8) */           \\\n                                     TypeAndValue };                        \\\n    TEST_ENV(RESOURCE, MAKE_RESOURCE_PATH(13, 26, 1));\n\nAVS_UNIT_TEST(cbor_in, get_integer_before_get_id) {\n    // SenML Value -> unsigned(42)\n    TEST_VALUE_ENV(\"\\x02\\x18\\x2A\");\n    ASSERT_FAIL(_anjay_get_i64_unlocked(in, &(int64_t) { 0 }));\n    TEST_TEARDOWN(FAIL);\n}\n\nAVS_UNIT_TEST(cbor_in, get_float_before_get_id) {\n    // unsigned(2) => SenML Value & simple_f32(3.0)\n    TEST_VALUE_ENV(\"\\x02\\xFA\\x40\\x40\\x00\\x00\");\n    ASSERT_FAIL(_anjay_get_double_unlocked(in, &(double) { 0 }));\n    TEST_TEARDOWN(FAIL);\n}\n\nAVS_UNIT_TEST(cbor_in, get_bytes_before_get_id) {\n    // unsigned(8) => SenML Data & bytes(foobar)\n    TEST_VALUE_ENV(\"\\x08\\x46\"\n                   \"foobar\");\n    ASSERT_FAIL(_anjay_get_bytes_unlocked(in, &(size_t) { 0 },\n                                          &(bool) { false }, (char[32]){}, 32));\n    TEST_TEARDOWN(FAIL);\n}\n\nAVS_UNIT_TEST(cbor_in, get_string_before_get_id) {\n    // unsigned(3) => SenML String & string(foobar)\n    TEST_VALUE_ENV(\"\\x03\\x66\"\n                   \"foobar\");\n    ASSERT_FAIL(_anjay_get_string_unlocked(in, (char[32]){}, 32));\n    TEST_TEARDOWN(FAIL);\n}\n\nAVS_UNIT_TEST(cbor_in, get_bool_before_get_id) {\n    // unsigned(4) => SenML Boolean Value & false\n    TEST_VALUE_ENV(\"\\x04\\xF4\");\n    ASSERT_FAIL(_anjay_get_bool_unlocked(in, &(bool) { false }));\n    TEST_TEARDOWN(FAIL);\n}\n\nAVS_UNIT_TEST(cbor_in, get_objlnk_before_get_id) {\n    // text(3) => \"vlo\" & string(32:42532)\n    TEST_VALUE_ENV(\"\\x63\"\n                   \"vlo\"\n                   \"\\x68\"\n                   \"32:42532\");\n    ASSERT_FAIL(_anjay_get_objlnk_unlocked(in, &(anjay_oid_t) { 0 },\n                                           &(anjay_iid_t) { 0 }));\n    TEST_TEARDOWN(FAIL);\n}\n\nAVS_UNIT_TEST(cbor_in, get_path_for_resource_instance_path) {\n    static const char RESOURCE_INSTANCE_PATH[] = {\n        \"\\x81\"         // array(1)\n        \"\\xA1\"         // map(1)\n        \"\\x00\"         // unsigned(0) => SenML Name\n        \"\\x68/3/0/0/1\" // text(8)\n    };\n    TEST_ENV(RESOURCE_INSTANCE_PATH, MAKE_RESOURCE_INSTANCE_PATH(3, 0, 0, 1));\n    anjay_uri_path_t path;\n    ASSERT_OK(_anjay_input_get_path(in, &path, NULL));\n    URI_EQUAL(&path, &MAKE_RESOURCE_INSTANCE_PATH(3, 0, 0, 1));\n    TEST_TEARDOWN(OK);\n}\n\n#define COMPOSITE_TEST_ENV(Data, Path)                               \\\n    avs_stream_inbuf_t stream = AVS_STREAM_INBUF_STATIC_INITIALIZER; \\\n    avs_stream_inbuf_set_buffer(&stream, Data, sizeof(Data) - 1);    \\\n    anjay_unlocked_input_ctx_t *in;                                  \\\n    ASSERT_OK(_anjay_input_senml_cbor_composite_read_create(         \\\n            &in, (avs_stream_t *) &stream, &(Path)));\n\nAVS_UNIT_TEST(cbor_in_composite, composite_read_mode_additional_payload) {\n    static const char RESOURCE_INSTANCE_WITH_PAYLOAD[] = {\n        \"\\x81\"         // array(1)\n        \"\\xA2\"         // map(2)\n        \"\\x00\"         // unsigned(0) => SenML Name\n        \"\\x68/3/0/0/1\" // text(8)\n        \"\\x02\"         // unsigned(2) => SenML Value\n        \"\\x63\"         // text(3)\n        \"foo\"\n    };\n    COMPOSITE_TEST_ENV(RESOURCE_INSTANCE_WITH_PAYLOAD, MAKE_ROOT_PATH());\n    anjay_uri_path_t path;\n    ASSERT_FAIL(_anjay_input_get_path(in, &path, NULL));\n    TEST_TEARDOWN(FAIL);\n}\n\n#ifdef ANJAY_WITH_LWM2M_GATEWAY\nAVS_UNIT_TEST(cbor_in_resource_with_prefix, single_instance) {\n    static const char RESOURCE_WITH_PREFIX[] = {\n        \"\\x81\"                 // array(1)\n        \"\\xA2\"                 // map(3)\n        \"\\x00\"                 // unsigned(0) => SenML Name\n        \"\\x70/0aapud0/13/26/1\" // text(16)\n        \"\\x02\"                 // unsigned(2) => SenML Value\n        \"\\x18\\x2A\"             // unsigned(42)\n    };\n    TEST_ENV(RESOURCE_WITH_PREFIX, TEST_RESOURCE_PATH_WITH_PREFIX);\n    check_paths(in, &MAKE_RESOURCE_PATH_WITH_PREFIX(\"0aapud0\", 13, 26, 1), 1);\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(cbor_in_resource_permuted_with_prefix, single_instance) {\n    static const char RESOURCE_WITH_PREFIX[] = {\n        \"\\x81\"                 // array(1)\n        \"\\xA2\"                 // map(2)\n        \"\\x02\"                 // unsigned(2) => SenML Value\n        \"\\x18\\x2A\"             // unsigned(42)\n        \"\\x00\"                 // unsigned(0) => SenML Name\n        \"\\x70/0aapud0/13/26/1\" // text(16)\n    };\n\n    TEST_ENV(RESOURCE_WITH_PREFIX, TEST_RESOURCE_PATH_WITH_PREFIX);\n    check_paths(in, &MAKE_RESOURCE_PATH_WITH_PREFIX(\"0aapud0\", 13, 26, 1), 1);\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(cbor_in_resource_with_prefix, single_instance_but_more_than_one) {\n    static const char RESOURCES_WITH_PREFIX[] = {\n        \"\\x82\"                 // array(2)\n        \"\\xA2\"                 // map(2)\n        \"\\x00\"                 // unsigned(0) => SenML Name\n        \"\\x70/0aapud0/13/26/1\" // text(16)\n        \"\\x02\"                 // unsigned(2) => SenML Value\n        \"\\x18\\x2A\"             // unsigned(42)\n                               // ,\n        \"\\xA2\"                 // map(2)\n        \"\\x00\"                 // unsigned(0) => SenML Name\n        \"\\x70/0aapud0/13/26/2\" // text(16)\n        \"\\x02\"                 // unsigned(2) => SenML Value\n        \"\\x18\\x2B\"             // unsigned(43)\n    };\n    TEST_ENV(RESOURCES_WITH_PREFIX, TEST_RESOURCE_PATH_WITH_PREFIX);\n    test_single_instance_but_more_than_one(\n            in, &MAKE_RESOURCE_PATH_WITH_PREFIX(\"0aapud0\", 13, 26, 1));\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(cbor_in_resource_with_prefix,\n              single_instance_but_more_than_one_without_last_get_path) {\n    static const char RESOURCES_WITH_PREFIX[] = {\n        \"\\x82\"                 // array(2)\n        \"\\xA2\"                 // map(2)\n        \"\\x00\"                 // unsigned(0) => SenML Name\n        \"\\x70/0aapud0/13/26/1\" // text(16)\n        \"\\x02\"                 // unsigned(2) => SenML Value\n        \"\\x18\\x2A\"             // unsigned(42)\n                               // ,\n        \"\\xA2\"                 // map(2)\n        \"\\x00\"                 // unsigned(0) => SenML Name\n        \"\\x70/0aapud0/13/26/2\" // text(16)\n        \"\\x02\"                 // unsigned(2) => SenML Value\n        \"\\x18\\x2B\"             // unsigned(43)\n    };\n\n    TEST_ENV(RESOURCES_WITH_PREFIX, TEST_RESOURCE_PATH_WITH_PREFIX);\n    check_path(in, &MAKE_RESOURCE_PATH_WITH_PREFIX(\"0aapud0\", 13, 26, 1), 42);\n\n    // Context is restricted to /0aapud0/13/26/1, but it has more data to\n    // obtain, which means the request is broken.\n    TEST_TEARDOWN(FAIL);\n}\n\nAVS_UNIT_TEST(cbor_in_resource_with_prefix,\n              single_instance_with_first_resource_unrelated) {\n    static const char RESOURCES_WITH_PREFIX[] = {\n        \"\\x82\"                 // array(2)\n        \"\\xA2\"                 // map(2)\n        \"\\x00\"                 // unsigned(0) => SenML Name\n        \"\\x70/0aapud0/13/26/2\" // text(16)\n        \"\\x02\"                 // unsigned(2) => SenML Value\n        \"\\x18\\x2A\"             // unsigned(42)\n                               // ,\n        \"\\xA2\"                 // map(2)\n        \"\\x00\"                 // unsigned(0) => SenML Name\n        \"\\x70/0aapud0/13/26/1\" // text(16)\n        \"\\x02\"                 // unsigned(2) => SenML Value\n        \"\\x18\\x2B\"             // unsigned(43)\n    };\n\n    // NOTE: Request is on /0aapud0/13/26/1 but the first resource in the\n    // payload is /0aapud0/13/26/2.\n    TEST_ENV(RESOURCES_WITH_PREFIX, TEST_RESOURCE_PATH_WITH_PREFIX);\n\n    ASSERT_EQ(_anjay_input_get_path(in, NULL, NULL), ANJAY_ERR_BAD_REQUEST);\n\n    // Basically nothing was extracted from the context, because it was broken\n    // from the very beginning.\n    TEST_TEARDOWN(FAIL);\n}\n\nAVS_UNIT_TEST(cbor_in_resource_permuted_with_prefix,\n              single_instance_but_more_than_one) {\n    static const char RESOURCES_WITH_PREFIX[] = {\n        \"\\x82\"                 // array(2)\n        \"\\xA2\"                 // map(2)\n        \"\\x02\"                 // unsigned(2) => SenML Value\n        \"\\x18\\x2A\"             // unsigned(42)\n        \"\\x00\"                 // unsigned(0) => SenML Name\n        \"\\x70/0aapud0/13/26/1\" // text(16)\n                               // ,\n        \"\\xA2\"                 // map(2)\n        \"\\x02\"                 // unsigned(2) => SenML Value\n        \"\\x18\\x2B\"             // unsigned(43)\n        \"\\x00\"                 // unsigned(0) => SenML Name\n        \"\\x70/0aapud0/13/26/2\" // text(16)\n    };\n    TEST_ENV(RESOURCES_WITH_PREFIX, TEST_RESOURCE_PATH_WITH_PREFIX);\n    test_single_instance_but_more_than_one(\n            in, &MAKE_RESOURCE_PATH_WITH_PREFIX(\"0aapud0\", 13, 26, 1));\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(cbor_in_resource_with_prefix, multiple_instance) {\n    static const char RESOURCES_WITH_PREFIX[] = {\n        \"\\x82\"                   // array(2)\n        \"\\xA2\"                   // map(2)\n        \"\\x00\"                   // unsigned(0) => SenML Name\n        \"\\x72/0aapud0/13/26/1/4\" // text(18)\n        \"\\x02\"                   // unsigned(2) => SenML Value\n        \"\\x18\\x2A\"               // unsigned(42)\n                                 // ,\n        \"\\xA2\"                   // map(2)\n        \"\\x00\"                   // unsigned(0) => SenML Name\n        \"\\x72/0aapud0/13/26/1/5\" // text(18)\n        \"\\x02\"                   // unsigned(2) => SenML Value\n        \"\\x18\\x2B\"               // unsigned(43)\n    };\n    TEST_ENV(RESOURCES_WITH_PREFIX, TEST_RESOURCE_PATH_WITH_PREFIX);\n    check_paths(in,\n                (const anjay_uri_path_t[2]) {\n                        MAKE_RESOURCE_INSTANCE_PATH_WITH_PREFIX(\"0aapud0\", 13,\n                                                                26, 1, 4),\n                        MAKE_RESOURCE_INSTANCE_PATH_WITH_PREFIX(\"0aapud0\", 13,\n                                                                26, 1, 5) },\n                2);\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(cbor_in_resource_with_prefix,\n              multiple_instance_with_different_prefixes) {\n    static const char RESOURCES_WITH_PREFIX[] = {\n        \"\\x82\"                   // array(2)\n        \"\\xA2\"                   // map(2)\n        \"\\x00\"                   // unsigned(0) => SenML Name\n        \"\\x72/prefix1/13/26/1/4\" // text(18)\n        \"\\x02\"                   // unsigned(2) => SenML Value\n        \"\\x18\\x2A\"               // unsigned(42)\n                                 // ,\n        \"\\xA2\"                   // map(2)\n        \"\\x00\"                   // unsigned(0) => SenML Name\n        \"\\x72/prefix2/13/26/1/5\" // text(18)\n        \"\\x02\"                   // unsigned(2) => SenML Value\n        \"\\x18\\x2B\"               // unsigned(43)\n    };\n    TEST_ENV(RESOURCES_WITH_PREFIX, MAKE_ROOT_PATH());\n    check_paths(in,\n                (const anjay_uri_path_t[2]) {\n                        MAKE_RESOURCE_INSTANCE_PATH_WITH_PREFIX(\"prefix1\", 13,\n                                                                26, 1, 4),\n                        MAKE_RESOURCE_INSTANCE_PATH_WITH_PREFIX(\"prefix2\", 13,\n                                                                26, 1, 5) },\n                2);\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(cbor_in_resource_permuted_with_prefix, multiple_instance) {\n    static const char RESOURCES_WITH_PREFIX[] = {\n        \"\\x82\"                   // array(2)\n        \"\\xA2\"                   // map(2)\n        \"\\x02\"                   // unsigned(2) => SenML Value\n        \"\\x18\\x2A\"               // unsigned(42)\n        \"\\x00\"                   // unsigned(0) => SenML Name\n        \"\\x72/0aapud0/13/26/1/4\" // text(18)\n                                 // ,\n        \"\\xA2\"                   // map(2)\n        \"\\x02\"                   // unsigned(2) => SenML Value\n        \"\\x18\\x2B\"               // unsigned(43)\n        \"\\x00\"                   // unsigned(0) => SenML Name\n        \"\\x72/0aapud0/13/26/1/5\" // text(18)\n    };\n    TEST_ENV(RESOURCES_WITH_PREFIX, TEST_RESOURCE_PATH_WITH_PREFIX);\n    check_paths(in,\n                (const anjay_uri_path_t[2]) {\n                        MAKE_RESOURCE_INSTANCE_PATH_WITH_PREFIX(\"0aapud0\", 13,\n                                                                26, 1, 4),\n                        MAKE_RESOURCE_INSTANCE_PATH_WITH_PREFIX(\"0aapud0\", 13,\n                                                                26, 1, 5) },\n                2);\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(cbor_in_resource_permuted_with_prefix,\n              multiple_instance_with_different_prefixes) {\n    static const char RESOURCES_WITH_PREFIX[] = {\n        \"\\x82\"                   // array(2)\n        \"\\xA2\"                   // map(2)\n        \"\\x02\"                   // unsigned(2) => SenML Value\n        \"\\x18\\x2A\"               // unsigned(42)\n        \"\\x00\"                   // unsigned(0) => SenML Name\n        \"\\x72/prefix1/13/26/1/4\" // text(18)\n                                 // ,\n        \"\\xA2\"                   // map(2)\n        \"\\x02\"                   // unsigned(2) => SenML Value\n        \"\\x18\\x2B\"               // unsigned(43)\n        \"\\x00\"                   // unsigned(0) => SenML Name\n        \"\\x72/prefix2/13/26/1/5\" // text(18)\n    };\n    TEST_ENV(RESOURCES_WITH_PREFIX, MAKE_ROOT_PATH());\n    check_paths(in,\n                (const anjay_uri_path_t[2]) {\n                        MAKE_RESOURCE_INSTANCE_PATH_WITH_PREFIX(\"prefix1\", 13,\n                                                                26, 1, 4),\n                        MAKE_RESOURCE_INSTANCE_PATH_WITH_PREFIX(\"prefix2\", 13,\n                                                                26, 1, 5) },\n                2);\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(cbor_in_instance_with_prefix, with_simple_resource) {\n    static const char RESOURCE_WITH_PREFIX[] = {\n        \"\\x81\"                 // array(1)\n        \"\\xA2\"                 // map(2)\n        \"\\x00\"                 // unsigned(0) => SenML Name\n        \"\\x70/0aapud0/13/26/1\" // text(16)\n        \"\\x02\"                 // unsigned(2) => SenML Value\n        \"\\x18\\x2A\"             // unsigned(42)\n    };\n    TEST_ENV(RESOURCE_WITH_PREFIX, TEST_INSTANCE_PATH_WITH_PREFIX);\n    check_paths(in, &MAKE_RESOURCE_PATH_WITH_PREFIX(\"0aapud0\", 13, 26, 1), 1);\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(cbor_in_instance_with_prefix, with_more_than_one_resource) {\n    static const char RESOURCES_WITH_PREFIX[] = {\n        \"\\x82\"                 // array(2)\n        \"\\xA2\"                 // map(2)\n        \"\\x00\"                 // unsigned(0) => SenML Name\n        \"\\x70/0aapud0/13/26/1\" // text(16)\n        \"\\x02\"                 // unsigned(2) => SenML Value\n        \"\\x18\\x2A\"             // unsigned(42)\n                               // ,\n        \"\\xA2\"                 // map(2)\n        \"\\x00\"                 // unsigned(0) => SenML Name\n        \"\\x70/0aapud0/13/26/2\" // text(16)\n        \"\\x02\"                 // unsigned(2) => SenML Value\n        \"\\x18\\x2B\"             // unsigned(43)\n    };\n    TEST_ENV(RESOURCES_WITH_PREFIX, TEST_INSTANCE_PATH_WITH_PREFIX);\n    check_paths(in,\n                (const anjay_uri_path_t[2]) {\n                        MAKE_RESOURCE_PATH_WITH_PREFIX(\"0aapud0\", 13, 26, 1),\n                        MAKE_RESOURCE_PATH_WITH_PREFIX(\"0aapud0\", 13, 26, 2) },\n                2);\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(cbor_in_instance_with_prefix,\n              with_more_than_one_resource_with_different_prefixes) {\n    static const char RESOURCES_WITH_PREFIX[] = {\n        \"\\x82\"                 // array(2)\n        \"\\xA2\"                 // map(2)\n        \"\\x00\"                 // unsigned(0) => SenML Name\n        \"\\x70/prefix1/13/26/1\" // text(16)\n        \"\\x02\"                 // unsigned(2) => SenML Value\n        \"\\x18\\x2A\"             // unsigned(42)\n                               // ,\n        \"\\xA2\"                 // map(2)\n        \"\\x00\"                 // unsigned(0) => SenML Name\n        \"\\x70/prefix2/13/26/2\" // text(16)\n        \"\\x02\"                 // unsigned(2) => SenML Value\n        \"\\x18\\x2B\"             // unsigned(43)\n    };\n    TEST_ENV(RESOURCES_WITH_PREFIX, MAKE_ROOT_PATH());\n    check_paths(in,\n                (const anjay_uri_path_t[2]) {\n                        MAKE_RESOURCE_PATH_WITH_PREFIX(\"prefix1\", 13, 26, 1),\n                        MAKE_RESOURCE_PATH_WITH_PREFIX(\"prefix2\", 13, 26, 2) },\n                2);\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(cbor_in_instance_with_prefix, resource_skipping) {\n    static const char RESOURCES_WITH_PREFIX[] = {\n        \"\\x82\"                 // array(2)\n        \"\\xA2\"                 // map(2)\n        \"\\x00\"                 // unsigned(0) => SenML Name\n        \"\\x70/0aapud0/13/26/1\" // text(16)\n        \"\\x02\"                 // unsigned(2) => SenML Value\n        \"\\x18\\x2A\"             // unsigned(42)\n                               // ,\n        \"\\xA2\"                 // map(2)\n        \"\\x00\"                 // unsigned(0) => SenML Name\n        \"\\x70/0aapud0/13/26/2\" // text(16)\n        \"\\x02\"                 // unsigned(2) => SenML Value\n        \"\\x18\\x2B\"             // unsigned(43)\n    };\n    TEST_ENV(RESOURCES_WITH_PREFIX, TEST_INSTANCE_PATH_WITH_PREFIX);\n    test_skipping(\n            in,\n            (const anjay_uri_path_t[2]) {\n                    MAKE_RESOURCE_PATH_WITH_PREFIX(\"0aapud0\", 13, 26, 1),\n                    MAKE_RESOURCE_PATH_WITH_PREFIX(\"0aapud0\", 13, 26, 2) },\n            2);\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(cbor_in_instance_permuted_with_prefix, resource_skipping) {\n    static const char RESOURCES_WITH_PREFIX[] = {\n        \"\\x82\"                 // array(2)\n        \"\\xA2\"                 // map(2)\n        \"\\x02\"                 // unsigned(2) => SenML Value\n        \"\\x18\\x2A\"             // unsigned(42)\n        \"\\x00\"                 // unsigned(0) => SenML Name\n        \"\\x70/0aapud0/13/26/1\" // text(16)\n                               // ,\n        \"\\xA2\"                 // map(2)\n        \"\\x02\"                 // unsigned(2) => SenML Value\n        \"\\x18\\x2B\"             // unsigned(43)\n        \"\\x00\"                 // unsigned(0) => SenML Name\n        \"\\x70/0aapud0/13/26/2\" // text(16)\n    };\n    TEST_ENV(RESOURCES_WITH_PREFIX, TEST_INSTANCE_PATH_WITH_PREFIX);\n    test_skipping(\n            in,\n            (const anjay_uri_path_t[2]) {\n                    MAKE_RESOURCE_PATH_WITH_PREFIX(\"0aapud0\", 13, 26, 1),\n                    MAKE_RESOURCE_PATH_WITH_PREFIX(\"0aapud0\", 13, 26, 2) },\n            2);\n    TEST_TEARDOWN(OK);\n}\nAVS_UNIT_TEST(cbor_in_instance_with_prefix, multiple_resource_skipping) {\n    static const char RESOURCES_WITH_PREFIX[] = {\n        \"\\x82\"                   // array(2)\n        \"\\xA2\"                   // map(2)\n        \"\\x00\"                   // unsigned(0) => SenML Name\n        \"\\x72/0aapud0/13/26/1/4\" // text(18)\n        \"\\x02\"                   // unsigned(2) => SenML Value\n        \"\\x18\\x2A\"               // unsigned(42)\n                                 // ,\n        \"\\xA2\"                   // map(2)\n        \"\\x00\"                   // unsigned(0) => SenML Name\n        \"\\x72/0aapud0/13/26/2/5\" // text(18)\n        \"\\x02\"                   // unsigned(2) => SenML Value\n        \"\\x18\\x2B\"               // unsigned(43)\n    };\n    TEST_ENV(RESOURCES_WITH_PREFIX, TEST_INSTANCE_PATH_WITH_PREFIX);\n    test_skipping(in,\n                  (const anjay_uri_path_t[2]) {\n                          MAKE_RESOURCE_INSTANCE_PATH_WITH_PREFIX(\"0aapud0\", 13,\n                                                                  26, 1, 4),\n                          MAKE_RESOURCE_INSTANCE_PATH_WITH_PREFIX(\"0aapud0\", 13,\n                                                                  26, 2, 5) },\n                  2);\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(cbor_in_object_with_prefix,\n              with_single_instance_and_some_resources) {\n    static const char RESOURCES_WITH_PREFIX[] = {\n        \"\\x82\"                 // array(2)\n        \"\\xA2\"                 // map(2)\n        \"\\x00\"                 // unsigned(0) => SenML Name\n        \"\\x70/0aapud0/13/26/1\" // text(16)\n        \"\\x02\"                 // unsigned(2) => SenML Value\n        \"\\x18\\x2A\"             // unsigned(42)\n                               // ,\n        \"\\xA2\"                 // map(2)\n        \"\\x00\"                 // unsigned(0) => SenML Name\n        \"\\x70/0aapud0/13/26/2\" // text(16)\n        \"\\x02\"                 // unsigned(2) => SenML Value\n        \"\\x18\\x2B\"             // unsigned(43)\n    };\n    TEST_ENV(RESOURCES_WITH_PREFIX,\n             MAKE_OBJECT_PATH_WITH_PREFIX(\"0aapud0\", 13));\n    check_paths(in,\n                (const anjay_uri_path_t[2]) {\n                        MAKE_RESOURCE_PATH_WITH_PREFIX(\"0aapud0\", 13, 26, 1),\n                        MAKE_RESOURCE_PATH_WITH_PREFIX(\"0aapud0\", 13, 26, 2) },\n                2);\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(cbor_in_object_with_prefix,\n              with_single_instance_and_some_resources_with_different_prefixes) {\n    static const char RESOURCES_WITH_PREFIX[] = {\n        \"\\x82\"                 // array(2)\n        \"\\xA2\"                 // map(2)\n        \"\\x00\"                 // unsigned(0) => SenML Name\n        \"\\x70/prefix1/13/26/1\" // text(16)\n        \"\\x02\"                 // unsigned(2) => SenML Value\n        \"\\x18\\x2A\"             // unsigned(42)\n                               // ,\n        \"\\xA2\"                 // map(2)\n        \"\\x00\"                 // unsigned(0) => SenML Name\n        \"\\x70/prefix2/13/26/2\" // text(16)\n        \"\\x02\"                 // unsigned(2) => SenML Value\n        \"\\x18\\x2B\"             // unsigned(43)\n    };\n    TEST_ENV(RESOURCES_WITH_PREFIX, MAKE_ROOT_PATH());\n    check_paths(in,\n                (const anjay_uri_path_t[2]) {\n                        MAKE_RESOURCE_PATH_WITH_PREFIX(\"prefix1\", 13, 26, 1),\n                        MAKE_RESOURCE_PATH_WITH_PREFIX(\"prefix2\", 13, 26, 2) },\n                2);\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(cbor_in_object_with_prefix,\n              with_single_instance_and_some_resources_with_and_without_prefix) {\n    static const char RESOURCES_WITH_PREFIX[] = {\n        \"\\x82\"                // array(2)\n        \"\\xA2\"                // map(2)\n        \"\\x00\"                // unsigned(0) => SenML Name\n        \"\\x68/13/26/1\"        // text(8)\n        \"\\x02\"                // unsigned(2) => SenML Value\n        \"\\x18\\x2A\"            // unsigned(42)\n                              // ,\n        \"\\xA2\"                // map(2)\n        \"\\x00\"                // unsigned(0) => SenML Name\n        \"\\x6F/prefix/13/26/2\" // text(15)\n        \"\\x02\"                // unsigned(2) => SenML Value\n        \"\\x18\\x2B\"            // unsigned(43)\n    };\n    TEST_ENV(RESOURCES_WITH_PREFIX, MAKE_ROOT_PATH());\n    check_paths(in,\n                (const anjay_uri_path_t[2]) {\n                        MAKE_RESOURCE_PATH(13, 26, 1),\n                        MAKE_RESOURCE_PATH_WITH_PREFIX(\"prefix\", 13, 26, 2) },\n                2);\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(cbor_in_object_with_prefix,\n              with_some_instances_and_some_resources) {\n    static const char RESOURCES_WITH_PREFIX[] = {\n        \"\\x84\"                 // array(4)\n        \"\\xA2\"                 // map(2)\n        \"\\x00\"                 // unsigned(0) => SenML Name\n        \"\\x70/0aapud0/13/26/1\" // text(16)\n        \"\\x02\"                 // unsigned(2) => SenML Value\n        \"\\x18\\x2A\"             // unsigned(42)\n                               // ,\n        \"\\xA2\"                 // map(2)\n        \"\\x00\"                 // unsigned(0) => SenML Name\n        \"\\x70/0aapud0/13/26/2\" // text(16)\n        \"\\x02\"                 // unsigned(2) => SenML Value\n        \"\\x18\\x2B\"             // unsigned(43)\n                               //\n        \"\\xA2\"                 // map(2)\n        \"\\x00\"                 // unsigned(0) => SenML Name\n        \"\\x70/0aapud0/13/27/3\" // text(16)\n        \"\\x02\"                 // unsigned(2) => SenML Value\n        \"\\x18\\x2C\"             // unsigned(44)\n                               // ,\n        \"\\xA2\"                 // map(2)\n        \"\\x00\"                 // unsigned(0) => SenML Name\n        \"\\x70/0aapud0/13/27/4\" // text(16)\n        \"\\x02\"                 // unsigned(2) => SenML Value\n        \"\\x18\\x2D\"             // unsigned(45)\n    };\n    TEST_ENV(RESOURCES_WITH_PREFIX,\n             MAKE_OBJECT_PATH_WITH_PREFIX(\"0aapud0\", 13));\n    check_paths(in,\n                (const anjay_uri_path_t[4]) {\n                        MAKE_RESOURCE_PATH_WITH_PREFIX(\"0aapud0\", 13, 26, 1),\n                        MAKE_RESOURCE_PATH_WITH_PREFIX(\"0aapud0\", 13, 26, 2),\n                        MAKE_RESOURCE_PATH_WITH_PREFIX(\"0aapud0\", 13, 27, 3),\n                        MAKE_RESOURCE_PATH_WITH_PREFIX(\"0aapud0\", 13, 27, 4) },\n                4);\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(cbor_in_object_with_prefix,\n              with_some_instances_and_some_resources_with_different_prefixes) {\n    static const char RESOURCES_WITH_PREFIX[] = {\n        \"\\x84\"                 // array(4)\n        \"\\xA2\"                 // map(2)\n        \"\\x00\"                 // unsigned(0) => SenML Name\n        \"\\x70/prefix1/13/26/1\" // text(16)\n        \"\\x02\"                 // unsigned(2) => SenML Value\n        \"\\x18\\x2A\"             // unsigned(42)\n                               // ,\n        \"\\xA2\"                 // map(2)\n        \"\\x00\"                 // unsigned(0) => SenML Name\n        \"\\x70/prefix2/13/26/2\" // text(16)\n        \"\\x02\"                 // unsigned(2) => SenML Value\n        \"\\x18\\x2B\"             // unsigned(43)\n                               //\n        \"\\xA2\"                 // map(2)\n        \"\\x00\"                 // unsigned(0) => SenML Name\n        \"\\x70/prefix3/13/27/3\" // text(16)\n        \"\\x02\"                 // unsigned(2) => SenML Value\n        \"\\x18\\x2C\"             // unsigned(44)\n                               // ,\n        \"\\xA2\"                 // map(2)\n        \"\\x00\"                 // unsigned(0) => SenML Name\n        \"\\x70/prefix4/13/27/4\" // text(16)\n        \"\\x02\"                 // unsigned(2) => SenML Value\n        \"\\x18\\x2D\"             // unsigned(45)\n    };\n    TEST_ENV(RESOURCES_WITH_PREFIX, MAKE_ROOT_PATH());\n    check_paths(in,\n                (const anjay_uri_path_t[4]) {\n                        MAKE_RESOURCE_PATH_WITH_PREFIX(\"prefix1\", 13, 26, 1),\n                        MAKE_RESOURCE_PATH_WITH_PREFIX(\"prefix2\", 13, 26, 2),\n                        MAKE_RESOURCE_PATH_WITH_PREFIX(\"prefix3\", 13, 27, 3),\n                        MAKE_RESOURCE_PATH_WITH_PREFIX(\"prefix4\", 13, 27, 4) },\n                4);\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(cbor_in_with_prefix_mismatch, mismatch_no_prefix_in_base) {\n    static const char RESOURCE_WITH_PREFIX[] = {\n        \"\\x81\"                 // array(1)\n        \"\\xA2\"                 // map(3)\n        \"\\x00\"                 // unsigned(0) => SenML Name\n        \"\\x70/0aapud0/13/26/1\" // text(16)\n        \"\\x02\"                 // unsigned(2) => SenML Value\n        \"\\x18\\x2A\"             // unsigned(42)\n    };\n    TEST_ENV(RESOURCE_WITH_PREFIX, TEST_RESOURCE_PATH);\n    ASSERT_EQ(_anjay_input_get_path(in, NULL, NULL), ANJAY_ERR_BAD_REQUEST);\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(cbor_in_with_prefix_mismatch, mismatch_no_prefix_in_payload) {\n    static const char RESOURCE_WITH_PREFIX[] = {\n        \"\\x81\"         // array(1)\n        \"\\xA2\"         // map(3)\n        \"\\x00\"         // unsigned(0) => SenML Name\n        \"\\x68/13/26/1\" // text(8)\n        \"\\x02\"         // unsigned(2) => SenML Value\n        \"\\x18\\x2A\"     // unsigned(42)\n    };\n    TEST_ENV(RESOURCE_WITH_PREFIX, TEST_RESOURCE_PATH_WITH_PREFIX);\n    ASSERT_EQ(_anjay_input_get_path(in, NULL, NULL), ANJAY_ERR_BAD_REQUEST);\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(cbor_in_with_prefix_mismatch, mismatch_different_prefix) {\n    static const char RESOURCE_WITH_PREFIX[] = {\n        \"\\x81\"                 // array(1)\n        \"\\xA2\"                 // map(3)\n        \"\\x00\"                 // unsigned(0) => SenML Name\n        \"\\x70/0aapud0/13/26/1\" // text(16)\n        \"\\x02\"                 // unsigned(2) => SenML Value\n        \"\\x18\\x2A\"             // unsigned(42)\n    };\n    TEST_ENV(RESOURCE_WITH_PREFIX,\n             MAKE_RESOURCE_PATH_WITH_PREFIX(\"prefix\", 13, 26, 1));\n    ASSERT_EQ(_anjay_input_get_path(in, NULL, NULL), ANJAY_ERR_BAD_REQUEST);\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(cbor_in_with_prefix_mismatch,\n              mismatch_first_prefix_fine_second_no) {\n    static const char RESOURCE_WITH_PREFIX[] = {\n        \"\\x82\"                 // array(2)\n        \"\\xA2\"                 // map(2)\n        \"\\x00\"                 // unsigned(0) => SenML Name\n        \"\\x70/prefix1/13/26/1\" // text(16)\n        \"\\x02\"                 // unsigned(2) => SenML Value\n        \"\\x18\\x2A\"             // unsigned(42)\n                               // ,\n        \"\\xA2\"                 // map(2)\n        \"\\x00\"                 // unsigned(0) => SenML Name\n        \"\\x70/prefix2/13/26/2\" // text(16)\n        \"\\x02\"                 // unsigned(2) => SenML Value\n        \"\\x18\\x2B\"             // unsigned(43)\n    };\n    TEST_ENV(RESOURCE_WITH_PREFIX,\n             MAKE_INSTANCE_PATH_WITH_PREFIX(\"prefix1\", 13, 26));\n    check_path(in, &MAKE_RESOURCE_PATH_WITH_PREFIX(\"prefix1\", 13, 26, 1), 42);\n    ASSERT_OK(_anjay_input_next_entry(in));\n    ASSERT_EQ(_anjay_input_get_path(in, NULL, NULL), ANJAY_ERR_BAD_REQUEST);\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(cbor_in_with_prefix, valid_paths) {\n    anjay_uri_path_t path;\n    ASSERT_OK(parse_absolute_path(&path, \"/prefix\"));\n    ASSERT_OK(parse_absolute_path(&path, \"/prefix/1\"));\n    ASSERT_OK(parse_absolute_path(&path, \"/prefix/1/2\"));\n    ASSERT_OK(parse_absolute_path(&path, \"/prefix/1/2/3\"));\n    ASSERT_OK(parse_absolute_path(&path, \"/prefix/1/2/3/4\"));\n    ASSERT_OK(parse_absolute_path(&path, \"/prefix/1/2/3/65534\"));\n    ASSERT_OK(parse_absolute_path(&path, \"/prefix/65534/65534/65534/65534\"));\n    ASSERT_OK(parse_absolute_path(&path, \"/dev65535\"));\n    ASSERT_OK(parse_absolute_path(&path, \"/.2137\"));\n}\n\nAVS_UNIT_TEST(cbor_in_with_prefix, invalid_paths) {\n    anjay_uri_path_t path;\n    ASSERT_FAIL(parse_absolute_path(&path, \"prefix\"));\n    ASSERT_FAIL(parse_absolute_path(&path, \"/prefix/\"));\n    ASSERT_FAIL(parse_absolute_path(&path, \"/prefix/prefix\"));\n    ASSERT_FAIL(parse_absolute_path(&path, \"/prefix//\"));\n    ASSERT_FAIL(parse_absolute_path(&path, \"/prefix/1/\"));\n    ASSERT_FAIL(parse_absolute_path(&path, \"/prefix/1/2/\"));\n    ASSERT_FAIL(parse_absolute_path(&path, \"/prefix/1/2/3/\"));\n    ASSERT_FAIL(parse_absolute_path(&path, \"/prefix/1/2/3/4/\"));\n    ASSERT_FAIL(parse_absolute_path(&path, \"/prefix/1/2/3/65535\"));\n    ASSERT_FAIL(parse_absolute_path(&path, \"/prefix/1/2/3/65536\"));\n    ASSERT_FAIL(parse_absolute_path(&path, \"/prefix/1/2//3\"));\n    ASSERT_FAIL(parse_absolute_path(&path, \"/prefix/-1/2/3\"));\n    ASSERT_FAIL(parse_absolute_path(&path, \"/dev655351\"));\n}\n\nAVS_UNIT_TEST(cbor_in_with_prefix, get_path_for_resource_instance_path) {\n    static const char RESOURCE_INSTANCE_PATH[] = {\n        \"\\x81\"                 // array(1)\n        \"\\xA1\"                 // map(1)\n        \"\\x00\"                 // unsigned(0) => SenML Name\n        \"\\x70/0aapud0/3/0/0/1\" // text(16)\n    };\n    TEST_ENV(RESOURCE_INSTANCE_PATH,\n             MAKE_RESOURCE_INSTANCE_PATH_WITH_PREFIX(\"0aapud0\", 3, 0, 0, 1));\n    anjay_uri_path_t path;\n    ASSERT_OK(_anjay_input_get_path(in, &path, NULL));\n    URI_EQUAL(&path,\n              &MAKE_RESOURCE_INSTANCE_PATH_WITH_PREFIX(\"0aapud0\", 3, 0, 0, 1));\n    TEST_TEARDOWN(OK);\n}\n\n#    define TEST_NAME_WITH_PREFIX_LONGER_THAN(prefix_len, base_name)           \\\n        static const size_t RESOURCE_WITH_PREFIX_MAX_SIZE = 100;               \\\n        const size_t path_len = prefix_len + strlen(\"//13/26/1\");              \\\n        size_t payload_size_without_prefix;                                    \\\n        if (path_len > 23) {                                                   \\\n            payload_size_without_prefix =                                      \\\n                    strlen(\"\\x81\\xA2\\xFF\\x78\\xFF//13/26/1\\x02\\x18\\x2A\");       \\\n        } else {                                                               \\\n            payload_size_without_prefix =                                      \\\n                    strlen(\"\\x81\\xA2\\xFF\\xFF//13/26/1\\x02\\x18\\x2A\");           \\\n        }                                                                      \\\n        ASSERT_TRUE(RESOURCE_WITH_PREFIX_MAX_SIZE                              \\\n                    >= (prefix_len + 1 + payload_size_without_prefix));        \\\n        uint8_t resource_with_prefix[RESOURCE_WITH_PREFIX_MAX_SIZE];           \\\n                                                                               \\\n        /* If this assertion is not met then change the path encoding */       \\\n        ASSERT_TRUE(path_len < 256);                                           \\\n                                                                               \\\n        char prefix[prefix_len + 1] = { 0 };                                   \\\n        for (size_t i = 0; i < prefix_len; ++i) {                              \\\n            prefix[i] = 'A';                                                   \\\n        }                                                                      \\\n                                                                               \\\n        int ret;                                                               \\\n        if (path_len > 23) {                                                   \\\n            ret = snprintf(resource_with_prefix,                               \\\n                           RESOURCE_WITH_PREFIX_MAX_SIZE,                      \\\n                           \"\\x81\\xA2\\xFF\\x78\\xFF/%s/13/26/1\\x02\\x18\\x2A\",      \\\n                           prefix);                                            \\\n            resource_with_prefix[4] = (uint8_t) path_len;                      \\\n        } else {                                                               \\\n            ret = snprintf(resource_with_prefix,                               \\\n                           RESOURCE_WITH_PREFIX_MAX_SIZE,                      \\\n                           \"\\x81\\xA2\\xFF\\xFF/%s/13/26/1\\x02\\x18\\x2A\", prefix); \\\n            resource_with_prefix[3] = 0x60 + (uint8_t) path_len;               \\\n        }                                                                      \\\n        resource_with_prefix[2] = base_name ? '\\x21' : '\\x00';                 \\\n                                                                               \\\n        ASSERT_TRUE(ret == (int) (prefix_len + payload_size_without_prefix));  \\\n                                                                               \\\n        TEST_ENV(resource_with_prefix, MAKE_ROOT_PATH());                      \\\n                                                                               \\\n        anjay_uri_path_t path;                                                 \\\n        ASSERT_EQ(_anjay_input_get_path(in, &path, NULL),                      \\\n                  ANJAY_ERR_BAD_REQUEST);                                      \\\n        TEST_TEARDOWN(FAIL);\n\nAVS_UNIT_TEST(\n        cbor_in_resource_with_prefix,\n        single_instance_with_prefix_in_name_longer_than_max_path_string_size) {\n    TEST_NAME_WITH_PREFIX_LONGER_THAN(MAX_PATH_STRING_SIZE, false);\n}\n\nAVS_UNIT_TEST(\n        cbor_in_resource_with_prefix,\n        single_instance_with_prefix_in_basename_longer_than_max_path_string_size) {\n    TEST_NAME_WITH_PREFIX_LONGER_THAN(MAX_PATH_STRING_SIZE, true);\n}\n\nAVS_UNIT_TEST(cbor_in_resource_with_prefix,\n              single_instance_with_prefix_in_name_longer_than_max_prefix_len) {\n    TEST_NAME_WITH_PREFIX_LONGER_THAN(ANJAY_GATEWAY_MAX_PREFIX_LEN, false);\n}\n\nAVS_UNIT_TEST(\n        cbor_in_resource_with_prefix,\n        single_instance_with_prefix_in_basename_longer_than_max_prefix_len) {\n    TEST_NAME_WITH_PREFIX_LONGER_THAN(ANJAY_GATEWAY_MAX_PREFIX_LEN, true);\n}\n#endif // ANJAY_WITH_LWM2M_GATEWAY\n\n#undef COMPOSITE_TEST_ENV\n#undef TEST_VALUE_ENV\n#undef TEST_ENV\n"
  },
  {
    "path": "tests/core/io/corelnk.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#define AVS_UNIT_ENABLE_SHORT_ASSERTS\n#include <avsystem/commons/avs_unit_mocksock.h>\n#include <avsystem/commons/avs_unit_test.h>\n\n#include \"tests/utils/dm.h\"\n\nstatic const anjay_dm_object_def_t *const OBJ2 =\n        &(const anjay_dm_object_def_t) {\n            .oid = 69,\n            .version = \"21.37\",\n            .handlers = { ANJAY_MOCK_DM_HANDLERS_BASIC }\n        };\n\nstatic const anjay_dm_object_def_t *const FAKE_SERVER_WITH_VER =\n        &(const anjay_dm_object_def_t) {\n            .oid = 1,\n            .version = \"1.1\",\n            .handlers = { ANJAY_MOCK_DM_HANDLERS }\n        };\n\n#define PREPARE_DM()                                                         \\\n    /* Security and OSCORE objects should be omitted */                      \\\n    _anjay_mock_dm_expect_list_instances(                                    \\\n            anjay, &FAKE_SERVER_WITH_VER, 0,                                 \\\n            (const anjay_iid_t[]) { 14, 42, 69, ANJAY_ID_INVALID });         \\\n    _anjay_mock_dm_expect_list_instances(anjay, &OBJ_WITH_RESET, 0,          \\\n                                         (const anjay_iid_t[]) {             \\\n                                                 ANJAY_ID_INVALID });        \\\n    _anjay_mock_dm_expect_list_instances(                                    \\\n            anjay, &OBJ, 0, (const anjay_iid_t[]) { 14, ANJAY_ID_INVALID }); \\\n    _anjay_mock_dm_expect_list_instances(                                    \\\n            anjay, &OBJ2, 0,                                                 \\\n            (const anjay_iid_t[]) { 14, 42, 69, ANJAY_ID_INVALID });         \\\n    _anjay_mock_dm_expect_list_instances(                                    \\\n            anjay, (const anjay_dm_object_def_t *const *) &EXECUTE_OBJ, 0,   \\\n            (const anjay_iid_t[]) { 14, 42, 69, ANJAY_ID_INVALID });\n\nAVS_UNIT_TEST(io_corelnk, test_corelnk_output) {\n    DM_TEST_INIT_WITH_OBJECTS(\n            &OBJ2, &OBJ, &FAKE_SECURITY, &FAKE_SERVER_WITH_VER,\n            (const anjay_dm_object_def_t *const *) &EXECUTE_OBJ,\n            (const anjay_dm_object_def_t *const *) &OBJ_WITH_RESET);\n\n    char *buf = NULL;\n    PREPARE_DM();\n\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_corelnk_query_dm(anjay_unlocked, &anjay_unlocked->dm,\n                                    ANJAY_LWM2M_VERSION_1_0, &buf));\n    ANJAY_MUTEX_UNLOCK(anjay);\n    AVS_UNIT_ASSERT_EQUAL_STRING(\n            buf,\n            \"</1>;ver=\\\"1.1\\\",</1/14>,</1/42>,</1/69>,</25>,</42/14>,</\"\n            \"69>;ver=\\\"21.37\\\",</69/\"\n            \"14>,</69/42>,</69/69>,</128/14>,</128/42>,</128/69>\");\n    avs_free(buf);\n    buf = NULL;\n#ifdef ANJAY_WITH_LWM2M11\n    PREPARE_DM();\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_corelnk_query_dm(anjay_unlocked, &anjay_unlocked->dm,\n                                    ANJAY_LWM2M_VERSION_1_1, &buf));\n    ANJAY_MUTEX_UNLOCK(anjay);\n\n    // both versions are valid\n    char *with_version = \"</1>;ver=1.1,</1/14>,</1/42>,</1/69>,</25>,</42/\"\n                         \"14>,</69>;ver=21.37,</69/\"\n                         \"14>,</69/42>,</69/69>,</128/14>,</128/42>,</128/69>\";\n    char *without_version =\n            \"</1/14>,</1/42>,</1/69>,</25>,</42/14>,</69>;ver=21.37,</69/\"\n            \"14>,</69/42>,</69/69>,</128/14>,</128/42>,</128/69>\";\n    ASSERT_TRUE(strcmp(buf, with_version) == 0\n                || strcmp(buf, without_version) == 0);\n    avs_free(buf);\n    buf = NULL;\n#endif // ANJAY_WITH_LWM2M11\n\n#ifdef ANJAY_WITH_LWM2M12\n    PREPARE_DM();\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_corelnk_query_dm(anjay_unlocked, &anjay_unlocked->dm,\n                                    ANJAY_LWM2M_VERSION_1_2, &buf));\n    ANJAY_MUTEX_UNLOCK(anjay);\n    AVS_UNIT_ASSERT_EQUAL_STRING(\n            buf,\n            \"</1>;ver=1.1,</1/14>,</1/42>,</1/69>,</25>,</42/14>,</\"\n            \"69>;ver=21.37,</69/\"\n            \"14>,</69/42>,</69/69>,</128/14>,</128/42>,</128/69>\");\n#endif // ANJAY_WITH_LWM2M12\n    DM_TEST_FINISH;\n    avs_free(buf);\n}\n"
  },
  {
    "path": "tests/core/io/dm_batch.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay/lwm2m_send.h>\n\n#ifdef ANJAY_WITH_LWM2M_GATEWAY\n#    include <anjay/lwm2m_gateway.h>\n#endif // ANJAY_WITH_LWM2M_GATEWAY\n\n#include <avsystem/commons/avs_base64.h>\n#include <avsystem/commons/avs_unit_test.h>\n\n#include <inttypes.h>\n\n#include \"tests/utils/mock_clock.h\"\n\n#define TEST_OID 1234\n\n#define BYTES_RID 0\n#define STRING_RID 1\n#define INT_RID 2\n#define UINT_RID 3\n#define DOUBLE_RID 4\n#define BOOL_RID 5\n#define OBJLNK_RID 6\n#define INT_ARRAY_RID 7\n#define ILLEGAL_IMPL_RID 8\n\nconst char test_bytes[] =\n        \"cfqgldupfjwxzxtmlzdouyimtewybqzmninterrjmrpvfsfyixtnvaqygtfiueme\";\n#define TEST_BYTES_SIZE (sizeof(test_bytes) - 1)\n#define STRING_VALUE \"test\"\n#define INT_VALUE 122333221\n#define UINT_VALUE UINT64_MAX\n#define DOUBLE_VALUE 1.1\n#define BOOL_VALUE true\n#define OBJLNK_OID 1\n#define OBJLNK_IID 2\nconst int int_array[4] = { 10, 20, 30, 40 };\nconst size_t int_array_size = sizeof(int_array) / sizeof(int_array[0]);\n\n#define MOCK_CLOCK_START_RELATIVE 1000\n#define MOCK_CLOCK_START_ABSOLUTE \\\n    (SENML_TIME_SECONDS_THRESHOLD + MOCK_CLOCK_START_RELATIVE)\n\nstatic int test_list_resources(anjay_t *anjay,\n                               const anjay_dm_object_def_t *const *obj_ptr,\n                               anjay_iid_t iid,\n                               anjay_dm_resource_list_ctx_t *ctx) {\n    (void) anjay;\n    (void) obj_ptr;\n    (void) iid;\n    anjay_dm_emit_res(ctx, BYTES_RID, ANJAY_DM_RES_R, ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, STRING_RID, ANJAY_DM_RES_R, ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, INT_RID, ANJAY_DM_RES_R, ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, UINT_RID, ANJAY_DM_RES_R, ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, DOUBLE_RID, ANJAY_DM_RES_R, ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, BOOL_RID, ANJAY_DM_RES_R, ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, OBJLNK_RID, ANJAY_DM_RES_R, ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, INT_ARRAY_RID, ANJAY_DM_RES_RM,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx, ILLEGAL_IMPL_RID, ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_PRESENT);\n    return 0;\n}\n\nstatic int test_resource_read(anjay_t *anjay,\n                              const anjay_dm_object_def_t *const *obj_ptr,\n                              anjay_iid_t iid,\n                              anjay_rid_t rid,\n                              anjay_riid_t riid,\n                              anjay_output_ctx_t *ctx) {\n    (void) anjay;\n    (void) obj_ptr;\n    (void) iid;\n\n    switch (rid) {\n    case BYTES_RID: {\n        assert(riid == ANJAY_ID_INVALID);\n        anjay_ret_bytes_ctx_t *bytes_ctx;\n        int retval = -1;\n        if ((bytes_ctx = anjay_ret_bytes_begin(ctx, TEST_BYTES_SIZE))) {\n            const size_t append_size = TEST_BYTES_SIZE / 4;\n            for (size_t i = 0; i < TEST_BYTES_SIZE; i += append_size) {\n                retval = anjay_ret_bytes_append(bytes_ctx, test_bytes + i,\n                                                append_size);\n                if (retval) {\n                    break;\n                }\n            }\n        }\n        return retval;\n    }\n    case STRING_RID:\n        assert(riid == ANJAY_ID_INVALID);\n        return anjay_ret_string(ctx, STRING_VALUE);\n    case INT_RID:\n        assert(riid == ANJAY_ID_INVALID);\n        return anjay_ret_i64(ctx, INT_VALUE);\n    case UINT_RID:\n        assert(riid == ANJAY_ID_INVALID);\n        return anjay_ret_u64(ctx, UINT_VALUE);\n    case DOUBLE_RID:\n        assert(riid == ANJAY_ID_INVALID);\n        return anjay_ret_double(ctx, DOUBLE_VALUE);\n    case BOOL_RID:\n        assert(riid == ANJAY_ID_INVALID);\n        return anjay_ret_bool(ctx, BOOL_VALUE);\n    case OBJLNK_RID:\n        assert(riid == ANJAY_ID_INVALID);\n        return anjay_ret_objlnk(ctx, OBJLNK_OID, OBJLNK_IID);\n    case INT_ARRAY_RID:\n        assert(riid < int_array_size);\n        return anjay_ret_i32(ctx, int_array[riid]);\n    case ILLEGAL_IMPL_RID:\n        assert(riid == ANJAY_ID_INVALID);\n        // Invalid case, it shouldn't be possible to call anjay_ret_* twice.\n        return anjay_ret_i64(ctx, 0) || anjay_ret_i64(ctx, 1);\n    default:\n        return -1;\n    }\n}\n\nstatic int\ntest_list_resource_instances(anjay_t *anjay,\n                             const anjay_dm_object_def_t *const *obj_ptr,\n                             anjay_iid_t iid,\n                             anjay_rid_t rid,\n                             anjay_dm_list_ctx_t *ctx) {\n    (void) anjay;\n    (void) obj_ptr;\n    (void) iid;\n\n    switch (rid) {\n    case INT_ARRAY_RID:\n        for (anjay_riid_t riid = 0; riid < int_array_size; ++riid) {\n            anjay_dm_emit(ctx, riid);\n        }\n        return 0;\n    default:\n        AVS_UNREACHABLE(\n                \"Attempted to list instances in a single-instance resource\");\n        return ANJAY_ERR_INTERNAL;\n    }\n}\n\nstatic const anjay_dm_object_def_t OBJECT_DEF = {\n    .oid = TEST_OID,\n    .handlers = {\n        .list_instances = anjay_dm_list_instances_SINGLE,\n        .list_resources = test_list_resources,\n        .resource_read = test_resource_read,\n        .list_resource_instances = test_list_resource_instances\n    }\n};\n\n#define TEST_SETUP(TimeStart)                                       \\\n    static const anjay_configuration_t CONFIG = {                   \\\n        .endpoint_name = \"test\"                                     \\\n    };                                                              \\\n                                                                    \\\n    anjay_t *anjay = anjay_new(&CONFIG);                            \\\n    AVS_UNIT_ASSERT_NOT_NULL(anjay);                                \\\n                                                                    \\\n    const anjay_dm_object_def_t *test_object_def_ptr = &OBJECT_DEF; \\\n    AVS_UNIT_ASSERT_SUCCESS(                                        \\\n            anjay_register_object(anjay, &test_object_def_ptr));    \\\n    anjay_batch_builder_t *builder = _anjay_batch_builder_new();    \\\n    AVS_UNIT_ASSERT_NOT_NULL(builder);                              \\\n                                                                    \\\n    _anjay_mock_clock_start(                                        \\\n            avs_time_monotonic_from_scalar((TimeStart), AVS_TIME_S));\n\n#define TEST_TEARDOWN()                     \\\n    _anjay_batch_builder_cleanup(&builder); \\\n    anjay_delete(anjay);                    \\\n    _anjay_mock_clock_finish();\n\nstatic inline bool is_data_valid(anjay_batch_data_t first,\n                                 anjay_batch_data_t second) {\n    bool retval = (first.type == second.type);\n    if (!retval) {\n        return retval;\n    }\n\n    switch (first.type) {\n    case ANJAY_BATCH_DATA_BYTES:\n        retval = (first.value.bytes.length == second.value.bytes.length)\n                 && !memcmp(first.value.bytes.data,\n                            second.value.bytes.data,\n                            first.value.bytes.length);\n        break;\n    case ANJAY_BATCH_DATA_STRING:\n        retval = !strcmp(first.value.string, second.value.string);\n        break;\n    case ANJAY_BATCH_DATA_INT:\n        retval = (first.value.int_value == second.value.int_value);\n        break;\n    case ANJAY_BATCH_DATA_UINT:\n        retval = (first.value.uint_value == second.value.uint_value);\n        break;\n    case ANJAY_BATCH_DATA_DOUBLE:\n        retval = (first.value.double_value == second.value.double_value);\n        break;\n    case ANJAY_BATCH_DATA_BOOL:\n        retval = (first.value.bool_value == second.value.bool_value);\n        break;\n    case ANJAY_BATCH_DATA_OBJLNK:\n        retval = (first.value.objlnk.oid == second.value.objlnk.oid)\n                 && (first.value.objlnk.iid == second.value.objlnk.iid);\n        break;\n    case ANJAY_BATCH_DATA_START_AGGREGATE:\n        retval = true;\n        break;\n    default:\n        AVS_UNREACHABLE(\"fix tests\");\n        retval = false;\n    }\n\n    return retval;\n}\n\nstatic inline bool is_time_almost_equal(avs_time_real_t older,\n                                        avs_time_real_t newer) {\n    return avs_time_duration_less(avs_time_real_diff(newer, older),\n                                  avs_time_duration_from_scalar(10,\n                                                                AVS_TIME_MS));\n}\n\n#ifdef ANJAY_WITH_SEND\nstatic bool is_entry_valid(anjay_batch_entry_t *entry,\n                           anjay_rid_t rid,\n                           anjay_riid_t riid,\n                           anjay_batch_data_t data) {\n    if (entry->path.ids[ANJAY_ID_OID] != TEST_OID\n            || entry->path.ids[ANJAY_ID_IID] != 0\n            || entry->path.ids[ANJAY_ID_RID] != rid\n            || entry->path.ids[ANJAY_ID_RIID] != riid\n            || !is_data_valid(entry->data, data)) {\n        return false;\n    }\n    if (data.type == ANJAY_BATCH_DATA_START_AGGREGATE) {\n        return !avs_time_real_valid(entry->timestamp);\n    } else {\n        return is_time_almost_equal(entry->timestamp, avs_time_real_now());\n    }\n}\n\nstatic inline int\nadd_current(anjay_batch_builder_t *builder, anjay_t *anjay, anjay_rid_t rid) {\n    return anjay_send_batch_data_add_current(\n            (anjay_send_batch_builder_t *) builder, anjay, TEST_OID, 0, rid);\n}\n\nAVS_UNIT_TEST(dm_batch, single_bytes) {\n    TEST_SETUP(MOCK_CLOCK_START_RELATIVE);\n\n    AVS_UNIT_ASSERT_SUCCESS(add_current(builder, anjay, BYTES_RID));\n\n    AVS_UNIT_ASSERT_EQUAL(AVS_LIST_SIZE(builder->list), 1);\n    AVS_UNIT_ASSERT_TRUE(is_entry_valid(AVS_LIST_TAIL(builder->list),\n                                        BYTES_RID,\n                                        ANJAY_ID_INVALID,\n                                        (anjay_batch_data_t) {\n                                            .type = ANJAY_BATCH_DATA_BYTES,\n                                            .value.bytes = {\n                                                .data = test_bytes,\n                                                .length = TEST_BYTES_SIZE\n                                            }\n                                        }));\n    TEST_TEARDOWN();\n}\n\nAVS_UNIT_TEST(dm_batch, single_string) {\n    TEST_SETUP(MOCK_CLOCK_START_RELATIVE);\n\n    AVS_UNIT_ASSERT_SUCCESS(add_current(builder, anjay, STRING_RID));\n\n    AVS_UNIT_ASSERT_EQUAL(AVS_LIST_SIZE(builder->list), 1);\n    AVS_UNIT_ASSERT_TRUE(is_entry_valid(AVS_LIST_TAIL(builder->list),\n                                        STRING_RID,\n                                        ANJAY_ID_INVALID,\n                                        (anjay_batch_data_t) {\n                                            .type = ANJAY_BATCH_DATA_STRING,\n                                            .value = {\n                                                .string = STRING_VALUE\n                                            }\n                                        }));\n    TEST_TEARDOWN();\n}\n\nAVS_UNIT_TEST(dm_batch, single_int) {\n    TEST_SETUP(MOCK_CLOCK_START_RELATIVE);\n\n    AVS_UNIT_ASSERT_SUCCESS(add_current(builder, anjay, INT_RID));\n\n    AVS_UNIT_ASSERT_EQUAL(AVS_LIST_SIZE(builder->list), 1);\n    AVS_UNIT_ASSERT_TRUE(is_entry_valid(AVS_LIST_TAIL(builder->list),\n                                        INT_RID,\n                                        ANJAY_ID_INVALID,\n                                        (anjay_batch_data_t) {\n                                            .type = ANJAY_BATCH_DATA_INT,\n                                            .value = {\n                                                .int_value = INT_VALUE\n                                            }\n                                        }));\n    TEST_TEARDOWN();\n}\n\nAVS_UNIT_TEST(dm_batch, single_uint) {\n    TEST_SETUP(MOCK_CLOCK_START_RELATIVE);\n\n    AVS_UNIT_ASSERT_SUCCESS(add_current(builder, anjay, UINT_RID));\n\n    AVS_UNIT_ASSERT_EQUAL(AVS_LIST_SIZE(builder->list), 1);\n    AVS_UNIT_ASSERT_TRUE(is_entry_valid(AVS_LIST_TAIL(builder->list),\n                                        UINT_RID,\n                                        ANJAY_ID_INVALID,\n                                        (anjay_batch_data_t) {\n                                            .type = ANJAY_BATCH_DATA_UINT,\n                                            .value = {\n                                                .uint_value = UINT_VALUE\n                                            }\n                                        }));\n    TEST_TEARDOWN();\n}\n\nAVS_UNIT_TEST(dm_batch, single_double) {\n    TEST_SETUP(MOCK_CLOCK_START_RELATIVE);\n\n    AVS_UNIT_ASSERT_SUCCESS(add_current(builder, anjay, DOUBLE_RID));\n\n    AVS_UNIT_ASSERT_EQUAL(AVS_LIST_SIZE(builder->list), 1);\n    AVS_UNIT_ASSERT_TRUE(is_entry_valid(AVS_LIST_TAIL(builder->list),\n                                        DOUBLE_RID,\n                                        ANJAY_ID_INVALID,\n                                        (anjay_batch_data_t) {\n                                            .type = ANJAY_BATCH_DATA_DOUBLE,\n                                            .value = {\n                                                .double_value = DOUBLE_VALUE\n                                            }\n                                        }));\n    TEST_TEARDOWN();\n}\n\nAVS_UNIT_TEST(dm_batch, single_bool) {\n    TEST_SETUP(MOCK_CLOCK_START_RELATIVE);\n\n    AVS_UNIT_ASSERT_SUCCESS(add_current(builder, anjay, BOOL_RID));\n\n    AVS_UNIT_ASSERT_EQUAL(AVS_LIST_SIZE(builder->list), 1);\n    AVS_UNIT_ASSERT_TRUE(is_entry_valid(AVS_LIST_TAIL(builder->list),\n                                        BOOL_RID,\n                                        ANJAY_ID_INVALID,\n                                        (anjay_batch_data_t) {\n                                            .type = ANJAY_BATCH_DATA_BOOL,\n                                            .value = {\n                                                .bool_value = BOOL_VALUE\n                                            }\n                                        }));\n    TEST_TEARDOWN();\n}\n\nAVS_UNIT_TEST(dm_batch, single_objlnk) {\n    TEST_SETUP(MOCK_CLOCK_START_RELATIVE);\n\n    AVS_UNIT_ASSERT_SUCCESS(add_current(builder, anjay, OBJLNK_RID));\n\n    AVS_UNIT_ASSERT_EQUAL(AVS_LIST_SIZE(builder->list), 1);\n    AVS_UNIT_ASSERT_TRUE(is_entry_valid(AVS_LIST_TAIL(builder->list),\n                                        OBJLNK_RID,\n                                        ANJAY_ID_INVALID,\n                                        (anjay_batch_data_t) {\n                                            .type = ANJAY_BATCH_DATA_OBJLNK,\n                                            .value.objlnk = {\n                                                .oid = OBJLNK_OID,\n                                                .iid = OBJLNK_IID\n                                            }\n                                        }));\n    TEST_TEARDOWN();\n}\n\nAVS_UNIT_TEST(dm_batch, two_resources) {\n    TEST_SETUP(MOCK_CLOCK_START_RELATIVE);\n\n    AVS_UNIT_ASSERT_SUCCESS(add_current(builder, anjay, INT_RID));\n    AVS_UNIT_ASSERT_TRUE(is_entry_valid(AVS_LIST_TAIL(builder->list),\n                                        INT_RID,\n                                        ANJAY_ID_INVALID,\n                                        (anjay_batch_data_t) {\n                                            .type = ANJAY_BATCH_DATA_INT,\n                                            .value = {\n                                                .int_value = INT_VALUE\n                                            }\n                                        }));\n\n    AVS_UNIT_ASSERT_SUCCESS(add_current(builder, anjay, DOUBLE_RID));\n    AVS_UNIT_ASSERT_TRUE(is_entry_valid(AVS_LIST_TAIL(builder->list),\n                                        DOUBLE_RID,\n                                        ANJAY_ID_INVALID,\n                                        (anjay_batch_data_t) {\n                                            .type = ANJAY_BATCH_DATA_DOUBLE,\n                                            .value = {\n                                                .double_value = DOUBLE_VALUE\n                                            }\n                                        }));\n\n    AVS_UNIT_ASSERT_EQUAL(AVS_LIST_SIZE(builder->list), 2);\n\n    TEST_TEARDOWN();\n}\n\nAVS_UNIT_TEST(dm_batch, multiple_instance_resource) {\n    TEST_SETUP(MOCK_CLOCK_START_RELATIVE);\n\n    AVS_UNIT_ASSERT_SUCCESS(add_current(builder, anjay, INT_ARRAY_RID));\n    AVS_UNIT_ASSERT_EQUAL(AVS_LIST_SIZE(builder->list), int_array_size + 1);\n\n    AVS_UNIT_ASSERT_TRUE(\n            is_entry_valid(builder->list, INT_ARRAY_RID, ANJAY_ID_INVALID,\n                           (anjay_batch_data_t) {\n                               .type = ANJAY_BATCH_DATA_START_AGGREGATE\n                           }));\n\n    anjay_batch_entry_t *entry;\n    uint16_t riid = 0;\n    AVS_LIST_FOREACH(entry, AVS_LIST_NEXT(builder->list)) {\n        AVS_UNIT_ASSERT_TRUE(is_entry_valid(entry,\n                                            INT_ARRAY_RID,\n                                            riid,\n                                            (anjay_batch_data_t) {\n                                                .type = ANJAY_BATCH_DATA_INT,\n                                                .value = {\n                                                    .int_value = int_array[riid]\n                                                }\n                                            }));\n        riid++;\n    }\n\n    TEST_TEARDOWN();\n}\n\nAVS_UNIT_TEST(dm_batch, illegal_op) {\n    TEST_SETUP(MOCK_CLOCK_START_RELATIVE);\n\n    AVS_LIST(anjay_batch_entry_t) *initial_append_ptr = builder->append_ptr;\n\n    AVS_UNIT_ASSERT_FAILED(add_current(builder, anjay, ILLEGAL_IMPL_RID));\n\n    AVS_UNIT_ASSERT_EQUAL(AVS_LIST_SIZE(builder->list), 0);\n    AVS_UNIT_ASSERT_TRUE(builder->append_ptr == initial_append_ptr);\n    AVS_UNIT_ASSERT_NULL(*builder->append_ptr);\n\n    TEST_TEARDOWN();\n}\n#endif // ANJAY_WITH_SEND\n\nAVS_UNIT_TEST(dm_batch, serialize_empty) {\n    TEST_SETUP(MOCK_CLOCK_START_RELATIVE);\n\n    anjay_batch_t *batch = _anjay_batch_builder_compile(&builder);\n    AVS_UNIT_ASSERT_NULL(builder);\n    AVS_UNIT_ASSERT_NOT_NULL(batch);\n\n    char buffer[200] = { 0 };\n    avs_stream_outbuf_t stream = AVS_STREAM_OUTBUF_STATIC_INITIALIZER;\n    avs_stream_outbuf_set_buffer(&stream, buffer, sizeof(buffer));\n    anjay_unlocked_output_ctx_t *out_ctx =\n            _anjay_output_senml_like_create((avs_stream_t *) &stream,\n                                            &MAKE_ROOT_PATH(),\n                                            AVS_COAP_FORMAT_SENML_JSON, NULL);\n    AVS_UNIT_ASSERT_NOT_NULL(out_ctx);\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_batch_data_output(anjay_unlocked, batch, 1, out_ctx));\n    ANJAY_MUTEX_UNLOCK(anjay);\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_output_ctx_destroy(&out_ctx));\n    const char expected[] = \"[]\";\n    AVS_UNIT_ASSERT_EQUAL(avs_stream_outbuf_offset(&stream),\n                          sizeof(expected) - 1);\n    AVS_UNIT_ASSERT_EQUAL_STRING(buffer, expected);\n\n    _anjay_batch_release(&batch);\n    AVS_UNIT_ASSERT_NULL(batch);\n\n    TEST_TEARDOWN();\n}\n\nAVS_UNIT_TEST(dm_batch, serialize_bytes) {\n    TEST_SETUP(MOCK_CLOCK_START_RELATIVE);\n\n    char encoded_test_bytes[100] = { 0 };\n    AVS_UNIT_ASSERT_SUCCESS(avs_base64_encode_custom(\n            encoded_test_bytes, sizeof(encoded_test_bytes),\n            (const uint8_t *) test_bytes, TEST_BYTES_SIZE,\n            (avs_base64_config_t) {\n                .alphabet = AVS_BASE64_URL_SAFE_CHARS,\n                .padding_char = '\\0'\n            }));\n\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_batch_add_bytes(\n            builder, &MAKE_RESOURCE_PATH(TEST_OID, 0, BYTES_RID),\n            AVS_TIME_REAL_INVALID, test_bytes, TEST_BYTES_SIZE));\n    anjay_batch_t *batch = _anjay_batch_builder_compile(&builder);\n    AVS_UNIT_ASSERT_NULL(builder);\n    AVS_UNIT_ASSERT_NOT_NULL(batch);\n\n    char buffer[200] = { 0 };\n    avs_stream_outbuf_t stream = AVS_STREAM_OUTBUF_STATIC_INITIALIZER;\n    avs_stream_outbuf_set_buffer(&stream, buffer, sizeof(buffer));\n    anjay_unlocked_output_ctx_t *out_ctx =\n            _anjay_output_senml_like_create((avs_stream_t *) &stream,\n                                            &MAKE_ROOT_PATH(),\n                                            AVS_COAP_FORMAT_SENML_JSON, NULL);\n    AVS_UNIT_ASSERT_NOT_NULL(out_ctx);\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_batch_data_output(anjay_unlocked, batch, 1, out_ctx));\n    ANJAY_MUTEX_UNLOCK(anjay);\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_output_ctx_destroy(&out_ctx));\n    char expected[200];\n    int expected_size =\n            avs_simple_snprintf(expected, sizeof(expected),\n                                \"[{\\\"n\\\":\\\"/%\" PRIu16 \"/%\" PRIu16 \"/%\" PRIu16\n                                \"\\\",\\\"vd\\\":\\\"%s\\\"}]\",\n                                TEST_OID, 0, BYTES_RID, encoded_test_bytes);\n    AVS_UNIT_ASSERT_EQUAL(avs_stream_outbuf_offset(&stream), expected_size);\n    AVS_UNIT_ASSERT_EQUAL_STRING(buffer, expected);\n\n    _anjay_batch_release(&batch);\n    AVS_UNIT_ASSERT_NULL(batch);\n\n    TEST_TEARDOWN();\n}\n\nAVS_UNIT_TEST(dm_batch, serialize_one_resource) {\n    TEST_SETUP(MOCK_CLOCK_START_RELATIVE);\n\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_batch_add_int(\n            builder, &MAKE_RESOURCE_PATH(TEST_OID, 0, INT_RID),\n            AVS_TIME_REAL_INVALID, INT_VALUE));\n    anjay_batch_t *batch = _anjay_batch_builder_compile(&builder);\n    AVS_UNIT_ASSERT_NULL(builder);\n    AVS_UNIT_ASSERT_NOT_NULL(batch);\n\n    char buffer[200] = { 0 };\n    avs_stream_outbuf_t stream = AVS_STREAM_OUTBUF_STATIC_INITIALIZER;\n    avs_stream_outbuf_set_buffer(&stream, buffer, sizeof(buffer));\n    anjay_unlocked_output_ctx_t *out_ctx =\n            _anjay_output_senml_like_create((avs_stream_t *) &stream,\n                                            &MAKE_ROOT_PATH(),\n                                            AVS_COAP_FORMAT_SENML_JSON, NULL);\n    AVS_UNIT_ASSERT_NOT_NULL(out_ctx);\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_batch_data_output(anjay_unlocked, batch, 1, out_ctx));\n    ANJAY_MUTEX_UNLOCK(anjay);\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_output_ctx_destroy(&out_ctx));\n    char expected[200];\n    int expected_size =\n            avs_simple_snprintf(expected, sizeof(expected),\n                                \"[{\\\"n\\\":\\\"/%\" PRIu16 \"/%\" PRIu16 \"/%\" PRIu16\n                                \"\\\",\\\"v\\\":%\" PRIi64 \"}]\",\n                                TEST_OID, 0, INT_RID, (int64_t) INT_VALUE);\n    AVS_UNIT_ASSERT_EQUAL(avs_stream_outbuf_offset(&stream), expected_size);\n    AVS_UNIT_ASSERT_EQUAL_STRING(buffer, expected);\n\n    _anjay_batch_release(&batch);\n    AVS_UNIT_ASSERT_NULL(batch);\n\n    TEST_TEARDOWN();\n}\n\nAVS_UNIT_TEST(dm_batch, serialize_one_resource_with_absolute_timestamp) {\n    TEST_SETUP(MOCK_CLOCK_START_ABSOLUTE);\n\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_batch_add_int(\n            builder, &MAKE_RESOURCE_PATH(TEST_OID, 0, INT_RID),\n            avs_time_real_from_scalar(MOCK_CLOCK_START_ABSOLUTE - 123,\n                                      AVS_TIME_S),\n            INT_VALUE));\n    anjay_batch_t *batch = _anjay_batch_builder_compile(&builder);\n    AVS_UNIT_ASSERT_NULL(builder);\n    AVS_UNIT_ASSERT_NOT_NULL(batch);\n\n    char buffer[200] = { 0 };\n    avs_stream_outbuf_t stream = AVS_STREAM_OUTBUF_STATIC_INITIALIZER;\n    avs_stream_outbuf_set_buffer(&stream, buffer, sizeof(buffer));\n    anjay_unlocked_output_ctx_t *out_ctx =\n            _anjay_output_senml_like_create((avs_stream_t *) &stream,\n                                            &MAKE_ROOT_PATH(),\n                                            AVS_COAP_FORMAT_SENML_JSON, NULL);\n    AVS_UNIT_ASSERT_NOT_NULL(out_ctx);\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_batch_data_output(anjay_unlocked, batch, 1, out_ctx));\n    ANJAY_MUTEX_UNLOCK(anjay);\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_output_ctx_destroy(&out_ctx));\n    char expected[200];\n    int expected_size =\n            avs_simple_snprintf(expected, sizeof(expected),\n                                \"[{\\\"n\\\":\\\"/%\" PRIu16 \"/%\" PRIu16 \"/%\" PRIu16\n                                \"\\\",\\\"bt\\\":%.17g,\\\"v\\\":%\" PRIi64 \"}]\",\n                                TEST_OID, 0, INT_RID,\n                                MOCK_CLOCK_START_ABSOLUTE - 123.,\n                                (int64_t) INT_VALUE);\n    AVS_UNIT_ASSERT_EQUAL(avs_stream_outbuf_offset(&stream), expected_size);\n    AVS_UNIT_ASSERT_EQUAL_STRING(buffer, expected);\n\n    _anjay_batch_release(&batch);\n    AVS_UNIT_ASSERT_NULL(batch);\n\n    TEST_TEARDOWN();\n}\n\nAVS_UNIT_TEST(dm_batch, serialize_two_resources) {\n    TEST_SETUP(MOCK_CLOCK_START_RELATIVE);\n\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_batch_add_int(\n            builder, &MAKE_RESOURCE_PATH(TEST_OID, 0, INT_RID),\n            AVS_TIME_REAL_INVALID, INT_VALUE));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_batch_add_string(\n            builder, &MAKE_RESOURCE_PATH(TEST_OID, 0, STRING_RID),\n            AVS_TIME_REAL_INVALID, STRING_VALUE));\n    anjay_batch_t *batch = _anjay_batch_builder_compile(&builder);\n    AVS_UNIT_ASSERT_NULL(builder);\n    AVS_UNIT_ASSERT_NOT_NULL(batch);\n\n    char buffer[200] = { 0 };\n    avs_stream_outbuf_t stream = AVS_STREAM_OUTBUF_STATIC_INITIALIZER;\n    avs_stream_outbuf_set_buffer(&stream, buffer, sizeof(buffer));\n    anjay_unlocked_output_ctx_t *out_ctx =\n            _anjay_output_senml_like_create((avs_stream_t *) &stream,\n                                            &MAKE_ROOT_PATH(),\n                                            AVS_COAP_FORMAT_SENML_JSON, NULL);\n    AVS_UNIT_ASSERT_NOT_NULL(out_ctx);\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_batch_data_output(anjay_unlocked, batch, 1, out_ctx));\n    ANJAY_MUTEX_UNLOCK(anjay);\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_output_ctx_destroy(&out_ctx));\n    char expected[200];\n    int expected_size =\n            avs_simple_snprintf(expected, sizeof(expected),\n                                \"[{\\\"n\\\":\\\"/%\" PRIu16 \"/%\" PRIu16 \"/%\" PRIu16\n                                \"\\\",\\\"v\\\":%\" PRIi64 \"},{\\\"n\\\":\\\"/%\" PRIu16\n                                \"/%\" PRIu16 \"/%\" PRIu16 \"\\\",\\\"vs\\\":\\\"%s\\\"}]\",\n                                TEST_OID, 0, INT_RID, (int64_t) INT_VALUE,\n                                TEST_OID, 0, STRING_RID, STRING_VALUE);\n    AVS_UNIT_ASSERT_EQUAL(avs_stream_outbuf_offset(&stream), expected_size);\n    AVS_UNIT_ASSERT_EQUAL_STRING(buffer, expected);\n\n    _anjay_batch_release(&batch);\n    AVS_UNIT_ASSERT_NULL(batch);\n\n    TEST_TEARDOWN();\n}\n\nAVS_UNIT_TEST(dm_batch, serialize_two_resources_with_relative_timestamp) {\n    TEST_SETUP(MOCK_CLOCK_START_RELATIVE);\n\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_batch_add_int(\n            builder, &MAKE_RESOURCE_PATH(TEST_OID, 0, INT_RID),\n            avs_time_real_from_scalar(1, AVS_TIME_MIN), INT_VALUE));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_batch_add_string(\n            builder, &MAKE_RESOURCE_PATH(TEST_OID, 0, STRING_RID),\n            avs_time_real_from_scalar(2, AVS_TIME_MIN), STRING_VALUE));\n    anjay_batch_t *batch = _anjay_batch_builder_compile(&builder);\n    AVS_UNIT_ASSERT_NULL(builder);\n    AVS_UNIT_ASSERT_NOT_NULL(batch);\n\n    // Because _anjay_batch_builder_compile() calls avs_time_real_now() it\n    // advances MOCK_CLOCK by 1 nanosecond and we want to disable this effect\n    // here\n    _anjay_mock_clock_reset(avs_time_monotonic_from_scalar(\n            MOCK_CLOCK_START_RELATIVE, AVS_TIME_S));\n\n    char buffer[200] = { 0 };\n    avs_stream_outbuf_t stream = AVS_STREAM_OUTBUF_STATIC_INITIALIZER;\n    avs_stream_outbuf_set_buffer(&stream, buffer, sizeof(buffer));\n    anjay_unlocked_output_ctx_t *out_ctx =\n            _anjay_output_senml_like_create((avs_stream_t *) &stream,\n                                            &MAKE_ROOT_PATH(),\n                                            AVS_COAP_FORMAT_SENML_JSON, NULL);\n    AVS_UNIT_ASSERT_NOT_NULL(out_ctx);\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_batch_data_output(anjay_unlocked, batch, 1, out_ctx));\n    ANJAY_MUTEX_UNLOCK(anjay);\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_output_ctx_destroy(&out_ctx));\n    char expected[200];\n    int expected_size = avs_simple_snprintf(\n            expected, sizeof(expected),\n            \"[{\\\"n\\\":\\\"/%\" PRIu16 \"/%\" PRIu16 \"/%\" PRIu16\n            \"\\\",\\\"bt\\\":%.17g,\\\"v\\\":%\" PRIi64 \"},{\\\"n\\\":\\\"/%\" PRIu16 \"/%\" PRIu16\n            \"/%\" PRIu16 \"\\\",\\\"bt\\\":%.17g,\\\"vs\\\":\\\"%s\\\"}]\",\n            TEST_OID, 0, INT_RID, 60. - MOCK_CLOCK_START_RELATIVE,\n            (int64_t) INT_VALUE, TEST_OID, 0, STRING_RID,\n            120. - MOCK_CLOCK_START_RELATIVE, STRING_VALUE);\n    AVS_UNIT_ASSERT_EQUAL(avs_stream_outbuf_offset(&stream), expected_size);\n    AVS_UNIT_ASSERT_EQUAL_STRING(buffer, expected);\n\n    _anjay_batch_release(&batch);\n    AVS_UNIT_ASSERT_NULL(batch);\n\n    TEST_TEARDOWN();\n}\n\nAVS_UNIT_TEST(dm_batch, serialize_resource_instance) {\n    TEST_SETUP(MOCK_CLOCK_START_RELATIVE);\n\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_batch_add_int(\n            builder,\n            &MAKE_RESOURCE_INSTANCE_PATH(TEST_OID, 0, INT_ARRAY_RID, 0),\n            AVS_TIME_REAL_INVALID, int_array[0]));\n    anjay_batch_t *batch = _anjay_batch_builder_compile(&builder);\n    AVS_UNIT_ASSERT_NULL(builder);\n    AVS_UNIT_ASSERT_NOT_NULL(batch);\n\n    char buffer[200] = { 0 };\n    avs_stream_outbuf_t stream = AVS_STREAM_OUTBUF_STATIC_INITIALIZER;\n    avs_stream_outbuf_set_buffer(&stream, buffer, sizeof(buffer));\n    anjay_unlocked_output_ctx_t *out_ctx =\n            _anjay_output_senml_like_create((avs_stream_t *) &stream,\n                                            &MAKE_ROOT_PATH(),\n                                            AVS_COAP_FORMAT_SENML_JSON, NULL);\n    AVS_UNIT_ASSERT_NOT_NULL(out_ctx);\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_batch_data_output(anjay_unlocked, batch, 1, out_ctx));\n    ANJAY_MUTEX_UNLOCK(anjay);\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_output_ctx_destroy(&out_ctx));\n    char expected[200];\n    int expected_size =\n            avs_simple_snprintf(expected, sizeof(expected),\n                                \"[{\\\"n\\\":\\\"/%\" PRIu16 \"/%\" PRIu16 \"/%\" PRIu16\n                                \"/%\" PRIu16 \"\\\",\\\"v\\\":%\" PRIi64 \"}]\",\n                                TEST_OID, 0, INT_ARRAY_RID, 0,\n                                (int64_t) int_array[0]);\n    AVS_UNIT_ASSERT_EQUAL(avs_stream_outbuf_offset(&stream), expected_size);\n    AVS_UNIT_ASSERT_EQUAL_STRING(buffer, expected);\n\n    _anjay_batch_release(&batch);\n    AVS_UNIT_ASSERT_NULL(batch);\n\n    TEST_TEARDOWN();\n}\n\nAVS_UNIT_TEST(dm_batch, absolute_timestamp_higher_than_serialization_time) {\n    TEST_SETUP(MOCK_CLOCK_START_ABSOLUTE);\n\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_batch_add_int(\n            builder, &MAKE_RESOURCE_PATH(TEST_OID, 0, INT_RID),\n            avs_time_real_from_scalar(MOCK_CLOCK_START_ABSOLUTE + 123,\n                                      AVS_TIME_S),\n            INT_VALUE));\n    anjay_batch_t *batch = _anjay_batch_builder_compile(&builder);\n    AVS_UNIT_ASSERT_NULL(builder);\n    AVS_UNIT_ASSERT_NOT_NULL(batch);\n\n    char buffer[200] = { 0 };\n    avs_stream_outbuf_t stream = AVS_STREAM_OUTBUF_STATIC_INITIALIZER;\n    avs_stream_outbuf_set_buffer(&stream, buffer, sizeof(buffer));\n    anjay_unlocked_output_ctx_t *out_ctx =\n            _anjay_output_senml_like_create((avs_stream_t *) &stream,\n                                            &MAKE_ROOT_PATH(),\n                                            AVS_COAP_FORMAT_SENML_JSON, NULL);\n    AVS_UNIT_ASSERT_NOT_NULL(out_ctx);\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_batch_data_output(anjay_unlocked, batch, 1, out_ctx));\n    ANJAY_MUTEX_UNLOCK(anjay);\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_output_ctx_destroy(&out_ctx));\n    char expected[200];\n    int expected_size =\n            avs_simple_snprintf(expected, sizeof(expected),\n                                \"[{\\\"n\\\":\\\"/%\" PRIu16 \"/%\" PRIu16 \"/%\" PRIu16\n                                \"\\\",\\\"v\\\":%\" PRIi64 \"}]\",\n                                TEST_OID, 0, INT_RID,\n                                // timestamp is omitted\n                                (int64_t) INT_VALUE);\n    AVS_UNIT_ASSERT_EQUAL(avs_stream_outbuf_offset(&stream), expected_size);\n    AVS_UNIT_ASSERT_EQUAL_STRING(buffer, expected);\n\n    _anjay_batch_release(&batch);\n    AVS_UNIT_ASSERT_NULL(batch);\n\n    TEST_TEARDOWN();\n}\n\nAVS_UNIT_TEST(dm_batch, relative_timestamp_higher_than_serialization_time) {\n    TEST_SETUP(MOCK_CLOCK_START_RELATIVE);\n\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_batch_add_int(\n            builder, &MAKE_RESOURCE_PATH(TEST_OID, 0, INT_RID),\n            avs_time_real_from_scalar(MOCK_CLOCK_START_RELATIVE + 123,\n                                      AVS_TIME_S),\n            INT_VALUE));\n    anjay_batch_t *batch = _anjay_batch_builder_compile(&builder);\n    AVS_UNIT_ASSERT_NULL(builder);\n    AVS_UNIT_ASSERT_NOT_NULL(batch);\n\n    char buffer[200] = { 0 };\n    avs_stream_outbuf_t stream = AVS_STREAM_OUTBUF_STATIC_INITIALIZER;\n    avs_stream_outbuf_set_buffer(&stream, buffer, sizeof(buffer));\n    anjay_unlocked_output_ctx_t *out_ctx =\n            _anjay_output_senml_like_create((avs_stream_t *) &stream,\n                                            &MAKE_ROOT_PATH(),\n                                            AVS_COAP_FORMAT_SENML_JSON, NULL);\n    AVS_UNIT_ASSERT_NOT_NULL(out_ctx);\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_batch_data_output(anjay_unlocked, batch, 1, out_ctx));\n    ANJAY_MUTEX_UNLOCK(anjay);\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_output_ctx_destroy(&out_ctx));\n    char expected[200];\n    int expected_size =\n            avs_simple_snprintf(expected, sizeof(expected),\n                                \"[{\\\"n\\\":\\\"/%\" PRIu16 \"/%\" PRIu16 \"/%\" PRIu16\n                                \"\\\",\\\"v\\\":%\" PRIi64 \"}]\",\n                                TEST_OID, 0, INT_RID,\n                                // timestamp is omitted\n                                (int64_t) INT_VALUE);\n    AVS_UNIT_ASSERT_EQUAL(avs_stream_outbuf_offset(&stream), expected_size);\n    AVS_UNIT_ASSERT_EQUAL_STRING(buffer, expected);\n\n    _anjay_batch_release(&batch);\n    AVS_UNIT_ASSERT_NULL(batch);\n\n    TEST_TEARDOWN();\n}\n\nAVS_UNIT_TEST(dm_batch, relative_timestamp_absolute_serialization_time) {\n    TEST_SETUP(MOCK_CLOCK_START_ABSOLUTE);\n\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_batch_add_int(\n            builder, &MAKE_RESOURCE_PATH(TEST_OID, 0, INT_RID),\n            avs_time_real_from_scalar(MOCK_CLOCK_START_RELATIVE, AVS_TIME_S),\n            INT_VALUE));\n    anjay_batch_t *batch = _anjay_batch_builder_compile(&builder);\n    AVS_UNIT_ASSERT_NULL(builder);\n    AVS_UNIT_ASSERT_NOT_NULL(batch);\n\n    char buffer[200] = { 0 };\n    avs_stream_outbuf_t stream = AVS_STREAM_OUTBUF_STATIC_INITIALIZER;\n    avs_stream_outbuf_set_buffer(&stream, buffer, sizeof(buffer));\n    anjay_unlocked_output_ctx_t *out_ctx =\n            _anjay_output_senml_like_create((avs_stream_t *) &stream,\n                                            &MAKE_ROOT_PATH(),\n                                            AVS_COAP_FORMAT_SENML_JSON, NULL);\n    AVS_UNIT_ASSERT_NOT_NULL(out_ctx);\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_batch_data_output(anjay_unlocked, batch, 1, out_ctx));\n    ANJAY_MUTEX_UNLOCK(anjay);\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_output_ctx_destroy(&out_ctx));\n    char expected[200];\n    int expected_size =\n            avs_simple_snprintf(expected, sizeof(expected),\n                                \"[{\\\"n\\\":\\\"/%\" PRIu16 \"/%\" PRIu16 \"/%\" PRIu16\n                                \"\\\",\\\"v\\\":%\" PRIi64 \"}]\",\n                                TEST_OID, 0, INT_RID,\n                                // timestamp is omitted\n                                (int64_t) INT_VALUE);\n    AVS_UNIT_ASSERT_EQUAL(avs_stream_outbuf_offset(&stream), expected_size);\n    AVS_UNIT_ASSERT_EQUAL_STRING(buffer, expected);\n\n    _anjay_batch_release(&batch);\n    AVS_UNIT_ASSERT_NULL(batch);\n\n    TEST_TEARDOWN();\n}\n\nAVS_UNIT_TEST(dm_batch, negative_timestamp) {\n    TEST_SETUP(MOCK_CLOCK_START_RELATIVE);\n\n    const int negative_timestamp = -MOCK_CLOCK_START_RELATIVE;\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_batch_add_int(\n            builder, &MAKE_RESOURCE_PATH(TEST_OID, 0, INT_RID),\n            avs_time_real_from_scalar(negative_timestamp, AVS_TIME_S),\n            INT_VALUE));\n    anjay_batch_t *batch = _anjay_batch_builder_compile(&builder);\n    AVS_UNIT_ASSERT_NULL(builder);\n    AVS_UNIT_ASSERT_NOT_NULL(batch);\n\n    // Because _anjay_batch_builder_compile() calls avs_time_real_now() it\n    // advances MOCK_CLOCK by 1 nanosecond and we want to disable this effect\n    // here\n    _anjay_mock_clock_reset(avs_time_monotonic_from_scalar(\n            MOCK_CLOCK_START_RELATIVE, AVS_TIME_S));\n\n    char buffer[200] = { 0 };\n    avs_stream_outbuf_t stream = AVS_STREAM_OUTBUF_STATIC_INITIALIZER;\n    avs_stream_outbuf_set_buffer(&stream, buffer, sizeof(buffer));\n    anjay_unlocked_output_ctx_t *out_ctx =\n            _anjay_output_senml_like_create((avs_stream_t *) &stream,\n                                            &MAKE_ROOT_PATH(),\n                                            AVS_COAP_FORMAT_SENML_JSON, NULL);\n    AVS_UNIT_ASSERT_NOT_NULL(out_ctx);\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_batch_data_output(anjay_unlocked, batch, 1, out_ctx));\n    ANJAY_MUTEX_UNLOCK(anjay);\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_output_ctx_destroy(&out_ctx));\n    char expected[200];\n    int expected_size =\n            avs_simple_snprintf(expected, sizeof(expected),\n                                \"[{\\\"n\\\":\\\"/%\" PRIu16 \"/%\" PRIu16 \"/%\" PRIu16\n                                \"\\\",\\\"bt\\\":%d,\\\"v\\\":%\" PRIi64 \"}]\",\n                                TEST_OID, 0, INT_RID,\n                                negative_timestamp - MOCK_CLOCK_START_RELATIVE,\n                                (int64_t) INT_VALUE);\n    AVS_UNIT_ASSERT_EQUAL(avs_stream_outbuf_offset(&stream), expected_size);\n    AVS_UNIT_ASSERT_EQUAL_STRING(buffer, expected);\n\n    _anjay_batch_release(&batch);\n    AVS_UNIT_ASSERT_NULL(batch);\n\n    TEST_TEARDOWN();\n}\n\n#ifdef ANJAY_WITH_LWM2M_GATEWAY\n\nstatic const anjay_dm_object_def_t OBJECT_DEF_GATEWAY_1 = {\n    .oid = TEST_OID,\n    .handlers = {\n        .list_instances = anjay_dm_list_instances_SINGLE,\n        .list_resources = test_list_resources,\n        .resource_read = test_resource_read,\n        .list_resource_instances = test_list_resource_instances\n    }\n};\n\nstatic const anjay_dm_object_def_t OBJECT_DEF_GATEWAY_2 = {\n    .oid = TEST_OID,\n    .handlers = {\n        .list_instances = anjay_dm_list_instances_SINGLE,\n        .list_resources = test_list_resources,\n        .resource_read = test_resource_read,\n        .list_resource_instances = test_list_resource_instances\n    }\n};\n\n#    define TEST_END_DEVICE_IID_1 0\n#    define TEST_END_DEVICE_IID_2 1\n\n#    define TEST_SETUP_GATEWAY(TimeStart)                                    \\\n        static const anjay_configuration_t CONFIG = {                        \\\n            .endpoint_name = \"test\"                                          \\\n        };                                                                   \\\n                                                                             \\\n        anjay_t *anjay = anjay_new(&CONFIG);                                 \\\n        AVS_UNIT_ASSERT_NOT_NULL(anjay);                                     \\\n        const anjay_dm_object_def_t *test_object_def_ptr_1 =                 \\\n                &OBJECT_DEF_GATEWAY_1;                                       \\\n        const anjay_dm_object_def_t *test_object_def_ptr_2 =                 \\\n                &OBJECT_DEF_GATEWAY_2;                                       \\\n        const anjay_dm_object_def_t *test_object_def_ptr_gateway =           \\\n                &OBJECT_DEF;                                                 \\\n        AVS_UNIT_ASSERT_SUCCESS(anjay_lwm2m_gateway_install(anjay));         \\\n        anjay_iid_t iid = ANJAY_ID_INVALID;                                  \\\n        AVS_UNIT_ASSERT_SUCCESS(anjay_lwm2m_gateway_register_device(         \\\n                anjay, \"device_id_1\", &iid));                                \\\n        AVS_UNIT_ASSERT_EQUAL(iid, TEST_END_DEVICE_IID_1);                   \\\n        AVS_UNIT_ASSERT_SUCCESS(anjay_lwm2m_gateway_register_object(         \\\n                anjay, iid, &test_object_def_ptr_1));                        \\\n        iid = ANJAY_ID_INVALID;                                              \\\n        AVS_UNIT_ASSERT_SUCCESS(anjay_lwm2m_gateway_register_device(         \\\n                anjay, \"device_id_2\", &iid));                                \\\n        AVS_UNIT_ASSERT_EQUAL(iid, TEST_END_DEVICE_IID_2);                   \\\n        AVS_UNIT_ASSERT_SUCCESS(anjay_lwm2m_gateway_register_object(         \\\n                anjay, iid, &test_object_def_ptr_2));                        \\\n        AVS_UNIT_ASSERT_SUCCESS(                                             \\\n                anjay_register_object(anjay, &test_object_def_ptr_gateway)); \\\n        anjay_batch_builder_t *builder = _anjay_batch_builder_new();         \\\n        AVS_UNIT_ASSERT_NOT_NULL(builder);                                   \\\n                                                                             \\\n        _anjay_mock_clock_start(                                             \\\n                avs_time_monotonic_from_scalar((TimeStart), AVS_TIME_S));\n\n#    define BATCH_COMPILE_BUILD_MSG()                                         \\\n        anjay_batch_t *batch = _anjay_batch_builder_compile(&builder);        \\\n        AVS_UNIT_ASSERT_NULL(builder);                                        \\\n        AVS_UNIT_ASSERT_NOT_NULL(batch);                                      \\\n                                                                              \\\n        char buffer[200] = { 0 };                                             \\\n        avs_stream_outbuf_t stream = AVS_STREAM_OUTBUF_STATIC_INITIALIZER;    \\\n        avs_stream_outbuf_set_buffer(&stream, buffer, sizeof(buffer));        \\\n        anjay_unlocked_output_ctx_t *out_ctx =                                \\\n                _anjay_output_senml_like_create((avs_stream_t *) &stream,     \\\n                                                &MAKE_ROOT_PATH(),            \\\n                                                AVS_COAP_FORMAT_SENML_JSON,   \\\n                                                NULL);                        \\\n        AVS_UNIT_ASSERT_NOT_NULL(out_ctx);                                    \\\n        ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);                              \\\n        AVS_UNIT_ASSERT_SUCCESS(                                              \\\n                _anjay_batch_data_output(anjay_unlocked, batch, 1, out_ctx)); \\\n        ANJAY_MUTEX_UNLOCK(anjay);                                            \\\n        AVS_UNIT_ASSERT_SUCCESS(_anjay_output_ctx_destroy(&out_ctx));\n\nAVS_UNIT_TEST(dm_batch, serialize_one_end_device_resource) {\n    TEST_SETUP_GATEWAY(MOCK_CLOCK_START_RELATIVE);\n\n    anjay_uri_path_t path;\n    _anjay_uri_path_with_prefix_from_end_device_iid(\n            &path, TEST_END_DEVICE_IID_1, TEST_OID, 0, INT_RID, UINT16_MAX);\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_batch_add_int(\n            builder, &path, AVS_TIME_REAL_INVALID, INT_VALUE));\n\n    BATCH_COMPILE_BUILD_MSG();\n\n    char expected[200];\n    int expected_size =\n            avs_simple_snprintf(expected, sizeof(expected),\n                                \"[{\\\"n\\\":\\\"/%s/%\" PRIu16 \"/%\" PRIu16 \"/%\" PRIu16\n                                \"\\\",\\\"v\\\":%\" PRIi64 \"}]\",\n                                path.prefix, TEST_OID, 0, INT_RID,\n                                (int64_t) INT_VALUE);\n    AVS_UNIT_ASSERT_EQUAL(avs_stream_outbuf_offset(&stream), expected_size);\n    AVS_UNIT_ASSERT_EQUAL_STRING(buffer, expected);\n\n    _anjay_batch_release(&batch);\n    AVS_UNIT_ASSERT_NULL(batch);\n\n    TEST_TEARDOWN();\n}\n\nAVS_UNIT_TEST(dm_batch, serialize_two_resources_different_end_devices) {\n    TEST_SETUP_GATEWAY(MOCK_CLOCK_START_RELATIVE);\n\n    anjay_uri_path_t path;\n    _anjay_uri_path_with_prefix_from_end_device_iid(\n            &path, TEST_END_DEVICE_IID_1, TEST_OID, 0, INT_RID, UINT16_MAX);\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_batch_add_int(\n            builder, &path, AVS_TIME_REAL_INVALID, INT_VALUE));\n    _anjay_uri_path_with_prefix_from_end_device_iid(\n            &path, TEST_END_DEVICE_IID_1, TEST_OID, 0, UINT_RID, UINT16_MAX);\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_batch_add_uint(\n            builder, &path, AVS_TIME_REAL_INVALID, UINT_VALUE));\n\n    BATCH_COMPILE_BUILD_MSG();\n\n    char expected[200];\n    int expected_size = avs_simple_snprintf(\n            expected, sizeof(expected),\n            \"[{\\\"n\\\":\\\"/%s/%\" PRIu16 \"/%\" PRIu16 \"/%\" PRIu16 \"\\\",\\\"v\\\":%\" PRIi64\n            \"},{\\\"n\\\":\\\"/%s/%\" PRIu16 \"/%\" PRIu16 \"/%\" PRIu16\n            \"\\\",\\\"v\\\":%\" PRIu64 \"}]\",\n            path.prefix, TEST_OID, 0, INT_RID, (int64_t) INT_VALUE, path.prefix,\n            TEST_OID, 0, UINT_RID, (uint64_t) UINT_VALUE);\n    AVS_UNIT_ASSERT_EQUAL(avs_stream_outbuf_offset(&stream), expected_size);\n    AVS_UNIT_ASSERT_EQUAL_STRING(buffer, expected);\n\n    _anjay_batch_release(&batch);\n    AVS_UNIT_ASSERT_NULL(batch);\n\n    TEST_TEARDOWN();\n}\n\nAVS_UNIT_TEST(dm_batch, serialize_two_end_device_resources) {\n    TEST_SETUP_GATEWAY(MOCK_CLOCK_START_RELATIVE);\n\n    anjay_uri_path_t path_1, path_2;\n    _anjay_uri_path_with_prefix_from_end_device_iid(\n            &path_1, TEST_END_DEVICE_IID_1, TEST_OID, 0, INT_RID, UINT16_MAX);\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_batch_add_int(\n            builder, &path_1, AVS_TIME_REAL_INVALID, INT_VALUE));\n    _anjay_uri_path_with_prefix_from_end_device_iid(\n            &path_2, TEST_END_DEVICE_IID_2, TEST_OID, 0, UINT_RID, UINT16_MAX);\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_batch_add_uint(\n            builder, &path_2, AVS_TIME_REAL_INVALID, UINT_VALUE));\n\n    BATCH_COMPILE_BUILD_MSG();\n\n    char expected[200];\n    int expected_size = avs_simple_snprintf(\n            expected, sizeof(expected),\n            \"[{\\\"n\\\":\\\"/%s/%\" PRIu16 \"/%\" PRIu16 \"/%\" PRIu16 \"\\\",\\\"v\\\":%\" PRIi64\n            \"},{\\\"n\\\":\\\"/%s/%\" PRIu16 \"/%\" PRIu16 \"/%\" PRIu16\n            \"\\\",\\\"v\\\":%\" PRIu64 \"}]\",\n            path_1.prefix, TEST_OID, 0, INT_RID, (int64_t) INT_VALUE,\n            path_2.prefix, TEST_OID, 0, UINT_RID, (uint64_t) UINT_VALUE);\n    AVS_UNIT_ASSERT_EQUAL(avs_stream_outbuf_offset(&stream), expected_size);\n    AVS_UNIT_ASSERT_EQUAL_STRING(buffer, expected);\n\n    _anjay_batch_release(&batch);\n    AVS_UNIT_ASSERT_NULL(batch);\n\n    TEST_TEARDOWN();\n}\n\nAVS_UNIT_TEST(dm_batch,\n              serialize_two_end_device_resource_one_gateway_resource) {\n    TEST_SETUP_GATEWAY(MOCK_CLOCK_START_RELATIVE);\n\n    anjay_uri_path_t path_1, path_2;\n    _anjay_uri_path_with_prefix_from_end_device_iid(\n            &path_1, TEST_END_DEVICE_IID_1, TEST_OID, 0, INT_RID, UINT16_MAX);\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_batch_add_int(\n            builder, &path_1, AVS_TIME_REAL_INVALID, INT_VALUE));\n    _anjay_uri_path_with_prefix_from_end_device_iid(\n            &path_2, TEST_END_DEVICE_IID_2, TEST_OID, 0, UINT_RID, UINT16_MAX);\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_batch_add_uint(\n            builder, &path_2, AVS_TIME_REAL_INVALID, UINT_VALUE));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_batch_add_int(\n            builder, &MAKE_RESOURCE_PATH(TEST_OID, 0, INT_RID),\n            AVS_TIME_REAL_INVALID, INT_VALUE));\n\n    BATCH_COMPILE_BUILD_MSG();\n\n    char expected[200];\n    int expected_size = avs_simple_snprintf(\n            expected, sizeof(expected),\n            \"[{\\\"n\\\":\\\"/%s/%\" PRIu16 \"/%\" PRIu16 \"/%\" PRIu16 \"\\\",\\\"v\\\":%\" PRIi64\n            \"},{\\\"n\\\":\\\"/%s/%\" PRIu16 \"/%\" PRIu16 \"/%\" PRIu16\n            \"\\\",\\\"v\\\":%\" PRIu64 \"},{\\\"n\\\":\\\"/%\" PRIu16 \"/%\" PRIu16 \"/%\" PRIu16\n            \"\\\",\\\"v\\\":%\" PRIu64 \"}]\",\n            path_1.prefix, TEST_OID, 0, INT_RID, (int64_t) INT_VALUE,\n            path_2.prefix, TEST_OID, 0, UINT_RID, (uint64_t) UINT_VALUE,\n            TEST_OID, 0, INT_RID, (int64_t) INT_VALUE);\n    AVS_UNIT_ASSERT_EQUAL(avs_stream_outbuf_offset(&stream), expected_size);\n    AVS_UNIT_ASSERT_EQUAL_STRING(buffer, expected);\n\n    _anjay_batch_release(&batch);\n    AVS_UNIT_ASSERT_NULL(batch);\n\n    TEST_TEARDOWN();\n}\n\n#endif // ANJAY_WITH_LWM2M_GATEWAY\n"
  },
  {
    "path": "tests/core/io/dynamic.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#include <avsystem/commons/avs_stream.h>\n#include <avsystem/commons/avs_stream_membuf.h>\n#include <avsystem/commons/avs_stream_v_table.h>\n\n#include <avsystem/coap/code.h>\n\n#define AVS_UNIT_ENABLE_SHORT_ASSERTS\n#include <avsystem/commons/avs_unit_mocksock.h>\n#include <avsystem/commons/avs_unit_test.h>\n\n#include <anjay/core.h>\n\n#include \"src/core/anjay_core.h\"\n#include \"tests/core/coap/utils.h\"\n#include \"tests/utils/dm.h\"\n\n/////////////////////////////////////////////////////////////////////// ENCODING\n\n#define TEST_ENV(Size, Format, Uri)                                           \\\n    char buf[Size];                                                           \\\n    avs_stream_outbuf_t outbuf = AVS_STREAM_OUTBUF_STATIC_INITIALIZER;        \\\n    avs_stream_outbuf_set_buffer(&outbuf, buf, sizeof(buf));                  \\\n    anjay_unlocked_output_ctx_t *out = NULL;                                  \\\n    ASSERT_OK(_anjay_output_dynamic_construct(&out, (avs_stream_t *) &outbuf, \\\n                                              (Uri), (Format), NULL,          \\\n                                              ANJAY_ACTION_READ));\n\n#define PATH_HIERARCHICAL true\n#define PATH_SIMPLE false\n\n#define VERIFY_BYTES(Data)                                              \\\n    do {                                                                \\\n        ASSERT_EQ(avs_stream_outbuf_offset(&outbuf), sizeof(Data) - 1); \\\n        ASSERT_EQ_BYTES(buf, Data);                                     \\\n    } while (0)\n\nAVS_UNIT_TEST(dynamic_out, bytes) {\n    TEST_ENV(512, AVS_COAP_FORMAT_PLAINTEXT, &MAKE_RESOURCE_PATH(0, 0, 0));\n\n    ASSERT_OK(_anjay_output_set_path(out, &MAKE_RESOURCE_PATH(0, 0, 42)));\n    ASSERT_OK(_anjay_ret_bytes_unlocked(out, \"1234567890\", 10));\n    ASSERT_FAIL(_anjay_ret_bytes_unlocked(out, \"0987654321\", 10));\n    ASSERT_FAIL(_anjay_output_ctx_destroy(&out));\n\n    VERIFY_BYTES(\"MTIzNDU2Nzg5MA==\");\n}\n\nAVS_UNIT_TEST(dynamic_out, string) {\n    TEST_ENV(512, AVS_COAP_FORMAT_PLAINTEXT, &MAKE_RESOURCE_PATH(0, 0, 0));\n\n    ASSERT_OK(_anjay_output_set_path(out, &MAKE_RESOURCE_PATH(0, 0, 42)));\n    ASSERT_OK(_anjay_ret_string_unlocked(out, \"0987654321\"));\n    ASSERT_FAIL(_anjay_ret_string_unlocked(out, \"1234567890\"));\n    ASSERT_FAIL(_anjay_output_ctx_destroy(&out));\n\n    VERIFY_BYTES(\"0987654321\");\n}\n\nAVS_UNIT_TEST(dynamic_out, i64) {\n    TEST_ENV(512, AVS_COAP_FORMAT_PLAINTEXT, &MAKE_RESOURCE_PATH(0, 0, 0));\n\n    ASSERT_OK(_anjay_output_set_path(out, &MAKE_RESOURCE_PATH(0, 0, 42)));\n    ASSERT_OK(_anjay_ret_i64_unlocked(out, 424242424242LL));\n    ASSERT_FAIL(_anjay_ret_i64_unlocked(out, 69));\n    ASSERT_FAIL(_anjay_output_ctx_destroy(&out));\n\n    VERIFY_BYTES(\"424242424242\");\n}\n\n#ifdef ANJAY_WITH_LWM2M11\nAVS_UNIT_TEST(dynamic_out, u64) {\n    TEST_ENV(512, AVS_COAP_FORMAT_PLAINTEXT, &MAKE_RESOURCE_PATH(0, 0, 0));\n\n    ASSERT_OK(_anjay_output_set_path(out, &MAKE_RESOURCE_PATH(0, 0, 42)));\n    ASSERT_OK(_anjay_ret_u64_unlocked(out, UINT64_MAX));\n    ASSERT_FAIL(_anjay_ret_u64_unlocked(out, 1));\n    ASSERT_FAIL(_anjay_output_ctx_destroy(&out));\n\n    VERIFY_BYTES(\"18446744073709551615\");\n}\n#endif // ANJAY_WITH_LWM2M11\n\nAVS_UNIT_TEST(dynamic_out, f64) {\n    TEST_ENV(512, AVS_COAP_FORMAT_PLAINTEXT, &MAKE_RESOURCE_PATH(0, 0, 0));\n\n    ASSERT_OK(_anjay_output_set_path(out, &MAKE_RESOURCE_PATH(0, 0, 42)));\n    ASSERT_OK(_anjay_ret_double_unlocked(out, 4053.125267029));\n    ASSERT_FAIL(_anjay_ret_double_unlocked(out, 3.14));\n    ASSERT_FAIL(_anjay_output_ctx_destroy(&out));\n\n    VERIFY_BYTES(\"4053.125267029\");\n}\n\nAVS_UNIT_TEST(dynamic_out, boolean) {\n    TEST_ENV(512, AVS_COAP_FORMAT_PLAINTEXT, &MAKE_RESOURCE_PATH(0, 0, 0));\n\n    ASSERT_OK(_anjay_output_set_path(out, &MAKE_RESOURCE_PATH(0, 0, 42)));\n    ASSERT_OK(_anjay_ret_bool_unlocked(out, false));\n    ASSERT_FAIL(_anjay_ret_bool_unlocked(out, true));\n    ASSERT_FAIL(_anjay_output_ctx_destroy(&out));\n\n    VERIFY_BYTES(\"0\");\n}\n\nAVS_UNIT_TEST(dynamic_out, objlnk) {\n    TEST_ENV(512, AVS_COAP_FORMAT_PLAINTEXT, &MAKE_RESOURCE_PATH(0, 0, 0));\n\n    ASSERT_OK(_anjay_output_set_path(out, &MAKE_RESOURCE_PATH(0, 0, 42)));\n    ASSERT_OK(_anjay_ret_objlnk_unlocked(out, 514, 69));\n    ASSERT_FAIL(_anjay_ret_objlnk_unlocked(out, 66, 77));\n    ASSERT_FAIL(_anjay_output_ctx_destroy(&out));\n\n    VERIFY_BYTES(\"514:69\");\n}\n\nAVS_UNIT_TEST(dynamic_out, array_from_instance) {\n    TEST_ENV(512, AVS_COAP_FORMAT_OMA_LWM2M_TLV, &MAKE_INSTANCE_PATH(0, 0));\n\n    ASSERT_OK(_anjay_output_set_path(out, &MAKE_RESOURCE_INSTANCE_PATH(0, 0, 42,\n                                                                       5)));\n    ASSERT_OK(_anjay_ret_i64_unlocked(out, 42));\n    ASSERT_OK(_anjay_output_set_path(out, &MAKE_RESOURCE_INSTANCE_PATH(0, 0, 42,\n                                                                       69)));\n    ASSERT_OK(_anjay_ret_string_unlocked(out, \"Hello, world!\"));\n    ASSERT_OK(_anjay_output_ctx_destroy(&out));\n\n    VERIFY_BYTES(\"\\x88\\x2A\\x13\" // array\n                 \"\\x41\\x05\\x2A\" // first entry\n                 \"\\x48\\x45\\x0D\"\n                 \"Hello, world!\" // second entry\n    );\n}\n\nAVS_UNIT_TEST(dynamic_out, array_from_resource) {\n    TEST_ENV(512, AVS_COAP_FORMAT_OMA_LWM2M_TLV, &MAKE_RESOURCE_PATH(0, 0, 42));\n\n    ASSERT_OK(_anjay_output_set_path(out, &MAKE_RESOURCE_INSTANCE_PATH(0, 0, 42,\n                                                                       5)));\n    ASSERT_OK(_anjay_ret_i64_unlocked(out, 42));\n    ASSERT_OK(_anjay_output_set_path(out, &MAKE_RESOURCE_INSTANCE_PATH(0, 0, 42,\n                                                                       69)));\n    ASSERT_OK(_anjay_ret_string_unlocked(out, \"Hello, world!\"));\n    ASSERT_OK(_anjay_output_ctx_destroy(&out));\n\n    VERIFY_BYTES(\"\\x88\\x2A\\x13\" // array\n                 \"\\x41\\x05\\x2A\" // first entry\n                 \"\\x48\\x45\\x0D\"\n                 \"Hello, world!\" // second entry\n    );\n}\n\nAVS_UNIT_TEST(dynamic_out, object) {\n    TEST_ENV(512, AVS_COAP_FORMAT_OMA_LWM2M_TLV, &MAKE_OBJECT_PATH(0));\n\n    ASSERT_OK(_anjay_output_set_path(out, &MAKE_RESOURCE_PATH(0, 42, 69)));\n    ASSERT_OK(_anjay_ret_i64_unlocked(out, 514));\n    ASSERT_OK(_anjay_output_ctx_destroy(&out));\n\n    VERIFY_BYTES(\"\\x04\\x2A\"         // object\n                 \"\\xC2\\x45\\x02\\x02\" // entry\n    );\n}\n\nAVS_UNIT_TEST(dynamic_out, method_not_implemented) {\n    TEST_ENV(512, AVS_COAP_FORMAT_PLAINTEXT, &MAKE_RESOURCE_PATH(0, 0, 42));\n\n    ASSERT_OK(_anjay_output_set_path(out, &MAKE_RESOURCE_PATH(0, 0, 42)));\n    ASSERT_OK(_anjay_ret_i64_unlocked(out, 514));\n    ASSERT_EQ(_anjay_ret_i64_unlocked(out, 69), -1);\n    ASSERT_EQ(_anjay_output_start_aggregate(out),\n              ANJAY_OUTCTXERR_METHOD_NOT_IMPLEMENTED);\n    ASSERT_EQ(_anjay_output_ctx_destroy(&out), -1);\n\n    VERIFY_BYTES(\"514\");\n}\n\nAVS_UNIT_TEST(dynamic_out, format_mismatch) {\n    TEST_ENV(512, AVS_COAP_FORMAT_OCTET_STREAM, &MAKE_RESOURCE_PATH(0, 0, 0));\n\n    ASSERT_OK(_anjay_output_set_path(out, &MAKE_RESOURCE_PATH(0, 0, 42)));\n    ASSERT_FAIL(_anjay_ret_string_unlocked(out, \"data\"));\n    ASSERT_EQ(_anjay_output_ctx_destroy(&out),\n              ANJAY_OUTCTXERR_METHOD_NOT_IMPLEMENTED);\n}\n\n#ifdef ANJAY_WITH_LWM2M11\nAVS_UNIT_TEST(dynamic_out, senml_call_anjay_ret_twice) {\n    TEST_ENV(512, AVS_COAP_FORMAT_SENML_JSON, &MAKE_ROOT_PATH());\n\n    ASSERT_OK(_anjay_output_set_path(out, &MAKE_RESOURCE_PATH(12, 34, 56)));\n    ASSERT_OK(_anjay_ret_i64_unlocked(out, 1337));\n    ASSERT_FAIL(_anjay_ret_i64_unlocked(out, 71830));\n    ASSERT_EQ(_anjay_output_ctx_destroy(&out), -1);\n\n    VERIFY_BYTES(\"[{\\\"n\\\":\\\"/12/34/56\\\",\\\"v\\\":1337}]\");\n}\n\nAVS_UNIT_TEST(dynamic_out, senml_call_set_path_twice) {\n    TEST_ENV(512, AVS_COAP_FORMAT_SENML_JSON, &MAKE_ROOT_PATH());\n\n    ASSERT_OK(_anjay_output_set_path(out, &MAKE_RESOURCE_PATH(12, 34, 56)));\n    ASSERT_FAIL(_anjay_output_set_path(out, &MAKE_RESOURCE_PATH(65, 43, 21)));\n    ASSERT_OK(_anjay_ret_i64_unlocked(out, 1337));\n    ASSERT_EQ(_anjay_output_ctx_destroy(&out), -1);\n\n    VERIFY_BYTES(\"[{\\\"n\\\":\\\"/12/34/56\\\",\\\"v\\\":1337}]\");\n}\n#endif // ANJAY_WITH_LWM2M11\n\n#undef VERIFY_BYTES\n#undef TEST_ENV\n\n/////////////////////////////////////////////////////////////////////// DECODING\n\ntypedef struct {\n    anjay_request_t request;\n    anjay_unlocked_input_ctx_t *input;\n} dynamic_test_env_t;\n\ntypedef struct {\n    const void *payload;\n    size_t size;\n} payload_view_t;\n\n#define PAYLOAD_BYTES(Payload)  \\\n    (payload_view_t) {          \\\n        .payload = (Payload),   \\\n        .size = sizeof(Payload) \\\n    }\n\n#define PAYLOAD_STRING(Payload)     \\\n    (payload_view_t) {              \\\n        .payload = (Payload),       \\\n        .size = sizeof(Payload) - 1 \\\n    }\n\ntypedef struct {\n    uint16_t content_format;\n    payload_view_t payload_view;\n    anjay_request_action_t action;\n    anjay_uri_path_t uri;\n    int expected_error;\n} dynamic_test_def_t;\n\nstatic dynamic_test_env_t dynamic_test_env(const dynamic_test_def_t def) {\n    dynamic_test_env_t env;\n    memset(&env, 0, sizeof(env));\n\n    avs_stream_t *payload_stream = avs_stream_membuf_create();\n    ASSERT_NOT_NULL(payload_stream);\n    ASSERT_OK(avs_stream_write(payload_stream, def.payload_view.payload,\n                               def.payload_view.size));\n\n    env.request.payload_stream = payload_stream;\n    env.request.action = def.action;\n    env.request.content_format = def.content_format;\n    env.request.requested_format = AVS_COAP_FORMAT_NONE;\n    env.request.request_code = AVS_COAP_CODE_POST;\n    env.request.uri = def.uri;\n\n    int result = _anjay_input_dynamic_construct(\n            &env.input, env.request.payload_stream, &env.request);\n    ASSERT_EQ(result, def.expected_error);\n    if (!def.expected_error) {\n        ASSERT_NOT_NULL(env.input);\n    }\n\n    return env;\n}\n\nstatic void dynamic_test_delete(dynamic_test_env_t *env) {\n    avs_stream_cleanup(&env->request.payload_stream);\n    _anjay_input_ctx_destroy(&env->input);\n}\n\nAVS_UNIT_TEST(dynamic_in, plain) {\n    dynamic_test_env_t env __attribute__((cleanup(dynamic_test_delete))) =\n            dynamic_test_env((dynamic_test_def_t) {\n                .content_format = AVS_COAP_FORMAT_PLAINTEXT,\n                .payload_view = PAYLOAD_STRING(\"NDI=\"),\n                .action = ANJAY_ACTION_WRITE\n            });\n\n    size_t bytes_read;\n    bool message_finished;\n    char buf[16];\n    int64_t value;\n    memset(buf, 0, sizeof(buf));\n    ASSERT_OK(_anjay_input_get_path(env.input, NULL, NULL));\n    ASSERT_OK(_anjay_get_bytes_unlocked(env.input, &bytes_read,\n                                        &message_finished, buf, sizeof(buf)));\n    // It fails, because text context is in byte mode.\n    ASSERT_FAIL(_anjay_get_i64_unlocked(env.input, &value));\n    ASSERT_EQ_STR(buf, \"42\");\n}\n\nAVS_UNIT_TEST(dynamic_in, no_content_format) {\n    dynamic_test_env_t env __attribute__((cleanup(dynamic_test_delete))) =\n            dynamic_test_env((dynamic_test_def_t) {\n                .content_format = AVS_COAP_FORMAT_NONE,\n                .payload_view = PAYLOAD_STRING(\"514\"),\n                .action = ANJAY_ACTION_WRITE\n            });\n}\n\nAVS_UNIT_TEST(dynamic_in, tlv) {\n    dynamic_test_env_t env __attribute__((cleanup(dynamic_test_delete))) =\n            dynamic_test_env((dynamic_test_def_t) {\n                .content_format = AVS_COAP_FORMAT_OMA_LWM2M_TLV,\n                .payload_view = PAYLOAD_BYTES(\"\\xC1\\x2A\\x45\"),\n                .action = ANJAY_ACTION_WRITE,\n                .uri = MAKE_RESOURCE_PATH(1, 2, 42),\n                // NOTE: gcc complains if the last structure field is not\n                // explicitlyinitialized\n                .expected_error = 0\n            });\n\n    int64_t value;\n    anjay_uri_path_t path;\n    ASSERT_OK(_anjay_input_get_path(env.input, &path, NULL));\n    ASSERT_TRUE(_anjay_uri_path_equal(&path, &MAKE_RESOURCE_PATH(1, 2, 42)));\n    ASSERT_OK(_anjay_get_i64_unlocked(env.input, &value));\n    ASSERT_EQ(value, 69);\n}\n\nAVS_UNIT_TEST(dynamic_in, opaque) {\n#define HELLO_WORLD \"Hello, world!\"\n    dynamic_test_env_t env __attribute__((cleanup(dynamic_test_delete))) =\n            dynamic_test_env((dynamic_test_def_t) {\n                .content_format = AVS_COAP_FORMAT_OCTET_STREAM,\n                .payload_view = PAYLOAD_STRING(HELLO_WORLD),\n                .action = ANJAY_ACTION_WRITE\n            });\n\n    size_t bytes_read;\n    bool message_finished;\n    char buf[32];\n    ASSERT_OK(_anjay_input_get_path(env.input, NULL, NULL));\n    ASSERT_FAIL(_anjay_get_string_unlocked(env.input, buf, sizeof(buf)));\n    ASSERT_OK(_anjay_get_bytes_unlocked(env.input, &bytes_read,\n                                        &message_finished, buf, sizeof(buf)));\n    ASSERT_TRUE(message_finished);\n    ASSERT_EQ(bytes_read, sizeof(HELLO_WORLD) - 1);\n    ASSERT_EQ_BYTES(buf, HELLO_WORLD);\n\n#undef HELLO_WORLD\n}\n\nAVS_UNIT_TEST(dynamic_in, unrecognized) {\n    dynamic_test_env_t env __attribute__((cleanup(dynamic_test_delete))) =\n            dynamic_test_env((dynamic_test_def_t) {\n                .content_format = 0x6969,\n                .payload_view = PAYLOAD_STRING(\"514\"),\n                .action = ANJAY_ACTION_WRITE,\n                .uri = MAKE_RESOURCE_PATH(1, 2, 42),\n                .expected_error = ANJAY_ERR_UNSUPPORTED_CONTENT_FORMAT\n            });\n}\n\n// clang-format off\n#define LOREM_IPSUM       \"Lorem ipsum dolor sit amet, consectetur cras amet.\"\n#define LOREM_IPSUM_PART1 \"Lorem ipsum dolor si\"\n#define LOREM_IPSUM_PART2                     \"t amet, consectetur \"\n#define LOREM_IPSUM_PART3                                         \"cras amet.\"\n// clang-format on\n#define LOREM_IPSUM_PART1_SIZE (sizeof(LOREM_IPSUM_PART1) - 1)\n#define LOREM_IPSUM_PART2_SIZE (sizeof(LOREM_IPSUM_PART2) - 1)\n#define LOREM_IPSUM_PART3_SIZE (sizeof(LOREM_IPSUM_PART3) - 1)\n#define LOREM_IPSUM_AS_BASE64 \\\n    \"TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNlY3RldHVyIGNyYXMgYW1ldC4\"\n#define LOREM_IPSUM_AS_BASE64_STRICT LOREM_IPSUM_AS_BASE64 \"=\"\n#define LOREM_IPSUM_AS_CBOR_BYTES \"X2\" LOREM_IPSUM\n#define LOREM_IPSUM_AS_CBOR_STRING \"x2\" LOREM_IPSUM\n#define LOREM_IPSUM_AS_TLV \"\\xc8\\x38\\x32\" LOREM_IPSUM\n#define LOREM_IPSUM_AS_SENML_JSON_BYTES \\\n    \"[{\\\"n\\\":\\\"/12/34/56\\\",\\\"vd\\\":\\\"\" LOREM_IPSUM_AS_BASE64 \"\\\"}]\"\n#define LOREM_IPSUM_AS_SENML_JSON_STRING \\\n    \"[{\\\"n\\\":\\\"/12/34/56\\\",\\\"vs\\\":\\\"\" LOREM_IPSUM \"\\\"}]\"\n#define LOREM_IPSUM_AS_SENML_CBOR_BYTES \\\n    \"\\x81\\xa2\\x00\\x69/12/34/56\\x08\\x58\\x32\" LOREM_IPSUM\n#define LOREM_IPSUM_AS_SENML_CBOR_STRING \\\n    \"\\x81\\xa2\\x00\\x69/12/34/56\\x03\\x78\\x32\" LOREM_IPSUM\n\nstatic void test_partial_bytes(uint16_t content_format,\n                               payload_view_t payload_view) {\n    dynamic_test_env_t env __attribute__((cleanup(dynamic_test_delete))) =\n            dynamic_test_env((dynamic_test_def_t) {\n                .content_format = content_format,\n                .payload_view = payload_view,\n                .action = ANJAY_ACTION_WRITE,\n                .uri.ids = { 12, 34, 56, ANJAY_ID_INVALID }\n            });\n\n    anjay_uri_path_t path;\n    ASSERT_OK(_anjay_input_get_path(env.input, &path, NULL));\n    ASSERT_TRUE(_anjay_uri_path_equal(&path, &env.request.uri));\n\n    size_t bytes_read;\n    bool message_finished;\n    char buf[LOREM_IPSUM_PART1_SIZE];\n    ASSERT_OK(_anjay_get_bytes_unlocked(env.input, &bytes_read,\n                                        &message_finished, buf, sizeof(buf)));\n    ASSERT_EQ(bytes_read, LOREM_IPSUM_PART1_SIZE);\n    ASSERT_FALSE(message_finished);\n    ASSERT_EQ_BYTES(buf, LOREM_IPSUM_PART1);\n\n    ASSERT_OK(_anjay_get_bytes_unlocked(env.input, &bytes_read,\n                                        &message_finished, buf, sizeof(buf)));\n    ASSERT_EQ(bytes_read, LOREM_IPSUM_PART2_SIZE);\n    ASSERT_FALSE(message_finished);\n    ASSERT_EQ_BYTES(buf, LOREM_IPSUM_PART2);\n\n    ASSERT_OK(_anjay_get_bytes_unlocked(env.input, &bytes_read,\n                                        &message_finished, buf, sizeof(buf)));\n    ASSERT_EQ(bytes_read, LOREM_IPSUM_PART3_SIZE);\n    ASSERT_TRUE(message_finished);\n    ASSERT_EQ_BYTES(buf, LOREM_IPSUM_PART3);\n}\n\nstatic void test_partial_string(uint16_t content_format,\n                                payload_view_t payload_view) {\n    dynamic_test_env_t env __attribute__((cleanup(dynamic_test_delete))) =\n            dynamic_test_env((dynamic_test_def_t) {\n                .content_format = content_format,\n                .payload_view = payload_view,\n                .action = ANJAY_ACTION_WRITE,\n                .uri.ids = { 12, 34, 56, ANJAY_ID_INVALID }\n            });\n\n    anjay_uri_path_t path;\n    ASSERT_OK(_anjay_input_get_path(env.input, &path, NULL));\n    ASSERT_TRUE(_anjay_uri_path_equal(&path, &env.request.uri));\n\n    char buf[LOREM_IPSUM_PART1_SIZE + 1];\n    ASSERT_EQ(_anjay_get_string_unlocked(env.input, buf, sizeof(buf)),\n              ANJAY_BUFFER_TOO_SHORT);\n    ASSERT_EQ_STR(buf, LOREM_IPSUM_PART1);\n\n    ASSERT_EQ(_anjay_get_string_unlocked(env.input, buf, sizeof(buf)),\n              ANJAY_BUFFER_TOO_SHORT);\n    ASSERT_EQ_STR(buf, LOREM_IPSUM_PART2);\n\n    ASSERT_OK(_anjay_get_string_unlocked(env.input, buf, sizeof(buf)));\n    ASSERT_EQ_STR(buf, LOREM_IPSUM_PART3);\n}\n\nAVS_UNIT_TEST(dynamic_in, opaque_partial_bytes) {\n    test_partial_bytes(AVS_COAP_FORMAT_OCTET_STREAM,\n                       PAYLOAD_STRING(LOREM_IPSUM));\n}\n\nAVS_UNIT_TEST(dynamic_in, text_partial_bytes) {\n    test_partial_bytes(AVS_COAP_FORMAT_PLAINTEXT,\n                       PAYLOAD_STRING(LOREM_IPSUM_AS_BASE64_STRICT));\n}\n\nAVS_UNIT_TEST(dynamic_in, text_partial_string) {\n    test_partial_string(AVS_COAP_FORMAT_PLAINTEXT, PAYLOAD_STRING(LOREM_IPSUM));\n}\n\nAVS_UNIT_TEST(dynamic_in, tlv_partial_bytes) {\n    test_partial_bytes(AVS_COAP_FORMAT_OMA_LWM2M_TLV,\n                       PAYLOAD_STRING(LOREM_IPSUM_AS_TLV));\n}\n\nAVS_UNIT_TEST(dynamic_in, tlv_partial_string) {\n    test_partial_string(AVS_COAP_FORMAT_OMA_LWM2M_TLV,\n                        PAYLOAD_STRING(LOREM_IPSUM_AS_TLV));\n}\n\n#ifdef ANJAY_WITH_SENML_JSON\nAVS_UNIT_TEST(dynamic_in, senml_json_partial_bytes) {\n    test_partial_bytes(AVS_COAP_FORMAT_SENML_JSON,\n                       PAYLOAD_STRING(LOREM_IPSUM_AS_SENML_JSON_BYTES));\n}\n\nAVS_UNIT_TEST(dynamic_in, senml_json_partial_string) {\n    test_partial_string(AVS_COAP_FORMAT_SENML_JSON,\n                        PAYLOAD_STRING(LOREM_IPSUM_AS_SENML_JSON_STRING));\n}\n#endif // ANJAY_WITH_SENML_JSON\n\n#ifdef ANJAY_WITH_CBOR\nAVS_UNIT_TEST(dynamic_in, cbor_partial_bytes) {\n    test_partial_bytes(AVS_COAP_FORMAT_CBOR,\n                       PAYLOAD_STRING(LOREM_IPSUM_AS_CBOR_BYTES));\n}\n\nAVS_UNIT_TEST(dynamic_in, cbor_partial_string) {\n    test_partial_string(AVS_COAP_FORMAT_CBOR,\n                        PAYLOAD_STRING(LOREM_IPSUM_AS_CBOR_STRING));\n}\n\nAVS_UNIT_TEST(dynamic_in, senml_cbor_partial_bytes) {\n    test_partial_bytes(AVS_COAP_FORMAT_SENML_CBOR,\n                       PAYLOAD_STRING(LOREM_IPSUM_AS_SENML_CBOR_BYTES));\n}\n\nAVS_UNIT_TEST(dynamic_in, senml_cbor_partial_string) {\n    test_partial_string(AVS_COAP_FORMAT_SENML_CBOR,\n                        PAYLOAD_STRING(LOREM_IPSUM_AS_SENML_CBOR_STRING));\n}\n#endif // ANJAY_WITH_CBOR\n"
  },
  {
    "path": "tests/core/io/json/json_decoder.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#include <avsystem/commons/avs_stream_membuf.h>\n#include <avsystem/commons/avs_stream_outbuf.h>\n#define AVS_UNIT_ENABLE_SHORT_ASSERTS\n#include <avsystem/commons/avs_unit_test.h>\n\n#include \"src/core/io/json/anjay_json_decoder.h\"\n#include \"tests/utils/utils.h\"\n\n#define SCOPED_TEST_ENV(Data, Size)                                        \\\n    SCOPED_PTR(avs_stream_t, avs_stream_cleanup)                           \\\n    STREAM = avs_stream_membuf_create();                                   \\\n    ASSERT_NOT_NULL(STREAM);                                               \\\n    ASSERT_OK(avs_stream_write(STREAM, (Data), (Size)));                   \\\n    SCOPED_PTR(anjay_json_like_decoder_t, _anjay_json_like_decoder_delete) \\\n    DECODER = _anjay_json_decoder_new(STREAM);                             \\\n    ASSERT_NOT_NULL(DECODER);\n\n#define TEST_NUMBER(Name, Value)                                     \\\n    AVS_UNIT_TEST(json_decoder, number_##Name) {                     \\\n        static const char data[] = #Value;                           \\\n        SCOPED_TEST_ENV(data, strlen(data));                         \\\n        anjay_json_like_number_t value;                              \\\n        ASSERT_OK(_anjay_json_like_decoder_number(DECODER, &value)); \\\n        ASSERT_EQ(value.type, ANJAY_JSON_LIKE_VALUE_DOUBLE);         \\\n        ASSERT_EQ(value.value.f64, (double) (Value));                \\\n        ASSERT_EQ(_anjay_json_like_decoder_state(DECODER),           \\\n                  ANJAY_JSON_LIKE_DECODER_STATE_FINISHED);           \\\n    }\n\nTEST_NUMBER(zero, 0);\nTEST_NUMBER(positive_integer, 1234);\nTEST_NUMBER(negative_integer, -1234);\nTEST_NUMBER(positive_fraction, 1.234);\nTEST_NUMBER(negative_fraction, -1.234);\nTEST_NUMBER(positive_integer_with_unsigned_lowercase_exponent, 1234e56);\nTEST_NUMBER(positive_integer_with_unsigned_uppercase_exponent, 1234E56);\nTEST_NUMBER(positive_integer_with_positive_lowercase_exponent, 1234e+56);\nTEST_NUMBER(positive_integer_with_positive_uppercase_exponent, 1234E+56);\nTEST_NUMBER(positive_integer_with_negative_lowercase_exponent, 1234e-56);\nTEST_NUMBER(positive_integer_with_negative_uppercase_exponent, 1234E-56);\nTEST_NUMBER(negative_integer_with_unsigned_lowercase_exponent, -1234e56);\nTEST_NUMBER(negative_integer_with_unsigned_uppercase_exponent, -1234E56);\nTEST_NUMBER(negative_integer_with_positive_lowercase_exponent, -1234e+56);\nTEST_NUMBER(negative_integer_with_positive_uppercase_exponent, -1234E+56);\nTEST_NUMBER(negative_integer_with_negative_lowercase_exponent, -1234e-56);\nTEST_NUMBER(negative_integer_with_negative_uppercase_exponent, -1234E-56);\nTEST_NUMBER(positive_fraction_with_unsigned_lowercase_exponent, 1.234e56);\nTEST_NUMBER(positive_fraction_with_unsigned_uppercase_exponent, 1.234E56);\nTEST_NUMBER(positive_fraction_with_positive_lowercase_exponent, 1.234e+56);\nTEST_NUMBER(positive_fraction_with_positive_uppercase_exponent, 1.234E+56);\nTEST_NUMBER(positive_fraction_with_negative_lowercase_exponent, 1.234e-56);\nTEST_NUMBER(positive_fraction_with_negative_uppercase_exponent, 1.234E-56);\nTEST_NUMBER(negative_fraction_with_unsigned_lowercase_exponent, -1.234e56);\nTEST_NUMBER(negative_fraction_with_unsigned_uppercase_exponent, -1.234E56);\nTEST_NUMBER(negative_fraction_with_positive_lowercase_exponent, -1.234e+56);\nTEST_NUMBER(negative_fraction_with_positive_uppercase_exponent, -1.234E+56);\nTEST_NUMBER(negative_fraction_with_negative_lowercase_exponent, -1.234e-56);\nTEST_NUMBER(negative_fraction_with_negative_uppercase_exponent, -1.234E-56);\nTEST_NUMBER(leading_zero_fraction, 0.123);\nTEST_NUMBER(leading_zero_exponent, 0e123);\n\nAVS_UNIT_TEST(json_decoder, number_too_long) {\n    char data[2 * ANJAY_MAX_DOUBLE_STRING_SIZE];\n    for (size_t i = 0; i < sizeof(data) - 1; ++i) {\n        data[i] = '1';\n    }\n    data[sizeof(data) - 1] = '\\0';\n    SCOPED_TEST_ENV(data, strlen(data));\n    anjay_json_like_number_t value;\n    ASSERT_FAIL(_anjay_json_like_decoder_number(DECODER, &value));\n    ASSERT_EQ(_anjay_json_like_decoder_state(DECODER),\n              ANJAY_JSON_LIKE_DECODER_STATE_ERROR);\n}\n\nAVS_UNIT_TEST(json_decoder, number_invalid_chars) {\n    static const char data[] = \"123false\";\n    SCOPED_TEST_ENV(data, strlen(data));\n\n    // unexpected character will be treated as end-of-token, so the number\n    // itself will parse just fine...\n    anjay_json_like_number_t value;\n    ASSERT_OK(_anjay_json_like_decoder_number(DECODER, &value));\n    ASSERT_EQ(value.type, ANJAY_JSON_LIKE_VALUE_DOUBLE);\n    ASSERT_EQ(value.value.f64, 123.0);\n\n    // ...but the \"next token\" will be unexpected at the top level\n    ASSERT_EQ(_anjay_json_like_decoder_state(DECODER),\n              ANJAY_JSON_LIKE_DECODER_STATE_ERROR);\n    ASSERT_FAIL(_anjay_json_like_decoder_current_value_type(\n            DECODER, &(anjay_json_like_value_type_t) { 0 }));\n}\n\nAVS_UNIT_TEST(json_decoder, number_unparsable_value) {\n    static const char data[] = \"1e2e3\";\n    SCOPED_TEST_ENV(data, strlen(data));\n    ASSERT_FAIL(_anjay_json_like_decoder_number(\n            DECODER, &(anjay_json_like_number_t) { 0 }));\n    ASSERT_EQ(_anjay_json_like_decoder_state(DECODER),\n              ANJAY_JSON_LIKE_DECODER_STATE_ERROR);\n}\n\nAVS_UNIT_TEST(json_decoder, number_leading_dot) {\n    static const char data[] = \".1\";\n    SCOPED_TEST_ENV(data, strlen(data));\n    ASSERT_FAIL(_anjay_json_like_decoder_number(\n            DECODER, &(anjay_json_like_number_t) { 0 }));\n    ASSERT_EQ(_anjay_json_like_decoder_state(DECODER),\n              ANJAY_JSON_LIKE_DECODER_STATE_ERROR);\n}\n\nAVS_UNIT_TEST(json_decoder, number_minus_leading_dot) {\n    static const char data[] = \"-.1\";\n    SCOPED_TEST_ENV(data, strlen(data));\n    ASSERT_FAIL(_anjay_json_like_decoder_number(\n            DECODER, &(anjay_json_like_number_t) { 0 }));\n    ASSERT_EQ(_anjay_json_like_decoder_state(DECODER),\n              ANJAY_JSON_LIKE_DECODER_STATE_ERROR);\n}\n\nstatic const char *read_short_string(anjay_json_like_decoder_t *ctx) {\n    static char short_string[128];\n    avs_stream_outbuf_t stream = AVS_STREAM_OUTBUF_STATIC_INITIALIZER;\n    avs_stream_outbuf_set_buffer(\n            &stream, short_string, sizeof(short_string) - 1);\n    int result = _anjay_json_like_decoder_bytes(ctx, (avs_stream_t *) &stream);\n    if (result) {\n        return NULL;\n    }\n    short_string[avs_stream_outbuf_offset(&stream)] = '\\0';\n    return short_string;\n}\n\nAVS_UNIT_TEST(json_decoder, string_simple) {\n    static const char data[] = \"\\\"Hello, world!\\\"\";\n    SCOPED_TEST_ENV(data, strlen(data));\n    ASSERT_EQ_STR(read_short_string(DECODER), \"Hello, world!\");\n    ASSERT_EQ(_anjay_json_like_decoder_state(DECODER),\n              ANJAY_JSON_LIKE_DECODER_STATE_FINISHED);\n}\n\nAVS_UNIT_TEST(json_decoder, string_unicode) {\n    static const char data[] = \"\\\"お前はもうライトウェイト\\\"\";\n    SCOPED_TEST_ENV(data, strlen(data));\n    ASSERT_EQ_STR(read_short_string(DECODER), \"お前はもうライトウェイト\");\n    ASSERT_EQ(_anjay_json_like_decoder_state(DECODER),\n              ANJAY_JSON_LIKE_DECODER_STATE_FINISHED);\n}\n\nAVS_UNIT_TEST(json_decoder, string_simple_escapes) {\n    static const char data[] = \"\\\"Ve\\\\ry use\\\\ful se\\\\t of \\\\\\\"\\\\bi\\\\ngo\\\\\\\" \"\n                               \"characters \\\\\\\\o\\\\/\\\"\";\n    SCOPED_TEST_ENV(data, strlen(data));\n    ASSERT_EQ_STR(read_short_string(DECODER),\n                  \"Ve\\ry use\\ful se\\t of \\\"\\bi\\ngo\\\" characters \\\\o/\");\n    ASSERT_EQ(_anjay_json_like_decoder_state(DECODER),\n              ANJAY_JSON_LIKE_DECODER_STATE_FINISHED);\n}\n\nAVS_UNIT_TEST(json_decoder, string_unicode_escapes) {\n    static const char data[] =\n            \"\\\"\\\\u304a\\\\u524D\\\\u306f\\\\u3082\\\\u3046\\\\u004c\\\\u0077\\\\u004D\\\\u0032\"\n            \"\\\\u004d\\\\u0020\\\\u0028\\\\u00B0\\\\u0414\\\\u00b0\\\\u0029\\\"\";\n    SCOPED_TEST_ENV(data, strlen(data));\n    ASSERT_EQ_STR(read_short_string(DECODER), \"お前はもうLwM2M (°Д°)\");\n    ASSERT_EQ(_anjay_json_like_decoder_state(DECODER),\n              ANJAY_JSON_LIKE_DECODER_STATE_FINISHED);\n}\n\nAVS_UNIT_TEST(json_decoder, string_invalid_characters) {\n    static const char data[] = \"\\\"Hello,\\nworld!\\\"\";\n    SCOPED_TEST_ENV(data, strlen(data));\n    ASSERT_NULL(read_short_string(DECODER));\n    ASSERT_EQ(_anjay_json_like_decoder_state(DECODER),\n              ANJAY_JSON_LIKE_DECODER_STATE_ERROR);\n}\n\nAVS_UNIT_TEST(json_decoder, string_invalid_escape) {\n    static const char data[] = \"\\\"Hello, \\\\world!\\\"\";\n    SCOPED_TEST_ENV(data, strlen(data));\n    ASSERT_NULL(read_short_string(DECODER));\n    ASSERT_EQ(_anjay_json_like_decoder_state(DECODER),\n              ANJAY_JSON_LIKE_DECODER_STATE_ERROR);\n}\n\nAVS_UNIT_TEST(json_decoder, string_too_short_unicode_escape) {\n    static const char data[] = \"\\\"Hello, world\\\\u21\\\"\";\n    SCOPED_TEST_ENV(data, strlen(data));\n    ASSERT_NULL(read_short_string(DECODER));\n    ASSERT_EQ(_anjay_json_like_decoder_state(DECODER),\n              ANJAY_JSON_LIKE_DECODER_STATE_ERROR);\n}\n\nAVS_UNIT_TEST(json_decoder, string_invalid_unicode_escape) {\n    static const char data[] = \"\\\"Hello, world\\\\uGHIJ\\\"\";\n    SCOPED_TEST_ENV(data, strlen(data));\n    ASSERT_NULL(read_short_string(DECODER));\n    ASSERT_EQ(_anjay_json_like_decoder_state(DECODER),\n              ANJAY_JSON_LIKE_DECODER_STATE_ERROR);\n}\n\nAVS_UNIT_TEST(json_decoder, string_null_unicode_escape) {\n    static const char data[] = \"\\\"Hello, world\\\\u\\0\\0\\0\\0\\\"\";\n    SCOPED_TEST_ENV(data, sizeof(data) - 1);\n    ASSERT_NULL(read_short_string(DECODER));\n    ASSERT_EQ(_anjay_json_like_decoder_state(DECODER),\n              ANJAY_JSON_LIKE_DECODER_STATE_ERROR);\n}\n\nAVS_UNIT_TEST(json_decoder, string_space_unicode_escape) {\n    static const char data[] = \"\\\"Hello, world\\\\u    \\\"\";\n    SCOPED_TEST_ENV(data, strlen(data));\n    ASSERT_NULL(read_short_string(DECODER));\n    ASSERT_EQ(_anjay_json_like_decoder_state(DECODER),\n              ANJAY_JSON_LIKE_DECODER_STATE_ERROR);\n}\n\nAVS_UNIT_TEST(json_decoder, boolean_true) {\n    static const char data[] = \"true\";\n    SCOPED_TEST_ENV(data, strlen(data));\n    bool value;\n    ASSERT_OK(_anjay_json_like_decoder_bool(DECODER, &value));\n    ASSERT_EQ(value, true);\n    ASSERT_EQ(_anjay_json_like_decoder_state(DECODER),\n              ANJAY_JSON_LIKE_DECODER_STATE_FINISHED);\n}\n\nAVS_UNIT_TEST(json_decoder, boolean_false) {\n    static const char data[] = \"false\";\n    SCOPED_TEST_ENV(data, strlen(data));\n    bool value;\n    ASSERT_OK(_anjay_json_like_decoder_bool(DECODER, &value));\n    ASSERT_EQ(value, false);\n    ASSERT_EQ(_anjay_json_like_decoder_state(DECODER),\n              ANJAY_JSON_LIKE_DECODER_STATE_FINISHED);\n}\n\nAVS_UNIT_TEST(json_decoder, boolean_true_too_short) {\n    static const char data[] = \"tru\";\n    SCOPED_TEST_ENV(data, strlen(data));\n    ASSERT_FAIL(_anjay_json_like_decoder_bool(DECODER, &(bool) { false }));\n    ASSERT_EQ(_anjay_json_like_decoder_state(DECODER),\n              ANJAY_JSON_LIKE_DECODER_STATE_ERROR);\n}\n\nAVS_UNIT_TEST(json_decoder, boolean_false_too_short) {\n    static const char data[] = \"fals\";\n    SCOPED_TEST_ENV(data, strlen(data));\n    ASSERT_FAIL(_anjay_json_like_decoder_bool(DECODER, &(bool) { false }));\n    ASSERT_EQ(_anjay_json_like_decoder_state(DECODER),\n              ANJAY_JSON_LIKE_DECODER_STATE_ERROR);\n}\n\nAVS_UNIT_TEST(json_decoder, boolean_true_wrong) {\n    static const char data[] = \"tRue\";\n    SCOPED_TEST_ENV(data, strlen(data));\n    ASSERT_FAIL(_anjay_json_like_decoder_bool(DECODER, &(bool) { false }));\n    ASSERT_EQ(_anjay_json_like_decoder_state(DECODER),\n              ANJAY_JSON_LIKE_DECODER_STATE_ERROR);\n}\n\nAVS_UNIT_TEST(json_decoder, boolean_false_wrong) {\n    static const char data[] = \"falSe\";\n    SCOPED_TEST_ENV(data, strlen(data));\n    ASSERT_FAIL(_anjay_json_like_decoder_bool(DECODER, &(bool) { false }));\n    ASSERT_EQ(_anjay_json_like_decoder_state(DECODER),\n              ANJAY_JSON_LIKE_DECODER_STATE_ERROR);\n}\n\nAVS_UNIT_TEST(json_decoder, null) {\n    static const char data[] = \"null\";\n    SCOPED_TEST_ENV(data, strlen(data));\n    anjay_json_like_value_type_t type;\n    ASSERT_OK(_anjay_json_like_decoder_current_value_type(DECODER, &type));\n    ASSERT_EQ(type, ANJAY_JSON_LIKE_VALUE_NULL);\n#ifdef ANJAY_WITH_LWM2M12\n    ASSERT_OK(_anjay_json_like_decoder_null(DECODER));\n    ASSERT_EQ(_anjay_json_like_decoder_state(DECODER),\n              ANJAY_JSON_LIKE_DECODER_STATE_FINISHED);\n#endif // ANJAY_WITH_LWM2M12\n}\n\n#ifdef ANJAY_WITH_LWM2M12\nAVS_UNIT_TEST(json_decoder, null_wrong) {\n    static const char data[] = \"nUll\";\n    SCOPED_TEST_ENV(data, strlen(data));\n    ASSERT_FAIL(_anjay_json_like_decoder_null(DECODER));\n    ASSERT_EQ(_anjay_json_like_decoder_state(DECODER),\n              ANJAY_JSON_LIKE_DECODER_STATE_ERROR);\n}\n#endif // ANJAY_WITH_LWM2M12\n\nAVS_UNIT_TEST(json_decoder, only_one_value) {\n    static const char data[] = \"true false\";\n    SCOPED_TEST_ENV(data, strlen(data));\n    bool value;\n    ASSERT_OK(_anjay_json_like_decoder_bool(DECODER, &value));\n    ASSERT_EQ(value, true);\n    ASSERT_EQ(_anjay_json_like_decoder_state(DECODER),\n              ANJAY_JSON_LIKE_DECODER_STATE_ERROR);\n    ASSERT_FAIL(_anjay_json_like_decoder_bool(DECODER, &value));\n}\n\nAVS_UNIT_TEST(json_decoder, flat_array) {\n    static const char data[] = \"[1, 2, 3]\";\n    SCOPED_TEST_ENV(data, strlen(data));\n    anjay_json_like_value_type_t type;\n    ASSERT_OK(_anjay_json_like_decoder_current_value_type(DECODER, &type));\n    ASSERT_EQ(ANJAY_JSON_LIKE_VALUE_ARRAY, type);\n\n    ASSERT_EQ(_anjay_json_like_decoder_nesting_level(DECODER), 0);\n    ASSERT_OK(_anjay_json_like_decoder_enter_array(DECODER));\n\n    anjay_json_like_number_t value;\n    ASSERT_EQ(_anjay_json_like_decoder_nesting_level(DECODER), 1);\n    ASSERT_OK(_anjay_json_like_decoder_number(DECODER, &value));\n    ASSERT_EQ(value.type, ANJAY_JSON_LIKE_VALUE_DOUBLE);\n    ASSERT_EQ(value.value.f64, 1.0);\n\n    ASSERT_EQ(_anjay_json_like_decoder_nesting_level(DECODER), 1);\n    ASSERT_OK(_anjay_json_like_decoder_number(DECODER, &value));\n    ASSERT_EQ(value.type, ANJAY_JSON_LIKE_VALUE_DOUBLE);\n    ASSERT_EQ(value.value.f64, 2.0);\n\n    ASSERT_EQ(_anjay_json_like_decoder_nesting_level(DECODER), 1);\n    ASSERT_OK(_anjay_json_like_decoder_number(DECODER, &value));\n    ASSERT_EQ(value.type, ANJAY_JSON_LIKE_VALUE_DOUBLE);\n    ASSERT_EQ(value.value.f64, 3.0);\n\n    ASSERT_EQ(_anjay_json_like_decoder_nesting_level(DECODER), 0);\n    ASSERT_EQ(_anjay_json_like_decoder_state(DECODER),\n              ANJAY_JSON_LIKE_DECODER_STATE_FINISHED);\n\n    ASSERT_FAIL(_anjay_json_like_decoder_number(DECODER, &value));\n}\n\nAVS_UNIT_TEST(json_decoder, empty_array) {\n    static const char data[] = \"[]\";\n    SCOPED_TEST_ENV(data, strlen(data));\n    anjay_json_like_value_type_t type;\n    ASSERT_OK(_anjay_json_like_decoder_current_value_type(DECODER, &type));\n    ASSERT_EQ(ANJAY_JSON_LIKE_VALUE_ARRAY, type);\n\n    ASSERT_EQ(_anjay_json_like_decoder_nesting_level(DECODER), 0);\n    ASSERT_OK(_anjay_json_like_decoder_enter_array(DECODER));\n    ASSERT_EQ(_anjay_json_like_decoder_nesting_level(DECODER), 0);\n    ASSERT_EQ(_anjay_json_like_decoder_state(DECODER),\n              ANJAY_JSON_LIKE_DECODER_STATE_FINISHED);\n\n    ASSERT_FAIL(_anjay_json_like_decoder_number(\n            DECODER, &(anjay_json_like_number_t) { 0 }));\n}\n\nAVS_UNIT_TEST(json_decoder, flat_map) {\n    static const char data[] = \"{ \\\"Fun\\\": true, \\\"Stuff\\\": -2 }\";\n    SCOPED_TEST_ENV(data, strlen(data));\n    anjay_json_like_value_type_t type;\n    ASSERT_OK(_anjay_json_like_decoder_current_value_type(DECODER, &type));\n    ASSERT_EQ(type, ANJAY_JSON_LIKE_VALUE_MAP);\n\n    ASSERT_EQ(_anjay_json_like_decoder_nesting_level(DECODER), 0);\n    ASSERT_OK(_anjay_json_like_decoder_enter_map(DECODER));\n\n    ASSERT_EQ(_anjay_json_like_decoder_nesting_level(DECODER), 1);\n    ASSERT_EQ_BYTES_SIZED(read_short_string(DECODER), \"Fun\", sizeof(\"Fun\"));\n    bool value;\n    ASSERT_OK(_anjay_json_like_decoder_bool(DECODER, &value));\n    ASSERT_EQ(value, true);\n\n    ASSERT_EQ(_anjay_json_like_decoder_nesting_level(DECODER), 1);\n    ASSERT_EQ_BYTES_SIZED(read_short_string(DECODER), \"Stuff\", sizeof(\"Stuff\"));\n    anjay_json_like_number_t number;\n    ASSERT_OK(_anjay_json_like_decoder_number(DECODER, &number));\n    ASSERT_EQ(number.type, ANJAY_JSON_LIKE_VALUE_DOUBLE);\n    ASSERT_EQ(number.value.f64, -2);\n\n    ASSERT_EQ(_anjay_json_like_decoder_nesting_level(DECODER), 0);\n    ASSERT_EQ(_anjay_json_like_decoder_state(DECODER),\n              ANJAY_JSON_LIKE_DECODER_STATE_FINISHED);\n}\n\nAVS_UNIT_TEST(json_decoder, invalid_comma_in_map) {\n    static const char data[] = \"{ \\\"Fun\\\", \\\"Stuff\\\" }\";\n    SCOPED_TEST_ENV(data, strlen(data));\n    anjay_json_like_value_type_t type;\n    ASSERT_OK(_anjay_json_like_decoder_current_value_type(DECODER, &type));\n    ASSERT_EQ(type, ANJAY_JSON_LIKE_VALUE_MAP);\n\n    ASSERT_EQ(_anjay_json_like_decoder_nesting_level(DECODER), 0);\n    ASSERT_OK(_anjay_json_like_decoder_enter_map(DECODER));\n\n    ASSERT_EQ(_anjay_json_like_decoder_nesting_level(DECODER), 1);\n    ASSERT_EQ_BYTES_SIZED(read_short_string(DECODER), \"Fun\", sizeof(\"Fun\"));\n\n    ASSERT_EQ(_anjay_json_like_decoder_state(DECODER),\n              ANJAY_JSON_LIKE_DECODER_STATE_ERROR);\n    ASSERT_EQ(_anjay_json_like_decoder_nesting_level(DECODER), 0);\n    ASSERT_FAIL(_anjay_json_like_decoder_current_value_type(DECODER, &type));\n}\n\nAVS_UNIT_TEST(json_decoder, invalid_colon_in_map) {\n    static const char data[] = \"{ \\\"Fun\\\": true: \\\"Stuff\\\" }\";\n    SCOPED_TEST_ENV(data, strlen(data));\n    anjay_json_like_value_type_t type;\n    ASSERT_OK(_anjay_json_like_decoder_current_value_type(DECODER, &type));\n    ASSERT_EQ(type, ANJAY_JSON_LIKE_VALUE_MAP);\n\n    ASSERT_EQ(_anjay_json_like_decoder_nesting_level(DECODER), 0);\n    ASSERT_OK(_anjay_json_like_decoder_enter_map(DECODER));\n\n    ASSERT_EQ(_anjay_json_like_decoder_nesting_level(DECODER), 1);\n    ASSERT_EQ_BYTES_SIZED(read_short_string(DECODER), \"Fun\", sizeof(\"Fun\"));\n    bool value;\n    ASSERT_OK(_anjay_json_like_decoder_bool(DECODER, &value));\n    ASSERT_EQ(value, true);\n\n    ASSERT_EQ(_anjay_json_like_decoder_state(DECODER),\n              ANJAY_JSON_LIKE_DECODER_STATE_ERROR);\n    ASSERT_EQ(_anjay_json_like_decoder_nesting_level(DECODER), 0);\n    ASSERT_FAIL(_anjay_json_like_decoder_current_value_type(DECODER, &type));\n}\n\nAVS_UNIT_TEST(json_decoder, empty_map) {\n    static const char data[] = \"{}\";\n    SCOPED_TEST_ENV(data, strlen(data));\n    anjay_json_like_value_type_t type;\n    ASSERT_OK(_anjay_json_like_decoder_current_value_type(DECODER, &type));\n    ASSERT_EQ(type, ANJAY_JSON_LIKE_VALUE_MAP);\n\n    ASSERT_EQ(_anjay_json_like_decoder_nesting_level(DECODER), 0);\n    ASSERT_OK(_anjay_json_like_decoder_enter_map(DECODER));\n    ASSERT_EQ(_anjay_json_like_decoder_nesting_level(DECODER), 0);\n    ASSERT_EQ(_anjay_json_like_decoder_state(DECODER),\n              ANJAY_JSON_LIKE_DECODER_STATE_FINISHED);\n}\n\nstatic void maps_in_array_test_impl(anjay_json_like_decoder_t *decoder) {\n    anjay_json_like_value_type_t type;\n    anjay_json_like_number_t number;\n    ASSERT_OK(_anjay_json_like_decoder_current_value_type(decoder, &type));\n    ASSERT_EQ(type, ANJAY_JSON_LIKE_VALUE_ARRAY);\n\n    ASSERT_EQ(_anjay_json_like_decoder_nesting_level(decoder), 0);\n    ASSERT_OK(_anjay_json_like_decoder_enter_array(decoder));\n    ASSERT_EQ(_anjay_json_like_decoder_nesting_level(decoder), 1);\n\n    ASSERT_OK(_anjay_json_like_decoder_current_value_type(decoder, &type));\n    ASSERT_EQ(type, ANJAY_JSON_LIKE_VALUE_MAP);\n\n    ASSERT_OK(_anjay_json_like_decoder_enter_map(decoder));\n    ASSERT_EQ(_anjay_json_like_decoder_nesting_level(decoder), 1);\n\n    ASSERT_OK(_anjay_json_like_decoder_current_value_type(decoder, &type));\n    ASSERT_EQ(type, ANJAY_JSON_LIKE_VALUE_MAP);\n\n    ASSERT_OK(_anjay_json_like_decoder_enter_map(decoder));\n\n    ASSERT_EQ(_anjay_json_like_decoder_nesting_level(decoder), 2);\n    ASSERT_EQ_STR(read_short_string(decoder), \"1\");\n\n    ASSERT_EQ(_anjay_json_like_decoder_nesting_level(decoder), 2);\n    ASSERT_OK(_anjay_json_like_decoder_number(decoder, &number));\n    ASSERT_EQ(number.type, ANJAY_JSON_LIKE_VALUE_DOUBLE);\n    ASSERT_EQ(number.value.f64, 2);\n\n    ASSERT_EQ(_anjay_json_like_decoder_nesting_level(decoder), 1);\n    ASSERT_OK(_anjay_json_like_decoder_current_value_type(decoder, &type));\n    ASSERT_EQ(type, ANJAY_JSON_LIKE_VALUE_MAP);\n\n    ASSERT_OK(_anjay_json_like_decoder_enter_map(decoder));\n\n    ASSERT_EQ(_anjay_json_like_decoder_nesting_level(decoder), 2);\n    ASSERT_EQ_STR(read_short_string(decoder), \"3\");\n\n    ASSERT_EQ(_anjay_json_like_decoder_nesting_level(decoder), 2);\n    ASSERT_OK(_anjay_json_like_decoder_number(decoder, &number));\n    ASSERT_EQ(number.type, ANJAY_JSON_LIKE_VALUE_DOUBLE);\n    ASSERT_EQ(number.value.f64, 4);\n\n    ASSERT_EQ(_anjay_json_like_decoder_nesting_level(decoder), 2);\n    ASSERT_EQ_STR(read_short_string(decoder), \"5\");\n\n    ASSERT_EQ(_anjay_json_like_decoder_nesting_level(decoder), 2);\n    ASSERT_OK(_anjay_json_like_decoder_number(decoder, &number));\n    ASSERT_EQ(number.type, ANJAY_JSON_LIKE_VALUE_DOUBLE);\n    ASSERT_EQ(number.value.f64, 6);\n\n    ASSERT_EQ(_anjay_json_like_decoder_nesting_level(decoder), 0);\n    ASSERT_EQ(_anjay_json_like_decoder_state(decoder),\n              ANJAY_JSON_LIKE_DECODER_STATE_FINISHED);\n}\n\nAVS_UNIT_TEST(json_decoder, maps_in_array) {\n    static const char data[] = \"[ {}, { \\\"1\\\": 2 }, { \\\"3\\\": 4, \\\"5\\\": 6 } ]\";\n    SCOPED_TEST_ENV(data, strlen(data));\n    maps_in_array_test_impl(DECODER);\n}\n\nAVS_UNIT_TEST(json_decoder, maps_in_array_no_whitespace) {\n    static const char data[] = \"[{},{\\\"1\\\":2},{\\\"3\\\":4,\\\"5\\\":6}]\";\n    SCOPED_TEST_ENV(data, strlen(data));\n    maps_in_array_test_impl(DECODER);\n}\n\nAVS_UNIT_TEST(json_decoder, maps_in_array_all_possible_whitespace) {\n    static const char data[] =\n            \" [\\r{\\n}\\t, \\r{\\n\\t\\\"1\\\" \\r\\n:\\t \\r2\\n\\t }\\r\\n\\t, \\r\\n\\t{ \\r\\n\\t \"\n            \"\\\"3\\\"\\r\\n\\t \\r:\\n\\t \\r\\n4\\t \\r\\n\\t, \\r\\n\\t \\r\\\"5\\\"\\n\\t \\r\\n\\t: \"\n            \"\\r\\n\\t \\r\\n6\\t \\r\\n\\t \\r}\\n\\t \\r\\n\\t] \\r\\n\\t \\r\\n\\t\";\n    SCOPED_TEST_ENV(data, strlen(data));\n    maps_in_array_test_impl(DECODER);\n}\n\nAVS_UNIT_TEST(json_decoder, arrays_in_map) {\n    static const char data[] = \"{ \\\"0\\\": [], \\\"1\\\": [2], \\\"3\\\": [4, 5, 6] }\";\n    SCOPED_TEST_ENV(data, strlen(data));\n\n    anjay_json_like_value_type_t type;\n    anjay_json_like_number_t number;\n    ASSERT_OK(_anjay_json_like_decoder_current_value_type(DECODER, &type));\n    ASSERT_EQ(type, ANJAY_JSON_LIKE_VALUE_MAP);\n\n    ASSERT_EQ(_anjay_json_like_decoder_nesting_level(DECODER), 0);\n    ASSERT_OK(_anjay_json_like_decoder_enter_map(DECODER));\n    ASSERT_EQ(_anjay_json_like_decoder_nesting_level(DECODER), 1);\n\n    ASSERT_EQ_STR(read_short_string(DECODER), \"0\");\n    ASSERT_OK(_anjay_json_like_decoder_current_value_type(DECODER, &type));\n    ASSERT_EQ(type, ANJAY_JSON_LIKE_VALUE_ARRAY);\n\n    ASSERT_OK(_anjay_json_like_decoder_enter_array(DECODER));\n    ASSERT_EQ(_anjay_json_like_decoder_nesting_level(DECODER), 1);\n\n    ASSERT_EQ_STR(read_short_string(DECODER), \"1\");\n    ASSERT_OK(_anjay_json_like_decoder_current_value_type(DECODER, &type));\n    ASSERT_EQ(type, ANJAY_JSON_LIKE_VALUE_ARRAY);\n\n    ASSERT_OK(_anjay_json_like_decoder_enter_array(DECODER));\n\n    ASSERT_EQ(_anjay_json_like_decoder_nesting_level(DECODER), 2);\n    ASSERT_OK(_anjay_json_like_decoder_number(DECODER, &number));\n    ASSERT_EQ(number.type, ANJAY_JSON_LIKE_VALUE_DOUBLE);\n    ASSERT_EQ(number.value.f64, 2);\n\n    ASSERT_EQ(_anjay_json_like_decoder_nesting_level(DECODER), 1);\n    ASSERT_EQ_STR(read_short_string(DECODER), \"3\");\n    ASSERT_OK(_anjay_json_like_decoder_current_value_type(DECODER, &type));\n    ASSERT_EQ(type, ANJAY_JSON_LIKE_VALUE_ARRAY);\n\n    ASSERT_OK(_anjay_json_like_decoder_enter_array(DECODER));\n\n    ASSERT_EQ(_anjay_json_like_decoder_nesting_level(DECODER), 2);\n    ASSERT_OK(_anjay_json_like_decoder_number(DECODER, &number));\n    ASSERT_EQ(number.type, ANJAY_JSON_LIKE_VALUE_DOUBLE);\n    ASSERT_EQ(number.value.f64, 4);\n\n    ASSERT_EQ(_anjay_json_like_decoder_nesting_level(DECODER), 2);\n    ASSERT_OK(_anjay_json_like_decoder_number(DECODER, &number));\n    ASSERT_EQ(number.type, ANJAY_JSON_LIKE_VALUE_DOUBLE);\n    ASSERT_EQ(number.value.f64, 5);\n\n    ASSERT_EQ(_anjay_json_like_decoder_nesting_level(DECODER), 2);\n    ASSERT_OK(_anjay_json_like_decoder_number(DECODER, &number));\n    ASSERT_EQ(number.type, ANJAY_JSON_LIKE_VALUE_DOUBLE);\n    ASSERT_EQ(number.value.f64, 6);\n\n    ASSERT_EQ(_anjay_json_like_decoder_nesting_level(DECODER), 0);\n    ASSERT_EQ(_anjay_json_like_decoder_state(DECODER),\n              ANJAY_JSON_LIKE_DECODER_STATE_FINISHED);\n}\n\nAVS_UNIT_TEST(json_decoder, nesting_too_deep) {\n    static const char data[] = \"[ [ [ 42 ] ] ]\";\n    SCOPED_TEST_ENV(data, strlen(data));\n\n    ASSERT_OK(_anjay_json_like_decoder_enter_array(DECODER));\n    ASSERT_OK(_anjay_json_like_decoder_enter_array(DECODER));\n    ASSERT_FAIL(_anjay_json_like_decoder_enter_array(DECODER));\n    ASSERT_EQ(_anjay_json_like_decoder_state(DECODER),\n              ANJAY_JSON_LIKE_DECODER_STATE_ERROR);\n}\n\nAVS_UNIT_TEST(json_decoder, non_string_map_keys) {\n    static const char data[] = \"{ 1: 2 }\";\n    SCOPED_TEST_ENV(data, strlen(data));\n\n    ASSERT_OK(_anjay_json_like_decoder_enter_map(DECODER));\n    ASSERT_EQ(_anjay_json_like_decoder_state(DECODER),\n              ANJAY_JSON_LIKE_DECODER_STATE_ERROR);\n    ASSERT_FAIL(_anjay_json_like_decoder_current_value_type(\n            DECODER, &(anjay_json_like_value_type_t) { 0 }));\n}\n\nAVS_UNIT_TEST(json_decoder, no_input) {\n    static const char data[] = \"\";\n    SCOPED_TEST_ENV(data, strlen(data));\n    ASSERT_EQ(_anjay_json_like_decoder_state(DECODER),\n              ANJAY_JSON_LIKE_DECODER_STATE_ERROR);\n    ASSERT_FAIL(_anjay_json_like_decoder_current_value_type(\n            DECODER, &(anjay_json_like_value_type_t) { 0 }));\n}\n\nAVS_UNIT_TEST(json_decoder, invalid_input) {\n    static const char data[] = \"manure\";\n    SCOPED_TEST_ENV(data, strlen(data));\n    ASSERT_EQ(_anjay_json_like_decoder_state(DECODER),\n              ANJAY_JSON_LIKE_DECODER_STATE_ERROR);\n    ASSERT_FAIL(_anjay_json_like_decoder_current_value_type(\n            DECODER, &(anjay_json_like_value_type_t) { 0 }));\n}\n\nAVS_UNIT_TEST(json_decoder, nested_unexpected_eof) {\n    static const char data[] = \"[\";\n    SCOPED_TEST_ENV(data, strlen(data));\n    anjay_json_like_value_type_t type;\n    ASSERT_OK(_anjay_json_like_decoder_current_value_type(DECODER, &type));\n    ASSERT_EQ(type, ANJAY_JSON_LIKE_VALUE_ARRAY);\n    ASSERT_OK(_anjay_json_like_decoder_enter_array(DECODER));\n    ASSERT_EQ(_anjay_json_like_decoder_state(DECODER),\n              ANJAY_JSON_LIKE_DECODER_STATE_ERROR);\n    ASSERT_FAIL(_anjay_json_like_decoder_current_value_type(DECODER, &type));\n}\n\nAVS_UNIT_TEST(json_decoder, nested_invalid_entry) {\n    static const char data[] = \"[manure]\";\n    SCOPED_TEST_ENV(data, strlen(data));\n    anjay_json_like_value_type_t type;\n    ASSERT_OK(_anjay_json_like_decoder_current_value_type(DECODER, &type));\n    ASSERT_EQ(type, ANJAY_JSON_LIKE_VALUE_ARRAY);\n    ASSERT_OK(_anjay_json_like_decoder_enter_array(DECODER));\n    ASSERT_EQ(_anjay_json_like_decoder_state(DECODER),\n              ANJAY_JSON_LIKE_DECODER_STATE_ERROR);\n    ASSERT_FAIL(_anjay_json_like_decoder_current_value_type(DECODER, &type));\n}\n\nAVS_UNIT_TEST(json_decoder, nested_next_unexpected_eof) {\n    static const char data[] = \"[42, \";\n    SCOPED_TEST_ENV(data, strlen(data));\n    anjay_json_like_value_type_t type;\n    ASSERT_OK(_anjay_json_like_decoder_current_value_type(DECODER, &type));\n    ASSERT_EQ(type, ANJAY_JSON_LIKE_VALUE_ARRAY);\n    ASSERT_OK(_anjay_json_like_decoder_enter_array(DECODER));\n    ASSERT_OK(_anjay_json_like_decoder_current_value_type(DECODER, &type));\n    ASSERT_EQ(type, ANJAY_JSON_LIKE_VALUE_DOUBLE);\n    ASSERT_OK(_anjay_json_like_decoder_number(\n            DECODER, &(anjay_json_like_number_t) { 0 }));\n    ASSERT_EQ(_anjay_json_like_decoder_state(DECODER),\n              ANJAY_JSON_LIKE_DECODER_STATE_ERROR);\n    ASSERT_FAIL(_anjay_json_like_decoder_current_value_type(DECODER, &type));\n}\n\nAVS_UNIT_TEST(json_decoder, nested_next_invalid_entry) {\n    static const char data[] = \"[42, manure]\";\n    SCOPED_TEST_ENV(data, strlen(data));\n    anjay_json_like_value_type_t type;\n    ASSERT_OK(_anjay_json_like_decoder_current_value_type(DECODER, &type));\n    ASSERT_EQ(type, ANJAY_JSON_LIKE_VALUE_ARRAY);\n    ASSERT_OK(_anjay_json_like_decoder_enter_array(DECODER));\n    ASSERT_OK(_anjay_json_like_decoder_current_value_type(DECODER, &type));\n    ASSERT_EQ(type, ANJAY_JSON_LIKE_VALUE_DOUBLE);\n    ASSERT_OK(_anjay_json_like_decoder_number(\n            DECODER, &(anjay_json_like_number_t) { 0 }));\n    ASSERT_EQ(_anjay_json_like_decoder_state(DECODER),\n              ANJAY_JSON_LIKE_DECODER_STATE_ERROR);\n    ASSERT_FAIL(_anjay_json_like_decoder_current_value_type(DECODER, &type));\n}\n\nAVS_UNIT_TEST(json_decoder, invalid_whitespace) {\n    static const char data[] = \"[\\v42\\f]\";\n    SCOPED_TEST_ENV(data, strlen(data));\n    anjay_json_like_value_type_t type;\n    ASSERT_OK(_anjay_json_like_decoder_current_value_type(DECODER, &type));\n    ASSERT_EQ(type, ANJAY_JSON_LIKE_VALUE_ARRAY);\n    ASSERT_OK(_anjay_json_like_decoder_enter_array(DECODER));\n    ASSERT_EQ(_anjay_json_like_decoder_state(DECODER),\n              ANJAY_JSON_LIKE_DECODER_STATE_ERROR);\n    ASSERT_FAIL(_anjay_json_like_decoder_current_value_type(DECODER, &type));\n}\n"
  },
  {
    "path": "tests/core/io/json_in.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#include <avsystem/commons/avs_memory.h>\n#include <avsystem/commons/avs_stream_inbuf.h>\n#include <avsystem/commons/avs_vector.h>\n\n#include \"senml_in_common.h\"\n\n#define TEST_ENV(Data, Path)                                         \\\n    avs_stream_inbuf_t stream = AVS_STREAM_INBUF_STATIC_INITIALIZER; \\\n    avs_stream_inbuf_set_buffer(&stream, Data, sizeof(Data) - 1);    \\\n    anjay_unlocked_input_ctx_t *in;                                  \\\n    ASSERT_OK(_anjay_input_json_create(&in, (avs_stream_t *) &stream, &(Path)));\n\nAVS_UNIT_TEST(json_in_resource, single_instance) {\n    static const char RESOURCE[] = \"[ { \\\"n\\\": \\\"/13/26/1\\\", \\\"v\\\": 42 } ]\";\n    TEST_ENV(RESOURCE, TEST_RESOURCE_PATH);\n    check_paths(in, &MAKE_RESOURCE_PATH(13, 26, 1), 1);\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(json_in_resource_permuted, single_instance) {\n    static const char RESOURCE[] = \"[ { \\\"v\\\": 42, \\\"n\\\": \\\"/13/26/1\\\" } ]\";\n    TEST_ENV(RESOURCE, TEST_RESOURCE_PATH);\n    check_paths(in, &MAKE_RESOURCE_PATH(13, 26, 1), 1);\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(json_in_resource, single_instance_with_trailing_comma) {\n    static const char RESOURCES[] = \"[ { \\\"n\\\": \\\"/13/26/1\\\", \\\"v\\\": 42 }, ]\";\n    TEST_ENV(RESOURCES, TEST_RESOURCE_PATH);\n    ASSERT_FAIL(_anjay_input_get_path(in, &(anjay_uri_path_t) { 0 }, NULL));\n    TEST_TEARDOWN(FAIL);\n}\n\nAVS_UNIT_TEST(json_in_resource, single_instance_with_invalid_data) {\n    static const char RESOURCES[] = \"[ { \\\"n\\\": \\\"/13/26/1\\\", manure } ]\";\n    TEST_ENV(RESOURCES, TEST_RESOURCE_PATH);\n    ASSERT_FAIL(_anjay_input_get_path(in, &(anjay_uri_path_t) { 0 }, NULL));\n    TEST_TEARDOWN(FAIL);\n}\n\nAVS_UNIT_TEST(json_in_resource, single_instance_with_invalid_data_later) {\n    static const char RESOURCES[] =\n            \"[ { \\\"n\\\": \\\"/13/26/1\\\", \\\"v\\\": 42 }, manure ]\";\n    TEST_ENV(RESOURCES, TEST_RESOURCE_PATH);\n    ASSERT_FAIL(_anjay_input_get_path(in, &(anjay_uri_path_t) { 0 }, NULL));\n    TEST_TEARDOWN(FAIL);\n}\n\nAVS_UNIT_TEST(json_in_resource, single_instance_but_more_than_one) {\n    static const char RESOURCES[] = \"[ { \\\"n\\\": \\\"/13/26/1\\\", \\\"v\\\": 42 }, \"\n                                    \"{ \\\"n\\\": \\\"/13/26/2\\\", \\\"v\\\": 43 } ]\";\n    TEST_ENV(RESOURCES, TEST_RESOURCE_PATH);\n    test_single_instance_but_more_than_one(in, &MAKE_RESOURCE_PATH(13, 26, 1));\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(json_in_resource,\n              single_instance_but_more_than_one_without_last_get_path) {\n    static const char RESOURCES[] = \"[ { \\\"n\\\": \\\"/13/26/1\\\", \\\"v\\\": 42 }, \"\n                                    \"{ \\\"n\\\": \\\"/13/26/2\\\", \\\"v\\\": 43 } ]\";\n    TEST_ENV(RESOURCES, TEST_RESOURCE_PATH);\n    check_path(in, &MAKE_RESOURCE_PATH(13, 26, 1), 42);\n\n    // Context is restricted to /13/26/1, but it has more data to obtain,\n    // which means the request is broken.\n    TEST_TEARDOWN(FAIL);\n}\n\nAVS_UNIT_TEST(json_in_resource, single_instance_with_first_resource_unrelated) {\n    static const char RESOURCES[] = \"[ { \\\"n\\\": \\\"/13/26/2\\\", \\\"v\\\": 42 }, \"\n                                    \"{ \\\"n\\\": \\\"/13/26/1\\\", \\\"v\\\": 43 } ]\";\n    // NOTE: Request is on /13/26/1 but the first resource in the payload is\n    // /13/26/2.\n    TEST_ENV(RESOURCES, TEST_RESOURCE_PATH);\n\n    ASSERT_EQ(_anjay_input_get_path(in, NULL, NULL), ANJAY_ERR_BAD_REQUEST);\n\n    // Basically nothing was extracted from the context, because it was broken\n    // from the very beginning.\n    TEST_TEARDOWN(FAIL);\n}\n\nAVS_UNIT_TEST(json_in_resource_permuted, single_instance_but_more_than_one) {\n    static const char RESOURCES[] = \"[ { \\\"v\\\": 42, \\\"n\\\": \\\"/13/26/1\\\" }, \"\n                                    \"{ \\\"v\\\": 43, \\\"n\\\": \\\"/13/26/2\\\" } ]\";\n    TEST_ENV(RESOURCES, TEST_RESOURCE_PATH);\n    test_single_instance_but_more_than_one(in, &MAKE_RESOURCE_PATH(13, 26, 1));\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(json_in_resource, multiple_instance) {\n    static const char RESOURCES[] = \"[ { \\\"n\\\": \\\"/13/26/1/4\\\", \\\"v\\\": 42 }, \"\n                                    \"{ \\\"n\\\": \\\"/13/26/1/5\\\", \\\"v\\\": 43 } ]\";\n    TEST_ENV(RESOURCES, TEST_RESOURCE_PATH);\n    check_paths(in,\n                (const anjay_uri_path_t[2]) {\n                        MAKE_RESOURCE_INSTANCE_PATH(13, 26, 1, 4),\n                        MAKE_RESOURCE_INSTANCE_PATH(13, 26, 1, 5) },\n                2);\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(json_in_resource_permuted, multiple_instance) {\n    static const char RESOURCES[] = \"[ { \\\"v\\\": 42, \\\"n\\\": \\\"/13/26/1/4\\\" }, \"\n                                    \"{ \\\"v\\\": 43, \\\"n\\\": \\\"/13/26/1/5\\\" } ]\";\n    TEST_ENV(RESOURCES, TEST_RESOURCE_PATH);\n    check_paths(in,\n                (const anjay_uri_path_t[2]) {\n                        MAKE_RESOURCE_INSTANCE_PATH(13, 26, 1, 4),\n                        MAKE_RESOURCE_INSTANCE_PATH(13, 26, 1, 5) },\n                2);\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(json_in_instance, with_simple_resource) {\n    static const char RESOURCE[] = \"[ { \\\"n\\\": \\\"/13/26/1\\\", \\\"v\\\": 42 } ]\";\n    TEST_ENV(RESOURCE, TEST_INSTANCE_PATH);\n    check_paths(in, &MAKE_RESOURCE_PATH(13, 26, 1), 1);\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(json_in_instance, with_more_than_one_resource) {\n    static const char RESOURCES[] = \"[ { \\\"n\\\": \\\"/13/26/1\\\", \\\"v\\\": 42 }, \"\n                                    \"{ \\\"n\\\": \\\"/13/26/2\\\", \\\"v\\\": 43 } ]\";\n    TEST_ENV(RESOURCES, TEST_INSTANCE_PATH);\n    check_paths(in,\n                (const anjay_uri_path_t[2]) { MAKE_RESOURCE_PATH(13, 26, 1),\n                                              MAKE_RESOURCE_PATH(13, 26, 2) },\n                2);\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(json_in_instance, resource_skipping) {\n    static const char RESOURCES[] = \"[ { \\\"n\\\": \\\"/13/26/1\\\", \\\"v\\\": 42 }, \"\n                                    \"{ \\\"n\\\": \\\"/13/26/2\\\", \\\"v\\\": 43 } ]\";\n    TEST_ENV(RESOURCES, TEST_INSTANCE_PATH);\n    test_skipping(in,\n                  (const anjay_uri_path_t[2]) { MAKE_RESOURCE_PATH(13, 26, 1),\n                                                MAKE_RESOURCE_PATH(13, 26, 2) },\n                  2);\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(json_in_resource, invalid_iid) {\n    static const char RESOURCES[] = \"[ { \\\"n\\\": \\\"/5/65535/1\\\", \\\"v\\\": 42 } ]\";\n    TEST_ENV(RESOURCES, MAKE_OBJECT_PATH(5));\n\n    anjay_uri_path_t path;\n    ASSERT_FAIL(_anjay_input_get_path(in, &path, NULL));\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(json_in_resource, invalid_rid) {\n    static const char RESOURCES[] = \"[ { \\\"n\\\": \\\"/5/0/65535\\\", \\\"v\\\": 42 } ]\";\n    TEST_ENV(RESOURCES, MAKE_INSTANCE_PATH(5, 0));\n\n    anjay_uri_path_t path;\n    ASSERT_FAIL(_anjay_input_get_path(in, &path, NULL));\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(json_in_resource, invalid_riid) {\n    static const char RESOURCES[] =\n            \"[ { \\\"n\\\": \\\"/5/0/3/65535\\\", \\\"v\\\": 42 } ]\";\n    TEST_ENV(RESOURCES, MAKE_RESOURCE_PATH(5, 0, 3));\n\n    anjay_uri_path_t path;\n    ASSERT_FAIL(_anjay_input_get_path(in, &path, NULL));\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(json_in_instance_permuted, resource_skipping) {\n    static const char RESOURCES[] = \"[ { \\\"v\\\": 42, \\\"n\\\": \\\"/13/26/1\\\" }, \"\n                                    \"{ \\\"v\\\": 43, \\\"n\\\": \\\"/13/26/2\\\" } ]\";\n    TEST_ENV(RESOURCES, TEST_INSTANCE_PATH);\n    test_skipping(in,\n                  (const anjay_uri_path_t[2]) { MAKE_RESOURCE_PATH(13, 26, 1),\n                                                MAKE_RESOURCE_PATH(13, 26, 2) },\n                  2);\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(json_in_instance, multiple_resource_skipping) {\n    static const char RESOURCES[] = \"[ { \\\"n\\\": \\\"/13/26/1/4\\\", \\\"v\\\": 42 }, \"\n                                    \"{ \\\"n\\\": \\\"/13/26/2/5\\\", \\\"v\\\": 43 } ]\";\n    TEST_ENV(RESOURCES, TEST_INSTANCE_PATH);\n    test_skipping(in,\n                  (const anjay_uri_path_t[2]) {\n                          MAKE_RESOURCE_INSTANCE_PATH(13, 26, 1, 4),\n                          MAKE_RESOURCE_INSTANCE_PATH(13, 26, 2, 5) },\n                  2);\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(json_in_object, with_single_instance_and_some_resources) {\n    static const char RESOURCES[] = \"[ { \\\"n\\\": \\\"/13/26/1\\\", \\\"v\\\": 42 }, \"\n                                    \"{ \\\"n\\\": \\\"/13/26/2\\\", \\\"v\\\": 43 } ]\";\n    TEST_ENV(RESOURCES, MAKE_OBJECT_PATH(13));\n    check_paths(in,\n                (const anjay_uri_path_t[2]) { MAKE_RESOURCE_PATH(13, 26, 1),\n                                              MAKE_RESOURCE_PATH(13, 26, 2) },\n                2);\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(json_in_object, invalid_oid) {\n    static const char RESOURCES[] = \"[ { \\\"n\\\": \\\"/65535/1/1\\\", \\\"v\\\": 42 } ]\";\n    TEST_ENV(RESOURCES, MAKE_ROOT_PATH());\n\n    anjay_uri_path_t path;\n    ASSERT_FAIL(_anjay_input_get_path(in, &path, NULL));\n\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(json_in_object, with_some_instances_and_some_resources) {\n    static const char RESOURCES[] = \"[ { \\\"n\\\": \\\"/13/26/1\\\", \\\"v\\\": 42 }, \"\n                                    \"{ \\\"n\\\": \\\"/13/26/2\\\", \\\"v\\\": 43 }, \"\n                                    \"{ \\\"n\\\": \\\"/13/27/3\\\", \\\"v\\\": 44 }, \"\n                                    \"{ \\\"n\\\": \\\"/13/27/4\\\", \\\"v\\\": 45 } ]\";\n    TEST_ENV(RESOURCES, MAKE_OBJECT_PATH(13));\n    check_paths(in,\n                (const anjay_uri_path_t[4]) { MAKE_RESOURCE_PATH(13, 26, 1),\n                                              MAKE_RESOURCE_PATH(13, 26, 2),\n                                              MAKE_RESOURCE_PATH(13, 27, 3),\n                                              MAKE_RESOURCE_PATH(13, 27, 4) },\n                4);\n    TEST_TEARDOWN(OK);\n}\n\n#define TEST_VALUE_ENV(TypeAndValue)                                           \\\n    static const char RESOURCE[] =                                             \\\n            \"[ { \\\"n\\\": \\\"/13/26/1\\\", \" TypeAndValue \" } ]\";                   \\\n    TEST_ENV(RESOURCE, MAKE_RESOURCE_PATH(13, 26, 1));                         \\\n    {                                                                          \\\n        anjay_uri_path_t path;                                                 \\\n        ASSERT_OK(_anjay_input_get_path(in, &path, NULL));                     \\\n        ASSERT_TRUE(                                                           \\\n                _anjay_uri_path_equal(&path, &MAKE_RESOURCE_PATH(13, 26, 1))); \\\n    }\n\nAVS_UNIT_TEST(json_in_value, string_with_zero_length_buffer) {\n    TEST_VALUE_ENV(\"\\\"vs\\\": \\\"foobar\\\"\");\n\n    char buf[16] = \"nothing\";\n    ASSERT_EQ(_anjay_get_string_unlocked(in, buf, 0), ANJAY_BUFFER_TOO_SHORT);\n    ASSERT_EQ(buf[0], 'n');\n\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(json_in_value, bytes_with_too_short_buffer) {\n    TEST_VALUE_ENV(\"\\\"vd\\\": \\\"Zm9vYmFy\\\"\"); // base64(foobar)\n\n    char buf[16] = \"nothing\";\n    size_t bytes_read;\n    bool message_finished;\n    ASSERT_OK(_anjay_get_bytes_unlocked(\n            in, &bytes_read, &message_finished, buf, 0));\n    ASSERT_EQ(bytes_read, 0);\n    ASSERT_EQ(message_finished, false);\n    ASSERT_EQ(buf[0], 'n');\n\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(json_in_value, double_as_i64_when_convertible) {\n    TEST_VALUE_ENV(\"\\\"v\\\": 3\");\n\n    int64_t value;\n    ASSERT_OK(_anjay_get_i64_unlocked(in, &value));\n    ASSERT_EQ(value, 3);\n\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(json_in_value, double_as_u64_when_convertible) {\n    TEST_VALUE_ENV(\"\\\"v\\\": 3\");\n\n    uint64_t value;\n    ASSERT_OK(_anjay_get_u64_unlocked(in, &value));\n    ASSERT_EQ(value, 3);\n\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(json_in_value, double_as_huge_u64_when_convertible) {\n    TEST_VALUE_ENV(\"\\\"v\\\": 1.844674407370955e19\");\n\n    uint64_t value;\n    ASSERT_OK(_anjay_get_u64_unlocked(in, &value));\n    ASSERT_EQ(value, UINT64_MAX - 2047);\n\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(json_in_value, double_as_i64_not_convertible) {\n    TEST_VALUE_ENV(\"\\\"v\\\": 3.1415926535\");\n\n    int64_t value;\n    ASSERT_FAIL(_anjay_get_i64_unlocked(in, &value));\n\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(json_in_value, objlnk_valid) {\n    TEST_VALUE_ENV(\"\\\"vlo\\\": \\\"32:42532\\\"\");\n\n    anjay_oid_t oid;\n    anjay_iid_t iid;\n    ASSERT_OK(_anjay_get_objlnk_unlocked(in, &oid, &iid));\n    ASSERT_EQ(oid, 32);\n    ASSERT_EQ(iid, 42532);\n\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(json_in_value, objlnk_with_trash_at_the_end) {\n    TEST_VALUE_ENV(\"\\\"vlo\\\": \\\"32:42foo\\\"\");\n\n    anjay_oid_t oid;\n    anjay_iid_t iid;\n    ASSERT_FAIL(_anjay_get_objlnk_unlocked(in, &oid, &iid));\n\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(json_in_value, objlnk_with_overflow) {\n    TEST_VALUE_ENV(\"\\\"vlo\\\": \\\"1:423444\\\"\");\n\n    anjay_oid_t oid;\n    anjay_iid_t iid;\n    ASSERT_FAIL(_anjay_get_objlnk_unlocked(in, &oid, &iid));\n\n    TEST_TEARDOWN(OK);\n}\n#undef TEST_VALUE_ENV\n\n#define TEST_VALUE_ENV(TypeAndValue)                         \\\n    static const char RESOURCE[] =                           \\\n            \"[ { \\\"n\\\": \\\"/13/26/1\\\", \" TypeAndValue \" } ]\"; \\\n    TEST_ENV(RESOURCE, MAKE_RESOURCE_PATH(13, 26, 1));\n\nAVS_UNIT_TEST(json_in, get_integer_before_get_id) {\n    TEST_VALUE_ENV(\"\\\"v\\\": 42\");\n    ASSERT_FAIL(_anjay_get_i64_unlocked(in, &(int64_t) { 0 }));\n    TEST_TEARDOWN(FAIL);\n}\n\nAVS_UNIT_TEST(json_in, get_float_before_get_id) {\n    TEST_VALUE_ENV(\"\\\"v\\\": 3.0\");\n    ASSERT_FAIL(_anjay_get_double_unlocked(in, &(double) { 0 }));\n    TEST_TEARDOWN(FAIL);\n}\n\nAVS_UNIT_TEST(json_in, get_bytes_before_get_id) {\n    TEST_VALUE_ENV(\"\\\"vd\\\": \\\"Zm9vYmFy\\\"\"); // base64(foobar)\n    ASSERT_FAIL(_anjay_get_bytes_unlocked(\n            in, &(size_t) { 0 }, &(bool) { false }, (char[32]){}, 32));\n    TEST_TEARDOWN(FAIL);\n}\n\nAVS_UNIT_TEST(json_in, get_string_before_get_id) {\n    TEST_VALUE_ENV(\"\\\"vs\\\": \\\"foobar\\\"\");\n    ASSERT_FAIL(_anjay_get_string_unlocked(in, (char[32]){}, 32));\n    TEST_TEARDOWN(FAIL);\n}\n\nAVS_UNIT_TEST(json_in, get_bool_before_get_id) {\n    TEST_VALUE_ENV(\"\\\"vb\\\": false\");\n    ASSERT_FAIL(_anjay_get_bool_unlocked(in, &(bool) { false }));\n    TEST_TEARDOWN(FAIL);\n}\n\nAVS_UNIT_TEST(json_in, get_objlnk_before_get_id) {\n    TEST_VALUE_ENV(\"\\\"vlo\\\": \\\"32:42532\\\"\");\n    ASSERT_FAIL(_anjay_get_objlnk_unlocked(\n            in, &(anjay_oid_t) { 0 }, &(anjay_iid_t) { 0 }));\n    TEST_TEARDOWN(FAIL);\n}\n\nAVS_UNIT_TEST(json_in, get_path_for_resource_instance_path) {\n    static const char RESOURCE_INSTANCE_PATH[] = \"[ { \\\"n\\\": \\\"/3/0/0/1\\\" } ]\";\n    TEST_ENV(RESOURCE_INSTANCE_PATH, MAKE_RESOURCE_INSTANCE_PATH(3, 0, 0, 1));\n    anjay_uri_path_t path;\n    ASSERT_OK(_anjay_input_get_path(in, &path, NULL));\n    ASSERT_TRUE(_anjay_uri_path_equal(\n            &path, &MAKE_RESOURCE_INSTANCE_PATH(3, 0, 0, 1)));\n    TEST_TEARDOWN(OK);\n}\n\n#define COMPOSITE_TEST_ENV(Data, Path)                               \\\n    avs_stream_inbuf_t stream = AVS_STREAM_INBUF_STATIC_INITIALIZER; \\\n    avs_stream_inbuf_set_buffer(&stream, Data, sizeof(Data) - 1);    \\\n    anjay_unlocked_input_ctx_t *in;                                  \\\n    ASSERT_OK(_anjay_input_json_composite_read_create(               \\\n            &in, (avs_stream_t *) &stream, &(Path)));\n\nAVS_UNIT_TEST(json_in_composite, composite_read_mode_additional_payload) {\n    static const char RESOURCE_INSTANCE_WITH_PAYLOAD[] =\n            \"[ { \\\"n\\\": \\\"/3/0/0/1\\\", \\\"v\\\": \\\"foo\\\" } ]\";\n    COMPOSITE_TEST_ENV(RESOURCE_INSTANCE_WITH_PAYLOAD, MAKE_ROOT_PATH());\n    anjay_uri_path_t path;\n    ASSERT_FAIL(_anjay_input_get_path(in, &path, NULL));\n    TEST_TEARDOWN(FAIL);\n}\n\n#undef COMPOSITE_TEST_ENV\n#undef TEST_VALUE_ENV\n#undef TEST_ENV\n"
  },
  {
    "path": "tests/core/io/lwm2m_cbor_in.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#include <avsystem/commons/avs_memory.h>\n#include <avsystem/commons/avs_stream_inbuf.h>\n#include <avsystem/commons/avs_vector.h>\n\n#define AVS_UNIT_ENABLE_SHORT_ASSERTS\n#include <avsystem/commons/avs_unit_test.h>\n\n#ifdef ANJAY_WITH_LWM2M_GATEWAY\n#    include <anjay/lwm2m_gateway.h>\n#    include <string.h>\n#endif // ANJAY_WITH_LWM2M_GATEWAY\n\n#define TEST_ENV(Data, Path)                                                \\\n    avs_stream_inbuf_t stream = AVS_STREAM_INBUF_STATIC_INITIALIZER;        \\\n    avs_stream_inbuf_set_buffer(&stream, Data, sizeof(Data) - 1);           \\\n    anjay_unlocked_input_ctx_t *in;                                         \\\n    ASSERT_OK(_anjay_input_lwm2m_cbor_create(&in, (avs_stream_t *) &stream, \\\n                                             &(Path)));\n\n#define TEST_TEARDOWN(ExpectedResult)                                       \\\n    do {                                                                    \\\n        AVS_CONCAT(ASSERT_, ExpectedResult)(_anjay_input_ctx_destroy(&in)); \\\n    } while (0)\n\n#ifdef ANJAY_WITH_LWM2M_GATEWAY\n#    define PATHS_EQUAL(Path1, Path2)        \\\n        (_anjay_uri_path_equal(Path1, Path2) \\\n         && _anjay_uri_path_prefix_equal(Path1, Path2))\n#else // ANJAY_WITH_LWM2M_GATEWAY\n#    define PATHS_EQUAL(Path1, Path2) _anjay_uri_path_equal(Path1, Path2)\n#endif // ANJAY_WITH_LWM2M_GATEWAY\n\nstatic const anjay_uri_path_t TEST_INSTANCE_PATH = MAKE_INSTANCE_PATH(13, 26);\n\nstatic const anjay_uri_path_t TEST_RESOURCE_PATH =\n        MAKE_RESOURCE_PATH(13, 26, 1);\n\nstatic const anjay_uri_path_t TEST_RESOURCE2_PATH =\n        MAKE_RESOURCE_PATH(13, 26, 2);\n\nstatic const anjay_uri_path_t TEST_RESOURCE_INSTANCE_PATH =\n        MAKE_RESOURCE_INSTANCE_PATH(13, 26, 1, 2);\n\nstatic const anjay_uri_path_t TEST_RESOURCE_INSTANCE2_PATH =\n        MAKE_RESOURCE_INSTANCE_PATH(13, 26, 1, 3);\n\nstatic const anjay_uri_path_t TEST_OTHER_OBJ_RESOURCE_PATH =\n        MAKE_RESOURCE_PATH(14, 27, 2);\n\nstatic void expect_path(anjay_unlocked_input_ctx_t *in,\n                        const anjay_uri_path_t *expected_path) {\n    anjay_uri_path_t path;\n    ASSERT_OK(_anjay_input_get_path(in, &path, NULL));\n    ASSERT_TRUE(PATHS_EQUAL(&path, expected_path));\n\n    // cached value\n    ASSERT_OK(_anjay_input_get_path(in, &path, NULL));\n    ASSERT_TRUE(PATHS_EQUAL(&path, expected_path));\n}\n\nstatic void expect_path_retrieval_failure(anjay_unlocked_input_ctx_t *in) {\n    anjay_uri_path_t path;\n    ASSERT_EQ(_anjay_input_get_path(in, &path, NULL), ANJAY_ERR_BAD_REQUEST);\n}\n\nstatic void pull_i64_value(anjay_unlocked_input_ctx_t *in, int64_t expected) {\n    int64_t value = -1;\n    ASSERT_OK(_anjay_get_i64_unlocked(in, &value));\n    ASSERT_EQ(value, expected);\n    ASSERT_OK(_anjay_input_next_entry(in));\n}\n\nstatic void pull_double_value(anjay_unlocked_input_ctx_t *in, double expected) {\n    double value = -1;\n    ASSERT_OK(_anjay_get_double_unlocked(in, &value));\n    ASSERT_EQ(value, expected);\n    ASSERT_OK(_anjay_input_next_entry(in));\n}\n\nstatic void pull_bool_true(anjay_unlocked_input_ctx_t *in) {\n    bool value = false;\n    ASSERT_OK(_anjay_get_bool_unlocked(in, &value));\n    ASSERT_TRUE(value);\n    ASSERT_OK(_anjay_input_next_entry(in));\n}\n\nstatic void pull_bytes_value(anjay_unlocked_input_ctx_t *in,\n                             const void *expected,\n                             size_t expected_size) {\n    void *buf = calloc(1, expected_size);\n    ASSERT_NOT_NULL(buf);\n    size_t bytes_read = 0;\n    bool message_finished = false;\n    ASSERT_OK(_anjay_get_bytes_unlocked(in, &bytes_read, &message_finished, buf,\n                                        expected_size));\n    ASSERT_TRUE(message_finished);\n    ASSERT_EQ(bytes_read, expected_size);\n    ASSERT_EQ_BYTES_SIZED(buf, expected, expected_size);\n    ASSERT_OK(_anjay_input_next_entry(in));\n    free(buf);\n}\n\nstatic void expect_whole_input_consumed(anjay_unlocked_input_ctx_t *in) {\n    ASSERT_EQ(_anjay_input_get_path(in, NULL, NULL), ANJAY_GET_PATH_END);\n    ASSERT_EQ(_anjay_json_like_decoder_state(((lwm2m_cbor_in_t *) in)->ctx),\n              ANJAY_JSON_LIKE_DECODER_STATE_FINISHED);\n}\n\nstatic void test_single_i64_resource(anjay_unlocked_input_ctx_t *in,\n                                     const anjay_uri_path_t *expected_path,\n                                     int64_t expected_value) {\n    expect_path(in, expected_path);\n    pull_i64_value(in, expected_value);\n    expect_whole_input_consumed(in);\n}\n\nstatic void test_single_double_resource(anjay_unlocked_input_ctx_t *in,\n                                        const anjay_uri_path_t *expected_path,\n                                        double expected_value) {\n    expect_path(in, expected_path);\n    pull_double_value(in, expected_value);\n    expect_whole_input_consumed(in);\n}\n\nstatic void test_single_bytes_resource(anjay_unlocked_input_ctx_t *in,\n                                       const anjay_uri_path_t *expected_path,\n                                       const void *expected_value,\n                                       size_t expected_size) {\n    expect_path(in, expected_path);\n    pull_bytes_value(in, expected_value, expected_size);\n    expect_whole_input_consumed(in);\n}\n\nstatic void test_two_i64_resources(anjay_unlocked_input_ctx_t *in,\n                                   const anjay_uri_path_t *first_expected_path,\n                                   int64_t first_expected_value,\n                                   const anjay_uri_path_t *second_expected_path,\n                                   int64_t second_expected_value) {\n    expect_path(in, first_expected_path);\n    pull_i64_value(in, first_expected_value);\n    expect_path(in, second_expected_path);\n    pull_i64_value(in, second_expected_value);\n    expect_whole_input_consumed(in);\n}\n\nAVS_UNIT_TEST(lwm2m_cbor_in_resource, single_resource) {\n    // {[13, 26, 1]: 42}\n    static const char DATA[] = { \"\\xA1\\x83\\x0D\\x18\\x1A\\x01\\x18\\x2A\" };\n    TEST_ENV(DATA, TEST_RESOURCE_PATH);\n    test_single_i64_resource(in, &TEST_RESOURCE_PATH, 42);\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(lwm2m_cbor_in_resource, single_resource_bool) {\n    // {[13, 26, 1]: true}\n    static const char DATA[] = { \"\\xA1\\x83\\x0D\\x18\\x1A\\x01\\xF5\" };\n    TEST_ENV(DATA, TEST_RESOURCE_PATH);\n    expect_path(in, &TEST_RESOURCE_PATH);\n    pull_bool_true(in);\n    expect_whole_input_consumed(in);\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(lwm2m_cbor_in_resource, single_resource_indefinite) {\n    // {_ [_ 13, 26, 1]: 42}\n    static const char DATA[] = { \"\\xBF\\x9F\\x0D\\x18\\x1A\\x01\\xFF\\x18\\x2A\\xFF\" };\n    TEST_ENV(DATA, TEST_RESOURCE_PATH);\n    test_single_i64_resource(in, &TEST_RESOURCE_PATH, 42);\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(lwm2m_cbor_in_resource, single_resource_nested) {\n    // {13: {26: {1: 42}}}\n    static const char DATA[] = { \"\\xA1\\x0D\\xA1\\x18\\x1A\\xA1\\x01\\x18\\x2A\" };\n    TEST_ENV(DATA, TEST_RESOURCE_PATH);\n    test_single_i64_resource(in, &TEST_RESOURCE_PATH, 42);\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(lwm2m_cbor_in_resource, single_resource_nested_arrays) {\n    // {[13]: {[26]: {[1]: 42}}}\n    static const char DATA[] = {\n        \"\\xA1\\x81\\x0D\\xA1\\x81\\x18\\x1A\\xA1\\x81\\x01\\x18\\x2A\"\n    };\n    TEST_ENV(DATA, TEST_RESOURCE_PATH);\n    test_single_i64_resource(in, &TEST_RESOURCE_PATH, 42);\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(lwm2m_cbor_in_resource,\n              single_resource_nested_arrays_all_indefinite) {\n    // {_ [_ 13]: {_ [_ 26]: {_ [_ 1]: 42}}}\n    static const char DATA[] = { \"\\xBF\\x9F\\x0D\\xFF\\xBF\\x9F\\x18\\x1A\\xFF\\xBF\\x9F\"\n                                 \"\\x01\\xFF\\x18\\x2A\\xFF\\xFF\\xFF\" };\n    TEST_ENV(DATA, TEST_RESOURCE_PATH);\n    test_single_i64_resource(in, &TEST_RESOURCE_PATH, 42);\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(lwm2m_cbor_in_resource_instance, max_possible_nesting) {\n    // Uses decimal fraction\n    // {[13]: {[26]: {[1]: {[2]: 4([-1, 45])}}}}\n    static const char DATA[] = { \"\\xA1\\x81\\x0D\\xA1\\x81\\x18\\x1A\\xA1\\x81\\x01\"\n                                 \"\\xA1\\x81\\x02\\xC4\\x82\\x20\\x18\\x2D\" };\n    TEST_ENV(DATA, TEST_RESOURCE_INSTANCE_PATH);\n    test_single_double_resource(in, &TEST_RESOURCE_INSTANCE_PATH, 4.5);\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(lwm2m_cbor_in_resource, two_resources_1) {\n    // {[13, 26]: {1: 42, 2: 21}}\n    static const char DATA[] = {\n        \"\\xA1\\x82\\x0D\\x18\\x1A\\xA2\\x01\\x18\\x2A\\x02\\x15\"\n    };\n    TEST_ENV(DATA, TEST_INSTANCE_PATH);\n    test_two_i64_resources(in, &TEST_RESOURCE_PATH, 42, &TEST_RESOURCE2_PATH,\n                           21);\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(lwm2m_cbor_in_resource, two_resources_1_repeated_next_entry) {\n    // {[13, 26]: {1: 42, 2: 21}}\n    static const char DATA[] = {\n        \"\\xA1\\x82\\x0D\\x18\\x1A\\xA2\\x01\\x18\\x2A\\x02\\x15\"\n    };\n    TEST_ENV(DATA, TEST_INSTANCE_PATH);\n    expect_path(in, &TEST_RESOURCE_PATH);\n    pull_i64_value(in, 42);\n    ASSERT_OK(_anjay_input_next_entry(in));\n    ASSERT_OK(_anjay_input_next_entry(in));\n    ASSERT_OK(_anjay_input_next_entry(in));\n    expect_path(in, &TEST_RESOURCE2_PATH);\n    pull_i64_value(in, 21);\n    expect_whole_input_consumed(in);\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(lwm2m_cbor_in_resource, two_resources_2) {\n    // {[13, 26, 1]: 42, [13, 26, 2]: 21}\n    static const char DATA[] = {\n        \"\\xA2\\x83\\x0D\\x18\\x1A\\x01\\x18\\x2A\\x83\\x0D\\x18\\x1A\\x02\\x15\"\n    };\n    TEST_ENV(DATA, TEST_INSTANCE_PATH);\n    test_two_i64_resources(in, &TEST_RESOURCE_PATH, 42, &TEST_RESOURCE2_PATH,\n                           21);\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(lwm2m_cbor_in_resource, two_resources_3) {\n    // {[13, 26]: {1: 42}, [13, 26, 2]: 21}\n    static const char DATA[] = {\n        \"\\xA2\\x82\\x0D\\x18\\x1A\\xA1\\x01\\x18\\x2A\\x83\\x0D\\x18\\x1A\\x02\\x15\"\n    };\n    TEST_ENV(DATA, TEST_INSTANCE_PATH);\n    test_two_i64_resources(in, &TEST_RESOURCE_PATH, 42, &TEST_RESOURCE2_PATH,\n                           21);\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(lwm2m_cbor_in_resource, two_resources_4) {\n    // {[13, 26, 1]: 42, [13, 26]: {[2]: 21}}\n    static const char DATA[] = {\n        \"\\xA2\\x83\\x0D\\x18\\x1A\\x01\\x18\\x2A\\x82\\x0D\\x18\\x1A\\xA1\\x81\\x02\\x15\"\n    };\n    TEST_ENV(DATA, TEST_INSTANCE_PATH);\n    test_two_i64_resources(in, &TEST_RESOURCE_PATH, 42, &TEST_RESOURCE2_PATH,\n                           21);\n    TEST_TEARDOWN(OK);\n}\n\n#define CHUNK1 \"\\x00\\x11\\x22\\x33\\x44\\x55\\x66\\x77\"\n#define CHUNK2 \"\\x88\\x99\\xAA\\xBB\\xCC\\xDD\\xEE\\xFF\"\n#define TEST_BYTES CHUNK1 CHUNK2\n\nAVS_UNIT_TEST(lwm2m_cbor_in_resource, bytes) {\n    // {[13, 26, 1]: h'00112233445566778899AABBCCDDEEFF'}\n    static const char DATA[] = { \"\\xA1\\x83\\x0D\\x18\\x1A\\x01\\x50\" TEST_BYTES };\n    TEST_ENV(DATA, TEST_RESOURCE_PATH);\n    test_single_bytes_resource(in, &TEST_RESOURCE_PATH, TEST_BYTES,\n                               sizeof(TEST_BYTES) - 1);\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(lwm2m_cbor_in_resource, bytes_indefinite) {\n    // {[13, 26, 1]: (_ h'0011223344556677', h'8899AABBCCDDEEFF')}\n    static const char DATA[] = { \"\\xA1\\x83\\x0D\\x18\\x1A\\x01\\x5F\\x48\" CHUNK1\n                                 \"\\x48\" CHUNK2 \"\\xFF\" };\n\n    TEST_ENV(DATA, TEST_RESOURCE_PATH);\n    test_single_bytes_resource(in, &TEST_RESOURCE_PATH, TEST_BYTES,\n                               sizeof(TEST_BYTES) - 1);\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(lwm2m_cbor_in_resource, bytes_indefinite_and_int) {\n    // {[13, 26]: {1: (_ h'0011223344556677', h'8899AABBCCDDEEFF'), 2: 7}}\n    static const char DATA[] = { \"\\xA1\\x82\\x0D\\x18\\x1A\\xA2\\x01\\x5F\\x48\" CHUNK1\n                                 \"\\x48\" CHUNK2 \"\\xFF\\x02\\x07\" };\n\n    TEST_ENV(DATA, TEST_INSTANCE_PATH);\n    expect_path(in, &TEST_RESOURCE_PATH);\n    pull_bytes_value(in, TEST_BYTES, sizeof(TEST_BYTES) - 1);\n    expect_path(in, &TEST_RESOURCE2_PATH);\n    pull_i64_value(in, 7);\n    expect_whole_input_consumed(in);\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(lwm2m_cbor_in_resource, bytes_indefinite_small_gets) {\n    // {[13, 26, 1]: (_ h'0011223344556677', h'8899AABBCCDDEEFF')}\n    static const char DATA[] = { \"\\xA1\\x83\\x0D\\x18\\x1A\\x01\\x5F\\x48\" CHUNK1\n                                 \"\\x48\" CHUNK2 \"\\xFF\" };\n\n    TEST_ENV(DATA, TEST_RESOURCE_PATH);\n    expect_path(in, &TEST_RESOURCE_PATH);\n\n    size_t bytes_to_read = sizeof(TEST_BYTES) - 1;\n    void *buf = calloc(1, bytes_to_read);\n    ASSERT_NOT_NULL(buf);\n\n    bool message_finished = false;\n    size_t total_bytes_read = 0;\n\n    while (!message_finished) {\n        size_t bytes_read;\n        ASSERT_OK(_anjay_get_bytes_unlocked(\n                in,\n                &bytes_read,\n                &message_finished,\n                buf + total_bytes_read,\n                AVS_MIN(bytes_to_read - total_bytes_read, 3)));\n        total_bytes_read += bytes_read;\n    }\n\n    ASSERT_EQ_BYTES_SIZED(buf, TEST_BYTES, bytes_to_read);\n\n    ASSERT_OK(_anjay_input_next_entry(in));\n    free(buf);\n\n    expect_whole_input_consumed(in);\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(lwm2m_cbor_in_resource, bytes_split) {\n    // {[13, 26, 1]: h'00112233445566778899AABBCCDDEEFF'}\n    static const char DATA[] = { \"\\xA1\\x83\\x0D\\x18\\x1A\\x01\\x50\" TEST_BYTES };\n\n    TEST_ENV(DATA, TEST_RESOURCE_PATH);\n    expect_path(in, &TEST_RESOURCE_PATH);\n\n    const size_t bytes_to_read = sizeof(TEST_BYTES) - 1;\n    char *buf = calloc(1, bytes_to_read);\n    ASSERT_NOT_NULL(buf);\n\n    size_t bytes_read = 0;\n    bool message_finished = true;\n    ASSERT_OK(_anjay_get_bytes_unlocked(in, &bytes_read, &message_finished, buf,\n                                        bytes_to_read / 2));\n    ASSERT_FALSE(message_finished);\n    ASSERT_EQ(bytes_read, bytes_to_read / 2);\n\n    ASSERT_OK(_anjay_get_bytes_unlocked(in,\n                                        &bytes_read,\n                                        &message_finished,\n                                        buf + bytes_read,\n                                        bytes_to_read / 2));\n    ASSERT_TRUE(message_finished);\n    ASSERT_EQ(bytes_read, bytes_to_read / 2);\n    ASSERT_EQ_BYTES_SIZED(buf, TEST_BYTES, bytes_to_read);\n\n    ASSERT_OK(_anjay_input_next_entry(in));\n    free(buf);\n\n    expect_whole_input_consumed(in);\n    TEST_TEARDOWN(OK);\n}\n\n#undef TEST_BYTES\n#undef CHUNK1\n#undef CHUNK2\n\n#define TEST_STRING_PART1 \"c--cossi\"\n#define TEST_STRING_PART2 \"ezepsulo\"\n#define TEST_STRING TEST_STRING_PART1 TEST_STRING_PART2\n\nAVS_UNIT_TEST(lwm2m_cbor_in_resource, string) {\n    // {[13, 26, 1]: \"c--cossiezepsulo\"}\n    static const char DATA[] = { \"\\xA1\\x83\\x0D\\x18\\x1A\\x01\\x70\" TEST_STRING };\n\n    TEST_ENV(DATA, TEST_RESOURCE_PATH);\n    expect_path(in, &TEST_RESOURCE_PATH);\n\n    const size_t bytes_to_read = sizeof(TEST_STRING);\n    void *buf = malloc(bytes_to_read);\n    ASSERT_NOT_NULL(buf);\n    memset(buf, 21, bytes_to_read);\n\n    ASSERT_OK(_anjay_get_string_unlocked(in, buf, bytes_to_read));\n    ASSERT_EQ_STR(buf, TEST_STRING);\n\n    ASSERT_OK(_anjay_input_next_entry(in));\n    free(buf);\n\n    expect_whole_input_consumed(in);\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(lwm2m_cbor_in_resource, string_split) {\n    // {[13, 26, 1]: \"c--cossiezepsulo\"}\n    static const char DATA[] = { \"\\xA1\\x83\\x0D\\x18\\x1A\\x01\\x70\" TEST_STRING };\n\n    TEST_ENV(DATA, TEST_RESOURCE_PATH);\n    expect_path(in, &TEST_RESOURCE_PATH);\n\n    const size_t bytes_to_read = sizeof(TEST_STRING) / 2 + 1;\n    void *buf = malloc(bytes_to_read);\n    ASSERT_NOT_NULL(buf);\n    memset(buf, 21, bytes_to_read);\n\n    ASSERT_EQ(_anjay_get_string_unlocked(in, buf, bytes_to_read),\n              ANJAY_BUFFER_TOO_SHORT);\n    ASSERT_EQ_STR(buf, TEST_STRING_PART1);\n    memset(buf, 21, bytes_to_read);\n\n    ASSERT_OK(_anjay_get_string_unlocked(in, buf, bytes_to_read));\n    ASSERT_EQ_STR(buf, TEST_STRING_PART2);\n\n    ASSERT_OK(_anjay_input_next_entry(in));\n    free(buf);\n\n    expect_whole_input_consumed(in);\n    TEST_TEARDOWN(OK);\n}\n\n#undef TEST_STRING\n#undef TEST_STRING_PART1\n#undef TEST_STRING_PART2\n\nAVS_UNIT_TEST(lwm2m_cbor_in_resource_instance, null_and_int) {\n    // {[13, 26, 1]: {0: null, 1: 5}}\n    static const char DATA[] = {\n        \"\\xA1\\x83\\x0D\\x18\\x1A\\x01\\xA2\\x02\\xF6\\x03\\x05\"\n    };\n\n    TEST_ENV(DATA, TEST_RESOURCE_PATH);\n\n    expect_path(in, &TEST_RESOURCE_INSTANCE_PATH);\n    ASSERT_OK(_anjay_input_get_null(in));\n    ASSERT_OK(_anjay_input_next_entry(in));\n\n    expect_path(in, &TEST_RESOURCE_INSTANCE2_PATH);\n    pull_i64_value(in, 5);\n\n    expect_whole_input_consumed(in);\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(lwm2m_cbor_in_resource_instance, try_null_then_int) {\n    // {[13, 26, 1, 2]: 5}\n    static const char DATA[] = { \"\\xA1\\x84\\x0D\\x18\\x1A\\x01\\x02\\x05\" };\n\n    TEST_ENV(DATA, TEST_RESOURCE_INSTANCE_PATH);\n\n    expect_path(in, &TEST_RESOURCE_INSTANCE_PATH);\n    ASSERT_FAIL(_anjay_input_get_null(in));\n    pull_i64_value(in, 5);\n\n    expect_whole_input_consumed(in);\n    TEST_TEARDOWN(OK);\n}\n\nstatic void expect_resource_path(anjay_unlocked_input_ctx_t *ctx,\n                                 uint16_t rid) {\n    expect_path(ctx, &MAKE_RESOURCE_PATH(13, 26, rid));\n}\n\nAVS_UNIT_TEST(lwm2m_cbor_in_resource, all_types) {\n    // It's important to duplicate some type at the end to ensure that nesting\n    // of the paths works correctly for all types.\n    // {[13, 26]: {1: 1, 2: -1, 3: 2.5, 4: \"test\", 5: h'11223344', 6: \"12:34\",\n    // 7: 1}}\n    static const char DATA[] = {\n        \"\\xA1\\x82\\x0D\\x18\\x1A\\xA7\\x01\\x01\\x02\\x20\\x03\\xF9\\x41\\x00\\x04\\x64\\x74\"\n        \"\\x65\\x73\\x74\\x05\\x44\\x11\\x22\\x33\\x44\\x06\\x65\\x31\\x32\\x3A\\x33\\x34\\x07\"\n        \"\\x01\"\n    };\n\n    TEST_ENV(DATA, TEST_INSTANCE_PATH);\n\n    expect_resource_path(in, 1);\n    uint64_t u64 = 0;\n    ASSERT_OK(_anjay_get_u64_unlocked(in, &u64));\n    ASSERT_EQ(u64, 1);\n    ASSERT_OK(_anjay_input_next_entry(in));\n\n    expect_resource_path(in, 2);\n    int64_t i64 = 0;\n    ASSERT_OK(_anjay_get_i64_unlocked(in, &i64));\n    ASSERT_EQ(i64, -1);\n    ASSERT_OK(_anjay_input_next_entry(in));\n\n    expect_resource_path(in, 3);\n    double f = 0;\n    ASSERT_OK(_anjay_get_double_unlocked(in, &f));\n    ASSERT_EQ(f, 2.5);\n    ASSERT_OK(_anjay_input_next_entry(in));\n\n    expect_resource_path(in, 4);\n    char str[sizeof(\"test\")];\n    ASSERT_OK(_anjay_get_string_unlocked(in, str, sizeof(str)));\n    ASSERT_EQ_STR(str, \"test\");\n    ASSERT_OK(_anjay_input_next_entry(in));\n\n    expect_resource_path(in, 5);\n    char buf[sizeof(\"\\x11\\x22\\x33\\x44\") - 1];\n    size_t bytes_read = 0;\n    bool message_finished = false;\n    ASSERT_OK(_anjay_get_bytes_unlocked(in, &bytes_read, &message_finished, buf,\n                                        sizeof(buf)));\n    ASSERT_EQ_BYTES_SIZED(buf, \"\\x11\\x22\\x33\\x44\", sizeof(buf));\n    ASSERT_OK(_anjay_input_next_entry(in));\n\n    expect_resource_path(in, 6);\n    anjay_oid_t oid = 0;\n    anjay_iid_t iid = 0;\n    ASSERT_OK(_anjay_get_objlnk_unlocked(in, &oid, &iid));\n    ASSERT_EQ(oid, 12);\n    ASSERT_EQ(iid, 34);\n    ASSERT_OK(_anjay_input_next_entry(in));\n\n    expect_resource_path(in, 7);\n    u64 = 0;\n    ASSERT_OK(_anjay_get_u64_unlocked(in, &u64));\n    ASSERT_EQ(u64, 1);\n    ASSERT_OK(_anjay_input_next_entry(in));\n\n    expect_whole_input_consumed(in);\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(lwm2m_cbor_in_resource, composite) {\n    // {13: {26: {1: 1}}, 14: {27: {2: 2}}}\n    static const char DATA[] = {\n        \"\\xA2\\x0D\\xA1\\x18\\x1A\\xA1\\x01\\x01\\x0E\\xA1\\x18\\x1B\\xA1\\x02\\x02\"\n    };\n\n    TEST_ENV(DATA, MAKE_ROOT_PATH());\n    test_two_i64_resources(in, &TEST_RESOURCE_PATH, 1,\n                           &TEST_OTHER_OBJ_RESOURCE_PATH, 2);\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(lwm2m_cbor_in_resource, composite_indefinite_maps) {\n    // {_ 13: {_ 26: {_ 1: 1}}, 14: {_ 27: {_ 2: 2}}}\n    static const char DATA[] = {\n        \"\\xBF\\x0D\\xBF\\x18\\x1A\\xBF\\x01\\x01\\xFF\\xFF\\x0E\\xBF\\x18\\x1B\\xBF\\x02\\x02\"\n        \"\\xFF\\xFF\\xFF\"\n    };\n\n    TEST_ENV(DATA, MAKE_ROOT_PATH());\n    test_two_i64_resources(in, &TEST_RESOURCE_PATH, 1,\n                           &TEST_OTHER_OBJ_RESOURCE_PATH, 2);\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(lwm2m_cbor_in_resource, composite_indefinite_maps_and_arrays) {\n    // {_ [_ 13]: {_ [_ 26]: {_ [_ 1]: 1}}, [_ 14]: {_ [_ 27]: {_ [_ 2]: 2}}}\n    static const char DATA[] = {\n        \"\\xBF\\x9F\\x0D\\xFF\\xBF\\x9F\\x18\\x1A\\xFF\\xBF\\x9F\\x01\\xFF\\x01\\xFF\\xFF\\x9F\"\n        \"\\x0E\\xFF\\xBF\\x9F\\x18\\x1B\\xFF\\xBF\\x9F\\x02\\xFF\\x02\\xFF\\xFF\\xFF\"\n    };\n\n    TEST_ENV(DATA, MAKE_ROOT_PATH());\n    test_two_i64_resources(in, &TEST_RESOURCE_PATH, 1,\n                           &TEST_OTHER_OBJ_RESOURCE_PATH, 2);\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(lwm2m_cbor_in, path_id_too_big_solo) {\n    // {[13, 26]: {65536: 5}}\n    static const char DATA[] = {\n        \"\\xA1\\x82\\x0D\\x18\\x1A\\xA1\\x1A\\x00\\x01\\x00\\x00\\x05\"\n    };\n\n    TEST_ENV(DATA, TEST_INSTANCE_PATH);\n    expect_path_retrieval_failure(in);\n    TEST_TEARDOWN(FAIL);\n}\n\nAVS_UNIT_TEST(lwm2m_cbor_in, path_id_too_big_array) {\n    // {[13, 26, 65536]: 5}\n    static const char DATA[] = {\n        \"\\xA1\\x83\\x0D\\x18\\x1A\\x1A\\x00\\x01\\x00\\x00\\x05\"\n    };\n\n    TEST_ENV(DATA, TEST_INSTANCE_PATH);\n    expect_path_retrieval_failure(in);\n    TEST_TEARDOWN(FAIL);\n}\n\nAVS_UNIT_TEST(lwm2m_cbor_in, path_too_long_1) {\n    // {[13, 26, 3, 4, 5]: 5}\n    static const char DATA[] = { \"\\xA1\\x85\\x0D\\x18\\x1A\\x03\\x04\\x05\\x05\" };\n\n    TEST_ENV(DATA, TEST_INSTANCE_PATH);\n    expect_path_retrieval_failure(in);\n    TEST_TEARDOWN(FAIL);\n}\n\nAVS_UNIT_TEST(lwm2m_cbor_in, path_too_long_2) {\n    // {[13, 26, 1]: {2: 5, [3, 4]: 6}}\n    static const char DATA[] = {\n        \"\\xA1\\x83\\x0D\\x18\\x1A\\x01\\xA2\\x02\\x05\\x82\\x03\\x04\\x05\"\n    };\n\n    TEST_ENV(DATA, TEST_RESOURCE_INSTANCE_PATH);\n\n    expect_path(in, &TEST_RESOURCE_INSTANCE_PATH);\n    pull_i64_value(in, 5);\n    expect_path_retrieval_failure(in);\n\n    TEST_TEARDOWN(FAIL);\n}\n\nAVS_UNIT_TEST(lwm2m_cbor_in, path_too_long_3) {\n    // {13: {26: {1: {2: {3: 4}}}}}\n    static const char DATA[] = {\n        \"\\xA1\\x0D\\xA1\\x18\\x1A\\xA1\\x01\\xA1\\x02\\xA1\\x03\\x04\"\n    };\n\n    TEST_ENV(DATA, TEST_RESOURCE_INSTANCE_PATH);\n    expect_path_retrieval_failure(in);\n    TEST_TEARDOWN(FAIL);\n}\n\nAVS_UNIT_TEST(lwm2m_cbor_in, path_with_empty_array_keys) {\n    // {13: {[]: 26}, {[]: 1}: 42}\n    static const char DATA[] = {\n        \"\\xA2\\x0D\\xA1\\x80\\x18\\x1A\\xA1\\x80\\x01\\x18\\x2A\"\n    };\n\n    TEST_ENV(DATA, MAKE_ROOT_PATH());\n    expect_path_retrieval_failure(in);\n    TEST_TEARDOWN(FAIL);\n}\n\nAVS_UNIT_TEST(lwm2m_cbor_in, path_outside_base_1) {\n    // {[13, 26, 1]: 42}\n    static const char DATA[] = { \"\\xA1\\x83\\x0D\\x18\\x1A\\x01\\x18\\x2A\" };\n    TEST_ENV(DATA, MAKE_OBJECT_PATH(14));\n    expect_path_retrieval_failure(in);\n    TEST_TEARDOWN(FAIL);\n}\n\nAVS_UNIT_TEST(lwm2m_cbor_in, path_outside_base_2) {\n    // {[13, 26, 1]: 42}\n    static const char DATA[] = { \"\\xA1\\x83\\x0D\\x18\\x1A\\x01\\x18\\x2A\" };\n    TEST_ENV(DATA, MAKE_INSTANCE_PATH(13, 27));\n    expect_path_retrieval_failure(in);\n    TEST_TEARDOWN(FAIL);\n}\n\nAVS_UNIT_TEST(lwm2m_cbor_in, path_outside_base_3) {\n    // {[13, 26, 1]: 42}\n    static const char DATA[] = { \"\\xA1\\x83\\x0D\\x18\\x1A\\x01\\x18\\x2A\" };\n    TEST_ENV(DATA, MAKE_RESOURCE_PATH(13, 26, 2));\n    expect_path_retrieval_failure(in);\n    TEST_TEARDOWN(FAIL);\n}\n\nAVS_UNIT_TEST(lwm2m_cbor_in, path_outside_base_4) {\n    // {[13, 26, 1]: 42}\n    static const char DATA[] = { \"\\xA1\\x83\\x0D\\x18\\x1A\\x01\\x18\\x2A\" };\n    TEST_ENV(DATA, MAKE_RESOURCE_INSTANCE_PATH(13, 26, 1, 69));\n    expect_path_retrieval_failure(in);\n    TEST_TEARDOWN(FAIL);\n}\n\nAVS_UNIT_TEST(lwm2m_cbor_in, path_unexpected_type_1) {\n    // {[13, 0.26, 1]: 42}\n    static const char DATA[] = {\n        \"\\xA1\\x83\\x0D\\xFB\\x3F\\xD0\\xA3\\xD7\\x0A\\x3D\\x70\\xA4\\x01\\x18\\x2A\"\n    };\n    TEST_ENV(DATA, MAKE_ROOT_PATH());\n    expect_path_retrieval_failure(in);\n    TEST_TEARDOWN(FAIL);\n}\n\nAVS_UNIT_TEST(lwm2m_cbor_in, path_unexpected_type_2) {\n    // {0.13: {[26, 1]: 42}}\n    static const char DATA[] = { \"\\xA1\\xFB\\x3F\\xC0\\xA3\\xD7\\x0A\\x3D\\x70\\xA4\\xA1\"\n                                 \"\\x82\\x18\\x1A\\x01\\x18\\x2A\" };\n    TEST_ENV(DATA, MAKE_ROOT_PATH());\n    expect_path_retrieval_failure(in);\n    TEST_TEARDOWN(FAIL);\n}\n\nAVS_UNIT_TEST(lwm2m_cbor_in, path_unexpected_type_3) {\n    // {13: {\"26\": {1: 42}}}\n    static const char DATA[] = { \"\\xA1\\x0D\\xA1\\x62\\x32\\x36\\xA1\\x01\\x18\\x2A\" };\n    TEST_ENV(DATA, MAKE_ROOT_PATH());\n    expect_path_retrieval_failure(in);\n    TEST_TEARDOWN(FAIL);\n}\n\nAVS_UNIT_TEST(lwm2m_cbor_in, not_a_map_int) {\n    // 0\n    static const char DATA[] = { \"\\x00\" };\n    avs_stream_inbuf_t stream = AVS_STREAM_INBUF_STATIC_INITIALIZER;\n    avs_stream_inbuf_set_buffer(&stream, DATA, sizeof(DATA) - 1);\n    anjay_unlocked_input_ctx_t *in;\n    ASSERT_FAIL(_anjay_input_lwm2m_cbor_create(&in, (avs_stream_t *) &stream,\n                                               &(MAKE_ROOT_PATH())));\n}\n\nAVS_UNIT_TEST(lwm2m_cbor_in, not_a_map_array) {\n    // [1, 2, 3, 4]\n    static const char DATA[] = { \"\\x84\\x01\\x02\\x03\\x04\" };\n    avs_stream_inbuf_t stream = AVS_STREAM_INBUF_STATIC_INITIALIZER;\n    avs_stream_inbuf_set_buffer(&stream, DATA, sizeof(DATA) - 1);\n    anjay_unlocked_input_ctx_t *in;\n    ASSERT_FAIL(_anjay_input_lwm2m_cbor_create(&in, (avs_stream_t *) &stream,\n                                               &(MAKE_ROOT_PATH())));\n}\n\nAVS_UNIT_TEST(lwm2m_cbor_in, path_lack_unexpected_array) {\n    // {13: {26: [{1: 42}]}}\n    static const char DATA[] = { \"\\xA1\\x0D\\xA1\\x18\\x1A\\x81\\xA1\\x01\\x18\\x2A\" };\n    TEST_ENV(DATA, MAKE_ROOT_PATH());\n    expect_path_retrieval_failure(in);\n    TEST_TEARDOWN(FAIL);\n}\n\nAVS_UNIT_TEST(lwm2m_cbor_in, path_sudden_nested_map_end) {\n    // {[13, 26]: {1: 42}} - cut in the middle, see below\n    static const char DATA[] = {\n        \"\\xA1\"     // map, 1 pair\n        \"\\x82\"     //   array, 2 elements\n        \"\\x0D\"     //     unsigned 13\n        \"\\x18\\x1A\" //     unsigned 26\n        \"\\xA1\"     //   map, 1 pair\n        // 2 elements should follow, but for the test we cut the input here\n    };\n    TEST_ENV(DATA, MAKE_ROOT_PATH());\n    expect_path_retrieval_failure(in);\n    TEST_TEARDOWN(OK);\n}\n\n#ifdef ANJAY_WITH_LWM2M_GATEWAY\n#    define PREFIXED_URI(Prefix, BasePathVar)                         \\\n        const anjay_uri_path_t AVS_CONCAT(Prefix, _, BasePathVar) = { \\\n            .ids = {                                                  \\\n                [ANJAY_ID_OID] = BasePathVar.ids[ANJAY_ID_OID],       \\\n                [ANJAY_ID_IID] = BasePathVar.ids[ANJAY_ID_IID],       \\\n                [ANJAY_ID_RID] = BasePathVar.ids[ANJAY_ID_RID],       \\\n                [ANJAY_ID_RIID] = BasePathVar.ids[ANJAY_ID_RIID],     \\\n            },                                                        \\\n            .prefix = #Prefix                                         \\\n        }\n\nstatic PREFIXED_URI(dev1, TEST_INSTANCE_PATH);\nstatic PREFIXED_URI(dev1, TEST_RESOURCE_PATH);\nstatic PREFIXED_URI(dev1, TEST_RESOURCE2_PATH);\nstatic PREFIXED_URI(dev1, TEST_RESOURCE_INSTANCE_PATH);\nstatic PREFIXED_URI(dev1, TEST_OTHER_OBJ_RESOURCE_PATH);\nstatic PREFIXED_URI(dev2, TEST_RESOURCE_PATH);\n\n#    undef PREFIXED_URI\n\nAVS_UNIT_TEST(lwm2m_cbor_in_resource, gw_single_resource) {\n    // {[\"dev1\", 13, 26, 1]: 42}\n    static const char DATA[] = {\n        \"\\xA1\\x84\\x64\\x64\\x65\\x76\\x31\\x0D\\x18\\x1A\\x01\\x18\\x2A\"\n    };\n    TEST_ENV(DATA, dev1_TEST_RESOURCE_PATH);\n    test_single_i64_resource(in, &dev1_TEST_RESOURCE_PATH, 42);\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(lwm2m_cbor_in_resource, gw_single_resource_indefinite) {\n    // {_ [_ \"dev1\", 13, 26, 1]: 42}\n    static const char DATA[] = {\n        \"\\xBF\\x9F\\x64\\x64\\x65\\x76\\x31\\x0D\\x18\\x1A\\x01\\xFF\\x18\\x2A\\xFF\"\n    };\n    TEST_ENV(DATA, dev1_TEST_RESOURCE_PATH);\n    test_single_i64_resource(in, &dev1_TEST_RESOURCE_PATH, 42);\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(lwm2m_cbor_in_resource, gw_single_resource_nested) {\n    // {\"dev1\": {13: {26: {1: 42}}}}\n    static const char DATA[] = {\n        \"\\xA1\\x64\\x64\\x65\\x76\\x31\\xA1\\x0D\\xA1\\x18\\x1A\\xA1\\x01\\x18\\x2A\"\n    };\n    TEST_ENV(DATA, dev1_TEST_RESOURCE_PATH);\n    test_single_i64_resource(in, &dev1_TEST_RESOURCE_PATH, 42);\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(lwm2m_cbor_in_resource, gw_single_resource_nested_arrays) {\n    // {[\"dev1\"]: {[13]: {[26]: {[1]: 42}}}}\n    static const char DATA[] = { \"\\xA1\\x81\\x64\\x64\\x65\\x76\\x31\\xA1\\x81\\x0D\\xA1\"\n                                 \"\\x81\\x18\\x1A\\xA1\\x81\\x01\\x18\\x2A\" };\n    TEST_ENV(DATA, dev1_TEST_RESOURCE_PATH);\n    test_single_i64_resource(in, &dev1_TEST_RESOURCE_PATH, 42);\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(lwm2m_cbor_in_resource,\n              gw_single_resource_nested_arrays_all_indefinite) {\n    // {_ [_ (_ \"dev1\")]: {_ [_ 13]: {_ [_ 26]: {_ [_ 1]: 42}}}}\n    static const char DATA[] = {\n        \"\\xBF\\x9F\\x7F\\x64\\x64\\x65\\x76\\x31\\xFF\\xFF\\xBF\\x9F\\x0D\\xFF\\xBF\\x9F\\x18\"\n        \"\\x1A\\xFF\\xBF\\x9F\\x01\\xFF\\x18\\x2A\\xFF\\xFF\\xFF\\xFF\"\n    };\n    TEST_ENV(DATA, dev1_TEST_RESOURCE_PATH);\n    test_single_i64_resource(in, &dev1_TEST_RESOURCE_PATH, 42);\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(lwm2m_cbor_in_resource_instance, gw_max_possible_nesting) {\n    // Uses decimal fraction\n    // {[\"dev1\"]: {[13]: {[26]: {[1]: {[2]: 4([-1, 45])}}}}}\n    static const char DATA[] = {\n        \"\\xA1\\x81\\x64\\x64\\x65\\x76\\x31\\xA1\\x81\\x0D\\xA1\\x81\\x18\\x1A\\xA1\\x81\\x01\"\n        \"\\xA1\\x81\\x02\\xC4\\x82\\x20\\x18\\x2D\"\n    };\n    TEST_ENV(DATA, dev1_TEST_RESOURCE_INSTANCE_PATH);\n    test_single_double_resource(in, &dev1_TEST_RESOURCE_INSTANCE_PATH, 4.5);\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(lwm2m_cbor_in_resource, gw_two_resources_1) {\n    // {[\"dev1\", 13, 26]: {1: 42, 2: 21}}\n    static const char DATA[] = {\n        \"\\xA1\\x83\\x64\\x64\\x65\\x76\\x31\\x0D\\x18\\x1A\\xA2\\x01\\x18\\x2A\\x02\\x15\"\n    };\n    TEST_ENV(DATA, dev1_TEST_INSTANCE_PATH);\n    test_two_i64_resources(in, &dev1_TEST_RESOURCE_PATH, 42,\n                           &dev1_TEST_RESOURCE2_PATH, 21);\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(lwm2m_cbor_in_resource, gw_two_resources_2) {\n    // {[\"dev1\", 13, 26, 1]: 42, [\"dev1\", 13, 26, 2]: 21}\n    static const char DATA[] = {\n        \"\\xA2\\x84\\x64\\x64\\x65\\x76\\x31\\x0D\\x18\\x1A\\x01\\x18\\x2A\\x84\\x64\\x64\\x65\"\n        \"\\x76\\x31\\x0D\\x18\\x1A\\x02\\x15\"\n    };\n    TEST_ENV(DATA, dev1_TEST_INSTANCE_PATH);\n    test_two_i64_resources(in, &dev1_TEST_RESOURCE_PATH, 42,\n                           &dev1_TEST_RESOURCE2_PATH, 21);\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(lwm2m_cbor_in_resource, gw_two_resources_3) {\n    // {[\"dev1\", 13, 26]: {1: 42}, [\"dev1\", 13, 26, 2]: 21}\n    static const char DATA[] = {\n        \"\\xA2\\x83\\x64\\x64\\x65\\x76\\x31\\x0D\\x18\\x1A\\xA1\\x01\\x18\\x2A\\x84\\x64\\x64\"\n        \"\\x65\\x76\\x31\\x0D\\x18\\x1A\\x02\\x15\"\n    };\n    TEST_ENV(DATA, dev1_TEST_INSTANCE_PATH);\n    test_two_i64_resources(in, &dev1_TEST_RESOURCE_PATH, 42,\n                           &dev1_TEST_RESOURCE2_PATH, 21);\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(lwm2m_cbor_in_resource, gw_two_resources_4) {\n    // {\"dev1\": {[13, 26, 1]: 42, [13, 26]: {[2]: 21}}}\n    static const char DATA[] = {\n        \"\\xA1\\x64\\x64\\x65\\x76\\x31\\xA2\\x83\\x0D\\x18\\x1A\\x01\\x18\\x2A\\x82\\x0D\\x18\"\n        \"\\x1A\\xA1\\x81\\x02\\x15\"\n    };\n    TEST_ENV(DATA, dev1_TEST_INSTANCE_PATH);\n    test_two_i64_resources(in, &dev1_TEST_RESOURCE_PATH, 42,\n                           &dev1_TEST_RESOURCE2_PATH, 21);\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(lwm2m_cbor_in_resource, gw_composite) {\n    // {\"dev1\": {[13, 26, 1]: 1, [14, 27, 2]: 2}, [\"dev2\", 13, 26]: {1: 3}, [13,\n    // 26, 1]: 4}\n    static const char DATA[] = {\n        \"\\xA3\\x64\\x64\\x65\\x76\\x31\\xA2\\x83\\x0D\\x18\\x1A\\x01\\x01\\x83\\x0E\\x18\\x1B\"\n        \"\\x02\\x02\\x83\\x64\\x64\\x65\\x76\\x32\\x0D\\x18\\x1A\\xA1\\x01\\x03\\x83\\x0D\\x18\"\n        \"\\x1A\\x01\\x04\"\n    };\n    TEST_ENV(DATA, MAKE_ROOT_PATH());\n\n    expect_path(in, &dev1_TEST_RESOURCE_PATH);\n    pull_i64_value(in, 1);\n    expect_path(in, &dev1_TEST_OTHER_OBJ_RESOURCE_PATH);\n    pull_i64_value(in, 2);\n    expect_path(in, &dev2_TEST_RESOURCE_PATH);\n    pull_i64_value(in, 3);\n    expect_path(in, &TEST_RESOURCE_PATH);\n    pull_i64_value(in, 4);\n\n    expect_whole_input_consumed(in);\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(lwm2m_cbor_in_resource, gw_composite_indefinite_maps_and_arrays) {\n    // {_ [_ (_ \"dev1\"), 13, 26]: {_ 1: 1}, [_ (_ \"dev2\"), 13, 26]: {_ 1: 2}, [_\n    // 13, 26, 1]: 3}\n    static const char DATA[] = {\n        \"\\xBF\\x9F\\x7F\\x64\\x64\\x65\\x76\\x31\\xFF\\x0D\\x18\\x1A\\xFF\\xBF\\x01\\x01\\xFF\"\n        \"\\x9F\\x7F\\x64\\x64\\x65\\x76\\x32\\xFF\\x0D\\x18\\x1A\\xFF\\xBF\\x01\\x02\\xFF\\x9F\"\n        \"\\x0D\\x18\\x1A\\x01\\xFF\\x03\\xFF\"\n    };\n\n    TEST_ENV(DATA, MAKE_ROOT_PATH());\n\n    expect_path(in, &dev1_TEST_RESOURCE_PATH);\n    pull_i64_value(in, 1);\n    expect_path(in, &dev2_TEST_RESOURCE_PATH);\n    pull_i64_value(in, 2);\n    expect_path(in, &TEST_RESOURCE_PATH);\n    pull_i64_value(in, 3);\n\n    expect_whole_input_consumed(in);\n    TEST_TEARDOWN(OK);\n}\n\nAVS_UNIT_TEST(lwm2m_cbor_in, gw_path_too_long_1) {\n    // {[\"dev1\", 13, 26, 3, 4, 5]: 5}\n    static const char DATA[] = {\n        \"\\xA1\\x86\\x64\\x64\\x65\\x76\\x31\\x0D\\x18\\x1A\\x03\\x04\\x05\\x05\"\n    };\n\n    TEST_ENV(DATA, dev1_TEST_INSTANCE_PATH);\n    expect_path_retrieval_failure(in);\n    TEST_TEARDOWN(FAIL);\n}\n\nAVS_UNIT_TEST(lwm2m_cbor_in, gw_path_too_long_2) {\n    // {[\"dev1\", 13, 26, 1]: {2: 5, [3, 4]: 6}}\n    static const char DATA[] = { \"\\xA1\\x84\\x64\\x64\\x65\\x76\\x31\\x0D\\x18\\x1A\\x01\"\n                                 \"\\xA2\\x02\\x05\\x82\\x03\\x04\\x06\" };\n\n    TEST_ENV(DATA, dev1_TEST_RESOURCE_INSTANCE_PATH);\n\n    expect_path(in, &dev1_TEST_RESOURCE_INSTANCE_PATH);\n    pull_i64_value(in, 5);\n    expect_path_retrieval_failure(in);\n\n    TEST_TEARDOWN(FAIL);\n}\n\nAVS_UNIT_TEST(lwm2m_cbor_in, gw_path_too_long_3) {\n    // {\"dev1\": {13: {26: {1: {2: {3: 4}}}}}}\n    static const char DATA[] = { \"\\xA1\\x64\\x64\\x65\\x76\\x31\\xA1\\x0D\\xA1\\x18\\x1A\"\n                                 \"\\xA1\\x01\\xA1\\x02\\xA1\\x03\\x04\" };\n\n    TEST_ENV(DATA, dev1_TEST_RESOURCE_INSTANCE_PATH);\n    expect_path_retrieval_failure(in);\n    TEST_TEARDOWN(FAIL);\n}\n\nAVS_UNIT_TEST(lwm2m_cbor_in, gw_path_prefix_mismatch_1) {\n    // {[13, 26, 1]: 42}\n    static const char DATA[] = { \"\\xA1\\x83\\x0D\\x18\\x1A\\x01\\x18\\x2A\" };\n    TEST_ENV(DATA, dev1_TEST_RESOURCE_PATH);\n    expect_path_retrieval_failure(in);\n    TEST_TEARDOWN(FAIL);\n}\n\nAVS_UNIT_TEST(lwm2m_cbor_in, gw_path_prefix_mismatch_2) {\n    // {[\"dev1\", 13, 26, 1]: 42}\n    static const char DATA[] = {\n        \"\\xA1\\x84\\x64\\x64\\x65\\x76\\x31\\x0D\\x18\\x1A\\x01\\x18\\x2A\"\n    };\n    TEST_ENV(DATA, TEST_RESOURCE_PATH);\n    expect_path_retrieval_failure(in);\n    TEST_TEARDOWN(FAIL);\n}\n\nAVS_UNIT_TEST(lwm2m_cbor_in, gw_path_prefix_mismatch_3) {\n    // {[\"dev2\", 13, 26, 1]: 42}\n    static const char DATA[] = {\n        \"\\xA1\\x84\\x64\\x64\\x65\\x76\\x32\\x0D\\x18\\x1A\\x01\\x18\\x2A\"\n    };\n    TEST_ENV(DATA, dev1_TEST_RESOURCE_PATH);\n    expect_path_retrieval_failure(in);\n    TEST_TEARDOWN(FAIL);\n}\n\nAVS_UNIT_TEST(lwm2m_cbor_in, gw_path_prefix_wrong_level_1) {\n    // {[13, 26]: {\"dev1\": {1: 42}}}\n    static const char DATA[] = {\n        \"\\xA1\\x82\\x0D\\x18\\x1A\\xA1\\x64\\x64\\x65\\x76\\x31\\xA1\\x01\\x18\\x2A\"\n    };\n    TEST_ENV(DATA, MAKE_ROOT_PATH());\n    expect_path_retrieval_failure(in);\n    TEST_TEARDOWN(FAIL);\n}\n\nAVS_UNIT_TEST(lwm2m_cbor_in, gw_path_prefix_wrong_level_2) {\n    // {[13, 26]: {[1, \"dev1\"]: 42}}\n    static const char DATA[] = {\n        \"\\xA1\\x82\\x0D\\x18\\x1A\\xA1\\x82\\x01\\x64\\x64\\x65\\x76\\x31\\x18\\x2A\"\n    };\n    TEST_ENV(DATA, MAKE_ROOT_PATH());\n    expect_path_retrieval_failure(in);\n    TEST_TEARDOWN(FAIL);\n}\n\nAVS_UNIT_TEST(lwm2m_cbor_in, gw_path_prefix_twice_in_array) {\n    // {[\"dev1\", 13, 26, \"dev1\"]: {1: 42}}\n    static const char DATA[] = { \"\\xA1\\x84\\x64\\x64\\x65\\x76\\x31\\x0D\\x18\\x1A\\x64\"\n                                 \"\\x64\\x65\\x76\\x31\\xA1\\x01\\x18\\x2A\" };\n    TEST_ENV(DATA, MAKE_ROOT_PATH());\n    expect_path_retrieval_failure(in);\n    TEST_TEARDOWN(FAIL);\n}\n\n// NOTE: if this ends up being configurable, come up with a way to encode\n// the string dynamically (including its length, which may have variable\n// size)\nAVS_STATIC_ASSERT(ANJAY_GATEWAY_MAX_PREFIX_LEN == 9, prefix_len_is_9);\n\nAVS_UNIT_TEST(lwm2m_cbor_in, gw_path_prefix_too_long_solo) {\n    // {\"123456789\": {[13, 26, 1]: 42}}\n    static const char DATA[] = { \"\\xA1\\x69\\x31\\x32\\x33\\x34\\x35\\x36\\x37\\x38\\x39\"\n                                 \"\\xA1\\x83\\x0D\\x18\\x1A\\x01\\x18\\x2A\" };\n    TEST_ENV(DATA, MAKE_ROOT_PATH());\n    expect_path_retrieval_failure(in);\n    TEST_TEARDOWN(FAIL);\n}\n\nAVS_UNIT_TEST(lwm2m_cbor_in, gw_path_prefix_too_long_array) {\n    // {[\"123456789\", 13, 26, 1]: 42}\n    static const char DATA[] = { \"\\xA1\\x84\\x69\\x31\\x32\\x33\\x34\\x35\\x36\\x37\\x38\"\n                                 \"\\x39\\x0D\\x18\\x1A\\x01\\x18\\x2A\" };\n    TEST_ENV(DATA, MAKE_ROOT_PATH());\n    expect_path_retrieval_failure(in);\n    TEST_TEARDOWN(FAIL);\n}\n\nAVS_UNIT_TEST(lwm2m_cbor_in, gw_path_prefix_too_long_array_indefinite) {\n    // {[(_ \"12345678\", \"12345678\")]: {[13, 26, 1]: 42}}\n    static const char DATA[] = {\n        \"\\xA1\\x81\\x7F\\x68\\x31\\x32\\x33\\x34\\x35\\x36\\x37\\x38\\x68\\x31\\x32\\x33\\x34\"\n        \"\\x35\\x36\\x37\\x38\\xFF\\xA1\\x83\\x0D\\x18\\x1A\\x01\\x18\\x2A\"\n    };\n    TEST_ENV(DATA, MAKE_ROOT_PATH());\n    expect_path_retrieval_failure(in);\n    TEST_TEARDOWN(FAIL);\n}\n\n#endif // ANJAY_WITH_LWM2M_GATEWAY\n\n#undef TEST_ENV\n"
  },
  {
    "path": "tests/core/io/lwm2m_cbor_out.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#include <avsystem/commons/avs_memory.h>\n#include <avsystem/commons/avs_stream_outbuf.h>\n#include <avsystem/commons/avs_utils.h>\n\n#define AVS_UNIT_ENABLE_SHORT_ASSERTS\n#include <avsystem/commons/avs_unit_test.h>\n\n#ifdef ANJAY_WITH_LWM2M_GATEWAY\n#    include <string.h>\n\n#    include <anjay/core.h>\n#endif // ANJAY_WITH_LWM2M_GATEWAY\n\n#define BUFFER_SIZE 64\n\n#define TEST_ROOT_PATH (anjay_uri_path_t) ROOT_PATH_INITIALIZER()\n#define TEST_OBJ_INST(Obj, Inst) \\\n    (anjay_uri_path_t) INSTANCE_PATH_INITIALIZER(Obj, Inst)\n#define TEST_OBJ_INST_RES(Obj, Inst, Res) \\\n    (anjay_uri_path_t) RESOURCE_PATH_INITIALIZER(Obj, Inst, Res)\n#define TEST_OBJ_INST_RES_INST(Obj, Inst, Res, ResInst) \\\n    (anjay_uri_path_t)                                  \\\n            RESOURCE_INSTANCE_PATH_INITIALIZER(Obj, Inst, Res, ResInst)\n\n#define TEST_ENV(Path)                                                        \\\n    static char stream_buffer[BUFFER_SIZE] = { 0 };                           \\\n    avs_stream_outbuf_t stream = AVS_STREAM_OUTBUF_STATIC_INITIALIZER;        \\\n    avs_stream_outbuf_set_buffer(&stream, stream_buffer, BUFFER_SIZE);        \\\n    anjay_unlocked_output_ctx_t *out;                                         \\\n    out = _anjay_output_lwm2m_cbor_create((avs_stream_t *) &stream, &(Path)); \\\n    ASSERT_NOT_NULL(out);                                                     \\\n    ASSERT_OK(_anjay_output_set_path(out, &(Path)));\n\n#define TEST_TEARDOWN(ExpectedData)                                       \\\n    do {                                                                  \\\n        ASSERT_OK(_anjay_output_ctx_destroy(&out));                       \\\n        ASSERT_EQ_BYTES_SIZED(                                            \\\n                stream_buffer, (ExpectedData), sizeof(ExpectedData) - 1); \\\n    } while (0)\n\nAVS_UNIT_TEST(lwm2m_cbor_out, single_resource) {\n    TEST_ENV(TEST_OBJ_INST_RES(13, 26, 1));\n\n    ASSERT_OK(_anjay_ret_i64_unlocked(out, 42));\n\n    // clang-format off\n    static const char EXPECTED_DATA[] = {\n        // {[13, 26, 1]: 42}\n        \"\\xBF\"             // map(*)\n            \"\\x83\"         // array(3)\n                \"\\x0D\"     // unsigned(13)\n                \"\\x18\\x1A\" // unsigned(26)\n                \"\\x01\"     // unsigned(1)\n            \"\\x18\\x2A\"     // unsigned(42)\n            \"\\xFF\"         // primitive(*)\n    };\n    // clang-format on\n    TEST_TEARDOWN(EXPECTED_DATA);\n}\n\nAVS_UNIT_TEST(lwm2m_cbor_out, two_resources) {\n    TEST_ENV(TEST_OBJ_INST(13, 26));\n\n    ASSERT_OK(_anjay_output_start_aggregate(out));\n    ASSERT_OK(_anjay_output_set_path(out, &TEST_OBJ_INST_RES(13, 26, 1)));\n    ASSERT_OK(_anjay_ret_i64_unlocked(out, 42));\n    ASSERT_OK(_anjay_output_set_path(out, &TEST_OBJ_INST_RES(13, 26, 2)));\n    ASSERT_OK(_anjay_ret_i64_unlocked(out, 21));\n\n    // clang-format off\n    static const char EXPECTED_DATA[] = {\n        // {[13, 26]: {1: 42, 2: 21}}\n        \"\\xBF\"             // map(*)\n            \"\\x82\"         // array(2)\n                \"\\x0D\"     // unsigned(13)\n                \"\\x18\\x1A\" // unsigned(26)\n            \"\\xBF\"         // map(*)\n                \"\\x01\"     // unsigned(1)\n                \"\\x18\\x2A\" // unsigned(42)\n                \"\\x02\"     // unsigned(2)\n                \"\\x15\"     // unsigned(21)\n                \"\\xFF\"     // primitive(*)\n           \"\\xFF\"          // primitive(*)\n    };\n    // clang-format on\n    TEST_TEARDOWN(EXPECTED_DATA);\n}\n\nAVS_UNIT_TEST(lwm2m_cbor_out, resource_instances_nested_maps) {\n    TEST_ENV(TEST_OBJ_INST(13, 26));\n\n    ASSERT_OK(_anjay_output_start_aggregate(out));\n    ASSERT_OK(_anjay_output_set_path(out, &TEST_OBJ_INST_RES(13, 26, 1)));\n    ASSERT_OK(_anjay_ret_i64_unlocked(out, 42));\n    ASSERT_OK(_anjay_output_set_path(out,\n                                     &TEST_OBJ_INST_RES_INST(13, 26, 3, 21)));\n    ASSERT_OK(_anjay_ret_double_unlocked(out, 69.68));\n    ASSERT_OK(_anjay_output_set_path(out,\n                                     &TEST_OBJ_INST_RES_INST(13, 26, 3, 37)));\n    ASSERT_OK(_anjay_ret_bool_unlocked(out, false));\n\n    // clang-format off\n    static const char EXPECTED_DATA[] = {\n        // {[13, 26]: {1: 42, 3: {21: 69.68, 37: false}}}\n        \"\\xBF\"                                             // map(*)\n            \"\\x82\"                                         // array(2)\n                \"\\x0D\"                                     // unsigned(13)\n                \"\\x18\\x1A\"                                 // unsigned(26)\n            \"\\xBF\"                                         // map(*)\n                \"\\x01\"                                     // unsigned(1)\n                \"\\x18\\x2A\"                                 // unsigned(42)\n                \"\\x03\"                                     // unsigned(2)\n                \"\\xBF\"                                     // map(*)\n                    \"\\x15\"                                 // unsigned(21)\n                    \"\\xFB\\x40\\x51\\x6B\\x85\\x1E\\xB8\\x51\\xEC\" // primitive(69.68)\n                    \"\\x18\\x25\"                             // unsigned(37)\n                    \"\\xF4\"                                 // primitive(false)\n                    \"\\xFF\"                                 // primitive(*)\n                \"\\xFF\"                                     // primitive(*)\n           \"\\xFF\"                                          // primitive(*)\n    };\n    // clang-format on\n    TEST_TEARDOWN(EXPECTED_DATA);\n}\n\nAVS_UNIT_TEST(lwm2m_cbor_out, two_objects_one_instance_two_resources) {\n    TEST_ENV(TEST_ROOT_PATH);\n\n    ASSERT_OK(_anjay_output_set_path(out, &TEST_OBJ_INST_RES(13, 26, 1)));\n    ASSERT_OK(_anjay_ret_i64_unlocked(out, 42));\n    ASSERT_OK(_anjay_output_set_path(out, &TEST_OBJ_INST_RES(13, 26, 2)));\n    ASSERT_OK(_anjay_ret_i64_unlocked(out, 21));\n\n    ASSERT_OK(_anjay_output_set_path(out, &TEST_OBJ_INST_RES(14, 27, 1)));\n    ASSERT_OK(_anjay_ret_i64_unlocked(out, 43));\n    ASSERT_OK(_anjay_output_set_path(out, &TEST_OBJ_INST_RES(14, 27, 2)));\n    ASSERT_OK(_anjay_ret_i64_unlocked(out, 22));\n\n    // clang-format off\n    static const char EXPECTED_DATA[] = {\n        // {13: {26: {1: 42, 2: 21}}, 14: {27: {1: 43, 2: 22}}}\n        \"\\xBF\"                 // map(*)\n            \"\\x0D\"             // unsigned(13)\n            \"\\xBF\"             // map(*)\n                \"\\x18\\x1A\"     // unsigned(26)\n                \"\\xBF\"         // map(*)\n                    \"\\x01\"     // unsigned(1)\n                    \"\\x18\\x2A\" // unsigned(42)\n                    \"\\x02\"     // unsigned(2)\n                    \"\\x15\"     // unsigned(21)\n                    \"\\xFF\"     // primitive(*)\n                \"\\xFF\"         // primitive(*)\n            \"\\x0E\"             // unsigned(14)\n            \"\\xBF\"             // map(*)\n                \"\\x18\\x1B\"     // unsigned(27)\n                \"\\xBF\"         // map(*)\n                    \"\\x01\"     // unsigned(1)\n                    \"\\x18\\x2B\" // unsigned(43)\n                    \"\\x02\"     // unsigned(2)\n                    \"\\x16\"     // unsigned(22)\n                    \"\\xFF\"     // primitive(*)\n                \"\\xFF\"         // primitive(*)\n            \"\\xFF\"             // primitive(*)\n\n    };\n    // clang-format on\n    TEST_TEARDOWN(EXPECTED_DATA);\n}\n\n#ifdef ANJAY_WITH_LWM2M_GATEWAY\n\nstatic void set_prefix(anjay_uri_path_t *uri, const char *prefix) {\n    assert(prefix);\n    size_t prefix_len = strlen(prefix);\n    assert(prefix_len < ANJAY_GATEWAY_MAX_PREFIX_LEN);\n    strcpy((void *) uri->prefix, (const void *) prefix);\n}\n\n#    define CREATE_PATH_WITH_PREFIX(Var, Ids, Prefix) \\\n        anjay_uri_path_t(Var) = (Ids);                \\\n        set_prefix(&(Var), (Prefix));\n\nAVS_UNIT_TEST(lwm2m_cbor_out, single_resource_with_prefix) {\n    CREATE_PATH_WITH_PREFIX(test_path, TEST_OBJ_INST_RES(13, 26, 1), \"dev1\");\n\n    TEST_ENV(test_path);\n\n    ASSERT_OK(_anjay_ret_i64_unlocked(out, 42));\n\n    // clang-format off\n    static const char EXPECTED_DATA[] = {\n        // {[\"dev1\", 13, 26, 1]: 42}\n        \"\\xBF\"                         // map(*)\n            \"\\x84\"                     // array(4)\n                \"\\x64\"                 // text(4)\n                    \"\\x64\\x65\\x76\\x31\" // \"dev1\"\n                \"\\x0D\"                 // unsigned(13)\n                \"\\x18\\x1A\"             // unsigned(26)\n                \"\\x01\"                 // unsigned(1)\n            \"\\x18\\x2A\"                 // unsigned(42)\n            \"\\xFF\"                     // primitive(*)\n    };\n    // clang-format on\n    TEST_TEARDOWN(EXPECTED_DATA);\n}\n\nAVS_UNIT_TEST(lwm2m_cbor_out, two_resources_with_prefix) {\n    CREATE_PATH_WITH_PREFIX(test_path1, TEST_OBJ_INST(13, 26), \"dev1\");\n    CREATE_PATH_WITH_PREFIX(test_path2, TEST_OBJ_INST_RES(13, 26, 1), \"dev1\");\n    CREATE_PATH_WITH_PREFIX(test_path3, TEST_OBJ_INST_RES(13, 26, 2), \"dev1\");\n\n    TEST_ENV(test_path1);\n\n    ASSERT_OK(_anjay_output_start_aggregate(out));\n    ASSERT_OK(_anjay_output_set_path(out, &test_path2));\n    ASSERT_OK(_anjay_ret_i64_unlocked(out, 42));\n    ASSERT_OK(_anjay_output_set_path(out, &test_path3));\n    ASSERT_OK(_anjay_ret_i64_unlocked(out, 21));\n\n    // clang-format off\n    static const char EXPECTED_DATA[] = {\n        // {[\"dev1\", 13, 26]: {1: 42, 2: 21}}\n        \"\\xBF\"                         // map(*)\n            \"\\x83\"                     // array(3)\n                \"\\x64\"                 // text(4)\n                    \"\\x64\\x65\\x76\\x31\" // \"dev1\"\n                \"\\x0D\"                 // unsigned(13)\n                \"\\x18\\x1A\"             // unsigned(26)\n            \"\\xBF\"                     // map(*)\n                \"\\x01\"                 // unsigned(1)\n                \"\\x18\\x2A\"             // unsigned(42)\n                \"\\x02\"                 // unsigned(2)\n                \"\\x15\"                 // unsigned(21)\n                \"\\xFF\"                 // primitive(*)\n           \"\\xFF\"                      // primitive(*)\n    };\n    // clang-format on\n    TEST_TEARDOWN(EXPECTED_DATA);\n}\n\nAVS_UNIT_TEST(lwm2m_cbor_out, two_objects_different_prefixes_max_nesting) {\n    CREATE_PATH_WITH_PREFIX(test_root_path, TEST_ROOT_PATH, \"\");\n    CREATE_PATH_WITH_PREFIX(\n            test_path1, TEST_OBJ_INST_RES_INST(13, 26, 1, 7), \"dev1\");\n    CREATE_PATH_WITH_PREFIX(\n            test_path2, TEST_OBJ_INST_RES_INST(13, 26, 1, 8), \"dev1\");\n    CREATE_PATH_WITH_PREFIX(\n            test_path3, TEST_OBJ_INST_RES_INST(14, 27, 1, 5), \"dev2\");\n    CREATE_PATH_WITH_PREFIX(\n            test_path4, TEST_OBJ_INST_RES_INST(14, 27, 2, 6), \"dev2\");\n\n    TEST_ENV(test_root_path);\n\n    ASSERT_OK(_anjay_output_set_path(out, &test_path1));\n    ASSERT_OK(_anjay_ret_i64_unlocked(out, 42));\n    ASSERT_OK(_anjay_output_set_path(out, &test_path2));\n    ASSERT_OK(_anjay_ret_i64_unlocked(out, 21));\n\n    ASSERT_OK(_anjay_output_set_path(out, &test_path3));\n    ASSERT_OK(_anjay_ret_i64_unlocked(out, 43));\n    ASSERT_OK(_anjay_output_set_path(out, &test_path4));\n    ASSERT_OK(_anjay_ret_i64_unlocked(out, 22));\n\n    // clang-format off\n    static const char EXPECTED_DATA[] = {\n        // {\n        //  \"dev1\": {13: {26: {1: {7: 42, 8: 21}}}},\n        //  \"dev2\": {14: {27: {1: {5: 43}, 2: {6: 22}}}}\n        // }\n        \"\\xBF\"                    // map(*)\n           \"\\x64\"                 // text(4)\n              \"\\x64\\x65\\x76\\x31\"  // \"dev1\"\n           \"\\xBF\"                 // map(*)\n              \"\\x0D\"              // unsigned(13)\n              \"\\xBF\"              // map(*)\n                 \"\\x18\\x1A\"       // unsigned(26)\n                 \"\\xBF\"           // map(*)\n                    \"\\x01\"        // unsigned(1)\n                    \"\\xBF\"        // map(*)\n                       \"\\x07\"     // unsigned(7)\n                       \"\\x18\\x2A\" // unsigned(42)\n                       \"\\x08\"     // unsigned(8)\n                       \"\\x15\"     // unsigned(21)\n                       \"\\xFF\"     // primitive(*)\n                    \"\\xFF\"        // primitive(*)\n                 \"\\xFF\"           // primitive(*)\n              \"\\xFF\"              // primitive(*)\n           \"\\x64\"                 // text(4)\n              \"\\x64\\x65\\x76\\x32\"  // \"dev2\"\n           \"\\xBF\"                 // map(*)\n              \"\\x0E\"              // unsigned(14)\n              \"\\xBF\"              // map(*)\n                 \"\\x18\\x1B\"       // unsigned(27)\n                 \"\\xBF\"           // map(*)\n                    \"\\x01\"        // unsigned(1)\n                    \"\\xBF\"        // map(*)\n                       \"\\x05\"     // unsigned(5)\n                       \"\\x18\\x2B\" // unsigned(43)\n                       \"\\xFF\"     // primitive(*)\n                    \"\\x02\"        // unsigned(2)\n                    \"\\xBF\"        // map(*)\n                       \"\\x06\"     // unsigned(6)\n                       \"\\x16\"     // unsigned(22)\n                       \"\\xFF\"     // primitive(*)\n                    \"\\xFF\"        // primitive(*)\n                 \"\\xFF\"           // primitive(*)\n              \"\\xFF\"              // primitive(*)\n           \"\\xFF\"                 // primitive(*)\n    };\n    // clang-format on\n    TEST_TEARDOWN(EXPECTED_DATA);\n}\n\nAVS_UNIT_TEST(lwm2m_cbor_out, three_objects_different_prefixes) {\n    CREATE_PATH_WITH_PREFIX(test_root_path, TEST_ROOT_PATH, \"\");\n    CREATE_PATH_WITH_PREFIX(test_path1, TEST_OBJ_INST_RES(13, 26, 1), \"dev1\");\n    CREATE_PATH_WITH_PREFIX(test_path2, TEST_OBJ_INST_RES(14, 27, 1), \"dev2\");\n    CREATE_PATH_WITH_PREFIX(test_path3, TEST_OBJ_INST_RES(15, 28, 1), \"dev3\");\n\n    TEST_ENV(test_root_path);\n\n    ASSERT_OK(_anjay_output_set_path(out, &test_path1));\n    ASSERT_OK(_anjay_ret_i64_unlocked(out, 42));\n    ASSERT_OK(_anjay_output_set_path(out, &test_path2));\n    ASSERT_OK(_anjay_ret_i64_unlocked(out, 43));\n    ASSERT_OK(_anjay_output_set_path(out, &test_path3));\n    ASSERT_OK(_anjay_ret_i64_unlocked(out, 44));\n\n    // clang-format off\n    static const char EXPECTED_DATA[] = {\n        // {\n        //  \"dev1\": {13: {26: {1: 42}}},\n        //  \"dev2\": {14: {27: {1: 43}}},\n        //  \"dev3\": {15: {28: {1: 44}}}\n        // }\n        \"\\xBF\"                    // map(*)\n           \"\\x64\"                 // text(4)\n              \"\\x64\\x65\\x76\\x31\"  // \"dev1\"\n           \"\\xBF\"                 // map(*)\n              \"\\x0D\"              // unsigned(13)\n              \"\\xBF\"              // map(*)\n                 \"\\x18\\x1A\"       // unsigned(26)\n                 \"\\xBF\"           // map(*)\n                    \"\\x01\"        // unsigned(1)\n                       \"\\x18\\x2A\" // unsigned(42)\n                    \"\\xFF\"        // primitive(*)\n                 \"\\xFF\"           // primitive(*)\n              \"\\xFF\"              // primitive(*)\n           \"\\x64\"                 // text(4)\n              \"\\x64\\x65\\x76\\x32\"  // \"dev2\"\n           \"\\xBF\"                 // map(*)\n              \"\\x0E\"              // unsigned(14)\n              \"\\xBF\"              // map(*)\n                 \"\\x18\\x1B\"       // unsigned(27)\n                 \"\\xBF\"           // map(*)\n                    \"\\x01\"        // unsigned(1)\n                       \"\\x18\\x2B\" // unsigned(43)\n                    \"\\xFF\"        // primitive(*)\n                 \"\\xFF\"           // primitive(*)\n              \"\\xFF\"              // primitive(*)\n           \"\\x64\"                 // text(4)\n              \"\\x64\\x65\\x76\\x33\"  // \"dev3\"\n           \"\\xBF\"                 // map(*)\n              \"\\x0F\"              // unsigned(15)\n              \"\\xBF\"              // map(*)\n                 \"\\x18\\x1C\"       // unsigned(28)\n                 \"\\xBF\"           // map(*)\n                    \"\\x01\"        // unsigned(1)\n                       \"\\x18\\x2C\" // unsigned(44)\n                    \"\\xFF\"        // primitive(*)\n                 \"\\xFF\"           // primitive(*)\n              \"\\xFF\"              // primitive(*)\n           \"\\xFF\"                 // primitive(*)\n    };\n    // clang-format on\n    TEST_TEARDOWN(EXPECTED_DATA);\n}\n\nAVS_UNIT_TEST(lwm2m_cbor_out, root_path_with_prefix) {\n    CREATE_PATH_WITH_PREFIX(test_root_path, TEST_ROOT_PATH, \"dev1\");\n    CREATE_PATH_WITH_PREFIX(\n            test_path1, TEST_OBJ_INST_RES_INST(13, 26, 1, 7), \"dev1\");\n    CREATE_PATH_WITH_PREFIX(\n            test_path2, TEST_OBJ_INST_RES_INST(13, 26, 1, 8), \"dev1\");\n\n    TEST_ENV(test_root_path);\n\n    ASSERT_OK(_anjay_output_clear_path(out));\n    ASSERT_OK(_anjay_output_set_path(out, &test_path1));\n    ASSERT_OK(_anjay_ret_i64_unlocked(out, 42));\n    ASSERT_OK(_anjay_output_set_path(out, &test_path2));\n    ASSERT_OK(_anjay_ret_i64_unlocked(out, 21));\n\n    // clang-format off\n    static const char EXPECTED_DATA[] = {\n        // {\"dev1\": {13: {26: {1: {7: 42, 8: 21}}}}}\n        \"\\xBF\"                    // map(*)\n           \"\\x64\"                 // text(4)\n              \"\\x64\\x65\\x76\\x31\"  // \"dev1\"\n           \"\\xBF\"                 // map(*)\n              \"\\x0D\"              // unsigned(13)\n              \"\\xBF\"              // map(*)\n                 \"\\x18\\x1A\"       // unsigned(26)\n                 \"\\xBF\"           // map(*)\n                    \"\\x01\"        // unsigned(1)\n                    \"\\xBF\"        // map(*)\n                       \"\\x07\"     // unsigned(7)\n                       \"\\x18\\x2A\" // unsigned(42)\n                       \"\\x08\"     // unsigned(8)\n                       \"\\x15\"     // unsigned(21)\n                       \"\\xFF\"     // primitive(*)\n                    \"\\xFF\"        // primitive(*)\n                 \"\\xFF\"           // primitive(*)\n              \"\\xFF\"              // primitive(*)\n           \"\\xFF\"                 // primitive(*)\n    };\n    // clang-format on\n    TEST_TEARDOWN(EXPECTED_DATA);\n}\n\nAVS_UNIT_TEST(lwm2m_cbor_out, mixed_data_end_device_with_gateway) {\n    CREATE_PATH_WITH_PREFIX(test_root_path, TEST_ROOT_PATH, \"\");\n    CREATE_PATH_WITH_PREFIX(test_path1, TEST_OBJ_INST_RES(13, 26, 1), \"dev1\");\n    CREATE_PATH_WITH_PREFIX(test_path2, TEST_OBJ_INST_RES(14, 27, 1), \"\");\n\n    TEST_ENV(test_root_path);\n\n    ASSERT_OK(_anjay_output_set_path(out, &test_path1));\n    ASSERT_OK(_anjay_ret_i64_unlocked(out, 42));\n    ASSERT_OK(_anjay_output_set_path(out, &test_path2));\n    ASSERT_OK(_anjay_ret_i64_unlocked(out, 21));\n\n    // clang-format off\n    static const char EXPECTED_DATA[] = {\n        // {\"dev1\": {13: {26: {1: 42}}}, 14: {27: {1: 21}}}\n        \"\\xBF\"                     // map(*)\n            \"\\x64\"                 // text(4)\n                \"\\x64\\x65\\x76\\x31\" // \"dev1\"\n            \"\\xBF\"                 // map(*)\n                \"\\x0D\"             // unsigned(13)\n                \"\\xBF\"             // map(*)\n                    \"\\x18\\x1A\"     // unsigned(26)\n                    \"\\xBF\"         // map(*)\n                        \"\\x01\"     // unsigned(1)\n                        \"\\x18\\x2A\" // unsigned(42)\n                        \"\\xFF\"     // primitive(*)\n                    \"\\xFF\"         // primitive(*)\n                \"\\xFF\"             // primitive(*)\n            \"\\x0E\"                 // unsigned(14)\n            \"\\xBF\"                 // map(*)\n                \"\\x18\\x1B\"         // unsigned(27)\n                \"\\xBF\"             // map(*)\n                    \"\\x01\"         // unsigned(1)\n                    \"\\x15\"         // unsigned(21)\n                    \"\\xFF\"         // primitive(*)\n                \"\\xFF\"             // primitive(*)\n            \"\\xFF\"                 // primitive(*)\n    };\n    // clang-format on\n    TEST_TEARDOWN(EXPECTED_DATA);\n}\n\n#endif // ANJAY_WITH_LWM2M_GATEWAY\n"
  },
  {
    "path": "tests/core/io/raw_cbor_in.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#define AVS_UNIT_ENABLE_SHORT_ASSERTS\n#include <avsystem/commons/avs_stream_inbuf.h>\n#include <avsystem/commons/avs_unit_test.h>\n\nstatic const anjay_uri_path_t TEST_RESOURCE_PATH =\n        RESOURCE_PATH_INITIALIZER(12, 34, 56);\n\n#define TEST_ENV(Data, Path)                                         \\\n    avs_stream_inbuf_t stream = AVS_STREAM_INBUF_STATIC_INITIALIZER; \\\n    avs_stream_inbuf_set_buffer(&stream, Data, sizeof(Data) - 1);    \\\n    anjay_unlocked_input_ctx_t *in;                                  \\\n    ASSERT_OK(_anjay_input_cbor_create(&in, (avs_stream_t *) &stream, &(Path)));\n\n#define TEST_TEARDOWN                             \\\n    do {                                          \\\n        ASSERT_OK(_anjay_input_ctx_destroy(&in)); \\\n    } while (0)\n\nAVS_UNIT_TEST(raw_cbor_in, single_integer) {\n    static const char RESOURCE[] = {\n        \"\\x18\\x2A\" // unsigned(42)\n    };\n    TEST_ENV(RESOURCE, TEST_RESOURCE_PATH);\n\n    anjay_uri_path_t path;\n    ASSERT_OK(_anjay_input_get_path(in, &path, NULL));\n    ASSERT_TRUE(_anjay_uri_path_equal(&path, &TEST_RESOURCE_PATH));\n\n    int32_t value;\n    ASSERT_OK(_anjay_get_i32_unlocked(in, &value));\n    ASSERT_EQ(value, 42);\n\n    ASSERT_OK(_anjay_input_next_entry(in));\n    ASSERT_EQ(_anjay_input_get_path(in, NULL, NULL), ANJAY_GET_PATH_END);\n    cbor_in_t *cbor_input_ctx = (cbor_in_t *) in;\n    ASSERT_TRUE(cbor_input_ctx->msg_finished);\n    ASSERT_EQ(_anjay_json_like_decoder_state(cbor_input_ctx->cbor_decoder),\n              ANJAY_JSON_LIKE_DECODER_STATE_FINISHED);\n\n    TEST_TEARDOWN;\n}\n\nAVS_UNIT_TEST(raw_cbor_in, single_decimal_fraction) {\n    static const char RESOURCE[] = {\n        \"\\xC4\"     // tag(4)\n        \"\\x82\"     // array(2)\n        \"\\x20\"     // negative(0)\n        \"\\x18\\x2D\" // unsigned(45)\n    };\n    TEST_ENV(RESOURCE, TEST_RESOURCE_PATH);\n\n    anjay_uri_path_t path;\n    ASSERT_OK(_anjay_input_get_path(in, &path, NULL));\n    ASSERT_TRUE(_anjay_uri_path_equal(&path, &TEST_RESOURCE_PATH));\n\n    double value;\n    ASSERT_OK(_anjay_get_double_unlocked(in, &value));\n    ASSERT_EQ(value, 4.5);\n\n    ASSERT_OK(_anjay_input_next_entry(in));\n    ASSERT_EQ(_anjay_input_get_path(in, NULL, NULL), ANJAY_GET_PATH_END);\n    cbor_in_t *cbor_input_ctx = (cbor_in_t *) in;\n    ASSERT_TRUE(cbor_input_ctx->msg_finished);\n    ASSERT_EQ(_anjay_json_like_decoder_state(cbor_input_ctx->cbor_decoder),\n              ANJAY_JSON_LIKE_DECODER_STATE_FINISHED);\n\n    TEST_TEARDOWN;\n}\n\nAVS_UNIT_TEST(raw_cbor_in, too_short_buffer_for_string) {\n    static const char RESOURCE[] = {\n        \"\\x6C#ZostanWDomu\" // text(12)\n    };\n    TEST_ENV(RESOURCE, TEST_RESOURCE_PATH);\n\n    anjay_uri_path_t path;\n    ASSERT_OK(_anjay_input_get_path(in, &path, NULL));\n    ASSERT_TRUE(_anjay_uri_path_equal(&path, &TEST_RESOURCE_PATH));\n\n    char too_short_buffer[8] = \"SOMEDATA\";\n    ASSERT_EQ(_anjay_get_string_unlocked(\n                      in, too_short_buffer, sizeof(too_short_buffer)),\n              ANJAY_BUFFER_TOO_SHORT);\n    ASSERT_EQ_STR(too_short_buffer, \"#Zostan\");\n    cbor_in_t *cbor_input_ctx = (cbor_in_t *) in;\n    ASSERT_FALSE(cbor_input_ctx->msg_finished);\n    ASSERT_EQ(_anjay_json_like_decoder_state(cbor_input_ctx->cbor_decoder),\n              ANJAY_JSON_LIKE_DECODER_STATE_OK);\n    ASSERT_OK(_anjay_input_get_path(in, NULL, NULL));\n\n    ASSERT_OK(_anjay_get_string_unlocked(\n            in, too_short_buffer, sizeof(too_short_buffer)));\n    ASSERT_EQ_STR(too_short_buffer, \"WDomu\");\n    ASSERT_TRUE(cbor_input_ctx->msg_finished);\n    ASSERT_EQ(_anjay_json_like_decoder_state(cbor_input_ctx->cbor_decoder),\n              ANJAY_JSON_LIKE_DECODER_STATE_FINISHED);\n    ASSERT_EQ(_anjay_input_get_path(in, NULL, NULL), ANJAY_GET_PATH_END);\n\n    TEST_TEARDOWN;\n}\n\nAVS_UNIT_TEST(raw_cbor_in, empty_string) {\n    static const char RESOURCE[] = {\n        \"\\x60\" // text(0)\n    };\n    TEST_ENV(RESOURCE, TEST_RESOURCE_PATH);\n\n    anjay_uri_path_t path;\n    ASSERT_OK(_anjay_input_get_path(in, &path, NULL));\n    ASSERT_TRUE(_anjay_uri_path_equal(&path, &TEST_RESOURCE_PATH));\n\n    char buffer[8];\n    ASSERT_OK(_anjay_get_string_unlocked(in, buffer, sizeof(buffer)));\n    ASSERT_EQ_STR(buffer, \"\");\n    cbor_in_t *cbor_input_ctx = (cbor_in_t *) in;\n    ASSERT_TRUE(cbor_input_ctx->msg_finished);\n    ASSERT_EQ(_anjay_json_like_decoder_state(cbor_input_ctx->cbor_decoder),\n              ANJAY_JSON_LIKE_DECODER_STATE_FINISHED);\n    ASSERT_EQ(_anjay_input_get_path(in, NULL, NULL), ANJAY_GET_PATH_END);\n\n    TEST_TEARDOWN;\n}\n\n#define CHUNK1 \"test\"\n#define CHUNK2 \"string\"\n#define TEST_STRING (CHUNK1 CHUNK2)\n\nstatic void test_string_indefinite_impl(anjay_unlocked_input_ctx_t *in) {\n    anjay_uri_path_t path;\n    ASSERT_OK(_anjay_input_get_path(in, &path, NULL));\n    ASSERT_TRUE(_anjay_uri_path_equal(&path, &TEST_RESOURCE_PATH));\n\n    char buffer[sizeof(TEST_STRING)];\n    ASSERT_OK(_anjay_get_string_unlocked(in, buffer, sizeof(buffer)));\n    ASSERT_EQ_STR(buffer, TEST_STRING);\n    cbor_in_t *cbor_input_ctx = (cbor_in_t *) in;\n    ASSERT_TRUE(cbor_input_ctx->msg_finished);\n    ASSERT_EQ(_anjay_json_like_decoder_state(cbor_input_ctx->cbor_decoder),\n              ANJAY_JSON_LIKE_DECODER_STATE_FINISHED);\n    ASSERT_EQ(_anjay_input_get_path(in, NULL, NULL), ANJAY_GET_PATH_END);\n}\n\nAVS_UNIT_TEST(raw_cbor_in, string_indefinite) {\n    // (_ \"test\", \"string\")\n    static const char RESOURCE[] = { \"\\x7F\\x64\" CHUNK1 \"\\x66\" CHUNK2 \"\\xFF\" };\n    TEST_ENV(RESOURCE, TEST_RESOURCE_PATH);\n    test_string_indefinite_impl(in);\n    TEST_TEARDOWN;\n}\n\nAVS_UNIT_TEST(raw_cbor_in, string_indefinite_with_empty_strings) {\n    // (_ \"\", \"test\", \"\", \"string\", \"\")\n    static const char RESOURCE[] = { \"\\x7F\\x60\\x64\" CHUNK1 \"\\x60\\x66\" CHUNK2\n                                     \"\\x60\\xFF\" };\n    TEST_ENV(RESOURCE, TEST_RESOURCE_PATH);\n    test_string_indefinite_impl(in);\n    TEST_TEARDOWN;\n}\n\nstatic void test_string_indefinite_empty_impl(anjay_unlocked_input_ctx_t *in) {\n    anjay_uri_path_t path;\n    ASSERT_OK(_anjay_input_get_path(in, &path, NULL));\n    ASSERT_TRUE(_anjay_uri_path_equal(&path, &TEST_RESOURCE_PATH));\n\n    char buffer[1];\n    ASSERT_OK(_anjay_get_string_unlocked(in, buffer, sizeof(buffer)));\n    ASSERT_EQ(buffer[0], '\\0');\n    cbor_in_t *cbor_input_ctx = (cbor_in_t *) in;\n    ASSERT_TRUE(cbor_input_ctx->msg_finished);\n    ASSERT_EQ(_anjay_json_like_decoder_state(cbor_input_ctx->cbor_decoder),\n              ANJAY_JSON_LIKE_DECODER_STATE_FINISHED);\n    ASSERT_EQ(_anjay_input_get_path(in, NULL, NULL), ANJAY_GET_PATH_END);\n}\n\nAVS_UNIT_TEST(raw_cbor_in, string_indefinite_empty_string) {\n    // (_ \"\")\n    static const char RESOURCE[] = { \"\\x7F\\x60\\xFF\" };\n    TEST_ENV(RESOURCE, TEST_RESOURCE_PATH);\n    test_string_indefinite_empty_impl(in);\n    TEST_TEARDOWN;\n}\n\nAVS_UNIT_TEST(raw_cbor_in, string_indefinite_empty_struct) {\n    // (_ )\n    static const char RESOURCE[] = { \"\\x7F\\xFF\" };\n    TEST_ENV(RESOURCE, TEST_RESOURCE_PATH);\n    test_string_indefinite_empty_impl(in);\n    TEST_TEARDOWN;\n}\n\nAVS_UNIT_TEST(raw_cbor_in, string_indefinite_small_gets) {\n    // (_ \"test\", \"string\")\n    static const char RESOURCE[] = { \"\\x7F\\x64\" CHUNK1 \"\\x66\" CHUNK2 \"\\xFF\" };\n    TEST_ENV(RESOURCE, TEST_RESOURCE_PATH);\n\n    anjay_uri_path_t path;\n    ASSERT_OK(_anjay_input_get_path(in, &path, NULL));\n    ASSERT_TRUE(_anjay_uri_path_equal(&path, &TEST_RESOURCE_PATH));\n\n    char buffer[sizeof(TEST_STRING)];\n    memset(buffer, 0, sizeof(buffer));\n\n    int result;\n\n    do {\n        size_t curr_length = strlen(buffer);\n        result = _anjay_get_string_unlocked(\n                in,\n                buffer + curr_length,\n                AVS_MIN(sizeof(buffer) - curr_length, 3));\n    } while (result == ANJAY_BUFFER_TOO_SHORT);\n\n    ASSERT_OK(result);\n    ASSERT_EQ_STR(buffer, TEST_STRING);\n    cbor_in_t *cbor_input_ctx = (cbor_in_t *) in;\n    ASSERT_TRUE(cbor_input_ctx->msg_finished);\n    ASSERT_EQ(_anjay_json_like_decoder_state(cbor_input_ctx->cbor_decoder),\n              ANJAY_JSON_LIKE_DECODER_STATE_FINISHED);\n    ASSERT_EQ(_anjay_input_get_path(in, NULL, NULL), ANJAY_GET_PATH_END);\n\n    TEST_TEARDOWN;\n}\n\n#undef TEST_STRING\n#undef CHUNK1\n#undef CHUNK2\n\n#define CHUNK1 \"\\x00\\x11\\x22\\x33\\x44\\x55\"\n#define CHUNK2 \"\\x66\\x77\\x88\\x99\"\n#define TEST_BYTES (CHUNK1 CHUNK2)\n\nstatic void test_bytes_indefinite_impl(anjay_unlocked_input_ctx_t *in) {\n    anjay_uri_path_t path;\n    ASSERT_OK(_anjay_input_get_path(in, &path, NULL));\n    ASSERT_TRUE(_anjay_uri_path_equal(&path, &TEST_RESOURCE_PATH));\n\n    char buffer[sizeof(TEST_BYTES)];\n    memset(buffer, 0, sizeof(buffer));\n    size_t bytes_read = 0;\n    bool message_finished = false;\n\n    ASSERT_OK(_anjay_get_bytes_unlocked(\n            in, &bytes_read, &message_finished, buffer, sizeof(buffer)));\n    ASSERT_EQ(bytes_read, sizeof(TEST_BYTES) - 1);\n    ASSERT_TRUE(message_finished);\n    ASSERT_EQ_BYTES_SIZED(buffer, TEST_BYTES, sizeof(buffer));\n\n    cbor_in_t *cbor_input_ctx = (cbor_in_t *) in;\n    ASSERT_EQ(_anjay_json_like_decoder_state(cbor_input_ctx->cbor_decoder),\n              ANJAY_JSON_LIKE_DECODER_STATE_FINISHED);\n    ASSERT_EQ(_anjay_input_get_path(in, NULL, NULL), ANJAY_GET_PATH_END);\n}\n\nAVS_UNIT_TEST(raw_cbor_in, bytes_indefinite) {\n    // (_ h'001122334455', h'66778899')\n    static const char RESOURCE[] = { \"\\x5F\\x46\" CHUNK1 \"\\x44\" CHUNK2 \"\\xFF\" };\n    TEST_ENV(RESOURCE, TEST_RESOURCE_PATH);\n    test_bytes_indefinite_impl(in);\n    TEST_TEARDOWN;\n}\n\nAVS_UNIT_TEST(raw_cbor_in, bytes_indefinite_with_empty_strings) {\n    // (_ h'', h'001122334455', h'', h'66778899', h'')\n    static const char RESOURCE[] = { \"\\x5F\\x40\\x46\" CHUNK1 \"\\x40\\x44\" CHUNK2\n                                     \"\\x40\\xFF\" };\n    TEST_ENV(RESOURCE, TEST_RESOURCE_PATH);\n    test_bytes_indefinite_impl(in);\n    TEST_TEARDOWN;\n}\n\nAVS_UNIT_TEST(raw_cbor_in, bytes_indefinite_small_gets) {\n    // (_ h'001122334455', h'66778899')\n    static const char RESOURCE[] = { \"\\x5F\\x46\" CHUNK1 \"\\x44\" CHUNK2 \"\\xFF\" };\n    TEST_ENV(RESOURCE, TEST_RESOURCE_PATH);\n\n    anjay_uri_path_t path;\n    ASSERT_OK(_anjay_input_get_path(in, &path, NULL));\n    ASSERT_TRUE(_anjay_uri_path_equal(&path, &TEST_RESOURCE_PATH));\n\n    char buffer[sizeof(TEST_BYTES) - 1];\n    memset(buffer, 0, sizeof(buffer));\n\n    bool message_finished = false;\n    size_t total_bytes_read = 0;\n    do {\n        size_t bytes_read = 0;\n        ASSERT_OK(_anjay_get_bytes_unlocked(\n                in,\n                &bytes_read,\n                &message_finished,\n                buffer + total_bytes_read,\n                AVS_MIN(sizeof(buffer) - total_bytes_read, 3)));\n        total_bytes_read += bytes_read;\n    } while (!message_finished);\n\n    ASSERT_EQ_BYTES_SIZED(buffer, TEST_BYTES, sizeof(buffer));\n    cbor_in_t *cbor_input_ctx = (cbor_in_t *) in;\n    ASSERT_TRUE(cbor_input_ctx->msg_finished);\n    ASSERT_EQ(_anjay_json_like_decoder_state(cbor_input_ctx->cbor_decoder),\n              ANJAY_JSON_LIKE_DECODER_STATE_FINISHED);\n    ASSERT_EQ(_anjay_input_get_path(in, NULL, NULL), ANJAY_GET_PATH_END);\n\n    TEST_TEARDOWN;\n}\n\n#undef TEST_BYTES\n#undef CHUNK1\n#undef CHUNK2\n"
  },
  {
    "path": "tests/core/io/senml_cbor_encoder.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#include <avsystem/commons/avs_unit_test.h>\n\n#include <avsystem/commons/avs_memory.h>\n#include <avsystem/commons/avs_stream_outbuf.h>\n#include <avsystem/commons/avs_utils.h>\n\n#include \"src/core/io/anjay_senml_like_encoder.h\"\n\n#include <math.h>\n#include <string.h>\n\ntypedef struct cbor_test_env {\n    avs_stream_outbuf_t outbuf;\n    char *buf; // heap-allocated to make Valgrind check for out-of-bounds access\n    anjay_senml_like_encoder_t *encoder;\n} cbor_test_env_t;\n\ntypedef struct {\n    const char *data;\n    size_t size;\n} test_data_t;\n\n#define MAKE_TEST_DATA(Data)     \\\n    (test_data_t) {              \\\n        .data = Data,            \\\n        .size = sizeof(Data) - 1 \\\n    }\n\nstatic void cbor_test_setup(cbor_test_env_t *env, size_t buf_size) {\n    memcpy(&env->outbuf,\n           &AVS_STREAM_OUTBUF_STATIC_INITIALIZER,\n           sizeof(avs_stream_outbuf_t));\n    env->buf = avs_malloc(buf_size);\n    AVS_UNIT_ASSERT_NOT_NULL(env->buf);\n    avs_stream_outbuf_set_buffer(&env->outbuf, env->buf, buf_size);\n    env->encoder =\n            _anjay_senml_cbor_encoder_new((avs_stream_t *) &env->outbuf, NULL);\n    AVS_UNIT_ASSERT_NOT_NULL(env->encoder);\n}\n\n#define VERIFY_BYTES(Env, Data)                                      \\\n    do {                                                             \\\n        AVS_UNIT_ASSERT_EQUAL(avs_stream_outbuf_offset(&Env.outbuf), \\\n                              sizeof(Data) - 1);                     \\\n        AVS_UNIT_ASSERT_EQUAL_BYTES(Env.buf, Data);                  \\\n    } while (0)\n\nAVS_UNIT_TEST(senml_cbor_encoder, empty) {\n    cbor_test_env_t env;\n    cbor_test_setup(&env, 32);\n    anjay_senml_like_encoder_t *encoder = env.encoder;\n\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_senml_like_encoder_cleanup(&encoder));\n    AVS_UNIT_ASSERT_NULL(encoder);\n    VERIFY_BYTES(env, \"\\x80\");\n\n    avs_free(env.buf);\n}\n\nAVS_UNIT_TEST(senml_cbor_encoder, integer) {\n    cbor_test_env_t env;\n    cbor_test_setup(&env, 32);\n\n    anjay_senml_like_encoder_t *encoder = env.encoder;\n\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_senml_like_element_begin(encoder, NULL, NULL, NAN));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_senml_like_encode_int(encoder, 100));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_senml_like_element_end(encoder));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_senml_like_encoder_cleanup(&encoder));\n    AVS_UNIT_ASSERT_NULL(encoder);\n\n    VERIFY_BYTES(env, \"\\x81\\xA1\\x02\\x18\\x64\");\n    avs_free(env.buf);\n}\n\nAVS_UNIT_TEST(senml_cbor_encoder, unsigned_integer) {\n    cbor_test_env_t env;\n    cbor_test_setup(&env, 32);\n\n    anjay_senml_like_encoder_t *encoder = env.encoder;\n\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_senml_like_element_begin(encoder, NULL, NULL, NAN));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_senml_like_encode_uint(encoder, 100));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_senml_like_element_end(encoder));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_senml_like_encoder_cleanup(&encoder));\n    AVS_UNIT_ASSERT_NULL(encoder);\n\n    VERIFY_BYTES(env, \"\\x81\\xA1\\x02\\x18\\x64\");\n    avs_free(env.buf);\n}\n\nAVS_UNIT_TEST(senml_cbor_encoder, float) {\n    cbor_test_env_t env;\n    cbor_test_setup(&env, 32);\n\n    anjay_senml_like_encoder_t *encoder = env.encoder;\n\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_senml_like_element_begin(encoder, NULL, NULL, NAN));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_senml_like_encode_double(encoder, 100000.0));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_senml_like_element_end(encoder));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_senml_like_encoder_cleanup(&encoder));\n    AVS_UNIT_ASSERT_NULL(encoder);\n\n    VERIFY_BYTES(env, \"\\x81\\xA1\\x02\\xFA\\x47\\xC3\\x50\\x00\");\n    avs_free(env.buf);\n}\n\nAVS_UNIT_TEST(senml_cbor_encoder, double) {\n    cbor_test_env_t env;\n    cbor_test_setup(&env, 32);\n\n    anjay_senml_like_encoder_t *encoder = env.encoder;\n\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_senml_like_element_begin(encoder, NULL, NULL, NAN));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_senml_like_encode_double(encoder, 1.1));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_senml_like_element_end(encoder));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_senml_like_encoder_cleanup(&encoder));\n    AVS_UNIT_ASSERT_NULL(encoder);\n\n    VERIFY_BYTES(env, \"\\x81\\xA1\\x02\\xFB\\x3F\\xF1\\x99\\x99\\x99\\x99\\x99\\x9A\");\n    avs_free(env.buf);\n}\n\nAVS_UNIT_TEST(senml_cbor_encoder, boolean) {\n    cbor_test_env_t env;\n    cbor_test_setup(&env, 32);\n\n    anjay_senml_like_encoder_t *encoder = env.encoder;\n\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_senml_like_element_begin(encoder, NULL, NULL, NAN));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_senml_like_encode_bool(encoder, true));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_senml_like_element_end(encoder));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_senml_like_encoder_cleanup(&encoder));\n    AVS_UNIT_ASSERT_NULL(encoder);\n\n    VERIFY_BYTES(env, \"\\x81\\xA1\\x04\\xF5\");\n    avs_free(env.buf);\n}\n\nAVS_UNIT_TEST(senml_cbor_encoder, string) {\n    cbor_test_env_t env;\n    cbor_test_setup(&env, 32);\n\n    anjay_senml_like_encoder_t *encoder = env.encoder;\n\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_senml_like_element_begin(encoder, NULL, NULL, NAN));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_senml_like_encode_string(encoder, \"senml\"));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_senml_like_element_end(encoder));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_senml_like_encoder_cleanup(&encoder));\n    AVS_UNIT_ASSERT_NULL(encoder);\n\n    VERIFY_BYTES(env,\n                 \"\\x81\\xA1\\x03\\x65\"\n                 \"senml\");\n    avs_free(env.buf);\n}\n\nAVS_UNIT_TEST(senml_cbor_encoder, bytes) {\n    cbor_test_env_t env;\n    cbor_test_setup(&env, 32);\n\n    anjay_senml_like_encoder_t *encoder = env.encoder;\n\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_senml_like_element_begin(encoder, NULL, NULL, NAN));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_senml_like_bytes_begin(encoder, 5));\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_senml_like_bytes_append(encoder, \"\\x01\\x02\", 2));\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_senml_like_bytes_append(encoder, \"\\x03\\x04\\x05\", 3));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_senml_like_bytes_end(encoder));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_senml_like_element_end(encoder));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_senml_like_encoder_cleanup(&encoder));\n    AVS_UNIT_ASSERT_NULL(encoder);\n\n    VERIFY_BYTES(env, \"\\x81\\xA1\\x08\\x45\\x01\\x02\\x03\\x04\\x05\");\n    avs_free(env.buf);\n}\n\nAVS_UNIT_TEST(senml_cbor_encoder, objlnk) {\n    cbor_test_env_t env;\n    cbor_test_setup(&env, 32);\n\n    anjay_senml_like_encoder_t *encoder = env.encoder;\n\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_senml_like_element_begin(encoder, NULL, NULL, NAN));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_senml_like_encode_objlnk(encoder, \"objlnk\"));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_senml_like_element_end(encoder));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_senml_like_encoder_cleanup(&encoder));\n    AVS_UNIT_ASSERT_NULL(encoder);\n\n    VERIFY_BYTES(env,\n                 \"\\x81\\xA1\\x63\"\n                 \"vlo\"\n                 \"\\x66\"\n                 \"objlnk\");\n    avs_free(env.buf);\n}\n\nAVS_UNIT_TEST(senml_cbor_encoder, basename) {\n    cbor_test_env_t env;\n    cbor_test_setup(&env, 32);\n\n    anjay_senml_like_encoder_t *encoder = env.encoder;\n\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_senml_like_element_begin(encoder, \"bn\", NULL, NAN));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_senml_like_encode_string(encoder, \"dummy\"));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_senml_like_element_end(encoder));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_senml_like_encoder_cleanup(&encoder));\n    AVS_UNIT_ASSERT_NULL(encoder);\n\n    VERIFY_BYTES(env,\n                 \"\\x81\\xA2\\x21\\x62\"\n                 \"bn\"\n                 \"\\x03\\x65\"\n                 \"dummy\");\n    avs_free(env.buf);\n}\n\nAVS_UNIT_TEST(senml_cbor_encoder, name) {\n    cbor_test_env_t env;\n    cbor_test_setup(&env, 32);\n\n    anjay_senml_like_encoder_t *encoder = env.encoder;\n\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_senml_like_element_begin(encoder, NULL, \"n\", NAN));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_senml_like_encode_string(encoder, \"dummy\"));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_senml_like_element_end(encoder));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_senml_like_encoder_cleanup(&encoder));\n    AVS_UNIT_ASSERT_NULL(encoder);\n\n    VERIFY_BYTES(env,\n                 \"\\x81\\xA2\\x00\\x61\"\n                 \"n\"\n                 \"\\x03\\x65\"\n                 \"dummy\");\n    avs_free(env.buf);\n}\n\nAVS_UNIT_TEST(senml_cbor_encoder, basename_and_name) {\n    cbor_test_env_t env;\n    cbor_test_setup(&env, 32);\n\n    anjay_senml_like_encoder_t *encoder = env.encoder;\n\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_senml_like_element_begin(encoder, \"bn\", \"n\", NAN));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_senml_like_encode_string(encoder, \"dummy\"));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_senml_like_element_end(encoder));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_senml_like_encoder_cleanup(&encoder));\n    AVS_UNIT_ASSERT_NULL(encoder);\n\n    VERIFY_BYTES(env,\n                 \"\\x81\\xA3\\x21\\x62\"\n                 \"bn\"\n                 \"\\x00\\x61\"\n                 \"n\"\n                 \"\\x03\\x65\"\n                 \"dummy\");\n    avs_free(env.buf);\n}\n\nAVS_UNIT_TEST(senml_cbor_encoder, basename_name_and_time) {\n    cbor_test_env_t env;\n    cbor_test_setup(&env, 32);\n\n    anjay_senml_like_encoder_t *encoder = env.encoder;\n\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_senml_like_element_begin(encoder, \"bn\", \"n\", 1.0));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_senml_like_encode_string(encoder, \"dummy\"));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_senml_like_element_end(encoder));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_senml_like_encoder_cleanup(&encoder));\n    AVS_UNIT_ASSERT_NULL(encoder);\n\n    VERIFY_BYTES(env,\n                 \"\\x81\\xA4\\x21\\x62\"\n                 \"bn\"\n                 \"\\x00\\x61\"\n                 \"n\"\n                 \"\\x22\\xFA\"\n                 \"\\x3F\\x80\\x00\\x00\"\n                 \"\\x03\\x65\"\n                 \"dummy\");\n    avs_free(env.buf);\n}\n\nAVS_UNIT_TEST(senml_cbor_encoder, two_elements) {\n    cbor_test_env_t env;\n    cbor_test_setup(&env, 32);\n\n    anjay_senml_like_encoder_t *encoder = env.encoder;\n\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_senml_like_element_begin(encoder, NULL, NULL, NAN));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_senml_like_encode_int(encoder, -12));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_senml_like_element_end(encoder));\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_senml_like_element_begin(encoder, NULL, NULL, NAN));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_senml_like_encode_string(encoder, \"test\"));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_senml_like_element_end(encoder));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_senml_like_encoder_cleanup(&encoder));\n    AVS_UNIT_ASSERT_NULL(encoder);\n\n    VERIFY_BYTES(env,\n                 \"\\x82\\xA1\\x02\\x2B\\xA1\\x03\\x64\"\n                 \"test\");\n    avs_free(env.buf);\n}\n\nAVS_UNIT_TEST(senml_cbor_encoder, not_closed_element) {\n    cbor_test_env_t env;\n    cbor_test_setup(&env, 32);\n\n    anjay_senml_like_encoder_t *encoder = env.encoder;\n\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_senml_like_element_begin(encoder, NULL, \"n\", NAN));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_senml_like_encode_string(encoder, \"dummy\"));\n    AVS_UNIT_ASSERT_FAILED(_anjay_senml_like_encoder_cleanup(&encoder));\n    AVS_UNIT_ASSERT_NULL(encoder);\n\n    avs_free(env.buf);\n}\n"
  },
  {
    "path": "tests/core/io/senml_cbor_out.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#include <avsystem/commons/avs_memory.h>\n#include <avsystem/commons/avs_stream_outbuf.h>\n#include <avsystem/commons/avs_utils.h>\n\n#define AVS_UNIT_ENABLE_SHORT_ASSERTS\n#include <avsystem/commons/avs_unit_test.h>\n\n#ifdef ANJAY_WITH_LWM2M_GATEWAY\n#    include <anjay/lwm2m_gateway.h>\n#    include <string.h>\n#endif // ANJAY_WITH_LWM2M_GATEWAY\n\n#define BUFFER_SIZE 128\n\n#define TEST_ROOT_PATH (anjay_uri_path_t) ROOT_PATH_INITIALIZER()\n#define TEST_OBJ_INST(Obj, Inst) \\\n    (anjay_uri_path_t) INSTANCE_PATH_INITIALIZER(Obj, Inst)\n#define TEST_OBJ_INST_RES(Obj, Inst, Res) \\\n    (anjay_uri_path_t) RESOURCE_PATH_INITIALIZER(Obj, Inst, Res)\n#define TEST_OBJ_INST_RES_INST(Obj, Inst, Res, ResInst) \\\n    (anjay_uri_path_t)                                  \\\n            RESOURCE_INSTANCE_PATH_INITIALIZER(Obj, Inst, Res, ResInst)\n\n#define TEST_ENV(Path, ItemsCount)                                     \\\n    static char stream_buffer[BUFFER_SIZE] = { 0 };                    \\\n    avs_stream_outbuf_t stream = AVS_STREAM_OUTBUF_STATIC_INITIALIZER; \\\n    avs_stream_outbuf_set_buffer(&stream, stream_buffer, BUFFER_SIZE); \\\n    anjay_unlocked_output_ctx_t *out;                                  \\\n    const size_t items_size = (ItemsCount);                            \\\n    out = _anjay_output_senml_like_create((avs_stream_t *) &stream,    \\\n                                          &(Path),                     \\\n                                          AVS_COAP_FORMAT_SENML_CBOR,  \\\n                                          &items_size);                \\\n    ASSERT_NOT_NULL(out);                                              \\\n    ASSERT_OK(_anjay_output_set_path(out, &(Path)));\n\n#define TEST_TEARDOWN(ExpectedData)                                       \\\n    do {                                                                  \\\n        ASSERT_OK(_anjay_output_ctx_destroy(&out));                       \\\n        ASSERT_EQ_BYTES_SIZED(                                            \\\n                stream_buffer, (ExpectedData), sizeof(ExpectedData) - 1); \\\n    } while (0)\n\nAVS_UNIT_TEST(senml_cbor_out, single_resource) {\n    TEST_ENV(TEST_OBJ_INST_RES(13, 26, 1), 1);\n\n    ASSERT_OK(_anjay_ret_i64_unlocked(out, 42));\n\n    // clang-format off\n    static const char EXPECTED_DATA[] = {\n        // [\n        //   {\"bn\": \"/13/26/1\", \"v\": 42}\n        // ]\n        \"\\x81\"                                         // array(1)\n            \"\\xA2\"                                     // map(2)\n                \"\\x21\"                                 // negative(1) \"bn\"\n                \"\\x68\"                                 // text(8)\n                    \"\\x2F\\x31\\x33\\x2F\\x32\\x36\\x2F\\x31\" // \"/13/26/1\"\n                \"\\x02\"                                 // unsigned(2) \"v\"\n                \"\\x18\\x2A\"                             // unsigned(42)\n    };\n    // clang-format on\n    TEST_TEARDOWN(EXPECTED_DATA);\n}\n\nAVS_UNIT_TEST(senml_cbor_out, two_resources) {\n    TEST_ENV(TEST_OBJ_INST(13, 26), 2);\n\n    ASSERT_OK(_anjay_output_start_aggregate(out));\n    ASSERT_OK(_anjay_output_set_path(out, &TEST_OBJ_INST_RES(13, 26, 1)));\n    ASSERT_OK(_anjay_ret_i64_unlocked(out, 42));\n    ASSERT_OK(_anjay_output_set_path(out, &TEST_OBJ_INST_RES(13, 26, 2)));\n    ASSERT_OK(_anjay_ret_i64_unlocked(out, 21));\n\n    // clang-format off\n    static const char EXPECTED_DATA[] = {\n        // [\n        //   {\"bn\": \"/13/26\", \"n\": \"/1\", \"v\": 42},\n        //   {\"n\": \"/2\", \"v\": 21}\n        // ]\n        \"\\x82\"                                 // array(2)\n            \"\\xA3\"                             // map(3)\n                \"\\x21\"                         // negative(1) \"bn\"\n                \"\\x66\"                         // text(6)\n                    \"\\x2F\\x31\\x33\\x2F\\x32\\x36\" // \"/13/26\"\n                \"\\x00\"                         // unsigned(0) \"n\"\n                \"\\x62\"                         // text(2)\n                    \"\\x2F\\x31\"                 // \"/1\"\n                \"\\x02\"                         // unsigned(2) \"v\"\n                \"\\x18\\x2A\"                     // unsigned(42)\n            \"\\xA2\"                             // map(2)\n                \"\\x00\"                         // unsigned(0) \"n\"\n                \"\\x62\"                         // text(2)\n                    \"\\x2F\\x32\"                 // \"/2\"\n                \"\\x02\"                         // unsigned(2) \"v\"\n                \"\\x15\"                         // unsigned(21)\n\n    };\n    // clang-format on\n    TEST_TEARDOWN(EXPECTED_DATA);\n}\n\nAVS_UNIT_TEST(senml_cbor_out, resource_instances_nested_maps) {\n    TEST_ENV(TEST_OBJ_INST(13, 26), 3);\n\n    ASSERT_OK(_anjay_output_start_aggregate(out));\n    ASSERT_OK(_anjay_output_set_path(out, &TEST_OBJ_INST_RES(13, 26, 1)));\n    ASSERT_OK(_anjay_ret_i64_unlocked(out, 42));\n    ASSERT_OK(_anjay_output_set_path(out,\n                                     &TEST_OBJ_INST_RES_INST(13, 26, 3, 21)));\n    ASSERT_OK(_anjay_ret_double_unlocked(out, 69.68));\n    ASSERT_OK(_anjay_output_set_path(out,\n                                     &TEST_OBJ_INST_RES_INST(13, 26, 3, 37)));\n    ASSERT_OK(_anjay_ret_bool_unlocked(out, false));\n\n    // clang-format off\n    static const char EXPECTED_DATA[] = {\n        // [\n        //   {\"bn\": \"/13/26\", \"n\": \"/1\", \"v\": 42},\n        //   {\"n\": \"/3/21\", \"v\": 69.68},\n        //   {\"n\": \"/3/37\", \"vb\": false}\n        // ]\n        \"\\x83\"                                         // array(3)\n            \"\\xA3\"                                     // map(3)\n                \"\\x21\"                                 // negative(1) \"bn\"\n                \"\\x66\"                                 // text(6)\n                    \"\\x2F\\x31\\x33\\x2F\\x32\\x36\"         // \"/13/26\"\n                \"\\x00\"                                 // unsigned(0) \"n\"\n                \"\\x62\"                                 // text(2)\n                    \"\\x2F\\x31\"                         // \"/1\"\n                \"\\x02\"                                 // unsigned(2) \"v\"\n                \"\\x18\\x2A\"                             // unsigned(42)\n            \"\\xA2\"                                     // map(2)\n                \"\\x00\"                                 // unsigned(0) \"n\"\n                \"\\x65\"                                 // text(5)\n                    \"\\x2F\\x33\\x2F\\x32\\x31\"             // \"/3/21\"\n                \"\\x02\"                                 // unsigned(2) \"v\"\n                \"\\xFB\\x40\\x51\\x6B\\x85\\x1E\\xB8\\x51\\xEC\" // primitive(4634603711031169516)\n            \"\\xA2\"                                     // map(2)\n                \"\\x00\"                                 // unsigned(0) \"n\"\n                \"\\x65\"                                 // text(5)\n                    \"\\x2F\\x33\\x2F\\x33\\x37\"             // \"/3/37\"\n                \"\\x04\"                                 // unsigned(4) \"vb\"\n                \"\\xF4\"                                 // primitive(20)\n    };\n    // clang-format on\n    TEST_TEARDOWN(EXPECTED_DATA);\n}\n\nAVS_UNIT_TEST(senml_cbor_out, two_objects_one_instance_two_resources) {\n    TEST_ENV(TEST_ROOT_PATH, 4);\n\n    ASSERT_OK(_anjay_output_set_path(out, &TEST_OBJ_INST_RES(13, 26, 1)));\n    ASSERT_OK(_anjay_ret_i64_unlocked(out, 42));\n    ASSERT_OK(_anjay_output_set_path(out, &TEST_OBJ_INST_RES(13, 26, 2)));\n    ASSERT_OK(_anjay_ret_i64_unlocked(out, 21));\n\n    ASSERT_OK(_anjay_output_set_path(out, &TEST_OBJ_INST_RES(14, 27, 1)));\n    ASSERT_OK(_anjay_ret_i64_unlocked(out, 43));\n    ASSERT_OK(_anjay_output_set_path(out, &TEST_OBJ_INST_RES(14, 27, 2)));\n    ASSERT_OK(_anjay_ret_i64_unlocked(out, 22));\n\n    // clang-format off\n    static const char EXPECTED_DATA[] = {\n        // [\n        //   {\"n\": \"/13/26/1\", \"v\": 42},\n        //   {\"n\": \"/13/26/2\", \"v\": 21},\n        //   {\"n\": \"/14/27/1\", \"v\": 43},\n        //   {\"n\": \"/14/27/2\", \"v\": 22}\n        // ]\n        \"\\x84\"                                         // array(4)\n            \"\\xA2\"                                     // map(2)\n                \"\\x00\"                                 // unsigned(0) \"n\"\n                \"\\x68\"                                 // text(8)\n                    \"\\x2F\\x31\\x33\\x2F\\x32\\x36\\x2F\\x31\" // \"/13/26/1\"\n                \"\\x02\"                                 // unsigned(2) \"v\"\n                \"\\x18\\x2A\"                             // unsigned(42)\n            \"\\xA2\"                                     // map(2)\n                \"\\x00\"                                 // unsigned(0) \"n\"\n                \"\\x68\"                                 // text(8)\n                    \"\\x2F\\x31\\x33\\x2F\\x32\\x36\\x2F\\x32\" // \"/13/26/2\"\n                \"\\x02\"                                 // unsigned(2) \"v\"\n                \"\\x15\"                                 // unsigned(21)\n            \"\\xA2\"                                     // map(2)\n                \"\\x00\"                                 // unsigned(0) \"n\"\n                \"\\x68\"                                 // text(8)\n                    \"\\x2F\\x31\\x34\\x2F\\x32\\x37\\x2F\\x31\" // \"/14/27/1\"\n                \"\\x02\"                                 // unsigned(2) \"v\"\n                \"\\x18\\x2B\"                             // unsigned(43)\n            \"\\xA2\"                                     // map(2)\n                \"\\x00\"                                 // unsigned(0) \"n\"\n                \"\\x68\"                                 // text(8)\n                    \"\\x2F\\x31\\x34\\x2F\\x32\\x37\\x2F\\x32\" // \"/14/27/2\"\n                \"\\x02\"                                 // unsigned(2) \"v\"\n                \"\\x16\"                                 // unsigned(22)\n    };\n    // clang-format on\n    TEST_TEARDOWN(EXPECTED_DATA);\n}\n\n#ifdef ANJAY_WITH_LWM2M_GATEWAY\n\nstatic void set_prefix(anjay_uri_path_t *uri, const char *prefix) {\n    assert(prefix);\n    size_t prefix_len = strlen(prefix);\n    assert(prefix_len < ANJAY_GATEWAY_MAX_PREFIX_LEN);\n    strcpy((void *) uri->prefix, (const void *) prefix);\n}\n\n#    define CREATE_PATH_WITH_PREFIX(Var, Ids, Prefix) \\\n        anjay_uri_path_t(Var) = (Ids);                \\\n        set_prefix(&(Var), (Prefix));\n\nAVS_UNIT_TEST(senml_cbor_out, single_resource_with_prefix) {\n    CREATE_PATH_WITH_PREFIX(test_path, TEST_OBJ_INST_RES(13, 26, 1), \"dev1\");\n\n    TEST_ENV(test_path, 1);\n\n    ASSERT_OK(_anjay_ret_i64_unlocked(out, 42));\n\n    // clang-format off\n    static const char EXPECTED_DATA[] = {\n        // [\n        //   {\"bn\": \"/dev1/13/26/1\", \"v\": 42}\n        // ]\n        \"\\x81\"                                         // array(1)\n            \"\\xA2\"                                     // map(2)\n                \"\\x21\"                                 // negative(1) \"bn\"\n                \"\\x6D\"                                 // text(13)\n                    \"\\x2F\\x64\\x65\\x76\\x31\\x2F\"\n                        \"\\x31\\x33\\x2F\\x32\\x36\\x2F\\x31\" // \"/dev1/13/26/1\"\n                \"\\x02\"                                 // unsigned(2) \"v\"\n                \"\\x18\\x2A\"                             // unsigned(42)\n    };\n    // clang-format on\n    TEST_TEARDOWN(EXPECTED_DATA);\n}\n\nAVS_UNIT_TEST(senml_cbor_out, two_resources_with_prefix) {\n    CREATE_PATH_WITH_PREFIX(test_path1, TEST_OBJ_INST(13, 26), \"dev1\");\n    CREATE_PATH_WITH_PREFIX(test_path2, TEST_OBJ_INST_RES(13, 26, 1), \"dev1\");\n    CREATE_PATH_WITH_PREFIX(test_path3, TEST_OBJ_INST_RES(13, 26, 2), \"dev1\");\n\n    TEST_ENV(test_path1, 2);\n\n    ASSERT_OK(_anjay_output_start_aggregate(out));\n    ASSERT_OK(_anjay_output_set_path(out, &test_path2));\n    ASSERT_OK(_anjay_ret_i64_unlocked(out, 42));\n    ASSERT_OK(_anjay_output_set_path(out, &test_path3));\n    ASSERT_OK(_anjay_ret_i64_unlocked(out, 21));\n\n    // clang-format off\n    static const char EXPECTED_DATA[] = {\n        // [\n        //   {\"bn\": \"/dev1/13/26\", \"n\": \"/1\", \"v\": 42},\n        //   {\"n\": \"/2\", \"v\": 21}\n        // ]\n        \"\\x82\"                                 // array(2)\n            \"\\xA3\"                             // map(3)\n                \"\\x21\"                         // negative(1) \"bn\"\n                \"\\x6B\"                         // text(11)\n                    \"\\x2F\\x64\\x65\\x76\\x31\\x2F\"\n                        \"\\x31\\x33\\x2F\\x32\\x36\" // \"/dev1/13/26\"\n                \"\\x00\"                         // unsigned(0) \"n\"\n                \"\\x62\"                         // text(2)\n                    \"\\x2F\\x31\"                 // \"/1\"\n                \"\\x02\"                         // unsigned(2) \"v\"\n                \"\\x18\\x2A\"                     // unsigned(42)\n            \"\\xA2\"                             // map(2)\n                \"\\x00\"                         // unsigned(0) \"n\"\n                \"\\x62\"                         // text(2)\n                    \"\\x2F\\x32\"                 // \"/2\"\n                \"\\x02\"                         // unsigned(2) \"v\"\n                \"\\x15\"                         // unsigned(21)\n    };\n    // clang-format on\n    TEST_TEARDOWN(EXPECTED_DATA);\n}\n\nAVS_UNIT_TEST(senml_cbor_out, three_objects_different_prefixes) {\n    CREATE_PATH_WITH_PREFIX(test_root_path, TEST_ROOT_PATH, \"\");\n    CREATE_PATH_WITH_PREFIX(test_path1, TEST_OBJ_INST_RES(13, 26, 1), \"dev1\");\n    CREATE_PATH_WITH_PREFIX(test_path2, TEST_OBJ_INST_RES(14, 27, 1), \"dev2\");\n    CREATE_PATH_WITH_PREFIX(test_path3, TEST_OBJ_INST_RES(15, 28, 1), \"dev3\");\n\n    TEST_ENV(test_root_path, 3);\n\n    ASSERT_OK(_anjay_output_set_path(out, &test_path1));\n    ASSERT_OK(_anjay_ret_i64_unlocked(out, 42));\n    ASSERT_OK(_anjay_output_set_path(out, &test_path2));\n    ASSERT_OK(_anjay_ret_i64_unlocked(out, 43));\n    ASSERT_OK(_anjay_output_set_path(out, &test_path3));\n    ASSERT_OK(_anjay_ret_i64_unlocked(out, 44));\n\n    // clang-format off\n    static const char EXPECTED_DATA[] = {\n        // [\n        //   {\"n\": \"/dev1/13/26/1\", \"v\": 42},\n        //   {\"n\": \"/dev2/14/27/1\", \"v\": 43},\n        //   {\"n\": \"/dev3/15/28/1\", \"v\": 44}\n        // ]\n        \"\\x83\"                                         // array(3)\n            \"\\xA2\"                                     // map(2)\n                \"\\x00\"                                 // unsigned(0) \"n\"\n                \"\\x6D\"                                 // text(13)\n                    \"\\x2F\\x64\\x65\\x76\\x31\\x2F\"\n                        \"\\x31\\x33\\x2F\\x32\\x36\\x2F\\x31\" // \"/dev1/13/26/1\"\n                \"\\x02\"                                 // unsigned(2) \"v\"\n                \"\\x18\\x2A\"                             // unsigned(42)\n            \"\\xA2\"                                     // map(2)\n                \"\\x00\"                                 // unsigned(0) \"n\"\n                \"\\x6D\"                                 // text(13)\n                    \"\\x2F\\x64\\x65\\x76\\x32\\x2F\"\n                        \"\\x31\\x34\\x2F\\x32\\x37\\x2F\\x31\" // \"/dev2/14/27/1\"\n                \"\\x02\"                                 // unsigned(2) \"v\"\n                \"\\x18\\x2B\"                             // unsigned(43)\n            \"\\xA2\"                                     // map(2)\n                \"\\x00\"                                 // unsigned(0) \"n\"\n                \"\\x6D\"                                 // text(13)\n                    \"\\x2F\\x64\\x65\\x76\\x33\\x2F\"\n                        \"\\x31\\x35\\x2F\\x32\\x38\\x2F\\x31\" // \"/dev3/15/28/1\"\n                \"\\x02\"                                 // unsigned(2) \"v\"\n                \"\\x18\\x2C\"                             // unsigned(44)\n    };\n    // clang-format on\n    TEST_TEARDOWN(EXPECTED_DATA);\n}\n\nAVS_UNIT_TEST(senml_cbor_out, root_path_with_prefix) {\n    CREATE_PATH_WITH_PREFIX(test_root_path, TEST_ROOT_PATH, \"dev1\");\n    CREATE_PATH_WITH_PREFIX(\n            test_path1, TEST_OBJ_INST_RES_INST(13, 26, 1, 7), \"dev1\");\n    CREATE_PATH_WITH_PREFIX(\n            test_path2, TEST_OBJ_INST_RES_INST(13, 26, 1, 8), \"dev1\");\n\n    TEST_ENV(test_root_path, 2);\n\n    ASSERT_OK(_anjay_output_clear_path(out));\n    ASSERT_OK(_anjay_output_set_path(out, &test_path1));\n    ASSERT_OK(_anjay_ret_i64_unlocked(out, 42));\n    ASSERT_OK(_anjay_output_set_path(out, &test_path2));\n    ASSERT_OK(_anjay_ret_i64_unlocked(out, 21));\n\n    // clang-format off\n    static const char EXPECTED_DATA[] = {\n        // [\n        //   {\"bn\": \"/dev1\", \"n\": \"/13/26/1/7\", \"v\": 42},\n        //   {\"n\": \"/13/26/1/8\", \"v\": 21}\n        // ]\n        \"\\x82\"                                         // array(2)\n            \"\\xA3\"                                     // map(3)\n                \"\\x21\"                                 // negative(1) \"bn\"\n                \"\\x65\"                                 // text(5)\n                    \"\\x2F\\x64\\x65\\x76\\x31\"             // \"/dev1\"\n                \"\\x00\"                                 // unsigned(0) \"n\"\n                \"\\x6a\"                                 // text(10)\n                    \"\\x2F\\x31\\x33\\x2F\\x32\"\n                        \"\\x36\\x2F\\x31\\x2F\\x37\"         // \"/13/26/1/7\"\n                \"\\x02\"                                 // unsigned(2) \"v\"\n                \"\\x18\\x2A\"                             // unsigned(42)\n            \"\\xA2\"                                     // map(2)\n                \"\\x00\"                                 // unsigned(0) \"n\"\n                \"\\x6A\"                                 // text(10)\n                    \"\\x2F\\x31\\x33\\x2F\\x32\"\n                        \"\\x36\\x2F\\x31\\x2F\\x38\"         // \"/13/26/1/8\"\n                \"\\x02\"                                 // unsigned(2) \"v\"\n                \"\\x15\"                                 // unsigned(21)\n    };\n    // clang-format on\n    TEST_TEARDOWN(EXPECTED_DATA);\n}\n\nAVS_UNIT_TEST(senml_cbor_out, mixed_data_end_device_with_gateway) {\n    CREATE_PATH_WITH_PREFIX(test_root_path, TEST_ROOT_PATH, \"\");\n    CREATE_PATH_WITH_PREFIX(test_path1, TEST_OBJ_INST_RES(13, 26, 1), \"dev1\");\n    CREATE_PATH_WITH_PREFIX(test_path2, TEST_OBJ_INST_RES(14, 27, 1), \"\");\n\n    TEST_ENV(test_root_path, 2);\n\n    ASSERT_OK(_anjay_output_set_path(out, &test_path1));\n    ASSERT_OK(_anjay_ret_i64_unlocked(out, 42));\n    ASSERT_OK(_anjay_output_set_path(out, &test_path2));\n    ASSERT_OK(_anjay_ret_i64_unlocked(out, 21));\n\n    // clang-format off\n    static const char EXPECTED_DATA[] = {\n        // [\n        //   {\"n\": \"/dev1/13/26/1\", \"v\": 42},\n        //   {\"n\": \"/14/27/1\", \"v\": 21}\n        // ]\n        \"\\x82\"                                         // array(2)\n            \"\\xA2\"                                     // map(2)\n                \"\\x00\"                                 // unsigned(0) \"n\"\n                \"\\x6D\"                                 // text(13)\n                    \"\\x2F\\x64\\x65\\x76\\x31\\x2F\\x31\"\n                        \"\\x33\\x2F\\x32\\x36\\x2F\\x31\"     // \"dev1/13/26/1\"\n                \"\\x02\"                                 // unsigned(2) \"v\"\n                \"\\x18\\x2A\"                             // unsigned(42)\n            \"\\xA2\"                                     // map(2)\n                \"\\x00\"                                 // unsigned(0) \"n\"\n                \"\\x68\"                                 // text(8)\n                    \"\\x2F\\x31\\x34\\x2F\\x32\\x37\\x2F\\x31\" // \"/14/27/1\"\n                \"\\x02\"                                 // unsigned(2) \"v\"\n                \"\\x15\"                                 // unsigned(21)\n    };\n    // clang-format on\n    TEST_TEARDOWN(EXPECTED_DATA);\n}\n\n#endif // ANJAY_WITH_LWM2M_GATEWAY\n"
  },
  {
    "path": "tests/core/io/senml_in_common.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ANJAY_IO_TEST_SENML_IN_COMMON_H\n#define ANJAY_IO_TEST_SENML_IN_COMMON_H\n\n#define AVS_UNIT_ENABLE_SHORT_ASSERTS\n#include <avsystem/commons/avs_unit_test.h>\n\n#include <anjay_modules/anjay_dm_utils.h>\n\n#define TEST_TEARDOWN(ExpectedResult)                                       \\\n    do {                                                                    \\\n        AVS_CONCAT(ASSERT_, ExpectedResult)(_anjay_input_ctx_destroy(&in)); \\\n    } while (0)\n\n#ifdef ANJAY_WITH_LWM2M_GATEWAY\n#    define URI_EQUAL(path, expected_path)                       \\\n        ASSERT_TRUE(_anjay_uri_path_equal(path, expected_path)); \\\n        ASSERT_TRUE(_anjay_uri_path_prefix_equal(path, expected_path));\n#else // ANJAY_WITH_LWM2M_GATEWAY\n#    define URI_EQUAL(path, expected_path) \\\n        ASSERT_TRUE(_anjay_uri_path_equal(path, expected_path));\n#endif // ANJAY_WITH_LWM2M_GATEWAY\n\nstatic const anjay_uri_path_t TEST_RESOURCE_PATH =\n        RESOURCE_PATH_INITIALIZER(13, 26, 1);\n\nstatic const anjay_uri_path_t TEST_INSTANCE_PATH =\n        INSTANCE_PATH_INITIALIZER(13, 26);\n\n#ifdef ANJAY_WITH_LWM2M_GATEWAY\nstatic const anjay_uri_path_t TEST_RESOURCE_PATH_WITH_PREFIX =\n        RESOURCE_PATH_INITIALIZER_WITH_PREFIX(\"0aapud0\", 13, 26, 1);\n\nstatic const anjay_uri_path_t TEST_INSTANCE_PATH_WITH_PREFIX =\n        INSTANCE_PATH_INITIALIZER_WITH_PREFIX(\"0aapud0\", 13, 26);\n#endif // ANJAY_WITH_LWM2M_GATEWAY\n\nstatic void check_path(anjay_unlocked_input_ctx_t *in,\n                       const anjay_uri_path_t *expected_path,\n                       int64_t expected_value) {\n    anjay_uri_path_t path;\n    int64_t value;\n    ASSERT_OK(_anjay_input_get_path(in, &path, NULL));\n    URI_EQUAL(&path, expected_path);\n\n    // cached value\n    ASSERT_OK(_anjay_input_get_path(in, &path, NULL));\n    URI_EQUAL(&path, expected_path);\n\n    ASSERT_OK(_anjay_get_i64_unlocked(in, &value));\n    ASSERT_EQ(value, expected_value);\n}\n\nstatic void check_paths(anjay_unlocked_input_ctx_t *in,\n                        const anjay_uri_path_t *expected_paths,\n                        const size_t paths_count) {\n    for (size_t i = 0; i < paths_count; i++) {\n        check_path(in, &expected_paths[i], 42 + (int) i);\n        ASSERT_OK(_anjay_input_next_entry(in));\n    }\n    ASSERT_OK(_anjay_input_next_entry(in));\n    ASSERT_EQ(_anjay_input_get_path(in, NULL, NULL), ANJAY_GET_PATH_END);\n    ASSERT_EQ(_anjay_json_like_decoder_state(((senml_in_t *) in)->ctx),\n              ANJAY_JSON_LIKE_DECODER_STATE_FINISHED);\n}\n\nstatic void\ntest_single_instance_but_more_than_one(anjay_unlocked_input_ctx_t *in,\n                                       const anjay_uri_path_t *expected_path) {\n    check_path(in, expected_path, 42);\n    ASSERT_OK(_anjay_input_next_entry(in));\n    // The resource is there, but the context doesn't return it because it\n    // is not related to the request resource path /13/26/1. In order to\n    // actually get it, we would have to do a request on an instance.\n    // Because the context top-level path is restricted, obtaining next id\n    // results in error.\n    ASSERT_EQ(_anjay_input_get_path(in, NULL, NULL), ANJAY_ERR_BAD_REQUEST);\n}\n\nstatic void test_skipping(anjay_unlocked_input_ctx_t *in,\n                          const anjay_uri_path_t *expected_paths,\n                          size_t paths_count) {\n    ASSERT_EQ(paths_count, 2);\n\n    anjay_uri_path_t path;\n    ASSERT_OK(_anjay_input_get_path(in, &path, NULL));\n    URI_EQUAL(&path, &expected_paths[0]);\n\n    // we may not like this resource for some reason, let's skip its value\n    ASSERT_OK(_anjay_input_next_entry(in));\n\n    check_path(in, &expected_paths[1], 43);\n\n    ASSERT_OK(_anjay_input_next_entry(in));\n    ASSERT_EQ(_anjay_input_get_path(in, NULL, NULL), ANJAY_GET_PATH_END);\n}\n\n#endif /* ANJAY_IO_TEST_SENML_IN_COMMON_H */\n"
  },
  {
    "path": "tests/core/io/senml_json_encoder.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n#include <anjay_init.h>\n\n#include <avsystem/commons/avs_unit_test.h>\n\n#include <avsystem/commons/avs_memory.h>\n#include <avsystem/commons/avs_stream_outbuf.h>\n#include <avsystem/commons/avs_utils.h>\n\n#include \"src/core/io/anjay_senml_like_encoder.h\"\n\n#include \"src/core/coap/anjay_content_format.h\"\n\n#include <math.h>\n#include <string.h>\n\ntypedef struct json_test_env {\n    avs_stream_outbuf_t outbuf;\n    char *buf; // heap-allocated to make Valgrind check for out-of-bounds access\n    anjay_senml_like_encoder_t *encoder;\n} json_test_env_t;\n\ntypedef struct {\n    const char *data;\n    size_t size;\n} test_data_t;\n\n#define MAKE_TEST_DATA(Data)     \\\n    (test_data_t) {              \\\n        .data = Data,            \\\n        .size = sizeof(Data) - 1 \\\n    }\n\nstatic void json_test_setup(json_test_env_t *env, size_t buf_size) {\n    memcpy(&env->outbuf,\n           &AVS_STREAM_OUTBUF_STATIC_INITIALIZER,\n           sizeof(avs_stream_outbuf_t));\n    env->buf = avs_calloc(1, buf_size);\n    AVS_UNIT_ASSERT_NOT_NULL(env->buf);\n    avs_stream_outbuf_set_buffer(&env->outbuf, env->buf, buf_size);\n    env->encoder = _anjay_senml_json_encoder_new((avs_stream_t *) &env->outbuf);\n    AVS_UNIT_ASSERT_NOT_NULL(env->encoder);\n}\n\n#define VERIFY_BYTES(Env, Data)                                      \\\n    do {                                                             \\\n        AVS_UNIT_ASSERT_EQUAL(avs_stream_outbuf_offset(&Env.outbuf), \\\n                              sizeof(Data) - 1);                     \\\n        AVS_UNIT_ASSERT_EQUAL_BYTES(Env.buf, Data);                  \\\n    } while (0)\n\nstatic void verify_bytes(json_test_env_t *env, test_data_t *data) {\n    AVS_UNIT_ASSERT_EQUAL(avs_stream_outbuf_offset(&env->outbuf), data->size);\n    AVS_UNIT_ASSERT_EQUAL_BYTES_SIZED(env->buf, data->data, data->size);\n}\n\nAVS_UNIT_TEST(senml_json_encoder, empty) {\n    json_test_env_t env;\n    json_test_setup(&env, 32);\n    anjay_senml_like_encoder_t *encoder = env.encoder;\n\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_senml_like_encoder_cleanup(&encoder));\n    AVS_UNIT_ASSERT_NULL(encoder);\n\n    VERIFY_BYTES(env, \"[]\");\n    avs_free(env.buf);\n}\n\nstatic void test_int(int64_t value, test_data_t *data) {\n    json_test_env_t env;\n    json_test_setup(&env, 32);\n\n    anjay_senml_like_encoder_t *encoder = env.encoder;\n\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_senml_like_element_begin(encoder, NULL, NULL, NAN));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_senml_like_encode_int(encoder, value));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_senml_like_element_end(encoder));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_senml_like_encoder_cleanup(&encoder));\n    AVS_UNIT_ASSERT_NULL(encoder);\n\n    verify_bytes(&env, data);\n    avs_free(env.buf);\n};\n\n#define TEST_INT_IMPL(Name, Num, Data)           \\\n    AVS_UNIT_TEST(senml_json_encoder, Name) {    \\\n        test_data_t data = MAKE_TEST_DATA(Data); \\\n        test_int(Num, &data);                    \\\n    }\n\n#define TEST_INT(Num, Data) TEST_INT_IMPL(AVS_CONCAT(int, __LINE__), Num, Data);\n\nTEST_INT(0, \"[{\\\"v\\\":0}]\")\nTEST_INT(INT16_MAX, \"[{\\\"v\\\":32767}]\")\nTEST_INT(UINT16_MAX, \"[{\\\"v\\\":65535}]\")\nTEST_INT(INT32_MAX, \"[{\\\"v\\\":2147483647}]\")\nTEST_INT(UINT32_MAX, \"[{\\\"v\\\":4294967295}]\")\nTEST_INT(INT64_MAX, \"[{\\\"v\\\":9223372036854775807}]\")\nTEST_INT(-1, \"[{\\\"v\\\":-1}]\")\nTEST_INT(INT64_MIN, \"[{\\\"v\\\":-9223372036854775808}]\")\n\nAVS_UNIT_TEST(senml_json_encoder, uint64_max) {\n    json_test_env_t env;\n    json_test_setup(&env, 32);\n    anjay_senml_like_encoder_t *encoder = env.encoder;\n\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_senml_like_element_begin(encoder, NULL, NULL, NAN));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_senml_like_encode_uint(encoder, UINT64_MAX));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_senml_like_element_end(encoder));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_senml_like_encoder_cleanup(&encoder));\n    AVS_UNIT_ASSERT_NULL(encoder);\n\n    VERIFY_BYTES(env, \"[{\\\"v\\\":18446744073709551615}]\");\n    avs_free(env.buf);\n}\n\n#define TEST_BOOL(Val, Data)                                                  \\\n    AVS_UNIT_TEST(senml_json_encoder, bool_##Val) {                           \\\n        json_test_env_t env;                                                  \\\n        json_test_setup(&env, 32);                                            \\\n        anjay_senml_like_encoder_t *encoder = env.encoder;                    \\\n        test_data_t expected = MAKE_TEST_DATA(Data);                          \\\n                                                                              \\\n        AVS_UNIT_ASSERT_SUCCESS(                                              \\\n                _anjay_senml_like_element_begin(encoder, NULL, NULL, NAN));   \\\n        AVS_UNIT_ASSERT_SUCCESS(_anjay_senml_like_encode_bool(encoder, Val)); \\\n        AVS_UNIT_ASSERT_SUCCESS(_anjay_senml_like_element_end(encoder));      \\\n        AVS_UNIT_ASSERT_SUCCESS(_anjay_senml_like_encoder_cleanup(&encoder)); \\\n                                                                              \\\n        verify_bytes(&env, &expected);                                        \\\n        avs_free(env.buf);                                                    \\\n    }\n\nTEST_BOOL(true, \"[{\\\"vb\\\":true}]\")\nTEST_BOOL(false, \"[{\\\"vb\\\":false}]\")\nTEST_BOOL(1, \"[{\\\"vb\\\":true}]\")\nTEST_BOOL(0, \"[{\\\"vb\\\":false}]\")\nTEST_BOOL(42, \"[{\\\"vb\\\":true}]\")\n\nAVS_UNIT_TEST(senml_json_encoder, simple_element) {\n    json_test_env_t env;\n    json_test_setup(&env, 32);\n    anjay_senml_like_encoder_t *encoder = env.encoder;\n    test_data_t expected = MAKE_TEST_DATA(\"[{\\\"v\\\":123}]\");\n\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_senml_like_element_begin(encoder, NULL, NULL, NAN));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_senml_like_encode_int(encoder, 123));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_senml_like_element_end(encoder));\n\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_senml_like_encoder_cleanup(&encoder));\n\n    verify_bytes(&env, &expected);\n    avs_free(env.buf);\n}\n\nstatic void test_string(const char *input, test_data_t *expected) {\n    json_test_env_t env;\n    json_test_setup(&env, 512);\n    anjay_senml_like_encoder_t *encoder = env.encoder;\n\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_senml_like_element_begin(encoder, NULL, NULL, NAN));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_senml_like_encode_string(encoder, input));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_senml_like_element_end(encoder));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_senml_like_encoder_cleanup(&encoder));\n\n    verify_bytes(&env, expected);\n    avs_free(env.buf);\n}\n\n#define TEST_STRING_NAMED_EXPLICIT(Name, Text, Expected) \\\n    AVS_UNIT_TEST(senml_json_encoder, string_##Name) {   \\\n        test_data_t expected = MAKE_TEST_DATA(Expected); \\\n        test_string(Text, &expected);                    \\\n    }\n\n#define TEST_STRING_NAMED(Name, Text) \\\n    TEST_STRING_NAMED_EXPLICIT(Name, Text, \"[{\\\"vs\\\":\\\"\" Text \"\\\"}]\")\n\nTEST_STRING_NAMED(empty, \"\")\nTEST_STRING_NAMED(\n        256chars,\n        \"oxazxnwrmthhloqwchkumektviptdztidxeelvgffcdoodpijsbikkkvrmtrxddmpidudj\"\n        \"ptfmqqgfkjlrsqrmagculcyjjbmxombbiqdhimwafcfaswhmmykezictjpidmxtoqnjmja\"\n        \"xzgvqdybtgneqsmlzhxqeuhibjopnregwykgpcdogguszhhffdeixispwfnwcufnmsxycy\"\n        \"qxquiqsuqwgkwafkeedsacxvvjwhpokaabxelqxzqutwab\");\nTEST_STRING_NAMED_EXPLICIT(escaped, \"\\\"\\\\\", \"[{\\\"vs\\\":\\\"\\\\\\\"\\\\\\\\\\\"}]\")\nTEST_STRING_NAMED_EXPLICIT(del, \"\\x7F\", \"[{\\\"vs\\\":\\\"\\\\u007f\\\"}]\")\n\nstatic void test_float(float value) {\n    json_test_env_t env;\n    json_test_setup(&env, 32);\n    anjay_senml_like_encoder_t *encoder = env.encoder;\n\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_senml_like_element_begin(encoder, NULL, NULL, NAN));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_senml_like_encode_double(encoder, value));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_senml_like_element_end(encoder));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_senml_like_encoder_cleanup(&encoder));\n\n    AVS_UNIT_ASSERT_EQUAL(strlen(env.buf),\n                          avs_stream_outbuf_offset(&env.outbuf));\n    float decoded;\n    char brace, bracket, nullbyte;\n    AVS_UNIT_ASSERT_EQUAL(sscanf(env.buf,\n                                 \"[{\\\"v\\\":%f%c%c%c\",\n                                 &decoded,\n                                 &brace,\n                                 &bracket,\n                                 &nullbyte),\n                          3);\n    AVS_UNIT_ASSERT_EQUAL(decoded, value);\n    AVS_UNIT_ASSERT_EQUAL(brace, '}');\n    AVS_UNIT_ASSERT_EQUAL(bracket, ']');\n\n    avs_free(env.buf);\n}\n\n#define TEST_FLOAT_IMPL(Name, Type, Num)      \\\n    AVS_UNIT_TEST(senml_json_encoder, Name) { \\\n        test_float(Num);                      \\\n    }\n\n#define TEST_FLOAT(Num) TEST_FLOAT_IMPL(AVS_CONCAT(float, __LINE__), float, Num)\n\nTEST_FLOAT(0.0)\nTEST_FLOAT(-0.0)\nTEST_FLOAT(1.0)\nTEST_FLOAT(100000.0)\nTEST_FLOAT(1.125)\n\nstatic void test_double(double value) {\n    json_test_env_t env;\n    json_test_setup(&env, 32);\n    anjay_senml_like_encoder_t *encoder = env.encoder;\n\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_senml_like_element_begin(encoder, NULL, NULL, NAN));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_senml_like_encode_double(encoder, value));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_senml_like_element_end(encoder));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_senml_like_encoder_cleanup(&encoder));\n\n    AVS_UNIT_ASSERT_EQUAL(strlen(env.buf),\n                          avs_stream_outbuf_offset(&env.outbuf));\n    double decoded;\n    char brace, bracket, nullbyte;\n    AVS_UNIT_ASSERT_EQUAL(sscanf(env.buf,\n                                 \"[{\\\"v\\\":%lf%c%c%c\",\n                                 &decoded,\n                                 &brace,\n                                 &bracket,\n                                 &nullbyte),\n                          3);\n    AVS_UNIT_ASSERT_EQUAL(decoded, value);\n    AVS_UNIT_ASSERT_EQUAL(brace, '}');\n    AVS_UNIT_ASSERT_EQUAL(bracket, ']');\n\n    avs_free(env.buf);\n}\n\n#define TEST_DOUBLE_IMPL(Name, Type, Num)     \\\n    AVS_UNIT_TEST(senml_json_encoder, Name) { \\\n        test_double(Num);                     \\\n    }\n\n#define TEST_DOUBLE(Num) \\\n    TEST_DOUBLE_IMPL(AVS_CONCAT(double, __LINE__), double, Num)\n\nTEST_DOUBLE(1.1)\nTEST_DOUBLE(100000.0)\nTEST_DOUBLE(1.0e+300)\nTEST_DOUBLE(-4.1)\n\nstatic void test_bytes(test_data_t *input, test_data_t *expected) {\n    json_test_env_t env;\n    json_test_setup(&env, 512);\n    anjay_senml_like_encoder_t *encoder = env.encoder;\n\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_senml_like_element_begin(encoder, NULL, NULL, NAN));\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_senml_like_bytes_begin(encoder, input->size));\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_senml_like_bytes_append(encoder, input->data, input->size));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_senml_like_bytes_end(encoder));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_senml_like_element_end(encoder));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_senml_like_encoder_cleanup(&encoder));\n\n    verify_bytes(&env, expected);\n    avs_free(env.buf);\n}\n\n#define TEST_BYTES(Name, Data, Expected)                 \\\n    AVS_UNIT_TEST(senml_json_encoder, bytes_##Name) {    \\\n        test_data_t input = MAKE_TEST_DATA(Data);        \\\n        test_data_t expected = MAKE_TEST_DATA(Expected); \\\n        test_bytes(&input, &expected);                   \\\n    }\n\nTEST_BYTES(0bytes, \"\", \"[{\\\"vd\\\":\\\"\\\"}]\")\nTEST_BYTES(4bytes, \"\\x01\\x02\\x03\\x04\", \"[{\\\"vd\\\":\\\"AQIDBA\\\"}]\")\nTEST_BYTES(256bytes,\n           \"\\xD8\\xE2\\xE6\\xED\\x90\\x05\\x29\\x3B\\x17\\xAC\\x8D\\x33\\x93\\x52\\xD9\\x6B\"\n           \"\\xF2\\xFB\\x20\\x74\\x3E\\x9C\\xEF\\xAD\\xBB\\x03\\xCE\\x0E\\xC5\\xBD\\x0D\\x2F\"\n           \"\\x42\\x6D\\x1C\\xD6\\xDB\\x29\\xF8\\xF6\\xA4\\x96\\x3D\\x7A\\x8A\\xEE\\xE6\\xF2\"\n           \"\\x56\\x1C\\xBE\\xCE\\x71\\x30\\x3B\\xEC\\xC9\\x86\\x71\\x96\\x86\\x51\\xA2\\xCA\"\n           \"\\x23\\x8A\\x0B\\x1D\\x67\\x3C\\x50\\xB8\\x66\\x4C\\x64\\x8C\\x31\\xCD\\x11\\x05\"\n           \"\\xCA\\x56\\x4B\\xBB\\x79\\x18\\x8F\\x5B\\xF1\\xE0\\x1E\\x85\\x38\\xBE\\x7A\\x6F\"\n           \"\\x30\\x4A\\xFD\\xB3\\x1B\\xA9\\x52\\xB4\\x0E\\x95\\x73\\x83\\xA5\\x33\\x9F\\x0C\"\n           \"\\x04\\x2E\\x33\\xB3\\xD5\\x0B\\x6E\\x02\\x0C\\xC7\\x0D\\x1A\\x1A\\x48\\x0C\\x92\"\n           \"\\x1B\\x62\\x83\\xCF\\xC1\\x5C\\x90\\xBC\\x83\\x3B\\x92\\xBF\\x8E\\xCE\\x7C\\xD6\"\n           \"\\x99\\x77\\xF2\\x66\\x92\\x0C\\xC6\\x0A\\x11\\x80\\xBE\\x03\\x59\\x23\\x89\\xF6\"\n           \"\\xEF\\x3A\\x5A\\x07\\xEB\\xEF\\x47\\xF0\\x1F\\xF0\\xB4\\x96\\x01\\x1B\\xE9\\x51\"\n           \"\\x40\\x70\\x16\\xDD\\xB2\\x9B\\xEB\\x42\\xAC\\x6E\\x45\\xE6\\xAE\\x8F\\xCE\\x9A\"\n           \"\\xC4\\xCB\\x09\\xE7\\x2C\\xE4\\x48\\x86\\xF0\\x9C\\x56\\x2C\\xEF\\x1B\\xD0\\x8E\"\n           \"\\x92\\xD4\\x61\\x15\\x46\\x76\\x19\\x32\\xDF\\x9F\\x98\\xC0\\x0A\\xF7\\xAE\\xA9\"\n           \"\\xD7\\x61\\xEC\\x8B\\x78\\xE5\\xAA\\xC6\\x0B\\x5D\\x98\\x1D\\x86\\xE6\\x57\\x67\"\n           \"\\x97\\x56\\x82\\x29\\xFF\\x8F\\x61\\x6C\\xA5\\xD0\\x08\\x20\\xAE\\x49\\x5B\\x04\",\n           \"[{\\\"vd\\\":\\\"2OLm7ZAFKTsXrI0zk1LZa_\"\n           \"L7IHQ-nO-tuwPODsW9DS9CbRzW2yn49qSWPXqK7ubyVhy-znEwO-\"\n           \"zJhnGWhlGiyiOKCx1nPFC4ZkxkjDHNEQXKVku7eRiPW_\"\n           \"HgHoU4vnpvMEr9sxupUrQOlXODpTOfDAQuM7PVC24CDMcNGhpIDJIbYoPPwVyQvIM7k\"\n           \"r-OznzWmXfyZpIMxgoRgL4DWSOJ9u86Wgfr70fwH_\"\n           \"C0lgEb6VFAcBbdspvrQqxuReauj86axMsJ5yzkSIbwnFYs7xvQjpLUYRVGdhky35-\"\n           \"YwAr3rqnXYeyLeOWqxgtdmB2G5ldnl1aCKf-PYWyl0AggrklbBA\\\"}]\")\n\nAVS_UNIT_TEST(senml_json_encoder, objlnk) {\n    json_test_env_t env;\n    json_test_setup(&env, 32);\n    anjay_senml_like_encoder_t *encoder = env.encoder;\n\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_senml_like_element_begin(encoder, NULL, NULL, NAN));\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_senml_like_encode_objlnk(encoder, \"012345:678901\"));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_senml_like_element_end(encoder));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_senml_like_encoder_cleanup(&encoder));\n\n    VERIFY_BYTES(env, \"[{\\\"vlo\\\":\\\"012345:678901\\\"}]\");\n    avs_free(env.buf);\n}\n\nAVS_UNIT_TEST(senml_json_encoder, time) {\n    json_test_env_t env;\n    json_test_setup(&env, 32);\n    anjay_senml_like_encoder_t *encoder = env.encoder;\n\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_senml_like_element_begin(encoder, NULL, NULL, 1.234));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_senml_like_element_end(encoder));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_senml_like_encoder_cleanup(&encoder));\n\n    VERIFY_BYTES(env, \"[{\\\"bt\\\":1.234}]\");\n    avs_free(env.buf);\n}\n\nAVS_UNIT_TEST(senml_json_encoder, array_with_one_empty_element) {\n    json_test_env_t env;\n    json_test_setup(&env, 32);\n    anjay_senml_like_encoder_t *encoder = env.encoder;\n\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_senml_like_element_begin(encoder, NULL, NULL, NAN));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_senml_like_element_end(encoder));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_senml_like_encoder_cleanup(&encoder));\n\n    VERIFY_BYTES(env, \"[{}]\");\n    avs_free(env.buf);\n}\n\nAVS_UNIT_TEST(senml_json_encoder, array_with_two_empty_elements) {\n    json_test_env_t env;\n    json_test_setup(&env, 32);\n    anjay_senml_like_encoder_t *encoder = env.encoder;\n\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_senml_like_element_begin(encoder, NULL, NULL, NAN));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_senml_like_element_end(encoder));\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_senml_like_element_begin(encoder, NULL, NULL, NAN));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_senml_like_element_end(encoder));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_senml_like_encoder_cleanup(&encoder));\n\n    VERIFY_BYTES(env, \"[{},{}]\");\n    avs_free(env.buf);\n}\n\nAVS_UNIT_TEST(senml_json_encoder, array_with_four_elements) {\n    json_test_env_t env;\n    json_test_setup(&env, 256);\n    anjay_senml_like_encoder_t *encoder = env.encoder;\n\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_senml_like_element_begin(encoder, \"basename\", NULL, NAN));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_senml_like_encode_int(encoder, 123));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_senml_like_element_end(encoder));\n\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_senml_like_element_begin(encoder, \"basename\", \"name\", NAN));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_senml_like_encode_double(encoder, 1.0));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_senml_like_element_end(encoder));\n\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_senml_like_element_begin(encoder, NULL, \"name\", NAN));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_senml_like_encode_bool(encoder, true));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_senml_like_element_end(encoder));\n\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_senml_like_element_begin(\n            encoder, \"basename\", \"name\", 2.125));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_senml_like_encode_string(encoder, \"dummy\"));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_senml_like_element_end(encoder));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_senml_like_encoder_cleanup(&encoder));\n\n    VERIFY_BYTES(env,\n                 \"[{\\\"bn\\\":\\\"basename\\\",\\\"v\\\":123},{\\\"bn\\\":\\\"basename\\\",\"\n                 \"\\\"n\\\":\\\"name\\\",\\\"v\\\":1},{\\\"n\\\":\\\"name\\\",\\\"vb\\\":true},{\\\"bn\\\":\"\n                 \"\\\"basename\\\",\\\"n\\\":\\\"name\\\",\\\"bt\\\":2.125,\\\"vs\\\":\\\"dummy\\\"}]\");\n\n    avs_free(env.buf);\n}\n"
  },
  {
    "path": "tests/core/io/text.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#include <avsystem/commons/avs_stream.h>\n#include <avsystem/commons/avs_stream_inbuf.h>\n#include <avsystem/commons/avs_unit_test.h>\n\n#include \"tests/utils/utils.h\"\n\n/////////////////////////////////////////////////////////////////////// ENCODING\n\nstatic void text_out_destroy(anjay_unlocked_ret_bytes_ctx_t ***ctx) {\n    if (ctx && *ctx && **ctx) {\n        _anjay_base64_ret_bytes_ctx_delete(*ctx);\n    }\n}\n\n#define TEST_ENV(Size)                                                 \\\n    char buf[Size];                                                    \\\n    avs_stream_outbuf_t outbuf = AVS_STREAM_OUTBUF_STATIC_INITIALIZER; \\\n    text_out_t out = { { &TEXT_OUT_VTABLE, 0 },                        \\\n                       NULL,                                           \\\n                       (avs_stream_t *) &outbuf,                       \\\n                       STATE_PATH_SET };                               \\\n    SCOPED_PTR(anjay_unlocked_ret_bytes_ctx_t *, text_out_destroy)     \\\n    _ret_bytes = &out.bytes;                                           \\\n    (void) _ret_bytes;                                                 \\\n    avs_stream_outbuf_set_buffer(&outbuf, buf, sizeof(buf))\n\nstatic void stringify_buf(avs_stream_outbuf_t *outbuf) {\n    outbuf->message_finished = 0;\n    AVS_UNIT_ASSERT_SUCCESS(avs_stream_write((avs_stream_t *) outbuf, \"\", 1));\n}\n\nAVS_UNIT_TEST(text_out, string) {\n    TEST_ENV(512);\n    static const char TEST_STRING[] = \"Hello, world!\";\n\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_ret_string_unlocked(\n            (anjay_unlocked_output_ctx_t *) &out, TEST_STRING));\n    stringify_buf(&outbuf);\n    AVS_UNIT_ASSERT_EQUAL_STRING(buf, TEST_STRING);\n}\n\nAVS_UNIT_TEST(text_out, string_err) {\n    TEST_ENV(8);\n    static const char TEST_STRING[] = \"Hello, world!\";\n\n    AVS_UNIT_ASSERT_FAILED(_anjay_ret_string_unlocked(\n            (anjay_unlocked_output_ctx_t *) &out, TEST_STRING));\n}\n\n#define TEST_i64(Val)                                            \\\n    do {                                                         \\\n        TEST_ENV(512);                                           \\\n        AVS_UNIT_ASSERT_SUCCESS(_anjay_ret_i64_unlocked(         \\\n                (anjay_unlocked_output_ctx_t *) &out, Val##LL)); \\\n        stringify_buf(&outbuf);                                  \\\n        AVS_UNIT_ASSERT_EQUAL_STRING(buf, #Val);                 \\\n    } while (false)\n\nAVS_UNIT_TEST(text_out, i64) {\n    TEST_i64(-1000000000000000000);\n    TEST_i64(514);\n    TEST_i64(0);\n    TEST_i64(-1);\n    TEST_i64(2147483647);\n    TEST_i64(-2147483648);\n    TEST_i64(1000000000000000000);\n}\n\n#undef TEST_i64\n\n#ifdef ANJAY_WITH_LWM2M11\n#    define TEST_u64(Val)                                             \\\n        do {                                                          \\\n            TEST_ENV(512);                                            \\\n            AVS_UNIT_ASSERT_SUCCESS(_anjay_ret_u64_unlocked(          \\\n                    (anjay_unlocked_output_ctx_t *) &out, Val##ULL)); \\\n            stringify_buf(&outbuf);                                   \\\n            AVS_UNIT_ASSERT_EQUAL_STRING(buf, #Val);                  \\\n        } while (false)\n\nAVS_UNIT_TEST(text_out, u64) {\n    TEST_u64(0);\n    TEST_u64(4294967295);\n    TEST_u64(18446744073709551615);\n}\n\n#    undef TEST_u64\n#endif // ANJAY_WITH_LWM2M11\n\n#define TEST_DOUBLE_IMPL(Val, Str)                           \\\n    do {                                                     \\\n        TEST_ENV(512);                                       \\\n        AVS_UNIT_ASSERT_SUCCESS(_anjay_ret_double_unlocked(  \\\n                (anjay_unlocked_output_ctx_t *) &out, Val)); \\\n        stringify_buf(&outbuf);                              \\\n        AVS_UNIT_ASSERT_EQUAL_STRING(buf, Str);              \\\n    } while (false)\n\n#define TEST_DOUBLE(Val) TEST_DOUBLE_IMPL(Val, #Val)\n\nAVS_UNIT_TEST(text_out, f64) {\n    TEST_DOUBLE(0);\n    TEST_DOUBLE(1);\n    TEST_DOUBLE(1.2);\n    TEST_DOUBLE(1.3125);\n#ifdef AVS_COMMONS_WITHOUT_FLOAT_FORMAT_SPECIFIERS\n    // NOTE: This variant of AVS_DOUBLE_AS_STRING() is slightly inaccurate in\n    // order to keep the implementation simpler. This level of inaccuracy is\n    // unlikely to cause problems in real-world applications.\n    TEST_DOUBLE_IMPL(4.2229999965160742e+37, \"4.2229999965160736e+37\");\n#else  // AVS_COMMONS_WITHOUT_FLOAT_FORMAT_SPECIFIERS\n    TEST_DOUBLE(4.2229999965160742e+37);\n#endif // AVS_COMMONS_WITHOUT_FLOAT_FORMAT_SPECIFIERS\n    TEST_DOUBLE(10000.5);\n    TEST_DOUBLE(10000000000000.5);\n    TEST_DOUBLE(3.26e+218);\n}\n\n#undef TEST_DOUBLE\n\n#define TEST_BOOL(Val)                                       \\\n    do {                                                     \\\n        TEST_ENV(512);                                       \\\n        AVS_UNIT_ASSERT_SUCCESS(_anjay_ret_bool_unlocked(    \\\n                (anjay_unlocked_output_ctx_t *) &out, Val)); \\\n        stringify_buf(&outbuf);                              \\\n        AVS_UNIT_ASSERT_EQUAL_STRING(buf, Val ? \"1\" : \"0\");  \\\n    } while (false)\n\nAVS_UNIT_TEST(text_out, boolean) {\n    TEST_BOOL(true);\n    TEST_BOOL(false);\n    TEST_BOOL(1);\n    TEST_BOOL(0);\n    TEST_BOOL(42);\n}\n\n#undef TEST_BOOL\n\n#define TEST_OBJLNK(Oid, Iid)                                     \\\n    do {                                                          \\\n        TEST_ENV(512);                                            \\\n        AVS_UNIT_ASSERT_SUCCESS(_anjay_ret_objlnk_unlocked(       \\\n                (anjay_unlocked_output_ctx_t *) &out, Oid, Iid)); \\\n        stringify_buf(&outbuf);                                   \\\n        AVS_UNIT_ASSERT_EQUAL_STRING(buf, #Oid \":\" #Iid);         \\\n    } while (false)\n\nAVS_UNIT_TEST(text_out, objlnk) {\n    TEST_OBJLNK(0, 0);\n    TEST_OBJLNK(1, 0);\n    TEST_OBJLNK(0, 1);\n    TEST_OBJLNK(1, 65535);\n    TEST_OBJLNK(65535, 1);\n    TEST_OBJLNK(65535, 65535);\n}\n\n#undef TEST_OBJLNK\n\nAVS_UNIT_TEST(text_out, unimplemented) {\n    TEST_ENV(512);\n    AVS_UNIT_ASSERT_NOT_NULL(_anjay_ret_bytes_begin_unlocked(\n            (anjay_unlocked_output_ctx_t *) &out, 3));\n    AVS_UNIT_ASSERT_FAILED(\n            _anjay_output_set_path((anjay_unlocked_output_ctx_t *) &out,\n                                   &MAKE_RESOURCE_INSTANCE_PATH(0, 0, 0, 1)));\n}\n\n#undef TEST_ENV\n\n/////////////////////////////////////////////////////////////////////// DECODING\n\n#define TEST_ENV(Data)                                               \\\n    avs_stream_inbuf_t stream = AVS_STREAM_INBUF_STATIC_INITIALIZER; \\\n    avs_stream_inbuf_set_buffer(&stream, Data, sizeof(Data) - 1);    \\\n    anjay_unlocked_input_ctx_t *in;                                  \\\n    AVS_UNIT_ASSERT_SUCCESS(                                         \\\n            _anjay_input_text_create(&in, (avs_stream_t *) &stream, NULL));\n\n#define TEST_TEARDOWN                                           \\\n    do {                                                        \\\n        AVS_UNIT_ASSERT_SUCCESS(_anjay_input_ctx_destroy(&in)); \\\n    } while (0)\n\nAVS_UNIT_TEST(text_in, string) {\n    const char TEST_STRING[] = \"Hello, world!\";\n    TEST_ENV(TEST_STRING);\n\n    char buf[64];\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_get_string_unlocked(in, buf, sizeof(buf)));\n    AVS_UNIT_ASSERT_EQUAL_STRING(buf, TEST_STRING);\n\n    TEST_TEARDOWN;\n}\n\nAVS_UNIT_TEST(text_in, string_too_long) {\n    TEST_ENV(\"Hello, world!\");\n\n    char buf[8];\n    AVS_UNIT_ASSERT_FAILED(_anjay_get_string_unlocked(in, buf, sizeof(buf)));\n    AVS_UNIT_ASSERT_EQUAL_STRING(buf, \"Hello, \"); // assert trailing nullbyte\n\n    TEST_TEARDOWN;\n}\n\n#define TEST_NUM_COMMON(Val, ...) \\\n    do {                          \\\n        TEST_ENV(#Val);           \\\n                                  \\\n        __VA_ARGS__;              \\\n                                  \\\n        TEST_TEARDOWN;            \\\n    } while (false)\n\n#define TEST_NUM_FAIL(Type, Getter, Val)                      \\\n    TEST_NUM_COMMON(Val, Type result; AVS_UNIT_ASSERT_FAILED( \\\n                            _anjay_get_##Getter##_unlocked(in, &result)))\n\n#define TEST_I64(Val)                                              \\\n    TEST_NUM_COMMON(Val, int64_t result; AVS_UNIT_ASSERT_SUCCESS(  \\\n                            _anjay_get_i64_unlocked(in, &result)); \\\n                    AVS_UNIT_ASSERT_EQUAL(result, (int64_t) (Val##ULL)))\n\n#define TEST_I64_FAIL(Val) TEST_NUM_FAIL(int64_t, i64, Val)\n\nAVS_UNIT_TEST(text_in, i64) {\n    TEST_I64(514);\n    TEST_I64(0);\n    TEST_I64(-1);\n    TEST_I64(2147483647);\n    TEST_I64(-2147483648);\n    TEST_I64(2147483648);\n    TEST_I64(-2147483649);\n    TEST_I64(9223372036854775807);\n    TEST_I64(-9223372036854775808);\n    TEST_I64_FAIL(9223372036854775808);\n    TEST_I64_FAIL(-9223372036854775809);\n    TEST_I64_FAIL(1.0);\n    TEST_I64_FAIL(wat);\n}\n\n#undef TEST_I64_FAIL\n#undef TEST_I64\n\n#define TEST_DOUBLE(Val)                                              \\\n    TEST_NUM_COMMON(Val, double result; AVS_UNIT_ASSERT_SUCCESS(      \\\n                            _anjay_get_double_unlocked(in, &result)); \\\n                    AVS_UNIT_ASSERT_EQUAL(result, (double) Val))\n\n#define TEST_DOUBLE_FAIL(Val) TEST_NUM_FAIL(double, double, Val)\n\nAVS_UNIT_TEST(text_in, f64) {\n    TEST_DOUBLE(0);\n    TEST_DOUBLE(0.0);\n    TEST_DOUBLE(1);\n    TEST_DOUBLE(1.0);\n    TEST_DOUBLE(1.2);\n    TEST_DOUBLE(1.3125);\n    TEST_DOUBLE(1.3125000);\n    TEST_DOUBLE(-10000.5);\n    TEST_DOUBLE(-10000000000000.5);\n    TEST_DOUBLE(4.223e+37);\n    TEST_DOUBLE(3.26e+218);\n    TEST_DOUBLE_FAIL(wat);\n}\n\n#undef TEST_DOUBLE_FAIL\n#undef TEST_DOUBLE\n\n#define TEST_BOOL(Val)                                              \\\n    TEST_NUM_COMMON(Val, bool result; AVS_UNIT_ASSERT_SUCCESS(      \\\n                            _anjay_get_bool_unlocked(in, &result)); \\\n                    AVS_UNIT_ASSERT_EQUAL(result, Val))\n\n#define TEST_BOOL_FAIL(Str)                                            \\\n    do {                                                               \\\n        TEST_ENV(Str);                                                 \\\n        bool result;                                                   \\\n        AVS_UNIT_ASSERT_FAILED(_anjay_get_bool_unlocked(in, &result)); \\\n        TEST_TEARDOWN;                                                 \\\n    } while (false);\n\nAVS_UNIT_TEST(text_in, boolean) {\n    TEST_BOOL(0);\n    TEST_BOOL(1);\n    TEST_BOOL_FAIL(\"2\");\n    TEST_BOOL_FAIL(\"-1\");\n    TEST_BOOL_FAIL(\"true\");\n    TEST_BOOL_FAIL(\"false\");\n    TEST_BOOL_FAIL(\"wat\");\n}\n\n#undef TEST_NUM_FAIL\n#undef TEST_NUM_COMMON\n\n#define TEST_OBJLNK_COMMON(Str, ...) \\\n    do {                             \\\n        TEST_ENV(Str);               \\\n        anjay_oid_t oid;             \\\n        anjay_iid_t iid;             \\\n        __VA_ARGS__;                 \\\n        TEST_TEARDOWN;               \\\n    } while (false)\n\n#define TEST_OBJLNK(Oid, Iid)                                               \\\n    TEST_OBJLNK_COMMON(#Oid \":\" #Iid,                                       \\\n                       AVS_UNIT_ASSERT_SUCCESS(                             \\\n                               _anjay_get_objlnk_unlocked(in, &oid, &iid)); \\\n                       AVS_UNIT_ASSERT_EQUAL(oid, Oid);                     \\\n                       AVS_UNIT_ASSERT_EQUAL(iid, Iid))\n\n#define TEST_OBJLNK_FAIL(Str)                  \\\n    TEST_OBJLNK_COMMON(Str,                    \\\n                       AVS_UNIT_ASSERT_FAILED( \\\n                               _anjay_get_objlnk_unlocked(in, &oid, &iid)))\n\nAVS_UNIT_TEST(text_in, objlnk) {\n    TEST_OBJLNK(0, 0);\n    TEST_OBJLNK(1, 0);\n    TEST_OBJLNK(0, 1);\n    TEST_OBJLNK(1, 65535);\n    TEST_OBJLNK(65535, 1);\n    TEST_OBJLNK(65535, 65535);\n    TEST_OBJLNK_FAIL(\"65536:1\");\n    TEST_OBJLNK_FAIL(\"1:65536\");\n    TEST_OBJLNK_FAIL(\"0: 0\");\n    TEST_OBJLNK_FAIL(\"0 :0\");\n    TEST_OBJLNK_FAIL(\" 0:0\");\n    TEST_OBJLNK_FAIL(\"0:0 \");\n    TEST_OBJLNK_FAIL(\"\");\n    TEST_OBJLNK_FAIL(\"0\");\n    TEST_OBJLNK_FAIL(\"wat\");\n    TEST_OBJLNK_FAIL(\"0:wat\");\n    TEST_OBJLNK_FAIL(\"wat:0\");\n}\n\n#undef TEST_OBJLNK_FAIL\n#undef TEST_OBJLNK\n#undef TEST_OBJLNK_COMMON\n#undef TEST_TEARDOWN\n#undef TEST_ENV\n"
  },
  {
    "path": "tests/core/io/tlv_in.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#include <avsystem/commons/avs_memory.h>\n#include <avsystem/commons/avs_stream_inbuf.h>\n\n#define AVS_UNIT_ENABLE_SHORT_ASSERTS\n#include <avsystem/commons/avs_unit_test.h>\n\n#include \"bigdata.h\"\n#include <anjay/core.h>\n\n#define TEST_ENV(Data, Path)                                         \\\n    avs_stream_inbuf_t stream = AVS_STREAM_INBUF_STATIC_INITIALIZER; \\\n    avs_stream_inbuf_set_buffer(&stream, Data, sizeof(Data) - 1);    \\\n    anjay_unlocked_input_ctx_t *in;                                  \\\n    ASSERT_OK(_anjay_input_tlv_create(&in, (avs_stream_t *) &stream, &(Path)));\n\n#define TEST_TEARDOWN                             \\\n    do {                                          \\\n        ASSERT_OK(_anjay_input_ctx_destroy(&in)); \\\n    } while (0)\n\n#define TLV_BYTES_TEST_DATA(Header, Data)                                    \\\n    do {                                                                     \\\n        char *buf = (char *) avs_malloc(sizeof(Data) + sizeof(Header));      \\\n        size_t bytes_read;                                                   \\\n        bool message_finished;                                               \\\n        ASSERT_OK(_anjay_get_bytes_unlocked(in, &bytes_read,                 \\\n                                            &message_finished, buf,          \\\n                                            sizeof(Data) + sizeof(Header))); \\\n        ASSERT_EQ(bytes_read, sizeof(Data) - 1);                             \\\n        ASSERT_TRUE(message_finished);                                       \\\n        ASSERT_EQ_BYTES(buf, Data);                                          \\\n        avs_free(buf);                                                       \\\n    } while (0)\n\nstatic const anjay_uri_path_t TEST_INSTANCE_PATH =\n        INSTANCE_PATH_INITIALIZER(3, 4);\n\n#define MAKE_TEST_RESOURCE_PATH(Rid)                          \\\n    (MAKE_RESOURCE_PATH(TEST_INSTANCE_PATH.ids[ANJAY_ID_OID], \\\n                        TEST_INSTANCE_PATH.ids[ANJAY_ID_IID], (Rid)))\n\n#define TLV_BYTES_TEST_PATH(Path)                           \\\n    do {                                                    \\\n        anjay_uri_path_t path;                              \\\n        ASSERT_OK(_anjay_input_get_path(in, &path, NULL));  \\\n        ASSERT_TRUE(_anjay_uri_path_equal(&path, &(Path))); \\\n    } while (0)\n\n#define TLV_BYTES_TEST(Name, Path, Header, Data)   \\\n    AVS_UNIT_TEST(tlv_in_bytes, Name##_with_id) {  \\\n        TEST_ENV(Header Data, TEST_INSTANCE_PATH); \\\n        TLV_BYTES_TEST_PATH(Path);                 \\\n        TLV_BYTES_TEST_PATH(Path);                 \\\n        TLV_BYTES_TEST_DATA(Header, Data);         \\\n        TEST_TEARDOWN;                             \\\n    }\n\n// 3 bits for length - <=7\nTLV_BYTES_TEST(len3b_id8b, MAKE_TEST_RESOURCE_PATH(0), \"\\xC7\\x00\", \"1234567\")\nTLV_BYTES_TEST(len3b_id16b,\n               MAKE_TEST_RESOURCE_PATH(42000),\n               \"\\xE7\\xA4\\x10\",\n               \"1234567\")\n\nTLV_BYTES_TEST(len8b_id8b,\n               MAKE_TEST_RESOURCE_PATH(255),\n               \"\\xC8\\xFF\\x08\",\n               \"12345678\")\nTLV_BYTES_TEST(len8b_id16b,\n               MAKE_TEST_RESOURCE_PATH(65534),\n               \"\\xE8\\xFF\\xFE\\x08\",\n               \"12345678\")\n\nTLV_BYTES_TEST(len16b_id8b,\n               MAKE_TEST_RESOURCE_PATH(42),\n               \"\\xD0\\x2A\\x03\\xE8\",\n               DATA1kB)\nTLV_BYTES_TEST(len16b_id16b,\n               MAKE_TEST_RESOURCE_PATH(42420),\n               \"\\xF0\\xA5\\xB4\\x03\\xE8\",\n               DATA1kB)\n\nTLV_BYTES_TEST(len24b_id8b,\n               MAKE_TEST_RESOURCE_PATH(69),\n               \"\\xD8\\x45\\x01\\x86\\xA0\",\n               DATA100kB)\nTLV_BYTES_TEST(len24b_id16b,\n               MAKE_TEST_RESOURCE_PATH(258),\n               \"\\xF8\\x01\\x02\\x01\\x86\\xA0\",\n               DATA100kB)\n\n#undef TLV_BYTES_TEST\n#undef TLV_BYTES_TEST_DATA\n\nAVS_UNIT_TEST(tlv_in_bytes, id_too_short) {\n    TEST_ENV(\"\\xE7\", MAKE_ROOT_PATH());\n\n    char buf[64];\n    size_t bytes_read;\n    bool message_finished;\n    ASSERT_FAIL(_anjay_get_bytes_unlocked(in, &bytes_read, &message_finished,\n                                          buf, sizeof(buf)));\n\n    TEST_TEARDOWN;\n}\n\nAVS_UNIT_TEST(tlv_in_bytes, length_too_short) {\n    TEST_ENV(\"\\xF8\\x01\\x02\\x01\\x86\", MAKE_ROOT_PATH());\n\n    char buf[64];\n    size_t bytes_read;\n    bool message_finished;\n    ASSERT_FAIL(_anjay_get_bytes_unlocked(in, &bytes_read, &message_finished,\n                                          buf, sizeof(buf)));\n\n    TEST_TEARDOWN;\n}\n\nAVS_UNIT_TEST(tlv_in_bytes, partial_read) {\n    static const char DATA[] = \"\\xC7\\x2A\"\n                               \"0123456\";\n    TEST_ENV(DATA, MAKE_INSTANCE_PATH(3, 4));\n\n    for (size_t i = 0; i < 7; ++i) {\n        char ch;\n        size_t bytes_read;\n        bool message_finished;\n        ASSERT_OK(_anjay_get_bytes_unlocked(in, &bytes_read, &message_finished,\n                                            &ch, 1));\n        if (i == 6) {\n            ASSERT_TRUE(message_finished);\n        } else {\n            ASSERT_FALSE(message_finished);\n            TLV_BYTES_TEST_PATH(MAKE_RESOURCE_PATH(3, 4, 42));\n        }\n        ASSERT_EQ(bytes_read, 1);\n        ASSERT_EQ(message_finished, (i == 6));\n        ASSERT_EQ(ch, DATA[i + 2]);\n    }\n\n    TEST_TEARDOWN;\n}\n\nAVS_UNIT_TEST(tlv_in_bytes, short_read_get_id) {\n    const char DATA[] = \"\\xC4\\x2A\"\n                        \"0123\"\n                        \"\\xC7\\x45\"\n                        \"0123456\"\n                        \"\\xC5\\x16\"\n                        \"01234\";\n    TEST_ENV(DATA, MAKE_INSTANCE_PATH(3, 4));\n\n    TLV_BYTES_TEST_PATH(MAKE_RESOURCE_PATH(3, 4, 42));\n    TLV_BYTES_TEST_PATH(MAKE_RESOURCE_PATH(3, 4, 42));\n    // skip reading altogether\n    ASSERT_OK(_anjay_input_next_entry(in));\n\n    TLV_BYTES_TEST_PATH(MAKE_RESOURCE_PATH(3, 4, 69));\n    // short read\n    char buf[3];\n    size_t bytes_read;\n    bool message_finished;\n    ASSERT_OK(_anjay_get_bytes_unlocked(in, &bytes_read, &message_finished, buf,\n                                        sizeof(buf)));\n    ASSERT_EQ(bytes_read, 3);\n    ASSERT_FALSE(message_finished);\n    ASSERT_EQ_BYTES_SIZED(buf, \"012\", 3);\n    TLV_BYTES_TEST_PATH(MAKE_RESOURCE_PATH(3, 4, 69));\n    ASSERT_OK(_anjay_input_next_entry(in));\n\n    TLV_BYTES_TEST_PATH(MAKE_RESOURCE_PATH(3, 4, 22));\n    TLV_BYTES_TEST_PATH(MAKE_RESOURCE_PATH(3, 4, 22));\n    // skip reading again\n    ASSERT_OK(_anjay_input_next_entry(in));\n\n    ASSERT_EQ(_anjay_input_get_path(in, &(anjay_uri_path_t) { 0 }, NULL),\n              ANJAY_GET_PATH_END);\n    TEST_TEARDOWN;\n}\n\n#undef TLV_BYTES_TEST_PATH\n\nAVS_UNIT_TEST(tlv_in_bytes, premature_end) {\n    static const char DATA[] = \"\\xC7\\x2A\"\n                               \"012\";\n    TEST_ENV(DATA, MAKE_ROOT_PATH());\n\n    char buf[16];\n    size_t bytes_read;\n    bool message_finished;\n    ASSERT_FAIL(_anjay_get_bytes_unlocked(in, &bytes_read, &message_finished,\n                                          buf, sizeof(buf)));\n\n    TEST_TEARDOWN;\n}\n\nAVS_UNIT_TEST(tlv_in_bytes, no_data) {\n    TEST_ENV(\"\", MAKE_ROOT_PATH());\n\n    unsigned seed = (unsigned) avs_time_real_now().since_real_epoch.seconds;\n    const char init = (char) avs_rand_r(&seed);\n    char buf[16] = { init };\n    size_t bytes_read;\n    bool message_finished;\n    ASSERT_OK(_anjay_get_bytes_unlocked(in, &bytes_read, &message_finished, buf,\n                                        sizeof(buf)));\n    /* buffer untouched, read 0 bytes */\n    ASSERT_EQ(buf[0], init);\n\n    TEST_TEARDOWN;\n}\n\n#undef TEST_TEARDOWN\n#undef TEST_ENV\n\n#define TEST_ENV(Data)                                               \\\n    avs_stream_inbuf_t stream = AVS_STREAM_INBUF_STATIC_INITIALIZER; \\\n    avs_stream_inbuf_set_buffer(&stream, Data, sizeof(Data) - 1);    \\\n    anjay_unlocked_input_ctx_t *in;                                  \\\n    ASSERT_OK(_anjay_input_tlv_create(&in, (avs_stream_t *) &stream, \\\n                                      &MAKE_ROOT_PATH()));           \\\n    tlv_in_t *ctx = (tlv_in_t *) in;                                 \\\n    ctx->has_path = true;                                            \\\n    tlv_entry_t *entry = tlv_entry_push(ctx);                        \\\n    AVS_UNIT_ASSERT_NOT_NULL(entry);                                 \\\n    entry->length = sizeof(Data) - 1;\n\n#define TEST_TEARDOWN                             \\\n    do {                                          \\\n        ASSERT_OK(_anjay_input_ctx_destroy(&in)); \\\n    } while (0)\n\nAVS_UNIT_TEST(tlv_in_types, string_ok) {\n    static const char TEST_STRING[] = \"Hello, world!\";\n    TEST_ENV(TEST_STRING);\n\n    char buf[16];\n    ASSERT_OK(_anjay_get_string_unlocked(in, buf, sizeof(buf)));\n    ASSERT_EQ_STR(buf, TEST_STRING);\n\n    TEST_TEARDOWN;\n}\n\nAVS_UNIT_TEST(tlv_in_types, string_overflow) {\n    static const char TEST_STRING[] = \"Hello, world!\";\n    TEST_ENV(TEST_STRING);\n\n    char buf[4];\n    ASSERT_EQ(_anjay_get_string_unlocked(in, buf, sizeof(buf)),\n              ANJAY_BUFFER_TOO_SHORT);\n    ASSERT_EQ_STR(buf, \"Hel\");\n    ASSERT_EQ(_anjay_get_string_unlocked(in, buf, sizeof(buf)),\n              ANJAY_BUFFER_TOO_SHORT);\n    ASSERT_EQ_STR(buf, \"lo,\");\n    ASSERT_EQ(_anjay_get_string_unlocked(in, buf, sizeof(buf)),\n              ANJAY_BUFFER_TOO_SHORT);\n    ASSERT_EQ_STR(buf, \" wo\");\n    ASSERT_EQ(_anjay_get_string_unlocked(in, buf, sizeof(buf)),\n              ANJAY_BUFFER_TOO_SHORT);\n    ASSERT_EQ_STR(buf, \"rld\");\n    ASSERT_OK(_anjay_get_string_unlocked(in, buf, sizeof(buf)));\n    ASSERT_EQ_STR(buf, \"!\");\n\n    TEST_TEARDOWN;\n}\n\n#define TEST_NUM_IMPL(Name, Type, Suffix, Num, Data)           \\\n    AVS_UNIT_TEST(tlv_in_types, Name) {                        \\\n        TEST_ENV(Data);                                        \\\n        Type value;                                            \\\n        ASSERT_OK(_anjay_get_##Suffix##_unlocked(in, &value)); \\\n        ASSERT_EQ(value, (Type) Num);                          \\\n        TEST_TEARDOWN;                                         \\\n    }\n\n#define TEST_NUM_FAIL_IMPL(Name, Type, Suffix, Data)             \\\n    AVS_UNIT_TEST(tlv_in_types, Name) {                          \\\n        TEST_ENV(Data);                                          \\\n        Type value;                                              \\\n        ASSERT_FAIL(_anjay_get_##Suffix##_unlocked(in, &value)); \\\n        TEST_TEARDOWN;                                           \\\n    }\n\n#define TEST_NUM(Type, Suffix, Num, Data) \\\n    TEST_NUM_IMPL(AVS_CONCAT(Suffix##_, __LINE__), Type, Suffix, Num, Data)\n#define TEST_NUM_FAIL(Type, Suffix, Data) \\\n    TEST_NUM_FAIL_IMPL(AVS_CONCAT(Suffix##fail_, __LINE__), Type, Suffix, Data)\n\n#define TEST_INT64(Num, Data) TEST_NUM(int64_t, i64, Num##LL, Data)\n\n#define TEST_INT64_FAIL(...) TEST_NUM_FAIL(int64_t, i64, __VA_ARGS__)\n\nTEST_INT64_FAIL(\"\")\nTEST_INT64(42, \"\\x2A\")\nTEST_INT64(4242, \"\\x10\\x92\")\nTEST_INT64_FAIL(\"\\x06\\x79\\x32\")\nTEST_INT64(424242, \"\\x00\\x06\\x79\\x32\")\nTEST_INT64(42424242, \"\\x02\\x87\\x57\\xB2\")\nTEST_INT64((int32_t) 4242424242, \"\\xFC\\xDE\\x41\\xB2\")\nTEST_INT64(4242424242, \"\\x00\\x00\\x00\\x00\\xFC\\xDE\\x41\\xB2\")\nTEST_INT64_FAIL(\"\\x62\\xC6\\xD1\\xA9\\xB2\")\nTEST_INT64(424242424242, \"\\x00\\x00\\x00\\x62\\xC6\\xD1\\xA9\\xB2\")\nTEST_INT64_FAIL(\"\\x26\\x95\\xA9\\xE6\\x49\\xB2\")\nTEST_INT64(42424242424242, \"\\x00\\x00\\x26\\x95\\xA9\\xE6\\x49\\xB2\")\nTEST_INT64_FAIL(\"\\x0F\\x12\\x76\\x5D\\xF4\\xC9\\xB2\")\nTEST_INT64(4242424242424242, \"\\x00\\x0F\\x12\\x76\\x5D\\xF4\\xC9\\xB2\")\nTEST_INT64(424242424242424242, \"\\x05\\xE3\\x36\\x3C\\xB3\\x9E\\xC9\\xB2\")\nTEST_INT64_FAIL(\"\\x00\\x05\\xE3\\x36\\x3C\\xB3\\x9E\\xC9\\xB2\")\n\n#define TEST_UINT64(Num, Data) TEST_NUM(uint64_t, u64, Num##ULL, Data)\n\n#ifdef ANJAY_WITH_LWM2M11\n#    define TEST_UINT64_FAIL(...) TEST_NUM_FAIL(uint64_t, u64, __VA_ARGS__)\n\nTEST_UINT64_FAIL(\"\")\nTEST_UINT64(42, \"\\x2A\")\nTEST_UINT64_FAIL(\"\\x06\\x79\\x32\")\nTEST_UINT64(4294967295, \"\\xFF\\xFF\\xFF\\xFF\")\nTEST_UINT64_FAIL(\"\\x01\\x00\\x00\\x00\\x00\")\nTEST_UINT64(4294967296, \"\\x00\\x00\\x00\\x01\\x00\\x00\\x00\\x00\")\nTEST_UINT64(18446744073709551615, \"\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\")\n#endif // ANJAY_WITH_LWM2M11\n\n#define TEST_DOUBLE(Num, Data) TEST_NUM(double, double, Num, Data)\n\n#define TEST_DOUBLE_FAIL(Data) TEST_NUM_FAIL(double, double, Data)\n\nTEST_DOUBLE_FAIL(\"\")\nTEST_DOUBLE_FAIL(\"\\x3F\")\nTEST_DOUBLE_FAIL(\"\\x3F\\x80\")\nTEST_DOUBLE_FAIL(\"\\x3F\\x80\\x00\")\nTEST_DOUBLE(1.0, \"\\x3F\\x80\\x00\\x00\")\nTEST_DOUBLE(-42.0e3, \"\\xC7\\x24\\x10\\x00\")\nTEST_DOUBLE_FAIL(\"\\x3F\\xF0\\x00\\x00\\x00\")\nTEST_DOUBLE_FAIL(\"\\x3F\\xF0\\x00\\x00\\x00\\x00\")\nTEST_DOUBLE_FAIL(\"\\x3F\\xF0\\x00\\x00\\x00\\x00\\x00\")\nTEST_DOUBLE(1.0, \"\\x3F\\xF0\\x00\\x00\\x00\\x00\\x00\\x00\")\nTEST_DOUBLE(1.1, \"\\x3F\\xF1\\x99\\x99\\x99\\x99\\x99\\x9A\")\nTEST_DOUBLE(-42.0e3, \"\\xC0\\xE4\\x82\\x00\\x00\\x00\\x00\\x00\")\nTEST_DOUBLE_FAIL(\"\\xC0\\xE4\\x82\\x00\\x00\\x00\\x00\\x00\\x00\")\n\n#define TEST_BOOL_IMPL(Name, Value, Data)                \\\n    AVS_UNIT_TEST(tlv_in_types, Name) {                  \\\n        TEST_ENV(Data);                                  \\\n        bool value;                                      \\\n        ASSERT_OK(_anjay_get_bool_unlocked(in, &value)); \\\n        ASSERT_EQ(!!(Value), value);                     \\\n        TEST_TEARDOWN;                                   \\\n    }\n\n#define TEST_BOOL_FAIL_IMPL(Name, Data)                    \\\n    AVS_UNIT_TEST(tlv_in_types, Name) {                    \\\n        TEST_ENV(Data);                                    \\\n        bool value;                                        \\\n        ASSERT_FAIL(_anjay_get_bool_unlocked(in, &value)); \\\n        TEST_TEARDOWN;                                     \\\n    }\n\n#define TEST_BOOL(Value, Data) \\\n    TEST_BOOL_IMPL(AVS_CONCAT(bool_, __LINE__), Value, Data)\n#define TEST_BOOL_FAIL(Data) \\\n    TEST_BOOL_FAIL_IMPL(AVS_CONCAT(bool_, __LINE__), Data)\n\nTEST_BOOL_FAIL(\"\")\nTEST_BOOL(false, \"\\0\")\nTEST_BOOL(true, \"\\1\")\nTEST_BOOL_FAIL(\"\\2\")\nTEST_BOOL_FAIL(\"\\0\\0\")\n\n#define TEST_OBJLNK_IMPL(Name, Oid, Iid, Data)                 \\\n    AVS_UNIT_TEST(tlv_in_types, Name) {                        \\\n        TEST_ENV(Data);                                        \\\n        anjay_oid_t oid;                                       \\\n        anjay_iid_t iid;                                       \\\n        ASSERT_OK(_anjay_get_objlnk_unlocked(in, &oid, &iid)); \\\n        ASSERT_EQ(oid, Oid);                                   \\\n        ASSERT_EQ(iid, Iid);                                   \\\n        TEST_TEARDOWN;                                         \\\n    }\n\n#define TEST_OBJLNK_FAIL_IMPL(Name, Data)                        \\\n    AVS_UNIT_TEST(tlv_in_types, Name) {                          \\\n        TEST_ENV(Data);                                          \\\n        anjay_oid_t oid;                                         \\\n        anjay_iid_t iid;                                         \\\n        ASSERT_FAIL(_anjay_get_objlnk_unlocked(in, &oid, &iid)); \\\n        TEST_TEARDOWN;                                           \\\n    }\n\n#define TEST_OBJLNK(...) \\\n    TEST_OBJLNK_IMPL(AVS_CONCAT(objlnk_, __LINE__), __VA_ARGS__)\n#define TEST_OBJLNK_FAIL(...) \\\n    TEST_OBJLNK_FAIL_IMPL(AVS_CONCAT(objlnk_, __LINE__), __VA_ARGS__)\n\nTEST_OBJLNK_FAIL(\"\")\nTEST_OBJLNK_FAIL(\"\\x00\")\nTEST_OBJLNK_FAIL(\"\\x00\\x00\")\nTEST_OBJLNK_FAIL(\"\\x00\\x00\\x00\")\nTEST_OBJLNK(0, 0, \"\\x00\\x00\\x00\\x00\")\nTEST_OBJLNK(1, 0, \"\\x00\\x01\\x00\\x00\")\nTEST_OBJLNK(0, 1, \"\\x00\\x00\\x00\\x01\")\nTEST_OBJLNK(1, 65535, \"\\x00\\x01\\xFF\\xFF\")\nTEST_OBJLNK(65535, 1, \"\\xFF\\xFF\\x00\\x01\")\nTEST_OBJLNK(65535, 65535, \"\\xFF\\xFF\\xFF\\xFF\")\nTEST_OBJLNK_FAIL(\"\\xFF\\xFF\\xFF\\xFF\\xFF\")\n\nAVS_UNIT_TEST(tlv_in_types, invalid_read) {\n    TEST_ENV(\"\\xC3\\x00\\x00\\x00\\x2A\"); // bytes that contain an int afterward\n\n    size_t bytes_read;\n    bool message_finished;\n    char ch;\n    ASSERT_OK(_anjay_get_bytes_unlocked(in, &bytes_read, &message_finished, &ch,\n                                        1));\n\n    int64_t value;\n    ASSERT_FAIL(_anjay_get_i64_unlocked(in, &value));\n\n    TEST_TEARDOWN;\n}\n\n#undef TEST_ENV\n\n#define TEST_ENV(Data, Path)                                         \\\n    avs_stream_inbuf_t stream = AVS_STREAM_INBUF_STATIC_INITIALIZER; \\\n    avs_stream_inbuf_set_buffer(&stream, Data, sizeof(Data) - 1);    \\\n    anjay_unlocked_input_ctx_t *in;                                  \\\n    ASSERT_OK(_anjay_input_tlv_create(&in, (avs_stream_t *) &stream, &(Path)));\n\nAVS_UNIT_TEST(tlv_in_path, typical_payload_for_create_without_iid) {\n    TEST_ENV(\"\\xC7\\x00\"\n             \"1234567\",\n             MAKE_OBJECT_PATH(42));\n\n    anjay_uri_path_t path;\n    ASSERT_OK(_anjay_input_get_path(in, &path, NULL));\n    ASSERT_TRUE(_anjay_uri_path_equal(\n            &path, &MAKE_RESOURCE_PATH(42, ANJAY_ID_INVALID, 0)));\n    TEST_TEARDOWN;\n}\n\nAVS_UNIT_TEST(tlv_in_path, payload_write_on_instance_with_rids_only) {\n    // [ RID(1)=10, RID(2)=10, RID(3)=10 ]\n    TEST_ENV(\"\\xc1\\x01\\x0a\\xc1\\x02\\x0a\\xc1\\x03\\x0a\", MAKE_INSTANCE_PATH(3, 4));\n    anjay_uri_path_t path;\n    ASSERT_OK(_anjay_input_get_path(in, &path, NULL));\n    ASSERT_TRUE(_anjay_uri_path_equal(&path, &MAKE_RESOURCE_PATH(3, 4, 1)));\n    ASSERT_OK(_anjay_input_next_entry(in));\n\n    ASSERT_OK(_anjay_input_get_path(in, &path, NULL));\n    ASSERT_TRUE(_anjay_uri_path_equal(&path, &MAKE_RESOURCE_PATH(3, 4, 2)));\n    ASSERT_OK(_anjay_input_next_entry(in));\n\n    ASSERT_OK(_anjay_input_get_path(in, &path, NULL));\n    ASSERT_TRUE(_anjay_uri_path_equal(&path, &MAKE_RESOURCE_PATH(3, 4, 3)));\n    ASSERT_OK(_anjay_input_next_entry(in));\n\n    ASSERT_EQ(_anjay_input_get_path(in, &path, NULL), ANJAY_GET_PATH_END);\n    TEST_TEARDOWN;\n}\n\nAVS_UNIT_TEST(tlv_in_path,\n              payload_write_on_instance_with_rids_uri_iid_mismatch) {\n    // IID(5, [ RID(1)=10 ])\n    TEST_ENV(\"\\x03\\x05\\xc1\\x01\\x0a\", MAKE_INSTANCE_PATH(3, 4));\n    anjay_uri_path_t path;\n    ASSERT_FAIL(_anjay_input_get_path(in, &path, NULL));\n\n    TEST_TEARDOWN;\n}\n\nAVS_UNIT_TEST(tlv_in_path, fail_on_path_with_invalid_iid) {\n    // IID(ANJAY_ID_INVALID, [ RID(1)=1 ])\n    TEST_ENV(\"\\x23\\xff\\xff\\xc1\\x01\\x0a\", MAKE_ROOT_PATH());\n    anjay_uri_path_t path;\n    ASSERT_FAIL(_anjay_input_get_path(in, &path, NULL));\n\n    TEST_TEARDOWN;\n}\n\nAVS_UNIT_TEST(tlv_in_path, fail_on_path_with_invalid_rid) {\n    // IID(5, [ RID(1)=ANJAY_ID_INVALID ])\n    TEST_ENV(\"\\x04\\x05\\xe1\\xff\\xff\\x0a\", MAKE_ROOT_PATH());\n    anjay_uri_path_t path;\n    ASSERT_FAIL(_anjay_input_get_path(in, &path, NULL));\n\n    TEST_TEARDOWN;\n}\n\nAVS_UNIT_TEST(tlv_in_path, fail_on_path_with_invalid_riid) {\n    // RIID=ANJAY_ID_INVALID\n    TEST_ENV(\"\\x61\\xff\\xff\\x0a\", MAKE_RESOURCE_PATH(5, 0, 1));\n    anjay_uri_path_t path;\n    ASSERT_FAIL(_anjay_input_get_path(in, &path, NULL));\n\n    TEST_TEARDOWN;\n}\n\nAVS_UNIT_TEST(tlv_in_path, payload_write_on_instance_with_rids) {\n    // IID(4, [ RID(1)=10, RID(2)=10 ])\n    TEST_ENV(\"\\x06\\x04\\xc1\\x01\\x0a\\xc1\\x02\\x0a\", MAKE_INSTANCE_PATH(3, 4));\n    anjay_uri_path_t path;\n    ASSERT_OK(_anjay_input_get_path(in, &path, NULL));\n    ASSERT_TRUE(_anjay_uri_path_equal(&path, &MAKE_RESOURCE_PATH(3, 4, 1)));\n    ASSERT_OK(_anjay_input_next_entry(in));\n\n    ASSERT_OK(_anjay_input_get_path(in, &path, NULL));\n    ASSERT_TRUE(_anjay_uri_path_equal(&path, &MAKE_RESOURCE_PATH(3, 4, 2)));\n    ASSERT_OK(_anjay_input_next_entry(in));\n\n    TEST_TEARDOWN;\n}\n\nAVS_UNIT_TEST(tlv_in_path, payload_write_on_resource_with_riids_only) {\n    TEST_ENV(\"\\x41\\x01\\x0a\\x41\\x02\\x0a\\x41\\x03\\x0a\",\n             MAKE_RESOURCE_PATH(3, 4, 5));\n    anjay_uri_path_t path;\n    ASSERT_OK(_anjay_input_get_path(in, &path, NULL));\n    ASSERT_TRUE(_anjay_uri_path_equal(\n            &path, &MAKE_RESOURCE_INSTANCE_PATH(3, 4, 5, 1)));\n    ASSERT_OK(_anjay_input_next_entry(in));\n\n    ASSERT_OK(_anjay_input_get_path(in, &path, NULL));\n    ASSERT_TRUE(_anjay_uri_path_equal(\n            &path, &MAKE_RESOURCE_INSTANCE_PATH(3, 4, 5, 2)));\n    ASSERT_OK(_anjay_input_next_entry(in));\n\n    ASSERT_OK(_anjay_input_get_path(in, &path, NULL));\n    ASSERT_TRUE(_anjay_uri_path_equal(\n            &path, &MAKE_RESOURCE_INSTANCE_PATH(3, 4, 5, 3)));\n    ASSERT_OK(_anjay_input_next_entry(in));\n\n    ASSERT_EQ(_anjay_input_get_path(in, &path, NULL), ANJAY_GET_PATH_END);\n\n    TEST_TEARDOWN;\n}\n\nAVS_UNIT_TEST(tlv_in_array, tlv_id_is_array) {\n    TEST_ENV(\"\\x80\\x05\", MAKE_RESOURCE_PATH(3, 4, 5));\n    anjay_uri_path_t path;\n    bool is_array;\n    ASSERT_OK(_anjay_input_get_path(in, &path, &is_array));\n    ASSERT_TRUE(_anjay_uri_path_equal(&path, &MAKE_RESOURCE_PATH(3, 4, 5)));\n    ASSERT_EQ(is_array, true);\n\n    TEST_TEARDOWN;\n}\n\nAVS_UNIT_TEST(tlv_in_empty, empty_instances_list) {\n    // [ Instance(1), Instance(2) ]\n    TEST_ENV(\"\\x00\\x01\\x00\\x02\", MAKE_OBJECT_PATH(3));\n    anjay_uri_path_t path;\n    bool is_array;\n    ASSERT_OK(_anjay_input_get_path(in, &path, &is_array));\n    ASSERT_TRUE(_anjay_uri_path_equal(&path, &MAKE_INSTANCE_PATH(3, 1)));\n    ASSERT_EQ(is_array, false);\n\n    ASSERT_OK(_anjay_input_next_entry(in));\n    ASSERT_OK(_anjay_input_get_path(in, &path, NULL));\n    ASSERT_TRUE(_anjay_uri_path_equal(&path, &MAKE_INSTANCE_PATH(3, 2)));\n\n    ASSERT_OK(_anjay_input_next_entry(in));\n    ASSERT_EQ(_anjay_input_get_path(in, NULL, NULL), ANJAY_GET_PATH_END);\n\n    TEST_TEARDOWN;\n}\n"
  },
  {
    "path": "tests/core/io/tlv_out.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#include <avsystem/commons/avs_memory.h>\n#include <avsystem/commons/avs_unit_test.h>\n\n#include \"bigdata.h\"\n\n///////////////////////////////////////////////////////////// ENCODING // SIMPLE\n\n#define TEST_ENV_COMMON(Uri)                                           \\\n    avs_stream_outbuf_t outbuf = AVS_STREAM_OUTBUF_STATIC_INITIALIZER; \\\n    avs_stream_outbuf_set_buffer(&outbuf, buf, sizeof(buf));           \\\n    anjay_unlocked_output_ctx_t *out =                                 \\\n            _anjay_output_tlv_create((avs_stream_t *) &outbuf, (Uri)); \\\n    AVS_UNIT_ASSERT_NOT_NULL(out)\n\n#define TEST_ENV(Size, Uri) \\\n    char buf[Size];         \\\n    TEST_ENV_COMMON((Uri))\n\n#define TEST_ENV_HEAP(Size, Uri)           \\\n    char *buf = (char *) avs_malloc(Size); \\\n    TEST_ENV_COMMON((Uri))\n\n#define VERIFY_BYTES(Data)                                       \\\n    do {                                                         \\\n        AVS_UNIT_ASSERT_EQUAL(avs_stream_outbuf_offset(&outbuf), \\\n                              sizeof(Data) - 1);                 \\\n        AVS_UNIT_ASSERT_EQUAL_BYTES(buf, Data);                  \\\n    } while (0)\n\n// 3 bits for length - <=7\nAVS_UNIT_TEST(tlv_out, bytes_3blen_8bid) {\n    static const char DATA[] = \"1234567\";\n    TEST_ENV(32, &MAKE_INSTANCE_PATH(0, 0));\n\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_output_set_path(out, &MAKE_RESOURCE_PATH(0, 0, 0)));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_ret_string_unlocked(out, DATA));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_output_ctx_destroy(&out));\n    VERIFY_BYTES(\"\\xC7\\x00\"\n                 \"1234567\");\n}\n\nAVS_UNIT_TEST(tlv_out, bytes_3blen_16bid) {\n    // 3 bits for length - <=7\n    static const char DATA[] = \"1234567\";\n    TEST_ENV(32, &MAKE_INSTANCE_PATH(0, 0));\n\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_output_set_path(out, &MAKE_RESOURCE_PATH(0, 0, 42000)));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_ret_string_unlocked(out, DATA));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_output_ctx_destroy(&out));\n    VERIFY_BYTES(\"\\xE7\\xA4\\x10\"\n                 \"1234567\");\n}\n\nAVS_UNIT_TEST(tlv_out, bytes_8blen_8bid) {\n    static const char DATA[] = \"12345678\";\n    TEST_ENV(32, &MAKE_INSTANCE_PATH(0, 0));\n\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_output_set_path(out, &MAKE_RESOURCE_PATH(0, 0, 255)));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_ret_string_unlocked(out, DATA));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_output_ctx_destroy(&out));\n    VERIFY_BYTES(\"\\xC8\\xFF\\x08\"\n                 \"12345678\");\n}\n\nAVS_UNIT_TEST(tlv_out, bytes_8blen_16bid) {\n    static const char DATA[] = \"12345678\";\n    TEST_ENV(32, &MAKE_INSTANCE_PATH(0, 0));\n\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_output_set_path(out, &MAKE_RESOURCE_PATH(0, 0, 65534)));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_ret_string_unlocked(out, DATA));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_output_ctx_destroy(&out));\n    VERIFY_BYTES(\"\\xE8\\xFF\\xFE\\x08\"\n                 \"12345678\");\n}\n\nAVS_UNIT_TEST(tlv_out, bytes_16blen_8bid) {\n    TEST_ENV(1024, &MAKE_INSTANCE_PATH(0, 0));\n\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_output_set_path(out, &MAKE_RESOURCE_PATH(0, 0, 42)));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_ret_string_unlocked(out, DATA1kB));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_output_ctx_destroy(&out));\n    VERIFY_BYTES(\"\\xD0\\x2A\\x03\\xE8\" DATA1kB);\n}\n\nAVS_UNIT_TEST(tlv_out, bytes_16blen_16bid) {\n    TEST_ENV(1024, &MAKE_INSTANCE_PATH(0, 0));\n\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_output_set_path(out, &MAKE_RESOURCE_PATH(0, 0, 42420)));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_ret_string_unlocked(out, DATA1kB));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_output_ctx_destroy(&out));\n    VERIFY_BYTES(\"\\xF0\\xA5\\xB4\\x03\\xE8\" DATA1kB);\n}\n\nAVS_UNIT_TEST(tlv_out, bytes_24blen_8bid) {\n    TEST_ENV(102400, &MAKE_INSTANCE_PATH(0, 0));\n\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_output_set_path(out, &MAKE_RESOURCE_PATH(0, 0, 69)));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_ret_string_unlocked(out, DATA100kB));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_output_ctx_destroy(&out));\n    VERIFY_BYTES(\"\\xD8\\x45\\x01\\x86\\xA0\" DATA100kB);\n}\n\nAVS_UNIT_TEST(tlv_out, bytes_24blen_16bid) {\n    TEST_ENV(102400, &MAKE_INSTANCE_PATH(0, 0));\n\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_output_set_path(out, &MAKE_RESOURCE_PATH(0, 0, 258)));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_ret_string_unlocked(out, DATA100kB));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_output_ctx_destroy(&out));\n    VERIFY_BYTES(\"\\xF8\\x01\\x02\\x01\\x86\\xA0\" DATA100kB);\n}\n\nAVS_UNIT_TEST(tlv_out, bytes_overlength) {\n    TEST_ENV_HEAP(20 * 1024 * 1024, &MAKE_INSTANCE_PATH(0, 0));\n\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_output_set_path(out, &MAKE_RESOURCE_PATH(0, 0, 1)));\n    AVS_UNIT_ASSERT_FAILED(_anjay_ret_string_unlocked(out, DATA20MB));\n    AVS_UNIT_ASSERT_FAILED(_anjay_output_ctx_destroy(&out));\n    avs_free(buf);\n}\n\nAVS_UNIT_TEST(tlv_out, zero_id) {\n    TEST_ENV(32, &MAKE_INSTANCE_PATH(0, 0));\n\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_output_set_path(out, &MAKE_RESOURCE_PATH(0, 0, 0)));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_ret_string_unlocked(out, \"test\"));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_output_ctx_destroy(&out));\n}\n\n#define TEST_INT64_IMPL(Name, Num, Data)                                    \\\n    AVS_UNIT_TEST(tlv_out, Name) {                                          \\\n        TEST_ENV(32, &MAKE_INSTANCE_PATH(0, 0));                            \\\n                                                                            \\\n        AVS_UNIT_ASSERT_SUCCESS(                                            \\\n                _anjay_output_set_path(out, &MAKE_RESOURCE_PATH(0, 0, 1))); \\\n        AVS_UNIT_ASSERT_SUCCESS(_anjay_ret_i64_unlocked(out, Num));         \\\n        AVS_UNIT_ASSERT_SUCCESS(_anjay_output_ctx_destroy(&out));           \\\n        VERIFY_BYTES(Data);                                                 \\\n    }\n\n#define TEST_INT64(Num, Data) \\\n    TEST_INT64_IMPL(AVS_CONCAT(i64_, __LINE__), Num##LL, Data)\n\nTEST_INT64(42,\n           \"\\xC1\\x01\"\n           \"\\x2A\")\nTEST_INT64(4242,\n           \"\\xC2\\x01\"\n           \"\\x10\\x92\")\nTEST_INT64(424242,\n           \"\\xC4\\x01\"\n           \"\\x00\\x06\\x79\\x32\")\nTEST_INT64(42424242,\n           \"\\xC4\\x01\"\n           \"\\x02\\x87\\x57\\xB2\")\nTEST_INT64((int32_t) 4242424242,\n           \"\\xC4\\x01\"\n           \"\\xFC\\xDE\\x41\\xB2\")\nTEST_INT64(4242424242, \"\\xC8\\x01\\x08\\x00\\x00\\x00\\x00\\xFC\\xDE\\x41\\xB2\")\nTEST_INT64(424242424242, \"\\xC8\\x01\\x08\\x00\\x00\\x00\\x62\\xC6\\xD1\\xA9\\xB2\")\nTEST_INT64(42424242424242, \"\\xC8\\x01\\x08\\x00\\x00\\x26\\x95\\xA9\\xE6\\x49\\xB2\")\nTEST_INT64(4242424242424242, \"\\xC8\\x01\\x08\\x00\\x0F\\x12\\x76\\x5D\\xF4\\xC9\\xB2\")\nTEST_INT64(424242424242424242, \"\\xC8\\x01\\x08\\x05\\xE3\\x36\\x3C\\xB3\\x9E\\xC9\\xB2\")\n\n#ifdef ANJAY_WITH_LWM2M11\n#    define TEST_UINT64_IMPL(Name, Num, Data)                           \\\n        AVS_UNIT_TEST(tlv_out, Name) {                                  \\\n            TEST_ENV(32, &MAKE_INSTANCE_PATH(0, 0));                    \\\n                                                                        \\\n            AVS_UNIT_ASSERT_SUCCESS(_anjay_output_set_path(             \\\n                    out, &MAKE_RESOURCE_PATH(0, 0, 1)));                \\\n            AVS_UNIT_ASSERT_SUCCESS(_anjay_ret_u64_unlocked(out, Num)); \\\n            AVS_UNIT_ASSERT_SUCCESS(_anjay_output_ctx_destroy(&out));   \\\n            VERIFY_BYTES(Data);                                         \\\n        }\n\n#    define TEST_UINT64(Num, Data) \\\n        TEST_UINT64_IMPL(AVS_CONCAT(u64_, __LINE__), Num##ULL, Data)\n\nTEST_UINT64(42,\n            \"\\xC1\\x01\"\n            \"\\x2A\")\nTEST_UINT64(4242,\n            \"\\xC2\\x01\"\n            \"\\x10\\x92\")\nTEST_UINT64(4294967295,\n            \"\\xC4\\x01\"\n            \"\\xFF\\xFF\\xFF\\xFF\")\nTEST_UINT64(4294967296,\n            \"\\xC8\\x01\\x08\"\n            \"\\x00\\x00\\x00\\x01\\x00\\x00\\x00\\x00\")\nTEST_UINT64(18446744073709551615,\n            \"\\xC8\\x01\\x08\"\n            \"\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\")\n#endif // ANJAY_WITH_LWM2M11\n\n#define TEST_DOUBLE_IMPL(Name, Num, Data)                                   \\\n    AVS_UNIT_TEST(tlv_out, Name) {                                          \\\n        TEST_ENV(32, &MAKE_INSTANCE_PATH(0, 0));                            \\\n                                                                            \\\n        AVS_UNIT_ASSERT_SUCCESS(                                            \\\n                _anjay_output_set_path(out, &MAKE_RESOURCE_PATH(0, 0, 1))); \\\n        AVS_UNIT_ASSERT_SUCCESS(_anjay_ret_double_unlocked(out, Num));      \\\n        AVS_UNIT_ASSERT_SUCCESS(_anjay_output_ctx_destroy(&out));           \\\n        VERIFY_BYTES(Data);                                                 \\\n    }\n\n#define TEST_DOUBLE(Num, Data) \\\n    TEST_DOUBLE_IMPL(AVS_CONCAT(double, __LINE__), Num, Data)\n\nTEST_DOUBLE(-42.0e3, \"\\xC4\\x01\\xC7\\x24\\x10\\x00\")\n\n// rounds exactly to float\nTEST_DOUBLE(1.0, \"\\xC4\\x01\\x3F\\x80\\x00\\x00\")\n\n// using double increases precision\nTEST_DOUBLE(1.1, \"\\xC8\\x01\\x08\\x3F\\xF1\\x99\\x99\\x99\\x99\\x99\\x9A\")\n\n#define TEST_BOOL(Val, Data)                                                \\\n    AVS_UNIT_TEST(tlv_out, bool_##Val) {                                    \\\n        TEST_ENV(32, &MAKE_INSTANCE_PATH(0, 0));                            \\\n                                                                            \\\n        AVS_UNIT_ASSERT_SUCCESS(                                            \\\n                _anjay_output_set_path(out, &MAKE_RESOURCE_PATH(0, 0, 1))); \\\n        AVS_UNIT_ASSERT_SUCCESS(_anjay_ret_bool_unlocked(out, Val));        \\\n        AVS_UNIT_ASSERT_SUCCESS(_anjay_output_ctx_destroy(&out));           \\\n        VERIFY_BYTES(\"\\xC1\\x01\" Data);                                      \\\n    }\n\nTEST_BOOL(true, \"\\1\")\nTEST_BOOL(false, \"\\0\")\nTEST_BOOL(1, \"\\1\")\nTEST_BOOL(0, \"\\0\")\nTEST_BOOL(42, \"\\1\")\n\n#define TEST_OBJLNK(Oid, Iid, Data)                                         \\\n    AVS_UNIT_TEST(tlv_out, objlnk_##Oid##_##Iid) {                          \\\n        TEST_ENV(32, &MAKE_INSTANCE_PATH(0, 0));                            \\\n                                                                            \\\n        AVS_UNIT_ASSERT_SUCCESS(                                            \\\n                _anjay_output_set_path(out, &MAKE_RESOURCE_PATH(0, 0, 1))); \\\n        AVS_UNIT_ASSERT_SUCCESS(_anjay_ret_objlnk_unlocked(out, Oid, Iid)); \\\n        AVS_UNIT_ASSERT_SUCCESS(_anjay_output_ctx_destroy(&out));           \\\n        VERIFY_BYTES(\"\\xC4\\x01\" Data);                                      \\\n    }\n\nTEST_OBJLNK(0, 0, \"\\x00\\x00\\x00\\x00\")\nTEST_OBJLNK(1, 0, \"\\x00\\x01\\x00\\x00\")\nTEST_OBJLNK(0, 1, \"\\x00\\x00\\x00\\x01\")\nTEST_OBJLNK(1, 65535, \"\\x00\\x01\\xFF\\xFF\")\nTEST_OBJLNK(65535, 1, \"\\xFF\\xFF\\x00\\x01\")\nTEST_OBJLNK(65535, 65535, \"\\xFF\\xFF\\xFF\\xFF\")\n\n////////////////////////////////////////////////////////////// ENCODING // ARRAY\n\nAVS_UNIT_TEST(tlv_out_array, simple) {\n    TEST_ENV(512, &MAKE_INSTANCE_PATH(0, 0));\n\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_output_set_path(\n            out, &MAKE_RESOURCE_INSTANCE_PATH(0, 0, 1, 42)));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_ret_i64_unlocked(out, 69));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_output_set_path(\n            out, &MAKE_RESOURCE_INSTANCE_PATH(0, 0, 1, 514)));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_ret_i64_unlocked(out, 696969));\n\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_output_set_path(out, &MAKE_RESOURCE_PATH(0, 0, 2)));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_ret_i64_unlocked(out, 4));\n\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_output_ctx_destroy(&out));\n\n    VERIFY_BYTES(\"\\x88\\x01\\x0A\"                 // array\n                 \"\\x41\\x2A\\x45\"                 // first entry\n                 \"\\x64\\x02\\x02\\x00\\x0A\\xA2\\x89\" // second entry\n                 \"\\xC1\\x02\\x04\"                 // another entry\n    );\n}\n\nAVS_UNIT_TEST(tlv_out_array, too_long) {\n    TEST_ENV_HEAP(100 * 1024 * 1024, &MAKE_INSTANCE_PATH(0, 0));\n\n    for (size_t i = 0; i < 20; ++i) {\n        // 1 MB each entry, 20 MB altogether\n        AVS_UNIT_ASSERT_SUCCESS(_anjay_output_set_path(\n                out, &MAKE_RESOURCE_INSTANCE_PATH(0, 0, 1, 1)));\n        AVS_UNIT_ASSERT_SUCCESS(_anjay_ret_string_unlocked(out, DATA1MB));\n    }\n    AVS_UNIT_ASSERT_FAILED(_anjay_output_ctx_destroy(&out));\n    avs_free(buf);\n}\n\nAVS_UNIT_TEST(tlv_out_array, array_index) {\n    TEST_ENV(512, &MAKE_INSTANCE_PATH(0, 0));\n\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_output_set_path(\n            out, &MAKE_RESOURCE_INSTANCE_PATH(0, 0, 1, 65534)));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_ret_i64_unlocked(out, 69));\n\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_output_ctx_destroy(&out));\n}\n\nAVS_UNIT_TEST(tlv_out, object_with_empty_bytes) {\n    TEST_ENV(512, &MAKE_OBJECT_PATH(0));\n\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_output_set_path(out, &MAKE_RESOURCE_PATH(0, 1, 0)));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_ret_bytes_unlocked(out, \"\", 0));\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_output_set_path(out, &MAKE_RESOURCE_PATH(0, 1, 1)));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_ret_bytes_unlocked(out, \"\", 1));\n\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_output_ctx_destroy(&out));\n}\n\n//////////////////////////////////////////// ENCODING // ADDITIONAL CORNER CASES\n\nAVS_UNIT_TEST(tlv_out, riid_as_root) {\n    static const char DATA[] = \"1234567\";\n    TEST_ENV(512, &MAKE_RESOURCE_INSTANCE_PATH(0, 0, 0, 0));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_output_set_path(\n            out, &MAKE_RESOURCE_INSTANCE_PATH(0, 0, 0, 0)));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_ret_string_unlocked(out, DATA));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_output_ctx_destroy(&out));\n    VERIFY_BYTES(\"\\x47\\x00\"\n                 \"1234567\");\n}\n\nAVS_UNIT_TEST(tlv_out, set_path) {\n    TEST_ENV(512, &MAKE_OBJECT_PATH(0));\n    AVS_UNIT_ASSERT_EQUAL(((tlv_out_t *) out)->level, TLV_OUT_LEVEL_IID);\n    AVS_UNIT_ASSERT_EQUAL(\n            ((tlv_out_t *) out)->levels[TLV_OUT_LEVEL_IID].next_id,\n            ANJAY_ID_INVALID);\n\n    // set path downwards\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_output_set_path(\n            out, &MAKE_RESOURCE_INSTANCE_PATH(0, 0, 0, 0)));\n    AVS_UNIT_ASSERT_EQUAL(((tlv_out_t *) out)->level, TLV_OUT_LEVEL_RIID);\n    AVS_UNIT_ASSERT_EQUAL(\n            ((tlv_out_t *) out)->levels[TLV_OUT_LEVEL_IID].next_id, 0);\n    AVS_UNIT_ASSERT_EQUAL(\n            ((tlv_out_t *) out)->levels[TLV_OUT_LEVEL_RID].next_id, 0);\n    AVS_UNIT_ASSERT_EQUAL(\n            ((tlv_out_t *) out)->levels[TLV_OUT_LEVEL_RIID].next_id, 0);\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_ret_bytes_unlocked(out, NULL, 0));\n\n    // set path upwards\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_output_set_path(out, &MAKE_INSTANCE_PATH(0, 0)));\n    AVS_UNIT_ASSERT_EQUAL(((tlv_out_t *) out)->level, TLV_OUT_LEVEL_IID);\n    AVS_UNIT_ASSERT_EQUAL(\n            ((tlv_out_t *) out)->levels[TLV_OUT_LEVEL_IID].next_id, 0);\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_output_start_aggregate(out));\n\n    // set path downwards again\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_output_set_path(\n            out, &MAKE_RESOURCE_INSTANCE_PATH(0, 1, 2, 3)));\n    AVS_UNIT_ASSERT_EQUAL(((tlv_out_t *) out)->level, TLV_OUT_LEVEL_RIID);\n    AVS_UNIT_ASSERT_EQUAL(\n            ((tlv_out_t *) out)->levels[TLV_OUT_LEVEL_IID].next_id, 1);\n    AVS_UNIT_ASSERT_EQUAL(\n            ((tlv_out_t *) out)->levels[TLV_OUT_LEVEL_RID].next_id, 2);\n    AVS_UNIT_ASSERT_EQUAL(\n            ((tlv_out_t *) out)->levels[TLV_OUT_LEVEL_RIID].next_id, 3);\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_ret_bytes_unlocked(out, NULL, 0));\n\n    // set unrelated path\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_output_set_path(\n            out, &MAKE_RESOURCE_INSTANCE_PATH(0, 4, 5, 6)));\n    AVS_UNIT_ASSERT_EQUAL(((tlv_out_t *) out)->level, TLV_OUT_LEVEL_RIID);\n    AVS_UNIT_ASSERT_EQUAL(\n            ((tlv_out_t *) out)->levels[TLV_OUT_LEVEL_IID].next_id, 4);\n    AVS_UNIT_ASSERT_EQUAL(\n            ((tlv_out_t *) out)->levels[TLV_OUT_LEVEL_RID].next_id, 5);\n    AVS_UNIT_ASSERT_EQUAL(\n            ((tlv_out_t *) out)->levels[TLV_OUT_LEVEL_RIID].next_id, 6);\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_ret_bytes_unlocked(out, NULL, 0));\n\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_output_ctx_destroy(&out));\n    VERIFY_BYTES(\"\\x04\\x00\" // instance /0/0\n                 \"\\x82\\x00\" // multiple resource /0/0/0\n                 \"\\x40\\x00\" // resource instance /0/0/0/0\n                 \"\\x00\\x00\" // instance /0/0 (again)\n                 \"\\x04\\x01\" // instance /0/1\n                 \"\\x82\\x02\" // multiple resource /0/1/2\n                 \"\\x40\\x03\" // resource instance /0/1/2/3\n                 \"\\x04\\x04\" // instance /0/4\n                 \"\\x82\\x05\" // multiple resource /0/4/5\n                 \"\\x40\\x06\" // resource instance /0/4/5/6\n    );\n}\n"
  },
  {
    "path": "tests/core/io.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#include <avsystem/commons/avs_stream_inbuf.h>\n#include <avsystem/commons/avs_unit_test.h>\n\n#include <anjay/core.h>\n\n#define TEST_ENV(Data)                                               \\\n    avs_stream_inbuf_t stream = AVS_STREAM_INBUF_STATIC_INITIALIZER; \\\n    avs_stream_inbuf_set_buffer(&stream, Data, sizeof(Data) - 1);    \\\n    anjay_unlocked_input_ctx_t *ctx;                                 \\\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_input_tlv_create(                 \\\n            &ctx,                                                    \\\n            (avs_stream_t *) &stream,                                \\\n            &MAKE_OBJECT_PATH(ANJAY_DM_OID_ACCESS_CONTROL)));\n\n#define TEST_TEARDOWN _anjay_input_ctx_destroy(&ctx);\n\nAVS_UNIT_TEST(input_array, example) {\n    TEST_ENV(              // example from spec 6.3.3.2\n            \"\\x08\\x00\\x11\" // Object Instance 0\n            \"\\xC1\\x00\\x03\" // Resource 0 - Object ID == 3\n            \"\\xC1\\x01\\x01\" // Resource 1 - Instance ID == 1\n            \"\\x86\\x02\"     // Resource 2 - ACL array\n            \"\\x41\\x01\\xE0\" // [1] -> -32\n            \"\\x41\\x02\\x80\" // [2] -> -128\n            \"\\xC1\\x03\\x01\" // Resource 3 - ACL owner == 1\n            \"\\x08\\x01\\x11\" // Object Instance 1\n            \"\\xC1\\x00\\x04\" // Resource 0 - Object ID == 4\n            \"\\xC1\\x01\\x02\" // Resource 1 - Instance ID == 2\n            \"\\x86\\x02\"     // Resource 2 - ACL array\n            \"\\x41\\x01\\x80\" // [1] -> -128\n            \"\\x41\\x02\\x80\" // [2] -> -128\n            \"\\xC1\\x03\\x01\" // Resource 3 - ACL owner == 1\n    );\n\n    anjay_uri_path_t path;\n    int64_t value;\n\n    // check paths for the first object\n    {\n        AVS_UNIT_ASSERT_SUCCESS(_anjay_input_get_path(ctx, &path, NULL));\n        AVS_UNIT_ASSERT_TRUE(_anjay_uri_path_equal(\n                &path, &MAKE_RESOURCE_PATH(ANJAY_DM_OID_ACCESS_CONTROL, 0, 0)));\n        AVS_UNIT_ASSERT_SUCCESS(_anjay_get_i64_unlocked(ctx, &value));\n        AVS_UNIT_ASSERT_EQUAL(value, 3);\n\n        AVS_UNIT_ASSERT_SUCCESS(_anjay_input_next_entry(ctx));\n        AVS_UNIT_ASSERT_SUCCESS(_anjay_input_get_path(ctx, &path, NULL));\n        AVS_UNIT_ASSERT_TRUE(_anjay_uri_path_equal(\n                &path, &MAKE_RESOURCE_PATH(ANJAY_DM_OID_ACCESS_CONTROL, 0, 1)));\n        AVS_UNIT_ASSERT_SUCCESS(_anjay_get_i64_unlocked(ctx, &value));\n        AVS_UNIT_ASSERT_EQUAL(value, 1);\n\n        AVS_UNIT_ASSERT_SUCCESS(_anjay_input_next_entry(ctx));\n        AVS_UNIT_ASSERT_SUCCESS(_anjay_input_get_path(ctx, &path, NULL));\n        AVS_UNIT_ASSERT_TRUE(_anjay_uri_path_equal(\n                &path,\n                &MAKE_RESOURCE_INSTANCE_PATH(\n                        ANJAY_DM_OID_ACCESS_CONTROL, 0, 2, 1)));\n        AVS_UNIT_ASSERT_SUCCESS(_anjay_get_i64_unlocked(ctx, &value));\n        AVS_UNIT_ASSERT_EQUAL(value, -32);\n\n        AVS_UNIT_ASSERT_SUCCESS(_anjay_input_next_entry(ctx));\n        AVS_UNIT_ASSERT_SUCCESS(_anjay_input_get_path(ctx, &path, NULL));\n        AVS_UNIT_ASSERT_TRUE(_anjay_uri_path_equal(\n                &path,\n                &MAKE_RESOURCE_INSTANCE_PATH(\n                        ANJAY_DM_OID_ACCESS_CONTROL, 0, 2, 2)));\n        AVS_UNIT_ASSERT_SUCCESS(_anjay_get_i64_unlocked(ctx, &value));\n        AVS_UNIT_ASSERT_EQUAL(value, -128);\n\n        AVS_UNIT_ASSERT_SUCCESS(_anjay_input_next_entry(ctx));\n        AVS_UNIT_ASSERT_SUCCESS(_anjay_input_get_path(ctx, &path, NULL));\n        AVS_UNIT_ASSERT_TRUE(_anjay_uri_path_equal(\n                &path, &MAKE_RESOURCE_PATH(ANJAY_DM_OID_ACCESS_CONTROL, 0, 3)));\n        AVS_UNIT_ASSERT_SUCCESS(_anjay_get_i64_unlocked(ctx, &value));\n        AVS_UNIT_ASSERT_EQUAL(value, 1);\n    }\n    _anjay_input_next_entry(ctx);\n\n    // do a half-assed job decoding the second one\n    {\n        AVS_UNIT_ASSERT_SUCCESS(_anjay_get_i64_unlocked(ctx, &value));\n        AVS_UNIT_ASSERT_EQUAL(value, 4);\n        AVS_UNIT_ASSERT_SUCCESS(_anjay_input_next_entry(ctx));\n\n        AVS_UNIT_ASSERT_SUCCESS(_anjay_get_i64_unlocked(ctx, &value));\n        AVS_UNIT_ASSERT_EQUAL(value, 2);\n        AVS_UNIT_ASSERT_SUCCESS(_anjay_input_next_entry(ctx));\n\n        AVS_UNIT_ASSERT_SUCCESS(_anjay_get_i64_unlocked(ctx, &value));\n        AVS_UNIT_ASSERT_EQUAL(value, -128);\n\n        // value already consumed\n        AVS_UNIT_ASSERT_FAILED(_anjay_get_i64_unlocked(ctx, &value));\n\n        AVS_UNIT_ASSERT_SUCCESS(_anjay_input_next_entry(ctx));\n        AVS_UNIT_ASSERT_SUCCESS(_anjay_get_i64_unlocked(ctx, &value));\n        AVS_UNIT_ASSERT_EQUAL(value, -128);\n\n        AVS_UNIT_ASSERT_SUCCESS(_anjay_input_next_entry(ctx));\n\n        AVS_UNIT_ASSERT_SUCCESS(_anjay_get_i64_unlocked(ctx, &value));\n        AVS_UNIT_ASSERT_EQUAL(value, 1);\n    }\n    _anjay_input_next_entry(ctx);\n\n    AVS_UNIT_ASSERT_EQUAL(_anjay_input_get_path(ctx, &path, NULL),\n                          ANJAY_GET_PATH_END);\n\n    TEST_TEARDOWN;\n}\n"
  },
  {
    "path": "tests/core/lwm2m_send.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#include <anjay/lwm2m_send.h>\n\n#include <avsystem/commons/avs_unit_mocksock.h>\n#include <avsystem/commons/avs_unit_test.h>\n#include <avsystem/commons/avs_utils.h>\n\n#include <avsystem/coap/async.h>\n\n#include <inttypes.h>\n\n#include \"src/core/anjay_core.h\"\n#include \"src/core/coap/anjay_content_format.h\"\n#include \"src/core/io/anjay_batch_builder.h\"\n#include \"src/core/io/anjay_common.h\"\n#include \"src/core/io/anjay_vtable.h\"\n#include \"src/core/io/cbor/anjay_cbor_types.h\"\n#include \"src/core/servers/anjay_servers_internal.h\"\n#include \"src/modules/server/anjay_mod_server.h\"\n#include \"tests/core/coap/utils.h\"\n#include \"tests/utils/dm.h\"\n\n#ifdef ANJAY_WITH_LWM2M_GATEWAY\n#    include <anjay/lwm2m_gateway.h>\n#endif // ANJAY_WITH_LWM2M_GATEWAY\n\nstatic const anjay_ssid_t SSID = 1;\n\nstatic const uint16_t MSG_ID = 0x0000;\n\nstatic const anjay_uri_path_t URI_PATH =\n        RESOURCE_PATH_INITIALIZER(42, 0xDEAD, 0);\n\nstatic const uint16_t VALUE = 0xFACE;\n\nstatic anjay_send_batch_t *\nget_new_batch_with_int_value(anjay_uri_path_t resource_path,\n                             uint64_t resource_value) {\n    assert(_anjay_uri_path_leaf_is(&resource_path, ANJAY_ID_RID));\n    anjay_send_batch_builder_t *builder = anjay_send_batch_builder_new();\n    AVS_UNIT_ASSERT_NOT_NULL(builder);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_send_batch_add_uint(\n            builder, resource_path.ids[ANJAY_ID_OID],\n            resource_path.ids[ANJAY_ID_IID], resource_path.ids[ANJAY_ID_RID],\n            ANJAY_ID_INVALID, AVS_TIME_REAL_INVALID, resource_value));\n    anjay_send_batch_t *batch = anjay_send_batch_builder_compile(&builder);\n    AVS_UNIT_ASSERT_NULL(builder);\n    AVS_UNIT_ASSERT_NOT_NULL(batch);\n    return batch;\n}\n\nstatic anjay_send_batch_t *get_new_batch_with_int_value_from_dm(\n        anjay_t *anjay,\n        const anjay_dm_object_def_t *const *obj_ptr,\n        anjay_uri_path_t resource_path,\n        int resource_value) {\n    assert(_anjay_uri_path_leaf_is(&resource_path, ANJAY_ID_RID));\n    anjay_send_batch_builder_t *builder = anjay_send_batch_builder_new();\n    AVS_UNIT_ASSERT_NOT_NULL(builder);\n    _anjay_mock_dm_expect_list_instances(\n            anjay, obj_ptr, 0,\n            (const anjay_iid_t[]) { resource_path.ids[ANJAY_ID_IID],\n                                    ANJAY_ID_INVALID });\n    anjay_mock_dm_res_entry_t resources[] = {\n        { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n        { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n        { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n        { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n        { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n        { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n        { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n        ANJAY_MOCK_DM_RES_END\n    };\n    size_t i;\n    for (i = 0; i < AVS_ARRAY_SIZE(resources); ++i) {\n        if (resources[i].rid == resource_path.ids[ANJAY_ID_RID]) {\n            resources[i].presence = ANJAY_DM_RES_PRESENT;\n            break;\n        }\n    }\n    AVS_UNIT_ASSERT_TRUE(i < AVS_ARRAY_SIZE(resources));\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, resource_path.ids[ANJAY_ID_IID], 0, resources);\n    _anjay_mock_dm_expect_resource_read(anjay, obj_ptr,\n                                        resource_path.ids[ANJAY_ID_IID],\n                                        resource_path.ids[ANJAY_ID_RID],\n                                        ANJAY_ID_INVALID, 0,\n                                        ANJAY_MOCK_DM_INT(0, resource_value));\n    AVS_UNIT_ASSERT_SUCCESS(anjay_send_batch_data_add_current(\n            builder, anjay, resource_path.ids[ANJAY_ID_OID],\n            resource_path.ids[ANJAY_ID_IID], resource_path.ids[ANJAY_ID_RID]));\n    anjay_send_batch_t *batch = anjay_send_batch_builder_compile(&builder);\n    AVS_UNIT_ASSERT_NULL(builder);\n    AVS_UNIT_ASSERT_NOT_NULL(batch);\n    return batch;\n}\n\ntypedef struct {\n    anjay_send_finished_handler_t *real_handler;\n    void *real_handler_data;\n} test_finished_handler_arg_t;\n\nstatic AVS_LIST(test_finished_handler_arg_t) HANDLER_WRAPPER_ARGS = NULL;\n\nstatic void test_finished_handler_wrapper(anjay_t *anjay,\n                                          anjay_ssid_t ssid,\n                                          const anjay_send_batch_t *batch,\n                                          int result,\n                                          void *arg_) {\n    test_finished_handler_arg_t *arg = (test_finished_handler_arg_t *) arg_;\n    if (arg->real_handler) {\n        arg->real_handler(anjay, ssid, batch, result, arg->real_handler_data);\n    }\n    AVS_LIST(test_finished_handler_arg_t) *arg_ptr =\n            AVS_LIST_FIND_PTR(&HANDLER_WRAPPER_ARGS, arg);\n    AVS_UNIT_ASSERT_NOT_NULL(arg_ptr);\n    AVS_LIST_DELETE(arg_ptr);\n}\n\nstatic void assert_there_is_not_any_server(anjay_t *anjay) {\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &FAKE_SERVER, 0, (const anjay_iid_t[]) { ANJAY_ID_INVALID });\n}\n\nstatic const anjay_mock_dm_res_entry_t SERVER_RESOURCES[] = {\n    { SERV_RES_SSID, ANJAY_DM_RES_R, ANJAY_DM_RES_PRESENT },\n    { SERV_RES_MUTE_SEND, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n    ANJAY_MOCK_DM_RES_END\n};\n\nstatic void assert_there_is_server_with_ssid(anjay_ssid_t ssid,\n                                             anjay_t *anjay) {\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &FAKE_SERVER, 0,\n            (const anjay_iid_t[]) { ssid, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(anjay, &FAKE_SERVER, ssid, 0,\n                                         SERVER_RESOURCES);\n    _anjay_mock_dm_expect_resource_read(anjay, &FAKE_SERVER, ssid,\n                                        SERV_RES_SSID, ANJAY_ID_INVALID, 0,\n                                        ANJAY_MOCK_DM_INT(0, ssid));\n}\n\nstatic void assert_mute_send_resource_read_failure(anjay_t *anjay,\n                                                   anjay_ssid_t ssid) {\n    _anjay_mock_dm_expect_list_resources(anjay, &FAKE_SERVER, ssid, 0,\n                                         SERVER_RESOURCES);\n    _anjay_mock_dm_expect_resource_read(anjay, &FAKE_SERVER, ssid,\n                                        SERV_RES_MUTE_SEND, ANJAY_ID_INVALID,\n                                        ANJAY_ERR_INTERNAL, ANJAY_MOCK_DM_NONE);\n}\n\nstatic void assert_mute_send_resource_equals(bool value,\n                                             anjay_t *anjay,\n                                             anjay_ssid_t ssid) {\n    _anjay_mock_dm_expect_list_resources(anjay, &FAKE_SERVER, ssid, 0,\n                                         SERVER_RESOURCES);\n    _anjay_mock_dm_expect_resource_read(anjay, &FAKE_SERVER, ssid,\n                                        SERV_RES_MUTE_SEND, ANJAY_ID_INVALID, 0,\n                                        ANJAY_MOCK_DM_BOOL(0, value));\n}\n\nstatic void\ntest_call_anjay_send(anjay_t *anjay,\n                     anjay_ssid_t ssid,\n                     const anjay_send_batch_t *data,\n                     anjay_send_finished_handler_t *finished_handler,\n                     void *finished_handler_data) {\n    assert_there_is_server_with_ssid(ssid, anjay);\n    assert_mute_send_resource_equals(false, anjay, ssid);\n\n    AVS_LIST(test_finished_handler_arg_t) wrapper_arg =\n            AVS_LIST_NEW_ELEMENT(test_finished_handler_arg_t);\n    AVS_UNIT_ASSERT_NOT_NULL(wrapper_arg);\n    wrapper_arg->real_handler = finished_handler;\n    wrapper_arg->real_handler_data = finished_handler_data;\n    AVS_LIST_INSERT(&HANDLER_WRAPPER_ARGS, wrapper_arg);\n\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    anjay_unlocked->servers->registration_info.lwm2m_version =\n            ANJAY_LWM2M_VERSION_1_1;\n    ANJAY_MUTEX_UNLOCK(anjay);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_send(\n            anjay, ssid, data, test_finished_handler_wrapper, wrapper_arg));\n    anjay_sched_run(anjay);\n}\n\ntypedef struct {\n    char payload[2048];\n    size_t payload_size;\n} expected_payload_t;\n\nstatic expected_payload_t\nget_expected_payload_for_batch_with_int_value(anjay_uri_path_t resource_path,\n                                              uint16_t resource_value,\n                                              double timestamp) {\n    AVS_ASSERT(resource_value > UINT8_MAX,\n               \"this function encodes properly only values greater than 0xFF\");\n    uint16_t converted_value = avs_convert_be16(resource_value);\n\n    assert(_anjay_uri_path_leaf_is(&resource_path, ANJAY_ID_RID));\n    const char *path_str = ANJAY_DEBUG_MAKE_PATH(&resource_path);\n    size_t path_str_len = strlen(path_str);\n    AVS_UNIT_ASSERT_TRUE(\n            path_str_len\n            <= 33); // otherwise the CBOR encoding would be different\n    char cbor_path_header = (char) (0x60 + path_str_len);\n\n    expected_payload_t result;\n    int payload_size;\n    int value_offset;\n    if (isnan(timestamp)) {\n        AVS_UNIT_ASSERT_TRUE(\n                (payload_size = avs_simple_snprintf(\n                         result.payload, sizeof(result.payload),\n                         \"\\x81\\xA2\\x21%c%s%c%c%n__\", cbor_path_header, path_str,\n                         SENML_LABEL_VALUE, CBOR_EXT_LENGTH_2BYTE,\n                         &value_offset))\n                >= 0);\n        memcpy(result.payload + value_offset, &converted_value,\n               sizeof(converted_value));\n    } else {\n        // This payload is valid only for timestamp that must be 8 bytes double\n        // and cannot be converted to 4-byte float\n        AVS_UNIT_ASSERT_FALSE(((float) timestamp) == timestamp);\n        int timestamp_offset;\n        uint64_t converted_timestamp = avs_htond(timestamp);\n        AVS_UNIT_ASSERT_TRUE(\n                (payload_size = avs_simple_snprintf(\n                         result.payload, sizeof(result.payload),\n                         \"\\x81\\xA3\\x21%c%s\\x22\\xFB%n________%c%c%n__\",\n                         cbor_path_header, path_str, &timestamp_offset,\n                         SENML_LABEL_VALUE, CBOR_EXT_LENGTH_2BYTE,\n                         &value_offset))\n                >= 0);\n        memcpy(result.payload + timestamp_offset, &converted_timestamp,\n               sizeof(converted_timestamp));\n        memcpy(result.payload + value_offset, &converted_value,\n               sizeof(converted_value));\n    }\n    result.payload_size = (size_t) payload_size;\n    return result;\n}\n\nstatic void\ntest_expect_scheduled_lwm2m_send_request(avs_net_socket_t *mocksock,\n                                         uint16_t msg_id,\n                                         avs_coap_token_t token,\n                                         expected_payload_t expected_payload) {\n    DM_TEST_REQUEST_FROM_CLIENT(\n            mocksock, CON, POST, ID_TOKEN_RAW(msg_id, token), PATH(\"dp\"),\n            CONTENT_FORMAT(SENML_CBOR),\n            PAYLOAD_EXTERNAL(expected_payload.payload,\n                             expected_payload.payload_size));\n}\n\nstatic void test_handle_lwm2m_send_response(anjay_t *anjay,\n                                            avs_net_socket_t *mocksock,\n                                            const coap_test_msg_t *msg) {\n    size_t old_queue_size = AVS_LIST_SIZE(HANDLER_WRAPPER_ARGS);\n\n    avs_unit_mocksock_input(mocksock, msg->content, msg->length);\n    expect_has_buffered_data_check(mocksock, false);\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    avs_coap_async_handle_incoming_packet(\n            _anjay_connection_get(&anjay_unlocked->servers->connections,\n                                  ANJAY_CONNECTION_PRIMARY)\n                    ->coap_ctx,\n            NULL, NULL);\n    ANJAY_MUTEX_UNLOCK(anjay);\n\n    AVS_UNIT_ASSERT_EQUAL(AVS_LIST_SIZE(HANDLER_WRAPPER_ARGS),\n                          old_queue_size - 1);\n}\n\nstatic void\nsend_finished_handler_result_validator(anjay_t *anjay,\n                                       anjay_ssid_t ssid,\n                                       const anjay_send_batch_t *batch,\n                                       int result,\n                                       void *expected_result) {\n    (void) anjay;\n    AVS_UNIT_ASSERT_EQUAL(ssid, SSID);\n    AVS_UNIT_ASSERT_NOT_NULL(batch);\n    AVS_UNIT_ASSERT_EQUAL(result, (int) (intptr_t) expected_result);\n}\n\nAVS_UNIT_TEST(anjay_send, success) {\n    DM_TEST_INIT;\n\n    anjay_send_batch_t *batch = get_new_batch_with_int_value(URI_PATH, VALUE);\n    test_expect_scheduled_lwm2m_send_request(\n            mocksocks[0], MSG_ID, nth_token(0),\n            get_expected_payload_for_batch_with_int_value(URI_PATH, VALUE,\n                                                          NAN));\n    test_call_anjay_send(anjay, SSID, batch,\n                         send_finished_handler_result_validator,\n                         (void *) (intptr_t) ANJAY_SEND_SUCCESS);\n    anjay_send_batch_release(&batch);\n    test_handle_lwm2m_send_response(anjay, mocksocks[0],\n                                    COAP_MSG(ACK, CHANGED,\n                                             ID_TOKEN_RAW(MSG_ID, nth_token(0)),\n                                             NO_PAYLOAD));\n\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(anjay_send, empty) {\n    DM_TEST_INIT;\n\n    anjay_send_batch_builder_t *builder = anjay_send_batch_builder_new();\n    AVS_UNIT_ASSERT_NOT_NULL(builder);\n    anjay_send_batch_t *batch = anjay_send_batch_builder_compile(&builder);\n    AVS_UNIT_ASSERT_NULL(builder);\n    AVS_UNIT_ASSERT_NOT_NULL(batch);\n    test_expect_scheduled_lwm2m_send_request(\n            mocksocks[0], MSG_ID, nth_token(0),\n            (expected_payload_t) { { (char) 0x80 }, 1 });\n    test_call_anjay_send(anjay, SSID, batch,\n                         send_finished_handler_result_validator,\n                         (void *) (intptr_t) ANJAY_SEND_SUCCESS);\n    anjay_send_batch_release(&batch);\n    test_handle_lwm2m_send_response(anjay, mocksocks[0],\n                                    COAP_MSG(ACK, CHANGED,\n                                             ID_TOKEN_RAW(MSG_ID, nth_token(0)),\n                                             NO_PAYLOAD));\n\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(anjay_send, error_responses) {\n    DM_TEST_INIT;\n    anjay_send_batch_t *batch = get_new_batch_with_int_value(URI_PATH, VALUE);\n\n    test_expect_scheduled_lwm2m_send_request(\n            mocksocks[0], MSG_ID, nth_token(0),\n            get_expected_payload_for_batch_with_int_value(URI_PATH, VALUE,\n                                                          NAN));\n    test_call_anjay_send(anjay, SSID, batch,\n                         send_finished_handler_result_validator,\n                         (void *) (intptr_t) ANJAY_ERR_SERVICE_UNAVAILABLE);\n    test_handle_lwm2m_send_response(anjay, mocksocks[0],\n                                    COAP_MSG(ACK, SERVICE_UNAVAILABLE,\n                                             ID_TOKEN_RAW(MSG_ID, nth_token(0)),\n                                             NO_PAYLOAD));\n\n    test_expect_scheduled_lwm2m_send_request(\n            mocksocks[0], (uint16_t) (MSG_ID + 1), nth_token(1),\n            get_expected_payload_for_batch_with_int_value(URI_PATH, VALUE,\n                                                          NAN));\n    test_call_anjay_send(anjay, SSID, batch,\n                         send_finished_handler_result_validator,\n                         (void *) (intptr_t) ANJAY_ERR_FORBIDDEN);\n    test_handle_lwm2m_send_response(\n            anjay, mocksocks[0],\n            COAP_MSG(ACK, FORBIDDEN,\n                     ID_TOKEN_RAW((uint16_t) (MSG_ID + 1), nth_token(1)),\n                     NO_PAYLOAD));\n\n    anjay_send_batch_release(&batch);\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(anjay_send, partial_success) {\n    DM_TEST_INIT;\n\n    anjay_send_batch_t *batch = get_new_batch_with_int_value(URI_PATH, VALUE);\n    test_expect_scheduled_lwm2m_send_request(\n            mocksocks[0], MSG_ID, nth_token(0),\n            get_expected_payload_for_batch_with_int_value(URI_PATH, VALUE,\n                                                          NAN));\n    test_call_anjay_send(anjay, SSID, batch,\n                         send_finished_handler_result_validator,\n                         (void *) (intptr_t) ANJAY_SEND_SUCCESS);\n    anjay_send_batch_release(&batch);\n    test_handle_lwm2m_send_response(\n            anjay, mocksocks[0],\n            COAP_MSG(ACK, CHANGED, ID_TOKEN_RAW(MSG_ID, nth_token(0)),\n                     BLOCK2(0, 16, \"12345678901234567890\")));\n\n    DM_TEST_FINISH;\n}\n\nstatic void test_expect_scheduled_lwm2m_send_retransmissions(\n        anjay_t *anjay,\n        avs_net_socket_t *mocksock,\n        uint16_t msg_id,\n        avs_coap_token_t token,\n        expected_payload_t expected_payload,\n        unsigned expected_retransmissions_count) {\n    for (unsigned i = 0; i < expected_retransmissions_count; i++) {\n        avs_time_duration_t delay;\n        AVS_UNIT_ASSERT_SUCCESS(anjay_sched_time_to_next(anjay, &delay));\n        _anjay_mock_clock_advance(delay);\n        DM_TEST_REQUEST_FROM_CLIENT(\n                mocksock, CON, POST, ID_TOKEN_RAW(msg_id, token), PATH(\"dp\"),\n                CONTENT_FORMAT(SENML_CBOR),\n                PAYLOAD_EXTERNAL(expected_payload.payload,\n                                 expected_payload.payload_size));\n        anjay_sched_run(anjay);\n    }\n}\n\nstatic void test_expect_lwm2m_send_retransmissions_timeout(anjay_t *anjay) {\n    size_t old_queue_size = AVS_LIST_SIZE(HANDLER_WRAPPER_ARGS);\n    avs_time_duration_t delay;\n    AVS_UNIT_ASSERT_SUCCESS(anjay_sched_time_to_next(anjay, &delay));\n    _anjay_mock_clock_advance(delay);\n    anjay_sched_run(anjay);\n    AVS_UNIT_ASSERT_EQUAL(AVS_LIST_SIZE(HANDLER_WRAPPER_ARGS),\n                          old_queue_size - 1);\n}\n\nAVS_UNIT_TEST(anjay_send, implicit_abort) {\n    DM_TEST_INIT;\n\n    anjay_send_batch_t *batch = get_new_batch_with_int_value(URI_PATH, VALUE);\n    test_expect_scheduled_lwm2m_send_request(\n            mocksocks[0], MSG_ID, nth_token(0),\n            get_expected_payload_for_batch_with_int_value(URI_PATH, VALUE,\n                                                          NAN));\n    test_call_anjay_send(anjay, SSID, batch, NULL, NULL);\n    anjay_send_batch_release(&batch);\n    test_expect_scheduled_lwm2m_send_retransmissions(\n            anjay, mocksocks[0], MSG_ID, nth_token(0),\n            get_expected_payload_for_batch_with_int_value(URI_PATH, VALUE, NAN),\n            AVS_COAP_DEFAULT_UDP_TX_PARAMS.max_retransmit);\n    test_expect_lwm2m_send_retransmissions_timeout(anjay);\n\n    DM_TEST_FINISH;\n}\n\nstatic void send_timeout_finished_handler(anjay_t *anjay,\n                                          anjay_ssid_t ssid,\n                                          const anjay_send_batch_t *batch,\n                                          int result,\n                                          void *data) {\n    (void) anjay;\n    AVS_UNIT_ASSERT_EQUAL(ssid, SSID);\n    AVS_UNIT_ASSERT_NOT_NULL(batch);\n    AVS_UNIT_ASSERT_EQUAL(result, ANJAY_SEND_TIMEOUT);\n    AVS_UNIT_ASSERT_NULL(data);\n}\n\nAVS_UNIT_TEST(anjay_send, explicit_abort) {\n    DM_TEST_INIT;\n\n    anjay_send_batch_t *batch = get_new_batch_with_int_value(URI_PATH, VALUE);\n    test_expect_scheduled_lwm2m_send_request(\n            mocksocks[0], MSG_ID, nth_token(0),\n            get_expected_payload_for_batch_with_int_value(URI_PATH, VALUE,\n                                                          NAN));\n    test_call_anjay_send(anjay, SSID, batch, send_timeout_finished_handler,\n                         NULL);\n    anjay_send_batch_release(&batch);\n    test_expect_scheduled_lwm2m_send_retransmissions(\n            anjay, mocksocks[0], MSG_ID, nth_token(0),\n            get_expected_payload_for_batch_with_int_value(URI_PATH, VALUE, NAN),\n            AVS_COAP_DEFAULT_UDP_TX_PARAMS.max_retransmit);\n    test_expect_lwm2m_send_retransmissions_timeout(anjay);\n\n    DM_TEST_FINISH;\n}\n\nstatic void\nsend_continue_twice_finished_handler(anjay_t *anjay,\n                                     anjay_ssid_t ssid,\n                                     const anjay_send_batch_t *batch,\n                                     int result,\n                                     void *data) {\n    unsigned *failure_counter = (unsigned *) data;\n    AVS_UNIT_ASSERT_EQUAL(result, ANJAY_SEND_TIMEOUT);\n    if (++(*failure_counter) <= 2) {\n        test_call_anjay_send(anjay, ssid, batch,\n                             send_continue_twice_finished_handler, data);\n    }\n}\n\nAVS_UNIT_TEST(anjay_send, continue) {\n    DM_TEST_INIT;\n\n    const unsigned messages_count_per_attempt =\n            1 + AVS_COAP_DEFAULT_UDP_TX_PARAMS.max_retransmit;\n    unsigned failure_counter = 0;\n\n    anjay_send_batch_t *batch = get_new_batch_with_int_value(URI_PATH, VALUE);\n    expected_payload_t expected_payload =\n            get_expected_payload_for_batch_with_int_value(URI_PATH, VALUE, NAN);\n    test_expect_scheduled_lwm2m_send_request(mocksocks[0], MSG_ID, nth_token(0),\n                                             expected_payload);\n    test_call_anjay_send(anjay, SSID, batch,\n                         send_continue_twice_finished_handler,\n                         &failure_counter);\n    anjay_send_batch_release(&batch);\n\n    test_expect_scheduled_lwm2m_send_retransmissions(\n            anjay, mocksocks[0], MSG_ID, nth_token(0), expected_payload,\n            messages_count_per_attempt - 1);\n    test_expect_scheduled_lwm2m_send_retransmissions(\n            anjay, mocksocks[0], (uint16_t) (MSG_ID + 1), nth_token(1),\n            expected_payload, messages_count_per_attempt);\n    test_expect_scheduled_lwm2m_send_retransmissions(\n            anjay, mocksocks[0], (uint16_t) (MSG_ID + 2), nth_token(2),\n            expected_payload, messages_count_per_attempt);\n    test_expect_lwm2m_send_retransmissions_timeout(anjay);\n\n    AVS_UNIT_ASSERT_EQUAL(failure_counter, 3);\n\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(anjay_send, resource_from_dm) {\n    DM_TEST_INIT;\n\n    const double absolute_time = SENML_TIME_SECONDS_THRESHOLD + 12345.6789e-9;\n    _anjay_mock_clock_reset(\n            avs_time_monotonic_from_fscalar(absolute_time, AVS_TIME_S));\n\n    anjay_send_batch_t *batch =\n            get_new_batch_with_int_value_from_dm(anjay, &OBJ, URI_PATH, VALUE);\n    test_expect_scheduled_lwm2m_send_request(\n            mocksocks[0], MSG_ID, nth_token(0),\n            get_expected_payload_for_batch_with_int_value(URI_PATH, VALUE,\n                                                          absolute_time));\n    test_call_anjay_send(anjay, SSID, batch,\n                         send_finished_handler_result_validator, NULL);\n    anjay_send_batch_release(&batch);\n    test_handle_lwm2m_send_response(anjay, mocksocks[0],\n                                    COAP_MSG(ACK, CHANGED,\n                                             ID_TOKEN_RAW(MSG_ID, nth_token(0)),\n                                             NO_PAYLOAD));\n\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(anjay_send, unreachable_server) {\n    DM_TEST_INIT_WITHOUT_SERVER;\n\n    anjay_send_batch_t *batch = get_new_batch_with_int_value(URI_PATH, VALUE);\n    assert_there_is_server_with_ssid(SSID, anjay);\n    assert_mute_send_resource_equals(false, anjay, SSID);\n    const anjay_send_result_t result =\n            anjay_send(anjay, SSID, batch, NULL, NULL);\n    anjay_send_batch_release(&batch);\n\n    AVS_UNIT_ASSERT_FAILED(result);\n    AVS_UNIT_ASSERT_EQUAL(result, ANJAY_SEND_ERR_OFFLINE);\n\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(anjay_send, offline_mode) {\n    DM_TEST_INIT;\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    anjay_unlocked->servers->registration_info.lwm2m_version =\n            ANJAY_LWM2M_VERSION_1_1;\n\n    avs_unit_mocksock_expect_shutdown(mocksocks[0]);\n    avs_net_socket_shutdown(mocksocks[0]);\n    avs_net_socket_close(mocksocks[0]);\n    // Mark UDP transport as offline - otherwise the server entry would be\n    // considered suspended for queue mode and the Send would be deferred.\n    anjay_unlocked->online_transports.udp = false;\n    ANJAY_MUTEX_UNLOCK(anjay);\n\n    anjay_send_batch_t *batch = get_new_batch_with_int_value(URI_PATH, VALUE);\n    assert_there_is_server_with_ssid(SSID, anjay);\n    assert_mute_send_resource_equals(false, anjay, SSID);\n    const anjay_send_result_t result =\n            anjay_send(anjay, SSID, batch, NULL, NULL);\n    anjay_send_batch_release(&batch);\n\n    AVS_UNIT_ASSERT_FAILED(result);\n    AVS_UNIT_ASSERT_EQUAL(result, ANJAY_SEND_ERR_OFFLINE);\n\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(anjay_send, ssid_any) {\n    DM_TEST_INIT;\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    anjay_unlocked->servers->registration_info.lwm2m_version =\n            ANJAY_LWM2M_VERSION_1_1;\n    ANJAY_MUTEX_UNLOCK(anjay);\n\n    anjay_send_batch_t *batch = get_new_batch_with_int_value(URI_PATH, VALUE);\n    const anjay_send_result_t result =\n            anjay_send(anjay, ANJAY_SSID_ANY, batch, NULL, NULL);\n    anjay_send_batch_release(&batch);\n\n    AVS_UNIT_ASSERT_FAILED(result);\n    AVS_UNIT_ASSERT_EQUAL(result, ANJAY_SEND_ERR_SSID);\n\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(anjay_send, ssid_bootstrap) {\n    DM_TEST_INIT;\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    anjay_unlocked->servers->registration_info.lwm2m_version =\n            ANJAY_LWM2M_VERSION_1_1;\n    ANJAY_MUTEX_UNLOCK(anjay);\n\n    anjay_send_batch_t *batch = get_new_batch_with_int_value(URI_PATH, VALUE);\n    const anjay_send_result_t result =\n            anjay_send(anjay, ANJAY_SSID_BOOTSTRAP, batch, NULL, NULL);\n    anjay_send_batch_release(&batch);\n\n    AVS_UNIT_ASSERT_FAILED(result);\n    AVS_UNIT_ASSERT_EQUAL(result, ANJAY_SEND_ERR_SSID);\n\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(anjay_send, not_existing_ssid) {\n    DM_TEST_INIT;\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    anjay_unlocked->servers->registration_info.lwm2m_version =\n            ANJAY_LWM2M_VERSION_1_1;\n    ANJAY_MUTEX_UNLOCK(anjay);\n\n    anjay_send_batch_t *batch = get_new_batch_with_int_value(URI_PATH, VALUE);\n    assert_there_is_not_any_server(anjay);\n    const anjay_send_result_t result =\n            anjay_send(anjay, 1234, batch, NULL, NULL);\n    anjay_send_batch_release(&batch);\n\n    AVS_UNIT_ASSERT_FAILED(result);\n    AVS_UNIT_ASSERT_EQUAL(result, ANJAY_SEND_ERR_SSID);\n\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(anjay_send, muted) {\n    DM_TEST_INIT;\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    anjay_unlocked->servers->registration_info.lwm2m_version =\n            ANJAY_LWM2M_VERSION_1_1;\n    ANJAY_MUTEX_UNLOCK(anjay);\n\n    anjay_send_batch_t *batch = get_new_batch_with_int_value(URI_PATH, VALUE);\n    assert_there_is_server_with_ssid(SSID, anjay);\n    assert_mute_send_resource_equals(true, anjay, SSID);\n    const anjay_send_result_t result =\n            anjay_send(anjay, SSID, batch, NULL, NULL);\n    anjay_send_batch_release(&batch);\n\n    AVS_UNIT_ASSERT_FAILED(result);\n    AVS_UNIT_ASSERT_EQUAL(result, ANJAY_SEND_ERR_MUTED);\n\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(anjay_send, assert_mute_send_resource_read_failure) {\n    DM_TEST_INIT;\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    anjay_unlocked->servers->registration_info.lwm2m_version =\n            ANJAY_LWM2M_VERSION_1_1;\n    ANJAY_MUTEX_UNLOCK(anjay);\n\n    anjay_send_batch_t *batch = get_new_batch_with_int_value(URI_PATH, VALUE);\n    assert_there_is_server_with_ssid(SSID, anjay);\n    assert_mute_send_resource_read_failure(anjay, SSID);\n    const anjay_send_result_t result =\n            anjay_send(anjay, SSID, batch, NULL, NULL);\n    anjay_send_batch_release(&batch);\n\n    AVS_UNIT_ASSERT_FAILED(result);\n    AVS_UNIT_ASSERT_EQUAL(result, ANJAY_SEND_ERR_MUTED);\n\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(anjay_send, add_multiple_successful) {\n    DM_TEST_INIT;\n    anjay_send_batch_builder_t *builder = anjay_send_batch_builder_new();\n    AVS_UNIT_ASSERT_NOT_NULL(builder);\n\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0, (const anjay_iid_t[]) { 1, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 1, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 1, ANJAY_DM_RES_R, ANJAY_DM_RES_PRESENT },\n                    { 2, ANJAY_DM_RES_R, ANJAY_DM_RES_PRESENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read(anjay, &OBJ, 1, 1, ANJAY_ID_INVALID, 0,\n                                        ANJAY_MOCK_DM_INT(0, 23));\n\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0, (const anjay_iid_t[]) { 1, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 1, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 1, ANJAY_DM_RES_R, ANJAY_DM_RES_PRESENT },\n                    { 2, ANJAY_DM_RES_R, ANJAY_DM_RES_PRESENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read(anjay, &OBJ, 1, 2, ANJAY_ID_INVALID, 0,\n                                        ANJAY_MOCK_DM_INT(0, 45));\n\n    anjay_send_resource_path_t paths[] = { { 42, 1, 1 }, { 42, 1, 2 } };\n\n    AVS_UNIT_ASSERT_SUCCESS(anjay_send_batch_data_add_current_multiple(\n            builder, anjay, paths, AVS_ARRAY_SIZE(paths)));\n    AVS_UNIT_ASSERT_EQUAL(\n            AVS_LIST_SIZE(((anjay_batch_builder_t *) builder)->list), 2);\n    anjay_send_batch_builder_cleanup(&builder);\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(anjay_send, add_multiple_single_resource_fail) {\n    DM_TEST_INIT;\n    anjay_send_batch_builder_t *builder = anjay_send_batch_builder_new();\n    AVS_UNIT_ASSERT_NOT_NULL(builder);\n\n    AVS_LIST(anjay_batch_entry_t) *initial_append_ptr =\n            ((anjay_batch_builder_t *) builder)->append_ptr;\n\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0, (const anjay_iid_t[]) { 1, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 1, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 1, ANJAY_DM_RES_R, ANJAY_DM_RES_PRESENT },\n                    { 2, ANJAY_DM_RES_R, ANJAY_DM_RES_PRESENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read(anjay, &OBJ, 1, 1, ANJAY_ID_INVALID, -1,\n                                        ANJAY_MOCK_DM_INT(0, 23));\n\n    anjay_send_resource_path_t paths[] = { { 42, 1, 1 } };\n\n    AVS_UNIT_ASSERT_FAILED(anjay_send_batch_data_add_current_multiple(\n            builder, anjay, paths, AVS_ARRAY_SIZE(paths)));\n    AVS_UNIT_ASSERT_EQUAL(\n            AVS_LIST_SIZE(((anjay_batch_builder_t *) builder)->list), 0);\n    AVS_UNIT_ASSERT_TRUE(initial_append_ptr\n                         == ((anjay_batch_builder_t *) builder)->append_ptr);\n    AVS_UNIT_ASSERT_NULL(*((anjay_batch_builder_t *) builder)->append_ptr);\n    anjay_send_batch_builder_cleanup(&builder);\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(anjay_send, add_multiple_twice_with_fail) {\n    DM_TEST_INIT;\n    anjay_send_batch_builder_t *builder = anjay_send_batch_builder_new();\n    AVS_UNIT_ASSERT_NOT_NULL(builder);\n\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0, (const anjay_iid_t[]) { 1, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 1, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 1, ANJAY_DM_RES_R, ANJAY_DM_RES_PRESENT },\n                    { 2, ANJAY_DM_RES_R, ANJAY_DM_RES_PRESENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read(anjay, &OBJ, 1, 1, ANJAY_ID_INVALID, 0,\n                                        ANJAY_MOCK_DM_INT(0, 23));\n\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0, (const anjay_iid_t[]) { 1, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 1, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 1, ANJAY_DM_RES_R, ANJAY_DM_RES_PRESENT },\n                    { 2, ANJAY_DM_RES_R, ANJAY_DM_RES_PRESENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read(anjay, &OBJ, 1, 2, ANJAY_ID_INVALID, 0,\n                                        ANJAY_MOCK_DM_INT(0, 45));\n\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0, (const anjay_iid_t[]) { 1, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 1, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 1, ANJAY_DM_RES_R, ANJAY_DM_RES_PRESENT },\n                    { 2, ANJAY_DM_RES_R, ANJAY_DM_RES_PRESENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read(anjay, &OBJ, 1, 1, ANJAY_ID_INVALID, 0,\n                                        ANJAY_MOCK_DM_INT(0, 23));\n\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0, (const anjay_iid_t[]) { 1, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 1, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 1, ANJAY_DM_RES_R, ANJAY_DM_RES_PRESENT },\n                    { 2, ANJAY_DM_RES_R, ANJAY_DM_RES_PRESENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read(anjay, &OBJ, 1, 2, ANJAY_ID_INVALID, -1,\n                                        ANJAY_MOCK_DM_INT(0, 45));\n\n    anjay_send_resource_path_t paths[] = { { 42, 1, 1 }, { 42, 1, 2 } };\n\n    AVS_UNIT_ASSERT_SUCCESS(anjay_send_batch_data_add_current_multiple(\n            builder, anjay, paths, AVS_ARRAY_SIZE(paths)));\n\n    AVS_LIST(anjay_batch_entry_t) *pre_fail_append_ptr =\n            ((anjay_batch_builder_t *) builder)->append_ptr;\n\n    AVS_UNIT_ASSERT_FAILED(anjay_send_batch_data_add_current_multiple(\n            builder, anjay, paths, AVS_ARRAY_SIZE(paths)));\n    AVS_UNIT_ASSERT_EQUAL(\n            AVS_LIST_SIZE(((anjay_batch_builder_t *) builder)->list), 2);\n    AVS_UNIT_ASSERT_TRUE(pre_fail_append_ptr\n                         == ((anjay_batch_builder_t *) builder)->append_ptr);\n    AVS_UNIT_ASSERT_NULL(*((anjay_batch_builder_t *) builder)->append_ptr);\n    anjay_send_batch_builder_cleanup(&builder);\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(anjay_send, add_multiple_ignore_not_found) {\n    DM_TEST_INIT;\n    anjay_send_batch_builder_t *builder = anjay_send_batch_builder_new();\n    AVS_UNIT_ASSERT_NOT_NULL(builder);\n\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0, (const anjay_iid_t[]) { 1, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 1, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 1, ANJAY_DM_RES_R, ANJAY_DM_RES_PRESENT },\n                    { 2, ANJAY_DM_RES_R, ANJAY_DM_RES_PRESENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read(anjay, &OBJ, 1, 1, ANJAY_ID_INVALID, 0,\n                                        ANJAY_MOCK_DM_INT(0, 23));\n\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0, (const anjay_iid_t[]) { 1, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 1, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 1, ANJAY_DM_RES_R, ANJAY_DM_RES_PRESENT },\n                    { 2, ANJAY_DM_RES_R, ANJAY_DM_RES_PRESENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read(anjay, &OBJ, 1, 2, ANJAY_ID_INVALID,\n                                        ANJAY_ERR_NOT_FOUND,\n                                        ANJAY_MOCK_DM_INT(0, 45));\n\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0, (const anjay_iid_t[]) { 1, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 1, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 1, ANJAY_DM_RES_R, ANJAY_DM_RES_PRESENT },\n                    { 2, ANJAY_DM_RES_R, ANJAY_DM_RES_PRESENT },\n                    ANJAY_MOCK_DM_RES_END });\n\n    anjay_send_resource_path_t paths[] = { { 42, 1, 1 },\n                                           { 42, 1, 2 },\n                                           { 42, 1, 3 } };\n\n    AVS_UNIT_ASSERT_SUCCESS(\n            anjay_send_batch_data_add_current_multiple_ignore_not_found(\n                    builder, anjay, paths, AVS_ARRAY_SIZE(paths)));\n    AVS_UNIT_ASSERT_EQUAL(\n            AVS_LIST_SIZE(((anjay_batch_builder_t *) builder)->list), 1);\n\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0, (const anjay_iid_t[]) { 1, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 1, 0,\n            (const anjay_mock_dm_res_entry_t[]) { { 1, ANJAY_DM_RES_R,\n                                                    ANJAY_DM_RES_PRESENT },\n                                                  ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read(anjay, &OBJ, 1, 1, ANJAY_ID_INVALID, -1,\n                                        ANJAY_MOCK_DM_INT(0, 45));\n\n    AVS_UNIT_ASSERT_FAILED(\n            anjay_send_batch_data_add_current_multiple_ignore_not_found(\n                    builder, anjay, paths, 1));\n    AVS_UNIT_ASSERT_EQUAL(\n            AVS_LIST_SIZE(((anjay_batch_builder_t *) builder)->list), 1);\n\n    // This should not be ignored.\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0, (const anjay_iid_t[]) { 1, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 1, 0,\n            (const anjay_mock_dm_res_entry_t[]) { { 1, ANJAY_DM_RES_R,\n                                                    ANJAY_DM_RES_PRESENT },\n                                                  ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read(anjay, &OBJ, 1, 1, ANJAY_ID_INVALID, -1,\n                                        ANJAY_MOCK_DM_INT(0, 45));\n    AVS_UNIT_ASSERT_FAILED(\n            anjay_send_batch_data_add_current_multiple_ignore_not_found(\n                    builder, anjay, paths, 1));\n    AVS_UNIT_ASSERT_EQUAL(\n            AVS_LIST_SIZE(((anjay_batch_builder_t *) builder)->list), 1);\n\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0, (const anjay_iid_t[]) { 1, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 1, 0,\n            (const anjay_mock_dm_res_entry_t[]) { { 1, ANJAY_DM_RES_R,\n                                                    ANJAY_DM_RES_PRESENT },\n                                                  ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read(anjay, &OBJ, 1, 1, ANJAY_ID_INVALID, 0,\n                                        ANJAY_MOCK_DM_INT(0, 45));\n    AVS_UNIT_ASSERT_SUCCESS(\n            anjay_send_batch_data_add_current(builder, anjay, 42, 1, 1));\n    AVS_UNIT_ASSERT_EQUAL(\n            AVS_LIST_SIZE(((anjay_batch_builder_t *) builder)->list), 2);\n\n    anjay_send_batch_builder_cleanup(&builder);\n    DM_TEST_FINISH;\n}\n\n#ifdef ANJAY_WITH_LWM2M_GATEWAY\n\n// clang-format off\n// array(1)\n// map(3)\n// negative(1) \"bn\"\n// text(11)\n// \"/dev0/4/4/1\"\n// timestamp\n#define GATEWAY_MSG_PAYLOAD_BEGIN                               \\\n        \"\\x81\"                                                  \\\n           \"\\xA3\"                                               \\\n              \"\\x21\"                                            \\\n              \"\\x6B\"                                            \\\n                 \"\\x2F\"GATEWAY_PREFIX\"\\x2F\\x34\\x2F\\x34\\x2F\\x31\" \\\n              \"\\x22\\xFA\\xC4\\x7A\\x00\\x00\"\n// clang-format on\n#    define GATEWAY_IID 0\n#    define GATEWAY_PREFIX \"dev0\"\n\nstatic const anjay_uri_path_t GATEWAY_PATH = RESOURCE_PATH_INITIALIZER(4, 4, 1);\n\n#    define PROCESS_SEND_OP_GATEWAY()                                      \\\n        anjay_send_batch_t *batch =                                        \\\n                anjay_send_batch_builder_compile(&builder);                \\\n        expected_payload_t expected_payload;                               \\\n        expected_payload.payload_size = sizeof(EXPECTED_DATA) - 1;         \\\n        memcpy(expected_payload.payload, EXPECTED_DATA,                    \\\n               sizeof(EXPECTED_DATA));                                     \\\n        test_expect_scheduled_lwm2m_send_request(                          \\\n                mocksocks[0], MSG_ID, nth_token(0), expected_payload);     \\\n        test_call_anjay_send(anjay, SSID, batch,                           \\\n                             send_finished_handler_result_validator,       \\\n                             (void *) (intptr_t) ANJAY_SEND_SUCCESS);      \\\n        anjay_send_batch_release(&batch);                                  \\\n        test_handle_lwm2m_send_response(                                   \\\n                anjay, mocksocks[0],                                       \\\n                COAP_MSG(ACK, CHANGED, ID_TOKEN_RAW(MSG_ID, nth_token(0)), \\\n                         NO_PAYLOAD));\n\nAVS_UNIT_TEST(anjay_send, gateway_add_int) {\n    DM_TEST_INIT;\n\n    anjay_send_batch_builder_t *builder = anjay_send_batch_builder_new();\n    avs_time_real_t timestamp = { 0 };\n    AVS_UNIT_ASSERT_SUCCESS(anjay_lwm2m_gateway_send_batch_add_int(\n            builder, GATEWAY_IID, GATEWAY_PATH.ids[ANJAY_ID_OID],\n            GATEWAY_PATH.ids[ANJAY_ID_IID], GATEWAY_PATH.ids[ANJAY_ID_RID],\n            UINT16_MAX, timestamp, 42));\n\n    static const char EXPECTED_DATA[] = {\n        GATEWAY_MSG_PAYLOAD_BEGIN \"\\x02\"     // unsigned(2) \"v\"\n                                  \"\\x18\\x2A\" // unsigned(42)\n    };\n    PROCESS_SEND_OP_GATEWAY();\n\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(anjay_send, gateway_add_uint) {\n    DM_TEST_INIT;\n\n    anjay_send_batch_builder_t *builder = anjay_send_batch_builder_new();\n    avs_time_real_t timestamp = { 0 };\n    AVS_UNIT_ASSERT_SUCCESS(anjay_lwm2m_gateway_send_batch_add_uint(\n            builder, GATEWAY_IID, GATEWAY_PATH.ids[ANJAY_ID_OID],\n            GATEWAY_PATH.ids[ANJAY_ID_IID], GATEWAY_PATH.ids[ANJAY_ID_RID],\n            UINT16_MAX, timestamp, 42));\n\n    static const char EXPECTED_DATA[] = {\n        GATEWAY_MSG_PAYLOAD_BEGIN \"\\x02\"     // unsigned(2) \"v\"\n                                  \"\\x18\\x2A\" // unsigned(42)\n    };\n    PROCESS_SEND_OP_GATEWAY();\n\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(anjay_send, gateway_add_bool) {\n    DM_TEST_INIT;\n\n    anjay_send_batch_builder_t *builder = anjay_send_batch_builder_new();\n    avs_time_real_t timestamp = { 0 };\n    AVS_UNIT_ASSERT_SUCCESS(anjay_lwm2m_gateway_send_batch_add_bool(\n            builder, GATEWAY_IID, GATEWAY_PATH.ids[ANJAY_ID_OID],\n            GATEWAY_PATH.ids[ANJAY_ID_IID], GATEWAY_PATH.ids[ANJAY_ID_RID],\n            UINT16_MAX, timestamp, false));\n\n    static const char EXPECTED_DATA[] = {\n        GATEWAY_MSG_PAYLOAD_BEGIN \"\\x04\" // unsigned(4) \"vb\"\n                                  \"\\xF4\" // primitive(20)\n    };\n    PROCESS_SEND_OP_GATEWAY();\n\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(anjay_send, gateway_add_string) {\n    DM_TEST_INIT;\n\n    anjay_send_batch_builder_t *builder = anjay_send_batch_builder_new();\n    avs_time_real_t timestamp = { 0 };\n    AVS_UNIT_ASSERT_SUCCESS(anjay_lwm2m_gateway_send_batch_add_string(\n            builder, GATEWAY_IID, GATEWAY_PATH.ids[ANJAY_ID_OID],\n            GATEWAY_PATH.ids[ANJAY_ID_IID], GATEWAY_PATH.ids[ANJAY_ID_RID],\n            UINT16_MAX, timestamp, \"dd\"));\n\n    static const char EXPECTED_DATA[] = { GATEWAY_MSG_PAYLOAD_BEGIN\n                                          \"\\x03\" // unsigned(3) \"vs\"\n                                          \"\\x62\"\n                                          \"dd\" };\n    PROCESS_SEND_OP_GATEWAY();\n\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(anjay_send, gateway_add_bytes) {\n    DM_TEST_INIT;\n\n    anjay_send_batch_builder_t *builder = anjay_send_batch_builder_new();\n    avs_time_real_t timestamp = { 0 };\n    AVS_UNIT_ASSERT_SUCCESS(anjay_lwm2m_gateway_send_batch_add_bytes(\n            builder, GATEWAY_IID, GATEWAY_PATH.ids[ANJAY_ID_OID],\n            GATEWAY_PATH.ids[ANJAY_ID_IID], GATEWAY_PATH.ids[ANJAY_ID_RID],\n            UINT16_MAX, timestamp, \"ddd\", 3));\n\n    static const char EXPECTED_DATA[] = { GATEWAY_MSG_PAYLOAD_BEGIN\n                                          \"\\x08\" // unsigned(8) \"vb\"\n                                          \"\\x43\"\n                                          \"ddd\" };\n    PROCESS_SEND_OP_GATEWAY();\n\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(anjay_send, gateway_add_double) {\n    DM_TEST_INIT;\n\n    anjay_send_batch_builder_t *builder = anjay_send_batch_builder_new();\n    avs_time_real_t timestamp = { 0 };\n    AVS_UNIT_ASSERT_SUCCESS(anjay_lwm2m_gateway_send_batch_add_double(\n            builder, GATEWAY_IID, GATEWAY_PATH.ids[ANJAY_ID_OID],\n            GATEWAY_PATH.ids[ANJAY_ID_IID], GATEWAY_PATH.ids[ANJAY_ID_RID],\n            UINT16_MAX, timestamp, 1.1));\n\n    static const char EXPECTED_DATA[] = {\n        GATEWAY_MSG_PAYLOAD_BEGIN \"\\x02\\xFB\\x3F\\xF1\\x99\\x99\\x99\\x99\\x99\\x9A\"\n    };\n    PROCESS_SEND_OP_GATEWAY();\n\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(anjay_send, gateway_add_objlink) {\n    DM_TEST_INIT;\n\n    anjay_send_batch_builder_t *builder = anjay_send_batch_builder_new();\n    avs_time_real_t timestamp = { 0 };\n    AVS_UNIT_ASSERT_SUCCESS(anjay_lwm2m_gateway_send_batch_add_objlnk(\n            builder, GATEWAY_IID, GATEWAY_PATH.ids[ANJAY_ID_OID],\n            GATEWAY_PATH.ids[ANJAY_ID_IID], GATEWAY_PATH.ids[ANJAY_ID_RID],\n            UINT16_MAX, timestamp, 1, 1));\n\n    static const char EXPECTED_DATA[] = { GATEWAY_MSG_PAYLOAD_BEGIN\n                                          \"\\x63\"\n                                          \"vlo\"\n                                          \"\\x63\\x31\\x3A\\x31\" };\n    PROCESS_SEND_OP_GATEWAY();\n\n    DM_TEST_FINISH;\n}\n\n#endif // ANJAY_WITH_LWM2M_GATEWAY\n"
  },
  {
    "path": "tests/core/observe/observe.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#include <math.h>\n#include <stdarg.h>\n\n#include <avsystem/commons/avs_unit_test.h>\n\n#include \"src/core/anjay_core.h\"\n#include \"src/core/servers/anjay_server_connections.h\"\n#include \"src/core/servers/anjay_servers_internal.h\"\n#include \"tests/core/coap/utils.h\"\n#include \"tests/utils/dm.h\"\n#include \"tests/utils/mock_clock.h\"\n#include \"tests/utils/utils.h\"\n\n#define MSG_ID_BASE 0x0000\n\nstatic void assert_observe_consistency(anjay_t *anjay_locked) {\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    AVS_LIST(anjay_observe_connection_entry_t) conn;\n    AVS_LIST_FOREACH(conn, anjay->observe.connection_entries) {\n        size_t path_refs_in_observations = 0;\n        AVS_SORTED_SET_ELEM(anjay_observation_t) observation;\n        AVS_SORTED_SET_FOREACH(observation, conn->observations) {\n            path_refs_in_observations += observation->paths_count;\n        }\n\n        size_t path_refs = 0;\n        AVS_SORTED_SET_ELEM(anjay_observe_path_entry_t) path_entry;\n        AVS_SORTED_SET_FOREACH(path_entry, conn->observed_paths) {\n            AVS_LIST(AVS_SORTED_SET_ELEM(anjay_observation_t)) ref;\n            AVS_LIST_FOREACH(ref, path_entry->refs) {\n                ++path_refs;\n                AVS_UNIT_ASSERT_NOT_NULL(ref);\n                AVS_UNIT_ASSERT_NOT_NULL(*ref);\n                AVS_UNIT_ASSERT_TRUE(\n                        AVS_SORTED_SET_FIND(conn->observations, *ref) == *ref);\n                bool path_found = false;\n                for (size_t i = 0; i < (*ref)->paths_count; ++i) {\n                    if (_anjay_uri_path_equal(&(*ref)->paths[i],\n                                              &path_entry->path)) {\n                        path_found = true;\n                        break;\n                    }\n                }\n                AVS_UNIT_ASSERT_TRUE(path_found);\n            }\n        }\n        AVS_UNIT_ASSERT_EQUAL(path_refs_in_observations, path_refs);\n    }\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n}\n\nstatic void assert_observe_size(anjay_t *anjay_locked, size_t sz) {\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    size_t result = 0;\n    AVS_LIST(anjay_observe_connection_entry_t) conn;\n    AVS_LIST_FOREACH(conn, anjay->observe.connection_entries) {\n        size_t local_size = AVS_SORTED_SET_SIZE(conn->observations);\n        AVS_UNIT_ASSERT_NOT_EQUAL(local_size, 0);\n        result += local_size;\n    }\n    AVS_UNIT_ASSERT_EQUAL(result, sz);\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n}\n\nstatic void assert_msg_details_equal(const anjay_msg_details_t *a,\n                                     const anjay_msg_details_t *b) {\n    AVS_UNIT_ASSERT_EQUAL(a->msg_code, b->msg_code);\n    AVS_UNIT_ASSERT_EQUAL(a->format, b->format);\n    /* Yes, a pointer comparison */\n    AVS_UNIT_ASSERT_TRUE(a->uri_path == b->uri_path);\n    AVS_UNIT_ASSERT_TRUE(a->uri_query == b->uri_query);\n    AVS_UNIT_ASSERT_TRUE(a->location_path == b->location_path);\n}\n\nstatic void assert_observe(anjay_t *anjay_locked,\n                           anjay_ssid_t ssid,\n                           const avs_coap_token_t *token,\n                           const anjay_uri_path_t *uri,\n                           const anjay_msg_details_t *details,\n                           const void *data,\n                           size_t length) {\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    AVS_LIST(anjay_observe_connection_entry_t) *conn_ptr =\n            _anjay_observe_find_connection_state((anjay_connection_ref_t) {\n                .server = *_anjay_servers_find_ptr(&anjay->servers, ssid),\n                .conn_type = ANJAY_CONNECTION_PRIMARY\n            });\n    AVS_UNIT_ASSERT_NOT_NULL(conn_ptr);\n    AVS_SORTED_SET_ELEM(anjay_observation_t) observation =\n            AVS_SORTED_SET_FIND((*conn_ptr)->observations,\n                                _anjay_observation_query(token));\n    AVS_UNIT_ASSERT_NOT_NULL(observation);\n    AVS_UNIT_ASSERT_EQUAL(observation->paths_count, 1);\n    AVS_UNIT_ASSERT_TRUE(_anjay_uri_path_equal(&observation->paths[0], uri));\n    AVS_UNIT_ASSERT_NULL(observation->last_unsent);\n    AVS_UNIT_ASSERT_NOT_NULL(observation->last_sent);\n    assert_msg_details_equal(&observation->last_sent->details, details);\n\n    char buf[length];\n    avs_stream_outbuf_t out_buf_stream = AVS_STREAM_OUTBUF_STATIC_INITIALIZER;\n    avs_stream_outbuf_set_buffer(&out_buf_stream, buf, length);\n\n    anjay_unlocked_output_ctx_t *out_ctx = NULL;\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_output_dynamic_construct(\n            &out_ctx, (avs_stream_t *) &out_buf_stream, uri, details->format,\n            NULL, ANJAY_ACTION_READ));\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_batch_data_output(anjay, observation->last_sent->values[0],\n                                     ANJAY_SSID_BOOTSTRAP, out_ctx));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_output_ctx_destroy(&out_ctx));\n    AVS_UNIT_ASSERT_EQUAL(avs_stream_outbuf_offset(&out_buf_stream), length);\n    AVS_UNIT_ASSERT_EQUAL_BYTES_SIZED(buf, data, length);\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n}\n\nstatic void expect_server_res_read(anjay_t *anjay,\n                                   const anjay_dm_object_def_t *const *obj,\n                                   anjay_ssid_t ssid,\n                                   anjay_rid_t rid,\n                                   const anjay_mock_dm_data_t *data) {\n    assert((*obj)->oid == ANJAY_DM_OID_SERVER);\n    _anjay_mock_dm_expect_list_instances(\n            anjay, obj, 0, (const anjay_iid_t[]) { ssid, ANJAY_ID_INVALID });\n    assert(rid > ANJAY_DM_RID_SERVER_SSID);\n    anjay_mock_dm_res_entry_t resources[] = {\n        { ANJAY_DM_RID_SERVER_SSID, ANJAY_DM_RES_R, ANJAY_DM_RES_PRESENT },\n        { ANJAY_DM_RID_SERVER_LIFETIME, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n        { ANJAY_DM_RID_SERVER_DEFAULT_PMIN, ANJAY_DM_RES_RW,\n          ANJAY_DM_RES_ABSENT },\n        { ANJAY_DM_RID_SERVER_DEFAULT_PMAX, ANJAY_DM_RES_RW,\n          ANJAY_DM_RES_ABSENT },\n        { ANJAY_DM_RID_SERVER_NOTIFICATION_STORING, ANJAY_DM_RES_RW,\n          ANJAY_DM_RES_ABSENT },\n        { ANJAY_DM_RID_SERVER_BINDING, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n        ANJAY_MOCK_DM_RES_END\n    };\n    size_t i;\n    for (i = 0; i < AVS_ARRAY_SIZE(resources); ++i) {\n        if (resources[i].rid == rid) {\n            resources[i].presence = ANJAY_DM_RES_PRESENT;\n            break;\n        }\n    }\n    AVS_UNIT_ASSERT_TRUE(i < AVS_ARRAY_SIZE(resources));\n    _anjay_mock_dm_expect_list_resources(anjay, obj, ssid, 0, resources);\n    _anjay_mock_dm_expect_resource_read(anjay, obj, ssid,\n                                        ANJAY_DM_RID_SERVER_SSID,\n                                        ANJAY_ID_INVALID, 0,\n                                        ANJAY_MOCK_DM_INT(0, ssid));\n    _anjay_mock_dm_expect_list_resources(anjay, obj, ssid, 0, resources);\n    _anjay_mock_dm_expect_resource_read(anjay, obj, ssid, rid, ANJAY_ID_INVALID,\n                                        0, data);\n}\n\nstatic void expect_read_notif_storing(anjay_t *anjay,\n                                      const anjay_dm_object_def_t *const *obj,\n                                      anjay_ssid_t ssid,\n                                      bool value) {\n    expect_server_res_read(anjay, obj, ssid,\n                           ANJAY_DM_RID_SERVER_NOTIFICATION_STORING,\n                           ANJAY_MOCK_DM_BOOL(0, value));\n}\n\n#define ASSERT_SUCCESS_TEST_RESULT(Ssid)                    \\\n    assert_observe(anjay, Ssid,                             \\\n                   &(const avs_coap_token_t) {              \\\n                       .size = 8,                           \\\n                       .bytes = \"SuccsTkn\"                  \\\n                   },                                       \\\n                   &MAKE_RESOURCE_PATH(42, 69, 4),          \\\n                   &(const anjay_msg_details_t) {           \\\n                       .msg_code = AVS_COAP_CODE_CONTENT,   \\\n                       .format = AVS_COAP_FORMAT_PLAINTEXT, \\\n                   },                                       \\\n                   \"514\", 3)\n\n#define SUCCESS_TEST(...)                                                     \\\n    DM_TEST_INIT_WITH_SSIDS(__VA_ARGS__);                                     \\\n    do {                                                                      \\\n        for (size_t i = 0; i < AVS_ARRAY_SIZE(ssids); ++i) {                  \\\n            DM_TEST_REQUEST(mocksocks[i], CON, GET,                           \\\n                            ID_TOKEN(0xFA3E, \"SuccsTkn\"), OBSERVE(0),         \\\n                            PATH(\"42\", \"69\", \"4\"));                           \\\n            _anjay_mock_dm_expect_list_instances(                             \\\n                    anjay, &OBJ, 0,                                           \\\n                    (const anjay_iid_t[]) { 69, ANJAY_ID_INVALID });          \\\n            _anjay_mock_dm_expect_list_resources(                             \\\n                    anjay, &OBJ, 69, 0,                                       \\\n                    (const anjay_mock_dm_res_entry_t[]) {                     \\\n                            { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },      \\\n                            { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },      \\\n                            { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },      \\\n                            { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },      \\\n                            { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },     \\\n                            { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },      \\\n                            { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },      \\\n                            ANJAY_MOCK_DM_RES_END });                         \\\n            _anjay_mock_dm_expect_resource_read(anjay, &OBJ, 69, 4,           \\\n                                                ANJAY_ID_INVALID, 0,          \\\n                                                ANJAY_MOCK_DM_INT(0, 514));   \\\n            DM_TEST_EXPECT_READ_NULL_ATTRS(ssids[i], 69, 4);                  \\\n            DM_TEST_EXPECT_RESPONSE(mocksocks[i], ACK, CONTENT,               \\\n                                    ID_TOKEN(0xFA3E, \"SuccsTkn\"), OBSERVE(0), \\\n                                    CONTENT_FORMAT(PLAINTEXT),                \\\n                                    PAYLOAD(\"514\"));                          \\\n            expect_has_buffered_data_check(mocksocks[i], false);              \\\n            DM_TEST_EXPECT_READ_NULL_ATTRS(ssids[i], 69, 4);                  \\\n            AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[i]));        \\\n            assert_observe_size(anjay, i + 1);                                \\\n            ASSERT_SUCCESS_TEST_RESULT(ssids[i]);                             \\\n        }                                                                     \\\n        anjay_sched_run(anjay);                                               \\\n    } while (0)\n\nAVS_UNIT_TEST(observe, read_failed) {\n    DM_TEST_INIT_WITH_SSIDS(4);\n    DM_TEST_REQUEST(mocksocks[0], CON, GET, ID_TOKEN(0xFA3E, \"Res7\"),\n                    OBSERVE(0), PATH(\"42\", \"5\", \"7\"));\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0, (const anjay_iid_t[]) { 14, 42, ANJAY_ID_INVALID });\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, NOT_FOUND,\n                            ID_TOKEN(0xFA3E, \"Res7\"), NO_PAYLOAD);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    assert_observe_consistency(anjay);\n    assert_observe_size(anjay, 0);\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(observe, simple) {\n    SUCCESS_TEST(14);\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(observe, read_attrs_failed) {\n    DM_TEST_INIT_WITH_SSIDS(4);\n    DM_TEST_REQUEST(mocksocks[0], CON, GET, ID_TOKEN(0xFA3E, \"Res4\"),\n                    OBSERVE(0), PATH(\"42\", \"69\", \"4\"));\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0,\n            (const anjay_iid_t[]) { 14, 42, 69, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 69, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                    { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read(anjay, &OBJ, 69, 4, ANJAY_ID_INVALID, 0,\n                                        ANJAY_MOCK_DM_INT(0, 514));\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0,\n            (const anjay_iid_t[]) { 14, 42, 69, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 69, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                    { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read_attrs(anjay, &OBJ, 69, 4, 4, -1, NULL);\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, CONTENT,\n                            ID_TOKEN(0xFA3E, \"Res4\"), CONTENT_FORMAT(PLAINTEXT),\n                            PAYLOAD(\"514\"));\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    assert_observe_consistency(anjay);\n    assert_observe_size(anjay, 0);\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(observe, multiple_equivalent_observations) {\n    SUCCESS_TEST(14);\n    // \"Res4\" observation is equivalent to \"SuccsTst\" created by SUCCESS_TEST()\n    DM_TEST_REQUEST(mocksocks[0], CON, GET, ID_TOKEN(0xFA3E, \"Res4\"),\n                    OBSERVE(0), PATH(\"42\", \"69\", \"4\"));\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0,\n            (const anjay_iid_t[]) { 14, 42, 69, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 69, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                    { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read(anjay, &OBJ, 69, 4, ANJAY_ID_INVALID, 0,\n                                        ANJAY_MOCK_DM_INT(0, 42));\n    DM_TEST_EXPECT_READ_NULL_ATTRS(14, 69, 4);\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, CONTENT,\n                            ID_TOKEN(0xFA3E, \"Res4\"), OBSERVE(0),\n                            CONTENT_FORMAT(PLAINTEXT), PAYLOAD(\"42\"));\n    expect_has_buffered_data_check(mocksocks[0], false);\n    DM_TEST_EXPECT_READ_NULL_ATTRS(14, 69, 4);\n    DM_TEST_EXPECT_READ_NULL_ATTRS(14, 69, 4);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    assert_observe_consistency(anjay);\n    assert_observe_size(anjay, 2);\n    ASSERT_SUCCESS_TEST_RESULT(14);\n    assert_observe(anjay, 14,\n                   &(const avs_coap_token_t) {\n                       .size = 4,\n                       .bytes = \"Res4\"\n                   },\n                   &MAKE_RESOURCE_PATH(42, 69, 4),\n                   &(const anjay_msg_details_t) {\n                       .msg_code = AVS_COAP_CODE_CONTENT,\n                       .format = AVS_COAP_FORMAT_PLAINTEXT\n                   },\n                   \"42\", 2);\n#undef TLV_RESPONSE\n    anjay_sched_run(anjay);\n    DM_TEST_EXPECT_READ_NULL_ATTRS(14, 69, 4);\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(observe, overwrite) {\n    SUCCESS_TEST(14);\n    DM_TEST_REQUEST(mocksocks[0], CON, GET, ID_TOKEN(0xFA3E, \"SuccsTkn\"),\n                    OBSERVE(0), PATH(\"42\", \"69\", \"5\"));\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0,\n            (const anjay_iid_t[]) { 14, 42, 69, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 69, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_RWM, ANJAY_DM_RES_ABSENT },\n                    { 1, ANJAY_DM_RES_RWM, ANJAY_DM_RES_ABSENT },\n                    { 2, ANJAY_DM_RES_RWM, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_RWM, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_RWM, ANJAY_DM_RES_ABSENT },\n                    { 5, ANJAY_DM_RES_RWM, ANJAY_DM_RES_PRESENT },\n                    { 6, ANJAY_DM_RES_RWM, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_list_resource_instances(\n            anjay, &OBJ, 69, 5, 0,\n            (const anjay_riid_t[]) { 4, 7, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_resource_read(anjay, &OBJ, 69, 5, 4, 0,\n                                        ANJAY_MOCK_DM_INT(0, 777));\n    _anjay_mock_dm_expect_resource_read(anjay, &OBJ, 69, 5, 7, 0,\n                                        ANJAY_MOCK_DM_STRING(0, \"Hi!\"));\n    DM_TEST_EXPECT_READ_NULL_ATTRS(14, 69, 5);\n#define TLV_RESPONSE   \\\n    \"\\x88\\x05\\x09\"     \\\n    \"\\x42\\x04\\x03\\x09\" \\\n    \"\\x43\\x07\"         \\\n    \"Hi!\"\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, CONTENT,\n                            ID_TOKEN(0xFA3E, \"SuccsTkn\"), OBSERVE(0),\n                            CONTENT_FORMAT(OMA_LWM2M_TLV),\n                            PAYLOAD(TLV_RESPONSE));\n    expect_has_buffered_data_check(mocksocks[0], false);\n    DM_TEST_EXPECT_READ_NULL_ATTRS(14, 69, 5);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    assert_observe_consistency(anjay);\n    assert_observe_size(anjay, 1);\n    assert_observe(anjay, 14,\n                   &(const avs_coap_token_t) {\n                       .size = 8,\n                       .bytes = \"SuccsTkn\"\n                   },\n                   &MAKE_RESOURCE_PATH(42, 69, 5),\n                   &(const anjay_msg_details_t) {\n                       .msg_code = AVS_COAP_CODE_CONTENT,\n                       .format = AVS_COAP_FORMAT_OMA_LWM2M_TLV\n                   },\n                   TLV_RESPONSE, sizeof(TLV_RESPONSE) - 1);\n#undef TLV_RESPONSE\n    anjay_sched_run(anjay);\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(observe, instance_overwrite) {\n    SUCCESS_TEST(14);\n    DM_TEST_REQUEST(mocksocks[0], CON, GET, ID_TOKEN(0xFA3E, \"ObjToken\"),\n                    OBSERVE(0), PATH(\"42\", \"69\"));\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0,\n            (const anjay_iid_t[]) { 14, 42, 69, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 69, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                    { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                    { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read(anjay, &OBJ, 69, 2, ANJAY_ID_INVALID, 0,\n                                        ANJAY_MOCK_DM_STRING(0, \"wow\"));\n    _anjay_mock_dm_expect_resource_read(anjay, &OBJ, 69, 4, ANJAY_ID_INVALID, 0,\n                                        ANJAY_MOCK_DM_STRING(0, \"such value\"));\n    DM_TEST_EXPECT_READ_NULL_ATTRS(14, 69, -1);\n#define TLV_RESPONSE \\\n    \"\\xc3\\x02\"       \\\n    \"wow\"            \\\n    \"\\xc8\\x04\\x0a\"   \\\n    \"such value\"\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, CONTENT,\n                            ID_TOKEN(0xFA3E, \"ObjToken\"), OBSERVE(0),\n                            CONTENT_FORMAT(OMA_LWM2M_TLV),\n                            PAYLOAD(TLV_RESPONSE));\n    expect_has_buffered_data_check(mocksocks[0], false);\n    DM_TEST_EXPECT_READ_NULL_ATTRS(14, 69, -1);\n    DM_TEST_EXPECT_READ_NULL_ATTRS(14, 69, 4);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    assert_observe_consistency(anjay);\n    assert_observe_size(anjay, 2);\n    ASSERT_SUCCESS_TEST_RESULT(14);\n    assert_observe(anjay, 14,\n                   &(const avs_coap_token_t) {\n                       .size = 8,\n                       .bytes = \"ObjToken\"\n                   },\n                   &MAKE_INSTANCE_PATH(42, 69),\n                   &(const anjay_msg_details_t) {\n                       .msg_code = AVS_COAP_CODE_CONTENT,\n                       .format = AVS_COAP_FORMAT_OMA_LWM2M_TLV\n                   },\n                   TLV_RESPONSE, sizeof(TLV_RESPONSE) - 1);\n#undef TLV_RESPONSE\n    anjay_sched_run(anjay);\n    DM_TEST_EXPECT_READ_NULL_ATTRS(14, 69, 4);\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(observe, cancel_deregister) {\n    SUCCESS_TEST(14);\n    DM_TEST_REQUEST(mocksocks[0], CON, GET, ID_TOKEN(0xFA3E, \"Res6\"),\n                    OBSERVE(0x01), PATH(\"42\", \"69\", \"6\"));\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0,\n            (const anjay_iid_t[]) { 14, 42, 69, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 69, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                    ANJAY_MOCK_DM_RES_END });\n\n    _anjay_mock_dm_expect_resource_read(anjay, &OBJ, 69, 6, ANJAY_ID_INVALID, 0,\n                                        ANJAY_MOCK_DM_STRING(0, \"Hello\"));\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, CONTENT,\n                            ID_TOKEN(0xFA3E, \"Res6\"), CONTENT_FORMAT(PLAINTEXT),\n                            PAYLOAD(\"Hello\"));\n    expect_has_buffered_data_check(mocksocks[0], false);\n    DM_TEST_EXPECT_READ_NULL_ATTRS(14, 69, 4);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    assert_observe_consistency(anjay);\n    assert_observe_size(anjay, 1);\n    DM_TEST_REQUEST(mocksocks[0], CON, GET, ID_TOKEN(0xFA3E, \"SuccsTkn\"),\n                    OBSERVE(0x01), PATH(\"42\", \"69\", \"4\"));\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0,\n            (const anjay_iid_t[]) { 14, 42, 69, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 69, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                    { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read(anjay, &OBJ, 69, 4, ANJAY_ID_INVALID, 0,\n                                        ANJAY_MOCK_DM_STRING(0, \"Good-bye\"));\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, CONTENT,\n                            ID_TOKEN(0xFA3E, \"SuccsTkn\"),\n                            CONTENT_FORMAT(PLAINTEXT), PAYLOAD(\"Good-bye\"));\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    assert_observe_consistency(anjay);\n    assert_observe_size(anjay, 0);\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(observe, cancel_deregister_keying) {\n    SUCCESS_TEST(14);\n    DM_TEST_REQUEST(mocksocks[0], CON, GET, ID_TOKEN(0xFA3E, \"Res5\"),\n                    OBSERVE(0), PATH(\"42\", \"69\", \"5\"));\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0,\n            (const anjay_iid_t[]) { 14, 42, 69, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 69, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                    { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read(anjay, &OBJ, 69, 5, ANJAY_ID_INVALID, 0,\n                                        ANJAY_MOCK_DM_INT(0, 42));\n    DM_TEST_EXPECT_READ_NULL_ATTRS(14, 69, 5);\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, CONTENT,\n                            ID_TOKEN(0xFA3E, \"Res5\"), OBSERVE(0),\n                            CONTENT_FORMAT(PLAINTEXT), PAYLOAD(\"42\"));\n    expect_has_buffered_data_check(mocksocks[0], false);\n    DM_TEST_EXPECT_READ_NULL_ATTRS(14, 69, 5);\n    DM_TEST_EXPECT_READ_NULL_ATTRS(14, 69, 4);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    assert_observe_consistency(anjay);\n    assert_observe_size(anjay, 2);\n\n    // CANCEL USING Res5 TOKEN BUT /42/69/4 PATH\n    DM_TEST_REQUEST(mocksocks[0], CON, GET, ID_TOKEN(0xFA3E, \"Res5\"),\n                    OBSERVE(0x01), PATH(\"42\", \"69\", \"4\"));\n    DM_TEST_EXPECT_READ_NULL_ATTRS(14, 69, 4);\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0,\n            (const anjay_iid_t[]) { 14, 42, 69, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 69, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                    { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read(anjay, &OBJ, 69, 4, ANJAY_ID_INVALID, 0,\n                                        ANJAY_MOCK_DM_STRING(0, \"Good-bye\"));\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, CONTENT,\n                            ID_TOKEN(0xFA3E, \"Res5\"), CONTENT_FORMAT(PLAINTEXT),\n                            PAYLOAD(\"Good-bye\"));\n    expect_has_buffered_data_check(mocksocks[0], false);\n    DM_TEST_EXPECT_READ_NULL_ATTRS(14, 69, 4);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    assert_observe_consistency(anjay);\n    assert_observe_size(anjay, 1);\n    ASSERT_SUCCESS_TEST_RESULT(14);\n\n    // CANCEL USING SuccsTkn TOKEN BUT /42/69/5 PATH\n    DM_TEST_REQUEST(mocksocks[0], CON, GET, ID_TOKEN(0xFA3F, \"SuccsTkn\"),\n                    OBSERVE(0x01), PATH(\"42\", \"69\", \"5\"));\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0,\n            (const anjay_iid_t[]) { 14, 42, 69, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 69, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                    { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read(anjay, &OBJ, 69, 5, ANJAY_ID_INVALID, 0,\n                                        ANJAY_MOCK_DM_STRING(0, \"Sayonara\"));\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, CONTENT,\n                            ID_TOKEN(0xFA3F, \"SuccsTkn\"),\n                            CONTENT_FORMAT(PLAINTEXT), PAYLOAD(\"Sayonara\"));\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    assert_observe_consistency(anjay);\n    assert_observe_size(anjay, 0);\n    DM_TEST_FINISH;\n}\n\nstatic inline void remove_server(AVS_LIST(anjay_server_info_t) *server_ptr) {\n    anjay_server_connection_t *connection =\n            _anjay_get_server_connection((const anjay_connection_ref_t) {\n                .server = *server_ptr,\n                .conn_type = ANJAY_CONNECTION_PRIMARY\n            });\n    AVS_UNIT_ASSERT_NOT_NULL(connection);\n    anjay_unlocked_t *anjay = (*server_ptr)->anjay;\n    _anjay_mocksock_expect_stats_zero(connection->conn_socket_);\n    _anjay_connection_internal_clean_socket(anjay, connection);\n    AVS_LIST_DELETE(server_ptr);\n}\n\nAVS_UNIT_TEST(observe, gc) {\n    SUCCESS_TEST(14, 69, 514, 666, 777);\n\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    remove_server(&anjay_unlocked->servers);\n    _anjay_observe_gc(anjay_unlocked);\n    ANJAY_MUTEX_UNLOCK(anjay);\n\n    assert_observe_consistency(anjay);\n    assert_observe_size(anjay, 4);\n    ASSERT_SUCCESS_TEST_RESULT(69);\n    ASSERT_SUCCESS_TEST_RESULT(514);\n    ASSERT_SUCCESS_TEST_RESULT(666);\n    ASSERT_SUCCESS_TEST_RESULT(777);\n\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    remove_server(AVS_LIST_NTH_PTR(&anjay_unlocked->servers, 3));\n    _anjay_observe_gc(anjay_unlocked);\n    ANJAY_MUTEX_UNLOCK(anjay);\n\n    assert_observe_consistency(anjay);\n    assert_observe_size(anjay, 3);\n    ASSERT_SUCCESS_TEST_RESULT(69);\n    ASSERT_SUCCESS_TEST_RESULT(514);\n    ASSERT_SUCCESS_TEST_RESULT(666);\n\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    remove_server(AVS_LIST_NTH_PTR(&anjay_unlocked->servers, 1));\n    _anjay_observe_gc(anjay_unlocked);\n    ANJAY_MUTEX_UNLOCK(anjay);\n\n    assert_observe_consistency(anjay);\n    assert_observe_size(anjay, 2);\n    ASSERT_SUCCESS_TEST_RESULT(69);\n    ASSERT_SUCCESS_TEST_RESULT(666);\n\n    DM_TEST_FINISH;\n}\n\nstatic void expect_read_res_attrs(anjay_t *anjay,\n                                  const anjay_dm_object_def_t *const *obj_ptr,\n                                  anjay_ssid_t ssid,\n                                  anjay_iid_t iid,\n                                  anjay_rid_t rid,\n                                  const anjay_dm_r_attributes_t *attrs) {\n    _anjay_mock_dm_expect_list_instances(\n            anjay, obj_ptr, 0, (const anjay_iid_t[]) { iid, ANJAY_ID_INVALID });\n    anjay_mock_dm_res_entry_t resources[] = {\n        { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n        { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n        { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n        { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n        { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n        { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n        { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n        ANJAY_MOCK_DM_RES_END\n    };\n    size_t i;\n    for (i = 0; i < AVS_ARRAY_SIZE(resources); ++i) {\n        if (resources[i].rid == rid) {\n            resources[i].presence = ANJAY_DM_RES_PRESENT;\n            break;\n        }\n    }\n    AVS_UNIT_ASSERT_TRUE(i < AVS_ARRAY_SIZE(resources));\n    _anjay_mock_dm_expect_list_resources(anjay, obj_ptr, iid, 0, resources);\n    _anjay_mock_dm_expect_resource_read_attrs(anjay, obj_ptr, iid, rid, ssid, 0,\n                                              attrs);\n    if (!_anjay_dm_attributes_full(&attrs->common)) {\n        _anjay_mock_dm_expect_instance_read_default_attrs(\n                anjay, obj_ptr, iid, ssid, 0, &ANJAY_DM_OI_ATTRIBUTES_EMPTY);\n        _anjay_mock_dm_expect_object_read_default_attrs(\n                anjay, obj_ptr, ssid, 0, &ANJAY_DM_OI_ATTRIBUTES_EMPTY);\n    }\n}\n\nstatic void expect_read_res(anjay_t *anjay,\n                            const anjay_dm_object_def_t *const *obj_ptr,\n                            anjay_iid_t iid,\n                            anjay_rid_t rid,\n                            const anjay_mock_dm_data_t *data) {\n    _anjay_mock_dm_expect_list_instances(\n            anjay, obj_ptr, 0, (const anjay_iid_t[]) { iid, ANJAY_ID_INVALID });\n    anjay_mock_dm_res_entry_t resources[] = {\n        { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n        { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n        { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n        { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n        { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n        { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n        { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n        ANJAY_MOCK_DM_RES_END\n    };\n    size_t i;\n    for (i = 0; i < AVS_ARRAY_SIZE(resources); ++i) {\n        if (resources[i].rid == rid) {\n            resources[i].presence = ANJAY_DM_RES_PRESENT;\n            break;\n        }\n    }\n    AVS_UNIT_ASSERT_TRUE(i < AVS_ARRAY_SIZE(resources));\n    _anjay_mock_dm_expect_list_resources(anjay, obj_ptr, iid, 0, resources);\n    _anjay_mock_dm_expect_resource_read(anjay, obj_ptr, iid, rid,\n                                        ANJAY_ID_INVALID, 0, data);\n}\n\nstatic const avs_coap_observe_id_t RES4_IDENTITY = {\n    .token = {\n        .size = 4,\n        .bytes = \"Res4\"\n    }\n};\n\nstatic void notify_max_period_test(const char *con_notify_ack,\n                                   size_t con_notify_ack_size,\n                                   size_t observe_size_after_ack) {\n    static const anjay_dm_r_attributes_t ATTRS = {\n        .common = {\n            .min_period = 1,\n            .max_period = 10,\n            .min_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n            .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE\n#ifdef ANJAY_WITH_LWM2M12\n            ,\n            .hqmax = ANJAY_ATTRIB_INTEGER_NONE\n#endif // ANJAY_WITH_LWM2M12\n        },\n        .greater_than = ANJAY_ATTRIB_DOUBLE_NONE,\n        .less_than = ANJAY_ATTRIB_DOUBLE_NONE,\n        .step = ANJAY_ATTRIB_DOUBLE_NONE\n#ifdef ANJAY_WITH_LWM2M12\n        ,\n        .edge = ANJAY_DM_EDGE_ATTR_NONE\n#endif // ANJAY_WITH_LWM2M12\n    };\n\n    ////// INITIALIZATION //////\n    DM_TEST_INIT_WITH_SSIDS(14);\n    DM_TEST_REQUEST(mocksocks[0], CON, GET, ID_TOKEN(0x69ED, \"Res4\"),\n                    OBSERVE(0), PATH(\"42\", \"69\", \"4\"));\n    expect_read_res(anjay, &OBJ, 69, 4, ANJAY_MOCK_DM_FLOAT(0, 514.0));\n    expect_read_res_attrs(anjay, &OBJ, 14, 69, 4, &ATTRS);\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, CONTENT,\n                            ID_TOKEN(0x69ED, \"Res4\"), CONTENT_FORMAT(PLAINTEXT),\n                            OBSERVE(0), PAYLOAD(\"514\"));\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n\n    assert_observe_size(anjay, 1);\n\n    ////// EMPTY SCHEDULER RUN //////\n    _anjay_mock_clock_advance(avs_time_duration_from_scalar(5, AVS_TIME_S));\n    anjay_sched_run(anjay);\n    assert_observe_consistency(anjay);\n    assert_observe_size(anjay, 1);\n\n    ////// PLAIN NOTIFICATION //////\n    _anjay_mock_clock_advance(avs_time_duration_from_scalar(5, AVS_TIME_S));\n    expect_read_res_attrs(anjay, &OBJ, 14, 69, 4, &ATTRS);\n    expect_read_res(anjay, &OBJ, 69, 4, ANJAY_MOCK_DM_STRING(0, \"Hello\"));\n    const coap_test_msg_t *notify_response =\n            COAP_MSG(NON, CONTENT, ID_TOKEN(MSG_ID_BASE, \"Res4\"), OBSERVE(1),\n                     CONTENT_FORMAT(PLAINTEXT), PAYLOAD(\"Hello\"));\n    avs_unit_mocksock_expect_output(mocksocks[0], notify_response->content,\n                                    notify_response->length);\n    anjay_sched_run(anjay);\n    assert_observe_consistency(anjay);\n    assert_observe_size(anjay, 1);\n\n    assert_observe(anjay, 14,\n                   &(const avs_coap_token_t) {\n                       .size = 4,\n                       .bytes = \"Res4\"\n                   },\n                   &MAKE_RESOURCE_PATH(42, 69, 4),\n                   &(const anjay_msg_details_t) {\n                       .msg_code = AVS_COAP_CODE_CONTENT,\n                       .format = AVS_COAP_FORMAT_PLAINTEXT\n                   },\n                   \"Hello\", 5);\n\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    AVS_UNIT_ASSERT_NOT_NULL(\n            AVS_SORTED_SET_FIRST(\n                    anjay_unlocked->observe.connection_entries->observations)\n                    ->notify_task);\n    AVS_UNIT_ASSERT_EQUAL(\n            AVS_SORTED_SET_FIRST(\n                    anjay_unlocked->observe.connection_entries->observations)\n                    ->last_sent->timestamp.since_real_epoch.seconds,\n            1010);\n    AVS_UNIT_ASSERT_EQUAL(\n            AVS_SORTED_SET_FIRST(\n                    anjay_unlocked->observe.connection_entries->observations)\n                    ->last_confirmable.since_real_epoch.seconds,\n            1000);\n    ANJAY_MUTEX_UNLOCK(anjay);\n\n    ////// CONFIRMABLE NOTIFICATION //////\n    _anjay_mock_clock_advance(avs_time_duration_diff(\n            avs_time_duration_from_scalar(1, AVS_TIME_DAY),\n            avs_time_duration_from_scalar(10, AVS_TIME_S)));\n    expect_read_res_attrs(anjay, &OBJ, 14, 69, 4, &ATTRS);\n    expect_read_res(anjay, &OBJ, 69, 4, ANJAY_MOCK_DM_STRING(0, \"Hi!\"));\n    const coap_test_msg_t *con_notify_response =\n            COAP_MSG(CON, CONTENT, ID_TOKEN(MSG_ID_BASE + 1, \"Res4\"),\n                     OBSERVE(2), CONTENT_FORMAT(PLAINTEXT), PAYLOAD(\"Hi!\"));\n    avs_unit_mocksock_expect_output(mocksocks[0], con_notify_response->content,\n                                    con_notify_response->length);\n    anjay_sched_run(anjay);\n    avs_unit_mocksock_input(mocksocks[0], con_notify_ack, con_notify_ack_size);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    anjay_serve(anjay, mocksocks[0]);\n    assert_observe_consistency(anjay);\n    assert_observe_size(anjay, observe_size_after_ack);\n    if (observe_size_after_ack) {\n        ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n        AVS_UNIT_ASSERT_EQUAL(\n                AVS_SORTED_SET_FIRST(anjay_unlocked->observe.connection_entries\n                                             ->observations)\n                        ->last_confirmable.since_real_epoch.seconds,\n                AVS_SORTED_SET_FIRST(anjay_unlocked->observe.connection_entries\n                                             ->observations)\n                        ->last_sent->timestamp.since_real_epoch.seconds);\n        ANJAY_MUTEX_UNLOCK(anjay);\n\n        ////// ANOTHER PLAIN NOTIFICATION //////\n        _anjay_mock_clock_advance(\n                avs_time_duration_from_scalar(10, AVS_TIME_S));\n        expect_read_res_attrs(anjay, &OBJ, 14, 69, 4, &ATTRS);\n        expect_read_res(anjay, &OBJ, 69, 4, ANJAY_MOCK_DM_STRING(0, \"Howdy!\"));\n        const coap_test_msg_t *non_notify_response =\n                COAP_MSG(NON, CONTENT, ID_TOKEN(0x0002, \"Res4\"), OBSERVE(3),\n                         CONTENT_FORMAT(PLAINTEXT), PAYLOAD(\"Howdy!\"));\n        avs_unit_mocksock_expect_output(mocksocks[0],\n                                        non_notify_response->content,\n                                        non_notify_response->length);\n        anjay_sched_run(anjay);\n        assert_observe_consistency(anjay);\n        assert_observe_size(anjay, 1);\n\n        assert_observe(anjay, 14,\n                       &(const avs_coap_token_t) {\n                           .size = 4,\n                           .bytes = \"Res4\"\n                       },\n                       &MAKE_RESOURCE_PATH(42, 69, 4),\n                       &(const anjay_msg_details_t) {\n                           .msg_code = AVS_COAP_CODE_CONTENT,\n                           .format = AVS_COAP_FORMAT_PLAINTEXT\n                       },\n                       \"Howdy!\", 6);\n    }\n\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(notify, max_period) {\n    notify_max_period_test(\"\\x60\\x00\\x00\\x01\", 4, 1); // CON\n    notify_max_period_test(\"\\x70\\x00\\x00\\x01\", 4, 0); // Reset\n}\n\nAVS_UNIT_TEST(notify, min_period) {\n    static const anjay_dm_r_attributes_t ATTRS = {\n        .common = {\n            .min_period = 10,\n            .max_period = 365 * 24 * 60 * 60 /* a year */,\n            .min_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n            .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE\n#ifdef ANJAY_WITH_LWM2M12\n            ,\n            .hqmax = ANJAY_ATTRIB_INTEGER_NONE\n#endif // ANJAY_WITH_LWM2M12\n        },\n        .greater_than = ANJAY_ATTRIB_DOUBLE_NONE,\n        .less_than = ANJAY_ATTRIB_DOUBLE_NONE,\n        .step = ANJAY_ATTRIB_DOUBLE_NONE\n#ifdef ANJAY_WITH_LWM2M12\n        ,\n        .edge = ANJAY_DM_EDGE_ATTR_NONE\n#endif // ANJAY_WITH_LWM2M12\n    };\n\n    ////// INITIALIZATION //////\n    DM_TEST_INIT_WITH_SSIDS(14);\n    DM_TEST_REQUEST(mocksocks[0], CON, GET, ID_TOKEN(0x69ED, \"Res4\"),\n                    OBSERVE(0), PATH(\"42\", \"69\", \"4\"));\n    expect_read_res(anjay, &OBJ, 69, 4, ANJAY_MOCK_DM_FLOAT(0, 514.0));\n    expect_read_res_attrs(anjay, &OBJ, 14, 69, 4, &ATTRS);\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, CONTENT,\n                            ID_TOKEN(0x69ED, \"Res4\"), CONTENT_FORMAT(PLAINTEXT),\n                            OBSERVE(0), PAYLOAD(\"514\"));\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n\n    assert_observe_size(anjay, 1);\n\n    ////// PMIN NOT REACHED //////\n    _anjay_mock_clock_advance(avs_time_duration_from_scalar(5, AVS_TIME_S));\n    expect_read_res_attrs(anjay, &OBJ, 14, 69, 4, &ATTRS);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_notify_changed(anjay, 42, 69, 4));\n    anjay_sched_run(anjay);\n\n    ////// PMIN REACHED //////\n    _anjay_mock_clock_advance(avs_time_duration_from_scalar(5, AVS_TIME_S));\n    expect_read_res_attrs(anjay, &OBJ, 14, 69, 4, &ATTRS);\n    expect_read_res(anjay, &OBJ, 69, 4, ANJAY_MOCK_DM_STRING(0, \"Hi!\"));\n    const coap_test_msg_t *notify_response =\n            COAP_MSG(NON, CONTENT, ID_TOKEN(MSG_ID_BASE, \"Res4\"), OBSERVE(1),\n                     CONTENT_FORMAT(PLAINTEXT), PAYLOAD(\"Hi!\"));\n    avs_unit_mocksock_expect_output(mocksocks[0], notify_response->content,\n                                    notify_response->length);\n    anjay_sched_run(anjay);\n    assert_observe_consistency(anjay);\n    assert_observe_size(anjay, 1);\n\n    ////// AFTER PMIN, NO CHANGE //////\n    _anjay_mock_clock_advance(avs_time_duration_from_scalar(10, AVS_TIME_S));\n    expect_read_res_attrs(anjay, &OBJ, 14, 69, 4, &ATTRS);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_notify_changed(anjay, 42, 69, 4));\n    expect_read_res_attrs(anjay, &OBJ, 14, 69, 4, &ATTRS);\n    expect_read_res(anjay, &OBJ, 69, 4, ANJAY_MOCK_DM_STRING(0, \"Hi!\"));\n    anjay_sched_run(anjay);\n    assert_observe_consistency(anjay);\n    assert_observe_size(anjay, 1);\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    AVS_UNIT_ASSERT_NOT_NULL(\n            AVS_SORTED_SET_FIRST(\n                    anjay_unlocked->observe.connection_entries->observations)\n                    ->notify_task);\n    ANJAY_MUTEX_UNLOCK(anjay);\n\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(notify, epmin_greater_than_pmax) {\n    static const anjay_dm_r_attributes_t ATTRS = {\n        .common = {\n            .min_period = 0,\n            .max_period = 5,\n            .min_eval_period = 8,\n            .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE\n#ifdef ANJAY_WITH_LWM2M12\n            ,\n            .hqmax = ANJAY_ATTRIB_INTEGER_NONE\n#endif // ANJAY_WITH_LWM2M12\n        },\n        .greater_than = ANJAY_ATTRIB_DOUBLE_NONE,\n        .less_than = ANJAY_ATTRIB_DOUBLE_NONE,\n        .step = ANJAY_ATTRIB_DOUBLE_NONE\n#ifdef ANJAY_WITH_LWM2M12\n        ,\n        .edge = ANJAY_DM_EDGE_ATTR_NONE\n#endif // ANJAY_WITH_LWM2M12\n    };\n\n    ////// INITIALIZATION //////\n    DM_TEST_INIT_WITH_SSIDS(14);\n    DM_TEST_REQUEST(mocksocks[0], CON, GET, ID_TOKEN(0x69ED, \"I love C\"),\n                    OBSERVE(0), PATH(\"42\", \"69\", \"4\"));\n    expect_read_res(anjay, &OBJ, 69, 4, ANJAY_MOCK_DM_INT(0, 314159));\n    expect_read_res_attrs(anjay, &OBJ, 14, 69, 4, &ATTRS);\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, CONTENT,\n                            ID_TOKEN(0x69ED, \"I love C\"),\n                            CONTENT_FORMAT(PLAINTEXT), OBSERVE(0),\n                            PAYLOAD(\"314159\"));\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n\n    ////// NOTIFICATION BEFORE EPMIN EXPIRATION //////\n    _anjay_mock_clock_advance(avs_time_duration_from_scalar(6, AVS_TIME_S));\n    expect_read_res_attrs(anjay, &OBJ, 14, 69, 4, &ATTRS);\n    // pmax has expired but epmin has not expired yet so we don't expect calling\n    // read_handler\n    const coap_test_msg_t *notify_response1 =\n            COAP_MSG(NON, CONTENT, ID_TOKEN(MSG_ID_BASE, \"I love C\"),\n                     OBSERVE(1), CONTENT_FORMAT(PLAINTEXT), PAYLOAD(\"314159\"));\n    avs_unit_mocksock_expect_output(mocksocks[0], notify_response1->content,\n                                    notify_response1->length);\n    anjay_sched_run(anjay);\n\n    ////// NOTIFICATION AFTER EPMIN EXPIRATION //////\n    _anjay_mock_clock_advance(avs_time_duration_from_scalar(6, AVS_TIME_S));\n    expect_read_res_attrs(anjay, &OBJ, 14, 69, 4, &ATTRS);\n    // epmin has finally expired so read_handler can be called\n    expect_read_res(anjay, &OBJ, 69, 4, ANJAY_MOCK_DM_INT(0, 271828));\n    const coap_test_msg_t *notify_response2 =\n            COAP_MSG(NON, CONTENT, ID_TOKEN(MSG_ID_BASE + 1, \"I love C\"),\n                     OBSERVE(2), CONTENT_FORMAT(PLAINTEXT), PAYLOAD(\"271828\"));\n    avs_unit_mocksock_expect_output(mocksocks[0], notify_response2->content,\n                                    notify_response2->length);\n    anjay_sched_run(anjay);\n\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(notify, epmin_less_than_pmax) {\n    static const anjay_dm_r_attributes_t ATTRS = {\n        .common = {\n            .min_period = 0,\n            .max_period = 365 * 24 * 60 * 60 /* a year */,\n            .min_eval_period = 15,\n            .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE\n#ifdef ANJAY_WITH_LWM2M12\n            ,\n            .hqmax = ANJAY_ATTRIB_INTEGER_NONE\n#endif // ANJAY_WITH_LWM2M12\n        },\n        .greater_than = ANJAY_ATTRIB_DOUBLE_NONE,\n        .less_than = ANJAY_ATTRIB_DOUBLE_NONE,\n        .step = ANJAY_ATTRIB_DOUBLE_NONE\n#ifdef ANJAY_WITH_LWM2M12\n        ,\n        .edge = ANJAY_DM_EDGE_ATTR_NONE\n#endif // ANJAY_WITH_LWM2M12\n    };\n\n    ////// INITIALIZATION //////\n    DM_TEST_INIT_WITH_SSIDS(14);\n    DM_TEST_REQUEST(mocksocks[0], CON, GET, ID_TOKEN(0x69ED, \"I love C\"),\n                    OBSERVE(0), PATH(\"42\", \"69\", \"4\"));\n    expect_read_res(anjay, &OBJ, 69, 4, ANJAY_MOCK_DM_INT(0, 314159));\n    expect_read_res_attrs(anjay, &OBJ, 14, 69, 4, &ATTRS);\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, CONTENT,\n                            ID_TOKEN(0x69ED, \"I love C\"),\n                            CONTENT_FORMAT(PLAINTEXT), OBSERVE(0),\n                            PAYLOAD(\"314159\"));\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n\n    ////// NOTIFY ABOUT RESOURCE CHANGE //////\n    _anjay_mock_clock_advance(avs_time_duration_from_scalar(10, AVS_TIME_S));\n    expect_read_res_attrs(anjay, &OBJ, 14, 69, 4, &ATTRS);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_notify_changed(anjay, 42, 69, 4));\n\n    ////// EPMIN EXPIRED BUT RESOURCE VALUE REMAINS THE SAME //////\n    _anjay_mock_clock_advance(avs_time_duration_from_scalar(10, AVS_TIME_S));\n    expect_read_res_attrs(anjay, &OBJ, 14, 69, 4, &ATTRS);\n    expect_read_res(anjay, &OBJ, 69, 4, ANJAY_MOCK_DM_INT(0, 314159));\n    anjay_sched_run(anjay);\n    // We don't send notification yet because resource value remains the same\n\n    ////// NOTIFY ABOUT RESOURCE CHANGE //////\n    _anjay_mock_clock_advance(avs_time_duration_from_scalar(10, AVS_TIME_S));\n    expect_read_res_attrs(anjay, &OBJ, 14, 69, 4, &ATTRS);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_notify_changed(anjay, 42, 69, 4));\n\n    ////// EPMIN EXPIRED AND RESOURCE VALUE CHANGED //////\n    _anjay_mock_clock_advance(avs_time_duration_from_scalar(10, AVS_TIME_S));\n    expect_read_res_attrs(anjay, &OBJ, 14, 69, 4, &ATTRS);\n    expect_read_res(anjay, &OBJ, 69, 4, ANJAY_MOCK_DM_INT(0, 271828));\n    const coap_test_msg_t *notify_response =\n            COAP_MSG(NON, CONTENT, ID_TOKEN(MSG_ID_BASE, \"I love C\"),\n                     OBSERVE(1), CONTENT_FORMAT(PLAINTEXT), PAYLOAD(\"271828\"));\n    avs_unit_mocksock_expect_output(mocksocks[0], notify_response->content,\n                                    notify_response->length);\n    anjay_sched_run(anjay);\n\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(notify, confirmable) {\n    ////// INITIALIZATION //////\n    const anjay_dm_object_def_t *const *obj_defs[] = {\n        DM_TEST_DEFAULT_OBJECTS\n    };\n    anjay_ssid_t ssids[] = { 14 };\n    DM_TEST_INIT_GENERIC(obj_defs, ssids,\n                         DM_TEST_CONFIGURATION(.confirmable_notifications =\n                                                       true));\n    DM_TEST_REQUEST(mocksocks[0], CON, GET, ID_TOKEN(0x69ED, \"Res4\"),\n                    OBSERVE(0), PATH(\"42\", \"69\", \"4\"));\n    expect_read_res(anjay, &OBJ, 69, 4, ANJAY_MOCK_DM_FLOAT(0, 514.0));\n    DM_TEST_EXPECT_READ_NULL_ATTRS(14, 69, 4);\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, CONTENT,\n                            ID_TOKEN(0x69ED, \"Res4\"), CONTENT_FORMAT(PLAINTEXT),\n                            OBSERVE(0), PAYLOAD(\"514\"));\n    expect_has_buffered_data_check(mocksocks[0], false);\n    DM_TEST_EXPECT_READ_NULL_ATTRS(14, 69, 4);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n\n    assert_observe_size(anjay, 1);\n\n    ////// EMPTY SCHEDULER RUN //////\n    _anjay_mock_clock_advance(avs_time_duration_from_scalar(5, AVS_TIME_S));\n    anjay_sched_run(anjay);\n    assert_observe_consistency(anjay);\n    assert_observe_size(anjay, 1);\n\n    ////// CONFIRMABLE NOTIFICATION //////\n    _anjay_mock_clock_advance(avs_time_duration_from_scalar(5, AVS_TIME_S));\n    DM_TEST_EXPECT_READ_NULL_ATTRS(14, 69, 4);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_notify_changed(anjay, 42, 69, 4));\n    DM_TEST_EXPECT_READ_NULL_ATTRS(14, 69, 4);\n    expect_read_res(anjay, &OBJ, 69, 4, ANJAY_MOCK_DM_INT(0, 42));\n    const coap_test_msg_t *notify_response =\n            COAP_MSG(CON, CONTENT, ID_TOKEN(MSG_ID_BASE, \"Res4\"), OBSERVE(1),\n                     CONTENT_FORMAT(PLAINTEXT), PAYLOAD(\"42\"));\n    avs_unit_mocksock_expect_output(mocksocks[0], notify_response->content,\n                                    notify_response->length);\n    anjay_sched_run(anjay);\n\n    const coap_test_msg_t *notify_ack =\n            COAP_MSG(ACK, EMPTY, ID(MSG_ID_BASE), NO_PAYLOAD);\n    avs_unit_mocksock_input(mocksocks[0], notify_ack->content,\n                            notify_ack->length);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    DM_TEST_EXPECT_READ_NULL_ATTRS(14, 69, 4);\n    anjay_serve(anjay, mocksocks[0]);\n\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(notify, extremes) {\n    static const anjay_dm_r_attributes_t ATTRS = {\n        .common = {\n            .min_period = 0,\n            .max_period = 365 * 24 * 60 * 60 /* a year */,\n            .min_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n            .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE\n#ifdef ANJAY_WITH_LWM2M12\n            ,\n            .hqmax = ANJAY_ATTRIB_INTEGER_NONE\n#endif // ANJAY_WITH_LWM2M12\n        },\n        .greater_than = 777.0,\n        .less_than = 69.0,\n        .step = ANJAY_ATTRIB_DOUBLE_NONE\n#ifdef ANJAY_WITH_LWM2M12\n        ,\n        .edge = ANJAY_DM_EDGE_ATTR_NONE\n#endif // ANJAY_WITH_LWM2M12\n    };\n\n    ////// INITIALIZATION //////\n    DM_TEST_INIT_WITH_SSIDS(14);\n    DM_TEST_REQUEST(mocksocks[0], CON, GET, ID_TOKEN(0x69ED, \"Res4\"),\n                    OBSERVE(0), PATH(\"42\", \"69\", \"4\"));\n    expect_read_res(anjay, &OBJ, 69, 4, ANJAY_MOCK_DM_FLOAT(0, 514.0));\n    expect_read_res_attrs(anjay, &OBJ, 14, 69, 4, &ATTRS);\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, CONTENT,\n                            ID_TOKEN(0x69ED, \"Res4\"), CONTENT_FORMAT(PLAINTEXT),\n                            OBSERVE(0), PAYLOAD(\"514\"));\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n\n    assert_observe_size(anjay, 1);\n\n    ////// LESS //////\n    expect_read_res_attrs(anjay, &OBJ, 14, 69, 4, &ATTRS);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_notify_changed(anjay, 42, 69, 4));\n    expect_read_res_attrs(anjay, &OBJ, 14, 69, 4, &ATTRS);\n    expect_read_res(anjay, &OBJ, 69, 4, ANJAY_MOCK_DM_FLOAT(0, 42.43));\n    const coap_test_msg_t *notify_response =\n            COAP_MSG(NON, CONTENT, ID_TOKEN(MSG_ID_BASE, \"Res4\"), OBSERVE(1),\n                     CONTENT_FORMAT(PLAINTEXT), PAYLOAD(\"42.43\"));\n    avs_unit_mocksock_expect_output(mocksocks[0], notify_response->content,\n                                    notify_response->length);\n    anjay_sched_run(anjay);\n    assert_observe_consistency(anjay);\n    assert_observe_size(anjay, 1);\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    AVS_UNIT_ASSERT_NOT_NULL(\n            AVS_SORTED_SET_FIRST(\n                    anjay_unlocked->observe.connection_entries->observations)\n                    ->notify_task);\n    ANJAY_MUTEX_UNLOCK(anjay);\n\n    ////// EVEN LESS //////\n    expect_read_res_attrs(anjay, &OBJ, 14, 69, 4, &ATTRS);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_notify_changed(anjay, 42, 69, 4));\n    expect_read_res_attrs(anjay, &OBJ, 14, 69, 4, &ATTRS);\n    expect_read_res(anjay, &OBJ, 69, 4, ANJAY_MOCK_DM_FLOAT(0, 14.7));\n    anjay_sched_run(anjay);\n    assert_observe_consistency(anjay);\n    assert_observe_size(anjay, 1);\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    AVS_UNIT_ASSERT_NOT_NULL(\n            AVS_SORTED_SET_FIRST(\n                    anjay_unlocked->observe.connection_entries->observations)\n                    ->notify_task);\n    ANJAY_MUTEX_UNLOCK(anjay);\n\n    ////// IN BETWEEN //////\n    expect_read_res_attrs(anjay, &OBJ, 14, 69, 4, &ATTRS);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_notify_changed(anjay, 42, 69, 4));\n    expect_read_res_attrs(anjay, &OBJ, 14, 69, 4, &ATTRS);\n    expect_read_res(anjay, &OBJ, 69, 4, ANJAY_MOCK_DM_INT(0, 695));\n    const coap_test_msg_t *notify_response2 =\n            COAP_MSG(NON, CONTENT, ID_TOKEN(MSG_ID_BASE + 1, \"Res4\"),\n                     OBSERVE(2), CONTENT_FORMAT(PLAINTEXT), PAYLOAD(\"695\"));\n    avs_unit_mocksock_expect_output(mocksocks[0], notify_response2->content,\n                                    notify_response2->length);\n    anjay_sched_run(anjay);\n    assert_observe_consistency(anjay);\n    assert_observe_size(anjay, 1);\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    AVS_UNIT_ASSERT_NOT_NULL(\n            AVS_SORTED_SET_FIRST(\n                    anjay_unlocked->observe.connection_entries->observations)\n                    ->notify_task);\n    ANJAY_MUTEX_UNLOCK(anjay);\n\n    ////// EQUAL - STILL NOT CROSSING //////\n    expect_read_res_attrs(anjay, &OBJ, 14, 69, 4, &ATTRS);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_notify_changed(anjay, 42, 69, 4));\n    expect_read_res_attrs(anjay, &OBJ, 14, 69, 4, &ATTRS);\n    expect_read_res(anjay, &OBJ, 69, 4, ANJAY_MOCK_DM_INT(0, 69));\n    anjay_sched_run(anjay);\n    assert_observe_consistency(anjay);\n    assert_observe_size(anjay, 1);\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    AVS_UNIT_ASSERT_NOT_NULL(\n            AVS_SORTED_SET_FIRST(\n                    anjay_unlocked->observe.connection_entries->observations)\n                    ->notify_task);\n    ANJAY_MUTEX_UNLOCK(anjay);\n\n    ////// GREATER //////\n    expect_read_res_attrs(anjay, &OBJ, 14, 69, 4, &ATTRS);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_notify_changed(anjay, 42, 69, 4));\n    expect_read_res_attrs(anjay, &OBJ, 14, 69, 4, &ATTRS);\n    expect_read_res(anjay, &OBJ, 69, 4, ANJAY_MOCK_DM_INT(0, 1024));\n    const coap_test_msg_t *notify_response3 =\n            COAP_MSG(NON, CONTENT, ID_TOKEN(MSG_ID_BASE + 2, \"Res4\"),\n                     OBSERVE(3), CONTENT_FORMAT(PLAINTEXT), PAYLOAD(\"1024\"));\n    avs_unit_mocksock_expect_output(mocksocks[0], notify_response3->content,\n                                    notify_response3->length);\n    anjay_sched_run(anjay);\n    assert_observe_consistency(anjay);\n    assert_observe_size(anjay, 1);\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    AVS_UNIT_ASSERT_NOT_NULL(\n            AVS_SORTED_SET_FIRST(\n                    anjay_unlocked->observe.connection_entries->observations)\n                    ->notify_task);\n    ANJAY_MUTEX_UNLOCK(anjay);\n\n    ////// STILL GREATER //////\n    expect_read_res_attrs(anjay, &OBJ, 14, 69, 4, &ATTRS);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_notify_changed(anjay, 42, 69, 4));\n    expect_read_res_attrs(anjay, &OBJ, 14, 69, 4, &ATTRS);\n    expect_read_res(anjay, &OBJ, 69, 4, ANJAY_MOCK_DM_INT(0, 999));\n    anjay_sched_run(anjay);\n    assert_observe_consistency(anjay);\n    assert_observe_size(anjay, 1);\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    AVS_UNIT_ASSERT_NOT_NULL(\n            AVS_SORTED_SET_FIRST(\n                    anjay_unlocked->observe.connection_entries->observations)\n                    ->notify_task);\n    ANJAY_MUTEX_UNLOCK(anjay);\n\n    ////// LESS AGAIN //////\n    expect_read_res_attrs(anjay, &OBJ, 14, 69, 4, &ATTRS);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_notify_changed(anjay, 42, 69, 4));\n    expect_read_res_attrs(anjay, &OBJ, 14, 69, 4, &ATTRS);\n    expect_read_res(anjay, &OBJ, 69, 4, ANJAY_MOCK_DM_FLOAT(0, -69.75));\n    const coap_test_msg_t *notify_response4 =\n            COAP_MSG(NON, CONTENT, ID_TOKEN(MSG_ID_BASE + 3, \"Res4\"),\n                     OBSERVE(4), CONTENT_FORMAT(PLAINTEXT), PAYLOAD(\"-69.75\"));\n    avs_unit_mocksock_expect_output(mocksocks[0], notify_response4->content,\n                                    notify_response4->length);\n    anjay_sched_run(anjay);\n    assert_observe_consistency(anjay);\n    assert_observe_size(anjay, 1);\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    AVS_UNIT_ASSERT_NOT_NULL(\n            AVS_SORTED_SET_FIRST(\n                    anjay_unlocked->observe.connection_entries->observations)\n                    ->notify_task);\n    ANJAY_MUTEX_UNLOCK(anjay);\n\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(notify, greater_only) {\n    static const anjay_dm_r_attributes_t ATTRS = {\n        .common = {\n            .min_period = 0,\n            .max_period = 365 * 24 * 60 * 60 /* a year */,\n            .min_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n            .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE\n#ifdef ANJAY_WITH_LWM2M12\n            ,\n            .hqmax = ANJAY_ATTRIB_INTEGER_NONE\n#endif // ANJAY_WITH_LWM2M12\n        },\n        .greater_than = 69.0,\n        .less_than = ANJAY_ATTRIB_DOUBLE_NONE,\n        .step = ANJAY_ATTRIB_DOUBLE_NONE\n#ifdef ANJAY_WITH_LWM2M12\n        ,\n        .edge = ANJAY_DM_EDGE_ATTR_NONE\n#endif // ANJAY_WITH_LWM2M12\n    };\n\n    ////// INITIALIZATION (GREATER) //////\n    DM_TEST_INIT_WITH_SSIDS(14);\n    DM_TEST_REQUEST(mocksocks[0], CON, GET, ID_TOKEN(0x69ED, \"Res4\"),\n                    OBSERVE(0), PATH(\"42\", \"69\", \"4\"));\n    expect_read_res(anjay, &OBJ, 69, 4, ANJAY_MOCK_DM_FLOAT(0, 514.0));\n    expect_read_res_attrs(anjay, &OBJ, 14, 69, 4, &ATTRS);\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, CONTENT,\n                            ID_TOKEN(0x69ED, \"Res4\"), CONTENT_FORMAT(PLAINTEXT),\n                            OBSERVE(0), PAYLOAD(\"514\"));\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n\n    assert_observe_size(anjay, 1);\n\n    ////// STILL GREATER //////\n    expect_read_res_attrs(anjay, &OBJ, 14, 69, 4, &ATTRS);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_notify_changed(anjay, 42, 69, 4));\n    expect_read_res_attrs(anjay, &OBJ, 14, 69, 4, &ATTRS);\n    expect_read_res(anjay, &OBJ, 69, 4, ANJAY_MOCK_DM_INT(0, 9001));\n    anjay_sched_run(anjay);\n    assert_observe_consistency(anjay);\n    assert_observe_size(anjay, 1);\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    AVS_UNIT_ASSERT_NOT_NULL(\n            AVS_SORTED_SET_FIRST(\n                    anjay_unlocked->observe.connection_entries->observations)\n                    ->notify_task);\n    ANJAY_MUTEX_UNLOCK(anjay);\n\n    ////// LESS //////\n    expect_read_res_attrs(anjay, &OBJ, 14, 69, 4, &ATTRS);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_notify_changed(anjay, 42, 69, 4));\n    expect_read_res_attrs(anjay, &OBJ, 14, 69, 4, &ATTRS);\n    expect_read_res(anjay, &OBJ, 69, 4, ANJAY_MOCK_DM_INT(0, 42));\n    const coap_test_msg_t *notify_response =\n            COAP_MSG(NON, CONTENT, ID_TOKEN(MSG_ID_BASE, \"Res4\"), OBSERVE(1),\n                     CONTENT_FORMAT(PLAINTEXT), PAYLOAD(\"42\"));\n    avs_unit_mocksock_expect_output(mocksocks[0], notify_response->content,\n                                    notify_response->length);\n    anjay_sched_run(anjay);\n    assert_observe_consistency(anjay);\n    assert_observe_size(anjay, 1);\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    AVS_UNIT_ASSERT_NOT_NULL(\n            AVS_SORTED_SET_FIRST(\n                    anjay_unlocked->observe.connection_entries->observations)\n                    ->notify_task);\n    ANJAY_MUTEX_UNLOCK(anjay);\n\n    ////// GREATER AGAIN //////\n    expect_read_res_attrs(anjay, &OBJ, 14, 69, 4, &ATTRS);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_notify_changed(anjay, 42, 69, 4));\n    expect_read_res_attrs(anjay, &OBJ, 14, 69, 4, &ATTRS);\n    expect_read_res(anjay, &OBJ, 69, 4, ANJAY_MOCK_DM_INT(0, 77));\n    const coap_test_msg_t *notify_response2 =\n            COAP_MSG(NON, CONTENT, ID_TOKEN(MSG_ID_BASE + 1, \"Res4\"),\n                     OBSERVE(2), CONTENT_FORMAT(PLAINTEXT), PAYLOAD(\"77\"));\n    avs_unit_mocksock_expect_output(mocksocks[0], notify_response2->content,\n                                    notify_response2->length);\n    anjay_sched_run(anjay);\n    assert_observe_consistency(anjay);\n    assert_observe_size(anjay, 1);\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    AVS_UNIT_ASSERT_NOT_NULL(\n            AVS_SORTED_SET_FIRST(\n                    anjay_unlocked->observe.connection_entries->observations)\n                    ->notify_task);\n    ANJAY_MUTEX_UNLOCK(anjay);\n\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(notify, less_only) {\n    static const anjay_dm_r_attributes_t ATTRS = {\n        .common = {\n            .min_period = 0,\n            .max_period = 365 * 24 * 60 * 60 /* a year */,\n            .min_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n            .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE\n#ifdef ANJAY_WITH_LWM2M12\n            ,\n            .hqmax = ANJAY_ATTRIB_INTEGER_NONE\n#endif // ANJAY_WITH_LWM2M12\n        },\n        .greater_than = ANJAY_ATTRIB_DOUBLE_NONE,\n        .less_than = 777.0,\n        .step = ANJAY_ATTRIB_DOUBLE_NONE\n#ifdef ANJAY_WITH_LWM2M12\n        ,\n        .edge = ANJAY_DM_EDGE_ATTR_NONE\n#endif // ANJAY_WITH_LWM2M12\n    };\n\n    ////// INITIALIZATION (GREATER) //////\n    DM_TEST_INIT_WITH_SSIDS(14);\n    DM_TEST_REQUEST(mocksocks[0], CON, GET, ID_TOKEN(0x69ED, \"Res4\"),\n                    OBSERVE(0), PATH(\"42\", \"69\", \"4\"));\n    expect_read_res(anjay, &OBJ, 69, 4, ANJAY_MOCK_DM_FLOAT(0, 1337.0));\n    expect_read_res_attrs(anjay, &OBJ, 14, 69, 4, &ATTRS);\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, CONTENT,\n                            ID_TOKEN(0x69ED, \"Res4\"), CONTENT_FORMAT(PLAINTEXT),\n                            OBSERVE(0), PAYLOAD(\"1337\"));\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n\n    assert_observe_size(anjay, 1);\n\n    ////// LESS //////\n    expect_read_res_attrs(anjay, &OBJ, 14, 69, 4, &ATTRS);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_notify_changed(anjay, 42, 69, 4));\n    expect_read_res_attrs(anjay, &OBJ, 14, 69, 4, &ATTRS);\n    expect_read_res(anjay, &OBJ, 69, 4, ANJAY_MOCK_DM_INT(0, 42));\n    const coap_test_msg_t *notify_response =\n            COAP_MSG(NON, CONTENT, ID_TOKEN(MSG_ID_BASE, \"Res4\"), OBSERVE(1),\n                     CONTENT_FORMAT(PLAINTEXT), PAYLOAD(\"42\"));\n    avs_unit_mocksock_expect_output(mocksocks[0], notify_response->content,\n                                    notify_response->length);\n    anjay_sched_run(anjay);\n    assert_observe_consistency(anjay);\n    assert_observe_size(anjay, 1);\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    AVS_UNIT_ASSERT_NOT_NULL(\n            AVS_SORTED_SET_FIRST(\n                    anjay_unlocked->observe.connection_entries->observations)\n                    ->notify_task);\n    ANJAY_MUTEX_UNLOCK(anjay);\n\n    ////// STILL LESS //////\n    expect_read_res_attrs(anjay, &OBJ, 14, 69, 4, &ATTRS);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_notify_changed(anjay, 42, 69, 4));\n    expect_read_res_attrs(anjay, &OBJ, 14, 69, 4, &ATTRS);\n    expect_read_res(anjay, &OBJ, 69, 4, ANJAY_MOCK_DM_INT(0, 514));\n    anjay_sched_run(anjay);\n    assert_observe_consistency(anjay);\n    assert_observe_size(anjay, 1);\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    AVS_UNIT_ASSERT_NOT_NULL(\n            AVS_SORTED_SET_FIRST(\n                    anjay_unlocked->observe.connection_entries->observations)\n                    ->notify_task);\n    ANJAY_MUTEX_UNLOCK(anjay);\n\n    ////// GREATER //////\n    expect_read_res_attrs(anjay, &OBJ, 14, 69, 4, &ATTRS);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_notify_changed(anjay, 42, 69, 4));\n    expect_read_res_attrs(anjay, &OBJ, 14, 69, 4, &ATTRS);\n    expect_read_res(anjay, &OBJ, 69, 4, ANJAY_MOCK_DM_INT(0, 9001));\n    const coap_test_msg_t *notify_response2 =\n            COAP_MSG(NON, CONTENT, ID_TOKEN(MSG_ID_BASE + 1, \"Res4\"),\n                     OBSERVE(2), CONTENT_FORMAT(PLAINTEXT), PAYLOAD(\"9001\"));\n    avs_unit_mocksock_expect_output(mocksocks[0], notify_response2->content,\n                                    notify_response2->length);\n    anjay_sched_run(anjay);\n    assert_observe_consistency(anjay);\n    assert_observe_size(anjay, 1);\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    AVS_UNIT_ASSERT_NOT_NULL(\n            AVS_SORTED_SET_FIRST(\n                    anjay_unlocked->observe.connection_entries->observations)\n                    ->notify_task);\n    ANJAY_MUTEX_UNLOCK(anjay);\n\n    ////// LESS AGAIN //////\n    expect_read_res_attrs(anjay, &OBJ, 14, 69, 4, &ATTRS);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_notify_changed(anjay, 42, 69, 4));\n    expect_read_res_attrs(anjay, &OBJ, 14, 69, 4, &ATTRS);\n    expect_read_res(anjay, &OBJ, 69, 4, ANJAY_MOCK_DM_INT(0, 69));\n    const coap_test_msg_t *notify_response3 =\n            COAP_MSG(NON, CONTENT, ID_TOKEN(MSG_ID_BASE + 2, \"Res4\"),\n                     OBSERVE(3), CONTENT_FORMAT(PLAINTEXT), PAYLOAD(\"69\"));\n    avs_unit_mocksock_expect_output(mocksocks[0], notify_response3->content,\n                                    notify_response3->length);\n    anjay_sched_run(anjay);\n    assert_observe_consistency(anjay);\n    assert_observe_size(anjay, 1);\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    AVS_UNIT_ASSERT_NOT_NULL(\n            AVS_SORTED_SET_FIRST(\n                    anjay_unlocked->observe.connection_entries->observations)\n                    ->notify_task);\n    ANJAY_MUTEX_UNLOCK(anjay);\n\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(notify, step) {\n    static const anjay_dm_r_attributes_t ATTRS = {\n        .common = {\n            .min_period = 0,\n            .max_period = 365 * 24 * 60 * 60 /* a year */,\n            .min_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n            .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE\n#ifdef ANJAY_WITH_LWM2M12\n            ,\n            .hqmax = ANJAY_ATTRIB_INTEGER_NONE\n#endif // ANJAY_WITH_LWM2M12\n        },\n        .greater_than = ANJAY_ATTRIB_DOUBLE_NONE,\n        .less_than = ANJAY_ATTRIB_DOUBLE_NONE,\n        .step = 10.0\n#ifdef ANJAY_WITH_LWM2M12\n        ,\n        .edge = ANJAY_DM_EDGE_ATTR_NONE\n#endif // ANJAY_WITH_LWM2M12\n    };\n\n    ////// INITIALIZATION //////\n    DM_TEST_INIT_WITH_SSIDS(14);\n    DM_TEST_REQUEST(mocksocks[0], CON, GET, ID_TOKEN(0x69ED, \"Res4\"),\n                    OBSERVE(0), PATH(\"42\", \"69\", \"4\"));\n    expect_read_res(anjay, &OBJ, 69, 4, ANJAY_MOCK_DM_FLOAT(0, 514.0));\n    expect_read_res_attrs(anjay, &OBJ, 14, 69, 4, &ATTRS);\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, CONTENT,\n                            ID_TOKEN(0x69ED, \"Res4\"), CONTENT_FORMAT(PLAINTEXT),\n                            OBSERVE(0), PAYLOAD(\"514\"));\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    assert_observe_size(anjay, 1);\n\n    ////// TOO LITTLE INCREASE //////\n    expect_read_res_attrs(anjay, &OBJ, 14, 69, 4, &ATTRS);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_notify_changed(anjay, 42, 69, 4));\n    expect_read_res_attrs(anjay, &OBJ, 14, 69, 4, &ATTRS);\n    expect_read_res(anjay, &OBJ, 69, 4, ANJAY_MOCK_DM_FLOAT(0, 523.5));\n    anjay_sched_run(anjay);\n    assert_observe_consistency(anjay);\n    assert_observe_size(anjay, 1);\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    AVS_UNIT_ASSERT_NOT_NULL(\n            AVS_SORTED_SET_FIRST(\n                    anjay_unlocked->observe.connection_entries->observations)\n                    ->notify_task);\n    ANJAY_MUTEX_UNLOCK(anjay);\n\n    ////// INCREASE BY EXACTLY stp //////\n    expect_read_res_attrs(anjay, &OBJ, 14, 69, 4, &ATTRS);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_notify_changed(anjay, 42, 69, 4));\n    expect_read_res_attrs(anjay, &OBJ, 14, 69, 4, &ATTRS);\n    expect_read_res(anjay, &OBJ, 69, 4, ANJAY_MOCK_DM_INT(0, 524));\n    const coap_test_msg_t *notify_response0 =\n            COAP_MSG(NON, CONTENT, ID_TOKEN(MSG_ID_BASE, \"Res4\"), OBSERVE(1),\n                     CONTENT_FORMAT(PLAINTEXT), PAYLOAD(\"524\"));\n    avs_unit_mocksock_expect_output(mocksocks[0], notify_response0->content,\n                                    notify_response0->length);\n    anjay_sched_run(anjay);\n    assert_observe_consistency(anjay);\n    assert_observe_size(anjay, 1);\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    AVS_UNIT_ASSERT_NOT_NULL(\n            AVS_SORTED_SET_FIRST(\n                    anjay_unlocked->observe.connection_entries->observations)\n                    ->notify_task);\n    ANJAY_MUTEX_UNLOCK(anjay);\n\n    ////// INCREASE BY OVER stp //////\n    expect_read_res_attrs(anjay, &OBJ, 14, 69, 4, &ATTRS);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_notify_changed(anjay, 42, 69, 4));\n    expect_read_res_attrs(anjay, &OBJ, 14, 69, 4, &ATTRS);\n    expect_read_res(anjay, &OBJ, 69, 4, ANJAY_MOCK_DM_FLOAT(0, 540.048));\n    const coap_test_msg_t *notify_response1 =\n            COAP_MSG(NON, CONTENT, ID_TOKEN(MSG_ID_BASE + 1, \"Res4\"),\n                     OBSERVE(2), CONTENT_FORMAT(PLAINTEXT), PAYLOAD(\"540.048\"));\n    avs_unit_mocksock_expect_output(mocksocks[0], notify_response1->content,\n                                    notify_response1->length);\n    anjay_sched_run(anjay);\n    assert_observe_consistency(anjay);\n    assert_observe_size(anjay, 1);\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    AVS_UNIT_ASSERT_NOT_NULL(\n            AVS_SORTED_SET_FIRST(\n                    anjay_unlocked->observe.connection_entries->observations)\n                    ->notify_task);\n    ANJAY_MUTEX_UNLOCK(anjay);\n\n    ////// NON-NUMERIC VALUE //////\n    expect_read_res_attrs(anjay, &OBJ, 14, 69, 4, &ATTRS);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_notify_changed(anjay, 42, 69, 4));\n    expect_read_res_attrs(anjay, &OBJ, 14, 69, 4, &ATTRS);\n    expect_read_res(anjay, &OBJ, 69, 4, ANJAY_MOCK_DM_STRING(0, \"trololo\"));\n    const coap_test_msg_t *notify_response2 =\n            COAP_MSG(NON, CONTENT, ID_TOKEN(MSG_ID_BASE + 2, \"Res4\"),\n                     OBSERVE(3), CONTENT_FORMAT(PLAINTEXT), PAYLOAD(\"trololo\"));\n    avs_unit_mocksock_expect_output(mocksocks[0], notify_response2->content,\n                                    notify_response2->length);\n    anjay_sched_run(anjay);\n    assert_observe_consistency(anjay);\n    assert_observe_size(anjay, 1);\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    AVS_UNIT_ASSERT_NOT_NULL(\n            AVS_SORTED_SET_FIRST(\n                    anjay_unlocked->observe.connection_entries->observations)\n                    ->notify_task);\n    ANJAY_MUTEX_UNLOCK(anjay);\n\n    ////// BACK TO NUMBERS //////\n    expect_read_res_attrs(anjay, &OBJ, 14, 69, 4, &ATTRS);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_notify_changed(anjay, 42, 69, 4));\n    expect_read_res_attrs(anjay, &OBJ, 14, 69, 4, &ATTRS);\n    expect_read_res(anjay, &OBJ, 69, 4, ANJAY_MOCK_DM_INT(0, 42));\n    const coap_test_msg_t *notify_response3 =\n            COAP_MSG(NON, CONTENT, ID_TOKEN(MSG_ID_BASE + 3, \"Res4\"),\n                     OBSERVE(4), CONTENT_FORMAT(PLAINTEXT), PAYLOAD(\"42\"));\n    avs_unit_mocksock_expect_output(mocksocks[0], notify_response3->content,\n                                    notify_response3->length);\n    anjay_sched_run(anjay);\n    assert_observe_consistency(anjay);\n    assert_observe_size(anjay, 1);\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    AVS_UNIT_ASSERT_NOT_NULL(\n            AVS_SORTED_SET_FIRST(\n                    anjay_unlocked->observe.connection_entries->observations)\n                    ->notify_task);\n    ANJAY_MUTEX_UNLOCK(anjay);\n\n    ////// TOO LITTLE DECREASE //////\n    expect_read_res_attrs(anjay, &OBJ, 14, 69, 4, &ATTRS);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_notify_changed(anjay, 42, 69, 4));\n    expect_read_res_attrs(anjay, &OBJ, 14, 69, 4, &ATTRS);\n    expect_read_res(anjay, &OBJ, 69, 4, ANJAY_MOCK_DM_FLOAT(0, 32.001));\n    anjay_sched_run(anjay);\n    assert_observe_consistency(anjay);\n    assert_observe_size(anjay, 1);\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    AVS_UNIT_ASSERT_NOT_NULL(\n            AVS_SORTED_SET_FIRST(\n                    anjay_unlocked->observe.connection_entries->observations)\n                    ->notify_task);\n    ANJAY_MUTEX_UNLOCK(anjay);\n\n    ////// DECREASE BY EXACTLY stp //////\n    expect_read_res_attrs(anjay, &OBJ, 14, 69, 4, &ATTRS);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_notify_changed(anjay, 42, 69, 4));\n    expect_read_res_attrs(anjay, &OBJ, 14, 69, 4, &ATTRS);\n    expect_read_res(anjay, &OBJ, 69, 4, ANJAY_MOCK_DM_INT(0, 31));\n    const coap_test_msg_t *notify_response4 =\n            COAP_MSG(NON, CONTENT, ID_TOKEN(MSG_ID_BASE + 4, \"Res4\"),\n                     OBSERVE(5), CONTENT_FORMAT(PLAINTEXT), PAYLOAD(\"31\"));\n    avs_unit_mocksock_expect_output(mocksocks[0], notify_response4->content,\n                                    notify_response4->length);\n    anjay_sched_run(anjay);\n    assert_observe_consistency(anjay);\n    assert_observe_size(anjay, 1);\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    AVS_UNIT_ASSERT_NOT_NULL(\n            AVS_SORTED_SET_FIRST(\n                    anjay_unlocked->observe.connection_entries->observations)\n                    ->notify_task);\n    ANJAY_MUTEX_UNLOCK(anjay);\n\n    ////// DECREASE BY MORE THAN stp //////\n    expect_read_res_attrs(anjay, &OBJ, 14, 69, 4, &ATTRS);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_notify_changed(anjay, 42, 69, 4));\n    expect_read_res_attrs(anjay, &OBJ, 14, 69, 4, &ATTRS);\n    expect_read_res(anjay, &OBJ, 69, 4, ANJAY_MOCK_DM_INT(0, 20));\n    const coap_test_msg_t *notify_response5 =\n            COAP_MSG(NON, CONTENT, ID_TOKEN(MSG_ID_BASE + 5, \"Res4\"),\n                     OBSERVE(6), CONTENT_FORMAT(PLAINTEXT), PAYLOAD(\"20\"));\n    avs_unit_mocksock_expect_output(mocksocks[0], notify_response5->content,\n                                    notify_response5->length);\n    anjay_sched_run(anjay);\n    assert_observe_consistency(anjay);\n    assert_observe_size(anjay, 1);\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    AVS_UNIT_ASSERT_NOT_NULL(\n            AVS_SORTED_SET_FIRST(\n                    anjay_unlocked->observe.connection_entries->observations)\n                    ->notify_task);\n    ANJAY_MUTEX_UNLOCK(anjay);\n\n    ////// INCREASE BY EXACTLY stp //////\n    expect_read_res_attrs(anjay, &OBJ, 14, 69, 4, &ATTRS);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_notify_changed(anjay, 42, 69, 4));\n    expect_read_res_attrs(anjay, &OBJ, 14, 69, 4, &ATTRS);\n    expect_read_res(anjay, &OBJ, 69, 4, ANJAY_MOCK_DM_INT(0, 30));\n    const coap_test_msg_t *notify_response6 =\n            COAP_MSG(NON, CONTENT, ID_TOKEN(MSG_ID_BASE + 6, \"Res4\"),\n                     OBSERVE(7), CONTENT_FORMAT(PLAINTEXT), PAYLOAD(\"30\"));\n    avs_unit_mocksock_expect_output(mocksocks[0], notify_response6->content,\n                                    notify_response6->length);\n    anjay_sched_run(anjay);\n    assert_observe_consistency(anjay);\n    assert_observe_size(anjay, 1);\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    AVS_UNIT_ASSERT_NOT_NULL(\n            AVS_SORTED_SET_FIRST(\n                    anjay_unlocked->observe.connection_entries->observations)\n                    ->notify_task);\n    ANJAY_MUTEX_UNLOCK(anjay);\n\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(notify, multiple_formats) {\n    static const anjay_dm_r_attributes_t ATTRS = {\n        .common = {\n            .min_period = 1,\n            .max_period = 10,\n            .min_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n            .max_eval_period = ANJAY_ATTRIB_INTEGER_NONE\n#ifdef ANJAY_WITH_LWM2M12\n            ,\n            .hqmax = ANJAY_ATTRIB_INTEGER_NONE\n#endif // ANJAY_WITH_LWM2M12\n        },\n        .greater_than = ANJAY_ATTRIB_DOUBLE_NONE,\n        .less_than = ANJAY_ATTRIB_DOUBLE_NONE,\n        .step = ANJAY_ATTRIB_DOUBLE_NONE\n#ifdef ANJAY_WITH_LWM2M12\n        ,\n        .edge = ANJAY_DM_EDGE_ATTR_NONE\n#endif // ANJAY_WITH_LWM2M12\n    };\n\n    ////// INITIALIZATION //////\n    DM_TEST_INIT_WITH_SSIDS(14);\n    // Token: N\n    DM_TEST_REQUEST(mocksocks[0], CON, GET, ID_TOKEN(0x69ED, \"N\"), OBSERVE(0),\n                    PATH(\"42\", \"69\", \"4\"));\n    expect_read_res(anjay, &OBJ, 69, 4, ANJAY_MOCK_DM_FLOAT(0, 514.0));\n    expect_read_res_attrs(anjay, &OBJ, 14, 69, 4, &ATTRS);\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, CONTENT, ID_TOKEN(0x69ED, \"N\"),\n                            CONTENT_FORMAT(PLAINTEXT), OBSERVE(0),\n                            PAYLOAD(\"514\"));\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    // Token: P\n    DM_TEST_REQUEST(mocksocks[0], CON, GET, ID_TOKEN(0x69ED, \"P\"), OBSERVE(0),\n                    ACCEPT(AVS_COAP_FORMAT_PLAINTEXT), PATH(\"42\", \"69\", \"4\"));\n    expect_read_res(anjay, &OBJ, 69, 4, ANJAY_MOCK_DM_FLOAT(0, 514.0));\n    expect_read_res_attrs(anjay, &OBJ, 14, 69, 4, &ATTRS);\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, CONTENT, ID_TOKEN(0x69ED, \"P\"),\n                            CONTENT_FORMAT(PLAINTEXT), OBSERVE(0),\n                            PAYLOAD(\"514\"));\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n    // Token: T\n    DM_TEST_REQUEST(mocksocks[0], CON, GET, ID_TOKEN(0x69ED, \"T\"),\n                    ACCEPT(AVS_COAP_FORMAT_OMA_LWM2M_TLV), OBSERVE(0),\n                    PATH(\"42\", \"69\", \"4\"));\n    expect_read_res(anjay, &OBJ, 69, 4, ANJAY_MOCK_DM_FLOAT(0, 514.0));\n    expect_read_res_attrs(anjay, &OBJ, 14, 69, 4, &ATTRS);\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, CONTENT, ID_TOKEN(0x69ED, \"T\"),\n                            CONTENT_FORMAT(OMA_LWM2M_TLV), OBSERVE(0),\n                            PAYLOAD(\"\\xC4\\x04\\x44\\x00\\x80\\x00\"));\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n\n    assert_observe_size(anjay, 3);\n\n    ////// NOTIFICATION //////\n    _anjay_mock_clock_advance(avs_time_duration_from_scalar(10, AVS_TIME_S));\n    // no format preference\n    expect_read_res_attrs(anjay, &OBJ, 14, 69, 4, &ATTRS);\n    expect_read_res(anjay, &OBJ, 69, 4, ANJAY_MOCK_DM_STRING(0, \"Hello\"));\n    const coap_test_msg_t *n_notify_response =\n            COAP_MSG(NON, CONTENT, ID_TOKEN(MSG_ID_BASE, \"N\"), OBSERVE(1),\n                     CONTENT_FORMAT(PLAINTEXT), PAYLOAD(\"Hello\"));\n    avs_unit_mocksock_expect_output(mocksocks[0], n_notify_response->content,\n                                    n_notify_response->length);\n    // plaintext\n    expect_read_res_attrs(anjay, &OBJ, 14, 69, 4, &ATTRS);\n    expect_read_res(anjay, &OBJ, 69, 4, ANJAY_MOCK_DM_STRING(0, \"Hello\"));\n    const coap_test_msg_t *p_notify_response =\n            COAP_MSG(NON, CONTENT, ID_TOKEN(MSG_ID_BASE + 1, \"P\"), OBSERVE(1),\n                     CONTENT_FORMAT(PLAINTEXT), PAYLOAD(\"Hello\"));\n    avs_unit_mocksock_expect_output(mocksocks[0], p_notify_response->content,\n                                    p_notify_response->length);\n    // TLV\n    expect_read_res_attrs(anjay, &OBJ, 14, 69, 4, &ATTRS);\n    expect_read_res(anjay, &OBJ, 69, 4, ANJAY_MOCK_DM_STRING(0, \"Hello\"));\n    const coap_test_msg_t *t_notify_response =\n            COAP_MSG(NON, CONTENT, ID_TOKEN(MSG_ID_BASE + 2, \"T\"), OBSERVE(1),\n                     CONTENT_FORMAT(OMA_LWM2M_TLV),\n                     PAYLOAD(\"\\xc5\\x04\"\n                             \"Hello\"));\n    avs_unit_mocksock_expect_output(mocksocks[0], t_notify_response->content,\n                                    t_notify_response->length);\n    anjay_sched_run(anjay);\n    assert_observe_consistency(anjay);\n    assert_observe_size(anjay, 3);\n\n    ////// NOTIFICATION - FORMAT CHANGE //////\n    _anjay_mock_clock_advance(avs_time_duration_from_scalar(10, AVS_TIME_S));\n    // no format preference - uses previous format (plaintext)\n    expect_read_res_attrs(anjay, &OBJ, 14, 69, 4, &ATTRS);\n    expect_read_res(anjay, &OBJ, 69, 4,\n                    ANJAY_MOCK_DM_BYTES(0, \"\\x12\\x34\\x56\\x78\"));\n    const coap_test_msg_t *n_bytes_response =\n            COAP_MSG(NON, CONTENT, ID_TOKEN(MSG_ID_BASE + 3, \"N\"), OBSERVE(2),\n                     CONTENT_FORMAT(PLAINTEXT), PAYLOAD(\"EjRWeA==\"));\n    avs_unit_mocksock_expect_output(mocksocks[0], n_bytes_response->content,\n                                    n_bytes_response->length);\n    // plaintext\n    expect_read_res_attrs(anjay, &OBJ, 14, 69, 4, &ATTRS);\n    expect_read_res(anjay, &OBJ, 69, 4,\n                    ANJAY_MOCK_DM_BYTES(0, \"\\x12\\x34\\x56\\x78\"));\n    const coap_test_msg_t *p_bytes_response =\n            COAP_MSG(NON, CONTENT, ID_TOKEN(MSG_ID_BASE + 4, \"P\"), OBSERVE(2),\n                     CONTENT_FORMAT(PLAINTEXT), PAYLOAD(\"EjRWeA==\"));\n    avs_unit_mocksock_expect_output(mocksocks[0], p_bytes_response->content,\n                                    p_bytes_response->length);\n    // TLV\n    expect_read_res_attrs(anjay, &OBJ, 14, 69, 4, &ATTRS);\n    expect_read_res(anjay, &OBJ, 69, 4,\n                    ANJAY_MOCK_DM_BYTES(0, \"\\x12\\x34\\x56\\x78\"));\n    const coap_test_msg_t *t_bytes_response =\n            COAP_MSG(NON, CONTENT, ID_TOKEN(MSG_ID_BASE + 5, \"T\"), OBSERVE(2),\n                     CONTENT_FORMAT(OMA_LWM2M_TLV),\n                     PAYLOAD(\"\\xc4\\x04\"\n                             \"\\x12\\x34\\x56\\x78\"));\n    avs_unit_mocksock_expect_output(mocksocks[0], t_bytes_response->content,\n                                    t_bytes_response->length);\n    anjay_sched_run(anjay);\n    assert_observe_consistency(anjay);\n    assert_observe_size(anjay, 3);\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(notify, storing_when_inactive) {\n    SUCCESS_TEST(14, 34);\n    anjay_server_connection_t *connection;\n    avs_net_socket_t *socket14;\n\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    connection = _anjay_get_server_connection((const anjay_connection_ref_t) {\n        .server = anjay_unlocked->servers,\n        .conn_type = ANJAY_CONNECTION_PRIMARY\n    });\n    AVS_UNIT_ASSERT_NOT_NULL(connection);\n\n    // deactivate the first server\n    socket14 = connection->conn_socket_;\n    connection->conn_socket_ = NULL;\n    _anjay_observe_gc(anjay_unlocked);\n    ANJAY_MUTEX_UNLOCK(anjay);\n\n    assert_observe_consistency(anjay);\n    assert_observe_size(anjay, 2);\n\n    // first notification\n    DM_TEST_EXPECT_READ_NULL_ATTRS(14, 69, 4);\n    DM_TEST_EXPECT_READ_NULL_ATTRS(34, 69, 4);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_notify_changed(anjay, 42, 69, 4));\n\n    _anjay_mock_clock_advance(avs_time_duration_from_scalar(1, AVS_TIME_S));\n\n    expect_read_notif_storing(anjay, &FAKE_SERVER, 14, true);\n    DM_TEST_EXPECT_READ_NULL_ATTRS(14, 69, 4);\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0, (const anjay_iid_t[]) { 69, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 69, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                    { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read(anjay, &OBJ, 69, 4, ANJAY_ID_INVALID, 0,\n                                        ANJAY_MOCK_DM_STRING(0, \"Rin\"));\n\n    DM_TEST_EXPECT_READ_NULL_ATTRS(34, 69, 4);\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0, (const anjay_iid_t[]) { 69, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 69, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                    { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read(anjay, &OBJ, 69, 4, ANJAY_ID_INVALID, 0,\n                                        ANJAY_MOCK_DM_STRING(0, \"Len\"));\n    const coap_test_msg_t *notify_response =\n            COAP_MSG(NON, CONTENT, ID_TOKEN(MSG_ID_BASE, \"SuccsTkn\"),\n                     OBSERVE(1), CONTENT_FORMAT(PLAINTEXT), PAYLOAD(\"Len\"));\n    avs_unit_mocksock_expect_output(mocksocks[1], notify_response->content,\n\n                                    notify_response->length);\n    DM_TEST_EXPECT_READ_NULL_ATTRS(34, 69, 4);\n    anjay_sched_run(anjay);\n\n    // second notification\n    DM_TEST_EXPECT_READ_NULL_ATTRS(14, 69, 4);\n    DM_TEST_EXPECT_READ_NULL_ATTRS(34, 69, 4);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_notify_changed(anjay, 42, 69, 4));\n\n    _anjay_mock_clock_advance(avs_time_duration_from_scalar(1, AVS_TIME_S));\n\n    expect_read_notif_storing(anjay, &FAKE_SERVER, 14, true);\n    DM_TEST_EXPECT_READ_NULL_ATTRS(14, 69, 4);\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0, (const anjay_iid_t[]) { 69, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 69, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                    { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read(anjay, &OBJ, 69, 4, ANJAY_ID_INVALID, 0,\n                                        ANJAY_MOCK_DM_STRING(0, \"Miku\"));\n\n    DM_TEST_EXPECT_READ_NULL_ATTRS(34, 69, 4);\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0, (const anjay_iid_t[]) { 69, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 69, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                    { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read(anjay, &OBJ, 69, 4, ANJAY_ID_INVALID, 0,\n                                        ANJAY_MOCK_DM_STRING(0, \"Luka\"));\n    const coap_test_msg_t *notify_response2 =\n            COAP_MSG(NON, CONTENT, ID_TOKEN(MSG_ID_BASE + 1, \"SuccsTkn\"),\n                     OBSERVE(2), CONTENT_FORMAT(PLAINTEXT), PAYLOAD(\"Luka\"));\n    avs_unit_mocksock_expect_output(mocksocks[1], notify_response2->content,\n                                    notify_response2->length);\n    DM_TEST_EXPECT_READ_NULL_ATTRS(34, 69, 4);\n    anjay_sched_run(anjay);\n\n    // reactivate the server\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    connection->conn_socket_ = socket14;\n    _anjay_observe_gc(anjay_unlocked);\n    ANJAY_MUTEX_UNLOCK(anjay);\n    assert_observe_consistency(anjay);\n    assert_observe_size(anjay, 2);\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    _anjay_observe_sched_flush((anjay_connection_ref_t) {\n        .server = anjay_unlocked->servers,\n        .conn_type = ANJAY_CONNECTION_PRIMARY\n    });\n    ANJAY_MUTEX_UNLOCK(anjay);\n\n    const coap_test_msg_t *notify_response3 =\n            COAP_MSG(NON, CONTENT, ID_TOKEN(0x0000, \"SuccsTkn\"), OBSERVE(1),\n                     CONTENT_FORMAT(PLAINTEXT), PAYLOAD(\"Rin\"));\n    avs_unit_mocksock_expect_output(mocksocks[0], notify_response3->content,\n                                    notify_response3->length);\n\n    const coap_test_msg_t *notify_response4 =\n            COAP_MSG(NON, CONTENT, ID_TOKEN(0x0001, \"SuccsTkn\"), OBSERVE(2),\n                     CONTENT_FORMAT(PLAINTEXT), PAYLOAD(\"Miku\"));\n    avs_unit_mocksock_expect_output(mocksocks[0], notify_response4->content,\n                                    notify_response4->length);\n    DM_TEST_EXPECT_READ_NULL_ATTRS(14, 69, 4);\n    anjay_sched_run(anjay);\n\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(notify, no_storing_when_disabled) {\n    SUCCESS_TEST(14, 34);\n    anjay_server_connection_t *connection;\n    avs_net_socket_t *socket14;\n\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    connection = _anjay_get_server_connection((const anjay_connection_ref_t) {\n        .server = anjay_unlocked->servers,\n        .conn_type = ANJAY_CONNECTION_PRIMARY\n    });\n    AVS_UNIT_ASSERT_NOT_NULL(connection);\n\n    // deactivate the first server\n    socket14 = connection->conn_socket_;\n    connection->conn_socket_ = NULL;\n    _anjay_observe_gc(anjay_unlocked);\n    ANJAY_MUTEX_UNLOCK(anjay);\n\n    assert_observe_consistency(anjay);\n    assert_observe_size(anjay, 2);\n\n    // first notification\n    DM_TEST_EXPECT_READ_NULL_ATTRS(14, 69, 4);\n    DM_TEST_EXPECT_READ_NULL_ATTRS(34, 69, 4);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_notify_changed(anjay, 42, 69, 4));\n\n    _anjay_mock_clock_advance(avs_time_duration_from_scalar(1, AVS_TIME_S));\n\n    expect_read_notif_storing(anjay, &FAKE_SERVER, 14, false);\n    DM_TEST_EXPECT_READ_NULL_ATTRS(34, 69, 4);\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0, (const anjay_iid_t[]) { 69, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 69, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                    { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read(anjay, &OBJ, 69, 4, ANJAY_ID_INVALID, 0,\n                                        ANJAY_MOCK_DM_STRING(0, \"Ia\"));\n    const coap_test_msg_t *notify_response =\n            COAP_MSG(NON, CONTENT, ID_TOKEN(MSG_ID_BASE, \"SuccsTkn\"),\n                     OBSERVE(1), CONTENT_FORMAT(PLAINTEXT), PAYLOAD(\"Ia\"));\n    avs_unit_mocksock_expect_output(mocksocks[1], notify_response->content,\n                                    notify_response->length);\n    DM_TEST_EXPECT_READ_NULL_ATTRS(34, 69, 4);\n    anjay_sched_run(anjay);\n\n    // second notification\n    DM_TEST_EXPECT_READ_NULL_ATTRS(14, 69, 4);\n    DM_TEST_EXPECT_READ_NULL_ATTRS(34, 69, 4);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_notify_changed(anjay, 42, 69, 4));\n\n    _anjay_mock_clock_advance(avs_time_duration_from_scalar(1, AVS_TIME_S));\n\n    expect_read_notif_storing(anjay, &FAKE_SERVER, 14, false);\n    DM_TEST_EXPECT_READ_NULL_ATTRS(34, 69, 4);\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0, (const anjay_iid_t[]) { 69, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 69, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                    { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read(anjay, &OBJ, 69, 4, ANJAY_ID_INVALID, 0,\n                                        ANJAY_MOCK_DM_STRING(0, \"Gumi\"));\n    const coap_test_msg_t *notify_response2 =\n            COAP_MSG(NON, CONTENT, ID_TOKEN(MSG_ID_BASE + 1, \"SuccsTkn\"),\n                     OBSERVE(2), CONTENT_FORMAT(PLAINTEXT), PAYLOAD(\"Gumi\"));\n    avs_unit_mocksock_expect_output(mocksocks[1], notify_response2->content,\n                                    notify_response2->length);\n    DM_TEST_EXPECT_READ_NULL_ATTRS(34, 69, 4);\n    anjay_sched_run(anjay);\n\n    // reactivate the server\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    connection->conn_socket_ = socket14;\n    _anjay_observe_gc(anjay_unlocked);\n    ANJAY_MUTEX_UNLOCK(anjay);\n    assert_observe_consistency(anjay);\n    assert_observe_size(anjay, 2);\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    DM_TEST_EXPECT_READ_NULL_ATTRS(14, 69, 4);\n    _anjay_observe_sched_flush((anjay_connection_ref_t) {\n        .server = anjay_unlocked->servers,\n        .conn_type = ANJAY_CONNECTION_PRIMARY\n    });\n    ANJAY_MUTEX_UNLOCK(anjay);\n\n    anjay_sched_run(anjay);\n\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(notify, storing_on_send_error) {\n    SUCCESS_TEST(14);\n\n    // first notification\n    DM_TEST_EXPECT_READ_NULL_ATTRS(14, 69, 4);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_notify_changed(anjay, 42, 69, 4));\n\n    _anjay_mock_clock_advance(avs_time_duration_from_scalar(1, AVS_TIME_S));\n\n    DM_TEST_EXPECT_READ_NULL_ATTRS(14, 69, 4);\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0, (const anjay_iid_t[]) { 69, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 69, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                    { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read(anjay, &OBJ, 69, 4, ANJAY_ID_INVALID, 0,\n                                        ANJAY_MOCK_DM_STRING(0, \"Meiko\"));\n    // this is a hack: all other errno values trigger reconnection\n    avs_unit_mocksock_output_fail(mocksocks[0], avs_errno(AVS_EMSGSIZE));\n    expect_read_notif_storing(anjay, &FAKE_SERVER, 14, true);\n    anjay_sched_run(anjay);\n\n    // second notification\n    DM_TEST_EXPECT_READ_NULL_ATTRS(14, 69, 4);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_notify_changed(anjay, 42, 69, 4));\n\n    _anjay_mock_clock_advance(avs_time_duration_from_scalar(1, AVS_TIME_S));\n\n    DM_TEST_EXPECT_READ_NULL_ATTRS(14, 69, 4);\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0, (const anjay_iid_t[]) { 69, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 69, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                    { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read(anjay, &OBJ, 69, 4, ANJAY_ID_INVALID, 0,\n                                        ANJAY_MOCK_DM_STRING(0, \"Kaito\"));\n    avs_unit_mocksock_output_fail(mocksocks[0], avs_errno(AVS_EMSGSIZE));\n    expect_read_notif_storing(anjay, &FAKE_SERVER, 14, true);\n    anjay_sched_run(anjay);\n\n    // anjay_serve() will reschedule notification sending\n    DM_TEST_REQUEST(mocksocks[0], CON, GET, ID(0xFB3E), PATH(\"42\", \"69\", \"3\"));\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0, (const anjay_iid_t[]) { 69, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 69, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                    { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read(anjay, &OBJ, 69, 3, ANJAY_ID_INVALID, 0,\n                                        ANJAY_MOCK_DM_STRING(0, \"Mayu\"));\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, CONTENT, ID(0xFB3E),\n                            CONTENT_FORMAT(PLAINTEXT), PAYLOAD(\"Mayu\"));\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n\n    // now the notifications shall arrive\n    const coap_test_msg_t *notify_response =\n            COAP_MSG(NON, CONTENT, ID_TOKEN(MSG_ID_BASE + 2, \"SuccsTkn\"),\n                     OBSERVE(3), CONTENT_FORMAT(PLAINTEXT), PAYLOAD(\"Meiko\"));\n    avs_unit_mocksock_expect_output(mocksocks[0], notify_response->content,\n                                    notify_response->length);\n\n    const coap_test_msg_t *notify_response2 =\n            COAP_MSG(NON, CONTENT, ID_TOKEN(MSG_ID_BASE + 3, \"SuccsTkn\"),\n                     OBSERVE(4), CONTENT_FORMAT(PLAINTEXT), PAYLOAD(\"Kaito\"));\n    avs_unit_mocksock_expect_output(mocksocks[0], notify_response2->content,\n                                    notify_response2->length);\n    DM_TEST_EXPECT_READ_NULL_ATTRS(14, 69, 4);\n    anjay_sched_run(anjay);\n\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(notify, no_storing_on_send_error) {\n    SUCCESS_TEST(14);\n\n    // first notification\n    DM_TEST_EXPECT_READ_NULL_ATTRS(14, 69, 4);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_notify_changed(anjay, 42, 69, 4));\n\n    _anjay_mock_clock_advance(avs_time_duration_from_scalar(1, AVS_TIME_S));\n\n    // let's leave storing on for a moment\n    DM_TEST_EXPECT_READ_NULL_ATTRS(14, 69, 4);\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0, (const anjay_iid_t[]) { 69, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 69, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                    { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read(anjay, &OBJ, 69, 4, ANJAY_ID_INVALID, 0,\n                                        ANJAY_MOCK_DM_STRING(0, \"Meiko\"));\n    avs_unit_mocksock_output_fail(mocksocks[0], avs_errno(AVS_EMSGSIZE));\n    expect_read_notif_storing(anjay, &FAKE_SERVER, 14, true);\n    anjay_sched_run(anjay);\n\n    // second notification\n    DM_TEST_EXPECT_READ_NULL_ATTRS(14, 69, 4);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_notify_changed(anjay, 42, 69, 4));\n\n    _anjay_mock_clock_advance(avs_time_duration_from_scalar(1, AVS_TIME_S));\n\n    // and now we have it disabled\n    DM_TEST_EXPECT_READ_NULL_ATTRS(14, 69, 4);\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0, (const anjay_iid_t[]) { 69, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 69, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                    { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read(anjay, &OBJ, 69, 4, ANJAY_ID_INVALID, 0,\n                                        ANJAY_MOCK_DM_STRING(0, \"Kaito\"));\n    avs_unit_mocksock_output_fail(mocksocks[0], avs_errno(AVS_EMSGSIZE));\n    expect_read_notif_storing(anjay, &FAKE_SERVER, 14, false);\n    anjay_sched_run(anjay);\n\n    // anjay_serve() will reschedule notification sending...\n    DM_TEST_REQUEST(mocksocks[0], CON, GET, ID(0xFB3E), PATH(\"42\", \"69\", \"3\"));\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0, (const anjay_iid_t[]) { 69, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 69, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                    { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read(anjay, &OBJ, 69, 3, ANJAY_ID_INVALID, 0,\n                                        ANJAY_MOCK_DM_STRING(0, \"Mayu\"));\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, CONTENT, ID(0xFB3E),\n                            CONTENT_FORMAT(PLAINTEXT), PAYLOAD(\"Mayu\"));\n    expect_has_buffered_data_check(mocksocks[0], false);\n    DM_TEST_EXPECT_READ_NULL_ATTRS(14, 69, 4);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n\n    // ...but nothing should come\n    anjay_sched_run(anjay);\n\n    DM_TEST_FINISH;\n}\n\nstatic void storing_of_errors_test_impl(bool storing_resource_value) {\n    SUCCESS_TEST(14);\n\n    // first notification\n    DM_TEST_EXPECT_READ_NULL_ATTRS(14, 69, 4);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_notify_changed(anjay, 42, 69, 4));\n\n    _anjay_mock_clock_advance(avs_time_duration_from_scalar(1, AVS_TIME_S));\n\n    // error during reading\n    DM_TEST_EXPECT_READ_NULL_ATTRS(14, 69, 4);\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, -1, (const anjay_iid_t[]) { 69, ANJAY_ID_INVALID });\n    avs_unit_mocksock_output_fail(mocksocks[0], avs_errno(AVS_EMSGSIZE));\n    expect_read_notif_storing(anjay, &FAKE_SERVER, 14, storing_resource_value);\n    anjay_sched_run(anjay);\n\n    // second notification - should not actually do anything\n    DM_TEST_EXPECT_READ_NULL_ATTRS(14, 69, 4);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_notify_changed(anjay, 42, 69, 4));\n\n    _anjay_mock_clock_advance(avs_time_duration_from_scalar(1, AVS_TIME_S));\n\n    // sending is now scheduled, should receive the previous error\n    const coap_test_msg_t *con_notify_response =\n            COAP_MSG(CON, INTERNAL_SERVER_ERROR,\n                     ID_TOKEN(MSG_ID_BASE + 1, \"SuccsTkn\"), NO_PAYLOAD);\n    avs_unit_mocksock_expect_output(mocksocks[0], con_notify_response->content,\n                                    con_notify_response->length);\n    anjay_sched_run(anjay);\n\n    const coap_test_msg_t *con_ack =\n            COAP_MSG(ACK, EMPTY, ID(MSG_ID_BASE + 1), NO_PAYLOAD);\n    avs_unit_mocksock_input(mocksocks[0], con_ack->content, con_ack->length);\n    expect_has_buffered_data_check(mocksocks[0], false);\n    anjay_serve(anjay, mocksocks[0]);\n\n    // now the notification shall be gone\n    assert_observe_consistency(anjay);\n    assert_observe_size(anjay, 0);\n\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(notify, storing_of_errors) {\n    storing_of_errors_test_impl(true);\n}\n\nAVS_UNIT_TEST(notify, no_storing_of_errors) {\n    // As a special exception, notification storing is always enabled for\n    // errors, regardless of the actual setting.\n    storing_of_errors_test_impl(false);\n}\n\nAVS_UNIT_TEST(notify, send_error) {\n    SUCCESS_TEST(14);\n\n    // first notification\n    DM_TEST_EXPECT_READ_NULL_ATTRS(14, 69, 4);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_notify_changed(anjay, 42, 69, 4));\n\n    _anjay_mock_clock_advance(avs_time_duration_from_scalar(1, AVS_TIME_S));\n\n    // let's leave storing on for a moment\n    DM_TEST_EXPECT_READ_NULL_ATTRS(14, 69, 4);\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ, 0, (const anjay_iid_t[]) { 69, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &OBJ, 69, 0,\n            (const anjay_mock_dm_res_entry_t[]) {\n                    { 0, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 1, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 2, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 3, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 4, ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT },\n                    { 5, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    { 6, ANJAY_DM_RES_RW, ANJAY_DM_RES_ABSENT },\n                    ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read(anjay, &OBJ, 69, 4, ANJAY_ID_INVALID, 0,\n                                        ANJAY_MOCK_DM_STRING(0, \"Meiko\"));\n    avs_unit_mocksock_output_fail(mocksocks[0], avs_errno(AVS_ECONNRESET));\n\n    _anjay_mocksock_expect_stats_zero(mocksocks[0]);\n#if defined(ANJAY_WITH_LWM2M11) && defined(ANJAY_WITH_BOOTSTRAP)\n    // Anjay will attempt to read \"Bootstrap on Registration Failure\" resource\n    _anjay_mock_dm_expect_list_instances(anjay, &FAKE_SERVER, -1,\n                                         (const anjay_iid_t[]) {\n                                                 ANJAY_ID_INVALID });\n#endif // defined(ANJAY_WITH_LWM2M11) && defined(ANJAY_WITH_BOOTSTRAP)\n    anjay_sched_run(anjay);\n\n    DM_TEST_FINISH;\n}\n\n#ifdef ANJAY_WITH_OBSERVATION_STATUS\nAVS_UNIT_TEST(observe_status, no_observations) {\n    SUCCESS_TEST(14);\n    const anjay_uri_path_t path = MAKE_RESOURCE_PATH(1, 0, 1);\n    anjay_resource_observation_status_t result;\n\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    result = _anjay_observe_status(anjay_unlocked, &path);\n    ANJAY_MUTEX_UNLOCK(anjay);\n\n    AVS_UNIT_ASSERT_FALSE(result.is_observed);\n    AVS_UNIT_ASSERT_EQUAL(result.min_period, 0);\n    AVS_UNIT_ASSERT_EQUAL(result.max_eval_period, ANJAY_ATTRIB_INTEGER_NONE);\n\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(observe_status, observed_path) {\n    static const anjay_dm_r_attributes_t ATTRS = {\n        .common = {\n            .min_period = 10,\n            .max_period = 365 * 24 * 60 * 60 /* a year */,\n            .min_eval_period = ANJAY_ATTRIB_INTEGER_NONE,\n            .max_eval_period = 360\n#    ifdef ANJAY_WITH_LWM2M12\n            ,\n            .hqmax = ANJAY_ATTRIB_INTEGER_NONE\n#    endif // ANJAY_WITH_LWM2M12\n        },\n        .greater_than = ANJAY_ATTRIB_DOUBLE_NONE,\n        .less_than = ANJAY_ATTRIB_DOUBLE_NONE,\n        .step = ANJAY_ATTRIB_DOUBLE_NONE\n#    ifdef ANJAY_WITH_LWM2M12\n        ,\n        .edge = ANJAY_DM_EDGE_ATTR_NONE\n#    endif // ANJAY_WITH_LWM2M12\n    };\n\n    ////// INITIALIZATION //////\n    DM_TEST_INIT_WITH_SSIDS(14);\n    DM_TEST_REQUEST(mocksocks[0], CON, GET, ID_TOKEN(0x69ED, \"Res4\"),\n                    OBSERVE(0), PATH(\"42\", \"69\", \"4\"));\n    expect_read_res(anjay, &OBJ, 69, 4, ANJAY_MOCK_DM_FLOAT(0, 514.0));\n    expect_read_res_attrs(anjay, &OBJ, 14, 69, 4, &ATTRS);\n    DM_TEST_EXPECT_RESPONSE(mocksocks[0], ACK, CONTENT,\n                            ID_TOKEN(0x69ED, \"Res4\"), CONTENT_FORMAT(PLAINTEXT),\n                            OBSERVE(0), PAYLOAD(\"514\"));\n    expect_has_buffered_data_check(mocksocks[0], false);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_serve(anjay, mocksocks[0]));\n\n    anjay_sched_run(anjay);\n    assert_observe_consistency(anjay);\n    assert_observe_size(anjay, 1);\n\n    anjay_resource_observation_status_t result;\n    expect_read_res_attrs(anjay, &OBJ, 14, 69, 4, &ATTRS);\n    result = anjay_resource_observation_status(anjay, 42, 69, 4);\n    AVS_UNIT_ASSERT_TRUE(result.is_observed);\n    AVS_UNIT_ASSERT_EQUAL(result.min_period, 10);\n    AVS_UNIT_ASSERT_EQUAL(result.max_eval_period, 360);\n\n    DM_TEST_FINISH;\n}\n#endif // ANJAY_WITH_OBSERVATION_STATUS\n"
  },
  {
    "path": "tests/core/observe/observe_mock.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ANJAY_TEST_OBSERVE_MOCK_H\n#define ANJAY_TEST_OBSERVE_MOCK_H\n\n#include <avsystem/commons/avs_unit_mock_helpers.h>\n\nAVS_UNIT_MOCK_CREATE(_anjay_dm_find_object_by_oid)\n#define _anjay_dm_find_object_by_oid(...) \\\n    AVS_UNIT_MOCK_WRAPPER(_anjay_dm_find_object_by_oid)(__VA_ARGS__)\n\nAVS_UNIT_MOCK_CREATE(send_initial_response)\n#define send_initial_response(...) \\\n    AVS_UNIT_MOCK_WRAPPER(send_initial_response)(__VA_ARGS__)\n\n#endif /* ANJAY_TEST_OBSERVE_MOCK_H */\n"
  },
  {
    "path": "tests/core/socket_mock.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#include \"socket_mock.h\"\n\nAVS_UNIT_MOCK_DEFINE(avs_net_tcp_socket_create)\nAVS_UNIT_MOCK_DEFINE(avs_net_udp_socket_create)\nAVS_UNIT_MOCK_DEFINE(avs_net_ssl_socket_create)\nAVS_UNIT_MOCK_DEFINE(avs_net_dtls_socket_create)\n"
  },
  {
    "path": "tests/core/socket_mock.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef SOCKET_MOCK_H\n#define SOCKET_MOCK_H\n\n#include <avsystem/commons/avs_net.h>\n#include <avsystem/commons/avs_unit_mock_helpers.h>\n\nextern AVS_UNIT_MOCK_DECLARE(avs_net_tcp_socket_create);\n#define avs_net_tcp_socket_create(...) \\\n    AVS_UNIT_MOCK_WRAPPER(avs_net_tcp_socket_create)(__VA_ARGS__)\n\nextern AVS_UNIT_MOCK_DECLARE(avs_net_udp_socket_create);\n#define avs_net_udp_socket_create(...) \\\n    AVS_UNIT_MOCK_WRAPPER(avs_net_udp_socket_create)(__VA_ARGS__)\n\nextern AVS_UNIT_MOCK_DECLARE(avs_net_ssl_socket_create);\n#define avs_net_ssl_socket_create(...) \\\n    AVS_UNIT_MOCK_WRAPPER(avs_net_ssl_socket_create)(__VA_ARGS__)\n\nextern AVS_UNIT_MOCK_DECLARE(avs_net_dtls_socket_create);\n#define avs_net_dtls_socket_create(...) \\\n    AVS_UNIT_MOCK_WRAPPER(avs_net_dtls_socket_create)(__VA_ARGS__)\n\n#endif /* SOCKET_MOCK_H */\n"
  },
  {
    "path": "tests/core/utils.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#include <stdio.h>\n\n#include <anjay_modules/anjay_time_defs.h>\n\n#include <anjay/core.h>\n#include <anjay/security.h>\n#include <anjay/server.h>\n\n#include <avsystem/commons/avs_unit_test.h>\n#include <avsystem/commons/avs_utils.h>\n\n#include \"tests/utils/utils.h\"\n\n#define ANJAY_URL_EMPTY   \\\n    (anjay_url_t) {       \\\n        .uri_path = NULL, \\\n        .uri_query = NULL \\\n    }\n\n#define AUTO_URL(Name) \\\n    __attribute__((__cleanup__(_anjay_url_cleanup))) anjay_url_t Name\n\nAVS_UNIT_TEST(parse_url, square_bracket_enclosed_host_address_too_long) {\n    anjay_url_t parsed_url = ANJAY_URL_EMPTY;\n\n    char url[ANJAY_MAX_URL_HOSTNAME_SIZE + sizeof(\"coap://[]\") + 1] =\n            \"coap://[\";\n    memset(url + sizeof(\"coap://[\") - 1,\n           'A',\n           sizeof(url) - sizeof(\"coap://[\") - 1);\n    url[sizeof(url) - 2] = ']';\n\n    AVS_UNIT_ASSERT_FAILED(_anjay_url_parse(url, &parsed_url));\n}\n\nAVS_UNIT_TEST(parse_url, without_credentials_port_and_path) {\n    anjay_url_t parsed_url = ANJAY_URL_EMPTY;\n\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_url_parse(\"coap://acs.avsystem.com\", &parsed_url));\n    AVS_UNIT_ASSERT_EQUAL_STRING(parsed_url.host, \"acs.avsystem.com\");\n    AVS_UNIT_ASSERT_EQUAL_STRING(parsed_url.port, \"5683\");\n}\n\nAVS_UNIT_TEST(parse_url, with_port_and_path) {\n    AUTO_URL(parsed_url) = ANJAY_URL_EMPTY;\n\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_url_parse(\n            \"coap://acs.avsystem.com:123/path/to/resource\", &parsed_url));\n}\n\nAVS_UNIT_TEST(parse_url, without_password_with_user) {\n    anjay_url_t parsed_url = ANJAY_URL_EMPTY;\n\n    AVS_UNIT_ASSERT_FAILED(\n            _anjay_url_parse(\"coap://user@acs.avsystem.com:123\", &parsed_url));\n}\n\nAVS_UNIT_TEST(parse_url, with_empty_user) {\n    anjay_url_t parsed_url = ANJAY_URL_EMPTY;\n\n    AVS_UNIT_ASSERT_FAILED(\n            _anjay_url_parse(\"coap://@acs.avsystem.com:123\", &parsed_url));\n}\n\nAVS_UNIT_TEST(parse_url, with_user_and_empty_password) {\n    anjay_url_t parsed_url = ANJAY_URL_EMPTY;\n\n    AVS_UNIT_ASSERT_FAILED(\n            _anjay_url_parse(\"coap://user:@acs.avsystem.com:123\", &parsed_url));\n}\n\nAVS_UNIT_TEST(parse_url, with_empty_user_and_empty_password) {\n    anjay_url_t parsed_url = ANJAY_URL_EMPTY;\n\n    AVS_UNIT_ASSERT_FAILED(\n            _anjay_url_parse(\"coap://:@acs.avsystem.com:123\", &parsed_url));\n}\n\nAVS_UNIT_TEST(parse_url, with_user_and_password) {\n    anjay_url_t parsed_url = ANJAY_URL_EMPTY;\n\n    AVS_UNIT_ASSERT_FAILED(_anjay_url_parse(\n            \"coap://user:password@acs.avsystem.com:123\", &parsed_url));\n}\n\nAVS_UNIT_TEST(parse_url, escaped_credentials) {\n    anjay_url_t parsed_url = ANJAY_URL_EMPTY;\n\n    AVS_UNIT_ASSERT_FAILED(_anjay_url_parse(\n            \"coap://user%25:p%40ssword@acs.avsystem.com\", &parsed_url));\n}\n\nAVS_UNIT_TEST(parse_url, coaps_url) {\n    anjay_url_t parsed_url = ANJAY_URL_EMPTY;\n\n    {\n        parsed_url = ANJAY_URL_EMPTY;\n        AVS_UNIT_ASSERT_SUCCESS(\n                _anjay_url_parse(\"coaps://[12::34]\", &parsed_url));\n        AVS_UNIT_ASSERT_EQUAL_STRING(parsed_url.host, \"12::34\");\n    }\n    {\n        parsed_url = ANJAY_URL_EMPTY;\n        AVS_UNIT_ASSERT_SUCCESS(\n                _anjay_url_parse(\"coaps://acs.avsystem.com\", &parsed_url));\n    }\n    {\n        parsed_url = ANJAY_URL_EMPTY;\n        AVS_UNIT_ASSERT_SUCCESS(\n                _anjay_url_parse(\"coaps://acs.avsystem.com:123\", &parsed_url));\n    }\n}\n\nAVS_UNIT_TEST(parse_url, null_in_username_and_password) {\n    anjay_url_t parsed_url = ANJAY_URL_EMPTY;\n\n    AVS_UNIT_ASSERT_FAILED(_anjay_url_parse(\n            \"coap://user%00:password@acs.avsystem.com\", &parsed_url));\n    AVS_UNIT_ASSERT_FAILED(_anjay_url_parse(\n            \"coap://user:pas%00sword@acs.avsystem.com\", &parsed_url));\n}\n\nAVS_UNIT_TEST(parse_url, port_length) {\n    anjay_url_t parsed_url = ANJAY_URL_EMPTY;\n\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_url_parse(\"coap://acs.avsystem.com:1234\", &parsed_url));\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_url_parse(\"coap://acs.avsystem.com:12345\", &parsed_url));\n    AVS_UNIT_ASSERT_FAILED(\n            _anjay_url_parse(\"coap://acs.avsystem.com:123456\", &parsed_url));\n    AVS_UNIT_ASSERT_FAILED(\n            _anjay_url_parse(\"coap://acs.avsystem.com:1234567\", &parsed_url));\n    AVS_UNIT_ASSERT_FAILED(\n            _anjay_url_parse(\"coap://acs.avsystem.com:\", &parsed_url));\n}\n\nAVS_UNIT_TEST(parse_url, port_invalid_characters) {\n    anjay_url_t parsed_url = ANJAY_URL_EMPTY;\n\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_url_parse(\"coap://acs.avsystem.com:12345\", &parsed_url));\n    AVS_UNIT_ASSERT_FAILED(\n            _anjay_url_parse(\"coap://acs.avsystem.com:1_234\", &parsed_url));\n    AVS_UNIT_ASSERT_FAILED(\n            _anjay_url_parse(\"coap://acs.avsystem.com:http\", &parsed_url));\n    AVS_UNIT_ASSERT_FAILED(\n            _anjay_url_parse(\"coap://acs.avsystem.com:12345_\", &parsed_url));\n}\n\nAVS_UNIT_TEST(parse_url, ipv6_address) {\n    anjay_url_t parsed_url = ANJAY_URL_EMPTY;\n\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_url_parse(\"coap://[12::34]\", &parsed_url));\n    AVS_UNIT_ASSERT_EQUAL_STRING(parsed_url.host, \"12::34\");\n    AVS_UNIT_ASSERT_EQUAL_STRING(parsed_url.port, \"5683\");\n}\n\nAVS_UNIT_TEST(parse_url, ipv6_address_with_port_and_path) {\n    AUTO_URL(parsed_url) = ANJAY_URL_EMPTY;\n\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_url_parse(\"coap://[12::34]:56/78\", &parsed_url));\n}\n\nAVS_UNIT_TEST(parse_url, ipv6_address_with_credentials) {\n    anjay_url_t parsed_url = ANJAY_URL_EMPTY;\n\n    AVS_UNIT_ASSERT_FAILED(_anjay_url_parse(\n            \"coap://user%25:p%40ssword@[12::34]:56/78\", &parsed_url));\n}\n\nAVS_UNIT_TEST(parse_url, invalid_ipv6_address) {\n    anjay_url_t parsed_url = ANJAY_URL_EMPTY;\n\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_url_parse(\"coap://[12:ff:ff::34]\", &parsed_url));\n    AVS_UNIT_ASSERT_FAILED(\n            _anjay_url_parse(\"coap://12:ff:ff::34]\", &parsed_url));\n    AVS_UNIT_ASSERT_FAILED(\n            _anjay_url_parse(\"coap://[12:ff:ff::34\", &parsed_url));\n    AVS_UNIT_ASSERT_FAILED(\n            _anjay_url_parse(\"coap://[12:ff:ff::34]:\", &parsed_url));\n}\n\nAVS_UNIT_TEST(parse_url, hostname_length) {\n    anjay_url_t parsed_url = ANJAY_URL_EMPTY;\n    char hostname[ANJAY_MAX_URL_HOSTNAME_SIZE + 1];\n    char url[256];\n\n    /* Test max length */\n    memset(hostname, 'a', ANJAY_MAX_URL_HOSTNAME_SIZE - 1);\n    hostname[ANJAY_MAX_URL_HOSTNAME_SIZE - 1] = '\\0';\n    sprintf(url, \"coap://%s\", hostname);\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_url_parse(url, &parsed_url));\n    AVS_UNIT_ASSERT_EQUAL_STRING(parsed_url.host, hostname);\n\n    /* Test max length + 1 */\n    memset(hostname, 'a', ANJAY_MAX_URL_HOSTNAME_SIZE);\n    hostname[ANJAY_MAX_URL_HOSTNAME_SIZE] = '\\0';\n    sprintf(url, \"coap://%s\", hostname);\n    AVS_UNIT_ASSERT_FAILED(_anjay_url_parse(url, &parsed_url));\n}\n\nAVS_UNIT_TEST(parse_url, empty_uri_path_and_query) {\n    anjay_url_t url;\n    memset(&url, 0, sizeof(url));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_url_parse(\"coaps://avsystem.com/\", &url));\n    AVS_UNIT_ASSERT_NULL(url.uri_path);\n    AVS_UNIT_ASSERT_NULL(url.uri_query);\n\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_url_parse(\"coaps://avsystem.com\", &url));\n    AVS_UNIT_ASSERT_NULL(url.uri_path);\n    AVS_UNIT_ASSERT_NULL(url.uri_query);\n}\n\nAVS_UNIT_TEST(parse_url, basic_segments) {\n    AUTO_URL(url) = ANJAY_URL_EMPTY;\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_url_parse(\"coaps://avsystem.com/0/1/2\", &url));\n\n    AVS_UNIT_ASSERT_EQUAL(AVS_LIST_SIZE(url.uri_path), 3);\n    AVS_UNIT_ASSERT_EQUAL(AVS_LIST_SIZE(url.uri_query), 0);\n    AVS_UNIT_ASSERT_EQUAL_STRING(AVS_LIST_NTH(url.uri_path, 0)->c_str, \"0\");\n    AVS_UNIT_ASSERT_EQUAL_STRING(AVS_LIST_NTH(url.uri_path, 1)->c_str, \"1\");\n    AVS_UNIT_ASSERT_EQUAL_STRING(AVS_LIST_NTH(url.uri_path, 2)->c_str, \"2\");\n}\n\nAVS_UNIT_TEST(parse_url, one_segment_empty) {\n    AUTO_URL(url) = ANJAY_URL_EMPTY;\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_url_parse(\"coaps://avsystem.com//\", &url));\n\n    AVS_UNIT_ASSERT_EQUAL(AVS_LIST_SIZE(url.uri_path), 1);\n    AVS_UNIT_ASSERT_EQUAL(AVS_LIST_SIZE(url.uri_query), 0);\n    AVS_UNIT_ASSERT_EQUAL_STRING(AVS_LIST_NTH(url.uri_path, 0)->c_str, \"\");\n}\n\nAVS_UNIT_TEST(parse_url, two_segments_empty) {\n    AUTO_URL(url) = ANJAY_URL_EMPTY;\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_url_parse(\"coaps://avsystem.com///\", &url));\n\n    AVS_UNIT_ASSERT_EQUAL(AVS_LIST_SIZE(url.uri_path), 2);\n    AVS_UNIT_ASSERT_EQUAL(AVS_LIST_SIZE(url.uri_query), 0);\n    AVS_UNIT_ASSERT_EQUAL_STRING(AVS_LIST_NTH(url.uri_path, 0)->c_str, \"\");\n    AVS_UNIT_ASSERT_EQUAL_STRING(AVS_LIST_NTH(url.uri_path, 1)->c_str, \"\");\n}\n\nAVS_UNIT_TEST(parse_url, basic_query) {\n    AUTO_URL(url) = ANJAY_URL_EMPTY;\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_url_parse(\"coaps://avsystem.com/t/o/p?k3k\", &url));\n    AVS_UNIT_ASSERT_EQUAL(AVS_LIST_SIZE(url.uri_path), 3);\n    AVS_UNIT_ASSERT_EQUAL(AVS_LIST_SIZE(url.uri_query), 1);\n    AVS_UNIT_ASSERT_EQUAL_STRING(AVS_LIST_NTH(url.uri_path, 0)->c_str, \"t\");\n    AVS_UNIT_ASSERT_EQUAL_STRING(AVS_LIST_NTH(url.uri_path, 1)->c_str, \"o\");\n    AVS_UNIT_ASSERT_EQUAL_STRING(AVS_LIST_NTH(url.uri_path, 2)->c_str, \"p\");\n    AVS_UNIT_ASSERT_EQUAL_STRING(url.uri_query->c_str, \"k3k\");\n}\n\nAVS_UNIT_TEST(parse_url, basic_query_invalid_chars) {\n    AUTO_URL(url) = ANJAY_URL_EMPTY;\n    AVS_UNIT_ASSERT_FAILED(\n            _anjay_url_parse(\"coaps://avsystem.com/t/o/p?|<3|<\", &url));\n    AVS_UNIT_ASSERT_NULL(url.uri_path);\n    AVS_UNIT_ASSERT_NULL(url.uri_query);\n}\n\nAVS_UNIT_TEST(parse_url, only_query) {\n    AUTO_URL(url) = ANJAY_URL_EMPTY;\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_url_parse(\"coaps://avsystem.com/?foo\", &url));\n    AVS_UNIT_ASSERT_EQUAL(AVS_LIST_SIZE(url.uri_path), 0);\n    AVS_UNIT_ASSERT_EQUAL(AVS_LIST_SIZE(url.uri_query), 1);\n    AVS_UNIT_ASSERT_EQUAL_STRING(url.uri_query->c_str, \"foo\");\n}\n\nAVS_UNIT_TEST(parse_url, empty_query_strings) {\n    AUTO_URL(url) = ANJAY_URL_EMPTY;\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_url_parse(\"coaps://avsystem.com/?&&&\", &url));\n    AVS_UNIT_ASSERT_EQUAL(AVS_LIST_SIZE(url.uri_path), 0);\n    AVS_UNIT_ASSERT_EQUAL(AVS_LIST_SIZE(url.uri_query), 4);\n    for (size_t i = 0; i < 4; ++i) {\n        AVS_UNIT_ASSERT_EQUAL_STRING(AVS_LIST_NTH(url.uri_query, i)->c_str, \"\");\n    }\n}\n\nAVS_UNIT_TEST(parse_url, escaped_uri_path) {\n    AUTO_URL(url) = ANJAY_URL_EMPTY;\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_url_parse(\"coap://avsystem.com/foo%26bar\", &url));\n    AVS_UNIT_ASSERT_EQUAL(AVS_LIST_SIZE(url.uri_path), 1);\n    AVS_UNIT_ASSERT_EQUAL(AVS_LIST_SIZE(url.uri_query), 0);\n    AVS_UNIT_ASSERT_EQUAL_STRING(url.uri_path->c_str, \"foo&bar\");\n}\n\nAVS_UNIT_TEST(parse_url, weird_query) {\n    AUTO_URL(url) = ANJAY_URL_EMPTY;\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_url_parse(\n            \"coap://avsystem.com/foo/bar?baz/weird/but/still/query\", &url));\n    AVS_UNIT_ASSERT_EQUAL(AVS_LIST_SIZE(url.uri_path), 2);\n    AVS_UNIT_ASSERT_EQUAL(AVS_LIST_SIZE(url.uri_query), 1);\n    AVS_UNIT_ASSERT_EQUAL_STRING(AVS_LIST_NTH(url.uri_path, 0)->c_str, \"foo\");\n    AVS_UNIT_ASSERT_EQUAL_STRING(AVS_LIST_NTH(url.uri_path, 1)->c_str, \"bar\");\n    AVS_UNIT_ASSERT_EQUAL_STRING(AVS_LIST_NTH(url.uri_query, 0)->c_str,\n                                 \"baz/weird/but/still/query\");\n}\n\nAVS_UNIT_TEST(parse_url, bad_percent_encoding) {\n    AUTO_URL(url) = ANJAY_URL_EMPTY;\n    AVS_UNIT_ASSERT_FAILED(_anjay_url_parse(\"coap://avsystem.com/fo%xa\", &url));\n    AVS_UNIT_ASSERT_FAILED(\n            _anjay_url_parse(\"coap://avsystem.com/foo?b%xar\", &url));\n    AVS_UNIT_ASSERT_NULL(url.uri_query);\n    AVS_UNIT_ASSERT_NULL(url.uri_path);\n}\n\nAVS_UNIT_TEST(snprintf, no_space_for_terminating_nullbyte) {\n    char buf[3];\n    int result = avs_simple_snprintf(buf, sizeof(buf), \"%s\", \"foo\");\n    AVS_UNIT_ASSERT_TRUE(result < 0);\n}\n\nAVS_UNIT_TEST(binding_mode_valid, unsupported_binding_mode) {\n    AVS_UNIT_ASSERT_FALSE(anjay_binding_mode_valid(\"☃\"));\n}\n"
  },
  {
    "path": "tests/doc/runtest.py",
    "content": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nimport argparse\nimport datetime\nimport errno\nimport logging\nimport os\nimport re\nimport subprocess\nimport sys\nimport urllib\nimport urllib.parse\nfrom collections import defaultdict\n\nimport requests\n\nPROJECT_ROOT = os.path.abspath(os.path.join(\n    os.path.dirname(__file__), '..', '..'))\nHTTP_STATUS_OK = 200\nFILE_EXTENSION = '.rst'\nREGEX = r'<(http.*?)>`_'\nPUBLIC_REPO_BLOB_PREFIX = 'https://github.com/AVSystem/Anjay/blob/master/'\nPUBLIC_REPO_TREE_PREFIX = 'https://github.com/AVSystem/Anjay/tree/master/'\nDEFAULT_DOC_PATH = os.path.join(PROJECT_ROOT, 'doc/sphinx/source')\n# User-Agent taken from Stack Overflow, some websites need it\nUSER_AGENT = \"Mozilla/5.0 (Windows NT 10.0; Win64; x64)\"\nBOT_PROTECTION_STATUS_CODES = {403, 429}\nBOT_PROTECTION_SERVER_HINTS = ('cloudflare', 'akamai', 'incapsula', 'imperva', 'sucuri')\n\n\ndef _get_ignored_patterns():\n    whitelist = os.environ.get('ANJAY_DOC_CHECK_WHITELIST') or ''\n    date, patterns = (whitelist.split('=', 1) + [''])[:2]\n    patterns = patterns.split(',')\n    if date == str(datetime.datetime.now().date()):\n        return patterns\n    else:\n        return []\n\ndef _get_permanent_ignored_patterns():\n    patterns = os.environ.get('ANJAY_DOC_CHECK_PERMANENT_WHITELIST') or ''\n    if patterns == '':\n        return []\n\n    patterns = patterns.split(',')\n    return patterns\n\nIGNORED_PATTERNS = _get_ignored_patterns()\nPERMANENT_IGNORED_PATTERNS = _get_permanent_ignored_patterns()\n\n\ndef explore(path):\n    for root, directories, file_names in os.walk(path):\n        for file_name in file_names:\n            file_path = os.path.join(root, file_name)\n            if file_path.lower().endswith(FILE_EXTENSION):\n                with open(file_path, encoding=\"utf-8\") as f:\n                    content = f.read()\n                    yield (file_path, content)\n\n\ndef find_urls(rst_content):\n    lines = enumerate(rst_content.splitlines(), 1)\n    return ((line_number, found_url)\n            for line_number, line_content in lines\n            for found_url in re.findall(REGEX, line_content))\n\n\nclass UrlChecker:\n    def __init__(self, max_attempts=5, strict=False):\n        super().__init__()\n        self.max_attempts = max_attempts\n        self.strict = strict\n\n    def _is_probably_bot_protected(self, url):\n        try:\n            response = requests.get(url, allow_redirects=True, timeout=10,\n                                    headers={\"User-Agent\": USER_AGENT})\n        except Exception:\n            return False\n\n        if response.status_code not in BOT_PROTECTION_STATUS_CODES:\n            return False\n\n        headers = {k.lower(): v.lower() for k, v in response.headers.items()}\n        server = headers.get('server', '')\n        if any(hint in server for hint in BOT_PROTECTION_SERVER_HINTS):\n            return True\n\n        bot_headers = ('cf-ray', 'cf-cache-status', 'x-sucuri-id', 'x-iinfo',\n                    'x-akamai-session-info', 'x-akamai-transformed')\n        if any(header in headers for header in bot_headers):\n            return True\n        return False\n\n    def is_url_valid(self, url, attempt=1):\n        if url.startswith(PUBLIC_REPO_BLOB_PREFIX):\n            return os.path.isfile(os.path.join(PROJECT_ROOT, url[len(PUBLIC_REPO_BLOB_PREFIX):]))\n        elif url.startswith(PUBLIC_REPO_TREE_PREFIX):\n            return os.path.isdir(os.path.join(PROJECT_ROOT, url[len(PUBLIC_REPO_TREE_PREFIX):]))\n        if any(pattern in url for pattern in PERMANENT_IGNORED_PATTERNS):\n            logging.warning(\n                'URL %s not checked due to ANJAY_DOC_CHECK_PERMANENT_WHITELIST' % (url,))\n            return True\n        if any(pattern in url for pattern in IGNORED_PATTERNS):\n            logging.warning(\n                'URL %s not checked due to ANJAY_DOC_CHECK_WHITELIST' % (url,))\n            return True\n\n        if attempt > self.max_attempts:\n            logging.error(\n                'URL %s could not be reached %d times. Giving up.' % (url, self.max_attempts))\n            logging.error(('If you believe this is a problem on the remote site, you can set the '\n                           + 'ANJAY_DOC_CHECK_WHITELIST=%s=%s environment variable to ignore this '\n                           + 'error for today.')\n                          % (datetime.datetime.now().date(),\n                             ','.join(IGNORED_PATTERNS + [urllib.parse.urlparse(url).hostname])))\n            return False\n\n        try:\n            self.perform_request(url)\n        except Exception as err:\n            if not self.strict and self._is_probably_bot_protected(url):\n                logging.warning('URL %s is protected by anti-bot rules and was marked as unverifiable.'\n                                % (url,))\n                return True\n            logging.warning('URL %s could not be reached (%d/%d): %s'\n                            % (url, attempt, self.max_attempts, sys.exc_info()[0]))\n            return self.is_url_valid(url, attempt + 1)\n\n        return True\n\n\nclass RequestsUrlChecker(UrlChecker):\n    def perform_request(self, url):\n        status = requests.head(url, allow_redirects=True, timeout=10,\n                               headers={\"User-Agent\": USER_AGENT})\n        if not status:\n            raise IOError(errno.EIO, 'Invalid HTTP status')\n        if status.status_code != HTTP_STATUS_OK:\n            raise IOError(errno.EIO, f'HTTP status: {status.status_code}')\n\n\nclass Wget2UrlChecker(UrlChecker):\n    def __init__(self, *args, **kwargs):\n        subprocess.run(['wget2', '--version'], stdout=subprocess.DEVNULL, check=True)\n        super().__init__(*args, **kwargs)\n\n    def run_wget2(self, url, extra_args=None):\n        args = ['wget2', '--no-http2', '--wait=1', '--random-wait', '-q', '-t', '1',\n                '-U', USER_AGENT, '-T', '10', '--prefer-family=IPv4', '-O', '/dev/null',\n                '--stats-site=csv:-']\n        if extra_args:\n            args += extra_args\n        args += ['--', url]\n\n        result = subprocess.run(args, stdout=subprocess.PIPE, check=True)\n        csv_lines = result.stdout.strip().split(b'\\n')\n        header_line = csv_lines[0].split(b',')\n        last_line = csv_lines[-1].split(b',')\n        # We use negative index because the URL column may contain commas\n        # and wget2 does not escape properly\n        status_index = header_line.index(b'Status') - len(header_line)\n        http_status = last_line[status_index]\n\n        if http_status != str(HTTP_STATUS_OK).encode():\n            raise IOError(errno.EIO, f'HTTP status: {http_status.decode()}')\n\n    def perform_request(self, url):\n        # Despite the wget2 can handle HTTP/2, there are few servers that use specific implementation\n        # of HTTP/2, which is not handled properly by this tool. That's why the --no-http2 option is used.\n        try:\n            self.run_wget2(url)\n        except subprocess.CalledProcessError as e:\n            logging.warning(f\"wget2 exit code: {e.returncode}\")\n            self.run_wget2(url, extra_args=['--ca-directory=/usr/local/share/ca-certificates/'])\n        except OSError as e:\n            if 'HTTP status: 403' in str(e):\n                self.run_wget2(url, extra_args=[\n                    '--header=Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8',\n                    '--header=Accept-Language: en-US,en;q=0.9',\n                    '--header=Cache-Control: no-cache',\n                    '--header=Pragma: no-cache',\n                    '--header=Upgrade-Insecure-Requests: 1',\n                    '--header=Sec-Fetch-Dest: document',\n                    '--header=Sec-Fetch-Mode: navigate',\n                    '--header=Sec-Fetch-Site: none',\n                    '--header=Sec-Fetch-User: ?1'\n                ])\n            else:\n                raise\n\n\n\ndef find_invalid_urls(checker, urls):\n    from multiprocessing import pool\n\n    with pool.Pool(2 * os.cpu_count()) as p:\n        responses = p.map(checker.is_url_valid, urls.keys())\n\n    invalid_urls = defaultdict(list)\n    for url, url_valid in zip(urls, responses):\n        if not url_valid:\n            invalid_urls[url] = urls[url]\n\n    return invalid_urls\n\n\ndef report(path, strict):\n    try:\n        checker = Wget2UrlChecker(strict=strict)\n    except:\n        checker = RequestsUrlChecker(strict=strict)\n\n    urls = defaultdict(list)\n    for file_path, content in explore(path):\n        for line_number, found_url in find_urls(content):\n            urls[found_url].append((file_path, line_number))\n\n    invalid_urls = find_invalid_urls(checker, urls)\n    if invalid_urls:\n        logging.warning('There are invalid urls.')\n        for (url, details) in invalid_urls.items():\n            print('URL: %s in:' % url)\n            for item in details:\n                print('\\t%s:%s' % item)\n        if not isinstance(checker, Wget2UrlChecker):\n            logging.warning(\"You may try installing 'wget2' to enable alternate URL checking logic.\")\n        sys.exit(-1)\n    else:\n        logging.info('All urls are valid.')\n\n\nif __name__ == \"__main__\":\n    logging.getLogger().setLevel(logging.INFO)\n    parser = argparse.ArgumentParser(\n        description='Explores RST files in specified or default path and validates URLs included in them.')\n    parser.add_argument('doc_path',\n                        type=str, default=DEFAULT_DOC_PATH, nargs='?',\n                        help='path to the doc folder')\n    parser.add_argument('--strict',\n                        action='store_true', default=False,\n                        help='do not treat anti-bot-protected URLs as unverifiable')\n    cmdline_args = parser.parse_args()\n    report(cmdline_args.doc_path, strict=cmdline_args.strict)\n"
  },
  {
    "path": "tests/fuzz/CMakeLists.txt",
    "content": "# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\noption(WITH_FUZZ_TESTS \"Compile fuzz tests using the AFL fuzzer\" OFF)\nif(NOT WITH_FUZZ_TESTS)\n    return()\nendif()\n\nif(NOT \"${CMAKE_C_COMPILER}\" MATCHES \"^(.*/)?afl-gcc$\")\n    message(WARNING \"AFL fuzz tests require compiling with afl-gcc, but CMAKE_C_COMPILER is set to ${CMAKE_C_COMPILER}\")\nendif()\n\nif(AFL_FUZZER_DIR)\n    set(AFL_FUZZ_EXECUTABLE \"${AFL_FUZZER_DIR}/afl-fuzz\")\nelse()\n    set(AFL_FUZZ_EXECUTABLE \"afl-fuzz\")\nendif()\n\nset(CMAKE_RUNTIME_OUTPUT_DIRECTORY \"${CMAKE_CURRENT_BINARY_DIR}/bin\")\n\nset(FUZZ_TEST_NAMES \"\")\nset(FUZZ_TEST_ARG_LISTS \"\")\nset(FUZZ_TEST_CASE_DIRS \"\")\nset(FUZZ_TEST_FINDINGS_DIRS \"\")\n\nmacro(add_fuzz_target NAME EXECUTABLE)\n    get_filename_component(EXECUTABLE \"${EXECUTABLE}\" ABSOLUTE)\n\n    list(APPEND FUZZ_TEST_NAMES ${NAME})\n    list(APPEND FUZZ_TEST_ARG_LISTS \"FUZZ_TEST_ARG_LIST_${NAME}\")\n    list(APPEND FUZZ_TEST_CASE_DIRS \"${CMAKE_CURRENT_SOURCE_DIR}/test_cases/${NAME}\")\n    list(APPEND FUZZ_TEST_FINDINGS_DIRS \"${CMAKE_CURRENT_BINARY_DIR}/findings/${NAME}\")\n\n    set(\"FUZZ_TEST_ARG_LIST_${NAME}\" \"${EXECUTABLE}\" ${ARGN})\n\n    set(FUZZ_TEST_NAMES \"${FUZZ_TEST_NAMES}\")\n    set(FUZZ_TEST_ARG_LISTS \"${FUZZ_TEST_ARG_LISTS}\")\n    set(FUZZ_TEST_CASE_DIRS \"${FUZZ_TEST_CASE_DIRS}\")\n    set(FUZZ_TEST_FINDINGS_DIRS \"${FUZZ_TEST_FINDINGS_DIRS}\")\nendmacro()\n\nfile(GLOB_RECURSE FUZZ_TEST_SOURCES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.c)\n\nforeach(FUZZ_TEST_SOURCE ${FUZZ_TEST_SOURCES})\n    get_filename_component(FUZZ_TEST_DIR \"${FUZZ_TEST_SOURCE}\" DIRECTORY)\n    get_filename_component(FUZZ_TEST_OUTPUT \"${FUZZ_TEST_SOURCE}\" NAME_WE)\n    set(FUZZ_TEST_OUTPUT \"${FUZZ_TEST_DIR}/${FUZZ_TEST_OUTPUT}\")\n    string(REPLACE / _ FUZZ_TEST_NAME ${FUZZ_TEST_OUTPUT})\n\n    add_executable(${FUZZ_TEST_NAME}_fuzz_test ${FUZZ_TEST_SOURCE} ${ABSOLUTE_SOURCES})\n    target_link_libraries(${FUZZ_TEST_NAME}_fuzz_test PRIVATE ${PROJECT_NAME})\n    target_include_directories(${FUZZ_TEST_NAME}_fuzz_test PRIVATE\n                               \"${ANJAY_SOURCE_DIR}\"\n                               $<TARGET_PROPERTY:anjay,INCLUDE_DIRECTORIES>)\n    set_property(TARGET ${FUZZ_TEST_NAME}_fuzz_test APPEND PROPERTY COMPILE_DEFINITIONS ANJAY_FUZZ_TEST)\n\n    add_fuzz_target(${FUZZ_TEST_NAME} \"${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${FUZZ_TEST_NAME}_fuzz_test\")\nendforeach()\n\nset(AFL_FUZZ_MEMORY_LIMIT_MEGS 32)\nset(AFL_FUZZ_TIMEOUT_MS 1000)\n\nlist(LENGTH FUZZ_TEST_NAMES FUZZ_TEST_COUNT)\nmath(EXPR FUZZ_TEST_MAX \"${FUZZ_TEST_COUNT} - 1\")\n\nforeach(i RANGE ${FUZZ_TEST_MAX})\n    list(GET FUZZ_TEST_NAMES ${i} FUZZ_TEST_NAME)\n    list(GET FUZZ_TEST_ARG_LISTS ${i} FUZZ_TEST_ARG_LIST_NAME)\n    list(GET FUZZ_TEST_CASE_DIRS ${i} FUZZ_TEST_CASE_DIR)\n    list(GET FUZZ_TEST_FINDINGS_DIRS ${i} FUZZ_TEST_FINDINGS_DIR)\n\n    message(STATUS \"Adding fuzz test target: fuzz_${FUZZ_TEST_NAME}: ${${FUZZ_TEST_ARG_LIST_NAME}}\")\n    add_custom_target(fuzz_${FUZZ_TEST_NAME}\n                      COMMAND mkdir -p \"${FUZZ_TEST_FINDINGS_DIR}\"\n                      COMMAND \"${AFL_FUZZ_EXECUTABLE}\" -i \"${FUZZ_TEST_CASE_DIR}\"\n                                                       -o \"${FUZZ_TEST_FINDINGS_DIR}\"\n                                                       -t ${AFL_FUZZ_TIMEOUT_MS}\n                                                       -m ${AFL_FUZZ_MEMORY_LIMIT_MEGS}\n                                                       \"${${FUZZ_TEST_ARG_LIST_NAME}}\"\n                                                       WORKING_DIRECTORY \"${CMAKE_CURRENT_BINARY_DIR}\"\n                      DEPENDS ${FUZZ_TEST_NAME}_fuzz_test)\nendforeach()\n"
  },
  {
    "path": "tests/fuzz/cbor/decoder.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#include <avsystem/commons/avs_defs.h>\n#include <avsystem/commons/avs_stream_file.h>\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n#include \"src/core/io/cbor/anjay_json_like_cbor_decoder.h\"\n\nstatic int decode_value(anjay_json_like_decoder_t *decoder);\n\nstatic int decode_number(anjay_json_like_decoder_t *decoder,\n                         anjay_json_like_value_type_t type) {\n    anjay_json_like_number_t number;\n    memset(&number, 0, sizeof(number));\n    if (_anjay_json_like_decoder_number(decoder, &number)) {\n        return -1;\n    }\n    if (number.type != type) {\n        abort();\n    }\n    return 0;\n}\n\nstatic int decode_string(anjay_json_like_decoder_t *decoder) {\n    anjay_io_cbor_bytes_ctx_t bytes = { 0 };\n    if (_anjay_io_cbor_get_bytes_ctx(decoder, &bytes)) {\n        return -1;\n    }\n    size_t remaining = bytes.bytes_available;\n\n    uint8_t buffer[1024];\n    bool finished = false;\n    while (!finished) {\n        size_t expected_bytes_count = AVS_MIN(sizeof(buffer), remaining);\n        size_t bytes_read;\n        if (_anjay_io_cbor_get_some_bytes(decoder, &bytes, buffer,\n                                          sizeof(buffer), &bytes_read,\n                                          &finished)) {\n            return -1;\n        }\n        assert(bytes_read == expected_bytes_count);\n        remaining -= bytes_read;\n        assert(finished == !remaining);\n    }\n    return 0;\n}\n\nstatic int decode_map(anjay_json_like_decoder_t *decoder) {\n    size_t outer_level = _anjay_json_like_decoder_nesting_level(decoder);\n    if (_anjay_json_like_decoder_enter_map(decoder)) {\n        return -1;\n    }\n    while (_anjay_json_like_decoder_nesting_level(decoder) > outer_level) {\n        // decode key and value\n        if (decode_value(decoder) || decode_value(decoder)) {\n            return -1;\n        }\n    }\n    return 0;\n}\n\nstatic int decode_array(anjay_json_like_decoder_t *decoder) {\n    size_t outer_level = _anjay_json_like_decoder_nesting_level(decoder);\n    if (_anjay_json_like_decoder_enter_array(decoder)) {\n        return -1;\n    }\n    while (_anjay_json_like_decoder_nesting_level(decoder) > outer_level) {\n        if (decode_value(decoder)) {\n            return -1;\n        }\n    }\n    return 0;\n}\n\nstatic int decode_value(anjay_json_like_decoder_t *decoder) {\n    anjay_json_like_value_type_t type;\n    if (_anjay_json_like_decoder_current_value_type(decoder, &type)) {\n        return -1;\n    }\n    switch (type) {\n    case ANJAY_JSON_LIKE_VALUE_BOOL: {\n        bool value;\n        (void) value;\n        return _anjay_json_like_decoder_bool(decoder, &value);\n    }\n    case ANJAY_JSON_LIKE_VALUE_DOUBLE:\n    case ANJAY_JSON_LIKE_VALUE_FLOAT:\n    case ANJAY_JSON_LIKE_VALUE_NEGATIVE_INT:\n    case ANJAY_JSON_LIKE_VALUE_UINT:\n        return decode_number(decoder, type);\n    case ANJAY_JSON_LIKE_VALUE_BYTE_STRING:\n    case ANJAY_JSON_LIKE_VALUE_TEXT_STRING:\n        return decode_string(decoder);\n    case ANJAY_JSON_LIKE_VALUE_MAP:\n        return decode_map(decoder);\n    case ANJAY_JSON_LIKE_VALUE_ARRAY:\n        return decode_array(decoder);\n    }\n    return 0;\n}\n\nstatic int decode_all(anjay_json_like_decoder_t *decoder) {\n    int result = 0;\n    while (!result) {\n        result = decode_value(decoder);\n    }\n    return result;\n}\n\nint main(int argc, char **argv) {\n    (void) argc;\n    (void) argv;\n\n    avs_stream_t *fp =\n            avs_stream_file_create(\"/dev/stdin\", AVS_STREAM_FILE_READ);\n    if (!fp) {\n        return -1;\n    }\n    anjay_json_like_decoder_t *decoder = _anjay_cbor_decoder_new(fp);\n    if (!decoder) {\n        avs_stream_cleanup(&fp);\n        return -1;\n    }\n    int result = decode_all(decoder);\n    _anjay_json_like_decoder_delete(&decoder);\n    avs_stream_cleanup(&fp);\n    return result;\n}\n"
  },
  {
    "path": "tests/fuzz/test_cases/cbor_decoder/boring",
    "content": "\u0018 cfoocbar\u0003 hwhatever@\t!\u0012o"
  },
  {
    "path": "tests/fuzz/test_cases/cbor_decoder/invalid",
    "content": "x -n\n"
  },
  {
    "path": "tests/fuzz/test_cases/coap_stream/valid_coap_msg",
    "content": "`\u0001\u0005\u0006"
  },
  {
    "path": "tests/integration/CMakeLists.txt",
    "content": "# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\ncmake_minimum_required(VERSION 3.16)\nproject(TestScripts LANGUAGES NONE) # silence the warning\n\nif(WITH_VALGRIND)\n    if(NOT VALGRIND)\n        message(WARNING \"WITH_VALGRIND is on, but VALGRIND variable is empty. Valgrind will be DISABLED in integration tests!\")\n    endif()\n\n    # just inlining ${VALGRIND} results in semicolons instead of spaces\n    string(REPLACE \";\" \" \" VALGRIND_ESC \"${VALGRIND}\")\n    macro(add_valgrind TEST_NAME)\n        set_property(TEST ${TEST_NAME} APPEND PROPERTY ENVIRONMENT \"VALGRIND=${VALGRIND_ESC}\")\n    endmacro()\nelse()\n    macro(add_valgrind)\n    endmacro()\nendif()\n\n### FindPython3\nif(NOT TARGET Python3::Interpreter)\n    include(../../cmake/requirePython3venv.cmake)\nendif()\n\nset(INTEGRATION_TEST_PREFIX \"test_demo_\")\n\nif(TEST_KEEP_SUCCESS_LOGS)\n    set(ADDITIONAL_RUNTEST_ARGS \"--keep-success-logs\")\nelse()\n    set(ADDITIONAL_RUNTEST_ARGS)\nendif()\n\nexecute_process(COMMAND \"${CMAKE_CURRENT_SOURCE_DIR}/runtest.py\" \"-l\"\n                WORKING_DIRECTORY \"${CMAKE_CURRENT_SOURCE_DIR}\"\n                RESULT_VARIABLE RUNTEST_LIST_RESULT\n                OUTPUT_VARIABLE RUNTEST_LIST_OUTPUT)\n\nif(NOT RUNTEST_LIST_RESULT EQUAL 0)\n    message(FATAL_ERROR \"runtest.py -l failed\")\nendif()\n\nstring(REPLACE \"\\n\" \";\" RUNTEST_LIST_OUTPUT \"${RUNTEST_LIST_OUTPUT}\")\nlist(SORT RUNTEST_LIST_OUTPUT)\nforeach(TEST_CASE_ENTRY IN LISTS RUNTEST_LIST_OUTPUT)\n    if(TEST_CASE_ENTRY MATCHES \"^\\\\* .*$\")\n        string(REGEX REPLACE \"^\\\\* \" \"\" TEST_CASE_ENTRY \"${TEST_CASE_ENTRY}\")\n        add_test(${INTEGRATION_TEST_PREFIX}${TEST_CASE_ENTRY}\n                 ${CMAKE_CURRENT_SOURCE_DIR}/runtest.py \"^${TEST_CASE_ENTRY}\\\\\\$\"\n                                                        --client=${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/demo\n                                                        ${ADDITIONAL_RUNTEST_ARGS})\n\n        # The longest test is 5 minutes, so set the timeout to 6 minutes\n        set_property(TEST ${INTEGRATION_TEST_PREFIX}${TEST_CASE_ENTRY} PROPERTY TIMEOUT 360)\n\n        add_valgrind(${INTEGRATION_TEST_PREFIX}${TEST_CASE_ENTRY})\n    endif()\nendforeach()\n\nif(NOT MBEDTLS_LIBRARY)\n    message(FATAL_ERROR \"mbed TLS is required for integration tests\")\nendif()\n\nset(PYMBEDTLS_SRC_DIR \"${CMAKE_CURRENT_SOURCE_DIR}/../../tools/test-framework-tools/pymbedtls\")\n\nadd_custom_target(pymbedtls COMMAND\n                  env \"MBEDTLS_ROOT_DIR=${MBEDTLS_ROOT_DIR}\"\n                  python3 -m pip install \"${PYMBEDTLS_SRC_DIR}\")\n\nset(WORKING_DIRECTORY \"${CMAKE_CURRENT_BINARY_DIR}\")\nconfigure_file(\"run_tests.sh.in\" \"run_tests.sh\")\nadd_custom_target(integration_check\n                  COMMAND ./run_tests.sh\n                  DEPENDS demo pymbedtls)\nadd_custom_target(integration_check_hsm\n                  COMMAND ./run_tests.sh -h\n                  DEPENDS demo pymbedtls)\nadd_dependencies(check integration_check)\n"
  },
  {
    "path": "tests/integration/framework/framework/__init__.py",
    "content": "# -*- coding: utf-8 -*-\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n"
  },
  {
    "path": "tests/integration/framework/framework/asserts.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nimport collections\nfrom typing import Optional\nfrom urllib import response\n\nfrom framework_tools.lwm2m.messages import *\nfrom framework_tools.lwm2m.coap.transport import Transport\n\nfrom .test_utils import DEMO_ENDPOINT_NAME\n\nclass Lwm2mAsserts:\n    def assertLwm2mPathValid(self, path):\n        \"\"\"\n        Convenience assert that checks if a byte-string PATH is in the form\n        /0/1/2. The PATH may contain 1-3 16bit integer segments.\n        \"\"\"\n        self.assertEqual('/', path[0],\n                         ('LwM2M path %r does not start with /' % (path,)))\n\n        segments = path[1:].split('/')\n        if len(segments) > 3:\n            self.fail(\n                'LwM2M path too long (expected at most 3 segments): %r' % (path,))\n\n        for segment in segments:\n            try:\n                self.assertTrue(0 <= int(segment) <= 2 ** 16 - 1,\n                                ('LwM2M path segment not in range [0, 65535] '\n                                 'in path %r' % (path,)))\n            except ValueError:\n                self.fail('segment rs is not an integer in link: %r' %\n                          (segment, path))\n\n    def assertLinkListValid(self, link_list):\n        \"\"\"\n        Convenience assert that checks if a byte-string LINK_LIST is in a CoRE\n        Link format https://tools.ietf.org/html/rfc6690 and all links are\n        valid LwM2M paths.\n        \"\"\"\n        if link_list == '':\n            self.fail('empty link list')\n\n        for obj in link_list.split(','):\n            path, *query = obj.split(';')\n            self.assertTrue((len(path) >= len('</0>')\n                             and path[0] == '<'\n                             and path[-1] == '>'),\n                            'invalid link: %r in %r' % (obj, link_list))\n            self.assertLwm2mPathValid(path[1:-1])\n            # TODO: check query strings\n\n    def assertMsgEqual(self, expected, actual, msg=None):\n        \"\"\"\n        Convenience assert that checks if ACTUAL Lwm2mMsg object matches\n        EXPECTED one.\n\n        ACTUAL and EXPECTED may have their MSG_ID, TOKEN, OPTIONS or CONTENT\n        fields set to lwm2m.messages.ANY, in which case the value will not\n        be checked.\n        \"\"\"\n        msg_prefix = msg + ': ' if msg else ''\n\n        try:\n            if actual.version is not None:\n                self.assertEqual(expected.version, actual.version,\n                                 msg_prefix + 'unexpected CoAP version')\n            if actual.type is not None:\n                self.assertEqual(expected.type, actual.type,\n                                 msg_prefix + 'unexpected CoAP type')\n            self.assertEqual(expected.code, actual.code,\n                             msg_prefix + 'unexpected CoAP code')\n\n            if expected.msg_id is not ANY and actual.msg_id is not ANY and actual.msg_id is not None:\n                self.assertEqual(expected.msg_id, actual.msg_id,\n                                 msg_prefix + 'unexpected CoAP message ID')\n            if expected.token is not ANY and actual.token is not ANY:\n                self.assertEqual(expected.token, actual.token,\n                                 msg_prefix + 'unexpected CoAP token')\n            if expected.options is not ANY and actual.options is not ANY:\n                self.assertEqual(expected.options, actual.options,\n                                 msg_prefix + 'unexpected CoAP option list')\n            if expected.content is not ANY and actual.content is not ANY:\n                self.assertEqual(expected.content, actual.content,\n                                 msg_prefix + 'unexpected CoAP content')\n        except AssertionError as e:\n            e.args = (e.args[0] + ('\\n\\n*** Expected ***\\n%s\\n*** Actual ***\\n%s\\n'\n                                   % (str(expected), str(actual))),) + e.args[1:]\n            raise\n\n    DEFAULT_REGISTER_ENDPOINT = '/rd/demo'\n\n    def assertTcpCsm(self, server=None):\n        serv = server or self.serv\n        pkt = serv.recv()\n        self.assertEqual(coap.Code.SIGNALING_CSM, pkt.code)\n        serv.send(coap.Packet(code=coap.Code.SIGNALING_CSM, token=b''))\n        return pkt\n\n    @staticmethod\n    def _expected_register_message(version, endpoint, lifetime, binding, lwm2m11_queue_mode):\n        # Note: the specific order of Uri-Query options does not matter, but\n        # our packet equality comparator does not distinguish betwen \"ordered\"\n        # and \"unordered\" options, so we expect a specific order of these\n        # query-strings. dict() does not guarantee the order of items until\n        # 3.7, so because we want to still work on 3.5, an explicitly ordered\n        # list is used instead.\n        query = [\n            'lwm2m=%s' % (version,),\n            'ep=%s' % (endpoint,),\n            'lt=%s' % (lifetime if lifetime is not None else 86400,)\n        ]\n        if binding is not None:\n            query.append('b=%s' % (binding,))\n        if lwm2m11_queue_mode:\n            query.append('Q')\n        return Lwm2mRegister('/rd?' + '&'.join(query))\n\n    def assertDemoRegisters(self,\n                            server=None,\n                            version='1.0',\n                            location=DEFAULT_REGISTER_ENDPOINT,\n                            endpoint=DEMO_ENDPOINT_NAME,\n                            lifetime=None,\n                            timeout_s=-1,\n                            respond=True,\n                            binding=None,\n                            lwm2m11_queue_mode=False,\n                            reject=False,\n                            response_filter=None):\n        # passing a float instead of an integer results in a disaster\n        # (serializes as e.g. lt=4.0 instead of lt=4), which makes the\n        # assertion fail\n        if lifetime is not None:\n            self.assertIsInstance(\n                lifetime, int, msg=\"lifetime MUST be an integer\")\n\n        serv = server or self.serv\n\n        pkt = serv.recv(timeout_s=timeout_s, filter=response_filter)\n        self.assertMsgEqual(self._expected_register_message(\n            version, endpoint, lifetime, binding, lwm2m11_queue_mode), pkt)\n        self.assertIsNotNone(pkt.content)\n        self.assertGreater(len(pkt.content), 0)\n        if respond:\n            if reject:\n                serv.send(Lwm2mErrorResponse(\n                    code=coap.Code.RES_UNAUTHORIZED, msg_id=pkt.msg_id, token=pkt.token))\n            else:\n                serv.send(Lwm2mCreated(location=location,\n                          msg_id=pkt.msg_id, token=pkt.token))\n        return pkt\n\n    def assertDemoUpdatesRegistration(self,\n                                      server=None,\n                                      location=DEFAULT_REGISTER_ENDPOINT,\n                                      lifetime: Optional[int] = None,\n                                      binding: Optional[str] = None,\n                                      sms_number: Optional[str] = None,\n                                      content: bytes = b'',\n                                      timeout_s: float = -1,\n                                      respond: bool = True,\n                                      response_filter=None):\n        serv = server or self.serv\n\n        query_args = (([('lt', lifetime)] if lifetime is not None else [])\n                      + ([('sms', sms_number)] if sms_number is not None else [])\n                      + ([('b', binding)] if binding is not None else []))\n        query_string = '&'.join('%s=%s' % tpl for tpl in query_args)\n\n        path = location\n        if query_string:\n            path += '?' + query_string\n\n        pkt = serv.recv(timeout_s=timeout_s, filter=response_filter)\n        self.assertMsgEqual(Lwm2mUpdate(path, content=content), pkt)\n        if respond:\n            serv.send(Lwm2mChanged.matching(pkt)())\n        return pkt\n\n    def assertDemoDeregisters(self, server=None, path=DEFAULT_REGISTER_ENDPOINT, timeout_s=-1, reset=True):\n        serv = server or self.serv\n\n        pkt = serv.recv(timeout_s=timeout_s)\n        self.assertMsgEqual(Lwm2mDeregister(path), pkt)\n\n        serv.send(Lwm2mDeleted(msg_id=pkt.msg_id, token=pkt.token))\n        if reset:\n            serv.reset()\n\n    def assertDemoRequestsBootstrap(self, uri_path='', uri_query=None, respond_with_error_code=None,\n                                    endpoint=DEMO_ENDPOINT_NAME, timeout_s=-1, preferred_content_format=None):\n        pkt = self.bootstrap_server.recv(timeout_s=timeout_s)\n        self.assertMsgEqual(Lwm2mRequestBootstrap(endpoint_name=endpoint,\n                                                  preferred_content_format=preferred_content_format,\n                                                  uri_path=uri_path,\n                                                  uri_query=uri_query), pkt)\n        if respond_with_error_code is None:\n            self.bootstrap_server.send(Lwm2mChanged.matching(pkt)())\n        else:\n            self.bootstrap_server.send(Lwm2mErrorResponse.matching(\n                pkt)(code=respond_with_error_code))\n\n    def assertDemoRequestsBootstrapPack(self, uri_path='', uri_query=None, respond_with_error_code=None,\n                                        endpoint=DEMO_ENDPOINT_NAME, timeout_s=-1, ):\n        pkt = self.bootstrap_server.recv(timeout_s=timeout_s)\n        assert (isinstance(pkt, Lwm2mBootstrapPackRequest))\n\n        self.assertEqual([coap.Option.ACCEPT(coap.ContentFormat.APPLICATION_LWM2M_SENML_CBOR)],\n                         pkt.get_options(coap.Option.ACCEPT))\n\n        if respond_with_error_code is not None:\n            self.bootstrap_server.send(Lwm2mErrorResponse.matching(\n                pkt)(code=respond_with_error_code))\n        else:\n            return pkt\n\n    def assertDtlsReconnect(self, server=None, timeout_s=-1, deadline=None, expected_error='0x6780'):\n        serv = server or self.serv\n\n        with self.assertRaises(RuntimeError) as raised:\n            serv.recv(timeout_s=timeout_s, deadline=deadline)\n        # -0x6780 == MBEDTLS_ERR_SSL_CLIENT_RECONNECT\n        if isinstance(expected_error, str):\n            expected_error = [expected_error]\n        self.assertTrue(any(err in raised.exception.args[0] for err in expected_error))\n\n    def assertPktIsDtlsClientHello(self, pkt, seq_number=ANY):\n        if seq_number is not ANY and seq_number >= 2 ** 48:\n            raise RuntimeError(\n                \"Sorry, encoding of sequence number greater than 2**48 - 1 is not supported\")\n\n        allowed_headers = set()\n        for version in (b'\\xfe\\xfd', b'\\xfe\\xff'):  # DTLS v1.0 or DTLS v1.2\n            header = b'\\x16'  # Content Type: Handshake\n            header += version\n            header += b'\\x00\\x00'  # Epoch: 0\n            if seq_number is not ANY:\n                # Sequence number is 48bit in length.\n                header += seq_number.to_bytes(48 // 8, byteorder='big')\n            allowed_headers.add(header)\n\n        self.assertIn(pkt[:len(next(iter(allowed_headers)))], allowed_headers)\n"
  },
  {
    "path": "tests/integration/framework/framework/create_package.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nimport binascii\nimport enum\nimport struct\nfrom typing import List, Optional\n\n\nclass PackageForcedError():\n    @enum.unique\n    class Firmware(enum.IntEnum):\n        NoError = 0\n        OutOfMemory = 1\n        FailedUpdate = 2\n        DelayedSuccess = 3\n        DelayedFailedUpdate = 4\n        SetSuccessInPerformUpgrade = 5\n        SetFailureInPerformUpgrade = 6\n        DoNothing = 7\n        Defer = 8\n\n    @enum.unique\n    class Software(enum.IntEnum):\n        NoError = 0\n        FailedInstall = 1\n        DelayedSuccessInstall = 2\n        DelayedFailedInstall = 3\n        SuccessInPerformInstall = 4\n        SuccessInPerformInstallActivate = 5\n        FailureInPerformInstall = 6\n        FailureInPerformUninstall = 7\n        FailureInPerformActivate = 8\n        FailureInPerformDeactivate = 9\n        FailureInPerformPrepareForUpdate = 10\n        DoNothing = 11\n\n\nLINKED_SLOTS = 8\nIMG_VER_STRLEN_MAX = 24  # 255.255.65535.4294967295\n\nHEADER_VER_FW_SW = 2\nHEADER_VER_AFU_SINGLE = 3\nHEADER_VER_AFU_MULTI = 4\n\n\ndef _make_package(\n        binary: bytes,\n        magic: bytes,\n        crc: Optional[int],\n        force_error: PackageForcedError,\n        pkg_version: bytes,\n        linked: list):\n    assert len(magic) == 8\n    assert len(pkg_version) <= IMG_VER_STRLEN_MAX\n\n    if crc is None:\n        crc = binascii.crc32(binary)\n\n    if magic == b'ANJAY_SW':\n        assert not linked, 'Linked instances provided with wrong magic'\n        assert force_error in PackageForcedError.Software, 'Wrong forced error for software package'\n        return struct.pack(f'>8sHHIB{len(pkg_version)}s', magic, HEADER_VER_FW_SW, force_error,\n                            crc, len(pkg_version), pkg_version) + binary\n    elif magic == b'ANJAY_FW':\n        assert not linked, 'Linked instances provided with wrong version of package format'\n        assert force_error in PackageForcedError.Firmware, 'Wrong forced error for firmware package'\n        return struct.pack(f'>8sHHIB{len(pkg_version)}s', magic, HEADER_VER_FW_SW, force_error, \n                           crc, len(pkg_version), pkg_version) + binary\n    elif magic in [b'AJAY_APP', b'AJAY_TEE', b'AJAYBOOT', b'AJAYMODE']:\n        assert len(linked) <= LINKED_SLOTS\n        assert force_error in PackageForcedError.Firmware, 'Wrong forced error for firmware package'\n        # Fill remaining linked slots with 0xFF or all slots if nothing provided.\n        # Demo understands 0xFF as no linked instance.\n        return struct.pack(f'>8sHHI{LINKED_SLOTS}sB{len(pkg_version)}s', magic, HEADER_VER_AFU_SINGLE, force_error,\n            crc, bytes(linked + [0xFF] *(LINKED_SLOTS - len(linked))), len(pkg_version), pkg_version) + binary\n    else:\n        raise ValueError('Unknown magic')\n\n\ndef make_firmware_package(\n        binary: bytes,\n        magic: bytes = b'ANJAY_FW',\n        crc: Optional[int] = None,\n        force_error: PackageForcedError.Firmware = PackageForcedError.Firmware.NoError,\n        pkg_version: bytes = b'1.0',\n        linked: list = []):\n    return _make_package(binary, magic, crc, force_error, pkg_version, linked)\n\n\ndef make_software_package(\n        binary: bytes,\n        crc: Optional[int] = None,\n        force_error: PackageForcedError.Software = PackageForcedError.Software.NoError):\n    return _make_package(binary, b'ANJAY_SW', crc, force_error, b'1.0', [])\n\n\ndef make_multiple_firmware_package(binary: List[bytes],\n                                   magic: bytes = b'MULTIPKG'):\n    meta = struct.pack(f'>8sHH{\"I\" * len(binary)}', magic, HEADER_VER_AFU_MULTI,\n                       len(binary), *[len(pkg) for pkg in binary])\n    package = meta\n    for pkg in binary:\n        package += pkg\n    return package\n\n\nif __name__ == '__main__':\n    import argparse\n\n    parser = argparse.ArgumentParser(\n        description='Create package from executable.')\n    parser.add_argument(\n        '-i',\n        '--in-file',\n        help='Path to the input executable. Default: stdin',\n        default='/dev/stdin')\n    parser.add_argument(\n        '-o',\n        '--out-file',\n        help='Path to save package to. If exists, it will be overwritten. Default: stdout',\n        default='/dev/stdout')\n    parser.add_argument(\n        '-m',\n        '--magic',\n        type=str,\n        help='Set package magic bytes to determine package type. '\n        'To create package for Firmware Update set magic to \"ANJAY_FW\". '\n        'To create package for Software Management set magic to \"ANJAY_SW\".'\n        'To create package for Advanced Firmware Update, the following magics'\n        'are possible: \"AJAY_APP\", \"AJAY_TEE\", \"AJAYBOOT\", \"AJAYMODE\", which'\n        'corresponds respectively with instances 1, 2, 3, and 4 of object /33629.'\n        'Any other magics will be rejected. Default: \"ANJAY_FW\".',\n        default='ANJAY_FW')\n    parser.add_argument(\n        '-c',\n        '--crc',\n        type=int,\n        help='Override CRC32 checksum value in package metadata with given value.',\n        default=None)\n    parser.add_argument(\n        '-e',\n        '--force-error',\n        type=str,\n        help=(\n            'Create a package that causes update failure. Possible values for Firmware Update and Advanced Firmware Update: ' +\n            ', '.join(\n                e.name for e in PackageForcedError.Firmware.__members__.values()) +\n            '. Possible values for Software Management: ' +\n            ', '.join(\n                e.name for e in PackageForcedError.Software.__members__.values())),\n        default=None)\n    parser.add_argument(\n        '-l',\n        '--linked-instances',\n        type=int,\n        nargs='+',\n        help='Add instances that are linked to package. Possible usage: -l 1 2 3, means that instances '\n            f'/33629/1, /33629/2 and /33629/3 are linked to package. Max amount of instances is {LINKED_SLOTS}.',\n        default=0)\n    parser.add_argument(\n        '-v',\n        '--version',\n        type=str,\n        help='Set package version. Version can be provided in SemVer format. Max version length is 24 chars.'\n        'For the Firmware Update and Software Management objects, the package will only pass validation if'\n        'its version is set to 1.0. Default: \"1.0\"',\n        default=\"1.0\")\n    \n    args = parser.parse_args()\n\n    if args.force_error is None:\n        args.force_error = PackageForcedError.Software.NoError if args.magic == 'ANJAY_SW' else PackageForcedError.Firmware.NoError\n    else:\n        try:\n            if args.magic == 'ANJAY_SW':\n                args.force_error = PackageForcedError.Software[args.force_error]\n            else:\n                args.force_error = PackageForcedError.Firmware[args.force_error]\n        except KeyError:\n            raise ValueError(f'Unsupported forced error: {args.force_error} for {args.magic}')\n    \n    linked = args.linked_instances if type(\n        args.linked_instances) == list else []\n\n    with open(args.in_file, 'rb') as in_file, open(args.out_file, 'wb') as out_file:\n        out_file.write(_make_package(in_file.read(),\n                                     magic=args.magic.encode('ascii'),\n                                     crc=args.crc,\n                                     force_error=args.force_error,\n                                     pkg_version=args.version.encode('ascii'),\n                                     linked=linked))\n"
  },
  {
    "path": "tests/integration/framework/framework/create_xlsx_test_report.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nimport argparse\nimport enum\nimport os\nimport re\nimport sys\nimport collections\nfrom operator import itemgetter\n\nfrom openpyxl import Workbook\nfrom openpyxl.utils import get_column_letter\nfrom openpyxl.worksheet.table import Table, TableStyleInfo\nfrom openpyxl.styles import Alignment, Font, PatternFill, Border, Side\n\n\nColumnValues = collections.namedtuple(\"ColumnValues\", [\"int\", \"name\"])\n\nclass ColumnIndex(enum.Enum):\n    TYPE        = ColumnValues(1, \"TYPE\")\n    SUITE       = ColumnValues(2, \"SUITE\")\n    NAME        = ColumnValues(3, \"NAME\")\n    STATUS      = ColumnValues(4, \"STATUS\")\n    TIME        = ColumnValues(5, \"TIME [s]\")\n    FAIL_REASON = ColumnValues(6, \"FAIL REASON\")\n\n    def to_letter(self):\n        return chr(ord('A') - 1 + self.value.int)\n\n# Assert that the FAIL_REASON has the largest value\nassert all(ColumnIndex.FAIL_REASON.value.int >= member.value.int for member in ColumnIndex), \\\n    \"FAIL_REASON should be the largest value\"\n\n# Assert that integer values are in ascending order (1, 2, 3 ...)\nfor i, elem in enumerate(ColumnIndex):\n    assert(elem.value.int == i + 1), \\\n        \"ColumnIndex integer values should be arranged in ascending order\"\n\n\nclass TestResultParser():\n    def __init__(self, log_file_path) -> None:\n        self.log_file_path = log_file_path\n\n    def get_test_results(self):\n        test_type, test_suite = self._get_test_type_and_suite()\n        file_content = self._read_log_file_content()\n        if file_content is None or len(file_content) == 0:\n            return None\n        test_name, test_status, test_time = self._get_result_info(file_content)\n        fail_reason = self._get_fail_reason(test_status, file_content)\n\n        ret_result = [None] * len(ColumnIndex)\n        ret_result[ColumnIndex.TYPE.value.int - 1] = test_type\n        ret_result[ColumnIndex.SUITE.value.int - 1] = test_suite\n        ret_result[ColumnIndex.NAME.value.int - 1] = test_name\n        ret_result[ColumnIndex.STATUS.value.int - 1] = test_status\n        ret_result[ColumnIndex.TIME.value.int - 1] = test_time\n        ret_result[ColumnIndex.FAIL_REASON.value.int - 1] = fail_reason\n        return ret_result\n\n    def _status_is_fail_or_error(self, status):\n        if status == \"FAIL\" or status == \"ERROR\":\n            return True\n        return False\n\n    def _read_log_file_content(self):\n        if not os.path.exists(self.log_file_path):\n            return None\n        with open(self.log_file_path, 'r') as file:\n            lines = file.readlines()\n\n        return lines\n\n    def _get_result_info(self, file_content):\n        # an example success log file may look like this:\n            #  SmsDtls  . . . . . . . . . . . . . . . . . . . . . . . . . . . OK (34.34s)\n        # an example FAIL/ERROR log file may look like this:\n            #  NonconfirmableExecuteTest  . . . . . . . . . . . . . . . . . . FAIL\n            # Test NonconfirmableExecuteTest failed!\n            # (assertion description...)\n            #\n            # Stack trace:\n            # (python stack trace...)\n        result_line = file_content[0].replace('. ', '').replace(\"  \", \" \").strip().split(\" \")\n        test_name = result_line[0]\n        test_status = result_line[1]\n        try:\n            test_time = result_line[2][1:-2]\n        except:\n            test_time = None\n\n        return test_name, test_status, test_time\n\n    def _has_whitespaces_only(self, line):\n        if re.match(r'^\\s*$', line):\n            return True\n        return False\n\n    def _get_fail_reason(self, status, file_content):\n        if not self._status_is_fail_or_error(status):\n            return \"\"\n        ret = []\n        for line in file_content:\n            if line == \"Stack trace:\\n\":\n                break\n            if self._has_whitespaces_only(line):\n                continue\n            ret.append(line)\n\n        return ''.join(ret[1:]).strip()\n\n    def _get_test_type_and_suite(self):\n        directory_name = os.path.basename(os.path.dirname(self.log_file_path)).split('.')\n        test_type = directory_name[0]\n        test_suite = '.'.join(directory_name[1:])\n\n        return test_type, test_suite\n\n\nclass XlsxFileCreator():\n    def __init__(self) -> None:\n        self.wb = Workbook()\n        self.ws = self.wb.active\n        self.number_of_rows = 0\n\n    def append_test_results(self, test_results):\n        sorted_results = self._sort_test_results(test_results)\n        for result in sorted_results:\n            if result is not None:\n                self.ws.append(result)\n                self.number_of_rows += 1\n\n    def perform_formating(self):\n        self._apply_vertical_center_alignment()\n        self._format_first_row()\n        self._adjust_rows_height()\n        self._adjust_columns_width()\n        self._add_status_colors()\n        self._format_time_as_numbers()\n        self._add_borders()\n        self._format_as_table()\n\n    def add_header(self):\n        header = []\n        for elem in ColumnIndex:\n            header.append(elem.value.name)\n        self.ws.append(header)\n        self.number_of_rows += 1\n\n    def _sort_test_results(self, unsorted_results):\n        sorted_results = sorted(unsorted_results, key=itemgetter(ColumnIndex.TYPE.value.int - 1,\n                                                                 ColumnIndex.SUITE.value.int - 1,\n                                                                 ColumnIndex.NAME.value.int - 1))\n        return sorted_results\n\n    def _adjust_rows_height(self):\n        for row in self.ws.iter_rows():\n            max_height = 0\n            for cell in row:\n                try:\n                    # NOTE: partially based on:\n                    # https://stackoverflow.com/questions/39529662/python-automatically-adjust-width-of-an-excel-files-columns\n                    cell_height = len(str(cell.value).split('\\n')) * 13 * cell.font.size/10\n                    if cell_height > max_height:\n                        max_height = cell_height\n                except:\n                    pass\n            self.ws.row_dimensions[row[0].row].height = max_height\n\n    def _adjust_columns_width(self):\n        for column in self.ws.iter_cols():\n            max_length = 0\n            column_letter = get_column_letter(column[0].column)\n            for cell in column:\n                try:\n                    # NOTE: partially based on:\n                    # https://stackoverflow.com/questions/39529662/python-automatically-adjust-width-of-an-excel-files-columns\n                    row_width = len(cell.value) * cell.font.size/10\n                    if row_width > max_length:\n                        max_length = row_width\n                        if cell.font.bold == True:\n                            max_length *= 1.1\n                except:\n                    pass\n            adjusted_width = (max_length + 3)\n            self.ws.column_dimensions[column_letter].width = adjusted_width\n\n    def _apply_vertical_center_alignment(self):\n        for row in self.ws.iter_rows():\n            for cell in row:\n                cell.alignment = Alignment(vertical='center')\n        for col in self.ws.iter_cols(min_col=1, max_col=len(ColumnIndex) - 1):\n            for cell in col:\n                cell.alignment = Alignment(horizontal='center', vertical='center')\n\n    def _format_first_row(self):\n        fill = PatternFill(start_color=\"729FCF\", end_color=\"729FCF\", fill_type=\"solid\")\n        for cell in self.ws[1]:\n            cell.font = Font(size=15, bold=True)\n            cell.alignment = Alignment(horizontal='center', vertical='center')\n            cell.fill = fill\n\n    def _add_status_colors(self):\n        ok_fill   = PatternFill(start_color=\"A1FF41\", end_color=\"A1FF41\", fill_type=\"solid\")\n        fail_fill = PatternFill(start_color=\"E11941\", end_color=\"E11941\", fill_type=\"solid\")\n        skip_fill = PatternFill(start_color=\"FDF362\", end_color=\"FDF362\", fill_type=\"solid\")\n        for col in self.ws.iter_cols(min_col=ColumnIndex.STATUS.value.int, max_col=ColumnIndex.STATUS.value.int):\n            for cell in col:\n                if cell.value == \"OK\":\n                    cell.fill = ok_fill\n                elif cell.value == \"FAIL\" or cell.value == \"ERROR\":\n                    cell.fill = fail_fill\n                elif cell.value == \"SKIP\":\n                    cell.fill = skip_fill\n\n    def _add_borders(self):\n        border = Border(\n            left   = Side(border_style=\"thin\", color=\"000000\"),\n            right  = Side(border_style=\"thin\", color=\"000000\"),\n            top    = Side(border_style=\"thin\", color=\"000000\"),\n            bottom = Side(border_style=\"thin\", color=\"000000\"),\n        )\n        for row in self.ws.iter_rows():\n            for cell in row:\n                cell.border = border\n\n    def _format_time_as_numbers(self):\n        for col in self.ws.iter_cols(min_col=ColumnIndex.TIME.value.int, max_col=ColumnIndex.TIME.value.int):\n            for cell in col:\n                if cell.value != ColumnIndex.TIME.value.name and cell.value != None:\n                    cell.number_format = \"0.00\"\n                    cell.value = float(cell.value)\n\n    def _format_as_table(self):\n        ref = \"A1:\" + list(ColumnIndex)[-1].to_letter() + str(self.number_of_rows)\n        table = Table(displayName=\"Table\", ref=ref)\n\n        style = TableStyleInfo(\n            name=\"TableStyleMedium9\", showFirstColumn=False,\n            showLastColumn=False, showRowStripes=False, showColumnStripes=False\n        )\n        table.tableStyleInfo = style\n\n        self.ws.add_table(table)\n\n\ndef enumerate_log_files(path):\n    for root, _, files in os.walk(path, topdown=False):\n        for file in files:\n            if re.match(r'.*\\.log$', file):\n                yield os.path.join(root, file)\n\n\ndef _main():\n    parser = argparse.ArgumentParser(description='Create .xlsx file out of output .log files',\n                                     formatter_class=argparse.ArgumentDefaultsHelpFormatter)\n    parser.add_argument('-d', '--log-dir',\n                        help='directory that contains output .log files',\n                        required=True)\n    args = parser.parse_args()\n\n    test_results = []\n    for log_file_path in enumerate_log_files(args.log_dir):\n        result_parser = TestResultParser(log_file_path)\n        test_results.append(result_parser.get_test_results())\n\n    xlsx_file = XlsxFileCreator()\n    xlsx_file.add_header()\n    xlsx_file.append_test_results(test_results)\n\n    xlsx_file.perform_formating()\n\n    output_path = os.path.join(args.log_dir, \"../..\", \"integration_test_results.xlsx\")\n    xlsx_file.wb.save(output_path)\n\n    print(f\"{os.path.basename(__file__)}: saved test reports to {os.path.abspath(output_path)}\")\n\nif __name__ == '__main__':\n    sys.exit(_main())\n"
  },
  {
    "path": "tests/integration/framework/framework/lwm2m_test.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nfrom . import test_suite\nfrom .test_utils import *\n\nfrom framework_tools.lwm2m import coap\nfrom framework_tools.lwm2m.server import Lwm2mServer\nfrom framework_tools.lwm2m.messages import *\nfrom framework_tools.lwm2m.objlink import Objlink\n"
  },
  {
    "path": "tests/integration/framework/framework/pretty_test_runner.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nimport os\nimport shutil\nimport sys\nimport time\nimport traceback\nimport unittest\n\nfrom .test_suite import get_test_name, get_suite_name, LogType\n\nCOLOR_DEFAULT = '\\033[0m'\nCOLOR_YELLOW = '\\033[0;33m'\nCOLOR_GREEN = '\\033[0;32m'\nCOLOR_RED = '\\033[0;31m'\nCOLOR_BLUE = '\\033[0;34m'\n\n\nclass ResultStream:\n    def __init__(self, stream):\n        width = shutil.get_terminal_size(fallback=(80, 24))[0]\n\n        self.stream = stream\n        self.colorize = os.isatty(stream.fileno())\n\n        self.test_name_indent = 2\n        self.test_result_width = len('OK (999.99s)') + self.test_name_indent\n        self.test_name_limit = width - self.test_name_indent - len(' ') - self.test_result_width\n\n        self.suite_result_width = self.test_result_width\n        self.suite_name_limit = width - len(' ') - self.suite_result_width\n\n    def _write_colored(self, color, text):\n        if self.colorize:\n            self.stream.write('%s%s%s' % (color, text, COLOR_DEFAULT))\n        else:\n            self.stream.write(text)\n\n    @staticmethod\n    def _pad_with_dots(text, desired_length):\n        extra_space = desired_length - len(text) - 1\n        return (text\n                + (' ' if extra_space % 2 else '')\n                + (' .' * (extra_space // 2)))\n\n    @staticmethod\n    def _limit_text_size(text, limit):\n        if len(text) > limit:\n            return text[:limit - 3] + '...'\n        return text\n\n    def write_suite_name(self, suite_name):\n        suite_name = self._limit_text_size(suite_name, self.suite_name_limit)\n\n        self._write_colored(COLOR_YELLOW, suite_name + '\\n')\n\n    def write_suite_result(self, suite_name, result):\n        suite_name = self._limit_text_size(suite_name, self.suite_name_limit)\n        suite_name = self._pad_with_dots(suite_name, self.suite_name_limit)\n\n        color = COLOR_GREEN if result.testsFailed == 0 else COLOR_RED\n        self._write_colored(COLOR_YELLOW, suite_name + ' ')\n        self._write_colored(color, '%d/%d\\n' % (result.testsPassed, result.testsRun))\n\n    def write_test_name(self, test_name):\n        test_name = self._limit_text_size(test_name, self.test_name_limit)\n        test_name = self._pad_with_dots(test_name, self.test_name_limit)\n\n        fmt = '%%%ds%%s ' % (self.test_name_indent)\n        self._write_colored(COLOR_DEFAULT, fmt % ('', test_name))\n\n    def write_test_success(self, seconds_elapsed):\n        time_color = (COLOR_GREEN if seconds_elapsed < 5.0\n                      else COLOR_YELLOW if seconds_elapsed < 30.0\n        else COLOR_RED)\n\n        self._write_colored(COLOR_GREEN, 'OK ')\n        self._write_colored(time_color, '(%.2fs)\\n' % seconds_elapsed)\n\n    def write_test_failure(self, header, test, err):\n        error_msg = 'Type: %s\\nValue: %s\\nStack trace:\\n%s\\n' % (\n            err[0].__name__, err[1], ''.join(traceback.format_tb(err[2])))\n\n        self._write_colored(COLOR_RED, header + '\\nTest ')\n        self._write_colored(COLOR_YELLOW, get_test_name(test))\n        self._write_colored(COLOR_RED, ' failed!\\n')\n        self._write_colored(COLOR_DEFAULT, error_msg)\n\n    def write_test_skip(self, reason):\n        text = 'SKIP\\n%s%s\\n' % (' ' * (self.test_name_indent * 2), reason)\n        self._write_colored(COLOR_BLUE, text)\n\n\nclass PrettyTestResult(unittest.TestResult):\n    def __init__(self, suite, stream, logdir, colorize=False):\n        unittest.TestResult.__init__(self)\n        self.suite = suite\n        self.stream = stream\n        self.logdir = logdir\n        self.logfile = None\n        self.times = {}\n        self.successes = []\n\n    def createLogFile(self, test):\n        self.finishLogFile()\n        f = open(os.path.join(self.logdir, f\"{get_test_name(test)}.log\"), 'w')\n        self.logfile = ResultStream(f)\n\n    def finishLogFile(self):\n        if getattr(self, 'logfile', None) is not None:\n            self.logfile.stream.close()\n            self.logfile = None\n\n    def startTest(self, test):\n        self.createLogFile(test)\n\n        self.logfile.write_test_name(get_test_name(test))\n        self.stream.write_test_name(get_test_name(test))\n\n        self.testsRun += 1\n        self.times[test] = time.time()\n\n    def addSuccess(self, test):\n        seconds_elapsed = time.time() - self.times[test]\n\n        self.logfile.write_test_success(seconds_elapsed)\n        self.stream.write_test_success(seconds_elapsed)\n        self.successes.append(test)\n\n    def _logError(self, header, test, err):\n        self.logfile.write_test_failure(header, test, err)\n        self.stream.write_test_failure(header, test, err)\n\n    def addError(self, test, err):\n        self._logError('ERROR', test, err)\n        self.errors.append((test, err))\n\n    def addFailure(self, test, err):\n        self._logError('FAIL', test, err)\n        self.failures.append((test, err))\n\n    def addSkip(self, test, reason):\n        self.logfile.write_test_skip(reason)\n        self.stream.write_test_skip(reason)\n\n    def errorSummary(self, log_root):\n        return ('\\n'.join('-----\\n'\n                          '%s:\\n'\n                          '%s\\n'\n                          'Demo log: %s\\n'\n                          'Valgrind: %s\\n'\n                          'PCAP:     %s\\n'\n                          '-----\\n'\n                          % (get_test_name(test),\n                             ''.join(traceback.format_exception(*err)),\n                             test.logs_path(LogType.Console, log_root),\n                             test.logs_path(LogType.Valgrind, log_root),\n                             test.logs_path(LogType.Pcap, log_root))\n                          for test, err in self.errors + self.failures))\n\n    @property\n    def testsPassed(self):\n        return self.testsRun - len(set(x[0] for x in self.errors + self.failures))\n\n    @property\n    def testsErrors(self):\n        # it's possible to get multiple errors from a single test\n        return len(set(x[0] for x in self.errors))\n\n    @property\n    def testsFailed(self):\n        return len(set(x[0] for x in self.failures))\n\n\nclass PrettyTestRunner(unittest.TextTestRunner):\n    def __init__(self, config, stream=sys.stderr):\n        self.stream = ResultStream(stream)\n        self.results = []\n        self.config = config\n\n    def run(self, suite, logdir):\n        \"Run given test suite.\"\n        result = PrettyTestResult(suite, self.stream, logdir)\n\n        suite_name = get_suite_name(suite)\n        self.stream.write_suite_name(suite_name)\n\n        try:\n            suite(result)\n        finally:\n            result.finishLogFile()\n        self.results.append(result)\n\n        self.stream.write_suite_result(suite_name, result)\n        return result\n"
  },
  {
    "path": "tests/integration/framework/framework/serialize_senml_cbor.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nfrom framework_tools.lwm2m.senml_cbor import *\nfrom .test_utils import *\n\n\ndef make_path(oid, iid, rid, riid=None):\n    if riid is None:\n        return f'/{oid}/{iid}/{rid}'\n    else:\n        return f'/{oid}/{iid}/{rid}/{riid}'\n\n\ndef extract_multiresource(oid, iid, rid, multires):\n    return [(make_path(oid, iid, rid, riid), value)\n            for riid, value in multires.items()]\n\n\ndef extract_resource(oid, iid, rid, value):\n    if isinstance(value, dict):\n        return extract_multiresource(oid, iid, rid, value)\n    else:\n        return [(make_path(oid, iid, rid), value)]  # This need to be a list\n        # because a multi resource\n        # case is also a list\n\n\ndef extract_instance(oid, iid, instance):\n    return [\n        res_path_and_value\n        for rid, value in instance.items()\n        for res_path_and_value in extract_resource(oid, iid, rid, value)\n    ]\n\n\ndef extract_objects(oid, object):\n    return [\n        res_path_and_value\n        for iid, instance in object.items()\n        for res_path_and_value in extract_instance(oid, iid, instance)\n    ]\n\n\ndef extract_config(config):\n    return [\n        res_path_and_value\n        for oid, object in config.items()\n        for res_path_and_value in extract_objects(oid, object)\n    ]\n\n\ndef serialize_config(config):\n    serialized = extract_config(config_to_dict(config))\n    out = []\n\n    for path, value in serialized:\n        if isinstance(value, bool):  # bool is treated as int, so check bool first\n            out.append({SenmlLabel.NAME: path, SenmlLabel.BOOL: value})\n        elif isinstance(value, int):\n            out.append({SenmlLabel.NAME: path, SenmlLabel.VALUE: value})\n        elif isinstance(value, str):\n            out.append({SenmlLabel.NAME: path, SenmlLabel.STRING: value})\n        elif isinstance(value, bytes):\n            out.append({SenmlLabel.NAME: path, SenmlLabel.OPAQUE: value})\n        elif isinstance(value, Objlink):\n            out.append({SenmlLabel.NAME: path, SenmlLabel.OBJLNK: str(value)})\n        else:\n            raise TypeError('Unsupported data type')\n\n    return CBOR.serialize(out)\n"
  },
  {
    "path": "tests/integration/framework/framework/test_suite.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nimport enum\nimport inspect\nimport io\nimport logging\nimport os\nimport re\nimport shutil\nimport subprocess\nimport threading\nimport unittest\nfrom typing import TypeVar\n\nfrom framework_tools.lwm2m import coap\nfrom framework_tools.lwm2m.coap.transport import Transport\nfrom framework_tools.lwm2m.tlv import TLV\nfrom framework_tools.lwm2m.messages import *\nfrom framework_tools.lwm2m.server import Lwm2mServer\nfrom .asserts import Lwm2mAsserts\nfrom .test_utils import *\nfrom .lwm2m_test import *\n\ntry:\n    import dpkt\n\n    _DPKT_AVAILABLE = True\nexcept ImportError:\n    _DPKT_AVAILABLE = False\n\nT = TypeVar('T')\n\n\nclass LogType(enum.Enum):\n    Console = 'console'\n    Valgrind = 'valgrind'\n    Pcap = 'pcap'\n\n    def extension(self):\n        if self == LogType.Pcap:\n            return '.pcapng'\n        else:\n            return '.log'\n\n\ndef read_some_with_timeout(fd, timeout_s):\n    import select\n    deadline = time.time() + timeout_s\n    while True:\n        if timeout_s < 0:\n            return b''\n        r, w, x = select.select([fd], [], [fd], timeout_s)\n        if len(r) > 0 or len(x) > 0:\n            buf = fd.read(65536)\n            if buf is not None and len(buf) > 0:\n                return buf\n        timeout_s = deadline - time.time()\n\n\ndef ensure_dir(dir_path):\n    try:\n        os.makedirs(dir_path)\n    except OSError:\n        if not os.path.isdir(dir_path):\n            raise\n\n\nclass CleanupList(list):\n    def __call__(self):\n        def merge_exceptions(old, new):\n            \"\"\"\n            Adds the \"old\" exception as a context of the \"new\" one and returns\n            the \"new\" one.\n\n            If the \"new\" exception already has a context, the \"old\" one is added\n            at the end of the chain, as the context of the innermost exception\n            that does not have a context.\n\n            When the returned exception is rethrown, it will be logged by the\n            standard Python exception formatter as something like:\n\n                Exception: old exception\n\n                During handling of the above exception, another exception occurred:\n\n                Traceback (most recent call last):\n                  ...\n                Exception: new exception\n\n            :param old: \"old\" exception\n            :param new: \"new\" exception\n            :return: \"new\" exception with updated context information\n            \"\"\"\n            tmp = new\n            while tmp.__context__ is not None:\n                if tmp.__context__ is old:\n                    return new\n                tmp = tmp.__context__\n            tmp.__context__ = old\n            return new\n\n        exc = None\n        for cleanup_func in self:\n            try:\n                cleanup_func()\n            except Exception as e:\n                exc = merge_exceptions(exc, e)\n\n        if exc is not None:\n            raise exc\n\n    def __enter__(self):\n        return self\n\n    def __exit__(self, _type, value, _traceback):\n        return self()\n\n\nclass Lwm2mDmOperations(Lwm2mAsserts):\n    def _perform_action(self, server, request, expected_response, timeout_s=None, deadline=None):\n        server.send(request)\n        if timeout_s is None and deadline is None:\n            timeout_s = -1\n\n        if server.transport == Transport.UDP and request.msg_id is not None and request.msg_id is not ANY:\n            filter = lambda pkt: pkt.msg_id == request.msg_id\n        elif expected_response.token is not ANY:\n            filter = lambda pkt: pkt.token == expected_response.token\n        else:\n            filter = lambda pkt: isinstance(pkt, type(expected_response))\n\n        res = server.recv(timeout_s=timeout_s, deadline=deadline, filter=filter)\n        self.assertMsgEqual(expected_response, res)\n        return res\n\n    def _make_expected_res(self, req, success_res_cls, expect_error_code):\n        req.fill_placeholders()\n\n        if expect_error_code is None:\n            return success_res_cls.matching(req)()\n        else:\n            return Lwm2mErrorResponse.matching(req)(code=expect_error_code)\n\n    def create_instance_with_arbitrary_payload(self, server, oid,\n                                               format=coap.ContentFormat.APPLICATION_LWM2M_TLV,\n                                               iid=None, payload=b'', expect_error_code=None,\n                                               **kwargs):\n        if iid is None:\n            raise ValueError(\"IID cannot be None\")\n\n        req = Lwm2mCreate(path='/%d' % oid, content=payload, format=format)\n        expected_res = self._make_expected_res(\n            req, Lwm2mCreated, expect_error_code)\n        return self._perform_action(server, req, expected_res, **kwargs)\n\n    def create_instance_with_payload(self, server, oid, iid=None, payload=b'',\n                                     expect_error_code=None, **kwargs):\n        if iid is None:\n            raise ValueError(\"IID cannot be None\")\n\n        instance_tlv = TLV.make_instance(\n            instance_id=iid, content=payload).serialize()\n        return self.create_instance_with_arbitrary_payload(server=server, oid=oid, iid=iid,\n                                                           payload=instance_tlv,\n                                                           expect_error_code=expect_error_code,\n                                                           **kwargs)\n\n    def create_instance(self, server, oid, iid=None, expect_error_code=None, **kwargs):\n        instance_tlv = None if iid is None else TLV.make_instance(\n            instance_id=iid).serialize()\n        req = Lwm2mCreate('/%d' % oid, instance_tlv)\n        expected_res = self._make_expected_res(\n            req, Lwm2mCreated, expect_error_code)\n        return self._perform_action(server, req, expected_res, **kwargs)\n\n    def create_instance_path(self, server, path, iid=None, expect_error_code=None, **kwargs):\n        instance_tlv = None if iid is None else TLV.make_instance(\n            instance_id=iid).serialize()\n        req = Lwm2mCreate(path, instance_tlv)\n        expected_res = self._make_expected_res(\n            req, Lwm2mCreated, expect_error_code)\n        return self._perform_action(server, req, expected_res, **kwargs)\n\n    def create(self, server, path, expect_error_code=None, **kwargs):\n        req = Lwm2mCreate(Lwm2mPath(path), None)\n        expected_res = self._make_expected_res(\n            req, Lwm2mCreated, expect_error_code)\n        return self._perform_action(server, req, expected_res, **kwargs)\n\n    def delete_instance(self, server, oid, iid, expect_error_code=None, **kwargs):\n        req = Lwm2mDelete('/%d/%d' % (oid, iid))\n        expected_res = self._make_expected_res(\n            req, Lwm2mDeleted, expect_error_code)\n        return self._perform_action(server, req, expected_res, **kwargs)\n\n    def delete(self, server, path, expect_error_code=None, **kwargs):\n        req = Lwm2mDelete(Lwm2mPath(path))\n        expected_res = self._make_expected_res(\n            req, Lwm2mDeleted, expect_error_code)\n        return self._perform_action(server, req, expected_res, **kwargs)\n\n    def read_path(self, server, path, expect_error_code=None, accept=None, **kwargs):\n        req = Lwm2mRead(path, accept=accept)\n        expected_res = self._make_expected_res(\n            req, Lwm2mContent, expect_error_code)\n        return self._perform_action(server, req, expected_res, **kwargs)\n\n    def read_resource_instance(self, server, oid, iid, rid, riid, expect_error_code=None,\n                               accept=None, **kwargs):\n        return self.read_path(server, '/%d/%d/%d/%d' % (oid, iid, rid, riid), expect_error_code,\n                              accept=accept, **kwargs)\n\n    def read_resource(self, server, oid, iid, rid, expect_error_code=None, accept=None, **kwargs):\n        return self.read_path(server, '/%d/%d/%d' % (oid, iid, rid), expect_error_code,\n                              accept=accept, **kwargs)\n\n    def read_instance(self, server, oid, iid, expect_error_code=None, accept=None, **kwargs):\n        return self.read_path(server, '/%d/%d' % (oid, iid), expect_error_code,\n                              accept=accept, **kwargs)\n\n    def read_object(self, server, oid, expect_error_code=None, accept=None, **kwargs):\n        return self.read_path(server, '/%d' % oid, expect_error_code, accept=accept, **kwargs)\n\n    def read_composite(self, server, paths=[], expect_error_code=None, accept=None, **kwargs):\n        req = Lwm2mReadComposite(paths=paths, accept=accept)\n        expected_res = self._make_expected_res(\n            req, Lwm2mContent, expect_error_code)\n        return self._perform_action(server, req, expected_res, **kwargs)\n\n    def observe_composite(self, server, paths=[], expect_error_code=None, accept=None, **kwargs):\n        req = Lwm2mObserveComposite(paths=paths, accept=accept)\n        expected_res = self._make_expected_res(\n            req, Lwm2mContent, expect_error_code)\n        return self._perform_action(server, req, expected_res, **kwargs)\n\n    def write_composite(self, server, content=b'', expect_error_code=None,\n                        format=coap.ContentFormat.APPLICATION_LWM2M_SENML_CBOR,\n                        **kwargs):\n        req = Lwm2mWriteComposite(content=content, format=format)\n        expected_res = self._make_expected_res(\n            req, Lwm2mChanged, expect_error_code)\n        return self._perform_action(server, req, expected_res, **kwargs)\n\n    def write_object(self, server, oid, content=b'', expect_error_code=None,\n                     format=coap.ContentFormat.APPLICATION_LWM2M_TLV, **kwargs):\n        req = Lwm2mWrite('/%d' % (oid,), content, format=format)\n        expected_res = self._make_expected_res(\n            req, Lwm2mChanged, expect_error_code)\n        return self._perform_action(server, req, expected_res, **kwargs)\n\n    def write_instance(self, server, oid, iid, content=b'', partial=False, expect_error_code=None,\n                       format=coap.ContentFormat.APPLICATION_LWM2M_TLV, **kwargs):\n        req = Lwm2mWrite('/%d/%d' % (oid, iid), content,\n                         format=format,\n                         update=partial)\n        expected_res = self._make_expected_res(\n            req, Lwm2mChanged, expect_error_code)\n        return self._perform_action(server, req, expected_res, **kwargs)\n\n    def write_resource(self, server, oid, iid, rid, content=b'', partial=False,\n                       format=coap.ContentFormat.TEXT_PLAIN,\n                       expect_error_code=None, **kwargs):\n        req = Lwm2mWrite('/%d/%d/%d' % (oid, iid, rid), content, format=format,\n                         update=partial)\n        expected_res = self._make_expected_res(\n            req, Lwm2mChanged, expect_error_code)\n        return self._perform_action(server, req, expected_res, **kwargs)\n\n    def write_resource_instance(self, server, oid, iid, rid, riid, content=b'', partial=False,\n                                format=coap.ContentFormat.TEXT_PLAIN,\n                                expect_error_code=None, **kwargs):\n        req = Lwm2mWrite('/%d/%d/%d/%d' % (oid, iid, rid, riid), content, format=format,\n                         update=partial)\n        expected_res = self._make_expected_res(\n            req, Lwm2mChanged, expect_error_code)\n        return self._perform_action(server, req, expected_res, **kwargs)\n\n    def write_path(self, server, path, content=b'', partial=False,\n                                format=coap.ContentFormat.TEXT_PLAIN,\n                                expect_error_code=None, **kwargs):\n        req = Lwm2mWrite(path, content, format=format,\n                         update=partial)\n        expected_res = self._make_expected_res(\n            req, Lwm2mChanged, expect_error_code)\n        return self._perform_action(server, req, expected_res, **kwargs)\n\n    def execute_resource(self, server, oid, iid, rid, content=b'', expect_error_code=None,\n                         **kwargs):\n        req = Lwm2mExecute('/%d/%d/%d' % (oid, iid, rid), content=content)\n        expected_res = self._make_expected_res(\n            req, Lwm2mChanged, expect_error_code)\n        return self._perform_action(server, req, expected_res, **kwargs)\n    \n    def execute_resource_path(self, server, path, content=b'', expect_error_code=None,\n                         **kwargs):\n        req = Lwm2mExecute(path, content=content)\n        expected_res = self._make_expected_res(\n            req, Lwm2mChanged, expect_error_code)\n        return self._perform_action(server, req, expected_res, **kwargs)\n\n    @staticmethod\n    def make_path(*args):\n        def ensure_valid_path(args):\n            import itertools\n            valid_args = list(itertools.takewhile(\n                lambda x: x is not None, list(args)))\n            if not all(x is None for x in args[len(valid_args):]):\n                raise AttributeError\n            return valid_args\n\n        return '/' + '/'.join(map(lambda arg: '%d' % arg, ensure_valid_path(list(args))))\n\n    def discover(self, server, oid=None, iid=None, rid=None, depth=None, expect_error_code=None,\n                 **kwargs):\n        req = Lwm2mDiscover(self.make_path(oid, iid, rid), depth)\n        expected_res = self._make_expected_res(\n            req, Lwm2mContent, expect_error_code)\n        return self._perform_action(server, req, expected_res, **kwargs)\n    \n    def discover_path(self, server, path=None, depth=None, expect_error_code=None,\n                 **kwargs):\n        req = Lwm2mDiscover(path, depth)\n        expected_res = self._make_expected_res(\n            req, Lwm2mContent, expect_error_code)\n        return self._perform_action(server, req, expected_res, **kwargs)\n\n    def observe(self, server, oid=None, iid=None, rid=None, riid=None, expect_error_code=None,\n                **kwargs):\n        req = Lwm2mObserve(\n            Lwm2mDmOperations.make_path(oid, iid, rid, riid), **kwargs)\n        expected_res = self._make_expected_res(\n            req, Lwm2mContent, expect_error_code)\n        return self._perform_action(server, req, expected_res)\n\n    def observe_path(self, server, path, expect_error_code=None,\n                **kwargs):\n        req = Lwm2mObserve(path, **kwargs)\n        expected_res = self._make_expected_res(\n            req, Lwm2mContent, expect_error_code)\n        return self._perform_action(server, req, expected_res)\n\n    def write_attributes(self, server, oid=None, iid=None, rid=None, query=[],\n                         expect_error_code=None, **kwargs):\n        req = Lwm2mWriteAttributes(\n            Lwm2mDmOperations.make_path(oid, iid, rid), query=query)\n        expected_res = self._make_expected_res(\n            req, Lwm2mChanged, expect_error_code)\n        return self._perform_action(server, req, expected_res, **kwargs)\n    \n    def write_attributes_path(self, server, path=None, query=[],\n                         expect_error_code=None, **kwargs):\n        req = Lwm2mWriteAttributes(path, query=query)\n        expected_res = self._make_expected_res(\n            req, Lwm2mChanged, expect_error_code)\n        return self._perform_action(server, req, expected_res, **kwargs)\n\n\nclass Lwm2mTest(unittest.TestCase, Lwm2mAsserts):\n    DEFAULT_MSG_TIMEOUT = 9000.0\n    DEFAULT_COMM_TIMEOUT = 9000.0\n\n    def __init__(self, test_method_name):\n        super().__init__(test_method_name)\n\n        self.servers = []\n        self.bootstrap_server = None\n\n    def setUp(self, extra_cmdline_args=None, psk_identity=None, psk_key=None, client_ca_path=None,\n              client_ca_file=None, server_crt_file=None, server_key_file=None,\n              transport=Transport.UDP, *args, **kwargs):\n        assert ((psk_identity is None) == (psk_key is None))\n        extra_args = []\n        tls_server_kwargs = {'transport': transport}\n        if 'ciphersuites' in kwargs:\n            tls_server_kwargs['ciphersuites'] = kwargs['ciphersuites']\n        if psk_identity:\n            extra_args += ['--identity', str(binascii.hexlify(psk_identity), 'ascii'),\n                           '--key', str(binascii.hexlify(psk_key), 'ascii')]\n            coap_server_builder = lambda: coap.TlsServer(psk_identity=psk_identity, psk_key=psk_key,\n                                                         **tls_server_kwargs)\n        elif server_crt_file:\n            coap_server_builder = lambda: coap.TlsServer(ca_path=client_ca_path,\n                                                         ca_file=client_ca_file,\n                                                         crt_file=server_crt_file,\n                                                         key_file=server_key_file,\n                                                         **tls_server_kwargs)\n        else:\n            coap_server_builder = lambda: coap.Server(transport=transport)\n        if extra_cmdline_args is not None:\n            extra_args += extra_cmdline_args\n\n        if 'servers' not in kwargs:\n            kwargs['servers'] = [Lwm2mServer(coap_server_builder())]\n        elif isinstance(kwargs['servers'], int):\n            servers = []\n            for i in range(kwargs['servers']):\n                servers.append(Lwm2mServer(coap_server_builder()))\n            kwargs['servers'] = servers\n\n        if kwargs.get('bootstrap_server', None) is True:\n            kwargs['bootstrap_server'] = Lwm2mServer(coap_server_builder())\n\n        self.setup_demo_with_servers(extra_cmdline_args=extra_args, *args, **kwargs)\n\n    @unittest.skip\n    def runTest(self):\n        raise NotImplementedError('runTest not implemented')\n\n    def tearDown(self, *args, **kwargs):\n        self.teardown_demo_with_servers(*args, **kwargs)\n\n    def set_config(self, config):\n        self.config = config\n\n    def log_filename(self, extension='.log'):\n        return os.path.join(self.suite_name(), self.test_name() + extension)\n\n    def test_name(self):\n        return self.__class__.__name__\n\n    def suite_name(self):\n        test_root = self.config.suite_root_path or os.path.dirname(\n            os.path.dirname(os.path.abspath(__file__)))\n        name = os.path.abspath(inspect.getfile(type(self)))\n\n        if name.endswith('.py'):\n            name = name[:-len('.py')]\n        name = name[len(test_root):] if name.startswith(test_root) else name\n        name = name.lstrip('/')\n        return name.replace('/', '.')\n\n    def make_demo_args(self,\n                       endpoint_name,\n                       servers,\n                       minimum_version,\n                       maximum_version,\n                       fw_updated_marker_path,\n                       afu_marker_path=None,\n                       afu_original_img_file_path=None,\n                       sw_mgmt_persistence_file=None,\n                       tls_version='TLSv1.2',\n                       ciphersuites=(0x1305, 0x1301, 0xC030, 0xC0A8, 0xC0AE),\n                       forced_client_security_mode=None):\n        \"\"\"\n        Helper method for easy generation of demo executable arguments.\n        \"\"\"\n        # LwM2M 1.2 doesn't specify any TLS 1.3 ciphersuites, but\n        # draft-ietf-uta-tls13-iot-profile-09, section 17 suggests:\n        # 0x1305 = TLS_AES_128_CCM_8_SHA256 - although compatible with CoAP,\n        #          prone to other issues - see the referenced document\n\n        # Additionally, to support ssl Python library (used in tests that use\n        # ssl.SSLContext API):\n        # 0x1301 = TLS_AES_128_GCM_SHA256 - supported by default if TLS 1.3 is\n        #          available\n        # 0xC030 = TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 - compatible with\n        #          shortened list of default TLS 1.2 ciphersuites (Python 3.10)\n\n        # Default ciphersuites mandated by LwM2M:\n        # 0xC0A8 = TLS_PSK_WITH_AES_128_CCM_8\n        # 0xC0AE = TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8\n\n        if forced_client_security_mode == None:\n            security_modes = set(serv.security_mode() for serv in servers)\n\n            self.assertLessEqual(len(security_modes), 1,\n                                'Attempted to mix security modes')\n\n            security_mode = next(iter(security_modes), 'nosec')\n        else:\n            security_mode = forced_client_security_mode\n\n        if security_mode == 'nosec':\n            protocol = 'coap'\n        else:\n            protocol = 'coaps'\n\n        args = ['--endpoint-name', endpoint_name,\n                '-v', minimum_version, '-V', maximum_version,\n                '--security-mode', security_mode]\n        if fw_updated_marker_path is not None:\n            args += ['--fw-updated-marker-path', fw_updated_marker_path]\n        if afu_marker_path is not None:\n            args += ['--afu-marker-path', afu_marker_path]\n        if afu_original_img_file_path is not None:\n            args += ['--afu-original-img-file-path', afu_original_img_file_path]\n        if sw_mgmt_persistence_file is not None:\n            args += ['--sw-mgmt-persistence-file', sw_mgmt_persistence_file]\n        if tls_version is not None:\n            args += ['--tls-version', tls_version]\n        if ciphersuites is not None:\n            args += ['--ciphersuites', ','.join(map(hex, ciphersuites))]\n\n        for serv in servers:\n            if serv.transport == Transport.TCP:\n                protocol += '+' + str(Transport.TCP)\n            args += ['--server-uri', '%s://127.0.0.1:%d' %\n                     (protocol, serv.get_listen_port(),)]\n\n        return args\n\n    def logs_path(self, log_type, log_root=None, **kwargs):\n        assert type(log_type) == LogType\n\n        dir_path = os.path.join(\n            log_root or self.config.logs_path, log_type.value)\n        log_path = os.path.join(dir_path, self.log_filename(\n            **kwargs, extension=log_type.extension()))\n        ensure_dir(os.path.dirname(log_path))\n        return log_path\n\n    def read_log_until_match(self, regex, timeout_s, alt_offset=None):\n        orig_offset = self.demo_process.log_file.tell()\n\n        if alt_offset is not None:\n            self.demo_process.log_file.seek(alt_offset, io.SEEK_SET)\n\n        deadline = time.time() + timeout_s\n        out = bytearray()\n        while True:\n            # Retain only the last two lines - two, because the regexes sometimes check for the end-of-line\n            last_lf = out.rfind(b'\\n')\n            if last_lf >= 0:\n                second_to_last_lf = out.rfind(b'\\n', 0, last_lf)\n                if second_to_last_lf >= 0:\n                    del out[0:second_to_last_lf + 1]\n\n            if self.demo_process.poll() is not None:\n                partial_timeout = 0\n            else:\n                partial_timeout = min(max(deadline - time.time(), 0.0), 1.0)\n\n            out += read_some_with_timeout(self.demo_process.log_file, partial_timeout)\n\n            match = re.search(regex, out)\n            if match:\n                # Move the file pointer to just after the match, in case we've read more\n                move_offset = match.end() - len(out)\n\n                if move_offset != 0:\n                    assert move_offset < 0\n                    self.demo_process.log_file.seek(move_offset, io.SEEK_CUR)\n\n                if alt_offset is not None:\n                    new_alt_offset = self.demo_process.log_file.tell()\n                    self.demo_process.log_file.seek(orig_offset, io.SEEK_SET)\n                    return new_alt_offset, match\n\n                return match\n            elif partial_timeout <= 0.0:\n                self.demo_process.log_file.seek(orig_offset, io.SEEK_SET)\n                return (alt_offset, None) if alt_offset is not None else None\n\n    def _get_valgrind_args(self):\n        import shlex\n\n        valgrind_list = []\n        if 'VALGRIND' in os.environ and os.environ['VALGRIND']:\n            valgrind_list = shlex.split(os.environ['VALGRIND'])\n            valgrind_list += ['--log-file=' + self.logs_path(LogType.Valgrind)]\n\n        return valgrind_list\n\n    def _get_demo_executable(self):\n        demo_executable = os.path.join(\n            self.config.demo_path, self.config.demo_cmd)\n\n        def is_file_executable(file_path):\n            return os.path.isfile(file_path) and os.access(file_path, os.X_OK)\n\n        if not is_file_executable(demo_executable):\n            print('ERROR: %s is NOT executable' % (demo_executable,), file=sys.stderr)\n            sys.exit(-1)\n\n        return demo_executable\n\n    def skipIfFeatureStatus(self, log, message):\n        import subprocess\n        import unittest\n        output = subprocess.run([self._get_demo_executable(), '-e', 'dummy', '-u', 'invalid'],\n                                stdout=subprocess.PIPE, stderr=subprocess.STDOUT).stdout.decode('utf-8')\n        if log in output:\n            raise unittest.SkipTest(message)\n\n    def _start_demo(self, cmdline_args, timeout_s=60, prepend_args=None):\n        \"\"\"\n        Starts the demo executable with given CMDLINE_ARGS.\n        \"\"\"\n        demo_executable = self._get_demo_executable()\n        if (os.environ.get('RR')\n                or ('RRR' in os.environ\n                    and test_or_suite_matches_query_regex(self, os.environ['RRR']))):\n            logging.info('*** rr-recording enabled ***')\n            # ignore valgrind if rr was requested\n            args_prefix = ['rr', 'record']\n        else:\n            args_prefix = self._get_valgrind_args()\n\n        demo_args = (prepend_args or []) + args_prefix + [demo_executable] + cmdline_args\n\n        import shlex\n        console_log_path = self.logs_path(LogType.Console)\n        console = open(console_log_path, 'ab')\n        console.write((' '.join(map(shlex.quote, demo_args)) + '\\n\\n').encode('utf-8'))\n        console.flush()\n\n        log_file_pos = console.tell()\n\n        logging.debug('starting demo: %s', ' '.join(\n            '\"%s\"' % arg for arg in demo_args))\n        import subprocess\n        self.demo_process = subprocess.Popen(demo_args,\n                                             stdin=subprocess.PIPE,\n                                             stdout=console,\n                                             stderr=console,\n                                             bufsize=0)\n        self.demo_process.log_file_write = console\n        self.demo_process.log_file_path = console_log_path\n        self.demo_process.log_file = open(\n            console_log_path, mode='rb', buffering=0)\n        self.demo_process.log_file.seek(log_file_pos)\n\n        if timeout_s is not None:\n            # wait until demo process starts\n            if self.read_log_until_match(regex=re.escape(b'*** ANJAY DEMO STARTUP FINISHED ***'),\n                                         timeout_s=timeout_s) is None:\n                raise self.failureException(\n                    'demo executable did not start in time')\n\n    DUMPCAP_COMMAND = 'dumpcap'\n\n    @staticmethod\n    def dumpcap_available():\n        return not os.getenv('NO_DUMPCAP') and shutil.which(Lwm2mTest.DUMPCAP_COMMAND) is not None\n\n    def _start_dumpcap(self, servers):\n        self.dumpcap_process = None\n        if not self.dumpcap_available():\n            return\n\n        def _filter_expr(servers):\n            \"\"\"\n            Generates a pcap_compile()-compatible filter program so that dumpcap will only capture packets that are\n            actually relevant to the current tests.\n\n            Captured packets will include (depending on protocol):\n            - UDP: datagrams sent or received on any of the given server ports, plus related ICMP Port Unreachable messages\n            - TCP: segments sent or received on any of the given server ports\n            \"\"\"\n\n            packet_filter=[]\n            for server in servers:\n                transport = server.get_transport()\n                port = server.get_listen_port()\n\n                if transport == Transport.UDP:\n                    # Filter expression for \"source or destination UDP port is any of ports\"\n                    udp_filter = f'(udp port {port})'\n\n                    # Below is the generation of filter expression for the ICMP messages.\n                    # Note that icmp[N] syntax accesses the Nth byte since the beginning of ICMP header\n                    # and icmp[N:M] syntax accesses an M-byte value starting at icmp[N]\n                    # - icmp[0] - ICMP type; 3 ~ Destination Unreachable\n                    # - icmp[1] - ICMP code; for Destination Unreachable: 3 ~ Destination port unreachable\n                    # - icmp[8] is the first byte of the IP header of a copy of the packet that caused the error\n                    #   - icmp[17] is the IP protocol number; 17 ~ UDP\n                    #   - IPv4 header is normally 20 bytes long (we don't anticipate options), so UDP header starts at icmp[28]\n                    #   - icmp[28:2] is the source UDP port of the original packet\n                    #   - icmp[30:2] is the destination UDP port of the original packet\n                    icmp_pu_filter = ('(icmp[28:2] = 0x%04x) or (icmp[30:2] = 0x%04x)' % (port, port))\n                    packet_filter.append(f'({udp_filter} or ((icmp[0] = 3) and (icmp[1] = 3) and (icmp[17] = 17) and ({icmp_pu_filter})))')\n                \n                elif transport == Transport.TCP:\n                    # Filter expression for \"source or destination TCP port is any of ports\"\n                    packet_filter.append(f'(tcp port {port})')\n                else:\n                    raise ValueError(f\"Unknown transport: {transport}\")\n\n            return ' or '.join(packet_filter)\n\n        self.dumpcap_file_path = self.logs_path(LogType.Pcap)\n        dumpcap_command = [self.DUMPCAP_COMMAND, '-w',\n                           self.dumpcap_file_path, '-i', 'lo', '-f', _filter_expr(servers)]\n        self.dumpcap_process = subprocess.Popen(dumpcap_command,\n                                                stdin=subprocess.DEVNULL,\n                                                stdout=subprocess.DEVNULL,\n                                                stderr=subprocess.PIPE,\n                                                bufsize=0)\n\n        # It takes a little while (around 0.5-0.6 seconds on a normal PC) for dumpcap to initialize and actually start\n        # capturing packets. We want all relevant packets captured, so we need to wait until dumpcap reports it's ready.\n        # Also, if we haven't done this, there would be a possibility that _terminate_dumpcap() would be called before\n        # full initialization of dumpcap - it would then essentially ignore the SIGTERM and our test would hang waiting\n        # for dumpcap's termination that would never come.\n        dumpcap_stderr = bytearray(b'')\n        MAX_DUMCAP_STARTUP_WAIT_S = 30\n        deadline = time.time() + MAX_DUMCAP_STARTUP_WAIT_S\n        while time.time() < deadline:\n            dumpcap_stderr += read_some_with_timeout(\n                self.dumpcap_process.stderr, 1)\n            if b'File:' in dumpcap_stderr:\n                break\n            if self.dumpcap_process.poll() is not None:\n                raise ChildProcessError(\n                    'Could not start %r\\n' % (dumpcap_command,))\n        else:\n            raise ChildProcessError(\n                'Could not start %r\\n' % (dumpcap_command,))\n\n        def _reader_func():\n            try:\n                while True:\n                    data = self.dumpcap_process.stderr.read()\n                    if len(data) == 0:  # EOF\n                        break\n            except:\n                pass\n\n        self.dumpcap_stderr_reader_thread = threading.Thread(\n            target=_reader_func)\n        self.dumpcap_stderr_reader_thread.start()\n\n    def setup_demo_with_servers(self,\n                                servers=1,\n                                num_servers_passed=None,\n                                bootstrap_server=False,\n                                legacy_server_initiated_bootstrap_allowed=True,\n                                extra_cmdline_args=[],\n                                auto_register=True,\n                                minimum_version='1.0',\n                                maximum_version='1.0',\n                                endpoint_name=DEMO_ENDPOINT_NAME,\n                                lifetime=None,\n                                binding=None,\n                                lwm2m11_queue_mode=False,\n                                fw_updated_marker_path=None,\n                                **kwargs):\n        \"\"\"\n        Starts the demo process and creates any required auxiliary objects (such as Lwm2mServer objects) or processes.\n\n        :param servers:\n        Lwm2mServer objects that shall be accessible to the test - they will be accessible through the self.servers\n        list. May be either an iterable of Lwm2mServer objects, or an integer - in the latter case, an appropriate\n        number of Lwm2mServer objects will be created.\n\n        :param num_servers_passed:\n        If passed, it shall be an integer that controls how many of the servers configured through the servers argument,\n        will be passed to demo's command line. All of them are passed by default. This option may be useful if some\n        servers are meant to be later configured e.g. via the Bootstrap Interface.\n\n        :param bootstrap_server:\n        Boolean value that controls whether to create a Bootstrap Server Lwm2mServer object. If true, it will be stored\n        in self.bootstrap_server. The bootstrap server is not included in anything related to the servers and\n        num_servers_passed arguments.\n\n        :param extra_cmdline_args:\n        List of command line arguments to pass to the demo process in addition to the ones generated from other\n        arguments.\n\n        :param auto_register:\n        If true (default), self.assertDemoRegisters() will be called for each server provisioned via the command line.\n\n        :param version:\n        Passed down to self.assertDemoRegisters() if auto_register is true\n\n        :param lifetime:\n        Passed down to self.assertDemoRegisters() if auto_register is true\n\n        :param binding:\n        Passed down to self.assertDemoRegisters() if auto_register is true\n\n        :return: None\n        \"\"\"\n        demo_args = []\n\n        if isinstance(servers, int):\n            self.servers = [Lwm2mServer() for _ in range(servers)]\n        else:\n            self.servers = list(servers)\n\n        servers_passed = self.servers\n        if num_servers_passed is not None:\n            servers_passed = servers_passed[:num_servers_passed]\n\n        if bootstrap_server is True:\n            self.bootstrap_server = Lwm2mServer()\n        elif bootstrap_server:\n            self.bootstrap_server = bootstrap_server\n        else:\n            self.bootstrap_server = None\n\n        if self.bootstrap_server is not None:\n            demo_args += [\n                '--bootstrap' if legacy_server_initiated_bootstrap_allowed else '--bootstrap=client-initiated-only']\n            all_servers = [self.bootstrap_server] + self.servers\n            all_servers_passed = [self.bootstrap_server] + servers_passed\n        else:\n            all_servers = self.servers\n            all_servers_passed = servers_passed\n\n        if fw_updated_marker_path is None:\n            fw_updated_marker_path = generate_temp_filename(\n                dir='/tmp', prefix='anjay-fw-updated-')\n\n        demo_args += self.make_demo_args(\n            endpoint_name, all_servers_passed,\n            minimum_version, maximum_version,\n            fw_updated_marker_path, **kwargs)\n        demo_args += extra_cmdline_args\n        if lifetime is not None:\n            demo_args += ['--lifetime', str(lifetime)]\n\n        try:\n            self.demo_process = None\n            self._start_dumpcap(all_servers)\n            self._start_demo(demo_args)\n\n            if auto_register:\n                if self.bootstrap_server is not None and (\n                        len(servers_passed) == 0 or legacy_server_initiated_bootstrap_allowed):\n                    # Bootstrap Server had to be passed first on the command line,\n                    # but Anjay will connect to it last\n                    all_servers_passed = servers_passed + [self.bootstrap_server]\n                else:\n                    all_servers_passed = servers_passed\n\n                for serv in all_servers_passed:\n                    if serv.security_mode() != 'nosec':\n                        serv.listen()\n                    if serv.transport == Transport.TCP:\n                        self.assertTcpCsm(serv)\n                for serv in servers_passed:\n                    self.assertDemoRegisters(serv,\n                                             version=maximum_version,\n                                             lifetime=lifetime,\n                                             binding=binding,\n                                             lwm2m11_queue_mode=lwm2m11_queue_mode)\n        except Exception:\n            try:\n                self.teardown_demo_with_servers(auto_deregister=False)\n            finally:\n                raise\n\n    def teardown_demo_with_servers(self,\n                                   auto_deregister=True,\n                                   shutdown_timeout_s=5.0,\n                                   force_kill=False,\n                                   *args,\n                                   **kwargs):\n        \"\"\"\n        Shuts down the demo process, either by:\n        - closing its standard input (\"Ctrl+D\" on its command line)\n        - sending SIGTERM to it\n        - sending SIGKILL to it\n        Each of the above methods is tried one after another.\n\n        :param auto_deregister:\n        If true (default), self.assertDemoDeregisters() is called before shutting down for each server in the\n        self.servers list (unless overridden by the deregister_servers argument).\n\n        :param shutdown_timeout_s:\n        Number of seconds to wait after each attempted method of shutting down the demo process before moving to the\n        next one (close input -> SIGTERM -> SIGKILL).\n\n        :param force_kill:\n        If set to True, demo will be forcefully terminated, and its exit code will be ignored.\n\n        :param deregister_servers:\n        If auto_deregister is true, specifies the list of servers to call self.assertDemoDeregisters() on, overriding\n        the default self.servers.\n\n        :param args:\n        Any other positional arguments to this function are passed down to self.assertDemoDeregisters().\n\n        :param kwargs:\n        Any other keyword arguments to this function are passed down to self.assertDemoDeregisters().\n\n        :return: None\n        \"\"\"\n        if auto_deregister and not 'deregister_servers' in kwargs:\n            kwargs = kwargs.copy()\n            kwargs['deregister_servers'] = self.servers\n\n        with CleanupList() as cleanup_funcs:\n            if not force_kill:\n                cleanup_funcs.append(\n                    lambda: self.request_demo_shutdown(*args, **kwargs))\n\n            cleanup_funcs.append(lambda: self._terminate_demo(\n                timeout_s=shutdown_timeout_s, force_kill=force_kill))\n            for serv in self.servers:\n                cleanup_funcs.append(serv.close)\n\n            if self.bootstrap_server:\n                cleanup_funcs.append(self.bootstrap_server.close)\n\n            cleanup_funcs.append(self._terminate_dumpcap)\n\n    def seek_demo_log_to_end(self):\n        self.demo_process.log_file.seek(\n            os.fstat(self.demo_process.log_file.fileno()).st_size)\n\n    def communicate(self, cmd, timeout=-1, match_regex=re.escape('(DEMO)>')):\n        \"\"\"\n        Writes CMD to the demo process stdin. If MATCH_REGEX is not None,\n        blocks until given regex is found on demo process stdout.\n        \"\"\"\n        if timeout < 0:\n            timeout = self.DEFAULT_COMM_TIMEOUT\n\n        self.seek_demo_log_to_end()\n        # For some reason, writing to a closed pipe seems to behave a little\n        # differently for macOS and Linux. On macOS, Python receives a SIGPIPE\n        # and therefore throws a BrokenPipeError. Let's silence it.\n        try:\n            self.demo_process.stdin.write((cmd.strip('\\n') + '\\n').encode())\n            self.demo_process.stdin.flush()\n        except BrokenPipeError:\n            pass\n\n        if match_regex:\n            result = self.read_log_until_match(match_regex.encode(), timeout_s=timeout)\n            if result is not None:\n                # we need to convert bytes-based match object to string-based one...\n                return re.search(match_regex, result.group(0).decode(errors='replace'))\n\n        return None\n\n    def _terminate_demo_impl(self, demo, timeout_s, force_kill):\n        if force_kill:\n            demo.kill()\n            demo.wait(timeout_s)\n            return 0\n\n        cleanup_actions = [\n            (timeout_s, lambda _: None),  # check if the demo already stopped\n            (timeout_s, lambda demo: demo.terminate()),\n            (None, lambda demo: demo.kill())\n        ]\n\n        for timeout, action in cleanup_actions:\n            action(demo)\n            try:\n                return demo.wait(timeout)\n            except subprocess.TimeoutExpired:\n                pass\n            else:\n                break\n        return -1\n\n    def _terminate_demo(self, timeout_s=5.0, force_kill=False):\n        if self.demo_process is None:\n            return\n\n        exc = sys.exc_info()\n        try:\n            return_value = self._terminate_demo_impl(\n                self.demo_process, timeout_s, force_kill)\n            self.assertEqual(\n                return_value, 0, 'demo terminated with nonzero exit code')\n        except:\n            if not exc[1]:\n                raise\n        finally:\n            self.demo_process.log_file.close()\n            self.demo_process.log_file_write.close()\n\n    def _terminate_dumpcap(self):\n        if self.dumpcap_process is None:\n            logging.debug('dumpcap not started, skipping')\n            return\n\n        # wait until all packets are written\n        last_size = -1\n        size = -1\n\n        MAX_DUMCAP_SHUTDOWN_WAIT_S = 30\n        deadline = time.time() + MAX_DUMCAP_SHUTDOWN_WAIT_S\n        while time.time() < deadline:\n            if size != last_size:\n                break\n            time.sleep(0.1)\n            last_size = size\n            size = os.stat(self.dumpcap_file_path).st_size\n        else:\n            logging.warn(\n                'dumpcap did not shut down on time, terminating anyway')\n\n        self.dumpcap_process.terminate()\n        self.dumpcap_process.wait()\n        self.dumpcap_stderr_reader_thread.join()\n        logging.debug('dumpcap terminated')\n\n    def coap_ping(self, server=None, timeout_s=-1):\n        serv = server or self.serv\n        if serv.transport == Transport.TCP:\n            req = coap.Packet(code=coap.Code.SIGNALING_PING)\n            serv.send(req)\n            self.assertEqual(coap.Code.SIGNALING_PONG, serv.recv(timeout_s=timeout_s).code)\n            return\n        req = Lwm2mEmpty(type=coap.Type.CONFIRMABLE)\n        serv.send(req)\n        self.assertMsgEqual(Lwm2mReset.matching(req)(), serv.recv(timeout_s=timeout_s))\n\n    def request_demo_shutdown(self, deregister_servers=[], timeout_s=-1, *args, **kwargs):\n        \"\"\"\n        Attempts to cleanly terminate demo by closing its STDIN.\n\n        If DEREGISTER_SERVERS is a non-empty list, the function waits until\n        demo deregisters from each server from the list.\n        \"\"\"\n        for serv in deregister_servers:\n            # send a CoAP ping to each of the connections\n            # to make sure that all data has been processed by the client\n            self.coap_ping(serv, timeout_s=timeout_s)\n\n        logging.debug('requesting clean demo shutdown')\n        if self.demo_process is None:\n            logging.debug('demo not started, skipping')\n            return\n\n        self.demo_process.stdin.close()\n\n        for serv in deregister_servers:\n            self.assertDemoDeregisters(serv, reset=False, timeout_s=timeout_s, *args, **kwargs)\n\n        logging.debug('demo terminated')\n\n    def get_socket_count(self):\n        return int(\n            self.communicate('socket-count', match_regex='SOCKET_COUNT==([0-9]+)\\n').group(1))\n\n    def wait_until_socket_count(self, expected, timeout_s):\n        deadline = time.time() + timeout_s\n        while self.get_socket_count() != expected:\n            if time.time() > deadline:\n                raise TimeoutError('Desired socket count not reached')\n            time.sleep(0.1)\n\n    def get_non_lwm2m_socket_count(self):\n        return int(self.communicate('non-lwm2m-socket-count',\n                                    match_regex='NON_LWM2M_SOCKET_COUNT==([0-9]+)\\n').group(1))\n\n    def get_demo_port(self, server_index=None):\n        if server_index is None:\n            server_index = -1\n        return int(\n            self.communicate('get-port %s' % (server_index,), match_regex='PORT==([0-9]+)\\n').group(\n                1))\n\n    def get_transport(self, socket_index=-1):\n        return self.communicate('get-transport %s' % (socket_index,),\n                                match_regex='TRANSPORT==([0-9a-zA-Z]+)\\n').group(1)\n\n    def get_all_connections_failed(self):\n        return bool(int(self.communicate('get-all-connections-failed',\n                                         match_regex='ALL_CONNECTIONS_FAILED==([0-9])\\n').group(1)))\n\n    def advance_demo_time(self, duration_s=0.0):\n        self.communicate('advance-time %s' % duration_s)\n\n    def ongoing_registration_exists(self):\n        result = self.communicate('ongoing-registration-exists',\n                                  match_regex='ONGOING_REGISTRATION==(true|false)\\n').group(1)\n        if result == \"true\":\n            return True\n        elif result == \"false\":\n            return False\n        raise ValueError(\"invalid value\")\n\n\nclass SingleServerAccessor:\n    @property\n    def serv(self) -> Lwm2mServer:\n        return self.servers[0]\n\n    @serv.setter\n    def serv(self, new_serv: Lwm2mServer):\n        self.servers[0] = new_serv\n\n    @serv.deleter\n    def serv(self):\n        del self.servers[0]\n\n\nclass Lwm2mSingleServerTest(Lwm2mTest, SingleServerAccessor):\n    def runTest(self):\n        pass\n\n\nclass Lwm2mDtlsSingleServerTest(Lwm2mSingleServerTest):\n    PSK_IDENTITY = b'test-identity'\n    PSK_KEY = b'test-key'\n\n    def setUp(self, *args, **kwargs):\n        assert (('psk_identity' in kwargs) == ('psk_key' in kwargs))\n        if 'psk_identity' not in kwargs:\n            kwargs['psk_identity'] = self.PSK_IDENTITY\n            kwargs['psk_key'] = self.PSK_KEY\n        super().setUp(*args, **kwargs)\n\n\nclass Lwm2mSingleTcpServerTest(Lwm2mSingleServerTest):\n    def setUp(self, extra_cmdline_args=None, *args, **kwargs):\n        extra_args = ['-q', 'T']\n        if extra_cmdline_args is not None:\n            extra_args += extra_cmdline_args\n\n        super().setUp(extra_cmdline_args=extra_args, transport=Transport.TCP, *args, **kwargs)\n\n\nclass Lwm2mTlsSingleServerTest(Lwm2mSingleTcpServerTest):\n    PSK_IDENTITY = b'test-identity'\n    PSK_KEY = b'test-key'\n\n    def setUp(self, *args, **kwargs):\n        super().setUp(psk_identity=self.PSK_IDENTITY, psk_key=self.PSK_KEY, *args, **kwargs)\n\n\n# This class **MUST** be specified as the first in superclass list, due to Python's method resolution order\n# (see https://www.python-course.eu/python3_multiple_inheritance.php) and the fact that not all setUp() methods\n# call super().setUp(). Failure to fulfill this requirement may lead to \"make check\" failing on systems\n# without dpkt or dumpcap available.\nclass PcapEnabledTest(Lwm2mTest):\n    def setUp(self, *args, **kwargs):\n        if not (_DPKT_AVAILABLE and Lwm2mTest.dumpcap_available()):\n            raise unittest.SkipTest('This test involves parsing PCAP file')\n        return super().setUp(*args, **kwargs)\n\n    def read_pcap(self):\n        def decode_packet(data):\n            # dumpcap captures contain Ethernet frames on Linux and\n            # loopback ones on BSD\n            for frame_type in [dpkt.ethernet.Ethernet, dpkt.loopback.Loopback]:\n                pkt = frame_type(data)\n                if isinstance(pkt.data, dpkt.ip.IP):\n                    return pkt\n\n            raise ValueError('Could not decode frame: %s' % pkt.hex())\n\n        with open(self.dumpcap_file_path, 'rb') as f:\n            r = dpkt.pcapng.Reader(f)\n            for pkt in iter(r):\n                yield decode_packet(pkt[1]).data\n\n    def _wait_until_condition(self, timeout_s, step_s, condition: lambda pkts: True):\n        if timeout_s is None:\n            timeout_s = self.DEFAULT_MSG_TIMEOUT\n        deadline = time.time() + timeout_s\n        while True:\n            if condition(self.read_pcap()):\n                return\n            if time.time() >= deadline:\n                raise TimeoutError(\n                    'Condition was not true in specified time interval')\n            time.sleep(step_s)\n\n    def _count_packets(self, condition: lambda pkts: True):\n        result = 0\n        for pkt in self.read_pcap():\n            if condition(pkt):\n                result += 1\n        return result\n\n    @staticmethod\n    def is_icmp_unreachable(pkt):\n        return isinstance(pkt, dpkt.ip.IP) \\\n            and isinstance(pkt.data, dpkt.icmp.ICMP) \\\n            and isinstance(pkt.data.data, dpkt.icmp.ICMP.Unreach)\n\n    @staticmethod\n    def is_dtls_client_hello(pkt):\n        header = b'\\x16'  # Content Type: Handshake\n        header += b'\\xfe\\xfd'  # Version: DTLS 1.2\n        header += b'\\x00\\x00'  # Epoch: 0\n        if isinstance(pkt, dpkt.ip.IP) and isinstance(pkt.data, dpkt.udp.UDP):\n            return pkt.udp.data[:len(header)] == header\n        else:\n            return False\n\n    @staticmethod\n    def is_nosec_register(pkt):\n        try:\n            # If it successfully parses as Lwm2mRegister it is a register\n            Lwm2mRegister.from_packet(coap.Packet.parse(pkt.data.data))\n            return True\n        except:\n            return False\n\n    def count_nosec_register_packets(self):\n        return self._count_packets(PcapEnabledTest.is_nosec_register)\n\n    def count_icmp_unreachable_packets(self):\n        return self._count_packets(PcapEnabledTest.is_icmp_unreachable)\n\n    def count_dtls_client_hello_packets(self):\n        return self._count_packets(PcapEnabledTest.is_dtls_client_hello)\n\n    def wait_until_icmp_unreachable_count(self, value, timeout_s=None, step_s=0.1):\n        def count_of_icmps_is_expected(pkts):\n            return self.count_icmp_unreachable_packets() >= value\n\n        try:\n            self._wait_until_condition(\n                timeout_s=timeout_s, step_s=step_s, condition=count_of_icmps_is_expected)\n        except TimeoutError:\n            raise TimeoutError('ICMP Unreachable packet not generated')\n\n\ndef get_test_name(test):\n    if isinstance(test, Lwm2mTest):\n        return test.test_name()\n    return test.id()\n\n\ndef get_full_test_name(test):\n    if isinstance(test, Lwm2mTest):\n        return test.suite_name() + '.' + test.test_name()\n    return test.id()\n\n\ndef get_suite_name(suite):\n    suite_names = []\n    for test in suite:\n        if isinstance(test, Lwm2mTest):\n            suite_names.append(test.suite_name())\n        elif isinstance(test, unittest.TestSuite):\n            suite_names.append(get_suite_name(test))\n        else:\n            suite_names.append(test.id())\n\n    suite_names = set(suite_names)\n    assert len(suite_names) == 1\n\n    return next(iter(suite_names)).replace('/', '.')\n\n\ndef test_or_suite_matches_query_regex(test_or_suite, query_regex):\n    \"\"\"\n    Test or test suite matches regex query when at least one of following\n    matches the regex:\n\n    * test name,\n    * suite name,\n    * \"suite_name.test_name\" string.\n\n    Substring matches are allowed unless the regex is anchored using ^ or $.\n    \"\"\"\n    if isinstance(test_or_suite, unittest.TestCase):\n        return (re.search(query_regex, get_test_name(test_or_suite))\n                or re.search(query_regex, get_full_test_name(test_or_suite)))\n    elif isinstance(test_or_suite, unittest.TestSuite):\n        return re.search(query_regex, get_suite_name(test_or_suite))\n    else:\n        raise TypeError('Neither a test nor suite: %r' % test_or_suite)\n"
  },
  {
    "path": "tests/integration/framework/framework/test_utils.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nimport binascii\nimport struct\nimport tempfile\nimport collections\nimport sys\nimport time\nimport socket\n\nfrom typing import Optional\n\nfrom framework_tools.lwm2m import coap\nfrom framework_tools.lwm2m.objlink import Objlink\n\nif sys.version_info[0] == 3 and sys.version_info[1] < 7:\n    # based on https://stackoverflow.com/a/18348004/2339636\n    def namedtuple(*args, defaults=None, **kwargs):\n        cls = collections.namedtuple(*args, **kwargs)\n        cls.__new__.__defaults__ = defaults\n        return cls\nelse:\n    namedtuple = collections.namedtuple\n\n\nclass SequentialMsgIdGenerator:\n    def __init__(self, start_id):\n        self.curr_id = start_id\n\n    def __next__(self):\n        return self.next()\n\n    def next(self):\n        self.curr_id = (self.curr_id + 1) % 2 ** 16\n        return self.curr_id\n\n\ndef generate_temp_filename(suffix='', prefix='tmp', dir=None):\n    with tempfile.NamedTemporaryFile(suffix=suffix, prefix=prefix, dir=dir) as f:\n        return f.name\n\n\ndef random_stuff(size):\n    import os\n    return os.urandom(size)\n\n\ndef uri_path_to_options(path):\n    assert path.startswith('/')\n    return [coap.Option.URI_PATH(x) for x in path.split('/')[1:]]\n\n\ndef get_another_token(token):\n    other = token\n    while other == token:\n        other = random_stuff(8)\n    return other\n\n\ndef config_to_dict(config):\n    return eval(config)\n\n\nclass OID:\n    Security = 0\n    Server = 1\n    AccessControl = 2\n    Device = 3\n    ConnectivityMonitoring = 4\n    FirmwareUpdate = 5\n    Location = 6\n    ConnectivityStatistics = 7\n    SoftwareManagement = 9\n    CellularConnectivity = 10\n    ApnConnectionProfile = 11\n    Portfolio = 16\n    BinaryAppDataContainer = 19\n    EventLog = 20\n    Lwm2mGateway = 25\n    Temperature = 3303\n    Accelerometer = 3313\n    PushButton = 3347\n    Test = 33605\n    ExtDevInfo = 33606\n    IpPing = 33607\n    GeoPoints = 33608\n    DownloadDiagnostics = 33609\n    AdvancedFirmwareUpdate = 33629\n\n\nclass RID:\n    class Security:\n        ServerURI = 0\n        Bootstrap = 1\n        Mode = 2\n        PKOrIdentity = 3\n        ServerPKOrIdentity = 4\n        SecretKey = 5\n        SMSSecurityMode = 6\n        SMSBindingKeyParameters = 7\n        SMSBindingSecretKeys = 8\n        ServerSMSNumber = 9\n        ShortServerID = 10\n        ClientHoldOffTime = 11\n        BootstrapTimeout = 12\n        MatchingType = 13\n        SNI = 14\n        CertificateUsage = 15\n        DtlsTlsCiphersuite = 16\n\n    class Server:\n        ShortServerID = 0\n        Lifetime = 1\n        DefaultMinPeriod = 2\n        DefaultMaxPeriod = 3\n        Disable = 4\n        DisableTimeout = 5\n        NotificationStoring = 6\n        Binding = 7\n        RegistrationUpdateTrigger = 8\n        RequestBootstrapTrigger = 9\n        TlsDtlsAlertCode = 11\n        LastBootstrapped = 12\n        BootstrapOnRegistrationFailure = 16\n        ServerCommunicationRetryCount = 17\n        ServerCommunicationRetryTimer = 18\n        ServerCommunicationSequenceDelayTimer = 19\n        ServerCommunicationSequenceRetryCount = 20\n        Trigger = 21\n        PreferredTransport = 22\n        MuteSend = 23\n\n    class AccessControl:\n        TargetOID = 0\n        TargetIID = 1\n        ACL = 2\n        Owner = 3\n\n    class Device:\n        Manufacturer = 0\n        ModelNumber = 1\n        SerialNumber = 2\n        FirmwareVersion = 3\n        Reboot = 4\n        FactoryReset = 5\n        AvailablePowerSources = 6\n        PowerSourceVoltage = 7\n        PowerSourceCurrent = 8\n        BatteryLevel = 9\n        MemoryFree = 10\n        ErrorCode = 11\n        ResetErrorCode = 12\n        CurrentTime = 13\n        UTCOffset = 14\n        Timezone = 15\n        SupportedBindingAndModes = 16\n        DeviceType = 17\n        HardwareVersion = 18\n        SoftwareVersion = 19\n        BatteryStatus = 20\n        MemoryTotal = 21\n        ExtDevInfo = 22\n\n    class ConnectivityMonitoring:\n        NetworkBearer = 0\n        AvailableNetworkBearer = 1\n        RadioSignalStrength = 2\n        LinkQuality = 3\n        IPAddresses = 4\n        RouterIPAddresses = 5\n        LinkUtilization = 6\n        APN = 7\n        CellID = 8\n        SMNC = 9\n        SMCC = 10\n\n    class FirmwareUpdate:\n        Package = 0\n        PackageURI = 1\n        Update = 2\n        State = 3\n        UpdateResult = 5\n        PackageName = 6\n        PackageVersion = 7\n        FirmwareUpdateProtocolSupport = 8\n        FirmwareUpdateDeliveryMethod = 9\n        Cancel = 10\n        Severity = 11\n        LastStateChangeTime = 12\n        MaxDeferPeriod = 13\n\n    class Location:\n        Latitude = 0\n        Longitude = 1\n        Altitude = 2\n        Uncertainty = 3\n        Velocity = 4\n        Timestamp = 5\n\n    class ConnectivityStatistics:\n        SMSTxCounter = 0\n        SMSRxCounter = 1\n        TxData = 2\n        RxData = 3\n        MaxMessageSize = 4\n        AverageMessageSize = 5\n        Start = 6\n        Stop = 7\n        CollectionPeriod = 8\n        CollectionDuration = 9\n\n    class SoftwareManagement:\n        PkgName = 0\n        PkgVersion = 1\n        Package = 2\n        PackageURI = 3\n        Install = 4\n        Checkpoint = 5\n        Uninstall = 6\n        UpdateState = 7\n        UpdateSupportedObjects = 8\n        UpdateResult = 9\n        Activate = 10\n        Deactivate = 11\n        ActivationState = 12\n        PackageSettings = 13\n        UserName = 14\n        Password = 15\n        StatusReason = 16\n        SoftwareComponentLink = 17\n        SoftwareComponentTreeLength = 18\n\n    class CellularConnectivity:\n        SMSCAddress = 0\n        DisableRadioPeriod = 1\n        ModuleActivationCode = 2\n        VendorSpecificExtensions = 3\n        PSMTimer = 4\n        ActiveTimer = 5\n        ServingPLMNRateControl = 6\n        eDRXParamtersForIuMode = 7\n        eDRXParamtersForWBS1Mode = 8\n        eDRXParametersForNBS1Mode = 9\n        eDRXParametersForAGbMode = 10\n        ActivatedProfileNames = 11\n        CoverageEnhancementLevel = 12\n        PowerSavingModes = 13\n        ActivePowerSavingModes = 14\n\n    class ApnConnectionProfile:\n        ProfileName = 0\n        APN = 1\n        AutoSelectAPNByDevice = 2\n        EnableStatus = 3\n        AuthenticationType = 4\n        UserName = 5\n        Secret = 6\n        ReconnectSchedule = 7\n        Validity = 8\n        ConnectionEstablishmentTime = 9\n        ConnectionEstablishmentResult = 10\n        ConnectionEstablishmentRejectCause = 11\n        ConnectionEndTime = 12\n        TotalBytesSent = 13\n        TotalBytesReceived = 14\n        IPAddress = 15\n        PrefixLength = 16\n        SubnetMask = 17\n        Gateway = 18\n        PrimaryDNSAddress = 19\n        SecondaryDNSAddress = 20\n        QCI = 21\n        VendorSpecificExtensions = 22\n        TotalPacketsSent = 23\n        PDNType = 24\n        APNRateControl = 25\n        ServingPLMNRateControl = 26\n        UplinkTimeUnit = 27\n        APNRateControlForExceptionData = 28\n        APNExceptionDataUplinkTimeUnit = 29\n        SupportedRATTypes = 30\n        RDSApplicationID = 31\n        RDSDestinationPort = 32\n        RDSSourcePort = 33\n\n    class GeoPoints:\n        Latitude = 0\n        Longitude = 1\n        Radius = 2\n        Description = 3\n        Inside = 4\n\n    class Temperature:\n        MinMeasuredValue = 5601\n        MaxMeasuredValue = 5602\n        MinRangeValue = 5603\n        MaxRangeValue = 5604\n        ResetMinAndMaxMeasuredValues = 5605\n        SensorValue = 5700\n        SensorUnits = 5701\n        ApplicationType = 5750\n\n    class Accelerometer:\n        MinRangeValue = 5603\n        MaxRangeValue = 5604\n        XValue = 5702\n        YValue = 5703\n        ZValue = 5704\n        SensorUnits = 5701\n\n    class PushButton:\n        DigitalInputState = 5500\n        DigitalInputCounter = 5501\n        ApplicationType = 5750\n\n    class Test:\n        Timestamp = 0\n        Counter = 1\n        IncrementCounter = 2\n        IntArray = 3\n        LastExecArgsArray = 4\n        ResBytes = 5\n        ResBytesSize = 6\n        ResBytesBurst = 7\n        ResInitIntArray = 9\n        ResRawBytes = 10\n        ResOpaqueArray = 11\n        ResInt = 12\n        ResBool = 13\n        ResFloat = 14\n        ResString = 15\n        ResObjlnk = 16\n        ResBytesZeroBegin = 17\n        ResDouble = 18\n        ResUnsignedInt = 19\n        ResUnsignedLong = 20\n        ToggleBool = 21\n        BoolArray = 22\n        ResInitBoolArray = 23\n\n    class AdvancedFirmwareUpdate:\n        Package = 0\n        PackageURI = 1\n        Update = 2\n        State = 3\n        UpdateResult = 5\n        PackageName = 6\n        PackageVersion = 7\n        FirmwareUpdateProtocolSupport = 8\n        FirmwareUpdateDeliveryMethod = 9\n        Cancel = 10\n        Severity = 11\n        LastStateChangeTime = 12\n        MaxDeferPeriod = 13\n        PartitionName = 14\n        CurrentVersion = 15\n        LinkedInstances = 16\n        ConflictingInstances = 17\n\n    class Portfolio:\n        Identity = 0\n        GetAuthData = 1\n        AuthData = 2\n        AuthStatus = 3\n\n    class ExtDevInfo:\n        ObuId = 0\n        PlateNumber = 1\n        Imei = 2\n        Imsi = 3\n        Iccid = 4\n        GprsRssi = 5\n        GprsPlmn = 6\n        GprsUlModulation = 7\n        GprsDlModulation = 8\n        GprsUlFrequency = 9\n        GprsDlFrequency = 10\n        RxBytes = 11\n        TxBytes = 12\n        NumIncomingRetransmissions = 13\n        NumOutgoingRetransmissions = 14\n        Uptime = 15\n\n    class IpPing:\n        Hostname = 0\n        Repetitions = 1\n        TimeoutMs = 2\n        BlockSize = 3\n        Dscp = 4\n        Run = 5\n        State = 6\n        SuccessCount = 7\n        ErrorCount = 8\n        AvgTimeMs = 9\n        MinTimeMs = 10\n        MaxTimeMs = 11\n        TimeStdevUs = 12\n\n    class DownloadDiagnostics:\n        State = 0\n        Url = 1\n        RomTimeUs = 2\n        BomTimeUs = 3\n        EomTimeUs = 4\n        TotalBytes = 5\n        Run = 6\n\n    class Lwm2mGateway:\n        DeviceID = 0\n        Prefix = 1\n        IoTEndDeviceObjects = 3\n\n    class BinaryAppDataContainer:\n        Data = 0\n        DataPriority = 1\n        DataCreationTime = 2\n        DataDescription = 3\n        DataFormat = 4\n        AppId = 5\n\n    class EventLog:\n        LogClass = 4010\n        LogStart = 4011\n        LogStop = 4012\n        LogStatus = 4013\n        LogData = 4014\n        LogDataFormat = 4015\n\n\nclass Lwm2mResourcePathHelper:\n    @classmethod\n    def from_rid_object(cls, rid_obj, oid, multi_instance=False, version=None):\n        return cls(resources={k: v for k, v in rid_obj.__dict__.items() if isinstance(v, int)},\n                   oid=oid,\n                   multi_instance=multi_instance,\n                   version=version)\n\n    def __init__(self, resources, oid, iid=None, multi_instance=False, version=None):\n        self.resources = resources\n        self.oid = oid\n        self.is_multi_instance = multi_instance\n        self.version = version\n\n        if iid is not None:\n            self.iid = iid\n        else:\n            self.iid = 0 if not self.is_multi_instance else None\n\n    def __getitem__(self, iid):\n        assert self.iid is None, \"IID specified more than once\"\n        assert self.is_multi_instance or self.iid == 0, \"IID must be 0 on single-instance objects\"\n\n        return type(self)(self.resources, oid=self.oid, iid=iid,\n                          multi_instance=True, version=self.version)\n\n    def __getattr__(self, name):\n        if name in self.resources:\n            assert self.iid is not None, \"IID not specified. Use ObjectName[IID].ResourceName\"\n            return '/%d/%d/%d' % (self.oid, self.iid, self.resources[name])\n        raise AttributeError\n\n\nclass ResPath:\n    Security = Lwm2mResourcePathHelper.from_rid_object(\n        RID.Security, oid=OID.Security, multi_instance=True)\n    Server = Lwm2mResourcePathHelper.from_rid_object(\n        RID.Server, oid=OID.Server, multi_instance=True)\n    AccessControl = Lwm2mResourcePathHelper.from_rid_object(RID.AccessControl,\n                                                             oid=OID.AccessControl,\n                                                             multi_instance=True)\n    Device = Lwm2mResourcePathHelper.from_rid_object(\n        RID.Device, oid=OID.Device)\n    ConnectivityMonitoring = Lwm2mResourcePathHelper.from_rid_object(RID.ConnectivityMonitoring,\n                                                                      oid=OID.ConnectivityMonitoring)\n    FirmwareUpdate = Lwm2mResourcePathHelper.from_rid_object(\n        RID.FirmwareUpdate, oid=OID.FirmwareUpdate)\n    Location = Lwm2mResourcePathHelper.from_rid_object(\n        RID.Location, oid=OID.Location)\n    ConnectivityStatistics = Lwm2mResourcePathHelper.from_rid_object(RID.ConnectivityStatistics,\n                                                                      oid=OID.ConnectivityStatistics)\n    SoftwareManagement = Lwm2mResourcePathHelper.from_rid_object(RID.SoftwareManagement, oid=OID.SoftwareManagement,  multi_instance=True)\n    CellularConnectivity = Lwm2mResourcePathHelper.from_rid_object(RID.CellularConnectivity,\n                                                                    oid=OID.CellularConnectivity,\n                                                                    version='1.1')\n    ApnConnectionProfile = Lwm2mResourcePathHelper.from_rid_object(RID.ApnConnectionProfile,\n                                                                    oid=OID.ApnConnectionProfile,\n                                                                    multi_instance=True)\n    Temperature = Lwm2mResourcePathHelper.from_rid_object(\n        RID.Temperature, oid=OID.Temperature)\n    Accelerometer = Lwm2mResourcePathHelper.from_rid_object(\n        RID.Accelerometer, oid=OID.Accelerometer)\n    PushButton = Lwm2mResourcePathHelper.from_rid_object(\n        RID.PushButton, oid=OID.PushButton)\n    Test = Lwm2mResourcePathHelper.from_rid_object(\n        RID.Test, oid=OID.Test, multi_instance=True)\n    Portfolio = Lwm2mResourcePathHelper.from_rid_object(\n        RID.Portfolio, oid=OID.Portfolio, multi_instance=True)\n    ExtDevInfo = Lwm2mResourcePathHelper.from_rid_object(\n        RID.ExtDevInfo, oid=OID.ExtDevInfo)\n    IpPing = Lwm2mResourcePathHelper.from_rid_object(\n        RID.IpPing, oid=OID.IpPing)\n    GeoPoints = Lwm2mResourcePathHelper.from_rid_object(\n        RID.GeoPoints, oid=OID.GeoPoints, multi_instance=True)\n    DownloadDiagnostics = Lwm2mResourcePathHelper.from_rid_object(RID.DownloadDiagnostics,\n                                                                   oid=OID.DownloadDiagnostics)\n    AdvancedFirmwareUpdate = Lwm2mResourcePathHelper.from_rid_object(\n        RID.AdvancedFirmwareUpdate, oid=OID.AdvancedFirmwareUpdate, multi_instance=True)\n    BinaryAppDataContainer = Lwm2mResourcePathHelper.from_rid_object(\n        RID.BinaryAppDataContainer, oid=OID.BinaryAppDataContainer, multi_instance=True)\n    EventLog = Lwm2mResourcePathHelper.from_rid_object(RID.EventLog, oid=OID.EventLog)\n\n    @classmethod\n    def objects(cls):\n        results = []\n        for _, field in cls.__dict__.items():\n            if isinstance(field, Lwm2mResourcePathHelper):\n                results.append(field)\n        return sorted(results, key=lambda field: field.oid)\n\n\nclass TxParams(namedtuple('TxParams',\n                          ['ack_timeout',\n                           'ack_random_factor',\n                           'max_retransmit',\n                           'max_latency'],\n                          defaults=(2.0, 1.5, 4.0, 100.0))):\n    def max_transmit_wait(self):\n        return self.ack_timeout * self.ack_random_factor * (2 ** (self.max_retransmit + 1) - 1)\n\n    def max_transmit_span(self):\n        return self.ack_timeout * (2 ** self.max_retransmit - 1) * self.ack_random_factor\n\n    def exchange_lifetime(self):\n        \"\"\"\n        From RFC7252: \"PROCESSING_DELAY is the time a node takes to turn\n        around a Confirmable message into an acknowledgement. We assume\n        the node will attempt to send an ACK before having the sender time\n        out, so as a conservative assumption we set it equal to ACK_TIMEOUT\"\n\n        Thus we use self.ack_timeout as a PROCESSING_DELAY in the formula below.\n        \"\"\"\n        return self.max_transmit_span() + 2 * self.max_latency + self.ack_timeout\n\n    def first_retransmission_timeout(self):\n        return self.ack_random_factor * self.ack_timeout\n\n    def last_retransmission_timeout(self):\n        return self.first_retransmission_timeout() * 2 ** self.max_retransmit\n\n\nDEMO_ENDPOINT_NAME = 'urn:dev:os:0023C7-000001'\n"
  },
  {
    "path": "tests/integration/framework/pyproject.toml",
    "content": "# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\n[build-system]\nrequires = [\"setuptools>=68\", \"wheel\"]\nbuild-backend = \"setuptools.build_meta\"\n\n[project]\nname = \"framework\"\nversion = \"1.0.0\"\n\n[tool.setuptools]\npackage-dir = {\"\" = \".\"}\n\n[tool.setuptools.packages.find]\nwhere = [\".\"]\ninclude = [\"framework*\"]\n"
  },
  {
    "path": "tests/integration/run_tests.sh.in",
    "content": "#!/usr/bin/env bash\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\n\nCOMMAND=\"@CMAKE_CTEST_COMMAND@\";\nRERUNS=@TEST_RERUNS@;\n\nCREATE_XLSX_REPORTS=\"python3 @CMAKE_SOURCE_DIR@/tests/integration/framework/framework/create_xlsx_test_report.py \\\n                                -d @CMAKE_BINARY_DIR@/output/test/integration/log/test/\"\n\nif [ \"$1\" == \"-h\" ]; then\n    COMMAND=\"@CMAKE_CTEST_COMMAND@ -R hsm\";\nfi\n\nif [ $RERUNS == 0 ]; then\n    $COMMAND -j@NPROC@ --output-on-failure && $CREATE_XLSX_REPORTS && exit 0;\nelse\n    $COMMAND -j@NPROC@ && $CREATE_XLSX_REPORTS && exit 0;\nfi\n\nif [ $RERUNS -gt 0 ]; then\n    if  [ $RERUNS -gt 1 ]; then\n        for i in $(seq 1 $(($RERUNS-1))); do\n            $COMMAND --rerun-failed && $CREATE_XLSX_REPORTS && exit 0;\n        done;\n    fi\n    $COMMAND --rerun-failed --output-on-failure && $CREATE_XLSX_REPORTS && exit 0;\nfi\n\n$CREATE_XLSX_REPORTS\n\nexit 1\n"
  },
  {
    "path": "tests/integration/runtest.py",
    "content": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nimport sys\n\nassert sys.version_info >= (3, 5), \"Python < 3.5 is unsupported\"\n\nimport unittest\nimport os\nimport collections.abc\nimport argparse\nimport time\nimport tempfile\nimport textwrap\nimport shutil\nimport logging\n\nfrom framework.pretty_test_runner import PrettyTestRunner\nfrom framework.pretty_test_runner import COLOR_DEFAULT, COLOR_YELLOW, COLOR_GREEN, COLOR_RED\nfrom framework.test_suite import Lwm2mTest, ensure_dir, get_full_test_name, get_suite_name, \\\n    test_or_suite_matches_query_regex, LogType\n\nif sys.version_info[0] >= 3:\n    sys.stderr = os.fdopen(2, 'w', 1)  # force line buffering\n\nROOT_DIR = os.path.abspath(os.path.dirname(__file__))\nUNITTEST_PATH = os.path.join(ROOT_DIR, 'suites')\n\n\ndef traverse(tree, cls=None):\n    if cls is None or isinstance(tree, cls):\n        yield tree\n\n    if isinstance(tree, collections.abc.Iterable):\n        for elem in tree:\n            for sub_elem in traverse(elem, cls):\n                yield sub_elem\n\n\ndef discover_test_suites(test_config):\n    if getattr(test_config, 'demo_path', None) is None:\n        try:\n            import pymbedtls\n        except ModuleNotFoundError:\n            # Fake pymbedtls module so that \"runtest.py -l\" works without making pymbedtls target\n            class FakePymbedtlsModule:\n                class Context:\n                    @staticmethod\n                    def supports_connection_id():\n                        return False\n                    \n                    @staticmethod\n                    def supports_TLS_1_3():\n                        return False\n                    \n                    @staticmethod\n                    def mbedtls_version():\n                        return 0x01000000\n\n            sys.modules['pymbedtls'] = FakePymbedtlsModule\n\n    loader = unittest.TestLoader()\n    loader.testMethodPrefix = 'runTest'\n\n    suite = loader.discover(UNITTEST_PATH, pattern='*.py', top_level_dir=UNITTEST_PATH)\n\n    for error in loader.errors:\n        print(error)\n    if len(loader.errors):\n        sys.exit(-1)\n\n    for test in traverse(suite, cls=Lwm2mTest):\n        test.set_config(test_config)\n    return suite\n\n\ndef list_tests(suite, header='Available tests:'):\n    print(header)\n    for test in traverse(suite, cls=Lwm2mTest):\n        print('* %s' % get_full_test_name(test))\n    print('')\n\n\ndef run_tests(suites, config):\n    test_runner = PrettyTestRunner(config)\n\n    start_time = time.time()\n    for suite in suites:\n        if suite.countTestCases() == 0:\n            continue\n\n        logdir = os.path.join(config.logs_path, 'test', get_suite_name(suite))\n        ensure_dir(logdir)\n\n        test_runner.run(suite, logdir)\n\n    seconds_elapsed = time.time() - start_time\n    all_tests = sum(r.testsRun for r in test_runner.results)\n    successes = sum(r.testsPassed for r in test_runner.results)\n    errors = sum(r.testsErrors for r in test_runner.results)\n    failures = sum(r.testsFailed for r in test_runner.results)\n\n    print('\\nFinished in %f s; %s%d/%d successes%s, %s%d/%d errors%s, %s%d/%d failures%s\\n' % (\n        seconds_elapsed, COLOR_GREEN if successes == all_tests else COLOR_YELLOW, successes,\n        all_tests, COLOR_DEFAULT, COLOR_RED if errors else COLOR_GREEN, errors, all_tests,\n        COLOR_DEFAULT, COLOR_RED if failures else COLOR_GREEN, failures, all_tests, COLOR_DEFAULT))\n\n    return test_runner.results\n\n\ndef filter_tests(suite, query_regex):\n    matching_tests = []\n\n    for test in suite:\n        if isinstance(test, unittest.TestCase):\n            if test_or_suite_matches_query_regex(test, query_regex):\n                matching_tests.append(test)\n        elif isinstance(test, unittest.TestSuite):\n            if test.countTestCases() == 0:\n                continue\n\n            if test_or_suite_matches_query_regex(test, query_regex):\n                matching_tests.append(test)\n            else:\n                matching_suite = filter_tests(test, query_regex)\n                if matching_suite.countTestCases() > 0:\n                    matching_tests.append(matching_suite)\n\n    return unittest.TestSuite(matching_tests)\n\n\ndef merge_directory(src, dst):\n    \"\"\"\n    Move all contents of SRC into DST, preserving directory structure.\n    \"\"\"\n    for item in os.listdir(src):\n        src_item = os.path.join(src, item)\n        dst_item = os.path.join(dst, item)\n\n        if os.path.isdir(src_item):\n            merge_directory(src_item, dst_item)\n        else:\n            ensure_dir(os.path.dirname(dst_item))\n            shutil.move(src_item, dst_item)\n\n\ndef remove_tests_logs(tests):\n    for test in tests:\n        for log_type in LogType:\n            try:\n                os.remove(test.logs_path(log_type))\n            except FileNotFoundError:\n                pass\n\n\nif __name__ == \"__main__\":\n    LOG_LEVEL = os.getenv('LOGLEVEL', 'info').upper()\n    try:\n        import coloredlogs\n\n        coloredlogs.install(level=LOG_LEVEL)\n    except ImportError:\n        logging.basicConfig(level=LOG_LEVEL)\n\n    parser = argparse.ArgumentParser(description=textwrap.dedent('''\n        Runs Anjay demo client against Python integration tests.\n\n        Following environment variables are recognized:\n\n          VALGRIND - can be set to Valgrind executable path + optional arguments. If set,\n                     demo client execution command will be prefixed with the value of this\n                     variable. Note that some tests ignore this command.\n\n          NO_DUMPCAP - if set and not empty, PCAP traffic recordings between demo client\n                       and mock server are not recorded.\n\n          RR - if set and not empty, demo client execution command is prefixed\n               with `rr record` to allow post-mortem debugging with `rr replay`.\n               Takes precedence over RRR.\n\n          RRR - if set and not empty, its value is is used for regex-matching applicable\n                tests or test suites. See REGEX MATCH RULES below. For matching\n                tests/suites, demo client execution command is prefixed with `rr record`\n                to allow post-mortem debugging with `rr replay`.\n\n        REGEX MATCH RULES\n        =================\n        {regex_match_rules_help}\n    '''.format(regex_match_rules_help=textwrap.indent(\n        textwrap.dedent(test_or_suite_matches_query_regex.__doc__), prefix=' ' * 8))),\n        formatter_class=argparse.RawDescriptionHelpFormatter)\n\n    parser.add_argument('--list', '-l', action='store_true',\n                        help='only list matching test cases, do not execute them')\n    parser.add_argument('--client', '-c', type=str, help='path to the demo application to use')\n    parser.add_argument('--keep-success-logs', action='store_true',\n                        help='keep logs from all tests, including ones that passed')\n    parser.add_argument('--target-logs-path', type=str, help='path where to leave the logs stored')\n    parser.add_argument('query_regex', type=str, default='', nargs='?',\n                        help='regex used to filter test cases. See REGEX MATCH RULES for details.')\n\n    cmdline_args = parser.parse_args(sys.argv[1:])\n\n    with tempfile.TemporaryDirectory() as tmp_log_dir:\n        class TestConfig:\n            if cmdline_args.client is not None:\n                demo_cmd = os.path.basename(cmdline_args.client)\n                demo_path = os.path.abspath(os.path.dirname(cmdline_args.client))\n                target_logs_path = os.path.abspath(\n                    cmdline_args.target_logs_path or os.path.join(demo_path,\n                                                                  '../test/integration/log'))\n\n            logs_path = tmp_log_dir\n            suite_root_path = os.path.abspath(UNITTEST_PATH)\n\n\n        def config_to_string(cfg):\n            config = sorted(\n                (k, v) for k, v in cfg.__dict__.items() if not k.startswith('_'))  # skip builtins\n\n            max_key_len = max(len(k) for k, _ in config)\n\n            return '\\n  '.join(\n                ['Test config:'] + ['%%-%ds = %%s' % max_key_len % kv for kv in config])\n\n\n        test_suites = discover_test_suites(TestConfig)\n        header = '%d tests:' % test_suites.countTestCases()\n\n        if cmdline_args.query_regex:\n            test_suites = filter_tests(test_suites, cmdline_args.query_regex)\n            header = '%d tests match pattern %s:' % (\n                test_suites.countTestCases(), cmdline_args.query_regex)\n\n        list_tests(test_suites, header=header)\n\n        result = None\n        if not cmdline_args.list:\n            if cmdline_args.client is None:\n                parser.error('--client/c is required unless -l/--list is specified')\n\n            sys.stderr.write('%s\\n\\n' % config_to_string(TestConfig))\n\n            try:\n                results = run_tests(test_suites, TestConfig)\n                for r in results:\n                    if r.errors or r.failures:\n                        print(r.errorSummary(log_root=TestConfig.target_logs_path))\n                    if not cmdline_args.keep_success_logs:\n                        remove_tests_logs(r.successes)\n\n                if any(r.errors or r.failures for r in results):\n                    raise SystemError(\"Some tests failed, inspect log for details\")\n            finally:\n                # calculate logs path based on executable path to prevent it\n                # from creating files in source directory if building out of source\n                ensure_dir(os.path.dirname(TestConfig.target_logs_path))\n                merge_directory(TestConfig.logs_path, TestConfig.target_logs_path)\n"
  },
  {
    "path": "tests/integration/suites/__init__.py",
    "content": "# -*- coding: utf-8 -*-\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n"
  },
  {
    "path": "tests/integration/suites/default/__init__.py",
    "content": "# -*- coding: utf-8 -*-\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n"
  },
  {
    "path": "tests/integration/suites/default/access_control.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nfrom framework_tools.lwm2m.tlv import TLVType\nfrom framework.lwm2m_test import *\n\nfrom . import bootstrap_server\n\n# In the following test suite we assume that self.servers[0] has SSID=1, and\n# self.servers[1] has SSID=2. Current implementation of the demo guarantees\n# that at least.\n#\n# Also SSID=2 is the master, and SSID=1 is his slave, it can do things allowed\n# only by SSID=2, and this test set shall check that this is indeed the case.\n\ng = SequentialMsgIdGenerator(1)\n\n# We'd love to keep instance id in some reasonable bound before we dive into\n# a solution that makes use of discover and so on.\nIID_BOUND = 64\n\n\n# This is all defined in standard\nclass AccessMask:\n    NONE = 0\n    READ = 1 << 0\n    WRITE = 1 << 1\n    EXECUTE = 1 << 2\n    DELETE = 1 << 3\n    CREATE = 1 << 4\n\n    OWNER = READ | WRITE | EXECUTE | DELETE\n\n\n\ndef make_acl_entry(ssid, access):\n    return (int(ssid), access.to_bytes(1, byteorder='big'))\n\n\nclass AccessControl:\n    # not declaring the helper class in global scope to prevent it from being\n    # considered a test case on its own\n    class Test(test_suite.Lwm2mTest, test_suite.Lwm2mDmOperations):\n        def validate_iid(self, iid):\n            self.assertTrue(iid >= 0 and iid < IID_BOUND)\n\n        def find_access_control_instance(self, server, oid, iid, expect_existence=True):\n            self.validate_iid(iid)\n            # It is very sad, that we have to iterate through\n            # Access Control instances, but we really do.\n            for instance in range(IID_BOUND):\n                req = Lwm2mRead(ResPath.AccessControl[instance].TargetIID)\n                server.send(req)\n                res = server.recv()\n\n                # TODO: assertMsgEqual(Lwm2mResponse.matching...)\n                if res.code != coap.Code.RES_CONTENT:\n                    continue\n\n                res = self.read_resource(server, OID.AccessControl, instance,\n                                         RID.AccessControl.TargetOID)\n                ret_oid = int(res.content)\n\n                res = self.read_resource(server, OID.AccessControl, instance,\n                                         RID.AccessControl.TargetIID)\n                ret_iid = int(res.content)\n\n                if ret_oid == oid and ret_iid == iid:\n                    return instance\n            if expect_existence:\n                assert False, \"%d/%d/%d does not exist\" % (OID.AccessControl, oid, iid)\n            return None\n\n        def update_access(self, server, oid, iid, acl, expected_acl=None, expect_error_code=None):\n            self.validate_iid(iid)\n            # Need to find Access Control instance for this iid.\n            ac_iid = self.find_access_control_instance(server, oid, iid)\n            self.assertTrue(ac_iid > 0)\n\n            tlv = TLV.make_multires(RID.AccessControl.ACL, acl).serialize()\n            if expected_acl:\n                assert expect_error_code is None\n                expected_tlv = TLV.make_multires(RID.AccessControl.ACL, expected_acl).serialize()\n            else:\n                expected_tlv = tlv\n            self.write_instance(server, OID.AccessControl, ac_iid, tlv, partial=True,\n                                expect_error_code=expect_error_code)\n            if not expect_error_code:\n                read_tlv = self.read_resource(server, OID.AccessControl, ac_iid,\n                                              RID.AccessControl.ACL,\n                                              accept=coap.ContentFormat.APPLICATION_LWM2M_TLV)\n                self.assertEqual(expected_tlv, read_tlv.content)\n\n        def setUp(self, servers=2, oid=OID.Test, extra_cmdline_args=[], **kwargs):\n            if isinstance(servers, int):\n                servers_count = servers\n            else:\n                servers_count = len(servers)\n            extra_args = sum((['--access-entry', '/%d/65535,%d,%d' % (oid, ssid, AccessMask.CREATE)] for ssid in\n                              range(2, servers_count + 1)),\n                             extra_cmdline_args)\n            self.setup_demo_with_servers(servers=servers,\n                                         extra_cmdline_args=extra_args,\n                                         **kwargs)\n\n        def tearDown(self):\n            self.teardown_demo_with_servers()\n\n\nclass CreateTest(AccessControl.Test):\n    def runTest(self):\n        observe_req = Lwm2mObserve('/%d' % (OID.AccessControl,))\n        self.servers[0].send(observe_req)\n        self.assertMsgEqual(Lwm2mContent.matching(observe_req)(), self.servers[0].recv())\n\n        # SSID 2 has Create flag on OID.Test object, let's check if it\n        # can really make an instance\n        self.create_instance(server=self.servers[1], oid=OID.Test)\n\n        # notification should arrive\n        self.assertMsgEqual(Lwm2mNotify(observe_req.token), self.servers[0].recv())\n\n        # Now do the same with SSID 1, it should fail very very much\n        self.create_instance(server=self.servers[0], oid=OID.Test,\n                             expect_error_code=coap.Code.RES_UNAUTHORIZED)\n\n\nclass ReadTest(AccessControl.Test):\n    def runTest(self):\n        self.create_instance(server=self.servers[1], oid=OID.Test)\n\n        # SSID 2 has rights obviously\n        self.read_resource(server=self.servers[1], oid=OID.Test, iid=0, rid=RID.Test.Timestamp)\n\n        # SSID 1 has no rights, it should not be able to read instance / resource\n        self.read_resource(server=self.servers[0], oid=OID.Test, iid=0, rid=RID.Test.Timestamp,\n                           expect_error_code=coap.Code.RES_UNAUTHORIZED)\n        self.read_instance(server=self.servers[0], oid=OID.Test, iid=0,\n                           expect_error_code=coap.Code.RES_UNAUTHORIZED)\n\n\nclass WriteAttributesTest(AccessControl.Test):\n    def runTest(self):\n        self.create_instance(server=self.servers[1], oid=OID.Test)\n\n        # SSID 2 can perform Write-Attributes\n        self.write_attributes(server=self.servers[1], oid=OID.Test, iid=0, query=['pmin=500'])\n\n        # SSID 1 can't, as it cannot Read\n        self.write_attributes(server=self.servers[0], oid=OID.Test, iid=0, query=['pmin=600'],\n                              expect_error_code=coap.Code.RES_UNAUTHORIZED)\n\n\nclass ChangingReadFlagsMatterTest(AccessControl.Test):\n    def runTest(self):\n        self.create_instance(server=self.servers[1], oid=OID.Test)\n\n        # Update SSID 1 access rights, so that he'll be able to read resource\n        self.update_access(server=self.servers[1], oid=OID.Test, iid=0,\n                           acl=[make_acl_entry(1, AccessMask.READ),\n                                make_acl_entry(2, AccessMask.OWNER)])\n\n        # SSID 1 shall be able to read the resource and the entire instance too\n        self.read_resource(server=self.servers[0], oid=OID.Test, iid=0, rid=RID.Test.Timestamp)\n        self.read_instance(server=self.servers[0], oid=OID.Test, iid=0)\n\n\nclass ChangingFlagsNotifiesTest(AccessControl.Test):\n    def runTest(self):\n        self.create_instance(server=self.servers[1], oid=OID.Test)\n\n        observe_req = Lwm2mObserve('/%d' % (OID.AccessControl,))\n        self.servers[0].send(observe_req)\n        self.assertMsgEqual(Lwm2mContent.matching(observe_req)(), self.servers[0].recv())\n\n        # Update SSID 1 access rights\n        self.update_access(server=self.servers[1], oid=OID.Test, iid=0,\n                           acl=[make_acl_entry(1, AccessMask.READ),\n                                make_acl_entry(2, AccessMask.OWNER)])\n\n        # notification should arrive\n        self.assertMsgEqual(Lwm2mNotify(observe_req.token), self.servers[0].recv())\n\n\nclass ExecuteTest(AccessControl.Test):\n    def runTest(self):\n        self.create_instance(server=self.servers[1], oid=OID.Test)\n\n        self.execute_resource(self.servers[1], OID.Test, 0, RID.Test.IncrementCounter)\n\n        # No fun for you SSID 1!\n        self.execute_resource(server=self.servers[0], oid=OID.Test, iid=0,\n                              rid=RID.Test.IncrementCounter,\n                              expect_error_code=coap.Code.RES_UNAUTHORIZED)\n\n\nclass ChangingExecuteFlagsMatter(AccessControl.Test):\n    def runTest(self):\n        self.create_instance(server=self.servers[1], oid=OID.Test)\n\n        self.update_access(server=self.servers[1], oid=OID.Test, iid=0,\n                           acl=[make_acl_entry(1, AccessMask.EXECUTE),\n                                make_acl_entry(2, AccessMask.OWNER)])\n\n        self.execute_resource(self.servers[0], OID.Test, 0, RID.Test.IncrementCounter)\n        self.execute_resource(self.servers[1], OID.Test, 0, RID.Test.IncrementCounter)\n\n\nclass DeleteTest(AccessControl.Test):\n    def runTest(self):\n        self.create_instance(server=self.servers[1], oid=OID.Test)\n\n        observe_req = Lwm2mObserve('/%d' % (OID.AccessControl,))\n        self.servers[0].send(observe_req)\n        self.assertMsgEqual(Lwm2mContent.matching(observe_req)(), self.servers[0].recv())\n\n        ac_iid = self.find_access_control_instance(server=self.servers[1], oid=OID.Test, iid=0)\n\n        self.delete_instance(server=self.servers[0], oid=OID.Test, iid=0,\n                             expect_error_code=coap.Code.RES_UNAUTHORIZED)\n        self.delete_instance(server=self.servers[1], oid=OID.Test, iid=0)\n\n        # notification should arrive\n        self.assertMsgEqual(Lwm2mNotify(observe_req.token), self.servers[0].recv())\n\n        # Instance is removed, so its corresponding Access Control Instance\n        # should be removed automatically too. The answer shall be NOT_FOUND\n        # according to the spec.\n        self.read_instance(server=self.servers[1], oid=OID.AccessControl, iid=ac_iid,\n                           expect_error_code=coap.Code.RES_NOT_FOUND)\n\n\nclass EmptyAclMeansFullAccessTest(AccessControl.Test):\n    def runTest(self):\n        self.create_instance(server=self.servers[1], oid=OID.Test)\n        self.update_access(server=self.servers[1], oid=OID.Test, iid=0, acl=[],\n                           expected_acl=[make_acl_entry(2, AccessMask.OWNER)])\n\n        # Read\n        self.read_resource(server=self.servers[1], oid=OID.Test, iid=0, rid=RID.Test.Timestamp)\n        self.read_resource(server=self.servers[0], oid=OID.Test, iid=0, rid=RID.Test.Timestamp,\n                           expect_error_code=coap.Code.RES_UNAUTHORIZED)\n        self.read_instance(server=self.servers[0], oid=OID.Test, iid=0,\n                           expect_error_code=coap.Code.RES_UNAUTHORIZED)\n        # Write\n        self.write_instance(server=self.servers[1], oid=OID.Test, iid=0, partial=True)\n        self.write_instance(server=self.servers[0], oid=OID.Test, iid=0, partial=True,\n                            expect_error_code=coap.Code.RES_UNAUTHORIZED)\n        # Execute\n        self.execute_resource(server=self.servers[1], oid=OID.Test, iid=0,\n                              rid=RID.Test.IncrementCounter)\n        # Delete\n        self.delete_instance(server=self.servers[1], oid=OID.Test, iid=0)\n\n\nclass NoDuplicatedAclTest(AccessControl.Test):\n    def runTest(self):\n        self.create_instance(server=self.servers[1], oid=OID.Test)\n        self.update_access(server=self.servers[1], oid=OID.Test, iid=0,\n                           acl=[make_acl_entry(2, AccessMask.OWNER),\n                                make_acl_entry(2, 0),\n                                make_acl_entry(2, AccessMask.OWNER)],\n                           expected_acl=[make_acl_entry(2, AccessMask.OWNER)])\n\n        ac_iid = self.find_access_control_instance(server=self.servers[1], oid=OID.Test, iid=0)\n\n        res = self.read_resource(server=self.servers[1], oid=OID.AccessControl, iid=ac_iid,\n                                 rid=RID.AccessControl.ACL)\n        self.assertEqual(coap.ContentFormat.APPLICATION_LWM2M_TLV, res.get_content_format())\n\n        tlv = TLV.parse(res.content)\n        # One Multiple Resource\n        self.assertEqual(len(tlv), 1)\n        # with valid ID\n        self.assertEqual(tlv[0].identifier, RID.AccessControl.ACL)\n        # with exactly one Instance (2,AccessMask.OWNER)\n        self.assertEqual(len(tlv[0].value), 1)\n        self.assertEqual(tlv[0].value[0].value, AccessMask.OWNER.to_bytes(1, byteorder='big'))\n\n\nclass DefaultAclTest(AccessControl.Test):\n    def runTest(self):\n        self.read_instance(server=self.servers[0], oid=OID.Server, iid=1)\n        self.read_instance(server=self.servers[0], oid=OID.Server, iid=2,\n                           expect_error_code=coap.Code.RES_UNAUTHORIZED)\n        self.read_instance(server=self.servers[1], oid=OID.Server, iid=1,\n                           expect_error_code=coap.Code.RES_UNAUTHORIZED)\n        self.read_instance(server=self.servers[1], oid=OID.Server, iid=2)\n\n        self.read_path(self.servers[0], ResPath.Device.SerialNumber)\n\n        self.create_instance(server=self.servers[1], oid=OID.Test)\n        self.update_access(server=self.servers[1], oid=OID.Test, iid=0,\n                           acl=[make_acl_entry(0, AccessMask.EXECUTE),\n                                make_acl_entry(2, AccessMask.OWNER)])\n\n        # Now SSID 1 should be able to execute because he obtains access from the\n        # default ACL (with ID=0)\n        self.execute_resource(server=self.servers[0], oid=OID.Test, iid=0,\n                              rid=RID.Test.IncrementCounter)\n\n        # Now we take away the rights and make sure SSID1 can not do execute\n        self.update_access(server=self.servers[1], oid=OID.Test, iid=0,\n                           acl=[make_acl_entry(0, AccessMask.NONE)],\n                           expected_acl=[make_acl_entry(0, AccessMask.NONE),\n                                         make_acl_entry(2, AccessMask.OWNER)])\n\n        self.execute_resource(server=self.servers[0], oid=OID.Test, iid=0,\n                              rid=RID.Test.IncrementCounter,\n                              expect_error_code=coap.Code.RES_UNAUTHORIZED)\n\n\nclass ReadObjectWithPartialReadAccessTest(AccessControl.Test):\n    def runTest(self):\n        self.create_instance(server=self.servers[1], oid=OID.Test)  # IID=1\n        self.create_instance(server=self.servers[1], oid=OID.Test)  # IID=2\n        self.create_instance(server=self.servers[1], oid=OID.Test)  # IID=3\n\n        # IID=0 will be readable by SSID=1 because of default ACL\n        self.update_access(server=self.servers[1], oid=OID.Test, iid=0,\n                           acl=[make_acl_entry(0, AccessMask.READ),\n                                make_acl_entry(2, AccessMask.OWNER)])\n        # IID=1 will not be readable by SSID=1\n        self.update_access(server=self.servers[1], oid=OID.Test, iid=1,\n                           acl=[make_acl_entry(0, AccessMask.EXECUTE),\n                                make_acl_entry(2, AccessMask.OWNER)])\n        # IID=2 will be readable by SSID=1 because of direct ACL entry\n        self.update_access(server=self.servers[1], oid=OID.Test, iid=2,\n                           acl=[make_acl_entry(1, AccessMask.READ),\n                                make_acl_entry(2, AccessMask.OWNER)])\n\n        # SSID=2 should see all three Instances\n        res = self.read_object(server=self.servers[1], oid=OID.Test)\n        self.assertEqual(coap.ContentFormat.APPLICATION_LWM2M_TLV, res.get_content_format())\n\n        tlv = TLV.parse(res.content)\n        expected_instances = set([0, 1, 2])\n        self.assertEqual(len(tlv), len(expected_instances))\n        for instance in tlv:\n            self.assertEqual(instance.tlv_type, TLVType.INSTANCE)\n            expected_instances.remove(instance.identifier)\n        self.assertEqual(len(expected_instances), 0)\n\n        # And SSID=1 should see only IID=1 and IID=3\n        res = self.read_object(server=self.servers[0], oid=OID.Test)\n        self.assertEqual(coap.ContentFormat.APPLICATION_LWM2M_TLV, res.get_content_format())\n\n        tlv = TLV.parse(res.content)\n        expected_instances = set([0, 2])\n        self.assertEqual(len(tlv), len(expected_instances))\n        for instance in tlv:\n            self.assertEqual(instance.tlv_type, TLVType.INSTANCE)\n            expected_instances.remove(instance.identifier)\n        self.assertEqual(len(expected_instances), 0)\n\n\nclass ReadObjectWithNoInstancesTest(AccessControl.Test):\n    def runTest(self):\n        res = self.read_object(server=self.servers[1], oid=OID.Test)\n        self.assertEqual(coap.ContentFormat.APPLICATION_LWM2M_TLV, res.get_content_format())\n\n        tlv = TLV.parse(res.content)\n        self.assertEqual(len(tlv), 0)\n        self.create_instance(server=self.servers[1], oid=OID.Test)\n        self.create_instance(server=self.servers[1], oid=OID.Test)\n        self.create_instance(server=self.servers[1], oid=OID.Test)\n\n        res = self.read_object(server=self.servers[0], oid=OID.Test)\n        self.assertEqual(coap.ContentFormat.APPLICATION_LWM2M_TLV, res.get_content_format())\n\n        tlv = TLV.parse(res.content)\n        self.assertEqual(len(tlv), 0)\n\n\nclass ActionOnNonexistentInstanceTest(AccessControl.Test):\n    def runTest(self):\n        # Every instance action on nonexistent instance shall return NOT_FOUND\n        self.read_instance(server=self.servers[0], oid=OID.Test, iid=2,\n                           expect_error_code=coap.Code.RES_NOT_FOUND)\n        self.delete_instance(server=self.servers[0], oid=OID.Test, iid=2,\n                             expect_error_code=coap.Code.RES_NOT_FOUND)\n        self.write_instance(server=self.servers[0], oid=OID.Test, iid=2,\n                            expect_error_code=coap.Code.RES_NOT_FOUND)\n        self.execute_resource(server=self.servers[0], oid=OID.Test, iid=2,\n                              rid=RID.Test.IncrementCounter,\n                              expect_error_code=coap.Code.RES_NOT_FOUND)\n\n\nclass RemovingAcoInstanceFailsTest(AccessControl.Test):\n    def runTest(self):\n        self.create_instance(server=self.servers[1], oid=OID.Test)\n        ac_iid = self.find_access_control_instance(self.servers[1], oid=OID.Test, iid=0)\n        self.delete_instance(server=self.servers[1], oid=OID.AccessControl, iid=ac_iid,\n                             expect_error_code=coap.Code.RES_UNAUTHORIZED)\n\n\nclass EveryoneHasReadAccessToAcoInstancesTest(AccessControl.Test):\n    def runTest(self):\n        self.read_instance(server=self.servers[0], oid=OID.AccessControl, iid=0)\n        self.read_instance(server=self.servers[1], oid=OID.AccessControl, iid=0)\n\n\nclass UnbootstrappingOnlyOneOwnerTest(AccessControl.Test):\n    def setUp(self):\n        super().setUp(servers=3)\n\n    def runTest(self):\n        self.create_instance(server=self.servers[2], oid=OID.Test)\n        ac_iid = self.find_access_control_instance(server=self.servers[2], oid=OID.Test, iid=0)\n        assert ac_iid\n        # Deleting server shall delete ACO Instance and /OID.Test/0 instance too.\n        self.communicate('trim-servers 2')\n        self.assertDemoDeregisters(self.servers[2])\n        self.assertDemoUpdatesRegistration(self.servers[0], content=ANY)\n        self.assertDemoUpdatesRegistration(self.servers[1], content=ANY)\n        del (self.servers[2])\n\n        assert not self.find_access_control_instance(server=self.servers[1], oid=OID.Test, iid=0,\n                                                     expect_existence=False)\n        self.read_instance(server=self.servers[1], oid=OID.Test, iid=1,\n                           expect_error_code=coap.Code.RES_NOT_FOUND)\n\n\nclass UnbootstrappingOwnerElection1(AccessControl.Test):\n    def setUp(self):\n        super().setUp(servers=3)\n\n    def runTest(self):\n        self.create_instance(server=self.servers[2], oid=OID.Test)\n        self.update_access(server=self.servers[2], oid=OID.Test, iid=0,\n                           acl=[make_acl_entry(1, AccessMask.WRITE | AccessMask.DELETE),\n                                make_acl_entry(2, AccessMask.WRITE | AccessMask.EXECUTE),\n                                make_acl_entry(3, AccessMask.OWNER)])\n        self.communicate('trim-servers 2')\n        self.assertDemoDeregisters(self.servers[2])\n        self.assertDemoUpdatesRegistration(self.servers[0], content=ANY)\n        self.assertDemoUpdatesRegistration(self.servers[1], content=ANY)\n        del (self.servers[2])\n        ac_iid = self.find_access_control_instance(server=self.servers[1], oid=OID.Test, iid=0)\n\n        # SSID=1 shall win the election\n        res = self.read_resource(self.servers[1],\n                                 OID.AccessControl, ac_iid, RID.AccessControl.Owner)\n        self.assertEqual(b'1', res.content)\n\n        serv3 = Lwm2mServer()\n        self.communicate('add-server coap://127.0.0.1:%d' % serv3.get_listen_port())\n        self.assertDemoUpdatesRegistration(self.servers[0], content=ANY)\n        self.assertDemoUpdatesRegistration(self.servers[1], content=ANY)\n        self.assertDemoRegisters(serv3)\n\n        # new SSID=3 is not the same server and does not have access any more\n        self.create_instance(server=serv3, oid=OID.Test,\n                             expect_error_code=coap.Code.RES_UNAUTHORIZED)\n\n\nclass UnbootstrappingOwnerElection2(AccessControl.Test):\n    def setUp(self):\n        super().setUp(servers=3)\n\n    def runTest(self):\n        self.create_instance(server=self.servers[2], oid=OID.Test)\n        self.update_access(server=self.servers[2], oid=OID.Test, iid=0,\n                           acl=[make_acl_entry(1, AccessMask.WRITE | AccessMask.EXECUTE),\n                                make_acl_entry(2, AccessMask.WRITE | AccessMask.DELETE),\n                                make_acl_entry(3, AccessMask.OWNER)])\n        self.communicate('trim-servers 2')\n        self.assertDemoDeregisters(self.servers[2])\n        self.assertDemoUpdatesRegistration(self.servers[0], content=ANY)\n        self.assertDemoUpdatesRegistration(self.servers[1], content=ANY)\n        del (self.servers[2])\n        ac_iid = self.find_access_control_instance(server=self.servers[1], oid=OID.Test, iid=0)\n\n        # SSID=2 shall win the election now\n        res = self.read_resource(self.servers[1],\n                                 OID.AccessControl, ac_iid, RID.AccessControl.Owner)\n        self.assertEqual(b'2', res.content)\n\n\nclass AclActiveDespiteOnlyOneServerSuccessfullyConnected(AccessControl.Test):\n    def setUp(self):\n        super().setUp(auto_register=False)\n\n    def tearDown(self):\n        self.teardown_demo_with_servers(deregister_servers=[self.servers[0]])\n\n    def runTest(self):\n        self.assertDemoRegisters(self.servers[0])\n\n        # reject the Register on second server\n        second_register = self.servers[1].recv()\n        self.assertIsInstance(second_register, Lwm2mRegister)\n        self.servers[1].send(Lwm2mReset.matching(second_register)())\n\n        # SSID 1 has no rights to read information about SSID 2, even though it's the only properly connected server\n        self.read_resource(server=self.servers[0], oid=OID.Server, iid=2,\n                           rid=RID.Server.ShortServerID,\n                           expect_error_code=coap.Code.RES_UNAUTHORIZED)\n\n\nclass AclBootstrapping(bootstrap_server.BootstrapServer.Test, test_suite.Lwm2mDmOperations):\n    def add_server(self, iid):\n        server = Lwm2mServer()\n        uri = 'coap://127.0.0.1:%d' % server.get_listen_port()\n        self.write_instance(self.bootstrap_server, OID.Security, iid,\n                            TLV.make_resource(RID.Security.ServerURI, uri).serialize()\n                            + TLV.make_resource(RID.Security.Bootstrap, 0).serialize()\n                            + TLV.make_resource(RID.Security.Mode, 3).serialize()\n                            + TLV.make_resource(RID.Security.ShortServerID, iid).serialize()\n                            + TLV.make_resource(RID.Security.PKOrIdentity, \"\").serialize()\n                            + TLV.make_resource(RID.Security.SecretKey, \"\").serialize())\n        self.write_instance(self.bootstrap_server, OID.Server, iid,\n                            TLV.make_resource(RID.Server.Lifetime, 86400).serialize()\n                            + TLV.make_resource(RID.Server.Binding, \"U\").serialize()\n                            + TLV.make_resource(RID.Server.ShortServerID, iid).serialize()\n                            + TLV.make_resource(RID.Server.NotificationStoring, True).serialize())\n        return server\n\n    def runTest(self):\n        self.bootstrap_server.connect_to_client(('127.0.0.1', self.get_demo_port()))\n\n        # Bootstrap Delete /\n        req = Lwm2mDelete('/')\n        self.bootstrap_server.send(req)\n        self.assertMsgEqual(Lwm2mDeleted.matching(req)(),\n                            self.bootstrap_server.recv())\n\n        # create servers\n        self.servers = [self.add_server(1), self.add_server(2)]\n\n        # create test objects\n        self.write_instance(self.bootstrap_server, OID.Test, 42)\n        self.write_instance(self.bootstrap_server, OID.Test, 69)\n        self.write_instance(self.bootstrap_server, OID.Test, 514)\n\n        # create ACLs\n        self.write_instance(self.bootstrap_server, OID.AccessControl, 7,\n                            TLV.make_resource(RID.AccessControl.TargetOID, OID.Test).serialize()\n                            + TLV.make_resource(RID.AccessControl.TargetIID, 514).serialize()\n                            + TLV.make_multires(RID.AccessControl.ACL, {2: 7}.items()).serialize()\n                            + TLV.make_resource(RID.AccessControl.Owner, 2).serialize())\n        self.write_instance(self.bootstrap_server, OID.AccessControl, 9,\n                            TLV.make_resource(RID.AccessControl.TargetOID, OID.Test).serialize()\n                            + TLV.make_resource(RID.AccessControl.TargetIID, 42).serialize()\n                            + TLV.make_multires(RID.AccessControl.ACL, {1: 7}.items()).serialize()\n                            + TLV.make_resource(RID.AccessControl.Owner, 1).serialize())\n\n        # check that those are the only ACLs currently in data model\n        self.assertIn(\n            b'</%d>,</%d/7>,</%d/9>,</%d>' % (\n                OID.AccessControl, OID.AccessControl, OID.AccessControl, OID.Device),\n            self.discover(self.bootstrap_server).content)\n\n        # send Bootstrap Finish\n        req = Lwm2mBootstrapFinish()\n        self.bootstrap_server.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.bootstrap_server.recv())\n\n        self.assertDemoRegisters(self.servers[0])\n        self.assertDemoRegisters(self.servers[1])\n\n        # SSID 1 rights\n        self.read_resource(server=self.servers[0], oid=OID.Test, iid=42, rid=RID.Test.Timestamp)\n        self.read_resource(server=self.servers[0], oid=OID.Test, iid=69, rid=RID.Test.Timestamp,\n                           expect_error_code=coap.Code.RES_UNAUTHORIZED)\n        self.read_resource(server=self.servers[0], oid=OID.Test, iid=514, rid=RID.Test.Timestamp,\n                           expect_error_code=coap.Code.RES_UNAUTHORIZED)\n\n        # SSID 2 rights\n        self.read_resource(server=self.servers[1], oid=OID.Test, iid=42, rid=RID.Test.Timestamp,\n                           expect_error_code=coap.Code.RES_UNAUTHORIZED)\n        self.read_resource(server=self.servers[1], oid=OID.Test, iid=69, rid=RID.Test.Timestamp,\n                           expect_error_code=coap.Code.RES_UNAUTHORIZED)\n        self.read_resource(server=self.servers[1], oid=OID.Test, iid=514, rid=RID.Test.Timestamp)\n\n\nclass InvalidAcl(AccessControl.Test):\n    def runTest(self):\n        self.create_instance(server=self.servers[1], oid=OID.Test)\n\n        # check that one cannot create an ACL with RIID==65535 (and that such attempt does not segfault)\n        self.update_access(server=self.servers[1], oid=OID.Test, iid=0,\n                           acl=[make_acl_entry(65535, AccessMask.READ)],\n                           expect_error_code=coap.Code.RES_BAD_REQUEST)\n\n        # check that valid ACL works after previous failed attempt\n        self.update_access(server=self.servers[1], oid=OID.Test, iid=0,\n                           acl=[make_acl_entry(2, AccessMask.READ)])\n"
  },
  {
    "path": "tests/integration/suites/default/advanced_firmware_update.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nimport asyncio\nimport http\nimport http.server\nimport os\nimport re\nimport ssl\nimport threading\nimport unittest\n\nfrom .firmware_update import FirmwareUpdate, UpdateState\nfrom .firmware_update import UpdateResult as FU_UpdateResult\nfrom framework_tools.coap_file_server import CoapFileServer\nfrom framework.lwm2m_test import *\nfrom framework.create_package import PackageForcedError, make_firmware_package, make_multiple_firmware_package\n\nfrom .access_control import AccessMask\nfrom .block_write import Block, equal_chunk_splitter, msg_id_generator\n\nclass UpdateSeverity:\n    CRITICAL = 0\n    MANDATORY = 1\n    OPTIONAL = 2\n\n\nclass UpdateResult(FU_UpdateResult):\n    CONFLICTING_STATE = 12\n    DEPENDENCY_ERROR = 13\n\n\nclass Instances:\n    APP = 0\n    TEE = 1\n    BOOT = 2\n    MODEM = 3\n\n\ndef packets_from_chunks(chunks, process_options=None,\n                        path=ResPath.AdvancedFirmwareUpdate[\n                            Instances.APP].Package,\n                        format=coap.ContentFormat.APPLICATION_OCTET_STREAM,\n                        code=coap.Code.REQ_PUT):\n    for idx, chunk in enumerate(chunks):\n        has_more = (idx != len(chunks) - 1)\n\n        options = ((uri_path_to_options(path) if path is not None else [])\n                   + [coap.Option.CONTENT_FORMAT(format),\n                      coap.Option.BLOCK1(seq_num=chunk.idx, has_more=has_more,\n                                         block_size=chunk.size)])\n\n        if process_options is not None:\n            options = process_options(options, idx)\n\n        yield coap.Packet(type=coap.Type.CONFIRMABLE,\n                          code=code,\n                          token=random_stuff(size=5),\n                          msg_id=next(msg_id_generator),\n                          options=options,\n                          content=chunk.content)\n\n\nGARBAGE_FILE = b'GARBAGE'\nDUMMY_FILE = os.urandom(1 * 1024)\nDUMMY_LONG_FILE = os.urandom(128 * 1024)\nFIRMWARE_PATH = '/firmware'\nFIRMWARE_SCRIPT_TEMPLATE = '#!/bin/sh\\n%secho updated > \"%s\"\\nrm \"$0\"\\n'\n\n\n#\n# Test cases below are derived from test cases used to test Firmware Update\n#\n\nclass AdvancedFirmwareUpdate:\n    class Test(test_suite.Lwm2mSingleServerTest):\n        FW_PKG_OPTS = {'magic': b'AJAY_APP', 'linked': []}\n\n        def set_auto_deregister(self, auto_deregister):\n            self.auto_deregister = auto_deregister\n\n        def set_check_marker(self, check_marker):\n            self.check_marker = check_marker\n\n        def set_reset_machine(self, reset_machine):\n            self.reset_machine = reset_machine\n\n        def set_expect_send_after_state_machine_reset(\n                self, expect_send_after_state_machine_reset):\n            self.expect_send_after_state_machine_reset = expect_send_after_state_machine_reset\n\n        def setUp(self, garbage=0, *args, **kwargs):\n            garbage_lines = ''\n            while garbage > 0:\n                garbage_line = '#' * (min(garbage, 80) - 1) + '\\n'\n                garbage_lines += garbage_line\n                garbage -= len(garbage_line)\n            self.ANJAY_MARKER_FILE = generate_temp_filename(\n                dir='/tmp', prefix='anjay-afu-marked-')\n            self.ORIGINAL_IMG_FILE = generate_temp_filename(\n                dir='/tmp', prefix='anjay-afu-bootloader-')\n            with open(self.ORIGINAL_IMG_FILE, 'wb') as f:\n                f.write(GARBAGE_FILE)\n            self.FIRMWARE_SCRIPT_CONTENT = \\\n                (FIRMWARE_SCRIPT_TEMPLATE %\n                 (garbage_lines, self.ANJAY_MARKER_FILE)).encode('ascii')\n            super().setUp(afu_marker_path=self.ANJAY_MARKER_FILE,\n                          afu_original_img_file_path=self.ORIGINAL_IMG_FILE,\n                          *args, **kwargs)\n\n        def tearDown(self):\n            auto_deregister = getattr(self, 'auto_deregister', True)\n            check_marker = getattr(self, 'check_marker', False)\n            reset_machine = getattr(self, 'reset_machine', True)\n            expect_send_after_state_machine_reset = getattr(self,\n                                                            'expect_send_after_state_machine_reset',\n                                                            False)\n            try:\n                if not check_marker:\n                    return\n                for _ in range(10):\n                    time.sleep(0.5)\n\n                    if os.path.isfile(self.ANJAY_MARKER_FILE):\n                        break\n                else:\n                    self.fail('firmware marker not created')\n                with open(self.ANJAY_MARKER_FILE, \"rb\") as f:\n                    line = f.readline()[:-1]\n                    self.assertEqual(line, b\"updated\")\n                os.unlink(self.ANJAY_MARKER_FILE)\n            finally:\n                os.unlink(self.ORIGINAL_IMG_FILE)\n                if reset_machine:\n                    # reset the state machine\n                    # Write /33629/0/1 (Firmware URI)\n                    req = Lwm2mWrite(ResPath.AdvancedFirmwareUpdate[\n                                         Instances.APP].PackageURI, '')\n                    self.serv.send(req)\n                    self.assertMsgEqual(\n                        Lwm2mChanged.matching(req)(), self.serv.recv())\n                    if expect_send_after_state_machine_reset:\n                        pkt = self.serv.recv()\n                        self.assertMsgEqual(Lwm2mSend(), pkt)\n                        CBOR.parse(pkt.content).verify_values(test=self,\n                                                              expected_value_map={\n                                                                  ResPath.AdvancedFirmwareUpdate[\n                                                                      Instances.APP].State: UpdateState.IDLE,\n                                                                  ResPath.AdvancedFirmwareUpdate[\n                                                                      Instances.APP].UpdateResult: UpdateResult.INITIAL\n                                                              })\n                        self.serv.send(Lwm2mChanged.matching(pkt)())\n                super().tearDown(auto_deregister=auto_deregister)\n\n        def read_update_result(self, inst: int):\n            req = Lwm2mRead(ResPath.AdvancedFirmwareUpdate[inst].UpdateResult)\n            self.serv.send(req)\n            res = self.serv.recv()\n            self.assertMsgEqual(Lwm2mContent.matching(req)(), res)\n            return int(res.content)\n\n        def read_state(self, inst: int):\n            req = Lwm2mRead(ResPath.AdvancedFirmwareUpdate[inst].State)\n            self.serv.send(req)\n            res = self.serv.recv()\n            self.assertMsgEqual(Lwm2mContent.matching(req)(), res)\n            return int(res.content)\n\n        def read_linked(self, inst: int):\n            req = Lwm2mRead(\n                ResPath.AdvancedFirmwareUpdate[inst].LinkedInstances)\n            self.serv.send(req)\n            res = self.serv.recv()\n            self.assertMsgEqual(Lwm2mContent.matching(req)(), res)\n            return res.content\n\n        def read_linked_and_check(self, inst: int, expected_inst: list):\n            \"\"\"\n            expected_inst   -- list of tuples, each of form (Resource Instance ID, Value)\n            \"\"\"\n            received = self.read_linked(inst)\n            expected = TLV.make_multires(\n                RID.AdvancedFirmwareUpdate.LinkedInstances,\n                expected_inst)\n            self.assertEqual(expected.serialize(), received)\n\n        def read_conflicting(self, inst: int):\n            req = Lwm2mRead(\n                ResPath.AdvancedFirmwareUpdate[inst].ConflictingInstances)\n            self.serv.send(req)\n            res = self.serv.recv()\n            self.assertMsgEqual(Lwm2mContent.matching(req)(), res)\n            return res.content\n\n        def read_conflicting_and_check(self, inst: int,\n                                       expected_inst: list):\n            \"\"\"\n            expected_inst   -- list of tuples, each of form (Resource Instance ID, Value)\n            \"\"\"\n            received = self.read_conflicting(inst)\n            expected = TLV.make_multires(\n                RID.AdvancedFirmwareUpdate.ConflictingInstances,\n                expected_inst)\n            self.assertEqual(expected.serialize(), received)\n\n        def write_firmware_and_wait_for_download(self, inst: int,\n                                                 firmware_uri: str,\n                                                 download_timeout_s=20):\n            # Write /33629/inst/1 (Firmware URI)\n            req = Lwm2mWrite(ResPath.AdvancedFirmwareUpdate[inst].PackageURI,\n                             firmware_uri)\n            self.serv.send(req)\n            self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                                self.serv.recv())\n\n            # wait until client downloads the firmware\n            deadline = time.time() + download_timeout_s\n            while time.time() < deadline:\n                time.sleep(0.5)\n\n                if self.read_state(inst) == UpdateState.DOWNLOADED:\n                    return\n\n            self.fail('firmware still not downloaded')\n\n        def wait_until_state_is(self, inst, state, timeout_s=10):\n            deadline = time.time() + timeout_s\n            while time.time() < deadline:\n                time.sleep(0.1)\n                if self.read_state(inst) == state:\n                    return\n\n            self.fail(f'state still is not {state}')\n\n        def execute_update_and_check_success(self, inst):\n            # Execute /33629/inst/2 (Update)\n            req = Lwm2mExecute(ResPath.AdvancedFirmwareUpdate[inst].Update)\n            self.serv.send(req)\n            self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                                self.serv.recv())\n\n            self.wait_until_state_is(inst, UpdateState.IDLE)\n\n            # Check /33629/inst result\n            self.assertEqual(UpdateResult.SUCCESS,\n                             self.read_update_result(inst))\n\n        def prepare_package(self, firmware: bytes):\n            self.PACKAGE = make_firmware_package(\n                firmware, **self.FW_PKG_OPTS)\n\n        def prepare_package_app_img(self, use_real_app=False):\n            if use_real_app:\n                with open(os.path.join(self.config.demo_path,\n                                       self.config.demo_cmd), 'rb') as f:\n                    firmware = f.read()\n            else:\n                firmware = self.FIRMWARE_SCRIPT_CONTENT\n            self.prepare_package(firmware)\n\n        def prepare_package_additional_img(self, content: bytes):\n            with open(self.ORIGINAL_IMG_FILE, 'wb') as f:\n                f.write(content)\n            self.prepare_package(content)\n\n    class TestWithHttpServer(FirmwareUpdate.TestWithHttpServerMixin, Test):\n        def provide_response_app_img(self, use_real_app=False):\n            super().provide_response(use_real_app)\n\n        def provide_response_additional_img(self, content: bytes, overwrite_original_img=True):\n            if overwrite_original_img:\n                with open(self.ORIGINAL_IMG_FILE, 'wb') as f:\n                    f.write(content)\n            super().provide_response(other_content=content)\n\n    class TestWithTlsServer(FirmwareUpdate.TestWithTlsServerMixin, Test):\n        def setUp(self, pass_cert_to_demo=True, cmd_arg='', **kwargs):\n            super().setUp(pass_cert_to_demo, cmd_arg='--afu-cert-file',\n                          **kwargs)\n\n    class TestWithHttpsServer(TestWithTlsServer,\n                              FirmwareUpdate.TestWithHttpsServerMixin,\n                              TestWithHttpServer):\n        pass\n\n    class TestWithCoapServer(FirmwareUpdate.TestWithCoapServerMixin, Test):\n        pass\n\n    class TestWithCoapsServer(FirmwareUpdate.TestWithCoapsServerMixin, Test):\n        def setUp(self, extra_cmdline_args=None, *args, **kwargs):\n            extra_cmdline_args = (extra_cmdline_args or []) + ['--afu-psk-identity',\n                                                               str(binascii.hexlify(\n                                                                   self.FW_PSK_IDENTITY), 'ascii'),\n                                                               '--afu-psk-key',\n                                                               str(binascii.hexlify(\n                                                                   self.FW_PSK_KEY), 'ascii')]\n            super().setUp(*args, extra_cmdline_args=extra_cmdline_args, **kwargs)\n\n    class TestWithPartialDownload(Test):\n        GARBAGE_SIZE = 8000\n\n        def wait_for_half_download(self):\n            # roughly twice the time expected as per SlowServer\n            deadline = time.time() + self.GARBAGE_SIZE / 500\n            fsize = 0\n            while time.time() < deadline:\n                time.sleep(0.5)\n                fsize = os.stat(self.fw_file_name).st_size\n                if fsize * 2 > self.GARBAGE_SIZE:\n                    break\n            if fsize * 2 <= self.GARBAGE_SIZE:\n                self.fail('firmware image not downloaded fast enough')\n            elif fsize > self.GARBAGE_SIZE:\n                self.fail('firmware image downloaded too quickly')\n\n        def setUp(self, *args, **kwargs):\n            super().setUp(garbage=self.GARBAGE_SIZE, *args, **kwargs)\n\n            import tempfile\n\n            with tempfile.NamedTemporaryFile(delete=False) as f:\n                self.fw_file_name = f.name\n            self.communicate('set-afu-package-path %s' %\n                             (os.path.abspath(self.fw_file_name)))\n\n    class TestWithPartialDownloadAndRestart(\n        FirmwareUpdate.DemoArgsExtractorMixin, TestWithPartialDownload):\n        pass\n\n    class TestWithPartialCoapDownloadAndRestart(\n        TestWithPartialDownloadAndRestart,\n        TestWithCoapServer):\n        def setUp(self):\n            class SlowServer(coap.Server):\n                def send(self, *args, **kwargs):\n                    time.sleep(0.5)\n                    result = super().send(*args, **kwargs)\n                    self.reset()  # allow requests from other ports\n                    return result\n\n            super().setUp(coap_server=SlowServer())\n\n            with self.file_server as file_server:\n                file_server.set_resource('/firmware',\n                                         make_firmware_package(\n                                             self.FIRMWARE_SCRIPT_CONTENT,\n                                             **self.FW_PKG_OPTS))\n                self.fw_uri = file_server.get_resource_uri('/firmware')\n\n    class TestWithPartialCoapsDownloadAndRestart(\n        TestWithPartialDownloadAndRestart,\n        TestWithCoapsServer):\n        def setUp(self):\n            class SlowServer(coap.DtlsServer):\n                def send(self, *args, **kwargs):\n                    time.sleep(0.5)\n                    return super().send(*args, **kwargs)\n\n            super().setUp(coap_server_class=SlowServer)\n\n            with self.file_server as file_server:\n                file_server.set_resource('/firmware',\n                                         make_firmware_package(\n                                             self.FIRMWARE_SCRIPT_CONTENT,\n                                             **self.FW_PKG_OPTS))\n                self.fw_uri = file_server.get_resource_uri('/firmware')\n\n    class TestWithPartialHttpDownloadAndRestart(\n        FirmwareUpdate.TestWithPartialHttpDownloadAndRestartMixin,\n        TestWithPartialDownloadAndRestart,\n        TestWithHttpServer):\n        pass\n\n    class BlockTest(Test, Block.Test):\n        def block_init_file(self):\n            import tempfile\n\n            with tempfile.NamedTemporaryFile(delete=False) as f:\n                fw_file_name = f.name\n            self.communicate(\n                'set-afu-package-path %s' % (os.path.abspath(fw_file_name)))\n            return fw_file_name\n\n        def block_send(self, data, splitter, **make_firmware_package_args):\n            fw_file_name = self.block_init_file()\n\n            make_firmware_package_args.update(self.FW_PKG_OPTS)\n            chunks = list(splitter(\n                make_firmware_package(data, **make_firmware_package_args)))\n\n            for request in packets_from_chunks(chunks):\n                self.serv.send(request)\n                response = self.serv.recv()\n                self.assertIsSuccessResponse(response, request)\n\n            with open(fw_file_name, 'rb') as fw_file:\n                self.assertEqual(fw_file.read(), data)\n\n            self.files_to_cleanup.append(fw_file_name)\n\n        def tearDown(self):\n            for file in self.files_to_cleanup:\n                try:\n                    os.unlink(file)\n                except FileNotFoundError:\n                    pass\n\n            # now reset the state machine\n            self.write_resource(self.serv, OID.AdvancedFirmwareUpdate, 0,\n                                RID.AdvancedFirmwareUpdate.Package, b'\\0',\n                                format=coap.ContentFormat.APPLICATION_OCTET_STREAM)\n            super(Block.Test, self).tearDown()\n\n\nclass AdvancedFirmwareUpdatePackageTest(AdvancedFirmwareUpdate.Test):\n    def setUp(self):\n        super().setUp()\n        self.set_check_marker(True)\n        self.set_auto_deregister(False)\n        self.set_reset_machine(False)\n\n    def runTest(self):\n        # Write /33629/0/0 (Firmware): script content\n        req = Lwm2mWrite(ResPath.AdvancedFirmwareUpdate[Instances.APP].Package,\n                         make_firmware_package(self.FIRMWARE_SCRIPT_CONTENT,\n                                               **self.FW_PKG_OPTS),\n                         format=coap.ContentFormat.APPLICATION_OCTET_STREAM)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n\n        # Execute /33629/0/2 (Update)\n        req = Lwm2mExecute(ResPath.AdvancedFirmwareUpdate[Instances.APP].Update)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n\n        # Demo delays its reset while APP update. Wait for it and terminate if reset not occurred.\n        self._terminate_demo()\n\n\nclass AdvancedFirmwareUpdateUriTest(AdvancedFirmwareUpdate.TestWithHttpServer):\n\n    def setUp(self, *args, **kwargs):\n        super().setUp(*args, **kwargs)\n        self.set_check_marker(True)\n        self.set_auto_deregister(False)\n        self.set_reset_machine(False)\n\n    def runTest(self):\n        self.provide_response_app_img()\n        self.write_firmware_and_wait_for_download(Instances.APP,\n                                                  self.get_firmware_uri())\n\n        # Execute /33629/0/2 (Update)\n        req = Lwm2mExecute(ResPath.AdvancedFirmwareUpdate[Instances.APP].Update)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n\n        # Demo delays its reset while APP update. Wait for it and terminate if reset not occurred.\n        self._terminate_demo()\n\n\nclass AdvancedFirmwareUpdateUriAutoSuspend(AdvancedFirmwareUpdateUriTest):\n    def setUp(self):\n        super().setUp(extra_cmdline_args=['--fw-auto-suspend'])\n\n\nclass AdvancedFirmwareUpdateUriManualSuspend(AdvancedFirmwareUpdate.TestWithHttpServer):\n    def runTest(self):\n        self.provide_response()\n\n        requests = list(self.requests)\n        firmware_uri = self.get_firmware_uri()\n\n        self.communicate('afu-suspend')\n\n        # Write /5/0/1 (Firmware URI)\n        req = Lwm2mWrite(ResPath.AdvancedFirmwareUpdate[Instances.APP].PackageURI, firmware_uri)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n\n        # wait until client enters the DOWNLOADING state\n        deadline = time.time() + 20\n        while time.time() < deadline:\n            time.sleep(0.5)\n            if self.read_state(Instances.APP) == UpdateState.DOWNLOADING:\n                break\n        else:\n            self.fail('firmware still not in DOWNLOADING state')\n\n        time.sleep(5)\n        self.assertEqual(requests, self.requests)\n\n        # resume the download\n        self.communicate('afu-reconnect')\n\n        # wait until client downloads the firmware\n        deadline = time.time() + 20\n        while time.time() < deadline:\n            time.sleep(0.5)\n            if self.read_state(Instances.APP) == UpdateState.DOWNLOADED:\n                break\n        else:\n            self.fail('firmware still not downloaded')\n\n        self.assertEqual(requests + ['/firmware'], self.requests)\n\n\nclass AdvancedFirmwareUpdateStateChangeTest(\n    AdvancedFirmwareUpdate.TestWithHttpServer):\n    def setUp(self):\n        super().setUp()\n        self.set_check_marker(True)\n        self.set_auto_deregister(False)\n        self.set_reset_machine(False)\n\n    def runTest(self):\n        # disable minimum notification period\n        write_attrs_req = Lwm2mWriteAttributes(\n            ResPath.AdvancedFirmwareUpdate[Instances.APP].State,\n            query=['pmin=0'])\n        self.serv.send(write_attrs_req)\n        self.assertMsgEqual(Lwm2mChanged.matching(\n            write_attrs_req)(), self.serv.recv())\n\n        # initial state should be 0\n        observe_req = Lwm2mObserve(\n            ResPath.AdvancedFirmwareUpdate[Instances.APP].State)\n        self.serv.send(observe_req)\n        self.assertMsgEqual(Lwm2mContent.matching(\n            observe_req)(content=b'0'), self.serv.recv())\n\n        # Write /33629/0/1 (Firmware URI)\n        req = Lwm2mWrite(\n            ResPath.AdvancedFirmwareUpdate[Instances.APP].PackageURI,\n            self.get_firmware_uri())\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n\n        # notification should be sent before downloading\n        self.assertMsgEqual(Lwm2mNotify(observe_req.token, b'1'),\n                            self.serv.recv())\n\n        self.provide_response_app_img()\n\n        # ... and after it finishes\n        self.assertMsgEqual(Lwm2mNotify(observe_req.token, b'2'),\n                            self.serv.recv())\n\n        # Execute /33629/0/2 (Update)\n        req = Lwm2mExecute(ResPath.AdvancedFirmwareUpdate[Instances.APP].Update)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n\n        # ... and when update starts\n        self.assertMsgEqual(Lwm2mNotify(observe_req.token, b'3'),\n                            self.serv.recv())\n\n        # there should be exactly one request\n        self.assertEqual(['/firmware'], self.requests)\n\n        # Demo delays its reset while APP update. Wait for it and terminate if reset not occurred.\n        self._terminate_demo()\n\n\nclass AdvancedFirmwareUpdateSendStateChangeTest(\n    AdvancedFirmwareUpdate.TestWithHttpServer):\n    def setUp(self):\n        super().setUp(minimum_version='1.1', maximum_version='1.1',\n                      extra_cmdline_args=['--afu-use-send'])\n        self.set_reset_machine(False)\n        self.set_expect_send_after_state_machine_reset(True)\n\n    def runTest(self):\n        self.assertEqual(self.read_state(Instances.APP), UpdateState.IDLE)\n\n        # Write /33629/0/1 (Firmware URI)\n        req = Lwm2mWrite(\n            ResPath.AdvancedFirmwareUpdate[Instances.APP].PackageURI,\n            self.get_firmware_uri())\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.serv.recv())\n\n        pkt = self.serv.recv()\n        self.assertMsgEqual(Lwm2mSend(), pkt)\n        CBOR.parse(pkt.content).verify_values(test=self,\n                                              expected_value_map={\n                                                  ResPath.AdvancedFirmwareUpdate[\n                                                      Instances.APP].UpdateResult: UpdateResult.INITIAL,\n                                                  ResPath.AdvancedFirmwareUpdate[\n                                                      Instances.APP].State: UpdateState.DOWNLOADING\n                                              })\n        self.serv.send(Lwm2mChanged.matching(pkt)())\n\n        self.provide_response_app_img(use_real_app=True)\n\n        pkt = self.serv.recv()\n        self.assertMsgEqual(Lwm2mSend(), pkt)\n        CBOR.parse(pkt.content).verify_values(test=self,\n                                              expected_value_map={\n                                                  ResPath.AdvancedFirmwareUpdate[\n                                                      Instances.APP].UpdateResult: UpdateResult.INITIAL,\n                                                  ResPath.AdvancedFirmwareUpdate[\n                                                      Instances.APP].State: UpdateState.DOWNLOADED\n                                              })\n        self.serv.send(Lwm2mChanged.matching(pkt)())\n\n        # Execute /33629/0/2 (Update)\n        req = Lwm2mExecute(ResPath.AdvancedFirmwareUpdate[Instances.APP].Update)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n\n        pkt = self.serv.recv()\n        self.assertMsgEqual(Lwm2mSend(), pkt)\n        CBOR.parse(pkt.content).verify_values(test=self,\n                                              expected_value_map={\n                                                  ResPath.AdvancedFirmwareUpdate[\n                                                      Instances.APP].UpdateResult: UpdateResult.INITIAL,\n                                                  ResPath.AdvancedFirmwareUpdate[\n                                                      Instances.APP].State: UpdateState.UPDATING\n                                              })\n        self.serv.send(Lwm2mChanged.matching(pkt)())\n\n        # there should be exactly one request\n        self.assertEqual(['/firmware'], self.requests)\n\n        self.serv.reset()\n        self.assertDemoRegisters(version='1.1')\n\n        pkt = self.serv.recv()\n        self.assertMsgEqual(Lwm2mSend(), pkt)\n        parsed_cbor = CBOR.parse(pkt.content)\n        parsed_cbor.verify_values(test=self,\n                                  expected_value_map={\n                                      ResPath.AdvancedFirmwareUpdate[\n                                          Instances.APP].UpdateResult: UpdateResult.SUCCESS,\n                                      ResPath.AdvancedFirmwareUpdate[\n                                          Instances.APP].State: UpdateState.IDLE\n                                  })\n        # Check if Send contains firmware and software version\n        self.assertEqual(parsed_cbor[3].get(SenmlLabel.NAME), '/3/0/3')\n        self.assertEqual(parsed_cbor[4].get(SenmlLabel.NAME), '/3/0/19')\n        self.serv.send(Lwm2mChanged.matching(pkt)())\n\n\nclass AdvancedFirmwareUpdateBadBase64(AdvancedFirmwareUpdate.Test):\n    def runTest(self):\n        # Write /33629/0/0 (Firmware): some random text to see how it makes the world burn\n        # (as text context does not implement some_bytes handler).\n        data = bytes(b'\\x01' * 16)\n        req = Lwm2mWrite(ResPath.AdvancedFirmwareUpdate[Instances.APP].Package,\n                         data,\n                         format=coap.ContentFormat.TEXT_PLAIN)\n        self.serv.send(req)\n        self.assertMsgEqual(\n            Lwm2mErrorResponse.matching(req)(coap.Code.RES_BAD_REQUEST),\n            self.serv.recv())\n\n\nclass AdvancedFirmwareUpdateGoodBase64(AdvancedFirmwareUpdate.Test):\n    def runTest(self):\n        import base64\n        data = base64.encodebytes(bytes(b'\\x01' * 16)).replace(b'\\n', b'')\n        req = Lwm2mWrite(ResPath.AdvancedFirmwareUpdate[Instances.APP].Package,\n                         data,\n                         format=coap.ContentFormat.TEXT_PLAIN)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n\n\nclass AdvancedFirmwareUpdateNullPkg(AdvancedFirmwareUpdate.TestWithHttpServer):\n    def runTest(self):\n        self.provide_response_app_img()\n        self.write_firmware_and_wait_for_download(Instances.APP,\n                                                  self.get_firmware_uri())\n\n        req = Lwm2mWrite(ResPath.AdvancedFirmwareUpdate[Instances.APP].Package,\n                         b'\\0',\n                         format=coap.ContentFormat.APPLICATION_OCTET_STREAM)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.serv.recv())\n\n        self.assertEqual(UpdateState.IDLE, self.read_state(Instances.APP))\n        self.assertEqual(UpdateResult.INITIAL,\n                         self.read_update_result(Instances.APP))\n\n\nclass AdvancedFirmwareUpdateEmptyPkgUri(\n    AdvancedFirmwareUpdate.TestWithHttpServer):\n    def runTest(self):\n        self.provide_response_app_img()\n        self.write_firmware_and_wait_for_download(Instances.APP,\n                                                  self.get_firmware_uri())\n\n        req = Lwm2mWrite(\n            ResPath.AdvancedFirmwareUpdate[Instances.APP].PackageURI, '')\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.serv.recv())\n\n        self.assertEqual(UpdateState.IDLE, self.read_state(Instances.APP))\n        self.assertEqual(UpdateResult.INITIAL,\n                         self.read_update_result(Instances.APP))\n\n\nclass AdvancedFirmwareUpdateInvalidUri(AdvancedFirmwareUpdate.Test):\n    def runTest(self):\n        # observe Result\n        observe_req = Lwm2mObserve(\n            ResPath.AdvancedFirmwareUpdate[Instances.APP].UpdateResult)\n        self.serv.send(observe_req)\n        self.assertMsgEqual(Lwm2mContent.matching(\n            observe_req)(content=b'0'), self.serv.recv())\n\n        req = Lwm2mWrite(\n            ResPath.AdvancedFirmwareUpdate[Instances.APP].PackageURI,\n            b'http://invalidfirmware.exe')\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.serv.recv())\n\n        while True:\n            notify = self.serv.recv()\n            self.assertMsgEqual(Lwm2mNotify(observe_req.token), notify)\n            if int(notify.content) != UpdateResult.INITIAL:\n                break\n        self.assertEqual(UpdateResult.INVALID_URI, int(notify.content))\n        self.serv.send(Lwm2mReset(msg_id=notify.msg_id))\n        self.assertEqual(UpdateState.IDLE, self.read_state(Instances.APP))\n\n\nclass AdvancedFirmwareUpdateUnsupportedUri(AdvancedFirmwareUpdate.Test):\n    def runTest(self):\n        req = Lwm2mWrite(\n            ResPath.AdvancedFirmwareUpdate[Instances.APP].PackageURI,\n            b'unsupported://uri.exe')\n        self.serv.send(req)\n        self.assertMsgEqual(\n            Lwm2mErrorResponse.matching(req)(coap.Code.RES_BAD_REQUEST),\n            self.serv.recv())\n        # This does not even change state or anything, because according to the LwM2M spec\n        # Server can't feed us with unsupported URI type\n        self.assertEqual(UpdateState.IDLE, self.read_state(Instances.APP))\n        self.assertEqual(UpdateResult.UNSUPPORTED_PROTOCOL,\n                         self.read_update_result(Instances.APP))\n\n\nclass AdvancedFirmwareUpdateOfflineUriTest(\n    AdvancedFirmwareUpdate.TestWithHttpServer):\n    def runTest(self):\n        self.communicate('enter-offline tcp')\n\n        self.assertEqual(UpdateState.IDLE, self.read_state(Instances.APP))\n        self.assertEqual(UpdateResult.INITIAL,\n                         self.read_update_result(Instances.APP))\n\n        req = Lwm2mWrite(\n            ResPath.AdvancedFirmwareUpdate[Instances.APP].PackageURI,\n            self.get_firmware_uri())\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.serv.recv())\n\n        self.assertEqual(UpdateState.IDLE, self.read_state(Instances.APP))\n        self.assertEqual(UpdateResult.CONNECTION_LOST,\n                         self.read_update_result(Instances.APP))\n\n\nclass AdvancedFirmwareUpdateReplacingPkgUri(\n    AdvancedFirmwareUpdate.TestWithHttpServer):\n    def runTest(self):\n        self.provide_response_app_img()\n        self.write_firmware_and_wait_for_download(Instances.APP,\n                                                  self.get_firmware_uri())\n\n        # This isn't specified anywhere as a possible transition, therefore\n        # it is most likely a bad request.\n        req = Lwm2mWrite(\n            ResPath.AdvancedFirmwareUpdate[Instances.APP].PackageURI,\n            'http://something')\n        self.serv.send(req)\n        self.assertMsgEqual(\n            Lwm2mErrorResponse.matching(req)(coap.Code.RES_BAD_REQUEST),\n            self.serv.recv())\n\n        self.assertEqual(UpdateState.DOWNLOADED, self.read_state(Instances.APP))\n        self.assertEqual(UpdateResult.INITIAL,\n                         self.read_update_result(Instances.APP))\n\n\nclass AdvancedFirmwareUpdateReplacingPkg(\n    AdvancedFirmwareUpdate.TestWithHttpServer):\n    def runTest(self):\n        self.provide_response_app_img()\n        self.write_firmware_and_wait_for_download(Instances.APP,\n                                                  self.get_firmware_uri())\n\n        # This isn't specified anywhere as a possible transition, therefore\n        # it is most likely a bad request.\n        req = Lwm2mWrite(ResPath.AdvancedFirmwareUpdate[Instances.APP].Package,\n                         b'trololo',\n                         format=coap.ContentFormat.APPLICATION_OCTET_STREAM)\n        self.serv.send(req)\n        self.assertMsgEqual(\n            Lwm2mErrorResponse.matching(req)(coap.Code.RES_BAD_REQUEST),\n            self.serv.recv())\n\n        self.assertEqual(UpdateState.DOWNLOADED, self.read_state(Instances.APP))\n        self.assertEqual(UpdateResult.INITIAL,\n                         self.read_update_result(Instances.APP))\n\n\n\n\nclass AdvancedFirmwareUpdateHttpsReconnectTest(\n    AdvancedFirmwareUpdate.TestWithPartialDownloadAndRestart,\n    AdvancedFirmwareUpdate.TestWithHttpsServer):\n    RESPONSE_DELAY = 0.5\n    CHUNK_SIZE = 1000\n    ETAGS = True\n\n    def setUp(self):\n        super().setUp()\n        self.set_check_marker(True)\n        self.set_auto_deregister(False)\n        self.set_reset_machine(False)\n\n    def runTest(self):\n        self.provide_response_app_img()\n\n        # Write /33629/0/1 (Firmware URI)\n        req = Lwm2mWrite(\n            ResPath.AdvancedFirmwareUpdate[Instances.APP].PackageURI,\n            self.get_firmware_uri())\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.serv.recv())\n\n        self.wait_for_half_download()\n\n        self.assertEqual(len(self.requests), 1)\n\n        self.communicate('afu-reconnect')\n        self.provide_response_app_img()\n\n        # wait until client downloads the firmware\n        deadline = time.time() + 20\n        while time.time() < deadline:\n            time.sleep(0.5)\n\n            if self.read_state(Instances.APP) == UpdateState.DOWNLOADED:\n                break\n        else:\n            raise\n\n        # Execute /33629/0/2 (Update)\n        req = Lwm2mExecute(ResPath.AdvancedFirmwareUpdate[Instances.APP].Update)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n\n        # Demo delays its reset while APP update. Wait for it and terminate if reset not occurred.\n        self._terminate_demo()\n\n\nclass AdvancedFirmwareUpdateHttpsCancelPackageTest(\n    AdvancedFirmwareUpdate.TestWithPartialDownload,\n    AdvancedFirmwareUpdate.TestWithHttpServer):\n    RESPONSE_DELAY = 0.5\n    CHUNK_SIZE = 1000\n    ETAGS = True\n\n    def runTest(self):\n        self.provide_response_app_img()\n\n        # Write /33629/0/1 (Firmware URI)\n        req = Lwm2mWrite(\n            ResPath.AdvancedFirmwareUpdate[Instances.APP].PackageURI,\n            self.get_firmware_uri())\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.serv.recv())\n\n        self.wait_for_half_download()\n\n        self.assertEqual(self.get_socket_count(), 2)\n\n        req = Lwm2mWrite(ResPath.AdvancedFirmwareUpdate[Instances.APP].Package,\n                         b'\\0',\n                         format=coap.ContentFormat.APPLICATION_OCTET_STREAM)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.serv.recv())\n\n        self.wait_until_socket_count(expected=1, timeout_s=5)\n\n        self.assertEqual(UpdateState.IDLE, self.read_state(Instances.APP))\n        self.assertEqual(UpdateResult.INITIAL,\n                         self.read_update_result(Instances.APP))\n\n\nclass AdvancedFirmwareUpdateHttpsCancelPackageUriTest(\n    AdvancedFirmwareUpdate.TestWithPartialDownload,\n    AdvancedFirmwareUpdate.TestWithHttpServer):\n    RESPONSE_DELAY = 0.5\n    CHUNK_SIZE = 1000\n    ETAGS = True\n\n    def runTest(self):\n        self.provide_response_app_img()\n\n        # Write /33629/0/1 (Firmware URI)\n        req = Lwm2mWrite(\n            ResPath.AdvancedFirmwareUpdate[Instances.APP].PackageURI,\n            self.get_firmware_uri())\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.serv.recv())\n\n        self.wait_for_half_download()\n\n        self.assertEqual(self.get_socket_count(), 2)\n\n        req = Lwm2mWrite(\n            ResPath.AdvancedFirmwareUpdate[Instances.APP].PackageURI, b'')\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.serv.recv())\n\n        self.wait_until_socket_count(expected=1, timeout_s=5)\n\n        self.assertEqual(UpdateState.IDLE, self.read_state(Instances.APP))\n        self.assertEqual(UpdateResult.INITIAL,\n                         self.read_update_result(Instances.APP))\n\n\nclass AdvancedFirmwareUpdateCoapCancelPackageUriTest(\n    AdvancedFirmwareUpdate.TestWithPartialDownload,\n    AdvancedFirmwareUpdate.TestWithCoapServer):\n    def runTest(self):\n        with self.file_server as file_server:\n            file_server.set_resource('/firmware',\n                                     make_firmware_package(\n                                         self.FIRMWARE_SCRIPT_CONTENT))\n            fw_uri = file_server.get_resource_uri('/firmware')\n\n            # Write /33629/0/1 (Firmware URI)\n            req = Lwm2mWrite(\n                ResPath.AdvancedFirmwareUpdate[Instances.APP].PackageURI,\n                fw_uri)\n            self.serv.send(req)\n            self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                                self.serv.recv())\n\n            # Handle one GET\n            file_server.handle_request()\n\n        self.assertEqual(self.get_socket_count(), 2)\n\n        # Cancel download\n        req = Lwm2mWrite(\n            ResPath.AdvancedFirmwareUpdate[Instances.APP].PackageURI, b'')\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.serv.recv())\n\n        self.wait_until_socket_count(expected=1, timeout_s=5)\n\n        self.assertEqual(UpdateState.IDLE, self.read_state(Instances.APP))\n        self.assertEqual(UpdateResult.INITIAL,\n                         self.read_update_result(Instances.APP))\n\n\nclass AdvancedFirmwareUpdateHttpsOfflineTest(\n    AdvancedFirmwareUpdate.TestWithPartialDownloadAndRestart,\n    AdvancedFirmwareUpdate.TestWithHttpServer):\n    RESPONSE_DELAY = 0.5\n    CHUNK_SIZE = 1000\n    ETAGS = True\n\n    def setUp(self):\n        super().setUp()\n        self.set_check_marker(True)\n        self.set_auto_deregister(False)\n        self.set_reset_machine(False)\n\n    def runTest(self):\n        self.provide_response_app_img()\n\n        # Write /33629/0/1 (Firmware URI)\n        req = Lwm2mWrite(\n            ResPath.AdvancedFirmwareUpdate[Instances.APP].PackageURI,\n            self.get_firmware_uri())\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.serv.recv())\n\n        self.wait_for_half_download()\n\n        self.assertEqual(self.get_socket_count(), 2)\n        self.communicate('enter-offline tcp')\n        self.wait_until_socket_count(expected=1, timeout_s=5)\n        self.provide_response_app_img()\n        self.communicate('exit-offline tcp')\n\n        deadline = time.time() + 20\n        while time.time() < deadline:\n            time.sleep(0.5)\n\n            if self.read_state(Instances.APP) == UpdateState.DOWNLOADED:\n                break\n        else:\n            raise\n\n        # Execute /33629/0/2 (Update)\n        req = Lwm2mExecute(ResPath.AdvancedFirmwareUpdate[Instances.APP].Update)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n\n        # Demo delays its reset while APP update. Wait for it and terminate if reset not occurred.\n        self._terminate_demo()\n\n\nclass AdvancedFirmwareUpdateHttpsTest(\n    AdvancedFirmwareUpdate.TestWithHttpsServer):\n    def setUp(self):\n        super().setUp()\n        self.set_check_marker(True)\n        self.set_auto_deregister(False)\n        self.set_reset_machine(False)\n\n    def runTest(self):\n        self.provide_response_app_img()\n        self.write_firmware_and_wait_for_download(Instances.APP,\n                                                  self.get_firmware_uri(),\n                                                  download_timeout_s=20)\n\n        # Execute /33629/0/2 (Update)\n        req = Lwm2mExecute(ResPath.AdvancedFirmwareUpdate[Instances.APP].Update)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n\n        # Demo delays its reset while APP update. Wait for it and terminate if reset not occurred.\n        self._terminate_demo()\n\n\nclass AdvancedFirmwareUpdateUnconfiguredHttpsTest(\n    AdvancedFirmwareUpdate.TestWithHttpsServer):\n    def setUp(self):\n        super().setUp(pass_cert_to_demo=False)\n\n    def runTest(self):\n        # disable minimum notification period\n        write_attrs_req = Lwm2mWriteAttributes(\n            ResPath.AdvancedFirmwareUpdate[Instances.APP].UpdateResult,\n            query=['pmin=0'])\n        self.serv.send(write_attrs_req)\n        self.assertMsgEqual(Lwm2mChanged.matching(\n            write_attrs_req)(), self.serv.recv())\n\n        # initial result should be 0\n        observe_req = Lwm2mObserve(\n            ResPath.AdvancedFirmwareUpdate[Instances.APP].UpdateResult)\n        self.serv.send(observe_req)\n        self.assertMsgEqual(Lwm2mContent.matching(\n            observe_req)(content=b'0'), self.serv.recv())\n\n        # Write /33629/0/1 (Firmware URI)\n        req = Lwm2mWrite(\n            ResPath.AdvancedFirmwareUpdate[Instances.APP].PackageURI,\n            self.get_firmware_uri())\n\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n\n        # even before reaching the server, we should get an error\n        notify_msg = self.serv.recv()\n        # no security information => \"Unsupported protocol\"\n        self.assertMsgEqual(Lwm2mNotify(observe_req.token,\n                                        str(UpdateResult.UNSUPPORTED_PROTOCOL).encode()),\n                            notify_msg)\n        self.serv.send(Lwm2mReset(msg_id=notify_msg.msg_id))\n        self.assertEqual(0, self.read_state(Instances.APP))\n\n\nclass AdvancedFirmwareUpdateUnconfiguredHttpsWithFallbackAttemptTest(\n    AdvancedFirmwareUpdate.TestWithHttpsServer):\n    def setUp(self):\n        super().setUp(pass_cert_to_demo=False,\n                      psk_identity=b'test-identity', psk_key=b'test-key')\n\n    def runTest(self):\n        # disable minimum notification period\n        write_attrs_req = Lwm2mWriteAttributes(\n            ResPath.AdvancedFirmwareUpdate[Instances.APP].UpdateResult,\n            query=['pmin=0'])\n        self.serv.send(write_attrs_req)\n        self.assertMsgEqual(Lwm2mChanged.matching(\n            write_attrs_req)(), self.serv.recv())\n\n        # initial result should be 0\n        observe_req = Lwm2mObserve(\n            ResPath.AdvancedFirmwareUpdate[Instances.APP].UpdateResult)\n        self.serv.send(observe_req)\n        self.assertMsgEqual(Lwm2mContent.matching(\n            observe_req)(content=b'0'), self.serv.recv())\n\n        # Write /33629/0/1 (Firmware URI)\n        req = Lwm2mWrite(\n            ResPath.AdvancedFirmwareUpdate[Instances.APP].PackageURI,\n            self.get_firmware_uri())\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n\n        # even before reaching the server, we should get an error\n        notify_msg = self.serv.recv()\n        # no security information => client will attempt PSK from data model\n        # and fail handshake => \"Connection lost\"\n        self.assertMsgEqual(Lwm2mNotify(observe_req.token,\n                                        str(UpdateResult.CONNECTION_LOST).encode()),\n                            notify_msg)\n        self.serv.send(Lwm2mReset(msg_id=notify_msg.msg_id))\n        self.assertEqual(0, self.read_state(Instances.APP))\n\n\nclass AdvancedFirmwareUpdateInvalidHttpsTest(\n    AdvancedFirmwareUpdate.TestWithHttpsServer):\n    def setUp(self):\n        super().setUp(cn='invalid_cn', alt_ip=None)\n\n    def runTest(self):\n        # disable minimum notification period\n        write_attrs_req = Lwm2mWriteAttributes(\n            ResPath.AdvancedFirmwareUpdate[Instances.APP].UpdateResult,\n            query=['pmin=0'])\n        self.serv.send(write_attrs_req)\n        self.assertMsgEqual(Lwm2mChanged.matching(\n            write_attrs_req)(), self.serv.recv())\n\n        # initial result should be 0\n        observe_req = Lwm2mObserve(\n            ResPath.AdvancedFirmwareUpdate[Instances.APP].UpdateResult)\n        self.serv.send(observe_req)\n        self.assertMsgEqual(Lwm2mContent.matching(\n            observe_req)(content=b'0'), self.serv.recv())\n\n        # Write /33629/0/1 (Firmware URI)\n        req = Lwm2mWrite(\n            ResPath.AdvancedFirmwareUpdate[Instances.APP].PackageURI,\n            self.get_firmware_uri())\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n\n        # even before reaching the server, we should get an error\n        notify_msg = self.serv.recv()\n        # handshake failure => \"Connection lost\"\n        self.assertMsgEqual(Lwm2mNotify(observe_req.token,\n                                        str(UpdateResult.CONNECTION_LOST).encode()),\n                            notify_msg)\n        self.serv.send(Lwm2mReset(msg_id=notify_msg.msg_id))\n        self.assertEqual(0, self.read_state(Instances.APP))\n\n\nclass AdvancedFirmwareUpdateResetInIdleState(AdvancedFirmwareUpdate.Test):\n    def runTest(self):\n        self.assertEqual(UpdateState.IDLE, self.read_state(Instances.APP))\n\n        req = Lwm2mWrite(\n            ResPath.AdvancedFirmwareUpdate[Instances.APP].PackageURI, b'')\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.serv.recv())\n        self.assertEqual(UpdateState.IDLE, self.read_state(Instances.APP))\n        self.assertEqual(UpdateResult.INITIAL,\n                         self.read_update_result(Instances.APP))\n\n        req = Lwm2mWrite(ResPath.AdvancedFirmwareUpdate[Instances.APP].Package,\n                         b'\\0',\n                         format=coap.ContentFormat.APPLICATION_OCTET_STREAM)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.serv.recv())\n        self.assertEqual(UpdateState.IDLE, self.read_state(Instances.APP))\n        self.assertEqual(UpdateResult.INITIAL,\n                         self.read_update_result(Instances.APP))\n\n\nclass AdvancedFirmwareUpdateCoapUri(AdvancedFirmwareUpdate.TestWithCoapServer):\n    def tearDown(self):\n        super().tearDown()\n\n        # there should be exactly one request\n        with self.file_server as file_server:\n            self.assertEqual(1, len(file_server.requests))\n            self.assertMsgEqual(CoapGet('/firmware'),\n                                file_server.requests[0])\n\n    def runTest(self):\n        with self.file_server as file_server:\n            file_server.set_resource('/firmware',\n                                     make_firmware_package(\n                                         self.FIRMWARE_SCRIPT_CONTENT,\n                                         **self.FW_PKG_OPTS))\n            fw_uri = file_server.get_resource_uri('/firmware')\n        self.write_firmware_and_wait_for_download(Instances.APP, fw_uri)\n\n\nclass AdvancedFirmwareUpdateCoapsUri(AdvancedFirmwareUpdate.TestWithCoapsServer,\n                                     AdvancedFirmwareUpdateCoapUri):\n    pass\n\n\nclass AdvancedFirmwareUpdateCoapsUriAutoSuspend(AdvancedFirmwareUpdateCoapsUri):\n    def setUp(self):\n        super().setUp(extra_cmdline_args=['--afu-auto-suspend'])\n\n\nclass AdvancedFirmwareUpdateCoapsUriManualSuspend(AdvancedFirmwareUpdateCoapsUri):\n    def runTest(self):\n        with self.file_server as file_server:\n            file_server.set_resource('/firmware',\n                                     make_firmware_package(self.FIRMWARE_SCRIPT_CONTENT,\n                                                           **self.FW_PKG_OPTS))\n            fw_uri = file_server.get_resource_uri('/firmware')\n\n        self.communicate('afu-suspend')\n\n        # Write /33629/0/1 (Firmware URI)\n        req = Lwm2mWrite(ResPath.AdvancedFirmwareUpdate[Instances.APP].PackageURI, fw_uri)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.serv.recv())\n\n        # wait until the state machine enters the DOWNLOADING state\n        deadline = time.time() + 20\n        while time.time() < deadline:\n            time.sleep(0.5)\n            if self.read_state(Instances.APP) == UpdateState.DOWNLOADING:\n                break\n        else:\n            self.fail('firmware still not in DOWNLOADING state')\n\n        time.sleep(5)\n        with self.file_server as file_server:\n            self.assertEqual(0, len(file_server.requests))\n\n        # resume the download\n        self.communicate('afu-reconnect')\n\n        # wait until client downloads the firmware\n        deadline = time.time() + 20\n        while time.time() < deadline:\n            time.sleep(0.5)\n            if self.read_state(Instances.APP) == UpdateState.DOWNLOADED:\n                break\n        else:\n            self.fail('firmware still not downloaded')\n\n\nclass AdvancedFirmwareUpdateCoapsReconnectTest(\n    AdvancedFirmwareUpdate.TestWithPartialCoapsDownloadAndRestart):\n    def runTest(self):\n        # Write /33629/0/1 (Firmware URI)\n        req = Lwm2mWrite(ResPath.AdvancedFirmwareUpdate[Instances.APP].PackageURI, self.fw_uri)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.serv.recv())\n\n        self.wait_for_half_download()\n\n        with self.file_server as file_server:\n            file_server._server.reset()\n            self.communicate('afu-reconnect')\n            self.assertDtlsReconnect(file_server._server, timeout_s=10,\n                                     expected_error=['0x7700', '0x7900'])\n\n        deadline = time.time() + 20\n        while time.time() < deadline:\n            time.sleep(0.5)\n\n            if self.read_state(Instances.APP) == UpdateState.DOWNLOADED:\n                break\n        else:\n            raise\n\n\nclass AdvancedFirmwareUpdateCoapsSuspendDuringOfflineAndReconnectDuringOnlineTest(\n    AdvancedFirmwareUpdate.TestWithPartialCoapsDownloadAndRestart):\n    def runTest(self):\n        # Write /33629/0/1 (Firmware URI)\n        req = Lwm2mWrite(ResPath.AdvancedFirmwareUpdate[Instances.APP].PackageURI, self.fw_uri)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.serv.recv())\n\n        self.wait_for_half_download()\n\n        with self.file_server as file_server:\n            # Flush any buffers\n            while True:\n                try:\n                    file_server._server.recv(timeout_s=1)\n                except socket.timeout:\n                    break\n\n            self.communicate('enter-offline')\n\n            with self.assertRaises(socket.timeout):\n                file_server._server.recv(timeout_s=5)\n\n            self.communicate('afu-suspend')\n\n            with self.assertRaises(socket.timeout):\n                file_server._server.recv(timeout_s=5)\n\n            self.communicate('exit-offline')\n\n            self.assertDemoRegisters()\n\n            with self.assertRaises(socket.timeout):\n                file_server._server.recv(timeout_s=5)\n\n            file_server._server.reset()\n            self.communicate('afu-reconnect')\n            self.assertPktIsDtlsClientHello(\n                file_server._server._raw_udp_socket.recv(65536, socket.MSG_PEEK))\n\n        deadline = time.time() + 20\n        while time.time() < deadline:\n            time.sleep(0.5)\n\n            if self.read_state(Instances.APP) == UpdateState.DOWNLOADED:\n                break\n        else:\n            raise\n\n\nclass AdvancedFirmwareUpdateCoapsSuspendDuringOfflineAndReconnectDuringOfflineTest(\n    AdvancedFirmwareUpdate.TestWithPartialCoapsDownloadAndRestart):\n    def runTest(self):\n        # Write /33629/0/1 (Firmware URI)\n        req = Lwm2mWrite(ResPath.AdvancedFirmwareUpdate[Instances.APP].PackageURI, self.fw_uri)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.serv.recv())\n\n        self.wait_for_half_download()\n\n        with self.file_server as file_server:\n            # Flush any buffers\n            while True:\n                try:\n                    file_server._server.recv(timeout_s=1)\n                except socket.timeout:\n                    break\n\n            self.communicate('enter-offline')\n\n            with self.assertRaises(socket.timeout):\n                file_server._server.recv(timeout_s=5)\n\n            self.communicate('afu-suspend')\n\n            with self.assertRaises(socket.timeout):\n                file_server._server.recv(timeout_s=5)\n\n            self.communicate('afu-reconnect')\n\n            with self.assertRaises(socket.timeout):\n                file_server._server.recv(timeout_s=5)\n\n            file_server._server.reset()\n            self.communicate('exit-offline')\n            self.assertPktIsDtlsClientHello(\n                file_server._server._raw_udp_socket.recv(65536, socket.MSG_PEEK))\n\n        self.assertDemoRegisters()\n\n        deadline = time.time() + 20\n        while time.time() < deadline:\n            time.sleep(0.5)\n\n            if self.read_state(Instances.APP) == UpdateState.DOWNLOADED:\n                break\n        else:\n            raise\n\n\nclass AdvancedFirmwareUpdateCoapsOfflineDuringSuspendAndReconnectDuringOnlineTest(\n    AdvancedFirmwareUpdate.TestWithPartialCoapsDownloadAndRestart):\n    def runTest(self):\n        # Write /33629/0/1 (Firmware URI)\n        req = Lwm2mWrite(ResPath.AdvancedFirmwareUpdate[Instances.APP].PackageURI, self.fw_uri)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.serv.recv())\n\n        self.wait_for_half_download()\n\n        with self.file_server as file_server:\n            # Flush any buffers\n            while True:\n                try:\n                    file_server._server.recv(timeout_s=1)\n                except socket.timeout:\n                    break\n\n            self.communicate('afu-suspend')\n\n            with self.assertRaises(socket.timeout):\n                file_server._server.recv(timeout_s=5)\n\n            self.communicate('enter-offline')\n\n            with self.assertRaises(socket.timeout):\n                file_server._server.recv(timeout_s=5)\n\n            self.communicate('exit-offline')\n\n            self.assertDemoRegisters()\n\n            with self.assertRaises(socket.timeout):\n                file_server._server.recv(timeout_s=5)\n\n            file_server._server.reset()\n            self.communicate('afu-reconnect')\n            self.assertPktIsDtlsClientHello(\n                file_server._server._raw_udp_socket.recv(65536, socket.MSG_PEEK))\n\n        deadline = time.time() + 20\n        while time.time() < deadline:\n            time.sleep(0.5)\n\n            if self.read_state(Instances.APP) == UpdateState.DOWNLOADED:\n                break\n        else:\n            raise\n\n\nclass AdvancedFirmwareUpdateCoapsOfflineDuringSuspendAndReconnectDuringOfflineTest(\n    AdvancedFirmwareUpdate.TestWithPartialCoapsDownloadAndRestart):\n    def runTest(self):\n        # Write /33629/0/1 (Firmware URI)\n        req = Lwm2mWrite(ResPath.AdvancedFirmwareUpdate[Instances.APP].PackageURI, self.fw_uri)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.serv.recv())\n\n        self.wait_for_half_download()\n\n        with self.file_server as file_server:\n            # Flush any buffers\n            while True:\n                try:\n                    file_server._server.recv(timeout_s=1)\n                except socket.timeout:\n                    break\n\n            self.communicate('afu-suspend')\n\n            with self.assertRaises(socket.timeout):\n                file_server._server.recv(timeout_s=5)\n\n            self.communicate('enter-offline')\n\n            with self.assertRaises(socket.timeout):\n                file_server._server.recv(timeout_s=5)\n\n            self.communicate('afu-reconnect')\n\n            with self.assertRaises(socket.timeout):\n                file_server._server.recv(timeout_s=5)\n\n            file_server._server.reset()\n            self.communicate('exit-offline')\n            self.assertPktIsDtlsClientHello(\n                file_server._server._raw_udp_socket.recv(65536, socket.MSG_PEEK))\n\n        self.assertDemoRegisters()\n\n        deadline = time.time() + 20\n        while time.time() < deadline:\n            time.sleep(0.5)\n\n            if self.read_state(Instances.APP) == UpdateState.DOWNLOADED:\n                break\n        else:\n            raise\n\n\nclass AdvancedFirmwareUpdateHttpSuspendDuringOfflineAndReconnectDuringOnlineTest(\n    AdvancedFirmwareUpdate.TestWithPartialHttpDownloadAndRestart):\n    def runTest(self):\n        self.provide_response()\n\n        # Write /33629/0/1 (Firmware URI)\n        req = Lwm2mWrite(ResPath.AdvancedFirmwareUpdate[Instances.APP].PackageURI,\n                         self.get_firmware_uri())\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.serv.recv())\n\n        self.wait_for_half_download()\n\n        self.communicate('enter-offline')\n\n        time.sleep(5)\n        fsize = os.stat(self.fw_file_name).st_size\n        self.assertEqual(len(self.requests), 1)\n        self.provide_response()\n\n        self.communicate('afu-suspend')\n\n        time.sleep(5)\n        self.assertEqual(os.stat(self.fw_file_name).st_size, fsize)\n        self.assertEqual(len(self.requests), 1)\n\n        self.communicate('exit-offline')\n\n        self.assertDemoRegisters()\n\n        time.sleep(5)\n        self.assertEqual(os.stat(self.fw_file_name).st_size, fsize)\n        self.assertEqual(len(self.requests), 1)\n\n        self.communicate('afu-reconnect')\n\n        deadline = time.time() + 20\n        while time.time() < deadline:\n            time.sleep(0.5)\n\n            if self.read_state(Instances.APP) == UpdateState.DOWNLOADED:\n                break\n        else:\n            raise\n\n        self.assertEqual(len(self.requests), 2)\n\n\nclass AdvancedFirmwareUpdateHttpSuspendDuringOfflineAndReconnectDuringOfflineTest(\n    AdvancedFirmwareUpdate.TestWithPartialHttpDownloadAndRestart):\n    def runTest(self):\n        self.provide_response()\n\n        # Write /33629/0/1 (Firmware URI)\n        req = Lwm2mWrite(ResPath.AdvancedFirmwareUpdate[Instances.APP].PackageURI,\n                         self.get_firmware_uri())\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.serv.recv())\n\n        self.wait_for_half_download()\n\n        self.communicate('enter-offline')\n\n        time.sleep(5)\n        fsize = os.stat(self.fw_file_name).st_size\n        self.assertEqual(len(self.requests), 1)\n        self.provide_response()\n\n        self.communicate('afu-suspend')\n\n        time.sleep(5)\n        self.assertEqual(os.stat(self.fw_file_name).st_size, fsize)\n        self.assertEqual(len(self.requests), 1)\n\n        self.communicate('afu-reconnect')\n\n        time.sleep(5)\n        self.assertEqual(os.stat(self.fw_file_name).st_size, fsize)\n        self.assertEqual(len(self.requests), 1)\n\n        self.communicate('exit-offline')\n\n        self.assertDemoRegisters()\n\n        deadline = time.time() + 20\n        while time.time() < deadline:\n            time.sleep(0.5)\n\n            if self.read_state(Instances.APP) == UpdateState.DOWNLOADED:\n                break\n        else:\n            raise\n\n        self.assertEqual(len(self.requests), 2)\n\n\nclass AdvancedFirmwareUpdateHttpOfflineDuringSuspendAndReconnectDuringOnlineTest(\n    AdvancedFirmwareUpdate.TestWithPartialHttpDownloadAndRestart):\n    def runTest(self):\n        self.provide_response()\n\n        # Write /33629/0/1 (Firmware URI)\n        req = Lwm2mWrite(ResPath.AdvancedFirmwareUpdate[Instances.APP].PackageURI,\n                         self.get_firmware_uri())\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.serv.recv())\n\n        self.wait_for_half_download()\n\n        self.communicate('afu-suspend')\n\n        time.sleep(5)\n        fsize = os.stat(self.fw_file_name).st_size\n        self.assertEqual(len(self.requests), 1)\n        self.provide_response()\n\n        self.communicate('enter-offline')\n\n        time.sleep(5)\n        self.assertEqual(os.stat(self.fw_file_name).st_size, fsize)\n        self.assertEqual(len(self.requests), 1)\n\n        self.communicate('exit-offline')\n\n        self.assertDemoRegisters()\n\n        time.sleep(5)\n        self.assertEqual(os.stat(self.fw_file_name).st_size, fsize)\n        self.assertEqual(len(self.requests), 1)\n\n        self.communicate('afu-reconnect')\n\n        deadline = time.time() + 20\n        while time.time() < deadline:\n            time.sleep(0.5)\n\n            if self.read_state(Instances.APP) == UpdateState.DOWNLOADED:\n                break\n        else:\n            raise\n\n        self.assertEqual(len(self.requests), 2)\n\n\nclass AdvancedFirmwareUpdateHttpOfflineDuringSuspendAndReconnectDuringOfflineTest(\n    AdvancedFirmwareUpdate.TestWithPartialHttpDownloadAndRestart):\n    def runTest(self):\n        self.provide_response()\n\n        # Write /33629/0/1 (Firmware URI)\n        req = Lwm2mWrite(ResPath.AdvancedFirmwareUpdate[Instances.APP].PackageURI,\n                         self.get_firmware_uri())\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.serv.recv())\n\n        self.wait_for_half_download()\n\n        self.communicate('afu-suspend')\n\n        time.sleep(5)\n        fsize = os.stat(self.fw_file_name).st_size\n        self.assertEqual(len(self.requests), 1)\n        self.provide_response()\n\n        self.communicate('enter-offline')\n\n        time.sleep(5)\n        self.assertEqual(os.stat(self.fw_file_name).st_size, fsize)\n        self.assertEqual(len(self.requests), 1)\n\n        self.communicate('afu-reconnect')\n\n        time.sleep(5)\n        self.assertEqual(os.stat(self.fw_file_name).st_size, fsize)\n        self.assertEqual(len(self.requests), 1)\n\n        self.communicate('exit-offline')\n\n        self.assertDemoRegisters()\n\n        deadline = time.time() + 20\n        while time.time() < deadline:\n            time.sleep(0.5)\n\n            if self.read_state(Instances.APP) == UpdateState.DOWNLOADED:\n                break\n        else:\n            raise\n\n        self.assertEqual(len(self.requests), 2)\n\n\nclass AdvancedFirmwareUpdateRestartWithDownloaded(AdvancedFirmwareUpdate.Test):\n    def setUp(self):\n        super().setUp()\n        self.set_check_marker(True)\n        self.set_auto_deregister(False)\n        self.set_reset_machine(False)\n\n    def runTest(self):\n        # Write /33629/0/0 (Firmware): script content\n        req = Lwm2mWrite(ResPath.AdvancedFirmwareUpdate[Instances.APP].Package,\n                         make_firmware_package(self.FIRMWARE_SCRIPT_CONTENT,\n                                               **self.FW_PKG_OPTS),\n                         format=coap.ContentFormat.APPLICATION_OCTET_STREAM)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n\n        self.assertEqual(UpdateState.DOWNLOADED, self.read_state(Instances.APP))\n        self.assertEqual(UpdateResult.INITIAL,\n                         self.read_update_result(Instances.APP))\n\n        # restart the app\n        self.teardown_demo_with_servers()\n        self.setup_demo_with_servers(\n            afu_marker_path=self.ANJAY_MARKER_FILE,\n            afu_original_img_file_path=self.ORIGINAL_IMG_FILE)\n\n        self.assertEqual(UpdateState.DOWNLOADED, self.read_state(Instances.APP))\n        self.assertEqual(UpdateResult.INITIAL,\n                         self.read_update_result(Instances.APP))\n\n        # Execute /33629/0/2 (Update)\n        req = Lwm2mExecute(ResPath.AdvancedFirmwareUpdate[Instances.APP].Update)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n\n        # Demo delays its reset while APP update. Wait for it and terminate if reset not occurred.\n        self._terminate_demo()\n\n\n\n\nclass AdvancedFirmwareUpdateResumeDownloadingOverHttpWithReconnect(\n    AdvancedFirmwareUpdate.TestWithPartialHttpDownloadAndRestart):\n    def _get_valgrind_args(self):\n        # we don't kill the process here, so we want Valgrind\n        return AdvancedFirmwareUpdate.TestWithHttpServer._get_valgrind_args(\n            self)\n\n    def send_headers(self, handler, response_content, response_etag):\n        if 'Range' in handler.headers:\n            self.assertEqual(handler.headers['If-Match'], response_etag)\n            match = re.fullmatch(r'bytes=([0-9]+)-', handler.headers['Range'])\n            self.assertIsNotNone(match)\n            offset = int(match.group(1))\n            handler.send_header('Content-range',\n                                'bytes %d-%d/*' % (\n                                    offset, len(response_content) - 1))\n            return offset\n\n    def runTest(self):\n        self.provide_response_app_img()\n        # Write /33629/0/1 (Firmware URI)\n        req = Lwm2mWrite(\n            ResPath.AdvancedFirmwareUpdate[Instances.APP].PackageURI,\n            self.get_firmware_uri())\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.serv.recv())\n\n        self.wait_for_half_download()\n\n        # reconnect\n        self.serv.reset()\n        self.communicate('reconnect')\n        self.provide_response_app_img()\n        self.assertDemoRegisters(self.serv, timeout_s=5)\n\n        # wait until client downloads the firmware\n        deadline = time.time() + 20\n        state = None\n        while time.time() < deadline:\n            fsize = os.stat(self.fw_file_name).st_size\n            self.assertGreater(fsize * 2, self.GARBAGE_SIZE)\n            state = self.read_state(Instances.APP)\n            self.assertIn(\n                state, {UpdateState.DOWNLOADING, UpdateState.DOWNLOADED})\n            if state == UpdateState.DOWNLOADED:\n                break\n            # prevent test from reading Result hundreds of times per second\n            time.sleep(0.5)\n\n        self.assertEqual(state, UpdateState.DOWNLOADED)\n\n        self.assertEqual(len(self.requests), 2)\n\n\n\n\nclass AdvancedFirmwareUpdateWithDelayedResultTest:\n    class TestMixin:\n        def runTest(self, forced_error, result):\n            with open(os.path.join(self.config.demo_path, self.config.demo_cmd),\n                      'rb') as f:\n                firmware = f.read()\n\n            # Write /33629/0/0 (Firmware)\n            self.block_send(firmware,\n                            equal_chunk_splitter(chunk_size=1024),\n                            force_error=forced_error)\n\n            # Execute /33629/0/2 (Update)\n            req = Lwm2mExecute(\n                ResPath.AdvancedFirmwareUpdate[Instances.APP].Update)\n            self.serv.send(req)\n            self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                                self.serv.recv())\n\n            self.serv.reset()\n            self.assertDemoRegisters()\n            self.assertEqual(\n                self.read_path(self.serv, ResPath.AdvancedFirmwareUpdate[\n                    Instances.APP].UpdateResult).content,\n                str(result).encode())\n            self.assertEqual(self.read_path(self.serv,\n                                            ResPath.AdvancedFirmwareUpdate[\n                                                Instances.APP].State).content,\n                             str(UpdateState.IDLE).encode())\n\n\nclass AdvancedFirmwareUpdateWithDelayedSuccessTest(\n    AdvancedFirmwareUpdateWithDelayedResultTest.TestMixin,\n    AdvancedFirmwareUpdate.BlockTest):\n    def runTest(self):\n        super().runTest(PackageForcedError.Firmware.DelayedSuccess,\n                        UpdateResult.SUCCESS)\n\n\nclass AdvancedFirmwareUpdateWithDelayedFailureTest(\n    AdvancedFirmwareUpdateWithDelayedResultTest.TestMixin,\n    AdvancedFirmwareUpdate.BlockTest):\n    def runTest(self):\n        super().runTest(PackageForcedError.Firmware.DelayedFailedUpdate,\n                        UpdateResult.FAILED)\n\n\nclass AdvancedFirmwareUpdateWithSetSuccessInPerformUpgrade(\n    AdvancedFirmwareUpdate.BlockTest):\n    def runTest(self):\n        with open(os.path.join(self.config.demo_path, self.config.demo_cmd),\n                  'rb') as f:\n            firmware = f.read()\n\n        # Write /33629/0/0 (Firmware)\n        self.block_send(firmware,\n                        equal_chunk_splitter(chunk_size=1024),\n                        force_error=PackageForcedError.Firmware.SetSuccessInPerformUpgrade)\n\n        # Execute /33629/0/2 (Update)\n        req = Lwm2mExecute(ResPath.AdvancedFirmwareUpdate[Instances.APP].Update)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n\n        # perform_upgrade handler is called via scheduler, so there is a small\n        # window during which reading the Firmware Update State still returns\n        # Updating. Wait for a while for State to actually change.\n        observed_states = []\n        deadline = time.time() + 5  # arbitrary limit\n        while not observed_states or observed_states[-1] == str(\n                UpdateState.UPDATING):\n            if time.time() > deadline:\n                self.fail(\n                    'Firmware Update did not finish on time, last state = %s' % (\n                        observed_states[-1] if observed_states else 'NONE'))\n            observed_states.append(\n                self.read_path(self.serv, ResPath.AdvancedFirmwareUpdate[\n                    Instances.APP].State).content.decode())\n            time.sleep(0.5)\n\n        self.assertNotEqual([], observed_states)\n        self.assertEqual(observed_states[-1], str(UpdateState.IDLE))\n        self.assertEqual(self.read_path(self.serv,\n                                        ResPath.AdvancedFirmwareUpdate[\n                                            Instances.APP].UpdateResult).content,\n                         str(UpdateResult.SUCCESS).encode())\n\n\nclass AdvancedFirmwareUpdateWithSetFailureInPerformUpgrade(\n    AdvancedFirmwareUpdate.BlockTest):\n    def runTest(self):\n        with open(os.path.join(self.config.demo_path, self.config.demo_cmd),\n                  'rb') as f:\n            firmware = f.read()\n\n        # Write /33629/0/0 (Firmware)\n        self.block_send(firmware,\n                        equal_chunk_splitter(chunk_size=1024),\n                        force_error=PackageForcedError.Firmware.SetFailureInPerformUpgrade)\n\n        # Execute /33629/0/2 (Update)\n        req = Lwm2mExecute(ResPath.AdvancedFirmwareUpdate[Instances.APP].Update)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n\n        # perform_upgrade handler is called via scheduler, so there is a small\n        # window during which reading the Firmware Update State still returns\n        # Updating. Wait for a while for State to actually change.\n        observed_states = []\n        deadline = time.time() + 5  # arbitrary limit\n        while not observed_states or observed_states[-1] == str(\n                UpdateState.UPDATING):\n            if time.time() > deadline:\n                self.fail(\n                    'Firmware Update did not finish on time, last state = %s' % (\n                        observed_states[-1] if observed_states else 'NONE'))\n            observed_states.append(\n                self.read_path(self.serv, ResPath.AdvancedFirmwareUpdate[\n                    Instances.APP].State).content.decode())\n            time.sleep(0.5)\n\n        self.assertNotEqual([], observed_states)\n        self.assertEqual(observed_states[-1], str(UpdateState.IDLE))\n        self.assertEqual(self.read_path(self.serv,\n                                        ResPath.AdvancedFirmwareUpdate[\n                                            Instances.APP].UpdateResult).content,\n                         str(UpdateResult.FAILED).encode())\n\n\ntry:\n    import aiocoap\n    import aiocoap.resource\n    import aiocoap.transports.tls\nexcept ImportError:\n    # FirmwareUpdateCoapTlsTest requires a bleeding-edge version of aiocoap, that at the time of\n    # writing this code, is not available even in the prerelease channel.\n    # So we're not enforcing this dependency for now.\n    pass\n\n\n@unittest.skipIf('aiocoap.transports.tls' not in sys.modules,\n                 'aiocoap.transports.tls not available')\n@unittest.skipIf(sys.version_info < (3, 5, 3),\n                 'SSLContext signature changed in Python 3.5.3')\nclass AdvancedFirmwareUpdateCoapTlsTest(\n    AdvancedFirmwareUpdate.TestWithTlsServer, test_suite.Lwm2mDmOperations):\n    def setUp(self):\n        super().setUp(garbage=8000)\n\n        class FirmwareResource(aiocoap.resource.Resource):\n            async def render_get(resource, request):\n                return aiocoap.Message(payload=make_firmware_package(\n                    self.FIRMWARE_SCRIPT_CONTENT, **self.FW_PKG_OPTS))\n\n        serversite = aiocoap.resource.Site()\n        serversite.add_resource(('firmware',), FirmwareResource())\n\n        sslctx = ssl.SSLContext()\n        sslctx.load_cert_chain(self._cert_file, self._key_file)\n\n        class EphemeralTlsServer(aiocoap.transports.tls.TLSServer):\n            _default_port = 0\n\n        class CoapTcpFileServerThread(threading.Thread):\n            def __init__(self):\n                super().__init__()\n                self.loop = asyncio.new_event_loop()\n                ctx = aiocoap.Context(loop=self.loop, serversite=serversite)\n                self.loop.run_until_complete(ctx._append_tokenmanaged_transport(\n                    lambda tman: EphemeralTlsServer.create_server(\n                        ('127.0.0.1', 0), tman, ctx.log,\n                        self.loop, sslctx)))\n\n                socket = \\\n                    ctx.request_interfaces[0].token_interface.server.sockets[0]\n                self.server_address = socket.getsockname()\n\n            def run(self):\n                asyncio.set_event_loop(self.loop)\n                try:\n                    self.loop.run_forever()\n                finally:\n                    self.loop.run_until_complete(\n                        self.loop.shutdown_asyncgens())\n                    self.loop.close()\n\n        self.server_thread = CoapTcpFileServerThread()\n        self.server_thread.start()\n\n        self.set_check_marker(True)\n        self.set_auto_deregister(False)\n        self.set_reset_machine(False)\n\n    def tearDown(self):\n        try:\n            super().tearDown()\n        finally:\n            self.server_thread.loop.call_soon_threadsafe(\n                self.server_thread.loop.stop)\n            self.server_thread.join()\n\n    def get_firmware_uri(self):\n        return 'coaps+tcp://127.0.0.1:%d/firmware' % (\n            self.server_thread.server_address[1],)\n\n    def runTest(self):\n        self.write_firmware_and_wait_for_download(Instances.APP,\n                                                  self.get_firmware_uri())\n\n        # Execute /33629/0/2 (Update)\n        req = Lwm2mExecute(ResPath.AdvancedFirmwareUpdate[Instances.APP].Update)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n\n        # Demo delays its reset while APP update. Wait for it and terminate if reset not occurred.\n        self._terminate_demo()\n\n\nclass AdvancedFirmwareUpdateWeakEtagTest(\n    AdvancedFirmwareUpdate.TestWithHttpServer):\n    def setUp(self):\n        super().setUp()\n        self.set_check_marker(True)\n        self.set_auto_deregister(False)\n        self.set_reset_machine(False)\n\n        orig_end_headers = self.http_server.RequestHandlerClass.end_headers\n\n        def updated_end_headers(request_handler):\n            request_handler.send_header('ETag', 'W/\"weaketag\"')\n            orig_end_headers(request_handler)\n\n        self.http_server.RequestHandlerClass.end_headers = updated_end_headers\n\n    def runTest(self):\n        self.provide_response_app_img()\n        self.write_firmware_and_wait_for_download(Instances.APP,\n                                                  self.get_firmware_uri())\n\n        with open(self.ANJAY_MARKER_FILE, 'rb') as f:\n            marker_data = f.read()\n\n        self.assertNotIn(b'weaketag', marker_data)\n\n        # Execute /33629/0/2 (Update)\n        req = Lwm2mExecute(ResPath.AdvancedFirmwareUpdate[Instances.APP].Update)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n\n        # Demo delays its reset while APP update. Wait for it and terminate if reset not occurred.\n        self._terminate_demo()\n\n\nclass SameSocketDownload:\n    class Test(test_suite.Lwm2mDtlsSingleServerTest,\n               test_suite.Lwm2mDmOperations,\n               AdvancedFirmwareUpdate.Test):\n        GARBAGE_SIZE = 2048\n        # Set to be able to comfortably test interleaved requests\n        ACK_TIMEOUT = 10\n        MAX_RETRANSMIT = 4\n        # Sometimes we want to allow the client to send more than one request at a time\n        # e.g. Update and GET.\n        NSTART = 1\n        LIFETIME = 86400\n        BINDING = 'U'\n        BLK_SZ = 1024\n\n        def setUp(self, *args, **kwargs):\n            if 'extra_cmdline_args' not in kwargs:\n                kwargs['extra_cmdline_args'] = []\n\n            kwargs['extra_cmdline_args'] += [\n                '--prefer-same-socket-downloads',\n                '--ack-timeout', str(self.ACK_TIMEOUT),\n                '--max-retransmit', str(self.MAX_RETRANSMIT),\n                '--ack-random-factor', str(1.0),\n                '--nstart', str(self.NSTART)\n            ]\n            kwargs['lifetime'] = self.LIFETIME\n            super().setUp(*args, **kwargs)\n            self.file_server = CoapFileServer(\n                self.serv._coap_server, binding=self.BINDING)\n            self.file_server.set_resource(path=FIRMWARE_PATH,\n                                          data=make_firmware_package(\n                                              b'a' * self.GARBAGE_SIZE\n                                              , **self.FW_PKG_OPTS))\n\n        def read_state(self, serv=None):\n            return int(self.read_resource(serv or self.serv,\n                                          oid=OID.AdvancedFirmwareUpdate,\n                                          iid=0,\n                                          rid=RID.AdvancedFirmwareUpdate.State).content)\n\n        def read_result(self, serv=None):\n            return int(self.read_resource(serv or self.serv,\n                                          oid=OID.AdvancedFirmwareUpdate,\n                                          iid=0,\n                                          rid=RID.AdvancedFirmwareUpdate.UpdateResult).content)\n\n        def start_download(self):\n            self.write_resource(self.serv,\n                                oid=OID.AdvancedFirmwareUpdate,\n                                iid=0,\n                                rid=RID.AdvancedFirmwareUpdate.PackageURI,\n                                content=self.file_server.get_resource_uri(\n                                    FIRMWARE_PATH))\n\n        def handle_get(self, pkt=None):\n            if pkt is None:\n                pkt = self.serv.recv()\n            block2 = pkt.get_options(coap.Option.BLOCK2)\n            if block2:\n                self.assertEqual(block2[0].block_size(), self.BLK_SZ)\n            self.file_server.handle_recvd_request(pkt)\n\n        def num_blocks(self):\n            return (len(\n                self.file_server._resources[\n                    FIRMWARE_PATH].data) + self.BLK_SZ - 1) // self.BLK_SZ\n\n\nclass AdvancedFirmwareDownloadSameSocket(SameSocketDownload.Test):\n    def runTest(self):\n        self.start_download()\n\n        for _ in range(self.num_blocks()):\n            pkt = self.serv.recv()\n            self.handle_get(pkt)\n\n        self.assertEqual(self.read_state(), UpdateState.DOWNLOADED)\n\n\nclass AdvancedFirmwareDownloadSameSocketAndOngoingBlockwiseWrite(\n    SameSocketDownload.Test):\n    def runTest(self):\n        self.create_instance(self.serv, oid=OID.Test, iid=1)\n        self.start_download()\n\n        resource_num_blocks = 10\n        self.assertGreaterEqual(resource_num_blocks, self.num_blocks())\n        for seq_num in range(resource_num_blocks):\n            if seq_num < self.num_blocks():\n                dl_req_get = self.serv.recv()\n\n            has_more = seq_num < resource_num_blocks - 1\n            # Server does blockwise write on the unrelated resource\n            pkt = Lwm2mWrite(ResPath.Test[1].ResRawBytes, b'x' * 16,\n                             format=coap.ContentFormat.APPLICATION_OCTET_STREAM,\n                             options=[\n                                 coap.Option.BLOCK1(seq_num, has_more, 16)])\n            self.serv.send(pkt)\n            if has_more:\n                self.assertMsgEqual(\n                    Lwm2mContinue.matching(pkt)(), self.serv.recv())\n            else:\n                self.assertMsgEqual(\n                    Lwm2mChanged.matching(pkt)(), self.serv.recv())\n\n            if seq_num < self.num_blocks():\n                # Client waits for next chunk of Raw Bytes, but gets firmware\n                # block instead\n                self.handle_get(dl_req_get)\n\n        self.assertEqual(self.read_state(), UpdateState.DOWNLOADED)\n\n\nclass AdvancedFirmwareDownloadSameSocketAndOngoingBlockwiseRead(\n    SameSocketDownload.Test):\n    BYTES = 160\n\n    def runTest(self):\n        self.create_instance(self.serv, oid=OID.Test, iid=1)\n        self.write_resource(self.serv, oid=OID.Test, iid=1,\n                            rid=RID.Test.ResBytesSize,\n                            content=bytes(str(self.BYTES), 'ascii'))\n        self.start_download()\n\n        block_size = 16\n        self.assertGreaterEqual(self.BYTES // block_size, self.num_blocks())\n        for seq_num in range(self.BYTES // block_size):\n            if seq_num < self.num_blocks():\n                dl_req_get = self.serv.recv()\n\n            # Server does blockwise write on the unrelated resource\n            pkt = Lwm2mRead(ResPath.Test[1].ResBytes,\n                            accept=coap.ContentFormat.APPLICATION_OCTET_STREAM,\n                            options=[\n                                coap.Option.BLOCK2(seq_num, 0, block_size)])\n            self.serv.send(pkt)\n            res = self.serv.recv()\n            self.assertEqual(pkt.msg_id, res.msg_id)\n            self.assertTrue(len(res.get_options(coap.Option.BLOCK2)) > 0)\n\n            if seq_num < self.num_blocks():\n                # Client waits for next chunk of Raw Bytes, but gets firmware\n                # block instead\n                self.handle_get(dl_req_get)\n\n        self.assertEqual(self.read_state(), UpdateState.DOWNLOADED)\n\n\nclass AdvancedFirmwareDownloadSameSocketUpdateDuringDownloadNstart2(\n    SameSocketDownload.Test):\n    NSTART = 2\n\n    def runTest(self):\n        self.start_download()\n\n        for _ in range(self.num_blocks()):\n            dl_req_get = self.serv.recv()\n            # rather than responding to a request, force Update\n            self.communicate('send-update')\n            self.assertDemoUpdatesRegistration(timeout_s=5)\n            # and only then respond with next block\n            self.handle_get(dl_req_get)\n\n        self.assertEqual(self.read_state(), UpdateState.DOWNLOADED)\n\n\nclass AdvancedFirmwareDownloadSameSocketUpdateDuringDownloadNstart1(\n    SameSocketDownload.Test):\n    def runTest(self):\n        self.start_download()\n\n        for _ in range(self.num_blocks()):\n            dl_req_get = self.serv.recv()\n            # rather than responding to a request, force Update\n            self.communicate('send-update')\n            # the Update won't be received, because of NSTART=1\n            with self.assertRaises(socket.timeout):\n                self.serv.recv(timeout_s=3)\n            # so we respond to a block\n            self.handle_get(dl_req_get)\n            # and only then the Update arrives\n            self.assertDemoUpdatesRegistration(timeout_s=5)\n\n        self.assertEqual(self.read_state(), UpdateState.DOWNLOADED)\n\n\nclass AdvancedFirmwareDownloadSameSocketAndReconnectNstart1(\n    SameSocketDownload.Test):\n    def runTest(self):\n        self.start_download()\n\n        for _ in range(self.num_blocks()):\n            # get dl request and ignore it\n            self.serv.recv()\n            # rather than responding to a request force reconnect\n            self.communicate('reconnect')\n            self.serv.reset()\n            # demo will resume DTLS session without sending any LwM2M messages\n            self.serv.listen()\n            # download request is retried\n            dl_req_get = self.serv.recv()\n            # and finally we respond to a block\n            self.handle_get(dl_req_get)\n\n        self.assertEqual(self.read_state(), UpdateState.DOWNLOADED)\n\n\nclass AdvancedFirmwareDownloadSameSocketUpdateTimeoutNstart2(\n    SameSocketDownload.Test):\n    NSTART = 2\n    LIFETIME = 5\n    MAX_RETRANSMIT = 1\n    ACK_TIMEOUT = 2\n\n    def runTest(self):\n        time.sleep(self.LIFETIME + 1)\n        self.assertMsgEqual(\n            Lwm2mUpdate(self.DEFAULT_REGISTER_ENDPOINT, content=b''),\n            self.serv.recv())\n        self.assertMsgEqual(\n            Lwm2mUpdate(self.DEFAULT_REGISTER_ENDPOINT, content=b''),\n            self.serv.recv())\n        self.start_download()\n\n        dl_get0_0 = self.serv.recv(filter=CoapGet._pkt_matches)\n        dl_get0_1 = self.serv.recv(filter=CoapGet._pkt_matches)  # retry\n        self.handle_get(dl_get0_0)\n        self.assertEqual(dl_get0_0.msg_id, dl_get0_1.msg_id)\n        dl_get1_0 = self.serv.recv(filter=CoapGet._pkt_matches)\n\n        # lifetime expired, demo re-registers\n        self.assertDemoRegisters(lifetime=self.LIFETIME)\n\n        # this is a retransmission\n        dl_get1_1 = self.serv.recv()\n        self.assertEqual(dl_get1_0.msg_id, dl_get1_1.msg_id)\n        self.handle_get(dl_get1_1)\n        self.handle_get(self.serv.recv())\n\n        self.assertEqual(self.read_state(), UpdateState.DOWNLOADED)\n\n\nclass AdvancedFirmwareDownloadSameSocketUpdateTimeoutNstart1(\n    SameSocketDownload.Test):\n    LIFETIME = 5\n    MAX_RETRANSMIT = 1\n    ACK_TIMEOUT = 2\n\n    def runTest(self):\n        time.sleep(self.LIFETIME + 1)\n        self.assertMsgEqual(\n            Lwm2mUpdate(self.DEFAULT_REGISTER_ENDPOINT, content=b''),\n            self.serv.recv())\n        self.assertMsgEqual(\n            Lwm2mUpdate(self.DEFAULT_REGISTER_ENDPOINT, content=b''),\n            self.serv.recv())\n        self.start_download()\n\n        # registration temporarily held due to ongoing download\n        self.handle_get(self.serv.recv())\n        # and only after handling the GET, it can be sent finally\n        self.assertDemoRegisters(lifetime=self.LIFETIME)\n        self.handle_get(self.serv.recv())\n        self.handle_get(self.serv.recv())\n\n        self.assertEqual(self.read_state(), UpdateState.DOWNLOADED)\n\n\nclass AdvancedFirmwareDownloadSameSocketDontCare(SameSocketDownload.Test):\n    def runTest(self):\n        self.start_download()\n        self.serv.recv()  # recv GET and ignore it\n\n\nclass AdvancedFirmwareDownloadSameSocketSuspendDueToOffline(\n    SameSocketDownload.Test):\n    ACK_TIMEOUT = 1.5\n\n    def runTest(self):\n        self.start_download()\n        self.serv.recv()  # ignore first GET\n        self.communicate('enter-offline')\n        with self.assertRaises(socket.timeout):\n            self.serv.recv(timeout_s=2 * self.ACK_TIMEOUT)\n        self.serv.reset()\n        self.communicate('exit-offline')\n\n        # demo will resume DTLS session without sending any LwM2M messages\n        self.serv.listen()\n\n        for _ in range(self.num_blocks()):\n            self.handle_get(self.serv.recv())\n\n        self.assertEqual(self.read_state(), UpdateState.DOWNLOADED)\n\n\nclass AdvancedFirmwareDownloadSameSocketSuspendDueToOfflineDuringUpdate(\n    SameSocketDownload.Test):\n    ACK_TIMEOUT = 1.5\n\n    def runTest(self):\n        self.start_download()\n        self.serv.recv()  # ignore first GET\n        self.communicate('send-update', match_regex=re.escape('Update sent'))\n        # actual Update message will not arrive due to NSTART\n        with self.serv.fake_close():\n            self.communicate('enter-offline')\n            self.wait_until_socket_count(expected=0, timeout_s=5)\n        self.serv.reset()\n        self.communicate('exit-offline')\n        # demo will resume DTLS session before sending Register\n        self.serv.listen()\n        self.assertDemoUpdatesRegistration()\n\n        for _ in range(self.num_blocks()):\n            self.handle_get(self.serv.recv())\n\n        self.assertEqual(self.read_state(), UpdateState.DOWNLOADED)\n\n\nclass AdvancedFirmwareDownloadSameSocketSuspendDueToOfflineDuringUpdateNoMessagesCheck(\n    SameSocketDownload.Test):\n    ACK_TIMEOUT = 1.5\n\n    def runTest(self):\n        # Note: This test is almost identical to the one above, but does not close the socket\n        # during the offline period. This is to check that the client does not attempt to send any\n        # packets during that time. With the bug that triggered the addition of these test cases,\n        # these were two distinct code flow paths.\n\n        self.start_download()\n        self.serv.recv()  # ignore first GET\n        self.communicate('send-update', match_regex=re.escape('Update sent'))\n        # actual Update message will not arrive due to NSTART\n        self.communicate('enter-offline')\n        with self.assertRaises(socket.timeout):\n            self.serv.recv(timeout_s=2 * self.ACK_TIMEOUT)\n        self.serv.reset()\n        self.communicate('exit-offline')\n        # demo will resume DTLS session before sending Update\n        self.serv.listen()\n        self.assertDemoUpdatesRegistration()\n\n        for _ in range(self.num_blocks()):\n            self.handle_get(self.serv.recv())\n\n        self.assertEqual(self.read_state(), UpdateState.DOWNLOADED)\n\n\nclass AdvancedFirmwareDownloadSameSocketAndBootstrap(SameSocketDownload.Test):\n    def setUp(self):\n        super().setUp(bootstrap_server=True)\n\n    def tearDown(self):\n        super(test_suite.Lwm2mSingleServerTest, self).tearDown(\n            deregister_servers=[self.new_server])\n\n    def runTest(self):\n        self.start_download()\n        self.serv.recv()  # recv GET and ignore it\n\n        self.execute_resource(self.serv,\n                              oid=OID.Server,\n                              iid=2,\n                              rid=RID.Server.RequestBootstrapTrigger)\n\n        self.assertDemoRequestsBootstrap()\n\n        self.new_server = Lwm2mServer()\n        self.write_resource(self.bootstrap_server,\n                            oid=OID.Security,\n                            iid=2,\n                            rid=RID.Security.ServerURI,\n                            content=bytes(\n                                'coap://127.0.0.1:%d' % self.new_server.get_listen_port(),\n                                'ascii'))\n        self.write_resource(self.bootstrap_server,\n                            oid=OID.Security,\n                            iid=2,\n                            rid=RID.Security.Mode,\n                            content=str(\n                                coap.server.SecurityMode.NoSec.value).encode())\n        req = Lwm2mBootstrapFinish()\n        self.bootstrap_server.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.bootstrap_server.recv())\n\n        self.assertDemoRegisters(self.new_server)\n        self.assertEqual(self.read_state(self.new_server), UpdateState.IDLE)\n\n\n\n\nclass AdvancedFirmwareUpdateCancelDuringIdleTest(AdvancedFirmwareUpdate.Test):\n    def runTest(self):\n        # Execute /33629/0/10 (Cancel)\n        req = Lwm2mExecute(ResPath.AdvancedFirmwareUpdate[Instances.APP].Cancel)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mErrorResponse.matching(req)(\n            code=coap.Code.RES_METHOD_NOT_ALLOWED),\n            self.serv.recv())\n\n        self.assertEqual(UpdateState.IDLE, self.read_state(Instances.APP))\n        self.assertEqual(UpdateResult.INITIAL,\n                         self.read_update_result(Instances.APP))\n\n\nclass AdvancedFirmwareUpdateCancelDuringDownloadingTest(\n    AdvancedFirmwareUpdate.TestWithPartialDownload,\n    AdvancedFirmwareUpdate.TestWithHttpServer):\n    RESPONSE_DELAY = 0.5\n    CHUNK_SIZE = 3000\n\n    def runTest(self):\n        self.provide_response_app_img()\n\n        # Write /33629/0/1 (Firmware URI)\n        req = Lwm2mWrite(\n            ResPath.AdvancedFirmwareUpdate[Instances.APP].PackageURI,\n            self.get_firmware_uri())\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.serv.recv())\n\n        self.wait_for_half_download()\n\n        # Execute /33629/0/10 (Cancel)\n        req = Lwm2mExecute(ResPath.AdvancedFirmwareUpdate[Instances.APP].Cancel)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n\n        self.assertEqual(UpdateState.IDLE, self.read_state(Instances.APP))\n        self.assertEqual(UpdateResult.CANCELLED,\n                         self.read_update_result(Instances.APP))\n\n\nclass AdvancedFirmwareUpdateCancelDuringDownloadedTest(\n    AdvancedFirmwareUpdate.TestWithHttpServer):\n    def runTest(self):\n        self.provide_response_app_img()\n        self.write_firmware_and_wait_for_download(Instances.APP,\n                                                  self.get_firmware_uri())\n\n        # Execute /33629/0/10 (Cancel)\n        req = Lwm2mExecute(ResPath.AdvancedFirmwareUpdate[Instances.APP].Cancel)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n\n        self.assertEqual(UpdateState.IDLE, self.read_state(Instances.APP))\n        self.assertEqual(UpdateResult.CANCELLED,\n                         self.read_update_result(Instances.APP))\n\n\nclass AdvancedFirmwareUpdateCancelDuringUpdatingTest(\n    AdvancedFirmwareUpdate.TestWithHttpServer):\n    def setUp(self):\n        super().setUp()\n        self.set_reset_machine(False)\n        # Don't run the downloaded package to be able to process Cancel\n        self.FW_PKG_OPTS = {\n            \"force_error\": PackageForcedError.Firmware.DoNothing,\n            'magic': b'AJAY_APP', 'linked': []\n        }\n\n    def runTest(self):\n        self.provide_response_app_img()\n        self.write_firmware_and_wait_for_download(Instances.APP,\n                                                  self.get_firmware_uri())\n\n        # Execute /33629/0/2 (Update)\n        req = Lwm2mExecute(ResPath.AdvancedFirmwareUpdate[Instances.APP].Update)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n\n        # Execute /33629/0/10 (Cancel)\n        req = Lwm2mExecute(ResPath.AdvancedFirmwareUpdate[Instances.APP].Cancel)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mErrorResponse.matching(req)(\n            code=coap.Code.RES_METHOD_NOT_ALLOWED),\n            self.serv.recv())\n\n\n\n\nclass AdvancedFirmwareUpdateMaxDeferPeriodInvalidValueTest(\n    AdvancedFirmwareUpdate.Test):\n    def runTest(self):\n        # Write /33629/0/13 (Maximum Defer Period)\n        req = Lwm2mWrite(\n            ResPath.AdvancedFirmwareUpdate[Instances.APP].MaxDeferPeriod, b'-5')\n        self.serv.send(req)\n        self.assertMsgEqual(\n            Lwm2mErrorResponse.matching(req)(coap.Code.RES_BAD_REQUEST),\n            self.serv.recv())\n\n\nclass AdvancedFirmwareUpdateMaxDeferPeriodValidValueTest(\n    AdvancedFirmwareUpdate.Test):\n    def runTest(self):\n        for max_defer_period_value in [b'0', b'30']:\n            # Write /33629/0/13 (Maximum Defer Period)\n            req = Lwm2mWrite(\n                ResPath.AdvancedFirmwareUpdate[Instances.APP].MaxDeferPeriod,\n                max_defer_period_value)\n            self.serv.send(req)\n            self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.serv.recv())\n\n\nclass AdvancedFirmwareUpdateWithDefer(AdvancedFirmwareUpdate.BlockTest):\n    def runTest(self):\n        with open(os.path.join(self.config.demo_path, self.config.demo_cmd),\n                  'rb') as f:\n            firmware = f.read()\n\n        # Write /33629/0/13 (Maximum Defer Period)\n        req = Lwm2mWrite(\n            ResPath.AdvancedFirmwareUpdate[Instances.APP].MaxDeferPeriod, b'30')\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n\n        # Write /33629/0/0 (Firmware)\n        self.block_send(firmware,\n                        equal_chunk_splitter(chunk_size=1024),\n                        force_error=PackageForcedError.Firmware.Defer)\n\n        # Execute /33629/0/2 (Update)\n        req = Lwm2mExecute(ResPath.AdvancedFirmwareUpdate[Instances.APP].Update)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n\n        # perform_upgrade handler is called via scheduler, so there is a small\n        # window during which reading the Firmware Update State still returns\n        # Updating. Wait for a while for State to actually change.\n        observed_states = []\n        deadline = time.time() + 5  # arbitrary limit\n        while not observed_states or observed_states[-1] == str(\n                UpdateState.UPDATING):\n            if time.time() > deadline:\n                self.fail(\n                    'Firmware Update did not finish on time, last state = %s' % (\n                        observed_states[-1] if observed_states else 'NONE'))\n            observed_states.append(\n                self.read_path(self.serv, ResPath.AdvancedFirmwareUpdate[\n                    Instances.APP].State).content.decode())\n            time.sleep(0.5)\n\n        self.assertNotEqual([], observed_states)\n        self.assertEqual(observed_states[-1], str(UpdateState.DOWNLOADED))\n        self.assertEqual(self.read_path(self.serv,\n                                        ResPath.AdvancedFirmwareUpdate[\n                                            Instances.APP].UpdateResult).content,\n                         str(UpdateResult.DEFERRED).encode())\n\n\nclass AdvancedFirmwareUpdateSeverityWriteInvalidValueTest(\n    AdvancedFirmwareUpdate.Test):\n    def runTest(self):\n        for invalid_severity in [b'-1', b'3']:\n            # Write /33629/0/11 (Severity)\n            req = Lwm2mWrite(\n                ResPath.AdvancedFirmwareUpdate[Instances.APP].Severity,\n                invalid_severity)\n            self.serv.send(req)\n            self.assertMsgEqual(\n                Lwm2mErrorResponse.matching(req)(coap.Code.RES_BAD_REQUEST),\n                self.serv.recv())\n\n\nclass AdvancedFirmwareUpdateSeverityWriteValidValueTest(\n    AdvancedFirmwareUpdate.Test):\n    def runTest(self):\n        valid_severity_values = [\n            UpdateSeverity.CRITICAL,\n            UpdateSeverity.MANDATORY,\n            UpdateSeverity.OPTIONAL\n        ]\n        for severity in [str(i).encode() for i in valid_severity_values]:\n            # Write /33629/0/11 (Severity)\n            req = Lwm2mWrite(\n                ResPath.AdvancedFirmwareUpdate[Instances.APP].Severity,\n                severity)\n            self.serv.send(req)\n            self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.serv.recv())\n\n\nclass AdvancedFirmwareUpdateSeverityReadTest(AdvancedFirmwareUpdate.Test):\n    def runTest(self):\n        # Read default /33629/0/11 (Severity)\n        req = Lwm2mRead(ResPath.AdvancedFirmwareUpdate[Instances.APP].Severity)\n        self.serv.send(req)\n        self.assertMsgEqual(\n            Lwm2mContent.matching(req)(\n                content=str(UpdateSeverity.MANDATORY).encode()),\n            self.serv.recv())\n\n\nclass AdvancedFirmwareUpdateLastStateChangeTime:\n    class Test:\n        def observe_state(self):\n            # Observe /33629/0/3 (State)\n            observe_req = Lwm2mObserve('/%d/0/%d' % (\n                OID.AdvancedFirmwareUpdate, RID.AdvancedFirmwareUpdate.State))\n            self.serv.send(observe_req)\n            self.assertMsgEqual(Lwm2mContent.matching(observe_req)(),\n                                self.serv.recv())\n            return observe_req.token\n\n        def cancel_observe_state(self, token):\n            cancel_req = Lwm2mObserve('/%d/0/%d' % (\n                OID.AdvancedFirmwareUpdate, RID.AdvancedFirmwareUpdate.State),\n                                      observe=1, token=token)\n            self.serv.send(cancel_req)\n            self.assertMsgEqual(Lwm2mContent.matching(cancel_req)(),\n                                self.serv.recv())\n\n        def get_states_and_timestamp(self, token, deadline=None):\n            # Receive a notification from /33629/0/3 and read /33629/0/12\n            notification_responses = [self.serv.recv(deadline=deadline)]\n            self.assertMsgEqual(Lwm2mNotify(token), notification_responses[0])\n\n            read_response = self.read_path(self.serv,\n                                           ResPath.AdvancedFirmwareUpdate[0].LastStateChangeTime,\n                                           deadline=deadline)\n            while True:\n                try:\n                    notification_responses.append(\n                        self.serv.recv(timeout_s=0,\n                                       filter=lambda pkt: isinstance(pkt,\n                                                                     Lwm2mNotify)))\n                except socket.timeout:\n                    break\n            return [r.content.decode() for r in\n                    notification_responses], read_response.content.decode()\n\n\nclass AdvancedFirmwareUpdateLastStateChangeTimeWithDelayedSuccessTest(\n    AdvancedFirmwareUpdate.BlockTest,\n    AdvancedFirmwareUpdateLastStateChangeTime.Test):\n    def runTest(self):\n        with open(os.path.join(self.config.demo_path, self.config.demo_cmd),\n                  'rb') as f:\n            firmware = f.read()\n\n        observe_token = self.observe_state()\n\n        # Write /33629/0/0 (Firmware)\n        self.block_send(firmware,\n                        equal_chunk_splitter(chunk_size=1024),\n                        force_error=PackageForcedError.Firmware.DelayedSuccess)\n\n        _, before_update_timestamp = self.get_states_and_timestamp(\n            observe_token)\n\n        time.sleep(1)\n        # Execute /33629/0/2 (Update)\n        req = Lwm2mExecute(ResPath.AdvancedFirmwareUpdate[Instances.APP].Update)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n\n        state_notification = self.serv.recv()\n        self.assertMsgEqual(Lwm2mNotify(observe_token), state_notification)\n\n        self.serv.reset()\n        self.assertDemoRegisters()\n\n        req = Lwm2mRead(\n            ResPath.AdvancedFirmwareUpdate[Instances.APP].LastStateChangeTime)\n        self.serv.send(req)\n        after_update_response = self.serv.recv()\n        self.assertMsgEqual(Lwm2mContent.matching(req)(), after_update_response)\n        all_timestamps = [before_update_timestamp,\n                          after_update_response.content.decode()]\n        self.assertEqual(all_timestamps, sorted(all_timestamps))\n\n\nclass AdvancedFirmwareUpdateLastStateChangeTimeWithDeferTest(\n    AdvancedFirmwareUpdate.BlockTest,\n    AdvancedFirmwareUpdateLastStateChangeTime.Test):\n    def observe_after_update(self, token):\n        observed_states = []\n        observed_timestamps = []\n        deadline = time.time() + 5  # arbitrary limit\n        while not observed_states or observed_states[-1] == str(\n                UpdateState.UPDATING):\n            states, timestamp = self.get_states_and_timestamp(token, deadline=deadline)\n            observed_states += states\n            observed_timestamps.append(timestamp)\n        self.assertNotEqual([], observed_timestamps)\n        return observed_timestamps\n\n    def runTest(self):\n        with open(os.path.join(self.config.demo_path, self.config.demo_cmd),\n                  'rb') as f:\n            firmware = f.read()\n\n        # Write /33629/0/13 (Maximum Defer Period)\n        req = Lwm2mWrite(\n            ResPath.AdvancedFirmwareUpdate[Instances.APP].MaxDeferPeriod, b'30')\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.serv.recv())\n\n        observe_token = self.observe_state()\n\n        # Write /33629/0/0 (Firmware)\n        self.block_send(firmware,\n                        equal_chunk_splitter(chunk_size=1024),\n                        force_error=PackageForcedError.Firmware.Defer)\n\n        _, after_write_timestamp = self.get_states_and_timestamp(observe_token)\n\n        time.sleep(1)\n        # Execute /33629/0/2 (Update)\n        req = Lwm2mExecute(ResPath.AdvancedFirmwareUpdate[Instances.APP].Update)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n\n        observed_timestamps = self.observe_after_update(observe_token)\n\n        all_timestamps = [after_write_timestamp] + observed_timestamps\n        self.assertEqual(all_timestamps, sorted(all_timestamps))\n\n        self.cancel_observe_state(observe_token)\n\n\nclass AdvancedFirmwareUpdateSeverityPersistenceTest(\n    AdvancedFirmwareUpdate.Test):\n    def restart(self):\n        self.teardown_demo_with_servers()\n        self.setup_demo_with_servers(\n            afu_marker_path=self.ANJAY_MARKER_FILE,\n            afu_original_img_file_path=self.ORIGINAL_IMG_FILE)\n\n    def runTest(self):\n        severity_values = [\n            UpdateSeverity.CRITICAL,\n            UpdateSeverity.MANDATORY,\n            UpdateSeverity.OPTIONAL\n        ]\n        for severity in [str(i).encode() for i in severity_values]:\n            # Write /33629/0/11 (Severity)\n            req = Lwm2mWrite(\n                ResPath.AdvancedFirmwareUpdate[Instances.APP].Severity,\n                severity)\n            self.serv.send(req)\n            self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.serv.recv())\n\n            req = Lwm2mWrite(\n                ResPath.AdvancedFirmwareUpdate[Instances.APP].Package,\n                make_firmware_package(self.FIRMWARE_SCRIPT_CONTENT,\n                                      **self.FW_PKG_OPTS),\n                format=coap.ContentFormat.APPLICATION_OCTET_STREAM)\n            self.serv.send(req)\n            self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.serv.recv())\n            self.restart()\n\n            req = Lwm2mRead(\n                ResPath.AdvancedFirmwareUpdate[Instances.APP].Severity)\n            self.serv.send(req)\n            res = self.serv.recv()\n            self.assertMsgEqual(Lwm2mContent.matching(req)(), res)\n\n            self.assertEqual(severity, res.content)\n            self.restart()\n\n\nclass AdvancedFirmwareUpdateDeadlinePersistenceTest(\n    FirmwareUpdate.DemoArgsExtractorMixin,\n    AdvancedFirmwareUpdate.BlockTest):\n    def get_deadline(self):\n        return int(self.communicate('get-afu-deadline',\n                                    match_regex='AFU_APP_UPDATE_DEADLINE==([0-9]+)\\n').group(\n            1))\n\n    def runTest(self):\n        with open(os.path.join(self.config.demo_path, self.config.demo_cmd),\n                  'rb') as f:\n            firmware = f.read()\n\n        # Write /33629/0/13 (Maximum Defer Period)\n        req = Lwm2mWrite(\n            ResPath.AdvancedFirmwareUpdate[Instances.APP].MaxDeferPeriod, b'30')\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n\n        # Write /33629/0/0 (Firmware)\n        self.block_send(firmware,\n                        equal_chunk_splitter(chunk_size=1024),\n                        force_error=PackageForcedError.Firmware.Defer)\n\n        # Execute /33629/0/2 (Update)\n        req = Lwm2mExecute(ResPath.AdvancedFirmwareUpdate[Instances.APP].Update)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n\n        # perform_upgrade handler is called via scheduler, so there is a small\n        # window during which reading the Firmware Update State still returns\n        # Updating. Wait for a while for State to actually change.\n        observed_states = []\n        deadline = time.time() + 5  # arbitrary limit\n        while not observed_states or observed_states[-1] == str(\n                UpdateState.UPDATING):\n            if time.time() > deadline:\n                self.fail(\n                    'Firmware Update did not finish on time, last state = %s' % (\n                        observed_states[-1] if observed_states else 'NONE'))\n            observed_states.append(\n                self.read_path(self.serv, ResPath.AdvancedFirmwareUpdate[\n                    Instances.APP].State).content.decode())\n            time.sleep(0.5)\n\n        self.assertNotEqual([], observed_states)\n        self.assertEqual(observed_states[-1], str(UpdateState.DOWNLOADED))\n\n        saved_deadline = self.get_deadline()\n\n        self.demo_process.kill()\n        self.serv.reset()\n        self._start_demo(self.cmdline_args)\n        self.assertDemoRegisters()\n\n        restored_deadline = self.get_deadline()\n\n        self.assertEqual(saved_deadline, restored_deadline)\n\n\n#\n# Below test cases are specific for Advanced Firmware Update\n#\n\nclass AdvancedFirmwareUpdatePackageTestTwoNotLinkedImages(\n    AdvancedFirmwareUpdate.Test):\n    def setUp(self):\n        super().setUp()\n        self.set_check_marker(True)\n        self.set_auto_deregister(False)\n        self.set_reset_machine(False)\n\n    def runTest(self):\n        # Prepare package for /33629/0\n        self.FW_PKG_OPTS = {'magic': b'AJAY_APP'}\n        self.prepare_package_app_img()\n\n        # Write /33629/0/0 (Firmware)\n        req = Lwm2mWrite(ResPath.AdvancedFirmwareUpdate[Instances.APP].Package,\n                         self.PACKAGE,\n                         format=coap.ContentFormat.APPLICATION_OCTET_STREAM)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n\n        # Check /33629/0 state and result\n        self.assertEqual(UpdateState.DOWNLOADED, self.read_state(Instances.APP))\n        self.assertEqual(UpdateResult.INITIAL,\n                         self.read_update_result(Instances.APP))\n\n        # Prepare package for /33629/1\n        self.FW_PKG_OPTS = {'magic': b'AJAY_TEE'}\n        self.prepare_package_additional_img(content=DUMMY_FILE)\n\n        # Write /33629/1/0 (Firmware)\n        req = Lwm2mWrite(ResPath.AdvancedFirmwareUpdate[Instances.TEE].Package,\n                         self.PACKAGE,\n                         format=coap.ContentFormat.APPLICATION_OCTET_STREAM)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n\n        # Check /33629/1 state and result\n        self.assertEqual(UpdateState.DOWNLOADED, self.read_state(Instances.TEE))\n        self.assertEqual(UpdateResult.INITIAL,\n                         self.read_update_result(Instances.TEE))\n\n        self.execute_update_and_check_success(Instances.TEE)\n\n        # Execute /33629/0/2 (Update)\n        req = Lwm2mExecute(ResPath.AdvancedFirmwareUpdate[Instances.APP].Update)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n\n        # Demo delays its reset while APP update. Wait for it and terminate if reset not occurred.\n        self._terminate_demo()\n\n\nclass AdvancedFirmwareUpdateUriTestTwoNotLinkedImages(\n    AdvancedFirmwareUpdate.TestWithHttpServer):\n\n    def setUp(self):\n        super().setUp()\n        self.set_check_marker(True)\n        self.set_auto_deregister(False)\n        self.set_reset_machine(False)\n\n    def runTest(self):\n        # Prepare package for /33629/0\n        self.FW_PKG_OPTS = {'magic': b'AJAY_APP'}\n        self.provide_response_app_img()\n\n        # Write /33629/0/1 (Package URI)\n        self.write_firmware_and_wait_for_download(Instances.APP,\n                                                  self.get_firmware_uri())\n\n        # Check /33629/0 result\n        self.assertEqual(UpdateResult.INITIAL,\n                         self.read_update_result(Instances.APP))\n\n        # Prepare package for /33629/1\n        self.FW_PKG_OPTS = {'magic': b'AJAY_TEE'}\n        self.provide_response_additional_img(content=DUMMY_FILE)\n\n        # Write /33629/1/1 (Package URI)\n        self.write_firmware_and_wait_for_download(Instances.TEE,\n                                                  self.get_firmware_uri())\n\n        # Check /33629/1 result\n        self.assertEqual(UpdateResult.INITIAL,\n                         self.read_update_result(Instances.TEE))\n\n        self.execute_update_and_check_success(Instances.TEE)\n\n        # Execute /33629/0/2 (Update)\n        req = Lwm2mExecute(ResPath.AdvancedFirmwareUpdate[Instances.APP].Update)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n\n        # Demo delays its reset while APP update. Wait for it and terminate if reset not occurred.\n        self._terminate_demo()\n\n\nclass AdvancedFirmwareUpdatePackageTestFourNotLinkedImages(\n    AdvancedFirmwareUpdate.Test):\n    def setUp(self):\n        super().setUp()\n        self.set_check_marker(True)\n        self.set_auto_deregister(False)\n        self.set_reset_machine(False)\n\n    def runTest(self):\n        # Prepare package for /33629/0\n        self.FW_PKG_OPTS = {'magic': b'AJAY_APP'}\n        self.prepare_package_app_img()\n\n        # Write /33629/0/0 (Firmware)\n        req = Lwm2mWrite(ResPath.AdvancedFirmwareUpdate[Instances.APP].Package,\n                         self.PACKAGE,\n                         format=coap.ContentFormat.APPLICATION_OCTET_STREAM)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n\n        # Check /33629/0 state and result\n        self.assertEqual(UpdateState.DOWNLOADED, self.read_state(Instances.APP))\n        self.assertEqual(UpdateResult.INITIAL,\n                         self.read_update_result(Instances.APP))\n\n        # Prepare package for /33629/1\n        self.FW_PKG_OPTS = {'magic': b'AJAY_TEE'}\n        self.prepare_package_additional_img(content=DUMMY_FILE)\n\n        # Write /33629/1/0 (Firmware)\n        req = Lwm2mWrite(ResPath.AdvancedFirmwareUpdate[Instances.TEE].Package,\n                         self.PACKAGE,\n                         format=coap.ContentFormat.APPLICATION_OCTET_STREAM)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n\n        # Check /33629/1 state and result\n        self.assertEqual(UpdateState.DOWNLOADED, self.read_state(Instances.TEE))\n        self.assertEqual(UpdateResult.INITIAL,\n                         self.read_update_result(Instances.TEE))\n\n        # Prepare package for /33629/2\n        self.FW_PKG_OPTS = {'magic': b'AJAYBOOT'}\n        self.prepare_package_additional_img(content=DUMMY_FILE)\n\n        # Write /33629/2/0 (Firmware)\n        req = Lwm2mWrite(ResPath.AdvancedFirmwareUpdate[Instances.BOOT].Package,\n                         self.PACKAGE,\n                         format=coap.ContentFormat.APPLICATION_OCTET_STREAM)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n\n        # Check /33629/2 state and result\n        self.assertEqual(UpdateState.DOWNLOADED,\n                         self.read_state(Instances.BOOT))\n        self.assertEqual(UpdateResult.INITIAL,\n                         self.read_update_result(Instances.BOOT))\n\n        # Prepare package for /33629/3\n        self.FW_PKG_OPTS = {'magic': b'AJAYMODE'}\n        self.prepare_package_additional_img(content=DUMMY_FILE)\n\n        # Write /33629/3/0 (Firmware)\n        req = Lwm2mWrite(\n            ResPath.AdvancedFirmwareUpdate[Instances.MODEM].Package,\n            self.PACKAGE,\n            format=coap.ContentFormat.APPLICATION_OCTET_STREAM)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n\n        # Check /33629/3 state and result\n        self.assertEqual(UpdateState.DOWNLOADED,\n                         self.read_state(Instances.MODEM))\n        self.assertEqual(UpdateResult.INITIAL,\n                         self.read_update_result(Instances.MODEM))\n\n        self.execute_update_and_check_success(Instances.TEE)\n        self.execute_update_and_check_success(Instances.BOOT)\n        self.execute_update_and_check_success(Instances.MODEM)\n\n        # Execute /33629/0/2 (Update)\n        req = Lwm2mExecute(ResPath.AdvancedFirmwareUpdate[Instances.APP].Update)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n\n        # Demo delays its reset while APP update. Wait for it and terminate if reset not occurred.\n        self._terminate_demo()\n\n\nclass AdvancedFirmwareUpdateUriTestFourNotLinkedImages(\n    AdvancedFirmwareUpdate.TestWithHttpServer):\n    def setUp(self):\n        super().setUp()\n        self.set_check_marker(True)\n        self.set_auto_deregister(False)\n        self.set_reset_machine(False)\n\n    def runTest(self):\n        # Prepare package for /33629/0\n        self.FW_PKG_OPTS = {'magic': b'AJAY_APP'}\n        self.provide_response_app_img()\n\n        # Write /33629/0/1 (Package URI)\n        self.write_firmware_and_wait_for_download(Instances.APP,\n                                                  self.get_firmware_uri())\n\n        # Check /33629/0 result\n        self.assertEqual(UpdateResult.INITIAL,\n                         self.read_update_result(Instances.APP))\n\n        # Prepare package for /33629/1\n        self.FW_PKG_OPTS = {'magic': b'AJAY_TEE'}\n        self.provide_response_additional_img(content=DUMMY_FILE)\n\n        # Write /33629/0/1 (Package URI)\n        self.write_firmware_and_wait_for_download(Instances.TEE,\n                                                  self.get_firmware_uri())\n\n        # Check /33629/1 result\n        self.assertEqual(UpdateResult.INITIAL,\n                         self.read_update_result(Instances.TEE))\n\n        # Prepare package for /33629/2\n        self.FW_PKG_OPTS = {'magic': b'AJAYBOOT'}\n        self.provide_response_additional_img(content=DUMMY_FILE)\n\n        # Write /33629/0/1 (Package URI)\n        self.write_firmware_and_wait_for_download(Instances.BOOT,\n                                                  self.get_firmware_uri())\n\n        # Check /33629/2 result\n        self.assertEqual(UpdateResult.INITIAL,\n                         self.read_update_result(Instances.BOOT))\n\n        # Prepare package for /33629/3\n        self.FW_PKG_OPTS = {'magic': b'AJAYMODE'}\n        self.provide_response_additional_img(content=DUMMY_FILE)\n\n        # Write /33629/0/1 (Package URI)\n        self.write_firmware_and_wait_for_download(Instances.MODEM,\n                                                  self.get_firmware_uri())\n\n        # Check /33629/3 result\n        self.assertEqual(UpdateResult.INITIAL,\n                         self.read_update_result(Instances.MODEM))\n\n        self.execute_update_and_check_success(Instances.TEE)\n        self.execute_update_and_check_success(Instances.BOOT)\n        self.execute_update_and_check_success(Instances.MODEM)\n\n        # Execute /33629/0/2 (Update)\n        req = Lwm2mExecute(ResPath.AdvancedFirmwareUpdate[Instances.APP].Update)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n\n        # Demo delays its reset while APP update. Wait for it and terminate if reset not occurred.\n        self._terminate_demo()\n\n\nclass AdvancedFirmwareUpdateUriTestFourNotLinkedImagesAPPFirst(\n    AdvancedFirmwareUpdate.TestWithHttpServer):\n    def setUp(self):\n        super().setUp()\n        self.set_check_marker(False)\n        self.set_auto_deregister(True)\n        self.set_reset_machine(False)\n\n    def runTest(self):\n        # Prepare package for /33629/0\n        self.FW_PKG_OPTS = {'magic': b'AJAY_APP'}\n        self.provide_response_app_img(use_real_app=True)\n\n        # Write /33629/0/1 (Package URI)\n        self.write_firmware_and_wait_for_download(Instances.APP,\n                                                  self.get_firmware_uri())\n\n        # Check /33629/0 result\n        self.assertEqual(UpdateResult.INITIAL,\n                         self.read_update_result(Instances.APP))\n\n        # Prepare package for /33629/1\n        self.FW_PKG_OPTS = {'magic': b'AJAY_TEE'}\n        self.provide_response_additional_img(content=DUMMY_FILE)\n\n        # Write /33629/0/1 (Package URI)\n        self.write_firmware_and_wait_for_download(Instances.TEE,\n                                                  self.get_firmware_uri())\n\n        # Check /33629/1 result\n        self.assertEqual(UpdateResult.INITIAL,\n                         self.read_update_result(Instances.TEE))\n\n        # Prepare package for /33629/2\n        self.FW_PKG_OPTS = {'magic': b'AJAYBOOT'}\n        self.provide_response_additional_img(content=DUMMY_FILE)\n\n        # Write /33629/0/1 (Package URI)\n        self.write_firmware_and_wait_for_download(Instances.BOOT,\n                                                  self.get_firmware_uri())\n\n        # Check /33629/2 result\n        self.assertEqual(UpdateResult.INITIAL,\n                         self.read_update_result(Instances.BOOT))\n\n        # Prepare package for /33629/3\n        self.FW_PKG_OPTS = {'magic': b'AJAYMODE'}\n        self.provide_response_additional_img(content=DUMMY_FILE)\n\n        # Write /33629/0/1 (Package URI)\n        self.write_firmware_and_wait_for_download(Instances.MODEM,\n                                                  self.get_firmware_uri())\n\n        # Check /33629/3 result\n        self.assertEqual(UpdateResult.INITIAL,\n                         self.read_update_result(Instances.MODEM))\n\n        # Execute /33629/0/2 (Update)\n        req = Lwm2mExecute(ResPath.AdvancedFirmwareUpdate[Instances.APP].Update)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n\n        self.serv.reset()\n        self.assertDemoRegisters()\n        self.execute_update_and_check_success(Instances.TEE)\n        self.execute_update_and_check_success(Instances.BOOT)\n        self.execute_update_and_check_success(Instances.MODEM)\n\n\nclass AdvancedFirmwareUpdateTestLinkedTeeToApp(\n    AdvancedFirmwareUpdate.TestWithHttpServer):\n    def setUp(self):\n        super().setUp()\n        self.set_check_marker(False)\n        self.set_auto_deregister(True)\n        self.set_reset_machine(False)\n\n    def runTest(self):\n        # Prepare package for /33629/0\n        self.FW_PKG_OPTS = {'magic': b'AJAY_APP',\n                            'linked': [Instances.TEE]}\n        self.provide_response_app_img()\n\n        # Check /33629/0/16 (LinkedInstances), there should not be any linked instances\n        self.read_linked_and_check(Instances.APP, [])\n\n        # Write /33629/0/1 (Package URI)\n        self.write_firmware_and_wait_for_download(Instances.APP,\n                                                  self.get_firmware_uri())\n\n        # Check /33629/0 result and linked instances\n        self.assertEqual(UpdateResult.INITIAL,\n                         self.read_update_result(Instances.APP))\n        self.read_linked_and_check(Instances.APP, [(1, Objlink(33629, 1))])\n\n\nclass AdvancedFirmwareUpdateTestLinkedOthersToApp(\n    AdvancedFirmwareUpdate.TestWithHttpServer):\n    def setUp(self):\n        super().setUp()\n        self.set_check_marker(False)\n        self.set_auto_deregister(True)\n        self.set_reset_machine(False)\n\n    def runTest(self):\n        # Prepare package for /33629/0\n        self.FW_PKG_OPTS = {'magic': b'AJAY_APP',\n                            'linked': [Instances.TEE,\n                                       Instances.BOOT,\n                                       Instances.MODEM]}\n        self.provide_response_app_img()\n\n        # Check /33629/0/16 (LinkedInstances), there should not be any linked instances\n        self.read_linked_and_check(Instances.APP, [])\n\n        # Write /33629/0/1 (Package URI)\n        self.write_firmware_and_wait_for_download(Instances.APP,\n                                                  self.get_firmware_uri())\n\n        # Check /33629/0 result linked and conflicting instances\n        self.assertEqual(UpdateResult.INITIAL,\n                         self.read_update_result(Instances.APP))\n        self.read_linked_and_check(Instances.APP, [(1, Objlink(33629, 1)),\n                                                   (2, Objlink(33629, 2)),\n                                                   (3, Objlink(33629, 3)), ])\n        self.read_conflicting_and_check(Instances.APP, [(1, Objlink(33629, 1)),\n                                                        (2, Objlink(33629, 2)),\n                                                        (3, Objlink(33629, 3))])\n\n        # Try to Update /33629/0\n        # Execute /33629/0/2 (Update)\n        req = Lwm2mExecute(ResPath.AdvancedFirmwareUpdate[Instances.APP].Update)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n\n        self.wait_until_state_is(Instances.APP, UpdateState.DOWNLOADED)\n\n        # Check /33629/0 result\n        self.assertEqual(UpdateResult.DEPENDENCY_ERROR,\n                         self.read_update_result(Instances.APP))\n\n\nclass AdvancedFirmwareUpdateTestConflictingAppAndTee(\n    AdvancedFirmwareUpdate.TestWithHttpServer):\n    def setUp(self):\n        super().setUp()\n        self.set_check_marker(False)\n        self.set_auto_deregister(True)\n        self.set_reset_machine(False)\n\n    def runTest(self):\n        # Prepare package for /33629/0\n        self.FW_PKG_OPTS = {'magic': b'AJAY_APP',\n                            'linked': [Instances.TEE]}\n        self.provide_response_app_img()\n\n        # Check /33629/0/17, there should not be any conflicting instances\n        self.read_conflicting_and_check(Instances.APP, [])\n\n        # Write /33629/0/1 (Package URI)\n        self.write_firmware_and_wait_for_download(Instances.APP,\n                                                  self.get_firmware_uri())\n\n        # Check /33629/0 result and conflicting instances\n        self.assertEqual(UpdateResult.INITIAL,\n                         self.read_update_result(Instances.APP))\n        self.read_conflicting_and_check(Instances.APP, [(1, Objlink(33629, 1))])\n\n\nclass AdvancedFirmwareUpdateTestResolveConflictingAppAndTee(\n    AdvancedFirmwareUpdate.TestWithHttpServer):\n    def setUp(self):\n        super().setUp()\n        self.set_check_marker(False)\n        self.set_auto_deregister(True)\n        self.set_reset_machine(False)\n\n    def runTest(self):\n        # Prepare package for /33629/0\n        self.FW_PKG_OPTS = {'magic': b'AJAY_APP',\n                            'linked': [Instances.TEE]}\n        self.provide_response_app_img()\n\n        # Check /33629/0/17, there should not be any conflicting instances\n        self.read_conflicting_and_check(Instances.APP, [])\n\n        # Write /33629/0/1 (Package URI)\n        self.write_firmware_and_wait_for_download(Instances.APP,\n                                                  self.get_firmware_uri())\n\n        # Check /33629/0 result and conflicting instances\n        self.assertEqual(UpdateResult.INITIAL,\n                         self.read_update_result(Instances.APP))\n        self.read_conflicting_and_check(Instances.APP, [(1, Objlink(33629, 1))])\n\n        # Prepare package for /33629/1\n        self.FW_PKG_OPTS = {'magic': b'AJAY_TEE'}\n        self.provide_response_additional_img(content=DUMMY_FILE)\n\n        # Write /33629/1/1 (Package URI)\n        self.write_firmware_and_wait_for_download(Instances.TEE,\n                                                  self.get_firmware_uri())\n\n        # Check /33629/1 result and linked\n        self.assertEqual(UpdateResult.INITIAL,\n                         self.read_update_result(Instances.TEE))\n        self.read_linked_and_check(Instances.TEE, [])\n\n        # Check again /33629/0/17, conflicting instances should disappear\n        self.read_conflicting_and_check(Instances.APP, [])\n\n\nclass AdvancedFirmwareUpdateTestResolveConflictingAndUpdateTeeAndBoot(\n    AdvancedFirmwareUpdate.TestWithHttpServer):\n    def setUp(self):\n        super().setUp()\n        self.set_check_marker(False)\n        self.set_auto_deregister(True)\n        self.set_reset_machine(False)\n\n    def runTest(self):\n        # Prepare package for /33629/1\n        self.FW_PKG_OPTS = {'magic': b'AJAY_TEE',\n                            'linked': [Instances.BOOT]}\n        self.provide_response_additional_img(content=DUMMY_FILE)\n\n        # Check /33629/1/17, there should not be any conflicting instances\n        self.read_conflicting_and_check(Instances.TEE, [])\n\n        # Write /33629/1/1 (Package URI)\n        self.write_firmware_and_wait_for_download(Instances.TEE,\n                                                  self.get_firmware_uri())\n\n        # Check /33629/1 result and conflicting instances\n        self.assertEqual(UpdateResult.INITIAL,\n                         self.read_update_result(Instances.TEE))\n        self.read_conflicting_and_check(Instances.TEE, [(2, Objlink(33629, 2))])\n\n        # Prepare package for /33629/2\n        self.FW_PKG_OPTS = {'magic': b'AJAYBOOT',\n                            'linked': [Instances.TEE]}\n        self.provide_response_additional_img(content=DUMMY_FILE)\n\n        # Write /33629/2/1 (Package URI)\n        self.write_firmware_and_wait_for_download(Instances.BOOT,\n                                                  self.get_firmware_uri())\n\n        # Check /33629/2 result and linked\n        self.assertEqual(UpdateResult.INITIAL,\n                         self.read_update_result(Instances.BOOT))\n        self.read_linked_and_check(Instances.BOOT, [(1, Objlink(33629, 1))])\n\n        # Check again /33629/1/17, conflicting instances should disappear\n        self.read_conflicting_and_check(Instances.TEE, [])\n\n        # Update 33629/1 TEE\n        self.execute_update_and_check_success(Instances.TEE)\n\n        # Check /33629/2 BOOT state and result, which should also be updated already\n        self.assertEqual(UpdateState.IDLE, self.read_state(Instances.BOOT))\n        self.assertEqual(UpdateResult.SUCCESS,\n                         self.read_update_result(Instances.BOOT))\n\n        # Check linked and conflicting are cleared\n        self.read_linked_and_check(Instances.TEE, [])\n        self.read_conflicting_and_check(Instances.TEE, [])\n        self.read_linked_and_check(Instances.BOOT, [])\n        self.read_conflicting_and_check(Instances.BOOT, [])\n\n\nclass AdvancedFirmwareUpdateTestNoConflictWithDownloadedEarlier(\n    AdvancedFirmwareUpdate.TestWithHttpServer):\n    def setUp(self):\n        super().setUp()\n        self.set_check_marker(False)\n        self.set_auto_deregister(True)\n        self.set_reset_machine(False)\n\n    def runTest(self):\n        # Prepare package for /33629/1\n        self.FW_PKG_OPTS = {'magic': b'AJAY_TEE'}\n        self.provide_response_additional_img(content=DUMMY_FILE)\n\n        # Write /33629/0/1 (Package URI)\n        self.write_firmware_and_wait_for_download(Instances.TEE,\n                                                  self.get_firmware_uri())\n\n        # Check /33629/1 result and linked\n        self.assertEqual(UpdateResult.INITIAL,\n                         self.read_update_result(Instances.TEE))\n        self.read_linked_and_check(Instances.TEE, [])\n\n        # Prepare package for /33629/1\n        self.FW_PKG_OPTS = {'magic': b'AJAY_APP',\n                            'linked': [Instances.TEE]}\n        self.provide_response_app_img()\n\n        # Check /33629/0/17, there should not be any conflicting instances\n        self.read_conflicting_and_check(Instances.APP, [])\n\n        # Write /33629/0/1 (Package URI)\n        self.write_firmware_and_wait_for_download(Instances.APP,\n                                                  self.get_firmware_uri())\n\n        # Check /33629/0 result\n        self.assertEqual(UpdateResult.INITIAL,\n                         self.read_update_result(Instances.APP))\n\n        # Check again /33629/0/17, TEE already downloaded so should not be any conflict\n        self.read_conflicting_and_check(Instances.APP, [])\n\n\nclass AdvancedFirmwareUpdateTestFailedUpdate(\n    AdvancedFirmwareUpdate.TestWithHttpServer):\n    def setUp(self):\n        super().setUp()\n        self.set_check_marker(False)\n        self.set_auto_deregister(True)\n        self.set_reset_machine(False)\n\n    def runTest(self):\n        # Prepare package for /33629/1\n        self.FW_PKG_OPTS = {'magic': b'AJAY_TEE',\n                            'linked': [Instances.BOOT]}\n        self.provide_response_additional_img(content=DUMMY_FILE, overwrite_original_img=False)\n\n        # Check /33629/1/17, there should not be any conflicting instances\n        self.read_conflicting_and_check(Instances.TEE, [])\n\n        # Write /33629/1/1 (Package URI)\n        self.write_firmware_and_wait_for_download(Instances.TEE,\n                                                  self.get_firmware_uri())\n\n        # Check /33629/1 result and conflicting instances\n        self.assertEqual(UpdateResult.INITIAL,\n                         self.read_update_result(Instances.TEE))\n        self.read_conflicting_and_check(Instances.TEE, [(2, Objlink(33629, 2))])\n\n        # Prepare package for /33629/2\n        self.FW_PKG_OPTS = {'magic': b'AJAYBOOT',\n                            'linked': [Instances.TEE]}\n        self.provide_response_additional_img(content=DUMMY_FILE, overwrite_original_img=False)\n\n        # Write /33629/2/1 (Package URI)\n        self.write_firmware_and_wait_for_download(Instances.BOOT,\n                                                  self.get_firmware_uri())\n\n        # Check /33629/2 result and linked\n        self.assertEqual(UpdateResult.INITIAL,\n                         self.read_update_result(Instances.BOOT))\n        self.read_linked_and_check(Instances.BOOT, [(1, Objlink(33629, 1))])\n\n        # Check again /33629/1/17, conflicting instances should disappear\n        self.read_conflicting_and_check(Instances.TEE, [])\n\n        # Execute /33629/1/2 (Update)\n        req = Lwm2mExecute(ResPath.AdvancedFirmwareUpdate[Instances.TEE].Update)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n\n        # This execute is not going to make successful update it is forced by\n        # option 'prep_additional=False' while calling `provide_response`.\n        # It means that this test just not going to prepare proper image to compare with by demo.\n        self.wait_until_state_is(Instances.TEE, UpdateState.DOWNLOADED)\n\n        # Check /33629/1 result\n        self.assertEqual(UpdateResult.FAILED,\n                         self.read_update_result(Instances.TEE))\n\n        # Check /33629/2 state and result\n        self.assertEqual(UpdateState.DOWNLOADED,\n                         self.read_state(Instances.BOOT))\n        self.assertEqual(UpdateResult.FAILED,\n                         self.read_update_result(Instances.BOOT))\n\n\nclass AdvancedFirmwareUpdateTestUpdateBootWithLinkedTee(\n    AdvancedFirmwareUpdate.TestWithHttpServer):\n    def setUp(self):\n        super().setUp()\n        self.set_check_marker(False)\n        self.set_auto_deregister(True)\n        self.set_reset_machine(False)\n\n    def runTest(self):\n        # Prepare package for /33629/2\n        self.FW_PKG_OPTS = {'magic': b'AJAYBOOT',\n                            'linked': [Instances.TEE]}\n        self.provide_response_additional_img(content=DUMMY_FILE)\n\n        # Check /33629/2/17, there should not be any conflicting instances\n        self.read_conflicting_and_check(Instances.BOOT, [])\n\n        # Write /33629/2/1 (Package URI)\n        self.write_firmware_and_wait_for_download(Instances.BOOT,\n                                                  self.get_firmware_uri())\n\n        # Check /33629/2 result and conflicting instances\n        self.assertEqual(UpdateResult.INITIAL,\n                         self.read_update_result(Instances.BOOT))\n        self.read_conflicting_and_check(Instances.BOOT,\n                                        [(1, Objlink(33629, 1))])\n\n        # Prepare package for /33629/1\n        self.FW_PKG_OPTS = {'magic': b'AJAY_TEE'}\n        self.provide_response_additional_img(content=DUMMY_FILE)\n\n        # Write /33629/0/1 (Package URI)\n        self.write_firmware_and_wait_for_download(Instances.TEE,\n                                                  self.get_firmware_uri())\n\n        # Check /33629/1 result and linked\n        self.assertEqual(UpdateResult.INITIAL,\n                         self.read_update_result(Instances.TEE))\n        self.read_linked_and_check(Instances.TEE, [])\n\n        # Check again /33629/2 state, result and conflicting instances\n        self.assertEqual(UpdateState.DOWNLOADED,\n                         self.read_state(Instances.BOOT))\n        self.assertEqual(UpdateResult.INITIAL,\n                         self.read_update_result(Instances.BOOT))\n        self.read_conflicting_and_check(Instances.BOOT, [])\n\n        # Execute /33629/2/2 (Update)\n        req = Lwm2mExecute(\n            ResPath.AdvancedFirmwareUpdate[Instances.BOOT].Update)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n\n        self.wait_until_state_is(Instances.TEE, UpdateState.IDLE)\n\n        # Check /33629/1 state, result, linked and conflicting\n        self.assertEqual(UpdateState.IDLE, self.read_state(Instances.TEE))\n        self.assertEqual(UpdateResult.SUCCESS,\n                         self.read_update_result(Instances.TEE))\n        self.read_linked_and_check(Instances.TEE, [])\n        self.read_conflicting_and_check(Instances.TEE, [])\n\n        # Check /33629/2 state, result, linked and conflicting\n        self.assertEqual(UpdateState.IDLE, self.read_state(Instances.BOOT))\n        self.assertEqual(UpdateResult.SUCCESS,\n                         self.read_update_result(Instances.BOOT))\n        self.read_linked_and_check(Instances.BOOT, [])\n        self.read_conflicting_and_check(Instances.BOOT, [])\n\n\nclass AdvancedFirmwareUpdateTestSetConflictAfterCancelOfLinkedImage(\n    AdvancedFirmwareUpdate.TestWithHttpServer):\n    def setUp(self):\n        super().setUp()\n        self.set_check_marker(False)\n        self.set_auto_deregister(True)\n        self.set_reset_machine(False)\n\n    def runTest(self):\n        # Prepare package for /33629/0\n        self.FW_PKG_OPTS = {'magic': b'AJAY_APP',\n                            'linked': [Instances.TEE]}\n        self.provide_response_app_img()\n\n        # Check /33629/0/17, there should not be any conflicting instances\n        self.read_conflicting_and_check(Instances.APP, [])\n\n        # Write /33629/0/1 (Package URI)\n        self.write_firmware_and_wait_for_download(Instances.APP,\n                                                  self.get_firmware_uri())\n\n        # Check /33629/0 result and conflicting instances\n        self.assertEqual(UpdateResult.INITIAL,\n                         self.read_update_result(Instances.APP))\n        self.read_conflicting_and_check(Instances.APP, [(1, Objlink(33629, 1))])\n\n        # Prepare package for /33629/1\n        self.FW_PKG_OPTS = {'magic': b'AJAY_TEE'}\n        self.provide_response_additional_img(content=DUMMY_FILE)\n\n        # Write /33629/0/1 (Package URI)\n        self.write_firmware_and_wait_for_download(Instances.TEE,\n                                                  self.get_firmware_uri())\n\n        # Check /33629/1 result and linked\n        self.assertEqual(UpdateResult.INITIAL,\n                         self.read_update_result(Instances.TEE))\n        self.read_linked_and_check(Instances.TEE, [])\n\n        # Check again /33629/0/17, conflicting instances should disappear\n        self.read_conflicting_and_check(Instances.APP, [])\n\n        # Execute /33629/1/10 (Cancel)\n        req = Lwm2mExecute(ResPath.AdvancedFirmwareUpdate[Instances.TEE].Cancel)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n\n        # Check again /33629/0/17, conflicting instances should be there again\n        self.read_conflicting_and_check(Instances.APP, [(1, Objlink(33629, 1))])\n\n\nclass AdvancedFirmwareUpdatePackageTestWithMultiPackage(\n    AdvancedFirmwareUpdate.Test):\n    def setUp(self):\n        super().setUp()\n        self.set_check_marker(True)\n        self.set_auto_deregister(False)\n        self.set_reset_machine(False)\n\n    def runTest(self):\n        # Prepare package for /33629/0\n        self.FW_PKG_OPTS = {'magic': b'AJAY_APP'}\n        self.prepare_package_app_img()\n        app_pkg = self.PACKAGE\n\n        # Prepare package for /33629/1\n        self.FW_PKG_OPTS = {'magic': b'AJAY_TEE'}\n        self.prepare_package_additional_img(content=DUMMY_FILE)\n        tee_pkg = self.PACKAGE\n\n        # Prepare multiple package\n        multi_pkg = make_multiple_firmware_package([app_pkg, tee_pkg])\n\n        # Write multiple package to /33629/0/0 (Firmware)\n        req = Lwm2mWrite(ResPath.AdvancedFirmwareUpdate[Instances.APP].Package,\n                         multi_pkg,\n                         format=coap.ContentFormat.APPLICATION_OCTET_STREAM)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n\n        # Check /33629/0 state and result\n        self.assertEqual(UpdateState.DOWNLOADED, self.read_state(Instances.APP))\n        self.assertEqual(UpdateResult.INITIAL,\n                         self.read_update_result(Instances.APP))\n\n        # Check /33629/1 state and result\n        self.assertEqual(UpdateState.DOWNLOADED, self.read_state(Instances.TEE))\n        self.assertEqual(UpdateResult.INITIAL,\n                         self.read_update_result(Instances.TEE))\n\n        self.execute_update_and_check_success(Instances.TEE)\n\n        # Execute /33629/0/2 (Update)\n        req = Lwm2mExecute(ResPath.AdvancedFirmwareUpdate[Instances.APP].Update)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n\n        # Demo delays its reset while APP update. Wait for it and terminate if reset not occurred.\n        self._terminate_demo()\n\n\nclass AdvancedFirmwareUpdatePackageTestWithMultiPackageAllImages(\n    AdvancedFirmwareUpdate.Test):\n    def setUp(self):\n        super().setUp()\n        self.set_check_marker(True)\n        self.set_auto_deregister(False)\n        self.set_reset_machine(False)\n\n    def runTest(self):\n        # Prepare package for /33629/0\n        self.FW_PKG_OPTS = {'magic': b'AJAY_APP'}\n        self.prepare_package_app_img()\n        app_pkg = self.PACKAGE\n\n        # Prepare package for /33629/1\n        self.FW_PKG_OPTS = {'magic': b'AJAY_TEE',\n                            'linked': [Instances.BOOT, Instances.MODEM]}\n        self.prepare_package_additional_img(content=DUMMY_FILE)\n        tee_pkg = self.PACKAGE\n\n        # Prepare package for /33629/2\n        self.FW_PKG_OPTS = {'magic': b'AJAYBOOT'}\n        self.prepare_package_additional_img(content=DUMMY_FILE)\n        boot_pkg = self.PACKAGE\n\n        # Prepare package for /33629/3\n        self.FW_PKG_OPTS = {'magic': b'AJAYMODE'}\n        self.prepare_package_additional_img(content=DUMMY_FILE)\n        modem_pkg = self.PACKAGE\n\n        # Prepare multiple package\n        multi_pkg = make_multiple_firmware_package(\n            [app_pkg, tee_pkg, boot_pkg, modem_pkg])\n\n        # Write multiple package to /33629/3/0 (Firmware)\n        req = Lwm2mWrite(\n            ResPath.AdvancedFirmwareUpdate[Instances.MODEM].Package,\n            multi_pkg,\n            format=coap.ContentFormat.APPLICATION_OCTET_STREAM)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n\n        # Check /33629/0 state and result\n        self.assertEqual(UpdateState.DOWNLOADED, self.read_state(Instances.APP))\n        self.assertEqual(UpdateResult.INITIAL,\n                         self.read_update_result(Instances.APP))\n\n        # Check /33629/1 state and result\n        self.assertEqual(UpdateState.DOWNLOADED, self.read_state(Instances.TEE))\n        self.assertEqual(UpdateResult.INITIAL,\n                         self.read_update_result(Instances.TEE))\n\n        # Check /33629/2 state and result\n        self.assertEqual(UpdateState.DOWNLOADED,\n                         self.read_state(Instances.BOOT))\n        self.assertEqual(UpdateResult.INITIAL,\n                         self.read_update_result(Instances.BOOT))\n\n        # Check /33629/3 state and result\n        self.assertEqual(UpdateState.DOWNLOADED,\n                         self.read_state(Instances.MODEM))\n        self.assertEqual(UpdateResult.INITIAL,\n                         self.read_update_result(Instances.MODEM))\n\n        # Execute /33629/1/2 (Update)\n        req = Lwm2mExecute(ResPath.AdvancedFirmwareUpdate[Instances.TEE].Update)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n\n        self.wait_until_state_is(Instances.TEE, UpdateState.IDLE)\n\n        # Check /33629/1 result\n        self.assertEqual(UpdateResult.SUCCESS,\n                         self.read_update_result(Instances.TEE))\n\n        # Check /33629/2 state and result\n        self.assertEqual(UpdateState.IDLE, self.read_state(Instances.BOOT))\n        self.assertEqual(UpdateResult.SUCCESS,\n                         self.read_update_result(Instances.BOOT))\n\n        # Check /33629/3 state and result\n        self.assertEqual(UpdateState.IDLE, self.read_state(Instances.MODEM))\n        self.assertEqual(UpdateResult.SUCCESS,\n                         self.read_update_result(Instances.MODEM))\n\n        # Execute /33629/0/2 (Update)\n        req = Lwm2mExecute(ResPath.AdvancedFirmwareUpdate[Instances.APP].Update)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n\n        # Demo delays its reset while APP update. Wait for it and terminate if reset not occurred.\n        self._terminate_demo()\n\n\nclass AdvancedFirmwareUpdatePackageTestWithMultiPackageConflictingDownloads(\n    AdvancedFirmwareUpdate.Test):\n    def setUp(self):\n        super().setUp()\n        self.set_check_marker(False)\n        self.set_auto_deregister(True)\n        self.set_reset_machine(False)\n\n    def runTest(self):\n        # Prepare package for /33629/1\n        self.FW_PKG_OPTS = {'magic': b'AJAY_TEE'}\n        self.prepare_package_additional_img(content=DUMMY_FILE)\n\n        # Write multiple package to /33629/1/0 (Firmware)\n        req = Lwm2mWrite(ResPath.AdvancedFirmwareUpdate[Instances.TEE].Package,\n                         self.PACKAGE,\n                         format=coap.ContentFormat.APPLICATION_OCTET_STREAM)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n\n        # Check /33629/1 state and result\n        self.assertEqual(UpdateState.DOWNLOADED, self.read_state(Instances.TEE))\n        self.assertEqual(UpdateResult.INITIAL,\n                         self.read_update_result(Instances.TEE))\n\n        # Prepare package for /33629/0\n        self.FW_PKG_OPTS = {'magic': b'AJAY_APP'}\n        self.prepare_package_app_img()\n        app_pkg = self.PACKAGE\n\n        # Prepare package for /33629/1\n        self.FW_PKG_OPTS = {'magic': b'AJAY_TEE'}\n        self.prepare_package_additional_img(content=DUMMY_FILE)\n        tee_pkg = self.PACKAGE\n\n        # Prepare multiple package\n        multi_pkg = make_multiple_firmware_package([app_pkg, tee_pkg])\n\n        # Write multiple package to /33629/3/0 (Firmware)\n        req = Lwm2mWrite(ResPath.AdvancedFirmwareUpdate[Instances.APP].Package,\n                         multi_pkg,\n                         format=coap.ContentFormat.APPLICATION_OCTET_STREAM)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n\n        # Check /33629/0 state and result\n        self.assertEqual(UpdateState.IDLE, self.read_state(Instances.APP))\n        self.assertEqual(UpdateResult.CONFLICTING_STATE,\n                         self.read_update_result(Instances.APP))\n        self.read_conflicting_and_check(Instances.APP, [(1, Objlink(33629, 1))])\n\n        # Check /33629/1 state and result\n        self.assertEqual(UpdateState.DOWNLOADED, self.read_state(Instances.TEE))\n        self.assertEqual(UpdateResult.INITIAL,\n                         self.read_update_result(Instances.TEE))\n\n\nclass AdvancedFirmwareUpdateUriTestExplicitLinkedUpdate(\n    AdvancedFirmwareUpdate.TestWithHttpServer):\n\n    def setUp(self):\n        super().setUp()\n        self.set_check_marker(False)\n        self.set_auto_deregister(True)\n        self.set_reset_machine(False)\n\n    def runTest(self):\n        # Prepare package for /33629/0\n        self.FW_PKG_OPTS = {'magic': b'AJAY_APP', 'linked': [1]}\n        self.provide_response_app_img(use_real_app=True)\n\n        # Write /33629/0/1 (Package URI)\n        self.write_firmware_and_wait_for_download(Instances.APP,\n                                                  self.get_firmware_uri())\n\n        # Check /33629/0 result and linked\n        self.assertEqual(UpdateResult.INITIAL,\n                         self.read_update_result(Instances.APP))\n        self.read_linked_and_check(Instances.APP, [(1, Objlink(33629, 1))])\n\n        # Prepare package for /33629/1\n        self.FW_PKG_OPTS = {'magic': b'AJAY_TEE', 'linked': [0]}\n        self.provide_response_additional_img(content=DUMMY_FILE)\n\n        # Write /33629/1/1 (Package URI)\n        self.write_firmware_and_wait_for_download(Instances.TEE,\n                                                  self.get_firmware_uri())\n\n        # Check /33629/1 result and linked\n        self.assertEqual(UpdateResult.INITIAL,\n                         self.read_update_result(Instances.TEE))\n        self.read_linked_and_check(Instances.TEE, [(0, Objlink(33629, 0))])\n\n        # Prepare package for /33629/2\n        self.FW_PKG_OPTS = {'magic': b'AJAYBOOT'}\n        self.prepare_package_additional_img(content=DUMMY_FILE)\n\n        # Write /33629/2/0 (Firmware)\n        req = Lwm2mWrite(ResPath.AdvancedFirmwareUpdate[Instances.BOOT].Package,\n                         self.PACKAGE,\n                         format=coap.ContentFormat.APPLICATION_OCTET_STREAM)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n\n        # Check /33629/2 state and result\n        self.assertEqual(UpdateState.DOWNLOADED,\n                         self.read_state(Instances.BOOT))\n        self.assertEqual(UpdateResult.INITIAL,\n                         self.read_update_result(Instances.BOOT))\n\n        # Prepare package for /33629/3\n        self.FW_PKG_OPTS = {'magic': b'AJAYMODE'}\n        self.prepare_package_additional_img(content=DUMMY_FILE)\n\n        # Write /33629/3/0 (Firmware)\n        req = Lwm2mWrite(\n            ResPath.AdvancedFirmwareUpdate[Instances.MODEM].Package,\n            self.PACKAGE,\n            format=coap.ContentFormat.APPLICATION_OCTET_STREAM)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n\n        # Check /33629/3 state and result\n        self.assertEqual(UpdateState.DOWNLOADED,\n                         self.read_state(Instances.MODEM))\n        self.assertEqual(UpdateResult.INITIAL,\n                         self.read_update_result(Instances.MODEM))\n\n        # Execute /33629/0/2 (Update)\n        req = Lwm2mExecute(ResPath.AdvancedFirmwareUpdate[Instances.APP].Update,\n                           content=b'0=\\'</33629/1>,</33629/2>,</33629/3>\\'')\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n\n        self.serv.reset()\n        self.assertDemoRegisters()\n\n        # Check /33629/0 state and result\n        self.assertEqual(UpdateState.IDLE,\n                         self.read_state(Instances.APP))\n        self.assertEqual(UpdateResult.SUCCESS,\n                         self.read_update_result(Instances.APP))\n\n        # Check /33629/1 state and result\n        self.assertEqual(UpdateState.IDLE,\n                         self.read_state(Instances.TEE))\n        self.assertEqual(UpdateResult.SUCCESS,\n                         self.read_update_result(Instances.TEE))\n\n        # Check /33629/2 state and result\n        self.assertEqual(UpdateState.IDLE,\n                         self.read_state(Instances.BOOT))\n        self.assertEqual(UpdateResult.SUCCESS,\n                         self.read_update_result(Instances.BOOT))\n\n        # Check /33629/3 state and result\n        self.assertEqual(UpdateState.IDLE,\n                         self.read_state(Instances.MODEM))\n        self.assertEqual(UpdateResult.SUCCESS,\n                         self.read_update_result(Instances.MODEM))\n\n\nclass AdvancedFirmwareUpdateUriTestExplicitSinglePartitionUpdate(\n    AdvancedFirmwareUpdate.TestWithHttpServer):\n\n    def setUp(self):\n        super().setUp()\n        self.set_check_marker(False)\n        self.set_auto_deregister(True)\n        self.set_reset_machine(False)\n\n    def runTest(self):\n        # Prepare package for /33629/0\n        self.FW_PKG_OPTS = {'magic': b'AJAY_APP', 'linked': [1]}\n        self.provide_response_app_img(use_real_app=True)\n\n        # Write /33629/0/1 (Package URI)\n        self.write_firmware_and_wait_for_download(Instances.APP,\n                                                  self.get_firmware_uri())\n\n        # Check /33629/0 result and linked\n        self.assertEqual(UpdateResult.INITIAL,\n                         self.read_update_result(Instances.APP))\n        self.read_linked_and_check(Instances.APP, [(1, Objlink(33629, 1))])\n\n        # Prepare package for /33629/1\n        self.FW_PKG_OPTS = {'magic': b'AJAY_TEE', 'linked': [0]}\n        self.provide_response_additional_img(content=DUMMY_FILE)\n\n        # Write /33629/1/1 (Package URI)\n        self.write_firmware_and_wait_for_download(Instances.TEE,\n                                                  self.get_firmware_uri())\n\n        # Check /33629/1 result and linked\n        self.assertEqual(UpdateResult.INITIAL,\n                         self.read_update_result(Instances.TEE))\n        self.read_linked_and_check(Instances.TEE, [(0, Objlink(33629, 0))])\n\n        # Prepare package for /33629/2\n        self.FW_PKG_OPTS = {'magic': b'AJAYBOOT'}\n        self.prepare_package_additional_img(content=DUMMY_FILE)\n\n        # Write /33629/2/0 (Firmware)\n        req = Lwm2mWrite(ResPath.AdvancedFirmwareUpdate[Instances.BOOT].Package,\n                         self.PACKAGE,\n                         format=coap.ContentFormat.APPLICATION_OCTET_STREAM)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n\n        # Check /33629/2 state and result\n        self.assertEqual(UpdateState.DOWNLOADED,\n                         self.read_state(Instances.BOOT))\n        self.assertEqual(UpdateResult.INITIAL,\n                         self.read_update_result(Instances.BOOT))\n\n        # Prepare package for /33629/3\n        self.FW_PKG_OPTS = {'magic': b'AJAYMODE'}\n        self.prepare_package_additional_img(content=DUMMY_FILE)\n\n        # Write /33629/3/0 (Firmware)\n        req = Lwm2mWrite(\n            ResPath.AdvancedFirmwareUpdate[Instances.MODEM].Package,\n            self.PACKAGE,\n            format=coap.ContentFormat.APPLICATION_OCTET_STREAM)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n\n        # Check /33629/3 state and result\n        self.assertEqual(UpdateState.DOWNLOADED,\n                         self.read_state(Instances.MODEM))\n        self.assertEqual(UpdateResult.INITIAL,\n                         self.read_update_result(Instances.MODEM))\n\n        # Execute /33629/0/2 (Update)\n        req = Lwm2mExecute(ResPath.AdvancedFirmwareUpdate[Instances.APP].Update,\n                           content=b'0')\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n\n        self.serv.reset()\n        self.assertDemoRegisters()\n\n        # Check /33629/0 state and result\n        self.assertEqual(UpdateState.IDLE,\n                         self.read_state(Instances.APP))\n        self.assertEqual(UpdateResult.SUCCESS,\n                         self.read_update_result(Instances.APP))\n\n        # Check /33629/1 state and result\n        self.assertEqual(UpdateState.DOWNLOADED,\n                         self.read_state(Instances.TEE))\n        self.assertEqual(UpdateResult.INITIAL,\n                         self.read_update_result(Instances.TEE))\n\n\nclass AdvancedFirmwareUpdateUriTestCheckPkgVersion(\n    AdvancedFirmwareUpdate.TestWithHttpServer):\n\n    def setUp(self):\n        super().setUp()\n        self.set_check_marker(False)\n        self.set_auto_deregister(True)\n        self.set_reset_machine(False)\n\n    def runTest(self):\n        # Prepare package for /33629/0\n        self.FW_PKG_OPTS = {'magic': b'AJAY_APP', 'pkg_version': b'2.0.1'}\n        self.provide_response_app_img()\n\n        # Write /33629/0/1 (Package URI)\n        self.write_firmware_and_wait_for_download(Instances.APP,\n                                                  self.get_firmware_uri())\n\n        # Check /33629/0 result\n        self.assertEqual(UpdateResult.INITIAL,\n                         self.read_update_result(Instances.APP))\n\n        # Check /33629/7 pkg_version\n        req = Lwm2mRead(\n            ResPath.AdvancedFirmwareUpdate[Instances.APP].PackageVersion)\n        self.serv.send(req)\n        res = self.serv.recv()\n        self.assertMsgEqual(Lwm2mContent.matching(req)(), res)\n        self.assertEqual(res.content, b'2.0.1')\n\n\nclass AdvancedFirmwareUpdateVersionConflictTest(\n    AdvancedFirmwareUpdate.TestWithHttpServer):\n\n    def setUp(self):\n        super().setUp()\n        self.set_check_marker(False)\n        self.set_auto_deregister(True)\n        self.set_reset_machine(False)\n\n    def runTest(self):\n        # Prepare package for /33629/0\n        self.FW_PKG_OPTS = {'magic': b'AJAY_APP', 'pkg_version': b'2.0.1'}\n        self.provide_response_app_img()\n\n        # Write /33629/0/1 (Package URI)\n        self.write_firmware_and_wait_for_download(Instances.APP,\n                                                  self.get_firmware_uri())\n\n        # Check /33629/0 result\n        self.assertEqual(UpdateResult.INITIAL,\n                         self.read_update_result(Instances.APP))\n\n        # In demo there is requirement that major version of TEE has to be equal or above new version of APP\n        # There should be conflicting instance\n        self.read_conflicting_and_check(Instances.APP, [(1, Objlink(33629, 1))])\n\n        # Execute /33629/0/2 (Update)\n        req = Lwm2mExecute(ResPath.AdvancedFirmwareUpdate[Instances.APP].Update)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n\n        self.wait_until_state_is(Instances.APP, UpdateState.DOWNLOADED)\n\n        # Check /33629/0 result\n        self.assertEqual(UpdateResult.DEPENDENCY_ERROR,\n                         self.read_update_result(Instances.APP))\n\n        # Prepare package for /33629/1\n        self.FW_PKG_OPTS = {'magic': b'AJAY_TEE', 'pkg_version': b'2.0.1'}\n        self.provide_response_additional_img(content=DUMMY_FILE)\n\n        # Write /33629/1/1 (Package URI)\n        self.write_firmware_and_wait_for_download(Instances.TEE,\n                                                  self.get_firmware_uri())\n\n        self.execute_update_and_check_success(Instances.TEE)\n\n        # Check again conflicting, it should disappear\n        self.read_conflicting_and_check(Instances.APP, [])\n\n\nclass AdvancedFirmwareUpdateQueueParallelPull(AdvancedFirmwareUpdate.TestWithCoapServer):\n    def setUp(self, coap_server=None, *args, **kwargs):\n        super().setUp(coap_server=[None, None], *args, **kwargs)\n\n    def runTest(self):\n        # Prepare package for /33629/0\n        self.FW_PKG_OPTS = {'magic': b'AJAY_APP'}\n        self.prepare_package_additional_img(content=DUMMY_LONG_FILE)\n        with self.get_file_server(serv=0) as file_server:\n            file_server.set_resource('/firmwareAPP',\n                                     self.PACKAGE)\n            fw_uri = file_server.get_resource_uri('/firmwareAPP')\n\n        # Write /33629/inst/1 (Firmware URI)\n        req1 = Lwm2mWrite(ResPath.AdvancedFirmwareUpdate[Instances.APP].PackageURI,\n                          fw_uri)\n        self.serv.send(req1)\n\n        # Prepare package for /33629/1\n        self.FW_PKG_OPTS = {'magic': b'AJAY_TEE'}\n        self.prepare_package_additional_img(content=DUMMY_FILE)\n        with self.get_file_server(serv=1) as file_server:\n            file_server.set_resource('/firmwareTEE',\n                                     self.PACKAGE)\n            fw_uri = file_server.get_resource_uri('/firmwareTEE')\n\n        # Write /33629/inst/1 (Firmware URI)\n        req2 = Lwm2mWrite(ResPath.AdvancedFirmwareUpdate[Instances.TEE].PackageURI,\n                          fw_uri)\n        self.serv.send(req2)\n\n        # This case finally tests if two strings occur one after another to ensure that\n        # download schedule for TEE is done just after the APP finish downloading\n        if self.read_log_until_match(regex=re.escape(b'instance /33629/0 downloaded successfully'),\n                                     timeout_s=15) is None:\n            raise self.failureException(\n                'string not found')\n\n        if self.read_log_until_match(regex=re.escape(b'download scheduled: ' + fw_uri.encode()),\n                                     timeout_s=3) is None:\n            raise self.failureException(\n                'string not found')\n\n        # There should be two request already received as we didn't check it before, to not wait for client\n        self.assertMsgEqual(Lwm2mChanged.matching(req1)(),\n                            self.serv.recv())\n        self.assertMsgEqual(Lwm2mChanged.matching(req2)(),\n                            self.serv.recv())\n\n        # Make sure that queued download finished properly\n        self.wait_until_state_is(Instances.TEE, UpdateState.DOWNLOADED)\n\n\nclass AdvancedFirmwareUpdateRejectPushWhilePull(AdvancedFirmwareUpdate.TestWithCoapServer):\n    def runTest(self):\n        # Prepare package for /33629/0\n        self.FW_PKG_OPTS = {'magic': b'AJAY_APP'}\n        self.prepare_package_additional_img(content=DUMMY_LONG_FILE)\n        with self.file_server as file_server:\n            file_server.set_resource('/firmwareAPP',\n                                     self.PACKAGE)\n            fw_uri = file_server.get_resource_uri('/firmwareAPP')\n\n        # Write /33629/inst/1 (Firmware URI)\n        req1 = Lwm2mWrite(ResPath.AdvancedFirmwareUpdate[Instances.APP].PackageURI,\n                          fw_uri)\n        self.serv.send(req1)\n\n        # Prepare package for /33629/1\n        self.FW_PKG_OPTS = {'magic': b'AJAY_TEE'}\n        self.prepare_package_additional_img(content=DUMMY_FILE)\n\n        # Write /33629/3/0 (Firmware)\n        req2 = Lwm2mWrite(\n            ResPath.AdvancedFirmwareUpdate[Instances.MODEM].Package,\n            self.PACKAGE,\n            format=coap.ContentFormat.APPLICATION_OCTET_STREAM)\n        self.serv.send(req2)\n\n        # There should be request already received as we didn't check it before, to not wait for client\n        self.assertMsgEqual(Lwm2mChanged.matching(req1)(),\n                            self.serv.recv())\n\n        # There is pull ongoing on another instance so push should return bad request\n        expected_res = Lwm2mChanged.matching(req2)()\n        expected_res.code = coap.Code.RES_METHOD_NOT_ALLOWED\n        self.assertMsgEqual(expected_res, self.serv.recv())\n\n        # Make sure that ongoing download is finished properly\n        self.wait_until_state_is(Instances.APP, UpdateState.DOWNLOADED)\n\n\nclass AdvancedFirmwareUpdateHandleTooManyPulls(AdvancedFirmwareUpdate.TestWithCoapServer):\n    def setUp(self, coap_server=None, *args, **kwargs):\n        super().setUp(coap_server=[None, None], *args, **kwargs)\n\n    def runTest(self):\n        # Prepare package for /33629/0\n        self.FW_PKG_OPTS = {'magic': b'AJAY_APP'}\n        self.prepare_package_additional_img(content=DUMMY_LONG_FILE)\n        with self.get_file_server(serv=0) as file_server:\n            file_server.set_resource('/firmwareAPP',\n                                     self.PACKAGE)\n            fw_uri = file_server.get_resource_uri('/firmwareAPP')\n\n        # Write /33629/inst/0 (Firmware URI)\n        req1 = Lwm2mWrite(ResPath.AdvancedFirmwareUpdate[Instances.APP].PackageURI,\n                          fw_uri)\n        self.serv.send(req1)\n\n        # Prepare package for /33629/1\n        self.FW_PKG_OPTS = {'magic': b'AJAY_TEE'}\n        self.prepare_package_additional_img(content=DUMMY_FILE)\n        with self.get_file_server(serv=1) as file_server:\n            file_server.set_resource('/firmwareTEE',\n                                     self.PACKAGE)\n            fw_uri = file_server.get_resource_uri('/firmwareTEE')\n\n        # Write /33629/inst/1 (Firmware URI)\n        req2 = Lwm2mWrite(ResPath.AdvancedFirmwareUpdate[Instances.TEE].PackageURI,\n                          fw_uri)\n        self.serv.send(req2)\n\n        # Write /33629/inst/1 (Firmware URI)\n        req3 = Lwm2mWrite(ResPath.AdvancedFirmwareUpdate[Instances.TEE].PackageURI,\n                          fw_uri)\n        self.serv.send(req3)\n\n        # There should be three requests already received as we didn't check it before, to not wait for client\n        self.assertMsgEqual(Lwm2mChanged.matching(req1)(),\n                            self.serv.recv())\n        self.assertMsgEqual(Lwm2mChanged.matching(req2)(),\n                            self.serv.recv())\n\n        # There is pull already ongoing so second one should return bad request\n        expected_res = Lwm2mChanged.matching(req3)()\n        expected_res.code = coap.Code.RES_BAD_REQUEST\n        self.assertMsgEqual(expected_res, self.serv.recv())\n\n        # Both instances still should end with DOWNLOADED state\n        self.wait_until_state_is(Instances.APP, UpdateState.DOWNLOADED)\n        self.wait_until_state_is(Instances.TEE, UpdateState.DOWNLOADED)\n\n\nclass AdvancedFirmwareUpdateHandleTooManyPullsWithSecureConnection(AdvancedFirmwareUpdate.TestWithCoapServer,\n                                                                   test_suite.Lwm2mDtlsSingleServerTest):\n    def setUp(self, coap_server=None, *args, **kwargs):\n        dtlserv = [coap.DtlsServer(psk_key=self.PSK_KEY, psk_identity=self.PSK_IDENTITY),\n                   coap.DtlsServer(psk_key=self.PSK_KEY, psk_identity=self.PSK_IDENTITY)]\n        super().setUp(coap_server=dtlserv, *args, **kwargs)\n\n    def runTest(self):\n        # Prepare package for /33629/0\n        self.FW_PKG_OPTS = {'magic': b'AJAY_APP'}\n        self.prepare_package_additional_img(content=DUMMY_FILE)\n        with self.get_file_server(serv=0) as file_server:\n            file_server.set_resource('/firmwareAPP',\n                                     self.PACKAGE)\n            fw_uri = file_server.get_resource_uri('/firmwareAPP')\n\n        # Write /33629/inst/0 (Firmware URI)\n        req1 = Lwm2mWrite(ResPath.AdvancedFirmwareUpdate[Instances.APP].PackageURI,\n                          fw_uri)\n        self.serv.send(req1)\n\n        # Prepare package for /33629/1\n        self.FW_PKG_OPTS = {'magic': b'AJAY_TEE'}\n        self.prepare_package_additional_img(content=DUMMY_FILE)\n        with self.get_file_server(serv=1) as file_server:\n            file_server.set_resource('/firmwareTEE',\n                                     self.PACKAGE)\n            fw_uri = file_server.get_resource_uri('/firmwareTEE')\n\n        # Write /33629/inst/1 (Firmware URI)\n        req2 = Lwm2mWrite(ResPath.AdvancedFirmwareUpdate[Instances.TEE].PackageURI,\n                          fw_uri)\n        self.serv.send(req2)\n\n        # Write /33629/inst/1 (Firmware URI)\n        req3 = Lwm2mWrite(ResPath.AdvancedFirmwareUpdate[Instances.TEE].PackageURI,\n                          fw_uri)\n        self.serv.send(req3)\n\n        # There should be three requests already received as we didn't check it before, to not wait for client\n        self.assertMsgEqual(Lwm2mChanged.matching(req1)(),\n                            self.serv.recv())\n        self.assertMsgEqual(Lwm2mChanged.matching(req2)(),\n                            self.serv.recv())\n\n        # There is pull already ongoing so second one should return bad request\n        expected_res = Lwm2mChanged.matching(req3)()\n        expected_res.code = coap.Code.RES_BAD_REQUEST\n        self.assertMsgEqual(expected_res, self.serv.recv())\n\n        # Both instances still should end with DOWNLOADED state\n        self.wait_until_state_is(Instances.APP, UpdateState.DOWNLOADED)\n        self.wait_until_state_is(Instances.TEE, UpdateState.DOWNLOADED)\n\n\nclass AdvancedFirmwareUpdateForceAppToUpdateFirstAndCheckProperStateOfAdditionalImgAfterReboot(\n    AdvancedFirmwareUpdate.TestWithHttpServer):\n    def setUp(self):\n        super().setUp()\n        self.set_check_marker(False)\n        self.set_auto_deregister(True)\n        self.set_reset_machine(False)\n\n    def runTest(self):\n        # Prepare package for /33629/0\n        self.FW_PKG_OPTS = {'magic': b'AJAY_APP'}\n        self.provide_response_app_img(use_real_app=True)\n\n        # Write /33629/0/1 (Package URI)\n        self.write_firmware_and_wait_for_download(Instances.APP,\n                                                  self.get_firmware_uri())\n\n        # Check /33629/0 state and result\n        self.assertEqual(UpdateState.DOWNLOADED, self.read_state(Instances.APP))\n        self.assertEqual(UpdateResult.INITIAL,\n                         self.read_update_result(Instances.APP))\n\n        # Prepare package for /33629/1\n        self.FW_PKG_OPTS = {'magic': b'AJAY_TEE'}\n        self.prepare_package_additional_img(content=DUMMY_FILE)\n\n        # Write /33629/1/0 (Firmware)\n        req = Lwm2mWrite(ResPath.AdvancedFirmwareUpdate[Instances.TEE].Package,\n                         self.PACKAGE,\n                         format=coap.ContentFormat.APPLICATION_OCTET_STREAM)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n\n        # Check /33629/1 state and result\n        self.assertEqual(UpdateState.DOWNLOADED, self.read_state(Instances.TEE))\n        self.assertEqual(UpdateResult.INITIAL,\n                         self.read_update_result(Instances.TEE))\n\n        # Execute /33629/1/2 (Update)\n        req = Lwm2mExecute(ResPath.AdvancedFirmwareUpdate[Instances.TEE].Update,\n                           content=b'0=\\'</33629/0>\\'')\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n\n        # Demo should reboot, it needs new server\n        self.serv.reset()\n        self.assertDemoRegisters()\n\n        # After init state should be IDLE in both instances\n        self.wait_until_state_is(Instances.APP, UpdateState.IDLE)\n        self.wait_until_state_is(Instances.TEE, UpdateState.IDLE)\n\n        # Check both results\n        self.assertEqual(UpdateResult.SUCCESS,\n                         self.read_update_result(Instances.APP))\n        self.assertEqual(UpdateResult.SUCCESS,\n                         self.read_update_result(Instances.TEE))\n\n\nclass AdvancedFirmwareUpdateCancelWhileDownloadQueued(AdvancedFirmwareUpdate.TestWithCoapServer):\n    def setUp(self, coap_server=None, *args, **kwargs):\n        super().setUp(coap_server=[None, None], *args, **kwargs)\n\n    def runTest(self):\n        # Prepare package for /33629/0\n        self.FW_PKG_OPTS = {'magic': b'AJAY_APP'}\n        self.prepare_package_app_img(use_real_app=True)\n        with self.get_file_server(serv=0) as file_server:\n            file_server.set_resource('/firmwareAPP',\n                                     self.PACKAGE)\n            fw_uri1 = file_server.get_resource_uri('/firmwareAPP')\n\n        # Prepare package for /33629/1\n        self.FW_PKG_OPTS = {'magic': b'AJAY_TEE'}\n        self.prepare_package_additional_img(content=DUMMY_FILE)\n        with self.get_file_server(serv=1) as file_server:\n            file_server.set_resource('/firmwareTEE',\n                                     self.PACKAGE)\n            fw_uri2 = file_server.get_resource_uri('/firmwareTEE')\n\n        # Write /33629/0/1 (Firmware URI)\n        req1 = Lwm2mWrite(ResPath.AdvancedFirmwareUpdate[Instances.APP].PackageURI,\n                          fw_uri1)\n        self.serv.send(req1)\n\n        # Write /33629/1/1 (Firmware URI)\n        req2 = Lwm2mWrite(ResPath.AdvancedFirmwareUpdate[Instances.TEE].PackageURI,\n                          fw_uri2)\n        self.serv.send(req2)\n\n        # There should be two request already received\n        self.assertMsgEqual(Lwm2mChanged.matching(req1)(),\n                            self.serv.recv())\n        self.assertMsgEqual(Lwm2mChanged.matching(req2)(),\n                            self.serv.recv())\n\n        # Wait for download to start\n        self.wait_until_state_is(Instances.APP, UpdateState.DOWNLOADING)\n        # TEE URI is going to be queued but its state should be DOWNLOADING as well\n        self.wait_until_state_is(Instances.TEE, UpdateState.DOWNLOADING)\n\n        # Execute /33629/0/10 (Cancel)\n        req1 = Lwm2mExecute(ResPath.AdvancedFirmwareUpdate[Instances.APP].Cancel)\n        self.serv.send(req1)\n\n        # Execute /33629/1/10 (Cancel)\n        req2 = Lwm2mExecute(ResPath.AdvancedFirmwareUpdate[Instances.TEE].Cancel)\n        self.serv.send(req2)\n\n        # Check APP instance abort\n        if self.read_log_until_match(regex=re.escape(b'Aborted ongoing download for instance 0'),\n                                     timeout_s=5) is None:\n            raise self.failureException(\n                'string not found')\n\n        # Download for instance TEE should start then\n        if self.read_log_until_match(regex=re.escape(b'Scheduled download for instance 1'),\n                                     timeout_s=5) is None:\n            raise self.failureException(\n                'string not found')\n\n        # Check TEE instance abort\n        if self.read_log_until_match(regex=re.escape(b'Aborted ongoing download for instance 1'),\n                                     timeout_s=5) is None:\n            raise self.failureException(\n                'string not found')\n\n        # There should be two request already received\n        self.assertMsgEqual(Lwm2mChanged.matching(req1)(),\n                            self.serv.recv())\n        self.assertMsgEqual(Lwm2mChanged.matching(req2)(),\n                            self.serv.recv())\n\n\n        # Check states and results\n        self.assertEqual(UpdateState.IDLE, self.read_state(Instances.APP))\n        self.assertEqual(UpdateResult.CANCELLED,\n                         self.read_update_result(Instances.APP))\n        self.assertEqual(UpdateState.IDLE, self.read_state(Instances.TEE))\n        self.assertEqual(UpdateResult.CANCELLED,\n                         self.read_update_result(Instances.TEE))\n\n\nclass AdvancedFirmwareUpdateCancelCurrentDownloadAndLeaveSecondOne(AdvancedFirmwareUpdate.TestWithCoapServer):\n    def setUp(self, coap_server=None, *args, **kwargs):\n        super().setUp(coap_server=[None, None], *args, **kwargs)\n\n    def runTest(self):\n        # Prepare package for /33629/0\n        self.FW_PKG_OPTS = {'magic': b'AJAY_APP'}\n        self.prepare_package_app_img(use_real_app=True)\n        with self.get_file_server(serv=0) as file_server:\n            file_server.set_resource('/firmwareAPP',\n                                     self.PACKAGE)\n            fw_uri1 = file_server.get_resource_uri('/firmwareAPP')\n\n        # Prepare package for /33629/1\n        self.FW_PKG_OPTS = {'magic': b'AJAY_TEE'}\n        self.prepare_package_additional_img(content=DUMMY_FILE)\n        with self.get_file_server(serv=1) as file_server:\n            file_server.set_resource('/firmwareTEE',\n                                     self.PACKAGE)\n            fw_uri2 = file_server.get_resource_uri('/firmwareTEE')\n\n        # Write /33629/0/1 (Firmware URI)\n        req1 = Lwm2mWrite(ResPath.AdvancedFirmwareUpdate[Instances.APP].PackageURI,\n                          fw_uri1)\n        self.serv.send(req1)\n\n        # Write /33629/1/1 (Firmware URI)\n        req2 = Lwm2mWrite(ResPath.AdvancedFirmwareUpdate[Instances.TEE].PackageURI,\n                          fw_uri2)\n        self.serv.send(req2)\n\n        # There should be two request already received\n        self.assertMsgEqual(Lwm2mChanged.matching(req1)(),\n                            self.serv.recv())\n        self.assertMsgEqual(Lwm2mChanged.matching(req2)(),\n                            self.serv.recv())\n\n        # Wait for download to start\n        self.wait_until_state_is(Instances.APP, UpdateState.DOWNLOADING)\n        # TEE URI is going to be queued but its state should be DOWNLOADING as well\n        self.wait_until_state_is(Instances.TEE, UpdateState.DOWNLOADING)\n\n        # Execute /33629/0/10 (Cancel)\n        req = Lwm2mExecute(ResPath.AdvancedFirmwareUpdate[Instances.APP].Cancel)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n\n        # Check states and results\n        self.assertEqual(UpdateState.IDLE, self.read_state(Instances.APP))\n        self.assertEqual(UpdateResult.CANCELLED,\n                         self.read_update_result(Instances.APP))\n        self.wait_until_state_is(Instances.TEE, UpdateState.DOWNLOADED)\n        self.assertEqual(UpdateResult.INITIAL,\n                         self.read_update_result(Instances.TEE))\n\n\nclass AdvancedFirmwareUpdateHttpRequestTimeoutTest(AdvancedFirmwareUpdate.TestWithPartialDownload,\n                                                   AdvancedFirmwareUpdate.TestWithHttpServer):\n    CHUNK_SIZE = 500\n    RESPONSE_DELAY = 0.5\n    TCP_REQUEST_TIMEOUT = 5\n\n    def setUp(self):\n        super().setUp(\n            extra_cmdline_args=['--afu-tcp-request-timeout', str(self.TCP_REQUEST_TIMEOUT)])\n\n    def runTest(self):\n        self.provide_response()\n\n        # Write /33629/0/1 (Firmware URI)\n        req = Lwm2mWrite(ResPath.AdvancedFirmwareUpdate[Instances.APP].PackageURI,\n                         self.get_firmware_uri())\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.serv.recv())\n\n        self.wait_for_half_download()\n        # Change RESPONSE_DELAY so that the server stops responding\n        self.RESPONSE_DELAY = self.TCP_REQUEST_TIMEOUT + 5\n\n        half_download_time = time.time()\n        self.wait_until_state_is(Instances.APP, UpdateState.IDLE,\n                                 timeout_s=self.TCP_REQUEST_TIMEOUT + 5)\n        fail_time = time.time()\n        self.assertEqual(self.read_update_result(Instances.APP), UpdateResult.CONNECTION_LOST)\n\n        self.assertAlmostEqual(fail_time, half_download_time + self.TCP_REQUEST_TIMEOUT, delta=1.5)\n\n\nclass AdvancedFirmwareUpdateHttpRequestTimeoutTest20sec(\n    AdvancedFirmwareUpdateHttpRequestTimeoutTest):\n    TCP_REQUEST_TIMEOUT = 20\n"
  },
  {
    "path": "tests/integration/suites/default/async.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nfrom framework.lwm2m_test import *\n\n\nclass DmChangeDuringRegistration(test_suite.Lwm2mTest):\n    def setUp(self):\n        super().setUp(servers=2, num_servers_passed=1, auto_register=False)\n\n    def runTest(self):\n        register = self.assertDemoRegisters(self.servers[0], respond=False)\n        self.assertTrue(register.content.startswith(b'</1/1>,</2>'))\n\n        self.communicate(\"add-server coap://127.0.0.1:%d\" % self.servers[1].get_listen_port())\n        self.assertDemoRegisters(self.servers[1])\n\n        register = self.assertDemoRegisters(self.servers[0], respond=False)\n        self.assertTrue(register.content.startswith(b'</1/1>,</1/2>,</2>'))\n        self.servers[0].send(\n            Lwm2mCreated.matching(register)(location=self.DEFAULT_REGISTER_ENDPOINT))\n\n\nclass NoDmChangeDuringRegistration(test_suite.Lwm2mSingleServerTest):\n    def setUp(self):\n        super().setUp(auto_register=False)\n\n    def runTest(self):\n        register1 = self.assertDemoRegisters(respond=False)\n\n        # This will schedule a reload, which should be a no-op\n        self.communicate('exit-offline')\n\n        register2 = self.assertDemoRegisters()\n        # assert that what we received is a retransmission, not a new register attempt\n        self.assertMsgEqual(register1, register2)\n\n\nclass QueueModeChangeDuringRegistration(test_suite.Lwm2mSingleServerTest):\n    def setUp(self):\n        super().setUp(auto_register=False, maximum_version='1.1')\n\n    def runTest(self):\n        register1 = self.assertDemoRegisters(version='1.1', respond=False)\n\n        self.communicate('set-queue-mode-preference PREFER_QUEUE_MODE')\n        # This will schedule a reload\n        self.communicate('exit-offline')\n\n        register2 = self.assertDemoRegisters(version='1.1', respond=False, lwm2m11_queue_mode=True)\n        self.assertNotEqual(register1.token, register2.token)\n\n        # This will schedule another reload, which should be a no-op\n        self.communicate('exit-offline')\n\n        register3 = self.assertDemoRegisters(version='1.1', lwm2m11_queue_mode=True)\n        # assert that what we received is a retransmission, not a new register attempt\n        self.assertMsgEqual(register2, register3)\n\n\nclass DmChangeDuringUpdate(test_suite.Lwm2mTest):\n    def setUp(self):\n        super().setUp(servers=2, num_servers_passed=1)\n\n    def runTest(self):\n        self.communicate('send-update')\n\n        update = self.assertDemoUpdatesRegistration(self.servers[0], respond=False)\n        self.assertEqual(update.content, b'')\n\n        self.communicate(\"add-server coap://127.0.0.1:%d\" % self.servers[1].get_listen_port())\n        self.assertDemoRegisters(self.servers[1])\n\n        update = self.assertDemoUpdatesRegistration(self.servers[0], content=ANY, respond=False)\n        self.assertTrue(update.content.startswith(b'</1/1>,</1/2>,</2>'))\n        self.servers[0].send(Lwm2mChanged.matching(update)())\n\n\nclass OfflineModeDuringRegistration(test_suite.Lwm2mSingleServerTest):\n    def setUp(self):\n        super().setUp(auto_register=False)\n\n    def runTest(self):\n        register1 = self.assertDemoRegisters(respond=False)\n\n        self.communicate('enter-offline')\n\n        # Register shall not be retried\n        with self.assertRaises(socket.timeout):\n            self.serv.recv(timeout_s=5)\n\n        self.communicate('exit-offline')\n\n        # Register shall be retried immediately, with different ID/token\n        register2 = self.assertDemoRegisters(timeout_s=1, respond=False)\n        self.assertNotEqual(register1, register2)\n        self.serv.send(Lwm2mCreated.matching(register2)(location=self.DEFAULT_REGISTER_ENDPOINT))\n\n\nclass OfflineModeDuringUpdate(test_suite.Lwm2mDtlsSingleServerTest):\n    def runTest(self):\n        self.communicate('send-update')\n\n        update1 = self.assertDemoUpdatesRegistration(respond=False)\n\n        self.communicate('enter-offline')\n\n        # Update shall not be retried\n        with self.assertRaises(socket.timeout):\n            self.serv.recv(timeout_s=5)\n\n        self.communicate('exit-offline')\n\n        # Update shall be retried immediately, with different ID/token\n        self.assertDtlsReconnect(timeout_s=1)\n        update2 = self.assertDemoUpdatesRegistration(timeout_s=1, respond=False)\n        self.assertNotEqual(update1, update2)\n        self.serv.send(Lwm2mChanged.matching(update2)())\n\n\nclass OfflineModeDuringUpdateAndExpiringLifetime(test_suite.Lwm2mDtlsSingleServerTest):\n    LIFETIME = 5\n\n    def setUp(self):\n        super().setUp(lifetime=self.LIFETIME)\n\n    def runTest(self):\n        update = self.assertDemoUpdatesRegistration(timeout_s=self.LIFETIME, respond=False)\n\n        self.communicate('enter-offline')\n\n        # Neither Update retry nor Register shall be sent\n        with self.assertRaises(socket.timeout):\n            self.serv.recv(timeout_s=self.LIFETIME)\n\n        self.communicate('exit-offline')\n\n        # Register shall be attempted immediately\n        self.assertDtlsReconnect(timeout_s=1)\n        self.assertDemoRegisters(timeout_s=1, lifetime=self.LIFETIME)\n\n\nclass RegisterAttemptDuringOfflineMode(test_suite.Lwm2mTest):\n    def setUp(self):\n        super().setUp(servers=2, num_servers_passed=1)\n\n    def runTest(self):\n        self.communicate('enter-offline')\n        self.communicate(\"add-server coap://127.0.0.1:%d\" % self.servers[1].get_listen_port())\n\n        # Register shall not be attempted\n        with self.assertRaises(socket.timeout):\n            self.servers[1].recv(timeout_s=5)\n\n        self.communicate('exit-offline')\n        self.assertDemoRegisters(self.servers[0])\n        self.assertDemoRegisters(self.servers[1])\n\n\nclass SocketUpdateDuringRegister(test_suite.Lwm2mSingleServerTest):\n    def setUp(self):\n        super().setUp(auto_register=False)\n\n    def runTest(self):\n        register1 = self.assertDemoRegisters(respond=False)\n\n        self.communicate('notify /0/1/1')\n        # this triggers _anjay_schedule_socket_update()\n        # and causes a new Register to replace the old one\n        register2 = self.assertDemoRegisters(respond=False)\n        self.assertNotEqual(register1, register2)\n        self.serv.send(Lwm2mCreated.matching(register2)(location=self.DEFAULT_REGISTER_ENDPOINT))\n\n\nclass AsyncNotifications:\n    class Test(test_suite.Lwm2mSingleServerTest, test_suite.Lwm2mDmOperations):\n        def clearObservation(self, notif, respond=False):\n            self.observe(self.serv, OID.Device, 0, RID.Device.CurrentTime, token=notif.token,\n                         observe=1)\n\n            # consume possibly outstanding Notify interrupting clean deregister\n            deadline = time.time() + 1.5\n            while True:\n                try:\n                    pkt = self.serv.recv(deadline=deadline)\n                    self.assertIsInstance(pkt, Lwm2mNotify)\n                    if respond:\n                        self.serv.send(Lwm2mEmpty.matching(pkt)())\n                except socket.timeout:\n                    break\n\n\nclass NonconfirmableNotificationsDuringUpdate(AsyncNotifications.Test):\n    def runTest(self):\n        notif = self.observe(self.serv, OID.Device, 0, RID.Device.CurrentTime)\n        self.communicate('send-update')\n\n        received_update_requests = 0\n        update_id = None\n        received_notifications = 0\n\n        start_time = time.time()\n        while received_update_requests < 3:\n            pkt = self.serv.recv(timeout_s=10)\n            if isinstance(pkt, Lwm2mUpdate):\n                if update_id is None:\n                    update_id = pkt.msg_id\n                else:\n                    # assert that this is indeed retransmission of the previous Update\n                    self.assertEqual(pkt.msg_id, update_id)\n                received_update_requests += 1\n            elif isinstance(pkt, Lwm2mNotify):\n                received_notifications += 1\n\n        self.serv.send(Lwm2mChanged.matching(pkt)())\n\n        while True:\n            try:\n                pkt = self.serv.recv(timeout_s=0.5)\n                self.assertIsInstance(pkt, Lwm2mNotify)\n                received_notifications += 1\n            except socket.timeout:\n                break\n\n        end_time = time.time()\n        self.assertAlmostEqual(received_notifications, end_time - start_time, delta=1.5)\n\n        self.clearObservation(notif, respond=False)\n\n\nclass ConfirmableNotificationsDuringUpdate(AsyncNotifications.Test):\n    def setUp(self):\n        super().setUp(extra_cmdline_args=['--confirmable-notifications'])\n\n    def runTest(self):\n        notif = self.observe(self.serv, OID.Device, 0, RID.Device.CurrentTime)\n        self.communicate('send-update')\n\n        # Confirmable notifications honor NSTART\n        received_update_requests = 0\n        update_id = None\n\n        start_time = time.time()\n        early_notify_checked_for = False\n        while received_update_requests < 3:\n            pkt = self.serv.recv(timeout_s=10)\n\n            # one Notify could be sent by the device before the Update message\n            if not early_notify_checked_for:\n                early_notify_checked_for = True\n                if isinstance(pkt, Lwm2mNotify):\n                    self.serv.send(Lwm2mEmpty.matching(pkt)())\n                    continue\n\n            self.assertIsInstance(pkt, Lwm2mUpdate)\n            if update_id is None:\n                update_id = pkt.msg_id\n            else:\n                # assert that this is indeed retransmission of the previous Update\n                self.assertEqual(pkt.msg_id, update_id)\n            received_update_requests += 1\n\n        self.serv.send(Lwm2mChanged.matching(pkt)())\n\n        received_notifications = 0\n        while True:\n            try:\n                pkt = self.serv.recv(timeout_s=0.5)\n                self.assertIsInstance(pkt, Lwm2mNotify)\n                received_notifications += 1\n                self.serv.send(Lwm2mEmpty.matching(pkt)())\n            except socket.timeout:\n                break\n\n        end_time = time.time()\n        self.assertAlmostEqual(received_notifications, end_time - start_time, delta=1.5)\n\n        self.clearObservation(notif, respond=True)\n"
  },
  {
    "path": "tests/integration/suites/default/block_response.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nimport unittest\n\nfrom framework_tools.lwm2m.tlv import TLV\nfrom framework.lwm2m_test import *\n\n\nclass BlockResponseTest(test_suite.Lwm2mSingleServerTest):\n    def setUp(self, bytes_size=9001, extra_cmdline_args=None):\n        super(BlockResponseTest, self).setUp(extra_cmdline_args=extra_cmdline_args)\n        self.make_test_instance()\n        self.set_bytes_size(0, bytes_size)\n        self.set_bytes_burst(0, 1000)\n\n    @unittest.skip\n    def runTest(self):\n        pass\n\n    def assertIdentityMatches(self, response, request):\n        self.assertEqual(request.msg_id, response.msg_id)\n\n        if response.code != coap.Code.EMPTY:\n            self.assertEqual(request.token, response.token)\n\n    def assertBlockResponse(self, response, seq_num, has_more, block_size):\n        self.assertEqual(response.get_options(coap.Option.BLOCK2)[0].has_more(), has_more)\n        self.assertEqual(response.get_options(coap.Option.BLOCK2)[0].seq_num(), seq_num)\n        self.assertEqual(response.get_options(coap.Option.BLOCK2)[0].block_size(), block_size)\n\n    def make_test_instance(self):\n        req = Lwm2mCreate(\"/%d\" % OID.Test)\n        self.serv.send(req)\n        response = self.serv.recv()\n        self.assertMsgEqual(Lwm2mCreated.matching(req)(), response);\n\n    def set_bytes_size(self, iid, size):\n        req = Lwm2mWrite(ResPath.Test[iid].ResBytesSize, str(size))\n        self.serv.send(req)\n        response = self.serv.recv()\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(), response);\n\n    def set_bytes_burst(self, iid, size):\n        req = Lwm2mWrite(\"/%d/%d\" % (OID.Test, iid),\n                         TLV.make_resource(RID.Test.ResBytesBurst, int(size)).serialize(),\n                         format=coap.ContentFormat.APPLICATION_LWM2M_TLV)\n        self.serv.send(req)\n        response = self.serv.recv()\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(), response);\n\n    def read_bytes(self, iid, seq_num=None, block_size=None, options_modifier=None, accept=None):\n        opts = [coap.Option.BLOCK2(seq_num=seq_num, has_more=0, block_size=block_size)] \\\n            if seq_num is not None and block_size else []\n        if options_modifier is not None:\n            opts = options_modifier(opts)\n\n        req = Lwm2mRead(ResPath.Test[iid].ResBytes,\n                        options=opts, accept=accept)\n        self.serv.send(req)\n        res = self.serv.recv()\n        self.assertIdentityMatches(res, req)\n        return res\n\n    def read_blocks(self, iid, block_size=1024, base_seq=0, accept=None):\n        data = bytearray()\n        while True:\n            tmp = self.read_bytes(iid, base_seq, block_size, accept=accept)\n            data += tmp.content\n            base_seq += 1\n            if not tmp.get_options(coap.Option.BLOCK2)[0].has_more():\n                break\n        return data\n\n\nclass BlockResponseFirstRequestIsNotBlock(BlockResponseTest):\n    def setUp(self):\n        super(BlockResponseFirstRequestIsNotBlock, self).setUp(bytes_size=9025)\n\n    def runTest(self):\n        response = self.read_bytes(iid=0)\n        self.assertBlockResponse(response, seq_num=0, has_more=1, block_size=1024)\n        # Currently we have to read this till the end\n        self.read_blocks(iid=0, base_seq=1)\n\nclass BlockResponseFirstRequestIsBlock(BlockResponseTest):\n    def runTest(self):\n        response = self.read_bytes(iid=0, seq_num=1, block_size=1024)\n        # Started from seq_num=1, clearly incorrect request\n        self.assertEqual(response.code, coap.Code.RES_SERVICE_UNAVAILABLE)\n\n        # Normal request\n        response = self.read_bytes(iid=0, seq_num=0, block_size=1024)\n        self.assertBlockResponse(response, seq_num=0, has_more=1, block_size=1024)\n        self.read_blocks(iid=0, base_seq=1)\n\n\nclass BlockResponseSizeNegotiation(BlockResponseTest):\n    def runTest(self):\n        # Forcing block_size from the very first request\n        data = self.read_blocks(iid=0, block_size=16, base_seq=0,\n                                accept=coap.ContentFormat.APPLICATION_OCTET_STREAM)\n        for i in range(len(data)):\n            self.assertEqual(data[i], i % 128)\n\n        # Forcing block_size after first message\n        response = self.read_bytes(iid=0,\n                                   accept=coap.ContentFormat.APPLICATION_OCTET_STREAM)\n        self.assertBlockResponse(response, seq_num=0, has_more=1, block_size=1024)\n\n        second_payload = self.read_blocks(iid=0, base_seq=1,\n                                          accept=coap.ContentFormat.APPLICATION_OCTET_STREAM)\n        self.assertEqual(data, response.content + second_payload)\n\n        # Negotiation after first message\n        response = self.read_bytes(iid=0, seq_num=0, block_size=32,\n                                   accept=coap.ContentFormat.APPLICATION_OCTET_STREAM)\n        self.assertBlockResponse(response, seq_num=0, has_more=1, block_size=32)\n\n        third_payload = self.read_blocks(iid=0, block_size=32, base_seq=1,\n                                         accept=coap.ContentFormat.APPLICATION_OCTET_STREAM)\n        self.assertEqual(data, response.content + third_payload)\n\n\nclass BlockResponseSizeRenegotiation(BlockResponseTest):\n    def runTest(self):\n        # Case 0: when first request does not contain BLOCK2 option.\n        response = self.read_bytes(iid=0, seq_num=None, block_size=None)\n        self.assertBlockResponse(response, seq_num=0, has_more=1, block_size=1024)\n\n        response = self.read_bytes(iid=0, seq_num=32, block_size=32)\n        self.assertBlockResponse(response, seq_num=32, has_more=1, block_size=32)\n\n        response = self.read_bytes(iid=0, seq_num=66, block_size=16)\n        self.assertBlockResponse(response, seq_num=66, has_more=1, block_size=16)\n\n        self.read_blocks(iid=0, base_seq=67, block_size=16)\n\n        # Case 1: when first request does contain BLOCK2 option.\n        response = self.read_bytes(iid=0, seq_num=0, block_size=512)\n        self.assertBlockResponse(response, seq_num=0, has_more=1, block_size=512)\n\n        response = self.read_bytes(iid=0, seq_num=16, block_size=32)\n        self.assertBlockResponse(response, seq_num=16, has_more=1, block_size=32)\n\n        response = self.read_bytes(iid=0, seq_num=34, block_size=16)\n        self.assertBlockResponse(response, seq_num=34, has_more=1, block_size=16)\n\n        self.read_blocks(iid=0, base_seq=35, block_size=16)\n\nclass BlockResponseInvalidSizeDuringRenegotation(BlockResponseTest):\n    def runTest(self):\n        # Case 0: when first request does not contain BLOCK2 option.\n        response = self.read_bytes(iid=0, seq_num=None, block_size=None)\n        self.assertBlockResponse(response, seq_num=0, has_more=1, block_size=1024)\n\n        response = self.read_bytes(iid=0, seq_num=0, block_size=2048)\n        self.assertIsInstance(response, Lwm2mErrorResponse)\n        self.assertEqual(response.code, coap.Code.RES_BAD_OPTION)\n\n        # finish the transfer before continuing\n        self.read_blocks(iid=0, base_seq=1, block_size=1024)\n\n        # Case 1: when first request does contain BLOCK2 option.\n        response = self.read_bytes(iid=0, seq_num=0, block_size=2048)\n        self.assertIsInstance(response, Lwm2mErrorResponse)\n        self.assertEqual(response.code, coap.Code.RES_BAD_OPTION)\n\n        self.read_blocks(iid=0, block_size=1024)\n\n\nclass BlockResponseBadBlock2SizeInTheMiddleOfTransfer(BlockResponseTest):\n    def runTest(self):\n        first_invalid_seq_num = 4\n        for seq_num in range(first_invalid_seq_num):\n            response = self.read_bytes(iid=0, seq_num=seq_num, block_size=1024)\n            self.assertBlockResponse(response, seq_num=seq_num, has_more=1, block_size=1024)\n\n        response = self.read_bytes(iid=0, seq_num=first_invalid_seq_num, block_size=2048)\n        self.assertIsInstance(response, Lwm2mErrorResponse)\n        self.assertEqual(response.code, coap.Code.RES_BAD_OPTION)\n\n        self.read_blocks(iid=0, base_seq=first_invalid_seq_num, block_size=1024)\n\n\nclass BlockResponseBadBlock1(BlockResponseTest):\n    def runTest(self):\n        def opts_modifier(opts):\n            return opts + [coap.Option.BLOCK1(0, 0, 16)]\n\n        response = self.read_bytes(iid=0, seq_num=0, block_size=512)\n        self.assertBlockResponse(response, seq_num=0, has_more=1, block_size=512)\n        response = self.read_bytes(iid=0, seq_num=1, block_size=512,\n                                   options_modifier=opts_modifier)\n\n        # should continue the transfer\n        self.assertEqual(response.code, coap.Code.RES_SERVICE_UNAVAILABLE)\n\n        self.read_blocks(iid=0, base_seq=1, block_size=512)\n\n\nclass BlockResponseBiggerBlockSizeThanData(BlockResponseTest):\n    def setUp(self):\n        super().setUp(bytes_size=5)\n\n    def runTest(self):\n        response = self.read_bytes(iid=0, seq_num=0, block_size=1024)\n        self.assertBlockResponse(response, seq_num=0, has_more=0, block_size=1024)\n\n\n# Tests different rates at which anjay's stream buffers are filled\nclass BlockResponseDifferentBursts(BlockResponseTest):\n    BYTES_AMOUNT = 9001\n\n    def setUp(self):\n        super().setUp(bytes_size=BlockResponseDifferentBursts.BYTES_AMOUNT)\n\n    def runTest(self):\n        for i in (1, 10, 50, 100, 1000, 1024, 1200, 2048, 4096, 5000, 9001):\n            self.set_bytes_burst(0, i)\n            self.read_blocks(iid=0)\n\n\nclass BlockResponseToNonBlockRequestRetransmission(BlockResponseTest):\n    def setUp(self):\n        super().setUp(extra_cmdline_args=['--cache-size', '2048'])\n\n    def runTest(self):\n        # non-BLOCK request\n        req = Lwm2mRead(ResPath.Test[0].ResBytes)\n        self.serv.send(req)\n\n        # BLOCK response received\n        res = self.serv.recv()\n        self.assertIdentityMatches(res, req)\n\n        block_opts = res.get_options(coap.Option.BLOCK2)\n        self.assertNotEqual([], block_opts)\n\n        # retransmit non-BLOCK request\n        self.serv.send(req)\n\n        # should receive the same response as before\n        res2 = self.serv.recv()\n        self.assertMsgEqual(res, res2)\n\n        # should be able to continue the transfer\n        self.read_blocks(iid=0, block_size=block_opts[0].block_size(), base_seq=1)\n\n\n@unittest.skip(\"TODO: broken due to T2338\")\nclass BlockResponseOutOfOrder(BlockResponseTest):\n    def runTest(self):\n        # RFC 7959 does not define the expected behaviour when a CoAP client attempt to retrieve a block that is past\n        # the end of the resource. Various CoAP implementations (taken from http://coap.technology/impls.html) behave\n        # differently:\n        #\n        # - Erbium (http://people.inf.ethz.ch/mkovatsc/erbium.php) - 4.02 Bad Option\n        # - Lobaro CoAP (http://www.lobaro.com/portfolio/lobaro-coap/) - 4.02 Bad Option\n        # - MR-CoAP (https://github.com/MR-CoAP/CoAP) - 4.00 Bad Request\n        # - Californium (http://www.eclipse.org/californium/) - success with empty content and Block2.More=false\n        #\n        # Anjay generates the resource contents on the fly, so it cannot even know the size of the resource in advance\n        # and does not accept request for any sequence number other than either the previously retrieved one or the one\n        # after it (retransmission or continuation).\n        #\n        # And it actually sends Reset upon receiving unexpected block number. We are not sure whether it makes any\n        # sense, but here's a test for that.\n\n        response = self.read_bytes(iid=0, seq_num=None, block_size=None)\n        self.assertBlockResponse(response, seq_num=0, has_more=1, block_size=1024)\n\n        response = self.read_bytes(iid=0, seq_num=42, block_size=1024)\n        self.assertIsInstance(response, Lwm2mReset)\n\n        # should be able to continue the transfer\n        self.read_blocks(iid=0, block_size=1024, base_seq=1)\n\n\nclass BlockResponseUnrelatedRequestsDoNotAbortTransfer(BlockResponseTest):\n    def runTest(self):\n        # start BLOCK response transfer\n        response = self.read_bytes(iid=0, seq_num=None, block_size=None)\n        self.assertBlockResponse(response, seq_num=0, has_more=1, block_size=1024)\n\n        # send unrelated request\n        req = Lwm2mRead(ResPath.Device.SerialNumber)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mErrorResponse.matching(req)(coap.Code.RES_SERVICE_UNAVAILABLE),\n                            self.serv.recv())\n\n        # send another unrelated request\n        req = Lwm2mWrite(ResPath.FirmwareUpdate.Package, b'A' * 16,\n                         options=[coap.Option.BLOCK1(seq_num=0, block_size=16, has_more=False)],\n                         format=coap.ContentFormat.APPLICATION_OCTET_STREAM)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mErrorResponse.matching(req)(coap.Code.RES_SERVICE_UNAVAILABLE),\n                            self.serv.recv())\n\n        # should be able to continue the transfer\n        block_opts = response.get_options(coap.Option.BLOCK2)\n        self.read_blocks(iid=0, block_size=block_opts[0].block_size(), base_seq=1)\n\n\nclass BlockResponseUnexpectedServerRequestInTheMiddleOfTransfer(BlockResponseTest):\n    def runTest(self):\n        response = self.read_bytes(iid=0, seq_num=None, block_size=None)\n        self.assertBlockResponse(response, seq_num=0, has_more=1, block_size=1024)\n\n        # send an unrelated request during a block-wise transfer\n        req = Lwm2mRead(ResPath.Device.Manufacturer)\n        self.serv.send(req)\n        res = self.serv.recv()\n        self.assertMsgEqual(Lwm2mErrorResponse.matching(req)(coap.Code.RES_SERVICE_UNAVAILABLE),\n                            res)\n        # TODO: T2327\n        # self.assertEqual(1, len(res.get_options(coap.Option.MAX_AGE)))\n\n        # continue reading block-wise response\n        self.read_blocks(iid=0, base_seq=1, block_size=1024)\n\n\nclass BlockResponseUnexpectedBlockServerRequestInTheMiddleOfTransfer(BlockResponseTest):\n    def runTest(self):\n        response = self.read_bytes(iid=0, seq_num=None, block_size=None)\n        self.assertBlockResponse(response, seq_num=0, has_more=1, block_size=1024)\n\n        # send an unrelated request during a block-wise transfer\n        # use BLOCK2 Option to not trigger heuristic based on it\n        req = Lwm2mRead(ResPath.Device.Manufacturer,\n                        options=[coap.Option.BLOCK2(seq_num=0, has_more=0, block_size=1024)])\n        self.serv.send(req)\n        res = self.serv.recv()\n        self.assertMsgEqual(Lwm2mErrorResponse.matching(req)(coap.Code.RES_SERVICE_UNAVAILABLE),\n                            res)\n        # TODO: T2327\n        # self.assertEqual(1, len(res.get_options(coap.Option.MAX_AGE)))\n\n        # continue reading block-wise response\n        self.read_blocks(iid=0, base_seq=1, block_size=1024)\n"
  },
  {
    "path": "tests/integration/suites/default/block_write.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nimport os\nimport unittest\n\nfrom collections import namedtuple\nfrom framework.lwm2m_test import *\nfrom framework.create_package import make_firmware_package\n\nmsg_id_generator = SequentialMsgIdGenerator(42)\n\nA_LOT = 10000\nA_LOT_OF_STUFF = random_stuff(A_LOT)\n\nA_LITTLE = 32\nA_LITTLE_STUFF = random_stuff(A_LITTLE)\n\nChunk = namedtuple('Chunk', ('idx', 'size', 'content'))\n\ndef equal_chunk_splitter(chunk_size):\n    def split(data):\n        return (Chunk(idx, chunk_size, chunk)\n                for idx, chunk in enumerate(data[i:i + chunk_size]\n                                            for i in range(0, len(data), chunk_size)))\n\n    return split\n\n\ndef packets_from_chunks(chunks, process_options=None, path=ResPath.FirmwareUpdate.Package,\n                        format=coap.ContentFormat.APPLICATION_OCTET_STREAM,\n                        code=coap.Code.REQ_PUT):\n    for idx, chunk in enumerate(chunks):\n        has_more = (idx != len(chunks) - 1)\n\n        options = ((uri_path_to_options(path) if path is not None else [])\n                   + [coap.Option.CONTENT_FORMAT(format),\n                      coap.Option.BLOCK1(seq_num=chunk.idx, has_more=has_more, block_size=chunk.size)])\n\n        if process_options is not None:\n            options = process_options(options, idx)\n\n        yield coap.Packet(type=coap.Type.CONFIRMABLE,\n                          code=code,\n                          token=random_stuff(size=5),\n                          msg_id=next(msg_id_generator),\n                          options=options,\n                          content=chunk.content)\n\n\nclass Block:\n    class Test(test_suite.Lwm2mSingleServerTest, test_suite.Lwm2mDmOperations):\n        def block_init_file(self):\n            import tempfile\n\n            with tempfile.NamedTemporaryFile(delete=False) as f:\n                fw_file_name = f.name\n            self.communicate('set-fw-package-path %s' % (os.path.abspath(fw_file_name)))\n            return fw_file_name\n\n        def block_send(self, data, splitter, **make_firmware_package_args):\n            fw_file_name = self.block_init_file()\n\n            chunks = list(splitter(make_firmware_package(data, **make_firmware_package_args)))\n\n            for request in packets_from_chunks(chunks):\n                self.serv.send(request)\n                response = self.serv.recv()\n                self.assertIsSuccessResponse(response, request)\n\n            with open(fw_file_name, 'rb') as fw_file:\n                self.assertEqual(fw_file.read(), data)\n\n            self.files_to_cleanup.append(fw_file_name)\n\n        def assertIsResponse(self, response, request):\n            self.assertEqual(coap.Type.ACKNOWLEDGEMENT, response.type)\n            self.assertEqual(request.msg_id, response.msg_id)\n            self.assertEqual(request.token, response.token)\n\n        def assertIsSuccessResponse(self, response, request):\n            self.assertIsResponse(response, request)\n\n            if request.get_options(coap.Option.BLOCK1)[0].has_more():\n                self.assertEqual(coap.Code.RES_CONTINUE, response.code)\n            else:\n                self.assertEqual(coap.Code.RES_CHANGED, response.code)\n\n            self.assertEqual(request.get_options(coap.Option.BLOCK1),\n                             response.options)\n\n        def perform_firmware_update_expect_success(self):\n            req = Lwm2mExecute(ResPath.FirmwareUpdate.Update)\n            self.serv.send(req)\n            self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                                self.serv.recv())\n\n        def setUp(self, *args, **kwargs):\n            super().setUp(*args, **kwargs)\n            self.files_to_cleanup = []\n\n\n        def tearDown(self):\n            for file in self.files_to_cleanup:\n                try:\n                    os.unlink(file)\n                except FileNotFoundError:\n                    pass\n\n            # now reset the state machine\n            self.write_resource(self.serv, OID.FirmwareUpdate, 0, RID.FirmwareUpdate.Package, b'\\0',\n                                format=coap.ContentFormat.APPLICATION_OCTET_STREAM)\n            super().tearDown()\n\n        @unittest.skip\n        def runTest(self):\n            pass\n\n\nclass BlockIncompleteTest(Block.Test):\n    def runTest(self):\n        # incomplete BLOCK should be rejected\n        chunks = list(equal_chunk_splitter(1024)(A_LOT_OF_STUFF))\n        self.assertGreater(len(chunks), 4)\n\n        packets = list(packets_from_chunks([chunks[0], chunks[1], chunks[2], chunks[3]]))\n        self.assertEqual(len(packets), 4)\n\n        # first packet with seq_num > 0 should be rejected\n        req = packets[-1]\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mErrorResponse.matching(req)(code=coap.Code.RES_REQUEST_ENTITY_INCOMPLETE),\n                            self.serv.recv())\n\n        # consecutive packets received by the anjay with such seq_nums: (0, k > 1)\n        req = packets[0]\n        self.serv.send(req)\n        res = self.serv.recv()\n        self.assertIsSuccessResponse(res, req)\n\n        req = packets[-1]\n        self.serv.send(req)\n        # there is no such exchange that this packet could be matched to - the client expects consecutive\n        # blocks\n        self.assertMsgEqual(Lwm2mErrorResponse.matching(req)(code=coap.Code.RES_SERVICE_UNAVAILABLE),\n                            self.serv.recv())\n\n        # consecutive packets received by the anjay with such seq_nums: (1, 2, 1)\n        req = packets[1]\n        self.serv.send(req)\n        res = self.serv.recv()\n        self.assertIsSuccessResponse(res, req)\n\n        req = packets[2]\n        self.serv.send(req)\n        res = self.serv.recv()\n        self.assertIsSuccessResponse(res, req)\n\n        req = packets[1]\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mErrorResponse.matching(req)(code=coap.Code.RES_SERVICE_UNAVAILABLE),\n                            self.serv.recv())\n\n        # Finish blockwise transfer\n        req = packets[3]\n        self.serv.send(req)\n        self.assertIsSuccessResponse(self.serv.recv(), req)\n\n\nclass BlockSizesTest(Block.Test):\n    def runTest(self):\n        # multiple chunk sizes: min/max/something in between\n        for chunk_size in (16, 256, 1024):\n            self.block_send(A_LOT_OF_STUFF, equal_chunk_splitter(chunk_size))\n            # now reset the state machine\n            self.write_resource(self.serv, OID.FirmwareUpdate, 0, RID.FirmwareUpdate.Package, b'\\0',\n                                format=coap.ContentFormat.APPLICATION_OCTET_STREAM)\n\n\nclass BlockSingleChunkTest(Block.Test):\n    def runTest(self):\n        # single-chunk block\n        self.block_send(A_LITTLE_STUFF, equal_chunk_splitter(chunk_size=len(A_LITTLE_STUFF) * 2))\n\n\nclass BlockVariableChunkSizeTest(Block.Test):\n    def runTest(self):\n        def shrinking_chunk_splitter(initial_chunk_size):\n            def split(data):\n                MIN_CHUNK_SIZE = 16\n\n                chunk_size = initial_chunk_size\n                idx = 0\n                i = 0\n                while i < len(data):\n                    yield Chunk(idx, chunk_size, data[i:i + chunk_size])\n\n                    i += chunk_size\n                    idx += 1\n                    if chunk_size > MIN_CHUNK_SIZE:\n                        self.assertTrue(chunk_size % 2 == 0)\n                        chunk_size //= 2\n                        idx *= 2\n\n            return split\n\n        def growing_chunk_splitter(initial_chunk_size):\n            def split(data):\n                MAX_CHUNK_SIZE = 1024\n\n                chunk_size = initial_chunk_size\n                idx = 0\n                i = 0\n\n                # block sequence number specifies block offset in multiples of\n                # chunk_size, so the smallest chunk size must be used twice to ensure\n                # that sequence numbers are integers\n                yield Chunk(0, chunk_size, data[0:chunk_size])\n                i += chunk_size\n                idx += 1\n\n                while i < len(data):\n                    yield Chunk(idx, chunk_size, data[i:i + chunk_size])\n\n                    i += chunk_size\n                    idx += 1\n                    if chunk_size < MAX_CHUNK_SIZE:\n                        chunk_size *= 2\n                        self.assertTrue(idx % 2 == 0)\n                        idx //= 2\n\n            return split\n\n        def alternating_size_chunk_splitter(sizes):\n            max_size = max(sizes)\n            self.assertTrue(all(max_size % x == 0 for x in sizes))\n\n            def split(data):\n                import itertools\n                sizes_iter = itertools.cycle(sizes)\n\n                chunk_size = next(sizes_iter)\n                idx = 0\n                i = 0\n\n                while i < len(data):\n                    for _ in range(max_size // chunk_size):\n                        yield Chunk(idx, chunk_size, data[i:i + chunk_size])\n\n                        i += chunk_size\n                        idx += 1\n                        if i >= len(data):\n                            return\n\n                    new_chunk_size = next(sizes_iter)\n                    if new_chunk_size < chunk_size:\n                        idx *= chunk_size // new_chunk_size\n                    else:\n                        self.assertTrue(idx % (new_chunk_size // chunk_size) == 0)\n                        idx //= new_chunk_size // chunk_size\n                    chunk_size = new_chunk_size\n\n            return split\n\n        # variable chunk size\n        self.block_send(A_LOT_OF_STUFF, shrinking_chunk_splitter(initial_chunk_size=1024))\n        self.write_resource(self.serv, OID.FirmwareUpdate, 0, RID.FirmwareUpdate.Package, b'\\0',\n                            format=coap.ContentFormat.APPLICATION_OCTET_STREAM)\n        self.block_send(A_LOT_OF_STUFF, growing_chunk_splitter(initial_chunk_size=16))\n        self.write_resource(self.serv, OID.FirmwareUpdate, 0, RID.FirmwareUpdate.Package, b'\\0',\n                            format=coap.ContentFormat.APPLICATION_OCTET_STREAM)\n        self.block_send(A_LOT_OF_STUFF, alternating_size_chunk_splitter(sizes=[32, 512, 256, 1024, 64]))\n\n\nclass BlockNonFirstTest(Block.Test):\n    def runTest(self):\n        data = A_LOT_OF_STUFF\n        splitter = equal_chunk_splitter(1024)\n\n        fw_file = self.block_init_file()\n        try:\n            chunks = list(splitter(data))\n\n            request = list(packets_from_chunks([chunks[1]]))[0]\n\n            self.serv.send(request)\n            self.assertMsgEqual(Lwm2mErrorResponse.matching(request)(coap.Code.RES_REQUEST_ENTITY_INCOMPLETE),\n                                self.serv.recv())\n        finally:\n            os.unlink(fw_file)\n\n\nclass BlockBrokenStreamTest(Block.Test):\n    def runTest(self):\n\n        data = A_LOT_OF_STUFF\n        splitter = equal_chunk_splitter(1024)\n\n        self.block_init_file()\n        chunks = list(splitter(data))[:2]\n\n        class ObjectIdIncrementer(object):\n            def __init__(self):\n                self.chunk_no = 0\n                self.last_orig_opts = None\n\n            def __call__(self, opts, idx):\n                import copy\n                self.last_orig_opts = copy.deepcopy(opts)\n                for i in range(len(opts)):\n                    if opts[i].matches(coap.Option.URI_PATH):\n                        opts[i] = coap.Option.URI_PATH(str(int(opts[i].content) + self.chunk_no))\n                        self.chunk_no += 1\n                        break\n                return opts\n\n        incrementer = ObjectIdIncrementer()\n        first_request, second_request = packets_from_chunks(chunks, incrementer)\n\n        self.serv.send(first_request)\n        self.assertIsSuccessResponse(self.serv.recv(), first_request)\n\n        # broken stream\n        self.serv.send(second_request)\n        res = self.serv.recv()\n        self.assertMsgEqual(Lwm2mErrorResponse.matching(second_request)(coap.Code.RES_SERVICE_UNAVAILABLE),\n                            res)\n        # TODO: T2327\n        # self.assertEqual(1, len(res.get_options(coap.Option.MAX_AGE)))\n\n        # send the valid packet so that demo can terminate cleanly\n        second_request.options = incrementer.last_orig_opts\n        self.serv.send(second_request)\n        self.assertIsSuccessResponse(self.serv.recv(), second_request)\n\n\ndef block2_adder(options, idx):\n    return options + [coap.Option.BLOCK2(seq_num=0, has_more=0, block_size=16)]\n\n\nclass BlockBidirectionalSuccess(Block.Test):\n    def runTest(self):\n        splitter = equal_chunk_splitter(1024)\n        chunks = list(splitter(A_LOT_OF_STUFF))\n        request = list(packets_from_chunks([chunks[0]], block2_adder))[0]\n        self.serv.send(request)\n        self.assertMsgEqual(Lwm2mChanged.matching(request)(), self.serv.recv())\n\n\nclass BlockMostlyUnidirectionalWithRandomlyInsertedBlock2(Block.Test):\n    def runTest(self):\n        chunks = list(equal_chunk_splitter(1024)(A_LOT_OF_STUFF))\n        self.assertGreater(len(chunks), 4)\n\n        def helper_modifier(options, idx):\n            if idx == 2:\n                return block2_adder(options, idx)\n            return options\n\n        packets = list(packets_from_chunks([chunks[0], chunks[1],\n                                            chunks[2], chunks[-1]], helper_modifier))\n\n        req = packets[0]\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mContinue.matching(req)(), self.serv.recv())\n\n        req = packets[1]\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mContinue.matching(req)(), self.serv.recv())\n\n        req = packets[2]\n        self.serv.send(req)\n\n        # And BAD_OPTION actually stops the block transfer\n        self.assertMsgEqual(Lwm2mErrorResponse.matching(req)(code=coap.Code.RES_BAD_OPTION),\n                            self.serv.recv())\n\n        # Finish blockwise transfer.\n        packets = list(packets_from_chunks([chunks[0], chunks[1],\n                                            chunks[2], chunks[3]]))\n        req = packets[2]\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mContinue.matching(req)(), self.serv.recv())\n\n        req = packets[3]\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.serv.recv())\n\n\nclass BlockDuplicate(Block.Test):\n    def setUp(self):\n        super().setUp(extra_cmdline_args=['--cache-size', '1024'])\n\n    def runTest(self):\n        pkt1 = Lwm2mWrite(ResPath.FirmwareUpdate.Package, b'x' * 16,\n                          format=coap.ContentFormat.APPLICATION_OCTET_STREAM,\n                          options=[coap.Option.BLOCK1(0, 1, 16)])\n\n        pkt2 = Lwm2mWrite(ResPath.FirmwareUpdate.Package, b'x' * 16,\n                          format=coap.ContentFormat.APPLICATION_OCTET_STREAM,\n                          options=[coap.Option.BLOCK1(1, 0, 16)])\n\n        self.serv.send(pkt1)\n        response1 = self.serv.recv()\n        self.assertMsgEqual(Lwm2mContinue.matching(pkt1)(), response1)\n\n        self.serv.send(pkt1)\n        response2 = self.serv.recv()\n        self.assertMsgEqual(response1, response2)\n\n        self.serv.send(pkt2)\n        self.assertMsgEqual(Lwm2mChanged.matching(pkt2)(), self.serv.recv())\n\n\nclass BlockBadBlock1SizeInTheMiddleOfTransfer(Block.Test):\n    def runTest(self):\n        num_correct_blocks = 4\n        seq_num = 0\n        for _ in range(num_correct_blocks):\n            pkt = Lwm2mWrite(ResPath.FirmwareUpdate.Package, b'x' * 16,\n                             format=coap.ContentFormat.APPLICATION_OCTET_STREAM,\n                             options=[coap.Option.BLOCK1(seq_num, 1, 16)])\n            self.serv.send(pkt)\n            self.assertMsgEqual(Lwm2mContinue.matching(pkt)(), self.serv.recv())\n\n            seq_num += 1\n\n        invalid_pkt = Lwm2mWrite(ResPath.FirmwareUpdate.Package, b'x' * 16,\n                                 format=coap.ContentFormat.APPLICATION_OCTET_STREAM,\n                                 options=[coap.Option.BLOCK1(seq_num, 1, 2048)])\n        self.serv.send(invalid_pkt)\n        self.assertMsgEqual(Lwm2mErrorResponse.matching(invalid_pkt)(code=coap.Code.RES_BAD_OPTION),\n                            self.serv.recv())\n\n        # finish the request\n        pkt = Lwm2mWrite(ResPath.FirmwareUpdate.Package, b'x' * 16,\n                                 format=coap.ContentFormat.APPLICATION_OCTET_STREAM,\n                                 options=[coap.Option.BLOCK1(seq_num, 0, 16)])\n        self.serv.send(pkt)\n        self.assertMsgEqual(Lwm2mChanged.matching(pkt)(), self.serv.recv())\n\n\nclass ValueSplitIntoSeparateBlocks(Block.Test):\n    def assertIntValueSplit(self, chunks, value):\n        chunks_content = [chunk.content for chunk in chunks]\n        chunk_pairs = zip(chunks_content[:-1], chunks_content[1:])\n        byte_value = value.to_bytes(4, byteorder='big')\n        value_parts = [(byte_value[:i], byte_value[i:]) for i in range(1, len(byte_value))]\n\n        for chunk_left, chunk_right in chunk_pairs:\n            for part_left, part_right in value_parts:\n                if chunk_left.endswith(part_left) and chunk_right.startswith(part_right):\n                    return\n\n        self.fail('Data does not split an integer')\n\n    def runTest(self):\n        self.create_instance(self.serv, OID.Test, 1)\n\n        value = 0x123456\n        array_content = enumerate([value] * 5)\n        content = TLV.make_multires(RID.Test.IntArray, array_content).serialize()\n        chunks = list(equal_chunk_splitter(16)(content))\n        self.assertIntValueSplit(chunks, value)\n\n        packets = packets_from_chunks(chunks, path=ResPath.Test[1].IntArray,\n                                      format=coap.ContentFormat.APPLICATION_LWM2M_TLV)\n        for request in packets:\n            self.serv.send(request)\n            response = self.serv.recv()\n            self.assertIsSuccessResponse(response, request)\n\n        client_content = self.read_resource(self.serv, OID.Test, 1, RID.Test.IntArray).content\n        self.assertEqual(content, client_content)\n\n\nclass MessageInTheMiddleOfBlockTransfer:\n    class Test(Block.Test):\n        def test_with_message(self, request, expected_response):\n            chunks = list(equal_chunk_splitter(1024)(A_LOT_OF_STUFF))\n            self.assertGreater(len(chunks), 4)\n\n            packets = list(packets_from_chunks(chunks))\n\n            self.serv.send(packets[0])\n            self.assertMsgEqual(Lwm2mContinue.matching(packets[0])(), self.serv.recv())\n\n            self.serv.send(packets[1])\n            self.assertMsgEqual(Lwm2mContinue.matching(packets[1])(), self.serv.recv())\n\n            self.serv.send(request)\n            if expected_response is not None:\n                self.assertMsgEqual(expected_response, self.serv.recv())\n\n            for request in packets[2:]:\n                self.serv.send(request)\n                response = self.serv.recv()\n                self.assertIsSuccessResponse(response, request)\n\n\nclass CoAPPingInTheMiddleOfBlockTransfer(MessageInTheMiddleOfBlockTransfer.Test):\n    def runTest(self):\n        req = Lwm2mEmpty(type=coap.Type.CONFIRMABLE)\n        req.fill_placeholders()\n        res = Lwm2mReset.matching(req)()\n        self.test_with_message(req, res)\n\n\nclass ConfirmableRequestInTheMiddleOfBlockTransfer(MessageInTheMiddleOfBlockTransfer.Test):\n    def runTest(self):\n        req = Lwm2mRead(ResPath.Device.Manufacturer)\n        req.fill_placeholders()\n        res = Lwm2mErrorResponse.matching(req)(coap.Code.RES_SERVICE_UNAVAILABLE)\n        self.test_with_message(req, res)\n\n\nclass NonConfirmableRequestInTheMiddleOfBlockTransfer(MessageInTheMiddleOfBlockTransfer.Test):\n    def runTest(self):\n        req = Lwm2mEmpty(type=coap.Type.NON_CONFIRMABLE)\n        self.test_with_message(req, expected_response=None)\n"
  },
  {
    "path": "tests/integration/suites/default/bootstrap_client.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nfrom framework_tools.lwm2m.coap.code import Code\nfrom framework_tools.lwm2m.coap.server import SecurityMode\nfrom framework.lwm2m_test import *\n\n\nclass BootstrapTest:\n    class TestMixin:\n        def perform_bootstrap_finish(self):\n            req = Lwm2mBootstrapFinish()\n            self.bootstrap_server.send(req)\n            self.assertMsgEqual(Lwm2mChanged.matching(\n                req)(), self.bootstrap_server.recv())\n\n        def add_server(self,\n                       server_iid,\n                       security_iid,\n                       server_uri,\n                       lifetime=86400,\n                       secure_identity=b'',\n                       secure_key=b'',\n                       security_mode: SecurityMode = SecurityMode.NoSec,\n                       binding=\"U\",\n                       additional_security_data=b'',\n                       additional_server_data=b'',\n                       bootstrap_on_registration_failure=None,\n                       server_communication_retry_count=1,\n                       server_communication_retry_timer=0,\n                       server_communication_sequence_retry_count=1,\n                       server_communication_sequence_delay_timer=0):\n            if bootstrap_on_registration_failure is not None:\n                additional_server_data += TLV.make_resource(\n                    RID.Server.BootstrapOnRegistrationFailure,\n                    bootstrap_on_registration_failure).serialize()\n\n            if server_communication_retry_count is not None:\n                additional_server_data += TLV.make_resource(\n                    RID.Server.ServerCommunicationRetryCount,\n                    server_communication_retry_count).serialize()\n                additional_server_data += TLV.make_resource(\n                    RID.Server.ServerCommunicationRetryTimer,\n                    server_communication_retry_timer).serialize()\n                additional_server_data += TLV.make_resource(\n                    RID.Server.ServerCommunicationSequenceRetryCount,\n                    server_communication_sequence_retry_count).serialize()\n                additional_server_data += TLV.make_resource(\n                    RID.Server.ServerCommunicationSequenceDelayTimer,\n                    server_communication_sequence_delay_timer).serialize()\n\n            # Create typical Server Object instance\n            self.write_instance(self.bootstrap_server, oid=OID.Server, iid=server_iid,\n                                content=TLV.make_resource(\n                                    RID.Server.Lifetime, lifetime).serialize()\n                                        + TLV.make_resource(RID.Server.ShortServerID,\n                                                            server_iid).serialize()\n                                        + TLV.make_resource(RID.Server.NotificationStoring,\n                                                            True).serialize()\n                                        + TLV.make_resource(RID.Server.Binding, binding).serialize()\n                                        + additional_server_data)\n\n            # Create typical (corresponding) Security Object instance\n            self.write_instance(self.bootstrap_server, oid=OID.Security, iid=security_iid,\n                                content=TLV.make_resource(\n                                    RID.Security.ServerURI, server_uri).serialize()\n                                        + TLV.make_resource(RID.Security.Bootstrap, 0).serialize()\n                                        + TLV.make_resource(RID.Security.Mode,\n                                                            security_mode.value).serialize()\n                                        + TLV.make_resource(RID.Security.ShortServerID,\n                                                            server_iid).serialize()\n                                        + TLV.make_resource(RID.Security.PKOrIdentity,\n                                                            secure_identity).serialize()\n                                        + TLV.make_resource(RID.Security.SecretKey,\n                                                            secure_key).serialize()\n                                        + additional_security_data)\n\n        def perform_typical_bootstrap(self, server_iid, security_iid, server_uri, lifetime=86400,\n                                      secure_identity=b'', secure_key=b'',\n                                      security_mode: SecurityMode = SecurityMode.NoSec,\n                                      finish=True, holdoff_s=None, binding=\"U\",\n                                      clear_everything=False,\n                                      endpoint=DEMO_ENDPOINT_NAME,\n                                      additional_security_data=b'',\n                                      additional_server_data=b'',\n                                      bootstrap_on_registration_failure=None,\n                                      bootstrap_request_timeout_s=None):\n            # For the first holdoff_s seconds, the client should wait for\n            # 1.0-style Server Initiated Bootstrap. Note that we subtract\n            # 2 seconds to take into account code execution delays.\n            if holdoff_s is None:\n                holdoff_s = self.holdoff_s or 0\n            no_message_s = max(0, holdoff_s - 2)\n            if no_message_s > 0:\n                with self.assertRaises(socket.timeout):\n                    print(self.bootstrap_server.recv(timeout_s=no_message_s))\n\n            # We should get Bootstrap Request now\n            if bootstrap_request_timeout_s is None:\n                self.assertDemoRequestsBootstrap(endpoint=endpoint)\n            elif bootstrap_request_timeout_s >= 0:\n                self.assertDemoRequestsBootstrap(\n                    endpoint=endpoint, timeout_s=bootstrap_request_timeout_s)\n\n            if clear_everything:\n                req = Lwm2mDelete('/')\n                self.bootstrap_server.send(req)\n                self.assertMsgEqual(Lwm2mDeleted.matching(req)(),\n                                    self.bootstrap_server.recv())\n\n            self.add_server(server_iid=server_iid,\n                            security_iid=security_iid,\n                            server_uri=server_uri,\n                            lifetime=lifetime,\n                            secure_identity=secure_identity,\n                            secure_key=secure_key,\n                            security_mode=security_mode,\n                            binding=binding,\n                            additional_security_data=additional_security_data,\n                            additional_server_data=additional_server_data,\n                            bootstrap_on_registration_failure=bootstrap_on_registration_failure)\n\n            if finish:\n                self.perform_bootstrap_finish()\n\n    class Test(TestMixin, test_suite.Lwm2mSingleServerTest, test_suite.Lwm2mDmOperations):\n        def setUp(self, servers=1, num_servers_passed=0, holdoff_s=None, timeout_s=None,\n                  bootstrap_server=True,\n                  extra_cmdline_args=None, **kwargs):\n            assert bootstrap_server\n            extra_args = extra_cmdline_args or []\n            if holdoff_s is not None:\n                extra_args += ['--bootstrap-holdoff', str(holdoff_s)]\n            if timeout_s is not None:\n                extra_args += ['--bootstrap-timeout', str(timeout_s)]\n\n            self.holdoff_s = holdoff_s\n            self.timeout_s = timeout_s\n            super().setUp(servers=servers, num_servers_passed=num_servers_passed,\n                          bootstrap_server=bootstrap_server,\n                          extra_cmdline_args=extra_args, **kwargs)\n\n\nclass BootstrapClientTest(BootstrapTest.Test):\n    def setUp(self):\n        super().setUp(holdoff_s=3, timeout_s=3)\n\n    def runTest(self):\n        self.perform_typical_bootstrap(server_iid=1,\n                                       security_iid=2,\n                                       server_uri='coap://127.0.0.1:%d' % self.serv.get_listen_port(),\n                                       lifetime=60)\n\n        # should now register with the non-bootstrap server\n        self.assertDemoRegisters(self.serv, lifetime=60)\n\n        self.assertEqual(2, self.get_socket_count())\n\n        # no message for now\n        with self.assertRaises(socket.timeout):\n            print(self.serv.recv(timeout_s=1))\n\n        # no changes\n        self.communicate('send-update')\n        self.assertDemoUpdatesRegistration()\n\n        # still no message\n        with self.assertRaises(socket.timeout):\n            print(self.serv.recv(timeout_s=3))\n\n        # now the Bootstrap Server Account should be purged...\n        self.assertEqual(1, self.get_socket_count())\n\n        # and we should get ICMP port unreachable on Bootstrap Finish...\n        self.bootstrap_server.send(Lwm2mBootstrapFinish())\n        # which raises ConnectionRefusedError on a socket.\n        with self.assertRaises(ConnectionRefusedError):\n            self.bootstrap_server.recv()\n\n        # client did not try to register to a Bootstrap server (as in T847)\n        with self.assertRaises(socket.timeout):\n            print(self.bootstrap_server.recv(timeout_s=1))\n\n        # ensure that bootstrap account was purged and client won't accept Request Bootstrap Trigger\n        self.execute_resource(server=self.serv, oid=OID.Server, iid=1, rid=RID.Server.RequestBootstrapTrigger,\n                              expect_error_code=Code.RES_METHOD_NOT_ALLOWED)\n\n\nclass BootstrapOneResourceAtATimeTest(BootstrapTest.Test):\n    def runTest(self):\n        self.assertDemoRequestsBootstrap(endpoint=DEMO_ENDPOINT_NAME)\n        # Create typical Server Object instance\n        for rid, value in ((RID.Server.Lifetime, '86400'),\n                           (RID.Server.ShortServerID, '1'),\n                           (RID.Server.NotificationStoring, '1'),\n                           (RID.Server.Binding, 'U')):\n            self.write_resource(self.bootstrap_server, oid=OID.Server, iid=1, rid=rid,\n                                content=value)\n        # Create typical (corresponding) Security Object instance\n        for rid, value in ((RID.Security.ServerURI,\n                            'coap://127.0.0.1:%d' % self.serv.get_listen_port()),\n                           (RID.Security.Bootstrap, '0'),\n                           (RID.Security.Mode, str(SecurityMode.NoSec.value)),\n                           (RID.Security.ShortServerID, '1')):\n            self.write_resource(self.bootstrap_server, oid=OID.Security, iid=2, rid=rid,\n                                content=value)\n        self.perform_bootstrap_finish()\n\n        # should now register with the non-bootstrap server\n        self.assertDemoRegisters(self.serv)\n\n\nclass BootstrapOnRegistrationFailure(BootstrapTest.Test):\n    def runTest(self):\n        self.perform_typical_bootstrap(server_iid=1,\n                                       security_iid=2,\n                                       server_uri='coap://127.0.0.1:%d' % self.serv.get_listen_port(),\n                                       lifetime=60,\n                                       additional_server_data=TLV.make_resource(\n                                           RID.Server.BootstrapOnRegistrationFailure,\n                                           True).serialize())\n\n        self.assertDemoRegisters(self.serv, lifetime=60, reject=True)\n\n        self.serv.reset()\n\n        self.perform_typical_bootstrap(server_iid=1,\n                                       security_iid=2,\n                                       server_uri='coap://127.0.0.1:%d' % self.serv.get_listen_port(),\n                                       lifetime=60,\n                                       additional_server_data=TLV.make_resource(\n                                           RID.Server.BootstrapOnRegistrationFailure,\n                                           True).serialize())\n\n        # There was a race condition in Anjay, which causes different behavior\n        # if ANJAY_SERVER_NEXT_ACTION_REFRESH action was executed before getting\n        # response to Register request. This sleep is added to ensure all jobs\n        # scheduled for 'now' will be called first.\n        time.sleep(1)\n        self.assertDemoRegisters(self.serv, lifetime=60)\n\n\nclass ClientBootstrapNotSentAfterDisableWithinHoldoffTest(BootstrapTest.Test):\n    def setUp(self):\n        super().setUp(num_servers_passed=1, holdoff_s=3, timeout_s=3)\n\n    def runTest(self):\n        # set Disable Timeout to 5\n        self.write_resource(server=self.serv, oid=OID.Server,\n                            iid=2, rid=RID.Server.DisableTimeout, content='5')\n        # disable the server\n        self.execute_resource(\n            server=self.serv, oid=OID.Server, iid=2, rid=RID.Server.Disable)\n\n        self.assertDemoDeregisters(self.serv)\n\n        with self.assertRaises(socket.timeout, msg=\"the client should not send \"\n                                                   \"Request Bootstrap after disabling the server\"):\n            self.bootstrap_server.recv(timeout_s=4)\n\n        self.assertDemoRegisters(self.serv, timeout_s=2)\n\n\nclass ClientBootstrapBacksOffAfterErrorResponse(BootstrapTest.Test):\n    def setUp(self):\n        super().setUp(servers=0)\n\n    def runTest(self):\n        self.assertDemoRequestsBootstrap(\n            respond_with_error_code=coap.Code.RES_INTERNAL_SERVER_ERROR)\n        with self.assertRaises(socket.timeout, msg=\"the client should not send \"\n                                                   \"Request Bootstrap immediately after receiving \"\n                                                   \"an error response\"):\n            self.bootstrap_server.recv(timeout_s=1)\n\n\nclass ClientBootstrapReconnect(BootstrapTest.Test):\n    def setUp(self):\n        super().setUp(servers=0)\n\n    def runTest(self):\n        self.assertDemoRequestsBootstrap()\n        self.communicate('reconnect')\n        self.assertDemoRequestsBootstrap()\n\n\nclass MultipleBootstrapSecurityInstancesNotAllowed(BootstrapTest.Test):\n    def setUp(self):\n        super().setUp(servers=0)\n\n    def runTest(self):\n        self.perform_typical_bootstrap(server_iid=1,\n                                       security_iid=2,\n                                       server_uri='coap://unused-in-this-test',\n                                       lifetime=86400,\n                                       finish=False)\n\n        # Bootstrap Server MUST NOT be allowed to create second Bootstrap Security Instance\n        self.write_instance(self.bootstrap_server, oid=OID.Security, iid=42,\n                            content=TLV.make_resource(\n                                RID.Security.ServerURI, 'coap://127.0.0.1:5683').serialize()\n                                    + TLV.make_resource(RID.Security.Bootstrap, 1).serialize()\n                                    + TLV.make_resource(RID.Security.Mode,\n                                                        3).serialize()\n                                    + TLV.make_resource(RID.Security.ShortServerID, 42).serialize()\n                                    + TLV.make_resource(RID.Security.PKOrIdentity, \"\").serialize()\n                                    + TLV.make_resource(RID.Security.SecretKey, \"\").serialize(),\n                            expect_error_code=coap.Code.RES_BAD_REQUEST)\n\n\nclass BootstrapUri(BootstrapTest.Test):\n    def make_demo_args(self, *args, **kwargs):\n        args = super().make_demo_args(*args, **kwargs)\n        for i in range(len(args)):\n            if args[i].startswith('coap'):\n                args[i] += '/some/crazy/path?and=more&craziness'\n        return args\n\n    def runTest(self):\n        self.assertDemoRequestsBootstrap(\n            uri_path='/some/crazy/path', uri_query=['and=more', 'craziness'])\n\n    def tearDown(self):\n        super().tearDown(auto_deregister=False)\n\n\nclass InvalidBootstrappedServer(BootstrapTest.Test):\n    def runTest(self):\n        self.perform_typical_bootstrap(server_iid=1,\n                                       security_iid=2,\n                                       server_uri='coap://127.0.0.1:%d' % self.serv.get_listen_port(),\n                                       lifetime=86400)\n\n        # demo should now try to register\n        req = self.serv.recv()\n        self.assertMsgEqual(Lwm2mRegister('/rd?lwm2m=1.0&ep=%s&lt=86400' % (DEMO_ENDPOINT_NAME,)),\n                            req)\n\n        # respond with 4.03\n        self.serv.send(Lwm2mErrorResponse.matching(req)\n                       (code=coap.Code.RES_FORBIDDEN))\n\n        # demo should retry Bootstrap\n        self.assertDemoRequestsBootstrap()\n\n    def tearDown(self):\n        super().tearDown(auto_deregister=False)\n\n\nclass BootstrapFinishWithTimeoutTwice(BootstrapTest.Test):\n    def setUp(self):\n        super().setUp(timeout_s=3600)\n\n    def runTest(self):\n        self.perform_typical_bootstrap(server_iid=1,\n                                       security_iid=2,\n                                       server_uri='coap://127.0.0.1:%d' % self.serv.get_listen_port(),\n                                       lifetime=86400)\n\n        self.assertDemoRegisters()\n\n        # send Bootstrap Finish once again to ensure client doesn't act abnormally\n        self.perform_bootstrap_finish()\n\n        self.assertDemoRegisters()\n\n\nclass ClientInitiatedBootstrapOnly(BootstrapTest.Test):\n    def setUp(self):\n        super().setUp(legacy_server_initiated_bootstrap_allowed=False)\n\n    def tearDown(self):\n        super().tearDown(auto_deregister=False)\n\n    def runTest(self):\n        self.perform_typical_bootstrap(server_iid=1,\n                                       security_iid=2,\n                                       server_uri='coap://127.0.0.1:%d' % self.serv.get_listen_port())\n        self.assertDemoRegisters()\n\n        self.assertEqual(self.get_socket_count(), 1)\n\n        # Trigger update\n        self.communicate('send-update')\n        update_pkt = self.assertDemoUpdatesRegistration(\n            self.serv, respond=False)\n        # Respond to it with 4.00 Bad Request to simulate some kind of client account expiration on server side.\n        self.serv.send(Lwm2mErrorResponse.matching(\n            update_pkt)(code=coap.Code.RES_BAD_REQUEST))\n        # This should cause client attempt to re-register.\n        register_pkt = self.assertDemoRegisters(self.serv, respond=False)\n        # To which we respond with 4.03 Forbidden, finishing off the communication.\n        self.serv.send(Lwm2mErrorResponse.matching(\n            register_pkt)(code=coap.Code.RES_FORBIDDEN))\n\n        # The client shall attempt bootstrapping again\n        self.assertDemoRequestsBootstrap()\n\n\nclass ClientInitiatedBootstrapFallbackOnly(BootstrapTest.Test):\n    PSK_IDENTITY = b'test-identity'\n    PSK_KEY = b'test-key'\n\n    def setUp(self):\n        # Using DTLS for Bootstrap Server allows us to check when does the client attempt to connect to it\n        # We need to use DTLS for regular server as well, as mixing security modes is not currently possible in demo\n        super().setUp(servers=[\n            Lwm2mServer(coap.DtlsServer(psk_key=self.PSK_KEY, psk_identity=self.PSK_IDENTITY))],\n            num_servers_passed=1,\n            bootstrap_server=Lwm2mServer(\n                coap.DtlsServer(psk_key=self.PSK_KEY, psk_identity=self.PSK_IDENTITY)),\n            extra_cmdline_args=['--identity',\n                                str(binascii.hexlify(self.PSK_IDENTITY), 'ascii'),\n                                '--key', str(binascii.hexlify(self.PSK_KEY), 'ascii')],\n            legacy_server_initiated_bootstrap_allowed=False)\n\n    def tearDown(self):\n        super().tearDown(auto_deregister=False)\n\n    def runTest(self):\n        # registration has been performed by setUp()\n        self.assertIsNotNone(self.serv.get_remote_addr())\n        self.assertEqual(self.get_socket_count(), 1)\n\n        # Trigger update\n        self.communicate('send-update')\n        update_pkt = self.assertDemoUpdatesRegistration(\n            self.serv, respond=False)\n        # Respond to it with 4.00 Bad Request to simulate some kind of client account expiration on server side.\n        self.serv.send(Lwm2mErrorResponse.matching(\n            update_pkt)(code=coap.Code.RES_BAD_REQUEST))\n        # This should cause client attempt to re-register.\n        register_pkt = self.assertDemoRegisters(self.serv, respond=False)\n        # To which we respond with 4.03 Forbidden, finishing off the communication.\n        self.serv.send(Lwm2mErrorResponse.matching(\n            register_pkt)(code=coap.Code.RES_FORBIDDEN))\n\n        # The client shall only now attempt bootstrap\n        self.assertIsNone(self.bootstrap_server.get_remote_addr())\n        self.assertDemoRequestsBootstrap()\n\n\n# Tests below take absurd amount of time when advance_time is unavailable\nclass BootstrapNoInteractionFromBootstrapServer(BootstrapTest.Test):\n    ACK_TIMEOUT = 1\n    MAX_RETRANSMIT = 1\n\n    def setUp(self):\n        # Done to have a relatively short EXCHANGE_LIFETIME\n        super().setUp(extra_cmdline_args=['--ack-random-factor', '1',\n                                          '--ack-timeout', '%s' % self.ACK_TIMEOUT,\n                                          '--max-retransmit', '%s' % self.MAX_RETRANSMIT])\n\n    def tearDown(self):\n        super().tearDown(auto_deregister=False)\n\n    def runTest(self):\n        # We should get Bootstrap Request now\n        self.assertDemoRequestsBootstrap()\n\n        self.assertEqual(1, self.get_socket_count())\n        self.advance_demo_time(TxParams(ack_timeout=self.ACK_TIMEOUT,\n                                        max_retransmit=self.MAX_RETRANSMIT).exchange_lifetime())\n        self.wait_until_socket_count(0, timeout_s=5)\n\n\nclass BootstrapNoInteractionFromBootstrapServerAfterSomeExchanges(BootstrapTest.Test):\n    ACK_TIMEOUT = 1\n    MAX_RETRANSMIT = 1\n\n    def setUp(self):\n        # Done to have a relatively short EXCHANGE_LIFETIME\n        super().setUp(extra_cmdline_args=['--ack-random-factor', '1',\n                                          '--ack-timeout', '%s' % self.ACK_TIMEOUT,\n                                          '--max-retransmit', '%s' % self.MAX_RETRANSMIT])\n\n    def tearDown(self):\n        super().tearDown(auto_deregister=False)\n\n    def runTest(self):\n        # Some random bootstrap operation, the data won't be used anyway.\n        self.perform_typical_bootstrap(server_iid=1,\n                                       security_iid=2,\n                                       server_uri='coap://127.0.0.1:9123',\n                                       security_mode=SecurityMode.NoSec,\n                                       finish=False)\n\n        self.assertEqual(1, self.get_socket_count())\n        self.advance_demo_time(TxParams(ack_timeout=self.ACK_TIMEOUT,\n                                        max_retransmit=self.MAX_RETRANSMIT).exchange_lifetime())\n        self.wait_until_socket_count(0, timeout_s=5)\n\n# In LwM2M specification we can find:\n# \n# If \"Bootstrap-Request\" operation is used in Step #0, the bootstrap procedure fails when the LwM2M Client does not\n# receive the \"Bootstrap-Finish\" operation after the EXCHANGE_LIFETIME time period expired. The EXCHANGE_LIFETIME\n# parameter is defined in [CoAP].\n# \n# It can be interpreted in two ways - after sending a “Bootstrap-Request”, the procedure is considered to have failed\n# if:\n# - a \"Bootstrap-Finish\" operation is not received within the period defined by EXCHANGE_LIFETIME\n#   counted from the \"Bootstrap-Request\" operation.\n# - a \"Bootstrap-Finish\" operation is not received within the period defined by EXCHANGE_LIFETIME\n#   counted from the last Bootstrap-related operation (excluding Bootstrap-Pack-Request).\n# \n# This test is for documentation purposes and shows that, in our library, we follow the second interpretation.\nclass BootstrapNoInteractionFromBootstrapServerAfterSomeExchangesCheckFinishTimeout(BootstrapTest.Test):\n    ACK_TIMEOUT = 1\n    MAX_RETRANSMIT = 1\n\n    def setUp(self):\n        # Done to have a relatively short EXCHANGE_LIFETIME\n        super().setUp(extra_cmdline_args=['--ack-random-factor', '1',\n                                          '--ack-timeout', '%s' % self.ACK_TIMEOUT,\n                                          '--max-retransmit', '%s' % self.MAX_RETRANSMIT])\n\n    def tearDown(self):\n        super().tearDown(auto_deregister=False)\n\n    def runTest(self):\n        # Some random bootstrap operation, the data won't be used anyway.\n        self.perform_typical_bootstrap(server_iid=1,\n                                       security_iid=2,\n                                       server_uri='coap://127.0.0.1:9123',\n                                       security_mode=SecurityMode.NoSec,\n                                       finish=False)\n\n        half_exchange_lifetime = TxParams(ack_timeout=self.ACK_TIMEOUT,\n                                        max_retransmit=self.MAX_RETRANSMIT).exchange_lifetime() / 2\n        self.advance_demo_time(half_exchange_lifetime)\n\n        # Bootstrap write operation after exchange_lifetime / 2\n        self.write_resource(self.bootstrap_server, oid=OID.Security, iid=2, rid=RID.Security.Mode,\n                                content=str(SecurityMode.PreSharedKey.value))\n        self.advance_demo_time(half_exchange_lifetime)\n        \n        # Some time for scheduled jobs\n        time.sleep(10)\n        # If there were no socket, that would mean the first interpretation is correct\n        self.assertEqual(1, self.get_socket_count())\n\n        self.advance_demo_time(half_exchange_lifetime - 11)\n        \n        self.assertEqual(1, self.get_socket_count())\n        self.wait_until_socket_count(0, timeout_s=5)\n\n\nclass DtlsBootstrap:\n    class Test(BootstrapTest.Test):\n        PSK_IDENTITY = b'test-identity'\n        PSK_KEY = b'test-key'\n\n        # example ciphersuites, source: https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml\n        SUPPORTED_CIPHER = 0xC0A8  # TLS_PSK_WITH_AES_128_CCM_8, recommended by RFC8252\n        # TLS_PSK_WITH_AES_256_CCM_8, supported by mbed TLS and OpenSSL, but not by pymbedtls\n        UNSUPPORTED_CIPHER = 0xC0A9\n\n        def setUp(self, **kwargs):\n            if 'servers' not in kwargs:\n                kwargs = kwargs.copy()\n                kwargs['servers'] = [Lwm2mServer(\n                    coap.DtlsServer(psk_key=self.PSK_KEY, psk_identity=self.PSK_IDENTITY))]\n            super().setUp(**kwargs)\n\n\nclass DtlsTlsCiphersuitesSingleSupportedCipher(DtlsBootstrap.Test):\n    \"\"\"\n    Verifies that setting DTLS/TLS Ciphersuite resource to a cipher supported\n    by the server makes the client register correctly.\n    \"\"\"\n\n    def runTest(self):\n        self.perform_typical_bootstrap(server_iid=1,\n                                       security_iid=2,\n                                       server_uri='coaps://127.0.0.1:%s' % (\n                                           self.serv.get_listen_port(),),\n                                       security_mode=SecurityMode.PreSharedKey,\n                                       secure_identity=self.PSK_IDENTITY,\n                                       secure_key=self.PSK_KEY,\n                                       additional_security_data=TLV.make_multires(\n                                           RID.Security.DtlsTlsCiphersuite,\n                                           enumerate([\n                                               self.SUPPORTED_CIPHER])).serialize())\n        self.assertDemoRegisters()\n\n\nclass DtlsTlsCiphersuitesSingleUnsupportedCipher(DtlsBootstrap.Test):\n    \"\"\"\n    Verifies that setting DTLS/TLS Ciphersuite resource to a cipher NOT\n    supported by the server makes the client fail to register, and not attempt\n    to continue. A DTLS alert should be observed.\n    \"\"\"\n\n    def tearDown(self):\n        super().tearDown(auto_deregister=False)\n\n    def runTest(self):\n        self.perform_typical_bootstrap(server_iid=1,\n                                       security_iid=2,\n                                       server_uri='coaps://127.0.0.1:%s' % (\n                                           self.serv.get_listen_port(),),\n                                       security_mode=SecurityMode.PreSharedKey,\n                                       secure_identity=self.PSK_IDENTITY,\n                                       secure_key=self.PSK_KEY,\n                                       additional_security_data=TLV.make_multires(\n                                           RID.Security.DtlsTlsCiphersuite,\n                                           enumerate([self.UNSUPPORTED_CIPHER])).serialize())\n        with self.assertRaisesRegex(RuntimeError,\n                                    r'The server has no ciphersuites in common|The handshake negotiation failed'):\n            self.assertDemoRegisters()\n        self.assertDemoRequestsBootstrap()\n\n\nclass DtlsTlsCiphersuitesUnsupportedAndSupportedCiphers(DtlsBootstrap.Test):\n    \"\"\"\n    Verifies that setting DTLS/TLS Ciphersuite resource to a list that contains\n    ciphers both supported and unsupported by server makes the client register\n    correctly.\n    \"\"\"\n\n    def runTest(self):\n        self.perform_typical_bootstrap(server_iid=1,\n                                       security_iid=2,\n                                       server_uri='coaps://127.0.0.1:%s' % (\n                                           self.serv.get_listen_port(),),\n                                       security_mode=SecurityMode.PreSharedKey,\n                                       secure_identity=self.PSK_IDENTITY,\n                                       secure_key=self.PSK_KEY,\n                                       additional_security_data=TLV.make_multires(\n                                           RID.Security.DtlsTlsCiphersuite, enumerate(\n                                               [self.UNSUPPORTED_CIPHER,\n                                                self.SUPPORTED_CIPHER])).serialize())\n        self.assertDemoRegisters()\n\n\nclass DtlsTlsConnectionFailedSetsAlertCode(BootstrapTest.Test):\n    def runTest(self):\n        dtls_server = coap.DtlsServer(psk_identity=b'foo', psk_key=b'bar')\n        self.perform_typical_bootstrap(server_iid=2,\n                                       security_iid=2,\n                                       server_uri='coaps://127.0.0.1:%d' % dtls_server.get_listen_port(),\n                                       lifetime=86400,\n                                       security_mode=SecurityMode.PreSharedKey,\n                                       binding='U',\n                                       secure_identity=b'foo',\n                                       secure_key=b'yyy')\n\n        try:\n            dtls_server.recv_raw(4096)\n        except RuntimeError as e:\n            self.assertIn('mbedtls_ssl_handshake failed', str(e))\n\n        # Bootstrap Finish succeeded, but the server was not reachable, due to bad credentials.\n        self.assertDemoRequestsBootstrap(timeout_s=5)\n\n        import json\n        response = json.loads(self.read_instance(self.bootstrap_server, oid=OID.Server, iid=2,\n                                                 accept=coap.ContentFormat.APPLICATION_LWM2M_SENML_JSON).content.decode())\n        for resource in response:\n            if resource['n'] == '/%d' % RID.Server.TlsDtlsAlertCode:\n                # 20 is \"bad_record_mac\" (see https://tools.ietf.org/html/rfc5246#section-7.2 for more details)\n                self.assertTrue(resource['v'] == 20)\n\n    def tearDown(self):\n        super().teardown_demo_with_servers(auto_deregister=False)\n\n\nclass BootstrapFallback(BootstrapTest.Test):\n    def setUp(self):\n        super().setUp(minimum_version='1.0', maximum_version='1.1')\n\n    def tearDown(self):\n        super().tearDown(auto_deregister=False)\n\n    def runTest(self):\n        self.assertDemoRequestsBootstrap(respond_with_error_code=coap.Code.RES_BAD_REQUEST,\n                                         preferred_content_format=coap.ContentFormat.APPLICATION_LWM2M_SENML_CBOR)\n        self.assertDemoRequestsBootstrap()\n\n\nclass BootstrapNoFallback(BootstrapTest.Test):\n    def setUp(self):\n        super().setUp(minimum_version='1.1', maximum_version='1.1')\n\n    def tearDown(self):\n        super().tearDown(auto_deregister=False)\n\n    def runTest(self):\n        self.assertDemoRequestsBootstrap(respond_with_error_code=coap.Code.RES_BAD_REQUEST,\n                                         preferred_content_format=coap.ContentFormat.APPLICATION_LWM2M_SENML_CBOR)\n        with self.assertRaises(socket.timeout):\n            self.bootstrap_server.recv(timeout_s=5)\n\n\nclass LastBootstrappedResource(BootstrapTest.Test):\n    def tearDown(self):\n        super().tearDown(auto_deregister=False)\n\n    def get_last_bootstrapped_timestamp(self, iid):\n        res = self.read_instance(self.bootstrap_server, oid=OID.Server,\n                                 iid=1, accept=coap.ContentFormat.APPLICATION_LWM2M_TLV)\n        self.assertEqual(coap.ContentFormat.APPLICATION_LWM2M_TLV,\n                         res.get_content_format())\n\n        tlv = TLV.parse(res.content)\n\n        for entry in tlv:\n            if entry.identifier == RID.Server.LastBootstrapped:\n                return entry\n\n    def runTest(self):\n        timestamp_before_bootstrap = int(time.time())\n        # Some random bootstrap operation, the data won't be used anyway.\n        self.perform_typical_bootstrap(server_iid=1,\n                                       security_iid=2,\n                                       server_uri='coap://127.0.0.1:%d' % self.serv.get_listen_port(),\n                                       security_mode=SecurityMode.NoSec,\n                                       finish=False)\n\n        last_bootstrapped_resource = self.get_last_bootstrapped_timestamp(1)\n        self.assertIsNotNone(last_bootstrapped_resource)\n        last_bootstrapped_timestamp = int.from_bytes(\n            last_bootstrapped_resource.value, byteorder='big')\n        self.assertGreaterEqual(\n            last_bootstrapped_timestamp, timestamp_before_bootstrap)\n        timestamp_before_bootstrap = last_bootstrapped_timestamp\n        time.sleep(1)\n\n        # Modify only Security object\n        self.write_instance(self.bootstrap_server, oid=OID.Security, iid=2,\n                            content=TLV.make_resource(\n                                RID.Security.ServerURI,\n                                'coap://127.0.0.1:%d' % self.serv.get_listen_port()).serialize()\n                                    + TLV.make_resource(RID.Security.Bootstrap, 0).serialize()\n                                    + TLV.make_resource(RID.Security.Mode,\n                                                        SecurityMode.NoSec.value).serialize()\n                                    + TLV.make_resource(RID.Security.ShortServerID, 42).serialize()\n                                    + TLV.make_resource(RID.Security.PKOrIdentity, b'').serialize()\n                                    + TLV.make_resource(RID.Security.SecretKey, b'').serialize())\n\n        last_bootstrapped_resource = self.get_last_bootstrapped_timestamp(1)\n        self.assertIsNotNone(last_bootstrapped_resource)\n        last_bootstrapped_timestamp = int.from_bytes(\n            last_bootstrapped_resource.value, byteorder='big')\n        self.assertGreaterEqual(\n            last_bootstrapped_timestamp, timestamp_before_bootstrap)\n        timestamp_before_bootstrap = last_bootstrapped_timestamp\n        time.sleep(1)\n\n        # Modify only Server object\n        self.write_instance(self.bootstrap_server, oid=OID.Server, iid=1,\n                            content=TLV.make_resource(\n                                RID.Server.Lifetime, 86400).serialize()\n                                    + TLV.make_resource(RID.Server.ShortServerID, 42).serialize()\n                                    + TLV.make_resource(RID.Server.NotificationStoring,\n                                                        True).serialize()\n                                    + TLV.make_resource(RID.Server.Binding, 'U').serialize())\n\n        last_bootstrapped_resource = self.get_last_bootstrapped_timestamp(1)\n        self.assertIsNotNone(last_bootstrapped_resource)\n        last_bootstrapped_timestamp = int.from_bytes(\n            last_bootstrapped_resource.value, byteorder='big')\n        self.assertGreaterEqual(\n            last_bootstrapped_timestamp, timestamp_before_bootstrap)\n\n\nclass BootstrappedSecurityInstanceBindingAndUriMismatch(BootstrapTest.Test):\n    def tearDown(self):\n        super().tearDown(auto_deregister=False)\n\n    def runTest(self):\n        self.assertDemoRequestsBootstrap(endpoint=DEMO_ENDPOINT_NAME)\n\n        req = Lwm2mDelete('/')\n        self.bootstrap_server.send(req)\n        self.assertMsgEqual(Lwm2mDeleted.matching(req)(),\n                            self.bootstrap_server.recv())\n\n        self.write_instance(self.bootstrap_server, oid=OID.Server, iid=1,\n                            content=TLV.make_resource(\n                                RID.Server.Lifetime, 86400).serialize()\n                                    + TLV.make_resource(RID.Server.ShortServerID, 42).serialize()\n                                    + TLV.make_resource(RID.Server.NotificationStoring,\n                                                        True).serialize()\n                                    + TLV.make_resource(RID.Server.Binding, 'N').serialize())\n\n        import socket\n        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n        s.bind(('127.0.0.1', 0))\n        s.listen()\n        s.settimeout(5)\n\n        self.write_instance(self.bootstrap_server, oid=OID.Security, iid=2,\n                            content=TLV.make_resource(\n                                RID.Security.ServerURI,\n                                'coap+tcp://127.0.0.1:%d' % s.getsockname()[1]).serialize()\n                                    + TLV.make_resource(RID.Security.Bootstrap, 0).serialize()\n                                    + TLV.make_resource(RID.Security.Mode,\n                                                        SecurityMode.NoSec.value).serialize()\n                                    + TLV.make_resource(RID.Security.ShortServerID, 42).serialize()\n                                    + TLV.make_resource(RID.Security.PKOrIdentity, b'').serialize()\n                                    + TLV.make_resource(RID.Security.SecretKey, b'').serialize())\n\n        self.perform_bootstrap_finish()\n\n        with self.assertRaises(socket.timeout):\n            s.accept()\n\n        self.assertDemoRequestsBootstrap(endpoint=DEMO_ENDPOINT_NAME)\n\n\n# NOTE: consecutive Bootstrap Requests are sent with exponential backoff (see schedule_request_bootstrap()),\n# starting with 3s. If we were to test like k different values for the resource that causes re-bootstrapping,\n# it makes more sense to do k separate tests and pay 3 seconds for each (O(k)), rather than one with cost:\n# 3*(1) + 3*(2) + 3*(3) + ... + 3*(k) = O(k^2)\nclass BootstrapSingleServerRegistrationOnFailureNotSet(BootstrapTest.Test):\n    BOOTSTRAP_ON_REGISTRATION_FAILURE = None\n\n    def tearDown(self):\n        super().tearDown(auto_deregister=False)\n\n    def runTest(self):\n        self.perform_typical_bootstrap(clear_everything=True,\n                                       server_iid=1,\n                                       security_iid=2,\n                                       # Definitely an incorrect address.\n                                       server_uri='coap://256.0.0.1:5683',\n                                       security_mode=SecurityMode.NoSec,\n                                       bootstrap_on_registration_failure=self.BOOTSTRAP_ON_REGISTRATION_FAILURE,\n                                       finish=True)\n        # See the comment above to understand where the timeout_s came from.\n        self.assertDemoRequestsBootstrap(timeout_s=3 + 1)\n\n\nclass BootstrapSingleServerRegistrationOnFailureFalse(\n    BootstrapSingleServerRegistrationOnFailureNotSet):\n    BOOTSTRAP_ON_REGISTRATION_FAILURE = False\n\n\nclass BootstrapSingleServerRegistrationOnFailureTrue(\n    BootstrapSingleServerRegistrationOnFailureNotSet):\n    BOOTSTRAP_ON_REGISTRATION_FAILURE = True\n\n\nclass BootstrapMultiServerRegistrationOnFailureTrue(BootstrapTest.Test):\n    def tearDown(self):\n        super().tearDown(auto_deregister=False)\n\n    def runTest(self):\n        self.assertDemoRequestsBootstrap()\n\n        self.add_server(server_iid=2,\n                        security_iid=2,\n                        server_uri='coap://127.0.0.1:%d' % self.serv.get_listen_port(),\n                        bootstrap_on_registration_failure=False)\n        self.add_server(server_iid=3,\n                        security_iid=3,\n                        server_uri='coap://256.0.0.1:5683',\n                        bootstrap_on_registration_failure=True)\n        self.perform_bootstrap_finish()\n        self.assertDemoRequestsBootstrap()\n\n\nclass BootstrapMultiServerRegistrationOnFailureNotSet(BootstrapTest.Test):\n    def tearDown(self):\n        super().tearDown(auto_deregister=False)\n\n    def runTest(self):\n        self.assertDemoRequestsBootstrap()\n\n        self.add_server(server_iid=2,\n                        security_iid=2,\n                        server_uri='coap://127.0.0.1:%d' % self.serv.get_listen_port())\n        self.add_server(server_iid=3,\n                        security_iid=3,\n                        server_uri='coap://256.0.0.1:5683')\n        self.perform_bootstrap_finish()\n        self.assertDemoRequestsBootstrap()\n\n\nclass BootstrapMultiServerRegistrationOnFailureFalse(BootstrapTest.Test):\n    def runTest(self):\n        self.assertDemoRequestsBootstrap()\n\n        self.add_server(server_iid=2,\n                        security_iid=2,\n                        server_uri='coap://127.0.0.1:%d' % self.serv.get_listen_port(),\n                        bootstrap_on_registration_failure=False)\n        self.add_server(server_iid=3,\n                        security_iid=3,\n                        server_uri='coap://256.0.0.1:5683',\n                        bootstrap_on_registration_failure=False)\n        self.perform_bootstrap_finish()\n        self.assertDemoRegisters(self.serv)\n\n        with self.assertRaises(socket.timeout):\n            print(self.bootstrap_server.recv(timeout_s=3))\n\n\nclass NoBootstrapAfterCompleteFail(BootstrapTest.Test):\n    PSK_IDENTITY = b'test-identity'\n    PSK_KEY = b'test-key'\n\n    def setUp(self, **kwargs):\n        # bootstrap server - PSK\n        # management server - NoSec\n        super().setUp(servers=[Lwm2mServer(coap.Server())],\n                      bootstrap_server=Lwm2mServer(coap.DtlsServer(psk_identity=self.PSK_IDENTITY,\n                                                                   psk_key=self.PSK_KEY)),\n                      psk_identity=self.PSK_IDENTITY, psk_key=self.PSK_KEY,\n                      legacy_server_initiated_bootstrap_allowed=False)\n\n    def tearDown(self):\n        super().tearDown(auto_deregister=False)\n\n    def runTest(self):\n        self.perform_typical_bootstrap(server_iid=2,\n                                       security_iid=2,\n                                       server_uri='coap://127.0.0.1:%d' % self.serv.get_listen_port(),\n                                       lifetime=10)\n        self.assertDemoRegisters(self.serv, lifetime=10)\n\n        with self.serv.fake_close():\n            with self.bootstrap_server.fake_close():\n                # let lifetime pass and everything fail\n                time.sleep(10)\n\n        self.communicate('reconnect')\n        # client shall connect to the regular server...\n        self.assertDemoRegisters(self.serv, lifetime=10)\n        # ...but don't attempt doing anything with the Bootstrap Server, not even handshake\n        self.bootstrap_server._raw_udp_socket.settimeout(2)\n        with self.assertRaises(socket.timeout):\n            self.bootstrap_server._raw_udp_socket.recv(4096)\n\n        # check that another registration failure will cause a bootstrap attempt\n        with self.serv.fake_close():\n            self.assertDtlsReconnect(self.bootstrap_server, timeout_s=10)\n            self.assertDemoRequestsBootstrap()\n\n\nclass BootstrapReconnectAfterCompleteFail(BootstrapTest.Test):\n    PSK_IDENTITY = b'test-identity'\n    PSK_KEY = b'test-key'\n\n    def setUp(self, **kwargs):\n        super().setUp(bootstrap_server=Lwm2mServer(\n            coap.DtlsServer(psk_identity=self.PSK_IDENTITY, psk_key=self.PSK_KEY)),\n            psk_identity=self.PSK_IDENTITY, psk_key=self.PSK_KEY,\n            legacy_server_initiated_bootstrap_allowed=False)\n\n    def tearDown(self):\n        super().tearDown(auto_deregister=False)\n\n    def runTest(self):\n        pkt = self.bootstrap_server.recv()\n        self.assertMsgEqual(Lwm2mRequestBootstrap(endpoint_name=DEMO_ENDPOINT_NAME), pkt)\n\n        with self.bootstrap_server.fake_close():\n            # let everything fail\n            time.sleep(10)\n\n        self.communicate('reconnect')\n        # client shall connect to the Bootstrap Server\n        self.assertDtlsReconnect(self.bootstrap_server, timeout_s=10)\n        self.assertDemoRequestsBootstrap()\n\n\nclass BootstrapCheckOngoingRegistrationsWithLegacyServerInitiated(BootstrapTest.Test):\n    def runTest(self):\n        # Client-Initiated Bootstrap\n        self.assertDemoRequestsBootstrap()\n        self.assertTrue(self.ongoing_registration_exists())\n        self.add_server(server_iid=1,\n                        security_iid=2,\n                        server_uri='coap://127.0.0.1:%d' % self.serv.get_listen_port())\n        self.perform_bootstrap_finish()\n\n        # Registration\n        pkt = self.assertDemoRegisters(respond=False)\n        self.assertTrue(self.ongoing_registration_exists())\n        self.serv.send(Lwm2mCreated.matching(pkt)(location='/rd/demo'))\n        self.assertFalse(self.ongoing_registration_exists())\n\n        # Server-Initiated Bootstrap\n        req = Lwm2mDelete('/')\n        self.bootstrap_server.send(req)\n        self.assertMsgEqual(Lwm2mDeleted.matching(req)(),\n                            self.bootstrap_server.recv())\n        self.assertTrue(self.ongoing_registration_exists())\n\n    def tearDown(self):\n        super().tearDown(auto_deregister=False)\n\n\nclass BootstrapCheckOngoingRegistrationsWithoutLegacyServerInitiated(BootstrapTest.Test):\n    def setUp(self):\n        super().setUp(maximum_version='1.1', legacy_server_initiated_bootstrap_allowed=False)\n\n    def runTest(self):\n        # Client-Initiated Bootstrap\n        self.assertDemoRequestsBootstrap(\n            preferred_content_format=coap.ContentFormat.APPLICATION_LWM2M_SENML_CBOR)\n        self.assertTrue(self.ongoing_registration_exists())\n        self.add_server(server_iid=1, security_iid=2,\n                        server_uri='coap://127.0.0.1:%d' % self.serv.get_listen_port())\n        self.perform_bootstrap_finish()\n\n        # Registration\n        pkt = self.assertDemoRegisters(version='1.1', respond=False)\n        self.assertTrue(self.ongoing_registration_exists())\n        self.serv.send(Lwm2mCreated.matching(pkt)(location='/rd/demo'))\n        self.assertFalse(self.ongoing_registration_exists())\n\n        # Server-Initiated Bootstrap\n        self.execute_resource(self.serv, OID.Server, 1, RID.Server.RequestBootstrapTrigger)\n        self.assertDemoRequestsBootstrap(\n            preferred_content_format=coap.ContentFormat.APPLICATION_LWM2M_SENML_CBOR)\n        self.assertTrue(self.ongoing_registration_exists())\n\n    def tearDown(self):\n        super().tearDown(auto_deregister=False)\n\n\nclass BootstrapCuriousServerDisabling(BootstrapTest.Test):\n    def setUp(self):\n        super().setUp(maximum_version='1.1', legacy_server_initiated_bootstrap_allowed=False)\n\n    def runTest(self):\n        self.assertDemoRequestsBootstrap(\n            preferred_content_format=coap.ContentFormat.APPLICATION_LWM2M_SENML_CBOR)\n\n        # rewrite the bootstrap instance\n        self.write_instance(self.bootstrap_server, oid=OID.Security, iid=1,\n                            content=TLV.make_resource(RID.Security.ServerURI,\n                                                      'coap://127.0.0.1:%d/' % self.bootstrap_server.get_listen_port()).serialize() + TLV.make_resource(\n                                RID.Security.ClientHoldOffTime, 1).serialize())\n        self.add_server(server_iid=1, security_iid=2, binding='UQ',\n                        server_uri='coap://127.0.0.1:%d' % self.serv.get_listen_port(),\n                        additional_security_data=TLV.make_resource(RID.Security.ClientHoldOffTime,\n                                                                   1).serialize())\n        self.perform_bootstrap_finish()\n\n        # Registration\n        self.assertDemoRegisters(version='1.1', lwm2m11_queue_mode=True)\n        self.wait_until_socket_count(expected=1, timeout_s=5)\n"
  },
  {
    "path": "tests/integration/suites/default/bootstrap_discover.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nfrom framework.lwm2m_test import *\n\nfrom . import bootstrap_client\nfrom . import bootstrap_server as bs\n\n\ndef expected_enabler_version_string(version):\n    return {\n        '1.0': 'lwm2m=\"1.0\",',\n        '1.1': '</>;lwm2m=1.1,',\n    }[version]\n\n\nclass BootstrapDiscoverBase:\n    class Test(bs.BootstrapServer.Test):\n        def setUp(self, version, **kwargs):\n            self.lwm2m_version = version\n            kwargs['maximum_version'] = version\n            super().setUp(**kwargs)\n\n        def expected_enabler_version_string(self):\n            return expected_enabler_version_string(self.lwm2m_version)\n\n\nclass BootstrapDiscover:\n    class FullNoServersTest(BootstrapDiscoverBase.Test,\n                            test_suite.Lwm2mDmOperations):\n        def runTest(self):\n            uri = ';uri=\"coap://127.0.0.1:{port_bs}\"'.format(\n                port_bs=self.bootstrap_server.get_listen_port())\n            if self.lwm2m_version == '1.0':\n                uri = ''\n\n            EXPECTED_PREFIX = '{prefix}</{sec}>,</{sec}/1>{uri},</{serv}>,</{ac}>,'.format(\n                prefix=self.expected_enabler_version_string(),\n                sec=OID.Security,\n                serv=OID.Server,\n                uri=uri,\n                ac=OID.AccessControl)\n            self.bootstrap_server.connect_to_client(\n                ('127.0.0.1', self.get_demo_port()))\n            discover_result = self.discover(self.bootstrap_server).content.decode()\n            self.assertLinkListValid(\n                discover_result[len(self.expected_enabler_version_string()):])\n            self.assertTrue(discover_result.startswith(EXPECTED_PREFIX))\n\n    class FullMultipleServersTest(BootstrapDiscoverBase.Test,\n                                  test_suite.Lwm2mDmOperations):\n        def runTest(self):\n            self.bootstrap_server.connect_to_client(\n                ('127.0.0.1', self.get_demo_port()))\n            self.write_instance(server=self.bootstrap_server, oid=OID.Server, iid=42,\n                                content=TLV.make_resource(\n                                    RID.Server.Lifetime, 60).serialize()\n                                + TLV.make_resource(RID.Server.Binding,\n                                                    \"U\").serialize()\n                                + TLV.make_resource(RID.Server.ShortServerID,\n                                                    11).serialize()\n                                + TLV.make_resource(RID.Server.NotificationStoring,\n                                                    True).serialize())\n\n            self.write_instance(server=self.bootstrap_server, oid=OID.Server, iid=24,\n                                content=TLV.make_resource(\n                                    RID.Server.Lifetime, 60).serialize()\n                                + TLV.make_resource(RID.Server.Binding,\n                                                    \"U\").serialize()\n                                + TLV.make_resource(RID.Server.ShortServerID,\n                                                    12).serialize()\n                                + TLV.make_resource(RID.Server.NotificationStoring,\n                                                    True).serialize())\n\n            uri2 = 'coap://127.0.0.1:9999'\n            self.write_instance(self.bootstrap_server, oid=OID.Security, iid=2,\n                                content=TLV.make_resource(\n                                    RID.Security.ServerURI, uri2).serialize()\n                                + TLV.make_resource(RID.Security.Bootstrap, 0).serialize()\n                                + TLV.make_resource(RID.Security.Mode,\n                                                    3).serialize()\n                                + TLV.make_resource(RID.Security.ShortServerID,\n                                                    11).serialize()\n                                + TLV.make_resource(RID.Security.PKOrIdentity,\n                                                    \"\").serialize()\n                                + TLV.make_resource(RID.Security.SecretKey, \"\").serialize())\n\n            uri10 = 'coap://127.0.0.1:11111'\n            self.write_instance(self.bootstrap_server, oid=OID.Security, iid=10,\n                                content=TLV.make_resource(\n                                    RID.Security.ServerURI, uri10).serialize()\n                                + TLV.make_resource(RID.Security.Bootstrap, 0).serialize()\n                                + TLV.make_resource(RID.Security.Mode,\n                                                    3).serialize()\n                                + TLV.make_resource(RID.Security.ShortServerID,\n                                                    12).serialize()\n                                + TLV.make_resource(RID.Security.PKOrIdentity,\n                                                    \"\").serialize()\n                                + TLV.make_resource(RID.Security.SecretKey, \"\").serialize())\n\n            uri = ';uri=\"coap://127.0.0.1:{port_bs}\"'.format(\n                port_bs=self.bootstrap_server.get_listen_port())\n            uri2 = ';uri=\"{uri2}\"'.format(uri2=uri2)\n            uri10 = ';uri=\"{uri10}\"'.format(uri10=uri10)\n            if self.lwm2m_version == '1.0':\n                # Bootstrap Discover does not report uri in 1.0 mode\n                uri = ''\n                uri2 = ''\n                uri10 = ''\n\n            EXPECTED_PREFIX = '{prefix}</{sec}>,</{sec}/1>{uri},' \\\n                              '</{sec}/2>;ssid=11{uri2},' \\\n                              '</{sec}/10>;ssid=12{uri10},' \\\n                              '</{serv}>,</{serv}/24>;ssid=12,' \\\n                              '</{serv}/42>;ssid=11,</{ac}>,'.format(\n                                  prefix=self.expected_enabler_version_string(),\n                                  sec=OID.Security,\n                                  serv=OID.Server,\n                                  ac=OID.AccessControl,\n                                  uri=uri,\n                                  uri2=uri2,\n                                  uri10=uri10).encode()\n            discover_result = self.discover(self.bootstrap_server)\n            self.assertEqual([coap.Option.CONTENT_FORMAT.APPLICATION_LINK],\n                             discover_result.get_options(coap.Option.CONTENT_FORMAT))\n            self.assertLinkListValid(discover_result.content.decode()[\n                                     len(self.expected_enabler_version_string()):])\n            expected_parameters = 1\n            self.assertIn(b'</%d>;ver=%s' % (OID.CellularConnectivity, self.objectVersionEncoder('1.1')),\n                          discover_result.content[len(self.expected_enabler_version_string()):])\n            # No more parameters\n            self.assertEqual(\n                expected_parameters + 1,\n                len(discover_result.content[len(EXPECTED_PREFIX):].split(b';')))\n            self.assertTrue(\n                discover_result.content.startswith(EXPECTED_PREFIX))\n\n\nclass BootstrapDiscover10FullNoServers(BootstrapDiscover.FullNoServersTest):\n    def setUp(self):\n        super().setUp(version='1.0')\n\n\nclass BootstrapDiscover11FullNoServers(BootstrapDiscover.FullNoServersTest):\n    def setUp(self):\n        super().setUp(version='1.1')\n\n\nclass BootstrapDiscover10FullMultipleServers(BootstrapDiscover.FullMultipleServersTest):\n    def setUp(self):\n        super().setUp(version='1.0')\n\n    def objectVersionEncoder(self, version):\n        return str.encode('\"' + version + '\"')\n\n\nclass BootstrapDiscover11FullMultipleServers(BootstrapDiscover.FullMultipleServersTest):\n    def setUp(self):\n        super().setUp(version='1.1')\n\n    def objectVersionEncoder(self, version):\n        return str.encode(version)\n\n\nclass BootstrapDiscoverOnNonexistingObject(bs.BootstrapServer.Test,\n                                           test_suite.Lwm2mDmOperations):\n    def runTest(self):\n        self.bootstrap_server.connect_to_client(\n            ('127.0.0.1', self.get_demo_port()))\n        self.discover(self.bootstrap_server, oid=42,\n                      expect_error_code=coap.Code.RES_NOT_FOUND)\n\n\nclass BootstrapDiscoverAfterEmptyInstancesWrite(bs.BootstrapServer.Test,\n                                                test_suite.Lwm2mDmOperations):\n    def runTest(self):\n        self.bootstrap_server.connect_to_client(\n            ('127.0.0.1', self.get_demo_port()))\n        self.write_object(self.bootstrap_server, oid=OID.Test,\n                          content=TLV.make_instance(0).serialize()\n                          + TLV.make_instance(1).serialize()\n                          + TLV.make_instance(2).serialize())\n        discover_result = self.discover(\n            self.bootstrap_server, oid=OID.Test).content\n        self.assertEqual(\n            'lwm2m=\"1.0\",</{oid}>,</{oid}/0>,</{oid}/1>,</{oid}/2>'.format(oid=OID.Test),\n            str(discover_result, 'ascii'))\n\n\nclass BootstrapDiscoverDepthInvalid(bs.BootstrapServer.Test, test_suite.Lwm2mDmOperations):\n    def setUp(self):\n        super().setUp(minimum_version='1.2', maximum_version='1.2')\n\n    def runTest(self):\n        self.assertDemoRequestsBootstrapPack(endpoint=DEMO_ENDPOINT_NAME,\n                                             respond_with_error_code=coap.Code.RES_NOT_FOUND)\n        self.assertDemoRequestsBootstrap(endpoint=DEMO_ENDPOINT_NAME,\n                                         preferred_content_format=coap.ContentFormat.APPLICATION_LWM2M_SENML_CBOR)\n        self.discover(self.bootstrap_server, oid=OID.Test, depth=1,\n                      expect_error_code=coap.Code.RES_BAD_REQUEST)\n"
  },
  {
    "path": "tests/integration/suites/default/bootstrap_factory.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nimport socket\n\nfrom framework.lwm2m_test import *\n\n\nclass BootstrapFactoryTest(test_suite.Lwm2mTest, test_suite.SingleServerAccessor):\n    def setUp(self):\n        extra_args = ['--bootstrap-timeout', '5']\n        self.setup_demo_with_servers(servers=1,\n                                     bootstrap_server=True,\n                                     extra_cmdline_args=extra_args)\n\n    def tearDown(self):\n        self.teardown_demo_with_servers()\n\n    def runTest(self):\n        self.assertEqual(2, self.get_socket_count())\n\n        # no message on the bootstrap socket - already bootstrapped\n        with self.assertRaises(socket.timeout):\n            print(self.bootstrap_server.recv(timeout_s=2))\n\n        # no changes\n        self.assertEqual(2, self.get_socket_count())\n\n        # still no message\n        with self.assertRaises(socket.timeout):\n            print(self.bootstrap_server.recv(timeout_s=4))\n\n        # Bootstrap Finish did not arrive, so Bootstrap Server timeout is not applicable here - no change\n        self.assertEqual(2, self.get_socket_count())\n\n        # Registration Update shall not include changes\n        self.communicate('send-update')\n        self.assertDemoUpdatesRegistration()\n\n        self.assertEqual(2, self.get_socket_count())\n"
  },
  {
    "path": "tests/integration/suites/default/bootstrap_holdoff.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nfrom framework_tools.lwm2m.tlv import TLV\nfrom framework.lwm2m_test import *\n\n\nclass BootstrapHoldoff:\n    class Test(test_suite.Lwm2mTest):\n        # Write for just a Security object but without Server Object\n        BS_WRITE = Lwm2mWrite('/%d/42' % (OID.Security,),\n                              TLV.make_resource(\n            RID.Security.ServerURI, 'coap://1.2.3.4:5678').serialize()\n            + TLV.make_resource(RID.Security.Bootstrap, 0).serialize()\n            + TLV.make_resource(RID.Security.Mode, 3).serialize()\n            + TLV.make_resource(RID.Security.ShortServerID, 42).serialize()\n            + TLV.make_resource(RID.Security.PKOrIdentity, b'').serialize()\n            + TLV.make_resource(RID.Security.SecretKey, b'').serialize(),\n            format=coap.ContentFormat.APPLICATION_LWM2M_TLV)\n\n        def setUp(self, holdoff_s=None, **kwargs):\n            extra_args = []\n            if holdoff_s is not None:\n                extra_args += ['--bootstrap-holdoff', str(holdoff_s)]\n            self.setup_demo_with_servers(servers=0,\n                                         bootstrap_server=True,\n                                         extra_cmdline_args=extra_args,\n                                         **kwargs)\n\n        def get_demo_port(self, server_index=None):\n            # wait for sockets initialization\n            # scheduler-based socket initialization might delay socket setup a bit;\n            # this loop is here to ensure `communicate()` call below works as\n            # expected\n            for _ in range(10):\n                if self.get_socket_count() > 0:\n                    break\n            else:\n                self.fail(\"sockets not initialized in time\")\n\n            return super().get_demo_port(server_index)\n\n        def receive_bs_req(self, timeout):\n            port = self.get_demo_port()\n            self.bootstrap_server.connect_to_client(('127.0.0.1', port))\n\n            # wait for holdoff to pass before client sends BS request\n            with self.assertRaises(socket.timeout):\n                self.bootstrap_server.recv(timeout_s=timeout-2)\n\n            # now it's time to receive BS request. There is +-2s of leeway\n            bs_req = self.bootstrap_server.recv(timeout_s=4)\n\n            self.assertMsgEqual(Lwm2mRequestBootstrap(endpoint_name=DEMO_ENDPOINT_NAME),\n                                bs_req)\n            self.bootstrap_server.send(Lwm2mChanged.matching(bs_req)())\n\n        def send_bs_finish(self):\n            req = Lwm2mBootstrapFinish()\n            self.bootstrap_server.send(req)\n            self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                                self.bootstrap_server.recv())\n\n\nclass BootstrapInitialHoldOffTimeToBig(BootstrapHoldoff.Test):\n    def setUp(self):\n        super().setUp(holdoff_s=30)\n\n    def runTest(self):\n        if self.read_log_until_match(b'Holdoff time is bigger then max allowed value, setting to 20.000000000 seconds', timeout_s=1.0) is None:\n            raise self.failureException(\n                'Holdoff time not limited to max value')\n\n        self.receive_bs_req(20)\n\n        self.bootstrap_server.send(self.BS_WRITE)\n        self.assertMsgEqual(Lwm2mChanged.matching(self.BS_WRITE)(),\n                            self.bootstrap_server.recv())\n\n        self.send_bs_finish()\n\n        self.request_demo_shutdown()\n\n\nclass BootstrapIncrementHoldOffTime(BootstrapHoldoff.Test):\n    def setUp(self):\n        super().setUp(holdoff_s=3)\n\n    def runTest(self):\n        holdoff = 3\n        holdoff_cap = 20\n        for _ in range(4):\n            if self.read_log_until_match((f'Scheduling bootstrap in {holdoff}.000000000').encode('utf-8'), timeout_s=1.0) is None:\n                raise self.failureException(\n                    f'Bootstrap not scheduled with holdoff {holdoff} sec')\n\n            self.receive_bs_req(holdoff)\n\n            # send BS data and expect positive response\n            self.bootstrap_server.send(self.BS_WRITE)\n            self.assertMsgEqual(Lwm2mChanged.matching(self.BS_WRITE)(),\n                                self.bootstrap_server.recv())\n\n            self.send_bs_finish()\n\n            holdoff = holdoff * 2\n            if (holdoff > holdoff_cap):\n                holdoff = holdoff_cap\n\n        self.request_demo_shutdown()\n"
  },
  {
    "path": "tests/integration/suites/default/bootstrap_pack.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nimport socket\n\nfrom framework_tools.lwm2m.coap.server import SecurityMode\nfrom framework.lwm2m_test import *\nfrom . import bootstrap_client\n\n\nclass BootstrapPackTest:\n    class Test(bootstrap_client.BootstrapTest.Test):\n        def setUp(self, *args, **kwargs):\n            super().setUp(minimum_version='1.2', maximum_version='1.2', *args, **kwargs)\n\n        def build_add_server_bp(self,\n                                server_iid,\n                                security_iid,\n                                server_uri,\n                                lifetime=86400,\n                                secure_identity=b'',\n                                secure_key=b'',\n                                security_mode: SecurityMode = SecurityMode.NoSec,\n                                binding=\"U\",\n                                bootstrap_on_registration_failure=None):\n            sec_pref = '0/' + str(security_iid) + '/'\n            security_data = [\n                {SenmlLabel.BASE_NAME: '/',\n                 SenmlLabel.NAME: sec_pref + '0', SenmlLabel.STRING: server_uri},\n                {SenmlLabel.NAME: sec_pref + '1', SenmlLabel.BOOL: False},\n                {SenmlLabel.NAME: sec_pref + '2', SenmlLabel.VALUE: security_mode.value}\n            ]\n            if secure_identity:\n                security_data += [\n                    {SenmlLabel.NAME: sec_pref + '3', SenmlLabel.OPAQUE: secure_identity}]\n            if secure_key:\n                security_data += [{SenmlLabel.NAME: sec_pref + '5', SenmlLabel.OPAQUE: secure_key}]\n            security_data += [{SenmlLabel.NAME: sec_pref + '10', SenmlLabel.VALUE: server_iid}]\n\n            srv_pref = '1/' + str(server_iid) + '/'\n            server_data = [\n                {SenmlLabel.NAME: srv_pref + '0', SenmlLabel.VALUE: server_iid},\n                {SenmlLabel.NAME: srv_pref + '1', SenmlLabel.VALUE: lifetime},\n                {SenmlLabel.NAME: srv_pref + '6', SenmlLabel.BOOL: False},\n                {SenmlLabel.NAME: srv_pref + '7', SenmlLabel.STRING: binding}\n            ]\n            if bootstrap_on_registration_failure is not None:\n                server_data += [{\n                    SenmlLabel.NAME: srv_pref + str(RID.Server.BootstrapOnRegistrationFailure),\n                    SenmlLabel.VALUE: bootstrap_on_registration_failure\n                }]\n\n            return security_data + server_data\n\n        def add_server_bp(self, pkt_to_match, *args, **kwargs):\n            self.bootstrap_server.send(Lwm2mContent.matching(pkt_to_match)(\n                content=CBOR.serialize(self.build_add_server_bp(*args, **kwargs)),\n                format=coap.ContentFormat.APPLICATION_LWM2M_SENML_CBOR))\n\n        def perform_bootstrap_pack(self, server_iid, security_iid, server_uri, lifetime=86400,\n                                   secure_identity=b'', secure_key=b'',\n                                   security_mode: SecurityMode = SecurityMode.NoSec,\n                                   holdoff_s=None, binding=\"U\",\n                                   endpoint=DEMO_ENDPOINT_NAME,\n                                   bootstrap_on_registration_failure=None,\n                                   bootstrap_request_timeout_s=None):\n            # For the first holdoff_s seconds, the client should wait for Bootstrap-Pack.\n            # Note that we subtract 1 second to take into account code execution delays.\n            if holdoff_s is None:\n                holdoff_s = self.holdoff_s or 0\n            no_message_s = max(0, holdoff_s - 1)\n            if no_message_s > 0:\n                with self.assertRaises(socket.timeout):\n                    print(self.bootstrap_server.recv(timeout_s=no_message_s))\n\n            # We should get BootstrapPack Request now\n            pkt = None\n            if bootstrap_request_timeout_s is None:\n                pkt = self.assertDemoRequestsBootstrapPack(endpoint=endpoint)\n            elif bootstrap_request_timeout_s >= 0:\n                pkt = self.assertDemoRequestsBootstrapPack(\n                    endpoint=endpoint, timeout_s=bootstrap_request_timeout_s)\n\n            self.add_server_bp(pkt_to_match=pkt,\n                               server_iid=server_iid,\n                               security_iid=security_iid,\n                               server_uri=server_uri,\n                               lifetime=lifetime,\n                               secure_identity=secure_identity,\n                               secure_key=secure_key,\n                               security_mode=security_mode,\n                               binding=binding,\n                               bootstrap_on_registration_failure=bootstrap_on_registration_failure)\n\n\nclass BootstrapPackBasicTest(BootstrapPackTest.Test):\n    def setUp(self):\n        super().setUp(holdoff_s=3, timeout_s=3)\n\n    def runTest(self):\n        self.perform_bootstrap_pack(server_iid=1,\n                                    security_iid=2,\n                                    server_uri='coap://127.0.0.1:%d' % self.serv.get_listen_port(),\n                                    lifetime=60)\n        self.assertDemoRegisters(self.serv, lifetime=60, version='1.2')\n\n\nclass BootstrapPackFallbackTest(BootstrapPackTest.Test):\n    def runTest(self):\n        # We should get BootstrapPackRequest in the beginning\n        pkt = self.assertDemoRequestsBootstrapPack(endpoint=DEMO_ENDPOINT_NAME)\n\n        # We respond with NOT_IMPLEMENTED\n        self.bootstrap_server.send(Lwm2mErrorResponse.matching(\n            pkt)(code=coap.Code.RES_NOT_IMPLEMENTED))\n\n        # Fallback to casual bootstrap\n        self.assertDemoRequestsBootstrap(endpoint=DEMO_ENDPOINT_NAME,\n                                         preferred_content_format=coap.ContentFormat.APPLICATION_LWM2M_SENML_CBOR)\n\n        # Now we can perform the casual bootstrap\n        req = Lwm2mDelete('/')\n        self.bootstrap_server.send(req)\n        self.assertMsgEqual(Lwm2mDeleted.matching(req)(),\n                            self.bootstrap_server.recv())\n        self.add_server(server_iid=1, security_iid=2,\n                        server_uri='coap://127.0.0.1:%d' % self.serv.get_listen_port(),\n                        lifetime=60)\n        self.perform_bootstrap_finish()\n\n        # And it should register\n        self.assertDemoRegisters(self.serv, lifetime=60, version='1.2')\n\n\nclass BootstrapPackBlockTest(BootstrapPackTest.Test):\n    def runTest(self):\n        # NOTE: The secure key field is unused,\n        # but it makes the data large enough to require a block transfer\n        pack = self.build_add_server_bp(\n            server_iid=1,\n            security_iid=2,\n            server_uri='coap://127.0.0.1:%d' % self.serv.get_listen_port(),\n            secure_key=random_stuff(102400))\n        pack_data = CBOR.serialize(pack)\n        pack_chunks = [pack_data[i:i + 1024] for i in range(0, len(pack_data), 1024)]\n\n        req = self.assertDemoRequestsBootstrapPack()\n        for seq_num in range(len(pack_chunks) - 1):\n            self.bootstrap_server.send(Lwm2mContent.matching(req)(\n                content=pack_chunks[seq_num],\n                format=coap.ContentFormat.APPLICATION_LWM2M_SENML_CBOR,\n                options=[coap.Option.BLOCK2(seq_num=seq_num, has_more=True, block_size=1024)]))\n\n            req = self.assertDemoRequestsBootstrapPack()\n            options = req.get_options(coap.Option.BLOCK2)\n            self.assertEqual(len(options), 1)\n            self.assertEqual(options[0].block_size(), 1024)\n            self.assertEqual(options[0].seq_num(), seq_num + 1)\n\n        self.bootstrap_server.send(Lwm2mContent.matching(req)(\n            content=pack_chunks[len(pack_chunks) - 1],\n            format=coap.ContentFormat.APPLICATION_LWM2M_SENML_CBOR,\n            options=[coap.Option.BLOCK2(seq_num=len(pack_chunks) - 1, has_more=False,\n                                        block_size=1024)]))\n\n        self.assertDemoRegisters(self.serv, version='1.2')\n"
  },
  {
    "path": "tests/integration/suites/default/bootstrap_server.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nimport socket\n\nfrom framework_tools.lwm2m.tlv import TLV\nfrom framework.lwm2m_test import *\nfrom . import bootstrap_holdoff as bsh\n\n\nclass BootstrapServer:\n    class Test(bsh.BootstrapHoldoff.Test):\n        def setUp(self, **kwargs):\n            super().setUp(holdoff_s=3, **kwargs)\n\n\nclass BootstrapServerTest(BootstrapServer.Test):\n    def runTest(self):\n        self.bootstrap_server.connect_to_client(('127.0.0.1', self.get_demo_port()))\n        req = Lwm2mWrite('/%d/42' % (OID.Server,),\n                         TLV.make_resource(RID.Server.Lifetime, 60).serialize()\n                         + TLV.make_resource(RID.Server.Binding, \"U\").serialize()\n                         + TLV.make_resource(RID.Server.ShortServerID, 42).serialize()\n                         + TLV.make_resource(RID.Server.NotificationStoring, True).serialize(),\n                         format=coap.ContentFormat.APPLICATION_LWM2M_TLV)\n        self.bootstrap_server.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.bootstrap_server.recv())\n\n        regular_serv = Lwm2mServer()\n        regular_serv_uri = 'coap://127.0.0.1:%d' % regular_serv.get_listen_port()\n\n        # Create Security object\n        req = Lwm2mWrite('/%d/42' % (OID.Security,),\n                         TLV.make_resource(RID.Security.ServerURI, regular_serv_uri).serialize()\n                         + TLV.make_resource(RID.Security.Bootstrap, 0).serialize()\n                         + TLV.make_resource(RID.Security.Mode, 3).serialize()\n                         + TLV.make_resource(RID.Security.ShortServerID, 42).serialize()\n                         + TLV.make_resource(RID.Security.PKOrIdentity, \"\").serialize()\n                         + TLV.make_resource(RID.Security.SecretKey, \"\").serialize(),\n                         format=coap.ContentFormat.APPLICATION_LWM2M_TLV)\n        self.bootstrap_server.send(req)\n\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.bootstrap_server.recv())\n\n        # no Client Initiated bootstrap\n        with self.assertRaises(socket.timeout):\n            print(self.bootstrap_server.recv(timeout_s=4))\n\n        # send Bootstrap Finish\n        req = Lwm2mBootstrapFinish()\n        self.bootstrap_server.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.bootstrap_server.recv())\n\n        self.assertDemoRegisters(server=regular_serv, lifetime=60)\n\n        # Bootstrap Delete / shall succeed\n        req = Lwm2mDelete('/')\n        self.bootstrap_server.send(req)\n        self.assertMsgEqual(Lwm2mDeleted.matching(req)(),\n                            self.bootstrap_server.recv())\n        # ...even twice\n        req = Lwm2mDelete('/')\n        self.bootstrap_server.send(req)\n        self.assertMsgEqual(Lwm2mDeleted.matching(req)(),\n                            self.bootstrap_server.recv())\n        # now send Bootstrap Finish\n        req = Lwm2mBootstrapFinish()\n        self.bootstrap_server.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.bootstrap_server.recv())\n\n        # the client will now start Client Initiated bootstrap, because it has no regular server connection\n        # this might happen after a backoff, if the Bootstrap Delete was handled before the response to Register\n        self.assertDemoRequestsBootstrap(timeout_s=20)\n\n        self.request_demo_shutdown()\n\n        regular_serv.close()\n\n\nclass BootstrapEmptyResourcesDoesNotSegfault(BootstrapServer.Test):\n    def runTest(self):\n        self.bootstrap_server.connect_to_client(('127.0.0.1', self.get_demo_port()))\n\n        req = Lwm2mWrite('/%d/42' % (OID.Security,),\n                         TLV.make_resource(RID.Security.ServerURI, 'coap://1.2.3.4:5678').serialize()\n                         + TLV.make_resource(RID.Security.Bootstrap, 0).serialize()\n                         + TLV.make_resource(RID.Security.Mode, 3).serialize()\n                         + TLV.make_resource(RID.Security.ShortServerID, 42).serialize()\n                         + TLV.make_resource(RID.Security.PKOrIdentity, b'').serialize()\n                         + TLV.make_resource(RID.Security.SecretKey, b'').serialize(),\n                         format=coap.ContentFormat.APPLICATION_LWM2M_TLV)\n\n        for _ in range(64):\n            self.bootstrap_server.send(req)\n            self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                                self.bootstrap_server.recv())\n\n        req = Lwm2mBootstrapFinish()\n        self.bootstrap_server.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.bootstrap_server.recv())\n        self.request_demo_shutdown()\n"
  },
  {
    "path": "tests/integration/suites/default/bootstrap_sync.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\nimport re\n\nfrom framework.lwm2m_test import *\n\n\nclass ClientIgnoresNonBootstrapTrafficDuringBootstrap(test_suite.Lwm2mSingleServerTest):\n    PSK_IDENTITY = b'test-identity'\n    PSK_KEY = b'test-key'\n\n    def setUp(self):\n        self.setup_demo_with_servers(servers=[Lwm2mServer(coap.DtlsServer(psk_key=self.PSK_KEY, psk_identity=self.PSK_IDENTITY))],\n                                     bootstrap_server=Lwm2mServer(coap.DtlsServer(psk_key=self.PSK_KEY, psk_identity=self.PSK_IDENTITY)),\n                                     extra_cmdline_args=['--identity',\n                                                         str(binascii.hexlify(self.PSK_IDENTITY), 'ascii'),\n                                                         '--key', str(binascii.hexlify(self.PSK_KEY), 'ascii')],\n                                     auto_register=False)\n\n        self.serv.listen()\n        self.bootstrap_server.listen()\n        self.assertDemoRegisters(self.serv)\n\n    def runTest(self):\n        req = Lwm2mCreate('/%d' % (OID.Test,))\n        self.serv.send(req)\n        res = self.serv.recv()\n        self.assertMsgEqual(Lwm2mCreated.matching(req)(), res)\n        self.assertEqual('/%d/0' % (OID.Test,), res.get_location_path())\n\n        # force an Update so that change to the data model does not get notified later\n        self.communicate('send-update')\n        self.assertDemoUpdatesRegistration(content=ANY)\n\n        req = Lwm2mRead(ResPath.Test[0].Counter)\n        self.serv.send(req)\n        res = self.serv.recv()\n        self.assertMsgEqual(Lwm2mContent.matching(req)(), res)\n        self.assertEqual(b'0', res.content)\n\n        # 1.0-style \"spurious\" Server Initiated Bootstrap\n        req = Lwm2mWrite(ResPath.Test[0].Counter, b'42')\n        self.bootstrap_server.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.bootstrap_server.recv())\n\n        # now regular server shall not be able to communicate with the client\n        req = Lwm2mExecute(ResPath.Test[0].IncrementCounter)\n        self.serv.send(req)\n        with self.assertRaises(OSError):\n            self.serv.recv(timeout_s=5)\n\n        self.assertEqual(1, self.get_socket_count())\n\n        # Bootstrap Finish\n        self.bootstrap_server.send(Lwm2mBootstrapFinish())\n        self.assertIsInstance(self.bootstrap_server.recv(), Lwm2mChanged)\n\n        # client reconnects with DTLS session resumption\n        self.assertDtlsReconnect()\n\n        # now we shall be able to do that Execute\n        req = Lwm2mExecute(ResPath.Test[0].IncrementCounter)\n        self.serv.send(req)\n        res = self.serv.recv()\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(), res)\n\n        # verify that Execute was performed just once\n        req = Lwm2mRead(ResPath.Test[0].Counter)\n        self.serv.send(req)\n        res = self.serv.recv()\n        self.assertMsgEqual(Lwm2mContent.matching(req)(), res)\n        self.assertEqual(b'43', res.content)\n"
  },
  {
    "path": "tests/integration/suites/default/bootstrap_transaction.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nfrom framework.lwm2m_test import *\nfrom suites.default.bootstrap_client import BootstrapTest\n\n\nclass BootstrapTransactionTest(test_suite.Lwm2mTest):\n    def setUp(self):\n        self.setup_demo_with_servers(servers=1, num_servers_passed=0, bootstrap_server=True,\n                                     extra_cmdline_args=['--bootstrap-timeout', '-1'])\n\n    def tearDown(self):\n        self.teardown_demo_with_servers(auto_deregister=False)\n\n    def runTest(self):\n        self.assertDemoRequestsBootstrap()\n\n        # Create Server object\n        req = Lwm2mWrite('/%d/1' % (OID.Server,),\n                         TLV.make_resource(RID.Server.Lifetime, 60).serialize() + TLV.make_resource(\n                             RID.Server.ShortServerID, 1).serialize() + TLV.make_resource(\n                             RID.Server.NotificationStoring, True).serialize() + TLV.make_resource(\n                             RID.Server.Binding, \"U\").serialize(),\n                         format=coap.ContentFormat.APPLICATION_LWM2M_TLV)\n        self.bootstrap_server.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.bootstrap_server.recv())\n\n        # Create Security object\n        regular_serv_uri = 'coap://127.0.0.1:%d' % self.servers[0].get_listen_port()\n\n        req = Lwm2mWrite('/%d/2' % (OID.Security,), TLV.make_resource(RID.Security.ServerURI,\n                                                                      regular_serv_uri).serialize() + TLV.make_resource(\n            RID.Security.Bootstrap, 0).serialize() + TLV.make_resource(RID.Security.Mode,\n                                                                       3).serialize() + TLV.make_resource(\n            RID.Security.ShortServerID, 1).serialize() + TLV.make_resource(\n            RID.Security.PKOrIdentity, \"\").serialize() + TLV.make_resource(RID.Security.SecretKey,\n                                                                           \"\").serialize(),\n                         format=coap.ContentFormat.APPLICATION_LWM2M_TLV)\n        self.bootstrap_server.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.bootstrap_server.recv())\n\n        # create incomplete Geo-Points object\n        req = Lwm2mWrite('/%d/42' % (OID.GeoPoints,),\n                         TLV.make_resource(RID.GeoPoints.Latitude, 42.0).serialize(),\n                         format=coap.ContentFormat.APPLICATION_LWM2M_TLV)\n        self.bootstrap_server.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.bootstrap_server.recv())\n\n        # send Bootstrap Finish\n        req = Lwm2mBootstrapFinish()\n        self.bootstrap_server.send(req)\n        self.assertMsgEqual(Lwm2mErrorResponse.matching(req)(coap.Code.RES_NOT_ACCEPTABLE),\n                            self.bootstrap_server.recv())\n\n        # still bootstrapping, so nothing shall be sent to the regular server\n        with self.assertRaises(socket.timeout):\n            print(self.servers[0].recv(timeout_s=5))\n\n        # check that still bootstrapping indeed\n        req = Lwm2mWrite('/%d/42' % (OID.GeoPoints,),\n                         TLV.make_resource(RID.GeoPoints.Longitude, 69.0).serialize(),\n                         format=coap.ContentFormat.APPLICATION_LWM2M_TLV)\n        self.bootstrap_server.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.bootstrap_server.recv())\n\n\nclass BootstrapTransactionPersistenceTest(test_suite.Lwm2mTest, test_suite.Lwm2mDmOperations):\n    def setUp(self):\n        self._dm_persistence_file = tempfile.NamedTemporaryFile()\n        self.setup_demo_with_servers(servers=0, bootstrap_server=True,\n                                     extra_cmdline_args=['--bootstrap-timeout', '-1',\n                                                         '--dm-persistence-file',\n                                                         self._dm_persistence_file.name])\n\n    def tearDown(self):\n        try:\n            self.teardown_demo_with_servers(auto_deregister=False)\n        finally:\n            self._dm_persistence_file.close()\n\n    def runTest(self):\n        self.assertDemoRequestsBootstrap()\n\n        # Create Server object without Binding\n        req = Lwm2mWrite('/%d/1' % (OID.Server,),\n                         TLV.make_resource(RID.Server.Lifetime, 60).serialize() + TLV.make_resource(\n                             RID.Server.ShortServerID, 1).serialize() + TLV.make_resource(\n                             RID.Server.NotificationStoring, True).serialize(),\n                         format=coap.ContentFormat.APPLICATION_LWM2M_TLV)\n        self.bootstrap_server.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.bootstrap_server.recv())\n\n        # Create Security object without URI\n        req = Lwm2mWrite('/%d/2' % (OID.Security,), TLV.make_resource(RID.Security.Bootstrap,\n                                                                      0).serialize() + TLV.make_resource(\n            RID.Security.Mode, 3).serialize() + TLV.make_resource(RID.Security.ShortServerID,\n                                                                  1).serialize() + TLV.make_resource(\n            RID.Security.PKOrIdentity, \"\").serialize() + TLV.make_resource(RID.Security.SecretKey,\n                                                                           \"\").serialize(),\n                         format=coap.ContentFormat.APPLICATION_LWM2M_TLV)\n        self.bootstrap_server.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.bootstrap_server.recv())\n\n        # send Bootstrap Finish\n        req = Lwm2mBootstrapFinish()\n        self.bootstrap_server.send(req)\n        self.assertMsgEqual(Lwm2mErrorResponse.matching(req)(coap.Code.RES_NOT_ACCEPTABLE),\n                            self.bootstrap_server.recv())\n\n        self.request_demo_shutdown()\n        self._terminate_demo()\n\n        self.bootstrap_server.reset()\n\n        self._start_demo(\n            ['--dm-persistence-file', self._dm_persistence_file.name] + self.make_demo_args(\n                DEMO_ENDPOINT_NAME, [], '1.0', '1.0', None))\n\n        # Demo shall launch, with the initial server configuration\n        self.assertDemoRequestsBootstrap()\n\n        # The previously created instances shall not be present\n        discover_result = self.discover(self.bootstrap_server).content.decode()\n        self.assertNotIn('</%d/1' % (OID.Server,), discover_result)\n        self.assertNotIn('</%d/2' % (OID.Security,), discover_result)\n\n\nclass UnregisteringAndRegisteringObjectsDuringBootstrapTransaction(BootstrapTest.Test):\n    def setUp(self):\n        super().setUp(num_servers_passed=1)\n\n    def tearDown(self):\n        super().tearDown(auto_deregister=False)\n\n    def runTest(self):\n        self.execute_resource(self.serv, OID.Server, 2, RID.Server.RequestBootstrapTrigger)\n\n        self.assertDemoRequestsBootstrap()\n        self.write_instance(self.bootstrap_server, oid=OID.Test, iid=42, content=b'')\n        self.communicate('unregister-object %d' % OID.Test, timeout=5)\n        self.communicate('reregister-object %d' % OID.Test, timeout=5)\n\n\nclass NotificationDuringBootstrap(BootstrapTest.Test, test_suite.Lwm2mDtlsSingleServerTest):\n    def setUp(self):\n        super().setUp(num_servers_passed=1)\n\n    def runTest(self):\n        self.observe(self.serv, OID.Device, 0, RID.Device.CurrentTime)\n\n        self.execute_resource(self.serv, OID.Server, 2, RID.Server.RequestBootstrapTrigger)\n        self.assertDemoRequestsBootstrap()\n        start_time = time.time()\n\n        notifications = 0\n        # Notifications generated before Request Bootstrap might still be sent\n        deadline = time.time() + 1\n        while True:\n            try:\n                self.assertIsInstance(self.serv.recv(deadline=deadline), Lwm2mNotify)\n                notifications += 1\n            except socket.timeout:\n                break\n\n        deadline += 4\n        with self.assertRaises(socket.timeout):\n            print(self.serv.recv(deadline=deadline))\n\n        self.serv.reset()\n\n        self.perform_bootstrap_finish()\n\n        # demo will resume DTLS session without sending any LwM2M messages\n        self.serv.listen()\n\n        notifications = 0\n        while True:\n            try:\n                self.assertIsInstance(self.serv.recv(timeout_s=0.8), Lwm2mNotify)\n                notifications += 1\n            except socket.timeout:\n                end_time = time.time()\n                break\n\n        self.assertTrue(\n            round(end_time - start_time - 2) <= notifications <= round(end_time - start_time + 2))\n\n\nclass NotificationDuringBootstrapInQueueMode(BootstrapTest.Test,\n                                             test_suite.Lwm2mDtlsSingleServerTest):\n    def setUp(self):\n        super().setUp(num_servers_passed=1, extra_cmdline_args=['--binding=UQ'],\n                      auto_register=False)\n        # demo will perform all DTLS handshakes before sending Register\n        self.serv.listen()\n        self.bootstrap_server.listen()\n        self.assertDemoRegisters(binding='UQ')\n\n    def runTest(self):\n        self.observe(self.serv, OID.Device, 0, RID.Device.CurrentTime)\n\n        self.execute_resource(self.serv, OID.Server, 2, RID.Server.RequestBootstrapTrigger)\n        self.assertDemoRequestsBootstrap()\n        start_time = time.time()\n\n        notifications = 0\n        # Notifications generated before Request Bootstrap might still be sent\n        deadline = time.time() + 1\n        while True:\n            try:\n                self.assertIsInstance(self.serv.recv(deadline=deadline), Lwm2mNotify)\n                notifications += 1\n            except socket.timeout:\n                break\n\n        deadline += 4\n        with self.assertRaises(socket.timeout):\n            print(self.serv.recv(deadline=deadline))\n\n        self.serv.reset()\n\n        self.perform_bootstrap_finish()\n\n        # demo will resume DTLS session without sending any LwM2M messages\n        self.serv.listen()\n\n        while True:\n            try:\n                self.assertIsInstance(self.serv.recv(timeout_s=0.8), Lwm2mNotify)\n                notifications += 1\n            except socket.timeout:\n                end_time = time.time()\n                break\n\n        self.assertTrue(\n            round(end_time - start_time - 2) <= notifications <= round(end_time - start_time + 2))\n\n\nclass ChangeServersDuringBootstrap(BootstrapTest.Test):\n    def setUp(self):\n        super().setUp(servers=2, num_servers_passed=2)\n\n    def tearDown(self):\n        super().tearDown(deregister_servers=[self.servers[0]])\n\n    def runTest(self):\n        self.servers[0].reset()\n        self.servers[1].reset()\n        self.bootstrap_server.connect_to_client(('127.0.0.1', self.get_demo_port()))\n        for i in (0, 1):\n            iid = i + 2\n            self.write_instance(self.bootstrap_server, OID.AccessControl, i + 1000,\n                                TLV.make_resource(RID.AccessControl.TargetOID,\n                                                  OID.Server).serialize() + TLV.make_resource(\n                                    RID.AccessControl.TargetIID,\n                                    iid).serialize() + TLV.make_resource(RID.AccessControl.Owner,\n                                                                         iid).serialize())\n        self.perform_bootstrap_finish()\n\n        self.assertDemoRegisters(self.servers[0])\n        self.assertDemoRegisters(self.servers[1])\n        self.coap_ping(self.servers[0])\n        self.coap_ping(self.servers[1])\n\n        self.execute_resource(self.servers[0], OID.Server, 2, RID.Server.RequestBootstrapTrigger)\n        self.assertDemoRequestsBootstrap()\n        self.servers[0].reset()\n        self.servers[1].reset()\n\n        self.communicate('trim-servers 2', timeout=5)\n\n        with self.assertRaises(socket.timeout):\n            print(self.servers[0].recv(timeout_s=5))\n        with self.assertRaises(socket.timeout):\n            print(self.servers[1].recv(timeout_s=5))\n\n        self.perform_bootstrap_finish()\n        self.assertDemoRegisters(self.servers[0])\n\n\nclass DisableServerDuringBootstrap(BootstrapTest.Test):\n    def setUp(self):\n        super().setUp(num_servers_passed=1)\n\n    def tearDown(self):\n        super().tearDown(auto_deregister=False)\n\n    def runTest(self):\n        self.execute_resource(self.serv, OID.Server, 2, RID.Server.RequestBootstrapTrigger)\n        self.serv.reset()\n        self.assertDemoRequestsBootstrap()\n\n        self.communicate('disable-server 2 3', timeout=5)\n\n        with self.assertRaises(socket.timeout):\n            print(self.serv.recv(timeout_s=5))\n\n\nclass EnableServerDuringBootstrap(BootstrapTest.Test):\n    def setUp(self):\n        super().setUp(num_servers_passed=1)\n\n    def tearDown(self):\n        super().tearDown(auto_deregister=False)\n\n    def runTest(self):\n        self.execute_resource(self.serv, OID.Server, 2, RID.Server.RequestBootstrapTrigger)\n        self.serv.reset()\n        self.assertDemoRequestsBootstrap()\n\n        self.communicate('disable-server 2 -1', timeout=5)\n        self.communicate('enable-server 2', timeout=5)\n\n        with self.assertRaises(socket.timeout):\n            print(self.serv.recv(timeout_s=5))\n\n\nclass ExitOfflineDuringBootstrap(BootstrapTest.Test):\n    def setUp(self):\n        super().setUp(num_servers_passed=1)\n\n    def tearDown(self):\n        super().tearDown(auto_deregister=False)\n\n    def runTest(self):\n        self.execute_resource(self.serv, OID.Server, 2, RID.Server.RequestBootstrapTrigger)\n        self.serv.reset()\n        self.assertDemoRequestsBootstrap()\n\n        self.communicate('exit-offline', timeout=5)\n\n        with self.assertRaises(socket.timeout):\n            print(self.serv.recv(timeout_s=5))\n\n\nclass EnterAndExitOfflineDuringBootstrap(BootstrapTest.Test):\n    def setUp(self):\n        super().setUp(num_servers_passed=1)\n\n    def tearDown(self):\n        super().tearDown(auto_deregister=False)\n\n    def runTest(self):\n        self.execute_resource(self.serv, OID.Server, 2, RID.Server.RequestBootstrapTrigger)\n        self.serv.reset()\n        self.assertDemoRequestsBootstrap()\n\n        self.communicate('enter-offline', timeout=5)\n        with self.assertRaises(socket.timeout):\n            print(self.serv.recv(timeout_s=2))\n        self.communicate('exit-offline', timeout=5)\n\n        self.assertDemoRequestsBootstrap()\n\n        with self.assertRaises(socket.timeout):\n            print(self.serv.recv(timeout_s=5))\n\n\nclass ReconnectDuringBootstrap(BootstrapTest.Test):\n    def setUp(self):\n        super().setUp(num_servers_passed=1)\n\n    def tearDown(self):\n        super().tearDown(auto_deregister=False)\n\n    def runTest(self):\n        self.execute_resource(self.serv, OID.Server, 2, RID.Server.RequestBootstrapTrigger)\n        self.serv.reset()\n        self.assertDemoRequestsBootstrap()\n\n        self.communicate('reconnect', timeout=5)\n        self.assertDemoRequestsBootstrap()\n\n        with self.assertRaises(socket.timeout):\n            print(self.serv.recv(timeout_s=5))\n"
  },
  {
    "path": "tests/integration/suites/default/buffer_sizes.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nimport socket\nimport unittest\n\nfrom framework.lwm2m_test import *\nfrom suites.default.retransmissions import RetransmissionTest\n\n\nclass BufferSizeTest:\n    class Base(test_suite.Lwm2mSingleServerTest,\n               test_suite.Lwm2mDmOperations):\n        def setUp(self, inbuf_size=4096, outbuf_size=4096, **kwargs):\n            if 'extra_cmdline_args' not in kwargs:\n                kwargs['extra_cmdline_args'] = []\n\n            kwargs['extra_cmdline_args'] += ['-I', str(inbuf_size), '-O', str(outbuf_size)]\n\n            super().setUp(**kwargs)\n\n\nclass SmallInputBufferAndLargeOptions(BufferSizeTest.Base):\n    def setUp(self):\n        super().setUp(inbuf_size=48)\n\n    def runTest(self):\n        self.create_instance(self.serv, oid=OID.Test, iid=1)\n\n        # these will be technically interpreted as Write-Attributes because of no Content-Format\n        pkt = Lwm2mWrite(ResPath.Test[1].ResBytesSize,\n                         options=[coap.Option.URI_QUERY('lt=0.' + '0' * 128),\n                                  coap.Option.URI_QUERY('gt=9.' + '0' * 128),\n                                  coap.Option.URI_QUERY('st=1.' + '0' * 128)],\n                         content=b'3',\n                         format=None)\n        self.serv.send(pkt)\n        self.assertMsgEqual(\n            Lwm2mErrorResponse.matching(pkt)(code=coap.Code.RES_REQUEST_ENTITY_TOO_LARGE),\n            self.serv.recv())\n\n        # When options do not dominate message size everything works fine.\n        pkt = Lwm2mWrite(ResPath.Test[1].ResBytesSize,\n                         options=[coap.Option.URI_QUERY('lt=0.0'),\n                                  coap.Option.URI_QUERY('gt=9.0'),\n                                  coap.Option.URI_QUERY('st=1.0')],\n                         content=b'3',\n                         format=None)\n        self.serv.send(pkt)\n        self.assertMsgEqual(Lwm2mChanged.matching(pkt)(), self.serv.recv())\n\n\nclass OutputBufferTooSmallButDemoDoesntCrash(BufferSizeTest.Base):\n    def setUp(self):\n        super().setUp(outbuf_size=8, auto_register=False)\n\n    def tearDown(self):\n        # If demo crashes valgrind will tell us.\n        super().tearDown(auto_deregister=False)\n\n    def runTest(self):\n        with self.assertRaises(socket.timeout):\n            self.serv.recv(timeout_s=3)\n\n\nclass OutputBufferSizeIsEnoughToHandleBlockRegister(BufferSizeTest.Base):\n    def setUp(self):\n        # +------+---------------------------------------------+\n        # | Size |                For what?                    |\n        # +------+---------------------------------------------+\n        # | 1B   |  version+type+token length                  |\n        # +------+---------------------------------------------+\n        # | 1B   |  message code                               |\n        # +------+---------------------------------------------+\n        # | 2B   |  message id                                 |\n        # +------+---------------------------------------------+\n        # | 8B   |  token                                      |\n        # +------+---------------------------------------------+\n        # | 3B   |  option URI_PATH == 'rd'                    |\n        # +------+---------------------------------------------+\n        # | 2B   |  option CONTENT_FORMAT == APPLICATION_LINK  |\n        # +------+---------------------------------------------+\n        # | 133B |  option URI_QUERY == 'ep=FFFF...F'          |\n        # +------+---------------------------------------------+\n        # | 9B   |  option URI_QUERY == 'lt=86400'             |\n        # +------+---------------------------------------------+\n        # | 10B  |  option URI_QUERY == 'lwm2m=1.0'            |\n        # +------+---------------------------------------------+\n        # | 2B   |  option BLOCK1                              |\n        # +------+---------------------------------------------+\n        # | 1B   |  payload marker                             |\n        # +------+---------------------------------------------+\n        # | 16B  |  payload                                    |\n        # +------+---------------------------------------------+\n        #\n        # In total: 1 + 1 + 2 + 8 + 3 + 2 + 133 + 9 + 10 + 2 + 1 + 16 = 188\n        #\n        # However, since we use maximum BLOCK option size for calculations, the\n        # actual size of buffer should be 191.\n        super().setUp(endpoint_name=\"F\" * 128, outbuf_size=191, auto_register=False)\n\n    def tearDown(self):\n        super().tearDown(auto_deregister=True)\n\n    def runTest(self):\n        from . import register as r\n        r.BlockRegister().Test()(self.serv)\n        with self.assertRaises(socket.timeout, msg='unexpected message'):\n            print(self.serv.recv(timeout_s=6))\n\n\nclass ConfiguredInputBufferSizeDeterminesMaxIncomingPacketSize(BufferSizeTest.Base):\n    def setUp(self):\n        # +------+---------------------------------------------+\n        # | Size |                For what?                    |\n        # +------+---------------------------------------------+\n        # | 1b   |  version+type+token length                  |\n        # +------+---------------------------------------------+\n        # | 1b   |  message code                               |\n        # +------+---------------------------------------------+\n        # | 2b   |  message id                                 |\n        # +------+---------------------------------------------+\n        # | 8b   |  token                                      |\n        # +------+---------------------------------------------+\n        # | 3b   |  option URI_PATH == 'rd'                    |\n        # +------+---------------------------------------------+\n        #\n        # in total: 1 + 1 + 2 + 8 + 3 = 15\n        super().setUp(inbuf_size=15, auto_register=False)\n\n    def tearDown(self):\n        super().tearDown(auto_deregister=True, path='/rd')\n\n    def runTest(self):\n        self.assertDemoRegisters(location='/rd')\n\n\nclass InputBufferSizeTooSmallToHoldRegisterResponse(RetransmissionTest.TestMixin,\n                                                    BufferSizeTest.Base):\n    def setUp(self):\n        # see calculation in ConfiguredInputBufferSizeDeterminesMaxIncomingPacketSize\n        # the buffer is 1B too short to hold Register response\n        super().setUp(inbuf_size=14, auto_register=False, bootstrap_server=True)\n\n    def tearDown(self):\n        super().tearDown(auto_deregister=False)\n\n    def runTest(self):\n        expected_req = Lwm2mRegister('/rd?lwm2m=1.0&ep=%s&lt=%d' % (DEMO_ENDPOINT_NAME, 86400))\n        # client should not be able to read the whole packet, ignoring it and causing a backoff\n        for _ in range(self.MAX_RETRANSMIT + 1):\n            req = self.serv.recv(timeout_s=self.last_retransmission_timeout())\n            self.assertMsgEqual(expected_req, req)\n            self.serv.send(Lwm2mCreated.matching(req)(location='/rd'))\n\n        # and after failing completely, client falls back to Client-Initiated Bootstrap\n        req = self.bootstrap_server.recv(timeout_s=self.last_retransmission_timeout() + 5)\n        self.assertIsInstance(req, Lwm2mRequestBootstrap)\n        self.bootstrap_server.send(Lwm2mChanged.matching(req)())\n"
  },
  {
    "path": "tests/integration/suites/default/cbor_encoding.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nimport cbor2\nfrom framework.lwm2m_test import *\nfrom framework.test_utils import *\n\nIID = 1\n\nclass CborEncodingTest:\n    class Test(test_suite.Lwm2mSingleServerTest,\n               test_suite.Lwm2mDmOperations):\n        def resource_path(self, rid, riid=None):\n            if riid is not None:\n                return '/%d/%d/%d/%d' % (OID.Test, IID, rid, riid)\n            else:\n                return '/%d/%d/%d' % (OID.Test, IID, rid)\n\n        def setUp(self):\n            super().setUp()\n            self.create_instance(self.serv, oid=OID.Test, iid=IID)\n\n\ndef as_cbor(pkt):\n    return cbor2.loads(pkt.content)\n\n\nclass CborEncodingReadInteger(CborEncodingTest.Test):\n    def runTest(self):\n        assigned_value = 1234\n        self.write_resource(self.serv, oid=OID.Test, iid=IID, rid=RID.Test.ResInt,\n                            content=str(assigned_value))\n\n        read_value = as_cbor(self.read_resource(self.serv, oid=OID.Test, iid=IID, rid=RID.Test.ResInt,\n                                                accept=coap.ContentFormat.APPLICATION_CBOR))\n\n        self.assertEqual(assigned_value, read_value)\n\n\nclass CborEncodingReadUnsignedInteger(CborEncodingTest.Test):\n    def runTest(self):\n        assigned_value = 5678\n        self.write_resource(self.serv, oid=OID.Test, iid=IID, rid=RID.Test.ResUnsignedInt,\n                            content=str(assigned_value))\n\n        read_value = as_cbor(self.read_resource(self.serv, oid=OID.Test, iid=IID, rid=RID.Test.ResUnsignedInt,\n                                                accept=coap.ContentFormat.APPLICATION_CBOR))\n\n        self.assertEqual(assigned_value, read_value)\n\n\nclass CborEncodingReadFloat(CborEncodingTest.Test):\n    def runTest(self):\n        assigned_value = 1.5\n        self.write_resource(self.serv, oid=OID.Test, iid=IID, rid=RID.Test.ResFloat,\n                            content=str(assigned_value))\n\n        read_value = as_cbor(self.read_resource(self.serv, oid=OID.Test, iid=IID, rid=RID.Test.ResFloat,\n                                                accept=coap.ContentFormat.APPLICATION_CBOR))\n\n        self.assertEqual(assigned_value, read_value)\n\n\nclass CborEncodingReadDouble(CborEncodingTest.Test):\n    def runTest(self):\n        assigned_value = 1.1\n        self.write_resource(self.serv, oid=OID.Test, iid=IID, rid=RID.Test.ResDouble,\n                            content=str(assigned_value))\n\n        read_value = as_cbor(self.read_resource(self.serv, oid=OID.Test, iid=IID, rid=RID.Test.ResDouble,\n                                                accept=coap.ContentFormat.APPLICATION_CBOR))\n\n        self.assertEqual(assigned_value, read_value)\n\n\nclass CborEncodingReadBool(CborEncodingTest.Test):\n    def runTest(self):\n        assigned_value = True\n        self.write_resource(self.serv, oid=OID.Test, iid=IID, rid=RID.Test.ResBool,\n                            content=str(int(assigned_value)))\n\n        read_value = as_cbor(self.read_resource(self.serv, oid=OID.Test, iid=IID, rid=RID.Test.ResBool,\n                                                accept=coap.ContentFormat.APPLICATION_CBOR))\n\n        self.assertEqual(assigned_value, read_value)\n\n\nclass CborEncodingReadObjectLink(CborEncodingTest.Test):\n    def runTest(self):\n        assigned_value = '33605:1'\n        self.write_resource(self.serv, oid=OID.Test, iid=IID, rid=RID.Test.ResObjlnk,\n                            content=assigned_value)\n\n        read_value = as_cbor(self.read_resource(self.serv, oid=OID.Test, iid=IID, rid=RID.Test.ResObjlnk,\n                                                accept=coap.ContentFormat.APPLICATION_CBOR))\n\n        self.assertEqual(assigned_value, read_value)\n\n\nclass CborEncodingReadOpaque(CborEncodingTest.Test):\n    def runTest(self):\n        assigned_value = b'losie, jelenie, sarny, dziki, lisy, borsuki, kuny, jenoty, wilki i rysie'\n        self.write_resource(self.serv, oid=OID.Test, iid=IID, rid=RID.Test.ResRawBytes,\n                            content=assigned_value,\n                            format=coap.ContentFormat.APPLICATION_OCTET_STREAM)\n\n        read_value = as_cbor(self.read_resource(self.serv, oid=OID.Test, iid=IID, rid=RID.Test.ResRawBytes,\n                                                accept=coap.ContentFormat.APPLICATION_CBOR))\n\n        self.assertEqual(assigned_value, read_value)\n\n\nclass CborEncodingReadString(CborEncodingTest.Test):\n    def runTest(self):\n        assigned_value = 'z ptakow: kuropatwy, bazanty, dzikie kaczki, gesi, lyski, bekasy i cietrzewie'\n        self.write_resource(self.serv, oid=OID.Test, iid=IID, rid=RID.Test.ResString,\n                            content=assigned_value)\n\n        read_value = as_cbor(self.read_resource(self.serv, oid=OID.Test, iid=IID, rid=RID.Test.ResString,\n                                                accept=coap.ContentFormat.APPLICATION_CBOR))\n\n        self.assertEqual(assigned_value, read_value)\n\nclass CborEncodingReadResourceInstance(CborEncodingTest.Test):\n    def runTest(self):\n        assigned_value = 1337\n        self.write_resource(self.serv, oid=OID.Test, iid=IID, rid=RID.Test.IntArray,\n                            format=coap.ContentFormat.APPLICATION_LWM2M_SENML_CBOR,\n                            content=CBOR.serialize(\n                                [{\n                                    SenmlLabel.VALUE: assigned_value,\n                                    SenmlLabel.NAME: self.resource_path(RID.Test.IntArray, 1)\n                                }]))\n\n        read_value = as_cbor(self.read_resource_instance(self.serv, oid=OID.Test, iid=IID, rid=RID.Test.IntArray, riid=1,\n                                                         accept=coap.ContentFormat.APPLICATION_CBOR))\n\n        self.assertEqual(assigned_value, read_value)\n"
  },
  {
    "path": "tests/integration/suites/default/cbor_requests.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nfrom framework.lwm2m_test import *\nfrom framework.test_utils import *\n\nIID = 1\n\n\nclass CborRequest:\n    class Test(test_suite.Lwm2mSingleServerTest,\n               test_suite.Lwm2mDmOperations):\n\n        def resource_path(self, rid, riid=None):\n            if riid is not None:\n                return '/%d/%d/%d/%d' % (OID.Test, IID, rid, riid)\n            else:\n                return '/%d/%d/%d' % (OID.Test, IID, rid)\n\n        def verify_instance(self, expected_path_value_map={}):\n            res = self.read_instance(self.serv,\n                                     oid=OID.Test,\n                                     iid=IID,\n                                     accept=coap.ContentFormat.APPLICATION_LWM2M_SENML_CBOR)\n            CBOR.parse(res.content).verify_values(test=self,\n                                                  expected_value_map=expected_path_value_map)\n\n        def write_resource_payload(self, rid, cbor_entries, expected_error=None,\n                                   additional_payload=b''):\n            self.write_resource(self.serv,\n                                oid=OID.Test,\n                                iid=IID,\n                                rid=rid,\n                                format=coap.ContentFormat.APPLICATION_LWM2M_SENML_CBOR,\n                                content=CBOR.serialize(cbor_entries) + additional_payload,\n                                expect_error_code=expected_error)\n\n        def cbor_write_resource(self, rid, value, expect_error_code=None):\n            self.write_resource(self.serv,\n                                oid=OID.Test,\n                                iid=IID,\n                                rid=rid,\n                                format=coap.ContentFormat.APPLICATION_CBOR,\n                                content=cbor2.dumps(value),\n                                expect_error_code=expect_error_code)\n\n        def write_instance_payload(self, cbor_entries, expected_error=None, additional_payload=b''):\n            self.write_instance(self.serv,\n                                oid=OID.Test,\n                                iid=IID,\n                                format=coap.ContentFormat.APPLICATION_LWM2M_SENML_CBOR,\n                                content=CBOR.serialize(cbor_entries) + additional_payload,\n                                expect_error_code=expected_error)\n\n        def setUp(self):\n            super().setUp()\n            self.create_instance(self.serv, oid=OID.Test, iid=IID)\n\n\nclass SenmlCborResourceWrite(CborRequest.Test):\n    def runTest(self):\n        self.write_resource_payload(RID.Test.ResInt,\n                                    [{\n                                        SenmlLabel.VALUE: 123456,\n                                        SenmlLabel.NAME: self.resource_path(RID.Test.ResInt)\n                                    }])\n        self.write_resource_payload(RID.Test.ResDouble,\n                                    [{\n                                        SenmlLabel.VALUE: 123456.0,\n                                        SenmlLabel.NAME: self.resource_path(RID.Test.ResDouble)\n                                    }])\n        self.write_resource_payload(RID.Test.ResBool,\n                                    [{\n                                        SenmlLabel.BOOL: True,\n                                        SenmlLabel.NAME: self.resource_path(RID.Test.ResBool)\n                                    }])\n        self.write_resource_payload(RID.Test.ResString,\n                                    [{\n                                        SenmlLabel.STRING: '12345',\n                                        SenmlLabel.NAME: self.resource_path(RID.Test.ResString)\n                                    }])\n        self.write_resource_payload(RID.Test.ResRawBytes,\n                                    [{\n                                        SenmlLabel.OPAQUE: b'12345',\n                                        SenmlLabel.NAME: self.resource_path(RID.Test.ResRawBytes)\n                                    }])\n        self.write_resource_payload(RID.Test.ResObjlnk,\n                                    [{\n                                        SenmlLabel.OBJLNK: '123:456',\n                                        SenmlLabel.NAME: self.resource_path(RID.Test.ResObjlnk)\n                                    }])\n        self.write_resource_payload(RID.Test.IntArray,\n                                    [{\n                                        SenmlLabel.VALUE: 9001,\n                                        SenmlLabel.NAME: self.resource_path(RID.Test.IntArray, 1)\n                                    }])\n        self.verify_instance({\n            self.resource_path(RID.Test.ResInt): 123456,\n            self.resource_path(RID.Test.ResDouble): 123456.0,\n            self.resource_path(RID.Test.ResBool): True,\n            self.resource_path(RID.Test.ResString): \"12345\",\n            self.resource_path(RID.Test.ResRawBytes): b'12345',\n            self.resource_path(RID.Test.ResObjlnk): '123:456',\n            self.resource_path(RID.Test.IntArray, 1): 9001,\n        })\n\n\nclass SenmlCborInstanceWrite(CborRequest.Test):\n    def runTest(self):\n        self.write_instance_payload([{\n            SenmlLabel.VALUE: 123456,\n            SenmlLabel.NAME: self.resource_path(RID.Test.ResInt)\n        },\n            {\n                SenmlLabel.VALUE: 123456.0,\n                SenmlLabel.NAME: self.resource_path(RID.Test.ResDouble)\n            },\n            {\n                SenmlLabel.BOOL: True,\n                SenmlLabel.NAME: self.resource_path(RID.Test.ResBool)\n            },\n            {\n                SenmlLabel.STRING: '12345',\n                SenmlLabel.NAME: self.resource_path(RID.Test.ResString)\n            },\n            {\n                SenmlLabel.OPAQUE: b'12345',\n                SenmlLabel.NAME: self.resource_path(RID.Test.ResRawBytes)\n            },\n            {\n                SenmlLabel.OBJLNK: '123:456',\n                SenmlLabel.NAME: self.resource_path(RID.Test.ResObjlnk)\n            },\n            {\n                SenmlLabel.VALUE: 9001,\n                SenmlLabel.NAME: self.resource_path(RID.Test.IntArray, 1)\n            },\n            {\n                SenmlLabel.VALUE: 9002,\n                SenmlLabel.NAME: self.resource_path(RID.Test.IntArray, 2)\n            }])\n\n        self.verify_instance({\n            self.resource_path(RID.Test.ResInt): 123456,\n            self.resource_path(RID.Test.ResDouble): 123456.0,\n            self.resource_path(RID.Test.ResBool): True,\n            self.resource_path(RID.Test.ResString): \"12345\",\n            self.resource_path(RID.Test.ResRawBytes): b'12345',\n            self.resource_path(RID.Test.ResObjlnk): '123:456',\n            self.resource_path(RID.Test.IntArray, 1): 9001,\n            self.resource_path(RID.Test.IntArray, 2): 9002,\n        })\n\n\nclass SenmlCborResourceWriteWithBasenameAllDivisions(CborRequest.Test):\n    def runTest(self):\n        path = self.resource_path(RID.Test.ResInt)\n        for i in range(len(path) + 1):\n            payload = {SenmlLabel.VALUE: 42}\n            basename, name = path[:i], path[i:]\n            if len(basename):\n                payload[SenmlLabel.BASE_NAME] = basename\n            if len(name):\n                payload[SenmlLabel.NAME] = name\n\n            self.write_resource_payload(RID.Test.ResInt, [payload])\n            self.verify_instance({path: 42})\n\n\nclass SenmlCborInstanceWriteBasenameEffectOnNextResources(CborRequest.Test):\n    def runTest(self):\n        payload = [\n            # First resource without a basename.\n            {\n                SenmlLabel.STRING: \"1234\",\n                SenmlLabel.NAME: self.resource_path(RID.Test.ResString),\n            },\n            # Second resource with basename included.\n            {\n                SenmlLabel.VALUE: 42,\n                SenmlLabel.NAME: \"%d\" % RID.Test.ResInt,\n                SenmlLabel.BASE_NAME: \"/%d/%d/\" % (OID.Test, IID)\n            },\n            # Third resource shall already be affected by the basename.\n            {\n                SenmlLabel.VALUE: 47.0,\n                SenmlLabel.NAME: \"%d\" % RID.Test.ResDouble,\n            },\n        ]\n        self.write_instance_payload(payload)\n        self.verify_instance({\n            self.resource_path(RID.Test.ResString): \"1234\",\n            self.resource_path(RID.Test.ResInt): 42,\n            self.resource_path(RID.Test.ResDouble): 47.0\n        })\n\n\nclass SenmlCborWriteBasenamePointsToOtherObject(CborRequest.Test):\n    def runTest(self):\n        payload = [{\n            SenmlLabel.STRING: \"1234\",\n            SenmlLabel.NAME: self.resource_path(RID.Test.ResString),\n            SenmlLabel.BASE_NAME: \"/2048\",\n        }]\n        self.write_instance_payload(payload, coap.Code.RES_BAD_REQUEST)\n        self.write_resource_payload(RID.Test.ResString, payload, coap.Code.RES_BAD_REQUEST)\n\n\nclass SenmlCborWriteBasenameTooNested(CborRequest.Test):\n    def runTest(self):\n        # Path too nested.\n        payload = [{\n            SenmlLabel.VALUE: 42,\n            SenmlLabel.NAME: self.resource_path(RID.Test.ResInt),\n            SenmlLabel.BASE_NAME: '/%d/1' % (OID.Test,),\n        }]\n        self.write_instance_payload(payload, coap.Code.RES_BAD_REQUEST)\n        self.write_resource_payload(RID.Test.ResInt, payload, coap.Code.RES_BAD_REQUEST)\n\n\nclass SenmlCborWriteBasenameItemTooLong(CborRequest.Test):\n    def runTest(self):\n        payload = [{\n            SenmlLabel.VALUE: 42,\n            SenmlLabel.NAME: self.resource_path(RID.Test.ResInt),\n            SenmlLabel.BASE_NAME: '/133777',\n        }]\n        self.write_instance_payload(payload, coap.Code.RES_BAD_REQUEST)\n        self.write_resource_payload(RID.Test.ResInt, payload, coap.Code.RES_BAD_REQUEST)\n\n\nclass SenmlCborWriteBasenameUnrelated(CborRequest.Test):\n    def runTest(self):\n        self.create_instance(self.serv, oid=OID.Test, iid=IID + 1)\n        # Path unrelated - request is made on /33605/IID but basename points to /33605/(IID+1)\n        payload = [{\n            SenmlLabel.VALUE: 42,\n            SenmlLabel.NAME: '%d' % RID.Test.ResInt,\n            SenmlLabel.BASE_NAME: '/%d/%d/' % (OID.Test, IID + 1),\n        }]\n        self.write_instance_payload(payload, coap.Code.RES_BAD_REQUEST)\n        self.write_resource_payload(RID.Test.ResInt, payload, coap.Code.RES_BAD_REQUEST)\n\n\nclass SenmlCborWriteWithUnknownDataAtTheEnd(CborRequest.Test):\n    def runTest(self):\n        payload = [{\n            SenmlLabel.VALUE: 42,\n            SenmlLabel.NAME: self.resource_path(RID.Test.ResInt),\n        }]\n        self.write_instance_payload(payload,\n                                    expected_error=coap.Code.RES_BAD_REQUEST,\n                                    additional_payload=b'stuff')\n        self.write_resource_payload(RID.Test.ResInt,\n                                    payload,\n                                    expected_error=coap.Code.RES_BAD_REQUEST,\n                                    additional_payload=b'more stuff')\n\n\nclass SenmlCborAttemptWritingValueToInstance(CborRequest.Test):\n    def runTest(self):\n        payload = [{\n            SenmlLabel.NAME: '/%d/%d' % (OID.Test, IID),\n            SenmlLabel.VALUE: 42\n        }]\n        self.write_instance_payload(payload, coap.Code.RES_BAD_REQUEST)\n\n\nclass SenmlCborAttemptWritingValueToMultipleResource(CborRequest.Test):\n    def runTest(self):\n        payload = [{\n            SenmlLabel.NAME: '/%d/%d/%d' % (OID.Test, IID, RID.Test.IntArray),\n            SenmlLabel.VALUE: 42\n        }]\n        self.write_instance_payload(payload, coap.Code.RES_BAD_REQUEST)\n\n\nclass CborResourceWrite(CborRequest.Test):\n    def runTest(self):\n        self.cbor_write_resource(RID.Test.ResInt, 123456)\n        self.cbor_write_resource(RID.Test.ResDouble, 123456.0)\n        self.cbor_write_resource(RID.Test.ResBool, True)\n        self.cbor_write_resource(RID.Test.ResString, '12345')\n        self.cbor_write_resource(RID.Test.ResRawBytes, b'12345')\n        self.cbor_write_resource(RID.Test.ResObjlnk, '123:456')\n\n        self.verify_instance({\n            self.resource_path(RID.Test.ResInt): 123456,\n            self.resource_path(RID.Test.ResDouble): 123456.0,\n            self.resource_path(RID.Test.ResBool): True,\n            self.resource_path(RID.Test.ResString): \"12345\",\n            self.resource_path(RID.Test.ResRawBytes): b'12345',\n            self.resource_path(RID.Test.ResObjlnk): '123:456'\n        })\n\n\nclass CborResourceStringWrite(CborRequest.Test):\n    def runTest(self):\n        too_long_string = 'xxxxxxxx10xxxxxxxx20xxxxxxxx30xxxxxxxx40xxxxxxxx50xxxxxxxx60' \\\n                          'xxxxxxxx70xxxxxxxx80xxxxxxxx90xxxxxxx100xxxxxxx110xxxxxxx120' \\\n                          'xxxx127'\n        self.cbor_write_resource(RID.Test.ResString,\n                                 too_long_string + 'TRUNCATED_PART_IN_TOO_LONG_STRING',\n                                 coap.Code.RES_INTERNAL_SERVER_ERROR)\n        self.verify_instance({\n            self.resource_path(RID.Test.ResString): too_long_string\n        })\n\n\nclass CborTryResourceInstanceWrite(CborRequest.Test):\n    def runTest(self):\n        self.write_resource_instance(self.serv,\n                                     oid=OID.Test,\n                                     iid=IID,\n                                     rid=RID.Test.IntArray,\n                                     riid=1,\n                                     format=coap.ContentFormat.APPLICATION_CBOR,\n                                     content=cbor2.dumps(1337),\n                                     expect_error_code=coap.Code.RES_NOT_FOUND)\n\n\nclass CborResourceInstanceWrite(CborRequest.Test):\n    def runTest(self):\n        self.write_resource_payload(RID.Test.IntArray,\n                                    [{\n                                        SenmlLabel.VALUE: 0,\n                                        SenmlLabel.NAME: self.resource_path(RID.Test.IntArray, 1)\n                                    }])\n\n        self.write_resource_instance(self.serv,\n                                     oid=OID.Test,\n                                     iid=IID,\n                                     rid=RID.Test.IntArray,\n                                     riid=1,\n                                     format=coap.ContentFormat.APPLICATION_CBOR,\n                                     content=cbor2.dumps(1337))\n\n        self.verify_instance({\n            self.resource_path(RID.Test.IntArray, 1): 1337\n        })\n\n\nclass SenmlCborAttemptWritingNull(CborRequest.Test):\n    def runTest(self):\n        payload = [{\n            SenmlLabel.NAME: self.resource_path(RID.Test.ResInt),\n            SenmlLabel.VALUE: None\n        }]\n        self.write_resource_payload(RID.Test.ResInt, payload, coap.Code.RES_BAD_REQUEST)\n"
  },
  {
    "path": "tests/integration/suites/default/client_block_request.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nimport math\nimport socket\nimport time\nimport unittest\n\nfrom framework.lwm2m_test import *\n\nfrom .register import BlockRegister\n\nclass BasicClientBlockRequest:\n    @staticmethod\n    def Test(base_class=test_suite.Lwm2mSingleServerTest):\n        class TestImpl(base_class):\n            def __init__(self, test_method_name):\n                super().__init__(test_method_name)\n\n                self.A_LOT = 256  # arbitratry, big enough to trigger block-wise Update\n                self.block_size = 1024  # maximum possible block size\n                self.expected_payload_size = None\n\n            def setUp(self):\n                super().setUp()\n\n                self.ac_object_instances_str = b','.join(b'</%d/%d>' % (OID.AccessControl, x) for x in range(self.A_LOT))\n                self.test_object_instances_str = b','.join(b'</%d/%d>' % (OID.Test, x) for x in range(self.A_LOT))\n                self.expected_payload_size = (len(self.ac_object_instances_str)\n                                              + len(self.test_object_instances_str)\n                                              + 250)  # estimated size of other objects\n                self.expected_num_blocks = int(math.ceil(self.expected_payload_size / self.block_size))\n\n                for _ in range(self.A_LOT):\n                    req = Lwm2mCreate('/%d' % (OID.Test,))\n                    self.serv.send(req)\n                    self.assertMsgEqual(Lwm2mCreated.matching(req)(),\n                                        self.serv.recv())\n\n            def recv(self, **kwargs):\n                \"\"\"\n                Receives a single packet. May be overridden in subclasses to perform\n                additional actions before/after actual recv.\n                \"\"\"\n                return self.serv.recv(**kwargs)\n\n            def block_recv_next(self,\n                                expected_seq_num,\n                                validate=True):\n                req = self.serv.recv()\n\n                if validate:\n                    has_more = (expected_seq_num < self.expected_num_blocks - 1)\n                    block_opt = coap.Option.BLOCK1(seq_num=expected_seq_num,\n                                                   has_more=has_more,\n                                                   block_size=self.block_size)\n\n                    self.assertMsgEqual(Lwm2mUpdate(self.DEFAULT_REGISTER_ENDPOINT,\n                                                    query=[],\n                                                    options=[block_opt]),\n                                        req)\n\n                return req\n\n            def block_recv(self,\n                           seq_num_begin=0,\n                           seq_num_end=None,\n                           validate=True,\n                           send_ack=None):\n                payload = bytearray()\n                expected_seq_num = seq_num_begin\n                wait_for_more = True\n\n                if send_ack is None:\n                    send_ack = self.serv.send\n\n                while wait_for_more:\n                    req = self.block_recv_next(expected_seq_num, validate=validate)\n                    expected_seq_num += 1\n                    payload += req.content\n\n                    block_opt = req.get_options(coap.Option.BLOCK1)[0]\n                    if block_opt.has_more():\n                        response = Lwm2mContinue.matching(req)(options=[block_opt])\n                    else:\n                        response = Lwm2mChanged.matching(req)(options=[block_opt])\n\n                    send_ack(response)\n\n                    wait_for_more = (block_opt.has_more() if seq_num_end is None\n                                     else expected_seq_num < seq_num_end)\n\n                return payload\n\n        return TestImpl\n\n\nclass ClientBlockRequest:\n    @staticmethod\n    def Test(*args, **kwargs):\n        class TestImpl(BasicClientBlockRequest.Test(*args, **kwargs)):\n            def __init__(self, test_method_name):\n                super().__init__(test_method_name)\n\n                self.expected_payload_size = None\n                self.expected_num_blocks = None\n\n            def set_block_size(self, new_block_size):\n                self.block_size = new_block_size\n                self.expected_num_blocks = int(math.ceil(self.expected_payload_size / self.block_size))\n\n            def setUp(self):\n                super().setUp()\n\n                self.communicate('send-update')\n                complete_payload = self.block_recv(validate=False)\n\n                # change something in the DM so that next Update includes the list\n                # of all instances\n                self.serv.send(Lwm2mCreate('/%d' % (OID.Test,)))\n                iid = int(self.serv.recv().get_options(coap.Option.LOCATION_PATH)[-1].content_to_str())\n\n                self.expected_payload_size = len(complete_payload) + len(',</%d/%d>,</%d/%d>' % (OID.Test, iid, OID.AccessControl, iid))\n                self.expected_num_blocks = int(math.ceil(self.expected_payload_size / self.block_size))\n\n            def tearDown(self, auto_deregister=False, **kwargs):\n                super().tearDown(auto_deregister=auto_deregister, **kwargs)\n\n        return TestImpl\n\n\nclass HumongousUpdateTest(BasicClientBlockRequest.Test()):\n    def runTest(self):\n        self.communicate('send-update')\n        complete_payload = self.block_recv()\n\n        self.assertLinkListValid(complete_payload.decode())\n        self.assertIn(self.ac_object_instances_str, complete_payload)\n        self.assertIn(self.test_object_instances_str, complete_payload)\n\n        with self.assertRaises(socket.timeout, msg=\"client did not accept Update response\"):\n            self.serv.recv(timeout_s=3)\n\n\nclass HumongousUpdateWithSeparateResponseTest(BasicClientBlockRequest.Test()):\n    def send_separate_ack(self, msg):\n        self.assertEqual(coap.Type.ACKNOWLEDGEMENT, msg.type,\n                         \"incorrect usage of SeparateResponseTest.send, it's \"\n                         \"supposed to only be used for ACKs to block-wise \"\n                         \"Update\")\n\n        separate_ack = Lwm2mEmpty.matching(msg)()\n        self.serv.send(separate_ack)\n\n        msg.type = coap.Type.CONFIRMABLE\n        msg.msg_id = ANY\n        self.serv.send(msg)\n\n        self.assertMsgEqual(Lwm2mEmpty.matching(msg)(),\n                            self.recv())\n\n    def runTest(self):\n        self.communicate('send-update')\n        complete_payload = self.block_recv(send_ack=self.send_separate_ack)\n\n        self.assertLinkListValid(complete_payload.decode())\n        self.assertIn(self.ac_object_instances_str, complete_payload)\n        self.assertIn(self.test_object_instances_str, complete_payload)\n\n        with self.assertRaises(socket.timeout, msg=\"client did not accept Update response\"):\n            self.serv.recv(timeout_s=3)\n\n\nclass HumongousUpdateWithNonConfirmableSeparateResponseTest(BasicClientBlockRequest.Test()):\n    def send_separate_ack(self, msg):\n        self.assertEqual(coap.Type.ACKNOWLEDGEMENT, msg.type,\n                         \"incorrect usage of SeparateResponseTest.send, it's \"\n                         \"supposed to only be used for ACKs to block-wise \"\n                         \"Update\")\n\n        separate_ack = Lwm2mEmpty.matching(msg)()\n        self.serv.send(separate_ack)\n\n        msg.type = coap.Type.NON_CONFIRMABLE\n        msg.msg_id = ANY\n        self.serv.send(msg)\n\n    def runTest(self):\n        self.communicate('send-update')\n        complete_payload = self.block_recv(send_ack=self.send_separate_ack)\n\n        self.assertLinkListValid(complete_payload.decode())\n        self.assertIn(self.ac_object_instances_str, complete_payload)\n        self.assertIn(self.test_object_instances_str, complete_payload)\n\n        with self.assertRaises(socket.timeout, msg=\"client did not accept Update response\"):\n            self.serv.recv(timeout_s=3)\n\n\nclass ResetResponseToFirstRequestBlock(ClientBlockRequest.Test()):\n    def runTest(self):\n        self.communicate('send-update')\n\n        req = self.block_recv_next(expected_seq_num=0)\n        self.serv.send(Lwm2mReset.matching(req)())\n        # client should abort\n        self.wait_until_socket_count(0, timeout_s=5)\n\n\nclass ResetResponseToIntermediateRequestBlock(ClientBlockRequest.Test()):\n    def runTest(self):\n        self.communicate('send-update')\n\n        self.block_recv(seq_num_begin=0,\n                        seq_num_end=(self.expected_num_blocks // 2))\n\n        req = self.block_recv_next(expected_seq_num=(self.expected_num_blocks // 2))\n        self.serv.send(Lwm2mReset.matching(req)())\n        # client should abort\n        self.wait_until_socket_count(0, timeout_s=5)\n\n\nclass ResetResponseToLastRequestBlock(ClientBlockRequest.Test()):\n    def runTest(self):\n        self.communicate('send-update')\n\n        self.block_recv(seq_num_begin=0,\n                        seq_num_end=(self.expected_num_blocks - 1))\n\n        req = self.block_recv_next(expected_seq_num=(self.expected_num_blocks - 1))\n        self.serv.send(Lwm2mReset.matching(req)())\n        # client should abort\n        self.wait_until_socket_count(0, timeout_s=5)\n\n\nclass CoapErrorResponseToFirstRequestBlock(ClientBlockRequest.Test()):\n    def runTest(self):\n        self.communicate('send-update')\n\n        req = self.block_recv_next(expected_seq_num=0)\n        self.serv.send(Lwm2mErrorResponse.matching(req)(coap.Code.RES_INTERNAL_SERVER_ERROR))\n        # client should fall-back to registration\n        BlockRegister().Test()(self.serv, verify=False)\n\n    def tearDown(self):\n        super().tearDown(auto_deregister=True)\n\n\nclass CoapErrorResponseToIntermediateRequestBlock(ClientBlockRequest.Test()):\n    def runTest(self):\n        self.communicate('send-update')\n\n        self.block_recv(seq_num_begin=0,\n                        seq_num_end=(self.expected_num_blocks // 2))\n\n        req = self.block_recv_next(expected_seq_num=(self.expected_num_blocks // 2))\n        self.serv.send(Lwm2mErrorResponse.matching(req)(coap.Code.RES_INTERNAL_SERVER_ERROR))\n        # client should fall-back to registration\n        BlockRegister().Test()(self.serv, verify=False)\n\n    def tearDown(self):\n        super().tearDown(auto_deregister=True)\n\n\nclass CoapErrorResponseToLastRequestBlock(ClientBlockRequest.Test()):\n    def runTest(self):\n        self.communicate('send-update')\n\n        self.block_recv(seq_num_begin=0,\n                        seq_num_end=(self.expected_num_blocks - 1))\n\n        req = self.block_recv_next(expected_seq_num=(self.expected_num_blocks - 1))\n        self.serv.send(Lwm2mErrorResponse.matching(req)(coap.Code.RES_INTERNAL_SERVER_ERROR))\n        # client should fall-back to registration\n        BlockRegister().Test()(self.serv, verify=False)\n\n    def tearDown(self):\n        super().tearDown(auto_deregister=True)\n\n\n# IcmpErrorResponse* tests flow looks like this:\n# 1. Force update.\n# 2. Either receive part of the update or not.\n# 3. Close Lwm2mServer socket.\n# 4. Sleep for some time, hoping that the demo will realize server socket\n#    is closed and a retry should be scheduled.\n# 5. Restart server and receive update.\n#\n# Now, 5 seconds is enough to make demo realize that server really stopped\n# running. Sleeping for shorter amounts of time might cause race condition,\n# and in effect test failure:\n# 1. as above\n# 2. as above\n# 3. During closing the socket another block message has been received,\n#    there is no answer from the server though due to state it is currently in,\n#    therefore recv() in demo reports timeout, which will cause retransmission\n#    after at most 3 seconds.\n# 4. Server restarts.\n# 5. Retransmitted packet hits the server, and an error occurs because\n#    we do not expect retransmitted block.\nICMP_ERROR_RESPONSE_SLEEP_SECONDS = 5\n\nclass IcmpErrorResponseAfterNoResponsesAtAll(ClientBlockRequest.Test(test_suite.Lwm2mDtlsSingleServerTest)):\n    def runTest(self):\n        self.communicate('send-update')\n        # Allow client to contaminate our packet queue.\n        time.sleep(5)\n\n        with self.serv.fake_close():\n            time.sleep(ICMP_ERROR_RESPONSE_SLEEP_SECONDS)\n\n        # client should abort\n        with self.assertRaises(socket.timeout):\n            self.serv.recv(timeout_s=(ICMP_ERROR_RESPONSE_SLEEP_SECONDS * 3))\n        self.assertEqual(self.get_socket_count(), 0)\n\n\nclass IcmpErrorResponseToFirstRequestBlock(ClientBlockRequest.Test(test_suite.Lwm2mDtlsSingleServerTest)):\n    def runTest(self):\n        with self.serv.fake_close():\n            self.communicate('send-update')\n            time.sleep(ICMP_ERROR_RESPONSE_SLEEP_SECONDS)\n\n        # client should abort\n        with self.assertRaises(socket.timeout):\n            self.serv.recv(timeout_s=(ICMP_ERROR_RESPONSE_SLEEP_SECONDS * 3))\n        self.assertEqual(self.get_socket_count(), 0)\n\n\nclass IcmpErrorResponseToIntermediateRequestBlock(ClientBlockRequest.Test(test_suite.Lwm2mDtlsSingleServerTest)):\n    def runTest(self):\n        self.communicate('send-update')\n        self.block_recv(seq_num_begin=0,\n                        seq_num_end=(self.expected_num_blocks // 2))\n\n        with self.serv.fake_close():\n            time.sleep(ICMP_ERROR_RESPONSE_SLEEP_SECONDS)\n\n        # client should abort\n        with self.assertRaises(socket.timeout):\n            self.serv.recv(timeout_s=(ICMP_ERROR_RESPONSE_SLEEP_SECONDS * 3))\n        self.assertEqual(self.get_socket_count(), 0)\n\n\nclass IcmpErrorResponseToLastRequestBlock(ClientBlockRequest.Test(test_suite.Lwm2mDtlsSingleServerTest)):\n    def runTest(self):\n        self.communicate('send-update')\n        self.block_recv(seq_num_begin=0,\n                        seq_num_end=(self.expected_num_blocks - 1))\n\n        with self.serv.fake_close():\n            time.sleep(ICMP_ERROR_RESPONSE_SLEEP_SECONDS)\n\n        # client should abort\n        with self.assertRaises(socket.timeout):\n            self.serv.recv(timeout_s=(ICMP_ERROR_RESPONSE_SLEEP_SECONDS * 3))\n        self.assertEqual(self.get_socket_count(), 0)\n\n\nclass NoResponseAfterFirstRequestBlock(ClientBlockRequest.Test()):\n    def tearDown(self):\n        super().tearDown(auto_deregister=True)\n\n    def runTest(self):\n        self.communicate('send-update')\n\n        self.block_recv_next(expected_seq_num=0)\n        # ignore packet - client should retry\n\n        self.block_recv()\n\n\nclass NoResponseAfterIntermediateRequestBlock(ClientBlockRequest.Test()):\n    def tearDown(self):\n        super().tearDown(auto_deregister=True)\n\n    def runTest(self):\n        self.communicate('send-update')\n\n        self.block_recv(seq_num_begin=0,\n                        seq_num_end=(self.expected_num_blocks // 2))\n\n        self.block_recv_next(expected_seq_num=(self.expected_num_blocks // 2))\n        # ignore packet - client should retry\n\n        self.block_recv(seq_num_begin=(self.expected_num_blocks // 2))\n\n\nclass NoResponseAfterLastRequestBlock(ClientBlockRequest.Test()):\n    def tearDown(self):\n        super().tearDown(auto_deregister=True)\n\n    def runTest(self):\n        self.communicate('send-update')\n\n        self.block_recv(seq_num_begin=0,\n                        seq_num_end=(self.expected_num_blocks - 1))\n\n        self.block_recv_next(expected_seq_num=(self.expected_num_blocks - 1))\n        # ignore packet - client should retry\n\n        self.block_recv(seq_num_begin=(self.expected_num_blocks - 1))\n\n\nclass BlockSizeRenegotiation(ClientBlockRequest.Test()):\n    def tearDown(self):\n        super().tearDown(auto_deregister=True)\n\n    def runTest(self):\n        # blocks 2+ should use reduced block size\n        self.communicate('send-update')\n\n        req = self.block_recv_next(expected_seq_num=0)\n        block1 = req.get_options(coap.Option.BLOCK1)[0]\n\n        new_block_size = 16\n        self.assertNotEqual(new_block_size, block1.block_size)\n\n        # request new block size in Continue message\n        block_opt = coap.Option.BLOCK1(seq_num=0, has_more=1, block_size=new_block_size)\n        self.serv.send(Lwm2mContinue.matching(req)(options=[block_opt]))\n\n        # change block size for future packets\n        block_size_ratio = self.block_size // new_block_size\n        self.set_block_size(new_block_size)\n\n        # receive remaining packets\n        self.block_recv(seq_num_begin=block_size_ratio)\n\n\nclass BlockSizeRenegotiationInTheMiddleOfTransfer(ClientBlockRequest.Test()):\n    def runTest(self):\n        # blocks 2+ should use reduced block size\n        self.communicate('send-update')\n        total_bytes = 0\n\n        req = self.block_recv(seq_num_begin=0,\n                              seq_num_end=(self.expected_num_blocks // 2))\n        total_bytes += len(req)\n        req = self.block_recv_next(expected_seq_num=(self.expected_num_blocks // 2))\n        total_bytes += len(req.content)\n\n        new_block_size = 16\n        self.set_block_size(new_block_size)\n\n        block1 = req.get_options(coap.Option.BLOCK1)[0]\n        self.assertNotEqual(new_block_size, block1.block_size)\n        # request new block size in Continue message\n        block_opt = coap.Option.BLOCK1(seq_num=total_bytes // new_block_size,\n                                       has_more=1,\n                                       block_size=new_block_size)\n\n        self.serv.send(Lwm2mContinue.matching(req)(options=[block_opt]))\n        # client should accept the new block size, and continue\n        self.block_recv(seq_num_begin=total_bytes // new_block_size)\n\n    def tearDown(self):\n        super().tearDown(auto_deregister=True)\n\nclass MismatchedResetWhileBlockRequestInProgress(ClientBlockRequest.Test()):\n    def tearDown(self):\n        super().tearDown(auto_deregister=True)\n\n    def runTest(self):\n        self.communicate('send-update')\n\n        self.block_recv(seq_num_begin=0,\n                        seq_num_end=(self.expected_num_blocks // 2))\n\n        req = self.block_recv_next(expected_seq_num=(self.expected_num_blocks // 2))\n\n        # Reset with mismatched msg_id should be ignored\n        self.serv.send(Lwm2mReset(msg_id=(req.msg_id + 1)))\n\n        # transfer should continue after receiving the correct response\n        self.serv.send(Lwm2mContinue.matching(req)(options=req.get_options(coap.Option.BLOCK1)))\n        self.block_recv(seq_num_begin=(self.expected_num_blocks // 2 + 1))\n\n\nclass UnexpectedServerRequestWhileBlockRequestInProgress(ClientBlockRequest.Test(),\n                                                         test_suite.Lwm2mDmOperations):\n    def tearDown(self):\n        super().tearDown(auto_deregister=True)\n\n    def runTest(self):\n        self.communicate('send-update')\n\n        self.block_recv(seq_num_begin=0,\n                        seq_num_end=(self.expected_num_blocks // 2))\n\n        block_req = self.block_recv_next(expected_seq_num=(self.expected_num_blocks // 2))\n        block_opt = block_req.get_options(coap.Option.BLOCK1)[0]\n        block_res = Lwm2mContinue.matching(block_req)(options=[block_opt])\n\n        # send an unrelated request during a block-wise transfer\n        self.read_path(self.serv, ResPath.Device.Manufacturer)\n\n        # continue block-wise request\n        self.serv.send(block_res)\n        self.block_recv(seq_num_begin=(self.expected_num_blocks // 2 + 1))\n"
  },
  {
    "path": "tests/integration/suites/default/coap.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nfrom framework.lwm2m_test import *\nfrom suites.default.bootstrap_client import BootstrapTest\nimport socket\nimport unittest\n\n\nclass Tests:\n\n    ROOT_PATH =     '/'\n    OBJECT_PATH =   '/1'\n    INSTANCE_PATH = '/1/2'\n    RESOURCE_PATH = '/1/2/3'\n    RESOURCE_INSTANCE_PATH = '/1/2/3/4'\n    EXTENDED_PATH = '/1/2/3/4/5'\n\n    def action(test, server, path, code, expect_error_code):\n        path = CoapPath(path)\n        req = coap.Packet(type=coap.Type.CONFIRMABLE,\n                          code=code,\n                          msg_id=ANY,\n                          token=ANY,\n                          options=path.to_uri_options())\n        res = Lwm2mErrorResponse.matching(req)(code=expect_error_code)\n        test._perform_action(server, req, res)\n\n    class CoapTest(test_suite.Lwm2mSingleServerTest,\n                   test_suite.Lwm2mDmOperations):\n        def test(self, path, code, expect_error_code):\n            Tests.action(self, self.serv, path, code, expect_error_code)\n\n    class BootstrapTest(BootstrapTest.Test):\n        def setUp(self):\n            super().setUp(servers=0)\n\n        def test(self, path, code, expect_error_code):\n            self.assertDemoRequestsBootstrap()\n            Tests.action(self, self.bootstrap_server, path, code, expect_error_code)\n\n\n'''\nRequest to operation mapping:\n\nDevice Management & Service Enablement Interface\n┌────────┬──────┬──────────────────┬──────────────────────────┬──────────────────────────┬──────────────────────────┬───────────────┐\n│        │ Root │      Object      │         Instance         │         Resource         │    Resource Instance     │ Extended path │\n├────────┼──────┼──────────────────┼──────────────────────────┼──────────────────────────┼──────────────────────────┼───────────────┤\n│ GET    │ ---  │ Read / Discover  │ Read / Discover          │ Read / Discover          │ Read                     │ ---           │\n│ PUT    │ ---  │ Write-Attributes │ Write / Write-Attributes │ Write / Write-Attributes │ Write / Write-Attributes │ ---           │\n│ POST   │ ---  │ Create           │ Write                    │ Execute                  │ Write                    │ ---           │\n│ DELETE │ ---  │ ---              │ Delete                   │ ---                      │ ---                      │ ---           │\n└────────┴──────┴──────────────────┴──────────────────────────┴──────────────────────────┴──────────────────────────┴───────────────┘\n\nBootstrap Interface\n┌────────┬──────────┬─────────────────┬──────────┬──────────┬───────────────────┬───────────────┐\n│        │   Root   │     Object      │ Instance │ Resource │ Resource Instance │ Extended path │\n├────────┼──────────┼─────────────────┼──────────┼──────────┼───────────────────┼───────────────┤\n│ GET    │ Discover │ Read / Discover │ Read     │ ---      │ ---               │ ---           │\n│ PUT    │ ---      │ Write           │ Write    │ Write    │ ---               │ ---           │\n│ POST   │ ---      │ ---             │ ---      │ ---      │ ---               │ ---           │\n│ DELETE │ Delete   │ Delete          │ Delete   │ ---      │ ---               │ ---           │\n└────────┴──────────┴─────────────────┴──────────┴──────────┴───────────────────┴───────────────┘\n\n* Discover requires Accept: application/link-format\n** Write-Attributes has not Content-Format specified\n'''\n\n\nclass GetOnRootPath(Tests.CoapTest):\n    def runTest(self):\n        self.test(path=Tests.ROOT_PATH,\n                  code=coap.Code.REQ_GET,\n                  expect_error_code=coap.Code.RES_BAD_REQUEST)\n\n\nclass PutOnRootPath(Tests.CoapTest):\n    def runTest(self):\n        self.test(path=Tests.ROOT_PATH,\n                  code=coap.Code.REQ_PUT,\n                  expect_error_code=coap.Code.RES_BAD_REQUEST)\n\n\nclass PostOnRootPath(Tests.CoapTest):\n    def runTest(self):\n        self.test(path=Tests.ROOT_PATH,\n                  code=coap.Code.REQ_POST,\n                  expect_error_code=coap.Code.RES_BAD_REQUEST)\n\n\nclass DeleteOnRootPath(Tests.CoapTest):\n    def runTest(self):\n        self.test(path=Tests.ROOT_PATH,\n                  code=coap.Code.REQ_DELETE,\n                  expect_error_code=coap.Code.RES_BAD_REQUEST)\n\n\nclass DeleteOnObjectPath(Tests.CoapTest):\n    def runTest(self):\n        self.test(path=Tests.OBJECT_PATH,\n                  code=coap.Code.REQ_DELETE,\n                  expect_error_code=coap.Code.RES_METHOD_NOT_ALLOWED)\n\n\nclass DeleteOnResourcePath(Tests.CoapTest):\n    def runTest(self):\n        self.test(path=Tests.RESOURCE_PATH,\n                  code=coap.Code.REQ_DELETE,\n                  expect_error_code=coap.Code.RES_METHOD_NOT_ALLOWED)\n\n\nclass GetOnExtendedPath(Tests.CoapTest):\n    def runTest(self):\n        self.test(path=Tests.EXTENDED_PATH,\n                  code=coap.Code.REQ_GET,\n                  expect_error_code=coap.Code.RES_BAD_OPTION)\n\n\nclass PutOnExtendedPath(Tests.CoapTest):\n    def runTest(self):\n        self.test(path=Tests.EXTENDED_PATH,\n                  code=coap.Code.REQ_PUT,\n                  expect_error_code=coap.Code.RES_BAD_OPTION)\n\n\nclass PostOnExtendedPath(Tests.CoapTest):\n    def runTest(self):\n        self.test(path=Tests.EXTENDED_PATH,\n                  code=coap.Code.REQ_POST,\n                  expect_error_code=coap.Code.RES_BAD_OPTION)\n\n\nclass DeleteOnExtendedPath(Tests.CoapTest):\n    def runTest(self):\n        self.test(path=Tests.EXTENDED_PATH,\n                  code=coap.Code.REQ_DELETE,\n                  expect_error_code=coap.Code.RES_BAD_OPTION)\n\n\nclass BootstrapGetOnRootPath(Tests.BootstrapTest):\n    def runTest(self):\n        self.test(path=Tests.ROOT_PATH,\n                  code=coap.Code.REQ_GET,\n                  expect_error_code=coap.Code.RES_METHOD_NOT_ALLOWED)\n\n\nclass BootstrapPutOnRootPath(Tests.BootstrapTest):\n    def runTest(self):\n        self.test(path=Tests.ROOT_PATH,\n                  code=coap.Code.REQ_PUT,\n                  expect_error_code=coap.Code.RES_METHOD_NOT_ALLOWED)\n\n\nclass BootstrapPostOnRootPath(Tests.BootstrapTest):\n    def runTest(self):\n        self.test(path=Tests.ROOT_PATH,\n                  code=coap.Code.REQ_POST,\n                  expect_error_code=coap.Code.RES_METHOD_NOT_ALLOWED)\n\n\nclass BootstrapPostOnObjectPath(Tests.BootstrapTest):\n    def runTest(self):\n        self.test(path=Tests.OBJECT_PATH,\n                  code=coap.Code.REQ_POST,\n                  expect_error_code=coap.Code.RES_METHOD_NOT_ALLOWED)\n\n\nclass BootstrapPostOnInstancePath(Tests.BootstrapTest):\n    def runTest(self):\n        self.test(path=Tests.INSTANCE_PATH,\n                  code=coap.Code.REQ_POST,\n                  expect_error_code=coap.Code.RES_METHOD_NOT_ALLOWED)\n\nclass BootstrapGetOnResourcePath(Tests.BootstrapTest):\n    def runTest(self):\n        self.test(path=Tests.RESOURCE_PATH,\n                  code=coap.Code.REQ_GET,\n                  expect_error_code=coap.Code.RES_METHOD_NOT_ALLOWED)\n\n\nclass BootstrapPostOnResourcePath(Tests.BootstrapTest):\n    def runTest(self):\n        self.test(path=Tests.RESOURCE_PATH,\n                  code=coap.Code.REQ_POST,\n                  expect_error_code=coap.Code.RES_METHOD_NOT_ALLOWED)\n\n\nclass BootstrapDeleteOnResourcePath(Tests.BootstrapTest):\n    def runTest(self):\n        self.test(path=Tests.RESOURCE_PATH,\n                  code=coap.Code.REQ_DELETE,\n                  expect_error_code=coap.Code.RES_BAD_REQUEST)\n\n\nclass BootstrapGetOnResourceInstancePath(Tests.BootstrapTest):\n    def runTest(self):\n        self.test(path=Tests.RESOURCE_INSTANCE_PATH,\n                  code=coap.Code.REQ_GET,\n                  expect_error_code=coap.Code.RES_METHOD_NOT_ALLOWED)\n\n\nclass BootstrapPutOnResourceInstancePath(Tests.BootstrapTest):\n    def runTest(self):\n        self.test(path=Tests.RESOURCE_INSTANCE_PATH,\n                  code=coap.Code.REQ_PUT,\n                  expect_error_code=coap.Code.RES_METHOD_NOT_ALLOWED)\n\n\nclass BootstrapPostOnResourceInstancePath(Tests.BootstrapTest):\n    def runTest(self):\n        self.test(path=Tests.RESOURCE_INSTANCE_PATH,\n                  code=coap.Code.REQ_POST,\n                  expect_error_code=coap.Code.RES_METHOD_NOT_ALLOWED)\n\n\nclass BootstrapDeleteOnResourceInstancePath(Tests.BootstrapTest):\n    def runTest(self):\n        self.test(path=Tests.RESOURCE_INSTANCE_PATH,\n                  code=coap.Code.REQ_DELETE,\n                  expect_error_code=coap.Code.RES_BAD_REQUEST)\n\n\nclass BootstrapGetOnExtendedPath(Tests.BootstrapTest):\n    def runTest(self):\n        self.test(path=Tests.EXTENDED_PATH,\n                  code=coap.Code.REQ_GET,\n                  expect_error_code=coap.Code.RES_BAD_OPTION)\n\n\nclass BootstrapPutOnExtendedPath(Tests.BootstrapTest):\n    def runTest(self):\n        self.test(path=Tests.EXTENDED_PATH,\n                  code=coap.Code.REQ_PUT,\n                  expect_error_code=coap.Code.RES_BAD_OPTION)\n\n\nclass BootstrapPostOnExtendedPath(Tests.BootstrapTest):\n    def runTest(self):\n        self.test(path=Tests.EXTENDED_PATH,\n                  code=coap.Code.REQ_POST,\n                  expect_error_code=coap.Code.RES_BAD_OPTION)\n\n\nclass BootstrapDeleteOnExtendedPath(Tests.BootstrapTest):\n    def runTest(self):\n        self.test(path=Tests.EXTENDED_PATH,\n                  code=coap.Code.REQ_DELETE,\n                  expect_error_code=coap.Code.RES_BAD_OPTION)\n"
  },
  {
    "path": "tests/integration/suites/default/con_attr.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nimport socket\n\nfrom framework.lwm2m_test import *\n\n\nclass ConfirmableTest(test_suite.Lwm2mSingleServerTest,\n                      test_suite.Lwm2mDmOperations):\n    def runTest(self):\n        # Create object\n        self.create_instance(self.serv, oid=OID.Test, iid=0)\n\n        # Write Attributes for Counter\n        self.write_attributes(self.serv, oid=OID.Test, iid=0, rid=RID.Test.Counter,\n                              query=['con=1', 'pmax=1'])\n\n        # Observe: Counter\n        counter_pkt = self.observe(self.serv, oid=OID.Test, iid=0, rid=RID.Test.Counter,\n                                   token=random_stuff(8))\n        # Observe: Timestamp\n        timestamp_pkt = self.observe(self.serv, oid=OID.Test, iid=0, rid=RID.Test.Timestamp,\n                                     token=get_another_token(counter_pkt.token))\n\n        con_count = 0\n        non_count = 0\n        for _ in range(10):\n            pkt = self.serv.recv()\n            self.assertEqual(pkt.code, coap.Code.RES_CONTENT)\n            self.assertIn(pkt.token, {counter_pkt.token, timestamp_pkt.token})\n            if pkt.token == counter_pkt.token:\n                self.assertEqual(pkt.type, coap.Type.CONFIRMABLE)\n                self.serv.send(Lwm2mEmpty.matching(pkt)())\n                con_count += 1\n            elif pkt.token == timestamp_pkt.token:\n                self.assertEqual(pkt.type, coap.Type.NON_CONFIRMABLE)\n                non_count += 1\n\n        self.assertGreater(con_count, 0)\n        self.assertGreater(non_count, 0)\n\n        # Cancel Observations\n        # We don't wait for response to the first Cancel Observe before sending\n        # the other one because expected responses are small enough to not\n        # trigger a BLOCK transfer. In that case, Anjay is not expected to\n        # attempt to receive additional packets during request handling - they\n        # should be handled sequentially without any errors.\n        self.serv.send(Lwm2mObserve(\n            ResPath.Test[0].Counter, token=counter_pkt.token, observe=1))\n        self.serv.send(Lwm2mObserve(\n            ResPath.Test[0].Timestamp, token=timestamp_pkt.token, observe=1))\n\n        # flush any remaining notifications & Cancel Observe responses\n        try:\n            while True:\n                pkt = self.serv.recv(timeout_s=0.5)\n                self.assertIn(\n                    pkt.token, {counter_pkt.token, timestamp_pkt.token})\n        except socket.timeout:\n            pass\n\n\nclass NonConfirmableTest(test_suite.Lwm2mSingleServerTest,\n                         test_suite.Lwm2mDmOperations):\n    def setUp(self):\n        super().setUp(extra_cmdline_args=['--confirmable-notifications'])\n\n    def runTest(self):\n        # Create object\n        self.create_instance(self.serv, oid=OID.Test, iid=0)\n\n        # Write Attributes for Counter\n        self.write_attributes(self.serv, oid=OID.Test, iid=0, rid=RID.Test.Counter,\n                              query=['con=0', 'pmax=1'])\n\n        # Observe: Counter\n        counter_pkt = self.observe(self.serv, oid=OID.Test, iid=0, rid=RID.Test.Counter,\n                                   token=random_stuff(8))\n        # Observe: Timestamp\n        timestamp_pkt = self.observe(self.serv, oid=OID.Test, iid=0, rid=RID.Test.Timestamp,\n                                     token=get_another_token(counter_pkt.token))\n\n        con_count = 0\n        non_count = 0\n        for _ in range(10):\n            pkt = self.serv.recv()\n            self.assertEqual(pkt.code, coap.Code.RES_CONTENT)\n            self.assertIn(pkt.token, {counter_pkt.token, timestamp_pkt.token})\n            if pkt.token == counter_pkt.token:\n                self.assertEqual(pkt.type, coap.Type.NON_CONFIRMABLE)\n                non_count += 1\n            elif pkt.token == timestamp_pkt.token:\n                self.assertEqual(pkt.type, coap.Type.CONFIRMABLE)\n                self.serv.send(Lwm2mEmpty.matching(pkt)())\n                con_count += 1\n\n        self.assertGreater(con_count, 0)\n        self.assertGreater(non_count, 0)\n\n        # Cancel Observations\n        # We don't wait for response to the first Cancel Observe before sending\n        # the other one because expected responses are small enough to not\n        # trigger a BLOCK transfer. In that case, Anjay is not expected to\n        # attempt to receive additional packets during request handling - they\n        # should be handled sequentially without any errors.\n        self.serv.send(Lwm2mObserve(\n            ResPath.Test[0].Counter, token=counter_pkt.token, observe=1))\n        self.serv.send(Lwm2mObserve(\n            ResPath.Test[0].Timestamp, token=timestamp_pkt.token, observe=1))\n\n        # flush any remaining notifications & Cancel Observe responses\n        try:\n            while True:\n                pkt = self.serv.recv(timeout_s=0.5)\n                self.assertIn(\n                    pkt.token, {counter_pkt.token, timestamp_pkt.token})\n        except socket.timeout:\n            pass\n"
  },
  {
    "path": "tests/integration/suites/default/conn_status_api.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nfrom framework.lwm2m_test import *\nfrom .access_control import AccessMask\nfrom .bootstrap_client import BootstrapTest\nfrom .register import RegisterUdp\nfrom .retransmissions import RetransmissionTest, RegisterTimeout, DeregisterIcmp\nfrom .queue_mode import QueueMode\n\nimport time\nimport re\nimport enum\n\n\nclass ConnStatusAPI:\n    class Status(enum.Enum):\n        INVALID = enum.auto()\n        ERROR = enum.auto()\n        INITIAL = enum.auto()\n        CONNECTING = enum.auto()\n        BOOTSTRAPPING = enum.auto()\n        BOOTSTRAPPED = enum.auto()\n        REGISTERING = enum.auto()\n        REGISTERED = enum.auto()\n        REG_FAILURE = enum.auto()\n        DEREGISTERING = enum.auto()\n        DEREGISTERED = enum.auto()\n        SUSPENDING = enum.auto()\n        SUSPENDED = enum.auto()\n        REREGISTERING = enum.auto()\n        UPDATING = enum.auto()\n\n    class TestMixin:\n        STATUS_CHANGE_REGEX = re.compile(\n            rb'Current status of the server with SSID (\\d+) is: (.+)\\n')\n\n        # we need this to ensure that other commands which search through\n        # logs will not accidentally consume statements we're interested in\n        log_alt_offset = 0\n\n        def assertStatusChanges(self, statuses, default_ssid=1, timeout_s=0):\n            deadline = time.time() + timeout_s\n\n            assert isinstance(statuses, list)\n\n            for status in statuses:\n                if isinstance(status, ConnStatusAPI.Status):\n                    expected_ssid = default_ssid\n                    expected_status = status\n                else:\n                    assert isinstance(status, tuple)\n                    assert [type(field) for field in status] == [\n                        int, ConnStatusAPI.Status]\n                    expected_ssid, expected_status = status\n\n                self.log_alt_offset, match = self.read_log_until_match(\n                    self.STATUS_CHANGE_REGEX,\n                    timeout_s=max(0, deadline - time.time()),\n                    alt_offset=self.log_alt_offset)\n                self.assertIsNotNone(match)\n\n                actual_ssid = int(match.group(1))\n                actual_status = ConnStatusAPI.Status[match.group(2).decode()]\n\n                self.assertEqual(actual_ssid, expected_ssid)\n                self.assertEqual(actual_status, expected_status)\n\n        def assertNoOutstandingStatusChanges(self):\n            _, match = self.read_log_until_match(\n                self.STATUS_CHANGE_REGEX, timeout_s=0, alt_offset=self.log_alt_offset)\n            self.assertIsNone(match)\n\n    class AutoRegDeregTestMixin(TestMixin):\n        def assertDemoRegisters(\n                self, server=None, initial=True, first_attempt=True, reregister=False, respond=True, reject=False, *args, **kwargs):\n            pkt = super().assertDemoRegisters(server=server,\n                                              respond=respond, reject=reject, *args, **kwargs)\n            expected_statuses = []\n\n            if initial:\n                expected_statuses.append(ConnStatusAPI.Status.INITIAL)\n\n            if first_attempt:\n                expected_statuses.append(ConnStatusAPI.Status.CONNECTING)\n                expected_statuses.append(ConnStatusAPI.Status.REGISTERING)\n\n            if reregister:\n                expected_statuses.append(ConnStatusAPI.Status.REREGISTERING)\n\n            if respond:\n                if reject:\n                    expected_statuses.append(ConnStatusAPI.Status.REG_FAILURE)\n                else:\n                    expected_statuses.append(ConnStatusAPI.Status.REGISTERED)\n\n            self.assertStatusChanges(expected_statuses, timeout_s=2)\n\n            return pkt\n\n        def assertDemoUpdatesRegistration(self, first_attempt=True, respond=True, *args, **kwargs):\n            pkt = super().assertDemoUpdatesRegistration(respond=respond, *args, **kwargs)\n            expected_statuses = []\n\n            if first_attempt:\n                expected_statuses.append(ConnStatusAPI.Status.UPDATING)\n\n            if respond:\n                expected_statuses.append(ConnStatusAPI.Status.REGISTERED)\n\n            self.assertStatusChanges(expected_statuses, timeout_s=2)\n\n            return pkt\n\n        def assertDemoDeregisters(self, server=None, *args, **kwargs):\n            super().assertDemoDeregisters(server=server, *args, **kwargs)\n            self.assertStatusChanges([\n                ConnStatusAPI.Status.DEREGISTERING,\n                ConnStatusAPI.Status.DEREGISTERED], timeout_s=2)\n\n        def tearDown(self, *args, **kwargs):\n            self.assertNoOutstandingStatusChanges()\n            super().tearDown(*args, **kwargs)\n\n\nclass DefaultRegDeregUDP(ConnStatusAPI.AutoRegDeregTestMixin,\n                         test_suite.Lwm2mSingleServerTest):\n    pass\n\n\nclass DefaultRegDeregDTLS(ConnStatusAPI.AutoRegDeregTestMixin,\n                          test_suite.Lwm2mDtlsSingleServerTest):\n    pass\n\n\nclass DefaultRegDeregTCP(ConnStatusAPI.AutoRegDeregTestMixin,\n                         test_suite.Lwm2mSingleTcpServerTest):\n    def setUp(self, *args, **kwargs):\n        super().setUp(binding='T', *args, **kwargs)\n\n\nclass DefaultRegDeregTLS(ConnStatusAPI.AutoRegDeregTestMixin,\n                         test_suite.Lwm2mTlsSingleServerTest):\n    def setUp(self, *args, **kwargs):\n        super().setUp(binding='T', *args, **kwargs)\n\n\nclass ServerStaysInRegisteringStateDuringRetries(\n        ConnStatusAPI.AutoRegDeregTestMixin, RegisterUdp.TestCase):\n    def runTest(self):\n        self.assertDemoRegisters(respond=False)\n        self.assertNoOutstandingStatusChanges()\n        self.assertDemoRegisters(\n            initial=False, first_attempt=False, respond=False, timeout_s=6)\n        self.assertNoOutstandingStatusChanges()\n        self.assertDemoRegisters(\n            initial=False, first_attempt=False, timeout_s=12)\n\n\nclass RejectedRegisterChangesStateToRegFailure(ConnStatusAPI.AutoRegDeregTestMixin,\n                                               test_suite.Lwm2mSingleServerTest):\n    def setUp(self, *args, **kwargs):\n        super().setUp(auto_register=False, *args, **kwargs)\n\n    def runTest(self):\n        self.assertDemoRegisters(reject=True)\n\n    def tearDown(self):\n        super().tearDown(auto_deregister=False)\n\n\nclass ConnectFailureChangesStateToError(ConnStatusAPI.AutoRegDeregTestMixin, test_suite.Lwm2mSingleTcpServerTest):\n    def setUp(self, *args, **kwargs):\n        super().setUp(binding='T', auto_register=False, *args, **kwargs)\n\n    def runTest(self):\n        self.assertStatusChanges(\n            [ConnStatusAPI.Status.INITIAL, ConnStatusAPI.Status.CONNECTING], timeout_s=2)\n        self.serv.close()\n        self.assertStatusChanges([ConnStatusAPI.Status.ERROR], timeout_s=2)\n\n    def tearDown(self):\n        super().tearDown(auto_deregister=False)\n\n\nclass UpdatesChangeStateToUpdating(ConnStatusAPI.AutoRegDeregTestMixin,\n                                   test_suite.Lwm2mSingleServerTest):\n    def runTest(self):\n        self.assertNoOutstandingStatusChanges()\n\n        self.communicate('send-update')\n        self.assertDemoUpdatesRegistration()\n        LIFETIME = 2\n\n        self.serv.send(Lwm2mWrite(ResPath.Server[1].Lifetime, str(LIFETIME)))\n        self.serv.recv()\n\n        self.assertDemoUpdatesRegistration(lifetime=LIFETIME)\n\n        self.assertNoOutstandingStatusChanges()\n\n        # wait for auto-scheduled Update\n        self.assertDemoUpdatesRegistration(timeout_s=LIFETIME)\n\n\nclass UpdateFailureChangesStateToReregistering(ConnStatusAPI.AutoRegDeregTestMixin, test_suite.Lwm2mSingleServerTest):\n    LIFETIME = 4\n\n    def setUp(self):\n        super().setUp(auto_register=False, lifetime=self.LIFETIME,\n                      extra_cmdline_args=['--ack-random-factor', '1', '--ack-timeout', '1',\n                                          '--max-retransmit', '1'])\n        self.assertDemoRegisters(lifetime=self.LIFETIME)\n\n    def runTest(self):\n        self.assertDemoUpdatesRegistration(timeout_s=self.LIFETIME / 2 + 1)\n\n        self.assertDemoUpdatesRegistration(\n            timeout_s=self.LIFETIME / 2 + 1, respond=False)\n        self.assertDemoUpdatesRegistration(\n            timeout_s=self.LIFETIME / 2 + 1, first_attempt=False, respond=False)\n        self.assertDemoRegisters(lifetime=self.LIFETIME, timeout_s=self.LIFETIME /\n                                 2 + 1, initial=False, first_attempt=False, reregister=True)\n\n\nclass DtlsReregisterFailureDoesntReportRegFailure(ConnStatusAPI.AutoRegDeregTestMixin,\n                                                  RetransmissionTest.TestMixin,\n                                                  test_suite.Lwm2mDtlsSingleServerTest):\n    MAX_RETRANSMIT = 1\n    ACK_TIMEOUT = 1\n\n    def tearDown(self):\n        super().teardown_demo_with_servers(auto_deregister=False)\n\n    def runTest(self):\n        # force re-registration;\n        # Register only falls back to handshake if it's not performed immediately after one\n        self.communicate('send-update')\n        pkt = self.assertDemoUpdatesRegistration(respond=False)\n        self.serv.send(Lwm2mErrorResponse.matching(pkt)\n                       (code=coap.Code.RES_NOT_FOUND))\n\n        # Ignore register requests.\n        for i in range(self.MAX_RETRANSMIT + 1):\n            self.assertDemoRegisters(\n                respond=False,\n                timeout_s=self.last_retransmission_timeout(),\n                initial=False,\n                first_attempt=False,\n                reregister=i == 0)\n\n        self.assertNoOutstandingStatusChanges()\n        self.wait_for_retransmission_response_timeout()\n\n        # Demo should fall back to DTLS handshake.\n        self.assertPktIsDtlsClientHello(\n            self.serv._raw_udp_socket.recv(4096), seq_number=0)\n        self.assertStatusChanges([ConnStatusAPI.Status.CONNECTING])\n\n        self.wait_for_retransmission_response_timeout()\n        # Ensure that the control is given back to the user.\n        self.assertTrue(self.get_all_connections_failed())\n        self.assertStatusChanges([ConnStatusAPI.Status.ERROR])\n        self.assertNoOutstandingStatusChanges()\n\n\nclass DeregisterFailure:\n    class TestMixin(ConnStatusAPI.AutoRegDeregTestMixin, DeregisterIcmp.TestMixin):\n        def tearDown(self):\n            self.assertStatusChanges(\n                [ConnStatusAPI.Status.DEREGISTERING,\n                 ConnStatusAPI.Status.ERROR], timeout_s=2)\n            super().tearDown()\n\n\nclass DeregisterFailureChangesStateToError(DeregisterFailure.TestMixin,\n                                           test_suite.Lwm2mSingleServerTest):\n    pass\n\n\nclass DtlsDeregisterFailureChangesStateToError(DeregisterFailure.TestMixin,\n                                               test_suite.Lwm2mDtlsSingleServerTest):\n    pass\n\n\nclass DisablingServerRemotely(\n        ConnStatusAPI.AutoRegDeregTestMixin, test_suite.Lwm2mSingleServerTest):\n    def assertSocketsPolled(self, num):\n        self.assertEqual(num, self.get_socket_count())\n\n    def runTest(self):\n        self.assertNoOutstandingStatusChanges()\n\n        # Write Disable Timeout\n        req = Lwm2mWrite(ResPath.Server[1].DisableTimeout, '6')\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.serv.recv())\n\n        self.assertNoOutstandingStatusChanges()\n\n        # Execute Disable\n        req = Lwm2mExecute(ResPath.Server[1].Disable)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.serv.recv())\n        self.assertStatusChanges(\n            [ConnStatusAPI.Status.SUSPENDING], timeout_s=2)\n\n        self.assertDemoDeregisters(timeout_s=5)\n        self.assertStatusChanges([ConnStatusAPI.Status.SUSPENDED], timeout_s=2)\n\n        # no message for now\n        with self.assertRaises(socket.timeout):\n            print(self.serv.recv(timeout_s=5))\n\n        self.assertSocketsPolled(0)\n        self.assertFalse(self.ongoing_registration_exists())\n\n        # we should get another Register\n        self.assertDemoRegisters(initial=False, timeout_s=3)\n\n        # no message for now\n        with self.assertRaises(socket.timeout):\n            print(self.serv.recv(timeout_s=2))\n\n        self.assertSocketsPolled(1)\n\n\nclass DisablingSecondTimeDoesntChangeState(ConnStatusAPI.TestMixin,\n                                           test_suite.Lwm2mTest,\n                                           test_suite.Lwm2mDmOperations):\n    def setUp(self, **kwargs):\n        super().setUp(servers=2, extra_cmdline_args=['--access-entry',\n                                                     '/%d/1,2,%d' % (OID.Server, AccessMask.OWNER)])\n\n    def runTest(self):\n        expected_states = []\n\n        for ssid in range(1, 3):\n            expected_states.append((ssid, ConnStatusAPI.Status.INITIAL))\n        for ssid in range(1, 3):\n            expected_states.append((ssid, ConnStatusAPI.Status.CONNECTING))\n            expected_states.append((ssid, ConnStatusAPI.Status.REGISTERING))\n        for ssid in range(1, 3):\n            expected_states.append((ssid, ConnStatusAPI.Status.REGISTERED))\n\n        self.assertStatusChanges(expected_states, timeout_s=2)\n        self.assertNoOutstandingStatusChanges()\n\n        self.write_resource(\n            self.servers[1], OID.Server, 1, RID.Server.DisableTimeout, b'6')\n        self.execute_resource(\n            self.servers[1], OID.Server, 1, RID.Server.Disable)\n        first_disable_timestamp = time.time()\n        self.assertDemoDeregisters(self.servers[0])\n\n        self.assertStatusChanges([\n            ConnStatusAPI.Status.SUSPENDING,\n            ConnStatusAPI.Status.DEREGISTERING,\n            ConnStatusAPI.Status.DEREGISTERED,\n            ConnStatusAPI.Status.SUSPENDED], timeout_s=2)\n        self.assertNoOutstandingStatusChanges()\n\n        # no message for now\n        with self.assertRaises(socket.timeout):\n            print(self.servers[0].recv(timeout_s=3))\n\n        # execute Disable again, this should reset the timer\n        self.execute_resource(\n            self.servers[1], OID.Server, 1, RID.Server.Disable)\n        with self.assertRaises(socket.timeout):\n            print(self.servers[0].recv(timeout_s=5))\n\n        self.assertNoOutstandingStatusChanges()\n\n        self.assertFalse(self.ongoing_registration_exists())\n\n        # only now the server should re-register\n        self.assertDemoRegisters(server=self.servers[0], timeout_s=3)\n        register_timestamp = time.time()\n\n        self.assertGreater(register_timestamp - first_disable_timestamp, 8)\n\n        self.assertStatusChanges([\n            ConnStatusAPI.Status.CONNECTING,\n            ConnStatusAPI.Status.REGISTERING,\n            ConnStatusAPI.Status.REGISTERED], timeout_s=2)\n        self.assertNoOutstandingStatusChanges()\n\n\nclass ShutdownDuringQueueModeChangesStateDirectlyToDeregistered(ConnStatusAPI.AutoRegDeregTestMixin,\n                                                                QueueMode.Test,\n                                                                test_suite.Lwm2mSingleServerTest):\n    def setUp(self):\n        self.skipIfFeatureStatus('ANJAY_WITHOUT_QUEUE_MODE_AUTOCLOSE = ON',\n                                 'If socket autoclose is disabled, the client deregisters as usual')\n        super().setUp()\n\n    def runTest(self):\n        self.assertNoOutstandingStatusChanges()\n\n        self.communicate('set-queue-mode-preference FORCE_QUEUE_MODE')\n\n        self.communicate('send-update')\n        self.assertDemoUpdatesRegistration()\n\n        # # await for the client to close the socket\n        time.sleep(self.max_transmit_wait() - 2)\n        self.assertEqual(self.get_socket_count(), 1)\n        time.sleep(4)\n        self.assertEqual(self.get_socket_count(), 0)\n\n    def tearDown(self):\n        super().tearDown(auto_deregister=False)\n\n    def _terminate_demo(self, *args, **kwargs):\n        # since the state change is induced during shutdown of the client, this\n        # is the only way to catch this state\n        self.assertStatusChanges(\n            [ConnStatusAPI.Status.DEREGISTERED], timeout_s=2)\n        super()._terminate_demo(*args, **kwargs)\n\n\n\n\nclass IcmpErrorDuringRegisterCausesError(ConnStatusAPI.AutoRegDeregTestMixin,\n                                         test_suite.PcapEnabledTest,\n                                         RetransmissionTest.TestMixin,\n                                         test_suite.Lwm2mSingleServerTest):\n    MAX_RETRANSMIT = 1\n    ACK_TIMEOUT = 4\n\n    def setUp(self):\n        super().setUp(auto_register=False)\n\n    def tearDown(self):\n        super().teardown_demo_with_servers(auto_deregister=False)\n\n    def runTest(self):\n        self.assertDemoRegisters()\n\n        # Close socket to induce ICMP port unreachable.\n        with self.serv.fake_close():\n            # Force Register\n            self.communicate('reconnect')\n            self.assertStatusChanges([\n                ConnStatusAPI.Status.CONNECTING,\n                ConnStatusAPI.Status.REGISTERING], timeout_s=2)\n            # Wait for ICMP port unreachable.\n            self.wait_until_icmp_unreachable_count(\n                1, timeout_s=self.last_retransmission_timeout())\n\n        # Ensure that the control is given back to the user.\n        with self.assertRaises(socket.timeout, msg=\"unexpected packets from the client\"):\n            self.serv.recv()\n\n        self.assertStatusChanges([ConnStatusAPI.Status.ERROR], timeout_s=2)\n\n        self.assertEqual(0, self.get_socket_count())\n        self.assertTrue(self.get_all_connections_failed())\n\n        self.assertNoOutstandingStatusChanges()\n\n\nclass RegisterTimeoutRegFailure:\n    class TestMixin(ConnStatusAPI.AutoRegDeregTestMixin, RegisterTimeout.TestMixin):\n        MAX_RETRANSMIT = 3\n\n        def runTest(self):\n            # Required for DTLS variant, in which this completes a handshake\n            # which is a part of connect() call.\n            self.serv.listen(timeout_s=10)\n\n            # Ignore register requests.\n            for i in range(self.MAX_RETRANSMIT + 1):\n                self.assertDemoRegisters(respond=False,\n                                         timeout_s=self.last_retransmission_timeout() + 5,\n                                         initial=i == 0,\n                                         first_attempt=i == 0)\n\n            self.assertNoOutstandingStatusChanges()\n            self.wait_for_retransmission_response_timeout()\n\n            self.assertStatusChanges(\n                [ConnStatusAPI.Status.REG_FAILURE], timeout_s=2)\n\n            # Ensure that server is considered unreachable, and control given back to the user.\n            self.assertEqual(0, self.get_socket_count())\n            self.assertTrue(self.get_all_connections_failed())\n\n            self.assertNoOutstandingStatusChanges()\n\n\nclass UdpRegisterTimeoutCausesRegFailure(RegisterTimeoutRegFailure.TestMixin,\n                                         test_suite.Lwm2mSingleServerTest):\n    pass\n\n\nclass DtlsRegisterTimeoutCausesRegFailure(RegisterTimeoutRegFailure.TestMixin,\n                                          test_suite.Lwm2mDtlsSingleServerTest):\n    pass\n\n\nclass ReportsErrorIfRegistrationFailsDueToNetworkIssues(ConnStatusAPI.AutoRegDeregTestMixin,\n                                                        test_suite.PcapEnabledTest,\n                                                        test_suite.Lwm2mDtlsSingleServerTest):\n    RETRY_COUNT = 3\n    MAX_RETRANSMIT = 1\n\n    def setUp(self):\n        extra_cmdline_args = ['--retry-count', str(self.RETRY_COUNT), '--max-retransmit',\n                              str(self.MAX_RETRANSMIT), '--ack-timeout', str(1)]\n        super().setUp(extra_cmdline_args=extra_cmdline_args, auto_register=False)\n\n    def tearDown(self):\n        super().tearDown(auto_deregister=False)\n\n    def runTest(self):\n        for i in range(self.MAX_RETRANSMIT + 1):\n            self.assertDemoRegisters(respond=False, timeout_s=5, initial=i == 0, first_attempt=i == 0)\n\n        num_initial_dtls_hs_packets = self.count_dtls_client_hello_packets()\n        # Close socket to induce ICMP port unreachable.\n        self.serv.close()\n\n        # Wait for ICMP port unreachable.\n        self.wait_until_icmp_unreachable_count(1, timeout_s=5)\n\n        self.assertStatusChanges([\n            ConnStatusAPI.Status.REG_FAILURE,\n            ConnStatusAPI.Status.CONNECTING,\n            ConnStatusAPI.Status.ERROR], timeout_s=2)\n\n        time.sleep(5)\n        # Ensure that no more retransmissions occurred.\n        self.assertEqual(1, self.count_icmp_unreachable_packets())\n        # Ensure that only one more dtls handshake messages occurred.\n        self.assertEqual(num_initial_dtls_hs_packets + 1,\n                         self.count_dtls_client_hello_packets())\n\n        # Ensure that the control is given back to the user.\n        self.assertTrue(self.get_all_connections_failed())\n        self.assertNoOutstandingStatusChanges()\n\n\nclass ReportsRegFailureIfConnectionErrorIsRegFailure(ConnStatusAPI.AutoRegDeregTestMixin,\n                                                     test_suite.PcapEnabledTest,\n                                                     test_suite.Lwm2mDtlsSingleServerTest):\n    RETRY_COUNT = 3\n    MAX_RETRANSMIT = 1\n\n    def setUp(self):\n        extra_cmdline_args = ['--retry-count', str(self.RETRY_COUNT), '--max-retransmit',\n                              str(self.MAX_RETRANSMIT), '--ack-timeout', str(1),\n                              '--connection-error-is-registration-failure']\n        super().setUp(extra_cmdline_args=extra_cmdline_args, auto_register=False)\n\n    def tearDown(self):\n        super().tearDown(auto_deregister=False)\n\n    def runTest(self):\n        for i in range(self.MAX_RETRANSMIT + 1):\n            self.assertDemoRegisters(respond=False, timeout_s=5, initial=i == 0, first_attempt=i == 0)\n\n        num_initial_dtls_hs_packets = self.count_dtls_client_hello_packets()\n        # Close socket to induce ICMP port unreachable.\n        self.serv.close()\n\n        # Wait for ICMP port unreachable.\n        self.wait_until_icmp_unreachable_count(1, timeout_s=5)\n\n        self.assertStatusChanges([\n            ConnStatusAPI.Status.REG_FAILURE,\n            ConnStatusAPI.Status.CONNECTING], timeout_s=2)\n\n        # And another one - the third registration attempt\n        self.wait_until_icmp_unreachable_count(1, timeout_s=5)\n\n        self.assertStatusChanges([\n            ConnStatusAPI.Status.REG_FAILURE,\n            ConnStatusAPI.Status.CONNECTING], timeout_s=2)\n\n        time.sleep(5)\n        # Ensure that no more retransmissions occurred.\n        self.assertEqual(2, self.count_icmp_unreachable_packets())\n        # Ensure that only one more dtls handshake messages occurred.\n        self.assertEqual(num_initial_dtls_hs_packets + 2,\n                         self.count_dtls_client_hello_packets())\n\n        self.assertStatusChanges([ConnStatusAPI.Status.REG_FAILURE])\n\n        # Ensure that the control is given back to the user.\n        self.assertTrue(self.get_all_connections_failed())\n        self.assertNoOutstandingStatusChanges()\n\n\nclass Bootstrap:\n    class TestMixin(ConnStatusAPI.AutoRegDeregTestMixin):\n        def assertDemoRequestsBootstrap(self, *args, **kwargs):\n            super().assertDemoRequestsBootstrap(*args, **kwargs)\n            self.assertStatusChanges([\n                ConnStatusAPI.Status.INITIAL,\n                ConnStatusAPI.Status.CONNECTING,\n                ConnStatusAPI.Status.BOOTSTRAPPING], default_ssid=65535, timeout_s=2)\n\n        def perform_bootstrap_finish(self, *args, **kwargs):\n            super().perform_bootstrap_finish(*args, **kwargs)\n            self.assertStatusChanges(\n                [ConnStatusAPI.Status.BOOTSTRAPPED], default_ssid=65535, timeout_s=2)\n\n\nclass DefaultBootstrapTest(Bootstrap.TestMixin, BootstrapTest.Test):\n    def setUp(self):\n        super().setUp(holdoff_s=3, timeout_s=3)\n\n    def runTest(self):\n        self.perform_typical_bootstrap(server_iid=1,\n                                       security_iid=2,\n                                       server_uri='coap://127.0.0.1:%d' % self.serv.get_listen_port(),\n                                       lifetime=60)\n\n        # should now register with the non-bootstrap server\n        self.assertDemoRegisters(self.serv, lifetime=60)\n\n\nclass BootstrapNoResponseTest(Bootstrap.TestMixin, BootstrapTest.Test):\n    ACK_TIMEOUT = 1\n    MAX_RETRANSMIT = 1\n\n    def setUp(self):\n        # Done to have a relatively short EXCHANGE_LIFETIME\n        super().setUp(extra_cmdline_args=['--ack-random-factor', '1',\n                                          '--ack-timeout', '%s' % self.ACK_TIMEOUT,\n                                          '--max-retransmit', '%s' % self.MAX_RETRANSMIT])\n\n    def tearDown(self):\n        super().tearDown(auto_deregister=False)\n\n    def runTest(self):\n        # We should get Bootstrap Request now\n        self.assertDemoRequestsBootstrap()\n\n        self.assertEqual(1, self.get_socket_count())\n        self.advance_demo_time(TxParams(ack_timeout=self.ACK_TIMEOUT,\n                                        max_retransmit=self.MAX_RETRANSMIT).exchange_lifetime())\n        self.wait_until_socket_count(0, timeout_s=5)\n        self.assertStatusChanges(\n            [ConnStatusAPI.Status.ERROR], default_ssid=65535)\n"
  },
  {
    "path": "tests/integration/suites/default/connection_id.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nimport contextlib\nimport errno\nimport select\nimport threading\nimport unittest\n\nimport pymbedtls\n\nfrom framework.lwm2m_test import *\nfrom suites.default.retransmissions import RetransmissionTest\n\n\nclass UdpProxy(threading.Thread):\n    def __init__(self, client_socket, server_socket):\n        super().__init__()\n        self._mutex = threading.Lock()\n        # This is the socket the demo sees and communicates with.\n        self._client_socket = client_socket\n        # This will be the socket connected to the LwM2M server. It MUST be connected to the target server.\n        self._server_socket = server_socket\n\n    def _handle_client_pkt(self, sock):\n        pkt, remote_addr = sock.recvfrom(4096)\n        self._client_socket.connect(remote_addr)\n\n        self._server_socket.send(pkt)\n\n    def _handle_server_pkt(self, sock):\n        assert self._client_socket is not None\n        pkt, _ = sock.recvfrom(4096)\n\n        self._client_socket.send(pkt)\n\n    def run(self):\n        with self._mutex:\n            self._operating = True\n\n        poller = select.poll()\n        poller.register(self._client_socket, select.POLLIN)\n        poller.register(self._server_socket, select.POLLIN)\n        while True:\n            with self._mutex:\n                if not self._operating:\n                    break\n\n            # Timeout after 60ms, to be able to interrupt the thread.\n            for (fd, event) in poller.poll(60):\n                if event & select.POLLIN:\n                    if fd == self._client_socket.fileno():\n                        self._handle_client_pkt(self._client_socket)\n                    elif fd == self._server_socket.fileno():\n                        self._handle_server_pkt(self._server_socket)\n                if event & (select.POLLERR | select.POLLHUP):\n                    raise RuntimeError('Socket error while trying to poll()')\n\n    def stop(self):\n        with self._mutex:\n            self._operating = False\n\n\nclass CoapServerWithProxy(coap.DtlsServer):\n    def __init__(self, *args, **kwargs):\n        # This is the socket the demo sees and communicates with.\n        self._client_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)\n        self._client_socket.bind(('', 0))\n        super().__init__(*args, **kwargs)\n        # First self.reset() is called in parent class, and it setups some stuff\n        # like an actual server socket. But then, another reset() would cause\n        # an attempt to reuse existing UDP port (the one returend by overriden\n        # get_listen_port()), therefore we disallow doing that.\n        self.reset = self._not_implemented\n\n    def _not_implemented(self, *args, **kwargs):\n        raise NotImplementedError\n\n    def get_listen_port(self):\n        return self.get_local_addr()[1]\n\n    def get_local_addr(self):\n        return self._client_socket.getsockname()\n\n    @contextlib.contextmanager\n    def server_proxy(self):\n        server_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)\n        server_socket.connect(super().get_local_addr())\n        proxy = UdpProxy(self._client_socket, server_socket)\n        proxy.start()\n        try:\n            yield\n        finally:\n            proxy.stop()\n            proxy.join()\n\n\ndef disconnect_socket(sock):\n    timeout = sock.gettimeout()\n    try:\n        # get rid of any data that might be in the socket buffer\n        try:\n            sock.settimeout(0)\n            sock.recv(65535)\n        except BlockingIOError:\n            pass\n        sock.connect(('', 0))\n    except OSError as e:\n        # On macOS, the call above returns failure, but actually works anyway...\n        if e.errno not in {errno.EAFNOSUPPORT, errno.EADDRNOTAVAIL}:\n            raise\n    finally:\n        sock.settimeout(timeout)\n\n\n# At the beginning of the test, things look like this:\n#\n# .------.      .-----------------------.      .-----------------------.      .--------------.\n# | demo | <--> | client proxy (port A) |      |                       |      | Lwm2m Server |\n# '------'      '-----------------------'      '-----------------------'      '--------------'\n#\n# That is, there's no path from demo to the actual Lwm2m Server.\n#\n# The path is created when one calls with self.serv.server_proxy(). The call creates\n# a \"server-side\" proxy socket (filling the empty box), forwarding packets from client\n# proxy to a backend Lwm2m Server:\n#\n# .------.      .-----------------------.      .-----------------------.      .--------------.\n# | demo | <--> | client proxy (port A) | <--> | server proxy (port B) | <--> | Lwm2m Server |\n# '------'      '-----------------------'      '-----------------------'      '--------------'\n#\n# From the Lwm2m Server perspective, the communication then looks like this (note that\n# it sees the client using port B, due to packets being passed-through the \"server proxy\"):\n#\n# .--------------.                                       .--------------.\n# | demo (port B)|                                       | Lwm2m Server |\n# '--------------'                                       '--------------'\n#       |    ----------- Client Hello connection_id() -------->   |\n#       |                                                         |\n#       |                             ...                         |\n#       |                                                         |\n#       |    <---- Server Hello + connection_id(\"something\") --   |\n#       |                                                         |\n#       |                             ...                         |\n#       |                                                         |\n#       |    <--------------- regular LwM2M stuff ------------>   |\n#\n# After a while, the Client's port changes for some reason. This is represented as\n# another call to with self.serv.server_proxy(), which basically creates a new\n# \"server proxy\" socket:\n#                                         note the port change -.\n#                                                               v\n# .------.      .-----------------------.      .-----------------------.      .--------------.\n# | demo | <--> | client proxy (port A) | <--> | server proxy (port C) | <--> | Lwm2m Server |\n# '------'      '-----------------------'      '-----------------------'      '--------------'\n#\n# In normal circumstances it'd confuse the Server, and a re-registration or at least\n# re-handshake would have happened. However, with connection_id extension, the Server\n# recognizes the client and no additional communication is performed.\n#\n# .--------------.                                       .--------------.\n# | demo (port C)|                                       | Lwm2m Server |\n# '--------------'                                       '--------------'\n#       |  ---- Lwm2M Update + connection_id(\"something\") ---->   |\n#       |                                                         |\n#       |    <---------------- 2.04 Changed -------------------   |\n#\n@unittest.skipIf(not pymbedtls.Context.supports_connection_id(),\n                 \"connection_id support is not enabled in pymbedtls\")\nclass DtlsConnectionIdTest(test_suite.Lwm2mDtlsSingleServerTest,\n                           test_suite.Lwm2mDmOperations):\n    CONNECTION_ID_VALUE = 'something'\n\n    def setUp(self, extra_cmdline_args=[], **kwargs):\n        if '--use-connection-id' not in extra_cmdline_args:\n            extra_cmdline_args.insert(0, '--use-connection-id')\n        server = Lwm2mServer(CoapServerWithProxy(psk_identity=self.PSK_IDENTITY,\n                                                 psk_key=self.PSK_KEY,\n                                                 connection_id=self.CONNECTION_ID_VALUE))\n        super().setUp(servers=[server], auto_register=False, extra_cmdline_args=extra_cmdline_args,\n                      **kwargs)\n\n    def runTest(self):\n        with self.serv.server_proxy():\n            self.assertDemoRegisters()\n            self.read_resource(self.serv, oid=OID.Device, iid=0, rid=0)\n\n        # Unconnect the socket at the pymbedtls site (this unconnects the link between \"server proxy\"\n        # and \"Lwm2m Server\" in the diagram above), to allow accepting packets from unknown endpoints.\n        disconnect_socket(self.serv.socket.py_socket)\n\n        with self.serv.server_proxy():\n            self.communicate('send-update')\n            self.assertDemoUpdatesRegistration()\n            super().request_demo_shutdown()\n            self.assertDemoDeregisters(reset=False)\n\n    def tearDown(self):\n        super().tearDown(auto_deregister=False)\n\n\n# The flow in this test is similar to DtlsConnectionIdTest, with the exception that\n# this time connection_id extension is not used, and Server ignores messages from\n# an endpoint it doesn't recognize via (host, port) tuple.\nclass DtlsWithoutConnectionIdTest(test_suite.Lwm2mDtlsSingleServerTest,\n                                  test_suite.Lwm2mDmOperations):\n\n    def setUp(self):\n        server = Lwm2mServer(CoapServerWithProxy(psk_identity=self.PSK_IDENTITY,\n                                                 psk_key=self.PSK_KEY))\n        super().setUp(servers=[server], auto_register=False)\n\n    def runTest(self):\n        with self.serv.server_proxy():\n            self.assertDemoRegisters()\n            self.read_resource(self.serv, oid=OID.Device, iid=0, rid=0)\n\n        # Unconnect the socket at the pymbedtls site (this unconnects the link between \"server proxy\"\n        # and \"Lwm2m Server\" in the diagram above), to allow accepting packets from unknown endpoints.\n        disconnect_socket(self.serv.socket.py_socket)\n\n        # Nonetheless, connection_id was not used, so we should expect that the server\n        # ignores Update messages messages.\n        with self.serv.server_proxy():\n            self.communicate('send-update')\n            with self.assertRaises(socket.timeout):\n                self.assertDemoUpdatesRegistration()\n\n    def tearDown(self):\n        super().tearDown(force_kill=True)\n\n\n"
  },
  {
    "path": "tests/integration/suites/default/crash.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nimport time\n\nfrom framework.lwm2m_test import *\n\n\n# Setting up the CoAP stream for communication with a specific server is done\n# by calling _anjay_get_server_stream. The stream has to be released with\n# _anjay_release_server_stream before acquiring it again, or the library will\n# cause an assertion failure.\n#\n# There was an issue in the Update sending routine that prevented the release\n# call from being made in the case of an socket error, such as receiving the\n# \"Port Unreachable\" ICMP message. This caused the demo to terminate on the\n# next attempt to use the stream.\n#\n# This test case ensures that the demo does not crash in such case.\n\nclass AccessViolationOn256ByteUri(test_suite.Lwm2mSingleServerTest):\n    # overrides a method from Lwm2mTest, called from Lwm2mTest.setup_demo_with_servers()\n    def make_demo_args(self, *args, **kwargs):\n        result = super().make_demo_args(*args, **kwargs)\n        url = result[-1]\n        self.assertTrue(url.startswith('coap://'))  # assert it is really a URL\n        url = url.replace('.0', '.' + '0' * (256 - len(url) + 1), 1)  # \"coap://127.000(...)000.0.1:12345/\"\n        self.assertEqual(len(url), 256)\n        result[-1] = url\n        return result\n\n    def setUp(self):\n        super().setUp(auto_register=False)\n\n    def tearDown(self):\n        super().tearDown(auto_deregister=False)\n\n    def runTest(self):\n        self.communicate('')\n\n\nclass CrashAfterRequestWithTokenFollowedByNoToken(test_suite.Lwm2mSingleServerTest):\n    def runTest(self):\n        # failure to clear the token from last message remembered by output\n        # buffer combined with not overriding cached token length in case of\n        # a zero-length token caused anjay to incorrectly assume a non-empty\n        # token was already written to the buffer\n        req = Lwm2mRead(ResPath.Location.Latitude, token=b'foo')\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mContent.matching(req)(),\n                            self.serv.recv())\n\n        # the bug causes assertion failure during handling of this message\n        req = Lwm2mRead(ResPath.Location.Latitude, token=b'')\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mContent.matching(req)(),\n                            self.serv.recv())\n\n\nclass ObserveCancelCrash(test_suite.Lwm2mSingleServerTest):\n    def runTest(self):\n        req = Lwm2mWriteAttributes(ResPath.Device.PowerSourceVoltage, pmax=1,\n                                   options=[coap.Option.URI_QUERY('con=1')])\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.serv.recv())\n\n        req = Lwm2mObserve(ResPath.Device.PowerSourceVoltage)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mContent.matching(req)(), self.serv.recv())\n\n        notif = self.serv.recv(timeout_s=1.5)\n        self.assertIsInstance(notif, Lwm2mNotify)\n        self.assertEqual(notif.type, coap.Type.CONFIRMABLE)\n\n        # send Cancel Observe before the ACK\n        req = Lwm2mObserve(ResPath.Device.PowerSourceVoltage, observe=1, token=notif.token)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mContent.matching(req)(), self.serv.recv())\n\n        # send the ACK\n        self.serv.send(Lwm2mEmpty.matching(notif)())\n\n\nclass ConfirmableBlockNotifCrash(test_suite.Lwm2mSingleServerTest, test_suite.Lwm2mDmOperations):\n    def runTest(self):\n        self.create_instance(self.serv, OID.Test)\n        self.write_attributes(self.serv, OID.Test, 0, RID.Test.ResBytes, ['con=1'])\n        obs = self.observe(self.serv, OID.Test, 0, RID.Test.ResBytes)\n        self.write_resource(self.serv, OID.Test, 0, RID.Test.ResBytesSize, b'1500')\n\n        expected_seq_count = 2\n        for seq in range(expected_seq_count):\n            pkt = self.serv.recv()\n            if seq == 0:\n                self.assertMsgEqual(\n                    Lwm2mNotify(token=obs.token, format=coap.ContentFormat.TEXT_PLAIN,\n                                confirmable=True, options=[coap.Option.OBSERVE(1),\n                                                           coap.Option.BLOCK2(seq,\n                                                                              seq < expected_seq_count - 1,\n                                                                              1024)]), pkt)\n            else:\n                self.assertMsgEqual(Lwm2mContent.matching(req)(format=coap.ContentFormat.TEXT_PLAIN,\n                                                               options=[coap.Option.BLOCK2(seq,\n                                                                                           seq < expected_seq_count - 1,\n                                                                                           1024)]),\n                                    pkt)\n            if seq < expected_seq_count - 1:\n                # Corner case: GET for the next block arrives before the ACK\n                req = Lwm2mRead(ResPath.Test[0].ResBytes, token=obs.token,\n                                options=[coap.Option.BLOCK2(seq + 1, False, 1024)])\n                self.serv.send(req)\n            if seq == 0:\n                self.serv.send(Lwm2mEmpty.matching(pkt)())\n"
  },
  {
    "path": "tests/integration/suites/default/create.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nfrom framework.lwm2m_test import *\n\n\nclass Create(test_suite.Lwm2mSingleServerTest):\n    def runTest(self):\n        req = Lwm2mCreate('/%d' % (OID.GeoPoints,),\n                          b''.join(map(TLV.serialize,\n                                       [TLV.make_resource(RID.GeoPoints.Latitude, 42.0),\n                                        TLV.make_resource(RID.GeoPoints.Longitude, 69.0),\n                                        TLV.make_resource(RID.GeoPoints.Radius, 5.0)])))\n        self.serv.send(req)\n        res = self.serv.recv()\n        self.assertMsgEqual(Lwm2mCreated.matching(req)(), res)\n\n        # attempt to create \"the same\" object again\n        iid = int(res.get_options(coap.Option.LOCATION_PATH)[1].content)\n        req = Lwm2mCreate('/%d' % (OID.GeoPoints,),\n                          TLV.make_instance(iid,\n                                            [TLV.make_resource(RID.GeoPoints.Latitude, 14.0),\n                                             TLV.make_resource(RID.GeoPoints.Longitude, 17.0),\n                                             TLV.make_resource(RID.GeoPoints.Radius, 19.0)]).serialize())\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mErrorResponse.matching(req)(code=coap.Code.RES_BAD_REQUEST),\n                            self.serv.recv())\n\n\nclass IncompleteCreate(test_suite.Lwm2mSingleServerTest):\n    def runTest(self):\n        req = Lwm2mCreate('/%d' % (OID.GeoPoints,),\n                          b''.join(map(TLV.serialize,\n                                       [TLV.make_resource(RID.GeoPoints.Latitude, 42.0),\n                                        TLV.make_resource(RID.GeoPoints.Longitude, 69.0)])))  # longitude\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mErrorResponse.matching(req)(code=coap.Code.RES_BAD_REQUEST),\n                            self.serv.recv())\n\n\nclass UnsupportedCreate(test_suite.Lwm2mSingleServerTest):\n    def runTest(self):\n        req = Lwm2mCreate('/%d' % (OID.GeoPoints,),\n                          b''.join(map(TLV.serialize,\n                                       [TLV.make_resource(RID.GeoPoints.Latitude, 42.0),\n                                        TLV.make_resource(RID.GeoPoints.Longitude, 69.0),\n                                        TLV.make_resource(RID.GeoPoints.Radius, 5.0),\n                                        TLV.make_resource(42, b'hello')])))  # unrecognized\n        self.serv.send(req)\n        res = self.serv.recv()\n        self.assertMsgEqual(Lwm2mCreated.matching(req)(), res)\n\n        req = Lwm2mRead(res.get_location_path())\n        self.serv.send(req)\n        res = self.serv.recv()\n        self.assertMsgEqual(Lwm2mContent.matching(req)(\n            format=coap.ContentFormat.APPLICATION_LWM2M_TLV,\n            content=b''.join(map(TLV.serialize,\n                                 [TLV.make_resource(RID.GeoPoints.Latitude, 42.0),\n                                  TLV.make_resource(RID.GeoPoints.Longitude, 69.0),\n                                  TLV.make_resource(RID.GeoPoints.Radius, 5.0),\n                                  TLV.make_resource(RID.GeoPoints.Description, b''),\n                                  TLV.make_resource(RID.GeoPoints.Inside, 0)]))), res)\n\n\nclass RootPathOnCreate(test_suite.Lwm2mSingleServerTest,\n                       test_suite.Lwm2mDmOperations):\n    def runTest(self):\n        self.create(self.serv, path='/', expect_error_code=coap.Code.RES_BAD_REQUEST)\n"
  },
  {
    "path": "tests/integration/suites/default/critical_opts.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nfrom framework.lwm2m_test import *\n\nimport unittest\n\nclass CriticalOptsTest(test_suite.Lwm2mSingleServerTest):\n    def runTest(self):\n        # This should result in 4.02 Bad Option response.\n        pkt = Lwm2mRead(ResPath.Server[1].ShortServerID, options=[coap.Option.IF_NONE_MATCH])\n        self.serv.send(pkt)\n        self.assertMsgEqual(Lwm2mErrorResponse.matching(pkt)(code=coap.Code.RES_BAD_OPTION),\n                            self.serv.recv())\n\n        # And this shuld work.\n        pkt = Lwm2mRead(ResPath.Server[1].ShortServerID)\n        self.serv.send(pkt)\n        self.assertMsgEqual(Lwm2mContent.matching(pkt)(),\n                            self.serv.recv())\n"
  },
  {
    "path": "tests/integration/suites/default/disable_server.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nfrom framework.lwm2m_test import *\nfrom .access_control import AccessMask\n\n\nclass DisableServerTest(test_suite.Lwm2mSingleServerTest):\n    def assertSocketsPolled(self, num):\n        self.assertEqual(num, self.get_socket_count())\n\n    def runTest(self):\n        # Write Disable Timeout\n        req = Lwm2mWrite(ResPath.Server[1].DisableTimeout, '6')\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.serv.recv())\n\n        # Execute Disable\n        req = Lwm2mExecute(ResPath.Server[1].Disable)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.serv.recv())\n\n        self.assertDemoDeregisters(timeout_s=5)\n\n        # no message for now\n        with self.assertRaises(socket.timeout):\n            print(self.serv.recv(timeout_s=5))\n\n        self.assertSocketsPolled(0)\n        self.assertFalse(self.ongoing_registration_exists())\n\n        # we should get another Register\n        self.assertDemoRegisters(timeout_s=3)\n\n        # no message for now\n        with self.assertRaises(socket.timeout):\n            print(self.serv.recv(timeout_s=2))\n\n        self.assertSocketsPolled(1)\n\n\nclass DisabledServerUpdateTriggerTest(test_suite.Lwm2mTest, test_suite.Lwm2mDmOperations):\n    def setUp(self, **kwargs):\n        super().setUp(servers=2, extra_cmdline_args=['--access-entry',\n                                                     '/%d/1,2,%d' % (OID.Server, AccessMask.OWNER)])\n\n    def runTest(self):\n        # Disable the first server\n        self.execute_resource(\n            self.servers[0], OID.Server, 1, RID.Server.Disable)\n        self.assertDemoDeregisters(self.servers[0])\n\n        # Update trigger for the disabled server should return BAD REQUEST\n        self.execute_resource(self.servers[1], OID.Server, 1, RID.Server.RegistrationUpdateTrigger,\n                              expect_error_code=coap.Code.RES_BAD_REQUEST)\n\n    def tearDown(self):\n        self.teardown_demo_with_servers(deregister_servers=[self.servers[1]])\n\n\nclass DisableServerRestartTest(test_suite.Lwm2mTest, test_suite.Lwm2mDmOperations):\n    def setUp(self, **kwargs):\n        super().setUp(servers=2, extra_cmdline_args=['--access-entry',\n                                                     '/%d/1,2,%d' % (OID.Server, AccessMask.OWNER)])\n\n    def runTest(self):\n        self.write_resource(\n            self.servers[1], OID.Server, 1, RID.Server.DisableTimeout, b'6')\n        self.execute_resource(\n            self.servers[1], OID.Server, 1, RID.Server.Disable)\n        first_disable_timestamp = time.time()\n        self.assertDemoDeregisters(self.servers[0])\n\n        # no message for now\n        with self.assertRaises(socket.timeout):\n            print(self.servers[0].recv(timeout_s=3))\n\n        # execute Disable again, this should reset the timer\n        self.execute_resource(\n            self.servers[1], OID.Server, 1, RID.Server.Disable)\n        with self.assertRaises(socket.timeout):\n            print(self.servers[0].recv(timeout_s=5))\n\n        self.assertFalse(self.ongoing_registration_exists())\n\n        # only now the server should re-register\n        self.assertDemoRegisters(server=self.servers[0], timeout_s=3)\n        register_timestamp = time.time()\n\n        self.assertGreater(register_timestamp - first_disable_timestamp, 8)\n\n\nclass DisableWithExecuteDuringUpdateTest(test_suite.Lwm2mSingleServerTest):\n    LIFETIME = 10\n    DISABLE_TIMEOUT = 5\n    EPSILON = 2\n\n    def setUp(self):\n        super().setUp(lifetime=self.LIFETIME)\n\n    def runTest(self):\n        # Write Disable Timeout\n        req = Lwm2mWrite(\n            ResPath.Server[1].DisableTimeout, str(self.DISABLE_TIMEOUT))\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.serv.recv())\n\n        # Await for the client to initiate the registraton update process...\n        pkt = self.assertDemoUpdatesRegistration(timeout_s=10, respond=False)\n\n        # ...but instead of responding to Update, send Disable first...\n        req = Lwm2mExecute(ResPath.Server[1].Disable)\n        self.serv.send(req)\n        time.sleep(0.25)\n\n        # ...and only then respond to \"queued\" Update message...\n        self.serv.send(Lwm2mChanged.matching(pkt)())\n\n        # ...and expect a response to Disable\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.serv.recv())\n\n        # demo deregisters properly...\n        self.assertDemoDeregisters(timeout_s=1)\n\n        # ...and registers back\n        self.assertDemoRegisters(\n            timeout_s=self.DISABLE_TIMEOUT + self.EPSILON, lifetime=self.LIFETIME)\n\n\nclass DisableWithAPIDuringUpdateTest(test_suite.Lwm2mSingleServerTest):\n    LIFETIME = 10\n    DISABLE_TIMEOUT = 5\n    EPSILON = 2\n\n    def setUp(self):\n        super().setUp(lifetime=self.LIFETIME)\n\n    def runTest(self):\n        # Write Disable Timeout\n        req = Lwm2mWrite(\n            ResPath.Server[1].DisableTimeout, str(self.DISABLE_TIMEOUT))\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.serv.recv())\n\n        # Await for the client to initiate the registraton update process...\n        pkt = self.assertDemoUpdatesRegistration(timeout_s=10, respond=False)\n\n        # ... and call disable before receiving the response\n        self.communicate(\"disable-server 1 %s\" % self.DISABLE_TIMEOUT)\n        time.sleep(0.25)\n\n        # ...and only then respond to \"queued\" Update message...\n        self.serv.send(Lwm2mChanged.matching(pkt)())\n\n        # demo deregisters properly...\n        self.assertDemoDeregisters(timeout_s=1)\n\n        # ...and registers back\n        self.assertDemoRegisters(\n            timeout_s=self.DISABLE_TIMEOUT + self.EPSILON, lifetime=self.LIFETIME)\n\n        # Same scanerio again, but with \"-1\" disable timeout meaning \"disable indefinitely\"\n        pkt = self.assertDemoUpdatesRegistration(timeout_s=10, respond=False)\n        self.communicate(\"disable-server 1 -1\")\n        time.sleep(0.25)\n        self.serv.send(Lwm2mChanged.matching(pkt)())\n\n        # demo deregisters properly...\n        self.assertDemoDeregisters(timeout_s=1)\n\n        # and stays in disabled state\n        with self.assertRaises(socket.timeout):\n            print(self.serv.recv(timeout_s=self.DISABLE_TIMEOUT + self.EPSILON))\n\n    def tearDown(self):\n        self.teardown_demo_with_servers(auto_deregister=False)\n\n\n"
  },
  {
    "path": "tests/integration/suites/default/discover_depth.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nfrom framework.lwm2m_test import *\n\nfrom . import test_object\n\n\nclass DiscoverDepth:\n    class Test(test_object.TestObject.TestCase, test_suite.Lwm2mDmOperations):\n        def setUp(self):\n            class EmptyClass:\n                pass\n\n            super().setUp(minimum_version='1.2', maximum_version='1.2')\n            self.execute_resource(self.serv, oid=OID.Test, iid=0, rid=RID.Test.ResInitIntArray,\n                                  content=b\"2='137'\")\n\n            self.write_attributes(self.serv, oid=OID.Test, query=['pmin=1'])\n            self.write_attributes(self.serv, oid=OID.Test, iid=0, query=['pmax=2'])\n\n            for resource in (RID.Test.__dict__.keys() - EmptyClass.__dict__.keys()):\n                rid = getattr(RID.Test, resource)\n                self.write_attributes(self.serv, oid=OID.Test, iid=0, rid=rid,\n                                      query=['gt=%d' % (rid,)])\n\n\nclass DiscoverObjectDepthTest(DiscoverDepth.Test):\n    def runTest(self):\n        result = self.discover(self.serv, oid=OID.Test, depth=0)\n        self.assertEqual(result.content, b'</33605>;pmin=1')\n\n        result = self.discover(self.serv, oid=OID.Test, depth=1)\n        self.assertEqual(result.content, b'</33605>;pmin=1,</33605/0>;pmax=2')\n\n        result = self.discover(self.serv, oid=OID.Test, depth=2)\n        self.assertEqual(result.content,\n                         b'</33605>;pmin=1,</33605/0>;pmax=2,</33605/0/0>;gt=0,</33605/0/1>;gt=1,'\n                         + b'</33605/0/2>;gt=2,</33605/0/3>;dim=1;gt=3,</33605/0/4>;dim=0;gt=4,'\n                         + b'</33605/0/5>;gt=5,</33605/0/6>;gt=6,</33605/0/7>;gt=7,'\n                         + b'</33605/0/9>;gt=9,</33605/0/10>;gt=10,</33605/0/11>;dim=1;gt=11,'\n                         + b'</33605/0/12>;gt=12,</33605/0/13>;gt=13,</33605/0/14>;gt=14,'\n                         + b'</33605/0/15>;gt=15,</33605/0/16>;gt=16,</33605/0/17>;gt=17,'\n                         + b'</33605/0/18>;gt=18,</33605/0/19>;gt=19,</33605/0/20>;gt=20,'\n                         + b'</33605/0/21>;gt=21,</33605/0/22>;dim=0;gt=22,</33605/0/23>;gt=23')\n\n\n        result = self.discover(self.serv, oid=OID.Test, depth=3)\n        self.assertEqual(result.content,\n                         b'</33605>;pmin=1,</33605/0>;pmax=2,</33605/0/0>;gt=0,</33605/0/1>;gt=1,'\n                         + b'</33605/0/2>;gt=2,</33605/0/3>;dim=1;gt=3,</33605/0/3/2>,'\n                         + b'</33605/0/4>;dim=0;gt=4,</33605/0/5>;gt=5,</33605/0/6>;gt=6,'\n                         + b'</33605/0/7>;gt=7,</33605/0/9>;gt=9,</33605/0/10>;gt=10,'\n                         + b'</33605/0/11>;dim=1;gt=11,</33605/0/11/2>,</33605/0/12>;gt=12,'\n                         + b'</33605/0/13>;gt=13,</33605/0/14>;gt=14,</33605/0/15>;gt=15,'\n                         + b'</33605/0/16>;gt=16,</33605/0/17>;gt=17,</33605/0/18>;gt=18,'\n                         + b'</33605/0/19>;gt=19,</33605/0/20>;gt=20,</33605/0/21>;gt=21,'\n                         + b'</33605/0/22>;dim=0;gt=22,</33605/0/23>;gt=23')\n\n\nclass DiscoverInstanceDepthTest(DiscoverDepth.Test):\n    def runTest(self):\n        result = self.discover(self.serv, oid=OID.Test, iid=0, depth=0)\n        self.assertEqual(result.content, b'</33605/0>;pmin=1;pmax=2')\n\n        result = self.discover(self.serv, oid=OID.Test, iid=0, depth=1)\n        self.assertEqual(result.content,\n                         b'</33605/0>;pmin=1;pmax=2,</33605/0/0>;gt=0,</33605/0/1>;gt=1,'\n                         + b'</33605/0/2>;gt=2,</33605/0/3>;dim=1;gt=3,</33605/0/4>;dim=0;gt=4,'\n                         + b'</33605/0/5>;gt=5,</33605/0/6>;gt=6,</33605/0/7>;gt=7,'\n                         + b'</33605/0/9>;gt=9,</33605/0/10>;gt=10,</33605/0/11>;dim=1;gt=11,'\n                         + b'</33605/0/12>;gt=12,</33605/0/13>;gt=13,</33605/0/14>;gt=14,'\n                         + b'</33605/0/15>;gt=15,</33605/0/16>;gt=16,</33605/0/17>;gt=17,'\n                         + b'</33605/0/18>;gt=18,</33605/0/19>;gt=19,</33605/0/20>;gt=20,'\n                         + b'</33605/0/21>;gt=21,</33605/0/22>;dim=0;gt=22,</33605/0/23>;gt=23')\n\n        result = self.discover(self.serv, oid=OID.Test, iid=0, depth=2)\n        self.assertEqual(result.content,\n                         b'</33605/0>;pmin=1;pmax=2,</33605/0/0>;gt=0,</33605/0/1>;gt=1,'\n                         + b'</33605/0/2>;gt=2,</33605/0/3>;dim=1;gt=3,</33605/0/3/2>,'\n                         + b'</33605/0/4>;dim=0;gt=4,</33605/0/5>;gt=5,</33605/0/6>;gt=6,'\n                         + b'</33605/0/7>;gt=7,</33605/0/9>;gt=9,</33605/0/10>;gt=10,'\n                         + b'</33605/0/11>;dim=1;gt=11,</33605/0/11/2>,</33605/0/12>;gt=12,'\n                         + b'</33605/0/13>;gt=13,</33605/0/14>;gt=14,</33605/0/15>;gt=15,'\n                         + b'</33605/0/16>;gt=16,</33605/0/17>;gt=17,</33605/0/18>;gt=18,'\n                         + b'</33605/0/19>;gt=19,</33605/0/20>;gt=20,</33605/0/21>;gt=21,'\n                         + b'</33605/0/22>;dim=0;gt=22,</33605/0/23>;gt=23')\n        max_depth_content = result.content\n\n        result = self.discover(self.serv, oid=OID.Test, iid=0, depth=3)\n        self.assertEqual(result.content, max_depth_content)\n\n\nclass DiscoverResourceDepthTest(DiscoverDepth.Test):\n    def runTest(self):\n        result = self.discover(self.serv, oid=OID.Test, iid=0, rid=RID.Test.IntArray, depth=0)\n        self.assertEqual(result.content, b'</33605/0/3>;dim=1;pmin=1;pmax=2;gt=3')\n\n        result = self.discover(self.serv, oid=OID.Test, iid=0, rid=RID.Test.IntArray, depth=1)\n        self.assertEqual(result.content, b'</33605/0/3>;dim=1;pmin=1;pmax=2;gt=3,</33605/0/3/2>')\n        max_depth_content = result.content\n\n        result = self.discover(self.serv, oid=OID.Test, iid=0, rid=RID.Test.IntArray, depth=2)\n        self.assertEqual(result.content, max_depth_content)\n\n        result = self.discover(self.serv, oid=OID.Test, iid=0, rid=RID.Test.IntArray, depth=3)\n        self.assertEqual(result.content, max_depth_content)\n"
  },
  {
    "path": "tests/integration/suites/default/downloader.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nimport concurrent.futures\nimport contextlib\nimport http.server\nimport os\nimport socket\nimport threading\nimport time\n\nfrom framework_tools.coap_file_server import CoapFileServerThread\nfrom framework.lwm2m_test import *\n\nDUMMY_PAYLOAD = os.urandom(16 * 1024)\n\n\nclass CoapDownload:\n    class Test(test_suite.Lwm2mSingleServerTest):\n        def setUp(self, coap_server: coap.Server = None):\n            super().setUp()\n\n            self.file_server_thread = CoapFileServerThread(coap_server or coap.Server())\n            self.file_server_thread.start()\n\n            self.tempfile = tempfile.NamedTemporaryFile()\n\n        @property\n        def file_server(self):\n            return self.file_server_thread.file_server\n\n        def register_resource(self, path, *args, **kwargs):\n            with self.file_server as file_server:\n                file_server.set_resource(path, *args, **kwargs)\n                return file_server.get_resource_uri(path)\n\n        def tearDown(self):\n            try:\n                super().tearDown()\n            finally:\n                self.tempfile.close()\n                self.file_server_thread.join()\n\n        def read(self, path: Lwm2mPath):\n            req = Lwm2mRead(path)\n            self.serv.send(req)\n            res = self.serv.recv()\n            self.assertMsgEqual(Lwm2mContent.matching(req)(), res)\n            return res.content\n\n        def wait_until_downloads_finished(self):\n            while self.get_socket_count() > len(self.servers):\n                time.sleep(0.1)\n\n    class ReconnectTest(test_suite.Lwm2mSingleServerTest):\n        def setUp(self, coap_server: coap.Server = None):\n            super().setUp()\n            self.communicate('trim-servers 0')\n            self.assertDemoDeregisters()\n            self.file_server = (coap_server or coap.Server())\n            self.file_server.set_timeout(15)\n            self.tempfile = tempfile.NamedTemporaryFile()\n\n        def tearDown(self):\n            self.file_server.close()\n            self.tempfile.close()\n            super().tearDown(auto_deregister=False)\n\n        @contextlib.contextmanager\n        def downloadContext(self, path, psk_identity='', psk_key='', payload=DUMMY_PAYLOAD):\n            # \"download\" command blocks until a connection is made, so we need to listen() in a separate thread\n            with concurrent.futures.ThreadPoolExecutor() as executor:\n                listen_future = executor.submit(self.file_server.listen)\n                self.communicate(\n                    'download %s://127.0.0.1:%s%s %s %s %s' % (\n                        'coaps' if isinstance(self.file_server, coap.DtlsServer) else 'coap',\n                        self.file_server.get_listen_port(), path, self.tempfile.name, psk_identity, psk_key))\n                listen_future.result()\n            self.wait_until_socket_count(1, timeout_s=1)\n\n            test_case = self\n\n            class DownloadContext:\n                def __init__(self):\n                    self.seq_num = 0\n                    self.block_size = 1024\n\n                @property\n                def read_offset(self):\n                    return self.seq_num * self.block_size\n\n                def transferData(self, bytes_limit=len(payload)):\n                    while self.read_offset < bytes_limit:\n                        req = test_case.file_server.recv()\n                        if self.read_offset != 0:\n                            test_case.assertMsgEqual(CoapGet(path, options=[\n                                coap.Option.BLOCK2(seq_num=self.seq_num, block_size=self.block_size, has_more=0)]), req)\n                        else:\n                            # NOTE: demo does not force any BLOCK2() option in first request at offset 0\n                            test_case.assertMsgEqual(CoapGet(path), req)\n\n                        block2opt = coap.Option.BLOCK2(\n                            seq_num=self.seq_num,\n                            has_more=(len(DUMMY_PAYLOAD) > self.read_offset + self.block_size),\n                            block_size=self.block_size)\n                        test_case.file_server.send(Lwm2mContent.matching(req)(\n                            content=DUMMY_PAYLOAD[self.read_offset:self.read_offset + self.block_size],\n                            options=[block2opt]))\n                        self.seq_num += 1\n\n            yield DownloadContext()\n\n            self.wait_until_socket_count(0, timeout_s=10)\n\n            with open(self.tempfile.name, 'rb') as f:\n                self.assertEqual(f.read(), DUMMY_PAYLOAD)\n\n\nclass CoapDownloadSockets(CoapDownload.Test):\n    def runTest(self):\n        self.communicate('download %s %s' % (self.register_resource('/', DUMMY_PAYLOAD), self.tempfile.name))\n\n        self.wait_until_socket_count(2, timeout_s=1)\n        self.assertEqual(1, self.get_non_lwm2m_socket_count())\n        self.assertEqual('UDP', self.get_transport(socket_index=-1))\n\n        # make sure the download is actually done\n        self.wait_until_downloads_finished()\n        with open(self.tempfile.name, 'rb') as f:\n            self.assertEqual(f.read(), DUMMY_PAYLOAD)\n\n\nclass CoapDownloadDoesNotBlockLwm2mTraffic(CoapDownload.Test):\n    def runTest(self):\n        self.communicate('download %s %s' % (self.register_resource('/', DUMMY_PAYLOAD), self.tempfile.name))\n\n        for _ in range(10):\n            self.read(ResPath.Device.SerialNumber)\n\n        # make sure the download is actually done\n        self.wait_until_downloads_finished()\n        with open(self.tempfile.name, 'rb') as f:\n            self.assertEqual(f.read(), DUMMY_PAYLOAD)\n\n\nclass CoapDownloadIgnoresUnrelatedRequests(CoapDownload.Test):\n    def runTest(self):\n        self.communicate('download %s %s' % (self.register_resource('/', DUMMY_PAYLOAD), self.tempfile.name))\n        self.wait_until_socket_count(2, timeout_s=1)\n\n        # wait for first request\n        while True:\n            with self.file_server as file_server:\n                if len(file_server.requests) != 0:\n                    break\n            time.sleep(0.001)\n\n        for _ in range(10):\n            with self.file_server as file_server:\n                file_server._server.send(Lwm2mRead(ResPath.Device.SerialNumber).fill_placeholders())\n\n        # make sure the download is actually done\n        self.wait_until_downloads_finished()\n        with open(self.tempfile.name, 'rb') as f:\n            self.assertEqual(f.read(), DUMMY_PAYLOAD)\n\n\nclass CoapDownloadOverDtls(CoapDownload.Test):\n    PSK_IDENTITY = b'Help'\n    PSK_KEY = b'ImTrappedInAUniverseFactory'\n\n    def setUp(self):\n        super().setUp(coap_server=coap.DtlsServer(psk_key=self.PSK_KEY, psk_identity=self.PSK_IDENTITY))\n\n    def runTest(self):\n        self.communicate('download %s %s %s %s' % (self.register_resource('/', DUMMY_PAYLOAD),\n                                                   self.tempfile.name,\n                                                   self.PSK_IDENTITY.decode('ascii'),\n                                                   self.PSK_KEY.decode('ascii')))\n\n        # make sure the download is actually done\n        self.wait_until_downloads_finished()\n        with open(self.tempfile.name, 'rb') as f:\n            self.assertEqual(f.read(), DUMMY_PAYLOAD)\n\n\nclass CoapDownloadRetransmissions(CoapDownload.Test):\n    def runTest(self):\n        def should_ignore_request(req):\n            try:\n                seq_num = req.get_options(coap.Option.BLOCK2)[0].seq_num()\n                required_retransmissions = seq_num % 4\n                with self.file_server as file_server:\n                    num_retransmissions = len([x for x in file_server.requests if x == req])\n                return num_retransmissions < required_retransmissions\n            except:\n                return False\n\n        with self.file_server as file_server:\n            file_server.set_resource('/', DUMMY_PAYLOAD)\n            file_server.should_ignore_request = should_ignore_request\n            uri = file_server.get_resource_uri('/')\n\n        self.communicate('download %s %s' % (uri, self.tempfile.name))\n\n        # make sure download succeeded\n        self.wait_until_downloads_finished()\n        with open(self.tempfile.name, 'rb') as f:\n            self.assertEqual(f.read(), DUMMY_PAYLOAD)\n\n\nclass CoapDownloadAbortsOnErrorResponse(CoapDownload.Test):\n    def runTest(self):\n        self.communicate('download %s %s' % (self.register_resource('/', DUMMY_PAYLOAD), self.tempfile.name))\n        self.wait_until_socket_count(2, timeout_s=1)\n        with self.file_server as file_server:\n            file_server.set_resource('/', None)\n\n        # make sure download was aborted\n        self.wait_until_downloads_finished()\n        with open(self.tempfile.name, 'rb') as f:\n            self.assertNotEqual(f.read(), DUMMY_PAYLOAD)\n\n\nclass CoapDownloadAbortsOnETagChange(CoapDownload.Test):\n    def runTest(self):\n        self.communicate('download %s %s' % (self.register_resource('/', DUMMY_PAYLOAD), self.tempfile.name))\n        self.wait_until_socket_count(2, timeout_s=1)\n\n        while True:\n            with self.file_server as file_server:\n                if len(file_server.requests) != 0:\n                    old_etag = file_server._resources['/'].etag\n                    new_etag = bytes([(old_etag[0] + 1) % 256]) + old_etag[1:]\n                    self.assertNotEqual(old_etag, new_etag)\n                    file_server.set_resource('/', DUMMY_PAYLOAD, etag=new_etag)\n                    break\n            time.sleep(0.001)\n\n        # make sure download was aborted\n        self.wait_until_downloads_finished()\n        with open(self.tempfile.name, 'rb') as f:\n            self.assertNotEqual(f.read(), DUMMY_PAYLOAD)\n\n\nclass CoapDownloadAbortsOnUnexpectedResponse(CoapDownload.Test):\n    def runTest(self):\n        with self.file_server as file_server:\n            file_server.set_resource('/', DUMMY_PAYLOAD)\n            file_server.should_ignore_request = lambda _: True\n            uri = file_server.get_resource_uri('/')\n\n        self.communicate('download %s %s' % (uri, self.tempfile.name))\n        self.wait_until_socket_count(2, timeout_s=1)\n\n        while True:\n            with self.file_server as file_server:\n                if len(file_server.requests) != 0:\n                    break\n            time.sleep(0.001)\n\n        with self.file_server as file_server:\n            file_server._server.send(Lwm2mCreated.matching(file_server.requests[-1])().fill_placeholders())\n\n        # make sure download was aborted\n        self.wait_until_downloads_finished()\n        with open(self.tempfile.name, 'rb') as f:\n            self.assertNotEqual(f.read(), DUMMY_PAYLOAD)\n\n\nclass CoapDownloadReconnect(CoapDownload.ReconnectTest):\n    def runTest(self):\n        with self.downloadContext('/test') as ctx:\n            ctx.transferData(len(DUMMY_PAYLOAD) / 2)\n\n            req = self.file_server.recv()\n            self.assertMsgEqual(CoapGet('/test', options=[\n                coap.Option.BLOCK2(seq_num=ctx.seq_num, block_size=ctx.block_size, has_more=0)]), req)\n\n            previous_port = self.file_server.get_remote_addr()[1]\n            self.file_server.reset()\n            self.communicate('reconnect')\n            self.file_server.listen()\n            self.assertNotEqual(self.file_server.get_remote_addr()[1], previous_port)\n\n            ctx.transferData()\n\n\nclass CoapDownloadOffline(CoapDownload.ReconnectTest):\n    def runTest(self):\n        with self.downloadContext('/test') as ctx:\n            ctx.transferData(len(DUMMY_PAYLOAD) / 2)\n\n            req = self.file_server.recv()\n            self.assertMsgEqual(CoapGet('/test', options=[\n                coap.Option.BLOCK2(seq_num=ctx.seq_num, block_size=ctx.block_size, has_more=0)]), req)\n\n            previous_port = self.file_server.get_remote_addr()[1]\n            self.file_server.reset()\n            self.communicate('enter-offline')\n            with self.assertRaises(socket.timeout):\n                self.file_server.listen(timeout_s=5)\n            self.communicate('exit-offline')\n            self.file_server.listen()\n            self.assertNotEqual(self.file_server.get_remote_addr()[1], previous_port)\n\n            ctx.transferData()\n\n\nclass CoapDownloadBlocks(CoapDownload.Test):\n    def runTest(self):\n        blocks = [slice(0, 1), slice(2, 4), slice(6, 10), slice(14, 22), slice(30, 46),\n                  slice(62, 94), slice(126, 190), slice(254, 382), slice(510, 766),\n                  slice(1022, 1534), slice(2046, 3070), slice(4094, 6142), slice(8190, 12286),\n                  slice(16382, None)]\n\n        self.communicate('download-blocks %s %s %s' % (\n            self.register_resource('/', DUMMY_PAYLOAD), self.tempfile.name,\n            ' '.join(('%s-%s' % (s.start, s.stop or '')) for s in blocks)))\n        self.wait_until_downloads_finished()\n\n        with open(self.tempfile.name, 'rb') as f:\n            self.assertEqual(f.read(), b''.join(DUMMY_PAYLOAD[s] for s in blocks))\n\n\nclass CoapDownloadBlocksAlt(CoapDownload.Test):\n    def runTest(self):\n        blocks = [slice(1, 2), slice(4, 6), slice(10, 14), slice(22, 30), slice(46, 62),\n                  slice(94, 126), slice(190, 254), slice(382, 510), slice(766, 1022),\n                  slice(1534, 2046), slice(3070, 4094), slice(6142, 8190), slice(12286, 16382)]\n\n        self.communicate('download-blocks %s %s %s' % (\n            self.register_resource('/', DUMMY_PAYLOAD), self.tempfile.name,\n            ' '.join(('%s-%s' % (s.start, s.stop)) for s in blocks)))\n        self.wait_until_downloads_finished()\n\n        with open(self.tempfile.name, 'rb') as f:\n            self.assertEqual(f.read(), b''.join(DUMMY_PAYLOAD[s] for s in blocks))\n\n\nclass CoapsDownloadReconnect(CoapDownload.ReconnectTest):\n    PSK_IDENTITY = '1d3nt17y'\n    PSK_KEY = 's3cr3tk3y'\n\n    def setUp(self):\n        super().setUp(coap.DtlsServer(psk_identity=self.PSK_IDENTITY, psk_key=self.PSK_KEY))\n\n    def runTest(self):\n        with self.downloadContext('/test', self.PSK_IDENTITY, self.PSK_KEY) as ctx:\n            ctx.transferData(len(DUMMY_PAYLOAD) / 2)\n\n            req = self.file_server.recv()\n            self.assertMsgEqual(CoapGet('/test', options=[\n                coap.Option.BLOCK2(seq_num=ctx.seq_num, block_size=ctx.block_size, has_more=0)]), req)\n\n            previous_port = self.file_server.get_remote_addr()[1]\n            self.file_server.reset()\n            self.communicate('reconnect')\n            self.file_server.listen()\n            self.assertNotEqual(self.file_server.get_remote_addr()[1], previous_port)\n\n            ctx.transferData()\n\n\nclass CoapsDownloadOffline(CoapDownload.ReconnectTest):\n    PSK_IDENTITY = '1d3nt17y'\n    PSK_KEY = 's3cr3tk3y'\n\n    def setUp(self):\n        super().setUp(coap.DtlsServer(psk_identity=self.PSK_IDENTITY, psk_key=self.PSK_KEY))\n\n    def runTest(self):\n        with self.downloadContext('/test', self.PSK_IDENTITY, self.PSK_KEY) as ctx:\n            ctx.transferData(len(DUMMY_PAYLOAD) / 2)\n\n            req = self.file_server.recv()\n            self.assertMsgEqual(CoapGet('/test', options=[\n                coap.Option.BLOCK2(seq_num=ctx.seq_num, block_size=ctx.block_size, has_more=0)]), req)\n\n            previous_port = self.file_server.get_remote_addr()[1]\n            self.file_server.reset()\n            self.communicate('enter-offline')\n            with self.assertRaises(socket.timeout):\n                self.file_server.listen(timeout_s=5)\n            self.communicate('exit-offline')\n            self.file_server.listen()\n            self.assertNotEqual(self.file_server.get_remote_addr()[1], previous_port)\n\n            ctx.transferData()\n\n\nclass CoapsDownloadUnaffectedByTcpOffline(CoapDownload.ReconnectTest):\n    PSK_IDENTITY = '1d3nt17y'\n    PSK_KEY = 's3cr3tk3y'\n\n    def setUp(self):\n        super().setUp(coap.DtlsServer(psk_identity=self.PSK_IDENTITY, psk_key=self.PSK_KEY))\n\n    def runTest(self):\n        with self.downloadContext('/test', self.PSK_IDENTITY, self.PSK_KEY) as ctx:\n            ctx.transferData(len(DUMMY_PAYLOAD) / 2)\n\n            req = self.file_server.recv()\n            self.assertMsgEqual(CoapGet('/test', options=[\n                coap.Option.BLOCK2(seq_num=ctx.seq_num, block_size=ctx.block_size, has_more=0)]), req)\n\n            self.communicate('enter-offline tcp')\n\n            ctx.transferData()\n\n\nclass HttpDownload:\n    class Test(test_suite.Lwm2mSingleServerTest):\n        def make_request_handler(self):\n            raise NotImplementedError\n\n        def _create_server(self):\n            return http.server.HTTPServer(('', 0), self.make_request_handler())\n\n        def cv_wait(self):\n            with self._response_cv:\n                self._response_cv.wait()\n\n        def cv_notify_all(self):\n            with self._response_cv:\n                self._response_cv.notify_all()\n\n        def setUp(self, *args, **kwargs):\n            self.http_server = self._create_server()\n            self._response_cv = threading.Condition()\n\n            super().setUp(*args, **kwargs)\n\n            self.server_thread = threading.Thread(target=lambda: self.http_server.serve_forever())\n            self.server_thread.start()\n\n        def tearDown(self, *args, **kwargs):\n            try:\n                super().tearDown(*args, **kwargs)\n            finally:\n                self.cv_notify_all()\n                self.http_server.shutdown()\n                self.server_thread.join()\n\n\nclass HttpSinglePacketDownloadDoesNotHangIfRemoteServerDoesntCloseConnection(HttpDownload.Test):\n    CONTENT = b'foo'\n\n    def make_request_handler(self):\n        test_case = self\n\n        class RequestHandler(http.server.BaseHTTPRequestHandler):\n            def do_GET(self):\n                self.send_response(http.HTTPStatus.OK)\n                self.send_header('Content-type', 'text/plain')\n                self.send_header('Content-length', str(len(test_case.CONTENT)))\n                self.end_headers()\n                self.wfile.write(test_case.CONTENT)\n                self.wfile.flush()\n\n                # Returning from this method makes the server close a TCP\n                # connection. Prevent this to check if the client behaves\n                # correctly even if server does not do this.\n                test_case.cv_wait()\n\n            def log_request(code='-', size='-'):\n                # don't display logs on successful request\n                pass\n\n        return RequestHandler\n\n    def runTest(self):\n        with tempfile.NamedTemporaryFile() as temp_file:\n            self.communicate('download http://127.0.0.1:%s %s' % (\n                self.http_server.server_address[1], temp_file.name))\n\n        LIMIT_S = 10\n        for _ in range(LIMIT_S * 10):\n            # when download finishes, its socket gets closed, leaving only LwM2M one\n            if self.get_socket_count() <= 1:\n                break\n            time.sleep(0.1)\n        else:\n            self.fail('download not completed on time')\n\n\nclass HttpDownloadDoesNotBlockOnNoPayloadAfterHeaders(HttpDownload.Test):\n    CONTENT = b'foo'\n\n    def make_request_handler(self):\n        test_case = self\n\n        class RequestHandler(http.server.BaseHTTPRequestHandler):\n            def do_GET(self):\n                self.send_response(http.HTTPStatus.OK)\n                self.send_header('Content-type', 'text/plain')\n                self.send_header('Content-length', str(len(test_case.CONTENT)))\n                self.end_headers()\n                self.wfile.flush()\n\n                # stop transferring data after receiving headers\n                test_case.cv_wait()\n\n                self.wfile.write(test_case.CONTENT)\n                self.wfile.flush()\n\n            def log_request(code='-', size='-'):\n                # don't display logs on successful request\n                pass\n\n        return RequestHandler\n\n    def runTest(self):\n        with tempfile.NamedTemporaryFile() as temp_file:\n            self.communicate('download http://127.0.0.1:%s %s' % (\n                self.http_server.server_address[1], temp_file.name))\n\n            # at this point, download should be started\n            # make sure that it does not block regular client operation\n            self.communicate('send-update', timeout=15)\n            self.assertDemoUpdatesRegistration()\n\n            self.cv_notify_all()\n"
  },
  {
    "path": "tests/integration/suites/default/factory_provisioning.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nfrom framework_tools.lwm2m.senml_cbor import *\nfrom framework.test_utils import *\nfrom framework.lwm2m_test import *\n\n\nclass ConnectToServerTest(test_suite.Lwm2mSingleServerTest, test_suite.Lwm2mDmOperations):\n    def setUp(self):\n        server = Lwm2mServer()\n        config = [\n            {SenmlLabel.NAME: '/0/1/0',  SenmlLabel.STRING: 'coap://127.0.0.1:%d' % (server.get_listen_port())},\n            {SenmlLabel.NAME: '/0/1/1',  SenmlLabel.BOOL: False},\n            {SenmlLabel.NAME: '/0/1/2',  SenmlLabel.VALUE: 3},\n            {SenmlLabel.NAME: '/0/1/10', SenmlLabel.VALUE: 1},\n            {SenmlLabel.NAME: '/1/1/0',  SenmlLabel.VALUE: 1},\n            {SenmlLabel.NAME: '/1/1/1',  SenmlLabel.VALUE: 86400},\n            {SenmlLabel.NAME: '/1/1/6',  SenmlLabel.BOOL: False},\n            {SenmlLabel.NAME: '/1/1/7',  SenmlLabel.STRING: 'U'},\n            {SenmlLabel.NAME: ResPath.Test[21].ResInt, SenmlLabel.VALUE: 64}\n        ]\n\n        with tempfile.NamedTemporaryFile() as f:\n            f.write(CBOR.serialize(config))\n            f.flush()\n            super().setUp(servers=[server], num_servers_passed=0,\n                          extra_cmdline_args=['--factory-provisioning-file', f.name])\n        self.assertDemoRegisters()\n\n    def runTest(self):\n        response = self.read_path(self.serv, ResPath.Test[21].ResInt)\n        self.assertEqual(response.get_content_format(), coap.ContentFormat.TEXT_PLAIN)\n        self.assertEqual(response.content, b'64')\n\nclass WriteMultiInstanceResourceTest(test_suite.Lwm2mSingleServerTest, test_suite.Lwm2mDmOperations):\n    def setUp(self):\n        server = Lwm2mServer()\n        config = [\n            {SenmlLabel.NAME: '/0/1/0',  SenmlLabel.STRING: 'coap://127.0.0.1:%d' % (server.get_listen_port())},\n            {SenmlLabel.NAME: '/0/1/1',  SenmlLabel.BOOL: False},\n            {SenmlLabel.NAME: '/0/1/2',  SenmlLabel.VALUE: 3},\n            {SenmlLabel.NAME: '/0/1/10', SenmlLabel.VALUE: 1},\n            {SenmlLabel.NAME: '/1/1/0',  SenmlLabel.VALUE: 1},\n            {SenmlLabel.NAME: '/1/1/1',  SenmlLabel.VALUE: 86400},\n            {SenmlLabel.NAME: '/1/1/6',  SenmlLabel.BOOL: False},\n            {SenmlLabel.NAME: ResPath.Test[1].IntArray + '/0', SenmlLabel.VALUE: 50},\n            {SenmlLabel.NAME: '/1/1/7',  SenmlLabel.STRING: 'U'},\n            {SenmlLabel.NAME: ResPath.Test[1].IntArray + '/1', SenmlLabel.VALUE: 99}\n        ]\n\n        with tempfile.NamedTemporaryFile() as f:\n            f.write(CBOR.serialize(config))\n            f.flush()\n            super().setUp(servers=[server], num_servers_passed=0,\n                          extra_cmdline_args=['--factory-provisioning-file', f.name])\n        self.assertDemoRegisters()\n\n    def runTest(self):\n        response = self.read_path(self.serv, ResPath.Test[1].IntArray + '/0')\n        self.assertEqual(response.get_content_format(), coap.ContentFormat.TEXT_PLAIN)\n        self.assertEqual(response.content, b'50')\n"
  },
  {
    "path": "tests/integration/suites/default/firmware_update.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nimport asyncio\nimport http\nimport http.server\nimport os\nimport re\nimport ssl\nimport threading\nimport unittest\nimport zlib\n\nfrom framework_tools.coap_file_server import CoapFileServerThread, CoapFileServer\nfrom framework.lwm2m_test import *\nfrom framework.create_package import PackageForcedError, make_firmware_package\n\nfrom .access_control import AccessMask\nfrom .block_write import Block, equal_chunk_splitter\nfrom .connection_id import CoapServerWithProxy, disconnect_socket\n\n\nimport pymbedtls\n\nclass UpdateState:\n    IDLE = 0\n    DOWNLOADING = 1\n    DOWNLOADED = 2\n    UPDATING = 3\n\n\nclass UpdateResult:\n    INITIAL = 0\n    SUCCESS = 1\n    NOT_ENOUGH_SPACE = 2\n    OUT_OF_MEMORY = 3\n    CONNECTION_LOST = 4\n    INTEGRITY_FAILURE = 5\n    UNSUPPORTED_PACKAGE_TYPE = 6\n    INVALID_URI = 7\n    FAILED = 8\n    UNSUPPORTED_PROTOCOL = 9\n    CANCELLED = 10\n    DEFERRED = 11\n\n\nFIRMWARE_PATH = '/firmware'\nFIRMWARE_SCRIPT_TEMPLATE = '#!/bin/sh\\n%secho updated > \"%s\"\\n'\n\nclass FirmwareUpdate:\n    class Test(test_suite.Lwm2mSingleServerTest):\n        FW_PKG_OPTS = {}\n\n        def set_auto_deregister(self, auto_deregister):\n            self.auto_deregister = auto_deregister\n\n        def set_check_marker(self, check_marker):\n            self.check_marker = check_marker\n\n        def set_reset_machine(self, reset_machine):\n            self.reset_machine = reset_machine\n\n        def set_expect_send_after_state_machine_reset(\n                self, expect_send_after_state_machine_reset):\n            self.expect_send_after_state_machine_reset = expect_send_after_state_machine_reset\n\n        def setUp(self, garbage=0, auto_remove=True, *args, **kwargs):\n            garbage_lines = ''\n            while garbage > 0:\n                garbage_line = '#' * (min(garbage, 80) - 1) + '\\n'\n                garbage_lines += garbage_line\n                garbage -= len(garbage_line)\n            self.ANJAY_MARKER_FILE = generate_temp_filename(\n                dir='/tmp', prefix='anjay-fw-updated-')\n            self.FIRMWARE_SCRIPT_CONTENT = \\\n                (FIRMWARE_SCRIPT_TEMPLATE %\n                 (garbage_lines, self.ANJAY_MARKER_FILE)).encode('ascii')\n            if auto_remove:\n                self.FIRMWARE_SCRIPT_CONTENT += 'rm \"$0\"\\n'.encode('ascii')\n            super().setUp(fw_updated_marker_path=self.ANJAY_MARKER_FILE, *args, **kwargs)\n\n        def tearDown(self):\n            auto_deregister = getattr(self, 'auto_deregister', True)\n            check_marker = getattr(self, 'check_marker', False)\n            reset_machine = getattr(self, 'reset_machine', True)\n            expect_send_after_state_machine_reset = getattr(self,\n                                                            'expect_send_after_state_machine_reset',\n                                                            False)\n            try:\n                if not check_marker:\n                    return\n                for _ in range(10):\n                    time.sleep(0.5)\n\n                    if os.path.isfile(self.ANJAY_MARKER_FILE):\n                        break\n                else:\n                    self.fail('firmware marker not created')\n                with open(self.ANJAY_MARKER_FILE, \"rb\") as f:\n                    line = f.readline()[:-1]\n                    self.assertEqual(line, b\"updated\")\n                os.unlink(self.ANJAY_MARKER_FILE)\n            finally:\n                if reset_machine:\n                    # reset the state machine\n                    # Write /5/0/1 (Firmware URI)\n                    self.write_firmware_uri_expect_success('')\n\n                    if expect_send_after_state_machine_reset:\n                        pkt = self.serv.recv()\n                        self.assertMsgEqual(Lwm2mSend(), pkt)\n                        CBOR.parse(pkt.content).verify_values(test=self,\n                                                              expected_value_map={\n                                                                  ResPath.FirmwareUpdate.State: UpdateState.IDLE,\n                                                                  ResPath.FirmwareUpdate.UpdateResult: UpdateResult.INITIAL\n                                                              })\n                        self.serv.send(Lwm2mChanged.matching(pkt)())\n                super().tearDown(auto_deregister=auto_deregister)\n\n        def read_update_result(self):\n            req = Lwm2mRead(ResPath.FirmwareUpdate.UpdateResult)\n            self.serv.send(req)\n            res = self.serv.recv()\n            self.assertMsgEqual(Lwm2mContent.matching(req)(), res)\n            return int(res.content)\n\n        def read_state(self):\n            req = Lwm2mRead(ResPath.FirmwareUpdate.State)\n            self.serv.send(req)\n            res = self.serv.recv()\n            self.assertMsgEqual(Lwm2mContent.matching(req)(), res)\n            return int(res.content)\n\n        def wait_for_download(self, download_timeout_s=20):\n            # wait until client downloads the firmware\n            deadline = time.time() + download_timeout_s\n            while time.time() < deadline:\n                time.sleep(0.5)\n\n                if self.read_state() == UpdateState.DOWNLOADED:\n                    return\n\n            self.fail('firmware still not downloaded')\n\n        def write_firmware_and_wait_for_download(self, firmware_uri: str,\n                                                 download_timeout_s=20):\n            # Write /5/0/1 (Firmware URI)\n            self.write_firmware_uri_expect_success(firmware_uri)\n\n            self.wait_for_download(download_timeout_s)\n\n        def write_firmware_uri_expect_success(self, firmware_uri: str):\n            req = Lwm2mWrite(ResPath.FirmwareUpdate.PackageURI, firmware_uri)\n            self.serv.send(req)\n            self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                                self.serv.recv())\n\n        def perform_firmware_update_expect_success(self):\n            req = Lwm2mExecute(ResPath.FirmwareUpdate.Update)\n            self.serv.send(req)\n            self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                                self.serv.recv())\n\n        def wait_until_state_is(self, state, timeout_s=10):\n            deadline = time.time() + timeout_s\n            while time.time() < deadline:\n                time.sleep(0.1)\n                if self.read_state() == state:\n                    return\n\n            self.fail(f'state still is not {state}')\n\n    class TestWithHttpServerMixin:\n        RESPONSE_DELAY = 0\n        CHUNK_SIZE = sys.maxsize\n        ETAGS = False\n        PATH = FIRMWARE_PATH\n\n        def get_firmware_uri(self):\n            return 'http://127.0.0.1:%d%s' % (\n                self.http_server.server_address[1], self.PATH)\n\n        def provide_response(self, use_real_app=False, other_content: bytes = None):\n            with self._response_cv:\n                self.assertIsNone(self._response_content)\n                if use_real_app:\n                    with open(os.path.join(self.config.demo_path, self.config.demo_cmd), 'rb') as f:\n                        firmware = f.read()\n                        self._response_content = make_firmware_package(\n                            firmware, **self.FW_PKG_OPTS)\n                elif other_content:\n                    self._response_content = make_firmware_package(\n                        other_content, **self.FW_PKG_OPTS)\n                else:\n                    self._response_content = make_firmware_package(\n                        self.FIRMWARE_SCRIPT_CONTENT, **self.FW_PKG_OPTS)\n                self._response_cv.notify()\n\n        def _create_server(self):\n            test_case = self\n\n            class FirmwareRequestHandler(http.server.BaseHTTPRequestHandler):\n                def do_GET(self):\n                    self.send_response(http.HTTPStatus.OK)\n                    self.send_header('Content-type', 'text/plain')\n                    if test_case.ETAGS:\n                        self.send_header('ETag', '\"some_etag\"')\n                    self.end_headers()\n\n                    # This condition variable makes it possible to defer sending the response.\n                    # FirmwareUpdateStateChangeTest uses it to ensure demo has enough time\n                    # to send the interim \"Downloading\" state notification.\n                    with test_case._response_cv:\n                        while test_case._response_content is None:\n                            test_case._response_cv.wait()\n                        response_content = test_case._response_content\n                        test_case.requests.append(self.path)\n                        test_case._response_content = None\n\n                    def chunks(data):\n                        for i in range(0, len(response_content),\n                                       test_case.CHUNK_SIZE):\n                            yield response_content[i:i + test_case.CHUNK_SIZE]\n\n                    for chunk in chunks(response_content):\n                        time.sleep(test_case.RESPONSE_DELAY)\n                        self.wfile.write(chunk)\n                        self.wfile.flush()\n\n                def log_request(self, code='-', size='-'):\n                    # don't display logs on successful request\n                    pass\n\n            class SilentServer(http.server.HTTPServer):\n                def handle_error(self, *args, **kwargs):\n                    # don't log BrokenPipeErrors\n                    if not isinstance(sys.exc_info()[1], BrokenPipeError):\n                        super().handle_error(*args, **kwargs)\n\n            return SilentServer(('', 0), FirmwareRequestHandler)\n\n        def write_firmware_and_wait_for_download(self, *args, **kwargs):\n            requests = list(self.requests)\n            super().write_firmware_and_wait_for_download(*args, **kwargs)\n            self.assertEqual(requests + [self.PATH], self.requests)\n\n        def setUp(self, *args, **kwargs):\n            self.requests = []\n            self._response_content = None\n            self._response_cv = threading.Condition()\n\n            self.http_server = self._create_server()\n\n            super().setUp(*args, **kwargs)\n\n            self.server_thread = threading.Thread(\n                target=lambda: self.http_server.serve_forever())\n            self.server_thread.start()\n\n        def tearDown(self):\n            try:\n                super().tearDown()\n            finally:\n                self.http_server.shutdown()\n                self.server_thread.join()\n\n    class TestWithHttpServer(TestWithHttpServerMixin, Test):\n        pass\n\n    class TestWithTlsServerMixin:\n        @staticmethod\n        def _generate_key():\n            from cryptography.hazmat.backends import default_backend\n            from cryptography.hazmat.primitives.asymmetric import rsa\n            return rsa.generate_private_key(public_exponent=65537, key_size=2048,\n                                            backend=default_backend())\n\n        @staticmethod\n        def _generate_cert(private_key, public_key, issuer_cn, cn='127.0.0.1', alt_ip=None,\n                           ca=False):\n            import datetime\n            import ipaddress\n            from cryptography import x509\n            from cryptography.x509.oid import NameOID\n            from cryptography.hazmat.backends import default_backend\n            from cryptography.hazmat.primitives import hashes\n\n            name = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, cn)])\n            issuer_name = x509.Name(\n                [x509.NameAttribute(NameOID.COMMON_NAME, issuer_cn)])\n            now = datetime.datetime.utcnow()\n            cert_builder = (x509.CertificateBuilder().\n                            subject_name(name).\n                            issuer_name(issuer_name).\n                            public_key(public_key).\n                            serial_number(1000).\n                            not_valid_before(now).\n                            not_valid_after(now + datetime.timedelta(days=1)))\n            if alt_ip is not None:\n                cert_builder = cert_builder.add_extension(x509.SubjectAlternativeName(\n                    [x509.DNSName(cn), x509.IPAddress(ipaddress.IPv4Address(alt_ip))]),\n                    critical=False)\n            if ca:\n                cert_builder = cert_builder.add_extension(\n                    x509.BasicConstraints(ca=True, path_length=None), critical=False)\n            return cert_builder.sign(\n                private_key, hashes.SHA256(), default_backend())\n\n        @staticmethod\n        def _generate_cert_and_key(\n                cn='127.0.0.1', alt_ip='127.0.0.1', ca=False):\n            key = FirmwareUpdate.TestWithTlsServerMixin._generate_key()\n            cert = FirmwareUpdate.TestWithTlsServerMixin._generate_cert(key, key.public_key(), cn, cn,\n                                                                        alt_ip, ca)\n            return cert, key\n\n        @staticmethod\n        def _generate_pem_cert_and_key(\n                cn='127.0.0.1', alt_ip='127.0.0.1', ca=False):\n            from cryptography.hazmat.primitives import serialization\n\n            cert, key = FirmwareUpdate.TestWithTlsServerMixin._generate_cert_and_key(\n                cn, alt_ip, ca)\n            cert_pem = cert.public_bytes(encoding=serialization.Encoding.PEM)\n            key_pem = key.private_bytes(encoding=serialization.Encoding.PEM,\n                                        format=serialization.PrivateFormat.TraditionalOpenSSL,\n                                        encryption_algorithm=serialization.NoEncryption())\n            return cert_pem, key_pem\n\n        def setUp(self, pass_cert_to_demo=True, cmd_arg='', **kwargs):\n            cert_kwargs = {}\n            for key in ('cn', 'alt_ip'):\n                if key in kwargs:\n                    cert_kwargs[key] = kwargs[key]\n                    del kwargs[key]\n            cert_pem, key_pem = self._generate_pem_cert_and_key(**cert_kwargs)\n\n            with tempfile.NamedTemporaryFile(delete=False) as cert_file, \\\n                    tempfile.NamedTemporaryFile(delete=False) as key_file:\n                cert_file.write(cert_pem)\n                cert_file.flush()\n\n                key_file.write(key_pem)\n                key_file.flush()\n\n                self._cert_file = cert_file.name\n                self._key_file = key_file.name\n\n            extra_cmdline_args = []\n            if 'extra_cmdline_args' in kwargs:\n                extra_cmdline_args += kwargs['extra_cmdline_args']\n                del kwargs['extra_cmdline_args']\n            if pass_cert_to_demo:\n                extra_cmdline_args += [cmd_arg, self._cert_file]\n            super().setUp(extra_cmdline_args=extra_cmdline_args, **kwargs)\n\n        def tearDown(self):\n            def unlink_without_err(fname):\n                try:\n                    os.unlink(fname)\n                except BaseException:\n                    print('unlink(%r) failed' % (fname,))\n                    sys.excepthook(*sys.exc_info())\n\n            try:\n                super().tearDown()\n            finally:\n                unlink_without_err(self._cert_file)\n                unlink_without_err(self._key_file)\n\n    class TestWithTlsServer(TestWithTlsServerMixin, Test):\n        def setUp(self, pass_cert_to_demo=True, cmd_arg='', **kwargs):\n            super().setUp(pass_cert_to_demo, cmd_arg='--fw-cert-file', **kwargs)\n\n    class TestWithHttpsServerMixin:\n        def get_firmware_uri(self):\n            http_uri = super().get_firmware_uri()\n            assert http_uri[:5] == 'http:'\n            return 'https:' + http_uri[5:]\n\n        def _create_server(self):\n            http_server = super()._create_server()\n            context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)\n            context.load_cert_chain(certfile=self._cert_file, keyfile=self._key_file)\n            http_server.socket = context.wrap_socket(http_server.socket, server_side=True)\n            return http_server\n\n    class TestWithHttpsServer(TestWithTlsServer, TestWithHttpsServerMixin, TestWithHttpServer):\n        pass\n\n    class TestWithCoapServerMixin:\n        PATH = FIRMWARE_PATH\n\n        def setUp(self, coap_server=None, *args, **kwargs):\n            super().setUp(*args, **kwargs)\n\n            if type(coap_server) is not list:\n                coap_server = [coap_server]\n\n            self.server_thread = []\n            for i, server in enumerate(coap_server):\n                self.server_thread.append(CoapFileServerThread(coap_server=server))\n                self.server_thread[i].start()\n\n        @property\n        def file_server(self):\n            return self.server_thread[0].file_server\n\n        def get_file_server(self, serv):\n            return self.server_thread[serv].file_server\n\n        def tearDown(self):\n            try:\n                super().tearDown()\n            finally:\n                for s in self.server_thread:\n                    s.join()\n\n    class TestWithCoapServer(TestWithCoapServerMixin, Test):\n        pass\n\n    class TestWithCoapsServerMixin(TestWithCoapServerMixin):\n        FW_PSK_IDENTITY = b'fw-psk-identity'\n        FW_PSK_KEY = b'fw-psk-key'\n        CONNECTION_ID_VALUE = 'connection_id_val'\n\n        def setUp(self, coap_server_class=coap.DtlsServer, extra_cmdline_args=None, *args, **kwargs):\n            extra_cmdline_args = (extra_cmdline_args or []) + ['--fw-psk-identity',\n                                                               str(binascii.hexlify(\n                                                                   self.FW_PSK_IDENTITY), 'ascii'),\n                                                               '--fw-psk-key', str(binascii.hexlify(\n                    self.FW_PSK_KEY), 'ascii')]\n\n            super().setUp(*args, coap_server=coap_server_class(psk_identity=self.FW_PSK_IDENTITY,\n                                                               psk_key=self.FW_PSK_KEY,\n                                                               connection_id=self.CONNECTION_ID_VALUE\n                                                                   if '--use-connection-id' in extra_cmdline_args else ''\n                                                               ),\n                          extra_cmdline_args=extra_cmdline_args, **kwargs)\n\n    class TestWithCoapsServerProxyMixin(TestWithCoapsServerMixin):\n        def setUp(self, extra_cmdline_args=['--use-connection-id'], *args, **kwargs):\n           super().setUp(coap_server_class=CoapServerWithProxy,\n                         extra_cmdline_args=extra_cmdline_args, *args, **kwargs)\n\n    class TestWithCoapsServerProxy(TestWithCoapsServerProxyMixin, Test):\n        pass\n\n    class TestWithCoapsServer(TestWithCoapsServerMixin, Test):\n        pass\n\n    class DemoArgsExtractorMixin:\n        def _get_valgrind_args(self):\n            # these tests call demo_process.kill(), so Valgrind is not really\n            # useful\n            return []\n\n        def _start_demo(self, cmdline_args, timeout_s=30):\n            self.cmdline_args = cmdline_args\n            return super()._start_demo(cmdline_args, timeout_s)\n\n    class TestWithPartialDownload:\n        GARBAGE_SIZE = 8000\n        \n        def _wait_for_download(self, fw_part_multiplier=0.5):\n            # roughly twice the time expected as per SlowServer\n            deadline = time.time() + self.GARBAGE_SIZE / 500\n            fsize = 0\n            while time.time() < deadline:\n                time.sleep(0.5)\n                fsize = os.stat(self.fw_file_name).st_size\n                if fsize > self.GARBAGE_SIZE * fw_part_multiplier:\n                    break\n            if fsize <= self.GARBAGE_SIZE * fw_part_multiplier:\n                self.fail('firmware image not downloaded fast enough')\n            elif fsize > self.GARBAGE_SIZE:\n                self.fail('firmware image downloaded too quickly')\n\n\n        def wait_for_half_download(self):\n            self._wait_for_download(0.5)\n\n        def wait_for_three_quarters_download(self):\n            self._wait_for_download(0.75)\n\n        def setUp(self, *args, **kwargs):\n            super().setUp(garbage=self.GARBAGE_SIZE, *args, **kwargs)\n\n            import tempfile\n\n            with tempfile.NamedTemporaryFile(delete=False) as f:\n                self.fw_file_name = f.name\n            self.communicate('set-fw-package-path %s' %\n                                (os.path.abspath(self.fw_file_name)))\n\n    class TestWithPartialDownloadAndRestart(\n        TestWithPartialDownload, DemoArgsExtractorMixin):\n        def setUp(self, *args, **kwargs):\n            if 'auto_remove' not in kwargs:\n                kwargs = {**kwargs, 'auto_remove': False}\n            super().setUp(*args, **kwargs)\n\n        def tearDown(self, check_fw_file=True):\n            try:\n                if check_fw_file:\n                    with open(self.fw_file_name, \"rb\") as f:\n                        self.assertEqual(f.read(), self.FIRMWARE_SCRIPT_CONTENT)\n                super().tearDown()\n            finally:\n                try:\n                    os.unlink(self.fw_file_name)\n                except FileNotFoundError:\n                    pass\n\n\n    class TestWithPartialCoapDownloadAndRestart(TestWithPartialDownloadAndRestart,\n                                                TestWithCoapServer):\n        def setUp(self):\n            class SlowServer(coap.Server):\n                def send(self, *args, **kwargs):\n                    time.sleep(0.5)\n                    result = super().send(*args, **kwargs)\n                    self.reset()  # allow requests from other ports\n                    return result\n\n            super().setUp(coap_server=SlowServer())\n\n            with self.file_server as file_server:\n                file_server.set_resource(self.PATH,\n                                         make_firmware_package(self.FIRMWARE_SCRIPT_CONTENT))\n                self.fw_uri = file_server.get_resource_uri(self.PATH)\n\n    class TestWithPartialCoapsDownloadAndRestart(TestWithPartialDownloadAndRestart,\n                                                 TestWithCoapsServer):\n        def setUp(self, extra_cmdline_args=[]):\n            class SlowServer(coap.DtlsServer):\n                def send(self, *args, **kwargs):\n                    time.sleep(0.5)\n                    return super().send(*args, **kwargs)\n\n            super().setUp(coap_server_class=SlowServer, extra_cmdline_args=extra_cmdline_args)\n\n            with self.file_server as file_server:\n                file_server.set_resource(self.PATH,\n                                         make_firmware_package(self.FIRMWARE_SCRIPT_CONTENT))\n                self.fw_uri = file_server.get_resource_uri(self.PATH)\n\n    class CoapDownloaderRetryMixIn:\n        COAP_DOWNLOADER_RETRY_COUNT_DEF = 1\n        COAP_DOWNLOADER_RETRY_DELAY_DEF = 5\n        FW_MAX_RETRANSMIT_DEF = 1\n\n        def setUp(\n                self,\n                retry_count=COAP_DOWNLOADER_RETRY_COUNT_DEF,\n                retry_delay=COAP_DOWNLOADER_RETRY_DELAY_DEF,\n                fw_max_retransmit=FW_MAX_RETRANSMIT_DEF,\n                extra_cmdline_args=[]):\n            self.coap_downloader_retry_delay = retry_delay\n            extra_cmdline_args += ['--coap-downloader-retry-count',\n                                   str(retry_count),\n                                   '--coap-downloader-retry-delay',\n                                   str(retry_delay),\n                                   '--fwu-max-retransmit',\n                                   str(fw_max_retransmit)]\n            super().setUp(extra_cmdline_args=extra_cmdline_args)\n\n    class CoapsDownloaderRetry(\n            CoapDownloaderRetryMixIn,\n            TestWithPartialCoapsDownloadAndRestart):\n        pass\n\n    class CoapsDownloaderRetry(CoapDownloaderRetryMixIn, TestWithPartialCoapsDownloadAndRestart):\n        pass\n\n    class TestWithPartialHttpDownloadAndRestartMixin:\n        def get_etag(self, response_content):\n            return '\"%d\"' % zlib.crc32(response_content)\n\n        def check_success(self, handler, response_content, response_etag):\n            pass\n\n        def send_headers(self, handler, response_content, response_etag):\n            pass\n\n        def _create_server(self):\n            test_case = self\n\n            class FirmwareRequestHandler(http.server.BaseHTTPRequestHandler):\n                def send_response(self, *args, **kwargs):\n                    self._response_sent = True\n                    return super().send_response(*args, **kwargs)\n\n                def do_GET(self):\n                    self._response_sent = False\n                    test_case.requests.append(self.path)\n\n                    # This condition variable makes it possible to defer sending the response.\n                    # FirmwareUpdateStateChangeTest uses it to ensure demo has enough time\n                    # to send the interim \"Downloading\" state notification.\n                    with test_case._response_cv:\n                        while test_case._response_content is None:\n                            test_case._response_cv.wait()\n                        response_content = test_case._response_content\n                        response_etag = test_case.get_etag(response_content)\n\n                        test_case.check_success(\n                            self, response_content, response_etag)\n                        if self._response_sent:\n                            return\n\n                        test_case._response_content = None\n\n                    self.send_response(http.HTTPStatus.OK)\n                    self.send_header('Content-type', 'text/plain')\n                    if response_etag is not None:\n                        self.send_header('ETag', response_etag)\n                    offset = test_case.send_headers(\n                        self, response_content, response_etag)\n                    if offset is None:\n                        offset = 0\n\n                    self.end_headers()\n\n                    while offset < len(response_content):\n                        chunk = response_content[offset:offset + 1024]\n                        self.wfile.write(chunk)\n                        offset += len(chunk)\n                        time.sleep(0.5)\n\n                def log_message(self, *args, **kwargs):\n                    # don't display logs\n                    pass\n\n            class SilentServer(http.server.HTTPServer):\n                def handle_error(self, *args, **kwargs):\n                    # don't log BrokenPipeErrors\n                    if not isinstance(sys.exc_info()[1], BrokenPipeError):\n                        super().handle_error(*args, **kwargs)\n\n            return SilentServer(('', 0), FirmwareRequestHandler)\n\n    class TestWithPartialHttpDownloadAndRestart(TestWithPartialHttpDownloadAndRestartMixin,\n                                                TestWithPartialDownloadAndRestart,\n                                                TestWithHttpServer):\n        pass\n\n\nclass FirmwareUpdatePackageTest(FirmwareUpdate.Test):\n    def setUp(self):\n        super().setUp()\n        self.set_check_marker(True)\n        self.set_auto_deregister(False)\n        self.set_reset_machine(False)\n\n    def runTest(self):\n        # Write /5/0/0 (Firmware): script content\n        req = Lwm2mWrite(ResPath.FirmwareUpdate.Package,\n                         make_firmware_package(self.FIRMWARE_SCRIPT_CONTENT),\n                         format=coap.ContentFormat.APPLICATION_OCTET_STREAM)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n\n        # Execute /5/0/2 (Update)\n        self.perform_firmware_update_expect_success()\n\n\nclass FirmwareUpdateUriTest(FirmwareUpdate.TestWithHttpServer):\n    def setUp(self, *args, **kwargs):\n        super().setUp(*args, **kwargs)\n        self.set_check_marker(True)\n        self.set_auto_deregister(False)\n        self.set_reset_machine(False)\n\n    def runTest(self):\n        self.provide_response()\n        self.write_firmware_and_wait_for_download(self.get_firmware_uri())\n\n        # Execute /5/0/2 (Update)\n        self.perform_firmware_update_expect_success()\n\n\nclass FirmwareUpdateUriAutoSuspend(FirmwareUpdateUriTest):\n    def setUp(self):\n        super().setUp(extra_cmdline_args=['--fw-auto-suspend'])\n\n\nclass FirmwareUpdateUriManualSuspend(FirmwareUpdate.TestWithHttpServer):\n    def runTest(self):\n        self.provide_response()\n\n        requests = list(self.requests)\n        firmware_uri = self.get_firmware_uri()\n\n        self.communicate('fw-update-suspend')\n\n        # Write /5/0/1 (Firmware URI)\n        self.write_firmware_uri_expect_success(firmware_uri)\n\n        # wait until client enters the DOWNLOADING state\n        deadline = time.time() + 20\n        while time.time() < deadline:\n            time.sleep(0.5)\n            if self.read_state() == UpdateState.DOWNLOADING:\n                break\n        else:\n            self.fail('firmware still not in DOWNLOADING state')\n\n        time.sleep(5)\n        self.assertEqual(requests, self.requests)\n\n        # resume the download\n        self.communicate('fw-update-reconnect')\n\n        # wait until client downloads the firmware\n        self.wait_for_download()\n\n        self.assertEqual(requests + ['/firmware'], self.requests)\n\n\nclass FirmwareUpdateStateChangeTest(FirmwareUpdate.TestWithHttpServer):\n    def setUp(self):\n        super().setUp()\n        self.set_check_marker(True)\n        self.set_auto_deregister(False)\n        self.set_reset_machine(False)\n\n    def runTest(self):\n        # disable minimum notification period\n        write_attrs_req = Lwm2mWriteAttributes(\n            ResPath.FirmwareUpdate.State, query=['pmin=0'])\n        self.serv.send(write_attrs_req)\n        self.assertMsgEqual(Lwm2mChanged.matching(\n            write_attrs_req)(), self.serv.recv())\n\n        # initial state should be 0\n        observe_req = Lwm2mObserve(ResPath.FirmwareUpdate.State)\n        self.serv.send(observe_req)\n        self.assertMsgEqual(Lwm2mContent.matching(\n            observe_req)(content=b'0'), self.serv.recv())\n\n        # Write /5/0/1 (Firmware URI)\n        self.write_firmware_uri_expect_success(self.get_firmware_uri())\n\n        # notification should be sent before downloading\n        self.assertMsgEqual(Lwm2mNotify(observe_req.token, b'1'),\n                            self.serv.recv())\n\n        self.provide_response()\n\n        # ... and after it finishes\n        self.assertMsgEqual(Lwm2mNotify(observe_req.token, b'2'),\n                            self.serv.recv())\n\n        # Execute /5/0/2 (Update)\n        self.perform_firmware_update_expect_success()\n\n        # ... and when update starts\n        self.assertMsgEqual(Lwm2mNotify(observe_req.token, b'3'),\n                            self.serv.recv())\n\n        # there should be exactly one request\n        self.assertEqual(['/firmware'], self.requests)\n\n\nclass FirmwareUpdateSendStateChangeTest(FirmwareUpdate.TestWithHttpServer):\n    def setUp(self):\n        super().setUp(minimum_version='1.1', maximum_version='1.1',\n                      extra_cmdline_args=['--fw-update-use-send'])\n        self.set_expect_send_after_state_machine_reset(True)\n\n    def runTest(self):\n        self.assertEqual(self.read_state(), UpdateState.IDLE)\n\n        # Write /5/0/1 (Firmware URI)\n        self.write_firmware_uri_expect_success(self.get_firmware_uri())\n\n        pkt = self.serv.recv()\n        self.assertMsgEqual(Lwm2mSend(), pkt)\n        CBOR.parse(pkt.content).verify_values(test=self,\n                                              expected_value_map={\n                                                  ResPath.FirmwareUpdate.UpdateResult: UpdateResult.INITIAL,\n                                                  ResPath.FirmwareUpdate.State: UpdateState.DOWNLOADING\n                                              })\n        self.serv.send(Lwm2mChanged.matching(pkt)())\n\n        self.provide_response(use_real_app=True)\n\n        pkt = self.serv.recv()\n        self.assertMsgEqual(Lwm2mSend(), pkt)\n        CBOR.parse(pkt.content).verify_values(test=self,\n                                              expected_value_map={\n                                                  ResPath.FirmwareUpdate.UpdateResult: UpdateResult.INITIAL,\n                                                  ResPath.FirmwareUpdate.State: UpdateState.DOWNLOADED\n                                              })\n        self.serv.send(Lwm2mChanged.matching(pkt)())\n\n        # Execute /5/0/2 (Update)\n        self.perform_firmware_update_expect_success()\n\n        pkt = self.serv.recv()\n        self.assertMsgEqual(Lwm2mSend(), pkt)\n        CBOR.parse(pkt.content).verify_values(test=self,\n                                              expected_value_map={\n                                                  ResPath.FirmwareUpdate.UpdateResult: UpdateResult.INITIAL,\n                                                  ResPath.FirmwareUpdate.State: UpdateState.UPDATING\n                                              })\n        self.serv.send(Lwm2mChanged.matching(pkt)())\n\n        # there should be exactly one request\n        self.assertEqual(['/firmware'], self.requests)\n\n        self.serv.reset()\n        self.assertDemoRegisters(version='1.1')\n\n        pkt = self.serv.recv()\n        self.assertMsgEqual(Lwm2mSend(), pkt)\n        parsed_cbor = CBOR.parse(pkt.content)\n        parsed_cbor.verify_values(test=self,\n                                  expected_value_map={\n                                      ResPath.FirmwareUpdate.UpdateResult: UpdateResult.SUCCESS,\n                                      ResPath.FirmwareUpdate.State: UpdateState.IDLE\n                                  })\n        # Check if Send contains firmware and software version\n        self.assertEqual(parsed_cbor[2].get(SenmlLabel.NAME), '/3/0/3')\n        self.assertEqual(parsed_cbor[3].get(SenmlLabel.NAME), '/3/0/19')\n        self.serv.send(Lwm2mChanged.matching(pkt)())\n\n\nclass FirmwareUpdateBadBase64(FirmwareUpdate.Test):\n    def runTest(self):\n        # Write /5/0/0 (Firmware): some random text to see how it makes the world burn\n        # (as text context does not implement some_bytes handler).\n        data = bytes(b'\\x01' * 16)\n        req = Lwm2mWrite(ResPath.FirmwareUpdate.Package, data,\n                         format=coap.ContentFormat.TEXT_PLAIN)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mErrorResponse.matching(req)(coap.Code.RES_BAD_REQUEST),\n                            self.serv.recv())\n\n\nclass FirmwareUpdateGoodBase64(FirmwareUpdate.Test):\n    def runTest(self):\n        import base64\n        data = base64.encodebytes(bytes(b'\\x01' * 16)).replace(b'\\n', b'')\n        req = Lwm2mWrite(ResPath.FirmwareUpdate.Package, data,\n                         format=coap.ContentFormat.TEXT_PLAIN)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n\n\nclass FirmwareUpdateNullPkg(FirmwareUpdate.TestWithHttpServer):\n    def runTest(self):\n        self.provide_response()\n        self.write_firmware_and_wait_for_download(self.get_firmware_uri())\n\n        req = Lwm2mWrite(ResPath.FirmwareUpdate.Package, b'\\0',\n                         format=coap.ContentFormat.APPLICATION_OCTET_STREAM)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.serv.recv())\n\n        self.assertEqual(UpdateState.IDLE, self.read_state())\n        self.assertEqual(UpdateResult.INITIAL, self.read_update_result())\n\n\nclass FirmwareUpdateEmptyPkgUri(FirmwareUpdate.TestWithHttpServer):\n    def runTest(self):\n        self.provide_response()\n        self.write_firmware_and_wait_for_download(self.get_firmware_uri())\n\n        # Write /5/0/1 (Firmware URI)\n        self.write_firmware_uri_expect_success(b'')\n\n        self.assertEqual(UpdateState.IDLE, self.read_state())\n        self.assertEqual(UpdateResult.INITIAL, self.read_update_result())\n\n\nclass FirmwareUpdateInvalidUri(FirmwareUpdate.Test):\n    def runTest(self):\n        # observe Result\n        observe_req = Lwm2mObserve(ResPath.FirmwareUpdate.UpdateResult)\n        self.serv.send(observe_req)\n        self.assertMsgEqual(Lwm2mContent.matching(\n            observe_req)(content=b'0'), self.serv.recv())\n\n        # Write /5/0/1 (Firmware URI)\n        self.write_firmware_uri_expect_success(b'http://invalidfirmware.exe')\n\n        while True:\n            notify = self.serv.recv()\n            self.assertMsgEqual(Lwm2mNotify(observe_req.token), notify)\n            if int(notify.content) != UpdateResult.INITIAL:\n                break\n        self.assertEqual(UpdateResult.INVALID_URI, int(notify.content))\n        self.serv.send(Lwm2mReset(msg_id=notify.msg_id))\n        self.assertEqual(UpdateState.IDLE, self.read_state())\n\n\nclass FirmwareUpdateUnsupportedUri(FirmwareUpdate.Test):\n    def runTest(self):\n        req = Lwm2mWrite(ResPath.FirmwareUpdate.PackageURI,\n                         b'unsupported://uri.exe')\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mErrorResponse.matching(req)(coap.Code.RES_BAD_REQUEST),\n                            self.serv.recv())\n        # This does not even change state or anything, because according to the LwM2M spec\n        # Server can't feed us with unsupported URI type\n        self.assertEqual(UpdateState.IDLE, self.read_state())\n        self.assertEqual(UpdateResult.UNSUPPORTED_PROTOCOL,\n                         self.read_update_result())\n\n\nclass FirmwareUpdateOfflineUriTest(FirmwareUpdate.TestWithHttpServer):\n    def runTest(self):\n        self.communicate('enter-offline tcp')\n\n        self.assertEqual(UpdateState.IDLE, self.read_state())\n        self.assertEqual(UpdateResult.INITIAL, self.read_update_result())\n\n        # Write /5/0/1 (Firmware URI)\n        self.write_firmware_uri_expect_success(self.get_firmware_uri())\n\n        self.assertEqual(UpdateState.IDLE, self.read_state())\n        self.assertEqual(UpdateResult.CONNECTION_LOST,\n                         self.read_update_result())\n\n\nclass FirmwareUpdateReplacingPkgUri(FirmwareUpdate.TestWithHttpServer):\n    def runTest(self):\n        self.provide_response()\n        self.write_firmware_and_wait_for_download(self.get_firmware_uri())\n\n        # This isn't specified anywhere as a possible transition, therefore\n        # it is most likely a bad request.\n        req = Lwm2mWrite(ResPath.FirmwareUpdate.PackageURI, 'http://something')\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mErrorResponse.matching(req)(coap.Code.RES_BAD_REQUEST),\n                            self.serv.recv())\n\n        self.assertEqual(UpdateState.DOWNLOADED, self.read_state())\n        self.assertEqual(UpdateResult.INITIAL, self.read_update_result())\n\n\nclass FirmwareUpdateReplacingPkg(FirmwareUpdate.TestWithHttpServer):\n    def runTest(self):\n        self.provide_response()\n        self.write_firmware_and_wait_for_download(self.get_firmware_uri())\n\n        # This isn't specified anywhere as a possible transition, therefore\n        # it is most likely a bad request.\n        req = Lwm2mWrite(ResPath.FirmwareUpdate.Package, b'trololo',\n                         format=coap.ContentFormat.APPLICATION_OCTET_STREAM)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mErrorResponse.matching(req)(coap.Code.RES_BAD_REQUEST),\n                            self.serv.recv())\n\n        self.assertEqual(UpdateState.DOWNLOADED, self.read_state())\n        self.assertEqual(UpdateResult.INITIAL, self.read_update_result())\n\n\nclass FirmwareUpdateHttpsResumptionTest(FirmwareUpdate.TestWithPartialDownloadAndRestart,\n                                        FirmwareUpdate.TestWithHttpsServer):\n    RESPONSE_DELAY = 0.5\n    CHUNK_SIZE = 1000\n    ETAGS = True\n\n    def setUp(self):\n        super().setUp()\n        self.set_check_marker(True)\n        self.set_auto_deregister(False)\n        self.set_reset_machine(False)\n\n    def runTest(self):\n        self.provide_response()\n\n        # Write /5/0/1 (Firmware URI)\n        self.write_firmware_uri_expect_success(self.get_firmware_uri())\n\n        self.wait_for_half_download()\n        self.demo_process.kill()\n\n        # restart demo app\n        self.serv.reset()\n\n        self.provide_response()\n        self._start_demo(self.cmdline_args)\n        self.assertDemoRegisters(self.serv)\n\n        self.wait_for_download()\n\n        # Execute /5/0/2 (Update)\n        self.perform_firmware_update_expect_success()\n\n\nclass FirmwareUpdateHttpsReconnectTest(FirmwareUpdate.TestWithPartialDownloadAndRestart,\n                                       FirmwareUpdate.TestWithHttpsServer):\n    RESPONSE_DELAY = 0.5\n    CHUNK_SIZE = 1000\n    ETAGS = True\n\n    def setUp(self):\n        super().setUp()\n        self.set_check_marker(True)\n        self.set_auto_deregister(False)\n        self.set_reset_machine(False)\n\n    def runTest(self):\n        self.provide_response()\n\n        # Write /5/0/1 (Firmware URI)\n        self.write_firmware_uri_expect_success(self.get_firmware_uri())\n\n        self.wait_for_half_download()\n\n        self.assertEqual(len(self.requests), 1)\n\n        self.communicate('fw-update-reconnect')\n        self.provide_response()\n\n        self.wait_for_download()\n\n        self.assertEqual(len(self.requests), 2)\n\n        # Execute /5/0/2 (Update)\n        self.perform_firmware_update_expect_success()\n\n\nclass FirmwareUpdateHttpsCancelPackageTest(FirmwareUpdate.TestWithPartialDownload,\n                                           FirmwareUpdate.TestWithHttpServer):\n    RESPONSE_DELAY = 0.5\n    CHUNK_SIZE = 1000\n    ETAGS = True\n\n    def runTest(self):\n        self.provide_response()\n\n        # Write /5/0/1 (Firmware URI)\n        self.write_firmware_uri_expect_success(self.get_firmware_uri())\n\n        self.wait_for_half_download()\n\n        self.assertEqual(self.get_socket_count(), 2)\n\n        req = Lwm2mWrite(ResPath.FirmwareUpdate.Package, b'\\0',\n                         format=coap.ContentFormat.APPLICATION_OCTET_STREAM)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.serv.recv())\n\n        self.wait_until_socket_count(expected=1, timeout_s=5)\n\n        self.assertEqual(UpdateState.IDLE, self.read_state())\n        self.assertEqual(UpdateResult.INITIAL, self.read_update_result())\n\n\nclass FirmwareUpdateHttpsCancelPackageUriTest(FirmwareUpdate.TestWithPartialDownload,\n                                              FirmwareUpdate.TestWithHttpServer):\n    RESPONSE_DELAY = 0.5\n    CHUNK_SIZE = 1000\n    ETAGS = True\n\n    def runTest(self):\n        self.provide_response()\n\n        # Write /5/0/1 (Firmware URI)\n        self.write_firmware_uri_expect_success(self.get_firmware_uri())\n\n        self.wait_for_half_download()\n\n        self.assertEqual(self.get_socket_count(), 2)\n\n        # Write /5/0/1 (Firmware URI)\n        self.write_firmware_uri_expect_success(b'')\n\n        self.wait_until_socket_count(expected=1, timeout_s=5)\n\n        self.assertEqual(UpdateState.IDLE, self.read_state())\n        self.assertEqual(UpdateResult.INITIAL, self.read_update_result())\n\n\nclass FirmwareUpdateCoapCancelPackageUriTest(FirmwareUpdate.TestWithPartialDownload,\n                                             FirmwareUpdate.TestWithCoapServer):\n    def runTest(self):\n        with self.file_server as file_server:\n            file_server.set_resource('/firmware',\n                                     make_firmware_package(self.FIRMWARE_SCRIPT_CONTENT))\n            fw_uri = file_server.get_resource_uri('/firmware')\n\n            # Write /5/0/1 (Firmware URI)\n            self.write_firmware_uri_expect_success(fw_uri)\n\n            # Handle one GET\n            file_server.handle_request()\n\n        self.assertEqual(self.get_socket_count(), 2)\n\n        # Cancel download\n        self.write_firmware_uri_expect_success(b'')\n\n        self.wait_until_socket_count(expected=1, timeout_s=5)\n\n        self.assertEqual(UpdateState.IDLE, self.read_state())\n        self.assertEqual(UpdateResult.INITIAL, self.read_update_result())\n\n\nclass FirmwareUpdateHttpsOfflineTest(FirmwareUpdate.TestWithPartialDownloadAndRestart,\n                                     FirmwareUpdate.TestWithHttpServer):\n    RESPONSE_DELAY = 0.5\n    CHUNK_SIZE = 1000\n    ETAGS = True\n\n    def setUp(self):\n        super().setUp()\n        self.set_check_marker(True)\n        self.set_auto_deregister(False)\n        self.set_reset_machine(False)\n\n    def runTest(self):\n        self.provide_response()\n\n        # Write /5/0/1 (Firmware URI)\n        self.write_firmware_uri_expect_success(self.get_firmware_uri())\n\n        self.wait_for_half_download()\n\n        self.assertEqual(self.get_socket_count(), 2)\n        self.communicate('enter-offline tcp')\n        self.wait_until_socket_count(expected=1, timeout_s=5)\n        self.provide_response()\n        self.communicate('exit-offline tcp')\n\n        self.wait_for_download()\n\n        # Execute /5/0/2 (Update)\n        self.perform_firmware_update_expect_success()\n\n\nclass FirmwareUpdateHttpsTest(FirmwareUpdate.TestWithHttpsServer):\n    def setUp(self):\n        super().setUp()\n        self.set_check_marker(True)\n        self.set_auto_deregister(False)\n        self.set_reset_machine(False)\n\n    def runTest(self):\n        self.provide_response()\n        self.write_firmware_and_wait_for_download(\n            self.get_firmware_uri(), download_timeout_s=20)\n\n        # Execute /5/0/2 (Update)\n        self.perform_firmware_update_expect_success()\n\nclass FirmwareUpdateUnconfiguredHttpsTest(FirmwareUpdate.TestWithHttpsServer):\n    def setUp(self):\n        super().setUp(pass_cert_to_demo=False)\n\n    def runTest(self):\n        # disable minimum notification period\n        write_attrs_req = Lwm2mWriteAttributes(ResPath.FirmwareUpdate.UpdateResult,\n                                               query=['pmin=0'])\n        self.serv.send(write_attrs_req)\n        self.assertMsgEqual(Lwm2mChanged.matching(\n            write_attrs_req)(), self.serv.recv())\n\n        # initial result should be 0\n        observe_req = Lwm2mObserve(ResPath.FirmwareUpdate.UpdateResult)\n        self.serv.send(observe_req)\n        self.assertMsgEqual(Lwm2mContent.matching(\n            observe_req)(content=b'0'), self.serv.recv())\n\n        # Write /5/0/1 (Firmware URI)\n        self.write_firmware_uri_expect_success(self.get_firmware_uri())\n\n        # even before reaching the server, we should get an error\n        notify_msg = self.serv.recv()\n        # no security information => \"Unsupported protocol\"\n        self.assertMsgEqual(Lwm2mNotify(observe_req.token,\n                                        str(UpdateResult.UNSUPPORTED_PROTOCOL).encode()),\n                            notify_msg)\n        self.serv.send(Lwm2mReset(msg_id=notify_msg.msg_id))\n        self.assertEqual(0, self.read_state())\n\n\nclass FirmwareUpdateUnconfiguredHttpsWithFallbackAttemptTest(\n    FirmwareUpdate.TestWithHttpsServer):\n    def setUp(self):\n        super().setUp(pass_cert_to_demo=False,\n                      psk_identity=b'test-identity', psk_key=b'test-key')\n\n    def runTest(self):\n        # disable minimum notification period\n        write_attrs_req = Lwm2mWriteAttributes(ResPath.FirmwareUpdate.UpdateResult,\n                                               query=['pmin=0'])\n        self.serv.send(write_attrs_req)\n        self.assertMsgEqual(Lwm2mChanged.matching(\n            write_attrs_req)(), self.serv.recv())\n\n        # initial result should be 0\n        observe_req = Lwm2mObserve(ResPath.FirmwareUpdate.UpdateResult)\n        self.serv.send(observe_req)\n        self.assertMsgEqual(Lwm2mContent.matching(\n            observe_req)(content=b'0'), self.serv.recv())\n\n        # Write /5/0/1 (Firmware URI)\n        self.write_firmware_uri_expect_success(self.get_firmware_uri())\n\n        # even before reaching the server, we should get an error\n        notify_msg = self.serv.recv()\n        # no security information => client will attempt PSK from data model\n        # and fail handshake => \"Connection lost\"\n        self.assertMsgEqual(Lwm2mNotify(observe_req.token,\n                                        str(UpdateResult.CONNECTION_LOST).encode()),\n                            notify_msg)\n        self.serv.send(Lwm2mReset(msg_id=notify_msg.msg_id))\n        self.assertEqual(0, self.read_state())\n\n\nclass FirmwareUpdateInvalidHttpsTest(FirmwareUpdate.TestWithHttpsServer):\n    def setUp(self):\n        super().setUp(cn='invalid_cn', alt_ip=None)\n\n    def runTest(self):\n        # disable minimum notification period\n        write_attrs_req = Lwm2mWriteAttributes(ResPath.FirmwareUpdate.UpdateResult,\n                                               query=['pmin=0'])\n        self.serv.send(write_attrs_req)\n        self.assertMsgEqual(Lwm2mChanged.matching(\n            write_attrs_req)(), self.serv.recv())\n\n        # initial result should be 0\n        observe_req = Lwm2mObserve(ResPath.FirmwareUpdate.UpdateResult)\n        self.serv.send(observe_req)\n        self.assertMsgEqual(Lwm2mContent.matching(\n            observe_req)(content=b'0'), self.serv.recv())\n\n        # Write /5/0/1 (Firmware URI)\n        self.write_firmware_uri_expect_success(self.get_firmware_uri())\n\n        # even before reaching the server, we should get an error\n        notify_msg = self.serv.recv()\n        # handshake failure => \"Connection lost\"\n        self.assertMsgEqual(Lwm2mNotify(observe_req.token,\n                                        str(UpdateResult.CONNECTION_LOST).encode()),\n                            notify_msg)\n        self.serv.send(Lwm2mReset(msg_id=notify_msg.msg_id))\n        self.assertEqual(0, self.read_state())\n\n\nclass FirmwareUpdateResetInIdleState(FirmwareUpdate.Test):\n    def runTest(self):\n        self.assertEqual(UpdateState.IDLE, self.read_state())\n\n        # Write /5/0/1 (Firmware URI)\n        self.write_firmware_uri_expect_success(b'')\n\n        self.assertEqual(UpdateState.IDLE, self.read_state())\n        self.assertEqual(UpdateResult.INITIAL, self.read_update_result())\n\n        req = Lwm2mWrite(ResPath.FirmwareUpdate.Package, b'\\0',\n                         format=coap.ContentFormat.APPLICATION_OCTET_STREAM)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.serv.recv())\n        self.assertEqual(UpdateState.IDLE, self.read_state())\n        self.assertEqual(UpdateResult.INITIAL, self.read_update_result())\n\n\nclass FirmwareUpdateCoapUri(FirmwareUpdate.TestWithCoapServer):\n    def tearDown(self):\n        super().tearDown()\n\n        # there should be exactly one request\n        with self.file_server as file_server:\n            self.assertEqual(1, len(file_server.requests))\n            self.assertMsgEqual(CoapGet('/firmware'),\n                                file_server.requests[0])\n\n    def runTest(self):\n        with self.file_server as file_server:\n            file_server.set_resource('/firmware',\n                                     make_firmware_package(self.FIRMWARE_SCRIPT_CONTENT))\n            fw_uri = file_server.get_resource_uri('/firmware')\n        self.write_firmware_and_wait_for_download(fw_uri)\n\n\nclass FirmwareUpdateCoapsUri(FirmwareUpdate.TestWithCoapsServer, FirmwareUpdateCoapUri):\n    pass\n\n\nclass FirmwareUpdateCoapsUriAutoSuspend(FirmwareUpdateCoapsUri):\n    def setUp(self):\n        super().setUp(extra_cmdline_args=['--fw-auto-suspend'])\n\n\nclass FirmwareUpdateCoapsUriManualSuspend(FirmwareUpdateCoapsUri):\n    def runTest(self):\n        with self.file_server as file_server:\n            file_server.set_resource('/firmware',\n                                     make_firmware_package(self.FIRMWARE_SCRIPT_CONTENT))\n            fw_uri = file_server.get_resource_uri('/firmware')\n\n        self.communicate('fw-update-suspend')\n\n        # Write /5/0/1 (Firmware URI)\n        self.write_firmware_uri_expect_success(fw_uri)\n\n        # wait until the state machine enters the DOWNLOADING state\n        deadline = time.time() + 20\n        while time.time() < deadline:\n            time.sleep(0.5)\n            if self.read_state() == UpdateState.DOWNLOADING:\n                break\n        else:\n            self.fail('firmware still not in DOWNLOADING state')\n\n        time.sleep(5)\n        with self.file_server as file_server:\n            self.assertEqual(0, len(file_server.requests))\n\n        # resume the download\n        self.communicate('fw-update-reconnect')\n\n        # wait until client downloads the firmware\n        self.wait_for_download()\n\nclass FirmwareUpdateCoapsReconnectTest(FirmwareUpdate.TestWithPartialCoapsDownloadAndRestart):\n    def setUp(self):\n        super().setUp()\n        self.set_check_marker(True)\n        self.set_auto_deregister(False)\n        self.set_reset_machine(False)\n\n    def runTest(self):\n        # Write /5/0/1 (Firmware URI)\n        self.write_firmware_uri_expect_success(self.fw_uri)\n\n        self.wait_for_half_download()\n\n        with self.file_server as file_server:\n            file_server._server.reset()\n            self.communicate('fw-update-reconnect')\n            self.assertDtlsReconnect(file_server._server, timeout_s=10,\n                                     expected_error=['0x7700', '0x7900'])\n\n        self.wait_for_download()\n\n        # Execute /5/0/2 (Update)\n        self.perform_firmware_update_expect_success()\n\n\nclass FirmwareUpdateCoapsSuspendDuringOfflineAndReconnectDuringOnlineTest(\n    FirmwareUpdate.TestWithPartialCoapsDownloadAndRestart):\n    def runTest(self):\n        # Write /5/0/1 (Firmware URI)\n        self.write_firmware_uri_expect_success(self.fw_uri)\n\n        self.wait_for_half_download()\n\n        with self.file_server as file_server:\n            # Flush any buffers\n            while True:\n                try:\n                    file_server._server.recv(timeout_s=1)\n                except socket.timeout:\n                    break\n\n            self.communicate('enter-offline')\n\n            with self.assertRaises(socket.timeout):\n                file_server._server.recv(timeout_s=5)\n\n            self.communicate('fw-update-suspend')\n\n            with self.assertRaises(socket.timeout):\n                file_server._server.recv(timeout_s=5)\n\n            self.communicate('exit-offline')\n\n            self.assertDemoRegisters()\n\n            with self.assertRaises(socket.timeout):\n                file_server._server.recv(timeout_s=5)\n\n            file_server._server.reset()\n            self.communicate('fw-update-reconnect')\n            self.assertPktIsDtlsClientHello(\n                file_server._server._raw_udp_socket.recv(65536, socket.MSG_PEEK))\n\n        self.wait_for_download()\n\n\nclass FirmwareUpdateCoapsSuspendDuringOfflineAndReconnectDuringOfflineTest(\n    FirmwareUpdate.TestWithPartialCoapsDownloadAndRestart):\n    def runTest(self):\n        # Write /5/0/1 (Firmware URI)\n        self.write_firmware_uri_expect_success(self.fw_uri)\n\n        self.wait_for_half_download()\n\n        with self.file_server as file_server:\n            # Flush any buffers\n            while True:\n                try:\n                    file_server._server.recv(timeout_s=1)\n                except socket.timeout:\n                    break\n\n            self.communicate('enter-offline')\n\n            with self.assertRaises(socket.timeout):\n                file_server._server.recv(timeout_s=5)\n\n            self.communicate('fw-update-suspend')\n\n            with self.assertRaises(socket.timeout):\n                file_server._server.recv(timeout_s=5)\n\n            self.communicate('fw-update-reconnect')\n\n            with self.assertRaises(socket.timeout):\n                file_server._server.recv(timeout_s=5)\n\n            file_server._server.reset()\n            self.communicate('exit-offline')\n            self.assertPktIsDtlsClientHello(\n                file_server._server._raw_udp_socket.recv(65536, socket.MSG_PEEK))\n\n        self.assertDemoRegisters()\n\n        self.wait_for_download()\n\n\nclass FirmwareUpdateCoapsOfflineDuringSuspendAndReconnectDuringOnlineTest(\n    FirmwareUpdate.TestWithPartialCoapsDownloadAndRestart):\n    def runTest(self):\n        # Write /5/0/1 (Firmware URI)\n        self.write_firmware_uri_expect_success(self.fw_uri)\n\n        self.wait_for_half_download()\n\n        with self.file_server as file_server:\n            # Flush any buffers\n            while True:\n                try:\n                    file_server._server.recv(timeout_s=1)\n                except socket.timeout:\n                    break\n\n            self.communicate('fw-update-suspend')\n\n            with self.assertRaises(socket.timeout):\n                file_server._server.recv(timeout_s=5)\n\n            self.communicate('enter-offline')\n\n            with self.assertRaises(socket.timeout):\n                file_server._server.recv(timeout_s=5)\n\n            self.communicate('exit-offline')\n\n            self.assertDemoRegisters()\n\n            with self.assertRaises(socket.timeout):\n                file_server._server.recv(timeout_s=5)\n\n            file_server._server.reset()\n            self.communicate('fw-update-reconnect')\n            self.assertPktIsDtlsClientHello(\n                file_server._server._raw_udp_socket.recv(65536, socket.MSG_PEEK))\n\n        self.wait_for_download()\n\n\nclass FirmwareUpdateCoapsOfflineDuringSuspendAndReconnectDuringOfflineTest(\n    FirmwareUpdate.TestWithPartialCoapsDownloadAndRestart):\n    def runTest(self):\n        # Write /5/0/1 (Firmware URI)\n        self.write_firmware_uri_expect_success(self.fw_uri)\n\n        self.wait_for_half_download()\n\n        with self.file_server as file_server:\n            # Flush any buffers\n            while True:\n                try:\n                    file_server._server.recv(timeout_s=1)\n                except socket.timeout:\n                    break\n\n            self.communicate('fw-update-suspend')\n\n            with self.assertRaises(socket.timeout):\n                file_server._server.recv(timeout_s=5)\n\n            self.communicate('enter-offline')\n\n            with self.assertRaises(socket.timeout):\n                file_server._server.recv(timeout_s=5)\n\n            self.communicate('fw-update-reconnect')\n\n            with self.assertRaises(socket.timeout):\n                file_server._server.recv(timeout_s=5)\n\n            file_server._server.reset()\n            self.communicate('exit-offline')\n            self.assertPktIsDtlsClientHello(\n                file_server._server._raw_udp_socket.recv(65536, socket.MSG_PEEK))\n\n        self.assertDemoRegisters()\n\n        self.wait_for_download()\n\n\nclass FirmwareUpdateCoapsSocketCloseResumptionTest(\n        FirmwareUpdate.CoapsDownloaderRetry):\n    def runTest(self):\n        # Write /5/0/1 (Firmware URI)\n        self.write_firmware_uri_expect_success(self.fw_uri)\n\n        self.wait_for_half_download()\n\n        with self.server_thread[0].file_server as file_server:\n            port = file_server._server.get_listen_port()\n            file_server._server.close()\n            if self.read_log_until_match(\n                regex=re.escape(\n                    b'retrying download 1, attempt number 1, with delay ' + str(\n                        self.coap_downloader_retry_delay).encode()),\n                    timeout_s=10) is None:\n                raise self.failureException('string not found')\n            download_failed_timestamp = time.time()\n            file_server._server.reset(port)\n\n            self.assertPktIsDtlsClientHello(\n                file_server._server._raw_udp_socket.recv(\n                    65536, socket.MSG_PEEK))\n            self.assertAlmostEqual(\n                download_failed_timestamp +\n                self.coap_downloader_retry_delay,\n                time.time(),\n                delta=1)\n\n        self.wait_for_download()\n\n\nclass FirmwareUpdateCoapsTimeoutResumptionTest(\n        FirmwareUpdate.CoapsDownloaderRetry):\n    def runTest(self):\n        # Write /5/0/1 (Firmware URI)\n        self.write_firmware_uri_expect_success(self.fw_uri)\n\n        self.wait_for_half_download()\n\n        # we take file server mutex, so it stops processing incoming packages\n        # and that's why there is a timeout on client side\n        with self.server_thread[0].file_server as file_server:\n            port = file_server._server.get_listen_port()\n\n            if self.read_log_until_match(\n                regex=re.escape(\n                    b'retrying download 1, attempt number 1, with delay ' + str(\n                        self.coap_downloader_retry_delay).encode()),\n                    timeout_s=10) is None:\n                raise self.failureException('string not found')\n            download_failed_timestamp = time.time()\n            file_server._server.reset(port)\n\n            self.assertPktIsDtlsClientHello(\n                file_server._server._raw_udp_socket.recv(\n                    65536, socket.MSG_PEEK))\n            self.assertAlmostEqual(\n                download_failed_timestamp +\n                self.coap_downloader_retry_delay,\n                time.time(),\n                delta=1)\n\n        self.wait_for_download()\n\n\n@unittest.skipIf(not pymbedtls.Context.supports_connection_id(),\n                 \"connection_id support is not enabled in pymbedtls\")\nclass FirmwareUpdateCoapsSocketCloseResumptionWithCIDTest(\n        FirmwareUpdate.CoapsDownloaderRetry):\n    def setUp(self):\n        super().setUp(extra_cmdline_args=['--use-connection-id'])\n\n    def runTest(self):\n        # Write /5/0/1 (Firmware URI)\n        self.write_firmware_uri_expect_success(self.fw_uri)\n\n        self.wait_for_half_download()\n\n        with self.server_thread[0].file_server as file_server:\n            with file_server._server.fake_close():\n                if self.read_log_until_match(\n                    regex=re.escape(\n                        b'retrying download 1, attempt number 1, with delay ' + str(\n                            self.coap_downloader_retry_delay).encode()),\n                        timeout_s=10) is None:\n                    raise self.failureException('string not found')\n                download_failed_timestamp = time.time()\n            # Unconnect the socket at the pymbedtls site,\n            # to allow accepting packets from unknown endpoints.\n            disconnect_socket(file_server._server.socket.py_socket)\n\n            file_server._server._raw_udp_socket.settimeout(\n                self.coap_downloader_retry_delay * 2)\n            # we don't expect handshake since we are using CID\n            with self.assertRaises(AssertionError):\n                self.assertPktIsDtlsClientHello(\n                    file_server._server._raw_udp_socket.recv(\n                        65536, socket.MSG_PEEK))\n            self.assertAlmostEqual(\n                download_failed_timestamp +\n                self.coap_downloader_retry_delay,\n                time.time(),\n                delta=1)\n\n        self.wait_for_download()\n\n\nclass FirmareUpdateCoapsRetryCountResetAndReachMaxRetryTest(\n        FirmwareUpdate.CoapsDownloaderRetry):\n    def setUp(self):\n        super().setUp(retry_count=2)\n\n    def runTest(self):\n        # Write /5/0/1 (Firmware URI)\n        self.write_firmware_uri_expect_success(self.fw_uri)\n\n        self.wait_for_half_download()\n\n        with self.server_thread[0].file_server as file_server:\n            port = file_server._server.get_listen_port()\n            file_server._server.close()\n            if self.read_log_until_match(\n                regex=re.escape(\n                    b'retrying download 1, attempt number 1, with delay ' + str(\n                        self.coap_downloader_retry_delay).encode()),\n                    timeout_s=10) is None:\n                raise self.failureException('string not found')\n            download_failed_timestamp = time.time()\n            file_server._server.reset(port)\n\n            self.assertPktIsDtlsClientHello(\n                file_server._server._raw_udp_socket.recv(\n                    65536, socket.MSG_PEEK))\n            self.assertAlmostEqual(\n                download_failed_timestamp +\n                self.coap_downloader_retry_delay,\n                time.time(),\n                delta=1)\n\n        self.wait_for_three_quarters_download()\n\n        with self.server_thread[0].file_server as file_server:\n            port = file_server._server.get_listen_port()\n            file_server._server.close()\n            if self.read_log_until_match(\n                regex=re.escape(\n                    b'retrying download 1, attempt number 1, with delay ' + str(\n                        self.coap_downloader_retry_delay).encode()),\n                    timeout_s=10) is None:\n                raise self.failureException('string not found')\n            download_failed_timestamp = time.time()\n\n            # first retry\n            if self.read_log_until_match(\n                    regex=re.escape(b'could not connect socket for download id = '),\n                    timeout_s=10) is None:\n                raise self.failureException('string not found')\n            self.assertAlmostEqual(\n                download_failed_timestamp +\n                self.coap_downloader_retry_delay,\n                time.time(),\n                delta=1)\n            retry_timestamp = time.time()\n\n            # second retry\n            if self.read_log_until_match(\n                    regex=re.escape(b'could not connect socket for download id = '),\n                    timeout_s=10) is None:\n                raise self.failureException('string not found')\n            self.assertAlmostEqual(\n                retry_timestamp +\n                self.coap_downloader_retry_delay,\n                time.time(),\n                delta=1)\n\n            file_server._server.reset(port)\n            file_server._server._raw_udp_socket.settimeout(\n                self.coap_downloader_retry_delay * 2)\n            # there should be no more retries\n            with self.assertRaises(socket.timeout):\n                file_server._server._raw_udp_socket.recv(\n                    65536, socket.MSG_PEEK)\n\n    def tearDown(self):\n        super().tearDown(check_fw_file=False)\n\nclass FirmareUpdateCoapsRetryCountResetAndFinishDownloadTest(\n        FirmwareUpdate.CoapsDownloaderRetry):\n    def setUp(self):\n        super().setUp(retry_count=2)\n\n    def runTest(self):\n        # Write /5/0/1 (Firmware URI)\n        self.write_firmware_uri_expect_success(self.fw_uri)\n\n        self.wait_for_half_download()\n\n        with self.server_thread[0].file_server as file_server:\n            port = file_server._server.get_listen_port()\n            file_server._server.close()\n\n            if self.read_log_until_match(\n                regex=re.escape(\n                    b'retrying download 1, attempt number 1, with delay ' + str(\n                        self.coap_downloader_retry_delay).encode()),\n                    timeout_s=10) is None:\n                raise self.failureException('string not found')\n            download_failed_timestamp = time.time()\n\n            # first retry - failed this will increment the counter\n            if self.read_log_until_match(\n                    regex=re.escape(b'could not connect socket for download id = '),\n                    timeout_s=10) is None:\n                raise self.failureException('string not found')\n            self.assertAlmostEqual(\n                download_failed_timestamp +\n                self.coap_downloader_retry_delay,\n                time.time(),\n                delta=1)\n\n            if self.read_log_until_match(\n                regex=re.escape(\n                    b'retrying download 1, attempt number 2, with delay ' + str(\n                        self.coap_downloader_retry_delay).encode()),\n                    timeout_s=10) is None:\n                raise self.failureException('string not found')\n            download_failed_timestamp = time.time()\n            file_server._server.reset(port)\n\n            self.assertPktIsDtlsClientHello(\n                file_server._server._raw_udp_socket.recv(\n                    65536, socket.MSG_PEEK))\n            self.assertAlmostEqual(\n                download_failed_timestamp +\n                self.coap_downloader_retry_delay,\n                time.time(),\n                delta=1)\n\n        self.wait_for_three_quarters_download()\n\n        with self.server_thread[0].file_server as file_server:\n            port = file_server._server.get_listen_port()\n            file_server._server.close()\n\n            # Counter was reseted after successful processing of next package\n            if self.read_log_until_match(\n                regex=re.escape(\n                    b'retrying download 1, attempt number 1, with delay ' + str(\n                        self.coap_downloader_retry_delay).encode()),\n                    timeout_s=10) is None:\n                raise self.failureException('string not found')\n            download_failed_timestamp = time.time()\n            file_server._server.reset(port)\n\n            self.assertPktIsDtlsClientHello(\n                file_server._server._raw_udp_socket.recv(\n                    65536, socket.MSG_PEEK))\n            self.assertAlmostEqual(\n                download_failed_timestamp +\n                self.coap_downloader_retry_delay,\n                time.time(),\n                delta=1)\n\n        self.wait_for_download()\n\nclass FirmwareUpdateCoapsResumptionScheduledDownloadCancelTest(\n        FirmwareUpdate.CoapsDownloaderRetry):\n    def runTest(self):\n        # Write /5/0/1 (Firmware URI)\n        self.write_firmware_uri_expect_success(self.fw_uri)\n\n        self.wait_for_half_download()\n\n        with self.server_thread[0].file_server as file_server:\n            port = file_server._server.get_listen_port()\n            file_server._server.close()\n            if self.read_log_until_match(\n                regex=re.escape(\n                    b'retrying download 1, attempt number 1, with delay ' + str(\n                        self.coap_downloader_retry_delay).encode()),\n                    timeout_s=10) is None:\n                raise self.failureException('string not found')\n            file_server._server.reset(port)\n\n            # cancel download\n            self.write_firmware_uri_expect_success(b'')\n            self.assertEqual(UpdateState.IDLE, self.read_state())\n            self.assertEqual(UpdateResult.INITIAL, self.read_update_result())\n\n            file_server._server._raw_udp_socket.settimeout(\n                self.coap_downloader_retry_delay * 2)\n\n            with self.assertRaises(socket.timeout):\n                file_server._server._raw_udp_socket.recv(\n                    65536, socket.MSG_PEEK)\n\n    def tearDown(self):\n        super().tearDown(check_fw_file=False)\n\n\nclass FirmwareUpdateCoapsResumptionScheduledOfflineModeBetweenTest(\n        FirmwareUpdate.CoapsDownloaderRetry):\n    def runTest(self):\n        # Write /5/0/1 (Firmware URI)\n        self.write_firmware_uri_expect_success(self.fw_uri)\n\n        self.wait_for_half_download()\n\n        with self.server_thread[0].file_server as file_server:\n            port = file_server._server.get_listen_port()\n            file_server._server.close()\n            if self.read_log_until_match(\n                regex=re.escape(\n                    b'retrying download 1, attempt number 1, with delay ' + str(\n                        self.coap_downloader_retry_delay).encode()),\n                    timeout_s=10) is None:\n                raise self.failureException('string not found')\n            download_failed_timestamp = time.time()\n            file_server._server.reset(port)\n\n            self.communicate('enter-offline')\n            time.sleep(2)\n            self.communicate('exit-offline')\n\n            # after exiting offline mode we should try to reconnect immediately\n            self.assertPktIsDtlsClientHello(\n                file_server._server._raw_udp_socket.recv(\n                    65536, socket.MSG_PEEK))\n            self.assertAlmostEqual(\n                download_failed_timestamp + 2, time.time(), delta=1)\n\n        self.assertDemoRegisters()\n\n        self.wait_for_download()\n\n\nclass FirmwareUpdateCoapsResumptionScheduledOfflineModeBetweenCheckRetryCounterTest(\n        FirmwareUpdate.CoapsDownloaderRetry):\n    def setUp(self):\n        super().setUp(retry_count=3)\n\n    def runTest(self):\n        # Write /5/0/1 (Firmware URI)\n        self.write_firmware_uri_expect_success(self.fw_uri)\n\n        self.wait_for_half_download()\n\n        with self.server_thread[0].file_server as file_server:\n            port = file_server._server.get_listen_port()\n            file_server._server.close()\n            if self.read_log_until_match(\n                regex=re.escape(\n                    b'retrying download 1, attempt number 1, with delay ' + str(\n                        self.coap_downloader_retry_delay).encode()),\n                    timeout_s=10) is None:\n                raise self.failureException('string not found')\n            download_failed_timestamp = time.time()\n\n            # first retray\n            if self.read_log_until_match(\n                    regex=re.escape(b'could not connect socket for download id = '),\n                    timeout_s=10) is None:\n                raise self.failureException('string not found')\n            retry_timestamp = time.time()\n            self.assertAlmostEqual(\n                download_failed_timestamp +\n                self.coap_downloader_retry_delay,\n                retry_timestamp,\n                delta=1)\n\n            self.communicate('enter-offline')\n            time.sleep(2)\n            self.communicate('exit-offline')\n\n            self.assertDemoRegisters()\n\n            # after exiting offline mode we should try to reconnect\n            # immediately, second retry\n            if self.read_log_until_match(\n                    regex=re.escape(b'could not connect socket for download id = '),\n                    timeout_s=10) is None:\n                raise self.failureException('string not found')\n            self.assertAlmostEqual(retry_timestamp + 2, time.time(), delta=1)\n            retry_timestamp = time.time()\n\n            # thrird retry\n            if self.read_log_until_match(\n                    regex=re.escape(b'could not connect socket for download id = '),\n                    timeout_s=10) is None:\n                raise self.failureException('string not found')\n            self.assertAlmostEqual(\n                retry_timestamp +\n                self.coap_downloader_retry_delay,\n                time.time(),\n                delta=1)\n\n            file_server._server.reset(port)\n            file_server._server._raw_udp_socket.settimeout(\n                self.coap_downloader_retry_delay * 2)\n            # there should be no more retries\n            with self.assertRaises(socket.timeout):\n                file_server._server._raw_udp_socket.recv(\n                    65536, socket.MSG_PEEK)\n\n    def tearDown(self):\n        super().tearDown(check_fw_file=False)\n\nclass FirmwareUpdateCoapsResumptionScheduledOfflineModeTest(\n        FirmwareUpdate.CoapsDownloaderRetry):\n    def runTest(self):\n        # Write /5/0/1 (Firmware URI)\n        self.write_firmware_uri_expect_success(self.fw_uri)\n\n        self.wait_for_half_download()\n\n        with self.server_thread[0].file_server as file_server:\n            port = file_server._server.get_listen_port()\n            file_server._server.close()\n            if self.read_log_until_match(\n                regex=re.escape(\n                    b'retrying download 1, attempt number 1, with delay ' + str(\n                        self.coap_downloader_retry_delay).encode()),\n                    timeout_s=10) is None:\n                raise self.failureException('string not found')\n            file_server._server.reset(port)\n\n            self.communicate('enter-offline')\n\n            timeout = file_server._server._raw_udp_socket.gettimeout()\n            file_server._server._raw_udp_socket.settimeout(\n                self.coap_downloader_retry_delay * 2)\n            # there should be no retries\n            with self.assertRaises(socket.timeout):\n                file_server._server._raw_udp_socket.recv(\n                    65536, socket.MSG_PEEK)\n            file_server._server._raw_udp_socket.settimeout(timeout)\n            self.communicate('exit-offline')\n\n        self.assertDemoRegisters()\n\n        self.wait_for_download()\n\nclass FirmwareUpdateCoapsResumptionScheduledUpdateSuspendTest(\n        FirmwareUpdate.CoapsDownloaderRetry):\n    def runTest(self):\n        # Write /5/0/1 (Firmware URI)\n        self.write_firmware_uri_expect_success(self.fw_uri)\n\n        self.wait_for_half_download()\n\n        with self.server_thread[0].file_server as file_server:\n            port = file_server._server.get_listen_port()\n            file_server._server.close()\n            if self.read_log_until_match(\n                regex=re.escape(\n                    b'retrying download 1, attempt number 1, with delay ' + str(\n                        self.coap_downloader_retry_delay).encode()),\n                    timeout_s=10) is None:\n                raise self.failureException('string not found')\n            file_server._server.reset(port)\n\n            self.communicate('fw-update-suspend')\n\n            timeout = file_server._server._raw_udp_socket.gettimeout()\n            file_server._server._raw_udp_socket.settimeout(\n                self.coap_downloader_retry_delay * 2)\n            # there should be no retries\n            with self.assertRaises(socket.timeout):\n                file_server._server._raw_udp_socket.recv(\n                    65536, socket.MSG_PEEK)\n            file_server._server._raw_udp_socket.settimeout(timeout)\n            self.communicate('fw-update-reconnect')\n\n        self.wait_for_download()\n\nclass FirmwareUpdateCoapsResumptionScheduledForceReconnect(\n        FirmwareUpdate.CoapsDownloaderRetry):\n    def runTest(self):\n        # Write /5/0/1 (Firmware URI)\n        self.write_firmware_uri_expect_success(self.fw_uri)\n\n        self.wait_for_half_download()\n\n        with self.server_thread[0].file_server as file_server:\n            port = file_server._server.get_listen_port()\n            file_server._server.close()\n            if self.read_log_until_match(\n                regex=re.escape(\n                    b'retrying download 1, attempt number 1, with delay ' + str(\n                        self.coap_downloader_retry_delay).encode()),\n                    timeout_s=10) is None:\n                raise self.failureException('string not found')\n            download_failed_timestamp = time.time()\n            file_server._server.reset(port)\n\n            # We don't call `fw-update-reconnect` right away to provoke a situation\n            # in which a reconnection attempt may occur from two sources. Without\n            # this delay reconnection related to retry mechanizm will be canceled\n            # becuase the file will have time to download before the time when the\n            # retry should take place.\n            sleep_time = self.coap_downloader_retry_delay - 1\n            time.sleep(sleep_time)\n            self.communicate('fw-update-reconnect')\n            self.assertPktIsDtlsClientHello(\n                file_server._server._raw_udp_socket.recv(\n                    65536, socket.MSG_PEEK))\n\n            # check if we didn't wait for the resumption delay and started it\n            # right away\n            self.assertAlmostEqual(\n                download_failed_timestamp + sleep_time,\n                time.time(),\n                delta=0.5)\n\n        self.wait_for_download()\n        self.assertTrue(\n            time.time() -\n            download_failed_timestamp -\n            1 > self.coap_downloader_retry_delay)\n\n\nclass FirmwareUpdateCoapsResumptionScheduledOfflineModeDifferentTransportTest(\n        FirmwareUpdate.CoapsDownloaderRetry):\n    def runTest(self):\n        # Write /5/0/1 (Firmware URI)\n        self.write_firmware_uri_expect_success(self.fw_uri)\n\n        self.wait_for_half_download()\n\n        with self.server_thread[0].file_server as file_server:\n            port = file_server._server.get_listen_port()\n            file_server._server.close()\n            if self.read_log_until_match(\n                regex=re.escape(\n                    b'retrying download 1, attempt number 1, with delay ' + str(\n                        self.coap_downloader_retry_delay).encode()),\n                    timeout_s=10) is None:\n                raise self.failureException('string not found')\n            file_server._server.reset(port)\n            download_failed_timestamp = time.time()\n\n            self.communicate('enter-offline tcp')\n            # retry should not by immediate\n            self.assertPktIsDtlsClientHello(\n                file_server._server._raw_udp_socket.recv(\n                    65536, socket.MSG_PEEK))\n            self.assertAlmostEqual(\n                download_failed_timestamp +\n                self.coap_downloader_retry_delay,\n                time.time(),\n                delta=1)\n\n        self.wait_for_download()\n\nclass FirmwareUpdateCoapsResumptionDuringReconnect(\n        FirmwareUpdate.CoapsDownloaderRetry):\n    def runTest(self):\n        # Write /5/0/1 (Firmware URI)\n        self.write_firmware_uri_expect_success(self.fw_uri)\n\n        self.wait_for_half_download()\n        self.communicate('fw-update-suspend')\n\n        with self.server_thread[0].file_server as file_server:\n            port = file_server._server.get_listen_port()\n            self.communicate('fw-update-reconnect')\n\n            if self.read_log_until_match(\n                regex=re.escape(\n                    b'retrying download 1, attempt number 1, with delay ' + str(\n                        self.coap_downloader_retry_delay).encode()),\n                    timeout_s=10) is None:\n                raise self.failureException('string not found')\n            download_failed_timestamp = time.time()\n            file_server._server.reset(port)\n\n            self.assertPktIsDtlsClientHello(\n                file_server._server._raw_udp_socket.recv(\n                    65536, socket.MSG_PEEK))\n            self.assertAlmostEqual(\n                download_failed_timestamp +\n                self.coap_downloader_retry_delay,\n                time.time(),\n                delta=1)\n\n        self.wait_for_download()\n\nclass FirmwareUpdateHttpSuspendDuringOfflineAndReconnectDuringOnlineTest(\n    FirmwareUpdate.TestWithPartialHttpDownloadAndRestart):\n    def runTest(self):\n        self.provide_response()\n\n        # Write /5/0/1 (Firmware URI)\n        self.write_firmware_uri_expect_success(self.get_firmware_uri())\n\n        self.wait_for_half_download()\n\n        self.communicate('enter-offline')\n\n        time.sleep(5)\n        fsize = os.stat(self.fw_file_name).st_size\n        self.assertEqual(len(self.requests), 1)\n        self.provide_response()\n\n        self.communicate('fw-update-suspend')\n\n        time.sleep(5)\n        self.assertEqual(os.stat(self.fw_file_name).st_size, fsize)\n        self.assertEqual(len(self.requests), 1)\n\n        self.communicate('exit-offline')\n\n        self.assertDemoRegisters()\n\n        time.sleep(5)\n        self.assertEqual(os.stat(self.fw_file_name).st_size, fsize)\n        self.assertEqual(len(self.requests), 1)\n\n        self.communicate('fw-update-reconnect')\n\n        self.wait_for_download()\n\n        self.assertEqual(len(self.requests), 2)\n\n\nclass FirmwareUpdateHttpSuspendDuringOfflineAndReconnectDuringOfflineTest(\n    FirmwareUpdate.TestWithPartialHttpDownloadAndRestart):\n    def runTest(self):\n        self.provide_response()\n\n        # Write /5/0/1 (Firmware URI)\n        self.write_firmware_uri_expect_success(self.get_firmware_uri())\n\n        self.wait_for_half_download()\n\n        self.communicate('enter-offline')\n\n        time.sleep(5)\n        fsize = os.stat(self.fw_file_name).st_size\n        self.assertEqual(len(self.requests), 1)\n        self.provide_response()\n\n        self.communicate('fw-update-suspend')\n\n        time.sleep(5)\n        self.assertEqual(os.stat(self.fw_file_name).st_size, fsize)\n        self.assertEqual(len(self.requests), 1)\n\n        self.communicate('fw-update-reconnect')\n\n        time.sleep(5)\n        self.assertEqual(os.stat(self.fw_file_name).st_size, fsize)\n        self.assertEqual(len(self.requests), 1)\n\n        self.communicate('exit-offline')\n\n        self.assertDemoRegisters()\n\n        self.wait_for_download()\n\n        self.assertEqual(len(self.requests), 2)\n\n\nclass FirmwareUpdateHttpOfflineDuringSuspendAndReconnectDuringOnlineTest(\n    FirmwareUpdate.TestWithPartialHttpDownloadAndRestart):\n    def runTest(self):\n        self.provide_response()\n\n        # Write /5/0/1 (Firmware URI)\n        self.write_firmware_uri_expect_success(self.get_firmware_uri())\n\n        self.wait_for_half_download()\n\n        self.communicate('fw-update-suspend')\n\n        time.sleep(5)\n        fsize = os.stat(self.fw_file_name).st_size\n        self.assertEqual(len(self.requests), 1)\n        self.provide_response()\n\n        self.communicate('enter-offline')\n\n        time.sleep(5)\n        self.assertEqual(os.stat(self.fw_file_name).st_size, fsize)\n        self.assertEqual(len(self.requests), 1)\n\n        self.communicate('exit-offline')\n\n        self.assertDemoRegisters()\n\n        time.sleep(5)\n        self.assertEqual(os.stat(self.fw_file_name).st_size, fsize)\n        self.assertEqual(len(self.requests), 1)\n\n        self.communicate('fw-update-reconnect')\n\n        self.wait_for_download()\n\n        self.assertEqual(len(self.requests), 2)\n\n\nclass FirmwareUpdateHttpOfflineDuringSuspendAndReconnectDuringOfflineTest(\n    FirmwareUpdate.TestWithPartialHttpDownloadAndRestart):\n    def runTest(self):\n        self.provide_response()\n\n        # Write /5/0/1 (Firmware URI)\n        self.write_firmware_uri_expect_success(self.get_firmware_uri())\n\n        self.wait_for_half_download()\n\n        self.communicate('fw-update-suspend')\n\n        time.sleep(5)\n        fsize = os.stat(self.fw_file_name).st_size\n        self.assertEqual(len(self.requests), 1)\n        self.provide_response()\n\n        self.communicate('enter-offline')\n\n        time.sleep(5)\n        self.assertEqual(os.stat(self.fw_file_name).st_size, fsize)\n        self.assertEqual(len(self.requests), 1)\n\n        self.communicate('fw-update-reconnect')\n\n        time.sleep(5)\n        self.assertEqual(os.stat(self.fw_file_name).st_size, fsize)\n        self.assertEqual(len(self.requests), 1)\n\n        self.communicate('exit-offline')\n\n        self.assertDemoRegisters()\n\n        self.wait_for_download()\n\n        self.assertEqual(len(self.requests), 2)\n\n\nclass FirmwareUpdateRestartWithDownloaded(FirmwareUpdate.Test):\n    def setUp(self):\n        super().setUp()\n        self.set_check_marker(True)\n        self.set_auto_deregister(False)\n        self.set_reset_machine(False)\n\n    def runTest(self):\n        # Write /5/0/0 (Firmware): script content\n        req = Lwm2mWrite(ResPath.FirmwareUpdate.Package,\n                         make_firmware_package(self.FIRMWARE_SCRIPT_CONTENT),\n                         format=coap.ContentFormat.APPLICATION_OCTET_STREAM)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n\n        # restart the app\n        self.teardown_demo_with_servers()\n        self.setup_demo_with_servers(\n            fw_updated_marker_path=self.ANJAY_MARKER_FILE)\n\n        self.assertEqual(UpdateState.DOWNLOADED, self.read_state())\n        self.assertEqual(UpdateResult.INITIAL, self.read_update_result())\n\n        # Execute /5/0/2 (Update)\n        self.perform_firmware_update_expect_success()\n\n\nclass FirmwareUpdateRestartWithDownloading(\n    FirmwareUpdate.TestWithPartialCoapDownloadAndRestart):\n    def runTest(self):\n        # Write /5/0/1 (Firmware URI)\n        self.write_firmware_uri_expect_success(self.fw_uri)\n\n        self.wait_for_half_download()\n\n        self.demo_process.kill()\n\n        # restart demo app\n        self.serv.reset()\n        with self.file_server as file_server:\n            file_server._server.reset()\n        self._start_demo(self.cmdline_args)\n        self.assertDemoRegisters(self.serv)\n\n        # wait until client downloads the firmware\n        deadline = time.time() + 20\n        state = None\n        while time.time() < deadline:\n            fsize = os.stat(self.fw_file_name).st_size\n            self.assertGreater(fsize * 2, self.GARBAGE_SIZE)\n            state = self.read_state()\n            self.assertIn(\n                state, {UpdateState.DOWNLOADING, UpdateState.DOWNLOADED})\n            if state == UpdateState.DOWNLOADED:\n                break\n        self.assertEqual(state, UpdateState.DOWNLOADED)\n\n\nclass FirmwareUpdateRestartWithDownloadingETagChange(\n    FirmwareUpdate.TestWithPartialCoapDownloadAndRestart):\n    def runTest(self):\n        # Write /5/0/1 (Firmware URI)\n        self.write_firmware_uri_expect_success(self.fw_uri)\n\n        self.wait_for_half_download()\n\n        self.demo_process.kill()\n\n        # restart demo app\n        self.serv.reset()\n        with self.file_server as file_server:\n            old_etag = file_server._resources['/firmware'].etag\n            new_etag = bytes([(old_etag[0] + 1) % 256]) + old_etag[1:]\n            self.assertNotEqual(old_etag, new_etag)\n            file_server.set_resource('/firmware',\n                                     make_firmware_package(\n                                         self.FIRMWARE_SCRIPT_CONTENT),\n                                     etag=new_etag)\n        self._start_demo(self.cmdline_args)\n        self.assertDemoRegisters(self.serv)\n\n        # wait until client downloads the firmware\n        deadline = time.time() + 20\n        state = None\n        file_truncated = False\n        while time.time() < deadline:\n            try:\n                fsize = os.stat(self.fw_file_name).st_size\n                if fsize * 2 <= self.GARBAGE_SIZE:\n                    file_truncated = True\n            except FileNotFoundError:\n                file_truncated = True\n            state = self.read_state()\n            self.assertIn(\n                state, {UpdateState.DOWNLOADING, UpdateState.DOWNLOADED})\n            if state == UpdateState.DOWNLOADED:\n                break\n            # prevent test from reading Result hundreds of times per second\n            time.sleep(0.5)\n\n        self.assertEqual(state, UpdateState.DOWNLOADED)\n        self.assertTrue(file_truncated)\n\n# For more information on how this test works, please refer to the DtlsConnectionIdTest header.\n# Keep in mind that in this test, we are testing the connection between the file server and the\n# client, as opposed to the previously mentioned test, where we test the connection between the\n# client and the LwM2M server.\n@unittest.skipIf(not pymbedtls.Context.supports_connection_id(),\n                 \"connection_id support is not enabled in pymbedtls\")\nclass FirmwareUpdateCoapsUriConnectionID(FirmwareUpdate.TestWithCoapsServerProxy):\n    REQUEST_FINAL_COUNT = 11\n\n    def setUp(self):\n        super().setUp(garbage=10240)\n\n    def tearDown(self):\n        super().tearDown()\n\n        with self.file_server as file_server:\n            # There might be some retransmissions, but they do not occur often and there is not\n            # much that we can do about it because we have to add the BLOCK2 option in following loop\n            # if we want to check requests\n            self.assertEqual(self.REQUEST_FINAL_COUNT, len(file_server.requests))\n\n            self.assertMsgEqual(CoapGet('/firmware'), file_server.requests[0])\n            for i in range(1, self.REQUEST_FINAL_COUNT):\n                self.assertMsgEqual(CoapGet('/firmware', options=[coap.Option.BLOCK2(\n                    seq_num=i, has_more=False, block_size=1024)]), file_server.requests[i])\n\n    def runTest(self):\n        with self.file_server as file_server:\n            file_server.set_resource('/firmware',\n                                    make_firmware_package(self.FIRMWARE_SCRIPT_CONTENT))\n            fw_uri = file_server.get_resource_uri('/firmware')\n        with self.server_thread[0]._file_server._server.server_proxy():\n            self.write_firmware_uri_expect_success(fw_uri)\n\n            deadline = time.time() + 20\n            while time.time() < deadline:\n                if self.REQUEST_FINAL_COUNT / 2 <= len(file_server.requests):\n                    break\n            else:\n                self.fail('at this point half of the firmware should have been downloaded')\n\n        # Unconnect the socket at the pymbedtls site (this unconnects the link between \"server proxy\"\n        # and CoAP file server, to allow accepting packets from unknown endpoints.\n        disconnect_socket(self.server_thread[0]._file_server._server.socket.py_socket)\n\n        self.assertTrue(len(file_server.requests) < 10)\n\n        with self.server_thread[0]._file_server._server.server_proxy():\n            self.server_thread[0].reset_timeout_occurred()\n            self.wait_for_download(20)\n            self.assertFalse(self.server_thread[0].get_timeout_occurred())\n\n@unittest.skipIf(not pymbedtls.Context.supports_connection_id(),\n                 \"connection_id support is not enabled in pymbedtls\")\nclass FirmwareUpdateCoapsUriConnectionIDSuspendAndReconnect(\n    FirmwareUpdate.TestWithPartialCoapsDownloadAndRestart):\n    def setUp(self):\n        extra_cmdline_args = ['--use-connection-id']\n        super().setUp(extra_cmdline_args=extra_cmdline_args)\n\n    def runTest(self):\n        # Write /5/0/1 (Firmware URI)\n        self.write_firmware_uri_expect_success(self.fw_uri)\n\n        self.wait_for_half_download()\n\n        with self.file_server as file_server:\n            # Flush any buffers\n            while True:\n                try:\n                    file_server._server.recv(timeout_s=1)\n                except socket.timeout:\n                    break\n\n            self.communicate('fw-update-suspend')\n\n            with self.assertRaises(socket.timeout):\n                file_server._server.recv(timeout_s=5)\n\n            self.communicate('enter-offline')\n\n            with self.assertRaises(socket.timeout):\n                file_server._server.recv(timeout_s=5)\n\n            self.communicate('exit-offline')\n\n            self.assertDemoRegisters()\n\n            with self.assertRaises(socket.timeout):\n                file_server._server.recv(timeout_s=5)\n\n            # Unconnect the socket at the pymbedtls site,\n            # to allow accepting packets from unknown endpoints.\n            disconnect_socket(file_server._server.socket.py_socket)\n\n            self.communicate('fw-update-reconnect')\n\n        self.wait_for_download()\n\nclass FirmwareUpdateCoapsUriWithoutConnectionID(FirmwareUpdate.TestWithCoapsServerProxy):\n    REQUEST_FINAL_COUNT = 5\n\n    def setUp(self):\n        super().setUp(extra_cmdline_args='', garbage=10240)\n\n    def tearDown(self):\n        self.assertEqual(self.read_state(), UpdateState.DOWNLOADING)\n        super().tearDown()\n\n        with self.file_server as file_server:\n            # +1 for one additional request\n            self.assertTrue(self.REQUEST_FINAL_COUNT + 1 >= len(file_server.requests))\n\n            self.assertMsgEqual(CoapGet('/firmware'), file_server.requests[0])\n            for i in range(1, len(file_server.requests)):\n                self.assertMsgEqual(CoapGet('/firmware', options=[coap.Option.BLOCK2(\n                    seq_num=i, has_more=False, block_size=1024)]), file_server.requests[i])\n\n    def runTest(self):\n        with self.file_server as file_server:\n            file_server.set_resource('/firmware',\n                                    make_firmware_package(self.FIRMWARE_SCRIPT_CONTENT))\n            fw_uri = file_server.get_resource_uri('/firmware')\n        with self.server_thread[0]._file_server._server.server_proxy():\n            self.write_firmware_uri_expect_success(fw_uri)\n\n            deadline = time.time() + 20\n            while time.time() < deadline:\n                if self.REQUEST_FINAL_COUNT <= len(file_server.requests):\n                    break\n            else:\n                self.fail('at this point half of the firmware should have been downloaded')\n\n        # Unconnect the socket at the pymbedtls site (this unconnects the link between \"server proxy\"\n        # and CoAP file server, to allow accepting packets from unknown endpoints.\n        disconnect_socket(self.server_thread[0]._file_server._server.socket.py_socket)\n\n        with self.server_thread[0]._file_server._server.server_proxy():\n            self.server_thread[0].reset_timeout_occurred()\n            deadline = time.time() + 7.5\n            while time.time() < deadline:\n                if self.server_thread[0].get_timeout_occurred():\n                    break\n            else:\n                self.fail('timeout should have occurred')\n\nclass FirmwareUpdateRestartWithDownloadingOverHttp(\n    FirmwareUpdate.TestWithPartialHttpDownloadAndRestart):\n    def get_etag(self, response_content):\n        return None\n\n    def runTest(self):\n        self.provide_response()\n        # Write /5/0/1 (Firmware URI)\n        self.write_firmware_uri_expect_success(self.get_firmware_uri())\n\n        self.wait_for_half_download()\n\n        self.assertEqual(2, self.get_socket_count())\n        self.assertEqual(1, self.get_non_lwm2m_socket_count())\n        self.assertEqual('TCP', self.get_transport(socket_index=-1))\n\n        self.demo_process.kill()\n\n        # restart demo app\n        self.serv.reset()\n\n        self.provide_response()\n        self._start_demo(self.cmdline_args)\n        self.assertDemoRegisters(self.serv)\n\n        # wait until client downloads the firmware\n        deadline = time.time() + 20\n        state = None\n        file_truncated = False\n        while time.time() < deadline:\n            try:\n                fsize = os.stat(self.fw_file_name).st_size\n                if fsize * 2 <= self.GARBAGE_SIZE:\n                    file_truncated = True\n            except FileNotFoundError:\n                file_truncated = True\n            state = self.read_state()\n            self.assertIn(\n                state, {UpdateState.DOWNLOADING, UpdateState.DOWNLOADED})\n            if state == UpdateState.DOWNLOADED:\n                break\n            # prevent test from reading Result hundreds of times per second\n            time.sleep(0.5)\n\n        self.assertEqual(state, UpdateState.DOWNLOADED)\n        self.assertTrue(file_truncated)\n\n        self.assertEqual(len(self.requests), 2)\n\n\nclass FirmwareUpdateResumeDownloadingOverHttp(\n    FirmwareUpdate.TestWithPartialHttpDownloadAndRestart):\n    def send_headers(self, handler, response_content, response_etag):\n        if 'Range' in handler.headers:\n            self.assertEqual(handler.headers['If-Match'], response_etag)\n            match = re.fullmatch(r'bytes=([0-9]+)-', handler.headers['Range'])\n            self.assertIsNotNone(match)\n            offset = int(match.group(1))\n            handler.send_header('Content-range',\n                                'bytes %d-%d/*' % (offset, len(response_content) - 1))\n            return offset\n\n    def runTest(self):\n        self.provide_response()\n        # Write /5/0/1 (Firmware URI)\n        self.write_firmware_uri_expect_success(self.get_firmware_uri())\n\n        self.wait_for_half_download()\n\n        self.demo_process.kill()\n\n        # restart demo app\n        self.serv.reset()\n\n        self.provide_response()\n        self._start_demo(self.cmdline_args)\n        self.assertDemoRegisters(self.serv)\n\n        # wait until client downloads the firmware\n        deadline = time.time() + 20\n        state = None\n        while time.time() < deadline:\n            fsize = os.stat(self.fw_file_name).st_size\n            self.assertGreater(fsize * 2, self.GARBAGE_SIZE)\n            state = self.read_state()\n            self.assertIn(\n                state, {UpdateState.DOWNLOADING, UpdateState.DOWNLOADED})\n            if state == UpdateState.DOWNLOADED:\n                break\n            # prevent test from reading Result hundreds of times per second\n            time.sleep(0.5)\n\n        self.assertEqual(state, UpdateState.DOWNLOADED)\n\n        self.assertEqual(len(self.requests), 2)\n\n\nclass FirmwareUpdateResumeDownloadingOverHttpWithReconnect(\n    FirmwareUpdate.TestWithPartialHttpDownloadAndRestart):\n    def _get_valgrind_args(self):\n        # we don't kill the process here, so we want Valgrind\n        return FirmwareUpdate.TestWithHttpServer._get_valgrind_args(self)\n\n    def send_headers(self, handler, response_content, response_etag):\n        if 'Range' in handler.headers:\n            self.assertEqual(handler.headers['If-Match'], response_etag)\n            match = re.fullmatch(r'bytes=([0-9]+)-', handler.headers['Range'])\n            self.assertIsNotNone(match)\n            offset = int(match.group(1))\n            handler.send_header('Content-range',\n                                'bytes %d-%d/*' % (offset, len(response_content) - 1))\n            return offset\n\n    def runTest(self):\n        self.provide_response()\n        # Write /5/0/1 (Firmware URI)\n        self.write_firmware_uri_expect_success(self.get_firmware_uri())\n\n        self.wait_for_half_download()\n\n        # reconnect\n        self.serv.reset()\n        self.communicate('reconnect')\n        self.provide_response()\n        self.assertDemoRegisters(self.serv, timeout_s=5)\n\n        # wait until client downloads the firmware\n        deadline = time.time() + 20\n        state = None\n        while time.time() < deadline:\n            fsize = os.stat(self.fw_file_name).st_size\n            self.assertGreater(fsize * 2, self.GARBAGE_SIZE)\n            state = self.read_state()\n            self.assertIn(\n                state, {UpdateState.DOWNLOADING, UpdateState.DOWNLOADED})\n            if state == UpdateState.DOWNLOADED:\n                break\n            # prevent test from reading Result hundreds of times per second\n            time.sleep(0.5)\n\n        self.assertEqual(state, UpdateState.DOWNLOADED)\n\n        self.assertEqual(len(self.requests), 2)\n\n\nclass FirmwareUpdateResumeFromStartWithDownloadingOverHttp(\n    FirmwareUpdate.TestWithPartialHttpDownloadAndRestart):\n    def runTest(self):\n        self.provide_response()\n        # Write /5/0/1 (Firmware URI)\n        self.write_firmware_uri_expect_success(self.get_firmware_uri())\n\n        self.wait_for_half_download()\n\n        self.demo_process.kill()\n\n        # restart demo app\n        self.serv.reset()\n\n        self.provide_response()\n        self._start_demo(self.cmdline_args)\n        self.assertDemoRegisters(self.serv)\n\n        # wait until client downloads the firmware\n        deadline = time.time() + 20\n        state = None\n        while time.time() < deadline:\n            fsize = os.stat(self.fw_file_name).st_size\n            self.assertGreater(fsize * 2, self.GARBAGE_SIZE)\n            state = self.read_state()\n            self.assertIn(\n                state, {UpdateState.DOWNLOADING, UpdateState.DOWNLOADED})\n            if state == UpdateState.DOWNLOADED:\n                break\n            # prevent test from reading Result hundreds of times per second\n            time.sleep(0.5)\n\n        self.assertEqual(state, UpdateState.DOWNLOADED)\n\n        self.assertEqual(len(self.requests), 2)\n\n\nclass FirmwareUpdateRestartAfter412WithDownloadingOverHttp(\n    FirmwareUpdate.TestWithPartialHttpDownloadAndRestart):\n    def check_success(self, handler, response_content, response_etag):\n        if 'If-Match' in handler.headers:\n            self.assertEqual(handler.headers['If-Match'], response_etag)\n            handler.send_error(http.HTTPStatus.PRECONDITION_FAILED)\n\n    def runTest(self):\n        self.provide_response()\n        # Write /5/0/1 (Firmware URI)\n        self.write_firmware_uri_expect_success(self.get_firmware_uri())\n\n        self.wait_for_half_download()\n\n        self.demo_process.kill()\n\n        # restart demo app\n        self.serv.reset()\n\n        self.provide_response()\n        self._start_demo(self.cmdline_args)\n        self.assertDemoRegisters(self.serv)\n\n        # wait until client downloads the firmware\n        deadline = time.time() + 20\n        state = None\n        file_truncated = False\n        while time.time() < deadline:\n            try:\n                fsize = os.stat(self.fw_file_name).st_size\n                if fsize * 2 <= self.GARBAGE_SIZE:\n                    file_truncated = True\n            except FileNotFoundError:\n                file_truncated = True\n            state = self.read_state()\n            self.assertIn(\n                state, {UpdateState.DOWNLOADING, UpdateState.DOWNLOADED})\n            if state == UpdateState.DOWNLOADED:\n                break\n            # prevent test from reading Result hundreds of times per second\n            time.sleep(0.5)\n\n        self.assertEqual(state, UpdateState.DOWNLOADED)\n        self.assertTrue(file_truncated)\n\n        self.assertEqual(len(self.requests), 3)\n\n\nclass FirmwareUpdateWithDelayedResultTest:\n    class TestMixin:\n        def runTest(self, forced_error, result):\n            with open(os.path.join(self.config.demo_path, self.config.demo_cmd), 'rb') as f:\n                firmware = f.read()\n\n            # Write /5/0/0 (Firmware)\n            self.block_send(firmware,\n                            equal_chunk_splitter(chunk_size=1024),\n                            force_error=forced_error)\n\n            # Execute /5/0/2 (Update)\n            self.perform_firmware_update_expect_success()\n\n            self.serv.reset()\n            self.assertDemoRegisters()\n            self.assertEqual(self.read_path(self.serv, ResPath.FirmwareUpdate.UpdateResult).content,\n                             str(result).encode())\n            self.assertEqual(self.read_path(self.serv, ResPath.FirmwareUpdate.State).content,\n                             str(UpdateState.IDLE).encode())\n\n\nclass FirmwareUpdateWithDelayedSuccessTest(\n    FirmwareUpdateWithDelayedResultTest.TestMixin, Block.Test):\n    def runTest(self):\n        super().runTest(PackageForcedError.Firmware.DelayedSuccess, UpdateResult.SUCCESS)\n\n\nclass FirmwareUpdateWithDelayedFailureTest(\n    FirmwareUpdateWithDelayedResultTest.TestMixin, Block.Test):\n    def runTest(self):\n        super().runTest(PackageForcedError.Firmware.DelayedFailedUpdate, UpdateResult.FAILED)\n\n\nclass FirmwareUpdateWithSetSuccessInPerformUpgrade(Block.Test):\n    def runTest(self):\n        with open(os.path.join(self.config.demo_path, self.config.demo_cmd), 'rb') as f:\n            firmware = f.read()\n\n        # Write /5/0/0 (Firmware)\n        self.block_send(firmware,\n                        equal_chunk_splitter(chunk_size=1024),\n                        force_error=PackageForcedError.Firmware.SetSuccessInPerformUpgrade)\n\n        # Execute /5/0/2 (Update)\n        self.perform_firmware_update_expect_success()\n\n        # perform_upgrade handler is called via scheduler, so there is a small\n        # window during which reading the Firmware Update State still returns\n        # Updating. Wait for a while for State to actually change.\n        observed_states = []\n        deadline = time.time() + 5  # arbitrary limit\n        while not observed_states or observed_states[-1] == str(\n                UpdateState.UPDATING):\n            if time.time() > deadline:\n                self.fail('Firmware Update did not finish on time, last state = %s' % (\n                    observed_states[-1] if observed_states else 'NONE'))\n            observed_states.append(\n                self.read_path(self.serv, ResPath.FirmwareUpdate.State).content.decode())\n            time.sleep(0.5)\n\n        self.assertNotEqual([], observed_states)\n        self.assertEqual(observed_states[-1], str(UpdateState.IDLE))\n        self.assertEqual(self.read_path(self.serv, ResPath.FirmwareUpdate.UpdateResult).content,\n                         str(UpdateResult.SUCCESS).encode())\n\n\nclass FirmwareUpdateWithSetFailureInPerformUpgrade(Block.Test):\n    def runTest(self):\n        with open(os.path.join(self.config.demo_path, self.config.demo_cmd), 'rb') as f:\n            firmware = f.read()\n\n        # Write /5/0/0 (Firmware)\n        self.block_send(firmware,\n                        equal_chunk_splitter(chunk_size=1024),\n                        force_error=PackageForcedError.Firmware.SetFailureInPerformUpgrade)\n\n        # Execute /5/0/2 (Update)\n        self.perform_firmware_update_expect_success()\n\n        # perform_upgrade handler is called via scheduler, so there is a small\n        # window during which reading the Firmware Update State still returns\n        # Updating. Wait for a while for State to actually change.\n        observed_states = []\n        deadline = time.time() + 5  # arbitrary limit\n        while not observed_states or observed_states[-1] == str(\n                UpdateState.UPDATING):\n            if time.time() > deadline:\n                self.fail('Firmware Update did not finish on time, last state = %s' % (\n                    observed_states[-1] if observed_states else 'NONE'))\n            observed_states.append(\n                self.read_path(self.serv, ResPath.FirmwareUpdate.State).content.decode())\n            time.sleep(0.5)\n\n        self.assertNotEqual([], observed_states)\n        self.assertEqual(observed_states[-1], str(UpdateState.IDLE))\n        self.assertEqual(self.read_path(self.serv, ResPath.FirmwareUpdate.UpdateResult).content,\n                         str(UpdateResult.FAILED).encode())\n\n\ntry:\n    import aiocoap\n    import aiocoap.resource\n    import aiocoap.transports.tls\nexcept ImportError:\n    # FirmwareUpdateCoapTlsTest requires a bleeding-edge version of aiocoap, that at the time of\n    # writing this code, is not available even in the prerelease channel.\n    # So we're not enforcing this dependency for now.\n    pass\n\n\n@unittest.skipIf('aiocoap.transports.tls' not in sys.modules,\n                 'aiocoap.transports.tls not available')\n@unittest.skipIf(sys.version_info < (3, 5, 3),\n                 'SSLContext signature changed in Python 3.5.3')\nclass FirmwareUpdateCoapTlsTest(\n    FirmwareUpdate.TestWithTlsServer, test_suite.Lwm2mDmOperations):\n    def setUp(self):\n        super().setUp(garbage=8000)\n\n        class FirmwareResource(aiocoap.resource.Resource):\n            async def render_get(resource, request):\n                return aiocoap.Message(payload=make_firmware_package(\n                    self.FIRMWARE_SCRIPT_CONTENT))\n\n        serversite = aiocoap.resource.Site()\n        serversite.add_resource(('firmware',), FirmwareResource())\n\n        sslctx = ssl.SSLContext()\n        sslctx.load_cert_chain(self._cert_file, self._key_file)\n\n        class EphemeralTlsServer(aiocoap.transports.tls.TLSServer):\n            _default_port = 0\n\n        class CoapTcpFileServerThread(threading.Thread):\n            def __init__(self):\n                super().__init__()\n                self.loop = asyncio.new_event_loop()\n                ctx = aiocoap.Context(loop=self.loop, serversite=serversite)\n                self.loop.run_until_complete(ctx._append_tokenmanaged_transport(\n                    lambda tman: EphemeralTlsServer.create_server(('127.0.0.1', 0), tman, ctx.log,\n                                                                  self.loop, sslctx)))\n\n                socket = ctx.request_interfaces[0].token_interface.server.sockets[0]\n                self.server_address = socket.getsockname()\n\n            def run(self):\n                asyncio.set_event_loop(self.loop)\n                try:\n                    self.loop.run_forever()\n                finally:\n                    self.loop.run_until_complete(\n                        self.loop.shutdown_asyncgens())\n                    self.loop.close()\n\n        self.server_thread = CoapTcpFileServerThread()\n        self.server_thread.start()\n\n        self.set_check_marker(True)\n        self.set_auto_deregister(False)\n        self.set_reset_machine(False)\n\n    def tearDown(self):\n        try:\n            super().tearDown()\n        finally:\n            self.server_thread.loop.call_soon_threadsafe(\n                self.server_thread.loop.stop)\n            self.server_thread.join()\n\n    def get_firmware_uri(self):\n        return 'coaps+tcp://127.0.0.1:%d/firmware' % (\n            self.server_thread.server_address[1],)\n\n    def runTest(self):\n        self.write_firmware_and_wait_for_download(self.get_firmware_uri())\n\n        # Execute /5/0/2 (Update)\n        self.perform_firmware_update_expect_success()\n\n\nclass FirmwareUpdateWeakEtagTest(FirmwareUpdate.TestWithHttpServer):\n    def setUp(self):\n        super().setUp()\n        self.set_check_marker(True)\n        self.set_auto_deregister(False)\n        self.set_reset_machine(False)\n\n        orig_end_headers = self.http_server.RequestHandlerClass.end_headers\n\n        def updated_end_headers(request_handler):\n            request_handler.send_header('ETag', 'W/\"weaketag\"')\n            orig_end_headers(request_handler)\n\n        self.http_server.RequestHandlerClass.end_headers = updated_end_headers\n\n    def runTest(self):\n        self.provide_response()\n        self.write_firmware_and_wait_for_download(self.get_firmware_uri())\n\n        with open(self.ANJAY_MARKER_FILE, 'rb') as f:\n            marker_data = f.read()\n\n        self.assertNotIn(b'weaketag', marker_data)\n\n        # Execute /5/0/2 (Update)\n        self.perform_firmware_update_expect_success()\n\n\nclass SameSocketDownload:\n    class Test(test_suite.Lwm2mDtlsSingleServerTest, test_suite.Lwm2mDmOperations):\n        GARBAGE_SIZE = 2048\n        # Set to be able to comfortably test interleaved requests\n        ACK_TIMEOUT = 10\n        DEF_MAX_RETRANSMIT = 4\n        # Sometimes we want to allow the client to send more than one request at a time\n        # e.g. Update and GET.\n        NSTART = 1\n        LIFETIME = 86400\n        BINDING = 'U'\n        BLK_SZ = 1024\n\n        def setUp(self, *args, **kwargs):\n            if 'extra_cmdline_args' not in kwargs:\n                kwargs['extra_cmdline_args'] = []\n\n            kwargs['extra_cmdline_args'] += [\n                '--prefer-same-socket-downloads',\n                '--ack-timeout', str(self.ACK_TIMEOUT),\n                '--ack-random-factor', str(1.0),\n                '--nstart', str(self.NSTART)\n            ]\n\n            if '--max-retransmit' not in kwargs['extra_cmdline_args']:\n                kwargs['extra_cmdline_args'] += [\n                    '--max-retransmit', str(self.DEF_MAX_RETRANSMIT)\n                ]\n\n            kwargs['lifetime'] = self.LIFETIME\n            super().setUp(*args, **kwargs)\n            self.file_server = CoapFileServer(\n                self.serv._coap_server, binding=self.BINDING)\n            self.file_server.set_resource(path=FIRMWARE_PATH,\n                                          data=make_firmware_package(b'a' * self.GARBAGE_SIZE))\n\n        def read_state(self, serv=None):\n            return int(self.read_resource(serv or self.serv,\n                                          oid=OID.FirmwareUpdate,\n                                          iid=0,\n                                          rid=RID.FirmwareUpdate.State).content)\n\n        def read_result(self, serv=None):\n            return int(self.read_resource(serv or self.serv,\n                                          oid=OID.FirmwareUpdate,\n                                          iid=0,\n                                          rid=RID.FirmwareUpdate.UpdateResult).content)\n\n        def start_download(self):\n            self.write_resource(self.serv,\n                                oid=OID.FirmwareUpdate,\n                                iid=0,\n                                rid=RID.FirmwareUpdate.PackageURI,\n                                content=self.file_server.get_resource_uri(FIRMWARE_PATH))\n\n        def handle_get(self, pkt=None):\n            if pkt is None:\n                pkt = self.serv.recv()\n            block2 = pkt.get_options(coap.Option.BLOCK2)\n            if block2:\n                self.assertEqual(block2[0].block_size(), self.BLK_SZ)\n            self.file_server.handle_recvd_request(pkt)\n\n        def num_blocks(self):\n            return (len(\n                self.file_server._resources[FIRMWARE_PATH].data) + self.BLK_SZ - 1) // self.BLK_SZ\n\n\nclass FirmwareDownloadSameSocket(SameSocketDownload.Test):\n    def runTest(self):\n        self.start_download()\n\n        for _ in range(self.num_blocks()):\n            pkt = self.serv.recv()\n            self.handle_get(pkt)\n\n        self.assertEqual(self.read_state(), UpdateState.DOWNLOADED)\n\n\nclass FirmwareDownloadSameSocketAndOngoingBlockwiseWrite(\n    SameSocketDownload.Test):\n    def runTest(self):\n        self.create_instance(self.serv, oid=OID.Test, iid=1)\n        self.start_download()\n\n        resource_num_blocks = 10\n        self.assertGreaterEqual(resource_num_blocks, self.num_blocks())\n        for seq_num in range(resource_num_blocks):\n            if seq_num < self.num_blocks():\n                dl_req_get = self.serv.recv()\n\n            has_more = seq_num < resource_num_blocks - 1\n            # Server does blockwise write on the unrelated resource\n            pkt = Lwm2mWrite(ResPath.Test[1].ResRawBytes, b'x' * 16,\n                             format=coap.ContentFormat.APPLICATION_OCTET_STREAM,\n                             options=[coap.Option.BLOCK1(seq_num, has_more, 16)])\n            self.serv.send(pkt)\n            if has_more:\n                self.assertMsgEqual(\n                    Lwm2mContinue.matching(pkt)(), self.serv.recv())\n            else:\n                self.assertMsgEqual(\n                    Lwm2mChanged.matching(pkt)(), self.serv.recv())\n\n            if seq_num < self.num_blocks():\n                # Client waits for next chunk of Raw Bytes, but gets firmware\n                # block instead\n                self.handle_get(dl_req_get)\n\n        self.assertEqual(self.read_state(), UpdateState.DOWNLOADED)\n\n\nclass FirmwareDownloadSameSocketAndOngoingBlockwiseRead(\n    SameSocketDownload.Test):\n    BYTES = 160\n\n    def runTest(self):\n        self.create_instance(self.serv, oid=OID.Test, iid=1)\n        self.write_resource(self.serv, oid=OID.Test, iid=1, rid=RID.Test.ResBytesSize,\n                            content=bytes(str(self.BYTES), 'ascii'))\n        self.start_download()\n\n        block_size = 16\n        self.assertGreaterEqual(self.BYTES // block_size, self.num_blocks())\n        for seq_num in range(self.BYTES // block_size):\n            if seq_num < self.num_blocks():\n                dl_req_get = self.serv.recv()\n\n            # Server does blockwise write on the unrelated resource\n            pkt = Lwm2mRead(ResPath.Test[1].ResBytes,\n                            accept=coap.ContentFormat.APPLICATION_OCTET_STREAM,\n                            options=[coap.Option.BLOCK2(seq_num, 0, block_size)])\n            self.serv.send(pkt)\n            res = self.serv.recv()\n            self.assertEqual(pkt.msg_id, res.msg_id)\n            self.assertTrue(len(res.get_options(coap.Option.BLOCK2)) > 0)\n\n            if seq_num < self.num_blocks():\n                # Client waits for next chunk of Raw Bytes, but gets firmware\n                # block instead\n                self.handle_get(dl_req_get)\n\n        self.assertEqual(self.read_state(), UpdateState.DOWNLOADED)\n\n\nclass FirmwareDownloadSameSocketUpdateDuringDownloadNstart2(\n    SameSocketDownload.Test):\n    NSTART = 2\n\n    def runTest(self):\n        self.start_download()\n\n        for _ in range(self.num_blocks()):\n            dl_req_get = self.serv.recv()\n            # rather than responding to a request, force Update\n            self.communicate('send-update')\n            self.assertDemoUpdatesRegistration(timeout_s=5)\n            # and only then respond with next block\n            self.handle_get(dl_req_get)\n\n        self.assertEqual(self.read_state(), UpdateState.DOWNLOADED)\n\n\nclass FirmwareDownloadSameSocketUpdateDuringDownloadNstart1(\n    SameSocketDownload.Test):\n    def runTest(self):\n        self.start_download()\n\n        for _ in range(self.num_blocks()):\n            dl_req_get = self.serv.recv()\n            # rather than responding to a request, force Update\n            self.communicate('send-update')\n            # the Update won't be received, because of NSTART=1\n            with self.assertRaises(socket.timeout):\n                self.serv.recv(timeout_s=3)\n            # so we respond to a block\n            self.handle_get(dl_req_get)\n            # and only then the Update arrives\n            self.assertDemoUpdatesRegistration(timeout_s=5)\n\n        self.assertEqual(self.read_state(), UpdateState.DOWNLOADED)\n\n\nclass FirmwareDownloadSameSocketAndReconnectNstart1(SameSocketDownload.Test):\n    def runTest(self):\n        self.start_download()\n\n        for _ in range(self.num_blocks()):\n            # get dl request and ignore it\n            self.serv.recv()\n            # rather than responding to a request force reconnect\n            self.communicate('reconnect')\n            self.serv.reset()\n            # demo will resume DTLS session without sending any LwM2M messages\n            self.serv.listen()\n            # download request is retried\n            dl_req_get = self.serv.recv()\n            # and finally we respond to a block\n            self.handle_get(dl_req_get)\n\n        self.assertEqual(self.read_state(), UpdateState.DOWNLOADED)\n\n\nclass FirmwareDownloadSameSocketUpdateTimeoutNstart2(SameSocketDownload.Test):\n    NSTART = 2\n    LIFETIME = 5\n    DEF_MAX_RETRANSMIT = 1\n    ACK_TIMEOUT = 2\n\n    def runTest(self):\n        time.sleep(self.LIFETIME + 1)\n        self.assertMsgEqual(Lwm2mUpdate(self.DEFAULT_REGISTER_ENDPOINT, content=b''),\n                            self.serv.recv())\n        self.assertMsgEqual(Lwm2mUpdate(self.DEFAULT_REGISTER_ENDPOINT, content=b''),\n                            self.serv.recv())\n        self.start_download()\n\n        dl_get0_0 = self.serv.recv(filter=CoapGet._pkt_matches)\n        dl_get0_1 = self.serv.recv(filter=CoapGet._pkt_matches)  # retry\n        self.handle_get(dl_get0_0)\n        self.assertEqual(dl_get0_0.msg_id, dl_get0_1.msg_id)\n        dl_get1_0 = self.serv.recv(filter=CoapGet._pkt_matches)\n\n        # lifetime expired, demo re-registers\n        self.assertDemoRegisters(lifetime=self.LIFETIME)\n\n        # this is a retransmission\n        dl_get1_1 = self.serv.recv()\n        self.assertEqual(dl_get1_0.msg_id, dl_get1_1.msg_id)\n        self.handle_get(dl_get1_1)\n        self.handle_get(self.serv.recv())\n\n        self.assertEqual(self.read_state(), UpdateState.DOWNLOADED)\n\n\nclass FirmwareDownloadSameSocketUpdateTimeoutNstart1(SameSocketDownload.Test):\n    LIFETIME = 5\n    DEF_MAX_RETRANSMIT = 1\n    ACK_TIMEOUT = 2\n\n    def runTest(self):\n        time.sleep(self.LIFETIME + 1)\n        self.assertMsgEqual(Lwm2mUpdate(self.DEFAULT_REGISTER_ENDPOINT, content=b''),\n                            self.serv.recv())\n        self.assertMsgEqual(Lwm2mUpdate(self.DEFAULT_REGISTER_ENDPOINT, content=b''),\n                            self.serv.recv())\n        self.start_download()\n\n        # registration temporarily held due to ongoing download\n        self.handle_get(self.serv.recv())\n        # and only after handling the GET, it can be sent finally\n        self.assertDemoRegisters(lifetime=self.LIFETIME)\n        self.handle_get(self.serv.recv())\n        self.handle_get(self.serv.recv())\n\n        self.assertEqual(self.read_state(), UpdateState.DOWNLOADED)\n\n\nclass FirmwareDownloadSameSocketDontCare(SameSocketDownload.Test):\n    def runTest(self):\n        self.start_download()\n        self.serv.recv()  # recv GET and ignore it\n\n\nclass FirmwareDownloadSameSocketSuspendDueToOffline(SameSocketDownload.Test):\n    ACK_TIMEOUT = 1.5\n\n    def runTest(self):\n        self.start_download()\n        self.serv.recv()  # ignore first GET\n        self.communicate('enter-offline')\n        with self.assertRaises(socket.timeout):\n            self.serv.recv(timeout_s=2 * self.ACK_TIMEOUT)\n        self.serv.reset()\n        self.communicate('exit-offline')\n\n        # demo will resume DTLS session without sending any LwM2M messages\n        self.serv.listen()\n\n        for _ in range(self.num_blocks()):\n            self.handle_get(self.serv.recv())\n\n        self.assertEqual(self.read_state(), UpdateState.DOWNLOADED)\n\n\nclass FirmwareDownloadSameSocketSuspendDueToOfflineDuringUpdate(\n    SameSocketDownload.Test):\n    ACK_TIMEOUT = 1.5\n\n    def runTest(self):\n        self.start_download()\n        self.serv.recv()  # ignore first GET\n        self.communicate('send-update', match_regex=re.escape('Update sent'))\n        # actual Update message will not arrive due to NSTART\n        with self.serv.fake_close():\n            self.communicate('enter-offline')\n            self.wait_until_socket_count(expected=0, timeout_s=5)\n        self.serv.reset()\n        self.communicate('exit-offline')\n        # demo will resume DTLS session before sending Register\n        self.serv.listen()\n        self.assertDemoUpdatesRegistration()\n\n        for _ in range(self.num_blocks()):\n            self.handle_get(self.serv.recv())\n\n        self.assertEqual(self.read_state(), UpdateState.DOWNLOADED)\n\n\nclass FirmwareDownloadSameSocketSuspendDueToOfflineDuringUpdateNoMessagesCheck(\n    SameSocketDownload.Test):\n    ACK_TIMEOUT = 1.5\n\n    def runTest(self):\n        # Note: This test is almost identical to the one above, but does not close the socket\n        # during the offline period. This is to check that the client does not attempt to send any\n        # packets during that time. With the bug that triggered the addition of these test cases,\n        # these were two distinct code flow paths.\n\n        self.start_download()\n        self.serv.recv()  # ignore first GET\n        self.communicate('send-update', match_regex=re.escape('Update sent'))\n        # actual Update message will not arrive due to NSTART\n        self.communicate('enter-offline')\n        with self.assertRaises(socket.timeout):\n            self.serv.recv(timeout_s=2 * self.ACK_TIMEOUT)\n        self.serv.reset()\n        self.communicate('exit-offline')\n        # demo will resume DTLS session before sending Update\n        self.serv.listen()\n        self.assertDemoUpdatesRegistration()\n\n        for _ in range(self.num_blocks()):\n            self.handle_get(self.serv.recv())\n\n        self.assertEqual(self.read_state(), UpdateState.DOWNLOADED)\n\n\nclass FirmwareDownloadSameSocketAndBootstrap(SameSocketDownload.Test):\n    def setUp(self):\n        super().setUp(bootstrap_server=True)\n\n    def tearDown(self):\n        super().tearDown(deregister_servers=[self.new_server])\n\n    def runTest(self):\n        self.start_download()\n        self.serv.recv()  # recv GET and ignore it\n\n        self.execute_resource(self.serv,\n                              oid=OID.Server,\n                              iid=2,\n                              rid=RID.Server.RequestBootstrapTrigger)\n\n        self.assertDemoRequestsBootstrap()\n\n        self.new_server = Lwm2mServer()\n        self.write_resource(self.bootstrap_server,\n                            oid=OID.Security,\n                            iid=2,\n                            rid=RID.Security.ServerURI,\n                            content=bytes('coap://127.0.0.1:%d' % self.new_server.get_listen_port(),\n                                          'ascii'))\n        self.write_resource(self.bootstrap_server,\n                            oid=OID.Security,\n                            iid=2,\n                            rid=RID.Security.Mode,\n                            content=str(coap.server.SecurityMode.NoSec.value).encode())\n        req = Lwm2mBootstrapFinish()\n        self.bootstrap_server.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.bootstrap_server.recv())\n\n        self.assertDemoRegisters(self.new_server)\n        self.assertEqual(self.read_state(self.new_server), UpdateState.IDLE)\n\n\nclass FirmwareDownloadSameSocketInterruptedByReboot(FirmwareUpdate.DemoArgsExtractorMixin,\n                                                    SameSocketDownload.Test):\n    def runTest(self):\n        self.start_download()\n\n        for i in range(self.num_blocks()):\n            self.handle_get(self.serv.recv(timeout_s=5))\n            if i == 0:\n                dl_req = self.serv.recv(timeout_s=5)\n                self.demo_process.kill()\n                self.serv.reset()\n                self._start_demo(self.cmdline_args)\n                self.assertDemoRegisters()\n\n        self.assertEqual(self.read_state(), UpdateState.DOWNLOADED)\n\n\nclass FirmwareDownloadSameSocketInterruptedByRebootTwoServers(FirmwareUpdate.DemoArgsExtractorMixin,\n                                                              SameSocketDownload.Test):\n    def setUp(self):\n        super().setUp(servers=2, extra_cmdline_args=[\n            '--access-entry', '/%d/0,1,%d' % (OID.FirmwareUpdate,\n                                              AccessMask.OWNER),\n            '--access-entry', '/%d/0,2,%d' % (OID.FirmwareUpdate, AccessMask.OWNER)])\n\n    def runTest(self):\n        assert self.serv == self.servers[0]\n\n        self.start_download()\n        self.handle_get(self.serv.recv(timeout_s=5))\n        dl_req = self.serv.recv(timeout_s=5)\n\n        self.demo_process.kill()\n        self.servers[0].reset()\n        self.servers[1].reset()\n        self._start_demo(self.cmdline_args)\n\n        # demo will resume all DTLS sessions before sending Register\n        self.servers[0].listen()\n        self.servers[1].listen()\n        self.assertDemoRegisters(self.servers[1])\n        # Still not registered with server=0, but the download is \"already\n        # started\"\n        self.assertEqual(self.read_state(\n            self.servers[1]), UpdateState.DOWNLOADING)\n        self.assertDemoRegisters(self.servers[0])\n\n        for _ in range(self.num_blocks() - 1):\n            self.handle_get(self.serv.recv(timeout_s=5))\n\n        self.assertEqual(self.read_state(), UpdateState.DOWNLOADED)\n\n\nclass FirmwareDownloadSameSocketInterruptedByRebootTwoServersFail(\n    FirmwareUpdate.DemoArgsExtractorMixin,\n    SameSocketDownload.Test):\n    def setUp(self):\n        super().setUp(servers=2, extra_cmdline_args=[\n            '--access-entry', '/%d/0,1,%d' % (OID.FirmwareUpdate,\n                                              AccessMask.OWNER),\n            '--access-entry', '/%d/0,2,%d' % (OID.FirmwareUpdate, AccessMask.OWNER)])\n\n    def runTest(self):\n        assert self.serv == self.servers[0]\n\n        self.start_download()\n        self.handle_get(self.serv.recv(timeout_s=5))\n        dl_req = self.serv.recv(timeout_s=5)\n\n        self.demo_process.kill()\n        self.servers[0].reset()\n        self.servers[1].reset()\n        self._start_demo(self.cmdline_args)\n\n        # demo will resume all DTLS sessions before sending Register\n        self.servers[0].listen()\n        self.servers[1].listen()\n        self.assertDemoRegisters(self.servers[1])\n        # Still not registered with server=0, but the download is \"already\n        # started\"\n        self.assertEqual(self.read_state(\n            self.servers[1]), UpdateState.DOWNLOADING)\n\n        # After 5 min, should stop trying.\n        self.advance_demo_time(5 * 60 + 1)\n        # Anjay checks server state every 1 second or so let's have some leeway\n        time.sleep(3)\n\n        self.assertEqual(self.read_result(\n            self.servers[1]), UpdateResult.CONNECTION_LOST)\n        self.assertEqual(self.read_state(self.servers[1]), UpdateState.IDLE)\n\n    def tearDown(self):\n        super().tearDown(deregister_servers=[self.servers[1]])\n\n\nclass FirmwareDownloadSameSocketInterruptedByRebootTwoServersCancel(\n    FirmwareUpdate.DemoArgsExtractorMixin,\n    SameSocketDownload.Test):\n    def setUp(self):\n        super().setUp(servers=2, extra_cmdline_args=[\n            '--access-entry', '/%d/0,1,%d' % (OID.FirmwareUpdate,\n                                              AccessMask.OWNER),\n            '--access-entry', '/%d/0,2,%d' % (OID.FirmwareUpdate, AccessMask.OWNER)])\n\n    def runTest(self):\n        assert self.serv == self.servers[0]\n\n        self.start_download()\n        self.handle_get(self.serv.recv(timeout_s=5))\n        dl_req = self.serv.recv(timeout_s=5)\n\n        self.demo_process.kill()\n        self.servers[0].reset()\n        self.servers[1].reset()\n        self._start_demo(self.cmdline_args)\n\n        # demo will resume all DTLS sessions before sending Register\n        self.servers[0].listen()\n        self.servers[1].listen()\n        self.assertDemoRegisters(self.servers[1])\n        # Still not registered with server=0, but the download is \"already\n        # started\"\n        self.assertEqual(self.read_state(\n            self.servers[1]), UpdateState.DOWNLOADING)\n\n        self.write_resource(self.servers[1],\n                            oid=OID.FirmwareUpdate,\n                            iid=0,\n                            rid=RID.FirmwareUpdate.Package,\n                            content=b'\\x00',\n                            format=coap.ContentFormat.APPLICATION_OCTET_STREAM)\n\n        self.assertEqual(self.read_result(\n            self.servers[1]), UpdateResult.INITIAL)\n        self.assertEqual(self.read_state(self.servers[1]), UpdateState.IDLE)\n        self.assertDemoRegisters(self.servers[0])\n\n\nclass FirmwareDownloadSameSocketResumptionTimeoutSocket(\n        FirmwareUpdate.CoapDownloaderRetryMixIn,\n        SameSocketDownload.Test):\n    def setUp(self):\n        super().setUp(extra_cmdline_args=['--max-retransmit', '1'])\n\n    def runTest(self):\n        self.start_download()\n\n        dl_req_get = self.serv.recv()\n        self.handle_get(dl_req_get)\n\n        # second block request\n        dl_req_get = self.serv.recv()\n\n        if self.read_log_until_match(\n                regex=re.escape(b'download failed: timeout'),\n                timeout_s=60) is None:\n            raise self.failureException('string not found')\n\n        # receive CoAP retransmission\n        dl_req_get_ret = self.serv.recv()\n        self.assertEqual(dl_req_get.token, dl_req_get_ret.token)\n\n        # receive retransmission related to our mechanism\n        dl_req_get = self.serv.recv()\n        self.handle_get(dl_req_get)\n\n        # last request\n        dl_req_get = self.serv.recv()\n        self.handle_get(dl_req_get)\n\n        self.assertEqual(self.read_state(), UpdateState.DOWNLOADED)\n\n\nclass FirmwareDownloadSameSocketResumptionDownloadAbortInduceByOtherOperation(\n        test_suite.PcapEnabledTest,\n        FirmwareUpdate.CoapDownloaderRetryMixIn,\n        SameSocketDownload.Test):\n    ACK_TIMEOUT = 1.5\n\n    def setUp(self):\n        super().setUp(extra_cmdline_args=['--max-retransmit', '1'])\n\n    def runTest(self):\n        self.start_download()\n\n        dl_req_get = self.serv.recv()\n        self.handle_get(dl_req_get)\n\n        deadline = time.time() + 5\n        while deadline > time.time():\n            num_initial_dtls_hs_packets = self.count_dtls_client_hello_packets()\n            if num_initial_dtls_hs_packets == 5:\n                break\n        else:\n            self.fail('wrong dtls handshake packets count')\n\n        # second block request\n        dl_req_get = self.serv.recv()\n\n        if self.read_log_until_match(\n                regex=re.escape(b'download failed: timeout'),\n                timeout_s=60) is None:\n            raise self.failureException('string not found')\n        \n        # receive CoAP retransmission\n        dl_req_get_ret = self.serv.recv()\n        self.assertEqual(dl_req_get.token, dl_req_get_ret.token)\n\n        # get ICMP error while sending update, download should also be aborted\n        self.serv.close()\n        self.communicate('send-update')\n\n        # Wait for ICMP port unreachable. One second timeout is enough as we\n        # should get the ICMP response right after we try to send a update.\n        self.wait_until_icmp_unreachable_count(1, timeout_s=1)\n\n        time.sleep(self.coap_downloader_retry_delay * 2)\n        # Ensure that no more retransmissions occurred.\n        self.assertEqual(1, self.count_icmp_unreachable_packets())\n        # Ensure that no more handashakes occurred.\n        self.assertEqual(num_initial_dtls_hs_packets,\n                         self.count_dtls_client_hello_packets())\n\n        # Ensure that the control is given back to the user.\n        self.assertTrue(self.get_all_connections_failed())\n\n    def tearDown(self):\n        super().tearDown(auto_deregister=False)\n\nclass FirmwareDownloadSameSocketResumptionCloseSocket(\n        test_suite.PcapEnabledTest,\n        FirmwareUpdate.CoapDownloaderRetryMixIn,\n        SameSocketDownload.Test):\n    ACK_TIMEOUT = 1.5\n\n    def setUp(self):\n        super().setUp(extra_cmdline_args=['--max-retransmit', '1'])\n\n    def runTest(self):\n        self.start_download()\n\n        dl_req_get = self.serv.recv()\n        self.handle_get(dl_req_get)\n\n        deadline = time.time() + 5\n        while deadline > time.time():\n            num_initial_dtls_hs_packets = self.count_dtls_client_hello_packets()\n            if num_initial_dtls_hs_packets == 5:\n                break\n        else:\n            self.fail('wrong dtls handshake packets count')\n\n        self.serv.close()\n\n        # Wait for ICMP port unreachable.\n        # Anjay will send a message to the server after ACK_TIMEOUT has passed\n        # so we add +1 sec. to prevent race conditions. After Anjay sends\n        # the re-transmission it will recieve a ICMP response because the server\n        # already closed.\n        self.wait_until_icmp_unreachable_count(1, timeout_s=(self.ACK_TIMEOUT + 1))\n\n        time.sleep(self.coap_downloader_retry_delay * 2)\n        # Ensure that no more retransmissions occurred.\n        self.assertEqual(1, self.count_icmp_unreachable_packets())\n        # Ensure that no more handashakes occurred.\n        self.assertEqual(num_initial_dtls_hs_packets,\n                         self.count_dtls_client_hello_packets())\n\n        # Ensure that the control is given back to the user.\n        self.assertTrue(self.get_all_connections_failed())\n\n    def tearDown(self):\n        super().tearDown(auto_deregister=False)\n\n\nclass FirmwareUpdateHttpRequestTimeoutTest(FirmwareUpdate.TestWithPartialDownload,\n                                           FirmwareUpdate.TestWithHttpServer):\n    CHUNK_SIZE = 500\n    RESPONSE_DELAY = 0.5\n    TCP_REQUEST_TIMEOUT = 5\n\n    def setUp(self):\n        super().setUp(extra_cmdline_args=['--fwu-tcp-request-timeout',\n                                          str(self.TCP_REQUEST_TIMEOUT)])\n\n    def runTest(self):\n        self.provide_response()\n\n        # Write /5/0/1 (Firmware URI)\n        self.write_firmware_uri_expect_success(self.get_firmware_uri())\n\n        self.wait_for_half_download()\n        # Change RESPONSE_DELAY so that the server stops responding\n        self.RESPONSE_DELAY = self.TCP_REQUEST_TIMEOUT + 5\n\n        half_download_time = time.time()\n        self.wait_until_state_is(UpdateState.IDLE, timeout_s=self.TCP_REQUEST_TIMEOUT + 5)\n        fail_time = time.time()\n        self.assertEqual(self.read_update_result(), UpdateResult.CONNECTION_LOST)\n\n        self.assertAlmostEqual(fail_time, half_download_time + self.TCP_REQUEST_TIMEOUT, delta=1.5)\n\n\nclass FirmwareUpdateHttpRequestTimeoutTest20sec(FirmwareUpdateHttpRequestTimeoutTest):\n    TCP_REQUEST_TIMEOUT = 20\n"
  },
  {
    "path": "tests/integration/suites/default/firmware_update11.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\nimport os\n\nfrom framework.lwm2m_test import *\nfrom framework.create_package import PackageForcedError, make_firmware_package\n\nfrom .block_write import Block, equal_chunk_splitter\nfrom .firmware_update import FirmwareUpdate, UpdateState, UpdateResult\n\nclass FirmwareUpdateModuleWithLwM2MResourcesBase:\n    def fwUpdateLwM2M11OptionalResDisabled(self):\n        self.skipIfFeatureStatus('ANJAY_WITH_MODULE_FW_UPDATE_V11_RESOURCES = OFF', 'FW Object LwM2M 1.1 optional resources disabled')\n\n    def setUp(self, extra_cmdline_args=[], *args, **kwargs):\n        self.fwUpdateLwM2M11OptionalResDisabled()\n\n        super().setUp(extra_cmdline_args=extra_cmdline_args, *args, **kwargs)\n\n\nclass UpdateSeverity:\n    CRITICAL = 0\n    MANDATORY = 1\n    OPTIONAL = 2\n\n\nclass FirmwareUpdateCancelDuringIdleTest(FirmwareUpdate.Test):\n    def runTest(self):\n        # Execute /5/0/10 (Cancel)\n        req = Lwm2mExecute(ResPath.FirmwareUpdate.Cancel)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mErrorResponse.matching(req)(code=coap.Code.RES_METHOD_NOT_ALLOWED),\n                            self.serv.recv())\n\n        self.assertEqual(UpdateState.IDLE, self.read_state())\n        self.assertEqual(UpdateResult.INITIAL, self.read_update_result())\n\n\nclass FirmwareUpdateCancelDuringDownloadingTest(FirmwareUpdate.TestWithPartialDownload,\n                                                FirmwareUpdate.TestWithHttpServer):\n    RESPONSE_DELAY = 0.5\n    CHUNK_SIZE = 3000\n\n    def runTest(self):\n        self.provide_response()\n\n        # Write /5/0/1 (Firmware URI)\n        self.write_firmware_uri_expect_success(self.get_firmware_uri())\n\n        self.wait_for_half_download()\n\n        # Execute /5/0/10 (Cancel)\n        req = Lwm2mExecute(ResPath.FirmwareUpdate.Cancel)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n\n        self.assertEqual(UpdateState.IDLE, self.read_state())\n        self.assertEqual(UpdateResult.CANCELLED, self.read_update_result())\n\n\nclass FirmwareUpdateCancelDuringDownloadedTest(FirmwareUpdate.TestWithHttpServer):\n    def runTest(self):\n        self.provide_response()\n        self.write_firmware_and_wait_for_download(self.get_firmware_uri())\n\n        # Execute /5/0/10 (Cancel)\n        req = Lwm2mExecute(ResPath.FirmwareUpdate.Cancel)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n\n        self.assertEqual(UpdateState.IDLE, self.read_state())\n        self.assertEqual(UpdateResult.CANCELLED, self.read_update_result())\n\n\nclass FirmwareUpdateCancelDuringUpdatingTest(FirmwareUpdate.TestWithHttpServer):\n    # Don't run the downloaded package to be able to process Cancel\n    FW_PKG_OPTS = {\n        \"force_error\": PackageForcedError.Firmware.DoNothing\n    }\n\n    def setUp(self):\n        super().setUp()\n        self.set_reset_machine(False)\n\n    def runTest(self):\n        self.provide_response()\n        self.write_firmware_and_wait_for_download(self.get_firmware_uri())\n\n        # Execute /5/0/2 (Update)\n        self.perform_firmware_update_expect_success()\n\n        # Execute /5/0/10 (Cancel)\n        req = Lwm2mExecute(ResPath.FirmwareUpdate.Cancel)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mErrorResponse.matching(req)(code=coap.Code.RES_METHOD_NOT_ALLOWED),\n                            self.serv.recv())\n\n\nclass FirmwareUpdateCancelAndUpdateAgainTest(FirmwareUpdate.TestWithPartialDownload,\n                                             FirmwareUpdate.TestWithHttpServer):\n    RESPONSE_DELAY = 0.5\n    CHUNK_SIZE = 3000\n\n    def setUp(self):\n        super().setUp()\n        self.set_check_marker(True)\n        self.set_auto_deregister(False)\n        self.set_reset_machine(False)\n\n    def runTest(self):\n        self.provide_response()\n\n        # Write /5/0/1 (Firmware URI)\n        self.write_firmware_uri_expect_success(self.get_firmware_uri())\n\n        self.wait_for_half_download()\n\n        # Execute /5/0/10 (Cancel)\n        req = Lwm2mExecute(ResPath.FirmwareUpdate.Cancel)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n\n        self.assertEqual(UpdateState.IDLE, self.read_state())\n        self.assertEqual(UpdateResult.CANCELLED, self.read_update_result())\n\n        self.provide_response()\n        self.write_firmware_and_wait_for_download(self.get_firmware_uri())\n\n        # Execute /5/0/2 (Update) again\n        self.perform_firmware_update_expect_success()\n\n\nclass FirmwareUpdateMaxDeferPeriodInvalidValueTest(FirmwareUpdateModuleWithLwM2MResourcesBase,\n                                                   FirmwareUpdate.Test):\n    def runTest(self):\n        # Write /5/0/13 (Maximum Defer Period)\n        req = Lwm2mWrite(ResPath.FirmwareUpdate.MaxDeferPeriod, b'-5')\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mErrorResponse.matching(req)(coap.Code.RES_BAD_REQUEST),\n                            self.serv.recv())\n\n\nclass FirmwareUpdateMaxDeferPeriodValidValueTest(FirmwareUpdateModuleWithLwM2MResourcesBase,\n                                                 FirmwareUpdate.Test):\n    def runTest(self):\n        for max_defer_period_value in [b'0', b'30']:\n            # Write /5/0/13 (Maximum Defer Period)\n            req = Lwm2mWrite(ResPath.FirmwareUpdate.MaxDeferPeriod, max_defer_period_value)\n            self.serv.send(req)\n            self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.serv.recv())\n\n\nclass FirmwareUpdateWithDefer(FirmwareUpdateModuleWithLwM2MResourcesBase,\n                              Block.Test):\n    def runTest(self):\n        with open(os.path.join(self.config.demo_path, self.config.demo_cmd), 'rb') as f:\n            firmware = f.read()\n\n        # Write /5/0/13 (Maximum Defer Period)\n        req = Lwm2mWrite(ResPath.FirmwareUpdate.MaxDeferPeriod, b'30')\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n\n        # Write /5/0/0 (Firmware)\n        self.block_send(firmware,\n                        equal_chunk_splitter(chunk_size=1024),\n                        force_error=PackageForcedError.Firmware.Defer)\n\n        # Execute /5/0/2 (Update)\n        self.perform_firmware_update_expect_success()\n\n        # perform_upgrade handler is called via scheduler, so there is a small\n        # window during which reading the Firmware Update State still returns\n        # Updating. Wait for a while for State to actually change.\n        observed_states = []\n        deadline = time.time() + 5  # arbitrary limit\n        while not observed_states or observed_states[-1] == str(UpdateState.UPDATING):\n            if time.time() > deadline:\n                self.fail('Firmware Update did not finish on time, last state = %s' % (\n                    observed_states[-1] if observed_states else 'NONE'))\n            observed_states.append(\n                self.read_path(self.serv, ResPath.FirmwareUpdate.State).content.decode())\n            time.sleep(0.5)\n\n        self.assertNotEqual([], observed_states)\n        self.assertEqual(observed_states[-1], str(UpdateState.DOWNLOADED))\n        self.assertEqual(self.read_path(self.serv, ResPath.FirmwareUpdate.UpdateResult).content,\n                         str(UpdateResult.DEFERRED).encode())\n\n\nclass FirmwareUpdateSeverityWriteInvalidValueTest(FirmwareUpdateModuleWithLwM2MResourcesBase, FirmwareUpdate.Test):\n    def runTest(self):\n        for invalid_severity in [b'-1', b'3']:\n            # Write /5/0/11 (Severity)\n            req = Lwm2mWrite(ResPath.FirmwareUpdate.Severity, invalid_severity)\n            self.serv.send(req)\n            self.assertMsgEqual(Lwm2mErrorResponse.matching(req)(coap.Code.RES_BAD_REQUEST),\n                                self.serv.recv())\n\n\nclass FirmwareUpdateSeverityWriteValidValueTest(FirmwareUpdateModuleWithLwM2MResourcesBase, FirmwareUpdate.Test):\n    def runTest(self):\n        valid_severity_values = [\n            UpdateSeverity.CRITICAL,\n            UpdateSeverity.MANDATORY,\n            UpdateSeverity.OPTIONAL\n        ]\n        for severity in [str(i).encode() for i in valid_severity_values]:\n            # Write /5/0/11 (Severity)\n            req = Lwm2mWrite(ResPath.FirmwareUpdate.Severity, severity)\n            self.serv.send(req)\n            self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.serv.recv())\n\n\nclass FirmwareUpdateSeverityReadTest(FirmwareUpdateModuleWithLwM2MResourcesBase,\n                                     FirmwareUpdate.Test):\n    def runTest(self):\n        # Read default /5/0/11 (Severity)\n        req = Lwm2mRead(ResPath.FirmwareUpdate.Severity)\n        self.serv.send(req)\n        self.assertMsgEqual(\n            Lwm2mContent.matching(req)(content=str(UpdateSeverity.MANDATORY).encode()),\n            self.serv.recv())\n\n\nclass FirmwareUpdateLastStateChangeTime:\n    class Test:\n        def observe_state(self):\n            # Observe /5/0/3 (State)\n            observe_req = Lwm2mObserve('/%d/0/%d' % (OID.FirmwareUpdate, RID.FirmwareUpdate.State))\n            self.serv.send(observe_req)\n            self.assertMsgEqual(Lwm2mContent.matching(observe_req)(), self.serv.recv())\n            return observe_req.token\n\n        def cancel_observe_state(self, token):\n            cancel_req = Lwm2mObserve('/%d/0/%d' % (OID.FirmwareUpdate, RID.FirmwareUpdate.State),\n                                      observe=1, token=token)\n            self.serv.send(cancel_req)\n            self.assertMsgEqual(Lwm2mContent.matching(cancel_req)(), self.serv.recv())\n\n        def get_states_and_timestamp(self, token, deadline=None):\n            # Receive a notification from /5/0/3 and read /5/0/12\n            notification_responses = [self.serv.recv(deadline=deadline)]\n            self.assertMsgEqual(Lwm2mNotify(token), notification_responses[0])\n\n            read_response = self.read_path(self.serv, ResPath.FirmwareUpdate.LastStateChangeTime,\n                                           deadline=deadline)\n            while True:\n                try:\n                    notification_responses.append(\n                        self.serv.recv(timeout_s=0,\n                                       filter=lambda pkt: isinstance(pkt, Lwm2mNotify)))\n                except socket.timeout:\n                    break\n            return [r.content.decode() for r in\n                    notification_responses], read_response.content.decode()\n\n\nclass FirmwareUpdateLastStateChangeTimeWithDelayedSuccessTest(FirmwareUpdateModuleWithLwM2MResourcesBase,\n                                                              Block.Test,\n                                                              FirmwareUpdateLastStateChangeTime.Test):\n    def runTest(self):\n        with open(os.path.join(self.config.demo_path, self.config.demo_cmd), 'rb') as f:\n            firmware = f.read()\n\n        observe_token = self.observe_state()\n\n        # Write /5/0/0 (Firmware)\n        self.block_send(firmware,\n                        equal_chunk_splitter(chunk_size=1024),\n                        force_error=PackageForcedError.Firmware.DelayedSuccess)\n\n        _, before_update_timestamp = self.get_states_and_timestamp(observe_token)\n\n        time.sleep(1)\n        # Execute /5/0/2 (Update)\n        self.perform_firmware_update_expect_success()\n\n        state_notification = self.serv.recv()\n        self.assertMsgEqual(Lwm2mNotify(observe_token), state_notification)\n\n        self.serv.reset()\n        self.assertDemoRegisters()\n\n        req = Lwm2mRead(ResPath.FirmwareUpdate.LastStateChangeTime)\n        self.serv.send(req)\n        after_update_response = self.serv.recv()\n        self.assertMsgEqual(Lwm2mContent.matching(req)(), after_update_response)\n        all_timestamps = [before_update_timestamp, after_update_response.content.decode()]\n        self.assertEqual(all_timestamps, sorted(all_timestamps))\n\n\nclass FirmwareUpdateLastStateChangeTimeWithDeferTest(FirmwareUpdateModuleWithLwM2MResourcesBase,\n                                                     Block.Test,\n                                                     FirmwareUpdateLastStateChangeTime.Test):\n    def observe_after_update(self, token):\n        observed_states = []\n        observed_timestamps = []\n        deadline = time.time() + 5  # arbitrary limit\n        while not observed_states or observed_states[-1] == str(UpdateState.UPDATING):\n            states, timestamp = self.get_states_and_timestamp(token, deadline=deadline)\n            observed_states += states\n            observed_timestamps.append(timestamp)\n        self.assertNotEqual([], observed_timestamps)\n        return observed_timestamps\n\n    def runTest(self):\n        with open(os.path.join(self.config.demo_path, self.config.demo_cmd), 'rb') as f:\n            firmware = f.read()\n\n        # Write /5/0/13 (Maximum Defer Period)\n        req = Lwm2mWrite(ResPath.FirmwareUpdate.MaxDeferPeriod, b'30')\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.serv.recv())\n\n        observe_token = self.observe_state()\n\n        # Write /5/0/0 (Firmware)\n        self.block_send(firmware,\n                        equal_chunk_splitter(chunk_size=1024),\n                        force_error=PackageForcedError.Firmware.Defer)\n\n        _, after_write_timestamp = self.get_states_and_timestamp(observe_token)\n\n        time.sleep(1)\n        # Execute /5/0/2 (Update)\n        self.perform_firmware_update_expect_success()\n\n        observed_timestamps = self.observe_after_update(observe_token)\n\n        all_timestamps = [after_write_timestamp] + observed_timestamps\n        self.assertEqual(all_timestamps, sorted(all_timestamps))\n\n        self.cancel_observe_state(observe_token)\n\n\nclass FirmwareUpdateSeverityPersistenceTest(FirmwareUpdateModuleWithLwM2MResourcesBase,\n                                            FirmwareUpdate.Test):\n    def restart(self):\n        self.teardown_demo_with_servers()\n        self.setup_demo_with_servers(\n            fw_updated_marker_path=self.ANJAY_MARKER_FILE)\n\n    def runTest(self):\n        severity_values = [\n            UpdateSeverity.CRITICAL,\n            UpdateSeverity.MANDATORY,\n            UpdateSeverity.OPTIONAL\n        ]\n        for severity in [str(i).encode() for i in severity_values]:\n            # Write /5/0/11 (Severity)\n            req = Lwm2mWrite(ResPath.FirmwareUpdate.Severity, severity)\n            self.serv.send(req)\n            self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.serv.recv())\n\n            req = Lwm2mWrite(ResPath.FirmwareUpdate.Package,\n                             make_firmware_package(self.FIRMWARE_SCRIPT_CONTENT),\n                             format=coap.ContentFormat.APPLICATION_OCTET_STREAM)\n            self.serv.send(req)\n            self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.serv.recv())\n            self.restart()\n\n            req = Lwm2mRead(ResPath.FirmwareUpdate.Severity)\n            self.serv.send(req)\n            res = self.serv.recv()\n            self.assertMsgEqual(Lwm2mContent.matching(req)(), res)\n\n            self.assertEqual(severity, res.content)\n            self.restart()\n\n\nclass FirmwareUpdateDeadlinePersistenceTest(FirmwareUpdateModuleWithLwM2MResourcesBase,\n                                            FirmwareUpdate.DemoArgsExtractorMixin,\n                                            Block.Test):\n    def get_deadline(self):\n        return int(self.communicate('get-fw-update-deadline',\n                                    match_regex='FW_UPDATE_DEADLINE==([0-9]+)\\n').group(1))\n\n    def runTest(self):\n        with open(os.path.join(self.config.demo_path, self.config.demo_cmd), 'rb') as f:\n            firmware = f.read()\n\n        # Write /5/0/13 (Maximum Defer Period)\n        req = Lwm2mWrite(ResPath.FirmwareUpdate.MaxDeferPeriod, b'30')\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n\n        # Write /5/0/0 (Firmware)\n        self.block_send(firmware,\n                        equal_chunk_splitter(chunk_size=1024),\n                        force_error=PackageForcedError.Firmware.Defer)\n\n        # Execute /5/0/2 (Update)\n        self.perform_firmware_update_expect_success()\n\n        # perform_upgrade handler is called via scheduler, so there is a small\n        # window during which reading the Firmware Update State still returns\n        # Updating. Wait for a while for State to actually change.\n        observed_states = []\n        deadline = time.time() + 5  # arbitrary limit\n        while not observed_states or observed_states[-1] == str(UpdateState.UPDATING):\n            if time.time() > deadline:\n                self.fail('Firmware Update did not finish on time, last state = %s' % (\n                    observed_states[-1] if observed_states else 'NONE'))\n            observed_states.append(\n                self.read_path(self.serv, ResPath.FirmwareUpdate.State).content.decode())\n            time.sleep(0.5)\n\n        self.assertNotEqual([], observed_states)\n        self.assertEqual(observed_states[-1], str(UpdateState.DOWNLOADED))\n\n        saved_deadline = self.get_deadline()\n\n        self.demo_process.kill()\n        self.serv.reset()\n        self._start_demo(self.cmdline_args)\n        self.assertDemoRegisters()\n\n        restored_deadline = self.get_deadline()\n\n        self.assertEqual(saved_deadline, restored_deadline)\n"
  },
  {
    "path": "tests/integration/suites/default/forbidden_on_register.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nfrom framework.lwm2m_test import *\nfrom framework.test_utils import *\nfrom framework_tools.lwm2m.coap.server import SecurityMode\n\nfrom . import bootstrap_client as bs\n\nimport socket\n\nclass Test:\n    PSK_KEY=b'key'\n    PSK_IDENTITY=b'identity'\n\n    class ClientReceivesForbiddenOnRegisterMixin:\n        def setUp(self):\n            self.setup_demo_with_servers(servers=1, auto_register=False)\n\n        def tearDown(self):\n            self.teardown_demo_with_servers(auto_deregister=False)\n\n        def runTest(self):\n            register_pkt = self.assertDemoRegisters(self.serv, respond=False)\n            self.serv.send(Lwm2mErrorResponse.matching(register_pkt)(code=coap.Code.RES_FORBIDDEN))\n            # Client shouldn't even retry\n            with self.assertRaises(socket.timeout):\n                self.serv.recv()\n\n\n    class ClientSessionRevokedMixin(test_suite.Lwm2mDmOperations):\n        def tearDown(self):\n            self.teardown_demo_with_servers(auto_deregister=False)\n\n        def runTest(self):\n            # Trigger update.\n            self.communicate('send-update')\n            update_pkt = self.assertDemoUpdatesRegistration(self.serv, respond=False)\n            # Respond to it with 4.00 Bad Request to simulate some kind of client account expiration on server side.\n            self.serv.send(Lwm2mErrorResponse.matching(update_pkt)(code=coap.Code.RES_BAD_REQUEST))\n            # This should cause client attempt to re-register.\n            register_pkt = self.assertDemoRegisters(self.serv, respond=False)\n            # To which we respond with 4.03 Forbidden, finishing off the communication.\n            self.serv.send(Lwm2mErrorResponse.matching(register_pkt)(code=coap.Code.RES_FORBIDDEN))\n            # Client shouldn't even retry\n            with self.assertRaises(socket.timeout):\n                self.serv.recv()\n\n    class ClientReceivesForbiddenOnRegisterAndFallbacksToBootstrapMixin(bs.BootstrapTest.Test):\n        def perform_bootstrap(self):\n            raise NotImplemented('To be implemented by deriving class')\n\n        def runTest(self):\n            self.perform_bootstrap()\n            # Demo tries to register.\n            register_pkt = self.assertDemoRegisters(self.serv, respond=False)\n            # To which we respond with 4.03 Forbidden.\n            self.serv.send(Lwm2mErrorResponse.matching(register_pkt)(code=coap.Code.RES_FORBIDDEN))\n            # Client should fallback to client initiated bootstrap\n            if self.bootstrap_server.security_mode() != 'nosec':\n                self.assertDtlsReconnect(self.bootstrap_server)\n            self.assertDemoRequestsBootstrap()\n            # Client shouldn't even retry to re-register\n            with self.assertRaises(socket.timeout):\n                self.serv.recv()\n\n        def tearDown(self):\n            self.teardown_demo_with_servers(auto_deregister=False)\n\n\n    class ClientSessionRevokedAndFallbackToBootstrapMixin(bs.BootstrapTest.Test):\n        def perform_bootstrap(self):\n            raise NotImplemented('To be implemented by deriving class')\n\n        def runTest(self):\n            self.perform_bootstrap()\n            # Demo normally registers.\n            self.assertDemoRegisters(self.serv)\n            # Trigger update.\n            self.communicate('send-update')\n            update_pkt = self.assertDemoUpdatesRegistration(self.serv, respond=False)\n            # Respond to it with 4.00 Bad Request to simulate some kind of client account expiration on server side.\n            self.serv.send(Lwm2mErrorResponse.matching(update_pkt)(code=coap.Code.RES_BAD_REQUEST))\n            # This should cause client attempt to re-register.\n            register_pkt = self.assertDemoRegisters(self.serv, respond=False)\n            # To which we respond with 4.03 Forbidden, finishing off the communication.\n            self.serv.send(Lwm2mErrorResponse.matching(register_pkt)(code=coap.Code.RES_FORBIDDEN))\n            # Client should fallback to client initiated bootstrap\n            self.assertDemoRequestsBootstrap()\n            # Client shouldn't even retry to re-register\n            with self.assertRaises(socket.timeout):\n                self.serv.recv()\n\n        def tearDown(self):\n            self.teardown_demo_with_servers(auto_deregister=False)\n\n\nclass ClientReceivesForbiddenOnRegister(Test.ClientReceivesForbiddenOnRegisterMixin,\n                                        test_suite.Lwm2mSingleServerTest):\n    pass\n\n\nclass DtlsClientReceivesForbiddenOnRegister(Test.ClientReceivesForbiddenOnRegisterMixin,\n                                            test_suite.Lwm2mDtlsSingleServerTest):\n    pass\n\n\nclass ClientSessionRevoked(Test.ClientSessionRevokedMixin,\n                           test_suite.Lwm2mSingleServerTest):\n    pass\n\n\nclass DtlsClientSessionRevoked(Test.ClientSessionRevokedMixin,\n                               test_suite.Lwm2mDtlsSingleServerTest):\n    pass\n\n\nclass ClientReceivesForbiddenOnRegisterAndFallbacksToBootstrap(Test.ClientReceivesForbiddenOnRegisterAndFallbacksToBootstrapMixin):\n    def perform_bootstrap(self):\n        self.perform_typical_bootstrap(server_iid=1,\n                                       security_iid=2,\n                                       server_uri='coap://127.0.0.1:%d' % self.serv.get_listen_port(),\n                                       lifetime=86400)\n\n\nclass DtlsClientReceivesForbiddenOnRegisterAndFallbacksToBootstrap(Test.ClientReceivesForbiddenOnRegisterAndFallbacksToBootstrapMixin):\n    def perform_bootstrap(self):\n        self.perform_typical_bootstrap(server_iid=1,\n                                       security_iid=2,\n                                       server_uri='coaps://127.0.0.1:%d' % self.serv.get_listen_port(),\n                                       secure_identity=Test.PSK_IDENTITY,\n                                       secure_key=Test.PSK_KEY,\n                                       security_mode=SecurityMode.PreSharedKey,\n                                       lifetime=86400)\n\n    def setUp(self):\n        super().setUp(servers=[Lwm2mServer(coap.DtlsServer(psk_key=Test.PSK_KEY, psk_identity=Test.PSK_IDENTITY))],\n                      num_servers_passed=0)\n\n\nclass ClientSessionRevokedAndFallbackToBootstrap(Test.ClientSessionRevokedAndFallbackToBootstrapMixin):\n    def perform_bootstrap(self):\n        self.perform_typical_bootstrap(server_iid=1,\n                                       security_iid=2,\n                                       server_uri='coap://127.0.0.1:%d' % self.serv.get_listen_port(),\n                                       lifetime=86400)\n\n\nclass DtlsClientSessionRevokedAndFallbackToBootstrap(Test.ClientSessionRevokedAndFallbackToBootstrapMixin):\n    def perform_bootstrap(self):\n        self.perform_typical_bootstrap(server_iid=1,\n                                       security_iid=2,\n                                       server_uri='coaps://127.0.0.1:%d' % self.serv.get_listen_port(),\n                                       secure_identity=Test.PSK_IDENTITY,\n                                       secure_key=Test.PSK_KEY,\n                                       security_mode=SecurityMode.PreSharedKey,\n                                       lifetime=86400)\n    def setUp(self):\n        super().setUp(servers=[Lwm2mServer(coap.DtlsServer(psk_key=Test.PSK_KEY, psk_identity=Test.PSK_IDENTITY))],\n                      num_servers_passed=0)\n"
  },
  {
    "path": "tests/integration/suites/default/formats.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nfrom framework.lwm2m_test import *\nfrom framework.test_utils import *\n\n\nclass WriteRequest:\n    def __init__(self, value, instance=1, resource=None, error=coap.Code.RES_BAD_REQUEST):\n        self.resource = resource\n        self.instance = instance\n        self.value = value\n        self.error = error\n\n\nclass FormatTest:\n    # From RFC7252, section 12.3 \"CoAP Content-Format Registry\":\n    #\n    # \"The identifiers between 65000 and 65535 inclusive are reserved for\n    #  experiments.\"\n    UNSUPPORTED_FORMAT = 65000\n\n    COMMON_BAD_WRITE_SCENARIOS = [\n        WriteRequest(resource=RID.Test.ResInt, value=b'not-an-int'),\n        WriteRequest(resource=RID.Test.ResBool, value=b'not-a-bool'),\n        WriteRequest(resource=RID.Test.ResFloat, value=b'not-a-float'),\n        WriteRequest(resource=RID.Test.ResObjlnk, value=b'not-an-objlnk'),\n    ]\n\n    class Test(test_suite.Lwm2mSingleServerTest,\n               test_suite.Lwm2mDmOperations):\n        def setUp(self):\n            super().setUp()\n            self.create_instance(self.serv, oid=OID.Test, iid=1)\n\n        def run_write_scenario(self, format, extra_scenarios=[]):\n            for scenario in FormatTest.COMMON_BAD_WRITE_SCENARIOS + extra_scenarios:\n                if scenario.resource is None:\n                    self.write_instance(self.serv, oid=OID.Test, iid=scenario.instance,\n                                        format=format, content=scenario.value,\n                                        expect_error_code=scenario.error)\n                else:\n                    self.write_resource(self.serv, oid=OID.Test, iid=1,\n                                        rid=scenario.resource, format=format,\n                                        content=scenario.value,\n                                        expect_error_code=scenario.error)\n\n\nclass BadFormatOnRequestTest(FormatTest.Test):\n    def runTest(self):\n        self.write_resource(self.serv, oid=OID.FirmwareUpdate, iid=0, rid=RID.FirmwareUpdate.PackageURI,\n                            format=FormatTest.UNSUPPORTED_FORMAT,\n                            expect_error_code=coap.Code.RES_UNSUPPORTED_CONTENT_FORMAT)\n\n\nclass BadFormatOnResponseTest(FormatTest.Test):\n    def runTest(self):\n        self.read_resource(self.serv, oid=OID.Device, iid=0, rid=RID.Device.Manufacturer,\n                           accept=FormatTest.UNSUPPORTED_FORMAT,\n                           expect_error_code=coap.Code.RES_NOT_ACCEPTABLE)\n\n\nclass PlaintextWritesTest(FormatTest.Test):\n    def runTest(self):\n        self.run_write_scenario(format=coap.ContentFormat.TEXT_PLAIN,\n                                extra_scenarios=[\n                                    # Writing the string/bytes isn't really interesting. Let's at least try with an empty payload.\n                                    WriteRequest(\n                                        resource=RID.Test.ResString, value=b'', error=None),\n                                    WriteRequest(\n                                        resource=RID.Test.ResRawBytes, value=b'', error=None),\n                                    # Internal buffer for storing objlnk is sizeof(\"65535:65535\") bytes only, let's overflow it.\n                                    WriteRequest(\n                                        resource=RID.Test.ResObjlnk, value=b'x'*32),\n                                    # Write on instance? This isn't the right format.\n                                    WriteRequest(value=b'nothing really'),\n                                    WriteRequest(value=b''),\n                                ])\n\n\nclass OpaqueWritesTest(FormatTest.Test):\n    def runTest(self):\n        # Almost nothing is supported for Opaque content format.\n        self.run_write_scenario(format=coap.ContentFormat.APPLICATION_OCTET_STREAM,\n                                extra_scenarios=[\n                                    WriteRequest(\n                                        resource=RID.Test.ResString, value=b''),\n                                    # Basically the only thing that'd work with opaque contexts.\n                                    WriteRequest(\n                                        resource=RID.Test.ResRawBytes, value=b'', error=None),\n                                    # Write on instance? This isn't the right format.\n                                    WriteRequest(value=b'nothing really'),\n                                    WriteRequest(value=b''),\n                                ])\n\n\nclass TlvWritesTest(FormatTest.Test):\n    def runTest(self):\n        self.run_write_scenario(format=coap.ContentFormat.APPLICATION_LWM2M_TLV,\n                                extra_scenarios=[\n                                    WriteRequest(\n                                        resource=RID.Test.ResString, value=b''),\n                                    WriteRequest(\n                                        resource=RID.Test.ResRawBytes, value=b''),\n                                    # Empty write on instance does nothing, but at least it succeeds for TLV contexts.\n                                    WriteRequest(value=b'', error=None)\n                                ])\n\n\nclass TlvInstanceWriteMethodNotAllowedTest(FormatTest.Test):\n    def runTest(self):\n        self.write_instance(self.serv, OID.Server, 1,\n                            TLV.make_resource(RID.Server.ShortServerID, 42).serialize(),\n                            partial=True, expect_error_code=coap.Code.RES_METHOD_NOT_ALLOWED)\n\n\nclass UnsupportedFormatWritesTest(FormatTest.Test):\n    def runTest(self):\n        for rid in (RID.Test.ResInt,\n                    RID.Test.ResBool,\n                    RID.Test.ResFloat,\n                    RID.Test.ResObjlnk,\n                    RID.Test.ResRawBytes):\n            for content in (b'whatever', b''):\n                self.write_resource(self.serv, oid=OID.Test, iid=1, rid=rid,\n                                    format=FormatTest.UNSUPPORTED_FORMAT,\n                                    content=content,\n                                    expect_error_code=coap.Code.RES_UNSUPPORTED_CONTENT_FORMAT)\n\n\nclass NonTlvAndNonJsonCreate(FormatTest.Test):\n    def runTest(self):\n        # Note: iid=1 is already taken away, by parent's setUp().\n        IID = 2\n\n        for known_format in (coap.ContentFormat.TEXT_PLAIN,\n                             coap.ContentFormat.APPLICATION_OCTET_STREAM,\n                             coap.ContentFormat.APPLICATION_CBOR):\n            self.create_instance_with_arbitrary_payload(self.serv, oid=OID.Test, iid=IID,\n                                                        format=known_format,\n                                                        expect_error_code=coap.Code.RES_BAD_REQUEST)\n\n        self.create_instance_with_arbitrary_payload(self.serv, oid=OID.Test, iid=IID,\n                                                    format=FormatTest.UNSUPPORTED_FORMAT,\n                                                    expect_error_code=coap.Code.RES_UNSUPPORTED_CONTENT_FORMAT)\n\n\nclass TlvRIDIncompatibleWithPathRID(FormatTest.Test):\n    def runTest(self):\n        self.write_resource(self.serv, oid=OID.Test, iid=1, rid=RID.Test.Counter,\n                            format=coap.ContentFormat.APPLICATION_LWM2M_TLV,\n                            content=TLV.make_resource(\n                                resource_id=RID.Test.ResBytesSize, content=123).serialize(),\n                            expect_error_code=coap.Code.RES_BAD_REQUEST)\n\n\nclass PreferredHierarchicalContentFormat_1_0(test_suite.Lwm2mSingleServerTest,\n                                             test_suite.Lwm2mDmOperations):\n    def setUp(self):\n        super().setUp(extra_cmdline_args=['--prefer-hierarchical-formats'])\n\n    def runTest(self):\n        self.assertEqual(coap.ContentFormat.APPLICATION_LWM2M_TLV,\n                         self.read_instance(self.serv, oid=OID.Device, iid=0).get_content_format())\n\n\nclass PreferredHierarchicalContentFormat_1_1(test_suite.Lwm2mSingleServerTest,\n                                             test_suite.Lwm2mDmOperations):\n    def setUp(self):\n        super().setUp(extra_cmdline_args=['--prefer-hierarchical-formats'],\n                      minimum_version='1.1',\n                      maximum_version='1.1')\n\n    def runTest(self):\n        self.assertEqual(coap.ContentFormat.APPLICATION_LWM2M_SENML_CBOR,\n                         self.read_instance(self.serv, oid=OID.Device, iid=0).get_content_format())\n"
  },
  {
    "path": "tests/integration/suites/default/hierarchical_cbor_encoding.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nimport cbor2\nfrom framework.lwm2m_test import *\nfrom framework.test_utils import *\n\nIID = 1\n\nSENML_CBOR_BASE_NAME_LABEL = -2\nSENML_CBOR_NAME_LABEL = 0\nSENML_CBOR_VALUE_LABEL = 2\nSENML_CBOR_STRING_VALUE_LABEL = 3\nSENML_CBOR_BOOLEAN_VALUE_LABEL = 4\nSENML_CBOR_DATA_VALUE_LABEL = 8\nSENML_CBOR_OBJECT_LINK_LABEL = \"vlo\"\n\n\nclass HierarchicalCborEncodingTest:\n    class Test(test_suite.Lwm2mSingleServerTest,\n               test_suite.Lwm2mDmOperations):\n        def setUp(self):\n            super().setUp()\n            self.create_instance(self.serv, oid=OID.Test, iid=IID)\n\n\ndef as_cbor(pkt):\n    return cbor2.loads(pkt.content)\n\n\ndef get_element(obj, name, label):\n    base_name = ''\n    for e in obj:\n        base_name = e.get(SENML_CBOR_BASE_NAME_LABEL, base_name)\n        sub_name = e.get(SENML_CBOR_NAME_LABEL, '')\n\n        if base_name + sub_name == name:\n            read_value = e[label]\n            return read_value\n\n\ndef path_to_string(path):\n    if isinstance(path, tuple) or isinstance(path, list):\n        result = \"\"\n        for p in path:\n            result += \"/\" + str(p)\n        return result\n    else:\n        return \"/\" + str(path)\n\n\n# flatten_dict adapted from\n# https://www.geeksforgeeks.org/python-convert-nested-dictionary-into-flattened-dictionary/\n\n\ndef flatten_dict(dd, prefix=''):\n    return {prefix + k: v\n            for kk, vv in dd.items()\n            for k, v in flatten_dict(vv, path_to_string(kk)).items()\n            } if isinstance(dd, dict) else {prefix: dd}\n\n\nclass SenmlCborEncoding:\n    format = coap.ContentFormat.APPLICATION_LWM2M_SENML_CBOR\n\n    def value_extractor(self, res, element_name, label):\n        return get_element(res, element_name, label)\n\n\nclass Lwm2mCborEncoding:\n    format = coap.ContentFormat.APPLICATION_LWM2M_CBOR\n\n    def value_extractor(self, res, element_name, label):\n        res = flatten_dict(res)\n        return res[element_name]\n\n\nclass BaseReadInteger:\n    class TestMixin(HierarchicalCborEncodingTest.Test):\n        def runTest(self):\n            assigned_value = 1234\n            self.write_resource(self.serv, oid=OID.Test, iid=IID, rid=RID.Test.ResInt,\n                                content=str(assigned_value))\n\n            res = as_cbor(\n                self.read_object(\n                    self.serv,\n                    oid=OID.Test,\n                    accept=self.format))\n            element_name = ResPath.Test[IID].ResInt\n\n            read_value = self.value_extractor(\n                res, element_name, SENML_CBOR_VALUE_LABEL)\n            self.assertEqual(assigned_value, read_value)\n\n\nclass BaseReadUnsignedInteger:\n    class TestMixin(HierarchicalCborEncodingTest.Test):\n        def runTest(self):\n            assigned_value = 5678\n            self.write_resource(self.serv, oid=OID.Test, iid=IID, rid=RID.Test.ResUnsignedInt,\n                                content=str(assigned_value))\n\n            res = as_cbor(\n                self.read_object(\n                    self.serv,\n                    oid=OID.Test,\n                    accept=self.format))\n            element_name = ResPath.Test[IID].ResUnsignedInt\n\n            read_value = self.value_extractor(\n                res, element_name, SENML_CBOR_VALUE_LABEL)\n            self.assertEqual(assigned_value, read_value)\n\n\nclass BaseReadFloat:\n    class TestMixin(HierarchicalCborEncodingTest.Test):\n        def runTest(self):\n            assigned_value = 1.5\n            self.write_resource(self.serv, oid=OID.Test, iid=IID, rid=RID.Test.ResFloat,\n                                content=str(assigned_value))\n\n            res = as_cbor(\n                self.read_object(\n                    self.serv,\n                    oid=OID.Test,\n                    accept=self.format))\n            element_name = ResPath.Test[IID].ResFloat\n\n            read_value = self.value_extractor(\n                res, element_name, SENML_CBOR_VALUE_LABEL)\n            self.assertEqual(assigned_value, read_value)\n\n\nclass BaseReadDouble:\n    class TestMixin(HierarchicalCborEncodingTest.Test):\n        def runTest(self):\n            assigned_value = 1.1\n            self.write_resource(self.serv, oid=OID.Test, iid=IID, rid=RID.Test.ResDouble,\n                                content=str(assigned_value))\n\n            res = as_cbor(\n                self.read_object(\n                    self.serv,\n                    oid=OID.Test,\n                    accept=self.format))\n            element_name = ResPath.Test[IID].ResDouble\n\n            read_value = self.value_extractor(\n                res, element_name, SENML_CBOR_VALUE_LABEL)\n            self.assertEqual(assigned_value, read_value)\n\n\nclass BaseReadBool:\n    class TestMixin(HierarchicalCborEncodingTest.Test):\n        def runTest(self):\n            assigned_value = True\n            self.write_resource(self.serv, oid=OID.Test, iid=IID, rid=RID.Test.ResBool,\n                                content=str(int(assigned_value)))\n\n            res = as_cbor(\n                self.read_object(\n                    self.serv,\n                    oid=OID.Test,\n                    accept=self.format))\n            element_name = ResPath.Test[IID].ResBool\n\n            read_value = self.value_extractor(\n                res, element_name, SENML_CBOR_BOOLEAN_VALUE_LABEL)\n            self.assertEqual(assigned_value, read_value)\n\n\nclass BaseReadObjectLink:\n    class TestMixin(HierarchicalCborEncodingTest.Test):\n        def runTest(self):\n            assigned_value = '33605:1'\n            self.write_resource(self.serv, oid=OID.Test, iid=IID, rid=RID.Test.ResObjlnk,\n                                content=assigned_value)\n\n            res = as_cbor(\n                self.read_object(\n                    self.serv,\n                    oid=OID.Test,\n                    accept=self.format))\n            element_name = ResPath.Test[IID].ResObjlnk\n\n            read_value = self.value_extractor(\n                res, element_name, SENML_CBOR_OBJECT_LINK_LABEL)\n            self.assertEqual(assigned_value, read_value)\n\n\nclass BaseReadOpaque:\n    class TestMixin(HierarchicalCborEncodingTest.Test):\n        def runTest(self):\n            assigned_value = b'losie, jelenie, sarny, dziki, lisy, borsuki, kuny, jenoty, wilki i rysie'\n            self.write_resource(self.serv, oid=OID.Test, iid=IID, rid=RID.Test.ResRawBytes,\n                                content=assigned_value,\n                                format=coap.ContentFormat.APPLICATION_OCTET_STREAM)\n\n            res = as_cbor(\n                self.read_object(\n                    self.serv,\n                    oid=OID.Test,\n                    accept=self.format))\n            element_name = ResPath.Test[IID].ResRawBytes\n\n            read_value = self.value_extractor(\n                res, element_name, SENML_CBOR_DATA_VALUE_LABEL)\n            self.assertEqual(assigned_value, read_value)\n\n\nclass BaseReadString:\n    class TestMixin(HierarchicalCborEncodingTest.Test):\n        def runTest(self):\n            assigned_value = 'z ptakow: kuropatwy, bazanty, dzikie kaczki, gesi, lyski, bekasy i cietrzewie'\n            self.write_resource(self.serv, oid=OID.Test, iid=IID, rid=RID.Test.ResString,\n                                content=assigned_value)\n\n            res = as_cbor(\n                self.read_object(\n                    self.serv,\n                    oid=OID.Test,\n                    accept=self.format))\n            element_name = ResPath.Test[IID].ResString\n\n            read_value = self.value_extractor(\n                res, element_name, SENML_CBOR_STRING_VALUE_LABEL)\n            self.assertEqual(assigned_value, read_value)\n\n\nclass BaseReadSingleInstance:\n    class TestMixin(HierarchicalCborEncodingTest.Test):\n        def runTest(self):\n            assigned_value = 1234\n            self.write_resource(self.serv, oid=OID.Test, iid=IID, rid=RID.Test.ResInt,\n                                content=str(assigned_value))\n\n            res = as_cbor(self.read_instance(self.serv, oid=OID.Test, iid=IID,\n                                             accept=self.format))\n            element_name = ResPath.Test[IID].ResInt\n            read_value = self.value_extractor(\n                res, element_name, SENML_CBOR_VALUE_LABEL)\n            self.assertEqual(assigned_value, read_value)\n\n            element_name = ResPath.Test[IID].ResBool\n            read_value = self.value_extractor(\n                res, element_name, SENML_CBOR_BOOLEAN_VALUE_LABEL)\n            self.assertEqual(False, read_value)\n\n\nclass BaseReadSingleResource:\n    class TestMixin(HierarchicalCborEncodingTest.Test):\n        def runTest(self):\n            assigned_value = 1.5\n            self.write_resource(self.serv, oid=OID.Test, iid=IID, rid=RID.Test.ResFloat,\n                                content=str(assigned_value))\n\n            res = as_cbor(self.read_resource(self.serv, oid=OID.Test, iid=IID, rid=RID.Test.ResFloat,\n                                             accept=self.format))\n            element_name = ResPath.Test[IID].ResFloat\n            read_value = self.value_extractor(\n                res, element_name, SENML_CBOR_VALUE_LABEL)\n            self.assertEqual(assigned_value, read_value)\n\n\nclass BaseReadMultipleResource:\n    class TestMixin(HierarchicalCborEncodingTest.Test):\n        def runTest(self):\n            values = {}\n            import random\n            for i in range(9):\n                values[i] = random.randint(0, 2**31)\n\n            execute_args = ','.join(\"%d='%d'\" % (k, v)\n                                    for k, v in values.items())\n            self.execute_resource(self.serv, oid=OID.Test, iid=IID, rid=RID.Test.ResInitIntArray,\n                                  content=bytes(execute_args, encoding='utf-8'))\n\n            res = as_cbor(self.read_resource(self.serv, oid=OID.Test, iid=IID, rid=RID.Test.IntArray,\n                                             accept=self.format))\n            element_name = '/%d/%d/%d/%d' % (OID.Test,\n                                             IID, RID.Test.IntArray, 0)\n            read_value = self.value_extractor(\n                res, element_name, SENML_CBOR_VALUE_LABEL)\n            self.assertEqual(values[0], read_value)\n\n            element_name = '/%d/%d/%d/%d' % (OID.Test,\n                                             IID, RID.Test.IntArray, 8)\n            read_value = self.value_extractor(\n                res, element_name, SENML_CBOR_VALUE_LABEL)\n            self.assertEqual(values[8], read_value)\n\n\nclass BaseReadComposite:\n    class TestMixin(HierarchicalCborEncodingTest.Test):\n        def runTest(self):\n            assigned_value = 1.5\n            self.write_resource(self.serv, oid=OID.Test, iid=IID, rid=RID.Test.ResFloat,\n                                content=str(assigned_value))\n\n            res = as_cbor(\n                self.read_composite(\n                    self.serv, [\n                        ResPath.Device.Manufacturer, ResPath.Test[IID].ResFloat], accept=self.format))\n\n            element_name = ResPath.Test[IID].ResFloat\n            read_value = self.value_extractor(\n                res, element_name, SENML_CBOR_VALUE_LABEL)\n            self.assertEqual(assigned_value, read_value)\n\n            element_name = ResPath.Device.Manufacturer\n            read_value = self.value_extractor(\n                res, element_name, SENML_CBOR_VALUE_LABEL)\n            self.assertEqual('0023C7', read_value)\n\n\nclass BaseReadCompositeSameResources:\n    class TestMixin(HierarchicalCborEncodingTest.Test):\n        def runTest(self):\n            assigned_value = 1.5\n            self.write_resource(self.serv, oid=OID.Test, iid=IID, rid=RID.Test.ResFloat,\n                                content=str(assigned_value))\n\n            res = as_cbor(\n                self.read_composite(\n                    self.serv, [\n                        ResPath.Test[IID].ResFloat, ResPath.Test[IID].ResFloat], accept=self.format))\n\n            element_name = ResPath.Test[IID].ResFloat\n            read_value = self.value_extractor(\n                res, element_name, SENML_CBOR_VALUE_LABEL)\n            self.assertEqual(assigned_value, read_value)\n\n# SenML CBOR\n\n\nclass SenmlCborEncodingReadInteger(\n        BaseReadInteger.TestMixin, SenmlCborEncoding):\n    pass\n\n\nclass SenmlCborEncodingReadUnsignedInteger(\n        BaseReadUnsignedInteger.TestMixin, SenmlCborEncoding):\n    pass\n\n\nclass SenmlCborEncodingReadFloat(BaseReadFloat.TestMixin, SenmlCborEncoding):\n    pass\n\n\nclass SenmlCborEncodingReadDouble(BaseReadDouble.TestMixin, SenmlCborEncoding):\n    pass\n\n\nclass SenmlCborEncodingReadBool(BaseReadBool.TestMixin, SenmlCborEncoding):\n    pass\n\n\nclass SenmlCborEncodingReadObjectLink(\n        BaseReadObjectLink.TestMixin, SenmlCborEncoding):\n    pass\n\n\nclass SenmlCborEncodingReadOpaque(BaseReadOpaque.TestMixin, SenmlCborEncoding):\n    pass\n\n\nclass SenmlCborEncodingReadString(BaseReadString.TestMixin, SenmlCborEncoding):\n    pass\n\n\nclass SenmlCborEncodingReadSingleInstance(\n        BaseReadSingleInstance.TestMixin, SenmlCborEncoding):\n    pass\n\n\nclass SenmlCborEncodingReadSingleResource(\n        BaseReadSingleResource.TestMixin, SenmlCborEncoding):\n    pass\n\n\nclass SenmlCborEncodingReadMultipleResource(\n        BaseReadMultipleResource.TestMixin, SenmlCborEncoding):\n    pass\n\n\nclass SenmlCborEncodingReadCompositeSameResources(\n        BaseReadCompositeSameResources.TestMixin, SenmlCborEncoding):\n    pass\n\n# LwM2M CBOR\n\nclass Lwm2mCborEncodingReadInteger(\n        BaseReadInteger.TestMixin, Lwm2mCborEncoding):\n    pass\n\n\nclass Lwm2mCborEncodingReadUnsignedInteger(\n        BaseReadUnsignedInteger.TestMixin, Lwm2mCborEncoding):\n    pass\n\n\nclass Lwm2mCborEncodingReadFloat(BaseReadFloat.TestMixin, Lwm2mCborEncoding):\n    pass\n\n\nclass Lwm2mCborEncodingReadDouble(BaseReadDouble.TestMixin, Lwm2mCborEncoding):\n    pass\n\n\nclass Lwm2mCborEncodingReadBool(BaseReadBool.TestMixin, Lwm2mCborEncoding):\n    pass\n\n\nclass Lwm2mCborEncodingReadObjectLink(\n        BaseReadObjectLink.TestMixin, Lwm2mCborEncoding):\n    pass\n\n\nclass Lwm2mCborEncodingReadOpaque(BaseReadOpaque.TestMixin, Lwm2mCborEncoding):\n    pass\n\n\nclass Lwm2mCborEncodingReadString(BaseReadString.TestMixin, Lwm2mCborEncoding):\n    pass\n\n\nclass Lwm2mCborEncodingReadSingleInstance(\n        BaseReadSingleInstance.TestMixin, Lwm2mCborEncoding):\n    pass\n\n\nclass Lwm2mCborEncodingReadSingleResource(\n        BaseReadSingleResource.TestMixin, Lwm2mCborEncoding):\n    pass\n\n\nclass Lwm2mCborEncodingReadMultipleResource(\n        BaseReadMultipleResource.TestMixin, Lwm2mCborEncoding):\n    pass\n\n\nclass Lwm2mCborEncodingReadComposite(\n        BaseReadComposite.TestMixin, Lwm2mCborEncoding):\n    pass\n\n\nclass Lwm2mCborEncodingReadCompositeSameResources(\n        BaseReadCompositeSameResources.TestMixin, Lwm2mCborEncoding):\n    pass\n"
  },
  {
    "path": "tests/integration/suites/default/ipso_objects.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nfrom framework.lwm2m_test import *\n\n\nclass IpsoBasicSensorObjectReadTest(test_suite.Lwm2mSingleServerTest,\n                                    test_suite.Lwm2mDmOperations):\n    def runTest(self):\n        # Read and check the unit\n        unit = (self.read_resource(self.serv, OID.Temperature,\n                0, RID.Temperature.SensorUnits)).content\n        self.assertEqual(unit, b'Cel')\n\n        # Read min and max value which can be measured by the sensor\n        lower_bound = float((self.read_resource(\n            self.serv, OID.Temperature, 0, RID.Temperature.MinRangeValue)).content)\n        upper_bound = float((self.read_resource(\n            self.serv, OID.Temperature, 0, RID.Temperature.MaxRangeValue)).content)\n\n        # Read the sensor value\n        response_1 = float((self.read_resource(\n            self.serv, OID.Temperature, 0, RID.Temperature.SensorValue)).content)\n\n        # Check if the measured value fits in range\n        self.assertGreaterEqual(response_1, lower_bound)\n        self.assertLessEqual(response_1, upper_bound)\n\n        # IPSO sensor objects in demo are refreshed every second, make sure that\n        # the value will be updated\n        time.sleep(2)\n\n        # Read the sensor value\n        response_2 = float((self.read_resource(\n            self.serv, OID.Temperature, 0, RID.Temperature.SensorValue)).content)\n\n        # Check if the measured value fits in range\n        self.assertGreaterEqual(response_2, lower_bound)\n        self.assertLessEqual(response_2, upper_bound)\n\n        # The values of the two consequtive reads should be different\n        # (not in general, but in the case of the demo)\n        self.assertNotEqual(response_1, response_2)\n\n\nclass Ipso3dSensorObjectReadTest(test_suite.Lwm2mSingleServerTest,\n                                 test_suite.Lwm2mDmOperations):\n    def runTest(self):\n        # Read and check the unit\n        unit = (self.read_resource(self.serv, OID.Accelerometer,\n                0, RID.Accelerometer.SensorUnits)).content\n        self.assertEqual(unit, b'm/s2')\n\n        # Read min and max value which can be measured by the sensor\n        lower_bound = float((self.read_resource(\n            self.serv, OID.Accelerometer, 0, RID.Accelerometer.MinRangeValue)).content)\n        upper_bound = float((self.read_resource(\n            self.serv, OID.Accelerometer, 0, RID.Accelerometer.MaxRangeValue)).content)\n\n        # We test all of the axes\n        for rid in [RID.Accelerometer.XValue, RID.Accelerometer.YValue, RID.Accelerometer.ZValue]:\n\n            # Read the sensor value\n            response_1 = float(\n                (self.read_resource(self.serv, OID.Accelerometer, 0, rid)).content)\n\n            # Check if the measured value fits in range\n            self.assertGreaterEqual(response_1, lower_bound)\n            self.assertLessEqual(response_1, upper_bound)\n\n            # IPSO sensor objects in demo are refreshed every second, make sure\n            # that the value will be updated\n            time.sleep(2)\n\n            # Read the sensor value\n            response_2 = float(\n                (self.read_resource(self.serv, OID.Accelerometer, 0, rid)).content)\n\n            # Check if the measured value fits in range\n            self.assertGreaterEqual(response_2, lower_bound)\n            self.assertLessEqual(response_2, upper_bound)\n\n            # The values of the two consequtive reads should be different\n            # (not in general, but in the case of the demo)\n            self.assertNotEqual(response_1, response_2)\n\n\nclass IpsoButtonObjectReadTest(test_suite.Lwm2mSingleServerTest,\n                               test_suite.Lwm2mDmOperations):\n    def runTest(self):\n        # Read and check the unit\n        at = (self.read_resource(self.serv, OID.PushButton,\n              0, RID.PushButton.ApplicationType)).content\n        self.assertEqual(at, b'Fake demo Button')\n\n        # Write Application Type and check if it changes\n        new_button_name = b'New button name'\n        self.write_resource(self.serv, OID.PushButton, 0,\n                            RID.PushButton.ApplicationType, content=new_button_name)\n        at = (self.read_resource(self.serv, OID.PushButton,\n              0, RID.PushButton.ApplicationType)).content\n        self.assertEqual(at, new_button_name)\n\n        # Press and release button several times\n        button_clicks = 9\n        for _ in range(button_clicks):\n            self.communicate(\"push-button-press 0\")\n            self.communicate(\"push-button-release 0\")\n\n        # Check the number of times pressed and state\n        times_pressed = int((self.read_resource(\n            self.serv, OID.PushButton, 0, RID.PushButton.DigitalInputCounter)).content)\n        self.assertEqual(times_pressed, button_clicks)\n        state = int((self.read_resource(self.serv, OID.PushButton,\n                    0, RID.PushButton.DigitalInputState)).content)\n        self.assertEqual(state, 0)\n\n        # Press it one more time\n        self.communicate(\"push-button-press 0\")\n\n        # Check the number of times pressed and state\n        times_pressed = int((self.read_resource(\n            self.serv, OID.PushButton, 0, RID.PushButton.DigitalInputCounter)).content)\n        self.assertEqual(times_pressed, button_clicks+1)\n        state = int((self.read_resource(self.serv, OID.PushButton,\n                    0, RID.PushButton.DigitalInputState)).content)\n        self.assertEqual(state, 1)\n"
  },
  {
    "path": "tests/integration/suites/default/json_encoding.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\nimport json\nimport base64\nfrom framework.lwm2m_test import *\nfrom framework.test_utils import *\n\nfrom . import plaintext_base64 as pb64\n\nIID = 1\n\nclass JsonEncodingTest:\n    class Test(test_suite.Lwm2mSingleServerTest,\n               test_suite.Lwm2mDmOperations):\n        def setUp(self):\n            super().setUp()\n            self.create_instance(self.serv, oid=OID.Test, iid=IID)\n\n\ndef as_json(pkt):\n    return json.loads(pkt.content.decode('utf-8'))\n\n\nclass JsonEncodingBnResource(JsonEncodingTest.Test):\n    def runTest(self):\n        res = as_json(self.read_resource(self.serv, oid=OID.Test, iid=IID, rid=RID.Test.Timestamp,\n                                         accept=coap.ContentFormat.APPLICATION_LWM2M_JSON))\n        self.assertEqual(ResPath.Test[1].Timestamp, res['bn'])\n\n\nclass JsonEncodingBnInstance(JsonEncodingTest.Test):\n    def runTest(self):\n        res = as_json(self.read_instance(self.serv, oid=OID.Test, iid=IID,\n                                         accept=coap.ContentFormat.APPLICATION_LWM2M_JSON))\n        self.assertEqual('/%d/1' % OID.Test, res['bn'])\n\n\nclass JsonEncodingBnObject(JsonEncodingTest.Test):\n    def runTest(self):\n        res = as_json(self.read_object(self.serv, oid=OID.Test,\n                                       accept=coap.ContentFormat.APPLICATION_LWM2M_JSON))\n        self.assertEqual('/%d' % OID.Test, res['bn'])\n\n\nclass JsonEncodingAllNamesAreSlashPrefixed(JsonEncodingTest.Test):\n    def runTest(self):\n        responses = [\n              as_json(self.read_object(self.serv, oid=OID.Test,\n                                       accept=coap.ContentFormat.APPLICATION_LWM2M_JSON)),\n              as_json(self.read_instance(self.serv, oid=OID.Test, iid=IID,\n                                         accept=coap.ContentFormat.APPLICATION_LWM2M_JSON)) ]\n        for response in responses:\n            self.assertTrue(len(response['e']) > 0)\n\n            for resource in response['e']:\n                self.assertEqual('/', resource['n'][0])\n\n        resource = as_json(self.read_resource(self.serv, oid=OID.Test, iid=IID, rid=RID.Test.Timestamp,\n                                              accept=coap.ContentFormat.APPLICATION_LWM2M_JSON))\n        # Resource path is in 'bn', therefore no 'n' parameter is specified by the client\n        self.assertFalse('n' in resource['e'])\n\n\nclass JsonEncodingBytesInBase64(JsonEncodingTest.Test):\n    def runTest(self):\n        some_bytes = pb64.test_object_bytes_generator(51)\n        some_bytes_b64 = base64.encodebytes(some_bytes).replace(b'\\n', b'')\n\n        self.write_resource(self.serv, oid=OID.Test, iid=IID, rid=RID.Test.ResRawBytes, content=some_bytes_b64)\n\n        result = as_json(self.read_resource(self.serv, oid=OID.Test, iid=IID,\n                                            rid=RID.Test.ResRawBytes,\n                                            accept=coap.ContentFormat.APPLICATION_LWM2M_JSON))\n\n        self.assertEqual(some_bytes_b64, bytes(result['e'][0]['sv'], encoding='utf-8'))\n\n\nclass JsonEncodingArrayOfOpaqueValues(JsonEncodingTest.Test):\n    def runTest(self):\n        values = {}\n        import random\n        for i in range(9):\n            values[i] = random.randint(0, 2**31)\n\n        execute_args = ','.join(\"%d='%d'\" % (k, v) for k, v in values.items())\n        self.execute_resource(self.serv, oid=OID.Test, iid=IID, rid=RID.Test.ResInitIntArray,\n                              content=bytes(execute_args, encoding='utf-8'))\n\n        result = as_json(self.read_resource(self.serv, oid=OID.Test, iid=IID,\n                                            rid=RID.Test.ResOpaqueArray,\n                                            accept=coap.ContentFormat.APPLICATION_LWM2M_JSON))\n\n        import struct\n        for instance in result['e']:\n            key = int(instance['n'][1:])\n            expected_bytes = struct.pack('!i', values[key])\n            expected_value = base64.encodebytes(expected_bytes).replace(b'\\n', b'')\n            self.assertEqual(expected_value, bytes(instance['sv'], encoding='utf-8'))\n"
  },
  {
    "path": "tests/integration/suites/default/json_requests.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nimport base64\nimport json\n\nfrom framework.lwm2m_test import *\nfrom framework.test_utils import *\n\nIID = 1\n\n\nclass JsonRequest:\n    class Test(test_suite.Lwm2mSingleServerTest,\n               test_suite.Lwm2mDmOperations):\n\n        def _verify_values(self, entries, expected_value_map):\n            \"\"\"\n            Verifies if the list contains all entries from expected_value_map.\n            Ignores timestamps.\n            \"\"\"\n            path_value = {}\n            basename = ''\n            for entry in entries:\n                basename = entry.get('bn', basename)\n                name = entry.get('n', '')\n                for value_type in ('v', 'vs', 'vd', 'vb', 'vlo'):\n                    if value_type in entry:\n                        path_value[basename + name] = entry[value_type]\n                        break\n\n            for path, value in expected_value_map.items():\n                self.assertIn(path, path_value)\n                self.assertEqual(path_value[path], value)\n\n        def resource_path(self, rid, riid=None):\n            if riid is not None:\n                return '/%d/%d/%d/%d' % (OID.Test, IID, rid, riid)\n            else:\n                return '/%d/%d/%d' % (OID.Test, IID, rid)\n\n        def verify_instance(self, expected_path_value_map={}):\n            res = self.read_instance(self.serv,\n                                     oid=OID.Test,\n                                     iid=IID,\n                                     accept=coap.ContentFormat.APPLICATION_LWM2M_SENML_JSON)\n            self._verify_values(entries=json.loads(res.content.decode()),\n                                expected_value_map=expected_path_value_map)\n\n        def write_resource_payload(self, rid, json_entries, expected_error=None,\n                                   additional_payload=b''):\n            self.write_resource(self.serv,\n                                oid=OID.Test,\n                                iid=IID,\n                                rid=rid,\n                                format=coap.ContentFormat.APPLICATION_LWM2M_SENML_JSON,\n                                content=json.dumps(json_entries).encode(\n                                    'utf-8') + additional_payload,\n                                expect_error_code=expected_error)\n\n        def write_instance_payload(self, json_entries, expected_error=None, additional_payload=b''):\n            self.write_instance(self.serv,\n                                oid=OID.Test,\n                                iid=IID,\n                                format=coap.ContentFormat.APPLICATION_LWM2M_SENML_JSON,\n                                content=json.dumps(json_entries).encode(\n                                    'utf-8') + additional_payload,\n                                expect_error_code=expected_error)\n\n        def setUp(self):\n            super().setUp()\n            self.create_instance(self.serv, oid=OID.Test, iid=IID)\n\n\nclass JsonResourceWrite(JsonRequest.Test):\n    def runTest(self):\n        self.write_resource_payload(RID.Test.ResInt,\n                                    [{\n                                        'v': 123456,\n                                        'n': self.resource_path(RID.Test.ResInt)\n                                    }])\n        self.write_resource_payload(RID.Test.ResDouble,\n                                    [{\n                                        'v': 123456.0,\n                                        'n': self.resource_path(RID.Test.ResDouble)\n                                    }])\n        self.write_resource_payload(RID.Test.ResBool,\n                                    [{\n                                        'vb': True,\n                                        'n': self.resource_path(RID.Test.ResBool)\n                                    }])\n        self.write_resource_payload(RID.Test.ResString,\n                                    [{\n                                        'vs': '12345',\n                                        'n': self.resource_path(RID.Test.ResString)\n                                    }])\n        self.write_resource_payload(RID.Test.ResRawBytes,\n                                    [{\n                                        'vd': base64.urlsafe_b64encode(\n                                            b'\\xff\\xfe\\xfd\\xfc\\xfb\\xfa\\xf9').rstrip(b'=').decode(\n                                            'utf-8'),\n                                        'n': self.resource_path(RID.Test.ResRawBytes)\n                                    }])\n        self.write_resource_payload(RID.Test.ResObjlnk,\n                                    [{\n                                        'vlo': '123:456',\n                                        'n': self.resource_path(RID.Test.ResObjlnk)\n                                    }])\n        self.write_resource_payload(RID.Test.IntArray,\n                                    [{\n                                        'v': 9001,\n                                        'n': self.resource_path(RID.Test.IntArray, 1)\n                                    }])\n        self.verify_instance({\n            self.resource_path(RID.Test.ResInt): 123456,\n            self.resource_path(RID.Test.ResDouble): 123456.0,\n            self.resource_path(RID.Test.ResBool): True,\n            self.resource_path(RID.Test.ResString): \"12345\",\n            self.resource_path(RID.Test.ResRawBytes): base64.urlsafe_b64encode(\n                b'\\xff\\xfe\\xfd\\xfc\\xfb\\xfa\\xf9').rstrip(b'=').decode('utf-8'),\n            self.resource_path(RID.Test.ResObjlnk): '123:456',\n            self.resource_path(RID.Test.IntArray, 1): 9001,\n        })\n\n\nclass JsonInstanceWrite(JsonRequest.Test):\n    def runTest(self):\n        self.write_instance_payload([{\n            'v': 123456,\n            'n': self.resource_path(RID.Test.ResInt)\n        },\n            {\n                'v': 123456.0,\n                'n': self.resource_path(RID.Test.ResDouble)\n            },\n            {\n                'vb': True,\n                'n': self.resource_path(RID.Test.ResBool)\n            },\n            {\n                'vs': '12345',\n                'n': self.resource_path(RID.Test.ResString)\n            },\n            {\n                'vd': base64.urlsafe_b64encode(b'\\xff\\xfe\\xfd\\xfc\\xfb\\xfa\\xf9').rstrip(b'=').decode(\n                    'utf-8'),\n                'n': self.resource_path(RID.Test.ResRawBytes)\n            },\n            {\n                'vlo': '123:456',\n                'n': self.resource_path(RID.Test.ResObjlnk)\n            },\n            {\n                'v': 9001,\n                'n': self.resource_path(RID.Test.IntArray, 1)\n            },\n            {\n                'v': 9002,\n                'n': self.resource_path(RID.Test.IntArray, 2)\n            }])\n\n        self.verify_instance({\n            self.resource_path(RID.Test.ResInt): 123456,\n            self.resource_path(RID.Test.ResDouble): 123456.0,\n            self.resource_path(RID.Test.ResBool): True,\n            self.resource_path(RID.Test.ResString): \"12345\",\n            self.resource_path(RID.Test.ResRawBytes): base64.urlsafe_b64encode(\n                b'\\xff\\xfe\\xfd\\xfc\\xfb\\xfa\\xf9').rstrip(b'=').decode('utf-8'),\n            self.resource_path(RID.Test.ResObjlnk): '123:456',\n            self.resource_path(RID.Test.IntArray, 1): 9001,\n            self.resource_path(RID.Test.IntArray, 2): 9002,\n        })\n\n\nclass JsonResourceWriteWithBasenameAllDivisions(JsonRequest.Test):\n    def runTest(self):\n        path = self.resource_path(RID.Test.ResInt)\n        for i in range(len(path) + 1):\n            payload = {'v': 42}\n            basename, name = path[:i], path[i:]\n            if len(basename):\n                payload['bn'] = basename\n            if len(name):\n                payload['n'] = name\n\n            self.write_resource_payload(RID.Test.ResInt, [payload])\n            self.verify_instance({path: 42})\n\n\nclass JsonInstanceWriteBasenameEffectOnNextResources(JsonRequest.Test):\n    def runTest(self):\n        payload = [\n            # First resource without a basename.\n            {\n                'vs': \"1234\",\n                'n': self.resource_path(RID.Test.ResString),\n            },\n            # Second resource with basename included.\n            {\n                'v': 42,\n                'n': \"%d\" % RID.Test.ResInt,\n                'bn': \"/%d/%d/\" % (OID.Test, IID)\n            },\n            # Third resource shall already be affected by the basename.\n            {\n                'v': 47.0,\n                'n': \"%d\" % RID.Test.ResDouble,\n            },\n        ]\n        self.write_instance_payload(payload)\n        self.verify_instance({\n            self.resource_path(RID.Test.ResString): \"1234\",\n            self.resource_path(RID.Test.ResInt): 42,\n            self.resource_path(RID.Test.ResDouble): 47.0\n        })\n\n\nclass JsonWriteBasenamePointsToOtherObject(JsonRequest.Test):\n    def runTest(self):\n        payload = [{\n            'vs': \"1234\",\n            'n': self.resource_path(RID.Test.ResString),\n            'bn': \"/2048\",\n        }]\n        self.write_instance_payload(payload, coap.Code.RES_BAD_REQUEST)\n        self.write_resource_payload(RID.Test.ResString, payload, coap.Code.RES_BAD_REQUEST)\n\n\nclass JsonWriteBasenameTooNested(JsonRequest.Test):\n    def runTest(self):\n        # Path too nested.\n        payload = [{\n            'v': 42,\n            'n': self.resource_path(RID.Test.ResInt),\n            'bn': '/%d/1' % (OID.Test,),\n        }]\n        self.write_instance_payload(payload, coap.Code.RES_BAD_REQUEST)\n        self.write_resource_payload(RID.Test.ResInt, payload, coap.Code.RES_BAD_REQUEST)\n\n\nclass JsonWriteBasenameItemTooLong(JsonRequest.Test):\n    def runTest(self):\n        payload = [{\n            'v': 42,\n            'n': self.resource_path(RID.Test.ResInt),\n            'bn': '/133777',\n        }]\n        self.write_instance_payload(payload, coap.Code.RES_BAD_REQUEST)\n        self.write_resource_payload(RID.Test.ResInt, payload, coap.Code.RES_BAD_REQUEST)\n\n\nclass JsonWriteBasenameUnrelated(JsonRequest.Test):\n    def runTest(self):\n        self.create_instance(self.serv, oid=OID.Test, iid=IID + 1)\n        # Path unrelated - request is made on /33605/IID but basename points to /33605/(IID+1)\n        payload = [{\n            'v': 42,\n            'n': '%d' % RID.Test.ResInt,\n            'bn': '/%d/%d/' % (OID.Test, IID + 1),\n        }]\n        self.write_instance_payload(payload, coap.Code.RES_BAD_REQUEST)\n        self.write_resource_payload(RID.Test.ResInt, payload, coap.Code.RES_BAD_REQUEST)\n\n\nclass JsonWriteWithUnknownDataAtTheEnd(JsonRequest.Test):\n    def runTest(self):\n        payload = [{\n            'v': 42,\n            'n': self.resource_path(RID.Test.ResInt),\n        }]\n        self.write_instance_payload(payload,\n                                    expected_error=coap.Code.RES_BAD_REQUEST,\n                                    additional_payload=b'stuff')\n        self.write_resource_payload(RID.Test.ResInt,\n                                    payload,\n                                    expected_error=coap.Code.RES_BAD_REQUEST,\n                                    additional_payload=b'more stuff')\n\n\nclass JsonAttemptWritingValueToInstance(JsonRequest.Test):\n    def runTest(self):\n        payload = [{\n            'n': '/%d/%d' % (OID.Test, IID),\n            'v': 42\n        }]\n        self.write_instance_payload(payload, coap.Code.RES_BAD_REQUEST)\n\n\nclass JsonAttemptWritingValueToMultipleResource(JsonRequest.Test):\n    def runTest(self):\n        payload = [{\n            'n': '/%d/%d/%d' % (OID.Test, IID, RID.Test.IntArray),\n            'v': 42\n        }]\n        self.write_instance_payload(payload, coap.Code.RES_BAD_REQUEST)\n\n\nclass JsonAttemptWritingNull(JsonRequest.Test):\n    def runTest(self):\n        payload = [{\n            'n': self.resource_path(RID.Test.ResInt),\n            'v': None\n        }]\n        self.write_resource_payload(RID.Test.ResInt, payload, coap.Code.RES_BAD_REQUEST)\n"
  },
  {
    "path": "tests/integration/suites/default/lwm2m_gateway.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\nfrom framework.lwm2m_test import *\nfrom . import register\nfrom . import access_control\nfrom .access_control import AccessMask\nfrom .bootstrap_client import BootstrapTest\nfrom .send import Send\nfrom .notifications import ConfirmableNotificationStatus\nfrom framework_tools.lwm2m.tlv import TLVType\nfrom framework.test_utils import *\nimport json\n\n\nclass Gateway:\n    class Base:\n        def gatewayDisabled(self):\n            self.skipIfFeatureStatus('ANJAY_WITH_LWM2M_GATEWAY = OFF', 'LwM2M Gateway disabled')\n\n        def setUp(self, extra_cmdline_args=[], *args, **kwargs):\n            self.gatewayDisabled()\n\n            extra_cmdline_args += [\"-g\"]\n            super().setUp(extra_cmdline_args=extra_cmdline_args, *args, **kwargs)\n\n        def assertInstances(self, expected_instances):\n            res = self.read_object(self.serv, OID.Lwm2mGateway)\n            tlv = TLV.parse(res.content)\n            self.assertEqual(len(tlv), len(expected_instances))\n            for instance in tlv:\n                self.assertEqual(instance.tlv_type, TLVType.INSTANCE)\n                expected_instances.remove(instance.identifier)\n            self.assertEqual(len(expected_instances), 0)\n\n        def extractPrefixes(self, server):\n            res = self.read_object(\n                server, OID.Lwm2mGateway, accept=coap.ContentFormat.APPLICATION_LWM2M_TLV)\n            self.assertEqual(coap.ContentFormat.APPLICATION_LWM2M_TLV,\n                             res.get_content_format())\n            tlv = TLV.parse(res.content)\n            prefixes = []\n            for instance in tlv:\n                resources = instance.value\n                for resource in resources:\n                    if resource.identifier == 1:\n                        prefixes.append(resources[1].value.decode())\n\n            self.assertEqual(len(prefixes), 2)\n            self.assertNotEqual(prefixes[0], prefixes[1])\n            return prefixes\n\n    class BaseWithRegister(Base, register.RegisterTest):\n        def setUp(self, *args, **kwargs):\n            super().setUp(*args, **kwargs)\n            self.extra_objects.append(Lwm2mResourcePathHelper.from_rid_object(\n                RID.Lwm2mGateway, oid=OID.Lwm2mGateway, multi_instance=True, version='2.0'))\n\n    class ObservationStatus(BaseWithRegister):\n        OBSER_EXISTS_WO_ATTR = (True, 0, -1)\n        OBSER_NOT_EXISTS = (False, 0, -1)\n\n        def setUp(self, *args, **kwargs):\n            version = '1.0'\n            if 'minimum_version' in kwargs and 'maximum_version' in kwargs:\n                version = '1.2'\n            super().setUp(*args, **kwargs)\n\n            self.assertDemoRegisters(version=version)\n            self.prefixes = self.extractPrefixes(self.serv)\n            self.path_gw1_res1 = \"/%s/%d/%d/%d\" % (\n                self.prefixes[0], OID.Temperature, 0, RID.Temperature.SensorValue)\n            self.path_gw1_res2 = \"/%s/%d/%d/%d\" % (\n                self.prefixes[0], OID.Temperature, 0, RID.Temperature.ApplicationType)\n            self.path_gw1_res3 = \"/%s/%d/%d/%d\" % (\n                self.prefixes[0], OID.Temperature, 1, RID.Temperature.SensorValue)\n            self.path_gw1_res4 = \"/%s/%d/%d/%d\" % (\n                self.prefixes[0], OID.PushButton, 0, RID.PushButton.DigitalInputState)\n            self.path_gw1_inst1 = \"/%s/%d/%d\" % (\n                self.prefixes[0], OID.Temperature, 0)\n            self.path_gw1_obj1 = \"/%s/%d\" % (self.prefixes[0], OID.Temperature)\n            self.path_gw2_res1 = \"/%s/%d/%d/%d\" % (\n                self.prefixes[1], OID.Temperature, 0, RID.Temperature.SensorValue)\n            self.path_gw2_res2 = \"/%s/%d/%d/%d\" % (\n                self.prefixes[1], OID.Temperature, 0, RID.Temperature.MaxMeasuredValue)\n            self.path_res1 = \"/%d/%d/%d\" % (OID.Temperature,\n                                            0, RID.Temperature.SensorValue)\n            self.path_res2 = \"/%d/%d/%d\" % (OID.Temperature,\n                                            0, RID.Temperature.MaxMeasuredValue)\n\n            # nothing is observed in the beginning\n            self.assertTupleEqual(self.observation_status(self.path_res1),\n                                  self.OBSER_NOT_EXISTS)\n            self.assertTupleEqual(self.observation_status(self.path_res2),\n                                  self.OBSER_NOT_EXISTS)\n            self.assertTupleEqual(self.gw_observation_status(self.path_gw1_res1),\n                                  self.OBSER_NOT_EXISTS)\n            self.assertTupleEqual(self.gw_observation_status(self.path_gw1_res2),\n                                  self.OBSER_NOT_EXISTS)\n            self.assertTupleEqual(self.gw_observation_status(self.path_gw1_res3),\n                                  self.OBSER_NOT_EXISTS)\n            self.assertTupleEqual(self.gw_observation_status(self.path_gw1_res4),\n                                  self.OBSER_NOT_EXISTS)\n            self.assertTupleEqual(self.gw_observation_status(self.path_gw2_res1),\n                                  self.OBSER_NOT_EXISTS)\n            self.assertTupleEqual(self.gw_observation_status(self.path_gw2_res2),\n                                  self.OBSER_NOT_EXISTS)\n\n        def tearDown(self):\n            # receive notifications that may have been generated in the meantime\n            while True:\n                try:\n                    self.serv.recv(\n                        timeout_s=0.5, filter=lambda pkt: isinstance(pkt, Lwm2mNotify))\n                except socket.timeout:\n                    break\n            super().tearDown()\n\n        def gw_observation_status(self, path):\n            dev_id = 65535\n            if path.startswith(\"/\" + self.prefixes[0]):\n                dev_id = 0\n            elif path.startswith(\"/\" + self.prefixes[1]):\n                dev_id = 1\n            pos = path.find('/', 1)\n            path = path[pos:]\n            (is_observed, min_period, max_eval_period) = self.communicate(\n                'observation-status %d %s' % (dev_id, path),\n                match_regex=('is_observed == (.*), '\n                             'min_period == (.*), '\n                             'max_eval_period == (.*)\\n')).groups()\n            self.assertIn(is_observed, ('true', 'false'))\n            return (is_observed == 'true', int(min_period), int(max_eval_period))\n\n        def observation_status(self, path):\n            (is_observed, min_period, max_eval_period) = self.communicate(\n                'observation-status %s' % path,\n                match_regex=('is_observed == (.*), '\n                             'min_period == (.*), '\n                             'max_eval_period == (.*)\\n')).groups()\n            self.assertIn(is_observed, ('true', 'false'))\n            return (is_observed == 'true', int(min_period), int(max_eval_period))\n\n    class BaseWithBootstrap(Base, BootstrapTest.TestMixin):\n        def bootstrapDisabled(self):\n            self.skipIfFeatureStatus('ANJAY_WITH_BOOTSTRAP = OFF', 'Bootstrap disabled')\n\n        def setUp(self, extra_cmdline_args=[], *args, **kwargs):\n            self.bootstrapDisabled()\n\n            # extra_cmdline_args += [\"-b\"]\n            super().setUp(extra_cmdline_args, servers=1, num_servers_passed=0, bootstrap_server=True, *args, *kwargs)\n\n\nclass RegisterWithGateway(Gateway.BaseWithRegister):\n    pass\n\n\nclass AddAndRemoveEndDevices(Gateway.BaseWithRegister):\n    def runTest(self):\n        self.assertDemoRegisters(self.serv)\n\n        # demo automatically starts with 2 End Devices\n        self.assertInstances(set([0, 1]))\n\n        # adding same device twice is forbidden\n        self.communicate('gw_register 0')\n        self.assertInstances(set([0, 1]))\n\n        self.communicate('gw_deregister 0')\n        self.assertInstances(set([1]))\n\n        self.communicate('gw_deregister 1')\n        self.assertInstances(set([]))\n\n        self.communicate('gw_register 0')\n        self.assertInstances(set([0]))\n\n        self.communicate('gw_register 1')\n        self.assertInstances(set([0, 1]))\n\n\nclass GatewayPerformBasicOperationsOnGatewayObject(Gateway.BaseWithRegister):\n    def runTest(self):\n        self.assertDemoRegisters(self.serv)\n\n        res = self.read_object(self.serv, OID.Lwm2mGateway)\n        self.assertEqual(coap.ContentFormat.APPLICATION_LWM2M_TLV,\n                         res.get_content_format())\n\n        res = self.read_instance(self.serv, OID.Lwm2mGateway, 0)\n        self.assertEqual(coap.ContentFormat.APPLICATION_LWM2M_TLV,\n                         res.get_content_format())\n\n        res = self.read_resource(self.serv, OID.Lwm2mGateway, 0, 0)\n        self.assertEqual(coap.ContentFormat.TEXT_PLAIN,\n                         res.get_content_format())\n        self.assertEqual(res.content.decode('utf-8'), \"urn:dev:001234\")\n        res = self.read_resource(self.serv, OID.Lwm2mGateway, 1, 0)\n        self.assertEqual(coap.ContentFormat.TEXT_PLAIN,\n                         res.get_content_format())\n        self.assertEqual(res.content.decode('utf-8'), \"urn:dev:556789\")\n        res = self.read_resource(self.serv, OID.Lwm2mGateway, 0, 1)\n        self.assertEqual(coap.ContentFormat.TEXT_PLAIN,\n                         res.get_content_format())\n        self.assertEqual(res.content.decode('utf-8'), \"dev0\")\n        res = self.read_resource(self.serv, OID.Lwm2mGateway, 1, 1)\n        self.assertEqual(coap.ContentFormat.TEXT_PLAIN,\n                         res.get_content_format())\n        self.assertEqual(res.content.decode('utf-8'), \"dev1\")\n        res = self.read_resource(self.serv, OID.Lwm2mGateway, 0, 3)\n        self.assertEqual(coap.ContentFormat.TEXT_PLAIN,\n                         res.get_content_format())\n        self.assertEqual(res.content.decode('utf-8'),\n                         \"</19/0>,</3303>;ver=1.1,</3303/0>,</3347/0>\")\n        res = self.read_resource(self.serv, OID.Lwm2mGateway, 1, 3)\n        self.assertEqual(coap.ContentFormat.TEXT_PLAIN,\n                         res.get_content_format())\n        self.assertEqual(res.content.decode('utf-8'),\n                         \"</19/0>,</3303>;ver=1.1,</3303/0>,</3347/0>\")\n\n        res = self.create_instance(self.serv, OID.Lwm2mGateway,\n                                   expect_error_code=coap.Code.RES_METHOD_NOT_ALLOWED)\n\n\nclass GatewayPerformBasicOperationsOnEndDevicesObject(Gateway.BaseWithRegister):\n    def runTest(self):\n        self.assertDemoRegisters(self.serv)\n\n        prefixes = self.extractPrefixes(self.serv)\n\n        # operate on buttons differently to verify if the Reads work properly\n\n        self.communicate(\"gw_press_button 0\")\n        self.communicate(\"gw_release_button 0\")\n        self.communicate(\"gw_press_button 1\")\n        self.communicate(\"gw_release_button 1\")\n        self.communicate(\"gw_press_button 1\")\n\n        # build expected contents\n        button1_res1_val = 0\n        button1_res2_val = 1\n        button1_res3_val = \"Button 0\"\n        button2_res1_val = 1\n        button2_res2_val = 2\n        button2_res3_val = \"Button 1\"\n\n        button1_res1 = TLV.make_resource(\n            RID.PushButton.DigitalInputState, button1_res1_val)\n        button1_res2 = TLV.make_resource(\n            RID.PushButton.DigitalInputCounter, button1_res2_val)\n        button1_res3 = TLV.make_resource(\n            RID.PushButton.ApplicationType, button1_res3_val)\n        button1_instance = button1_res1.serialize() \\\n            + button1_res2.serialize() \\\n            + button1_res3.serialize()\n        button1_object = TLV.make_instance(0, [button1_res1,\n                                               button1_res2,\n                                               button1_res3]).serialize()\n\n        button2_res1 = TLV.make_resource(\n            RID.PushButton.DigitalInputState, button2_res1_val)\n        button2_res2 = TLV.make_resource(\n            RID.PushButton.DigitalInputCounter, button2_res2_val)\n        button2_res3 = TLV.make_resource(\n            RID.PushButton.ApplicationType, button2_res3_val)\n        button2_instance = button2_res1.serialize() \\\n            + button2_res2.serialize() \\\n            + button2_res3.serialize()\n        button2_object = TLV.make_instance(0, [button2_res1,\n                                               button2_res2,\n                                               button2_res3]).serialize()\n\n        # Read on Object\n        res = self.read_path(self.serv, '/%s/3347' % prefixes[0])\n        self.assertEqual(coap.ContentFormat.APPLICATION_LWM2M_TLV,\n                         res.get_content_format())\n        self.assertEqual(res.content, button1_object)\n\n        res = self.read_path(self.serv, '/%s/3347' % prefixes[1])\n        self.assertEqual(coap.ContentFormat.APPLICATION_LWM2M_TLV,\n                         res.get_content_format())\n        self.assertEqual(res.content, button2_object)\n\n        # Read on Instance\n        res = self.read_path(self.serv, '/%s/3347/0' % prefixes[0])\n        self.assertEqual(coap.ContentFormat.APPLICATION_LWM2M_TLV,\n                         res.get_content_format())\n        self.assertEqual(res.content, button1_instance)\n\n        res = self.read_path(self.serv, '/%s/3347/0' % prefixes[1])\n        self.assertEqual(coap.ContentFormat.APPLICATION_LWM2M_TLV,\n                         res.get_content_format())\n        self.assertEqual(res.content, button2_instance)\n\n        # Read on Resource\n        res = self.read_path(self.serv, '/%s/3347/0/5500' % prefixes[0])\n        self.assertEqual(coap.ContentFormat.TEXT_PLAIN,\n                         res.get_content_format())\n        self.assertEqual(res.content.decode(), str(button1_res1_val))\n        res = self.read_path(self.serv, '/%s/3347/0/5501' % prefixes[0])\n        self.assertEqual(coap.ContentFormat.TEXT_PLAIN,\n                         res.get_content_format())\n        self.assertEqual(res.content.decode(), str(button1_res2_val))\n        res = self.read_path(self.serv, '/%s/3347/0/5750' % prefixes[0])\n        self.assertEqual(coap.ContentFormat.TEXT_PLAIN,\n                         res.get_content_format())\n        self.assertEqual(res.content.decode(), button1_res3_val)\n\n        res = self.read_path(self.serv, '/%s/3347/0/5500' % prefixes[1])\n        self.assertEqual(coap.ContentFormat.TEXT_PLAIN,\n                         res.get_content_format())\n        self.assertEqual(res.content.decode(), str(button2_res1_val))\n        res = self.read_path(self.serv, '/%s/3347/0/5501' % prefixes[1])\n        self.assertEqual(coap.ContentFormat.TEXT_PLAIN,\n                         res.get_content_format())\n        self.assertEqual(res.content.decode(), str(button2_res2_val))\n        res = self.read_path(self.serv, '/%s/3347/0/5750' % prefixes[1])\n        self.assertEqual(coap.ContentFormat.TEXT_PLAIN,\n                         res.get_content_format())\n        self.assertEqual(res.content.decode(), button2_res3_val)\n\n        # Write on Resource\n        req = Lwm2mWrite('/%s/3347/0/5750' % (prefixes[0]), \"df\",\n                         format=coap.ContentFormat.TEXT_PLAIN)\n        expected_res = self._make_expected_res(\n            req, success_res_cls=Lwm2mChanged, expect_error_code=None)\n        res = self._perform_action(\n            self.serv, request=req, expected_response=expected_res)\n\n        req = Lwm2mWrite('/%s/3347/0/5750' % (prefixes[1]), \"gh\",\n                         format=coap.ContentFormat.TEXT_PLAIN)\n        expected_res = self._make_expected_res(\n            req, success_res_cls=Lwm2mChanged, expect_error_code=None)\n        res = self._perform_action(\n            self.serv, request=req, expected_response=expected_res)\n\n        res = self.read_path(self.serv, '/%s/3347/0/5750' % prefixes[0])\n        self.assertEqual(coap.ContentFormat.TEXT_PLAIN,\n                         res.get_content_format())\n        self.assertEqual(res.content.decode(), \"df\")\n\n        res = self.read_path(self.serv, '/%s/3347/0/5750' % prefixes[1])\n        self.assertEqual(coap.ContentFormat.TEXT_PLAIN,\n                         res.get_content_format())\n        self.assertEqual(res.content.decode(), \"gh\")\n\n        # Execute on Resource\n        req = Lwm2mExecute('/%s/3303/0/5605' % (prefixes[0]))\n        expected_res = self._make_expected_res(\n            req, success_res_cls=Lwm2mChanged, expect_error_code=None)\n        res = self._perform_action(\n            self.serv, request=req, expected_response=expected_res)\n\n\nclass PerformReadOperationOnMultiInstanceResources(Gateway.BaseWithRegister):\n    def runTest(self):\n        self.assertDemoRegisters(self.serv)\n\n        prefixes = self.extractPrefixes(self.serv)\n\n        self.communicate(\n            \"gw-badc-write 0 0 0 device 0 value of multi instance resource\")\n        self.communicate(\n            \"gw-badc-write 1 0 0 device 1 value of multi instance resource\")\n\n        # Read resource\n        res = self.read_path(self.serv, '/%s/19/0/0/0' % prefixes[0],\n                             accept=coap.ContentFormat.APPLICATION_OCTET_STREAM)\n        self.assertEqual(\n            coap.ContentFormat.APPLICATION_OCTET_STREAM, res.get_content_format())\n        self.assertEqual(res.content.decode(),\n                         'device 0 value of multi instance resource')\n\n        res = self.read_path(self.serv, '/%s/19/0/0/0' % prefixes[1],\n                             accept=coap.ContentFormat.APPLICATION_OCTET_STREAM)\n        self.assertEqual(\n            coap.ContentFormat.APPLICATION_OCTET_STREAM, res.get_content_format())\n        self.assertEqual(res.content.decode(),\n                         'device 1 value of multi instance resource')\n\n\nclass PerformWriteOperationOnMultiInstanceResources(Gateway.BaseWithRegister):\n    def runTest(self):\n        self.assertDemoRegisters(self.serv)\n\n        prefixes = self.extractPrefixes(self.serv)\n\n        # Write and Read on Resource Instance\n        dev_0_request = [\n            {\n                SenmlLabel.BASE_NAME: f'/{prefixes[0]}/19/0/0/0',\n                SenmlLabel.OPAQUE: b'test value 1'\n            }\n        ]\n\n        dev_1_request = [\n            {\n                SenmlLabel.BASE_NAME: f'/{prefixes[1]}/19/0/0/0',\n                SenmlLabel.OPAQUE: b'different test value'\n            }\n        ]\n\n        req = Lwm2mWrite('/%s/19/0' % (prefixes[0]), CBOR.serialize(dev_0_request),\n                         format=coap.ContentFormat.APPLICATION_LWM2M_SENML_CBOR)\n        expected_res = self._make_expected_res(\n            req, success_res_cls=Lwm2mChanged, expect_error_code=None)\n        res = self._perform_action(\n            self.serv, request=req, expected_response=expected_res)\n\n        req = Lwm2mWrite('/%s/19/0' % (prefixes[1]), CBOR.serialize(dev_1_request),\n                         format=coap.ContentFormat.APPLICATION_LWM2M_SENML_CBOR)\n        expected_res = self._make_expected_res(\n            req, success_res_cls=Lwm2mChanged, expect_error_code=None)\n        res = self._perform_action(\n            self.serv, request=req, expected_response=expected_res)\n\n        # Check the values\n        res = self.read_path(self.serv, '/%s/19/0/0/0' % prefixes[0],\n                             accept=coap.ContentFormat.APPLICATION_OCTET_STREAM)\n        self.assertEqual(\n            coap.ContentFormat.APPLICATION_OCTET_STREAM, res.get_content_format())\n        self.assertEqual(res.content.decode(), 'test value 1')\n\n        res = self.read_path(self.serv, '/%s/19/0/0/0' % prefixes[1],\n                             accept=coap.ContentFormat.APPLICATION_OCTET_STREAM)\n        self.assertEqual(\n            coap.ContentFormat.APPLICATION_OCTET_STREAM, res.get_content_format())\n        self.assertEqual(res.content.decode(), 'different test value')\n\n\nclass WriteMultipleResourcesOnEndDevice(Gateway.BaseWithRegister):\n    def runTest(self):\n        self.assertDemoRegisters(self.serv)\n\n        prefixes = self.extractPrefixes(self.serv)\n\n        # Write new resource instance\n        dev_0_request = [\n            {\n                SenmlLabel.BASE_NAME: f'/{prefixes[0]}/19/0/',\n                SenmlLabel.NAME: '0/0',\n                SenmlLabel.OPAQUE: b'resource_instance_1_value'\n            },\n            {\n                SenmlLabel.NAME: '0/1',\n                SenmlLabel.OPAQUE: b'resource_instance_2_value'\n            }\n        ]\n\n        self.write_path(self.serv, path=\"/%s/19/0\" % prefixes[0], content=CBOR.serialize(dev_0_request),\n                        format=coap.ContentFormat.APPLICATION_LWM2M_SENML_CBOR)\n\n        # Check resource instance dim - should be equal to 2\n        ret = self.discover_path(\n            self.serv, path=\"/%s/19/0\" % prefixes[0], depth=1)\n        self.assertEqual(ret.content, b'</19/0>,</19/0/0>;dim=2')\n\n        # Check the values\n        res = self.read_path(self.serv, '/%s/19/0/0/0' % prefixes[0],\n                             accept=coap.ContentFormat.APPLICATION_OCTET_STREAM)\n        self.assertEqual(\n            coap.ContentFormat.APPLICATION_OCTET_STREAM, res.get_content_format())\n        self.assertEqual(res.content.decode(), 'resource_instance_1_value')\n\n        res = self.read_path(self.serv, '/%s/19/0/0/1' % prefixes[0],\n                             accept=coap.ContentFormat.APPLICATION_OCTET_STREAM)\n        self.assertEqual(\n            coap.ContentFormat.APPLICATION_OCTET_STREAM, res.get_content_format())\n        self.assertEqual(res.content.decode(), 'resource_instance_2_value')\n\n\nclass AccessControlCreateTest(Gateway.Base, access_control.AccessControl.Test):\n    def setUp(self):\n        super().setUp(servers=2, oid=OID.BinaryAppDataContainer,\n                      extra_cmdline_args=['--access-entry', '/%s/0,0,%s' % (OID.Lwm2mGateway, AccessMask.READ),\n                                          '--access-entry', '/%s/1,0,%s' % (OID.Lwm2mGateway, AccessMask.READ)])\n\n    def runTest(self):\n        prefixes = self.extractPrefixes(self.servers[0])\n\n        # SSID 2 has Create flag on OID.BinaryAppDataContainer object\n        self.create(server=self.servers[1], path=\"/19\")\n        # Now do the same with SSID 1, it should fail\n        self.create(server=self.servers[0], path=\"/19\",\n                    expect_error_code=coap.Code.RES_UNAUTHORIZED)\n        # Create instance for endpoint objects, Access Control has no impact on them\n        self.create(server=self.servers[0], path=\"/%s/19\" % prefixes[0])\n        self.create(server=self.servers[1], path=\"/%s/19\" % prefixes[0])\n\n\nclass AccessControlDeleteTest(Gateway.Base, access_control.AccessControl.Test):\n    def setUp(self):\n        super().setUp(servers=2, oid=OID.BinaryAppDataContainer,\n                      extra_cmdline_args=['--access-entry', '/%s/0,0,%s' % (OID.Lwm2mGateway, AccessMask.READ),\n                                          '--access-entry', '/%s/1,0,%s' % (OID.Lwm2mGateway, AccessMask.READ)])\n\n    def runTest(self):\n        prefixes = self.extractPrefixes(server=self.servers[0])\n\n        self.create(server=self.servers[1], path=\"/19\")\n\n        ac_iid = self.find_access_control_instance(\n            server=self.servers[1], oid=OID.BinaryAppDataContainer, iid=0)\n\n        # Delete instances of end device objects\n        self.delete(server=self.servers[0], path=\"/%s/19/0\" % prefixes[0])\n        self.create(server=self.servers[0], path=\"/%s/19\" % prefixes[0])\n        self.delete(server=self.servers[1], path=\"/%s/19/0\" % prefixes[0])\n        # Deleting instance of BinaryAppDataContainer object for end device should not impact Access Control object\n        self.read_instance(\n            server=self.servers[1], oid=OID.AccessControl, iid=ac_iid)\n\n        self.delete_instance(server=self.servers[0], oid=OID.BinaryAppDataContainer, iid=0,\n                             expect_error_code=coap.Code.RES_UNAUTHORIZED)\n        self.delete_instance(\n            server=self.servers[1], oid=OID.BinaryAppDataContainer, iid=0)\n\n        # Instance is removed, so its corresponding Access Control Instance\n        # should be removed automatically too. The answer shall be NOT_FOUND\n        # according to the spec.\n        self.read_instance(server=self.servers[1], oid=OID.AccessControl, iid=ac_iid,\n                           expect_error_code=coap.Code.RES_NOT_FOUND)\n\n\nclass GatewayWriteAttributes(Gateway.BaseWithRegister):\n    def setUp(self):\n        # LwM2M 1.2 is set to see all the attributes in observe on Object Level\n        super().setUp(minimum_version='1.2', maximum_version='1.2')\n\n    def checkDiscoverValues(self):\n        prefixes = self.extractPrefixes(self.serv)\n        # Discover RIID level is forbidden, rest should work\n        self.discover_path(self.serv, path=\"/%s/19/0/0/0\" %\n                           prefixes[0], expect_error_code=coap.Code.RES_METHOD_NOT_ALLOWED)\n        res = self.discover_path(self.serv, path=\"/%s/19/0/0\" % prefixes[0])\n        self.assertEqual(res.content.decode(),\n                         \"</19/0/0>;dim=1;pmin=1;pmax=4;epmin=5,</19/0/0/0>;epmin=6\")\n        res = self.discover_path(self.serv, path=\"/%s/19/0\" % prefixes[0])\n        self.assertEqual(res.content.decode(),\n                         \"</19/0>;pmin=3;pmax=4,</19/0/0>;dim=1;pmin=1;epmin=5\")\n        res = self.discover_path(self.serv, path=\"/%s/19\" % prefixes[0])\n        self.assertEqual(res.content.decode(),\n                         \"</19>;pmin=4;pmax=5,</19/0>;pmin=3;pmax=4,\"\n                         \"</19/0/0>;dim=1;pmin=1;epmin=5\")\n\n    def runTest(self):\n        self.assertDemoRegisters(self.serv, version='1.2')\n\n        prefixes = self.extractPrefixes(self.serv)\n\n        # no attributes at the beginning\n        res = self.discover_path(self.serv, path=\"/%s/19\" % prefixes[0])\n        self.assertEqual(res.content.decode(),\n                         \"</19>,</19/0>,</19/0/0>;dim=1\")\n\n        self.write_attributes_path(\n            self.serv, path=\"/%s/19/0/0/0\" % prefixes[0], query=['epmin=6'])\n        self.write_attributes_path(\n            self.serv, path=\"/%s/19/0/0\" % prefixes[0], query=['epmin=5', 'pmin=1'])\n        self.write_attributes_path(\n            self.serv, path=\"/%s/19/0\" % prefixes[0], query=['pmin=3', 'pmax=4'])\n        self.write_attributes_path(\n            self.serv, path=\"/%s/19\" % prefixes[0], query=['pmin=4', 'pmax=5'])\n\n        # check if attributes are aplied and discover properly\n        self.checkDiscoverValues()\n\n        # check if invalid values are not applied and don't mess with current state\n        self.write_attributes_path(self.serv, path=\"/%s/19/0/0\" % prefixes[0], query=['epmin=-10'],\n                                   expect_error_code=coap.Code.RES_BAD_OPTION)\n        self.write_attributes_path(self.serv, path=\"/%s/19/0\" % prefixes[0], query=['pmin=-20'],\n                                   expect_error_code=coap.Code.RES_BAD_OPTION)\n        self.write_attributes_path(self.serv, path=\"/%s/19\" % prefixes[0], query=['pmax=-1'],\n                                   expect_error_code=coap.Code.RES_BAD_OPTION)\n        self.checkDiscoverValues()\n\n        # check for crosstalk - write different attributes to same Object in Anjay DM and End Device DM\n        self.write_attributes_path(\n            self.serv, path=\"/%s/3303/0/5700\" % prefixes[0], query=['epmin=6'])\n        self.write_attributes_path(\n            self.serv, path=\"/%s/3303/0/5700\" % prefixes[0], query=['pmax=5'])\n        self.write_attributes_path(\n            self.serv, path=\"/3303/0/5700\", query=['gt=3'])\n        self.write_attributes_path(\n            self.serv, path=\"/3303/0/5700\", query=['lt=1'])\n\n        res = self.discover_path(\n            self.serv, path=\"/%s/3303/0/5700\" % prefixes[0])\n        self.assertEqual(res.content.decode(),\n                         \"</3303/0/5700>;pmax=5;epmin=6\")\n        res = self.discover_path(self.serv, path=\"/3303/0/5700\")\n        self.assertEqual(res.content.decode(),\n                         \"</3303/0/5700>;gt=3;lt=1\")\n\n\nclass GatewayWriteAttributesMultipleServers(Gateway.Base, test_suite.Lwm2mTest, test_suite.Lwm2mDmOperations):\n    def setUp(self):\n        super().setUp(servers=2,\n                      extra_cmdline_args=['--access-entry', '/%s/0,0,%s' % (OID.Lwm2mGateway, AccessMask.READ),\n                                          '--access-entry', '/%s/1,0,%s' % (OID.Lwm2mGateway, AccessMask.READ)],\n                      minimum_version='1.2', maximum_version='1.2')\n\n    def runTest(self):\n        self.coap_ping(self.servers[0])\n        self.coap_ping(self.servers[1])\n        prefixes = self.extractPrefixes(self.servers[0])\n\n        res = self.discover_path(self.servers[0], path=\"/%s/19\" % prefixes[0])\n        self.assertEqual(res.content.decode(),\n                         \"</19>,</19/0>,</19/0/0>;dim=1\")\n\n        res = self.discover_path(self.servers[1], path=\"/%s/19\" % prefixes[0])\n        self.assertEqual(res.content.decode(),\n                         \"</19>,</19/0>,</19/0/0>;dim=1\")\n\n        # server 0 sets and properly reads its attributes\n        self.write_attributes_path(\n            self.servers[0], path=\"/%s/19/0/0\" % prefixes[0], query=['pmin=1', 'pmax=5'])\n        res = self.discover_path(\n            self.servers[0], path=\"/%s/19/0/0\" % prefixes[0], depth=0)\n        self.assertEqual(res.content.decode(), \"</19/0/0>;dim=1;pmin=1;pmax=5\")\n        self.write_attributes_path(\n            self.servers[0], path=\"/%s/19/0\" % prefixes[0], query=['pmin=3', 'pmax=4'])\n        res = self.discover_path(\n            self.servers[0], path=\"/%s/19/0\" % prefixes[0], depth=0)\n        self.assertEqual(res.content.decode(), \"</19/0>;pmin=3;pmax=4\")\n        self.write_attributes_path(\n            self.servers[0], path=\"/%s/19\" % prefixes[0], query=['pmin=4', 'pmax=5'])\n        res = self.discover_path(\n            self.servers[0], path=\"/%s/19\" % prefixes[0], depth=0)\n        self.assertEqual(res.content.decode(), \"</19>;pmin=4;pmax=5\")\n\n        # server 1 does not see attributes set by server 0\n        res = self.discover_path(self.servers[1], path=\"/%s/19\" % prefixes[0])\n        self.assertEqual(res.content.decode(), \"</19>,</19/0>,</19/0/0>;dim=1\")\n\n        # server 1 sets attributes and they don't overwrite attributes set by server 0\n        self.write_attributes_path(\n            self.servers[1], path=\"/%s/19/0/0\" % prefixes[0], query=['epmin=4', 'pmin=2'])\n        res = self.discover_path(\n            self.servers[0], path=\"/%s/19/0/0\" % prefixes[0], depth=0)\n        self.assertEqual(res.content.decode(), \"</19/0/0>;dim=1;pmin=1;pmax=5\")\n        self.write_attributes_path(\n            self.servers[1], path=\"/%s/19/0\" % prefixes[0], query=['pmin=1', 'pmax=6'])\n        res = self.discover_path(\n            self.servers[0], path=\"/%s/19/0\" % prefixes[0], depth=0)\n        self.assertEqual(res.content.decode(), \"</19/0>;pmin=3;pmax=4\")\n        self.write_attributes_path(\n            self.servers[1], path=\"/%s/19\" % prefixes[0], query=['pmin=8', 'pmax=9'])\n        res = self.discover_path(\n            self.servers[0], path=\"/%s/19\" % prefixes[0], depth=0)\n        self.assertEqual(res.content.decode(), \"</19>;pmin=4;pmax=5\")\n\n\nclass SendFromDataModelTest(Gateway.Base, Send.Test):\n    def setApplicationTypeRes(self, prefixes):\n        self.write_path(self.serv, path='/%s/3303/0/5750' % (prefixes[0]),\n                        content=\"Test1\", format=coap.ContentFormat.TEXT_PLAIN)\n        self.write_path(self.serv, path='/%s/3303/0/5750' % (prefixes[1]),\n                        content=\"Test2\", format=coap.ContentFormat.TEXT_PLAIN)\n        self.write_path(self.serv, path='/%s/3347/0/5750' % (prefixes[0]),\n                        content=\"Test3\", format=coap.ContentFormat.TEXT_PLAIN)\n        self.write_path(self.serv, path='/%s/3347/0/5750' % (prefixes[1]),\n                        content=\"Test4\", format=coap.ContentFormat.TEXT_PLAIN)\n\n    def processSend(self):\n        pkt = self.serv.recv()\n        self.assertMsgEqual(Lwm2mSend(), pkt)\n        self.serv.send(Lwm2mChanged.matching(pkt)())\n        return CBOR.parse(pkt.content)\n\n    def runTest(self):\n        prefixes = self.extractPrefixes(self.serv)\n        self.setApplicationTypeRes(prefixes)\n\n        # SEND with gateway resources\n        self.communicate(\n            'send 1 %s %s' %\n            (ResPath.Device.ModelNumber,\n             ResPath.Device.Manufacturer))\n\n        cbor = self.processSend()\n        cbor.verify_values(test=self,\n                           expected_value_map={\n                               ResPath.Device.ModelNumber: 'demo-client',\n                               ResPath.Device.Manufacturer: '0023C7'\n                           })\n        self.assertEqual(cbor[0].get(SenmlLabel.BASE_NAME), '/3/0')\n        self.assertIsNotNone(cbor[0].get(SenmlLabel.BASE_TIME))\n        self.assertIsNone(cbor[0].get(SenmlLabel.TIME))\n\n        self.assertIsNone(cbor[1].get(SenmlLabel.BASE_NAME))\n        self.assertIsNone(cbor[1].get(SenmlLabel.BASE_TIME))\n        self.assertIsNone(cbor[1].get(SenmlLabel.TIME))\n\n        # SEND with end device resources\n        self.communicate(\n            'send 1 0 %s 0 %s' %\n            (ResPath.PushButton.ApplicationType,\n             ResPath.PushButton.DigitalInputCounter))\n\n        cbor = self.processSend()\n        cbor.verify_values(test=self,\n                           expected_value_map={\n                               '/%s/3347/0/5750' % (prefixes[0]): 'Test3',\n                               '/%s/3347/0/5501' % (prefixes[0]): 0\n                           })\n        self.assertEqual(cbor[0].get(SenmlLabel.BASE_NAME),\n                         '/%s/3347/0' % (prefixes[0]))\n        self.assertIsNotNone(cbor[0].get(SenmlLabel.BASE_TIME))\n        self.assertIsNone(cbor[0].get(SenmlLabel.TIME))\n\n        self.assertIsNone(cbor[1].get(SenmlLabel.BASE_NAME))\n        self.assertIsNone(cbor[1].get(SenmlLabel.BASE_TIME))\n        self.assertIsNone(cbor[1].get(SenmlLabel.TIME))\n\n        # SEND with same resources and two end devices\n        self.communicate(\n            'send 1 0 %s 1 %s' %\n            (ResPath.Temperature.ApplicationType,\n             ResPath.Temperature.ApplicationType))\n\n        cbor = self.processSend()\n        cbor.verify_values(test=self,\n                           expected_value_map={\n                               '/%s/3303/0/5750' % (prefixes[0]): 'Test1',\n                               '/%s/3303/0/5750' % (prefixes[1]): 'Test2'\n                           })\n        self.assertIsNone(cbor[0].get(SenmlLabel.BASE_NAME))\n        self.assertIsNotNone(cbor[0].get(SenmlLabel.BASE_TIME))\n        self.assertIsNone(cbor[0].get(SenmlLabel.TIME))\n\n        self.assertIsNone(cbor[1].get(SenmlLabel.BASE_NAME))\n        self.assertIsNotNone(cbor[1].get(SenmlLabel.BASE_TIME))\n        self.assertIsNone(cbor[1].get(SenmlLabel.TIME))\n\n        # SEND with different resources and two end devices\n        self.communicate(\n            'send 1 0 %s 1 %s' %\n            (ResPath.PushButton.DigitalInputCounter,\n             ResPath.PushButton.ApplicationType))\n\n        cbor = self.processSend()\n        cbor.verify_values(test=self,\n                           expected_value_map={\n                               '/%s/3347/0/5501' % (prefixes[0]): 0,\n                               '/%s/3347/0/5750' % (prefixes[1]): 'Test4'\n                           })\n        self.assertIsNone(cbor[0].get(SenmlLabel.BASE_NAME))\n        self.assertIsNotNone(cbor[0].get(SenmlLabel.BASE_TIME))\n        self.assertIsNone(cbor[0].get(SenmlLabel.TIME))\n\n        self.assertIsNone(cbor[1].get(SenmlLabel.BASE_NAME))\n        self.assertIsNotNone(cbor[1].get(SenmlLabel.BASE_TIME))\n        self.assertIsNone(cbor[1].get(SenmlLabel.TIME))\n\n        # SEND with gateway and two end devices resources\n        self.communicate(\n            'send 1 0 %s %s 1 %s' %\n            (ResPath.Temperature.ApplicationType,\n             ResPath.Device.ModelNumber,\n             ResPath.PushButton.ApplicationType))\n\n        cbor = self.processSend()\n        cbor.verify_values(test=self,\n                           expected_value_map={\n                               '/%s/3303/0/5750' % (prefixes[0]): 'Test1',\n                               ResPath.Device.ModelNumber: 'demo-client',\n                               '/%s/3347/0/5750' % (prefixes[1]): 'Test4'\n                           })\n        self.assertIsNone(cbor[0].get(SenmlLabel.BASE_NAME))\n        self.assertIsNotNone(cbor[0].get(SenmlLabel.BASE_TIME))\n        self.assertIsNone(cbor[0].get(SenmlLabel.TIME))\n        self.assertIsNone(cbor[1].get(SenmlLabel.BASE_NAME))\n        self.assertIsNotNone(cbor[1].get(SenmlLabel.BASE_TIME))\n        self.assertIsNone(cbor[1].get(SenmlLabel.TIME))\n        self.assertIsNone(cbor[2].get(SenmlLabel.BASE_NAME))\n        self.assertIsNotNone(cbor[2].get(SenmlLabel.BASE_TIME))\n        self.assertIsNone(cbor[2].get(SenmlLabel.TIME))\n\n\nclass WriteComposite(Gateway.BaseWithRegister):\n    def runTest(self):\n        request = [{\n            'n': '/dev0/3303/0/5750',\n            'vs': \"aa\"\n        }\n        ]\n\n        self.assertDemoRegisters(self.serv)\n\n        self.write_composite(self.serv, content=json.dumps(request),\n                             format=coap.ContentFormat.APPLICATION_LWM2M_SENML_JSON,\n                             expect_error_code=coap.Code.RES_METHOD_NOT_ALLOWED)\n\n\nclass ReadComposite(Gateway.BaseWithRegister):\n    def runTest(self):\n        self.assertDemoRegisters(self.serv)\n\n        self.read_composite(self.serv, paths=['/dev0/3303/0/5750'],\n                            expect_error_code=coap.Code.RES_METHOD_NOT_ALLOWED,\n                            accept=coap.ContentFormat.APPLICATION_LWM2M_SENML_JSON)\n\n\nclass ObserveComposite(Gateway.BaseWithRegister):\n    def runTest(self):\n        self.assertDemoRegisters(self.serv)\n\n        self.observe_composite(self.serv, paths=['/dev0/3303/0/5750'],\n                               expect_error_code=coap.Code.RES_METHOD_NOT_ALLOWED)\n\n\nclass ObservationStatusTest(Gateway.ObservationStatus):\n    def runTest(self):\n        self.assertTupleEqual(self.gw_observation_status(self.path_gw1_res1),\n                              (False, 0, -1))\n        notif1 = self.observe_path(self.serv, path=self.path_gw1_res1)\n        self.assertTupleEqual(self.gw_observation_status(self.path_gw1_res1),\n                              # We expect pmin == 0 because  ANJAY_DM_DEFAULT_PMIN_VALUE == 0\n                              (True, 0, -1))\n        self.write_attributes_path(\n            self.serv, path=self.path_gw1_res1, query=['pmin=42'])\n        self.assertTupleEqual(self.gw_observation_status(self.path_gw1_res1),\n                              (True, 42, -1))\n        notif2 = self.observe_path(\n            self.serv, path=self.path_gw1_obj1)\n        self.assertTupleEqual(self.gw_observation_status(self.path_gw1_res1),\n                              # Now 2 paths are observed:\n                              # 1. /dev0/3303/0/5700, pmin = 42\n                              # 2. /dev0/3303,        pmin = 0 (ANJAY_DM_DEFAULT_PMIN_VALUE)\n                              # So we expect effective pmin = min{42, 0} = 0\n                              (True, 0, -1))\n        self.write_attributes_path(\n            self.serv, path=self.path_gw1_obj1, query=['pmin=41'])\n        self.assertTupleEqual(self.gw_observation_status(self.path_gw1_res1),\n                              # Attributes for the paths have changed:\n                              # 1. /dev0/3303/0/5700, pmin = 42\n                              # 2. /dev0/3303,        pmin = 41\n                              # Now 41 is effective pmin\n                              (True, 41, -1))\n        self.write_attributes_path(\n            self.serv, path=self.path_gw1_obj1, query=['pmin=43'])\n        self.assertTupleEqual(self.gw_observation_status(self.path_gw1_res1),\n                              # Attributes for the paths have changed:\n                              # 1. /dev0/3303/0/5700, pmin = 42\n                              # 2. /dev0/3303,        pmin = 43\n                              # Now 42 is effective pmin\n                              (True, 42, -1))\n        self.write_attributes_path(\n            self.serv, path=self.path_gw1_obj1, query=['epmax=5'])\n        self.assertTupleEqual(self.gw_observation_status(self.path_gw1_res1),\n                              (True, 42, 5))\n\n        # cancel observations\n        self.observe_path(self.serv, path=self.path_gw1_obj1,\n                          token=notif2.token, observe=1)\n        self.observe_path(self.serv, path=self.path_gw1_res1,\n                          token=notif1.token, observe=1)\n        self.assertTupleEqual(self.gw_observation_status(\n            # NOTE: attribute values don't correspond to the actual values stored in the attribute storage\n            self.path_gw1_res1), (False, 0, -1))\n\n\nclass ObservationStatusObserveOperationTest(Gateway.ObservationStatus):\n    def runTest(self):\n        # set observe on Anjay\n        notif1 = self.observe_path(self.serv, path=self.path_res1)\n        self.assertTupleEqual(self.observation_status(self.path_res1),\n                              self.OBSER_EXISTS_WO_ATTR)\n        self.assertTupleEqual(self.gw_observation_status(self.path_gw1_res1),\n                              self.OBSER_NOT_EXISTS)\n        self.assertTupleEqual(self.gw_observation_status(self.path_gw2_res1),\n                              self.OBSER_NOT_EXISTS)\n\n        # Cancel observe\n        self.observe_path(self.serv, path=self.path_res1,\n                          observe=1, token=notif1.token)\n\n        # nothing is observed\n        self.assertTupleEqual(self.observation_status(self.path_res1),\n                              self.OBSER_NOT_EXISTS)\n        self.assertTupleEqual(self.gw_observation_status(self.path_gw1_res1),\n                              self.OBSER_NOT_EXISTS)\n        self.assertTupleEqual(self.gw_observation_status(self.path_gw2_res1),\n                              self.OBSER_NOT_EXISTS)\n\n        notif1 = self.observe_path(self.serv, path=self.path_gw1_res1)\n        self.assertTupleEqual(self.observation_status(self.path_res1),\n                              self.OBSER_NOT_EXISTS)\n        self.assertTupleEqual(self.gw_observation_status(self.path_gw1_res1),\n                              self.OBSER_EXISTS_WO_ATTR)\n        self.assertTupleEqual(self.gw_observation_status(self.path_gw2_res1),\n                              self.OBSER_NOT_EXISTS)\n\n        # Cancel observe\n        self.observe_path(self.serv, path=self.path_gw1_res1,\n                          observe=1, token=notif1.token)\n\n        # nothing is observed\n        self.assertTupleEqual(self.observation_status(self.path_res1),\n                              self.OBSER_NOT_EXISTS)\n        self.assertTupleEqual(self.gw_observation_status(self.path_gw1_res1),\n                              self.OBSER_NOT_EXISTS)\n        self.assertTupleEqual(self.gw_observation_status(self.path_gw2_res1),\n                              self.OBSER_NOT_EXISTS)\n\n\nclass ObservationStatusCancelOperationTest(Gateway.ObservationStatus):\n    def runTest(self):\n        # set observe on Anjay\n        observe_res_sv1 = self.observe_path(self.serv, path=self.path_res1)\n        observe_res_sv2 = self.observe_path(self.serv, path=self.path_gw1_res1)\n        observe_res_sv3 = self.observe_path(self.serv, path=self.path_gw2_res1)\n        observe_res_mmv2 = self.observe_path(\n            self.serv, path=self.path_gw1_res2)\n\n        # check if observes are set\n        self.assertTupleEqual(self.observation_status(self.path_res1),\n                              self.OBSER_EXISTS_WO_ATTR)\n        self.assertTupleEqual(self.gw_observation_status(self.path_gw1_res1),\n                              self.OBSER_EXISTS_WO_ATTR)\n        self.assertTupleEqual(self.gw_observation_status(self.path_gw2_res1),\n                              self.OBSER_EXISTS_WO_ATTR)\n        self.assertTupleEqual(self.gw_observation_status(self.path_gw1_res2),\n                              self.OBSER_EXISTS_WO_ATTR)\n\n        # cancel one of them\n        self.observe_path(self.serv, path=self.path_gw1_res1,\n                          observe=1, token=observe_res_sv2.token)\n\n        # check if rest are still set\n        self.assertTupleEqual(self.observation_status(self.path_res1),\n                              self.OBSER_EXISTS_WO_ATTR)\n        self.assertTupleEqual(self.gw_observation_status(self.path_gw1_res1),\n                              self.OBSER_NOT_EXISTS)\n        self.assertTupleEqual(self.gw_observation_status(self.path_gw2_res1),\n                              self.OBSER_EXISTS_WO_ATTR)\n        self.assertTupleEqual(self.gw_observation_status(self.path_gw1_res2),\n                              self.OBSER_EXISTS_WO_ATTR)\n\n        # cancel another one\n        self.observe_path(self.serv, path=self.path_gw2_res1,\n                          observe=1, token=observe_res_sv3.token)\n\n        # check if rest are still set\n        self.assertTupleEqual(self.observation_status(self.path_res1),\n                              self.OBSER_EXISTS_WO_ATTR)\n        self.assertTupleEqual(self.gw_observation_status(self.path_gw1_res1),\n                              self.OBSER_NOT_EXISTS)\n        self.assertTupleEqual(self.gw_observation_status(self.path_gw2_res1),\n                              self.OBSER_NOT_EXISTS)\n        self.assertTupleEqual(self.gw_observation_status(self.path_gw1_res2),\n                              self.OBSER_EXISTS_WO_ATTR)\n\n        # cancel another one\n        self.observe_path(self.serv, path=self.path_res1,\n                          observe=1, token=observe_res_sv1.token)\n\n        # check if rest are still set\n        self.assertTupleEqual(self.observation_status(self.path_res1),\n                              self.OBSER_NOT_EXISTS)\n        self.assertTupleEqual(self.gw_observation_status(self.path_gw1_res1),\n                              self.OBSER_NOT_EXISTS)\n        self.assertTupleEqual(self.gw_observation_status(self.path_gw2_res1),\n                              self.OBSER_NOT_EXISTS)\n        self.assertTupleEqual(self.gw_observation_status(self.path_gw1_res2),\n                              self.OBSER_EXISTS_WO_ATTR)\n\n        # cancel last one\n        self.observe_path(self.serv, path=self.path_gw1_res2,\n                          observe=1, token=observe_res_mmv2.token)\n\n        # check there are no observes\n        self.assertTupleEqual(self.observation_status(self.path_res1),\n                              self.OBSER_NOT_EXISTS)\n        self.assertTupleEqual(self.gw_observation_status(self.path_gw1_res1),\n                              self.OBSER_NOT_EXISTS)\n        self.assertTupleEqual(self.gw_observation_status(self.path_gw2_res1),\n                              self.OBSER_NOT_EXISTS)\n        self.assertTupleEqual(self.gw_observation_status(self.path_gw1_res2),\n                              self.OBSER_NOT_EXISTS)\n\n\nclass ObservationStatusRemoveObjectInstance(Gateway.ObservationStatus):\n    def setUp(self):\n        super().setUp(minimum_version='1.2', maximum_version='1.2')\n\n    def runTest(self):\n        self.create(self.serv, path=self.path_gw1_obj1)\n\n        # set observe on Anjay\n        self.observe_path(self.serv, path=self.path_res1)\n        notif = self.observe_path(self.serv, path=self.path_gw1_res1, pmax=1)\n        self.observe_path(self.serv, path=self.path_gw1_res2)\n        self.observe_path(self.serv, path=self.path_gw1_res3)\n        self.observe_path(self.serv, path=self.path_gw1_res4)\n        self.observe_path(self.serv, path=self.path_gw2_res1)\n\n        # check if observes are set\n        self.assertTupleEqual(self.observation_status(self.path_res1),\n                              self.OBSER_EXISTS_WO_ATTR)\n        self.assertTupleEqual(self.gw_observation_status(self.path_gw1_res1),\n                              self.OBSER_EXISTS_WO_ATTR)\n        self.assertTupleEqual(self.gw_observation_status(self.path_gw1_res2),\n                              self.OBSER_EXISTS_WO_ATTR)\n        self.assertTupleEqual(self.gw_observation_status(self.path_gw1_res3),\n                              self.OBSER_EXISTS_WO_ATTR)\n        self.assertTupleEqual(self.gw_observation_status(self.path_gw1_res4),\n                              self.OBSER_EXISTS_WO_ATTR)\n        self.assertTupleEqual(self.gw_observation_status(self.path_gw2_res1),\n                              self.OBSER_EXISTS_WO_ATTR)\n\n        self.delete(self.serv, path=self.path_gw1_inst1)\n\n        not_found = self.serv.recv(timeout_s=1, filter=lambda pkt: pkt.code ==\n                                   coap.Code.RES_NOT_FOUND and pkt.token == notif.token)\n        res = Lwm2mEmpty.matching(not_found)()\n        self.serv.send(res)\n\n        self.assertTupleEqual(self.observation_status(self.path_res1),\n                              self.OBSER_EXISTS_WO_ATTR)\n        self.assertTupleEqual(self.gw_observation_status(self.path_gw1_res1),\n                              self.OBSER_NOT_EXISTS)\n        # this one is still active, because there is no pmax attribute and anjay_notify_changed is not called, so Anjay doesn't try to read value\n        self.assertTupleEqual(self.gw_observation_status(self.path_gw1_res2),\n                              self.OBSER_EXISTS_WO_ATTR)\n        self.assertTupleEqual(self.gw_observation_status(self.path_gw1_res3),\n                              self.OBSER_EXISTS_WO_ATTR)\n        self.assertTupleEqual(self.gw_observation_status(self.path_gw1_res4),\n                              self.OBSER_EXISTS_WO_ATTR)\n        self.assertTupleEqual(self.gw_observation_status(self.path_gw2_res1),\n                              self.OBSER_EXISTS_WO_ATTR)\n\n\nclass ObservationStatusRemoveEndDevice(Gateway.ObservationStatus):\n    def setUp(self):\n        super().setUp(minimum_version='1.2', maximum_version='1.2')\n\n    def runTest(self):\n        # set observe on Anjay\n        self.observe_path(self.serv, path=self.path_res1)\n        notif1 = self.observe_path(self.serv, path=self.path_gw1_res1, pmax=1)\n        notif2 = self.observe_path(self.serv, path=self.path_gw1_res2)\n        notif3 = self.observe_path(self.serv, path=self.path_gw1_inst1)\n        self.observe_path(self.serv, path=self.path_gw2_res1)\n\n        # check if observes are set\n        self.assertTupleEqual(self.observation_status(self.path_res1),\n                              self.OBSER_EXISTS_WO_ATTR)\n        self.assertTupleEqual(self.gw_observation_status(self.path_gw1_res1),\n                              self.OBSER_EXISTS_WO_ATTR)\n        self.assertTupleEqual(self.gw_observation_status(self.path_gw1_res2),\n                              self.OBSER_EXISTS_WO_ATTR)\n        self.assertTupleEqual(self.gw_observation_status(self.path_gw2_res1),\n                              self.OBSER_EXISTS_WO_ATTR)\n\n        self.communicate('gw_deregister 0',\n                         match_regex='command: gw_deregister 0')\n        res = self.read_object(self.serv, OID.Lwm2mGateway)\n        for entry in CBOR.parse(res.content):\n            name = entry.get(SenmlLabel.NAME, '')\n            self.assertTrue(\"/0/\" not in name)\n\n        for _ in range(3):\n            not_found = self.serv.recv(timeout_s=1, filter=lambda pkt: pkt.code == coap.Code.RES_NOT_FOUND and pkt.token in (\n                notif1.token, notif2.token, notif3.token))\n            res = Lwm2mEmpty.matching(not_found)()\n            self.serv.send(res)\n\n        self.assertTupleEqual(self.observation_status(self.path_res1),\n                              self.OBSER_EXISTS_WO_ATTR)\n        # unfortunately we don't go through anjay_observe_path_entry_t, we simple check if gateway instance with a such prefix exists\n        self.assertTupleEqual(self.gw_observation_status(self.path_gw1_res1),\n                              self.OBSER_NOT_EXISTS)\n        self.assertTupleEqual(self.gw_observation_status(self.path_gw1_res2),\n                              self.OBSER_NOT_EXISTS)\n        self.assertTupleEqual(self.gw_observation_status(self.path_gw2_res1),\n                              self.OBSER_EXISTS_WO_ATTR)\n\n\nclass ObservationStatusRemoveAfterGettingRST(Gateway.ObservationStatus):\n    def setUp(self):\n        super().setUp(minimum_version='1.2', maximum_version='1.2')\n\n    def runTest(self):\n        self.create(self.serv, path=self.path_gw1_obj1)\n\n        self.write_attributes_path(\n            self.serv, self.path_gw1_res1, query=['con=1'])\n        # set observe on Anjay\n        self.observe_path(self.serv, path=self.path_res1)\n        notif1 = self.observe_path(self.serv, path=self.path_gw1_res1, pmax=1)\n        self.observe_path(self.serv, path=self.path_gw1_res2)\n        self.observe_path(self.serv, path=self.path_gw1_res3)\n        self.observe_path(self.serv, path=self.path_gw1_res4)\n        self.observe_path(self.serv, path=self.path_gw2_res1)\n\n        self.assertTupleEqual(self.gw_observation_status(self.path_gw1_res1),\n                              self.OBSER_EXISTS_WO_ATTR)\n\n        notif2 = self.serv.recv(timeout_s=1, filter=lambda pkt: isinstance(\n            pkt, Lwm2mNotify) and pkt.token == notif1.token)\n        res = Lwm2mReset.matching(notif2)()\n        self.serv.send(res)\n\n        self.assertTupleEqual(self.observation_status(self.path_res1),\n                              self.OBSER_EXISTS_WO_ATTR)\n        self.assertTupleEqual(self.gw_observation_status(self.path_gw1_res1),\n                              self.OBSER_NOT_EXISTS)\n        self.assertTupleEqual(self.gw_observation_status(self.path_gw1_res2),\n                              self.OBSER_EXISTS_WO_ATTR)\n        self.assertTupleEqual(self.gw_observation_status(self.path_gw1_res3),\n                              self.OBSER_EXISTS_WO_ATTR)\n        self.assertTupleEqual(self.gw_observation_status(self.path_gw1_res4),\n                              self.OBSER_EXISTS_WO_ATTR)\n        self.assertTupleEqual(self.gw_observation_status(self.path_gw2_res1),\n                              self.OBSER_EXISTS_WO_ATTR)\n\n\nclass BootstrapEndDevices(Gateway.BaseWithBootstrap,\n                          test_suite.Lwm2mDtlsSingleServerTest,\n                          test_suite.Lwm2mDmOperations):\n    def runTest(self):\n        self.assertDemoRequestsBootstrap()\n\n        # I need to hardcode the prefix and number of end devices here as the\n        # Bootstrap Read can't target Objects other than /1 Server and\n        # /2 Access Control; self.extractPrefixes() won't work\n\n        # Try creating Gateway Object Instance\n        self.write_instance(self.bootstrap_server,\n                            oid=OID.Lwm2mGateway,\n                            iid=3,\n                            expect_error_code=coap.Code.RES_METHOD_NOT_ALLOWED,\n                            partial=False)\n        \n        # Try deleting Gateway Object Instance\n        self.delete_instance(self.bootstrap_server,\n                             oid=OID.Lwm2mGateway,\n                             iid=1,\n                             expect_error_code=coap.Code.RES_METHOD_NOT_ALLOWED)\n        \n        # Try creating Instances of End Devices Objects\n        self.write_path(server=self.bootstrap_server,\n                        path='/dev0/%d/3' % OID.BinaryAppDataContainer,\n                        expect_error_code=coap.Code.RES_METHOD_NOT_ALLOWED,\n                        partial=False)\n        \n        # Try reading Resource on End Device \n        self.read_path(server=self.bootstrap_server,\n                       path='/dev0/%d/0/0' % OID.BinaryAppDataContainer,\n                       expect_error_code=coap.Code.RES_METHOD_NOT_ALLOWED)\n        \n        # Try writing a Resource on End Device\n        self.write_path(server=self.bootstrap_server,\n                        path='/dev0/%d/0/0/0' % OID.BinaryAppDataContainer,\n                        content=b'00',\n                        expect_error_code=coap.Code.RES_METHOD_NOT_ALLOWED,\n                        partial=False)\n        \n        # Try discovering Object Instance on End Device\n        self.discover_path(server=self.bootstrap_server,\n                           path='/dev0/%d/0' % OID.BinaryAppDataContainer,\n                           expect_error_code=coap.Code.RES_METHOD_NOT_ALLOWED)\n\n        # Verify End Devices DM not reported in Discover '/'\n        discover_root_res = self.discover_path(server=self.bootstrap_server,\n                                               path='/')\n        self.assertNotIn('dev', discover_root_res.content.decode())\n\n        self.bootstrap_server.send(Lwm2mBootstrapFinish())\n\n    def tearDown(self):\n        super().tearDown(auto_deregister=False)\n\n\nclass ConfirmableNotificationStatusGateway(\n        Gateway.ObservationStatus, ConfirmableNotificationStatus.Test):\n    def setUp(self):\n        super().setUp(extra_cmdline_args=['--confirmable-notifications'])\n\n    def runTest(self):\n        observe1_path = \"/%s/%d/%d/%d\" % (\n                self.prefixes[0], OID.BinaryAppDataContainer, 0,\n                                  RID.BinaryAppDataContainer.Data)\n        observe2_path = \"/%s/%d/%d/%d\" % (\n                self.prefixes[1], OID.BinaryAppDataContainer, 0,\n                                  RID.BinaryAppDataContainer.Data)\n        observe1 = self.observe_path(self.serv, observe1_path)\n        observe2 = self.observe_path(self.serv, observe2_path)\n\n        self.communicate(\"gw-badc-write 0 0 0 value1\")\n        self.communicate(\"gw-badc-write 1 0 0 value2\")\n\n        notify = self.serv.recv()\n\n        self.assertIsInstance(notify, Lwm2mNotify)\n        self.assertEqual(bytes(notify.token), bytes(observe1.token))\n        self.serv.send(Lwm2mEmpty.matching(notify)())\n\n        self.read_log_until_confirmable_notification_success(\n            ssid=1, paths_count=1)\n        self.read_log_until_path_occur(observe1_path)\n\n        notify = self.serv.recv()\n\n        self.assertIsInstance(notify, Lwm2mNotify)\n        self.assertEqual(bytes(notify.token), bytes(observe2.token))\n        self.serv.send(Lwm2mEmpty.matching(notify)())\n\n        self.read_log_until_confirmable_notification_success(\n            ssid=1, paths_count=1)\n        self.read_log_until_path_occur(observe2_path)\n"
  },
  {
    "path": "tests/integration/suites/default/lwm2m_gateway_observe_attributes.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nfrom framework.lwm2m_test import *\n\nfrom .access_control import AccessMask\nfrom . import lwm2m_gateway as gw\nimport base64\n\n\nclass ObserveWithGatewayBase(gw.Gateway.Base):\n    prefixes = []\n    button_input_counter_path = \"\"\n    button_input_state_path = \"\"\n    badc_data_path = \"\"\n    \n    def setUp(self, *args, **kwargs):\n        super().setUp(*args, **kwargs)\n        self.prefixes = self.extractPrefixes(self.serv)\n        self.button_input_counter_path = \"/%s/%d/0/%d\" % (self.prefixes[0],\n                                                          OID.PushButton,\n                                                          RID.PushButton.DigitalInputCounter)\n        self.button_input_state_path = \"/%s/%d/0/%d\" % (self.prefixes[0],\n                                                        OID.PushButton,\n                                                        RID.PushButton.DigitalInputState)\n        self.badc_data_path = \"/%s/%d/0/%d\" % (self.prefixes[0],\n                                               OID.BinaryAppDataContainer,\n                                               RID.BinaryAppDataContainer.Data)\n\n    def clickButtonNTimesInOfflineMode(self, servers, count):\n        self.communicate('enter-offline')\n\n        for click in range(count):\n            self.communicate(\"gw_press_button 0\")\n            time.sleep(0.1)\n            self.communicate(\"gw_release_button 0\")\n            time.sleep(0.1)\n\n        for serv in servers:\n            serv.reset()\n\n        self.communicate('exit-offline')\n\n        # demo will resume DTLS session without sending any LwM2M messages\n        for serv in servers:\n            serv.listen()\n\nclass ObserveAttributesTest(ObserveWithGatewayBase,\n                            test_suite.Lwm2mDtlsSingleServerTest,\n                            test_suite.Lwm2mDmOperations):\n    def runTest(self):\n        # Observe: Counter\n        counter_pkt = self.observe_path(self.serv, path=self.button_input_counter_path)\n\n        # no message should arrive here\n        with self.assertRaises(socket.timeout):\n            self.serv.recv(timeout_s=5)\n\n        # Attribute invariants\n        self.write_attributes_path(self.serv, path=self.button_input_counter_path,\n                                   query=['st=-1'],\n                                   expect_error_code=coap.Code.RES_BAD_REQUEST)\n        self.write_attributes_path(self.serv, path=self.button_input_counter_path,\n                                   query=['lt=9', 'gt=4'],\n                                   expect_error_code=coap.Code.RES_BAD_REQUEST)\n        self.write_attributes_path(self.serv, path=self.button_input_counter_path,\n                                   query=['lt=4', 'gt=9', 'st=3'],\n                                   expect_error_code=coap.Code.RES_BAD_REQUEST)\n\n        # unparsable attributes\n        self.write_attributes_path(self.serv, path=self.button_input_counter_path,\n                                   query=['lt=invalid'],\n                                   expect_error_code=coap.Code.RES_BAD_OPTION)\n\n        # Write Attributes\n        self.write_attributes_path(self.serv, path=self.button_input_counter_path,\n                                   query=['pmax=2'])\n\n        # now we should get notifications, even though nothing changed\n        pkt = self.serv.recv(timeout_s=3)\n        self.assertEqual(pkt.code, coap.Code.RES_CONTENT)\n        self.assertEqual(pkt.content, counter_pkt.content)\n\n        # and another one\n        pkt = self.serv.recv(timeout_s=3)\n        self.assertEqual(pkt.code, coap.Code.RES_CONTENT)\n        self.assertEqual(pkt.content, counter_pkt.content)\n\n\nclass ObserveResourceInvalidPmax(ObserveWithGatewayBase,\n                                 test_suite.Lwm2mDtlsSingleServerTest,\n                                 test_suite.Lwm2mDmOperations):\n    def runTest(self):\n        # Set invalid pmax (smaller than pmin)\n        self.write_attributes_path(self.serv, path=self.badc_data_path, query=['pmin=2', 'pmax=1'])\n\n        self.observe_path(self.serv, path=self.badc_data_path)\n\n        # No notification should arrive\n        with self.assertRaises(socket.timeout):\n            print(self.serv.recv(timeout_s=3))\n\n\nclass ObserveResourceZeroPmax(ObserveWithGatewayBase,\n                              test_suite.Lwm2mDtlsSingleServerTest,\n                              test_suite.Lwm2mDmOperations):\n    def runTest(self):\n        # Set invalid pmax (equal to 0)\n        self.write_attributes_path(\n            self.serv, path=self.badc_data_path, query=['pmax=0']\n        )\n\n        self.observe_path(self.serv, path=self.badc_data_path)\n\n        # No notification should arrive\n        with self.assertRaises(socket.timeout):\n            print(self.serv.recv(timeout_s=2))\n\n\nclass ObserveWithMultipleServers(ObserveWithGatewayBase,\n                                 test_suite.Lwm2mDtlsSingleServerTest,\n                                 test_suite.Lwm2mDmOperations):\n    def setUp(self):\n        super().setUp(servers=2,\n                      extra_cmdline_args=['--access-entry', '/%s/0,0,%s' % (OID.Lwm2mGateway, AccessMask.READ),\n                                          '--access-entry', '/%s/1,0,%s' % (OID.Lwm2mGateway, AccessMask.READ)])\n\n    def runTest(self):\n        path = '/%s/%d/0/%d' % (self.prefixes[0], OID.Temperature, RID.Temperature.ApplicationType)\n        observe = self.observe_path(self.servers[1], path=path)\n\n        # Expecting silence\n        with self.assertRaises(socket.timeout):\n            self.servers[1].recv(timeout_s=2)\n\n        self.write_path(self.servers[0], path=path, content=b'app1')\n\n        pkt = self.servers[1].recv()\n        self.assertMsgEqual(Lwm2mContent(msg_id=ANY,\n                                         type=coap.Type.NON_CONFIRMABLE,\n                                         token=observe.token),\n                            pkt)\n\n\nclass ObserveWithDefaultAttributesTest(ObserveWithGatewayBase,\n                                       test_suite.Lwm2mDtlsSingleServerTest,\n                                       test_suite.Lwm2mDmOperations):\n    def runTest(self):\n        # Observe\n        observe_res_pkt = self.observe_path(self.serv, self.badc_data_path)\n        # no message should arrive here\n        with self.assertRaises(socket.timeout):\n            self.serv.recv(timeout_s=5)\n\n        # Attributes set\n        self.write_attributes_path(self.serv, path=self.badc_data_path, query=['pmax=1', 'pmin=1'])\n        # And should now start arriving each second\n        for _ in range(3):\n            pkt = self.serv.recv(timeout_s=2)\n            self.assertEqual(pkt.code, coap.Code.RES_CONTENT)\n            self.assertEqual(pkt.content, observe_res_pkt.content)\n        # Up until they're reset\n        self.write_attributes_path(self.serv, path=self.badc_data_path, query=['pmax=0', 'pmin=0'])\n        with self.assertRaises(socket.timeout):\n            self.serv.recv(timeout_s=5)\n\n\nclass ObserveOfflineWithStoredNotificationLimit(ObserveWithGatewayBase,\n                                                test_suite.Lwm2mDtlsSingleServerTest,\n                                                test_suite.Lwm2mDmOperations):\n    QUEUE_SIZE = 3\n\n    def setUp(self):\n        super().setUp(extra_cmdline_args=['--stored-notification-limit', str(self.QUEUE_SIZE)])\n        self.write_resource(server=self.serv, oid=OID.Server, iid=1,\n                            rid=RID.Server.NotificationStoring, content='1')\n\n    def runTest(self):\n        SKIP_NOTIFICATIONS = 3  # number of Notify messages that should be skipped\n\n        observe = self.observe_path(self.serv, path=self.button_input_counter_path)\n        self.assertEqual(int(observe.content.decode('utf-8')), 0)\n\n        # # generate enough notifications to cause dropping the oldest ones\n        self.clickButtonNTimesInOfflineMode(self.servers, self.QUEUE_SIZE + SKIP_NOTIFICATIONS)\n\n        seen_values = []\n\n        # exactly QUEUE_SIZE notifications should be sent\n        for _ in range(self.QUEUE_SIZE):\n            pkt = self.serv.recv(timeout_s=0.5)\n            self.assertMsgEqual(Lwm2mContent(msg_id=ANY,\n                                             type=coap.Type.NON_CONFIRMABLE,\n                                             token=observe.token),\n                                pkt)\n            seen_values.append(pkt.content)\n\n        with self.assertRaises(socket.timeout):\n            self.serv.recv(2)\n\n        # make sure the oldest values were dropped\n        for idx in range(SKIP_NOTIFICATIONS + 1):\n            self.assertNotIn(str(idx).encode('utf-8'), seen_values)\n\n\nclass ObserveOfflineWithStoredNotificationLimitAndMultipleServers(ObserveWithGatewayBase,\n                                                                  test_suite.Lwm2mDtlsSingleServerTest,\n                                                                  test_suite.Lwm2mDmOperations):\n    # value divisible by number of servers\n    QUEUE_SIZE = 4\n\n    def setUp(self):\n        super().setUp(servers=2, psk_identity=b'test-identity', psk_key=b'test-key',\n                      extra_cmdline_args=['--stored-notification-limit', str(self.QUEUE_SIZE),\n                                          '--access-entry', '/%s/0,0,%s' % (OID.Lwm2mGateway, AccessMask.READ),\n                                          '--access-entry', '/%s/1,0,%s' % (OID.Lwm2mGateway, AccessMask.READ)],)\n        self.write_resource(self.servers[0], OID.Server, 1, RID.Server.NotificationStoring, '1')\n        self.write_resource(self.servers[1], OID.Server, 2, RID.Server.NotificationStoring, '1')\n\n    def runTest(self):\n        SKIP_NOTIFICATIONS = 3  # number of Notify messages that should be skipped per server\n\n        observes = [\n            self.observe_path(self.servers[0], self.button_input_counter_path),\n            self.observe_path(self.servers[1], self.button_input_counter_path),\n        ]\n        self.assertEqual(int(observes[0].content.decode('utf-8')), 0)\n        self.assertEqual(int(observes[1].content.decode('utf-8')), 0)\n\n        # generate enough notifications to cause dropping the oldest ones\n        self.clickButtonNTimesInOfflineMode(self.servers, int(self.QUEUE_SIZE / 2 + SKIP_NOTIFICATIONS))\n\n        remaining_notifications = self.QUEUE_SIZE\n        seen_values = []\n\n        # exactly QUEUE_SIZE notifications in total should be sent\n        for observe, serv in zip(observes, self.servers):\n            try:\n                for _ in range(remaining_notifications):\n                    pkt = serv.recv(timeout_s=0.5)\n                    self.assertMsgEqual(Lwm2mContent(msg_id=ANY,\n                                                     type=coap.Type.NON_CONFIRMABLE,\n                                                     token=observe.token),\n                                        pkt)\n                    remaining_notifications -= 1\n                    seen_values.append(pkt.content)\n            except socket.timeout:\n                pass\n\n        self.assertEqual(remaining_notifications, 0)\n\n        for serv in self.servers:\n            with self.assertRaises(socket.timeout):\n                serv.recv(2)\n\n        # make sure the oldest values were dropped\n        for idx in range(SKIP_NOTIFICATIONS + 1):\n            self.assertNotIn(str(idx).encode('utf-8'), seen_values)\n\n\nclass ObserveOfflineWithStoringDisabled(ObserveWithGatewayBase,\n                                        test_suite.Lwm2mDtlsSingleServerTest,\n                                        test_suite.Lwm2mDmOperations):\n    def runTest(self):\n        self.write_resource(self.serv, OID.Server, 1, RID.Server.NotificationStoring, '0')\n        observe = self.observe_path(self.serv, path=\"/%s/%d/0/%d\" % \n                                    (self.prefixes[0], OID.Temperature, RID.Temperature.SensorValue))\n\n        self.communicate('enter-offline')\n        # wait long enough to cause dropping and receive any outstanding notifications\n        deadline = time.time() + 2.0\n        while True:\n            timeout = deadline - time.time()\n            if timeout <= 0.0:\n                break\n            try:\n                self.assertMsgEqual(Lwm2mNotify(token=observe.token),\n                                    self.serv.recv(timeout_s=timeout))\n            except socket.timeout:\n                pass\n\n        time.sleep(5.0)\n\n        self.communicate('exit-offline')\n        self.assertDtlsReconnect()\n        pkt = self.serv.recv(timeout_s=2)\n        self.assertMsgEqual(Lwm2mNotify(token=observe.token), pkt)\n\n\nclass ObserveOfflineUnchangingPmaxWithStoringDisabled(ObserveWithGatewayBase,\n                                                      test_suite.Lwm2mDtlsSingleServerTest,\n                                                      test_suite.Lwm2mDmOperations):\n    def runTest(self):\n        self.write_resource(self.serv, OID.Server, 1, RID.Server.NotificationStoring, '0')\n        self.write_attributes_path(self.serv, self.badc_data_path, ['pmax=1'])\n        observe = self.observe_path(self.servers[0], self.badc_data_path)\n\n        self.communicate('enter-offline')\n        # wait long enough to cause dropping and receive any outstanding notifications\n        deadline = time.time() + 2.0\n        while True:\n            timeout = deadline - time.time()\n            if timeout <= 0.0:\n                break\n            try:\n                self.assertMsgEqual(Lwm2mNotify(token=observe.token),\n                                    self.serv.recv(timeout_s=timeout))\n            except socket.timeout:\n                pass\n\n        time.sleep(5.0)\n\n        self.communicate('exit-offline')\n        self.assertDtlsReconnect()\n        pkt = self.serv.recv(timeout_s=2)\n        self.assertMsgEqual(Lwm2mNotify(token=observe.token), pkt)\n\n\nclass ObserveResourceInstance(ObserveWithGatewayBase,\n                              test_suite.Lwm2mDtlsSingleServerTest,\n                              test_suite.Lwm2mDmOperations):\n    def setUp(self):\n        super().setUp(maximum_version='1.1')\n\n    def runTest(self):\n        path = \"/%s/%d/0/%d/0\" % (self.prefixes[0],\n                                  OID.BinaryAppDataContainer,\n                                  RID.BinaryAppDataContainer.Data)\n        \n        # Observe resource instance\n        observe_pkt = self.observe_path(self.serv, path)\n        self.assertEqual(\"\", observe_pkt.content.decode())\n\n        # No notifications without value change\n        with self.assertRaises(socket.timeout):\n            self.serv.recv(timeout_s=5)\n\n        # Change value of a not observed resource instance\n        self.communicate(\"gw-badc-write 1 0 0 value1\")\n        with self.assertRaises(socket.timeout):\n            self.serv.recv(timeout_s=1)\n        self.communicate(\"gw-badc-write 0 0 1 value1\")\n        with self.assertRaises(socket.timeout):\n            self.serv.recv(timeout_s=1)\n\n        # Change value of an observed resource instance, notification is expected\n        self.communicate(\"gw-badc-write 0 0 0 value2\")\n        notify_pkt = self.serv.recv(timeout_s=1)\n        encoded =  base64.b64encode(b\"value2\")\n        self.assertMsgEqual(Lwm2mNotify(token=observe_pkt.token, content=encoded), notify_pkt)\n\n\nfrom .observe_attributes import Hqmax\n\nclass ObservePositiveHqmax(ObserveWithGatewayBase,\n                           Hqmax.Test):\n    def runTest(self):\n        SKIP_NOTIFICATIONS = 3  # number of Notify messages that should be skipped\n        HQMAX = 2\n\n        self.write_attributes_path(self.serv, self.button_input_counter_path, query=['hqmax=%d' % HQMAX])\n        observe = self.observe_path(self.serv, self.button_input_counter_path)\n        self.assertEqual(int(observe.content.decode('utf-8')), 0)\n\n        # # generate enough notifications to cause dropping the oldest ones\n        self.clickButtonNTimesInOfflineMode(self.servers, HQMAX + SKIP_NOTIFICATIONS)\n\n        seen_values = []\n\n        # exactly HQMAX notifications should be sent\n        for _ in range(HQMAX):\n            pkt = self.serv.recv(timeout_s=0.5)\n            self.assertMsgEqual(Lwm2mContent(msg_id=ANY,\n                                             type=coap.Type.NON_CONFIRMABLE,\n                                             token=observe.token),\n                                pkt)\n            seen_values.append(pkt.content)\n\n        with self.assertRaises(socket.timeout):\n            self.serv.recv(2)\n\n        # make sure the oldest values were dropped\n        for idx in range(SKIP_NOTIFICATIONS + 1):\n            self.assertNotIn(str(idx).encode('utf-8'), seen_values)\n\n\nclass ObserveZeroHqmax(ObserveWithGatewayBase,\n                       Hqmax.Test):\n    def runTest(self):\n        HQMAX = 0\n        SKIP_NOTIFICATIONS = 3  # number of Notify messages that should be skipped\n\n        self.write_attributes_path(self.serv, self.button_input_counter_path, query=['hqmax=%d' % HQMAX])\n\n        self.observe_path(self.serv, self.button_input_counter_path)\n\n        # # generate enough notifications to cause dropping the oldest ones\n        self.clickButtonNTimesInOfflineMode(self.servers, HQMAX + SKIP_NOTIFICATIONS)\n\n        # no notifications should be sent\n        with self.assertRaises(socket.timeout):\n            self.serv.recv(2)\n\n\nclass ObserveHqmaxAndMultipleServers(ObserveWithGatewayBase,\n                                     Hqmax.Test):\n    def setUp(self):\n        super().setUp(servers=2, \n                      extra_cmdline_args=['--access-entry', '/%s/0,0,%s' % (OID.Lwm2mGateway, AccessMask.READ),\n                                          '--access-entry', '/%s/1,0,%s' % (OID.Lwm2mGateway, AccessMask.READ)])\n\n    def runTest(self):\n        hqmaxes = [2, 3]  # number of Notify messages for each server that should be sent\n        total_notifications = sum(hqmaxes)  # number of total Notify messages that should be sent\n        skips = [total_notifications - hqmax for hqmax in\n                 hqmaxes]  # number of Notify messages for each server that should be skipped\n        self.write_attributes_path(self.servers[0], path=self.button_input_counter_path,\n                              query=['hqmax=%d' % hqmaxes[0]])\n        self.write_attributes_path(self.servers[1], path=self.button_input_counter_path,\n                              query=['hqmax=%d' % hqmaxes[1]])\n\n        observes = [\n            self.observe_path(self.servers[0], path=self.button_input_counter_path),\n            self.observe_path(self.servers[1], path=self.button_input_counter_path),\n        ]\n        self.assertEqual(int(observes[0].content.decode('utf-8')), 0)\n        self.assertEqual(int(observes[1].content.decode('utf-8')), 0)\n\n        # # generate enough notifications to cause dropping the oldest ones\n        self.clickButtonNTimesInOfflineMode(self.servers, total_notifications)\n\n        seen_values = []\n\n        remaining_notifications = total_notifications\n        for observe, serv, hqmax in zip(observes, self.servers, hqmaxes):\n            try:\n                for _ in range(hqmax):\n                    pkt = serv.recv(timeout_s=0.5)\n                    self.assertMsgEqual(Lwm2mContent(msg_id=ANY,\n                                                     type=coap.Type.NON_CONFIRMABLE,\n                                                     token=observe.token),\n                                        pkt)\n                    remaining_notifications -= 1\n                    seen_values.append(pkt.content)\n            except socket.timeout:\n                pass\n\n        self.assertEqual(remaining_notifications, 0)\n\n        for serv in self.servers:\n            with self.assertRaises(socket.timeout):\n                serv.recv(2)\n\n        # make sure the oldest values were dropped\n        for _, skip in zip(observes, skips):\n            for idx in range(skip):\n                self.assertNotIn(str(idx).encode('utf-8'), seen_values)\n\n\nclass ObserveHqmaxGreaterThanQueueSize(ObserveWithGatewayBase,\n                                       Hqmax.Test):\n    def runTest(self):\n        HQMAX = 8\n        SKIP_NOTIFICATIONS = HQMAX - self.QUEUE_SIZE  # number of Notify messages that should be skipped\n\n        self.write_attributes_path(self.serv, path=self.button_input_counter_path,\n                                   query=['hqmax=%d' % HQMAX])\n        observe = self.observe_path(self.serv, path=self.button_input_counter_path)\n        self.assertEqual(int(observe.content.decode('utf-8')), 0)\n\n        # # generate enough notifications to cause dropping the oldest ones\n        self.clickButtonNTimesInOfflineMode(self.servers, HQMAX)\n\n        seen_values = []\n\n        # exactly QUEUE_SIZE notifications should be sent\n        for _ in range(self.QUEUE_SIZE):\n            pkt = self.serv.recv(timeout_s=0.5)\n            self.assertMsgEqual(Lwm2mContent(msg_id=ANY,\n                                             type=coap.Type.NON_CONFIRMABLE,\n                                             token=observe.token),\n                                pkt)\n            seen_values.append(pkt.content)\n\n        with self.assertRaises(socket.timeout):\n            self.serv.recv(2)\n\n        # make sure the oldest values were dropped\n        for idx in range(SKIP_NOTIFICATIONS + 1):\n            self.assertNotIn(str(idx).encode('utf-8'), seen_values)\n\n\nclass ObserveEdgeRisingTest(ObserveWithGatewayBase,\n                            test_suite.Lwm2mDtlsSingleServerTest,\n                            test_suite.Lwm2mDmOperations):\n    def runTest(self):\n        # Observe with edge = 1 (notify on rising edge)\n        self.write_attributes_path(self.serv, path=self.button_input_state_path,\n                                   query=['edge=1'])\n        observe = self.observe_path(self.serv, path=self.button_input_state_path)\n\n        # Change boolean resource value to 1\n        self.communicate(\"gw_press_button 0\")\n        # Expect a notification on transition 0 -> 1\n        self.assertMsgEqual(Lwm2mNotify(token=observe.token), self.serv.recv(timeout_s=2))\n\n        # Change boolean resource value to 0\n        self.communicate(\"gw_release_button 0\")\n        # No notification should be sent on transition 1 -> 0\n        with self.assertRaises(socket.timeout):\n            self.serv.recv(2)\n\n\nclass ObserveEdgeFallingTest(ObserveWithGatewayBase,\n                             test_suite.Lwm2mDtlsSingleServerTest,\n                             test_suite.Lwm2mDmOperations):\n    def runTest(self):\n        # Set initial boolean resource value to 1\n        self.communicate(\"gw_press_button 0\")\n\n        # Observe with edge = 0 (notify on falling edge)\n        self.write_attributes_path(self.serv, path=self.button_input_state_path,\n                                   query=['edge=0'])\n        observe = self.observe_path(self.serv, path=self.button_input_state_path)\n\n        # Change boolean resource value to 0\n        self.communicate(\"gw_release_button 0\")\n        # Expect a notification on transition 1 -> 0\n        self.assertMsgEqual(Lwm2mNotify(token=observe.token), self.serv.recv(timeout_s=2))\n\n        # Change boolean resource value to 1\n        self.communicate(\"gw_press_button 0\")\n        # No notification should be sent on transition 0 -> 1\n        with self.assertRaises(socket.timeout):\n            self.serv.recv(2)\n"
  },
  {
    "path": "tests/integration/suites/default/modify_servers.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nimport socket\n\nfrom framework.lwm2m_test import *\n\n\nclass ModifyServersTest(test_suite.Lwm2mTest):\n    def setUp(self):\n        self.setup_demo_with_servers(servers=2, auto_register=False)\n\n    def tearDown(self):\n        self.coap_ping(self.servers[0])\n        self.coap_ping(self.servers[1])\n        self.request_demo_shutdown()\n        self.assertDemoDeregisters(self.servers[0], path='/rd/demo')\n        self.assertDemoDeregisters(self.servers[1], path='/rd/server3')\n        self.teardown_demo_with_servers(auto_deregister=False)\n\n    def runTest(self):\n        self.assertDemoRegisters(server=self.servers[0], location='/rd/demo')\n        self.assertDemoRegisters(server=self.servers[1], location='/rd/server2')\n\n        # remove second server\n        self.communicate(\"trim-servers 1\")\n        self.assertDemoDeregisters(server=self.servers[1], path='/rd/server2')\n        self.assertDemoUpdatesRegistration(server=self.servers[0], content=ANY)\n\n        # add another server\n        self.communicate(\"add-server coap://127.0.0.1:%d\" % self.servers[1].get_listen_port())\n        self.assertDemoUpdatesRegistration(server=self.servers[0], content=ANY)\n        self.assertDemoRegisters(server=self.servers[1], location='/rd/server3')\n"
  },
  {
    "path": "tests/integration/suites/default/msg_cache.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nfrom framework_tools.lwm2m.tlv import TLV\nfrom framework.lwm2m_test import *\nfrom framework.test_utils import *\nfrom . import block_response as br\n\n\nclass CacheTest(test_suite.Lwm2mSingleServerTest,\n                test_suite.Lwm2mDmOperations):\n    def setUp(self):\n        super().setUp(extra_cmdline_args=['--cache-size', '4096'])\n        self.create_instance(self.serv, oid=OID.Test, iid=1)\n\n    def runTest(self):\n        req = Lwm2mRead(ResPath.Test[1].Counter)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mContent.matching(req)(content=b'0'),\n                            self.serv.recv())\n\n        # execute Increment Counter\n        inc_req = Lwm2mExecute(ResPath.Test[1].IncrementCounter)\n        self.serv.send(inc_req)\n        inc_res = self.serv.recv()\n        self.assertMsgEqual(Lwm2mChanged.matching(inc_req)(), inc_res)\n\n        req = Lwm2mRead(ResPath.Test[1].Counter)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mContent.matching(req)(content=b'1'),\n                            self.serv.recv())\n\n        # retransmit Increment Counter\n        self.serv.send(inc_req)\n        # should receive identical response\n        self.assertMsgEqual(inc_res, self.serv.recv())\n\n        # Counter should not increment second time\n        req = Lwm2mRead(ResPath.Test[1].Counter)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mContent.matching(req)(content=b'1'),\n                            self.serv.recv())\n\n        # a new Execute should increment it though\n        req = Lwm2mExecute(ResPath.Test[1].IncrementCounter)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.serv.recv())\n\n        req = Lwm2mRead(ResPath.Test[1].Counter)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mContent.matching(req)(content=b'2'),\n                            self.serv.recv())\n\n\nclass MultipleServerCacheTest(test_suite.Lwm2mTest):\n    def setUp(self):\n        self.setup_demo_with_servers(servers=2,\n                                     extra_cmdline_args=['--cache-size', '4096'])\n\n    def tearDown(self):\n        self.teardown_demo_with_servers()\n\n    def runTest(self):\n        s0_req = Lwm2mRead(ResPath.Device.SerialNumber)\n        s1_req = Lwm2mWrite(ResPath.Server[2].Lifetime, '60')\n\n        s0_req.msg_id = 1234\n        s1_req.msg_id = 1234\n\n        self.servers[0].send(s0_req)\n        s0_res = self.servers[0].recv()\n        self.assertMsgEqual(Lwm2mContent.matching(s0_req)(), s0_res)\n\n        # send a different message with same ID from a different server\n        # client should be able to distinguish those and not consider\n        # this a retransmission of previous request\n        self.servers[1].send(s1_req)\n        s1_res = self.servers[1].recv()\n        self.assertMsgEqual(Lwm2mChanged.matching(s1_req)(), s1_res)\n        self.assertDemoUpdatesRegistration(self.servers[1], lifetime=60)\n\n        # we should still be able to get both responses after retransmitting\n        self.servers[0].send(s0_req)\n        self.assertMsgEqual(s0_res, self.servers[0].recv())\n\n        self.servers[1].send(s1_req)\n        self.assertMsgEqual(s1_res, self.servers[1].recv())\n\n        # ...even if the requests are swapped\n        self.servers[0].send(s1_req)\n        self.assertMsgEqual(s0_res, self.servers[0].recv())\n\n        self.servers[1].send(s0_req)\n        self.assertMsgEqual(s1_res, self.servers[1].recv())\n\n\nclass CachedBlockResponse(br.BlockResponseTest,\n                          test_suite.Lwm2mDmOperations):\n    def setUp(self):\n        super().setUp(bytes_size=2048,\n                      extra_cmdline_args=['--cache-size', '4096'])\n\n    def runTest(self):\n        # Induce a block transfer.\n        req = Lwm2mRead(ResPath.Test[0].ResBytes)\n        self.serv.send(req)\n\n        res0 = self.serv.recv()\n        self.assertMsgEqual(Lwm2mContent.matching(req)(), res0)\n\n        # Retransmit, expecting exactly the same message\n        self.serv.send(req)\n        res1 = self.serv.recv()\n        self.assertMsgEqual(res0, res1)\n\n        # Read everything till the end\n        self.read_blocks(iid=0, base_seq=1)\n"
  },
  {
    "path": "tests/integration/suites/default/notification_timestamps.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nimport json\nimport time\n\nfrom framework.lwm2m_test import *\n\n\ndef as_json(pkt):\n    return json.loads(pkt.content.decode('utf-8'))\n\n\nclass NotificationTimestampsInLegacyJsonTest(test_suite.Lwm2mSingleServerTest,\n                                             test_suite.Lwm2mDmOperations):\n    def runTest(self):\n        self.create_instance(self.serv, oid=OID.Test, iid=0)\n        res = as_json(self.observe(self.serv, oid=OID.Test, iid=0, rid=RID.Test.Counter,\n                                   accept=coap.ContentFormat.APPLICATION_LWM2M_JSON))\n        self.assertEqual(res['bn'], ResPath.Test[0].Counter)\n        self.assertIsInstance(res['e'], list)\n        self.assertEqual(len(res['e']), 1)\n        self.assertNotIn('n', res['e'][0])\n        self.assertEqual(res['e'][0]['v'], 0)\n        self.assertAlmostEqual(res['e'][0]['t'], time.time(), delta=2)\n\n        self.execute_resource(\n            self.serv,\n            oid=OID.Test,\n            iid=0,\n            rid=RID.Test.IncrementCounter)\n        notification = self.serv.recv()\n        self.assertIsInstance(notification, Lwm2mNotify)\n        res2 = as_json(notification)\n        self.assertEqual(res2['e'][0]['v'], 1)\n        self.assertAlmostEqual(res2['e'][0]['t'], time.time(), delta=2)\n        self.assertGreater(res2['e'][0]['t'], res['e'][0]['t'])\n\n        # Check if the responses have identical structure\n        res2['e'][0]['v'] = 0\n        res2['e'][0]['t'] = res['e'][0]['t']\n        self.assertEqual(res, res2)\n\n\nclass NotificationTimestampsInSenmlJsonTest(test_suite.Lwm2mSingleServerTest,\n                                            test_suite.Lwm2mDmOperations):\n    def runTest(self):\n        self.create_instance(self.serv, oid=OID.Test, iid=0)\n        res = as_json(self.observe(self.serv, oid=OID.Test, iid=0, rid=RID.Test.Counter,\n                                   accept=coap.ContentFormat.APPLICATION_LWM2M_SENML_JSON))\n        self.assertIsInstance(res, list)\n        self.assertEqual(len(res), 1)\n        self.assertEqual(res[0]['bn'], ResPath.Test[0].Counter)\n        self.assertNotIn('n', res[0])\n        self.assertEqual(res[0]['v'], 0)\n        self.assertAlmostEqual(res[0]['bt'], time.time(), delta=2)\n\n        self.execute_resource(\n            self.serv,\n            oid=OID.Test,\n            iid=0,\n            rid=RID.Test.IncrementCounter)\n        notification = self.serv.recv()\n        self.assertIsInstance(notification, Lwm2mNotify)\n        res2 = as_json(notification)\n        self.assertEqual(res2[0]['v'], 1)\n        self.assertAlmostEqual(res2[0]['bt'], time.time(), delta=2)\n        self.assertGreater(res2[0]['bt'], res[0]['bt'])\n\n        # Check if the responses have identical structure\n        res2[0]['v'] = 0\n        res2[0]['bt'] = res[0]['bt']\n        self.assertEqual(res, res2)\n\n\nclass NotificationTimestampsInSenmlCborTest(test_suite.Lwm2mSingleServerTest,\n                                            test_suite.Lwm2mDmOperations):\n    def runTest(self):\n        self.create_instance(self.serv, oid=OID.Test, iid=0)\n        res = cbor2.loads(self.observe(self.serv, oid=OID.Test, iid=0, rid=RID.Test.Counter,\n                                       accept=coap.ContentFormat.APPLICATION_LWM2M_SENML_CBOR).content)\n        self.assertIsInstance(res, list)\n        self.assertEqual(len(res), 1)\n        self.assertEqual(res[0][SenmlLabel.BASE_NAME.value],\n                         ResPath.Test[0].Counter)\n        self.assertNotIn(SenmlLabel.NAME.value, res[0])\n        self.assertEqual(res[0][SenmlLabel.VALUE.value], 0)\n        self.assertAlmostEqual(\n            res[0][SenmlLabel.BASE_TIME.value], time.time(), delta=2)\n\n        self.execute_resource(\n            self.serv,\n            oid=OID.Test,\n            iid=0,\n            rid=RID.Test.IncrementCounter)\n        notification = self.serv.recv()\n        self.assertIsInstance(notification, Lwm2mNotify)\n        res2 = cbor2.loads(notification.content)\n        self.assertEqual(res2[0][SenmlLabel.VALUE.value], 1)\n        self.assertAlmostEqual(\n            res2[0][SenmlLabel.BASE_TIME.value], time.time(), delta=2)\n        self.assertGreater(res2[0][SenmlLabel.BASE_TIME.value],\n                           res[0][SenmlLabel.BASE_TIME.value])\n\n        # Check if the responses have identical structure\n        res2[0][SenmlLabel.VALUE.value] = 0\n        res2[0][SenmlLabel.BASE_TIME.value] = res[0][SenmlLabel.BASE_TIME.value]\n        self.assertEqual(res, res2)\n"
  },
  {
    "path": "tests/integration/suites/default/notifications.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\nfrom framework.lwm2m_test import *\nfrom framework_tools.lwm2m.coap.transport import Transport\n\nimport re\nfrom string import Template\nfrom .access_control import AccessMask, make_acl_entry, AccessControl\n\n\nclass CancellingConfirmableNotifications(test_suite.Lwm2mSingleServerTest,\n                                         test_suite.Lwm2mDmOperations):\n    def setUp(self):\n        super().setUp(extra_cmdline_args=['--confirmable-notifications'])\n\n    def runTest(self):\n        self.create_instance(self.serv, oid=OID.Test, iid=0)\n\n        observe1 = self.observe(self.serv, oid=OID.Test, iid=0, rid=RID.Test.Counter)\n        observe2 = self.observe(self.serv, oid=OID.Test, iid=0, rid=RID.Test.Counter)\n\n        self.execute_resource(self.serv, oid=OID.Test, iid=0, rid=RID.Test.IncrementCounter)\n        notify1 = self.serv.recv()\n        if bytes(notify1.token) != bytes(observe1.token):\n            # which observation is handled first isn't exactly deterministic\n            observe1, observe2 = observe2, observe1\n\n        self.assertIsInstance(notify1, Lwm2mNotify)\n        self.assertEqual(bytes(notify1.token), bytes(observe1.token))\n        self.serv.send(Lwm2mEmpty.matching(notify1)())\n\n        notify2 = self.serv.recv()\n        self.assertIsInstance(notify2, Lwm2mNotify)\n        self.assertEqual(bytes(notify2.token), bytes(observe2.token))\n        self.serv.send(Lwm2mEmpty.matching(notify2)())\n\n        self.assertEqual(notify1.content, notify2.content)\n\n        # Cancel the next notification\n        self.execute_resource(self.serv, oid=OID.Test, iid=0, rid=RID.Test.IncrementCounter)\n        notify1 = self.serv.recv()\n        self.assertIsInstance(notify1, Lwm2mNotify)\n        self.assertEqual(bytes(notify1.token), bytes(observe1.token))\n        self.assertNotEqual(notify1.content, notify2.content)\n        self.serv.send(Lwm2mReset.matching(notify1)())\n\n        # the second observation should still produce notifications\n        notify2 = self.serv.recv()\n        self.assertIsInstance(notify2, Lwm2mNotify)\n        self.assertEqual(bytes(notify2.token), bytes(observe2.token))\n        self.assertEqual(notify1.content, notify2.content)\n        self.serv.send(Lwm2mEmpty.matching(notify2)())\n\n        self.execute_resource(self.serv, oid=OID.Test, iid=0, rid=RID.Test.IncrementCounter)\n        notify2 = self.serv.recv()\n        self.assertIsInstance(notify2, Lwm2mNotify)\n        self.assertEqual(bytes(notify2.token), bytes(observe2.token))\n        self.assertNotEqual(notify1.content, notify2.content)\n        self.serv.send(Lwm2mReset.matching(notify2)())\n\n\nclass ClientInitiatedNotificationCancellation:\n    class Test(test_suite.Lwm2mDmOperations):\n        def make_cancellation_response(self, notify):\n            return None\n\n        def assertConIfUdp(self, msg):\n            if self.serv.transport == Transport.UDP:\n                self.assertEqual(msg.type, coap.Type.CONFIRMABLE)\n\n        def assertNonIfUdp(self, msg):\n            if self.serv.transport == Transport.UDP:\n                self.assertEqual(msg.type, coap.Type.NON_CONFIRMABLE)\n\n        def setUp(self, *args, **kwargs):\n            super().setUp(*args, **kwargs)\n\n        def runTest(self):\n            observe = self.observe(self.serv, oid=OID.Location, iid=0, rid=RID.Location.Latitude)\n\n            notify = self.serv.recv(timeout_s=2)\n            self.assertIsInstance(notify, Lwm2mNotify)\n            self.assertNonIfUdp(notify)\n            self.assertEqual(bytes(notify.token), bytes(observe.token))\n            self.assertEqual(len(notify.get_options(coap.Option.OBSERVE)), 1)\n\n            # Unregister the Location object, this should make a read on the\n            # resource to fail, which should make the client cancel the\n            # observation on its initiative\n            self.communicate('unregister-object %d' % OID.Location)\n            self.assertDemoUpdatesRegistration(content=ANY)\n\n            notify = self.serv.recv(timeout_s=2)\n            self.assertEqual(notify.code, coap.Code.RES_NOT_FOUND)\n            self.assertConIfUdp(notify)\n            self.assertEqual(bytes(notify.token), bytes(observe.token))\n            self.assertEqual(len(notify.get_options(coap.Option.OBSERVE)), 0)\n\n            res = self.make_cancellation_response(notify)\n            if res is not None:\n                self.serv.send(res)\n\n            # We should not receive any more notifications\n            with self.assertRaises(socket.timeout):\n                self.serv.recv(timeout_s=5)\n\n    class TestWithAck(Test):\n        def make_cancellation_response(self, notify):\n            return Lwm2mEmpty.matching(notify)()\n\n    # Some servers answer such a notification with a RST, not an ACK.\n    class TestWithRst(Test):\n        def make_cancellation_response(self, notify):\n            return Lwm2mReset.matching(notify)()\n\n\nclass ClientInitiatedNotificationCancellationUdpAck(ClientInitiatedNotificationCancellation.TestWithAck,\n                                                    test_suite.Lwm2mSingleServerTest):\n    pass\n\n\nclass ClientInitiatedNotificationCancellationDtlsAck(ClientInitiatedNotificationCancellation.TestWithAck,\n                                                    test_suite.Lwm2mDtlsSingleServerTest):\n    pass\n\n\nclass ClientInitiatedNotificationCancellationUdpRst(ClientInitiatedNotificationCancellation.TestWithRst,\n                                                    test_suite.Lwm2mSingleServerTest):\n    pass\n\n\nclass ClientInitiatedNotificationCancellationDtlsRst(ClientInitiatedNotificationCancellation.TestWithRst,\n                                                    test_suite.Lwm2mDtlsSingleServerTest):\n    pass\n\n\nclass ClientInitiatedNotificationCancellationTcp(ClientInitiatedNotificationCancellation.Test,\n                                                    test_suite.Lwm2mSingleTcpServerTest):\n    def setUp(self, *args, **kwargs):\n        super().setUp(binding='T', *args, **kwargs)\n\n\nclass ClientInitiatedNotificationCancellationTls(ClientInitiatedNotificationCancellation.Test,\n                                                    test_suite.Lwm2mTlsSingleServerTest):\n    def setUp(self, *args, **kwargs):\n        super().setUp(binding='T', *args, **kwargs)\n\n\nclass SelfNotifyDisabled(test_suite.Lwm2mSingleServerTest, test_suite.Lwm2mDmOperations):\n    def runTest(self):\n        self.create_instance(self.serv, oid=OID.Test, iid=42)\n\n        self.observe(self.serv, oid=OID.Test, iid=42, rid=RID.Test.ResInt)\n\n        self.write_resource(self.serv, oid=OID.Test, iid=42, rid=RID.Test.ResInt, content=b'42')\n        # no notification expected in this case\n        with self.assertRaises(socket.timeout):\n            self.serv.recv(timeout_s=3)\n\n\nclass SelfNotifyEnabled(test_suite.Lwm2mSingleServerTest, test_suite.Lwm2mDmOperations):\n    def setUp(self):\n        super().setUp(extra_cmdline_args=['--enable-self-notify'])\n\n    def runTest(self):\n        self.create_instance(self.serv, oid=OID.Test, iid=42)\n\n        observe = self.observe(self.serv, oid=OID.Test, iid=42, rid=RID.Test.ResInt)\n\n        self.write_resource(self.serv, oid=OID.Test, iid=42, rid=RID.Test.ResInt, content=b'42')\n        notify = self.serv.recv()\n        self.assertIsInstance(notify, Lwm2mNotify)\n        self.assertEqual(bytes(notify.token), bytes(observe.token))\n        self.assertEqual(notify.content, b'42')\n\n\nclass RegisterCancelsNotifications(test_suite.Lwm2mSingleServerTest, test_suite.Lwm2mDmOperations):\n    def runTest(self):\n        self.create_instance(self.serv, oid=OID.Test, iid=1)\n        self.create_instance(self.serv, oid=OID.Test, iid=2)\n\n        self.observe(self.serv, oid=OID.Test, iid=1, rid=RID.Test.Counter)\n        self.execute_resource(self.serv, oid=OID.Test, iid=1, rid=RID.Test.IncrementCounter)\n        self.assertIsInstance(self.serv.recv(), Lwm2mNotify)\n\n        self.observe(self.serv, oid=OID.Test, iid=2, rid=RID.Test.Counter)\n        self.execute_resource(self.serv, oid=OID.Test, iid=2, rid=RID.Test.IncrementCounter)\n        self.assertIsInstance(self.serv.recv(), Lwm2mNotify)\n\n        self.communicate('send-update')\n        update = self.assertDemoUpdatesRegistration(respond=False, content=ANY)\n\n        # Notifications shall work while Updating\n        self.execute_resource(self.serv, oid=OID.Test, iid=1, rid=RID.Test.IncrementCounter)\n        self.execute_resource(self.serv, oid=OID.Test, iid=2, rid=RID.Test.IncrementCounter)\n        notifications = 0\n        while True:\n            pkt = self.serv.recv()\n            if notifications < 2 and isinstance(pkt, Lwm2mNotify):\n                notifications += 1\n                if notifications == 2:\n                    break\n            else:\n                self.assertMsgEqual(pkt, update)\n\n        # force a Register\n        self.serv.send(Lwm2mErrorResponse.matching(update)(coap.Code.RES_FORBIDDEN))\n        register = self.assertDemoRegisters(respond=False)\n\n        # Notifications shall no longer work here\n        self.execute_resource(self.serv, oid=OID.Test, iid=1, rid=RID.Test.IncrementCounter)\n        self.execute_resource(self.serv, oid=OID.Test, iid=2, rid=RID.Test.IncrementCounter)\n        pkt = self.serv.recv()\n        self.assertMsgEqual(pkt, register)\n\n        self.serv.send(Lwm2mCreated.matching(pkt)(location=self.DEFAULT_REGISTER_ENDPOINT))\n\n        # Check that notifications still don't work\n        self.execute_resource(self.serv, oid=OID.Test, iid=1, rid=RID.Test.IncrementCounter)\n        self.execute_resource(self.serv, oid=OID.Test, iid=2, rid=RID.Test.IncrementCounter)\n        with self.assertRaises(socket.timeout):\n            print(self.serv.recv(timeout_s=5))\n\n\nclass LifetimeExpirationCancelsObserveWhileStoring(test_suite.Lwm2mDtlsSingleServerTest,\n                                                   test_suite.Lwm2mDmOperations):\n    def setUp(self):\n        super().setUp(lifetime=30)\n\n    def runTest(self):\n        expiration_time = float(self.communicate('registration-expiration-time 1',\n                                                 match_regex='REGISTRATION_EXPIRATION_TIME=(.*)\\n').group(\n            1))\n\n        observe = self.observe(self.serv, oid=OID.Device, iid=0, rid=RID.Device.CurrentTime)\n        observe_start_time = time.time()\n\n        notifications_generated = 0\n        self.communicate('enter-offline')\n        # Receive the notifications that might have managed to be sent before entering offline mode\n        deadline = time.time() + 5\n        while time.time() < deadline:\n            try:\n                self.assertIsInstance(self.serv.recv(deadline=deadline), Lwm2mNotify)\n                notifications_generated += 1\n            except socket.timeout:\n                break\n\n        # Check that the notifications are stored alright during offline mode\n        deadline = expiration_time + 5\n        while True:\n            timeout_s = max(0.0, deadline - time.time())\n            if self.read_log_until_match(\n                    b'Notify for token %s scheduled:' % (binascii.hexlify(observe.token),),\n                    timeout_s=timeout_s) is not None:\n                notifications_generated += 1\n            else:\n                break\n\n        # Assert that the notification has been implicitly cancelled\n        self.assertIsNotNone(\n            self.read_log_until_match(b'Observe cancel: %s\\n' % (binascii.hexlify(observe.token),),\n                                      timeout_s=5))\n\n        self.assertAlmostEqual(notifications_generated, expiration_time - observe_start_time,\n                               delta=2)\n\n        self.communicate('exit-offline')\n\n        # The client will register again\n        self.assertDtlsReconnect()\n        self.assertDemoRegisters(lifetime=30)\n        # Check that no notifications are sent\n        with self.assertRaises(socket.timeout):\n            print(self.serv.recv(timeout_s=5))\n\n\nclass UdpNotificationErrorTest(test_suite.Lwm2mSingleServerTest, test_suite.Lwm2mDmOperations):\n    def setUp(self):\n        super().setUp(extra_cmdline_args=['--confirmable-notifications'])\n\n    def runTest(self):\n        self.write_resource(self.serv, oid=OID.Server, iid=1, rid=RID.Server.DefaultMaxPeriod,\n                            content=b'2')\n\n        self.create_instance(self.serv, oid=OID.Test, iid=1)\n        orig_notif = self.observe(self.serv, oid=OID.Test, iid=1, rid=RID.Test.Counter)\n        self.delete_instance(self.serv, oid=OID.Test, iid=1)\n\n        notif = self.serv.recv(timeout_s=5)\n        self.assertEqual(notif.code, coap.Code.RES_NOT_FOUND)\n        self.assertEqual(notif.token, orig_notif.token)\n        self.serv.send(Lwm2mEmpty.matching(notif)())\n\n\nclass TcpNotificationErrorTest(test_suite.Lwm2mSingleTcpServerTest, test_suite.Lwm2mDmOperations):\n    def setUp(self):\n        super().setUp(extra_cmdline_args=['--confirmable-notifications'], binding='T')\n\n    def runTest(self):\n        self.write_resource(self.serv, oid=OID.Server, iid=1, rid=RID.Server.DefaultMaxPeriod,\n                            content=b'2')\n\n        self.create_instance(self.serv, oid=OID.Test, iid=1)\n        orig_notif = self.observe(self.serv, oid=OID.Test, iid=1, rid=RID.Test.Counter)\n        self.delete_instance(self.serv, oid=OID.Test, iid=1)\n\n        notif = self.serv.recv(timeout_s=5)\n        self.assertEqual(notif.code, coap.Code.RES_NOT_FOUND)\n        self.assertEqual(notif.token, orig_notif.token)\n\n\nclass NextPlannedNotify(test_suite.Lwm2mSingleServerTest, test_suite.Lwm2mDmOperations):\n    def setUp(self):\n        super().setUp()\n\n    def runTest(self):\n        # set up observe without attributes\n        self.observe(self.serv, oid=OID.Device, iid=0, rid=RID.Device.ErrorCode)\n\n        # ensure there is no planned notify\n        self.communicate('next-planned-pmax-notify',\n                         match_regex='NEXT_PLANNED_PMAX_NOTIFY=TIME_INVALID\\n')\n        self.communicate('next-planned-notify',\n                         match_regex='NEXT_PLANNED_NOTIFY=TIME_INVALID\\n')\n        \n        # add an observation with pmin&pmax\n        write_atts = self.write_attributes(self.serv, oid=OID.Device, iid=0, rid=RID.Device.Timezone,\n                            query=['pmin=5','pmax=5'])\n        self.assertEqual(write_atts.code, coap.Code.RES_CHANGED)\n\n        second_notif = self.observe(self.serv, oid=OID.Device, iid=0, rid=RID.Device.Timezone)\n        self.assertEqual(second_notif.code, coap.Code.RES_CONTENT)\n\n        expected_notify_time = time.time() + 5.0\n\n        time.sleep(0.1)\n\n        # verify that Anjay schedules the notifications\n        planned_notify_time = float(\n            self.communicate('next-planned-notify',\n                             match_regex='NEXT_PLANNED_NOTIFY=(.*)\\n').group(1))\n        self.assertAlmostEqual(expected_notify_time, planned_notify_time, delta=0.1)\n\n        # wait for the notification and check if next-planned-notify was rescheduled\n        notif = self.serv.recv(timeout_s=6)\n        self.assertEqual(notif.code, coap.Code.RES_CONTENT)\n        self.assertEqual(notif.token, second_notif.token)\n\n        expected_notify_time = expected_notify_time + 5\n        planned_notify_time = float(\n            self.communicate('next-planned-notify',\n                             match_regex='NEXT_PLANNED_NOTIFY=(.*)\\n').group(1))\n        self.assertAlmostEqual(expected_notify_time, planned_notify_time, delta=0.1)\n\n        # cancel the observation\n        self.observe(self.serv, oid=OID.Device, iid=0, rid=RID.Device.Timezone, observe=1, token=notif.token)\n        time.sleep(0.2)\n\n        # ensure there is no planned notify\n        self.communicate('next-planned-pmax-notify',\n                         match_regex='NEXT_PLANNED_PMAX_NOTIFY=TIME_INVALID\\n')\n        self.communicate('next-planned-notify',\n                         match_regex='NEXT_PLANNED_NOTIFY=TIME_INVALID\\n')\n\n\nclass ConfirmableNotificationStatus():\n\n    class Test(test_suite.Lwm2mDmOperations):\n        PREFIX = \"confirmable_notification_status_callback:\"\n        CONFIRMABLE_NOTIFICATION_SUCCESS = Template(f\"{PREFIX} Acknowledgement for notification was \"\n                                                    \"received for server SSID $ssid, paths count \"\n                                                    \"$paths_count:\")\n        CONFIRMABLE_NOTIFICATION_FAILURE = Template(f\"{PREFIX} There was some error during receiving \"\n                                                    \"acknowledgement/sending notification for server \"\n                                                    \"SSID $ssid, paths count $paths_count:\")\n        LOG_TIMEOUT = 10\n\n        def read_log_until_confirmable_notification_success(\n                self, ssid, paths_count):\n            if self.read_log_until_match(\n                regex=re.escape(\n                    self.CONFIRMABLE_NOTIFICATION_SUCCESS.substitute(\n                        ssid=ssid, paths_count=paths_count).encode()),\n                    timeout_s=self.LOG_TIMEOUT) is None:\n                raise self.failureException('string not found')\n\n        def read_log_until_confirmable_notification_fail(\n                self, ssid, paths_count):\n            if self.read_log_until_match(\n                regex=re.escape(\n                    self.CONFIRMABLE_NOTIFICATION_FAILURE.substitute(\n                        ssid=ssid, paths_count=paths_count).encode()),\n                    timeout_s=self.LOG_TIMEOUT) is None:\n                raise self.failureException('string not found')\n\n        def read_log_until_path_occur(self, path):\n            if self.read_log_until_match(\n                    regex=re.escape((f'{self.PREFIX} ' + path).encode()),\n                    timeout_s=self.LOG_TIMEOUT) is None:\n                raise self.failureException('string not found')\n\n        # This method is called to be sure that there is no unserved confirmed notification when\n        # leaving test case\n        def receive_notification_and_respond(self, server):\n            notify = server.recv()\n            server.send(Lwm2mEmpty.matching(notify)())\n\n    class BasicTest(Test):\n        def runTest(self):\n            observe_path = self.make_path(\n                OID.Location, 0, RID.Location.Latitude)\n            observe = self.observe_path(self.serv, observe_path)\n\n            notify = self.serv.recv()\n\n            self.assertIsInstance(notify, Lwm2mNotify)\n            self.assertMsgEqual(\n                Lwm2mNotify(\n                    token=observe.token,\n                    confirmable=True),\n                notify)\n            self.serv.send(Lwm2mEmpty.matching(notify)())\n\n            self.read_log_until_confirmable_notification_success(\n                ssid=1, paths_count=1)\n            self.read_log_until_path_occur(observe_path)\n\n        def tearDown(self):\n            self.receive_notification_and_respond(self.serv)\n            super().tearDown()\n\n\nclass ConfirmableNotificationStatusUDP(ConfirmableNotificationStatus.BasicTest,\n                                       test_suite.Lwm2mSingleServerTest):\n    def setUp(self):\n        super().setUp(extra_cmdline_args=['--confirmable-notifications'])\n\n\nclass ConfirmableNotificationStatusTCP(ConfirmableNotificationStatus.BasicTest,\n                                       test_suite.Lwm2mSingleTcpServerTest):\n    def setUp(self, *args, **kwargs):\n        super().setUp(binding='T', *args, **kwargs)\n\n\nclass ConfirmableNotificationStatusTLS(ConfirmableNotificationStatus.BasicTest,\n                                       test_suite.Lwm2mTlsSingleServerTest):\n    def setUp(self, *args, **kwargs):\n        super().setUp(binding='T', *args, **kwargs)\n\n\nclass ConfirmableNotificationStatusAttr(test_suite.Lwm2mSingleServerTest,\n                                        ConfirmableNotificationStatus.Test):\n    def setUp(self):\n        super().setUp(minimum_version='1.2', maximum_version='1.2')\n\n    def runTest(self):\n        observe_path = self.make_path(OID.Location, 0, RID.Location.Latitude)\n        self.write_attributes_path(self.serv, observe_path, query=['con=1'])\n        observe = self.observe_path(self.serv, observe_path)\n\n        notify = self.serv.recv()\n\n        self.assertIsInstance(notify, Lwm2mNotify)\n        self.assertMsgEqual(\n            Lwm2mNotify(\n                token=observe.token,\n                confirmable=True),\n            notify)\n        self.serv.send(Lwm2mEmpty.matching(notify)())\n\n        self.read_log_until_confirmable_notification_success(\n            ssid=1, paths_count=1)\n        self.read_log_until_path_occur(observe_path)\n\n    def tearDown(self):\n        self.receive_notification_and_respond(self.serv)\n        super().tearDown()\n\n\nclass ConfirmableNotificationStatusTwoObservations(test_suite.Lwm2mSingleServerTest,\n                                                   ConfirmableNotificationStatus.Test):\n    def setUp(self):\n        super().setUp(extra_cmdline_args=['--confirmable-notifications'])\n\n    def runTest(self):\n        self.create_instance(self.serv, oid=OID.Test, iid=0)\n\n        observe1_path = self.make_path(OID.Test, 0, RID.Test.Counter)\n        observe2_path = self.make_path(OID.Location, 0)\n        observe1 = self.observe_path(self.serv, observe1_path)\n        observe2 = self.observe_path(self.serv, observe2_path)\n\n        notify = self.serv.recv()\n\n        self.assertIsInstance(notify, Lwm2mNotify)\n        self.assertMsgEqual(\n            Lwm2mNotify(\n                token=observe2.token,\n                confirmable=True),\n            notify)\n\n        # even if a new notification will be scheduled, the status callback should execute\n        # correctly for the previous notification after sending ack\n        self.execute_resource(\n            self.serv,\n            oid=OID.Test,\n            iid=0,\n            rid=RID.Test.IncrementCounter)\n        if self.read_log_until_match(\n                regex=re.escape(\n                    b'Execute ' +\n                    self.make_path(\n                        OID.Test,\n                        0,\n                        RID.Test.IncrementCounter).encode()),\n                timeout_s=self.LOG_TIMEOUT) is None:\n            raise self.failureException('string not found')\n        time.sleep(0.5)\n        self.serv.send(Lwm2mEmpty.matching(notify)())\n\n        self.read_log_until_confirmable_notification_success(\n            ssid=1, paths_count=1)\n        self.read_log_until_path_occur(observe2_path)\n\n        notify = self.serv.recv()\n        self.assertIsInstance(notify, Lwm2mNotify)\n        self.assertMsgEqual(\n            Lwm2mNotify(\n                token=observe1.token,\n                confirmable=True),\n            notify)\n        self.serv.send(Lwm2mEmpty.matching(notify)())\n\n        self.read_log_until_confirmable_notification_success(\n            ssid=1, paths_count=1)\n        self.read_log_until_path_occur(observe1_path)\n\n    def tearDown(self):\n        self.receive_notification_and_respond(self.serv)\n        super().tearDown()\n\n\nclass ConfirmableNotificationStatusComposite(test_suite.Lwm2mSingleServerTest,\n                                             ConfirmableNotificationStatus.Test):\n    def setUp(self):\n        super().setUp(extra_cmdline_args=['--confirmable-notifications'],\n                      minimum_version='1.1', maximum_version='1.1')\n\n    def runTest(self):\n        paths = [self.make_path(OID.Location, 0, RID.Location.Latitude),\n                 self.make_path(OID.FirmwareUpdate),\n                 self.make_path(OID.Device, 0, RID.Device.AvailablePowerSources)]\n\n        observe = self.observe_composite(self.serv, paths)\n\n        notify = self.serv.recv()\n\n        self.assertIsInstance(notify, Lwm2mNotify)\n        self.assertMsgEqual(\n            Lwm2mNotify(\n                token=observe.token,\n                confirmable=True),\n            notify)\n        self.serv.send(Lwm2mEmpty.matching(notify)())\n\n        self.read_log_until_confirmable_notification_success(\n            ssid=1, paths_count=len(paths))\n        for path in paths:\n            self.read_log_until_path_occur(path)\n\n    def tearDown(self):\n        self.receive_notification_and_respond(self.serv)\n        super().tearDown()\n\n\nclass ConfirmableNotificationStatusOffline(test_suite.Lwm2mDtlsSingleServerTest,\n                                           ConfirmableNotificationStatus.Test):\n    def setUp(self):\n        super().setUp(extra_cmdline_args=['--confirmable-notifications'])\n\n    def runTest(self):\n        PMAX_S = 1\n        QUEUED_NOTIFICATIONS = 3\n\n        observe_path = self.make_path(\n            OID.FirmwareUpdate, 0, RID.FirmwareUpdate.State)\n        self.write_attributes_path(\n            self.serv,\n            observe_path,\n            query=[\n                'pmax=%d' %\n                PMAX_S])\n        observe = self.observe_path(self.serv, observe_path)\n\n        self.communicate('enter-offline')\n\n        time.sleep(PMAX_S * QUEUED_NOTIFICATIONS)\n        self.serv.reset()\n\n        # prevent the demo from queueing any additional notifications\n        self.communicate(\n            'set-attrs %s 1 pmin=9999 pmax=9999' %\n            (ResPath.FirmwareUpdate.State,))\n        # wait until attribute change gets applied during next notification\n        # poll\n        time.sleep(PMAX_S)\n\n        self.communicate('exit-offline')\n\n        for _ in range(3):\n            notify = self.serv.recv()\n            self.assertIsInstance(notify, Lwm2mNotify)\n            self.assertMsgEqual(\n                Lwm2mNotify(\n                    token=observe.token,\n                    confirmable=True),\n                notify)\n            self.serv.send(Lwm2mEmpty.matching(notify)())\n\n            self.read_log_until_confirmable_notification_success(\n                ssid=1, paths_count=1)\n            self.read_log_until_path_occur(observe_path)\n\n        with self.assertRaises(socket.timeout):\n            self.serv.recv(PMAX_S * 2)\n\n\nclass ConfirmableNotificationStatusTwoServers(test_suite.Lwm2mSingleServerTest,\n                                              ConfirmableNotificationStatus.Test,\n                                              AccessControl.Test):\n    def setUp(self):\n        super().setUp(servers=2, extra_cmdline_args=[\n            '--confirmable-notifications', '--access-entry', \n            '/%d/65535,1,%d' % (OID.Test, AccessMask.CREATE)])\n\n    def runTest(self):\n        self.create_instance(self.servers[0], oid=OID.Test, iid=0)\n\n        self.update_access(self.servers[0], OID.Test, 0,\n                           [make_acl_entry(1, AccessMask.EXECUTE | AccessMask.READ),\n                            make_acl_entry(2, AccessMask.READ)])\n\n        observe_path = self.make_path(OID.Test, 0, RID.Test.Counter)\n        observe1 = self.observe_path(self.servers[0], observe_path)\n        observe2 = self.observe_path(self.servers[1], observe_path)\n\n        self.execute_resource(\n            self.serv,\n            oid=OID.Test,\n            iid=0,\n            rid=RID.Test.IncrementCounter)\n\n        notify1 = self.servers[0].recv()\n        notify2 = self.servers[1].recv()\n\n        self.assertIsInstance(notify1, Lwm2mNotify)\n        self.assertMsgEqual(\n            Lwm2mNotify(\n                token=observe1.token,\n                confirmable=True),\n            notify1)\n        self.assertIsInstance(notify2, Lwm2mNotify)\n        self.assertMsgEqual(\n            Lwm2mNotify(\n                token=observe2.token,\n                confirmable=True),\n            notify2)\n\n        self.servers[0].send(Lwm2mEmpty.matching(notify1)())\n\n        self.read_log_until_confirmable_notification_success(\n            ssid=1, paths_count=1)\n        self.read_log_until_path_occur(observe_path)\n\n        self.servers[1].send(Lwm2mEmpty.matching(notify2)())\n\n        self.read_log_until_confirmable_notification_success(\n            ssid=2, paths_count=1)\n        self.read_log_until_path_occur(observe_path)\n\n\nclass ConfirmableNotificationStatusNotFound(test_suite.Lwm2mSingleServerTest,\n                                            ConfirmableNotificationStatus.Test):\n    def runTest(self):\n        observe_path = self.make_path(\n            OID.Temperature, 0, RID.Temperature.SensorValue)\n        observe = self.observe_path(self.serv, observe_path)\n\n        self.communicate('temperature-remove-instance 0')\n\n        notify = self.serv.recv()\n\n        if notify.code == coap.Code.RES_CONTENT:\n            self.serv.send(Lwm2mEmpty.matching(notify)())\n            notify = self.serv.recv()\n\n        self.assertEqual(notify.code, coap.Code.RES_NOT_FOUND)\n        self.assertEqual(bytes(notify.token), bytes(observe.token))\n        self.assertEqual(notify.type, coap.Type.CONFIRMABLE)\n        self.serv.send(Lwm2mEmpty.matching(notify)())\n\n        self.read_log_until_confirmable_notification_success(\n            ssid=1, paths_count=1)\n        self.read_log_until_path_occur(observe_path)\n\n\nclass ConfirmableNotificationStatusRST(test_suite.Lwm2mSingleServerTest,\n                                       ConfirmableNotificationStatus.Test):\n    def setUp(self):\n        super().setUp(extra_cmdline_args=['--confirmable-notifications'])\n\n    def runTest(self):\n        observe_path = self.make_path(OID.Location, 0, RID.Location.Latitude)\n        observe = self.observe_path(self.serv, observe_path)\n\n        notify = self.serv.recv()\n\n        self.assertIsInstance(notify, Lwm2mNotify)\n        self.assertMsgEqual(\n            Lwm2mNotify(\n                token=observe.token,\n                confirmable=True),\n            notify)\n\n        self.serv.send(Lwm2mReset.matching(notify)())\n\n        self.read_log_until_confirmable_notification_fail(\n            ssid=1, paths_count=1)\n        self.read_log_until_path_occur(observe_path)\n\n\nclass ConfirmableNotificationStatusWithoutAck(test_suite.Lwm2mSingleServerTest,\n                                              ConfirmableNotificationStatus.Test):\n    def setUp(self):\n        super().setUp(extra_cmdline_args=['--confirmable-notifications', '--max-retransmit', '0',\n                                          '--ack-timeout', '1'])\n\n    def runTest(self):\n        observe_path = self.make_path(OID.Location, 0, RID.Location.Latitude)\n        observe = self.observe_path(self.serv, observe_path)\n\n        notify = self.serv.recv()\n\n        self.assertIsInstance(notify, Lwm2mNotify)\n        self.assertMsgEqual(\n            Lwm2mNotify(\n                token=observe.token,\n                confirmable=True),\n            notify)\n\n        self.read_log_until_confirmable_notification_fail(\n            ssid=1, paths_count=1)\n        self.read_log_until_path_occur(observe_path)\n    \n    def tearDown(self):\n        super().tearDown(auto_deregister=False)\n\n\nclass ConfirmableNotificationStatusSendFailed(test_suite.Lwm2mSingleServerTest,\n                                              ConfirmableNotificationStatus.Test):\n    def setUp(self):\n        super().setUp(extra_cmdline_args=['--confirmable-notifications', '--max-retransmit', '0',\n                                          '--ack-timeout', '1'])\n\n    def runTest(self):\n        observe_path = self.make_path(OID.Location, 0, RID.Location.Latitude)\n        self.observe_path(self.serv, observe_path)\n\n        with self.serv.fake_close():\n            self.read_log_until_confirmable_notification_fail(\n                ssid=1, paths_count=1)\n            self.read_log_until_path_occur(observe_path)\n\n    def tearDown(self):\n        super().tearDown(auto_deregister=False)\n\n\nclass ConfirmableNotificationStatusNoConfirmable(test_suite.Lwm2mSingleServerTest,\n                                                 ConfirmableNotificationStatus.Test):\n    def runTest(self):\n        observe_path = self.make_path(OID.Location, 0, RID.Location.Latitude)\n        observe = self.observe_path(self.serv, observe_path)\n\n        notify = self.serv.recv()\n\n        self.assertIsInstance(notify, Lwm2mNotify)\n        self.assertMsgEqual(\n            Lwm2mNotify(\n                token=observe.token,\n                confirmable=False),\n            notify)\n\n        self.observe_path(\n            self.serv,\n            observe_path,\n            token=notify.token,\n            observe=1)\n\n        if self.read_log_until_match(\n            regex=re.escape(\n                self.CONFIRMABLE_NOTIFICATION_SUCCESS.substitute(ssid=1, paths_count=1).encode()),\n                timeout_s=10) is not None:\n            raise self.failureException('string should not be found')\n"
  },
  {
    "path": "tests/integration/suites/default/observe_attributes.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nfrom framework.lwm2m_test import *\n\nfrom . import access_control as ac\n\n\nclass ObserveAttributesTest(test_suite.Lwm2mSingleServerTest,\n                            test_suite.Lwm2mDmOperations):\n    def runTest(self):\n        # Create object\n        self.create_instance(self.serv, oid=OID.Test, iid=0)\n        # Observe: Counter\n        counter_pkt = self.observe(self.serv, oid=OID.Test, iid=0, rid=RID.Test.Counter)\n\n        # no message should arrive here\n        with self.assertRaises(socket.timeout):\n            self.serv.recv(timeout_s=5)\n\n        # Attribute invariants\n        self.write_attributes(self.serv, oid=OID.Test, iid=0, rid=RID.Test.Counter, query=['st=-1'],\n                              expect_error_code=coap.Code.RES_BAD_REQUEST)\n        self.write_attributes(self.serv, oid=OID.Test, iid=0, rid=RID.Test.Counter,\n                              query=['lt=9', 'gt=4'], expect_error_code=coap.Code.RES_BAD_REQUEST)\n        self.write_attributes(self.serv, oid=OID.Test, iid=0, rid=RID.Test.Counter,\n                              query=['lt=4', 'gt=9', 'st=3'],\n                              expect_error_code=coap.Code.RES_BAD_REQUEST)\n\n        # unparsable attributes\n        self.write_attributes(self.serv, oid=OID.Test, iid=0, rid=RID.Test.Counter,\n                              query=['lt=invalid'], expect_error_code=coap.Code.RES_BAD_OPTION)\n\n        # Write Attributes\n        self.write_attributes(self.serv, oid=OID.Test, iid=0, rid=RID.Test.Counter,\n                              query=['pmax=2'])\n\n        # now we should get notifications, even though nothing changed\n        pkt = self.serv.recv(timeout_s=3)\n        self.assertEqual(pkt.code, coap.Code.RES_CONTENT)\n        self.assertEqual(pkt.content, counter_pkt.content)\n\n        # and another one\n        pkt = self.serv.recv(timeout_s=3)\n        self.assertEqual(pkt.code, coap.Code.RES_CONTENT)\n        self.assertEqual(pkt.content, counter_pkt.content)\n\n\nclass ObserveResourceInvalidPmax(test_suite.Lwm2mSingleServerTest,\n                                 test_suite.Lwm2mDmOperations):\n    def runTest(self):\n        # Create object\n        self.create_instance(self.serv, oid=OID.Test, iid=1)\n\n        # Set invalid pmax (smaller than pmin)\n        self.write_attributes(\n            self.serv, oid=OID.Test, iid=1, rid=RID.Test.Counter, query=['pmin=2', 'pmax=1'])\n\n        self.observe(self.serv, oid=OID.Test, iid=1, rid=RID.Test.Counter)\n\n        # No notification should arrive\n        with self.assertRaises(socket.timeout):\n            print(self.serv.recv(timeout_s=3))\n\n\nclass ObserveResourceZeroPmax(test_suite.Lwm2mSingleServerTest,\n                              test_suite.Lwm2mDmOperations):\n    def runTest(self):\n        # Create object\n        self.create_instance(self.serv, oid=OID.Test, iid=1)\n\n        # Set invalid pmax (equal to 0)\n        self.write_attributes(\n            self.serv, oid=OID.Test, iid=1, rid=RID.Test.Counter, query=['pmax=0'])\n\n        self.observe(self.serv, oid=OID.Test, iid=1, rid=RID.Test.Counter)\n\n        # No notification should arrive\n        with self.assertRaises(socket.timeout):\n            print(self.serv.recv(timeout_s=2))\n\n\nclass ObserveResourceWithEmptyHandler(test_suite.Lwm2mSingleServerTest,\n                                      test_suite.Lwm2mDmOperations):\n    def runTest(self):\n        # See T832. resource_read handler implemented as 'return 0;'\n        # used to cause segfault when observed.\n\n        # Create object\n        self.create_instance(self.serv, oid=OID.Test, iid=0)\n\n        self.write_resource(self.serv, oid=OID.Test, iid=0, rid=RID.Test.ResBytesZeroBegin,\n                            content='0')\n\n        # Observe: Empty\n        self.observe(self.serv, oid=OID.Test, iid=0, rid=RID.Test.ResBytes,\n                     expect_error_code=coap.Code.RES_INTERNAL_SERVER_ERROR)\n        # hopefully that does not segfault\n\n\nclass ObserveWithMultipleServers(ac.AccessControl.Test):\n    def runTest(self):\n        self.create_instance(server=self.servers[1], oid=OID.Test, iid=0)\n        self.update_access(server=self.servers[1], oid=OID.Test, iid=0,\n                           acl=[ac.make_acl_entry(1, ac.AccessMask.READ | ac.AccessMask.EXECUTE),\n                                ac.make_acl_entry(2, ac.AccessMask.OWNER)])\n        # Observe: Counter\n        self.observe(self.servers[1], oid=OID.Test, iid=0, rid=RID.Test.Counter)\n        # Expecting silence\n        with self.assertRaises(socket.timeout):\n            self.servers[1].recv(timeout_s=2)\n\n        self.write_attributes(self.servers[1], oid=OID.Test, iid=0, rid=RID.Test.Counter,\n                              query=['gt=1'])\n        with self.assertRaises(socket.timeout):\n            self.servers[1].recv(timeout_s=2)\n\n        self.execute_resource(self.servers[0], oid=OID.Test, iid=0, rid=RID.Test.IncrementCounter)\n        self.execute_resource(self.servers[0], oid=OID.Test, iid=0, rid=RID.Test.IncrementCounter)\n        pkt = self.servers[1].recv()\n        self.assertEqual(pkt.code, coap.Code.RES_CONTENT)\n        self.assertEqual(pkt.content, b'2')\n\n\nclass ObserveWithDefaultAttributesTest(test_suite.Lwm2mSingleServerTest,\n                                       test_suite.Lwm2mDmOperations):\n    def runTest(self):\n        # Create object\n        self.create_instance(self.serv, oid=OID.Test, iid=0)\n        # Observe: Counter\n        counter_pkt = self.observe(self.serv, oid=OID.Test, iid=0, rid=RID.Test.Counter)\n        # no message should arrive here\n        with self.assertRaises(socket.timeout):\n            self.serv.recv(timeout_s=5)\n\n        # Attributes set via public API\n        self.communicate('set-attrs %s 1 pmax=1 pmin=1' % (ResPath.Test[0].Counter,))\n        # And should now start arriving each second\n        pkt = self.serv.recv(timeout_s=2)\n        self.assertEqual(pkt.code, coap.Code.RES_CONTENT)\n        self.assertEqual(pkt.content, counter_pkt.content)\n        # Up until they're reset\n        self.communicate('set-attrs %s 1' % (ResPath.Test[0].Counter,))\n\n\nclass ObserveOfflineWithStoredNotificationLimit(test_suite.Lwm2mDtlsSingleServerTest,\n                                                test_suite.Lwm2mDmOperations):\n    QUEUE_SIZE = 3\n\n    def setUp(self):\n        super().setUp(extra_cmdline_args=['--stored-notification-limit', str(self.QUEUE_SIZE)])\n        self.write_resource(self.serv, OID.Server, 1, RID.Server.NotificationStoring, '1')\n\n    def runTest(self):\n        PMAX_S = 1\n        SKIP_NOTIFICATIONS = 3  # number of Notify messages that should be skipped\n        EPSILON_S = PMAX_S / 2  # extra time to wait for each Notify\n\n        self.write_attributes(self.serv, OID.Device, 0, RID.Device.CurrentTime,\n                              query=['pmax=%d' % PMAX_S])\n        observe = self.observe(self.serv, OID.Device, 0, RID.Device.CurrentTime)\n\n        self.communicate('enter-offline')\n        # wait long enough to cause dropping oldest notifications\n        time.sleep(PMAX_S * (self.QUEUE_SIZE + SKIP_NOTIFICATIONS))\n        self.serv.reset()\n\n        # prevent the demo from queueing any additional notifications\n        self.communicate('set-attrs %s 1 pmin=9999 pmax=9999' % (ResPath.Device.CurrentTime,))\n        # wait until attribute change gets applied during next notification poll\n        time.sleep(PMAX_S)\n\n        self.communicate('exit-offline')\n\n        # demo will resume DTLS session without sending any LwM2M messages\n        self.serv.listen()\n\n        seen_values = []\n\n        # exactly QUEUE_SIZE notifications should be sent\n        for _ in range(self.QUEUE_SIZE):\n            pkt = self.serv.recv(timeout_s=EPSILON_S)\n            self.assertMsgEqual(Lwm2mContent(msg_id=ANY,\n                                             type=coap.Type.NON_CONFIRMABLE,\n                                             token=observe.token),\n                                pkt)\n            seen_values.append(pkt.content)\n\n        with self.assertRaises(socket.timeout):\n            self.serv.recv(PMAX_S * 2)\n\n        # make sure the oldest values were dropped\n        for idx in range(SKIP_NOTIFICATIONS):\n            self.assertNotIn(str(int(observe.content.decode('utf-8')) + idx).encode('utf-8'),\n                             seen_values)\n\n\nclass ObserveOfflineWithStoredNotificationLimitAndMultipleServers(test_suite.Lwm2mTest,\n                                                                  test_suite.Lwm2mDmOperations):\n    QUEUE_SIZE = 3\n\n    def setUp(self):\n        super().setUp(servers=2, psk_identity=b'test-identity', psk_key=b'test-key',\n                      extra_cmdline_args=['--stored-notification-limit', str(self.QUEUE_SIZE)])\n        self.write_resource(self.servers[0], OID.Server, 1, RID.Server.NotificationStoring, '1')\n        self.write_resource(self.servers[1], OID.Server, 2, RID.Server.NotificationStoring, '1')\n\n    def runTest(self):\n        PMAX_S = 1\n        SKIP_NOTIFICATIONS = 3  # number of Notify messages that should be skipped per server\n        EPSILON_S = PMAX_S / 2  # extra time to wait for each Notify\n\n        self.write_attributes(self.servers[0], OID.Device, 0, RID.Device.CurrentTime,\n                              query=['pmax=%d' % PMAX_S])\n        self.write_attributes(self.servers[1], OID.Device, 0, RID.Device.CurrentTime,\n                              query=['pmax=%d' % PMAX_S])\n\n        observes = [\n            self.observe(self.servers[0], OID.Device, 0, RID.Device.CurrentTime),\n            self.observe(self.servers[1], OID.Device, 0, RID.Device.CurrentTime),\n        ]\n\n        self.communicate('enter-offline')\n        # wait long enough to cause dropping oldest notifications\n        time.sleep(PMAX_S * (self.QUEUE_SIZE / 2 + SKIP_NOTIFICATIONS))\n        for serv in self.servers:\n            serv.reset()\n\n        # prevent the demo from queueing any additional notifications\n        self.communicate('set-attrs %s 1 pmin=9999 pmax=9999' % (ResPath.Device.CurrentTime,))\n        self.communicate('set-attrs %s 2 pmin=9999 pmax=9999' % (ResPath.Device.CurrentTime,))\n        # wait until attribute change gets applied during next notification poll\n        time.sleep(PMAX_S)\n\n        self.communicate('exit-offline')\n\n        # demo will resume DTLS sessions without sending any LwM2M messages\n        for serv in self.servers:\n            serv.listen()\n\n        remaining_notifications = self.QUEUE_SIZE\n        seen_values = []\n\n        # exactly QUEUE_SIZE notifications in total should be sent\n        for observe, serv in zip(observes, self.servers):\n            try:\n                for _ in range(remaining_notifications):\n                    pkt = serv.recv(timeout_s=EPSILON_S)\n                    self.assertMsgEqual(Lwm2mContent(msg_id=ANY,\n                                                     type=coap.Type.NON_CONFIRMABLE,\n                                                     token=observe.token),\n                                        pkt)\n                    remaining_notifications -= 1\n                    seen_values.append(pkt.content)\n            except socket.timeout:\n                pass\n\n        self.assertEqual(remaining_notifications, 0)\n\n        for serv in self.servers:\n            with self.assertRaises(socket.timeout):\n                serv.recv(PMAX_S * 2)\n\n        # make sure the oldest values were dropped\n        for idx in range(SKIP_NOTIFICATIONS):\n            self.assertNotIn(str(int(observe.content.decode('utf-8')) + idx).encode('utf-8'),\n                             seen_values)\n\n\nclass ObserveOfflineWithStoringDisabled(test_suite.Lwm2mDtlsSingleServerTest,\n                                        test_suite.Lwm2mDmOperations):\n    def runTest(self):\n        self.write_resource(self.serv, OID.Server, 1, RID.Server.NotificationStoring, '0')\n        observe = self.observe(self.servers[0], OID.Device, 0, RID.Device.CurrentTime)\n\n        self.communicate('enter-offline')\n        # wait long enough to cause dropping and receive any outstanding notifications\n        deadline = time.time() + 2.0\n        while True:\n            timeout = deadline - time.time()\n            if timeout <= 0.0:\n                break\n            try:\n                self.assertMsgEqual(Lwm2mNotify(token=observe.token),\n                                    self.serv.recv(timeout_s=timeout))\n            except socket.timeout:\n                pass\n\n        time.sleep(5.0)\n\n        self.communicate('exit-offline')\n        self.assertDtlsReconnect()\n        pkt = self.serv.recv(timeout_s=2)\n        self.assertMsgEqual(Lwm2mNotify(token=observe.token), pkt)\n        self.assertAlmostEqual(float(pkt.content.decode('utf-8')), time.time(), delta=1.0)\n\n\nclass ObserveOfflineUnchangingPmaxWithStoringDisabled(test_suite.Lwm2mDtlsSingleServerTest,\n                                                      test_suite.Lwm2mDmOperations):\n    def runTest(self):\n        self.write_resource(self.serv, OID.Server, 1, RID.Server.NotificationStoring, '0')\n        self.write_attributes(self.serv, OID.Device, 0, RID.Device.SerialNumber, ['pmax=1'])\n        observe = self.observe(self.servers[0], OID.Device, 0, RID.Device.SerialNumber)\n\n        self.communicate('enter-offline')\n        # wait long enough to cause dropping and receive any outstanding notifications\n        deadline = time.time() + 2.0\n        while True:\n            timeout = deadline - time.time()\n            if timeout <= 0.0:\n                break\n            try:\n                self.assertMsgEqual(Lwm2mNotify(token=observe.token),\n                                    self.serv.recv(timeout_s=timeout))\n            except socket.timeout:\n                pass\n\n        time.sleep(5.0)\n\n        self.communicate('exit-offline')\n        self.assertDtlsReconnect()\n        pkt = self.serv.recv(timeout_s=2)\n        self.assertMsgEqual(Lwm2mNotify(token=observe.token), pkt)\n\n\nclass ResetWithoutMatchingObservation(test_suite.Lwm2mSingleServerTest):\n    # T2359 - receiving a Reset message that cannot be matched to any existing\n    # observation used to crash the application.\n    def runTest(self):\n        self.serv.send(Lwm2mReset(msg_id=0))\n\n\nclass ObserveResourceInstance(test_suite.Lwm2mSingleServerTest,\n                              test_suite.Lwm2mDmOperations):\n    def setUp(self):\n        super().setUp(maximum_version='1.1')\n\n    def runTest(self):\n        # Create object\n        self.create_instance(self.serv, oid=OID.Test, iid=0)\n        # Initialize integer array\n        self.execute_resource(self.serv, oid=OID.Test, iid=0,\n                              rid=RID.Test.ResInitIntArray, content=b\"0='1337'\")\n        discover_output = self.discover(self.serv, oid=OID.Test,\n                                        iid=0, rid=RID.Test.IntArray).content\n        self.assertEqual(b'</%d/0/%d>;dim=1,</%d/0/%d/0>' %\n                         (OID.Test, RID.Test.IntArray, OID.Test, RID.Test.IntArray),\n                         discover_output)\n        # Write gt attribute\n        self.write_attributes(self.serv, oid=OID.Test, iid=0,\n                              rid=RID.Test.IntArray, query=['gt=2000'])\n        discover_output = self.discover(self.serv, oid=OID.Test,\n                                        iid=0, rid=RID.Test.IntArray).content\n        self.assertEqual(b'</%d/0/%d>;dim=1;gt=2000,</%d/0/%d/0>' %\n                         (OID.Test, RID.Test.IntArray, OID.Test, RID.Test.IntArray),\n                         discover_output)\n        # Observe resource instance\n        observe_pkt = self.observe(self.serv, oid=OID.Test, iid=0, rid=RID.Test.IntArray, riid=0)\n        self.assertEqual(b'1337', observe_pkt.content)\n        # Change value of /33605/0/3/0 to 1500\n        self.execute_resource(self.serv, oid=OID.Test, iid=0,\n                              rid=RID.Test.ResInitIntArray, content=b\"0='1500'\")\n        with self.assertRaises(socket.timeout):\n            self.serv.recv(timeout_s=1)\n        # Change value of /33605/0/3/0 to 2000\n        self.execute_resource(self.serv, oid=OID.Test, iid=0,\n                              rid=RID.Test.ResInitIntArray, content=b\"0='2000'\")\n        with self.assertRaises(socket.timeout):\n            self.serv.recv(timeout_s=1)\n        # Change value of /33605/0/3/0 to 2001, notification is expected\n        self.execute_resource(self.serv, oid=OID.Test, iid=0,\n                              rid=RID.Test.ResInitIntArray, content=b\"0='2001'\")\n        notify_pkt = self.serv.recv(timeout_s=1)\n        self.assertMsgEqual(Lwm2mNotify(token=observe_pkt.token, content=b'2001'), notify_pkt)\n\n\nclass Hqmax:\n    class Test(test_suite.Lwm2mDtlsSingleServerTest,\n               test_suite.Lwm2mDmOperations):\n        QUEUE_SIZE = 6\n\n        def setUp(self, servers=1, extra_cmdline_args=[]):\n\n            extra_cmdline_args += ['--stored-notification-limit', str(self.QUEUE_SIZE)]\n            super().setUp(servers=servers, extra_cmdline_args=extra_cmdline_args)\n            for server in range(servers):\n                self.write_resource(self.servers[server], OID.Server, server + 1,\n                                    RID.Server.NotificationStoring, '1')\n\n\nclass ObservePositiveHqmax(Hqmax.Test):\n    def runTest(self):\n        PMAX_S = 1\n        SKIP_NOTIFICATIONS = 3  # number of Notify messages that should be skipped\n        EPSILON_S = PMAX_S / 2  # extra time to wait for each Notify\n\n        HQMAX = 2\n        self.write_attributes(self.serv, OID.Device, 0, RID.Device.CurrentTime,\n                              query=['pmax=%d' % PMAX_S, 'hqmax=%d' % HQMAX])\n\n        observe = self.observe(self.serv, OID.Device, 0, RID.Device.CurrentTime)\n\n        self.communicate('enter-offline')\n        # wait long enough to cause dropping oldest notifications\n        time.sleep(PMAX_S * (HQMAX + SKIP_NOTIFICATIONS))\n        self.serv.reset()\n\n        # prevent the demo from queueing any additional notifications\n        self.communicate('set-attrs %s 1 pmin=9999 pmax=9999' % (ResPath.Device.CurrentTime,))\n        # wait until attribute change gets applied during next notification poll\n        time.sleep(PMAX_S)\n\n        self.communicate('exit-offline')\n\n        # demo will resume DTLS session without sending any LwM2M messages\n        self.serv.listen()\n\n        seen_values = []\n\n        # exactly HQMAX notifications should be sent\n        for _ in range(HQMAX):\n            pkt = self.serv.recv(timeout_s=EPSILON_S)\n            self.assertMsgEqual(Lwm2mContent(msg_id=ANY,\n                                             type=coap.Type.NON_CONFIRMABLE,\n                                             token=observe.token),\n                                pkt)\n            seen_values.append(pkt.content)\n\n        with self.assertRaises(socket.timeout):\n            self.serv.recv(PMAX_S * 2)\n\n        # make sure the oldest values were dropped\n        for idx in range(SKIP_NOTIFICATIONS):\n            self.assertNotIn(str(int(observe.content.decode('utf-8')) + idx).encode('utf-8'),\n                             seen_values)\n\n\nclass ObserveZeroHqmax(Hqmax.Test):\n    def runTest(self):\n        PMAX_S = 1\n\n        HQMAX = 0\n        self.write_attributes(self.serv, OID.Device, 0, RID.Device.CurrentTime,\n                              query=['pmax=%d' % PMAX_S, 'hqmax=%d' % HQMAX])\n\n        self.observe(self.serv, OID.Device, 0, RID.Device.CurrentTime)\n\n        self.communicate('enter-offline')\n        # wait long enough to potentially store notifications\n        time.sleep(PMAX_S * 2)\n        self.serv.reset()\n\n        # prevent the demo from queueing any additional notifications\n        self.communicate('set-attrs %s 1 pmin=9999 pmax=9999' % (ResPath.Device.CurrentTime,))\n        # wait until attribute change gets applied during next notification poll\n        time.sleep(PMAX_S)\n\n        self.communicate('exit-offline')\n\n        # demo will resume DTLS session without sending any LwM2M messages\n        self.serv.listen()\n\n        # no notifications should be sent\n        with self.assertRaises(socket.timeout):\n            self.serv.recv(PMAX_S * 2)\n\n\nclass ObserveHqmaxAndMultipleServers(Hqmax.Test):\n    def setUp(self):\n        super().setUp(servers=2)\n\n    def runTest(self):\n        PMAX_S = 1\n        EPSILON_S = PMAX_S / 2  # extra time to wait for each Notify\n\n        hqmaxes = [2, 3]  # number of Notify messages for each server that should be sent\n        total_notifications = sum(hqmaxes)  # number of total Notify messages that should be sent\n        skips = [total_notifications - hqmax for hqmax in\n                 hqmaxes]  # number of Notify messages for each server that should be skipped\n        self.write_attributes(self.servers[0], OID.Device, 0, RID.Device.CurrentTime,\n                              query=['pmax=%d' % PMAX_S, 'hqmax=%d' % hqmaxes[0]])\n        self.write_attributes(self.servers[1], OID.Device, 0, RID.Device.CurrentTime,\n                              query=['pmax=%d' % PMAX_S, 'hqmax=%d' % hqmaxes[1]])\n\n        observes = [\n            self.observe(self.servers[0], OID.Device, 0, RID.Device.CurrentTime),\n            self.observe(self.servers[1], OID.Device, 0, RID.Device.CurrentTime),\n        ]\n\n        self.communicate('enter-offline')\n        # wait long enough to cause dropping oldest notifications\n        time.sleep(PMAX_S * total_notifications)\n        for serv in self.servers:\n            serv.reset()\n\n        # prevent the demo from queueing any additional notifications\n        self.communicate('set-attrs %s 1 pmin=9999 pmax=9999' % (ResPath.Device.CurrentTime,))\n        self.communicate('set-attrs %s 2 pmin=9999 pmax=9999' % (ResPath.Device.CurrentTime,))\n        # wait until attribute change gets applied during next notification poll\n        time.sleep(PMAX_S)\n\n        self.communicate('exit-offline')\n\n        # demo will resume DTLS sessions without sending any LwM2M messages\n        for serv in self.servers:\n            serv.listen()\n\n        seen_values = []\n\n        remaining_notifications = total_notifications\n        for observe, serv, hqmax in zip(observes, self.servers, hqmaxes):\n            try:\n                for _ in range(hqmax):\n                    pkt = serv.recv(timeout_s=EPSILON_S)\n                    self.assertMsgEqual(Lwm2mContent(msg_id=ANY,\n                                                     type=coap.Type.NON_CONFIRMABLE,\n                                                     token=observe.token),\n                                        pkt)\n                    remaining_notifications -= 1\n                    seen_values.append(pkt.content)\n            except socket.timeout:\n                pass\n\n        self.assertEqual(remaining_notifications, 0)\n\n        for serv in self.servers:\n            with self.assertRaises(socket.timeout):\n                serv.recv(PMAX_S * 2)\n\n        # make sure the oldest values were dropped\n        for observe, skip in zip(observes, skips):\n            for idx in range(skip):\n                self.assertNotIn(str(int(observe.content.decode('utf-8')) + idx).encode('utf-8'),\n                                 seen_values)\n\n\nclass ObserveHqmaxGreaterThanQueueSize(Hqmax.Test):\n    def runTest(self):\n        PMAX_S = 1\n        HQMAX = 8\n        SKIP_NOTIFICATIONS = HQMAX - self.QUEUE_SIZE  # number of Notify messages that should be skipped\n        EPSILON_S = PMAX_S / 2  # extra time to wait for each Notify\n\n        self.write_attributes(self.serv, OID.Device, 0, RID.Device.CurrentTime,\n                              query=['pmax=%d' % PMAX_S, 'hqmax=%d' % HQMAX])\n\n        observe = self.observe(self.serv, OID.Device, 0, RID.Device.CurrentTime)\n\n        self.communicate('enter-offline')\n        # wait long enough to cause dropping oldest notifications\n        time.sleep(PMAX_S * HQMAX)\n        self.serv.reset()\n\n        # prevent the demo from queueing any additional notifications\n        self.communicate('set-attrs %s 1 pmin=9999 pmax=9999' % (ResPath.Device.CurrentTime,))\n        # wait until attribute change gets applied during next notification poll\n        time.sleep(PMAX_S)\n\n        self.communicate('exit-offline')\n\n        # demo will resume DTLS session without sending any LwM2M messages\n        self.serv.listen()\n\n        seen_values = []\n\n        # exactly QUEUE_SIZE notifications should be sent\n        for _ in range(self.QUEUE_SIZE):\n            pkt = self.serv.recv(timeout_s=EPSILON_S)\n            self.assertMsgEqual(Lwm2mContent(msg_id=ANY,\n                                             type=coap.Type.NON_CONFIRMABLE,\n                                             token=observe.token),\n                                pkt)\n            seen_values.append(pkt.content)\n\n        with self.assertRaises(socket.timeout):\n            self.serv.recv(PMAX_S * 2)\n\n        # make sure the oldest values were dropped\n        for idx in range(SKIP_NOTIFICATIONS):\n            self.assertNotIn(str(int(observe.content.decode('utf-8')) + idx).encode('utf-8'),\n                             seen_values)\n\n\nclass ObserveEdgeRisingTest(test_suite.Lwm2mSingleServerTest,\n                            test_suite.Lwm2mDmOperations):\n    def runTest(self):\n        # Create object\n        self.create_instance(self.serv, oid=OID.Test, iid=1)\n        # Set initial boolean resource value to 0\n        self.write_resource(self.serv, oid=OID.Test, iid=1, rid=RID.Test.ResBool, content=b'0')\n\n        # Observe with edge = 1 (notify on rising edge)\n        self.write_attributes(self.serv, oid=OID.Test, iid=1, rid=RID.Test.ResBool,\n                              query=['edge=1'])\n        observe = self.observe(self.serv, oid=OID.Test, iid=1, rid=RID.Test.ResBool)\n\n        # Change boolean resource value to 1\n        self.execute_resource(self.serv, oid=OID.Test, iid=1, rid=RID.Test.ToggleBool)\n        # Expect a notification on transition 0 -> 1\n        self.assertMsgEqual(Lwm2mNotify(token=observe.token), self.serv.recv(timeout_s=2))\n\n        # Change boolean resource value to 0\n        self.execute_resource(self.serv, oid=OID.Test, iid=1, rid=RID.Test.ToggleBool)\n        # No notification should be sent on transition 1 -> 0\n        with self.assertRaises(socket.timeout):\n            self.serv.recv(2)\n\n\nclass ObserveEdgeFallingTest(test_suite.Lwm2mSingleServerTest,\n                             test_suite.Lwm2mDmOperations):\n    def runTest(self):\n        # Create object\n        self.create_instance(self.serv, oid=OID.Test, iid=1)\n        # Set initial boolean resource value to 1\n        self.write_resource(self.serv, oid=OID.Test, iid=1, rid=RID.Test.ResBool, content=b'1')\n\n        # Observe with edge = 0 (notify on falling edge)\n        self.write_attributes(self.serv, oid=OID.Test, iid=1, rid=RID.Test.ResBool,\n                              query=['edge=0'])\n        observe = self.observe(self.serv, oid=OID.Test, iid=1, rid=RID.Test.ResBool)\n\n        # Change boolean resource value to 0\n        self.execute_resource(self.serv, oid=OID.Test, iid=1, rid=RID.Test.ToggleBool)\n        # Expect a notification on transition 1 -> 0\n        self.assertMsgEqual(Lwm2mNotify(token=observe.token), self.serv.recv(timeout_s=2))\n\n        # Change boolean resource value to 1\n        self.execute_resource(self.serv, oid=OID.Test, iid=1, rid=RID.Test.ToggleBool)\n        # No notification should be sent on transition 0 -> 1\n        with self.assertRaises(socket.timeout):\n            self.serv.recv(2)\n\n\nclass ObserveEdgeRisingMultipleInstanceResourceTest(test_suite.Lwm2mSingleServerTest,\n                                                    test_suite.Lwm2mDmOperations):\n    def runTest(self):\n        # Create object\n        self.create_instance(self.serv, oid=OID.Test, iid=1)\n\n        # Create a new boolean resource instance with value 0\n        self.execute_resource(self.serv, oid=OID.Test, iid=1,\n                              rid=RID.Test.ResInitBoolArray, content=b\"0='0'\")\n\n        # Observe with edge = 1 (notify on rising edge)\n        self.write_attributes(self.serv, oid=OID.Test, iid=1,\n                              rid=RID.Test.BoolArray, query=['edge=1'])\n        observe = self.observe(self.serv, oid=OID.Test, iid=1,\n                               rid=RID.Test.BoolArray, riid=0)\n\n        # Change boolean resource instance value to 1\n        self.execute_resource(self.serv, oid=OID.Test, iid=1, rid=RID.Test.ToggleBool)\n        # Expect a notification on transition 0 -> 1\n        self.assertMsgEqual(Lwm2mNotify(token=observe.token), self.serv.recv(timeout_s=2))\n\n        # Change boolean resource instance value to 0\n        self.execute_resource(self.serv, oid=OID.Test, iid=1, rid=RID.Test.ToggleBool)\n        # No notification should be sent on transition 1 -> 0\n        with self.assertRaises(socket.timeout):\n            self.serv.recv(2)\n\n\nclass ObserveEdgeFallingMultipleInstanceResourceTest(test_suite.Lwm2mSingleServerTest,\n                                                     test_suite.Lwm2mDmOperations):\n    def runTest(self):\n        # Create object\n        self.create_instance(self.serv, oid=OID.Test, iid=1)\n\n        # Create a new boolean resource instance with value 1\n        self.execute_resource(self.serv, oid=OID.Test, iid=1,\n                              rid=RID.Test.ResInitBoolArray, content=b\"0='1'\")\n\n        # Observe with edge = 0 (notify on falling edge)\n        self.write_attributes(self.serv, oid=OID.Test, iid=1,\n                              rid=RID.Test.BoolArray, query=['edge=0'])\n        observe = self.observe(self.serv, oid=OID.Test, iid=1,\n                               rid=RID.Test.BoolArray, riid=0)\n\n        # Change boolean resource instance value to 0\n        self.execute_resource(self.serv, oid=OID.Test, iid=1, rid=RID.Test.ToggleBool)\n        # Expect a notification on transition 1 -> 0\n        self.assertMsgEqual(Lwm2mNotify(token=observe.token), self.serv.recv(timeout_s=2))\n\n        # Change boolean resource instance value to 1\n        self.execute_resource(self.serv, oid=OID.Test, iid=1, rid=RID.Test.ToggleBool)\n        # No notification should be sent on transition 0 -> 1\n        with self.assertRaises(socket.timeout):\n            self.serv.recv(2)\n"
  },
  {
    "path": "tests/integration/suites/default/observe_with_attributes.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nimport socket\nimport time\nimport unittest\n\nfrom framework.lwm2m_test import *\n\nfrom . import access_control as ac\n\n# Most of these tests are slightly modified versions of those for the classic\n# observation attributes\n\n\nclass ObserveWithAttributesBasicTest(test_suite.Lwm2mSingleServerTest,\n                                     test_suite.Lwm2mDmOperations):\n    def setUp(self):\n        super().setUp(maximum_version='1.2')\n\n    def runTest(self):\n        # Create object\n        self.create_instance(self.serv, oid=OID.Test, iid=0)\n\n        # no message should arrive here\n        with self.assertRaises(socket.timeout):\n            self.serv.recv(timeout_s=5)\n\n        # Attribute invariants\n        self.observe(self.serv, oid=OID.Test, iid=0, rid=RID.Test.Counter, st=-1,\n                     expect_error_code=coap.Code.RES_BAD_REQUEST)\n        self.observe(self.serv, oid=OID.Test, iid=0, rid=RID.Test.Counter,\n                     lt=9, gt=4, expect_error_code=coap.Code.RES_BAD_REQUEST)\n        self.observe(self.serv, oid=OID.Test, iid=0, rid=RID.Test.Counter,\n                     lt=4, gt=9, st=3, expect_error_code=coap.Code.RES_BAD_REQUEST)\n\n        # Write Attributes\n        counter_pkt = self.observe(\n            self.serv, oid=OID.Test, iid=0, rid=RID.Test.Counter, pmax=2)\n\n        # now we should get notifications, even though nothing changed\n        pkt = self.serv.recv(timeout_s=3)\n        self.assertEqual(pkt.code, coap.Code.RES_CONTENT)\n        self.assertEqual(pkt.content, counter_pkt.content)\n\n        # and another one\n        pkt = self.serv.recv(timeout_s=3)\n        self.assertEqual(pkt.code, coap.Code.RES_CONTENT)\n        self.assertEqual(pkt.content, counter_pkt.content)\n\n\nclass ObserveWithAttributesResourceInvalidPmax(test_suite.Lwm2mSingleServerTest,\n                                               test_suite.Lwm2mDmOperations):\n    def setUp(self):\n        super().setUp(maximum_version='1.2')\n\n    def runTest(self):\n        # Create object\n        self.create_instance(self.serv, oid=OID.Test, iid=1)\n\n        # Observe with invalid pmax (smaller than pmin)\n        self.observe(self.serv, oid=OID.Test, iid=1,\n                     rid=RID.Test.Counter, pmin=2, pmax=1)\n\n        # No notification should arrive\n        with self.assertRaises(socket.timeout):\n            print(self.serv.recv(timeout_s=3))\n\n\nclass ObserveWithAttributesResourceZeroPmax(test_suite.Lwm2mSingleServerTest,\n                                            test_suite.Lwm2mDmOperations):\n    def setUp(self):\n        super().setUp(maximum_version='1.2')\n\n    def runTest(self):\n        # Create object\n        self.create_instance(self.serv, oid=OID.Test, iid=1)\n\n        # Observe with invalid pmax (equal to 0)\n        self.observe(self.serv, oid=OID.Test, iid=1,\n                     rid=RID.Test.Counter, pmax=0)\n\n        # No notification should arrive\n        with self.assertRaises(socket.timeout):\n            print(self.serv.recv(timeout_s=2))\n\n\nclass ObserveWithAttributesResourceZeroPmax(test_suite.Lwm2mSingleServerTest,\n                                            test_suite.Lwm2mDmOperations):\n    def setUp(self):\n        super().setUp(maximum_version='1.2')\n\n    def runTest(self):\n        # Create object\n        self.create_instance(self.serv, oid=OID.Test, iid=1)\n\n        # Observe with invalid pmax (equal to 0)\n        self.observe(self.serv, oid=OID.Test, iid=1,\n                     rid=RID.Test.Counter, pmax=0)\n\n        # No notification should arrive\n        with self.assertRaises(socket.timeout):\n            print(self.serv.recv(timeout_s=2))\n\n\nclass ObserveWithAttributesWithMultipleServers(ac.AccessControl.Test):\n    def setUp(self):\n        super().setUp(maximum_version='1.2')\n\n    def runTest(self):\n        self.create_instance(server=self.servers[1], oid=OID.Test, iid=0)\n        self.update_access(server=self.servers[1], oid=OID.Test, iid=0,\n                           acl=[ac.make_acl_entry(1, ac.AccessMask.READ | ac.AccessMask.EXECUTE),\n                                ac.make_acl_entry(2, ac.AccessMask.OWNER)])\n        # Observe: Counter\n        self.observe(self.servers[1], oid=OID.Test,\n                     iid=0, rid=RID.Test.Counter, gt=1)\n        # Expecting silence\n        with self.assertRaises(socket.timeout):\n            self.servers[1].recv(timeout_s=4)\n\n        self.execute_resource(\n            self.servers[0], oid=OID.Test, iid=0, rid=RID.Test.IncrementCounter)\n        self.execute_resource(\n            self.servers[0], oid=OID.Test, iid=0, rid=RID.Test.IncrementCounter)\n        pkt = self.servers[1].recv()\n        self.assertEqual(pkt.code, coap.Code.RES_CONTENT)\n        self.assertEqual(pkt.content, b'2')\n\n\nclass ObserveWithAttributesOfflineUnchangingPmaxWithStoringDisabled(test_suite.Lwm2mDtlsSingleServerTest,\n                                                                    test_suite.Lwm2mDmOperations):\n    def setUp(self):\n        super().setUp(maximum_version='1.2')\n\n    def runTest(self):\n        self.write_resource(self.serv, OID.Server, 1,\n                            RID.Server.NotificationStoring, '0')\n        observe = self.observe(\n            self.servers[0], OID.Device, 0, RID.Device.SerialNumber, pmax=1)\n\n        self.communicate('enter-offline')\n        # wait long enough to cause dropping and receive any outstanding notifications\n        deadline = time.time() + 2.0\n        while True:\n            timeout = deadline - time.time()\n            if timeout <= 0.0:\n                break\n            try:\n                self.assertMsgEqual(Lwm2mNotify(token=observe.token),\n                                    self.serv.recv(timeout_s=timeout))\n            except socket.timeout:\n                pass\n\n        time.sleep(5.0)\n\n        self.communicate('exit-offline')\n        self.assertDtlsReconnect()\n        pkt = self.serv.recv(timeout_s=2)\n        self.assertMsgEqual(Lwm2mNotify(token=observe.token), pkt)\n\n\nclass ObserveWithAttributesResourceInstance(test_suite.Lwm2mSingleServerTest,\n                                            test_suite.Lwm2mDmOperations):\n    def setUp(self):\n        super().setUp(maximum_version='1.2')\n\n    def runTest(self):\n        # Create object\n        self.create_instance(self.serv, oid=OID.Test, iid=0)\n        # Initialize integer array\n        self.execute_resource(self.serv, oid=OID.Test, iid=0,\n                              rid=RID.Test.ResInitIntArray, content=b\"0='1337'\")\n        discover_output = self.discover(self.serv, oid=OID.Test,\n                                        iid=0, rid=RID.Test.IntArray).content\n        self.assertEqual(b'</%d/0/%d>;dim=1,</%d/0/%d/0>' %\n                         (OID.Test, RID.Test.IntArray, OID.Test, RID.Test.IntArray), discover_output)\n        # Observe resource instance\n        observe_pkt = self.observe(\n            self.serv, oid=OID.Test, iid=0, rid=RID.Test.IntArray, riid=0, gt=2000)\n        self.assertEqual(b'1337', observe_pkt.content)\n        # Change value of /33605/0/3/0 to 1500\n        self.execute_resource(self.serv, oid=OID.Test, iid=0,\n                              rid=RID.Test.ResInitIntArray, content=b\"0='1500'\")\n        with self.assertRaises(socket.timeout):\n            self.serv.recv(timeout_s=1)\n        # Change value of /33605/0/3/0 to 2000\n        self.execute_resource(self.serv, oid=OID.Test, iid=0,\n                              rid=RID.Test.ResInitIntArray, content=b\"0='2000'\")\n        with self.assertRaises(socket.timeout):\n            self.serv.recv(timeout_s=1)\n        # Change value of /33605/0/3/0 to 2001, notification is expected\n        self.execute_resource(self.serv, oid=OID.Test, iid=0,\n                              rid=RID.Test.ResInitIntArray, content=b\"0='2001'\")\n        notify_pkt = self.serv.recv(timeout_s=1)\n        self.assertMsgEqual(Lwm2mNotify(\n            token=observe_pkt.token, content=b'2001'), notify_pkt)\n\n\nclass ObserveWithAttributesConflict(test_suite.Lwm2mSingleServerTest,\n                                    test_suite.Lwm2mDmOperations):\n    def setUp(self):\n        super().setUp(maximum_version='1.2')\n\n    def runTest(self):\n        # Create object\n        self.create_instance(self.serv, oid=OID.Test, iid=0)\n\n        # Write an sttribute\n        self.write_attributes(self.serv, oid=OID.Test, iid=0, rid=RID.Test.Counter,\n                              query=['gt=2'])\n\n        # Set up an observation with the same attibute attached\n        observe_pkt = self.observe(\n            self.serv, oid=OID.Test, iid=0, rid=RID.Test.Counter, gt=7)\n\n        # Increase counter to cross the treshold\n        for _ in range(8):\n            self.execute_resource(self.serv, oid=OID.Test,\n                                  iid=0, rid=RID.Test.IncrementCounter)\n\n        # At this point we should receive the first notification (ignoring gt=2 attached to the\n        # resource)\n        notify_pkt = self.serv.recv(timeout_s=1)\n        self.assertMsgEqual(Lwm2mNotify(\n            token=observe_pkt.token, content=b'8'), notify_pkt)\n\n\nclass ObserveWithAttributesDefaultPmin(test_suite.Lwm2mSingleServerTest,\n                                       test_suite.Lwm2mDmOperations):\n    def setUp(self):\n        super().setUp(maximum_version='1.2')\n\n    def runTest(self):\n        # Create object\n        self.create_instance(self.serv, oid=OID.Test, iid=0)\n\n        # Write some large pmin which should not be used\n        self.write_attributes(self.serv, oid=OID.Test, iid=0, rid=RID.Test.Counter,\n                              query=['pmin=9999'])\n\n        # Observe with gt attribute which requires pmin to be exceded to send notification\n        observe_pkt = self.observe(\n            self.serv, oid=OID.Test, iid=0, rid=RID.Test.Counter, gt=7)\n\n        # Increase counter to cross the treshold\n        for _ in range(8):\n            self.execute_resource(self.serv, oid=OID.Test,\n                                  iid=0, rid=RID.Test.IncrementCounter)\n\n        # The notification should be received without waiting for pmin set for the resource\n        notify_pkt = self.serv.recv(timeout_s=1)\n        self.assertMsgEqual(Lwm2mNotify(\n            token=observe_pkt.token, content=b'8'), notify_pkt)\n\n\nclass ObserveWithAttributesPreserveAttrs(test_suite.Lwm2mSingleServerTest,\n                                         test_suite.Lwm2mDmOperations):\n    def setUp(self):\n        super().setUp(maximum_version='1.2')\n\n    def runTest(self):\n        # Create object\n        self.create_instance(self.serv, oid=OID.Test, iid=0)\n\n        # Write some large gt which should not be used\n        self.write_attributes(self.serv, oid=OID.Test, iid=0, rid=RID.Test.Counter,\n                              query=['gt=9999'])\n\n        # Observe with gt attribute which requires pmin to be exceeded to send notification\n        observe_pkt = self.observe(\n            self.serv, oid=OID.Test, iid=0, rid=RID.Test.Counter, lt=3, gt=7)\n\n        # Discover should return the value of the attribute attached to the resource, not to\n        # the observation\n        discover_pkt = self.discover(\n            self.serv, oid=OID.Test, iid=0, rid=RID.Test.Counter)\n        self.assertEqual(discover_pkt.content, b'</33605/0/1>;gt=9999')\n"
  },
  {
    "path": "tests/integration/suites/default/offline.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nfrom framework.lwm2m_test import *\nfrom . import retransmissions\n\nOFFLINE_INTERVAL = 4\n\n\nclass OfflineWithDtlsResumeTest(test_suite.Lwm2mDtlsSingleServerTest):\n    def runTest(self):\n        # Create object\n        req = Lwm2mCreate('/%d' % (OID.Test,), TLV.make_instance(instance_id=0).serialize())\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mCreated.matching(req)(), self.serv.recv())\n\n        # Force Update so that we won't have differing data models during exit-offline\n        self.communicate('send-update')\n        self.assertDemoUpdatesRegistration(content=ANY)\n\n        # Observe: Timestamp\n        observe_req = Lwm2mObserve(ResPath.Test[0].Timestamp)\n        self.serv.send(observe_req)\n\n        timestamp_pkt = self.serv.recv()\n        self.assertMsgEqual(Lwm2mContent.matching(observe_req)(), timestamp_pkt)\n\n        # now enter offline mode\n        self.communicate('enter-offline')\n        enter_offline_time = time.time()\n\n        # if we were not fast enough, one more message might have come;\n        # we try to support both cases\n        try:\n            timestamp_pkt = self.serv.recv(timeout_s=1)\n        except socket.timeout:\n            pass\n\n        # now no messages shall arrive\n        with self.assertRaises(socket.timeout):\n            self.serv.recv(timeout_s=OFFLINE_INTERVAL)\n\n        # exit offline mode\n        self.communicate('exit-offline')\n\n        # client reconnects with DTLS session resumption\n        self.assertDtlsReconnect()\n\n        notifications = 0\n        while True:\n            try:\n                timestamp_pkt = self.serv.recv(timeout_s=0.9)\n                self.assertEqual(timestamp_pkt.token, observe_req.token)\n                notifications += 1\n            except socket.timeout:\n                break\n        end_time = time.time()\n\n        self.assertGreaterEqual(notifications, end_time - enter_offline_time - 1)\n        self.assertLessEqual(notifications, end_time - enter_offline_time + 1)\n\n        # Cancel Observe\n        req = Lwm2mObserve(ResPath.Test[0].Timestamp, observe=1, token=observe_req.token)\n        self.serv.send(req)\n        timestamp_pkt = self.serv.recv()\n        self.assertMsgEqual(Lwm2mContent.matching(req)(), timestamp_pkt)\n\n        # now no messages shall arrive\n        with self.assertRaises(socket.timeout):\n            self.serv.recv(timeout_s=2)\n\n\nclass OfflineWithReregisterTest(test_suite.Lwm2mDtlsSingleServerTest):\n    LIFETIME = OFFLINE_INTERVAL - 1\n\n    def setUp(self):\n        super().setUp(lifetime=OfflineWithReregisterTest.LIFETIME)\n\n    def runTest(self):\n        self.communicate('enter-offline')\n\n        # now no messages shall arrive\n        with self.assertRaises(socket.timeout):\n            self.serv.recv(timeout_s=OFFLINE_INTERVAL)\n\n        self.communicate('exit-offline')\n\n        # Register shall now come\n        self.assertDtlsReconnect()\n        self.assertDemoRegisters(lifetime=OfflineWithReregisterTest.LIFETIME)\n\n\nclass OfflineWithSecurityObjectChange(test_suite.Lwm2mDtlsSingleServerTest):\n    def runTest(self):\n        self.communicate('enter-offline')\n        # Notify anjay that Security Object Resource changed\n        self.communicate('notify %s' % (ResPath.Security[0].ServerURI,))\n        # This should not reload sockets\n        with self.assertRaises(socket.timeout):\n            self.serv.recv(timeout_s=1)\n\n        # Notify anjay that Security Object Instances changed\n        self.communicate('notify /0')\n        with self.assertRaises(socket.timeout):\n            self.serv.recv(timeout_s=1)\n\n        self.communicate('exit-offline')\n        self.assertDtlsReconnect()\n\n\nclass OfflineWithReconnect(test_suite.Lwm2mDtlsSingleServerTest):\n    def runTest(self):\n        self.communicate('enter-offline')\n        with self.assertRaises(socket.timeout):\n            self.serv.recv(timeout_s=OFFLINE_INTERVAL)\n        self.communicate('reconnect')\n        self.assertDtlsReconnect()\n\n\nclass OfflineWithoutDtlsTest(test_suite.Lwm2mSingleServerTest):\n    def runTest(self):\n        self.communicate('enter-offline')\n        with self.assertRaises(socket.timeout):\n            self.serv.recv(timeout_s=OFFLINE_INTERVAL)\n        self.communicate('exit-offline')\n        self.assertDemoRegisters()\n\n\nclass OfflineWithRegistrationUpdateSchedule(test_suite.Lwm2mDtlsSingleServerTest):\n    def runTest(self):\n        self.communicate('enter-offline')\n        self.communicate('send-update 0')\n        with self.assertRaises(socket.timeout):\n            pkt = self.serv.recv(timeout_s=1)\n\n        self.communicate('exit-offline')\n        self.assertDtlsReconnect()\n        self.assertDemoUpdatesRegistration()\n\n\nclass OfflineWithQueueMode:\n    class Test(retransmissions.RetransmissionTest.TestMixin, test_suite.Lwm2mDtlsSingleServerTest):\n        def setUp(self, extra_cmdline_args=None, binding='UQ', *args, **kwargs):\n            self.skipIfFeatureStatus('ANJAY_WITHOUT_QUEUE_MODE_AUTOCLOSE = ON',\n                                     'Queue mode autoclose disabled')\n            super().setUp(*args, extra_cmdline_args=(extra_cmdline_args or []) + ['--binding=UQ'],\n                          binding=binding, **kwargs)\n\n\nclass OfflineWithQueueModeTest(OfflineWithQueueMode.Test):\n    def runTest(self):\n        self.wait_until_socket_count(0, timeout_s=self.max_transmit_wait() + 2)\n        self.communicate('enter-offline')\n        time.sleep(2)\n\n        # After exiting offline mode, the client shall not reconnect\n        self.communicate('exit-offline')\n        with self.assertRaises(socket.timeout):\n            self.serv.recv(timeout_s=5)\n\n        # Something should happen only when there is something to send\n        self.communicate('send-update')\n        self.assertDtlsReconnect()\n        self.assertDemoUpdatesRegistration()\n\n\nclass OfflineWithQueueModeScheduledUpdate(OfflineWithQueueMode.Test):\n    def setUp(self):\n        super().setUp(lifetime=45, auto_register=False)\n\n    def runTest(self):\n        self.assertDemoRegisters(lifetime=45, binding='UQ')\n        next_planned_update = time.time() + 45.0 - self.max_transmit_wait()\n\n        self.wait_until_socket_count(0, timeout_s=self.max_transmit_wait() + 2.0)\n        self.communicate('enter-offline')\n        time.sleep(2)\n\n        # After exiting offline mode, the client shall not reconnect\n        self.communicate('exit-offline')\n        timeout_s = next_planned_update - time.time() - 2.0\n        self.assertGreater(timeout_s, 0.0)\n        with self.assertRaises(socket.timeout):\n            self.serv.recv(timeout_s=timeout_s)\n\n        # The Update message shall be delivered on time\n        self.assertDtlsReconnect(timeout_s=5)\n        self.assertDemoUpdatesRegistration()\n\n\nclass OfflineWithQueueModeNotify(OfflineWithQueueMode.Test, test_suite.Lwm2mDmOperations):\n    def runTest(self):\n        token = self.observe(self.serv, OID.EventLog, 0, RID.EventLog.LogData).token\n        self.wait_until_socket_count(0, timeout_s=self.max_transmit_wait() + 2)\n        self.communicate('enter-offline')\n        time.sleep(2)\n\n        # After exiting offline mode, the client shall not reconnect\n        self.communicate('exit-offline')\n        with self.assertRaises(socket.timeout):\n            self.serv.recv(timeout_s=5)\n\n        # Something should happen only when there is something to send\n        self.communicate('set-event-log-data Papaya')\n        self.assertDtlsReconnect()\n        self.assertMsgEqual(Lwm2mNotify(token=token), self.serv.recv())\n\n\nclass OfflineWithQueueModeScheduledNotify(OfflineWithQueueMode.Test, test_suite.Lwm2mDmOperations):\n    def runTest(self):\n        token = self.observe(self.serv, OID.EventLog, 0, RID.EventLog.LogData).token\n        self.wait_until_socket_count(0, timeout_s=self.max_transmit_wait() + 2)\n        self.communicate('enter-offline')\n        time.sleep(2)\n\n        self.communicate('set-event-log-data Papaya')\n        time.sleep(1)\n        # After exiting offline mode, the client shall deliver the unsent notification\n        self.communicate('exit-offline')\n        self.assertDtlsReconnect()\n        self.assertMsgEqual(Lwm2mNotify(token=token), self.serv.recv())\n\n\nclass OfflineWithQueueModeScheduledSend(OfflineWithQueueMode.Test, test_suite.Lwm2mDmOperations):\n    def setUp(self):\n        super().setUp(auto_register=False, minimum_version='1.1', maximum_version='1.1')\n        self.assertDemoRegisters(version='1.1', lwm2m11_queue_mode=True)\n\n    def runTest(self):\n        self.wait_until_socket_count(0, timeout_s=self.max_transmit_wait() + 2)\n        self.communicate('enter-offline')\n        time.sleep(2)\n\n        self.communicate('send_deferrable 1 %s' % (ResPath.Device.ModelNumber,))\n        time.sleep(1)\n        # After exiting offline mode, the client shall deliver the unsent notification\n        self.communicate('exit-offline')\n        self.assertDtlsReconnect()\n        pkt = self.serv.recv()\n        self.assertMsgEqual(Lwm2mSend(), pkt)\n        self.serv.send(Lwm2mChanged.matching(pkt)())\n\n\nclass ExternalSetLifetimeWhenOffline(test_suite.Lwm2mSingleServerTest):\n    def runTest(self):\n        self.communicate('enter-offline')\n        self.communicate('set-lifetime 1 8192')\n        # Anjay does not wake up\n        with self.assertRaises(socket.timeout):\n            self.serv.recv(timeout_s=3)\n        self.communicate('exit-offline')\n        self.assertDemoRegisters(lifetime=8192)\n\n\nclass ExternalSetLifetimeWhenOfflineDtls(test_suite.Lwm2mDtlsSingleServerTest):\n    def runTest(self):\n        self.communicate('enter-offline')\n        self.communicate('set-lifetime 1 8192')\n        # Anjay does not wake up\n        with self.assertRaises(socket.timeout):\n            self.serv.recv(timeout_s=3)\n        self.communicate('exit-offline')\n        self.assertDtlsReconnect()\n        self.assertDemoUpdatesRegistration(lifetime=8192)\n\n\nclass ObservationDroppingAfterNosecReconnect(test_suite.Lwm2mSingleServerTest,\n                                             test_suite.Lwm2mDmOperations):\n    def runTest(self):\n        self.observe(self.serv, OID.Device, 0, RID.Device.CurrentTime)\n        self.communicate('enter-offline')\n        # if we were not fast enough, one more message might have come;\n        # we try to support both cases\n        try:\n            self.serv.recv(timeout_s=1)\n        except socket.timeout:\n            pass\n\n        # now no messages shall arrive\n        with self.assertRaises(socket.timeout):\n            self.serv.recv(timeout_s=OFFLINE_INTERVAL)\n\n        # exit offline mode\n        self.communicate('exit-offline')\n        self.assertDemoRegisters()\n\n        # observation got canceled, no new messages shall arrive\n        with self.assertRaises(socket.timeout):\n            self.serv.recv(timeout_s=OFFLINE_INTERVAL)\n\n\nclass ForceReregisterDuringOffline(test_suite.Lwm2mDtlsSingleServerTest):\n    def runTest(self):\n        self.communicate('enter-offline')\n        self.wait_until_socket_count(0, timeout_s=5)\n        self.communicate('send-register')\n        time.sleep(1)\n        self.communicate('exit-offline')\n        self.assertDtlsReconnect()\n        self.assertDemoRegisters()\n"
  },
  {
    "path": "tests/integration/suites/default/plaintext_base64.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nimport base64\nimport itertools\nimport unittest\n\nfrom framework.lwm2m_test import *\nfrom framework.test_utils import *\n\nfrom . import block_response as br\n\n\ndef test_object_bytes_generator(num_bytes):\n    \"\"\" Generates exactly the same sequences of bytes as Test Object does. \"\"\"\n    return bytes(itertools.islice(itertools.cycle(range(128)), num_bytes))\n\n\nclass Base64Test:\n    class Test(test_suite.Lwm2mSingleServerTest,\n               test_suite.Lwm2mDmOperations):\n        def setUp(self):\n            super().setUp()\n            self.create_instance(self.serv, oid=OID.Test, iid=1)\n\n@unittest.skip(\"TODO: CoAP2 does not allow sending non-block messages larger than 1024\")\nclass Base64DifferentLengths(Base64Test.Test):\n    def runTest(self):\n        for length in range(1, 1049):\n            self.write_resource(self.serv, oid=OID.Test, iid=1,\n                                rid=RID.Test.ResBytesSize, content=str(length))\n            result = self.read_resource(self.serv, oid=OID.Test, iid=1, rid=RID.Test.ResBytes,\n                                        accept=coap.ContentFormat.TEXT_PLAIN)\n            decoded = base64.decodebytes(result.content)\n            self.assertEqual(test_object_bytes_generator(length), decoded)\n\n\nclass Base64BlockTransfer(br.BlockResponseTest, test_suite.Lwm2mDmOperations):\n    def runTest(self):\n        LENGTH = 9001\n        self.write_resource(self.serv, oid=OID.Test, iid=0,\n                            rid=RID.Test.ResBytesSize, content=str(LENGTH))\n        result = self.read_blocks(iid=0, accept=coap.ContentFormat.TEXT_PLAIN)\n        decoded = base64.decodebytes(result)\n        self.assertEqual(test_object_bytes_generator(LENGTH), decoded)\n\n\n@unittest.skip(\"TODO: CoAP2 does not allow sending non-block messages larger than 1024\")\nclass Base64ReadWrite(Base64Test.Test):\n    def runTest(self):\n        for length in range(1, 1049):\n            raw_data = test_object_bytes_generator(length)\n            b64_data = base64.encodebytes(raw_data).replace(b'\\n', b'')\n\n            self.write_resource(self.serv, oid=OID.Test, iid=1,\n                                rid=RID.Test.ResRawBytes, content=b64_data)\n\n            data = self.read_resource(self.serv, oid=OID.Test, iid=1,\n                                      rid=RID.Test.ResRawBytes,\n                                      accept=coap.ContentFormat.TEXT_PLAIN)\n            self.assertEqual(raw_data, base64.decodebytes(data.content))\n\n\nclass Base64InvalidWrite(Base64Test.Test):\n    def runTest(self):\n        def write(value, expected_error_code=coap.Code.RES_BAD_REQUEST):\n            self.write_resource(self.serv, oid=OID.Test, iid=1,\n                                rid=RID.Test.ResRawBytes, content=value,\n                                expect_error_code=expected_error_code)\n\n        write(b'A=A')\n        write(b'A=AAA')\n        write(b'A AAA')\n        write(b'A\\nAAA')\n        write(b'A\\vAAA')\n        write(b'A\\tAAA')\n        write(b'A==AAA=')\n        write(b'A')\n        write(b'AA')\n        write(b'AAA')\n        write(b'=')\n        write(b'==')\n        write(b'===')\n"
  },
  {
    "path": "tests/integration/suites/default/port_rebind.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nimport contextlib\nimport socket\nimport time\n\nfrom framework.lwm2m_test import *\n\n\nclass RandomPortRebind(test_suite.Lwm2mDtlsSingleServerTest, test_suite.Lwm2mDmOperations):\n    def runTest(self):\n        demo_port = self.serv.get_remote_addr()[1]\n\n        self.communicate('enter-offline')\n\n        deadline = time.time() + 5\n        while self.get_socket_count() > 0:\n            if time.time() > deadline:\n                self.fail('Socket not closed')\n            time.sleep(0.1)\n\n        conflict_socket_ipv4 = None\n        conflict_socket_ipv6 = None\n\n        try:\n            conflict_socket_ipv4 = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM)\n            conflict_socket_ipv4.bind(('', demo_port))\n        except:\n            pass\n        try:\n            conflict_socket_ipv6 = socket.socket(family=socket.AF_INET6, type=socket.SOCK_DGRAM)\n            conflict_socket_ipv6.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 1)\n            conflict_socket_ipv6.bind(('::', demo_port))\n        except:\n            pass\n\n        self.serv.reset()\n        self.communicate('exit-offline')\n        self.serv.listen(timeout_s=5)\n\n        # check that everything is working\n        self.read_path(self.serv, ResPath.Device.Manufacturer)\n\n        if conflict_socket_ipv4:\n            conflict_socket_ipv4.close()\n        if conflict_socket_ipv6:\n            conflict_socket_ipv6.close()\n\n        self.assertNotEqual(self.serv.get_remote_addr()[1], demo_port)\n\n\nclass PredefinedPortRebind(test_suite.Lwm2mDtlsSingleServerTest, test_suite.Lwm2mDmOperations):\n    def setUp(self):\n        with contextlib.closing(socket.socket(type=socket.SOCK_DGRAM)) as ephemeral_probe:\n            ephemeral_probe.bind(('0.0.0.0', 0))\n            self._demo_port = ephemeral_probe.getsockname()[1]\n\n        self.assertNotEqual(self._demo_port, 0)\n        super().setUp(extra_cmdline_args=['--port', '%s' % (self._demo_port,)])\n\n    def tearDown(self):\n        super().tearDown(auto_deregister=False)\n\n    def runTest(self):\n        self.assertEqual(self._demo_port, self.serv.get_remote_addr()[1])\n\n        self.communicate('enter-offline')\n\n        deadline = time.time() + 5\n        while self.get_socket_count() > 0:\n            if time.time() > deadline:\n                self.fail('Socket not closed')\n            time.sleep(0.1)\n\n        # Anjay first attempts to re-bind on the same address family, and if that fails\n        # it tries another (if possible). The thing is, in both cases it uses the same\n        # port. Now, on macOS you can have an IPv4 socket bound to some port, and still\n        # be able to bind using IPv6 on the same port. On Linux, you can't. To mitigate\n        # this mess, we may bind on IPv6 (if available) because then due to IPv4-mapping\n        # it should be not possible to bind using IPv4 to the same port.\n        for family, addr in zip((socket.AF_INET6, socket.AF_INET), ('::', '0.0.0.0')):\n            try:\n                with contextlib.closing(socket.socket(family, socket.SOCK_DGRAM)) as conflict_socket:\n                    conflict_socket.bind((addr, self._demo_port))\n\n                    self.serv.reset()\n                    self.communicate('exit-offline')\n                    with self.assertRaises(socket.timeout):\n                        self.serv.listen(timeout_s=5)\n                break\n            except socket.error:\n                continue\n\n        # inability to bind on predefined port is fatal, check that demo does not retry\n        with self.assertRaises(socket.timeout):\n            self.serv.listen(timeout_s=15)\n        self.assertEqual(self.get_socket_count(), 0)\n"
  },
  {
    "path": "tests/integration/suites/default/queue_mode.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nimport framework.test_suite as test_suite\nfrom framework.lwm2m_test import *\nfrom . import access_control, retransmissions, firmware_update\n\n\nclass QueueMode:\n    # it's kind of a duplicate of skipIfFeatureStatus(), but here it's not\n    # a simple run or skip, expected behavior depends on it\n    @staticmethod\n    def autoclose_disabled(test_case):\n        import subprocess\n        output = subprocess.run([test_case._get_demo_executable(), '-e', 'dummy', '-u', 'invalid'],\n                                stdout=subprocess.PIPE, stderr=subprocess.STDOUT).stdout.decode(\n            'utf-8')\n        return 'ANJAY_WITHOUT_QUEUE_MODE_AUTOCLOSE = ON' in output\n\n    class Test(retransmissions.RetransmissionTest.TestMixin):\n        def setUp(self, *args, **kwargs):\n            self.autoclose_disabled = QueueMode.autoclose_disabled(self)\n            super().setUp(*args, **kwargs)\n\n\nclass QueueModeBehaviour(retransmissions.RetransmissionTest.TestMixin,\n                         access_control.AccessControl.Test):\n    PSK_IDENTITY = b'test-identity'\n    PSK_KEY = b'test-key'\n\n    def setUp(self):\n        self.skipIfFeatureStatus('ANJAY_WITHOUT_QUEUE_MODE_AUTOCLOSE = ON',\n                                 'Queue mode autoclose disabled')\n        super().setUp(servers=[\n            Lwm2mServer(coap.DtlsServer(psk_key=self.PSK_KEY, psk_identity=self.PSK_IDENTITY)),\n            Lwm2mServer(coap.DtlsServer(psk_key=self.PSK_KEY, psk_identity=self.PSK_IDENTITY))],\n            extra_cmdline_args=['--identity', str(binascii.hexlify(self.PSK_IDENTITY), 'ascii'),\n                                '--key', str(binascii.hexlify(self.PSK_KEY), 'ascii')])\n\n    def tearDown(self):\n        self.teardown_demo_with_servers(deregister_servers=[self.servers[1]])\n\n    def runTest(self):\n        # create the test object and give read access to servers[0]\n        self.create_instance(server=self.servers[1], oid=OID.Test)\n        self.update_access(server=self.servers[1], oid=OID.Test, iid=0,\n                           acl=[access_control.make_acl_entry(1, access_control.AccessMask.READ),\n                                access_control.make_acl_entry(2, access_control.AccessMask.OWNER)])\n\n        # first check if sockets stay online in non-queue mode\n        time.sleep(self.max_transmit_wait() - 2)\n        self.assertEqual(self.get_socket_count(), 2)\n        time.sleep(4)\n        self.assertEqual(self.get_socket_count(), 2)\n\n        # put servers[0] into queue mode\n        self.write_resource(self.servers[0], OID.Server, 1, RID.Server.Binding, b'UQ')\n        self.assertDemoUpdatesRegistration(self.servers[0], binding='UQ', content=ANY)\n\n        # Observe the Counter argument\n        self.observe(self.servers[0], OID.Test, 0, RID.Test.Counter)\n\n        time.sleep(self.max_transmit_wait() - 2)\n        self.assertEqual(self.get_socket_count(), 2)\n        time.sleep(4)\n        self.assertEqual(self.get_socket_count(), 1)\n\n        # Trigger Notification from the non-queue server\n        self.execute_resource(self.servers[1], OID.Test, 0, RID.Test.IncrementCounter)\n\n        self.assertDtlsReconnect(self.servers[0])\n        pkt = self.servers[0].recv()\n        self.assertIsInstance(pkt, Lwm2mNotify)\n        self.assertEqual(self.get_socket_count(), 2)\n\n        # \"queued RPCs\"\n        self.read_resource(self.servers[0], OID.Test, 0, RID.Test.Timestamp)\n        # cancel observation\n        self.observe(self.servers[0], OID.Test, 0, RID.Test.Counter, observe=1)\n\n        # assert queue mode operation again\n        time.sleep(12)\n        self.assertEqual(self.get_socket_count(), 2)\n        time.sleep(4)\n        self.assertEqual(self.get_socket_count(), 1)\n\n\nclass QueueModePreferenceIneffectiveForLwm2m10(QueueModeBehaviour):\n    def runTest(self):\n        self.communicate('set-queue-mode-preference PREFER_QUEUE_MODE')\n        self.communicate('send-update')\n        self.assertDemoUpdatesRegistration(self.servers[0])\n        self.assertDemoUpdatesRegistration(self.servers[1])\n        super().runTest()\n\n\nclass ForceQueueMode(QueueMode.Test, test_suite.Lwm2mSingleServerTest):\n    def tearDown(self):\n        auto_deregister = False\n        auto_deregister = self.autoclose_disabled\n        super().tearDown(auto_deregister=auto_deregister)\n\n    def runTest(self):\n        self.communicate('set-queue-mode-preference FORCE_QUEUE_MODE')\n\n        # change is not applied until Update\n        time.sleep(self.max_transmit_wait() - 2)\n        self.assertEqual(self.get_socket_count(), 1)\n        time.sleep(4)\n        self.assertEqual(self.get_socket_count(), 1)\n\n        self.communicate('send-update')\n        self.assertDemoUpdatesRegistration()\n\n        if self.autoclose_disabled:\n            return\n        # effectively queue mode, even though binding is \"U\"\n        time.sleep(self.max_transmit_wait() - 2)\n        self.assertEqual(self.get_socket_count(), 1)\n        time.sleep(4)\n        self.assertEqual(self.get_socket_count(), 0)\n\n\nclass ForceOnlineMode(retransmissions.RetransmissionTest.TestMixin,\n                      test_suite.Lwm2mSingleServerTest):\n    def setUp(self):\n        super().setUp(extra_cmdline_args=['--binding=UQ'], auto_register=False)\n        self.assertDemoRegisters(self.serv, binding='UQ')\n\n    def runTest(self):\n        self.communicate('set-queue-mode-preference FORCE_ONLINE_MODE')\n        self.communicate('send-update')\n        self.assertDemoUpdatesRegistration()\n\n        # effectively online mode, even though binding is \"UQ\"\n        time.sleep(self.max_transmit_wait() - 2)\n        self.assertEqual(self.get_socket_count(), 1)\n        time.sleep(4)\n        self.assertEqual(self.get_socket_count(), 1)\n\n\nclass Lwm2m11QueueMode(retransmissions.RetransmissionTest.TestMixin,\n                       test_suite.Lwm2mDtlsSingleServerTest):\n    def setUp(self):\n        self.skipIfFeatureStatus('ANJAY_WITHOUT_QUEUE_MODE_AUTOCLOSE = ON',\n                                 'Queue mode autoclose disabled')\n        super().setUp(maximum_version='1.1')\n\n    def runTest(self):\n        # default: Prefer Online Mode, no queue mode\n        time.sleep(self.max_transmit_wait() - 2)\n        self.assertEqual(self.get_socket_count(), 1)\n        time.sleep(4)\n        self.assertEqual(self.get_socket_count(), 1)\n\n        # Force Online Mode, no queue mode\n        self.communicate('set-queue-mode-preference FORCE_ONLINE_MODE')\n        self.communicate('send-update')\n        self.assertDemoUpdatesRegistration()\n        time.sleep(self.max_transmit_wait() - 2)\n        self.assertEqual(self.get_socket_count(), 1)\n        time.sleep(4)\n        self.assertEqual(self.get_socket_count(), 1)\n\n        # Prefer Queue Mode, queue mode\n        self.communicate('set-queue-mode-preference PREFER_QUEUE_MODE')\n        self.communicate('send-update')\n        # enabling queue mode on 1.1, needs re-registration\n        self.assertDemoRegisters(self.serv, version='1.1', lwm2m11_queue_mode=True)\n        time.sleep(self.max_transmit_wait() - 2)\n        self.assertEqual(self.get_socket_count(), 1)\n        time.sleep(4)\n        self.assertEqual(self.get_socket_count(), 0)\n\n        # Force Queue Mode, queue mode\n        self.communicate('set-queue-mode-preference FORCE_QUEUE_MODE')\n        self.communicate('send-update')\n        self.assertDtlsReconnect(self.serv)\n        self.assertDemoUpdatesRegistration()\n        time.sleep(self.max_transmit_wait() - 2)\n        self.assertEqual(self.get_socket_count(), 1)\n        time.sleep(4)\n        self.assertEqual(self.get_socket_count(), 0)\n\n        # Prefer Online Mode again, no queue mode\n        self.communicate('set-queue-mode-preference PREFER_ONLINE_MODE')\n        self.communicate('send-update')\n        # disabling queue mode on 1.1, needs re-registration\n        self.assertDtlsReconnect(self.serv)\n        self.assertDemoRegisters(self.serv, version='1.1', lwm2m11_queue_mode=False)\n        time.sleep(self.max_transmit_wait() - 2)\n        self.assertEqual(self.get_socket_count(), 1)\n        time.sleep(4)\n        self.assertEqual(self.get_socket_count(), 1)\n\n\nclass Lwm2m11UQBinding(QueueMode.Test, test_suite.Lwm2mDtlsSingleServerTest):\n    def setUp(self):\n        # UQ binding is not LwM2M 1.1-compliant, but we support it anyway\n        super().setUp(extra_cmdline_args=['--binding=UQ'], auto_register=False,\n                      maximum_version='1.1')\n        self.assertDemoRegisters(self.serv, version='1.1', lwm2m11_queue_mode=True)\n\n    def runTest(self):\n        if self.autoclose_disabled:\n            return\n        # default: Prefer Online Mode, queue mode\n        time.sleep(self.max_transmit_wait() - 2)\n        self.assertEqual(self.get_socket_count(), 1)\n        time.sleep(4)\n        self.assertEqual(self.get_socket_count(), 0)\n\n        # Prefer Queue Mode, queue mode\n        self.communicate('set-queue-mode-preference PREFER_QUEUE_MODE')\n        self.communicate('send-update')\n        self.assertDtlsReconnect(self.serv)\n        self.assertDemoUpdatesRegistration()\n        time.sleep(self.max_transmit_wait() - 2)\n        self.assertEqual(self.get_socket_count(), 1)\n        time.sleep(4)\n        self.assertEqual(self.get_socket_count(), 0)\n\n        # Force Queue Mode, queue mode\n        self.communicate('set-queue-mode-preference FORCE_QUEUE_MODE')\n        self.communicate('send-update')\n        self.assertDtlsReconnect(self.serv)\n        self.assertDemoUpdatesRegistration()\n        time.sleep(self.max_transmit_wait() - 2)\n        self.assertEqual(self.get_socket_count(), 1)\n        time.sleep(4)\n        self.assertEqual(self.get_socket_count(), 0)\n\n        # Force Online Mode, no queue mode\n        self.communicate('set-queue-mode-preference FORCE_ONLINE_MODE')\n        self.communicate('send-update')\n        # disabling queue mode on 1.1, needs re-registration\n        self.assertDtlsReconnect(self.serv)\n        self.assertDemoRegisters(self.serv, version='1.1', lwm2m11_queue_mode=False)\n        time.sleep(self.max_transmit_wait() - 2)\n        self.assertEqual(self.get_socket_count(), 1)\n        time.sleep(4)\n        self.assertEqual(self.get_socket_count(), 1)\n\n\nclass QueueModeAfterManualReconnect(retransmissions.RetransmissionTest.TestMixin,\n                                    firmware_update.SameSocketDownload.Test):\n    def setUp(self):\n        self.skipIfFeatureStatus('ANJAY_WITHOUT_QUEUE_MODE_AUTOCLOSE = ON',\n                                 'Queue mode autoclose disabled')\n        super().setUp(extra_cmdline_args=['--binding=UQ'], maximum_version='1.1', binding=None,\n                      lwm2m11_queue_mode=True, psk_identity=b'test-identity', psk_key=b'test-key')\n\n    def tearDown(self):\n        super().tearDown(auto_deregister=False)\n\n    def runTest(self):\n        self.start_download()\n        self.serv.recv()\n        with self.serv.fake_close():\n            self.wait_until_socket_count(0, timeout_s=5)\n\n        # reconnect after a failure\n        self.serv.reset()\n        self.communicate('reconnect')\n        self.serv.listen(timeout_s=5)\n        time.sleep(self.max_transmit_wait() - 2)\n        self.assertEqual(self.get_socket_count(), 1)\n        time.sleep(4)\n        self.assertEqual(self.get_socket_count(), 0)\n\n\nclass QueueModeAfterTimedOutSend(QueueMode.Test, firmware_update.SameSocketDownload.Test):\n    def setUp(self):\n        super().setUp(extra_cmdline_args=['--binding=UQ'], maximum_version='1.1', binding=None,\n                      lwm2m11_queue_mode=True, psk_identity=b'test-identity', psk_key=b'test-key')\n\n    def tearDown(self):\n        auto_deregister = False\n        auto_deregister = self.autoclose_disabled\n        super().tearDown(auto_deregister=auto_deregister)\n\n    def runTest(self):\n        self.start_download()\n        self.serv.recv()\n        with self.serv.fake_close():\n            self.wait_until_socket_count(0, timeout_s=5)\n\n        # reconnect after a failure\n        self.serv.reset()\n        self.communicate('reconnect')\n        self.serv.listen(timeout_s=5)\n        time.sleep(self.max_transmit_wait() - 2)\n        self.assertEqual(self.get_socket_count(), 1)\n\n        self.communicate('send 1 %s' % (ResPath.Device.ModelNumber,))\n        sent_time = time.time()\n        expected_close = sent_time + self.max_transmit_wait()\n\n        for i in range(self.MAX_RETRANSMIT + 1):\n            pkt = self.serv.recv(timeout_s=max(1, expected_close - time.time()))\n            self.assertMsgEqual(Lwm2mSend(), pkt)\n\n        timeout = expected_close - time.time() - 2\n        if timeout > 0.0:\n            time.sleep(timeout)\n        self.assertEqual(self.get_socket_count(), 1)\n        time.sleep(4)\n        if self.autoclose_disabled:\n            return\n        self.assertEqual(self.get_socket_count(), 0)\n\n\nclass TlsQueueMode(test_suite.Lwm2mSingleTcpServerTest):\n    PSK_IDENTITY = b'test-identity'\n    PSK_KEY = b'test-key'\n\n    def setUp(self, *args, **kwargs):\n        self.skipIfFeatureStatus('ANJAY_WITHOUT_QUEUE_MODE_AUTOCLOSE = ON',\n                                 'Queue mode autoclose disabled')\n        super().setUp(extra_cmdline_args=['--tcp-request-timeout', '5'],\n                      psk_identity=self.PSK_IDENTITY, psk_key=self.PSK_KEY, maximum_version='1.1',\n                      binding='T')\n\n    def runTest(self):\n        self.communicate('set-queue-mode-preference PREFER_QUEUE_MODE')\n        self.communicate('send-update')\n        self.assertDemoRegisters(version='1.1', binding='T', lwm2m11_queue_mode=True)\n        self.wait_until_socket_count(0, timeout_s=6)\n\n        time.sleep(1)\n        self.serv.reset()\n        self.communicate('send-update')\n        self.serv.listen(timeout_s=5)\n        self.read_log_until_match(b'statefully resumed connection', timeout_s=5)\n        # no CSM message here\n        self.assertDemoUpdatesRegistration()\n\n\nclass NosecTcpQueueMode(test_suite.Lwm2mSingleTcpServerTest):\n    def setUp(self, *args, **kwargs):\n        self.skipIfFeatureStatus('ANJAY_WITHOUT_QUEUE_MODE_AUTOCLOSE = ON',\n                                 'Queue mode autoclose disabled')\n        super().setUp(extra_cmdline_args=['--tcp-request-timeout', '5'], maximum_version='1.1',\n                      binding='T')\n\n    def runTest(self):\n        self.communicate('set-queue-mode-preference PREFER_QUEUE_MODE')\n        self.communicate('send-update')\n        self.assertDemoRegisters(version='1.1', binding='T', lwm2m11_queue_mode=True)\n        self.wait_until_socket_count(0, timeout_s=6)\n\n        time.sleep(1)\n        self.serv.reset()\n        self.communicate('send-update')\n        self.assertTcpCsm()\n        self.assertDemoRegisters(version='1.1', binding='T', lwm2m11_queue_mode=True)\n\n\nclass ReconnectServerIgnoredDuringQueueMode(QueueModeAfterManualReconnect):\n    def runTest(self):\n        time.sleep(self.max_transmit_wait() - 2)\n        self.assertEqual(self.get_socket_count(), 1)\n        time.sleep(4)\n        self.assertEqual(self.get_socket_count(), 0)\n\n        self.communicate('reconnect-server 1')\n        with self.assertRaises(socket.timeout):\n            self.serv.recv(timeout_s=5)\n\n\nclass ReconnectServerDuringQueueMode(retransmissions.RetransmissionTest.TestMixin,\n                                          firmware_update.SameSocketDownload.Test):\n    def setUp(self):\n        self.skipIfFeatureStatus('ANJAY_WITHOUT_QUEUE_MODE_AUTOCLOSE = OFF',\n                                 'Queue mode autoclose enabled')\n        super().setUp(extra_cmdline_args=['--binding=UQ'], maximum_version='1.1', binding=None,\n                      lwm2m11_queue_mode=True, psk_identity=b'test-identity', psk_key=b'test-key')\n\n    def runTest(self):\n        time.sleep(self.max_transmit_wait() + 2)\n        self.assertEqual(self.get_socket_count(), 1)\n        self.communicate('reconnect-server 1')\n        self.assertDtlsReconnect()\n\n\nclass ForceReregisterDuringQueueMode(QueueModeAfterManualReconnect):\n    def runTest(self):\n        time.sleep(self.max_transmit_wait() - 2)\n        self.assertEqual(self.get_socket_count(), 1)\n        time.sleep(4)\n        self.assertEqual(self.get_socket_count(), 0)\n\n        self.communicate('send-register 1')\n        self.assertDtlsReconnect()\n        self.assertDemoRegisters(version='1.1', lwm2m11_queue_mode=True)\n        time.sleep(self.max_transmit_wait() - 2)\n        self.assertEqual(self.get_socket_count(), 1)\n        time.sleep(4)\n        self.assertEqual(self.get_socket_count(), 0)\n"
  },
  {
    "path": "tests/integration/suites/default/read_composite.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nfrom framework.lwm2m_test import *\nfrom framework.test_utils import *\nfrom . import block_write as bw\n\n\nclass Test:\n    class ReadComposite(test_suite.Lwm2mSingleServerTest,\n                        test_suite.Lwm2mDmOperations):\n        def setUp(self, inbuf_size=None, outbuf_size=None, extra_cmdline_args=None, **kwargs):\n            extra_args = extra_cmdline_args\n            if extra_args is None:\n                extra_args = []\n            if inbuf_size is not None:\n                extra_args += ['-I', str(inbuf_size)]\n            if outbuf_size is not None:\n                extra_args += ['-O', str(outbuf_size)]\n\n            super().setUp(maximum_version='1.1', extra_cmdline_args=extra_args, **kwargs)\n\n\nclass ReadCompositeSupportedFormats(Test.ReadComposite):\n    def runTest(self):\n        SUPPORTED_FORMATS = [\n            coap.ContentFormat.APPLICATION_LWM2M_SENML_CBOR,\n            coap.ContentFormat.APPLICATION_LWM2M_SENML_JSON,\n            coap.ContentFormat.APPLICATION_LWM2M_CBOR,\n        ]\n        for fmt in SUPPORTED_FORMATS:\n            self.read_composite(self.serv, [ResPath.Device.Manufacturer], accept=fmt)\n\n        for fmt in coap.ContentFormat.iter():\n            if fmt in SUPPORTED_FORMATS:\n                continue\n\n            self.read_composite(self.serv, [ResPath.Device.Manufacturer], accept=fmt,\n                                expect_error_code=coap.Code.RES_NOT_ACCEPTABLE)\n\n\nclass ReadCompositePartialPresence(Test.ReadComposite):\n    def runTest(self):\n        # /33605/0 is not present, and will not be returned in the payload\n        res = self.read_composite(self.serv,\n                                  [ResPath.Test[0].ResBytesBurst,\n                                   ResPath.Device.Manufacturer])\n        self.assertEqual(\n            [{\n                SenmlLabel.NAME: ResPath.Device.Manufacturer,\n                SenmlLabel.STRING: '0023C7'\n            }],\n            CBOR.parse(res.content))\n\n\n# LightweightM2M-1.1-int-281 - Partially Successful Read-Composite Operation\nclass ReadCompositeExecutableResource(Test.ReadComposite):\n    def runTest(self):\n        res = self.read_composite(self.serv,\n                                  [ResPath.Server[1].Lifetime,\n                                   ResPath.Server[1].Binding,\n                                   ResPath.Server[1].RegistrationUpdateTrigger])\n        self.assertEqual(\n            [{\n                SenmlLabel.BASE_NAME: '/%d/%d' % (OID.Server, 1),\n                SenmlLabel.NAME: '/%d' % (RID.Server.Lifetime,),\n                SenmlLabel.VALUE: 86400\n            }, {\n                SenmlLabel.NAME: '/%d' % (RID.Server.Binding,),\n                SenmlLabel.STRING: 'U'\n            }],\n            CBOR.parse(res.content))\n\n\nBIG_LIST_OF_REQUESTED_PATHS = [\n    ResPath.Test[0].Timestamp,\n    ResPath.Test[0].ResInt,\n    ResPath.Test[0].ResBool,\n    ResPath.Test[0].ResFloat,\n    ResPath.Test[0].ResString,\n    ResPath.Test[0].ResDouble\n]\n\n\nclass ReadCompositeBlockRequest(Test.ReadComposite):\n    INBUF_SIZE = 64\n\n    def setUp(self):\n        super().setUp(inbuf_size=self.__class__.INBUF_SIZE)\n\n    def runTest(self):\n        self.create_instance(self.serv, oid=OID.Test, iid=0)\n\n        encoded_paths = CBOR.serialize([\n            {SenmlLabel.NAME: str(p)} for p in BIG_LIST_OF_REQUESTED_PATHS\n        ])\n        self.assertGreater(len(encoded_paths), self.__class__.INBUF_SIZE)\n\n        pkts = list(bw.packets_from_chunks(chunks=list(bw.equal_chunk_splitter(16)(encoded_paths)),\n                                           format=coap.ContentFormat.APPLICATION_LWM2M_SENML_CBOR,\n                                           code=coap.Code.REQ_FETCH,\n                                           path=None))\n\n        for pkt in pkts[:-1]:\n            self.serv.send(pkt)\n            self.assertMsgEqual(Lwm2mContinue.matching(pkt)(), self.serv.recv())\n\n        self.serv.send(pkts[-1])\n        res = self.serv.recv()\n        self.assertMsgEqual(Lwm2mContent.matching(pkts[-1])(), res)\n\n\nclass ReadCompositeBlockResponse(Test.ReadComposite):\n    # enough to hold Register, but not Read-Composite response\n    OUTPUT_SIZE = 100\n\n    def setUp(self, **kwargs):\n        super().setUp(outbuf_size=self.__class__.OUTPUT_SIZE, auto_register=False, **kwargs)\n\n        from . import register as r\n        r.BlockRegister().Test()(self.serv, version='1.1')\n\n        self.create_instance(self.serv, oid=OID.Test, iid=0)\n\n    def runTest(self):\n        req = Lwm2mReadComposite(paths=BIG_LIST_OF_REQUESTED_PATHS)\n        self.serv.send(req)\n        # Read to the end using blockwise transfer.\n        while True:\n            pkt = self.serv.recv()\n            block2 = pkt.get_options(coap.Option.BLOCK2)[0]\n            if not block2.has_more():\n                break\n\n            block2 = coap.Option.BLOCK2(seq_num=block2.seq_num() + 1,\n                                        has_more=False,\n                                        block_size=block2.block_size())\n            self.serv.send(Lwm2mReadComposite(token=req.token, paths=[], options=[block2]))\n\n\nclass ReadCompositeBlockResponseRetransmissions(ReadCompositeBlockResponse):\n    def setUp(self):\n        super().setUp(extra_cmdline_args=['--cache-size', '4096'])\n\n    def runTest(self):\n        req = Lwm2mReadComposite(paths=BIG_LIST_OF_REQUESTED_PATHS)\n\n        self.serv.send(req)\n        resp1 = self.serv.recv()\n\n        self.serv.send(req)\n        resp2 = self.serv.recv()\n\n        self.assertMsgEqual(resp1, resp2)\n\n        # Read the rest of the response\n        pkt = resp1\n        while True:\n            block2 = pkt.get_options(coap.Option.BLOCK2)[0]\n            if not block2.has_more():\n                break\n\n            block2 = coap.Option.BLOCK2(seq_num=block2.seq_num() + 1,\n                                        has_more=False,\n                                        block_size=block2.block_size())\n            self.serv.send(Lwm2mReadComposite(token=req.token, paths=[], options=[block2]))\n            pkt = self.serv.recv()\n\n\nclass ReadCompositeRootPath(Test.ReadComposite):\n    def runTest(self):\n        req = Lwm2mReadComposite(paths=['/'])\n        self.serv.send(req)\n\n        # Read to the end using blockwise transfer.\n        while True:\n            pkt = self.serv.recv()\n            block2 = pkt.get_options(coap.Option.BLOCK2)[0]\n            if not block2.has_more():\n                break\n\n            block2 = coap.Option.BLOCK2(seq_num=block2.seq_num() + 1,\n                                        has_more=False,\n                                        block_size=block2.block_size())\n            self.serv.send(Lwm2mReadComposite(token=req.token, paths=[], options=[block2]))\n"
  },
  {
    "path": "tests/integration/suites/default/reboot.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nfrom framework.lwm2m_test import *\n\n\nclass RebootSendsResponseTest(test_suite.Lwm2mSingleServerTest):\n    def _get_valgrind_args(self):\n        # Reboot cannot be performed when demo is run under valgrind\n        return []\n\n    def runTest(self):\n        # should send a response before rebooting\n        req = Lwm2mExecute(ResPath.Device.Reboot)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.serv.recv())\n\n        # should register after rebooting\n        self.serv.reset()\n        self.assertDemoRegisters(self.serv)\n"
  },
  {
    "path": "tests/integration/suites/default/register.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nimport concurrent.futures\nimport os\nimport socket\nimport unittest\n\nfrom framework_tools.lwm2m.coap.server import SecurityMode\nfrom framework_tools.lwm2m.coap.transport import Transport\nfrom framework.lwm2m_test import *\nfrom suites.default import bootstrap_client\n\nimport pymbedtls\n\nclass CertificatesTest:\n    class TestMixin:\n        def _cert_file(self, filename):\n            if os.path.isabs(filename):\n                return filename\n            else:\n                # demo_path = 'anjay/output/bin'\n                return os.path.join(os.path.dirname(self.config.demo_path), 'certs', filename)\n\n        def setUp(self, client_ca_file=None, server_crt=None, server_key=None, client_crt_file=None,\n                  client_key_file=None, server_crt_file=None, extra_cmdline_args=None,\n                  *args, **kwargs):\n            extra_cmdline_args = [*extra_cmdline_args] if extra_cmdline_args is not None else []\n            if client_ca_file is not None:\n                self.client_ca_file = self._cert_file(client_ca_file)\n            if server_crt is not None:\n                self.server_crt = self._cert_file(server_crt)\n            if server_key is not None:\n                self.server_key = self._cert_file(server_key)\n            if (client_crt_file is not None):\n                self.client_crt_file = self._cert_file(client_crt_file)\n                extra_cmdline_args += ['-C' + self.client_crt_file]\n            if (client_key_file is not None):\n                self.client_key_file = self._cert_file(client_key_file)\n                extra_cmdline_args += ['-K' + self.client_key_file]\n            if (server_crt_file is not None):\n                self.server_crt_file = self._cert_file(server_crt_file)\n                extra_cmdline_args += ['-P' + self.server_crt_file]\n            super().setUp(client_ca_file=getattr(self, 'client_ca_file', None),\n                          server_crt_file=getattr(self, 'server_crt', None),\n                          server_key_file=getattr(self, 'server_key', None),\n                          extra_cmdline_args=extra_cmdline_args, *args, **kwargs)\n\n    class TestUDPMixin(TestMixin, test_suite.Lwm2mSingleServerTest):\n        pass\n\n    class TestTCPMixin(TestMixin, test_suite.Lwm2mSingleTcpServerTest):\n        def setUp(self, *args, **kwargs):\n\n            tls_version = 'TLSv1.2'\n            # pymbedtls.Context.mbedtls_version returns pymbedtls mbedtls version but we assume that\n            # Anjay uses the same\n            #\n            # Hybrid TLS 1.2 / 1.3 is not supported in Mbed TLS on server side until v3.5.0\n            if pymbedtls.Context.supports_TLS_1_3() and pymbedtls.Context.mbedtls_version() >= 0x03050000:\n                tls_version = 'TLSv1.3'\n\n            super().setUp(binding='T', tls_version=tls_version, *args, **kwargs)\n\n\n    class PcapEnabledTestMixin:\n        def read_certificate(self, file):\n            import cryptography\n            import cryptography.hazmat\n            import cryptography.x509\n            with open(self._cert_file(file), 'rb') as f:\n                data = f.read()\n            if b'-----BEGIN' in data:\n                return cryptography.x509.load_pem_x509_certificate(\n                    data, backend=cryptography.hazmat.backends.default_backend())\n            else:\n                return cryptography.x509.load_der_x509_certificate(\n                    data, backend=cryptography.hazmat.backends.default_backend())\n\n        def read_public_key(self, cert):\n            import cryptography\n            import cryptography.hazmat\n            import cryptography.x509\n\n            if not isinstance(cert, cryptography.x509.Certificate):\n                cert = self.read_certificate(cert)\n\n            return cert.public_key().public_bytes(\n                cryptography.hazmat.primitives.serialization.Encoding.DER,\n                cryptography.hazmat.primitives.serialization.PublicFormat.SubjectPublicKeyInfo)\n\n        def get_certificate_packet(self, cert=None, common_name=None):\n            import dpkt\n            import cryptography\n            import cryptography.hazmat\n            assert cert is None or common_name is None\n\n            if common_name is None:\n                if cert is None:\n                    cert = self.client_crt_file\n                if isinstance(cert, str):\n                    cert = self.read_certificate(cert)\n                common_name = cert.subject\n            if not isinstance(common_name, bytes):\n                common_name = common_name.public_bytes(\n                    cryptography.hazmat.backends.default_backend())\n\n            for pkt in self.read_pcap():\n                if isinstance(pkt, dpkt.ip.IP) \\\n                        and isinstance(pkt.data, (dpkt.udp.UDP, dpkt.tcp.TCP)) \\\n                        and pkt.data.dport == self.serv.get_listen_port() \\\n                        and common_name in pkt.data.data:\n                    return pkt.data.data\n            return None\n\n    class PcapEnabledTestUDP(test_suite.PcapEnabledTest, TestUDPMixin, PcapEnabledTestMixin):\n        pass\n\n    class PcapEnabledTestTCP(test_suite.PcapEnabledTest, TestTCPMixin, PcapEnabledTestMixin):\n        pass\n\n\nclass RegisterWithCertificates(CertificatesTest.TestUDPMixin):\n    def setUp(self):\n        super().setUp(server_crt='server.crt', server_key='server.key')\n\nclass RegisterTCPWithCertificates(CertificatesTest.TestTCPMixin):\n    def setUp(self):\n        super().setUp(server_crt='server.crt', server_key='server.key')\n\nclass RegisterTCPServerCertClientPSK(CertificatesTest.TestTCPMixin):\n    auto_deregister = False\n    def setUp(self):\n        extra_cmdline_args = ['--identity', str(binascii.hexlify(b'random'), 'ascii'),\n                              '--key',      str(binascii.hexlify(b'values'), 'ascii')]\n        super().setUp(server_crt='server.crt', server_key='server.key',\n                    extra_cmdline_args=extra_cmdline_args, forced_client_security_mode = 'psk',\n                    auto_register=False)\n\n    def runTest(self):\n        with self.assertRaises(RuntimeError):\n            self.assertTcpCsm(self.serv)\n            self.assertDemoRegisters(self.serv, binding='T')\n            self.auto_deregister = True\n        # -30592 == -0x7780 == MBEDTLS_ERR_SSL_FATAL_ALERT_MESSAGE\n        self.read_log_until_match(b'handshake failed: -30592', 1)\n\n    def tearDown(self):\n        super().tearDown(auto_deregister=self.auto_deregister)\n\n# TODO For now DTLSv1.3 in not supported in Mbed TLS\n# class RegisterUDPServerCertClientPSK(CertificatesTest.TestUDPMixin):\n#     auto_deregister = False\n#     def setUp(self):\n#         extra_cmdline_args = ['--identity', str(binascii.hexlify(b'random'), 'ascii'),\n#                               '--key',      str(binascii.hexlify(b'values'), 'ascii')]\n#         super().setUp(server_crt='server.crt', server_key='server.key',\n#                     extra_cmdline_args=extra_cmdline_args, forced_client_security_mode = 'psk',\n#                     tls_version='TLSv1.3', auto_register=False)\n\n#     def runTest(self):\n#         with self.assertRaises(RuntimeError):\n#             self.assertDemoRegisters(self.serv)\n#             self.auto_deregister = True\n#         # -30592 == -0x7780 == MBEDTLS_ERR_SSL_FATAL_ALERT_MESSAGE\n#         self.read_log_until_match(b'handshake failed: -30592', 1)\n\n#     def tearDown(self):\n#         super().tearDown(auto_deregister=self.auto_deregister)\n\nclass RegisterTCPWithPSK(CertificatesTest.TestTCPMixin):\n    def setUp(self):\n        super().setUp(psk_identity=b'random', psk_key=b'value')\n\n\nclass RegisterWithCertificateChainExternalMixin:\n    def setUp(self, extra_cmdline_args=[], **kwargs):\n        extra_cmdline_args += ['--use-external-security-info']\n        super().setUp(client_ca_file='root.crt', server_crt='server.crt', server_key='server.key',\n                      client_crt_file='client2-full-path.crt', client_key_file='client2.key.der',\n                      extra_cmdline_args=extra_cmdline_args, **kwargs)\n\n    def runTest(self):\n        import time\n\n        certificate_packet = None\n        deadline = time.time() + self.DEFAULT_MSG_TIMEOUT\n        while certificate_packet is None and time.time() <= deadline:\n            time.sleep(0.1)\n            certificate_packet = self.get_certificate_packet()\n\n        self.assertIsNotNone(certificate_packet)\n        self.assertIn(self.read_public_key('client2.crt.der'), certificate_packet)\n        self.assertIn(self.read_public_key('client2_ca.crt.der'), certificate_packet)\n        self.assertIn(self.read_public_key('root.crt.der'), certificate_packet)\n\nclass RegisterUDPWithCertificateChainExternal(RegisterWithCertificateChainExternalMixin,\n                                              CertificatesTest.PcapEnabledTestUDP):\n    pass\n\n@unittest.skipIf(pymbedtls.Context.supports_TLS_1_3(), \"TLS 1.3 encrypts its handshake messages\")\nclass RegisterTCPWithCertificateChainExternal(RegisterWithCertificateChainExternalMixin,\n                                              CertificatesTest.PcapEnabledTestTCP):\n    pass\n\nclass RegisterWithCertificateChainRebuiltMixin:\n    def setUp(self):\n        super().setUp(client_ca_file='root.crt', server_crt='server.crt',\n                      server_crt_file='server.crt.der', server_key='server.key',\n                      client_crt_file='client2.crt.der', client_key_file='client2.key.der',\n                      extra_cmdline_args=['--pkix-trust-store',\n                                          self._cert_file('client2_ca-and-root.crt'),\n                                          '--rebuild-client-cert-chain'])\n\n    def runTest(self):\n        import time\n\n        certificate_packet = None\n        deadline = time.time() + self.DEFAULT_MSG_TIMEOUT\n        while certificate_packet is None and time.time() <= deadline:\n            time.sleep(0.1)\n            certificate_packet = self.get_certificate_packet()\n\n        self.assertIsNotNone(certificate_packet)\n        self.assertIn(self.read_public_key('client2.crt.der'), certificate_packet)\n        self.assertIn(self.read_public_key('client2_ca.crt.der'), certificate_packet)\n        self.assertIn(self.read_public_key('root.crt.der'), certificate_packet)\n\nclass RegisterUDPWithCertificateChainRebuilt(RegisterWithCertificateChainRebuiltMixin,\n                                              CertificatesTest.PcapEnabledTestUDP):\n    pass\n\n@unittest.skipIf(pymbedtls.Context.supports_TLS_1_3(), \"TLS 1.3 encrypts its handshake messages\")\nclass RegisterTCPWithCertificateChainRebuilt(RegisterWithCertificateChainRebuiltMixin,\n                                              CertificatesTest.PcapEnabledTestTCP):\n    pass\n\n\nclass RegisterWithCyclicCertificateChainRebuiltMixin:\n    def setUp(self):\n        self._cleanup_list = test_suite.CleanupList()\n\n        try:\n            # Generate cyclic root certificates\n            from cryptography.hazmat.primitives import serialization\n            from suites.default import firmware_update\n            TestWithTlsServer = firmware_update.FirmwareUpdate.TestWithTlsServer\n\n            key1 = TestWithTlsServer._generate_key()\n            key2 = TestWithTlsServer._generate_key()\n\n            self.cert1 = TestWithTlsServer._generate_cert(private_key=key2,\n                                                          public_key=key1.public_key(),\n                                                          issuer_cn='Root2', cn='Root1', ca=True)\n            self.cert2 = TestWithTlsServer._generate_cert(private_key=key1,\n                                                          public_key=key2.public_key(),\n                                                          issuer_cn='Root1', cn='Root2', ca=True)\n\n            cert1_pem = self.cert1.public_bytes(encoding=serialization.Encoding.PEM)\n            cert2_pem = self.cert2.public_bytes(encoding=serialization.Encoding.PEM)\n\n            trust_store_file = tempfile.NamedTemporaryFile()\n            self._cleanup_list.append(trust_store_file.close)\n            trust_store_file.write(cert1_pem)\n            trust_store_file.write(cert2_pem)\n            trust_store_file.flush()\n\n            client_key = TestWithTlsServer._generate_key()\n            self.client_cert = TestWithTlsServer._generate_cert(private_key=key1,\n                                                                public_key=client_key.public_key(),\n                                                                issuer_cn='Root1', cn='127.0.0.1',\n                                                                alt_ip='127.0.0.1')\n\n            client_key_der = client_key.private_bytes(\n                encoding=serialization.Encoding.DER,\n                format=serialization.PrivateFormat.TraditionalOpenSSL,\n                encryption_algorithm=serialization.NoEncryption())\n            self.client_cert_der = self.client_cert.public_bytes(encoding=serialization.Encoding.DER)\n\n            client_key_file = tempfile.NamedTemporaryFile()\n            self._cleanup_list.append(client_key_file.close)\n            client_key_file.write(client_key_der)\n            client_key_file.flush()\n\n            client_cert_file = tempfile.NamedTemporaryFile()\n            self._cleanup_list.append(client_cert_file.close)\n            client_cert_file.write(self.client_cert_der)\n            client_cert_file.flush()\n\n            super().setUp(client_ca_file=trust_store_file.name,\n                          server_crt='self-signed/server.crt',\n                          server_crt_file='self-signed/server.crt.der',\n                          server_key='self-signed/server.key',\n                          client_crt_file=client_cert_file.name,\n                          client_key_file=client_key_file.name,\n                          extra_cmdline_args=['--pkix-trust-store', trust_store_file.name,\n                                              '--rebuild-client-cert-chain'],\n                          ciphersuites=[])\n        except:\n            self._cleanup_list()\n            raise\n\n    def runTest(self):\n        import time\n\n        certificate_packet = None\n        deadline = time.time() + self.DEFAULT_MSG_TIMEOUT\n        while certificate_packet is None and time.time() <= deadline:\n            time.sleep(0.1)\n            certificate_packet = self.get_certificate_packet()\n\n        self.assertIsNotNone(certificate_packet)\n        self.assertIn(self.read_public_key(self.client_cert), certificate_packet)\n        self.assertIn(self.read_public_key(self.cert1), certificate_packet)\n        self.assertIn(self.read_public_key(self.cert2), certificate_packet)\n\n    def tearDown(self):\n        try:\n            super().tearDown()\n        finally:\n            self._cleanup_list()\n\nclass RegisterUDPWithCyclicCertificateChainRebuilt(RegisterWithCyclicCertificateChainRebuiltMixin,\n                                                    CertificatesTest.PcapEnabledTestUDP):\n    pass\n\n@unittest.skipIf(pymbedtls.Context.supports_TLS_1_3(), \"TLS 1.3 encrypts its handshake messages\")\nclass RegisterTCPWithCyclicCertificateChainRebuilt(RegisterWithCyclicCertificateChainRebuiltMixin,\n                                                    CertificatesTest.PcapEnabledTestTCP):\n    pass\n\n\nclass RegisterWithCertificateChainExternalPersistenceMixin:\n    def setUp(self):\n        self._dm_persistence_file = tempfile.NamedTemporaryFile()\n        super().setUp(extra_cmdline_args=['--dm-persistence-file', self._dm_persistence_file.name])\n\n    def runTest(self):\n        super().runTest()\n\n        self.request_demo_shutdown()\n        self.assertDemoDeregisters()\n        self._terminate_demo()\n\n        self._start_demo(['--dm-persistence-file', self._dm_persistence_file.name]\n                         + self.make_demo_args(DEMO_ENDPOINT_NAME, [], '1.0', '1.0', None))\n        if self.serv.transport == Transport.TCP:\n            self.assertTcpCsm()\n            self.assertDemoRegisters(binding='T')\n        else:\n            self.assertDemoRegisters()\n\n    def tearDown(self):\n        try:\n            super().tearDown()\n        finally:\n            self._dm_persistence_file.close()\n\nclass RegisterUDPWithCertificateChainExternalPersistence(RegisterWithCertificateChainExternalPersistenceMixin,\n                                                         RegisterWithCertificateChainExternalMixin,\n                                                         CertificatesTest.PcapEnabledTestUDP):\n    pass\n\n@unittest.skipIf(pymbedtls.Context.supports_TLS_1_3(), \"TLS 1.3 encrypts its handshake messages\")\nclass RegisterTCPWithCertificateChainExternalPersistence(RegisterWithCertificateChainExternalPersistenceMixin,\n                                                         RegisterWithCertificateChainExternalMixin,\n                                                         CertificatesTest.PcapEnabledTestTCP):\n    pass\n\n\nclass RegisterWithSelfSignedCertificatesAndServerPublicKeyMixin:\n    def setUp(self):\n        super().setUp(server_crt='self-signed/server.crt', server_key='self-signed/server.key',\n                      client_crt_file='self-signed/client.crt.der',\n                      client_key_file='self-signed/client.key.der',\n                      server_crt_file='self-signed/server.crt.der')\n\nclass RegisterUDPWithSelfSignedCertificatesAndServerPublicKey(\n    RegisterWithSelfSignedCertificatesAndServerPublicKeyMixin, CertificatesTest.TestUDPMixin):\n    pass\n\nclass RegisterTCPWithSelfSignedCertificatesAndServerPublicKey(\n    RegisterWithSelfSignedCertificatesAndServerPublicKeyMixin, CertificatesTest.TestTCPMixin):\n    pass\n\n\nclass RegisterWithMismatchedServerPublicKeyMixin:\n    auto_deregister = False\n    def setUp(self):\n        super().setUp(server_crt='self-signed/server.crt', server_key='self-signed/server.key',\n                      client_crt_file='self-signed/client.crt.der',\n                      client_key_file='self-signed/client.key.der',\n                      # Use server.crt.der generated from different server.crt (not self-signed/server.crt)\n                      server_crt_file='server.crt.der', auto_register=False)\n\n    def runTest(self):\n        with self.assertRaises((RuntimeError, socket.timeout)):\n            self.assertDemoRegisters(self.serv)\n            self.auto_deregister = True\n        # -9984 == -0x2700 == MBEDTLS_ERR_X509_CERT_VERIFY_FAILED\n        self.read_log_until_match(b'handshake failed: -9984', 1)\n\n    def tearDown(self):\n        super().tearDown(auto_deregister=self.auto_deregister)\n\nclass RegisterUDPWithMismatchedServerPublicKey(\n    RegisterWithMismatchedServerPublicKeyMixin, CertificatesTest.TestUDPMixin):\n    pass\n\nclass RegisterTCPWithMismatchedServerPublicKey(\n    RegisterWithMismatchedServerPublicKeyMixin, CertificatesTest.TestTCPMixin):\n    pass\n\n\nclass RegisterWithCertificatesAndServerPublicKeyMixin:\n    def setUp(self):\n        super().setUp(server_crt='server.crt', server_key='server.key',\n                      client_crt_file=None, client_key_file=None, server_crt_file='server.crt.der')\n\n@unittest.skip(\"TODO: broken due to T1083\")\nclass RegisterUDPWithCertificatesAndServerPublicKey(\n    RegisterWithCertificatesAndServerPublicKeyMixin, CertificatesTest.TestUDPMixin):\n    pass\n\n@unittest.skip(\"TODO: broken due to T1083\")\nclass RegisterTCPWithCertificatesAndServerPublicKey(\n    RegisterWithCertificatesAndServerPublicKeyMixin, CertificatesTest.TestTCPMixin):\n    pass\n\nclass RegisterMixin:\n    extra_objects = []\n    def expected_content(self, version='1.0'):\n        result = []\n        for obj in sorted(ResPath.objects() + self.extra_objects, key=lambda field: field.oid):\n            if obj.oid == OID.Security:\n                # Security (/0) instances MUST not be a part of the list\n                # see LwM2M spec, Register/Update operations description\n                continue\n\n            if obj.oid == OID.Server:\n                result.append('</%d/1>' % (obj.oid,))\n            elif obj.oid == OID.SoftwareManagement:\n                result.append('</%d/0>' % (obj.oid,))\n                result.append('</%d/1>' % (obj.oid,))\n            elif obj.oid == OID.Lwm2mGateway:\n                entry = '</%d>' % (obj.oid,)\n                if obj.version is not None:\n                    if version == '1.0':\n                        entry += ';ver=\"%s\"' % (obj.version,)\n                    else:\n                        entry += ';ver=%s' % (obj.version,)\n                result.append(entry)\n                result.append('</%d/0>' % (obj.oid,))\n                result.append('</%d/1>' % (obj.oid,))\n            elif obj.is_multi_instance or obj.version is not None:\n                entry = '</%d>' % (obj.oid,)\n                if obj.version is not None:\n                    if version == '1.0':\n                        entry += ';ver=\"%s\"' % (obj.version,)\n                    else:\n                        entry += ';ver=%s' % (obj.version,)\n                result.append(entry)\n            if not obj.is_multi_instance:\n                result.append('</%d/0>' % (obj.oid,))\n\n        return ','.join(result).encode()\n\n\nclass BlockRegister:\n    class Test(RegisterMixin, unittest.TestCase):\n        def __call__(self, server, timeout_s=None, verify=True, version='1.0'):\n            register_content = b''\n            while True:\n                if timeout_s is None:\n                    pkt = server.recv()\n                else:\n                    pkt = server.recv(timeout_s=timeout_s)\n\n                block1 = pkt.get_options(coap.Option.BLOCK1)\n                self.assertIn(len(block1), {0, 1})\n                register_content += pkt.content\n                if len(block1) < 1 or not block1[0].has_more():\n                    break\n                server.send(Lwm2mContinue.matching(pkt)(options=block1))\n\n            if verify:\n                self.assertEqual(self.expected_content(version), register_content)\n\n            server.send(Lwm2mCreated.matching(pkt)(location='/rd/demo', options=block1))\n\n\n\nclass Register:\n    class TestCase(RegisterMixin, test_suite.Lwm2mDmOperations):\n        def setUp(self, *args, **kwargs):\n            self.extra_objects = []\n            # skip initial registration\n            super().setUp(auto_register=False, *args, **kwargs)\n\n\n\nclass RegisterUdp:\n    class TestCase(Register.TestCase, test_suite.Lwm2mSingleServerTest):\n        pass\n\n\nclass RegisterTcp:\n    class TestCase(Register.TestCase, test_suite.Lwm2mSingleTcpServerTest):\n        pass\n\n\nclass RegisterTcpWithAbort(\n        test_suite.Lwm2mSingleTcpServerTest, test_suite.Lwm2mDmOperations):\n    def setUp(self):\n        # skip initial registration\n        super().setUp(auto_register=False)\n\n    def runTest(self):\n        self.assertTcpCsm()\n        pkt = self.serv.recv()\n        self.assertMsgEqual(\n            Lwm2mRegister(\n                '/rd?lwm2m=1.0&ep=%s&lt=86400&b=T' %\n                (DEMO_ENDPOINT_NAME)), pkt)\n\n        abort_pkt = coap.Packet(code=coap.Code.SIGNALING_ABORT)\n        self.serv.send(abort_pkt)\n\n        pkt = self.serv.recv()\n        self.assertMsgEqual(abort_pkt, pkt)\n\n        self.serv.reset()\n        # No message should arrive\n        with self.assertRaises(socket.timeout):\n            self.serv.recv(timeout_s=5)\n\n    def tearDown(self):\n        super().tearDown(auto_deregister=False)\n\n\nclass RegisterTest(RegisterUdp.TestCase):\n    def runTest(self):\n        # should send Register request at start\n        pkt = self.serv.recv()\n        self.assertMsgEqual(\n            Lwm2mRegister('/rd?lwm2m=1.0&ep=%s&lt=86400' % (DEMO_ENDPOINT_NAME,),\n                          content=self.expected_content()),\n            pkt)\n\n        # should retry when no response is sent\n        pkt = self.serv.recv(timeout_s=6)\n\n        self.assertMsgEqual(\n            Lwm2mRegister('/rd?lwm2m=1.0&ep=%s&lt=86400' % (DEMO_ENDPOINT_NAME,),\n                          content=self.expected_content()),\n            pkt)\n\n        # should ignore this message as Message ID does not match\n        self.serv.send(Lwm2mCreated(msg_id=((pkt.msg_id + 1) % (1 << 16)),\n                                    token=pkt.token,\n                                    location='/rd/demo'))\n\n        # should retry\n        pkt = self.serv.recv(timeout_s=12)\n\n        self.assertMsgEqual(\n            Lwm2mRegister('/rd?lwm2m=1.0&ep=%s&lt=86400' % (DEMO_ENDPOINT_NAME,),\n                          content=self.expected_content()),\n            pkt)\n\n        # should not retry after receiving valid response\n        self.serv.send(Lwm2mCreated.matching(pkt)(location='/rd/demo'))\n\n        with self.assertRaises(socket.timeout, msg='unexpected message'):\n            print(self.serv.recv(timeout_s=6))\n            \n\nclass RegisterCheckOngoingRegistrations(RegisterUdp.TestCase):\n    def runTest(self):\n        self.assertTrue(self.ongoing_registration_exists())\n\n        pkt = self.serv.recv()\n        self.assertMsgEqual(\n            Lwm2mRegister('/rd?lwm2m=1.0&ep=%s&lt=86400' % (DEMO_ENDPOINT_NAME,),\n                          content=self.expected_content()), pkt)\n\n        self.assertTrue(self.ongoing_registration_exists())\n\n        self.serv.send(Lwm2mCreated.matching(pkt)(location='/rd/demo'))\n\n        self.assertFalse(self.ongoing_registration_exists())\n\n\nclass RegisterWithLostSeparateAck(RegisterUdp.TestCase):\n    def runTest(self):\n        # should send Register request at start\n        pkt = self.serv.recv()\n        self.assertMsgEqual(\n            Lwm2mRegister('/rd?lwm2m=1.0&ep=%s&lt=86400' % (DEMO_ENDPOINT_NAME,),\n                          content=self.expected_content()),\n            pkt)\n\n        # Separate Response: Confirmable; msg_id does not match, but token does\n        res = Lwm2mCreated(msg_id=((pkt.msg_id + 1) % (1 << 16)),\n                           token=pkt.token,\n                           location='/rd/demo')\n        res.type = coap.Type.CONFIRMABLE\n\n        # should respond with Empty ACK\n        self.serv.send(res)\n\n        self.assertMsgEqual(Lwm2mEmpty.matching(res)(),\n                            self.serv.recv())\n\n\nclass RegisterWithBlock(test_suite.Lwm2mSingleServerTest):\n    def setUp(self):\n        extra_args = '-I 64 -O 128'.split()\n        self.setup_demo_with_servers(servers=1,\n                                     extra_cmdline_args=extra_args,\n                                     auto_register=False)\n\n    def runTest(self):\n        BlockRegister().Test()(self.serv)\n        with self.assertRaises(socket.timeout, msg='unexpected message'):\n            print(self.serv.recv(timeout_s=6))\n\n\nclass ConcurrentRequestWhileWaitingForResponse:\n    class TestMixin:\n        def runTest(self):\n            path = '/rd?lwm2m=1.0&ep=%s&lt=86400' % DEMO_ENDPOINT_NAME\n            if self.serv.transport == Transport.TCP:\n                path += '&b=T'\n                self.assertTcpCsm()\n            pkt = self.serv.recv()\n            self.assertMsgEqual(Lwm2mRegister(path, content=self.expected_content()), pkt)\n            self.read_path(self.serv, ResPath.Device.Manufacturer)\n            self.serv.send(Lwm2mCreated.matching(pkt)(location='/rd/demo'))\n\n\nclass ConcurrentRequestWhileWaitingForResponseUdp(\n    ConcurrentRequestWhileWaitingForResponse.TestMixin, RegisterUdp.TestCase):\n    pass\n\n\nclass ConcurrentRequestWhileWaitingForResponseTcp(\n    ConcurrentRequestWhileWaitingForResponse.TestMixin, RegisterTcp.TestCase):\n    pass\n\n\nclass RegisterUri:\n    class TestMixin:\n        def make_demo_args(self, *args, **kwargs):\n            args = super().make_demo_args(*args, **kwargs)\n            for i in range(len(args)):\n                if args[i].startswith('coap'):\n                    args[i] += '/i/am/crazy/and?lwm2m=i&ep=know&lt=it'\n            return args\n\n        def runTest(self):\n            path = '/i/am/crazy/and/rd?lwm2m=i&ep=know&lt=it&lwm2m=1.0&ep=%s&lt=86400' % DEMO_ENDPOINT_NAME\n            if self.serv.transport == Transport.TCP:\n                path += '&b=T'\n                self.assertTcpCsm()\n            pkt = self.serv.recv()\n            self.assertMsgEqual(Lwm2mRegister(path, content=self.expected_content()), pkt)\n            self.serv.send(Lwm2mCreated.matching(pkt)(location='/some/weird/rd/point'))\n\n            # Update shall not contain the path and query from Server URI\n            self.communicate('send-update')\n            pkt = self.serv.recv()\n            self.assertMsgEqual(Lwm2mUpdate('/some/weird/rd/point', query=[], content=b''),\n                                pkt)\n            self.serv.send(Lwm2mChanged.matching(pkt)())\n\n        def tearDown(self):\n            self.teardown_demo_with_servers(path='/some/weird/rd/point')\n\n\nclass RegisterUriUdp(RegisterUri.TestMixin, RegisterUdp.TestCase):\n    pass\n\n\nclass RegisterUriTcp(RegisterUri.TestMixin, RegisterTcp.TestCase):\n    pass\n\n\nclass RegisterWithPskExternal(test_suite.Lwm2mDtlsSingleServerTest):\n    def setUp(self, extra_cmdline_args=[], **kwargs):\n        extra_cmdline_args += ['--use-external-security-info']\n        super().setUp(extra_cmdline_args=extra_cmdline_args, **kwargs)\n\n\nclass RegisterSni(test_suite.PcapEnabledTest,\n                  test_suite.Lwm2mDtlsSingleServerTest):\n    SNI = 'SomeServerHost'\n\n    def setUp(self):\n        extra_args = ['--sni', self.SNI]\n        super().setUp(extra_cmdline_args=extra_args, auto_register=False)\n\n    def runTest(self):\n        pkt = self.serv._raw_udp_socket.recv(4096)\n        self.assertPktIsDtlsClientHello(pkt, seq_number=0)\n        self.assertIn(bytes(self.SNI, 'ascii'), pkt)\n        self.assertDemoRegisters()\n\n\nclass Lwm2m11BindingSemantics(bootstrap_client.BootstrapTest.Test):\n    @property\n    def tcp_serv(self) -> Lwm2mServer:\n        return self.servers[1]\n\n    def setUp(self):\n        super().setUp(servers=[Lwm2mServer(), Lwm2mServer(coap.Server(transport=Transport.TCP))],\n                      maximum_version='1.1')\n\n    def runTest(self):\n        self.assertDemoRequestsBootstrap(\n            preferred_content_format=coap.ContentFormat.APPLICATION_LWM2M_SENML_CBOR)\n        self.bootstrap_server.send(Lwm2mDelete('/'))\n        self.assertIsInstance(self.bootstrap_server.recv(), Lwm2mDeleted)\n\n        self.write_instance(self.bootstrap_server, oid=OID.Security, iid=101,\n                            content=TLV.make_resource(RID.Security.ServerURI,\n                                                      'coap://127.0.0.1:%d' % self.serv.get_listen_port()).serialize()\n                                    + TLV.make_resource(RID.Security.Bootstrap, 0).serialize()\n                                    + TLV.make_resource(RID.Security.Mode,\n                                                        SecurityMode.NoSec.value).serialize()\n                                    + TLV.make_resource(RID.Security.ShortServerID,\n                                                        100).serialize())\n\n        self.write_instance(self.bootstrap_server, oid=OID.Security, iid=102,\n                            content=TLV.make_resource(RID.Security.ServerURI,\n                                                      'coap+tcp://127.0.0.1:%d' % self.tcp_serv.get_listen_port()).serialize()\n                                    + TLV.make_resource(RID.Security.Bootstrap, 0).serialize()\n                                    + TLV.make_resource(RID.Security.Mode,\n                                                        SecurityMode.NoSec.value).serialize()\n                                    + TLV.make_resource(RID.Security.ShortServerID,\n                                                        100).serialize())\n\n        self.write_instance(self.bootstrap_server, oid=OID.Server, iid=100,\n                            content=TLV.make_resource(RID.Server.Lifetime, 86400).serialize()\n                                    + TLV.make_resource(RID.Server.ShortServerID, 100).serialize()\n                                    + TLV.make_resource(RID.Server.NotificationStoring,\n                                                        True).serialize()\n                                    + TLV.make_resource(RID.Server.Binding, 'UT').serialize())\n\n        self.perform_bootstrap_finish()\n\n        # register with UDP binding\n        pkt = self.serv.recv()\n        self.assertIsInstance(pkt, Lwm2mRegister)\n        self.serv.send(Lwm2mCreated.matching(pkt)(location=self.DEFAULT_REGISTER_ENDPOINT))\n\n        # change binding and register with TCP binding\n        # receive CSM packet in background to avoid race condition\n        with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:\n            future = executor.submit(self.tcp_serv.recv, timeout_s=10)\n            self.write_resource(self.serv, OID.Server, 100, RID.Server.Binding, 'TU')\n            pkt = future.result()\n\n        assert pkt.code == coap.Code.SIGNALING_CSM\n        self.tcp_serv.send(coap.Packet(code=coap.Code.SIGNALING_CSM, token=b''))\n\n        pkt = self.tcp_serv.recv()\n        self.assertIsInstance(pkt, Lwm2mRegister)\n        self.tcp_serv.send(Lwm2mCreated.matching(pkt)(location=self.DEFAULT_REGISTER_ENDPOINT))\n\n        self.serv.reset()\n\n        # set Preferred Transport\n        self.write_resource(self.tcp_serv, OID.Server, 100, RID.Server.PreferredTransport, 'U')\n\n        # register with UDP binding\n        pkt = self.serv.recv()\n        self.assertIsInstance(pkt, Lwm2mRegister)\n        self.serv.send(Lwm2mCreated.matching(pkt)(location=self.DEFAULT_REGISTER_ENDPOINT))\n\n    def tearDown(self):\n        super().tearDown(deregister_servers=[self.serv])\n"
  },
  {
    "path": "tests/integration/suites/default/request_too_large.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nfrom framework.lwm2m_test import *\n\n\nclass RequestTooLarge(test_suite.Lwm2mSingleServerTest):\n    def setUp(self):\n        super().setUp(extra_cmdline_args=['-I', '1000'])\n\n    def runTest(self):\n        req = Lwm2mWrite(ResPath.FirmwareUpdate.Package, random_stuff(1200))\n        self.serv.send(req)\n        res = self.serv.recv()\n        self.assertMsgEqual(\n            Lwm2mErrorResponse.matching(req)(coap.Code.RES_REQUEST_ENTITY_TOO_LARGE), res)\n"
  },
  {
    "path": "tests/integration/suites/default/retransmissions.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nimport contextlib\nimport time\nimport socket\n\nfrom framework_tools.lwm2m.coap.server import SecurityMode\nfrom framework.lwm2m_test import *\nfrom suites.default import bootstrap_client\nfrom suites.default.block_write import equal_chunk_splitter, packets_from_chunks, Block\n\n\nclass RetransmissionTest:\n    class TestMixin:\n        # Note that these values differ from default ones in Anjay. This is done to\n        # speed up the test execution a bit by limiting the number of retransmissions\n        # as well as wait intervals between them.\n        ACK_RANDOM_FACTOR = 1.0\n        ACK_TIMEOUT = 2.0\n        MAX_RETRANSMIT = 2\n        CONFIRMABLE_NOTIFICATIONS = False\n\n        def tx_params(self):\n            return TxParams(ack_random_factor=self.ACK_RANDOM_FACTOR,\n                            ack_timeout=self.ACK_TIMEOUT,\n                            max_retransmit=self.MAX_RETRANSMIT)\n\n        def setUp(self, *args, **kwargs):\n            extra_cmdline_args = [\n                '--ack-random-factor', str(self.tx_params().ack_random_factor),\n                '--ack-timeout', str(self.tx_params().ack_timeout),\n                '--max-retransmit', str(self.tx_params().max_retransmit),\n                '--dtls-hs-retry-wait-min', str(\n                    self.tx_params().first_retransmission_timeout()),\n                '--dtls-hs-retry-wait-max', str(\n                    self.tx_params().last_retransmission_timeout()),\n            ]\n            if self.CONFIRMABLE_NOTIFICATIONS:\n                extra_cmdline_args += ['--confirmable-notifications']\n            if 'extra_cmdline_args' in kwargs:\n                kwargs['extra_cmdline_args'] = extra_cmdline_args + \\\n                    kwargs['extra_cmdline_args']\n                super().setUp(*args, **kwargs)\n            else:\n                super().setUp(*args, **kwargs, extra_cmdline_args=extra_cmdline_args)\n\n        def last_retransmission_timeout(self):\n            return self.tx_params().last_retransmission_timeout()\n\n        def wait_for_retransmission_response_timeout(self, margin_s=1.0):\n            time.sleep(self.last_retransmission_timeout() + margin_s)\n\n        def max_transmit_wait(self):\n            return self.tx_params().max_transmit_wait()\n\n\nclass DtlsHsFailOnIcmpTest(test_suite.PcapEnabledTest,\n                           RetransmissionTest.TestMixin,\n                           test_suite.Lwm2mDtlsSingleServerTest):\n    def setup_demo_with_servers(self, **kwargs):\n        for server in kwargs['servers']:\n            self._server_close_stack.enter_context(server.fake_close())\n        super().setup_demo_with_servers(**kwargs)\n\n    def setUp(self):\n        self._server_close_stack = contextlib.ExitStack()\n        super().setUp(auto_register=False)\n\n    def tearDown(self):\n        self._server_close_stack.close()  # in case runTest() failed\n        super().tearDown()\n\n    def runTest(self):\n        # wait until ultimate failure\n        self.wait_until_icmp_unreachable_count(1, timeout_s=10)\n\n        # Ensure that the control is given back to the user.\n        self.assertTrue(self.get_all_connections_failed())\n\n        # attempt reconnection\n        self._server_close_stack.close()  # unclose the server socket\n        self.communicate('reconnect')\n        self.assertDemoRegisters(self.serv, timeout_s=10)\n        self.assertEqual(1, self.count_icmp_unreachable_packets())\n\n\nclass DtlsHsRetryOnTimeoutTest(RetransmissionTest.TestMixin,\n                               test_suite.Lwm2mDtlsSingleServerTest):\n    def setUp(self):\n        super().setUp(auto_register=False)\n\n    def tearDown(self):\n        super().teardown_demo_with_servers(auto_deregister=False)\n\n    def runTest(self):\n        for i in range(self.MAX_RETRANSMIT + 1):\n            self.assertPktIsDtlsClientHello(\n                self.serv._raw_udp_socket.recv(4096), seq_number=i)\n\n        self.wait_for_retransmission_response_timeout()\n        # Ensure that the control is given back to the user.\n        self.assertTrue(self.get_all_connections_failed())\n\n\nclass DtlsRegisterTimeoutFallbacksToHsTest(RetransmissionTest.TestMixin,\n                                           test_suite.Lwm2mDtlsSingleServerTest):\n    # These settings speed up tests considerably.\n    MAX_RETRANSMIT = 1\n    ACK_TIMEOUT = 1\n\n    def tearDown(self):\n        super().teardown_demo_with_servers(auto_deregister=False)\n\n    def runTest(self):\n        # force re-registration;\n        # Register only falls back to handshake if it's not performed immediately after one\n        self.communicate('send-update')\n        pkt = self.assertDemoUpdatesRegistration(respond=False)\n        self.serv.send(Lwm2mErrorResponse.matching(pkt)\n                       (code=coap.Code.RES_NOT_FOUND))\n\n        # Ignore register requests.\n        for _ in range(self.MAX_RETRANSMIT + 1):\n            self.assertDemoRegisters(\n                respond=False, timeout_s=self.last_retransmission_timeout())\n\n        self.wait_for_retransmission_response_timeout()\n\n        # Demo should fall back to DTLS handshake.\n        self.assertPktIsDtlsClientHello(\n            self.serv._raw_udp_socket.recv(4096), seq_number=0)\n\n        self.wait_for_retransmission_response_timeout()\n        # Ensure that the control is given back to the user.\n        self.assertTrue(self.get_all_connections_failed())\n\n\nclass RegisterTimeout:\n    class TestMixin(RetransmissionTest.TestMixin):\n        # These settings speed up tests considerably.\n        MAX_RETRANSMIT = 1\n        ACK_TIMEOUT = 1\n\n        def setUp(self):\n            super().setUp(auto_register=False)\n\n        def tearDown(self):\n            super().teardown_demo_with_servers(auto_deregister=False)\n\n        def runTest(self):\n            # Ignore register requests.\n            for _ in range(self.MAX_RETRANSMIT + 1):\n                self.assertDemoRegisters(respond=False,\n                                         timeout_s=self.last_retransmission_timeout() + 5)\n\n            self.wait_for_retransmission_response_timeout()\n\n            # Ensure that server is considered unreachable, and control given back to the user.\n            self.assertEqual(0, self.get_socket_count())\n            self.assertTrue(self.get_all_connections_failed())\n\n\nclass DtlsRegisterFirstTimeoutFailsTest(RegisterTimeout.TestMixin,\n                                        test_suite.Lwm2mDtlsSingleServerTest):\n    pass\n\n\nclass RegisterTimeoutFails(RegisterTimeout.TestMixin,\n                           test_suite.Lwm2mSingleServerTest):\n    pass\n\n\nclass DtlsRegisterFailsOnIcmpTest(test_suite.PcapEnabledTest,\n                                  RetransmissionTest.TestMixin,\n                                  test_suite.Lwm2mDtlsSingleServerTest):\n    def setUp(self):\n        super().setUp(auto_register=False)\n\n    def tearDown(self):\n        super().teardown_demo_with_servers(auto_deregister=False)\n\n    def runTest(self):\n        self.assertDemoRegisters(respond=False)\n        # Give dumpcap a little bit of time to write to dump file.\n        time.sleep(self.ACK_TIMEOUT / 2)\n        num_initial_dtls_hs_packets = self.count_dtls_client_hello_packets()\n        # Close socket to induce ICMP port unreachable.\n        self.serv.close()\n\n        # Wait for ICMP port unreachable.\n        self.wait_until_icmp_unreachable_count(\n            1, timeout_s=self.last_retransmission_timeout())\n\n        self.wait_for_retransmission_response_timeout()\n        # Ensure that no more retransmissions occurred.\n        self.assertEqual(1, self.count_icmp_unreachable_packets())\n        # Ensure that no more dtls handshake messages occurred.\n        self.assertEqual(num_initial_dtls_hs_packets,\n                         self.count_dtls_client_hello_packets())\n\n        # Ensure that the control is given back to the user.\n        self.assertTrue(self.get_all_connections_failed())\n\n\nclass RegisterIcmpTest(test_suite.PcapEnabledTest,\n                       RetransmissionTest.TestMixin,\n                       test_suite.Lwm2mSingleServerTest):\n    MAX_RETRANSMIT = 1\n    ACK_TIMEOUT = 4\n\n    def setUp(self):\n        super().setUp(auto_register=False)\n\n    def tearDown(self):\n        super().teardown_demo_with_servers(auto_deregister=False)\n\n    def runTest(self):\n        self.assertDemoRegisters()\n        # Close socket to induce ICMP port unreachable.\n        with self.serv.fake_close():\n            # Force Register\n            self.communicate('reconnect')\n            # Wait for ICMP port unreachable.\n            self.wait_until_icmp_unreachable_count(\n                1, timeout_s=self.last_retransmission_timeout())\n\n        # Ensure that the control is given back to the user.\n        with self.assertRaises(socket.timeout, msg=\"unexpected packets from the client\"):\n            self.serv.recv()\n\n        self.assertEqual(0, self.get_socket_count())\n        self.assertTrue(self.get_all_connections_failed())\n\n\nclass DeregisterTimeout:\n    class TestMixin(RetransmissionTest.TestMixin):\n        def tearDown(self):\n            super().teardown_demo_with_servers(auto_deregister=False)\n\n        def runTest(self):\n            self.communicate('trim-servers 0')\n            for _ in range(self.MAX_RETRANSMIT + 1):\n                self.assertMsgEqual(Lwm2mDeregister(self.DEFAULT_REGISTER_ENDPOINT),\n                                    self.serv.recv(timeout_s=self.last_retransmission_timeout()))\n\n            self.wait_for_retransmission_response_timeout()\n            self.assertEqual(0, self.get_socket_count())\n\n\nclass DtlsDeregisterTimeoutTest(DeregisterTimeout.TestMixin,\n                                test_suite.Lwm2mDtlsSingleServerTest):\n    pass\n\n\nclass DeregisterTimeoutTest(DeregisterTimeout.TestMixin,\n                            test_suite.Lwm2mSingleServerTest):\n    pass\n\n\nclass DeregisterIcmp:\n    class TestMixin(test_suite.PcapEnabledTest,\n                    RetransmissionTest.TestMixin):\n        def tearDown(self):\n            super().teardown_demo_with_servers(auto_deregister=False)\n\n        def runTest(self):\n            # Close socket to induce ICMP port unreachables.\n            self.serv.close()\n            self.communicate('trim-servers 0')\n            self.wait_until_icmp_unreachable_count(\n                1, timeout_s=2 * self.last_retransmission_timeout())\n            # Give demo time to realize deregister failed.\n            time.sleep(self.ACK_TIMEOUT * self.ACK_RANDOM_FACTOR)\n            # Ensure that no more retransmissions occurred.\n            self.assertEqual(1, self.count_icmp_unreachable_packets())\n            self.assertEqual(0, self.get_socket_count())\n\n\nclass DtlsDeregisterIcmpTest(DeregisterIcmp.TestMixin,\n                             test_suite.Lwm2mDtlsSingleServerTest):\n    pass\n\n\nclass DeregisterIcmpTest(DeregisterIcmp.TestMixin,\n                         test_suite.Lwm2mSingleServerTest):\n    pass\n\n\nclass DtlsUpdateTimeoutFallbacksToRegisterTest(RetransmissionTest.TestMixin,\n                                               test_suite.Lwm2mDtlsSingleServerTest,\n                                               test_suite.Lwm2mDmOperations):\n    def tearDown(self):\n        super().teardown_demo_with_servers(auto_deregister=False)\n\n    def runTest(self):\n        new_lifetime = int(2 * self.max_transmit_wait())\n        # Change lifetime to 2*MAX_TRANSMIT_WAIT\n        self.write_resource(self.serv, oid=OID.Server, iid=1,\n                            rid=RID.Server.Lifetime, content=str(new_lifetime))\n        self.assertDemoUpdatesRegistration(lifetime=new_lifetime)\n        # Demo should attempt to update registration.\n        for _ in range(self.MAX_RETRANSMIT + 1):\n            self.assertDemoUpdatesRegistration(\n                respond=False, timeout_s=new_lifetime)\n        self.wait_for_retransmission_response_timeout()\n\n        # Demo should re-register\n        for _ in range(self.MAX_RETRANSMIT + 1):\n            self.assertDemoRegisters(\n                respond=False, lifetime=new_lifetime, timeout_s=self.max_transmit_wait())\n        self.wait_for_retransmission_response_timeout()\n\n        self.serv._raw_udp_socket.settimeout(\n            self.last_retransmission_timeout() + 1)\n        # Demo should attempt handshake\n        for i in range(self.MAX_RETRANSMIT + 1):\n            self.assertPktIsDtlsClientHello(\n                self.serv._raw_udp_socket.recv(4096))\n        self.wait_for_retransmission_response_timeout()\n\n        with self.assertRaises(socket.timeout, msg=\"unexpected packets from the client\"):\n            self.serv._raw_udp_socket.recv(4096)\n\n        self.assertEqual(0, self.get_socket_count())\n\n\nclass UpdateTimeoutFallbacksToRegisterTest(RetransmissionTest.TestMixin,\n                                           test_suite.Lwm2mSingleServerTest,\n                                           test_suite.Lwm2mDmOperations):\n    def runTest(self):\n        new_lifetime = int(2 * self.max_transmit_wait())\n        # Change lifetime to 2*MAX_TRANSMIT_WAIT\n        self.write_resource(self.serv, oid=OID.Server, iid=1,\n                            rid=RID.Server.Lifetime, content=str(new_lifetime))\n        self.assertDemoUpdatesRegistration(lifetime=new_lifetime)\n        # Demo should attempt to update registration.\n        for _ in range(self.MAX_RETRANSMIT + 1):\n            self.assertDemoUpdatesRegistration(\n                respond=False, timeout_s=new_lifetime)\n        self.wait_for_retransmission_response_timeout()\n\n        # Demo should re-register\n        self.assertDemoRegisters(lifetime=new_lifetime)\n\n\nclass UpdateTimeoutWithQueueModeTest(RetransmissionTest.TestMixin,\n                                     test_suite.Lwm2mSingleServerTest,\n                                     test_suite.Lwm2mDmOperations):\n    def setUp(self):\n        super().setUp(binding='UQ', extra_cmdline_args=['--binding=UQ'])\n\n    def runTest(self):\n        self.communicate('send-update')\n        for _ in range(self.MAX_RETRANSMIT + 1):\n            self.assertDemoUpdatesRegistration(\n                respond=False, timeout_s=2 * self.max_transmit_wait())\n        self.wait_for_retransmission_response_timeout()\n\n        # Demo should re-register\n        self.assertDemoRegisters(binding='UQ')\n\n\nclass UpdateFailsOnIcmpTest:\n    class TestMixin(test_suite.PcapEnabledTest,\n                    RetransmissionTest.TestMixin,\n                    test_suite.Lwm2mDmOperations):\n        def tearDown(self):\n            super().teardown_demo_with_servers(auto_deregister=False)\n\n        def runTest(self):\n            new_lifetime = int(2 * self.max_transmit_wait())\n            # Change lifetime to 2*MAX_TRANSMIT_WAIT\n            self.write_resource(self.serv, oid=OID.Server, iid=1, rid=RID.Server.Lifetime,\n                                content=str(new_lifetime))\n            self.assertDemoUpdatesRegistration(lifetime=new_lifetime)\n            # Give dumpcap a little bit of time to write to dump file.\n            time.sleep(self.ACK_TIMEOUT)\n            num_initial_dtls_hs_packets = self.count_dtls_client_hello_packets()\n\n            self.serv.close()\n\n            # Wait for ICMP port unreachable.\n            self.wait_until_icmp_unreachable_count(1, timeout_s=new_lifetime)\n\n            self.wait_for_retransmission_response_timeout()\n            # Ensure that no more retransmissions occurred.\n            self.assertEqual(1, self.count_icmp_unreachable_packets())\n            # Ensure that no more dtls handshake messages occurred.\n            self.assertEqual(num_initial_dtls_hs_packets,\n                             self.count_dtls_client_hello_packets())\n\n            # Ensure that the control is given back to the user.\n            self.assertTrue(self.get_all_connections_failed())\n\n\nclass DtlsUpdateIcmpTest(UpdateFailsOnIcmpTest.TestMixin,\n                         test_suite.Lwm2mDtlsSingleServerTest):\n    pass\n\n\nclass UpdateIcmpTest(UpdateFailsOnIcmpTest.TestMixin,\n                     test_suite.Lwm2mSingleServerTest):\n    pass\n\n\nclass DtlsRequestBootstrapTimeoutFallbacksToHsTest(RetransmissionTest.TestMixin,\n                                                   test_suite.Lwm2mDtlsSingleServerTest):\n    # These settings speed up tests considerably.\n    MAX_RETRANSMIT = 1\n    ACK_TIMEOUT = 1\n\n    def setUp(self):\n        super().setUp(bootstrap_server=Lwm2mServer(coap.DtlsServer(psk_identity=self.PSK_IDENTITY, psk_key=self.PSK_KEY)),\n                      auto_register=False)\n        self.serv.listen()\n        self.bootstrap_server.listen()\n        self.assertDemoRegisters(self.serv)\n\n    def tearDown(self):\n        super().teardown_demo_with_servers(auto_deregister=False)\n\n    def runTest(self):\n        # force Client-Initiated Bootstrap;\n        # Request Bootstrap only falls back to handshake if it's not performed immediately after one\n        self.communicate('send-update')\n        pkt = self.assertDemoUpdatesRegistration(respond=False)\n        self.serv.send(Lwm2mErrorResponse.matching(pkt)\n                       (code=coap.Code.RES_FORBIDDEN))\n        pkt = self.assertDemoRegisters(respond=False)\n        self.serv.send(Lwm2mErrorResponse.matching(pkt)\n                       (code=coap.Code.RES_FORBIDDEN))\n\n        # Ignore Request Bootstrap requests.\n        for _ in range(self.MAX_RETRANSMIT + 1):\n            pkt = self.bootstrap_server.recv(\n                timeout_s=self.last_retransmission_timeout())\n            self.assertIsInstance(pkt, Lwm2mRequestBootstrap)\n\n        self.wait_for_retransmission_response_timeout()\n\n        # Demo should fall back to DTLS handshake.\n        self.assertPktIsDtlsClientHello(\n            self.bootstrap_server._raw_udp_socket.recv(4096), seq_number=0)\n\n        self.wait_for_retransmission_response_timeout()\n        # Ensure that the control is given back to the user.\n        self.assertTrue(self.get_all_connections_failed())\n\n\nclass RequestBootstrapTimeoutFails(RetransmissionTest.TestMixin,\n                                   test_suite.Lwm2mTest):\n    # These settings speed up tests considerably.\n    MAX_RETRANSMIT = 1\n    ACK_TIMEOUT = 1\n\n    def setUp(self, bootstrap_server=True, *args, **kwargs):\n        super().setUp(servers=0, bootstrap_server=bootstrap_server, *args, **kwargs)\n\n    def runTest(self):\n        # Ignore Request Bootstrap requests.\n        for _ in range(self.MAX_RETRANSMIT + 1):\n            pkt = self.bootstrap_server.recv(\n                timeout_s=self.last_retransmission_timeout() + 5)\n            self.assertIsInstance(pkt, Lwm2mRequestBootstrap)\n\n        self.wait_for_retransmission_response_timeout()\n\n        # Ensure that server is considered unreachable, and control given back to the user.\n        self.assertEqual(0, self.get_socket_count())\n        self.assertTrue(self.get_all_connections_failed())\n\n\nclass DtlsRequestBootstrapFirstTimeoutFailsTest(RequestBootstrapTimeoutFails):\n    PSK_IDENTITY = b'test-identity'\n    PSK_KEY = b'test-key'\n\n    def setUp(self):\n        super().setUp(bootstrap_server=Lwm2mServer(coap.DtlsServer(psk_identity=self.PSK_IDENTITY, psk_key=self.PSK_KEY)),\n                      extra_cmdline_args=['--identity', str(binascii.hexlify(self.PSK_IDENTITY), 'ascii'),\n                                          '--key', str(binascii.hexlify(self.PSK_KEY), 'ascii')])\n\n\nclass DtlsRequestBootstrapFailsOnIcmpTest(test_suite.PcapEnabledTest,\n                                          RetransmissionTest.TestMixin,\n                                          test_suite.Lwm2mDtlsSingleServerTest):\n    PSK_IDENTITY = b'test-identity'\n    PSK_KEY = b'test-key'\n\n    def setUp(self):\n        super().setUp(bootstrap_server=Lwm2mServer(coap.DtlsServer(psk_identity=self.PSK_IDENTITY, psk_key=self.PSK_KEY)),\n                      auto_register=False)\n\n    def tearDown(self):\n        super().teardown_demo_with_servers(auto_deregister=False)\n\n    def runTest(self):\n        self.serv.listen()\n        self.bootstrap_server.listen()\n        pkt = self.assertDemoRegisters(respond=False)\n        # Give dumpcap a little bit of time to write to dump file.\n        time.sleep(self.ACK_TIMEOUT / 2)\n        num_initial_dtls_hs_packets = self.count_dtls_client_hello_packets()\n        # Close the socket to induce ICMP port unreachable\n        self.bootstrap_server.close()\n\n        # respond with Forbidden to Register so that client falls back to Bootstrap\n        self.serv.send(Lwm2mErrorResponse.matching(pkt)\n                       (code=coap.Code.RES_FORBIDDEN))\n\n        # Wait for ICMP port unreachable.\n        self.wait_until_icmp_unreachable_count(\n            1, timeout_s=self.last_retransmission_timeout())\n\n        self.wait_for_retransmission_response_timeout()\n        # Ensure that no more retransmissions occurred.\n        self.assertEqual(1, self.count_icmp_unreachable_packets())\n        # Ensure that no more dtls handshake messages occurred.\n        self.assertEqual(num_initial_dtls_hs_packets,\n                         self.count_dtls_client_hello_packets())\n\n        # Ensure that the control is given back to the user.\n        self.assertTrue(self.get_all_connections_failed())\n\n\nclass RequestBootstrapIcmpTest(test_suite.PcapEnabledTest,\n                               RetransmissionTest.TestMixin,\n                               test_suite.Lwm2mTest):\n    MAX_RETRANSMIT = 1\n    ACK_TIMEOUT = 4\n\n    def setUp(self):\n        super().setUp(servers=0, bootstrap_server=True)\n\n    def tearDown(self):\n        super().teardown_demo_with_servers(auto_deregister=False)\n\n    def runTest(self):\n        pkt = self.bootstrap_server.recv()\n        self.assertIsInstance(pkt, Lwm2mRequestBootstrap)\n        self.bootstrap_server.send(Lwm2mChanged.matching(pkt)())\n        # Close socket to induce ICMP port unreachable.\n        with self.bootstrap_server.fake_close():\n            # Force Register\n            self.communicate('reconnect')\n            # Wait for ICMP port unreachable.\n            self.wait_until_icmp_unreachable_count(\n                1, timeout_s=self.last_retransmission_timeout())\n\n        # Ensure that the control is given back to the user.\n        with self.assertRaises(socket.timeout, msg=\"unexpected packets from the client\"):\n            self.bootstrap_server.recv()\n\n        self.assertEqual(0, self.get_socket_count())\n        self.assertTrue(self.get_all_connections_failed())\n\n\n# Tests below check that Anjay does not go crazy when faced with network connection problems while attempting to send\n# Notify messages. Some previous versions could easily get into an infinite loop of repeating the Notify message without\n# any backoff, and so on - so we test that the behaviour is sane.\n\n\nclass NotificationIcmpTest(test_suite.PcapEnabledTest,\n                           RetransmissionTest.TestMixin,\n                           test_suite.Lwm2mSingleServerTest,\n                           test_suite.Lwm2mDmOperations):\n    CONFIRMABLE_NOTIFICATIONS = True\n\n    def tearDown(self):\n        super().teardown_demo_with_servers(auto_deregister=False)\n\n    def runTest(self):\n        self.create_instance(self.serv, oid=OID.Test, iid=1)\n        self.observe(self.serv, oid=OID.Test, iid=1, rid=RID.Test.Timestamp)\n\n        with self.serv.fake_close():\n            self.wait_until_icmp_unreachable_count(1)\n\n        # client should give up on retransmitting the notification after ICMP error\n        with self.assertRaises(socket.timeout):\n            self.serv.recv(timeout_s=self.last_retransmission_timeout() + 3)\n        self.assertEqual(1, self.count_icmp_unreachable_packets())\n\n        # the client should not attempt to reach the server again\n        self.assertTrue(self.get_all_connections_failed())\n        self.assertEqual(0, self.get_socket_count())\n\n\nclass NotificationDtlsFailsOnIcmpTest(test_suite.PcapEnabledTest,\n                                      RetransmissionTest.TestMixin,\n                                      test_suite.Lwm2mDtlsSingleServerTest,\n                                      test_suite.Lwm2mDmOperations):\n    CONFIRMABLE_NOTIFICATIONS = True\n\n    def tearDown(self):\n        super().teardown_demo_with_servers(auto_deregister=False)\n\n    def runTest(self):\n        self.create_instance(self.serv, oid=OID.Test, iid=1)\n\n        # force an Update so that change to the data model does not get notified later\n        self.communicate('send-update')\n        self.assertDemoUpdatesRegistration(content=ANY)\n        # Give dumpcap a little bit of time to write to dump file.\n        time.sleep(self.ACK_TIMEOUT / 2)\n        num_initial_dtls_hs_packets = self.count_dtls_client_hello_packets()\n\n        self.observe(self.serv, oid=OID.Test, iid=1, rid=RID.Test.Timestamp)\n\n        self.serv.close()\n        self.wait_until_icmp_unreachable_count(1, timeout_s=8)\n\n        self.wait_for_retransmission_response_timeout()\n        # Ensure that no more retransmissions occurred.\n        self.assertEqual(1, self.count_icmp_unreachable_packets())\n        # Ensure that no more dtls handshake messages occurred.\n        self.assertEqual(num_initial_dtls_hs_packets,\n                         self.count_dtls_client_hello_packets())\n\n        # Ensure that the control is given back to the user.\n        self.assertTrue(self.get_all_connections_failed())\n\n\nclass NotificationTimeoutIsIgnored:\n    class TestMixin(RetransmissionTest.TestMixin,\n                    test_suite.Lwm2mDmOperations):\n        CONFIRMABLE_NOTIFICATIONS = True\n\n        def runTest(self):\n            self.skipIfFeatureStatus('WITH_AVS_COAP_OBSERVE_CANCEL_ON_TIMEOUT = ON',\n                                     'Timeout cancels observation in that configuration')\n\n            # Trigger a CON notification\n            self.create_instance(self.serv, oid=OID.Test, iid=1)\n\n            # force an Update so that change to the data model does not get notified later\n            self.communicate('send-update')\n            self.assertDemoUpdatesRegistration(content=ANY)\n\n            self.observe(self.serv, oid=OID.Test, iid=1, rid=RID.Test.Counter)\n            self.execute_resource(self.serv, oid=OID.Test,\n                                  iid=1, rid=RID.Test.IncrementCounter)\n\n            first_pkt = self.serv.recv(timeout_s=2)\n            first_attempt = time.time()\n            self.assertIsInstance(first_pkt, Lwm2mNotify)\n\n            for attempt in range(self.MAX_RETRANSMIT):\n                self.assertIsInstance(\n                    self.serv.recv(timeout_s=30), Lwm2mNotify)\n            last_attempt = time.time()\n\n            transmit_span_lower_bound = self.ACK_TIMEOUT * \\\n                ((2 ** self.MAX_RETRANSMIT) - 1)\n            transmit_span_upper_bound = transmit_span_lower_bound * self.ACK_RANDOM_FACTOR\n\n            self.assertGreater(last_attempt - first_attempt,\n                               transmit_span_lower_bound - 1)\n            self.assertLess(last_attempt - first_attempt,\n                            transmit_span_upper_bound + 1)\n\n            time.sleep(self.last_retransmission_timeout() + 1)\n\n            # check that following notifications still trigger attempts to send the value\n            self.execute_resource(self.serv, oid=OID.Test,\n                                  iid=1, rid=RID.Test.IncrementCounter)\n            pkt = self.serv.recv(\n                timeout_s=self.last_retransmission_timeout() + 2)\n            self.assertIsInstance(pkt, Lwm2mNotify)\n            self.assertEqual(pkt.content, first_pkt.content)\n            self.serv.send(Lwm2mReset.matching(pkt)())\n\n\nclass NotificationTimeoutCancelsObservation:\n    class TestMixin(RetransmissionTest.TestMixin,\n                    test_suite.Lwm2mDmOperations):\n        CONFIRMABLE_NOTIFICATIONS = True\n\n        def runTest(self):\n            self.skipIfFeatureStatus('WITH_AVS_COAP_OBSERVE_CANCEL_ON_TIMEOUT = OFF',\n                                     'Timeout does not cancel observation in that configuration')\n\n            # Trigger a CON notification\n            self.create_instance(self.serv, oid=OID.Test, iid=1)\n\n            # force an Update so that change to the data model does not get notified later\n            self.communicate('send-update')\n            self.assertDemoUpdatesRegistration(content=ANY)\n\n            self.observe(self.serv, oid=OID.Test, iid=1, rid=RID.Test.Counter)\n            self.execute_resource(self.serv, oid=OID.Test,\n                                  iid=1, rid=RID.Test.IncrementCounter)\n\n            first_pkt = self.serv.recv(timeout_s=2)\n            first_attempt = time.time()\n            self.assertIsInstance(first_pkt, Lwm2mNotify)\n\n            for attempt in range(self.MAX_RETRANSMIT):\n                self.assertIsInstance(\n                    self.serv.recv(timeout_s=30), Lwm2mNotify)\n            last_attempt = time.time()\n\n            transmit_span_lower_bound = self.ACK_TIMEOUT * \\\n                ((2 ** self.MAX_RETRANSMIT) - 1)\n            transmit_span_upper_bound = transmit_span_lower_bound * self.ACK_RANDOM_FACTOR\n\n            self.assertGreater(last_attempt - first_attempt,\n                               transmit_span_lower_bound - 1)\n            self.assertLess(last_attempt - first_attempt,\n                            transmit_span_upper_bound + 1)\n\n            time.sleep(self.last_retransmission_timeout() + 1)\n\n            # check that the observation is really cancelled\n            self.execute_resource(self.serv, oid=OID.Test,\n                                  iid=1, rid=RID.Test.IncrementCounter)\n            with self.assertRaises(socket.timeout):\n                print(self.serv.recv(\n                    timeout_s=self.last_retransmission_timeout() + 5))\n\n\nclass NotificationDtlsTimeoutIsIgnoredTest(NotificationTimeoutIsIgnored.TestMixin,\n                                           test_suite.Lwm2mDtlsSingleServerTest):\n    pass\n\n\nclass NotificationTimeoutIsIgnoredTest(NotificationTimeoutIsIgnored.TestMixin,\n                                       test_suite.Lwm2mSingleServerTest):\n    pass\n\n\nclass NotificationDtlsTimeoutCancelsObservationTest(NotificationTimeoutCancelsObservation.TestMixin,\n                                                    test_suite.Lwm2mDtlsSingleServerTest):\n    pass\n\n\nclass NotificationTimeoutCancelsObservationTest(NotificationTimeoutCancelsObservation.TestMixin,\n                                                test_suite.Lwm2mSingleServerTest):\n    pass\n\n\nclass ReplacedBootstrapServerReconnectTest(RetransmissionTest.TestMixin,\n                                           test_suite.Lwm2mDtlsSingleServerTest,\n                                           test_suite.Lwm2mDmOperations):\n    MAX_RETRANSMIT = 1\n    ACK_TIMEOUT = 1\n\n    def setUp(self):\n        super().setUp(bootstrap_server=Lwm2mServer(coap.DtlsServer(psk_identity=self.PSK_IDENTITY, psk_key=self.PSK_KEY)),\n                      num_servers_passed=0)\n\n    def tearDown(self):\n        super().tearDown(auto_deregister=False)\n\n    def runTest(self):\n        lifetime = 2 * self.max_transmit_wait()\n\n        pkt = self.bootstrap_server.recv()\n        self.assertIsInstance(pkt, Lwm2mRequestBootstrap)\n        self.bootstrap_server.send(Lwm2mChanged.matching(pkt)())\n\n        pkt = Lwm2mDiscover('/%d' % (OID.Security,))\n        self.bootstrap_server.send(pkt)\n        self.assertMsgEqual(Lwm2mContent.matching(pkt)(content=b'lwm2m=\"1.0\",</%d>,</%d/1>' % (OID.Security, OID.Security)),\n                            self.bootstrap_server.recv())\n\n        # replace the existing instance\n        self.write_instance(self.bootstrap_server, oid=OID.Security, iid=1,\n                            content=TLV.make_resource(\n                                RID.Security.ServerURI, 'coaps://127.0.0.1:%d' % self.bootstrap_server.get_listen_port()).serialize()\n                            + TLV.make_resource(RID.Security.Bootstrap, 1).serialize()\n                            + TLV.make_resource(RID.Security.Mode, coap.server.SecurityMode.PreSharedKey.value).serialize()\n                            + TLV.make_resource(RID.Security.PKOrIdentity, self.PSK_IDENTITY).serialize()\n                            + TLV.make_resource(RID.Security.SecretKey, self.PSK_KEY).serialize())\n\n        # provision the regular Server instance\n        self.write_instance(self.bootstrap_server, oid=OID.Security, iid=2,\n                            content=TLV.make_resource(\n                                RID.Security.ServerURI, 'coaps://127.0.0.1:%d' % self.serv.get_listen_port()).serialize()\n                            + TLV.make_resource(RID.Security.Bootstrap, 0).serialize()\n                            + TLV.make_resource(RID.Security.Mode, coap.server.SecurityMode.PreSharedKey.value).serialize()\n                            + TLV.make_resource(RID.Security.ShortServerID, 2).serialize()\n                            + TLV.make_resource(RID.Security.PKOrIdentity, self.PSK_IDENTITY).serialize()\n                            + TLV.make_resource(RID.Security.SecretKey, self.PSK_KEY).serialize())\n        self.write_instance(self.bootstrap_server, oid=OID.Server, iid=2,\n                            content=TLV.make_resource(\n                                RID.Server.Lifetime, int(lifetime)).serialize()\n                            + TLV.make_resource(RID.Server.ShortServerID, 2).serialize()\n                            + TLV.make_resource(RID.Server.NotificationStoring, True).serialize()\n                            + TLV.make_resource(RID.Server.Binding,\n                                                \"U\").serialize()\n                            + TLV.make_resource(RID.Server.ServerCommunicationRetryCount, 1).serialize()\n                            + TLV.make_resource(RID.Server.ServerCommunicationSequenceRetryCount, 1).serialize()\n                            )\n\n        # Bootstrap Finish\n        pkt = Lwm2mBootstrapFinish()\n        self.bootstrap_server.send(pkt)\n        self.assertMsgEqual(Lwm2mChanged.matching(pkt)(),\n                            self.bootstrap_server.recv())\n\n        # demo will refresh Bootstrap connection...\n        self.assertDtlsReconnect(self.bootstrap_server)\n\n        # ...and Register with the regular server\n        self.assertDemoRegisters(lifetime=int(lifetime))\n\n        # let the Update fail\n        self.assertIsInstance(self.serv.recv(timeout_s=lifetime), Lwm2mUpdate)\n        self.assertIsInstance(self.serv.recv(\n            timeout_s=self.ACK_TIMEOUT + 1), Lwm2mUpdate)\n\n        # client falls back to Register\n        pkt = self.serv.recv(timeout_s=lifetime)\n        self.assertIsInstance(pkt, Lwm2mRegister)\n        self.serv.send(Lwm2mErrorResponse.matching(pkt)\n                       (coap.Code.RES_FORBIDDEN))\n\n        # now the client falls back to Bootstrap, and doesn't get response\n        self.assertIsInstance(\n            self.bootstrap_server.recv(), Lwm2mRequestBootstrap)\n        self.assertIsInstance(self.bootstrap_server.recv(\n            timeout_s=self.ACK_TIMEOUT + 1), Lwm2mRequestBootstrap)\n\n        # rehandshake should appear here\n        self.assertDtlsReconnect(\n            self.bootstrap_server, timeout_s=2*self.ACK_TIMEOUT + 1)\n        self.assertIsInstance(\n            self.bootstrap_server.recv(), Lwm2mRequestBootstrap)\n\n\nclass ModifyingTxParams(RetransmissionTest.TestMixin, bootstrap_client.BootstrapTest.Test):\n    def setUp(self):\n        super().setUp(minimum_version='1.0', maximum_version='1.1')\n\n    def runTest(self):\n        # The client will attempt Request Bootstrap as version 1.1 (with pct= option)\n        pkt1 = self.bootstrap_server.recv(timeout_s=self.max_transmit_wait())\n        pkt1_time = time.time()\n        self.assertMsgEqual(Lwm2mRequestBootstrap(endpoint_name=DEMO_ENDPOINT_NAME,\n                                                  preferred_content_format=coap.ContentFormat.APPLICATION_LWM2M_SENML_CBOR),\n                            pkt1)\n\n        pkt2 = self.bootstrap_server.recv(timeout_s=self.max_transmit_wait())\n        pkt2_time = time.time()\n        self.assertMsgEqual(pkt1, pkt2)\n\n        pkt3 = self.bootstrap_server.recv(timeout_s=self.max_transmit_wait())\n        pkt3_time = time.time()\n        self.assertMsgEqual(pkt2, pkt3)\n\n        # check that it used the initial transmission params\n        self.assertAlmostEqual(pkt2_time - pkt1_time,\n                               self.ACK_TIMEOUT, delta=0.5)\n        self.assertAlmostEqual(pkt3_time - pkt2_time,\n                               2.0 * self.ACK_TIMEOUT, delta=0.5)\n\n        # Now let's change the transmission params\n        # ACK_TIMEOUT=5, ACK_RANDOM_FACTOR=1, MAX_RETRANSMIT=1, NSTART=1\n        self.communicate('set-tx-param udp 5 1 1 1')\n        self.ACK_TIMEOUT = 5.0\n        # Respond with an error so that the client falls back to 1.0\n        self.bootstrap_server.send(\n            Lwm2mErrorResponse.matching(pkt3)(code=coap.Code.RES_BAD_REQUEST))\n\n        # Transmission params should have changed,\n        # so check that the new ACK_TIMEOUT is in effect for the 1.0 attempt\n        pkt1 = self.bootstrap_server.recv(timeout_s=self.max_transmit_wait())\n        pkt1_time = time.time()\n        self.assertMsgEqual(Lwm2mRequestBootstrap(\n            endpoint_name=DEMO_ENDPOINT_NAME), pkt1)\n\n        pkt2 = self.bootstrap_server.recv(timeout_s=self.max_transmit_wait())\n        pkt2_time = time.time()\n        self.assertMsgEqual(pkt1, pkt2)\n\n        self.assertAlmostEqual(pkt2_time - pkt1_time,\n                               self.ACK_TIMEOUT, delta=0.5)\n\n        # Respond to Request Bootstrap\n        self.bootstrap_server.send(Lwm2mChanged.matching(pkt2)())\n        self.perform_typical_bootstrap(server_iid=1, security_iid=2,\n                                       server_uri='coap://127.0.0.1:%d' % self.serv.get_listen_port(),\n                                       bootstrap_request_timeout_s=-1)\n\n        # Client shall now register; check that it also uses the new transmission params\n        pkt1 = self.serv.recv(timeout_s=self.max_transmit_wait())\n        pkt1_time = time.time()\n        self.assertMsgEqual(Lwm2mRegister('/rd?lwm2m=1.1&ep=%s&lt=86400' % DEMO_ENDPOINT_NAME),\n                            pkt1)\n\n        pkt2 = self.serv.recv(timeout_s=self.max_transmit_wait())\n        pkt2_time = time.time()\n        self.assertMsgEqual(pkt1, pkt2)\n\n        self.assertAlmostEqual(pkt2_time - pkt1_time,\n                               self.ACK_TIMEOUT, delta=0.5)\n\n        self.serv.send(Lwm2mCreated.matching(pkt2)(\n            location=self.DEFAULT_REGISTER_ENDPOINT))\n\n        # check that downloads also use the new transmission params\n        dl_server = coap.Server()\n        try:\n            with tempfile.NamedTemporaryFile() as tmp:\n                self.communicate(\n                    'download coap://127.0.0.1:%d %s' % (dl_server.get_listen_port(), tmp.name))\n\n                pkt1 = dl_server.recv(timeout_s=self.max_transmit_wait())\n                pkt1_time = time.time()\n\n                pkt2 = dl_server.recv(timeout_s=self.max_transmit_wait())\n                pkt2_time = time.time()\n                self.assertMsgEqual(pkt1, pkt2)\n\n                self.assertAlmostEqual(\n                    pkt2_time - pkt1_time, self.ACK_TIMEOUT, delta=0.5)\n\n                dl_server.send(Lwm2mErrorResponse.matching(pkt2)(\n                    code=coap.Code.RES_NOT_FOUND).fill_placeholders())\n        finally:\n            dl_server.close()\n\n\nclass ModifyingExchangeTimeout(RetransmissionTest.TestMixin, bootstrap_client.BootstrapTest.Test):\n    EXCHANGE_LIFETIME = 2.0\n\n    def runTest(self):\n        self.assertDemoRequestsBootstrap()\n\n        # change exchange lifetime to 2 seconds\n        self.communicate('set-coap-exchange-timeout udp %s' %\n                         (self.EXCHANGE_LIFETIME,))\n\n        # Create typical Server Object instance\n        server_entries_no_ssid = [TLV.make_resource(RID.Server.Lifetime, 86400),\n                                  TLV.make_resource(\n                                      RID.Server.NotificationStoring, True),\n                                  TLV.make_resource(RID.Server.Binding, \"U\"),\n                                  TLV.make_resource(\n                                      RID.Server.ServerCommunicationRetryCount, 1),\n                                  TLV.make_resource(\n                                      RID.Server.ServerCommunicationRetryTimer, 0),\n                                  TLV.make_resource(\n                                      RID.Server.ServerCommunicationSequenceRetryCount, 1),\n                                  TLV.make_resource(\n                                      RID.Server.ServerCommunicationSequenceDelayTimer, 0)]\n        server_tlv = b''.join(entry.serialize() for entry in [\n            TLV.make_resource(RID.Server.ShortServerID, 1)] + server_entries_no_ssid)\n        # Create typical (corresponding) Security Object instance\n        security_tlv = b''.join(entry.serialize() for entry in [\n            TLV.make_resource(RID.Security.ServerURI,\n                              'coap://127.0.0.1:%d' % self.serv.get_listen_port()),\n            TLV.make_resource(RID.Security.Bootstrap, 0),\n            TLV.make_resource(RID.Security.Mode, SecurityMode.NoSec.value),\n            TLV.make_resource(RID.Security.ShortServerID, 1),\n            TLV.make_resource(RID.Security.PKOrIdentity, b''),\n            TLV.make_resource(RID.Security.SecretKey, b'')])\n\n        # Try sending that as Block\n        assert len(server_tlv) > 16\n        server_chunks = list(equal_chunk_splitter(16)(server_tlv))\n        packets = list(packets_from_chunks(server_chunks, path='/%d/%d' % (OID.Server, 1),\n                                           format=coap.ContentFormat.APPLICATION_LWM2M_TLV))\n\n        req = packets[0]\n        self.bootstrap_server.send(req)\n        self.assertMsgEqual(Lwm2mContinue.matching(req)(),\n                            self.bootstrap_server.recv())\n\n        # Wait for the exchange to time out\n        time.sleep(self.EXCHANGE_LIFETIME + 0.5)\n\n        # The second packet shall no longer match to any exchange\n        req = packets[1]\n        self.bootstrap_server.send(req)\n        self.assertMsgEqual(\n            Lwm2mErrorResponse.matching(req)(\n                code=coap.Code.RES_REQUEST_ENTITY_INCOMPLETE),\n            self.bootstrap_server.recv())\n\n        # That's tested, now bootstrap normally\n        self.write_instance(self.bootstrap_server,\n                            oid=OID.Server, iid=1, content=server_tlv)\n        self.write_instance(self.bootstrap_server,\n                            oid=OID.Security, iid=1, content=security_tlv)\n        self.perform_bootstrap_finish()\n\n        self.assertDemoRegisters()\n        self.wait_until_socket_count(1, timeout_s=2)\n\n        # Check that the new connection also uses the new exchange timeout\n        server_chunks = list(equal_chunk_splitter(16)(\n            b''.join(entry.serialize() for entry in server_entries_no_ssid)))\n        packets = list(packets_from_chunks(server_chunks, path='/%d/%d' % (OID.Server, 1),\n                                           format=coap.ContentFormat.APPLICATION_LWM2M_TLV))\n\n        req = packets[0]\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mContinue.matching(req)(), self.serv.recv())\n\n        # Wait for the exchange to time out\n        time.sleep(self.EXCHANGE_LIFETIME + 0.5)\n\n        # The second packet shall no longer match to any exchange\n        req = packets[1]\n        self.serv.send(req)\n        self.assertMsgEqual(\n            Lwm2mErrorResponse.matching(req)(\n                code=coap.Code.RES_REQUEST_ENTITY_INCOMPLETE),\n            self.serv.recv())\n\n\nclass ModifyingDtlsHsTimers(RetransmissionTest.TestMixin, bootstrap_client.DtlsBootstrap.Test):\n    HANDSHAKE_TIMEOUT = 3\n\n    def setUp(self):\n        super().setUp(bootstrap_server=Lwm2mServer(\n            coap.DtlsServer(psk_key=self.PSK_KEY, psk_identity=self.PSK_IDENTITY)),\n            extra_cmdline_args=['--identity', str(binascii.hexlify(self.PSK_IDENTITY), 'ascii'),\n                                '--key', str(binascii.hexlify(self.PSK_KEY), 'ascii')],\n            legacy_server_initiated_bootstrap_allowed=False)\n\n    def runTest(self):\n        self.assertDemoRequestsBootstrap()\n\n        # change DTLS handshake timeouts\n        self.communicate(\n            'set-dtls-handshake-timeout %d %d' % (self.HANDSHAKE_TIMEOUT, self.HANDSHAKE_TIMEOUT))\n\n        self.perform_typical_bootstrap(server_iid=1, security_iid=2,\n                                       server_uri='coaps://127.0.0.1:%s' % (\n                                           self.serv.get_listen_port(),),\n                                       security_mode=SecurityMode.PreSharedKey,\n                                       secure_identity=self.PSK_IDENTITY, secure_key=self.PSK_KEY,\n                                       bootstrap_request_timeout_s=-1)\n\n        self.assertPktIsDtlsClientHello(self.serv._raw_udp_socket.recv(65536))\n        mgmt_hello_time = time.time()\n\n        self.assertPktIsDtlsClientHello(\n            self.bootstrap_server._raw_udp_socket.recv(65536))\n        bootstrap_hello_time = time.time()\n\n        self.wait_until_socket_count(0, timeout_s=self.HANDSHAKE_TIMEOUT + 1)\n        everything_failed_time = time.time()\n\n        self.assertAlmostEqual(bootstrap_hello_time - mgmt_hello_time, self.HANDSHAKE_TIMEOUT,\n                               delta=0.5)\n        self.assertAlmostEqual(everything_failed_time - bootstrap_hello_time,\n                               self.HANDSHAKE_TIMEOUT, delta=0.5)\n\n        self.communicate('reconnect')\n        self.assertDemoRegisters()\n\n        # Change DTLS handshake timeouts again, but this time in offline mode\n        self.communicate('enter-offline')\n        self.wait_until_socket_count(0, timeout_s=5)\n        self.communicate('set-dtls-handshake-timeout %d %d' % (\n            self.HANDSHAKE_TIMEOUT, 2 * self.HANDSHAKE_TIMEOUT))\n        self.communicate('exit-offline')\n\n        self.assertPktIsDtlsClientHello(self.serv._raw_udp_socket.recv(65536))\n        first_hello_time = time.time()\n\n        # Second Client Hello shall come now\n        self.assertDtlsReconnect(timeout_s=self.HANDSHAKE_TIMEOUT + 2.0)\n        second_hello_time = time.time()\n\n        self.assertAlmostEqual(second_hello_time - first_hello_time, self.HANDSHAKE_TIMEOUT,\n                               delta=0.5)\n"
  },
  {
    "path": "tests/integration/suites/default/security.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\nimport base64\nimport datetime\nimport os\nimport subprocess\nimport threading\n\nimport cryptography\nimport cryptography.hazmat\nimport cryptography.hazmat.backends\nimport cryptography.x509\n\nfrom framework_tools.lwm2m.coap.server import SecurityMode\nfrom framework_tools.lwm2m.coap.transport import Transport\nfrom framework.lwm2m_test import *\nfrom framework.test_utils import DEMO_ENDPOINT_NAME, RID, OID\nfrom suites.default import bootstrap_client\nfrom suites.default import register\nfrom suites.default import retransmissions\n\n\nclass SecurityObjectDmOperationsBySingleServer(test_suite.Lwm2mSingleServerTest,\n                                               test_suite.Lwm2mDmOperations):\n    def runTest(self):\n        # Every security instance action on nonexistent instance shall return NOT_AUTHORIZED to not disclose\n        # any information.\n        for i in range(3):\n            self.read_instance(server=self.serv, oid=OID.Security, iid=i,\n                               expect_error_code=coap.Code.RES_UNAUTHORIZED)\n            self.delete_instance(server=self.serv, oid=OID.Security, iid=i,\n                                 expect_error_code=coap.Code.RES_UNAUTHORIZED)\n            self.write_instance(server=self.serv, oid=OID.Security, iid=i,\n                                expect_error_code=coap.Code.RES_UNAUTHORIZED)\n            self.execute_resource(server=self.serv, oid=OID.Security, iid=i,\n                                  rid=RID.Security.Bootstrap,\n                                  expect_error_code=coap.Code.RES_UNAUTHORIZED)\n            self.write_attributes(server=self.serv, oid=OID.Security, iid=i,\n                                  rid=RID.Security.Bootstrap, query=['pmax=1'],\n                                  expect_error_code=coap.Code.RES_UNAUTHORIZED)\n\n\n"
  },
  {
    "path": "tests/integration/suites/default/send.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nimport itertools\n\nfrom framework.lwm2m_test import *\nfrom framework.test_utils import *\nfrom . import retransmissions\n\n\nclass Send:\n    class Test(test_suite.Lwm2mSingleServerTest,\n               test_suite.Lwm2mDmOperations):\n        def setUp(self, *args, minimum_version='1.1', maximum_version='1.1', **kwargs):\n            super().setUp(*args, minimum_version=minimum_version, maximum_version=maximum_version,\n                          **kwargs)\n\n        def block_recv(self):\n            blocks = []\n            for seq_num in itertools.count(start=0, step=1):\n                pkt = self.serv.recv()\n\n                block1_opts = pkt.get_options(coap.Option.BLOCK1)\n                block1_opt = block1_opts[0]\n                self.assertEqual(len(block1_opts), 1, msg='BLOCK1 option %s' % (\n                    'duplicated' if block1_opts else 'not found',))\n                self.assertMsgEqual(Lwm2mSend(options=[block1_opt]), pkt)\n\n                blocks.append(pkt)\n\n                if block1_opt.has_more():\n                    self.serv.send(Lwm2mContinue.matching(pkt)(options=[block1_opt]))\n                else:\n                    break\n\n            return blocks\n\n\nclass SendFromDataModelTest(Send.Test):\n    def runTest(self):\n        self.communicate('send 1 %s' % (ResPath.Device.ModelNumber,))\n        pkt = self.serv.recv()\n\n        self.assertMsgEqual(Lwm2mSend(), pkt)\n        self.serv.send(Lwm2mChanged.matching(pkt)())\n\n        cbor = CBOR.parse(pkt.content)\n        cbor.verify_values(test=self,\n                           expected_value_map={\n                               ResPath.Device.ModelNumber: 'demo-client'\n                           })\n\n\nclass TrySendSecurityObjectResource(SendFromDataModelTest):\n    def runTest(self):\n        self.communicate('send 1 %s' % (ResPath.Security[1].ServerURI))\n\n        # The command above should fail and nothing should arrive, execute\n        # valid send command to check if only one message arrives.\n        super().runTest()\n\n\nclass SendFromDataModelTestTwoResources(Send.Test):\n    def runTest(self):\n        self.communicate(\n            'send 1 %s %s' %\n            (ResPath.Device.ModelNumber,\n             ResPath.Server[1].Lifetime))\n        pkt = self.serv.recv()\n\n        self.assertMsgEqual(Lwm2mSend(), pkt)\n        self.serv.send(Lwm2mChanged.matching(pkt)())\n\n        cbor = CBOR.parse(pkt.content)\n\n        self.assertIsNone(cbor[0].get(SenmlLabel.BASE_NAME))\n        self.assertIsNotNone(cbor[0].get(SenmlLabel.BASE_TIME))\n        self.assertIsNone(cbor[0].get(SenmlLabel.TIME))\n\n        self.assertIsNone(cbor[1].get(SenmlLabel.BASE_NAME))\n        self.assertIsNone(cbor[1].get(SenmlLabel.BASE_TIME))\n        self.assertIsNone(cbor[1].get(SenmlLabel.TIME))\n\n        cbor.verify_values(test=self,\n                           expected_value_map={\n                               ResPath.Device.ModelNumber: 'demo-client',\n                               ResPath.Server[1].Lifetime: 86400\n                           })\n\n\nclass SendFromDataModelTestTwoResourcesWithCommonPrefix(Send.Test):\n    def runTest(self):\n        self.communicate(\n            'send 1 %s %s' %\n            (ResPath.Device.ModelNumber,\n             ResPath.Device.Manufacturer))\n        pkt = self.serv.recv()\n\n        self.assertMsgEqual(Lwm2mSend(), pkt)\n        self.serv.send(Lwm2mChanged.matching(pkt)())\n\n        cbor = CBOR.parse(pkt.content)\n\n        self.assertEqual(cbor[0].get(SenmlLabel.BASE_NAME), '/3/0')\n        self.assertIsNotNone(cbor[0].get(SenmlLabel.BASE_TIME))\n        self.assertIsNone(cbor[0].get(SenmlLabel.TIME))\n\n        self.assertIsNone(cbor[1].get(SenmlLabel.BASE_NAME))\n        self.assertIsNone(cbor[1].get(SenmlLabel.BASE_TIME))\n        self.assertIsNone(cbor[1].get(SenmlLabel.TIME))\n\n        cbor.verify_values(test=self,\n                           expected_value_map={\n                               ResPath.Device.ModelNumber: 'demo-client',\n                               ResPath.Device.Manufacturer: '0023C7'\n                           })\n\n\nclass SendBlockTest(Send.Test):\n    def runTest(self):\n        self.create_instance(self.serv, oid=OID.Test, iid=0)\n        self.write_resource(self.serv, oid=OID.Test, iid=0, rid=RID.Test.ResBytesSize,\n                            content=b'1024')\n        self.communicate('send 1 %s' % (ResPath.Test[0].ResBytes,))\n\n        blocks = self.block_recv()\n        self.serv.send(Lwm2mChanged.matching(blocks[-1])())\n\n        cbor = CBOR.parse(b''.join(p.content for p in blocks))\n        cbor.verify_values(test=self,\n                           expected_value_map={\n                               ResPath.Test[0].ResBytes: bytes(b % 128 for b in range(1024))\n                           })\n\n\nclass MuteSend(Send.Test):\n    def runTest(self):\n        self.communicate('send 1 %s' % ResPath.Device.ModelNumber)\n        pkt = self.serv.recv()\n        self.assertMsgEqual(Lwm2mSend(), pkt)\n        self.serv.send(Lwm2mChanged.matching(pkt)())\n\n        # Mute\n        mute_send_path = Lwm2mResourcePath(ResPath.Server[1].MuteSend)\n        oid = mute_send_path.object_id\n        iid = mute_send_path.instance_id\n        rid = mute_send_path.resource_id\n        self.write_resource(self.serv, oid, iid, rid, b'1')\n        self.assertTrue(self.communicate('send 1 %s' % ResPath.Device.ModelNumber,\n                                         match_regex='cannot perform LwM2M Send, result: 2',\n                                         timeout=1))\n\n        # Unmute\n        self.write_resource(self.serv, oid, iid, rid, b'0')\n        self.communicate('send 1 %s' % ResPath.Device.ModelNumber)\n        pkt = self.serv.recv()\n        self.assertMsgEqual(Lwm2mSend(), pkt)\n        self.serv.send(Lwm2mChanged.matching(pkt)())\n\n\nclass DeferrableSend(Send.Test):\n    def runTest(self):\n        self.write_resource(self.serv, OID.Server, 1, RID.Server.DisableTimeout, '5')\n        self.execute_resource(self.serv, OID.Server, 1, RID.Server.Disable)\n        self.assertDemoDeregisters()\n\n        self.communicate('send_deferrable 1 %s' % ResPath.Device.ModelNumber)\n        self.assertDemoRegisters(version='1.1', timeout_s=10)\n        pkt = self.serv.recv(timeout_s=5)\n        self.assertMsgEqual(Lwm2mSend(), pkt)\n        self.serv.send(Lwm2mChanged.matching(pkt)())\n\n\nclass DeferrableSendDeferredMultipleTimes(Send.Test):\n    def setUp(self):\n        super().setUp(bootstrap_server=True)\n\n    def runTest(self):\n        self.execute_resource(self.serv, OID.Server, 2, RID.Server.RequestBootstrapTrigger)\n        self.assertDemoRequestsBootstrap(\n            preferred_content_format=coap.ContentFormat.APPLICATION_LWM2M_SENML_CBOR)\n\n        self.serv.reset()\n\n        self.communicate('send_deferrable 2 %s' % ResPath.Device.ModelNumber)\n\n        self.write_resource(self.bootstrap_server, OID.Security, 2, RID.Security.ServerURI,\n                            'coap://completely.invalid.server')\n        req = Lwm2mBootstrapFinish()\n        self.bootstrap_server.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.bootstrap_server.recv())\n\n        # URI is invalid, client will retry bootstrap - make it fail miserably\n        self.assertDemoRequestsBootstrap(\n            preferred_content_format=coap.ContentFormat.APPLICATION_LWM2M_SENML_CBOR,\n            respond_with_error_code=coap.Code.RES_FORBIDDEN)\n\n        self.communicate('reconnect')\n\n        self.assertDemoRequestsBootstrap(\n            preferred_content_format=coap.ContentFormat.APPLICATION_LWM2M_SENML_CBOR)\n        self.write_resource(self.bootstrap_server, OID.Security, 2, RID.Security.ServerURI,\n                            'coap://127.0.0.1:%d' % (self.serv.get_listen_port(),))\n        req = Lwm2mBootstrapFinish()\n        self.bootstrap_server.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.bootstrap_server.recv())\n\n        # demo can register again, and the Send operation is finally performed\n        self.assertDemoRegisters(version='1.1')\n        pkt = self.serv.recv(timeout_s=5)\n        self.assertMsgEqual(Lwm2mSend(), pkt)\n        self.serv.send(Lwm2mChanged.matching(pkt)())\n\n\nclass DeferrableSendFail(Send.Test):\n    def setUp(self):\n        super().setUp(minimum_version='1.0')\n\n    def runTest(self):\n        self.write_resource(self.serv, OID.Server, 1, RID.Server.DisableTimeout, '5')\n        self.execute_resource(self.serv, OID.Server, 1, RID.Server.Disable)\n        self.assertDemoDeregisters()\n\n        self.communicate('send_deferrable 1 %s' % ResPath.Device.ModelNumber)\n\n        # demo attempts to register with lwm2m=1.1\n        pkt = self.assertDemoRegisters(self.serv, version='1.1', timeout_s=10, respond=False)\n        self.serv.send(Lwm2mErrorResponse.matching(pkt)(coap.Code.RES_PRECONDITION_FAILED))\n\n        # demo retries with lwm2m=1.0\n        self.assertDemoRegisters(self.serv, version='1.0')\n        self.assertIsNotNone(self.read_log_until_match(b'SEND FINISHED HANDLER: -3', 60))\n\n\nclass CleanupWhileThereIsDeferredSend(Send.Test):\n    def tearDown(self):\n        try:\n            self.request_demo_shutdown()\n            self.assertIsNotNone(self.read_log_until_match(b'SEND FINISHED HANDLER: -2', 60))\n        finally:\n            super().tearDown(auto_deregister=False)\n\n    def runTest(self):\n        self.write_resource(self.serv, OID.Server, 1, RID.Server.DisableTimeout, '30')\n        self.execute_resource(self.serv, OID.Server, 1, RID.Server.Disable)\n        self.assertDemoDeregisters()\n\n        self.communicate('send_deferrable 1 %s' % ResPath.Device.ModelNumber)\n\n\nclass ServerRemovedWhileThereIsDeferredSend(Send.Test):\n    def tearDown(self):\n        super().tearDown(auto_deregister=False)\n\n    def runTest(self):\n        self.write_resource(self.serv, OID.Server, 1, RID.Server.DisableTimeout, '5')\n        self.execute_resource(self.serv, OID.Server, 1, RID.Server.Disable)\n        self.assertDemoDeregisters()\n\n        self.communicate('send_deferrable 1 %s' % ResPath.Device.ModelNumber)\n        self.communicate('trim-servers 0')\n        self.assertIsNotNone(self.read_log_until_match(b'SEND FINISHED HANDLER: -3', 60))\n\n\nclass ServerRemovedWhileAwaitingResponseToDeferredSend(Send.Test):\n    def setUp(self):\n        super().setUp(extra_cmdline_args=['--nstart', '2'])\n\n    def tearDown(self):\n        super().tearDown(auto_deregister=False)\n\n    def runTest(self):\n        self.write_resource(self.serv, OID.Server, 1, RID.Server.DisableTimeout, '5')\n        self.execute_resource(self.serv, OID.Server, 1, RID.Server.Disable)\n        self.assertDemoDeregisters()\n\n        self.communicate('send_deferrable 1 %s' % ResPath.Device.ModelNumber)\n        self.assertDemoRegisters(version='1.1', timeout_s=10)\n        pkt = self.serv.recv(timeout_s=5)\n        self.assertMsgEqual(Lwm2mSend(), pkt)\n\n        self.communicate('trim-servers 0')\n        self.assertDemoDeregisters()\n        self.assertIsNotNone(self.read_log_until_match(b'SEND FINISHED HANDLER: -2', 60))\n\n\nclass QueueModeSend(retransmissions.RetransmissionTest.TestMixin, Send.Test):\n    PSK_IDENTITY = b'test-identity'\n    PSK_KEY = b'test-key'\n\n    def setUp(self):\n        self.skipIfFeatureStatus('ANJAY_WITHOUT_QUEUE_MODE_AUTOCLOSE = ON',\n                                 'Queue mode autoclose disabled')\n\n        super().setUp(psk_identity=self.PSK_IDENTITY, psk_key=self.PSK_KEY,\n                      extra_cmdline_args=['--binding=UQ'], auto_register=False)\n        self.assertDemoRegisters(version='1.1', lwm2m11_queue_mode=True)\n\n    def runTest(self):\n        self.wait_until_socket_count(0, timeout_s=self.max_transmit_wait() + 2)\n\n        self.communicate('send 1 %s' % (ResPath.Device.ModelNumber,))\n\n        self.assertDtlsReconnect(timeout_s=5)\n        pkt = self.serv.recv()\n        self.assertMsgEqual(Lwm2mSend(), pkt)\n        self.serv.send(Lwm2mChanged.matching(pkt)())\n\n        cbor = CBOR.parse(pkt.content)\n        cbor.verify_values(test=self,\n                           expected_value_map={\n                               ResPath.Device.ModelNumber: 'demo-client'\n                           })\n"
  },
  {
    "path": "tests/integration/suites/default/senml_json_encoding.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\nimport json\nimport base64\nfrom framework.lwm2m_test import *\nfrom framework.test_utils import *\n\nfrom . import plaintext_base64 as pb64\n\nIID = 1\n\nclass JsonEncodingTest:\n    class Test(test_suite.Lwm2mSingleServerTest,\n               test_suite.Lwm2mDmOperations):\n        def setUp(self):\n            super().setUp()\n            self.create_instance(self.serv, oid=OID.Test, iid=IID)\n\n\ndef as_json(pkt):\n    return json.loads(pkt.content.decode('utf-8'))\n\n\ndef get_element(res, name):\n    base_name = ''\n    for e in res:\n        base_name = e.get('bn', base_name)\n        element_name = e.get('n', '')\n        if base_name + element_name == name:\n            return e\n\n\nclass JsonEncodingSenmlReadInteger(JsonEncodingTest.Test):\n    def runTest(self):\n        assigned_value = 1234\n        self.write_resource(self.serv, oid=OID.Test, iid=IID, rid=RID.Test.ResInt,\n                            content=str(assigned_value))\n\n        res = as_json(self.read_object(self.serv, oid=OID.Test,\n                                       accept=coap.ContentFormat.APPLICATION_LWM2M_SENML_JSON))\n        element_name = ResPath.Test[IID].ResInt\n        read_value = get_element(res, element_name)['v']\n        self.assertEqual(assigned_value, read_value)\n\n\nclass JsonEncodingSenmlReadUnsignedInteger(JsonEncodingTest.Test):\n    def runTest(self):\n        assigned_value = 5678\n        self.write_resource(self.serv, oid=OID.Test, iid=IID, rid=RID.Test.ResUnsignedInt,\n                            content=str(assigned_value))\n\n        res = as_json(self.read_object(self.serv, oid=OID.Test,\n                                       accept=coap.ContentFormat.APPLICATION_LWM2M_SENML_JSON))\n        element_name = ResPath.Test[IID].ResUnsignedInt\n        read_value = get_element(res, element_name)['v']\n        self.assertEqual(assigned_value, read_value)\n\n\nclass JsonEncodingSenmlReadFloat(JsonEncodingTest.Test):\n    def runTest(self):\n        assigned_value = 12.34\n        self.write_resource(self.serv, oid=OID.Test, iid=IID, rid=RID.Test.ResFloat,\n                            content=str(assigned_value))\n\n        res = as_json(self.read_object(self.serv, oid=OID.Test,\n                                       accept=coap.ContentFormat.APPLICATION_LWM2M_SENML_JSON))\n        element_name = ResPath.Test[IID].ResFloat\n        read_value = get_element(res, element_name)['v']\n        self.assertAlmostEqual(assigned_value, read_value, places=6)\n\n\nclass JsonEncodingSenmlReadBool(JsonEncodingTest.Test):\n    def runTest(self):\n        assigned_value = True\n        self.write_resource(self.serv, oid=OID.Test, iid=IID, rid=RID.Test.ResBool,\n                            content=str(int(assigned_value)))\n\n        res = as_json(self.read_object(self.serv, oid=OID.Test,\n                                       accept=coap.ContentFormat.APPLICATION_LWM2M_SENML_JSON))\n        element_name = ResPath.Test[IID].ResBool\n        read_value = get_element(res, element_name)['vb']\n        self.assertEqual(assigned_value, read_value)\n\n\nclass JsonEncodingSenmlReadObjectLink(JsonEncodingTest.Test):\n    def runTest(self):\n        assigned_value = '33605:1'\n        self.write_resource(self.serv, oid=OID.Test, iid=IID, rid=RID.Test.ResObjlnk,\n                            content=assigned_value)\n\n        res = as_json(self.read_object(self.serv, oid=OID.Test,\n                                       accept=coap.ContentFormat.APPLICATION_LWM2M_SENML_JSON))\n        element_name = ResPath.Test[IID].ResObjlnk\n        read_value = get_element(res, element_name)['vlo']\n        self.assertEqual(assigned_value, read_value)\n\n\nclass JsonEncodingSenmlReadOpaque(JsonEncodingTest.Test):\n    def runTest(self):\n        assigned_value = b'blokuje cie dzbanie'\n        expected_value = base64.urlsafe_b64encode(assigned_value).rstrip(b'=').decode('UTF-8')\n        self.write_resource(self.serv, oid=OID.Test, iid=IID, rid=RID.Test.ResRawBytes,\n                            content=assigned_value,\n                            format=coap.ContentFormat.APPLICATION_OCTET_STREAM)\n\n        res = as_json(self.read_object(self.serv, oid=OID.Test,\n                                       accept=coap.ContentFormat.APPLICATION_LWM2M_SENML_JSON))\n        element_name = ResPath.Test[IID].ResRawBytes\n        read_value = get_element(res, element_name)['vd']\n        self.assertEqual(expected_value, read_value)\n\n\nclass JsonEncodingSenmlReadString(JsonEncodingTest.Test):\n    def runTest(self):\n        assigned_value = 'kruzi'\n        self.write_resource(self.serv, oid=OID.Test, iid=IID, rid=RID.Test.ResString,\n                            content=assigned_value)\n\n        res = as_json(self.read_object(self.serv, oid=OID.Test,\n                                       accept=coap.ContentFormat.APPLICATION_LWM2M_SENML_JSON))\n        element_name = ResPath.Test[IID].ResString\n        read_value = get_element(res, element_name)['vs']\n        self.assertEqual(assigned_value, read_value)\n"
  },
  {
    "path": "tests/integration/suites/default/separate_response.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nimport socket\nimport unittest\n\nfrom framework.lwm2m_test import *\n\nclass SeparateResponseTest(test_suite.Lwm2mSingleServerTest):\n    def setUp(self):\n        # skip initial registration\n        super().setUp(auto_register=False)\n\n    def runTest(self):\n        # receive Register\n        req = self.serv.recv()\n        self.assertMsgEqual(\n            Lwm2mRegister('/rd?lwm2m=1.0&ep=%s&lt=86400' % (DEMO_ENDPOINT_NAME,)),\n            req)\n\n        # Separate Response: empty ACK\n        self.serv.send(Lwm2mEmpty.matching(req)())\n\n        # Separate Response: actual response\n        msg_id_generator = SequentialMsgIdGenerator(req.msg_id + 100)\n\n        req = Lwm2mCreated(msg_id=next(msg_id_generator),\n                           token=req.token,\n                           location=self.DEFAULT_REGISTER_ENDPOINT)\n        req.type = coap.Type.CONFIRMABLE\n\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mEmpty.matching(req)(),\n                            self.serv.recv())\n\n        # check Separate Response to an Update\n        self.communicate('send-update')\n        req = self.serv.recv()\n\n        self.assertMsgEqual(Lwm2mUpdate(path=self.DEFAULT_REGISTER_ENDPOINT,\n                                        content=b''),\n                            req)\n\n        # Separate Response: empty ACK\n        self.serv.send(Lwm2mEmpty.matching(req)())\n\n        # Separate Response with invalid token\n        invalid_req = Lwm2mChanged(msg_id=next(msg_id_generator),\n                                   token=get_another_token(req.token))\n        invalid_req.type = coap.Type.CONFIRMABLE\n\n        # Anjay sees it as unknown message and responds with Reset\n        self.serv.send(invalid_req)\n        self.assertMsgEqual(Lwm2mReset.matching(invalid_req)(),\n                            self.serv.recv())\n\n        # Separate Response: actual response\n        req = Lwm2mChanged(msg_id=next(msg_id_generator),\n                           token=req.token)\n        req.type = coap.Type.CONFIRMABLE\n        self.serv.send(req)\n\n        self.assertMsgEqual(Lwm2mEmpty.matching(req)(),\n                            self.serv.recv())\n\n        # should not send more messages after receiving a correct response\n        with self.assertRaises(socket.timeout, msg='unexpected message'):\n            print(self.serv.recv(timeout_s=3))\n\nclass SeparateResponseToSendTest(test_suite.Lwm2mSingleServerTest):\n    def setUp(self):\n        super().setUp(maximum_version='1.1')\n\n    def runTest(self):\n        self.communicate('send 1 %s' % (ResPath.Server[1].Lifetime,))\n        req = self.serv.recv()\n        self.assertEqual('/dp', req.get_uri_path())\n\n        # Separate Response: empty ACK\n        self.serv.send(Lwm2mEmpty.matching(req)())\n\n        # Separate Response\n        req = Lwm2mChanged(msg_id=(req.msg_id * 2) % (2 ** 16), token=req.token)\n        req.type = coap.Type.CONFIRMABLE\n        self.serv.send(req)\n\n        self.assertMsgEqual(Lwm2mEmpty.matching(req)(),\n                            self.serv.recv())\n\n        # should not send more messages after receiving a correct response\n        with self.assertRaises(socket.timeout, msg='unexpected message'):\n            print(self.serv.recv(timeout_s=6))\n\nclass SeparateResponseToSendTimeoutTest(test_suite.Lwm2mSingleServerTest):\n    def setUp(self):\n        super().setUp(maximum_version='1.1',\n                      extra_cmdline_args=[\n                          '--ack-random-factor', '1',\n                          '--ack-timeout', '1',\n                          '--max-retransmit', '1'\n                      ])\n\n    def runTest(self):\n        self.communicate('send 1 %s' % (ResPath.Server[1].Lifetime,))\n        req = self.serv.recv()\n        self.assertEqual('/dp', req.get_uri_path())\n\n        # Separate Response: empty ACK\n        self.serv.send(Lwm2mEmpty.matching(req)())\n\n        # Separate Response timeout in CoAP2 is EXCHANGE_LIFETIME, which is effectively:\n        #   ACK_TIMEOUT * (2 ** MAX_RETRANSMIT) * ACK_RANDOM_FACTOR + 200\n        # Unfortunately the 200 part is hardcoded,\n        # based on the value of MAX_LATENCY given in RFC 7252\n        with self.assertRaises(socket.timeout, msg='unexpected message'):\n            print(self.serv.recv(timeout_s=210))\n\n        # Separate Response\n        req = Lwm2mChanged(msg_id=(req.msg_id * 2) % (2 ** 16), token=req.token)\n        req.type = coap.Type.CONFIRMABLE\n        self.serv.send(req)\n\n        # Anjay sees it as unknown message and responds with Reset\n        self.assertMsgEqual(Lwm2mReset.matching(req)(),\n                            self.serv.recv())\n\n        # should not send more messages after receiving a correct response\n        with self.assertRaises(socket.timeout, msg='unexpected message'):\n            print(self.serv.recv(timeout_s=6))\n"
  },
  {
    "path": "tests/integration/suites/default/software_mgmt.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\nimport asyncio\nimport os\nimport re\nimport threading\nimport unittest\nimport ssl\n\nfrom framework_tools.coap_file_server import CoapFileServer\nfrom framework.lwm2m_test import *\nfrom framework.create_package import PackageForcedError, make_software_package\n\nfrom .firmware_update import FirmwareUpdate\nfrom .block_write import Block, equal_chunk_splitter, msg_id_generator\n\n\nclass ActivationState:\n    DEACTIVATED = 0\n    ACTIVATED = 1\n\n\nclass UpdateState:\n    INITIAL = 0\n    DOWNLOAD_STARTED = 1\n    DOWNLOADED = 2\n    DELIVERED = 3\n    INSTALLED = 4\n\n\nclass UpdateResult:\n    INITIAL = 0\n    DOWNLOADING = 1\n    INSTALLED = 2\n    DOWNLOADED_VERIFIED = 3\n    NOT_ENOUGH_SPACE = 50\n    OUT_OF_MEMORY = 51\n    CONNECTION_LOST = 52\n    INTEGRITY_FAILURE = 53\n    UNSUPPORTED_PACKAGE_TYPE = 54\n    INVALID_URI = 56\n    UPDATE_ERROR = 57\n    INSTALLATION_FAILURE = 58\n    UNINSTALLATION_FAILURE = 59\n\n\nSOFTWARE_PATH = '/software'\nSOFTWARE_SCRIPT_TEMPLATE = '#!/bin/sh\\n%secho installed > \"%s\"\\n'\n\nINSTANCE_COUNT = 2\n\n\ndef packets_from_chunks(chunks, process_options=None,\n                        path=ResPath.SoftwareManagement[0].Package,\n                        format=coap.ContentFormat.APPLICATION_OCTET_STREAM,\n                        code=coap.Code.REQ_PUT):\n    for idx, chunk in enumerate(chunks):\n        has_more = (idx != len(chunks) - 1)\n\n        options = ((uri_path_to_options(path) if path is not None else [])\n                   + [coap.Option.CONTENT_FORMAT(format),\n                      coap.Option.BLOCK1(seq_num=chunk.idx, has_more=has_more,\n                                         block_size=chunk.size)])\n\n        if process_options is not None:\n            options = process_options(options, idx)\n\n        yield coap.Packet(type=coap.Type.CONFIRMABLE,\n                          code=code,\n                          token=random_stuff(size=5),\n                          msg_id=next(msg_id_generator),\n                          options=options,\n                          content=chunk.content)\n\n\nclass SoftwareManagement:\n    class Test(test_suite.Lwm2mSingleServerTest):\n        SW_PKG_OPTS = {}\n\n        def set_auto_deregister(self, auto_deregister):\n            self.auto_deregister = auto_deregister\n\n        def set_check_marker(self, check_marker):\n            self.check_marker = check_marker\n\n        def setUp(self, garbage=0, auto_remove=True, *args, **kwargs):\n            garbage_lines = ''\n            while garbage > 0:\n                garbage_line = '#' * (min(garbage, 80) - 1) + '\\n'\n                garbage_lines += garbage_line\n                garbage -= len(garbage_line)\n            self.ANJAY_MARKER_FILE = generate_temp_filename(\n                dir='/tmp', prefix='anjay-sw-updated-')\n            self.ANJAY_PERSISTENCE_FILE = generate_temp_filename(\n                dir='/tmp', prefix='anjay-sw-persistence-')\n            self.SOFTWARE_SCRIPT_CONTENT = \\\n                (SOFTWARE_SCRIPT_TEMPLATE %\n                 (garbage_lines, self.ANJAY_MARKER_FILE)).encode('ascii')\n            if auto_remove:\n                self.SOFTWARE_SCRIPT_CONTENT += 'rm \"$0\"\\n'.encode('ascii')\n            super().setUp(sw_mgmt_persistence_file=self.ANJAY_PERSISTENCE_FILE, *args, **kwargs)\n\n        def tearDown(self):\n            auto_deregister = getattr(self, 'auto_deregister', True)\n            check_marker = getattr(self, 'check_marker', False)\n\n            try:\n                if not check_marker:\n                    return\n                for _ in range(10):\n                    time.sleep(0.5)\n\n                    if os.path.isfile(self.ANJAY_MARKER_FILE):\n                        break\n                else:\n                    self.fail('software marker not created')\n                with open(self.ANJAY_MARKER_FILE, \"rb\") as f:\n                    line = f.readline()[:-1]\n                    self.assertEqual(line, b\"installed\")\n                os.unlink(self.ANJAY_MARKER_FILE)\n            finally:\n                super().tearDown(auto_deregister=auto_deregister)\n\n        def read_update_result(self, inst: int = 0):\n            req = Lwm2mRead(ResPath.SoftwareManagement[inst].UpdateResult)\n            self.serv.send(req)\n            res = self.serv.recv()\n            self.assertMsgEqual(Lwm2mContent.matching(req)(), res)\n            return int(res.content)\n\n        def read_state(self, inst: int = 0):\n            req = Lwm2mRead(ResPath.SoftwareManagement[inst].UpdateState)\n            self.serv.send(req)\n            res = self.serv.recv()\n            self.assertMsgEqual(Lwm2mContent.matching(req)(), res)\n            return int(res.content)\n\n        def read_activation_state(self, inst: int = 0):\n            req = Lwm2mRead(ResPath.SoftwareManagement[inst].ActivationState)\n            self.serv.send(req)\n            res = self.serv.recv()\n            self.assertMsgEqual(Lwm2mContent.matching(req)(), res)\n            return int(res.content)\n\n        def wait_for_download(self, download_timeout_s=20, inst: int = 0):\n            # wait until client downloads and verify the software\n            deadline = time.time() + download_timeout_s\n            while time.time() < deadline:\n                time.sleep(0.5)\n\n                if self.read_state(inst) == UpdateState.DELIVERED:\n                    return\n\n            self.fail('software still not downloaded')\n\n        def write_software_and_wait_for_download(self, software_uri: str,\n                                                 download_timeout_s=20, inst: int = 0):\n            # Write /9/0/3 (Package URI)\n            self.write_software_uri_expect_success(software_uri, inst)\n\n            self.wait_for_download(download_timeout_s, inst)\n\n        def write_software_uri_expect_success(self, software_uri, inst: int = 0):\n            req = Lwm2mWrite(\n                ResPath.SoftwareManagement[inst].PackageURI, software_uri)\n            self.serv.send(req)\n            self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                                self.serv.recv())\n\n        def perform_software_install_expect_success(self, inst: int = 0):\n            req = Lwm2mExecute(ResPath.SoftwareManagement[inst].Install)\n            self.serv.send(req)\n            self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                                self.serv.recv())\n\n        def perform_software_uninstall_expect_success(self, inst: int = 0, content=b''):\n            req = Lwm2mExecute(\n                ResPath.SoftwareManagement[inst].Uninstall, content=content)\n            self.serv.send(req)\n            self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                                self.serv.recv())\n\n        def wait_until_state_is(self, state, timeout_s=10, inst: int = 0):\n            deadline = time.time() + timeout_s\n            while time.time() < deadline:\n                time.sleep(0.1)\n                if self.read_state(inst) == state:\n                    return\n\n            self.fail(f'state still is not {state}')\n\n        def wait_until_result_is(self, result, timeout_s=10, inst: int = 0):\n            deadline = time.time() + timeout_s\n            while time.time() < deadline:\n                time.sleep(0.1)\n                if self.read_update_result(inst) == result:\n                    return\n\n            self.fail(f'result still is not {result}')\n\n    class TestWithHttpServer(FirmwareUpdate.TestWithHttpServerMixin, Test):\n        PATH = SOFTWARE_PATH\n\n        def get_software_uri(self):\n            return super().get_firmware_uri()\n\n        def provide_response(self, other_content: bytes = None):\n            with self._response_cv:\n                self.assertIsNone(self._response_content)\n                if other_content:\n                    self._response_content = make_software_package(\n                        other_content, **self.SW_PKG_OPTS)\n                else:\n                    self._response_content = make_software_package(\n                        self.SOFTWARE_SCRIPT_CONTENT, **self.SW_PKG_OPTS)\n                self._response_cv.notify()\n\n    class TestWithTlsServer(FirmwareUpdate.TestWithTlsServerMixin, Test):\n        def setUp(self, pass_cert_to_demo=True, cmd_arg='', **kwargs):\n            super().setUp(pass_cert_to_demo, cmd_arg='--sw-mgmt-cert-file', **kwargs)\n\n    class TestWithHttpsServer(TestWithTlsServer, FirmwareUpdate.TestWithHttpsServerMixin, TestWithHttpServer):\n        def get_software_uri(self):\n            return super().get_firmware_uri()\n\n    class TestWithCoapServer(FirmwareUpdate.TestWithCoapServerMixin, Test):\n        pass\n\n    class TestWithCoapsServer(FirmwareUpdate.TestWithCoapServerMixin, Test):\n        SW_PSK_IDENTITY = b'sw-mgmt-psk-identity'\n        SW_PSK_KEY = b'sw-mgmt-psk-key'\n\n        def setUp(self, coap_server_class=coap.DtlsServer, extra_cmdline_args=None, *args, **kwargs):\n            extra_cmdline_args = (extra_cmdline_args or []) + ['--sw-mgmt-psk-identity',\n                                                               str(binascii.hexlify(\n                                                                   self.SW_PSK_IDENTITY), 'ascii'),\n                                                               '--sw-mgmt-psk-key', str(binascii.hexlify(\n                                                                   self.SW_PSK_KEY), 'ascii')]\n            super().setUp(*args, coap_server=coap_server_class(psk_identity=self.SW_PSK_IDENTITY,\n                                                               psk_key=self.SW_PSK_KEY),\n                          extra_cmdline_args=extra_cmdline_args, **kwargs)\n\n    class TestWithPartialDownload:\n        GARBAGE_SIZE = 8000\n\n        def wait_for_half_download(self):\n            # roughly twice the time expected as per SlowServer\n            deadline = time.time() + self.GARBAGE_SIZE / 500\n            fsize = 0\n            while time.time() < deadline:\n                time.sleep(0.5)\n                fsize = os.stat(self.sw_file_name).st_size\n                if fsize * 2 > self.GARBAGE_SIZE:\n                    break\n            if fsize * 2 <= self.GARBAGE_SIZE:\n                self.fail('software image not downloaded fast enough')\n            elif fsize > self.GARBAGE_SIZE:\n                self.fail('software image downloaded too quickly')\n\n        def setUp(self, *args, **kwargs):\n            super().setUp(garbage=self.GARBAGE_SIZE, *args, **kwargs)\n\n            import tempfile\n\n            with tempfile.NamedTemporaryFile(delete=False) as f:\n                self.sw_file_name = f.name\n            self.communicate('set-sw-mgmt-package-path 0 %s' %\n                             (os.path.abspath(self.sw_file_name)))\n\n    class TestWithPartialDownloadAndRestart(\n            TestWithPartialDownload, FirmwareUpdate.DemoArgsExtractorMixin):\n        def setUp(self, *args, **kwargs):\n            if 'auto_remove' not in kwargs:\n                kwargs = {**kwargs, 'auto_remove': False}\n            super().setUp(*args, **kwargs)\n\n        def tearDown(self):\n            try:\n                with open(self.sw_file_name, \"rb\") as f:\n                    self.assertEqual(f.read(), self.SOFTWARE_SCRIPT_CONTENT)\n                super().tearDown()\n            finally:\n                try:\n                    os.unlink(self.sw_file_name)\n                except FileNotFoundError:\n                    pass\n\n    class TestWithPartialCoapDownloadAndRestart(TestWithPartialDownloadAndRestart,\n                                                TestWithCoapServer):\n        def setUp(self):\n            class SlowServer(coap.Server):\n                def send(self, *args, **kwargs):\n                    time.sleep(0.5)\n                    result = super().send(*args, **kwargs)\n                    self.reset()  # allow requests from other ports\n                    return result\n\n            super().setUp(coap_server=SlowServer())\n\n            with self.file_server as file_server:\n                file_server.set_resource(self.PATH,\n                                         make_software_package(self.SOFTWARE_SCRIPT_CONTENT))\n                self.sw_uri = file_server.get_resource_uri(self.PATH)\n\n    class TestWithPartialCoapsDownloadAndRestart(TestWithPartialDownloadAndRestart,\n                                                 TestWithCoapsServer):\n        def setUp(self):\n            class SlowServer(coap.DtlsServer):\n                def send(self, *args, **kwargs):\n                    time.sleep(0.5)\n                    return super().send(*args, **kwargs)\n\n            super().setUp(coap_server_class=SlowServer)\n\n            with self.file_server as file_server:\n                file_server.set_resource(self.PATH,\n                                         make_software_package(self.SOFTWARE_SCRIPT_CONTENT))\n                self.sw_uri = file_server.get_resource_uri(self.PATH)\n\n    class TestWithPartialHttpDownloadAndRestart(FirmwareUpdate.TestWithPartialHttpDownloadAndRestartMixin,\n                                                TestWithPartialDownloadAndRestart,\n                                                TestWithHttpServer):\n        pass\n\n    class BlockTest(Test, Block.Test):\n        def block_init_file(self):\n            import tempfile\n\n            with tempfile.NamedTemporaryFile(delete=False) as f:\n                sw_file_name = f.name\n            self.communicate(\n                'set-sw-mgmt-package-path 0 %s' % (os.path.abspath(sw_file_name)))\n            return sw_file_name\n\n        def block_send(self, data, splitter, **make_software_package_args):\n            sw_file_name = self.block_init_file()\n\n            make_software_package_args.update(self.SW_PKG_OPTS)\n            chunks = list(splitter(\n                make_software_package(data, **make_software_package_args)))\n\n            for request in packets_from_chunks(chunks):\n                self.serv.send(request)\n                response = self.serv.recv()\n                self.assertIsSuccessResponse(response, request)\n\n            self.wait_until_state_is(state=UpdateState.DELIVERED)\n\n            with open(sw_file_name, 'rb') as sw_file:\n                self.assertEqual(sw_file.read(), data)\n\n            self.files_to_cleanup.append(sw_file_name)\n\n        def tearDown(self):\n            for file in self.files_to_cleanup:\n                try:\n                    os.unlink(file)\n                except FileNotFoundError:\n                    pass\n\n            super(Block.Test, self).tearDown()\n\n\nclass SoftwareManagementPackageTest(SoftwareManagement.Test):\n    def setUp(self):\n        super().setUp()\n        self.set_check_marker(True)\n        self.set_auto_deregister(True)\n\n    def runTest(self):\n        # Write /9/0/2 (Software): script content\n        req = Lwm2mWrite(ResPath.SoftwareManagement[0].Package,\n                         make_software_package(self.SOFTWARE_SCRIPT_CONTENT),\n                         format=coap.ContentFormat.APPLICATION_OCTET_STREAM)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n        self.wait_until_state_is(UpdateState.DELIVERED)\n        # Execute /9/0/4 (Install)\n        self.perform_software_install_expect_success()\n\n\nclass SoftwareManagementUriTest(SoftwareManagement.TestWithHttpServer):\n    def setUp(self, *args, **kwargs):\n        super().setUp(*args, **kwargs)\n        self.set_check_marker(True)\n        self.set_auto_deregister(True)\n\n    def runTest(self):\n        self.provide_response()\n        self.write_software_and_wait_for_download(self.get_software_uri())\n\n        # Execute /9/0/4 (Install)\n        self.perform_software_install_expect_success()\n\n\nclass SoftwareManagementUriAutoSuspend(SoftwareManagementUriTest):\n    def setUp(self):\n        super().setUp(extra_cmdline_args=['--sw-mgmt-auto-suspend'])\n\n\nclass SoftwareManagementUriManualSuspend(SoftwareManagement.TestWithHttpServer):\n    def runTest(self):\n        self.provide_response()\n\n        requests = list(self.requests)\n        software_uri = self.get_software_uri()\n\n        self.communicate('sw-mgmt-suspend')\n\n        # Write /9/0/3 (Package URI)\n        self.write_software_uri_expect_success(software_uri)\n\n        # wait until client enters the DOWNLOAD STARTED state\n        deadline = time.time() + 20\n        while time.time() < deadline:\n            time.sleep(0.5)\n            if self.read_state() == UpdateState.DOWNLOAD_STARTED:\n                break\n        else:\n            self.fail('software still not in DOWNLOAD STARTED state')\n\n        time.sleep(5)\n        self.assertEqual(requests, self.requests)\n\n        # resume the download\n        self.communicate('sw-mgmt-reconnect')\n\n        self.wait_for_download()\n\n        self.assertEqual(requests + [SOFTWARE_PATH], self.requests)\n\n\nclass SoftwareManagementTryActivateInWrongState(SoftwareManagement.TestWithHttpServer):\n    def setUp(self):\n        super().setUp()\n        self.set_check_marker(True)\n        self.set_auto_deregister(True)\n\n    def try_activate_deactivate(self):\n        req = Lwm2mExecute(ResPath.SoftwareManagement[0].Activate)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mErrorResponse.matching(req)(coap.Code.RES_METHOD_NOT_ALLOWED),\n                            self.serv.recv())\n\n        req = Lwm2mExecute(ResPath.SoftwareManagement[0].Deactivate)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mErrorResponse.matching(req)(coap.Code.RES_METHOD_NOT_ALLOWED),\n                            self.serv.recv())\n\n    def runTest(self):\n        self.try_activate_deactivate()\n\n        # Write /9/0/3 (Package URI)\n        self.write_software_uri_expect_success(self.get_software_uri())\n\n        self.wait_until_state_is(UpdateState.DOWNLOAD_STARTED)\n        self.try_activate_deactivate()\n\n        self.provide_response()\n\n        self.wait_until_state_is(UpdateState.DELIVERED)\n        self.try_activate_deactivate()\n\n        # Execute /9/0/4 (Install)\n        self.perform_software_install_expect_success()\n\n        req = Lwm2mExecute(ResPath.SoftwareManagement[0].Activate)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n\n        req = Lwm2mExecute(ResPath.SoftwareManagement[0].Deactivate)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n\n\nclass SoftwareManagementStateAndResultChangeMixin:\n    update_state_observe_token = 0\n    update_result_observe_token = 0\n    activation_state_observe_token = 0\n\n    def check_notifications(self, update_state=None, update_result=None, activation_state=None):\n\n        count_of_arg = sum(1 for arg in (\n            update_state, update_result, activation_state) if arg is not None)\n\n        for _ in range(count_of_arg):\n            recv = self.serv.recv()\n            if update_state is not None and recv.token == self.update_state_observe_token:\n                self.assertMsgEqual(Lwm2mNotify(self.update_state_observe_token, str(update_state).encode()),\n                                    recv)\n            elif update_result is not None and recv.token == self.update_result_observe_token:\n                self.assertMsgEqual(Lwm2mNotify(self.update_result_observe_token, str(update_result).encode()),\n                                    recv)\n            elif activation_state is not None and recv.token == self.activation_state_observe_token:\n                self.assertMsgEqual(Lwm2mNotify(self.activation_state_observe_token, str(activation_state).encode()),\n                                    recv)\n            else:\n                self.fail('unexpected token')\n\n    def runTest(self):\n        # disable minimum notification period\n        write_attrs_req = Lwm2mWriteAttributes(\n            ResPath.SoftwareManagement[0].UpdateState, query=['pmin=0'])\n        self.serv.send(write_attrs_req)\n        self.assertMsgEqual(Lwm2mChanged.matching(\n            write_attrs_req)(), self.serv.recv())\n\n        # initial state should be 0\n        observe_req = Lwm2mObserve(ResPath.SoftwareManagement[0].UpdateState)\n        self.serv.send(observe_req)\n        self.assertMsgEqual(Lwm2mContent.matching(\n            observe_req)(content=str(UpdateState.INITIAL).encode()), self.serv.recv())\n        self.update_state_observe_token = observe_req.token\n\n        observe_req = Lwm2mObserve(ResPath.SoftwareManagement[0].UpdateResult)\n        self.serv.send(observe_req)\n        self.assertMsgEqual(Lwm2mContent.matching(\n            observe_req)(content=str(UpdateResult.INITIAL).encode()), self.serv.recv())\n        self.update_result_observe_token = observe_req.token\n\n        # we shouldn't get any notification from this resource in this test\n        observe_req = Lwm2mObserve(\n            ResPath.SoftwareManagement[0].ActivationState)\n        self.serv.send(observe_req)\n        self.assertMsgEqual(Lwm2mContent.matching(\n            observe_req)(content=str(ActivationState.DEACTIVATED).encode()), self.serv.recv())\n        self.activation_state_observe_token = observe_req.token\n\n    def check_recv(self):\n        try:\n            self.serv.recv(timeout_s=0.5)\n            self.fail('we should not receive anything at this point')\n        except socket.timeout:\n            pass\n\n\nclass SoftwareManagementStateAndResultChangeTest(SoftwareManagementStateAndResultChangeMixin, SoftwareManagement.TestWithHttpServer):\n    install = True\n    activate = False\n    uninstall = True\n\n    def setUp(self, *args, **kwargs):\n        super().setUp(*args, **kwargs)\n        if self.install:\n            self.set_check_marker(True)\n        self.set_auto_deregister(True)\n\n    def runTest(self):\n        super().runTest()\n\n        # Write /9/0/3 (Package URI)\n        self.write_software_uri_expect_success(self.get_software_uri())\n\n        # notifications should be sent before downloading\n        self.check_notifications(\n            UpdateState.DOWNLOAD_STARTED, UpdateResult.DOWNLOADING)\n\n        self.provide_response()\n\n        # ... after it finishes\n        self.check_notifications(UpdateState.DOWNLOADED, UpdateResult.INITIAL)\n\n        # ... after package validation\n        self.assertMsgEqual(Lwm2mNotify(self.update_state_observe_token, str(UpdateState.DELIVERED).encode()),\n                            self.serv.recv())\n\n        if self.install:\n            # Execute /9/0/4 (Install)\n            self.perform_software_install_expect_success()\n\n            # ... after installation\n            self.check_notifications(\n                UpdateState.INSTALLED, UpdateResult.INSTALLED)\n\n        if self.activate:\n            req = Lwm2mExecute(ResPath.SoftwareManagement[0].Activate)\n            self.serv.send(req)\n            self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                                self.serv.recv())\n\n            # notification should be sent after activation\n            self.assertMsgEqual(Lwm2mNotify(self.activation_state_observe_token, str(ActivationState.ACTIVATED).encode()),\n                                self.serv.recv())\n\n        if self.uninstall:\n            # Execute /9/0/6 (Uninstall)\n            self.perform_software_uninstall_expect_success()\n\n            # ... and after uninstallation\n            self.check_notifications(UpdateState.INITIAL, UpdateResult.INITIAL)\n\n        self.check_recv()\n\n        # there should be exactly one request\n        self.assertEqual([SOFTWARE_PATH], self.requests)\n\n\nclass SoftwareManagementActivateChangeTest(SoftwareManagementStateAndResultChangeTest):\n    install = True\n    activate = True\n    uninstall = False\n\n    def runTest(self):\n        super().runTest()\n\n        req = Lwm2mExecute(ResPath.SoftwareManagement[0].Deactivate)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n\n        # ... and after deactivation\n        self.assertMsgEqual(Lwm2mNotify(self.activation_state_observe_token, str(ActivationState.DEACTIVATED).encode()),\n                            self.serv.recv())\n\n        # Execute /9/0/6 (Uninstall)\n        self.perform_software_uninstall_expect_success()\n\n        # after deinstallation we should get UpdateState and UpdateResult notification\n        self.check_notifications(UpdateState.INITIAL, UpdateResult.INITIAL)\n\n        self.check_recv()\n\n\nclass SoftwareManagementUninstallFromDeliveredTest(SoftwareManagementStateAndResultChangeTest):\n    install = False\n    activate = False\n    uninstall = False\n\n    def runTest(self):\n        super().runTest()\n\n        result = self.read_update_result()\n        # Execute /9/0/6 (Uninstall)\n        self.perform_software_uninstall_expect_success()\n\n        # after deinstallation we should get UpdateState notification\n        self.check_notifications(UpdateState.INITIAL)\n\n        self.assertEqual(result, self.read_update_result())\n\n        self.check_recv()\n\n\nclass SoftwareManagementActivateUninstallFromInstalledTest(SoftwareManagementStateAndResultChangeTest):\n    install = True\n    activate = True\n    uninstall = False\n\n    def runTest(self):\n        super().runTest()\n\n        # Execute /9/0/6 (Uninstall)\n        self.perform_software_uninstall_expect_success()\n\n        # after deinstallation we should get UpdateState, UpdateResult and ActivationState notification\n        # Note: We did not find find any mention in the documentation about changing the status of resource 12,\n        # but it seems to us right to expect it to change to DEACTIVATED in such a case.\n        self.check_notifications(\n            UpdateState.INITIAL, UpdateResult.INITIAL, ActivationState.DEACTIVATED)\n\n        self.check_recv()\n\n\nclass SoftwareManagementInstallSetFailedTest(SoftwareManagementStateAndResultChangeTest):\n    install = False\n    activate = False\n    uninstall = False\n\n    def setUp(self):\n        super().setUp()\n        self.SW_PKG_OPTS = {\n            'force_error': PackageForcedError.Software.FailureInPerformInstall}\n\n    def runTest(self):\n        super().runTest()\n\n        # Execute /9/0/4 (Install)\n        self.perform_software_install_expect_success()\n\n        # ... after installation\n        self.check_notifications(\n            update_result=UpdateResult.INSTALLATION_FAILURE)\n\n        self.check_recv()\n\n\nclass SoftwareManagementInstallFailedTest(SoftwareManagementStateAndResultChangeTest):\n    install = False\n    activate = False\n    uninstall = False\n\n    def setUp(self):\n        super().setUp()\n        self.SW_PKG_OPTS = {\n            'force_error': PackageForcedError.Software.FailedInstall}\n\n    def runTest(self):\n        super().runTest()\n\n        # Execute /9/0/4 (Install)\n        self.perform_software_install_expect_success()\n\n        # ... after installation\n        self.check_notifications(\n            update_result=UpdateResult.INSTALLATION_FAILURE)\n\n        self.check_recv()\n\n\nclass SoftwareManagementUninstallFromInstallStateFailedTest(SoftwareManagementStateAndResultChangeTest):\n    install = True\n    activate = False\n    uninstall = False\n\n    def setUp(self):\n        super().setUp()\n        self.SW_PKG_OPTS = {\n            'force_error': PackageForcedError.Software.FailureInPerformUninstall}\n\n    def runTest(self):\n        super().runTest()\n\n        # Execute /9/0/6 (Uninstall)\n        # Note: There is UNINSTALLATION_FAILURE (Uninstallation Failure during forUpdate(arg=0)) UpdateResult,\n        # but the OMA documentation doesn't describe a failure uninstallation transition, so we stick to our implementation.\n        req = Lwm2mExecute(ResPath.SoftwareManagement[0].Uninstall)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mErrorResponse.matching(req)(coap.Code.RES_INTERNAL_SERVER_ERROR),\n                            self.serv.recv())\n\n        self.check_recv()\n\n\nclass SoftwareManagementUninstallFromDeliveredWithArgTest(SoftwareManagementStateAndResultChangeTest):\n    install = False\n    activate = False\n    uninstall = False\n\n    def runTest(self):\n        super().runTest()\n\n        result = self.read_update_result()\n\n        # Execute /9/0/6 (Uninstall)\n        self.perform_software_uninstall_expect_success(content=b'0')\n\n        # after deinstallation we should get UpdateState notification\n        self.check_notifications(UpdateState.INITIAL)\n\n        self.assertEqual(result, self.read_update_result())\n\n        self.check_recv()\n\n\nclass SoftwareManagementUninstallFromInstalledWithArgTest(SoftwareManagementStateAndResultChangeTest):\n    install = True\n    activate = False\n    uninstall = False\n\n    def runTest(self):\n        super().runTest()\n\n        # Execute /9/0/6 (Uninstall)\n        self.perform_software_uninstall_expect_success(content=b'0')\n\n        # after deinstallation we should get UpdateState notification\n        self.check_notifications(UpdateState.INITIAL, UpdateResult.INITIAL)\n\n        self.check_recv()\n\n\nclass SoftwareManagementActivateUninstallFromInstalledWithArgTest(SoftwareManagementStateAndResultChangeTest):\n    install = True\n    activate = True\n    uninstall = False\n\n    def runTest(self):\n        super().runTest()\n\n        # Execute /9/0/6 (Uninstall)\n        self.perform_software_uninstall_expect_success(content=b'0')\n\n        # after deinstallation we should get UpdateState, UpdateResult and ActivationState notification\n        # Note: We did not find find any mention in the documentation about changing the status of resource 12,\n        # but it seems to us right to expect it to change to DEACTIVATED in such a case.\n        self.check_notifications(\n            UpdateState.INITIAL, UpdateResult.INITIAL, ActivationState.DEACTIVATED)\n\n        self.check_recv()\n\n\nclass SoftwareManagementUninstallPrepareForUpdateFromDeliveredTest(SoftwareManagementStateAndResultChangeTest):\n    install = False\n    activate = False\n    uninstall = False\n\n    def runTest(self):\n        super().runTest()\n\n        # Execute /9/0/6 (Uninstall)\n        req = Lwm2mExecute(\n            ResPath.SoftwareManagement[0].Uninstall, content=b'1')\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mErrorResponse.matching(req)(coap.Code.RES_METHOD_NOT_ALLOWED),\n                            self.serv.recv())\n\n        self.check_recv()\n\n\nclass SoftwareManagementUninstallPrepareForUpdateFromInstalledTest(SoftwareManagementStateAndResultChangeTest):\n    install = True\n    activate = False\n    uninstall = False\n\n    def runTest(self):\n        super().runTest()\n\n        # Execute /9/0/6 (Uninstall)\n        self.perform_software_uninstall_expect_success(content=b'1')\n\n        # after deinstallation we should get UpdateState, UpdateResult and ActivationState notification\n        self.check_notifications(UpdateState.INITIAL, UpdateResult.INITIAL)\n\n        self.check_recv()\n\n\nclass SoftwareManagementActivateUninstallPrepareForUpdateFromInstalledTest(SoftwareManagementStateAndResultChangeTest):\n    install = True\n    activate = True\n    uninstall = False\n\n    def runTest(self):\n        super().runTest()\n\n        # Execute /9/0/6 (Uninstall)\n        self.perform_software_uninstall_expect_success(content=b'1')\n\n        # after deinstallation we should get UpdateState, UpdateResult and ActivationState notification\n        # Note: We did not find find any mention in the documentation about changing the status of resource 12,\n        # but it seems to us right to expect it to change to DEACTIVATED in such a case.\n        self.check_notifications(\n            UpdateState.INITIAL, UpdateResult.INITIAL, ActivationState.DEACTIVATED)\n\n        self.check_recv()\n\n\nclass SoftwareManagementUninstallPrepareForUpdateFailedTest(SoftwareManagementStateAndResultChangeTest):\n    install = True\n    activate = False\n    uninstall = False\n\n    def setUp(self):\n        super().setUp()\n        self.SW_PKG_OPTS = {\n            'force_error': PackageForcedError.Software.FailureInPerformPrepareForUpdate}\n\n    def runTest(self):\n        super().runTest()\n        # Execute /9/0/6 (Uninstall)\n        req = Lwm2mExecute(\n            ResPath.SoftwareManagement[0].Uninstall, content=b'1')\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mErrorResponse.matching(req)(coap.Code.RES_METHOD_NOT_ALLOWED),\n                            self.serv.recv())\n\n        self.check_recv()\n\n\nclass SoftwareManagementDoubleActivationAllowedTest(SoftwareManagementStateAndResultChangeTest):\n    install = True\n    activate = True\n    uninstall = False\n\n    def runTest(self):\n        super().runTest()\n\n        req = Lwm2mExecute(ResPath.SoftwareManagement[0].Activate)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n\n        self.check_recv()\n\n\nclass SoftwareManagementDoubleActivationNotAllowedTest(SoftwareManagementStateAndResultChangeTest):\n    install = True\n    activate = True\n    uninstall = False\n\n    def setUp(self):\n        super().setUp(extra_cmdline_args=[\n            '--sw-mgmt-disable-repeated-activation-deactivation'])\n\n    def runTest(self):\n        super().runTest()\n\n        req = Lwm2mExecute(ResPath.SoftwareManagement[0].Activate)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mErrorResponse.matching(req)(coap.Code.RES_METHOD_NOT_ALLOWED),\n                            self.serv.recv())\n\n        self.check_recv()\n\n\nclass SoftwareManagementDoubleDeactivationAllowedTest(SoftwareManagementStateAndResultChangeTest):\n    install = True\n    activate = True\n    uninstall = False\n\n    def runTest(self):\n        super().runTest()\n\n        req = Lwm2mExecute(ResPath.SoftwareManagement[0].Deactivate)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n\n        self.check_notifications(activation_state=ActivationState.DEACTIVATED)\n\n        req = Lwm2mExecute(ResPath.SoftwareManagement[0].Deactivate)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n\n        self.check_recv()\n\n\nclass SoftwareManagementDoubleDeactivationNotAllowedTest(SoftwareManagementStateAndResultChangeTest):\n    install = True\n    activate = True\n    uninstall = False\n\n    def setUp(self):\n        super().setUp(extra_cmdline_args=[\n            '--sw-mgmt-disable-repeated-activation-deactivation'])\n\n    def runTest(self):\n        super().runTest()\n\n        req = Lwm2mExecute(ResPath.SoftwareManagement[0].Deactivate)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n\n        self.check_notifications(activation_state=ActivationState.DEACTIVATED)\n\n        req = Lwm2mExecute(ResPath.SoftwareManagement[0].Deactivate)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mErrorResponse.matching(req)(coap.Code.RES_METHOD_NOT_ALLOWED),\n                            self.serv.recv())\n\n        self.check_recv()\n\n\nclass SoftwareManagementPersistenceTest(FirmwareUpdate.DemoArgsExtractorMixin, SoftwareManagement.TestWithHttpServer):\n    def setUp(self):\n        super().setUp()\n        self.set_check_marker(True)\n        self.set_auto_deregister(True)\n\n    def restart(self):\n        self.teardown_demo_with_servers()\n        self.setup_demo_with_servers(\n            sw_mgmt_persistence_file=self.ANJAY_PERSISTENCE_FILE)\n\n    def runTest(self):\n        self.assertEqual(UpdateState.INITIAL, self.read_state())\n        self.assertEqual(UpdateResult.INITIAL, self.read_update_result())\n        self.assertEqual(ActivationState.DEACTIVATED,\n                         self.read_activation_state())\n\n        self.provide_response()\n        self.write_software_and_wait_for_download(self.get_software_uri())\n\n        # Note: Restart with crash before integrity check is tested in SoftwareManagementRestartBeforeIntegrity\n        self.wait_until_state_is(UpdateState.DELIVERED)\n\n        self.restart()\n\n        self.assertEqual(UpdateState.DELIVERED, self.read_state())\n        self.assertEqual(UpdateResult.INITIAL, self.read_update_result())\n        self.assertEqual(ActivationState.DEACTIVATED,\n                         self.read_activation_state())\n\n        self.perform_software_install_expect_success()\n\n        self.wait_until_state_is(UpdateState.INSTALLED)\n\n        self.restart()\n\n        self.assertEqual(UpdateState.INSTALLED, self.read_state())\n        self.assertEqual(UpdateResult.INSTALLED, self.read_update_result())\n        self.assertEqual(ActivationState.DEACTIVATED,\n                         self.read_activation_state())\n\n        req = Lwm2mExecute(ResPath.SoftwareManagement[0].Activate)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n\n        self.restart()\n\n        self.assertEqual(UpdateState.INSTALLED, self.read_state())\n        self.assertEqual(UpdateResult.INSTALLED, self.read_update_result())\n        self.assertEqual(ActivationState.ACTIVATED,\n                         self.read_activation_state())\n\n        req = Lwm2mExecute(ResPath.SoftwareManagement[0].Deactivate)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n\n        self.restart()\n\n        self.assertEqual(UpdateState.INSTALLED, self.read_state())\n        self.assertEqual(UpdateResult.INSTALLED, self.read_update_result())\n        self.assertEqual(ActivationState.DEACTIVATED,\n                         self.read_activation_state())\n\n        self.perform_software_uninstall_expect_success()\n\n        self.restart()\n\n        self.assertEqual(UpdateState.INITIAL, self.read_state())\n        self.assertEqual(UpdateResult.INITIAL, self.read_update_result())\n        self.assertEqual(ActivationState.DEACTIVATED,\n                         self.read_activation_state())\n\n\nclass SoftwareManagementAddNewInstanceTest(SoftwareManagement.TestWithHttpServer, test_suite.Lwm2mDmOperations):\n    def setUp(self, *args, **kwargs):\n        super().setUp(*args, **kwargs)\n        self.set_check_marker(True)\n        self.set_auto_deregister(True)\n\n    def runTest(self):\n        self.create_instance(self.serv, oid=OID.SoftwareManagement, iid=2)\n        self.create_instance(self.serv, oid=OID.SoftwareManagement,\n                             iid=3, expect_error_code=coap.Code.RES_METHOD_NOT_ALLOWED)\n\n        self.provide_response()\n        self.write_software_and_wait_for_download(\n            self.get_software_uri(), inst=2)\n\n        # Execute /9/2/4 (Install)\n        self.perform_software_install_expect_success(inst=2)\n\n\nclass SoftwareManagementRemoveInstanceTest(SoftwareManagement.TestWithHttpServer, test_suite.Lwm2mDmOperations):\n    def setUp(self, *args, **kwargs):\n        super().setUp(*args, **kwargs)\n        self.set_check_marker(True)\n        self.set_auto_deregister(True)\n\n    def runTest(self):\n        self.delete_instance(self.serv, oid=OID.SoftwareManagement, iid=0)\n\n        self.provide_response()\n        self.write_software_and_wait_for_download(\n            self.get_software_uri(), inst=1)\n\n        # Execute /9/1/4 (Install)\n        self.perform_software_install_expect_success(inst=1)\n\n\nclass SoftwareManagementBadBase64(SoftwareManagement.Test):\n    def runTest(self):\n        # Write /9/0/2 (Software): some random text to see how it makes the world burn\n        # (as text context does not implement some_bytes handler).\n        data = bytes(b'\\x01' * 16)\n        req = Lwm2mWrite(\n            ResPath.SoftwareManagement[0].Package, data, format=coap.ContentFormat.TEXT_PLAIN)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mErrorResponse.matching(req)(coap.Code.RES_BAD_REQUEST),\n                            self.serv.recv())\n\n\nclass SoftwareManagementGoodBase64(SoftwareManagement.Test):\n    def runTest(self):\n        import base64\n        data = base64.encodebytes(bytes(b'\\x01' * 16)).replace(b'\\n', b'')\n        req = Lwm2mWrite(ResPath.SoftwareManagement[0].Package, data,\n                         format=coap.ContentFormat.TEXT_PLAIN)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n\n\nclass SoftwareManagementEmptyPkg(SoftwareManagement.TestWithHttpServer):\n    def runTest(self):\n        self.provide_response()\n        self.write_software_and_wait_for_download(self.get_software_uri())\n\n        req = Lwm2mWrite(ResPath.SoftwareManagement[0].Package, b'',\n                         format=coap.ContentFormat.APPLICATION_OCTET_STREAM)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mErrorResponse.matching(req)(coap.Code.RES_METHOD_NOT_ALLOWED),\n                            self.serv.recv())\n\n        self.assertEqual(UpdateState.DELIVERED, self.read_state())\n        self.assertEqual(UpdateResult.INITIAL, self.read_update_result())\n\n\nclass SoftwareManagementEmptyPkgUri(SoftwareManagement.TestWithHttpServer):\n    def runTest(self):\n        self.provide_response()\n        self.write_software_and_wait_for_download(self.get_software_uri())\n\n        req = Lwm2mWrite(ResPath.SoftwareManagement[0].PackageURI, b'')\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mErrorResponse.matching(req)(coap.Code.RES_METHOD_NOT_ALLOWED),\n                            self.serv.recv())\n\n        self.assertEqual(UpdateState.DELIVERED, self.read_state())\n        self.assertEqual(UpdateResult.INITIAL, self.read_update_result())\n\n\nclass SoftwareManagementInvalidUri(SoftwareManagement.Test):\n    def runTest(self):\n        # observe Result\n        observe_req = Lwm2mObserve(ResPath.SoftwareManagement[0].UpdateResult)\n        self.serv.send(observe_req)\n        self.assertMsgEqual(Lwm2mContent.matching(\n            observe_req)(content=b'0'), self.serv.recv())\n\n        # Write /9/0/3 (Package URI)\n        self.write_software_uri_expect_success(b'http://invalidsoftware.exe')\n\n        while True:\n            notify = self.serv.recv()\n            self.assertMsgEqual(Lwm2mNotify(observe_req.token), notify)\n            if int(notify.content) != UpdateResult.INITIAL:\n                break\n        self.serv.send(Lwm2mReset(msg_id=notify.msg_id))\n        self.assertEqual(UpdateResult.INVALID_URI, int(notify.content))\n        self.assertEqual(UpdateState.INITIAL, self.read_state())\n\n\nclass SoftwareManagementUnsupportedUri(SoftwareManagement.Test):\n    def runTest(self):\n        req = Lwm2mWrite(ResPath.SoftwareManagement[0].PackageURI,\n                         b'unsupported://uri.exe')\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mErrorResponse.matching(req)(coap.Code.RES_BAD_REQUEST),\n                            self.serv.recv())\n\n        # This does not even change state or anything, because according to the LwM2M spec\n        # Server can't feed us with unsupported URI type\n        self.assertEqual(UpdateState.INITIAL, self.read_state())\n        self.assertEqual(UpdateResult.INVALID_URI,\n                         self.read_update_result())\n\n\nclass SoftwareManagementOfflineUriTest(SoftwareManagement.TestWithHttpServer):\n    def runTest(self):\n        self.communicate('enter-offline tcp')\n\n        self.assertEqual(UpdateState.INITIAL, self.read_state())\n        self.assertEqual(UpdateResult.INITIAL, self.read_update_result())\n\n        # Write /9/0/3 (Package URI)\n        self.write_software_uri_expect_success(self.get_software_uri())\n\n        self.assertEqual(UpdateState.INITIAL, self.read_state())\n        self.assertEqual(UpdateResult.CONNECTION_LOST,\n                         self.read_update_result())\n\n\nclass SoftwareManagementReplacingPkgUri(SoftwareManagement.TestWithHttpServer):\n    def runTest(self):\n        self.provide_response()\n        self.write_software_and_wait_for_download(self.get_software_uri())\n\n        # This isn't specified anywhere as a possible transition, therefore\n        # it is most likely a bad request.\n        req = Lwm2mWrite(\n            ResPath.SoftwareManagement[0].PackageURI, 'http://something')\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mErrorResponse.matching(req)(coap.Code.RES_METHOD_NOT_ALLOWED),\n                            self.serv.recv())\n\n        self.assertEqual(UpdateState.DELIVERED, self.read_state())\n        self.assertEqual(UpdateResult.INITIAL, self.read_update_result())\n\n\nclass SoftwareManagementReplacingPkg(SoftwareManagement.TestWithHttpServer):\n    def runTest(self):\n        self.provide_response()\n        self.write_software_and_wait_for_download(self.get_software_uri())\n\n        # This isn't specified anywhere as a possible transition, therefore\n        # it is most likely a bad request.\n        req = Lwm2mWrite(ResPath.SoftwareManagement[0].Package, b'trololo',\n                         format=coap.ContentFormat.APPLICATION_OCTET_STREAM)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mErrorResponse.matching(req)(coap.Code.RES_METHOD_NOT_ALLOWED),\n                            self.serv.recv())\n\n        self.assertEqual(UpdateState.DELIVERED, self.read_state())\n        self.assertEqual(UpdateResult.INITIAL, self.read_update_result())\n\n\nclass SoftwareManagementHttpsReconnectTest(SoftwareManagement.TestWithPartialDownloadAndRestart,\n                                           SoftwareManagement.TestWithHttpsServer):\n    RESPONSE_DELAY = 0.5\n    CHUNK_SIZE = 1000\n\n    def setUp(self):\n        super().setUp()\n        self.set_check_marker(True)\n        self.set_auto_deregister(True)\n\n    def runTest(self):\n        self.provide_response()\n\n        # Write /9/0/3 (Package URI)\n        self.write_software_uri_expect_success(self.get_software_uri())\n\n        self.wait_for_half_download()\n\n        self.assertEqual(len(self.requests), 1)\n\n        self.communicate('sw-mgmt-reconnect')\n        self.provide_response()\n\n        self.wait_for_download()\n\n        self.assertEqual(len(self.requests), 2)\n\n        # Execute /9/0/4 (Install)\n        self.perform_software_install_expect_success()\n\n\nclass SoftwareManagementHttpsWritePackageDuringDownloadingTest(SoftwareManagement.TestWithPartialDownload,\n                                                               SoftwareManagement.TestWithHttpServer):\n    RESPONSE_DELAY = 0.5\n    CHUNK_SIZE = 1000\n\n    def runTest(self):\n        self.provide_response()\n\n        # Write /9/0/3 (Package URI)\n        self.write_software_uri_expect_success(self.get_software_uri())\n\n        self.wait_for_half_download()\n\n        self.assertEqual(self.get_socket_count(), 2)\n\n        req = Lwm2mWrite(ResPath.SoftwareManagement[0].Package, b'',\n                         format=coap.ContentFormat.APPLICATION_OCTET_STREAM)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mErrorResponse.matching(req)(coap.Code.RES_METHOD_NOT_ALLOWED),\n                            self.serv.recv())\n\n        self.wait_until_socket_count(expected=2, timeout_s=5)\n\n        self.assertEqual(UpdateState.DOWNLOAD_STARTED, self.read_state())\n        self.assertEqual(UpdateResult.DOWNLOADING, self.read_update_result())\n\n\nclass SoftwareManagementHttpsWritePackageUriDuringDownloadingTest(SoftwareManagement.TestWithPartialDownload,\n                                                                  SoftwareManagement.TestWithHttpServer):\n    RESPONSE_DELAY = 0.5\n    CHUNK_SIZE = 1000\n\n    def runTest(self):\n        self.provide_response()\n\n        # Write /9/0/3 (Package URI)\n        self.write_software_uri_expect_success(self.get_software_uri())\n\n        self.wait_for_half_download()\n\n        self.assertEqual(self.get_socket_count(), 2)\n\n        req = Lwm2mWrite(ResPath.SoftwareManagement[0].PackageURI, b'')\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mErrorResponse.matching(req)(coap.Code.RES_METHOD_NOT_ALLOWED),\n                            self.serv.recv())\n\n        self.wait_until_socket_count(expected=2, timeout_s=5)\n\n        self.assertEqual(UpdateState.DOWNLOAD_STARTED, self.read_state())\n        self.assertEqual(UpdateResult.DOWNLOADING, self.read_update_result())\n\n\nclass SoftwareManagementCoapWritePackageUriDuringDownloadingTest(SoftwareManagement.TestWithPartialDownload,\n                                                                 SoftwareManagement.TestWithCoapServer):\n    def runTest(self):\n        with self.file_server as file_server:\n            file_server.set_resource(self.PATH,\n                                     make_software_package(self.SOFTWARE_SCRIPT_CONTENT))\n            sw_uri = file_server.get_resource_uri(self.PATH)\n\n            # Write /9/0/3 (Package URI)\n            self.write_software_uri_expect_success(sw_uri)\n\n            # Handle one GET\n            file_server.handle_request()\n\n        self.assertEqual(self.get_socket_count(), 2)\n\n        req = Lwm2mWrite(ResPath.SoftwareManagement[0].PackageURI, b'')\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mErrorResponse.matching(req)(coap.Code.RES_METHOD_NOT_ALLOWED),\n                            self.serv.recv())\n\n        self.wait_until_socket_count(expected=2, timeout_s=5)\n\n        self.assertEqual(UpdateState.DOWNLOAD_STARTED, self.read_state())\n        self.assertEqual(UpdateResult.DOWNLOADING, self.read_update_result())\n\n\nclass SoftwareManagementHttpsOfflineTest(SoftwareManagement.TestWithPartialDownloadAndRestart,\n                                         SoftwareManagement.TestWithHttpServer):\n    RESPONSE_DELAY = 0.5\n    CHUNK_SIZE = 1000\n\n    def setUp(self):\n        super().setUp()\n        self.set_check_marker(True)\n        self.set_auto_deregister(True)\n\n    def runTest(self):\n        self.provide_response()\n\n        # Write /9/0/3 (Package URI)\n        self.write_software_uri_expect_success(self.get_software_uri())\n\n        self.wait_for_half_download()\n\n        self.assertEqual(self.get_socket_count(), 2)\n        self.communicate('enter-offline tcp')\n        self.wait_until_socket_count(expected=1, timeout_s=5)\n        self.provide_response()\n        self.communicate('exit-offline tcp')\n\n        self.wait_for_download()\n\n        # Execute /9/0/4 (Install)\n        self.perform_software_install_expect_success()\n\n\nclass SoftwareManagementHttpsTest(SoftwareManagement.TestWithHttpsServer):\n    def setUp(self):\n        super().setUp()\n        self.set_check_marker(True)\n        self.set_auto_deregister(True)\n\n    def runTest(self):\n        self.provide_response()\n        self.write_software_and_wait_for_download(\n            self.get_software_uri(), download_timeout_s=20)\n\n        # Execute /9/0/4 (Install)\n        self.perform_software_install_expect_success()\n\n\nclass SoftwareManagementUnconfiguredHttpsTest(SoftwareManagement.TestWithHttpsServer):\n    def setUp(self):\n        super().setUp(pass_cert_to_demo=False)\n\n    def runTest(self):\n        # disable minimum notification period\n        write_attrs_req = Lwm2mWriteAttributes(ResPath.SoftwareManagement[0].UpdateResult,\n                                               query=['pmin=0'])\n        self.serv.send(write_attrs_req)\n        self.assertMsgEqual(Lwm2mChanged.matching(\n            write_attrs_req)(), self.serv.recv())\n\n        # initial result should be 0\n        observe_req = Lwm2mObserve(ResPath.SoftwareManagement[0].UpdateResult)\n        self.serv.send(observe_req)\n        self.assertMsgEqual(Lwm2mContent.matching(\n            observe_req)(content=b'0'), self.serv.recv())\n\n        # Write /9/0/3 (Package URI)\n        self.write_software_uri_expect_success(self.get_software_uri())\n\n        # even before reaching the server, we should get an error\n        notify_msg = self.serv.recv()\n        # no security information => \"invalid URI\"\n        self.assertMsgEqual(Lwm2mNotify(observe_req.token,\n                                        str(UpdateResult.INVALID_URI).encode()),\n                            notify_msg)\n        self.serv.send(Lwm2mReset(msg_id=notify_msg.msg_id))\n        self.assertEqual(UpdateState.INITIAL, self.read_state())\n\n\nclass SoftwareManagementUnconfiguredHttpsWithFallbackAttemptTest(\n        SoftwareManagement.TestWithHttpsServer):\n    def setUp(self):\n        super().setUp(pass_cert_to_demo=False,\n                      psk_identity=b'test-identity', psk_key=b'test-key')\n\n    def runTest(self):\n        # disable minimum notification period\n        write_attrs_req = Lwm2mWriteAttributes(ResPath.SoftwareManagement[0].UpdateResult,\n                                               query=['pmin=0'])\n        self.serv.send(write_attrs_req)\n        self.assertMsgEqual(Lwm2mChanged.matching(\n            write_attrs_req)(), self.serv.recv())\n\n        # initial result should be 0\n        observe_req = Lwm2mObserve(ResPath.SoftwareManagement[0].UpdateResult)\n        self.serv.send(observe_req)\n        self.assertMsgEqual(Lwm2mContent.matching(\n            observe_req)(content=b'0'), self.serv.recv())\n\n        # Write /9/0/3 (Package URI)\n        self.write_software_uri_expect_success(self.get_software_uri())\n\n        # even before reaching the server, we should get an error\n        notify_msg = self.serv.recv()\n        # no security information => client will attempt PSK from data model\n        # and fail handshake => \"Connection lost\"\n        self.assertMsgEqual(Lwm2mNotify(observe_req.token,\n                                        str(UpdateResult.CONNECTION_LOST).encode()),\n                            notify_msg)\n        self.serv.send(Lwm2mReset(msg_id=notify_msg.msg_id))\n        self.assertEqual(UpdateState.INITIAL, self.read_state())\n\n\nclass SoftwareManagementInvalidHttpsTest(SoftwareManagement.TestWithHttpsServer):\n    def setUp(self):\n        super().setUp(cn='invalid_cn', alt_ip=None)\n\n    def runTest(self):\n        # disable minimum notification period\n        write_attrs_req = Lwm2mWriteAttributes(ResPath.SoftwareManagement[0].UpdateResult,\n                                               query=['pmin=0'])\n        self.serv.send(write_attrs_req)\n        self.assertMsgEqual(Lwm2mChanged.matching(\n            write_attrs_req)(), self.serv.recv())\n\n        # initial result should be 0\n        observe_req = Lwm2mObserve(ResPath.SoftwareManagement[0].UpdateResult)\n        self.serv.send(observe_req)\n        self.assertMsgEqual(Lwm2mContent.matching(\n            observe_req)(content=b'0'), self.serv.recv())\n\n        # Write /9/0/3 (Package URI)\n        self.write_software_uri_expect_success(self.get_software_uri())\n\n        # even before reaching the server, we should get an error\n        notify_msg = self.serv.recv()\n        # handshake failure => \"Connection lost\"\n        self.assertMsgEqual(Lwm2mNotify(observe_req.token,\n                                        str(UpdateResult.CONNECTION_LOST).encode()),\n                            notify_msg)\n        self.serv.send(Lwm2mReset(msg_id=notify_msg.msg_id))\n        self.assertEqual(0, self.read_state())\n\n\nclass SoftwareManagementWriteEmptyUriInIdleState(SoftwareManagement.Test):\n    def runTest(self):\n        self.assertEqual(UpdateState.INITIAL, self.read_state())\n\n        req = Lwm2mWrite(ResPath.SoftwareManagement[0].PackageURI, b'')\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mErrorResponse.matching(req)(coap.Code.RES_BAD_REQUEST),\n                            self.serv.recv())\n\n        self.assertEqual(UpdateState.INITIAL, self.read_state())\n        self.assertEqual(UpdateResult.INVALID_URI, self.read_update_result())\n\n\nclass SoftwareManagementCoapUri(SoftwareManagement.TestWithCoapServer):\n    def tearDown(self):\n        super().tearDown()\n\n        # there should be exactly one request\n        with self.file_server as file_server:\n            self.assertEqual(1, len(file_server.requests))\n            self.assertMsgEqual(CoapGet(self.PATH),\n                                file_server.requests[0])\n\n    def runTest(self):\n        with self.file_server as file_server:\n            file_server.set_resource(self.PATH,\n                                     make_software_package(self.SOFTWARE_SCRIPT_CONTENT))\n            sw_uri = file_server.get_resource_uri(self.PATH)\n        self.write_software_and_wait_for_download(sw_uri)\n\n\nclass SoftwareManagementCoapsUri(SoftwareManagement.TestWithCoapsServer, SoftwareManagementCoapUri):\n    pass\n\n\nclass SoftwareManagementCoapsUriAutoSuspend(SoftwareManagementCoapsUri):\n    def setUp(self):\n        super().setUp(extra_cmdline_args=['--sw-mgmt-auto-suspend'])\n\n\nclass SoftwareManagementCoapsUriManualSuspend(SoftwareManagementCoapsUri):\n    def runTest(self):\n        with self.file_server as file_server:\n            file_server.set_resource(self.PATH,\n                                     make_software_package(self.SOFTWARE_SCRIPT_CONTENT))\n            sw_uri = file_server.get_resource_uri(self.PATH)\n\n        self.communicate('sw-mgmt-suspend')\n\n        # Write /9/0/3 (Package URI)\n        self.write_software_uri_expect_success(sw_uri)\n\n        # wait until the state machine enters the DOWNLOAD STARTED state\n        deadline = time.time() + 20\n        while time.time() < deadline:\n            time.sleep(0.5)\n            if self.read_state() == UpdateState.DOWNLOAD_STARTED:\n                break\n        else:\n            self.fail('software still not in DOWNLOAD STARTED state')\n\n        time.sleep(5)\n        with self.file_server as file_server:\n            self.assertEqual(0, len(file_server.requests))\n\n        # resume the download\n        self.communicate('sw-mgmt-reconnect')\n\n        # wait until client downloads the software\n        self.wait_for_download()\n\n\nclass SoftwareManagementCoapsReconnectTest(SoftwareManagement.TestWithPartialCoapsDownloadAndRestart):\n    def setUp(self):\n        super().setUp()\n        self.set_check_marker(True)\n        self.set_auto_deregister(True)\n\n    def runTest(self):\n        # Write /9/0/3 (Package URI)\n        self.write_software_uri_expect_success(self.sw_uri)\n\n        self.wait_for_half_download()\n\n        with self.file_server as file_server:\n            file_server._server.reset()\n            self.communicate('sw-mgmt-reconnect')\n            self.assertDtlsReconnect(file_server._server, timeout_s=10,\n                                     expected_error=['0x7700', '0x7900'])\n\n        self.wait_for_download()\n\n        # Execute /9/0/4 (Install)\n        self.perform_software_install_expect_success()\n\n\nclass SoftwareManagementCoapsSuspendDuringOfflineAndReconnectDuringOnlineTest(SoftwareManagement.TestWithPartialCoapsDownloadAndRestart):\n    def runTest(self):\n        # Write /9/0/3 (Package URI)\n        self.write_software_uri_expect_success(self.sw_uri)\n\n        self.wait_for_half_download()\n\n        with self.file_server as file_server:\n            # Flush any buffers\n            while True:\n                try:\n                    file_server._server.recv(timeout_s=1)\n                except socket.timeout:\n                    break\n\n            self.communicate('enter-offline')\n\n            with self.assertRaises(socket.timeout):\n                file_server._server.recv(timeout_s=5)\n\n            self.communicate('sw-mgmt-suspend')\n\n            with self.assertRaises(socket.timeout):\n                file_server._server.recv(timeout_s=5)\n\n            self.communicate('exit-offline')\n\n            self.assertDemoRegisters()\n\n            with self.assertRaises(socket.timeout):\n                file_server._server.recv(timeout_s=5)\n\n            file_server._server.reset()\n            self.communicate('sw-mgmt-reconnect')\n            self.assertPktIsDtlsClientHello(\n                file_server._server._raw_udp_socket.recv(65536, socket.MSG_PEEK))\n\n        self.wait_for_download()\n\n\nclass SoftwareManagementCoapsSuspendDuringOfflineAndReconnectDuringOfflineTest(\n        SoftwareManagement.TestWithPartialCoapsDownloadAndRestart):\n    def runTest(self):\n        # Write /9/0/3 (Package URI)\n        self.write_software_uri_expect_success(self.sw_uri)\n\n        self.wait_for_half_download()\n\n        with self.file_server as file_server:\n            # Flush any buffers\n            while True:\n                try:\n                    file_server._server.recv(timeout_s=1)\n                except socket.timeout:\n                    break\n\n            self.communicate('enter-offline')\n\n            with self.assertRaises(socket.timeout):\n                file_server._server.recv(timeout_s=5)\n\n            self.communicate('sw-mgmt-suspend')\n\n            with self.assertRaises(socket.timeout):\n                file_server._server.recv(timeout_s=5)\n\n            self.communicate('sw-mgmt-reconnect')\n\n            with self.assertRaises(socket.timeout):\n                file_server._server.recv(timeout_s=5)\n\n            file_server._server.reset()\n            self.communicate('exit-offline')\n            self.assertPktIsDtlsClientHello(\n                file_server._server._raw_udp_socket.recv(65536, socket.MSG_PEEK))\n\n        self.assertDemoRegisters()\n\n        self.wait_for_download()\n\n\nclass SoftwareManagementCoapsOfflineDuringSuspendAndReconnectDuringOnlineTest(\n        SoftwareManagement.TestWithPartialCoapsDownloadAndRestart):\n    def runTest(self):\n        # Write /9/0/3 (Package URI)\n        self.write_software_uri_expect_success(self.sw_uri)\n\n        self.wait_for_half_download()\n\n        with self.file_server as file_server:\n            # Flush any buffers\n            while True:\n                try:\n                    file_server._server.recv(timeout_s=1)\n                except socket.timeout:\n                    break\n\n            self.communicate('sw-mgmt-suspend')\n\n            with self.assertRaises(socket.timeout):\n                file_server._server.recv(timeout_s=5)\n\n            self.communicate('enter-offline')\n\n            with self.assertRaises(socket.timeout):\n                file_server._server.recv(timeout_s=5)\n\n            self.communicate('exit-offline')\n\n            self.assertDemoRegisters()\n\n            with self.assertRaises(socket.timeout):\n                file_server._server.recv(timeout_s=5)\n\n            file_server._server.reset()\n            self.communicate('sw-mgmt-reconnect')\n            self.assertPktIsDtlsClientHello(\n                file_server._server._raw_udp_socket.recv(65536, socket.MSG_PEEK))\n\n        self.wait_for_download()\n\n\nclass SoftwareManagementCoapsOfflineDuringSuspendAndReconnectDuringOfflineTest(\n        SoftwareManagement.TestWithPartialCoapsDownloadAndRestart):\n    def runTest(self):\n        # Write /9/0/3 (Package URI)\n        self.write_software_uri_expect_success(self.sw_uri)\n\n        self.wait_for_half_download()\n\n        with self.file_server as file_server:\n            # Flush any buffers\n            while True:\n                try:\n                    file_server._server.recv(timeout_s=1)\n                except socket.timeout:\n                    break\n\n            self.communicate('sw-mgmt-suspend')\n\n            with self.assertRaises(socket.timeout):\n                file_server._server.recv(timeout_s=5)\n\n            self.communicate('enter-offline')\n\n            with self.assertRaises(socket.timeout):\n                file_server._server.recv(timeout_s=5)\n\n            self.communicate('sw-mgmt-reconnect')\n\n            with self.assertRaises(socket.timeout):\n                file_server._server.recv(timeout_s=5)\n\n            file_server._server.reset()\n            self.communicate('exit-offline')\n            self.assertPktIsDtlsClientHello(\n                file_server._server._raw_udp_socket.recv(65536, socket.MSG_PEEK))\n\n        self.assertDemoRegisters()\n\n        self.wait_for_download()\n\n\nclass SoftwareManagementHttpSuspendDuringOfflineAndReconnectDuringOnlineTest(\n        SoftwareManagement.TestWithPartialHttpDownloadAndRestart):\n    def runTest(self):\n        self.provide_response()\n\n        # Write /9/0/3 (Package URI)\n        self.write_software_uri_expect_success(self.get_software_uri())\n\n        self.wait_for_half_download()\n\n        self.communicate('enter-offline')\n\n        time.sleep(5)\n        fsize = os.stat(self.sw_file_name).st_size\n        self.assertEqual(len(self.requests), 1)\n        self.provide_response()\n\n        self.communicate('sw-mgmt-suspend')\n\n        time.sleep(5)\n        self.assertEqual(os.stat(self.sw_file_name).st_size, fsize)\n        self.assertEqual(len(self.requests), 1)\n\n        self.communicate('exit-offline')\n\n        self.assertDemoRegisters()\n\n        time.sleep(5)\n        self.assertEqual(os.stat(self.sw_file_name).st_size, fsize)\n        self.assertEqual(len(self.requests), 1)\n\n        self.communicate('sw-mgmt-reconnect')\n\n        self.wait_for_download()\n\n        self.assertEqual(len(self.requests), 2)\n\n\nclass SoftwareManagementHttpSuspendDuringOfflineAndReconnectDuringOfflineTest(\n        SoftwareManagement.TestWithPartialHttpDownloadAndRestart):\n    def runTest(self):\n        self.provide_response()\n\n        # Write /9/0/3 (Package URI)\n        self.write_software_uri_expect_success(self.get_software_uri())\n\n        self.wait_for_half_download()\n\n        self.communicate('enter-offline')\n\n        time.sleep(5)\n        fsize = os.stat(self.sw_file_name).st_size\n        self.assertEqual(len(self.requests), 1)\n        self.provide_response()\n\n        self.communicate('sw-mgmt-suspend')\n\n        time.sleep(5)\n        self.assertEqual(os.stat(self.sw_file_name).st_size, fsize)\n        self.assertEqual(len(self.requests), 1)\n\n        self.communicate('sw-mgmt-reconnect')\n\n        time.sleep(5)\n        self.assertEqual(os.stat(self.sw_file_name).st_size, fsize)\n        self.assertEqual(len(self.requests), 1)\n\n        self.communicate('exit-offline')\n\n        self.assertDemoRegisters()\n\n        self.wait_for_download()\n\n        self.assertEqual(len(self.requests), 2)\n\n\nclass SoftwareManagementHttpOfflineDuringSuspendAndReconnectDuringOnlineTest(\n        SoftwareManagement.TestWithPartialHttpDownloadAndRestart):\n    def runTest(self):\n        self.provide_response()\n\n        # Write /9/0/3 (Package URI)\n        self.write_software_uri_expect_success(self.get_software_uri())\n\n        self.wait_for_half_download()\n\n        self.communicate('sw-mgmt-suspend')\n\n        time.sleep(5)\n        fsize = os.stat(self.sw_file_name).st_size\n        self.assertEqual(len(self.requests), 1)\n        self.provide_response()\n\n        self.communicate('enter-offline')\n\n        time.sleep(5)\n        self.assertEqual(os.stat(self.sw_file_name).st_size, fsize)\n        self.assertEqual(len(self.requests), 1)\n\n        self.communicate('exit-offline')\n\n        self.assertDemoRegisters()\n\n        time.sleep(5)\n        self.assertEqual(os.stat(self.sw_file_name).st_size, fsize)\n        self.assertEqual(len(self.requests), 1)\n\n        self.communicate('sw-mgmt-reconnect')\n\n        self.wait_for_download()\n\n        self.assertEqual(len(self.requests), 2)\n\n\nclass SoftwareManagementHttpOfflineDuringSuspendAndReconnectDuringOfflineTest(\n        SoftwareManagement.TestWithPartialHttpDownloadAndRestart):\n    def runTest(self):\n        self.provide_response()\n\n        # Write /9/0/3 (Package URI)\n        self.write_software_uri_expect_success(self.get_software_uri())\n\n        self.wait_for_half_download()\n\n        self.communicate('sw-mgmt-suspend')\n\n        time.sleep(5)\n        fsize = os.stat(self.sw_file_name).st_size\n        self.assertEqual(len(self.requests), 1)\n        self.provide_response()\n\n        self.communicate('enter-offline')\n\n        time.sleep(5)\n        self.assertEqual(os.stat(self.sw_file_name).st_size, fsize)\n        self.assertEqual(len(self.requests), 1)\n\n        self.communicate('sw-mgmt-reconnect')\n\n        time.sleep(5)\n        self.assertEqual(os.stat(self.sw_file_name).st_size, fsize)\n        self.assertEqual(len(self.requests), 1)\n\n        self.communicate('exit-offline')\n\n        self.assertDemoRegisters()\n\n        self.wait_for_download()\n\n        self.assertEqual(len(self.requests), 2)\n\n\nclass SoftwareManagementRestartBeforeIntegrity(SoftwareManagement.Test):\n    def setUp(self):\n        super().setUp(extra_cmdline_args=[\n            '--sw-mgmt-terminate-after-downloading'])\n        self.set_check_marker(True)\n        self.set_auto_deregister(True)\n\n    def runTest(self):\n        # Write /9/0/2 (Software)\n        req = Lwm2mWrite(ResPath.SoftwareManagement[0].Package,\n                         make_software_package(self.SOFTWARE_SCRIPT_CONTENT),\n                         format=coap.ContentFormat.APPLICATION_OCTET_STREAM)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n\n        # wait until demo stops responding\n        deadline = time.time() + 20\n        while time.time() < deadline:\n            ret = self.communicate('send-update', timeout=2)\n            if ret is None:\n                break\n            time.sleep(0.2)\n        else:\n            self.fail('demo still active')\n\n        # restart the app\n        self.assertDemoDeregisters()\n        self.teardown_demo_with_servers(auto_deregister=False)\n        self.setup_demo_with_servers(\n            sw_mgmt_persistence_file=self.ANJAY_PERSISTENCE_FILE)\n\n        self.assertEqual(UpdateState.DELIVERED, self.read_state())\n        self.assertEqual(UpdateResult.INITIAL, self.read_update_result())\n\n        # Execute /9/0/4 (Install)\n        self.perform_software_install_expect_success()\n\n\nclass SoftwareManagementRestartAfterIntegrityFailed(SoftwareManagement.Test):\n    def runTest(self):\n        # Write /9/0/2 (Software)\n        req = Lwm2mWrite(ResPath.SoftwareManagement[0].Package,\n                         make_software_package(\n                             self.SOFTWARE_SCRIPT_CONTENT, crc=2137),\n                         format=coap.ContentFormat.APPLICATION_OCTET_STREAM)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n\n        self.wait_until_result_is(UpdateResult.INTEGRITY_FAILURE)\n        self.assertEqual(UpdateResult.INTEGRITY_FAILURE,\n                         self.read_update_result())\n        self.assertEqual(UpdateState.INITIAL, self.read_state())\n\n        # restart the app\n        self.teardown_demo_with_servers()\n        self.setup_demo_with_servers(\n            sw_mgmt_persistence_file=self.ANJAY_PERSISTENCE_FILE)\n\n        self.assertEqual(UpdateState.INITIAL, self.read_state())\n        self.assertEqual(UpdateResult.INITIAL, self.read_update_result())\n\n\nclass SoftwareManagementRestartWithDelivered(SoftwareManagement.Test):\n    def setUp(self):\n        super().setUp()\n        self.set_check_marker(True)\n        self.set_auto_deregister(True)\n\n    def runTest(self):\n        # Write /9/0/2 (Software)\n        req = Lwm2mWrite(ResPath.SoftwareManagement[0].Package,\n                         make_software_package(self.SOFTWARE_SCRIPT_CONTENT),\n                         format=coap.ContentFormat.APPLICATION_OCTET_STREAM)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n\n        self.wait_until_state_is(UpdateState.DELIVERED)\n\n        # restart the app\n        self.teardown_demo_with_servers()\n        self.setup_demo_with_servers(\n            sw_mgmt_persistence_file=self.ANJAY_PERSISTENCE_FILE)\n\n        self.assertEqual(UpdateState.DELIVERED, self.read_state())\n        self.assertEqual(UpdateResult.INITIAL, self.read_update_result())\n\n        # Execute /9/0/4 (Install)\n        self.perform_software_install_expect_success()\n\n\nclass SoftwareManagementResumeDownloadingOverHttpWithReconnect(\n        SoftwareManagement.TestWithPartialHttpDownloadAndRestart):\n    def _get_valgrind_args(self):\n        # we don't kill the process here, so we want Valgrind\n        return SoftwareManagement.TestWithHttpServer._get_valgrind_args(self)\n\n    def send_headers(self, handler, response_content, response_etag):\n        if 'Range' in handler.headers:\n            self.assertEqual(handler.headers['If-Match'], response_etag)\n            match = re.fullmatch(r'bytes=([0-9]+)-', handler.headers['Range'])\n            self.assertIsNotNone(match)\n            offset = int(match.group(1))\n            handler.send_header('Content-range',\n                                'bytes %d-%d/*' % (offset, len(response_content) - 1))\n            return offset\n\n    def runTest(self):\n        self.provide_response()\n        # Write /9/0/3 (Package URI)\n        self.write_software_uri_expect_success(self.get_software_uri())\n\n        self.wait_for_half_download()\n\n        # reconnect\n        self.serv.reset()\n        self.communicate('reconnect')\n        self.provide_response()\n        self.assertDemoRegisters(self.serv, timeout_s=5)\n\n        # wait until client downloads the software\n        deadline = time.time() + 20\n        state = None\n        while time.time() < deadline:\n            fsize = os.stat(self.sw_file_name).st_size\n            self.assertGreater(fsize * 2, self.GARBAGE_SIZE)\n            state = self.read_state()\n            self.assertIn(\n                state, {UpdateState.DOWNLOAD_STARTED, UpdateState.DOWNLOADED, UpdateState.DELIVERED})\n            if state == UpdateState.DELIVERED:\n                break\n            # prevent test from reading Result hundreds of times per second\n            time.sleep(0.5)\n\n        self.assertEqual(state, UpdateState.DELIVERED)\n\n        self.assertEqual(len(self.requests), 2)\n\n\nclass SoftwareManagementWithDelayedResultTest:\n    class TestMixin:\n        def runTest(self, forced_error, state, result):\n            with open(os.path.join(self.config.demo_path, self.config.demo_cmd), 'rb') as f:\n                software = f.read()\n\n            # Write /9/0/2 (Software)\n            self.block_send(software,\n                            equal_chunk_splitter(chunk_size=1024),\n                            force_error=forced_error)\n\n            # Execute /9/0/4 (Install)\n            req = Lwm2mExecute(ResPath.SoftwareManagement[0].Install)\n            self.serv.send(req)\n            self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                                self.serv.recv())\n\n            self.serv.reset()\n            self.assertDemoRegisters()\n            self.assertEqual(self.read_path(self.serv, ResPath.SoftwareManagement[0].UpdateResult).content,\n                             str(UpdateResult.INITIAL).encode())\n            self.assertEqual(self.read_path(self.serv, ResPath.SoftwareManagement[0].UpdateState).content,\n                             str(UpdateState.DELIVERED).encode())\n\n            self.wait_until_result_is(result)\n\n            self.assertEqual(self.read_path(self.serv, ResPath.SoftwareManagement[0].UpdateResult).content,\n                             str(result).encode())\n            self.assertEqual(self.read_path(self.serv, ResPath.SoftwareManagement[0].UpdateState).content,\n                             str(state).encode())\n            self.assertEqual(self.read_path(self.serv, ResPath.SoftwareManagement[0].ActivationState).content,\n                             str(ActivationState.DEACTIVATED).encode())\n\n\nclass SoftwareManagementWithDelayedSuccessTest(\n        SoftwareManagementWithDelayedResultTest.TestMixin, SoftwareManagement.BlockTest):\n    def runTest(self):\n        super().runTest(PackageForcedError.Software.DelayedSuccessInstall,\n                        UpdateState.INSTALLED, UpdateResult.INSTALLED)\n\n\nclass SoftwareManagementWithDelayedFailureTest(\n        SoftwareManagementWithDelayedResultTest.TestMixin, SoftwareManagement.BlockTest):\n    def runTest(self):\n        super().runTest(PackageForcedError.Software.DelayedFailedInstall,\n                        UpdateState.DELIVERED, UpdateResult.INSTALLATION_FAILURE)\n\n\nclass SoftwareManagementWithSetSuccessInPerformInstall(SoftwareManagement.BlockTest):\n    def runTest(self):\n        with open(os.path.join(self.config.demo_path, self.config.demo_cmd), 'rb') as f:\n            software = f.read()\n\n        # Write /9/0/2 (Software)\n        self.block_send(software,\n                        equal_chunk_splitter(chunk_size=1024),\n                        force_error=PackageForcedError.Software.SuccessInPerformInstall)\n\n        # Execute /9/0/4 (Install)\n        self.perform_software_install_expect_success()\n\n        # pkg_install_job is called via scheduler, so there is a small\n        # window during which reading the Software Update Result still returns\n        # INITIAL. Wait for a while for State to actually change.\n        self.wait_until_result_is(UpdateResult.INSTALLED, timeout_s=5)\n\n        self.assertEqual(self.read_state(), UpdateState.INSTALLED)\n        self.assertEqual(self.read_update_result(), UpdateResult.INSTALLED)\n        self.assertEqual(self.read_activation_state(),\n                         ActivationState.DEACTIVATED)\n\n\nclass SoftwareManagementWithSetSuccessInPerformInstallActivate(SoftwareManagement.BlockTest):\n    def runTest(self):\n        with open(os.path.join(self.config.demo_path, self.config.demo_cmd), 'rb') as f:\n            software = f.read()\n\n        # Write /9/0/2 (Software)\n        self.block_send(software,\n                        equal_chunk_splitter(chunk_size=1024),\n                        force_error=PackageForcedError.Software.SuccessInPerformInstallActivate)\n\n        # Execute /9/0/4 (Install)\n        self.perform_software_install_expect_success()\n\n        # pkg_install_job is called via scheduler, so there is a small\n        # window during which reading the Software Update Result still returns\n        # INITIAL. Wait for a while for State to actually change.\n        self.wait_until_result_is(UpdateResult.INSTALLED, timeout_s=5)\n\n        self.assertEqual(self.read_state(), UpdateState.INSTALLED)\n        self.assertEqual(self.read_update_result(), UpdateResult.INSTALLED)\n        self.assertEqual(self.read_activation_state(),\n                         ActivationState.ACTIVATED)\n\n\nclass SoftwareManagementWithSetFailureInPerformInstall(SoftwareManagement.BlockTest):\n    def runTest(self):\n        with open(os.path.join(self.config.demo_path, self.config.demo_cmd), 'rb') as f:\n            software = f.read()\n\n        # Write /9/0/2 (Software)\n        self.block_send(software,\n                        equal_chunk_splitter(chunk_size=1024),\n                        force_error=PackageForcedError.Software.FailureInPerformInstall)\n\n        self.wait_until_state_is(UpdateState.DELIVERED)\n\n        # Execute /9/0/4 (Install)\n        self.perform_software_install_expect_success()\n\n        # pkg_install_job is called via scheduler, so there is a small\n        # window during which reading the Software Update Result still returns\n        # INITIAL. Wait for a while for State to actually change.\n        self.wait_until_result_is(\n            UpdateResult.INSTALLATION_FAILURE, timeout_s=5)\n\n        self.assertEqual(self.read_state(), UpdateState.DELIVERED)\n        self.assertEqual(self.read_update_result(),\n                         UpdateResult.INSTALLATION_FAILURE)\n        self.assertEqual(self.read_activation_state(),\n                         ActivationState.DEACTIVATED)\n\n\nclass SoftwareManagementWithoutReboot(SoftwareManagement.BlockTest):\n    def runTest(self):\n        with open(os.path.join(self.config.demo_path, self.config.demo_cmd), 'rb') as f:\n            software = f.read()\n\n        # Write /9/0/2 (Software)\n        self.block_send(software,\n                        equal_chunk_splitter(chunk_size=1024),\n                        force_error=PackageForcedError.Software.DoNothing)\n\n        self.wait_until_state_is(UpdateState.DELIVERED)\n\n        # Execute /9/0/4 (Install)\n        self.perform_software_install_expect_success()\n\n        # Wait until internal state machine is updated\n        # We cannot rely on SoftwareManagement.State resource because it is installed first\n        # and the user code is only notified later, via a scheduler job\n        self.read_log_until_match(regex=re.escape(\n            b'*** SOFTWARE INSTALL:'), timeout_s=5)\n\n        self.assertEqual(self.read_state(), UpdateState.DELIVERED)\n\n        self.communicate('set-sw-mgmt-install-result 0 1')\n\n        self.assertEqual(self.read_state(), UpdateState.INSTALLED)\n        self.assertEqual(self.read_update_result(), UpdateResult.INSTALLED)\n        self.assertEqual(self.read_activation_state(),\n                         ActivationState.DEACTIVATED)\n\n\ntry:\n    import aiocoap\n    import aiocoap.resource\n    import aiocoap.transports.tls\nexcept ImportError:\n    # SoftwareManagementCoapTlsTest requires a bleeding-edge version of aiocoap, that at the time of\n    # writing this code, is not available even in the prerelease channel.\n    # So we're not enforcing this dependency for now.\n    pass\n\n\n@unittest.skipIf('aiocoap.transports.tls' not in sys.modules,\n                 'aiocoap.transports.tls not available')\n@unittest.skipIf(sys.version_info < (3, 5, 3),\n                 'SSLContext signature changed in Python 3.5.3')\nclass SoftwareManagementCoapTlsTest(\n        SoftwareManagement.TestWithTlsServer, test_suite.Lwm2mDmOperations):\n    def setUp(self):\n        super().setUp(garbage=8000)\n\n        class SoftwareResource(aiocoap.resource.Resource):\n            async def render_get(resource, request):\n                return aiocoap.Message(payload=make_software_package(\n                    self.SOFTWARE_SCRIPT_CONTENT))\n\n        serversite = aiocoap.resource.Site()\n        serversite.add_resource(('software',), SoftwareResource())\n\n        sslctx = ssl.SSLContext()\n        sslctx.load_cert_chain(self._cert_file, self._key_file)\n\n        class EphemeralTlsServer(aiocoap.transports.tls.TLSServer):\n            _default_port = 0\n\n        class CoapTcpFileServerThread(threading.Thread):\n            def __init__(self):\n                super().__init__()\n                self.loop = asyncio.new_event_loop()\n                ctx = aiocoap.Context(loop=self.loop, serversite=serversite)\n                self.loop.run_until_complete(ctx._append_tokenmanaged_transport(\n                    lambda tman: EphemeralTlsServer.create_server(('127.0.0.1', 0), tman, ctx.log,\n                                                                  self.loop, sslctx)))\n\n                socket = ctx.request_interfaces[0].token_interface.server.sockets[0]\n                self.server_address = socket.getsockname()\n\n            def run(self):\n                asyncio.set_event_loop(self.loop)\n                try:\n                    self.loop.run_forever()\n                finally:\n                    self.loop.run_until_complete(\n                        self.loop.shutdown_asyncgens())\n                    self.loop.close()\n\n        self.server_thread = CoapTcpFileServerThread()\n        self.server_thread.start()\n\n        self.set_check_marker(True)\n        self.set_auto_deregister(True)\n\n    def tearDown(self):\n        try:\n            super().tearDown()\n        finally:\n            self.server_thread.loop.call_soon_threadsafe(\n                self.server_thread.loop.stop)\n            self.server_thread.join()\n\n    def get_software_uri(self):\n        return 'coaps+tcp://127.0.0.1:%d/software' % (\n            self.server_thread.server_address[1],)\n\n    def runTest(self):\n        self.write_software_and_wait_for_download(self.get_software_uri())\n\n        # Execute /9/0/4 (Install)\n        self.perform_software_install_expect_success()\n\n\nclass SameSocketDownload:\n    class Test(test_suite.Lwm2mDtlsSingleServerTest, test_suite.Lwm2mDmOperations):\n        GARBAGE_SIZE = 2048\n        # Set to be able to comfortably test interleaved requests\n        ACK_TIMEOUT = 10\n        MAX_RETRANSMIT = 4\n        # Sometimes we want to allow the client to send more than one request at a time\n        # e.g. Update and GET.\n        NSTART = 1\n        LIFETIME = 86400\n        BINDING = 'U'\n        BLK_SZ = 1024\n\n        def setUp(self, *args, **kwargs):\n            if 'extra_cmdline_args' not in kwargs:\n                kwargs['extra_cmdline_args'] = []\n\n            self.ANJAY_PERSISTENCE_FILE = generate_temp_filename(\n                dir='/tmp', prefix='anjay-sw-persistence-')\n\n            kwargs['extra_cmdline_args'] += [\n                '--prefer-same-socket-downloads',\n                '--ack-timeout', str(self.ACK_TIMEOUT),\n                '--max-retransmit', str(self.MAX_RETRANSMIT),\n                '--ack-random-factor', str(1.0),\n                '--nstart', str(self.NSTART),\n                '--sw-mgmt-persistence-file', self.ANJAY_PERSISTENCE_FILE\n            ]\n            kwargs['lifetime'] = self.LIFETIME\n            super().setUp(*args, **kwargs)\n            self.file_server = CoapFileServer(\n                self.serv._coap_server, binding=self.BINDING)\n            self.file_server.set_resource(path=SOFTWARE_PATH,\n                                          data=make_software_package(b'a' * self.GARBAGE_SIZE))\n\n        def read_state(self, serv=None):\n            return int(self.read_resource(serv or self.serv,\n                                          oid=OID.SoftwareManagement,\n                                          iid=0,\n                                          rid=RID.SoftwareManagement.UpdateState).content)\n\n        def read_result(self, serv=None):\n            return int(self.read_resource(serv or self.serv,\n                                          oid=OID.SoftwareManagement,\n                                          iid=0,\n                                          rid=RID.SoftwareManagement.UpdateResult).content)\n\n        def start_download(self):\n            self.write_resource(self.serv,\n                                oid=OID.SoftwareManagement,\n                                iid=0,\n                                rid=RID.SoftwareManagement.PackageURI,\n                                content=self.file_server.get_resource_uri(SOFTWARE_PATH))\n\n        def handle_get(self, pkt=None):\n            if pkt is None:\n                pkt = self.serv.recv()\n            block2 = pkt.get_options(coap.Option.BLOCK2)\n            if block2:\n                self.assertEqual(block2[0].block_size(), self.BLK_SZ)\n            self.file_server.handle_recvd_request(pkt)\n\n        def num_blocks(self):\n            return (len(\n                self.file_server._resources[SOFTWARE_PATH].data) + self.BLK_SZ - 1) // self.BLK_SZ\n\n        def wait_for_delivered_state(self):\n            deadline = time.time() + 5\n            while time.time() < deadline:\n                time.sleep(0.5)\n\n                if self.read_state() == UpdateState.DELIVERED:\n                    return\n\n            self.fail('software still not downloaded')\n\n        def tearDown(self, *args, **kwargs):\n            super().tearDown(*args, **kwargs)\n\n\nclass SoftwareManagementDownloadSameSocket(SameSocketDownload.Test):\n    def runTest(self):\n        self.start_download()\n\n        for _ in range(self.num_blocks()):\n            pkt = self.serv.recv()\n            self.handle_get(pkt)\n\n        # wait until client downloads and verify the software\n        self.wait_for_delivered_state()\n\n        self.assertEqual(self.read_state(), UpdateState.DELIVERED)\n\n\nclass SoftwareManagementDownloadSameSocketAndOngoingBlockwiseWrite(\n        SameSocketDownload.Test):\n    def runTest(self):\n        self.create_instance(self.serv, oid=OID.Test, iid=1)\n        self.start_download()\n\n        resource_num_blocks = 10\n        self.assertGreaterEqual(resource_num_blocks, self.num_blocks())\n        for seq_num in range(resource_num_blocks):\n            if seq_num < self.num_blocks():\n                dl_req_get = self.serv.recv()\n\n            has_more = seq_num < resource_num_blocks - 1\n            # Server does blockwise write on the unrelated resource\n            pkt = Lwm2mWrite(ResPath.Test[1].ResRawBytes, b'x' * 16,\n                             format=coap.ContentFormat.APPLICATION_OCTET_STREAM,\n                             options=[coap.Option.BLOCK1(seq_num, has_more, 16)])\n            self.serv.send(pkt)\n            if has_more:\n                self.assertMsgEqual(\n                    Lwm2mContinue.matching(pkt)(), self.serv.recv())\n            else:\n                self.assertMsgEqual(\n                    Lwm2mChanged.matching(pkt)(), self.serv.recv())\n\n            if seq_num < self.num_blocks():\n                # Client waits for next chunk of Raw Bytes, but gets software\n                # block instead\n                self.handle_get(dl_req_get)\n\n        # wait until client downloads and verify the software\n        self.wait_for_delivered_state()\n\n        self.assertEqual(self.read_state(), UpdateState.DELIVERED)\n\n\nclass SoftwareManagementDownloadSameSocketAndOngoingBlockwiseRead(\n        SameSocketDownload.Test):\n    BYTES = 160\n\n    def runTest(self):\n        self.create_instance(self.serv, oid=OID.Test, iid=1)\n        self.write_resource(self.serv, oid=OID.Test, iid=1, rid=RID.Test.ResBytesSize,\n                            content=bytes(str(self.BYTES), 'ascii'))\n        self.start_download()\n\n        block_size = 16\n        self.assertGreaterEqual(self.BYTES // block_size, self.num_blocks())\n        for seq_num in range(self.BYTES // block_size):\n            if seq_num < self.num_blocks():\n                dl_req_get = self.serv.recv()\n\n            # Server does blockwise write on the unrelated resource\n            pkt = Lwm2mRead(ResPath.Test[1].ResBytes,\n                            accept=coap.ContentFormat.APPLICATION_OCTET_STREAM,\n                            options=[coap.Option.BLOCK2(seq_num, 0, block_size)])\n            self.serv.send(pkt)\n            res = self.serv.recv()\n            self.assertEqual(pkt.msg_id, res.msg_id)\n            self.assertTrue(len(res.get_options(coap.Option.BLOCK2)) > 0)\n\n            if seq_num < self.num_blocks():\n                # Client waits for next chunk of Raw Bytes, but gets software\n                # block instead\n                self.handle_get(dl_req_get)\n\n        # wait until client downloads and verify the software\n        self.wait_for_delivered_state()\n\n        self.assertEqual(self.read_state(), UpdateState.DELIVERED)\n\n\nclass SoftwareManagementDownloadSameSocketUpdateDuringDownloadNstart2(\n        SameSocketDownload.Test):\n    NSTART = 2\n\n    def runTest(self):\n        self.start_download()\n\n        for _ in range(self.num_blocks()):\n            dl_req_get = self.serv.recv()\n            # rather than responding to a request, force Update\n            self.communicate('send-update')\n            self.assertDemoUpdatesRegistration(timeout_s=5)\n            # and only then respond with next block\n            self.handle_get(dl_req_get)\n\n        # wait until client downloads and verify the software\n        self.wait_for_delivered_state()\n\n        self.assertEqual(self.read_state(), UpdateState.DELIVERED)\n\n\nclass SoftwareManagementDownloadSameSocketUpdateDuringDownloadNstart1(\n        SameSocketDownload.Test):\n    def runTest(self):\n        self.start_download()\n\n        for _ in range(self.num_blocks()):\n            dl_req_get = self.serv.recv()\n            # rather than responding to a request, force Update\n            self.communicate('send-update')\n            # the Update won't be received, because of NSTART=1\n            with self.assertRaises(socket.timeout):\n                self.serv.recv(timeout_s=3)\n            # so we respond to a block\n            self.handle_get(dl_req_get)\n            # and only then the Update arrives\n            self.assertDemoUpdatesRegistration(timeout_s=5)\n\n        # wait until client downloads and verify the software\n        self.wait_for_delivered_state()\n\n        self.assertEqual(self.read_state(), UpdateState.DELIVERED)\n\n\nclass SoftwareManagementSameSocketAndReconnectNstart1(SameSocketDownload.Test):\n    def runTest(self):\n        self.start_download()\n\n        for _ in range(self.num_blocks()):\n            # get dl request and ignore it\n            self.serv.recv()\n            # rather than responding to a request force reconnect\n            self.communicate('reconnect')\n            self.serv.reset()\n            # demo will resume DTLS session without sending any LwM2M messages\n            self.serv.listen()\n            # download request is retried\n            dl_req_get = self.serv.recv()\n            # and finally we respond to a block\n            self.handle_get(dl_req_get)\n\n        # wait until client downloads and verify the software\n        self.wait_for_delivered_state()\n\n        self.assertEqual(self.read_state(), UpdateState.DELIVERED)\n\n\nclass SoftwareManagementDownloadSameSocketUpdateTimeoutNstart2(SameSocketDownload.Test):\n    NSTART = 2\n    LIFETIME = 5\n    MAX_RETRANSMIT = 1\n    ACK_TIMEOUT = 2\n\n    def runTest(self):\n        time.sleep(self.LIFETIME + 1)\n        self.assertMsgEqual(Lwm2mUpdate(self.DEFAULT_REGISTER_ENDPOINT, content=b''),\n                            self.serv.recv())\n        self.assertMsgEqual(Lwm2mUpdate(self.DEFAULT_REGISTER_ENDPOINT, content=b''),\n                            self.serv.recv())\n        self.start_download()\n\n        dl_get0_0 = self.serv.recv(filter=CoapGet._pkt_matches)\n        dl_get0_1 = self.serv.recv(filter=CoapGet._pkt_matches)  # retry\n        self.handle_get(dl_get0_0)\n        self.assertEqual(dl_get0_0.msg_id, dl_get0_1.msg_id)\n        dl_get1_0 = self.serv.recv(filter=CoapGet._pkt_matches)\n\n        # lifetime expired, demo re-registers\n        self.assertDemoRegisters(lifetime=self.LIFETIME)\n\n        # this is a retransmission\n        dl_get1_1 = self.serv.recv()\n        self.assertEqual(dl_get1_0.msg_id, dl_get1_1.msg_id)\n        self.handle_get(dl_get1_1)\n        self.handle_get(self.serv.recv())\n\n        # wait until client downloads and verify the software\n        self.wait_for_delivered_state()\n\n        self.assertEqual(self.read_state(), UpdateState.DELIVERED)\n\n\nclass SoftwareManagementSameSocketUpdateTimeoutNstart1(SameSocketDownload.Test):\n    LIFETIME = 5\n    MAX_RETRANSMIT = 1\n    ACK_TIMEOUT = 2\n\n    def runTest(self):\n        time.sleep(self.LIFETIME + 1)\n        self.assertMsgEqual(Lwm2mUpdate(self.DEFAULT_REGISTER_ENDPOINT, content=b''),\n                            self.serv.recv())\n        self.assertMsgEqual(Lwm2mUpdate(self.DEFAULT_REGISTER_ENDPOINT, content=b''),\n                            self.serv.recv())\n        self.start_download()\n\n        # registration temporarily held due to ongoing download\n        self.handle_get(self.serv.recv())\n        # and only after handling the GET, it can be sent finally\n        self.assertDemoRegisters(lifetime=self.LIFETIME)\n        self.handle_get(self.serv.recv())\n        self.handle_get(self.serv.recv())\n\n        # wait until client downloads and verify the software\n        self.wait_for_delivered_state()\n\n        self.assertEqual(self.read_state(), UpdateState.DELIVERED)\n\n\nclass SoftwareManagementSameSocketDontCare(SameSocketDownload.Test):\n    def runTest(self):\n        self.start_download()\n        self.serv.recv()  # recv GET and ignore it\n\n\nclass SoftwareManagementSameSocketSuspendDueToOffline(SameSocketDownload.Test):\n    ACK_TIMEOUT = 1.5\n\n    def runTest(self):\n        self.start_download()\n        self.serv.recv()  # ignore first GET\n        self.communicate('enter-offline')\n        with self.assertRaises(socket.timeout):\n            self.serv.recv(timeout_s=2 * self.ACK_TIMEOUT)\n        self.serv.reset()\n        self.communicate('exit-offline')\n\n        # demo will resume DTLS session without sending any LwM2M messages\n        self.serv.listen()\n\n        for _ in range(self.num_blocks()):\n            self.handle_get(self.serv.recv())\n\n        # wait until client downloads and verify the software\n        self.wait_for_delivered_state()\n\n        self.assertEqual(self.read_state(), UpdateState.DELIVERED)\n\n\nclass SoftwareManagementSameSocketSuspendDueToOfflineDuringUpdate(\n        SameSocketDownload.Test):\n    ACK_TIMEOUT = 1.5\n\n    def runTest(self):\n        self.start_download()\n        self.serv.recv()  # ignore first GET\n        self.communicate('send-update', match_regex=re.escape('Update sent'))\n        # actual Update message will not arrive due to NSTART\n        with self.serv.fake_close():\n            self.communicate('enter-offline')\n            self.wait_until_socket_count(expected=0, timeout_s=5)\n        self.serv.reset()\n        self.communicate('exit-offline')\n        # demo will resume DTLS session before sending Register\n        self.serv.listen()\n        self.assertDemoUpdatesRegistration()\n\n        for _ in range(self.num_blocks()):\n            self.handle_get(self.serv.recv())\n\n        # wait until client downloads and verify the software\n        self.wait_for_delivered_state()\n\n        self.assertEqual(self.read_state(), UpdateState.DELIVERED)\n\n\nclass SoftwareManagementSameSocketSuspendDueToOfflineDuringUpdateNoMessagesCheck(\n        SameSocketDownload.Test):\n    ACK_TIMEOUT = 1.5\n\n    def runTest(self):\n        # Note: This test is almost identical to the one above, but does not close the socket\n        # during the offline period. This is to check that the client does not attempt to send any\n        # packets during that time. With the bug that triggered the addition of these test cases,\n        # these were two distinct code flow paths.\n\n        self.start_download()\n        self.serv.recv()  # ignore first GET\n        self.communicate('send-update', match_regex=re.escape('Update sent'))\n        # actual Update message will not arrive due to NSTART\n        self.communicate('enter-offline')\n        with self.assertRaises(socket.timeout):\n            self.serv.recv(timeout_s=2 * self.ACK_TIMEOUT)\n        self.serv.reset()\n        self.communicate('exit-offline')\n        # demo will resume DTLS session before sending Update\n        self.serv.listen()\n        self.assertDemoUpdatesRegistration()\n\n        for _ in range(self.num_blocks()):\n            self.handle_get(self.serv.recv())\n\n        # wait until client downloads and verify the software\n        self.wait_for_delivered_state()\n\n        self.assertEqual(self.read_state(), UpdateState.DELIVERED)\n\n\nclass SoftwareManagementDownloadSameSocketAndBootstrap(SameSocketDownload.Test):\n    def setUp(self):\n        super().setUp(bootstrap_server=True)\n\n    def tearDown(self):\n        super().tearDown(deregister_servers=[self.new_server])\n\n    def runTest(self):\n        self.start_download()\n        self.serv.recv()  # recv GET and ignore it\n\n        self.execute_resource(self.serv,\n                              oid=OID.Server,\n                              iid=2,\n                              rid=RID.Server.RequestBootstrapTrigger)\n\n        self.assertDemoRequestsBootstrap()\n\n        self.new_server = Lwm2mServer()\n        self.write_resource(self.bootstrap_server,\n                            oid=OID.Security,\n                            iid=2,\n                            rid=RID.Security.ServerURI,\n                            content=bytes('coap://127.0.0.1:%d' % self.new_server.get_listen_port(),\n                                          'ascii'))\n        self.write_resource(self.bootstrap_server,\n                            oid=OID.Security,\n                            iid=2,\n                            rid=RID.Security.Mode,\n                            content=str(coap.server.SecurityMode.NoSec.value).encode())\n        req = Lwm2mBootstrapFinish()\n        self.bootstrap_server.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.bootstrap_server.recv())\n\n        self.assertDemoRegisters(self.new_server)\n        self.assertEqual(self.read_state(self.new_server), UpdateState.INITIAL)\n\n\nclass SoftwareManagementHttpRequestTimeoutTest(SoftwareManagement.TestWithPartialDownload,\n                                               SoftwareManagement.TestWithHttpServer):\n    CHUNK_SIZE = 500\n    RESPONSE_DELAY = 0.5\n    TCP_REQUEST_TIMEOUT = 5\n\n    def setUp(self):\n        super().setUp(extra_cmdline_args=['--sw-mgmt-tcp-request-timeout',\n                                          str(self.TCP_REQUEST_TIMEOUT)])\n\n    def runTest(self):\n        self.provide_response()\n\n        # Write /9/0/3 (Package URI)\n        self.write_software_uri_expect_success(self.get_software_uri())\n\n        self.wait_for_half_download()\n        # Change RESPONSE_DELAY so that the server stops responding\n        self.RESPONSE_DELAY = self.TCP_REQUEST_TIMEOUT + 5\n\n        half_download_time = time.time()\n        self.wait_until_state_is(\n            UpdateState.INITIAL, timeout_s=self.TCP_REQUEST_TIMEOUT + 5)\n        fail_time = time.time()\n        self.assertEqual(self.read_update_result(),\n                         UpdateResult.CONNECTION_LOST)\n\n        self.assertAlmostEqual(\n            fail_time, half_download_time + self.TCP_REQUEST_TIMEOUT, delta=1.5)\n\n\nclass SoftwareManagementHttpRequestTimeoutTest20sec(SoftwareManagementHttpRequestTimeoutTest):\n    TCP_REQUEST_TIMEOUT = 20\n"
  },
  {
    "path": "tests/integration/suites/default/ssl_error_api.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nfrom framework.lwm2m_test import *\n\nimport re\nimport os\nimport enum\n\n\nclass SSLAlertDescription(enum.IntEnum):\n    CLOSE_NOTIFY = 0\n    UNEXPECTED_MESSAGE = 10\n    BAD_RECORD_MAC = 20\n    DECRYPTION_FAILED_RESERVED = 21\n    RECORD_OVERFLOW = 22\n    DECOMPRESSION_FAILURE_RESERVED = 30\n    expect_handshake_failURE = 40\n    NO_CERTIFICATE_RESERVED = 41\n    BAD_CERTIFICATE = 42\n    UNSUPPORTED_CERTIFICATE = 43\n    CERTIFICATE_REVOKED = 44\n    CERTIFICATE_EXPIRED = 45\n    CERTIFICATE_UNKNOWN = 46\n    ILLEGAL_PARAMETER = 47\n    UNKNOWN_CA = 48\n    ACCESS_DENIED = 49\n    DECODE_ERROR = 50\n    DECRYPT_ERROR = 51\n    EXPORT_RESTRICTION_RESERVED = 60\n    PROTOCOL_VERSION = 70\n    INSUFFICIENT_SECURITY = 71\n    INTERNAL_ERROR = 80\n    INAPPROPRIATE_FALLBACK = 86\n    USER_CANCELED = 90\n    NO_RENEGOTIATION_RESERVED = 100\n    MISSING_EXTENSION = 109\n    UNSUPPORTED_EXTENSION = 110\n    CERTIFICATE_UNOBTAINABLE_RESERVED = 111\n    UNRECOGNIZED_NAME = 112\n    BAD_CERTIFICATE_STATUS_RESPONSE = 113\n    BAD_CERTIFICATE_HASH_VALUE_RESERVED = 114\n    UNKNOWN_PSK_IDENTITY = 115\n    CERTIFICATE_REQUIRED = 116\n    NO_APPLICATION_PROTOCOL = 120\n    UNKNOWN = 255\n\n\nclass SSLErrorCategory(enum.IntEnum):\n    SSL_ALERT = 8572\n    SSL_LIB_ERROR = 8573\n\nclass SSLErrorAPITest():\n    class SSLErrorAPISingleServerTest(test_suite.Lwm2mSingleServerTest):\n        SSL_ERROR_REGEX = re.compile(\n        rb'SSL error from server with SSID=(\\d+): category=(\\d+), code=(\\d+)\\n')\n\n        # Keep a dedicated offset so checks in this file do not consume lines\n        # expected by other read_log_until_match() users.\n        log_alt_offset = 0\n\n        def _assertSslError(self, expected_ssl_errors, timeout=5):\n            self.log_alt_offset, match = self.read_log_until_match(\n                self.SSL_ERROR_REGEX,\n                timeout_s=timeout,\n                alt_offset=self.log_alt_offset)\n            self.assertIsNotNone(match)\n            _actual_ssid = int(match.group(1))\n            actual_category = int(match.group(2))\n            if actual_category == SSLErrorCategory.SSL_ALERT:\n                actual_code = int(match.group(3)) & 0xFF\n            else:\n                actual_code = int(match.group(3))\n\n            self.assertIn((actual_category, actual_code), expected_ssl_errors)\n\n            return actual_category, actual_code\n\n        def _cert_file(self, filename):\n            return os.path.join(os.path.dirname(self.config.demo_path), 'certs', filename)\n\n        def setUp(self, server_crt_file=None, server_key_file=None,\n                 client_cert_on_server_file=None, client_crt_file=None,\n                 client_key_file=None, server_crt_on_client_file=None,\n                 *args, **kwargs):\n            self.skipIfFeatureStatus('ANJAY_WITH_SSL_ERROR_API = OFF',\n                                    'SSL error API disabled')\n\n            extra_cmdline_args = []\n            extra_cmdline_args += ['--certificate-usage', '3']\n            extra_cmdline_args += ['--dtls-hs-retry-wait-max', '3']\n\n\n            if server_crt_file is not None:\n                self.server_crt_file = self._cert_file(server_crt_file)\n            if server_key_file is not None:\n                self.server_key_file = self._cert_file(server_key_file)\n            if client_cert_on_server_file is not None:\n                self.client_cert_on_server_file = self._cert_file(client_cert_on_server_file)\n            if (client_crt_file is not None):\n                self.client_crt_file = self._cert_file(client_crt_file)\n                extra_cmdline_args += ['-C', self.client_crt_file]\n            if (client_key_file is not None):\n                self.client_key_file = self._cert_file(client_key_file)\n                extra_cmdline_args += ['-K', self.client_key_file]\n            if (server_crt_on_client_file is not None):\n                self.server_crt_on_client_file = self._cert_file(server_crt_on_client_file)\n                extra_cmdline_args += ['-P', self.server_crt_on_client_file]\n\n            super().setUp(\n                servers=[Lwm2mServer(\n                    coap.DtlsServer(ca_file=getattr(self, 'client_cert_on_server_file', None),\n                                    crt_file=getattr(self, 'server_crt_file', None),\n                                    key_file=getattr(self, 'server_key_file', None)))],\n                extra_cmdline_args=extra_cmdline_args, auto_register=False, *args, **kwargs)\n\n        def runTest(self, expected_ssl_errors, expect_handshake_fail=True):\n            if expect_handshake_fail:\n                with self.assertRaisesRegex(RuntimeError,\n                                            'mbedtls_ssl_handshake failed:'):\n                    self.assertDemoRegisters()\n\n            self._assertSslError(expected_ssl_errors)\n\n        def tearDown(self):\n            # No need to deregister - handshake should fail and the client should not register\n            super().tearDown(auto_deregister=False)\n\n\nclass SSLErrorOnRegisterWithMismatchedServerPublicKey(SSLErrorAPITest.SSLErrorAPISingleServerTest):\n    def setUp(self):\n        super().setUp(server_crt_file='self-signed/server.crt',\n                      server_key_file='self-signed/server.key',\n                      client_cert_on_server_file='self-signed/client.crt.der',\n                      client_crt_file='self-signed/client.crt.der',\n                      client_key_file='self-signed/client.key.der',\n                      server_crt_on_client_file='server.crt.der')\n\n    def runTest(self):\n        # MBEDTLS_ERR_X509_CERT_VERIFY_FAILED\n        super().runTest([(SSLErrorCategory.SSL_LIB_ERROR, 9984)])\n\n\nclass SSLErrorOnRegisterWithMismatchedClientPublicKey(SSLErrorAPITest.SSLErrorAPISingleServerTest):\n    def setUp(self):\n        super().setUp(server_crt_file='self-signed/server.crt',\n                      server_key_file='self-signed/server.key',\n                      client_cert_on_server_file='client.crt.der',\n                      client_crt_file='self-signed/client.crt.der',\n                      client_key_file='self-signed/client.key.der',\n                      server_crt_on_client_file='self-signed/server.crt.der')\n\n    def runTest(self):\n        super().runTest([(SSLErrorCategory.SSL_ALERT,\n                          SSLAlertDescription.UNKNOWN_CA)])\n\n\nclass SSLErrorOnRegisterWithWrongClientKey(SSLErrorAPITest.SSLErrorAPISingleServerTest):\n    def setUp(self):\n        super().setUp(server_crt_file='self-signed/server.crt',\n                      server_key_file='self-signed/server.key',\n                      client_cert_on_server_file='self-signed/client.crt.der',\n                      client_crt_file='self-signed/client.crt.der',\n                      client_key_file='client.key.der',\n                      server_crt_on_client_file='self-signed/server.crt.der')\n\n    def runTest(self):\n        # MBEDTLS_ERR_ECP_VERIFY_FAILED or MBEDTLS_ERR_ECP_BAD_INPUT_DATA,\n        # depending on verification path\n        # handshake not attempted due to client key mismatch\n        super().runTest([(SSLErrorCategory.SSL_LIB_ERROR, 19968),\n                         (SSLErrorCategory.SSL_LIB_ERROR, 20352)],\n                        expect_handshake_fail=False)\n\n\nclass SSLErrorOnRegisterWithWrongServerKey(SSLErrorAPITest.SSLErrorAPISingleServerTest):\n    def setUp(self):\n        super().setUp(server_crt_file='self-signed/server.crt',\n                      server_key_file='server.key',\n                      client_cert_on_server_file='self-signed/client.crt.der',\n                      client_crt_file='self-signed/client.crt.der',\n                      client_key_file='self-signed/client.key.der',\n                      server_crt_on_client_file='self-signed/server.crt.der')\n\n    def runTest(self):\n        # MBEDTLS_ERR_ECP_VERIFY_FAILED\n        super().runTest([(SSLErrorCategory.SSL_LIB_ERROR, 19968)])\n\n\nclass SSLErrorOnRegisterWithWrongClientKeyAndCert(SSLErrorAPITest.SSLErrorAPISingleServerTest):\n    def setUp(self):\n        super().setUp(server_crt_file='self-signed/server.crt',\n                      server_key_file='self-signed/server.key',\n                      client_cert_on_server_file='self-signed/client.crt',\n                      client_crt_file='client.crt.der',\n                      client_key_file='client.key.der',\n                      server_crt_on_client_file='self-signed/server.crt.der')\n\n    def runTest(self):\n        # MBEDTLS_ERR_ECP_BAD_INPUT_DATA\n        super().runTest([(SSLErrorCategory.SSL_ALERT,\n                          SSLAlertDescription.UNKNOWN_CA)])\n\n\nclass SSLErrorOnRegisterWithWrongServerKeyAndCert(SSLErrorAPITest.SSLErrorAPISingleServerTest):\n    def setUp(self):\n        super().setUp(server_crt_file='server.crt',\n                      server_key_file='server.key',\n                      client_cert_on_server_file='self-signed/client.crt',\n                      client_crt_file='self-signed/client.crt.der',\n                      client_key_file='self-signed/client.key.der',\n                      server_crt_on_client_file='self-signed/server.crt.der')\n\n    def runTest(self):\n        # MBEDTLS_ERR_X509_CERT_VERIFY_FAILED\n        super().runTest([(SSLErrorCategory.SSL_LIB_ERROR, 9984)])\n"
  },
  {
    "path": "tests/integration/suites/default/stats.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nfrom framework_tools.lwm2m import messages\nfrom framework.lwm2m_test import test_suite\nfrom framework_tools.lwm2m.coap import Type\nfrom framework.test_utils import OID, RID\n\n\nclass StatsTest:\n    class Test(test_suite.Lwm2mSingleServerTest, test_suite.Lwm2mDmOperations):\n        @staticmethod\n        def get_coap_packet_size(pkt):\n            return len(pkt.serialize())\n\n        @staticmethod\n        def get_coap_content(pkt):\n            return pkt.content.decode()\n\n        def lwm2m_read_int_resource(self, oid, iid, rid):\n            return int(self.get_coap_content(self.read_resource(self.serv, oid, iid, rid)))\n\n\nclass BytesReceived(StatsTest.Test):\n    PATH = (OID.ExtDevInfo, 0, RID.ExtDevInfo.RxBytes)\n\n    def runTest(self):\n        req = messages.Lwm2mRead('/%d/%d/%d' % self.PATH).fill_placeholders()\n        req_packet_size = self.get_coap_packet_size(req)\n        expected_res = self._make_expected_res(req, messages.Lwm2mContent, expect_error_code=None)\n\n        old_value = int(self.get_coap_content(self._perform_action(self.serv, req, expected_res)))\n        new_value = self.lwm2m_read_int_resource(*self.PATH)\n        value_diff = new_value - old_value\n        self.assertEqual(req_packet_size, value_diff)\n\n\nclass BytesSent(StatsTest.Test):\n    PATH = (OID.ExtDevInfo, 0, RID.ExtDevInfo.TxBytes)\n\n    def runTest(self):\n        response = self.read_resource(self.serv, *self.PATH)\n        response_packet_size = self.get_coap_packet_size(response)\n        old_value = int(self.get_coap_content(response))\n        new_value = self.lwm2m_read_int_resource(*self.PATH)\n        value_diff = new_value - old_value\n        self.assertEqual(response_packet_size, value_diff)\n\n\nclass IncomingRetransmissions(StatsTest.Test):\n    PATH = (OID.ExtDevInfo, 0, RID.ExtDevInfo.NumIncomingRetransmissions)\n\n    def setUp(self):\n        super().setUp(extra_cmdline_args=['--cache-size', '1000'])\n\n    def runTest(self):\n        req = messages.Lwm2mRead('/%d/%d/%d' % self.PATH)\n        # Send same message twice\n        self.serv.send(req)\n        self.assertMsgEqual(messages.Lwm2mContent.matching(req)(), self.serv.recv())\n        self.serv.send(req)\n        self.assertMsgEqual(messages.Lwm2mContent.matching(req)(), self.serv.recv())\n\n        num_incoming_retransmissions_res = self.lwm2m_read_int_resource(*self.PATH)\n        self.assertEqual(1, num_incoming_retransmissions_res)\n\n\nclass OutgoingRetransmissions(StatsTest.Test):\n    PATH = (OID.ExtDevInfo, 0, RID.ExtDevInfo.NumOutgoingRetransmissions)\n\n    def setUp(self):\n        super().setUp(extra_cmdline_args=['--confirmable-notifications'])\n\n    def runTest(self):\n        SOME_RESOURCE_PATH = (OID.Device, 0, RID.Device.Manufacturer)\n        self.write_attributes(self.serv, *SOME_RESOURCE_PATH, query=['pmax=1'])\n        self.observe(self.serv, OID.Device, 0, RID.Device.Manufacturer)\n\n        ignored_notify = self.serv.recv()\n        self.assertIsInstance(ignored_notify, messages.Lwm2mNotify)\n\n        second_notify = self.serv.recv()\n        self.assertIsInstance(second_notify, messages.Lwm2mNotify)\n        ack_for_second_notify = messages.Lwm2mEmpty().matching(second_notify)(type=Type.RESET)\n        self.serv.send(ack_for_second_notify)\n\n        num_outgoing_retransmissions_res = self.lwm2m_read_int_resource(*self.PATH)\n        self.assertEqual(1, num_outgoing_retransmissions_res)\n"
  },
  {
    "path": "tests/integration/suites/default/test_object.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\nimport json\nimport time\n\nfrom framework_tools.lwm2m.tlv import TLVType\nfrom framework.lwm2m_test import *\n\n\nclass TestObject:\n    class TestCase(test_suite.Lwm2mSingleServerTest):\n        def setUp(self, *args, **kwargs):\n            super().setUp(*args, **kwargs)\n\n            req = Lwm2mCreate('/%d' % (OID.Test,))\n            self.serv.send(req)\n            self.assertMsgEqual(Lwm2mCreated.matching(req)(),\n                                self.serv.recv())\n\n\nclass TimestampTest(TestObject.TestCase):\n    def runTest(self):\n        req = Lwm2mRead(ResPath.Test[0].Counter)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mContent.matching(req)(),\n                            self.serv.recv())\n\n\nclass CounterTest(TestObject.TestCase):\n    def runTest(self):\n        # ensure the counter is zero initially\n        req = Lwm2mRead(ResPath.Test[0].Counter, accept=coap.ContentFormat.TEXT_PLAIN)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mContent.matching(req)(content=b'0'),\n                            self.serv.recv())\n\n        # execute Increment Counter\n        req = Lwm2mExecute(ResPath.Test[0].IncrementCounter)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n\n        # counter should be incremented by the execute\n        req = Lwm2mRead(ResPath.Test[0].Counter, accept=coap.ContentFormat.TEXT_PLAIN)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mContent.matching(req)(content=b'1'),\n                            self.serv.recv())\n\n\nclass NonconfirmableExecuteTest(TestObject.TestCase):\n    def runTest(self):\n        # ensure the counter is zero initially\n        req = Lwm2mRead(ResPath.Test[0].Counter, accept=coap.ContentFormat.TEXT_PLAIN)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mContent.matching(req)(content=b'0'),\n                            self.serv.recv())\n\n        # execute Increment Counter\n        req = Lwm2mMsg(type=coap.Type.NON_CONFIRMABLE, code=coap.Code.REQ_POST, msg_id=ANY,\n                       token=ANY, options=uri_path_to_options(ResPath.Test[0].IncrementCounter))\n        self.serv.send(req)\n        # It was a request, and the client will send a response even if it's non-confirmable\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n\n        # counter should be incremented by the execute\n        req = Lwm2mRead(ResPath.Test[0].Counter, accept=coap.ContentFormat.TEXT_PLAIN)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mContent.matching(req)(content=b'1'),\n                            self.serv.recv())\n\n\nclass IntegerArrayTest(TestObject.TestCase):\n    def runTest(self):\n        # ensure the array is empty\n        req = Lwm2mRead(ResPath.Test[0].IntArray)\n        self.serv.send(req)\n\n        empty_array_tlv = TLV.make_multires(resource_id=RID.Test.IntArray, instances=[])\n        self.assertMsgEqual(Lwm2mContent.matching(req)(content=empty_array_tlv.serialize()),\n                            self.serv.recv())\n\n        # write something\n        array_tlv = TLV.make_multires(\n            resource_id=RID.Test.IntArray,\n            instances=[\n                # (1, (0).to_bytes(0, byteorder='big')),\n                (2, (12).to_bytes(1, byteorder='big')),\n                (4, (1234).to_bytes(2, byteorder='big')),\n            ])\n        req = Lwm2mWrite(ResPath.Test[0].IntArray,\n                         array_tlv.serialize(),\n                         format=coap.ContentFormat.APPLICATION_LWM2M_TLV)\n        self.serv.send(req)\n\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n\n        # check updated content\n        req = Lwm2mRead(ResPath.Test[0].IntArray)\n        self.serv.send(req)\n\n        self.assertMsgEqual(Lwm2mContent.matching(req)(content=array_tlv.serialize(),\n                                                       format=coap.ContentFormat.APPLICATION_LWM2M_TLV),\n                            self.serv.recv())\n\n\nclass AttemptToWriteSingleAsMultipleTest(TestObject.TestCase):\n    def runTest(self):\n        req = Lwm2mWrite(ResPath.Test[0].ResInt,\n                         TLV.make_instance(0, [TLV.make_multires(12, [(0, 42)])]).serialize(),\n                         format=coap.ContentFormat.APPLICATION_LWM2M_TLV)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mErrorResponse.matching(req)(coap.Code.RES_BAD_REQUEST),\n                            self.serv.recv())\n\n\nclass ExecArgsArrayTest(TestObject.TestCase):\n    def runTest(self):\n        args = [\n            (7, None),\n            (1, b''),\n            (2, b'1'),\n            (3, b'12345'),\n            (9, b'0' * 512)  # keep this value small, to avoid triggering blockwise transfers\n        ]\n\n        req = Lwm2mRead(ResPath.Test[0].LastExecArgsArray)\n        self.serv.send(req)\n\n        empty_array_tlv = TLV.make_multires(resource_id=RID.Test.LastExecArgsArray, instances=[])\n        self.assertMsgEqual(Lwm2mContent.matching(req)(content=empty_array_tlv.serialize()),\n                            self.serv.recv())\n\n        # perform the Execute with arguments\n        exec_content = b','.join(b'%d' % k if v is None else b\"%d='%s'\" % (k, v)\n                                 for k, v in args)\n        req = Lwm2mExecute(ResPath.Test[0].IncrementCounter, content=exec_content)\n        self.serv.send(req)\n\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n\n        # Execute args should now be saved in the Execute Arguments array\n        req = Lwm2mRead(ResPath.Test[0].LastExecArgsArray)\n        self.serv.send(req)\n\n        exec_args_tlv = TLV.make_multires(resource_id=RID.Test.LastExecArgsArray,\n                                          instances=sorted(\n                                              dict((k, v or b'') for k, v in args).items()))\n        self.assertMsgEqual(Lwm2mContent.matching(req)(content=exec_args_tlv.serialize()),\n                            self.serv.recv())\n\n\nclass ExecArgsDuplicateTest(TestObject.TestCase):\n    def runTest(self):\n        args = [\n            (1, None),\n            (1, b''),\n            (2, b'1'),\n            (3, b'12345'),\n            (9, b'0' * 1024)\n        ]\n\n        # perform the Execute with arguments\n        exec_content = b','.join(b'%d' % k if v is None else b\"%d='%s'\" % (k, v)\n                                 for k, v in args)\n        req = Lwm2mExecute(ResPath.Test[0].IncrementCounter, content=exec_content)\n        self.serv.send(req)\n\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n\n        # Execute Arguments array will now be unreadable due to duplicate Resource Instance IDs\n        req = Lwm2mRead(ResPath.Test[0].LastExecArgsArray)\n        self.serv.send(req)\n\n        self.assertMsgEqual(\n            Lwm2mErrorResponse.matching(req)(code=coap.Code.RES_INTERNAL_SERVER_ERROR),\n            self.serv.recv())\n\n\nclass EmptyBytesTest(TestObject.TestCase, test_suite.Lwm2mDmOperations):\n    def runTest(self):\n        response = self.read_path(self.serv, ResPath.Test[0].ResBytes)\n        self.assertEqual(response.get_content_format(), coap.ContentFormat.TEXT_PLAIN)\n        self.assertEqual(response.content, b'')\n\n        response = self.read_path(self.serv, ResPath.Test[0].ResBytes,\n                                  accept=coap.ContentFormat.TEXT_PLAIN)\n        self.assertEqual(response.get_content_format(), coap.ContentFormat.TEXT_PLAIN)\n        self.assertEqual(response.content, b'')\n\n        response = self.read_path(self.serv, ResPath.Test[0].ResBytes,\n                                  accept=coap.ContentFormat.APPLICATION_OCTET_STREAM)\n        self.assertEqual(response.get_content_format(), coap.ContentFormat.APPLICATION_OCTET_STREAM)\n        self.assertEqual(response.content, b'')\n\n        response = self.read_instance(self.serv, OID.Test, 0,\n                                      accept=coap.ContentFormat.APPLICATION_LWM2M_TLV)\n        self.assertEqual(response.get_content_format(), coap.ContentFormat.APPLICATION_LWM2M_TLV)\n        tlv = TLV.parse(response.content)\n        self.assertEqual(tlv[0].tlv_type, TLVType.RESOURCE)\n        self.assertEqual(tlv[0].identifier, RID.Test.Timestamp)\n        expected_rest = [\n            TLV.make_resource(RID.Test.Counter, 0),\n            TLV.make_multires(RID.Test.IntArray, {}),\n            TLV.make_multires(RID.Test.LastExecArgsArray, {}),\n            TLV.make_resource(RID.Test.ResBytes, b''),\n            TLV.make_resource(RID.Test.ResBytesSize, 0),\n            TLV.make_resource(RID.Test.ResBytesBurst, 1000),\n            TLV.make_resource(RID.Test.ResRawBytes, b''),\n            TLV.make_multires(RID.Test.ResOpaqueArray, {}),\n            TLV.make_resource(RID.Test.ResInt, 0),\n            TLV.make_resource(RID.Test.ResBool, 0),\n            TLV.make_resource(RID.Test.ResFloat, 0.0),\n            TLV.make_resource(RID.Test.ResString, ''),\n            TLV.make_resource(RID.Test.ResObjlnk, b'\\0\\0\\0\\0'),\n            TLV.make_resource(RID.Test.ResBytesZeroBegin, 1),\n            TLV.make_resource(RID.Test.ResDouble, 0.0),\n            TLV.make_resource(RID.Test.ResUnsignedInt, 0),\n            TLV.make_resource(RID.Test.ResUnsignedLong, 0),\n            TLV.make_multires(RID.Test.BoolArray, {}),\n        ]\n        self.assertEqual(len(tlv), len(expected_rest) + 1)\n        for i in range(len(expected_rest)):\n            self.assertEqual(tlv[i + 1], expected_rest[i])\n\n        response = self.read_instance(self.serv, OID.Test, 0,\n                                      accept=coap.ContentFormat.APPLICATION_LWM2M_JSON)\n        self.assertEqual(response.get_content_format(), coap.ContentFormat.APPLICATION_LWM2M_JSON)\n        js = json.loads(response.content.decode('utf-8'))\n        self.assertEqual(len(js), 2)\n        self.assertEqual(js['bn'], '/%d/0' % (OID.Test,))\n        e = js['e']\n        self.assertEqual(len(e[0]), 2)\n        self.assertEqual(e[0]['n'], '/%d' % (RID.Test.Timestamp,))\n        self.assertIsInstance(e[0]['v'], int)\n        expected_rest = [\n            {'n': '/%d' % (RID.Test.Counter,), 'v': 0},\n            {'n': '/%d' % (RID.Test.ResBytes,), 'sv': ''},\n            {'n': '/%d' % (RID.Test.ResBytesSize,), 'v': 0},\n            {'n': '/%d' % (RID.Test.ResBytesBurst,), 'v': 1000},\n            {'n': '/%d' % (RID.Test.ResRawBytes,), 'sv': ''},\n            {'n': '/%d' % (RID.Test.ResInt,), 'v': 0},\n            {'n': '/%d' % (RID.Test.ResBool,), 'bv': False},\n            {'n': '/%d' % (RID.Test.ResFloat,), 'v': 0.0},\n            {'n': '/%d' % (RID.Test.ResString,), 'sv': ''},\n            {'n': '/%s' % (RID.Test.ResObjlnk,), 'ov': '0:0'},\n            {'n': '/%s' % (RID.Test.ResBytesZeroBegin,), 'bv': True},\n            {'n': '/%s' % (RID.Test.ResDouble,), 'v': 0.0},\n            {'n': '/%s' % (RID.Test.ResUnsignedInt,), 'v': 0},\n            {'n': '/%s' % (RID.Test.ResUnsignedLong,), 'v': 0},\n        ]\n        self.assertEqual(len(e), len(expected_rest) + 1)\n        for i in range(len(expected_rest)):\n            self.assertEqual(e[i + 1], expected_rest[i])\n\n\nclass UpdateResourceInstanceTest(TestObject.TestCase):\n    def runTest(self):\n        # ensure the array is empty\n        req = Lwm2mRead(ResPath.Test[0].IntArray)\n        self.serv.send(req)\n\n        empty_array_tlv = TLV.make_multires(resource_id=RID.Test.IntArray, instances=[])\n        self.assertMsgEqual(Lwm2mContent.matching(req)(content=empty_array_tlv.serialize()),\n                            self.serv.recv())\n\n        # write something\n        array_tlv = TLV.make_multires(\n            resource_id=RID.Test.IntArray,\n            instances=[\n                (2, (12).to_bytes(1, byteorder='big')),\n                (4, (97).to_bytes(1, byteorder='big')),\n            ])\n        req = Lwm2mWrite(ResPath.Test[0].IntArray,\n                         array_tlv.serialize(),\n                         format=coap.ContentFormat.APPLICATION_LWM2M_TLV)\n        self.serv.send(req)\n\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n\n        # check updated content\n        req = Lwm2mRead(ResPath.Test[0].IntArray)\n        self.serv.send(req)\n\n        self.assertMsgEqual(Lwm2mContent.matching(req)(content=array_tlv.serialize(),\n                                                       format=coap.ContentFormat.APPLICATION_LWM2M_TLV),\n                            self.serv.recv())\n\n        # update one of resource instances\n        to_update_array_tlv = TLV.make_multires(\n            resource_id=RID.Test.IntArray,\n            instances=[\n                (4, (65).to_bytes(1, byteorder='big'))\n            ])\n        req = Lwm2mWrite('/%d/0' % (OID.Test,),\n                         to_update_array_tlv.serialize(),\n                         format=coap.ContentFormat.APPLICATION_LWM2M_TLV,\n                         update=True)\n        self.serv.send(req)\n\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n\n        # check updated content\n        updated_array_tlv = TLV.make_multires(\n            resource_id=RID.Test.IntArray,\n            instances=[\n                (2, (12).to_bytes(1, byteorder='big')),\n                (4, (65).to_bytes(1, byteorder='big')),\n            ])\n\n        req = Lwm2mRead(ResPath.Test[0].IntArray)\n        self.serv.send(req)\n\n        self.assertMsgEqual(Lwm2mContent.matching(req)(content=updated_array_tlv.serialize(),\n                                                       format=coap.ContentFormat.APPLICATION_LWM2M_TLV),\n                            self.serv.recv())\n\n\nclass WriteNonexistent(TestObject.TestCase):\n    def runTest(self):\n        # The Test object contains a Timestamp resource, so sleep to the beginning of a second to\n        # maximize the probability that we won't cross that between the two reads\n        time.sleep(1.0 - (time.time() % 1.0))\n\n        # Read the state of the object\n        req = Lwm2mRead('/%d' % (OID.Test,))\n        self.serv.send(req)\n        read_response1 = self.serv.recv()\n        self.assertMsgEqual(Lwm2mContent.matching(req)(), read_response1)\n\n        # Write on an Object Instance path with a\n        req = Lwm2mWrite('/%d/%d' % (OID.Test, 0), TLV.make_resource(213, b'7').serialize(),\n                         format=coap.ContentFormat.APPLICATION_LWM2M_TLV)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n\n        # Read the object again and assert that it hasn't changed\n        req = Lwm2mRead('/%d' % (OID.Test,))\n        self.serv.send(req)\n        read_response2 = self.serv.recv()\n        self.assertMsgEqual(Lwm2mContent.matching(req)(), read_response2)\n\n        self.assertEqual(read_response1.content, read_response2.content)\n"
  },
  {
    "path": "tests/integration/suites/default/time_api.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nimport re\n\nfrom framework.lwm2m_test import *\n\n\nclass TimeAPIChecks:\n    class Test(test_suite.Lwm2mTest):\n        def setUp(self):\n            self.setup_demo_with_servers(servers=1)\n\n        def validTime(self, time):\n            r = re.match('([0-9]+)\\.([0-9]+)', time)\n            return r is not None\n\n        # Expects the log to be in format\n        # [NAME_OF_TIMESTAMP]=[point in time according to the real-time clock]\n        def readTimeFromLog(self, cmd, strTimestampName):\n            reg_exp = strTimestampName + '=([0-9]+\\.[0-9]+)\\n'\n            log_line = self.communicate(cmd, 5, reg_exp)\n            self.assertIsNotNone(log_line)\n            self.assertTrue(self.validTime(log_line.group(1)))\n            return float(log_line.group(1))\n\n        def readAllTimestampsFromLog(self, ssid=None):\n            cmd_suffix = ''\n            if ssid is not None:\n                cmd_suffix = ' ' + str(ssid)\n\n            reg_time = self.readTimeFromLog(\n                'last-registration-time' + cmd_suffix, 'LAST_REGISTRATION_TIME')\n            next_update_time = self.readTimeFromLog(\n                'next-update-time' + cmd_suffix, 'NEXT_UPDATE_TIME')\n            last_comm_time = self.readTimeFromLog(\n                'last-communication-time' + cmd_suffix, 'LAST_COMMUNICATION_TIME')\n\n            return reg_time, next_update_time, last_comm_time\n\n\nclass RegistrationTimeNotChanged(TimeAPIChecks.Test):\n    def runTest(self):\n        reg_time_1, next_update_time_1, last_comm_time_1 = self.readAllTimestampsFromLog()\n\n        # this won't cause an update to be sent\n        self.communicate('advance-time 3600')\n        time.sleep(5)\n\n        # the returned times should stay the same\n        reg_time_2, next_update_time_2, last_comm_time_2 = self.readAllTimestampsFromLog()\n\n        self.assertEqual(reg_time_1, reg_time_2)\n        self.assertAlmostEqual(next_update_time_1, next_update_time_2, delta=0.005)\n        self.assertEqual(last_comm_time_1, last_comm_time_2)\n\n\nclass RegistrationTimeAfterUpdate(TimeAPIChecks.Test):\n    def runTest(self):\n        self.servers[0].set_timeout(timeout_s=1)\n\n        reg_time_1, next_update_time_1, last_comm_time_1 = self.readAllTimestampsFromLog()\n\n        self.communicate('advance-time 3600')\n        self.communicate('send-update')  # force update\n        pkt = self.servers[0].recv()\n        self.assertMsgEqual(Lwm2mUpdate(self.DEFAULT_REGISTER_ENDPOINT,\n                                        query=[],\n                                        content=b''),\n                            pkt)\n\n        self.servers[0].send(Lwm2mChanged.matching(pkt)())\n\n        reg_time_2, next_update_time_2, last_comm_time_2 = self.readAllTimestampsFromLog()\n\n        self.assertLess(reg_time_1, reg_time_2)\n        self.assertLess(next_update_time_1, next_update_time_2)\n        self.assertLess(last_comm_time_1, last_comm_time_2)\n\n\nclass RegistrationTimeMultipleServers(TimeAPIChecks.Test):\n    def setUp(self):\n        self.setup_demo_with_servers(servers=2)\n\n    def runTest(self):\n        self.coap_ping(self.servers[0])\n        self.coap_ping(self.servers[1])\n\n        reg_time, next_update_time, last_comm_time = self.readAllTimestampsFromLog()\n        srv_1_reg_time, srv_1_next_update_time, srv_1_last_comm_time = self.readAllTimestampsFromLog(\n            1)\n        srv_2_reg_time, srv_2_next_update_time, srv_2_last_comm_time = self.readAllTimestampsFromLog(\n            2)\n\n        self.assertEqual(reg_time, srv_2_reg_time)\n        self.assertAlmostEqual(next_update_time, srv_1_next_update_time, delta=0.005)\n        self.assertEqual(last_comm_time, srv_2_last_comm_time)\n\n        # check if srv_1 and srv_2 times are different and that we are not returing\n        # the same value\n        self.assertNotEqual(srv_1_reg_time, srv_2_reg_time)\n        self.assertNotEqual(srv_1_next_update_time, srv_2_next_update_time)\n        self.assertNotEqual(srv_1_last_comm_time, srv_2_last_comm_time)\n\n\nclass RegistrationTimeAfterUpdateMultipleServers(TimeAPIChecks.Test):\n    def setUp(self):\n        self.setup_demo_with_servers(servers=2)\n\n    def runTest(self):\n        self.servers[0].set_timeout(timeout_s=1)\n\n        srv_1_reg_time_old, srv_1_next_update_time_old, srv_1_last_comm_time_old = self.readAllTimestampsFromLog(\n            1)\n        srv_2_reg_time_old, srv_2_next_update_time_old, srv_2_last_comm_time_old = self.readAllTimestampsFromLog(\n            2)\n\n        self.communicate('advance-time 3600')\n        self.communicate('send-update 1')  # update only server 1\n        pkt = self.servers[0].recv()\n        self.assertMsgEqual(Lwm2mUpdate(self.DEFAULT_REGISTER_ENDPOINT,\n                                        query=[],\n                                        content=b''),\n                            pkt)\n\n        self.servers[0].send(Lwm2mChanged.matching(pkt)())\n\n        srv_1_reg_time_new, srv_1_next_update_time_new, srv_1_last_comm_time_new = self.readAllTimestampsFromLog(\n            1)\n        srv_2_reg_time_new, srv_2_next_update_time_new, srv_2_last_comm_time_new = self.readAllTimestampsFromLog(\n            2)\n\n        # times for server 1 should be updated for server 2 should stay the same\n        self.assertLess(srv_1_reg_time_old, srv_1_reg_time_new)\n        self.assertLess(srv_1_next_update_time_old, srv_1_next_update_time_new)\n        self.assertLess(srv_1_last_comm_time_old, srv_1_last_comm_time_new)\n\n        self.assertEqual(srv_2_reg_time_old, srv_2_reg_time_new)\n        self.assertAlmostEqual(srv_2_next_update_time_old, srv_2_next_update_time_new, delta=0.005)\n        self.assertEqual(srv_2_last_comm_time_old, srv_2_last_comm_time_new)\n"
  },
  {
    "path": "tests/integration/suites/default/unregister.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nimport re\n\nfrom framework.lwm2m_test import *\n\n\ndef object_set_from_payload(payload):\n    return set(int(re.match(b'^</(\\d+)[/>]', elem).group(1)) for elem in payload.split(b','))\n\ndef unregister_test(oid):\n    class UnregisterTest(test_suite.Lwm2mSingleServerTest):\n        def setUp(self):\n            super().setUp(auto_register=False)\n            pkt = self.serv.recv()\n            expected = Lwm2mRegister('/rd?lwm2m=1.0&ep=%s&lt=%d' % (DEMO_ENDPOINT_NAME, 86400))\n            self.assertMsgEqual(expected, pkt)\n            self.serv.send(\n                Lwm2mCreated(location=self.DEFAULT_REGISTER_ENDPOINT, msg_id=pkt.msg_id, token=pkt.token))\n            self.initial_objects = object_set_from_payload(pkt.content)\n\n        def runTest(self):\n            self.communicate('unregister-object %d' % oid)\n            pkt = self.serv.recv()\n            self.assertMsgEqual(Lwm2mUpdate(self.DEFAULT_REGISTER_ENDPOINT), pkt)\n            current_objects = object_set_from_payload(pkt.content)\n\n            self.assertEqual(self.initial_objects - {oid}, current_objects)\n\n            self.serv.send(Lwm2mChanged.matching(pkt)())\n\n    return UnregisterTest\n\n\nclass UnregisterDevice(unregister_test(OID.Device)): pass\n\n\nclass UnregisterConnectivityMonitoring(unregister_test(OID.ConnectivityMonitoring)): pass\n\n\nclass UnregisterLocation(unregister_test(OID.Location)): pass\n\n\nclass UnregisterConnectivityStatistics(unregister_test(OID.ConnectivityStatistics)): pass\n\n\nclass UnregisterCellConnectivity(unregister_test(OID.CellularConnectivity)): pass\n\n\nclass UnregisterApnConnectionProfile(unregister_test(OID.ApnConnectionProfile)): pass\n\n\nclass UnregisterTest(unregister_test(OID.Test)): pass\n\n\nclass UnregisterExtDevInfo(unregister_test(OID.ExtDevInfo)): pass\n\n\nclass UnregisterIpPing(unregister_test(OID.IpPing)): pass\n\n\nclass UnregisterGeopoints(unregister_test(OID.GeoPoints)): pass\n\n\nclass UnregisterDownloadDiagnostics(unregister_test(OID.DownloadDiagnostics)): pass\n"
  },
  {
    "path": "tests/integration/suites/default/update.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nfrom framework.lwm2m_test import *\n\n\nclass UpdateTest(test_suite.Lwm2mSingleServerTest):\n    def runTest(self):\n        # should send a correct Update\n        self.communicate('send-update')\n        pkt = self.serv.recv()\n        self.assertMsgEqual(Lwm2mUpdate(self.DEFAULT_REGISTER_ENDPOINT, query=[], content=b''), pkt)\n\n        self.serv.send(Lwm2mChanged.matching(pkt)())\n\n        # should not send more messages after receiving a correct response\n        with self.assertRaises(socket.timeout, msg='unexpected message'):\n            print(self.serv.recv(timeout_s=6))\n\n        # should automatically send Updates before lifetime expires\n        LIFETIME = 2\n\n        self.serv.send(Lwm2mWrite(ResPath.Server[1].Lifetime, str(LIFETIME)))\n        pkt = self.serv.recv()\n\n        self.assertMsgEqual(Lwm2mChanged.matching(pkt)(), pkt)\n        self.assertDemoUpdatesRegistration(lifetime=LIFETIME)\n\n        # wait for auto-scheduled Update\n        self.assertDemoUpdatesRegistration(timeout_s=LIFETIME)\n\n\nclass UpdateServerDownReconnectTest(test_suite.PcapEnabledTest, test_suite.Lwm2mSingleServerTest):\n    def runTest(self):\n        # respond with Port Unreachable to the next packet\n        with self.serv.fake_close():\n            self.communicate('send-update')\n            self.wait_until_icmp_unreachable_count(1, timeout_s=10)\n\n        # client should abort\n        with self.assertRaises(socket.timeout):\n            self.serv.recv(timeout_s=5)\n        self.assertEqual(self.get_socket_count(), 0)\n\n    def tearDown(self):\n        super().tearDown(auto_deregister=False)\n        self.assertEqual(self.count_icmp_unreachable_packets(), 1)\n\n\nclass ReconnectTest(test_suite.Lwm2mDtlsSingleServerTest):\n    def runTest(self):\n        self.communicate('reconnect')\n\n        # server is connected, so only a packet from the same remote port will pass this assertion\n        self.assertDtlsReconnect()\n\n\nclass UpdateFallbacksToRegisterAfterLifetimeExpiresTest(test_suite.Lwm2mSingleServerTest):\n    LIFETIME = 4\n\n    def setUp(self):\n        super().setUp(auto_register=False, lifetime=self.LIFETIME,\n                      extra_cmdline_args=['--ack-random-factor', '1', '--ack-timeout', '1',\n                                          '--max-retransmit', '1'])\n        self.assertDemoRegisters(lifetime=self.LIFETIME)\n\n    def runTest(self):\n        self.assertDemoUpdatesRegistration(timeout_s=self.LIFETIME / 2 + 1)\n\n        self.assertDemoUpdatesRegistration(timeout_s=self.LIFETIME / 2 + 1, respond=False)\n        self.assertDemoUpdatesRegistration(timeout_s=self.LIFETIME / 2 + 1, respond=False)\n        self.assertDemoRegisters(lifetime=self.LIFETIME, timeout_s=self.LIFETIME / 2 + 1)\n\n\nclass UpdateFallbacksToRegisterAfterCoapClientErrorResponse(test_suite.Lwm2mSingleServerTest):\n    def runTest(self):\n        def check(code: coap.Code):\n            self.communicate('send-update')\n\n            req = self.serv.recv()\n            self.assertMsgEqual(Lwm2mUpdate(self.DEFAULT_REGISTER_ENDPOINT, content=b''), req)\n            self.serv.send(Lwm2mErrorResponse.matching(req)(code))\n\n            self.assertDemoRegisters()\n\n        # check all possible client (4.xx) errors\n        for detail in range(32):\n            if detail == 13:\n                # TODO: do not ignore Request Entity Too Large (T2171)\n                continue\n            check(coap.Code(4, detail))\n\n\nclass ReconnectFailsWithCoapErrorCodeTest(test_suite.Lwm2mSingleServerTest):\n    def tearDown(self):\n        super().tearDown(auto_deregister=False)\n\n    def runTest(self):\n        # should send an Update with reconnect\n        self.communicate('reconnect')\n        self.serv.reset()\n\n        pkt = self.serv.recv()\n        self.assertMsgEqual(Lwm2mRegister('/rd?lwm2m=1.0&ep=%s&lt=86400' % (DEMO_ENDPOINT_NAME,)),\n                            pkt)\n        self.serv.send(Lwm2mErrorResponse.matching(pkt)(code=coap.Code.RES_INTERNAL_SERVER_ERROR))\n\n        # client should abort\n        with self.assertRaises(socket.timeout):\n            self.serv.recv(timeout_s=10)\n        self.assertEqual(self.get_socket_count(), 0)\n\n\nclass ReconnectFailsWithConnectionRefusedTest(test_suite.Lwm2mDtlsSingleServerTest,\n                                              test_suite.Lwm2mDmOperations):\n    def tearDown(self):\n        super().tearDown(auto_deregister=False)\n\n    def runTest(self):\n        # should try to resume DTLS session\n        with self.serv.fake_close():\n            self.communicate('reconnect')\n\n            # give the process some time to fail\n            time.sleep(1)\n\n        # client should abort\n        with self.assertRaises(socket.timeout):\n            self.serv.recv(timeout_s=5)\n        self.assertEqual(self.get_socket_count(), 0)\n\n\nclass ConcurrentRequestWhileWaitingForResponse(test_suite.Lwm2mSingleServerTest,\n                                               test_suite.Lwm2mDmOperations):\n    def runTest(self):\n        self.communicate('send-update')\n\n        pkt = self.serv.recv()\n        self.assertMsgEqual(Lwm2mUpdate(self.DEFAULT_REGISTER_ENDPOINT, query=[], content=b''), pkt)\n\n        self.read_path(self.serv, ResPath.Device.Manufacturer)\n\n        self.serv.send(Lwm2mChanged.matching(pkt)())\n\n\nclass UpdateAfterLifetimeChange(test_suite.Lwm2mSingleServerTest):\n    def runTest(self):\n        req = Lwm2mWrite(ResPath.Server[1].Lifetime, b'5')\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.serv.recv())\n\n        self.assertDemoUpdatesRegistration(lifetime=5)\n        # Next update should be there shortly\n        self.assertDemoUpdatesRegistration(timeout_s=5)\n\n        req = Lwm2mWrite(ResPath.Server[1].Lifetime, b'86400')\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.serv.recv())\n        self.assertDemoUpdatesRegistration(lifetime=86400)\n\n\nclass NoUpdateDuringShutdownTest(test_suite.Lwm2mSingleServerTest):\n    def runTest(self):\n        self.communicate(\n            'schedule-update-on-exit')  # tearDown() expects a De-Register operation and will fail on  # unexpected Update\n\n\nclass ExternalSetLifetimeForcesUpdate(test_suite.Lwm2mSingleServerTest):\n    def runTest(self):\n        self.communicate('set-lifetime 1 9001')\n        self.assertDemoUpdatesRegistration(lifetime=9001)\n\n\nclass ExternalSetLifetimeForcesUpdateOnlyIfChanged(test_suite.Lwm2mSingleServerTest):\n    def runTest(self):\n        # default lifetime is 86400 or so, so we should not have any updates\n        self.communicate('set-lifetime 1 86400')\n        with self.assertRaises(socket.timeout):\n            self.serv.recv(timeout_s=3)\n\n\nclass UpdateImmediatelyDisabledTest(test_suite.Lwm2mSingleServerTest, test_suite.Lwm2mDmOperations):\n    def runTest(self):\n        self.create_instance(self.serv, OID.Test, iid=1)\n        # no Update message expected in this case\n        with self.assertRaises(socket.timeout):\n            self.serv.recv(timeout_s=3)\n\n\nclass UpdateImmediatelyEnabledTest(test_suite.Lwm2mSingleServerTest, test_suite.Lwm2mDmOperations):\n    def setUp(self):\n        super().setUp(extra_cmdline_args=['--update-immediately-on-dm-change'])\n\n    def runTest(self):\n        self.create_instance(self.serv, OID.Test, iid=42)\n        # Update message shall be automatically generated\n        pkt = self.assertDemoUpdatesRegistration(self.serv, content=ANY)\n        self.assertIn(f'</{OID.Test}/42>'.encode(), pkt.content)\n"
  },
  {
    "path": "tests/integration/suites/default/uri_change_reregister.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nfrom framework.lwm2m_test import *\n\n\nclass UriChangeReregisterTest(test_suite.Lwm2mTest):\n    def setUp(self):\n        self.setup_demo_with_servers(servers=2,\n                                     num_servers_passed=1,\n                                     bootstrap_server=True,\n                                     auto_register=False)\n\n    def tearDown(self):\n        self.teardown_demo_with_servers(deregister_servers=[self.servers[1]])\n\n    def runTest(self):\n        regular_serv1_uri = 'coap://127.0.0.1:%d' % self.servers[0].get_listen_port()\n        regular_serv2_uri = 'coap://127.0.0.1:%d' % self.servers[1].get_listen_port()\n\n        # Register to regular_serv1\n        pkt = self.servers[0].recv()\n        self.assertMsgEqual(\n            Lwm2mRegister('/rd?lwm2m=1.0&ep=%s&lt=86400' % (DEMO_ENDPOINT_NAME,)),\n            pkt)\n        self.servers[0].send(Lwm2mCreated.matching(pkt)(location='/rd/demo'))\n\n        self.assertEqual(2, self.get_socket_count())\n\n        # modify the server URI\n        demo_port = self.get_demo_port()\n        self.bootstrap_server.connect_to_client(('127.0.0.1', demo_port))\n\n        req = Lwm2mWrite(ResPath.Security[2].ServerURI, regular_serv2_uri)\n        self.bootstrap_server.send(req)\n\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.bootstrap_server.recv())\n\n        # send Bootstrap Finish - trigger notifications\n        req = Lwm2mBootstrapFinish()\n        self.bootstrap_server.send(req)\n\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.bootstrap_server.recv())\n\n        # we should now get a Register on the new URL\n        self.assertDemoRegisters(self.servers[1])\n"
  },
  {
    "path": "tests/integration/suites/default/write_composite.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\nimport base64\nimport json\n\nfrom framework.lwm2m_test import *\nfrom framework.test_utils import *\n\nIID = 1\n\n\nclass Test:\n    class WriteComposite(test_suite.Lwm2mSingleServerTest,\n                         test_suite.Lwm2mDmOperations):\n        def resource_path(self, rid, riid=None):\n            if riid is not None:\n                return '/%d/%d/%d/%d' % (OID.Test, IID, rid, riid)\n            else:\n                return '/%d/%d/%d' % (OID.Test, IID, rid)\n\n        def setUp(self, maximum_version='1.1'):\n            super().setUp(maximum_version=maximum_version)\n            self.create_instance(self.serv, oid=OID.Test, iid=IID)\n\n    class WriteCompositeNull(WriteComposite):\n        def setUp(self):\n            super().setUp(maximum_version='1.2')\n\n            array_tlv = TLV.make_multires(\n                resource_id=RID.Test.IntArray,\n                instances=[\n                    # (1, (0).to_bytes(0, byteorder='big')),\n                    (2, (12).to_bytes(1, byteorder='big')),\n                    (4, (1234).to_bytes(2, byteorder='big')),\n                ])\n            req = Lwm2mWrite(ResPath.Test[IID].IntArray,\n                             array_tlv.serialize(),\n                             format=coap.ContentFormat.APPLICATION_LWM2M_TLV)\n            self.serv.send(req)\n\n            self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                                self.serv.recv())\n\n\nclass WriteCompositeCborTypical(Test.WriteComposite):\n    def runTest(self):\n        request = [\n            {\n                SenmlLabel.NAME: ResPath.Test[IID].ResInt,\n                SenmlLabel.VALUE: 42\n            },\n            {\n                SenmlLabel.NAME: ResPath.Test[IID].ResString,\n                SenmlLabel.STRING: 'test'\n            },\n            {\n                SenmlLabel.NAME: ResPath.Device.UTCOffset,\n                SenmlLabel.STRING: 'offset'\n            }\n        ]\n\n        self.write_composite(self.serv, content=CBOR.serialize(request))\n        res = self.read_composite(self.serv, paths=[entry[SenmlLabel.NAME] for entry in request])\n        self.assertEqual(CBOR.parse(res.content), request)\n\n\nclass WriteCompositeJsonTypical(Test.WriteComposite):\n    def runTest(self):\n        request = [\n            {\n                'n': ResPath.Test[IID].ResInt,\n                'v': 42\n            },\n            {\n                'n': ResPath.Test[IID].ResString,\n                'vs': 'test'\n            },\n            {\n                'n': ResPath.Device.UTCOffset,\n                'vs': 'offset'\n            }\n        ]\n\n        self.write_composite(self.serv, content=json.dumps(request),\n                             format=coap.ContentFormat.APPLICATION_LWM2M_SENML_JSON)\n        res = self.read_composite(self.serv, paths=[entry['n'] for entry in request],\n                                  accept=coap.ContentFormat.APPLICATION_LWM2M_SENML_JSON)\n        self.assertEqual(json.loads(res.content.decode()), request)\n\n\nclass WriteCompositeNonexistingResourceInstance(Test.WriteComposite):\n    def runTest(self):\n        request = [\n            {\n                'n': self.resource_path(RID.Test.IntArray, 0),\n                'v': 12\n            },\n            {\n                'n': self.resource_path(RID.Test.IntArray, 1),\n                'v': 73\n            }\n        ]\n\n        self.write_composite(self.serv, content=json.dumps(request),\n                             format=coap.ContentFormat.APPLICATION_LWM2M_SENML_JSON)\n        res = self.read_composite(self.serv, paths=[entry['n'] for entry in request],\n                                  accept=coap.ContentFormat.APPLICATION_LWM2M_SENML_JSON)\n        self.assertEqual(json.loads(res.content.decode()), [\n            {\n                'bn': self.resource_path(RID.Test.IntArray),\n                'n': '/%d' % (0,),\n                'v': 12\n            },\n            {\n                'n': '/%d' % (1,),\n                'v': 73\n            }\n        ])\n\n\nclass WriteCompositeBasenameOnly(Test.WriteComposite):\n    def runTest(self):\n        self.create_instance(self.serv, oid=OID.Test, iid=IID + 1)\n        self.create_instance(self.serv, oid=OID.Test, iid=IID + 2)\n        request = [\n            {\n                'vd': base64.encodebytes(b'test').strip().rstrip(b'=').decode(),\n                'bn': '/%d/%d/%d' % (OID.Test, IID, RID.Test.ResRawBytes)\n            },\n            {\n                'vd': base64.encodebytes(b'hurrdurr').strip().rstrip(b'=').decode(),\n                'bn': '/%d/%d/%d' % (OID.Test, IID + 1, RID.Test.ResRawBytes)\n            },\n            {\n                'vd': base64.encodebytes(b'herpderp').strip().rstrip(b'=').decode(),\n                'bn': '/%d/%d/%d' % (OID.Test, IID + 2, RID.Test.ResRawBytes)\n            }\n        ]\n\n        self.write_composite(self.serv, content=json.dumps(request),\n                             format=coap.ContentFormat.APPLICATION_LWM2M_SENML_JSON)\n        res = self.read_composite(self.serv, paths=[entry['bn'] for entry in request],\n                                  accept=coap.ContentFormat.APPLICATION_LWM2M_SENML_JSON)\n        self.assertEqual(json.loads(res.content.decode()), [\n            {\n                'bn': f'/{OID.Test}',\n                'n': f'/{IID}/{RID.Test.ResRawBytes}',\n                'vd': request[0]['vd']\n            },\n            {\n                'n': f'/{IID + 1}/{RID.Test.ResRawBytes}',\n                'vd': request[1]['vd']\n            },\n            {\n                'n': f'/{IID + 2}/{RID.Test.ResRawBytes}',\n                'vd': request[2]['vd']\n            }\n        ])\n\n\nclass WriteCompositeUnsupportedContentFromat(Test.WriteComposite):\n    def runTest(self):\n        self.write_composite(self.serv, content='nothing special',\n                             format=coap.ContentFormat.TEXT_PLAIN,\n                             expect_error_code=coap.Code.RES_UNSUPPORTED_CONTENT_FORMAT)\n\n\nclass WriteCompositeCborNull(Test.WriteCompositeNull):\n    def runTest(self):\n        request = [\n            {\n                SenmlLabel.NAME: ResPath.Test[IID].ResInt,\n                SenmlLabel.VALUE: 42\n            },\n            {\n                # nonexistent instance\n                SenmlLabel.NAME: ResPath.Test[IID].IntArray + '/1',\n                SenmlLabel.VALUE: None\n            },\n            {\n                SenmlLabel.NAME: ResPath.Test[IID].IntArray + '/2',\n                SenmlLabel.VALUE: None\n            },\n            {\n                SenmlLabel.NAME: ResPath.Test[IID].ResString,\n                SenmlLabel.STRING: 'test'\n            }\n        ]\n\n        self.write_composite(self.serv, content=CBOR.serialize(request),\n                             format=coap.ContentFormat.APPLICATION_LWM2M_SENML_ETCH_CBOR)\n        res = self.read_composite(self.serv,\n                                  paths=[ResPath.Test[IID].ResInt, ResPath.Test[IID].IntArray,\n                                         ResPath.Test[IID].ResString],\n                                  accept=coap.ContentFormat.APPLICATION_LWM2M_SENML_CBOR)\n        response = CBOR.parse(res.content)\n\n        expected_response = [\n            {\n                SenmlLabel.BASE_NAME: '/%d/%d' % (OID.Test, IID),\n                SenmlLabel.NAME: '/%d' % (RID.Test.ResInt,),\n                SenmlLabel.VALUE: 42\n            },\n            {\n                SenmlLabel.NAME: '/%d/%d' % (RID.Test.IntArray, 4),\n                SenmlLabel.VALUE: 1234\n            },\n            {\n                SenmlLabel.NAME: '/%d' % (RID.Test.ResString,),\n                SenmlLabel.STRING: 'test'\n            }\n        ]\n        self.assertEqual(response, expected_response)\n\n\nclass WriteCompositeJsonNull(Test.WriteCompositeNull):\n    def runTest(self):\n        request = [\n            {\n                'n': ResPath.Test[IID].ResInt,\n                'v': 42\n            },\n            {\n                # nonexistent instance\n                'n': ResPath.Test[IID].IntArray + '/1',\n                'v': None\n            },\n            {\n                'n': ResPath.Test[IID].IntArray + '/2',\n                'v': None\n            },\n            {\n                'n': ResPath.Test[IID].ResString,\n                'vs': 'test'\n            }\n        ]\n\n        self.write_composite(self.serv, content=json.dumps(request),\n                             format=coap.ContentFormat.APPLICATION_LWM2M_SENML_ETCH_JSON)\n        res = self.read_composite(self.serv,\n                                  paths=[ResPath.Test[IID].ResInt, ResPath.Test[IID].IntArray,\n                                         ResPath.Test[IID].ResString],\n                                  accept=coap.ContentFormat.APPLICATION_LWM2M_SENML_JSON)\n        response = json.loads(res.content.decode())\n\n        expected_response = [\n            {\n                'bn': '/%d/%d' % (OID.Test, IID),\n                'n': '/%d' % (RID.Test.ResInt,),\n                'v': 42\n            },\n            {\n                'n': '/%d/%d' % (RID.Test.IntArray, 4),\n                'v': 1234\n            },\n            {\n                'n': '/%d' % (RID.Test.ResString,),\n                'vs': 'test'\n            }\n        ]\n        self.assertEqual(response, expected_response)\n"
  },
  {
    "path": "tests/integration/suites/sensitive/__init__.py",
    "content": "# -*- coding: utf-8 -*-\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n"
  },
  {
    "path": "tests/integration/suites/sensitive/advanced_firmware_update.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nimport os\nimport re\n\nfrom framework.lwm2m_test import *\nfrom framework.create_package import PackageForcedError\n\nfrom suites.default.advanced_firmware_update import AdvancedFirmwareUpdate, equal_chunk_splitter\nfrom suites.default.advanced_firmware_update import UpdateResult, UpdateState, Instances\n\n\nclass AdvancedFirmwareUpdateWithoutReboot(AdvancedFirmwareUpdate.BlockTest):\n    def runTest(self):\n        with open(os.path.join(self.config.demo_path, self.config.demo_cmd), 'rb') as f:\n            firmware = f.read()\n\n        # Write /33629/0/0 (Package)\n        self.block_send(firmware,\n                        equal_chunk_splitter(chunk_size=1024),\n                        force_error=PackageForcedError.Firmware.DoNothing)\n\n        # Execute /33629/0/2 (Update)\n        req = Lwm2mExecute(ResPath.AdvancedFirmwareUpdate[Instances.APP].Update)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n\n        # Wait until internal state machine is updated\n        # We cannot rely on FirmwareUpdate.State resource because it is updated first\n        # and the user code is only notified later, via a scheduler job\n        self.read_log_until_match(regex=re.escape(b'*** FIRMWARE UPDATE:'), timeout_s=5)\n\n        self.assertEqual(\n            self.read_path(self.serv, ResPath.AdvancedFirmwareUpdate[Instances.APP].State).content.decode(),\n            str(UpdateState.UPDATING))\n\n        self.communicate('set-afu-result ' + str(UpdateResult.SUCCESS))\n\n        self.assertEqual(\n            self.read_path(self.serv, ResPath.AdvancedFirmwareUpdate[Instances.APP].State).content.decode(),\n            str(UpdateState.IDLE))\n        self.assertEqual(self.read_path(self.serv, ResPath.AdvancedFirmwareUpdate[Instances.APP].UpdateResult).content,\n                         str(UpdateResult.SUCCESS).encode())\n"
  },
  {
    "path": "tests/integration/suites/sensitive/bootstrap_client.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\n\nfrom framework_tools.lwm2m.coap.server import SecurityMode\nfrom framework.lwm2m_test import *\nfrom framework import test_suite\n\nfrom suites.default.bootstrap_client import BootstrapTest\n\n\nclass BootstrapIncorrectData(BootstrapTest.Test):\n    PSK_IDENTITY = b'test-identity'\n    PSK_KEY = b'test-key'\n\n    def setUp(self, **kwargs):\n        super().setUp(servers=[Lwm2mServer(coap.DtlsServer(psk_key=self.PSK_KEY, psk_identity=self.PSK_IDENTITY))],\n                      num_servers_passed=0, **kwargs)\n\n    def tearDown(self):\n        super().tearDown(auto_deregister=False)\n\n    def test_bootstrap_backoff(self, num_attempts):\n        # NOTE: mbed TLS (hence, the demo client) sends Client Key Exchange, Change Cipher Spec and Encrypted Handshake\n        # Message as three separate UDP packets, but as three send() calls without any recv() attempts between them.\n        # On the server side (i.e., in this test code), the fatal Alert (\"Unknown PSK identity\" in this case) is sent\n        # after receiving Client Key Exchange. Depending on the timing of processing on both endpoints, it may be the\n        # case that the Alert is sent by the server before the Change Cipher Spec and Encrypted Handshake are even\n        # generated by the client. In that case, on the server we'd get \"handshake failed\" exception TWICE - once due to\n        # the actual error, and then second time - because mbed TLS will attempt to interpret the Change Cipher Spec\n        # datagram as a \"malformed Client Hello\". So we need to somehow discard the packets on the server side. We\n        # cannot \"fake-close\" the socket after failed handshake, as that causes ICMP unreachable packets to be\n        # generated, which in turn causes the client to restart the handshake. So we instead do this convoluted flow of\n        # calling reset() just before Bootstrap Finish, so that we're absolutely sure that all leftover messages are\n        # discarded just before we get the new Client Hello.\n\n        holdoff_s = 0\n        last_time = time.time()\n        for attempt in range(num_attempts):\n            # Create Security Object instance with deliberately wrong keys\n            self.perform_typical_bootstrap(server_iid=1,\n                                           security_iid=2,\n                                           server_uri='coaps://127.0.0.1:%d' % self.serv.get_listen_port(),\n                                           secure_identity=self.PSK_IDENTITY + b'hurr',\n                                           secure_key=self.PSK_KEY + b'durr',\n                                           security_mode=SecurityMode.PreSharedKey,\n                                           finish=False,\n                                           holdoff_s=max(last_time + holdoff_s - time.time(), 0))\n            last_time = time.time()\n\n            self.serv.reset()\n            self.perform_bootstrap_finish()\n            with self.assertRaisesRegex(RuntimeError, 'handshake failed'):\n                self.serv.recv()\n\n            holdoff_s = min(max(2 * holdoff_s, 3), 20)\n\n    def runTest(self):\n        self.test_bootstrap_backoff(3)\n\n        # now bootstrap the right keys\n        self.perform_typical_bootstrap(server_iid=1,\n                                       security_iid=2,\n                                       server_uri='coaps://127.0.0.1:%d' % self.serv.get_listen_port(),\n                                       secure_identity=self.PSK_IDENTITY,\n                                       secure_key=self.PSK_KEY,\n                                       security_mode=SecurityMode.PreSharedKey,\n                                       finish=False,\n                                       holdoff_s=12)\n\n        self.serv.reset()\n        self.perform_bootstrap_finish()\n        self.assertDemoRegisters()\n\n        # Trigger update\n        self.communicate('send-update')\n        update_pkt = self.assertDemoUpdatesRegistration(\n            self.serv, respond=False)\n        # Respond to it with 4.00 Bad Request to simulate some kind of client account expiration on server side.\n        self.serv.send(Lwm2mErrorResponse.matching(\n            update_pkt)(code=coap.Code.RES_BAD_REQUEST))\n        # This should cause client attempt to re-register.\n        register_pkt = self.assertDemoRegisters(self.serv, respond=False)\n        # To which we respond with 4.03 Forbidden, finishing off the communication.\n        self.serv.send(Lwm2mErrorResponse.matching(\n            register_pkt)(code=coap.Code.RES_FORBIDDEN))\n\n        # check that bootstrap backoff is restarted\n        self.test_bootstrap_backoff(2)\n\n\nclass ClientInitiatedBootstrapOnlyWithIncorrectData(BootstrapIncorrectData):\n    def setUp(self):\n        super().setUp(legacy_server_initiated_bootstrap_allowed=False)\n"
  },
  {
    "path": "tests/integration/suites/sensitive/firmware_update.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nimport os\nimport re\nimport socket\n\nfrom framework_tools.lwm2m.coap.server import SecurityMode\nfrom framework.lwm2m_test import *\nfrom framework import test_suite\nfrom framework.create_package import PackageForcedError\n\nfrom suites.default.block_write import Block, equal_chunk_splitter\nfrom suites.default.firmware_update import UpdateResult, UpdateState\n\n\nclass FirmwareUpdateWithoutReboot(Block.Test):\n    def runTest(self):\n        with open(os.path.join(self.config.demo_path, self.config.demo_cmd), 'rb') as f:\n            firmware = f.read()\n\n        # Write /5/0/0 (Firmware)\n        self.block_send(firmware,\n                        equal_chunk_splitter(chunk_size=1024),\n                        force_error=PackageForcedError.Firmware.DoNothing)\n\n        # Execute /5/0/2 (Update)\n        self.perform_firmware_update_expect_success()\n\n        # Wait until internal state machine is updated\n        # We cannot rely on FirmwareUpdate.State resource because it is updated first\n        # and the user code is only notified later, via a scheduler job\n        self.read_log_until_match(regex=re.escape(b'*** FIRMWARE UPDATE:'), timeout_s=5)\n\n        self.assertEqual(self.read_path(self.serv, ResPath.FirmwareUpdate.State).content.decode(),\n                         str(UpdateState.UPDATING))\n\n        self.communicate('set-fw-update-result ' + str(UpdateResult.SUCCESS))\n\n        self.assertEqual(self.read_path(self.serv, ResPath.FirmwareUpdate.State).content.decode(),\n                         str(UpdateState.IDLE))\n        self.assertEqual(self.read_path(self.serv, ResPath.FirmwareUpdate.UpdateResult).content,\n                         str(UpdateResult.SUCCESS).encode())\n"
  },
  {
    "path": "tests/integration/suites/sensitive/update.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nimport os\nimport socket\n\nfrom framework_tools.lwm2m.coap.server import SecurityMode\nfrom framework.lwm2m_test import *\nfrom framework import test_suite\n\n\nclass ReconnectBootstrapTest(test_suite.Lwm2mSingleServerTest):\n    def setUp(self):\n        self.setup_demo_with_servers(servers=0, bootstrap_server=True)\n\n    def runTest(self):\n        pkt = self.bootstrap_server.recv()\n        self.assertMsgEqual(Lwm2mRequestBootstrap(endpoint_name=DEMO_ENDPOINT_NAME),\n                            pkt)\n        self.bootstrap_server.send(Lwm2mChanged.matching(pkt)())\n\n        original_remote_addr = self.bootstrap_server.get_remote_addr()\n\n        # reconnect\n        self.communicate('reconnect')\n        self.bootstrap_server.reset()\n        pkt = self.bootstrap_server.recv()\n\n        # should retain remote port after reconnecting\n        self.assertEqual(original_remote_addr,\n                         self.bootstrap_server.get_remote_addr())\n        self.assertMsgEqual(Lwm2mRequestBootstrap(endpoint_name=DEMO_ENDPOINT_NAME),\n                            pkt)\n\n        self.bootstrap_server.send(Lwm2mChanged.matching(pkt)())\n\n        demo_port = self.get_demo_port()\n        self.assertEqual(self.bootstrap_server.get_remote_addr()[1], demo_port)\n\n        # send Bootstrap Finish\n        req = Lwm2mBootstrapFinish()\n        self.bootstrap_server.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.bootstrap_server.recv())\n\n        # reconnect once again\n        self.communicate('reconnect')\n\n        # now there should be no Bootstrap Request\n        with self.assertRaises(socket.timeout):\n            print(self.bootstrap_server.recv(timeout_s=3))\n\n        # should retain remote port after reconnecting\n        new_demo_port = self.get_demo_port()\n        self.assertEqual(demo_port, new_demo_port)\n\n        self.bootstrap_server.connect_to_client(('127.0.0.1', new_demo_port))\n\n        # DELETE /33605, essentially a no-op to check connectivity\n        req = Lwm2mDelete(Lwm2mPath('/%d' % (OID.Test,)))\n        self.bootstrap_server.send(req)\n        self.assertMsgEqual(Lwm2mDeleted.matching(req)(),\n                            self.bootstrap_server.recv())\n"
  },
  {
    "path": "tests/integration/suites/testfest/__init__.py",
    "content": "# -*- coding: utf-8 -*-\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n"
  },
  {
    "path": "tests/integration/suites/testfest/bootstrap.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nimport unittest\n\nfrom framework.lwm2m_test import *\n\nfrom .dm.utils import DataModel\n\n\nclass Test0_ClientInitiatedBootstrap(DataModel.Test):\n    PSK_IDENTITY = b'test-identity'\n    PSK_KEY = b'test-key'\n\n    def setUp(self):\n        self.setup_demo_with_servers(servers=[Lwm2mServer(coap.DtlsServer(psk_key=self.PSK_KEY, psk_identity=self.PSK_IDENTITY))],\n                                     num_servers_passed=0,\n                                     bootstrap_server=True)\n\n    def tearDown(self):\n        self.assertDemoRegisters()\n        super().teardown_demo_with_servers()\n\n    def runTest(self):\n        # 1. Without Instance of Server Object, the Client performs a\n        #    BOOTSTRAP-REQUEST (CoAP POST /bs?ep{Endpoint Client Name})\n        #    in using the Resource values of the Instance 1 of\n        #    Security Object ID:0 to contact the Bootstrap Server\n        #\n        # A. In test step 1., the Bootstrap Server received a Success Message\n        #    (\"2.04\" Changed) related to the BOOTSTRAP-REQUEST of the Client\n        self.assertDemoRequestsBootstrap()\n\n        # 2. The Bootstrap Server uploads the Configuration C.1 in the\n        #    Client in performing two BOOTSTRAP-WRITE (CoAP PUT /0,\n        #    CoAP PUT /1) in using the set of values defined in\n        #    0-SetOfValue_1 and 0-SetOfValue_2 (Object Device ID:3,is\n        #    automatically created and filled-up by the Client)\n        #\n        # B. In test step 2., Client received WRITE operation(s) to setup the\n        #    C.1 configuration (0-SetOfValue)\n        # C. In test step 2., Server received Success (\"2.04\" Changed)\n        #    message(s) from Server related to WRITE operation(s)\n        regular_serv_uri = 'coaps://127.0.0.1:%d' % self.serv.get_listen_port()\n        self.test_write('/%d' % OID.Security,\n                        TLV.make_instance(0, [TLV.make_resource(RID.Security.ServerURI, regular_serv_uri),\n                            TLV.make_resource(RID.Security.Bootstrap, 0),\n                            TLV.make_resource(RID.Security.Mode, 0),\n                            TLV.make_resource(RID.Security.ShortServerID, 1),\n                            TLV.make_resource(RID.Security.PKOrIdentity, self.PSK_IDENTITY),\n                            TLV.make_resource(RID.Security.SecretKey, self.PSK_KEY)]).serialize(),\n                        format=coap.ContentFormat.APPLICATION_LWM2M_TLV,\n                        server=self.bootstrap_server)\n        self.test_write('/%d' % OID.Server,\n                        TLV.make_instance(0, [\n                            TLV.make_resource(RID.Server.ShortServerID, 1),\n                            TLV.make_resource(RID.Server.Lifetime, 86400),\n                            TLV.make_resource(RID.Server.NotificationStoring, False),\n                            TLV.make_resource(RID.Server.Binding, \"U\")]).serialize(),\n                        format=coap.ContentFormat.APPLICATION_LWM2M_TLV,\n                        server=self.bootstrap_server)\n\n        # 3. The Bootstrap Server performs a BOOTSTRAP-DISCOVER\n        #    operation (CoAP GET Accept:40 / ) to verify the Client setup\n        # D. In test step 3., the Bootstrap Server received the Success\n        #    message (\"2.05\" Content) along with the payload related to the\n        #    BOOTSTRAP-DISCOVER request and containing :\n        #    lwm2m=\"1.0\",</0/0>;ssid=1,</0/1>, </1/0>;ssid=1,</3/0>\n        link_list = self.test_discover('/', server=self.bootstrap_server)\n        links = link_list.split(b',')\n        self.assertIn(b'lwm2m=\"1.0\"', links)\n        self.assertIn(b'</%d/0>;ssid=1' % (OID.Security,), links)\n        self.assertIn(b'</%d/1>' % (OID.Security,), links)\n        self.assertIn(b'</%d/0>;ssid=1' % (OID.Server,), links)\n        self.assertIn(b'</%d/0>' % (OID.Device,), links)\n\n        # 4. The Bootstrap Server performs a BOOTSTRAP-FINISH\n        #    operation (CoAP POST /bs) to end-up that BS phase\n        # Registration message (CoAP POST) is sent from client to server\n        #\n        # E. In test step 4., the Bootstrap Server received the Success\n        #    message (\"2.04\" Changed) : no inconsistency detected\n        req = Lwm2mBootstrapFinish()\n        self.bootstrap_server.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.bootstrap_server.recv())\n"
  },
  {
    "path": "tests/integration/suites/testfest/dm/__init__.py",
    "content": "# -*- coding: utf-8 -*-\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n"
  },
  {
    "path": "tests/integration/suites/testfest/dm/advanced_firmware_update.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nimport contextlib\nimport http\nimport os\nimport resource\nimport socket\nimport threading\nimport time\n\nfrom framework.lwm2m_test import *\nfrom framework.create_package import PackageForcedError, make_firmware_package\n\nfrom .utils import DataModel, ValueValidator as VV\n\n\nclass Instances:\n    APP = 0\n    TEE = 1\n    BOOT = 2\n    MODEM = 3\n\n\n# All test cases in this file are derived from their counterparts placed in\n# dm/firmware_update.py. They are based directly on requirements and cases\n# contained in OMA-ETS-Lwm2m document. Note that Advanced Firmware Update object\n# (33629) is not standardized by OMA and as such it is not mentioned in\n# OMA-ETS-Lwm2m document. That is why tests in this file are based on cases\n# created for standard Firmware Update object (OID = 5).\n\n\nclass AdvancedFirmwareUpdate:\n    class Test(DataModel.Test):\n        FW_PKG_OPTS = {'magic': b'AJAY_APP', 'linked': []}\n\n        def collect_values(self, path: Lwm2mPath, final_value, max_iterations=300, step_time=0.1):\n            observed_values = []\n            orig_timeout = self.serv.get_timeout()\n            try:\n                deadline = time.time() + max_iterations * step_time\n                while True:\n                    timeout = max(deadline - time.time(), 0.0)\n                    self.serv.set_timeout(timeout)\n                    try:\n                        state = self.test_read(path)\n                    except socket.timeout:\n                        break\n                    observed_values.append(state)\n                    if state == final_value:\n                        break\n                    time.sleep(step_time)\n                return observed_values\n            finally:\n                self.serv.set_timeout(orig_timeout)\n\n        def setUp(self, extra_cmdline_args=[]):\n            self.ANJAY_MARKER_FILE = generate_temp_filename(dir='/tmp', prefix='anjay-afu-marked-')\n            self.ORIGINAL_IMG_FILE = generate_temp_filename(dir='/tmp', prefix='anjay-afu-bootloader-')\n            super().setUp(afu_marker_path=self.ANJAY_MARKER_FILE,\n                          afu_original_img_file_path=self.ORIGINAL_IMG_FILE,\n                          extra_cmdline_args=extra_cmdline_args)\n\n        def tearDown(self):\n            # reset the state machine\n            # Write /33629/0/1 (Firmware URI)\n            req = Lwm2mWrite(ResPath.AdvancedFirmwareUpdate[Instances.APP].PackageURI, '')\n            self.serv.send(req)\n            self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.serv.recv())\n            super().tearDown()\n\n    class TestWithCoapServer(Test):\n        def setUp(self, coap_server=None, extra_cmdline_args=[]):\n            super().setUp(extra_cmdline_args=extra_cmdline_args)\n\n            from framework_tools.coap_file_server import CoapFileServerThread\n            self.server_thread = CoapFileServerThread(coap_server=coap_server)\n            self.server_thread.start()\n\n        @property\n        def file_server(self):\n            return self.server_thread.file_server\n\n        def tearDown(self):\n            try:\n                super().tearDown()\n            finally:\n                self.server_thread.join()\n\n\nclass AdvancedFirmwareUpdateWithHttpServer:\n    class Test(AdvancedFirmwareUpdate.Test):\n        FIRMWARE_PATH = '/firmware'\n        HTTP_SERVER_CLASS = http.server.HTTPServer\n\n        def get_firmware_uri(self):\n            return 'http://127.0.0.1:%d%s' % (self.http_server.server_address[1], self.FIRMWARE_PATH)\n\n        def before_download(self):\n            pass\n\n        def during_download(self, request_handler):\n            pass\n\n        def setUp(self, firmware_package):\n            super().setUp()\n\n            test_case = self\n\n            class FirmwareRequestHandler(http.server.BaseHTTPRequestHandler):\n                def do_GET(self):\n                    test_case.requests.append(self.path)\n                    test_case.before_download()\n\n                    self.send_response(http.HTTPStatus.OK)\n                    self.send_header('Content-type', 'application/octet-stream')\n                    self.send_header('Content-length', len(firmware_package))\n                    self.end_headers()\n\n                    # give the test some time to read \"Downloading\" state\n                    time.sleep(1)\n\n                    test_case.during_download(self)\n                    self.wfile.write(firmware_package)\n\n                def log_request(code='-', size='-'):\n                    # don't display logs on successful request\n                    pass\n\n            class SilentServer(self.HTTP_SERVER_CLASS):\n                def handle_error(self, *args, **kwargs):\n                    # don't log BrokenPipeErrors\n                    if not isinstance(sys.exc_info()[1], BrokenPipeError):\n                        super().handle_error(*args, **kwargs)\n\n            self.requests = []\n            self.http_server = SilentServer(('', 0), FirmwareRequestHandler)\n\n            self.server_thread = threading.Thread(target=lambda: self.http_server.serve_forever())\n            self.server_thread.start()\n\n        def tearDown(self):\n            try:\n                super().tearDown()\n            finally:\n                self.http_server.shutdown()\n                self.server_thread.join()\n\n            # there should be exactly one request\n            self.assertEqual([self.FIRMWARE_PATH], self.requests)\n\n\nclass Test751_AdvancedFirmwareUpdate_QueryingTheReadableResources(AdvancedFirmwareUpdate.Test):\n    def runTest(self):\n        # 1. READ (CoAP GET) operation is performed on the Advanced Firmware Update\n        #    Object Instance\n        #\n        # A. In test step 1, the Server receives the status code \"2.05\" for\n        #    READ operation success\n        # B. In test step 1, the returned values regarding State (ID:3) and\n        #    Update Result (ID:5) prove the Client FW update Capability is in\n        #    initial state (State=Idle & Update Result= Initial Value).\n        # C. In test step 1, the returned values regarding Advanced Firmware Update\n        #    Protocol Support (ID:8) & Advanced Firmware Update Delivery Method\n        #    (ID:9) allow to determine the supported characteristics of the\n        #    Client FW Update Capability.\n        self.test_read('/%d/0' % OID.AdvancedFirmwareUpdate,\n                       VV.tlv_instance(\n                           resource_validators={\n                               RID.AdvancedFirmwareUpdate.State: VV.from_raw_int(0),\n                               RID.AdvancedFirmwareUpdate.UpdateResult: VV.from_raw_int(0),\n                               RID.AdvancedFirmwareUpdate.FirmwareUpdateProtocolSupport: VV.multiple_resource(\n                                   VV.from_values(b'\\x00', b'\\x01', b'\\x02', b'\\x03', b'\\x04', b'\\x05')),\n                               RID.AdvancedFirmwareUpdate.FirmwareUpdateDeliveryMethod: VV.from_raw_int(2),\n                           },\n                           ignore_extra=True))\n\n\nclass Test755_AdvancedFirmwareUpdate_SettingTheWritableResourcePackage(AdvancedFirmwareUpdate.Test):\n    def runTest(self):\n        # 1. A WRITE (CoAP PUT) operation with a NULL value ('\\0') is\n        #    performed by the Server on the Package Resource (ID:0) of the\n        #    FW Update Object Instance\n        #\n        # A. In test step 1, the Server receives the success message \"2.04\"\n        #    associated with the WRITE operation\n        self.test_write(ResPath.AdvancedFirmwareUpdate[Instances.APP].Package, b'\\0',\n                        format=coap.ContentFormat.APPLICATION_OCTET_STREAM)\n\n        # 2. The Server READs (CoAP GET) the FW Object Instance to get\n        #    the values of the State (ID:3) and Update Result (ID:5) Resources\n        #\n        # B. In test step 2, the Server receives the success message \"2.05\" along\n        #    with the value of State and Update Result Resources values.\n        # C. In test step 2, the queried State and Update Result Resources values\n        #    are both 0 (Idle / Initial value): FW Update Object Instance is in the\n        #    Initial state.\n        self.test_read('/%d/0' % OID.AdvancedFirmwareUpdate,\n                       VV.tlv_instance(\n                           resource_validators={\n                               RID.AdvancedFirmwareUpdate.State: VV.from_raw_int(0),\n                               RID.AdvancedFirmwareUpdate.UpdateResult: VV.from_raw_int(0),\n                           },\n                           ignore_extra=True))\n\n        # 3. A WRITE (CoAP PUT) operation with a valid image is\n        #    performed by the Server on the Package Resource (ID:0) of the\n        #    FW Update Object Instance\n        #\n        # D. In test step 3, the Server receives the success message \"2.04\"\n        #    associated with the WRITE request for loading the firmware image.\n        self.test_write(ResPath.AdvancedFirmwareUpdate[Instances.APP].Package,\n                        make_firmware_package(b'', **self.FW_PKG_OPTS),\n                        format=coap.ContentFormat.APPLICATION_OCTET_STREAM)\n\n        # 4. The Server READs (CoAP GET) the FW Object Instance to get\n        #    the values of the State (ID:3) and Update Result (ID:5) Resources\n        #\n        # E. In test step 4, the Server receives the success message \"2.05\" along\n        #    with the State and Update Result Resources values.\n        # F. In test step 4, the queried value of State resource is 2 (Downloaded)\n        #    and the value of Update Result value is still 0 (Initial Value)\n        self.test_read('/%d/0' % OID.AdvancedFirmwareUpdate,\n                       VV.tlv_instance(\n                           resource_validators={\n                               RID.AdvancedFirmwareUpdate.State: VV.from_raw_int(2),\n                               RID.AdvancedFirmwareUpdate.UpdateResult: VV.from_raw_int(0),\n                           },\n                           ignore_extra=True))\n\n\nclass Test756_AdvancedFirmwareUpdate_SettingTheWritableResourcePackageURI(AdvancedFirmwareUpdateWithHttpServer.Test):\n    def setUp(self):\n        super().setUp(make_firmware_package(b'', **self.FW_PKG_OPTS))\n\n    def runTest(self):\n        # 1. A WRITE (CoAP PUT) operation with an empty string value is\n        #    performed by the Server on the Package Resource (ID:0) of the FW\n        #    Update Object Instance\n        #\n        # A. In test step 1, the Server receives the success message \"2.04\"\n        #    associated with the WRITE operation\n        self.test_write(ResPath.AdvancedFirmwareUpdate[Instances.APP].PackageURI, b'')\n\n        # 2. The Server READs (CoAP GET) the FW Object Instance to get the\n        #    values of the State (ID:3) and Update Result (ID:5) Resources\n        #\n        # B. In test step 2, the Server receives the success message \"2.05\" along\n        #    with the value of State and Update Result Resources values.\n        # C. In test step 2, the queried State and Update Result Resources values\n        #    are both 0 (Idle / Initial value): FW Update Object Instance is in the\n        #    Initial state.\n        self.test_read('/%d/0' % OID.AdvancedFirmwareUpdate,\n                       VV.tlv_instance(\n                           resource_validators={\n                               RID.AdvancedFirmwareUpdate.State: VV.from_raw_int(0),\n                               RID.AdvancedFirmwareUpdate.UpdateResult: VV.from_raw_int(0),\n                           },\n                           ignore_extra=True))\n\n        # 3. A WRITE (CoAP PUT) operation with a valid image is performed by\n        #    the Server on the Package Resource (ID:0) of the FW Update Object\n        #    Instance\n        #\n        # D. In test step 3, the Server receives the success message \"2.04\"\n        #    associated with the WRITE request for the loadded image.\n        self.test_write(ResPath.AdvancedFirmwareUpdate[Instances.APP].PackageURI,\n                        self.get_firmware_uri())\n\n        # give the client some time to download firmware\n        time.sleep(3)\n\n        # 4. The Server READs (CoAP GET) the FW Object Instance to get the\n        #    values of the State (ID:3) and Update Result (ID:5) Resources\n        #\n        # E. In test step 4, the Server receives the success message \"2.05\" along\n        #    with the State and Update Result Resources values.\n        # F. In test step 4, the queried value of State resource is 2 (Downloaded)\n        #    and the value of Update Result value is still 0 (Initial Value)\n        self.test_read('/%d/0' % OID.AdvancedFirmwareUpdate,\n                       VV.tlv_instance(\n                           resource_validators={\n                               RID.AdvancedFirmwareUpdate.State: VV.from_raw_int(2),\n                               RID.AdvancedFirmwareUpdate.UpdateResult: VV.from_raw_int(0),\n                           },\n                           ignore_extra=True))\n\n\nclass Test760_AdvancedFirmwareUpdate_BasicObservationAndNotificationOnFirmwareUpdateObjectResources(\n    AdvancedFirmwareUpdate.Test):\n    def runTest(self):\n        # 1. The Server communicates to the Client pmin=2 and pmax=10\n        #    periods with a WRITE-ATTRIBUTE (CoAP PUT) operation at\n        #    the FW Update Object Instance level.\n        #\n        # A. In test step 1, the Server receives the success message \"2.04\" associated\n        #    with the WRITE-ATTRIBUTE operation.\n        self.test_write_attributes('/%d/0' % OID.AdvancedFirmwareUpdate,\n                                   pmin=2, pmax=10)\n\n        # 2. The Server Sends OBSERVE (CoAP Observe Option) message\n        #    to activate reporting on the State Resource (/33629/0/3) of the FW\n        #    Update Object Instance.\n        #\n        # B. In test step 2, the Server receives the success message \"2.05\" associated\n        #    with the OBSERVE operation, along with the value of State =Idle\n        req = Lwm2mObserve(ResPath.AdvancedFirmwareUpdate[Instances.APP].State)\n        self.serv.send(req)\n        res = self.serv.recv()\n        self.assertMsgEqual(Lwm2mContent.matching(req)(content=b'0'), res)\n\n        # 3. The Server delivers the firmware to the Client through a WRITE\n        #    (CoAP PUT) operation on the Package Resource (/33629/0/0)\n        #\n        # C. In test step 3, the Server receives the success message \"2.04\" associated\n        #    with the WRITE operation delivering the firmaware image.\n        self.test_write(ResPath.AdvancedFirmwareUpdate[Instances.APP].Package,\n                        make_firmware_package(b'', **self.FW_PKG_OPTS),\n                        format=coap.ContentFormat.APPLICATION_OCTET_STREAM)\n\n        # 4. The Client reports requested information with a NOTIFY\n        #    message (CoAP response)\n        #\n        # D. In test step 4, the State Resource value returned by the Client in NOTIFY\n        #    message is set to \"Downloaded\"\n        req = Lwm2mObserve(ResPath.AdvancedFirmwareUpdate[Instances.APP].State)\n        self.serv.send(req)\n        res = self.serv.recv(timeout_s=3)\n        self.assertMsgEqual(Lwm2mContent.matching(req)(content=b'2'), res)\n\n\nclass Test770_AdvancedFirmwareUpdate_SuccessfulFirmwareUpdateViaCoAP(AdvancedFirmwareUpdate.Test):\n    def runTest(self):\n        demo_executable = os.path.join(self.config.demo_path, self.config.demo_cmd)\n        with open(demo_executable, 'rb') as f:\n            payload = f.read()\n\n        prev_version = self.test_read(ResPath.Device.FirmwareVersion)\n\n        # 1. Step 1 – Package Delivery\n        #    a. The Server places the Client in the initial state of the FW Update\n        #       process : A WRITE (CoAP PUT) operation with a NULL value\n        #       (‘\\0’) is performed by the Server on the Package Resource\n        #       (ID:0) of the FW Update Object Instance\n        #\n        # A. Step 1 – Package Delivery\n        #    a. In the test step 1.a, the Server receives the status code \"2.04\" for\n        #       the WRITE success setting the Client in the FW update initial\n        #       state.\n        #    d. Update Result is \"0\" (Initial Value) during the whole step\n        self.test_write(ResPath.AdvancedFirmwareUpdate[Instances.APP].Package, b'\\0',\n                        coap.ContentFormat.APPLICATION_OCTET_STREAM)\n\n        self.assertEqual(b'0', self.test_read(ResPath.AdvancedFirmwareUpdate[Instances.APP].State))\n\n        # 1. Step 1 – Package Delivery\n        #    b. The Server delivers the firmware to the Client through a WRITE\n        #       (CoAP PUT) operation on the Package Resource (/33629/0/0)\n        #\n        # A. Step 1 – Package Delivery\n        #    b. In the test step 1.b, The Server receives success message with\n        #       either a \"2.31\" status code (Continue) or a final \"2.04\" status\n        #       code.\n        self.test_write_block(ResPath.AdvancedFirmwareUpdate[Instances.APP].Package,\n                              make_firmware_package(payload, **self.FW_PKG_OPTS),\n                              coap.ContentFormat.APPLICATION_OCTET_STREAM)\n\n        # 1. Step 1 – Package Delivery\n        #    c. Polling (READ command) or Notification on Update Result\n        #       and State Resources is performed, up to the time State Resource\n        #       takes the ‘Downloaded’ value (2)\n        #\n        # A. Step 1 – Package Delivery\n        #    c. In the test step 1.c State Resource can take the value \"1\"\n        #       (Downloading) during this sub-step and will take the value \"2\" at\n        #       the end (Downloaded)\n        #    d. Update Result is \"0\" (Initial Value) during the whole step\n        self.assertEqual(b'2', self.test_read(ResPath.AdvancedFirmwareUpdate[Instances.APP].State))\n        self.assertEqual(b'0', self.test_read(ResPath.AdvancedFirmwareUpdate[Instances.APP].UpdateResult))\n\n        # 2. Step 2 – Advanced Firmware Update\n        #    a. When the download is completed (State Resource value is ‘2’\n        #       Downloaded) , the Server initiates a firmware update by\n        #       triggering EXECUTE command on Update Resource (CoAP\n        #       POST /33629/0/2)\n        #\n        # B. Step 2 – Advanced Firmware Update\n        #    a. In test step 2.a, the Server receives a success message \"2.04\"\n        #       (Changed) in response to the EXECUTE command\n        self.test_execute(ResPath.AdvancedFirmwareUpdate[Instances.APP].Update)\n        # not supported: Updating state only observable via Observe\n        # self.assertEqual(b'3', self.test_read(ResPath.FirmwareUpdate.State))\n\n        # 2. Step 2 – Advanced Firmware Update\n        #    b. Polling (READ command) or Notification on Update Result and\n        #       State Resources is performed, up to the time State Resource is\n        #       turned back to Idle value (0) or Update Result Resource contains\n        #       an other value than the Initial one (0)\n        #\n        # B. Step 2 – Advanced Firmware Update\n        #    b. In test step 2.b, the Server receives success message(s) \"2.05\"\n        #       Contents along with a State Resource value of \"3\" (Updating)\n        #       or \"0\" (Idle) and an Update Ressource value of \"0\" (Initial\n        #       Value) or \"1\" (Firmware updated successfully)\n        self.serv.reset()\n        self.assertDemoRegisters()\n\n        # 3. Step 3 – Process verification\n        #    a. The Server READs Update Result (\"/33629/0/5\") and State (\"/33629/0/3\")\n        #       Resources to know the result of the advanced firmware update procedure.\n        #\n        # C. Step 3 – Process verification\n        #    a. In test step 3.a, the Server receives success message(s) \"2.05\"\n        #       Content\" along with a State Resource value of \"0\" (Idle) and an\n        #       Update Ressource value of \"1\" (Firmware updated successfully)\n        self.assertEqual(b'0', self.test_read(ResPath.AdvancedFirmwareUpdate[Instances.APP].State))\n        self.assertEqual(b'1', self.test_read(ResPath.AdvancedFirmwareUpdate[Instances.APP].UpdateResult))\n\n        # 3. Step 3 – Process verification\n        #    b. The Server READs the Resource \"Advanced Firmware Update\" from the\n        #       Object Device Instance (\"/3/0/3\")\n        #\n        # C. Step 3 – Process verification\n        #    b. In test step 3.b, the Server receives success message \"2.05\"\n        #       Content\" along with the expected value of the Resource\n        #       Firmware Version from the Object Device Instance\n        #\n        # TODO: we currently update firmware with an identical executable,\n        # so the version does not change\n        self.assertEqual(prev_version, self.test_read(ResPath.Device.FirmwareVersion))\n\n\nclass Test771_AdvancedFirmwareUpdate_SuccessfulFirmwareUpdateViaAlternateMechanism(\n    AdvancedFirmwareUpdateWithHttpServer.Test):\n    def setUp(self):\n        demo_executable = os.path.join(self.config.demo_path, self.config.demo_cmd)\n        with open(demo_executable, 'rb') as f:\n            pkg = make_firmware_package(f.read(), **self.FW_PKG_OPTS)\n\n        super().setUp(pkg)\n\n    def runTest(self):\n        # In this test the package version stays the same after update\n        prev_version = self.test_read(ResPath.Device.FirmwareVersion)\n\n        # 1. Step 1 – Package Delivery\n        #    a. The Server places the Client in the initial state of the FW\n        #       Update process : A WRITE (CoAP PUT) operation with an\n        #       empty string value is performed by the Server on the Package\n        #       URI Resource (ID:1) of the FW Update Object Instance\n        #\n        # A. Step 1 – Package Delivery\n        #    a. In the test step 1.a, the Server receives the status code \"2.04\"\n        #       for the WRITE success setting the Client in the FW update\n        #       initial state.\n        #    e. Update Result is \"0\" (Initial Value) during the whole test\n        #       step 1\n        self.test_write(ResPath.AdvancedFirmwareUpdate[Instances.APP].PackageURI, '')\n        self.assertEqual(b'0', self.test_read(ResPath.FirmwareUpdate.UpdateResult))\n\n        # 1. Step 1 – Package Delivery\n        #    b. The Server delivers the Package URI to the Client through a\n        #       WRITE (CoAP PUT) operation on the Package URI Resource\n        #       (/33629/0/1)\n        #\n        # A. Step 1 – Package Delivery\n        #    b. In the test step 1.b, the Server receives the status code \"2.04\"\n        #       for the WRITE success setting the Package URI Client in the\n        #       FW update Object Instance\n        self.test_write(ResPath.AdvancedFirmwareUpdate[Instances.APP].PackageURI,\n                        self.get_firmware_uri())\n        self.assertEqual(b'0', self.test_read(ResPath.AdvancedFirmwareUpdate[Instances.APP].UpdateResult))\n\n        # 1. Step 1 – Package Delivery\n        #    c. The Client downloads the firmware from the provided URI via\n        #       an alternative mechanism (not CoAP)\n        #    d. Polling ( successive READ commands) or Notification on\n        #       Update Result and State Resources is performed, up to the time\n        #       State Resource takes the ‘Downloaded’ value (2)\n        #\n        # A. Step 1 – Package Delivery\n        #    c. In the test step 1.c, The Server receives success message\n        #       with either a \"2.31\" status code (Continue) or a final \"2.04\"\n        #       status code.\n        #    d. In the test step 1.d State Resource can take the value \"1\"\n        #       (Downloading) during this sub-step and will take the value\n        #       \"2\" at the end (Downloaded)\n        #    e. Update Result is \"0\" (Initial Value) during the whole test\n        #       step 1\n        observed_states = self.collect_values(ResPath.AdvancedFirmwareUpdate[Instances.APP].State, b'2')\n        self.assertEqual(b'2', observed_states[-1])\n        self.assertIn(set(observed_states), [{b'0', b'1', b'2'}, {b'1', b'2'}])\n\n        self.assertEqual(b'0', self.test_read(ResPath.AdvancedFirmwareUpdate[Instances.APP].UpdateResult))\n\n        # 2. Step 2 – Advanced Firmware Update\n        #    a. When the download is completed (State Resource value is ‘2’\n        #       Downloaded) , the Server initiates a advanced firmware update by\n        #       triggering EXECUTE command on Update Resource (CoAP\n        #       POST /33629/0/2 )\n        #\n        # B. Step 2 – Advanced Firmware Update\n        #    a. In test step 2.a, the Server receives a success message \"2.04\"\n        #       (Changed) in response to the EXECUTE command\n        self.test_execute(ResPath.AdvancedFirmwareUpdate[Instances.APP].Update)\n        # not supported: Updating state only observable via Observe\n        # self.assertEqual(b'3', self.test_read(ResPath.FirmwareUpdate.State))\n\n        # 2. Step 2 – Advanced Firmware Update\n        #    b. Polling (READ command) or Notification on Update Result and\n        #       State Resources is performed, up to the time State Resource is\n        #       turned back to Idle value (0) or Update Result Resource contains\n        #       an other value than the Initial one (0)\n        #\n        # B. Step 2 – Advanced Firmware Update\n        #    b. In test step 2.b, the Server receives success message(s) \"2.05\"\n        #       Contents along with a State Resource value of \"3\" (Updating)\n        #       or \"0\" (Idle) and an Update Ressource value of \"0\" (Initial\n        #       Value) or \"1\" (Firmware updated successfully)\n        self.serv.reset()\n        self.assertDemoRegisters()\n\n        # 3. Step 3 – Process verification\n        #    a. The Server READs Update Result (\"/33629/0/5\") and State (\"/33629/0/3\")\n        #       Resources to know the result of the advanced firmware update procedure.\n        #\n        # C. Step 3 – Process verification\n        #    a. In test step 3.a, the Server receives success message(s) \"2.05\"\n        #       Content\" along with a State Resource value of \"0\" (Idle) and an\n        #       Update Ressource value of \"1\" (Firmware updated successfully)\n        self.assertEqual(b'0', self.test_read(ResPath.AdvancedFirmwareUpdate[Instances.APP].State))\n        self.assertEqual(b'1', self.test_read(ResPath.AdvancedFirmwareUpdate[Instances.APP].UpdateResult))\n\n        # 3. Step 3 – Process verification\n        #    b. The Server READs the Resource \"Advanced Firmware Update\" from the\n        #       Object Device Instance (\"/3/0/3\")\n        #\n        # C. Step 3 – Process verification\n        #    b. In test step 3.b, the Server receives success message \"2.05\"\n        #       Content\" along with the expected value of the Resource\n        #       Firmware Version from the Object Device Instance\n        #\n        # TODO: we currently update firmware with an identical executable,\n        # so the version does not change\n        self.assertEqual(prev_version, self.test_read(ResPath.Device.FirmwareVersion))\n\n\nclass Test772_AdvancedFirmwareUpdate_ErrorCase_FirmwarePackageNotDownloaded(AdvancedFirmwareUpdate.Test):\n    def runTest(self):\n        # 1. The Server send a READ operation (CoAP GET /33629/0) to the Client on the\n        #    FW Update Object Instance to obtain the values of the State and Update\n        #    Resources.\n        #\n        # A. In test step 1, the Server receives a success message (\"2.05\" Content)\n        #    associated to its READ command along with a State Resource value\n        #    which is not \"2\" (Downloaded) and a valid (0..9) Update Resource\n        #    value\n        state = self.test_read(ResPath.AdvancedFirmwareUpdate[Instances.APP].State)\n        self.assertNotEqual(b'2', state)\n\n        update_result = self.test_read(ResPath.AdvancedFirmwareUpdate[Instances.APP].UpdateResult)\n        self.assertIn(int(update_result.decode('ascii')), range(10))\n\n        # 2. the Client receives an EXECUTE operation on the Update Resource\n        #    (CoAP POST /33629/0/2 ) of the FW Update Object Instance\n        #\n        # B. In test step 2, the Server receives the status code \"4.05\" for method\n        #    not allowed associated to its EXECUTE command\n        req = Lwm2mExecute(ResPath.AdvancedFirmwareUpdate[Instances.APP].Update)\n        self.serv.send(req)\n        res = self.serv.recv()\n        self.assertMsgEqual(Lwm2mErrorResponse.matching(req)(coap.Code.RES_METHOD_NOT_ALLOWED),\n                            res)\n\n        # 3. The Server send a READ operation again (CoAP GET /33629/0/3) to the Client\n        #    on the FW Update Object Instance to obtain the State and the Update\n        #    Resource values\n        #\n        # C. In test step 3, the Server receives a success message (\"2.05\" Content)\n        #    associated to its READ command along with a State Resource value\n        #    and an Update Result Resource value, identical to the ones retrieved\n        #    in Pass-Criteria A. The firmware has not bee installed.\n        self.assertEqual(state, self.test_read(ResPath.AdvancedFirmwareUpdate[Instances.APP].State))\n        self.assertEqual(update_result, self.test_read(ResPath.AdvancedFirmwareUpdate[Instances.APP].UpdateResult))\n\n\nclass Test773_AdvancedFirmwareUpdate_ErrorCase_NotEnoughStorage_FirmwareURI(AdvancedFirmwareUpdateWithHttpServer.Test):\n    def setUp(self):\n        @contextlib.contextmanager\n        def temporary_soft_fsize_limit(limit_bytes):\n            prev_limit = resource.getrlimit(resource.RLIMIT_FSIZE)\n\n            try:\n                resource.setrlimit(resource.RLIMIT_FSIZE, (limit_bytes, prev_limit[1]))\n                yield\n            finally:\n                resource.setrlimit(resource.RLIMIT_FSIZE, prev_limit)\n\n        demo_executable = os.path.join(self.config.demo_path, self.config.demo_cmd)\n        with open(demo_executable, 'rb') as f:\n            payload = f.read()\n\n        # Limit file size for demo so that full firmware is too much.\n        # After demo starts, we can safely restore original limit, as\n        # the client already inherited smaller one.\n        with temporary_soft_fsize_limit(len(payload) // 2):\n            super().setUp(payload)\n\n    def runTest(self):\n        # 1. The Server verifies through a READ (CoAP GET) command on\n        #    /33629/0/3 (State) the FW Update Object Instance of the Client is in\n        #    Idle State\n        #\n        # A. In test step 1, the Server receives the status code \"2.05 \" (Content) for\n        #    the READ success command, along with the State Resource value of\n        #    \"0\" (Idle)\n        self.assertEqual(b'0', self.test_read(ResPath.AdvancedFirmwareUpdate[Instances.APP].State))\n\n        # 2. The Server delivers the firmware package to the Client either\n        #    through a WRITE (CoAP PUT) operation in the Package\n        #    Resource (/33629/0/0) or through a WRITE operation of an URI in the\n        #    Package URI Resource.\n        #\n        # B. In test step 2., the Server receives the status code \"2.04\" (Changed)\n        #    for the WRITE command setting either the Package URI Resource or\n        #    setting the Package Resource, according to the chosen firmware\n        #    delivery method.\n        self.test_write(ResPath.AdvancedFirmwareUpdate[Instances.APP].PackageURI,\n                        self.get_firmware_uri())\n\n        # 3. The firmware downloading process is runing The Server sends\n        #    repeated READs or OBSERVE on State and Update Result\n        #    Resources (CoAP GET /33629/0) of the FW Update Object Instance\n        #    to determine when the download is completed or if an error\n        #    occured.Before the end of download, the device runs out of\n        #    storage and cannot finish the download\n        # 4. When the Package delivery is stopped the server READs Update\n        #    Result to know the result of the firmware update procedure.\n        #\n        # C. In test step 3., the State Resource retrieved with a value of \"1\" from\n        #    successive Server READs or Client NOTIFY messages, indicates the\n        #    download stage of the Package Delivery is engaged\n        # D. In test step 3., the Update Result Resource (/33629/0/5) retrieved from\n        #    successive Server READs or Client NOTIFY messages will take the\n        #    value \"2\" indicating an error occurred during the downloading\n        #    process related to shortage of storage memory The State Resource\n        #    value never reaches the Downloaded value (\"2\")\n        # E. In test step 4., the success READ message(s) (status code \"2.05\"\n        #    Content) on State Resource with value \"0\" (Idle) and Update Result\n        #    Resource with value \"2\" indicates the firmware Package Delivery\n        observed_values = self.collect_values(ResPath.AdvancedFirmwareUpdate[Instances.APP].State, b'0')\n        self.assertEqual(b'0', observed_values[-1])\n        self.assertEqual({b'0', b'1'}, set(observed_values))\n        self.assertEqual(b'2', self.test_read(ResPath.AdvancedFirmwareUpdate[Instances.APP].UpdateResult))\n\n\nclass Test773_AdvancedFirmwareUpdate_ErrorCase_NotEnoughStorage_FirmwareURI_CoAP(\n    AdvancedFirmwareUpdate.TestWithCoapServer):\n    def setUp(self):\n        # limit file size to 100K; enough for persistence file, not\n        # enough for firmware\n        import resource\n        self.prev_limit = resource.getrlimit(resource.RLIMIT_FSIZE)\n        new_limit_b = 100 * 1024\n        resource.setrlimit(resource.RLIMIT_FSIZE, (new_limit_b, self.prev_limit[1]))\n\n        demo_executable = os.path.join(self.config.demo_path, self.config.demo_cmd)\n        with open(demo_executable, 'rb') as f:\n            self._payload = f.read()\n\n        super().setUp()\n\n    def tearDown(self):\n        import resource\n        resource.setrlimit(resource.RLIMIT_FSIZE, self.prev_limit)\n\n        super().tearDown()\n\n    def runTest(self):\n        with self.file_server as file_server:\n            file_server.set_resource('/firmware', self._payload)\n            uri = file_server.get_resource_uri('/firmware')\n\n        # 1. The Server verifies through a READ (CoAP GET) command on\n        #    /33629/0/3 (State) the FW Update Object Instance of the Client is in\n        #    Idle State\n        #\n        # A. In test step 1, the Server receives the status code \"2.05 \" (Content) for\n        #    the READ success command, along with the State Resource value of\n        #    \"0\" (Idle)\n        self.assertEqual(b'0', self.test_read(ResPath.AdvancedFirmwareUpdate[Instances.APP].State))\n\n        # 2. The Server delivers the firmware package to the Client either\n        #    through a WRITE (CoAP PUT) operation in the Package\n        #    Resource (/33629/0/0) or through a WRITE operation of an URI in the\n        #    Package URI Resource.\n        #\n        # B. In test step 2., the Server receives the status code \"2.04\" (Changed)\n        #    for the WRITE command setting either the Package URI Resource or\n        #    setting the Package Resource, according to the chosen firmware\n        #    delivery method.\n        self.test_write(ResPath.AdvancedFirmwareUpdate[Instances.APP].PackageURI, uri)\n\n        # 3. The firmware downloading process is runing The Server sends\n        #    repeated READs or OBSERVE on State and Update Result\n        #    Resources (CoAP GET /33629/0) of the FW Update Object Instance\n        #    to determine when the download is completed or if an error\n        #    occured.Before the end of download, the device runs out of\n        #    storage and cannot finish the download\n        # 4. When the Package delivery is stopped the server READs Update\n        #    Result to know the result of the firmware update procedure.\n        #\n        # C. In test step 3., the State Resource retrieved with a value of \"1\" from\n        #    successive Server READs or Client NOTIFY messages, indicates the\n        #    download stage of the Package Delivery is engaged\n        # D. In test step 3., the Update Result Resource (/33629/0/5) retrieved from\n        #    successive Server READs or Client NOTIFY messages will take the\n        #    value \"2\" indicating an error occurred during the downloading\n        #    process related to shortage of storage memory The State Resource\n        #    value never reaches the Downloaded value (\"2\")\n        # E. In test step 4., the success READ message(s) (status code \"2.05\"\n        #    Content) on State Resource with value \"0\" (Idle) and Update Result\n        #    Resource with value \"2\" indicates the firmware Package Delivery\n        observed_values = self.collect_values(ResPath.AdvancedFirmwareUpdate[Instances.APP].State, b'0')\n        self.assertEqual(b'0', observed_values[-1])\n        self.assertEqual({b'0', b'1'}, set(observed_values))\n        self.assertEqual(b'2', self.test_read(ResPath.AdvancedFirmwareUpdate[Instances.APP].UpdateResult))\n\n\nclass Test774_AdvancedFirmwareUpdate_ErrorCase_OutOfMemory(AdvancedFirmwareUpdate.Test):\n    def runTest(self):\n        demo_executable = os.path.join(self.config.demo_path, self.config.demo_cmd)\n        with open(demo_executable, 'rb') as f:\n            payload = f.read()\n\n        # 1. The Server verifies through a READ (CoAP GET) command on\n        #    /33629/0/3 (State) the FW Update Object Instance of the Client is in Idle\n        #    State\n        #\n        # A. In test step 1, the Server receives the status code \"2.05\" (Content) for\n        #    the READ success command, along with the State Resource value of\n        #    \"0\" (Idle)\n        self.assertEqual(b'0', self.test_read(ResPath.AdvancedFirmwareUpdate[Instances.APP].State))\n\n        # 2. The Server delivers the firmware package to the Client either through\n        #    a WRITE (CoAP PUT) operation in the Package Resource (/33629/0/0) or\n        #    through a WRITE operation of an URI in the Package URI Resource.\n        #\n        # B. In test step 2., the Server receives the status code \"2.04\" (Changed)\n        #    for the WRITE command setting either the Package URI Resource or\n        #    setting the Package Resource, according to the chosen firmware\n        #    delivery method.\n        self.test_write_block(ResPath.AdvancedFirmwareUpdate[Instances.APP].Package,\n                              make_firmware_package(payload,\n                                                    **self.FW_PKG_OPTS,\n                                                    force_error=PackageForcedError.Firmware.OutOfMemory),\n                              coap.ContentFormat.APPLICATION_OCTET_STREAM)\n\n        # 3. The firmware download process is runing The Server sends repeated\n        #    READs or OBSERVE on State and Update Result Resources (CoAP\n        #    GET /33629/0) of the FW Update Object Instance to determine when the\n        #    download is completed or if an error occured.Before the end of\n        #    download, the Client runs out of RAM and cannot finish the\n        #    download\n        # 4. When the Package delivery is stopped the server READs Update\n        #    Result to know the result of the firmware update procedure.\n        #\n        # C. In test step 3., the State Resource retrieved with a value of \"1\" from\n        #    successive Server READs or Client NOTIFY messages, indicates the\n        #    download stage of the Package Delivery is engaged\n        # D. In test step 3., the Update Result Resource (/33629/0/5) retrieved from\n        #    successive Server READs or Client NOTIFY messages will take the\n        #    value \"2\" indicating an error occurred during the download process\n        #    related to shortage of RAM The State Resource value never reaches\n        #    the Downloaded value (\"3\")\n        # E. In test step 4., the success READ message(s) (status code \"2.05\"\n        #    Content) on State Resource with value \"0\" (Idle) and Update Result\n        #    Resource with value \"3\" indicates the firmware Package Delivery\n        #    aborted due to shortage of RAM.\n        self.assertEqual(b'0', self.test_read(ResPath.AdvancedFirmwareUpdate[Instances.APP].State))\n        self.assertEqual(b'3', self.test_read(ResPath.AdvancedFirmwareUpdate[Instances.APP].UpdateResult))\n\n\nclass AdvancedFirmwareUpdate_ErrorCase_OutOfMemory_PackageURI(AdvancedFirmwareUpdateWithHttpServer.Test):\n    def setUp(self):\n        demo_executable = os.path.join(self.config.demo_path, self.config.demo_cmd)\n        with open(demo_executable, 'rb') as f:\n            pkg = make_firmware_package(f.read(),\n                                        **self.FW_PKG_OPTS,\n                                        force_error=PackageForcedError.Firmware.OutOfMemory)\n\n        super().setUp(pkg)\n\n    def runTest(self):\n        # Test 774, but with Package URI\n        self.assertEqual(b'0', self.test_read(ResPath.AdvancedFirmwareUpdate[Instances.APP].UpdateResult))\n        self.test_write(ResPath.AdvancedFirmwareUpdate[Instances.APP].PackageURI, self.get_firmware_uri())\n\n        observed_values = self.collect_values(ResPath.AdvancedFirmwareUpdate[Instances.APP].State, b'0')\n        self.assertEqual(b'0', observed_values[-1])\n        self.assertEqual({b'0', b'1'}, set(observed_values))\n        self.assertEqual(b'3', self.test_read(ResPath.AdvancedFirmwareUpdate[Instances.APP].UpdateResult))\n\n\nclass Test775_AdvancedFirmwareUpdate_ErrorCase_ConnectionLostDuringDownloadPackageURI(\n    AdvancedFirmwareUpdateWithHttpServer.Test):\n    class NoShutdownHttpServer(http.server.HTTPServer):\n        def shutdown_request(self, request):\n            pass\n\n        def close_request(self, request):\n            pass\n\n    HTTP_SERVER_CLASS = NoShutdownHttpServer\n\n    def during_download(self, req_handler):\n        self._dangling_http_socket = req_handler.request\n        # HACK to ignore any calls on .wfile afterwards\n        req_handler.wfile = ANY\n\n    def setUp(self):\n        self._dangling_http_socket = None\n        demo_executable = os.path.join(self.config.demo_path, self.config.demo_cmd)\n        with open(demo_executable, 'rb') as f:\n            pkg = make_firmware_package(f.read(),\n                                        **self.FW_PKG_OPTS,\n                                        force_error=PackageForcedError.Firmware.OutOfMemory)\n\n        super().setUp(pkg)\n\n    def tearDown(self):\n        try:\n            if self._dangling_http_socket is not None:\n                self._dangling_http_socket.close()\n        finally:\n            super().tearDown()\n\n    def runTest(self):\n        # 1. The Server verifies through a READ (CoAP GET) command on\n        #    /33629/0/3 (State) the FW Update Object Instance of the Client is in Idle\n        #    State\n        #\n        # A. In test step 1., the Server receives the status code \"2.05 \" (Content)\n        #    for the READ success command, along with the State Resource value\n        #    of \"0\" (Idle)\n        self.assertEqual(b'0', self.test_read(ResPath.AdvancedFirmwareUpdate[Instances.APP].State))\n\n        # 2. The Server delivers the firmware package to the Client through a\n        #    WRITE operation of an URI in the Package URI Resource.\n        #\n        # B. In test step 2., the Server receives the status code \"2.04\" (Changed)\n        #    for the WRITE command setting the Package URI Resource\n        #    according to the PULL firmware delivery method.\n        self.test_write(ResPath.AdvancedFirmwareUpdate[Instances.APP].PackageURI, self.get_firmware_uri())\n\n        # 3. The Server sends repeated READs or OBSERVE on State and Update\n        #    Result Resources (CoAP GET /33629/0) of the FW Update Object\n        #    Instance to determine when the download is completed or if an error\n        #    occured.Before the end of download, the connection is intentionnaly\n        #    lost and the download cannot be finished.\n        # 4. When the Package delivery is stopped the Server READs Update\n        #    Result to know the result of the firmware update procedure.\n        #\n        # C. In test step 3., the State Resource value set to \"1\" retrieved from\n        #    successive Server READs or Client NOTIFY messages, indicates the\n        #    Package Delivery process is engaged in a Download stage\n        # D. In test step 3., the Update Result Resource (/33629/0/5) retrieved from\n        #    successive Server READs or Client NOTIFY messages will take the\n        #    value \"4\" indicating an error occurred during the downloading\n        #    process related to connection lost\n        # E. In test step 4., the success READ message(s) (status code \"2.05\"\n        #    Content) on State Resource with value \"0\" (Idle) and Update Result\n        #    Resource with value \"4\" indicates the firmware Package Delivery\n        #    aborted due to connection lost dur the Package delivery.\n        observed_values = self.collect_values(ResPath.AdvancedFirmwareUpdate[Instances.APP].State, b'0',\n                                              max_iterations=600)  # wait up to 60 seconds\n        self.assertEqual(b'0', observed_values[-1])\n        self.assertEqual({b'0', b'1'}, set(observed_values))\n        self.assertEqual(b'4', self.test_read(ResPath.AdvancedFirmwareUpdate[Instances.APP].UpdateResult))\n\n\nclass Test775_AdvancedFirmwareUpdate_ErrorCase_ConnectionLostDuringDownloadPackageURI_CoAP(\n    AdvancedFirmwareUpdate.TestWithCoapServer):\n    def setUp(self):\n        demo_executable = os.path.join(self.config.demo_path, self.config.demo_cmd)\n        with open(demo_executable, 'rb') as f:\n            pkg = make_firmware_package(f.read(),\n                                        **self.FW_PKG_OPTS,\n                                        force_error=PackageForcedError.Firmware.OutOfMemory)\n\n        class MuteServer(coap.Server):\n            def send(self, *args, **kwargs):\n                pass\n\n        super().setUp(coap_server=MuteServer(), extra_cmdline_args=['--afu-ack-timeout', '1'])\n\n        with self.file_server as file_server:\n            file_server.set_resource('/firmware', pkg)\n            self._uri = file_server.get_resource_uri('/firmware')\n\n    def runTest(self):\n        # 1. The Server verifies through a READ (CoAP GET) command on\n        #    /33629/0/3 (State) the FW Update Object Instance of the Client is in Idle\n        #    State\n        #\n        # A. In test step 1., the Server receives the status code \"2.05 \" (Content)\n        #    for the READ success command, along with the State Resource value\n        #    of \"0\" (Idle)\n        self.assertEqual(b'0', self.test_read(ResPath.AdvancedFirmwareUpdate[Instances.APP].State))\n\n        # 2. The Server delivers the firmware package to the Client through a\n        #    WRITE operation of an URI in the Package URI Resource.\n        #\n        # B. In test step 2., the Server receives the status code \"2.04\" (Changed)\n        #    for the WRITE command setting the Package URI Resource\n        #    according to the PULL firmware delivery method.\n        self.test_write(ResPath.AdvancedFirmwareUpdate[Instances.APP].PackageURI, self._uri)\n\n        # 3. The Server sends repeated READs or OBSERVE on State and Update\n        #    Result Resources (CoAP GET /33629/0) of the FW Update Object\n        #    Instance to determine when the download is completed or if an error\n        #    occured.Before the end of download, the connection is intentionnaly\n        #    lost and the download cannot be finished.\n        # 4. When the Package delivery is stopped the Server READs Update\n        #    Result to know the result of the firmware update procedure.\n        #\n        # C. In test step 3., the State Resource value set to \"1\" retrieved from\n        #    successive Server READs or Client NOTIFY messages, indicates the\n        #    Package Delivery process is engaged in a Download stage\n        # D. In test step 3., the Update Result Resource (/33629/0/5) retrieved from\n        #    successive Server READs or Client NOTIFY messages will take the\n        #    value \"4\" indicating an error occurred during the downloading\n        #    process related to connection lost\n        # E. In test step 4., the success READ message(s) (status code \"2.05\"\n        #    Content) on State Resource with value \"0\" (Idle) and Update Result\n        #    Resource with value \"4\" indicates the firmware Package Delivery\n        #    aborted due to connection lost dur the Package delivery.\n        observed_values = self.collect_values(ResPath.AdvancedFirmwareUpdate[Instances.APP].State, b'0',\n                                              max_iterations=50, step_time=1)\n        self.assertEqual(b'0', observed_values[-1])\n        self.assertEqual({b'0', b'1'}, set(observed_values))\n        self.assertEqual(b'4', self.test_read(ResPath.AdvancedFirmwareUpdate[Instances.APP].UpdateResult))\n\n\nclass Test776_AdvancedFirmwareUpdate_ErrorCase_CRCCheckFail(AdvancedFirmwareUpdate.Test):\n    def runTest(self):\n        demo_executable = os.path.join(self.config.demo_path, self.config.demo_cmd)\n        with open(demo_executable, 'rb') as f:\n            payload = f.read()\n\n        # 1. The Server verifies through a READ (CoAP GET) command on\n        #    /33629/0/3 (State) the FW Update Object Instance of the Client is in Idle\n        #    State\n        #\n        # A. In test step 1, the Server receives the status code \"2.05 \" (Content) for\n        #    the READ success command, along with the State Resource value of\n        #    \"0\" (Idle)\n        self.assertEqual(b'0', self.test_read(ResPath.AdvancedFirmwareUpdate[Instances.APP].State))\n\n        # 2. The Server delivers the firmware package to the Client either through\n        #    a WRITE (CoAP PUT) operation in the Package Resource (/33629/0/0) or\n        #    through a WRITE operation of an URI in the Package URI Resource.\n        #\n        # B. In test step 2., the Server receives the status code \"2.04\" (Changed)\n        #    for the WRITE command setting either the Package URI Resource or\n        #    setting the Package Resource, according to the chosen firmware\n        #    delivery method.\n        self.test_write_block(ResPath.AdvancedFirmwareUpdate[Instances.APP].Package,\n                              make_firmware_package(payload,\n                                                    **self.FW_PKG_OPTS,\n                                                    crc=0),\n                              coap.ContentFormat.APPLICATION_OCTET_STREAM)\n\n        # 3. The Server sends repeated READs or OBSERVE on State and Update\n        #    Result Resources (CoAP GET /33629/0) of the FW Update Object\n        #    Instance to determine when the download is completed or if an error\n        #    occured. The firmware package Integry Check failure stopped the\n        #    download process.\n        # 4. When the Package delivery is stopped the server READs Update\n        #    Result to know the result of the firmware update procedure.\n        #\n        # C. In test step 3., the State Resource value set to \"1\" retrieved from\n        #    successive Server READs or Client NOTIFY messages, indicates the\n        #    Package Delivery process is maintained in Downloading stage\n        # D. In test step 3., the Update Result Resource (/33629/0/5) retrieved from\n        #    successive Server READs or Client NOTIFY messages will take the\n        #    value \"5\" indicating an error occurred during the downloading\n        #    process related to the failure of the firmware package integrity check\n        # E. In test step 4., the success READ message(s) (status code \"2.05\"\n        #    Content) on State Resource with value \"0\" (Idle) and Update Result\n        #    Resource with value \"5\" indicates the firmware Package Delivery\n        #    aborted due to a Firmware Package Integrity failure.\n        self.assertEqual(b'0', self.test_read(ResPath.AdvancedFirmwareUpdate[Instances.APP].State))\n        self.assertEqual(b'5', self.test_read(ResPath.AdvancedFirmwareUpdate[Instances.APP].UpdateResult))\n\n\nclass AdvancedFirmwareUpdate_ErrorCase_CRCCheckFail_PackageURI(AdvancedFirmwareUpdateWithHttpServer.Test):\n    def setUp(self):\n        demo_executable = os.path.join(self.config.demo_path, self.config.demo_cmd)\n        with open(demo_executable, 'rb') as f:\n            pkg = make_firmware_package(f.read(),\n                                        **self.FW_PKG_OPTS,\n                                        crc=0)\n\n        super().setUp(pkg)\n\n    def runTest(self):\n        # Test 776, but with Package URI\n        self.assertEqual(b'0', self.test_read(ResPath.AdvancedFirmwareUpdate[Instances.APP].UpdateResult))\n        self.test_write(ResPath.AdvancedFirmwareUpdate[Instances.APP].PackageURI, self.get_firmware_uri())\n\n        observed_values = self.collect_values(ResPath.AdvancedFirmwareUpdate[Instances.APP].State, b'0')\n        self.assertEqual(b'0', observed_values[-1])\n        self.assertEqual({b'0', b'1'}, set(observed_values))\n        self.assertEqual(b'5', self.test_read(ResPath.AdvancedFirmwareUpdate[Instances.APP].UpdateResult))\n\n\nclass Test777_AdvancedFirmwareUpdate_ErrorCase_UnsupportedPackageType(AdvancedFirmwareUpdate.Test):\n    def runTest(self):\n        # 1. The Server verifies through a READ (CoAP GET) command on\n        #    /33629/0/3 (State) the FW Update Object Instance of the Client is in Idle\n        #    State\n        #\n        # A. In test step 1, the Server receives the status code \"2.05 \" (Content) for\n        #    the READ success command, along with the State Resource value of\n        #    \"0\" (Idle)\n        self.assertEqual(b'0', self.test_read(ResPath.AdvancedFirmwareUpdate[Instances.APP].State))\n\n        # 2. The Server delivers the firmware package to the Client either through\n        #    a WRITE (CoAP PUT) operation in the Package Resource (/33629/0/0 ) or\n        #    through a WRITE operation of an URI in the Package URI Resource.\n        #\n        # B. In test step 2., the Server receives the status code \"2.04\" (Changed)\n        #    for the WRITE command setting either the Package URI Resource or\n        #    setting the Package Resource, according to the chosen firmware\n        #    delivery method.\n        self.test_write(ResPath.AdvancedFirmwareUpdate[Instances.APP].Package, b'A' * 1024,\n                        format=coap.ContentFormat.APPLICATION_OCTET_STREAM)\n\n        # 3. The Server sends repeated READs or OBSERVE on State and Update\n        #    Result Resources (CoAP GET /33629/0) of the FW Update Object\n        #    Instance to determine when the download is completed or if an error\n        #    occured. The Download cannot be finished since the firmware\n        #    package type is not supported by the Client\n        # 4. When the Package delivery is stopped the server READs Update\n        #    Result to know the result of the firmware update procedure.\n        #\n        # C. In test step 3., the State Resource value set to \"1\" retrieved from\n        #    successive Server READs or Client NOTIFY messages, indicates the\n        #    Package Delivery process is in Downloading stage\n        # D. In test step 3., the Update Result Resource (/33629/0/5) retrieved from\n        #    successive Server READs or Client NOTIFY messages will take the\n        #    value \"6\" indicating an error occurred during the downloading\n        #    process related to the firmware package type not supported by the\n        #    Client.\n        # E. In test step 4., the success READ message(s) (status code \"2.05\"\n        #    Content) on State Resource with value \"1\" (Downloading) and\n        #    Update Result Resource with value \"6 indicates the firmware Package\n        #    Delivery aborted due to a firmware package type not supported by the\n        #    Client.\n        self.assertEqual(b'0', self.test_read(ResPath.AdvancedFirmwareUpdate[Instances.APP].State))\n        self.assertEqual(b'6', self.test_read(ResPath.AdvancedFirmwareUpdate[Instances.APP].UpdateResult))\n\n\nclass AdvancedFirmwareUpdate_ErrorCase_UnsupportedPackageType_PackageURI(AdvancedFirmwareUpdateWithHttpServer.Test):\n    def setUp(self):\n        super().setUp(b'A' * 1024)\n\n    def runTest(self):\n        # Test 777, but with Package URI\n        self.assertEqual(b'0', self.test_read(ResPath.AdvancedFirmwareUpdate[Instances.APP].UpdateResult))\n        self.test_write(ResPath.AdvancedFirmwareUpdate[Instances.APP].PackageURI, self.get_firmware_uri())\n\n        observed_values = self.collect_values(ResPath.AdvancedFirmwareUpdate[Instances.APP].State, b'0')\n        self.assertEqual(b'0', observed_values[-1])\n        self.assertEqual({b'0', b'1'}, set(observed_values))\n        self.assertEqual(b'6', self.test_read(ResPath.AdvancedFirmwareUpdate[Instances.APP].UpdateResult))\n\n\nclass Test778_AdvancedFirmwareUpdate_ErrorCase_InvalidURI(AdvancedFirmwareUpdate.Test):\n    def runTest(self):\n        # 1. The Server verifies through a READ (CoAP GET) command on\n        #    /33629/0/3 (State) the FW Update Object Instance of the Client is in Idle\n        #    State\n        #\n        # A. In test step 1, the Server receives the status code \"2.05 \" (Content) for\n        #    the READ success command, along with the State Resource value of\n        #    \"0\" (Idle)\n        self.assertEqual(b'0', self.test_read(ResPath.AdvancedFirmwareUpdate[Instances.APP].State))\n\n        # 2. The Server initiates a firmware package delivery to the Client through\n        #    a WRITE operation of an invalid URI in the Package URI Resource.\n        #\n        # B. In test step 2., the Server receives the status code \"2.04\" (Changed)\n        #    for the WRITE command setting the Package URI Resource\n        self.test_write(ResPath.AdvancedFirmwareUpdate[Instances.APP].PackageURI,\n                        'http://mylovelyfwserver/')\n\n        # 3. The Server sends repeated READs or OBSERVE on State and Update\n        #    Result Resources (CoAP GET /33629/0) of the FW Update Object\n        #    Instance to determine when the download is completed or if an error\n        #    occured. The download process is stopped by the Client due to the\n        #    usage of a bad URI.\n        # 4. When the Package delivery is stopped the server READs Update\n        #    Result to know the result of the firmware update procedure.\n        # C. In test step 3., the State Resource value set to \"1\" retrieved from\n        #    successive Server READs or Client NOTIFY messages, indicates the\n        #    Package Delivery process is maintained in Downloading stage\n        # D. In test step 3., the Update Result Resource (/33629/0/5) retrieved from\n        #    successive Server READs or Client NOTIFY messages will take the\n        #    value \"7\" indicating an error occurred during the downloading\n        #    process related to the usage of a bad URI\n        # E. In test step 4., the success READ message(s) (status code \"2.05\"\n        #    Content) on State Resource with value \"0\" (Idle) and Update Result\n        #    Resource with value \"7\" indicates the firmware Package Delivery\n        #    aborted due to the connection to an Invalid URI for the firmware\n        #    package delivery.\n        observed_values = self.collect_values(ResPath.AdvancedFirmwareUpdate[Instances.APP].State, b'0')\n        self.assertEqual(b'0', observed_values[-1])\n        # TODO? client does not report \"Downloading\" state\n        # self.assertEqual({b'0', b'1'}, set(observed_values))\n        self.assertEqual({b'0'}, set(observed_values))\n        self.assertEqual(b'7', self.test_read(ResPath.AdvancedFirmwareUpdate[Instances.APP].UpdateResult))\n\n\nclass Test779_AdvancedFirmwareUpdate_ErrorCase_UnsuccessfulFirmwareUpdate(AdvancedFirmwareUpdate.Test):\n    def runTest(self):\n        demo_executable = os.path.join(self.config.demo_path, self.config.demo_cmd)\n        with open(demo_executable, 'rb') as f:\n            payload = f.read()\n\n        prev_version = self.test_read(ResPath.Device.FirmwareVersion)\n\n        # 1. Step 1 – Package Delivery\n        #    a. The Server verifies through a READ (CoAP GET) command on\n        #       /33629/0/3 (State) the FW Update Object Instance of the Client is in Idle\n        #       State\n        #\n        # A. Package Delivery\n        #    a. In test step 1.a, the Server receives the status code \"2.05 \" (Content)\n        #       for the READ success command, along with the State Resource value\n        #       of \"0\" (Idle)\n        self.assertEqual(b'0', self.test_read(ResPath.AdvancedFirmwareUpdate[Instances.APP].State))\n\n        # 1. Step 1 – Package Delivery\n        #    b. The Server retrieves (CoAP GET) the initial value of the Firmware\n        #       Version Resource from the Object Device Instance for verification in\n        #       the Pass Criteria (C)\n        #\n        # A. Package Delivery\n        #    b. In test step 1.b, the Server receives the status code \"2.05 \" (Content)\n        #       for the READ success command, along with the initial value of the\n        #       Firmware version Resource available from the Object Device\n        #       Instance.\n        prev_version = self.test_read(ResPath.Device.FirmwareVersion)\n\n        # 1. Step 1 – Package Delivery\n        #    c. The Server delivers the firmware package to the Client either\n        #       through a WRITE (CoAP PUT) operation in the Package Resource\n        #       (/33629/0/0 ) or through a WRITE operation of an URI in the Package URI\n        #       Resource.\n        #\n        # A. Package Delivery\n        #    c. In test step 1.c, the Server receives the status code \"2.04\" (Changed)\n        #       for the WRITE command setting either the Package URI Resource or\n        #       setting the Package Resource, according to the chosen firmware\n        #       delivery method.\n        self.test_write_block(ResPath.AdvancedFirmwareUpdate[Instances.APP].Package,\n                              make_firmware_package(payload,\n                                                    **self.FW_PKG_OPTS,\n                                                    force_error=PackageForcedError.Firmware.FailedUpdate),\n                              coap.ContentFormat.APPLICATION_OCTET_STREAM)\n\n        # 1. Step 1 – Package Delivery\n        #    d. Polling ( successive READ commands) or Notification on Update\n        #       Result and State Resources is performed, up to the time State\n        #       Resource takes the ‘Downloaded’ value (2)\n        #\n        # A. Package Delivery\n        #    d. In at this end of test step 1.d, the State Resource take the value \"2\"\n        #       (Downloaded)\n        self.assertEqual(b'2', self.test_read(ResPath.AdvancedFirmwareUpdate[Instances.APP].State))\n\n        # 2. Step 2 – Installation Failure\n        #    a. When the download is completed (State Resource value is ‘2’\n        #       Downloaded) , the Server initiates a firmware update by triggering\n        #       EXECUTE command on Update Resource (CoAP POST /33629/0/2 )\n        #\n        # B. Installation failure\n        #    a. In test step 2.a, the Server receives a success message \"2.04\"\n        #       (Changed) in response to the EXECUTE command\n        self.test_execute(ResPath.AdvancedFirmwareUpdate[Instances.APP].Update)\n\n        # 2. Step 2 – Installation Failure\n        #    b. Polling (READ command) or Notification on Update Result and\n        #       State Resources is performed, up to the time State Resource is\n        #       turned back to 2 (Downloaded) or the Update Result Resource\n        #       contains the value \"8\" (Firmware update failed )\n        #\n        # B. Installation failure\n        #    b. In test step 2.b, the Server receives success message(s) \"2.05\"\n        #       Contents along with a State Resource value of \"3\" (Updating) or \"2\"\n        #       (Downloaded) and an Update Ressource value of \"0\" (Initial Value)\n        #       and \"8\" at the end (Firmware updated failure)\n        observed_values = self.collect_values(ResPath.AdvancedFirmwareUpdate[Instances.APP].State, b'2')\n        self.assertEqual(b'2', observed_values[-1])\n        # state == 3 may or may not not be observed\n        self.assertTrue(set(observed_values).issubset({b'2', b'3'}))\n        self.assertEqual(b'8', self.test_read(ResPath.AdvancedFirmwareUpdate[Instances.APP].UpdateResult))\n\n        # 3. Step 3 – Process Verification\n        #    a. The server READs Update Result & State Resources to know the\n        #       result of the firmware update procedure.\n        #\n        # C. Process Verification\n        #    a. In test step 3.a, the Server receives success message(s) \"2.05\"\n        #       Content\" along with a State Resource value of \"2\" (Downloaded) and\n        #       an Update Ressource value of \"8\" (Firmware updated failed)\n        self.assertEqual(b'2', self.test_read(ResPath.AdvancedFirmwareUpdate[Instances.APP].State))\n        self.assertEqual(b'8', self.test_read(ResPath.AdvancedFirmwareUpdate[Instances.APP].UpdateResult))\n\n        # 3. Step 3 – Process Verification\n        #    b. The Server READs the Firmware Version Resource from the\n        #       Object Device Instance\n        #\n        # C. Process Verification\n        #    b. In test step 3.b the Server receives success message(s) \"2.05\" Content\"\n        #       along with a Firmware Version Resource value form the Object\n        #       Device Instance which has not changed compared to the one retrieved\n        #       in Pass Criteria A.b\n        self.assertEqual(prev_version, self.test_read(ResPath.Device.FirmwareVersion))\n\n\nclass AdvancedFirmwareUpdate_ErrorCase_UnsuccessfulFirmwareUpdate_PackageURI(AdvancedFirmwareUpdateWithHttpServer.Test):\n    def setUp(self):\n        demo_executable = os.path.join(self.config.demo_path, self.config.demo_cmd)\n        with open(demo_executable, 'rb') as f:\n            pkg = make_firmware_package(f.read(),\n                                        **self.FW_PKG_OPTS,\n                                        force_error=PackageForcedError.Firmware.FailedUpdate)\n\n        super().setUp(pkg)\n\n    def runTest(self):\n        # Test 777, but with Package URI\n        self.assertEqual(b'0', self.test_read(ResPath.AdvancedFirmwareUpdate[Instances.APP].UpdateResult))\n        prev_version = self.test_read(ResPath.Device.FirmwareVersion)\n\n        self.test_write(ResPath.AdvancedFirmwareUpdate[Instances.APP].PackageURI, self.get_firmware_uri())\n\n        observed_values = self.collect_values(ResPath.AdvancedFirmwareUpdate[Instances.APP].State, b'2')\n        self.assertEqual(b'2', observed_values[-1])\n        self.assertEqual({b'1', b'2'}, set(observed_values))\n\n        req = Lwm2mExecute(ResPath.AdvancedFirmwareUpdate[Instances.APP].Update)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n\n        observed_values = self.collect_values(ResPath.AdvancedFirmwareUpdate[Instances.APP].State, b'2')\n        self.assertEqual(b'2', observed_values[-1])\n        # state == 3 may or may not not be observed\n        self.assertTrue(set(observed_values).issubset({b'2', b'3'}))\n        self.assertEqual(b'8', self.test_read(ResPath.AdvancedFirmwareUpdate[Instances.APP].UpdateResult))\n\n        self.assertEqual(prev_version, self.test_read(ResPath.Device.FirmwareVersion))\n"
  },
  {
    "path": "tests/integration/suites/testfest/dm/connectivity_management.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nfrom framework.lwm2m_test import *\n\nfrom .utils import DataModel, ValueValidator as VV\n\n\nclass Test1200_QueryingReadableResourcesOfCellularConnectivityObject(DataModel.Test):\n    def runTest(self):\n        # 1. READ (CoAP GET /10/0) operation on the Instance of the Object\n        #    ID:10 is received by the Client and its answer is sent back to the\n        #    Server\n        #\n        # A. In test step 1 :the Server receives the success message (2.05 Content)\n        #    associated to its READ request\n        # B. In step 1: the values returned by the Client in TLV format is consistent\n        #    with the Configuration C 10. used for that test (Activated Profine Names,\n        #    Serving PLMN rate Control...).\n        self.test_read('/%d/0' % OID.CellularConnectivity,\n                       VV.tlv_instance(\n                           resource_validators={\n                               RID.CellularConnectivity.ServingPLMNRateControl: VV.from_raw_int(),\n                               RID.CellularConnectivity.ActivatedProfileNames:  VV.multiple_resource(VV.ascii_string()),\n                               # all other Resources are optional\n                           },\n                           ignore_extra=True))\n\n\nclass Test1201_QueryingReadableResourcesOfCellularConnectivityObjectInVersion1_1(DataModel.Test):\n    def runTest(self):\n        # 1. DISCOVER (CoAP GET Accept:40) operation is performed by the\n        #    Server on Object ID:10\n        #\n        # A. In test step 1, the Server – along with the success message 2.05 –\n        #    received the information related to Object ID:10 including\n        #    </10>;ver=\"1.1\",</10/0/6>,</10/0/11>, </10/0/13>, </10/0/14>\n        link_list = self.test_discover('/%d' % OID.CellularConnectivity).decode()\n        links = link_list.split(',')\n        self.assertIn('</%d>;ver=\"1.1\"' % (OID.CellularConnectivity,), links)\n        self.assertIn('<%s>' % (ResPath.CellularConnectivity.ServingPLMNRateControl,), links)\n        self.assertIn('<%s>' % (ResPath.CellularConnectivity.ActivatedProfileNames,), links)\n        self.assertIn('<%s>' % (ResPath.CellularConnectivity.PowerSavingModes,), links)\n        self.assertIn('<%s>' % (ResPath.CellularConnectivity.ActivePowerSavingModes), links)\n\n        # 2. READ (CoAP GET /10/0) operation on the Instance of the Object\n        #    ID:10 is received by the Client and its answer is sent back to the\n        #    Server\n        #\n        # B. In test step 2 :the Server receives the success message (2.05 Content)\n        #    associated to its READ request on Object Instance /10/0\n        # C. In test step 2: the values returned by the Client in TLV format is\n        #    consistent with the Configuration C 10. used for that test (Activated\n        #    Profine Names, Serving PLMN rate Control, Power Saving Modes,\n        #    Active Power Sa&ving Modes...).\n        self.test_read('/%d/0' % OID.CellularConnectivity,\n                       VV.tlv_instance(\n                           resource_validators={\n                               RID.CellularConnectivity.ServingPLMNRateControl: VV.from_raw_int(),\n                               RID.CellularConnectivity.ActivatedProfileNames:  VV.multiple_resource(VV.ascii_string()),\n                               RID.CellularConnectivity.PowerSavingModes:       VV.from_raw_int(),\n                               RID.CellularConnectivity.ActivePowerSavingModes: VV.from_raw_int(),\n                               # all other Resources are optional\n                           },\n                           ignore_extra=True))\n\nclass Test1210_SettingPowerSavingModeResourceOfCellularConnectivityObject(DataModel.Test):\n    def runTest(self):\n        # 1. DISCOVER (CoAP GET Accept:40) operation is performed by the\n        #    Server on Object ID:10\n        #\n        # A. In test step 1, the Server – along with the success message 2.05 –\n        #    received the information related to Object ID:10 including\n        #    </10>;ver=\"1.1\",</10/0/6>,</10/0/11>, </10/0/13>, </10/0/14>\n        link_list = self.test_discover('/%d' % OID.CellularConnectivity).decode()\n        links = link_list.split(',')\n        self.assertIn('</%d>;ver=\"1.1\"' % (OID.CellularConnectivity,), links)\n        self.assertIn('<%s>' % (ResPath.CellularConnectivity.ServingPLMNRateControl,), links)\n        self.assertIn('<%s>' % (ResPath.CellularConnectivity.ActivatedProfileNames,), links)\n        self.assertIn('<%s>' % (ResPath.CellularConnectivity.PowerSavingModes,), links)\n        self.assertIn('<%s>' % (ResPath.CellularConnectivity.ActivePowerSavingModes), links)\n\n        # 2. A READ (CoAP GET /10/0/13) operation on the Instance 0 of the\n        #    Object ID:10 is performed by the Server\n        #\n        # B. In test step 2 : the Server receives the success message (2.05 Content) and\n        #    the requested value of the \"Power Saving Modes\" Resource (/10/0/13).\n        #    This value must mach the value present in the C.11 configuration.\n        self.assertEqual(b'3', self.test_read(ResPath.CellularConnectivity.PowerSavingModes))\n\n        # 3. A WRITE operation is performed by the Server on the \"Active Power\n        #    Saving Modes\" Resource (CoAP PUT/POST 10/0/14) of the Object\n        #    ID:10 Instance 0, in using the set of values contains in the 1210-\n        #    SetOfValues sample below.The data format TLV is used. (11542)\n        #\n        # C. In step 3, the Server receives the success message (2.04 Changed) related\n        #    to the WRITE command\n        self.test_write(ResPath.CellularConnectivity.ActivePowerSavingModes, '2')\n\n        # 4. A READ (CoAP GET /10/0/14) operation is performed by the Server\n        #    on the Instance 0 of the Object ID:10\n        #\n        # D. In test step 4 : the Server receives the success message (2.05 Content)\n        #    associated to its READ request and the received value for the \"Active\n        #    Power Saving Modes\" Resource\" must match the value contains in the\n        #    1210-SetOfValues sample.\n        self.assertEqual(b'2', self.test_read(ResPath.CellularConnectivity.ActivePowerSavingModes))\n\n\nclass Test1230_ObservationAndNotificationOnCellularConnectivityObjectRelatedToPowerSavingModeResources(DataModel.Test):\n    def runTest(self):\n        # 1. DISCOVER (CoAP GET Accept:40) operation is performed by the\n        #    Server on Object ID:10\n        #\n        # A. In test step 1, the Server – along with the success message 2.05 –\n        #    received the information related to Object ID:10 including\n        #    </10>;ver=\"1.1\",</10/0/6>,</10/0/11>, </10/0/13>, </10/0/14>\n        link_list = self.test_discover('/%d' % OID.CellularConnectivity).decode()\n        links = link_list.split(',')\n        self.assertIn('</%d>;ver=\"1.1\"' % (OID.CellularConnectivity,), links)\n        self.assertIn('<%s>' % (ResPath.CellularConnectivity.ServingPLMNRateControl,), links)\n        self.assertIn('<%s>' % (ResPath.CellularConnectivity.ActivatedProfileNames,), links)\n        self.assertIn('<%s>' % (ResPath.CellularConnectivity.PowerSavingModes,), links)\n        self.assertIn('<%s>' % (ResPath.CellularConnectivity.ActivePowerSavingModes), links)\n\n        # 2. Server communicates with the Object ID:10 in version 1.1 via\n        #    WRITE-ATTRIBUTE (CoAP PUT) operations, to set the pmin &\n        #    pmax Attributes of each targeted Resources (e.g.\n        #    /10/0/13?pmin=5&pmax=15 and /10/0/14?pmin10&pmax=20)\n        # 3. Server sends OBSERVE (CoAP GET operation with Observe Option\n        #    set to 0) messages for the targeted Resources (ID:13 & ID:14 ) to\n        #    activate reporting\n        # 4. Client reports requested information with a NOTIFY message (CoAP\n        #    responses)\n        #\n        # B. In test step 2, the Server receives success messages (\"2.04\" Changed)\n        #    related to the WRITE-ATTRIBUTES operations\n        # C. In test step 3, the Server receives success messages (\"2.05\" Content)\n        #    along with the initial values of Resource ID:13 and ID:14\n        # D. In test step 4, the Server regularly receives consistent information on the\n        #    targeted Resources ID:13 & ID:14 of the Object ID:10 Instance 0.\n        self.test_observe('/%d/0' % OID.CellularConnectivity,\n                          VV.tlv_instance(\n                              resource_validators={\n                                  RID.CellularConnectivity.ServingPLMNRateControl: VV.from_raw_int(),\n                                  RID.CellularConnectivity.ActivatedProfileNames:  VV.multiple_resource(VV.ascii_string()),\n                                  RID.CellularConnectivity.PowerSavingModes:       VV.from_raw_int(),\n                                  RID.CellularConnectivity.ActivePowerSavingModes: VV.from_raw_int(),\n                                  # all other Resources are optional\n                              },\n                              ignore_extra=True),\n                          pmin=1, pmax=3)\n\n\nclass Test1250_ApnConfiguration(DataModel.Test):\n    def runTest(self):\n        # 1. CREATE (COAP POST) operation is performed by the Server\n        #    targeting APN Connection Profile Object (ID:11) to create a 2nd\n        #    instance of the APN connection profile Object with a new APN\n        #    which is not active yet.\n        #\n        # A. In test step 1., The Server receives a Success message (\"2.01\"\n        #    Created) associated to the Instantiation of the APN connection\n        #    profile Object.\n        new_iid = self.test_create('/%d' % OID.ApnConnectionProfile,\n                                   TLV.make_resource(RID.ApnConnectionProfile.ProfileName, 'NewProfile').serialize()\n                                   + TLV.make_resource(RID.ApnConnectionProfile.AuthenticationType, 3).serialize()) # 3 == None\n\n        # 2. The Server triggers a Registration Update Trigger for forcing an\n        #    UPDATE registration from the Client\n        #\n        # B. In test step 2., the Client receives a Registration Update Trigger\n        #    request on Resource ID:7 of the Device Object Instance (ID:3)\n        # C. In test step 2., the Server receives the Success message (\"2.04\"\n        #    Changed) associated to the Update Registration Request\n        self.test_execute(ResPath.Server[0].RegistrationUpdateTrigger)\n\n        # 3. UPDATE (registration) message (COAP POST) is sent from\n        #    Client to Server including information about the supported\n        #    Objects and Object Instances and namely the new Instance of\n        #    the APN Connection Profile Object\n        #\n        # D. In test step 3, the Server receives an UPDATE (registration)\n        #    operation from the Client along with the updated list of\n        #    Object/Object Instances in that Client. This list contains the new\n        #    Instance of the APN connection profile Object (/11/1)\n        pkt = self.serv.recv()\n        self.assertMsgEqual(Lwm2mUpdate(self.DEFAULT_REGISTER_ENDPOINT), pkt)\n        self.serv.send(Lwm2mChanged.matching(pkt)())\n\n        self.assertIn(b'</%d/%d>' % (OID.ApnConnectionProfile, new_iid), pkt.content)\n\n        # 4. Server activates the new APN Connection Profile in changing\n        #    Enable status to True by WRITE operation (COAP PUT /11/1/3)\n        #\n        # E. In test step 4, the Server receives the Success message\n        #    associated with the Server WRITE operation for the new APN\n        #    activation.\n        self.test_write(ResPath.ApnConnectionProfile[new_iid].EnableStatus, '1')\n\n        # 5. Server reads the list of active APN Connection Profiles by\n        #    performing a TLV READ targeting Resource ID:11 of Object\n        #    10 (/10/11)\n        # F. In test step 5., the Server receives a Success message (\"2.05\"\n        #    Content) along with the list of the active APN containing the\n        #    new Created APN (11:1)\n        # NOTE: In case the device only supports one active APN profile this test is\n        # passed when the new APN profile is activated.\n        self.test_read('/%d/0' % OID.CellularConnectivity,\n                       VV.tlv_instance(resource_validators={\n                               RID.CellularConnectivity.ActivatedProfileNames : VV.multiple_resource(VV.objlnk((OID.ApnConnectionProfile, new_iid))),\n                           },\n                           ignore_extra=True))\n"
  },
  {
    "path": "tests/integration/suites/testfest/dm/connectivity_monitoring.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nfrom framework.lwm2m_test import *\n\nfrom .utils import DataModel, ValueValidator as VV\n\n\nclass Test701_ConnectivityMonitoring_QueryingTheReadableResourcesOfObject(DataModel.Test):\n    def runTest(self):\n        integer_array = VV.multiple_resource(VV.from_raw_int())\n        string_array = VV.multiple_resource(VV.ascii_string())\n\n        # 1. READ (CoAP GET) operation is performed by the Server on the\n        #    Connectivity Monitoring Object Instance of the Client\n        #\n        # A. In test step 1., the Client received a READ (Coap GET) command\n        #    from the Server on the Connectivity Monitoring Object Instance\n        # B. In test step 1., the Server receives the status code \"2.05\" for READ\n        #    message success\n        # C. In test step 1., along with the success message, the mandatory\n        #    Resources (ID:0, 1,2, 4) and the optional ones, are received by the\n        #    Server with expected values in compliance with LwM2M technical\n        #    specification TS 1.0.\n        self.test_read('/%d/0' % OID.ConnectivityMonitoring,\n                       VV.tlv_instance(\n                           resource_validators={\n                               RID.ConnectivityMonitoring.NetworkBearer:          VV.from_raw_int(),\n                               RID.ConnectivityMonitoring.AvailableNetworkBearer: integer_array,\n                               RID.ConnectivityMonitoring.RadioSignalStrength:    VV.from_raw_int(),\n                               RID.ConnectivityMonitoring.IPAddresses:            string_array,\n                           },\n                           ignore_extra=True))\n\n\nclass Test710_ConnectivityMonitoring_ObservationAndNotificationOfObservableResources(DataModel.Test):\n    def runTest(self):\n        # 1. The Server communicates to the Client the pmin=2 and pmax=10\n        #    periods, threshold values with a WRITE ATTRIBUTE (CoAP\n        #    PUT) operation at the Connectivity Monitoring Object Instance\n        #    level\n        # 2. The Server sends OBSERVE (CoAP Observe Option) message\n        #    to activate reporting on the Monitoring Object Instance\n        # 3. The Client reports requested information with a NOTIFY\n        #    message (CoAP response)\n        #\n        # A. In test step 1., the Client has received a WRITE ATTRIBUTE\n        #    Command (CoAP PUT) targeting the Connectivity Monitoring\n        #    Object Instance with the proper pmin=2, and pmax=10\n        #    parameters\n        # B. In test step 1, the Server received the success message (2.04\n        #    Changed) in response to the WRITE ATTRIBUTE command\n        # C. In test step 2. the Client received the OBSERVE operation\n        #    targeting the Connectivity Monitoring Object Instance\n        # D. In test step 2., in response to its OBSERVE request, the Server\n        #    receives the success message (Content 2.05) along with the\n        #    Connectivity Object Instance initial values\n        # E. In test step 3., based on pmin/pmax periods parameters received\n        #    in test step 1., the Client reports Information to the Server with a\n        #    NOTIFY message containing the Connectivity Object Instance\n        #    updated values.\n        # F. In test step 3., the values receives by the Server along with the\n        #    success message (Content 2.05) must be as expected\n        self.test_observe('/%d/0' % OID.ConnectivityMonitoring,\n                          VV.tlv_instance(resource_validators={},\n                                          ignore_extra=True),\n                          pmin=2, pmax=10)\n"
  },
  {
    "path": "tests/integration/suites/testfest/dm/connectivity_statistics.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nimport time\n\nfrom framework.lwm2m_test import *\n\nfrom .utils import DataModel, ValueValidator as VV\n\n\nclass Test901_ConnectivityStatistics_QueryingADataCollectionFromConnectivityObjectInstance(DataModel.Test):\n    def runTest(self):\n        # 1. EXECUTE operation (CoAP POST) is performed on the Start\n        #    Resource of the Connectivity Statistics Object Instance\n        #\n        # A. In test step 1., the Client receives a Start command for the\n        #    Connectivity Statistics Object Instance (CoAP POST)\n        # B. In test step 1., the Server receives the success message (\"2.4\"\n        #    Changed) in response of its EXECUTE command on the Client\n        self.test_execute(ResPath.ConnectivityStatistics.Start)\n\n        # 2. After few seconds, an EXECUTE operation (CoAP POST) is\n        #    performed on the Stop Ressource of the Connectivity Statistics\n        #    Object Instance\n        #\n        # C. In test step 2., the Client receives a Stop command for the\n        #    Connectivity Statistics Object Instance (CoAP POST)\n        # D. In test step 2., the Server receives the success message (\"2.4\"\n        #    Changed) in response of its EXECUTE command on the Client\n        time.sleep(5)\n        self.test_execute(ResPath.ConnectivityStatistics.Stop)\n\n        # 3. A READ (CoAP GET) operation is performed by the Server on\n        #    the Connectivity Object Instance of the Client\n        #\n        # E. In test step 3., the Client receives a READ operation on the\n        #    Connectivity Object Instance\n        # F. In test step 3., the Server receives the status code \"2.05\" for the\n        #    READ message success\n        # G. In test step 3., along with the success message, the Server receives the\n        #    Connectivity Statistics Object Instance value according to the Client\n        #    preferred data format.\n        # H. In test step 3, the received Connectivity Statistics Object Instance\n        #    contains expected values in compliance with the LwM2M technical\n        #    specification 1.0, for the mandatory Resources (Network Bearer,\n        #    Available Network Bearer, Radio Signal Bearer, IP Address), and the\n        #    optional ones\n        #\n        # NOTE: listed Resources are from Connectivity Monitoring, not Statistics\n        self.test_read('/%d/0' % OID.ConnectivityStatistics,\n                       VV.tlv_instance(resource_validators={\n                           RID.ConnectivityStatistics.SMSTxCounter:       VV.from_raw_int(),\n                           RID.ConnectivityStatistics.SMSRxCounter:       VV.from_raw_int(),\n                           RID.ConnectivityStatistics.RxData:             VV.from_raw_int(),\n                           RID.ConnectivityStatistics.TxData:             VV.from_raw_int(),\n                           RID.ConnectivityStatistics.MaxMessageSize:     VV.from_raw_int(),\n                           RID.ConnectivityStatistics.AverageMessageSize: VV.from_raw_int(),\n                           RID.ConnectivityStatistics.CollectionPeriod:   VV.from_raw_int(),\n                        }, ignore_missing=True)) # all readable Resources are optional\n\n\nclass Test905_ConnectivityStatistics_SettingTheWritableResources(DataModel.Test):\n    def runTest(self):\n        # 1. A READ (CoAP GET) operation is performed by the Server on the\n        #    Connectivity Object Instance of the Client\n        #\n        # A. In test step 1., the Client receives a READ operation on the\n        #    Connectivity Object Instance\n        # B. In test step 1., the Server receives the status code \"2.05\" for the\n        #    READ success message along with the Connectivity Statistics Object\n        #    Instance value according to the Client preferred data format.\n        prev_values = self.test_read('/%d/0' % OID.ConnectivityStatistics)\n        prev_values_tlv = TLV.parse(prev_values)\n        prev_collection_period_raw = [x for x in prev_values_tlv if x.identifier == RID.ConnectivityStatistics.CollectionPeriod][0].value\n        prev_collection_period = struct.unpack('>Q', prev_collection_period_raw.rjust(8, b'\\0'))[0]\n\n        # 2. A WRITE (CoAP PUT) operation is performed on the Client\n        #    targeting the Collection Period Resource (ID:8) of the Connectivity\n        #    Object Instance with a value different of the one collected in step 2.\n        # 3. A new READ (CoAP GET) operation is performed by the Server on\n        #    the Connectivity Object Instance of the Client\n        #\n        # C. In step 2, the Client receives a WRITE command (CoAP PUT)\n        #    targeting the Collection Period of the Connectivity Statistics Object\n        #    Instance with a value different of the one collected in pass B.\n        # D. In test step 2, the Server received receives a success message (2.04\n        #    Changed) related to its WRITE operation\n        # E. In test step 3., the Client receives a new READ operation on the\n        #    Connectivity Object Instance\n        # F. In step 3, the Server receives success message (2.05 Content) and the\n        #    requested value in the LwM2M Client preferred data format\n        # G. The value of the Collection Period Resource value collected in Pass F,\n        #    correspond to the value which was set in step 2\n        self.test_write_validated(ResPath.ConnectivityStatistics.CollectionPeriod,\n                                  str(prev_collection_period + 1))\n\n\nclass Test910_ConnectivityStatistics_BasicObservationAndNotificationOnConnectivityStatisticsObjectInstance(DataModel.Test):\n    def runTest(self):\n        validator = VV.tlv_instance(resource_validators={\n                                        RID.ConnectivityStatistics.SMSTxCounter:       VV.from_raw_int(),\n                                        RID.ConnectivityStatistics.SMSRxCounter:       VV.from_raw_int(),\n                                        RID.ConnectivityStatistics.RxData:             VV.from_raw_int(),\n                                        RID.ConnectivityStatistics.TxData:             VV.from_raw_int(),\n                                        RID.ConnectivityStatistics.MaxMessageSize:     VV.from_raw_int(),\n                                        RID.ConnectivityStatistics.AverageMessageSize: VV.from_raw_int(),\n                                        RID.ConnectivityStatistics.CollectionPeriod:   VV.from_raw_int(),\n                                    }, ignore_missing=True) # all readable Resources are optional\n\n        # 1. The Server communicates to the Client pmin=2 & pmax=10\n        #    periods with a WRITE ATTRIBUTE (CoAP PUT) operation at\n        #    the Connectivity Statistics Instance level.\n        #\n        # A. In test step 1., the Server received a WRITE ATTRIBUTE command\n        #    (CoAP PUT) targeting the Connectivity Statistics Object Instance\n        #    with the proper pmin=2 and pmin=10 parameters.\n        # B. In test step 1., the Server received the success message (2.04\n        #    Changed) in response to the WRITE ATTRIBUTE command\n        self.test_write_attributes('/%d/0' % OID.ConnectivityStatistics,\n                                   pmin=2, pmax=10)\n\n        # 2. The Server set (CoAP PUT) the Collection Period Resource to 0\n        #    in the Connectivity Statistics Object Instance.\n        #\n        # C. In test step 2 the Client received the OBSERVE operation targeting\n        #    the Connectivity Statistics Object Instance\n        self.test_write(ResPath.ConnectivityStatistics.CollectionPeriod, '0')\n\n        # 3. The Server sends OBSERVE (CoAP Observe Option) message to\n        #    activate reporting on the Connectivity Statistics Instance.\n        #\n        # D. In test step2, in response to its OBSERVE operation, the Server\n        #    receives the success message (Content 2.05) along with the initial\n        #    values of the Connectivity Statistics Object Instance\n        req = Lwm2mObserve('/%d/0' % OID.ConnectivityStatistics)\n        self.serv.send(req)\n        res = self.serv.recv()\n        self.assertMsgEqual(Lwm2mContent.matching(req)(), res)\n        validator.validate(res.content)\n\n        # 4. The Server starts the data collection on the Connectivity Staistics\n        #    Object Instance by triggering the Start Resource of this Object\n        #    Instance (CoAP POST)\n        self.test_execute(ResPath.ConnectivityStatistics.Start)\n\n        # 5. The Client reports requested information with NOTIFY messages\n        #    (CoAP response)\n        # 6. Several NOTIFY messages are received by the Server related to\n        #    the Connectivity Statistics Object Instance\n        #\n        # E. In test step 3, based on pmin/pmax periods parameters received in test\n        #    step 1., the Client reports information to the Server with NOTIFY\n        #    messages containing the updated values of the Connectivity Statistics.\n        #\n        # F. In test step 3., the values returned by the Server along with the\n        #    success message (Content 2.05) must be as expected.\n        self.test_expect_notify(token=req.token, validator=validator, timeout_s=10.5)\n        self.test_expect_notify(token=req.token, validator=validator, timeout_s=10.5)\n\n        # 7. The Server stops the data collection on the Connectivity Staistics\n        #    Object Instance by triggering the Stop Resource of this Object\n        #    Instance (CoAP POST)\n        self.test_execute(ResPath.ConnectivityStatistics.Stop)\n\n        # 8. The Server stops the OBSERVATION process\n        self.test_cancel_observe('/%d/0' % OID.ConnectivityStatistics)\n"
  },
  {
    "path": "tests/integration/suites/testfest/dm/device.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nfrom framework.lwm2m_test import *\n\nfrom .utils import DataModel, ValueValidator as VV\n\n\nclass Test651_DeviceObject_QueryingTheReadableResourcesOfObject(DataModel.Test):\n    def runTest(self):\n        # Precondition:\n        # The Initial values of the Device Object (ID:3) Instance, are saved on\n        # the Server\n        rids_to_restore = (RID.Device.CurrentTime,\n                           RID.Device.UTCOffset,\n                           RID.Device.Timezone)\n        prev_values = self.test_read('/%d/0' % OID.Device)\n        prev_values_tlv = TLV.parse(prev_values)\n        values_to_restore = [x for x in prev_values_tlv if x.identifier in rids_to_restore]\n\n        tlv = TLV.make_instance(0, [\n            TLV.make_resource(RID.Device.CurrentTime, 1367491215),\n            TLV.make_resource(RID.Device.UTCOffset, '+02:00'),\n            TLV.make_resource(RID.Device.Timezone, 'Europe/Paris')\n            ])\n\n        # 1. A 1st WRITE (CoAP POST) operation on the Device Object (ID:3)\n        #    Instance is performed in using the set of values contains in the\n        #    651-SetOfValues sample below.The data format TLV is used. (11542)\n        self.test_write('/%d/0' % OID.Device, tlv.serialize(),\n                        coap.ContentFormat.APPLICATION_LWM2M_TLV,\n                        update=True)\n\n        # 2. The Server READs the result of the WRITE operation by querying\n        #    the Device Object (ID:3) Instance.\n        self.test_read('/%d/0' % OID.Device,\n                       VV.tlv_instance(\n                           resource_validators={\n                               RID.Device.CurrentTime: VV.from_raw_int(),\n                               RID.Device.UTCOffset:   VV.ascii_string('+02:00'),\n                               RID.Device.Timezone:    VV.ascii_string('Europe/Paris'),\n                           },\n                           ignore_extra=True))\n\n        # 3. A 2nd WRITE (CoAP PUT) operation on the Device Object (ID:3)\n        #    Instance is performed in using the Initial values which have been\n        #    preserved (pre-conditions).\n        self.test_write('/%d/0' % OID.Device,\n                        b''.join(tlv.serialize() for tlv in values_to_restore),\n                        coap.ContentFormat.APPLICATION_LWM2M_TLV)\n\n        # 4. The Server READs the result of the previous WRITE operation by\n        #    querying the Server Object (ID:3) Instance.\n\n        def filter_out_dynamic_resources(tlv):\n            return b''.join(x.serialize() for x in TLV.parse(tlv) \\\n                    if x.identifier not in (RID.Device.CurrentTime,\n                                            RID.Device.PowerSourceVoltage,\n                                            RID.Device.PowerSourceCurrent))\n\n        self.assertEqual(filter_out_dynamic_resources(prev_values),\n                         filter_out_dynamic_resources(self.test_read('/%d/0' % OID.Device)))\n\n        # A. The Server receives the correct status codes for the steps 1.\n        #    (2.04), 2. (2.05), 3. (2.04), & 4 (2.05) of the test.\n        # B. In test step 3., the received values are as expected (readable\n        #    resources) and consistent with the 651-SetOfValues sample.\n        # C. In test step 7, the received values are consistent with the values\n        #    present in the initial Configuration C.3\n\n\nclass Test652_DeviceObject_QueryingTheFirmwareVersionFromTheClient(DataModel.Test):\n    def runTest(self):\n        # 1. READ (CoAP GET) operation is performed on Device Object\n        #    Resource \"Firmware Version\"\n        #\n        # A. In test step 2, the Server received the requested information\n        #    (Firmware Version ) in the data format preferred by the Client\n        # B. In test step 2 the Server receives the success message (2.05 Content)\n        self.test_read(ResPath.Device.FirmwareVersion, VV.ascii_string(), coap.ContentFormat.TEXT_PLAIN)\n\n\nclass Test680_CreateObjectInstance(DataModel.Test):\n    def runTest(self):\n        # 1. CREATE (CoAP POST) operation is performed on Device Object\n        #\n        # A. In test step 1, the Server receives the failure message\n        #    (4.05 Method Not Allowed)\n        req = Lwm2mCreate('/%d' % OID.Device)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mErrorResponse.matching(req)(coap.Code.RES_METHOD_NOT_ALLOWED),\n                            self.serv.recv())\n\n\nclass Test685_DeleteObjectInstance(DataModel.Test):\n    def runTest(self):\n        # 1. DELETE (CoAP DELETE) operation is performed on the\n        #    Device Object Instance (/3/0)\n        #\n        # A. In test step 1, the Server received the requested information\n        #    (Firmware Version ) in the data format preferred by the Client\n        # B. In test step 1 the Server receives the failure message (4.05 Method\n        #    Not Allowed)\n        #\n        # NOTE: A. is clearly invalid.\n        req = Lwm2mDelete('/%d/0' % OID.Device)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mErrorResponse.matching(req)(coap.Code.RES_METHOD_NOT_ALLOWED),\n                            self.serv.recv())\n"
  },
  {
    "path": "tests/integration/suites/testfest/dm/firmware_update.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nimport contextlib\nimport http\nimport os\nimport resource\nimport socket\nimport threading\nimport time\n\nfrom framework.lwm2m_test import *\nfrom framework.create_package import PackageForcedError, make_firmware_package\n\nfrom .utils import DataModel, ValueValidator as VV\n\n\nclass FirmwareUpdate:\n    class Test(DataModel.Test):\n        def collect_values(self, path: Lwm2mPath, final_value, max_iterations=300, step_time=0.1):\n            observed_values = []\n            orig_timeout = self.serv.get_timeout()\n            try:\n                deadline = time.time() + max_iterations * step_time\n                while True:\n                    timeout = max(deadline - time.time(), 0.0)\n                    self.serv.set_timeout(timeout)\n                    try:\n                        state = self.test_read(path)\n                    except socket.timeout:\n                        break\n                    observed_values.append(state)\n                    if state == final_value:\n                        break\n                    time.sleep(step_time)\n                return observed_values\n            finally:\n                self.serv.set_timeout(orig_timeout)\n\n        def setUp(self, extra_cmdline_args=[]):\n            self.ANJAY_MARKER_FILE = generate_temp_filename(dir='/tmp', prefix='anjay-fw-updated-')\n            super().setUp(fw_updated_marker_path=self.ANJAY_MARKER_FILE, extra_cmdline_args=extra_cmdline_args)\n\n        def tearDown(self):\n            # reset the state machine\n            # Write /5/0/1 (Firmware URI)\n            req = Lwm2mWrite(ResPath.FirmwareUpdate.PackageURI, '')\n            self.serv.send(req)\n            self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.serv.recv())\n            super().tearDown()\n\n    class TestWithCoapServer(Test):\n        def setUp(self, coap_server=None, extra_cmdline_args=[]):\n            super().setUp(extra_cmdline_args=extra_cmdline_args)\n\n            from framework_tools.coap_file_server import CoapFileServerThread\n            self.server_thread = CoapFileServerThread(coap_server=coap_server)\n            self.server_thread.start()\n\n        @property\n        def file_server(self):\n            return self.server_thread.file_server\n\n        def tearDown(self):\n            try:\n                super().tearDown()\n            finally:\n                self.server_thread.join()\n\n\nclass FirmwareUpdateWithHttpServer:\n    class Test(FirmwareUpdate.Test):\n        FIRMWARE_PATH = '/firmware'\n        HTTP_SERVER_CLASS = http.server.HTTPServer\n\n        def get_firmware_uri(self):\n            return 'http://127.0.0.1:%d%s' % (self.http_server.server_address[1], self.FIRMWARE_PATH)\n\n        def before_download(self):\n            pass\n\n        def during_download(self, request_handler):\n            pass\n\n        def setUp(self, firmware_package):\n            super().setUp()\n\n            test_case = self\n\n            class FirmwareRequestHandler(http.server.BaseHTTPRequestHandler):\n                def do_GET(self):\n                    test_case.requests.append(self.path)\n                    test_case.before_download()\n\n                    self.send_response(http.HTTPStatus.OK)\n                    self.send_header('Content-type', 'application/octet-stream')\n                    self.send_header('Content-length', len(firmware_package))\n                    self.end_headers()\n\n                    # give the test some time to read \"Downloading\" state\n                    time.sleep(1)\n\n                    test_case.during_download(self)\n                    self.wfile.write(firmware_package)\n\n                def log_request(code='-', size='-'):\n                    # don't display logs on successful request\n                    pass\n\n            class SilentServer(self.HTTP_SERVER_CLASS):\n                def handle_error(self, *args, **kwargs):\n                    # don't log BrokenPipeErrors\n                    if not isinstance(sys.exc_info()[1], BrokenPipeError):\n                        super().handle_error(*args, **kwargs)\n\n            self.requests = []\n            self.http_server = SilentServer(('', 0), FirmwareRequestHandler)\n\n            self.server_thread = threading.Thread(target=lambda: self.http_server.serve_forever())\n            self.server_thread.start()\n\n        def tearDown(self):\n            try:\n                super().tearDown()\n            finally:\n                self.http_server.shutdown()\n                self.server_thread.join()\n\n            # there should be exactly one request\n            self.assertEqual([self.FIRMWARE_PATH], self.requests)\n\n\nclass Test751_FirmwareUpdate_QueryingTheReadableResources(FirmwareUpdate.Test):\n    def runTest(self):\n        # 1. READ (CoAP GET) operation is performed on the Firmware Update\n        #    Object Instance\n        #\n        # A. In test step 1, the Server receives the status code \"2.05\" for\n        #    READ operation success\n        # B. In test step 1, the returned values regarding State (ID:3) and\n        #    Update Result (ID:5) prove the Client FW update Capability is in\n        #    initial state (State=Idle & Update Result= Initial Value).\n        # C. In test step 1, the returned values regarding Firmware Update\n        #    Protocol Support (ID:8) & Firmware Update Delivery Method\n        #    (ID:9) allow to determine the supported characteristics of the\n        #    Client FW Update Capability.\n        self.test_read('/%d/0' % OID.FirmwareUpdate,\n                       VV.tlv_instance(\n                           resource_validators={\n                               RID.FirmwareUpdate.State:                         VV.from_raw_int(0),\n                               RID.FirmwareUpdate.UpdateResult:                  VV.from_raw_int(0),\n                               RID.FirmwareUpdate.FirmwareUpdateProtocolSupport: VV.multiple_resource(VV.from_values(b'\\x00', b'\\x01', b'\\x02', b'\\x03', b'\\x04', b'\\x05')),\n                               RID.FirmwareUpdate.FirmwareUpdateDeliveryMethod:  VV.from_raw_int(2),\n                           },\n                           ignore_extra=True))\n\n\nclass Test755_FirmwareUpdate_SettingTheWritableResourcePackage(FirmwareUpdate.Test):\n    def runTest(self):\n        # 1. A WRITE (CoAP PUT) operation with a NULL value ('\\0') is\n        #    performed by the Server on the Package Resource (ID:0) of the\n        #    FW Update Object Instance\n        #\n        # A. In test step 1, the Server receives the success message \"2.04\"\n        #    associated with the WRITE operation\n        self.test_write(ResPath.FirmwareUpdate.Package, b'\\0',\n                        format=coap.ContentFormat.APPLICATION_OCTET_STREAM)\n\n        # 2. The Server READs (CoAP GET) the FW Object Instance to get\n        #    the values of the State (ID:3) and Update Result (ID:5) Resources\n        #\n        # B. In test step 2, the Server receives the success message \"2.05\" along\n        #    with the value of State and Update Result Resources values.\n        # C. In test step 2, the queried State and Update Result Resources values\n        #    are both 0 (Idle / Initial value): FW Update Object Instance is in the\n        #    Initial state.\n        self.test_read('/%d/0' % OID.FirmwareUpdate,\n                       VV.tlv_instance(\n                           resource_validators={\n                               RID.FirmwareUpdate.State:        VV.from_raw_int(0),\n                               RID.FirmwareUpdate.UpdateResult: VV.from_raw_int(0),\n                           },\n                           ignore_extra=True))\n\n        # 3. A WRITE (CoAP PUT) operation with a valid image is\n        #    performed by the Server on the Package Resource (ID:0) of the\n        #    FW Update Object Instance\n        #\n        # D. In test step 3, the Server receives the success message \"2.04\"\n        #    associated with the WRITE request for loading the firmware image.\n        self.test_write(ResPath.FirmwareUpdate.Package,\n                        make_firmware_package(b''),\n                        format=coap.ContentFormat.APPLICATION_OCTET_STREAM)\n\n        # 4. The Server READs (CoAP GET) the FW Object Instance to get\n        #    the values of the State (ID:3) and Update Result (ID:5) Resources\n        #\n        # E. In test step 4, the Server receives the success message \"2.05\" along\n        #    with the State and Update Result Resources values.\n        # F. In test step 4, the queried value of State resource is 2 (Downloaded)\n        #    and the value of Update Result value is still 0 (Initial Value)\n        self.test_read('/%d/0' % OID.FirmwareUpdate,\n                       VV.tlv_instance(\n                           resource_validators={\n                               RID.FirmwareUpdate.State:        VV.from_raw_int(2),\n                               RID.FirmwareUpdate.UpdateResult: VV.from_raw_int(0),\n                           },\n                           ignore_extra=True))\n\n\nclass Test756_FirmwareUpdate_SettingTheWritableResourcePackageURI(FirmwareUpdateWithHttpServer.Test):\n    def setUp(self):\n        super().setUp(make_firmware_package(b''))\n\n    def runTest(self):\n        # 1. A WRITE (CoAP PUT) operation with an empty string value is\n        #    performed by the Server on the Package Resource (ID:0) of the FW\n        #    Update Object Instance\n        #\n        # A. In test step 1, the Server receives the success message \"2.04\"\n        #    associated with the WRITE operation\n        self.test_write(ResPath.FirmwareUpdate.PackageURI, b'')\n\n        # 2. The Server READs (CoAP GET) the FW Object Instance to get the\n        #    values of the State (ID:3) and Update Result (ID:5) Resources\n        #\n        # B. In test step 2, the Server receives the success message \"2.05\" along\n        #    with the value of State and Update Result Resources values.\n        # C. In test step 2, the queried State and Update Result Resources values\n        #    are both 0 (Idle / Initial value): FW Update Object Instance is in the\n        #    Initial state.\n        self.test_read('/%d/0' % OID.FirmwareUpdate,\n                       VV.tlv_instance(\n                           resource_validators={\n                               RID.FirmwareUpdate.State:        VV.from_raw_int(0),\n                               RID.FirmwareUpdate.UpdateResult: VV.from_raw_int(0),\n                           },\n                           ignore_extra=True))\n\n        # 3. A WRITE (CoAP PUT) operation with a valid image is performed by\n        #    the Server on the Package Resource (ID:0) of the FW Update Object\n        #    Instance\n        #\n        # D. In test step 3, the Server receives the success message \"2.04\"\n        #    associated with the WRITE request for the loadded image.\n        self.test_write(ResPath.FirmwareUpdate.PackageURI,\n                        self.get_firmware_uri())\n\n        # give the client some time to download firmware\n        time.sleep(3)\n\n        # 4. The Server READs (CoAP GET) the FW Object Instance to get the\n        #    values of the State (ID:3) and Update Result (ID:5) Resources\n        #\n        # E. In test step 4, the Server receives the success message \"2.05\" along\n        #    with the State and Update Result Resources values.\n        # F. In test step 4, the queried value of State resource is 2 (Downloaded)\n        #    and the value of Update Result value is still 0 (Initial Value)\n        self.test_read('/%d/0' % OID.FirmwareUpdate,\n                       VV.tlv_instance(\n                           resource_validators={\n                               RID.FirmwareUpdate.State:        VV.from_raw_int(2),\n                               RID.FirmwareUpdate.UpdateResult: VV.from_raw_int(0),\n                           },\n                           ignore_extra=True))\n\n\nclass Test760_FirmwareUpdate_BasicObservationAndNotificationOnFirmwareUpdateObjectResources(FirmwareUpdate.Test):\n    def runTest(self):\n        # 1. The Server communicates to the Client pmin=2 and pmax=10\n        #    periods with a WRITE-ATTRIBUTE (CoAP PUT) operation at\n        #    the FW Update Object Instance level.\n        #\n        # A. In test step 1, the Server receives the success message \"2.04\" associated\n        #    with the WRITE-ATTRIBUTE operation.\n        self.test_write_attributes('/%d/0' % OID.FirmwareUpdate,\n                                   pmin=2, pmax=10)\n\n        # 2. The Server Sends OBSERVE (CoAP Observe Option) message\n        #    to activate reporting on the State Resource (/5/0/3) of the FW\n        #    Update Object Instance.\n        #\n        # B. In test step 2, the Server receives the success message \"2.05\" associated\n        #    with the OBSERVE operation, along with the value of State =Idle\n        req = Lwm2mObserve(ResPath.FirmwareUpdate.State)\n        self.serv.send(req)\n        res = self.serv.recv()\n        self.assertMsgEqual(Lwm2mContent.matching(req)(content=b'0'), res)\n\n        # 3. The Server delivers the firmware to the Client through a WRITE\n        #    (CoAP PUT) operation on the Package Resource (/5/0/0)\n        #\n        # C. In test step 3, the Server receives the success message \"2.04\" associated\n        #    with the WRITE operation delivering the firmaware image.\n        self.test_write(ResPath.FirmwareUpdate.Package,\n                        make_firmware_package(b''),\n                        format=coap.ContentFormat.APPLICATION_OCTET_STREAM)\n\n        # 4. The Client reports requested information with a NOTIFY\n        #    message (CoAP response)\n        #\n        # D. In test step 4, the State Resource value returned by the Client in NOTIFY\n        #    message is set to \"Downloaded\"\n        req = Lwm2mObserve(ResPath.FirmwareUpdate.State)\n        self.serv.send(req)\n        res = self.serv.recv(timeout_s=3)\n        self.assertMsgEqual(Lwm2mContent.matching(req)(content=b'2'), res)\n\n\nclass Test770_FirmwareUpdate_SuccessfulFirmwareUpdateViaCoAP(FirmwareUpdate.Test):\n    def runTest(self):\n        demo_executable = os.path.join(self.config.demo_path, self.config.demo_cmd)\n        with open(demo_executable, 'rb') as f:\n            payload = f.read()\n\n        prev_version = self.test_read(ResPath.Device.FirmwareVersion)\n\n        # 1. Step 1 – Package Delivery\n        #    a. The Server places the Client in the initial state of the FW Update\n        #       process : A WRITE (CoAP PUT) operation with a NULL value\n        #       (‘\\0’) is performed by the Server on the Package Resource\n        #       (ID:0) of the FW Update Object Instance\n        #\n        # A. Step 1 – Package Delivery\n        #    a. In the test step 1.a, the Server receives the status code \"2.04\" for\n        #       the WRITE success setting the Client in the FW update initial\n        #       state.\n        #    d. Update Result is \"0\" (Initial Value) during the whole step\n        self.test_write(ResPath.FirmwareUpdate.Package, b'\\0',\n                        coap.ContentFormat.APPLICATION_OCTET_STREAM)\n\n        self.assertEqual(b'0', self.test_read(ResPath.FirmwareUpdate.State))\n\n        # 1. Step 1 – Package Delivery\n        #    b. The Server delivers the firmware to the Client through a WRITE\n        #       (CoAP PUT) operation on the Package Resource (/5/0/0)\n        #\n        # A. Step 1 – Package Delivery\n        #    b. In the test step 1.b, The Server receives success message with\n        #       either a \"2.31\" status code (Continue) or a final \"2.04\" status\n        #       code.\n        self.test_write_block(ResPath.FirmwareUpdate.Package,\n                              make_firmware_package(payload),\n                              coap.ContentFormat.APPLICATION_OCTET_STREAM)\n\n        # 1. Step 1 – Package Delivery\n        #    c. Polling (READ command) or Notification on Update Result\n        #       and State Resources is performed, up to the time State Resource\n        #       takes the ‘Downloaded’ value (2)\n        #\n        # A. Step 1 – Package Delivery\n        #    c. In the test step 1.c State Resource can take the value \"1\"\n        #       (Downloading) during this sub-step and will take the value \"2\" at\n        #       the end (Downloaded)\n        #    d. Update Result is \"0\" (Initial Value) during the whole step\n        self.assertEqual(b'2', self.test_read(ResPath.FirmwareUpdate.State))\n        self.assertEqual(b'0', self.test_read(ResPath.FirmwareUpdate.UpdateResult))\n\n        # 2. Step 2 – Firmware Update\n        #    a. When the download is completed (State Resource value is ‘2’\n        #       Downloaded) , the Server initiates a firmware update by\n        #       triggering EXECUTE command on Update Resource (CoAP\n        #       POST /5/0/2)\n        #\n        # B. Step 2 – Firmware Update\n        #    a. In test step 2.a, the Server receives a success message \"2.04\"\n        #       (Changed) in response to the EXECUTE command\n        self.test_execute(ResPath.FirmwareUpdate.Update)\n        # not supported: Updating state only observable via Observe\n        # self.assertEqual(b'3', self.test_read(ResPath.FirmwareUpdate.State))\n\n        # 2. Step 2 – Firmware Update\n        #    b. Polling (READ command) or Notification on Update Result and\n        #       State Resources is performed, up to the time State Resource is\n        #       turned back to Idle value (0) or Update Result Resource contains\n        #       an other value than the Initial one (0)\n        #\n        # B. Step 2 – Firmware Update\n        #    b. In test step 2.b, the Server receives success message(s) \"2.05\"\n        #       Contents along with a State Resource value of \"3\" (Updating)\n        #       or \"0\" (Idle) and an Update Ressource value of \"0\" (Initial\n        #       Value) or \"1\" (Firmware updated successfully)\n        self.serv.reset()\n        self.assertDemoRegisters()\n\n        # 3. Step 3 – Process verification\n        #    a. The Server READs Update Result (\"/5/0/5\") and State (\"/5/0/3\")\n        #       Resources to know the result of the firmware update procedure.\n        #\n        # C. Step 3 – Process verification\n        #    a. In test step 3.a, the Server receives success message(s) \"2.05\"\n        #       Content\" along with a State Resource value of \"0\" (Idle) and an\n        #       Update Ressource value of \"1\" (Firmware updated successfully)\n        self.assertEqual(b'0', self.test_read(ResPath.FirmwareUpdate.State))\n        self.assertEqual(b'1', self.test_read(ResPath.FirmwareUpdate.UpdateResult))\n\n        # 3. Step 3 – Process verification\n        #    b. The Server READs the Resource \"Firmware Update\" from the\n        #       Object Device Instance (\"/3/0/3\")\n        #\n        # C. Step 3 – Process verification\n        #    b. In test step 3.b, the Server receives success message \"2.05\"\n        #       Content\" along with the expected value of the Resource\n        #       Firmware Version from the Object Device Instance\n        #\n        # TODO: we currently update firmware with an identical executable,\n        # so the version does not change\n        self.assertEqual(prev_version, self.test_read(ResPath.Device.FirmwareVersion))\n\n\nclass Test771_FirmwareUpdate_SuccessfulFirmwareUpdateViaAlternateMechanism(FirmwareUpdateWithHttpServer.Test):\n    def setUp(self):\n        demo_executable = os.path.join(self.config.demo_path, self.config.demo_cmd)\n        with open(demo_executable, 'rb') as f:\n            pkg = make_firmware_package(f.read())\n\n        super().setUp(pkg)\n\n    def runTest(self):\n        # In this test the package version stays the same after update\n        prev_version = self.test_read(ResPath.Device.FirmwareVersion)\n\n        # 1. Step 1 – Package Delivery\n        #    a. The Server places the Client in the initial state of the FW\n        #       Update process : A WRITE (CoAP PUT) operation with an\n        #       empty string value is performed by the Server on the Package\n        #       URI Resource (ID:1) of the FW Update Object Instance\n        #\n        # A. Step 1 – Package Delivery\n        #    a. In the test step 1.a, the Server receives the status code \"2.04\"\n        #       for the WRITE success setting the Client in the FW update\n        #       initial state.\n        #    e. Update Result is \"0\" (Initial Value) during the whole test\n        #       step 1\n        self.test_write(ResPath.FirmwareUpdate.PackageURI, '')\n        self.assertEqual(b'0', self.test_read(ResPath.FirmwareUpdate.UpdateResult))\n\n        # 1. Step 1 – Package Delivery\n        #    b. The Server delivers the Package URI to the Client through a\n        #       WRITE (CoAP PUT) operation on the Package URI Resource\n        #       (/5/0/1)\n        #\n        # A. Step 1 – Package Delivery\n        #    b. In the test step 1.b, the Server receives the status code \"2.04\"\n        #       for the WRITE success setting the Package URI Client in the\n        #       FW update Object Instance\n        self.test_write(ResPath.FirmwareUpdate.PackageURI,\n                        self.get_firmware_uri())\n        self.assertEqual(b'0', self.test_read(ResPath.FirmwareUpdate.UpdateResult))\n\n        # 1. Step 1 – Package Delivery\n        #    c. The Client downloads the firmware from the provided URI via\n        #       an alternative mechanism (not CoAP)\n        #    d. Polling ( successive READ commands) or Notification on\n        #       Update Result and State Resources is performed, up to the time\n        #       State Resource takes the ‘Downloaded’ value (2)\n        #\n        # A. Step 1 – Package Delivery\n        #    c. In the test step 1.c, The Server receives success message\n        #       with either a \"2.31\" status code (Continue) or a final \"2.04\"\n        #       status code.\n        #    d. In the test step 1.d State Resource can take the value \"1\"\n        #       (Downloading) during this sub-step and will take the value\n        #       \"2\" at the end (Downloaded)\n        #    e. Update Result is \"0\" (Initial Value) during the whole test\n        #       step 1\n        observed_states = self.collect_values(ResPath.FirmwareUpdate.State, b'2')\n        self.assertEqual(b'2', observed_states[-1])\n        self.assertIn(set(observed_states), [{b'0', b'1', b'2'}, {b'1', b'2'}])\n\n        self.assertEqual(b'0', self.test_read(ResPath.FirmwareUpdate.UpdateResult))\n\n        # 2. Step 2 – Firmware Update\n        #    a. When the download is completed (State Resource value is ‘2’\n        #       Downloaded) , the Server initiates a firmware update by\n        #       triggering EXECUTE command on Update Resource (CoAP\n        #       POST /5/0/2 )\n        #\n        # B. Step 2 – Firmware Update\n        #    a. In test step 2.a, the Server receives a success message \"2.04\"\n        #       (Changed) in response to the EXECUTE command\n        self.test_execute(ResPath.FirmwareUpdate.Update)\n        # not supported: Updating state only observable via Observe\n        # self.assertEqual(b'3', self.test_read(ResPath.FirmwareUpdate.State))\n\n        # 2. Step 2 – Firmware Update\n        #    b. Polling (READ command) or Notification on Update Result and\n        #       State Resources is performed, up to the time State Resource is\n        #       turned back to Idle value (0) or Update Result Resource contains\n        #       an other value than the Initial one (0)\n        #\n        # B. Step 2 – Firmware Update\n        #    b. In test step 2.b, the Server receives success message(s) \"2.05\"\n        #       Contents along with a State Resource value of \"3\" (Updating)\n        #       or \"0\" (Idle) and an Update Ressource value of \"0\" (Initial\n        #       Value) or \"1\" (Firmware updated successfully)\n        self.serv.reset()\n        self.assertDemoRegisters()\n\n        # 3. Step 3 – Process verification\n        #    a. The Server READs Update Result (\"/5/0/5\") and State (\"/5/0/3\")\n        #       Resources to know the result of the firmware update procedure.\n        #\n        # C. Step 3 – Process verification\n        #    a. In test step 3.a, the Server receives success message(s) \"2.05\"\n        #       Content\" along with a State Resource value of \"0\" (Idle) and an\n        #       Update Ressource value of \"1\" (Firmware updated successfully)\n        self.assertEqual(b'0', self.test_read(ResPath.FirmwareUpdate.State))\n        self.assertEqual(b'1', self.test_read(ResPath.FirmwareUpdate.UpdateResult))\n\n        # 3. Step 3 – Process verification\n        #    b. The Server READs the Resource \"Firmware Update\" from the\n        #       Object Device Instance (\"/3/0/3\")\n        #\n        # C. Step 3 – Process verification\n        #    b. In test step 3.b, the Server receives success message \"2.05\"\n        #       Content\" along with the expected value of the Resource\n        #       Firmware Version from the Object Device Instance\n        #\n        # TODO: we currently update firmware with an identical executable,\n        # so the version does not change\n        self.assertEqual(prev_version, self.test_read(ResPath.Device.FirmwareVersion))\n\n\nclass Test772_FirmwareUpdate_ErrorCase_FirmwarePackageNotDownloaded(FirmwareUpdate.Test):\n    def runTest(self):\n        # 1. The Server send a READ operation (CoAP GET /5/0) to the Client on the\n        #    FW Update Object Instance to obtain the values of the State and Update\n        #    Resources.\n        #\n        # A. In test step 1, the Server receives a success message (\"2.05\" Content)\n        #    associated to its READ command along with a State Resource value\n        #    which is not \"2\" (Downloaded) and a valid (0..9) Update Resource\n        #    value\n        state = self.test_read(ResPath.FirmwareUpdate.State)\n        self.assertNotEqual(b'2', state)\n\n        update_result = self.test_read(ResPath.FirmwareUpdate.UpdateResult)\n        self.assertIn(int(update_result.decode('ascii')), range(10))\n\n        # 2. the Client receives an EXECUTE operation on the Update Resource\n        #    (CoAP POST /5/0/2 ) of the FW Update Object Instance\n        #\n        # B. In test step 2, the Server receives the status code \"4.05\" for method\n        #    not allowed associated to its EXECUTE command\n        req = Lwm2mExecute(ResPath.FirmwareUpdate.Update)\n        self.serv.send(req)\n        res = self.serv.recv()\n        self.assertMsgEqual(Lwm2mErrorResponse.matching(req)(coap.Code.RES_METHOD_NOT_ALLOWED),\n                            res)\n\n        # 3. The Server send a READ operation again (CoAP GET /5/0/3) to the Client\n        #    on the FW Update Object Instance to obtain the State and the Update\n        #    Resource values\n        #\n        # C. In test step 3, the Server receives a success message (\"2.05\" Content)\n        #    associated to its READ command along with a State Resource value\n        #    and an Update Result Resource value, identical to the ones retrieved\n        #    in Pass-Criteria A. The firmware has not bee installed.\n        self.assertEqual(state, self.test_read(ResPath.FirmwareUpdate.State))\n        self.assertEqual(update_result, self.test_read(ResPath.FirmwareUpdate.UpdateResult))\n\n\nclass Test773_FirmwareUpdate_ErrorCase_NotEnoughStorage_FirmwareURI(FirmwareUpdateWithHttpServer.Test):\n    def setUp(self):\n        @contextlib.contextmanager\n        def temporary_soft_fsize_limit(limit_bytes):\n            prev_limit = resource.getrlimit(resource.RLIMIT_FSIZE)\n\n            try:\n                resource.setrlimit(resource.RLIMIT_FSIZE, (limit_bytes, prev_limit[1]))\n                yield\n            finally:\n                resource.setrlimit(resource.RLIMIT_FSIZE, prev_limit)\n\n        demo_executable = os.path.join(self.config.demo_path, self.config.demo_cmd)\n        with open(demo_executable, 'rb') as f:\n            payload = f.read()\n\n        # Limit file size for demo so that full firmware is too much.\n        # After demo starts, we can safely restore original limit, as\n        # the client already inherited smaller one.\n        with temporary_soft_fsize_limit(len(payload) // 2):\n            super().setUp(payload)\n\n    def runTest(self):\n        # 1. The Server verifies through a READ (CoAP GET) command on\n        #    /5/0/3 (State) the FW Update Object Instance of the Client is in\n        #    Idle State\n        #\n        # A. In test step 1, the Server receives the status code \"2.05 \" (Content) for\n        #    the READ success command, along with the State Resource value of\n        #    \"0\" (Idle)\n        self.assertEqual(b'0', self.test_read(ResPath.FirmwareUpdate.State))\n\n        # 2. The Server delivers the firmware package to the Client either\n        #    through a WRITE (CoAP PUT) operation in the Package\n        #    Resource (/5/0/0) or through a WRITE operation of an URI in the\n        #    Package URI Resource.\n        #\n        # B. In test step 2., the Server receives the status code \"2.04\" (Changed)\n        #    for the WRITE command setting either the Package URI Resource or\n        #    setting the Package Resource, according to the chosen firmware\n        #    delivery method.\n        self.test_write(ResPath.FirmwareUpdate.PackageURI,\n                        self.get_firmware_uri())\n\n        # 3. The firmware downloading process is runing The Server sends\n        #    repeated READs or OBSERVE on State and Update Result\n        #    Resources (CoAP GET /5/0) of the FW Update Object Instance\n        #    to determine when the download is completed or if an error\n        #    occured.Before the end of download, the device runs out of\n        #    storage and cannot finish the download\n        # 4. When the Package delivery is stopped the server READs Update\n        #    Result to know the result of the firmware update procedure.\n        #\n        # C. In test step 3., the State Resource retrieved with a value of \"1\" from\n        #    successive Server READs or Client NOTIFY messages, indicates the\n        #    download stage of the Package Delivery is engaged\n        # D. In test step 3., the Update Result Resource (/5/0/5) retrieved from\n        #    successive Server READs or Client NOTIFY messages will take the\n        #    value \"2\" indicating an error occurred during the downloading\n        #    process related to shortage of storage memory The State Resource\n        #    value never reaches the Downloaded value (\"2\")\n        # E. In test step 4., the success READ message(s) (status code \"2.05\"\n        #    Content) on State Resource with value \"0\" (Idle) and Update Result\n        #    Resource with value \"2\" indicates the firmware Package Delivery\n        observed_values = self.collect_values(ResPath.FirmwareUpdate.State, b'0')\n        self.assertEqual(b'0', observed_values[-1])\n        self.assertEqual({b'0', b'1'}, set(observed_values))\n        self.assertEqual(b'2', self.test_read(ResPath.FirmwareUpdate.UpdateResult))\n\n\nclass Test773_FirmwareUpdate_ErrorCase_NotEnoughStorage_FirmwareURI_CoAP(FirmwareUpdate.TestWithCoapServer):\n    def setUp(self):\n        # limit file size to 100K; enough for persistence file, not\n        # enough for firmware\n        import resource\n        self.prev_limit = resource.getrlimit(resource.RLIMIT_FSIZE)\n        new_limit_b = 100 * 1024\n        resource.setrlimit(resource.RLIMIT_FSIZE, (new_limit_b, self.prev_limit[1]))\n\n        demo_executable = os.path.join(self.config.demo_path, self.config.demo_cmd)\n        with open(demo_executable, 'rb') as f:\n            self._payload = f.read()\n\n        super().setUp()\n\n    def tearDown(self):\n        import resource\n        resource.setrlimit(resource.RLIMIT_FSIZE, self.prev_limit)\n\n        super().tearDown()\n\n    def runTest(self):\n        with self.file_server as file_server:\n            file_server.set_resource('/firmware', self._payload)\n            uri = file_server.get_resource_uri('/firmware')\n\n        # 1. The Server verifies through a READ (CoAP GET) command on\n        #    /5/0/3 (State) the FW Update Object Instance of the Client is in\n        #    Idle State\n        #\n        # A. In test step 1, the Server receives the status code \"2.05 \" (Content) for\n        #    the READ success command, along with the State Resource value of\n        #    \"0\" (Idle)\n        self.assertEqual(b'0', self.test_read(ResPath.FirmwareUpdate.State))\n\n        # 2. The Server delivers the firmware package to the Client either\n        #    through a WRITE (CoAP PUT) operation in the Package\n        #    Resource (/5/0/0) or through a WRITE operation of an URI in the\n        #    Package URI Resource.\n        #\n        # B. In test step 2., the Server receives the status code \"2.04\" (Changed)\n        #    for the WRITE command setting either the Package URI Resource or\n        #    setting the Package Resource, according to the chosen firmware\n        #    delivery method.\n        self.test_write(ResPath.FirmwareUpdate.PackageURI, uri)\n\n        # 3. The firmware downloading process is runing The Server sends\n        #    repeated READs or OBSERVE on State and Update Result\n        #    Resources (CoAP GET /5/0) of the FW Update Object Instance\n        #    to determine when the download is completed or if an error\n        #    occured.Before the end of download, the device runs out of\n        #    storage and cannot finish the download\n        # 4. When the Package delivery is stopped the server READs Update\n        #    Result to know the result of the firmware update procedure.\n        #\n        # C. In test step 3., the State Resource retrieved with a value of \"1\" from\n        #    successive Server READs or Client NOTIFY messages, indicates the\n        #    download stage of the Package Delivery is engaged\n        # D. In test step 3., the Update Result Resource (/5/0/5) retrieved from\n        #    successive Server READs or Client NOTIFY messages will take the\n        #    value \"2\" indicating an error occurred during the downloading\n        #    process related to shortage of storage memory The State Resource\n        #    value never reaches the Downloaded value (\"2\")\n        # E. In test step 4., the success READ message(s) (status code \"2.05\"\n        #    Content) on State Resource with value \"0\" (Idle) and Update Result\n        #    Resource with value \"2\" indicates the firmware Package Delivery\n        observed_values = self.collect_values(ResPath.FirmwareUpdate.State, b'0')\n        self.assertEqual(b'0', observed_values[-1])\n        self.assertEqual({b'0', b'1'}, set(observed_values))\n        self.assertEqual(b'2', self.test_read(ResPath.FirmwareUpdate.UpdateResult))\n\n\nclass Test774_FirmwareUpdate_ErrorCase_OutOfMemory(FirmwareUpdate.Test):\n    def runTest(self):\n        demo_executable = os.path.join(self.config.demo_path, self.config.demo_cmd)\n        with open(demo_executable, 'rb') as f:\n            payload = f.read()\n\n        # 1. The Server verifies through a READ (CoAP GET) command on\n        #    /5/0/3 (State) the FW Update Object Instance of the Client is in Idle\n        #    State\n        #\n        # A. In test step 1, the Server receives the status code \"2.05\" (Content) for\n        #    the READ success command, along with the State Resource value of\n        #    \"0\" (Idle)\n        self.assertEqual(b'0', self.test_read(ResPath.FirmwareUpdate.State))\n\n        # 2. The Server delivers the firmware package to the Client either through\n        #    a WRITE (CoAP PUT) operation in the Package Resource (/5/0/0) or\n        #    through a WRITE operation of an URI in the Package URI Resource.\n        #\n        # B. In test step 2., the Server receives the status code \"2.04\" (Changed)\n        #    for the WRITE command setting either the Package URI Resource or\n        #    setting the Package Resource, according to the chosen firmware\n        #    delivery method.\n        self.test_write_block(ResPath.FirmwareUpdate.Package,\n                              make_firmware_package(payload, force_error=PackageForcedError.Firmware.OutOfMemory),\n                              coap.ContentFormat.APPLICATION_OCTET_STREAM)\n\n        # 3. The firmware download process is runing The Server sends repeated\n        #    READs or OBSERVE on State and Update Result Resources (CoAP\n        #    GET /5/0) of the FW Update Object Instance to determine when the\n        #    download is completed or if an error occured.Before the end of\n        #    download, the Client runs out of RAM and cannot finish the\n        #    download\n        # 4. When the Package delivery is stopped the server READs Update\n        #    Result to know the result of the firmware update procedure.\n        #\n        # C. In test step 3., the State Resource retrieved with a value of \"1\" from\n        #    successive Server READs or Client NOTIFY messages, indicates the\n        #    download stage of the Package Delivery is engaged\n        # D. In test step 3., the Update Result Resource (/5/0/5) retrieved from\n        #    successive Server READs or Client NOTIFY messages will take the\n        #    value \"2\" indicating an error occurred during the download process\n        #    related to shortage of RAM The State Resource value never reaches\n        #    the Downloaded value (\"3\")\n        # E. In test step 4., the success READ message(s) (status code \"2.05\"\n        #    Content) on State Resource with value \"0\" (Idle) and Update Result\n        #    Resource with value \"3\" indicates the firmware Package Delivery\n        #    aborted due to shortage of RAM.\n        self.assertEqual(b'0', self.test_read(ResPath.FirmwareUpdate.State))\n        self.assertEqual(b'3', self.test_read(ResPath.FirmwareUpdate.UpdateResult))\n\n\nclass FirmwareUpdate_ErrorCase_OutOfMemory_PackageURI(FirmwareUpdateWithHttpServer.Test):\n    def setUp(self):\n        demo_executable = os.path.join(self.config.demo_path, self.config.demo_cmd)\n        with open(demo_executable, 'rb') as f:\n            pkg = make_firmware_package(f.read(), force_error=PackageForcedError.Firmware.OutOfMemory)\n\n        super().setUp(pkg)\n\n    def runTest(self):\n        # Test 774, but with Package URI\n        self.assertEqual(b'0', self.test_read(ResPath.FirmwareUpdate.UpdateResult))\n        self.test_write(ResPath.FirmwareUpdate.PackageURI, self.get_firmware_uri())\n\n        observed_values = self.collect_values(ResPath.FirmwareUpdate.State, b'0')\n        self.assertEqual(b'0', observed_values[-1])\n        self.assertEqual({b'0', b'1'}, set(observed_values))\n        self.assertEqual(b'3', self.test_read(ResPath.FirmwareUpdate.UpdateResult))\n\n\nclass Test775_FirmwareUpdate_ErrorCase_ConnectionLostDuringDownloadPackageURI(FirmwareUpdateWithHttpServer.Test):\n    class NoShutdownHttpServer(http.server.HTTPServer):\n        def shutdown_request(self, request):\n            pass\n\n        def close_request(self, request):\n            pass\n\n    HTTP_SERVER_CLASS = NoShutdownHttpServer\n\n    def during_download(self, req_handler):\n        self._dangling_http_socket = req_handler.request\n        # HACK to ignore any calls on .wfile afterwards\n        req_handler.wfile = ANY\n\n    def setUp(self):\n        self._dangling_http_socket = None\n        demo_executable = os.path.join(self.config.demo_path, self.config.demo_cmd)\n        with open(demo_executable, 'rb') as f:\n            pkg = make_firmware_package(f.read(), force_error=PackageForcedError.Firmware.OutOfMemory)\n\n        super().setUp(pkg)\n\n    def tearDown(self):\n        try:\n            if self._dangling_http_socket is not None:\n                self._dangling_http_socket.close()\n        finally:\n            super().tearDown()\n\n    def runTest(self):\n        # 1. The Server verifies through a READ (CoAP GET) command on\n        #    /5/0/3 (State) the FW Update Object Instance of the Client is in Idle\n        #    State\n        #\n        # A. In test step 1., the Server receives the status code \"2.05 \" (Content)\n        #    for the READ success command, along with the State Resource value\n        #    of \"0\" (Idle)\n        self.assertEqual(b'0', self.test_read(ResPath.FirmwareUpdate.State))\n\n        # 2. The Server delivers the firmware package to the Client through a\n        #    WRITE operation of an URI in the Package URI Resource.\n        #\n        # B. In test step 2., the Server receives the status code \"2.04\" (Changed)\n        #    for the WRITE command setting the Package URI Resource\n        #    according to the PULL firmware delivery method.\n        self.test_write(ResPath.FirmwareUpdate.PackageURI, self.get_firmware_uri())\n\n        # 3. The Server sends repeated READs or OBSERVE on State and Update\n        #    Result Resources (CoAP GET /5/0) of the FW Update Object\n        #    Instance to determine when the download is completed or if an error\n        #    occured.Before the end of download, the connection is intentionnaly\n        #    lost and the download cannot be finished.\n        # 4. When the Package delivery is stopped the Server READs Update\n        #    Result to know the result of the firmware update procedure.\n        #\n        # C. In test step 3., the State Resource value set to \"1\" retrieved from\n        #    successive Server READs or Client NOTIFY messages, indicates the\n        #    Package Delivery process is engaged in a Download stage\n        # D. In test step 3., the Update Result Resource (/5/0/5) retrieved from\n        #    successive Server READs or Client NOTIFY messages will take the\n        #    value \"4\" indicating an error occurred during the downloading\n        #    process related to connection lost\n        # E. In test step 4., the success READ message(s) (status code \"2.05\"\n        #    Content) on State Resource with value \"0\" (Idle) and Update Result\n        #    Resource with value \"4\" indicates the firmware Package Delivery\n        #    aborted due to connection lost dur the Package delivery.\n        observed_values = self.collect_values(ResPath.FirmwareUpdate.State, b'0',\n                                              max_iterations=600)  # wait up to 60 seconds\n        self.assertEqual(b'0', observed_values[-1])\n        self.assertEqual({b'0', b'1'}, set(observed_values))\n        self.assertEqual(b'4', self.test_read(ResPath.FirmwareUpdate.UpdateResult))\n\n\nclass Test775_FirmwareUpdate_ErrorCase_ConnectionLostDuringDownloadPackageURI_CoAP(FirmwareUpdate.TestWithCoapServer):\n    def setUp(self):\n        demo_executable = os.path.join(self.config.demo_path, self.config.demo_cmd)\n        with open(demo_executable, 'rb') as f:\n            pkg = make_firmware_package(f.read(), force_error=PackageForcedError.Firmware.OutOfMemory)\n\n        class MuteServer(coap.Server):\n            def send(self, *args, **kwargs):\n                pass\n\n        super().setUp(coap_server=MuteServer(), extra_cmdline_args=['--fwu-ack-timeout', '1'])\n\n        with self.file_server as file_server:\n            file_server.set_resource('/firmware', pkg)\n            self._uri = file_server.get_resource_uri('/firmware')\n\n    def runTest(self):\n        # 1. The Server verifies through a READ (CoAP GET) command on\n        #    /5/0/3 (State) the FW Update Object Instance of the Client is in Idle\n        #    State\n        #\n        # A. In test step 1., the Server receives the status code \"2.05 \" (Content)\n        #    for the READ success command, along with the State Resource value\n        #    of \"0\" (Idle)\n        self.assertEqual(b'0', self.test_read(ResPath.FirmwareUpdate.State))\n\n        # 2. The Server delivers the firmware package to the Client through a\n        #    WRITE operation of an URI in the Package URI Resource.\n        #\n        # B. In test step 2., the Server receives the status code \"2.04\" (Changed)\n        #    for the WRITE command setting the Package URI Resource\n        #    according to the PULL firmware delivery method.\n        self.test_write(ResPath.FirmwareUpdate.PackageURI, self._uri)\n\n        # 3. The Server sends repeated READs or OBSERVE on State and Update\n        #    Result Resources (CoAP GET /5/0) of the FW Update Object\n        #    Instance to determine when the download is completed or if an error\n        #    occured.Before the end of download, the connection is intentionnaly\n        #    lost and the download cannot be finished.\n        # 4. When the Package delivery is stopped the Server READs Update\n        #    Result to know the result of the firmware update procedure.\n        #\n        # C. In test step 3., the State Resource value set to \"1\" retrieved from\n        #    successive Server READs or Client NOTIFY messages, indicates the\n        #    Package Delivery process is engaged in a Download stage\n        # D. In test step 3., the Update Result Resource (/5/0/5) retrieved from\n        #    successive Server READs or Client NOTIFY messages will take the\n        #    value \"4\" indicating an error occurred during the downloading\n        #    process related to connection lost\n        # E. In test step 4., the success READ message(s) (status code \"2.05\"\n        #    Content) on State Resource with value \"0\" (Idle) and Update Result\n        #    Resource with value \"4\" indicates the firmware Package Delivery\n        #    aborted due to connection lost dur the Package delivery.\n        observed_values = self.collect_values(ResPath.FirmwareUpdate.State, b'0',\n                                              max_iterations=50, step_time=1)\n        self.assertEqual(b'0', observed_values[-1])\n        self.assertEqual({b'0', b'1'}, set(observed_values))\n        self.assertEqual(b'4', self.test_read(ResPath.FirmwareUpdate.UpdateResult))\n\n\nclass Test776_FirmwareUpdate_ErrorCase_CRCCheckFail(FirmwareUpdate.Test):\n    def runTest(self):\n        demo_executable = os.path.join(self.config.demo_path, self.config.demo_cmd)\n        with open(demo_executable, 'rb') as f:\n            payload = f.read()\n\n        # 1. The Server verifies through a READ (CoAP GET) command on\n        #    /5/0/3 (State) the FW Update Object Instance of the Client is in Idle\n        #    State\n        #\n        # A. In test step 1, the Server receives the status code \"2.05 \" (Content) for\n        #    the READ success command, along with the State Resource value of\n        #    \"0\" (Idle)\n        self.assertEqual(b'0', self.test_read(ResPath.FirmwareUpdate.State))\n\n        # 2. The Server delivers the firmware package to the Client either through\n        #    a WRITE (CoAP PUT) operation in the Package Resource (/5/0/0) or\n        #    through a WRITE operation of an URI in the Package URI Resource.\n        #\n        # B. In test step 2., the Server receives the status code \"2.04\" (Changed)\n        #    for the WRITE command setting either the Package URI Resource or\n        #    setting the Package Resource, according to the chosen firmware\n        #    delivery method.\n        self.test_write_block(ResPath.FirmwareUpdate.Package,\n                              make_firmware_package(payload, crc=0),\n                              coap.ContentFormat.APPLICATION_OCTET_STREAM)\n\n        # 3. The Server sends repeated READs or OBSERVE on State and Update\n        #    Result Resources (CoAP GET /5/0) of the FW Update Object\n        #    Instance to determine when the download is completed or if an error\n        #    occured. The firmware package Integry Check failure stopped the\n        #    download process.\n        # 4. When the Package delivery is stopped the server READs Update\n        #    Result to know the result of the firmware update procedure.\n        #\n        # C. In test step 3., the State Resource value set to \"1\" retrieved from\n        #    successive Server READs or Client NOTIFY messages, indicates the\n        #    Package Delivery process is maintained in Downloading stage\n        # D. In test step 3., the Update Result Resource (/5/0/5) retrieved from\n        #    successive Server READs or Client NOTIFY messages will take the\n        #    value \"5\" indicating an error occurred during the downloading\n        #    process related to the failure of the firmware package integrity check\n        # E. In test step 4., the success READ message(s) (status code \"2.05\"\n        #    Content) on State Resource with value \"0\" (Idle) and Update Result\n        #    Resource with value \"5\" indicates the firmware Package Delivery\n        #    aborted due to a Firmware Package Integrity failure.\n        self.assertEqual(b'0', self.test_read(ResPath.FirmwareUpdate.State))\n        self.assertEqual(b'5', self.test_read(ResPath.FirmwareUpdate.UpdateResult))\n\n\nclass FirmwareUpdate_ErrorCase_CRCCheckFail_PackageURI(FirmwareUpdateWithHttpServer.Test):\n    def setUp(self):\n        demo_executable = os.path.join(self.config.demo_path, self.config.demo_cmd)\n        with open(demo_executable, 'rb') as f:\n            pkg = make_firmware_package(f.read(), crc=0)\n\n        super().setUp(pkg)\n\n    def runTest(self):\n        # Test 776, but with Package URI\n        self.assertEqual(b'0', self.test_read(ResPath.FirmwareUpdate.UpdateResult))\n        self.test_write(ResPath.FirmwareUpdate.PackageURI, self.get_firmware_uri())\n\n        observed_values = self.collect_values(ResPath.FirmwareUpdate.State, b'0')\n        self.assertEqual(b'0', observed_values[-1])\n        self.assertEqual({b'0', b'1'}, set(observed_values))\n        self.assertEqual(b'5', self.test_read(ResPath.FirmwareUpdate.UpdateResult))\n\n\nclass Test777_FirmwareUpdate_ErrorCase_UnsupportedPackageType(FirmwareUpdate.Test):\n    def runTest(self):\n        # 1. The Server verifies through a READ (CoAP GET) command on\n        #    /5/0/3 (State) the FW Update Object Instance of the Client is in Idle\n        #    State\n        #\n        # A. In test step 1, the Server receives the status code \"2.05 \" (Content) for\n        #    the READ success command, along with the State Resource value of\n        #    \"0\" (Idle)\n        self.assertEqual(b'0', self.test_read(ResPath.FirmwareUpdate.State))\n\n        # 2. The Server delivers the firmware package to the Client either through\n        #    a WRITE (CoAP PUT) operation in the Package Resource (/5/0/0 ) or\n        #    through a WRITE operation of an URI in the Package URI Resource.\n        #\n        # B. In test step 2., the Server receives the status code \"2.04\" (Changed)\n        #    for the WRITE command setting either the Package URI Resource or\n        #    setting the Package Resource, according to the chosen firmware\n        #    delivery method.\n        self.test_write(ResPath.FirmwareUpdate.Package, b'A' * 1024,\n                        format=coap.ContentFormat.APPLICATION_OCTET_STREAM)\n\n        # 3. The Server sends repeated READs or OBSERVE on State and Update\n        #    Result Resources (CoAP GET /5/0) of the FW Update Object\n        #    Instance to determine when the download is completed or if an error\n        #    occured. The Download cannot be finished since the firmware\n        #    package type is not supported by the Client\n        # 4. When the Package delivery is stopped the server READs Update\n        #    Result to know the result of the firmware update procedure.\n        #\n        # C. In test step 3., the State Resource value set to \"1\" retrieved from\n        #    successive Server READs or Client NOTIFY messages, indicates the\n        #    Package Delivery process is in Downloading stage\n        # D. In test step 3., the Update Result Resource (/5/0/5) retrieved from\n        #    successive Server READs or Client NOTIFY messages will take the\n        #    value \"6\" indicating an error occurred during the downloading\n        #    process related to the firmware package type not supported by the\n        #    Client.\n        # E. In test step 4., the success READ message(s) (status code \"2.05\"\n        #    Content) on State Resource with value \"1\" (Downloading) and\n        #    Update Result Resource with value \"6 indicates the firmware Package\n        #    Delivery aborted due to a firmware package type not supported by the\n        #    Client.\n        self.assertEqual(b'0', self.test_read(ResPath.FirmwareUpdate.State))\n        self.assertEqual(b'6', self.test_read(ResPath.FirmwareUpdate.UpdateResult))\n\n\nclass FirmwareUpdate_ErrorCase_UnsupportedPackageType_PackageURI(FirmwareUpdateWithHttpServer.Test):\n    def setUp(self):\n        super().setUp(b'A' * 1024)\n\n    def runTest(self):\n        # Test 777, but with Package URI\n        self.assertEqual(b'0', self.test_read(ResPath.FirmwareUpdate.UpdateResult))\n        self.test_write(ResPath.FirmwareUpdate.PackageURI, self.get_firmware_uri())\n\n        observed_values = self.collect_values(ResPath.FirmwareUpdate.State, b'0')\n        self.assertEqual(b'0', observed_values[-1])\n        self.assertEqual({b'0', b'1'}, set(observed_values))\n        self.assertEqual(b'6', self.test_read(ResPath.FirmwareUpdate.UpdateResult))\n\n\nclass Test778_FirmwareUpdate_ErrorCase_InvalidURI(FirmwareUpdate.Test):\n    def runTest(self):\n        # 1. The Server verifies through a READ (CoAP GET) command on\n        #    /5/0/3 (State) the FW Update Object Instance of the Client is in Idle\n        #    State\n        #\n        # A. In test step 1, the Server receives the status code \"2.05 \" (Content) for\n        #    the READ success command, along with the State Resource value of\n        #    \"0\" (Idle)\n        self.assertEqual(b'0', self.test_read(ResPath.FirmwareUpdate.State))\n\n        # 2. The Server initiates a firmware package delivery to the Client through\n        #    a WRITE operation of an invalid URI in the Package URI Resource.\n        #\n        # B. In test step 2., the Server receives the status code \"2.04\" (Changed)\n        #    for the WRITE command setting the Package URI Resource\n        self.test_write(ResPath.FirmwareUpdate.PackageURI,\n                        'http://mylovelyfwserver/')\n\n        # 3. The Server sends repeated READs or OBSERVE on State and Update\n        #    Result Resources (CoAP GET /5/0) of the FW Update Object\n        #    Instance to determine when the download is completed or if an error\n        #    occured. The download process is stopped by the Client due to the\n        #    usage of a bad URI.\n        # 4. When the Package delivery is stopped the server READs Update\n        #    Result to know the result of the firmware update procedure.\n        # C. In test step 3., the State Resource value set to \"1\" retrieved from\n        #    successive Server READs or Client NOTIFY messages, indicates the\n        #    Package Delivery process is maintained in Downloading stage\n        # D. In test step 3., the Update Result Resource (/5/0/5) retrieved from\n        #    successive Server READs or Client NOTIFY messages will take the\n        #    value \"7\" indicating an error occurred during the downloading\n        #    process related to the usage of a bad URI\n        # E. In test step 4., the success READ message(s) (status code \"2.05\"\n        #    Content) on State Resource with value \"0\" (Idle) and Update Result\n        #    Resource with value \"7\" indicates the firmware Package Delivery\n        #    aborted due to the connection to an Invalid URI for the firmware\n        #    package delivery.\n        observed_values = self.collect_values(ResPath.FirmwareUpdate.State, b'0')\n        self.assertEqual(b'0', observed_values[-1])\n        # TODO? client does not report \"Downloading\" state\n        # self.assertEqual({b'0', b'1'}, set(observed_values))\n        self.assertEqual({b'0'}, set(observed_values))\n        self.assertEqual(b'7', self.test_read(ResPath.FirmwareUpdate.UpdateResult))\n\n\nclass Test779_FirmwareUpdate_ErrorCase_UnsuccessfulFirmwareUpdate(FirmwareUpdate.Test):\n    def runTest(self):\n        demo_executable = os.path.join(self.config.demo_path, self.config.demo_cmd)\n        with open(demo_executable, 'rb') as f:\n            payload = f.read()\n\n        prev_version = self.test_read(ResPath.Device.FirmwareVersion)\n\n        # 1. Step 1 – Package Delivery\n        #    a. The Server verifies through a READ (CoAP GET) command on\n        #       /5/0/3 (State) the FW Update Object Instance of the Client is in Idle\n        #       State\n        #\n        # A. Package Delivery\n        #    a. In test step 1.a, the Server receives the status code \"2.05 \" (Content)\n        #       for the READ success command, along with the State Resource value\n        #       of \"0\" (Idle)\n        self.assertEqual(b'0', self.test_read(ResPath.FirmwareUpdate.State))\n\n        # 1. Step 1 – Package Delivery\n        #    b. The Server retrieves (CoAP GET) the initial value of the Firmware\n        #       Version Resource from the Object Device Instance for verification in\n        #       the Pass Criteria (C)\n        #\n        # A. Package Delivery\n        #    b. In test step 1.b, the Server receives the status code \"2.05 \" (Content)\n        #       for the READ success command, along with the initial value of the\n        #       Firmware version Resource available from the Object Device\n        #       Instance.\n        prev_version = self.test_read(ResPath.Device.FirmwareVersion)\n\n        # 1. Step 1 – Package Delivery\n        #    c. The Server delivers the firmware package to the Client either\n        #       through a WRITE (CoAP PUT) operation in the Package Resource\n        #       (/5/0/0 ) or through a WRITE operation of an URI in the Package URI\n        #       Resource.\n        #\n        # A. Package Delivery\n        #    c. In test step 1.c, the Server receives the status code \"2.04\" (Changed)\n        #       for the WRITE command setting either the Package URI Resource or\n        #       setting the Package Resource, according to the chosen firmware\n        #       delivery method.\n        self.test_write_block(ResPath.FirmwareUpdate.Package,\n                              make_firmware_package(payload, force_error=PackageForcedError.Firmware.FailedUpdate),\n                              coap.ContentFormat.APPLICATION_OCTET_STREAM)\n\n        # 1. Step 1 – Package Delivery\n        #    d. Polling ( successive READ commands) or Notification on Update\n        #       Result and State Resources is performed, up to the time State\n        #       Resource takes the ‘Downloaded’ value (2)\n        #\n        # A. Package Delivery\n        #    d. In at this end of test step 1.d, the State Resource take the value \"2\"\n        #       (Downloaded)\n        self.assertEqual(b'2', self.test_read(ResPath.FirmwareUpdate.State))\n\n        # 2. Step 2 – Installation Failure\n        #    a. When the download is completed (State Resource value is ‘2’\n        #       Downloaded) , the Server initiates a firmware update by triggering\n        #       EXECUTE command on Update Resource (CoAP POST /5/0/2 )\n        #\n        # B. Installation failure\n        #    a. In test step 2.a, the Server receives a success message \"2.04\"\n        #       (Changed) in response to the EXECUTE command\n        self.test_execute(ResPath.FirmwareUpdate.Update)\n\n        # 2. Step 2 – Installation Failure\n        #    b. Polling (READ command) or Notification on Update Result and\n        #       State Resources is performed, up to the time State Resource is\n        #       turned back to 2 (Downloaded) or the Update Result Resource\n        #       contains the value \"8\" (Firmware update failed )\n        #\n        # B. Installation failure\n        #    b. In test step 2.b, the Server receives success message(s) \"2.05\"\n        #       Contents along with a State Resource value of \"3\" (Updating) or \"2\"\n        #       (Downloaded) and an Update Ressource value of \"0\" (Initial Value)\n        #       and \"8\" at the end (Firmware updated failure)\n        observed_values = self.collect_values(ResPath.FirmwareUpdate.State, b'2')\n        self.assertEqual(b'2', observed_values[-1])\n        # state == 3 may or may not not be observed\n        self.assertTrue(set(observed_values).issubset({b'2', b'3'}))\n        self.assertEqual(b'8', self.test_read(ResPath.FirmwareUpdate.UpdateResult))\n\n        # 3. Step 3 – Process Verification\n        #    a. The server READs Update Result & State Resources to know the\n        #       result of the firmware update procedure.\n        #\n        # C. Process Verification\n        #    a. In test step 3.a, the Server receives success message(s) \"2.05\"\n        #       Content\" along with a State Resource value of \"2\" (Downloaded) and\n        #       an Update Ressource value of \"8\" (Firmware updated failed)\n        self.assertEqual(b'2', self.test_read(ResPath.FirmwareUpdate.State))\n        self.assertEqual(b'8', self.test_read(ResPath.FirmwareUpdate.UpdateResult))\n\n        # 3. Step 3 – Process Verification\n        #    b. The Server READs the Firmware Version Resource from the\n        #       Object Device Instance\n        #\n        # C. Process Verification\n        #    b. In test step 3.b the Server receives success message(s) \"2.05\" Content\"\n        #       along with a Firmware Version Resource value form the Object\n        #       Device Instance which has not changed compared to the one retrieved\n        #       in Pass Criteria A.b\n        self.assertEqual(prev_version, self.test_read(ResPath.Device.FirmwareVersion))\n\n\nclass FirmwareUpdate_ErrorCase_UnsuccessfulFirmwareUpdate_PackageURI(FirmwareUpdateWithHttpServer.Test):\n    def setUp(self):\n        demo_executable = os.path.join(self.config.demo_path, self.config.demo_cmd)\n        with open(demo_executable, 'rb') as f:\n            pkg = make_firmware_package(f.read(), force_error=PackageForcedError.Firmware.FailedUpdate)\n\n        super().setUp(pkg)\n\n    def runTest(self):\n        # Test 777, but with Package URI\n        self.assertEqual(b'0', self.test_read(ResPath.FirmwareUpdate.UpdateResult))\n        prev_version = self.test_read(ResPath.Device.FirmwareVersion)\n\n        self.test_write(ResPath.FirmwareUpdate.PackageURI, self.get_firmware_uri())\n\n        observed_values = self.collect_values(ResPath.FirmwareUpdate.State, b'2')\n        self.assertEqual(b'2', observed_values[-1])\n        self.assertEqual({b'1', b'2'}, set(observed_values))\n\n        req = Lwm2mExecute(ResPath.FirmwareUpdate.Update)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                            self.serv.recv())\n\n        observed_values = self.collect_values(ResPath.FirmwareUpdate.State, b'2')\n        self.assertEqual(b'2', observed_values[-1])\n        # state == 3 may or may not not be observed\n        self.assertTrue(set(observed_values).issubset({b'2', b'3'}))\n        self.assertEqual(b'8', self.test_read(ResPath.FirmwareUpdate.UpdateResult))\n\n        self.assertEqual(prev_version, self.test_read(ResPath.Device.FirmwareVersion))\n"
  },
  {
    "path": "tests/integration/suites/testfest/dm/location.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nfrom framework.lwm2m_test import *\n\nfrom .utils import DataModel, ValueValidator as VV\n\n\nclass Test801_Location_QueryingTheResourcesOfObject(DataModel.Test):\n    def runTest(self):\n        # 1. READ (CoAP GET) is performed by the Server on the Location\n        #    Object Instance of the Client\n        #\n        # A. In test step 1., the Client receives a READ (CoAP GET) command\n        #    from the Server on the Location Object Instance\n        # B. In test step 1., the Server receives the status code \"2.05\" for READ\n        #    message success\n        # C. In test step 1., along with the success message, the mandatory\n        #    Resources (Latitude, Longitude, Timestamp) and optional ones, are\n        #    received by the Server with expected values in compliance with\n        #    LwM2M technical specification 1.0\n        self.test_read('/%d/0' % OID.Location,\n                       VV.tlv_instance(resource_validators={\n                            RID.Location.Latitude: VV.float(),\n                            RID.Location.Longitude: VV.float(),\n                            RID.Location.Timestamp: VV.from_raw_int(),\n                           }, ignore_extra=True))\n\n\nclass Test810_Location_ObservationAndNotificationOfObservableResources(DataModel.Test):\n    def runTest(self):\n        # 1. The Server communicates to the Client pmin=2 pmax=10 period\n        #    threshold values with a WRITE ATTRIBUTE (CoAP PUT)\n        #    operation at the Location Object Instance level\n        # 2. The Server sends OBSERVE (CoAP Observe Option) message\n        #    to activate reporting on the Location Object Instance.\n        # 3. The Client reports requested information with a NOTIFY\n        #    message (CoAP response)\n        #\n        # A. In test step 1., the Server received a WRITE ATTRIBUTE command\n        #    (CoAP PUT) targeting the Location Object Instance with the proper\n        #    pmin=2 and pmin=10 parameters.\n        # B. In test step 1., the Server received the success message (2.04\n        #    Changed) in response to the WRITE ATTRIBUTE command\n        # C. In test step 2., the Client received the OBSERVE operation targeting\n        #    the Location Object Instance\n        # D. In test step 2., in response to its OBSERVE operation, the Server\n        #    receives the success message (Content 2.05) along with the initial\n        #    values of the Location Object Instance\n        # E. In test step 3., based on pmin/pmax periods parameters received in\n        #    test step 1., the Client reports information to the Server with NOTIFY\n        #    messages containing the Location Object Instance updated values.\n        # F. In test step 3., the values received by the Server along with the\n        #    success message (Content 2.05) must be as expected : at less the\n        #    Mandatory Timestamp Resource must have admissible values\n        #    according to the pmin and pmax parameters.\n        self.test_observe('/%d/0' % OID.Location,\n                          VV.tlv_instance(resource_validators={\n                               RID.Location.Latitude: VV.float(),\n                               RID.Location.Longitude: VV.float(),\n                               RID.Location.Timestamp: VV.from_raw_int(),\n                              }, ignore_extra=True),\n                          pmin=2, pmax=10)\n"
  },
  {
    "path": "tests/integration/suites/testfest/dm/portfolio.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nfrom framework.lwm2m_test import *\nfrom framework_tools.lwm2m.tlv import TLV\n\nfrom .utils import DataModel, ValueValidator\n\n# Client shall be Bootstrapped with specific Portfolio instances\n# /16/0/0:\n#   - /16/0/0/0: \"Host Device ID #1\"\n#   - /16/0/0/1: \"Host Develce Manufacturer #1\"\n#   - /16/0/0/2: \"Host Device Model #1\"\n#   - /16/0/0/3: \"Host Device Software Version #1\"\n#\n# We avoid explicit bootstrapping and just initialize /16 normally.\nIDENTITIES_FOR_INSTANCE = {\n    0: [(0, \"Host Device ID #1\"),\n        # Yes, it's \"Develce\".\n        (1, \"Host Develce Manufacturer #1\"),\n        (2, \"Host Device Model #1\"),\n        (3, \"Host Device Software Version #1\")],\n    1: [(0, \"Host Device ID #2\"),\n        (1, \"Host Device Model #2\")]\n}\n\nclass Test:\n    class Portfolio(DataModel.Test,\n                    test_suite.Lwm2mDmOperations):\n        def setUp(self):\n            super().setUp()\n            self.create_instance(self.serv, oid=OID.Portfolio, iid=0)\n            self.test_write(path=ResPath.Portfolio[0].Identity,\n                            value=TLV.make_multires(RID.Portfolio.Identity,\n                                                    IDENTITIES_FOR_INSTANCE[0]).serialize(),\n                            format=coap.ContentFormat.APPLICATION_LWM2M_TLV)\n\n\nclass Test1630_CreatePortfolioObjectInstance(Test.Portfolio):\n    def runTest(self):\n        # 1. Discover is performed by the Server on /16, expecting '</16/0/0>' in the resulting payload\n        linklist = self.discover(self.serv, oid=OID.Portfolio).content.decode()\n        self.assertLinkListValid(linklist)\n        self.assertIn('<%s>' % (ResPath.Portfolio[0].Identity,), linklist.split(','))\n\n        # 2. Create is performed </16/1> with specified payload\n        self.create_instance_with_payload(self.serv, oid=OID.Portfolio, iid=1,\n                                          payload=[TLV.make_multires(RID.Portfolio.Identity,\n                                                                     IDENTITIES_FOR_INSTANCE[1])])\n        # 3. Discover is performed by the Server on /16\n        linklist = self.discover(self.serv, oid=OID.Portfolio).content.decode()\n        self.assertLinkListValid(linklist)\n        self.assertIn('<%s>' % (ResPath.Portfolio[0].Identity,), linklist.split(','))\n        self.assertIn('<%s>' % (ResPath.Portfolio[1].Identity,), linklist.split(','))\n\n        # 4. Read entire object.\n        EXPECTED_TLV = TLV.make_instance(0, [ TLV.make_multires(RID.Portfolio.Identity, IDENTITIES_FOR_INSTANCE[0]) ]).serialize() \\\n                    + TLV.make_instance(1, [ TLV.make_multires(RID.Portfolio.Identity, IDENTITIES_FOR_INSTANCE[1]) ]).serialize()\n        self.assertEqual(EXPECTED_TLV, self.read_object(self.serv, oid=OID.Portfolio).content)\n\nclass Test1635_DeletePortfolioObjectInstance(Test.Portfolio):\n    def runTest(self):\n        # 1. Discover is performed by the Server on /16, expecting '</16/0/0>' in the resulting payload\n        linklist = self.discover(self.serv, oid=OID.Portfolio).content.decode()\n        self.assertLinkListValid(linklist)\n        self.assertIn('<%s>' % (ResPath.Portfolio[0].Identity,), linklist.split(','))\n\n        # 2. Delete on each instance (we have one)\n        self.delete_instance(self.serv, oid=OID.Portfolio, iid=0)\n\n        # 3. Discover to confirm object is cleared\n        self.assertEqual(self.discover(self.serv, oid=OID.Portfolio).content,\n                         b'</%d>' % (OID.Portfolio,))\n"
  },
  {
    "path": "tests/integration/suites/testfest/dm/utils.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nimport socket\nimport json\nimport enum\nfrom typing import List, Optional, Mapping, Tuple\n\nfrom framework_tools.lwm2m.tlv import *\nfrom framework.lwm2m_test import *\n\n\nclass ValueValidator:\n    def validate(self, value):\n        \"\"\"\n        Implementations of this method should raise a ValueError on validation\n        failure. Any return value is considered correct, any exception other\n        than ValueError is propagated up.\n        \"\"\"\n        raise NotImplementedError\n\n    @classmethod\n    def value(cls, expected):\n        class Validator(cls):\n            def validate(self, value):\n                if expected is not None and value != expected:\n                    raise ValueError('invalid value: expected %r, got %r'\n                                     % (expected, value))\n        return Validator()\n\n    @classmethod\n    def from_constructor(cls, ctor, expected_value=None):\n        class Validator(cls):\n            def validate(self, value):\n                cls.value(expected_value).validate(ctor(value))\n\n        return Validator()\n\n\n    @classmethod\n    def from_values(cls, *allowed_values):\n        class Validator(cls):\n            def validate(self, value):\n                if value not in allowed_values:\n                    raise ValueError('%s is not a valid value (expected one of: %s)'\n                                     % (value, ' '.join(map(str, allowed_values))))\n\n        return Validator()\n\n    @classmethod\n    def integer(cls, value: int = None):\n        return cls.from_constructor(int, value)\n\n    @classmethod\n    def float(cls, expected_value: float = None):\n        class Validator(cls):\n            def validate(self, value):\n                try:\n                    if len(value) == 4:\n                        v = struct.unpack('>f', value)[0]\n                    else:\n                        v = struct.unpack('>d', value)[0]\n\n                    cls.value(expected_value).validate(v)\n                except struct.error:\n                    raise ValueError('could not unpack raw float (hex: %s)' % binascii.hexlify(value))\n\n        return Validator()\n\n    @classmethod\n    def float_as_string(cls):\n        class Validator(cls):\n            def validate(self, value):\n                float(value.decode('ascii'))\n\n        return Validator()\n\n    @classmethod\n    def boolean(cls):\n        return cls.from_values(b'0', b'1')\n\n    @classmethod\n    def ascii_string(cls, expected_value: str = None):\n        class Validator(cls):\n            def validate(self, value):\n                s = value.decode('ascii')\n\n        return Validator()\n\n    @classmethod\n    def multiple_resource(cls, internal_validator):\n        class Validator(cls):\n            def validate(self, value):\n                tlv = TLV.parse(value)\n                if len(tlv) != 1 or tlv[0].tlv_type != TLVType.MULTIPLE_RESOURCE:\n                    raise ValueError('expected a single Multiple Resource')\n                for res_instance in tlv[0].value:\n                    if res_instance.tlv_type != TLVType.RESOURCE_INSTANCE:\n                        raise ValueError('expected Resource Instance list')\n                    internal_validator.validate(res_instance.value)\n\n        return Validator()\n\n    @classmethod\n    def from_raw_int(cls, expected_value: int = None):\n        class Validator(cls):\n            def validate(self, value):\n                if len(value) > 8:\n                    raise ValueError('raw integer value too long: expected at most 8 bytes, got %d' % len(value))\n                try:\n                    v = struct.unpack('>Q', (bytes(8) + value)[-8:])[0]\n                    if v is not None and v != expected_value:\n                        cls.value(expected_value).validate(v)\n\n                except struct.error:\n                    raise ValueError('could not unpack raw integer (hex: %s)' % binascii.hexlify(value))\n\n        return Validator()\n\n    @classmethod\n    def objlnk(cls, expected_value: Optional[Tuple[int, int]] = None):\n        class Validator(cls):\n            def validate(self, value):\n                try:\n                    oid, iid = struct.unpack('>HH', value)\n\n                    if not (0 <= oid <= 65535):\n                        raise ValueError('invalid ObjLnk object ID: %r' % segments[0])\n                    if not (0 <= iid <= 65534):\n                        raise ValueError('invalid ObjLnk instance ID: %r' % segments[1])\n\n                    if expected_value is not None:\n                        cls.value(expected_value).validate((oid, iid))\n                except struct.error:\n                    raise ValueError('could not unpack objlnk (hex: %s)' % binascii.hexlify(value))\n\n        return Validator()\n\n    @classmethod\n    def tlv_multiple_resource(cls, internal_validator):\n        class Validator(cls):\n            def validate(self, tlv_list):\n                for tlv in tlv_list:\n                    if tlv.tlv_type != TLVType.RESOURCE_INSTANCE:\n                        raise ValueError('not a valid Multiple Resource')\n                    internal_validator.validate(tlv.value)\n\n        return Validator()\n\n    @classmethod\n    def tlv_resources(cls, *internal_validators):\n        class Validator(cls):\n            def validate(self, value):\n                tlv_list = TLV.parse(value)\n                for tlv, validator in zip(tlv_list, internal_validators):\n                    validator.validate(tlv.value)\n\n        return Validator()\n\n    @classmethod\n    def tlv_instance(cls,\n                     resource_validators: Mapping[int, 'Validator'],\n                     instance_id: Optional[int] = None,\n                     ignore_duplicates: bool = False,\n                     ignore_missing: bool = False,\n                     ignore_extra: bool = False):\n        class Validator(cls):\n            def validate(self, value):\n                tlv = TLV.parse(value)\n\n                if instance_id is None:\n                    resources_tlv = tlv\n                else:\n                    if len(tlv) != 1:\n                        raise ValueError('expected TLV with 1 Object Instance, got '\n                                         '%d' % len(tlv))\n\n                    tlv = tlv[0]\n                    if tlv.tlv_type != TLVType.INSTANCE:\n                        raise ValueError('not an Object Instance TLV')\n\n                    if tlv.identifier != instance_id:\n                        raise ValueError('expected Instance ID = %d, got %d'\n                                         % (instance_id, tlv.identifier))\n\n                    resources_tlv = tlv.value\n\n                found_rids = set()\n\n                for sub_tlv in resources_tlv:\n                    assert sub_tlv.tlv_type in (TLVType.RESOURCE,\n                                                TLVType.MULTIPLE_RESOURCE)\n\n                    if sub_tlv.identifier not in resource_validators:\n                        if not ignore_extra:\n                            raise ValueError('unexpected Resource ID = %d'\n                                             % sub_tlv.identifier)\n                    else:\n                        if sub_tlv.identifier in found_rids:\n                            if not ignore_duplicates:\n                                raise ValueError('unexpected duplicate Resource'\n                                                 'ID = %d' % sub_tlv.identifier)\n\n                        found_rids.add(sub_tlv.identifier)\n                        try:\n                            if sub_tlv.tlv_type == TLVType.MULTIPLE_RESOURCE:\n                                resource_validators[sub_tlv.identifier].validate(sub_tlv.serialize())\n                            else:\n                                resource_validators[sub_tlv.identifier].validate(sub_tlv.value)\n                        except Exception as e:\n                            raise ValueError('Validation of resource %d failed'\n                                             % sub_tlv.identifier) from e\n\n                not_found = set(resource_validators) - found_rids\n                if not_found and not ignore_missing:\n                    raise ValueError('Resource IDs not found in TLV: %s'\n                                     % ','.join(not_found))\n\n        return Validator()\n\n    @classmethod\n    def json(cls):\n        class Validator(cls):\n            def validate(self, value_bytes):\n                obj = json.loads(value_bytes.decode('utf-8'))\n                unexpected_keys = [k for k in obj if k not in ('bn', 'bt', 'e')]\n                if unexpected_keys:\n                    raise ValueError('unexpected JSON key(s): ' + ', '.join(map(repr, unexpected_keys)))\n\n                base_name = ''\n                if 'bn' in obj:\n                    try:\n                        base_name = str(Lwm2mPath(obj['bn']))\n                    except ValueError as e:\n                        raise ValueError('not a valid JSON base name path: %r' % (obj['bn'],)) from e\n\n                if 'bt' in obj:\n                    base_time = obj['bt']\n                    if not isinstance(base_time, float) and not isinstance(base_time, int):\n                        raise ValueError('not a valid JSON base time (float expected): %r' % (base_time,))\n\n                resource_list = obj['e']\n                try:\n                    iter(resource_list)\n                except TypeError:\n                    raise ValueError('not a valid JSON: expected iterable, got %r' % (resource_list,))\n\n                for resource in resource_list:\n                    unexpected_keys = [k for k in resource if k not in ('n', 't', 'v', 'bv', 'ov', 'sv')]\n                    if unexpected_keys:\n                        raise ValueError('unexpected JSON key(s) in e. object: ' + ', '.join(map(repr, unexpected_keys)))\n\n                    if 'n' in resource:\n                        full_path = CoapPath(base_name + resource['n'])\n                        if len(full_path.segments) > 4:\n                            raise ValueError('not a valid JSON response: path too long (%s, base: %s)' % (full_path, base_name))\n\n                        for segment in full_path.segments:\n                            try:\n                                n = int(segment)\n                                if not 0 <= n <= 65535:\n                                    raise ValueError('LwM2M path segment not in range [0; 65535]: %s' % (segment,))\n                            except ValueError as e:\n                                raise ValueError('not an integer: %s' % (segment,)) from e\n\n                    if 't' in resource:\n                        if not isinstance(resource['t'], float) and not isinstance(resource['t'], int):\n                            raise ValueError('not a valid JSON time (float expected): %r' % (resource['t'],))\n\n                    num_value_entries = sum(int(k in ('v', 'bv', 'ov', 'sv')) for k in resource)\n                    if num_value_entries != 1:\n                        raise ValueError('not a valid JSON: %d value entries in %s, expected one' % (num_value_entries, ', '.join(resource)))\n\n                    if 'v' in resource:\n                        if not isinstance(resource['v'], float) and not isinstance(resource['v'], int):\n                            raise ValueError('not a valid JSON value (float expected): %r' % (resource['v'],))\n                    if 'bv' in resource:\n                        if not isinstance(resource['bv'], bool):\n                            raise ValueError('not a valid JSON value (boolean expected): %r' % (resource['bv'],))\n                    if 'ov' in resource:\n                        try:\n                            objlnk = [int(x) for x in resource['ov'].split(':')]\n                            if len(objlnk) != 2 or not all(0 <= x <= 65535 for x in objlnk):\n                                raise ValueError\n                        except ValueError as e:\n                            raise ValueError('not a valid JSON value (objlnk expected): %r' % (resource['ov'],)) from e\n                    if 'sv' in resource:\n                        if not isinstance(resource['sv'], str):\n                            raise ValueError('not a valid JSON value (string expected): %r' % (resource['sv'],))\n\n        return Validator()\n\n\nclass CancelObserveMethod(enum.IntEnum):\n    DontCancel = 0\n    Reset = 1\n    ObserveOption = 2\n\n\nclass DataModel:\n    class Test(test_suite.Lwm2mSingleServerTest):\n        def setUp(self, **kwargs):\n            kwargs['extra_cmdline_args'] = (\n                    kwargs.get('extra_cmdline_args', [])\n                    + ['--security-iid', '0',\n                       '--server-iid', '0'])\n\n            super().setUp(**kwargs)\n\n        def test_read(self,\n                      path: Lwm2mPath,\n                      validator: Optional[ValueValidator] = None,\n                      format: Optional[int] = None,\n                      server: Optional[Lwm2mServer] = None):\n            server = server or self.serv\n\n            req = Lwm2mRead(path, accept=format)\n            server.send(req)\n\n            res = server.recv()\n            self.assertMsgEqual(Lwm2mContent.matching(req)(), res)\n\n            if format is not None:\n                if format == coap.ContentFormat.TEXT_PLAIN:\n                    # plaintext format is implied, it may be omitted by the client\n                    self.assertIn(res.get_content_format(), (None, format))\n                else:\n                    self.assertEqual(res.get_content_format(), format)\n\n            if validator is not None:\n                try:\n                    validator.validate(res.content)\n                except ValueError as e:\n                    raise ValueError('invalid value in Read response for %s: %s' % (path, res.content)) from e\n\n            return res.content\n\n        def test_write(self,\n                       path: Lwm2mPath,\n                       value: str,\n                       format: coap.ContentFormat = coap.ContentFormat.TEXT_PLAIN,\n                       server: Optional[Lwm2mServer] = None,\n                       update: bool = False):\n            server = server or self.serv\n\n            # WRITE (CoAP PUT/POST) on the resource with a value\n            # admissible with regards to LwM2M technical specification\n            req = Lwm2mWrite(path, value, format=format, update=update)\n            server.send(req)\n\n            # Server receives success message (2.04 Changed)\n            self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                                server.recv())\n\n        def test_write_validated(self,\n                                 path: Lwm2mPath,\n                                 value: str,\n                                 alternative_acceptable_values: List[str] = [],\n                                 server: Optional[Lwm2mServer] = None):\n            self.test_write(path, value, server=server)\n\n            acceptable_values = [v.encode('ascii') for v in [value] + alternative_acceptable_values]\n            self.test_read(path, ValueValidator.from_values(*acceptable_values), server=server)\n\n        def test_write_block(self,\n                             path: Lwm2mPath,\n                             payload: bytes,\n                             format: coap.ContentFormat,\n                             block_size: int = 1024,\n                             return_on_fail: bool = False,\n                             server: Optional[Lwm2mServer] = None):\n            server = server or self.serv\n            offset = 0\n\n            while offset < len(payload):\n                new_offset = offset + block_size\n                seq_num = offset // block_size\n\n                block = payload[offset:new_offset]\n                has_more = (new_offset < len(payload))\n                block_opt = coap.Option.BLOCK1(seq_num=seq_num, has_more=has_more, block_size=block_size)\n\n                msg = Lwm2mWrite(path, block, format, options=[block_opt])\n                server.send(msg)\n\n                expected_response = Lwm2mContinue if has_more else Lwm2mChanged\n                res = server.recv()\n                if return_on_fail and not isinstance(res, expected_response):\n                    return res\n\n                self.assertMsgEqual(expected_response.matching(msg)(), res)\n\n                offset = new_offset\n\n        def test_expect_notify(self,\n                               token: bytes,\n                               validator: ValueValidator,\n                               timeout_s: float,\n                               server: Optional[Lwm2mServer] = None):\n            server = server or self.serv\n\n            notification = server.recv(timeout_s=timeout_s)\n            self.assertMsgEqual(Lwm2mNotify(token=token), notification)\n            try:\n                validator.validate(notification.content)\n                return notification\n            except ValueError as e:\n                raise ValueError('invalid value in Notify response %s' % (notification.content),) from e\n\n        def test_cancel_observe(self,\n                                path: Optional[Lwm2mPath] = None,\n                                msg_id: Optional[int] = None,\n                                token: bytes = None,\n                                server: Optional[Lwm2mServer] = None):\n            server = server or self.serv\n\n            if path is None and msg_id is None:\n                raise ValueError('Either path or msg_id must be specified')\n\n            if path is not None:\n                req = Lwm2mObserve(path, observe=1,\n                                   **({'token': token} if token is not None else {}))\n                server.send(req)\n                self.assertMsgEqual(Lwm2mContent.matching(req)(), server.recv())\n            elif msg_id is not None:\n                server.send(Lwm2mReset(msg_id=msg_id))\n\n        def test_observe(self,\n                         path: Lwm2mPath,\n                         validator: ValueValidator,\n                         server: Optional[Lwm2mServer] = None,\n                         cancel_observe: CancelObserveMethod = CancelObserveMethod.Reset,\n                         **attributes):\n            server = server or self.serv\n\n            if not attributes:\n                attributes['pmax'] = 1\n\n            # The server communicates to the device min/max period,\n            # threshold value and step with a WRITE ATTRIBUTE (CoAP\n            # PUT) operation\n            req = Lwm2mWriteAttributes(path, **attributes)\n            server.send(req)\n            self.assertMsgEqual(Lwm2mChanged.matching(req)(), server.recv())\n\n            req = Lwm2mObserve(path)\n            server.send(req)\n            res = server.recv()\n            self.assertMsgEqual(Lwm2mContent.matching(req)(), res)\n            try:\n                validator.validate(res.content)\n            except ValueError as e:\n                raise ValueError('invalid value in Observe response for %s: %s' % (path, res.content)) from e\n\n            # Client reports requested information with a NOTIFY message\n            # (COAP responses)\n            try:\n                notification = self.test_expect_notify(token=req.token,\n                                                       validator=validator,\n                                                       timeout_s=(attributes['pmax'] + 0.5))\n            except ValueError as e:\n                raise ValueError('invalid value in Notify response for %s' % (path,)) from e\n\n            if cancel_observe == CancelObserveMethod.Reset:\n                # Server sends Cancel Observe (COAP RESET message) to\n                # cancel the Observation relationship.\n                self.test_cancel_observe(msg_id=notification.msg_id, server=server)\n            elif cancel_observe == CancelObserveMethod.ObserveOption:\n                # Server sends Cancel Observe (COAP GET with Observe=1 option)\n                # to cancel the Observation relationship.\n                self.test_cancel_observe(path, token=req.token, server=server)\n\n            if cancel_observe != CancelObserveMethod.DontCancel:\n                # Client stops reporting requested information and removes\n                # associated entries from the list of observers\n                with self.assertRaises(socket.timeout):\n                    server.recv(timeout_s=2)\n\n        def test_discover(self,\n                          path: Lwm2mPath,\n                          server: Optional[Lwm2mServer] = None):\n            server = server or self.serv\n\n            req = Lwm2mDiscover(path)\n            server.send(req)\n\n            res = server.recv()\n            self.assertMsgEqual(Lwm2mContent.matching(req)(), res)\n\n            return res.content\n\n        def test_execute(self,\n                         path: Lwm2mPath,\n                         server: Optional[Lwm2mServer] = None):\n            server = server or self.serv\n\n            req = Lwm2mExecute(path)\n            server.send(req)\n            res = server.recv()\n            self.assertMsgEqual(Lwm2mChanged.matching(req)(), res)\n\n            return res.content\n\n        def test_write_attributes(self,\n                                  path: Lwm2mPath,\n                                  server: Optional[Lwm2mServer] = None,\n                                  **kwargs):\n            server = server or self.serv\n\n            req = Lwm2mWriteAttributes(path, **kwargs)\n            server.send(req)\n            self.assertMsgEqual(Lwm2mChanged.matching(req)(),\n                                server.recv())\n\n\n        def test_create(self,\n                        path: Lwm2mPath,\n                        content: bytes,\n                        server: Optional[Lwm2mServer] = None):\n            server = server or self.serv\n\n            req = Lwm2mCreate(path, content)\n            server.send(req)\n            res = server.recv()\n            self.assertMsgEqual(Lwm2mCreated.matching(req)(), res)\n\n            location = res.get_location_path()\n            self.assertTrue(location.startswith(path + '/'))\n\n            return int(location[len(path) + 1:])\n"
  },
  {
    "path": "tests/integration/suites/testfest/management.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nimport unittest\n\nfrom framework.lwm2m_test import *\n\nfrom .dm.utils import DataModel, ValueValidator\n\n\nclass Test201_QueryingBasicInformationInPlainTextFormat(DataModel.Test):\n    def runTest(self):\n        # 1. Successive READ (CoAP GET) operations are performed on\n        #    the targeted Resources of the Device Object (ID:3) Instance,\n        #    with the CoAP Accept option set to Plain Text data format (0)\n        #\n        # A. In test step 1, Server has received all the expected \"Success\" messages\n        #    (2.05 Content) along with the requested information with the expected\n        #    values and according to the Plain Text data format:\n        #    - Manufacturer Name (ID:0)\n        #    - Model number (ID:1)\n        #    - Serial number (ID:2)\n        self.test_read(ResPath.Device.Manufacturer, ValueValidator.ascii_string(), coap.ContentFormat.TEXT_PLAIN)\n        self.test_read(ResPath.Device.ModelNumber,  ValueValidator.ascii_string(), coap.ContentFormat.TEXT_PLAIN)\n        self.test_read(ResPath.Device.SerialNumber, ValueValidator.ascii_string(), coap.ContentFormat.TEXT_PLAIN)\n\n\nclass Test203_QueryingBasicInformationInTLVFormat(DataModel.Test):\n    def runTest(self):\n        # 1. READ (CoAP GET) operation on Device Object Instance\n        #    (/3/0), with the CoAP Accept option set to TLV data format (11542)\n        # 2. Bits 7-6=00= Object Instance in which case the Value contains\n        #    one or more Resource TLVs\n        # 3. Bits 7-6=11= Resource with Value\n        #\n        # A. In test step 1. Server has received the \"Success\" message (2.05\n        #    Content), along with the requested information in the TLV data format\n        #    (11542) and containing the expected values concerning:\n        #    - Manufacturer Name (ID:0)\n        #    - Model number (ID:1)\n        #    - Serial number (ID:2)\n        #    - Firmware Version (ID:3)\n        #    - Error Code (ID:11)\n        #    - Supported Binding and Modes (ID:16) (\"U\")\n        string = ValueValidator.ascii_string()\n        integer_array = ValueValidator.multiple_resource(ValueValidator.from_raw_int())\n\n        self.test_read('/%d/0' % OID.Device,\n                       ValueValidator.tlv_instance(\n                           resource_validators={\n                               RID.Device.Manufacturer:             string,\n                               RID.Device.ModelNumber:              string,\n                               RID.Device.SerialNumber:             string,\n                               RID.Device.FirmwareVersion:          string,\n                               RID.Device.ErrorCode:                integer_array,\n                               RID.Device.SupportedBindingAndModes: string,\n                           },\n                           ignore_extra=True))\n\n\nclass Test204_QueryingBasicInformationInJSONFormat(DataModel.Test):\n    def runTest(self):\n        # 1. READ (CoAP GET) operation on the Device Object Instance\n        #    (/3/0), with the CoAP Accept option set to JSON data format (11543)\n        #\n        # A. In test step 1. Server has received the success message (2.05\n        #    Content) along with the requested information in JSON data format\n        #    (11543) and containing the expected values concerning:\n        #    - Manufacturer Name (ID:0)\n        #    - Model number (ID:1)\n        #    - Serial number (ID:2)\n        #    - Firmware Version (ID:3)\n        #    - Error Code (ID:11)\n        #    - Supported Binding and Modes (ID:16) (\"U\")\n        #\n        # TODO: check JSON contents, not only structure\n        self.test_read('/%d/0' % OID.Device,\n                       ValueValidator.json(),\n                       coap.ContentFormat.APPLICATION_LWM2M_JSON)\n\n\nclass Test205_SettingBasicInformationInPlainTextFormat(DataModel.Test):\n    def runTest(self):\n        # This test has to set the following resources with specific values:\n        # - Default minimum period (ID:2) : 0101 sec\n        # - Default maximum period (ID:3) : 1010 sec\n        # - Disable timeout (ID:5) : 2000 sec\n        #\n        # 1. Successive WRITE (CoAP PUT) operations are performed on the\n        #    Resources of the Instance 0 of the Server Object in using the\n        #    predefined values above. The Plain Text data format (0) is used\n        # 2. The Server verifies (READs/CoAP GET) the result of the WRITE\n        #    operations by querying the Instance 0 of the Server Object in using\n        #    the TLV data format.\n        # 3. The steps 1., 2. of the test are re-played with the initial values\n        #    of the Configuration 3\n        #\n        # A. In test step 1. the Server receives a \"Sucesss\" message (2.04\n        #    Changed) for each WRITE operation\n        self.test_write_validated(ResPath.Server[0].DefaultMinPeriod, '101')\n        self.test_write_validated(ResPath.Server[0].DefaultMaxPeriod, '1010')\n        self.test_write_validated(ResPath.Server[0].DisableTimeout, '2000')\n\n\nclass Test215_SettingBasicInformationInTLVFormat(DataModel.Test):\n    def runTest(self):\n        # Precondition:\n        # The current values of the Server Object (ID:1) Instance 0, are saved\n        # on the Server\n        rids_to_restore = (RID.Server.Lifetime,\n                           RID.Server.DefaultMinPeriod,\n                           RID.Server.DefaultMaxPeriod,\n                           RID.Server.DisableTimeout,\n                           RID.Server.NotificationStoring,\n                           RID.Server.Binding,\n                           RID.Server.ServerCommunicationRetryCount,\n                           RID.Server.ServerCommunicationRetryTimer,\n                           RID.Server.ServerCommunicationSequenceRetryCount,\n                           RID.Server.ServerCommunicationSequenceDelayTimer,\n                           )\n        prev_values = self.test_read('/%d/0' % OID.Server)\n        prev_values_tlv = TLV.parse(prev_values)\n        values_to_restore = [x for x in prev_values_tlv if x.identifier in rids_to_restore]\n\n        tlv = TLV.make_instance(0, [\n            TLV.make_resource(RID.Server.DefaultMinPeriod, 101),\n            TLV.make_resource(RID.Server.DefaultMaxPeriod, 1010),\n            TLV.make_resource(RID.Server.DisableTimeout, 2000),\n            TLV.make_resource(RID.Server.NotificationStoring, 1),\n            TLV.make_resource(RID.Server.Binding, 'UQ')\n            ])\n\n        # 1. A 1st WRITE (CoAP POST) operation on the Server Object (ID:1)\n        #    Instance 0 is performed in using the set of values contains in the\n        #    215-SetOfValues sample below.The data format TLV is used. (11542)\n        # 2. The Server READs the result of the WRITE operation by querying the\n        #    Server Object (ID:1) Instance 0.\n        self.test_write('/%d/0' % OID.Server, tlv.serialize(),\n                        coap.ContentFormat.APPLICATION_LWM2M_TLV,\n                        update=True)\n\n        # NOTE: changing server parameters triggers Update\n        self.assertDemoUpdatesRegistration(binding='UQ')\n\n        self.test_read('/%d/0' % OID.Server,\n                       ValueValidator.tlv_instance(\n                           resource_validators={\n                               RID.Server.DefaultMinPeriod:    ValueValidator.from_raw_int(101),\n                               RID.Server.DefaultMaxPeriod:    ValueValidator.from_raw_int(1010),\n                               RID.Server.DisableTimeout:      ValueValidator.from_raw_int(2000),\n                               RID.Server.NotificationStoring: ValueValidator.from_raw_int(1),\n                               RID.Server.Binding:             ValueValidator.ascii_string('UQ'),\n                           },\n                           ignore_extra=True))\n\n        # 3. A 2nd WRITE (CoAP PUT) operation on the Server Object (ID:1)\n        #    Instance 0 is performed in using the Initial values preserved on the\n        #    Server in conformance to the test pre-conditions.\n        # 4. The Server READs the result of the previous WRITE operation by\n        #    querying the Server Object (ID:1) Instance 0.\n        self.test_write('/%d/0' % OID.Server,\n                        b''.join(tlv.serialize() for tlv in values_to_restore),\n                        coap.ContentFormat.APPLICATION_LWM2M_TLV)\n\n        # NOTE: changing server parameters triggers Update\n        self.assertDemoUpdatesRegistration(binding='U')\n\n        self.assertEqual(prev_values,\n                         self.test_read('/%d/0' % OID.Server))\n\n\n@unittest.skip(\"JSON parsing is not implemented\")\nclass Test220_SettingBasicInformationInJSONFormat(DataModel.Test):\n    def runTest(self):\n        # 1. A 1st WRITE (CoAP POST) operation on the Server Object (ID:1)\n        #    Instance 0 is performed in using the set of values contains in the 220-\n        #    SetOfValues sample below.The data format JSON is used. (11543)\n        # 2. The Server READs the result of the WRITE operation by querying the\n        #    the previously targeted Resources of the Server Object (ID:1) Instance 0.\n        # 3. A 2nd WRITE (CoAP PUT) operation on the Server Object (ID:1)\n        #    Instance 0 is performed in using the Initial values which have been\n        #    preserved (pre-conditions).\n        # 4. The Server READs the result of the previous WRITE operation by\n        #    querying the Server Object (ID:1) Instance 0.\n        # A. The Server receives the correct status codes for the steps 1. (2.04),\n        #    2.(2.05), 3. (2.04) and 4. (2.05) of the test.\n        # B. In test step 2. , the received values are consistent with the 220-\n        #    SetOfValues sample\n        # C. In test step 4, the received values are consistent with the values\n        #    present in the initial Configuration 3\n        pass\n\n\nclass Test241_ExecutableResourceRebootingTheDevice(DataModel.Test):\n    def _get_valgrind_args(self):\n        # Reboot cannot be performed when demo is run under valgrind\n        return []\n\n    def runTest(self):\n        # 1. Server performs Execute (CoAP POST) operation on the\n        #    Reboot Resource of Device Object (ID:3) Instance\n        # A. In test step 1, the Server receives the success message (2.04\n        #    Changed) related to the EXECUTE operation on the Client\n        req = Lwm2mExecute(ResPath.Device.Reboot)\n        self.serv.send(req)\n        self.assertMsgEqual(Lwm2mChanged.matching(req)(), self.serv.recv())\n\n        # 2. Client registers again with the Server as in the test\n        #    LightweightM2M-1.0-int-101\n        # B. In test step2., Device reboots and the Client registers\n        #    successfully with the Server again (see LightweightM2M-1.0-\n        #    int-101)\n        self.serv.reset()\n        self.assertDemoRegisters()\n\n\nclass Test260_DiscoverCommand(DataModel.Test):\n    def runTest(self):\n        # 1. The initial DISCOVER (CoAP GET Accept:40) operation is\n        #    performed on Objet Device ID:3\n        #\n        # A. In test step 1, the Success Message (\"2.05\" Content) is received by\n        # the Server along with the payload related to the DISCOVER request\n        # and containing :\n        # </3>,</3/0/1>,</3/0/2>,</3/0/3>,</3/0/4>,</3/0/6>;dim=2,\n        # </3/0/7>;dim=2, </3/0/8>;dim=2, </3/0/9>,</3/0/11>,</3/0/16>\n        #\n        # NOTE: According to\n        # https://github.com/OpenMobileAlliance/OMA_LwM2M_for_Developers/issues/135\n        # Discover on Object level should not return Resource attributes.\n        # Even if it did, /3/0/11 is a Multiple Resource, so it should have\n        # ;dim= attribute as well.\n        # TODO: this needs to be clarified.\n        link_list = self.test_discover('/%d' % (OID.Device,)).decode()\n        links = link_list.split(',')\n        self.assertIn('</%d>' % (OID.Device,), links)\n        self.assertIn('<%s>' % (ResPath.Device.ModelNumber,), links)\n        self.assertIn('<%s>' % (ResPath.Device.SerialNumber,), links)\n        self.assertIn('<%s>' % (ResPath.Device.FirmwareVersion,), links)\n        self.assertIn('<%s>' % (ResPath.Device.Reboot,), links)\n        self.assertIn('<%s>' % (ResPath.Device.AvailablePowerSources,), links)\n        self.assertIn('<%s>' % (ResPath.Device.PowerSourceVoltage,), links)\n        self.assertIn('<%s>' % (ResPath.Device.PowerSourceCurrent,), links)\n        self.assertIn('<%s>' % (ResPath.Device.BatteryLevel,), links)\n        self.assertIn('<%s>' % (ResPath.Device.ErrorCode,), links)\n        self.assertIn('<%s>' % (ResPath.Device.SupportedBindingAndModes,), links)\n\n        # 2. A WRITE-ATTRIBUTES operation set the pmin=10 & pmax=200\n        #    <NOTIFICATION> Attributes at the Object Instance level\n        #\n        # B. In test step 2, a Success message is received by the Server (\"2.04\"\n        #    Changed) related to the WRITE-ATTRIBUTES operation\n        self.test_write_attributes('/%d/0' % (OID.Device,), pmin=10, pmax=200)\n\n        # 3. Same DISCOVER command as in test step 1., is performed\n        #\n        # C. In test step 3, the Success message (\"2.05\" Content) is received by\n        #    the Server along with the payload related to the DISCOVER request\n        #    and containing :\n        #    < /3>,</3/0>;pmin=10;pmax=200,</3/0/1>, </3/0/2>, </3/0/3>,\n        #    </3/0/4>, </3/0/6>;dim=2, </3/0/7>;dim=2, </3/0/8>;dim=2,\n        #    </3/0/9>,</3/0/11>,</3/0/16>\n        #\n        # TODO: WTF? this should not show attributes as well\n        link_list = self.test_discover('/%d' % (OID.Device,)).decode()\n        links = link_list.split(',')\n        self.assertIn('</%d>' % (OID.Device,), links)\n        self.assertIn('<%s>' % (ResPath.Device.ModelNumber,), links)\n        self.assertIn('<%s>' % (ResPath.Device.SerialNumber,), links)\n        self.assertIn('<%s>' % (ResPath.Device.FirmwareVersion,), links)\n        self.assertIn('<%s>' % (ResPath.Device.Reboot,), links)\n        self.assertIn('<%s>' % (ResPath.Device.AvailablePowerSources,), links)\n        self.assertIn('<%s>' % (ResPath.Device.PowerSourceVoltage,), links)\n        self.assertIn('<%s>' % (ResPath.Device.PowerSourceCurrent,), links)\n        self.assertIn('<%s>' % (ResPath.Device.BatteryLevel,), links)\n        self.assertIn('<%s>' % (ResPath.Device.ErrorCode,), links)\n        self.assertIn('<%s>' % (ResPath.Device.SupportedBindingAndModes,), links)\n\n        # 4. The WRITE-ATTRIBUTES operation set the lt=1, gt=6 et st=1\n        #    <NOTIFICATION> Attributes of the Resource ID:7 (Power\n        #    Source Voltage)\n        #\n        # D. In test step 4, a \"Success\" message is received by the Server\n        # (\"2.04\" Changed) related to the WRITE-ATTRIBUTES operation\n        self.test_write_attributes(ResPath.Device.PowerSourceVoltage, lt=1, gt=6, st=1)\n\n        # 5. Same DISCOVER command as in test step 1. is performed\n        #\n        # E. In test step 5, the Success message (\"2.05\" Content) is received\n        #    by the Server along with the same payload received in step 3,\n        #    except for the Resource 7 :\n        #    < /3>,</3/0>;pmin=10;pmax=200,</3/0/1>, </3/0/2>, </3/0/3>,\n        #    </3/0/4>, </3/0/6>;dim=2, </3/0/7>;dim=2, ;lt=1;\n        #\n        # TODO: WTF? this should not show attributes as well\n        link_list = self.test_discover('/%d' % (OID.Device,)).decode()\n        links = link_list.split(',')\n        self.assertIn('</%d>' % (OID.Device,), links)\n        self.assertIn('<%s>' % (ResPath.Device.ModelNumber,), links)\n        self.assertIn('<%s>' % (ResPath.Device.SerialNumber,), links)\n        self.assertIn('<%s>' % (ResPath.Device.FirmwareVersion,), links)\n        self.assertIn('<%s>' % (ResPath.Device.Reboot,), links)\n        self.assertIn('<%s>' % (ResPath.Device.AvailablePowerSources,), links)\n        self.assertIn('<%s>' % (ResPath.Device.PowerSourceVoltage,), links)\n        self.assertIn('<%s>' % (ResPath.Device.PowerSourceCurrent,), links)\n        self.assertIn('<%s>' % (ResPath.Device.BatteryLevel,), links)\n        self.assertIn('<%s>' % (ResPath.Device.ErrorCode,), links)\n        self.assertIn('<%s>' % (ResPath.Device.SupportedBindingAndModes,), links)\n\n        # 6. DISCOVER operation is performed on Resource ID:7 of the\n        #    Object Device Instance\n        #\n        # F. In test step 6, the Success message (\"2.05\" Content) is received by\n        #    the Server along with the payload related to the DISCOVER request\n        #    and just containing :\n        #    </3/0/7>;pmin=10;pmax=200;dim=2 ;lt=1;gt=6 ;st=1\n        link_list = self.test_discover(ResPath.Device.PowerSourceVoltage)\n        self.assertNotIn(b',', link_list)\n        attrs = link_list.split(b';')\n        self.assertIn(b'pmin=10', attrs)\n        self.assertIn(b'pmax=200', attrs)\n        self.assertIn(b'lt=1', attrs)\n        self.assertIn(b'gt=6', attrs)\n        self.assertIn(b'st=1', attrs)\n        self.assertTrue(any(attr.startswith(b'dim=') for attr in attrs))\n\n\nclass Test261_WriteAttributeOperationOnMultipleResource(DataModel.Test):\n    def setUp(self):\n        super().setUp(maximum_version='1.1')\n\n    def runTest(self):\n        # 1. A DISCOVER operation is performed on Error Code resource (ID: 11) of the Object Device\n        #    (ID: 3)\n        #\n        # A. In test step 1, the Success Message (“2.05” Content) is received by the Server along\n        #    with the payload related to the DISCOVER request and containing:\n        #    </3/0/11>;dim=1,</3/0/11/0>\n        discover_result = self.test_discover('/%d/0/%d' % (OID.Device, RID.Device.ErrorCode))\n        self.assertEqual(b'</%d/0/%d>;dim=1,</%d/0/%d/0>'\n                         % (OID.Device, RID.Device.ErrorCode, OID.Device, RID.Device.ErrorCode),\n                         discover_result)\n\n        # 2. A WRITE-ATTRIBUTES operation set the pmin=10&pmax=200 <NOTIFICATION> Attributes at the\n        #    Object level (ID: /3)\n        #\n        # B. In test step 2., 3. et 4. a Success message is received by the Server (“2.04” Changed)\n        #    related to the WRITE-ATTRIBUTES operation\n        self.test_write_attributes('/%d' % (OID.Device,), pmin=10, pmax=200)\n\n        # 3. A WRITE-ATTRIBUTES operation set the pmax=320 <NOTIFICATION> Attributes at the Object\n        #    Instance level (ID: /3/0)\n        #\n        # B. In test step 2., 3. et 4. a Success message is received by the Server (“2.04” Changed)\n        #    related to the WRITE-ATTRIBUTES operation\n        self.test_write_attributes('/%d/0' % (OID.Device,), pmax=320)\n\n        # 4. A WRITE-ATTRIBUTES operation set the pmax=100&epmin=1&epmax=20 <NOTIFICATION>\n        #    Attributes at the Resource Instance level (ID: /3/0/11/0)\n        #\n        # B. In test step 2., 3. et 4. a Success message is received by the Server (“2.04” Changed)\n        #    related to the WRITE-ATTRIBUTES operation\n        self.test_write_attributes('/%d/0/%d/0' % (OID.Device, RID.Device.ErrorCode), pmax=100,\n                                   epmin=1, epmax=20)\n\n        # 5. The same DISCOVER than in step 1. is performed\n        #\n        # C. In test step 5, the Success Message (“2.05” Content) is received by the Server along\n        #    with the payload related to the DISCOVER request and containing:\n        #    </3/0/11>;dim=1;pmin=10;pmax=320,</3/0/11/0>;pmax=100;epmin=1;epmax=20\n        discover_result = self.test_discover('/%d/0/%d' % (OID.Device, RID.Device.ErrorCode))\n        self.assertEqual(b'</%d/0/%d>;dim=1;pmin=10;pmax=320,</%d/0/%d/0>;pmax=100;epmin=1;epmax=20'\n                         % (OID.Device, RID.Device.ErrorCode, OID.Device, RID.Device.ErrorCode),\n                         discover_result)\n"
  },
  {
    "path": "tests/integration/suites/testfest/multi_servers.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nfrom framework.lwm2m_test import *\nfrom .dm.utils import DataModel\n\n\nclass Test950_MultiServersRegistration(test_suite.Lwm2mTest):\n    def setUp(self):\n        self.setup_demo_with_servers(servers=2,\n                                     auto_register=False)\n\n    def runTest(self):\n        # 1. Registration message (CoAP POST) is sent from the Client to\n        #    Server #1\n        #\n        # A. In test step 1., the Server receives the REGISTER command\n        #    along with the information related to the Server Account #1\n        #    (see LwM2M-1.0-int-101 Test Case for more)\n        # B. In test step 1., the Client receives the \"Success\" message from\n        #    Server #1 (2.01 Created)\n        self.assertDemoRegisters(self.servers[0])\n\n        # 2. Registration message (CoAP POST) is sent from the Client to\n        #    Server #2\n        #\n        # C. In test step 2., the Server receives the REGISTER command\n        #    along with the information related to the Server Account #2\n        #    (see LwM2M-1.0-int-101 Test Case for more)\n        # D. In test step 2., the Client receives the \"Success\" message from\n        #    Server #2 (2.01 Created)\n        self.assertDemoRegisters(self.servers[1])\n\n    def tearDown(self):\n        self.teardown_demo_with_servers()\n\n\nclass Test951_MultiServersAttributes(DataModel.Test):\n    def setUp(self):\n        self.setup_demo_with_servers(servers=2)\n\n    def runTest(self):\n        # 1. The Server #1 communicates to the Client pmin=2 and\n        #    pmax=10 periods with a WRITE-ATTRIBUTE (CoAP PUT)\n        #    operation at the Device Object Instance level.\n        #\n        # A. In test step 1., the Server#1 receives the \"Success\" message\n        #    from the Client (2.04 Changed)\n        self.test_write_attributes('/%d/0' % OID.Device,\n                                   server=self.servers[0],\n                                   pmin=2, pmax=10)\n\n        # 2. The Server #2 communicates to the Client pmin=15 and\n        #    pmax=50 periods with a WRITE-ATTRIBUTE (CoAP PUT)\n        #    operation at the Device Object Instance level.\n        #\n        # B. In test step 2., the Server#2 receives the \"Success\" message\n        #    from the Client (2.04 Changed)\n        self.test_write_attributes('/%d/0' % OID.Device,\n                                   server=self.servers[1],\n                                   pmin=15, pmax=50)\n\n        # 3. The Server #1 sends a DISCOVER command to the Client\n        #\n        # C. In test step 3., the Server#1 receives the \"Success\" message\n        #    from Client (2.05 Content) related to its DISCOVER command\n        #    along with the payload containing the following information\n        #    regarding the Device Object Instance :\n        #    </3/0>;pmin=2;pmax=10,</3/0/0>,</3/0/1>,</3/0/2>,</3/0/3>,\n        #    </3/0/11>,</3/0/16>\n        link_list = self.test_discover('/%d/0' % OID.Device,\n                                       server=self.servers[0]).decode()\n        links = link_list.split(',')\n        self.assertIn('</%d/0>;pmin=2;pmax=10' % (OID.Device,), links)\n        self.assertIn('<%s>' % (ResPath.Device.Manufacturer,), links)\n        self.assertIn('<%s>' % (ResPath.Device.ModelNumber,), links)\n        self.assertIn('<%s>' % (ResPath.Device.SerialNumber,), links)\n        self.assertIn('<%s>' % (ResPath.Device.FirmwareVersion,), links)\n        # Multiple Resource, with dim attribute\n        self.assertTrue(any(x.startswith('<%s>' % (ResPath.Device.ErrorCode,)) for x in links))\n        self.assertIn('<%s>' % (ResPath.Device.SupportedBindingAndModes,), links)\n\n        # 4. The Server #2 send a DISCOVER command to the Client\n        #\n        # D. In test step 4., the Server#2 receives the \"Success\" message\n        #    from Client (2.05 Content) related to the DISCOVER\n        #    command along with the payload containing the following\n        #    information regarding the Device Object Instance :\n        #    </3/0>;pmin=15;pmax=50,</3/0/0>,</3/0/1>,</3/0/2>,</3/0/3>\n        #    ,</3/0/11>,</3/0/16>\n        link_list = self.test_discover('/%d/0' % OID.Device,\n                                       server=self.servers[1]).decode()\n        links = link_list.split(',')\n        self.assertIn('</%d/0>;pmin=15;pmax=50' % (OID.Device,), links)\n        self.assertIn('<%s>' % (ResPath.Device.Manufacturer,), links)\n        self.assertIn('<%s>' % (ResPath.Device.ModelNumber,), links)\n        self.assertIn('<%s>' % (ResPath.Device.SerialNumber,), links)\n        self.assertIn('<%s>' % (ResPath.Device.FirmwareVersion,), links)\n        # Multiple Resource, with dim attribute\n        self.assertTrue(any(x.startswith('<%s>' % (ResPath.Device.ErrorCode,)) for x in links))\n        self.assertIn('<%s>' % (ResPath.Device.SupportedBindingAndModes,), links)\n"
  },
  {
    "path": "tests/integration/suites/testfest/register.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nimport unittest\n\nfrom framework.lwm2m_test import *\nfrom .dm.utils import DataModel\n\n\nclass Test101_InitialRegistration(test_suite.Lwm2mSingleServerTest):\n    def setUp(self):\n        super().setUp(auto_register=False)\n\n    def runTest(self):\n        # 1. Registration message (CoAP POST) is sent from Client to Server.\n        pkt = self.serv.recv()\n        self.assertMsgEqual(\n            Lwm2mRegister('/rd?lwm2m=1.0&ep=%s&lt=86400' % (DEMO_ENDPOINT_NAME,)),\n            pkt)\n\n        # A. In test step 1. , the Server receives the REGISTER command\n        # along with the following information:\n        # - Endpoint Client Name\n        # - registration lifetime\n        # - LwM2M version\n        # - binding mode (optional)\n        # - SMS number (optional)\n        # - Objects and Object Instances (mandatory and optional objects /\n        #   object instances) ; possibly with Version of Objects\n        #    - Objects and Object instances (mandatory and optional\n        #      objects/object instances)\n        self.assertLinkListValid(pkt.content.decode())\n\n        # B. In test step 1. , Client received \"Success\" message from Server\n        #    (2.01 Created) related to the REGISTER command.\n        self.serv.send(Lwm2mCreated.matching(pkt)(location=self.DEFAULT_REGISTER_ENDPOINT))\n\n\nclass Test102_RegistrationUpdate(DataModel.Test):\n    def runTest(self):\n        # 1. The Server set the lifetime resource of the Server Object\n        #    Instance to 20 sec (CoAP PUT /1/0/1)\n        #\n        # A. In test step 1., the Server received a Success Message (2.04\n        #    Changed) related to its setting request\n        self.test_write(ResPath.Server[0].Lifetime, '20')\n\n        # 2. UPDATE (Registration) message (CoAP POST) is sent from\n        #    Client to Server with Lifetime parameter set to 20 sec\n        #\n        # B. In test step 2., Server has received UPDATE operation with\n        #    lifetime parameter = 20 sec\n        pkt = self.serv.recv()\n        self.assertMsgEqual(Lwm2mUpdate(self.DEFAULT_REGISTER_ENDPOINT,\n                                        query=['lt=20'],\n                                        content=b''),\n                            pkt)\n        # C. In test step 2., Client has received \"Success\" (2.04) message\n        #    from Server related to the UPDATE command\n        self.serv.send(Lwm2mChanged.matching(pkt)())\n\n        # 3. On option, before the registration expires (20 sec) the Client\n        #    send a new UPDATE message without parameter to the Server\n        #\n        # D. In test step 3., either the Server received an UPDATE operation\n        #    with no parameter or a de_registratoin occurs in the Server\n        #    after the 20s\n        pkt = self.serv.recv(timeout_s=20)\n        self.assertMsgEqual(Lwm2mUpdate(self.DEFAULT_REGISTER_ENDPOINT,\n                                        query=[],\n                                        content=b''),\n                            pkt)\n        self.serv.send(Lwm2mChanged.matching(pkt)())\n\n\nclass Test103_Deregistration(DataModel.Test):\n    def tearDown(self):\n        self.teardown_demo_with_servers(auto_deregister=False)\n\n    def runTest(self):\n        # 1. The Server sends EXECUTE command on the Disable\n        #    Resource of the Server Object (ID:1) Instance.\n        # A. In test step 1., the Server receives the \"Success\" message (2.04\n        #    Changed) from the Client\n        self.test_execute(ResPath.Server[0].Disable)\n\n        # 2. Deregistration message (CoAP DELETE) is sent from Client to Server.\n        pkt = self.serv.recv()\n        self.assertMsgEqual(Lwm2mDeregister(self.DEFAULT_REGISTER_ENDPOINT), pkt)\n\n        # B. In test step 2., the Client receives \"Success\" message (2.02\n        #    Deleted) from the Server\n        self.serv.send(Lwm2mDeleted.matching(pkt)())\n\n        # C. Client is removed from the servers registration database\n\n\nclass Test104_RegistrationUpdateTrigger(DataModel.Test):\n    def setUp(self):\n        super().setUp(lifetime=20)\n\n    def runTest(self):\n        # 1. Before Client registration expires on the Server (for test\n        #    purposes a short registration lifetime is chosen 20 sec) a\n        #    Registration Update Trigger message CoAP POST /1/0/8 is\n        #    sent from Server to Client\n        #\n        # A. In test step 1., Client received a Registration Update Trigger\n        # B. In test step 1., Server received a \"Success\" message (2.04\n        #    Changed) related to the EXECUTE command (Registration\n        #    Update Trigger).\n        self.test_execute(ResPath.Server[0].RegistrationUpdateTrigger)\n\n        # 2. UPDATE (Registration) message (CoAP POST) is sent from\n        #    Client to Server\n        # C. In test step 2., Server received UPDATE operation without\n        #    parameter\n        pkt = self.serv.recv()\n        self.assertMsgEqual(Lwm2mUpdate(self.DEFAULT_REGISTER_ENDPOINT,\n                                        query=[],\n                                        content=b''),\n                            pkt)\n        # D. In test step 2., Client has received \"Success\" message (2.04\n        #    Changed) from Server related to its UPDATE message\n        self.serv.send(Lwm2mChanged.matching(pkt)())\n\n        # E. After test step 2., the Client is still registered in the Server\n        #    while the initial Registration lifetime expired\n"
  },
  {
    "path": "tests/integration/suites/testfest/reporting.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nimport unittest\nfrom framework.lwm2m_test import *\n\nfrom .dm.utils import *\n\n\nclass Test301_ObservationAndNotificationOfParameterValues(DataModel.Test):\n    def runTest(self):\n        VV = ValueValidator\n        int_array = VV.tlv_resources(VV.tlv_multiple_resource(VV.from_raw_int()))\n\n        # 1. Server Determines which Power Sources are on the Device (READ\n        #    access to Resource ID:6 of Device Object ID:3)\n        self.test_read(ResPath.Device.AvailablePowerSources)\n\n        # 2. Server communicates with the Device Object ID:3 via WRITE-ATTRIBUTE\n        #    (CoAP PUT) operations, to set the pmin & pmax\n        #    Attributes of each targeted Resources (e.g.\n        #    /3/0/7?pmin=5&pmax=15 and /3/0/8?pmin10&pmax=20)\n        # 3. Server sends OBSERVE (CoAP GET operation with Observe\n        #    Option set to 0) messages for the targeted Resources (ID:7 & ID:8 )\n        #    to activate reporting\n        # 4. Client reports requested information with a NOTIFY message\n        #    (CoAP responses)\n        #\n        # A. The Server regularly receives the requested information and\n        #    displays \"Power Source Voltage\" and \"Power Source Current\"\n        #    values to the user if possible.\n        self.test_observe(ResPath.Device.PowerSourceVoltage,\n                          int_array, pmin=1, pmax=2, st=1)\n        self.test_observe(ResPath.Device.PowerSourceCurrent,\n                          int_array, pmin=1, pmax=2, st=1)\n\n\n@unittest.skip(\"Covered by test 301\")\nclass Test302_CancelObservationsUsingResetOperation(DataModel.Test):\n    def runTest(self):\n        # 1. Client reports requested information with NOTIFY messages\n        #    (CoAP responses) on Resource ID:7 and ID:8\n        # 2. On receving a NOTIFY message from Resource ID:7, the\n        #    Server sends a Cancel Observation (CoAP RESET message) to\n        #    remove the Observation relationship with that Resource .\n        # 3. On receving a NOTIFY message from Resource ID:8, the\n        #    Server sends a Cancel Observation (CoAP RESET message) to\n        #    remove the Observation relationship with that Resource .\n        # 4. Client stops reporting requested information and removes\n        #    associated entries from the list of observers\n        #\n        # A. The Server receives regular NOTIFICATION from Device\n        #    Object ID:3 on Resources Power Source Voltage (ID:7) and\n        #    Power Source Current (ID:8)\n        # B. The Server stops receiving information first on \"Power Source\n        #    Voltage\" then on \"Power Source Current\" after having sent the\n        #    Cancel Observation (Reset Operation) on that Resources.\n        #    Associated entries from the list of observers are removed.\n        pass\n\n\nclass Test303_CancelObservationsUsingCancelParameter(DataModel.Test):\n    def runTest(self):\n        VV = ValueValidator\n        int_array = VV.tlv_resources(VV.tlv_multiple_resource(VV.from_raw_int()))\n\n        # 1. Client reports requested information with a NOTIFY message\n        #    (CoAP responses)\n        # 2. After receiving few notifications, Server sends OBSERVE\n        #    operation with the CANCEL parameter (CoAP GET message\n        #    with Observe option set to 1) to cancel the Observation\n        #    relationship with the Instance of the Device Object (/3/0)\n        # 3. Client stops reporting requested information on both Resources\n        #    and removes associated entries from the list of observers.\n        #\n        # A. The Server receives regular NOTIFICATION from Device\n        #    Object ID:3 on Resources Power Source Voltage (ID:7) and\n        #    Power Source Current (ID:8)\n        # B. The Server stops receiving information on \"Power Source\n        #    Voltage\" and \"Power Source Current\" after having sent the\n        #    Cancel Observation (with Cancel parameter) on the Device\n        #    Object Instance (/3/0).Associated entries from the list of\n        #    observers are removed.\n        self.test_observe(ResPath.Device.PowerSourceVoltage,\n                          int_array, cancel_observe=CancelObserveMethod.ObserveOption,\n                          pmin=1, pmax=2, st=1)\n        self.test_observe(ResPath.Device.PowerSourceCurrent,\n                          int_array, cancel_observe=CancelObserveMethod.ObserveOption,\n                          pmin=1, pmax=2, st=1)\n"
  },
  {
    "path": "tests/integration/suites/testfest/security.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nimport binascii\n\nfrom framework.lwm2m_test import *\n\nfrom .dm.utils import DataModel\n\n\nclass Test401_UDPChannelSecurity_PreSharedKeyMode(DataModel.Test):\n    PSK_IDENTITY = b'test-identity'\n    PSK_KEY = b'test-key'\n\n    def setUp(self):\n        from pymbedtls import PskSecurity\n        self.setup_demo_with_servers(servers=[Lwm2mServer(coap.DtlsServer(psk_key=self.PSK_KEY, psk_identity=self.PSK_IDENTITY))],\n                                     extra_cmdline_args=['--identity',\n                                                         str(binascii.hexlify(self.PSK_IDENTITY), 'ascii'),\n                                                         '--key', str(binascii.hexlify(self.PSK_KEY), 'ascii')],\n                                     auto_register=False)\n\n    def runTest(self):\n        # 1. DTLS Session is established\n        # 2. Registration message (CoAP POST) is sent from LwM2M\n        #    Client to LwM2M Server.\n        # 3. Client receives Success message (2.01 Created) from the Server.\n        #\n        # A. In test step 2 & 3, Registration command of the Client on the Server\n        #    is performed successfully over the DTLS session\n        self.assertDemoRegisters()\n\n        # 4. READ (CoAP GET) on the Instance of the Device Object is\n        #    performed using the default TLV data format (cf Test\n        #    LwM2M-1.0-int-203)\n        # 5. Server receives success message (2.05 Content) and the\n        #    requested values (encrypted)\n        #\n        # B. In test step 4 & 5 the READ command work successfully over the\n        #    DTLS session.\n        self.test_read('/%d/0' % OID.Device)\n"
  },
  {
    "path": "tests/modules/access_control/access_control.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <string.h>\n\n#include <avsystem/commons/avs_unit_test.h>\n\n#include <anjay/access_control.h>\n#include <anjay/core.h>\n\n#include <anjay_modules/dm/anjay_execute.h>\n\n#include \"src/core/anjay_core.h\"\n#include \"src/core/servers/anjay_servers_internal.h\"\n#include \"src/modules/access_control/anjay_mod_access_control.h\"\n#include \"tests/utils/dm.h\"\n\n#define TEST_OID 0x100\nstatic const anjay_dm_object_def_t *const TEST = &(\n        const anjay_dm_object_def_t) {\n    .oid = TEST_OID,\n    .handlers = {\n        .list_instances = _anjay_mock_dm_list_instances,\n        .instance_create = _anjay_mock_dm_instance_create,\n        .instance_remove = _anjay_mock_dm_instance_remove,\n        .list_resources = _anjay_mock_dm_list_resources,\n        .resource_read = _anjay_mock_dm_resource_read,\n        .resource_write = _anjay_mock_dm_resource_write,\n        .resource_execute = _anjay_mock_dm_resource_execute,\n        .list_resource_instances = _anjay_mock_dm_list_resource_instances\n    }\n};\n\n#define ACCESS_CONTROL_TEST_INIT                                            \\\n    DM_TEST_INIT_WITH_OBJECTS(&FAKE_SECURITY, &FAKE_SERVER, &TEST);         \\\n                                                                            \\\n    AVS_UNIT_ASSERT_SUCCESS(anjay_access_control_install(anjay));           \\\n                                                                            \\\n    /* prevent sending Update, as that will fail in the test environment */ \\\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);                                \\\n    avs_sched_del(&anjay_unlocked->servers->next_action_handle);            \\\n    ANJAY_MUTEX_UNLOCK(anjay);                                              \\\n                                                                            \\\n    anjay_sched_run(anjay)\n\nAVS_UNIT_TEST(access_control, set_acl) {\n    ACCESS_CONTROL_TEST_INIT;\n\n    const anjay_iid_t iid = 1;\n    const anjay_ssid_t ssid = 1;\n\n    {\n        ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n        anjay_notify_queue_t queue = NULL;\n        AVS_UNIT_ASSERT_SUCCESS(_anjay_notify_queue_instance_created(\n                &queue, &MAKE_INSTANCE_PATH(TEST->oid, iid)));\n\n        // transaction validation\n        _anjay_mock_dm_expect_list_instances(\n                anjay, &TEST, 0, (anjay_iid_t[]){ iid, ANJAY_ID_INVALID });\n        _anjay_mock_dm_expect_list_instances(\n                anjay, &FAKE_SERVER, 0,\n                (const anjay_iid_t[]) { 0, ANJAY_ID_INVALID });\n        _anjay_mock_dm_expect_list_resources(\n                anjay, &FAKE_SERVER, 0, 0,\n                (const anjay_mock_dm_res_entry_t[]) {\n                        { ANJAY_DM_RID_SERVER_SSID, ANJAY_DM_RES_R,\n                          ANJAY_DM_RES_PRESENT },\n                        ANJAY_MOCK_DM_RES_END });\n        _anjay_mock_dm_expect_resource_read(anjay, &FAKE_SERVER, 0,\n                                            ANJAY_DM_RID_SERVER_SSID,\n                                            ANJAY_ID_INVALID, 0,\n                                            ANJAY_MOCK_DM_INT(0, ssid));\n        AVS_UNIT_ASSERT_SUCCESS(\n                _anjay_notify_flush(anjay_unlocked, ssid, &queue));\n        ANJAY_MUTEX_UNLOCK(anjay);\n    }\n\n    // NULL AC object ptr\n    AVS_UNIT_ASSERT_FAILED(anjay_access_control_set_acl(\n            NULL, TEST->oid, iid, ssid, ANJAY_ACCESS_MASK_NONE));\n\n    // unknown Object ID\n    AVS_UNIT_ASSERT_FAILED(\n            anjay_access_control_set_acl(anjay, (anjay_oid_t) (TEST->oid + 1),\n                                         iid, ssid, ANJAY_ACCESS_MASK_NONE));\n\n    // unknown Object instance ID\n    anjay_iid_t iids[iid + 2];\n    for (anjay_iid_t i = 0; i <= iid; ++i) {\n        iids[i] = i;\n    }\n    iids[iid + 1] = ANJAY_ID_INVALID;\n    _anjay_mock_dm_expect_list_instances(anjay, &TEST, 0, iids);\n    AVS_UNIT_ASSERT_FAILED(anjay_access_control_set_acl(\n            anjay, TEST->oid, (anjay_iid_t) (iid + 1), ssid,\n            ANJAY_ACCESS_MASK_NONE));\n\n    // Create flag in access mask\n    AVS_UNIT_ASSERT_FAILED(anjay_access_control_set_acl(\n            anjay, TEST->oid, iid, ssid, ANJAY_ACCESS_MASK_CREATE));\n    AVS_UNIT_ASSERT_FAILED(anjay_access_control_set_acl(\n            anjay, TEST->oid, iid, ssid, ANJAY_ACCESS_MASK_FULL));\n\n    {\n        // valid call\n        anjay_access_mask_t mask =\n                ANJAY_ACCESS_MASK_READ | ANJAY_ACCESS_MASK_WRITE\n                | ANJAY_ACCESS_MASK_EXECUTE | ANJAY_ACCESS_MASK_DELETE;\n        AVS_UNIT_ASSERT_SUCCESS(anjay_access_control_set_acl(anjay, TEST->oid,\n                                                             iid, ssid, mask));\n\n        ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n        access_control_t *ac = _anjay_access_control_get(anjay_unlocked);\n        AVS_UNIT_ASSERT_EQUAL(AVS_LIST_SIZE(ac->current.instances), 1);\n\n        AVS_LIST(access_control_instance_t) inst = ac->current.instances;\n        AVS_UNIT_ASSERT_EQUAL(AVS_LIST_SIZE(inst->acl), 1);\n\n        AVS_UNIT_ASSERT_EQUAL(inst->acl->ssid, ssid);\n        AVS_UNIT_ASSERT_EQUAL(inst->acl->mask, mask);\n        ANJAY_MUTEX_UNLOCK(anjay);\n    }\n\n    {\n        // overwrite existing entry\n        anjay_access_mask_t mask = ANJAY_ACCESS_MASK_READ;\n        AVS_UNIT_ASSERT_SUCCESS(anjay_access_control_set_acl(anjay, TEST->oid,\n                                                             iid, ssid, mask));\n\n        ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n        access_control_t *ac = _anjay_access_control_get(anjay_unlocked);\n        AVS_UNIT_ASSERT_EQUAL(AVS_LIST_SIZE(ac->current.instances), 1);\n\n        AVS_LIST(access_control_instance_t) inst = ac->current.instances;\n        AVS_UNIT_ASSERT_EQUAL(AVS_LIST_SIZE(inst->acl), 1);\n\n        // ensure mask was overwritten\n        AVS_UNIT_ASSERT_EQUAL(inst->acl->ssid, ssid);\n        AVS_UNIT_ASSERT_EQUAL(inst->acl->mask, mask);\n        ANJAY_MUTEX_UNLOCK(anjay);\n    }\n\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(access_control, set_owner) {\n    ACCESS_CONTROL_TEST_INIT;\n\n    // SSID == 0 is invalid\n    AVS_UNIT_ASSERT_FAILED(anjay_access_control_set_owner(\n            anjay, TEST->oid, 1, ANJAY_SSID_ANY, NULL));\n\n    // Basic happy path\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &TEST, 0, (anjay_iid_t[]){ 1, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &FAKE_SERVER, 0,\n            (const anjay_iid_t[]) { 0, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &FAKE_SERVER, 0, 0,\n            (const anjay_mock_dm_res_entry_t[]) { { ANJAY_DM_RID_SERVER_SSID,\n                                                    ANJAY_DM_RES_R,\n                                                    ANJAY_DM_RES_PRESENT },\n                                                  ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read(anjay, &FAKE_SERVER, 0,\n                                        ANJAY_DM_RID_SERVER_SSID,\n                                        ANJAY_ID_INVALID, 0,\n                                        ANJAY_MOCK_DM_INT(0, 1));\n    AVS_UNIT_ASSERT_SUCCESS(\n            anjay_access_control_set_owner(anjay, TEST->oid, 1, 1, NULL));\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    access_control_t *ac = _anjay_access_control_get(anjay_unlocked);\n    AVS_UNIT_ASSERT_EQUAL(AVS_LIST_SIZE(ac->current.instances), 1);\n    AVS_LIST(access_control_instance_t) inst = ac->current.instances;\n    AVS_UNIT_ASSERT_EQUAL(inst->iid, 0);\n    AVS_UNIT_ASSERT_EQUAL(inst->owner, 1);\n    ANJAY_MUTEX_UNLOCK(anjay);\n\n    // Conflicting Access Control Object Instance ID\n    anjay_iid_t inout_acl_iid = 1;\n    AVS_UNIT_ASSERT_FAILED(anjay_access_control_set_owner(anjay, TEST->oid, 1,\n                                                          2, &inout_acl_iid));\n    AVS_UNIT_ASSERT_EQUAL(inout_acl_iid, 0);\n\n    // Validation failure: inexistent target\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &TEST, 0, (anjay_iid_t[]){ 1, ANJAY_ID_INVALID });\n    AVS_UNIT_ASSERT_FAILED(\n            anjay_access_control_set_owner(anjay, TEST->oid, 2, 1, NULL));\n\n    // Happy path with reading of Access Control Object Instance ID\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &TEST, 0, (anjay_iid_t[]){ 1, 2, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &FAKE_SERVER, 0,\n            (const anjay_iid_t[]) { 0, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &FAKE_SERVER, 0, 0,\n            (const anjay_mock_dm_res_entry_t[]) { { ANJAY_DM_RID_SERVER_SSID,\n                                                    ANJAY_DM_RES_R,\n                                                    ANJAY_DM_RES_PRESENT },\n                                                  ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read(anjay, &FAKE_SERVER, 0,\n                                        ANJAY_DM_RID_SERVER_SSID,\n                                        ANJAY_ID_INVALID, 0,\n                                        ANJAY_MOCK_DM_INT(0, 1));\n    inout_acl_iid = ANJAY_ID_INVALID;\n    AVS_UNIT_ASSERT_SUCCESS(anjay_access_control_set_owner(anjay, TEST->oid, 2,\n                                                           1, &inout_acl_iid));\n    AVS_UNIT_ASSERT_EQUAL(inout_acl_iid, 1);\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    access_control_t *ac = _anjay_access_control_get(anjay_unlocked);\n    AVS_UNIT_ASSERT_EQUAL(AVS_LIST_SIZE(ac->current.instances), 2);\n    ANJAY_MUTEX_UNLOCK(anjay);\n\n    // SSID validation error (existing target)\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &FAKE_SERVER, 0,\n            (const anjay_iid_t[]) { 0, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &FAKE_SERVER, 0, 0,\n            (const anjay_mock_dm_res_entry_t[]) { { ANJAY_DM_RID_SERVER_SSID,\n                                                    ANJAY_DM_RES_R,\n                                                    ANJAY_DM_RES_PRESENT },\n                                                  ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read(anjay, &FAKE_SERVER, 0,\n                                        ANJAY_DM_RID_SERVER_SSID,\n                                        ANJAY_ID_INVALID, 0,\n                                        ANJAY_MOCK_DM_INT(0, 1));\n    AVS_UNIT_ASSERT_FAILED(\n            anjay_access_control_set_owner(anjay, TEST->oid, 2, 2, NULL));\n\n    // SSID validation error (new target)\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &TEST, 0, (anjay_iid_t[]){ 1, 2, 3, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &FAKE_SERVER, 0,\n            (const anjay_iid_t[]) { 0, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &FAKE_SERVER, 0, 0,\n            (const anjay_mock_dm_res_entry_t[]) { { ANJAY_DM_RID_SERVER_SSID,\n                                                    ANJAY_DM_RES_R,\n                                                    ANJAY_DM_RES_PRESENT },\n                                                  ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read(anjay, &FAKE_SERVER, 0,\n                                        ANJAY_DM_RID_SERVER_SSID,\n                                        ANJAY_ID_INVALID, 0,\n                                        ANJAY_MOCK_DM_INT(0, 1));\n    AVS_UNIT_ASSERT_FAILED(\n            anjay_access_control_set_owner(anjay, TEST->oid, 3, 2, NULL));\n\n    // No-op\n    AVS_UNIT_ASSERT_SUCCESS(anjay_access_control_set_owner(anjay, TEST->oid, 2,\n                                                           1, &inout_acl_iid));\n\n    // Changing owner to the Bootstrap Server\n    AVS_UNIT_ASSERT_SUCCESS(anjay_access_control_set_owner(\n            anjay, TEST->oid, 2, ANJAY_SSID_BOOTSTRAP, &inout_acl_iid));\n\n    // Happy path with setting of Access Control Object Instance ID\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &TEST, 0, (anjay_iid_t[]){ 1, 2, 21, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &FAKE_SERVER, 0,\n            (const anjay_iid_t[]) { 0, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &FAKE_SERVER, 0, 0,\n            (const anjay_mock_dm_res_entry_t[]) { { ANJAY_DM_RID_SERVER_SSID,\n                                                    ANJAY_DM_RES_R,\n                                                    ANJAY_DM_RES_PRESENT },\n                                                  ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read(anjay, &FAKE_SERVER, 0,\n                                        ANJAY_DM_RID_SERVER_SSID,\n                                        ANJAY_ID_INVALID, 0,\n                                        ANJAY_MOCK_DM_INT(0, 1));\n    inout_acl_iid = 37;\n    AVS_UNIT_ASSERT_SUCCESS(anjay_access_control_set_owner(anjay, TEST->oid, 21,\n                                                           1, &inout_acl_iid));\n    AVS_UNIT_ASSERT_EQUAL(inout_acl_iid, 37);\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    access_control_t *ac = _anjay_access_control_get(anjay_unlocked);\n    AVS_UNIT_ASSERT_EQUAL(AVS_LIST_SIZE(ac->current.instances), 3);\n    AVS_UNIT_ASSERT_EQUAL(ac->current.instances->iid, 0);\n    AVS_UNIT_ASSERT_EQUAL(AVS_LIST_NEXT(ac->current.instances)->iid, 1);\n    AVS_UNIT_ASSERT_EQUAL(\n            AVS_LIST_NEXT(AVS_LIST_NEXT(ac->current.instances))->iid, 37);\n    ANJAY_MUTEX_UNLOCK(anjay);\n\n    // Attempting to reuse existing Access Control Object Instance ID\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &TEST, 0, (anjay_iid_t[]){ 1, 2, 21, 42, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &FAKE_SERVER, 0,\n            (const anjay_iid_t[]) { 0, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_resources(\n            anjay, &FAKE_SERVER, 0, 0,\n            (const anjay_mock_dm_res_entry_t[]) { { ANJAY_DM_RID_SERVER_SSID,\n                                                    ANJAY_DM_RES_R,\n                                                    ANJAY_DM_RES_PRESENT },\n                                                  ANJAY_MOCK_DM_RES_END });\n    _anjay_mock_dm_expect_resource_read(anjay, &FAKE_SERVER, 0,\n                                        ANJAY_DM_RID_SERVER_SSID,\n                                        ANJAY_ID_INVALID, 0,\n                                        ANJAY_MOCK_DM_INT(0, 1));\n    inout_acl_iid = 37;\n    AVS_UNIT_ASSERT_FAILED(anjay_access_control_set_owner(anjay, TEST->oid, 42,\n                                                          1, &inout_acl_iid));\n    AVS_UNIT_ASSERT_EQUAL(inout_acl_iid, 37);\n\n    DM_TEST_FINISH;\n}\n"
  },
  {
    "path": "tests/modules/access_control/persistence.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <avsystem/commons/avs_unit_mock_helpers.h>\n#include <avsystem/commons/avs_unit_test.h>\n\n#include <avsystem/commons/avs_stream_inbuf.h>\n#include <avsystem/commons/avs_stream_outbuf.h>\n\n#include <anjay/access_control.h>\n#include <anjay/core.h>\n\n#include \"src/modules/access_control/anjay_mod_access_control.h\"\n\nstatic int null_list_instances(anjay_t *anjay,\n                               const anjay_dm_object_def_t *const *obj_ptr,\n                               anjay_dm_list_ctx_t *ctx) {\n    (void) anjay;\n    (void) obj_ptr;\n    (void) ctx;\n    return 0;\n}\n\nstatic anjay_dm_object_def_t *make_mock_object(anjay_oid_t oid) {\n    anjay_dm_object_def_t *obj =\n            (anjay_dm_object_def_t *) avs_calloc(1,\n                                                 sizeof(anjay_dm_object_def_t));\n    if (obj) {\n        obj->oid = oid;\n        obj->handlers.list_instances = null_list_instances;\n    }\n    return obj;\n}\n\ntypedef bool comparator_t(const void *a, const void *b);\n\nstatic bool\nlists_equal(AVS_LIST(void) a, AVS_LIST(void) b, comparator_t *equals) {\n    AVS_LIST(void) p = a;\n    AVS_LIST(void) q = b;\n    while (p && q) {\n        if (!equals(p, q)) {\n            return false;\n        }\n        p = AVS_LIST_NEXT(p);\n        q = AVS_LIST_NEXT(q);\n    }\n    return p == q;\n}\n\nstatic bool acl_entry_equal(const void *a, const void *b) {\n    const acl_entry_t *p = (const acl_entry_t *) a;\n    const acl_entry_t *q = (const acl_entry_t *) b;\n    return p == q || (p->mask == q->mask && p->ssid == q->ssid);\n}\n\nstatic bool instances_equal(const void *a, const void *b) {\n    const access_control_instance_t *p = (const access_control_instance_t *) a;\n    const access_control_instance_t *q = (const access_control_instance_t *) b;\n    return (p == q)\n           || (p->iid == q->iid && p->target.oid == q->target.oid\n               && p->target.iid == q->target.iid && p->owner == q->owner\n               && lists_equal(p->acl, q->acl, acl_entry_equal));\n}\n\nstatic bool aco_equal(access_control_t *a, access_control_t *b) {\n    return lists_equal(a->current.instances, b->current.instances,\n                       instances_equal);\n}\n\nstatic anjay_t *ac_test_create_fake_anjay(void) {\n    anjay_configuration_t fake_config;\n    memset(&fake_config, 0, sizeof(fake_config));\n    fake_config.endpoint_name = \"fake\";\n    anjay_t *fake_anjay = anjay_new(&fake_config);\n    AVS_UNIT_ASSERT_NOT_NULL(fake_anjay);\n    return fake_anjay;\n}\n\ntypedef struct {\n    char buffer[8192];\n    avs_stream_inbuf_t in;\n    avs_stream_outbuf_t out;\n} storage_ctx_t;\n\nstatic void init_context(storage_ctx_t *ctx) {\n    memcpy(&ctx->in, &AVS_STREAM_INBUF_STATIC_INITIALIZER,\n           sizeof(avs_stream_inbuf_t));\n    memcpy(&ctx->out, &AVS_STREAM_OUTBUF_STATIC_INITIALIZER,\n           sizeof(avs_stream_outbuf_t));\n    ctx->out.buffer = ctx->buffer;\n    ctx->out.buffer_size = sizeof(ctx->buffer);\n    ctx->in.buffer = ctx->buffer;\n}\n\nAVS_UNIT_TEST(access_control_persistence, empty_aco) {\n    anjay_t *anjay1 = ac_test_create_fake_anjay();\n    anjay_t *anjay2 = ac_test_create_fake_anjay();\n\n    storage_ctx_t ctx = {\n        .buffer = { 0 }\n    };\n    init_context(&ctx);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_access_control_install(anjay1));\n    AVS_UNIT_ASSERT_SUCCESS(anjay_access_control_install(anjay2));\n    AVS_UNIT_ASSERT_SUCCESS(\n            anjay_access_control_persist(anjay1, (avs_stream_t *) &ctx.out));\n\n    ctx.in.buffer_size = avs_stream_outbuf_offset(&ctx.out);\n    AVS_UNIT_ASSERT_SUCCESS(\n            anjay_access_control_restore(anjay2, (avs_stream_t *) &ctx.in));\n    ANJAY_MUTEX_LOCK(anjay1_unlocked, anjay1);\n    ANJAY_MUTEX_LOCK(anjay2_unlocked, anjay2);\n    AVS_UNIT_ASSERT_TRUE(aco_equal(_anjay_access_control_get(anjay1_unlocked),\n                                   _anjay_access_control_get(anjay2_unlocked)));\n    ANJAY_MUTEX_UNLOCK(anjay2);\n    AVS_UNIT_ASSERT_NULL(\n            _anjay_access_control_get(anjay1_unlocked)->current.instances);\n    ANJAY_MUTEX_UNLOCK(anjay1);\n\n    anjay_delete(anjay1);\n    anjay_delete(anjay2);\n}\n\nAVS_UNIT_TEST(access_control_persistence, normal_usage) {\n    anjay_t *anjay1 = ac_test_create_fake_anjay();\n    anjay_t *anjay2 = ac_test_create_fake_anjay();\n\n    storage_ctx_t ctx = {\n        .buffer = { 0 }\n    };\n    init_context(&ctx);\n\n    AVS_UNIT_ASSERT_SUCCESS(anjay_access_control_install(anjay1));\n    AVS_UNIT_ASSERT_SUCCESS(anjay_access_control_install(anjay2));\n\n    access_control_t *ac1;\n    ANJAY_MUTEX_LOCK(anjay1_unlocked, anjay1);\n    ac1 = _anjay_access_control_get(anjay1_unlocked);\n    ANJAY_MUTEX_UNLOCK(anjay1);\n\n    access_control_t *ac2;\n    ANJAY_MUTEX_LOCK(anjay2_unlocked, anjay2);\n    ac2 = _anjay_access_control_get(anjay2_unlocked);\n    ANJAY_MUTEX_UNLOCK(anjay2);\n\n    const anjay_dm_object_def_t *mock_obj1 = make_mock_object(32);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_register_object(anjay1, &mock_obj1));\n    AVS_UNIT_ASSERT_SUCCESS(anjay_register_object(anjay2, &mock_obj1));\n    const anjay_dm_object_def_t *mock_obj2 = make_mock_object(64);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_register_object(anjay1, &mock_obj2));\n    AVS_UNIT_ASSERT_SUCCESS(anjay_register_object(anjay2, &mock_obj2));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_access_control_add_instance(\n            ac1,\n            _anjay_access_control_create_missing_ac_instance(\n                    &(const acl_target_t) {\n                        .oid = mock_obj1->oid,\n                        .iid = ANJAY_ID_INVALID\n                    }),\n            NULL));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_access_control_add_instance(\n            ac1,\n            _anjay_access_control_create_missing_ac_instance(\n                    &(const acl_target_t) {\n                        .oid = mock_obj2->oid,\n                        .iid = ANJAY_ID_INVALID\n                    }),\n            NULL));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_access_control_add_instance(\n            ac2,\n            _anjay_access_control_create_missing_ac_instance(\n                    &(const acl_target_t) {\n                        .oid = mock_obj1->oid,\n                        .iid = ANJAY_ID_INVALID\n                    }),\n            NULL));\n    AVS_UNIT_ASSERT_SUCCESS(_anjay_access_control_add_instance(\n            ac2,\n            _anjay_access_control_create_missing_ac_instance(\n                    &(const acl_target_t) {\n                        .oid = mock_obj2->oid,\n                        .iid = ANJAY_ID_INVALID\n                    }),\n            NULL));\n    // There are now 2 bootstrap instances.\n    AVS_UNIT_ASSERT_EQUAL(AVS_LIST_SIZE(ac1->current.instances), 2);\n    AVS_UNIT_ASSERT_EQUAL(AVS_LIST_SIZE(ac2->current.instances), 2);\n\n    AVS_LIST(acl_entry_t) acl1 = NULL;\n    AVS_LIST_APPEND(&acl1, AVS_LIST_NEW_ELEMENT(acl_entry_t));\n    *acl1 = (acl_entry_t) {\n        .mask = 0xFFFF,\n        .ssid = 1\n    };\n    AVS_LIST_INSERT(&acl1, AVS_LIST_NEW_ELEMENT(acl_entry_t));\n    *acl1 = (acl_entry_t) {\n        .mask = 0xDEAD,\n        .ssid = 0xBABE\n    };\n    access_control_instance_t instance1 = (access_control_instance_t) {\n        .target = {\n            .oid = 32,\n            .iid = 42\n        },\n        .iid = 3,\n        .owner = 23,\n        .has_acl = true,\n        .acl = acl1\n    };\n    access_control_instance_t instance2 = (access_control_instance_t) {\n        .target = {\n            .oid = 64,\n            .iid = 43\n        },\n        .iid = 4,\n        .owner = 32\n    };\n    AVS_LIST(access_control_instance_t) entry1 =\n            AVS_LIST_NEW_ELEMENT(access_control_instance_t);\n    AVS_LIST(access_control_instance_t) entry2 =\n            AVS_LIST_NEW_ELEMENT(access_control_instance_t);\n    *entry1 = instance1;\n    *entry2 = instance2;\n    AVS_LIST_APPEND(&ac1->current.instances, entry1);\n    AVS_LIST_APPEND(&ac1->current.instances, entry2);\n    AVS_UNIT_ASSERT_EQUAL(AVS_LIST_SIZE(ac1->current.instances), 4);\n    AVS_UNIT_ASSERT_SUCCESS(\n            anjay_access_control_persist(anjay1, (avs_stream_t *) &ctx.out));\n\n    ctx.in.buffer_size = avs_stream_outbuf_offset(&ctx.out);\n    AVS_UNIT_ASSERT_SUCCESS(\n            anjay_access_control_restore(anjay2, (avs_stream_t *) &ctx.in));\n\n    AVS_UNIT_ASSERT_EQUAL(AVS_LIST_SIZE(ac2->current.instances), 4);\n    AVS_UNIT_ASSERT_TRUE(aco_equal(ac1, ac2));\n\n    anjay_delete(anjay1);\n    anjay_delete(anjay2);\n\n    avs_free((anjay_dm_object_def_t *) (intptr_t) mock_obj1);\n    avs_free((anjay_dm_object_def_t *) (intptr_t) mock_obj2);\n}\n"
  },
  {
    "path": "tests/modules/factory_provisioning/provisioning.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#include <avsystem/commons/avs_stream_membuf.h>\n#include <avsystem/commons/avs_unit_test.h>\n\n#include <anjay/factory_provisioning.h>\n\n#include \"tests/utils/dm.h\"\n\n// NOTE: Success case is tested by\n// tests/integration/suites/defaultfactory_provisioning.py\n\nAVS_UNIT_TEST(factory_provisioning, fail_rollback) {\n    DM_TEST_INIT_WITH_OBJECTS(&OBJ_WITH_TRANSACTION, &FAKE_SECURITY,\n                              &FAKE_SERVER);\n    avs_stream_t *stream = avs_stream_membuf_create();\n    AVS_UNIT_ASSERT_NOT_NULL(stream);\n    static const char PROVISIONING_DATA[] = \"\\x82\" // array(2)\n                                            \"\\xa2\" // map(2)\n                                            \"\\x00\\x69\"\n                                            \"/69/420/2\" // name: \"/69/420/2\"\n                                            \"\\x02\\x01\"  // value: 1\n                                            \"\\xa2\"      // map(2)\n                                            \"\\x00\\x69\"\n                                            \"/69/420/3\" // name: \"/69/420/3\"\n                                            \"\\x02\\x07\"; // value: 7\n    AVS_UNIT_ASSERT_SUCCESS(avs_stream_write(stream, PROVISIONING_DATA,\n                                             sizeof(PROVISIONING_DATA) - 1));\n    avs_unit_mocksock_expect_shutdown(mocksocks[0]);\n    // Implicit DELETE /\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &FAKE_SERVER, 0, (const anjay_iid_t[]) { ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_list_instances(anjay, &OBJ_WITH_TRANSACTION, 0,\n                                         (const anjay_iid_t[]) {\n                                                 ANJAY_ID_INVALID });\n    // actual write\n    _anjay_mock_dm_expect_list_instances(anjay, &OBJ_WITH_TRANSACTION, 0,\n                                         (const anjay_iid_t[]) {\n                                                 ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_transaction_begin(anjay, &OBJ_WITH_TRANSACTION, 0);\n    _anjay_mock_dm_expect_instance_create(anjay, &OBJ_WITH_TRANSACTION, 420, 0);\n    _anjay_mock_dm_expect_resource_write(anjay, &OBJ_WITH_TRANSACTION, 420, 2,\n                                         ANJAY_ID_INVALID,\n                                         ANJAY_MOCK_DM_INT(0, 1), 0);\n    _anjay_mock_dm_expect_list_instances(\n            anjay, &OBJ_WITH_TRANSACTION, 0,\n            (const anjay_iid_t[]) { 420, ANJAY_ID_INVALID });\n    _anjay_mock_dm_expect_resource_write(anjay, &OBJ_WITH_TRANSACTION, 420, 3,\n                                         ANJAY_ID_INVALID,\n                                         ANJAY_MOCK_DM_INT(0, 7), 0);\n    // fail transaction validation\n    _anjay_mock_dm_expect_transaction_validate(anjay, &OBJ_WITH_TRANSACTION,\n                                               -1);\n    _anjay_mock_dm_expect_transaction_rollback(anjay, &OBJ_WITH_TRANSACTION, 0);\n    AVS_UNIT_ASSERT_FAILED(anjay_factory_provision(anjay, stream));\n    avs_stream_cleanup(&stream);\n    DM_TEST_FINISH;\n}\n"
  },
  {
    "path": "tests/modules/lwm2m_gateway/lwm2m_gateway.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n#include <avsystem/commons/avs_unit_test.h>\n\n#include \"src/core/anjay_io_core.h\"\n#include \"tests/utils/dm.h\"\n\n#define LWM2M_GATEWAY_TESTS_INIT()                                         \\\n    DM_TEST_INIT_WITH_OBJECTS(&FAKE_SECURITY, &FAKE_SERVER);               \\\n    AVS_UNIT_ASSERT_SUCCESS(anjay_lwm2m_gateway_install(anjay));           \\\n    anjay_iid_t iid = ANJAY_ID_INVALID;                                    \\\n    lwm2m_gateway_instance_t *inst;                                        \\\n    lwm2m_gateway_obj_t *gw;                                               \\\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);                               \\\n    gw = (lwm2m_gateway_obj_t *) _anjay_dm_module_get_arg(anjay_unlocked,  \\\n                                                          gateway_delete); \\\n    (void) gw;                                                             \\\n    (void) inst;                                                           \\\n    (void) iid;                                                            \\\n    ANJAY_MUTEX_UNLOCK(anjay);\n\nAVS_UNIT_TEST(lwm2m_gateway, add_and_remove_instances) {\n    LWM2M_GATEWAY_TESTS_INIT();\n\n    // register 1st device\n    iid = ANJAY_ID_INVALID;\n    AVS_UNIT_ASSERT_SUCCESS(\n            anjay_lwm2m_gateway_register_device(anjay, \"SN01\", &iid));\n    AVS_UNIT_ASSERT_EQUAL(iid, 0);\n    inst = find_instance(gw, iid);\n    AVS_UNIT_ASSERT_NOT_NULL(inst);\n    AVS_UNIT_ASSERT_EQUAL_STRING(inst->prefix, \"dev0\");\n\n    // register 2nd device\n    iid = 1;\n    AVS_UNIT_ASSERT_SUCCESS(\n            anjay_lwm2m_gateway_register_device(anjay, \"SN02\", &iid));\n    AVS_UNIT_ASSERT_EQUAL(iid, 1);\n    inst = find_instance(gw, iid);\n    AVS_UNIT_ASSERT_NOT_NULL(inst);\n    AVS_UNIT_ASSERT_EQUAL_STRING(inst->prefix, \"dev1\");\n\n    // register 3rd device\n    iid = ANJAY_ID_INVALID;\n    AVS_UNIT_ASSERT_SUCCESS(\n            anjay_lwm2m_gateway_register_device(anjay, \"SN02\", &iid));\n    AVS_UNIT_ASSERT_EQUAL(iid, 2);\n    inst = find_instance(gw, iid);\n    AVS_UNIT_ASSERT_NOT_NULL(inst);\n    AVS_UNIT_ASSERT_EQUAL_STRING(inst->prefix, \"dev2\");\n\n    // try registering 3rd device again\n    iid = 2;\n    AVS_UNIT_ASSERT_FAILED(\n            anjay_lwm2m_gateway_register_device(anjay, \"SN02\", &iid));\n    inst = find_instance(gw, iid);\n    AVS_UNIT_ASSERT_NOT_NULL(inst);\n    AVS_UNIT_ASSERT_EQUAL_STRING(inst->prefix, \"dev2\");\n\n    // remove 2nd device\n    AVS_UNIT_ASSERT_SUCCESS(anjay_lwm2m_gateway_deregister_device(anjay, 1));\n    AVS_UNIT_ASSERT_NOT_NULL(find_instance(gw, 0));\n    AVS_UNIT_ASSERT_NULL(find_instance(gw, 1));\n    AVS_UNIT_ASSERT_NOT_NULL(find_instance(gw, 2));\n\n    // add 4th device that gets first free iid - 1\n    iid = ANJAY_ID_INVALID;\n    AVS_UNIT_ASSERT_SUCCESS(\n            anjay_lwm2m_gateway_register_device(anjay, \"SN01\", &iid));\n    AVS_UNIT_ASSERT_EQUAL(iid, 1);\n\n    // remove 1st device\n    AVS_UNIT_ASSERT_SUCCESS(anjay_lwm2m_gateway_deregister_device(anjay, 0));\n    AVS_UNIT_ASSERT_NULL(find_instance(gw, 0));\n    AVS_UNIT_ASSERT_NOT_NULL(find_instance(gw, 1));\n\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(lwm2m_gateway, register_and_deregister_before_installing) {\n    DM_TEST_INIT_WITH_OBJECTS(&FAKE_SECURITY, &FAKE_SERVER);\n\n    anjay_iid_t iid = ANJAY_ID_INVALID;\n    AVS_UNIT_ASSERT_FAILED(\n            anjay_lwm2m_gateway_register_device(anjay, \"SN01\", &iid));\n    AVS_UNIT_ASSERT_FAILED(anjay_lwm2m_gateway_deregister_device(anjay, 0));\n    AVS_UNIT_ASSERT_FAILED(anjay_lwm2m_gateway_deregister_device(anjay, 1));\n\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(lwm2m_gateway, deregister_non_existent) {\n    LWM2M_GATEWAY_TESTS_INIT();\n\n    AVS_UNIT_ASSERT_FAILED(anjay_lwm2m_gateway_deregister_device(anjay, 0));\n    AVS_UNIT_ASSERT_FAILED(anjay_lwm2m_gateway_deregister_device(anjay, 1));\n\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(lwm2m_gateway, install_twice) {\n    LWM2M_GATEWAY_TESTS_INIT();\n\n    AVS_UNIT_ASSERT_FAILED(anjay_lwm2m_gateway_install(anjay));\n\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(lwm2m_gateway, map_prefix_not_installed) {\n    DM_TEST_INIT_WITH_OBJECTS(&FAKE_SECURITY, &FAKE_SERVER);\n    anjay_dm_t dummy_dm;\n    const anjay_dm_t *dm = &dummy_dm;\n    anjay_attr_storage_t dummy_as;\n    anjay_attr_storage_t *as = &dummy_as;\n\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    AVS_UNIT_ASSERT_FAILED(\n            _anjay_lwm2m_gateway_prefix_to_dm(anjay_unlocked, \"dev0\", &dm));\n    AVS_UNIT_ASSERT_FAILED(\n            _anjay_lwm2m_gateway_prefix_to_as(anjay_unlocked, \"dev0\", &as));\n    ANJAY_MUTEX_UNLOCK(anjay);\n    AVS_UNIT_ASSERT_NULL(dm);\n    AVS_UNIT_ASSERT_NULL(as);\n\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(lwm2m_gateway, map_prefix_found) {\n    LWM2M_GATEWAY_TESTS_INIT();\n    const anjay_dm_t *dm = NULL;\n    anjay_attr_storage_t *as = NULL;\n\n    AVS_UNIT_ASSERT_SUCCESS(\n            anjay_lwm2m_gateway_register_device(anjay, \"SN01\", &iid));\n\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_lwm2m_gateway_prefix_to_dm(anjay_unlocked, \"dev0\", &dm));\n    AVS_UNIT_ASSERT_SUCCESS(\n            _anjay_lwm2m_gateway_prefix_to_as(anjay_unlocked, \"dev0\", &as));\n    ANJAY_MUTEX_UNLOCK(anjay);\n    AVS_UNIT_ASSERT_NOT_NULL(dm);\n    AVS_UNIT_ASSERT_NOT_NULL(as);\n\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(lwm2m_gateway, map_prefix_not_found) {\n    LWM2M_GATEWAY_TESTS_INIT();\n    anjay_dm_t dummy_dm;\n    const anjay_dm_t *dm = &dummy_dm;\n    anjay_attr_storage_t dummy_as;\n    anjay_attr_storage_t *as = &dummy_as;\n\n    AVS_UNIT_ASSERT_SUCCESS(\n            anjay_lwm2m_gateway_register_device(anjay, \"SN01\", &iid));\n\n    ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);\n    AVS_UNIT_ASSERT_FAILED(\n            _anjay_lwm2m_gateway_prefix_to_dm(anjay_unlocked, \"prefix\", &dm));\n    AVS_UNIT_ASSERT_FAILED(\n            _anjay_lwm2m_gateway_prefix_to_as(anjay_unlocked, \"prefix\", &as));\n    ANJAY_MUTEX_UNLOCK(anjay);\n    AVS_UNIT_ASSERT_NULL(dm);\n    AVS_UNIT_ASSERT_NULL(as);\n\n    DM_TEST_FINISH;\n}\n\nstatic anjay_dm_object_def_t *make_mock_object(anjay_oid_t oid) {\n    anjay_dm_object_def_t *obj =\n            (anjay_dm_object_def_t *) avs_calloc(1,\n                                                 sizeof(anjay_dm_object_def_t));\n    if (obj) {\n        obj->oid = oid;\n    }\n    return obj;\n}\n\nAVS_UNIT_TEST(lwm2m_gateway, register_objects_ok) {\n    LWM2M_GATEWAY_TESTS_INIT();\n\n    AVS_UNIT_ASSERT_SUCCESS(\n            anjay_lwm2m_gateway_register_device(anjay, \"SN01\", &iid));\n\n    const anjay_dm_object_def_t *mock_obj1 = make_mock_object(32);\n    const anjay_dm_object_def_t *mock_obj2 = make_mock_object(23);\n\n    AVS_UNIT_ASSERT_SUCCESS(\n            anjay_lwm2m_gateway_register_object(anjay, iid, &mock_obj1));\n    AVS_UNIT_ASSERT_SUCCESS(\n            anjay_lwm2m_gateway_register_object(anjay, iid, &mock_obj2));\n\n    avs_free((anjay_dm_object_def_t *) (intptr_t) mock_obj1);\n    avs_free((anjay_dm_object_def_t *) (intptr_t) mock_obj2);\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(lwm2m_gateway, register_object_gateway_not_installed) {\n    DM_TEST_INIT_WITH_OBJECTS(&FAKE_SECURITY, &FAKE_SERVER);\n    anjay_iid_t iid = 1;\n\n    const anjay_dm_object_def_t *mock_obj1 = make_mock_object(32);\n\n    AVS_UNIT_ASSERT_FAILED(\n            anjay_lwm2m_gateway_register_object(anjay, iid, &mock_obj1));\n\n    avs_free((anjay_dm_object_def_t *) (intptr_t) mock_obj1);\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(lwm2m_gateway, register_object_device_not_found) {\n    LWM2M_GATEWAY_TESTS_INIT();\n\n    AVS_UNIT_ASSERT_SUCCESS(\n            anjay_lwm2m_gateway_register_device(anjay, \"SN01\", &iid));\n    iid += 1;\n\n    const anjay_dm_object_def_t *mock_obj1 = make_mock_object(32);\n\n    AVS_UNIT_ASSERT_FAILED(\n            anjay_lwm2m_gateway_register_object(anjay, iid, &mock_obj1));\n\n    avs_free((anjay_dm_object_def_t *) (intptr_t) mock_obj1);\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(lwm2m_gateway, register_object_invalid_obj_def) {\n    LWM2M_GATEWAY_TESTS_INIT();\n\n    const anjay_dm_object_def_t *mock_obj1 = NULL;\n\n    AVS_UNIT_ASSERT_FAILED(\n            anjay_lwm2m_gateway_register_object(anjay, iid, &mock_obj1));\n\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(lwm2m_gateway, unregister_objects_ok) {\n    LWM2M_GATEWAY_TESTS_INIT();\n\n    AVS_UNIT_ASSERT_SUCCESS(\n            anjay_lwm2m_gateway_register_device(anjay, \"SN01\", &iid));\n\n    const anjay_dm_object_def_t *mock_obj1 = make_mock_object(32);\n    const anjay_dm_object_def_t *mock_obj2 = make_mock_object(23);\n\n    AVS_UNIT_ASSERT_SUCCESS(\n            anjay_lwm2m_gateway_register_object(anjay, iid, &mock_obj1));\n    AVS_UNIT_ASSERT_SUCCESS(\n            anjay_lwm2m_gateway_register_object(anjay, iid, &mock_obj2));\n    AVS_UNIT_ASSERT_SUCCESS(\n            anjay_lwm2m_gateway_unregister_object(anjay, iid, &mock_obj1));\n    AVS_UNIT_ASSERT_SUCCESS(\n            anjay_lwm2m_gateway_unregister_object(anjay, iid, &mock_obj2));\n\n    avs_free((anjay_dm_object_def_t *) (intptr_t) mock_obj1);\n    avs_free((anjay_dm_object_def_t *) (intptr_t) mock_obj2);\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(lwm2m_gateway, unregister_object_gateway_not_installed) {\n    DM_TEST_INIT_WITH_OBJECTS(&FAKE_SECURITY, &FAKE_SERVER);\n    anjay_iid_t iid = 1;\n\n    const anjay_dm_object_def_t *mock_obj1 = make_mock_object(32);\n\n    AVS_UNIT_ASSERT_FAILED(\n            anjay_lwm2m_gateway_register_object(anjay, iid, &mock_obj1));\n\n    avs_free((anjay_dm_object_def_t *) (intptr_t) mock_obj1);\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(lwm2m_gateway, unregister_object_device_not_found) {\n    LWM2M_GATEWAY_TESTS_INIT();\n\n    AVS_UNIT_ASSERT_SUCCESS(\n            anjay_lwm2m_gateway_register_device(anjay, \"SN01\", &iid));\n    iid += 1;\n\n    const anjay_dm_object_def_t *mock_obj1 = NULL;\n\n    AVS_UNIT_ASSERT_FAILED(\n            anjay_lwm2m_gateway_unregister_object(anjay, iid, &mock_obj1));\n\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(lwm2m_gateway, unregister_object_invalid_obj_def) {\n    LWM2M_GATEWAY_TESTS_INIT();\n\n    const anjay_dm_object_def_t *mock_obj1 = NULL;\n\n    AVS_UNIT_ASSERT_FAILED(\n            anjay_lwm2m_gateway_unregister_object(anjay, iid, &mock_obj1));\n\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(lwm2m_gateway, unregister_not_installed) {\n    LWM2M_GATEWAY_TESTS_INIT();\n\n    const anjay_dm_object_def_t *mock_obj1 = make_mock_object(32);\n\n    AVS_UNIT_ASSERT_FAILED(\n            anjay_lwm2m_gateway_unregister_object(anjay, iid, &mock_obj1));\n\n    avs_free((anjay_dm_object_def_t *) (intptr_t) mock_obj1);\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(lwm2m_gateway, notify_changed_success) {\n    LWM2M_GATEWAY_TESTS_INIT();\n\n    // Register device\n    iid = ANJAY_ID_INVALID;\n    AVS_UNIT_ASSERT_SUCCESS(\n            anjay_lwm2m_gateway_register_device(anjay, \"SN01\", &iid));\n    AVS_UNIT_ASSERT_EQUAL(iid, 0);\n\n    // Notify a resource change\n    AVS_UNIT_ASSERT_SUCCESS(\n            anjay_lwm2m_gateway_notify_changed(anjay, iid, 3, 0, 1));\n\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(lwm2m_gateway, notify_changed_gateway_not_installed) {\n    DM_TEST_INIT_WITH_OBJECTS(&FAKE_SECURITY, &FAKE_SERVER);\n\n    // Attempt to notify without installing the gateway\n    AVS_UNIT_ASSERT_FAILED(\n            anjay_lwm2m_gateway_notify_changed(anjay, 0, 3, 0, 1));\n\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(lwm2m_gateway, notify_changed_device_not_registered) {\n    LWM2M_GATEWAY_TESTS_INIT();\n\n    // Attempt to notify a change for an unregistered device\n    AVS_UNIT_ASSERT_FAILED(\n            anjay_lwm2m_gateway_notify_changed(anjay, 1, 3, 0, 1));\n\n    iid = ANJAY_ID_INVALID;\n    AVS_UNIT_ASSERT_SUCCESS(\n            anjay_lwm2m_gateway_register_device(anjay, \"SN01\", &iid));\n    AVS_UNIT_ASSERT_EQUAL(iid, 0);\n\n    AVS_UNIT_ASSERT_FAILED(\n            anjay_lwm2m_gateway_notify_changed(anjay, 1, 3, 0, 1));\n\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(lwm2m_gateway, notify_instances_changed_success) {\n    LWM2M_GATEWAY_TESTS_INIT();\n\n    iid = ANJAY_ID_INVALID;\n    AVS_UNIT_ASSERT_SUCCESS(\n            anjay_lwm2m_gateway_register_device(anjay, \"SN01\", &iid));\n    AVS_UNIT_ASSERT_EQUAL(iid, 0);\n\n    const anjay_dm_object_def_t *mock_obj = make_mock_object(3);\n    AVS_UNIT_ASSERT_SUCCESS(\n            anjay_lwm2m_gateway_register_object(anjay, iid, &mock_obj));\n\n    AVS_UNIT_ASSERT_SUCCESS(\n            anjay_lwm2m_gateway_notify_instances_changed(anjay, iid, 3));\n\n    avs_free((anjay_dm_object_def_t *) (intptr_t) mock_obj);\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(lwm2m_gateway, notify_instances_changed_device_not_registered) {\n    LWM2M_GATEWAY_TESTS_INIT();\n\n    AVS_UNIT_ASSERT_FAILED(\n            anjay_lwm2m_gateway_notify_instances_changed(anjay, 1, 3));\n\n    iid = ANJAY_ID_INVALID;\n    AVS_UNIT_ASSERT_SUCCESS(\n            anjay_lwm2m_gateway_register_device(anjay, \"SN01\", &iid));\n    AVS_UNIT_ASSERT_EQUAL(iid, 0);\n\n    AVS_UNIT_ASSERT_FAILED(\n            anjay_lwm2m_gateway_notify_instances_changed(anjay, 1, 3));\n\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(lwm2m_gateway, notify_instances_changed_object_not_registered) {\n    LWM2M_GATEWAY_TESTS_INIT();\n\n    iid = ANJAY_ID_INVALID;\n    AVS_UNIT_ASSERT_SUCCESS(\n            anjay_lwm2m_gateway_register_device(anjay, \"SN01\", &iid));\n    AVS_UNIT_ASSERT_EQUAL(iid, 0);\n\n    // this function does not check whether OID is valid, it fails when anjay\n    // attempts to create a notification for it.\n    AVS_UNIT_ASSERT_SUCCESS(\n            anjay_lwm2m_gateway_notify_instances_changed(anjay, iid, 5));\n\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(lwm2m_gateway, notify_instances_changed_gateway_not_installed) {\n    DM_TEST_INIT_WITH_OBJECTS(&FAKE_SECURITY, &FAKE_SERVER);\n\n    AVS_UNIT_ASSERT_FAILED(\n            anjay_lwm2m_gateway_notify_instances_changed(anjay, 0, 3));\n\n    DM_TEST_FINISH;\n}\n\nAVS_UNIT_TEST(lwm2m_gateway, notify_instances_changed_invalid_oid) {\n    LWM2M_GATEWAY_TESTS_INIT();\n\n    iid = ANJAY_ID_INVALID;\n    AVS_UNIT_ASSERT_SUCCESS(\n            anjay_lwm2m_gateway_register_device(anjay, \"SN01\", &iid));\n    AVS_UNIT_ASSERT_EQUAL(iid, 0);\n\n    // this function does not check whether OID is valid, it fails when anjay\n    // attempts to create a notification for it.\n    AVS_UNIT_ASSERT_SUCCESS(\n            anjay_lwm2m_gateway_notify_instances_changed(anjay, iid, 65535));\n\n    DM_TEST_FINISH;\n}\n"
  },
  {
    "path": "tests/modules/security/api.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <avsystem/commons/avs_unit_test.h>\n\n#include \"tests/utils/utils.h\"\n\nstatic const anjay_configuration_t CONFIG = {\n    .endpoint_name = \"test\"\n};\n\ntypedef struct {\n    anjay_t *anjay;\n} security_test_env_t;\n\n#define SCOPED_SERVER_TEST_ENV(Name)                           \\\n    SCOPED_PTR(security_test_env_t, security_test_env_destroy) \\\n    Name = security_test_env_create();\n\nstatic security_test_env_t *security_test_env_create(void) {\n    security_test_env_t *env = (__typeof__(env)) avs_calloc(1, sizeof(*env));\n    AVS_UNIT_ASSERT_NOT_NULL(env);\n    env->anjay = anjay_new(&CONFIG);\n    AVS_UNIT_ASSERT_NOT_NULL(env->anjay);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_security_object_install(env->anjay));\n    return env;\n}\n\nstatic void security_test_env_destroy(security_test_env_t **env) {\n    anjay_delete((*env)->anjay);\n    avs_free(*env);\n}\n\nstatic const anjay_security_instance_t instance1 = {\n    .ssid = 0,\n    .server_uri = \"coap://1.2.3.4\",\n    .bootstrap_server = false,\n    .security_mode = ANJAY_SECURITY_NOSEC,\n    .client_holdoff_s = -1,\n    .bootstrap_timeout_s = -1\n};\n\nstatic const anjay_security_instance_t instance2 = {\n    .ssid = 1,\n    .server_uri = \"coap://1.2.3.4\",\n    .bootstrap_server = false,\n    .security_mode = ANJAY_SECURITY_NOSEC,\n    .client_holdoff_s = -1,\n    .bootstrap_timeout_s = -1\n};\n\nAVS_UNIT_TEST(security_object_api, add_instances_with_duplicated_ids) {\n    SCOPED_SERVER_TEST_ENV(env);\n    anjay_iid_t iid = 0;\n    AVS_UNIT_ASSERT_SUCCESS(\n            anjay_security_object_add_instance(env->anjay, &instance1, &iid));\n    AVS_UNIT_ASSERT_FAILED(\n            anjay_security_object_add_instance(env->anjay, &instance2, &iid));\n}\n\nAVS_UNIT_TEST(security_object_api, add_instances_with_duplicated_ssids) {\n    SCOPED_SERVER_TEST_ENV(env);\n    anjay_iid_t iid = 1;\n    AVS_UNIT_ASSERT_SUCCESS(\n            anjay_security_object_add_instance(env->anjay, &instance1, &iid));\n    iid = 2;\n    AVS_UNIT_ASSERT_SUCCESS(\n            anjay_security_object_add_instance(env->anjay, &instance2, &iid));\n    iid = 3;\n    AVS_UNIT_ASSERT_FAILED(\n            anjay_security_object_add_instance(env->anjay, &instance1, &iid));\n    AVS_UNIT_ASSERT_FAILED(\n            anjay_security_object_add_instance(env->anjay, &instance2, &iid));\n}\n\nAVS_UNIT_TEST(security_object_api, add_instance_with_null_uri) {\n    SCOPED_SERVER_TEST_ENV(env);\n    anjay_iid_t iid = 1;\n    static const anjay_security_instance_t instance = {\n        .ssid = 0,\n        .server_uri = NULL,\n        .bootstrap_server = false,\n        .security_mode = ANJAY_SECURITY_NOSEC,\n        .client_holdoff_s = -1,\n        .bootstrap_timeout_s = -1\n    };\n    AVS_UNIT_ASSERT_FAILED(\n            anjay_security_object_add_instance(env->anjay, &instance, &iid));\n}\n"
  },
  {
    "path": "tests/modules/security/persistence.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <avsystem/commons/avs_stream.h>\n#include <avsystem/commons/avs_stream_membuf.h>\n#include <avsystem/commons/avs_unit_test.h>\n\n#include \"tests/utils/utils.h\"\n\nstatic const anjay_configuration_t CONFIG = {\n    .endpoint_name = \"test\"\n};\n\ntypedef struct {\n    anjay_t *anjay_stored;\n    anjay_t *anjay_restored;\n    anjay_dm_installed_object_t stored;\n    anjay_dm_installed_object_t restored;\n    sec_repr_t *stored_repr;\n    sec_repr_t *restored_repr;\n    avs_stream_t *stream;\n} security_persistence_test_env_t;\n\n#define SCOPED_SECURITY_PERSISTENCE_TEST_ENV(Name)    \\\n    SCOPED_PTR(security_persistence_test_env_t,       \\\n               security_persistence_test_env_destroy) \\\n    Name = security_persistence_test_env_create();\n\nstatic security_persistence_test_env_t *\nsecurity_persistence_test_env_create(void) {\n    security_persistence_test_env_t *env =\n            (__typeof__(env)) avs_calloc(1, sizeof(*env));\n    AVS_UNIT_ASSERT_NOT_NULL(env);\n    env->anjay_stored = anjay_new(&CONFIG);\n    AVS_UNIT_ASSERT_NOT_NULL(env->anjay_stored);\n    env->anjay_restored = anjay_new(&CONFIG);\n    AVS_UNIT_ASSERT_NOT_NULL(env->anjay_restored);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_security_object_install(env->anjay_stored));\n    AVS_UNIT_ASSERT_SUCCESS(anjay_security_object_install(env->anjay_restored));\n    env->stream = avs_stream_membuf_create();\n    AVS_UNIT_ASSERT_NOT_NULL(env->stream);\n    ANJAY_MUTEX_LOCK(anjay_unlocked, env->anjay_stored);\n    env->stored = *_anjay_dm_find_object_by_oid(_anjay_get_dm(anjay_unlocked),\n                                                ANJAY_DM_OID_SECURITY);\n    ANJAY_MUTEX_UNLOCK(env->anjay_stored);\n    ANJAY_MUTEX_LOCK(anjay_unlocked, env->anjay_restored);\n    env->restored = *_anjay_dm_find_object_by_oid(_anjay_get_dm(anjay_unlocked),\n                                                  ANJAY_DM_OID_SECURITY);\n    ANJAY_MUTEX_UNLOCK(env->anjay_restored);\n    env->stored_repr = _anjay_sec_get(env->stored);\n    env->restored_repr = _anjay_sec_get(env->restored);\n    return env;\n}\n\nstatic void\nsecurity_persistence_test_env_destroy(security_persistence_test_env_t **env) {\n    anjay_delete((*env)->anjay_stored);\n    anjay_delete((*env)->anjay_restored);\n    avs_stream_cleanup(&(*env)->stream);\n    avs_free(*env);\n}\n\nAVS_UNIT_TEST(security_persistence, empty_store_restore) {\n    SCOPED_SECURITY_PERSISTENCE_TEST_ENV(env);\n    AVS_UNIT_ASSERT_EQUAL(0, AVS_LIST_SIZE(env->stored_repr->instances));\n    AVS_UNIT_ASSERT_SUCCESS(\n            anjay_security_object_persist(env->anjay_stored, env->stream));\n    AVS_UNIT_ASSERT_SUCCESS(\n            anjay_security_object_restore(env->anjay_restored, env->stream));\n    AVS_UNIT_ASSERT_EQUAL(0, AVS_LIST_SIZE(env->restored_repr->instances));\n}\n\nstatic const char BUFFERS[][50] = {\n    \"Fitter Happier, more productive                \",\n    \"comfortable, not drinking too much             \",\n    \"regular exercise at the gym (3 days a week) ...\"\n};\n\nstatic const anjay_security_instance_t BOOTSTRAP_INSTANCE = {\n    .ssid = 0,\n    .server_uri = \"coap://at.ease/eating?well\",\n    .bootstrap_server = true,\n    .security_mode = ANJAY_SECURITY_NOSEC,\n    .client_holdoff_s = -1,\n    .bootstrap_timeout_s = -1,\n    .public_cert_or_psk_identity = (const uint8_t *) BUFFERS[0],\n    .public_cert_or_psk_identity_size = sizeof(BUFFERS[0]),\n    .private_cert_or_psk_key = (const uint8_t *) BUFFERS[1],\n    .private_cert_or_psk_key_size = sizeof(BUFFERS[1]),\n    .server_public_key = (const uint8_t *) BUFFERS[2],\n    .server_public_key_size = sizeof(BUFFERS[2])\n};\n\nstatic void assert_raw_buffers_equal(const anjay_raw_buffer_t *a,\n                                     const anjay_raw_buffer_t *b) {\n    AVS_UNIT_ASSERT_EQUAL(a->size, b->size);\n    AVS_UNIT_ASSERT_EQUAL_BYTES_SIZED(a->data, b->data, a->size);\n}\n\n#ifdef ANJAY_WITH_SECURITY_STRUCTURED\nstatic const avs_crypto_security_info_union_t *\nget_actual_security_info(const avs_crypto_security_info_union_t *info) {\n    switch (info->source) {\n    case AVS_CRYPTO_DATA_SOURCE_ARRAY:\n        AVS_UNIT_ASSERT_EQUAL(info->info.array.element_count, 1);\n        AVS_UNIT_ASSERT_EQUAL(info->info.array.array_ptr[0].type, info->type);\n        return get_actual_security_info(&info->info.array.array_ptr[0]);\n    case AVS_CRYPTO_DATA_SOURCE_LIST:\n        AVS_UNIT_ASSERT_NOT_NULL(info->info.list.list_head);\n        AVS_UNIT_ASSERT_NULL(AVS_LIST_NEXT(info->info.list.list_head));\n        AVS_UNIT_ASSERT_EQUAL(info->info.list.list_head->type, info->type);\n        return get_actual_security_info(info->info.list.list_head);\n    default:\n        return info;\n    }\n}\n#endif // ANJAY_WITH_SECURITY_STRUCTURED\n\nstatic void assert_sec_key_or_data_equal(const sec_key_or_data_t *a,\n                                         const sec_key_or_data_t *b) {\n    AVS_UNIT_ASSERT_EQUAL(a->type, b->type);\n    switch (a->type) {\n    case SEC_KEY_AS_DATA:\n        assert_raw_buffers_equal(&a->value.data, &b->value.data);\n        break;\n    case SEC_KEY_AS_KEY_EXTERNAL:\n    case SEC_KEY_AS_KEY_OWNED: {\n#ifdef ANJAY_WITH_SECURITY_STRUCTURED\n        const avs_crypto_security_info_union_t *info_a =\n                get_actual_security_info(&a->value.key.info);\n        const avs_crypto_security_info_union_t *info_b =\n                get_actual_security_info(&b->value.key.info);\n        AVS_UNIT_ASSERT_EQUAL(info_a->type, info_b->type);\n        AVS_UNIT_ASSERT_EQUAL(info_a->source, AVS_CRYPTO_DATA_SOURCE_BUFFER);\n        AVS_UNIT_ASSERT_EQUAL(info_b->source, AVS_CRYPTO_DATA_SOURCE_BUFFER);\n        AVS_UNIT_ASSERT_NULL(info_a->info.buffer.password);\n        AVS_UNIT_ASSERT_NULL(info_b->info.buffer.password);\n        AVS_UNIT_ASSERT_EQUAL(info_a->info.buffer.buffer_size,\n                              info_b->info.buffer.buffer_size);\n        AVS_UNIT_ASSERT_EQUAL_BYTES_SIZED(info_a->info.buffer.buffer,\n                                          info_b->info.buffer.buffer,\n                                          info_a->info.buffer.buffer_size);\n#else  // ANJAY_WITH_SECURITY_STRUCTURED\n        AVS_UNIT_ASSERT_NULL(\"Unsupported sec_key_or_data_t type\");\n#endif // ANJAY_WITH_SECURITY_STRUCTURED\n        break;\n    }\n    }\n}\n\nstatic void assert_instances_equal(const sec_instance_t *a,\n                                   const sec_instance_t *b) {\n    AVS_UNIT_ASSERT_EQUAL(a->iid, b->iid);\n    AVS_UNIT_ASSERT_EQUAL_STRING(a->server_uri, b->server_uri);\n    AVS_UNIT_ASSERT_EQUAL(a->is_bootstrap, b->is_bootstrap);\n    AVS_UNIT_ASSERT_EQUAL((uint32_t) a->security_mode,\n                          (uint32_t) b->security_mode);\n    assert_sec_key_or_data_equal(&a->public_cert_or_psk_identity,\n                                 &b->public_cert_or_psk_identity);\n    assert_sec_key_or_data_equal(&a->private_cert_or_psk_key,\n                                 &b->private_cert_or_psk_key);\n    assert_raw_buffers_equal(&a->server_public_key, &b->server_public_key);\n    AVS_UNIT_ASSERT_EQUAL(a->ssid, b->ssid);\n    AVS_UNIT_ASSERT_EQUAL(a->holdoff_s, b->holdoff_s);\n    AVS_UNIT_ASSERT_EQUAL(a->bs_timeout_s, b->bs_timeout_s);\n\n    for (size_t i = 0; i < AVS_ARRAY_SIZE(a->present_resources); ++i) {\n        AVS_UNIT_ASSERT_EQUAL(a->present_resources[i], b->present_resources[i]);\n    }\n}\n\nstatic void assert_objects_equal(const sec_repr_t *a, const sec_repr_t *b) {\n    AVS_LIST(sec_instance_t) a_it = a->instances;\n    AVS_LIST(sec_instance_t) b_it = b->instances;\n    AVS_UNIT_ASSERT_EQUAL(AVS_LIST_SIZE(a->instances),\n                          AVS_LIST_SIZE(b->instances));\n    while (a_it && b_it) {\n        assert_instances_equal(a_it, b_it);\n        a_it = AVS_LIST_NEXT(a_it);\n        b_it = AVS_LIST_NEXT(b_it);\n    }\n    /* Both should be NULL */\n    AVS_UNIT_ASSERT_TRUE(a_it == b_it);\n}\n\nAVS_UNIT_TEST(security_persistence, basic_store_restore) {\n    SCOPED_SECURITY_PERSISTENCE_TEST_ENV(env);\n    anjay_iid_t iid = ANJAY_ID_INVALID;\n    AVS_UNIT_ASSERT_SUCCESS(anjay_security_object_add_instance(\n            env->anjay_stored, &BOOTSTRAP_INSTANCE, &iid));\n    AVS_UNIT_ASSERT_TRUE(anjay_security_object_is_modified(env->anjay_stored));\n    AVS_UNIT_ASSERT_SUCCESS(\n            anjay_security_object_persist(env->anjay_stored, env->stream));\n    AVS_UNIT_ASSERT_FALSE(anjay_security_object_is_modified(env->anjay_stored));\n    AVS_UNIT_ASSERT_SUCCESS(\n            anjay_security_object_restore(env->anjay_restored, env->stream));\n    assert_objects_equal(_anjay_sec_get(env->stored),\n                         _anjay_sec_get(env->restored));\n}\n\n#ifdef ANJAY_WITH_SECURITY_STRUCTURED\nAVS_UNIT_TEST(security_persistence, structured_store_restore) {\n    const anjay_security_instance_t BOOTSTRAP_INSTANCE_STRUCTURED = {\n        .ssid = 0,\n        .server_uri = \"coap://at.ease/eating?well\",\n        .bootstrap_server = true,\n        .security_mode = ANJAY_SECURITY_NOSEC,\n        .client_holdoff_s = -1,\n        .bootstrap_timeout_s = -1,\n        .public_cert = avs_crypto_certificate_chain_info_from_buffer(\n                BUFFERS[0], sizeof(BUFFERS[0])),\n        .private_key = avs_crypto_private_key_info_from_buffer(\n                BUFFERS[1], sizeof(BUFFERS[1]), NULL),\n        .server_public_key = (const uint8_t *) BUFFERS[2],\n        .server_public_key_size = sizeof(BUFFERS[2])\n    };\n\n    SCOPED_SECURITY_PERSISTENCE_TEST_ENV(env);\n    anjay_iid_t iid = ANJAY_ID_INVALID;\n    AVS_UNIT_ASSERT_SUCCESS(anjay_security_object_add_instance(\n            env->anjay_stored, &BOOTSTRAP_INSTANCE_STRUCTURED, &iid));\n    AVS_UNIT_ASSERT_TRUE(anjay_security_object_is_modified(env->anjay_stored));\n    AVS_UNIT_ASSERT_SUCCESS(\n            anjay_security_object_persist(env->anjay_stored, env->stream));\n    AVS_UNIT_ASSERT_FALSE(anjay_security_object_is_modified(env->anjay_stored));\n    AVS_UNIT_ASSERT_SUCCESS(\n            anjay_security_object_restore(env->anjay_restored, env->stream));\n    assert_objects_equal(_anjay_sec_get(env->stored),\n                         _anjay_sec_get(env->restored));\n}\n#endif // ANJAY_WITH_SECURITY_STRUCTURED\n\nAVS_UNIT_TEST(security_persistence, invalid_object_to_restore) {\n    SCOPED_SECURITY_PERSISTENCE_TEST_ENV(env);\n    anjay_iid_t iid = ANJAY_ID_INVALID;\n    AVS_UNIT_ASSERT_SUCCESS(anjay_security_object_add_instance(\n            env->anjay_stored, &BOOTSTRAP_INSTANCE, &iid));\n\n    AVS_LIST(sec_instance_t) first_clone =\n            _anjay_sec_clone_instances(_anjay_sec_get(env->stored));\n    AVS_LIST(sec_instance_t) second_clone =\n            _anjay_sec_clone_instances(_anjay_sec_get(env->stored));\n    /* Two bootstrap servers on the list, this is pretty bad. */\n    first_clone->ssid = 2;\n    AVS_LIST_APPEND(&env->stored_repr->instances, first_clone);\n\n    /* This is to check that restored object will be untouched on failure */\n    AVS_LIST_APPEND(&env->restored_repr->instances, second_clone);\n\n    AVS_UNIT_ASSERT_SUCCESS(\n            anjay_security_object_persist(env->anjay_stored, env->stream));\n\n    AVS_UNIT_ASSERT_FALSE(\n            anjay_security_object_is_modified(env->anjay_restored));\n    AVS_UNIT_ASSERT_FAILED(\n            anjay_security_object_restore(env->anjay_restored, env->stream));\n    AVS_UNIT_ASSERT_FALSE(\n            anjay_security_object_is_modified(env->anjay_restored));\n\n    /* Restored Object remains untouched */\n    AVS_UNIT_ASSERT_EQUAL(AVS_LIST_SIZE(env->restored_repr->instances),\n                          AVS_LIST_SIZE(second_clone));\n    assert_instances_equal(AVS_LIST_NTH(env->restored_repr->instances, 0),\n                           AVS_LIST_NTH(second_clone, 0));\n}\n\nAVS_UNIT_TEST(security_persistence, modification_flag_add_instance) {\n    SCOPED_SECURITY_PERSISTENCE_TEST_ENV(env);\n    /* At the beginning security object is not modified */\n    AVS_UNIT_ASSERT_FALSE(anjay_security_object_is_modified(env->anjay_stored));\n    /* Invalid instance does not change the modification flag */\n    anjay_iid_t iid = ANJAY_ID_INVALID;\n    const anjay_security_instance_t invalid_instance = {\n        .server_uri = \"\"\n    };\n    AVS_UNIT_ASSERT_FAILED(anjay_security_object_add_instance(\n            env->anjay_stored, &invalid_instance, &iid));\n    AVS_UNIT_ASSERT_FALSE(anjay_security_object_is_modified(env->anjay_stored));\n    /* Same thing applies if the flag already was set to true */\n    _anjay_sec_mark_modified(_anjay_sec_get(env->stored));\n    AVS_UNIT_ASSERT_FAILED(anjay_security_object_add_instance(\n            env->anjay_stored, &invalid_instance, &iid));\n    AVS_UNIT_ASSERT_TRUE(anjay_security_object_is_modified(env->anjay_stored));\n    _anjay_sec_clear_modified(_anjay_sec_get(env->stored));\n\n    /* And valid instance does change the flag */\n    AVS_UNIT_ASSERT_SUCCESS(anjay_security_object_add_instance(\n            env->anjay_stored, &BOOTSTRAP_INSTANCE, &iid));\n    AVS_UNIT_ASSERT_TRUE(anjay_security_object_is_modified(env->anjay_stored));\n}\n\nAVS_UNIT_TEST(security_persistence, modification_flag_purge) {\n    SCOPED_SECURITY_PERSISTENCE_TEST_ENV(env);\n    /* Purged object remains unmodified after purge */\n    anjay_security_object_purge(env->anjay_stored);\n    AVS_UNIT_ASSERT_FALSE(anjay_security_object_is_modified(env->anjay_stored));\n\n    anjay_iid_t iid = ANJAY_ID_INVALID;\n    AVS_UNIT_ASSERT_SUCCESS(anjay_security_object_add_instance(\n            env->anjay_stored, &BOOTSTRAP_INSTANCE, &iid));\n    AVS_UNIT_ASSERT_TRUE(anjay_security_object_is_modified(env->anjay_stored));\n    /* Simulate persistence operation. */\n    _anjay_sec_clear_modified(_anjay_sec_get(env->stored));\n    AVS_UNIT_ASSERT_FALSE(anjay_security_object_is_modified(env->anjay_stored));\n    anjay_security_object_purge(env->anjay_stored);\n    AVS_UNIT_ASSERT_TRUE(anjay_security_object_is_modified(env->anjay_stored));\n}\n"
  },
  {
    "path": "tests/modules/server/api.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <avsystem/commons/avs_unit_test.h>\n\n#include \"tests/utils/utils.h\"\n\nstatic const anjay_configuration_t CONFIG = {\n    .endpoint_name = \"test\"\n};\n\ntypedef struct {\n    anjay_t *anjay;\n} server_test_env_t;\n\n#define SCOPED_SERVER_TEST_ENV(Name)                       \\\n    SCOPED_PTR(server_test_env_t, server_test_env_destroy) \\\n    Name = server_test_env_create();\n\nstatic server_test_env_t *server_test_env_create(void) {\n    server_test_env_t *env = (__typeof__(env)) avs_calloc(1, sizeof(*env));\n    AVS_UNIT_ASSERT_NOT_NULL(env);\n    env->anjay = anjay_new(&CONFIG);\n    AVS_UNIT_ASSERT_NOT_NULL(env->anjay);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_server_object_install(env->anjay));\n    return env;\n}\n\nstatic void server_test_env_destroy(server_test_env_t **env) {\n    anjay_delete((*env)->anjay);\n    avs_free(*env);\n}\n\nstatic const anjay_server_instance_t instance1 = {\n    .ssid = 1,\n    .lifetime = 42,\n    .default_min_period = -1,\n    .default_max_period = -1,\n    .disable_timeout = -1,\n    .binding = \"U\",\n    .notification_storing = false\n};\n\nstatic const anjay_server_instance_t instance2 = {\n    .ssid = 2,\n    .lifetime = 424,\n    .default_min_period = -1,\n    .default_max_period = -1,\n    .disable_timeout = -1,\n    .binding = \"U\",\n    .notification_storing = false\n};\n\nAVS_UNIT_TEST(server_object_api, add_instances_with_duplicated_ids) {\n    SCOPED_SERVER_TEST_ENV(env);\n    anjay_iid_t iid = 1;\n    AVS_UNIT_ASSERT_SUCCESS(\n            anjay_server_object_add_instance(env->anjay, &instance1, &iid));\n    AVS_UNIT_ASSERT_FAILED(\n            anjay_server_object_add_instance(env->anjay, &instance2, &iid));\n}\n\nAVS_UNIT_TEST(server_object_api, add_instances_with_duplicated_ssids) {\n    SCOPED_SERVER_TEST_ENV(env);\n    anjay_iid_t iid = 1;\n    AVS_UNIT_ASSERT_SUCCESS(\n            anjay_server_object_add_instance(env->anjay, &instance1, &iid));\n    iid = 2;\n    AVS_UNIT_ASSERT_SUCCESS(\n            anjay_server_object_add_instance(env->anjay, &instance2, &iid));\n    iid = 3;\n    AVS_UNIT_ASSERT_FAILED(\n            anjay_server_object_add_instance(env->anjay, &instance1, &iid));\n    AVS_UNIT_ASSERT_FAILED(\n            anjay_server_object_add_instance(env->anjay, &instance2, &iid));\n}\n\nAVS_UNIT_TEST(server_object_api, set_lifetime) {\n    SCOPED_SERVER_TEST_ENV(env);\n    anjay_iid_t iid = 1;\n    AVS_UNIT_ASSERT_SUCCESS(\n            anjay_server_object_add_instance(env->anjay, &instance1, &iid));\n    AVS_UNIT_ASSERT_SUCCESS(\n            anjay_server_object_set_lifetime(env->anjay, iid, 1234));\n}\n\nstatic const anjay_server_instance_t instance_lifetime_zero = {\n    .ssid = 1,\n    .lifetime = 0,\n    .default_min_period = -1,\n    .default_max_period = -1,\n    .disable_timeout = -1,\n    .binding = \"U\",\n    .notification_storing = false\n};\n\nAVS_UNIT_TEST(server_object_api, add_instances_with_inifinite_lifetime) {\n    SCOPED_SERVER_TEST_ENV(env);\n    anjay_iid_t iid = 1;\n    AVS_UNIT_ASSERT_SUCCESS(anjay_server_object_add_instance(\n            env->anjay, &instance_lifetime_zero, &iid));\n}\n"
  },
  {
    "path": "tests/modules/server/persistence.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <avsystem/commons/avs_stream.h>\n#include <avsystem/commons/avs_stream_membuf.h>\n#include <avsystem/commons/avs_unit_test.h>\n\n#include \"tests/utils/utils.h\"\n\nstatic const anjay_configuration_t CONFIG = {\n    .endpoint_name = \"test\"\n};\n\ntypedef struct {\n    anjay_t *anjay_stored;\n    anjay_t *anjay_restored;\n    anjay_dm_installed_object_t stored;\n    anjay_dm_installed_object_t restored;\n    server_repr_t *stored_repr;\n    server_repr_t *restored_repr;\n    avs_stream_t *stream;\n} server_persistence_test_env_t;\n\n#define SCOPED_SERVER_PERSISTENCE_TEST_ENV(Name)    \\\n    SCOPED_PTR(server_persistence_test_env_t,       \\\n               server_persistence_test_env_destroy) \\\n    Name = server_persistence_test_env_create();\n\nstatic server_persistence_test_env_t *server_persistence_test_env_create(void) {\n    server_persistence_test_env_t *env =\n            (__typeof__(env)) avs_calloc(1, sizeof(*env));\n    AVS_UNIT_ASSERT_NOT_NULL(env);\n    env->anjay_stored = anjay_new(&CONFIG);\n    AVS_UNIT_ASSERT_NOT_NULL(env->anjay_stored);\n    env->anjay_restored = anjay_new(&CONFIG);\n    AVS_UNIT_ASSERT_NOT_NULL(env->anjay_stored);\n    AVS_UNIT_ASSERT_SUCCESS(anjay_server_object_install(env->anjay_stored));\n    AVS_UNIT_ASSERT_SUCCESS(anjay_server_object_install(env->anjay_restored));\n    env->stream = avs_stream_membuf_create();\n    AVS_UNIT_ASSERT_NOT_NULL(env->stream);\n    ANJAY_MUTEX_LOCK(anjay_unlocked, env->anjay_stored);\n    env->stored = *_anjay_dm_find_object_by_oid(_anjay_get_dm(anjay_unlocked),\n                                                ANJAY_DM_OID_SERVER);\n    ANJAY_MUTEX_UNLOCK(env->anjay_stored);\n    ANJAY_MUTEX_LOCK(anjay_unlocked, env->anjay_restored);\n    env->restored = *_anjay_dm_find_object_by_oid(_anjay_get_dm(anjay_unlocked),\n                                                  ANJAY_DM_OID_SERVER);\n    ANJAY_MUTEX_UNLOCK(env->anjay_restored);\n    env->stored_repr = _anjay_serv_get(env->stored);\n    env->restored_repr = _anjay_serv_get(env->restored);\n    return env;\n}\n\nstatic void\nserver_persistence_test_env_destroy(server_persistence_test_env_t **env) {\n    anjay_delete((*env)->anjay_stored);\n    anjay_delete((*env)->anjay_restored);\n    avs_stream_cleanup(&(*env)->stream);\n    avs_free(*env);\n}\n\nstatic void assert_instances_equal(const server_instance_t *a,\n                                   const server_instance_t *b) {\n    AVS_UNIT_ASSERT_EQUAL(a->iid, b->iid);\n    AVS_UNIT_ASSERT_EQUAL_STRING(a->binding.data, b->binding.data);\n    AVS_UNIT_ASSERT_EQUAL(a->ssid, b->ssid);\n    AVS_UNIT_ASSERT_EQUAL(a->lifetime, b->lifetime);\n    AVS_UNIT_ASSERT_EQUAL(a->default_min_period, b->default_min_period);\n    AVS_UNIT_ASSERT_EQUAL(a->default_max_period, b->default_max_period);\n#ifndef ANJAY_WITHOUT_DEREGISTER\n    AVS_UNIT_ASSERT_EQUAL(a->disable_timeout, b->disable_timeout);\n#endif // ANJAY_WITHOUT_DEREGISTER\n    AVS_UNIT_ASSERT_EQUAL(a->notification_storing, b->notification_storing);\n#ifdef ANJAY_WITH_LWM2M11\n    AVS_UNIT_ASSERT_EQUAL(a->last_alert, b->last_alert);\n    AVS_UNIT_ASSERT_EQUAL(a->last_bootstrapped_timestamp,\n                          b->last_bootstrapped_timestamp);\n    AVS_UNIT_ASSERT_EQUAL(a->bootstrap_on_registration_failure,\n                          b->bootstrap_on_registration_failure);\n    AVS_UNIT_ASSERT_EQUAL(a->server_communication_retry_count,\n                          b->server_communication_retry_count);\n    AVS_UNIT_ASSERT_EQUAL(a->server_communication_retry_timer,\n                          b->server_communication_retry_timer);\n    AVS_UNIT_ASSERT_EQUAL(a->server_communication_sequence_retry_count,\n                          b->server_communication_sequence_retry_count);\n    AVS_UNIT_ASSERT_EQUAL(a->server_communication_sequence_delay_timer,\n                          b->server_communication_sequence_delay_timer);\n    AVS_UNIT_ASSERT_EQUAL(a->preferred_transport, b->preferred_transport);\n#    ifdef ANJAY_WITH_SEND\n    AVS_UNIT_ASSERT_EQUAL(a->mute_send, b->mute_send);\n#    endif // ANJAY_WITH_SEND\n\n    for (size_t i = 0; i < AVS_ARRAY_SIZE(a->present_resources); ++i) {\n        AVS_UNIT_ASSERT_EQUAL(a->present_resources[i], b->present_resources[i]);\n    }\n#endif // ANJAY_WITH_LWM2M11\n}\n\nAVS_UNIT_TEST(server_persistence, empty_store_restore) {\n    SCOPED_SERVER_PERSISTENCE_TEST_ENV(env);\n    AVS_UNIT_ASSERT_EQUAL(0, AVS_LIST_SIZE(env->stored_repr->instances));\n    AVS_UNIT_ASSERT_SUCCESS(\n            anjay_server_object_persist(env->anjay_stored, env->stream));\n    AVS_UNIT_ASSERT_SUCCESS(\n            anjay_server_object_restore(env->anjay_restored, env->stream));\n    AVS_UNIT_ASSERT_EQUAL(0, AVS_LIST_SIZE(env->restored_repr->instances));\n}\n\nAVS_UNIT_TEST(server_persistence, nonempty_store_restore_version_1) {\n    SCOPED_SERVER_PERSISTENCE_TEST_ENV(env);\n    /*\n     * This represents following server instance persisted with version=1:\n     * const anjay_server_instance_t instance = {\n     *     .ssid = 42,\n     *     .lifetime = 9001,\n     *     .default_min_period = -1,\n     *     .default_max_period = -1,\n     *     .disable_timeout = -1,\n     *     .binding = \"UQ\",\n     *     .notification_storing = true\n     * };\n     */\n    const char persisted_binary[] =\n            \"\\x53\\x52\\x56\\x01\\x00\\x00\\x00\\x01\\x00\\x01\\x01\\x01\\x01\\x01\\x00\\x2a\"\n            \"\\x00\\x00\\x23\\x29\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\"\n            \"\\x01\\x55\\x51\\x00\\x00\\x00\\x00\\x00\\x00\";\n    const size_t persisted_binary_size = sizeof(persisted_binary) - 1;\n    AVS_UNIT_ASSERT_SUCCESS(avs_stream_write(\n            env->stream, persisted_binary, persisted_binary_size));\n\n    AVS_UNIT_ASSERT_SUCCESS(\n            anjay_server_object_restore(env->anjay_restored, env->stream));\n    AVS_UNIT_ASSERT_EQUAL(1, AVS_LIST_SIZE(env->restored_repr->instances));\n    const server_instance_t expected_server_instance = {\n        .iid = 1,\n        .ssid = 42,\n        .lifetime = 9001,\n        .default_min_period = -1,\n        .default_max_period = -1,\n#ifndef ANJAY_WITHOUT_DEREGISTER\n        .disable_timeout = -1,\n#endif // ANJAY_WITHOUT_DEREGISTER\n        .binding = {\n            .data = \"UQ\",\n        },\n        .notification_storing = true,\n#ifdef ANJAY_WITH_LWM2M11\n        .bootstrap_on_registration_failure = true,\n#endif // ANJAY_WITH_LWM2M11\n        .present_resources = {\n            [SERV_RES_SSID] = true,\n            [SERV_RES_LIFETIME] = true,\n#ifndef ANJAY_WITHOUT_DEREGISTER\n            [SERV_RES_DISABLE] = true,\n#endif // ANJAY_WITHOUT_DEREGISTER\n            [SERV_RES_NOTIFICATION_STORING_WHEN_DISABLED_OR_OFFLINE] = true,\n            [SERV_RES_BINDING] = true,\n            [SERV_RES_REGISTRATION_UPDATE_TRIGGER] = true,\n#ifdef ANJAY_WITH_LWM2M11\n            [SERV_RES_BOOTSTRAP_REQUEST_TRIGGER] = true,\n            [SERV_RES_BOOTSTRAP_ON_REGISTRATION_FAILURE] = true,\n#    ifdef ANJAY_WITH_SEND\n            [SERV_RES_MUTE_SEND] = true\n#    endif // ANJAY_WITH_SEND\n#endif     // ANJAY_WITH_LWM2M11\n        }\n    };\n    assert_instances_equal(&expected_server_instance,\n                           env->restored_repr->instances);\n}\n\nAVS_UNIT_TEST(server_persistence, nonempty_store_restore) {\n    SCOPED_SERVER_PERSISTENCE_TEST_ENV(env);\n    const anjay_server_instance_t instance = {\n        .ssid = 42,\n        .lifetime = 9001,\n        .default_min_period = -1,\n        .default_max_period = -1,\n        .disable_timeout = -1,\n        .binding = \"UQ\",\n        .notification_storing = true,\n#ifdef ANJAY_WITH_LWM2M11\n        .bootstrap_on_registration_failure = &(bool) { false },\n        .preferred_transport = 'U',\n        .mute_send = true,\n        .communication_sequence_retry_count = &(uint32_t) { 2 },\n        .communication_sequence_delay_timer = &(uint32_t) { 10 },\n#endif // ANJAY_WITH_LWM2M11\n    };\n\n    anjay_iid_t iid = 1;\n    AVS_UNIT_ASSERT_SUCCESS(anjay_server_object_add_instance(\n            env->anjay_stored, &instance, &iid));\n    AVS_UNIT_ASSERT_SUCCESS(\n            anjay_server_object_persist(env->anjay_stored, env->stream));\n    AVS_UNIT_ASSERT_SUCCESS(\n            anjay_server_object_restore(env->anjay_restored, env->stream));\n    AVS_UNIT_ASSERT_EQUAL(1, AVS_LIST_SIZE(env->restored_repr->instances));\n    const server_instance_t expected_server_instance = {\n        .iid = 1,\n        .binding = {\n            .data = \"UQ\",\n        },\n        .ssid = 42,\n        .lifetime = 9001,\n        .notification_storing = true,\n#ifdef ANJAY_WITH_LWM2M11\n        .bootstrap_on_registration_failure = false,\n        .preferred_transport = 'U',\n#    ifdef ANJAY_WITH_SEND\n        .mute_send = true,\n#    endif // ANJAY_WITH_SEND\n        .server_communication_sequence_retry_count = 2,\n        .server_communication_sequence_delay_timer = 10,\n#endif // ANJAY_WITH_LWM2M11\n        .present_resources = {\n            [SERV_RES_SSID] = true,\n            [SERV_RES_LIFETIME] = true,\n#ifndef ANJAY_WITHOUT_DEREGISTER\n            [SERV_RES_DISABLE] = true,\n#endif // ANJAY_WITHOUT_DEREGISTER\n            [SERV_RES_NOTIFICATION_STORING_WHEN_DISABLED_OR_OFFLINE] = true,\n            [SERV_RES_BINDING] = true,\n            [SERV_RES_REGISTRATION_UPDATE_TRIGGER] = true,\n#ifdef ANJAY_WITH_LWM2M11\n            [SERV_RES_BOOTSTRAP_REQUEST_TRIGGER] = true,\n            [SERV_RES_BOOTSTRAP_ON_REGISTRATION_FAILURE] = true,\n            [SERV_RES_SERVER_COMMUNICATION_SEQUENCE_RETRY_COUNT] = true,\n            [SERV_RES_SERVER_COMMUNICATION_SEQUENCE_DELAY_TIMER] = true,\n            [SERV_RES_PREFERRED_TRANSPORT] = true,\n#    ifdef ANJAY_WITH_SEND\n            [SERV_RES_MUTE_SEND] = true\n#    endif // ANJAY_WITH_SEND\n#endif     // ANJAY_WITH_LWM2M11\n        }\n    };\n    assert_instances_equal(&expected_server_instance,\n                           env->restored_repr->instances);\n}\n\nAVS_UNIT_TEST(server_persistence, modification_flag_add_instance) {\n    SCOPED_SERVER_PERSISTENCE_TEST_ENV(env);\n    /* At the beginning server object is not modified */\n    AVS_UNIT_ASSERT_FALSE(anjay_server_object_is_modified(env->anjay_stored));\n    /* Invalid instance does not change the modification flag */\n    anjay_iid_t iid = ANJAY_ID_INVALID;\n    const anjay_server_instance_t invalid_instance = {\n        .ssid = 0\n    };\n    AVS_UNIT_ASSERT_FAILED(anjay_server_object_add_instance(\n            env->anjay_stored, &invalid_instance, &iid));\n    AVS_UNIT_ASSERT_FALSE(anjay_server_object_is_modified(env->anjay_stored));\n    /* Same thing applies if the flag already was set to true */\n    _anjay_serv_mark_modified(_anjay_serv_get(env->stored));\n    AVS_UNIT_ASSERT_FAILED(anjay_server_object_add_instance(\n            env->anjay_stored, &invalid_instance, &iid));\n    AVS_UNIT_ASSERT_TRUE(anjay_server_object_is_modified(env->anjay_stored));\n    _anjay_serv_clear_modified(_anjay_serv_get(env->stored));\n\n    const anjay_server_instance_t instance = {\n        .ssid = 42,\n        .lifetime = 9001,\n        .default_min_period = -1,\n        .default_max_period = -1,\n        .disable_timeout = -1,\n        .binding = \"U\",\n        .notification_storing = true\n    };\n    /* And valid instance does change the flag */\n    AVS_UNIT_ASSERT_SUCCESS(anjay_server_object_add_instance(\n            env->anjay_stored, &instance, &iid));\n    AVS_UNIT_ASSERT_TRUE(anjay_server_object_is_modified(env->anjay_stored));\n}\n\nAVS_UNIT_TEST(server_persistence, modification_flag_purge) {\n    SCOPED_SERVER_PERSISTENCE_TEST_ENV(env);\n    /* Purged object remains unmodified after purge */\n    anjay_server_object_purge(env->anjay_stored);\n    AVS_UNIT_ASSERT_FALSE(anjay_server_object_is_modified(env->anjay_stored));\n\n    anjay_iid_t iid = ANJAY_ID_INVALID;\n    const anjay_server_instance_t instance = {\n        .ssid = 42,\n        .lifetime = 9001,\n        .default_min_period = -1,\n        .default_max_period = -1,\n        .disable_timeout = -1,\n        .binding = \"U\",\n        .notification_storing = true\n    };\n    AVS_UNIT_ASSERT_SUCCESS(anjay_server_object_add_instance(\n            env->anjay_stored, &instance, &iid));\n    AVS_UNIT_ASSERT_TRUE(anjay_server_object_is_modified(env->anjay_stored));\n    /* Simulate persistence operation. */\n    _anjay_serv_clear_modified(_anjay_serv_get(env->stored));\n    AVS_UNIT_ASSERT_FALSE(anjay_server_object_is_modified(env->anjay_stored));\n    anjay_server_object_purge(env->anjay_stored);\n    AVS_UNIT_ASSERT_TRUE(anjay_server_object_is_modified(env->anjay_stored));\n}\n"
  },
  {
    "path": "tests/utils/coap/socket.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#include \"tests/utils/coap/socket.h\"\n\nvoid _anjay_mocksock_create(avs_net_socket_t **mocksock,\n                            int inner_mtu,\n                            int mtu) {\n    avs_unit_mocksock_create_datagram(mocksock);\n    avs_unit_mocksock_enable_recv_timeout_getsetopt(\n            *mocksock, avs_time_duration_from_scalar(30, AVS_TIME_S));\n    if (inner_mtu >= 0) {\n        avs_unit_mocksock_enable_inner_mtu_getopt(*mocksock, inner_mtu);\n    }\n    if (mtu >= 0) {\n        avs_unit_mocksock_enable_mtu_getopt(*mocksock, mtu);\n    }\n}\n"
  },
  {
    "path": "tests/utils/coap/socket.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ANJAY_TEST_COAP_SOCKET_H\n#define ANJAY_TEST_COAP_SOCKET_H\n\n#include <avsystem/commons/avs_unit_mocksock.h>\n\n/**\n * NOTE: inner_mtu / mtu may be set to a negative value, in which case\n * they are not automatically handled by mocksock_get_opt()\n */\nvoid _anjay_mocksock_create(avs_net_socket_t **mocksock,\n                            int inner_mtu,\n                            int mtu);\n\n#endif /* ANJAY_TEST_COAP_SOCKET_H */\n"
  },
  {
    "path": "tests/utils/dm.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#include <avsystem/commons/avs_unit_test.h>\n\n#include <avsystem/coap/ctx.h>\n#include <avsystem/coap/udp.h>\n\n#include \"src/core/anjay_core.h\"\n#include \"src/core/anjay_stats.h\"\n#include \"tests/utils/coap/socket.h\"\n#include \"tests/utils/dm.h\"\n#include \"tests/utils/utils.h\"\n\n// HACK to enable _anjay_server_cleanup\n#define ANJAY_SERVERS_INTERNALS\n#include \"src/core/servers/anjay_server_connections.h\"\n#include \"src/core/servers/anjay_servers_internal.h\"\n#undef ANJAY_SERVERS_INTERNALS\n\nanjay_t *_anjay_test_dm_init(const anjay_configuration_t *config) {\n    _anjay_mock_clock_start(avs_time_monotonic_from_scalar(1000, AVS_TIME_S));\n    _anjay_mock_dm_expected_commands_clear();\n    anjay_t *anjay = anjay_new(config);\n    AVS_UNIT_ASSERT_NOT_NULL(anjay);\n    _anjay_test_dm_unsched_reload_sockets(anjay);\n    return anjay;\n}\n\nvoid _anjay_test_dm_unsched_notify_clb(anjay_t *anjay_locked) {\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    _anjay_notify_clear_queue(&anjay->scheduled_notify.queue);\n    avs_sched_del(&anjay->scheduled_notify.handle);\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n}\n\nvoid _anjay_test_dm_unsched_reload_sockets(anjay_t *anjay_locked) {\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    avs_sched_del(&anjay->reload_servers_sched_job_handle);\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n}\n\navs_net_socket_t *_anjay_test_dm_create_socket(bool connected) {\n    avs_net_socket_t *socket = NULL;\n    _anjay_mocksock_create(&socket, 1252, 1252);\n    if (connected) {\n        avs_unit_mocksock_expect_connect(socket, \"\", \"\");\n        AVS_UNIT_ASSERT_SUCCESS(avs_net_socket_connect(socket, \"\", \"\"));\n    }\n    avs_unit_mocksock_enable_recv_timeout_getsetopt(\n            socket, avs_time_duration_from_scalar(1, AVS_TIME_S));\n    avs_unit_mocksock_enable_inner_mtu_getopt(socket, 1252);\n    avs_unit_mocksock_enable_state_getopt(socket);\n    return socket;\n}\n\navs_net_socket_t *_anjay_test_dm_install_socket(anjay_t *anjay_locked,\n                                                anjay_ssid_t ssid) {\n    avs_net_socket_t *socket = _anjay_test_dm_create_socket(true);\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    AVS_UNIT_ASSERT_NOT_NULL(\n            AVS_LIST_INSERT_NEW(anjay_server_info_t, &anjay->servers));\n    anjay->servers->anjay = anjay;\n    anjay->servers->ssid = ssid;\n    anjay->servers->registration_info.expire_time.since_real_epoch.seconds =\n            INT64_MAX;\n    anjay_server_connection_t *connection =\n            _anjay_get_server_connection((const anjay_connection_ref_t) {\n                .server = anjay->servers,\n                .conn_type = ANJAY_CONNECTION_PRIMARY\n            });\n    AVS_UNIT_ASSERT_NOT_NULL(connection);\n    connection->conn_socket_ = socket;\n    connection->coap_ctx = avs_coap_udp_ctx_create(\n            _anjay_get_coap_sched(anjay), &AVS_COAP_DEFAULT_UDP_TX_PARAMS,\n            anjay->in_shared_buffer, anjay->out_shared_buffer,\n            anjay->udp_response_cache, anjay->prng_ctx.ctx);\n    AVS_UNIT_ASSERT_SUCCESS(\n            avs_coap_ctx_set_socket(connection->coap_ctx, socket));\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return socket;\n}\n\nvoid _anjay_test_dm_finish(anjay_t *anjay_locked) {\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    anjay_server_info_t *server;\n    AVS_LIST_FOREACH(server, anjay->servers) {\n        anjay_server_connection_t *connection =\n                _anjay_get_server_connection((const anjay_connection_ref_t) {\n                    .server = server,\n                    .conn_type = ANJAY_CONNECTION_PRIMARY\n                });\n        if (connection->conn_socket_) {\n            avs_unit_mocksock_assert_expects_met(connection->conn_socket_);\n            avs_unit_mocksock_assert_io_clean(connection->conn_socket_);\n            _anjay_mocksock_expect_stats_zero(connection->conn_socket_);\n        }\n    }\n    AVS_LIST_CLEAR(&anjay->servers) {\n        _anjay_server_cleanup(anjay->servers);\n    }\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    anjay_delete(anjay_locked);\n    _anjay_mock_dm_expect_clean();\n    _anjay_mock_clock_finish();\n}\n\nint _anjay_test_dm_fake_security_list_instances(\n        anjay_t *anjay_locked,\n        const anjay_dm_object_def_t *const *obj_ptr,\n        anjay_dm_list_ctx_t *ctx) {\n    (void) obj_ptr;\n    ANJAY_MUTEX_LOCK(anjay, anjay_locked);\n    AVS_LIST(anjay_server_info_t) it;\n    AVS_LIST_FOREACH(it, anjay->servers) {\n        anjay_ssid_t ssid = it->ssid;\n        ANJAY_MUTEX_UNLOCK_FOR_CALLBACK(anjay_locked_again, anjay);\n        if (ssid == ANJAY_ID_INVALID) {\n            anjay_dm_emit(ctx, 0);\n        } else {\n            anjay_dm_emit(ctx, ssid);\n        }\n        ANJAY_MUTEX_LOCK_AFTER_CALLBACK(anjay_locked_again);\n    }\n    ANJAY_MUTEX_UNLOCK(anjay_locked);\n    return 0;\n}\n\nint _anjay_test_dm_fake_security_list_resources(\n        anjay_t *anjay,\n        const anjay_dm_object_def_t *const *obj_ptr,\n        anjay_iid_t iid,\n        anjay_dm_resource_list_ctx_t *ctx) {\n    (void) anjay;\n    (void) obj_ptr;\n    (void) iid;\n    anjay_dm_emit_res(ctx,\n                      ANJAY_DM_RID_SECURITY_BOOTSTRAP,\n                      ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx,\n                      ANJAY_DM_RID_SECURITY_SSID,\n                      ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_PRESENT);\n    anjay_dm_emit_res(ctx,\n                      ANJAY_DM_RID_SECURITY_BOOTSTRAP_TIMEOUT,\n                      ANJAY_DM_RES_R,\n                      ANJAY_DM_RES_PRESENT);\n    return 0;\n}\n\nint _anjay_test_dm_fake_security_read(\n        anjay_t *anjay,\n        const anjay_dm_object_def_t *const *obj_ptr,\n        anjay_iid_t iid,\n        anjay_rid_t rid,\n        anjay_riid_t riid,\n        anjay_output_ctx_t *ctx) {\n    (void) anjay;\n    (void) obj_ptr;\n    assert(riid == ANJAY_ID_INVALID);\n    switch (rid) {\n    case ANJAY_DM_RID_SECURITY_BOOTSTRAP:\n        return anjay_ret_bool(ctx, (iid == 0));\n    case ANJAY_DM_RID_SECURITY_SSID:\n        return anjay_ret_i32(ctx, iid ? iid : ANJAY_ID_INVALID);\n    case ANJAY_DM_RID_SECURITY_BOOTSTRAP_TIMEOUT:\n        return anjay_ret_i32(ctx, 1);\n    default:\n        return -1;\n    }\n}\n"
  },
  {
    "path": "tests/utils/dm.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ANJAY_TEST_DM_H\n#define ANJAY_TEST_DM_H\n\n#include <avsystem/commons/avs_unit_mocksock.h>\n\n#include <anjay_modules/anjay_dm_utils.h>\n#include <anjay_modules/anjay_raw_buffer.h>\n\n#include \"tests/core/coap/utils.h\"\n#include \"tests/utils/mock_clock.h\"\n#include \"tests/utils/mock_dm.h\"\n\nanjay_t *_anjay_test_dm_init(const anjay_configuration_t *config);\n\nvoid _anjay_test_dm_unsched_notify_clb(anjay_t *anjay);\n\nvoid _anjay_test_dm_unsched_reload_sockets(anjay_t *anjay);\n\navs_net_socket_t *_anjay_test_dm_create_socket(bool connected);\n\navs_net_socket_t *_anjay_test_dm_install_socket(anjay_t *anjay,\n                                                anjay_ssid_t ssid);\nvoid _anjay_test_dm_finish(anjay_t *anjay);\n\nint _anjay_test_dm_fake_security_list_instances(\n        anjay_t *anjay,\n        const anjay_dm_object_def_t *const *obj_ptr,\n        anjay_dm_list_ctx_t *ctx);\n\nint _anjay_test_dm_fake_security_list_resources(\n        anjay_t *anjay,\n        const anjay_dm_object_def_t *const *obj_ptr,\n        anjay_iid_t iid,\n        anjay_dm_resource_list_ctx_t *ctx);\n\nint _anjay_test_dm_fake_security_read(\n        anjay_t *anjay,\n        const anjay_dm_object_def_t *const *obj_ptr,\n        anjay_iid_t iid,\n        anjay_rid_t rid,\n        anjay_riid_t riid,\n        anjay_output_ctx_t *ctx);\n\nstatic inline int\n_anjay_test_dm_instance_reset_NOOP(anjay_t *anjay,\n                                   const anjay_dm_object_def_t *const *obj_ptr,\n                                   anjay_iid_t iid) {\n    (void) anjay;\n    (void) obj_ptr;\n    (void) iid;\n    return 0;\n}\n\nstatic const anjay_dm_object_def_t *const OBJ = &(const anjay_dm_object_def_t) {\n    .oid = 42,\n    .handlers = { ANJAY_MOCK_DM_HANDLERS,\n                  .instance_reset = _anjay_test_dm_instance_reset_NOOP }\n};\n\nstatic const anjay_dm_object_def_t *const OBJ_NOATTRS =\n        &(const anjay_dm_object_def_t) {\n            .oid = 93,\n            .handlers = { ANJAY_MOCK_DM_HANDLERS_BASIC,\n                          .instance_reset = _anjay_test_dm_instance_reset_NOOP }\n        };\n\nstatic const anjay_dm_object_def_t *const OBJ_WITH_RESET =\n        &(const anjay_dm_object_def_t) {\n            .oid = 25,\n            .handlers = { ANJAY_MOCK_DM_HANDLERS,\n                          .instance_reset = _anjay_mock_dm_instance_reset }\n        };\n\nstatic const anjay_dm_object_def_t *const OBJ_WITH_TRANSACTION = &(\n        const anjay_dm_object_def_t) {\n    .oid = 69,\n    .handlers = { ANJAY_MOCK_DM_HANDLERS_BASIC, ANJAY_MOCK_DM_HANDLERS_REST,\n                  ANJAY_MOCK_DM_HANDLERS_TRANSACTION,\n                  .instance_reset = _anjay_test_dm_instance_reset_NOOP }\n};\n\nstatic anjay_dm_object_def_t *const EXECUTE_OBJ = &(anjay_dm_object_def_t) {\n    .oid = 128,\n    .handlers = { ANJAY_MOCK_DM_HANDLERS }\n};\n\nstatic const anjay_dm_object_def_t *const FAKE_SECURITY =\n        &(const anjay_dm_object_def_t) {\n            .oid = 0,\n            .handlers = {\n                .list_instances = _anjay_test_dm_fake_security_list_instances,\n                .list_resources = _anjay_test_dm_fake_security_list_resources,\n                .resource_read = _anjay_test_dm_fake_security_read,\n                ANJAY_MOCK_DM_HANDLERS_TRANSACTION_NOOP\n            }\n        };\n\nstatic const anjay_dm_object_def_t *const FAKE_SECURITY2 =\n        &(const anjay_dm_object_def_t) {\n            .oid = 0,\n            .handlers = { ANJAY_MOCK_DM_HANDLERS }\n        };\n\nstatic const anjay_dm_object_def_t *const FAKE_SERVER =\n        &(const anjay_dm_object_def_t) {\n            .oid = 1,\n            .handlers = { ANJAY_MOCK_DM_HANDLERS }\n        };\n\n#define DM_TEST_CONFIGURATION(...)                \\\n    &(anjay_configuration_t) {                    \\\n        .endpoint_name = \"urn:dev:os:anjay-test\", \\\n        .in_buffer_size = 4096,                   \\\n        .out_buffer_size = 4096, __VA_ARGS__      \\\n    }\n\n#define DM_TEST_INIT_OBJECTS__(ObjDefs, ...)                        \\\n    reset_token_generator();                                        \\\n    anjay_t *anjay = _anjay_test_dm_init((__VA_ARGS__));            \\\n    do {                                                            \\\n        for (size_t _i = 0; _i < AVS_ARRAY_SIZE((ObjDefs)); ++_i) { \\\n            AVS_UNIT_ASSERT_SUCCESS(                                \\\n                    anjay_register_object(anjay, (ObjDefs)[_i]));   \\\n        }                                                           \\\n    } while (false)\n\n#define DM_TEST_POST_INIT__                                                   \\\n    _anjay_test_dm_unsched_notify_clb(anjay);                                 \\\n    AVS_UNIT_ASSERT_EQUAL(anjay_sched_calculate_wait_time_ms(anjay, INT_MAX), \\\n                          INT_MAX)\n\n#define DM_TEST_INIT_GENERIC(ObjDefs, Ssids, ...)                          \\\n    DM_TEST_INIT_OBJECTS__(ObjDefs, __VA_ARGS__);                          \\\n    avs_net_socket_t *mocksocks[AVS_ARRAY_SIZE((Ssids))];                  \\\n    for (size_t _i = AVS_ARRAY_SIZE((Ssids)) - 1;                          \\\n         _i < AVS_ARRAY_SIZE((Ssids));                                     \\\n         --_i) {                                                           \\\n        mocksocks[_i] = _anjay_test_dm_install_socket(anjay, (Ssids)[_i]); \\\n    }                                                                      \\\n    (void) mocksocks;                                                      \\\n    DM_TEST_POST_INIT__\n\n#define DM_TEST_DEFAULT_OBJECTS                                  \\\n    &OBJ, &FAKE_SECURITY, &FAKE_SERVER,                          \\\n            (const anjay_dm_object_def_t *const *) &EXECUTE_OBJ, \\\n            (const anjay_dm_object_def_t *const *) &OBJ_WITH_RESET\n\n#define DM_TEST_INIT_WITH_OBJECTS(...)                                \\\n    const anjay_dm_object_def_t *const *obj_defs[] = { __VA_ARGS__ }; \\\n    anjay_ssid_t ssids[] = { 1 };                                     \\\n    DM_TEST_INIT_GENERIC(obj_defs, ssids, DM_TEST_CONFIGURATION())\n\n#define DM_TEST_INIT_WITH_SSIDS(...)                   \\\n    const anjay_dm_object_def_t *const *obj_defs[] = { \\\n        DM_TEST_DEFAULT_OBJECTS                        \\\n    };                                                 \\\n    anjay_ssid_t ssids[] = { __VA_ARGS__ };            \\\n    DM_TEST_INIT_GENERIC(obj_defs, ssids, DM_TEST_CONFIGURATION())\n\n#define DM_TEST_INIT_WITHOUT_SERVER                            \\\n    const anjay_dm_object_def_t *const *obj_defs[] = {         \\\n        DM_TEST_DEFAULT_OBJECTS                                \\\n    };                                                         \\\n    DM_TEST_INIT_OBJECTS__(obj_defs, DM_TEST_CONFIGURATION()); \\\n    DM_TEST_POST_INIT__\n\n#define DM_TEST_INIT DM_TEST_INIT_WITH_SSIDS(1)\n\n#define DM_TEST_INIT_WITH_CONFIG(...)                  \\\n    const anjay_dm_object_def_t *const *obj_defs[] = { \\\n        DM_TEST_DEFAULT_OBJECTS                        \\\n    };                                                 \\\n    anjay_ssid_t ssids[] = { 1 };                      \\\n    DM_TEST_INIT_GENERIC(obj_defs, ssids, DM_TEST_CONFIGURATION(__VA_ARGS__))\n\n#define DM_TEST_FINISH _anjay_test_dm_finish(anjay)\n\n#define DM_TEST_EXPECT_RESPONSE(                                \\\n        Mocksock, Type, Code, Id, ... /* Payload, Opts... */)   \\\n    do {                                                        \\\n        const coap_test_msg_t *response =                       \\\n                COAP_MSG(Type, Code, Id, __VA_ARGS__);          \\\n        avs_unit_mocksock_expect_output(                        \\\n                Mocksock, response->content, response->length); \\\n    } while (0)\n\n#define DM_TEST_REQUEST_FROM_CLIENT DM_TEST_EXPECT_RESPONSE\n\n#define DM_TEST_REQUEST(Mocksock, Type, Code, Id, ... /* Payload, Opts... */) \\\n    do {                                                                      \\\n        const coap_test_msg_t *request =                                      \\\n                COAP_MSG(Type, Code, Id, __VA_ARGS__);                        \\\n        avs_unit_mocksock_input(Mocksock, request->content, request->length); \\\n    } while (0)\n\n#define DM_TEST_EXPECT_READ_NULL_ATTRS(Ssid, Iid, Rid)                     \\\n    do {                                                                   \\\n        _anjay_mock_dm_expect_list_instances(                              \\\n                anjay,                                                     \\\n                &OBJ,                                                      \\\n                0,                                                         \\\n                (const anjay_iid_t[]) { Iid, ANJAY_ID_INVALID });          \\\n        if (Rid >= 0) {                                                    \\\n            _anjay_mock_dm_expect_list_resources(                          \\\n                    anjay,                                                 \\\n                    &OBJ,                                                  \\\n                    Iid,                                                   \\\n                    0,                                                     \\\n                    (const anjay_mock_dm_res_entry_t[]) {                  \\\n                            { 0, ANJAY_DM_RES_RW, Rid == 0 },              \\\n                            { 1, ANJAY_DM_RES_RW, Rid == 1 },              \\\n                            { 2, ANJAY_DM_RES_RW, Rid == 2 },              \\\n                            { 3, ANJAY_DM_RES_RW, Rid == 3 },              \\\n                            { 4, ANJAY_DM_RES_RW, Rid == 4 },              \\\n                            { 5, ANJAY_DM_RES_RW, Rid == 5 },              \\\n                            { 6, ANJAY_DM_RES_RW, Rid == 6 },              \\\n                            ANJAY_MOCK_DM_RES_END });                      \\\n            _anjay_mock_dm_expect_resource_read_attrs(                     \\\n                    anjay,                                                 \\\n                    &OBJ,                                                  \\\n                    Iid,                                                   \\\n                    (anjay_rid_t) Rid,                                     \\\n                    Ssid,                                                  \\\n                    0,                                                     \\\n                    &ANJAY_DM_R_ATTRIBUTES_EMPTY);                         \\\n        }                                                                  \\\n        _anjay_mock_dm_expect_instance_read_default_attrs(                 \\\n                anjay, &OBJ, Iid, Ssid, 0, &ANJAY_DM_OI_ATTRIBUTES_EMPTY); \\\n        _anjay_mock_dm_expect_object_read_default_attrs(                   \\\n                anjay, &OBJ, Ssid, 0, &ANJAY_DM_OI_ATTRIBUTES_EMPTY);      \\\n        _anjay_mock_dm_expect_list_instances(anjay,                        \\\n                                             &FAKE_SERVER,                 \\\n                                             0,                            \\\n                                             (const anjay_iid_t[]) {       \\\n                                                     ANJAY_ID_INVALID });  \\\n    } while (0)\n\n#endif /* ANJAY_TEST_DM_H */\n"
  },
  {
    "path": "tests/utils/mock_clock.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#define _GNU_SOURCE // for RTLD_NEXT\n#include <anjay_init.h>\n\n#include <dlfcn.h>\n#include <time.h>\n\n#include <avsystem/commons/avs_list.h>\n#include <avsystem/commons/avs_unit_test.h>\n\n#include <anjay_modules/anjay_time_defs.h>\n\n#include \"tests/utils/mock_clock.h\"\n\nstatic avs_time_monotonic_t MOCK_CLOCK = { { 0, -1 } };\n\nvoid _anjay_mock_clock_start(const avs_time_monotonic_t t) {\n    const avs_time_monotonic_t last_value = MOCK_CLOCK;\n    MOCK_CLOCK = AVS_TIME_MONOTONIC_INVALID;\n    AVS_UNIT_ASSERT_FALSE(avs_time_monotonic_valid(last_value));\n    AVS_UNIT_ASSERT_TRUE(avs_time_monotonic_valid(t));\n    MOCK_CLOCK = t;\n}\n\nvoid _anjay_mock_clock_reset(const avs_time_monotonic_t t) {\n    _anjay_mock_clock_finish();\n    _anjay_mock_clock_start(t);\n}\n\nvoid _anjay_mock_clock_advance(const avs_time_duration_t t) {\n    AVS_UNIT_ASSERT_TRUE(avs_time_monotonic_valid(MOCK_CLOCK));\n    AVS_UNIT_ASSERT_TRUE(avs_time_duration_valid(t));\n    MOCK_CLOCK = avs_time_monotonic_add(MOCK_CLOCK, t);\n}\n\nvoid _anjay_mock_clock_finish(void) {\n    AVS_UNIT_ASSERT_TRUE(avs_time_monotonic_valid(MOCK_CLOCK));\n    MOCK_CLOCK = AVS_TIME_MONOTONIC_INVALID;\n}\n\nstatic int (*orig_clock_gettime)(clockid_t, struct timespec *);\n\nAVS_UNIT_GLOBAL_INIT(verbose) {\n    (void) verbose;\n    typedef int (*clock_gettime_t)(clockid_t, struct timespec *);\n    orig_clock_gettime =\n            (clock_gettime_t) (intptr_t) dlsym(RTLD_NEXT, \"clock_gettime\");\n}\n\nint clock_gettime(clockid_t clock, struct timespec *t);\nint clock_gettime(clockid_t clock, struct timespec *t) {\n    if (avs_time_monotonic_valid(MOCK_CLOCK)) {\n        // all clocks are equivalent for our purposes, so ignore clock\n        t->tv_sec = (time_t) MOCK_CLOCK.since_monotonic_epoch.seconds;\n        t->tv_nsec = MOCK_CLOCK.since_monotonic_epoch.nanoseconds;\n        return 0;\n    } else {\n        return orig_clock_gettime(clock, t);\n    }\n}\n"
  },
  {
    "path": "tests/utils/mock_clock.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ANJAY_TEST_MOCK_CLOCK_H\n#define ANJAY_TEST_MOCK_CLOCK_H\n\n#include <avsystem/commons/avs_time.h>\n\nvoid _anjay_mock_clock_start(const avs_time_monotonic_t t);\nvoid _anjay_mock_clock_reset(const avs_time_monotonic_t t);\nvoid _anjay_mock_clock_advance(const avs_time_duration_t t);\nvoid _anjay_mock_clock_finish(void);\n\n#endif /* ANJAY_TEST_MOCK_CLOCK_H */\n"
  },
  {
    "path": "tests/utils/mock_dm.c",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <anjay_init.h>\n\n#include <stdbool.h>\n#include <string.h>\n\n#include <avsystem/commons/avs_list.h>\n#include <avsystem/commons/avs_unit_test.h>\n\n#include \"src/core/dm/anjay_dm_execute.h\"\n#include \"tests/utils/mock_dm.h\"\n\ntypedef enum {\n    MOCK_DM_OBJECT_READ_DEFAULT_ATTRS,\n    MOCK_DM_OBJECT_WRITE_DEFAULT_ATTRS,\n    MOCK_DM_INSTANCE_RESET,\n    MOCK_DM_LIST_INSTANCES,\n    MOCK_DM_INSTANCE_CREATE,\n    MOCK_DM_INSTANCE_REMOVE,\n    MOCK_DM_INSTANCE_READ_DEFAULT_ATTRS,\n    MOCK_DM_INSTANCE_WRITE_DEFAULT_ATTRS,\n    MOCK_DM_LIST_RESOURCES,\n    MOCK_DM_RESOURCE_READ,\n    MOCK_DM_RESOURCE_WRITE,\n    MOCK_DM_RESOURCE_EXECUTE,\n    MOCK_DM_RESOURCE_RESET,\n    MOCK_DM_LIST_RESOURCE_INSTANCES,\n    MOCK_DM_RESOURCE_READ_ATTRS,\n    MOCK_DM_RESOURCE_WRITE_ATTRS,\n    MOCK_DM_RESOURCE_INSTANCE_READ_ATTRS,\n    MOCK_DM_RESOURCE_INSTANCE_WRITE_ATTRS,\n    MOCK_DM_TRANSACTION_BEGIN,\n    MOCK_DM_TRANSACTION_VALIDATE,\n    MOCK_DM_TRANSACTION_COMMIT,\n    MOCK_DM_TRANSACTION_ROLLBACK\n#ifdef ANJAY_WITH_LWM2M12\n    ,\n    MOCK_DM_RESOURCE_INSTANCE_REMOVE\n#endif // ANJAY_WITH_LWM2M12\n} anjay_mock_dm_expected_command_type_t;\n\ntypedef struct {\n    anjay_mock_dm_expected_command_type_t command;\n    const char *command_str;\n    anjay_t *anjay;\n    const anjay_dm_object_def_t *const *obj_ptr;\n    union {\n        anjay_iid_t iid;\n        anjay_rid_t rid;\n        anjay_ssid_t ssid;\n        struct {\n            anjay_iid_t iid;\n            anjay_rid_t rid;\n        } iid_and_rid;\n        struct {\n            anjay_iid_t iid;\n            anjay_rid_t rid;\n            anjay_riid_t riid;\n        } iid_rid_riid;\n        struct {\n            anjay_ssid_t ssid;\n            anjay_iid_t iid;\n        } ssid_and_iid;\n        struct {\n            anjay_ssid_t ssid;\n            anjay_iid_t iid;\n            anjay_rid_t rid;\n        } ssid_iid_rid;\n        struct {\n            anjay_ssid_t ssid;\n            anjay_iid_t iid;\n            anjay_rid_t rid;\n            anjay_riid_t riid;\n        } ssid_iid_rid_riid;\n    } input;\n    union {\n        uint16_t *id_array;\n        anjay_mock_dm_res_entry_t *res_array;\n        anjay_mock_dm_data_t data;\n        const anjay_mock_dm_execute_data_t *execute_data;\n        anjay_dm_oi_attributes_t common_attributes;\n        anjay_dm_r_attributes_t resource_attributes;\n    } value;\n    int retval;\n} anjay_mock_dm_expected_command_t;\n\nstatic AVS_LIST(anjay_mock_dm_expected_command_t) EXPECTED_COMMANDS;\n\n#define DM_ACTION_COMMON(UName)                                         \\\n    do {                                                                \\\n        (void) MOCK_DM_##UName;                                         \\\n        AVS_UNIT_ASSERT_NOT_NULL(EXPECTED_COMMANDS);                    \\\n        AVS_UNIT_ASSERT_EQUAL_STRING(EXPECTED_COMMANDS->command_str,    \\\n                                     AVS_QUOTE_MACRO(MOCK_DM_##UName)); \\\n        AVS_UNIT_ASSERT_TRUE(EXPECTED_COMMANDS->anjay == anjay);        \\\n        AVS_UNIT_ASSERT_TRUE(EXPECTED_COMMANDS->obj_ptr == obj_ptr);    \\\n    } while (0)\n\n#define DM_ACTION_RETURN                                  \\\n    do {                                                  \\\n        int retval##__LINE__ = EXPECTED_COMMANDS->retval; \\\n        AVS_LIST_DELETE(&EXPECTED_COMMANDS);              \\\n        return retval##__LINE__;                          \\\n    } while (0)\n\nvoid _anjay_mock_dm_assert_common_attributes_equal(\n        const anjay_dm_oi_attributes_t *a, const anjay_dm_oi_attributes_t *b) {\n#ifdef ANJAY_WITH_CON_ATTR\n    AVS_UNIT_ASSERT_FIELD_EQUAL(a, b, con);\n#endif // ANJAY_WITH_CON_ATTR\n    AVS_UNIT_ASSERT_FIELD_EQUAL(a, b, min_period);\n    AVS_UNIT_ASSERT_FIELD_EQUAL(a, b, max_period);\n    AVS_UNIT_ASSERT_FIELD_EQUAL(a, b, min_eval_period);\n    AVS_UNIT_ASSERT_FIELD_EQUAL(a, b, max_eval_period);\n#ifdef ANJAY_WITH_LWM2M12\n    AVS_UNIT_ASSERT_FIELD_EQUAL(a, b, hqmax);\n#endif // ANJAY_WITH_LWM2M12\n}\n\nvoid _anjay_mock_dm_assert_attributes_equal(const anjay_dm_r_attributes_t *a,\n                                            const anjay_dm_r_attributes_t *b) {\n    _anjay_mock_dm_assert_common_attributes_equal(&a->common, &b->common);\n    AVS_UNIT_ASSERT_FIELD_EQUAL(a, b, greater_than);\n    AVS_UNIT_ASSERT_FIELD_EQUAL(a, b, less_than);\n    AVS_UNIT_ASSERT_FIELD_EQUAL(a, b, step);\n#ifdef ANJAY_WITH_LWM2M12\n    AVS_UNIT_ASSERT_FIELD_EQUAL(a, b, edge);\n#endif // ANJAY_WITH_LWM2M12\n}\n\nint _anjay_mock_dm_object_read_default_attrs(\n        anjay_t *anjay,\n        const anjay_dm_object_def_t *const *obj_ptr,\n        anjay_ssid_t ssid,\n        anjay_dm_oi_attributes_t *out) {\n    DM_ACTION_COMMON(OBJECT_READ_DEFAULT_ATTRS);\n    EXPECTED_COMMANDS->input.ssid = ssid;\n    *out = EXPECTED_COMMANDS->value.common_attributes;\n    DM_ACTION_RETURN;\n}\n\nint _anjay_mock_dm_object_write_default_attrs(\n        anjay_t *anjay,\n        const anjay_dm_object_def_t *const *obj_ptr,\n        anjay_ssid_t ssid,\n        const anjay_dm_oi_attributes_t *attrs) {\n    DM_ACTION_COMMON(OBJECT_WRITE_DEFAULT_ATTRS);\n    EXPECTED_COMMANDS->input.ssid = ssid;\n    _anjay_mock_dm_assert_common_attributes_equal(\n            attrs, &EXPECTED_COMMANDS->value.common_attributes);\n    DM_ACTION_RETURN;\n}\n\nint _anjay_mock_dm_list_instances(anjay_t *anjay,\n                                  const anjay_dm_object_def_t *const *obj_ptr,\n                                  anjay_dm_list_ctx_t *ctx) {\n    DM_ACTION_COMMON(LIST_INSTANCES);\n    // anjay_dm_emit() may call other handlers,\n    // so pop the command from the queue early\n    AVS_LIST(anjay_mock_dm_expected_command_t) command =\n            AVS_LIST_DETACH(&EXPECTED_COMMANDS);\n    for (const anjay_iid_t *iid = command->value.id_array;\n         *iid != ANJAY_ID_INVALID;\n         ++iid) {\n        anjay_dm_emit(ctx, *iid);\n    }\n    avs_free(command->value.id_array);\n    int retval = command->retval;\n    AVS_LIST_DELETE(&command);\n    return retval;\n}\n\n#define INSTANCE_ACTION(LName, UName)                                    \\\n    int _anjay_mock_dm_instance_##LName(                                 \\\n            anjay_t *anjay, const anjay_dm_object_def_t *const *obj_ptr, \\\n            anjay_iid_t iid) {                                           \\\n        DM_ACTION_COMMON(INSTANCE_##UName);                              \\\n        AVS_UNIT_ASSERT_EQUAL(iid, EXPECTED_COMMANDS->input.iid);        \\\n        DM_ACTION_RETURN;                                                \\\n    }\n\nINSTANCE_ACTION(reset, RESET)\nINSTANCE_ACTION(remove, REMOVE)\nINSTANCE_ACTION(create, CREATE)\n\nint _anjay_mock_dm_instance_read_default_attrs(\n        anjay_t *anjay,\n        const anjay_dm_object_def_t *const *obj_ptr,\n        anjay_iid_t iid,\n        anjay_ssid_t ssid,\n        anjay_dm_oi_attributes_t *out) {\n    DM_ACTION_COMMON(INSTANCE_READ_DEFAULT_ATTRS);\n    AVS_UNIT_ASSERT_EQUAL(ssid, EXPECTED_COMMANDS->input.ssid_and_iid.ssid);\n    AVS_UNIT_ASSERT_EQUAL(iid, EXPECTED_COMMANDS->input.ssid_and_iid.iid);\n    *out = EXPECTED_COMMANDS->value.common_attributes;\n    DM_ACTION_RETURN;\n}\n\nint _anjay_mock_dm_instance_write_default_attrs(\n        anjay_t *anjay,\n        const anjay_dm_object_def_t *const *obj_ptr,\n        anjay_iid_t iid,\n        anjay_ssid_t ssid,\n        const anjay_dm_oi_attributes_t *attrs) {\n    DM_ACTION_COMMON(INSTANCE_WRITE_DEFAULT_ATTRS);\n    AVS_UNIT_ASSERT_EQUAL(ssid, EXPECTED_COMMANDS->input.ssid_and_iid.ssid);\n    AVS_UNIT_ASSERT_EQUAL(iid, EXPECTED_COMMANDS->input.ssid_and_iid.iid);\n    _anjay_mock_dm_assert_common_attributes_equal(\n            attrs, &EXPECTED_COMMANDS->value.common_attributes);\n    DM_ACTION_RETURN;\n}\n\nint _anjay_mock_dm_list_resources(anjay_t *anjay,\n                                  const anjay_dm_object_def_t *const *obj_ptr,\n                                  anjay_iid_t iid,\n                                  anjay_dm_resource_list_ctx_t *ctx) {\n    DM_ACTION_COMMON(LIST_RESOURCES);\n    AVS_UNIT_ASSERT_EQUAL(iid, EXPECTED_COMMANDS->input.iid);\n    // avs_dm_emit_res() may call other handlers,\n    // so pop the command from the queue early\n    AVS_LIST(anjay_mock_dm_expected_command_t) command =\n            AVS_LIST_DETACH(&EXPECTED_COMMANDS);\n    if (command->value.res_array) {\n        for (const anjay_mock_dm_res_entry_t *res = command->value.res_array;\n             res->rid != ANJAY_ID_INVALID;\n             ++res) {\n            anjay_dm_emit_res(ctx, res->rid, res->kind, res->presence);\n        }\n        avs_free(command->value.id_array);\n    }\n    int retval = command->retval;\n    AVS_LIST_DELETE(&command);\n    return retval;\n}\n\nstatic void perform_output(anjay_output_ctx_t *ctx,\n                           const anjay_mock_dm_data_t *output) {\n    int retval;\n    switch (output->type) {\n    case MOCK_DATA_NONE:\n        return;\n    case MOCK_DATA_BYTES:\n        retval = anjay_ret_bytes(ctx, output->data.bytes.data,\n                                 output->data.bytes.length);\n        break;\n    case MOCK_DATA_STRING:\n        retval = anjay_ret_string(ctx, output->data.str);\n        break;\n    case MOCK_DATA_INT:\n        retval = anjay_ret_i64(ctx, output->data.i);\n        break;\n#ifdef ANJAY_WITH_LWM2M11\n    case MOCK_DATA_UINT:\n        retval = anjay_ret_u64(ctx, output->data.u);\n        break;\n#endif // ANJAY_WITH_LWM2M11\n    case MOCK_DATA_FLOAT:\n        retval = anjay_ret_double(ctx, output->data.f);\n        break;\n    case MOCK_DATA_BOOL:\n        retval = anjay_ret_bool(ctx, output->data.b);\n        break;\n    case MOCK_DATA_OBJLNK:\n        retval = anjay_ret_objlnk(ctx, output->data.objlnk.oid,\n                                  output->data.objlnk.iid);\n        break;\n    }\n    AVS_UNIT_ASSERT_EQUAL(retval, output->expected_retval);\n}\n\nint _anjay_mock_dm_resource_read(anjay_t *anjay,\n                                 const anjay_dm_object_def_t *const *obj_ptr,\n                                 anjay_iid_t iid,\n                                 anjay_rid_t rid,\n                                 anjay_riid_t riid,\n                                 anjay_output_ctx_t *ctx) {\n    DM_ACTION_COMMON(RESOURCE_READ);\n    AVS_UNIT_ASSERT_EQUAL(iid, EXPECTED_COMMANDS->input.iid_rid_riid.iid);\n    AVS_UNIT_ASSERT_EQUAL(rid, EXPECTED_COMMANDS->input.iid_rid_riid.rid);\n    AVS_UNIT_ASSERT_EQUAL(riid, EXPECTED_COMMANDS->input.iid_rid_riid.riid);\n    perform_output(ctx, &EXPECTED_COMMANDS->value.data);\n    DM_ACTION_RETURN;\n}\n\nstatic void perform_input(anjay_input_ctx_t *ctx,\n                          const anjay_mock_dm_data_t *input) {\n    int retval = 0;\n    switch (input->type) {\n    case MOCK_DATA_NONE:\n        return;\n    case MOCK_DATA_BYTES: {\n        size_t bytes_read;\n        bool message_finished;\n        char buf[input->data.bytes.length];\n        retval = anjay_get_bytes(ctx, &bytes_read, &message_finished, buf,\n                                 sizeof(buf));\n        if (!retval) {\n            AVS_UNIT_ASSERT_EQUAL(bytes_read, sizeof(buf));\n            AVS_UNIT_ASSERT_TRUE(message_finished);\n            AVS_UNIT_ASSERT_EQUAL_BYTES_SIZED(buf, input->data.bytes.data,\n                                              input->data.bytes.length);\n        }\n        break;\n    }\n    case MOCK_DATA_STRING: {\n        char buf[strlen(input->data.str) + 1];\n        retval = anjay_get_string(ctx, buf, sizeof(buf));\n        if (!retval) {\n            AVS_UNIT_ASSERT_EQUAL_STRING(buf, input->data.str);\n        }\n        break;\n    }\n    case MOCK_DATA_INT: {\n        int64_t value;\n        retval = anjay_get_i64(ctx, &value);\n        if (!retval) {\n            AVS_UNIT_ASSERT_EQUAL(value, input->data.i);\n        }\n        break;\n    }\n#ifdef ANJAY_WITH_LWM2M11\n    case MOCK_DATA_UINT: {\n        uint64_t value;\n        retval = anjay_get_u64(ctx, &value);\n        if (!retval) {\n            AVS_UNIT_ASSERT_EQUAL(value, input->data.u);\n        }\n        break;\n    }\n#endif // ANJAY_WITH_LWM2M11\n    case MOCK_DATA_FLOAT: {\n        double value;\n        retval = anjay_get_double(ctx, &value);\n        if (!retval) {\n            AVS_UNIT_ASSERT_EQUAL(value, input->data.f);\n        }\n        break;\n    }\n    case MOCK_DATA_BOOL: {\n        bool value;\n        retval = anjay_get_bool(ctx, &value);\n        if (!retval) {\n            AVS_UNIT_ASSERT_EQUAL(value, input->data.b);\n        }\n        break;\n    }\n    case MOCK_DATA_OBJLNK: {\n        anjay_oid_t oid;\n        anjay_iid_t iid;\n        retval = anjay_get_objlnk(ctx, &oid, &iid);\n        if (!retval) {\n            AVS_UNIT_ASSERT_EQUAL(oid, input->data.objlnk.oid);\n            AVS_UNIT_ASSERT_EQUAL(iid, input->data.objlnk.iid);\n        }\n        break;\n    }\n    default:\n        AVS_UNIT_ASSERT_NOT_EQUAL(input->type, input->type);\n    }\n    AVS_UNIT_ASSERT_EQUAL(retval, input->expected_retval);\n}\n\nint _anjay_mock_dm_resource_write(anjay_t *anjay,\n                                  const anjay_dm_object_def_t *const *obj_ptr,\n                                  anjay_iid_t iid,\n                                  anjay_rid_t rid,\n                                  anjay_riid_t riid,\n                                  anjay_input_ctx_t *ctx) {\n    DM_ACTION_COMMON(RESOURCE_WRITE);\n    AVS_UNIT_ASSERT_EQUAL(iid, EXPECTED_COMMANDS->input.iid_rid_riid.iid);\n    AVS_UNIT_ASSERT_EQUAL(rid, EXPECTED_COMMANDS->input.iid_rid_riid.rid);\n    AVS_UNIT_ASSERT_EQUAL(riid, EXPECTED_COMMANDS->input.iid_rid_riid.riid);\n    perform_input(ctx, &EXPECTED_COMMANDS->value.data);\n    DM_ACTION_RETURN;\n}\n\nint _anjay_mock_dm_resource_execute(anjay_t *anjay,\n                                    const anjay_dm_object_def_t *const *obj_ptr,\n                                    anjay_iid_t iid,\n                                    anjay_rid_t rid,\n                                    anjay_execute_ctx_t *ctx) {\n    int retval = 0;\n    int arg;\n    bool has_value;\n    DM_ACTION_COMMON(RESOURCE_EXECUTE);\n    AVS_UNIT_ASSERT_EQUAL(iid, EXPECTED_COMMANDS->input.iid_and_rid.iid);\n    AVS_UNIT_ASSERT_EQUAL(rid, EXPECTED_COMMANDS->input.iid_and_rid.rid);\n    if (EXPECTED_COMMANDS->value.execute_data) {\n        for (const anjay_mock_dm_execute_arg_t *const *mock_arg =\n                     *EXPECTED_COMMANDS->value.execute_data;\n             mock_arg && *mock_arg;\n             ++mock_arg) {\n            retval = anjay_execute_get_next_arg(ctx, &arg, &has_value);\n            if (!retval) {\n                AVS_UNIT_ASSERT_EQUAL(arg, (*mock_arg)->arg);\n                AVS_UNIT_ASSERT_EQUAL(has_value, !!(*mock_arg)->value);\n                if ((*mock_arg)->value) {\n                    char buf[strlen((*mock_arg)->value) + 1];\n                    size_t bytes_read;\n                    retval = anjay_execute_get_arg_value(ctx, &bytes_read, buf,\n                                                         sizeof(buf));\n                    if (!retval) {\n                        AVS_UNIT_ASSERT_EQUAL(bytes_read,\n                                              strlen((*mock_arg)->value));\n                        AVS_UNIT_ASSERT_EQUAL_STRING(buf, (*mock_arg)->value);\n                    }\n                }\n            }\n            AVS_UNIT_ASSERT_EQUAL(retval, (*mock_arg)->expected_retval);\n        }\n        if (!retval) {\n            AVS_UNIT_ASSERT_EQUAL(anjay_execute_get_next_arg(ctx, &arg,\n                                                             &has_value),\n                                  ANJAY_EXECUTE_GET_ARG_END);\n            AVS_UNIT_ASSERT_EQUAL(arg, -1);\n            AVS_UNIT_ASSERT_FALSE(has_value);\n        }\n    }\n    DM_ACTION_RETURN;\n}\n\nint _anjay_mock_dm_resource_reset(anjay_t *anjay,\n                                  const anjay_dm_object_def_t *const *obj_ptr,\n                                  anjay_iid_t iid,\n                                  anjay_rid_t rid) {\n    DM_ACTION_COMMON(RESOURCE_RESET);\n    AVS_UNIT_ASSERT_EQUAL(iid, EXPECTED_COMMANDS->input.iid_and_rid.iid);\n    AVS_UNIT_ASSERT_EQUAL(rid, EXPECTED_COMMANDS->input.iid_and_rid.rid);\n    DM_ACTION_RETURN;\n}\n\nint _anjay_mock_dm_list_resource_instances(\n        anjay_t *anjay,\n        const anjay_dm_object_def_t *const *obj_ptr,\n        anjay_iid_t iid,\n        anjay_rid_t rid,\n        anjay_dm_list_ctx_t *ctx) {\n    DM_ACTION_COMMON(LIST_RESOURCE_INSTANCES);\n    AVS_UNIT_ASSERT_EQUAL(iid, EXPECTED_COMMANDS->input.iid_and_rid.iid);\n    AVS_UNIT_ASSERT_EQUAL(rid, EXPECTED_COMMANDS->input.iid_and_rid.rid);\n    // avs_dm_emit() may call other handlers,\n    // so pop the command from the queue early\n    AVS_LIST(anjay_mock_dm_expected_command_t) command =\n            AVS_LIST_DETACH(&EXPECTED_COMMANDS);\n    if (command->value.id_array) {\n        for (const anjay_riid_t *riid = command->value.id_array;\n             *riid != ANJAY_ID_INVALID;\n             ++riid) {\n            anjay_dm_emit(ctx, *riid);\n        }\n        avs_free(command->value.id_array);\n    }\n    int retval = command->retval;\n    AVS_LIST_DELETE(&command);\n    return retval;\n}\n\nint _anjay_mock_dm_resource_read_attrs(\n        anjay_t *anjay,\n        const anjay_dm_object_def_t *const *obj_ptr,\n        anjay_iid_t iid,\n        anjay_rid_t rid,\n        anjay_ssid_t ssid,\n        anjay_dm_r_attributes_t *out) {\n    DM_ACTION_COMMON(RESOURCE_READ_ATTRS);\n    AVS_UNIT_ASSERT_EQUAL(ssid, EXPECTED_COMMANDS->input.ssid_iid_rid.ssid);\n    AVS_UNIT_ASSERT_EQUAL(iid, EXPECTED_COMMANDS->input.ssid_iid_rid.iid);\n    AVS_UNIT_ASSERT_EQUAL(rid, EXPECTED_COMMANDS->input.ssid_iid_rid.rid);\n    *out = EXPECTED_COMMANDS->value.resource_attributes;\n    DM_ACTION_RETURN;\n}\n\nint _anjay_mock_dm_resource_write_attrs(\n        anjay_t *anjay,\n        const anjay_dm_object_def_t *const *obj_ptr,\n        anjay_iid_t iid,\n        anjay_rid_t rid,\n        anjay_ssid_t ssid,\n        const anjay_dm_r_attributes_t *attrs) {\n    DM_ACTION_COMMON(RESOURCE_WRITE_ATTRS);\n    AVS_UNIT_ASSERT_EQUAL(ssid, EXPECTED_COMMANDS->input.ssid_iid_rid.ssid);\n    AVS_UNIT_ASSERT_EQUAL(iid, EXPECTED_COMMANDS->input.ssid_iid_rid.iid);\n    AVS_UNIT_ASSERT_EQUAL(rid, EXPECTED_COMMANDS->input.ssid_iid_rid.rid);\n    _anjay_mock_dm_assert_attributes_equal(\n            attrs, &EXPECTED_COMMANDS->value.resource_attributes);\n    DM_ACTION_RETURN;\n}\n\n#define TRANSACTION_ACTION(LName, UName)                                   \\\n    int _anjay_mock_dm_transaction_##LName(                                \\\n            anjay_t *anjay, const anjay_dm_object_def_t *const *obj_ptr) { \\\n        DM_ACTION_COMMON(TRANSACTION_##UName);                             \\\n        DM_ACTION_RETURN;                                                  \\\n    }\n\nTRANSACTION_ACTION(begin, BEGIN)\nTRANSACTION_ACTION(validate, VALIDATE)\nTRANSACTION_ACTION(commit, COMMIT)\nTRANSACTION_ACTION(rollback, ROLLBACK)\n\n#ifdef ANJAY_WITH_LWM2M11\nint _anjay_mock_dm_resource_instance_read_attrs(\n        anjay_t *anjay,\n        const anjay_dm_object_def_t *const *obj_ptr,\n        anjay_iid_t iid,\n        anjay_rid_t rid,\n        anjay_riid_t riid,\n        anjay_ssid_t ssid,\n        anjay_dm_r_attributes_t *out) {\n    DM_ACTION_COMMON(RESOURCE_INSTANCE_READ_ATTRS);\n    AVS_UNIT_ASSERT_EQUAL(ssid,\n                          EXPECTED_COMMANDS->input.ssid_iid_rid_riid.ssid);\n    AVS_UNIT_ASSERT_EQUAL(iid, EXPECTED_COMMANDS->input.ssid_iid_rid_riid.iid);\n    AVS_UNIT_ASSERT_EQUAL(rid, EXPECTED_COMMANDS->input.ssid_iid_rid_riid.rid);\n    AVS_UNIT_ASSERT_EQUAL(riid,\n                          EXPECTED_COMMANDS->input.ssid_iid_rid_riid.riid);\n    *out = EXPECTED_COMMANDS->value.resource_attributes;\n    DM_ACTION_RETURN;\n}\n\nint _anjay_mock_dm_resource_instance_write_attrs(\n        anjay_t *anjay,\n        const anjay_dm_object_def_t *const *obj_ptr,\n        anjay_iid_t iid,\n        anjay_rid_t rid,\n        anjay_riid_t riid,\n        anjay_ssid_t ssid,\n        const anjay_dm_r_attributes_t *attrs) {\n    DM_ACTION_COMMON(RESOURCE_INSTANCE_WRITE_ATTRS);\n    AVS_UNIT_ASSERT_EQUAL(ssid,\n                          EXPECTED_COMMANDS->input.ssid_iid_rid_riid.ssid);\n    AVS_UNIT_ASSERT_EQUAL(iid, EXPECTED_COMMANDS->input.ssid_iid_rid_riid.iid);\n    AVS_UNIT_ASSERT_EQUAL(rid, EXPECTED_COMMANDS->input.ssid_iid_rid_riid.rid);\n    AVS_UNIT_ASSERT_EQUAL(riid,\n                          EXPECTED_COMMANDS->input.ssid_iid_rid_riid.riid);\n    _anjay_mock_dm_assert_attributes_equal(\n            attrs, &EXPECTED_COMMANDS->value.resource_attributes);\n    DM_ACTION_RETURN;\n}\n#endif // ANJAY_WITH_LWM2M11\n\n#ifdef ANJAY_WITH_LWM2M12\nint _anjay_mock_dm_resource_instance_remove(\n        anjay_t *anjay,\n        const anjay_dm_object_def_t *const *obj_ptr,\n        anjay_iid_t iid,\n        anjay_rid_t rid,\n        anjay_riid_t riid) {\n    DM_ACTION_COMMON(RESOURCE_INSTANCE_REMOVE);\n    AVS_UNIT_ASSERT_EQUAL(iid, EXPECTED_COMMANDS->input.iid_rid_riid.iid);\n    AVS_UNIT_ASSERT_EQUAL(rid, EXPECTED_COMMANDS->input.iid_rid_riid.rid);\n    AVS_UNIT_ASSERT_EQUAL(riid, EXPECTED_COMMANDS->input.iid_rid_riid.riid);\n    DM_ACTION_RETURN;\n}\n#endif // ANJAY_WITH_LWM2M12\n\nstatic anjay_mock_dm_expected_command_t *\nnew_expected_command_impl(anjay_mock_dm_expected_command_type_t type,\n                          const char *type_str) {\n    anjay_mock_dm_expected_command_t *new_command =\n            AVS_LIST_NEW_ELEMENT(anjay_mock_dm_expected_command_t);\n    AVS_UNIT_ASSERT_NOT_NULL(new_command);\n    new_command->command = type;\n    new_command->command_str = type_str;\n    AVS_LIST_APPEND(&EXPECTED_COMMANDS, new_command);\n    return new_command;\n}\n\n#define NEW_EXPECTED_COMMAND(Type) \\\n    (new_expected_command_impl((Type), AVS_QUOTE_MACRO(Type)))\n\nvoid _anjay_mock_dm_expect_object_read_default_attrs(\n        anjay_t *anjay,\n        const anjay_dm_object_def_t *const *obj_ptr,\n        anjay_ssid_t ssid,\n        int retval,\n        const anjay_dm_oi_attributes_t *attrs) {\n    anjay_mock_dm_expected_command_t *command =\n            NEW_EXPECTED_COMMAND(MOCK_DM_OBJECT_READ_DEFAULT_ATTRS);\n    command->anjay = anjay;\n    command->obj_ptr = obj_ptr;\n    command->input.ssid = ssid;\n    command->retval = retval;\n    if (attrs) {\n        command->value.common_attributes = *attrs;\n    } else {\n        AVS_UNIT_ASSERT_FAILED(retval);\n    }\n}\n\nvoid _anjay_mock_dm_expect_object_write_default_attrs(\n        anjay_t *anjay,\n        const anjay_dm_object_def_t *const *obj_ptr,\n        anjay_ssid_t ssid,\n        const anjay_dm_oi_attributes_t *attrs,\n        int retval) {\n    anjay_mock_dm_expected_command_t *command =\n            NEW_EXPECTED_COMMAND(MOCK_DM_OBJECT_WRITE_DEFAULT_ATTRS);\n    command->anjay = anjay;\n    command->obj_ptr = obj_ptr;\n    command->input.ssid = ssid;\n    command->retval = retval;\n    command->value.common_attributes = *attrs;\n}\n\nvoid _anjay_mock_dm_expect_list_instances(\n        anjay_t *anjay,\n        const anjay_dm_object_def_t *const *obj_ptr,\n        int retval,\n        const anjay_iid_t *iid_array) {\n    anjay_mock_dm_expected_command_t *command =\n            NEW_EXPECTED_COMMAND(MOCK_DM_LIST_INSTANCES);\n    command->anjay = anjay;\n    command->obj_ptr = obj_ptr;\n    size_t array_size = 1;\n    while (iid_array[array_size - 1] != ANJAY_ID_INVALID) {\n        ++array_size;\n    }\n    command->value.id_array = avs_malloc(array_size * sizeof(anjay_iid_t));\n    AVS_UNIT_ASSERT_NOT_NULL(command->value.id_array);\n    memcpy(command->value.id_array, iid_array,\n           array_size * sizeof(anjay_iid_t));\n    command->retval = retval;\n}\n\n#define EXPECT_INSTANCE_ACTION(LName, UName)                             \\\n    void _anjay_mock_dm_expect_instance_##LName(                         \\\n            anjay_t *anjay, const anjay_dm_object_def_t *const *obj_ptr, \\\n            anjay_iid_t iid, int retval) {                               \\\n        anjay_mock_dm_expected_command_t *command =                      \\\n                NEW_EXPECTED_COMMAND(MOCK_DM_INSTANCE_##UName);          \\\n        command->anjay = anjay;                                          \\\n        command->obj_ptr = obj_ptr;                                      \\\n        command->input.iid = iid;                                        \\\n        command->retval = retval;                                        \\\n    }\n\nEXPECT_INSTANCE_ACTION(reset, RESET)\nEXPECT_INSTANCE_ACTION(remove, REMOVE)\nEXPECT_INSTANCE_ACTION(create, CREATE)\n\nvoid _anjay_mock_dm_expect_instance_read_default_attrs(\n        anjay_t *anjay,\n        const anjay_dm_object_def_t *const *obj_ptr,\n        anjay_iid_t iid,\n        anjay_ssid_t ssid,\n        int retval,\n        const anjay_dm_oi_attributes_t *attrs) {\n    anjay_mock_dm_expected_command_t *command =\n            NEW_EXPECTED_COMMAND(MOCK_DM_INSTANCE_READ_DEFAULT_ATTRS);\n    command->anjay = anjay;\n    command->obj_ptr = obj_ptr;\n    command->input.ssid_and_iid.ssid = ssid;\n    command->input.ssid_and_iid.iid = iid;\n    command->retval = retval;\n    if (attrs) {\n        command->value.common_attributes = *attrs;\n    } else {\n        AVS_UNIT_ASSERT_FAILED(retval);\n    }\n}\n\nvoid _anjay_mock_dm_expect_instance_write_default_attrs(\n        anjay_t *anjay,\n        const anjay_dm_object_def_t *const *obj_ptr,\n        anjay_iid_t iid,\n        anjay_ssid_t ssid,\n        const anjay_dm_oi_attributes_t *attrs,\n        int retval) {\n    anjay_mock_dm_expected_command_t *command =\n            NEW_EXPECTED_COMMAND(MOCK_DM_INSTANCE_WRITE_DEFAULT_ATTRS);\n    command->anjay = anjay;\n    command->obj_ptr = obj_ptr;\n    command->input.ssid_and_iid.ssid = ssid;\n    command->input.ssid_and_iid.iid = iid;\n    command->retval = retval;\n    command->value.common_attributes = *attrs;\n}\n\nvoid _anjay_mock_dm_expect_list_resources(\n        anjay_t *anjay,\n        const anjay_dm_object_def_t *const *obj_ptr,\n        anjay_iid_t iid,\n        int retval,\n        const anjay_mock_dm_res_entry_t *res_array) {\n    anjay_mock_dm_expected_command_t *command =\n            NEW_EXPECTED_COMMAND(MOCK_DM_LIST_RESOURCES);\n    command->anjay = anjay;\n    command->obj_ptr = obj_ptr;\n    command->input.iid = iid;\n    if (res_array) {\n        size_t array_size = 1;\n        while (res_array[array_size - 1].rid != ANJAY_ID_INVALID) {\n            ++array_size;\n        }\n        command->value.res_array =\n                avs_malloc(array_size * sizeof(anjay_mock_dm_res_entry_t));\n        AVS_UNIT_ASSERT_NOT_NULL(command->value.id_array);\n        memcpy(command->value.res_array, res_array,\n               array_size * sizeof(anjay_mock_dm_res_entry_t));\n    }\n    command->retval = retval;\n}\n\n#define EXPECT_RESOURCE_ACTION_COMMON(UName)                \\\n    anjay_mock_dm_expected_command_t *command =             \\\n            NEW_EXPECTED_COMMAND(MOCK_DM_RESOURCE_##UName); \\\n    command->anjay = anjay;                                 \\\n    command->obj_ptr = obj_ptr;                             \\\n    command->input.iid_and_rid.iid = iid;                   \\\n    command->input.iid_and_rid.rid = rid;                   \\\n    command->retval = retval;\n\nvoid _anjay_mock_dm_expect_resource_read(\n        anjay_t *anjay,\n        const anjay_dm_object_def_t *const *obj_ptr,\n        anjay_iid_t iid,\n        anjay_rid_t rid,\n        anjay_riid_t riid,\n        int retval,\n        const anjay_mock_dm_data_t *data) {\n    anjay_mock_dm_expected_command_t *command =\n            NEW_EXPECTED_COMMAND(MOCK_DM_RESOURCE_READ);\n    command->anjay = anjay;\n    command->obj_ptr = obj_ptr;\n    command->input.iid_rid_riid.iid = iid;\n    command->input.iid_rid_riid.rid = rid;\n    command->input.iid_rid_riid.riid = riid;\n    command->retval = retval;\n    command->value.data = *data;\n}\n\nvoid _anjay_mock_dm_expect_resource_write(\n        anjay_t *anjay,\n        const anjay_dm_object_def_t *const *obj_ptr,\n        anjay_iid_t iid,\n        anjay_rid_t rid,\n        anjay_rid_t riid,\n        const anjay_mock_dm_data_t *data,\n        int retval) {\n    anjay_mock_dm_expected_command_t *command =\n            NEW_EXPECTED_COMMAND(MOCK_DM_RESOURCE_WRITE);\n    command->anjay = anjay;\n    command->obj_ptr = obj_ptr;\n    command->input.iid_rid_riid.iid = iid;\n    command->input.iid_rid_riid.rid = rid;\n    command->input.iid_rid_riid.riid = riid;\n    command->retval = retval;\n    command->value.data = *data;\n}\n\nvoid _anjay_mock_dm_expect_resource_execute(\n        anjay_t *anjay,\n        const anjay_dm_object_def_t *const *obj_ptr,\n        anjay_iid_t iid,\n        anjay_rid_t rid,\n        const anjay_mock_dm_execute_data_t *data,\n        int retval) {\n    EXPECT_RESOURCE_ACTION_COMMON(EXECUTE);\n    command->value.execute_data = data;\n}\n\nvoid _anjay_mock_dm_expect_resource_reset(\n        anjay_t *anjay,\n        const anjay_dm_object_def_t *const *obj_ptr,\n        anjay_iid_t iid,\n        anjay_rid_t rid,\n        int retval) {\n    EXPECT_RESOURCE_ACTION_COMMON(RESET);\n}\n\nvoid _anjay_mock_dm_expect_list_resource_instances(\n        anjay_t *anjay,\n        const anjay_dm_object_def_t *const *obj_ptr,\n        anjay_iid_t iid,\n        anjay_rid_t rid,\n        int retval,\n        const anjay_riid_t *riid_array) {\n    anjay_mock_dm_expected_command_t *command =\n            NEW_EXPECTED_COMMAND(MOCK_DM_LIST_RESOURCE_INSTANCES);\n    command->anjay = anjay;\n    command->obj_ptr = obj_ptr;\n    command->input.iid_and_rid.iid = iid;\n    command->input.iid_and_rid.rid = rid;\n    command->retval = retval;\n    if (riid_array) {\n        size_t array_size = 1;\n        while (riid_array[array_size - 1] != ANJAY_ID_INVALID) {\n            ++array_size;\n        }\n        command->value.id_array = avs_malloc(array_size * sizeof(anjay_riid_t));\n        AVS_UNIT_ASSERT_NOT_NULL(command->value.id_array);\n        memcpy(command->value.id_array, riid_array,\n               array_size * sizeof(anjay_riid_t));\n    }\n}\n\nvoid _anjay_mock_dm_expect_resource_read_attrs(\n        anjay_t *anjay,\n        const anjay_dm_object_def_t *const *obj_ptr,\n        anjay_iid_t iid,\n        anjay_rid_t rid,\n        anjay_ssid_t ssid,\n        int retval,\n        const anjay_dm_r_attributes_t *attrs) {\n    anjay_mock_dm_expected_command_t *command =\n            NEW_EXPECTED_COMMAND(MOCK_DM_RESOURCE_READ_ATTRS);\n    command->anjay = anjay;\n    command->obj_ptr = obj_ptr;\n    command->input.ssid_iid_rid.ssid = ssid;\n    command->input.ssid_iid_rid.iid = iid;\n    command->input.ssid_iid_rid.rid = rid;\n    command->retval = retval;\n    if (attrs) {\n        command->value.resource_attributes = *attrs;\n    } else {\n        AVS_UNIT_ASSERT_FAILED(retval);\n    }\n}\n\nvoid _anjay_mock_dm_expect_resource_write_attrs(\n        anjay_t *anjay,\n        const anjay_dm_object_def_t *const *obj_ptr,\n        anjay_iid_t iid,\n        anjay_rid_t rid,\n        anjay_ssid_t ssid,\n        const anjay_dm_r_attributes_t *attrs,\n        int retval) {\n    anjay_mock_dm_expected_command_t *command =\n            NEW_EXPECTED_COMMAND(MOCK_DM_RESOURCE_WRITE_ATTRS);\n    command->anjay = anjay;\n    command->obj_ptr = obj_ptr;\n    command->input.ssid_iid_rid.ssid = ssid;\n    command->input.ssid_iid_rid.iid = iid;\n    command->input.ssid_iid_rid.rid = rid;\n    command->retval = retval;\n    command->value.resource_attributes = *attrs;\n}\n\nvoid _anjay_mock_dm_expect_resource_instance_read_attrs(\n        anjay_t *anjay,\n        const anjay_dm_object_def_t *const *obj_ptr,\n        anjay_iid_t iid,\n        anjay_rid_t rid,\n        anjay_riid_t riid,\n        anjay_ssid_t ssid,\n        int retval,\n        const anjay_dm_r_attributes_t *attrs) {\n    anjay_mock_dm_expected_command_t *command =\n            NEW_EXPECTED_COMMAND(MOCK_DM_RESOURCE_INSTANCE_READ_ATTRS);\n    command->anjay = anjay;\n    command->obj_ptr = obj_ptr;\n    command->input.ssid_iid_rid_riid.ssid = ssid;\n    command->input.ssid_iid_rid_riid.iid = iid;\n    command->input.ssid_iid_rid_riid.rid = rid;\n    command->input.ssid_iid_rid_riid.riid = riid;\n    command->retval = retval;\n    if (attrs) {\n        command->value.resource_attributes = *attrs;\n    } else {\n        AVS_UNIT_ASSERT_FAILED(retval);\n    }\n}\n\nvoid _anjay_mock_dm_expect_resource_instance_write_attrs(\n        anjay_t *anjay,\n        const anjay_dm_object_def_t *const *obj_ptr,\n        anjay_iid_t iid,\n        anjay_rid_t rid,\n        anjay_riid_t riid,\n        anjay_ssid_t ssid,\n        const anjay_dm_r_attributes_t *attrs,\n        int retval) {\n    anjay_mock_dm_expected_command_t *command =\n            NEW_EXPECTED_COMMAND(MOCK_DM_RESOURCE_INSTANCE_WRITE_ATTRS);\n    command->anjay = anjay;\n    command->obj_ptr = obj_ptr;\n    command->input.ssid_iid_rid_riid.ssid = ssid;\n    command->input.ssid_iid_rid_riid.iid = iid;\n    command->input.ssid_iid_rid_riid.rid = rid;\n    command->input.ssid_iid_rid_riid.riid = riid;\n    command->retval = retval;\n    command->value.resource_attributes = *attrs;\n}\n\n#define EXPECT_TRANSACTION_ACTION(LName, UName)                          \\\n    void _anjay_mock_dm_expect_transaction_##LName(                      \\\n            anjay_t *anjay, const anjay_dm_object_def_t *const *obj_ptr, \\\n            int retval) {                                                \\\n        anjay_mock_dm_expected_command_t *command =                      \\\n                NEW_EXPECTED_COMMAND(MOCK_DM_TRANSACTION_##UName);       \\\n        command->anjay = anjay;                                          \\\n        command->obj_ptr = obj_ptr;                                      \\\n        command->retval = retval;                                        \\\n    }\n\nEXPECT_TRANSACTION_ACTION(begin, BEGIN)\nEXPECT_TRANSACTION_ACTION(validate, VALIDATE)\nEXPECT_TRANSACTION_ACTION(commit, COMMIT)\nEXPECT_TRANSACTION_ACTION(rollback, ROLLBACK)\n\n#ifdef ANJAY_WITH_LWM2M12\nvoid _anjay_mock_dm_expect_resource_instance_remove(\n        anjay_t *anjay,\n        const anjay_dm_object_def_t *const *obj_ptr,\n        anjay_iid_t iid,\n        anjay_rid_t rid,\n        anjay_riid_t riid,\n        int retval) {\n    anjay_mock_dm_expected_command_t *command =\n            NEW_EXPECTED_COMMAND(MOCK_DM_RESOURCE_INSTANCE_REMOVE);\n    command->anjay = anjay;\n    command->obj_ptr = obj_ptr;\n    command->input.iid_rid_riid.iid = iid;\n    command->input.iid_rid_riid.rid = rid;\n    command->input.iid_rid_riid.riid = riid;\n    command->retval = retval;\n}\n#endif // ANJAY_WITH_LWM2M12\n\nvoid _anjay_mock_dm_expect_clean(void) {\n    AVS_UNIT_ASSERT_NULL(EXPECTED_COMMANDS);\n}\n\nvoid _anjay_mock_dm_expected_commands_clear(void) {\n    AVS_LIST_CLEAR(&EXPECTED_COMMANDS);\n}\n"
  },
  {
    "path": "tests/utils/mock_dm.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ANJAY_TEST_MOCK_DM_H\n#define ANJAY_TEST_MOCK_DM_H\n\n#include <anjay/dm.h>\n\ntypedef enum {\n    MOCK_DATA_NONE,\n    MOCK_DATA_BYTES,\n    MOCK_DATA_STRING,\n    MOCK_DATA_INT,\n#ifdef ANJAY_WITH_LWM2M11\n    MOCK_DATA_UINT,\n#endif // ANJAY_WITH_LWM2M11\n    MOCK_DATA_FLOAT,\n    MOCK_DATA_BOOL,\n    MOCK_DATA_OBJLNK\n} anjay_mock_dm_data_type_t;\n\ntypedef union {\n    struct {\n        const void *data;\n        size_t length;\n    } bytes;\n    const char *str;\n    int64_t i;\n#ifdef ANJAY_WITH_LWM2M11\n    uint64_t u;\n#endif // ANJAY_WITH_LWM2M11\n    double f;\n    bool b;\n    struct {\n        anjay_oid_t oid;\n        anjay_iid_t iid;\n    } objlnk;\n} anjay_mock_dm_data_value_t;\n\ntypedef struct {\n    anjay_mock_dm_data_type_t type;\n    anjay_mock_dm_data_value_t data;\n    int expected_retval;\n} anjay_mock_dm_data_t;\n\n#define ANJAY_MOCK_DM_NONE           \\\n    (&(const anjay_mock_dm_data_t) { \\\n        .type = MOCK_DATA_NONE       \\\n    })\n\n#pragma GCC diagnostic ignored \"-Wunused-value\"\n\n#define ANJAY_MOCK_DM_BYTES(Retval, Str)  \\\n    (&(const anjay_mock_dm_data_t) {      \\\n        .type = MOCK_DATA_BYTES,          \\\n        .data = {                         \\\n            .bytes = {                    \\\n                .data = Str,              \\\n                .length = sizeof(Str) - 1 \\\n            }                             \\\n        },                                \\\n        .expected_retval = Retval         \\\n    })\n\n#define ANJAY_MOCK_DM_STRING(Retval, Str) \\\n    (&(const anjay_mock_dm_data_t) {      \\\n        .type = MOCK_DATA_STRING,         \\\n        .data = {                         \\\n            .str = Str                    \\\n        },                                \\\n        .expected_retval = Retval         \\\n    })\n\n#define ANJAY_MOCK_DM_INT(Retval, Value) \\\n    (&(const anjay_mock_dm_data_t) {     \\\n        .type = MOCK_DATA_INT,           \\\n        .data = {                        \\\n            .i = Value                   \\\n        },                               \\\n        .expected_retval = Retval        \\\n    })\n\n#ifdef ANJAY_WITH_LWM2M11\n#    define ANJAY_MOCK_DM_UINT(Retval, Value) \\\n        (&(const anjay_mock_dm_data_t) {      \\\n            .type = MOCK_DATA_UINT,           \\\n            .data = {                         \\\n                .u = Value                    \\\n            },                                \\\n            .expected_retval = Retval         \\\n        })\n#endif // ANJAY_WITH_LWM2M11\n\n#define ANJAY_MOCK_DM_FLOAT(Retval, Value) \\\n    (&(const anjay_mock_dm_data_t) {       \\\n        .type = MOCK_DATA_FLOAT,           \\\n        .data = {                          \\\n            .f = Value                     \\\n        },                                 \\\n        .expected_retval = Retval          \\\n    })\n\n#define ANJAY_MOCK_DM_BOOL(Retval, Value) \\\n    (&(const anjay_mock_dm_data_t) {      \\\n        .type = MOCK_DATA_BOOL,           \\\n        .data = {                         \\\n            .b = Value                    \\\n        },                                \\\n        .expected_retval = Retval         \\\n    })\n\n#define ANJAY_MOCK_DM_OBJLNK(Retval, Oid, Iid) \\\n    (&(const anjay_mock_dm_data_t) {           \\\n        .type = MOCK_DATA_OBJLNK,              \\\n        .data = {                              \\\n            .objlnk = {                        \\\n                .oid = Oid,                    \\\n                .iid = Iid                     \\\n            }                                  \\\n        },                                     \\\n        .expected_retval = Retval              \\\n    })\n\ntypedef struct {\n    int expected_retval;\n    int arg;\n    const char *value;\n} anjay_mock_dm_execute_arg_t;\n\n#define ANJAY_MOCK_DM_EXECUTE_ARG(Retval, ...) \\\n    (&(const anjay_mock_dm_execute_arg_t) { Retval, __VA_ARGS__ })\n\ntypedef const anjay_mock_dm_execute_arg_t *anjay_mock_dm_execute_data_t[];\n\n#define ANJAY_MOCK_DM_EXECUTE(...) \\\n    (&(const anjay_mock_dm_execute_arg_t *[]) { __VA_ARGS__, NULL })\n\nvoid _anjay_mock_dm_assert_common_attributes_equal(\n        const anjay_dm_oi_attributes_t *a, const anjay_dm_oi_attributes_t *b);\n\nvoid _anjay_mock_dm_assert_attributes_equal(const anjay_dm_r_attributes_t *a,\n                                            const anjay_dm_r_attributes_t *b);\n\ntypedef struct {\n    anjay_rid_t rid;\n    anjay_dm_resource_kind_t kind;\n    anjay_dm_resource_presence_t presence;\n} anjay_mock_dm_res_entry_t;\n\n#define ANJAY_MOCK_DM_RES_END \\\n    { ANJAY_ID_INVALID, ANJAY_DM_RES_R, ANJAY_DM_RES_ABSENT }\n\nanjay_dm_object_read_default_attrs_t _anjay_mock_dm_object_read_default_attrs;\nanjay_dm_object_write_default_attrs_t _anjay_mock_dm_object_write_default_attrs;\nanjay_dm_instance_reset_t _anjay_mock_dm_instance_reset;\nanjay_dm_list_instances_t _anjay_mock_dm_list_instances;\nanjay_dm_instance_create_t _anjay_mock_dm_instance_create;\nanjay_dm_instance_remove_t _anjay_mock_dm_instance_remove;\nanjay_dm_instance_read_default_attrs_t\n        _anjay_mock_dm_instance_read_default_attrs;\nanjay_dm_instance_write_default_attrs_t\n        _anjay_mock_dm_instance_write_default_attrs;\nanjay_dm_list_resources_t _anjay_mock_dm_list_resources;\nanjay_dm_resource_read_t _anjay_mock_dm_resource_read;\nanjay_dm_resource_write_t _anjay_mock_dm_resource_write;\nanjay_dm_resource_execute_t _anjay_mock_dm_resource_execute;\nanjay_dm_resource_reset_t _anjay_mock_dm_resource_reset;\nanjay_dm_list_resource_instances_t _anjay_mock_dm_list_resource_instances;\nanjay_dm_resource_read_attrs_t _anjay_mock_dm_resource_read_attrs;\nanjay_dm_resource_write_attrs_t _anjay_mock_dm_resource_write_attrs;\nanjay_dm_transaction_begin_t _anjay_mock_dm_transaction_begin;\nanjay_dm_transaction_validate_t _anjay_mock_dm_transaction_validate;\nanjay_dm_transaction_commit_t _anjay_mock_dm_transaction_commit;\nanjay_dm_transaction_rollback_t _anjay_mock_dm_transaction_rollback;\n#ifdef ANJAY_WITH_LWM2M11\nanjay_dm_resource_instance_read_attrs_t\n        _anjay_mock_dm_resource_instance_read_attrs;\nanjay_dm_resource_instance_write_attrs_t\n        _anjay_mock_dm_resource_instance_write_attrs;\n#endif // ANJAY_WITH_LWM2M11\n#ifdef ANJAY_WITH_LWM2M12\nanjay_dm_resource_instance_remove_t _anjay_mock_dm_resource_instance_remove;\n#endif // ANJAY_WITH_LWM2M12\n\n#define ANJAY_MOCK_DM_HANDLERS_BASIC                     \\\n    .list_instances = _anjay_mock_dm_list_instances,     \\\n    .instance_create = _anjay_mock_dm_instance_create,   \\\n    .instance_remove = _anjay_mock_dm_instance_remove,   \\\n    .list_resources = _anjay_mock_dm_list_resources,     \\\n    .resource_read = _anjay_mock_dm_resource_read,       \\\n    .resource_write = _anjay_mock_dm_resource_write,     \\\n    .resource_execute = _anjay_mock_dm_resource_execute, \\\n    .resource_reset = _anjay_mock_dm_resource_reset,     \\\n    .list_resource_instances = _anjay_mock_dm_list_resource_instances\n\n#if defined(ANJAY_WITH_LWM2M12)\n#    define ANJAY_MOCK_DM_HANDLERS_REST                                        \\\n        .object_read_default_attrs = _anjay_mock_dm_object_read_default_attrs, \\\n        .object_write_default_attrs =                                          \\\n                _anjay_mock_dm_object_write_default_attrs,                     \\\n        .instance_read_default_attrs =                                         \\\n                _anjay_mock_dm_instance_read_default_attrs,                    \\\n        .instance_write_default_attrs =                                        \\\n                _anjay_mock_dm_instance_write_default_attrs,                   \\\n        .resource_read_attrs = _anjay_mock_dm_resource_read_attrs,             \\\n        .resource_write_attrs = _anjay_mock_dm_resource_write_attrs,           \\\n        .resource_instance_read_attrs =                                        \\\n                _anjay_mock_dm_resource_instance_read_attrs,                   \\\n        .resource_instance_write_attrs =                                       \\\n                _anjay_mock_dm_resource_instance_write_attrs,                  \\\n        .resource_instance_remove = _anjay_mock_dm_resource_instance_remove\n#elif defined(ANJAY_WITH_LWM2M11)\n#    define ANJAY_MOCK_DM_HANDLERS_REST                                        \\\n        .object_read_default_attrs = _anjay_mock_dm_object_read_default_attrs, \\\n        .object_write_default_attrs =                                          \\\n                _anjay_mock_dm_object_write_default_attrs,                     \\\n        .instance_read_default_attrs =                                         \\\n                _anjay_mock_dm_instance_read_default_attrs,                    \\\n        .instance_write_default_attrs =                                        \\\n                _anjay_mock_dm_instance_write_default_attrs,                   \\\n        .resource_read_attrs = _anjay_mock_dm_resource_read_attrs,             \\\n        .resource_write_attrs = _anjay_mock_dm_resource_write_attrs,           \\\n        .resource_instance_read_attrs =                                        \\\n                _anjay_mock_dm_resource_instance_read_attrs,                   \\\n        .resource_instance_write_attrs =                                       \\\n                _anjay_mock_dm_resource_instance_write_attrs\n#else // defined(ANJAY_WITH_LWM2M11)\n#    define ANJAY_MOCK_DM_HANDLERS_REST                                        \\\n        .object_read_default_attrs = _anjay_mock_dm_object_read_default_attrs, \\\n        .object_write_default_attrs =                                          \\\n                _anjay_mock_dm_object_write_default_attrs,                     \\\n        .instance_read_default_attrs =                                         \\\n                _anjay_mock_dm_instance_read_default_attrs,                    \\\n        .instance_write_default_attrs =                                        \\\n                _anjay_mock_dm_instance_write_default_attrs,                   \\\n        .resource_read_attrs = _anjay_mock_dm_resource_read_attrs,             \\\n        .resource_write_attrs = _anjay_mock_dm_resource_write_attrs\n#endif // ANJAY_WITH_LWM2M11\n\n#define ANJAY_MOCK_DM_HANDLERS_TRANSACTION_NOOP        \\\n    .transaction_begin = anjay_dm_transaction_NOOP,    \\\n    .transaction_validate = anjay_dm_transaction_NOOP, \\\n    .transaction_commit = anjay_dm_transaction_NOOP,   \\\n    .transaction_rollback = anjay_dm_transaction_NOOP\n\n#define ANJAY_MOCK_DM_HANDLERS_TRANSACTION                       \\\n    .transaction_begin = _anjay_mock_dm_transaction_begin,       \\\n    .transaction_validate = _anjay_mock_dm_transaction_validate, \\\n    .transaction_commit = _anjay_mock_dm_transaction_commit,     \\\n    .transaction_rollback = _anjay_mock_dm_transaction_rollback\n\n#define ANJAY_MOCK_DM_HANDLERS                                 \\\n    ANJAY_MOCK_DM_HANDLERS_BASIC, ANJAY_MOCK_DM_HANDLERS_REST, \\\n            ANJAY_MOCK_DM_HANDLERS_TRANSACTION_NOOP\n\nvoid _anjay_mock_dm_expect_object_read_default_attrs(\n        anjay_t *anjay,\n        const anjay_dm_object_def_t *const *obj_ptr,\n        anjay_ssid_t ssid,\n        int retval,\n        const anjay_dm_oi_attributes_t *attrs);\nvoid _anjay_mock_dm_expect_object_write_default_attrs(\n        anjay_t *anjay,\n        const anjay_dm_object_def_t *const *obj_ptr,\n        anjay_ssid_t ssid,\n        const anjay_dm_oi_attributes_t *attrs,\n        int retval);\nvoid _anjay_mock_dm_expect_instance_reset(\n        anjay_t *anjay,\n        const anjay_dm_object_def_t *const *obj_ptr,\n        anjay_iid_t iid,\n        int retval);\nvoid _anjay_mock_dm_expect_list_instances(\n        anjay_t *anjay,\n        const anjay_dm_object_def_t *const *obj_ptr,\n        int retval,\n        const anjay_iid_t *iid_array);\nvoid _anjay_mock_dm_expect_instance_create(\n        anjay_t *anjay,\n        const anjay_dm_object_def_t *const *obj_ptr,\n        anjay_iid_t iid,\n        int retval);\nvoid _anjay_mock_dm_expect_instance_remove(\n        anjay_t *anjay,\n        const anjay_dm_object_def_t *const *obj_ptr,\n        anjay_iid_t iid,\n        int retval);\nvoid _anjay_mock_dm_expect_instance_read_default_attrs(\n        anjay_t *anjay,\n        const anjay_dm_object_def_t *const *obj_ptr,\n        anjay_iid_t iid,\n        anjay_ssid_t ssid,\n        int retval,\n        const anjay_dm_oi_attributes_t *attrs);\nvoid _anjay_mock_dm_expect_instance_write_default_attrs(\n        anjay_t *anjay,\n        const anjay_dm_object_def_t *const *obj_ptr,\n        anjay_iid_t iid,\n        anjay_ssid_t ssid,\n        const anjay_dm_oi_attributes_t *attrs,\n        int retval);\nvoid _anjay_mock_dm_expect_list_resources(\n        anjay_t *anjay,\n        const anjay_dm_object_def_t *const *obj_ptr,\n        anjay_iid_t iid,\n        int retval,\n        const anjay_mock_dm_res_entry_t *res_array);\nvoid _anjay_mock_dm_expect_resource_read(\n        anjay_t *anjay,\n        const anjay_dm_object_def_t *const *obj_ptr,\n        anjay_iid_t iid,\n        anjay_rid_t rid,\n        anjay_riid_t riid,\n        int retval,\n        const anjay_mock_dm_data_t *data);\nvoid _anjay_mock_dm_expect_resource_write(\n        anjay_t *anjay,\n        const anjay_dm_object_def_t *const *obj_ptr,\n        anjay_iid_t iid,\n        anjay_rid_t rid,\n        anjay_riid_t riid,\n        const anjay_mock_dm_data_t *data,\n        int retval);\nvoid _anjay_mock_dm_expect_resource_execute(\n        anjay_t *anjay,\n        const anjay_dm_object_def_t *const *obj_ptr,\n        anjay_iid_t iid,\n        anjay_rid_t rid,\n        const anjay_mock_dm_execute_data_t *data,\n        int retval);\nvoid _anjay_mock_dm_expect_resource_reset(\n        anjay_t *anjay,\n        const anjay_dm_object_def_t *const *obj_ptr,\n        anjay_iid_t iid,\n        anjay_rid_t rid,\n        int retval);\nvoid _anjay_mock_dm_expect_list_resource_instances(\n        anjay_t *anjay,\n        const anjay_dm_object_def_t *const *obj_ptr,\n        anjay_iid_t iid,\n        anjay_rid_t rid,\n        int retval,\n        const anjay_riid_t *riid_array);\nvoid _anjay_mock_dm_expect_resource_read_attrs(\n        anjay_t *anjay,\n        const anjay_dm_object_def_t *const *obj_ptr,\n        anjay_iid_t iid,\n        anjay_rid_t rid,\n        anjay_ssid_t ssid,\n        int retval,\n        const anjay_dm_r_attributes_t *attrs);\nvoid _anjay_mock_dm_expect_resource_write_attrs(\n        anjay_t *anjay,\n        const anjay_dm_object_def_t *const *obj_ptr,\n        anjay_iid_t iid,\n        anjay_rid_t rid,\n        anjay_ssid_t ssid,\n        const anjay_dm_r_attributes_t *attrs,\n        int retval);\nvoid _anjay_mock_dm_expect_resource_instance_read_attrs(\n        anjay_t *anjay,\n        const anjay_dm_object_def_t *const *obj_ptr,\n        anjay_iid_t iid,\n        anjay_rid_t rid,\n        anjay_riid_t riid,\n        anjay_ssid_t ssid,\n        int retval,\n        const anjay_dm_r_attributes_t *attrs);\nvoid _anjay_mock_dm_expect_resource_instance_write_attrs(\n        anjay_t *anjay,\n        const anjay_dm_object_def_t *const *obj_ptr,\n        anjay_iid_t iid,\n        anjay_rid_t rid,\n        anjay_riid_t riid,\n        anjay_ssid_t ssid,\n        const anjay_dm_r_attributes_t *attrs,\n        int retval);\nvoid _anjay_mock_dm_expect_transaction_begin(\n        anjay_t *anjay,\n        const anjay_dm_object_def_t *const *obj_ptr,\n        int retval);\nvoid _anjay_mock_dm_expect_transaction_validate(\n        anjay_t *anjay,\n        const anjay_dm_object_def_t *const *obj_ptr,\n        int retval);\nvoid _anjay_mock_dm_expect_transaction_commit(\n        anjay_t *anjay,\n        const anjay_dm_object_def_t *const *obj_ptr,\n        int retval);\nvoid _anjay_mock_dm_expect_transaction_rollback(\n        anjay_t *anjay,\n        const anjay_dm_object_def_t *const *obj_ptr,\n        int retval);\n#ifdef ANJAY_WITH_LWM2M12\nvoid _anjay_mock_dm_expect_resource_instance_remove(\n        anjay_t *anjay,\n        const anjay_dm_object_def_t *const *obj_ptr,\n        anjay_iid_t iid,\n        anjay_rid_t rid,\n        anjay_riid_t riid,\n        int retval);\n#endif // ANJAY_WITH_LWM2M12\nvoid _anjay_mock_dm_expect_clean(void);\nvoid _anjay_mock_dm_expected_commands_clear(void);\n\n#endif /* ANJAY_TEST_MOCK_DM_H */\n"
  },
  {
    "path": "tests/utils/utils.h",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef ANJAY_TEST_UTILS_H\n#define ANJAY_TEST_UTILS_H\n\n#include <avsystem/commons/avs_unit_mocksock.h>\n#include <math.h>\n\n#define SCOPED_PTR(Type, Deleter) __attribute__((__cleanup__(Deleter))) Type *\n\nstatic inline void _anjay_mocksock_expect_stats_zero(avs_net_socket_t *socket) {\n    avs_unit_mocksock_expect_shutdown(socket);\n    avs_unit_mocksock_expect_get_opt(socket, AVS_NET_SOCKET_OPT_BYTES_SENT,\n                                     (avs_net_socket_opt_value_t) {\n                                         .bytes_sent = 0\n                                     });\n    avs_unit_mocksock_expect_get_opt(socket,\n                                     AVS_NET_SOCKET_OPT_BYTES_RECEIVED,\n                                     (avs_net_socket_opt_value_t) {\n                                         .bytes_received = 0\n                                     });\n}\n\n#endif /* ANJAY_TEST_UTILS_H */\n"
  },
  {
    "path": "tools/analyze",
    "content": "#!/usr/bin/env bash\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nset -e\n\n. \"$(dirname \"$0\")/utils.sh\"\nROOT_DIR=\"$(dirname \"$(dirname \"$(canonicalize \"$0\")\")\")\"\n\n. \"$ROOT_DIR/tools/utils.sh\"\n\nOUTPUT_DIR=\"$ROOT_DIR/scan-build-result\"\nNUM_JOBS=\"$(num_processors)\"\nSCAN_BUILD=\"$(canonicalize \"$(which scan-build)\")\"\n\nprint_help() {\n    cat <<EOF >&2\nNAME\n    $0 - Run scan-build analysis tool on the repository.\n\nSYNOPSIS\n    $0 [ OPTIONS... ]\n\nOPTIONS\n    -j, --jobs N\n            - run build in N parallel jobs.\n              Default: $NUM_JOBS\n    -o, --output-dir DIR\n            - save issue report in DIR.\n              Default: $OUTPUT_DIR\n    -s, --scan-build PATH\n            - path to the scan-build executable.\n              Default: $SCAN_BUILD\n    -h, --help\n            - print this message and exit.\nEOF\n}\n\nwhile [[ \"$#\" > 0 ]]; do\n    case \"$1\" in\n        --jobs|-j)         shift; NUM_JOBS=\"$1\"; ;;\n        --output-dir|-o)   shift; OUTPUT_DIR=\"$(mkdir -p \"$1\"; canonicalize \"$1\")\" ;;\n        --scan-build|-s)   shift; SCAN_BUILD=\"$(canonicalize \"$1\")\" ;;\n        --help|-h|*)\n            print_help\n            exit 0\n            ;;\n    esac\n\n    shift\ndone\n\n[[ \"$SCAN_BUILD\" ]] || die \"scan-build not found, use --scan-build\"\n\ncat <<EOF\nUsing configuration:\n- output dir: $OUTPUT_DIR\n- jobs: $NUM_JOBS\n- scan-build: $SCAN_BUILD\n\nEOF\n\nTMPDIR=\"$(mktemp -d)\"\natexit \"rm -rf '$TMPDIR'\"\n\nanalyze() {\n    \"$SCAN_BUILD\" --status-bugs -no-failure-reports -o \"$OUTPUT_DIR\" \\\n        \"$ROOT_DIR/devconfig -DSCAN_BUILD_BINARY=\"$SCAN_BUILD\" -DWITH_NESTED_FUNCTION_MUTEX_LOCKS=OFF -DWITH_AVS_CRYPTO_PKI_ENGINE=OFF\" \\\n        && \"$SCAN_BUILD\" --status-bugs -o \"$OUTPUT_DIR\" make -j\"$NUM_JOBS\"\n}\n\n\ncd \"$TMPDIR\"\nif ! analyze; then\n    warn \"scan-build found some issues, report: $OUTPUT_DIR\"\n\n    # Some browsers (e.g. chromium) have problems starting up when CWD does not\n    # exist. Since $TMPDIR is removed as soon as the script exits, and xdg-open\n    # returns early before the browser even starts, it is possible that CWD\n    # is gone at the point browser starts.\n    # To avoid that problem, we change CWD to something existing before calling\n    # xdg-open.\n    cd \"$HOME\"\n\n    # open newest index.html\n    REPORT=\"file://$(find \"$OUTPUT_DIR\" -name index.html | xargs ls -t | head -1)\"\n    if command -v xdg-open > /dev/null; then\n        xdg-open \"$REPORT\"\n    else\n        open \"$REPORT\"\n    fi\nfi\n"
  },
  {
    "path": "tools/anjay_codegen.py",
    "content": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nimport datetime\nimport argparse\nimport collections\nimport textwrap\nimport operator\nimport sys\nimport re\nfrom xml.etree import ElementTree\nfrom xml.etree.ElementTree import Element\nfrom typing import Mapping, Tuple, Optional\nfrom jinja2 import Environment\n\nC_DYNAMIC_INST_TEMPLATE = \"\"\"\\\n/**\n * Generated by anjay_codegen.py on {{ date_time }}\n *\n * LwM2M Object: {{ obj.name }}\n * ID: {{ obj.oid }}, URN: {{ obj.urn }}, {{ obj.mandatory_str }}, {{ obj.multiple_str }}\n *\n * {{ obj.description }}\n */\n#include <assert.h>\n#include <stdbool.h>\n\n#include <anjay/anjay.h>\n#include <avsystem/commons/avs_defs.h>\n{% if obj.multiple %}\n#include <avsystem/commons/avs_list.h>\n{% endif %}\n#include <avsystem/commons/avs_memory.h>\n\n{% for res in obj.resources %}\n/**\n * {{ res.name }}: {{ res.operations }}, {{ res.multiple_str }}, {{ res.mandatory_str }}\n * type: {{ res.type }}, range: {{ res.range_enumeration }}, unit: {{ res.units }}\n{% if res.description %}\n * {{ res.description }}\n{% endif %}\n */\n#define {{ res.name_upper }} {{ res.rid }}\n\n{% endfor %}\n{% if obj.multiple %}\ntypedef struct {{ obj_inst_tag }} {\n    anjay_iid_t iid;\n\n    // TODO: instance state\n} {{ obj_inst_type }};\n\n{% endif %}\ntypedef struct {{ obj_repr_tag }} {\n    const anjay_dm_object_def_t *def;\n{% if obj.multiple %}\n    AVS_LIST({{ obj_name_snake }}_instance_t) instances;\n{% endif %}\n\n    // TODO: object state\n} {{ obj_repr_type }};\n\nstatic inline {{ obj_repr_type }} *\nget_obj(const anjay_dm_object_def_t *const *obj_ptr) {\n    assert(obj_ptr);\n    return AVS_CONTAINER_OF(obj_ptr, {{ obj_repr_type }}, def);\n}\n\n{% if obj.multiple %}\nstatic {{ obj_inst_type }} *find_instance(const {{ obj_repr_type }} *obj,\n{{ \" \" * (obj_inst_type|length + 22) }} anjay_iid_t iid) {\n    AVS_LIST({{ obj_inst_type }}) it;\n    AVS_LIST_FOREACH(it, obj->instances) {\n        if (it->iid == iid) {\n            return it;\n        } else if (it->iid > iid) {\n            break;\n        }\n    }\n\n    return NULL;\n}\n\nstatic int list_instances(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr,\n                          anjay_dm_list_ctx_t *ctx) {\n    (void) anjay;\n\n    AVS_LIST({{ obj_inst_type }}) it;\n    AVS_LIST_FOREACH(it, get_obj(obj_ptr)->instances) {\n        anjay_dm_emit(ctx, it->iid);\n    }\n\n    return 0;\n}\n\nstatic int init_instance({{ obj_inst_type }} *inst, anjay_iid_t iid) {\n    assert(iid != ANJAY_ID_INVALID);\n\n    inst->iid = iid;\n    // TODO: instance init\n\n    // TODO: return 0 on success, negative value on failure\n    return 0;\n}\n\nstatic void release_instance({{ obj_inst_type }} *inst) {\n    // TODO: instance cleanup\n    (void) inst;\n}\n\nstatic {{ obj_inst_type }} *\nadd_instance({{ obj_repr_type }} *obj, anjay_iid_t iid) {\n    assert(find_instance(obj, iid) == NULL);\n\n    AVS_LIST({{ obj_inst_type }}) created =\n            AVS_LIST_NEW_ELEMENT({{ obj_inst_type }});\n    if (!created) {\n        return NULL;\n    }\n\n    int result = init_instance(created, iid);\n    if (result) {\n        AVS_LIST_CLEAR(&created);\n        return NULL;\n    }\n\n    AVS_LIST({{ obj_inst_type }}) *ptr;\n    AVS_LIST_FOREACH_PTR(ptr, &obj->instances) {\n        if ((*ptr)->iid > created->iid) {\n            break;\n        }\n    }\n\n    AVS_LIST_INSERT(ptr, created);\n    return created;\n}\n\nstatic int instance_create(anjay_t *anjay,\n                           const anjay_dm_object_def_t *const *obj_ptr,\n                           anjay_iid_t iid) {\n    (void) anjay;\n    {{ obj_repr_type }} *obj = get_obj(obj_ptr);\n\n    return add_instance(obj, iid) ? 0 : ANJAY_ERR_INTERNAL;\n}\n\nstatic int instance_remove(anjay_t *anjay,\n                           const anjay_dm_object_def_t *const *obj_ptr,\n                           anjay_iid_t iid) {\n    (void) anjay;\n    {{ obj_repr_type }} *obj = get_obj(obj_ptr);\n\n    AVS_LIST({{ obj_inst_type }}) *it;\n    AVS_LIST_FOREACH_PTR(it, &obj->instances) {\n        if ((*it)->iid == iid) {\n            release_instance(*it);\n            AVS_LIST_DELETE(it);\n            return 0;\n        } else if ((*it)->iid > iid) {\n            break;\n        }\n    }\n\n    assert(0);\n    return ANJAY_ERR_NOT_FOUND;\n}\n\n{% endif %}\n{% if obj.needs_instance_reset_handler %}\nstatic int instance_reset(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr,\n                          anjay_iid_t iid) {\n    (void) anjay;\n{% if not obj.multiple %}\n    (void) iid;\n{% endif %}\n\n    {{ obj_repr_type }} *obj = get_obj(obj_ptr);\n{% if obj.multiple %}\n    {{ obj_inst_type }} *inst = find_instance(obj, iid);\n    assert(inst);\n{% else %}\n    assert(iid == 0);\n{% endif %}\n\n    // TODO: instance reset\n    return 0;\n}\n\n{% endif %}\nstatic int list_resources(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr,\n                          anjay_iid_t iid,\n                          anjay_dm_resource_list_ctx_t *ctx) {\n    (void) anjay;\n    (void) obj_ptr;\n    (void) iid;\n\n{% for res in obj.resources %}\n    anjay_dm_emit_res(ctx, {{ res.name_upper }},\n                      {{ res.kind_enum }}, ANJAY_DM_RES_PRESENT);\n{% endfor %}\n    return 0;\n}\n\n{% if obj.has_any_readable_resources %}\nstatic int resource_read(anjay_t *anjay,\n                         const anjay_dm_object_def_t *const *obj_ptr,\n                         anjay_iid_t iid,\n                         anjay_rid_t rid,\n                         anjay_riid_t riid,\n                         anjay_output_ctx_t *ctx) {\n    (void) anjay;\n{% if not obj.multiple %}\n    (void) iid;\n{% endif %}\n\n    {{ obj_repr_type }} *obj = get_obj(obj_ptr);\n{% if obj.multiple %}\n    {{ obj_inst_type }} *inst = find_instance(obj, iid);\n    assert(inst);\n{% else %}\n    assert(iid == 0);\n{% endif %}\n\n    switch (rid) {\n{% for res in obj.resources %}\n{% if 'R' in res.operations %}\n    case {{ res.name_upper }}:{{ res.read_handler|indent(4) }}\n\n{% endif %}\n{% endfor %}\n    default:\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\n{% endif %}\n{% if obj.has_any_writable_resources %}\nstatic int resource_write(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr,\n                          anjay_iid_t iid,\n                          anjay_rid_t rid,\n                          anjay_riid_t riid,\n                          anjay_input_ctx_t *ctx) {\n    (void) anjay;\n{% if not obj.multiple %}\n    (void) iid;\n{% endif %}\n\n    {{ obj_repr_type }} *obj = get_obj(obj_ptr);\n{% if obj.multiple %}\n    {{ obj_inst_type }} *inst = find_instance(obj, iid);\n    assert(inst);\n{% else %}\n    assert(iid == 0);\n{% endif %}\n\n    switch (rid) {\n{% for res in obj.resources %}\n{% if 'W' in res.operations %}\n    case {{ res.name_upper }}:{{ res.write_handler|indent(4) }}\n\n{% endif %}\n{% endfor %}\n    default:\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\n{% endif %}\n{% if obj.has_any_executable_resources %}\nstatic int resource_execute(anjay_t *anjay,\n                            const anjay_dm_object_def_t *const *obj_ptr,\n                            anjay_iid_t iid,\n                            anjay_rid_t rid,\n                            anjay_execute_ctx_t *arg_ctx) {\n    (void) anjay;\n{% if not obj.multiple %}\n    (void) iid;\n{% endif %}\n    (void) arg_ctx;\n\n    {{ obj_repr_type }} *obj = get_obj(obj_ptr);\n{% if obj.multiple %}\n    {{ obj_inst_type }} *inst = find_instance(obj, iid);\n    assert(inst);\n{% else %}\n    assert(iid == 0);\n{% endif %}\n\n    switch (rid) {\n{% for res in obj.resources %}\n{% if 'E' in res.operations %}\n    case {{ res.name_upper }}:\n        return ANJAY_ERR_NOT_IMPLEMENTED; // TODO\n\n{% endif %}\n{% endfor %}\n    default:\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\n{% endif %}\n{% if obj.has_any_multiple_writable_resources %}\nstatic int resource_reset(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr,\n                          anjay_iid_t iid,\n                          anjay_rid_t rid) {\n    (void) anjay;\n{% if not obj.multiple %}\n    (void) iid;\n{% endif %}\n\n    {{ obj_repr_type }} *obj = get_obj(obj_ptr);\n{% if obj.multiple %}\n    {{ obj_inst_type }} *inst = find_instance(obj, iid);\n    assert(inst);\n{% else %}\n    assert(iid == 0);\n{% endif %}\n\n    switch (rid) {\n{% for res in obj.resources %}\n{% if res.multiple and 'W' in res.operations %}\n    case {{ res.name_upper }}:\n        return ANJAY_ERR_NOT_IMPLEMENTED; // TODO: remove all Resource Instances\n\n{% endif %}\n{% endfor %}\n    default:\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\nstatic int resource_instance_remove(anjay_t *anjay,\n                                    const anjay_dm_object_def_t *const *obj_ptr,\n                                    anjay_iid_t iid,\n                                    anjay_rid_t rid,\n                                    anjay_riid_t riid) {\n    // NOTE: This handler can be removed if the client application\n    // does not need to support LwM2M 1.2.\n\n    (void) anjay;\n{% if not obj.multiple %}\n    (void) iid;\n{% endif %}\n\n    {{ obj_repr_type }} *obj = get_obj(obj_ptr);\n{% if obj.multiple %}\n    {{ obj_inst_type }} *inst = find_instance(obj, iid);\n    assert(inst);\n{% else %}\n    assert(iid == 0);\n{% endif %}\n\n    switch (rid) {\n{% for res in obj.resources %}\n{% if res.multiple and 'W' in res.operations %}\n    case {{ res.name_upper }}:\n        // TODO: extract and remove Resource Instance\n        return ANJAY_ERR_NOT_IMPLEMENTED;\n\n{% endif %}\n{% endfor %}\n    default:\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\n{% endif %}\n{% if obj.has_any_multiple_resources %}\nstatic int list_resource_instances(anjay_t *anjay,\n                                   const anjay_dm_object_def_t *const *obj_ptr,\n                                   anjay_iid_t iid,\n                                   anjay_rid_t rid,\n                                   anjay_dm_list_ctx_t *ctx) {\n    (void) anjay;\n{% if not obj.multiple %}\n    (void) iid;\n{% endif %}\n\n    {{ obj_repr_type }} *obj = get_obj(obj_ptr);\n{% if obj.multiple %}\n    {{ obj_inst_type }} *inst = find_instance(obj, iid);\n    assert(inst);\n{% else %}\n    assert(iid == 0);\n{% endif %}\n\n    switch (rid) {\n{% for res in obj.resources %}\n{% if res.multiple %}\n    case {{ res.name_upper }}:\n        // anjay_dm_emit(ctx, ...); // TODO\n        return 0;\n\n{% endif %}\n{% endfor %}\n    default:\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\n{% endif %}\nstatic const anjay_dm_object_def_t OBJ_DEF = {\n    .oid = {{ obj.oid }},\n{% if obj.version not in ['', '1.0'] %}\n    .version = \"{{ obj.version }}\",\n{% endif %}\n    .handlers = {\n{% for handler in handlers %}\n{% if handler is string %}\n{{ '' if handler == '' else '        ' + handler }}\n{% else %}\n        {{ '.%s = %s' % handler }}{{ \"\" if loop.last else \",\" }}\n{% endif %}\n{% endfor %}\n    }\n};\n\nconst anjay_dm_object_def_t **{{ obj_name_snake }}_object_create(void) {\n    {{ obj_repr_type }} *obj = ({{ obj_repr_type }} *) avs_calloc(1, sizeof({{ obj_repr_type }}));\n    if (!obj) {\n        return NULL;\n    }\n    obj->def = &OBJ_DEF;\n\n    // TODO: object init\n\n    return &obj->def;\n}\n\nvoid {{ obj_name_snake }}_object_release(const anjay_dm_object_def_t **def) {\n    if (def) {\n        {{ obj_repr_type }} *obj = get_obj(def);\n{% if obj.multiple %}\n        AVS_LIST_CLEAR(&obj->instances) {\n            release_instance(obj->instances);\n        }\n{% endif %}\n\n        // TODO: object cleanup\n\n        avs_free(obj);\n    }\n}\n\"\"\"\n\nC_STATIC_INST_TEMPLATE = \"\"\"\\\n/**\n * Generated by anjay_codegen.py on {{ date_time }}\n *\n * LwM2M Object: {{ obj.name }}\n * ID: {{ obj.oid }}, URN: {{ obj.urn }}, {{ obj.mandatory_str }}, {{ obj.multiple_str }}\n *\n * {{ obj.description }}\n */\n#include <assert.h>\n#include <stdbool.h>\n\n#include <anjay/anjay.h>\n#include <avsystem/commons/avs_defs.h>\n#include <avsystem/commons/avs_memory.h>\n\n{% for res in obj.resources %}\n/**\n * {{ res.name }}: {{ res.operations }}, {{ res.multiple_str }}, {{ res.mandatory_str }}\n * type: {{ res.type }}, range: {{ res.range_enumeration }}, unit: {{ res.units }}\n{% if res.description %}\n * {{ res.description }}\n{% endif %}\n */\n#define {{ res.name_upper }} {{ res.rid }}\n\n{% endfor %}\n{% if obj.multiple %}\ntypedef struct {{ obj_inst_tag }} {\n    // TODO: instance state\n} {{ obj_inst_type }};\n\n{% endif %}\ntypedef struct {{ obj_repr_tag }} {\n    const anjay_dm_object_def_t *def;\n{% if obj.multiple %}\n    {{ obj_inst_type }} instances[{{ instances_number }}];\n{% endif %}\n\n    // TODO: object state\n} {{ obj_repr_type }};\n\nstatic inline {{ obj_repr_type }} *\nget_obj(const anjay_dm_object_def_t *const *obj_ptr) {\n    assert(obj_ptr);\n    return AVS_CONTAINER_OF(obj_ptr, {{ obj_repr_type }}, def);\n}\n\n{% if obj.multiple %}\nstatic int list_instances(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr,\n                          anjay_dm_list_ctx_t *ctx) {\n    (void) anjay;\n\n    {{ obj_repr_type }} *obj = get_obj(obj_ptr);\n    for (anjay_iid_t iid = 0; iid < AVS_ARRAY_SIZE(obj->instances); iid++) {\n        anjay_dm_emit(ctx, iid);\n    }\n\n    return 0;\n}\n\n{% endif %}\n{% if obj.needs_instance_reset_handler %}\nstatic int instance_reset(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr,\n                          anjay_iid_t iid) {\n    (void) anjay;\n{% if not obj.multiple %}\n    (void) iid;\n{% endif %}\n\n    {{ obj_repr_type }} *obj = get_obj(obj_ptr);\n{% if obj.multiple %}\n    assert(iid < AVS_ARRAY_SIZE(obj->instances));\n    {{ obj_inst_type }} *inst = &obj->instances[iid];\n{% else %}\n    assert(iid == 0);\n{% endif %}\n\n    // TODO: instance reset\n\n    // TODO: return 0 on success, negative value on failure\n    return 0;\n}\n\n{% endif %}\nstatic int list_resources(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr,\n                          anjay_iid_t iid,\n                          anjay_dm_resource_list_ctx_t *ctx) {\n    (void) anjay;\n    (void) obj_ptr;\n    (void) iid;\n\n{% for res in obj.resources %}\n    anjay_dm_emit_res(ctx, {{ res.name_upper }},\n                      {{ res.kind_enum }}, ANJAY_DM_RES_PRESENT);\n{% endfor %}\n    return 0;\n}\n\n{% if obj.has_any_readable_resources %}\nstatic int resource_read(anjay_t *anjay,\n                         const anjay_dm_object_def_t *const *obj_ptr,\n                         anjay_iid_t iid,\n                         anjay_rid_t rid,\n                         anjay_riid_t riid,\n                         anjay_output_ctx_t *ctx) {\n    (void) anjay;\n{% if not obj.multiple %}\n    (void) iid;\n{% endif %}\n\n    {{ obj_repr_type }} *obj = get_obj(obj_ptr);\n{% if obj.multiple %}\n    assert(iid < AVS_ARRAY_SIZE(obj->instances));\n    {{ obj_inst_type }} *inst = &obj->instances[iid];\n{% else %}\n    assert(iid == 0);\n{% endif %}\n\n    switch (rid) {\n{% for res in obj.resources %}\n{% if 'R' in res.operations %}\n    case {{ res.name_upper }}:{{ res.read_handler|indent(4) }}\n\n{% endif %}\n{% endfor %}\n    default:\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\n{% endif %}\n{% if obj.has_any_writable_resources %}\nstatic int resource_write(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr,\n                          anjay_iid_t iid,\n                          anjay_rid_t rid,\n                          anjay_riid_t riid,\n                          anjay_input_ctx_t *ctx) {\n    (void) anjay;\n{% if not obj.multiple %}\n    (void) iid;\n{% endif %}\n\n    {{ obj_repr_type }} *obj = get_obj(obj_ptr);\n{% if obj.multiple %}\n    assert(iid < AVS_ARRAY_SIZE(obj->instances));\n    {{ obj_inst_type }} *inst = &obj->instances[iid];\n{% else %}\n    assert(iid == 0);\n{% endif %}\n\n    switch (rid) {\n{% for res in obj.resources %}\n{% if 'W' in res.operations %}\n    case {{ res.name_upper }}:{{ res.write_handler|indent(4) }}\n\n{% endif %}\n{% endfor %}\n    default:\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\n{% endif %}\n{% if obj.has_any_executable_resources %}\nstatic int resource_execute(anjay_t *anjay,\n                            const anjay_dm_object_def_t *const *obj_ptr,\n                            anjay_iid_t iid,\n                            anjay_rid_t rid,\n                            anjay_execute_ctx_t *arg_ctx) {\n    (void) anjay;\n{% if not obj.multiple %}\n    (void) iid;\n{% endif %}\n    (void) arg_ctx;\n\n    {{ obj_repr_type }} *obj = get_obj(obj_ptr);\n{% if obj.multiple %}\n    assert(iid < AVS_ARRAY_SIZE(obj->instances));\n    {{ obj_inst_type }} *inst = &obj->instances[iid];\n{% else %}\n    assert(iid == 0);\n{% endif %}\n\n    switch (rid) {\n{% for res in obj.resources %}\n{% if 'E' in res.operations %}\n    case {{ res.name_upper }}:\n        return ANJAY_ERR_NOT_IMPLEMENTED; // TODO\n\n{% endif %}\n{% endfor %}\n    default:\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\n{% endif %}\n{% if obj.has_any_multiple_writable_resources %}\nstatic int resource_reset(anjay_t *anjay,\n                          const anjay_dm_object_def_t *const *obj_ptr,\n                          anjay_iid_t iid,\n                          anjay_rid_t rid) {\n    (void) anjay;\n{% if not obj.multiple %}\n    (void) iid;\n{% endif %}\n\n    {{ obj_repr_type }} *obj = get_obj(obj_ptr);\n{% if obj.multiple %}\n    assert(iid < AVS_ARRAY_SIZE(obj->instances));\n    {{ obj_inst_type }} *inst = &obj->instances[iid];\n{% else %}\n    assert(iid == 0);\n{% endif %}\n\n    switch (rid) {\n{% for res in obj.resources %}\n{% if res.multiple and 'W' in res.operations %}\n    case {{ res.name_upper }}:\n        return ANJAY_ERR_NOT_IMPLEMENTED; // TODO: remove all Resource Instances\n\n{% endif %}\n{% endfor %}\n    default:\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\nstatic int resource_instance_remove(anjay_t *anjay,\n                                    const anjay_dm_object_def_t *const *obj_ptr,\n                                    anjay_iid_t iid,\n                                    anjay_rid_t rid,\n                                    anjay_riid_t riid) {\n    // NOTE: This handler can be removed if the client application\n    // does not need to support LwM2M 1.2.\n\n    (void) anjay;\n{% if not obj.multiple %}\n    (void) iid;\n{% endif %}\n\n    {{ obj_repr_type }} *obj = get_obj(obj_ptr);\n{% if obj.multiple %}\n    assert(iid < AVS_ARRAY_SIZE(obj->instances));\n    {{ obj_inst_type }} *inst = &obj->instances[iid];\n{% else %}\n    assert(iid == 0);\n{% endif %}\n\n    switch (rid) {\n{% for res in obj.resources %}\n{% if res.multiple and 'W' in res.operations %}\n    case {{ res.name_upper }}:\n        // TODO: extract and remove Resource Instance\n        return ANJAY_ERR_NOT_IMPLEMENTED;\n\n{% endif %}\n{% endfor %}\n    default:\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\n{% endif %}\n{% if obj.has_any_multiple_resources %}\nstatic int list_resource_instances(anjay_t *anjay,\n                                   const anjay_dm_object_def_t *const *obj_ptr,\n                                   anjay_iid_t iid,\n                                   anjay_rid_t rid,\n                                   anjay_dm_list_ctx_t *ctx) {\n    (void) anjay;\n{% if not obj.multiple %}\n    (void) iid;\n{% endif %}\n\n    {{ obj_repr_type }} *obj = get_obj(obj_ptr);\n{% if obj.multiple %}\n    assert(iid < AVS_ARRAY_SIZE(obj->instances));\n    {{ obj_inst_type }} *inst = &obj->instances[iid];\n{% else %}\n    assert(iid == 0);\n{% endif %}\n\n    switch (rid) {\n{% for res in obj.resources %}\n{% if res.multiple %}\n    case {{ res.name_upper }}:\n        // anjay_dm_emit(ctx, ...); // TODO\n        return 0;\n\n{% endif %}\n{% endfor %}\n    default:\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\n{% endif %}\nstatic const anjay_dm_object_def_t OBJ_DEF = {\n    .oid = {{ obj.oid }},\n{% if obj.version not in ['', '1.0'] %}\n    .version = \"{{ obj.version }}\",\n{% endif %}\n    .handlers = {\n{% for handler in handlers %}\n{% if handler is string %}\n{{ '' if handler == '' else '        ' + handler }}\n{% else %}\n        {{ '.%s = %s' % handler }}{{ \"\" if loop.last else \",\" }}\n{% endif %}\n{% endfor %}\n    }\n};\n\nconst anjay_dm_object_def_t **{{ obj_name_snake }}_object_create(void) {\n    {{ obj_repr_type }} *obj = ({{ obj_repr_type }} *) avs_calloc(1, sizeof({{ obj_repr_type }}));\n    if (!obj) {\n        return NULL;\n    }\n    obj->def = &OBJ_DEF;\n\n    // TODO: object init\n\n    return &obj->def;\n}\n\nvoid {{ obj_name_snake }}_object_release(const anjay_dm_object_def_t **def) {\n    if (def) {\n        {{ obj_repr_type }} *obj = get_obj(def);\n\n        // TODO: object cleanup\n\n        avs_free(obj);\n    }\n}\n\"\"\"\n\nCXX_DYNAMIC_INST_TEMPLATE = \"\"\"\\\n/**\n * Generated by anjay_codegen.py on {{ date_time }}\n *\n * LwM2M Object: {{ obj.name }}\n * ID: {{ obj.oid }}, URN: {{ obj.urn }}, {{ obj.mandatory_str }}, {{ obj.multiple_str }}\n *\n * {{ obj.description }}\n */\n#include <algorithm>\n#include <assert.h>\n#include <stdbool.h>\n\n#include <anjay/anjay.h>\n#include <avsystem/commons/avs_defs.h>\n{% if obj.multiple %}\n#include <avsystem/commons/avs_list_cxx.hpp>\n{% endif %}\n\nusing namespace std;\n\nnamespace {\n\n{% for res in obj.resources %}\n/**\n * {{ res.name }}: {{ res.operations }}, {{ res.multiple_str }}, {{ res.mandatory_str }}\n * type: {{ res.type }}, range: {{ res.range_enumeration }}, unit: {{ res.units }}\n{% if res.description %}\n * {{ res.description }}\n{% endif %}\n */\nconstexpr anjay_rid_t {{ res.name_upper }} = {{ res.rid }};\n\n{% endfor %}\n{% if obj.multiple %}\nstruct {{ obj_inst_cxx_type }} {\n    const anjay_iid_t iid;\n    // TODO: instance state\n\n    explicit {{ obj_inst_cxx_type }}(anjay_iid_t assigned_iid) : iid(assigned_iid) {\n        // TODO: instance init\n    }\n\n    ~{{ obj_inst_cxx_type }}() {\n        // TODO: instance cleanup\n    }\n\n    {{ obj_inst_cxx_type }}(const {{ obj_inst_cxx_type }} &) = delete;\n    {{ obj_inst_cxx_type }} &operator=(const {{ obj_inst_cxx_type }} &) = delete;\n};\n\n{% endif %}\nstruct {{ obj_cxx_type }} {\n    const anjay_dm_object_def_t *const def;\n{% if obj.multiple %}\n    avs::List<{{ obj_inst_cxx_type }}> instances;\n{% endif %}\n    // TODO: object state\n\n    {{ obj_cxx_type }}();\n    ~{{ obj_cxx_type }}();\n    {{ obj_cxx_type }}(const {{ obj_cxx_type }} &) = delete;\n    {{ obj_cxx_type }} &operator=(const {{ obj_cxx_type }} &) = delete;\n};\n\ninline {{ obj_cxx_type }} *\nget_obj(const anjay_dm_object_def_t *const *obj_ptr) {\n    assert(obj_ptr);\n    static const {{ obj_cxx_type }} *const FAKE_OBJECT_PTR = nullptr;\n    static const auto DEF_PTR_OFFSET =\n            intptr_t(reinterpret_cast<const char *>(&FAKE_OBJECT_PTR[1].def)\n                     - reinterpret_cast<const char *>(&FAKE_OBJECT_PTR[1]));\n    return reinterpret_cast<{{ obj_cxx_type }} *>(intptr_t(obj_ptr) - DEF_PTR_OFFSET);\n}\n\n{% if obj.multiple %}\n{{ obj_inst_cxx_type }} *find_instance({{ obj_cxx_type }} *obj,\n{{ \" \" * (obj_inst_cxx_type|length + 15) }} anjay_iid_t iid) {\n    for ({{ obj_inst_cxx_type }} &instance : obj->instances) {\n        if (instance.iid == iid) {\n            return &instance;\n        } else if (instance.iid > iid) {\n            break;\n        }\n    }\n\n    return nullptr;\n}\n\nint list_instances(anjay_t *,\n                   const anjay_dm_object_def_t *const *obj_ptr,\n                   anjay_dm_list_ctx_t *ctx) {\n    for (const {{ obj_inst_cxx_type }} &instance : get_obj(obj_ptr)->instances) {\n        anjay_dm_emit(ctx, instance.iid);\n    }\n\n    return 0;\n}\n\n{{ obj_inst_cxx_type }} *\nadd_instance({{ obj_cxx_type }} *obj, anjay_iid_t iid) {\n    auto insert_it = std::find_if(obj->instances.begin(), obj->instances.end(),\n                                  [iid](const {{ obj_inst_cxx_type }} &inst) {\n                                      return inst.iid >= iid;\n                                  });\n    assert(insert_it == obj->instances.end() || insert_it->iid != iid);\n    auto instance_it = obj->instances.emplace(insert_it, iid);\n    if (instance_it == obj->instances.end()) {\n        return NULL;\n    }\n    return &(*instance_it);\n}\n\nint instance_create(anjay_t *,\n                    const anjay_dm_object_def_t *const *obj_ptr,\n                    anjay_iid_t iid) {\n    {{ obj_cxx_type }} *obj = get_obj(obj_ptr);\n\n    return add_instance(obj, iid) ? 0 : ANJAY_ERR_INTERNAL;\n}\n\nint instance_remove(anjay_t *,\n                    const anjay_dm_object_def_t *const *obj_ptr,\n                    anjay_iid_t iid) {\n    {{ obj_cxx_type }} *obj = get_obj(obj_ptr);\n\n    auto erase_it = std::find_if(obj->instances.begin(), obj->instances.end(),\n                                 [iid](const {{ obj_inst_cxx_type }} &inst) {\n                                     return inst.iid == iid;\n                                 });\n    assert(erase_it != obj->instances.end());\n    obj->instances.erase(erase_it);\n    return 0;\n}\n\n{% endif %}\n{% if obj.needs_instance_reset_handler %}\nint instance_reset(anjay_t *,\n                   const anjay_dm_object_def_t *const *obj_ptr,\n                   anjay_iid_t iid) {\n{% if not obj.multiple %}\n    (void) iid;\n{% endif %}\n\n    {{ obj_cxx_type }} *obj = get_obj(obj_ptr);\n{% if obj.multiple %}\n    {{ obj_inst_cxx_type }} *inst = find_instance(obj, iid);\n    assert(inst);\n{% else %}\n    assert(iid == 0);\n{% endif %}\n\n    // TODO: instance reset\n    return 0;\n}\n\n{% endif %}\nint list_resources(anjay_t *,\n                   const anjay_dm_object_def_t *const *,\n                   anjay_iid_t,\n                   anjay_dm_resource_list_ctx_t *ctx) {\n{% for res in obj.resources %}\n    anjay_dm_emit_res(ctx, {{ res.name_upper }},\n                      {{ res.kind_enum }}, ANJAY_DM_RES_PRESENT);\n{% endfor %}\n    return 0;\n}\n\n{% if obj.has_any_readable_resources %}\nint resource_read(anjay_t *,\n                  const anjay_dm_object_def_t *const *obj_ptr,\n                  anjay_iid_t iid,\n                  anjay_rid_t rid,\n                  anjay_riid_t riid,\n                  anjay_output_ctx_t *ctx) {\n{% if not obj.multiple %}\n    (void) iid;\n{% endif %}\n\n    {{ obj_cxx_type }} *obj = get_obj(obj_ptr);\n{% if obj.multiple %}\n    {{ obj_inst_cxx_type }} *inst = find_instance(obj, iid);\n    assert(inst);\n{% else %}\n    assert(iid == 0);\n{% endif %}\n\n    switch (rid) {\n{% for res in obj.resources %}\n{% if 'R' in res.operations %}\n    case {{ res.name_upper }}:{{ res.read_handler|indent(4) }}\n\n{% endif %}\n{% endfor %}\n    default:\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\n{% endif %}\n{% if obj.has_any_writable_resources %}\nint resource_write(anjay_t *,\n                   const anjay_dm_object_def_t *const *obj_ptr,\n                   anjay_iid_t iid,\n                   anjay_rid_t rid,\n                   anjay_riid_t riid,\n                   anjay_input_ctx_t *ctx) {\n{% if not obj.multiple %}\n    (void) iid;\n{% endif %}\n\n    {{ obj_cxx_type }} *obj = get_obj(obj_ptr);\n{% if obj.multiple %}\n    {{ obj_inst_cxx_type }} *inst = find_instance(obj, iid);\n    assert(inst);\n{% else %}\n    assert(iid == 0);\n{% endif %}\n\n    switch (rid) {\n{% for res in obj.resources %}\n{% if 'W' in res.operations %}\n    case {{ res.name_upper }}:{{ res.write_handler|indent(4) }}\n\n{% endif %}\n{% endfor %}\n    default:\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\n{% endif %}\n{% if obj.has_any_executable_resources %}\nint resource_execute(anjay_t *,\n                     const anjay_dm_object_def_t *const *obj_ptr,\n                     anjay_iid_t iid,\n                     anjay_rid_t rid,\n                     anjay_execute_ctx_t *) {\n{% if not obj.multiple %}\n    (void) iid;\n{% endif %}\n\n    {{ obj_cxx_type }} *obj = get_obj(obj_ptr);\n{% if obj.multiple %}\n    {{ obj_inst_cxx_type }} *inst = find_instance(obj, iid);\n    assert(inst);\n{% else %}\n    assert(iid == 0);\n{% endif %}\n\n    switch (rid) {\n{% for res in obj.resources %}\n{% if 'E' in res.operations %}\n    case {{ res.name_upper }}:\n        return ANJAY_ERR_NOT_IMPLEMENTED; // TODO\n\n{% endif %}\n{% endfor %}\n    default:\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\n{% endif %}\n{% if obj.has_any_multiple_writable_resources %}\nint resource_reset(anjay_t *,\n                   const anjay_dm_object_def_t *const *obj_ptr,\n                   anjay_iid_t iid,\n                   anjay_rid_t rid) {\n{% if not obj.multiple %}\n    (void) iid;\n{% endif %}\n\n    {{ obj_cxx_type }} *obj = get_obj(obj_ptr);\n{% if obj.multiple %}\n    {{ obj_inst_cxx_type }} *inst = find_instance(obj, iid);\n    assert(inst);\n{% else %}\n    assert(iid == 0);\n{% endif %}\n\n    switch (rid) {\n{% for res in obj.resources %}\n{% if res.multiple and 'W' in res.operations %}\n    case {{ res.name_upper }}:\n        return ANJAY_ERR_NOT_IMPLEMENTED; // TODO: remove all Resource Instances\n\n{% endif %}\n{% endfor %}\n    default:\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\nint resource_instance_remove(anjay_t *,\n                             const anjay_dm_object_def_t *const *obj_ptr,\n                             anjay_iid_t iid,\n                             anjay_rid_t rid,\n                             anjay_riid_t riid) {\n    // NOTE: This handler can be removed if the client application\n    // does not need to support LwM2M 1.2.\n{% if not obj.multiple %}\n    (void) iid;\n{% endif %}\n\n    {{ obj_cxx_type }} *obj = get_obj(obj_ptr);\n{% if obj.multiple %}\n    {{ obj_inst_cxx_type }} *inst = find_instance(obj, iid);\n    assert(inst);\n{% else %}\n    assert(iid == 0);\n{% endif %}\n\n    switch (rid) {\n{% for res in obj.resources %}\n{% if res.multiple and 'W' in res.operations %}\n    case {{ res.name_upper }}:\n        // TODO: extract and remove Resource Instance\n        return ANJAY_ERR_NOT_IMPLEMENTED;\n\n{% endif %}\n{% endfor %}\n    default:\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\n{% endif %}\n{% if obj.has_any_multiple_resources %}\nint list_resource_instances(anjay_t *,\n                            const anjay_dm_object_def_t *const *obj_ptr,\n                            anjay_iid_t iid,\n                            anjay_rid_t rid,\n                            anjay_dm_list_ctx_t *ctx) {\n{% if not obj.multiple %}\n    (void) iid;\n{% endif %}\n\n    {{ obj_cxx_type }} *obj = get_obj(obj_ptr);\n{% if obj.multiple %}\n    {{ obj_inst_cxx_type }} *inst = find_instance(obj, iid);\n    assert(inst);\n{% else %}\n    assert(iid == 0);\n{% endif %}\n\n    switch (rid) {\n{% for res in obj.resources %}\n{% if res.multiple %}\n    case {{ res.name_upper }}:\n        // anjay_dm_emit(ctx, ...); // TODO\n        return 0;\n\n{% endif %}\n{% endfor %}\n    default:\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\n{% endif %}\nstruct ObjDef : public anjay_dm_object_def_t {\n    ObjDef() : anjay_dm_object_def_t() {\n        oid = {{ obj.oid }};\n{% if obj.version not in ['', '1.0'] %}\n        version = \"{{ obj.version }}\";\n{% endif %}\n\n{% for handler in handlers %}\n{% if handler is string %}\n{{ '' if handler == '' else '        ' + handler }}\n{% else %}\n        {{ 'handlers.%s = %s;' % handler }}\n{% endif %}\n{% endfor %}\n    }\n} const OBJ_DEF;\n\n{{ obj_cxx_type }}::{{ obj_cxx_type }}() : def(&OBJ_DEF) {\n    // TODO: object init\n}\n\n{{ obj_cxx_type }}::~{{ obj_cxx_type }}() {\n    // TODO: object cleanup\n}\n\n} // namespace\n\nconst anjay_dm_object_def_t **{{ obj_name_snake }}_object_create(void) {\n    {{ obj_cxx_type }} *obj = new (nothrow) {{ obj_cxx_type }};\n    if (!obj) {\n        return NULL;\n    }\n    return const_cast<const anjay_dm_object_def_t **>(&obj->def);\n}\n\nvoid {{ obj_name_snake }}_object_release(const anjay_dm_object_def_t **def) {\n    if (def) {\n        delete get_obj(def);\n    }\n}\n\"\"\"\n\nCXX_STATIC_INST_TEMPLATE = \"\"\"\\\n/**\n * Generated by anjay_codegen.py on {{ date_time }}\n *\n * LwM2M Object: {{ obj.name }}\n * ID: {{ obj.oid }}, URN: {{ obj.urn }}, {{ obj.mandatory_str }}, {{ obj.multiple_str }}\n *\n * {{ obj.description }}\n */\n#include <algorithm>\n#include <array>\n#include <assert.h>\n#include <stdbool.h>\n\n#include <anjay/anjay.h>\n#include <avsystem/commons/avs_defs.h>\n\nusing namespace std;\n\nnamespace {\n\n{% for res in obj.resources %}\n/**\n * {{ res.name }}: {{ res.operations }}, {{ res.multiple_str }}, {{ res.mandatory_str }}\n * type: {{ res.type }}, range: {{ res.range_enumeration }}, unit: {{ res.units }}\n{% if res.description %}\n * {{ res.description }}\n{% endif %}\n */\nconstexpr anjay_rid_t {{ res.name_upper }} = {{ res.rid }};\n\n{% endfor %}\n{% if obj.multiple %}\nstruct {{ obj_inst_cxx_type }} {\n    // TODO: instance state\n};\n\n{% endif %}\nstruct {{ obj_cxx_type }} {\n    const anjay_dm_object_def_t *const def;\n{% if obj.multiple %}\n    std::array<{{ obj_inst_cxx_type }}, {{ instances_number }}> instances;\n{% endif %}\n    // TODO: object state\n\n    {{ obj_cxx_type }}();\n    ~{{ obj_cxx_type }}();\n    {{ obj_cxx_type }}(const {{ obj_cxx_type }} &) = delete;\n    {{ obj_cxx_type }} &operator=(const {{ obj_cxx_type }} &) = delete;\n};\n\ninline {{ obj_cxx_type }} *\nget_obj(const anjay_dm_object_def_t *const *obj_ptr) {\n    assert(obj_ptr);\n    static const {{ obj_cxx_type }} *const FAKE_OBJECT_PTR = nullptr;\n    static const auto DEF_PTR_OFFSET =\n            intptr_t(reinterpret_cast<const char *>(&FAKE_OBJECT_PTR[1].def)\n                     - reinterpret_cast<const char *>(&FAKE_OBJECT_PTR[1]));\n    return reinterpret_cast<{{ obj_cxx_type }} *>(intptr_t(obj_ptr) - DEF_PTR_OFFSET);\n}\n\n{% if obj.multiple %}\nint list_instances(anjay_t *,\n                   const anjay_dm_object_def_t *const *obj_ptr,\n                   anjay_dm_list_ctx_t *ctx) {\n    {{ obj_cxx_type }} *obj = get_obj(obj_ptr);\n    for (anjay_iid_t iid = 0; iid < obj->instances.size(); iid++) {\n        anjay_dm_emit(ctx, iid);\n    }\n\n    return 0;\n}\n\n{% endif %}\n{% if obj.needs_instance_reset_handler %}\nint instance_reset(anjay_t *,\n                   const anjay_dm_object_def_t *const *obj_ptr,\n                   anjay_iid_t iid) {\n{% if not obj.multiple %}\n    (void) iid;\n{% endif %}\n\n    {{ obj_cxx_type }} *obj = get_obj(obj_ptr);\n{% if obj.multiple %}\n    assert(iid < obj->instances.size());\n    {{ obj_inst_cxx_type }} &inst = obj->instances[iid];\n{% else %}\n    assert(iid == 0);\n{% endif %}\n\n    // TODO: instance reset\n\n    // TODO: return 0 on success, negative value on failure\n    return 0;\n}\n\n{% endif %}\nint list_resources(anjay_t *,\n                   const anjay_dm_object_def_t *const *,\n                   anjay_iid_t,\n                   anjay_dm_resource_list_ctx_t *ctx) {\n{% for res in obj.resources %}\n    anjay_dm_emit_res(ctx, {{ res.name_upper }},\n                      {{ res.kind_enum }}, ANJAY_DM_RES_PRESENT);\n{% endfor %}\n    return 0;\n}\n\n{% if obj.has_any_readable_resources %}\nint resource_read(anjay_t *,\n                  const anjay_dm_object_def_t *const *obj_ptr,\n                  anjay_iid_t iid,\n                  anjay_rid_t rid,\n                  anjay_riid_t riid,\n                  anjay_output_ctx_t *ctx) {\n{% if not obj.multiple %}\n    (void) iid;\n{% endif %}\n\n    {{ obj_cxx_type }} *obj = get_obj(obj_ptr);\n{% if obj.multiple %}\n    assert(iid < obj->instances.size());\n    {{ obj_inst_cxx_type }} &inst = obj->instances[iid];\n{% else %}\n    assert(iid == 0);\n{% endif %}\n\n    switch (rid) {\n{% for res in obj.resources %}\n{% if 'R' in res.operations %}\n    case {{ res.name_upper }}:{{ res.read_handler|indent(4) }}\n\n{% endif %}\n{% endfor %}\n    default:\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\n{% endif %}\n{% if obj.has_any_writable_resources %}\nint resource_write(anjay_t *,\n                   const anjay_dm_object_def_t *const *obj_ptr,\n                   anjay_iid_t iid,\n                   anjay_rid_t rid,\n                   anjay_riid_t riid,\n                   anjay_input_ctx_t *ctx) {\n{% if not obj.multiple %}\n    (void) iid;\n{% endif %}\n\n    {{ obj_cxx_type }} *obj = get_obj(obj_ptr);\n{% if obj.multiple %}\n    assert(iid < obj->instances.size());\n    {{ obj_inst_cxx_type }} &inst = obj->instances[iid];\n{% else %}\n    assert(iid == 0);\n{% endif %}\n\n    switch (rid) {\n{% for res in obj.resources %}\n{% if 'W' in res.operations %}\n    case {{ res.name_upper }}:{{ res.write_handler|indent(4) }}\n\n{% endif %}\n{% endfor %}\n    default:\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\n{% endif %}\n{% if obj.has_any_executable_resources %}\nint resource_execute(anjay_t *,\n                     const anjay_dm_object_def_t *const *obj_ptr,\n                     anjay_iid_t iid,\n                     anjay_rid_t rid,\n                     anjay_execute_ctx_t *) {\n{% if not obj.multiple %}\n    (void) iid;\n{% endif %}\n\n    {{ obj_cxx_type }} *obj = get_obj(obj_ptr);\n{% if obj.multiple %}\n    assert(iid < obj->instances.size());\n    {{ obj_inst_cxx_type }} &inst = obj->instances[iid];\n{% else %}\n    assert(iid == 0);\n{% endif %}\n\n    switch (rid) {\n{% for res in obj.resources %}\n{% if 'E' in res.operations %}\n    case {{ res.name_upper }}:\n        return ANJAY_ERR_NOT_IMPLEMENTED; // TODO\n\n{% endif %}\n{% endfor %}\n    default:\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\n{% endif %}\n{% if obj.has_any_multiple_writable_resources %}\nint resource_reset(anjay_t *,\n                   const anjay_dm_object_def_t *const *obj_ptr,\n                   anjay_iid_t iid,\n                   anjay_rid_t rid) {\n{% if not obj.multiple %}\n    (void) iid;\n{% endif %}\n\n    {{ obj_cxx_type }} *obj = get_obj(obj_ptr);\n{% if obj.multiple %}\n    assert(iid < obj->instances.size());\n    {{ obj_inst_cxx_type }} &inst = obj->instances[iid];\n{% else %}\n    assert(iid == 0);\n{% endif %}\n\n    switch (rid) {\n{% for res in obj.resources %}\n{% if res.multiple and 'W' in res.operations %}\n    case {{ res.name_upper }}:\n        return ANJAY_ERR_NOT_IMPLEMENTED; // TODO: remove all Resource Instances\n\n{% endif %}\n{% endfor %}\n    default:\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\nint resource_instance_remove(anjay_t *,\n                             const anjay_dm_object_def_t *const *obj_ptr,\n                             anjay_iid_t iid,\n                             anjay_rid_t rid,\n                             anjay_riid_t riid) {\n    // NOTE: This handler can be removed if the client application\n    // does not need to support LwM2M 1.2.\n{% if not obj.multiple %}\n    (void) iid;\n{% endif %}\n\n    {{ obj_cxx_type }} *obj = get_obj(obj_ptr);\n{% if obj.multiple %}\n    assert(iid < obj->instances.size());\n    {{ obj_inst_cxx_type }} &inst = obj->instances[iid];\n{% else %}\n    assert(iid == 0);\n{% endif %}\n\n    switch (rid) {\n{% for res in obj.resources %}\n{% if res.multiple and 'W' in res.operations %}\n    case {{ res.name_upper }}:\n        // TODO: extract and remove Resource Instance\n        return ANJAY_ERR_NOT_IMPLEMENTED;\n\n{% endif %}\n{% endfor %}\n    default:\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\n{% endif %}\n{% if obj.has_any_multiple_resources %}\nint list_resource_instances(anjay_t *,\n                            const anjay_dm_object_def_t *const *obj_ptr,\n                            anjay_iid_t iid,\n                            anjay_rid_t rid,\n                            anjay_dm_list_ctx_t *ctx) {\n{% if not obj.multiple %}\n    (void) iid;\n{% endif %}\n\n    {{ obj_cxx_type }} *obj = get_obj(obj_ptr);\n{% if obj.multiple %}\n    assert(iid < obj->instances.size());\n    {{ obj_inst_cxx_type }} &inst = obj->instances[iid];\n{% else %}\n    assert(iid == 0);\n{% endif %}\n\n    switch (rid) {\n{% for res in obj.resources %}\n{% if res.multiple %}\n    case {{ res.name_upper }}:\n        // anjay_dm_emit(ctx, ...); // TODO\n        return 0;\n\n{% endif %}\n{% endfor %}\n    default:\n        return ANJAY_ERR_METHOD_NOT_ALLOWED;\n    }\n}\n\n{% endif %}\nstruct ObjDef : public anjay_dm_object_def_t {\n    ObjDef() : anjay_dm_object_def_t() {\n        oid = {{ obj.oid }};\n{% if obj.version not in ['', '1.0'] %}\n        version = \"{{ obj.version }}\";\n{% endif %}\n\n{% for handler in handlers %}\n{% if handler is string %}\n{{ '' if handler == '' else '        ' + handler }}\n{% else %}\n        {{ 'handlers.%s = %s;' % handler }}\n{% endif %}\n{% endfor %}\n    }\n} const OBJ_DEF;\n\n{{ obj_cxx_type }}::{{ obj_cxx_type }}() : def(&OBJ_DEF) {\n    // TODO: object init\n}\n\n{{ obj_cxx_type }}::~{{ obj_cxx_type }}() {\n    // TODO: object cleanup\n}\n\n} // namespace\n\nconst anjay_dm_object_def_t **{{ obj_name_snake }}_object_create(void) {\n    {{ obj_cxx_type }} *obj = new (nothrow) {{ obj_cxx_type }};\n    if (!obj) {\n        return NULL;\n    }\n    return const_cast<const anjay_dm_object_def_t **>(&obj->def);\n}\n\nvoid {{ obj_name_snake }}_object_release(const anjay_dm_object_def_t **def) {\n    if (def) {\n        delete get_obj(def);\n    }\n}\n\"\"\"\n\n\nNONALPHANUM_REGEX = re.compile(r'[^a-zA-Z0-9]+')\n\nDIGIT_SPELLINGS = {\n    '0': 'zero',\n    '1': 'one',\n    '2': 'two',\n    '3': 'three',\n    '4': 'four',\n    '5': 'five',\n    '6': 'six',\n    '7': 'seven',\n    '8': 'eight',\n    '9': 'nine'\n}\n\n\ndef _node_text(n: Element) -> str:\n    return (n.text if (n is not None and n.text is not None) else '').strip()\n\n\ndef _sanitize_identifier(n: str) -> str:\n    identifier = NONALPHANUM_REGEX.sub('_', n).strip('_')\n    # Identifiers are not allowed to have a leading digit\n    return DIGIT_SPELLINGS.get(identifier[0], identifier[0]) + identifier[1:]\n\n\nclass ResourceDef(collections.namedtuple('ResourceDef', ['rid', 'name', 'operations', 'multiple', 'mandatory', 'type',\n                                                         'range_enumeration', 'units', 'description'])):\n    @property\n    def mandatory_str(self) -> str:\n        return 'Mandatory' if self.mandatory else 'Optional'\n\n    @property\n    def multiple_str(self):\n        return 'Multiple' if self.multiple else 'Single'\n\n    @property\n    def name_upper(self) -> str:\n        return _sanitize_identifier('RID_' + self.name.upper())\n\n    @property\n    def kind_enum(self) -> str:\n        if self.operations not in {'R', 'W', 'RW', 'E', 'BS_RW'}:\n            raise AssertionError('unexpected operations: ' + self.operations)\n        result = 'ANJAY_DM_RES_' + self.operations\n        if self.multiple:\n            if 'E' in self.operations:\n                raise AssertionError('multiple-instance executable resources are not supported')\n            result += 'M'\n        return result\n\n    @property\n    def read_handler(self) -> Optional[str]:\n        assert 'R' in self.operations\n\n        types = [\n            (('boolean', 'bool'), 'anjay_ret_bool(%s, 0)'),\n            (('integer', 'int'),  'anjay_ret_i32(%s, 0)'),\n            (('float',),          'anjay_ret_double(%s, 0)'),\n            (('corelnk', # TODO T2033\n              'string', 'str'),   'anjay_ret_string(%s, \"\")'),\n            (('opaque',),         'anjay_ret_bytes(%s, \"\", 0)'),\n            (('time',),           'anjay_ret_i64(%s, 0)'),\n            (('objlnk',),         'anjay_ret_objlnk(%s, 0, 0)'),\n            (('unsigned integer',\n              'unsigned int',\n              'unsigned'),        'anjay_ret_u32(%s, 0)')\n        ]\n\n        def get_ret_func(type):\n            for match_types, ret_func in types:\n                if type in match_types:\n                    return ret_func\n            else:\n                raise AssertionError('unexpected type: ' + type)\n\n        if not self.multiple:\n            return textwrap.indent(textwrap.dedent(\"\"\"\n                    assert(riid == ANJAY_ID_INVALID);\n                    return %s; // TODO\"\"\"), '    ') % (get_ret_func(self.type) % ('ctx',))\n        else:\n            return textwrap.indent(textwrap.dedent(\"\"\"\n                    // TODO: extract Resource Instance\n                    return %s; // TODO\"\"\"), '    ') % (get_ret_func(self.type) % ('ctx',))\n\n\n    @property\n    def write_handler(self) -> Optional[Tuple[str, str]]:\n        assert 'W' in self.operations\n\n        types = [\n            (('boolean', 'bool'), 'bool value',      'anjay_get_bool(%s, &value)'),\n            (('integer', 'int'),  'int32_t value',   'anjay_get_i32(%s, &value)'),\n            (('float',),          'double value',    'anjay_get_double(%s, &value)'),\n            (('corelnk', # TODO T2033\n              'string', 'str'),   'char value[256]', 'anjay_get_string(%s, value, sizeof(value))'),\n            (('opaque',),\n                 'uint8_t value[256];\\n'\n                 '    bool finished;\\n'\n                 '    size_t bytes_read',\n                 'anjay_get_bytes(%s,\\n'\n                 '                           &bytes_read,\\n'\n                 '                           &finished,\\n'\n                 '                           value,\\n'\n                 '                           sizeof(value))'),\n            (('time',),           'int64_t value',   'anjay_get_i64(%s, &value)'),\n            (('objlnk',),\n                'anjay_oid_t objlnk_oid;\\n'\n                '    anjay_iid_t objlnk_iid',\n                'anjay_get_objlnk(%s, &objlnk_oid, &objlnk_iid)'),\n            (('unsigned integer',\n              'unsigned int',\n              'unsigned'),        'uint32_t value',  'anjay_get_u32(%s, &value)')\n        ]\n\n        def get_get_func(type):\n            for match_types, alloc_value, get_func in types:\n                if type in match_types:\n                    return alloc_value, get_func\n            else:\n                raise AssertionError('unexpected type: ' + type)\n\n        local_def, get_func = get_get_func(self.type.lower())\n        get_func %= ('ctx',)\n        if not self.multiple:\n            return ' ' + textwrap.dedent(\"\"\"\\\n                    {\n                        assert(riid == ANJAY_ID_INVALID);\n                        %s; // TODO\n                        return %s; // TODO\n                    }\"\"\") % (local_def, get_func)\n        else:\n            return ' ' + textwrap.dedent(\"\"\"\\\n                    {\n                        // TODO: extract Resource Instance\n                        %s; // TODO\n                        return %s; // TODO\n                    }\"\"\") % (local_def, get_func)\n\n    @classmethod\n    def from_etree(cls, res: Element) -> 'ResourceDef':\n        return cls(rid=int(res.get('ID')),\n                   name=_node_text(res.find('Name')),\n                   operations=_node_text(res.find('Operations')).upper()\n                       or 'BS_RW', # no operations = resource modifiable by Bootstrap Server\n                   multiple={'Single': False, 'Multiple': True}[_node_text(res.find('MultipleInstances'))],\n                   mandatory={'Optional': False, 'Mandatory': True}[_node_text(res.find('Mandatory'))],\n                   type=(_node_text(res.find('Type')).lower() or 'N/A'),\n                   range_enumeration=(_node_text(res.find('RangeEnumeration')) or 'N/A'),\n                   units=(_node_text(res.find('Units')) or 'N/A'),\n                   description=textwrap.fill(_node_text(res.find('Description'))).replace('\\n', '\\n * '))\n\n\nclass ObjectDef(collections.namedtuple('ObjectDef',\n                                       ['oid', 'name', 'version', 'description', 'urn', 'multiple', 'mandatory', 'resources'])):\n    @property\n    def name_snake(self) -> str:\n        return _sanitize_identifier(self.name).lower()\n\n    @property\n    def name_pascal(self) -> str:\n        return ''.join(word.capitalize() for word in _sanitize_identifier(self.name).split('_'))\n\n    @property\n    def mandatory_str(self) -> str:\n        return 'Mandatory' if self.mandatory else 'Optional'\n\n    @property\n    def multiple_str(self):\n        return 'Multiple' if self.multiple else 'Single'\n\n    @property\n    def has_any_readable_resources(self) -> bool:\n        return any('R' in res.operations for res in self.resources)\n\n    @property\n    def has_any_writable_resources(self) -> bool:\n        return any('W' in res.operations for res in self.resources)\n\n    @property\n    def has_any_executable_resources(self) -> bool:\n        return any('E' in res.operations for res in self.resources)\n\n    @property\n    def has_any_multiple_resources(self) -> bool:\n        return any(res.multiple for res in self.resources)\n\n    @property\n    def has_any_multiple_writable_resources(self) -> bool:\n        return any((res.multiple and 'W' in res.operations) for res in self.resources)\n\n    @property\n    def needs_instance_reset_handler(self) -> bool:\n        return self.multiple or self.has_any_writable_resources\n\n    @staticmethod\n    def parse_resources(obj: ElementTree):\n        return sorted([ResourceDef.from_etree(item) for item in obj.find('Resources').findall('Item')],\n                       key=operator.attrgetter('rid'))\n\n    @classmethod\n    def from_etree(cls, obj: ElementTree, resources_subset: set) -> 'ObjectDef':\n        resources = ObjectDef.parse_resources(obj)\n        if resources_subset is not None:\n            resources = [ r for r in resources if r.rid in resources_subset ]\n\n        return cls(oid=int(_node_text(obj.find('ObjectID'))),\n                   name=_node_text(obj.find('Name')),\n                   version=_node_text(obj.find('ObjectVersion')),\n                   description=textwrap.fill(_node_text(obj.find('Description1'))).replace('\\n', '\\n * '),\n                   urn=_node_text(obj.find('ObjectURN')),\n                   multiple={'Single': False, 'Multiple': True}[_node_text(obj.find('MultipleInstances'))],\n                   mandatory={'Optional': False, 'Mandatory': True}[_node_text(obj.find('Mandatory'))],\n                   resources=resources)\n\n\ndef generate_object_boilerplate(obj_tree: ElementTree, cxx: bool, instances_number: int, resources_subset: set = None):\n    obj = ObjectDef.from_etree(obj_tree, resources_subset)\n\n    jinja_env = Environment(trim_blocks=True)\n\n    handlers = []\n    if obj.multiple:\n        handlers.append(('list_instances', 'list_instances'))\n        if not instances_number:\n            handlers.append(('instance_create', 'instance_create'))\n            handlers.append(('instance_remove', 'instance_remove'))\n    else:\n        handlers.append(('list_instances', 'anjay_dm_list_instances_SINGLE'))\n\n    if obj.needs_instance_reset_handler:\n        handlers.append(('instance_reset', 'instance_reset'))\n\n    handlers.append('')\n    handlers.append(('list_resources', 'list_resources'))\n    if obj.has_any_readable_resources:\n        handlers.append(('resource_read', 'resource_read'))\n    if obj.has_any_writable_resources:\n        handlers.append(('resource_write', 'resource_write'))\n    if obj.has_any_executable_resources:\n        handlers.append(('resource_execute', 'resource_execute'))\n    if obj.has_any_multiple_writable_resources:\n        handlers.append(('resource_reset', 'resource_reset'))\n    if obj.has_any_multiple_resources:\n        handlers.append(('list_resource_instances', 'list_resource_instances'))\n\n    handlers.append('')\n    handlers.append('// TODO: implement these if transactional write/create is required')\n    handlers.append(('transaction_begin', 'anjay_dm_transaction_NOOP'))\n    handlers.append(('transaction_validate', 'anjay_dm_transaction_NOOP'))\n    handlers.append(('transaction_commit', 'anjay_dm_transaction_NOOP'))\n    handlers.append(('transaction_rollback', 'anjay_dm_transaction_NOOP'))\n\n    if obj.has_any_multiple_writable_resources:\n        handlers.append('')\n        handlers.append(('resource_instance_remove', 'resource_instance_remove'))\n    template_args = dict(\n        obj=obj,\n        handlers=handlers,\n        date_time=datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'),\n        obj_name_snake=obj.name_snake,\n        obj_repr_tag=obj.name_snake + '_object_struct',\n        obj_repr_type=obj.name_snake + '_object_t',\n        obj_inst_tag=obj.name_snake + '_instance_struct',\n        obj_inst_type=obj.name_snake + '_instance_t',\n        obj_cxx_type=obj.name_pascal + 'Object',\n        obj_inst_cxx_type=obj.name_pascal + 'Instance'\n    )\n\n    if instances_number:\n        return (jinja_env.from_string(CXX_STATIC_INST_TEMPLATE if cxx else C_STATIC_INST_TEMPLATE)\n                .render(**template_args,\n                        instances_number=instances_number))\n    else:\n        return (jinja_env.from_string(CXX_DYNAMIC_INST_TEMPLATE if cxx else C_DYNAMIC_INST_TEMPLATE)\n                    .render(**template_args))\n\n\nif __name__ == '__main__':\n    parser = argparse.ArgumentParser(description='Parses an LwM2M object definition XML and generates Anjay object skeleton')\n    parser.add_argument('-i', '--input', help='Input filename or - to read from stdin')\n    parser.add_argument('-o', '--output', default='/dev/stdout', help='Output filename (default: stdout)')\n    parser.add_argument('-x', '--c++', dest='cxx', action='store_true', help='Generate C++ code (default: C)')\n    parser.add_argument('-l', '--list', help='List resources and their names only', action='store_true')\n    parser.add_argument('-r', '--resources', nargs='+', type=int, help='Generate code only for a specific list of resources. If the resource does not exist it is silently ignored.')\n    parser.add_argument('-n', '--instances-number', metavar='{1,2,...,65534}', dest='instances_number', type=int,\n                        help='Number of instances of the generated object. It forces using the template with statically allocated instances. '\n                        'If the object is single instance it is silently ignored.')\n\n    args = parser.parse_args()\n    if args.input == '-':\n        args.input = '/dev/stdin'\n    if args.output == '-':\n        args.output = '/dev/stdout'\n\n    if args.input is None or (args.instances_number is not None and args.instances_number not in range(1, 65535)):\n        parser.print_usage()\n        sys.exit(1)\n\n    with open(args.input) as f:\n        tree = ElementTree.fromstring(f.read())\n        obj = tree.find('Object')\n        if args.list:\n            for r in ObjectDef.parse_resources(obj):\n                print(r.rid, r.name, '(mandatory)' if r.mandatory else '')\n            sys.exit(0)\n\n        boilerplate = generate_object_boilerplate(obj, args.cxx, args.instances_number, args.resources)\n\n    with open(args.output, 'w') as f:\n        print(boilerplate, file=f)\n"
  },
  {
    "path": "tools/anjay_config_log_tool.py",
    "content": "#!/usr/bin/env python3\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\nimport argparse\nimport enum\nimport os\nimport re\nimport sys\n\nPROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))\n\nConfigFileVariableType = enum.Enum('ConfigFileVariableType', 'FLAG VALUE OPT_VALUE')\n\n\ndef default_config_files(project_root=PROJECT_ROOT):\n    return [\n        os.path.join(project_root, 'deps', 'avs_commons', 'include_public', 'avsystem', 'commons',\n                     'avs_commons_config.h.in'),\n        os.path.join(project_root, 'deps', 'avs_coap', 'include_public', 'avsystem', 'coap',\n                     'avs_coap_config.h.in'),\n        os.path.join(project_root, 'include_public', 'anjay', 'anjay_config.h.in')]\n\n\ndef default_config_log_file(project_root=PROJECT_ROOT):\n    return os.path.join(project_root, 'src', 'anjay_config_log.h')\n\n\ndef enumerate_variables(config_files):\n    result = {}\n    origins = {}\n\n    def emit(config_file, name, value):\n        if name in result:\n            assert name in origins\n            if result[name] != value:\n                raise ValueError('Variable %s from %s conflicts with one from %s' % (\n                    name, config_file, origins[name]))\n        result[name] = value\n        origins[name] = config_file\n\n    for config_file in config_files:\n        with open(config_file) as f:\n            for line in f:\n                stripped = line.strip()\n                match = re.match(r'#[ \\t]*define[ \\t]+([A-Za-z_0-9]+)[ \\t]+[^@]*@([A-Za-z_0-9]+)@',\n                                 stripped)\n                if match:\n                    emit(config_file, match.group(1), ConfigFileVariableType.VALUE)\n                    continue\n\n                match = re.match(\n                    r'#[ \\t]*cmakedefine[ \\t]+([A-Za-z_0-9]+)[ \\t]+[^@]*@([A-Za-z_0-9]+)@',\n                    stripped)\n                if match:\n                    emit(config_file, match.group(1), ConfigFileVariableType.OPT_VALUE)\n                    continue\n\n                match = re.match(r'#[ \\t]*cmakedefine[ \\t]+([A-Za-z_0-9]+)', stripped)\n                if match:\n                    emit(config_file, match.group(1), ConfigFileVariableType.FLAG)\n                    continue\n\n                if any(re.search(pattern, stripped) for pattern in\n                       (r'^[ \\t]*#[ \\t]*cmakedefine', r'^[ \\t]*#.*@[A-Za-z_0-9]+@')):\n                    raise ValueError('Found unloggable line in %s: %s' % (config_file, stripped))\n\n    return result\n\n\ndef _generate_body(variables):\n    lines = []\n    for name, type in sorted(variables.items()):\n        if type == ConfigFileVariableType.FLAG:\n            lines.append('#ifdef ' + name)\n            lines.append('    _anjay_log(anjay, TRACE, \"%s = ON\");' % (name,))\n            lines.append('#else // ' + name)\n            lines.append('    _anjay_log(anjay, TRACE, \"%s = OFF\");' % (name,))\n            lines.append('#endif // ' + name)\n        elif type == ConfigFileVariableType.VALUE:\n            lines.append(\n                '    _anjay_log(anjay, TRACE, \"%s = \" AVS_QUOTE_MACRO(%s));' % (name, name))\n        elif type == ConfigFileVariableType.OPT_VALUE:\n            lines.append('#ifdef ' + name)\n            lines.append(\n                '    _anjay_log(anjay, TRACE, \"%s = \" AVS_QUOTE_MACRO(%s));' % (name, name))\n            lines.append('#else // ' + name)\n            lines.append('    _anjay_log(anjay, TRACE, \"%s = OFF\");' % (name,))\n            lines.append('#endif // ' + name)\n    return '\\n'.join(lines)\n\n\ndef _split_config_log_file(config_log_file):\n    def find_only(haystack, needle):\n        pos = haystack.find(needle)\n        if pos < 0:\n            raise ValueError(\"Could not find '%s' in %s\" % (needle, config_log_file))\n        other = haystack.find(needle, pos + 1)\n        if other >= 0:\n            raise ValueError(\"Found '%s' more than once in %s\" % (needle, config_log_file))\n        return pos\n\n    with open(config_log_file) as f:\n        config_log = f.read()\n\n    lbracket = find_only(config_log, '{')\n    rbracket = find_only(config_log, '}')\n    return config_log[:lbracket + 1], config_log[lbracket + 1:rbracket], config_log[rbracket:]\n\n\ndef validate(config_log_file, config_files):\n    header, contents, footer = _split_config_log_file(config_log_file)\n    expected_contents = _generate_body(enumerate_variables(config_files))\n    if contents.strip() != expected_contents.strip():\n        raise ValueError('%s is outdated. Please regenerate it by calling \"%s update\"' % (\n            config_log_file, __file__))\n\n\ndef update(config_log_file, config_files):\n    header, contents, footer = _split_config_log_file(config_log_file)\n    new_contents = _generate_body(enumerate_variables(config_files))\n    with open(config_log_file, 'w') as f:\n        f.write(header)\n        f.write('\\n')\n        f.write(new_contents)\n        f.write('\\n')\n        f.write(footer)\n\n\ndef list_flags(config_files):\n    for name, type in sorted(enumerate_variables(config_files).items()):\n        if type == ConfigFileVariableType.FLAG:\n            print(name)\n\n\ndef _main():\n    parser = argparse.ArgumentParser(description='Validate or update anjay_config_log.h file.',\n                                     formatter_class=argparse.ArgumentDefaultsHelpFormatter)\n    parser.add_argument('command', choices=['validate', 'update', 'list_flags'])\n    parser.add_argument('-c', '--config-files',\n                        help='%r-separated list of *.h.in files to enumerate variables from' % (\n                            os.pathsep,), default=default_config_files())\n    parser.add_argument('-l', '--config-log-file', help='anjay_config_log.h file to operate on',\n                        default=default_config_log_file())\n    args = parser.parse_args()\n\n    config_files = args.config_files\n    if isinstance(config_files, str):\n        config_files = config_files.split(os.pathsep)\n\n    if args.command == 'validate':\n        return validate(args.config_log_file, config_files)\n    elif args.command == 'update':\n        return update(args.config_log_file, config_files)\n    elif args.command == 'list_flags':\n        return list_flags(config_files)\n    else:\n        raise RuntimeError('Invalid command')\n\n\nif __name__ == '__main__':\n    sys.exit(_main())\n"
  },
  {
    "path": "tools/build_doxygen_docs.py",
    "content": "#!/usr/bin/env python3\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\nimport hashlib\nimport re\nimport shutil\nimport subprocess\nimport sys\nfrom pathlib import Path\n\nPROJECT_ROOT = Path(__file__).resolve().parent.parent\nEXAMPLE_CONFIGS_PATH = PROJECT_ROOT / \"example_configs/linux_lwm2m11\"\nINCLUDE_PUBLIC_PATH = PROJECT_ROOT / \"include_public\"\n\n# Configs to patch (relative to EXAMPLE_CONFIGS_PATH / INCLUDE_PUBLIC_PATH)\nCONFIGS = [\n    \"anjay/anjay_config.h\",\n    \"avsystem/commons/avs_commons_config.h\",\n    \"avsystem/coap/avs_coap_config.h\"\n]\n\n\ndef patch_single_file(src_file, dest_file):\n    \"\"\"\n    Reads a source file, replaces commented out undefs (/* #undef X */)\n    with defines (#define X), and writes the result to the destination.\n    \"\"\"\n    with open(src_file, 'r', encoding='utf-8') as f:\n        content = f.read()\n\n    # Enable disabled macros\n    patched_content = re.sub(\n        r'/\\*\\s*#undef\\s+(\\w+)\\s*\\*/', r'#define \\1', content)\n\n    # Ensure destination directory exists\n    dest_file.parent.mkdir(parents=True, exist_ok=True)\n\n    with open(dest_file, 'w', encoding='utf-8') as f:\n        f.write(patched_content)\n\n\ndef stage_files(doxygen_input_dir):\n    \"\"\"\n    Copies include_public headers and patches example configs into doxygen_input_dir/include_public.\n    \"\"\"\n    dest_include_public = doxygen_input_dir / 'include_public'\n\n    # 1. Clean destination if exists to ensure no stale files\n    if dest_include_public.exists():\n        shutil.rmtree(dest_include_public)\n\n    # 2. Copy all include_public content\n    if INCLUDE_PUBLIC_PATH.exists():\n        shutil.copytree(INCLUDE_PUBLIC_PATH,\n                        dest_include_public, dirs_exist_ok=True)\n\n    # 3. Patch and copy configs over the copied headers\n    for config in CONFIGS:\n        src = EXAMPLE_CONFIGS_PATH / config\n        dst = dest_include_public / config\n        if src.exists():\n            patch_single_file(src, dst)\n        else:\n            print(f\"Warning: Source config not found: {src}\")\n\n\ndef calculate_checksum(directory, doxyfile_path):\n    \"\"\"\n    Calculates a combined MD5 checksum for:\n    1. The Doxygen configuration file.\n    2. All .h files found recursively in the provided directory.\n    \"\"\"\n    md5 = hashlib.md5()\n\n    # 1. Update hash with the content of the Doxyfile\n    with open(doxyfile_path, 'rb') as f:\n        md5.update(f.read())\n\n    # 2. Walk through the directory and collect .h files\n    all_header_files = sorted(p for p in Path(directory).rglob('*.h'))\n\n    for filepath in all_header_files:\n        # Include filename in hash to detect renames\n        md5.update(str(filepath.relative_to(directory)).encode('utf-8'))\n        with open(filepath, 'rb') as f:\n            md5.update(f.read())\n\n    return md5.hexdigest()\n\n\ndef _main():\n    if len(sys.argv) < 4:\n        print(\"Usage: python build_doxygen_docs.py <doxygen_input_dir> <doxyfile_path> <hash_cache_file>\")\n        return 1\n\n    doxygen_input_dir = Path(sys.argv[1]).resolve()\n    doxyfile_path = Path(sys.argv[2]).resolve()\n    hash_cache_file = Path(sys.argv[3]).resolve()\n\n    # Ensure we are running from a location where relative paths are valid\n    if not INCLUDE_PUBLIC_PATH.exists():\n        print(\n            f\"Error: Could not find {INCLUDE_PUBLIC_PATH}. Run from project root.\")\n        return 1\n\n    print(f\"Staging files to {doxygen_input_dir} ...\")\n\n    # 1. Stage files (copy & patch)\n    stage_files(doxygen_input_dir)\n\n    staging_include_public = doxygen_input_dir / 'include_public'\n\n    # 2. Calculate checksum\n    current_hash = calculate_checksum(staging_include_public, doxyfile_path)\n\n    # 3. Check cache\n    try:\n        stored_hash = hash_cache_file.read_text(encoding='utf-8').strip()\n    except OSError:\n        stored_hash = \"\"\n\n    if current_hash == stored_hash:\n        print(\n            \"--> No changes detected in public headers or Doxyfile. Skipping Doxygen build.\")\n        return 0\n\n    print(\"--> Changes detected! Running Doxygen...\")\n\n    # 4. Run Doxygen\n    # Doxyfile is already configured (via CMake) to look at doxygen_input_dir\n    result = subprocess.run([\"doxygen\", str(doxyfile_path)], check=False)\n\n    if result.returncode != 0:\n        print(f\"Error: Doxygen failed with return code {result.returncode}\")\n        return result.returncode\n\n    print(\"--> Doxygen build complete.\")\n\n    # 5. Update cache\n    try:\n        hash_cache_file.parent.mkdir(parents=True, exist_ok=True)\n        with open(hash_cache_file, 'w') as f:\n            f.write(current_hash)\n        print(\"Checksum updated.\")\n    except OSError as e:\n        print(f\"Warning: Could not save checksum to {hash_cache_file}: {e}\")\n\n    return 0\n\n\nif __name__ == \"__main__\":\n    sys.exit(_main())\n"
  },
  {
    "path": "tools/build_sphinx_docs.py",
    "content": "#!/usr/bin/env python3\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\nimport sys\nimport hashlib\nimport subprocess\nfrom pathlib import Path\n\n\ndef _calculate_recursive_hash(directories):\n    \"\"\"Calculates a combined MD5 checksum for all files in provided directories.\"\"\"\n    md5 = hashlib.md5()\n\n    for directory in directories:\n        directory = Path(directory).resolve()\n        if not directory.exists():\n            continue\n\n        all_files = sorted(p for p in directory.rglob('*') if p.is_file())\n\n        for filepath in all_files:\n            # Include filename in hash to detect renames\n            md5.update(str(filepath.relative_to(directory)).encode('utf-8'))\n            with open(filepath, 'rb') as f:\n                md5.update(f.read())\n\n    return md5.hexdigest()\n\n\ndef _main():\n    if len(sys.argv) < 5:\n        print(\n            \"Usage: python3 build_sphinx_docs.py <source_dir> <conf_dir> <output_dir> <cache_file> [extra_dependency_dirs...]\")\n        return 1\n\n    source_dir = sys.argv[1]\n    conf_dir = sys.argv[2]\n    output_dir = sys.argv[3]\n    cache_file = sys.argv[4]\n    extra_deps = sys.argv[5:]\n\n    scan_dirs = [source_dir] + extra_deps\n    print(f\"Scanning for changes in: {', '.join(scan_dirs)} ...\")\n    current_hash = _calculate_recursive_hash(scan_dirs)\n\n    # Check stored hash\n    input_hash_file = Path(cache_file).resolve()\n\n    try:\n        stored_hash = Path(input_hash_file).read_text(encoding='utf-8').strip()\n    except OSError:\n        stored_hash = \"\"\n    if current_hash == stored_hash:\n        print(f\"--> No changes detected. Skipping Sphinx build.\")\n        return 0\n\n    print(f\"--> Changes detected. Running Sphinx...\")\n\n    # Construct Sphinx command\n    # -j auto: Parallel build\n    # -b html: HTML builder\n    # -c conf_dir: Configuration directory\n    # source_dir: Source files\n    # output_dir: Output directory\n    # We use \"input_hash_file\" for cache logic\n    command = [\"sphinx-build\",\n               \"-j\", \"auto\",\n               \"-b\",\n               \"html\",\n               \"-c\", conf_dir,\n               source_dir,\n               output_dir]\n    print(f\"    Command: {' '.join(command)}\")\n\n    # Run Sphinx\n    result = subprocess.run(command)\n\n    if result.returncode != 0:\n        print(f\"Error: Sphinx failed with return code {result.returncode}\")\n        return result.returncode\n\n    # Update cache\n    input_hash_file.parent.mkdir(parents=True, exist_ok=True)\n    try:\n        with open(input_hash_file, 'w') as f:\n            f.write(current_hash)\n    except OSError as e:\n        print(f\"Warning: Could not save hash to {input_hash_file}: {e}\")\n\n    return 0\n\n\nif __name__ == \"__main__\":\n    sys.exit(_main())\n"
  },
  {
    "path": "tools/ci/build-docker-images.sh",
    "content": "#!/usr/bin/env bash\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nset -e\n\nSCRIPT_DIR=\"$(dirname \"$(readlink -f \"$BASH_SOURCE\")\")\"\nANJAY_ROOT=\"$(dirname \"$(dirname \"$SCRIPT_DIR\")\")\"\n\nprint_help() {\n    cat <<EOF >&2\nNAME\n    $(basename \"$0\") - Builds internal Docker image for use in Gitlab CI.\n\nSYNOPSIS\n    $(basename \"$0\") [-h|--help]\n    $(basename \"$0\") --version <version>\n\nOPTIONS\n    -v, --version <version>\n            - image version.\n    -h, --help\n            - print help message and exit.\n\nUSAGE\n    First step is to build docker image locally e.g.:\n      $(basename \"$0\") --version 0.1\n\n    Second step is to push built image to docker.io\n      docker login docker.io\n      docker push avsystemembedded/anjay-travis:<image_version>\n      docker logout\nEOF\n}\n\nwhile [[ $# -gt 0 ]]; do\n    case \"$1\" in\n        -v|--version)                IMAGE_VERSION=\"$2\"; shift ;;\n        -h|--help)\n            print_help\n            exit 0\n            ;;\n        *)\n            echo \"unrecognized option: $1; use -h or --help for help\"\n            die\n            ;;\n    esac\n\n    shift\ndone\n\n [[ \"$IMAGE_VERSION\" ]] || (echo \"ERROR: Missing image version\" && exit 1)\n\nbuild-docker-image() {\n    local NAME=\"$1\"\n    local DOCKERFILE=\"$2\"\n    pushd \"$ANJAY_ROOT\"\n        docker build --no-cache -t \"$NAME\" -f \"$DOCKERFILE\" .\n    popd\n}\n\nfor IMAGE_DOCKERFILE in \"$SCRIPT_DIR/\"*/Dockerfile; do\n    IMAGE_NAME=\"avsystemembedded/anjay-travis:$(basename \"$(dirname \"$IMAGE_DOCKERFILE\")\")-\"$IMAGE_VERSION\"\"\n    build-docker-image \"$IMAGE_NAME\" \"$IMAGE_DOCKERFILE\"\ndone\n"
  },
  {
    "path": "tools/ci/rockylinux-9/Dockerfile",
    "content": "# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\n# NOTE: After changing this file, please remember to update the Docker image on\n# Docker Hub (for more information, check the help message of \n# tools/ci/build-docker-images.sh script).\n\nFROM rockylinux/rockylinux:9\nRUN dnf update -y && \\\n    dnf install -y --allowerasing python3-pip git openssl-devel zlib-devel \\\n                   python3 python3-devel wget openssl valgrind curl cmake \\\n                   gcc gcc-c++ wireshark-cli which 'dnf-command(config-manager)'\nRUN dnf config-manager --set-enabled crb && \\\n    dnf install -y epel-release && \\\n    dnf install -y mbedtls-devel\n\n# set up Python3 venv\nCOPY requirements.txt requirements.txt\nRUN python3 -m venv /venv\nRUN . /venv/bin/activate && \\\n    pip install --upgrade pip && \\\n    pip install -r requirements.txt\nENTRYPOINT [\"/bin/bash\", \"-c\", \". /venv/bin/activate && exec \\\"$@\\\"\", \"--\"]\n"
  },
  {
    "path": "tools/ci/ubuntu-22.04/Dockerfile",
    "content": "# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\n# NOTE: After changing this file, please remember to update the Docker image on\n# Docker Hub (for more information, check the help message of \n# tools/ci/build-docker-images.sh script).\n\nFROM ubuntu:22.04\nRUN apt-get update && \\\n    env DEBIAN_FRONTEND=noninteractive \\\n    apt-get install -y python3-pip git libmbedtls-dev libssl-dev zlib1g-dev \\\n        python3 libpython3-dev wget valgrind curl cmake build-essential tshark \\\n        file python3-venv\n\n# set up Python3 venv\nCOPY requirements.txt requirements.txt\nRUN python3 -m venv /venv\nRUN . /venv/bin/activate && \\\n    pip install --upgrade pip && \\\n    pip install -r requirements.txt\nENTRYPOINT [\"/bin/bash\", \"-c\", \". /venv/bin/activate && exec \\\"$@\\\"\", \"--\"]\n\n# Solve issues with EPERM when running dumpcap\nRUN setcap '' $(which dumpcap)\n"
  },
  {
    "path": "tools/ci/ubuntu-24.04/Dockerfile",
    "content": "# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\n# NOTE: After changing this file, please remember to update the Docker image on\n# Docker Hub (for more information, check the help message of \n# tools/ci/build-docker-images.sh script).\n\nFROM ubuntu:24.04\nRUN apt-get update && \\\n    env DEBIAN_FRONTEND=noninteractive \\\n    apt-get install -y python3-pip git libmbedtls-dev libssl-dev zlib1g-dev \\\n        python3 libpython3-dev wget valgrind curl cmake build-essential tshark \\\n        file python3-venv\n\n# set up Python3 venv\nCOPY requirements.txt requirements.txt\nRUN python3 -m venv /venv\nRUN . /venv/bin/activate && \\\n    pip install --upgrade pip && \\\n    pip install -r requirements.txt\nENTRYPOINT [\"/bin/bash\", \"-c\", \". /venv/bin/activate && exec \\\"$@\\\"\", \"--\"]\n\n# Solve issues with EPERM when running dumpcap\nRUN setcap '' $(which dumpcap)\n"
  },
  {
    "path": "tools/ci-psa/Dockerfile",
    "content": "# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\n\nFROM ubuntu:24.04\nRUN apt-get update && \\\n    DEBIAN_FRONTEND=noninteractive \\\n    apt-get install -yq git build-essential cmake zlib1g-dev doxygen python3 \\\n        libpython3-dev libssl-dev python3-pip clang-tools valgrind opensc \\\n        libengine-pkcs11-openssl docker.io nodejs curl jq automake python3-venv\nCOPY requirements.txt .\nRUN python3 -m venv /venv\nRUN . /venv/bin/activate && \\\n    pip install --upgrade pip && \\\n    pip install -r requirements.txt\nENTRYPOINT [\"/bin/bash\", \"-c\", \". /venv/bin/activate && exec \\\"$@\\\"\", \"--\"]\n\nRUN $(which echo) -e \"                                                      \\n\\\n                                                                            \\n\\\n    set -e                                                                  \\n\\\n    TMPDIR=\\\"$(mktemp -d)\\\"                                                 \\n\\\n    pushd \\\"$TMPDIR\\\"                                                       \\n\\\n        git clone https://github.com/ARMmbed/mbedtls.git                    \\n\\\n        cd mbedtls                                                          \\n\\\n        git checkout v3.1.0                                                 \\n\\\n        scripts/config.py set MBEDTLS_USE_PSA_CRYPTO                        \\n\\\n        cmake -DUSE_SHARED_MBEDTLS_LIBRARY=On -DCMAKE_INSTALL_PREFIX=/usr . \\n\\\n        make                                                                \\n\\\n        make install                                                        \\n\\\n    popd                                                                    \\n\\\n    rm -rf \\\"$TMPDIR\\\"                                                      \\n\\\n                                                                            \\n\\\n\" | bash\n"
  },
  {
    "path": "tools/ci-psa/README.md",
    "content": "This Dockerfile is developed to build PSA examples and run PSA tests.\nIt contains the basic libs needed to build Anjay and additionaly custom build\nof mbed TLS with PSA enabled.\n\nTo build the image, you can run the following command from the root of the Anjay\nrepository:\n```bash\ndocker build --no-cache -f tools/ci-psa/Dockerfile .\n```\n\n"
  },
  {
    "path": "tools/conditional_headers_whitelist.json",
    "content": "{\n    \"\": [\n        \"anjay_init\\\\.h\",\n        \"avsystem/commons/[^.]*\\\\.h\",\n        \"avsystem/coap/[^.]*\\\\.h\",\n        \"anjay/[^.]*\\\\.h\",\n        \"anjay_modules/[^.]*\\\\.h\"\n    ],\n    \"anjay_core\\\\.c\": [\n        \"anjay_config_log\\\\.h\"\n    ],\n    \"anjay_event_loop\\\\.c\": [\n        \"poll\\\\.h\",\n        \"sys/select\\\\.h\"\n    ],\n    \"anjay_utils_core\\\\.h\": [\n        \"stdatomic\\\\.h\"\n    ]\n}\n"
  },
  {
    "path": "tools/coverage",
    "content": "#!/usr/bin/env bash\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nset -e\n\n. \"$(dirname \"$0\")/utils.sh\"\n\nfunction die() {\n    echo -e \"$@\" >&2\n    exit 1\n}\n\nwhich gcc || die \"gcc not found, exiting\"\nwhich lcov || die \"lcov not found, exiting\"\nwhich genhtml || die \"genhtml not found, exiting\"\n\nGCC_VERSION=$(gcc --version 2>&1 | head -n 1 | awk 'END {print $NF}')\nGCC_MAJOR_VERSION=${GCC_VERSION%%.*}\nLCOV_VERSION=$(lcov --version 2>&1 | head -n 1 | awk 'END {print $NF}')\nLCOV_MAJOR_VERSION=${LCOV_VERSION%%.*}\n\nif [ \"$LCOV_MAJOR_VERSION\" -gt 1 ]; then\n    LCOV_ADDITIONAL_OPTS=\"--rc branch_coverage=1 --ignore-errors mismatch\"\nelse\n    LCOV_ADDITIONAL_OPTS=\"--rc lcov_branch_coverage=1\"\nfi\n\n[[ \"$PROJECT_ROOT\" ]] || PROJECT_ROOT=\"$(dirname \"$(dirname \"$(canonicalize \"$0\")\")\")\"\n\nrm -rf \"$PROJECT_ROOT/build/coverage\"\nmkdir -p \"$PROJECT_ROOT/build/coverage\"\npushd \"$PROJECT_ROOT/build/coverage\"\n    \"$PROJECT_ROOT/devconfig\" --without-analysis --without-memcheck --no-examples -DCMAKE_C_FLAGS=\"-std=c99 -D_POSIX_C_SOURCE=200809L -g -O0 --coverage\" -DCMAKE_EXE_LINKER_FLAGS=\"--coverage\" \"$@\"\n    make anjay_check -j$(num_processors)\n    mkdir -p \"$PROJECT_ROOT/coverage\"\n    lcov $LCOV_ADDITIONAL_OPTS -c -d . -o coverage.info --gcov-tool /usr/bin/gcov-$GCC_MAJOR_VERSION\n    lcov $LCOV_ADDITIONAL_OPTS --remove coverage.info \"$PROJECT_ROOT/tests/*\" \"$PROJECT_ROOT/deps/*\" \"/usr/*\" \"$PROJECT_ROOT/include_public/*\" -o coverage.info\n    genhtml coverage.info --branch-coverage --function-coverage --output-directory \"$PROJECT_ROOT/coverage\"\npopd\n\ncat <<EOF\n\n-----\nCoverage report generated in $PROJECT_ROOT/coverage\nEOF\n"
  },
  {
    "path": "tools/find_unused_code.py",
    "content": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nimport argparse\nimport collections\nimport logging\nimport os\nimport re\nimport shutil\nimport subprocess\nimport sys\nimport tempfile\n\nPROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))\n\nViolation = collections.namedtuple('Violation', ['file', 'object', 'symbol'])\n\n\nclass Ignores(collections.namedtuple('Ignores',\n                                     ['ignore_files', 'ignore_objects', 'ignore_symbols'])):\n    def is_ignored(self, violation):\n        return any(re.search(ignore_file, violation.file) is not None for ignore_file in\n                   self.ignore_files) or any(\n            re.search(ignore_object, violation.object) is not None for ignore_object in\n            self.ignore_objects) or any(\n            re.search(ignore_symbol, violation.symbol) is not None for ignore_symbol in\n            self.ignore_symbols)\n\n\ndef filter_out_static_symbols(violations):\n    files = {v.file for v in violations}\n    files = [(f if os.path.isabs(f) else os.path.join('demo', f)) for f in files]\n\n    static_symbols = set()\n    if len(files) > 0:\n        for entry in subprocess.run(['nm', '--portability'] + files, universal_newlines=True,\n                                    stdout=subprocess.PIPE, check=True).stdout.splitlines():\n            columns = entry.split()\n            # second column of nm output specifies symbol type\n            # it's supposed to be a single letter, see man nm for details\n            # lowercase generally means the symbol has internal linkage\n            if len(columns) >= 2 and re.search(r'[a-z]', columns[1]) is not None:\n                static_symbols.add(columns[0])\n\n    return {v for v in violations if v.symbol not in static_symbols}\n\n\ndef find_unused_code(jobs, ignores):\n    # check installed versions\n    subprocess.run(['gcc', '--version'], text=True, check=True)\n    subprocess.run(['ld', '-v'], text=True, check=True)\n    subprocess.run(['cmake', '--version'], text=True, check=True)\n\n    DEVCONFIG_OUT_FNAME = 'devconfig.out'\n    MAKE_OUT_FNAME = 'make.out'\n    UNUSED_SECTIONS_FNAME = 'unused-sections'\n\n    logging.info('configuring: %s/%s', os.getcwd(), DEVCONFIG_OUT_FNAME)\n    with open(DEVCONFIG_OUT_FNAME, 'w') as out:\n        subprocess.run([os.path.join(PROJECT_ROOT, 'devconfig'), '--without-memcheck',\n                        '-DCMAKE_C_FLAGS=-ffunction-sections -fdata-sections',\n                        '-DCMAKE_EXE_LINKER_FLAGS=-Wl,--gc-sections -Wl,--print-gc-sections -Wl,--gc-keep-exported',\n                        '-DWITH_AVS_CRYPTO_PKI_ENGINE=OFF', '-DWITH_STATIC_ANALYSIS=OFF'],\n                       stdout=out, stderr=out, check=True)\n\n    with open('CMakeCache.txt', 'r') as f:\n        cmake_binary_candidates = [line for line in f.readlines() if line.startswith('CMAKE_COMMAND')]\n        assert len(cmake_binary_candidates) == 1\n        cmake_binary = cmake_binary_candidates[0].split('=')[-1].strip()\n\n    logging.info('compiling: %s/%s', os.getcwd(), MAKE_OUT_FNAME)\n    with open(MAKE_OUT_FNAME, 'w') as out:\n        subprocess.run([cmake_binary, '--build', '.', '--', '-j%d' % (jobs,)], stdout=out, stderr=out,\n                       check=True)\n\n    # examples of lines we're looking for in ld output:\n    #\n    ### GNU ld 2.26.1 on Ubuntu 16.04\n    # /usr/bin/ld: Removing unused section '.rodata.AVS_NET_EPROTO' in file '../output/lib/libavs_net.a(net_impl.c.o)'\n    # /usr/bin/ld: Removing unused section '.data' in file '/usr/lib/gcc/x86_64-linux-gnu/5/crtbegin.o'\n    #\n    ### GNU ld 2.31.1 on Ubuntu 18.10 (lowercase r in \"removing\")\n    # /usr/bin/ld: removing unused section '.rodata.AVS_NET_EPROTO' in file '../output/lib/libavs_net.a(net_impl.c.o)'\n    # /usr/bin/ld: removing unused section '.data' in file '/usr/lib/gcc/x86_64-linux-gnu/5/crtbegin.o'\n    #\n    violations = set()\n    with open(MAKE_OUT_FNAME, 'r') as f:\n        for line in f:\n            match = re.search(r'removing unused section (.*) in file (.*)$', line.strip(),\n                              re.IGNORECASE)\n            if match is None:\n                continue\n\n            symbol = match.group(1).strip().strip(\"'\")\n            # drop .$SECTION. prefix\n            symbol = re.sub(r'^\\.(data|rodata|text)\\.(rel\\.ro\\.local\\.)?', '', symbol, 1)\n\n            file_obj = match.group(2).strip().strip(\"'\")\n            match = re.fullmatch(r'(.*)\\((.*)\\)', file_obj)\n            if match is not None:\n                violations.add(Violation(file=match.group(1), object=match.group(2), symbol=symbol))\n            else:\n                violations.add(Violation(file=file_obj, object=file_obj, symbol=symbol))\n\n    violations = {v for v in violations if not ignores.is_ignored(v)}\n    violations = filter_out_static_symbols(violations)\n\n    if len(violations) == 0:\n        return 0\n\n    logging.error('unused symbols found:')\n    # Column formatting, adapted from https://stackoverflow.com/a/12065663\n    widths = [max(map(len, col)) for col in zip(*violations)]\n    with open(UNUSED_SECTIONS_FNAME, 'w') as f:\n        for violation in sorted(violations):\n            line = '  '.join((val.ljust(width) for val, width in zip(violation, widths)))\n            print(line, file=f)\n            logging.error(line)\n\n    return 1\n\n\nif __name__ == '__main__':\n    parser = argparse.ArgumentParser(description=\"Find unused exported symbols in the codebase\")\n    parser.add_argument('-j', '--jobs', type=int, help='run build in N parallel jobs',\n                        default=os.cpu_count())\n    parser.add_argument('-s', '--ignore-symbol', action='append',\n                        help='do not report unused symbols matching REGEX')\n    parser.add_argument('-o', '--ignore-object', action='append',\n                        help='do not report unused symbols from object files matching REGEX')\n    parser.add_argument('-f', '--ignore-file', action='append',\n                        help='do not report unused symbols from files matching REGEX')\n    parser.add_argument('--preserve-tmpdir', action='store_true',\n                        help='do not delete temporary directory after finishing')\n\n    args = parser.parse_args()\n    logging.getLogger().setLevel(logging.DEBUG)\n    for file in args.ignore_file:\n        logging.debug('ignoring file: %s', file)\n    for obj in args.ignore_object:\n        logging.debug('ignoring object file: %s', obj)\n    for sym in args.ignore_symbol:\n        logging.debug('ignoring symbol: %s', sym)\n\n    tmpdir = tempfile.mkdtemp()\n    try:\n        os.chdir(tmpdir)\n        sys.exit(find_unused_code(args.jobs, Ignores(args.ignore_file, args.ignore_object,\n                                                     args.ignore_symbol)))\n    finally:\n        if not args.preserve_tmpdir:\n            shutil.rmtree(tmpdir)\n"
  },
  {
    "path": "tools/generate-certs.sh",
    "content": "#!/usr/bin/env bash\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nset -e\n\n. \"$(dirname \"$0\")/utils.sh\"\n\nTRUSTSTORE_PASSWORD=rootPass\nKEYSTORE_PASSWORD=endPass\n\nSCRIPT_DIR=\"$(dirname \"$(canonicalize \"$0\")\")\"\nROOT_DIR=\"$(dirname \"$SCRIPT_DIR\")\"\nTOOLS_DIR=\"$SCRIPT_DIR\"\n\nif [ -z \"$OPENSSL\" ]; then\n    # default OpenSSL on macOS is outdated and buggy\n    # use the one from Homebrew if available\n    BREW_OPENSSL=\"$(brew --prefix openssl 2>/dev/null || true)\"\n    if [ \"$BREW_OPENSSL\" ]; then\n        OPENSSL=\"$BREW_OPENSSL/bin/openssl\"\n    else\n        OPENSSL=openssl\n    fi\nfi\n\nOPENSSL_VERSION=\"$(\"$OPENSSL\" version | awk '{print $2}')\"\nOPENSSL_VERSION_MAJOR=\"${OPENSSL_VERSION%%.*}\"\nOPENSSL_VERSION_TMP=\"${OPENSSL_VERSION:${#OPENSSL_VERSION_MAJOR}+1}\"\nOPENSSL_VERSION_MINOR=\"${OPENSSL_VERSION_TMP%%.*}\"\nOPENSSL_VERSION_TMP=\"${OPENSSL_VERSION_TMP:${#OPENSSL_VERSION_MINOR}+1}\"\nOPENSSL_VERSION_PATCH=\"${OPENSSL_VERSION_TMP%%.*}\"\n# remove non-numeric suffix\nOPENSSL_VERSION_PATCH=\"$(echo \"$OPENSSL_VERSION_PATCH\" | sed -e 's/[^0-9]*$//')\"\n\nopenssl_packed_version() {\n    echo \"$(($3 + 256 * ($2 + 256 * $1)))\"\n}\n\nOPENSSL_PACKED_VERSION=\"$(openssl_packed_version $OPENSSL_VERSION_MAJOR $OPENSSL_VERSION_MINOR $OPENSSL_VERSION_PATCH)\"\n\nif [[ \"$#\" < 1 ]]; then\n    CERTS_DIR=\"$(pwd)/certs\"\nelse\n    CERTS_DIR=\"$1\"\nfi\n\nif [[ -d \"$CERTS_DIR\" ]]; then\n    echo \"WARNING: $CERTS_DIR already exists, its contents will be removed\"\n    echo -n \"[ENTER to continue, CTRL-C to abort] \"\n    read _ || true # ignore EOF\n    rm -rf \"$CERTS_DIR\"\nfi\nmkdir -p \"$CERTS_DIR\"\npushd \"$CERTS_DIR\" >/dev/null 2>&1\n\necho \"* generating self signed certs\"\nmkdir self-signed\ncd self-signed\n\n# MSYS translates arguments that start with \"/\" to Windows paths... but /CN= is not a path, so we disable it for this call\nexport MSYS2_ARG_CONV_EXCL='*'\n\n\"$OPENSSL\" ecparam -name prime256v1 -genkey -out server.key\n\"$OPENSSL\" ecparam -name prime256v1 -genkey -out client.key\n\nif [ \"$OPENSSL_PACKED_VERSION\" -ge \"$(openssl_packed_version 1 1 1)\" ]; then\n    # OpenSSL >= 1.1.1, we have -addext\n    ADDEXT_OPT=(-addext 'subjectAltName = DNS:127.0.0.1,IP:127.0.0.1')\nelse\n    # OpenSSL <= 1.1.0, skip -addext\n    ADDEXT_OPT=()\nfi\n\"$OPENSSL\" req -batch -new -subj '/CN=127.0.0.1' \"${ADDEXT_OPT[@]}\" -key server.key -x509 -sha256 -days 9999 -out server.crt\n\"$OPENSSL\" req -batch -new -subj '/CN=localhost' -key client.key -x509 -sha256 -days 9999 -out client.crt\n\"$OPENSSL\" x509 -in server.crt -outform der > server.crt.der\n\"$OPENSSL\" x509 -in client.crt -outform der > client.crt.der\n\"$OPENSSL\" pkcs8 -topk8 -in client.key -inform pem -outform der -nocrypt > client.key.der\ncd ..\necho \"* generating self signed certs - done\"\n\necho \"* generating root cert\"\n\"$OPENSSL\" ecparam -name prime256v1 -genkey -out root.key\n\"$OPENSSL\" req -batch -new -subj '/CN=root' -key root.key -x509 -sha256 -days 9999 -out root.crt\n\"$OPENSSL\" x509 -in root.crt -outform der > root.crt.der\necho \"* generating root cert - done\"\n\necho \"* generating client cert\"\n\"$OPENSSL\" ecparam -name prime256v1 -genkey -out client.key\n\"$OPENSSL\" req -batch -new -subj '/CN=localhost' -key client.key -sha256 -out client.csr\n\"$OPENSSL\" x509 -sha256 -req -in client.csr -CA root.crt -CAkey root.key -out client.crt -days 9999 -CAcreateserial\ncat client.crt root.crt > client-and-root.crt\necho \"* generating client cert - done\"\n\"$OPENSSL\" x509 -in client.crt -outform der > client.crt.der\n\"$OPENSSL\" pkcs8 -topk8 -in client.key -inform pem -outform der \\\n    -passin \"pass:$KEYSTORE_PASSWORD\" -nocrypt > client.key.der\n\necho \"* generating client2_ca cert\"\n\"$OPENSSL\" ecparam -name prime256v1 -genkey -out client2_ca.key\n\"$OPENSSL\" req -batch -new -subj '/CN=intermediate' -key client2_ca.key -sha256 -out client2_ca.csr\n\"$OPENSSL\" x509 -sha256 -req -in client2_ca.csr -CA root.crt -CAkey root.key -out client2_ca.crt -days 9999 -extfile <(echo 'basicConstraints = CA:TRUE') -CAcreateserial\ncat client2_ca.crt root.crt > client2_ca-and-root.crt\necho \"* generating client2_ca cert - done\"\n\"$OPENSSL\" x509 -in client2_ca.crt -outform der > client2_ca.crt.der\n\necho \"* generating client2 cert\"\n\"$OPENSSL\" ecparam -name prime256v1 -genkey -out client2.key\n\"$OPENSSL\" req -batch -new -subj '/CN=client2' -key client2.key -sha256 -out client2.csr\n\"$OPENSSL\" x509 -sha256 -req -in client2.csr -CA client2_ca.crt -CAkey client2_ca.key -out client2.crt -days 9999 -CAcreateserial\ncat client2.crt client2_ca.crt > client2-and-ca.crt\ncat client2-and-ca.crt root.crt > client2-full-path.crt\necho \"* generating client2 cert - done\"\n\"$OPENSSL\" x509 -in client2.crt -outform der > client2.crt.der\n\"$OPENSSL\" pkcs8 -topk8 -in client2.key -inform pem -outform der \\\n    -passin \"pass:$KEYSTORE_PASSWORD\" -nocrypt > client2.key.der\n\necho \"* generating server_ca cert\"\n\"$OPENSSL\" ecparam -name prime256v1 -genkey -out server_ca.key\n\"$OPENSSL\" req -batch -new -subj '/CN=localhost' -key server_ca.key -sha256 -out server_ca.csr\n\"$OPENSSL\" x509 -sha256 -req -in server_ca.csr -CA root.crt -CAkey root.key -out server_ca.crt -days 9999 -extfile <(echo 'basicConstraints = CA:TRUE') -CAcreateserial\ncat server_ca.crt root.crt > server_ca-and-root.crt\necho \"* generating server_ca cert - done\"\n\"$OPENSSL\" x509 -in server_ca.crt -outform der > server_ca.crt.der\n\necho \"* generating server cert\"\n\"$OPENSSL\" ecparam -name prime256v1 -genkey -out server.key\n\"$OPENSSL\" req -batch -new -subj '/CN=127.0.0.1' -key server.key -sha256 -out server.csr\n\"$OPENSSL\" x509 -sha256 -req -in server.csr -CA server_ca.crt -CAkey server_ca.key -out server.crt -days 9999 -extfile <(echo 'subjectAltName = DNS:127.0.0.1,IP:127.0.0.1') -CAcreateserial\ncat server.crt server_ca.crt > server-and-ca.crt\ncat server-and-ca.crt root.crt > server-full-path.crt\necho \"* generating server cert - done\"\n\"$OPENSSL\" x509 -in server.crt -outform der > server.crt.der\n\ngenerate_java_keystores() {\n    set -e\n    if ! KEYTOOL=\"$(which keytool)\" || [ -z \"$KEYTOOL\" ] || ! \"$KEYTOOL\" -help >/dev/null 2>/dev/null; then\n        echo ''\n        echo \"NOTE: keytool not found, not generating keystores for Java/Californium\"\n        echo ''\n    else\n        echo \"* creating trustStore.jks\"\n        yes | \"$KEYTOOL\" -importcert -alias root -file root.crt.der -keystore trustStore.jks -storetype PKCS12 -storepass \"$TRUSTSTORE_PASSWORD\" >/dev/null\n        echo \"* creating trustStore.jks - done\"\n\n        echo \"* creating keyStore.jks\"\n        for NAME in client server; do\n            \"$OPENSSL\" pkcs12 -export -in \"${NAME}.crt\" -inkey \"${NAME}.key\" -passin \"pass:$KEYSTORE_PASSWORD\" -out \"${NAME}.p12\" -name \"${NAME}\" -CAfile root.crt -caname root -password \"pass:$KEYSTORE_PASSWORD\"\n            \"$KEYTOOL\" -importkeystore -deststorepass \"$KEYSTORE_PASSWORD\" -destkeystore keyStore.jks -deststoretype PKCS12 -srckeystore \"${NAME}.p12\" -srcstoretype PKCS12 -srcstorepass \"$KEYSTORE_PASSWORD\" -alias \"${NAME}\" -trustcacerts\n        done\n        echo \"* creating keyStore.jks - done\"\n\n        echo ''\n        echo \"NOTE: To make demo successfully connect to Californium cf-secure server, copy contents of the $CERTS_DIR to the cf-secure/certs subdirectory and restart the server.\"\n        echo ''\n    fi\n}\n\ngenerate_java_keystores || {\n    echo ''\n    echo 'NOTE: Error while generating keystores for Java/Californium, ignoring'\n    echo ''\n}\n\npopd >/dev/null 2>&1\n"
  },
  {
    "path": "tools/generate_doxygen_config.py",
    "content": "#!/usr/bin/env python3\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n#\n# -----------------------------------------------------------------------------\n# Anjay Doxygen Configuration Helper\n#\n# This script scans the example config files and extracts all defined macros\n# (including those commented out with /* #undef ... */) to be used in the\n# Doxygen PREDEFINED tag.\n# -----------------------------------------------------------------------------\nimport re\nimport sys\nfrom pathlib import Path\n\nPROJECT_ROOT = Path(__file__).resolve().parent.parent\nEXAMPLE_CONFIGS_PATH = PROJECT_ROOT / \"example_configs/linux_lwm2m11\"\n\n# configs paths relative to EXAMPLE_CONFIGS_PATH\nCONFIGS = [\n    \"anjay/anjay_config.h\",\n    \"avsystem/commons/avs_commons_config.h\",\n    \"avsystem/coap/avs_coap_config.h\"\n]\n\n\ndef extract_macros_from_file(file_path):\n    \"\"\"\n    Extracts macro names from a file to be used in PREDEFINED list.\n    Handles both #define and /* #undef ... */\n    \"\"\"\n    if not file_path.exists():\n        print(f\"[Error] File not found for macro extraction: {file_path}\")\n        sys.exit(1)\n\n    with open(file_path, 'r', encoding='utf-8') as f:\n        lines = f.readlines()\n\n    macros = []\n    # Exclude these headers from macro extraction because if they are defined in\n    # doxygen, it does not analyze the contents of config files\n    EXCLUDED = {\"ANJAY_CONFIG_H\", \"AVS_COMMONS_CONFIG_H\", \"AVS_COAP_CONFIG_H\"}\n\n    for line in lines:\n        # Match #define MACRO\n        match_def = re.match(r'^\\s*#define\\s+([A-Za-z_][A-Za-z0-9_]*)', line)\n        if match_def:\n            macro = match_def.group(1)\n            if macro not in EXCLUDED:\n                macros.append(macro)\n                continue\n\n        # Match /* #undef MACRO */\n        match_undef = re.match(\n            r'^\\s*/\\*\\s*#undef\\s+([A-Za-z_][A-Za-z0-9_]*)', line)\n        if match_undef:\n            macro = match_undef.group(1)\n            if macro not in EXCLUDED:\n                macros.append(macro)\n\n    return macros\n\n\ndef run_generate_predefined(output_defines_file):\n    \"\"\"Executes the generate-predefined logic.\"\"\"\n    all_macros = []\n    for config in CONFIGS:\n        # Resolve path relative to script location -> root -> example configs\n        src_file = EXAMPLE_CONFIGS_PATH / config\n        all_macros.extend(extract_macros_from_file(src_file))\n\n    # Determine absolute path for output\n    output_defines_path = Path(output_defines_file).resolve()\n\n    # Ensure directory exists\n    output_defines_path.parent.mkdir(parents=True, exist_ok=True)\n\n    # Write content\n    new_content = ' '.join(all_macros)\n    with open(output_defines_path, 'w', encoding='utf-8') as f:\n        f.write(new_content)\n\n\nif __name__ == '__main__':\n    if len(sys.argv) < 2:\n        print(\"Usage: python generate_doxygen_config.py <output_defines_file>\")\n        sys.exit(1)\n\n    output_defines_file = sys.argv[1]\n    run_generate_predefined(output_defines_file)\n"
  },
  {
    "path": "tools/lwm2m_object_registry.py",
    "content": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\n\nimport urllib.request\nimport argparse\nimport collections\nimport logging\nimport sys\nimport os\nfrom xml.etree import ElementTree\nfrom itertools import groupby\nfrom operator import attrgetter\n\nclass Lwm2mObjectEntry:\n    \"\"\"\n    LwM2M Object Registry entry.\n\n    Available attributes are the same as tag names in the DDF XML structure.\n    \"\"\"\n\n    def __init__(self, tree):\n        self._tree = tree\n\n    def __getattr__(self, name):\n        node = self._tree.find(name)\n        if node is not None and node.text is not None:\n            return node.text.strip()\n        return self._tree.get(name)\n\n    def __lt__(self, other):\n        return (self.ObjectID, self.Ver) < (other.ObjectID, other.Ver)\n\n\ndef _read_url(url: str) -> bytes:\n    # we need to change the User-Agent - default one causes the server\n    # to respond with 403 Forbidden\n    req = urllib.request.Request(url, headers={'User-Agent': 'Mozilla/5.0'})\n    with urllib.request.urlopen(req) as f:\n        return f.read()\n\n\nclass Lwm2mObjectRegistry:\n    def __init__(self, repo_url='https://raw.githubusercontent.com/OpenMobileAlliance/lwm2m-registry/prod'):\n        self.repo_url = repo_url\n        ddf_url = repo_url + '/DDF.xml'\n        root = ElementTree.fromstring(_read_url(ddf_url))\n        entries = (Lwm2mObjectEntry(obj) for obj in root.findall('Item'))\n\n        grouped = ((int(key), list(group)) for key, group in groupby(entries, attrgetter('ObjectID')))\n        self.objects = collections.OrderedDict(grouped)\n\n\ndef _print_object_list():\n    for oid, objs in Lwm2mObjectRegistry().objects.items():\n        for obj in objs:\n            print('%d\\t%s\\t%s' % (oid, obj.Ver, obj.Name))\n\n\ndef get_object_definition(urn_or_oid, version):\n    urn = urn_or_oid.strip()\n    if urn.startswith('urn:oma:lwm2m:'):\n        oid = int(urn.split(':')[-1])\n    else:\n        oid = int(urn)\n\n    try:\n        registry = Lwm2mObjectRegistry() \n        objects = registry.objects[oid]\n        available_versions_message = 'Available versions for object with ID %d: %s' % (\n            oid, ', '.join(str(obj.Ver) for obj in objects))\n\n        if version is None:\n            if (len(objects) > 1):\n                logging.info('%s; defaulting to maximum available version: %s' % (\n                    available_versions_message, max(objects).Ver))\n            object_ddf_url = max(objects).DDF\n        else:\n            object_ddf_url = next(obj for obj in objects if obj.Ver == version).DDF\n        if not object_ddf_url:\n            raise ValueError(\"Object with ID = %d doesn't have attached XML definition\" % oid)\n        if not object_ddf_url.startswith('http'):\n            object_ddf_url = registry.repo_url + '/' + object_ddf_url\n        return _read_url(object_ddf_url).decode('utf-8-sig')\n    except KeyError:\n        raise ValueError('Object with ID = %d not found' % oid)\n    except StopIteration:\n        raise ValueError(available_versions_message)\n\n\ndef _print_object_definition(urn_or_oid, version):\n    print(get_object_definition(urn_or_oid, version))\n\n\nif __name__ == '__main__':\n    logging.getLogger().setLevel(logging.INFO)\n\n    parser = argparse.ArgumentParser(description=\"Accesses LwM2M Object registry\")\n    parser.add_argument(\"-l\", \"--list\", action='store_true', help=\"List all registered LwM2M Objects\")\n    parser.add_argument(\"-g\", \"--get-xml\", type=str, metavar='urn_or_oid', help=\"Get Object definition XML by URN or ID\")\n    parser.add_argument(\"-v\", \"--object-version\", metavar='ver', type=str, help=\n        \"Explicitly choose version of an object if there exists more than one with the same ObjectID. Applicable only \"\n        \"with --get-xml argument. Without --object-version specified, most up to date version is chosen.\")\n\n    args = parser.parse_args()\n\n    if args.list and args.get_xml is not None:\n        print('conflicting options: --list, --get-xml', file=sys.stderr)\n        sys.exit(1)\n\n    if args.object_version is not None and args.get_xml is None:\n        print('--object-version option is applicable only with --get-xml', file=sys.stderr)\n        sys.exit(1)\n\n    if args.list:\n        _print_object_list()\n    elif args.get_xml is not None:\n        _print_object_definition(args.get_xml, args.object_version)\n    else:\n        parser.print_usage()\n        sys.exit(1)\n"
  },
  {
    "path": "tools/markdown-toc.py",
    "content": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nimport re\nimport argparse\nimport string\nimport sys\nimport collections\n\n\nTOC_START_MARKER = '<!-- toc -->'\nTOC_END_MARKER = '<!-- /toc -->'\n\n\nHeader = collections.namedtuple('Header', ['level', 'title'])\n\n\ndef extract_toc_headers(content):\n    headers = []\n\n    for line in content.split('\\n'):\n        # double hash is a dirty hack to skip comment lines in code blocks\n        m = re.fullmatch(r'(##+)(.*)', line)\n        if m is not None:\n            headers.append(Header(level=len(m.group(1)),\n                                  title=m.group(2).strip()))\n        elif line.strip() == TOC_END_MARKER:\n            # headers preceding TOC are not included in the table\n            headers = []\n\n    return headers\n\n\ndef strip_links(text):\n    return re.sub(r'\\[(.*?)]\\(.*?\\)', r'\\1', text)\n\n\ndef anchor_from_title(title):\n    # Should be fine, as it follows: https://github.com/jch/html-pipeline/blob/master/lib/html/pipeline/toc_filter.rb#L42\n    title = title.lower()\n    title = ''.join(c for c in title if c not in string.punctuation)\n    return title.replace(' ', '-')\n\n\ndef make_toc_from_headers(headers):\n    min_level = min(h.level for h in headers)\n    toc_string = '\\n'\n\n    for header in headers:\n        indent = '  ' * (header.level - min_level)\n        linkless_header = strip_links(header.title)\n        toc_string += '%s* [%s](#%s)\\n' % (indent, linkless_header, anchor_from_title(linkless_header))\n\n    return toc_string\n\n\nparser = argparse.ArgumentParser(formatter_class=argparse.RawDescriptionHelpFormatter,\n                                 description='''\nChecks or generates a table of contents for a Markdown file. TOC contents are\ninserted between %s and %s markers in the parsed file.\n\nIf markers are not present, TOC is not inserted.\n\nNote: top-level headers (ones starting with a single #) are not included in the TOC.\n'''.strip() % (TOC_START_MARKER, TOC_END_MARKER))\n\nparser.add_argument('files', metavar='FILE', nargs='+',\n                    help='Files to update table of contents in.')\nparser.add_argument('--update', '-u', action='store_true',\n                    help='Update TOC in specified files.')\nparser.add_argument('--check', '-c', action='store_true',\n                    help='Set non-zero exit code if TOC needs updating in any '\n                         'of specified files.')\n\ncmdline_args = parser.parse_args()\nhas_changes = False\n\nfor filename in cmdline_args.files:\n    with open(filename) as f:\n        content = f.read()\n\n    toc = make_toc_from_headers(extract_toc_headers(content))\n    new_content = re.sub(re.escape(TOC_START_MARKER) + '.*' + re.escape(TOC_END_MARKER),\n                         '%s\\n%s\\n%s' % (TOC_START_MARKER, toc, TOC_END_MARKER),\n                         content,\n                         flags=re.DOTALL)\n\n    if content == new_content:\n        print('%s: content not changed' % (filename,))\n    else:\n        print('%s: changed, new TOC:\\n%s' % (filename, toc))\n        has_changes = True\n\n        if cmdline_args.update:\n            with open(filename, 'w') as f:\n                f.write(new_content)\n\nif cmdline_args.check:\n    sys.exit(1 if has_changes else 0)\n"
  },
  {
    "path": "tools/provisioning-tool/configs/cert_info.json",
    "content": "{\n    \"countryName\": \"PL\",\n    \"stateOrProvinceName\": \"Malopolska\",\n    \"localityName\": \"Cracow\",\n    \"organizationName\": \"AVSystem\",\n    \"organizationUnitName\": \"Embedded\",\n    \"serialNumber\": 1,\n    \"validityOffsetInSeconds\": 220752000,\n    \"digest\": \"sha512\",\n    \"ellipticCurve\": \"secp384r1\"\n}\n"
  },
  {
    "path": "tools/provisioning-tool/configs/endpoint_cfg",
    "content": "{\n    OID.Security: {\n        1: {\n            RID.Security.ServerURI          : 'coaps://eu.iot.avsystem.cloud:5684',\n            RID.Security.Bootstrap          : False,\n            RID.Security.Mode               : 0,  # 0: PSK, 2: Cert, 3: NoSec\n            RID.Security.PKOrIdentity       : b'reg-test-psk-identity',\n            RID.Security.SecretKey          : b'3x@mpl3P5K53cr3tK3y',\n            RID.Security.ShortServerID      : 1\n        },\n    },\n\n    OID.Server: {\n        1: {\n            RID.Server.ShortServerID        : 1,\n            RID.Server.Lifetime             : 86400,\n            RID.Server.NotificationStoring  : False,\n            RID.Server.Binding              : 'U'\n        },\n    }\n}\n"
  },
  {
    "path": "tools/provisioning-tool/configs/lwm2m_server.json",
    "content": "{\n    \"url\": \"https://eu.iot.avsystem.cloud\",\n    \"port\": 8087,\n    \"domain\": \"/embedded/\"\n}\n"
  },
  {
    "path": "tools/provisioning-tool/factory_prov/__init__.py",
    "content": "# -*- coding: utf-8 -*-\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nimport os\nimport sys\n\nsys.path = [os.path.join(\n    os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))),\n    'tests', 'integration')] + sys.path\n"
  },
  {
    "path": "tools/provisioning-tool/factory_prov/cert_gen.py",
    "content": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\nimport datetime\n\nfrom cryptography import x509\nfrom cryptography.hazmat.primitives import hashes\nfrom cryptography.hazmat.primitives import serialization\nfrom cryptography.hazmat.primitives.asymmetric import ec, rsa\n\n_HASHES_BY_NAME = dict((hash.name, hash) for hash in hashes.__dict__.values() if\n                       isinstance(hash, type) and\n                       issubclass(hash, hashes.HashAlgorithm) and\n                       isinstance(hash.name, str))\n\n\ndef gen_cert_and_key(cert_info, key_file, cert_file):\n    if 'RSAKeyLen' in cert_info:\n        if 'ellipticCurve' in cert_info:\n            raise KeyError('RSAKeyLen and ellipticCurve specified together')\n        key = rsa.generate_private_key(65537, cert_info['RSAKeyLen'])\n    else:\n        curve = cert_info.get('ellipticCurve', 'secp256r1')\n        try:\n            curve = ec._CURVE_TYPES[curve]()\n        except KeyError:\n            raise ValueError('{} is not a supported elliptic curve'.format(curve))\n        key = ec.generate_private_key(curve)\n\n    try:\n        hash = _HASHES_BY_NAME[cert_info.get('digest', 'sha256')]()\n    except KeyError:\n        raise ValueError('{} is not a supported digest algorithm'.format(cert_info['digest']))\n\n    subject_attrs = []\n    if 'countryName' in cert_info:\n        subject_attrs.append(\n            x509.NameAttribute(x509.oid.NameOID.COUNTRY_NAME, cert_info['countryName']))\n    if 'stateOrProvinceName' in cert_info:\n        subject_attrs.append(x509.NameAttribute(x509.oid.NameOID.STATE_OR_PROVINCE_NAME,\n                                                cert_info['stateOrProvinceName']))\n    if 'localityName' in cert_info:\n        subject_attrs.append(\n            x509.NameAttribute(x509.oid.NameOID.LOCALITY_NAME, cert_info['localityName']))\n    if 'organizationName' in cert_info:\n        subject_attrs.append(\n            x509.NameAttribute(x509.oid.NameOID.ORGANIZATION_NAME, cert_info['organizationName']))\n    if 'organizationUnitName' in cert_info:\n        subject_attrs.append(x509.NameAttribute(x509.oid.NameOID.ORGANIZATIONAL_UNIT_NAME,\n                                                cert_info['organizationUnitName']))\n    if 'emailAddress' in cert_info:\n        subject_attrs.append(\n            x509.NameAttribute(x509.oid.NameOID.EMAIL_ADDRESS, cert_info['emailAddress']))\n    if 'commonName' in cert_info:\n        subject_attrs.append(\n            x509.NameAttribute(x509.oid.NameOID.COMMON_NAME, cert_info['commonName']))\n    else:\n        raise ValueError(\n            'Missing commonName information for certificate generation')\n\n    # create a self-signed cert\n    subject = x509.Name(subject_attrs)\n    now = datetime.datetime.utcnow()\n    cert_builder = (x509.CertificateBuilder().\n                    subject_name(subject).\n                    issuer_name(subject).\n                    public_key(key.public_key()).\n                    serial_number(cert_info.get('serialNumber', 1)).\n                    not_valid_before(now).\n                    not_valid_after(now + datetime.timedelta(\n        seconds=cert_info.get('validityOffsetInSeconds', 7 * 365 * 24 * 60 * 60))))\n\n    cert = cert_builder.sign(key, hash)\n\n    # Create two copies of cert. One in PEM format and one in DER format\n    # Key is need only in DER format\n    # PEM format certificate can be viewed using:\n    # openssl x509 -in selfsigned.crt -text -noout\n    #\n    # DER format certificate can be viewed using:\n    # openssl x509 -inform der -in selfsigned.der -text -noout\n    with open(f'{cert_file}.crt', 'wb') as f:\n        f.write(cert.public_bytes(serialization.Encoding.PEM))\n    with open(f'{cert_file}.der', 'wb') as f:\n        f.write(cert.public_bytes(serialization.Encoding.DER))\n    with open(f'{key_file}.der', 'wb') as f:\n        f.write(key.private_bytes(encoding=serialization.Encoding.DER,\n                                  format=serialization.PrivateFormat.PKCS8,\n                                  encryption_algorithm=serialization.NoEncryption()))\n"
  },
  {
    "path": "tools/provisioning-tool/factory_prov/factory_prov.py",
    "content": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nimport json\nimport os.path\n\nimport requests\nfrom factory_prov.cert_gen import *\nfrom framework import serialize_senml_cbor as ssc\nfrom framework_tools.lwm2m.coap.server import SecurityMode\nfrom framework.test_utils import *\n\nSupportedSecMode = [\n    SecurityMode.PreSharedKey,\n    SecurityMode.Certificate,\n    SecurityMode.NoSec,\n    SecurityMode.CertificateWithEst\n]\n\n\nclass CoioteRegistration:\n    def __init__(self, srv_info):\n        self.addr = srv_info.get('url', 'https://eu.iot.avsystem.cloud') + \\\n                    ':' + \\\n                    str(srv_info.get('port', 8087))\n\n        if 'access_token' not in srv_info:\n            raise ValueError(\n                'Missing access token to connect to Coiote DM API')\n        self.token = srv_info['access_token']\n\n        if 'endpoint_name' not in srv_info:\n            raise ValueError('Missing endpint name in configuration')\n        self.endpoint_name = srv_info['endpoint_name']\n\n        if 'domain' not in srv_info:\n            raise ValueError('Missing domain in configuration')\n        self.domain = srv_info['domain']\n\n        self.sec_mode = srv_info.get('sec_mode', 'nosec')\n        self.pk_id = srv_info.get('pk_identity', None)\n        self.pkey = srv_info.get('pkey', None)\n\n    def register(self):\n        API = '/api/coiotedm/v3/devices'\n        hex_psk = None\n        if self.pkey is not None:\n            hex_psk = {'HexadecimalPsk': self.pkey.hex()}\n\n        headers = {\n            'Authorization': 'Bearer ' + self.token,\n            'Accept': 'application/json'\n        }\n\n        request = {\n            'properties': {\n                'endpointName': self.endpoint_name\n            },\n            'connectorType': 'management',\n            'domain': self.domain,\n            'securityMode': self.sec_mode,\n            'dtlsIdentity': self.pk_id,\n            'dtlsPsk': hex_psk\n        }\n\n        resp = requests.post(url=self.addr + API,\n                             headers=headers, json=request)\n        if resp.ok:\n            print('Device \"%s\" successfully registered' % self.endpoint_name)\n        elif resp.status_code in [400, 403, 404, 409, 429, 503]:\n            raise ConnectionError(\n                f'{resp.status_code} ' + resp.json().get('error', 'No error message'))\n        elif 401 <= resp.status_code < 600:\n            resp.raise_for_status()\n        else:\n            raise ConnectionError(f'response: {resp.status_code}')\n\n\nclass FactoryProvisioning:\n    def __init__(self,\n                 endpoint_cfg,\n                 endpoint_name,\n                 server_info,\n                 token,\n                 cert_info):\n        if server_info is not None:\n            with open(server_info, 'r') as file:\n                self.srv_info = json.load(file)\n        else:\n            self.srv_info = None\n\n        self.endpoint_name = endpoint_name\n        self.access_token = token\n\n        # cfg_dict is used as a helper for easy extraction of resources\n        self.cfg_dict = {}\n\n        with open(endpoint_cfg, 'r') as file:\n            self.endpoint_cfg = file.read()\n            self.cfg_dict = config_to_dict(self.endpoint_cfg)\n\n            self.sec_mode = self.__extract_sec_mode()\n            if self.sec_mode == SecurityMode.PreSharedKey.value:\n                self.pk_id = self.__extract_pk_id()\n                self.pkey = self.__extract_pkey()\n\n        if cert_info is not None:\n            if os.path.isfile(cert_info):\n                with open(cert_info, 'r') as file:\n                    self.cert_info = json.load(file)\n        else:\n            self.cert_info = None\n\n        # private/public certificates\n        self.server_cert = None\n        self.endpoint_cert = None\n        self.endpoint_key = None\n\n    def __extract_resource(self, oid, rid):\n        if oid in self.cfg_dict:\n            list_of_inst = list(self.cfg_dict[oid].values())\n            if len(list_of_inst) != 1:\n                raise ValueError(\n                    'Invalid number of instances of object /%d. Only one instance supported.' % oid)\n            instance = list_of_inst[0]\n            if rid in instance:\n                return instance[rid]\n        raise ValueError(\n            'Missing /%d/*/%d resource in endpoint configuration' % (oid, rid))\n\n    def __extract_sec_mode(self):\n        retval = self.__extract_resource(OID.Security, RID.Security.Mode)\n        if retval in [v.value for v in SupportedSecMode]:\n            return retval\n        else:\n            raise ValueError(\n                'Unsupported Security Mode in endpoint configuration')\n\n    def __extract_pkey(self):\n        retval = self.__extract_resource(OID.Security, RID.Security.SecretKey)\n        if len(retval) > 0:\n            return retval\n        else:\n            raise ValueError('Security Object Secret Key resource is empty')\n\n    def __extract_pk_id(self):\n        retval = self.__extract_resource(\n            OID.Security, RID.Security.PKOrIdentity)\n        if len(retval) > 0:\n            return retval\n        else:\n            raise ValueError(\n                'Security Object Private Key ID resource is empty')\n\n    def __serialize_config(self):\n        return ssc.serialize_config(str(self.cfg_dict))\n\n    def get_sec_mode(self):\n        return str(SecurityMode(self.sec_mode))\n\n    def set_server_cert(self, cert):\n        if os.path.isfile(cert):\n            self.server_cert = cert\n        else:\n            raise OSError('Missing server cert')\n\n    def set_endpoint_cert_and_key(self, cert, key):\n        if os.path.isfile(cert) and os.path.isfile(key):\n            self.endpoint_cert = cert\n            self.endpoint_key = key\n        else:\n            raise OSError('Missing endpoint cert/key')\n\n    def generate_self_signed_cert(self):\n        if self.cert_info is None:\n            raise ValueError('Missing information for certificate generation')\n\n        print('Generating device certificates...')\n        try:\n            os.mkdir('cert')\n        except FileExistsError:\n            pass\n        cert_dir = os.path.realpath('cert')\n        key_filename = os.path.join(cert_dir, 'client_key')\n        cert_filename = os.path.join(cert_dir, 'client_cert')\n\n        if 'commonName' in self.cert_info:\n            cert_info = self.cert_info\n        else:\n            cert_info = {**self.cert_info, 'commonName': self.endpoint_name}\n\n        gen_cert_and_key(cert_info, key_filename, cert_filename)\n        try:\n            self.set_endpoint_cert_and_key(cert_filename + '.der', key_filename + '.der')\n            print('Certificates generated')\n        except:\n            raise RuntimeError('Failed to generate certificate')\n\n    def provision_device(self):\n        print(f'Security Mode set to \"{SecurityMode(self.sec_mode)}\"')\n\n        if self.sec_mode == SecurityMode.Certificate.value or self.sec_mode == SecurityMode.CertificateWithEst.value:\n            list_of_sec_inst = list(self.cfg_dict[0].values())\n            if len(list_of_sec_inst) != 1:\n                raise ValueError(\n                    'Invalid number of Security instances. Requires exactly one instance.')\n\n            sec_inst = list_of_sec_inst[0]\n\n            if self.server_cert is not None:\n                print(f'Load server cert: {self.server_cert}')\n                with open(self.server_cert, 'rb') as cert:\n                    sec_inst[RID.Security.ServerPKOrIdentity] = cert.read()\n            else:\n                raise ValueError('Missing server cert')\n\n            if self.endpoint_cert is not None:\n                print(f'Load endpoint cert: {self.endpoint_cert}')\n                with open(self.endpoint_cert, 'rb') as cert:\n                    sec_inst[RID.Security.PKOrIdentity] = cert.read()\n            else:\n                raise ValueError('Missing endpoint cert')\n\n            if self.endpoint_key is not None:\n                print(f'Load endpoint key: {self.endpoint_key}')\n                with open(self.endpoint_key, 'rb') as cert:\n                    sec_inst[RID.Security.SecretKey] = cert.read()\n            else:\n                raise ValueError('Missing endpoint key')\n\n        print('Serializing endpoint configuration...')\n        cbor_blob = self.__serialize_config()\n        if len(cbor_blob) > 0:\n            print('SenML CBOR blob created')\n        else:\n            raise RuntimeError('Failed to serialized endpoint configuration')\n\n        # TODO: open device and send blob/certs to the device, for now dump blob to file\n        with open('SenMLCBOR', 'wb') as file:\n            file.write(cbor_blob)\n\n        print('Load endpoint configuration: SenMLCBOR')\n\n    def register(self):\n        if self.srv_info is None:\n            raise ValueError(\n                'Missing Coiote server information for registration operation')\n\n        if self.access_token is None:\n            raise ValueError(\n                'Missing access token for authorization to Coiote server')\n\n        if self.endpoint_name is None:\n            raise ValueError(\n                'Missing endpoint name for registration to Coiote server')\n\n        self.srv_info['sec_mode'] = str(SecurityMode(self.sec_mode))\n        self.srv_info['access_token'] = self.access_token\n        self.srv_info['endpoint_name'] = self.endpoint_name\n        if self.sec_mode == SecurityMode.PreSharedKey.value:\n            self.srv_info['pk_identity'] = self.pk_id.decode()\n            self.srv_info['pkey'] = self.pkey\n        print('Register endpoint to Coiote...')\n        CoioteServer = CoioteRegistration(self.srv_info)\n        CoioteServer.register()\n"
  },
  {
    "path": "tools/provisioning-tool/ptool.py",
    "content": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nimport argparse\nimport requests\nimport sys\nfrom factory_prov import factory_prov as fp\n\n\nif __name__ == '__main__':\n    parser = argparse.ArgumentParser(\n        description='Factory provisioning tool')\n    parser.add_argument('-c', '--endpoint_cfg', type=str,\n                        help='Configuration file containing device information to be loaded on the device',\n                        required=True)\n    parser.add_argument('-e', '--URN', type=str,\n                        help='Endpoint name to use during registration',\n                        required=False)\n    parser.add_argument('-s', '--server', type=str,\n                        help='JSON format file containing Coiote server information',\n                        required=False)\n    parser.add_argument('-t', '--token', type=str,\n                        help='Access token for authorization to Coiote server',\n                        required=False)\n    parser.add_argument('-C', '--cert', type=str,\n                        help='JSON format file containing information for the generation of a self signed certificate',\n                        required=False)\n    parser.add_argument('-k', '--pkey', type=str,\n                        help='Endpoint private key in DER format, ignored if CERT parameter is set',\n                        required=False)\n    parser.add_argument('-r', '--pcert', type=str,\n                        help='Endpoint public cert in DER format, ignored if CERT parameter is set',\n                        required=False,)\n    parser.add_argument('-p', '--scert', type=str,\n                        help='Server public cert in DER format',\n                        required=False)\n\n    args = parser.parse_args()\n\n    ret_val = 1\n\n    try:\n        fcty = fp.FactoryProvisioning(args.endpoint_cfg, args.URN, args.server,\n                                      args.token, args.cert)\n        if fcty.get_sec_mode() == 'cert' or fcty.get_sec_mode() == 'est':\n            if args.scert is not None:\n                fcty.set_server_cert(args.scert)\n\n            if args.cert is not None:\n                fcty.generate_self_signed_cert()\n            elif args.pkey is not None and args.pcert is not None:\n                fcty.set_endpoint_cert_and_key(args.pcert, args.pkey)\n\n        fcty.provision_device()\n\n        if args.server is not None and args.token is not None and args.URN is not None:\n            fcty.register()\n\n        ret_val = 0\n    except ValueError as err:\n        print('Incorrect configuration:', err)\n    except ConnectionError as err:\n        print('Coiote server error:', err)\n    except requests.HTTPError as err:\n        print(err)\n    except OSError as err:\n        print(err)\n    except RuntimeError as err:\n        print(err)\n    except:\n        print('Unexpected error, abort script execution')\n    finally:\n        sys.exit(ret_val)\n"
  },
  {
    "path": "tools/symlink-check.sh",
    "content": "#!/usr/bin/env bash\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nset -e\n\nEXCEPTIONS=(\n    \"^\\./\\.git/\"\n    \"^\\./examples/\"\n    \"^\\./test_ghactions\"\n    \"/doc/sphinx/html/\"\n    \"^\\./venv/\"\n)\n\ncd \"$(dirname \"$(dirname \"$0\")\")\"\n\nfind . -type l | grep -v -f <(for ((I=0; I<\"${#EXCEPTIONS[@]}\"; ++I)); do\n    echo \"${EXCEPTIONS[I]}\"\ndone) | {\n    FOUND=0\n    while read REPLY; do\n        echo \"$REPLY\"\n        FOUND=1\n    done\n\n    exit \"$FOUND\"\n}\n"
  },
  {
    "path": "tools/test-framework-tools/.gitignore",
    "content": "__pycache__/\n*.orig\n*.pyc\n*.cache\n\n# Python venv\n/venv/\nbuild/\ndist/\n*.egg-info/\n\n# py-build-cmake cache\n.py-build-cmake_cache\n"
  },
  {
    "path": "tools/test-framework-tools/nsh-lwm2m/cbor_shell.py",
    "content": "# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nimport os\nimport sys\n\nimport powercmd\nimport typing\nfrom framework_tools.lwm2m.messages import EscapedBytes\nfrom framework_tools.lwm2m.senml_cbor import *\n\ndef encode_int(data):\n    return { SenmlLabel.VALUE: int(data) }\n\ndef encode_double(data):\n    return { SenmlLabel.VALUE: float(data) }\n\ndef encode_string(data):\n    return { SenmlLabel.STRING: str(data, 'ascii') }\n\ndef encode_opaque(data):\n    return { SenmlLabel.OPAQUE: data }\n\ndef encode_objlnk(data):\n    return { SenmlLabel.OBJLNK: str(data, 'ascii') }\n\n\nclass ResourceType:\n    TYPES = {\n        'int': encode_int,\n        'double': encode_double,\n        'string': encode_string,\n        'opaque': encode_opaque,\n        'objlnk': encode_objlnk,\n    }\n    DEFAULT = TYPES['string']\n\n    @staticmethod\n    def powercmd_parse(text):\n        return ResourceType.TYPES.get(text, ResourceType.DEFAULT)\n\n    @staticmethod\n    def powercmd_complete(text):\n        from powercmd.match_string import match_string\n        return match_string(text, ResourceType.TYPES.keys())\n\n\nclass CBORBuilderShell(powercmd.Cmd):\n    def __init__(self, prompt):\n        super().__init__()\n        self.prompt = prompt\n        self.data = []\n\n    def _name_to_idx(self, name):\n        for idx, entry in enumerate(self.data):\n            if entry[SenmlLabel.NAME] == name:\n                return idx\n        return -1\n\n    def do_show(self):\n        print(CBOR.parse(CBOR.serialize(self.data)), '\\n')\n\n    def do_serialize(self):\n        print(''.join('\\\\x%02x' % (c,) for c in CBOR.serialize(self.data)))\n\n    def do_remove(self,\n                  name: str):\n        idx = self._name_to_idx(name)\n        if idx == -1:\n            raise ValueError('Invalid name')\n        del self.data[idx]\n\n    def do_add_resource(self,\n                        name: str,\n                        type: ResourceType = ResourceType.DEFAULT,\n                        value: EscapedBytes = None,\n                        basename: str = None):\n        if self._name_to_idx(name) != -1:\n            return ValueError('Path with such name already exists')\n\n        entry = {\n            SenmlLabel.NAME: name\n        }\n        if basename is not None:\n            entry[SenmlLabel.BASE_NAME] = basename\n        if value is not None:\n            entry.update(type(value))\n        self.data += [ entry ]\n"
  },
  {
    "path": "tools/test-framework-tools/nsh-lwm2m/nsh_lwm2m.py",
    "content": "#!/usr/bin/env python3\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nimport os\nimport sys\n\nassert sys.version_info >= (3, 5), \"Python < 3.5 is unsupported\"\n\nSCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))\n\nfrom framework_tools.lwm2m.coap.server import SecurityMode\nfrom framework_tools.lwm2m.server import Lwm2mServer\nfrom framework_tools.lwm2m.messages import *\nfrom framework_tools.lwm2m.tlv import TLV\nfrom framework_tools.lwm2m import coap\nfrom cbor_shell import CBORBuilderShell\nfrom tlv_shell import TLVBuilderShell\nimport powercmd\nfrom prompt_toolkit.history import FileHistory\nfrom typing import List, Optional, Tuple, Mapping\nimport logging\nimport select\nimport enum\nimport socket\nimport re\nimport glob\nimport collections\nimport binascii\nimport argparse\n\n\nREGISTER_PATH = '/rd/demo'\nDEFAULT_COAP_PORT = 5683\nDEFAULT_COAPS_PORT = 5684\n\n\ndef block_size(s):\n    val = int(s)\n    if not 2 ** 4 <= val <= 2 ** 10:\n        raise ValueError('invalid block size, expected 16 <= %d <= 1024' % (val,))\n    if val & val - 1:\n        raise ValueError('invalid block size, expected power of 2, got %d' % (val,))\n    return val\n\n\ndef chunks(seq, n):\n    for i in range(0, len(seq), n):\n        yield seq[i:i + n]\n\n\ndef make_response(req, code, content='', extra_opts=[], type=coap.Type.ACKNOWLEDGEMENT):\n    return coap.Packet(type=type,\n                       code=code,\n                       msg_id=(req.msg_id if type in (coap.Type.ACKNOWLEDGEMENT, coap.Type.RESET) else ANY),\n                       token=req.token,\n                       options=([coap.ContentFormatOption.APPLICATION_OCTET_STREAM] if content else []) + extra_opts,\n                       content=content)\n\n\ndef _create_coap_server(port: int,\n                        psk_identity: str = None,\n                        psk_key: str = None,\n                        ca_path: str = None,\n                        ca_file: str = None,\n                        crt_file: str = None,\n                        key_file: str = None,\n                        ipv6: bool = False,\n                        tcp: bool = False,\n                        debug: bool = False,\n                        reuse_port: bool = False,\n                        connection_id: str = ''):\n    \"\"\"\n    Sets up a CoAP(s) server bound to given PORT.\n\n    If any of: PSK_IDENTITY, PSK_KEY, CA_PATH, CA_FILE, CRT_FILE, KEY_FILE\n    are specified, sets up a DTLS server, otherwise - raw CoAP server.\n    \"\"\"\n    use_psk = psk_identity is not None and psk_key is not None\n    use_cert = any((ca_path, ca_file, crt_file, key_file))\n    if use_psk and use_cert:\n        raise RuntimeError(\"Cannot use PSK and Certificates at the same time\")\n\n    use_dtls = use_psk or use_cert\n    if debug and not use_dtls:\n        print('warning: debug mode not available on non-DTLS sockets')\n\n    if port is None or port < 0:\n        port = DEFAULT_COAPS_PORT if use_dtls else DEFAULT_COAP_PORT\n\n    if use_dtls:\n        if tcp:\n            raise RuntimeError('Only DTLS servers are supported')\n        if use_psk:\n            return coap.DtlsServer(psk_key=psk_key, psk_identity=psk_identity,\n                                   listen_port=port, debug=debug, use_ipv6=ipv6,\n                                   reuse_port=reuse_port, connection_id=connection_id)\n        else:\n            return coap.DtlsServer(ca_path=ca_path, ca_file=ca_file, crt_file=crt_file,\n                                   key_file=key_file, listen_port=port, debug=debug,\n                                   use_ipv6=ipv6, reuse_port=reuse_port, connection_id=connection_id)\n    else:\n        if len(connection_id) > 0:\n            raise RuntimeError('Cannot use connection_id in NoSec mode')\n\n        transport = coap.transport.Transport.UDP\n        if tcp:\n            transport = coap.transport.Transport.TCP\n        return coap.Server(port, ipv6, reuse_port=reuse_port, transport=transport)\n\n\nclass CoapFileServer:\n    def __init__(self,\n                 root_directory: str,\n                 port: int,\n                 psk_identity: str = None,\n                 psk_key: str = None,\n                 ca_path: str = None,\n                 ca_file: str = None,\n                 crt_file: str = None,\n                 key_file: str = None,\n                 ipv6: bool = False,\n                 debug: bool = False):\n        assert port != 0\n\n        self.root_directory = os.path.abspath(root_directory)\n        self.listen_port = port\n        self.psk_identity = psk_identity\n        self.psk_key = psk_key\n        self.ca_path = ca_path\n        self.ca_file = ca_file\n        self.crt_file = crt_file\n        self.key_file = key_file\n        self.ipv6 = ipv6\n        self.debug = debug\n\n    def _path_to_filename(self, path: str):\n        return os.path.join(self.root_directory, path.lstrip('/'))\n\n    def _read_file_chunk(self,\n                         path: str,\n                         offset: int,\n                         size: int) -> Tuple[bytes, bool]:  # data, has_more\n        file_path = self._path_to_filename(path)\n        stat = os.stat(file_path)\n\n        with open(file_path, 'rb') as f:\n            f.seek(offset)\n            data = f.read(size)\n\n        return (data, offset + size < stat.st_size)\n\n    @staticmethod\n    def _handle_bad_request(req):\n        return coap.Packet(type=coap.Type.ACKNOWLEDGEMENT,\n                           code=coap.Code.RES_BAD_REQUEST,\n                           msg_id=req.msg_id,\n                           token=req.token)\n\n    @staticmethod\n    def _handle_ping(req):\n        return coap.Packet(type=coap.Type.RESET,\n                           code=coap.Code.EMPTY,\n                           msg_id=req.msg_id,\n                           token=b'')\n\n    def _handle_get(self, req):\n        path = req.get_uri_path()\n        block = req.get_options(coap.Option.BLOCK2)\n\n        try:\n            with open(self._path_to_filename(path), 'rb') as f:\n                crc32 = binascii.unhexlify(hex(binascii.crc32(f.read()))[len('0x'):])\n\n            seq_num = 0\n            block_size = 1024\n            if block:\n                seq_num = block[0].seq_num()\n                block_size = block[0].block_size()\n\n            data, has_more = self._read_file_chunk(path,\n                                                   offset=seq_num * block_size,\n                                                   size=block_size)\n\n            extra_opts = [coap.Option.ETAG(crc32)]\n            if block or has_more:\n                extra_opts += [coap.Option.BLOCK2(seq_num=seq_num,\n                                                  has_more=has_more,\n                                                  block_size=block_size)]\n\n                return make_response(req=req,\n                                     code=coap.Code.RES_CONTENT,\n                                     content=data,\n                                     extra_opts=extra_opts)\n        except FileNotFoundError:\n            return make_response(req=req,\n                                 code=coap.Code.RES_NOT_FOUND)\n\n    @staticmethod\n    def _handle_unsupported(req):\n        return coap.Packet(type=coap.Type.ACKNOWLEDGEMENT,\n                           code=coap.Code.RES_METHOD_NOT_ALLOWED,\n                           msg_id=req.msg_id,\n                           token=req.token)\n\n    @staticmethod\n    def _packet_summary(pkt):\n        def block_summary(pkt):\n            blk1 = pkt.get_options(coap.Option.BLOCK1)\n            blk2 = pkt.get_options(coap.Option.BLOCK2)\n            return ', '.join(['']\n                             + ([repr(blk1[0])] if blk1 else [])\n                             + ([repr(blk2[0])] if blk2 else []))\n\n        return ('%s, %s, id=%s, token=%s%s'\n                % (pkt.code,\n                   pkt.type,\n                   pkt.msg_id,\n                   pkt.token,\n                   block_summary(pkt)))\n\n    def _handle_packet(self, serv, req):\n        res = None\n\n        try:\n            print('<- %s' % self._packet_summary(req))\n            if self.debug:\n                print(req)\n\n            if req.type not in (coap.Type.CONFIRMABLE, coap.Type.NON_CONFIRMABLE):\n                print('-> ignored (neither CON or NON)')\n                return\n\n            if req.code == coap.Code.EMPTY:\n                if len(req.token) == 0:\n                    res = self._handle_ping(req)\n                else:\n                    res = self._handle_bad_request(req)\n\n            if req.code == coap.Code.REQ_GET:\n                res = self._handle_get(req)\n\n            if res is None:\n                res = self._handle_unsupported(req)\n\n            print('-> %s' % self._packet_summary(res))\n            if self.debug:\n                print(res)\n        except KeyboardInterrupt:\n            raise\n        except:\n            import traceback\n            traceback.print_exc()\n            res = make_response(req=req,\n                                code=coap.Code.RES_INTERNAL_SERVER_ERROR)\n\n        serv.send(res)\n\n    def _create_server(self):\n        serv = _create_coap_server(port=self.listen_port,\n                                   psk_identity=self.psk_identity,\n                                   psk_key=self.psk_key,\n                                   ca_path=self.ca_path,\n                                   ca_file=self.ca_file,\n                                   crt_file=self.crt_file,\n                                   key_file=self.key_file,\n                                   ipv6=self.ipv6,\n                                   debug=self.debug,\n                                   reuse_port=True)\n        return (serv.socket.fileno(), serv)\n\n    @staticmethod\n    def _poll_event_to_string(evt):\n        return {\n            select.POLLIN: 'POLLIN',\n            select.POLLOUT: 'POLLOUT',\n            select.POLLERR: 'POLLERR',\n            select.POLLPRI: 'POLLPRI',\n            select.POLLHUP: 'POLLHUP',\n            select.POLLNVAL: 'POLLNVAL',\n        }.get(evt, 'unknown poll event: %d' % evt)\n\n    def serve_forever(self):\n        servers = {}\n        poll = select.poll()\n\n        def ensure_listening_server_exists():\n            if not any(s.get_remote_addr() is None for s in servers.values()):\n                serv_fd, coap_serv = self._create_server()\n                servers[serv_fd] = coap_serv\n                poll.register(serv_fd, select.POLLIN | select.POLLERR | select.POLLHUP)\n\n        try:\n            print('Serving directory %s on port %s...' % (self.root_directory, self.listen_port))\n            print('Press CTRL-C to stop')\n\n            while True:\n                ensure_listening_server_exists()\n                for fd, event in poll.poll():\n                    if event in (select.POLLERR, select.POLLHUP):\n                        print('fd %d: %s' % (fd, self._poll_event_to_string(event)))\n                        del servers[fd]\n                    elif event == select.POLLIN:\n                        try:\n                            serv = servers[fd]\n                            pkt = serv.recv()\n                            # recv() connects the server to a remote endpoint; make sure\n                            # there is always an \"unconnected\" one\n                            ensure_listening_server_exists()\n                            self._handle_packet(serv, pkt)\n                        except ValueError as e:\n                            print(e)\n                    else:\n                        print('fd %d: %s' % (fd, self._poll_event_to_string(event)))\n        finally:\n            for serv in servers.values():\n                try:\n                    serv.close()\n                except:\n                    pass\n\n\nSend = collections.namedtuple('Send', ['msg'])\nRecv = collections.namedtuple('Recv', ['msg'])\n\nNSH_HISTORY_FILE = os.path.join(os.path.expanduser('~'), '.nsh_history')\n\n\nclass Lwm2mCmd(powercmd.Cmd):\n    def set_prompt(self, extra_text=None):\n        extra_text = ' %s' % (extra_text,) if extra_text else ''\n        self.prompt = '[%s]%s $ ' % (self.__class__.__name__, extra_text)\n\n    def __init__(self, cmdline_args):\n        super().__init__(history=FileHistory(NSH_HISTORY_FILE))\n\n        self.cmdline_args = cmdline_args\n        self.serv = None\n        self.payload_buffer = b''\n        self.expected_message = ANY\n        self.set_prompt()\n        self.history = []\n\n        self.auto_reregister = True\n        self.auto_update = True\n        self.auto_ack = True\n        self.auto_bspack_error = True\n\n        self.cached_bspack_request = None\n\n        self.history = []\n\n        if cmdline_args.listen is not None:\n            port = self.cmdline_args.listen\n            if port is None or port < 0:\n                use_dtls = self.cmdline_args.psk_identity is not None and self.cmdline_args.psk_key is not None\n                port = DEFAULT_COAPS_PORT if use_dtls else DEFAULT_COAP_PORT\n            try:\n                self.do_listen()\n            except Exception as e:\n                print('could not listen on port %d (%s)' % (port, e))\n                print('use \"listen\" command to start the server')\n\n    def _get_last_request(self):\n        for entry in reversed(self.history):\n            if isinstance(entry, Recv) and entry.msg.code.is_request():\n                return entry.msg\n\n        return None\n\n    def do_reset_history(self):\n        \"Clears command history.\"\n        self.history = []\n\n    def do_details(self,\n                   idx: int = 1):\n        \"\"\"\n        Displays details of a recent message.\n\n        Examples:\n            /details     - display last message\n            /details NUM - display NUM-th last message\n        \"\"\"\n\n        for entry in reversed(self.history):\n            if (isinstance(entry, Recv)\n                    or isinstance(entry, Send)):\n                idx -= 1\n                if idx <= 0:\n                    print('\\n*** %s ***' % (entry.__class__.__name__))\n                    print(entry.msg.details())\n                    return\n\n        print('message not found')\n\n    def do_connect(self,\n                   host: str = None,\n                   port: int = DEFAULT_COAP_PORT,\n                   bind: int = 0):\n        \"\"\"\n        Connects the socket to given HOST:PORT. Future packets will be sent to\n        this address.\n        \"\"\"\n        host = host or ('::1' if self.cmdline_args.ipv6 else '127.0.0.1')\n        self.serv = None\n        self.serv = coap.Server(use_ipv6=self.cmdline_args.ipv6, listen_port=bind)\n        self.serv.connect_to_client((host, port))\n        print('new remote endpoint: %s:%d' % (host, port))\n\n    def do_unconnect(self):\n        \"\"\"\n        \"Unconnects\" the socket from an already accepted client. The idea is that\n        then the server will be able to receive packets from different (host, port),\n        which may be useful for testing purposes.\n        \"\"\"\n        self.serv._raw_udp_socket.connect(('', 0))\n\n    def do_listen(self,\n                  port: int = None,\n                  psk_identity: str = None,\n                  psk_key: str = None,\n                  ca_path: str = None,\n                  ca_file: str = None,\n                  crt_file: str = None,\n                  key_file: str = None,\n                  ipv6: bool = None,\n                  tcp: bool = None,\n                  debug: bool = None,\n                  connection_id: str = ''):\n        \"\"\"\n        Starts listening on given PORT. If PSK_IDENTITY and PSK_KEY are\n        specified, sets up a DTLS server, otherwise - raw CoAP server.\n        \"\"\"\n\n        try:\n            self.serv = None\n\n            port = port or self.cmdline_args.listen\n            psk_identity = psk_identity or self.cmdline_args.psk_identity\n            psk_key = psk_key or self.cmdline_args.psk_key\n            debug = debug or self.cmdline_args.debug\n            ipv6 = ipv6 or self.cmdline_args.ipv6\n            tcp = tcp or self.cmdline_args.tcp\n\n            coap_serv = _create_coap_server(port=port,\n                                            psk_identity=psk_identity,\n                                            psk_key=psk_key,\n                                            ca_path=ca_path,\n                                            ca_file=ca_file,\n                                            crt_file=crt_file,\n                                            key_file=key_file,\n                                            ipv6=ipv6,\n                                            tcp=tcp,\n                                            debug=debug,\n                                            connection_id=connection_id)\n            self.serv = Lwm2mServer(coap_serv)\n\n            print('waiting for a client on port %d ...'\n                  % (self.serv.get_listen_port(),))\n            self.serv.listen(timeout_s=None)\n            msg = self._recv()\n\n            # When using IPv6, get_remote_addr() returns 4-tuple with some additional\n            # information we don't need.\n            self.set_prompt('port: %d, client: %s:%d'\n                            % ((self.serv.get_listen_port(),) + self.serv.get_remote_addr()[:2]))\n\n            if isinstance(msg, Lwm2mRegister):\n                self._send(Lwm2mCreated.matching(msg)(location=REGISTER_PATH))\n            elif isinstance(msg, Lwm2mRequestBootstrap):\n                self._send(Lwm2mChanged.matching(msg)())\n            elif isinstance(msg, Lwm2mBootstrapPackRequest):\n                print(\"Received Bootstrap Pack Request\")\n                if self.auto_bspack_error:\n                    self._send(Lwm2mErrorResponse.matching(msg)(code=coap.Code.RES_NOT_FOUND))\n                else:\n                    self.cached_bspack_request = msg\n\n            self.payload_buffer = b''\n        except KeyboardInterrupt:\n            pass\n        except Exception as e:\n            print(e)\n            raise e\n\n    def do_payload_buffer_clear(self):\n        self.payload_buffer = b''\n\n    def do_payload_buffer_show(self):\n        print(repr(self.payload_buffer))\n\n    def do_payload_buffer_show_hex(self):\n        print(coap.utils.hexlify(self.payload_buffer))\n\n    def do_payload_buffer_show_tlv(self):\n        print(TLV.parse(self.payload_buffer))\n\n    def _msg_verbose_compare(self, expected, actual):\n        log = ''\n\n        if expected is None:\n            log += 'no message expected'\n        elif actual is None:\n            log += 'no message received'\n        else:\n            if actual.version is not None:\n                if expected.version != actual.version:\n                    log += 'unexpected CoAP version\\n'\n            if actual.type is not None:\n                if expected.type != actual.type:\n                    log += 'unexpected CoAP type\\n'\n            if expected.code != actual.code:\n                log += 'unexpected CoAP code\\n'\n\n            if expected.msg_id is not ANY:\n                if expected.msg_id != actual.msg_id:\n                    log += 'unexpected CoAP message ID\\n'\n            if expected.token is not ANY:\n                if expected.token != actual.token:\n                    log += 'unexpected CoAP token\\n'\n            if expected.options is not ANY:\n                if expected.options != actual.options:\n                    log += 'unexpected CoAP option list\\n'\n            if expected.content is not ANY:\n                if expected.content != actual.content:\n                    log += 'unexpected CoAP content\\n'\n\n        if log:\n            print('*** unexpected message ***\\n'\n                  '%s\\n'\n                  '--- GOT ---\\n'\n                  '%s\\n'\n                  '--- EXPECTED ---\\n'\n                  '%s\\n'\n                  '------' % (log, actual, expected))\n\n    def _recv(self, timeout_s=None):\n        msg = None\n\n        try:\n            pkt = self.serv.recv(timeout_s)\n            msg = get_lwm2m_msg(pkt)\n            print('<- %s' % (msg.summary(),))\n            self.history.append(Recv(msg))\n\n            self.payload_buffer += pkt.content\n        finally:\n            if self.expected_message is not ANY:\n                self._msg_verbose_compare(self.expected_message, msg)\n                self.expected_message = ANY\n\n        return msg\n\n    def _send(self, msg: Lwm2mMsg, timeout_s: float = 3):\n        if not self.serv:\n            raise Exception('not connected to any remote host')\n\n        msg.fill_placeholders()\n\n        print('-> %s' % (msg.summary(),))\n        self.history.append(Send(msg))\n        self.serv.send(msg)\n\n        if msg.type == coap.Type.CONFIRMABLE:\n            try:\n                return self._recv(timeout_s=timeout_s)\n            except socket.timeout:\n                print('response not received')\n\n    def do_coap(self,\n                type: coap.Type = coap.Type.CONFIRMABLE,\n                code: coap.Code = coap.Code.REQ_GET,\n                msg_id: int = ANY,\n                token: EscapedBytes = b'',\n                options: List[coap.Option] = [],\n                content: EscapedBytes = b'',\n                respond: bool = True):\n        \"\"\"\n        Send a custom CoAP message.\n\n        If message CODE indicates a response, attempts to match MSG_ID and\n        TOKEN to a last received request unless RESPOND is set to False.\n        \"\"\"\n        if msg_id != ANY or token != b'' or not code.is_response:\n            respond = False\n\n        if respond:\n            last_req = self._get_last_request()\n            if not last_req:\n                raise ValueError('no request to respond to')\n            else:\n                print('responding to: %s' % (last_req.summary(),))\n\n            self._send(Lwm2mMsg.from_packet(make_response(last_req, code, content, options)))\n        else:\n            self._send(Lwm2mMsg(type=type,\n                                code=code,\n                                msg_id=msg_id,\n                                token=token,\n                                options=options,\n                                content=content))\n\n    def do_lwm2m_decode(self,\n                        data: EscapedBytes):\n        \"\"\"\n        Decodes a CoAP message and displays it in a human-readable form.\n        \"\"\"\n        print(get_lwm2m_msg(coap.Packet.parse(data)))\n\n    def do_coap_decode(self,\n                       data: EscapedBytes):\n        \"\"\"\n        Decodes a CoAP message and displays it in a human-readable form.\n        \"\"\"\n        print(coap.Packet.parse(data))\n\n    def do_tlv(self):\n        \"\"\"\n        Launch a TLV sub-shell that facilitates creating TLV payloads.\n        \"\"\"\n        bracket_idx = self.prompt.index(']')\n        prompt = self.prompt[:bracket_idx] + '/TLV' + self.prompt[bracket_idx:]\n\n        tlv_shell = TLVBuilderShell(prompt)\n        tlv_shell.cmdloop()\n        tlv_shell.do_serialize()\n\n    def do_cbor(self):\n        \"\"\"\n        Launch a CBOR sub-shell that facilitates creating CBOR payloads.\n        \"\"\"\n        bracket_idx = self.prompt.index(']')\n        prompt = self.prompt[:bracket_idx] + '/CBOR' + self.prompt[bracket_idx:]\n\n        cbor_shell = CBORBuilderShell(prompt)\n        cbor_shell.cmdloop()\n        cbor_shell.do_serialize()\n\n    def do_set(self,\n               auto_update: bool = None,\n               auto_reregister: bool = None,\n               auto_bspack_error: bool = None,\n               auto_ack: bool = None):\n        if auto_reregister is not None:\n            self.auto_reregister = auto_reregister\n            print('Auto register responses %s' % ('enabled' if self.auto_reregister else 'disabled',))\n        if auto_update is not None:\n            self.auto_update = auto_update\n            print('Auto update responses %s' % ('enabled' if self.auto_update else 'disabled',))\n        if auto_bspack_error is not None:\n            self.auto_bspack_error = auto_bspack_error\n            print('Auto Bootstrap Pack error responses %s' %\n                  ('enabled' if self.auto_bspack_error else 'disabled',))\n        if auto_ack is not None:\n            self.auto_ack = auto_ack\n            print('Auto 0.00 ACK responses %s' % ('enabled' if self.auto_ack else 'disabled',))\n\n    def try_read(self, timeout_s=0.01):\n        try:\n            msg = self._recv(timeout_s=timeout_s)\n\n            if isinstance(msg, Lwm2mDeregister):\n                self._send(Lwm2mDeleted.matching(msg)())\n            elif self.auto_reregister and isinstance(msg, Lwm2mRegister):\n                self._send(Lwm2mCreated.matching(msg)(location=REGISTER_PATH))\n            elif (self.auto_update and isinstance(msg, Lwm2mUpdate)) or isinstance(msg, Lwm2mSend):\n                self._send(Lwm2mChanged.matching(msg)())\n            elif (self.auto_ack and msg.type == coap.Type.CONFIRMABLE):\n                self._send(Lwm2mEmpty.matching(msg)())\n\n            return msg\n        except socket.timeout:\n            pass\n\n    def do_sleep(self, timeout_s: float):\n        \"\"\"\n        Blocks for TIMEOUT_S seconds.\n        \"\"\"\n        import time\n        time.sleep(timeout_s)\n\n    def do_recv(self, timeout_s: float = None):\n        \"\"\"\n        Waits for a next incoming message. If TIMEOUT_S is specified, the\n        command will not wait longer than TIMEOUT_S if no messages are received.\n        \"\"\"\n        self.try_read(timeout_s)\n\n    def do_expect(self,\n                  msg_code: str):\n        \"\"\"\n        Makes the shell compare next received packet against the one configured\n        via this command and print a message if a mismatch is detected.\n\n        MSG_CODE can be:\n        - a string with Python code that evalutes to a correct message,\n        - None, if no messages are expected,\n        - ANY to disable checking (default).\n\n        Note: after receiving each message the \"expected\" value is set to ANY.\n        \"\"\"\n        self.expected_message = eval(msg_code)\n        if isinstance(self.expected_message, Lwm2mMsg):\n            print('Expecting: %s' % (self.expected_message.summary()))\n        else:\n            print('Expecting: %s' % (str(self.expected_message),))\n\n    def prepare_bootstrap(self,\n                          uri,\n                          security_mode,\n                          psk_identity,\n                          client_cert_path,\n                          psk_key,\n                          client_private_key_path,\n                          server_cert_path):\n        if security_mode is None:\n            security_mode = {\n                'coap': SecurityMode.NoSec,\n                'coaps': SecurityMode.PreSharedKey\n            }[uri.split(':')[0]]\n\n        pubkey_or_identity = b''\n        if psk_identity:\n            pubkey_or_identity = psk_identity\n        elif client_cert_path:\n            with open(client_cert_path, 'rb') as f:\n                pubkey_or_identity = f.read()\n\n        privkey = b''\n        if psk_key:\n            privkey = psk_key\n        elif client_private_key_path:\n            with open(client_private_key_path, 'rb') as f:\n                privkey = f.read()\n\n        server_pubkey_or_identity = b''\n        if server_cert_path:\n            with open(server_cert_path, 'rb') as f:\n                server_pubkey_or_identity = f.read()\n\n        return security_mode, pubkey_or_identity, privkey, server_pubkey_or_identity\n\n    def do_bootstrap(self,\n                     uri: str,\n                     security_mode: SecurityMode = None,\n                     psk_identity: EscapedBytes = None,\n                     psk_key: EscapedBytes = None,\n                     client_cert_path: str = None,\n                     client_private_key_path: str = None,\n                     server_cert_path: str = None,\n                     ssid: int = 1,\n                     is_bootstrap: bool = False,\n                     lifetime: int = 86400,\n                     notification_storing: bool = False,\n                     binding: str = 'U',\n                     iid: int = 1,\n                     finish: bool = True,\n                     tls_ciphersuites: List[int] = []):\n        \"\"\"\n        Sets up a Security and Server instances for an LwM2M server.\n\n        In case of PreSharedKey security mode, PSK_IDENTITY and PSK_KEY\n        are literal sequences to be used as DTLS identity and secret key.\n\n        In case of Certificate security mode, CLIENT_CERT_PATH and\n        SERVER_CERT_PATH shall be paths to binary DER-encoded X.509\n        certificates, and CLIENT_PRIVATE_KEY_PATH to binary DER-encoded\n        PKCS#8 file, which MUST NOT be password-protected.\n\n        If IS_BOOTSTRAP is True, only the Security object instance is\n        configured. LIFETIME, NOTIFICATION_STORING and BINDING are ignored\n        in such case. SSID is still set for the Security instance.\n\n        Both Security and Server object instances are created with given IID.\n\n        If FINISH is set to True, a Bootstap Finish message will be sent\n        after setting up Security/Server instances.\n        \"\"\"\n\n        if ((psk_identity or psk_key)\n                and (client_cert_path or client_private_key_path or server_cert_path)):\n            print('Cannot set both PSK and cert mode at the same time')\n            return\n\n        bootstrap_vars = self.prepare_bootstrap(uri,\n                                                security_mode,\n                                                psk_identity,\n                                                client_cert_path,\n                                                psk_key,\n                                                client_private_key_path,\n                                                server_cert_path)\n        security_mode, pubkey_or_identity, privkey, server_pubkey_or_identity = bootstrap_vars\n\n        security = TLV.make_instance(iid,\n                                     [TLV.make_resource(0, uri),\n                                      TLV.make_resource(\n                                          1, 1 if is_bootstrap else 0),\n                                      TLV.make_resource(\n                                          2, security_mode.value),\n                                      TLV.make_resource(3, pubkey_or_identity),\n                                      TLV.make_resource(\n                                          4, server_pubkey_or_identity),\n                                      TLV.make_resource(5, privkey),\n                                      TLV.make_resource(10, ssid),\n                                      TLV.make_multires(16, enumerate(tls_ciphersuites))])\n        server = TLV.make_instance(iid,\n                                   [TLV.make_resource(0, ssid),\n                                    TLV.make_resource(1, lifetime),\n                                    TLV.make_resource(\n                                        6, 1 if notification_storing else 0),\n                                    TLV.make_resource(7, binding)])\n\n        self._send(Lwm2mWrite('/0', security.serialize(),\n                   format=coap.ContentFormat.APPLICATION_LWM2M_TLV))\n\n        if not is_bootstrap:\n            self._send(Lwm2mWrite('/1', server.serialize(),\n                       format=coap.ContentFormat.APPLICATION_LWM2M_TLV))\n\n        if finish:\n            self._send(Lwm2mBootstrapFinish())\n\n    def do_bootstrap_pack(self,\n                          uri: str,\n                          security_mode: SecurityMode = None,\n                          psk_identity: EscapedBytes = None,\n                          psk_key: EscapedBytes = None,\n                          client_cert_path: str = None,\n                          client_private_key_path: str = None,\n                          server_cert_path: str = None,\n                          ssid: int = 1,\n                          is_bootstrap: bool = False,\n                          lifetime: int = 86400,\n                          notification_storing: bool = False,\n                          binding: str = 'U',\n                          iid: int = 1,\n                          tls_ciphersuites: List[int] = []):\n        \"\"\"\n        Responds to the cached BootstrapPackRequest with BootstrapPack with the content\n        created from the arguments in the same way as the content of bootstrap writes\n        send by the BOOTSTRAP command.\n\n        It can be used only with AUTO_BSPACK_ERROR unset, because without that\n        BootstrapPackRequest is automatically responded with an error message.\n\n        Available as a part of LwM2M 1.2 commercial feature only.\n        \"\"\"\n\n        if self.cached_bspack_request is None:\n            print(\"Skipping - no BootstrapPackRequest is cached.\")\n            return\n\n        accept = self.cached_bspack_request.get_options(coap.Option.ACCEPT)\n        format = coap.ContentFormat.APPLICATION_LWM2M_SENML_CBOR\n        if accept is not None and coap.Option.ACCEPT(format) not in accept:\n            print('No accepted format is supported')\n            print('Accepted formats: ' + str(accept))\n            self._send(Lwm2mErrorResponse.matching(self.cached_bspack_request)(\n                code=coap.Code.RES_NOT_ACCEPTABLE))\n            self.cached_bspack_request = None\n            return\n\n        if ((psk_identity or psk_key)\n                and (client_cert_path or client_private_key_path or server_cert_path)):\n            print('Cannot set both PSK and cert mode at the same time')\n            self.cached_bspack_request = None\n            return\n\n        bootstrap_vars = self.prepare_bootstrap(uri,\n                                                security_mode,\n                                                psk_identity,\n                                                client_cert_path,\n                                                psk_key,\n                                                client_private_key_path,\n                                                server_cert_path)\n        security_mode, pubkey_or_identity, privkey, server_pubkey_or_identity = bootstrap_vars\n\n        sec_pref = '0/' + str(iid) + '/'\n\n        security_data = [\n            {SenmlLabel.BASE_NAME: '/',\n                SenmlLabel.NAME: sec_pref + '0', SenmlLabel.STRING: uri},\n            {SenmlLabel.NAME: sec_pref + '1', SenmlLabel.BOOL: is_bootstrap},\n            {SenmlLabel.NAME: sec_pref + '2', SenmlLabel.VALUE: security_mode.value}\n        ]\n\n        if pubkey_or_identity:\n            security_data += [{SenmlLabel.NAME: sec_pref +\n                               '3', SenmlLabel.OPAQUE: pubkey_or_identity}]\n\n        if server_pubkey_or_identity:\n            security_data += [{SenmlLabel.NAME: sec_pref + '4',\n                               SenmlLabel.OPAQUE: server_pubkey_or_identity}]\n\n        if privkey:\n            security_data += [{SenmlLabel.NAME: sec_pref +\n                               '5', SenmlLabel.OPAQUE: privkey}]\n\n        security_data += [{SenmlLabel.NAME: sec_pref +\n                           '10', SenmlLabel.VALUE: ssid}]\n\n        for (num, ciphersuite) in enumerate(tls_ciphersuites):\n            security_data += [{SenmlLabel.NAME: sec_pref +\n                               '16/' + str(num), SenmlLabel.VALUE: ciphersuite}]\n\n        srv_pref = '1/' + str(iid) + '/'\n\n        server_data = [\n            {SenmlLabel.NAME: srv_pref + '0', SenmlLabel.VALUE: ssid},\n            {SenmlLabel.NAME: srv_pref + '1', SenmlLabel.VALUE: lifetime},\n            {SenmlLabel.NAME: srv_pref + '6', SenmlLabel.BOOL: notification_storing},\n            {SenmlLabel.NAME: srv_pref + '7', SenmlLabel.STRING: binding}\n        ]\n\n        data = security_data + server_data\n        if is_bootstrap:\n            data = security_data\n\n        self._send(Lwm2mContent.matching(self.cached_bspack_request)(\n            content=CBOR.serialize(data), format=format))\n        self.cached_bspack_request = None\n\n    def do_write_file(self,\n                      fname: str,\n                      path: str or Lwm2mResourcePath,\n                      format: coap.ContentFormatOption = coap.ContentFormatOption.APPLICATION_OCTET_STREAM,\n                      chunksize: int = 1024,\n                      timeout_s: float = 3):\n        \"\"\"\n        Opens file fname and attempts to push it using BLOCK1 to the Client.\n        \"\"\"\n        with open(fname, 'rb') as f:\n            contents = f.read()\n            maxindex = (len(contents) + chunksize - 1) // chunksize\n            for index, chunk in enumerate([contents[i*chunksize:(i+1)*chunksize] for i in range(maxindex)]):\n                response = self._send(\n                    Lwm2mWrite(path=path,\n                               content=chunk,\n                               format=format,\n                               options=[coap.Option.BLOCK1(index, index < maxindex - 1, chunksize)]),\n                    timeout_s=timeout_s)\n                if response.code != coap.Code.RES_CONTINUE:\n                    break\n\n    def do_udp(self,\n               content: EscapedBytes):\n        if hasattr(self.serv.socket, 'py_socket'):\n            self.serv.socket.py_socket.sendall(content)\n        else:\n            self.serv.socket.sendall(content)\n\n    def do_file_server(self,\n                       root_directory: str = '.',\n                       port: int = None,\n                       psk_identity: str = None,\n                       psk_key: str = None,\n                       ca_path: str = None,\n                       ca_file: str = None,\n                       crt_file: str = None,\n                       key_file: str = None,\n                       ipv6: bool = False,\n                       debug: bool = False):\n        \"\"\"\n        Serves files from ROOT_DIRECTORY over CoAP(s).\n        \"\"\"\n\n        dtls = (psk_identity and psk_key) or (ca_path or ca_file or crt_file or key_file)\n        port = port or (5684 if dtls else 5683)\n\n        try:\n            CoapFileServer(root_directory=root_directory,\n                           port=port,\n                           psk_identity=psk_identity,\n                           psk_key=psk_key,\n                           ca_path=ca_path,\n                           ca_file=ca_file,\n                           crt_file=crt_file,\n                           key_file=key_file,\n                           ipv6=ipv6,\n                           debug=debug).serve_forever()\n        except KeyboardInterrupt:\n            pass\n\n    def emptyline(self):\n        while self.try_read():\n            pass\n\n\ndef _make_command_handler(cls):\n    \"\"\"\n    Turn given CLS, representing a CoAP or LWM2M message, into a callable\n    command.\n\n    Since the powercmd engine uses method signature to perform tab-completion,\n    we must ensure that constructor signature is retained in the wrapper.\n    \"\"\"\n    import functools\n\n    # this decorator makes send_msg signature be a clone of cls.__init__ one\n    @functools.wraps(cls.__init__)\n    def send_msg(self, *args, **kwargs):\n        try:\n            self._send(cls(*args, **kwargs))\n        except Exception as e:\n            raise e.__class__('could not send %s (%s)' % (cls.__name__, e))\n\n    return send_msg\n\n\ndef _snake_case_from_camel_case(name):\n    \"\"\"\n    Transforms camelCase/PascalCase string into snake_case.\n    Source: http://stackoverflow.com/a/1176023/2339636\n    \"\"\"\n    s1 = re.sub('(.)([A-Z][a-z]+)', r'\\1_\\2', name)\n    return re.sub('([a-z0-9])([A-Z])', r'\\1_\\2', s1).lower()\n\n\ndef _is_command_class(cls):\n    \"\"\"\n    Checks if CLS represents a CoAP or LWM2M message that can be turned into\n    a shell command.\n\n    Such class must be derived from Lwm2mMsg, and all its arguments (excluding\n    `self`) must have a type annotation.\n    \"\"\"\n    if type(cls) is not type:\n        return False\n    if not issubclass(cls, Lwm2mMsg):\n        return False\n\n    import inspect\n    params = inspect.signature(cls.__init__).parameters\n\n    if any(p.annotation is inspect.Parameter.empty\n           for p in list(params.values())[1:]):  # ignore 'self'\n        print('ignoring %s: not command-compatible' % (cls.__name__,))\n        return False\n\n    return True\n\n\ndef _load_message_commands():\n    \"\"\"\n    Load all Lwm2mMsg subclasses containing constructors with command-compatible\n    signatures (i.e. having type annotations for all non-self arguments)\n    \"\"\"\n    from framework_tools.lwm2m import messages\n\n    cmds_loaded = 0\n\n    for cls_name, cls in messages.__dict__.items():\n        if _is_command_class(cls):\n            if cls_name.lower().startswith('lwm2m'):\n                name = cls_name[len('lwm2m'):]\n            else:\n                name = cls_name\n            name = 'do_' + _snake_case_from_camel_case(name)\n\n            if hasattr(Lwm2mCmd, name):\n                raise ValueError('multiple definitions of command %s' % (name,))\n\n            try:\n                setattr(Lwm2mCmd, name, _make_command_handler(cls))\n            except Exception as e:\n                raise ValueError('could not load command: %s (%s)' % (name, e))\n\n            cmds_loaded += 1\n\n    return cmds_loaded\n\n\n_cmds_loaded = _load_message_commands()\nprint('loaded %d message types' % (_cmds_loaded,))\nprint('')\n\nif __name__ == '__main__':\n    LOG_LEVEL = os.getenv('LOGLEVEL', 'info').upper()\n    try:\n        import coloredlogs\n        coloredlogs.install(level=LOG_LEVEL)\n    except ImportError:\n        logging.basicConfig(level=LOG_LEVEL)\n\n    parser = argparse.ArgumentParser()\n    parser.add_argument('--ipv6', '-6', default=False, action='store_true',\n                        help=('Use IPv6 by default.'))\n    parser.add_argument('--listen', '-l',\n                        type=int, const=-1, metavar='PORT', nargs='?',\n                        help=('Immediately starts listening on specified CoAP port. '\n                              'Default UDP, can be TCP if --tcp is passed. '\n                              'If PORT is not specified, default one is used (%s '\n                              'for CoAP, %s for CoAP/(D)TLS)' % (DEFAULT_COAP_PORT,\n                                                                 DEFAULT_COAPS_PORT)))\n    parser.add_argument('--tcp', '-t', default=False, action='store_true',\n                        help='Listen on TCP port')\n    parser.add_argument('--psk-identity', '-i',\n                        type=str, metavar='IDENTITY',\n                        help='PSK identity to use for DTLS connection (literal string).')\n    parser.add_argument('--psk-key', '-k',\n                        type=str, metavar='KEY',\n                        help='PSK key to use for DTLS connection (literal string).')\n    parser.add_argument('--debug', action='store_true',\n                        help='Enable mbed TLS debug output')\n\n    cmdline_args = parser.parse_args()\n\n    Lwm2mCmd(cmdline_args).cmdloop()\n"
  },
  {
    "path": "tools/test-framework-tools/nsh-lwm2m/tlv_shell.py",
    "content": "# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nimport os\nimport sys\n\nfrom typing import List, Tuple\n\nimport powercmd\nfrom framework_tools.lwm2m.messages import EscapedBytes\nfrom framework_tools.lwm2m.tlv import TLV, TLVType\n\n\nclass ResourceType:\n    TYPES = {}\n    DEFAULT = lambda x: x\n\n    @staticmethod\n    def powercmd_parse(text):\n        return ResourceType.TYPES.get(text, ResourceType.DEFAULT)\n\n    @staticmethod\n    def powercmd_complete(text):\n        from powercmd.match_string import match_string\n        return match_string(text, ResourceType.TYPES.keys())\n\n\nResourceType.TYPES = {\n    'int': TLV.encode_int,\n    'double': TLV.encode_double\n}\n\n\nclass ResourceOrInstancePath:\n    \"\"\"\n    TLVBuilderShell helper type used for selecting a Resource, Resource Instance\n    or Object Instance meant to be removed from the TLV being built.\n\n    Example:\n        '0' - the top-level element of the TLV with id=0.\n        '0/1' - the child of the /0 element with id=1. It may be a Resource,\n                Multiple Resource or Resource Instance.\n        '0/1/2' - Resource Instance with id=2 of the /0/1 element. Such\n                  3-segment path can only point to a Resource Instance.\n    \"\"\"\n\n    @staticmethod\n    def powercmd_parse(text):\n        return ResourceOrInstancePath([int(x) for x in text.split('/')])\n\n    def __init__(self,\n                 segments: List[int]):\n        if not (1 <= len(segments) <= 3):\n            raise ValueError('Resource path must have 1-3 segments, got %s' % (str(segments),))\n        self.segments = segments\n\n    def __str__(self):\n        return '/'.join(map(str, self.segments))\n\n\nclass ResourceParentPath(ResourceOrInstancePath):\n    \"\"\"\n    TLVBuilderShell helper type used for selecting a parent Object Instance or\n    Multiple Resource for an newly created elements.\n\n    Note: segments of this path are element indexes, not IDs.\n\n    Example:\n        '0' - the first top-level element of the TLV. It may be either an\n              Object Instance or a Multiple Resource.\n        '0/1' - the second child of the /0 element. Such 2-segment path can\n                only point to a Multiple Resource.\n    \"\"\"\n\n    def __init__(self,\n                 segments: List[int]):\n        if not (1 <= len(segments) <= 2):\n            raise ValueError('Resource parent path must have 1-2 segments, got %s' % (str(segments),))\n        ResourceOrInstancePath.__init__(self, segments)\n\nclass TLVBuilderShell(powercmd.Cmd):\n    def __init__(self, prompt):\n        super(TLVBuilderShell, self).__init__()\n\n        self.tlv = []\n        self.current = None\n        self.prompt = prompt\n        self._top_level_type = None\n\n        # Returns string corresponding to the level of the given type in TLVTree, which can be used\n        # for the comparison of the levels of some TLVType variables and for logging the level.\n        self._get_type_level = {\n            TLVType.INSTANCE:'instance',\n            TLVType.RESOURCE:'resource',\n            TLVType.MULTIPLE_RESOURCE:'resource',\n            TLVType.RESOURCE_INSTANCE:'resource instance'\n        }\n        self._complex_types = {\n            TLVType.INSTANCE,\n            TLVType.MULTIPLE_RESOURCE\n        }\n\n    def _display_tlv(self,\n                     tlv,\n                     path,\n                     indent=''):\n        select_mark = ' *'[int(tlv is self.current)]\n        print('%s %-8s%s%s' % (select_mark, path, indent, tlv.to_string_without_id()))\n        if tlv.tlv_type in (TLVType.MULTIPLE_RESOURCE, TLVType.INSTANCE):\n            for child in tlv.value :\n                self._display_tlv(child, path + '/' + str(child.identifier), indent + '  ')\n\n    def _id_to_idx(self,\n                   id,\n                   tlv_list):\n        for idx, element in enumerate(tlv_list):\n            if element.identifier == id:\n                return idx\n        raise IndexError('Out of range')\n\n    def _get_tlv_by_id(self,\n                       id,\n                       tlv_list):\n        return tlv_list[self._id_to_idx(id, tlv_list)]\n\n    def _contains_id(self,\n                     id,\n                     tlv_list):\n        try:\n            self._id_to_idx(id, tlv_list)\n            return True\n        except IndexError:\n            return False\n\n    def _tlv_from_path(self,\n                       path):\n        try :\n            tlv_list = self.tlv\n            for id in path.segments:\n                result = self._get_tlv_by_id(id, tlv_list)\n                tlv_list = result.value\n        except Exception:\n            raise ValueError('Incorrect path')\n        return result\n\n    def do_make_multires(self,\n                         content: List[Tuple[int, EscapedBytes]]):\n        \"\"\"\n        Builds Multiple Resource Instances from the list of tuples, each\n        of form (Instance ID,Value).\n        \"\"\"\n        for iid, value in content:\n            self.do_add_resource_instance(iid, value)\n\n    def do_show(self):\n        \"\"\"\n        Displays current element structure in a human-readable form.\n        \"\"\"\n        print('  %-8s%s' % ('path', 'value'))\n        print('---------------')\n        for tlv in self.tlv:\n            self._display_tlv(tlv, str(tlv.identifier))\n\n    def do_serialize(self):\n        \"\"\"\n        Displays the prepared strucure as a TLV-encoded hex-escaped string.\n        \"\"\"\n\n        def to_hex(tlv):\n            return ''.join('\\\\x%02x' % (c,) for c in tlv.serialize())\n\n        print(''.join(map(to_hex, self.tlv)))\n\n    def do_deserialize(self,\n                       data: EscapedBytes):\n        \"\"\"\n        Loads a TLV-encoded element structure for further processing.\n        \"\"\"\n        self.tlv = TLV.parse(data)\n        self.do_show()\n\n    def _add(self,\n             identifier: int,\n             tlv_type: TLVType,\n             parent_tlv_type: TLVType,\n             value: bytes):\n        res_tlv = TLV(tlv_type, identifier, value)\n\n        if self._top_level_type is None:\n            self._top_level_type = self._get_type_level[tlv_type]\n            print('Selected top-level: ' + self._get_type_level[tlv_type])\n\n        if self._get_type_level[tlv_type] == self._top_level_type:\n            if self._contains_id(identifier, self.tlv):\n                raise ValueError('Element with such ID and parent already exists')\n            self.tlv.append(res_tlv)\n            if tlv_type in self._complex_types:\n                self.current = self.tlv[-1]\n        else:\n            if self.current == None or self.current.tlv_type != parent_tlv_type:\n                raise ValueError('cannot add a %s' % (str(tlv_type),))\n            if self._contains_id(identifier, self.current.value):\n                raise ValueError('Element with such ID and parent already exists')\n            self.current.value.append(res_tlv)\n\n        return res_tlv\n\n    def do_add_resource(self,\n                        identifier: int,\n                        value: EscapedBytes,\n                        type: ResourceType = ResourceType.DEFAULT):\n        \"\"\"\n        Creates a Resource under the currently selected Object Instance. If\n        there is none, it is created as a top-level element.\n        \"\"\"\n        self._add(identifier, TLVType.RESOURCE, TLVType.INSTANCE, type(value))\n\n    def do_add_multiple_resource(self,\n                                 identifier: int):\n        \"\"\"\n        Creates a Multiple Resource under the currently selected Object\n        Instance. If there is none, it is created as a top-level element.\n        \"\"\"\n        self.current = self._add(identifier, TLVType.MULTIPLE_RESOURCE, TLVType.INSTANCE, [])\n\n    def do_add_resource_instance(self,\n                                 identifier: int,\n                                 value: EscapedBytes,\n                                 type: ResourceType = ResourceType.DEFAULT):\n        \"\"\"\n        Creates a Resource Instance of the currently selected Multiple Resource.\n        \"\"\"\n        self._add(identifier, TLVType.RESOURCE_INSTANCE, TLVType.MULTIPLE_RESOURCE, type(value))\n\n    def do_add_instance(self,\n                        identifier: int):\n        \"\"\"\n        Creates an Object Instance with given IDENTIFIER.\n\n        Object Instances are always created as top-level elements.\n        \"\"\"\n        self._add(identifier, TLVType.INSTANCE, None, [])\n\n    def do_remove(self,\n                  path: ResourceOrInstancePath):\n        \"\"\"\n        Removes an element under the PATH.\n\n        The PATH may consist of 1-3 integers separated by a '/' character.\n\n        Example paths:\n        - '5' - the top-level element with id=5,\n        - '5/2' - the element with id=2 of the element under 5,\n        - '3/1/2' - Resource Instance with id=2 of the Multiple Resource\n                    to which ponts the path 5/. The second-level Resource must be a\n                    Multiple Resource in this case.\n        \"\"\"\n        try:\n            if len(path.segments) == 1:\n                del self.tlv[self._id_to_idx(path.segments[0], self.tlv)]\n            else:\n                tlv = self._get_tlv_by_id(path.segments[0], self.tlv)\n                for id in path.segments[1:-1]:\n                    tlv = tlv.value[self._id_to_idx(id, tlv.value)]\n                del tlv.value[self._id_to_idx(path.segments[-1], tlv.value)]\n        except IndexError:\n            raise ValueError('instance or resource %s does not exist', (path,))\n\n    def do_select(self,\n                  path: ResourceParentPath):\n        \"\"\"\n        Selects an Object Instance or Multiple Resource that further add_* calls\n        will add elements into.\n\n        The PATH consists of 1-2 integers separated by a '/' character.\n\n        Example paths:\n        - '4' - Object Instance or Multiple Resource (id=4),\n        - '4/1' - Multiple Resource (id=1) inside the first Object Instance (id=4).\n        \"\"\"\n        self.current = self._tlv_from_path(path)\n        current_tlv_type = self.current.tlv_type\n        if current_tlv_type not in (TLVType.MULTIPLE_RESOURCE, TLVType.INSTANCE):\n            self.current = None\n            raise ValueError('resource %s is %s, cannot be selected'\n                                % (path, current_tlv_type))\n\n        print('selected: %s %s' % (path, str(self.current)))\n"
  },
  {
    "path": "tools/test-framework-tools/pymbedtls/CMakeLists.txt",
    "content": "# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\ncmake_minimum_required(VERSION 3.22)\n\nif(NOT DEFINED PY_BUILD_CMAKE_VERSION)\n    message(FATAL_ERROR\n        \"This CMake project is intended to be built via py-build-cmake\"\n    )\nendif()\n\nif(NOT PY_BUILD_CMAKE_IMPORT_NAME)\n    message(FATAL_ERROR \"PY_BUILD_CMAKE_IMPORT_NAME not set by py-build-cmake\")\nendif()\n\nproject(pymbedtls CXX)\n\nset(CMAKE_CXX_STANDARD 14)\nset(CMAKE_CXX_STANDARD_REQUIRED ON)\nset(CMAKE_CXX_EXTENSIONS OFF)\n\nfile(GLOB sources \"src/*.cpp\")\nfile(GLOB headers \"src/*.hpp\")\n\ninclude(cmake/QueryPythonForPybind11.cmake)\nfind_pybind11_python_first()\n\npybind11_add_module(pymbedtls MODULE ${sources} ${headers})\n\n# Avoid polluting the exported symbols\nset_target_properties(pymbedtls PROPERTIES\n    CXX_VISIBILITY_PRESET hidden\n    VISIBILITY_INLINES_HIDDEN YES\n)\n\n# Pass env MBEDTLS_ROOT_DIR into the CMake variable expected by FindMbedTLS.cmake\nif(NOT DEFINED MBEDTLS_ROOT_DIR AND DEFINED ENV{MBEDTLS_ROOT_DIR})\n    set(MBEDTLS_ROOT_DIR \"$ENV{MBEDTLS_ROOT_DIR}\")\nendif()\n\n# Prepend CMake module path to find FindMbedTLS.cmake\nlist(PREPEND CMAKE_MODULE_PATH \"${CMAKE_CURRENT_SOURCE_DIR}/cmake\")\n\nfind_package(MbedTLS REQUIRED)\ntarget_link_libraries(pymbedtls PRIVATE mbedtls mbedx509 mbedcrypto)\n\n# If using a specific build of mbedTLS, bake in a hint for runtime linker that\n# the mbedTLS shared libraries can be found in MBEDTLS_ROOT_DIR/lib. This avoids\n# the need to set LD_LIBRARY_PATH at runtime.\nif(MBEDTLS_ROOT_DIR)\n    set(_mbedtls_libdir \"${MBEDTLS_ROOT_DIR}/lib\")\n    set_target_properties(pymbedtls PROPERTIES\n        BUILD_RPATH \"${_mbedtls_libdir}\"\n        INSTALL_RPATH \"${_mbedtls_libdir}\"\n    )\nendif()\n\ninstall(TARGETS pymbedtls\n        DESTINATION ${PY_BUILD_CMAKE_IMPORT_NAME})\n"
  },
  {
    "path": "tools/test-framework-tools/pymbedtls/cmake/FindMbedTLS.cmake",
    "content": "# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\n# NOTE:\n# This is extracted from avs_commons. Long-term it'd be preferable to assume\n# that the system has mbedTLS installed and use CMake config mode instead, but\n# for now let's use the exact same logic as in avs_commons to avoid\n# discrepancies.\n\n#.rst:\n# FindMbedTLS\n# -----------\n#\n# Find the mbedTLS encryption library.\n#\n# Imported Targets\n# ^^^^^^^^^^^^^^^^\n#\n# This module defines the following :prop_tgt:`IMPORTED` targets:\n#\n# ``mbedtls``\n#   The mbedTLS ``mbedtls`` library, if found.\n# ``mbedcrypto``\n#   The mbedtls ``crypto`` library, if found.\n# ``mbedx509``\n#   The mbedtls ``x509`` library, if found.\n#\n# Result Variables\n# ^^^^^^^^^^^^^^^^\n#\n# This module will set the following variables in your project:\n#\n# ``MBEDTLS_FOUND``\n#   System has the mbedTLS library.\n# ``MBEDTLS_INCLUDE_DIR``\n#   The mbedTLS include directory.\n# ``MBEDTLS_LIBRARY``\n#   The mbedTLS SSL library.\n# ``MBEDTLS_CRYPTO_LIBRARY``\n#   The mbedTLS crypto library.\n# ``MBEDTLS_X509_LIBRARY``\n#   The mbedTLS x509 library.\n# ``MBEDTLS_LIBRARIES``\n#   All mbedTLS libraries.\n# ``MBEDTLS_VERSION``\n#   This is set to ``$major.$minor.$patch``.\n# ``MBEDTLS_VERSION_MAJOR``\n#   Set to major mbedTLS version number.\n# ``MBEDTLS_VERSION_MINOR``\n#   Set to minor mbedTLS version number.\n# ``MBEDTLS_VERSION_PATCH``\n#   Set to patch mbedTLS version number.\n#\n# Hints\n# ^^^^^\n#\n# Set ``MBEDTLS_ROOT_DIR`` to the root directory of an mbedTLS installation.\n# Set ``MBEDTLS_USE_STATIC_LIBS`` to ``TRUE`` to look for static libraries.\n\nset(_ORIG_FIND_ROOT_PATH_MODE_INCLUDE \"${CMAKE_FIND_ROOT_PATH_MODE_INCLUDE}\")\nset(_ORIG_FIND_ROOT_PATH_MODE_LIBRARY \"${CMAKE_FIND_ROOT_PATH_MODE_LIBRARY}\")\n\nif(MBEDTLS_ROOT_DIR)\n    # Disable re-rooting paths in find_path/find_library.\n    # This assumes MBEDTLS_ROOT_DIR is an absolute path.\n    set(_EXTRA_FIND_ARGS \"NO_DEFAULT_PATH\")\n    set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE NEVER)\n    set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY NEVER)\nendif()\n\nfind_path(MBEDTLS_INCLUDE_DIR\n          NAMES mbedtls/ssl.h\n          PATH_SUFFIXES include\n          HINTS ${MBEDTLS_ROOT_DIR}\n          ${_EXTRA_FIND_ARGS})\n\n# based on https://github.com/ARMmbed/mbedtls/issues/298\nset(MBEDTLS_BUILD_INFO_FILE)\nif(MBEDTLS_INCLUDE_DIR)\n    if(EXISTS \"${MBEDTLS_INCLUDE_DIR}/mbedtls/build_info.h\")\n        # Mbed TLS 3.x\n        set(MBEDTLS_BUILD_INFO_FILE \"${MBEDTLS_INCLUDE_DIR}/mbedtls/build_info.h\")\n    elseif(EXISTS \"${MBEDTLS_INCLUDE_DIR}/mbedtls/version.h\")\n        # Mbed TLS 2.x\n        set(MBEDTLS_BUILD_INFO_FILE \"${MBEDTLS_INCLUDE_DIR}/mbedtls/version.h\")\n    endif()\nendif()\n\nif(MBEDTLS_BUILD_INFO_FILE)\n    file(STRINGS \"${MBEDTLS_BUILD_INFO_FILE}\" VERSION_STRING_LINE REGEX \"^#define MBEDTLS_VERSION_STRING[ \\\\t\\\\n\\\\r]+\\\"[^\\\"]*\\\"$\")\n    file(STRINGS \"${MBEDTLS_BUILD_INFO_FILE}\" VERSION_MAJOR_LINE REGEX \"^#define MBEDTLS_VERSION_MAJOR[ \\\\t\\\\n\\\\r]+[0-9]+$\")\n    file(STRINGS \"${MBEDTLS_BUILD_INFO_FILE}\" VERSION_MINOR_LINE REGEX \"^#define MBEDTLS_VERSION_MINOR[ \\\\t\\\\n\\\\r]+[0-9]+$\")\n    file(STRINGS \"${MBEDTLS_BUILD_INFO_FILE}\" VERSION_PATCH_LINE REGEX \"^#define MBEDTLS_VERSION_PATCH[ \\\\t\\\\n\\\\r]+[0-9]+$\")\n\n    string(REGEX REPLACE \"^#define MBEDTLS_VERSION_STRING[ \\\\t\\\\n\\\\r]+\\\"([^\\\"]*)\\\"$\" \"\\\\1\" MBEDTLS_VERSION \"${VERSION_STRING_LINE}\")\n    string(REGEX REPLACE \"^#define MBEDTLS_VERSION_MAJOR[ \\\\t\\\\n\\\\r]+([0-9]+)$\" \"\\\\1\" MBEDTLS_VERSION_MAJOR \"${VERSION_MAJOR_LINE}\")\n    string(REGEX REPLACE \"^#define MBEDTLS_VERSION_MINOR[ \\\\t\\\\n\\\\r]+([0-9]+)$\" \"\\\\1\" MBEDTLS_VERSION_MINOR \"${VERSION_MINOR_LINE}\")\n    string(REGEX REPLACE \"^#define MBEDTLS_VERSION_PATCH[ \\\\t\\\\n\\\\r]+([0-9]+)$\" \"\\\\1\" MBEDTLS_VERSION_PATCH \"${VERSION_PATCH_LINE}\")\nendif()\n\n\nif(MBEDTLS_USE_STATIC_LIBS)\n    set(_MBEDTLS_LIB_NAME libmbedtls.a)\n    set(_MBEDTLS_CRYPTO_LIB_NAME libmbedcrypto.a)\n    set(_MBEDTLS_X509_LIB_NAME libmbedx509.a)\nelse()\n    set(_MBEDTLS_LIB_NAME mbedtls)\n    set(_MBEDTLS_CRYPTO_LIB_NAME mbedcrypto)\n    set(_MBEDTLS_X509_LIB_NAME mbedx509)\nendif()\n\nfind_library(MBEDTLS_LIBRARY\n             NAMES ${_MBEDTLS_LIB_NAME}\n             PATH_SUFFIXES lib\n             HINTS ${MBEDTLS_ROOT_DIR}\n             ${_EXTRA_FIND_ARGS})\n\nfind_library(MBEDTLS_CRYPTO_LIBRARY\n             NAMES ${_MBEDTLS_CRYPTO_LIB_NAME}\n             PATH_SUFFIXES lib\n             HINTS ${MBEDTLS_ROOT_DIR}\n             ${_EXTRA_FIND_ARGS})\n\nfind_library(MBEDTLS_X509_LIBRARY\n             NAMES ${_MBEDTLS_X509_LIB_NAME}\n             PATH_SUFFIXES lib\n             HINTS ${MBEDTLS_ROOT_DIR}\n             ${_EXTRA_FIND_ARGS})\n\nset(MBEDTLS_LIBRARIES ${MBEDTLS_LIBRARY} ${MBEDTLS_CRYPTO_LIBRARY} ${MBEDTLS_X509_LIBRARY})\n\nif(MBEDTLS_INCLUDE_DIR)\n    set(MBEDTLS_FOUND TRUE)\nendif()\n\nset(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE \"${_ORIG_FIND_ROOT_PATH_MODE_INCLUDE}\")\nset(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY \"${_ORIG_FIND_ROOT_PATH_MODE_LIBRARY}\")\n\n\ninclude(FindPackageHandleStandardArgs)\nfind_package_handle_standard_args(MbedTLS\n                                  FOUND_VAR MBEDTLS_FOUND\n                                  REQUIRED_VARS\n                                      MBEDTLS_INCLUDE_DIR\n                                      MBEDTLS_LIBRARY\n                                      MBEDTLS_CRYPTO_LIBRARY\n                                      MBEDTLS_X509_LIBRARY\n                                      MBEDTLS_LIBRARIES\n                                      MBEDTLS_VERSION\n                                  VERSION_VAR MBEDTLS_VERSION)\n\n\nif(NOT TARGET mbedcrypto)\n    add_library(mbedcrypto UNKNOWN IMPORTED)\n    set_target_properties(mbedcrypto PROPERTIES\n                          INTERFACE_INCLUDE_DIRECTORIES \"${MBEDTLS_INCLUDE_DIR}\"\n                          IMPORTED_LINK_INTERFACE_LANGUAGES \"C\"\n                          IMPORTED_LOCATION \"${MBEDTLS_CRYPTO_LIBRARY}\")\nendif()\n\nif(NOT TARGET mbedx509)\n    add_library(mbedx509 UNKNOWN IMPORTED)\n    set_target_properties(mbedx509 PROPERTIES\n                          INTERFACE_INCLUDE_DIRECTORIES \"${MBEDTLS_INCLUDE_DIR}\"\n                          INTERFACE_LINK_LIBRARIES mbedcrypto\n                          IMPORTED_LINK_INTERFACE_LANGUAGES \"C\"\n                          IMPORTED_LOCATION \"${MBEDTLS_X509_LIBRARY}\")\nendif()\n\nif(NOT TARGET mbedtls)\n    add_library(mbedtls UNKNOWN IMPORTED)\n    set_target_properties(mbedtls PROPERTIES\n                          INTERFACE_INCLUDE_DIRECTORIES \"${MBEDTLS_INCLUDE_DIR}\"\n                          INTERFACE_LINK_LIBRARIES mbedx509\n                          IMPORTED_LINK_INTERFACE_LANGUAGES \"C\"\n                          IMPORTED_LOCATION \"${MBEDTLS_LIBRARY}\")\nendif()\n"
  },
  {
    "path": "tools/test-framework-tools/pymbedtls/cmake/QueryPythonForPybind11.cmake",
    "content": "# This file is derived from:\n#  py-build-cmake, examples/pybind11-project/cmake/QueryPythonForPybind11.cmake\n#  https://github.com/tttapa/py-build-cmake/blob/ea6fad016da1069a41222c73f345f06c9049b059/examples/pybind11-project/cmake/QueryPythonForPybind11.cmake\n#\n# Note: The upsteam project is MIT-licensed, original license follows:\n# MIT License\n\n# Copyright (c) 2022 Pieter P\n\n# Permission is hereby granted, free of charge, to any person obtaining a copy\n# of this software and associated documentation files (the \"Software\"), to deal\n# in the Software without restriction, including without limitation the rights\n# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n# copies of the Software, and to permit persons to whom the Software is\n# furnished to do so, subject to the following conditions:\n\n# The above copyright notice and this permission notice shall be included in all\n# copies or substantial portions of the Software.\n\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n# SOFTWARE.\n\noption(USE_GLOBAL_PYBIND11 \"Don't query Python to find pybind11\" Off)\nmark_as_advanced(USE_GLOBAL_PYBIND11)\n\n# First tries to find Python 3, then tries to import the pybind11 module to\n# query the CMake config location, and finally imports pybind11 using\n# find_package(pybind11 ${ARGN} REQUIRED CONFIG CMAKE_FIND_ROOT_PATH_BOTH),\n# where ${ARGN} are the arguments passed to this macro.\nmacro(find_pybind11_python_first)\n\n    # https://github.com/pybind/pybind11/pull/5083\n    set(PYBIND11_USE_CROSSCOMPILING On)\n\n    # Find Python\n    if (CMAKE_CROSSCOMPILING AND NOT (APPLE AND \"$ENV{CIBUILDWHEEL}\" STREQUAL \"1\"))\n        find_package(Python3 REQUIRED COMPONENTS Development.Module)\n    else()\n        find_package(Python3 REQUIRED COMPONENTS Interpreter Development.Module)\n    endif()\n\n    # Query Python to see if it knows where the pybind11 root is\n    if (NOT USE_GLOBAL_PYBIND11 AND Python3_EXECUTABLE)\n        if (NOT pybind11_ROOT OR NOT EXISTS ${pybind11_ROOT})\n            message(STATUS \"Detecting pybind11 CMake location\")\n            execute_process(COMMAND ${Python3_EXECUTABLE}\n                    -m pybind11 --cmakedir\n                OUTPUT_VARIABLE PY_BUILD_PYBIND11_ROOT\n                OUTPUT_STRIP_TRAILING_WHITESPACE\n                RESULT_VARIABLE PY_BUILD_CMAKE_PYBIND11_RESULT)\n            # If it was successful\n            if (PY_BUILD_CMAKE_PYBIND11_RESULT EQUAL 0)\n                message(STATUS \"pybind11 CMake location: ${PY_BUILD_PYBIND11_ROOT}\")\n                set(pybind11_ROOT ${PY_BUILD_PYBIND11_ROOT}\n                    CACHE PATH \"Path to the pybind11 CMake configuration.\" FORCE)\n            else()\n                unset(pybind11_ROOT CACHE)\n            endif()\n        endif()\n    endif()\n\n    # pybind11 is header-only, so finding a native version is fine\n    find_package(pybind11 ${ARGN} REQUIRED CONFIG CMAKE_FIND_ROOT_PATH_BOTH)\n\nendmacro()\n"
  },
  {
    "path": "tools/test-framework-tools/pymbedtls/pymbedtls/__init__.py",
    "content": "# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\nfrom .pymbedtls import *\n"
  },
  {
    "path": "tools/test-framework-tools/pymbedtls/pyproject.toml",
    "content": "# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\n[project]\nname = \"pymbedtls\"\nversion = \"0.6.0\"\ndescription = \"MbedTLS wrapper for Python\"\nlicense = \"Commercial\"\nauthors = [\n  {name = \"AVSystem\", email = \"avsystem@avsystem.com\"}\n]\n\n[build-system]\nrequires = [\n  \"py-build-cmake~=0.5.0\",\n  \"pybind11==2.10.1\"\n]\nbuild-backend = \"py_build_cmake.build\"\n\n[tool.py-build-cmake.sdist]\ninclude = [\n  \"CMakeLists.txt\",\n  \"cmake/*.cmake\",\n  \"src/*.cpp\",\n  \"src/*.hpp\",\n  \"pymbedtls/__init__.py\"\n]\n\n[tool.py-build-cmake.cmake]\nminimum_version = \"3.22\"\nbuild_type = \"Debug\"\nbuild_args = [\"-j\"]\n"
  },
  {
    "path": "tools/test-framework-tools/pymbedtls/src/common.cpp",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <mbedtls/error.h>\n\n#include <sstream>\n\n#include \"common.hpp\"\n\nusing namespace std;\n\nnamespace ssl {\nnamespace detail {\nstring to_hex(int n) {\n    if (n < 0) {\n        return \"-\" + to_hex(-n);\n    } else {\n        stringstream ss;\n        ss << \"0x\" << hex << n;\n        return ss.str();\n    }\n}\n\n} // namespace detail\n\nstring mbedtls_error_string(int error_code) {\n    char buf[1024];\n    mbedtls_strerror(error_code, buf, sizeof(buf));\n    return string(buf) + \" (\" + detail::to_hex(error_code) + \")\";\n}\n\n} // namespace ssl\n"
  },
  {
    "path": "tools/test-framework-tools/pymbedtls/src/common.hpp",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef PYMBEDTLS_COMMON_HPP\n#define PYMBEDTLS_COMMON_HPP\n#include <stdexcept>\n#include <string>\n\nnamespace ssl {\n\nstd::string mbedtls_error_string(int error_code);\n\nclass mbedtls_error : public std::runtime_error {\npublic:\n    mbedtls_error(const std::string &message, int error_code)\n            : std::runtime_error(message + \": \"\n                                 + mbedtls_error_string(error_code)) {}\n};\n\n} // namespace ssl\n\nnamespace helpers {\n\ntemplate <typename Callable>\nclass defer_obj {\nprivate:\n    Callable deferred;\n\npublic:\n    defer_obj(defer_obj &&other) noexcept\n            : deferred(std::move(other.deferred)) {}\n    defer_obj(const defer_obj &) = delete;\n    defer_obj &operator=(const defer_obj &) = delete;\n    defer_obj &operator=(defer_obj &&) = delete;\n\n    template <typename TempCallable>\n    defer_obj(TempCallable &&deferred)\n            : deferred(std::forward<TempCallable>(deferred)) {}\n    ~defer_obj() {\n        deferred();\n    }\n};\n\n// This helper ensures that some code will be called on destruction, i.e. exit\n// from the scope, no matter if it's a return, an exception or a normal exit.\ntemplate <typename Callable>\ndefer_obj<Callable> defer(Callable &&to_defer) {\n    return { std::forward<Callable>(to_defer) };\n}\n\n}; // namespace helpers\n\n#endif // PYMBEDTLS_COMMON_HPP\n"
  },
  {
    "path": "tools/test-framework-tools/pymbedtls/src/context.cpp",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <cstring>\n#include <stdexcept>\n\n#include <mbedtls/platform.h>\n\n#if defined(MBEDTLS_USE_PSA_CRYPTO) || defined(MBEDTLS_PSA_CRYPTO_C)\n#    include <psa/crypto.h>\n#endif // defined(MBEDTLS_USE_PSA_CRYPTO) || defined(MBEDTLS_PSA_CRYPTO_C)\n\n#include \"context.hpp\"\n\nusing namespace std;\n\nnamespace ssl {\n\nContext::Context(std::shared_ptr<SecurityInfo> security,\n                 bool debug,\n                 std::string connection_id)\n        : security_(security), debug_(debug), connection_id_(connection_id) {\n#if defined(MBEDTLS_USE_PSA_CRYPTO) || defined(MBEDTLS_PSA_CRYPTO_C)\n    if (psa_crypto_init() != PSA_SUCCESS) {\n        throw runtime_error(\"psa_crypto_init() failed\");\n    }\n#endif // defined(MBEDTLS_USE_PSA_CRYPTO) || defined(MBEDTLS_PSA_CRYPTO_C)\n\n    memset(&session_cache_, 0, sizeof(session_cache_));\n    mbedtls_ssl_cache_init(&session_cache_);\n\n#if !defined(MBEDTLS_SSL_DTLS_CONNECTION_ID)\n    if (connection_id.size() > 0) {\n        throw runtime_error(\n                \"connection_id is not supported in this version of pymbedtls\");\n    }\n#endif // !MBEDTLS_SSL_DTLS_CONNECTION_ID\n}\n\nContext::~Context() {\n    mbedtls_ssl_cache_free(&session_cache_);\n}\n\n} // namespace ssl\n"
  },
  {
    "path": "tools/test-framework-tools/pymbedtls/src/context.hpp",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef PYMBEDTLS_CONTEXT_HPP\n#define PYMBEDTLS_CONTEXT_HPP\n\n#include <memory>\n#include <string>\n\n#include <mbedtls/ssl_cache.h>\n\nnamespace ssl {\n\nclass SecurityInfo;\n\nclass Context {\n    mbedtls_ssl_cache_context session_cache_;\n    std::shared_ptr<SecurityInfo> security_;\n    bool debug_;\n    std::string connection_id_;\n\npublic:\n    Context(std::shared_ptr<SecurityInfo> security,\n            bool debug,\n            std::string connection_id);\n    ~Context();\n\n    mbedtls_ssl_cache_context *session_cache() {\n        return &session_cache_;\n    }\n\n    std::shared_ptr<SecurityInfo> security() const {\n        return security_;\n    }\n\n    std::string connection_id() const {\n        return connection_id_;\n    }\n\n    bool debug() const {\n        return debug_;\n    }\n};\n\n} // namespace ssl\n\n#endif // PYMBEDTLS_CONTEXT_HPP\n"
  },
  {
    "path": "tools/test-framework-tools/pymbedtls/src/pybind11_interop.hpp",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef PYMBEDTLS_PYBIND11_INTEROP\n#define PYMBEDTLS_PYBIND11_INTEROP\n#include <pybind11/eval.h>\n#include <pybind11/pybind11.h>\n#include <pybind11/stl.h>\n\n#include <type_traits>\n\nnamespace py = pybind11;\n\ntemplate <typename Result, typename... Args>\ntypename std::enable_if<!std::is_void<Result>::value, Result>::type\ncall_method(py::object py_object, const char *name, Args &&... args) {\n    auto f = py_object.attr(name);\n    auto result = f(std::forward<Args>(args)...);\n    return py::cast<Result>(result);\n}\n\ntemplate <typename Result, typename... Args>\ntypename std::enable_if<std::is_void<Result>::value>::type\ncall_method(py::object py_object, const char *name, Args &&... args) {\n    auto f = py_object.attr(name);\n    (void) f(std::forward<Args>(args)...);\n}\n\n#endif // PYMBEDTLS_PYBIND11_INTEROP\n"
  },
  {
    "path": "tools/test-framework-tools/pymbedtls/src/pymbedtls.cpp",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <exception>\n#include <iostream>\n#include <string>\n\n#include \"context.hpp\"\n#include \"security.hpp\"\n#include \"socket.hpp\"\n\n#include \"pybind11_interop.hpp\"\n\n#include <mbedtls/version.h>\n\nusing namespace std;\n\nnamespace {\n\ntemplate <typename... Args>\nvoid method_unimplemented(py::object self, const Args &...) {\n    throw logic_error(\"method not implemented\");\n}\n\n} // namespace\n\nPYBIND11_MODULE(pymbedtls, m) {\n    using namespace ssl;\n\n    py::class_<SecurityInfo, shared_ptr<SecurityInfo>>(m, \"SecurityInfo\")\n            .def(\"name\", &SecurityInfo::name)\n            .def(\"set_ciphersuites\", &SecurityInfo::set_ciphersuites);\n\n    py::class_<PskSecurity, SecurityInfo, shared_ptr<PskSecurity>>(\n            m, \"PskSecurity\")\n            .def(py::init<const string &, const string &>(),\n                 py::arg(\"key\"),\n                 py::arg(\"identity\"));\n\n    py::class_<CertSecurity, SecurityInfo, shared_ptr<CertSecurity>>(\n            m, \"CertSecurity\")\n            .def(py::init<const char *, const char *, const char *,\n                          const char *>(),\n                 py::arg(\"ca_path\"),\n                 py::arg(\"ca_file\"),\n                 py::arg(\"crt_file\"),\n                 py::arg(\"key_file\"));\n\n    py::class_<Context, shared_ptr<Context>>(m, \"Context\")\n            .def(py::init<shared_ptr<SecurityInfo>, bool, std::string>(),\n                 py::arg(\"security\"),\n                 py::arg(\"debug\") = false,\n                 py::arg(\"connection_id\") = \"\")\n            .def_static(\"supports_connection_id\",\n                        []() -> bool {\n#if defined(MBEDTLS_SSL_DTLS_CONNECTION_ID)\n                            return true;\n#else\n                            return false;\n#endif\n                        })\n            .def_static(\"supports_TLS_1_3\",\n                        []() -> bool {\n#if defined(MBEDTLS_SSL_PROTO_TLS1_3)\n                            return true;\n#else\n                            return false;\n#endif\n                        })\n            .def_static(\"mbedtls_version\",\n                        []() -> uint32_t { return MBEDTLS_VERSION_NUMBER; });\n\n    py::class_<ServerSocket>(m, \"ServerSocket\")\n            .def(py::init<shared_ptr<Context>, py::object>(),\n                 py::arg(\"context\"),\n                 py::arg(\"socket\"))\n            .def(\"accept\", &ServerSocket::accept,\n                 py::arg(\"handshake_timeouts_s\") = py::none())\n            .def(\"__getattr__\", &ServerSocket::__getattr__)\n            .def(\"__setattr__\", &ServerSocket::__setattr__);\n\n    auto socket_scope =\n            py::class_<Socket>(m, \"Socket\")\n                    .def(py::init<shared_ptr<Context>, py::object,\n                                  SocketType>(),\n                         py::arg(\"context\"),\n                         py::arg(\"socket\"),\n                         py::arg(\"socket_type\"))\n                    .def(\"connect\", &Socket::connect, py::arg(\"host_port\"),\n                         py::arg(\"handshake_timeouts_s\") = py::none())\n                    .def(\"send\", &Socket::send)\n                    .def(\"sendall\", &Socket::send)\n                    .def(\"sendto\", &method_unimplemented<string, py::object>)\n                    .def(\"recv\", &Socket::recv)\n                    .def(\"recv_into\", &method_unimplemented<py::object>)\n                    .def(\"recvfrom\", &method_unimplemented<int>)\n                    .def(\"recvfrom_into\", &method_unimplemented<py::object>)\n                    .def(\"peer_cert\", &Socket::peer_cert)\n                    .def(\"__getattr__\", &Socket::__getattr__)\n                    .def(\"__setattr__\", &Socket::__setattr__);\n\n    py::enum_<SocketType>(socket_scope, \"Type\")\n            .value(\"Client\", SocketType::Client)\n            .value(\"Server\", SocketType::Server)\n            .export_values();\n    // most verbose logs available\n    mbedtls_debug_set_threshold(4);\n\n    set_terminate([]() {\n        cerr << \"Terminate called in pymbedtls. This almost certainly means \"\n                \"that an exception was thrown in a callback, that indirectly \"\n                \"was called by a callee not expecting an exception that would \"\n                \"require rethrowing. Consider analyzing the core dump in gdb \"\n                \"to determine the C++ stack trace leading to this point.\"\n             << endl;\n        exception_ptr eptr = current_exception();\n        if (eptr) {\n            try {\n                rethrow_exception(eptr);\n            } catch (const exception &e) {\n                cerr << \"Uncaught exception with reason: \" << e.what() << endl;\n            } catch (...) {\n                cerr << \"Uncaught unknown throwable\" << endl;\n            }\n        } else {\n            cerr << \"Couldn't determine the throwable that caused the \"\n                    \"terminate call\"\n                 << endl;\n        }\n        cerr << flush;\n        std::abort();\n    });\n}\n"
  },
  {
    "path": "tools/test-framework-tools/pymbedtls/src/security.cpp",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <stdexcept>\n\n#include \"common.hpp\"\n#include \"security.hpp\"\n#include \"socket.hpp\"\n\n#include \"pybind11_interop.hpp\"\n\nusing namespace std;\n\nnamespace ssl {\n\nvoid SecurityInfo::configure(Socket &socket) {\n    if (!ciphersuites_.empty()) {\n        socket.ciphersuites_ = ciphersuites_;\n        socket.ciphersuites_.push_back(0);\n        mbedtls_ssl_conf_ciphersuites(&socket.config_,\n                                      socket.ciphersuites_.data());\n    }\n}\n\nvoid PskSecurity::configure(Socket &socket) {\n    mbedtls_ssl_conf_psk(&socket.config_,\n                         reinterpret_cast<const unsigned char *>(key_.data()),\n                         key_.size(),\n                         reinterpret_cast<const unsigned char *>(\n                                 identity_.data()),\n                         identity_.size());\n\n    SecurityInfo::configure(socket);\n}\n\nstring PskSecurity::name() const {\n    return \"psk\";\n}\n\nCertSecurity::CertSecurity(const char *ca_path,\n                           const char *ca_file,\n                           const char *crt_file,\n                           const char *key_file)\n        : SecurityInfo({ MBEDTLS_TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8,\n                         ADDITIONAL_TLS1_3_CIPHERSUITES }),\n          configure_ca_(ca_path || ca_file),\n          configure_crt_(crt_file && key_file) {\n    mbedtls_pk_init(&pk_ctx_);\n    mbedtls_x509_crt_init(&ca_certs_);\n    mbedtls_x509_crt_init(&crt_);\n\n    int result;\n    if (ca_path\n            && (result = mbedtls_x509_crt_parse_path(&ca_certs_, ca_path))) {\n        throw mbedtls_error(string(\"Could not load certificates from CA-path \")\n                                    + ca_path,\n                            result);\n    }\n    if (ca_file\n            && (result = mbedtls_x509_crt_parse_file(&ca_certs_, ca_file))) {\n        throw mbedtls_error(string(\"Could not load certificate from CA-file \")\n                                    + ca_file,\n                            result);\n    }\n    if (key_file) {\n#if MBEDTLS_VERSION_NUMBER >= 0x03000000\n        mbedtls_ctr_drbg_context rng;\n        mbedtls_ctr_drbg_init(&rng);\n#endif // MBEDTLS_VERSION_NUMBER >= 0x03000000\n        result = mbedtls_pk_parse_keyfile(&pk_ctx_, key_file, nullptr\n#if MBEDTLS_VERSION_NUMBER >= 0x03000000\n                                          ,\n                                          mbedtls_ctr_drbg_random, &rng\n#endif // MBEDTLS_VERSION_NUMBER >= 0x03000000\n        );\n        if (result) {\n            throw mbedtls_error(string(\"Could not parse private-key file \")\n                                        + key_file,\n                                result);\n        }\n    }\n    if (crt_file && (result = mbedtls_x509_crt_parse_file(&crt_, crt_file))) {\n        throw mbedtls_error(string(\"Could not load certificate from file \")\n                                    + crt_file,\n                            result);\n    }\n}\n\nCertSecurity::~CertSecurity() {\n    mbedtls_x509_crt_free(&crt_);\n    mbedtls_x509_crt_free(&ca_certs_);\n    mbedtls_pk_free(&pk_ctx_);\n}\n\nvoid CertSecurity::configure(Socket &socket) {\n    SecurityInfo::configure(socket);\n    mbedtls_ssl_conf_authmode(&socket.config_, MBEDTLS_SSL_VERIFY_NONE);\n\n    if (configure_ca_) {\n        mbedtls_ssl_conf_authmode(&socket.config_, MBEDTLS_SSL_VERIFY_REQUIRED);\n        mbedtls_ssl_conf_ca_chain(&socket.config_, &ca_certs_, nullptr);\n    }\n    if (configure_crt_) {\n        int result =\n                mbedtls_ssl_conf_own_cert(&socket.config_, &crt_, &pk_ctx_);\n        if (result) {\n            throw mbedtls_error(\"Could not set own certificate\", result);\n        }\n    }\n}\n\nstring CertSecurity::name() const {\n    return \"cert\";\n}\n\n} // namespace ssl\n"
  },
  {
    "path": "tools/test-framework-tools/pymbedtls/src/security.hpp",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef PYMBEDTLS_SECURITY_H\n#define PYMBEDTLS_SECURITY_H\n#include <mbedtls/ssl.h>\n\n#include <memory>\n#include <string>\n#include <vector>\n\n#ifdef MBEDTLS_SSL_PROTO_TLS1_3\n#    define ADDITIONAL_TLS1_3_CIPHERSUITES                                    \\\n        MBEDTLS_TLS1_3_AES_128_GCM_SHA256, MBEDTLS_TLS1_3_AES_256_GCM_SHA384, \\\n                MBEDTLS_TLS1_3_CHACHA20_POLY1305_SHA256,                      \\\n                MBEDTLS_TLS1_3_AES_128_CCM_SHA256,                            \\\n                MBEDTLS_TLS1_3_AES_128_CCM_8_SHA256\n#else // MBEDTLS_SSL_PROTO_TLS1_3\n#    define ADDITIONAL_TLS1_3_CIPHERSUITES\n#endif // MBEDTLS_SSL_PROTO_TLS1_3\n\nnamespace ssl {\nclass Socket;\n\nclass SecurityInfo {\nprotected:\n    std::vector<int> ciphersuites_;\n\n    SecurityInfo(std::vector<int> &&default_ciphersuites)\n            : ciphersuites_(std::move(default_ciphersuites)) {}\n\npublic:\n    virtual ~SecurityInfo() = default;\n    virtual void configure(Socket &socket);\n    virtual std::string name() const = 0;\n    void set_ciphersuites(const std::vector<int> &ciphersuites) {\n        ciphersuites_ = ciphersuites;\n    }\n};\n\nclass PskSecurity : public SecurityInfo {\n    std::string key_;\n    std::string identity_;\n\npublic:\n    PskSecurity(const std::string &key, const std::string &identity)\n            : SecurityInfo({ MBEDTLS_TLS_PSK_WITH_AES_128_CCM_8,\n                             ADDITIONAL_TLS1_3_CIPHERSUITES }),\n              key_(key),\n              identity_(identity) {}\n\n    PskSecurity() = default;\n    PskSecurity(const PskSecurity &) = default;\n    virtual void configure(Socket &socket);\n    virtual std::string name() const;\n};\n\nclass CertSecurity : public SecurityInfo {\n    mbedtls_pk_context pk_ctx_;\n    mbedtls_x509_crt ca_certs_;\n    mbedtls_x509_crt crt_;\n    bool configure_ca_;\n    bool configure_crt_;\n\npublic:\n    /**\n     * @param ca_path  Path containing the top-level PEM/DER encoded CA(s)\n     * @param ca_file  The PEM/DER encoded file containing top-level CA(s)\n     * @param crt_file The PEM/DER encoded file containing client/server\n     *                 certificates\n     * @param key_file The PEM/DER encoded file containing client/server key\n     */\n    CertSecurity(const char *ca_path,\n                 const char *ca_file,\n                 const char *crt_file,\n                 const char *key_file);\n\n    virtual ~CertSecurity();\n\n    CertSecurity() = default;\n    CertSecurity(const CertSecurity &) = default;\n    virtual void configure(Socket &socket);\n    virtual std::string name() const;\n};\n\n} // namespace ssl\n\n#endif // PYMBEDTLS_SECURITY_H\n"
  },
  {
    "path": "tools/test-framework-tools/pymbedtls/src/socket.cpp",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#include <chrono>\n#include <sstream>\n#include <stdexcept>\n\n#include <arpa/inet.h>\n\n#include <sys/socket.h>\n#include <sys/types.h>\n\n#include \"common.hpp\"\n#include \"context.hpp\"\n#include \"security.hpp\"\n#include \"socket.hpp\"\n\nusing namespace std;\nusing namespace chrono;\n\nnamespace {\n\nint process_python_socket_error(py::error_already_set &err, int default_err) {\n    // Ensure that the `socket` in the eval below is actually python\n    // socket, and not some module found in the context of python code\n    // that caused this _recv() to be called on c++ side.\n    py::object scope = py::module::import(\"socket\").attr(\"__dict__\");\n\n    if (err.matches(py::eval(\"timeout\", scope))) {\n        return MBEDTLS_ERR_SSL_TIMEOUT;\n    } else {\n        return default_err;\n    }\n}\n\nint get_socket_type(const py::object &py_socket) {\n    int result = py_socket.attr(\"type\").cast<int>();\n    // On Linux, some flags may be stored in the socket type value, and some\n    // versions of Python update them when changing socket blocking state.\n    // We need to strip them for the values to meaningfully compare to anything.\n#ifdef SOCK_NONBLOCK\n    result &= ~SOCK_NONBLOCK;\n#endif // SOCK_NONBLOCK\n#ifdef SOCK_CLOEXEC\n    result &= ~SOCK_CLOEXEC;\n#endif // SOCK_CLOEXEC\n    return result;\n}\n\n} // namespace\n\nnamespace ssl {\n\nint Socket::bio_send(void *self,\n                     const unsigned char *buf,\n                     size_t len) noexcept try {\n    Socket *socket = reinterpret_cast<Socket *>(self);\n\n    call_method<void>(\n            socket->py_socket_, \"sendall\",\n            py::reinterpret_borrow<py::object>(\n                    PyMemoryView_FromMemory((char *) buf, len, PyBUF_READ)));\n    return (int) len;\n} catch (py::error_already_set &err) {\n    return process_python_socket_error(err, MBEDTLS_ERR_NET_SEND_FAILED);\n}\n\nnamespace {\ntuple<string, int> host_port_to_std_tuple(py::tuple host_port) {\n    return make_tuple(py::cast<string>(host_port[0]),\n                      py::cast<int>(host_port[1]));\n}\n} // namespace\n\nbool py_timeout_finite(py::object timeout) {\n    return !timeout.is(py::none());\n}\n\nuint32_t to_millis_timeout(py::object timeout) {\n    if (!py_timeout_finite(timeout)) {\n        return UINT32_MAX;\n    }\n    return (uint32_t) (py::cast<double>(timeout) * 1000.0);\n}\n\nint Socket::bio_recv(void *self,\n                     unsigned char *buf,\n                     size_t len,\n                     uint32_t mbedtls_timeout) noexcept {\n    Socket *socket = reinterpret_cast<Socket *>(self);\n\n    py::object py_buf = py::reinterpret_borrow<py::object>(\n            PyMemoryView_FromMemory((char *) buf, len, PyBUF_WRITE));\n\n    py::object py_timeout =\n            call_method<py::object>(socket->py_socket_, \"gettimeout\");\n\n    // Since this method will possibly do multiple recv() calls, we'll be\n    // adjusting the underlying timeout in the runtime. When we're done,\n    // restore the original timeout.\n    const auto restore_timeout = helpers::defer([&] {\n        call_method<void>(socket->py_socket_, \"settimeout\", py_timeout);\n    });\n\n    // If the timeout set by mbedTLS is 0 (infinite), assume the timeout we set\n    // to the underlying socket (as we do in avs_commons). Otherwise, timeout\n    // from mbedTLS gets precedence (this happens e.g. during handshake).\n    uint32_t timeout_ms = mbedtls_timeout > 0 ? mbedtls_timeout\n                                              : to_millis_timeout(py_timeout);\n    bool timeout_finite = mbedtls_timeout > 0 || py_timeout_finite(py_timeout);\n    int socket_type = get_socket_type(socket->py_socket_);\n\n    int bytes_received = 0;\n    do {\n        try {\n            if (timeout_finite) {\n                call_method<void>(socket->py_socket_, \"settimeout\",\n                                  timeout_ms / 1000.0);\n            }\n\n            py::tuple num_received_and_peer;\n            const auto before_recv = steady_clock::now();\n            if (socket_type == SOCK_DGRAM) {\n                num_received_and_peer =\n                        call_method<py::tuple>(socket->py_socket_,\n                                               \"recvfrom_into\", py_buf);\n                bytes_received = py::cast<int>(num_received_and_peer[0]);\n            } else {\n                bytes_received = call_method<int>(socket->py_socket_,\n                                                  \"recv_into\", py_buf);\n            }\n\n            if (timeout_finite) {\n                auto elapsed_ms = duration_cast<milliseconds>(\n                                          steady_clock::now() - before_recv)\n                                          .count();\n                if (elapsed_ms > timeout_ms) {\n                    timeout_ms = 0;\n                } else {\n                    timeout_ms -= elapsed_ms;\n                }\n            }\n\n            if (socket_type == SOCK_DGRAM) {\n                // Unfortunately directly comparing two py::tuples yields false,\n                // if they're not the same objects.\n                auto peer_host_port_tuple =\n                        py::cast<py::tuple>(num_received_and_peer[1]);\n                auto recv_host_port =\n                        host_port_to_std_tuple(peer_host_port_tuple);\n\n                if (socket->client_host_and_port_ != recv_host_port) {\n                    if (!socket->in_handshake_\n                            && socket->context_->connection_id().size()) {\n                        // The message may still originate from an endpoint that\n                        // we know, but we cannot verify it at this stage,\n                        // because no TLS record parsing has been made. We need\n                        // to delay it till mbedtls_ssl_read() finishes.\n                        socket->last_recv_host_and_port_ = recv_host_port;\n                    } else {\n                        // ignore this message.\n                        continue;\n                    }\n                }\n\n                // Ensure that we're still connected to the known (host, port).\n                // We may not be, if someone \"disconnected\" the socket to test\n                // connection_id behavior.\n                call_method<void>(socket->py_socket_, \"connect\",\n                                  socket->client_host_and_port_);\n            }\n            break;\n        } catch (py::error_already_set &err) {\n            bytes_received =\n                    process_python_socket_error(err,\n                                                MBEDTLS_ERR_NET_RECV_FAILED);\n\n            // when in handshake, Python exceptions are not rethrown\n            if (!socket->in_handshake_) {\n                if (!socket->exception_capturer_) {\n                    terminate();\n                }\n                *socket->exception_capturer_ = current_exception();\n            }\n            break;\n        }\n    } while (timeout_ms > 0);\n\n    return bytes_received;\n}\n\nSocket::HandshakeResult Socket::do_handshake() {\n    in_handshake_ = true;\n    const auto clear_in_handshake =\n            helpers::defer([&] { in_handshake_ = false; });\n\n    for (;;) {\n        int result = mbedtls_ssl_handshake(&mbedtls_context_);\n        if (result == 0) {\n            break;\n        } else if (result == MBEDTLS_ERR_SSL_HELLO_VERIFY_REQUIRED) {\n            // mbedtls is unable to continue in such case; one needs to\n            // reset the SSL context and try again\n            return HandshakeResult::HelloVerifyRequired;\n        } else if (result != MBEDTLS_ERR_SSL_WANT_READ\n                   && result != MBEDTLS_ERR_SSL_WANT_WRITE) {\n            throw mbedtls_error(\"mbedtls_ssl_handshake failed\", result);\n        }\n    }\n\n    return HandshakeResult::Finished;\n}\n\nnamespace {\n\nvoid debug_mbedtls(void * /*ctx*/,\n                   int /*level*/,\n                   const char *file,\n                   int line,\n                   const char *str) noexcept {\n    fprintf(stderr, \"%s:%04d: %s\", file, line, str);\n}\n\n} // namespace\n\nSocket::Socket(shared_ptr<Context> context,\n               py::object py_socket,\n               SocketType type)\n        : context_(context),\n          type_(type),\n          py_socket_(py_socket),\n          in_handshake_(false),\n          exception_capturer_(nullptr),\n          client_host_and_port_(),\n          last_recv_host_and_port_() {\n    mbedtls_ssl_init(&mbedtls_context_);\n    // Zeroize cookie context. This prevents issue\n    // https://github.com/ARMmbed/mbedtls/issues/843.\n    memset(&cookie_, 0, sizeof(cookie_));\n    mbedtls_ssl_cookie_init(&cookie_);\n    mbedtls_ssl_config_init(&config_);\n    mbedtls_entropy_init(&entropy_);\n    mbedtls_ctr_drbg_init(&rng_);\n\n    int result = mbedtls_ctr_drbg_seed(&rng_, mbedtls_entropy_func, &entropy_,\n                                       NULL, 0);\n    if (result) {\n        throw mbedtls_error(\"mbedtls_ctr_drbg_seed failed\", result);\n    }\n\n    int socket_type = get_socket_type(py_socket_);\n    result = mbedtls_ssl_config_defaults(\n            &config_,\n            type == SocketType::Client ? MBEDTLS_SSL_IS_CLIENT\n                                       : MBEDTLS_SSL_IS_SERVER,\n            socket_type == SOCK_DGRAM ? MBEDTLS_SSL_TRANSPORT_DATAGRAM\n                                      : MBEDTLS_SSL_TRANSPORT_STREAM,\n            MBEDTLS_SSL_PRESET_DEFAULT);\n    if (result) {\n        throw mbedtls_error(\"mbedtls_ssl_config_defaults failed\", result);\n    }\n\n    if (context_->debug()) {\n        mbedtls_ssl_conf_dbg(&config_, debug_mbedtls, NULL);\n    }\n\n    // Force (D)TLS 1.2 or higher\n    mbedtls_ssl_conf_min_version(&config_, MBEDTLS_SSL_MAJOR_VERSION_3,\n                                 MBEDTLS_SSL_MINOR_VERSION_3);\n    mbedtls_ssl_conf_rng(&config_, mbedtls_ctr_drbg_random, &rng_);\n\n#if defined(MBEDTLS_SSL_DTLS_CONNECTION_ID)\n    if (context->connection_id().size() > 0\n            && (result = mbedtls_ssl_conf_cid(\n                        &config_, context->connection_id().size(),\n                        MBEDTLS_SSL_UNEXPECTED_CID_IGNORE))) {\n        throw mbedtls_error(\"mbedtls_ssl_conf_cid failed\", result);\n    }\n#endif // MBEDTLS_SSL_DTLS_CONNECTION_ID\n\n    context_->security()->configure(*this);\n\n    if ((result = mbedtls_ssl_cookie_setup(&cookie_, mbedtls_ctr_drbg_random,\n                                           &rng_))) {\n        throw mbedtls_error(\"mbedtls_ssl_cookie_setup failed\", result);\n    }\n\n    mbedtls_ssl_conf_dtls_cookies(&config_,\n                                  mbedtls_ssl_cookie_write,\n                                  mbedtls_ssl_cookie_check,\n                                  &cookie_);\n\n    mbedtls_ssl_conf_session_cache(&config_, context_->session_cache(),\n                                   mbedtls_ssl_cache_get,\n                                   mbedtls_ssl_cache_set);\n    mbedtls_ssl_set_bio(&mbedtls_context_, this, &Socket::bio_send, NULL,\n                        &Socket::bio_recv);\n    mbedtls_ssl_set_timer_cb(&mbedtls_context_, &timer_,\n                             mbedtls_timing_set_delay,\n                             mbedtls_timing_get_delay);\n\n    if ((result = mbedtls_ssl_setup(&mbedtls_context_, &config_))) {\n        throw mbedtls_error(\"mbedtls_ssl_setup failed\", result);\n    }\n#if defined(MBEDTLS_SSL_DTLS_CONNECTION_ID)\n    if (context->connection_id().size() > 0\n            && (result = mbedtls_ssl_set_cid(\n                        &mbedtls_context_,\n                        MBEDTLS_SSL_CID_ENABLED,\n                        reinterpret_cast<const unsigned char *>(\n                                context->connection_id().data()),\n                        context->connection_id().size()))) {\n        throw mbedtls_error(\"mbedtls_ssl_set_cid failed\", result);\n    }\n#endif // MBEDTLS_SSL_DTLS_CONNECTION_ID\n}\n\nSocket::~Socket() {\n    mbedtls_entropy_free(&entropy_);\n    mbedtls_ssl_config_free(&config_);\n    mbedtls_ssl_cookie_free(&cookie_);\n    mbedtls_ssl_free(&mbedtls_context_);\n}\n\nvoid Socket::perform_handshake(py::tuple host_port,\n                               py::object handshake_timeouts_s_,\n                               bool py_connect) {\n    if (py_connect) {\n        call_method<void>(py_socket_, \"connect\", host_port);\n    }\n\n    last_recv_host_and_port_ = client_host_and_port_ = host_port_to_std_tuple(\n            call_method<py::tuple>(py_socket_, \"getpeername\"));\n\n    if (!handshake_timeouts_s_.is_none()) {\n        auto handshake_timeouts_s = py::cast<py::tuple>(handshake_timeouts_s_);\n        auto min = py::cast<double>(handshake_timeouts_s[0]);\n        auto max = py::cast<double>(handshake_timeouts_s[1]);\n        mbedtls_ssl_conf_handshake_timeout(&config_, uint32_t(min * 1000.0),\n                                           uint32_t(max * 1000.0));\n    }\n\n    HandshakeResult hs_result;\n\n    do {\n        int result = mbedtls_ssl_session_reset(&mbedtls_context_);\n        if (result) {\n            throw mbedtls_error(\"mbedtls_ssl_sssion_reset failed\", result);\n        }\n        string address = get<0>(client_host_and_port_);\n        if (type_ == SocketType::Client) {\n            result = mbedtls_ssl_set_hostname(&mbedtls_context_,\n                                              address.c_str());\n            if (result) {\n                throw mbedtls_error(\"mbedtls_ssl_set_hostname failed\", result);\n            }\n        } else {\n            result = mbedtls_ssl_set_client_transport_id(\n                    &mbedtls_context_,\n                    reinterpret_cast<const unsigned char *>(address.c_str()),\n                    address.length());\n            if (result) {\n                throw mbedtls_error(\n                        \"mbedtls_ssl_set_client_transport_id failed\", result);\n            }\n        }\n        hs_result = do_handshake();\n    } while (hs_result == HandshakeResult::HelloVerifyRequired);\n}\n\nvoid Socket::send(const string &data) {\n    size_t total_sent = 0;\n\n    while (total_sent < data.size()) {\n        int sent = mbedtls_ssl_write(&mbedtls_context_,\n                                     reinterpret_cast<const unsigned char *>(\n                                             &data[total_sent]),\n                                     data.size() - total_sent);\n        if (sent < 0) {\n            if (sent == MBEDTLS_ERR_SSL_WANT_READ\n                    || sent == MBEDTLS_ERR_SSL_WANT_WRITE) {\n                continue;\n            } else {\n                throw mbedtls_error(\"mbedtls_ssl_write failed\", sent);\n            }\n        }\n\n        total_sent += (size_t) sent;\n    }\n}\n\npy::bytes Socket::recv(int) {\n    unsigned char buffer[65536];\n    int result = 0;\n\n    exception_ptr captured_exception;\n    exception_capturer_ = &captured_exception;\n    const auto clear_capturer =\n            helpers::defer([&] { exception_capturer_ = nullptr; });\n\n    do {\n        result = mbedtls_ssl_read(&mbedtls_context_, buffer, sizeof(buffer));\n    } while (result == MBEDTLS_ERR_SSL_WANT_READ\n             || result == MBEDTLS_ERR_SSL_WANT_WRITE);\n\n    if (result < 0) {\n        if (result == MBEDTLS_ERR_SSL_TIMEOUT\n                || result == MBEDTLS_ERR_NET_RECV_FAILED) {\n            if (captured_exception) {\n                rethrow_exception(captured_exception);\n            }\n            throw runtime_error(\"Expected a Python exception to rethrow\");\n        } else if (result == MBEDTLS_ERR_SSL_CLIENT_RECONNECT) {\n            try {\n                do_handshake();\n            } catch (mbedtls_error &) {\n                // ignore handshake errors, if any, to make sure that the\n                // read error is the one that's actually thrown\n            }\n        }\n        throw mbedtls_error(\"mbedtls_ssl_read failed\", result);\n    }\n    if (captured_exception) {\n        throw runtime_error(\"Expected no Python exception to rethrow\");\n    }\n\n    if (last_recv_host_and_port_ != client_host_and_port_) {\n        // During Socket::_recv(), there had to be a message from a (host, port)\n        // we weren't sure about, but enabled connection_id verified it is the\n        // same client but from the different address. Let's adjust.\n        client_host_and_port_ = last_recv_host_and_port_;\n        call_method<void>(py_socket_, \"connect\", client_host_and_port_);\n    }\n\n    return py::bytes(reinterpret_cast<const char *>(buffer), result);\n}\n\npy::bytes Socket::peer_cert() {\n    const mbedtls_x509_crt *cert = mbedtls_ssl_get_peer_cert(&mbedtls_context_);\n    if (cert) {\n#if MBEDTLS_VERSION_NUMBER >= 0x03000000 && MBEDTLS_VERSION_NUMBER < 0x03010000\n        return py::bytes(reinterpret_cast<const char *>(\n                                 cert->MBEDTLS_PRIVATE(raw).MBEDTLS_PRIVATE(p)),\n                         cert->MBEDTLS_PRIVATE(raw).MBEDTLS_PRIVATE(len));\n#else  // MBEDTLS_VERSION_NUMBER >= 0x03000000\n       // && MBEDTLS_VERSION_NUMBER < 0x03010000\n        return py::bytes(reinterpret_cast<const char *>(cert->raw.p),\n                         cert->raw.len);\n#endif // MBEDTLS_VERSION_NUMBER >= 0x03000000\n       // && MBEDTLS_VERSION_NUMBER < 0x03010000\n    }\n    return py::bytes();\n}\n\npy::object Socket::__getattr__(py::object name) {\n    if (py::cast<string>(name) == \"py_socket\") {\n        return py_socket_;\n    } else {\n        return call_method<py::object>(py_socket_, \"__getattribute__\", name);\n    }\n}\n\nvoid Socket::__setattr__(py::object name, py::object value) {\n    if (py::cast<string>(name) == \"py_socket\") {\n        py_socket_ = value;\n    } else {\n        call_method<py::object>(py_socket_, \"__setattribute__\", name, value);\n    }\n}\n\nnamespace {\n\nvoid enable_reuse(const py::object &socket) {\n    // Socket binding reuse on *nixes is crazy.\n    // See http://stackoverflow.com/a/14388707 for details.\n    //\n    // In short:\n    //\n    // On *BSD and macOS, we need both SO_REUSEADDR and SO_REUSEPORT, so\n    // that we can bind multiple sockets to exactly the same address and\n    // port (before calling connect(), which will resolve the ambiguity).\n    //\n    // On Linux, SO_REUSEADDR alone already has those semantics for UDP\n    // sockets. Linux also has SO_REUSEPORT, but for UDP sockets, it has\n    // very special meaning that enables round-robin load-balancing between\n    // sockets bound to the same address and port, and we don't want that.\n    //\n    // Some more exotic systems (Windows, Solaris) do not have SO_REUSEPORT\n    // at all, so we can always just set SO_REUSEADDR and see what happens.\n    // It may or may not work, but at least it'll compile ;)\n#ifdef SO_REUSEADDR\n    call_method<void>(socket, \"setsockopt\", SOL_SOCKET, SO_REUSEADDR, 1);\n#endif\n#if !defined(__linux__) && defined(SO_REUSEPORT)\n    call_method<void>(socket, \"setsockopt\", SOL_SOCKET, SO_REUSEPORT, 1);\n#endif\n}\n\n} // namespace\n\nServerSocket::ServerSocket(shared_ptr<Context> context, py::object py_socket)\n        : context_(context), py_socket_(py_socket) {\n    enable_reuse(py_socket_);\n}\n\nunique_ptr<Socket> ServerSocket::accept(py::object handshake_timeouts_s) {\n    int socket_type = get_socket_type(py_socket_);\n    py::object client_py_sock;\n    py::tuple remote_addr;\n\n    if (socket_type == SOCK_DGRAM) {\n        // use old socket to communicate with client\n        // create a new one for listening\n        py::object bound_addr =\n                call_method<py::object>(py_socket_, \"getsockname\");\n        py::tuple data__remote_addr =\n                call_method<py::tuple>(py_socket_, \"recvfrom\", 1,\n                                       (int) MSG_PEEK);\n        remote_addr = py::cast<py::tuple>(data__remote_addr[1]);\n\n        client_py_sock = py::eval(\"socket.socket\")(py_socket_.attr(\"family\"),\n                                                   socket_type,\n                                                   py_socket_.attr(\"proto\"));\n        enable_reuse(client_py_sock);\n\n        call_method<void>(client_py_sock, \"bind\", bound_addr);\n\n        // we have called recvfrom() on py_socket_ and we now want that data\n        // to show up on the client_socket - so let's swap them\n        swap(py_socket_, client_py_sock);\n\n        call_method<void>(client_py_sock, \"connect\", remote_addr);\n    } else {\n        // TCP\n        py::tuple client_py_sock__remote_addr =\n                call_method<py::tuple>(py_socket_, \"accept\");\n        client_py_sock = client_py_sock__remote_addr[0];\n        remote_addr = py::cast<py::tuple>(client_py_sock__remote_addr[1]);\n\n        enable_reuse(client_py_sock);\n    }\n\n    unique_ptr<Socket> client_sock =\n            make_unique<Socket>(context_, move(client_py_sock),\n                                SocketType::Server);\n    client_sock->perform_handshake(remote_addr, handshake_timeouts_s, false);\n    return client_sock;\n}\n\npy::object ServerSocket::__getattr__(py::object name) {\n    if (py::cast<string>(name) == \"py_socket\") {\n        return py_socket_;\n    } else {\n        return call_method<py::object>(py_socket_, \"__getattribute__\", name);\n    }\n}\n\nvoid ServerSocket::__setattr__(py::object name, py::object value) {\n    if (py::cast<string>(name) == \"py_socket\") {\n        py_socket_ = value;\n    } else {\n        call_method<py::object>(py_socket_, \"__setattribute__\", name, value);\n    }\n}\n\n} // namespace ssl\n"
  },
  {
    "path": "tools/test-framework-tools/pymbedtls/src/socket.hpp",
    "content": "/*\n * Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n * AVSystem Anjay LwM2M SDK\n * All rights reserved.\n *\n * Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n * See the attached LICENSE file for details.\n */\n\n#ifndef PYMBEDTLS_SOCKET_HPP\n#define PYMBEDTLS_SOCKET_HPP\n#include <mbedtls/ctr_drbg.h>\n#include <mbedtls/debug.h>\n#include <mbedtls/entropy.h>\n#include <mbedtls/error.h>\n#include <mbedtls/version.h>\n#if MBEDTLS_VERSION_NUMBER >= 0x02040000 // mbed TLS 2.4 deprecated net.h\n#    include <mbedtls/net_sockets.h>\n#else // support mbed TLS <=2.3\n#    include <mbedtls/net.h>\n#endif\n#include <mbedtls/ssl.h>\n#include <mbedtls/ssl_cache.h>\n#include <mbedtls/ssl_cookie.h>\n#include <mbedtls/timing.h>\n\n#include <exception>\n#include <memory>\n\n#include \"pybind11_interop.hpp\"\n\nnamespace ssl {\nclass Context;\n\nenum class SocketType { Client, Server };\n\nclass Socket {\n    friend class SecurityInfo;\n    friend class PskSecurity;\n    friend class CertSecurity;\n\n    enum class HandshakeResult { Finished, HelloVerifyRequired };\n\n    std::shared_ptr<Context> context_;\n    mbedtls_ssl_context mbedtls_context_;\n    mbedtls_ssl_cookie_ctx cookie_;\n    mbedtls_ssl_config config_;\n    mbedtls_entropy_context entropy_;\n    mbedtls_ctr_drbg_context rng_;\n    mbedtls_timing_delay_context timer_;\n    std::vector<int> ciphersuites_;\n\n    SocketType type_;\n    py::object py_socket_;\n    bool in_handshake_;\n\n    // Used to capture exceptions that may be thrown in callbacks that are\n    // implemented in C++, but called from C code. As it's generally wrong to\n    // throw exception through the stack which incorporates C code, we capture\n    // the exception in case we expect some callback to generate it, and then\n    // rethrow it in a safe place.\n    //\n    // As we want to capture exceptions only when explicicitly requested, we use\n    // a pointer to std::exception_ptr for additional level of indirection.\n    std::exception_ptr *exception_capturer_;\n\n    // Used to match incoming packets with a client we initially are\n    // connect()'ed to. It may change, if, for example connection_id extension\n    // is used and we received a packet from a different endpoint but the\n    // connection_id matched.\n    std::tuple<std::string, int> client_host_and_port_;\n    // Updated whenever we receive a packet from an endpoint we don't recognize.\n    // It must be there, because at the time of performing recv() we haven't\n    // parsed the packet as TLS record, and we cannot extract the connection_id\n    // (if any) to see if the packet is indeed valid and should be handled.\n    std::tuple<std::string, int> last_recv_host_and_port_;\n\n    static int\n    bio_send(void *self, const unsigned char *buf, size_t len) noexcept;\n    static int bio_recv(void *self,\n                        unsigned char *buf,\n                        size_t len,\n                        uint32_t timeout_ms) noexcept;\n\n    HandshakeResult do_handshake();\n\npublic:\n    Socket(std::shared_ptr<Context> context,\n           py::object py_socket,\n           SocketType type);\n\n    ~Socket();\n\n    void perform_handshake(py::tuple host_port,\n                           py::object handshake_timeouts_s_,\n                           bool py_connect);\n    void send(const std::string &data);\n    py::bytes recv(int);\n    py::bytes peer_cert();\n\n    // __getattr__ (and __setattr__) is called when Python is unable to directly\n    // find the attribute of an object. By redirecting this to __get_attribute__\n    // of the py_socket_ field, we're essentially extending the class of\n    // py_socket_.\n    py::object __getattr__(py::object name);\n    void __setattr__(py::object name, py::object value);\n\n    void connect(py::tuple host_port, py::object handshake_timeouts_s) {\n        perform_handshake(host_port, handshake_timeouts_s, true);\n    }\n};\n\nclass ServerSocket {\n    std::shared_ptr<Context> context_;\n    py::object py_socket_;\n\npublic:\n    ServerSocket(std::shared_ptr<Context> context, py::object py_socket);\n\n    std::unique_ptr<Socket> accept(py::object handshake_timeouts_s);\n\n    py::object __getattr__(py::object name);\n    void __setattr__(py::object name, py::object value);\n};\n\n} // namespace ssl\n\n#endif // PYMBEDTLS_SOCKET_HPP\n"
  },
  {
    "path": "tools/test-framework-tools/tools/framework_tools/__init__.py",
    "content": "# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n"
  },
  {
    "path": "tools/test-framework-tools/tools/framework_tools/coap_file_server.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\nimport contextlib\nimport socket\nimport struct\nimport threading\nimport time\nimport zlib\nfrom typing import NamedTuple\n\nfrom .lwm2m.messages import *\n\n\nclass CoapFileServer:\n    Resource = NamedTuple('Resource', [('etag', bytes), ('data', bytes)])\n\n    def __init__(self, coap_server: coap.Server, binding='U'):\n        self._resources = {}\n        self._server = coap_server\n        self.requests = []\n        self.should_ignore_request = lambda _: False\n        self.binding = binding\n\n    def set_resource(self,\n                     path: str,\n                     data: Optional[bytes],\n                     etag: Optional[bytes] = None):\n        if data is not None:\n            if etag is None:\n                etag = struct.pack('>I', zlib.crc32(data))\n            self._resources[path] = self.Resource(etag=etag, data=data)\n        else:\n            del self._resources[path]\n\n    def get_resource_uri(self, path: CoapPath):\n        if path not in self._resources:\n            raise ValueError('unknown resource: %s' % (path,))\n\n        if isinstance(self._server, coap.TlsServer):\n            proto = 'coaps'\n        elif isinstance(self._server, coap.Server):\n            proto = 'coap'\n        else:\n            raise TypeError('unexpected server type')\n\n        if self.binding == 'N':\n            return '%s+nidd://%s' % (proto, path)\n\n        return '%s://127.0.0.1:%d%s' % (proto, self._server.get_listen_port(), path)\n\n    def _recv_request(self, timeout_s):\n        if self._server.get_remote_addr() is None and not self._server.accepted_connection:\n            try:\n                self._server.listen(timeout_s=timeout_s)\n            except socket.timeout:\n                pass\n\n        return self._server.recv(timeout_s=timeout_s)\n\n    def handle_recvd_request(self, req):\n        self.requests.append(req)\n\n        if self.should_ignore_request(req):\n            return\n\n        if req.type != coap.Type.CONFIRMABLE:\n            return\n\n        if req.code.cls == 0:\n            if req.code != coap.Code.REQ_GET:\n                self._server.send(Lwm2mErrorResponse.matching(req)(\n                    code=coap.Code.RES_METHOD_NOT_ALLOWED).fill_placeholders())\n                return\n        else:\n            self._server.send(Lwm2mReset.matching(req).fill_placeholders())\n            return\n\n        # Confirmable GET request\n        path = req.get_uri_path()\n        if path not in self._resources:\n            self._server.send(Lwm2mErrorResponse.matching(req)(\n                code=coap.Code.RES_NOT_FOUND).fill_placeholders())\n            return\n\n        # CON GET to a known path\n        block2 = req.get_options(coap.Option.BLOCK2)\n        if block2:\n            block2 = block2[0]\n        else:\n            block2 = coap.Option.BLOCK2(\n                seq_num=0, has_more=False, block_size=1024)\n\n        resource = self._resources[path]\n        data_offset = block2.seq_num() * block2.block_size()\n        res_block2 = coap.Option.BLOCK2(seq_num=block2.seq_num(),\n                                        has_more=data_offset + block2.block_size() < len(\n                                            resource.data),\n                                        block_size=block2.block_size())\n        content = resource.data[data_offset:data_offset + block2.block_size()]\n\n        self._server.send(Lwm2mContent.matching(req)(content=content,\n                                                     options=[res_block2,\n                                                              coap.Option.ETAG(resource.etag)]))\n\n    def handle_request(self, timeout_s=5.0):\n        self.handle_recvd_request(self._recv_request(timeout_s=timeout_s))\n\n\nclass CoapFileServerThread(threading.Thread):\n    def __init__(self, coap_server: coap.Server = None):\n        super().__init__()\n\n        self._mutex = threading.RLock()\n        self._file_server = CoapFileServer(coap_server or coap.Server())\n        self._shutdown = False\n        self._timeout_occurred = False\n\n    def run(self):\n        while not self._shutdown:\n            try:\n                with self._mutex:\n                    self._file_server.handle_request()\n            except socket.timeout:\n                self._timeout_occurred = True\n            time.sleep(0.01)  # yield to the scheduler\n\n    def join(self):\n        self._shutdown = True\n        super().join()\n\n    def get_timeout_occurred(self):\n        return self._timeout_occurred\n\n    def reset_timeout_occurred(self):\n        self._timeout_occurred = False\n\n    @property\n    @contextlib.contextmanager\n    def file_server(self):\n        with self._mutex:\n            yield self._file_server\n"
  },
  {
    "path": "tools/test-framework-tools/tools/framework_tools/lwm2m/__init__.py",
    "content": "# -*- coding: utf-8 -*-\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n"
  },
  {
    "path": "tools/test-framework-tools/tools/framework_tools/lwm2m/coap/__init__.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nfrom . import utils\n\nfrom .code import Code\nfrom .content_format import ContentFormat\nfrom .option import Option, ContentFormatOption, AcceptOption\nfrom .packet import Packet\nfrom .server import Server, TlsServer, DtlsServer\nfrom .type import Type\n\n__all__ = [\n    'utils',\n    'Code',\n    'ContentFormat',\n    'Option', 'ContentFormatOption', 'AcceptOption',\n    'Packet',\n    'Server', 'TlsServer', 'DtlsServer',\n    'Type'\n]\n"
  },
  {
    "path": "tools/test-framework-tools/tools/framework_tools/lwm2m/coap/code.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nimport struct\n\n\nclass Code(object):\n    @staticmethod\n    def powercmd_parse(text):\n        from powercmd.utils import match_instance\n\n        try:\n            cls, detail = map(int, text.split('.'))\n            return Code(cls, detail)\n        except ValueError:\n            return match_instance(Code, text)\n\n    @staticmethod\n    def powercmd_complete(text):\n        from powercmd.utils import get_available_instance_names\n        from powercmd.match_string import match_string\n\n        possible = get_available_instance_names(Code)\n        return match_string(text, possible)\n\n    def __init__(self, cls, detail):\n        if cls < 0 or cls > 7:\n            raise ValueError('invalid code class')\n        if detail < 0 or cls > 31:\n            raise ValueError('invalid code detail')\n\n        self.cls = cls\n        self.detail = detail\n\n    @staticmethod\n    def parse(data):\n        return Code.from_byte(struct.unpack('!B', data[:1]))\n\n    @staticmethod\n    def from_byte(val):\n        return Code((val >> 5) & 0x7, val & 0x1F)\n\n    def as_byte(self):\n        return (self.cls << 5) | self.detail\n\n    def get_name(self):\n        for name, code in Code.__dict__.items():\n            if (isinstance(code, Code)\n                    and self.cls == code.cls\n                    and self.detail == code.detail):\n                return name\n        return None\n\n    def __str__(self):\n        name = self.get_name()\n        return '%d.%02d%s' % (self.cls, self.detail, ' (%s)' % name if name else '')\n\n    def __repr__(self):\n        name = self.get_name()\n        if name:\n            return 'coap.Code.%s' % (name,)\n        else:\n            return 'coap.Code(\"%d.%02d\")' % (self.cls, self.detail)\n\n    def __eq__(self, other):\n        return (type(self) is type(other)\n                and self.cls == other.cls\n                and self.detail == other.detail)\n\n    def is_request(self):\n        return self.cls == 0 and self.detail != 0\n\n    def is_response(self):\n        return self.cls in (2, 4, 5)\n\n\nCode.EMPTY = Code(0, 0)\n\nCode.REQ_GET =    Code(0, 1)\nCode.REQ_POST =   Code(0, 2)\nCode.REQ_PUT =    Code(0, 3)\nCode.REQ_DELETE = Code(0, 4)\nCode.REQ_FETCH  = Code(0, 5)\nCode.REQ_IPATCH = Code(0, 7)\n\nCode.RES_CREATED                    = Code(2,  1)\nCode.RES_DELETED                    = Code(2,  2)\nCode.RES_VALID                      = Code(2,  3)\nCode.RES_CHANGED                    = Code(2,  4)\nCode.RES_CONTENT                    = Code(2,  5)\nCode.RES_CONTINUE                   = Code(2,  31)\nCode.RES_BAD_REQUEST                = Code(4,  0)\nCode.RES_UNAUTHORIZED               = Code(4,  1)\nCode.RES_BAD_OPTION                 = Code(4,  2)\nCode.RES_FORBIDDEN                  = Code(4,  3)\nCode.RES_NOT_FOUND                  = Code(4,  4)\nCode.RES_METHOD_NOT_ALLOWED         = Code(4,  5)\nCode.RES_NOT_ACCEPTABLE             = Code(4,  6)\nCode.RES_REQUEST_ENTITY_INCOMPLETE  = Code(4,  8)\nCode.RES_PRECONDITION_FAILED        = Code(4, 12)\nCode.RES_REQUEST_ENTITY_TOO_LARGE   = Code(4, 13)\nCode.RES_UNSUPPORTED_CONTENT_FORMAT = Code(4, 15)\nCode.RES_INTERNAL_SERVER_ERROR      = Code(5,  0)\nCode.RES_NOT_IMPLEMENTED            = Code(5,  1)\nCode.RES_BAD_GATEWAY                = Code(5,  2)\nCode.RES_SERVICE_UNAVAILABLE        = Code(5,  3)\nCode.RES_GATEWAY_TIMEOUT            = Code(5,  4)\nCode.RES_PROXYING_NOT_SUPPORTED     = Code(5,  5)\n\nCode.SIGNALING_CSM     = Code(7, 1)\nCode.SIGNALING_PING    = Code(7, 2)\nCode.SIGNALING_PONG    = Code(7, 3)\nCode.SIGNALING_RELEASE = Code(7, 4)\nCode.SIGNALING_ABORT   = Code(7, 5)\n"
  },
  {
    "path": "tools/test-framework-tools/tools/framework_tools/lwm2m/coap/content_format.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\n\nclass ContentFormat(object):\n    TEXT_PLAIN = 0\n    APPLICATION_LINK = 40\n    APPLICATION_OCTET_STREAM = 42\n    APPLICATION_CBOR = 60\n    APPLICATION_LWM2M_SENML_JSON = 110\n    APPLICATION_LWM2M_SENML_CBOR = 112\n    APPLICATION_PKCS7_CERTS_ONLY = 281\n    APPLICATION_PKCS10 = 286\n    APPLICATION_PKIX_CERT = 287\n    APPLICATION_LWM2M_SENML_ETCH_JSON = 320\n    APPLICATION_LWM2M_SENML_ETCH_CBOR = 322\n    APPLICATION_LWM2M_TEXT_LEGACY = 1541\n    APPLICATION_LWM2M_TLV_LEGACY = 1542\n    APPLICATION_LWM2M_JSON_LEGACY = 1543\n    APPLICATION_LWM2M_OPAQUE_LEGACY = 1544\n    APPLICATION_LWM2M_TLV = 11542\n    APPLICATION_LWM2M_JSON = 11543\n    APPLICATION_LWM2M_CBOR = 11544\n\n    @staticmethod\n    def to_str(fmt):\n        for k, v in ContentFormat.__dict__.items():\n            if v == fmt:\n                return k\n        return 'Unknown format (%s)' % (str(fmt),)\n\n    @staticmethod\n    def to_repr(fmt):\n        for k, v in ContentFormat.__dict__.items():\n            if v == fmt:\n                return k\n        return str(fmt)\n\n    @staticmethod\n    def _iter_formats(selector=lambda f: True):\n        class EmptyClass:\n            pass\n        # Removing common fields, to avoid accidental selection of some internal fields.\n        distinct_fields = ContentFormat.__dict__.keys() - EmptyClass.__dict__.keys()\n        return (getattr(ContentFormat, field) for field in distinct_fields \\\n                if field.replace('_', '').isupper() and selector(field))\n\n    @staticmethod\n    def iter():\n        return ContentFormat._iter_formats()\n"
  },
  {
    "path": "tools/test-framework-tools/tools/framework_tools/lwm2m/coap/option.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nimport inspect\nimport math\nimport struct\n\nfrom .content_format import ContentFormat\nfrom .utils import hexlify_nonprintable\n\n\nclass OptionLike(object):\n    def __init__(self, cls, number):\n        self.cls = cls\n        self.number = number\n\n\nclass Option(OptionLike):\n    @staticmethod\n    def powercmd_complete(text):\n        from powercmd.utils import match_instance, get_available_instance_names\n        from powercmd.match_string import match_string\n\n        paren_idx = text.find('(')\n        if paren_idx >= 0:\n            # argument completion\n            opt_name = text[:paren_idx]\n            if opt_name == 'Option':\n                ctor = Option\n            else:\n                ctor = match_instance(Option, text[:paren_idx],\n                                      match_extra_cls=[OptionConstructor])\n                if not ctor:\n                    return []\n                elif isinstance(ctor, OptionConstructor):\n                    ctor = ctor.content_mapper\n\n            sig = inspect.signature(ctor)\n\n            # the '' is a hack to prevent Cmd from actually inserting the completion\n            return ['', str(sig)]\n\n        possible = get_available_instance_names(Option,\n                                                match_extra_cls=[OptionConstructor],\n                                                append_paren_to_callables=True)\n        return match_string(text, possible)\n\n    @staticmethod\n    def powercmd_parse(text):\n        from powercmd.utils import match_instance\n\n        paren_idx = text.find('(')\n        if paren_idx >= 0:\n            ctor = match_instance(Option, text[:paren_idx],\n                                  match_extra_cls=[OptionConstructor])\n            arg = eval(text[paren_idx:])\n            opt = ctor(*arg) if isinstance(arg, tuple) else ctor(arg)\n        else:\n            opt = match_instance(Option, text)\n\n        return opt\n\n    def __init__(self, number, content=b''):\n        OptionLike.__init__(self, type(self), number)\n        self.content = content\n\n    @staticmethod\n    def parse_ext_value(short_value, data):\n        if short_value < 13:\n            return short_value\n        elif short_value == 13:\n            return 13 + struct.unpack('!B', bytes([next(data)]))[0]\n        elif short_value == 14:\n            return 13 + 256 + struct.unpack('!H', bytes([next(data) for i in range(2)]))[0]\n        elif short_value == 15:\n            raise ValueError('reserved short value')\n\n    @staticmethod\n    def parse(data_, prev_opt_number):\n        short_delta_length, = struct.unpack('!B', bytes([next(data_)]))\n        short_delta = (short_delta_length >> 4) & 0x0F\n        short_length = short_delta_length & 0x0F\n\n        number_delta = Option.parse_ext_value(short_delta, data_)\n        number = prev_opt_number + number_delta\n\n        length = Option.parse_ext_value(short_length, data_)\n\n        try:\n            content = bytes([next(data_) for i in range(length)])\n        except StopIteration:\n            raise ValueError('incomplete option')\n\n        return Option.get_class_by_number(number)(number, content)\n\n    @staticmethod\n    def serialize_ext_value(value):\n        if value >= 13 + 256:\n            return 14, struct.pack('!H', value - 13 - 256)\n        elif value >= 13:\n            return 13, struct.pack('!B', value - 13)\n        else:\n            return value, b''\n\n    def serialize(self, prev_opt_number):\n        short_delta, ext_delta = Option.serialize_ext_value(self.number - prev_opt_number)\n        short_length, ext_length = Option.serialize_ext_value(len(self.content))\n\n        return struct.pack('!B', (short_delta << 4) | short_length) + ext_delta + ext_length + self.content\n\n    @classmethod\n    def get_class_by_number(cls, number):\n        for opt in cls.__dict__.values():\n            if isinstance(opt, OptionLike) and opt.number == number:\n                return opt.cls\n\n        return Option\n\n    @classmethod\n    def get_name_by_number(cls, number):\n        for name, opt in cls.__dict__.items():\n            if isinstance(opt, OptionLike) and opt.number == number:\n                return name\n\n        return None\n\n    @classmethod\n    def get_number_of(cls, ctor):\n        for opt in cls.__dict__.values():\n            if isinstance(opt, OptionLike) and ctor is opt:\n                return opt.number\n\n        return None\n\n    def content_to_str(self):\n        return hexlify_nonprintable(self.content)\n\n    def __str__(self):\n        opt_name = Option.get_name_by_number(self.number)\n        return 'option %d%s, content (%d bytes): %s' % (\n            self.number,\n            ' (%s)' % (opt_name,) if opt_name else '',\n            len(self.content),\n            self.content_to_str())\n\n    def __repr__(self):\n        opt_name = Option.get_name_by_number(self.number)\n        if opt_name:\n            return 'coap.Option.%s' % (opt_name,)\n        else:\n            return 'coap.Option(%d, %s)' % (self.number, repr(self.content))\n\n    def __eq__(self, other):\n        return (type(self) is type(other)\n                and self.number == other.number\n                and self.content == other.content)\n\n    def matches(self, what):\n        num = Option.get_number_of(what)\n        assert num is not None\n        return self.number == num\n\n\nclass IntOption(Option):\n    @staticmethod\n    def _pad_to_power_of_2_size(val):\n        def _min_power_of_2_greater_or_equal(x):\n            x -= 1\n            x |= x >> 1\n            x |= x >> 2\n            x |= x >> 4\n            x |= x >> 8\n            x |= x >> 16\n            return x + 1\n\n        min_pot_ge = _min_power_of_2_greater_or_equal(len(val))\n        return (b'\\0' * (min_pot_ge - len(val))) + val\n\n    def content_to_int(self):\n        padded = IntOption._pad_to_power_of_2_size(self.content)\n\n        return {\n            0: lambda: 0,\n            1: lambda: struct.unpack('!B', padded)[0],\n            2: lambda: struct.unpack('!H', padded)[0],\n            4: lambda: struct.unpack('!I', padded)[0],\n            8: lambda: struct.unpack('!Q', padded)[0]\n        }[len(padded)]()\n\n    def content_to_str(self):\n        return str(self.content_to_int())\n\n    def __eq__(self, other):\n        return (type(self) is type(other)\n                and self.number == other.number\n                and self.content_to_int() == other.content_to_int())\n\n    def __repr__(self):\n        opt_name = Option.get_name_by_number(self.number)\n        return 'coap.Option.%s(%s)' % (opt_name, self.content_to_str())\n\n\nclass StringOption(Option):\n    def __repr__(self):\n        opt_name = Option.get_name_by_number(self.number)\n        return 'coap.Option.%s(%s)' % (opt_name, repr(self.content_to_str()))\n\n\nclass OpaqueOption(Option):\n    def __repr__(self):\n        opt_name = Option.get_name_by_number(self.number)\n        return 'coap.Option.%s(%s)' % (opt_name, repr(self.content_to_str()))\n\n\nclass ContentFormatOption(IntOption):\n    @staticmethod\n    def powercmd_complete(text):\n        from powercmd.utils import get_available_instance_names\n        from powercmd.match_string import match_string\n\n        possible = get_available_instance_names(ContentFormatOption)\n        return match_string(text, possible)\n\n    @staticmethod\n    def powercmd_parse(text):\n        from powercmd.utils import match_instance\n\n        try:\n            return Option.CONTENT_FORMAT(int(text))\n        except ValueError:\n            return match_instance(ContentFormatOption, text)\n\n    def content_to_str(self):\n        number = self.content_to_int()\n        for name, opt in ContentFormat.__dict__.items():\n            if opt == number:\n                return '%d (%s)' % (number, name)\n        return str(number)\n\n    def __repr__(self):\n        opt_name = Option.get_name_by_number(self.number)\n        return 'coap.Option.%s(%s)' % (\n            opt_name, ContentFormat.to_repr(self.content_to_int()))\n\n\nclass AcceptOption(ContentFormatOption):\n    @staticmethod\n    def powercmd_parse(text):\n        from powercmd.utils import match_instance\n\n        try:\n            return Option.ACCEPT(int(text))\n        except ValueError:\n            return match_instance(AcceptOption, text)\n\n\nclass OptionConstructor(OptionLike):\n    def __init__(self, cls, number, content_mapper):\n        OptionLike.__init__(self, cls, number)\n        self.content_mapper = content_mapper\n\n    def __call__(self, *args, **kwargs):\n        return self.cls(self.number, self.content_mapper(*args, **kwargs))\n\n\nclass BlockOption(IntOption):\n    def seq_num(self):\n        content = self.content_to_int()\n        return content >> 4\n\n    def block_size(self):\n        content = self.content_to_int()\n        return 2 ** (4 + (content & 0x7))\n\n    def has_more(self):\n        content = self.content_to_int()\n        return bool(content & 0x8)\n\n    def content_to_str(self):\n        return 'seq_num=%d, has_more=%d, block_size=%d' % (self.seq_num(),\n                                                           self.has_more(),\n                                                           self.block_size())\n\nclass OscoreOption(OpaqueOption):\n    def __init__(self, number, content):\n        super().__init__(number, content)\n\n        piv_len = self.content[0] & int('111', 2)\n        has_kid = bool(self.content[0] & int('1000', 2))\n        has_kid_ctx = bool(self.content[0] & int('10000', 2))\n\n        if piv_len == 0:\n            self.piv = None\n        else:\n            self.piv = int.from_bytes(self.content[1:piv_len], byteorder='big', signed=False)\n\n        kid_ctx_len = 0\n        if has_kid_ctx:\n            kid_ctx_len = self.content[piv_len+1]\n            self.kid_ctx = self.content[piv_len+2:piv_len+2+kid_ctx_len]\n        else:\n            self.kid_ctx = None\n\n        if has_kid:\n            self.kid = self.content[(1 + piv_len + (1 + kid_ctx_len) if has_kid_ctx else 0):]\n        else:\n            self.kid = None\n\n    def content_to_str(self):\n        return 'piv=%d, kid=%a, kid_ctx=%a' \\\n            % (self.piv, self.kid.hex() if self.kid else None, self.kid_ctx.hex() if self.kid_ctx else None)\n\n    def key_id(self):\n        return self.kid\n\n    def id_ctx(self):\n        return self.kid_ctx\n\n    def partial_iv(self):\n        return self.piv\n\n\ndef is_power_of_2(num):\n    return ((num & (num - 1)) == 0) and num > 0\n\n\ndef pack_block(seq_num: int,\n               has_more: bool,\n               block_size: int):\n    if (seq_num >= 2 ** 20\n            or not is_power_of_2(block_size)\n            or not 16 <= block_size <= 2048):\n        raise ValueError('invalid arguments')\n\n    szx = round(math.log(block_size, 2)) - 4\n    unpacked = (seq_num << 4) | (int(has_more) << 3) | (szx & 0x7)\n    packed = struct.pack('!I', unpacked)\n    return packed.lstrip(b'\\0')\n\n\nOption.IF_NONE_MATCH   = Option(5)\n\nOption.IF_MATCH        = OptionConstructor(Option, 1, lambda x: x)\nOption.ETAG            = OptionConstructor(Option, 4, lambda x: x)\n\nOption.URI_HOST        = OptionConstructor(StringOption, 3,  lambda string: bytes(string, 'ascii'))\nOption.LOCATION_PATH   = OptionConstructor(StringOption, 8,  lambda string: bytes(string, 'ascii'))\nOption.URI_PATH        = OptionConstructor(StringOption, 11, lambda string: bytes(string, 'ascii'))\nOption.URI_QUERY       = OptionConstructor(StringOption, 15, lambda string: bytes(string, 'ascii'))\nOption.LOCATION_QUERY  = OptionConstructor(StringOption, 20, lambda string: bytes(string, 'ascii'))\nOption.PROXY_URI       = OptionConstructor(StringOption, 35, lambda string: bytes(string, 'ascii'))\nOption.PROXY_SCHEME    = OptionConstructor(StringOption, 39, lambda string: bytes(string, 'ascii'))\n\nOption.OBSERVE         = OptionConstructor(IntOption, 6, lambda int8: struct.pack('!B', int8))\nOption.URI_PORT        = OptionConstructor(IntOption, 7,  lambda int16: struct.pack('!H', int16))\nOption.MAX_AGE         = OptionConstructor(IntOption, 14, lambda int32: struct.pack('!I', int32))\nOption.SIZE1           = OptionConstructor(IntOption, 60, lambda int32: struct.pack('!I', int32))\n\nOption.BLOCK1          = OptionConstructor(BlockOption, 27, pack_block)\nOption.BLOCK2          = OptionConstructor(BlockOption, 23, pack_block)\n\nOption.OSCORE          = OptionConstructor(OscoreOption, 9, lambda data: data)\n\n\ndef pack_content_format(fmt: ContentFormat):\n    return struct.pack('!H', fmt)\n\n\nOption.CONTENT_FORMAT  = OptionConstructor(ContentFormatOption, 12, pack_content_format)\nOption.ACCEPT          = OptionConstructor(AcceptOption,        17, pack_content_format)\n\nfor fmt_name, fmt_value in ((k, v) for k, v in ContentFormat.__dict__.items() if isinstance(v, int)):\n    setattr(ContentFormatOption, fmt_name, Option.CONTENT_FORMAT(fmt_value))\n    setattr(Option.CONTENT_FORMAT, fmt_name, Option.CONTENT_FORMAT(fmt_value))\n    setattr(AcceptOption, fmt_name, Option.ACCEPT(fmt_value))\n    setattr(Option.ACCEPT, fmt_name, Option.ACCEPT(fmt_value))\n"
  },
  {
    "path": "tools/test-framework-tools/tools/framework_tools/lwm2m/coap/packet.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\nimport collections.abc\nimport itertools\nimport operator\nimport struct\nimport logging\nimport textwrap\n\nfrom .code import Code\nfrom .option import Option\nfrom .type import Type\nfrom .utils import hexlify, hexlify_nonprintable\nfrom .transport import Transport\n\nfrom collections import namedtuple\n\nHeader = namedtuple('Header', ('code', 'version', 'type', 'id', 'token_length'))\n\n\nclass Placeholder:\n    def __init__(self, name):\n        self.name = name\n\n    def __str__(self):\n        return self.name\n\n    def __repr__(self):\n        return self.name\n\n    def __getattr__(self, _):\n        return self\n\n    def __call__(self, *_, **__):\n        return self\n\n    def __iter__(self):\n        return iter([])\n\n\n# A special value that may be used as msg_id, token, options or content to\n# indicate that any value is acceptable when comparing it to another message.\nANY = Placeholder('ANY')\n\n\nclass SequentialMsgIdGenerator:\n    def __init__(self, start_id):\n        self.curr_id = start_id\n\n    def __next__(self):\n        return self.next()\n\n    def next(self):\n        self.curr_id = (self.curr_id + 1) % 2 ** 16\n        return self.curr_id\n\n\n_ID_GENERATOR = SequentialMsgIdGenerator(0x1337)\n\n\nclass RandomTokenGenerator:\n    def __next__(self):\n        return self.next()\n\n    @staticmethod\n    def next():\n        with open('/dev/urandom', 'rb') as f:\n            return f.read(8)\n\n\n_TOKEN_GENERATOR = RandomTokenGenerator()\n\ndef _parse_tcp_header(bytestream):\n    len_tkl = next(bytestream)\n    short_len = len_tkl >> 4\n    token_length = len_tkl & 0x0F\n\n    if short_len <= 12:\n        length = short_len\n    elif short_len == 13:\n        length = next(bytestream) + 13\n    elif short_len == 14:\n        length_bytes = bytes([next(bytestream) for i in range(2)])\n        length = int.from_bytes(length_bytes, byteorder='big') + 269\n    else:\n        length_bytes = bytes([next(bytestream) for i in range(4)])\n        length = int.from_bytes(length_bytes, byteorder='big') + 65805\n\n    code = Code.from_byte(next(bytestream))\n    return Header(code, None, None, None, token_length), length + token_length\n\ndef _parse_udp_header(bytestream):\n    header = bytes([next(bytestream) for i in range(4)])\n    version_type_token_length, code, msg_id = struct.unpack('!BBH', header)\n\n    code = Code.from_byte(code)\n    version = (version_type_token_length >> 6) & 0x03\n    type = Type((version_type_token_length >> 4) & 0x03)\n    token_length = version_type_token_length & 0x0F\n\n    if version != 1:\n        raise ValueError(\"invalid CoAP version: %d, expected 1\" % version)\n\n    return Header(code, version, type, msg_id, token_length)\n\nclass Packet(object):\n    def __init__(self, type=None, code=1, msg_id=0, token=b'', options=None, content=b'', version=1):\n        self.version = version\n        self.type = type\n        self.code = code\n        self.msg_id = msg_id\n        self.token = token\n        self.options = sorted(options or [], key=operator.attrgetter('number')) if options is not ANY else ANY\n        if content is ANY or content is None:\n            self.content = content\n        else:\n            self.content = bytes(content)\n\n    def __repr__(self):\n        return ('coap.Packet(type=%s,\\n'\n                '            code=%s,\\n'\n                '            msg_id=%s,\\n'\n                '            token=%s,\\n'\n                '            options=%s,\\n'\n                '            content=%s,\\n'\n                '            version=%s)'\n                % (repr(self.type),\n                   repr(self.code),\n                   repr(self.msg_id),\n                   repr(self.token),\n                   repr(self.options),\n                   repr(self.content),\n                   self.version))\n\n    def _size_breakdown(self, header):\n        serialized_opt_sizes = []\n        prev_opt_number = 0\n        for o in self.options:\n            serialized_opt_sizes.append(len(o.serialize(prev_opt_number)))\n            prev_opt_number = o.number\n\n        sizes = {\n            'header_size': 4,\n            'token_size': len(self.token) if self.token else 0,\n            'options_size': sum(serialized_opt_sizes),\n            'marker_size': (1 if self.content else 0),\n            'payload_size': len(self.content) if self.content else 0\n        }\n\n        options_breakdown = [\n            '- %d for %s' % (size, opt) for size, opt in zip(serialized_opt_sizes, self.options)\n        ]\n        return textwrap.dedent('''\\\n            {header}\n            - header:         {header_size:>5}\n            - token:          {token_size:>5}\n            - options:        {options_size:>5}\n            {options_breakdown}\n            - payload marker: {marker_size:>5}\n            - payload:        {payload_size:>5}\n                              -----\n            TOTAL             {packet_size:>5}''').format(\n                header=header,\n                packet_size=sum(sizes.values()),\n                options_breakdown=textwrap.indent('\\n'.join(options_breakdown), prefix='  '),\n                **sizes)\n\n    @staticmethod\n    def parse(self, transport=Transport.UDP):\n        bytestream = self\n        if not isinstance(bytestream, collections.abc.Iterator):\n            bytestream = iter(bytestream)\n\n        try:\n            if transport == Transport.UDP:\n                header = _parse_udp_header(bytestream)\n            elif transport == Transport.TCP:\n                header, length = _parse_tcp_header(bytestream)\n                bytestream = itertools.islice(bytestream, length)\n            else:\n                raise ValueError(\"Invalid transport: %r\" % (transport,))\n        except StopIteration:\n            raise ValueError(\"invalid CoAP message: %s\" % (hexlify(self),))\n\n        if header.token_length > 8:\n            raise ValueError(\"invalid CoAP token length: %d, expected <= 8\" % header.token_length)\n\n        token = bytes([next(bytestream) for i in range(header.token_length)])\n\n        options = []\n        content = b''\n\n        while True:\n            try:\n                byte = next(bytestream)\n            except StopIteration:\n                break\n            if byte == 0xFF:\n                content = bytes(bytestream)\n                if not content:\n                    raise ValueError('payload marker at end of packet is invalid')\n                break\n            else:\n                bytestream = itertools.chain((byte,), bytestream)\n                opt = Option.parse(bytestream, options[-1].number if options else 0)\n                options.append(opt)\n\n        pkt = Packet(header.type, header.code, header.id, token, options, content, header.version)\n        if transport == Transport.UDP:\n            # TODO: add log for TCP\n            logging.debug('%s', pkt._size_breakdown('received'))\n        return pkt\n\n    def fill_placeholders(self):\n        if self.msg_id is ANY:\n            self.msg_id = next(_ID_GENERATOR)\n        if self.token is ANY:\n            self.token = next(_TOKEN_GENERATOR)\n        if self.options is ANY:\n            self.options = []\n        if self.content is ANY:\n            self.content = b''\n\n        return self\n\n    def _serialize_tcp_header(self, options_length, content_length):\n        token_length = len(self.token)\n        if token_length > 8:\n            raise ValueError(\"invalid CoAP token length: %d, expected <= 8\" % token_length)\n        length = options_length + content_length\n\n        if length < 13:\n            return struct.pack('BB', (length << 4) | token_length, self.code.as_byte())\n        elif length < 269:\n            return struct.pack('BBB', (13 << 4) | token_length, length - 13, self.code.as_byte())\n        elif length < 65805:\n            return struct.pack('!BHB', (14 << 4) | token_length, length - 269, self.code.as_byte())\n        else:\n            return struct.pack('!BIB', (15 << 4) | token_length, length - 65805, self.code.as_byte())\n\n    def _serialize_udp_header(self):\n        return struct.pack('!BBH',\n                        (self.version << 6) | (self.type.value << 4) | (len(self.token) & 0xF),\n                        self.code.as_byte(),\n                        self.msg_id)\n\n    def serialize(self, transport=Transport.UDP):\n        if any(x is ANY for x in (self.msg_id, self.token, self.options, self.content)):\n            raise ValueError('cannot serialize CoAP packet: placeholder values present')\n\n        content = b'\\xFF' + self.content if self.content else b''\n\n        prev_opt_number = 0\n        serialized_opts = []\n        for o in self.options:\n            serialized_opts.append(o.serialize(prev_opt_number))\n            prev_opt_number = o.number\n\n        if transport == Transport.UDP:\n            logging.debug('%s', self._size_breakdown('sent'))\n            data = self._serialize_udp_header()\n        elif transport == Transport.TCP:\n            # TODO: add log for TCP\n            data = self._serialize_tcp_header(len(b''.join(serialized_opts)), len(content))\n        else:\n            raise ValueError(\"Invalid transport: %r\" % (transport,))\n\n        return (data + self.token + b''.join(serialized_opts) + content)\n\n    def get_options(self, type):\n        return [o for o in self.options if o.number == type.number]\n\n    def get_uri_path(self):\n        path = []\n        for opt in self.options:\n            if opt.matches(Option.URI_PATH):\n                path.append(opt.content.decode('ascii'))\n        return '/' + '/'.join(path)\n\n    def get_location_path(self):\n        path = []\n        for opt in self.options:\n            if opt.matches(Option.LOCATION_PATH):\n                path.append(opt.content.decode('ascii'))\n        if path:\n            return '/' + '/'.join(path)\n\n    def get_content_format(self):\n        opts = self.get_options(Option.CONTENT_FORMAT)\n        if len(opts) == 0:\n            return None\n        elif len(opts) != 1:\n            raise ValueError('%d Content-Format options found' % (len(opts),))\n        return opts[0].content_to_int()\n\n    def get_full_uri(self):\n        path = []\n        query = []\n\n        for opt in self.options:\n            if opt.matches(Option.URI_PATH):\n                path.append(opt.content.decode('ascii'))\n            elif opt.matches(Option.URI_QUERY):\n                query.append(opt.content.decode('ascii'))\n\n        return (('/' + '/'.join(path) if path else '')\n                + (('?' + '&'.join(query)) if query else ''))\n\n    def __str__(self):\n        if self.token is ANY:\n            token_str = str(self.token)\n        else:\n            token_str = hexlify_nonprintable(self.token) + ' (length: %d)' % (len(self.token),)\n\n        if self.options is ANY:\n            options_str = str(self.options)\n        else:\n            options_str = '\\n    ' + '\\n    '.join(str(o) for o in self.options)\n\n        return (\n            'version: %s\\n'\n            'type: %s\\n'\n            'code: %s\\n'\n            'msg_id: %s\\n'\n            'token: %s\\n'\n            'options:%s\\n'\n            'content: %s\\n' % (\n                self.version,\n                self.type,\n                self.code,\n                str(self.msg_id),\n                token_str,\n                options_str,\n                ('%d bytes' % len(self.content)) if self.content is not ANY else str(self.content)\n            ))\n\n    def __eq__(self, rhs: 'Packet'):\n        return ((type(self), self.version, self.type, self.code, self.msg_id,\n                 self.token, self.options, self.content)\n                == (type(rhs), rhs.version, rhs.type, rhs.code, rhs.msg_id,\n                    rhs.token, rhs.options, rhs.content))\n"
  },
  {
    "path": "tools/test-framework-tools/tools/framework_tools/lwm2m/coap/server.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nimport contextlib\nimport enum\nimport errno\nimport socket\nimport time\nfrom typing import Tuple, Optional\n\nfrom .packet import Packet\nfrom .transport import Transport\n\n\nclass SecurityMode(enum.Enum):\n    PreSharedKey = 0\n    RawPublicKey = 1\n    Certificate = 2\n    NoSec = 3\n    CertificateWithEst = 4\n\n    def __str__(self):\n        if self == SecurityMode.PreSharedKey:\n            return 'psk'\n        elif self == SecurityMode.RawPublicKey:\n            return 'rpk'\n        elif self == SecurityMode.Certificate:\n            return 'cert'\n        elif self == SecurityMode.CertificateWithEst:\n            return 'est'\n        else:\n            return 'nosec'\n\n\ndef _calculate_deadline(timeout_s=-1, deadline=None):\n    if deadline is None and timeout_s is not None and timeout_s >= 0:\n        return time.time() + timeout_s\n    else:\n        return deadline\n\n\n@contextlib.contextmanager\ndef _override_timeout(sock, *args, **kwargs):\n    deadline = _calculate_deadline(*args, **kwargs)\n    skip_override = sock is None or deadline is None\n\n    if skip_override:\n        yield\n    else:\n        orig_timeout_s = sock.gettimeout()\n        try:\n            sock.settimeout(max(deadline - time.time(), 0))\n            yield\n        finally:\n            try:\n                sock.settimeout(orig_timeout_s)\n            except OSError as e:\n                if e.errno == errno.EBADF:\n                    # sock has been closed during yield, ignore\n                    pass\n                else:\n                    raise\n\n\ndef _disconnect_socket(old_sock, family):\n    \"\"\"\n    Attempts to \"disconnect\" an UDP socket, making it accept packets from\n    all remote addresses again. POSIX says that:\n\n        If address is a null address for the protocol, the socket's peer\n        address shall be reset.\n\n    So, in theory, connect(('', 0)) should be enough. On FreeBSD 11 though,\n    the peer address is reset by passing *an invalid address* instead\n    (see https://www.freebsd.org/cgi/man.cgi?connect), and apparently\n    0.0.0.0:0 is a perfectly valid one that happens to fail with\n    EADDRNOTAVAIL. And even though Linux supports resetting peer address\n    by connect() to an address with AF_UNSPEC family, Python does not offer\n    an API that allows it: address family always matches the one set on\n    the socket.\n\n    As a workaround, we create a new socket and bind it to the same port.\n    Fun thing is, we can't really close the original socket and then create\n    a new one: if the socket is bound to an ephemeral port, that would\n    create a window of opportunity for the OS to assign the port to\n    *another* socket. This was exactly the case we observed on OSX, which\n    apparently prefers reusing recently released ephemeral ports.\n\n    To avoid that, we try something more complex:\n    1. Create a new socket,\n    2. Enable SO_REUSEADDR/SO_REUSEPORT on both old and new sockets,\n    3. Bind new socket to the same local address as the old one,\n    4. Close the old socket,\n    5. Set SO_REUSEADDR/SO_REUSEPORT values on the new socket to the same\n       ones as previously used by the old socket.\n    \"\"\"\n    new_sock = socket.socket(family, socket.SOCK_DGRAM, 0)\n\n    orig_reuse_addr = old_sock.getsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR)\n    orig_reuse_port = old_sock.getsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT)\n\n    orig_ipv6only = None\n    if family == socket.AF_INET6 and hasattr(socket, 'IPV6_V6ONLY'):\n        orig_ipv6only = old_sock.getsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY)\n\n    # temporarily set REUSEADDR and REUSEPORT to allow new socket to bind\n    # to the same port\n    def set_sock_reuse(sock, reuse_addr, reuse_port):\n        sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, reuse_addr)\n        sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, reuse_port)\n\n    set_sock_reuse(new_sock, 1, 1)\n    set_sock_reuse(old_sock, 1, 1)\n\n    if orig_ipv6only is not None:\n        new_sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, orig_ipv6only)\n\n    new_sock.bind(old_sock.getsockname())\n    old_sock.close()\n\n    # restore original state of REUSEADDR/REUSEPORT\n    set_sock_reuse(new_sock, orig_reuse_addr, orig_reuse_port)\n    return new_sock\n\n\nclass Server(object):\n    def __init__(self, listen_port=0, use_ipv6=False, reuse_port=False, transport=Transport.UDP):\n        self._prev_remote_endpoint = None\n        self.socket_timeout = None\n        self.socket = None\n        self.server_socket = None\n        self.family = socket.AF_INET6 if use_ipv6 else socket.AF_INET\n        self.ipv6_only = (str(use_ipv6).lower() == 'only')\n        self.transport = transport\n        self.reuse_port = reuse_port\n        self.accepted_connection = False\n        self._filtered_messages = []\n\n        self.reset(listen_port)\n\n    def listen(self, *args, **kwargs):\n        deadline = _calculate_deadline(*args, **kwargs)\n\n        if self.transport == Transport.UDP:\n            assert self.get_remote_addr() is None\n            with _override_timeout(self.socket, deadline=deadline):\n                raw_pkt, remote_addr_port = self.socket.recvfrom(65536)\n\n            self.connect_to_client(remote_addr_port)\n            self._filtered_messages.append(raw_pkt)\n        elif self.transport == Transport.TCP:\n            self.socket.listen()\n            with _override_timeout(self.socket, deadline=deadline):\n                client_socket, _ = self.socket.accept()\n                if self.server_socket is not None:\n                    self.server_socket.close()\n                self.server_socket = self.socket\n                self.socket = client_socket\n        else:\n            raise ValueError(\"Invalid transport: %r\" % (self.transport,))\n\n    def connect_to_client(self, remote_addr: Tuple[str, int]) -> None:\n        \"\"\"\n        This may be used e.g. for 1.0-style Server-Initiated Bootstrap over NoSec, when there is\n        absolutely no initial traffic from the client.\n        \"\"\"\n        self.socket.connect(remote_addr)\n        self.accepted_connection = True\n\n    @property\n    def _raw_udp_socket(self) -> None:\n        return self.socket\n\n    @_raw_udp_socket.setter\n    def _raw_udp_socket(self, value) -> None:\n        self.socket = value\n\n    def _fake_close(self) -> None:\n        \"\"\"\n        Force the OS to send Port Unreachable ICMP responses whenever the client\n        attempts to send a message, without actually closing the socket. This\n        prevents other threads from reusing the same ephemeral port.\n\n        We do this by connecting the socket to a port that is supposed to be\n        unused: UDP/1, which is assigned to TCP Port Service Multiplexer by IANA\n        (https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.txt)\n        or UDP/2, which is supposedly used by some \"compressnet Management\n        Utility\".\n        \"\"\"\n        assert self._prev_remote_endpoint is None\n\n        try:\n            self._prev_remote_endpoint = self._raw_udp_socket.getpeername()\n        except OSError:\n            pass\n\n        self._raw_udp_socket.connect(('', 1))\n\n    def _fake_unclose(self) -> None:\n        if self._prev_remote_endpoint:\n            self._raw_udp_socket.connect(self._prev_remote_endpoint)\n            self._prev_remote_endpoint = None\n        else:\n            self._raw_udp_socket = _disconnect_socket(self._raw_udp_socket, self.family)\n\n    def _flush_recv_queue(self) -> None:\n        self._filtered_messages.clear()\n        with _override_timeout(self._raw_udp_socket, 0):\n            try:\n                while True:\n                    self._raw_udp_socket.recv(4096)\n            except:\n                pass\n\n    @contextlib.contextmanager\n    def fake_close(self):\n        try:\n            self._fake_close()\n            # Sometimes there may be a packet that managed to arrive before socket\n            # went into \"fake offline\" mode. We certainly don't expect it after\n            # restoring the socket.\n            self._flush_recv_queue()\n            yield\n        finally:\n            self._fake_unclose()\n\n    def close(self) -> None:\n        if self.socket:\n            self.socket.close()\n            self.socket = None\n            self._filtered_messages.clear()\n\n    def reset(self, listen_port=None) -> None:\n        if listen_port is None:\n            listen_port = self.get_listen_port() if self.socket else 0\n\n        self.close()\n        if self.server_socket is not None:\n            self.socket = self.server_socket\n        else:\n            self.socket = socket.socket(self.family,\n                                        socket.SOCK_STREAM if self.transport == Transport.TCP else socket.SOCK_DGRAM)\n            self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT,\n                                   1 if self.reuse_port else 0)\n            self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR,\n                                   1 if self.reuse_port else 0)\n            if self.family == socket.AF_INET6 and hasattr(socket, 'IPV6_V6ONLY'):\n                self.socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY,\n                                       1 if self.ipv6_only else 0)\n            self.socket.bind(('', listen_port))\n        self.accepted_connection = False\n\n    def send(self, coap_packet: Packet) -> None:\n        self.socket.send(coap_packet.serialize(transport=self.transport))\n\n    def recv_raw(self, timeout_s=-1, deadline=None, peek=False, bufsize=65536):\n        deadline = _calculate_deadline(timeout_s, deadline)\n\n        # NOTE: get_remote_addr() can sometimes return None, if someone\n        # decided to \"unconnect\" the socket from a certain client. It is\n        # only done for testing connection_id.\n        if not self.get_remote_addr() and not self.accepted_connection:\n            self.listen(deadline=deadline)\n\n        self.accepted_connection = True\n\n        if len(self._filtered_messages) > 0:\n            if peek:\n                return self._filtered_messages[0]\n            else:\n                return self._filtered_messages.pop(0)\n\n        with _override_timeout(self.socket, deadline=deadline):\n            raw_pkt = self.socket.recv(bufsize)\n\n        if peek:\n            self._filtered_messages.append(raw_pkt)\n\n        return raw_pkt\n\n    def recv(self, timeout_s: float = -1, deadline=None, filter=None, peek=False) -> Packet:\n        deadline = _calculate_deadline(timeout_s, deadline)\n\n        if filter is None:\n            filter = lambda _: True\n\n        filtered_messages = []\n        while True:\n            try:\n                if self.transport == Transport.TCP:\n                    raw_pkt = b''\n\n                    def pkt_iterator():\n                        nonlocal raw_pkt\n                        while True:\n                            data = self.recv_raw(deadline=deadline, bufsize=1)\n                            if len(data) == 0:\n                                break\n                            raw_pkt += data\n                            for b in data:\n                                yield b\n\n                    pkt_source = pkt_iterator()\n                else:\n                    raw_pkt = self.recv_raw(deadline=deadline)\n                    pkt_source = raw_pkt\n                pkt = Packet.parse(pkt_source, transport=self.transport)\n            except BlockingIOError:\n                raise socket.timeout('timed out')\n            filter_result = filter(pkt)\n            if peek or not filter_result:\n                filtered_messages.append(raw_pkt)\n            if filter_result:\n                break\n\n        self._filtered_messages += filtered_messages\n        return pkt\n\n    def set_timeout(self, timeout_s: float) -> None:\n        self.socket_timeout = timeout_s\n        if self.socket:\n            self.socket.settimeout(timeout_s)\n\n    def get_timeout(self) -> Optional[float]:\n        if self.socket:\n            return self.socket.gettimeout()\n        return self.socket_timeout\n\n    def get_listen_port(self) -> int:\n        return self.socket.getsockname()[1]\n\n    def get_local_addr(self) -> Optional[Tuple[str, int]]:\n        return self.socket.getsockname()\n\n    def get_remote_addr(self) -> Optional[Tuple[str, int]]:\n        if not self.socket:\n            return None\n\n        try:\n            return self.socket.getpeername()\n        except Exception:\n            return None\n\n    def security_mode(self):\n        return 'nosec'\n\n    def get_transport(self):\n        return self.transport\n\n\nclass TlsServer(Server):\n    def __init__(self, psk_identity=None, psk_key=None, ca_path=None, ca_file=None, crt_file=None,\n                 key_file=None, listen_port=0, debug=False, use_ipv6=False, reuse_port=False,\n                 connection_id='', ciphersuites=None, transport=Transport.TCP):\n        use_psk = (psk_identity and psk_key)\n        use_certs = any((ca_path, ca_file, crt_file, key_file))\n        if use_psk and use_certs:\n            raise ValueError(\"Cannot use PSK and Certificates at the same time\")\n\n        try:\n            from pymbedtls import PskSecurity, CertSecurity, Context\n        except ImportError:\n            raise ImportError('could not import pymbedtls! run '\n                              '`make pymbedlts` target or '\n                              '`python -m pip install tools/test-framework-tools/pymbedtls`')\n\n        if use_psk:\n            security = PskSecurity(psk_key, psk_identity)\n        elif use_certs:\n            security = CertSecurity(ca_path, ca_file, crt_file, key_file)\n        else:\n            raise ValueError(\"Neither PSK nor Certificates were configured for use with DTLS\")\n\n        if ciphersuites is not None:\n            security.set_ciphersuites(ciphersuites)\n\n        self._pymbedtls_context = Context(security, debug, connection_id)\n        self._security_mode = security.name()\n\n        super().__init__(listen_port, use_ipv6, reuse_port=reuse_port, transport=transport)\n\n    def connect_to_client(self, remote_addr: Tuple[str, int]) -> None:\n        raise NotImplementedError('connect_to_client() not supported for DTLS servers')\n\n    @property\n    def _raw_udp_socket(self) -> None:\n        return self.socket.py_socket\n\n    @_raw_udp_socket.setter\n    def _raw_udp_socket(self, value) -> None:\n        self.socket.py_socket = value\n\n    def reset(self, listen_port=None) -> None:\n        from pymbedtls import ServerSocket\n        super().reset(listen_port)\n        if not isinstance(self.socket, ServerSocket):\n            self.socket = ServerSocket(self._pymbedtls_context, self.socket)\n\n    def listen(self, *args, **kwargs) -> None:\n        deadline = _calculate_deadline(*args, **kwargs)\n\n        from pymbedtls import ServerSocket\n        assert isinstance(self.socket, ServerSocket)\n\n        if self.transport == Transport.TCP:\n            self.socket.py_socket.listen()\n\n        with _override_timeout(self.socket, deadline=deadline):\n            client_socket = self.socket.accept()\n            if self.socket_timeout is not None:\n                client_socket.settimeout(self.socket_timeout)\n\n        if self.transport == Transport.UDP:\n            self.socket.close()\n            self.socket = client_socket\n        elif self.transport == Transport.TCP:\n            if self.server_socket is not None:\n                self.server_socket.close()\n            self.server_socket = self.socket\n            self.socket = client_socket\n        else:\n            raise ValueError(\"Invalid transport: %r\" % (self.transport,))\n\n    def security_mode(self):\n        # Either 'psk' or 'cert'.\n        return self._security_mode\n\n\nclass DtlsServer(TlsServer):\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs, transport=Transport.UDP)\n"
  },
  {
    "path": "tools/test-framework-tools/tools/framework_tools/lwm2m/coap/transport.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nimport enum\n\nclass Transport(enum.Enum):\n    TCP = 0\n    UDP = 1\n\n    def __str__(self):\n        if self == Transport.TCP:\n            return 'tcp'\n        else:\n            return 'udp'\n"
  },
  {
    "path": "tools/test-framework-tools/tools/framework_tools/lwm2m/coap/type.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\n\nclass Type(object):\n    @staticmethod\n    def powercmd_parse(text):\n        from powercmd.utils import match_instance\n\n        return match_instance(Type, text)\n\n    @staticmethod\n    def powercmd_complete(text):\n        from powercmd.utils import get_available_instance_names\n        from powercmd.match_string import match_string\n\n        possible = get_available_instance_names(Type)\n        return match_string(text, possible)\n\n    def __init__(self, value):\n        if value not in range(4):\n            raise ValueError(\"invalid CoAP packet type: %d\" % value)\n\n        self.value = value\n\n    def __str__(self):\n        return {\n            0: 'CONFIRMABLE',\n            1: 'NON_CONFIRMABLE',\n            2: 'ACKNOWLEDGEMENT',\n            3: 'RESET'\n        }[self.value]\n\n    def __repr__(self):\n        return 'coap.Type.%s' % str(self)\n\n    def __eq__(self, other):\n        return (type(self) is type(other)\n                and self.value == other.value)\n\n\nType.CONFIRMABLE = Type(0)\nType.NON_CONFIRMABLE = Type(1)\nType.ACKNOWLEDGEMENT = Type(2)\nType.RESET = Type(3)\n"
  },
  {
    "path": "tools/test-framework-tools/tools/framework_tools/lwm2m/coap/utils.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nimport string\n\n\ndef hexlify(s):\n    return ''.join('\\\\x%02x' % c for c in s)\n\n\ndef hexlify_nonprintable(s):\n    return ''.join(chr(c) if chr(c) in string.printable else ('\\\\x%02x' % c) for c in s)\n"
  },
  {
    "path": "tools/test-framework-tools/tools/framework_tools/lwm2m/messages.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nimport copy\nimport functools\nimport sys\nfrom typing import List, Optional, T\n \nfrom . import coap\nfrom .coap.packet import ANY\nfrom .coap.utils import hexlify_nonprintable, hexlify\nfrom .path import CoapPath, Lwm2mPath, Lwm2mNonemptyPath, Lwm2mObjectPath, Lwm2mResourcePath\nfrom .senml_cbor import *\nfrom .tlv import TLV\n\nclass EscapedBytes:\n    \"\"\"\n    A pseudo-type that allows parsing a hex-escaped string into a standard\n    bytes object when used as an argument type hint for the powercmd command\n    handler.\n\n    Example:\n        '\\x61\\x62\\x63' - parsed as b'abc'\n    \"\"\"\n\n    @staticmethod\n    def powercmd_parse(text):\n        import ast\n        result = ast.literal_eval('b\"%s\"' % (text,))\n        return result\n\n\ndef concat_if_not_any(*lists: List[T]):\n    if all(list is ANY for list in lists):\n        return ANY\n\n    return sum((([] if list is ANY else list) for list in lists), [])\n\n\nclass Lwm2mMsg(coap.Packet):\n    \"\"\"\n    Base class of all LWM2M messages.\n    \"\"\"\n\n    @classmethod\n    def from_packet(cls, pkt: coap.Packet):\n        if not cls._pkt_matches(pkt):\n            raise TypeError('packet does not match %s' % (cls.__name__,))\n\n        msg = copy.copy(pkt)\n        # It's an awful hack that may explode if any Lwm2mMsg subclass\n        # introduces any fields not present in the coap.Packet class.\n        # Since all Lwm2mMsg subclasses are meant to be thin wrappers\n        # facilitating message creation/recognition, it should never be\n        # a problem.\n        msg.__class__ = cls\n        return msg\n\n    @staticmethod\n    def _pkt_matches(_pkt: coap.Packet):\n        return True\n\n    def summary(self):\n        def block_summary(pkt):\n            blk1 = pkt.get_options(coap.Option.BLOCK1)\n            blk2 = pkt.get_options(coap.Option.BLOCK2)\n            return ', '.join(['']\n                             + ([repr(blk1[0])] if blk1 else [])\n                             + ([repr(blk2[0])] if blk2 else []))\n\n        return ('%s, %s, id=%s, token=%s%s'\n                % (self.code,\n                   self.type,\n                   self.msg_id,\n                   self.token,\n                   block_summary(self)))\n\n    def content_summary(self):\n        if self.content is ANY:\n            return 'ANY'\n        else:\n            return shorten(hexlify_nonprintable(self.content))\n\n    @staticmethod\n    def _decode_text_content(content):\n        return 'ascii-ish:\\n' + hexlify_nonprintable(content) + '\\n'\n\n    @staticmethod\n    def _decode_binary_content(content):\n        return 'binary:\\n' + hexlify(content) + '\\n'\n\n    @staticmethod\n    def _decode_tlv_content(content):\n        try:\n            return str(TLV.parse(content))\n        except Exception as exc:\n            return ('(malformed TLV: %s)\\n' % (exc,)\n                    + Lwm2mMsg._decode_binary_content(content))\n\n    @staticmethod\n    def _decode_json_content(content):\n        import json\n        import pprint\n        return str(pprint.pformat(json.loads(content)))\n\n    @staticmethod\n    def _decode_cbor_content(content):\n        try:\n            return str(CBOR.parse(content))\n        except Exception as exc:\n            return ('(malformed CBOR: %s)\\n' % (exc,)\n                    + Lwm2mMsg._decode_binary_content(content))\n\n\n    def _decode_content(self):\n        if self.content is ANY:\n            return ''\n\n        decoders = {\n            coap.ContentFormat.TEXT_PLAIN: Lwm2mMsg._decode_text_content,\n            coap.ContentFormat.APPLICATION_LINK: Lwm2mMsg._decode_text_content,\n            coap.ContentFormat.APPLICATION_LWM2M_TLV: Lwm2mMsg._decode_tlv_content,\n            coap.ContentFormat.APPLICATION_OCTET_STREAM: Lwm2mMsg._decode_binary_content,\n            coap.ContentFormat.APPLICATION_LWM2M_JSON: Lwm2mMsg._decode_json_content,\n            coap.ContentFormat.APPLICATION_LWM2M_SENML_JSON: Lwm2mMsg._decode_json_content,\n            coap.ContentFormat.APPLICATION_LWM2M_SENML_CBOR: Lwm2mMsg._decode_cbor_content,\n        }\n\n        desired_decoders = set()\n\n        for opt in self.get_options(coap.Option.CONTENT_FORMAT):\n            decoder = decoders.get(opt.content_to_int(),\n                                   Lwm2mMsg._decode_binary_content)\n            desired_decoders.add(decoder)\n\n        if not desired_decoders:\n            desired_decoders.add(decoders[coap.ContentFormat.TEXT_PLAIN])\n\n        decoded_content = ''\n        for decoder in desired_decoders:\n            decoded_content += decoder(self.content) + '\\n'\n\n        return decoded_content\n\n    def details(self):\n        return str(self)\n\n    def __str__(self):\n        return '%s\\n\\n%s\\n%s' % (self.summary(),\n                                 super().__str__(),\n                                 self._decode_content())\n\n\nclass Lwm2mResponse(Lwm2mMsg):\n    \"\"\"\n    Base class for all LWM2M responses.\n    \"\"\"\n\n    @staticmethod\n    def _pkt_matches(_pkt: coap.Packet):\n        return False\n\n    @classmethod\n    def matching(cls, request):\n        if issubclass(cls, Lwm2mEmpty):\n            return functools.partial(cls, msg_id=request.msg_id)\n        else:\n            return functools.partial(cls,\n                                     msg_id=request.msg_id,\n                                     token=request.token)\n\n\ndef is_lwm2m_nonempty_path(path):\n    try:\n        return Lwm2mNonemptyPath(path) is not None\n    except ValueError:\n        return False\n\n\ndef is_lwm2m_path(path):\n    try:\n        return Lwm2mPath(path) is not None\n    except ValueError:\n        return False\n\n\ndef is_link_format(pkt):\n    fmt = pkt.get_options(coap.Option.CONTENT_FORMAT)\n    return (fmt == [coap.Option.CONTENT_FORMAT.APPLICATION_LINK]\n            or (fmt == [] and pkt.content == b''))\n\n\ndef shorten(text):\n    if len(text) > 30:\n        return text[:27] + '...'\n    return text\n\n\nclass Lwm2mRequestBootstrap(Lwm2mMsg):\n    @staticmethod\n    def _pkt_matches(pkt: coap.Packet):\n        \"\"\"Checks if the PKT is a LWM2M Request Bootstrap message.\"\"\"\n        return (pkt.type in (None, coap.Type.CONFIRMABLE)\n                and pkt.code == coap.Code.REQ_POST\n                and '/bs?ep=' in pkt.get_full_uri())\n\n    def __init__(self,\n                 endpoint_name: str,\n                 preferred_content_format: int = None,\n                 msg_id: int = ANY,\n                 token: EscapedBytes = ANY,\n                 uri_path: str = '',\n                 uri_query: List[str] = None,\n                 options: List[coap.Option] = ANY,\n                 content: EscapedBytes = ANY):\n        if not uri_query:\n            uri_query = []\n        uri_query = uri_query + ['ep=' + endpoint_name]\n        if preferred_content_format is not None:\n            uri_query = uri_query + ['pct=%d' % (preferred_content_format,)]\n        super().__init__(type=coap.Type.CONFIRMABLE,\n                         code=coap.Code.REQ_POST,\n                         msg_id=msg_id,\n                         token=token,\n                         options=concat_if_not_any(\n                             CoapPath(uri_path + '/bs').to_uri_options(),\n                             [coap.Option.URI_QUERY(query)\n                              for query in uri_query],\n                             options),\n                         content=content)\n\n    def summary(self):\n        return ('Request Bootstrap %s: %s' % (self.get_full_uri(),\n                                              self.content_summary()))\n\n\nclass Lwm2mBootstrapFinish(Lwm2mMsg):\n    @staticmethod\n    def _pkt_matches(pkt: coap.Packet):\n        \"\"\"Checks if the PKT is a LWM2M Bootstrap Finish message.\"\"\"\n        return (pkt.type in (None, coap.Type.CONFIRMABLE)\n                and pkt.code == coap.Code.REQ_POST\n                and pkt.get_full_uri().endswith('/bs'))\n\n    def __init__(self,\n                 msg_id: int = ANY,\n                 token: EscapedBytes = ANY,\n                 options: List[coap.Option] = ANY,\n                 content: EscapedBytes = ANY):\n        super().__init__(type=coap.Type.CONFIRMABLE,\n                         code=coap.Code.REQ_POST,\n                         msg_id=msg_id,\n                         token=token,\n                         options=concat_if_not_any(\n                             CoapPath('/bs').to_uri_options(),\n                             options),\n                         content=content)\n\n    def summary(self):\n        return ('Bootstrap Finish %s: %s' % (self.get_full_uri(),\n                                             self.content_summary()))\n\n\nclass EstCoapsSimpleEnroll(Lwm2mMsg):\n    @staticmethod\n    def _pkt_matches(pkt: coap.Packet):\n        \"\"\"Checks if the PKT is an EST-coaps Simple Enroll message.\"\"\"\n        return (pkt.type in (None, coap.Type.CONFIRMABLE)\n                and pkt.code == coap.Code.REQ_POST\n                and pkt.get_full_uri().endswith('/est/sen'))\n\n    def __init__(self,\n                 msg_id: int = ANY,\n                 token: EscapedBytes = ANY,\n                 uri_path: str = '',\n                 uri_query: List[str] = None,\n                 options: List[coap.Option] = ANY,\n                 content: EscapedBytes = ANY):\n        if not uri_query:\n            uri_query = []\n        super().__init__(type=coap.Type.CONFIRMABLE,\n                         code=coap.Code.REQ_POST,\n                         msg_id=msg_id,\n                         token=token,\n                         options=concat_if_not_any(\n                             CoapPath(uri_path + '/est/sen').to_uri_options(),\n                             [coap.Option.URI_QUERY(query)\n                              for query in uri_query],\n                             options),\n                         content=content)\n\n    def summary(self):\n        return ('EST-coaps Simple Enroll %s: %s' % (self.get_full_uri(),\n                                                    self.content_summary()))\n\n\nclass EstCoapsSimpleReenroll(Lwm2mMsg):\n    @staticmethod\n    def _pkt_matches(pkt: coap.Packet):\n        \"\"\"Checks if the PKT is an EST-coaps Simple Re-enroll message.\"\"\"\n        return (pkt.type in (None, coap.Type.CONFIRMABLE)\n                and pkt.code == coap.Code.REQ_POST\n                and pkt.get_full_uri().endswith('/est/sren'))\n\n    def __init__(self,\n                 msg_id: int = ANY,\n                 token: EscapedBytes = ANY,\n                 uri_path: str = '',\n                 uri_query: List[str] = None,\n                 options: List[coap.Option] = ANY,\n                 content: EscapedBytes = ANY):\n        if not uri_query:\n            uri_query = []\n        super().__init__(type=coap.Type.CONFIRMABLE,\n                         code=coap.Code.REQ_POST,\n                         msg_id=msg_id,\n                         token=token,\n                         options=concat_if_not_any(\n                             CoapPath(uri_path + '/est/sren').to_uri_options(),\n                             [coap.Option.URI_QUERY(query)\n                              for query in uri_query],\n                             options),\n                         content=content)\n\n    def summary(self):\n        return ('EST-coaps Simple Re-enroll %s: %s' % (self.get_full_uri(),\n                                                       self.content_summary()))\n\n\nclass EstCoapsCaCerts(Lwm2mMsg):\n    @staticmethod\n    def _pkt_matches(pkt: coap.Packet):\n        \"\"\"Checks if the PKT is an EST-coaps CA Certs message.\"\"\"\n        return (pkt.type in (None, coap.Type.CONFIRMABLE)\n                and pkt.code == coap.Code.REQ_GET\n                and pkt.get_full_uri().endswith('/est/crts'))\n\n    def __init__(self,\n                 msg_id: int = ANY,\n                 token: EscapedBytes = ANY,\n                 uri_path: str = '',\n                 uri_query: List[str] = None,\n                 options: List[coap.Option] = ANY,\n                 content: EscapedBytes = ANY):\n        if not uri_query:\n            uri_query = []\n        super().__init__(type=coap.Type.CONFIRMABLE,\n                         code=coap.Code.REQ_GET,\n                         msg_id=msg_id,\n                         token=token,\n                         options=concat_if_not_any(\n                             CoapPath(uri_path + '/est/crts').to_uri_options(),\n                             [coap.Option.URI_QUERY(query)\n                              for query in uri_query],\n                             options),\n                         content=content)\n\n    def summary(self):\n        return ('EST-coaps CA Certs %s: %s' % (self.get_full_uri(),\n                                               self.content_summary()))\n\n\ndef _split_string_path(path: str,\n                       query: List[str] = None):\n    \"\"\"\n    Splits a CoAP PATH given as string into a path component and a list of\n    query strings (\"foo=bar\").\n    Returns (CoapPath, List[str]) tuple with a parsed CoapPath and a list of\n    query strings from the PATH concatenated with QUERY contents (if any).\n    \"\"\"\n    path_query = []\n\n    if isinstance(path, str):\n        if query is None:\n            if '?' in path:\n                path, query_string = path.split('?', maxsplit=1)\n                path_query = query_string.split('&')\n\n        path = CoapPath(path)\n\n    return path, path_query + (query or [])\n\n\nclass Lwm2mRegister(Lwm2mMsg):\n    @staticmethod\n    def _pkt_matches(pkt: coap.Packet):\n        \"\"\"Checks if the PKT is a LWM2M Register message.\"\"\"\n        return (pkt.type in (None, coap.Type.CONFIRMABLE)\n                and pkt.code == coap.Code.REQ_POST\n                and is_link_format(pkt)\n                and pkt.get_uri_path().endswith('/rd'))\n\n    def __init__(self,\n                 path: str or CoapPath,\n                 query: List[str] = None,\n                 msg_id: int = ANY,\n                 token: EscapedBytes = ANY,\n                 options: List[coap.Option] = ANY,\n                 content: EscapedBytes = ANY):\n        path, query = _split_string_path(path, query)\n\n        super().__init__(type=coap.Type.CONFIRMABLE,\n                         code=coap.Code.REQ_POST,\n                         msg_id=msg_id,\n                         token=token,\n                         options=concat_if_not_any(\n                             path.to_uri_options(),\n                             [coap.Option.URI_QUERY(q) for q in query],\n                             [coap.Option.CONTENT_FORMAT.APPLICATION_LINK],\n                             options),\n                         content=content)\n\n    def summary(self):\n        return ('Register %s: %s' % (self.get_full_uri(),\n                                     self.content_summary()))\n\n\nclass Lwm2mUpdate(Lwm2mMsg):\n    @staticmethod\n    def _pkt_matches(pkt: coap.Packet):\n        \"\"\"Checks if the PKT is a LWM2M Update message.\"\"\"\n        # Update is very similar to Execute\n        # assumption: Update will never be called on a path that resembles\n        # /OID or /OID/IID or /OID/IID/RID\n        return (pkt.type in (None, coap.Type.CONFIRMABLE)\n                and pkt.code == coap.Code.REQ_POST\n                and '/rd/' in pkt.get_uri_path()\n                and (is_link_format(pkt)\n                     or not is_lwm2m_nonempty_path(pkt.get_full_uri())))\n\n    def __init__(self,\n                 path: str or CoapPath,\n                 query: List[str] = None,\n                 msg_id: int = ANY,\n                 token: EscapedBytes = ANY,\n                 options: List[coap.Option] = ANY,\n                 content: EscapedBytes = ANY):\n        path, query = _split_string_path(path, query)\n\n        super().__init__(type=coap.Type.CONFIRMABLE,\n                         code=coap.Code.REQ_POST,\n                         msg_id=msg_id,\n                         token=token,\n                         options=concat_if_not_any(\n                             path.to_uri_options(),\n                             [coap.Option.URI_QUERY(q) for q in query],\n                             ([coap.Option.CONTENT_FORMAT.APPLICATION_LINK]\n                              if content else []),\n                             options),\n                         content=content)\n\n    def summary(self):\n        return ('Update %s: %s' % (self.get_full_uri(),\n                                   self.content_summary()))\n\n\nclass Lwm2mDeregister(Lwm2mMsg):\n    @staticmethod\n    def _pkt_matches(pkt: coap.Packet):\n        return (pkt.type in (None, coap.Type.CONFIRMABLE)\n                and pkt.code == coap.Code.REQ_DELETE\n                and not is_lwm2m_nonempty_path(pkt.get_uri_path()))\n\n    def __init__(self,\n                 path: str or CoapPath,\n                 msg_id: int = ANY,\n                 token: EscapedBytes = ANY,\n                 options: List[coap.Option] = ANY):\n        if isinstance(path, str):\n            path = CoapPath(path)\n\n        super().__init__(type=coap.Type.CONFIRMABLE,\n                         code=coap.Code.REQ_DELETE,\n                         msg_id=msg_id,\n                         token=token,\n                         options=concat_if_not_any(\n                             path.to_uri_options(),\n                             options))\n\n    def summary(self):\n        return 'De-register ' + self.get_full_uri()\n\n\ndef add_attributes_to_query(query,\n                            lt: float = None,\n                            gt: float = None,\n                            st: float = None,\n                            pmin: int = None,\n                            pmax: int = None,\n                            epmin: int = None,\n                            epmax: int = None,\n                            hqmax: int = None):\n    if lt is not None:\n        query.append('lt=%f' % (lt,))\n    if gt is not None:\n        query.append('gt=%f' % (gt,))\n    if st is not None:\n        query.append('st=%f' % (st,))\n    if pmin is not None:\n        query.append('pmin=%d' % (pmin,))\n    if pmax is not None:\n        query.append('pmax=%d' % (pmax,))\n    if epmin is not None:\n        query.append('epmin=%d' % (epmin,))\n    if epmax is not None:\n        query.append('epmax=%d' % (epmax,))\n    if hqmax is not None:\n        query.append('hqmax=%d' % (hqmax,))\n\n    return query\n\nclass Lwm2mSend(Lwm2mMsg):\n    @staticmethod\n    def _pkt_matches(pkt: coap.Packet):\n        \"\"\"Checks if the PKT is a LWM2M Send message.\"\"\"\n        return (pkt.type in (None, coap.Type.CONFIRMABLE)\n                and pkt.code == coap.Code.REQ_POST\n                and pkt.get_uri_path().endswith('/dp'))\n\n    def __init__(self,\n                 path: str or CoapPath = '/dp',\n                 msg_id: int = ANY,\n                 token: EscapedBytes = ANY,\n                 format: coap.ContentFormatOption = coap.ContentFormat.APPLICATION_LWM2M_SENML_CBOR,\n                 options: List[coap.Option] = ANY,\n                 content: EscapedBytes = ANY):\n        if isinstance(format, int):\n            format = coap.Option.CONTENT_FORMAT(format)\n\n        path, _ = _split_string_path(path)\n        format_opt = [format] if format is not ANY else ANY\n\n        super().__init__(type=coap.Type.CONFIRMABLE,\n                         code=coap.Code.REQ_POST,\n                         msg_id=msg_id,\n                         token=token,\n                         options=concat_if_not_any(\n                             path.to_uri_options(),\n                             format_opt,\n                             options),\n                         content=content)\n\n    def summary(self):\n        return ('Send %s: %s' % (self.get_full_uri(), self.content_summary()))\n\n\nclass Lwm2mReadComposite(Lwm2mMsg):\n    @staticmethod\n    def _pkt_matches(pkt: coap.Packet):\n        return (pkt.type in (None, coap.Type.CONFIRMABLE)\n                and pkt.code == coap.Code.REQ_FETCH)\n\n    def __init__(self,\n                 paths: List[str] or List[CoapPath],\n                 accept: coap.AcceptOption = None,\n                 msg_id: int = ANY,\n                 token: EscapedBytes = ANY,\n                 options: List[coap.Option] = ANY):\n        self._requested_paths = paths\n\n        if isinstance(accept, int):\n            accept = coap.Option.ACCEPT(accept)\n\n        encoded_paths = CBOR.serialize([\n            {SenmlLabel.NAME: str(p)} for p in paths\n        ])\n\n        super().__init__(type=coap.Type.CONFIRMABLE,\n                         code=coap.Code.REQ_FETCH,\n                         msg_id=msg_id,\n                         token=token,\n                         options=concat_if_not_any(\n                             CoapPath('/').to_uri_options(),\n                             ([accept] if accept is not None else []),\n                             options,\n                             [coap.ContentFormatOption.APPLICATION_LWM2M_SENML_CBOR]),\n                         content=encoded_paths)\n\n    def summary(self):\n        text = 'Read-Composite ' + \\\n            ', '.join(str(p) for p in self._requested_paths)\n\n        accept = self.get_options(coap.Option.ACCEPT)\n        if accept:\n            accept_vals = [x.content_to_int() for x in accept]\n            text += ': accept ' + \\\n                ', '.join(map(coap.ContentFormat.to_str, accept_vals))\n\n        return text\n\n\nclass Lwm2mObserveComposite(Lwm2mReadComposite):\n    @staticmethod\n    def _pkt_matches(pkt: coap.Packet):\n        return (Lwm2mReadComposite._pkt_matches(pkt)\n                and len(pkt.get_options(coap.Option.OBSERVE)) > 0)\n\n    def __init__(self,\n                 paths: List[str] or List[CoapPath],\n                 observe: int = 0,\n                 accept: coap.AcceptOption = None,\n                 lt: float = None,\n                 gt: float = None,\n                 st: float = None,\n                 pmin: int = None,\n                 pmax: int = None,\n                 epmin: int = None,\n                 epmax: int = None,\n                 hqmax: int = None,\n                 msg_id: int = ANY,\n                 token: EscapedBytes = ANY,\n                 options: List[coap.Option] = ANY):\n        query = add_attributes_to_query([], lt, gt, st, pmin, pmax, epmin, epmax, hqmax)\n        super().__init__(paths=paths,\n                         accept=accept,\n                         msg_id=msg_id,\n                         token=token,\n                         options=concat_if_not_any(\n                             [coap.Option.OBSERVE(observe)],\n                             [coap.Option.URI_QUERY(x) for x in query],\n                             options))\n\n\nclass CoapGet(Lwm2mMsg):\n    @staticmethod\n    def _pkt_matches(pkt: coap.Packet):\n        return (pkt.type in (None, coap.Type.CONFIRMABLE)\n                and pkt.code == coap.Code.REQ_GET)\n\n    def __init__(self,\n                 path: str or CoapPath,\n                 accept: coap.AcceptOption = None,\n                 msg_id: int = ANY,\n                 token: EscapedBytes = ANY,\n                 options: List[coap.Option] = ANY):\n        if isinstance(path, str):\n            path = CoapPath(path)\n\n        if isinstance(accept, int):\n            accept = coap.Option.ACCEPT(accept)\n\n        super().__init__(type=coap.Type.CONFIRMABLE,\n                         code=coap.Code.REQ_GET,\n                         msg_id=msg_id,\n                         token=token,\n                         options=concat_if_not_any(\n                             path.to_uri_options(),\n                             ([accept] if accept is not None else []),\n                             options))\n\n    def summary(self):\n        text = 'GET ' + self.get_full_uri()\n\n        accept = self.get_options(coap.Option.ACCEPT)\n        if accept:\n            accept_vals = [x.content_to_int() for x in accept]\n            text += ': accept ' + \\\n                ', '.join(map(coap.ContentFormat.to_str, accept_vals))\n\n        return text\n\n\nclass Lwm2mBootstrapPackRequest(CoapGet):\n    @staticmethod\n    def _pkt_matches(pkt: coap.Packet):\n        \"\"\"Checks if the PKT is LWM2M Bootstrap-Pack-Request message.\"\"\"\n        return (pkt.type in (None, coap.Type.CONFIRMABLE)\n                and pkt.code == coap.Code.REQ_GET\n                and '/bspack?' in pkt.get_full_uri())\n\n    def endpoint_name_matches(self, endpoint_name: str):\n        return self.endpoint_name == endpoint_name\n\n    def __init__(self,\n                 endpoint_name: str,\n                 accept: coap.AcceptOption = None,\n                 msg_id: int = ANY,\n                 token: EscapedBytes = ANY,\n                 uri_path: str = '',\n                 uri_query: List[str] = None,\n                 options: List[coap.Option] = ANY):\n        if not uri_query:\n            uri_query = []\n        uri_query = uri_query + ['ep=' + endpoint_name]\n\n        super().__init__(path=CoapPath(uri_path + '/bspack'),\n                         accept=accept,\n                         options=concat_if_not_any(\n                             [coap.Option.URI_QUERY(query)\n                              for query in uri_query],\n                             options),\n                         msg_id=msg_id,\n                         token=token)\n\n    def summary(self):\n        text = 'Bootstrap-Pack-Request %s: %s' % (self.get_full_uri(),\n                                                  self.content_summary())\n        accept = self.get_options(coap.Option.ACCEPT)\n        if accept:\n            accept_vals = [x.content_to_int() for x in accept]\n            text += ': accept ' + \\\n                ', '.join(map(coap.ContentFormat.to_str, accept_vals))\n        return text\n\n\nclass Lwm2mRead(CoapGet):\n    @staticmethod\n    def _pkt_matches(pkt: coap.Packet):\n        return (CoapGet._pkt_matches(pkt)\n                and is_lwm2m_nonempty_path(pkt.get_uri_path())\n                and not is_link_format(pkt))\n\n    def __init__(self,\n                 path: str or Lwm2mNonemptyPath,\n                 accept: coap.AcceptOption = None,\n                 msg_id: int = ANY,\n                 token: EscapedBytes = ANY,\n                 options: List[coap.Option] = ANY):\n        if isinstance(path, str):\n            path = Lwm2mNonemptyPath(path)\n\n        super().__init__(path=path, accept=accept, msg_id=msg_id,\n                         token=token, options=options)\n\n    def summary(self):\n        text = 'Read ' + self.get_full_uri()\n\n        accept = self.get_options(coap.Option.ACCEPT)\n        if accept:\n            accept_vals = [x.content_to_int() for x in accept]\n            text += ': accept ' + \\\n                ', '.join(map(coap.ContentFormat.to_str, accept_vals))\n\n        return text\n\n\nclass Lwm2mObserve(Lwm2mRead):\n    @staticmethod\n    def _pkt_matches(pkt: coap.Packet):\n        return (Lwm2mRead._pkt_matches(pkt)\n                and len(pkt.get_options(coap.Option.OBSERVE)) > 0)\n\n    def __init__(self,\n                 path: str or Lwm2mNonemptyPath,\n                 observe: int = 0,\n                 accept: coap.AcceptOption = None,\n                 lt: float = None,\n                 gt: float = None,\n                 st: float = None,\n                 pmin: int = None,\n                 pmax: int = None,\n                 epmin: int = None,\n                 epmax: int = None,\n                 hqmax: int = None,\n                 msg_id: int = ANY,\n                 token: EscapedBytes = ANY,\n                 options: List[coap.Option] = ANY):\n        if isinstance(path, str):\n            path = Lwm2mNonemptyPath(path)\n\n        if isinstance(accept, int):\n            accept = coap.Option.ACCEPT(accept)\n\n        query = add_attributes_to_query([], lt, gt, st, pmin, pmax, epmin, epmax, hqmax)\n\n        super().__init__(path=path,\n                         accept=accept,\n                         msg_id=msg_id,\n                         token=token,\n                         options=concat_if_not_any(\n                             [coap.Option.OBSERVE(observe)],\n                             [coap.Option.URI_QUERY(x) for x in query],\n                             options))\n\n    def summary(self):\n        opt = self.get_options(coap.Option.OBSERVE)\n        if len(opt) > 1:\n            text = 'Observe %s (multiple Observe options)' % (\n                self.get_full_uri(),)\n        else:\n            opt = opt[0]\n            if opt.content_to_int() == 0:\n                text = 'Observe ' + self.get_full_uri()\n            elif opt.content_to_int() == 1:\n                text = 'Cancel Observation ' + self.get_full_uri()\n            else:\n                text = 'Observe %s (invalid Observe value: %d)' % (\n                    self.get_full_uri(), opt.content_to_int())\n\n        accept = self.get_options(coap.Option.ACCEPT)\n        if accept:\n            accept_vals = [x.content_to_int() for x in accept]\n            text += ': accept ' + \\\n                ', '.join(map(coap.ContentFormat.to_str, accept_vals))\n\n        return text\n\n\nclass Lwm2mDiscover(CoapGet):\n    @staticmethod\n    def _pkt_matches(pkt: coap.Packet):\n        return (CoapGet._pkt_matches(pkt)\n                and is_lwm2m_nonempty_path(pkt.get_uri_path())\n                and is_link_format(pkt))\n\n    def __init__(self,\n                 path: str or Lwm2mPath,\n                 depth: Optional[int] = None,\n                 msg_id: int = ANY,\n                 token: EscapedBytes = ANY,\n                 options: List[coap.Option] = ANY):\n        super().__init__(path=path,\n                         msg_id=msg_id,\n                         token=token,\n                         accept=coap.Option.ACCEPT.APPLICATION_LINK,\n                         options=concat_if_not_any(options, [coap.Option.URI_QUERY(\n                             'depth=%d' % (depth,))] if depth is not None else []))\n\n    def summary(self):\n        return 'Discover ' + self.get_full_uri()\n\n\nclass Lwm2mWrite(Lwm2mMsg):\n    @staticmethod\n    def _pkt_matches(pkt: coap.Packet):\n        return (pkt.type in (None, coap.Type.CONFIRMABLE)\n                and pkt.code in (coap.Code.REQ_PUT, coap.Code.REQ_POST)\n                and is_lwm2m_nonempty_path(pkt.get_uri_path())\n                and not is_link_format(pkt))\n\n    def __init__(self,\n                 path: str or Lwm2mNonemptyPath,\n                 content: EscapedBytes,\n                 format: coap.ContentFormatOption = coap.ContentFormat.TEXT_PLAIN,\n                 update: bool = False,\n                 msg_id: int = ANY,\n                 token: EscapedBytes = ANY,\n                 options: List[coap.Option] = ANY):\n\n        if isinstance(path, str):\n            path = Lwm2mNonemptyPath(path)\n        if isinstance(content, str):\n            content = bytes(content, 'ascii')\n\n        if isinstance(format, int):\n            format = coap.Option.CONTENT_FORMAT(format)\n\n        super().__init__(type=coap.Type.CONFIRMABLE,\n                         code=(coap.Code.REQ_POST if update else coap.Code.REQ_PUT),\n                         msg_id=msg_id,\n                         token=token,\n                         options=concat_if_not_any(\n                             path.to_uri_options(),\n                             [format] if format is not None else [],\n                             options),\n                         content=content)\n\n    def summary(self):\n        fmt_vals = [x.content_to_int()\n                    for x in self.get_options(coap.Option.CONTENT_FORMAT)]\n        fmt = ', '.join(map(coap.ContentFormat.to_str, fmt_vals))\n\n        return ('Write%s %s: %s, %d bytes'\n                % (' (update)' if self.code == coap.Code.REQ_POST else '',\n                   self.get_full_uri(), fmt, len(self.content)))\n\n\nclass Lwm2mWriteComposite(Lwm2mMsg):\n    @staticmethod\n    def _pkt_matches(pkt: coap.Packet):\n        return (pkt.type in (None, coap.Type.CONFIRMABLE)\n                and pkt.code == coap.Code.REQ_IPATCH\n                and pkt.get_uri_path() == '/'\n                and not is_link_format(pkt))\n\n    def __init__(self,\n                 content: EscapedBytes,\n                 format: coap.ContentFormatOption,\n                 msg_id: int = ANY,\n                 token: EscapedBytes = ANY,\n                 options: List[coap.Option] = ANY):\n        if isinstance(content, str):\n            content = bytes(content, 'ascii')\n\n        if isinstance(format, int):\n            format = coap.Option.CONTENT_FORMAT(format)\n\n        super().__init__(type=coap.Type.CONFIRMABLE,\n                         code=coap.Code.REQ_IPATCH,\n                         msg_id=msg_id,\n                         token=token,\n                         options=concat_if_not_any(\n                             [format],\n                             options),\n                         content=content)\n\n    def summary(self):\n        fmt_vals = [x.content_to_int()\n                    for x in self.get_options(coap.Option.CONTENT_FORMAT)]\n        fmt = ', '.join(map(coap.ContentFormat.to_str, fmt_vals))\n\n        return ('Write Composite %s: %s, %d bytes'\n                % (self.get_full_uri(), fmt, len(self.content)))\n\n\nclass Lwm2mWriteAttributes(Lwm2mMsg):\n    @staticmethod\n    def _pkt_matches(pkt: coap.Packet):\n        return (pkt.type in (None, coap.Type.CONFIRMABLE)\n                and pkt.code == coap.Code.REQ_PUT\n                and is_lwm2m_nonempty_path(pkt.get_uri_path())\n                and pkt.get_content_format() is None)\n\n    def __init__(self,\n                 path: str or Lwm2mNonemptyPath,\n                 lt: float = None,\n                 gt: float = None,\n                 st: float = None,\n                 pmin: int = None,\n                 pmax: int = None,\n                 epmin: int = None,\n                 epmax: int = None,\n                 hqmax: int = None,\n                 query: List[str] = None,\n                 msg_id: int = ANY,\n                 token: EscapedBytes = ANY,\n                 options: List[coap.Option] = ANY):\n        path, query = _split_string_path(path, query)\n\n        query = add_attributes_to_query(query, lt, gt, st, pmin, pmax, epmin, epmax, hqmax)\n\n        super().__init__(type=coap.Type.CONFIRMABLE,\n                         code=coap.Code.REQ_PUT,\n                         msg_id=msg_id,\n                         token=token,\n                         options=concat_if_not_any(\n                             path.to_uri_options(),\n                             [coap.Option.URI_QUERY(x) for x in query],\n                             options),\n                         content=b'')\n\n    def summary(self):\n        attrs = ', '.join(x.content_to_str()\n                          for x in self.get_options(coap.Option.URI_QUERY))\n        return 'Write Attributes %s: %s' % (self.get_full_uri(), attrs)\n\n\nclass Lwm2mExecute(Lwm2mMsg):\n    @staticmethod\n    def _pkt_matches(pkt: coap.Packet):\n        return (pkt.type in (None, coap.Type.CONFIRMABLE)\n                and pkt.code == coap.Code.REQ_POST\n                and is_lwm2m_nonempty_path(pkt.get_full_uri())\n                and pkt.get_content_format() is None)\n\n    def __init__(self,\n                 path: str or Lwm2mResourcePath,\n                 content: EscapedBytes = b'',\n                 msg_id: int = ANY,\n                 token: EscapedBytes = ANY,\n                 options: List[coap.Option] = ANY):\n        if isinstance(path, str):\n            path = Lwm2mResourcePath(path)\n\n        super().__init__(type=coap.Type.CONFIRMABLE,\n                         code=coap.Code.REQ_POST,\n                         msg_id=msg_id,\n                         token=token,\n                         options=concat_if_not_any(path.to_uri_options(),\n                                                   options),\n                         content=content)\n\n    def summary(self):\n        return ('Execute %s: %s' % (self.get_full_uri(),\n                                    self.content_summary()))\n\n\nclass Lwm2mCreate(Lwm2mMsg):\n    @staticmethod\n    def _pkt_matches(pkt: coap.Packet):\n        return (pkt.type in (None, coap.Type.CONFIRMABLE)\n                and pkt.code == coap.Code.REQ_POST\n                and is_lwm2m_nonempty_path(pkt.get_uri_path())\n                and pkt.get_content_format() == coap.ContentFormat.APPLICATION_LWM2M_TLV)\n\n    def __init__(self,\n                 path: str or Lwm2mObjectPath,\n                 content: EscapedBytes = b'',\n                 msg_id: int = ANY,\n                 token: EscapedBytes = ANY,\n                 options: List[coap.Option] = ANY,\n                 format: coap.ContentFormatOption = coap.Option.CONTENT_FORMAT.APPLICATION_LWM2M_TLV):\n        if isinstance(path, str):\n            path = Lwm2mObjectPath(path)\n\n        if isinstance(format, int):\n            format = coap.Option.CONTENT_FORMAT(format)\n\n        super().__init__(type=coap.Type.CONFIRMABLE,\n                         code=coap.Code.REQ_POST,\n                         msg_id=msg_id,\n                         token=token,\n                         options=concat_if_not_any(\n                             path.to_uri_options(),\n                             [format],\n                             options),\n                         content=content)\n\n    def summary(self):\n        return 'Create %s: %s' % (self.get_full_uri(), self.content_summary())\n\n\nclass Lwm2mDelete(Lwm2mMsg):\n    @staticmethod\n    def _pkt_matches(pkt: coap.Packet):\n        # TODO: this should be done by checking the packet source/target\n        # if REQ_DELETE is sent by server. it's Delete; otherwise - De-Register\n        # assumption: De-Register will never be called on a path\n        # that resembles /OID/IID\n        return (pkt.type in (None, coap.Type.CONFIRMABLE)\n                and pkt.code == coap.Code.REQ_DELETE\n                and is_lwm2m_path(pkt.get_uri_path()))\n\n    def __init__(self,\n                 path: str or Lwm2mPath,\n                 msg_id: int = ANY,\n                 token: EscapedBytes = ANY,\n                 options: List[coap.Option] = ANY):\n        if isinstance(path, str):\n            path = Lwm2mPath(path)\n        if path.resource_id is not None:\n            raise ValueError(\n                'LWM2M Resource path is not applicable to a Delete: %s' % (path,))\n\n        super().__init__(type=coap.Type.CONFIRMABLE,\n                         code=coap.Code.REQ_DELETE,\n                         msg_id=msg_id,\n                         token=token,\n                         options=concat_if_not_any(path.to_uri_options(),\n                                                   options))\n\n    def summary(self):\n        return 'Delete ' + self.get_full_uri()\n\n\n# Classes defined below are responses that should be matched to some request.\n# Therefeore, msg_id and token in the constructor are mandatory.\n\nclass Lwm2mContent(Lwm2mResponse):\n    @staticmethod\n    def _pkt_matches(pkt: coap.Packet):\n        return pkt.code == coap.Code.RES_CONTENT\n\n    def __init__(self,\n                 msg_id: int,\n                 token: EscapedBytes,\n                 content: EscapedBytes = ANY,\n                 format: coap.ContentFormatOption = ANY,\n                 type: coap.Type = coap.Type.ACKNOWLEDGEMENT,\n                 options: List[coap.Option] = ANY):\n        if isinstance(format, int):\n            format = coap.Option.CONTENT_FORMAT(format)\n\n        all_opts = [format] if format is not ANY else ANY\n        if options is not ANY:\n            all_opts = (all_opts + options) if all_opts is not ANY else options\n\n        super().__init__(type=type,\n                         code=coap.Code.RES_CONTENT,\n                         msg_id=msg_id,\n                         token=token,\n                         options=all_opts,\n                         content=content)\n\n    def make_content_summary(self):\n        format_opts = self.get_options(coap.Option.CONTENT_FORMAT)\n        if format_opts:\n            fmt = format_opts[0]\n        else:\n            fmt = coap.Option.CONTENT_FORMAT.TEXT_PLAIN\n\n        if fmt == coap.Option.CONTENT_FORMAT.TEXT_PLAIN:\n            return self.content_summary()\n        else:\n            return '(%s; %d bytes)' % (fmt.content_to_str(), len(self.content))\n\n    def summary(self):\n        return 'Content ' + self.make_content_summary()\n\n\nclass Lwm2mNotify(Lwm2mContent):\n    @staticmethod\n    def _pkt_matches(pkt: coap.Packet):\n        return (Lwm2mContent._pkt_matches(pkt)\n                and pkt.get_options(coap.Option.OBSERVE))\n\n    def __init__(self,\n                 token: EscapedBytes,\n                 content: EscapedBytes = ANY,\n                 format: coap.ContentFormatOption = ANY,\n                 confirmable: bool = False,\n                 options: List[coap.Option] = ANY):\n        if isinstance(format, int):\n            format = coap.Option.CONTENT_FORMAT(format)\n\n        super().__init__(type=coap.Type.CONFIRMABLE if confirmable else coap.Type.NON_CONFIRMABLE,\n                         msg_id=ANY,\n                         token=token,\n                         content=content,\n                         format=format,\n                         options=options)\n\n    def summary(self):\n        observe_opts = self.get_options(coap.Option.OBSERVE)\n        seq = '/'.join(str(opt.content_to_int()) for opt in observe_opts)\n        return 'Notify (%s, seq %s, token %s) %s' % (\n            str(self.type), seq, hexlify(self.token), self.make_content_summary())\n\n\nclass Lwm2mCreated(Lwm2mResponse):\n    @staticmethod\n    def _pkt_matches(pkt: coap.Packet):\n        return pkt.code == coap.Code.RES_CREATED\n\n    def __init__(self,\n                 msg_id: int,\n                 token: EscapedBytes,\n                 location: str or CoapPath = ANY,\n                 options: List[coap.Option] = ANY):\n        if isinstance(location, str):\n            location = CoapPath(location)\n\n        super().__init__(type=coap.Type.ACKNOWLEDGEMENT,\n                         code=coap.Code.RES_CREATED,\n                         msg_id=msg_id,\n                         token=token,\n                         options=concat_if_not_any(\n                             location.to_uri_options(\n                                 opt=coap.Option.LOCATION_PATH),\n                             options))\n\n    def summary(self):\n        location = self.get_location_path()\n        return 'Created ' + (location or '(no location-path)')\n\n\nclass Lwm2mDeleted(Lwm2mResponse):\n    @staticmethod\n    def _pkt_matches(pkt: coap.Packet):\n        return pkt.code == coap.Code.RES_DELETED\n\n    def __init__(self,\n                 msg_id: int,\n                 token: EscapedBytes,\n                 location: str or CoapPath = ANY,\n                 options: List[coap.Option] = ANY):\n        if location is not ANY and isinstance(location, str):\n            location = CoapPath(location)\n\n        super().__init__(type=coap.Type.ACKNOWLEDGEMENT,\n                         code=coap.Code.RES_DELETED,\n                         msg_id=msg_id,\n                         token=token,\n                         options=concat_if_not_any(\n                             location.to_uri_options(\n                                 coap.Option.LOCATION_PATH),\n                             options))\n\n    def summary(self):\n        location = self.get_location_path()\n        return 'Deleted ' + (location or '(no location-path)')\n\n\nclass Lwm2mChanged(Lwm2mResponse):\n    @staticmethod\n    def _pkt_matches(pkt: coap.Packet):\n        return pkt.code == coap.Code.RES_CHANGED\n\n    def __init__(self,\n                 msg_id: int,\n                 token: EscapedBytes,\n                 location: str or CoapPath = ANY,\n                 options: List[coap.Option] = ANY,\n                 content: EscapedBytes = ANY):\n        if location is not ANY and isinstance(location, str):\n            location = CoapPath(location)\n\n        super().__init__(type=coap.Type.ACKNOWLEDGEMENT,\n                         code=coap.Code.RES_CHANGED,\n                         msg_id=msg_id,\n                         token=token,\n                         options=concat_if_not_any(\n                             location.to_uri_options(\n                                 coap.Option.LOCATION_PATH),\n                             options),\n                         content=content)\n\n    def summary(self):\n        location = self.get_location_path()\n        return 'Changed ' + (location or '(no location path)')\n\n\nclass Lwm2mErrorResponse(Lwm2mResponse):\n    @staticmethod\n    def _pkt_matches(pkt: coap.Packet):\n        return (pkt.type in (None, coap.Type.ACKNOWLEDGEMENT)\n                and pkt.code.cls in (4, 5))\n\n    def __init__(self,\n                 code: coap.Code,\n                 msg_id: int,\n                 token: EscapedBytes,\n                 options: List[coap.Option] = ANY):\n        if code.cls not in (4, 5):\n            raise ValueError('Error responses must have code class 4 or 5')\n\n        super().__init__(type=coap.Type.ACKNOWLEDGEMENT,\n                         code=code,\n                         msg_id=msg_id,\n                         token=token,\n                         options=options)\n\n    def summary(self):\n        content_str = shorten(\n            hexlify_nonprintable(self.content)) if self.content else '(no details available)'\n        return '%s: %s' % (str(self.code), content_str)\n\n\nclass Lwm2mEmpty(Lwm2mResponse):\n    @staticmethod\n    def _pkt_matches(pkt: coap.Packet):\n        return (pkt.code == coap.Code.EMPTY\n                and pkt.token == b''\n                and pkt.options == []\n                and pkt.content == b'')\n\n    def __init__(self,\n                 type: coap.Type = coap.Type.ACKNOWLEDGEMENT,\n                 msg_id: int = ANY):\n        super().__init__(type=type,\n                         code=coap.Code.EMPTY,\n                         msg_id=msg_id,\n                         token=b'',\n                         content=b'')\n\n    def summary(self):\n        return 'Empty %s, msg_id = %d' % (str(self.type), self.msg_id)\n\n\nclass Lwm2mReset(Lwm2mEmpty):\n    @staticmethod\n    def _pkt_matches(pkt: coap.Packet):\n        return (Lwm2mEmpty._pkt_matches(pkt)\n                and pkt.type == coap.Type.RESET)\n\n    def __init__(self, msg_id: int = ANY):\n        super().__init__(msg_id=msg_id, type=coap.Type.RESET)\n\n    def summary(self):\n        return 'Reset, msg_id = %d' % (self.msg_id,)\n\n\nclass Lwm2mContinue(Lwm2mResponse):\n    @staticmethod\n    def _pkt_matches(pkt: coap.Packet):\n        return pkt.code == coap.Code.RES_CONTINUE\n\n    def __init__(self,\n                 msg_id: int,\n                 token: EscapedBytes,\n                 type: coap.Type = coap.Type.ACKNOWLEDGEMENT,\n                 options: List[coap.Option] = ANY):\n        super().__init__(type=type,\n                         code=coap.Code.RES_CONTINUE,\n                         msg_id=msg_id,\n                         token=token,\n                         options=options)\n\n    def summary(self):\n        return 'Continue, msg_id = %d, token = %s' \\\n            % (self.msg_id if self.msg_id is not None else 'None', self.token)\n\n\ndef _get_ordered_types_list():\n    def _sequence_preserving_uniq(seq):\n        seen = set()\n        return [x for x in seq if not (x in seen or seen.add(x))]\n\n    msg_subclasses = [v for v in sys.modules[__name__].__dict__.values()\n                      if isinstance(v, type) and issubclass(v, Lwm2mMsg)]\n\n    # for each Lwm2mMsg subclass, list the subclass and all its base classes\n    # up to and including Lwm2mMsg\n    # make sure that bases are before subclasses\n    types = []\n    for cls in msg_subclasses:\n        types += [base for base in reversed(cls.mro())\n                  if issubclass(base, Lwm2mMsg)]\n\n    # leave only the first occurrence of every class\n    # reverse the result so that subclasses are always first\n    ordered_types = list(reversed(_sequence_preserving_uniq(types)))\n\n    # sanity check: for any class in ORDERED_TYPES all its subclasses are\n    # BEFORE it on the list\n    for left_idx, left in enumerate(ordered_types):\n        for right in ordered_types[left_idx + 1:]:\n            assert not issubclass(right, left)\n\n    return ordered_types\n\n\nTYPES = _get_ordered_types_list()\n\n\ndef get_lwm2m_msg(pkt: coap.Packet):\n    for t in TYPES:\n        try:\n            return t.from_packet(pkt)\n        except TypeError:\n            pass\n\n    raise ValueError('should never happen')\n"
  },
  {
    "path": "tools/test-framework-tools/tools/framework_tools/lwm2m/objlink.py",
    "content": "# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nclass Objlink:\n    def __init__(self, ObjID, ObjInstID):\n        self.ObjID = ObjID\n        self.ObjInstID = ObjInstID\n\n    def __str__(self):\n        return '%d:%d' % (self.ObjID, self.ObjInstID)\n"
  },
  {
    "path": "tools/test-framework-tools/tools/framework_tools/lwm2m/path.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nfrom . import coap\n\n\nclass CoapPath(object):\n    def __init__(self, text):\n        if not text.startswith('/'):\n            raise ValueError('not a valid CoAP path: %s' % (text,))\n\n        if text == '/':\n            self.segments = []\n        else:\n            self.segments = text[1:].split('/')\n\n    def __str__(self):\n        return '/' + '/'.join(self.segments)\n\n    def __repr__(self):\n        return '%s(\\'%s\\')' % (self.__class__.__name__, str(self))\n\n    def to_uri_options(self, opt=coap.Option.URI_PATH):\n        return [opt(str(segment)) for segment in self.segments]\n\n\nclass Lwm2mPath(CoapPath):\n    def __init__(self, text):\n        if not text.startswith('/'):\n            raise ValueError('not a valid LwM2M path: %s' % (text,))\n\n        self.numeric_segment_offset = 0\n        if text.count('/') > 1:\n            prefix, path = text[1:].split('/', maxsplit=1)\n\n            # Try to detect if the first segement contains a non-numerical value\n            if not prefix.isdigit() and prefix != \"\":\n                self.numeric_segment_offset = 1\n\n        super().__init__(text)\n\n        if len(self.segments) > 4 + self.numeric_segment_offset:\n            raise ValueError(\n                'LwM2M path must not have more than 4 numeric segments and 1 non-numeric prefix')\n\n        for segment in self.segments[self.numeric_segment_offset:]:\n            try:\n                int(segment)\n            except ValueError as e:\n                raise ValueError(\n                    'LWM2M path segment is not an integer: %s' % (segment,), e)\n\n    # The first segment can hold either a prefix or a object id, this function\n    # helps extract the object, instance, resource and resource instance id from\n    # the segment list. It is not intended to extract the prefix from the\n    # segment list.\n    def __get_segment_wrapper(self, idx):\n        idx += self.numeric_segment_offset\n        return int(self.segments[idx]) if len(self.segments) > idx else None\n\n    @property\n    def path_prefix(self):\n        return self.segments[0] if self.numeric_segment_offset > 0 else None\n\n    @property\n    def object_id(self):\n        return self.__get_segment_wrapper(0)\n\n    @property\n    def instance_id(self):\n        return self.__get_segment_wrapper(1)\n\n    @property\n    def resource_id(self):\n        return self.__get_segment_wrapper(2)\n\n    @property\n    def resource_instance_id(self):\n        return self.__get_segment_wrapper(3)\n\n\nclass Lwm2mNonemptyPath(Lwm2mPath):\n    def __init__(self, text):\n        super().__init__(text)\n\n        if len(self.segments) == 0 + self.numeric_segment_offset:\n            raise ValueError('this LWM2M path requires at least Object ID')\n\n\nclass Lwm2mObjectPath(Lwm2mNonemptyPath):\n    def __init__(self, text):\n        super().__init__(text)\n\n        if len(self.segments) != 1 + self.numeric_segment_offset:\n            raise ValueError('not a LWM2M Object path: %s' % (text,))\n\n\nclass Lwm2mInstancePath(Lwm2mNonemptyPath):\n    def __init__(self, text):\n        super().__init__(text)\n\n        if len(self.segments) != 2 + self.numeric_segment_offset:\n            raise ValueError('not a LWM2M Instance path: %s' % (text,))\n\n\nclass Lwm2mResourcePath(Lwm2mNonemptyPath):\n    def __init__(self, text):\n        super().__init__(text)\n\n        if len(self.segments) != 3 + self.numeric_segment_offset:\n            raise ValueError('not a LWM2M Resource path: %s' % (text,))\n"
  },
  {
    "path": "tools/test-framework-tools/tools/framework_tools/lwm2m/senml_cbor.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nimport cbor2\nimport collections\nimport enum\nimport textwrap\n\n@enum.unique\nclass SenmlLabel(enum.Enum):\n    BASE_TIME = -3\n    BASE_NAME = -2\n    NAME = 0\n    VALUE = 2\n    STRING = 3\n    BOOL = 4\n    TIME = 6\n    OPAQUE = 8\n    OBJLNK = \"vlo\"\n\n\nclass CborResourceList(list):\n    def __str__(self):\n        \"\"\"\n        A list of CBOR map entries corresponding to resources from the payload.\n        \"\"\"\n        return 'CBOR (%d elements):\\n\\n' % len(self) \\\n                + '\\n'.join(textwrap.indent(str(e), '  ') for e in self)\n\n    def verify_values(self, test, expected_value_map):\n        \"\"\"\n        Verifies if the list contains all entries from EXPECTED_VALUE_MAP.\n        Ignores timestamps.\n\n        Requires passing TEST to make use of assertX().\n        \"\"\"\n        path_value = {}\n        basename = ''\n        for entry in self:\n            basename = entry.get(SenmlLabel.BASE_NAME, basename)\n            name = entry.get(SenmlLabel.NAME, '')\n            for value_type in (SenmlLabel.VALUE,\n                               SenmlLabel.STRING,\n                               SenmlLabel.OPAQUE,\n                               SenmlLabel.BOOL,\n                               SenmlLabel.OBJLNK):\n                if value_type in entry:\n                    path_value[basename + name] = entry[value_type]\n                    break\n\n        for path, value in expected_value_map.items():\n            test.assertIn(path, path_value)\n            test.assertEqual(path_value[path], value)\n\n\nclass CborResource(dict):\n    def __init__(self, value):\n        # Remap integers, or other raw SenML labels to SenmlLabel instances.\n        for k, v in value.items():\n            try:\n                self[SenmlLabel(k)] = v\n            except ValueError:\n                self[k] = v\n\n\nclass CBOR:\n    @staticmethod\n    def parse(data) -> CborResourceList:\n        return CborResourceList(CborResource(r) for r in cbor2.loads(data))\n\n    @staticmethod\n    def serialize(entries) -> bytes:\n        entry_list = []\n        for e in entries:\n            entry = {}\n            for k, v in e.items():\n                if k in SenmlLabel:\n                    entry[k.value] = v\n                else:\n                    entry[k] = v\n            entry_list += [ entry ]\n\n        return cbor2.dumps(entry_list)\n"
  },
  {
    "path": "tools/test-framework-tools/tools/framework_tools/lwm2m/server.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nfrom . import coap\nfrom .messages import get_lwm2m_msg\n\n\nclass Lwm2mServer:\n    def __init__(self, coap_server=None):\n        super().__setattr__('_coap_server', coap_server or coap.Server())\n        self.set_timeout(timeout_s=15)\n\n    def send(self, pkt: coap.Packet):\n        if not isinstance(pkt, coap.Packet):\n            raise ValueError(('pkt is %r, expected coap.Packet; did you forget additional parentheses? ' +\n                             'valid syntax: Lwm2mSomething.matching(pkt)()') % (type(pkt),))\n        self._coap_server.send(pkt.fill_placeholders())\n\n    def recv(self, timeout_s: float = -1, deadline=None, filter=None, peek=False):\n        lwm2m_filter = None\n        if filter is not None:\n            lwm2m_filter = lambda pkt: filter(get_lwm2m_msg(pkt))\n        pkt = self._coap_server.recv(timeout_s=timeout_s, deadline=deadline, filter=lwm2m_filter,\n                                     peek=peek)\n        return get_lwm2m_msg(pkt)\n\n    def __getattr__(self, name):\n        return getattr(self._coap_server, name)\n\n    def __setattr__(self, name, value):\n        return setattr(self._coap_server, name, value)\n\n    def __delattr__(self, name):\n        return delattr(self._coap_server, name)\n"
  },
  {
    "path": "tools/test-framework-tools/tools/framework_tools/lwm2m/tlv.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nimport struct\nimport typing\nfrom textwrap import indent\nfrom .objlink import Objlink\n\n\nclass TLVType:\n    def __init__(self, value):\n        self.value = value\n\n    def __eq__(self, other):\n        if isinstance(other, TLVType):\n            return self.value == other.value\n        else:\n            return self.value == other\n\n    def __str__(self):\n        matching = [name for name, value in TLVType.__dict__.items()\n                    if isinstance(value, TLVType) and value.value == self.value]\n\n        assert len(matching) == 1\n        return matching[0]\n\n    def __hash__(self):\n        return hash(str(self))\n\n\nTLVType.INSTANCE = TLVType(0)\nTLVType.RESOURCE_INSTANCE = TLVType(1)\nTLVType.MULTIPLE_RESOURCE = TLVType(2)\nTLVType.RESOURCE = TLVType(3)\n\n\nclass TLVList(list):\n    def __str__(self):\n        \"\"\"\n        A list of TLVs used as a result of TLV.parse, with a custom __str__ function\n        for convenience.\n        \"\"\"\n        return 'TLV (%d elements):\\n\\n' % len(self) + indent('\\n'.join(x.full_description() for x in self), '  ')\n\n\nclass TLV:\n    class BytesDispenser:\n        def __init__(self, data):\n            self.data = data\n            self.at = 0\n\n        def take(self, n):\n            if self.at + n > len(self.data):\n                raise IndexError('attempted to take %d bytes, but only %d available'\n                                 % (n, len(self.data) - self.at))\n\n            self.at += n\n            return self.data[self.at - n:self.at]\n\n        def bytes_remaining(self):\n            return len(self.data) - self.at\n\n    @staticmethod\n    def encode_int(data):\n        value = int(data)\n        for n, modifier in zip([8, 16, 32, 64], ['b', 'h', 'i', 'q']):\n            if -2 ** (n - 1) <= value < 2 ** (n - 1):\n                return struct.pack('>%s' % modifier, value)\n\n        raise NotImplementedError(\"integer out of supported range\")\n\n    @staticmethod\n    def encode_double(data):\n        return struct.pack('>d', float(data))\n\n    @staticmethod\n    def encode_float(data):\n        return struct.pack('>f', float(data))\n\n    @staticmethod\n    def encode_objlink(data: Objlink):\n        return struct.pack('>HH', data.ObjID, data.ObjInstID)\n\n    @staticmethod\n    def make_instance(instance_id: int,\n                      content: typing.Iterable['TLV'] = None):\n        \"\"\"\n        Creates an Object Instance TLV.\n        instance_id -- ID of the Object Instance\n        resources   -- serialized list of TLV resources\n        \"\"\"\n        return TLV(TLVType.INSTANCE, instance_id, content or [])\n\n    @staticmethod\n    def _encode_resource_value(content: int or float or str or bytes or Objlink):\n        if isinstance(content, int):\n            content = TLV.encode_int(content)\n        elif isinstance(content, float):\n            as_float = TLV.encode_float(content)\n            if struct.unpack('>f', as_float)[0] == content:\n                content = as_float  # single precision is enough\n            else:\n                content = TLV.encode_double(content)\n        elif isinstance(content, str):\n            content = content.encode('ascii')\n        elif isinstance(content, Objlink):\n            content = TLV.encode_objlink(content)\n\n        if not isinstance(content, bytes):\n            raise ValueError('Unsupported resource value type: ' + type(content).__name__)\n\n        return content\n\n    @staticmethod\n    def make_resource(resource_id: int,\n                      content: int or float or str or bytes):\n        \"\"\"\n        Creates a Resource TLV.\n        resource_id -- ID of the Resource\n        content     -- Resource content. If an integer is passed, its U2-encoded\n                       form is used. Strings are ASCII-encoded.\n        \"\"\"\n        return TLV(TLVType.RESOURCE, resource_id, TLV._encode_resource_value(content))\n\n    @staticmethod\n    def make_multires(resource_id, instances):\n        \"\"\"\n        Encodes Multiple Resource Instances and their values in TLV\n\n        resource_id -- ID of Resource to be encoded\n        instances   -- list of tuples, each of form (Resource Instance ID, Value)\n        \"\"\"\n        children = []\n        for riid, value in instances:\n            children.append(TLV(TLVType.RESOURCE_INSTANCE, int(riid),\n                                TLV._encode_resource_value(value)))\n        return TLV(TLVType.MULTIPLE_RESOURCE, int(resource_id), children)\n\n    @staticmethod\n    def _parse_internal(data):\n        type_byte, = struct.unpack('!B', data.take(1))\n\n        tlv_type = TLVType((type_byte >> 6) & 0b11)\n        id_field_size = (type_byte >> 5) & 0b1\n        length_field_size = (type_byte >> 3) & 0b11\n\n        identifier_bits = b'\\x00' + data.take(1 + id_field_size)\n        identifier, = struct.unpack('!H', identifier_bits[-2:])\n\n        if length_field_size == 0:\n            length = type_byte & 0b111\n        else:\n            length_bits = b'\\x00' * 3 + data.take(length_field_size)\n            length, = struct.unpack('!I', length_bits[-4:])\n\n        if tlv_type == TLVType.RESOURCE:\n            return TLV(tlv_type, identifier, data.take(length))\n        elif tlv_type == TLVType.RESOURCE_INSTANCE:\n            return TLV(tlv_type, identifier, data.take(length))\n        elif tlv_type == TLVType.MULTIPLE_RESOURCE:\n            res_instances = []\n            data = TLV.BytesDispenser(data.take(length))\n            while data.bytes_remaining() > 0:\n                res_instances.append(TLV._parse_internal(data))\n\n            if not all(x.tlv_type == TLVType.RESOURCE_INSTANCE for x in res_instances):\n                raise ValueError('not all parsed objects are Resource Instances')\n\n            return TLV(tlv_type, identifier, res_instances)\n        elif tlv_type == TLVType.INSTANCE:\n            resources = []\n            data = TLV.BytesDispenser(data.take(length))\n            while data.bytes_remaining() > 0:\n                resources.append(TLV._parse_internal(data))\n\n            if data.bytes_remaining() > 0:\n                raise ValueError('stray bytes at end of data')\n\n            if not all(x.tlv_type in (TLVType.RESOURCE, TLVType.MULTIPLE_RESOURCE) for x in resources):\n                raise ValueError('not all parsed objects are Resources')\n\n            return TLV(tlv_type, identifier, resources)\n\n    @staticmethod\n    def parse(data) -> TLVList:\n        data = TLV.BytesDispenser(data)\n\n        result = TLVList()\n        while data.bytes_remaining() > 0:\n            result.append(TLV._parse_internal(data))\n        return result\n\n    def __init__(self, tlv_type, identifier, value):\n        self.tlv_type = tlv_type\n        self.identifier = identifier\n        self.value = value\n\n    def serialize(self):\n        if self.tlv_type in (TLVType.RESOURCE, TLVType.RESOURCE_INSTANCE):\n            data = self.value\n        else:\n            data = b''.join(x.serialize() for x in self.value)\n\n        type_field = (self.tlv_type.value << 6)\n\n        id_bytes = b''\n        if self.identifier < 2 ** 8:\n            id_bytes = struct.pack('!B', self.identifier)\n        else:\n            assert self.identifier < 2 ** 16\n            type_field |= 0b100000\n            id_bytes = struct.pack('!H', self.identifier)\n\n        len_bytes = b''\n        if len(data) < 8:\n            type_field |= len(data)\n        elif len(data) < 2 ** 8:\n            type_field |= 0b01000\n            len_bytes = struct.pack('!B', len(data))\n        elif len(data) < 2 ** 16:\n            type_field |= 0b10000\n            len_bytes = struct.pack('!H', len(data))\n        else:\n            assert len(data) < 2 ** 24\n            type_field |= 0b11000\n            len_bytes = struct.pack('!I', len(data))[1:]\n\n        return struct.pack('!B', type_field) + id_bytes + len_bytes + data\n\n    def _get_resource_value(self):\n        assert self.tlv_type in (TLVType.RESOURCE, TLVType.RESOURCE_INSTANCE)\n\n        value = str(self.value)\n\n        if len(self.value) <= 8:\n            value += ' (int: %d' % struct.unpack('>Q', (bytes(8) + self.value)[-8:])[0]\n\n            if len(self.value) == 4:\n                value += ', float: %f' % struct.unpack('>f', self.value)[0]\n                value += ', objlink: %d:%d' % struct.unpack('>HH', self.value)\n            elif len(self.value) == 8:\n                value += ', double: %f' % struct.unpack('>d', self.value)[0]\n\n            value += ')'\n\n        return value\n\n    def __str__(self):\n        if self.tlv_type == TLVType.INSTANCE:\n            return 'instance %d (%d resources)' % (self.identifier, len(self.value))\n        elif self.tlv_type == TLVType.MULTIPLE_RESOURCE:\n            return 'multiple resource %d (%d instances)' % (self.identifier, len(self.value))\n        elif self.tlv_type == TLVType.RESOURCE_INSTANCE:\n            return 'resource instance %d = %s' % (self.identifier, self._get_resource_value())\n        elif self.tlv_type == TLVType.RESOURCE:\n            return 'resource %d = %s' % (self.identifier, self._get_resource_value())\n\n    def to_string_without_id(self):\n        if self.tlv_type == TLVType.INSTANCE:\n            return 'instance (%d resources)' % len(self.value)\n        elif self.tlv_type == TLVType.MULTIPLE_RESOURCE:\n            return 'multiple resource (%d instances)' % len(self.value)\n        elif self.tlv_type == TLVType.RESOURCE_INSTANCE:\n            return 'resource instance = %s' % self._get_resource_value()\n        elif self.tlv_type == TLVType.RESOURCE:\n            return 'resource = %s' % self._get_resource_value()\n\n    def __eq__(self, other):\n        return (isinstance(other, TLV)\n                and self.tlv_type == other.tlv_type\n                and self.identifier == other.identifier\n                and self.value == other.value)\n\n    def full_description(self):\n        if self.tlv_type == TLVType.INSTANCE:\n            return ('instance %d (%d resources)\\n%s'\n                    % (self.identifier, len(self.value),\n                       indent('\\n'.join(x.full_description() for x in self.value), '  ')))\n        elif self.tlv_type == TLVType.MULTIPLE_RESOURCE:\n            return ('multiple resource %d (%d instances)\\n%s'\n                    % (self.identifier, len(self.value),\n                       indent('\\n'.join(x.full_description() for x in self.value), '  ')))\n        else:\n            return str(self)\n"
  },
  {
    "path": "tools/test-framework-tools/tools/pyproject.toml",
    "content": "# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\n[build-system]\nrequires = [\"setuptools>=68\", \"wheel\"]\nbuild-backend = \"setuptools.build_meta\"\n\n[project]\nname = \"framework-tools\"\nversion = \"1.0.0\"\n\n[tool.setuptools]\npackage-dir = {\"\" = \".\"}\n\n[tool.setuptools.packages.find]\nwhere = [\".\"]\ninclude = [\"framework_tools*\"]\n"
  },
  {
    "path": "tools/test_duplicates.py",
    "content": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nimport collections\nimport re\nimport sys\n\n\ndef find_closing_bracket(s, start_index, start='(', end=')'):\n    def find_closing_quote(s, quote_char, start_index):\n        i = start_index\n        while i < len(s):\n            if s[i] == quote_char:\n                return i\n            elif s[i] == '\\\\':\n                i += 1\n            i += 1\n        return None\n\n    par_depth = 0\n    i = start_index\n    while i < len(s):\n        if s[i] == start:\n            par_depth += 1\n        elif s[i] == end:\n            if par_depth == 0:\n                return i\n            else:\n                par_depth -= 1\n        elif s[i] == '\"' or s[i] == '\\'':\n            i = find_closing_quote(s, s[i], i + 1)\n            if i is None: break\n        i += 1\n    return None\n\n\ndef strip_function_like_macros(s, replace_with):\n    r = re.compile(\"[A-Z][a-zA-Z0-9_]*\\s*\\(\")\n    while True:\n        m = r.search(s)\n        if m is None: break\n        closing = find_closing_bracket(s, m.end())\n        if closing is None: break\n        s = s[:m.start()] + replace_with + s[closing + 1:]\n    return s\n\n\ndef strip_gcc_attributes(s):\n    r = re.compile(\"__attribute__\\s*\\(\")\n    while True:\n        m = r.search(s)\n        if m is None: break\n        closing = find_closing_bracket(s, m.end())\n        if closing is None: break\n        s = s[:m.start()] + s[closing + 1:]\n    return s\n\n\ndef strip_curly_blocks(s, replace_with):\n    while True:\n        index = s.find('{')\n        if index < 0: break\n        closing = find_closing_bracket(s, index + 1, '{', '}')\n        if closing is None: break\n        s = s[:index] + replace_with + s[closing + 1:]\n    return s\n\n\ndef extract_function_name(decl):\n    beg = 0\n    while beg < len(decl):\n        opening_paren = decl.index('(', beg)\n        closing_paren = find_closing_bracket(decl, opening_paren + 1)\n        if closing_paren is None:\n            break\n        elif closing_paren == len(decl) - 1:\n            match = re.search('[a-z_][a-zA-Z0-9_]*$', decl[:opening_paren])\n            if match:\n                return match.group()\n        beg = closing_paren + 1\n    return None\n\n\ndef extract_function_names(filename):\n    with open(filename, 'r') as f:\n        contents = f.read()\n    contents = re.sub('\\\\\\s*\\n', ' ', contents)  # join lines on trailing backslash\n    contents = re.sub('/\\*.*?\\*/', '', contents, flags=re.DOTALL)  # remove block comments\n    contents = re.sub('//.*$', '', contents, flags=re.MULTILINE)  # remove line comments\n    contents = re.sub('^\\s*#.*$', '', contents, flags=re.MULTILINE)  # remove preprocessor directives\n    contents = strip_function_like_macros(contents, 'MACRO')\n    contents = strip_gcc_attributes(contents)\n    contents = re.sub('extern\\s*\"C\"\\s*\\{', '', contents)  # remove extern \"C\" qualifiers\n    contents = strip_curly_blocks(contents, ';')\n    contents = re.sub('\\s+', ' ', contents)  # replace all whitespace (including newlines with single spaces\n    declarations = contents.split(';')\n    declarations = (item.strip() for item in declarations if 'typedef ' not in item)\n    declarations = filter(lambda item: item.endswith(')'), declarations)\n    declarations = (extract_function_name(decl) for decl in declarations)\n    return filter(lambda item: item is not None, declarations)\n\n\nfunction_files = collections.defaultdict(lambda: set())\nfor file in sys.argv[1:]:\n    for function in extract_function_names(file):\n        function_files[function].add(file)\n\nresult = 0\nfor function in function_files:\n    if len(function_files[function]) > 1:\n        result = -1\n        sys.stderr.write('Function {} declared {} times:\\n'.format(function, len(function_files[function])))\n        for file in function_files[function]:\n            sys.stderr.write('  > ' + file + '\\n')\nsys.exit(result)\n"
  },
  {
    "path": "tools/test_ghactions.py",
    "content": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n#\n# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nimport argparse\nimport logging\nimport multiprocessing\nimport os\nimport re\nimport subprocess\nimport tempfile\nimport yaml\nimport copy\n\n\ndef apply_substitution(data, pattern, repl):\n    if isinstance(data, dict):\n        keys = data.keys()\n    else:\n        keys = range(len(data))\n    for key in keys:\n        if isinstance(data[key], str):\n            data[key] = re.sub(pattern, repl, data[key])\n        else:\n            apply_substitution(data[key], pattern, repl)\n\n\ndef validate_job(job):\n    if not set(job.keys()).issubset({'container', 'env', 'name', 'runs-on', 'steps'}):\n        raise NotImplementedError('Unsupported job features used')\n\n    if 'container' not in job:\n        raise NotImplementedError('Container not specified')\n\n    checkout_steps = 0\n    for step in job['steps']:\n        if step == {'uses': 'actions/checkout@v1', 'with': {'submodules': 'recursive'}}:\n            checkout_steps += 1\n        elif step.keys() != {'run'}:\n            raise NotImplementedError('Unsupported step type')\n    if checkout_steps != 1:\n        raise NotImplementedError('There needs to be a single checkout step')\n\n\ndef enumerate_jobs(yaml):\n    pattern = re.compile(r'\\$\\{\\{\\s*matrix\\.\\w+\\s*\\}\\}')\n    jobs = yaml.get('jobs') or {}\n    for job_name, job in jobs.items():\n        if 'strategy' not in job:\n            # just a single job\n            yield {'name': job_name, **job}\n            continue\n\n        # jobs with no containers/images (e.g. 'macos-*' jobs) are not supported\n        if 'container' not in job:\n            continue\n\n        # fail-fast option is not supported\n        if 'fail-fast' in job['strategy']:\n            del job['strategy']['fail-fast']\n\n        if job['strategy'].keys() != {'matrix'} or job['strategy']['matrix'].keys() != {'include'}:\n            raise NotImplementedError('Unsupported strategy configuration')\n\n        for matrix_entry in job['strategy']['matrix']['include']:\n            copied_job = copy.deepcopy(job)\n            del copied_job['strategy']\n\n            for sub_key in matrix_entry.keys():\n                apply_substitution(copied_job, r'\\${{ *matrix\\.' + sub_key + r' *}}',\n                                   matrix_entry[sub_key])\n\n            unsubstituted_matrix_env_vars = [\n                key for key, value in copied_job['env'].items() if pattern.fullmatch(value)]\n            for key in unsubstituted_matrix_env_vars:\n                del copied_job['env'][key]\n\n            yield {'name': '%s %r' % (job_name, matrix_entry), **copied_job}\n\n\ndef run_job(job, root_dir):\n    DOCKER_ROOT_DIR = '/test_ghactions_root'\n    DOCKER_SCRIPT_DIR = '/test_ghactions_script'\n\n    validate_job(job)\n    with tempfile.TemporaryDirectory() as temp_dir:\n        with open(os.path.join(temp_dir, 'run.sh'), 'w') as script:\n            script.write('#!/bin/sh\\n')\n            script.write('set -e\\n')\n            script.write('TEMP_DIR=\"$(mktemp -d)\"\\n')\n            script.write('cp -a ' + DOCKER_ROOT_DIR + ' \"$TEMP_DIR\"\\n')\n            script.write('cd \"$TEMP_DIR\"/' +\n                         os.path.basename(DOCKER_ROOT_DIR) + '\\n')\n            for step in job['steps']:\n                if step.keys() == {'run'}:\n                    script.write(step['run'] + '\\n')\n            script.flush()\n            os.chmod(os.path.join(temp_dir, 'run.sh'), 0o777)\n\n            command = [\n                'docker', 'run', '--rm', '--mount',\n                'type=bind,source=' +\n                os.path.realpath(root_dir) + ',target=' + DOCKER_ROOT_DIR,\n                '--mount', 'type=bind,source=' + temp_dir + ',target=' + DOCKER_SCRIPT_DIR]\n            env = job.get('env') or {}\n            for env_key, env_value in env.items():\n                command += ['--env', env_key + '=' + env_value]\n            command.append(job['container'])\n            command.append(DOCKER_SCRIPT_DIR + '/run.sh')\n\n        logging.log(logging.INFO, 'Running %s: %r', job['name'], command)\n        subprocess.check_call(command)\n\n\ndef _main():\n    parser = argparse.ArgumentParser(\n        'Runs .github/workflows/anjay-tests.yml using Docker.')\n    parser.add_argument('-r', '--root-dir', type=str, help='Root directory of Anjay repo',\n                        required=True)\n    args = parser.parse_args()\n\n    with open(os.path.join(args.root_dir, '.github', 'workflows', 'anjay-tests.yml'), 'r') as f:\n        parsed = yaml.safe_load(f)\n\n    logging.basicConfig(level=logging.NOTSET)\n    jobs = list(enumerate_jobs(parsed))\n\n    for job in jobs:\n        to_be_replaced = 'make check'\n        replacing = 'make -j{} anjay_check avs_commons_check avs_coap_check examples'.format(\n            multiprocessing.cpu_count())\n        apply_substitution(job, to_be_replaced, replacing)\n    for job in jobs:\n        run_job(job, args.root_dir)\n\n\nif __name__ == '__main__':\n    _main()\n"
  },
  {
    "path": "tools/utils.sh",
    "content": "# Copyright 2017-2026 AVSystem <avsystem@avsystem.com>\n# AVSystem Anjay LwM2M SDK\n# All rights reserved.\n#\n# Licensed under AVSystem Anjay LwM2M Client SDK - Non-Commercial License.\n# See the attached LICENSE file for details.\n\nlog() {\n    local FG_BOLD=\"\\033[1m\"\n    local FG_DEFAULT=\"\\033[0m\"\n\n    echo -e \"$(date) ${FG_BOLD}$0: ${FUNCNAME[1]}:${FG_DEFAULT} $@\" >&2\n}\n\nwarn() {\n    local COL_YELLOW=\"\\033[33m\"\n    local COL_DEFAULT=\"\\033[0m\"\n\n    log \"${COL_YELLOW}$@${COL_DEFAULT}\"\n}\n\ndie() {\n    local COL_RED=\"\\033[31m\"\n    local COL_YELLOW=\"\\033[33m\"\n    local COL_DEFAULT=\"\\033[0m\"\n\n    echo -ne \"$COL_RED\"\n    log \"$@\"\n\n    read LINE FUNC FILE <<<$(caller 0)\n    echo -e \"${COL_YELLOW}at $FILE:$LINE ($FUNC)$COL_DEFAULT\"\n\n    local FRAME=1\n    while [[ $FRAME -le 10 ]]; do\n        if ! caller $FRAME >/dev/null; then\n            exit 1\n        fi\n\n        read LINE FUNC FILE <<<$(caller $FRAME)\n        echo -e \"$COL_YELLOW  called from $FILE:$LINE ($FUNC)$COL_DEFAULT\"\n        ((FRAME++))\n    done\n\n    echo -e \"$COL_YELLOW  (more frames follow)\"\n    exit 1\n}\n\ncanonicalize() {\n    echo \"$(cd \"$(dirname \"$1\")\" && pwd -P)/$(basename \"$1\")\"\n}\n\nnum_processors() {\n    if command -v nproc > /dev/null; then\n        nproc\n    else\n        sysctl -n hw.ncpu\n    fi\n}\n\nif [[ ! \"$ATEXIT_SETUP\" ]]; then\n    ATEXIT_SETUP=1\n    ATEXIT_SCHEDULED=()\n\n    atexit() {\n        for EXPR in \"$@\"; do\n            ATEXIT_SCHEDULED+=(\"$@\")\n        done\n    }\n\n    atexit_handler() {\n        for CMD in \"${ATEXIT_SCHEDULED[@]}\"; do\n            log \"$CMD\"\n\n            # when `set -e` is used, any error causes the script to exit.\n            # trap EXIT handlers are still executed, but if any command\n            # inside one fails as well, the handler will be terminated too.\n            # `|| true` below ensures that failure of one scheduled command\n            # does not prevent other ones from executing.\n            eval \"$CMD\" || true\n        done\n\n        ATEXIT_SCHEDULED=()\n    }\n\n    [[ \"$(trap -p EXIT)\" ]] && die \"trap EXIT already set\"\n\n    trap atexit_handler EXIT\nfi\n"
  },
  {
    "path": "valgrind_test.supp",
    "content": "{\n   getaddrinfo-globalstate\n   Memcheck:Leak\n   fun:malloc\n   fun:*\n   fun:*\n   fun:*\n   fun:*\n   fun:gaih_inet*\n   fun:getaddrinfo\n}\n{\n   getaddrinfo-invalidfree\n   Memcheck:Free\n   fun:free\n   fun:__libc_freeres\n   ...\n   fun:exit\n}\n{\n   ssl_handshake-value\n   Memcheck:Value8\n   ...\n   obj:*/libssl.so.*\n   fun:ssl_handshake\n}\n{\n   ssl_handshake-cond\n   Memcheck:Cond\n   ...\n   obj:*/libssl.so.*\n   fun:ssl_handshake\n}\n{\n   libcrypto-value\n   Memcheck:Value8\n   obj:*/libcrypto.so.*\n}\n{\n   libssl-value\n   Memcheck:Value8\n   obj:*/libssl.so.*\n}\n{\n   libcrypto-cond\n   Memcheck:Cond\n   obj:*/libcrypto.so.*\n}\n{\n   libssl-cond\n   Memcheck:Cond\n   obj:*/libssl.so.*\n}\n{\n   sendto-argument\n   Memcheck:Param\n   socketcall.sendto(msg)\n   ...\n   fun:BIO_write\n   ...\n   fun:ssl_handshake\n}\n{\n    recv\n    Memcheck:Cond\n    obj:*/vgpreload_memcheck*\n    obj:*/libssl.so.*\n    ...\n    obj:*/libssl.so.*\n    fun:receive_ssl\n}\n{\n   engine-private-key\n   Memcheck:Leak\n   fun:malloc\n   ...\n   fun:ENGINE_load_private_key\n   fun:_avs_crypto_openssl_engine_load_private_key\n}\n{\n   engine-cert\n   Memcheck:Leak\n   fun:malloc\n   ...\n   fun:ENGINE_ctrl_cmd\n   fun:_avs_crypto_openssl_engine_load_certs\n}\n{\n   engine-new\n   Memcheck:Leak\n   fun:malloc\n   ...\n   fun:ENGINE_new\n}\n"
  }
]